Skip to content

Commit 392c249

Browse files
author
Yevgeniy Miretskiy
committed
bugfix: Tighten up memory model.
Fixes #415 As documented in #415, the memory mode exposed by wasmer-go is error prone and hard to use. Introduce a `CPtrBase[T]` helper struct to attempt to surface errors often and early. The `CPtrBase` is meant to store a CGo pointer. Then, through conditional compilation, a debug implementation, enabled via `-tags memcheck` tag, is used, which adds expensive pointer access checks as well as forces calls to `runtime.GC()` whenever underlying pointer accessed. These access are very slow -- however, they cause almost immediate crashes to surface in tests. It's possible that some issues still remain; The fixes were applied in order to ensure that the tests pass 5000 times. Beyond that, there could still be remaining issues.
1 parent f09913d commit 392c249

29 files changed

+785
-442
lines changed

wasmer/config.go

Lines changed: 55 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ const (
2121

2222
// Strings returns the CompilerKind as a string.
2323
//
24-
// CRANELIFT.String() // "cranelift"
25-
// LLVM.String() // "llvm"
24+
// CRANELIFT.String() // "cranelift"
25+
// LLVM.String() // "llvm"
2626
func (self CompilerKind) String() string {
2727
switch self {
2828
case CRANELIFT:
@@ -40,7 +40,7 @@ func (self CompilerKind) String() string {
4040
// IsCompilerAvailable checks that the given compiler is available
4141
// in this current version of `wasmer-go`.
4242
//
43-
// IsCompilerAvailable(CRANELIFT)
43+
// IsCompilerAvailable(CRANELIFT)
4444
func IsCompilerAvailable(compiler CompilerKind) bool {
4545
return bool(C.wasmer_is_compiler_available(uint32(C.wasmer_compiler_t(compiler))))
4646
}
@@ -64,8 +64,8 @@ const (
6464

6565
// Strings returns the EngineKind as a string.
6666
//
67-
// JIT.String() // "jit"
68-
// NATIVE.String() // "native"
67+
// JIT.String() // "jit"
68+
// NATIVE.String() // "native"
6969
func (self EngineKind) String() string {
7070
switch self {
7171
case UNIVERSAL:
@@ -80,35 +80,31 @@ func (self EngineKind) String() string {
8080
// IsEngineAvailable checks that the given engine is available in this
8181
// current version of `wasmer-go`.
8282
//
83-
// IsEngineAvailable(UNIVERSAL)
83+
// IsEngineAvailable(UNIVERSAL)
8484
func IsEngineAvailable(engine EngineKind) bool {
8585
return bool(C.wasmer_is_engine_available(uint32(C.wasmer_engine_t(engine))))
8686
}
8787

8888
// Config holds the compiler and the Engine used by the Store.
8989
type Config struct {
90-
_inner *C.wasm_config_t
90+
CPtrBase[*C.wasm_config_t]
9191
}
9292

9393
// NewConfig instantiates and returns a new Config.
9494
//
95-
// config := NewConfig()
95+
// config := NewConfig()
9696
func NewConfig() *Config {
97-
config := C.wasm_config_new()
98-
99-
return &Config{
100-
_inner: config,
101-
}
97+
return &Config{CPtrBase: mkPtr(C.wasm_config_new())}
10298
}
10399

104100
func (self *Config) inner() *C.wasm_config_t {
105-
return self._inner
101+
return self.ptr()
106102
}
107103

108104
// UseNativeEngine sets the engine to Universal in the configuration.
109105
//
110-
// config := NewConfig()
111-
// config.UseUniversalEngine()
106+
// config := NewConfig()
107+
// config.UseUniversalEngine()
112108
//
113109
// This method might fail if the Universal engine isn't
114110
// available. Check `IsEngineAvailable` to learn more.
@@ -124,8 +120,8 @@ func (self *Config) UseUniversalEngine() *Config {
124120

125121
// UseDylibEngine sets the engine to Dylib in the configuration.
126122
//
127-
// config := NewConfig()
128-
// config.UseDylibEngine()
123+
// config := NewConfig()
124+
// config.UseDylibEngine()
129125
//
130126
// This method might fail if the Dylib engine isn't available. Check
131127
// `IsEngineAvailable` to learn more.
@@ -153,8 +149,8 @@ func (self *Config) UseNativeEngine() *Config {
153149

154150
// UseCraneliftCompiler sets the compiler to Cranelift in the configuration.
155151
//
156-
// config := NewConfig()
157-
// config.UseCraneliftCompiler()
152+
// config := NewConfig()
153+
// config.UseCraneliftCompiler()
158154
//
159155
// This method might fail if the Cranelift compiler isn't
160156
// available. Check `IsCompilerAvailable` to learn more.
@@ -182,14 +178,17 @@ func metering_delegate(op C.wasmer_parser_operator_t) C.uint64_t {
182178
}
183179

184180
// PushMeteringMiddleware allows the middleware metering to be engaged on a map of opcode to cost
185-
// config := NewConfig()
186-
// opmap := map[uint32]uint32{
187-
// End: 1,
188-
// LocalGet: 1,
189-
// I32Add: 4,
190-
// }
191-
// config.PushMeteringMiddleware(7865444, opmap)
192-
func (self *Config) PushMeteringMiddleware(maxGasUsageAllowed uint64, opMap map[Opcode]uint32) *Config {
181+
//
182+
// config := NewConfig()
183+
// opmap := map[uint32]uint32{
184+
// End: 1,
185+
// LocalGet: 1,
186+
// I32Add: 4,
187+
// }
188+
// config.PushMeteringMiddleware(7865444, opmap)
189+
func (self *Config) PushMeteringMiddleware(
190+
maxGasUsageAllowed uint64, opMap map[Opcode]uint32,
191+
) *Config {
193192
if opCodeMap == nil {
194193
// REVIEW only allowing this to be set once
195194
opCodeMap = opMap
@@ -200,40 +199,43 @@ func (self *Config) PushMeteringMiddleware(maxGasUsageAllowed uint64, opMap map[
200199

201200
// PushMeteringMiddlewarePtr allows the middleware metering to be engaged on an unsafe.Pointer
202201
// this pointer must be a to C based function with a signature of:
203-
// extern uint64_t cost_delegate_func(enum wasmer_parser_operator_t op);
202+
//
203+
// extern uint64_t cost_delegate_func(enum wasmer_parser_operator_t op);
204+
//
204205
// package main
205206
//
206207
// #include <wasmer.h>
207208
// extern uint64_t metering_delegate_alt(enum wasmer_parser_operator_t op);
208209
// import "C"
209210
// import "unsafe"
210211
//
211-
// func getInternalCPointer() unsafe.Pointer {
212-
// return unsafe.Pointer(C.metering_delegate_alt)
213-
// }
212+
// func getInternalCPointer() unsafe.Pointer {
213+
// return unsafe.Pointer(C.metering_delegate_alt)
214+
// }
214215
//
215216
// //export metering_delegate_alt
216-
// func metering_delegate_alt(op C.wasmer_parser_operator_t) C.uint64_t {
217-
// v, b := opCodeMap[Opcode(op)]
218-
// if !b {
219-
// return 0 // no value means no cost
220-
// }
221-
// return C.uint64_t(v)
222-
// }
223217
//
224-
// void main(){
225-
// config := NewConfig()
226-
// config.PushMeteringMiddlewarePtr(800000000, getInternalCPointer())
227-
// }
218+
// func metering_delegate_alt(op C.wasmer_parser_operator_t) C.uint64_t {
219+
// v, b := opCodeMap[Opcode(op)]
220+
// if !b {
221+
// return 0 // no value means no cost
222+
// }
223+
// return C.uint64_t(v)
224+
// }
225+
//
226+
// void main(){
227+
// config := NewConfig()
228+
// config.PushMeteringMiddlewarePtr(800000000, getInternalCPointer())
229+
// }
228230
func (self *Config) PushMeteringMiddlewarePtr(maxGasUsageAllowed uint64, p unsafe.Pointer) *Config {
229231
C.wasm_config_push_middleware(self.inner(), C.wasmer_metering_as_middleware(C.wasmer_metering_new(getPlatformLong(maxGasUsageAllowed), (*[0]byte)(p))))
230232
return self
231233
}
232234

233235
// UseLLVMCompiler sets the compiler to LLVM in the configuration.
234236
//
235-
// config := NewConfig()
236-
// config.UseLLVMCompiler()
237+
// config := NewConfig()
238+
// config.UseLLVMCompiler()
237239
//
238240
// This method might fail if the LLVM compiler isn't available. Check
239241
// `IsCompilerAvailable` to learn more.
@@ -250,8 +252,8 @@ func (self *Config) UseLLVMCompiler() *Config {
250252
// UseSinglepassCompiler sets the compiler to Singlepass in the
251253
// configuration.
252254
//
253-
// config := NewConfig()
254-
// config.UseSinglepassCompiler()
255+
// config := NewConfig()
256+
// config.UseSinglepassCompiler()
255257
//
256258
// This method might fail if the Singlepass compiler isn't
257259
// available. Check `IsCompilerAvailable` to learn more.
@@ -267,14 +269,14 @@ func (self *Config) UseSinglepassCompiler() *Config {
267269

268270
// Use a specific target for doing cross-compilation.
269271
//
270-
// triple, _ := NewTriple("aarch64-unknown-linux-gnu")
271-
// cpuFeatures := NewCpuFeatures()
272-
// target := NewTarget(triple, cpuFeatures)
272+
// triple, _ := NewTriple("aarch64-unknown-linux-gnu")
273+
// cpuFeatures := NewCpuFeatures()
274+
// target := NewTarget(triple, cpuFeatures)
273275
//
274-
// config := NewConfig()
275-
// config.UseTarget(target)
276+
// config := NewConfig()
277+
// config.UseTarget(target)
276278
func (self *Config) UseTarget(target *Target) *Config {
277-
C.wasm_config_set_target(self.inner(), target.inner())
279+
C.wasm_config_set_target(self.inner(), target.release())
278280

279281
return self
280282
}

wasmer/config_test.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,16 @@ func TestConfig(t *testing.T) {
2929
engine := NewEngineWithConfig(config)
3030
store := NewStore(engine)
3131
module, err := NewModule(store, testGetBytes("tests.wasm"))
32+
defer runtime.KeepAlive(module)
33+
3234
assert.NoError(t, err)
3335

3436
instance, err := NewInstance(module, NewImportObject())
3537
assert.NoError(t, err)
3638

37-
sum, err := instance.Exports.GetFunction("sum")
39+
sum, release, err := instance.GetFunctionSafe("sum")
3840
assert.NoError(t, err)
41+
defer release(instance)
3942

4043
result, err := sum(37, 5)
4144
assert.NoError(t, err)
@@ -53,12 +56,14 @@ func TestConfigForMetering(t *testing.T) {
5356
store := NewStore(engine)
5457
module, err := NewModule(store, testGetBytes("tests.wasm"))
5558
assert.NoError(t, err)
59+
defer runtime.KeepAlive(module)
5660

5761
instance, err := NewInstance(module, NewImportObject())
5862
assert.NoError(t, err)
5963

60-
sum, err := instance.Exports.GetFunction("sum")
64+
sum, release, err := instance.GetFunctionSafe("sum")
6165
assert.NoError(t, err)
66+
defer release(instance)
6267

6368
result, err := sum(37, 5)
6469
assert.NoError(t, err)
@@ -69,18 +74,19 @@ func TestConfigForMetering(t *testing.T) {
6974
}
7075

7176
func TestConfigForMeteringFn(t *testing.T) {
72-
7377
config := NewConfig().PushMeteringMiddlewarePtr(800000000, getInternalCPointer())
7478
engine := NewEngineWithConfig(config)
7579
store := NewStore(engine)
7680
module, err := NewModule(store, testGetBytes("tests.wasm"))
7781
assert.NoError(t, err)
82+
defer runtime.KeepAlive(module)
7883

7984
instance, err := NewInstance(module, NewImportObject())
8085
assert.NoError(t, err)
8186

82-
sum, err := instance.Exports.GetFunction("sum")
87+
sum, release, err := instance.GetFunctionSafe("sum")
8388
assert.NoError(t, err)
89+
defer release(instance)
8490

8591
result, err := sum(37, 5)
8692
assert.NoError(t, err)
@@ -145,12 +151,14 @@ func TestConfig_AllCombinations(t *testing.T) {
145151
store := NewStore(engine)
146152
module, err := NewModule(store, testGetBytes("tests.wasm"))
147153
assert.NoError(t, err)
154+
defer runtime.KeepAlive(module)
148155

149156
instance, err := NewInstance(module, NewImportObject())
150157
assert.NoError(t, err)
151158

152-
sum, err := instance.Exports.GetFunction("sum")
159+
sum, release, err := instance.GetFunctionSafe("sum")
153160
assert.NoError(t, err)
161+
defer release(instance)
154162

155163
result, err := sum(37, 5)
156164
assert.NoError(t, err)

wasmer/cptr.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package wasmer
2+
3+
// CPtrBase is a based struct for a C pointer.
4+
// It is intended to be embedded into any structure
5+
// that stores a C pointer.
6+
// While this based is parameterized on type any, using any
7+
// type other than *C.xxx pointer should be considered an undefined behavior.
8+
type CPtrBase[T comparable] struct {
9+
_ptr T
10+
maybeStack // stack of the creating goroutine (if enabled via memcheck)
11+
maybeNil[T] // indicates if initial value of _ptr was nil (if enabled via memcheck)
12+
}
13+
14+
// release returns the C pointer stored in this base and clears the finalizer.
15+
func (b *CPtrBase[T]) release() T {
16+
var zero T
17+
v := b.ptr()
18+
b._ptr = zero
19+
b.ClearFinalizer()
20+
return v
21+
}
22+
23+
func (b *CPtrBase[T]) SetFinalizer(free func(v T)) {
24+
doSetFinalizer(b, free)
25+
}
26+
27+
func (b *CPtrBase[T]) ClearFinalizer() {
28+
doClearFinalizer(b)
29+
}
30+
31+
func mkPtr[T comparable](ptr T) CPtrBase[T] {
32+
return doMkPtr(ptr)
33+
}

0 commit comments

Comments
 (0)