// Copyright 2020 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include "src/utils/utils.h" #include "src/utils/vector.h" #include "src/wasm/module-decoder.h" #include "src/wasm/struct-types.h" #include "src/wasm/wasm-arguments.h" #include "src/wasm/wasm-engine.h" #include "src/wasm/wasm-module-builder.h" #include "src/wasm/wasm-module.h" #include "src/wasm/wasm-objects-inl.h" #include "src/wasm/wasm-opcodes.h" #include "test/cctest/cctest.h" #include "test/cctest/compiler/value-helper.h" #include "test/cctest/wasm/wasm-run-utils.h" #include "test/common/wasm/test-signatures.h" #include "test/common/wasm/wasm-macro-gen.h" #include "test/common/wasm/wasm-module-runner.h" namespace v8 { namespace internal { namespace wasm { namespace test_gc { using F = std::pair; class WasmGCTester { public: WasmGCTester() : flag_gc(&v8::internal::FLAG_experimental_wasm_gc, true), flag_reftypes(&v8::internal::FLAG_experimental_wasm_reftypes, true), flag_typedfuns(&v8::internal::FLAG_experimental_wasm_typed_funcref, true), zone(&allocator, ZONE_NAME), builder_(&zone), isolate_(CcTest::InitIsolateOnce()), scope(isolate_), thrower(isolate_, "Test wasm GC") { testing::SetupIsolateForWasmModule(isolate_); } byte AddGlobal(ValueType type, bool mutability, WasmInitExpr init) { return builder_.AddGlobal(type, mutability, std::move(init)); } byte DefineFunction(FunctionSig* sig, std::initializer_list locals, std::initializer_list code) { WasmFunctionBuilder* fun = builder_.AddFunction(sig); for (ValueType local : locals) { fun->AddLocal(local); } fun->EmitCode(code.begin(), static_cast(code.size())); return fun->func_index(); } void DefineExportedFunction(const char* name, FunctionSig* sig, std::initializer_list code) { WasmFunctionBuilder* fun = builder_.AddFunction(sig); fun->EmitCode(code.begin(), static_cast(code.size())); builder_.AddExport(CStrVector(name), fun); } MaybeHandle CallExportedFunction(const char* name, int argc, Handle args[]) { Handle func = testing::GetExportedFunction(isolate_, instance_, name) .ToHandleChecked(); return Execution::Call(isolate_, func, isolate_->factory()->undefined_value(), argc, args); } byte DefineStruct(std::initializer_list fields) { StructType::Builder type_builder(&zone, static_cast(fields.size())); for (F field : fields) { type_builder.AddField(field.first, field.second); } return builder_.AddStructType(type_builder.Build()); } byte DefineArray(ValueType element_type, bool mutability) { return builder_.AddArrayType(zone.New(element_type, mutability)); } byte DefineSignature(FunctionSig* sig) { return builder_.AddSignature(sig); } void CompileModule() { ZoneBuffer buffer(&zone); builder_.WriteTo(&buffer); MaybeHandle maybe_instance = testing::CompileAndInstantiateForTesting( isolate_, &thrower, ModuleWireBytes(buffer.begin(), buffer.end())); if (thrower.error()) FATAL("%s", thrower.error_msg()); instance_ = maybe_instance.ToHandleChecked(); } void CallFunctionImpl(uint32_t function_index, const FunctionSig* sig, CWasmArgumentsPacker* packer) { WasmCodeRefScope scope; NativeModule* native_module = instance_->module_object().native_module(); WasmCode* code = native_module->GetCode(function_index); Address wasm_call_target = code->instruction_start(); Handle object_ref = instance_; Handle c_wasm_entry = compiler::CompileCWasmEntry(isolate_, sig, native_module->module()); Execution::CallWasm(isolate_, c_wasm_entry, wasm_call_target, object_ref, packer->argv()); } void CheckResult(uint32_t function_index, int32_t expected) { FunctionSig* sig = sigs.i_v(); DCHECK(*sig == *instance_->module()->functions[function_index].sig); CWasmArgumentsPacker packer(CWasmArgumentsPacker::TotalSize(sig)); CallFunctionImpl(function_index, sig, &packer); packer.Reset(); CHECK_EQ(expected, packer.Pop()); } void CheckResult(uint32_t function_index, int32_t expected, int32_t arg) { FunctionSig* sig = sigs.i_i(); DCHECK(*sig == *instance_->module()->functions[function_index].sig); CWasmArgumentsPacker packer(CWasmArgumentsPacker::TotalSize(sig)); packer.Push(arg); CallFunctionImpl(function_index, sig, &packer); packer.Reset(); CHECK_EQ(expected, packer.Pop()); } MaybeHandle GetResultObject(uint32_t function_index) { const FunctionSig* sig = instance_->module()->functions[function_index].sig; CWasmArgumentsPacker packer(CWasmArgumentsPacker::TotalSize(sig)); CallFunctionImpl(function_index, sig, &packer); packer.Reset(); return Handle(Object(packer.Pop
()), isolate_); } void CheckHasThrown(uint32_t function_index, int32_t arg) { FunctionSig* sig = sigs.i_i(); DCHECK(*sig == *instance_->module()->functions[function_index].sig); CWasmArgumentsPacker packer(CWasmArgumentsPacker::TotalSize(sig)); packer.Push(arg); CallFunctionImpl(function_index, sig, &packer); CHECK(isolate_->has_pending_exception()); isolate_->clear_pending_exception(); } Handle instance() { return instance_; } Isolate* isolate() { return isolate_; } WasmModuleBuilder* builder() { return &builder_; } TestSignatures sigs; private: const FlagScope flag_gc; const FlagScope flag_reftypes; const FlagScope flag_typedfuns; v8::internal::AccountingAllocator allocator; Zone zone; WasmModuleBuilder builder_; Isolate* const isolate_; const HandleScope scope; Handle instance_; ErrorThrower thrower; }; ValueType ref(uint32_t type_index) { return ValueType::Ref(type_index, kNonNullable); } ValueType optref(uint32_t type_index) { return ValueType::Ref(type_index, kNullable); } // TODO(7748): Use WASM_EXEC_TEST once interpreter and liftoff are supported. TEST(WasmBasicStruct) { WasmGCTester tester; const byte type_index = tester.DefineStruct({F(kWasmI32, true), F(kWasmI32, true)}); const byte empty_struct_index = tester.DefineStruct({}); ValueType kRefType = ref(type_index); ValueType kEmptyStructType = ref(empty_struct_index); ValueType kOptRefType = optref(type_index); FunctionSig sig_q_v(1, 0, &kRefType); FunctionSig sig_qe_v(1, 0, &kEmptyStructType); // Test struct.new and struct.get. const byte kGet1 = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_STRUCT_GET( type_index, 0, WASM_STRUCT_NEW_WITH_RTT(type_index, WASM_I32V(42), WASM_I32V(64), WASM_RTT_CANON(type_index))), kExprEnd}); // Test struct.new and struct.get. const byte kGet2 = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_STRUCT_GET( type_index, 1, WASM_STRUCT_NEW_WITH_RTT(type_index, WASM_I32V(42), WASM_I32V(64), WASM_RTT_CANON(type_index))), kExprEnd}); // Test struct.new, returning struct reference. const byte kGetStruct = tester.DefineFunction( &sig_q_v, {}, {WASM_STRUCT_NEW_WITH_RTT(type_index, WASM_I32V(42), WASM_I32V(64), WASM_RTT_CANON(type_index)), kExprEnd}); // Test struct.new, returning reference to an empty struct. const byte kGetEmptyStruct = tester.DefineFunction( &sig_qe_v, {}, {WASM_STRUCT_NEW_WITH_RTT(empty_struct_index, WASM_RTT_CANON(empty_struct_index)), kExprEnd}); // Test struct.set, struct refs types in locals. const byte j_local_index = 0; const byte j_field_index = 0; const byte kSet = tester.DefineFunction( tester.sigs.i_v(), {kOptRefType}, {WASM_SET_LOCAL( j_local_index, WASM_STRUCT_NEW_WITH_RTT(type_index, WASM_I32V(42), WASM_I32V(64), WASM_RTT_CANON(type_index))), WASM_STRUCT_SET(type_index, j_field_index, WASM_GET_LOCAL(j_local_index), WASM_I32V(-99)), WASM_STRUCT_GET(type_index, j_field_index, WASM_GET_LOCAL(j_local_index)), kExprEnd}); tester.CompileModule(); tester.CheckResult(kGet1, 42); tester.CheckResult(kGet2, 64); CHECK(tester.GetResultObject(kGetStruct).ToHandleChecked()->IsWasmStruct()); CHECK(tester.GetResultObject(kGetEmptyStruct) .ToHandleChecked() ->IsWasmStruct()); tester.CheckResult(kSet, -99); } // Test struct.set, ref.as_non_null, // struct refs types in globals and if-results. TEST(WasmRefAsNonNull) { WasmGCTester tester; const byte type_index = tester.DefineStruct({F(kWasmI32, true), F(kWasmI32, true)}); ValueType kRefTypes[] = {ref(type_index)}; ValueType kOptRefType = optref(type_index); FunctionSig sig_q_v(1, 0, kRefTypes); const byte global_index = tester.AddGlobal(kOptRefType, true, WasmInitExpr::RefNullConst( static_cast(type_index))); const byte field_index = 0; const byte kFunc = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_SET_GLOBAL( global_index, WASM_STRUCT_NEW_WITH_RTT(type_index, WASM_I32V(55), WASM_I32V(66), WASM_RTT_CANON(type_index))), WASM_STRUCT_GET( type_index, field_index, WASM_REF_AS_NON_NULL(WASM_IF_ELSE_R(kOptRefType, WASM_I32V(1), WASM_GET_GLOBAL(global_index), WASM_REF_NULL(type_index)))), kExprEnd}); tester.CompileModule(); tester.CheckResult(kFunc, 55); } TEST(WasmBrOnNull) { WasmGCTester tester; const byte type_index = tester.DefineStruct({F(kWasmI32, true), F(kWasmI32, true)}); ValueType kRefTypes[] = {ref(type_index)}; ValueType kOptRefType = optref(type_index); FunctionSig sig_q_v(1, 0, kRefTypes); const byte l_local_index = 0; const byte kTaken = tester.DefineFunction( tester.sigs.i_v(), {kOptRefType}, {WASM_BLOCK_I(WASM_I32V(42), // Branch will be taken. // 42 left on stack outside the block (not 52). WASM_BR_ON_NULL(0, WASM_GET_LOCAL(l_local_index)), WASM_I32V(52), WASM_BR(0)), kExprEnd}); const byte m_field_index = 0; const byte kNotTaken = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_BLOCK_I( WASM_I32V(42), WASM_STRUCT_GET( type_index, m_field_index, // Branch will not be taken. // 52 left on stack outside the block (not 42). WASM_BR_ON_NULL(0, WASM_STRUCT_NEW_WITH_RTT( type_index, WASM_I32V(52), WASM_I32V(62), WASM_RTT_CANON(type_index)))), WASM_BR(0)), kExprEnd}); tester.CompileModule(); tester.CheckResult(kTaken, 42); tester.CheckResult(kNotTaken, 52); } TEST(BrOnCast) { WasmGCTester tester; const byte type_index = tester.DefineStruct({F(kWasmI32, true)}); const byte rtt_index = tester.AddGlobal(ValueType::Rtt(type_index, 1), false, WasmInitExpr::RttCanon( static_cast(type_index))); const byte kTestStruct = tester.DefineFunction( tester.sigs.i_v(), {kWasmI32, kWasmEqRef}, {WASM_BLOCK(WASM_SET_LOCAL(0, WASM_I32V(111)), // Pipe a struct through a local so it's statically typed // as eqref. WASM_SET_LOCAL( 1, WASM_STRUCT_NEW_WITH_RTT(type_index, WASM_I32V(1), WASM_GET_GLOBAL(rtt_index))), WASM_GET_LOCAL(1), // The struct is not an i31, so this branch isn't taken. WASM_BR_ON_CAST(0, WASM_RTT_CANON(kI31RefCode)), WASM_SET_LOCAL(0, WASM_I32V(222)), // Final result. // This branch is taken. WASM_BR_ON_CAST(0, WASM_GET_GLOBAL(rtt_index)), // Not executed due to the branch. WASM_DROP, WASM_SET_LOCAL(0, WASM_I32V(333))), WASM_GET_LOCAL(0), kExprEnd}); const byte kTestI31 = tester.DefineFunction( tester.sigs.i_v(), {kWasmI32, kWasmEqRef}, {WASM_BLOCK(WASM_SET_LOCAL(0, WASM_I32V(111)), // Pipe an i31ref through a local so it's statically typed // as eqref. WASM_SET_LOCAL(1, WASM_I31_NEW(WASM_I32V(42))), WASM_GET_LOCAL(1), // The i31 is not a struct, so this branch isn't taken. WASM_BR_ON_CAST(0, WASM_GET_GLOBAL(rtt_index)), WASM_SET_LOCAL(0, WASM_I32V(222)), // Final result. // This branch is taken. WASM_BR_ON_CAST(0, WASM_RTT_CANON(kI31RefCode)), // Not executed due to the branch. WASM_DROP, WASM_SET_LOCAL(0, WASM_I32V(333))), WASM_GET_LOCAL(0), kExprEnd}); const byte kTestNull = tester.DefineFunction( tester.sigs.i_v(), {kWasmI32, kWasmEqRef}, {WASM_BLOCK(WASM_SET_LOCAL(0, WASM_I32V(111)), WASM_GET_LOCAL(1), // Put a nullref onto the value stack. // Neither of these branches is taken for nullref. WASM_BR_ON_CAST(0, WASM_RTT_CANON(kI31RefCode)), WASM_SET_LOCAL(0, WASM_I32V(222)), WASM_BR_ON_CAST(0, WASM_GET_GLOBAL(rtt_index)), WASM_DROP, WASM_SET_LOCAL(0, WASM_I32V(333))), // Final result. WASM_GET_LOCAL(0), kExprEnd}); const byte kTypedAfterBranch = tester.DefineFunction( tester.sigs.i_v(), {kWasmI32, kWasmEqRef}, {WASM_SET_LOCAL(1, WASM_STRUCT_NEW_WITH_RTT(type_index, WASM_I32V(42), WASM_GET_GLOBAL(rtt_index))), WASM_BLOCK(WASM_SET_LOCAL( // The outer block catches the struct left behind by the inner block // and reads its field. 0, WASM_STRUCT_GET( type_index, 0, // The inner block should take the early branch with a struct // on the stack. WASM_BLOCK_R(ValueType::Ref(type_index, kNonNullable), WASM_GET_LOCAL(1), WASM_BR_ON_CAST(0, WASM_GET_GLOBAL(rtt_index)), // Returning 123 is the unreachable failure case. WASM_SET_LOCAL(0, WASM_I32V(123)), WASM_BR(1))))), WASM_GET_LOCAL(0), kExprEnd}); tester.CompileModule(); tester.CheckResult(kTestStruct, 222); tester.CheckResult(kTestI31, 222); tester.CheckResult(kTestNull, 333); tester.CheckResult(kTypedAfterBranch, 42); } TEST(WasmRefEq) { WasmGCTester tester; byte type_index = tester.DefineStruct({F(kWasmI32, true), F(kWasmI32, true)}); ValueType kRefTypes[] = {ref(type_index)}; ValueType kOptRefType = optref(type_index); FunctionSig sig_q_v(1, 0, kRefTypes); byte local_index = 0; const byte kFunc = tester.DefineFunction( tester.sigs.i_v(), {kOptRefType}, {WASM_SET_LOCAL(local_index, WASM_STRUCT_NEW_WITH_RTT( type_index, WASM_I32V(55), WASM_I32V(66), WASM_RTT_CANON(type_index))), WASM_I32_ADD( WASM_I32_SHL( WASM_REF_EQ( // true WASM_GET_LOCAL(local_index), WASM_GET_LOCAL(local_index)), WASM_I32V(0)), WASM_I32_ADD( WASM_I32_SHL(WASM_REF_EQ( // false WASM_GET_LOCAL(local_index), WASM_STRUCT_NEW_WITH_RTT( type_index, WASM_I32V(55), WASM_I32V(66), WASM_RTT_CANON(type_index))), WASM_I32V(1)), WASM_I32_ADD(WASM_I32_SHL( // false WASM_REF_EQ(WASM_GET_LOCAL(local_index), WASM_REF_NULL(type_index)), WASM_I32V(2)), WASM_I32_SHL(WASM_REF_EQ( // true WASM_REF_NULL(type_index), WASM_REF_NULL(type_index)), WASM_I32V(3))))), kExprEnd}); tester.CompileModule(); tester.CheckResult(kFunc, 0b1001); } TEST(WasmPackedStructU) { WasmGCTester tester; const byte type_index = tester.DefineStruct( {F(kWasmI8, true), F(kWasmI16, true), F(kWasmI32, true)}); ValueType struct_type = optref(type_index); const byte local_index = 0; int32_t expected_output_0 = 0x1234; int32_t expected_output_1 = -1; const byte kF0 = tester.DefineFunction( tester.sigs.i_v(), {struct_type}, {WASM_SET_LOCAL(local_index, WASM_STRUCT_NEW_WITH_RTT( type_index, WASM_I32V(expected_output_0), WASM_I32V(expected_output_1), WASM_I32V(0x12345678), WASM_RTT_CANON(type_index))), WASM_STRUCT_GET_U(type_index, 0, WASM_GET_LOCAL(local_index)), kExprEnd}); const byte kF1 = tester.DefineFunction( tester.sigs.i_v(), {struct_type}, {WASM_SET_LOCAL(local_index, WASM_STRUCT_NEW_WITH_RTT( type_index, WASM_I32V(expected_output_0), WASM_I32V(expected_output_1), WASM_I32V(0x12345678), WASM_RTT_CANON(type_index))), WASM_STRUCT_GET_U(type_index, 1, WASM_GET_LOCAL(local_index)), kExprEnd}); tester.CompileModule(); tester.CheckResult(kF0, static_cast(expected_output_0)); tester.CheckResult(kF1, static_cast(expected_output_1)); } TEST(WasmPackedStructS) { WasmGCTester tester; const byte type_index = tester.DefineStruct( {F(kWasmI8, true), F(kWasmI16, true), F(kWasmI32, true)}); ValueType struct_type = optref(type_index); const byte local_index = 0; int32_t expected_output_0 = 0x80; int32_t expected_output_1 = 42; const byte kF0 = tester.DefineFunction( tester.sigs.i_v(), {struct_type}, {WASM_SET_LOCAL( local_index, WASM_STRUCT_NEW_WITH_RTT(type_index, WASM_I32V(expected_output_0), WASM_I32V(expected_output_1), WASM_I32V(0), WASM_RTT_CANON(type_index))), WASM_STRUCT_GET_S(type_index, 0, WASM_GET_LOCAL(local_index)), kExprEnd}); const byte kF1 = tester.DefineFunction( tester.sigs.i_v(), {struct_type}, {WASM_SET_LOCAL( local_index, WASM_STRUCT_NEW_WITH_RTT(type_index, WASM_I32V(0x80), WASM_I32V(expected_output_1), WASM_I32V(0), WASM_RTT_CANON(type_index))), WASM_STRUCT_GET_S(type_index, 1, WASM_GET_LOCAL(local_index)), kExprEnd}); tester.CompileModule(); tester.CheckResult(kF0, static_cast(expected_output_0)); tester.CheckResult(kF1, static_cast(expected_output_1)); } TEST(WasmLetInstruction) { WasmGCTester tester; const byte type_index = tester.DefineStruct({F(kWasmI32, true), F(kWasmI32, true)}); const byte let_local_index = 0; const byte let_field_index = 0; const byte kLetTest1 = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_LET_1_I( WASM_SEQ(kRefCode, type_index), WASM_STRUCT_NEW_WITH_RTT(type_index, WASM_I32V(42), WASM_I32V(52), WASM_RTT_CANON(type_index)), WASM_STRUCT_GET(type_index, let_field_index, WASM_GET_LOCAL(let_local_index))), kExprEnd}); const byte let_2_field_index = 0; const byte kLetTest2 = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_LET_2_I( kI32Code, WASM_I32_ADD(WASM_I32V(42), WASM_I32V(-32)), WASM_SEQ(kRefCode, type_index), WASM_STRUCT_NEW_WITH_RTT(type_index, WASM_I32V(42), WASM_I32V(52), WASM_RTT_CANON(type_index)), WASM_I32_MUL(WASM_STRUCT_GET(type_index, let_2_field_index, WASM_GET_LOCAL(1)), WASM_GET_LOCAL(0))), kExprEnd}); const byte kLetTestLocals = tester.DefineFunction( tester.sigs.i_i(), {kWasmI32}, {WASM_SET_LOCAL(1, WASM_I32V(100)), WASM_LET_2_I( kI32Code, WASM_I32V(1), kI32Code, WASM_I32V(10), WASM_I32_SUB(WASM_I32_ADD(WASM_GET_LOCAL(0), // 1st let-local WASM_GET_LOCAL(2)), // Parameter WASM_I32_ADD(WASM_GET_LOCAL(1), // 2nd let-local WASM_GET_LOCAL(3)))), // Function local kExprEnd}); // Result: (1 + 1000) - (10 + 100) = 891 const byte let_erase_local_index = 0; const byte kLetTestErase = tester.DefineFunction( tester.sigs.i_v(), {kWasmI32}, {WASM_SET_LOCAL(let_erase_local_index, WASM_I32V(0)), WASM_LET_1_V(kI32Code, WASM_I32V(1), WASM_NOP), WASM_GET_LOCAL(let_erase_local_index), kExprEnd}); // The result should be 0 and not 1, as local_get(0) refers to the original // local. tester.CompileModule(); tester.CheckResult(kLetTest1, 42); tester.CheckResult(kLetTest2, 420); tester.CheckResult(kLetTestLocals, 891, 1000); tester.CheckResult(kLetTestErase, 0); } TEST(WasmBasicArray) { WasmGCTester tester; const byte type_index = tester.DefineArray(wasm::kWasmI32, true); ValueType kRefTypes[] = {ref(type_index)}; FunctionSig sig_q_v(1, 0, kRefTypes); ValueType kOptRefType = optref(type_index); // f: a = [12, 12, 12]; a[1] = 42; return a[arg0] const byte local_index = 1; const byte kGetElem = tester.DefineFunction( tester.sigs.i_i(), {kOptRefType}, {WASM_SET_LOCAL(local_index, WASM_ARRAY_NEW_WITH_RTT( type_index, WASM_I32V(12), WASM_I32V(3), WASM_RTT_CANON(type_index))), WASM_ARRAY_SET(type_index, WASM_GET_LOCAL(local_index), WASM_I32V(1), WASM_I32V(42)), WASM_ARRAY_GET(type_index, WASM_GET_LOCAL(local_index), WASM_GET_LOCAL(0)), kExprEnd}); // Reads and returns an array's length. const byte kGetLength = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_ARRAY_LEN(type_index, WASM_ARRAY_NEW_WITH_RTT( type_index, WASM_I32V(0), WASM_I32V(42), WASM_RTT_CANON(type_index))), kExprEnd}); // Create an array of length 2, initialized to [42, 42]. const byte kAllocate = tester.DefineFunction( &sig_q_v, {}, {WASM_ARRAY_NEW_WITH_RTT(type_index, WASM_I32V(42), WASM_I32V(2), WASM_RTT_CANON(type_index)), kExprEnd}); tester.CompileModule(); tester.CheckResult(kGetElem, 12, 0); tester.CheckResult(kGetElem, 42, 1); tester.CheckResult(kGetElem, 12, 2); tester.CheckHasThrown(kGetElem, 3); tester.CheckHasThrown(kGetElem, -1); tester.CheckResult(kGetLength, 42); MaybeHandle h_result = tester.GetResultObject(kAllocate); CHECK(h_result.ToHandleChecked()->IsWasmArray()); #if OBJECT_PRINT h_result.ToHandleChecked()->Print(); #endif } TEST(WasmPackedArrayU) { WasmGCTester tester; const byte array_index = tester.DefineArray(kWasmI8, true); ValueType array_type = optref(array_index); const byte param_index = 0; const byte local_index = 1; int32_t expected_output_3 = 258; const byte kF = tester.DefineFunction( tester.sigs.i_i(), {array_type}, {WASM_SET_LOCAL(local_index, WASM_ARRAY_NEW_WITH_RTT( array_index, WASM_I32V(0), WASM_I32V(4), WASM_RTT_CANON(array_index))), WASM_ARRAY_SET(array_index, WASM_GET_LOCAL(local_index), WASM_I32V(0), WASM_I32V(1)), WASM_ARRAY_SET(array_index, WASM_GET_LOCAL(local_index), WASM_I32V(1), WASM_I32V(10)), WASM_ARRAY_SET(array_index, WASM_GET_LOCAL(local_index), WASM_I32V(2), WASM_I32V(200)), WASM_ARRAY_SET(array_index, WASM_GET_LOCAL(local_index), WASM_I32V(3), WASM_I32V(expected_output_3)), WASM_ARRAY_GET_U(array_index, WASM_GET_LOCAL(local_index), WASM_GET_LOCAL(param_index)), kExprEnd}); tester.CompileModule(); tester.CheckResult(kF, 1, 0); tester.CheckResult(kF, 10, 1); tester.CheckResult(kF, 200, 2); // Only the 2 lsb's of 258 should be stored in the array. tester.CheckResult(kF, static_cast(expected_output_3), 3); } TEST(WasmPackedArrayS) { WasmGCTester tester; const byte array_index = tester.DefineArray(kWasmI16, true); ValueType array_type = optref(array_index); int32_t expected_outputs[] = {0x12345678, 10, 0xFEDC, 0xFF1234}; const byte param_index = 0; const byte local_index = 1; const byte kF = tester.DefineFunction( tester.sigs.i_i(), {array_type}, {WASM_SET_LOCAL( local_index, WASM_ARRAY_NEW_WITH_RTT(array_index, WASM_I32V(0x12345678), WASM_I32V(4), WASM_RTT_CANON(array_index))), WASM_ARRAY_SET(array_index, WASM_GET_LOCAL(local_index), WASM_I32V(1), WASM_I32V(10)), WASM_ARRAY_SET(array_index, WASM_GET_LOCAL(local_index), WASM_I32V(2), WASM_I32V(0xFEDC)), WASM_ARRAY_SET(array_index, WASM_GET_LOCAL(local_index), WASM_I32V(3), WASM_I32V(0xFF1234)), WASM_ARRAY_GET_S(array_index, WASM_GET_LOCAL(local_index), WASM_GET_LOCAL(param_index)), kExprEnd}); tester.CompileModule(); // Exactly the 2 lsb's should be stored by array.new. tester.CheckResult(kF, static_cast(expected_outputs[0]), 0); tester.CheckResult(kF, static_cast(expected_outputs[1]), 1); // Sign should be extended. tester.CheckResult(kF, static_cast(expected_outputs[2]), 2); // Exactly the 2 lsb's should be stored by array.set. tester.CheckResult(kF, static_cast(expected_outputs[3]), 3); } TEST(NewDefault) { WasmGCTester tester; const byte struct_type = tester.DefineStruct( {F(wasm::kWasmI32, true), F(wasm::kWasmF64, true), F(optref(0), true)}); const byte array_type = tester.DefineArray(wasm::kWasmI32, true); // Returns: struct[0] + f64_to_i32(struct[1]) + (struct[2].is_null ^ 1) == 0. const byte allocate_struct = tester.DefineFunction( tester.sigs.i_v(), {optref(struct_type)}, {WASM_SET_LOCAL(0, WASM_STRUCT_NEW_DEFAULT(struct_type, WASM_RTT_CANON(struct_type))), WASM_I32_ADD( WASM_I32_ADD(WASM_STRUCT_GET(struct_type, 0, WASM_GET_LOCAL(0)), WASM_I32_SCONVERT_F64(WASM_STRUCT_GET( struct_type, 1, WASM_GET_LOCAL(0)))), WASM_I32_XOR(WASM_REF_IS_NULL( WASM_STRUCT_GET(struct_type, 2, WASM_GET_LOCAL(0))), WASM_I32V(1))), kExprEnd}); const byte allocate_array = tester.DefineFunction( tester.sigs.i_v(), {optref(array_type)}, {WASM_SET_LOCAL(0, WASM_ARRAY_NEW_DEFAULT(array_type, WASM_I32V(2), WASM_RTT_CANON(array_type))), WASM_I32_ADD( WASM_ARRAY_GET(array_type, WASM_GET_LOCAL(0), WASM_I32V(0)), WASM_ARRAY_GET(array_type, WASM_GET_LOCAL(0), WASM_I32V(1))), kExprEnd}); tester.CompileModule(); tester.CheckResult(allocate_struct, 0); tester.CheckResult(allocate_array, 0); } TEST(BasicRTT) { WasmGCTester tester; const byte type_index = tester.DefineStruct({F(wasm::kWasmI32, true)}); const byte subtype_index = tester.DefineStruct({F(wasm::kWasmI32, true), F(wasm::kWasmI32, true)}); ValueType kRttTypes[] = {ValueType::Rtt(type_index, 1)}; FunctionSig sig_t_v(1, 0, kRttTypes); ValueType kRttSubtypes[] = { ValueType::Rtt(static_cast(subtype_index), 2)}; FunctionSig sig_t2_v(1, 0, kRttSubtypes); ValueType kRttTypesDeeper[] = {ValueType::Rtt(type_index, 2)}; FunctionSig sig_t3_v(1, 0, kRttTypesDeeper); ValueType kRefTypes[] = {ref(type_index)}; FunctionSig sig_q_v(1, 0, kRefTypes); const byte kRttCanon = tester.DefineFunction( &sig_t_v, {}, {WASM_RTT_CANON(type_index), kExprEnd}); const byte kRttSub = tester.DefineFunction( &sig_t2_v, {}, {WASM_RTT_SUB(subtype_index, WASM_RTT_CANON(type_index)), kExprEnd}); const byte kRttSubGeneric = tester.DefineFunction( &sig_t3_v, {}, {WASM_RTT_SUB(type_index, WASM_RTT_CANON(kEqRefCode)), kExprEnd}); const byte kStructWithRtt = tester.DefineFunction( &sig_q_v, {}, {WASM_STRUCT_NEW_WITH_RTT(type_index, WASM_I32V(42), WASM_RTT_CANON(type_index)), kExprEnd}); const int kFieldIndex = 1; const int kStructIndexCode = 1; // Shifted in 'let' block. const int kRttIndexCode = 0; // Let-bound, hence first local. // This implements the following function: // var local_struct: type0; // let (local_rtt = rtt.sub(rtt.canon(type0), type1) in { // local_struct = new type1 with rtt 'local_rtt'; // return (ref.test local_struct local_rtt) + // ((ref.cast local_struct local_rtt)[field0]); // } // The expected return value is 1+42 = 43. const byte kRefCast = tester.DefineFunction( tester.sigs.i_v(), {optref(type_index)}, {WASM_LET_1_I( WASM_RTT(2, subtype_index), WASM_RTT_SUB(subtype_index, WASM_RTT_CANON(type_index)), WASM_SET_LOCAL(kStructIndexCode, WASM_STRUCT_NEW_WITH_RTT( subtype_index, WASM_I32V(11), WASM_I32V(42), WASM_GET_LOCAL(kRttIndexCode))), WASM_I32_ADD( WASM_REF_TEST(type_index, subtype_index, WASM_GET_LOCAL(kStructIndexCode), WASM_GET_LOCAL(kRttIndexCode)), WASM_STRUCT_GET(subtype_index, kFieldIndex, WASM_REF_CAST(type_index, subtype_index, WASM_GET_LOCAL(kStructIndexCode), WASM_GET_LOCAL(kRttIndexCode)))), kExprEnd)}); tester.CompileModule(); Handle ref_result = tester.GetResultObject(kRttCanon).ToHandleChecked(); CHECK(ref_result->IsMap()); Handle map = Handle::cast(ref_result); CHECK(map->IsWasmStructMap()); CHECK_EQ(reinterpret_cast
( tester.instance()->module()->struct_type(type_index)), map->wasm_type_info().foreign_address()); Handle subref_result = tester.GetResultObject(kRttSub).ToHandleChecked(); CHECK(subref_result->IsMap()); Handle submap = Handle::cast(subref_result); CHECK_EQ(*map, submap->wasm_type_info().parent()); CHECK_EQ(reinterpret_cast
( tester.instance()->module()->struct_type(subtype_index)), submap->wasm_type_info().foreign_address()); Handle subref_result_canonicalized = tester.GetResultObject(kRttSub).ToHandleChecked(); CHECK(subref_result.is_identical_to(subref_result_canonicalized)); Handle sub_generic_1 = tester.GetResultObject(kRttSubGeneric).ToHandleChecked(); Handle sub_generic_2 = tester.GetResultObject(kRttSubGeneric).ToHandleChecked(); CHECK(sub_generic_1.is_identical_to(sub_generic_2)); Handle s = tester.GetResultObject(kStructWithRtt).ToHandleChecked(); CHECK(s->IsWasmStruct()); CHECK_EQ(Handle::cast(s)->map(), *map); tester.CheckResult(kRefCast, 43); } TEST(ArrayNewMap) { WasmGCTester tester; const byte type_index = tester.DefineArray(kWasmI32, true); ValueType array_type = ValueType::Ref(type_index, kNonNullable); FunctionSig sig(1, 0, &array_type); const byte array_new_with_rtt = tester.DefineFunction( &sig, {}, {WASM_ARRAY_NEW_WITH_RTT(type_index, WASM_I32V(10), WASM_I32V(42), WASM_RTT_CANON(type_index)), kExprEnd}); ValueType rtt_type = ValueType::Rtt(type_index, 1); FunctionSig rtt_canon_sig(1, 0, &rtt_type); const byte kRttCanon = tester.DefineFunction( &rtt_canon_sig, {}, {WASM_RTT_CANON(type_index), kExprEnd}); tester.CompileModule(); Handle map = tester.GetResultObject(kRttCanon).ToHandleChecked(); Handle result = tester.GetResultObject(array_new_with_rtt).ToHandleChecked(); CHECK(result->IsWasmArray()); CHECK_EQ(Handle::cast(result)->map(), *map); } TEST(FunctionRefs) { WasmGCTester tester; const byte func_index = tester.DefineFunction(tester.sigs.i_v(), {}, {WASM_I32V(42), kExprEnd}); const byte sig_index = 0; const byte other_sig_index = tester.DefineSignature(tester.sigs.d_d()); // This is just so func_index counts as "declared". tester.AddGlobal(ValueType::Ref(sig_index, kNullable), false, WasmInitExpr::RefFuncConst(func_index)); ValueType func_type = ValueType::Ref(sig_index, kNonNullable); FunctionSig sig_func(1, 0, &func_type); ValueType rtt1 = ValueType::Rtt(sig_index, 1); FunctionSig sig_rtt1(1, 0, &rtt1); const byte rtt_canon = tester.DefineFunction( &sig_rtt1, {}, {WASM_RTT_CANON(sig_index), kExprEnd}); ValueType rtt2 = ValueType::Rtt(sig_index, 2); FunctionSig sig_rtt2(1, 0, &rtt2); const byte rtt_sub = tester.DefineFunction( &sig_rtt2, {}, {WASM_RTT_SUB(sig_index, WASM_RTT_CANON(kFuncRefCode)), kExprEnd}); const byte cast = tester.DefineFunction( &sig_func, {kWasmFuncRef}, {WASM_SET_LOCAL(0, WASM_REF_FUNC(func_index)), WASM_REF_CAST(kFuncRefCode, sig_index, WASM_GET_LOCAL(0), WASM_RTT_CANON(sig_index)), kExprEnd}); const byte cast_reference = tester.DefineFunction( &sig_func, {}, {WASM_REF_FUNC(sig_index), kExprEnd}); const byte test = tester.DefineFunction( tester.sigs.i_v(), {kWasmFuncRef}, {WASM_SET_LOCAL(0, WASM_REF_FUNC(func_index)), WASM_REF_TEST(kFuncRefCode, other_sig_index, WASM_GET_LOCAL(0), WASM_RTT_CANON(other_sig_index)), kExprEnd}); tester.CompileModule(); Handle result_canon = tester.GetResultObject(rtt_canon).ToHandleChecked(); CHECK(result_canon->IsMap()); Handle map_canon = Handle::cast(result_canon); CHECK(map_canon->IsJSFunctionMap()); Handle result_sub = tester.GetResultObject(rtt_sub).ToHandleChecked(); CHECK(result_sub->IsMap()); Handle map_sub = Handle::cast(result_sub); CHECK(map_sub->IsJSFunctionMap()); Handle result_cast = tester.GetResultObject(cast).ToHandleChecked(); CHECK(result_cast->IsJSFunction()); Handle cast_function = Handle::cast(result_cast); Handle result_cast_reference = tester.GetResultObject(cast_reference).ToHandleChecked(); CHECK(result_cast_reference->IsJSFunction()); Handle cast_function_reference = Handle::cast(result_cast_reference); CHECK_EQ(cast_function->code().raw_instruction_start(), cast_function_reference->code().raw_instruction_start()); tester.CheckResult(test, 0); } TEST(CallRef) { WasmGCTester tester; byte callee = tester.DefineFunction( tester.sigs.i_ii(), {}, {WASM_I32_ADD(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1)), kExprEnd}); byte caller = tester.DefineFunction( tester.sigs.i_i(), {}, {WASM_CALL_REF(WASM_REF_FUNC(callee), WASM_I32V(42), WASM_GET_LOCAL(0)), kExprEnd}); // This is just so func_index counts as "declared". tester.AddGlobal(ValueType::Ref(0, kNullable), false, WasmInitExpr::RefFuncConst(callee)); tester.CompileModule(); tester.CheckResult(caller, 47, 5); } TEST(RefTestCastNull) { WasmGCTester tester; byte type_index = tester.DefineStruct({F(wasm::kWasmI32, true)}); const byte kRefTestNull = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_REF_TEST(type_index, type_index, WASM_REF_NULL(type_index), WASM_RTT_CANON(type_index)), kExprEnd}); const byte kRefCastNull = tester.DefineFunction( tester.sigs.i_i(), // Argument and return value ignored {}, {WASM_REF_CAST(type_index, type_index, WASM_REF_NULL(type_index), WASM_RTT_CANON(type_index)), kExprDrop, WASM_I32V(0), kExprEnd}); tester.CompileModule(); tester.CheckResult(kRefTestNull, 0); tester.CheckHasThrown(kRefCastNull, 0); } TEST(BasicI31) { WasmGCTester tester; const byte kSigned = tester.DefineFunction( tester.sigs.i_i(), {}, {WASM_I31_GET_S(WASM_I31_NEW(WASM_GET_LOCAL(0))), kExprEnd}); const byte kUnsigned = tester.DefineFunction( tester.sigs.i_i(), {}, {WASM_I31_GET_U(WASM_I31_NEW(WASM_GET_LOCAL(0))), kExprEnd}); // TODO(7748): Support (rtt.canon i31), and add a test like: // (ref.test (i31.new ...) (rtt.canon i31)). tester.CompileModule(); tester.CheckResult(kSigned, 123, 123); tester.CheckResult(kUnsigned, 123, 123); // Truncation: tester.CheckResult(kSigned, 0x1234, static_cast(0x80001234)); tester.CheckResult(kUnsigned, 0x1234, static_cast(0x80001234)); // Sign/zero extension: tester.CheckResult(kSigned, -1, 0x7FFFFFFF); tester.CheckResult(kUnsigned, 0x7FFFFFFF, 0x7FFFFFFF); } TEST(I31Casts) { WasmGCTester tester; const byte struct_type = tester.DefineStruct({F(wasm::kWasmI32, true)}); const byte i31_rtt = tester.AddGlobal(ValueType::Rtt(HeapType::kI31, 1), false, WasmInitExpr::RttCanon(HeapType::kI31)); const byte struct_rtt = tester.AddGlobal(ValueType::Rtt(struct_type, 1), false, WasmInitExpr::RttCanon( static_cast(struct_type))); // Adds the result of a successful typecheck to the untagged value, i.e. // should return 1 + 42 = 43. const byte kTestAndCastSuccess = tester.DefineFunction( tester.sigs.i_v(), {kWasmEqRef}, {WASM_SET_LOCAL(0, WASM_I31_NEW(WASM_I32V(42))), WASM_I32_ADD(WASM_REF_TEST(kEqRefCode, kI31RefCode, WASM_GET_LOCAL(0), WASM_GET_GLOBAL(i31_rtt)), WASM_I31_GET_S(WASM_REF_CAST(kEqRefCode, kI31RefCode, WASM_GET_LOCAL(0), WASM_GET_GLOBAL(i31_rtt)))), kExprEnd}); // Adds the results of two unsuccessful type checks (an i31ref is not a // struct, nor the other way round). const byte kTestFalse = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_I32_ADD( WASM_REF_TEST(kEqRefCode, kI31RefCode, WASM_STRUCT_NEW_WITH_RTT(struct_type, WASM_I32V(42), WASM_GET_GLOBAL(struct_rtt)), WASM_GET_GLOBAL(i31_rtt)), WASM_REF_TEST(kEqRefCode, struct_type, WASM_I31_NEW(WASM_I32V(23)), WASM_GET_GLOBAL(struct_rtt))), kExprEnd}); // Tries to cast an i31ref to a struct, which should trap. const byte kCastI31ToStruct = tester.DefineFunction( tester.sigs.i_i(), // Argument and return value ignored {}, {WASM_STRUCT_GET( struct_type, 0, WASM_REF_CAST(kEqRefCode, struct_type, WASM_I31_NEW(WASM_I32V(42)), WASM_GET_GLOBAL(struct_rtt))), kExprEnd}); // Tries to cast a struct to i31ref, which should trap. const byte kCastStructToI31 = tester.DefineFunction( tester.sigs.i_i(), // Argument and return value ignored {}, {WASM_I31_GET_S( WASM_REF_CAST(kEqRefCode, kI31RefCode, WASM_STRUCT_NEW_WITH_RTT(struct_type, WASM_I32V(42), WASM_GET_GLOBAL(struct_rtt)), WASM_GET_GLOBAL(i31_rtt))), kExprEnd}); tester.CompileModule(); tester.CheckResult(kTestAndCastSuccess, 43); tester.CheckResult(kTestFalse, 0); tester.CheckHasThrown(kCastI31ToStruct, 0); tester.CheckHasThrown(kCastStructToI31, 0); } TEST(GlobalInitReferencingGlobal) { WasmGCTester tester; const byte from = tester.AddGlobal(kWasmI32, false, WasmInitExpr(42)); const byte to = tester.AddGlobal(kWasmI32, false, WasmInitExpr::GlobalGet(from)); const byte func = tester.DefineFunction(tester.sigs.i_v(), {}, {WASM_GET_GLOBAL(to), kExprEnd}); tester.CompileModule(); tester.CheckResult(func, 42); } TEST(JsAccess) { WasmGCTester tester; const byte type_index = tester.DefineStruct({F(wasm::kWasmI32, true)}); ValueType kRefTypes[] = {ref(type_index)}; ValueType kEqRefTypes[] = {kWasmEqRef}; ValueType kEqToI[] = {kWasmI32, kWasmEqRef}; FunctionSig sig_t_v(1, 0, kRefTypes); FunctionSig sig_q_v(1, 0, kEqRefTypes); FunctionSig sig_i_q(1, 1, kEqToI); tester.DefineExportedFunction( "disallowed", &sig_t_v, {WASM_STRUCT_NEW_WITH_RTT(type_index, WASM_I32V(42), WASM_RTT_CANON(type_index)), kExprEnd}); // Same code, different signature. tester.DefineExportedFunction( "producer", &sig_q_v, {WASM_STRUCT_NEW_WITH_RTT(type_index, WASM_I32V(42), WASM_RTT_CANON(type_index)), kExprEnd}); tester.DefineExportedFunction( "consumer", &sig_i_q, {WASM_STRUCT_GET(type_index, 0, WASM_REF_CAST(kEqRefCode, type_index, WASM_GET_LOCAL(0), WASM_RTT_CANON(type_index))), kExprEnd}); tester.CompileModule(); Isolate* isolate = tester.isolate(); TryCatch try_catch(reinterpret_cast(isolate)); MaybeHandle maybe_result = tester.CallExportedFunction("disallowed", 0, nullptr); CHECK(maybe_result.is_null()); CHECK(try_catch.HasCaught()); try_catch.Reset(); isolate->clear_pending_exception(); maybe_result = tester.CallExportedFunction("producer", 0, nullptr); if (maybe_result.is_null()) { FATAL("Calling 'producer' failed: %s", *v8::String::Utf8Value(reinterpret_cast(isolate), try_catch.Message()->Get())); } { Handle args[] = {maybe_result.ToHandleChecked()}; maybe_result = tester.CallExportedFunction("consumer", 1, args); } if (maybe_result.is_null()) { FATAL("Calling 'consumer' failed: %s", *v8::String::Utf8Value(reinterpret_cast(isolate), try_catch.Message()->Get())); } Handle result = maybe_result.ToHandleChecked(); CHECK(result->IsSmi()); CHECK_EQ(42, Smi::cast(*result).value()); // Calling {consumer} with any other object (e.g. the Smi we just got as // {result}) should trap. { Handle args[] = {result}; maybe_result = tester.CallExportedFunction("consumer", 1, args); } CHECK(maybe_result.is_null()); CHECK(try_catch.HasCaught()); try_catch.Reset(); isolate->clear_pending_exception(); } } // namespace test_gc } // namespace wasm } // namespace internal } // namespace v8