wa-lang.org/wazero@v1.0.2/runtime_test.go (about)

     1  package wazero
     2  
     3  import (
     4  	"context"
     5  	_ "embed"
     6  	"errors"
     7  	"testing"
     8  	"time"
     9  
    10  	"wa-lang.org/wazero/api"
    11  	"wa-lang.org/wazero/experimental"
    12  	"wa-lang.org/wazero/internal/leb128"
    13  	"wa-lang.org/wazero/internal/testing/require"
    14  	"wa-lang.org/wazero/internal/version"
    15  	"wa-lang.org/wazero/internal/wasm"
    16  	binaryformat "wa-lang.org/wazero/internal/wasm/binary"
    17  	"wa-lang.org/wazero/sys"
    18  )
    19  
    20  var (
    21  	binaryNamedZero = binaryformat.EncodeModule(&wasm.Module{NameSection: &wasm.NameSection{ModuleName: "0"}})
    22  	// testCtx is an arbitrary, non-default context. Non-nil also prevents linter errors.
    23  	testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary")
    24  )
    25  
    26  var _ context.Context = &HostContext{}
    27  
    28  // HostContext contain the content will be used in host function call
    29  type HostContext struct {
    30  	Content string
    31  }
    32  
    33  func (h *HostContext) Deadline() (deadline time.Time, ok bool) { return }
    34  
    35  func (h *HostContext) Done() <-chan struct{} { return nil }
    36  
    37  func (h *HostContext) Err() error { return nil }
    38  
    39  func (h *HostContext) Value(key interface{}) interface{} { return nil }
    40  
    41  func TestNewRuntimeWithConfig_version(t *testing.T) {
    42  	cfg := NewRuntimeConfig().(*runtimeConfig)
    43  	oldNewEngine := cfg.newEngine
    44  	cfg.newEngine = func(ctx context.Context, features api.CoreFeatures) wasm.Engine {
    45  		// Ensures that wazeroVersion is propagated to the engine.
    46  		v := ctx.Value(version.WazeroVersionKey{})
    47  		require.NotNil(t, v)
    48  		require.Equal(t, wazeroVersion, v.(string))
    49  		return oldNewEngine(ctx, features)
    50  	}
    51  	_ = NewRuntimeWithConfig(testCtx, cfg)
    52  }
    53  
    54  func TestRuntime_CompileModule(t *testing.T) {
    55  	tests := []struct {
    56  		name          string
    57  		runtime       Runtime
    58  		wasm          []byte
    59  		moduleBuilder HostModuleBuilder
    60  		expected      func(CompiledModule)
    61  	}{
    62  		{
    63  			name: "no name section",
    64  			wasm: binaryformat.EncodeModule(&wasm.Module{}),
    65  		},
    66  		{
    67  			name: "empty NameSection.ModuleName",
    68  			wasm: binaryformat.EncodeModule(&wasm.Module{NameSection: &wasm.NameSection{}}),
    69  		},
    70  		{
    71  			name: "NameSection.ModuleName",
    72  			wasm: binaryformat.EncodeModule(&wasm.Module{NameSection: &wasm.NameSection{ModuleName: "test"}}),
    73  			expected: func(compiled CompiledModule) {
    74  				require.Equal(t, "test", compiled.Name())
    75  			},
    76  		},
    77  		{
    78  			name: "FunctionSection, but not exported",
    79  			wasm: binaryformat.EncodeModule(&wasm.Module{
    80  				TypeSection:     []*wasm.FunctionType{{Params: []api.ValueType{api.ValueTypeI32}}},
    81  				FunctionSection: []wasm.Index{0},
    82  				CodeSection:     []*wasm.Code{{Body: []byte{wasm.OpcodeEnd}}},
    83  			}),
    84  			expected: func(compiled CompiledModule) {
    85  				require.Nil(t, compiled.ImportedFunctions())
    86  				require.Zero(t, len(compiled.ExportedFunctions()))
    87  			},
    88  		},
    89  		{
    90  			name: "FunctionSection exported",
    91  			wasm: binaryformat.EncodeModule(&wasm.Module{
    92  				TypeSection:     []*wasm.FunctionType{{Params: []api.ValueType{api.ValueTypeI32}}},
    93  				FunctionSection: []wasm.Index{0},
    94  				CodeSection:     []*wasm.Code{{Body: []byte{wasm.OpcodeEnd}}},
    95  				ExportSection: []*wasm.Export{{
    96  					Type:  wasm.ExternTypeFunc,
    97  					Name:  "function",
    98  					Index: 0,
    99  				}},
   100  			}),
   101  			expected: func(compiled CompiledModule) {
   102  				require.Nil(t, compiled.ImportedFunctions())
   103  				f := compiled.ExportedFunctions()["function"]
   104  				require.Equal(t, []api.ValueType{api.ValueTypeI32}, f.ParamTypes())
   105  			},
   106  		},
   107  		{
   108  			name: "MemorySection, but not exported",
   109  			wasm: binaryformat.EncodeModule(&wasm.Module{
   110  				MemorySection: &wasm.Memory{Min: 2, Max: 3, IsMaxEncoded: true},
   111  			}),
   112  			expected: func(compiled CompiledModule) {
   113  				require.Nil(t, compiled.ImportedMemories())
   114  				require.Zero(t, len(compiled.ExportedMemories()))
   115  			},
   116  		},
   117  		{
   118  			name: "MemorySection exported",
   119  			wasm: binaryformat.EncodeModule(&wasm.Module{
   120  				MemorySection: &wasm.Memory{Min: 2, Max: 3, IsMaxEncoded: true},
   121  				ExportSection: []*wasm.Export{{
   122  					Type:  wasm.ExternTypeMemory,
   123  					Name:  "memory",
   124  					Index: 0,
   125  				}},
   126  			}),
   127  			expected: func(compiled CompiledModule) {
   128  				require.Nil(t, compiled.ImportedMemories())
   129  				mem := compiled.ExportedMemories()["memory"]
   130  				require.Equal(t, uint32(2), mem.Min())
   131  				max, ok := mem.Max()
   132  				require.Equal(t, uint32(3), max)
   133  				require.True(t, ok)
   134  			},
   135  		},
   136  	}
   137  
   138  	r := NewRuntime(testCtx)
   139  	defer r.Close(testCtx)
   140  
   141  	for _, tt := range tests {
   142  		tc := tt
   143  
   144  		t.Run(tc.name, func(t *testing.T) {
   145  			m, err := r.CompileModule(testCtx, tc.wasm)
   146  			require.NoError(t, err)
   147  			if tc.expected == nil {
   148  				tc.expected = func(CompiledModule) {}
   149  			}
   150  			tc.expected(m)
   151  			require.Equal(t, r.(*runtime).store.Engine, m.(*compiledModule).compiledEngine)
   152  		})
   153  	}
   154  }
   155  
   156  func TestRuntime_CompileModule_Errors(t *testing.T) {
   157  	tests := []struct {
   158  		name        string
   159  		wasm        []byte
   160  		expectedErr string
   161  	}{
   162  		{
   163  			name:        "nil",
   164  			expectedErr: "binary == nil",
   165  		},
   166  		{
   167  			name:        "invalid binary",
   168  			wasm:        append(binaryformat.Magic, []byte("yolo")...),
   169  			expectedErr: "invalid version header",
   170  		},
   171  		{
   172  			name:        "memory has too many pages",
   173  			wasm:        binaryformat.EncodeModule(&wasm.Module{MemorySection: &wasm.Memory{Min: 2, Cap: 2, Max: 70000, IsMaxEncoded: true}}),
   174  			expectedErr: "section memory: max 70000 pages (4 Gi) over limit of 65536 pages (4 Gi)",
   175  		},
   176  	}
   177  
   178  	r := NewRuntime(testCtx)
   179  	defer r.Close(testCtx)
   180  
   181  	for _, tt := range tests {
   182  		tc := tt
   183  
   184  		t.Run(tc.name, func(t *testing.T) {
   185  			_, err := r.CompileModule(testCtx, tc.wasm)
   186  			require.EqualError(t, err, tc.expectedErr)
   187  		})
   188  	}
   189  }
   190  
   191  // TestModule_Memory only covers a couple cases to avoid duplication of internal/wasm/runtime_test.go
   192  func TestModule_Memory(t *testing.T) {
   193  	tests := []struct {
   194  		name        string
   195  		wasm        []byte
   196  		expected    bool
   197  		expectedLen uint32
   198  	}{
   199  		{
   200  			name: "no memory",
   201  			wasm: binaryformat.EncodeModule(&wasm.Module{}),
   202  		},
   203  		{
   204  			name: "memory exported, one page",
   205  			wasm: binaryformat.EncodeModule(&wasm.Module{
   206  				MemorySection: &wasm.Memory{Min: 1},
   207  				ExportSection: []*wasm.Export{{Name: "memory", Type: api.ExternTypeMemory}},
   208  			}),
   209  			expected:    true,
   210  			expectedLen: 65536,
   211  		},
   212  	}
   213  
   214  	for _, tt := range tests {
   215  		tc := tt
   216  
   217  		t.Run(tc.name, func(t *testing.T) {
   218  			r := NewRuntime(testCtx)
   219  			defer r.Close(testCtx)
   220  
   221  			// Instantiate the module and get the export of the above memory
   222  			module, err := r.InstantiateModuleFromBinary(testCtx, tc.wasm)
   223  			require.NoError(t, err)
   224  
   225  			mem := module.ExportedMemory("memory")
   226  			if tc.expected {
   227  				require.Equal(t, tc.expectedLen, mem.Size(testCtx))
   228  			} else {
   229  				require.Nil(t, mem)
   230  			}
   231  		})
   232  	}
   233  }
   234  
   235  // TestModule_Global only covers a couple cases to avoid duplication of internal/wasm/global_test.go
   236  func TestModule_Global(t *testing.T) {
   237  	globalVal := int64(100) // intentionally a value that differs in signed vs unsigned encoding
   238  
   239  	tests := []struct {
   240  		name                      string
   241  		module                    *wasm.Module // module as wat doesn't yet support globals
   242  		expected, expectedMutable bool
   243  	}{
   244  		{
   245  			name:   "no global",
   246  			module: &wasm.Module{},
   247  		},
   248  		{
   249  			name: "global not exported",
   250  			module: &wasm.Module{
   251  				GlobalSection: []*wasm.Global{
   252  					{
   253  						Type: &wasm.GlobalType{ValType: wasm.ValueTypeI64, Mutable: true},
   254  						Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI64Const, Data: leb128.EncodeInt64(globalVal)},
   255  					},
   256  				},
   257  			},
   258  		},
   259  		{
   260  			name: "global exported",
   261  			module: &wasm.Module{
   262  				GlobalSection: []*wasm.Global{
   263  					{
   264  						Type: &wasm.GlobalType{ValType: wasm.ValueTypeI64},
   265  						Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI64Const, Data: leb128.EncodeInt64(globalVal)},
   266  					},
   267  				},
   268  				ExportSection: []*wasm.Export{
   269  					{Type: wasm.ExternTypeGlobal, Name: "global"},
   270  				},
   271  			},
   272  			expected: true,
   273  		},
   274  		{
   275  			name: "global exported and mutable",
   276  			module: &wasm.Module{
   277  				GlobalSection: []*wasm.Global{
   278  					{
   279  						Type: &wasm.GlobalType{ValType: wasm.ValueTypeI64, Mutable: true},
   280  						Init: &wasm.ConstantExpression{Opcode: wasm.OpcodeI64Const, Data: leb128.EncodeInt64(globalVal)},
   281  					},
   282  				},
   283  				ExportSection: []*wasm.Export{
   284  					{Type: wasm.ExternTypeGlobal, Name: "global"},
   285  				},
   286  			},
   287  			expected:        true,
   288  			expectedMutable: true,
   289  		},
   290  	}
   291  
   292  	for _, tt := range tests {
   293  		tc := tt
   294  
   295  		t.Run(tc.name, func(t *testing.T) {
   296  			r := NewRuntime(testCtx).(*runtime)
   297  			defer r.Close(testCtx)
   298  
   299  			code := &compiledModule{module: tc.module}
   300  
   301  			err := r.store.Engine.CompileModule(testCtx, code.module, nil)
   302  			require.NoError(t, err)
   303  
   304  			// Instantiate the module and get the export of the above global
   305  			module, err := r.InstantiateModule(testCtx, code, NewModuleConfig())
   306  			require.NoError(t, err)
   307  
   308  			global := module.ExportedGlobal("global")
   309  			if !tc.expected {
   310  				require.Nil(t, global)
   311  				return
   312  			}
   313  			require.Equal(t, uint64(globalVal), global.Get(testCtx))
   314  
   315  			mutable, ok := global.(api.MutableGlobal)
   316  			require.Equal(t, tc.expectedMutable, ok)
   317  			if ok {
   318  				mutable.Set(testCtx, 2)
   319  				require.Equal(t, uint64(2), global.Get(testCtx))
   320  			}
   321  		})
   322  	}
   323  }
   324  
   325  func TestRuntime_InstantiateModule_UsesContext(t *testing.T) {
   326  	r := NewRuntime(testCtx)
   327  	defer r.Close(testCtx)
   328  
   329  	// Define a function that will be set as the start function
   330  	var calledStart bool
   331  	start := func(ctx context.Context) {
   332  		calledStart = true
   333  		require.Equal(t, testCtx, ctx)
   334  	}
   335  
   336  	_, err := r.NewHostModuleBuilder("env").
   337  		NewFunctionBuilder().WithFunc(start).Export("start").
   338  		Instantiate(testCtx, r)
   339  	require.NoError(t, err)
   340  
   341  	one := uint32(1)
   342  	binary := binaryformat.EncodeModule(&wasm.Module{
   343  		TypeSection:     []*wasm.FunctionType{{}},
   344  		ImportSection:   []*wasm.Import{{Module: "env", Name: "start", Type: wasm.ExternTypeFunc, DescFunc: 0}},
   345  		FunctionSection: []wasm.Index{0},
   346  		CodeSection: []*wasm.Code{
   347  			{Body: []byte{wasm.OpcodeCall, 0, wasm.OpcodeEnd}}, // Call the imported env.start.
   348  		},
   349  		StartSection: &one,
   350  	})
   351  
   352  	code, err := r.CompileModule(testCtx, binary)
   353  	require.NoError(t, err)
   354  
   355  	// Instantiate the module, which calls the start function. This will fail if the context wasn't as intended.
   356  	mod, err := r.InstantiateModule(testCtx, code, NewModuleConfig())
   357  	require.NoError(t, err)
   358  
   359  	require.True(t, calledStart)
   360  
   361  	// Closing the module shouldn't remove the compiler cache
   362  	require.NoError(t, mod.Close(testCtx))
   363  	require.Equal(t, uint32(2), r.(*runtime).store.Engine.CompiledModuleCount())
   364  }
   365  
   366  // TestRuntime_InstantiateModuleFromBinary_DoesntEnforce_Start ensures wapc-go work when modules import WASI, but don't
   367  // export "_start".
   368  func TestRuntime_InstantiateModuleFromBinary_DoesntEnforce_Start(t *testing.T) {
   369  	r := NewRuntime(testCtx)
   370  	defer r.Close(testCtx)
   371  
   372  	binary := binaryformat.EncodeModule(&wasm.Module{
   373  		MemorySection: &wasm.Memory{Min: 1},
   374  		ExportSection: []*wasm.Export{{Name: "memory", Type: wasm.ExternTypeMemory, Index: 0}},
   375  	})
   376  
   377  	mod, err := r.InstantiateModuleFromBinary(testCtx, binary)
   378  	require.NoError(t, err)
   379  	require.NoError(t, mod.Close(testCtx))
   380  }
   381  
   382  func TestRuntime_InstantiateModuleFromBinary_ErrorOnStart(t *testing.T) {
   383  	tests := []struct {
   384  		name, wasm string
   385  	}{
   386  		{
   387  			name: "_start function",
   388  			wasm: `(module
   389  	(import "" "start" (func $start))
   390  	(export "_start" (func $start))
   391  )`,
   392  		},
   393  		{
   394  			name: ".start function",
   395  			wasm: `(module
   396  	(import "" "start" (func $start))
   397  	(start $start)
   398  )`,
   399  		},
   400  	}
   401  
   402  	for _, tt := range tests {
   403  		tc := tt
   404  
   405  		t.Run(tc.name, func(t *testing.T) {
   406  			r := NewRuntime(testCtx)
   407  			defer r.Close(testCtx)
   408  
   409  			start := func() {
   410  				panic(errors.New("ice cream"))
   411  			}
   412  
   413  			host, err := r.NewHostModuleBuilder("").
   414  				NewFunctionBuilder().WithFunc(start).Export("start").
   415  				Instantiate(testCtx, r)
   416  			require.NoError(t, err)
   417  
   418  			// Start the module as a WASI command. We expect it to fail.
   419  			_, err = r.InstantiateModuleFromBinary(testCtx, []byte(tc.wasm))
   420  			require.Error(t, err)
   421  
   422  			// Close the imported module, which should remove its compiler cache.
   423  			require.NoError(t, host.Close(testCtx))
   424  
   425  			// The compiler cache of the importing module should be removed on error.
   426  			require.Zero(t, r.(*runtime).store.Engine.CompiledModuleCount())
   427  		})
   428  	}
   429  }
   430  
   431  // TestRuntime_InstantiateModule_WithName tests that we can pre-validate (cache) a module and instantiate it under
   432  // different names. This pattern is used in wapc-go.
   433  func TestRuntime_InstantiateModule_WithName(t *testing.T) {
   434  	r := NewRuntime(testCtx)
   435  	defer r.Close(testCtx)
   436  
   437  	base, err := r.CompileModule(testCtx, binaryNamedZero)
   438  	require.NoError(t, err)
   439  
   440  	require.Equal(t, "0", base.(*compiledModule).module.NameSection.ModuleName)
   441  
   442  	// Use the same runtime to instantiate multiple modules
   443  	internal := r.(*runtime).ns
   444  	m1, err := r.InstantiateModule(testCtx, base, NewModuleConfig().WithName("1"))
   445  	require.NoError(t, err)
   446  
   447  	require.Nil(t, internal.Module("0"))
   448  	require.Equal(t, internal.Module("1"), m1)
   449  
   450  	m2, err := r.InstantiateModule(testCtx, base, NewModuleConfig().WithName("2"))
   451  	require.NoError(t, err)
   452  
   453  	require.Nil(t, internal.Module("0"))
   454  	require.Equal(t, internal.Module("2"), m2)
   455  }
   456  
   457  func TestRuntime_InstantiateModule_ExitError(t *testing.T) {
   458  	r := NewRuntime(testCtx)
   459  	defer r.Close(testCtx)
   460  
   461  	start := func(ctx context.Context, m api.Module) {
   462  		require.NoError(t, m.CloseWithExitCode(ctx, 2))
   463  	}
   464  
   465  	_, err := r.NewHostModuleBuilder("env").
   466  		NewFunctionBuilder().WithFunc(start).Export("exit").
   467  		Instantiate(testCtx, r)
   468  	require.NoError(t, err)
   469  
   470  	one := uint32(1)
   471  	binary := binaryformat.EncodeModule(&wasm.Module{
   472  		TypeSection:     []*wasm.FunctionType{{}},
   473  		ImportSection:   []*wasm.Import{{Module: "env", Name: "exit", Type: wasm.ExternTypeFunc, DescFunc: 0}},
   474  		FunctionSection: []wasm.Index{0},
   475  		CodeSection: []*wasm.Code{
   476  			{Body: []byte{wasm.OpcodeCall, 0, wasm.OpcodeEnd}}, // Call the imported env.start.
   477  		},
   478  		StartSection: &one,
   479  	})
   480  
   481  	code, err := r.CompileModule(testCtx, binary)
   482  	require.NoError(t, err)
   483  
   484  	// Instantiate the module, which calls the start function.
   485  	_, err = r.InstantiateModule(testCtx, code, NewModuleConfig().WithName("call-exit"))
   486  
   487  	// Ensure the exit error propagated and didn't wrap.
   488  	require.Equal(t, err, sys.NewExitError("call-exit", 2))
   489  }
   490  
   491  func TestRuntime_CloseWithExitCode(t *testing.T) {
   492  	bin := binaryformat.EncodeModule(&wasm.Module{
   493  		TypeSection:     []*wasm.FunctionType{{}},
   494  		FunctionSection: []wasm.Index{0},
   495  		CodeSection:     []*wasm.Code{{Body: []byte{wasm.OpcodeEnd}}},
   496  		ExportSection:   []*wasm.Export{{Type: wasm.ExternTypeFunc, Index: 0, Name: "func"}},
   497  	})
   498  
   499  	tests := []struct {
   500  		name     string
   501  		exitCode uint32
   502  	}{
   503  		{
   504  			name:     "exit code 0",
   505  			exitCode: uint32(0),
   506  		},
   507  		{
   508  			name:     "exit code 2",
   509  			exitCode: uint32(2),
   510  		},
   511  	}
   512  
   513  	for _, tt := range tests {
   514  		tc := tt
   515  		t.Run(tc.name, func(t *testing.T) {
   516  			r := NewRuntime(testCtx)
   517  
   518  			code, err := r.CompileModule(testCtx, bin)
   519  			require.NoError(t, err)
   520  
   521  			// Instantiate two modules.
   522  			m1, err := r.InstantiateModule(testCtx, code, NewModuleConfig().WithName("mod1"))
   523  			require.NoError(t, err)
   524  			m2, err := r.InstantiateModule(testCtx, code, NewModuleConfig().WithName("mod2"))
   525  			require.NoError(t, err)
   526  
   527  			func1 := m1.ExportedFunction("func")
   528  			func2 := m2.ExportedFunction("func")
   529  
   530  			// Modules not closed so calls succeed
   531  
   532  			_, err = func1.Call(testCtx)
   533  			require.NoError(t, err)
   534  
   535  			_, err = func2.Call(testCtx)
   536  			require.NoError(t, err)
   537  
   538  			if tc.exitCode == 0 {
   539  				err = r.Close(testCtx)
   540  			} else {
   541  				err = r.CloseWithExitCode(testCtx, tc.exitCode)
   542  			}
   543  			require.NoError(t, err)
   544  
   545  			// Modules closed so calls fail
   546  			_, err = func1.Call(testCtx)
   547  			require.ErrorIs(t, err, sys.NewExitError("mod1", tc.exitCode))
   548  
   549  			_, err = func2.Call(testCtx)
   550  			require.ErrorIs(t, err, sys.NewExitError("mod2", tc.exitCode))
   551  		})
   552  	}
   553  }
   554  
   555  func TestHostFunctionWithCustomContext(t *testing.T) {
   556  	const fistString = "hello"
   557  	const secondString = "hello call"
   558  	hostCtx := &HostContext{fistString}
   559  	r := NewRuntime(hostCtx)
   560  	defer r.Close(hostCtx)
   561  
   562  	// Define a function that will be set as the start function
   563  	var calledStart bool
   564  	var calledCall bool
   565  	start := func(ctx context.Context, module api.Module) {
   566  		hts, ok := ctx.(*HostContext)
   567  		if !ok {
   568  			t.Fatal("decorate call context could effect host ctx cast failed, please consider it.")
   569  		}
   570  		calledStart = true
   571  		require.NotNil(t, hts)
   572  		require.Equal(t, fistString, hts.Content)
   573  	}
   574  
   575  	callFunc := func(ctx context.Context, module api.Module) {
   576  		hts, ok := ctx.(*HostContext)
   577  		if !ok {
   578  			t.Fatal("decorate call context could effect host ctx cast failed, please consider it.")
   579  		}
   580  		calledCall = true
   581  		require.NotNil(t, hts)
   582  		require.Equal(t, secondString, hts.Content)
   583  	}
   584  
   585  	_, err := r.NewHostModuleBuilder("env").
   586  		NewFunctionBuilder().WithFunc(start).Export("host").
   587  		NewFunctionBuilder().WithFunc(callFunc).Export("host2").
   588  		Instantiate(hostCtx, r)
   589  	require.NoError(t, err)
   590  
   591  	one := uint32(0)
   592  	binary := binaryformat.EncodeModule(&wasm.Module{
   593  		TypeSection: []*wasm.FunctionType{{}, {}},
   594  		ImportSection: []*wasm.Import{
   595  			{Module: "env", Name: "host", Type: wasm.ExternTypeFunc, DescFunc: 0},
   596  			{Module: "env", Name: "host2", Type: wasm.ExternTypeFunc, DescFunc: 0},
   597  		},
   598  		FunctionSection: []wasm.Index{0, 1},
   599  		CodeSection: []*wasm.Code{
   600  			{Body: []byte{wasm.OpcodeCall, 0, wasm.OpcodeEnd}}, // Call the imported env.host.
   601  			{Body: []byte{wasm.OpcodeCall, 1, wasm.OpcodeEnd}}, // Call the imported env.host.
   602  		},
   603  		ExportSection: []*wasm.Export{
   604  			{Type: api.ExternTypeFunc, Name: "callHost", Index: uint32(3)},
   605  		},
   606  		StartSection: &one,
   607  	})
   608  
   609  	code, err := r.CompileModule(hostCtx, binary)
   610  	require.NoError(t, err)
   611  
   612  	// Instantiate the module, which calls the start function. This will fail if the context wasn't as intended.
   613  	ins, err := r.InstantiateModule(hostCtx, code, NewModuleConfig())
   614  	require.NoError(t, err)
   615  	require.True(t, calledStart)
   616  
   617  	// add the new context content for call with used in host function
   618  	hostCtx.Content = secondString
   619  	_, err = ins.ExportedFunction("callHost").Call(hostCtx)
   620  	require.NoError(t, err)
   621  	require.True(t, calledCall)
   622  }
   623  
   624  func TestRuntime_Close_ClosesCompiledModules(t *testing.T) {
   625  	engine := &mockEngine{name: "mock", cachedModules: map[*wasm.Module]struct{}{}}
   626  	conf := *engineLessConfig
   627  	conf.newEngine = func(context.Context, api.CoreFeatures) wasm.Engine {
   628  		return engine
   629  	}
   630  	r := NewRuntimeWithConfig(testCtx, &conf)
   631  	defer r.Close(testCtx)
   632  
   633  	// Normally compiled modules are closed when instantiated but this is never instantiated.
   634  	_, err := r.CompileModule(testCtx, binaryNamedZero)
   635  	require.NoError(t, err)
   636  	require.Equal(t, uint32(1), engine.CompiledModuleCount())
   637  
   638  	err = r.Close(testCtx)
   639  	require.NoError(t, err)
   640  
   641  	// Closing the runtime should remove the compiler cache
   642  	require.Zero(t, engine.CompiledModuleCount())
   643  }
   644  
   645  type mockEngine struct {
   646  	name          string
   647  	cachedModules map[*wasm.Module]struct{}
   648  }
   649  
   650  // CompileModule implements the same method as documented on wasm.Engine.
   651  func (e *mockEngine) CompileModule(_ context.Context, module *wasm.Module, _ []experimental.FunctionListener) error {
   652  	e.cachedModules[module] = struct{}{}
   653  	return nil
   654  }
   655  
   656  // CompiledModuleCount implements the same method as documented on wasm.Engine.
   657  func (e *mockEngine) CompiledModuleCount() uint32 {
   658  	return uint32(len(e.cachedModules))
   659  }
   660  
   661  // DeleteCompiledModule implements the same method as documented on wasm.Engine.
   662  func (e *mockEngine) DeleteCompiledModule(module *wasm.Module) {
   663  	delete(e.cachedModules, module)
   664  }
   665  
   666  // NewModuleEngine implements the same method as documented on wasm.Engine.
   667  func (e *mockEngine) NewModuleEngine(_ string, _ *wasm.Module, _, _ []*wasm.FunctionInstance) (wasm.ModuleEngine, error) {
   668  	return nil, nil
   669  }