github.com/bananabytelabs/wazero@v0.0.0-20240105073314-54b22a776da8/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/bananabytelabs/wazero/internal/asm"
    10  	"github.com/bananabytelabs/wazero/internal/testing/require"
    11  	"github.com/bananabytelabs/wazero/internal/wasm"
    12  	"github.com/bananabytelabs/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{{1}, {2}, {3}, {4}, {5}}
   480  
   481  	for i := 0; i < len(origins); i++ {
   482  		t.Run(strconv.Itoa(i), func(t *testing.T) {
   483  			env := newCompilerEnvironment()
   484  
   485  			insts := make([]wasm.ElementInstance, len(origins))
   486  			copy(insts, origins)
   487  			env.module().ElementInstances = insts
   488  
   489  			// Verify assumption that before Drop instruction, all the element instances are not empty.
   490  			for _, inst := range insts {
   491  				require.NotEqual(t, 0, len(inst))
   492  			}
   493  
   494  			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{
   495  				HasElementInstances: true,
   496  			})
   497  
   498  			err := compiler.compilePreamble()
   499  			require.NoError(t, err)
   500  
   501  			err = compiler.compileElemDrop(operationPtr(wazeroir.NewOperationElemDrop(uint32(i))))
   502  			require.NoError(t, err)
   503  
   504  			code := asm.CodeSegment{}
   505  			defer func() { require.NoError(t, code.Unmap()) }()
   506  
   507  			// Generate the code under test.
   508  			err = compiler.compileReturnFunction()
   509  			require.NoError(t, err)
   510  			_, err = compiler.compile(code.NextCodeSection())
   511  			require.NoError(t, err)
   512  
   513  			// Run code.
   514  			env.exec(code.Bytes())
   515  
   516  			require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
   517  
   518  			for j := 0; j < len(insts); j++ {
   519  				if i == j {
   520  					require.Zero(t, len(env.module().ElementInstances[j]))
   521  				} else {
   522  					require.NotEqual(t, 0, len(env.module().ElementInstances[j]))
   523  				}
   524  			}
   525  		})
   526  	}
   527  }
   528  
   529  func TestCompiler_compileTableCopy(t *testing.T) {
   530  	const tableSize = 100
   531  	tests := []struct {
   532  		sourceOffset, destOffset, size uint32
   533  		requireOutOfBoundsError        bool
   534  	}{
   535  		{sourceOffset: 0, destOffset: 0, size: 0},
   536  		{sourceOffset: 10, destOffset: 5, size: 10},
   537  		{sourceOffset: 10, destOffset: 9, size: 1},
   538  		{sourceOffset: 10, destOffset: 9, size: 2},
   539  		{sourceOffset: 0, destOffset: 10, size: 10},
   540  		{sourceOffset: 0, destOffset: 5, size: 10},
   541  		{sourceOffset: 9, destOffset: 10, size: 10},
   542  		{sourceOffset: 11, destOffset: 13, size: 4},
   543  		{sourceOffset: 0, destOffset: 10, size: 5},
   544  		{sourceOffset: 1, destOffset: 10, size: 5},
   545  		{sourceOffset: 0, destOffset: 10, size: 1},
   546  		{sourceOffset: 0, destOffset: 10, size: 0},
   547  		{sourceOffset: 5, destOffset: 10, size: 10},
   548  		{sourceOffset: 5, destOffset: 10, size: 5},
   549  		{sourceOffset: 5, destOffset: 10, size: 1},
   550  		{sourceOffset: 5, destOffset: 10, size: 0},
   551  		{sourceOffset: 10, destOffset: 0, size: 10},
   552  		{sourceOffset: 1, destOffset: 0, size: 2},
   553  		{sourceOffset: 1, destOffset: 0, size: 20},
   554  		{sourceOffset: 10, destOffset: 0, size: 5},
   555  		{sourceOffset: 10, destOffset: 0, size: 1},
   556  		{sourceOffset: 10, destOffset: 0, size: 0},
   557  		{sourceOffset: tableSize, destOffset: 0, size: 1, requireOutOfBoundsError: true},
   558  		{sourceOffset: tableSize + 1, destOffset: 0, size: 0, requireOutOfBoundsError: true},
   559  		{sourceOffset: 0, destOffset: tableSize, size: 1, requireOutOfBoundsError: true},
   560  		{sourceOffset: 0, destOffset: tableSize + 1, size: 0, requireOutOfBoundsError: true},
   561  		{sourceOffset: tableSize - 99, destOffset: 0, size: 100, requireOutOfBoundsError: true},
   562  		{sourceOffset: 0, destOffset: tableSize - 99, size: 100, requireOutOfBoundsError: true},
   563  	}
   564  
   565  	for i, tt := range tests {
   566  		tc := tt
   567  		t.Run(strconv.Itoa(i), func(t *testing.T) {
   568  			env := newCompilerEnvironment()
   569  			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{HasTable: true})
   570  
   571  			err := compiler.compilePreamble()
   572  			require.NoError(t, err)
   573  
   574  			// Compile operands.
   575  			err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(tc.destOffset)))
   576  			require.NoError(t, err)
   577  			err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(tc.sourceOffset)))
   578  			require.NoError(t, err)
   579  			err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(tc.size)))
   580  			require.NoError(t, err)
   581  
   582  			err = compiler.compileTableCopy(operationPtr(wazeroir.NewOperationTableCopy(0, 0)))
   583  			require.NoError(t, err)
   584  
   585  			code := asm.CodeSegment{}
   586  			defer func() { require.NoError(t, code.Unmap()) }()
   587  
   588  			// Generate the code under test.
   589  			err = compiler.compileReturnFunction()
   590  			require.NoError(t, err)
   591  			_, err = compiler.compile(code.NextCodeSection())
   592  			require.NoError(t, err)
   593  
   594  			// Setup the table.
   595  			table := make([]wasm.Reference, tableSize)
   596  			env.addTable(&wasm.TableInstance{References: table})
   597  			for i := 0; i < tableSize; i++ {
   598  				table[i] = uintptr(i)
   599  			}
   600  
   601  			// Run code.
   602  			env.exec(code.Bytes())
   603  
   604  			if !tc.requireOutOfBoundsError {
   605  				exp := make([]wasm.Reference, tableSize)
   606  				for i := 0; i < tableSize; i++ {
   607  					exp[i] = uintptr(i)
   608  				}
   609  				copy(exp[tc.destOffset:],
   610  					exp[tc.sourceOffset:tc.sourceOffset+tc.size])
   611  
   612  				// Check the status code and the destination memory region.
   613  				require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
   614  				require.Equal(t, exp, table)
   615  			} else {
   616  				require.Equal(t, nativeCallStatusCodeInvalidTableAccess, env.compilerStatus())
   617  			}
   618  		})
   619  	}
   620  }
   621  
   622  func TestCompiler_compileTableInit(t *testing.T) {
   623  	elementInstances := []wasm.ElementInstance{
   624  		{}, {1, 2, 3, 4, 5},
   625  	}
   626  
   627  	const tableSize = 100
   628  	tests := []struct {
   629  		sourceOffset, destOffset uint32
   630  		elemIndex                uint32
   631  		copySize                 uint32
   632  		expOutOfBounds           bool
   633  	}{
   634  		{sourceOffset: 0, destOffset: 0, copySize: 0, elemIndex: 0},
   635  		{sourceOffset: 0, destOffset: 0, copySize: 1, elemIndex: 0, expOutOfBounds: true},
   636  		{sourceOffset: 1, destOffset: 0, copySize: 0, elemIndex: 0, expOutOfBounds: true},
   637  		{sourceOffset: 0, destOffset: 0, copySize: 0, elemIndex: 1},
   638  		{sourceOffset: 0, destOffset: 0, copySize: 5, elemIndex: 1},
   639  		{sourceOffset: 0, destOffset: 0, copySize: 1, elemIndex: 1},
   640  		{sourceOffset: 0, destOffset: 0, copySize: 3, elemIndex: 1},
   641  		{sourceOffset: 0, destOffset: 1, copySize: 3, elemIndex: 1},
   642  		{sourceOffset: 0, destOffset: 7, copySize: 4, elemIndex: 1},
   643  		{sourceOffset: 1, destOffset: 7, copySize: 4, elemIndex: 1},
   644  		{sourceOffset: 4, destOffset: 7, copySize: 1, elemIndex: 1},
   645  		{sourceOffset: 5, destOffset: 7, copySize: 0, elemIndex: 1},
   646  		{sourceOffset: 0, destOffset: 7, copySize: 5, elemIndex: 1},
   647  		{sourceOffset: 1, destOffset: 0, copySize: 3, elemIndex: 1},
   648  		{sourceOffset: 0, destOffset: 1, copySize: 4, elemIndex: 1},
   649  		{sourceOffset: 1, destOffset: 1, copySize: 3, elemIndex: 1},
   650  		{sourceOffset: 0, destOffset: 10, copySize: 5, elemIndex: 1},
   651  		{sourceOffset: 0, destOffset: 0, copySize: 6, elemIndex: 1, expOutOfBounds: true},
   652  		{sourceOffset: 6, destOffset: 0, copySize: 0, elemIndex: 1, expOutOfBounds: true},
   653  	}
   654  
   655  	for i, tt := range tests {
   656  		tc := tt
   657  		t.Run(strconv.Itoa(i), func(t *testing.T) {
   658  			env := newCompilerEnvironment()
   659  			env.module().ElementInstances = elementInstances
   660  
   661  			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{
   662  				HasElementInstances: true, HasTable: true,
   663  			})
   664  
   665  			err := compiler.compilePreamble()
   666  			require.NoError(t, err)
   667  
   668  			// Compile operands.
   669  			err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(tc.destOffset)))
   670  			require.NoError(t, err)
   671  			err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(tc.sourceOffset)))
   672  			require.NoError(t, err)
   673  			err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(tc.copySize)))
   674  			require.NoError(t, err)
   675  
   676  			err = compiler.compileTableInit(operationPtr(wazeroir.NewOperationTableInit(tc.elemIndex, 0)))
   677  			require.NoError(t, err)
   678  
   679  			// Setup the table.
   680  			table := make([]wasm.Reference, tableSize)
   681  			env.addTable(&wasm.TableInstance{References: table})
   682  			for i := 0; i < tableSize; i++ {
   683  				table[i] = uintptr(i)
   684  			}
   685  
   686  			code := asm.CodeSegment{}
   687  			defer func() { require.NoError(t, code.Unmap()) }()
   688  
   689  			// Generate the code under test.
   690  			err = compiler.compileReturnFunction()
   691  			require.NoError(t, err)
   692  			_, err = compiler.compile(code.NextCodeSection())
   693  			require.NoError(t, err)
   694  
   695  			// Run code.
   696  			env.exec(code.Bytes())
   697  
   698  			if !tc.expOutOfBounds {
   699  				require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
   700  				exp := make([]wasm.Reference, tableSize)
   701  				for i := 0; i < tableSize; i++ {
   702  					exp[i] = uintptr(i)
   703  				}
   704  				if inst := elementInstances[tc.elemIndex]; inst != nil {
   705  					copy(exp[tc.destOffset:], inst[tc.sourceOffset:tc.sourceOffset+tc.copySize])
   706  				}
   707  				require.Equal(t, exp, table)
   708  			} else {
   709  				require.Equal(t, nativeCallStatusCodeInvalidTableAccess, env.compilerStatus())
   710  			}
   711  		})
   712  	}
   713  }
   714  
   715  type dog struct{ name string }
   716  
   717  func TestCompiler_compileTableSet(t *testing.T) {
   718  	externDog := &dog{name: "sushi"}
   719  	externrefOpaque := uintptr(unsafe.Pointer(externDog))
   720  	funcref := &function{moduleInstance: &wasm.ModuleInstance{}}
   721  	funcrefOpaque := uintptr(unsafe.Pointer(funcref))
   722  
   723  	externTable := &wasm.TableInstance{Type: wasm.RefTypeExternref, References: []wasm.Reference{0, 0, externrefOpaque, 0, 0}}
   724  	funcrefTable := &wasm.TableInstance{Type: wasm.RefTypeFuncref, References: []wasm.Reference{0, 0, 0, 0, funcrefOpaque}}
   725  	tables := []*wasm.TableInstance{externTable, funcrefTable}
   726  
   727  	tests := []struct {
   728  		name       string
   729  		tableIndex uint32
   730  		offset     uint32
   731  		in         uintptr
   732  		expExtern  bool
   733  		expError   bool
   734  	}{
   735  		{
   736  			name:       "externref - non nil",
   737  			tableIndex: 0,
   738  			offset:     2,
   739  			in:         externrefOpaque,
   740  			expExtern:  true,
   741  		},
   742  		{
   743  			name:       "externref - nil",
   744  			tableIndex: 0,
   745  			offset:     1,
   746  			in:         0,
   747  			expExtern:  true,
   748  		},
   749  		{
   750  			name:       "externref - out of bounds",
   751  			tableIndex: 0,
   752  			offset:     10,
   753  			in:         0,
   754  			expError:   true,
   755  		},
   756  		{
   757  			name:       "funcref - non nil",
   758  			tableIndex: 1,
   759  			offset:     4,
   760  			in:         funcrefOpaque,
   761  			expExtern:  false,
   762  		},
   763  		{
   764  			name:       "funcref - nil",
   765  			tableIndex: 1,
   766  			offset:     3,
   767  			in:         0,
   768  			expExtern:  false,
   769  		},
   770  		{
   771  			name:       "funcref - out of bounds",
   772  			tableIndex: 1,
   773  			offset:     100000,
   774  			in:         0,
   775  			expError:   true,
   776  		},
   777  	}
   778  
   779  	for _, tt := range tests {
   780  		tc := tt
   781  		t.Run(tc.name, func(t *testing.T) {
   782  			env := newCompilerEnvironment()
   783  
   784  			for _, table := range tables {
   785  				env.addTable(table)
   786  			}
   787  
   788  			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{
   789  				HasTable: true,
   790  			})
   791  
   792  			err := compiler.compilePreamble()
   793  			require.NoError(t, err)
   794  
   795  			err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(tc.offset)))
   796  			require.NoError(t, err)
   797  
   798  			err = compiler.compileConstI64(operationPtr(wazeroir.NewOperationConstI64(uint64(tc.in))))
   799  			require.NoError(t, err)
   800  
   801  			err = compiler.compileTableSet(operationPtr(wazeroir.NewOperationTableSet(tc.tableIndex)))
   802  			require.NoError(t, err)
   803  
   804  			code := asm.CodeSegment{}
   805  			defer func() { require.NoError(t, code.Unmap()) }()
   806  
   807  			// Generate the code under test.
   808  			err = compiler.compileReturnFunction()
   809  			require.NoError(t, err)
   810  			_, err = compiler.compile(code.NextCodeSection())
   811  			require.NoError(t, err)
   812  
   813  			// Run code.
   814  			env.exec(code.Bytes())
   815  
   816  			if tc.expError {
   817  				require.Equal(t, nativeCallStatusCodeInvalidTableAccess, env.compilerStatus())
   818  			} else {
   819  				require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
   820  				require.Equal(t, uint64(0), env.stackPointer())
   821  
   822  				if tc.expExtern {
   823  					actual := dogFromPtr(externTable.References[tc.offset])
   824  					exp := externDog
   825  					if tc.in == 0 {
   826  						exp = nil
   827  					}
   828  					require.Equal(t, exp, actual)
   829  				} else {
   830  					actual := functionFromPtr(funcrefTable.References[tc.offset])
   831  					exp := funcref
   832  					if tc.in == 0 {
   833  						exp = nil
   834  					}
   835  					require.Equal(t, exp, actual)
   836  				}
   837  			}
   838  		})
   839  	}
   840  }
   841  
   842  //go:nocheckptr ignore "pointer arithmetic result points to invalid allocation"
   843  func dogFromPtr(ptr uintptr) *dog {
   844  	if ptr == 0 {
   845  		return nil
   846  	}
   847  	return (*dog)(unsafe.Pointer(ptr))
   848  }
   849  
   850  //go:nocheckptr ignore "pointer arithmetic result points to invalid allocation"
   851  func functionFromPtr(ptr uintptr) *function {
   852  	if ptr == 0 {
   853  		return nil
   854  	}
   855  	return (*function)(unsafe.Pointer(ptr))
   856  }
   857  
   858  func TestCompiler_compileTableGet(t *testing.T) {
   859  	externDog := &dog{name: "sushi"}
   860  	externrefOpaque := uintptr(unsafe.Pointer(externDog))
   861  	funcref := &function{moduleInstance: &wasm.ModuleInstance{}}
   862  	funcrefOpaque := uintptr(unsafe.Pointer(funcref))
   863  	tables := []*wasm.TableInstance{
   864  		{Type: wasm.RefTypeExternref, References: []wasm.Reference{0, 0, externrefOpaque, 0, 0}},
   865  		{Type: wasm.RefTypeFuncref, References: []wasm.Reference{0, 0, 0, 0, funcrefOpaque}},
   866  	}
   867  
   868  	tests := []struct {
   869  		name       string
   870  		tableIndex uint32
   871  		offset     uint32
   872  		exp        uintptr
   873  		expError   bool
   874  	}{
   875  		{
   876  			name:       "externref - non nil",
   877  			tableIndex: 0,
   878  			offset:     2,
   879  			exp:        externrefOpaque,
   880  		},
   881  		{
   882  			name:       "externref - nil",
   883  			tableIndex: 0,
   884  			offset:     4,
   885  			exp:        0,
   886  		},
   887  		{
   888  			name:       "externref - out of bounds",
   889  			tableIndex: 0,
   890  			offset:     5,
   891  			expError:   true,
   892  		},
   893  		{
   894  			name:       "funcref - non nil",
   895  			tableIndex: 1,
   896  			offset:     4,
   897  			exp:        funcrefOpaque,
   898  		},
   899  		{
   900  			name:       "funcref - nil",
   901  			tableIndex: 1,
   902  			offset:     1,
   903  			exp:        0,
   904  		},
   905  		{
   906  			name:       "funcref - out of bounds",
   907  			tableIndex: 1,
   908  			offset:     1000,
   909  			expError:   true,
   910  		},
   911  	}
   912  
   913  	for _, tt := range tests {
   914  		tc := tt
   915  		t.Run(tc.name, func(t *testing.T) {
   916  			env := newCompilerEnvironment()
   917  
   918  			for _, table := range tables {
   919  				env.addTable(table)
   920  			}
   921  
   922  			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{
   923  				HasTable: true,
   924  			})
   925  
   926  			err := compiler.compilePreamble()
   927  			require.NoError(t, err)
   928  
   929  			err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(tc.offset)))
   930  			require.NoError(t, err)
   931  
   932  			err = compiler.compileTableGet(operationPtr(wazeroir.NewOperationTableGet(tc.tableIndex)))
   933  			require.NoError(t, err)
   934  
   935  			code := asm.CodeSegment{}
   936  			defer func() { require.NoError(t, code.Unmap()) }()
   937  
   938  			// Generate the code under test.
   939  			err = compiler.compileReturnFunction()
   940  			require.NoError(t, err)
   941  			_, err = compiler.compile(code.NextCodeSection())
   942  			require.NoError(t, err)
   943  
   944  			// Run code.
   945  			env.exec(code.Bytes())
   946  
   947  			if tc.expError {
   948  				require.Equal(t, nativeCallStatusCodeInvalidTableAccess, env.compilerStatus())
   949  			} else {
   950  				require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
   951  				require.Equal(t, uint64(1), env.stackPointer())
   952  				require.Equal(t, uint64(tc.exp), env.stackTopAsUint64())
   953  			}
   954  		})
   955  	}
   956  }
   957  
   958  func TestCompiler_compileRefFunc(t *testing.T) {
   959  	env := newCompilerEnvironment()
   960  	compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{})
   961  
   962  	err := compiler.compilePreamble()
   963  	require.NoError(t, err)
   964  
   965  	me := env.moduleEngine()
   966  	const numFuncs = 20
   967  	for i := 0; i < numFuncs; i++ {
   968  		me.functions = append(me.functions, function{moduleInstance: &wasm.ModuleInstance{}})
   969  	}
   970  
   971  	for i := 0; i < numFuncs; i++ {
   972  		i := i
   973  		t.Run(strconv.Itoa(i), func(t *testing.T) {
   974  			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{})
   975  
   976  			err := compiler.compilePreamble()
   977  			require.NoError(t, err)
   978  
   979  			err = compiler.compileRefFunc(operationPtr(wazeroir.NewOperationRefFunc(uint32(i))))
   980  			require.NoError(t, err)
   981  
   982  			code := asm.CodeSegment{}
   983  			defer func() { require.NoError(t, code.Unmap()) }()
   984  
   985  			// Generate the code under test.
   986  			err = compiler.compileReturnFunction()
   987  			require.NoError(t, err)
   988  			_, err = compiler.compile(code.NextCodeSection())
   989  			require.NoError(t, err)
   990  
   991  			// Run code.
   992  			env.exec(code.Bytes())
   993  
   994  			require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
   995  			require.Equal(t, uint64(1), env.stackPointer())
   996  			require.Equal(t, uintptr(unsafe.Pointer(&me.functions[i])), uintptr(env.stackTopAsUint64()))
   997  		})
   998  	}
   999  }