1
0
mirror of https://github.com/taigrr/gopher-os synced 2025-01-18 04:43:13 -08:00
Achilleas Anagnostopoulos 63c69fe8d3 acpi: implement internal VM method for executing AML blocks
The execBlock method supports the various control flows defined by the
ACPI standard (next instr, continue, break and return). It is designed
so that it can be recursively invoked both AML methods and various
flow-altering opcodes (e.g. opIf, opWhile e.t.c.)
2017-12-04 06:35:26 +00:00

271 lines
6.1 KiB
Go

package aml
import (
"os"
"reflect"
"testing"
)
func TestVMInit(t *testing.T) {
t.Run("success", func(t *testing.T) {
resolver := &mockResolver{
tableFiles: []string{"DSDT.aml"},
}
vm := NewVM(os.Stderr, resolver)
if err := vm.Init(); err != nil {
t.Fatal(err)
}
})
t.Run("parse error", func(t *testing.T) {
resolver := &fixedPayloadResolver{
// invalid payload (incomplete opcode)
payload: []byte{extOpPrefix},
}
expErr := &Error{message: errParsingAML.Module + ": " + errParsingAML.Error()}
vm := NewVM(os.Stderr, resolver)
if err := vm.Init(); !reflect.DeepEqual(err, expErr) {
t.Fatalf("expected Init() to return errParsingAML; got %v", err)
}
})
}
func TestVMObjectLookups(t *testing.T) {
resolver := &mockResolver{
tableFiles: []string{"DSDT.aml"},
}
vm := NewVM(os.Stderr, resolver)
if err := vm.Init(); err != nil {
t.Fatal(err)
}
specs := []struct {
absPath string
match bool
}{
{
``,
false,
},
{
`\`,
true,
},
{
`\_SB_.PCI0.SBRG.PIC_`,
true,
},
{
`\_SB_.PCI0.UNKNOWN_PATH`,
false,
},
}
for specIndex, spec := range specs {
foundMatch := vm.Lookup(spec.absPath) != nil
if foundMatch != spec.match {
t.Errorf("[spec %d] expected lookup match status to be %t", specIndex, spec.match)
}
}
}
func TestVMVisit(t *testing.T) {
resolver := &mockResolver{
tableFiles: []string{"parser-testsuite-DSDT.aml"},
}
vm := NewVM(os.Stderr, resolver)
if err := vm.Init(); err != nil {
t.Fatal(err)
}
var (
methodCount int
expCount = 2
)
vm.Visit(EntityTypeMethod, func(_ int, ent Entity) bool {
methodCount++
return true
})
if methodCount != expCount {
t.Fatalf("expected visitor to be invoked for %d methods; got %d", expCount, methodCount)
}
}
func TestVMExecBlockControlFlows(t *testing.T) {
resolver := &mockResolver{}
vm := NewVM(os.Stderr, resolver)
if err := vm.Init(); err != nil {
t.Fatal(err)
}
t.Run("sequential ctrl flow", func(t *testing.T) {
block := &scopeEntity{
children: []Entity{
&unnamedEntity{op: opcode(0)},
&unnamedEntity{op: opcode(0)},
&unnamedEntity{op: opcode(0)},
&unnamedEntity{op: opcode(0)},
},
}
var instrExecCount int
vm.jumpTable[0] = func(ctx *execContext, _ Entity) *Error {
instrExecCount++
return nil
}
ctx := new(execContext)
if err := vm.execBlock(ctx, block); err != nil {
t.Fatal(err)
}
if instrExecCount != len(block.Children()) {
t.Errorf("expected opcode 0 to be executed %d times; got %d", len(block.Children()), instrExecCount)
}
if ctx.ctrlFlow != ctrlFlowTypeNextOpcode {
t.Errorf("expected ctx.ctrlFlow to be %d; got %d", ctrlFlowTypeNextOpcode, ctx.ctrlFlow)
}
})
t.Run("break ctrl flow", func(t *testing.T) {
block := &scopeEntity{
children: []Entity{
&unnamedEntity{op: opcode(0)},
&unnamedEntity{op: opcode(0)},
// These instructions will not be executed due to the break
&unnamedEntity{op: opcode(0)},
&unnamedEntity{op: opcode(0)},
&unnamedEntity{op: opcode(0)},
},
}
var instrExecCount int
vm.jumpTable[0] = func(ctx *execContext, _ Entity) *Error {
instrExecCount++
// Break out of "loop" after the 2nd instruction
if instrExecCount == 2 {
ctx.ctrlFlow = ctrlFlowTypeBreak
}
return nil
}
ctx := new(execContext)
if err := vm.execBlock(ctx, block); err != nil {
t.Fatal(err)
}
if exp := 2; instrExecCount != exp {
t.Errorf("expected opcode 0 to be executed %d times; got %d", exp, instrExecCount)
}
if ctx.ctrlFlow != ctrlFlowTypeBreak {
t.Errorf("expected ctx.ctrlFlow to be %d; got %d", ctrlFlowTypeBreak, ctx.ctrlFlow)
}
})
t.Run("continue ctrl flow", func(t *testing.T) {
block := &scopeEntity{
children: []Entity{
&unnamedEntity{op: opcode(0)},
&unnamedEntity{op: opcode(0)},
&unnamedEntity{op: opcode(0)},
&unnamedEntity{op: opcode(0)},
&unnamedEntity{op: opcode(0)},
// These commands will not be executed
&unnamedEntity{op: opcode(0)},
&unnamedEntity{op: opcode(0)},
},
}
var instrExecCount int
vm.jumpTable[0] = func(ctx *execContext, _ Entity) *Error {
instrExecCount++
// Continue out of "loop" after the 5th instruction run
if instrExecCount == 5 {
ctx.ctrlFlow = ctrlFlowTypeContinue
}
return nil
}
ctx := new(execContext)
if err := vm.execBlock(ctx, block); err != nil {
t.Fatal(err)
}
if exp := 5; instrExecCount != exp {
t.Errorf("expected opcode 0 to be executed %d times; got %d", exp, instrExecCount)
}
if ctx.ctrlFlow != ctrlFlowTypeContinue {
t.Errorf("expected ctx.ctrlFlow to be %d; got %d", ctrlFlowTypeContinue, ctx.ctrlFlow)
}
})
t.Run("return ctrl flow", func(t *testing.T) {
block := &scopeEntity{
children: []Entity{
&unnamedEntity{op: opcode(0)},
&unnamedEntity{op: opcode(0)},
&unnamedEntity{op: opcode(0)},
},
}
var instrExecCount int
vm.jumpTable[0] = func(ctx *execContext, _ Entity) *Error {
instrExecCount++
// return after 2nd instruction execution
if instrExecCount == 2 {
ctx.retVal = "foo"
ctx.ctrlFlow = ctrlFlowTypeFnReturn
}
return nil
}
ctx := new(execContext)
if err := vm.execBlock(ctx, block); err != nil {
t.Fatal(err)
}
if exp := 2; instrExecCount != exp {
t.Errorf("expected opcode 0 to be executed %d times; got %d", exp, instrExecCount)
}
if exp := "foo"; ctx.retVal != exp {
t.Errorf("expected retVal to be %v; got %v", exp, ctx.retVal)
}
// ctrlFlow should remain FnReturn so we can exit any nested
// loops till we reach the invoked function.
if ctx.ctrlFlow != ctrlFlowTypeFnReturn {
t.Errorf("expected ctx.ctrlFlow to be %d; got %d", ctrlFlowTypeFnReturn, ctx.ctrlFlow)
}
})
t.Run("instr exec error", func(t *testing.T) {
block := &scopeEntity{
children: []Entity{
&unnamedEntity{op: opcode(0)},
},
}
vm.jumpTable[0] = opExecNotImplemented
ctx := new(execContext)
expErr := &Error{message: "opcode Zero not implemented"}
if err := vm.execBlock(ctx, block); err == nil || err.Error() != expErr.Error() {
t.Errorf("expected to get error: %v; got: %v", expErr, err)
}
})
}