mirror of
https://github.com/taigrr/gopher-os
synced 2025-01-18 04:43:13 -08:00
acpi: implement vmStore/Copy for local/method args and references
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.
This commit is contained in:
parent
1a2d075aa2
commit
dfaf068735
@ -12,6 +12,21 @@ const (
|
||||
maxMethodArgs = 7
|
||||
)
|
||||
|
||||
var (
|
||||
errNilStoreOperands = &Error{message: "vmStore: src and/or dst operands are nil"}
|
||||
errInvalidStoreDestination = &Error{message: "vmStore: destination operand is not an AML entity"}
|
||||
errCopyFailed = &Error{message: "vmCopyObject: copy failed"}
|
||||
)
|
||||
|
||||
// objRef is a pointer to an argument (local or global) or a named AML object.
|
||||
type objRef struct {
|
||||
ref interface{}
|
||||
|
||||
// isArgRef specifies whether this is a reference to a method argument.
|
||||
// Different rules (p.884) apply for this particular type of reference.
|
||||
isArgRef bool
|
||||
}
|
||||
|
||||
// ctrlFlowType describes the different ways that the control flow can be altered
|
||||
// while executing a set of AML opcodes.
|
||||
type ctrlFlowType uint8
|
||||
|
@ -28,8 +28,108 @@ func vmLoad(ctx *execContext, arg interface{}) (interface{}, *Error) {
|
||||
return uint64(1), nil
|
||||
}
|
||||
return uint64(0), nil
|
||||
case *objRef:
|
||||
// According to p. 884 of the spec, reading from a
|
||||
// method argument reference automatically dereferences
|
||||
// the value
|
||||
if typ.isArgRef {
|
||||
return typ.ref, nil
|
||||
}
|
||||
|
||||
// In all other cases we return back the reference itself
|
||||
return typ, nil
|
||||
default:
|
||||
return typ, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// vmCondStore is a wrapper around vmWrite that checks whether argIndex
|
||||
// contains a non-nil target before attempting to write val to it. If argIndex
|
||||
// is out of bounds or it points to a nil target then this function behaves as
|
||||
// a no-op.
|
||||
func vmCondStore(ctx *execContext, val interface{}, ent Entity, argIndex int) *Error {
|
||||
args := ent.getArgs()
|
||||
if len(args) <= argIndex || vmIsNilTarget(args[argIndex]) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return vmStore(ctx, val, args[argIndex])
|
||||
}
|
||||
|
||||
// vmStore attempts to write the value contained in src to dst.
|
||||
func vmStore(ctx *execContext, src, dst interface{}) *Error {
|
||||
if dst == nil || src == nil {
|
||||
return errNilStoreOperands
|
||||
}
|
||||
|
||||
// The target should be some type of AML Entity
|
||||
dstEnt, ok := dst.(Entity)
|
||||
if !ok {
|
||||
return errInvalidStoreDestination
|
||||
}
|
||||
dstOp := dstEnt.getOpcode()
|
||||
|
||||
// According to the spec, storing to a constant is a no-op and not a
|
||||
// fatal error. In addition, if the destination is the Debug opbject,
|
||||
// the interpreter must display the value written to it. This
|
||||
// interpreter implementation just treats this as a no-op.
|
||||
if _, ok := dst.(*constEntity); ok || dstOp == opDebug {
|
||||
return nil
|
||||
}
|
||||
|
||||
// The spec requires the interpreter to make a copy of the src object
|
||||
// and apply the appropriate conversions depending on the destination
|
||||
// object type
|
||||
srcCopy, err := vmCopyObject(ctx, src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch {
|
||||
case opIsLocalArg(dstOp):
|
||||
// According to p.897 of the spec, writing to a local object
|
||||
// always overwrites the previous value with a copy of src even
|
||||
// if this is an object reference
|
||||
ctx.localArg[dstOp-opLocal0] = srcCopy
|
||||
return nil
|
||||
case opIsMethodArg(dstOp):
|
||||
// According to p.896 of the spec, if ArgX is a reference
|
||||
// we need to dereference it and store the copied object
|
||||
// in the reference. In all other cases we just overwrite the
|
||||
// value in ArgX with the object copy
|
||||
if dstRef, isRef := ctx.methodArg[dstOp-opArg0].(*objRef); isRef {
|
||||
dstRef.ref = srcCopy
|
||||
} else {
|
||||
ctx.methodArg[dstOp-opArg0] = srcCopy
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return &Error{message: "vmStore: unsupported opcode: " + dstOp.String()}
|
||||
}
|
||||
|
||||
// vmIsNilTarget returns true if t is nil or a nil const entity.
|
||||
func vmIsNilTarget(target interface{}) bool {
|
||||
if target == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if ent, ok := target.(*constEntity); ok {
|
||||
return ent.val != nil && ent.val != 0
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// vmCopyObject returns a copy of obj.
|
||||
func vmCopyObject(ctx *execContext, obj interface{}) (interface{}, *Error) {
|
||||
switch typ := obj.(type) {
|
||||
case string:
|
||||
return typ, nil
|
||||
case uint64:
|
||||
return typ, nil
|
||||
}
|
||||
return nil, errCopyFailed
|
||||
}
|
||||
|
@ -6,6 +6,11 @@ import (
|
||||
)
|
||||
|
||||
func TestVMLoad(t *testing.T) {
|
||||
// Use a pointer to ensure that when we dereference an objRef we get
|
||||
// back the same pointer
|
||||
uniqueVal := &execContext{}
|
||||
aRef := &objRef{isArgRef: false, ref: 42}
|
||||
|
||||
specs := []struct {
|
||||
ctx *execContext
|
||||
argIn interface{}
|
||||
@ -56,6 +61,19 @@ func TestVMLoad(t *testing.T) {
|
||||
"foo",
|
||||
nil,
|
||||
},
|
||||
// reference handling
|
||||
{
|
||||
nil,
|
||||
&objRef{isArgRef: true, ref: uniqueVal},
|
||||
uniqueVal,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
nil,
|
||||
aRef,
|
||||
aRef,
|
||||
nil,
|
||||
},
|
||||
// Unsupported reads
|
||||
{
|
||||
nil,
|
||||
@ -76,6 +94,155 @@ func TestVMLoad(t *testing.T) {
|
||||
got, reflect.TypeOf(got),
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestVMStore(t *testing.T) {
|
||||
t.Run("errors", func(t *testing.T) {
|
||||
ctx := new(execContext)
|
||||
|
||||
if err := vmStore(ctx, nil, 42); err != errNilStoreOperands {
|
||||
t.Fatal("expected to get errNilStoreOperands")
|
||||
}
|
||||
|
||||
if err := vmStore(ctx, "foo", nil); err != errNilStoreOperands {
|
||||
t.Fatal("expected to get errNilStoreOperands")
|
||||
}
|
||||
|
||||
if err := vmStore(ctx, 42, "not-an-entity"); err != errInvalidStoreDestination {
|
||||
t.Fatal("expected to get errInvalidStoreDestination")
|
||||
}
|
||||
|
||||
if err := vmStore(ctx, &unnamedEntity{}, &unnamedEntity{op: opArg0}); err != errCopyFailed {
|
||||
t.Fatal("expected to get errCopyFailed")
|
||||
}
|
||||
|
||||
// Storing to fields, bufferFields & named objects is not yet supported
|
||||
expErr := &Error{message: "vmStore: unsupported opcode: Buffer"}
|
||||
if err := vmStore(ctx, uint64(42), &scopeEntity{op: opBuffer, name: "BUF0"}); err == nil || err.Error() != expErr.Error() {
|
||||
t.Fatalf("expected to get error: %v; got %v", expErr, err)
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
t.Run("store to local arg", func(t *testing.T) {
|
||||
ctx := &execContext{
|
||||
localArg: [maxLocalArgs]interface{}{
|
||||
"foo",
|
||||
uint64(42),
|
||||
10,
|
||||
},
|
||||
}
|
||||
|
||||
if err := vmStore(ctx, uint64(123), &unnamedEntity{op: opLocal1}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expArgs := [maxLocalArgs]interface{}{
|
||||
"foo",
|
||||
uint64(123),
|
||||
10,
|
||||
}
|
||||
if !reflect.DeepEqual(ctx.localArg, expArgs) {
|
||||
t.Fatalf("expected local args to be %v; got %v", expArgs, ctx.localArg)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("store to method arg", func(t *testing.T) {
|
||||
ref := &objRef{isArgRef: true, ref: "foo"}
|
||||
ctx := &execContext{
|
||||
methodArg: [maxMethodArgs]interface{}{
|
||||
"foo",
|
||||
uint64(42),
|
||||
ref,
|
||||
},
|
||||
}
|
||||
|
||||
if err := vmStore(ctx, uint64(123), &unnamedEntity{op: opArg1}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := vmStore(ctx, "bar", &unnamedEntity{op: opArg2}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(ctx.methodArg[0], "foo") {
|
||||
t.Fatal("expected methodArg[0] not to be modified")
|
||||
}
|
||||
|
||||
if exp := uint64(123); !reflect.DeepEqual(ctx.methodArg[1], exp) {
|
||||
t.Fatalf("expected methodArg[1] to be set to: %v; got %v", exp, ctx.methodArg[1])
|
||||
}
|
||||
|
||||
if ctx.methodArg[2] != ref {
|
||||
t.Fatal("expected the reference instance in methodArg[2] not to be modified")
|
||||
}
|
||||
|
||||
if exp := "bar"; ref.ref != exp {
|
||||
t.Fatalf("expected the referenced value by methodArg[2] to be set to: %v; got %v", exp, ctx.methodArg[2])
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("store to Debug object or a constant", func(t *testing.T) {
|
||||
constant := &constEntity{val: "foo"}
|
||||
if err := vmStore(nil, 42, constant); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if exp := "foo"; constant.val != exp {
|
||||
t.Fatalf("expected storing to constant to be a no-op; constant value changed from %v to %v", exp, constant.val)
|
||||
}
|
||||
|
||||
if err := vmStore(nil, 42, &unnamedEntity{op: opDebug}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestVMCondStore(t *testing.T) {
|
||||
specs := []struct {
|
||||
ctx *execContext
|
||||
args []interface{}
|
||||
argIndex int
|
||||
val interface{}
|
||||
}{
|
||||
// Not enough args to get target
|
||||
{
|
||||
nil,
|
||||
[]interface{}{"foo"},
|
||||
2,
|
||||
"bar",
|
||||
},
|
||||
// Target is nil
|
||||
{
|
||||
nil,
|
||||
[]interface{}{nil, "foo"},
|
||||
0,
|
||||
"bar",
|
||||
},
|
||||
// Target is a constant with a nil value
|
||||
{
|
||||
nil,
|
||||
[]interface{}{
|
||||
&constEntity{},
|
||||
},
|
||||
0,
|
||||
"bar",
|
||||
},
|
||||
// Valid target
|
||||
{
|
||||
&execContext{},
|
||||
[]interface{}{
|
||||
&unnamedEntity{op: opLocal0},
|
||||
},
|
||||
0,
|
||||
"bar",
|
||||
},
|
||||
}
|
||||
|
||||
for specIndex, spec := range specs {
|
||||
if err := vmCondStore(spec.ctx, spec.val, &unnamedEntity{args: spec.args}, spec.argIndex); err != nil {
|
||||
t.Errorf("[spec %d] error: %v", specIndex, spec)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user