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"}
|
||||
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.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Binary file not shown.
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user