The VM.execBlock method has been converted to a standalone function.
Access to the vm is facilitated via the ctx argument.
The VM will calculate the start and end instruction pointer offsets for
all scoped blocks in a pre-processing pass which is initiated by the
VM.checkEntities call when it encounters a Method entity. As opcodes
may trigger the execution of multiple opcodes (e.g. if/else) or even
mutate the execution flow (e.g. a break inside a while loop) we need
to keep track of the IP offsets in all scoped blocks so the VM can
provide accurate IP values for stack traces.
The stack trace is included as part of the *Error struct and is
populated automatically by execBlock whenever an error occurs. A
convenience Error.StackTrace() method is provided for obtaining
a formatted version of the stack trace as a string.
Each stack trace entry contains information about the method name inside
which an error executed, the table name where the method was defined as
well as the opcode type and IP offset (relative to the method start) where the
error occured. Stack traces are also preserved across method
invocations. An example flow that generates a fatal error is included in
the vm-testsuite-DSDT.dsl file (method \NST2). Calling this method with
the appropriate arguments generates a stack trace that looks like this:
Stack trace:
[000] [DSDT] [NST2():0x2] opcode: Store
[001] [DSDT] [NST3():0x1] opcode: Add
[002] [DSDT] [NST4():0x8] opcode: If
[003] [DSDT] [NST4():0x9] opcode: Fatal
The getOpcode() method of methodInvocationEntity has been patched so
that a non-AML opcode is returned (selected to be lastOpcode + 1). The
jumpTable at position [lastOpcode+1] is populated with a method that
handles the method invocation logic.
Whenever a method invocation is encountered, the interpreter will try to
lazilly resolve the invoked method definition. Then, a new execution
context will be allocated on the stack and the method args will be
automatically populated by resolving (via a vmLoad call) all arguments
of the methodInvocationEntity instance. The interpreter will then invoke
the method using the new context, fetch the result value (new ctx
retVal) and resolve it (via vmLoad) back to value that can be stored in
the original context that triggered the method invocation.
The converter is pretty basic at the moment and it only supports
converting to/from Integer and String types. This commit also includes
some argument => uint64 conversion helpers that will serve as the basis
for implementing the ALU opcodes.
The vmStore implementation does not support storing data to other AML
entities such as Buffers, Fields or Regions. Support for these entities
will be included in a separate commit.
The current vmCopyObject implementation is as simple as possible for
now; only copying of strings and uint64 values is supported. This
matches the behavior of vmLoad.
Both vmLoad/Store functions support reading/writing to/from object
references following the ACPI spec rules about automatic dereferencing.
The helper only supports reading a subset of the available AML operands:
- constants (uint64, strings and bools; bools are auto-casted to uint64)
- local and method args
The implementation will recursively drill down into the operand values
till it reaches a value that can be mapped to a Go uint64 or string
type. For example, if arg0 contains a constant with the value "foo",
vmRead will recurse into the arg0 value and return "foo"
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.)
Due to the large number of opcodes that the AML VM needs to support,
using a long switch statement will not be as performant as setting up a
jump table due to the way that the go compiler generates code for long
switch statements on integer values (asm code indicates that binary
search is used to select the switch target).
For the time being, the populateJumpTable method will assign a
placeholder function to each jump table entry that just returns a
"opcode X not implemented error".
This commit updates the post-parse step so that:
- the visitor not longer recurses into method bodies. Since code inside
methods may potentially generate dynamic/scoped entities or even use
conditional invocations (if CondRefOf(X) { X(...) }), symbol resolution
will be deferred to the AML interpreter.
- parent-child relationships between entities are checked and updated if
not properly specified
Since the ACPI standard allows forward function declarations this step
is required so we can properly parse the argument list for function
invocations. Contrary to other AML entities, method invocations do not
include any sort of pkgLength information so unless we track the
expected argument count for each function, our parser will not be able
to figure out where the argument list ends.
vmm.IdentityMapRegion can be used by device drivers that want to
establish an identity mapping for a contiguous physical memory block in
order to access some hardware or table.