github.com/tetratelabs/wazero@v1.7.3-0.20240513003603-48f702e154b5/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/tetratelabs/wazero/api"
    12  	"github.com/tetratelabs/wazero/experimental"
    13  	"github.com/tetratelabs/wazero/internal/internalapi"
    14  	"github.com/tetratelabs/wazero/internal/leb128"
    15  	"github.com/tetratelabs/wazero/internal/sys"
    16  	"github.com/tetratelabs/wazero/internal/testing/hammer"
    17  	"github.com/tetratelabs/wazero/internal/testing/require"
    18  	"github.com/tetratelabs/wazero/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(p, n int) {
   224  		mod, instantiateErr := s.Instantiate(testCtx, importingModule, fmt.Sprintf("%d:%d", p, n), 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(p, n int) {
   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  // GetGlobalValue implements the same method as documented on wasm.ModuleEngine.
   467  func (e *mockModuleEngine) GetGlobalValue(idx Index) (lo, hi uint64) { panic("BUG") }
   468  
   469  // SetGlobalValue implements the same method as documented on wasm.ModuleEngine.
   470  func (e *mockModuleEngine) SetGlobalValue(idx Index, lo, hi uint64) { panic("BUG") }
   471  
   472  // OwnsGlobals implements the same method as documented on wasm.ModuleEngine.
   473  func (e *mockModuleEngine) OwnsGlobals() bool { return false }
   474  
   475  // DoneInstantiation implements the same method as documented on wasm.ModuleEngine.
   476  func (e *mockModuleEngine) DoneInstantiation() {}
   477  
   478  // FunctionInstanceReference implements the same method as documented on wasm.ModuleEngine.
   479  func (e *mockModuleEngine) FunctionInstanceReference(i Index) Reference {
   480  	return e.functionRefs[i]
   481  }
   482  
   483  // ResolveImportedFunction implements the same method as documented on wasm.ModuleEngine.
   484  func (e *mockModuleEngine) ResolveImportedFunction(index, importedIndex Index, _ ModuleEngine) {
   485  	e.resolveImportsCalled[index] = importedIndex
   486  }
   487  
   488  // ResolveImportedMemory implements the same method as documented on wasm.ModuleEngine.
   489  func (e *mockModuleEngine) ResolveImportedMemory(imp ModuleEngine) {
   490  	e.importedMemModEngine = imp
   491  }
   492  
   493  // NewFunction implements the same method as documented on wasm.ModuleEngine.
   494  func (e *mockModuleEngine) NewFunction(index Index) api.Function {
   495  	return &mockCallEngine{index: index, callFailIndex: e.callFailIndex}
   496  }
   497  
   498  // InitializeFuncrefGlobals implements the same method as documented on wasm.ModuleEngine.
   499  func (e *mockModuleEngine) InitializeFuncrefGlobals(globals []*GlobalInstance) {}
   500  
   501  // Name implements the same method as documented on wasm.ModuleEngine.
   502  func (e *mockModuleEngine) Name() string {
   503  	return e.name
   504  }
   505  
   506  // Close implements the same method as documented on wasm.ModuleEngine.
   507  func (e *mockModuleEngine) Close(context.Context) {
   508  }
   509  
   510  // Call implements the same method as documented on api.Function.
   511  func (ce *mockCallEngine) Definition() api.FunctionDefinition { return nil }
   512  
   513  // Call implements the same method as documented on api.Function.
   514  func (ce *mockCallEngine) Call(ctx context.Context, _ ...uint64) (results []uint64, err error) {
   515  	return nil, ce.CallWithStack(ctx, nil)
   516  }
   517  
   518  // CallWithStack implements the same method as documented on api.Function.
   519  func (ce *mockCallEngine) CallWithStack(_ context.Context, _ []uint64) error {
   520  	if ce.callFailIndex >= 0 && ce.index == Index(ce.callFailIndex) {
   521  		return errors.New("call failed")
   522  	}
   523  	return nil
   524  }
   525  
   526  func TestStore_getFunctionTypeID(t *testing.T) {
   527  	t.Run("too many functions", func(t *testing.T) {
   528  		s := newStore()
   529  		const max = 10
   530  		s.functionMaxTypes = max
   531  		s.typeIDs = make(map[string]FunctionTypeID)
   532  		for i := 0; i < max; i++ {
   533  			s.typeIDs[strconv.Itoa(i)] = 0
   534  		}
   535  		_, err := s.GetFunctionTypeID(&FunctionType{})
   536  		require.Error(t, err)
   537  	})
   538  	t.Run("ok", func(t *testing.T) {
   539  		tests := []FunctionType{
   540  			{Params: []ValueType{}},
   541  			{Params: []ValueType{ValueTypeF32}},
   542  			{Results: []ValueType{ValueTypeF64}},
   543  			{Params: []ValueType{ValueTypeI32}, Results: []ValueType{ValueTypeI64}},
   544  		}
   545  
   546  		for _, tt := range tests {
   547  			tc := tt
   548  			t.Run(tc.String(), func(t *testing.T) {
   549  				s := newStore()
   550  				actual, err := s.GetFunctionTypeID(&tc)
   551  				require.NoError(t, err)
   552  
   553  				expectedTypeID, ok := s.typeIDs[tc.String()]
   554  				require.True(t, ok)
   555  				require.Equal(t, expectedTypeID, actual)
   556  			})
   557  		}
   558  	})
   559  }
   560  
   561  func TestGlobalInstance_initialize(t *testing.T) {
   562  	t.Run("basic type const expr", func(t *testing.T) {
   563  		for _, vt := range []ValueType{ValueTypeI32, ValueTypeI64, ValueTypeF32, ValueTypeF64} {
   564  			t.Run(ValueTypeName(vt), func(t *testing.T) {
   565  				g := &GlobalInstance{Type: GlobalType{ValType: vt}}
   566  				expr := &ConstantExpression{}
   567  				switch vt {
   568  				case ValueTypeI32:
   569  					expr.Data = []byte{1}
   570  					expr.Opcode = OpcodeI32Const
   571  				case ValueTypeI64:
   572  					expr.Data = []byte{2}
   573  					expr.Opcode = OpcodeI64Const
   574  				case ValueTypeF32:
   575  					expr.Data = u64.LeBytes(api.EncodeF32(math.MaxFloat32))
   576  					expr.Opcode = OpcodeF32Const
   577  				case ValueTypeF64:
   578  					expr.Data = u64.LeBytes(api.EncodeF64(math.MaxFloat64))
   579  					expr.Opcode = OpcodeF64Const
   580  				}
   581  
   582  				g.initialize(nil, expr, nil)
   583  
   584  				switch vt {
   585  				case ValueTypeI32:
   586  					require.Equal(t, int32(1), int32(g.Val))
   587  				case ValueTypeI64:
   588  					require.Equal(t, int64(2), int64(g.Val))
   589  				case ValueTypeF32:
   590  					require.Equal(t, float32(math.MaxFloat32), math.Float32frombits(uint32(g.Val)))
   591  				case ValueTypeF64:
   592  					require.Equal(t, math.MaxFloat64, math.Float64frombits(g.Val))
   593  				}
   594  			})
   595  		}
   596  	})
   597  	t.Run("ref.null", func(t *testing.T) {
   598  		tests := []struct {
   599  			name string
   600  			expr *ConstantExpression
   601  		}{
   602  			{
   603  				name: "ref.null (externref)",
   604  				expr: &ConstantExpression{
   605  					Opcode: OpcodeRefNull,
   606  					Data:   []byte{RefTypeExternref},
   607  				},
   608  			},
   609  			{
   610  				name: "ref.null (funcref)",
   611  				expr: &ConstantExpression{
   612  					Opcode: OpcodeRefNull,
   613  					Data:   []byte{RefTypeFuncref},
   614  				},
   615  			},
   616  		}
   617  
   618  		for _, tt := range tests {
   619  			tc := tt
   620  			t.Run(tc.name, func(t *testing.T) {
   621  				g := GlobalInstance{}
   622  				g.Type.ValType = tc.expr.Data[0]
   623  				g.initialize(nil, tc.expr, nil)
   624  				require.Equal(t, uint64(0), g.Val)
   625  			})
   626  		}
   627  	})
   628  	t.Run("ref.func", func(t *testing.T) {
   629  		g := GlobalInstance{Type: GlobalType{ValType: RefTypeFuncref}}
   630  		g.initialize(nil,
   631  			&ConstantExpression{Opcode: OpcodeRefFunc, Data: []byte{1}},
   632  			func(funcIndex Index) Reference {
   633  				require.Equal(t, Index(1), funcIndex)
   634  				return 0xdeadbeaf
   635  			},
   636  		)
   637  		require.Equal(t, uint64(0xdeadbeaf), g.Val)
   638  	})
   639  	t.Run("global expr", func(t *testing.T) {
   640  		tests := []struct {
   641  			valueType  ValueType
   642  			val, valHi uint64
   643  		}{
   644  			{valueType: ValueTypeI32, val: 10},
   645  			{valueType: ValueTypeI64, val: 20},
   646  			{valueType: ValueTypeF32, val: uint64(math.Float32bits(634634432.12311))},
   647  			{valueType: ValueTypeF64, val: math.Float64bits(1.12312311)},
   648  			{valueType: ValueTypeV128, val: 0x1, valHi: 0x2},
   649  			{valueType: ValueTypeExternref, val: 0x12345},
   650  			{valueType: ValueTypeFuncref, val: 0x54321},
   651  		}
   652  
   653  		for _, tt := range tests {
   654  			tc := tt
   655  			t.Run(ValueTypeName(tc.valueType), func(t *testing.T) {
   656  				// The index specified in Data equals zero.
   657  				expr := &ConstantExpression{Data: []byte{0}, Opcode: OpcodeGlobalGet}
   658  				globals := []*GlobalInstance{{Val: tc.val, ValHi: tc.valHi, Type: GlobalType{ValType: tc.valueType}}}
   659  
   660  				g := &GlobalInstance{Type: GlobalType{ValType: tc.valueType}}
   661  				g.initialize(globals, expr, nil)
   662  
   663  				switch tc.valueType {
   664  				case ValueTypeI32:
   665  					require.Equal(t, int32(tc.val), int32(g.Val))
   666  				case ValueTypeI64:
   667  					require.Equal(t, int64(tc.val), int64(g.Val))
   668  				case ValueTypeF32:
   669  					require.Equal(t, tc.val, g.Val)
   670  				case ValueTypeF64:
   671  					require.Equal(t, tc.val, g.Val)
   672  				case ValueTypeV128:
   673  					require.Equal(t, uint64(0x1), g.Val)
   674  					require.Equal(t, uint64(0x2), g.ValHi)
   675  				case ValueTypeFuncref, ValueTypeExternref:
   676  					require.Equal(t, tc.val, g.Val)
   677  				}
   678  			})
   679  		}
   680  	})
   681  
   682  	t.Run("vector", func(t *testing.T) {
   683  		expr := &ConstantExpression{Data: []byte{
   684  			1, 0, 0, 0, 0, 0, 0, 0,
   685  			2, 0, 0, 0, 0, 0, 0, 0,
   686  		}, Opcode: OpcodeVecV128Const}
   687  		g := GlobalInstance{Type: GlobalType{ValType: ValueTypeV128}}
   688  		g.initialize(nil, expr, nil)
   689  		require.Equal(t, uint64(0x1), g.Val)
   690  		require.Equal(t, uint64(0x2), g.ValHi)
   691  	})
   692  }
   693  
   694  func Test_resolveImports(t *testing.T) {
   695  	const moduleName = "test"
   696  	const name = "target"
   697  
   698  	t.Run("module not instantiated", func(t *testing.T) {
   699  		m := &ModuleInstance{s: newStore()}
   700  		err := m.resolveImports(&Module{ImportPerModule: map[string][]*Import{"unknown": {{}}}})
   701  		require.EqualError(t, err, "module[unknown] not instantiated")
   702  	})
   703  	t.Run("export instance not found", func(t *testing.T) {
   704  		m := &ModuleInstance{s: newStore()}
   705  		m.s.nameToModule[moduleName] = &ModuleInstance{Exports: map[string]*Export{}, ModuleName: moduleName}
   706  		err := m.resolveImports(&Module{ImportPerModule: map[string][]*Import{moduleName: {{Name: "unknown"}}}})
   707  		require.EqualError(t, err, "\"unknown\" is not exported in module \"test\"")
   708  	})
   709  	t.Run("func", func(t *testing.T) {
   710  		t.Run("ok", func(t *testing.T) {
   711  			s := newStore()
   712  			s.nameToModule[moduleName] = &ModuleInstance{
   713  				Exports: map[string]*Export{
   714  					name: {Type: ExternTypeFunc, Index: 2},
   715  					"":   {Type: ExternTypeFunc, Index: 4},
   716  				},
   717  				ModuleName: moduleName,
   718  				Source: &Module{
   719  					FunctionSection: []Index{0, 0, 1, 0, 0},
   720  					TypeSection: []FunctionType{
   721  						{Params: []ValueType{ExternTypeFunc}},
   722  						{Params: []ValueType{i32}, Results: []ValueType{ValueTypeV128}},
   723  					},
   724  				},
   725  			}
   726  
   727  			module := &Module{
   728  				TypeSection: []FunctionType{
   729  					{Params: []ValueType{i32}, Results: []ValueType{ValueTypeV128}},
   730  					{Params: []ValueType{ExternTypeFunc}},
   731  				},
   732  				ImportFunctionCount: 2,
   733  				ImportPerModule: map[string][]*Import{
   734  					moduleName: {
   735  						{Module: moduleName, Name: name, Type: ExternTypeFunc, DescFunc: 0, IndexPerType: 0},
   736  						{Module: moduleName, Name: "", Type: ExternTypeFunc, DescFunc: 1, IndexPerType: 1},
   737  					},
   738  				},
   739  			}
   740  
   741  			m := &ModuleInstance{Engine: &mockModuleEngine{resolveImportsCalled: map[Index]Index{}}, s: s, Source: module}
   742  			err := m.resolveImports(module)
   743  			require.NoError(t, err)
   744  
   745  			me := m.Engine.(*mockModuleEngine)
   746  			require.Equal(t, me.resolveImportsCalled[0], Index(2))
   747  			require.Equal(t, me.resolveImportsCalled[1], Index(4))
   748  		})
   749  		t.Run("signature mismatch", func(t *testing.T) {
   750  			s := newStore()
   751  			s.nameToModule[moduleName] = &ModuleInstance{
   752  				Exports: map[string]*Export{
   753  					name: {Type: ExternTypeFunc, Index: 0},
   754  				},
   755  				ModuleName: moduleName,
   756  				TypeIDs:    []FunctionTypeID{123435},
   757  				Source: &Module{
   758  					FunctionSection: []Index{0},
   759  					TypeSection: []FunctionType{
   760  						{Params: []ValueType{}},
   761  					},
   762  				},
   763  			}
   764  			module := &Module{
   765  				TypeSection: []FunctionType{{Results: []ValueType{ValueTypeF32}}},
   766  				ImportPerModule: map[string][]*Import{
   767  					moduleName: {{Module: moduleName, Name: name, Type: ExternTypeFunc, DescFunc: 0}},
   768  				},
   769  			}
   770  
   771  			m := &ModuleInstance{Engine: &mockModuleEngine{resolveImportsCalled: map[Index]Index{}}, s: s, Source: module}
   772  			err := m.resolveImports(module)
   773  			require.EqualError(t, err, "import func[test.target]: signature mismatch: v_f32 != v_v")
   774  		})
   775  	})
   776  	t.Run("global", func(t *testing.T) {
   777  		t.Run("ok", func(t *testing.T) {
   778  			s := newStore()
   779  			g := &GlobalInstance{Type: GlobalType{ValType: ValueTypeI32}}
   780  			m := &ModuleInstance{Globals: make([]*GlobalInstance, 1), s: s}
   781  			s.nameToModule[moduleName] = &ModuleInstance{
   782  				Globals: []*GlobalInstance{g},
   783  				Exports: map[string]*Export{name: {Type: ExternTypeGlobal, Index: 0}}, ModuleName: moduleName,
   784  			}
   785  			err := m.resolveImports(
   786  				&Module{
   787  					ImportPerModule: map[string][]*Import{moduleName: {{Name: name, Type: ExternTypeGlobal, DescGlobal: g.Type}}},
   788  				},
   789  			)
   790  			require.NoError(t, err)
   791  			require.True(t, globalsContain(m.Globals, g), "expected to find %v in %v", g, m.Globals)
   792  		})
   793  		t.Run("mutability mismatch", func(t *testing.T) {
   794  			s := newStore()
   795  			s.nameToModule[moduleName] = &ModuleInstance{
   796  				Globals: []*GlobalInstance{{Type: GlobalType{Mutable: false}}},
   797  				Exports: map[string]*Export{name: {
   798  					Type:  ExternTypeGlobal,
   799  					Index: 0,
   800  				}},
   801  				ModuleName: moduleName,
   802  			}
   803  			m := &ModuleInstance{Globals: make([]*GlobalInstance, 1), s: s}
   804  			err := m.resolveImports(&Module{
   805  				ImportPerModule: map[string][]*Import{moduleName: {
   806  					{Module: moduleName, Name: name, Type: ExternTypeGlobal, DescGlobal: GlobalType{Mutable: true}},
   807  				}},
   808  			})
   809  			require.EqualError(t, err, "import global[test.target]: mutability mismatch: true != false")
   810  		})
   811  		t.Run("type mismatch", func(t *testing.T) {
   812  			s := newStore()
   813  			s.nameToModule[moduleName] = &ModuleInstance{
   814  				Globals: []*GlobalInstance{{Type: GlobalType{ValType: ValueTypeI32}}},
   815  				Exports: map[string]*Export{name: {
   816  					Type:  ExternTypeGlobal,
   817  					Index: 0,
   818  				}},
   819  				ModuleName: moduleName,
   820  			}
   821  			m := &ModuleInstance{Globals: make([]*GlobalInstance, 1), s: s}
   822  			err := m.resolveImports(&Module{
   823  				ImportPerModule: map[string][]*Import{moduleName: {
   824  					{Module: moduleName, Name: name, Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeF64}},
   825  				}},
   826  			})
   827  			require.EqualError(t, err, "import global[test.target]: value type mismatch: f64 != i32")
   828  		})
   829  	})
   830  	t.Run("memory", func(t *testing.T) {
   831  		t.Run("ok", func(t *testing.T) {
   832  			max := uint32(10)
   833  			memoryInst := &MemoryInstance{Max: max}
   834  			s := newStore()
   835  			importedME := &mockModuleEngine{}
   836  			s.nameToModule[moduleName] = &ModuleInstance{
   837  				MemoryInstance: memoryInst,
   838  				Exports: map[string]*Export{name: {
   839  					Type: ExternTypeMemory,
   840  				}},
   841  				ModuleName: moduleName,
   842  				Engine:     importedME,
   843  			}
   844  			m := &ModuleInstance{s: s, Engine: &mockModuleEngine{resolveImportsCalled: map[Index]Index{}}}
   845  			err := m.resolveImports(&Module{
   846  				ImportPerModule: map[string][]*Import{
   847  					moduleName: {{Module: moduleName, Name: name, Type: ExternTypeMemory, DescMem: &Memory{Max: max}}},
   848  				},
   849  			})
   850  			require.NoError(t, err)
   851  			require.Equal(t, m.MemoryInstance, memoryInst)
   852  			require.Equal(t, importedME, m.Engine.(*mockModuleEngine).importedMemModEngine)
   853  		})
   854  		t.Run("minimum size mismatch", func(t *testing.T) {
   855  			importMemoryType := &Memory{Min: 2, Cap: 2}
   856  			s := newStore()
   857  			s.nameToModule[moduleName] = &ModuleInstance{
   858  				MemoryInstance: &MemoryInstance{Min: importMemoryType.Min - 1, Cap: 2},
   859  				Exports: map[string]*Export{name: {
   860  					Type: ExternTypeMemory,
   861  				}},
   862  				ModuleName: moduleName,
   863  			}
   864  			m := &ModuleInstance{s: s}
   865  			err := m.resolveImports(&Module{
   866  				ImportPerModule: map[string][]*Import{
   867  					moduleName: {{Module: moduleName, Name: name, Type: ExternTypeMemory, DescMem: importMemoryType}},
   868  				},
   869  			})
   870  			require.EqualError(t, err, "import memory[test.target]: minimum size mismatch: 2 > 1")
   871  		})
   872  		t.Run("maximum size mismatch", func(t *testing.T) {
   873  			s := newStore()
   874  			s.nameToModule[moduleName] = &ModuleInstance{
   875  				MemoryInstance: &MemoryInstance{Max: MemoryLimitPages},
   876  				Exports: map[string]*Export{name: {
   877  					Type: ExternTypeMemory,
   878  				}},
   879  				ModuleName: moduleName,
   880  			}
   881  
   882  			max := uint32(10)
   883  			importMemoryType := &Memory{Max: max}
   884  			m := &ModuleInstance{s: s}
   885  			err := m.resolveImports(&Module{
   886  				ImportPerModule: map[string][]*Import{moduleName: {{Module: moduleName, Name: name, Type: ExternTypeMemory, DescMem: importMemoryType}}},
   887  			})
   888  			require.EqualError(t, err, "import memory[test.target]: maximum size mismatch: 10 < 65536")
   889  		})
   890  	})
   891  }
   892  
   893  func TestModuleInstance_validateData(t *testing.T) {
   894  	m := &ModuleInstance{MemoryInstance: &MemoryInstance{Buffer: make([]byte, 5)}}
   895  	tests := []struct {
   896  		name   string
   897  		data   []DataSegment
   898  		expErr string
   899  	}{
   900  		{
   901  			name: "ok",
   902  			data: []DataSegment{
   903  				{OffsetExpression: ConstantExpression{Opcode: OpcodeI32Const, Data: const1}, Init: []byte{0}},
   904  				{OffsetExpression: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(2)}, Init: []byte{0}},
   905  			},
   906  		},
   907  		{
   908  			name: "out of bounds - single one byte",
   909  			data: []DataSegment{
   910  				{OffsetExpression: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(5)}, Init: []byte{0}},
   911  			},
   912  			expErr: "data[0]: out of bounds memory access",
   913  		},
   914  		{
   915  			name: "out of bounds - multi bytes",
   916  			data: []DataSegment{
   917  				{OffsetExpression: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(0)}, Init: []byte{0}},
   918  				{OffsetExpression: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(3)}, Init: []byte{0, 1, 2}},
   919  			},
   920  			expErr: "data[1]: out of bounds memory access",
   921  		},
   922  	}
   923  
   924  	for _, tt := range tests {
   925  		tc := tt
   926  		t.Run(tc.name, func(t *testing.T) {
   927  			err := m.validateData(tc.data)
   928  			if tc.expErr != "" {
   929  				require.EqualError(t, err, tc.expErr)
   930  			} else {
   931  				require.NoError(t, err)
   932  			}
   933  		})
   934  	}
   935  }
   936  
   937  func TestModuleInstance_applyData(t *testing.T) {
   938  	t.Run("ok", func(t *testing.T) {
   939  		m := &ModuleInstance{MemoryInstance: &MemoryInstance{Buffer: make([]byte, 10)}}
   940  		err := m.applyData([]DataSegment{
   941  			{OffsetExpression: ConstantExpression{Opcode: OpcodeI32Const, Data: const0}, Init: []byte{0xa, 0xf}},
   942  			{OffsetExpression: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeUint32(8)}, Init: []byte{0x1, 0x5}},
   943  		})
   944  		require.NoError(t, err)
   945  		require.Equal(t, []byte{0xa, 0xf, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x5}, m.MemoryInstance.Buffer)
   946  		require.Equal(t, [][]byte{{0xa, 0xf}, {0x1, 0x5}}, m.DataInstances)
   947  	})
   948  	t.Run("error", func(t *testing.T) {
   949  		m := &ModuleInstance{MemoryInstance: &MemoryInstance{Buffer: make([]byte, 5)}}
   950  		err := m.applyData([]DataSegment{
   951  			{OffsetExpression: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeUint32(8)}, Init: []byte{}},
   952  		})
   953  		require.EqualError(t, err, "data[0]: out of bounds memory access")
   954  	})
   955  }
   956  
   957  func globalsContain(globals []*GlobalInstance, want *GlobalInstance) bool {
   958  	for _, f := range globals {
   959  		if f == want {
   960  			return true
   961  		}
   962  	}
   963  	return false
   964  }
   965  
   966  func TestModuleInstance_applyElements(t *testing.T) {
   967  	leb128_100 := leb128.EncodeInt32(100)
   968  
   969  	t.Run("extenref", func(t *testing.T) {
   970  		m := &ModuleInstance{}
   971  		m.Tables = []*TableInstance{{Type: RefTypeExternref, References: make([]Reference, 10)}}
   972  		for i := range m.Tables[0].References {
   973  			m.Tables[0].References[i] = 0xffff // non-null ref.
   974  		}
   975  
   976  		// This shouldn't panic.
   977  		m.applyElements([]ElementSegment{{Mode: ElementModeActive, OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128_100}}})
   978  		m.applyElements([]ElementSegment{
   979  			{Mode: ElementModeActive, OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0}}, Init: make([]Index, 3)},
   980  			{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.
   981  			{Mode: ElementModeActive, OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{5}}, Init: make([]Index, 5)},
   982  		})
   983  		require.Equal(t, []Reference{0, 0, 0, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff},
   984  			m.Tables[0].References)
   985  		m.applyElements([]ElementSegment{
   986  			{Mode: ElementModeActive, OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{5}}, Init: make([]Index, 5)},
   987  		})
   988  		require.Equal(t, []Reference{0, 0, 0, 0xffff, 0xffff, 0, 0, 0, 0, 0}, m.Tables[0].References)
   989  	})
   990  	t.Run("funcref", func(t *testing.T) {
   991  		e := &mockEngine{}
   992  		me, err := e.NewModuleEngine(nil, nil)
   993  		me.(*mockModuleEngine).functionRefs = map[Index]Reference{0: 0xa, 1: 0xaa, 2: 0xaaa, 3: 0xaaaa}
   994  		require.NoError(t, err)
   995  		m := &ModuleInstance{Engine: me, Globals: []*GlobalInstance{{}, {Val: 0xabcde}}}
   996  
   997  		m.Tables = []*TableInstance{{Type: RefTypeFuncref, References: make([]Reference, 10)}}
   998  		for i := range m.Tables[0].References {
   999  			m.Tables[0].References[i] = 0xffff // non-null ref.
  1000  		}
  1001  
  1002  		// This shouldn't panic.
  1003  		m.applyElements([]ElementSegment{{Mode: ElementModeActive, OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128_100}, Init: []Index{1, 2, 3}}})
  1004  		m.applyElements([]ElementSegment{
  1005  			{Mode: ElementModeActive, OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0}}, Init: []Index{0, 1, 2}},
  1006  			{Mode: ElementModeActive, OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{9}}, Init: []Index{1 | elementInitImportedGlobalReferenceType}},
  1007  			{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.
  1008  			{Mode: ElementModeActive, OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{5}}, Init: make([]Index, 5)},
  1009  		})
  1010  		require.Equal(t, []Reference{0xa, 0xaa, 0xaaa, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xabcde},
  1011  			m.Tables[0].References)
  1012  		m.applyElements([]ElementSegment{
  1013  			{Mode: ElementModeActive, OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{5}}, Init: []Index{0, ElementInitNullReference, 2}},
  1014  		})
  1015  		require.Equal(t, []Reference{0xa, 0xaa, 0xaaa, 0xffff, 0xffff, 0xa, 0xffff, 0xaaa, 0xffff, 0xabcde},
  1016  			m.Tables[0].References)
  1017  	})
  1018  }