diff --git a/src/gopheros/device/acpi/aml/vm.go b/src/gopheros/device/acpi/aml/vm.go index 5d6a12b..5ef0d66 100644 --- a/src/gopheros/device/acpi/aml/vm.go +++ b/src/gopheros/device/acpi/aml/vm.go @@ -20,6 +20,9 @@ var ( errArgIndexOutOfBounds = &Error{message: "vm: arg index out of bounds"} errDivideByZero = &Error{message: "vm: division by zero"} errInvalidComparisonType = &Error{message: "vm: logic opcodes can only be applied to Integer, String or Buffer arguments"} + errWhileBodyNotScopedEntity = &Error{message: "vmOpWHile: Wihile body must be a scoped entity"} + errIfBodyNotScopedEntity = &Error{message: "vmOpIf: If body must be a scoped entity"} + errElseBodyNotScopedEntity = &Error{message: "vmOpIf: Else body must be a scoped entity"} ) // objRef is a pointer to an argument (local or global) or a named AML object. diff --git a/src/gopheros/device/acpi/aml/vm_jumptable.go b/src/gopheros/device/acpi/aml/vm_jumptable.go index f40fada..fa082a7 100644 --- a/src/gopheros/device/acpi/aml/vm_jumptable.go +++ b/src/gopheros/device/acpi/aml/vm_jumptable.go @@ -12,6 +12,10 @@ func (vm *VM) populateJumpTable() { // Control-flow opcodes vm.jumpTable[opReturn] = vmOpReturn + vm.jumpTable[opBreak] = vmOpBreak + vm.jumpTable[opContinue] = vmOpContinue + vm.jumpTable[opWhile] = vmOpWhile + vm.jumpTable[opIf] = vmOpIf // ALU opcodes vm.jumpTable[opAdd] = vmOpAdd diff --git a/src/gopheros/device/acpi/aml/vm_op_flow.go b/src/gopheros/device/acpi/aml/vm_op_flow.go index 1782381..eac20ea 100644 --- a/src/gopheros/device/acpi/aml/vm_op_flow.go +++ b/src/gopheros/device/acpi/aml/vm_op_flow.go @@ -14,3 +14,106 @@ func vmOpReturn(ctx *execContext, ent Entity) *Error { ctx.retVal, err = vmLoad(ctx, args[0]) return err } + +func vmOpBreak(ctx *execContext, ent Entity) *Error { + ctx.ctrlFlow = ctrlFlowTypeBreak + return nil +} + +func vmOpContinue(ctx *execContext, ent Entity) *Error { + ctx.ctrlFlow = ctrlFlowTypeContinue + return nil +} + +// Args: Predicate {TermList} +// Execute the scoped termlist block until predicate evaluates to false or any +// of the instructions in the TermList changes the control flow to break or +// return. +func vmOpWhile(ctx *execContext, ent Entity) *Error { + var ( + predRes interface{} + err *Error + whileBlock ScopeEntity + isScopedEnt bool + args = ent.getArgs() + argLen = len(args) + ) + + if argLen != 2 { + return errArgIndexOutOfBounds + } + + if whileBlock, isScopedEnt = args[1].(ScopeEntity); !isScopedEnt { + return errWhileBodyNotScopedEntity + } + + for err == nil { + if predRes, err = vmLoad(ctx, args[0]); err != nil { + continue + } + + if predResAsUint, isUint := predRes.(uint64); !isUint || predResAsUint != 1 { + break + } + + err = ctx.vm.execBlock(ctx, whileBlock) + if ctx.ctrlFlow == ctrlFlowTypeFnReturn { + // Preserve return flow type so we exit the innermost function + break + } else if ctx.ctrlFlow == ctrlFlowTypeBreak { + // Exit while block and switch to sequential execution for the code + // that follows + ctx.ctrlFlow = ctrlFlowTypeNextOpcode + break + } + + // Restart while block but reset to sequential execution so the predicate + // and while body can be properly evaluated + ctx.ctrlFlow = ctrlFlowTypeNextOpcode + } + + return err +} + +// Args: Predicate {Pred == true TermList} {Pref == false TermList}? +// +// Execute the scoped term list if predicate evaluates to true; If predicate +// evaluates to false and the optional else block is defined then it will be +// executed instead. +func vmOpIf(ctx *execContext, ent Entity) *Error { + var ( + predRes interface{} + err *Error + ifBlock, elseBlock ScopeEntity + isScopedEnt bool + args = ent.getArgs() + argLen = len(args) + ) + + if argLen < 2 || argLen > 3 { + return errArgIndexOutOfBounds + } + + if ifBlock, isScopedEnt = args[1].(ScopeEntity); !isScopedEnt { + return errIfBodyNotScopedEntity + } + + // Check for the optional else block + if argLen == 3 { + if elseBlock, isScopedEnt = args[2].(ScopeEntity); !isScopedEnt { + return errElseBodyNotScopedEntity + } + } + + if predRes, err = vmLoad(ctx, args[0]); err != nil { + return err + } + + if predResAsUint, isUint := predRes.(uint64); !isUint || predResAsUint == 1 { + return ctx.vm.execBlock(ctx, ifBlock) + } else if elseBlock != nil { + return ctx.vm.execBlock(ctx, elseBlock) + } + + return nil +} diff --git a/src/gopheros/device/acpi/aml/vm_op_flow_test.go b/src/gopheros/device/acpi/aml/vm_op_flow_test.go index 304f509..7005fb7 100644 --- a/src/gopheros/device/acpi/aml/vm_op_flow_test.go +++ b/src/gopheros/device/acpi/aml/vm_op_flow_test.go @@ -1,6 +1,10 @@ package aml -import "testing" +import ( + "os" + "reflect" + "testing" +) func TestFlowExpressionErrors(t *testing.T) { t.Run("opReturn errors", func(t *testing.T) { @@ -10,3 +14,176 @@ func TestFlowExpressionErrors(t *testing.T) { } }) } + +func TestVMFlowChanges(t *testing.T) { + resolver := &mockResolver{ + tableFiles: []string{"vm-testsuite-DSDT.aml"}, + } + + vm := NewVM(os.Stderr, resolver) + if err := vm.Init(); err != nil { + t.Fatal(err) + } + + specs := []struct { + method string + inputA, inputB interface{} + exp interface{} + }{ + {`\FL00`, uint64(0), "sequential", uint64(8)}, + {`\FL00`, uint64(42), "sequential", uint64(16)}, + {`\FL00`, uint64(100), "sequential", uint64(32)}, + {`\FL00`, uint64(999), "break", uint64(1)}, + {`\FL00`, uint64(42), "continue", uint64(0)}, + {`\FL00`, uint64(42), "return", uint64(0xbadf00d)}, + } + + for specIndex, spec := range specs { + m := vm.Lookup(spec.method) + if m == nil { + t.Errorf("error looking up method: %q", spec.method) + continue + } + + method := m.(*Method) + + ctx := &execContext{ + methodArg: [maxMethodArgs]interface{}{spec.inputA, spec.inputB}, + vm: vm, + } + + if err := vm.execBlock(ctx, method); err != nil { + t.Errorf("[spec %02d] %s: invocation failed: %v\n", specIndex, spec.method, err) + continue + } + + if !reflect.DeepEqual(ctx.retVal, spec.exp) { + t.Errorf("[spec %02d] %s: expected %d; got %v\n", specIndex, spec.method, spec.exp, ctx.retVal) + } + } +} + +func TestVMFlowOpErrors(t *testing.T) { + op0Err := &Error{message: "something went wrong with op 0"} + op1Err := &Error{message: "something went wrong with op 1"} + op2Err := &Error{message: "something went wrong with op 2"} + + vm := &VM{sizeOfIntInBits: 64} + vm.populateJumpTable() + vm.jumpTable[0] = func(_ *execContext, ent Entity) *Error { return op0Err } + vm.jumpTable[1] = func(_ *execContext, ent Entity) *Error { return op1Err } + vm.jumpTable[2] = func(_ *execContext, ent Entity) *Error { return op2Err } + + specs := []struct { + handler opHandler + entArgs []interface{} + expErr *Error + }{ + // opWhile tests + { + vmOpWhile, + []interface{}{"args < 2"}, + errArgIndexOutOfBounds, + }, + { + vmOpWhile, + []interface{}{ + "foo", + "not a scoped ent", + }, + errWhileBodyNotScopedEntity, + }, + { + vmOpWhile, + []interface{}{ + &unnamedEntity{op: 0}, + &scopeEntity{}, + }, + op0Err, + }, + { + vmOpWhile, + []interface{}{ + uint64(1), + // raise an error while exeuting the body of the while statement + &scopeEntity{ + children: []Entity{ + &unnamedEntity{op: 1}, + }, + }, + }, + op1Err, + }, + // opIf tests + { + vmOpIf, + []interface{}{"args < 2"}, + errArgIndexOutOfBounds, + }, + { + vmOpIf, + []interface{}{"args", ">", "3", "!!!"}, + errArgIndexOutOfBounds, + }, + { + vmOpIf, + []interface{}{ + "foo", + "if body not a scoped ent", + }, + errIfBodyNotScopedEntity, + }, + { + vmOpIf, + []interface{}{ + "foo", + &scopeEntity{}, + "else body not a scoped ent", + }, + errElseBodyNotScopedEntity, + }, + { + vmOpIf, + []interface{}{ + &unnamedEntity{op: 0}, + &scopeEntity{}, + }, + op0Err, + }, + { + vmOpIf, + []interface{}{ + uint64(1), + // raise an error while executing the If body + &scopeEntity{ + children: []Entity{ + &unnamedEntity{op: 1}, + }, + }, + }, + op1Err, + }, + { + vmOpIf, + []interface{}{ + uint64(0), + &scopeEntity{}, + // raise an error while exeuting the Else body + &scopeEntity{ + children: []Entity{ + &unnamedEntity{op: 2}, + }, + }, + }, + op2Err, + }, + } + + ctx := &execContext{vm: vm} + for specIndex, spec := range specs { + ent := &unnamedEntity{args: spec.entArgs} + if err := spec.handler(ctx, ent); err == nil || err.Error() != spec.expErr.Error() { + t.Errorf("[spec %d] expected error: %s; got %v", specIndex, spec.expErr.Error(), err) + } + } +} diff --git a/src/gopheros/device/acpi/table/tabletest/vm-testsuite-DSDT.aml b/src/gopheros/device/acpi/table/tabletest/vm-testsuite-DSDT.aml index dcfa0ea..fb1e009 100644 Binary files a/src/gopheros/device/acpi/table/tabletest/vm-testsuite-DSDT.aml and b/src/gopheros/device/acpi/table/tabletest/vm-testsuite-DSDT.aml differ diff --git a/src/gopheros/device/acpi/table/tabletest/vm-testsuite-DSDT.dsl b/src/gopheros/device/acpi/table/tabletest/vm-testsuite-DSDT.dsl index e965006..86c6761 100644 --- a/src/gopheros/device/acpi/table/tabletest/vm-testsuite-DSDT.dsl +++ b/src/gopheros/device/acpi/table/tabletest/vm-testsuite-DSDT.dsl @@ -204,4 +204,36 @@ DefinitionBlock ("vm-testsuite-DSDT.aml", "DSDT", 2, "GOPHER", "GOPHEROS", 0x000 { Return(!Arg0) } + + // Flow control ops + Method(FL00, 2, NotSerialized) + { + Local0 = 0 + Local1 = 0 + While( Local0 < 8 ) + { + Local0++ + + If(Arg1 == "continue"){ + Continue + } ElseIf(Arg1 == "return"){ + Return(0xbadf00d) + } + + If(Arg0 == 42){ + Local1 += 2 + } ElseIf(Arg0 == 100){ + Local1 += 4 + } Else { + Local1++ + } + + If(Arg1 == "break"){ + Break + } + } + + Return(Local1) + } + }