mirror of
https://github.com/taigrr/gopher-os
synced 2025-01-18 04:43:13 -08:00
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.)
This commit is contained in:
parent
718006f4e4
commit
63c69fe8d3
@ -131,6 +131,24 @@ func (vm *VM) Visit(entType EntityType, visitorFn Visitor) {
|
||||
scopeVisit(0, vm.rootNS, entType, visitorFn)
|
||||
}
|
||||
|
||||
// execBlock attempts to execute all AML opcodes in the supplied scoped entity.
|
||||
// If all opcodes are successfully executed, the provided execContext will be
|
||||
// updated to reflect the current VM state. Otherwise, an error will be
|
||||
// returned.
|
||||
func (vm *VM) execBlock(ctx *execContext, block ScopeEntity) *Error {
|
||||
instrList := block.Children()
|
||||
numInstr := len(instrList)
|
||||
|
||||
for instrIndex := 0; instrIndex < numInstr && ctx.ctrlFlow == ctrlFlowTypeNextOpcode; instrIndex++ {
|
||||
instr := instrList[instrIndex]
|
||||
if err := vm.jumpTable[instr.getOpcode()](ctx, instr); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// defaultACPIScopes constructs a tree of scoped entities that correspond to
|
||||
// the predefined scopes contained in the ACPI specification and returns back
|
||||
// its root node.
|
||||
|
@ -94,3 +94,177 @@ func TestVMVisit(t *testing.T) {
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user