diff --git a/src/gopheros/device/acpi/aml/vm.go b/src/gopheros/device/acpi/aml/vm.go index 8a1865a..43ce83e 100644 --- a/src/gopheros/device/acpi/aml/vm.go +++ b/src/gopheros/device/acpi/aml/vm.go @@ -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 diff --git a/src/gopheros/device/acpi/aml/vm_load_store.go b/src/gopheros/device/acpi/aml/vm_load_store.go index 318e74e..4a91899 100644 --- a/src/gopheros/device/acpi/aml/vm_load_store.go +++ b/src/gopheros/device/acpi/aml/vm_load_store.go @@ -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 +} diff --git a/src/gopheros/device/acpi/aml/vm_load_store_test.go b/src/gopheros/device/acpi/aml/vm_load_store_test.go index c9d711c..caece4c 100644 --- a/src/gopheros/device/acpi/aml/vm_load_store_test.go +++ b/src/gopheros/device/acpi/aml/vm_load_store_test.go @@ -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) + } } }