diff --git a/internal/emval.go b/internal/emval.go index ce4dd5b..d8e7443 100644 --- a/internal/emval.go +++ b/internal/emval.go @@ -9,6 +9,7 @@ import ( "strings" "unicode" + "github.com/jerbob92/wazero-emscripten-embind/js" "github.com/jerbob92/wazero-emscripten-embind/types" "github.com/tetratelabs/wazero/api" @@ -1234,8 +1235,97 @@ var EmvalEquals = api.GoModuleFunc(func(ctx context.Context, mod api.Module, sta }) var EmvalGetModuleProperty = api.GoModuleFunc(func(ctx context.Context, mod api.Module, stack []uint64) { - // @todo: implement me. - panic("EmvalGetModuleProperty call unimplemented") + engine := MustGetEngineFromContext(ctx, mod).(*engine) + namePtr := api.DecodeI32(stack[0]) + name, err := engine.getStringOrSymbol(uint32(namePtr)) + if err != nil { + panic(fmt.Errorf("could not get symbol name")) + } + + switch name { + case "HEAP8": + typedMemoryView, ok := allMemoryAs[int8](mod.Memory(), 1) + if !ok { + panic(fmt.Errorf("could not create memory view")) + } + stack[0] = api.EncodeI32(engine.emvalEngine.toHandle(js.Int8Array{ + Buffer: typedMemoryView, + Length: len(typedMemoryView), + })) + return + case "HEAP16": + typedMemoryView, ok := allMemoryAs[int16](mod.Memory(), 2) + if !ok { + panic(fmt.Errorf("could not create memory view")) + } + stack[0] = api.EncodeI32(engine.emvalEngine.toHandle(js.Int16Array{ + Buffer: typedMemoryView, + Length: len(typedMemoryView), + })) + return + case "HEAPU8": + typedMemoryView, ok := allMemoryAs[uint8](mod.Memory(), 1) + if !ok { + panic(fmt.Errorf("could not create memory view")) + } + stack[0] = api.EncodeI32(engine.emvalEngine.toHandle(js.Uint8Array{ + Buffer: typedMemoryView, + Length: len(typedMemoryView), + })) + return + case "HEAPU16": + typedMemoryView, ok := allMemoryAs[uint16](mod.Memory(), 2) + if !ok { + panic(fmt.Errorf("could not create memory view")) + } + stack[0] = api.EncodeI32(engine.emvalEngine.toHandle(js.Uint16Array{ + Buffer: typedMemoryView, + Length: len(typedMemoryView), + })) + return + case "HEAP32": + typedMemoryView, ok := allMemoryAs[int32](mod.Memory(), 4) + if !ok { + panic(fmt.Errorf("could not create memory view")) + } + stack[0] = api.EncodeI32(engine.emvalEngine.toHandle(js.Int32Array{ + Buffer: typedMemoryView, + Length: len(typedMemoryView), + })) + return + case "HEAPU32": + typedMemoryView, ok := allMemoryAs[uint32](mod.Memory(), 4) + if !ok { + panic(fmt.Errorf("could not create memory view")) + } + stack[0] = api.EncodeI32(engine.emvalEngine.toHandle(js.Uint32Array{ + Buffer: typedMemoryView, + Length: len(typedMemoryView), + })) + return + case "HEAPF32": + typedMemoryView, ok := allMemoryAs[float32](mod.Memory(), 4) + if !ok { + panic(fmt.Errorf("could not create memory view")) + } + stack[0] = api.EncodeI32(engine.emvalEngine.toHandle(js.Float32Array{ + Buffer: typedMemoryView, + Length: len(typedMemoryView), + })) + return + case "HEAPF64": + typedMemoryView, ok := allMemoryAs[float64](mod.Memory(), 8) + if !ok { + panic(fmt.Errorf("could not create memory view")) + } + stack[0] = api.EncodeI32(engine.emvalEngine.toHandle(js.Float64Array{ + Buffer: typedMemoryView, + Length: len(typedMemoryView), + })) + return + } + + panic(fmt.Errorf("could not find module property: %s", name)) }) var EmvalIn = api.GoModuleFunc(func(ctx context.Context, mod api.Module, stack []uint64) { diff --git a/internal/memory_view.go b/internal/memory_view.go index dc705da..177d2b3 100644 --- a/internal/memory_view.go +++ b/internal/memory_view.go @@ -72,6 +72,17 @@ func memoryAs[T any](memory api.Memory, offset uint32, elementSize uint32, lengt return unsafe.Slice((*T)(unsafe.Pointer(&memoryView[0])), length), true } +func allMemoryAs[T any](memory api.Memory, elementSize uint32) ([]T, bool) { + memoryView, ok := memory.Read(0, memory.Size()) + if !ok { + return nil, ok + } + + length := memory.Size() / elementSize + + return unsafe.Slice((*T)(unsafe.Pointer(&memoryView[0])), length), true +} + func (mvt *memoryViewType) ToWireType(ctx context.Context, mod api.Module, destructors *[]*destructorFunc, o any) (uint64, error) { return 0, fmt.Errorf("ToWireType is not supported for memory views") } diff --git a/js/array.go b/js/array.go new file mode 100644 index 0000000..74b22de --- /dev/null +++ b/js/array.go @@ -0,0 +1,107 @@ +package js + +import ( + "fmt" + "unsafe" +) + +type ArrayBuffer struct { + Buffer []uint8 + Length int +} + +type Int8Array struct { + Buffer []int8 + Length int +} + +type Int16Array struct { + Buffer []int16 + Length int +} + +type Uint8Array struct { + Buffer []uint8 + Length int +} + +type Uint16Array struct { + Buffer []uint16 + Length int +} + +type Int32Array struct { + Buffer []int32 + Length int +} + +type Uint32Array struct { + Buffer []uint32 + Length int +} + +type Float32Array struct { + Buffer []float32 + Length int +} + +func (obj *Float32Array) New(argTypes []string, args ...any) (any, error) { + if len(argTypes) == 3 { + switch v := args[0].(type) { + case Uint8Array: + sourceArray := unsafe.Slice((*float32)(unsafe.Pointer(&v.Buffer[0])), v.Length) + return &Float32Array{ + Buffer: sourceArray[args[1].(int):(args[2].(int) * 4)], + Length: args[2].(int) / 4, + }, nil + case *Uint8Array: + sourceArray := unsafe.Slice((*float32)(unsafe.Pointer(&v.Buffer[0])), v.Length) + return &Float32Array{ + Buffer: sourceArray[args[1].(int):(args[2].(int) * 4)], + Length: args[2].(int) / 4, + }, nil + case ArrayBuffer: + sourceArray := unsafe.Slice((*float32)(unsafe.Pointer(&v.Buffer[0])), v.Length) + return &Float32Array{ + Buffer: sourceArray[args[1].(int):(args[2].(int) * 4)], + Length: args[2].(int) / 4, + }, nil + case *ArrayBuffer: + sourceArray := unsafe.Slice((*float32)(unsafe.Pointer(&v.Buffer[0])), v.Length) + return &Float32Array{ + Buffer: sourceArray[args[1].(int):(args[2].(int) * 4)], + Length: args[2].(int) / 4, + }, nil + default: + return nil, fmt.Errorf("unknown source type: %T", v) + } + } + + // @todo: implement other constructors. + return nil, fmt.Errorf("unknown constructor of length %d", len(argTypes)) +} + +func (obj *Float32Array) Set(input any) error { + switch v := input.(type) { + case Float32Array: + obj.Buffer = v.Buffer + obj.Length = v.Length + return nil + case *Float32Array: + obj.Buffer = v.Buffer + obj.Length = v.Length + return nil + } + + return fmt.Errorf("set can only receive Float32Array or *Float32Array, got %T", input) +} + +type Float64Array struct { + Buffer []float64 + Length int +} + +func (obj *Float64Array) New(argTypes []string, args ...any) (any, error) { + // @todo: implement me. + return Float64Array{}, nil +} diff --git a/tests/generated_test.go b/tests/generated_test.go index cdd15c1..ef81a58 100644 --- a/tests/generated_test.go +++ b/tests/generated_test.go @@ -2,6 +2,7 @@ package tests import ( "context" + "github.com/jerbob92/wazero-emscripten-embind/js" "log" "os" "testing" @@ -1094,6 +1095,75 @@ var _ = Describe("executing original embind tests", Label("library"), func() { } }) + Context("get_module_property", func() { + It("returns an error when requesting an unknown module property", func() { + res, err := generated.Get_module_property(engine, ctx, "unknown") + Expect(err).To(Not(BeNil())) + if err != nil { + Expect(err.Error()).To(ContainSubstring("could not find module property: unknown")) + } + Expect(res).To(BeNil()) + }) + + It("returns an array of the right size for the memory view", func() { + res, err := generated.Get_module_property(engine, ctx, "HEAP8") + Expect(err).To(BeNil()) + Expect(res).To(BeAssignableToTypeOf(js.Int8Array{})) + expectedLength := int(mod.Memory().Size()) + Expect(res.(js.Int8Array).Length).To(Equal(expectedLength)) + Expect(res.(js.Int8Array).Buffer).To(HaveLen(expectedLength)) + + res, err = generated.Get_module_property(engine, ctx, "HEAPU8") + Expect(err).To(BeNil()) + Expect(res).To(BeAssignableToTypeOf(js.Uint8Array{})) + expectedLength = int(mod.Memory().Size()) + Expect(res.(js.Uint8Array).Length).To(Equal(expectedLength)) + Expect(res.(js.Uint8Array).Buffer).To(HaveLen(expectedLength)) + + res, err = generated.Get_module_property(engine, ctx, "HEAP16") + Expect(err).To(BeNil()) + Expect(res).To(BeAssignableToTypeOf(js.Int16Array{})) + expectedLength = int(mod.Memory().Size() / 2) + Expect(res.(js.Int16Array).Length).To(Equal(expectedLength)) + Expect(res.(js.Int16Array).Buffer).To(HaveLen(expectedLength)) + + res, err = generated.Get_module_property(engine, ctx, "HEAPU16") + Expect(err).To(BeNil()) + Expect(res).To(BeAssignableToTypeOf(js.Uint16Array{})) + expectedLength = int(mod.Memory().Size() / 2) + Expect(res.(js.Uint16Array).Length).To(Equal(expectedLength)) + Expect(res.(js.Uint16Array).Buffer).To(HaveLen(expectedLength)) + + res, err = generated.Get_module_property(engine, ctx, "HEAP32") + Expect(err).To(BeNil()) + Expect(res).To(BeAssignableToTypeOf(js.Int32Array{})) + expectedLength = int(mod.Memory().Size() / 4) + Expect(res.(js.Int32Array).Length).To(Equal(expectedLength)) + Expect(res.(js.Int32Array).Buffer).To(HaveLen(expectedLength)) + + res, err = generated.Get_module_property(engine, ctx, "HEAPU32") + Expect(err).To(BeNil()) + Expect(res).To(BeAssignableToTypeOf(js.Uint32Array{})) + expectedLength = int(mod.Memory().Size() / 4) + Expect(res.(js.Uint32Array).Length).To(Equal(expectedLength)) + Expect(res.(js.Uint32Array).Buffer).To(HaveLen(expectedLength)) + + res, err = generated.Get_module_property(engine, ctx, "HEAPF32") + Expect(err).To(BeNil()) + Expect(res).To(BeAssignableToTypeOf(js.Float32Array{})) + expectedLength = int(mod.Memory().Size() / 4) + Expect(res.(js.Float32Array).Length).To(Equal(expectedLength)) + Expect(res.(js.Float32Array).Buffer).To(HaveLen(expectedLength)) + + res, err = generated.Get_module_property(engine, ctx, "HEAPF64") + Expect(err).To(BeNil()) + Expect(res).To(BeAssignableToTypeOf(js.Float64Array{})) + expectedLength = int(mod.Memory().Size() / 8) + Expect(res.(js.Float64Array).Length).To(Equal(expectedLength)) + Expect(res.(js.Float64Array).Buffer).To(HaveLen(expectedLength)) + }) + }) + /* test("passing Symbol or BigInt as floats always throws", function() { assert.throws(TypeError, function() { cm.const_ref_adder(Symbol('0'), 1); }); @@ -1176,9 +1246,7 @@ var _ = Describe("executing original embind tests", Label("library"), func() { assert.equal(x, instance.argument); }); - test("can return module property objects", function() { - assert.equal(cm.HEAP8, cm.get_module_property("HEAP8")); - }); + test("can return big class instances", function() { var c = cm.embind_test_return_big_class_instance();