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  }