1
0
mirror of https://github.com/taigrr/gopher-os synced 2025-01-18 04:43:13 -08:00

acpi: add VM-support for while/if/elseif/else blocks

This commit is contained in:
Achilleas Anagnostopoulos 2017-11-26 17:31:14 +00:00
parent 2be5b9d224
commit ad6c7ee991
6 changed files with 320 additions and 1 deletions

View File

@ -20,6 +20,9 @@ var (
errArgIndexOutOfBounds = &Error{message: "vm: arg index out of bounds"} errArgIndexOutOfBounds = &Error{message: "vm: arg index out of bounds"}
errDivideByZero = &Error{message: "vm: division by zero"} errDivideByZero = &Error{message: "vm: division by zero"}
errInvalidComparisonType = &Error{message: "vm: logic opcodes can only be applied to Integer, String or Buffer arguments"} 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. // objRef is a pointer to an argument (local or global) or a named AML object.

View File

@ -12,6 +12,10 @@ func (vm *VM) populateJumpTable() {
// Control-flow opcodes // Control-flow opcodes
vm.jumpTable[opReturn] = vmOpReturn vm.jumpTable[opReturn] = vmOpReturn
vm.jumpTable[opBreak] = vmOpBreak
vm.jumpTable[opContinue] = vmOpContinue
vm.jumpTable[opWhile] = vmOpWhile
vm.jumpTable[opIf] = vmOpIf
// ALU opcodes // ALU opcodes
vm.jumpTable[opAdd] = vmOpAdd vm.jumpTable[opAdd] = vmOpAdd

View File

@ -14,3 +14,106 @@ func vmOpReturn(ctx *execContext, ent Entity) *Error {
ctx.retVal, err = vmLoad(ctx, args[0]) ctx.retVal, err = vmLoad(ctx, args[0])
return err 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
}

View File

@ -1,6 +1,10 @@
package aml package aml
import "testing" import (
"os"
"reflect"
"testing"
)
func TestFlowExpressionErrors(t *testing.T) { func TestFlowExpressionErrors(t *testing.T) {
t.Run("opReturn errors", func(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)
}
}
}

View File

@ -204,4 +204,36 @@ DefinitionBlock ("vm-testsuite-DSDT.aml", "DSDT", 2, "GOPHER", "GOPHEROS", 0x000
{ {
Return(!Arg0) 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)
}
} }