diff --git a/wasmer/import.go b/wasmer/import.go index 26f39d56..126f6e19 100644 --- a/wasmer/import.go +++ b/wasmer/import.go @@ -178,8 +178,11 @@ func (instanceContext *InstanceContext) Memory() *Memory { return &instanceContext.memory } -// Data returns the instance context data as an `unsafe.Pointer`. It's -// up to the user to cast it appropriately as a pointer to a data. -func (instanceContext *InstanceContext) Data() unsafe.Pointer { - return cWasmerInstanceContextDataGet(instanceContext.context) +// Data returns the instance context data as an `interface{}`. It's up to the +// user to assert the proper type. +func (instanceContext *InstanceContext) Data() interface{} { + ctxDataIdx := *(*int)(cWasmerInstanceContextDataGet(instanceContext.context)) + ctxDataMtx.RLock() + defer ctxDataMtx.RUnlock() + return ctxData[ctxDataIdx] } diff --git a/wasmer/instance.go b/wasmer/instance.go index 0a055cda..5872533c 100644 --- a/wasmer/instance.go +++ b/wasmer/instance.go @@ -2,6 +2,8 @@ package wasmer import ( "fmt" + "runtime" + "sync" "unsafe" ) @@ -71,6 +73,8 @@ type Instance struct { // The exported memory of a WebAssembly instance. Memory *Memory + + ctxDataIdx *int } // NewInstance constructs a new `Instance` with no imported functions. @@ -419,14 +423,50 @@ func (instance *Instance) HasMemory() bool { return nil != instance.Memory } +var ( + // In order to avoid passing illegal Go pointers across the CGo FFI, + // store Instance Context Data in ctxData and simply pass the index + // through the FFI instead. + // + // See Instance.SetContextData and InstanceContext.Data. + ctxData map[int]interface{} + nextCtxDataIdx int + ctxDataMtx sync.RWMutex +) + // SetContextData assigns a data that can be used by all imported // functions. Indeed, each imported function receives as its first // argument an instance context (see `InstanceContext`). An instance // context can hold a pointer to any kind of data. It is important to // understand that this data is shared by all imported function, it's // global to the instance. -func (instance *Instance) SetContextData(data unsafe.Pointer) { - cWasmerInstanceContextDataSet(instance.instance, data) +func (instance *Instance) SetContextData(data interface{}) { + ctxDataMtx.Lock() + if instance.ctxDataIdx == nil { + instance.ctxDataIdx = new(int) + *instance.ctxDataIdx = nextCtxDataIdx + nextCtxDataIdx++ + + // When instance is GC'd, clean up its ctxData. + // Set the finalizer on the unexported instance.ctxDataIdx, + // instead of directly on the instance, to allow users of this + // package to set their own finalizer on the Instance for other + // reasons. + runtime.SetFinalizer(instance.ctxDataIdx, func(idx *int) { + // Launch a goroutine to avoid blocking other + // finalizers while waiting for the mutex lock. + go func() { + ctxDataMtx.Lock() + delete(ctxData, *idx) + ctxDataMtx.Unlock() + }() + }) + } + ctxData[*instance.ctxDataIdx] = data + ctxDataMtx.Unlock() + + cWasmerInstanceContextDataSet(instance.instance, + unsafe.Pointer(&instance.ctxDataIdx)) } // Close closes/frees an `Instance`.