@@ -67,24 +67,32 @@ type wModule struct {
6767
6868var _ module.Module = (* wModule )(nil )
6969
70- func (m * wModule ) NewInstance (functionName string , paramSets ... map [string ]any ) (module.Instance , error ) {
71- var nextFunction = func () module.MemSize { return 0 }
72- // Register the `lens.next` function required as an import for wasm lens modules
73- importObject := map [string ]any {
74- "lens" : map [string ]any {
75- "next" : js .FuncOf (func (this js.Value , args []js.Value ) any {
76- return nextFunction ()
77- }),
78- },
79- }
70+ // instanceHandles holds all per-instance js resources. Storing them behind a pointer lets
71+ // Reset swap the contents in-place so that the Alloc/Transform/Memory closures — which all
72+ // capture the same *instanceHandles — automatically see the fresh instance.
73+ type instanceHandles struct {
74+ instance js.Value
75+ memory js.Value
76+ alloc js.Value
77+ transform js.Value
78+ }
8079
80+ // newInstanceHandles instantiates the module with the given importObject and applies
81+ // set_param when params is non-empty.
82+ func newInstanceHandles (
83+ rt * wRuntime ,
84+ mod js.Value ,
85+ fnName string ,
86+ params map [string ]any ,
87+ importObject map [string ]any ,
88+ ) (* instanceHandles , error ) {
8189 // Instantiates a WebAssembly.Instance from a WebAssembly.Module with imports.
8290 //
8391 // https://developer.mozilla.org/en-US/docs/WebAssembly/JavaScript_interface/instantiate_static
84- promise := m . runtime . webAssembly .Call ("instantiate" , m . module , importObject )
92+ promise := rt . webAssembly .Call ("instantiate" , mod , importObject )
8593 results , err := await (promise )
8694 if err != nil {
87- return module. Instance {} , err
95+ return nil , err
8896 }
8997 instance := results [0 ]
9098
@@ -98,37 +106,28 @@ func (m *wModule) NewInstance(functionName string, paramSets ...map[string]any)
98106 // https://developer.mozilla.org/en-US/docs/WebAssembly/JavaScript_interface/Memory
99107 memory := exports .Get ("memory" )
100108 if memory .Type () != js .TypeObject {
101- return module. Instance {}, errors . New ( fmt .Sprintf ("Export `%s` does not exist" , "memory" ) )
109+ return nil , fmt .Errorf ("Export `%s` does not exist" , "memory" )
102110 }
103111
104112 alloc := exports .Get ("alloc" )
105113 if alloc .Type () != js .TypeFunction {
106- return module. Instance {}, errors . New ( fmt .Sprintf ("Export `%s` does not exist" , "alloc" ) )
114+ return nil , fmt .Errorf ("Export `%s` does not exist" , "alloc" )
107115 }
108116
109- transform := exports .Get (functionName )
117+ transform := exports .Get (fnName )
110118 if transform .Type () != js .TypeFunction {
111- return module.Instance {}, errors .New (fmt .Sprintf ("Export `%s` does not exist" , functionName ))
112- }
113-
114- params := map [string ]any {}
115- // Merge the param sets into a single map in case more than
116- // one map is provided.
117- for _ , paramSet := range paramSets {
118- for key , value := range paramSet {
119- params [key ] = value
120- }
119+ return nil , fmt .Errorf ("Export `%s` does not exist" , fnName )
121120 }
122121
123122 if len (params ) > 0 {
124123 setParam := exports .Get ("set_param" )
125124 if setParam .Type () != js .TypeFunction {
126- return module. Instance {}, errors . New ( fmt .Sprintf ("Export `%s` does not exist" , "set_param" ) )
125+ return nil , fmt .Errorf ("Export `%s` does not exist" , "set_param" )
127126 }
128127
129128 sourceBytes , err := json .Marshal (params )
130129 if err != nil {
131- return module. Instance {} , err
130+ return nil , err
132131 }
133132
134133 // allocate memory to write to
@@ -138,7 +137,7 @@ func (m *wModule) NewInstance(functionName string, paramSets ...map[string]any)
138137
139138 err = pipes .WriteItem (w , module .JSONTypeID , sourceBytes )
140139 if err != nil {
141- return module. Instance {} , err
140+ return nil , err
142141 }
143142
144143 // set param from JavaScript memory
@@ -149,46 +148,85 @@ func (m *wModule) NewInstance(functionName string, paramSets ...map[string]any)
149148 // from memory using `pipes.GetItem`.
150149 id , data , err := pipes .ReadItem (r )
151150 if id .IsError () {
152- return module. Instance {} , errors .New (string (data ))
151+ return nil , errors .New (string (data ))
153152 }
154153 if err != nil {
155- return module. Instance {} , err
154+ return nil , err
156155 }
157156 }
158157
159- jsMemory := js .Global ().Get ("Uint8Array" ).New (memory .Get ("buffer" ))
160- initialLen := jsMemory .Get ("length" ).Int ()
161- initialState := make ([]byte , initialLen )
162- js .CopyBytesToGo (initialState , jsMemory )
158+ return & instanceHandles {
159+ instance : instance ,
160+ memory : memory ,
161+ alloc : alloc ,
162+ transform : transform ,
163+ }, nil
164+ }
165+
166+ func (m * wModule ) NewInstance (functionName string , paramSets ... map [string ]any ) (module.Instance , error ) {
167+ params := map [string ]any {}
168+ // Merge the param sets into a single map in case more than
169+ // one map is provided.
170+ for _ , paramSet := range paramSets {
171+ for key , value := range paramSet {
172+ params [key ] = value
173+ }
174+ }
175+
176+ var nextFn = func () module.MemSize { return 0 }
177+ // Register the `lens.next` function required as an import for wasm lens modules. The
178+ // import object (and its single js.Func) is created once and reused across re-instantiation
179+ // so Reset does not churn js callback registrations.
180+ importObject := map [string ]any {
181+ "lens" : map [string ]any {
182+ "next" : js .FuncOf (func (this js.Value , args []js.Value ) any {
183+ return nextFn ()
184+ }),
185+ },
186+ }
187+
188+ handles , err := newInstanceHandles (m .runtime , m .module , functionName , params , importObject )
189+ if err != nil {
190+ return module.Instance {}, err
191+ }
192+
193+ var resetErr error
163194
164195 return module.Instance {
165196 Alloc : func (u module.MemSize ) (module.MemSize , error ) {
166- result := alloc .Invoke (int32 (u ))
197+ if resetErr != nil {
198+ return 0 , resetErr
199+ }
200+ result := handles .alloc .Invoke (int32 (u ))
167201 return module .MemSize (result .Int ()), nil
168202 },
169203 Transform : func (next func () module.MemSize ) (module.MemSize , error ) {
204+ if resetErr != nil {
205+ return 0 , resetErr
206+ }
170207 // By assigning the next function immediately prior to calling transform, we allow multiple
171208 // pipeline stages to share the same wasm instance - provided they are not called concurrently.
172209 // This also allows module state to be shared across pipeline stages.
173- nextFunction = next
174- result := transform .Invoke ()
210+ nextFn = next
211+ result := handles . transform .Invoke ()
175212 return module .MemSize (result .Int ()), nil
176213 },
177214 Memory : func () module.Memory {
178- buffer := memory .Get ("buffer" )
179- return newMemory (buffer )
215+ return newMemory (handles .memory .Get ("buffer" ))
180216 },
181217 Reset : func () {
182- initialLen := len (initialState )
183- currentLen := jsMemory .Get ("length" ).Int ()
184-
185- js .CopyBytesToJS (jsMemory , initialState )
186-
187- for i := initialLen ; i < currentLen ; i ++ {
188- jsMemory .SetIndex (i , js .ValueOf (0 ))
218+ nextFn = func () module.MemSize { return 0 }
219+ newHandles , err := newInstanceHandles (m .runtime , m .module , functionName , params , importObject )
220+ if err != nil {
221+ resetErr = err
222+ return
189223 }
224+ resetErr = nil
225+ * handles = * newHandles
226+ // The displaced WebAssembly.Instance has no explicit close; dropping the reference
227+ // lets the runtime GC reclaim its linear memory (issue #159).
190228 },
191- OwnedBy : instance ,
229+ OwnedBy : handles ,
192230 }, nil
193231}
194232
0 commit comments