From 63c69fe8d321bbf7c508925e97b6da51103168ae Mon Sep 17 00:00:00 2001 From: Achilleas Anagnostopoulos Date: Mon, 16 Oct 2017 07:02:00 +0100 Subject: [PATCH] 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.) --- src/gopheros/device/acpi/aml/vm.go | 18 +++ src/gopheros/device/acpi/aml/vm_test.go | 174 ++++++++++++++++++++++++ 2 files changed, 192 insertions(+) diff --git a/src/gopheros/device/acpi/aml/vm.go b/src/gopheros/device/acpi/aml/vm.go index 59d1a94..51f5c40 100644 --- a/src/gopheros/device/acpi/aml/vm.go +++ b/src/gopheros/device/acpi/aml/vm.go @@ -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. diff --git a/src/gopheros/device/acpi/aml/vm_test.go b/src/gopheros/device/acpi/aml/vm_test.go index 0e5fbbc..62b0d4b 100644 --- a/src/gopheros/device/acpi/aml/vm_test.go +++ b/src/gopheros/device/acpi/aml/vm_test.go @@ -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) + } + }) +}