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