diff --git a/wasmer/config.go b/wasmer/config.go index ef8b9e9..cb3a5f0 100644 --- a/wasmer/config.go +++ b/wasmer/config.go @@ -21,8 +21,8 @@ const ( // Strings returns the CompilerKind as a string. // -// CRANELIFT.String() // "cranelift" -// LLVM.String() // "llvm" +// CRANELIFT.String() // "cranelift" +// LLVM.String() // "llvm" func (self CompilerKind) String() string { switch self { case CRANELIFT: @@ -40,7 +40,7 @@ func (self CompilerKind) String() string { // IsCompilerAvailable checks that the given compiler is available // in this current version of `wasmer-go`. // -// IsCompilerAvailable(CRANELIFT) +// IsCompilerAvailable(CRANELIFT) func IsCompilerAvailable(compiler CompilerKind) bool { return bool(C.wasmer_is_compiler_available(uint32(C.wasmer_compiler_t(compiler)))) } @@ -64,8 +64,8 @@ const ( // Strings returns the EngineKind as a string. // -// JIT.String() // "jit" -// NATIVE.String() // "native" +// JIT.String() // "jit" +// NATIVE.String() // "native" func (self EngineKind) String() string { switch self { case UNIVERSAL: @@ -80,35 +80,31 @@ func (self EngineKind) String() string { // IsEngineAvailable checks that the given engine is available in this // current version of `wasmer-go`. // -// IsEngineAvailable(UNIVERSAL) +// IsEngineAvailable(UNIVERSAL) func IsEngineAvailable(engine EngineKind) bool { return bool(C.wasmer_is_engine_available(uint32(C.wasmer_engine_t(engine)))) } // Config holds the compiler and the Engine used by the Store. type Config struct { - _inner *C.wasm_config_t + CPtrBase[*C.wasm_config_t] } // NewConfig instantiates and returns a new Config. // -// config := NewConfig() +// config := NewConfig() func NewConfig() *Config { - config := C.wasm_config_new() - - return &Config{ - _inner: config, - } + return &Config{CPtrBase: mkPtr(C.wasm_config_new())} } func (self *Config) inner() *C.wasm_config_t { - return self._inner + return self.ptr() } // UseNativeEngine sets the engine to Universal in the configuration. // -// config := NewConfig() -// config.UseUniversalEngine() +// config := NewConfig() +// config.UseUniversalEngine() // // This method might fail if the Universal engine isn't // available. Check `IsEngineAvailable` to learn more. @@ -124,8 +120,8 @@ func (self *Config) UseUniversalEngine() *Config { // UseDylibEngine sets the engine to Dylib in the configuration. // -// config := NewConfig() -// config.UseDylibEngine() +// config := NewConfig() +// config.UseDylibEngine() // // This method might fail if the Dylib engine isn't available. Check // `IsEngineAvailable` to learn more. @@ -153,8 +149,8 @@ func (self *Config) UseNativeEngine() *Config { // UseCraneliftCompiler sets the compiler to Cranelift in the configuration. // -// config := NewConfig() -// config.UseCraneliftCompiler() +// config := NewConfig() +// config.UseCraneliftCompiler() // // This method might fail if the Cranelift compiler isn't // available. Check `IsCompilerAvailable` to learn more. @@ -182,14 +178,17 @@ func metering_delegate(op C.wasmer_parser_operator_t) C.uint64_t { } // PushMeteringMiddleware allows the middleware metering to be engaged on a map of opcode to cost -// config := NewConfig() -// opmap := map[uint32]uint32{ -// End: 1, -// LocalGet: 1, -// I32Add: 4, -// } -// config.PushMeteringMiddleware(7865444, opmap) -func (self *Config) PushMeteringMiddleware(maxGasUsageAllowed uint64, opMap map[Opcode]uint32) *Config { +// +// config := NewConfig() +// opmap := map[uint32]uint32{ +// End: 1, +// LocalGet: 1, +// I32Add: 4, +// } +// config.PushMeteringMiddleware(7865444, opmap) +func (self *Config) PushMeteringMiddleware( + maxGasUsageAllowed uint64, opMap map[Opcode]uint32, +) *Config { if opCodeMap == nil { // REVIEW only allowing this to be set once opCodeMap = opMap @@ -200,7 +199,9 @@ func (self *Config) PushMeteringMiddleware(maxGasUsageAllowed uint64, opMap map[ // PushMeteringMiddlewarePtr allows the middleware metering to be engaged on an unsafe.Pointer // this pointer must be a to C based function with a signature of: -// extern uint64_t cost_delegate_func(enum wasmer_parser_operator_t op); +// +// extern uint64_t cost_delegate_func(enum wasmer_parser_operator_t op); +// // package main // // #include @@ -208,23 +209,24 @@ func (self *Config) PushMeteringMiddleware(maxGasUsageAllowed uint64, opMap map[ // import "C" // import "unsafe" // -// func getInternalCPointer() unsafe.Pointer { -// return unsafe.Pointer(C.metering_delegate_alt) -// } +// func getInternalCPointer() unsafe.Pointer { +// return unsafe.Pointer(C.metering_delegate_alt) +// } // // //export metering_delegate_alt -// func metering_delegate_alt(op C.wasmer_parser_operator_t) C.uint64_t { -// v, b := opCodeMap[Opcode(op)] -// if !b { -// return 0 // no value means no cost -// } -// return C.uint64_t(v) -// } // -// void main(){ -// config := NewConfig() -// config.PushMeteringMiddlewarePtr(800000000, getInternalCPointer()) -// } +// func metering_delegate_alt(op C.wasmer_parser_operator_t) C.uint64_t { +// v, b := opCodeMap[Opcode(op)] +// if !b { +// return 0 // no value means no cost +// } +// return C.uint64_t(v) +// } +// +// void main(){ +// config := NewConfig() +// config.PushMeteringMiddlewarePtr(800000000, getInternalCPointer()) +// } func (self *Config) PushMeteringMiddlewarePtr(maxGasUsageAllowed uint64, p unsafe.Pointer) *Config { C.wasm_config_push_middleware(self.inner(), C.wasmer_metering_as_middleware(C.wasmer_metering_new(getPlatformLong(maxGasUsageAllowed), (*[0]byte)(p)))) return self @@ -232,8 +234,8 @@ func (self *Config) PushMeteringMiddlewarePtr(maxGasUsageAllowed uint64, p unsaf // UseLLVMCompiler sets the compiler to LLVM in the configuration. // -// config := NewConfig() -// config.UseLLVMCompiler() +// config := NewConfig() +// config.UseLLVMCompiler() // // This method might fail if the LLVM compiler isn't available. Check // `IsCompilerAvailable` to learn more. @@ -250,8 +252,8 @@ func (self *Config) UseLLVMCompiler() *Config { // UseSinglepassCompiler sets the compiler to Singlepass in the // configuration. // -// config := NewConfig() -// config.UseSinglepassCompiler() +// config := NewConfig() +// config.UseSinglepassCompiler() // // This method might fail if the Singlepass compiler isn't // available. Check `IsCompilerAvailable` to learn more. @@ -267,14 +269,14 @@ func (self *Config) UseSinglepassCompiler() *Config { // Use a specific target for doing cross-compilation. // -// triple, _ := NewTriple("aarch64-unknown-linux-gnu") -// cpuFeatures := NewCpuFeatures() -// target := NewTarget(triple, cpuFeatures) +// triple, _ := NewTriple("aarch64-unknown-linux-gnu") +// cpuFeatures := NewCpuFeatures() +// target := NewTarget(triple, cpuFeatures) // -// config := NewConfig() -// config.UseTarget(target) +// config := NewConfig() +// config.UseTarget(target) func (self *Config) UseTarget(target *Target) *Config { - C.wasm_config_set_target(self.inner(), target.inner()) + C.wasm_config_set_target(self.inner(), target.release()) return self } diff --git a/wasmer/config_test.go b/wasmer/config_test.go index eea00fd..69a4c08 100644 --- a/wasmer/config_test.go +++ b/wasmer/config_test.go @@ -34,8 +34,9 @@ func TestConfig(t *testing.T) { instance, err := NewInstance(module, NewImportObject()) assert.NoError(t, err) - sum, err := instance.Exports.GetFunction("sum") + sum, release, err := instance.GetFunctionSafe("sum") assert.NoError(t, err) + defer release(instance) result, err := sum(37, 5) assert.NoError(t, err) @@ -57,8 +58,9 @@ func TestConfigForMetering(t *testing.T) { instance, err := NewInstance(module, NewImportObject()) assert.NoError(t, err) - sum, err := instance.Exports.GetFunction("sum") + sum, release, err := instance.GetFunctionSafe("sum") assert.NoError(t, err) + defer release(instance) result, err := sum(37, 5) assert.NoError(t, err) @@ -69,7 +71,6 @@ func TestConfigForMetering(t *testing.T) { } func TestConfigForMeteringFn(t *testing.T) { - config := NewConfig().PushMeteringMiddlewarePtr(800000000, getInternalCPointer()) engine := NewEngineWithConfig(config) store := NewStore(engine) @@ -79,8 +80,9 @@ func TestConfigForMeteringFn(t *testing.T) { instance, err := NewInstance(module, NewImportObject()) assert.NoError(t, err) - sum, err := instance.Exports.GetFunction("sum") + sum, release, err := instance.GetFunctionSafe("sum") assert.NoError(t, err) + defer release(instance) result, err := sum(37, 5) assert.NoError(t, err) @@ -149,8 +151,9 @@ func TestConfig_AllCombinations(t *testing.T) { instance, err := NewInstance(module, NewImportObject()) assert.NoError(t, err) - sum, err := instance.Exports.GetFunction("sum") + sum, release, err := instance.GetFunctionSafe("sum") assert.NoError(t, err) + defer release(instance) result, err := sum(37, 5) assert.NoError(t, err) diff --git a/wasmer/cptr.go b/wasmer/cptr.go new file mode 100644 index 0000000..0026367 --- /dev/null +++ b/wasmer/cptr.go @@ -0,0 +1,33 @@ +package wasmer + +// CPtrBase is a based struct for a C pointer. +// It is intended to be embedded into any structure +// that stores a C pointer. +// While this based is parameterized on type any, using any +// type other than *C.xxx pointer should be considered an undefined behavior. +type CPtrBase[T comparable] struct { + _ptr T + maybeStack // stack of the creating goroutine (if enabled via memcheck) + maybeNil[T] // indicates if initial value of _ptr was nil (if enabled via memcheck) +} + +// release returns the C pointer stored in this base and clears the finalizer. +func (b *CPtrBase[T]) release() T { + var zero T + v := b.ptr() + b._ptr = zero + b.ClearFinalizer() + return v +} + +func (b *CPtrBase[T]) SetFinalizer(free func(v T)) { + doSetFinalizer(b, free) +} + +func (b *CPtrBase[T]) ClearFinalizer() { + doClearFinalizer(b) +} + +func mkPtr[T comparable](ptr T) CPtrBase[T] { + return doMkPtr(ptr) +} diff --git a/wasmer/cptr_check.go b/wasmer/cptr_check.go new file mode 100644 index 0000000..280317b --- /dev/null +++ b/wasmer/cptr_check.go @@ -0,0 +1,138 @@ +//go:build memcheck + +package wasmer + +import ( + "bytes" + "fmt" + "os" + "os/signal" + "runtime" + "sync" + "sync/atomic" + "syscall" + "unsafe" +) + +func doMkPtr[T comparable](ptr T) CPtrBase[T] { + var zero T + b := CPtrBase[T]{_ptr: ptr} + b._stack = stack(3) + b.maybeNil._nil = ptr == zero + b.maybeNil._zero = zero + return b +} + +type maybeNil[T comparable] struct { + _nil bool + _zero T +} + +// maybeStack stores the stack of the goroutine. +type maybeStack struct { + _stack []byte +} + +// ptr returns the C pointer stored in this base. +func (b *CPtrBase[T]) ptr() T { + if !b.maybeNil._nil && b._ptr == b.maybeNil._zero { + panic(fmt.Errorf("attempt to use a released object; ptr was allocated by\n%s", b._stack)) + } + runtime.GC() + return b._ptr +} + +func doSetFinalizer[T comparable](b *CPtrBase[T], free func(v T)) { + finalizers.Store(uintptr(unsafe.Pointer(b)), stack(3)) + + runtime.SetFinalizer(b, func(b *CPtrBase[T]) { + ptr := b._ptr + b._ptr = b.maybeNil._zero + b.maybeNil._nil = false + free(ptr) + finalizers.Delete(uintptr(unsafe.Pointer(b))) + }) +} + +func doClearFinalizer[T comparable](b *CPtrBase[T]) { + runtime.SetFinalizer(b, nil) + finalizers.Delete(uintptr(unsafe.Pointer(b))) +} + +var finalizers sync.Map // uintptr -> []byte (the stack of the caller which created the finalizer) + +func init() { + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGABRT, syscall.SIGSEGV, syscall.SIGBUS, syscall.SIGINT) + + go func() { + s := <-c + fmt.Fprintf(os.Stderr, "Signal %s received\n", s) + + allStacks := make([]byte, 1<<18) + sl := runtime.Stack(allStacks, true) + if sl == len(allStacks) { + // Try again, once, with a bigger buffer + allStacks = make([]byte, 1<<20) + sl = runtime.Stack(allStacks, true) + } + allStacks = allStacks[:sl] + + fmt.Fprintf(os.Stderr, "\n%s\n\n", allStacks) + fmt.Fprintf(os.Stderr, "Pending Finalizers:\n") + + n := 0 + finalizers.Range(func(k, v interface{}) bool { + n++ + stack := v.([]byte) + fmt.Fprintf(os.Stderr, "\n--finilizer %d: %s\n\n", n, stack) + return true + }) + os.Exit(1) + }() +} + +// stackCache seems to be a bit of an overkill for debug build, but +// some of the tests (e.g. TestSumLoop) allocate many pointers; and +// the time and memory used for that adds up. +const stackCacheMaxSize = 1 << 9 + +var stackCache sync.Map // uintptr -> []byte +var stackCacheSize atomic.Int32 + +func stack(skip int) []byte { + var buf bytes.Buffer + const maxDepth = 32 + pc := make([]uintptr, maxDepth) + // Skip the requested number of frames + the frames for this function + n := runtime.Callers(skip+1, pc) + if n == 0 { + return nil + } + pc = pc[:n] + + if v, ok := stackCache.Load(pc[0]); ok { + return v.([]byte) + } + + // Convert program counters to frames + frames := runtime.CallersFrames(pc) + for { + frame, more := frames.Next() + // Format the frame as "file:line function" + fmt.Fprintf(&buf, "%s:%d %s\n", frame.File, frame.Line, frame.Function) + if !more { + break + } + } + + s := buf.Bytes() + stackCache.Store(pc[0], s) + if stackCacheSize.Add(1) > stackCacheMaxSize { + stackCache.Range(func(k, v interface{}) bool { + stackCache.Delete(k) + return stackCacheSize.Add(-1) > (stackCacheMaxSize >> 1) + }) + } + return s +} diff --git a/wasmer/cptr_nocheck.go b/wasmer/cptr_nocheck.go new file mode 100644 index 0000000..6e59ab7 --- /dev/null +++ b/wasmer/cptr_nocheck.go @@ -0,0 +1,32 @@ +//go:build !memcheck + +package wasmer + +import "runtime" + +func doMkPtr[T comparable](ptr T) CPtrBase[T] { + return CPtrBase[T]{_ptr: ptr} +} + +// maybeStack is a no-op implementation for storing +// ptr creator stack. +type maybeStack struct{} + +// maybeNil is a no-op implementation for indicating +// if initial value of _ptr was nil. +type maybeNil[T comparable] struct{} + +// ptr returns the C pointer stored in this base. +func (b *CPtrBase[T]) ptr() T { + return b._ptr +} + +func doSetFinalizer[T comparable](b *CPtrBase[T], free func(v T)) { + runtime.SetFinalizer(b, func(b *CPtrBase[T]) { + free(b.ptr()) + }) +} + +func doClearFinalizer[T comparable](b *CPtrBase[T]) { + runtime.SetFinalizer(b, nil) +} diff --git a/wasmer/engine.go b/wasmer/engine.go index 6bbd2a3..e04055d 100644 --- a/wasmer/engine.go +++ b/wasmer/engine.go @@ -2,47 +2,39 @@ package wasmer // #include import "C" -import "runtime" // Engine is used by the Store to drive the compilation and the // execution of a WebAssembly module. type Engine struct { - _inner *C.wasm_engine_t + CPtrBase[*C.wasm_engine_t] } func newEngine(engine *C.wasm_engine_t) *Engine { - self := &Engine{ - _inner: engine, - } - - runtime.SetFinalizer(self, func(self *Engine) { - C.wasm_engine_delete(self.inner()) + self := &Engine{CPtrBase: mkPtr(engine)} + self.SetFinalizer(func(self *C.wasm_engine_t) { + C.wasm_engine_delete(self) }) - return self } // NewEngine instantiates and returns a new Engine with the default configuration. // -// engine := NewEngine() -// +// engine := NewEngine() func NewEngine() *Engine { return newEngine(C.wasm_engine_new()) } // NewEngineWithConfig instantiates and returns a new Engine with the given configuration. // -// config := NewConfig() -// engine := NewEngineWithConfig(config) -// +// config := NewConfig() +// engine := NewEngineWithConfig(config) func NewEngineWithConfig(config *Config) *Engine { return newEngine(C.wasm_engine_new_with_config(config.inner())) } // NewUniversalEngine instantiates and returns a new Universal engine. // -// engine := NewUniversalEngine() -// +// engine := NewUniversalEngine() func NewUniversalEngine() *Engine { config := NewConfig() config.UseUniversalEngine() @@ -52,8 +44,7 @@ func NewUniversalEngine() *Engine { // NewDylibEngine instantiates and returns a new Dylib engine. // -// engine := NewDylibEngine() -// +// engine := NewDylibEngine() func NewDylibEngine() *Engine { config := NewConfig() config.UseDylibEngine() @@ -62,7 +53,7 @@ func NewDylibEngine() *Engine { } func (self *Engine) inner() *C.wasm_engine_t { - return self._inner + return self.ptr() } // NewJITEngine is a deprecated function. Please use NewUniversalEngine instead. diff --git a/wasmer/engine_test.go b/wasmer/engine_test.go index fea23cf..e35ec06 100644 --- a/wasmer/engine_test.go +++ b/wasmer/engine_test.go @@ -1,9 +1,10 @@ package wasmer import ( - "github.com/stretchr/testify/assert" "runtime" "testing" + + "github.com/stretchr/testify/assert" ) func testEngine(t *testing.T, engine *Engine) { @@ -14,8 +15,9 @@ func testEngine(t *testing.T, engine *Engine) { instance, err := NewInstance(module, NewImportObject()) assert.NoError(t, err) - sum, err := instance.Exports.GetFunction("sum") + sum, release, err := instance.GetFunctionSafe("sum") assert.NoError(t, err) + defer release(instance) result, err := sum(37, 5) assert.NoError(t, err) @@ -43,7 +45,6 @@ func TestEngineWithTarget(t *testing.T) { assert.NoError(t, err) cpuFeatures := NewCpuFeatures() - assert.NoError(t, err) target := NewTarget(triple, cpuFeatures) diff --git a/wasmer/exporttype_test.go b/wasmer/exporttype_test.go index d19ac6f..78e191f 100644 --- a/wasmer/exporttype_test.go +++ b/wasmer/exporttype_test.go @@ -1,8 +1,9 @@ package wasmer import ( - "github.com/stretchr/testify/assert" "testing" + + "github.com/stretchr/testify/assert" ) func TestExportTypeForFunctionType(t *testing.T) { @@ -46,7 +47,7 @@ func TestExportTypeForTableType(t *testing.T) { limits, err := NewLimits(minimum, maximum) assert.NoError(t, err) - tableType := NewTableType(valueType, limits) + tableType := NewTableType(valueType.release(), limits) name := "foo" exportType := NewExportType(name, tableType) diff --git a/wasmer/extern.go b/wasmer/extern.go index 691c5e5..cbd370a 100644 --- a/wasmer/extern.go +++ b/wasmer/extern.go @@ -7,7 +7,7 @@ import "runtime" // Extern is the runtime representation of an entity that can be // imported or exported. type Extern struct { - _inner *C.wasm_extern_t + CPtrBase[*C.wasm_extern_t] _ownedBy interface{} } @@ -18,10 +18,10 @@ type IntoExtern interface { } func newExtern(pointer *C.wasm_extern_t, ownedBy interface{}) *Extern { - extern := &Extern{_inner: pointer, _ownedBy: ownedBy} + extern := &Extern{CPtrBase: mkPtr(pointer), _ownedBy: ownedBy} if ownedBy == nil { - runtime.SetFinalizer(extern, func(extern *Extern) { + extern.SetFinalizer(func(v *C.wasm_extern_t) { C.wasm_extern_delete(extern.inner()) }) } @@ -30,7 +30,7 @@ func newExtern(pointer *C.wasm_extern_t, ownedBy interface{}) *Extern { } func (self *Extern) inner() *C.wasm_extern_t { - return self._inner + return self.ptr() } func (self *Extern) ownedBy() interface{} { @@ -47,8 +47,8 @@ func (self *Extern) IntoExtern() *Extern { // Kind returns the Extern's ExternKind. // -// global, _ := instance.Exports.GetGlobal("exported_global") -// _ = global.IntoExtern().Kind() +// global, _ := instance.Exports.GetGlobal("exported_global") +// _ = global.IntoExtern().Kind() func (self *Extern) Kind() ExternKind { kind := ExternKind(C.wasm_extern_kind(self.inner())) @@ -59,8 +59,8 @@ func (self *Extern) Kind() ExternKind { // Type returns the Extern's ExternType. // -// global, _ := instance.Exports.GetGlobal("exported_global") -// _ = global.IntoExtern().Type() +// global, _ := instance.Exports.GetGlobal("exported_global") +// _ = global.IntoExtern().Type() func (self *Extern) Type() *ExternType { ty := C.wasm_extern_type(self.inner()) @@ -74,9 +74,9 @@ func (self *Extern) Type() *ExternType { // Note:️ If the Extern is not a Function, IntoFunction will return nil // as its result. // -// function, _ := instance.Exports.GetFunction("exported_function") -// extern = function.IntoExtern() -// _ := extern.IntoFunction() +// function, _ := instance.Exports.GetFunction("exported_function") +// extern = function.IntoExtern() +// _ := extern.IntoFunction() func (self *Extern) IntoFunction() *Function { pointer := C.wasm_extern_as_func(self.inner()) @@ -92,9 +92,9 @@ func (self *Extern) IntoFunction() *Function { // Note:️ If the Extern is not a Global, IntoGlobal will return nil as // its result. // -// global, _ := instance.Exports.GetGlobal("exported_global") -// extern = global.IntoExtern() -// _ := extern.IntoGlobal() +// global, _ := instance.Exports.GetGlobal("exported_global") +// extern = global.IntoExtern() +// _ := extern.IntoGlobal() func (self *Extern) IntoGlobal() *Global { pointer := C.wasm_extern_as_global(self.inner()) @@ -110,9 +110,9 @@ func (self *Extern) IntoGlobal() *Global { // Note:️ If the Extern is not a Table, IntoTable will return nil as // its result. // -// table, _ := instance.Exports.GetTable("exported_table") -// extern = table.IntoExtern() -// _ := extern.IntoTable() +// table, _ := instance.Exports.GetTable("exported_table") +// extern = table.IntoExtern() +// _ := extern.IntoTable() func (self *Extern) IntoTable() *Table { pointer := C.wasm_extern_as_table(self.inner()) @@ -128,9 +128,9 @@ func (self *Extern) IntoTable() *Table { // Note:️ If the Extern is not a Memory, IntoMemory will return nil as // its result. // -// memory, _ := instance.Exports.GetMemory("exported_memory") -// extern = memory.IntoExtern() -// _ := extern.IntoMemory() +// memory, _ := instance.Exports.GetMemory("exported_memory") +// extern = memory.IntoExtern() +// _ := extern.IntoMemory() func (self *Extern) IntoMemory() *Memory { pointer := C.wasm_extern_as_memory(self.inner()) diff --git a/wasmer/function_test.go b/wasmer/function_test.go index bbe188a..abcd94d 100644 --- a/wasmer/function_test.go +++ b/wasmer/function_test.go @@ -1,8 +1,11 @@ package wasmer import ( - "github.com/stretchr/testify/assert" + "runtime" "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestRawFunction(t *testing.T) { @@ -26,6 +29,8 @@ func TestRawFunction(t *testing.T) { assert.NoError(t, err) sum, err := instance.Exports.GetRawFunction("sum") + defer runtime.KeepAlive(instance) // Keep instance around as long as sum is around. + assert.NoError(t, err) assert.Equal(t, sum.ParameterArity(), uint(2)) assert.Equal(t, sum.ResultArity(), uint(1)) @@ -57,6 +62,7 @@ func TestFunctionNative(t *testing.T) { sum, err := instance.Exports.GetFunction("sum") assert.NoError(t, err) + defer runtime.KeepAlive(instance) // Keep instance around as long as sum is around. result, err := sum(1, 2) assert.NoError(t, err) @@ -80,8 +86,9 @@ func TestFunctionCallReturnZeroValue(t *testing.T) { instance, err := NewInstance(module, NewImportObject()) assert.NoError(t, err) - test, err := instance.Exports.GetFunction("test") + test, release, err := instance.GetFunctionSafe("test") assert.NoError(t, err) + defer release(instance) result, err := test(1, 2) assert.NoError(t, err) @@ -107,8 +114,9 @@ func TestFunctionCallReturnMultipleValues(t *testing.T) { instance, err := NewInstance(module, NewImportObject()) assert.NoError(t, err) - swap, err := instance.Exports.GetFunction("swap") + swap, release, err := instance.GetFunctionSafe("swap") assert.NoError(t, err) + defer release(instance) results, err := swap(41, 42) assert.NoError(t, err) @@ -118,7 +126,10 @@ func TestFunctionCallReturnMultipleValues(t *testing.T) { func TestFunctionSum(t *testing.T) { instance := testGetInstance(t) - f, _ := instance.Exports.GetFunction("sum") + f, release, err := instance.GetFunctionSafe("sum") + require.NoError(t, err) + defer release(instance) + result, err := f(1, 2) assert.NoError(t, err) assert.Equal(t, result, int32(3)) @@ -128,6 +139,8 @@ func TestFunctionArity0(t *testing.T) { instance := testGetInstance(t) f, _ := instance.Exports.GetFunction("arity_0") + defer runtime.KeepAlive(instance) // Keep instance around as long as f is around. + result, err := f() assert.NoError(t, err) assert.Equal(t, result, int32(42)) @@ -137,6 +150,8 @@ func TestFunctionI32I32(t *testing.T) { instance := testGetInstance(t) f, _ := instance.Exports.GetFunction("i32_i32") + defer runtime.KeepAlive(instance) // Keep instance around as long as f is around. + result, err := f(7) assert.NoError(t, err) assert.Equal(t, result, int32(7)) @@ -167,6 +182,8 @@ func TestFunctionI64I64(t *testing.T) { instance := testGetInstance(t) f, _ := instance.Exports.GetFunction("i64_i64") + defer runtime.KeepAlive(instance) // Keep instance around as long as f is around. + result, err := f(7) assert.NoError(t, err) assert.Equal(t, result, int64(7)) @@ -200,6 +217,8 @@ func TestFunctionF32F32(t *testing.T) { instance := testGetInstance(t) f, _ := instance.Exports.GetFunction("f32_f32") + defer runtime.KeepAlive(instance) // Keep instance around as long as f is around. + result, err := f(float32(7.42)) assert.NoError(t, err) assert.Equal(t, result, float32(7.42)) @@ -209,6 +228,8 @@ func TestFunctionF64F64(t *testing.T) { instance := testGetInstance(t) f, _ := instance.Exports.GetFunction("f64_f64") + defer runtime.KeepAlive(instance) + result, err := f(7.42) assert.NoError(t, err) assert.Equal(t, result, float64(7.42)) @@ -221,6 +242,8 @@ func TestFunctionI32I64F32F64F64(t *testing.T) { instance := testGetInstance(t) f, _ := instance.Exports.GetFunction("i32_i64_f32_f64_f64") + defer runtime.KeepAlive(instance) + result, err := f(1, 2, float32(3.4), 5.6) assert.NoError(t, err) assert.Equal(t, float64(int(result.(float64)*10000000))/10000000, 1+2+3.4+5.6) @@ -230,6 +253,7 @@ func TestFunctionBoolCastedtoI32(t *testing.T) { instance := testGetInstance(t) f, _ := instance.Exports.GetFunction("bool_casted_to_i32") + defer runtime.KeepAlive(instance) result, err := f() assert.NoError(t, err) assert.Equal(t, result, int32(1)) @@ -275,6 +299,7 @@ func TestHostFunction(t *testing.T) { addOne, err := instance.Exports.GetFunction("add_one") assert.NoError(t, err) + defer runtime.KeepAlive(instance) result, err := addOne(41) assert.NoError(t, err) @@ -319,8 +344,9 @@ func TestHostFunction_WithI64(t *testing.T) { instance, err := NewInstance(module, importObject) assert.NoError(t, err) - addOne, err := instance.Exports.GetFunction("add_one") + addOne, release, err := instance.GetFunctionSafe("add_one") assert.NoError(t, err) + defer release(instance) result, err := addOne(41) assert.NoError(t, err) @@ -381,8 +407,9 @@ func TestHostFunctionWithEnv(t *testing.T) { environment.instance = instance - addOne, err := instance.Exports.GetFunction("add_one") + addOne, release, err := instance.GetFunctionSafe("add_one") assert.NoError(t, err) + defer release(instance) result, err := addOne(7) assert.NoError(t, err) @@ -502,8 +529,9 @@ func TestHostFunctionTrap(t *testing.T) { instance, err := NewInstance(module, importObject) assert.NoError(t, err) - addOne, err := instance.Exports.GetFunction("add_one") + addOne, release, err := instance.GetFunctionSafe("add_one") assert.NoError(t, err) + defer release(instance) _, err = addOne(41) assert.IsType(t, err, &TrapError{}) diff --git a/wasmer/functiontype_test.go b/wasmer/functiontype_test.go index e8431d5..02b168f 100644 --- a/wasmer/functiontype_test.go +++ b/wasmer/functiontype_test.go @@ -1,8 +1,9 @@ package wasmer import ( - "github.com/stretchr/testify/assert" "testing" + + "github.com/stretchr/testify/assert" ) func TestFunctionType(t *testing.T) { diff --git a/wasmer/global.go b/wasmer/global.go index 6a6271b..a451b33 100644 --- a/wasmer/global.go +++ b/wasmer/global.go @@ -8,20 +8,20 @@ import ( // Global stores a single value of the given GlobalType. // -// See also +// # See also // // https://webassembly.github.io/spec/core/syntax/modules.html#globals -// type Global struct { - _inner *C.wasm_global_t + CPtrBase[*C.wasm_global_t] + typ *GlobalType _ownedBy interface{} } func newGlobal(pointer *C.wasm_global_t, ownedBy interface{}) *Global { - global := &Global{_inner: pointer, _ownedBy: ownedBy} + global := &Global{CPtrBase: mkPtr(pointer), _ownedBy: ownedBy} if ownedBy == nil { - runtime.SetFinalizer(global, func(global *Global) { + global.SetFinalizer(func(v *C.wasm_global_t) { C.wasm_global_delete(global.inner()) }) } @@ -33,10 +33,9 @@ func newGlobal(pointer *C.wasm_global_t, ownedBy interface{}) *Global { // // It takes three arguments, the Store, the GlobalType and the Value for the Global. // -// valueType := NewValueType(I32) -// globalType := NewGlobalType(valueType, CONST) -// global := NewGlobal(store, globalType, NewValue(42, I32)) -// +// valueType := NewValueType(I32) +// globalType := NewGlobalType(valueType, CONST) +// global := NewGlobal(store, globalType, NewValue(42, I32)) func NewGlobal(store *Store, ty *GlobalType, value Value) *Global { pointer := C.wasm_global_new( store.inner(), @@ -48,7 +47,7 @@ func NewGlobal(store *Store, ty *GlobalType, value Value) *Global { } func (self *Global) inner() *C.wasm_global_t { - return self._inner + return self.ptr() } func (self *Global) ownedBy() interface{} { @@ -61,9 +60,8 @@ func (self *Global) ownedBy() interface{} { // IntoExtern converts the Global into an Extern. // -// global, _ := instance.Exports.GetGlobal("exported_global") -// extern := global.IntoExtern() -// +// global, _ := instance.Exports.GetGlobal("exported_global") +// extern := global.IntoExtern() func (self *Global) IntoExtern() *Extern { pointer := C.wasm_global_as_extern(self.inner()) @@ -72,24 +70,26 @@ func (self *Global) IntoExtern() *Extern { // Type returns the Global's GlobalType. // -// global, _ := instance.Exports.GetGlobal("exported_global") -// ty := global.Type() -// +// global, _ := instance.Exports.GetGlobal("exported_global") +// ty := global.Type() func (self *Global) Type() *GlobalType { - ty := C.wasm_global_type(self.inner()) + defer runtime.KeepAlive(self) - runtime.KeepAlive(self) + if self.typ == nil { + ptr := self.inner() + ty := C.wasm_global_type(ptr) + self.typ = newGlobalType(ty, self.ownedBy()) + } - return newGlobalType(ty, self.ownedBy()) + return self.typ } // Set sets the Global's value. // // It takes two arguments, the Global's value as a native Go value and the value's ValueKind. // -// global, _ := instance.Exports.GetGlobal("exported_global") -// _ = global.Set(1, I32) -// +// global, _ := instance.Exports.GetGlobal("exported_global") +// _ = global.Set(1, I32) func (self *Global) Set(value interface{}, kind ValueKind) error { if self.Type().Mutability() == IMMUTABLE { return newErrorWith("The global variable is not mutable, cannot set a new value") @@ -109,9 +109,8 @@ func (self *Global) Set(value interface{}, kind ValueKind) error { // Get returns the Global's value as a native Go value. // -// global, _ := instance.Exports.GetGlobal("exported_global") -// value, _ := global.Get() -// +// global, _ := instance.Exports.GetGlobal("exported_global") +// value, _ := global.Get() func (self *Global) Get() (interface{}, error) { var value C.wasm_val_t diff --git a/wasmer/global_test.go b/wasmer/global_test.go index 364c0ac..17839ee 100644 --- a/wasmer/global_test.go +++ b/wasmer/global_test.go @@ -1,8 +1,10 @@ package wasmer import ( - "github.com/stretchr/testify/assert" + "runtime" "testing" + + "github.com/stretchr/testify/assert" ) var TestBytes = []byte(` @@ -19,7 +21,7 @@ var TestBytes = []byte(` (i32.add (global.get $x) (i32.const 1))))) `) -func testGetGlobalInstance(t *testing.T) *Instance { +func testGetGlobalInstance(t *testing.T) (*Instance, func()) { engine := NewEngine() store := NewStore(engine) module, err := NewModule(store, TestBytes) @@ -28,11 +30,17 @@ func testGetGlobalInstance(t *testing.T) *Instance { instance, err := NewInstance(module, NewImportObject()) assert.NoError(t, err) - return instance + return instance, func() { + runtime.KeepAlive(store) + runtime.KeepAlive(module) + runtime.KeepAlive(instance) + } } func TestGlobalGetType(t *testing.T) { - x, err := testGetGlobalInstance(t).Exports.GetGlobal("x") + inst, release := testGetGlobalInstance(t) + defer release() + x, err := inst.Exports.GetGlobal("x") assert.NoError(t, err) ty := x.Type() @@ -41,7 +49,9 @@ func TestGlobalGetType(t *testing.T) { } func TestGlobalMutable(t *testing.T) { - exports := testGetGlobalInstance(t).Exports + inst, release := testGetGlobalInstance(t) + defer release() + exports := inst.Exports x, err := exports.GetGlobal("x") assert.NoError(t, err) @@ -49,15 +59,17 @@ func TestGlobalMutable(t *testing.T) { y, err := exports.GetGlobal("y") assert.NoError(t, err) - assert.Equal(t, y.Type().Mutability(), MUTABLE) + assert.Equal(t, MUTABLE, y.Type().Mutability()) z, err := exports.GetGlobal("z") assert.NoError(t, err) - assert.Equal(t, z.Type().Mutability(), IMMUTABLE) + assert.Equal(t, IMMUTABLE, z.Type().Mutability()) } func TestGlobalReadWrite(t *testing.T) { - y, err := testGetGlobalInstance(t).Exports.GetGlobal("y") + inst, release := testGetGlobalInstance(t) + defer release() + y, err := inst.Exports.GetGlobal("y") assert.NoError(t, err) inititalValue, err := y.Get() @@ -73,7 +85,8 @@ func TestGlobalReadWrite(t *testing.T) { } func TestGlobalReadWriteAndExportedFunctions(t *testing.T) { - instance := testGetGlobalInstance(t) + instance, release := testGetGlobalInstance(t) + defer release() x, err := instance.Exports.GetGlobal("x") assert.NoError(t, err) @@ -103,7 +116,9 @@ func TestGlobalReadWriteAndExportedFunctions(t *testing.T) { } func TestGlobalReadWriteConstant(t *testing.T) { - z, err := testGetGlobalInstance(t).Exports.GetGlobal("z") + inst, release := testGetGlobalInstance(t) + defer release() + z, err := inst.Exports.GetGlobal("z") assert.NoError(t, err) value, err := z.Get() diff --git a/wasmer/globaltype.go b/wasmer/globaltype.go index 276a69c..64b345c 100644 --- a/wasmer/globaltype.go +++ b/wasmer/globaltype.go @@ -16,9 +16,8 @@ const ( // String returns the GlobalMutability as a string. // -// IMMUTABLE.String() // "const" -// MUTABLE.String() // "var" -// +// IMMUTABLE.String() // "const" +// MUTABLE.String() // "var" func (self GlobalMutability) String() string { switch self { case IMMUTABLE: @@ -31,20 +30,19 @@ func (self GlobalMutability) String() string { // GlobalType classifies global variables, which hold a value and can either be mutable or immutable. // -// See also +// # See also // // Specification: https://webassembly.github.io/spec/core/syntax/types.html#global-types -// type GlobalType struct { - _inner *C.wasm_globaltype_t + CPtrBase[*C.wasm_globaltype_t] _ownedBy interface{} } func newGlobalType(pointer *C.wasm_globaltype_t, ownedBy interface{}) *GlobalType { - globalType := &GlobalType{_inner: pointer, _ownedBy: ownedBy} + globalType := &GlobalType{CPtrBase: mkPtr(pointer), _ownedBy: ownedBy} if ownedBy == nil { - runtime.SetFinalizer(globalType, func(globalType *GlobalType) { + globalType.SetFinalizer(func(v *C.wasm_globaltype_t) { C.wasm_globaltype_delete(globalType.inner()) }) } @@ -54,9 +52,8 @@ func newGlobalType(pointer *C.wasm_globaltype_t, ownedBy interface{}) *GlobalTyp // NewGlobalType instantiates a new GlobalType from a ValueType and a GlobalMutability // -// valueType := NewValueType(I32) -// globalType := NewGlobalType(valueType, IMMUTABLE) -// +// valueType := NewValueType(I32) +// globalType := NewGlobalType(valueType, IMMUTABLE) func NewGlobalType(valueType *ValueType, mutability GlobalMutability) *GlobalType { pointer := C.wasm_globaltype_new(valueType.inner(), C.wasm_mutability_t(mutability)) @@ -64,7 +61,7 @@ func NewGlobalType(valueType *ValueType, mutability GlobalMutability) *GlobalTyp } func (self *GlobalType) inner() *C.wasm_globaltype_t { - return self._inner + return self.ptr() } func (self *GlobalType) ownedBy() interface{} { @@ -77,10 +74,9 @@ func (self *GlobalType) ownedBy() interface{} { // ValueType returns the GlobalType's ValueType // -// valueType := NewValueType(I32) -// globalType := NewGlobalType(valueType, IMMUTABLE) -// globalType.ValueType().Kind().String() // "i32" -// +// valueType := NewValueType(I32) +// globalType := NewGlobalType(valueType, IMMUTABLE) +// globalType.ValueType().Kind().String() // "i32" func (self *GlobalType) ValueType() *ValueType { pointer := C.wasm_globaltype_content(self.inner()) @@ -91,10 +87,9 @@ func (self *GlobalType) ValueType() *ValueType { // Mutability returns the GlobalType's GlobalMutability // -// valueType := NewValueType(I32) -// globalType := NewGlobalType(valueType, IMMUTABLE) -// globalType.Mutability().String() // "const" -// +// valueType := NewValueType(I32) +// globalType := NewGlobalType(valueType, IMMUTABLE) +// globalType.Mutability().String() // "const" func (self *GlobalType) Mutability() GlobalMutability { mutability := GlobalMutability(C.wasm_globaltype_mutability(self.inner())) @@ -105,10 +100,9 @@ func (self *GlobalType) Mutability() GlobalMutability { // IntoExternType converts the GlobalType into an ExternType. // -// valueType := NewValueType(I32) -// globalType := NewGlobalType(valueType, IMMUTABLE) -// externType = globalType.IntoExternType() -// +// valueType := NewValueType(I32) +// globalType := NewGlobalType(valueType, IMMUTABLE) +// externType = globalType.IntoExternType() func (self *GlobalType) IntoExternType() *ExternType { pointer := C.wasm_globaltype_as_externtype_const(self.inner()) diff --git a/wasmer/importtype.go b/wasmer/importtype.go index 5062053..1b6e603 100644 --- a/wasmer/importtype.go +++ b/wasmer/importtype.go @@ -50,16 +50,16 @@ func (self *importTypes) close() { // ImportType is a descriptor for an imported value into a WebAssembly // module. type ImportType struct { - _inner *C.wasm_importtype_t + CPtrBase[*C.wasm_importtype_t] _ownedBy interface{} } func newImportType(pointer *C.wasm_importtype_t, ownedBy interface{}) *ImportType { - importType := &ImportType{_inner: pointer, _ownedBy: ownedBy} + importType := &ImportType{CPtrBase: mkPtr(pointer), _ownedBy: ownedBy} if ownedBy == nil { - runtime.SetFinalizer(importType, func(self *ImportType) { - self.Close() + importType.SetFinalizer(func(importType *C.wasm_importtype_t) { + C.wasm_importtype_delete(importType) }) } @@ -72,10 +72,9 @@ func newImportType(pointer *C.wasm_importtype_t, ownedBy interface{}) *ImportTyp // Note:️ An extern type is anything implementing IntoExternType: // FunctionType, GlobalType, MemoryType, TableType. // -// valueType := NewValueType(I32) -// globalType := NewGlobalType(valueType, CONST) -// importType := NewImportType("ns", "host_global", globalType) -// +// valueType := NewValueType(I32) +// globalType := NewGlobalType(valueType, CONST) +// importType := NewImportType("ns", "host_global", globalType) func NewImportType(module string, name string, ty IntoExternType) *ImportType { moduleName := newName(module) nameName := newName(name) @@ -90,7 +89,7 @@ func NewImportType(module string, name string, ty IntoExternType) *ImportType { } func (self *ImportType) inner() *C.wasm_importtype_t { - return self._inner + return self.ptr() } func (self *ImportType) ownedBy() interface{} { @@ -103,11 +102,10 @@ func (self *ImportType) ownedBy() interface{} { // Module returns the ImportType's module name (or namespace). // -// valueType := NewValueType(I32) -// globalType := NewGlobalType(valueType, CONST) -// importType := NewImportType("ns", "host_global", globalType) -// _ = importType.Module() -// +// valueType := NewValueType(I32) +// globalType := NewGlobalType(valueType, CONST) +// importType := NewImportType("ns", "host_global", globalType) +// _ = importType.Module() func (self *ImportType) Module() string { byteVec := C.wasm_importtype_module(self.inner()) module := C.GoStringN(byteVec.data, C.int(byteVec.size)) @@ -119,11 +117,10 @@ func (self *ImportType) Module() string { // Name returns the ImportType's name. // -// valueType := NewValueType(I32) -// globalType := NewGlobalType(valueType, CONST) -// importType := NewImportType("ns", "host_global", globalType) -// _ = importType.Name() -// +// valueType := NewValueType(I32) +// globalType := NewGlobalType(valueType, CONST) +// importType := NewImportType("ns", "host_global", globalType) +// _ = importType.Name() func (self *ImportType) Name() string { byteVec := C.wasm_importtype_name(self.inner()) name := C.GoStringN(byteVec.data, C.int(byteVec.size)) @@ -135,11 +132,10 @@ func (self *ImportType) Name() string { // Type returns the ImportType's type as an ExternType. // -// valueType := NewValueType(I32) -// globalType := NewGlobalType(valueType, CONST) -// importType := NewImportType("ns", "host_global", globalType) -// _ = importType.Type() -// +// valueType := NewValueType(I32) +// globalType := NewGlobalType(valueType, CONST) +// importType := NewImportType("ns", "host_global", globalType) +// _ = importType.Type() func (self *ImportType) Type() *ExternType { ty := C.wasm_importtype_type(self.inner()) diff --git a/wasmer/importtype_test.go b/wasmer/importtype_test.go index f60585b..0cb39ca 100644 --- a/wasmer/importtype_test.go +++ b/wasmer/importtype_test.go @@ -1,8 +1,10 @@ package wasmer import ( - "github.com/stretchr/testify/assert" + "runtime" "testing" + + "github.com/stretchr/testify/assert" ) func TestImportTypeForFunctionType(t *testing.T) { @@ -50,7 +52,7 @@ func TestImportTypeForTableType(t *testing.T) { limits, err := NewLimits(minimum, maximum) assert.NoError(t, err) - tableType := NewTableType(valueType, limits) + tableType := NewTableType(valueType.release(), limits) module := "foo" name := "bar" @@ -68,6 +70,15 @@ func TestImportTypeForTableType(t *testing.T) { limitsAgain := tableTypeAgain.Limits() assert.Equal(t, limitsAgain.Minimum(), minimum) assert.Equal(t, limitsAgain.Maximum(), maximum) + + runtime.KeepAlive(valueType) + runtime.KeepAlive(limits) + runtime.KeepAlive(tableType) + runtime.KeepAlive(importType) + runtime.KeepAlive(externType) + runtime.KeepAlive(tableTypeAgain) + runtime.KeepAlive(valueTypeAgain) + runtime.KeepAlive(limitsAgain) } func TestImportTypeForMemoryType(t *testing.T) { diff --git a/wasmer/instance.go b/wasmer/instance.go index a6e5ed2..4489d14 100644 --- a/wasmer/instance.go +++ b/wasmer/instance.go @@ -5,7 +5,7 @@ import "C" import "runtime" type Instance struct { - _inner *C.wasm_instance_t + CPtrBase[*C.wasm_instance_t] Exports *Exports // without this, imported functions may be freed before execution of an exported function is complete. @@ -55,9 +55,9 @@ func NewInstance(module *Module, imports *ImportObject) (*Instance, error) { } self := &Instance{ - _inner: instance, - Exports: newExports(instance, module), - imports: imports, + CPtrBase: mkPtr(instance), + Exports: newExports(instance, module), + imports: imports, } runtime.SetFinalizer(self, func(self *Instance) { @@ -68,22 +68,22 @@ func NewInstance(module *Module, imports *ImportObject) (*Instance, error) { } func (self *Instance) inner() *C.wasm_instance_t { - return self._inner + return self.ptr() } // GetRemainingPoints exposes wasm meterings remaining gas or points func (self *Instance) GetRemainingPoints() uint64 { - return uint64(C.wasmer_metering_get_remaining_points(self._inner)) + return uint64(C.wasmer_metering_get_remaining_points(self.ptr())) } // GetRemainingPoints a bool to determine if the engine has been shutdown from meter exhaustion func (self *Instance) MeteringPointsExhausted() bool { - return bool(C.wasmer_metering_points_are_exhausted(self._inner)) + return bool(C.wasmer_metering_points_are_exhausted(self.ptr())) } // SetRemainingPoints imposes a new gas limit on the wasm engine func (self *Instance) SetRemainingPoints(newLimit uint64) { - C.wasmer_metering_set_remaining_points(self._inner, C.uint64_t(newLimit)) + C.wasmer_metering_set_remaining_points(self.ptr(), C.uint64_t(newLimit)) } // ReleaseFn is a function to release resources diff --git a/wasmer/instance_test.go b/wasmer/instance_test.go index ec43826..7f92610 100644 --- a/wasmer/instance_test.go +++ b/wasmer/instance_test.go @@ -1,8 +1,10 @@ package wasmer import ( - "github.com/stretchr/testify/assert" + "runtime" "testing" + + "github.com/stretchr/testify/assert" ) func TestInstance(t *testing.T) { @@ -88,6 +90,18 @@ func TestInstanceMissingImports(t *testing.T) { } func TestInstanceTraps(t *testing.T) { + if runtime.GOOS == "darwin" { + // This test fails on darwin (when executed repeated) with the following error: + // signal 16 received but handler not on signal stack + // mp.gsignal stack .... + // fatal error: non-Go code set up signal handler without SA_ONSTACK flag + // + // This is a bit strange since + // https://github.com/wasmerio/wasmer/blob/cfb9413a670a0123f4f403ecf1897257fb681e72/lib/vm/src/trap/traphandlers.rs#L163 + // appears to set this flag; but maybe it's not applied to darwin? + t.Skip("skipping test on non-linux platforms") + } + engine := NewEngine() store := NewStore(engine) module, err := NewModule( diff --git a/wasmer/memory_test.go b/wasmer/memory_test.go index 0049ecf..e647342 100644 --- a/wasmer/memory_test.go +++ b/wasmer/memory_test.go @@ -70,6 +70,7 @@ func TestMemoryData(t *testing.T) { getString, err := instance.Exports.GetFunction("string") assert.NoError(t, err) + defer runtime.KeepAlive(instance) ptr, err := getString() assert.NoError(t, err) @@ -139,11 +140,13 @@ func TestSumLoop(t *testing.T) { // if KeepAlive call removed. runtime.GC() - hi := 10240 + // Number of iterations is set fairly low because the overhead + // of using `-tags memcheck` (and runtime.GC) adds up rather quickly. + hi := 128 n := int32(0) for i := range hi { + // t.Logf("Iteration %d: sum=%d", i, n) res, err := sum(n, i+1) - //runtime.GC() require.NoError(t, err) n = res.(int32) } diff --git a/wasmer/module.go b/wasmer/module.go index 9cc581d..9297ab9 100644 --- a/wasmer/module.go +++ b/wasmer/module.go @@ -47,12 +47,13 @@ import ( // initialization logic in the form of data and element segments or a // start function. // -// See also +// # See also // // Specification: https://webassembly.github.io/spec/core/syntax/modules.html#modules type Module struct { - _inner *C.wasm_module_t - store *Store + CPtrBase[*C.wasm_module_t] + + store *Store // Stored if computed to avoid further reallocations. importTypes *importTypes // Stored if computed to avoid further reallocations. @@ -64,10 +65,10 @@ type Module struct { // It takes two arguments, the Store and the Wasm module as a byte // array of WAT code. // -// wasmBytes := []byte(`...`) -// engine := wasmer.NewEngine() -// store := wasmer.NewStore(engine) -// module, err := wasmer.NewModule(store, wasmBytes) +// wasmBytes := []byte(`...`) +// engine := wasmer.NewEngine() +// store := wasmer.NewStore(engine) +// module, err := wasmer.NewModule(store, wasmBytes) func NewModule(store *Store, bytes []byte) (*Module, error) { wasmBytes, err := Wat2Wasm(string(bytes)) @@ -86,11 +87,13 @@ func NewModule(store *Store, bytes []byte) (*Module, error) { err2 := maybeNewErrorFromWasmer(func() bool { self = &Module{ - _inner: C.to_wasm_module_new(store.inner(), wasmBytesPtr, C.size_t(wasmBytesLength)), - store: store, + CPtrBase: mkPtr(C.to_wasm_module_new( + store.inner(), wasmBytesPtr, C.size_t(wasmBytesLength)), + ), + store: store, } - return self._inner == nil + return self.ptr() == nil }) if err2 != nil { @@ -110,12 +113,12 @@ func NewModule(store *Store, bytes []byte) (*Module, error) { // byte array. The function returns an error describing why the bytes // are invalid, otherwise it returns nil. // -// wasmBytes := []byte(`...`) -// engine := wasmer.NewEngine() -// store := wasmer.NewStore(engine) -// err := wasmer.ValidateModule(store, wasmBytes) +// wasmBytes := []byte(`...`) +// engine := wasmer.NewEngine() +// store := wasmer.NewStore(engine) +// err := wasmer.ValidateModule(store, wasmBytes) // -// isValid := err != nil +// isValid := err != nil func ValidateModule(store *Store, bytes []byte) error { wasmBytes, err := Wat2Wasm(string(bytes)) @@ -145,18 +148,18 @@ func ValidateModule(store *Store, bytes []byte) error { } func (self *Module) inner() *C.wasm_module_t { - return self._inner + return self.ptr() } // Name returns the Module's name. // // Note:️ This is not part of the standard Wasm C API. It is Wasmer specific. // -// wasmBytes := []byte(`(module $moduleName)`) -// engine := wasmer.NewEngine() -// store := wasmer.NewStore(engine) -// module, _ := wasmer.NewModule(store, wasmBytes) -// name := module.Name() +// wasmBytes := []byte(`(module $moduleName)`) +// engine := wasmer.NewEngine() +// store := wasmer.NewStore(engine) +// module, _ := wasmer.NewModule(store, wasmBytes) +// name := module.Name() func (self *Module) Name() string { var name C.wasm_name_t @@ -171,11 +174,11 @@ func (self *Module) Name() string { // Imports returns the Module's imports as an ImportType array. // -// wasmBytes := []byte(`...`) -// engine := wasmer.NewEngine() -// store := wasmer.NewStore(engine) -// module, _ := wasmer.NewModule(store, wasmBytes) -// imports := module.Imports() +// wasmBytes := []byte(`...`) +// engine := wasmer.NewEngine() +// store := wasmer.NewStore(engine) +// module, _ := wasmer.NewModule(store, wasmBytes) +// imports := module.Imports() func (self *Module) Imports() []*ImportType { if nil == self.importTypes { self.importTypes = newImportTypes(self) @@ -184,13 +187,35 @@ func (self *Module) Imports() []*ImportType { return self.importTypes.importTypes } +// ImportsSafe is the same as Imports method but returns a release +// method that should be invoked with self as the argument. +// +// This is necessary because the following code is not safe: +// +// module := NewModule() +// imports := module.Imports() // This is the last time `module` is used +// // Use imports +// if len(imports) > 0 { ...} +// +// Module finalizer may run at any point after the last use of module. +// As a result, the underlying C memory may be collected by GC. +// To avoid this, use ImportsSafe: +// +// module := NewModule() +// imports, release := module.ImportsSafe() +// defer release(module) +// // Use imports +func (self *Module) ImportsSafe() ([]*ImportType, ReleaseFn[*Module]) { + return self.Imports(), keepAlive +} + // Exports returns the Module's exports as an ExportType array. // -// wasmBytes := []byte(`...`) -// engine := wasmer.NewEngine() -// store := wasmer.NewStore(engine) -// module, _ := wasmer.NewModule(store, wasmBytes) -// exports := module.Exports() +// wasmBytes := []byte(`...`) +// engine := wasmer.NewEngine() +// store := wasmer.NewStore(engine) +// module, _ := wasmer.NewModule(store, wasmBytes) +// exports := module.Exports() func (self *Module) Exports() []*ExportType { if nil == self.exportTypes { self.exportTypes = newExportTypes(self) @@ -199,13 +224,19 @@ func (self *Module) Exports() []*ExportType { return self.exportTypes.exportTypes } +// ExportsSafe is similar to Exports method but returns a release function +// to ensure the Module is not garbage collected. +func (self *Module) ExportsSafe() ([]*ExportType, ReleaseFn[*Module]) { + return self.Exports(), keepAlive +} + // Serialize serializes the module and returns the Wasm code as an byte array. // -// wasmBytes := []byte(`...`) -// engine := wasmer.NewEngine() -// store := wasmer.NewStore(engine) -// module, _ := wasmer.NewModule(store, wasmBytes) -// bytes, err := module.Serialize() +// wasmBytes := []byte(`...`) +// engine := wasmer.NewEngine() +// store := wasmer.NewStore(engine) +// module, _ := wasmer.NewModule(store, wasmBytes) +// bytes, err := module.Serialize() func (self *Module) Serialize() ([]byte, error) { var bytes C.wasm_byte_vec_t @@ -227,13 +258,13 @@ func (self *Module) Serialize() ([]byte, error) { // DeserializeModule deserializes an byte array to a Module. // -// wasmBytes := []byte(`...`) -// engine := wasmer.NewEngine() -// store := wasmer.NewStore(engine) -// module, _ := wasmer.NewModule(store, wasmBytes) -// bytes, err := module.Serialize() -// //... -// deserializedModule, err := wasmer.DeserializeModule(store, bytes) +// wasmBytes := []byte(`...`) +// engine := wasmer.NewEngine() +// store := wasmer.NewStore(engine) +// module, _ := wasmer.NewModule(store, wasmBytes) +// bytes, err := module.Serialize() +// //... +// deserializedModule, err := wasmer.DeserializeModule(store, bytes) func DeserializeModule(store *Store, bytes []byte) (*Module, error) { var bytesPtr *C.uint8_t bytesLength := len(bytes) @@ -246,19 +277,21 @@ func DeserializeModule(store *Store, bytes []byte) (*Module, error) { err := maybeNewErrorFromWasmer(func() bool { self = &Module{ - _inner: C.to_wasm_module_deserialize(store.inner(), bytesPtr, C.size_t(bytesLength)), - store: store, + CPtrBase: mkPtr(C.to_wasm_module_deserialize( + store.inner(), bytesPtr, C.size_t(bytesLength)), + ), + store: store, } - return self._inner == nil + return self.ptr() == nil }) if err != nil { return nil, err } - runtime.SetFinalizer(self, func(self *Module) { - C.wasm_module_delete(self.inner()) + self.SetFinalizer(func(v *C.wasm_module_t) { + C.wasm_module_delete(v) }) return self, nil diff --git a/wasmer/module_test.go b/wasmer/module_test.go index d82040f..120d588 100644 --- a/wasmer/module_test.go +++ b/wasmer/module_test.go @@ -1,8 +1,9 @@ package wasmer import ( - "github.com/stretchr/testify/assert" "testing" + + "github.com/stretchr/testify/assert" ) func TestModule(t *testing.T) { @@ -39,6 +40,18 @@ func TestModuleNameNone(t *testing.T) { assert.Equal(t, name, "") } +// Almost immediate crash w/ memcheck: +// === RUN TestModuleImports +// runtime: out of memory: cannot allocate 18446744073189457920-byte block (3702784 in use) +// fatal error: out of memory +// +// Or: +// +// module_test.go:67: +// Error Trace: module_test.go:67 +// Error: Not equal: +// expected: "function" +// actual : "" func TestModuleImports(t *testing.T) { engine := NewEngine() store := NewStore(engine) @@ -54,55 +67,57 @@ func TestModuleImports(t *testing.T) { ) assert.NoError(t, err) - imports := module.Imports() + imports, release := module.ImportsSafe() + defer release(module) // Keep module alive. + assert.Equal(t, len(imports), 4) // 0 - assert.Equal(t, imports[0].Module(), "ns") - assert.Equal(t, imports[0].Name(), "function") + assert.Equal(t, "ns", imports[0].Module()) + assert.Equal(t, "function", imports[0].Name()) type0 := imports[0].Type() - assert.Equal(t, type0.Kind(), FUNCTION) + assert.Equal(t, FUNCTION, type0.Kind()) functionType := type0.IntoFunctionType() - assert.Equal(t, len(functionType.Params()), 0) - assert.Equal(t, len(functionType.Results()), 0) + assert.Equal(t, 0, len(functionType.Params())) + assert.Equal(t, 0, len(functionType.Results())) // 1 - assert.Equal(t, imports[1].Module(), "ns") - assert.Equal(t, imports[1].Name(), "global") + assert.Equal(t, "ns", imports[1].Module()) + assert.Equal(t, "global", imports[1].Name()) type1 := imports[1].Type() - assert.Equal(t, type1.Kind(), GLOBAL) + assert.Equal(t, GLOBAL, type1.Kind()) globalType := type1.IntoGlobalType() - assert.Equal(t, globalType.ValueType().Kind(), F32) - assert.Equal(t, globalType.Mutability(), IMMUTABLE) + assert.Equal(t, F32, globalType.ValueType().Kind()) + assert.Equal(t, IMMUTABLE, globalType.Mutability()) // 2 - assert.Equal(t, imports[2].Module(), "ns") - assert.Equal(t, imports[2].Name(), "table") + assert.Equal(t, "ns", imports[2].Module()) + assert.Equal(t, "table", imports[2].Name()) type2 := imports[2].Type() - assert.Equal(t, type2.Kind(), TABLE) + assert.Equal(t, TABLE, type2.Kind()) tableType := type2.IntoTableType() tableLimits := tableType.Limits() - assert.Equal(t, tableType.ValueType().Kind(), FuncRef) - assert.Equal(t, tableLimits.Minimum(), uint32(1)) - assert.Equal(t, tableLimits.Maximum(), uint32(2)) + assert.Equal(t, FuncRef, tableType.ValueType().Kind()) + assert.EqualValues(t, 1, tableLimits.Minimum()) + assert.EqualValues(t, 2, tableLimits.Maximum()) // 3 - assert.Equal(t, imports[3].Module(), "ns") - assert.Equal(t, imports[3].Name(), "memory") + assert.Equal(t, "ns", imports[3].Module()) + assert.Equal(t, "memory", imports[3].Name()) type3 := imports[3].Type() - assert.Equal(t, type3.Kind(), MEMORY) + assert.Equal(t, MEMORY, type3.Kind()) memoryType := type3.IntoMemoryType() memoryLimits := memoryType.Limits() - assert.Equal(t, memoryLimits.Minimum(), uint32(3)) - assert.Equal(t, memoryLimits.Maximum(), uint32(4)) + assert.EqualValues(t, 3, memoryLimits.Minimum()) + assert.EqualValues(t, 4, memoryLimits.Maximum()) } func TestModuleExports(t *testing.T) { @@ -120,49 +135,51 @@ func TestModuleExports(t *testing.T) { ) assert.NoError(t, err) - exports := module.Exports() - assert.Equal(t, len(exports), 4) + exports, release := module.ExportsSafe() + defer release(module) // Keep module alive. + + assert.Equal(t, 4, len(exports)) // 0 - assert.Equal(t, exports[0].Name(), "function") + assert.Equal(t, "function", exports[0].Name()) type0 := exports[0].Type() - assert.Equal(t, type0.Kind(), FUNCTION) + assert.Equal(t, FUNCTION, type0.Kind()) functionType := type0.IntoFunctionType() - assert.Equal(t, len(functionType.Params()), 2) - assert.Equal(t, len(functionType.Results()), 0) + assert.Equal(t, 2, len(functionType.Params())) + assert.Equal(t, 0, len(functionType.Results())) // 1 - assert.Equal(t, exports[1].Name(), "global") + assert.Equal(t, "global", exports[1].Name()) type1 := exports[1].Type() - assert.Equal(t, type1.Kind(), GLOBAL) + assert.Equal(t, GLOBAL, type1.Kind()) globalType := type1.IntoGlobalType() - assert.Equal(t, globalType.ValueType().Kind(), I32) - assert.Equal(t, globalType.Mutability(), IMMUTABLE) + assert.Equal(t, I32, globalType.ValueType().Kind()) + assert.Equal(t, IMMUTABLE, globalType.Mutability()) // 2 - assert.Equal(t, exports[2].Name(), "table") + assert.Equal(t, "table", exports[2].Name()) type2 := exports[2].Type() - assert.Equal(t, type2.Kind(), TABLE) + assert.Equal(t, TABLE, type2.Kind()) tableType := type2.IntoTableType() tableLimits := tableType.Limits() - assert.Equal(t, tableType.ValueType().Kind(), FuncRef) - assert.Equal(t, tableLimits.Minimum(), uint32(0)) + assert.Equal(t, FuncRef, tableType.ValueType().Kind()) + assert.EqualValues(t, 0, tableLimits.Minimum()) // 3 - assert.Equal(t, exports[3].Name(), "memory") + assert.Equal(t, "memory", exports[3].Name()) type3 := exports[3].Type() - assert.Equal(t, type3.Kind(), MEMORY) + assert.Equal(t, MEMORY, type3.Kind()) memoryType := type3.IntoMemoryType() memoryLimits := memoryType.Limits() - assert.Equal(t, memoryLimits.Minimum(), uint32(1)) + assert.EqualValues(t, 1, memoryLimits.Minimum()) } func TestModuleSerialize(t *testing.T) { @@ -190,16 +207,18 @@ func TestModuleDeserialize(t *testing.T) { moduleAgain, err := DeserializeModule(store, serializedModule) assert.NoError(t, err) + // Note: We probably should use ExportsSafe (to be safe), but because + // moduleAgain is used at the end of the test, moduleAgain is kept alive. exports := moduleAgain.Exports() - assert.Equal(t, len(exports), 1) - assert.Equal(t, exports[0].Name(), "function") + assert.Equal(t, 1, len(exports)) + assert.Equal(t, "function", exports[0].Name()) type0 := exports[0].Type() - assert.Equal(t, type0.Kind(), FUNCTION) + assert.Equal(t, FUNCTION, type0.Kind()) functionType := type0.IntoFunctionType() - assert.Equal(t, len(functionType.Params()), 2) - assert.Equal(t, len(functionType.Results()), 0) + assert.Equal(t, 2, len(functionType.Params())) + assert.Equal(t, 0, len(functionType.Results())) _, err = NewInstance(moduleAgain, NewImportObject()) assert.NoError(t, err) diff --git a/wasmer/store.go b/wasmer/store.go index fdf485d..2770bcb 100644 --- a/wasmer/store.go +++ b/wasmer/store.go @@ -12,33 +12,31 @@ import "runtime" // The Store holds the Engine (that is — amongst many things — used to // compile the Wasm bytes into a valid module artifact). // -// See also +// # See also // // Specification: https://webassembly.github.io/spec/core/exec/runtime.html#store type Store struct { - _inner *C.wasm_store_t + CPtrBase[*C.wasm_store_t] Engine *Engine } // NewStore instantiates a new Store with an Engine. // -// engine := NewEngine() -// store := NewStore(engine) +// engine := NewEngine() +// store := NewStore(engine) func NewStore(engine *Engine) *Store { self := &Store{ - _inner: C.wasm_store_new(engine.inner()), - Engine: engine, + CPtrBase: mkPtr(C.wasm_store_new(engine.inner())), + Engine: engine, } - - runtime.SetFinalizer(self, func(self *Store) { - self.Close() + self.SetFinalizer(func(self *C.wasm_store_t) { + C.wasm_store_delete(self) }) - return self } func (self *Store) inner() *C.wasm_store_t { - return self._inner + return self.ptr() } // Force to close the Store. diff --git a/wasmer/tabletype.go b/wasmer/tabletype.go index 3b3f544..7318051 100644 --- a/wasmer/tabletype.go +++ b/wasmer/tabletype.go @@ -6,21 +6,20 @@ import "runtime" // TableType classifies tables over elements of element types within a size range. // -// See also +// # See also // // Specification: https://webassembly.github.io/spec/core/syntax/types.html#table-types -// type TableType struct { - _inner *C.wasm_tabletype_t + CPtrBase[*C.wasm_tabletype_t] _ownedBy interface{} } func newTableType(pointer *C.wasm_tabletype_t, ownedBy interface{}) *TableType { - tableType := &TableType{_inner: pointer, _ownedBy: ownedBy} + tableType := &TableType{CPtrBase: mkPtr(pointer), _ownedBy: ownedBy} if ownedBy == nil { - runtime.SetFinalizer(tableType, func(tableType *TableType) { - C.wasm_tabletype_delete(tableType.inner()) + tableType.SetFinalizer(func(v *C.wasm_tabletype_t) { + C.wasm_tabletype_delete(v) }) } @@ -29,19 +28,18 @@ func newTableType(pointer *C.wasm_tabletype_t, ownedBy interface{}) *TableType { // NewTableType instantiates a new TableType given a ValueType and some Limits. // -// valueType := NewValueType(I32) -// limits := NewLimits(1, 4) -// tableType := NewTableType(valueType, limits) -// _ = tableType.IntoExternType() -// -func NewTableType(valueType *ValueType, limits *Limits) *TableType { - pointer := C.wasm_tabletype_new(valueType.inner(), limits.inner()) +// valueType := NewValueType(I32) +// limits := NewLimits(1, 4) +// tableType := NewTableType(valueType, limits) +// _ = tableType.IntoExternType() +func NewTableType(valueType *C.wasm_valtype_t, limits *Limits) *TableType { + pointer := C.wasm_tabletype_new(valueType, limits.inner()) return newTableType(pointer, nil) } func (self *TableType) inner() *C.wasm_tabletype_t { - return self._inner + return self.ptr() } func (self *TableType) ownedBy() interface{} { @@ -54,11 +52,10 @@ func (self *TableType) ownedBy() interface{} { // ValueType returns the TableType's ValueType. // -// valueType := NewValueType(I32) -// limits := NewLimits(1, 4) -// tableType := NewTableType(valueType, limits) -// _ = tableType.ValueType() -// +// valueType := NewValueType(I32) +// limits := NewLimits(1, 4) +// tableType := NewTableType(valueType, limits) +// _ = tableType.ValueType() func (self *TableType) ValueType() *ValueType { pointer := C.wasm_tabletype_element(self.inner()) @@ -69,11 +66,10 @@ func (self *TableType) ValueType() *ValueType { // Limits returns the TableType's Limits. // -// valueType := NewValueType(I32) -// limits := NewLimits(1, 4) -// tableType := NewTableType(valueType, limits) -// _ = tableType.Limits() -// +// valueType := NewValueType(I32) +// limits := NewLimits(1, 4) +// tableType := NewTableType(valueType, limits) +// _ = tableType.Limits() func (self *TableType) Limits() *Limits { limits := newLimits(C.wasm_tabletype_limits(self.inner()), self.ownedBy()) @@ -84,11 +80,10 @@ func (self *TableType) Limits() *Limits { // IntoExternType converts the TableType into an ExternType. // -// valueType := NewValueType(I32) -// limits := NewLimits(1, 4) -// tableType := NewTableType(valueType, limits) -// _ = tableType.IntoExternType() -// +// valueType := NewValueType(I32) +// limits := NewLimits(1, 4) +// tableType := NewTableType(valueType, limits) +// _ = tableType.IntoExternType() func (self *TableType) IntoExternType() *ExternType { pointer := C.wasm_tabletype_as_externtype_const(self.inner()) diff --git a/wasmer/tabletype_test.go b/wasmer/tabletype_test.go index e6defa5..9350a27 100644 --- a/wasmer/tabletype_test.go +++ b/wasmer/tabletype_test.go @@ -1,8 +1,9 @@ package wasmer import ( - "github.com/stretchr/testify/assert" "testing" + + "github.com/stretchr/testify/assert" ) func TestTableType(t *testing.T) { @@ -13,7 +14,7 @@ func TestTableType(t *testing.T) { limits, err := NewLimits(minimum, maximum) assert.NoError(t, err) - tableType := NewTableType(valueType, limits) + tableType := NewTableType(valueType.release(), limits) valueTypeAgain := tableType.ValueType() assert.Equal(t, valueTypeAgain.Kind(), I32) @@ -31,7 +32,7 @@ func TestTableTypeIntoExternTypeAndBack(t *testing.T) { limits, err := NewLimits(minimum, maximum) assert.NoError(t, err) - tableType := NewTableType(valueType, limits) + tableType := NewTableType(valueType.release(), limits) externType := tableType.IntoExternType() assert.Equal(t, externType.Kind(), TABLE) diff --git a/wasmer/target.go b/wasmer/target.go index edf0efc..d349912 100644 --- a/wasmer/target.go +++ b/wasmer/target.go @@ -2,60 +2,51 @@ package wasmer // #include import "C" -import "runtime" // Target represents a triple + CPU features pairs. type Target struct { - _inner *C.wasmer_target_t + CPtrBase[*C.wasmer_target_t] } func newTarget(target *C.wasmer_target_t) *Target { - self := &Target{ - _inner: target, - } - - runtime.SetFinalizer(self, func(self *Target) { - C.wasmer_target_delete(self.inner()) + self := &Target{CPtrBase: mkPtr(target)} + self.SetFinalizer(func(v *C.wasmer_target_t) { + C.wasmer_target_delete(v) }) - return self } // NewTarget creates a new target. // -// triple, err := NewTriple("aarch64-unknown-linux-gnu") -// cpuFeatures := NewCpuFeatures() -// target := NewTarget(triple, cpuFeatures) +// triple, err := NewTriple("aarch64-unknown-linux-gnu") +// cpuFeatures := NewCpuFeatures() +// target := NewTarget(triple, cpuFeatures) func NewTarget(triple *Triple, cpuFeatures *CpuFeatures) *Target { - return newTarget(C.wasmer_target_new(triple.inner(), cpuFeatures.inner())) + return newTarget(C.wasmer_target_new(triple.release(), cpuFeatures.release())) } func (self *Target) inner() *C.wasmer_target_t { - return self._inner + return self.ptr() } // Triple; historically such things had three fields, though they have // added additional fields over time. type Triple struct { - _inner *C.wasmer_triple_t + CPtrBase[*C.wasmer_triple_t] } func newTriple(triple *C.wasmer_triple_t) *Triple { - self := &Triple{ - _inner: triple, - } - - runtime.SetFinalizer(self, func(self *Triple) { - C.wasmer_triple_delete(self.inner()) + self := &Triple{CPtrBase: mkPtr(triple)} + self.SetFinalizer(func(v *C.wasmer_triple_t) { + C.wasmer_triple_delete(v) }) - return self } // NewTriple creates a new triple, otherwise it returns an error // specifying why the provided triple isn't valid. // -// triple, err := NewTriple("aarch64-unknown-linux-gnu") +// triple, err := NewTriple("aarch64-unknown-linux-gnu") func NewTriple(triple string) (*Triple, error) { cTripleName := newName(triple) defer C.wasm_name_delete(&cTripleName) @@ -81,7 +72,7 @@ func NewTripleFromHost() *Triple { } func (self *Triple) inner() *C.wasmer_triple_t { - return self._inner + return self.ptr() } // CpuFeatures holds a set of CPU features. They are identified by @@ -122,18 +113,16 @@ func (self *Triple) inner() *C.wasmer_triple_t { // // • lzcnt. type CpuFeatures struct { - _inner *C.wasmer_cpu_features_t + CPtrBase[*C.wasmer_cpu_features_t] } func newCpuFeatures(cpu_features *C.wasmer_cpu_features_t) *CpuFeatures { self := &CpuFeatures{ - _inner: cpu_features, + CPtrBase: mkPtr(cpu_features), } - - runtime.SetFinalizer(self, func(self *CpuFeatures) { - C.wasmer_cpu_features_delete(self.inner()) + self.SetFinalizer(func(v *C.wasmer_cpu_features_t) { + C.wasmer_cpu_features_delete(v) }) - return self } @@ -160,5 +149,5 @@ func (self *CpuFeatures) Add(feature string) error { } func (self *CpuFeatures) inner() *C.wasmer_cpu_features_t { - return self._inner + return self.ptr() } diff --git a/wasmer/valuetype.go b/wasmer/valuetype.go index 4337c66..b63a82e 100644 --- a/wasmer/valuetype.go +++ b/wasmer/valuetype.go @@ -35,12 +35,12 @@ const ( // String returns the ValueKind as a string. // -// I32.String() // "i32" -// I64.String() // "i64" -// F32.String() // "f32" -// F64.String() // "f64" -// AnyRef.String() // "anyref" -// FuncRef.String() // "funcref" +// I32.String() // "i32" +// I64.String() // "i64" +// F32.String() // "f32" +// F64.String() // "f64" +// AnyRef.String() // "anyref" +// FuncRef.String() // "funcref" func (self ValueKind) String() string { switch self { case I32: @@ -61,24 +61,24 @@ func (self ValueKind) String() string { // IsNumber returns true if the ValueKind is a number type. // -// I32.IsNumber() // true -// I64.IsNumber() // true -// F32.IsNumber() // true -// F64.IsNumber() // true -// AnyRef.IsNumber() // false -// FuncRef.IsNumber() // false +// I32.IsNumber() // true +// I64.IsNumber() // true +// F32.IsNumber() // true +// F64.IsNumber() // true +// AnyRef.IsNumber() // false +// FuncRef.IsNumber() // false func (self ValueKind) IsNumber() bool { return bool(C.wasm_valkind_is_num(C.wasm_valkind_t(self))) } // IsReference returns true if the ValueKind is a reference. // -// I32.IsReference() // false -// I64.IsReference() // false -// F32.IsReference() // false -// F64.IsReference() // false -// AnyRef.IsReference() // true -// FuncRef.IsReference() // true +// I32.IsReference() // false +// I64.IsReference() // false +// F32.IsReference() // false +// F64.IsReference() // false +// AnyRef.IsReference() // true +// FuncRef.IsReference() // true func (self ValueKind) IsReference() bool { return bool(C.wasm_valkind_is_ref(C.wasm_valkind_t(self))) } @@ -90,39 +90,38 @@ func (self ValueKind) inner() C.wasm_valkind_t { // ValueType classifies the individual values that WebAssembly code // can compute with and the values that a variable accepts. type ValueType struct { - _inner *C.wasm_valtype_t + CPtrBase[*C.wasm_valtype_t] _ownedBy interface{} } // NewValueType instantiates a new ValueType given a ValueKind. // -// valueType := NewValueType(I32) +// valueType := NewValueType(I32) func NewValueType(kind ValueKind) *ValueType { pointer := C.wasm_valtype_new(C.wasm_valkind_t(kind)) - return newValueType(pointer, nil) } func newValueType(pointer *C.wasm_valtype_t, ownedBy interface{}) *ValueType { - valueType := &ValueType{_inner: pointer, _ownedBy: ownedBy} + valueType := &ValueType{CPtrBase: mkPtr(pointer), _ownedBy: ownedBy} - if ownedBy == nil { - runtime.SetFinalizer(valueType, func(valueType *ValueType) { - C.wasm_valtype_delete(valueType.inner()) - }) - } + //if ownedBy == nil { + // valueType.SetFinalizer(func(v *C.wasm_valtype_t) { + // C.wasm_valtype_delete(v) + // }) + //} return valueType } func (self *ValueType) inner() *C.wasm_valtype_t { - return self._inner + return self.ptr() } // Kind returns the ValueType's ValueKind // -// valueType := NewValueType(I32) -// _ = valueType.Kind() +// valueType := NewValueType(I32) +// _ = valueType.Kind() func (self *ValueType) Kind() ValueKind { kind := ValueKind(C.wasm_valtype_kind(self.inner())) @@ -134,15 +133,15 @@ func (self *ValueType) Kind() ValueKind { // NewValueTypes instantiates a new ValueType array from a list of // ValueKind. Note that this list may be empty. // -// valueTypes := NewValueTypes(I32, I64, F32) +// valueTypes := NewValueTypes(I32, I64, F32) // // Note:️ NewValueTypes is specifically designed to help you declare // function types, e.g. with NewFunctionType: // -// functionType := NewFunctionType( -// NewValueTypes(), // arguments -// NewValueTypes(I32), // results -// ) +// functionType := NewFunctionType( +// NewValueTypes(), // arguments +// NewValueTypes(I32), // results +// ) func NewValueTypes(kinds ...ValueKind) []*ValueType { valueTypes := make([]*ValueType, len(kinds))