mirror of
https://github.com/taigrr/gopher-os
synced 2025-01-18 04:43:13 -08:00
acpi: add VM-support for method invocations
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.
This commit is contained in:
parent
ad6c7ee991
commit
540986cb0b
@ -409,7 +409,7 @@ type namedReference struct {
|
||||
func (ref *namedReference) Resolve(errWriter io.Writer, rootNs ScopeEntity) bool {
|
||||
if ref.target == nil {
|
||||
if ref.target = scopeFind(ref.parent, rootNs, ref.targetName); ref.target == nil {
|
||||
kfmt.Fprintf(errWriter, "could not resolve referenced symbol: %s (parent: %s)\n", ref.targetName, ref.parent.Name())
|
||||
kfmt.Fprintf(errWriter, "could not resolve referenced symbol: %s (parent: %s)\n", ref.targetName, entName(ref.parent))
|
||||
return false
|
||||
}
|
||||
}
|
||||
@ -425,11 +425,18 @@ type methodInvocationEntity struct {
|
||||
method *Method
|
||||
}
|
||||
|
||||
// getOpcode returns a fake opcode (set to numOpcodes) for method invocations.
|
||||
// This allows the interpreter to register a handler for method invocations in
|
||||
// its jumpTable that does not produce a conflict with any existing opcodes.
|
||||
func (m *methodInvocationEntity) getOpcode() opcode {
|
||||
return numOpcodes
|
||||
}
|
||||
|
||||
func (m *methodInvocationEntity) Resolve(errWriter io.Writer, rootNs ScopeEntity) bool {
|
||||
if m.method == nil {
|
||||
var isMethod bool
|
||||
if m.method, isMethod = scopeFind(m.parent, rootNs, m.methodName).(*Method); !isMethod {
|
||||
kfmt.Fprintf(errWriter, "could not resolve merenced method: %s (parent: %s)\n", m.methodName, m.parent.Name())
|
||||
kfmt.Fprintf(errWriter, "could not resolve merenced method: %s (parent: %s)\n", m.methodName, entName(m.parent))
|
||||
return false
|
||||
}
|
||||
}
|
||||
@ -504,3 +511,12 @@ func (ent *mutexEntity) setTableHandle(h uint8) { ent.tableHandle = h }
|
||||
type eventEntity struct {
|
||||
namedEntity
|
||||
}
|
||||
|
||||
// entName returns the entity name if ent is not nil or a blank string otherwise.
|
||||
func entName(ent Entity) string {
|
||||
if ent == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return ent.Name()
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ type VM struct {
|
||||
// value so that it can be used by the data conversion helpers.
|
||||
sizeOfIntInBits int
|
||||
|
||||
jumpTable [numOpcodes]opHandler
|
||||
jumpTable [numOpcodes + 1]opHandler
|
||||
}
|
||||
|
||||
// NewVM creates a new AML VM and initializes it with the default scope
|
||||
@ -169,7 +169,7 @@ func (vm *VM) checkEntities() *Error {
|
||||
|
||||
switch typ := ent.(type) {
|
||||
case *Method:
|
||||
// Do not recurse into methods; ath this stage we are only interested in
|
||||
// Do not recurse into methods; at this stage we are only interested in
|
||||
// initializing static entities.
|
||||
return false
|
||||
case *bufferEntity:
|
||||
@ -209,6 +209,33 @@ func (vm *VM) Visit(entType EntityType, visitorFn Visitor) {
|
||||
scopeVisit(0, vm.rootNS, entType, visitorFn)
|
||||
}
|
||||
|
||||
// execMethod creates a new execution context and invokes the given method
|
||||
// passing along the supplied args. It populates the retVal of the input
|
||||
// context with the result of the method invocation.
|
||||
func (vm *VM) execMethod(ctx *execContext, method *Method, args ...interface{}) *Error {
|
||||
var (
|
||||
invCtx = execContext{vm: vm}
|
||||
err *Error
|
||||
)
|
||||
|
||||
// Resolve invocation args and populate methodArgs for the new context
|
||||
for argIndex := 0; argIndex < len(args); argIndex++ {
|
||||
invCtx.methodArg[argIndex], err = vmLoad(ctx, args[argIndex])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Execute method and resolve the return value before storing it to the
|
||||
// parent context's retVal.
|
||||
err = vm.execBlock(&invCtx, method)
|
||||
if err == nil {
|
||||
ctx.retVal, err = vmLoad(&invCtx, invCtx.retVal)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// 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
|
||||
|
@ -46,6 +46,10 @@ func (vm *VM) populateJumpTable() {
|
||||
|
||||
// Store-related opcodes
|
||||
vm.jumpTable[opStore] = vmOpStore
|
||||
|
||||
// Method invocation dispatcher; to avoid clashes with real AML opcodes,
|
||||
// method invocations are assigned an opcode with value (lastOpcode + 1)
|
||||
vm.jumpTable[numOpcodes] = vmOpMethodInvocation
|
||||
}
|
||||
|
||||
// opExecNotImplemented is a placeholder handler that returns a non-implemented
|
||||
|
@ -117,3 +117,17 @@ func vmOpIf(ctx *execContext, ent Entity) *Error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// vmOpMethodInvocation dispatches a method invocation and sets ctx.retVal
|
||||
// to the value returned by the method invocation. This function also supports
|
||||
// invocation of methods that are provided by the kernel host such as the ones
|
||||
// defined in section 5.7 of the ACPI spec.
|
||||
func vmOpMethodInvocation(ctx *execContext, ent Entity) *Error {
|
||||
// Make sure the target method is properly resolved
|
||||
inv := ent.(*methodInvocationEntity)
|
||||
if !inv.Resolve(ctx.vm.errWriter, ctx.vm.rootNS) {
|
||||
return &Error{message: "call to undefined method: " + inv.methodName}
|
||||
}
|
||||
|
||||
return ctx.vm.execMethod(ctx, inv.method, ent.getArgs()...)
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package aml
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
@ -187,3 +188,61 @@ func TestVMFlowOpErrors(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestVMNestedMethodCalls(t *testing.T) {
|
||||
resolver := &mockResolver{
|
||||
tableFiles: []string{"vm-testsuite-DSDT.aml"},
|
||||
}
|
||||
|
||||
vm := NewVM(ioutil.Discard, resolver)
|
||||
if err := vm.Init(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Run("nested call success", func(t *testing.T) {
|
||||
inv := &methodInvocationEntity{
|
||||
unnamedEntity: unnamedEntity{
|
||||
args: []interface{}{uint64(10)},
|
||||
},
|
||||
methodName: `\NST0`,
|
||||
}
|
||||
|
||||
ctx := &execContext{vm: vm}
|
||||
if err := vmOpMethodInvocation(ctx, inv); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if exp := uint64(52); !reflect.DeepEqual(ctx.retVal, exp) {
|
||||
t.Fatalf("expected return value to be: %v; got: %v", exp, ctx.retVal)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("undefined method", func(t *testing.T) {
|
||||
inv := &methodInvocationEntity{methodName: `UNDEFINED`}
|
||||
|
||||
ctx := &execContext{vm: vm}
|
||||
expErr := "call to undefined method: UNDEFINED"
|
||||
if err := vmOpMethodInvocation(ctx, inv); err == nil || err.Error() != expErr {
|
||||
t.Fatalf("expected error: %s; got %v", expErr, err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("method arg load error", func(t *testing.T) {
|
||||
op0Err := &Error{message: "something went wrong with op 0"}
|
||||
vm.jumpTable[0] = func(_ *execContext, ent Entity) *Error { return op0Err }
|
||||
|
||||
inv := &methodInvocationEntity{
|
||||
unnamedEntity: unnamedEntity{
|
||||
args: []interface{}{
|
||||
&unnamedEntity{}, // vmLoad will invoke jumpTable[0] which always returns an error
|
||||
},
|
||||
},
|
||||
methodName: `\NST0`,
|
||||
}
|
||||
|
||||
ctx := &execContext{vm: vm}
|
||||
if err := vmOpMethodInvocation(ctx, inv); err != op0Err {
|
||||
t.Fatalf("expected error: %s; got %v", op0Err, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
Binary file not shown.
@ -236,4 +236,15 @@ DefinitionBlock ("vm-testsuite-DSDT.aml", "DSDT", 2, "GOPHER", "GOPHEROS", 0x000
|
||||
Return(Local1)
|
||||
}
|
||||
|
||||
// Netsed method invocations
|
||||
Method(NST0, 1, NotSerialized)
|
||||
{
|
||||
Return(NST1(Arg0))
|
||||
}
|
||||
|
||||
Method(NST1, 1, NotSerialized)
|
||||
{
|
||||
Return(Arg0+42)
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user