Signed-off-by: Achille Roussel <achille.roussel@gmail.com> Co-authored-by: Crypt Keeper <64215+codefromthecrypt@users.noreply.github.com>
498 lines
18 KiB
Go
498 lines
18 KiB
Go
package compiler
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
"math"
|
|
"testing"
|
|
|
|
"github.com/tetratelabs/wazero/internal/asm"
|
|
"github.com/tetratelabs/wazero/internal/testing/require"
|
|
"github.com/tetratelabs/wazero/internal/wasm"
|
|
"github.com/tetratelabs/wazero/internal/wazeroir"
|
|
)
|
|
|
|
func TestCompiler_compileMemoryGrow(t *testing.T) {
|
|
env := newCompilerEnvironment()
|
|
compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil)
|
|
err := compiler.compilePreamble()
|
|
require.NoError(t, err)
|
|
|
|
err = compiler.compileMemoryGrow()
|
|
require.NoError(t, err)
|
|
|
|
// Emit arbitrary code after MemoryGrow returned so that we can verify
|
|
// that the code can set the return address properly.
|
|
const expValue uint32 = 100
|
|
err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(expValue)))
|
|
require.NoError(t, err)
|
|
err = compiler.compileReturnFunction()
|
|
require.NoError(t, err)
|
|
|
|
code := asm.CodeSegment{}
|
|
defer func() { require.NoError(t, code.Unmap()) }()
|
|
|
|
// Generate and run the code under test.
|
|
_, err = compiler.compile(code.NextCodeSection())
|
|
require.NoError(t, err)
|
|
env.exec(code.Bytes())
|
|
|
|
// After the initial exec, the code must exit with builtin function call status and funcaddress for memory grow.
|
|
require.Equal(t, nativeCallStatusCodeCallBuiltInFunction, env.compilerStatus())
|
|
require.Equal(t, builtinFunctionIndexMemoryGrow, env.builtinFunctionCallAddress())
|
|
|
|
// Reenter from the return address.
|
|
nativecall(
|
|
env.ce.returnAddress,
|
|
env.callEngine(),
|
|
env.module(),
|
|
)
|
|
|
|
// Check if the code successfully executed the code after builtin function call.
|
|
require.Equal(t, expValue, env.stackTopAsUint32())
|
|
require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
|
|
}
|
|
|
|
func TestCompiler_compileMemorySize(t *testing.T) {
|
|
env := newCompilerEnvironment()
|
|
compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{HasMemory: true})
|
|
|
|
err := compiler.compilePreamble()
|
|
require.NoError(t, err)
|
|
|
|
// Emit memory.size instructions.
|
|
err = compiler.compileMemorySize()
|
|
require.NoError(t, err)
|
|
// At this point, the size of memory should be pushed onto the stack.
|
|
requireRuntimeLocationStackPointerEqual(t, uint64(1), compiler)
|
|
|
|
err = compiler.compileReturnFunction()
|
|
require.NoError(t, err)
|
|
|
|
code := asm.CodeSegment{}
|
|
defer func() { require.NoError(t, code.Unmap()) }()
|
|
|
|
// Generate and run the code under test.
|
|
_, err = compiler.compile(code.NextCodeSection())
|
|
require.NoError(t, err)
|
|
env.exec(code.Bytes())
|
|
|
|
require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
|
|
require.Equal(t, uint32(defaultMemoryPageNumInTest), env.stackTopAsUint32())
|
|
}
|
|
|
|
func TestCompiler_compileLoad(t *testing.T) {
|
|
// For testing. Arbitrary number is fine.
|
|
loadTargetValue := uint64(0x12_34_56_78_9a_bc_ef_fe)
|
|
baseOffset := uint32(100)
|
|
arg := wazeroir.MemoryArg{Offset: 361}
|
|
offset := baseOffset + arg.Offset
|
|
|
|
tests := []struct {
|
|
name string
|
|
isFloatTarget bool
|
|
operationSetupFn func(t *testing.T, compiler compilerImpl)
|
|
loadedValueVerifyFn func(t *testing.T, loadedValueAsUint64 uint64)
|
|
}{
|
|
{
|
|
name: "i32.load",
|
|
operationSetupFn: func(t *testing.T, compiler compilerImpl) {
|
|
err := compiler.compileLoad(operationPtr(wazeroir.NewOperationLoad(wazeroir.UnsignedTypeI32, arg)))
|
|
require.NoError(t, err)
|
|
},
|
|
loadedValueVerifyFn: func(t *testing.T, loadedValueAsUint64 uint64) {
|
|
require.Equal(t, uint32(loadTargetValue), uint32(loadedValueAsUint64))
|
|
},
|
|
},
|
|
{
|
|
name: "i64.load",
|
|
operationSetupFn: func(t *testing.T, compiler compilerImpl) {
|
|
err := compiler.compileLoad(operationPtr(wazeroir.NewOperationLoad(wazeroir.UnsignedTypeI64, arg)))
|
|
require.NoError(t, err)
|
|
},
|
|
loadedValueVerifyFn: func(t *testing.T, loadedValueAsUint64 uint64) {
|
|
require.Equal(t, loadTargetValue, loadedValueAsUint64)
|
|
},
|
|
},
|
|
{
|
|
name: "f32.load",
|
|
operationSetupFn: func(t *testing.T, compiler compilerImpl) {
|
|
err := compiler.compileLoad(operationPtr(wazeroir.NewOperationLoad(wazeroir.UnsignedTypeF32, arg)))
|
|
require.NoError(t, err)
|
|
},
|
|
loadedValueVerifyFn: func(t *testing.T, loadedValueAsUint64 uint64) {
|
|
require.Equal(t, uint32(loadTargetValue), uint32(loadedValueAsUint64))
|
|
},
|
|
isFloatTarget: true,
|
|
},
|
|
{
|
|
name: "f64.load",
|
|
operationSetupFn: func(t *testing.T, compiler compilerImpl) {
|
|
err := compiler.compileLoad(operationPtr(wazeroir.NewOperationLoad(wazeroir.UnsignedTypeF64, arg)))
|
|
require.NoError(t, err)
|
|
},
|
|
loadedValueVerifyFn: func(t *testing.T, loadedValueAsUint64 uint64) {
|
|
require.Equal(t, loadTargetValue, loadedValueAsUint64)
|
|
},
|
|
isFloatTarget: true,
|
|
},
|
|
{
|
|
name: "i32.load8s",
|
|
operationSetupFn: func(t *testing.T, compiler compilerImpl) {
|
|
err := compiler.compileLoad8(operationPtr(wazeroir.NewOperationLoad8(wazeroir.SignedInt32, arg)))
|
|
require.NoError(t, err)
|
|
},
|
|
loadedValueVerifyFn: func(t *testing.T, loadedValueAsUint64 uint64) {
|
|
require.Equal(t, int32(int8(loadedValueAsUint64)), int32(uint32(loadedValueAsUint64)))
|
|
},
|
|
},
|
|
{
|
|
name: "i32.load8u",
|
|
operationSetupFn: func(t *testing.T, compiler compilerImpl) {
|
|
err := compiler.compileLoad8(operationPtr(wazeroir.NewOperationLoad8(wazeroir.SignedUint32, arg)))
|
|
require.NoError(t, err)
|
|
},
|
|
loadedValueVerifyFn: func(t *testing.T, loadedValueAsUint64 uint64) {
|
|
require.Equal(t, uint32(byte(loadedValueAsUint64)), uint32(loadedValueAsUint64))
|
|
},
|
|
},
|
|
{
|
|
name: "i64.load8s",
|
|
operationSetupFn: func(t *testing.T, compiler compilerImpl) {
|
|
err := compiler.compileLoad8(operationPtr(wazeroir.NewOperationLoad8(wazeroir.SignedInt64, arg)))
|
|
require.NoError(t, err)
|
|
},
|
|
loadedValueVerifyFn: func(t *testing.T, loadedValueAsUint64 uint64) {
|
|
require.Equal(t, int64(int8(loadedValueAsUint64)), int64(loadedValueAsUint64))
|
|
},
|
|
},
|
|
{
|
|
name: "i64.load8u",
|
|
operationSetupFn: func(t *testing.T, compiler compilerImpl) {
|
|
err := compiler.compileLoad8(operationPtr(wazeroir.NewOperationLoad8(wazeroir.SignedUint64, arg)))
|
|
require.NoError(t, err)
|
|
},
|
|
loadedValueVerifyFn: func(t *testing.T, loadedValueAsUint64 uint64) {
|
|
require.Equal(t, uint64(byte(loadedValueAsUint64)), loadedValueAsUint64)
|
|
},
|
|
},
|
|
{
|
|
name: "i32.load16s",
|
|
operationSetupFn: func(t *testing.T, compiler compilerImpl) {
|
|
err := compiler.compileLoad16(operationPtr(wazeroir.NewOperationLoad16(wazeroir.SignedInt32, arg)))
|
|
require.NoError(t, err)
|
|
},
|
|
loadedValueVerifyFn: func(t *testing.T, loadedValueAsUint64 uint64) {
|
|
require.Equal(t, int32(int16(loadedValueAsUint64)), int32(uint32(loadedValueAsUint64)))
|
|
},
|
|
},
|
|
{
|
|
name: "i32.load16u",
|
|
operationSetupFn: func(t *testing.T, compiler compilerImpl) {
|
|
err := compiler.compileLoad16(operationPtr(wazeroir.NewOperationLoad16(wazeroir.SignedUint32, arg)))
|
|
require.NoError(t, err)
|
|
},
|
|
loadedValueVerifyFn: func(t *testing.T, loadedValueAsUint64 uint64) {
|
|
require.Equal(t, uint32(loadedValueAsUint64), uint32(loadedValueAsUint64))
|
|
},
|
|
},
|
|
{
|
|
name: "i64.load16s",
|
|
operationSetupFn: func(t *testing.T, compiler compilerImpl) {
|
|
err := compiler.compileLoad16(operationPtr(wazeroir.NewOperationLoad16(wazeroir.SignedInt64, arg)))
|
|
require.NoError(t, err)
|
|
},
|
|
loadedValueVerifyFn: func(t *testing.T, loadedValueAsUint64 uint64) {
|
|
require.Equal(t, int64(int16(loadedValueAsUint64)), int64(loadedValueAsUint64))
|
|
},
|
|
},
|
|
{
|
|
name: "i64.load16u",
|
|
operationSetupFn: func(t *testing.T, compiler compilerImpl) {
|
|
err := compiler.compileLoad16(operationPtr(wazeroir.NewOperationLoad16(wazeroir.SignedUint64, arg)))
|
|
require.NoError(t, err)
|
|
},
|
|
loadedValueVerifyFn: func(t *testing.T, loadedValueAsUint64 uint64) {
|
|
require.Equal(t, uint64(uint16(loadedValueAsUint64)), loadedValueAsUint64)
|
|
},
|
|
},
|
|
{
|
|
name: "i64.load32s",
|
|
operationSetupFn: func(t *testing.T, compiler compilerImpl) {
|
|
err := compiler.compileLoad32(operationPtr(wazeroir.NewOperationLoad32(true, arg)))
|
|
require.NoError(t, err)
|
|
},
|
|
loadedValueVerifyFn: func(t *testing.T, loadedValueAsUint64 uint64) {
|
|
require.Equal(t, int64(int32(loadedValueAsUint64)), int64(loadedValueAsUint64))
|
|
},
|
|
},
|
|
{
|
|
name: "i64.load32u",
|
|
operationSetupFn: func(t *testing.T, compiler compilerImpl) {
|
|
err := compiler.compileLoad32(operationPtr(wazeroir.NewOperationLoad32(false, arg)))
|
|
require.NoError(t, err)
|
|
},
|
|
loadedValueVerifyFn: func(t *testing.T, loadedValueAsUint64 uint64) {
|
|
require.Equal(t, uint64(uint32(loadedValueAsUint64)), loadedValueAsUint64)
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
env := newCompilerEnvironment()
|
|
compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{HasMemory: true})
|
|
|
|
err := compiler.compilePreamble()
|
|
require.NoError(t, err)
|
|
|
|
binary.LittleEndian.PutUint64(env.memory()[offset:], loadTargetValue)
|
|
|
|
// Before load operation, we must push the base offset value.
|
|
err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(baseOffset)))
|
|
require.NoError(t, err)
|
|
|
|
tc.operationSetupFn(t, compiler)
|
|
|
|
// At this point, the loaded value must be on top of the stack, and placed on a register.
|
|
requireRuntimeLocationStackPointerEqual(t, uint64(1), compiler)
|
|
require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
|
|
loadedLocation := compiler.runtimeValueLocationStack().peek()
|
|
require.True(t, loadedLocation.onRegister())
|
|
if tc.isFloatTarget {
|
|
require.Equal(t, registerTypeVector, loadedLocation.getRegisterType())
|
|
} else {
|
|
require.Equal(t, registerTypeGeneralPurpose, loadedLocation.getRegisterType())
|
|
}
|
|
err = compiler.compileReturnFunction()
|
|
require.NoError(t, err)
|
|
|
|
code := asm.CodeSegment{}
|
|
defer func() { require.NoError(t, code.Unmap()) }()
|
|
|
|
// Generate and run the code under test.
|
|
_, err = compiler.compile(code.NextCodeSection())
|
|
require.NoError(t, err)
|
|
env.exec(code.Bytes())
|
|
|
|
// Verify the loaded value.
|
|
require.Equal(t, uint64(1), env.stackPointer())
|
|
tc.loadedValueVerifyFn(t, env.stackTopAsUint64())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCompiler_compileStore(t *testing.T) {
|
|
// For testing. Arbitrary number is fine.
|
|
storeTargetValue := uint64(math.MaxUint64)
|
|
baseOffset := uint32(100)
|
|
arg := wazeroir.MemoryArg{Offset: 361}
|
|
offset := arg.Offset + baseOffset
|
|
|
|
tests := []struct {
|
|
name string
|
|
isFloatTarget bool
|
|
targetSizeInBytes uint32
|
|
operationSetupFn func(t *testing.T, compiler compilerImpl)
|
|
storedValueVerifyFn func(t *testing.T, mem []byte)
|
|
}{
|
|
{
|
|
name: "i32.store",
|
|
targetSizeInBytes: 32 / 8,
|
|
operationSetupFn: func(t *testing.T, compiler compilerImpl) {
|
|
err := compiler.compileStore(operationPtr(wazeroir.NewOperationStore(wazeroir.UnsignedTypeI32, arg)))
|
|
require.NoError(t, err)
|
|
},
|
|
storedValueVerifyFn: func(t *testing.T, mem []byte) {
|
|
require.Equal(t, uint32(storeTargetValue), binary.LittleEndian.Uint32(mem[offset:]))
|
|
},
|
|
},
|
|
{
|
|
name: "f32.store",
|
|
isFloatTarget: true,
|
|
targetSizeInBytes: 32 / 8,
|
|
operationSetupFn: func(t *testing.T, compiler compilerImpl) {
|
|
err := compiler.compileStore(operationPtr(wazeroir.NewOperationStore(wazeroir.UnsignedTypeF32, arg)))
|
|
require.NoError(t, err)
|
|
},
|
|
storedValueVerifyFn: func(t *testing.T, mem []byte) {
|
|
require.Equal(t, uint32(storeTargetValue), binary.LittleEndian.Uint32(mem[offset:]))
|
|
},
|
|
},
|
|
{
|
|
name: "i64.store",
|
|
targetSizeInBytes: 64 / 8,
|
|
operationSetupFn: func(t *testing.T, compiler compilerImpl) {
|
|
err := compiler.compileStore(operationPtr(wazeroir.NewOperationStore(wazeroir.UnsignedTypeI64, arg)))
|
|
require.NoError(t, err)
|
|
},
|
|
storedValueVerifyFn: func(t *testing.T, mem []byte) {
|
|
require.Equal(t, storeTargetValue, binary.LittleEndian.Uint64(mem[offset:]))
|
|
},
|
|
},
|
|
{
|
|
name: "f64.store",
|
|
isFloatTarget: true,
|
|
targetSizeInBytes: 64 / 8,
|
|
operationSetupFn: func(t *testing.T, compiler compilerImpl) {
|
|
err := compiler.compileStore(operationPtr(wazeroir.NewOperationStore(wazeroir.UnsignedTypeF64, arg)))
|
|
require.NoError(t, err)
|
|
},
|
|
storedValueVerifyFn: func(t *testing.T, mem []byte) {
|
|
require.Equal(t, storeTargetValue, binary.LittleEndian.Uint64(mem[offset:]))
|
|
},
|
|
},
|
|
{
|
|
name: "store8",
|
|
targetSizeInBytes: 1,
|
|
operationSetupFn: func(t *testing.T, compiler compilerImpl) {
|
|
err := compiler.compileStore8(operationPtr(wazeroir.NewOperationStore8(arg)))
|
|
require.NoError(t, err)
|
|
},
|
|
storedValueVerifyFn: func(t *testing.T, mem []byte) {
|
|
require.Equal(t, byte(storeTargetValue), mem[offset])
|
|
},
|
|
},
|
|
{
|
|
name: "store16",
|
|
targetSizeInBytes: 16 / 8,
|
|
operationSetupFn: func(t *testing.T, compiler compilerImpl) {
|
|
err := compiler.compileStore16(operationPtr(wazeroir.NewOperationStore16(arg)))
|
|
require.NoError(t, err)
|
|
},
|
|
storedValueVerifyFn: func(t *testing.T, mem []byte) {
|
|
require.Equal(t, uint16(storeTargetValue), binary.LittleEndian.Uint16(mem[offset:]))
|
|
},
|
|
},
|
|
{
|
|
name: "store32",
|
|
targetSizeInBytes: 32 / 8,
|
|
operationSetupFn: func(t *testing.T, compiler compilerImpl) {
|
|
err := compiler.compileStore32(operationPtr(wazeroir.NewOperationStore32(arg)))
|
|
require.NoError(t, err)
|
|
},
|
|
storedValueVerifyFn: func(t *testing.T, mem []byte) {
|
|
require.Equal(t, uint32(storeTargetValue), binary.LittleEndian.Uint32(mem[offset:]))
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
env := newCompilerEnvironment()
|
|
compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{HasMemory: true})
|
|
|
|
err := compiler.compilePreamble()
|
|
require.NoError(t, err)
|
|
|
|
// Before store operations, we must push the base offset, and the store target values.
|
|
err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(baseOffset)))
|
|
require.NoError(t, err)
|
|
if tc.isFloatTarget {
|
|
err = compiler.compileConstF64(operationPtr(wazeroir.NewOperationConstF64(math.Float64frombits(storeTargetValue))))
|
|
} else {
|
|
err = compiler.compileConstI64(operationPtr(wazeroir.NewOperationConstI64(storeTargetValue)))
|
|
}
|
|
require.NoError(t, err)
|
|
|
|
tc.operationSetupFn(t, compiler)
|
|
|
|
// At this point, no registers must be in use, and no values on the stack since we consumed two values.
|
|
require.Zero(t, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
|
|
requireRuntimeLocationStackPointerEqual(t, uint64(0), compiler)
|
|
|
|
code := asm.CodeSegment{}
|
|
defer func() { require.NoError(t, code.Unmap()) }()
|
|
|
|
// Generate the code under test.
|
|
err = compiler.compileReturnFunction()
|
|
require.NoError(t, err)
|
|
_, err = compiler.compile(code.NextCodeSection())
|
|
require.NoError(t, err)
|
|
|
|
// Set the value on the left and right neighboring memoryregion,
|
|
// so that we can verify the operation doesn't affect there.
|
|
ceil := offset + tc.targetSizeInBytes
|
|
mem := env.memory()
|
|
expectedNeighbor8Bytes := uint64(0x12_34_56_78_9a_bc_ef_fe)
|
|
binary.LittleEndian.PutUint64(mem[offset-8:offset], expectedNeighbor8Bytes)
|
|
binary.LittleEndian.PutUint64(mem[ceil:ceil+8], expectedNeighbor8Bytes)
|
|
|
|
// Run code.
|
|
env.exec(code.Bytes())
|
|
|
|
tc.storedValueVerifyFn(t, mem)
|
|
|
|
// The neighboring bytes must be intact.
|
|
require.Equal(t, expectedNeighbor8Bytes, binary.LittleEndian.Uint64(mem[offset-8:offset]))
|
|
require.Equal(t, expectedNeighbor8Bytes, binary.LittleEndian.Uint64(mem[ceil:ceil+8]))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCompiler_MemoryOutOfBounds(t *testing.T) {
|
|
bases := []uint32{0, 1 << 5, 1 << 9, 1 << 10, 1 << 15, math.MaxUint32 - 1, math.MaxUint32}
|
|
offsets := []uint32{
|
|
0,
|
|
1 << 10, 1 << 31,
|
|
defaultMemoryPageNumInTest*wasm.MemoryPageSize - 1, defaultMemoryPageNumInTest * wasm.MemoryPageSize,
|
|
math.MaxInt32 - 1, math.MaxInt32 - 2, math.MaxInt32 - 3, math.MaxInt32 - 4,
|
|
math.MaxInt32 - 5, math.MaxInt32 - 8, math.MaxInt32 - 9, math.MaxInt32, math.MaxUint32,
|
|
}
|
|
targetSizeInBytes := []int64{1, 2, 4, 8}
|
|
for _, base := range bases {
|
|
base := base
|
|
for _, offset := range offsets {
|
|
offset := offset
|
|
for _, targetSizeInByte := range targetSizeInBytes {
|
|
targetSizeInByte := targetSizeInByte
|
|
t.Run(fmt.Sprintf("base=%d,offset=%d,targetSizeInBytes=%d", base, offset, targetSizeInByte), func(t *testing.T) {
|
|
env := newCompilerEnvironment()
|
|
compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil)
|
|
|
|
err := compiler.compilePreamble()
|
|
require.NoError(t, err)
|
|
|
|
err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(base)))
|
|
require.NoError(t, err)
|
|
|
|
arg := wazeroir.MemoryArg{Offset: offset}
|
|
|
|
switch targetSizeInByte {
|
|
case 1:
|
|
err = compiler.compileLoad8(operationPtr(wazeroir.NewOperationLoad8(wazeroir.SignedInt32, arg)))
|
|
case 2:
|
|
err = compiler.compileLoad16(operationPtr(wazeroir.NewOperationLoad16(wazeroir.SignedInt32, arg)))
|
|
case 4:
|
|
err = compiler.compileLoad32(operationPtr(wazeroir.NewOperationLoad32(false, arg)))
|
|
case 8:
|
|
err = compiler.compileLoad(operationPtr(wazeroir.NewOperationLoad(wazeroir.UnsignedTypeF64, arg)))
|
|
default:
|
|
t.Fail()
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
require.NoError(t, compiler.compileReturnFunction())
|
|
|
|
code := asm.CodeSegment{}
|
|
defer func() { require.NoError(t, code.Unmap()) }()
|
|
|
|
// Generate the code under test and run.
|
|
_, err = compiler.compile(code.NextCodeSection())
|
|
require.NoError(t, err)
|
|
env.exec(code.Bytes())
|
|
|
|
mem := env.memory()
|
|
if ceil := int64(base) + int64(offset) + int64(targetSizeInByte); int64(len(mem)) < ceil {
|
|
// If the targe memory region's ceil exceeds the length of memory, we must exit the function
|
|
// with nativeCallStatusCodeMemoryOutOfBounds status code.
|
|
require.Equal(t, nativeCallStatusCodeMemoryOutOfBounds, env.compilerStatus())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|