wa-lang.org/wazero@v1.0.2/internal/engine/compiler/compiler_post1_0_test.go (about)

     1  package compiler
     2  
     3  import (
     4  	"fmt"
     5  	"strconv"
     6  	"testing"
     7  	"unsafe"
     8  
     9  	"wa-lang.org/wazero/internal/testing/require"
    10  	"wa-lang.org/wazero/internal/wasm"
    11  	"wa-lang.org/wazero/internal/wazeroir"
    12  )
    13  
    14  func TestCompiler_compileSignExtend(t *testing.T) {
    15  	type fromKind byte
    16  	from8, from16, from32 := fromKind(0), fromKind(1), fromKind(2)
    17  
    18  	t.Run("32bit", func(t *testing.T) {
    19  		tests := []struct {
    20  			in       int32
    21  			expected int32
    22  			fromKind fromKind
    23  		}{
    24  			// https://github.com/WebAssembly/spec/blob/ee4a6c40afa22e3e4c58610ce75186aafc22344e/test/core/i32.wast#L270-L276
    25  			{in: 0, expected: 0, fromKind: from8},
    26  			{in: 0x7f, expected: 127, fromKind: from8},
    27  			{in: 0x80, expected: -128, fromKind: from8},
    28  			{in: 0xff, expected: -1, fromKind: from8},
    29  			{in: 0x012345_00, expected: 0, fromKind: from8},
    30  			{in: -19088768 /* = 0xfedcba_80 bit pattern */, expected: -0x80, fromKind: from8},
    31  			{in: -1, expected: -1, fromKind: from8},
    32  
    33  			// https://github.com/WebAssembly/spec/blob/ee4a6c40afa22e3e4c58610ce75186aafc22344e/test/core/i32.wast#L278-L284
    34  			{in: 0, expected: 0, fromKind: from16},
    35  			{in: 0x7fff, expected: 32767, fromKind: from16},
    36  			{in: 0x8000, expected: -32768, fromKind: from16},
    37  			{in: 0xffff, expected: -1, fromKind: from16},
    38  			{in: 0x0123_0000, expected: 0, fromKind: from16},
    39  			{in: -19103744 /* = 0xfedc_8000 bit pattern */, expected: -0x8000, fromKind: from16},
    40  			{in: -1, expected: -1, fromKind: from16},
    41  		}
    42  
    43  		for _, tt := range tests {
    44  			tc := tt
    45  			t.Run(fmt.Sprintf("0x%x", tc.in), func(t *testing.T) {
    46  				env := newCompilerEnvironment()
    47  				compiler := env.requireNewCompiler(t, newCompiler, nil)
    48  				err := compiler.compilePreamble()
    49  				require.NoError(t, err)
    50  
    51  				// Setup the promote target.
    52  				err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: uint32(tc.in)})
    53  				require.NoError(t, err)
    54  
    55  				if tc.fromKind == from8 {
    56  					err = compiler.compileSignExtend32From8()
    57  				} else {
    58  					err = compiler.compileSignExtend32From16()
    59  				}
    60  				require.NoError(t, err)
    61  
    62  				// To verify the behavior, we release the value
    63  				// to the stack.
    64  				err = compiler.compileReturnFunction()
    65  				require.NoError(t, err)
    66  
    67  				// Generate and run the code under test.
    68  				code, _, err := compiler.compile()
    69  				require.NoError(t, err)
    70  				env.exec(code)
    71  
    72  				require.Equal(t, uint64(1), env.stackPointer())
    73  				require.Equal(t, tc.expected, env.stackTopAsInt32())
    74  			})
    75  		}
    76  	})
    77  	t.Run("64bit", func(t *testing.T) {
    78  		tests := []struct {
    79  			in       int64
    80  			expected int64
    81  			fromKind fromKind
    82  		}{
    83  			// https://github.com/WebAssembly/spec/blob/ee4a6c40afa22e3e4c58610ce75186aafc22344e/test/core/i64.wast#L271-L277
    84  			{in: 0, expected: 0, fromKind: from8},
    85  			{in: 0x7f, expected: 127, fromKind: from8},
    86  			{in: 0x80, expected: -128, fromKind: from8},
    87  			{in: 0xff, expected: -1, fromKind: from8},
    88  			{in: 0x01234567_89abcd_00, expected: 0, fromKind: from8},
    89  			{in: 81985529216486784 /* = 0xfedcba98_765432_80 bit pattern */, expected: -0x80, fromKind: from8},
    90  			{in: -1, expected: -1, fromKind: from8},
    91  
    92  			// https://github.com/WebAssembly/spec/blob/ee4a6c40afa22e3e4c58610ce75186aafc22344e/test/core/i64.wast#L279-L285
    93  			{in: 0, expected: 0, fromKind: from16},
    94  			{in: 0x7fff, expected: 32767, fromKind: from16},
    95  			{in: 0x8000, expected: -32768, fromKind: from16},
    96  			{in: 0xffff, expected: -1, fromKind: from16},
    97  			{in: 0x12345678_9abc_0000, expected: 0, fromKind: from16},
    98  			{in: 81985529216466944 /* = 0xfedcba98_7654_8000 bit pattern */, expected: -0x8000, fromKind: from16},
    99  			{in: -1, expected: -1, fromKind: from16},
   100  
   101  			// https://github.com/WebAssembly/spec/blob/ee4a6c40afa22e3e4c58610ce75186aafc22344e/test/core/i64.wast#L287-L296
   102  			{in: 0, expected: 0, fromKind: from32},
   103  			{in: 0x7fff, expected: 32767, fromKind: from32},
   104  			{in: 0x8000, expected: 32768, fromKind: from32},
   105  			{in: 0xffff, expected: 65535, fromKind: from32},
   106  			{in: 0x7fffffff, expected: 0x7fffffff, fromKind: from32},
   107  			{in: 0x80000000, expected: -0x80000000, fromKind: from32},
   108  			{in: 0xffffffff, expected: -1, fromKind: from32},
   109  			{in: 0x01234567_00000000, expected: 0, fromKind: from32},
   110  			{in: -81985529054232576 /* = 0xfedcba98_80000000 bit pattern */, expected: -0x80000000, fromKind: from32},
   111  			{in: -1, expected: -1, fromKind: from32},
   112  		}
   113  
   114  		for _, tt := range tests {
   115  			tc := tt
   116  			t.Run(fmt.Sprintf("0x%x", tc.in), func(t *testing.T) {
   117  				env := newCompilerEnvironment()
   118  				compiler := env.requireNewCompiler(t, newCompiler, nil)
   119  				err := compiler.compilePreamble()
   120  				require.NoError(t, err)
   121  
   122  				// Setup the promote target.
   123  				err = compiler.compileConstI64(&wazeroir.OperationConstI64{Value: uint64(tc.in)})
   124  				require.NoError(t, err)
   125  
   126  				if tc.fromKind == from8 {
   127  					err = compiler.compileSignExtend64From8()
   128  				} else if tc.fromKind == from16 {
   129  					err = compiler.compileSignExtend64From16()
   130  				} else {
   131  					err = compiler.compileSignExtend64From32()
   132  				}
   133  				require.NoError(t, err)
   134  
   135  				// To verify the behavior, we release the value
   136  				// to the stack.
   137  				err = compiler.compileReturnFunction()
   138  				require.NoError(t, err)
   139  
   140  				// Generate and run the code under test.
   141  				code, _, err := compiler.compile()
   142  				require.NoError(t, err)
   143  				env.exec(code)
   144  
   145  				require.Equal(t, uint64(1), env.stackPointer())
   146  				require.Equal(t, tc.expected, env.stackTopAsInt64())
   147  			})
   148  		}
   149  	})
   150  }
   151  
   152  func TestCompiler_compileMemoryCopy(t *testing.T) {
   153  	const checkCeil = 100
   154  	tests := []struct {
   155  		sourceOffset, destOffset, size uint32
   156  		requireOutOfBoundsError        bool
   157  	}{
   158  		{sourceOffset: 0, destOffset: 0, size: 0},
   159  		{sourceOffset: 10, destOffset: 5, size: 10},
   160  		{sourceOffset: 10, destOffset: 9, size: 1},
   161  		{sourceOffset: 10, destOffset: 9, size: 2},
   162  		{sourceOffset: 0, destOffset: 10, size: 10},
   163  		{sourceOffset: 0, destOffset: 5, size: 10},
   164  		{sourceOffset: 9, destOffset: 10, size: 10},
   165  		{sourceOffset: 11, destOffset: 13, size: 4},
   166  		{sourceOffset: 0, destOffset: 10, size: 5},
   167  		{sourceOffset: 1, destOffset: 10, size: 5},
   168  		{sourceOffset: 0, destOffset: 10, size: 1},
   169  		{sourceOffset: 0, destOffset: 10, size: 0},
   170  		{sourceOffset: 5, destOffset: 10, size: 10},
   171  		{sourceOffset: 5, destOffset: 10, size: 5},
   172  		{sourceOffset: 5, destOffset: 10, size: 1},
   173  		{sourceOffset: 5, destOffset: 10, size: 0},
   174  		{sourceOffset: 10, destOffset: 0, size: 10},
   175  		{sourceOffset: 1, destOffset: 0, size: 2},
   176  		{sourceOffset: 1, destOffset: 0, size: 20},
   177  		{sourceOffset: 10, destOffset: 0, size: 5},
   178  		{sourceOffset: 10, destOffset: 0, size: 1},
   179  		{sourceOffset: 10, destOffset: 0, size: 0},
   180  		{sourceOffset: 0, destOffset: 50, size: 48},
   181  		{sourceOffset: 0, destOffset: 50, size: 49},
   182  		{sourceOffset: 10, destOffset: 20, size: 72},
   183  		{sourceOffset: 20, destOffset: 10, size: 72},
   184  		{sourceOffset: 19, destOffset: 18, size: 79},
   185  		{sourceOffset: 20, destOffset: 19, size: 79},
   186  		{sourceOffset: defaultMemoryPageNumInTest * wasm.MemoryPageSize, destOffset: 0, size: 1, requireOutOfBoundsError: true},
   187  		{sourceOffset: defaultMemoryPageNumInTest*wasm.MemoryPageSize + 1, destOffset: 0, size: 0, requireOutOfBoundsError: true},
   188  		{sourceOffset: 0, destOffset: defaultMemoryPageNumInTest * wasm.MemoryPageSize, size: 1, requireOutOfBoundsError: true},
   189  		{sourceOffset: 0, destOffset: defaultMemoryPageNumInTest*wasm.MemoryPageSize + 1, size: 0, requireOutOfBoundsError: true},
   190  		{sourceOffset: defaultMemoryPageNumInTest*wasm.MemoryPageSize - 99, destOffset: 0, size: 100, requireOutOfBoundsError: true},
   191  		{sourceOffset: 0, destOffset: defaultMemoryPageNumInTest*wasm.MemoryPageSize - 99, size: 100, requireOutOfBoundsError: true},
   192  	}
   193  
   194  	for i, tt := range tests {
   195  		tc := tt
   196  		t.Run(strconv.Itoa(i), func(t *testing.T) {
   197  			env := newCompilerEnvironment()
   198  			compiler := env.requireNewCompiler(t, newCompiler, &wazeroir.CompilationResult{HasMemory: true, Signature: &wasm.FunctionType{}})
   199  
   200  			err := compiler.compilePreamble()
   201  			require.NoError(t, err)
   202  
   203  			// Compile operands.
   204  			err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.destOffset})
   205  			require.NoError(t, err)
   206  			err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.sourceOffset})
   207  			require.NoError(t, err)
   208  			err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.size})
   209  			require.NoError(t, err)
   210  
   211  			err = compiler.compileMemoryCopy()
   212  			require.NoError(t, err)
   213  
   214  			// Generate the code under test.
   215  			err = compiler.compileReturnFunction()
   216  			require.NoError(t, err)
   217  			code, _, err := compiler.compile()
   218  			require.NoError(t, err)
   219  
   220  			// Setup the source memory region.
   221  			mem := env.memory()
   222  			for i := 0; i < checkCeil; i++ {
   223  				mem[i] = byte(i)
   224  			}
   225  
   226  			// Run code.
   227  			env.exec(code)
   228  
   229  			if !tc.requireOutOfBoundsError {
   230  				exp := make([]byte, checkCeil)
   231  				for i := 0; i < checkCeil; i++ {
   232  					exp[i] = byte(i)
   233  				}
   234  				copy(exp[tc.destOffset:],
   235  					exp[tc.sourceOffset:tc.sourceOffset+tc.size])
   236  
   237  				// Check the status code and the destination memory region.
   238  				require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
   239  				require.Equal(t, exp, mem[:checkCeil])
   240  			} else {
   241  				require.Equal(t, nativeCallStatusCodeMemoryOutOfBounds, env.compilerStatus())
   242  			}
   243  		})
   244  	}
   245  }
   246  
   247  func TestCompiler_compileMemoryFill(t *testing.T) {
   248  	const checkCeil = 50
   249  
   250  	tests := []struct {
   251  		v, destOffset           uint32
   252  		size                    uint32
   253  		requireOutOfBoundsError bool
   254  	}{
   255  		{v: 0, destOffset: 0, size: 25},
   256  		{v: 0, destOffset: 10, size: 17},
   257  		{v: 0, destOffset: 10, size: 15},
   258  		{v: 0, destOffset: 10, size: 5},
   259  		{v: 0, destOffset: 10, size: 1},
   260  		{v: 0, destOffset: 10, size: 0},
   261  		{v: 5, destOffset: 10, size: 27},
   262  		{v: 5, destOffset: 10, size: 25},
   263  		{v: 5, destOffset: 10, size: 21},
   264  		{v: 5, destOffset: 10, size: 10},
   265  		{v: 5, destOffset: 10, size: 5},
   266  		{v: 5, destOffset: 10, size: 1},
   267  		{v: 5, destOffset: 10, size: 0},
   268  		{v: 10, destOffset: 0, size: 10},
   269  		{v: 10, destOffset: 0, size: 5},
   270  		{v: 10, destOffset: 0, size: 1},
   271  		{v: 10, destOffset: 0, size: 0},
   272  		{v: 10, destOffset: defaultMemoryPageNumInTest*wasm.MemoryPageSize - 99, size: 100, requireOutOfBoundsError: true},
   273  		{v: 10, destOffset: defaultMemoryPageNumInTest * wasm.MemoryPageSize, size: 5, requireOutOfBoundsError: true},
   274  		{v: 10, destOffset: defaultMemoryPageNumInTest * wasm.MemoryPageSize, size: 1, requireOutOfBoundsError: true},
   275  		{v: 10, destOffset: defaultMemoryPageNumInTest*wasm.MemoryPageSize + 1, size: 0, requireOutOfBoundsError: true},
   276  	}
   277  
   278  	for i, tt := range tests {
   279  		tc := tt
   280  		t.Run(strconv.Itoa(i), func(t *testing.T) {
   281  			env := newCompilerEnvironment()
   282  			compiler := env.requireNewCompiler(t, newCompiler, &wazeroir.CompilationResult{HasMemory: true, Signature: &wasm.FunctionType{}})
   283  
   284  			err := compiler.compilePreamble()
   285  			require.NoError(t, err)
   286  
   287  			// Compile operands.
   288  			err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.destOffset})
   289  			require.NoError(t, err)
   290  			err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.v})
   291  			require.NoError(t, err)
   292  			err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.size})
   293  			require.NoError(t, err)
   294  
   295  			err = compiler.compileMemoryFill()
   296  			require.NoError(t, err)
   297  
   298  			// Generate the code under test.
   299  			err = compiler.compileReturnFunction()
   300  			require.NoError(t, err)
   301  			code, _, err := compiler.compile()
   302  			require.NoError(t, err)
   303  
   304  			// Setup the memory region.
   305  			mem := env.memory()
   306  			for i := 0; i < checkCeil; i++ {
   307  				mem[i] = byte(i)
   308  			}
   309  
   310  			// Run code.
   311  			env.exec(code)
   312  
   313  			if !tc.requireOutOfBoundsError {
   314  				exp := make([]byte, checkCeil)
   315  				for i := 0; i < checkCeil; i++ {
   316  					if i >= int(tc.destOffset) && i < int(tc.destOffset+tc.size) {
   317  						exp[i] = byte(tc.v)
   318  					} else {
   319  						exp[i] = byte(i)
   320  					}
   321  				}
   322  
   323  				// Check the status code and the destination memory region.
   324  				require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
   325  				require.Equal(t, exp, mem[:checkCeil])
   326  			} else {
   327  				require.Equal(t, nativeCallStatusCodeMemoryOutOfBounds, env.compilerStatus())
   328  			}
   329  		})
   330  	}
   331  }
   332  
   333  func TestCompiler_compileDataDrop(t *testing.T) {
   334  	origins := [][]byte{
   335  		{1}, {2}, {3}, {4}, {5}, {6}, {7}, {8}, {9}, {10},
   336  	}
   337  
   338  	for i := 0; i < len(origins); i++ {
   339  		t.Run(strconv.Itoa(i), func(t *testing.T) {
   340  			env := newCompilerEnvironment()
   341  
   342  			env.module().DataInstances = make([][]byte, len(origins))
   343  			copy(env.module().DataInstances, origins)
   344  
   345  			compiler := env.requireNewCompiler(t, newCompiler, &wazeroir.CompilationResult{
   346  				HasDataInstances: true, Signature: &wasm.FunctionType{},
   347  			})
   348  
   349  			err := compiler.compilePreamble()
   350  			require.NoError(t, err)
   351  
   352  			err = compiler.compileDataDrop(&wazeroir.OperationDataDrop{
   353  				DataIndex: uint32(i),
   354  			})
   355  			require.NoError(t, err)
   356  
   357  			// Generate the code under test.
   358  			err = compiler.compileReturnFunction()
   359  			require.NoError(t, err)
   360  			code, _, err := compiler.compile()
   361  			require.NoError(t, err)
   362  
   363  			// Run code.
   364  			env.exec(code)
   365  
   366  			require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
   367  
   368  			// Check if the target data instance is dropped from the dataInstances slice.
   369  			for j := 0; j < len(origins); j++ {
   370  				if i == j {
   371  					require.Nil(t, env.module().DataInstances[j])
   372  				} else {
   373  					require.NotNil(t, env.module().DataInstances[j])
   374  				}
   375  			}
   376  		})
   377  	}
   378  }
   379  
   380  func TestCompiler_compileMemoryInit(t *testing.T) {
   381  	dataInstances := []wasm.DataInstance{
   382  		nil, {1, 2, 3, 4, 5},
   383  	}
   384  
   385  	tests := []struct {
   386  		sourceOffset, destOffset uint32
   387  		dataIndex                uint32
   388  		copySize                 uint32
   389  		expOutOfBounds           bool
   390  	}{
   391  		{sourceOffset: 0, destOffset: 0, copySize: 0, dataIndex: 0},
   392  		{sourceOffset: 0, destOffset: 0, copySize: 1, dataIndex: 0, expOutOfBounds: true},
   393  		{sourceOffset: 1, destOffset: 0, copySize: 0, dataIndex: 0, expOutOfBounds: true},
   394  		{sourceOffset: 0, destOffset: 0, copySize: 0, dataIndex: 1},
   395  		{sourceOffset: 0, destOffset: 0, copySize: 5, dataIndex: 1},
   396  		{sourceOffset: 0, destOffset: 0, copySize: 1, dataIndex: 1},
   397  		{sourceOffset: 0, destOffset: 0, copySize: 3, dataIndex: 1},
   398  		{sourceOffset: 0, destOffset: 1, copySize: 3, dataIndex: 1},
   399  		{sourceOffset: 0, destOffset: 7, copySize: 4, dataIndex: 1},
   400  		{sourceOffset: 1, destOffset: 7, copySize: 4, dataIndex: 1},
   401  		{sourceOffset: 4, destOffset: 7, copySize: 1, dataIndex: 1},
   402  		{sourceOffset: 5, destOffset: 7, copySize: 0, dataIndex: 1},
   403  		{sourceOffset: 0, destOffset: 7, copySize: 5, dataIndex: 1},
   404  		{sourceOffset: 1, destOffset: 0, copySize: 3, dataIndex: 1},
   405  		{sourceOffset: 0, destOffset: 1, copySize: 4, dataIndex: 1},
   406  		{sourceOffset: 1, destOffset: 1, copySize: 3, dataIndex: 1},
   407  		{sourceOffset: 0, destOffset: 10, copySize: 5, dataIndex: 1},
   408  		{sourceOffset: 0, destOffset: 0, copySize: 6, dataIndex: 1, expOutOfBounds: true},
   409  		{sourceOffset: 0, destOffset: defaultMemoryPageNumInTest * wasm.MemoryPageSize, copySize: 5, dataIndex: 1, expOutOfBounds: true},
   410  		{sourceOffset: 0, destOffset: defaultMemoryPageNumInTest*wasm.MemoryPageSize - 3, copySize: 5, dataIndex: 1, expOutOfBounds: true},
   411  		{sourceOffset: 6, destOffset: 0, copySize: 0, dataIndex: 1, expOutOfBounds: true},
   412  	}
   413  
   414  	for i, tt := range tests {
   415  		tc := tt
   416  		t.Run(strconv.Itoa(i), func(t *testing.T) {
   417  			env := newCompilerEnvironment()
   418  			env.module().DataInstances = dataInstances
   419  
   420  			compiler := env.requireNewCompiler(t, newCompiler, &wazeroir.CompilationResult{
   421  				HasDataInstances: true, HasMemory: true,
   422  				Signature: &wasm.FunctionType{},
   423  			})
   424  
   425  			err := compiler.compilePreamble()
   426  			require.NoError(t, err)
   427  
   428  			// Compile operands.
   429  			err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.destOffset})
   430  			require.NoError(t, err)
   431  			err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.sourceOffset})
   432  			require.NoError(t, err)
   433  			err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.copySize})
   434  			require.NoError(t, err)
   435  
   436  			err = compiler.compileMemoryInit(&wazeroir.OperationMemoryInit{
   437  				DataIndex: tc.dataIndex,
   438  			})
   439  			require.NoError(t, err)
   440  
   441  			// Generate the code under test.
   442  			err = compiler.compileReturnFunction()
   443  			require.NoError(t, err)
   444  			code, _, err := compiler.compile()
   445  			require.NoError(t, err)
   446  
   447  			// Run code.
   448  			env.exec(code)
   449  
   450  			if !tc.expOutOfBounds {
   451  				mem := env.memory()
   452  				exp := make([]byte, defaultMemoryPageNumInTest*wasm.MemoryPageSize)
   453  				if dataInst := dataInstances[tc.dataIndex]; dataInst != nil {
   454  					copy(exp[tc.destOffset:], dataInst[tc.sourceOffset:tc.sourceOffset+tc.copySize])
   455  				}
   456  				require.Equal(t, exp[:20], mem[:20])
   457  			} else {
   458  				require.Equal(t, nativeCallStatusCodeMemoryOutOfBounds, env.compilerStatus())
   459  			}
   460  		})
   461  	}
   462  }
   463  
   464  func TestCompiler_compileElemDrop(t *testing.T) {
   465  	origins := []wasm.ElementInstance{
   466  		{References: []wasm.Reference{1}},
   467  		{References: []wasm.Reference{2}},
   468  		{References: []wasm.Reference{3}},
   469  		{References: []wasm.Reference{4}},
   470  		{References: []wasm.Reference{5}},
   471  	}
   472  
   473  	for i := 0; i < len(origins); i++ {
   474  		t.Run(strconv.Itoa(i), func(t *testing.T) {
   475  			env := newCompilerEnvironment()
   476  
   477  			insts := make([]wasm.ElementInstance, len(origins))
   478  			copy(insts, origins)
   479  			env.module().ElementInstances = insts
   480  
   481  			// Verify assumption that before Drop instruction, all the element instances are not empty.
   482  			for _, inst := range insts {
   483  				require.NotEqual(t, 0, len(inst.References))
   484  			}
   485  
   486  			compiler := env.requireNewCompiler(t, newCompiler, &wazeroir.CompilationResult{
   487  				HasElementInstances: true, Signature: &wasm.FunctionType{},
   488  			})
   489  
   490  			err := compiler.compilePreamble()
   491  			require.NoError(t, err)
   492  
   493  			err = compiler.compileElemDrop(&wazeroir.OperationElemDrop{
   494  				ElemIndex: uint32(i),
   495  			})
   496  			require.NoError(t, err)
   497  
   498  			// Generate the code under test.
   499  			err = compiler.compileReturnFunction()
   500  			require.NoError(t, err)
   501  			code, _, err := compiler.compile()
   502  			require.NoError(t, err)
   503  
   504  			// Run code.
   505  			env.exec(code)
   506  
   507  			require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
   508  
   509  			for j := 0; j < len(insts); j++ {
   510  				if i == j {
   511  					require.Zero(t, len(env.module().ElementInstances[j].References))
   512  				} else {
   513  					require.NotEqual(t, 0, len(env.module().ElementInstances[j].References))
   514  				}
   515  			}
   516  		})
   517  	}
   518  }
   519  
   520  func TestCompiler_compileTableCopy(t *testing.T) {
   521  	const tableSize = 100
   522  	tests := []struct {
   523  		sourceOffset, destOffset, size uint32
   524  		requireOutOfBoundsError        bool
   525  	}{
   526  		{sourceOffset: 0, destOffset: 0, size: 0},
   527  		{sourceOffset: 10, destOffset: 5, size: 10},
   528  		{sourceOffset: 10, destOffset: 9, size: 1},
   529  		{sourceOffset: 10, destOffset: 9, size: 2},
   530  		{sourceOffset: 0, destOffset: 10, size: 10},
   531  		{sourceOffset: 0, destOffset: 5, size: 10},
   532  		{sourceOffset: 9, destOffset: 10, size: 10},
   533  		{sourceOffset: 11, destOffset: 13, size: 4},
   534  		{sourceOffset: 0, destOffset: 10, size: 5},
   535  		{sourceOffset: 1, destOffset: 10, size: 5},
   536  		{sourceOffset: 0, destOffset: 10, size: 1},
   537  		{sourceOffset: 0, destOffset: 10, size: 0},
   538  		{sourceOffset: 5, destOffset: 10, size: 10},
   539  		{sourceOffset: 5, destOffset: 10, size: 5},
   540  		{sourceOffset: 5, destOffset: 10, size: 1},
   541  		{sourceOffset: 5, destOffset: 10, size: 0},
   542  		{sourceOffset: 10, destOffset: 0, size: 10},
   543  		{sourceOffset: 1, destOffset: 0, size: 2},
   544  		{sourceOffset: 1, destOffset: 0, size: 20},
   545  		{sourceOffset: 10, destOffset: 0, size: 5},
   546  		{sourceOffset: 10, destOffset: 0, size: 1},
   547  		{sourceOffset: 10, destOffset: 0, size: 0},
   548  		{sourceOffset: tableSize, destOffset: 0, size: 1, requireOutOfBoundsError: true},
   549  		{sourceOffset: tableSize + 1, destOffset: 0, size: 0, requireOutOfBoundsError: true},
   550  		{sourceOffset: 0, destOffset: tableSize, size: 1, requireOutOfBoundsError: true},
   551  		{sourceOffset: 0, destOffset: tableSize + 1, size: 0, requireOutOfBoundsError: true},
   552  		{sourceOffset: tableSize - 99, destOffset: 0, size: 100, requireOutOfBoundsError: true},
   553  		{sourceOffset: 0, destOffset: tableSize - 99, size: 100, requireOutOfBoundsError: true},
   554  	}
   555  
   556  	for i, tt := range tests {
   557  		tc := tt
   558  		t.Run(strconv.Itoa(i), func(t *testing.T) {
   559  			env := newCompilerEnvironment()
   560  			compiler := env.requireNewCompiler(t, newCompiler, &wazeroir.CompilationResult{HasTable: true, Signature: &wasm.FunctionType{}})
   561  
   562  			err := compiler.compilePreamble()
   563  			require.NoError(t, err)
   564  
   565  			// Compile operands.
   566  			err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.destOffset})
   567  			require.NoError(t, err)
   568  			err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.sourceOffset})
   569  			require.NoError(t, err)
   570  			err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.size})
   571  			require.NoError(t, err)
   572  
   573  			err = compiler.compileTableCopy(&wazeroir.OperationTableCopy{})
   574  			require.NoError(t, err)
   575  
   576  			// Generate the code under test.
   577  			err = compiler.compileReturnFunction()
   578  			require.NoError(t, err)
   579  			code, _, err := compiler.compile()
   580  			require.NoError(t, err)
   581  
   582  			// Setup the table.
   583  			table := make([]wasm.Reference, tableSize)
   584  			env.addTable(&wasm.TableInstance{References: table})
   585  			for i := 0; i < tableSize; i++ {
   586  				table[i] = uintptr(i)
   587  			}
   588  
   589  			// Run code.
   590  			env.exec(code)
   591  
   592  			if !tc.requireOutOfBoundsError {
   593  				exp := make([]wasm.Reference, tableSize)
   594  				for i := 0; i < tableSize; i++ {
   595  					exp[i] = uintptr(i)
   596  				}
   597  				copy(exp[tc.destOffset:],
   598  					exp[tc.sourceOffset:tc.sourceOffset+tc.size])
   599  
   600  				// Check the status code and the destination memory region.
   601  				require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
   602  				require.Equal(t, exp, table)
   603  			} else {
   604  				require.Equal(t, nativeCallStatusCodeInvalidTableAccess, env.compilerStatus())
   605  			}
   606  		})
   607  	}
   608  }
   609  
   610  func TestCompiler_compileTableInit(t *testing.T) {
   611  	elementInstances := []wasm.ElementInstance{
   612  		{}, {References: []wasm.Reference{1, 2, 3, 4, 5}},
   613  	}
   614  
   615  	const tableSize = 100
   616  	tests := []struct {
   617  		sourceOffset, destOffset uint32
   618  		elemIndex                uint32
   619  		copySize                 uint32
   620  		expOutOfBounds           bool
   621  	}{
   622  		{sourceOffset: 0, destOffset: 0, copySize: 0, elemIndex: 0},
   623  		{sourceOffset: 0, destOffset: 0, copySize: 1, elemIndex: 0, expOutOfBounds: true},
   624  		{sourceOffset: 1, destOffset: 0, copySize: 0, elemIndex: 0, expOutOfBounds: true},
   625  		{sourceOffset: 0, destOffset: 0, copySize: 0, elemIndex: 1},
   626  		{sourceOffset: 0, destOffset: 0, copySize: 5, elemIndex: 1},
   627  		{sourceOffset: 0, destOffset: 0, copySize: 1, elemIndex: 1},
   628  		{sourceOffset: 0, destOffset: 0, copySize: 3, elemIndex: 1},
   629  		{sourceOffset: 0, destOffset: 1, copySize: 3, elemIndex: 1},
   630  		{sourceOffset: 0, destOffset: 7, copySize: 4, elemIndex: 1},
   631  		{sourceOffset: 1, destOffset: 7, copySize: 4, elemIndex: 1},
   632  		{sourceOffset: 4, destOffset: 7, copySize: 1, elemIndex: 1},
   633  		{sourceOffset: 5, destOffset: 7, copySize: 0, elemIndex: 1},
   634  		{sourceOffset: 0, destOffset: 7, copySize: 5, elemIndex: 1},
   635  		{sourceOffset: 1, destOffset: 0, copySize: 3, elemIndex: 1},
   636  		{sourceOffset: 0, destOffset: 1, copySize: 4, elemIndex: 1},
   637  		{sourceOffset: 1, destOffset: 1, copySize: 3, elemIndex: 1},
   638  		{sourceOffset: 0, destOffset: 10, copySize: 5, elemIndex: 1},
   639  		{sourceOffset: 0, destOffset: 0, copySize: 6, elemIndex: 1, expOutOfBounds: true},
   640  		{sourceOffset: 6, destOffset: 0, copySize: 0, elemIndex: 1, expOutOfBounds: true},
   641  	}
   642  
   643  	for i, tt := range tests {
   644  		tc := tt
   645  		t.Run(strconv.Itoa(i), func(t *testing.T) {
   646  			env := newCompilerEnvironment()
   647  			env.module().ElementInstances = elementInstances
   648  
   649  			compiler := env.requireNewCompiler(t, newCompiler, &wazeroir.CompilationResult{
   650  				HasElementInstances: true, HasTable: true,
   651  				Signature: &wasm.FunctionType{},
   652  			})
   653  
   654  			err := compiler.compilePreamble()
   655  			require.NoError(t, err)
   656  
   657  			// Compile operands.
   658  			err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.destOffset})
   659  			require.NoError(t, err)
   660  			err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.sourceOffset})
   661  			require.NoError(t, err)
   662  			err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.copySize})
   663  			require.NoError(t, err)
   664  
   665  			err = compiler.compileTableInit(&wazeroir.OperationTableInit{
   666  				ElemIndex: tc.elemIndex,
   667  			})
   668  			require.NoError(t, err)
   669  
   670  			// Setup the table.
   671  			table := make([]wasm.Reference, tableSize)
   672  			env.addTable(&wasm.TableInstance{References: table})
   673  			for i := 0; i < tableSize; i++ {
   674  				table[i] = uintptr(i)
   675  			}
   676  
   677  			// Generate the code under test.
   678  			err = compiler.compileReturnFunction()
   679  			require.NoError(t, err)
   680  			code, _, err := compiler.compile()
   681  			require.NoError(t, err)
   682  
   683  			// Run code.
   684  			env.exec(code)
   685  
   686  			if !tc.expOutOfBounds {
   687  				require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
   688  				exp := make([]wasm.Reference, tableSize)
   689  				for i := 0; i < tableSize; i++ {
   690  					exp[i] = uintptr(i)
   691  				}
   692  				if inst := elementInstances[tc.elemIndex]; inst.References != nil {
   693  					copy(exp[tc.destOffset:], inst.References[tc.sourceOffset:tc.sourceOffset+tc.copySize])
   694  				}
   695  				require.Equal(t, exp, table)
   696  			} else {
   697  				require.Equal(t, nativeCallStatusCodeInvalidTableAccess, env.compilerStatus())
   698  			}
   699  		})
   700  	}
   701  }
   702  
   703  type dog struct{ name string }
   704  
   705  func TestCompiler_compileTableSet(t *testing.T) {
   706  	externDog := &dog{name: "sushi"}
   707  	externrefOpaque := uintptr(unsafe.Pointer(externDog))
   708  	funcref := &function{source: &wasm.FunctionInstance{}}
   709  	funcrefOpaque := uintptr(unsafe.Pointer(funcref))
   710  
   711  	externTable := &wasm.TableInstance{Type: wasm.RefTypeExternref, References: []wasm.Reference{0, 0, externrefOpaque, 0, 0}}
   712  	funcrefTable := &wasm.TableInstance{Type: wasm.RefTypeFuncref, References: []wasm.Reference{0, 0, 0, 0, funcrefOpaque}}
   713  	tables := []*wasm.TableInstance{externTable, funcrefTable}
   714  
   715  	tests := []struct {
   716  		name       string
   717  		tableIndex uint32
   718  		offset     uint32
   719  		in         uintptr
   720  		expExtern  bool
   721  		expError   bool
   722  	}{
   723  		{
   724  			name:       "externref - non nil",
   725  			tableIndex: 0,
   726  			offset:     2,
   727  			in:         externrefOpaque,
   728  			expExtern:  true,
   729  		},
   730  		{
   731  			name:       "externref - nil",
   732  			tableIndex: 0,
   733  			offset:     1,
   734  			in:         0,
   735  			expExtern:  true,
   736  		},
   737  		{
   738  			name:       "externref - out of bounds",
   739  			tableIndex: 0,
   740  			offset:     10,
   741  			in:         0,
   742  			expError:   true,
   743  		},
   744  		{
   745  			name:       "funcref - non nil",
   746  			tableIndex: 1,
   747  			offset:     4,
   748  			in:         funcrefOpaque,
   749  			expExtern:  false,
   750  		},
   751  		{
   752  			name:       "funcref - nil",
   753  			tableIndex: 1,
   754  			offset:     3,
   755  			in:         0,
   756  			expExtern:  false,
   757  		},
   758  		{
   759  			name:       "funcref - out of bounds",
   760  			tableIndex: 1,
   761  			offset:     100000,
   762  			in:         0,
   763  			expError:   true,
   764  		},
   765  	}
   766  
   767  	for _, tt := range tests {
   768  		tc := tt
   769  		t.Run(tc.name, func(t *testing.T) {
   770  			env := newCompilerEnvironment()
   771  
   772  			for _, table := range tables {
   773  				env.addTable(table)
   774  			}
   775  
   776  			compiler := env.requireNewCompiler(t, newCompiler, &wazeroir.CompilationResult{
   777  				HasTable:  true,
   778  				Signature: &wasm.FunctionType{},
   779  			})
   780  
   781  			err := compiler.compilePreamble()
   782  			require.NoError(t, err)
   783  
   784  			err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.offset})
   785  			require.NoError(t, err)
   786  
   787  			err = compiler.compileConstI64(&wazeroir.OperationConstI64{Value: uint64(tc.in)})
   788  			require.NoError(t, err)
   789  
   790  			err = compiler.compileTableSet(&wazeroir.OperationTableSet{TableIndex: tc.tableIndex})
   791  			require.NoError(t, err)
   792  
   793  			// Generate the code under test.
   794  			err = compiler.compileReturnFunction()
   795  			require.NoError(t, err)
   796  			code, _, err := compiler.compile()
   797  			require.NoError(t, err)
   798  
   799  			// Run code.
   800  			env.exec(code)
   801  
   802  			if tc.expError {
   803  				require.Equal(t, nativeCallStatusCodeInvalidTableAccess, env.compilerStatus())
   804  			} else {
   805  				require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
   806  				require.Equal(t, uint64(0), env.stackPointer())
   807  
   808  				if tc.expExtern {
   809  					actual := dogFromPtr(externTable.References[tc.offset])
   810  					exp := externDog
   811  					if tc.in == 0 {
   812  						exp = nil
   813  					}
   814  					require.Equal(t, exp, actual)
   815  				} else {
   816  					actual := functionFromPtr(funcrefTable.References[tc.offset])
   817  					exp := funcref
   818  					if tc.in == 0 {
   819  						exp = nil
   820  					}
   821  					require.Equal(t, exp, actual)
   822  				}
   823  			}
   824  		})
   825  	}
   826  }
   827  
   828  //go:nocheckptr ignore "pointer arithmetic result points to invalid allocation"
   829  func dogFromPtr(ptr uintptr) *dog {
   830  	if ptr == 0 {
   831  		return nil
   832  	}
   833  	return (*dog)(unsafe.Pointer(ptr))
   834  }
   835  
   836  //go:nocheckptr ignore "pointer arithmetic result points to invalid allocation"
   837  func functionFromPtr(ptr uintptr) *function {
   838  	if ptr == 0 {
   839  		return nil
   840  	}
   841  	return (*function)(unsafe.Pointer(ptr))
   842  }
   843  
   844  func TestCompiler_compileTableGet(t *testing.T) {
   845  	externDog := &dog{name: "sushi"}
   846  	externrefOpaque := uintptr(unsafe.Pointer(externDog))
   847  	funcref := &function{source: &wasm.FunctionInstance{}}
   848  	funcrefOpaque := uintptr(unsafe.Pointer(funcref))
   849  	tables := []*wasm.TableInstance{
   850  		{Type: wasm.RefTypeExternref, References: []wasm.Reference{0, 0, externrefOpaque, 0, 0}},
   851  		{Type: wasm.RefTypeFuncref, References: []wasm.Reference{0, 0, 0, 0, funcrefOpaque}},
   852  	}
   853  
   854  	tests := []struct {
   855  		name       string
   856  		tableIndex uint32
   857  		offset     uint32
   858  		exp        uintptr
   859  		expError   bool
   860  	}{
   861  		{
   862  			name:       "externref - non nil",
   863  			tableIndex: 0,
   864  			offset:     2,
   865  			exp:        externrefOpaque,
   866  		},
   867  		{
   868  			name:       "externref - nil",
   869  			tableIndex: 0,
   870  			offset:     4,
   871  			exp:        0,
   872  		},
   873  		{
   874  			name:       "externref - out of bounds",
   875  			tableIndex: 0,
   876  			offset:     5,
   877  			expError:   true,
   878  		},
   879  		{
   880  			name:       "funcref - non nil",
   881  			tableIndex: 1,
   882  			offset:     4,
   883  			exp:        funcrefOpaque,
   884  		},
   885  		{
   886  			name:       "funcref - nil",
   887  			tableIndex: 1,
   888  			offset:     1,
   889  			exp:        0,
   890  		},
   891  		{
   892  			name:       "funcref - out of bounds",
   893  			tableIndex: 1,
   894  			offset:     1000,
   895  			expError:   true,
   896  		},
   897  	}
   898  
   899  	for _, tt := range tests {
   900  		tc := tt
   901  		t.Run(tc.name, func(t *testing.T) {
   902  			env := newCompilerEnvironment()
   903  
   904  			for _, table := range tables {
   905  				env.addTable(table)
   906  			}
   907  
   908  			compiler := env.requireNewCompiler(t, newCompiler, &wazeroir.CompilationResult{
   909  				HasTable:  true,
   910  				Signature: &wasm.FunctionType{},
   911  			})
   912  
   913  			err := compiler.compilePreamble()
   914  			require.NoError(t, err)
   915  
   916  			err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: tc.offset})
   917  			require.NoError(t, err)
   918  
   919  			err = compiler.compileTableGet(&wazeroir.OperationTableGet{TableIndex: tc.tableIndex})
   920  			require.NoError(t, err)
   921  
   922  			// Generate the code under test.
   923  			err = compiler.compileReturnFunction()
   924  			require.NoError(t, err)
   925  			code, _, err := compiler.compile()
   926  			require.NoError(t, err)
   927  
   928  			// Run code.
   929  			env.exec(code)
   930  
   931  			if tc.expError {
   932  				require.Equal(t, nativeCallStatusCodeInvalidTableAccess, env.compilerStatus())
   933  			} else {
   934  				require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
   935  				require.Equal(t, uint64(1), env.stackPointer())
   936  				require.Equal(t, uint64(tc.exp), env.stackTopAsUint64())
   937  			}
   938  		})
   939  	}
   940  }
   941  
   942  func TestCompiler_compileRefFunc(t *testing.T) {
   943  	env := newCompilerEnvironment()
   944  	compiler := env.requireNewCompiler(t, newCompiler, &wazeroir.CompilationResult{Signature: &wasm.FunctionType{}})
   945  
   946  	err := compiler.compilePreamble()
   947  	require.NoError(t, err)
   948  
   949  	me := env.moduleEngine()
   950  	const numFuncs = 20
   951  	for i := 0; i < numFuncs; i++ {
   952  		me.functions = append(me.functions, &function{source: &wasm.FunctionInstance{}})
   953  	}
   954  
   955  	for i := 0; i < numFuncs; i++ {
   956  		i := i
   957  		t.Run(strconv.Itoa(i), func(t *testing.T) {
   958  			compiler := env.requireNewCompiler(t, newCompiler, &wazeroir.CompilationResult{Signature: &wasm.FunctionType{}})
   959  
   960  			err := compiler.compilePreamble()
   961  			require.NoError(t, err)
   962  
   963  			err = compiler.compileRefFunc(&wazeroir.OperationRefFunc{FunctionIndex: uint32(i)})
   964  			require.NoError(t, err)
   965  
   966  			// Generate the code under test.
   967  			err = compiler.compileReturnFunction()
   968  			require.NoError(t, err)
   969  			code, _, err := compiler.compile()
   970  			require.NoError(t, err)
   971  
   972  			// Run code.
   973  			env.exec(code)
   974  
   975  			require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
   976  			require.Equal(t, uint64(1), env.stackPointer())
   977  			require.Equal(t, uintptr(unsafe.Pointer(me.functions[i])), uintptr(env.stackTopAsUint64()))
   978  		})
   979  	}
   980  }