github.com/wasilibs/wazerox@v0.0.0-20240124024944-4923be63ab5f/runtime_test.go (about)

     1  package wazero
     2  
     3  import (
     4  	"context"
     5  	_ "embed"
     6  	"errors"
     7  	"sync"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/wasilibs/wazerox/api"
    12  	"github.com/wasilibs/wazerox/experimental"
    13  	"github.com/wasilibs/wazerox/internal/filecache"
    14  	"github.com/wasilibs/wazerox/internal/leb128"
    15  	"github.com/wasilibs/wazerox/internal/platform"
    16  	"github.com/wasilibs/wazerox/internal/testing/binaryencoding"
    17  	"github.com/wasilibs/wazerox/internal/testing/require"
    18  	"github.com/wasilibs/wazerox/internal/wasm"
    19  	"github.com/wasilibs/wazerox/sys"
    20  )
    21  
    22  var (
    23  	binaryNamedZero = binaryencoding.EncodeModule(&wasm.Module{NameSection: &wasm.NameSection{ModuleName: "0"}})
    24  	// testCtx is an arbitrary, non-default context. Non-nil also prevents linter errors.
    25  	testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary")
    26  )
    27  
    28  var _ context.Context = &HostContext{}
    29  
    30  // HostContext contain the content will be used in host function call
    31  type HostContext struct {
    32  	Content string
    33  }
    34  
    35  func (h *HostContext) Deadline() (deadline time.Time, ok bool) { return }
    36  
    37  func (h *HostContext) Done() <-chan struct{} { return nil }
    38  
    39  func (h *HostContext) Err() error { return nil }
    40  
    41  func (h *HostContext) Value(key interface{}) interface{} { return nil }
    42  
    43  func TestRuntime_CompileModule(t *testing.T) {
    44  	tests := []struct {
    45  		name          string
    46  		runtime       Runtime
    47  		wasm          *wasm.Module
    48  		moduleBuilder HostModuleBuilder
    49  		expected      func(CompiledModule)
    50  	}{
    51  		{
    52  			name: "no name section",
    53  			wasm: &wasm.Module{},
    54  		},
    55  		{
    56  			name: "empty NameSection.ModuleName",
    57  			wasm: &wasm.Module{NameSection: &wasm.NameSection{}},
    58  		},
    59  		{
    60  			name: "NameSection.ModuleName",
    61  			wasm: &wasm.Module{NameSection: &wasm.NameSection{ModuleName: "test"}},
    62  			expected: func(compiled CompiledModule) {
    63  				require.Equal(t, "test", compiled.Name())
    64  			},
    65  		},
    66  		{
    67  			name: "FunctionSection, but not exported",
    68  			wasm: &wasm.Module{
    69  				TypeSection:     []wasm.FunctionType{{Params: []api.ValueType{api.ValueTypeI32}}},
    70  				FunctionSection: []wasm.Index{0},
    71  				CodeSection:     []wasm.Code{{Body: []byte{wasm.OpcodeEnd}}},
    72  			},
    73  			expected: func(compiled CompiledModule) {
    74  				require.Nil(t, compiled.ImportedFunctions())
    75  				require.Zero(t, len(compiled.ExportedFunctions()))
    76  			},
    77  		},
    78  		{
    79  			name: "FunctionSection exported",
    80  			wasm: &wasm.Module{
    81  				TypeSection:     []wasm.FunctionType{{Params: []api.ValueType{api.ValueTypeI32}}},
    82  				FunctionSection: []wasm.Index{0},
    83  				CodeSection:     []wasm.Code{{Body: []byte{wasm.OpcodeEnd}}},
    84  				ExportSection: []wasm.Export{{
    85  					Type:  wasm.ExternTypeFunc,
    86  					Name:  "function",
    87  					Index: 0,
    88  				}},
    89  			},
    90  			expected: func(compiled CompiledModule) {
    91  				require.Nil(t, compiled.ImportedFunctions())
    92  				f := compiled.ExportedFunctions()["function"]
    93  				require.Equal(t, []api.ValueType{api.ValueTypeI32}, f.ParamTypes())
    94  			},
    95  		},
    96  		{
    97  			name: "MemorySection, but not exported",
    98  			wasm: &wasm.Module{
    99  				MemorySection: &wasm.Memory{Min: 2, Max: 3, IsMaxEncoded: true},
   100  			},
   101  			expected: func(compiled CompiledModule) {
   102  				require.Nil(t, compiled.ImportedMemories())
   103  				require.Zero(t, len(compiled.ExportedMemories()))
   104  			},
   105  		},
   106  		{
   107  			name: "MemorySection exported",
   108  			wasm: &wasm.Module{
   109  				MemorySection: &wasm.Memory{Min: 2, Max: 3, IsMaxEncoded: true},
   110  				ExportSection: []wasm.Export{{
   111  					Type:  wasm.ExternTypeMemory,
   112  					Name:  "memory",
   113  					Index: 0,
   114  				}},
   115  			},
   116  			expected: func(compiled CompiledModule) {
   117  				require.Nil(t, compiled.ImportedMemories())
   118  				mem := compiled.ExportedMemories()["memory"]
   119  				require.Equal(t, uint32(2), mem.Min())
   120  				max, ok := mem.Max()
   121  				require.Equal(t, uint32(3), max)
   122  				require.True(t, ok)
   123  			},
   124  		},
   125  	}
   126  
   127  	_r := NewRuntime(testCtx)
   128  	defer _r.Close(testCtx)
   129  
   130  	r := _r.(*runtime)
   131  
   132  	for _, tt := range tests {
   133  		tc := tt
   134  
   135  		t.Run(tc.name, func(t *testing.T) {
   136  			bin := binaryencoding.EncodeModule(tc.wasm)
   137  
   138  			m, err := r.CompileModule(testCtx, bin)
   139  			require.NoError(t, err)
   140  			if tc.expected == nil {
   141  				tc.expected = func(CompiledModule) {}
   142  			}
   143  			tc.expected(m)
   144  			require.Equal(t, r.store.Engine, m.(*compiledModule).compiledEngine)
   145  
   146  			// TypeIDs must be assigned to compiledModule.
   147  			expTypeIDs, err := r.store.GetFunctionTypeIDs(tc.wasm.TypeSection)
   148  			require.NoError(t, err)
   149  			require.Equal(t, expTypeIDs, m.(*compiledModule).typeIDs)
   150  		})
   151  	}
   152  }
   153  
   154  func TestRuntime_CompileModule_Errors(t *testing.T) {
   155  	tests := []struct {
   156  		name        string
   157  		wasm        []byte
   158  		expectedErr string
   159  	}{
   160  		{
   161  			name:        "nil",
   162  			expectedErr: "invalid magic number",
   163  		},
   164  		{
   165  			name:        "invalid binary",
   166  			wasm:        append(binaryencoding.Magic, []byte("yolo")...),
   167  			expectedErr: "invalid version header",
   168  		},
   169  		{
   170  			name:        "memory has too many pages",
   171  			wasm:        binaryencoding.EncodeModule(&wasm.Module{MemorySection: &wasm.Memory{Min: 2, Cap: 2, Max: 70000, IsMaxEncoded: true}}),
   172  			expectedErr: "section memory: max 70000 pages (4 Gi) over limit of 65536 pages (4 Gi)",
   173  		},
   174  	}
   175  
   176  	r := NewRuntime(testCtx)
   177  	defer r.Close(testCtx)
   178  
   179  	for _, tt := range tests {
   180  		tc := tt
   181  
   182  		t.Run(tc.name, func(t *testing.T) {
   183  			_, err := r.CompileModule(testCtx, tc.wasm)
   184  			require.EqualError(t, err, tc.expectedErr)
   185  		})
   186  	}
   187  }
   188  
   189  // TestModule_Memory only covers a couple cases to avoid duplication of internal/wasm/runtime_test.go
   190  func TestModule_Memory(t *testing.T) {
   191  	tests := []struct {
   192  		name        string
   193  		wasm        []byte
   194  		expected    bool
   195  		expectedLen uint32
   196  	}{
   197  		{
   198  			name: "no memory",
   199  			wasm: binaryencoding.EncodeModule(&wasm.Module{}),
   200  		},
   201  		{
   202  			name: "memory exported, one page",
   203  			wasm: binaryencoding.EncodeModule(&wasm.Module{
   204  				MemorySection: &wasm.Memory{Min: 1},
   205  				ExportSection: []wasm.Export{{Name: "memory", Type: api.ExternTypeMemory}},
   206  			}),
   207  			expected:    true,
   208  			expectedLen: 65536,
   209  		},
   210  	}
   211  
   212  	for _, tt := range tests {
   213  		tc := tt
   214  
   215  		t.Run(tc.name, func(t *testing.T) {
   216  			r := NewRuntime(testCtx)
   217  			defer r.Close(testCtx)
   218  
   219  			// Instantiate the module and get the export of the above memory
   220  			module, err := r.Instantiate(testCtx, tc.wasm)
   221  			require.NoError(t, err)
   222  
   223  			mem := module.ExportedMemory("memory")
   224  			if tc.expected {
   225  				require.Equal(t, tc.expectedLen, mem.Size())
   226  				defs := module.ExportedMemoryDefinitions()
   227  				require.Equal(t, 1, len(defs))
   228  				def := defs["memory"]
   229  				require.Equal(t, tc.expectedLen>>16, def.Min())
   230  			} else {
   231  				require.Nil(t, mem)
   232  				require.Zero(t, len(module.ExportedMemoryDefinitions()))
   233  			}
   234  		})
   235  	}
   236  }
   237  
   238  // TestModule_Global only covers a couple cases to avoid duplication of internal/wasm/global_test.go
   239  func TestModule_Global(t *testing.T) {
   240  	globalVal := int64(100) // intentionally a value that differs in signed vs unsigned encoding
   241  
   242  	tests := []struct {
   243  		name                      string
   244  		module                    *wasm.Module // module as wat doesn't yet support globals
   245  		expected, expectedMutable bool
   246  	}{
   247  		{
   248  			name:   "no global",
   249  			module: &wasm.Module{},
   250  		},
   251  		{
   252  			name: "global not exported",
   253  			module: &wasm.Module{
   254  				GlobalSection: []wasm.Global{
   255  					{
   256  						Type: wasm.GlobalType{ValType: wasm.ValueTypeI64, Mutable: true},
   257  						Init: wasm.ConstantExpression{Opcode: wasm.OpcodeI64Const, Data: leb128.EncodeInt64(globalVal)},
   258  					},
   259  				},
   260  			},
   261  		},
   262  		{
   263  			name: "global exported",
   264  			module: &wasm.Module{
   265  				GlobalSection: []wasm.Global{
   266  					{
   267  						Type: wasm.GlobalType{ValType: wasm.ValueTypeI64},
   268  						Init: wasm.ConstantExpression{Opcode: wasm.OpcodeI64Const, Data: leb128.EncodeInt64(globalVal)},
   269  					},
   270  				},
   271  				Exports: map[string]*wasm.Export{
   272  					"global": {Type: wasm.ExternTypeGlobal, Name: "global"},
   273  				},
   274  			},
   275  			expected: true,
   276  		},
   277  		{
   278  			name: "global exported and mutable",
   279  			module: &wasm.Module{
   280  				GlobalSection: []wasm.Global{
   281  					{
   282  						Type: wasm.GlobalType{ValType: wasm.ValueTypeI64, Mutable: true},
   283  						Init: wasm.ConstantExpression{Opcode: wasm.OpcodeI64Const, Data: leb128.EncodeInt64(globalVal)},
   284  					},
   285  				},
   286  				Exports: map[string]*wasm.Export{
   287  					"global": {Type: wasm.ExternTypeGlobal, Name: "global"},
   288  				},
   289  			},
   290  			expected:        true,
   291  			expectedMutable: true,
   292  		},
   293  	}
   294  
   295  	for _, tt := range tests {
   296  		tc := tt
   297  
   298  		t.Run(tc.name, func(t *testing.T) {
   299  			r := NewRuntime(testCtx).(*runtime)
   300  			defer r.Close(testCtx)
   301  
   302  			code := &compiledModule{module: tc.module}
   303  
   304  			err := r.store.Engine.CompileModule(testCtx, code.module, nil, false)
   305  			require.NoError(t, err)
   306  
   307  			// Instantiate the module and get the export of the above global
   308  			module, err := r.InstantiateModule(testCtx, code, NewModuleConfig())
   309  			require.NoError(t, err)
   310  
   311  			global := module.ExportedGlobal("global")
   312  			if !tc.expected {
   313  				require.Nil(t, global)
   314  				return
   315  			}
   316  			require.Equal(t, uint64(globalVal), global.Get())
   317  
   318  			mutable, ok := global.(api.MutableGlobal)
   319  			require.Equal(t, tc.expectedMutable, ok)
   320  			if ok {
   321  				mutable.Set(2)
   322  				require.Equal(t, uint64(2), global.Get())
   323  			}
   324  		})
   325  	}
   326  }
   327  
   328  func TestRuntime_InstantiateModule_UsesContext(t *testing.T) {
   329  	r := NewRuntime(testCtx)
   330  	defer r.Close(testCtx)
   331  
   332  	// Define a function that will be set as the start function
   333  	var calledStart bool
   334  	start := func(ctx context.Context) {
   335  		calledStart = true
   336  		require.Equal(t, testCtx, ctx)
   337  	}
   338  
   339  	_, err := r.NewHostModuleBuilder("env").
   340  		NewFunctionBuilder().WithFunc(start).Export("start").
   341  		Instantiate(testCtx)
   342  	require.NoError(t, err)
   343  
   344  	one := uint32(1)
   345  	binary := binaryencoding.EncodeModule(&wasm.Module{
   346  		TypeSection:     []wasm.FunctionType{{}},
   347  		ImportSection:   []wasm.Import{{Module: "env", Name: "start", Type: wasm.ExternTypeFunc, DescFunc: 0}},
   348  		FunctionSection: []wasm.Index{0},
   349  		CodeSection: []wasm.Code{
   350  			{Body: []byte{wasm.OpcodeCall, 0, wasm.OpcodeEnd}}, // Call the imported env.start.
   351  		},
   352  		StartSection: &one,
   353  	})
   354  
   355  	code, err := r.CompileModule(testCtx, binary)
   356  	require.NoError(t, err)
   357  
   358  	// Instantiate the module, which calls the start function. This will fail if the context wasn't as intended.
   359  	mod, err := r.InstantiateModule(testCtx, code, NewModuleConfig())
   360  	require.NoError(t, err)
   361  
   362  	require.True(t, calledStart)
   363  
   364  	// Closing the module shouldn't remove the compiler cache
   365  	require.NoError(t, mod.Close(testCtx))
   366  	require.Equal(t, uint32(2), r.(*runtime).store.Engine.CompiledModuleCount())
   367  }
   368  
   369  // TestRuntime_Instantiate_DoesntEnforce_Start ensures wapc-go work when modules import WASI, but don't
   370  // export "_start".
   371  func TestRuntime_Instantiate_DoesntEnforce_Start(t *testing.T) {
   372  	r := NewRuntime(testCtx)
   373  	defer r.Close(testCtx)
   374  
   375  	binary := binaryencoding.EncodeModule(&wasm.Module{
   376  		MemorySection: &wasm.Memory{Min: 1},
   377  		ExportSection: []wasm.Export{{Name: "memory", Type: wasm.ExternTypeMemory, Index: 0}},
   378  	})
   379  
   380  	mod, err := r.Instantiate(testCtx, binary)
   381  	require.NoError(t, err)
   382  	require.NoError(t, mod.Close(testCtx))
   383  }
   384  
   385  func TestRuntime_Instantiate_ErrorOnStart(t *testing.T) {
   386  	tests := []struct {
   387  		name, wasm string
   388  	}{
   389  		{
   390  			name: "_start function",
   391  			wasm: `(module
   392  	(import "" "start" (func $start))
   393  	(export "_start" (func $start))
   394  )`,
   395  		},
   396  		{
   397  			name: ".start function",
   398  			wasm: `(module
   399  	(import "" "start" (func $start))
   400  	(start $start)
   401  )`,
   402  		},
   403  	}
   404  
   405  	for _, tt := range tests {
   406  		tc := tt
   407  
   408  		t.Run(tc.name, func(t *testing.T) {
   409  			r := NewRuntime(testCtx)
   410  			defer r.Close(testCtx)
   411  
   412  			start := func() {
   413  				panic(errors.New("ice cream"))
   414  			}
   415  
   416  			host, err := r.NewHostModuleBuilder("host").
   417  				NewFunctionBuilder().WithFunc(start).Export("start").
   418  				Instantiate(testCtx)
   419  			require.NoError(t, err)
   420  
   421  			// Start the module as a WASI command. We expect it to fail.
   422  			_, err = r.Instantiate(testCtx, []byte(tc.wasm))
   423  			require.Error(t, err)
   424  
   425  			// Close the imported module, which should remove its compiler cache.
   426  			require.NoError(t, host.Close(testCtx))
   427  
   428  			// The compiler cache of the importing module should be removed on error.
   429  			require.Zero(t, r.(*runtime).store.Engine.CompiledModuleCount())
   430  		})
   431  	}
   432  }
   433  
   434  // TestRuntime_InstantiateModule_WithName tests that we can pre-validate (cache) a module and instantiate it under
   435  // different names. This pattern is used in wapc-go.
   436  func TestRuntime_InstantiateModule_WithName(t *testing.T) {
   437  	r := NewRuntime(testCtx)
   438  	defer r.Close(testCtx)
   439  
   440  	base, err := r.CompileModule(testCtx, binaryNamedZero)
   441  	require.NoError(t, err)
   442  
   443  	require.Equal(t, "0", base.(*compiledModule).module.NameSection.ModuleName)
   444  
   445  	// Use the same runtime to instantiate multiple modules
   446  	internal := r.(*runtime)
   447  	m1, err := r.InstantiateModule(testCtx, base, NewModuleConfig().WithName("1"))
   448  	require.NoError(t, err)
   449  	require.Equal(t, "1", m1.Name())
   450  
   451  	require.Nil(t, internal.Module("0"))
   452  	require.Equal(t, internal.Module("1"), m1)
   453  
   454  	m2, err := r.InstantiateModule(testCtx, base, NewModuleConfig().WithName("2"))
   455  	require.NoError(t, err)
   456  	require.Equal(t, "2", m2.Name())
   457  
   458  	require.Nil(t, internal.Module("0"))
   459  	require.Equal(t, internal.Module("2"), m2)
   460  
   461  	// Empty name module shouldn't be returned via Module() for future optimization.
   462  	m3, err := r.InstantiateModule(testCtx, base, NewModuleConfig().WithName(""))
   463  	require.NoError(t, err)
   464  	require.Equal(t, "", m3.Name())
   465  
   466  	ret := internal.Module("")
   467  	require.Nil(t, ret)
   468  }
   469  
   470  func TestRuntime_InstantiateModule_ExitError(t *testing.T) {
   471  	r := NewRuntime(testCtx)
   472  	defer r.Close(testCtx)
   473  
   474  	tests := []struct {
   475  		name        string
   476  		exitCode    uint32
   477  		export      bool
   478  		expectedErr error
   479  	}{
   480  		{
   481  			name:        "start: exit code 0",
   482  			exitCode:    0,
   483  			expectedErr: sys.NewExitError(0),
   484  		},
   485  		{
   486  			name:        "start: exit code 2",
   487  			exitCode:    2,
   488  			expectedErr: sys.NewExitError(2),
   489  		},
   490  		{
   491  			name:     "_start: exit code 0",
   492  			exitCode: 0,
   493  			export:   true,
   494  		},
   495  		{
   496  			name:        "_start: exit code 2",
   497  			exitCode:    2,
   498  			export:      true,
   499  			expectedErr: sys.NewExitError(2),
   500  		},
   501  	}
   502  
   503  	for _, tt := range tests {
   504  		tc := tt
   505  		t.Run(tc.name, func(t *testing.T) {
   506  			start := func(ctx context.Context, m api.Module) {
   507  				require.NoError(t, m.CloseWithExitCode(ctx, tc.exitCode))
   508  			}
   509  
   510  			env, err := r.NewHostModuleBuilder("env").
   511  				NewFunctionBuilder().WithFunc(start).Export("exit").
   512  				Instantiate(testCtx)
   513  			require.NoError(t, err)
   514  			defer env.Close(testCtx)
   515  
   516  			mod := &wasm.Module{
   517  				TypeSection:     []wasm.FunctionType{{}},
   518  				ImportSection:   []wasm.Import{{Module: "env", Name: "exit", Type: wasm.ExternTypeFunc, DescFunc: 0}},
   519  				FunctionSection: []wasm.Index{0},
   520  				CodeSection: []wasm.Code{
   521  					{Body: []byte{wasm.OpcodeCall, 0, wasm.OpcodeEnd}}, // Call the imported env.start.
   522  				},
   523  			}
   524  			if tc.export {
   525  				mod.ExportSection = []wasm.Export{
   526  					{Name: "_start", Type: wasm.ExternTypeFunc, Index: 1},
   527  				}
   528  			} else {
   529  				one := uint32(1)
   530  				mod.StartSection = &one
   531  			}
   532  			binary := binaryencoding.EncodeModule(mod)
   533  
   534  			// Instantiate the module, which calls the start function.
   535  			m, err := r.InstantiateWithConfig(testCtx, binary,
   536  				NewModuleConfig().WithName("call-exit"))
   537  
   538  			// Ensure the exit error propagated and didn't wrap.
   539  			require.Equal(t, tc.expectedErr, err)
   540  
   541  			// Ensure calling close again doesn't break
   542  			if err == nil {
   543  				require.NoError(t, m.Close(testCtx))
   544  			}
   545  		})
   546  	}
   547  }
   548  
   549  func TestRuntime_CloseWithExitCode(t *testing.T) {
   550  	bin := binaryencoding.EncodeModule(&wasm.Module{
   551  		TypeSection:     []wasm.FunctionType{{}},
   552  		FunctionSection: []wasm.Index{0},
   553  		CodeSection:     []wasm.Code{{Body: []byte{wasm.OpcodeEnd}}},
   554  		ExportSection:   []wasm.Export{{Type: wasm.ExternTypeFunc, Index: 0, Name: "func"}},
   555  	})
   556  
   557  	tests := []struct {
   558  		name     string
   559  		exitCode uint32
   560  	}{
   561  		{
   562  			name:     "exit code 0",
   563  			exitCode: uint32(0),
   564  		},
   565  		{
   566  			name:     "exit code 2",
   567  			exitCode: uint32(2),
   568  		},
   569  	}
   570  
   571  	for _, tt := range tests {
   572  		tc := tt
   573  		t.Run(tc.name, func(t *testing.T) {
   574  			r := NewRuntime(testCtx)
   575  
   576  			code, err := r.CompileModule(testCtx, bin)
   577  			require.NoError(t, err)
   578  
   579  			// Instantiate two modules.
   580  			m1, err := r.InstantiateModule(testCtx, code, NewModuleConfig().WithName("mod1"))
   581  			require.NoError(t, err)
   582  			m2, err := r.InstantiateModule(testCtx, code, NewModuleConfig().WithName("mod2"))
   583  			require.NoError(t, err)
   584  
   585  			func1 := m1.ExportedFunction("func")
   586  			require.Equal(t, map[string]api.FunctionDefinition{"func": func1.Definition()},
   587  				m1.ExportedFunctionDefinitions())
   588  			func2 := m2.ExportedFunction("func")
   589  			require.Equal(t, map[string]api.FunctionDefinition{"func": func2.Definition()},
   590  				m2.ExportedFunctionDefinitions())
   591  
   592  			// Modules not closed so calls succeed
   593  
   594  			_, err = func1.Call(testCtx)
   595  			require.NoError(t, err)
   596  
   597  			_, err = func2.Call(testCtx)
   598  			require.NoError(t, err)
   599  
   600  			if tc.exitCode == 0 {
   601  				err = r.Close(testCtx)
   602  			} else {
   603  				err = r.CloseWithExitCode(testCtx, tc.exitCode)
   604  			}
   605  			require.NoError(t, err)
   606  
   607  			// Modules closed so calls fail
   608  			_, err = func1.Call(testCtx)
   609  			require.ErrorIs(t, err, sys.NewExitError(tc.exitCode))
   610  
   611  			_, err = func2.Call(testCtx)
   612  			require.ErrorIs(t, err, sys.NewExitError(tc.exitCode))
   613  		})
   614  	}
   615  }
   616  
   617  func TestHostFunctionWithCustomContext(t *testing.T) {
   618  	for _, tc := range []struct {
   619  		name   string
   620  		config RuntimeConfig
   621  	}{
   622  		{name: "compiler", config: NewRuntimeConfigCompiler()},
   623  		{name: "interpreter", config: NewRuntimeConfigInterpreter()},
   624  	} {
   625  		t.Run(tc.name, func(t *testing.T) {
   626  			const fistString = "hello"
   627  			const secondString = "hello call"
   628  			hostCtx := &HostContext{fistString}
   629  			r := NewRuntimeWithConfig(hostCtx, tc.config)
   630  			defer r.Close(hostCtx)
   631  
   632  			// Define a function that will be set as the start function
   633  			var calledStart bool
   634  			var calledCall bool
   635  			start := func(ctx context.Context, module api.Module) {
   636  				hts, ok := ctx.(*HostContext)
   637  				if !ok {
   638  					t.Fatal("decorate call context could effect host ctx cast failed, please consider it.")
   639  				}
   640  				calledStart = true
   641  				require.NotNil(t, hts)
   642  				require.Equal(t, fistString, hts.Content)
   643  			}
   644  
   645  			callFunc := func(ctx context.Context, module api.Module) {
   646  				hts, ok := ctx.(*HostContext)
   647  				if !ok {
   648  					t.Fatal("decorate call context could effect host ctx cast failed, please consider it.")
   649  				}
   650  				calledCall = true
   651  				require.NotNil(t, hts)
   652  				require.Equal(t, secondString, hts.Content)
   653  			}
   654  
   655  			_, err := r.NewHostModuleBuilder("env").
   656  				NewFunctionBuilder().WithFunc(start).Export("host").
   657  				NewFunctionBuilder().WithFunc(callFunc).Export("host2").
   658  				Instantiate(hostCtx)
   659  			require.NoError(t, err)
   660  
   661  			startFnIndex := uint32(2)
   662  			binary := binaryencoding.EncodeModule(&wasm.Module{
   663  				TypeSection: []wasm.FunctionType{{}},
   664  				ImportSection: []wasm.Import{
   665  					{Module: "env", Name: "host", Type: wasm.ExternTypeFunc, DescFunc: 0},
   666  					{Module: "env", Name: "host2", Type: wasm.ExternTypeFunc, DescFunc: 0},
   667  				},
   668  				FunctionSection: []wasm.Index{0, 0},
   669  				CodeSection: []wasm.Code{
   670  					{Body: []byte{wasm.OpcodeCall, 0, wasm.OpcodeEnd}}, // Call the imported env.host.
   671  					{Body: []byte{wasm.OpcodeCall, 1, wasm.OpcodeEnd}}, // Call the imported env.host.
   672  				},
   673  				ExportSection: []wasm.Export{
   674  					{Type: api.ExternTypeFunc, Name: "callHost", Index: uint32(3)},
   675  				},
   676  				StartSection: &startFnIndex,
   677  			})
   678  
   679  			// Instantiate the module, which calls the start function. This will fail if the context wasn't as intended.
   680  			ins, err := r.Instantiate(hostCtx, binary)
   681  			require.NoError(t, err)
   682  			require.True(t, calledStart)
   683  
   684  			// add the new context content for call with used in host function
   685  			hostCtx.Content = secondString
   686  			_, err = ins.ExportedFunction("callHost").Call(hostCtx)
   687  			require.NoError(t, err)
   688  			require.True(t, calledCall)
   689  		})
   690  	}
   691  }
   692  
   693  func TestRuntime_Close_ClosesCompiledModules(t *testing.T) {
   694  	for _, tc := range []struct {
   695  		name                 string
   696  		withCompilationCache bool
   697  	}{
   698  		{name: "with cache", withCompilationCache: true},
   699  		{name: "without cache", withCompilationCache: false},
   700  	} {
   701  		t.Run(tc.name, func(t *testing.T) {
   702  			engine := &mockEngine{name: "mock", cachedModules: map[*wasm.Module]struct{}{}}
   703  			conf := *engineLessConfig
   704  			conf.newEngine = func(context.Context, api.CoreFeatures, filecache.Cache) wasm.Engine { return engine }
   705  			if tc.withCompilationCache {
   706  				conf.cache = NewCompilationCache()
   707  			}
   708  			r := NewRuntimeWithConfig(testCtx, &conf)
   709  			defer r.Close(testCtx)
   710  
   711  			// Normally compiled modules are closed when instantiated but this is never instantiated.
   712  			_, err := r.CompileModule(testCtx, binaryNamedZero)
   713  			require.NoError(t, err)
   714  			require.Equal(t, uint32(1), engine.CompiledModuleCount())
   715  
   716  			err = r.Close(testCtx)
   717  			require.NoError(t, err)
   718  
   719  			// Closing the runtime should remove the compiler cache if cache is not configured.
   720  			require.Equal(t, !tc.withCompilationCache, engine.closed)
   721  		})
   722  	}
   723  }
   724  
   725  // TestRuntime_Closed ensures invocation of closed Runtime's methods is safe.
   726  func TestRuntime_Closed(t *testing.T) {
   727  	for _, tc := range []struct {
   728  		name    string
   729  		errFunc func(r Runtime, mod CompiledModule) error
   730  	}{
   731  		{
   732  			name: "InstantiateModule",
   733  			errFunc: func(r Runtime, mod CompiledModule) error {
   734  				_, err := r.InstantiateModule(testCtx, mod, NewModuleConfig())
   735  				return err
   736  			},
   737  		},
   738  		{
   739  			name: "Instantiate",
   740  			errFunc: func(r Runtime, mod CompiledModule) error {
   741  				_, err := r.Instantiate(testCtx, binaryNamedZero)
   742  				return err
   743  			},
   744  		},
   745  		{
   746  			name: "CompileModule",
   747  			errFunc: func(r Runtime, mod CompiledModule) error {
   748  				_, err := r.CompileModule(testCtx, binaryNamedZero)
   749  				return err
   750  			},
   751  		},
   752  	} {
   753  		t.Run(tc.name, func(t *testing.T) {
   754  			engine := &mockEngine{name: "mock", cachedModules: map[*wasm.Module]struct{}{}}
   755  			conf := *engineLessConfig
   756  			conf.newEngine = func(context.Context, api.CoreFeatures, filecache.Cache) wasm.Engine { return engine }
   757  			r := NewRuntimeWithConfig(testCtx, &conf)
   758  			defer r.Close(testCtx)
   759  
   760  			// Normally compiled modules are closed when instantiated but this is never instantiated.
   761  			mod, err := r.CompileModule(testCtx, binaryNamedZero)
   762  			require.NoError(t, err)
   763  			require.Equal(t, uint32(1), engine.CompiledModuleCount())
   764  
   765  			err = r.Close(testCtx)
   766  			require.NoError(t, err)
   767  
   768  			// Closing the runtime should remove the compiler cache if cache is not configured.
   769  			require.True(t, engine.closed)
   770  
   771  			require.EqualError(t, tc.errFunc(r, mod), "runtime closed with exit_code(0)")
   772  		})
   773  	}
   774  }
   775  
   776  type mockEngine struct {
   777  	name          string
   778  	cachedModules map[*wasm.Module]struct{}
   779  	closed        bool
   780  }
   781  
   782  // CompileModule implements the same method as documented on wasm.Engine.
   783  func (e *mockEngine) CompileModule(_ context.Context, module *wasm.Module, _ []experimental.FunctionListener, _ bool) error {
   784  	e.cachedModules[module] = struct{}{}
   785  	return nil
   786  }
   787  
   788  // CompiledModuleCount implements the same method as documented on wasm.Engine.
   789  func (e *mockEngine) CompiledModuleCount() uint32 {
   790  	return uint32(len(e.cachedModules))
   791  }
   792  
   793  // DeleteCompiledModule implements the same method as documented on wasm.Engine.
   794  func (e *mockEngine) DeleteCompiledModule(module *wasm.Module) {
   795  	delete(e.cachedModules, module)
   796  }
   797  
   798  // NewModuleEngine implements the same method as documented on wasm.Engine.
   799  func (e *mockEngine) NewModuleEngine(_ *wasm.Module, _ *wasm.ModuleInstance) (wasm.ModuleEngine, error) {
   800  	return nil, nil
   801  }
   802  
   803  // NewModuleEngine implements the same method as documented on wasm.Close.
   804  func (e *mockEngine) Close() (err error) {
   805  	e.closed = true
   806  	return
   807  }
   808  
   809  // TestNewRuntime_concurrent ensures that concurrent execution of NewRuntime is race-free.
   810  // This depends on -race flag.
   811  func TestNewRuntime_concurrent(t *testing.T) {
   812  	const num = 100
   813  	var wg sync.WaitGroup
   814  	c := NewCompilationCache()
   815  	// If available, uses two engine configurations for the single compilation cache.
   816  	configs := [2]RuntimeConfig{NewRuntimeConfigInterpreter().WithCompilationCache(c)}
   817  	if platform.CompilerSupported() {
   818  		configs[1] = NewRuntimeConfigCompiler().WithCompilationCache(c)
   819  	} else {
   820  		configs[1] = NewRuntimeConfigInterpreter().WithCompilationCache(c)
   821  	}
   822  	wg.Add(num)
   823  	for i := 0; i < num; i++ {
   824  		i := i
   825  		go func() {
   826  			defer wg.Done()
   827  			r := NewRuntimeWithConfig(testCtx, configs[i%2])
   828  			err := r.Close(testCtx)
   829  			require.NoError(t, err)
   830  		}()
   831  	}
   832  	wg.Wait()
   833  }