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:
parent
2be5b9d224
commit
ad6c7ee991
@ -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.
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Binary file not shown.
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user