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

     1  package wasm
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"math"
     8  	"strconv"
     9  	"testing"
    10  
    11  	"github.com/wasilibs/wazerox/api"
    12  	"github.com/wasilibs/wazerox/experimental"
    13  	"github.com/wasilibs/wazerox/internal/internalapi"
    14  	"github.com/wasilibs/wazerox/internal/leb128"
    15  	"github.com/wasilibs/wazerox/internal/sys"
    16  	"github.com/wasilibs/wazerox/internal/testing/hammer"
    17  	"github.com/wasilibs/wazerox/internal/testing/require"
    18  	"github.com/wasilibs/wazerox/internal/u64"
    19  )
    20  
    21  func TestModuleInstance_Memory(t *testing.T) {
    22  	tests := []struct {
    23  		name        string
    24  		input       *Module
    25  		expected    bool
    26  		expectedLen uint32
    27  	}{
    28  		{
    29  			name:  "no memory",
    30  			input: &Module{},
    31  		},
    32  		{
    33  			name: "memory not exported, one page",
    34  			input: &Module{
    35  				MemorySection:           &Memory{Min: 1, Cap: 1},
    36  				MemoryDefinitionSection: []MemoryDefinition{{}},
    37  			},
    38  		},
    39  		{
    40  			name: "memory exported, different name",
    41  			input: &Module{
    42  				MemorySection:           &Memory{Min: 1, Cap: 1},
    43  				MemoryDefinitionSection: []MemoryDefinition{{}},
    44  				ExportSection:           []Export{{Type: ExternTypeMemory, Name: "momory", Index: 0}},
    45  			},
    46  		},
    47  		{
    48  			name: "memory exported, but zero length",
    49  			input: &Module{
    50  				MemorySection:           &Memory{},
    51  				MemoryDefinitionSection: []MemoryDefinition{{}},
    52  				Exports:                 map[string]*Export{"memory": {Type: ExternTypeMemory, Name: "memory"}},
    53  			},
    54  			expected: true,
    55  		},
    56  		{
    57  			name: "memory exported, one page",
    58  			input: &Module{
    59  				MemorySection:           &Memory{Min: 1, Cap: 1},
    60  				MemoryDefinitionSection: []MemoryDefinition{{}},
    61  				Exports:                 map[string]*Export{"memory": {Type: ExternTypeMemory, Name: "memory"}},
    62  			},
    63  			expected:    true,
    64  			expectedLen: 65536,
    65  		},
    66  		{
    67  			name: "memory exported, two pages",
    68  			input: &Module{
    69  				MemorySection:           &Memory{Min: 2, Cap: 2},
    70  				MemoryDefinitionSection: []MemoryDefinition{{}},
    71  				Exports:                 map[string]*Export{"memory": {Type: ExternTypeMemory, Name: "memory"}},
    72  			},
    73  			expected:    true,
    74  			expectedLen: 65536 * 2,
    75  		},
    76  	}
    77  
    78  	for _, tt := range tests {
    79  		tc := tt
    80  
    81  		t.Run(tc.name, func(t *testing.T) {
    82  			s := newStore()
    83  
    84  			instance, err := s.Instantiate(testCtx, tc.input, "test", nil, nil)
    85  			require.NoError(t, err)
    86  
    87  			mem := instance.ExportedMemory("memory")
    88  			if tc.expected {
    89  				require.Equal(t, tc.expectedLen, mem.Size())
    90  			} else {
    91  				require.Nil(t, mem)
    92  			}
    93  		})
    94  	}
    95  }
    96  
    97  func TestStore_Instantiate(t *testing.T) {
    98  	s := newStore()
    99  	m, err := NewHostModule(
   100  		"foo",
   101  		[]string{"fn"},
   102  		map[string]*HostFunc{"fn": {ExportName: "fn", Code: Code{GoFunc: func() {}}}},
   103  		api.CoreFeaturesV1,
   104  	)
   105  	require.NoError(t, err)
   106  
   107  	sysCtx := sys.DefaultContext(nil)
   108  	mod, err := s.Instantiate(testCtx, m, "bar", sysCtx, []FunctionTypeID{0})
   109  	require.NoError(t, err)
   110  	defer mod.Close(testCtx)
   111  
   112  	t.Run("ModuleInstance defaults", func(t *testing.T) {
   113  		require.Equal(t, s.nameToModule["bar"], mod)
   114  		require.Equal(t, s.nameToModule["bar"].MemoryInstance, mod.MemoryInstance)
   115  		require.Equal(t, s, mod.s)
   116  		require.Equal(t, sysCtx, mod.Sys)
   117  	})
   118  }
   119  
   120  func TestStore_CloseWithExitCode(t *testing.T) {
   121  	const importedModuleName = "imported"
   122  	const importingModuleName = "test"
   123  
   124  	tests := []struct {
   125  		name       string
   126  		testClosed bool
   127  	}{
   128  		{
   129  			name:       "nothing closed",
   130  			testClosed: false,
   131  		},
   132  		{
   133  			name:       "partially closed",
   134  			testClosed: true,
   135  		},
   136  	}
   137  
   138  	for _, tt := range tests {
   139  		tc := tt
   140  		t.Run(tc.name, func(t *testing.T) {
   141  			s := newStore()
   142  
   143  			_, err := s.Instantiate(testCtx, &Module{
   144  				TypeSection:               []FunctionType{v_v},
   145  				FunctionSection:           []uint32{0},
   146  				CodeSection:               []Code{{Body: []byte{OpcodeEnd}}},
   147  				Exports:                   map[string]*Export{"fn": {Type: ExternTypeFunc, Name: "fn"}},
   148  				FunctionDefinitionSection: []FunctionDefinition{{Functype: &v_v}},
   149  			}, importedModuleName, nil, []FunctionTypeID{0})
   150  			require.NoError(t, err)
   151  
   152  			m2, err := s.Instantiate(testCtx, &Module{
   153  				ImportFunctionCount:     1,
   154  				TypeSection:             []FunctionType{v_v},
   155  				ImportSection:           []Import{{Type: ExternTypeFunc, Module: importedModuleName, Name: "fn", DescFunc: 0}},
   156  				MemorySection:           &Memory{Min: 1, Cap: 1},
   157  				MemoryDefinitionSection: []MemoryDefinition{{}},
   158  				GlobalSection:           []Global{{Type: GlobalType{}, Init: ConstantExpression{Opcode: OpcodeI32Const, Data: const1}}},
   159  				TableSection:            []Table{{Min: 10}},
   160  			}, importingModuleName, nil, []FunctionTypeID{0})
   161  			require.NoError(t, err)
   162  
   163  			if tc.testClosed {
   164  				err = m2.CloseWithExitCode(testCtx, 2)
   165  				require.NoError(t, err)
   166  			}
   167  
   168  			err = s.CloseWithExitCode(testCtx, 2)
   169  			require.NoError(t, err)
   170  
   171  			// If Store.CloseWithExitCode was dispatched properly, modules should be empty
   172  			require.Nil(t, s.moduleList)
   173  
   174  			// Store state zeroed
   175  			require.Zero(t, len(s.typeIDs))
   176  		})
   177  	}
   178  }
   179  
   180  func TestStore_hammer(t *testing.T) {
   181  	const importedModuleName = "imported"
   182  
   183  	m, err := NewHostModule(
   184  		importedModuleName,
   185  		[]string{"fn"},
   186  		map[string]*HostFunc{"fn": {ExportName: "fn", Code: Code{GoFunc: func() {}}}},
   187  		api.CoreFeaturesV1,
   188  	)
   189  	require.NoError(t, err)
   190  
   191  	s := newStore()
   192  	imported, err := s.Instantiate(testCtx, m, importedModuleName, nil, []FunctionTypeID{0})
   193  	require.NoError(t, err)
   194  
   195  	_, ok := s.nameToModule[imported.Name()]
   196  	require.True(t, ok)
   197  
   198  	importingModule := &Module{
   199  		ImportFunctionCount:     1,
   200  		TypeSection:             []FunctionType{v_v},
   201  		FunctionSection:         []uint32{0},
   202  		CodeSection:             []Code{{Body: []byte{OpcodeEnd}}},
   203  		MemorySection:           &Memory{Min: 1, Cap: 1},
   204  		MemoryDefinitionSection: []MemoryDefinition{{}},
   205  		GlobalSection: []Global{{
   206  			Type: GlobalType{ValType: ValueTypeI32},
   207  			Init: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(1)},
   208  		}},
   209  		TableSection: []Table{{Min: 10}},
   210  		ImportSection: []Import{
   211  			{Type: ExternTypeFunc, Module: importedModuleName, Name: "fn", DescFunc: 0},
   212  		},
   213  	}
   214  
   215  	// Concurrent instantiate, close should test if locks work on the ns. If they don't, we should see leaked modules
   216  	// after all of these complete, or an error raised.
   217  	P := 8               // max count of goroutines
   218  	N := 1000            // work per goroutine
   219  	if testing.Short() { // Adjust down if `-test.short`
   220  		P = 4
   221  		N = 100
   222  	}
   223  	hammer.NewHammer(t, P, N).Run(func(name string) {
   224  		mod, instantiateErr := s.Instantiate(testCtx, importingModule, name, sys.DefaultContext(nil), []FunctionTypeID{0})
   225  		require.NoError(t, instantiateErr)
   226  		require.NoError(t, mod.Close(testCtx))
   227  	}, nil)
   228  	if t.Failed() {
   229  		return // At least one test failed, so return now.
   230  	}
   231  
   232  	// Close the imported module.
   233  	require.NoError(t, imported.Close(testCtx))
   234  
   235  	// All instances are freed.
   236  	require.Nil(t, s.moduleList)
   237  }
   238  
   239  func TestStore_hammer_close(t *testing.T) {
   240  	const importedModuleName = "imported"
   241  
   242  	m, err := NewHostModule(
   243  		importedModuleName,
   244  		[]string{"fn"},
   245  		map[string]*HostFunc{"fn": {ExportName: "fn", Code: Code{GoFunc: func() {}}}},
   246  		api.CoreFeaturesV1,
   247  	)
   248  	require.NoError(t, err)
   249  
   250  	s := newStore()
   251  	imported, err := s.Instantiate(testCtx, m, importedModuleName, nil, []FunctionTypeID{0})
   252  	require.NoError(t, err)
   253  
   254  	_, ok := s.nameToModule[imported.Name()]
   255  	require.True(t, ok)
   256  
   257  	importingModule := &Module{
   258  		ImportFunctionCount:     1,
   259  		TypeSection:             []FunctionType{v_v},
   260  		FunctionSection:         []uint32{0},
   261  		CodeSection:             []Code{{Body: []byte{OpcodeEnd}}},
   262  		MemorySection:           &Memory{Min: 1, Cap: 1},
   263  		MemoryDefinitionSection: []MemoryDefinition{{}},
   264  		GlobalSection: []Global{{
   265  			Type: GlobalType{ValType: ValueTypeI32},
   266  			Init: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(1)},
   267  		}},
   268  		TableSection: []Table{{Min: 10}},
   269  		ImportSection: []Import{
   270  			{Type: ExternTypeFunc, Module: importedModuleName, Name: "fn", DescFunc: 0},
   271  		},
   272  	}
   273  
   274  	const instCount = 10000
   275  	instances := make([]api.Module, instCount)
   276  	for i := 0; i < instCount; i++ {
   277  		mod, instantiateErr := s.Instantiate(testCtx, importingModule, strconv.Itoa(i), sys.DefaultContext(nil), []FunctionTypeID{0})
   278  		require.NoError(t, instantiateErr)
   279  		instances[i] = mod
   280  	}
   281  
   282  	hammer.NewHammer(t, 100, 2).Run(func(name string) {
   283  		for i := 0; i < instCount; i++ {
   284  			if i == instCount/2 {
   285  				// Close store concurrently as well.
   286  				err := s.CloseWithExitCode(testCtx, 0)
   287  				require.NoError(t, err)
   288  			}
   289  			err := instances[i].CloseWithExitCode(testCtx, 0)
   290  			require.NoError(t, err)
   291  		}
   292  		require.NoError(t, err)
   293  	}, nil)
   294  	if t.Failed() {
   295  		return // At least one test failed, so return now.
   296  	}
   297  
   298  	// All instances are freed.
   299  	require.Nil(t, s.moduleList)
   300  }
   301  
   302  func TestStore_Instantiate_Errors(t *testing.T) {
   303  	const importedModuleName = "imported"
   304  	const importingModuleName = "test"
   305  
   306  	m, err := NewHostModule(
   307  		importedModuleName,
   308  		[]string{"fn"},
   309  		map[string]*HostFunc{"fn": {ExportName: "fn", Code: Code{GoFunc: func() {}}}},
   310  		api.CoreFeaturesV1,
   311  	)
   312  	require.NoError(t, err)
   313  
   314  	t.Run("Fails if module name already in use", func(t *testing.T) {
   315  		s := newStore()
   316  		_, err = s.Instantiate(testCtx, m, importedModuleName, nil, []FunctionTypeID{0})
   317  		require.NoError(t, err)
   318  
   319  		// Trying to register it again should fail
   320  		_, err = s.Instantiate(testCtx, m, importedModuleName, nil, []FunctionTypeID{0})
   321  		require.EqualError(t, err, "module[imported] has already been instantiated")
   322  	})
   323  
   324  	t.Run("fail resolve import", func(t *testing.T) {
   325  		s := newStore()
   326  		_, err = s.Instantiate(testCtx, m, importedModuleName, nil, []FunctionTypeID{0})
   327  		require.NoError(t, err)
   328  
   329  		hm := s.nameToModule[importedModuleName]
   330  		require.NotNil(t, hm)
   331  
   332  		_, err = s.Instantiate(testCtx, &Module{
   333  			TypeSection: []FunctionType{v_v},
   334  			ImportSection: []Import{
   335  				// The first import resolve succeeds -> increment hm.dependentCount.
   336  				{Type: ExternTypeFunc, Module: importedModuleName, Name: "fn", DescFunc: 0},
   337  				// But the second one tries to import uninitialized-module ->
   338  				{Type: ExternTypeFunc, Module: "non-exist", Name: "fn", DescFunc: 0},
   339  			},
   340  			ImportPerModule: map[string][]*Import{
   341  				importedModuleName: {{Type: ExternTypeFunc, Module: importedModuleName, Name: "fn", DescFunc: 0}},
   342  				"non-exist":        {{Name: "fn", DescFunc: 0}},
   343  			},
   344  		}, importingModuleName, nil, nil)
   345  		require.EqualError(t, err, "module[non-exist] not instantiated")
   346  	})
   347  
   348  	t.Run("creating engine failed", func(t *testing.T) {
   349  		s := newStore()
   350  
   351  		_, err = s.Instantiate(testCtx, m, importedModuleName, nil, []FunctionTypeID{0})
   352  		require.NoError(t, err)
   353  
   354  		hm := s.nameToModule[importedModuleName]
   355  		require.NotNil(t, hm)
   356  
   357  		engine := s.Engine.(*mockEngine)
   358  		engine.shouldCompileFail = true
   359  
   360  		importingModule := &Module{
   361  			ImportFunctionCount: 1,
   362  			TypeSection:         []FunctionType{v_v},
   363  			FunctionSection:     []uint32{0, 0},
   364  			CodeSection: []Code{
   365  				{Body: []byte{OpcodeEnd}},
   366  				{Body: []byte{OpcodeEnd}},
   367  			},
   368  			ImportSection: []Import{
   369  				{Type: ExternTypeFunc, Module: importedModuleName, Name: "fn", DescFunc: 0},
   370  			},
   371  		}
   372  
   373  		_, err = s.Instantiate(testCtx, importingModule, importingModuleName, nil, []FunctionTypeID{0})
   374  		require.EqualError(t, err, "some engine creation error")
   375  	})
   376  
   377  	t.Run("start func failed", func(t *testing.T) {
   378  		s := newStore()
   379  		engine := s.Engine.(*mockEngine)
   380  		engine.callFailIndex = 1
   381  
   382  		_, err = s.Instantiate(testCtx, m, importedModuleName, nil, []FunctionTypeID{0})
   383  		require.NoError(t, err)
   384  
   385  		hm := s.nameToModule[importedModuleName]
   386  		require.NotNil(t, hm)
   387  
   388  		startFuncIndex := uint32(1)
   389  		importingModule := &Module{
   390  			ImportFunctionCount: 1,
   391  			TypeSection:         []FunctionType{v_v},
   392  			FunctionSection:     []uint32{0},
   393  			CodeSection:         []Code{{Body: []byte{OpcodeEnd}}},
   394  			StartSection:        &startFuncIndex,
   395  			ImportSection: []Import{
   396  				{Type: ExternTypeFunc, Module: importedModuleName, Name: "fn", DescFunc: 0},
   397  			},
   398  		}
   399  
   400  		_, err = s.Instantiate(testCtx, importingModule, importingModuleName, nil, []FunctionTypeID{0})
   401  		require.EqualError(t, err, "start function[1] failed: call failed")
   402  	})
   403  }
   404  
   405  type mockEngine struct {
   406  	shouldCompileFail bool
   407  	callFailIndex     int
   408  }
   409  
   410  type mockModuleEngine struct {
   411  	name                 string
   412  	callFailIndex        int
   413  	functionRefs         map[Index]Reference
   414  	resolveImportsCalled map[Index]Index
   415  	importedMemModEngine ModuleEngine
   416  	lookupEntries        map[Index]mockModuleEngineLookupEntry
   417  }
   418  
   419  type mockModuleEngineLookupEntry struct {
   420  	m     *ModuleInstance
   421  	index Index
   422  }
   423  
   424  type mockCallEngine struct {
   425  	internalapi.WazeroOnlyType
   426  	index         Index
   427  	callFailIndex int
   428  }
   429  
   430  func newStore() *Store {
   431  	return NewStore(api.CoreFeaturesV1, &mockEngine{shouldCompileFail: false, callFailIndex: -1})
   432  }
   433  
   434  // CompileModule implements the same method as documented on wasm.Engine.
   435  func (e *mockEngine) Close() error {
   436  	return nil
   437  }
   438  
   439  // CompileModule implements the same method as documented on wasm.Engine.
   440  func (e *mockEngine) CompileModule(context.Context, *Module, []experimental.FunctionListener, bool) error {
   441  	return nil
   442  }
   443  
   444  // LookupFunction implements the same method as documented on wasm.Engine.
   445  func (e *mockModuleEngine) LookupFunction(_ *TableInstance, _ FunctionTypeID, offset Index) (*ModuleInstance, Index) {
   446  	if entry, ok := e.lookupEntries[offset]; ok {
   447  		return entry.m, entry.index
   448  	}
   449  	return nil, 0
   450  }
   451  
   452  // CompiledModuleCount implements the same method as documented on wasm.Engine.
   453  func (e *mockEngine) CompiledModuleCount() uint32 { return 0 }
   454  
   455  // DeleteCompiledModule implements the same method as documented on wasm.Engine.
   456  func (e *mockEngine) DeleteCompiledModule(*Module) {}
   457  
   458  // NewModuleEngine implements the same method as documented on wasm.Engine.
   459  func (e *mockEngine) NewModuleEngine(_ *Module, _ *ModuleInstance) (ModuleEngine, error) {
   460  	if e.shouldCompileFail {
   461  		return nil, fmt.Errorf("some engine creation error")
   462  	}
   463  	return &mockModuleEngine{callFailIndex: e.callFailIndex, resolveImportsCalled: map[Index]Index{}}, nil
   464  }
   465  
   466  // mockModuleEngine implements the same method as documented on wasm.ModuleEngine.
   467  func (e *mockModuleEngine) DoneInstantiation() {}
   468  
   469  // FunctionInstanceReference implements the same method as documented on wasm.ModuleEngine.
   470  func (e *mockModuleEngine) FunctionInstanceReference(i Index) Reference {
   471  	return e.functionRefs[i]
   472  }
   473  
   474  // ResolveImportedFunction implements the same method as documented on wasm.ModuleEngine.
   475  func (e *mockModuleEngine) ResolveImportedFunction(index, importedIndex Index, _ ModuleEngine) {
   476  	e.resolveImportsCalled[index] = importedIndex
   477  }
   478  
   479  // ResolveImportedMemory implements the same method as documented on wasm.ModuleEngine.
   480  func (e *mockModuleEngine) ResolveImportedMemory(imp ModuleEngine) {
   481  	e.importedMemModEngine = imp
   482  }
   483  
   484  // NewFunction implements the same method as documented on wasm.ModuleEngine.
   485  func (e *mockModuleEngine) NewFunction(index Index) api.Function {
   486  	return &mockCallEngine{index: index, callFailIndex: e.callFailIndex}
   487  }
   488  
   489  // InitializeFuncrefGlobals implements the same method as documented on wasm.ModuleEngine.
   490  func (e *mockModuleEngine) InitializeFuncrefGlobals(globals []*GlobalInstance) {}
   491  
   492  // Name implements the same method as documented on wasm.ModuleEngine.
   493  func (e *mockModuleEngine) Name() string {
   494  	return e.name
   495  }
   496  
   497  // Close implements the same method as documented on wasm.ModuleEngine.
   498  func (e *mockModuleEngine) Close(context.Context) {
   499  }
   500  
   501  // Call implements the same method as documented on api.Function.
   502  func (ce *mockCallEngine) Definition() api.FunctionDefinition { return nil }
   503  
   504  // Call implements the same method as documented on api.Function.
   505  func (ce *mockCallEngine) Call(ctx context.Context, _ ...uint64) (results []uint64, err error) {
   506  	return nil, ce.CallWithStack(ctx, nil)
   507  }
   508  
   509  // CallWithStack implements the same method as documented on api.Function.
   510  func (ce *mockCallEngine) CallWithStack(_ context.Context, _ []uint64) error {
   511  	if ce.callFailIndex >= 0 && ce.index == Index(ce.callFailIndex) {
   512  		return errors.New("call failed")
   513  	}
   514  	return nil
   515  }
   516  
   517  func TestStore_getFunctionTypeID(t *testing.T) {
   518  	t.Run("too many functions", func(t *testing.T) {
   519  		s := newStore()
   520  		const max = 10
   521  		s.functionMaxTypes = max
   522  		s.typeIDs = make(map[string]FunctionTypeID)
   523  		for i := 0; i < max; i++ {
   524  			s.typeIDs[strconv.Itoa(i)] = 0
   525  		}
   526  		_, err := s.GetFunctionTypeID(&FunctionType{})
   527  		require.Error(t, err)
   528  	})
   529  	t.Run("ok", func(t *testing.T) {
   530  		tests := []FunctionType{
   531  			{Params: []ValueType{}},
   532  			{Params: []ValueType{ValueTypeF32}},
   533  			{Results: []ValueType{ValueTypeF64}},
   534  			{Params: []ValueType{ValueTypeI32}, Results: []ValueType{ValueTypeI64}},
   535  		}
   536  
   537  		for _, tt := range tests {
   538  			tc := tt
   539  			t.Run(tc.String(), func(t *testing.T) {
   540  				s := newStore()
   541  				actual, err := s.GetFunctionTypeID(&tc)
   542  				require.NoError(t, err)
   543  
   544  				expectedTypeID, ok := s.typeIDs[tc.String()]
   545  				require.True(t, ok)
   546  				require.Equal(t, expectedTypeID, actual)
   547  			})
   548  		}
   549  	})
   550  }
   551  
   552  func TestGlobalInstance_initialize(t *testing.T) {
   553  	t.Run("basic type const expr", func(t *testing.T) {
   554  		for _, vt := range []ValueType{ValueTypeI32, ValueTypeI64, ValueTypeF32, ValueTypeF64} {
   555  			t.Run(ValueTypeName(vt), func(t *testing.T) {
   556  				g := &GlobalInstance{Type: GlobalType{ValType: vt}}
   557  				expr := &ConstantExpression{}
   558  				switch vt {
   559  				case ValueTypeI32:
   560  					expr.Data = []byte{1}
   561  					expr.Opcode = OpcodeI32Const
   562  				case ValueTypeI64:
   563  					expr.Data = []byte{2}
   564  					expr.Opcode = OpcodeI64Const
   565  				case ValueTypeF32:
   566  					expr.Data = u64.LeBytes(api.EncodeF32(math.MaxFloat32))
   567  					expr.Opcode = OpcodeF32Const
   568  				case ValueTypeF64:
   569  					expr.Data = u64.LeBytes(api.EncodeF64(math.MaxFloat64))
   570  					expr.Opcode = OpcodeF64Const
   571  				}
   572  
   573  				g.initialize(nil, expr, nil)
   574  
   575  				switch vt {
   576  				case ValueTypeI32:
   577  					require.Equal(t, int32(1), int32(g.Val))
   578  				case ValueTypeI64:
   579  					require.Equal(t, int64(2), int64(g.Val))
   580  				case ValueTypeF32:
   581  					require.Equal(t, float32(math.MaxFloat32), math.Float32frombits(uint32(g.Val)))
   582  				case ValueTypeF64:
   583  					require.Equal(t, math.MaxFloat64, math.Float64frombits(g.Val))
   584  				}
   585  			})
   586  		}
   587  	})
   588  	t.Run("ref.null", func(t *testing.T) {
   589  		tests := []struct {
   590  			name string
   591  			expr *ConstantExpression
   592  		}{
   593  			{
   594  				name: "ref.null (externref)",
   595  				expr: &ConstantExpression{
   596  					Opcode: OpcodeRefNull,
   597  					Data:   []byte{RefTypeExternref},
   598  				},
   599  			},
   600  			{
   601  				name: "ref.null (funcref)",
   602  				expr: &ConstantExpression{
   603  					Opcode: OpcodeRefNull,
   604  					Data:   []byte{RefTypeFuncref},
   605  				},
   606  			},
   607  		}
   608  
   609  		for _, tt := range tests {
   610  			tc := tt
   611  			t.Run(tc.name, func(t *testing.T) {
   612  				g := GlobalInstance{}
   613  				g.Type.ValType = tc.expr.Data[0]
   614  				g.initialize(nil, tc.expr, nil)
   615  				require.Equal(t, uint64(0), g.Val)
   616  			})
   617  		}
   618  	})
   619  	t.Run("ref.func", func(t *testing.T) {
   620  		g := GlobalInstance{Type: GlobalType{ValType: RefTypeFuncref}}
   621  		g.initialize(nil,
   622  			&ConstantExpression{Opcode: OpcodeRefFunc, Data: []byte{1}},
   623  			func(funcIndex Index) Reference {
   624  				require.Equal(t, Index(1), funcIndex)
   625  				return 0xdeadbeaf
   626  			},
   627  		)
   628  		require.Equal(t, uint64(0xdeadbeaf), g.Val)
   629  	})
   630  	t.Run("global expr", func(t *testing.T) {
   631  		tests := []struct {
   632  			valueType  ValueType
   633  			val, valHi uint64
   634  		}{
   635  			{valueType: ValueTypeI32, val: 10},
   636  			{valueType: ValueTypeI64, val: 20},
   637  			{valueType: ValueTypeF32, val: uint64(math.Float32bits(634634432.12311))},
   638  			{valueType: ValueTypeF64, val: math.Float64bits(1.12312311)},
   639  			{valueType: ValueTypeV128, val: 0x1, valHi: 0x2},
   640  			{valueType: ValueTypeExternref, val: 0x12345},
   641  			{valueType: ValueTypeFuncref, val: 0x54321},
   642  		}
   643  
   644  		for _, tt := range tests {
   645  			tc := tt
   646  			t.Run(ValueTypeName(tc.valueType), func(t *testing.T) {
   647  				// The index specified in Data equals zero.
   648  				expr := &ConstantExpression{Data: []byte{0}, Opcode: OpcodeGlobalGet}
   649  				globals := []*GlobalInstance{{Val: tc.val, ValHi: tc.valHi, Type: GlobalType{ValType: tc.valueType}}}
   650  
   651  				g := &GlobalInstance{Type: GlobalType{ValType: tc.valueType}}
   652  				g.initialize(globals, expr, nil)
   653  
   654  				switch tc.valueType {
   655  				case ValueTypeI32:
   656  					require.Equal(t, int32(tc.val), int32(g.Val))
   657  				case ValueTypeI64:
   658  					require.Equal(t, int64(tc.val), int64(g.Val))
   659  				case ValueTypeF32:
   660  					require.Equal(t, tc.val, g.Val)
   661  				case ValueTypeF64:
   662  					require.Equal(t, tc.val, g.Val)
   663  				case ValueTypeV128:
   664  					require.Equal(t, uint64(0x1), g.Val)
   665  					require.Equal(t, uint64(0x2), g.ValHi)
   666  				case ValueTypeFuncref, ValueTypeExternref:
   667  					require.Equal(t, tc.val, g.Val)
   668  				}
   669  			})
   670  		}
   671  	})
   672  
   673  	t.Run("vector", func(t *testing.T) {
   674  		expr := &ConstantExpression{Data: []byte{
   675  			1, 0, 0, 0, 0, 0, 0, 0,
   676  			2, 0, 0, 0, 0, 0, 0, 0,
   677  		}, Opcode: OpcodeVecV128Const}
   678  		g := GlobalInstance{Type: GlobalType{ValType: ValueTypeV128}}
   679  		g.initialize(nil, expr, nil)
   680  		require.Equal(t, uint64(0x1), g.Val)
   681  		require.Equal(t, uint64(0x2), g.ValHi)
   682  	})
   683  }
   684  
   685  func Test_resolveImports(t *testing.T) {
   686  	const moduleName = "test"
   687  	const name = "target"
   688  
   689  	t.Run("module not instantiated", func(t *testing.T) {
   690  		m := &ModuleInstance{s: newStore()}
   691  		err := m.resolveImports(&Module{ImportPerModule: map[string][]*Import{"unknown": {{}}}})
   692  		require.EqualError(t, err, "module[unknown] not instantiated")
   693  	})
   694  	t.Run("export instance not found", func(t *testing.T) {
   695  		m := &ModuleInstance{s: newStore()}
   696  		m.s.nameToModule[moduleName] = &ModuleInstance{Exports: map[string]*Export{}, ModuleName: moduleName}
   697  		err := m.resolveImports(&Module{ImportPerModule: map[string][]*Import{moduleName: {{Name: "unknown"}}}})
   698  		require.EqualError(t, err, "\"unknown\" is not exported in module \"test\"")
   699  	})
   700  	t.Run("func", func(t *testing.T) {
   701  		t.Run("ok", func(t *testing.T) {
   702  			s := newStore()
   703  			s.nameToModule[moduleName] = &ModuleInstance{
   704  				Exports: map[string]*Export{
   705  					name: {Type: ExternTypeFunc, Index: 2},
   706  					"":   {Type: ExternTypeFunc, Index: 4},
   707  				},
   708  				ModuleName: moduleName,
   709  				Source: &Module{
   710  					FunctionSection: []Index{0, 0, 1, 0, 0},
   711  					TypeSection: []FunctionType{
   712  						{Params: []ValueType{ExternTypeFunc}},
   713  						{Params: []ValueType{i32}, Results: []ValueType{ValueTypeV128}},
   714  					},
   715  				},
   716  			}
   717  
   718  			module := &Module{
   719  				TypeSection: []FunctionType{
   720  					{Params: []ValueType{i32}, Results: []ValueType{ValueTypeV128}},
   721  					{Params: []ValueType{ExternTypeFunc}},
   722  				},
   723  				ImportFunctionCount: 2,
   724  				ImportPerModule: map[string][]*Import{
   725  					moduleName: {
   726  						{Module: moduleName, Name: name, Type: ExternTypeFunc, DescFunc: 0, IndexPerType: 0},
   727  						{Module: moduleName, Name: "", Type: ExternTypeFunc, DescFunc: 1, IndexPerType: 1},
   728  					},
   729  				},
   730  			}
   731  
   732  			m := &ModuleInstance{Engine: &mockModuleEngine{resolveImportsCalled: map[Index]Index{}}, s: s, Source: module}
   733  			err := m.resolveImports(module)
   734  			require.NoError(t, err)
   735  
   736  			me := m.Engine.(*mockModuleEngine)
   737  			require.Equal(t, me.resolveImportsCalled[0], Index(2))
   738  			require.Equal(t, me.resolveImportsCalled[1], Index(4))
   739  		})
   740  		t.Run("signature mismatch", func(t *testing.T) {
   741  			s := newStore()
   742  			s.nameToModule[moduleName] = &ModuleInstance{
   743  				Exports: map[string]*Export{
   744  					name: {Type: ExternTypeFunc, Index: 0},
   745  				},
   746  				ModuleName: moduleName,
   747  				TypeIDs:    []FunctionTypeID{123435},
   748  				Source: &Module{
   749  					FunctionSection: []Index{0},
   750  					TypeSection: []FunctionType{
   751  						{Params: []ValueType{}},
   752  					},
   753  				},
   754  			}
   755  			module := &Module{
   756  				TypeSection: []FunctionType{{Results: []ValueType{ValueTypeF32}}},
   757  				ImportPerModule: map[string][]*Import{
   758  					moduleName: {{Module: moduleName, Name: name, Type: ExternTypeFunc, DescFunc: 0}},
   759  				},
   760  			}
   761  
   762  			m := &ModuleInstance{Engine: &mockModuleEngine{resolveImportsCalled: map[Index]Index{}}, s: s, Source: module}
   763  			err := m.resolveImports(module)
   764  			require.EqualError(t, err, "import func[test.target]: signature mismatch: v_f32 != v_v")
   765  		})
   766  	})
   767  	t.Run("global", func(t *testing.T) {
   768  		t.Run("ok", func(t *testing.T) {
   769  			s := newStore()
   770  			g := &GlobalInstance{Type: GlobalType{ValType: ValueTypeI32}}
   771  			m := &ModuleInstance{Globals: make([]*GlobalInstance, 1), s: s}
   772  			s.nameToModule[moduleName] = &ModuleInstance{
   773  				Globals: []*GlobalInstance{g},
   774  				Exports: map[string]*Export{name: {Type: ExternTypeGlobal, Index: 0}}, ModuleName: moduleName,
   775  			}
   776  			err := m.resolveImports(
   777  				&Module{
   778  					ImportPerModule: map[string][]*Import{moduleName: {{Name: name, Type: ExternTypeGlobal, DescGlobal: g.Type}}},
   779  				},
   780  			)
   781  			require.NoError(t, err)
   782  			require.True(t, globalsContain(m.Globals, g), "expected to find %v in %v", g, m.Globals)
   783  		})
   784  		t.Run("mutability mismatch", func(t *testing.T) {
   785  			s := newStore()
   786  			s.nameToModule[moduleName] = &ModuleInstance{
   787  				Globals: []*GlobalInstance{{Type: GlobalType{Mutable: false}}},
   788  				Exports: map[string]*Export{name: {
   789  					Type:  ExternTypeGlobal,
   790  					Index: 0,
   791  				}},
   792  				ModuleName: moduleName,
   793  			}
   794  			m := &ModuleInstance{Globals: make([]*GlobalInstance, 1), s: s}
   795  			err := m.resolveImports(&Module{
   796  				ImportPerModule: map[string][]*Import{moduleName: {
   797  					{Module: moduleName, Name: name, Type: ExternTypeGlobal, DescGlobal: GlobalType{Mutable: true}},
   798  				}},
   799  			})
   800  			require.EqualError(t, err, "import global[test.target]: mutability mismatch: true != false")
   801  		})
   802  		t.Run("type mismatch", func(t *testing.T) {
   803  			s := newStore()
   804  			s.nameToModule[moduleName] = &ModuleInstance{
   805  				Globals: []*GlobalInstance{{Type: GlobalType{ValType: ValueTypeI32}}},
   806  				Exports: map[string]*Export{name: {
   807  					Type:  ExternTypeGlobal,
   808  					Index: 0,
   809  				}},
   810  				ModuleName: moduleName,
   811  			}
   812  			m := &ModuleInstance{Globals: make([]*GlobalInstance, 1), s: s}
   813  			err := m.resolveImports(&Module{
   814  				ImportPerModule: map[string][]*Import{moduleName: {
   815  					{Module: moduleName, Name: name, Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeF64}},
   816  				}},
   817  			})
   818  			require.EqualError(t, err, "import global[test.target]: value type mismatch: f64 != i32")
   819  		})
   820  	})
   821  	t.Run("memory", func(t *testing.T) {
   822  		t.Run("ok", func(t *testing.T) {
   823  			max := uint32(10)
   824  			memoryInst := &MemoryInstance{Max: max}
   825  			s := newStore()
   826  			importedME := &mockModuleEngine{}
   827  			s.nameToModule[moduleName] = &ModuleInstance{
   828  				MemoryInstance: memoryInst,
   829  				Exports: map[string]*Export{name: {
   830  					Type: ExternTypeMemory,
   831  				}},
   832  				ModuleName: moduleName,
   833  				Engine:     importedME,
   834  			}
   835  			m := &ModuleInstance{s: s, Engine: &mockModuleEngine{resolveImportsCalled: map[Index]Index{}}}
   836  			err := m.resolveImports(&Module{
   837  				ImportPerModule: map[string][]*Import{
   838  					moduleName: {{Module: moduleName, Name: name, Type: ExternTypeMemory, DescMem: &Memory{Max: max}}},
   839  				},
   840  			})
   841  			require.NoError(t, err)
   842  			require.Equal(t, m.MemoryInstance, memoryInst)
   843  			require.Equal(t, importedME, m.Engine.(*mockModuleEngine).importedMemModEngine)
   844  		})
   845  		t.Run("minimum size mismatch", func(t *testing.T) {
   846  			importMemoryType := &Memory{Min: 2, Cap: 2}
   847  			s := newStore()
   848  			s.nameToModule[moduleName] = &ModuleInstance{
   849  				MemoryInstance: &MemoryInstance{Min: importMemoryType.Min - 1, Cap: 2},
   850  				Exports: map[string]*Export{name: {
   851  					Type: ExternTypeMemory,
   852  				}},
   853  				ModuleName: moduleName,
   854  			}
   855  			m := &ModuleInstance{s: s}
   856  			err := m.resolveImports(&Module{
   857  				ImportPerModule: map[string][]*Import{
   858  					moduleName: {{Module: moduleName, Name: name, Type: ExternTypeMemory, DescMem: importMemoryType}},
   859  				},
   860  			})
   861  			require.EqualError(t, err, "import memory[test.target]: minimum size mismatch: 2 > 1")
   862  		})
   863  		t.Run("maximum size mismatch", func(t *testing.T) {
   864  			s := newStore()
   865  			s.nameToModule[moduleName] = &ModuleInstance{
   866  				MemoryInstance: &MemoryInstance{Max: MemoryLimitPages},
   867  				Exports: map[string]*Export{name: {
   868  					Type: ExternTypeMemory,
   869  				}},
   870  				ModuleName: moduleName,
   871  			}
   872  
   873  			max := uint32(10)
   874  			importMemoryType := &Memory{Max: max}
   875  			m := &ModuleInstance{s: s}
   876  			err := m.resolveImports(&Module{
   877  				ImportPerModule: map[string][]*Import{moduleName: {{Module: moduleName, Name: name, Type: ExternTypeMemory, DescMem: importMemoryType}}},
   878  			})
   879  			require.EqualError(t, err, "import memory[test.target]: maximum size mismatch: 10 < 65536")
   880  		})
   881  	})
   882  }
   883  
   884  func TestModuleInstance_validateData(t *testing.T) {
   885  	m := &ModuleInstance{MemoryInstance: &MemoryInstance{Buffer: make([]byte, 5)}}
   886  	tests := []struct {
   887  		name   string
   888  		data   []DataSegment
   889  		expErr string
   890  	}{
   891  		{
   892  			name: "ok",
   893  			data: []DataSegment{
   894  				{OffsetExpression: ConstantExpression{Opcode: OpcodeI32Const, Data: const1}, Init: []byte{0}},
   895  				{OffsetExpression: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(2)}, Init: []byte{0}},
   896  			},
   897  		},
   898  		{
   899  			name: "out of bounds - single one byte",
   900  			data: []DataSegment{
   901  				{OffsetExpression: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(5)}, Init: []byte{0}},
   902  			},
   903  			expErr: "data[0]: out of bounds memory access",
   904  		},
   905  		{
   906  			name: "out of bounds - multi bytes",
   907  			data: []DataSegment{
   908  				{OffsetExpression: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(0)}, Init: []byte{0}},
   909  				{OffsetExpression: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(3)}, Init: []byte{0, 1, 2}},
   910  			},
   911  			expErr: "data[1]: out of bounds memory access",
   912  		},
   913  	}
   914  
   915  	for _, tt := range tests {
   916  		tc := tt
   917  		t.Run(tc.name, func(t *testing.T) {
   918  			err := m.validateData(tc.data)
   919  			if tc.expErr != "" {
   920  				require.EqualError(t, err, tc.expErr)
   921  			} else {
   922  				require.NoError(t, err)
   923  			}
   924  		})
   925  	}
   926  }
   927  
   928  func TestModuleInstance_applyData(t *testing.T) {
   929  	t.Run("ok", func(t *testing.T) {
   930  		m := &ModuleInstance{MemoryInstance: &MemoryInstance{Buffer: make([]byte, 10)}}
   931  		err := m.applyData([]DataSegment{
   932  			{OffsetExpression: ConstantExpression{Opcode: OpcodeI32Const, Data: const0}, Init: []byte{0xa, 0xf}},
   933  			{OffsetExpression: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeUint32(8)}, Init: []byte{0x1, 0x5}},
   934  		})
   935  		require.NoError(t, err)
   936  		require.Equal(t, []byte{0xa, 0xf, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x5}, m.MemoryInstance.Buffer)
   937  		require.Equal(t, [][]byte{{0xa, 0xf}, {0x1, 0x5}}, m.DataInstances)
   938  	})
   939  	t.Run("error", func(t *testing.T) {
   940  		m := &ModuleInstance{MemoryInstance: &MemoryInstance{Buffer: make([]byte, 5)}}
   941  		err := m.applyData([]DataSegment{
   942  			{OffsetExpression: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeUint32(8)}, Init: []byte{}},
   943  		})
   944  		require.EqualError(t, err, "data[0]: out of bounds memory access")
   945  	})
   946  }
   947  
   948  func globalsContain(globals []*GlobalInstance, want *GlobalInstance) bool {
   949  	for _, f := range globals {
   950  		if f == want {
   951  			return true
   952  		}
   953  	}
   954  	return false
   955  }
   956  
   957  func TestModuleInstance_applyElements(t *testing.T) {
   958  	leb128_100 := leb128.EncodeInt32(100)
   959  
   960  	t.Run("extenref", func(t *testing.T) {
   961  		m := &ModuleInstance{}
   962  		m.Tables = []*TableInstance{{Type: RefTypeExternref, References: make([]Reference, 10)}}
   963  		for i := range m.Tables[0].References {
   964  			m.Tables[0].References[i] = 0xffff // non-null ref.
   965  		}
   966  
   967  		// This shouldn't panic.
   968  		m.applyElements([]ElementSegment{{Mode: ElementModeActive, OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128_100}}})
   969  		m.applyElements([]ElementSegment{
   970  			{Mode: ElementModeActive, OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0}}, Init: make([]Index, 3)},
   971  			{Mode: ElementModeActive, OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128_100}, Init: make([]Index, 5)}, // Iteration stops at this point, so the offset:5 below shouldn't be applied.
   972  			{Mode: ElementModeActive, OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{5}}, Init: make([]Index, 5)},
   973  		})
   974  		require.Equal(t, []Reference{0, 0, 0, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff},
   975  			m.Tables[0].References)
   976  		m.applyElements([]ElementSegment{
   977  			{Mode: ElementModeActive, OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{5}}, Init: make([]Index, 5)},
   978  		})
   979  		require.Equal(t, []Reference{0, 0, 0, 0xffff, 0xffff, 0, 0, 0, 0, 0}, m.Tables[0].References)
   980  	})
   981  	t.Run("funcref", func(t *testing.T) {
   982  		e := &mockEngine{}
   983  		me, err := e.NewModuleEngine(nil, nil)
   984  		me.(*mockModuleEngine).functionRefs = map[Index]Reference{0: 0xa, 1: 0xaa, 2: 0xaaa, 3: 0xaaaa}
   985  		require.NoError(t, err)
   986  		m := &ModuleInstance{Engine: me, Globals: []*GlobalInstance{{}, {Val: 0xabcde}}}
   987  
   988  		m.Tables = []*TableInstance{{Type: RefTypeFuncref, References: make([]Reference, 10)}}
   989  		for i := range m.Tables[0].References {
   990  			m.Tables[0].References[i] = 0xffff // non-null ref.
   991  		}
   992  
   993  		// This shouldn't panic.
   994  		m.applyElements([]ElementSegment{{Mode: ElementModeActive, OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128_100}, Init: []Index{1, 2, 3}}})
   995  		m.applyElements([]ElementSegment{
   996  			{Mode: ElementModeActive, OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0}}, Init: []Index{0, 1, 2}},
   997  			{Mode: ElementModeActive, OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{9}}, Init: []Index{1 | ElementInitImportedGlobalFunctionReference}},
   998  			{Mode: ElementModeActive, OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128_100}, Init: make([]Index, 5)}, // Iteration stops at this point, so the offset:5 below shouldn't be applied.
   999  			{Mode: ElementModeActive, OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{5}}, Init: make([]Index, 5)},
  1000  		})
  1001  		require.Equal(t, []Reference{0xa, 0xaa, 0xaaa, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xabcde},
  1002  			m.Tables[0].References)
  1003  		m.applyElements([]ElementSegment{
  1004  			{Mode: ElementModeActive, OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{5}}, Init: []Index{0, ElementInitNullReference, 2}},
  1005  		})
  1006  		require.Equal(t, []Reference{0xa, 0xaa, 0xaaa, 0xffff, 0xffff, 0xa, 0xffff, 0xaaa, 0xffff, 0xabcde},
  1007  			m.Tables[0].References)
  1008  	})
  1009  }