wa-lang.org/wazero@v1.0.2/imports/emscripten/emscripten.go (about) 1 // Package emscripten contains Go-defined special functions imported by 2 // Emscripten under the module name "env". 3 // 4 // Emscripten has many imports which are triggered on build flags. Use 5 // FunctionExporter, instead of Instantiate, to define more "env" functions. 6 // 7 // # Relationship to WASI 8 // 9 // Emscripten typically requires wasi_snapshot_preview1 to implement exit. 10 // 11 // See wasi_snapshot_preview1.Instantiate and 12 // https://github.com/emscripten-core/emscripten/wiki/WebAssembly-Standalone 13 package emscripten 14 15 import ( 16 "context" 17 18 "wa-lang.org/wazero" 19 "wa-lang.org/wazero/api" 20 "wa-lang.org/wazero/internal/wasm" 21 "wa-lang.org/wazero/internal/wasmruntime" 22 ) 23 24 // MustInstantiate calls Instantiate or panics on error. 25 // 26 // This is a simpler function for those who know the module "env" is not 27 // already instantiated, and don't need to unload it. 28 func MustInstantiate(ctx context.Context, r wazero.Runtime) { 29 if _, err := Instantiate(ctx, r); err != nil { 30 panic(err) 31 } 32 } 33 34 // Instantiate instantiates the "env" module used by Emscripten into the 35 // runtime default namespace. 36 // 37 // # Notes 38 // 39 // - Failure cases are documented on wazero.Namespace InstantiateModule. 40 // - Closing the wazero.Runtime has the same effect as closing the result. 41 // - To add more functions to the "env" module, use FunctionExporter. 42 // - To instantiate into another wazero.Namespace, use FunctionExporter. 43 func Instantiate(ctx context.Context, r wazero.Runtime) (api.Closer, error) { 44 builder := r.NewHostModuleBuilder("env") 45 NewFunctionExporter().ExportFunctions(builder) 46 return builder.Instantiate(ctx, r) 47 } 48 49 // FunctionExporter configures the functions in the "env" module used by 50 // Emscripten. 51 type FunctionExporter interface { 52 // ExportFunctions builds functions to export with a wazero.HostModuleBuilder 53 // named "env". 54 ExportFunctions(wazero.HostModuleBuilder) 55 } 56 57 // NewFunctionExporter returns a FunctionExporter object with trace disabled. 58 func NewFunctionExporter() FunctionExporter { 59 return &functionExporter{} 60 } 61 62 type functionExporter struct{} 63 64 // ExportFunctions implements FunctionExporter.ExportFunctions 65 func (functionExporter) ExportFunctions(builder wazero.HostModuleBuilder) { 66 exporter := builder.(wasm.HostFuncExporter) 67 exporter.ExportHostFunc(notifyMemoryGrowth) 68 exporter.ExportHostFunc(invokeI) 69 exporter.ExportHostFunc(invokeIi) 70 exporter.ExportHostFunc(invokeIii) 71 exporter.ExportHostFunc(invokeIiii) 72 exporter.ExportHostFunc(invokeIiiii) 73 exporter.ExportHostFunc(invokeV) 74 exporter.ExportHostFunc(invokeVi) 75 exporter.ExportHostFunc(invokeVii) 76 exporter.ExportHostFunc(invokeViii) 77 exporter.ExportHostFunc(invokeViiii) 78 } 79 80 // emscriptenNotifyMemoryGrowth is called when wasm is compiled with 81 // `-s ALLOW_MEMORY_GROWTH` and a "memory.grow" instruction succeeded. 82 // The memoryIndex parameter will be zero until "multi-memory" is implemented. 83 // 84 // Note: This implementation is a no-op and can be overridden by users manually 85 // by redefining the same function. wazero will likely implement a generic 86 // memory growth hook obviating this as well. 87 // 88 // Here's the import in a user's module that ends up using this, in WebAssembly 89 // 1.0 (MVP) Text Format: 90 // 91 // (import "env" "emscripten_notify_memory_growth" 92 // (func $emscripten_notify_memory_growth (param $memory_index i32))) 93 // 94 // See https://github.com/emscripten-core/emscripten/blob/3.1.16/system/lib/standalone/standalone.c#L118 95 // and https://emscripten.org/docs/api_reference/emscripten.h.html#abi-functions 96 const functionNotifyMemoryGrowth = "emscripten_notify_memory_growth" 97 98 var notifyMemoryGrowth = &wasm.HostFunc{ 99 ExportNames: []string{functionNotifyMemoryGrowth}, 100 Name: functionNotifyMemoryGrowth, 101 ParamTypes: []wasm.ValueType{wasm.ValueTypeI32}, 102 ParamNames: []string{"memory_index"}, 103 Code: &wasm.Code{IsHostFunction: true, Body: []byte{wasm.OpcodeEnd}}, 104 } 105 106 // All `invoke_` functions have an initial "index" parameter of 107 // api.ValueTypeI32. This is the index of the desired funcref in the only table 108 // in the module. The type of the funcref is via naming convention. The first 109 // character after `invoke_` decides the result type: 'v' for no result or 'i' 110 // for api.ValueTypeI32. Any count of 'i' following that are api.ValueTypeI32 111 // parameters. 112 // 113 // For example, the function `invoke_iiiii` signature has five parameters, but 114 // also five i's. The five 'i's mean there are four parameters 115 // 116 // (import "env" "invoke_iiiii" (func $invoke_iiiii 117 // (param i32 i32 i32 i32 i32) (result i32)))) 118 // 119 // So, the above function if invoked `invoke_iiiii(1234, 1, 2, 3, 4)` would 120 // look up the funcref at table index 1234, which has a type i32i32i3232_i32 121 // and invoke it with the remaining parameters, 122 const ( 123 i32 = wasm.ValueTypeI32 124 125 functionInvokeI = "invoke_i" 126 functionInvokeIi = "invoke_ii" 127 functionInvokeIii = "invoke_iii" 128 functionInvokeIiii = "invoke_iiii" 129 functionInvokeIiiii = "invoke_iiiii" 130 131 functionInvokeV = "invoke_v" 132 functionInvokeVi = "invoke_vi" 133 functionInvokeVii = "invoke_vii" 134 functionInvokeViii = "invoke_viii" 135 functionInvokeViiii = "invoke_viiii" 136 ) 137 138 var invokeI = &wasm.HostFunc{ 139 ExportNames: []string{functionInvokeI}, 140 Name: functionInvokeI, 141 ParamTypes: []api.ValueType{i32}, 142 ParamNames: []string{"index"}, 143 ResultTypes: []api.ValueType{i32}, 144 Code: &wasm.Code{ 145 IsHostFunction: true, 146 GoFunc: api.GoModuleFunc(invokeIFn), 147 }, 148 } 149 150 func invokeIFn(ctx context.Context, mod api.Module, stack []uint64) { 151 ret, err := callDynamic(ctx, mod.(*wasm.CallContext), "v_i32", wasm.Index(stack[0]), nil) 152 if err != nil { 153 panic(err) 154 } 155 stack[0] = ret[0] 156 } 157 158 var invokeIi = &wasm.HostFunc{ 159 ExportNames: []string{functionInvokeIi}, 160 Name: functionInvokeIi, 161 ParamTypes: []api.ValueType{i32, i32}, 162 ParamNames: []string{"index", "a1"}, 163 ResultTypes: []api.ValueType{i32}, 164 Code: &wasm.Code{ 165 IsHostFunction: true, 166 GoFunc: api.GoModuleFunc(invokeIiFn), 167 }, 168 } 169 170 func invokeIiFn(ctx context.Context, mod api.Module, stack []uint64) { 171 ret, err := callDynamic(ctx, mod.(*wasm.CallContext), "i32_i32", wasm.Index(stack[0]), stack[1:]) 172 if err != nil { 173 panic(err) 174 } 175 stack[0] = ret[0] 176 } 177 178 var invokeIii = &wasm.HostFunc{ 179 ExportNames: []string{functionInvokeIii}, 180 Name: functionInvokeIii, 181 ParamTypes: []api.ValueType{i32, i32, i32}, 182 ParamNames: []string{"index", "a1", "a2"}, 183 ResultTypes: []api.ValueType{i32}, 184 Code: &wasm.Code{ 185 IsHostFunction: true, 186 GoFunc: api.GoModuleFunc(invokeIiiFn), 187 }, 188 } 189 190 func invokeIiiFn(ctx context.Context, mod api.Module, stack []uint64) { 191 ret, err := callDynamic(ctx, mod.(*wasm.CallContext), "i32i32_i32", wasm.Index(stack[0]), stack[1:]) 192 if err != nil { 193 panic(err) 194 } 195 stack[0] = ret[0] 196 } 197 198 var invokeIiii = &wasm.HostFunc{ 199 ExportNames: []string{functionInvokeIiii}, 200 Name: functionInvokeIiii, 201 ParamTypes: []api.ValueType{i32, i32, i32, i32}, 202 ParamNames: []string{"index", "a1", "a2", "a3"}, 203 ResultTypes: []api.ValueType{i32}, 204 Code: &wasm.Code{ 205 IsHostFunction: true, 206 GoFunc: api.GoModuleFunc(invokeIiiiFn), 207 }, 208 } 209 210 func invokeIiiiFn(ctx context.Context, mod api.Module, stack []uint64) { 211 ret, err := callDynamic(ctx, mod.(*wasm.CallContext), "i32i32i32_i32", wasm.Index(stack[0]), stack[1:]) 212 if err != nil { 213 panic(err) 214 } 215 stack[0] = ret[0] 216 } 217 218 var invokeIiiii = &wasm.HostFunc{ 219 ExportNames: []string{functionInvokeIiiii}, 220 Name: functionInvokeIiiii, 221 ParamTypes: []api.ValueType{i32, i32, i32, i32, i32}, 222 ParamNames: []string{"index", "a1", "a2", "a3", "a4"}, 223 ResultTypes: []api.ValueType{i32}, 224 Code: &wasm.Code{ 225 IsHostFunction: true, 226 GoFunc: api.GoModuleFunc(invokeIiiiiFn), 227 }, 228 } 229 230 func invokeIiiiiFn(ctx context.Context, mod api.Module, stack []uint64) { 231 ret, err := callDynamic(ctx, mod.(*wasm.CallContext), "i32i32i32i32_i32", wasm.Index(stack[0]), stack[1:]) 232 if err != nil { 233 panic(err) 234 } 235 stack[0] = ret[0] 236 } 237 238 var invokeV = &wasm.HostFunc{ 239 ExportNames: []string{functionInvokeV}, 240 Name: functionInvokeV, 241 ParamTypes: []api.ValueType{i32}, 242 ParamNames: []string{"index"}, 243 ResultTypes: []api.ValueType{}, 244 Code: &wasm.Code{ 245 IsHostFunction: true, 246 GoFunc: api.GoModuleFunc(invokeVFn), 247 }, 248 } 249 250 func invokeVFn(ctx context.Context, mod api.Module, stack []uint64) { 251 _, err := callDynamic(ctx, mod.(*wasm.CallContext), "v_v", wasm.Index(stack[0]), nil) 252 if err != nil { 253 panic(err) 254 } 255 } 256 257 var invokeVi = &wasm.HostFunc{ 258 ExportNames: []string{functionInvokeVi}, 259 Name: functionInvokeVi, 260 ParamTypes: []api.ValueType{i32, i32}, 261 ParamNames: []string{"index", "a1"}, 262 ResultTypes: []api.ValueType{}, 263 Code: &wasm.Code{ 264 IsHostFunction: true, 265 GoFunc: api.GoModuleFunc(invokeViFn), 266 }, 267 } 268 269 func invokeViFn(ctx context.Context, mod api.Module, stack []uint64) { 270 _, err := callDynamic(ctx, mod.(*wasm.CallContext), "i32_v", wasm.Index(stack[0]), stack[1:]) 271 if err != nil { 272 panic(err) 273 } 274 } 275 276 var invokeVii = &wasm.HostFunc{ 277 ExportNames: []string{functionInvokeVii}, 278 Name: functionInvokeVii, 279 ParamTypes: []api.ValueType{i32, i32, i32}, 280 ParamNames: []string{"index", "a1", "a2"}, 281 ResultTypes: []api.ValueType{}, 282 Code: &wasm.Code{ 283 IsHostFunction: true, 284 GoFunc: api.GoModuleFunc(invokeViiFn), 285 }, 286 } 287 288 func invokeViiFn(ctx context.Context, mod api.Module, stack []uint64) { 289 _, err := callDynamic(ctx, mod.(*wasm.CallContext), "i32i32_v", wasm.Index(stack[0]), stack[1:]) 290 if err != nil { 291 panic(err) 292 } 293 } 294 295 var invokeViii = &wasm.HostFunc{ 296 ExportNames: []string{functionInvokeViii}, 297 Name: functionInvokeViii, 298 ParamTypes: []api.ValueType{i32, i32, i32, i32}, 299 ParamNames: []string{"index", "a1", "a2", "a3"}, 300 ResultTypes: []api.ValueType{}, 301 Code: &wasm.Code{ 302 IsHostFunction: true, 303 GoFunc: api.GoModuleFunc(invokeViiiFn), 304 }, 305 } 306 307 func invokeViiiFn(ctx context.Context, mod api.Module, stack []uint64) { 308 _, err := callDynamic(ctx, mod.(*wasm.CallContext), "i32i32i32_v", wasm.Index(stack[0]), stack[1:]) 309 if err != nil { 310 panic(err) 311 } 312 } 313 314 var invokeViiii = &wasm.HostFunc{ 315 ExportNames: []string{functionInvokeViiii}, 316 Name: functionInvokeViiii, 317 ParamTypes: []api.ValueType{i32, i32, i32, i32, i32}, 318 ParamNames: []string{"index", "a1", "a2", "a3", "a4"}, 319 ResultTypes: []api.ValueType{}, 320 Code: &wasm.Code{ 321 IsHostFunction: true, 322 GoFunc: api.GoModuleFunc(invokeViiiiFn), 323 }, 324 } 325 326 func invokeViiiiFn(ctx context.Context, mod api.Module, stack []uint64) { 327 _, err := callDynamic(ctx, mod.(*wasm.CallContext), "i32i32i32i32_v", wasm.Index(stack[0]), stack[1:]) 328 if err != nil { 329 panic(err) 330 } 331 } 332 333 // callDynamic special cases dynamic calls needed for emscripten `invoke_` 334 // functions such as `invoke_ii` or `invoke_v`. 335 // 336 // # Parameters 337 // 338 // - ctx: the propagated go context. 339 // - callCtx: the incoming context of the `invoke_` function. 340 // - typeName: used to look up the function type. ex "i32i32_i32" or "v_i32" 341 // - tableOffset: position in the module's only table 342 // - params: parameters to the funcref 343 func callDynamic(ctx context.Context, callCtx *wasm.CallContext, typeName string, tableOffset wasm.Index, params []uint64) (results []uint64, err error) { 344 m := callCtx.Module() 345 typeId, ok := m.TypeIDIndex[typeName] 346 if !ok { 347 return nil, wasmruntime.ErrRuntimeIndirectCallTypeMismatch 348 } 349 350 t := m.Tables[0] // Emscripten doesn't use multiple tables 351 idx, err := m.Engine.LookupFunction(t, typeId, tableOffset) 352 if err != nil { 353 return nil, err 354 } 355 return callCtx.Function(idx).Call(ctx, params...) 356 }