github.com/tetratelabs/wazero@v1.2.1/imports/emscripten/emscripten_test.go (about)

     1  package emscripten
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	_ "embed"
     7  	"testing"
     8  
     9  	"github.com/tetratelabs/wazero"
    10  	"github.com/tetratelabs/wazero/api"
    11  	"github.com/tetratelabs/wazero/experimental"
    12  	"github.com/tetratelabs/wazero/experimental/logging"
    13  	"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
    14  	internal "github.com/tetratelabs/wazero/internal/emscripten"
    15  	"github.com/tetratelabs/wazero/internal/testing/binaryencoding"
    16  	"github.com/tetratelabs/wazero/internal/testing/require"
    17  	"github.com/tetratelabs/wazero/internal/wasm"
    18  )
    19  
    20  const (
    21  	i64 = wasm.ValueTypeI64
    22  	f32 = wasm.ValueTypeF32
    23  	f64 = wasm.ValueTypeF64
    24  )
    25  
    26  // growWasm was compiled from testdata/grow.cc
    27  //
    28  //go:embed testdata/grow.wasm
    29  var growWasm []byte
    30  
    31  // invokeWasm was generated by the following:
    32  //
    33  //	cd testdata; wat2wasm --debug-names invoke.wat
    34  //
    35  //go:embed testdata/invoke.wasm
    36  var invokeWasm []byte
    37  
    38  // testCtx is an arbitrary, non-default context. Non-nil also prevents linter errors.
    39  var testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary")
    40  
    41  // TestGrow is an integration test until we have an Emscripten example.
    42  func TestGrow(t *testing.T) {
    43  	var log bytes.Buffer
    44  
    45  	// Set context to one that has an experimental listener
    46  	ctx := context.WithValue(testCtx, experimental.FunctionListenerFactoryKey{},
    47  		logging.NewHostLoggingListenerFactory(&log, logging.LogScopeMemory))
    48  
    49  	r := wazero.NewRuntime(ctx)
    50  	defer r.Close(ctx)
    51  
    52  	wasi_snapshot_preview1.MustInstantiate(ctx, r)
    53  
    54  	_, err := Instantiate(ctx, r)
    55  	require.NoError(t, err)
    56  
    57  	// Emscripten exits main with zero by default, which coerces to nul.
    58  	_, err = r.Instantiate(ctx, growWasm)
    59  	require.Nil(t, err)
    60  
    61  	// We expect the memory no-op memory growth hook to be invoked as wasm.
    62  	require.Contains(t, log.String(), "==> env.emscripten_notify_memory_growth(memory_index=0)")
    63  }
    64  
    65  func TestNewFunctionExporterForModule(t *testing.T) {
    66  	tests := []struct {
    67  		name     string
    68  		input    *wasm.Module
    69  		expected emscriptenFns
    70  	}{
    71  		{
    72  			name:     "empty",
    73  			input:    &wasm.Module{},
    74  			expected: emscriptenFns{},
    75  		},
    76  		{
    77  			name: internal.FunctionNotifyMemoryGrowth,
    78  			input: &wasm.Module{
    79  				TypeSection: []wasm.FunctionType{
    80  					{Params: []wasm.ValueType{i32}},
    81  				},
    82  				ImportSection: []wasm.Import{
    83  					{
    84  						Module: "env", Name: internal.FunctionNotifyMemoryGrowth,
    85  						Type:     wasm.ExternTypeFunc,
    86  						DescFunc: 0,
    87  					},
    88  				},
    89  			},
    90  			expected: []*wasm.HostFunc{internal.NotifyMemoryGrowth},
    91  		},
    92  		{
    93  			name: "all result types",
    94  			input: &wasm.Module{
    95  				TypeSection: []wasm.FunctionType{
    96  					{Params: []wasm.ValueType{i32}},
    97  					{Params: []wasm.ValueType{i32}, Results: []wasm.ValueType{i32}},
    98  					{Params: []wasm.ValueType{i32}, Results: []wasm.ValueType{i64}},
    99  					{Params: []wasm.ValueType{i32}, Results: []wasm.ValueType{f32}},
   100  					{Params: []wasm.ValueType{i32}, Results: []wasm.ValueType{f64}},
   101  				},
   102  				ImportSection: []wasm.Import{
   103  					{
   104  						Module: "env", Name: "invoke_v",
   105  						Type:     wasm.ExternTypeFunc,
   106  						DescFunc: 0,
   107  					},
   108  					{
   109  						Module: "env", Name: "invoke_i",
   110  						Type:     wasm.ExternTypeFunc,
   111  						DescFunc: 1,
   112  					},
   113  					{
   114  						Module: "env", Name: "invoke_p",
   115  						Type:     wasm.ExternTypeFunc,
   116  						DescFunc: 1,
   117  					},
   118  					{
   119  						Module: "env", Name: "invoke_j",
   120  						Type:     wasm.ExternTypeFunc,
   121  						DescFunc: 2,
   122  					},
   123  					{
   124  						Module: "env", Name: "invoke_f",
   125  						Type:     wasm.ExternTypeFunc,
   126  						DescFunc: 3,
   127  					},
   128  					{
   129  						Module: "env", Name: "invoke_d",
   130  						Type:     wasm.ExternTypeFunc,
   131  						DescFunc: 4,
   132  					},
   133  				},
   134  			},
   135  			expected: []*wasm.HostFunc{
   136  				{
   137  					ExportName: "invoke_v",
   138  					ParamTypes: []api.ValueType{i32},
   139  					ParamNames: []string{"index"},
   140  					Code:       wasm.Code{GoFunc: &internal.InvokeFunc{FunctionType: &wasm.FunctionType{}}},
   141  				},
   142  				{
   143  					ExportName:  "invoke_i",
   144  					ParamTypes:  []api.ValueType{i32},
   145  					ParamNames:  []string{"index"},
   146  					ResultTypes: []api.ValueType{i32},
   147  					Code:        wasm.Code{GoFunc: &internal.InvokeFunc{FunctionType: &wasm.FunctionType{Results: []api.ValueType{i32}}}},
   148  				},
   149  				{
   150  					ExportName:  "invoke_p",
   151  					ParamTypes:  []api.ValueType{i32},
   152  					ParamNames:  []string{"index"},
   153  					ResultTypes: []api.ValueType{i32},
   154  					Code:        wasm.Code{GoFunc: &internal.InvokeFunc{FunctionType: &wasm.FunctionType{Results: []api.ValueType{i32}}}},
   155  				},
   156  				{
   157  					ExportName:  "invoke_j",
   158  					ParamTypes:  []api.ValueType{i32},
   159  					ParamNames:  []string{"index"},
   160  					ResultTypes: []api.ValueType{i64},
   161  					Code:        wasm.Code{GoFunc: &internal.InvokeFunc{FunctionType: &wasm.FunctionType{Results: []api.ValueType{i64}}}},
   162  				},
   163  				{
   164  					ExportName:  "invoke_f",
   165  					ParamTypes:  []api.ValueType{i32},
   166  					ParamNames:  []string{"index"},
   167  					ResultTypes: []api.ValueType{f32},
   168  					Code:        wasm.Code{GoFunc: &internal.InvokeFunc{FunctionType: &wasm.FunctionType{Results: []api.ValueType{f32}}}},
   169  				},
   170  				{
   171  					ExportName:  "invoke_d",
   172  					ParamTypes:  []api.ValueType{i32},
   173  					ParamNames:  []string{"index"},
   174  					ResultTypes: []api.ValueType{f64},
   175  					Code:        wasm.Code{GoFunc: &internal.InvokeFunc{FunctionType: &wasm.FunctionType{Results: []api.ValueType{f64}}}},
   176  				},
   177  			},
   178  		},
   179  		{
   180  			name: "ignores other imports",
   181  			input: &wasm.Module{
   182  				TypeSection: []wasm.FunctionType{
   183  					{Params: []wasm.ValueType{i32}},
   184  				},
   185  				ImportSection: []wasm.Import{
   186  					{
   187  						Module: "anv", Name: "invoke_v",
   188  						Type:     wasm.ExternTypeFunc,
   189  						DescFunc: 0,
   190  					},
   191  					{
   192  						Module: "env", Name: "invoke_v",
   193  						Type:     wasm.ExternTypeFunc,
   194  						DescFunc: 0,
   195  					},
   196  					{
   197  						Module: "env", Name: "grow",
   198  						Type:     wasm.ExternTypeFunc,
   199  						DescFunc: 0,
   200  					},
   201  				},
   202  			},
   203  			expected: []*wasm.HostFunc{
   204  				{
   205  					ExportName: "invoke_v",
   206  					ParamTypes: []api.ValueType{i32},
   207  					ParamNames: []string{"index"},
   208  					Code:       wasm.Code{GoFunc: &internal.InvokeFunc{FunctionType: &wasm.FunctionType{}}},
   209  				},
   210  			},
   211  		},
   212  		{
   213  			name: "invoke_v and " + internal.FunctionNotifyMemoryGrowth,
   214  			input: &wasm.Module{
   215  				TypeSection: []wasm.FunctionType{{Params: []wasm.ValueType{i32}}},
   216  				ImportSection: []wasm.Import{
   217  					{
   218  						Module: "env", Name: "invoke_v",
   219  						Type:     wasm.ExternTypeFunc,
   220  						DescFunc: 0,
   221  					},
   222  					{
   223  						Module: "env", Name: internal.FunctionNotifyMemoryGrowth,
   224  						Type:     wasm.ExternTypeFunc,
   225  						DescFunc: 0,
   226  					},
   227  				},
   228  			},
   229  			expected: []*wasm.HostFunc{
   230  				{
   231  					ExportName: "invoke_v",
   232  					ParamTypes: []api.ValueType{i32},
   233  					ParamNames: []string{"index"},
   234  					Code:       wasm.Code{GoFunc: &internal.InvokeFunc{FunctionType: &wasm.FunctionType{}}},
   235  				},
   236  				internal.NotifyMemoryGrowth,
   237  			},
   238  		},
   239  		{
   240  			name: "invoke_vi",
   241  			input: &wasm.Module{
   242  				TypeSection: []wasm.FunctionType{
   243  					{Params: []wasm.ValueType{i32, i32}},
   244  				},
   245  				ImportSection: []wasm.Import{
   246  					{
   247  						Module: "env", Name: "invoke_vi",
   248  						Type:     wasm.ExternTypeFunc,
   249  						DescFunc: 0,
   250  					},
   251  				},
   252  			},
   253  			expected: []*wasm.HostFunc{
   254  				{
   255  					ExportName: "invoke_vi",
   256  					ParamTypes: []api.ValueType{i32, i32},
   257  					ParamNames: []string{"index", "a1"},
   258  					Code:       wasm.Code{GoFunc: &internal.InvokeFunc{FunctionType: &wasm.FunctionType{Params: []api.ValueType{i32}}}},
   259  				},
   260  			},
   261  		},
   262  		{
   263  			name: "invoke_iiiii",
   264  			input: &wasm.Module{
   265  				TypeSection: []wasm.FunctionType{
   266  					{
   267  						Params:  []wasm.ValueType{i32, i32, i32, i32, i32},
   268  						Results: []wasm.ValueType{i32},
   269  					},
   270  				},
   271  				ImportSection: []wasm.Import{
   272  					{
   273  						Module: "env", Name: "invoke_iiiii",
   274  						Type:     wasm.ExternTypeFunc,
   275  						DescFunc: 0,
   276  					},
   277  				},
   278  			},
   279  			expected: []*wasm.HostFunc{
   280  				{
   281  					ExportName:  "invoke_iiiii",
   282  					ParamTypes:  []api.ValueType{i32, i32, i32, i32, i32},
   283  					ParamNames:  []string{"index", "a1", "a2", "a3", "a4"},
   284  					ResultTypes: []wasm.ValueType{i32},
   285  					Code: wasm.Code{GoFunc: &internal.InvokeFunc{FunctionType: &wasm.FunctionType{
   286  						Params:  []api.ValueType{i32, i32, i32, i32},
   287  						Results: []api.ValueType{i32},
   288  					}}},
   289  				},
   290  			},
   291  		},
   292  		{
   293  			name: "invoke_viiiddiiiiii",
   294  			input: &wasm.Module{
   295  				TypeSection: []wasm.FunctionType{
   296  					{
   297  						Params: []wasm.ValueType{i32, i32, i32, i32, f64, f64, i32, i32, i32, i32, i32, i32},
   298  					},
   299  				},
   300  				ImportSection: []wasm.Import{
   301  					{
   302  						Module: "env", Name: "invoke_viiiddiiiiii",
   303  						Type:     wasm.ExternTypeFunc,
   304  						DescFunc: 0,
   305  					},
   306  				},
   307  			},
   308  			expected: []*wasm.HostFunc{
   309  				{
   310  					ExportName: "invoke_viiiddiiiiii",
   311  					ParamTypes: []api.ValueType{i32, i32, i32, i32, f64, f64, i32, i32, i32, i32, i32, i32},
   312  					ParamNames: []string{"index", "a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "a10", "a11"},
   313  					Code: wasm.Code{GoFunc: &internal.InvokeFunc{FunctionType: &wasm.FunctionType{
   314  						Params: []api.ValueType{i32, i32, i32, f64, f64, i32, i32, i32, i32, i32, i32},
   315  					}}},
   316  				},
   317  			},
   318  		},
   319  	}
   320  
   321  	for _, tt := range tests {
   322  		tc := tt
   323  
   324  		t.Run(tc.name, func(t *testing.T) {
   325  			r := wazero.NewRuntime(testCtx)
   326  			defer r.Close(testCtx)
   327  
   328  			guest, err := r.CompileModule(testCtx, binaryencoding.EncodeModule(tc.input))
   329  			require.NoError(t, err)
   330  
   331  			exporter, err := NewFunctionExporterForModule(guest)
   332  			require.NoError(t, err)
   333  			actual := exporter.(emscriptenFns)
   334  
   335  			require.Equal(t, len(tc.expected), len(actual))
   336  			for i, expected := range tc.expected {
   337  				require.Equal(t, expected, actual[i], actual[i].ExportName)
   338  			}
   339  		})
   340  	}
   341  }
   342  
   343  func TestInstantiateForModule(t *testing.T) {
   344  	var log bytes.Buffer
   345  
   346  	// Set context to one that has an experimental listener
   347  	ctx := context.WithValue(testCtx, experimental.FunctionListenerFactoryKey{}, logging.NewLoggingListenerFactory(&log))
   348  
   349  	r := wazero.NewRuntime(ctx)
   350  	defer r.Close(ctx)
   351  
   352  	compiled, err := r.CompileModule(ctx, invokeWasm)
   353  	require.NoError(t, err)
   354  
   355  	_, err = InstantiateForModule(ctx, r, compiled)
   356  	require.NoError(t, err)
   357  
   358  	mod, err := r.InstantiateModule(ctx, compiled, wazero.NewModuleConfig())
   359  	require.NoError(t, err)
   360  
   361  	tests := []struct {
   362  		name, funcName          string
   363  		tableOffset             int
   364  		params, expectedResults []uint64
   365  		expectedLog             string
   366  	}{
   367  		{
   368  			name:            "invoke_i",
   369  			funcName:        "call_v_i32",
   370  			expectedResults: []uint64{42},
   371  			expectedLog: `--> .call_v_i32(0)
   372  	==> env.invoke_i(index=0)
   373  		--> .v_i32()
   374  		<-- 42
   375  	<== 42
   376  <-- 42
   377  `,
   378  		},
   379  		{
   380  			name:            "invoke_ii",
   381  			funcName:        "call_i32_i32",
   382  			tableOffset:     2,
   383  			params:          []uint64{42},
   384  			expectedResults: []uint64{42},
   385  			expectedLog: `--> .call_i32_i32(2,42)
   386  	==> env.invoke_ii(index=2,a1=42)
   387  		--> .i32_i32(42)
   388  		<-- 42
   389  	<== 42
   390  <-- 42
   391  `,
   392  		},
   393  		{
   394  			name:            "invoke_iii",
   395  			funcName:        "call_i32i32_i32",
   396  			tableOffset:     4,
   397  			params:          []uint64{1, 2},
   398  			expectedResults: []uint64{3},
   399  			expectedLog: `--> .call_i32i32_i32(4,1,2)
   400  	==> env.invoke_iii(index=4,a1=1,a2=2)
   401  		--> .i32i32_i32(1,2)
   402  		<-- 3
   403  	<== 3
   404  <-- 3
   405  `,
   406  		},
   407  		{
   408  			name:            "invoke_iiii",
   409  			funcName:        "call_i32i32i32_i32",
   410  			tableOffset:     6,
   411  			params:          []uint64{1, 2, 4},
   412  			expectedResults: []uint64{7},
   413  			expectedLog: `--> .call_i32i32i32_i32(6,1,2,4)
   414  	==> env.invoke_iiii(index=6,a1=1,a2=2,a3=4)
   415  		--> .i32i32i32_i32(1,2,4)
   416  		<-- 7
   417  	<== 7
   418  <-- 7
   419  `,
   420  		},
   421  		{
   422  			name:            "invoke_iiiii",
   423  			funcName:        "calli32_i32i32i32i32_i32",
   424  			tableOffset:     8,
   425  			params:          []uint64{1, 2, 4, 8},
   426  			expectedResults: []uint64{15},
   427  			expectedLog: `--> .calli32_i32i32i32i32_i32(8,1,2,4,8)
   428  	==> env.invoke_iiiii(index=8,a1=1,a2=2,a3=4,a4=8)
   429  		--> .i32i32i32i32_i32(1,2,4,8)
   430  		<-- 15
   431  	<== 15
   432  <-- 15
   433  `,
   434  		},
   435  		{
   436  			name:        "invoke_v",
   437  			funcName:    "call_v_v",
   438  			tableOffset: 10,
   439  			expectedLog: `--> .call_v_v(10)
   440  	==> env.invoke_v(index=10)
   441  		--> .v_v()
   442  		<--
   443  	<==
   444  <--
   445  `,
   446  		},
   447  		{
   448  			name:        "invoke_vi",
   449  			funcName:    "call_i32_v",
   450  			tableOffset: 12,
   451  			params:      []uint64{42},
   452  			expectedLog: `--> .call_i32_v(12,42)
   453  	==> env.invoke_vi(index=12,a1=42)
   454  		--> .i32_v(42)
   455  		<--
   456  	<==
   457  <--
   458  `,
   459  		},
   460  		{
   461  			name:        "invoke_vii",
   462  			funcName:    "call_i32i32_v",
   463  			tableOffset: 14,
   464  			params:      []uint64{1, 2},
   465  			expectedLog: `--> .call_i32i32_v(14,1,2)
   466  	==> env.invoke_vii(index=14,a1=1,a2=2)
   467  		--> .i32i32_v(1,2)
   468  		<--
   469  	<==
   470  <--
   471  `,
   472  		},
   473  		{
   474  			name:        "invoke_viii",
   475  			funcName:    "call_i32i32i32_v",
   476  			tableOffset: 16,
   477  			params:      []uint64{1, 2, 4},
   478  			expectedLog: `--> .call_i32i32i32_v(16,1,2,4)
   479  	==> env.invoke_viii(index=16,a1=1,a2=2,a3=4)
   480  		--> .i32i32i32_v(1,2,4)
   481  		<--
   482  	<==
   483  <--
   484  `,
   485  		},
   486  		{
   487  			name:        "invoke_viiii",
   488  			funcName:    "calli32_i32i32i32i32_v",
   489  			tableOffset: 18,
   490  			params:      []uint64{1, 2, 4, 8},
   491  			expectedLog: `--> .calli32_i32i32i32i32_v(18,1,2,4,8)
   492  	==> env.invoke_viiii(index=18,a1=1,a2=2,a3=4,a4=8)
   493  		--> .i32i32i32i32_v(1,2,4,8)
   494  		<--
   495  	<==
   496  <--
   497  `,
   498  		},
   499  	}
   500  
   501  	for _, tt := range tests {
   502  		tc := tt
   503  
   504  		t.Run(tc.name, func(t *testing.T) {
   505  			defer log.Reset()
   506  
   507  			params := tc.params
   508  			params = append([]uint64{uint64(tc.tableOffset)}, params...)
   509  
   510  			results, err := mod.ExportedFunction(tc.funcName).Call(testCtx, params...)
   511  			require.NoError(t, err)
   512  			require.Equal(t, tc.expectedResults, results)
   513  
   514  			// We expect to see the dynamic function call target
   515  			require.Equal(t, tc.expectedLog, log.String())
   516  
   517  			// We expect an unreachable function to err
   518  			params[0]++
   519  			_, err = mod.ExportedFunction(tc.funcName).Call(testCtx, params...)
   520  			require.Error(t, err)
   521  		})
   522  	}
   523  }