// 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/base/vector.h" #include "src/codegen/signature.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/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: explicit WasmGCTester( TestExecutionTier execution_tier = TestExecutionTier::kTurbofan) : flag_gc(&v8::internal::v8_flags.experimental_wasm_gc, true), flag_typedfuns(&v8::internal::v8_flags.experimental_wasm_typed_funcref, true), flag_liftoff(&v8::internal::v8_flags.liftoff, execution_tier == TestExecutionTier::kLiftoff), flag_liftoff_only(&v8::internal::v8_flags.liftoff_only, execution_tier == TestExecutionTier::kLiftoff), flag_wasm_dynamic_tiering(&v8::internal::v8_flags.wasm_dynamic_tiering, v8::internal::v8_flags.liftoff_only != true), flag_tierup(&v8::internal::v8_flags.wasm_tier_up, false), zone_(&allocator, ZONE_NAME), builder_(&zone_), isolate_(CcTest::InitIsolateOnce()), scope(isolate_), thrower(isolate_, "Test wasm GC") { testing::SetupIsolateForWasmModule(isolate_); } uint8_t AddGlobal(ValueType type, bool mutability, WasmInitExpr init) { return builder_.AddGlobal(type, mutability, init); } uint8_t DefineFunction(FunctionSig* sig, std::initializer_list locals, std::initializer_list code) { return DefineFunctionImpl(builder_.AddFunction(sig), locals, code); } uint8_t DefineFunction(uint32_t sig_index, std::initializer_list locals, std::initializer_list code) { return DefineFunctionImpl(builder_.AddFunction(sig_index), locals, code); } 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(base::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); } uint8_t DefineStruct(std::initializer_list fields, uint32_t supertype = kNoSuperType, bool is_final = false) { 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(), is_final, supertype); } uint8_t DefineArray(ValueType element_type, bool mutability, uint32_t supertype = kNoSuperType, bool is_final = false) { return builder_.AddArrayType(zone_.New(element_type, mutability), is_final, supertype); } uint8_t DefineSignature(FunctionSig* sig, uint32_t supertype = kNoSuperType, bool is_final = false) { return builder_.ForceAddSignature(sig, is_final, supertype); } uint8_t DefineTable(ValueType type, uint32_t min_size, uint32_t max_size) { return builder_.AddTable(type, min_size, max_size); } 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 CheckResult(uint32_t function_index, int32_t expected) { const FunctionSig* sig = sigs.i_v(); DCHECK(*sig == *instance_->module()->functions[function_index].sig); CWasmArgumentsPacker packer(CWasmArgumentsPacker::TotalSize(sig)); CheckResultImpl(function_index, sig, &packer, expected); } void CheckResult(uint32_t function_index, int32_t expected, int32_t arg) { const FunctionSig* sig = sigs.i_i(); DCHECK(*sig == *instance_->module()->functions[function_index].sig); CWasmArgumentsPacker packer(CWasmArgumentsPacker::TotalSize(sig)); packer.Push(arg); CheckResultImpl(function_index, sig, &packer, expected); } MaybeHandle GetResultObject(uint32_t function_index) { const FunctionSig* sig = instance_->module()->functions[function_index].sig; DCHECK_EQ(sig->parameter_count(), 0); DCHECK_EQ(sig->return_count(), 1); CWasmArgumentsPacker packer(CWasmArgumentsPacker::TotalSize(sig)); CallFunctionImpl(function_index, sig, &packer); CHECK(!isolate_->has_pending_exception()); packer.Reset(); return Handle(Object(packer.Pop
()), isolate_); } MaybeHandle GetResultObject(uint32_t function_index, int32_t arg) { const FunctionSig* sig = instance_->module()->functions[function_index].sig; DCHECK_EQ(sig->parameter_count(), 1); DCHECK_EQ(sig->return_count(), 1); DCHECK(sig->parameters()[0] == kWasmI32); CWasmArgumentsPacker packer(CWasmArgumentsPacker::TotalSize(sig)); packer.Push(arg); CallFunctionImpl(function_index, sig, &packer); CHECK(!isolate_->has_pending_exception()); packer.Reset(); return Handle(Object(packer.Pop
()), isolate_); } void CheckHasThrown(uint32_t function_index, const char* expected = "") { const FunctionSig* sig = instance_->module()->functions[function_index].sig; DCHECK_EQ(sig->parameter_count(), 0); CWasmArgumentsPacker packer(CWasmArgumentsPacker::TotalSize(sig)); CheckHasThrownImpl(function_index, sig, &packer, expected); } void CheckHasThrown(uint32_t function_index, int32_t arg, const char* expected = "") { const FunctionSig* sig = instance_->module()->functions[function_index].sig; DCHECK_EQ(sig->parameter_count(), 1); DCHECK(sig->parameters()[0] == kWasmI32); CWasmArgumentsPacker packer(CWasmArgumentsPacker::TotalSize(sig)); packer.Push(arg); CheckHasThrownImpl(function_index, sig, &packer, expected); } bool HasSimdSupport(TestExecutionTier tier) const { #if V8_TARGET_ARCH_X64 || V8_TARGET_ARCH_IA32 // Liftoff does not have a fallback for executing SIMD instructions if // SSE4_1 is not available. if (tier == TestExecutionTier::kLiftoff && !CpuFeatures::IsSupported(SSE4_1)) { return false; } #endif USE(tier); return true; } Handle instance() { return instance_; } Isolate* isolate() { return isolate_; } WasmModuleBuilder* builder() { return &builder_; } Zone* zone() { return &zone_; } TestSignatures sigs; private: const FlagScope flag_gc; const FlagScope flag_typedfuns; const FlagScope flag_liftoff; const FlagScope flag_liftoff_only; const FlagScope flag_wasm_dynamic_tiering; const FlagScope flag_tierup; uint8_t DefineFunctionImpl(WasmFunctionBuilder* fun, std::initializer_list locals, std::initializer_list code) { for (ValueType local : locals) { fun->AddLocal(local); } fun->EmitCode(code.begin(), static_cast(code.size())); return fun->func_index(); } void CheckResultImpl(uint32_t function_index, const FunctionSig* sig, CWasmArgumentsPacker* packer, int32_t expected) { CallFunctionImpl(function_index, sig, packer); if (isolate_->has_pending_exception()) { Handle message = ErrorUtils::ToString(isolate_, handle(isolate_->pending_exception(), isolate_)) .ToHandleChecked(); FATAL("%s", message->ToCString().get()); } packer->Reset(); CHECK_EQ(expected, packer->Pop()); } void CheckHasThrownImpl(uint32_t function_index, const FunctionSig* sig, CWasmArgumentsPacker* packer, const char* expected) { CallFunctionImpl(function_index, sig, packer); CHECK(isolate_->has_pending_exception()); Handle message = ErrorUtils::ToString(isolate_, handle(isolate_->pending_exception(), isolate_)) .ToHandleChecked(); std::string message_str(message->ToCString().get()); CHECK_NE(message_str.find(expected), std::string::npos); isolate_->clear_pending_exception(); } void CallFunctionImpl(uint32_t function_index, const FunctionSig* sig, CWasmArgumentsPacker* packer) { WasmCodeRefScope code_ref_scope; NativeModule* native_module = instance_->module_object().native_module(); Address wasm_call_target = instance_->GetCallTarget(function_index); 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()); } 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); } ValueType refNull(uint32_t type_index) { return ValueType::RefNull(type_index); } WASM_COMPILED_EXEC_TEST(WasmBasicStruct) { WasmGCTester tester(execution_tier); const uint8_t type_index = tester.DefineStruct({F(kWasmI32, true), F(kWasmI32, true)}); const uint8_t empty_struct_index = tester.DefineStruct({}); ValueType kRefType = ref(type_index); ValueType kEmptyStructType = ref(empty_struct_index); ValueType kRefNullType = refNull(type_index); FunctionSig sig_q_v(1, 0, &kRefType); FunctionSig sig_qe_v(1, 0, &kEmptyStructType); // Test struct.new and struct.get. const uint8_t kGet1 = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_STRUCT_GET( type_index, 0, WASM_STRUCT_NEW(type_index, WASM_I32V(42), WASM_I32V(64))), kExprEnd}); // Test struct.new and struct.get. const uint8_t kGet2 = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_STRUCT_GET( type_index, 1, WASM_STRUCT_NEW(type_index, WASM_I32V(42), WASM_I32V(64))), kExprEnd}); // Test struct.new, returning struct reference. const uint8_t kGetStruct = tester.DefineFunction( &sig_q_v, {}, {WASM_STRUCT_NEW(type_index, WASM_I32V(42), WASM_I32V(64)), kExprEnd}); const uint8_t kGetStructNominal = tester.DefineFunction( &sig_q_v, {}, {WASM_STRUCT_NEW_DEFAULT(type_index), WASM_DROP, WASM_STRUCT_NEW(type_index, WASM_I32V(42), WASM_I32V(64)), kExprEnd}); // Test struct.new, returning reference to an empty struct. const uint8_t kGetEmptyStruct = tester.DefineFunction( &sig_qe_v, {}, {WASM_GC_OP(kExprStructNew), empty_struct_index, kExprEnd}); // Test struct.set, struct refs types in locals. const uint8_t j_local_index = 0; const uint8_t j_field_index = 0; const uint8_t kSet = tester.DefineFunction( tester.sigs.i_v(), {kRefNullType}, {WASM_LOCAL_SET(j_local_index, WASM_STRUCT_NEW(type_index, WASM_I32V(42), WASM_I32V(64))), WASM_STRUCT_SET(type_index, j_field_index, WASM_LOCAL_GET(j_local_index), WASM_I32V(-99)), WASM_STRUCT_GET(type_index, j_field_index, WASM_LOCAL_GET(j_local_index)), kExprEnd}); const uint8_t kNullDereference = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_STRUCT_GET(type_index, 0, WASM_REF_NULL(type_index)), kExprEnd}); tester.CompileModule(); tester.CheckResult(kGet1, 42); tester.CheckResult(kGet2, 64); CHECK(tester.GetResultObject(kGetStruct).ToHandleChecked()->IsWasmStruct()); CHECK(tester.GetResultObject(kGetStructNominal) .ToHandleChecked() ->IsWasmStruct()); CHECK(tester.GetResultObject(kGetEmptyStruct) .ToHandleChecked() ->IsWasmStruct()); tester.CheckResult(kSet, -99); tester.CheckHasThrown(kNullDereference); } // Test struct.get, ref.as_non_null and ref-typed globals. WASM_COMPILED_EXEC_TEST(WasmRefAsNonNull) { WasmGCTester tester(execution_tier); const uint8_t type_index = tester.DefineStruct({F(kWasmI32, true), F(kWasmI32, true)}); ValueType kRefTypes[] = {ref(type_index)}; ValueType kRefNullType = refNull(type_index); FunctionSig sig_q_v(1, 0, kRefTypes); const uint8_t global_index = tester.AddGlobal(kRefNullType, true, WasmInitExpr::RefNullConst( static_cast(type_index))); const uint8_t field_index = 0; const uint8_t kNonNull = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_GLOBAL_SET(global_index, WASM_STRUCT_NEW(type_index, WASM_I32V(55), WASM_I32V(66))), WASM_STRUCT_GET(type_index, field_index, WASM_REF_AS_NON_NULL(WASM_GLOBAL_GET(global_index))), kExprEnd}); const uint8_t kNull = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_GLOBAL_SET(global_index, WASM_REF_NULL(type_index)), WASM_STRUCT_GET(type_index, field_index, WASM_REF_AS_NON_NULL(WASM_GLOBAL_GET(global_index))), kExprEnd}); tester.CompileModule(); tester.CheckResult(kNonNull, 55); tester.CheckHasThrown(kNull); } WASM_COMPILED_EXEC_TEST(WasmRefAsNonNullSkipCheck) { FlagScope no_check(&v8_flags.experimental_wasm_skip_null_checks, true); WasmGCTester tester(execution_tier); const uint8_t type_index = tester.DefineStruct({F(kWasmI32, true), F(kWasmI32, true)}); ValueType kRefType = ref(type_index); FunctionSig sig_q_v(1, 0, &kRefType); const uint8_t global_index = tester.AddGlobal(refNull(type_index), true, WasmInitExpr::RefNullConst( static_cast(type_index))); const uint8_t kFunc = tester.DefineFunction( &sig_q_v, {}, {WASM_GLOBAL_SET(global_index, WASM_REF_NULL(type_index)), WASM_REF_AS_NON_NULL(WASM_GLOBAL_GET(global_index)), kExprEnd}); tester.CompileModule(); Handle result = tester.GetResultObject(kFunc).ToHandleChecked(); // Without null checks, ref.as_non_null can actually return null. CHECK(result->IsWasmNull()); } WASM_COMPILED_EXEC_TEST(WasmBrOnNull) { WasmGCTester tester(execution_tier); const uint8_t type_index = tester.DefineStruct({F(kWasmI32, true), F(kWasmI32, true)}); ValueType kRefTypes[] = {ref(type_index)}; ValueType kRefNullType = refNull(type_index); FunctionSig sig_q_v(1, 0, kRefTypes); const uint8_t local_index = 0; const uint8_t kTaken = tester.DefineFunction( tester.sigs.i_v(), {kRefNullType}, {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_LOCAL_GET(local_index)), WASM_I32V(52), WASM_BR(0)), kExprEnd}); const uint8_t field_index = 0; const uint8_t kNotTaken = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_BLOCK_I( WASM_I32V(42), WASM_STRUCT_GET( type_index, field_index, // Branch will not be taken. // 52 left on stack outside the block (not 42). WASM_BR_ON_NULL(0, WASM_STRUCT_NEW(type_index, WASM_I32V(52), WASM_I32V(62)))), WASM_BR(0)), kExprEnd}); tester.CompileModule(); tester.CheckResult(kTaken, 42); tester.CheckResult(kNotTaken, 52); } WASM_COMPILED_EXEC_TEST(WasmBrOnNonNull) { WasmGCTester tester(execution_tier); const uint8_t type_index = tester.DefineStruct({F(kWasmI32, true), F(kWasmI32, true)}); ValueType kRefType = ref(type_index); ValueType kRefNullType = refNull(type_index); FunctionSig sig_q_v(1, 0, &kRefType); const uint8_t field_index = 0; const uint8_t kTaken = tester.DefineFunction( tester.sigs.i_v(), {kRefNullType, kRefNullType}, {WASM_LOCAL_SET( 0, WASM_STRUCT_NEW(type_index, WASM_I32V(52), WASM_I32V(62))), WASM_LOCAL_SET( 1, WASM_STRUCT_NEW(type_index, WASM_I32V(11), WASM_I32V(22))), WASM_STRUCT_GET(type_index, field_index, WASM_BLOCK_R(ref(type_index), // Branch will be taken, and the block will // return struct(52, 62). WASM_BR_ON_NON_NULL(0, WASM_LOCAL_GET(0)), WASM_REF_AS_NON_NULL(WASM_LOCAL_GET(1)))), kExprEnd}); const uint8_t kNotTaken = tester.DefineFunction( tester.sigs.i_v(), {kRefNullType, kRefNullType}, {WASM_LOCAL_SET(0, WASM_REF_NULL(type_index)), WASM_LOCAL_SET( 1, WASM_STRUCT_NEW(type_index, WASM_I32V(11), WASM_I32V(22))), WASM_STRUCT_GET(type_index, field_index, WASM_BLOCK_R(ref(type_index), // Branch will not be taken, and the block // will return struct(11, 22). WASM_BR_ON_NON_NULL(0, WASM_LOCAL_GET(0)), WASM_REF_AS_NON_NULL(WASM_LOCAL_GET(1)))), kExprEnd}); tester.CompileModule(); tester.CheckResult(kTaken, 52); tester.CheckResult(kNotTaken, 11); } WASM_COMPILED_EXEC_TEST(RefCast) { WasmGCTester tester(execution_tier); const uint8_t supertype_index = tester.DefineStruct({F(kWasmI32, true)}); const uint8_t subtype1_index = tester.DefineStruct( {F(kWasmI32, true), F(kWasmF32, false)}, supertype_index); const uint8_t subtype2_index = tester.DefineStruct( {F(kWasmI32, true), F(kWasmI64, false)}, supertype_index); auto super_sig = FixedSizeSignature::Params(ValueType::RefNull(subtype1_index)) .Returns(ValueType::RefNull(supertype_index)); auto sub_sig1 = FixedSizeSignature::Params(ValueType::RefNull(supertype_index)) .Returns(ValueType::RefNull(subtype1_index)); auto sub_sig2 = FixedSizeSignature::Params(ValueType::RefNull(supertype_index)) .Returns(ValueType::RefNull(subtype2_index)); const uint8_t function_type_index = tester.DefineSignature(&super_sig); const uint8_t function_subtype1_index = tester.DefineSignature(&sub_sig1, function_type_index); const uint8_t function_subtype2_index = tester.DefineSignature(&sub_sig2, function_type_index); const uint8_t function_index = tester.DefineFunction( function_subtype1_index, {}, {WASM_STRUCT_NEW(subtype1_index, WASM_I32V(10), WASM_F32(20)), WASM_END}); // Just so this function counts as "declared". tester.AddGlobal(ValueType::RefNull(function_type_index), false, WasmInitExpr::RefFuncConst(function_index)); const uint8_t kTestSuccessful = tester.DefineFunction( tester.sigs.i_v(), {ValueType::RefNull(supertype_index)}, {WASM_LOCAL_SET( 0, WASM_STRUCT_NEW(subtype1_index, WASM_I32V(10), WASM_F32(20))), WASM_STRUCT_GET(subtype1_index, 0, WASM_REF_CAST(WASM_LOCAL_GET(0), subtype1_index)), WASM_END}); const uint8_t kTestFailed = tester.DefineFunction( tester.sigs.i_v(), {ValueType::RefNull(supertype_index)}, {WASM_LOCAL_SET( 0, WASM_STRUCT_NEW(subtype1_index, WASM_I32V(10), WASM_F32(20))), WASM_STRUCT_GET(subtype2_index, 0, WASM_REF_CAST(WASM_LOCAL_GET(0), subtype2_index)), WASM_END}); const uint8_t kFuncTestSuccessfulSuper = tester.DefineFunction( tester.sigs.i_v(), {ValueType::RefNull(function_type_index)}, {WASM_LOCAL_SET(0, WASM_REF_FUNC(function_index)), WASM_REF_CAST(WASM_LOCAL_GET(0), function_type_index), WASM_DROP, WASM_I32V(0), WASM_END}); const uint8_t kFuncTestSuccessfulSub = tester.DefineFunction( tester.sigs.i_v(), {ValueType::RefNull(function_type_index)}, {WASM_LOCAL_SET(0, WASM_REF_FUNC(function_index)), WASM_REF_CAST(WASM_LOCAL_GET(0), function_subtype1_index), WASM_DROP, WASM_I32V(0), WASM_END}); const uint8_t kFuncTestFailed = tester.DefineFunction( tester.sigs.i_v(), {ValueType::RefNull(function_type_index)}, {WASM_LOCAL_SET(0, WASM_REF_FUNC(function_index)), WASM_REF_CAST(WASM_LOCAL_GET(0), function_subtype2_index), WASM_DROP, WASM_I32V(1), WASM_END}); tester.CompileModule(); tester.CheckResult(kTestSuccessful, 10); tester.CheckHasThrown(kTestFailed); tester.CheckResult(kFuncTestSuccessfulSuper, 0); tester.CheckResult(kFuncTestSuccessfulSub, 0); tester.CheckHasThrown(kFuncTestFailed); } WASM_COMPILED_EXEC_TEST(RefCastNoChecks) { FlagScope scope(&v8_flags.experimental_wasm_assume_ref_cast_succeeds, true); WasmGCTester tester(execution_tier); const uint8_t supertype_index = tester.DefineStruct({F(kWasmI32, true)}); const uint8_t subtype1_index = tester.DefineStruct( {F(kWasmI32, true), F(kWasmF32, true)}, supertype_index); const uint8_t kTestSuccessful = tester.DefineFunction( tester.sigs.i_v(), {ValueType::RefNull(supertype_index)}, {WASM_LOCAL_SET(0, WASM_STRUCT_NEW_DEFAULT(subtype1_index)), WASM_STRUCT_GET(subtype1_index, 0, WASM_REF_CAST(WASM_LOCAL_GET(0), subtype1_index)), WASM_END}); tester.CompileModule(); tester.CheckResult(kTestSuccessful, 0); } WASM_COMPILED_EXEC_TEST(BrOnCast) { WasmGCTester tester(execution_tier); const uint8_t type_index = tester.DefineStruct({F(kWasmI32, true)}); const uint8_t other_type_index = tester.DefineStruct({F(kWasmF32, true)}); const uint8_t kTestStructStatic = tester.DefineFunction( tester.sigs.i_v(), {kWasmI32, kWasmStructRef}, {WASM_BLOCK_R( ValueType::RefNull(type_index), WASM_LOCAL_SET(0, WASM_I32V(111)), // Pipe a struct through a local so it's statically typed // as structref. WASM_LOCAL_SET(1, WASM_STRUCT_NEW(other_type_index, WASM_F32(1.0))), WASM_LOCAL_GET(1), // The type check fails, so this branch isn't taken. WASM_BR_ON_CAST(0, kStructRefCode, type_index), WASM_DROP, WASM_LOCAL_SET(0, WASM_I32V(221)), // (Final result) - 1 WASM_LOCAL_SET(1, WASM_STRUCT_NEW(type_index, WASM_I32V(1))), WASM_LOCAL_GET(1), // This branch is taken. WASM_BR_ON_CAST(0, kStructRefCode, type_index), WASM_GC_OP(kExprRefCast), type_index, // Not executed due to the branch. WASM_LOCAL_SET(0, WASM_I32V(333))), WASM_GC_OP(kExprStructGet), type_index, 0, WASM_LOCAL_GET(0), kExprI32Add, kExprEnd}); const uint8_t kTestStructStaticNull = tester.DefineFunction( tester.sigs.i_v(), {kWasmI32, kWasmStructRef}, {WASM_BLOCK_R( ValueType::RefNull(type_index), WASM_LOCAL_SET(0, WASM_I32V(111)), // Pipe a struct through a local so it's statically typed as // structref. WASM_LOCAL_SET(1, WASM_STRUCT_NEW(other_type_index, WASM_F32(1.0))), WASM_LOCAL_GET(1), // The type check fails, so this branch isn't taken. WASM_BR_ON_CAST(0, kStructRefCode, type_index), WASM_DROP, WASM_LOCAL_SET(0, WASM_I32V(221)), // (Final result) - 1 WASM_LOCAL_SET(1, WASM_STRUCT_NEW(type_index, WASM_I32V(1))), WASM_LOCAL_GET(1), // This branch is taken. WASM_BR_ON_CAST_NULL(0, kStructRefCode, type_index), WASM_GC_OP(kExprRefCast), type_index, // Not executed due to the branch. WASM_LOCAL_SET(0, WASM_I32V(333))), WASM_GC_OP(kExprStructGet), type_index, 0, WASM_LOCAL_GET(0), kExprI32Add, kExprEnd}); const uint8_t kTestNullDeprecated = tester.DefineFunction( tester.sigs.i_v(), {kWasmI32, kWasmStructRef}, {WASM_BLOCK_R(ValueType::RefNull(type_index), WASM_LOCAL_SET(0, WASM_I32V(111)), WASM_LOCAL_GET(1), // Put a nullref onto the value stack. // Not taken for nullref. WASM_BR_ON_CAST(0, kStructRefCode, type_index), WASM_GC_OP(kExprRefCastDeprecated), type_index, WASM_LOCAL_SET(0, WASM_I32V(222))), // Final result. WASM_DROP, WASM_LOCAL_GET(0), kExprEnd}); const uint8_t kTestNull = tester.DefineFunction( tester.sigs.i_v(), {kWasmI32, kWasmStructRef}, {WASM_BLOCK_R(ValueType::RefNull(type_index), WASM_LOCAL_SET(0, WASM_I32V(111)), WASM_LOCAL_GET(1), // Put a nullref onto the value stack. // Not taken for nullref. WASM_BR_ON_CAST(0, kStructRefCode, type_index), WASM_GC_OP(kExprRefCast), type_index), // Traps WASM_DROP, WASM_LOCAL_GET(0), kExprEnd}); // "br_on_cast null" also branches on null, treating it as a successful cast. const uint8_t kTestNullNull = tester.DefineFunction( tester.sigs.i_v(), {kWasmI32, kWasmStructRef}, {WASM_BLOCK_R(ValueType::RefNull(type_index), WASM_LOCAL_SET(0, WASM_I32V(111)), WASM_LOCAL_GET(1), // Put a nullref onto the value stack. // Taken for nullref with br_on_cast null. WASM_BR_ON_CAST_NULL(0, kStructRefCode, type_index), WASM_GC_OP(kExprRefCast), type_index), WASM_DROP, WASM_LOCAL_GET(0), kExprEnd}); const uint8_t kTypedAfterBranch = tester.DefineFunction( tester.sigs.i_v(), {kWasmI32, kWasmStructRef}, {WASM_LOCAL_SET(1, WASM_STRUCT_NEW(type_index, WASM_I32V(42))), WASM_BLOCK_I( // The inner block should take the early branch with a struct // on the stack. WASM_BLOCK_R(ValueType::Ref(type_index), WASM_LOCAL_GET(1), WASM_BR_ON_CAST(0, kStructRefCode, type_index), // Returning 123 is the unreachable failure case. WASM_I32V(123), WASM_BR(1)), // The outer block catches the struct left behind by the inner block // and reads its field. WASM_GC_OP(kExprStructGet), type_index, 0), kExprEnd}); tester.CompileModule(); tester.CheckResult(kTestStructStatic, 222); tester.CheckResult(kTestStructStaticNull, 222); tester.CheckResult(kTestNullDeprecated, 222); tester.CheckHasThrown(kTestNull); tester.CheckResult(kTestNullNull, 111); tester.CheckResult(kTypedAfterBranch, 42); } WASM_COMPILED_EXEC_TEST(BrOnCastFail) { WasmGCTester tester(execution_tier); const uint8_t type0 = tester.DefineStruct({F(kWasmI32, true)}); const uint8_t type1 = tester.DefineStruct({F(kWasmI64, true), F(kWasmI32, true)}); const int field0_value = 5; const int field1_value = 25; const int null_value = 45; // local_0 = value; // if (!(local_0 instanceof type0)) goto block1; // return static_cast(local_0).field_0; // block1: // if (local_0 == nullptr) goto block2; // return static_cast(local_0).field_1; // block2: // return null_value; #define FUNCTION_BODY(value) \ WASM_LOCAL_SET(0, WASM_SEQ(value)), \ WASM_BLOCK( \ WASM_BLOCK_R(kWasmStructRef, WASM_LOCAL_GET(0), \ WASM_BR_ON_CAST_FAIL(0, kStructRefCode, type0), \ WASM_GC_OP(kExprStructGet), type0, 0, kExprReturn), \ kExprBrOnNull, 0, WASM_GC_OP(kExprRefCast), type1, \ WASM_GC_OP(kExprStructGet), type1, 1, kExprReturn), \ WASM_I32V(null_value), kExprEnd const uint8_t kBranchTaken = tester.DefineFunction( tester.sigs.i_v(), {kWasmStructRef}, {FUNCTION_BODY( WASM_STRUCT_NEW(type1, WASM_I64V(10), WASM_I32V(field1_value)))}); const uint8_t kBranchNotTaken = tester.DefineFunction( tester.sigs.i_v(), {kWasmStructRef}, {FUNCTION_BODY(WASM_STRUCT_NEW(type0, WASM_I32V(field0_value)))}); const uint8_t kNull = tester.DefineFunction(tester.sigs.i_v(), {kWasmStructRef}, {FUNCTION_BODY(WASM_REF_NULL(type0))}); const uint8_t kUnrelatedTypes = tester.DefineFunction( tester.sigs.i_v(), {ValueType::RefNull(type1)}, {FUNCTION_BODY( WASM_STRUCT_NEW(type1, WASM_I64V(10), WASM_I32V(field1_value)))}); #undef FUNCTION_BODY const uint8_t kBranchTakenStatic = tester.DefineFunction( tester.sigs.i_v(), {kWasmStructRef}, {WASM_LOCAL_SET( 0, WASM_STRUCT_NEW(type1, WASM_I64V(10), WASM_I32V(field1_value))), WASM_BLOCK( WASM_BLOCK_R(kWasmStructRef, WASM_LOCAL_GET(0), WASM_BR_ON_CAST_FAIL(0, kStructRefCode, type0), WASM_GC_OP(kExprStructGet), type0, 0, kExprReturn), kExprBrOnNull, 0, WASM_GC_OP(kExprRefCast), type1, WASM_GC_OP(kExprStructGet), type1, 1, kExprReturn), WASM_I32V(null_value), kExprEnd}); tester.CompileModule(); tester.CheckResult(kBranchTaken, field1_value); tester.CheckResult(kBranchTakenStatic, field1_value); tester.CheckResult(kBranchNotTaken, field0_value); tester.CheckResult(kNull, null_value); tester.CheckResult(kUnrelatedTypes, field1_value); } WASM_COMPILED_EXEC_TEST(WasmRefEq) { WasmGCTester tester(execution_tier); uint8_t type_index = tester.DefineStruct({F(kWasmI32, true), F(kWasmI32, true)}); ValueType kRefTypes[] = {ref(type_index)}; ValueType kRefNullType = refNull(type_index); FunctionSig sig_q_v(1, 0, kRefTypes); uint8_t local_index = 0; const uint8_t kFunc = tester.DefineFunction( tester.sigs.i_v(), {kRefNullType}, {WASM_LOCAL_SET(local_index, WASM_STRUCT_NEW(type_index, WASM_I32V(55), WASM_I32V(66))), WASM_I32_ADD( WASM_I32_SHL( WASM_REF_EQ( // true WASM_LOCAL_GET(local_index), WASM_LOCAL_GET(local_index)), WASM_I32V(0)), WASM_I32_ADD( WASM_I32_SHL(WASM_REF_EQ( // false WASM_LOCAL_GET(local_index), WASM_STRUCT_NEW(type_index, WASM_I32V(55), WASM_I32V(66))), WASM_I32V(1)), WASM_I32_ADD(WASM_I32_SHL( // false WASM_REF_EQ(WASM_LOCAL_GET(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); } WASM_COMPILED_EXEC_TEST(WasmPackedStructU) { WasmGCTester tester(execution_tier); const uint8_t type_index = tester.DefineStruct( {F(kWasmI8, true), F(kWasmI16, true), F(kWasmI32, true)}); ValueType struct_type = refNull(type_index); const uint8_t local_index = 0; int32_t expected_output_0 = 0x1234; int32_t expected_output_1 = -1; const uint8_t kF0 = tester.DefineFunction( tester.sigs.i_v(), {struct_type}, {WASM_LOCAL_SET(local_index, WASM_STRUCT_NEW(type_index, WASM_I32V(expected_output_0), WASM_I32V(expected_output_1), WASM_I32V(0x12345678))), WASM_STRUCT_GET_U(type_index, 0, WASM_LOCAL_GET(local_index)), kExprEnd}); const uint8_t kF1 = tester.DefineFunction( tester.sigs.i_v(), {struct_type}, {WASM_LOCAL_SET(local_index, WASM_STRUCT_NEW(type_index, WASM_I32V(expected_output_0), WASM_I32V(expected_output_1), WASM_I32V(0x12345678))), WASM_STRUCT_GET_U(type_index, 1, WASM_LOCAL_GET(local_index)), kExprEnd}); tester.CompileModule(); tester.CheckResult(kF0, static_cast(expected_output_0)); tester.CheckResult(kF1, static_cast(expected_output_1)); } WASM_COMPILED_EXEC_TEST(WasmPackedStructS) { WasmGCTester tester(execution_tier); const uint8_t type_index = tester.DefineStruct( {F(kWasmI8, true), F(kWasmI16, true), F(kWasmI32, true)}); ValueType struct_type = refNull(type_index); const uint8_t local_index = 0; int32_t expected_output_0 = 0x80; int32_t expected_output_1 = 42; const uint8_t kF0 = tester.DefineFunction( tester.sigs.i_v(), {struct_type}, {WASM_LOCAL_SET( local_index, WASM_STRUCT_NEW(type_index, WASM_I32V(expected_output_0), WASM_I32V(expected_output_1), WASM_I32V(0))), WASM_STRUCT_GET_S(type_index, 0, WASM_LOCAL_GET(local_index)), kExprEnd}); const uint8_t kF1 = tester.DefineFunction( tester.sigs.i_v(), {struct_type}, {WASM_LOCAL_SET(local_index, WASM_STRUCT_NEW(type_index, WASM_I32V(0x80), WASM_I32V(expected_output_1), WASM_I32V(0))), WASM_STRUCT_GET_S(type_index, 1, WASM_LOCAL_GET(local_index)), kExprEnd}); tester.CompileModule(); tester.CheckResult(kF0, static_cast(expected_output_0)); tester.CheckResult(kF1, static_cast(expected_output_1)); } WASM_COMPILED_EXEC_TEST(WasmBasicArray) { WasmGCTester tester(execution_tier); if (!tester.HasSimdSupport(execution_tier)) return; const uint8_t type_index = tester.DefineArray(wasm::kWasmI32, true); const uint8_t fp_type_index = tester.DefineArray(wasm::kWasmF64, true); const uint8_t immut_type_index = tester.DefineArray(wasm::kWasmI32, false); ValueType kRefTypes[] = {ref(type_index)}; FunctionSig sig_q_v(1, 0, kRefTypes); ValueType kRefNullType = refNull(type_index); // f: a = [12, 12, 12]; a[1] = 42; return a[arg0] const uint8_t local_index = 1; const uint8_t kGetElem = tester.DefineFunction( tester.sigs.i_i(), {kRefNullType}, {WASM_LOCAL_SET(local_index, WASM_ARRAY_NEW(type_index, WASM_I32V(12), WASM_I32V(3))), WASM_ARRAY_SET(type_index, WASM_LOCAL_GET(local_index), WASM_I32V(1), WASM_I32V(42)), WASM_ARRAY_GET(type_index, WASM_LOCAL_GET(local_index), WASM_LOCAL_GET(0)), kExprEnd}); // Reads and returns an array's length. const uint8_t kGetLength = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_ARRAY_LEN(WASM_ARRAY_NEW(type_index, WASM_I32V(0), WASM_I32V(42))), kExprEnd}); // Create an array of length 2, initialized to [42, 42]. const uint8_t kAllocate = tester.DefineFunction( &sig_q_v, {}, {WASM_ARRAY_NEW(type_index, WASM_I32V(42), WASM_I32V(2)), kExprEnd}); const uint8_t kAllocateStatic = tester.DefineFunction( &sig_q_v, {}, {WASM_ARRAY_NEW_DEFAULT(type_index, WASM_I32V(2)), WASM_DROP, WASM_ARRAY_NEW(type_index, WASM_I32V(42), WASM_I32V(2)), kExprEnd}); const uint8_t kInit = tester.DefineFunction(&sig_q_v, {}, {WASM_ARRAY_NEW_FIXED(type_index, 3, WASM_I32V(10), WASM_I32V(20), WASM_I32V(30)), kExprEnd}); const uint8_t kImmutable = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_ARRAY_GET(immut_type_index, WASM_ARRAY_NEW_FIXED(immut_type_index, 2, WASM_I32V(42), WASM_I32V(43)), WASM_I32V(0)), kExprEnd}); const uint32_t kLongLength = 1u << 16; const uint8_t kAllocateLarge = tester.DefineFunction( &sig_q_v, {}, {WASM_ARRAY_NEW_DEFAULT(type_index, WASM_I32V(kLongLength)), kExprEnd}); ArrayType array_type(kWasmI32, true); const uint32_t kTooLong = WasmArray::MaxLength(&array_type) + 1; const uint8_t kAllocateTooLarge = tester.DefineFunction( &sig_q_v, {}, {WASM_ARRAY_NEW_DEFAULT(type_index, WASM_I32V(kTooLong)), kExprEnd}); // Tests that fp arrays work properly. // f: a = [10.0, 10.0, 10.0]; a[1] = 42.42; return static_cast(a[1]); double result_value = 42.42; const uint8_t kTestFpArray = tester.DefineFunction( tester.sigs.i_v(), {refNull(fp_type_index)}, {WASM_LOCAL_SET( 0, WASM_ARRAY_NEW(fp_type_index, WASM_F64(10.0), WASM_I32V(3))), WASM_ARRAY_SET(fp_type_index, WASM_LOCAL_GET(0), WASM_I32V(1), WASM_F64(result_value)), WASM_I32_SCONVERT_F64( WASM_ARRAY_GET(fp_type_index, WASM_LOCAL_GET(0), WASM_I32V(1))), 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); tester.CheckResult(kImmutable, 42); tester.CheckResult(kTestFpArray, static_cast(result_value)); Handle h_result = tester.GetResultObject(kAllocate).ToHandleChecked(); CHECK(h_result->IsWasmArray()); CHECK_EQ(2, Handle::cast(h_result)->length()); h_result = tester.GetResultObject(kAllocateStatic).ToHandleChecked(); CHECK(h_result->IsWasmArray()); CHECK_EQ(2, Handle::cast(h_result)->length()); Handle init_result = tester.GetResultObject(kInit).ToHandleChecked(); CHECK(init_result->IsWasmArray()); CHECK_EQ(3, Handle::cast(init_result)->length()); CHECK_EQ(10, Handle::cast(init_result)->GetElement(0).to_i32()); CHECK_EQ(20, Handle::cast(init_result)->GetElement(1).to_i32()); CHECK_EQ(30, Handle::cast(init_result)->GetElement(2).to_i32()); MaybeHandle maybe_large_result = tester.GetResultObject(kAllocateLarge); Handle large_result = maybe_large_result.ToHandleChecked(); CHECK(large_result->IsWasmArray()); CHECK(Handle::cast(large_result)->Size() > kMaxRegularHeapObjectSize); tester.CheckHasThrown(kAllocateTooLarge, "requested new array is too large"); } WASM_COMPILED_EXEC_TEST(WasmPackedArrayU) { WasmGCTester tester(execution_tier); const uint8_t array_index = tester.DefineArray(kWasmI8, true); ValueType array_type = refNull(array_index); const uint8_t param_index = 0; const uint8_t local_index = 1; int32_t expected_output_3 = 258; const uint8_t kF = tester.DefineFunction( tester.sigs.i_i(), {array_type}, {WASM_LOCAL_SET(local_index, WASM_ARRAY_NEW(array_index, WASM_I32V(0), WASM_I32V(4))), WASM_ARRAY_SET(array_index, WASM_LOCAL_GET(local_index), WASM_I32V(0), WASM_I32V(1)), WASM_ARRAY_SET(array_index, WASM_LOCAL_GET(local_index), WASM_I32V(1), WASM_I32V(10)), WASM_ARRAY_SET(array_index, WASM_LOCAL_GET(local_index), WASM_I32V(2), WASM_I32V(200)), WASM_ARRAY_SET(array_index, WASM_LOCAL_GET(local_index), WASM_I32V(3), WASM_I32V(expected_output_3)), WASM_ARRAY_GET_U(array_index, WASM_LOCAL_GET(local_index), WASM_LOCAL_GET(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); } WASM_COMPILED_EXEC_TEST(WasmPackedArrayS) { WasmGCTester tester(execution_tier); const uint8_t array_index = tester.DefineArray(kWasmI16, true); ValueType array_type = refNull(array_index); int32_t array_elements[] = {0x12345678, 10, 0xFEDC, 0xFF1234}; const uint8_t param_index = 0; const uint8_t local_index = 1; const uint8_t kF = tester.DefineFunction( tester.sigs.i_i(), {array_type}, {WASM_LOCAL_SET(local_index, WASM_ARRAY_NEW(array_index, WASM_I32V(array_elements[0]), WASM_I32V(4))), WASM_ARRAY_SET(array_index, WASM_LOCAL_GET(local_index), WASM_I32V(1), WASM_I32V(array_elements[1])), WASM_ARRAY_SET(array_index, WASM_LOCAL_GET(local_index), WASM_I32V(2), WASM_I32V(array_elements[2])), WASM_ARRAY_SET(array_index, WASM_LOCAL_GET(local_index), WASM_I32V(3), WASM_I32V(array_elements[3])), WASM_ARRAY_GET_S(array_index, WASM_LOCAL_GET(local_index), WASM_LOCAL_GET(param_index)), kExprEnd}); tester.CompileModule(); // Exactly the 2 lsb's should be stored by array.new. tester.CheckResult(kF, static_cast(array_elements[0]), 0); tester.CheckResult(kF, static_cast(array_elements[1]), 1); // Sign should be extended. tester.CheckResult(kF, static_cast(array_elements[2]), 2); // Exactly the 2 lsb's should be stored by array.set. tester.CheckResult(kF, static_cast(array_elements[3]), 3); } WASM_COMPILED_EXEC_TEST(WasmArrayCopy) { WasmGCTester tester(execution_tier); const uint8_t array32_index = tester.DefineArray(kWasmI32, true); const uint8_t array16_index = tester.DefineArray(kWasmI16, true); const uint8_t arrayref_index = tester.DefineArray(refNull(array32_index), true); // Copies i32 ranges: local1[0..3] to local2[6..9]. const uint8_t kCopyI32 = tester.DefineFunction( tester.sigs.i_i(), {refNull(array32_index), refNull(array32_index)}, {WASM_LOCAL_SET(1, WASM_ARRAY_NEW_DEFAULT(array32_index, WASM_I32V(10))), WASM_ARRAY_SET(array32_index, WASM_LOCAL_GET(1), WASM_I32V(0), WASM_I32V(0)), WASM_ARRAY_SET(array32_index, WASM_LOCAL_GET(1), WASM_I32V(1), WASM_I32V(1)), WASM_ARRAY_SET(array32_index, WASM_LOCAL_GET(1), WASM_I32V(2), WASM_I32V(2)), WASM_ARRAY_SET(array32_index, WASM_LOCAL_GET(1), WASM_I32V(3), WASM_I32V(3)), WASM_LOCAL_SET(2, WASM_ARRAY_NEW_DEFAULT(array32_index, WASM_I32V(10))), WASM_ARRAY_COPY(array32_index, array32_index, WASM_LOCAL_GET(2), WASM_I32V(6), WASM_LOCAL_GET(1), WASM_I32V(0), WASM_I32V(4)), WASM_ARRAY_GET(array32_index, WASM_LOCAL_GET(2), WASM_LOCAL_GET(0)), kExprEnd}); // Copies i16 ranges: local1[0..3] to local2[6..9]. const uint8_t kCopyI16 = tester.DefineFunction( tester.sigs.i_i(), {refNull(array16_index), refNull(array16_index)}, {WASM_LOCAL_SET(1, WASM_ARRAY_NEW_DEFAULT(array16_index, WASM_I32V(10))), WASM_ARRAY_SET(array16_index, WASM_LOCAL_GET(1), WASM_I32V(0), WASM_I32V(0)), WASM_ARRAY_SET(array16_index, WASM_LOCAL_GET(1), WASM_I32V(1), WASM_I32V(1)), WASM_ARRAY_SET(array16_index, WASM_LOCAL_GET(1), WASM_I32V(2), WASM_I32V(2)), WASM_ARRAY_SET(array16_index, WASM_LOCAL_GET(1), WASM_I32V(3), WASM_I32V(3)), WASM_LOCAL_SET(2, WASM_ARRAY_NEW_DEFAULT(array16_index, WASM_I32V(10))), WASM_ARRAY_COPY(array16_index, array16_index, WASM_LOCAL_GET(2), WASM_I32V(6), WASM_LOCAL_GET(1), WASM_I32V(0), WASM_I32V(4)), WASM_ARRAY_GET_S(array16_index, WASM_LOCAL_GET(2), WASM_LOCAL_GET(0)), kExprEnd}); // Copies reference ranges: local1[0..3] to local2[6..9]. const uint8_t kCopyRef = tester.DefineFunction( FunctionSig::Build(tester.zone(), {refNull(array32_index)}, {kWasmI32}), {refNull(arrayref_index), refNull(arrayref_index)}, {WASM_LOCAL_SET(1, WASM_ARRAY_NEW_DEFAULT(arrayref_index, WASM_I32V(10))), WASM_ARRAY_SET(arrayref_index, WASM_LOCAL_GET(1), WASM_I32V(0), WASM_ARRAY_NEW_DEFAULT(array32_index, WASM_I32V(6))), WASM_ARRAY_SET(arrayref_index, WASM_LOCAL_GET(1), WASM_I32V(1), WASM_ARRAY_NEW_DEFAULT(array32_index, WASM_I32V(7))), WASM_ARRAY_SET(arrayref_index, WASM_LOCAL_GET(1), WASM_I32V(2), WASM_ARRAY_NEW_DEFAULT(array32_index, WASM_I32V(8))), WASM_ARRAY_SET(arrayref_index, WASM_LOCAL_GET(1), WASM_I32V(3), WASM_ARRAY_NEW_DEFAULT(array32_index, WASM_I32V(9))), WASM_LOCAL_SET(2, WASM_ARRAY_NEW_DEFAULT(arrayref_index, WASM_I32V(10))), WASM_ARRAY_COPY(arrayref_index, arrayref_index, WASM_LOCAL_GET(2), WASM_I32V(6), WASM_LOCAL_GET(1), WASM_I32V(0), WASM_I32V(4)), WASM_ARRAY_GET(arrayref_index, WASM_LOCAL_GET(2), WASM_LOCAL_GET(0)), kExprEnd}); // Copies overlapping reference ranges: local1[0..3] to local1[2..5]. const uint8_t kCopyRefOverlapping = tester.DefineFunction( FunctionSig::Build(tester.zone(), {refNull(array32_index)}, {kWasmI32}), {refNull(arrayref_index)}, {WASM_LOCAL_SET(1, WASM_ARRAY_NEW_DEFAULT(arrayref_index, WASM_I32V(10))), WASM_ARRAY_SET(arrayref_index, WASM_LOCAL_GET(1), WASM_I32V(0), WASM_ARRAY_NEW_DEFAULT(array32_index, WASM_I32V(2))), WASM_ARRAY_SET(arrayref_index, WASM_LOCAL_GET(1), WASM_I32V(1), WASM_ARRAY_NEW_DEFAULT(array32_index, WASM_I32V(3))), WASM_ARRAY_SET(arrayref_index, WASM_LOCAL_GET(1), WASM_I32V(2), WASM_ARRAY_NEW_DEFAULT(array32_index, WASM_I32V(4))), WASM_ARRAY_SET(arrayref_index, WASM_LOCAL_GET(1), WASM_I32V(3), WASM_ARRAY_NEW_DEFAULT(array32_index, WASM_I32V(5))), WASM_ARRAY_COPY(arrayref_index, arrayref_index, WASM_LOCAL_GET(1), WASM_I32V(2), WASM_LOCAL_GET(1), WASM_I32V(0), WASM_I32V(4)), WASM_ARRAY_GET(arrayref_index, WASM_LOCAL_GET(1), WASM_LOCAL_GET(0)), kExprEnd}); const uint8_t kOobSource = tester.DefineFunction( tester.sigs.v_v(), {refNull(array32_index), refNull(array32_index)}, {WASM_LOCAL_SET(0, WASM_ARRAY_NEW_DEFAULT(array32_index, WASM_I32V(10))), WASM_LOCAL_SET(1, WASM_ARRAY_NEW_DEFAULT(array32_index, WASM_I32V(10))), WASM_ARRAY_COPY(array32_index, array32_index, WASM_LOCAL_GET(1), WASM_I32V(6), WASM_LOCAL_GET(0), WASM_I32V(8), WASM_I32V(4)), kExprEnd}); const uint8_t kOobDestination = tester.DefineFunction( tester.sigs.v_v(), {refNull(array32_index), refNull(array32_index)}, {WASM_LOCAL_SET(0, WASM_ARRAY_NEW_DEFAULT(array32_index, WASM_I32V(10))), WASM_LOCAL_SET(1, WASM_ARRAY_NEW_DEFAULT(array32_index, WASM_I32V(10))), WASM_ARRAY_COPY(array32_index, array32_index, WASM_LOCAL_GET(1), WASM_I32V(6), WASM_LOCAL_GET(0), WASM_I32V(3), WASM_I32V(5)), kExprEnd}); const uint8_t kZeroLength = tester.DefineFunction( tester.sigs.i_v(), {refNull(arrayref_index), refNull(arrayref_index)}, {WASM_LOCAL_SET(0, WASM_ARRAY_NEW_DEFAULT(arrayref_index, WASM_I32V(10))), WASM_LOCAL_SET(1, WASM_ARRAY_NEW_DEFAULT(arrayref_index, WASM_I32V(10))), WASM_ARRAY_COPY(arrayref_index, arrayref_index, WASM_LOCAL_GET(1), WASM_I32V(6), WASM_LOCAL_GET(0), WASM_I32V(3), WASM_I32V(0)), WASM_I32V(0), kExprEnd}); tester.CompileModule(); tester.CheckResult(kCopyI32, 0, 5); tester.CheckResult(kCopyI32, 0, 6); tester.CheckResult(kCopyI32, 1, 7); tester.CheckResult(kCopyI32, 2, 8); tester.CheckResult(kCopyI32, 3, 9); tester.CheckResult(kCopyI16, 0, 5); tester.CheckResult(kCopyI16, 0, 6); tester.CheckResult(kCopyI16, 1, 7); tester.CheckResult(kCopyI16, 2, 8); tester.CheckResult(kCopyI16, 3, 9); { Handle result5 = tester.GetResultObject(kCopyRef, 5).ToHandleChecked(); CHECK(result5->IsWasmNull()); for (int i = 6; i <= 9; i++) { Handle res = tester.GetResultObject(kCopyRef, i).ToHandleChecked(); CHECK(res->IsWasmArray()); CHECK_EQ(Handle::cast(res)->length(), static_cast(i)); } } CHECK(tester.GetResultObject(kCopyRefOverlapping, 6) .ToHandleChecked() ->IsWasmNull()); Handle res0 = tester.GetResultObject(kCopyRefOverlapping, 0).ToHandleChecked(); CHECK(res0->IsWasmArray()); CHECK_EQ(Handle::cast(res0)->length(), static_cast(2)); for (int i = 2; i <= 5; i++) { Handle res = tester.GetResultObject(kCopyRefOverlapping, i).ToHandleChecked(); CHECK(res->IsWasmArray()); CHECK_EQ(Handle::cast(res)->length(), static_cast(i)); } tester.CheckHasThrown(kOobSource); tester.CheckHasThrown(kOobDestination); tester.CheckResult(kZeroLength, 0); // Does not throw. } WASM_COMPILED_EXEC_TEST(NewDefault) { WasmGCTester tester(execution_tier); if (!tester.HasSimdSupport(execution_tier)) return; tester.builder()->StartRecursiveTypeGroup(); const uint8_t struct_type = tester.DefineStruct( {F(wasm::kWasmI32, true), F(wasm::kWasmF64, true), F(refNull(0), true)}); tester.builder()->EndRecursiveTypeGroup(); const uint8_t array_type = tester.DefineArray(wasm::kWasmI32, true); // Returns: struct[0] + f64_to_i32(struct[1]) + (struct[2].is_null ^ 1) == 0. const uint8_t allocate_struct = tester.DefineFunction( tester.sigs.i_v(), {refNull(struct_type)}, {WASM_LOCAL_SET(0, WASM_STRUCT_NEW_DEFAULT(struct_type)), WASM_I32_ADD( WASM_I32_ADD(WASM_STRUCT_GET(struct_type, 0, WASM_LOCAL_GET(0)), WASM_I32_SCONVERT_F64(WASM_STRUCT_GET( struct_type, 1, WASM_LOCAL_GET(0)))), WASM_I32_XOR(WASM_REF_IS_NULL( WASM_STRUCT_GET(struct_type, 2, WASM_LOCAL_GET(0))), WASM_I32V(1))), kExprEnd}); const uint8_t allocate_array = tester.DefineFunction( tester.sigs.i_v(), {refNull(array_type)}, {WASM_LOCAL_SET(0, WASM_ARRAY_NEW_DEFAULT(array_type, WASM_I32V(2))), WASM_I32_ADD( WASM_ARRAY_GET(array_type, WASM_LOCAL_GET(0), WASM_I32V(0)), WASM_ARRAY_GET(array_type, WASM_LOCAL_GET(0), WASM_I32V(1))), kExprEnd}); tester.CompileModule(); tester.CheckResult(allocate_struct, 0); tester.CheckResult(allocate_array, 0); } WASM_COMPILED_EXEC_TEST(RefTrivialCastsStatic) { WasmGCTester tester(execution_tier); uint8_t type_index = tester.DefineStruct({F(wasm::kWasmI32, true)}); uint8_t subtype_index = tester.DefineStruct( {F(wasm::kWasmI32, true), F(wasm::kWasmS128, true)}, type_index); ValueType sig_types[] = {kWasmS128, kWasmI32, kWasmF64}; FunctionSig sig(1, 2, sig_types); uint8_t sig_index = tester.DefineSignature(&sig); const uint8_t kRefTestNullDeprecated = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_REF_TEST_DEPRECATED(WASM_REF_NULL(type_index), subtype_index), kExprEnd}); // Upcasts should be optimized away for nominal types. const uint8_t kRefTestUpcastDeprecated = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_REF_TEST_DEPRECATED(WASM_STRUCT_NEW_DEFAULT(subtype_index), type_index), kExprEnd}); const uint8_t kRefTestUpcastNullDeprecated = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_REF_TEST_DEPRECATED(WASM_REF_NULL(subtype_index), type_index), kExprEnd}); const uint8_t kRefTestUnrelatedNullableDeprecated = tester.DefineFunction( tester.sigs.i_v(), {refNull(subtype_index)}, {WASM_LOCAL_SET(0, WASM_STRUCT_NEW_DEFAULT(subtype_index)), WASM_REF_TEST_DEPRECATED(WASM_LOCAL_GET(0), sig_index), kExprEnd}); const uint8_t kRefTestUnrelatedNullDeprecated = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_REF_TEST_DEPRECATED(WASM_REF_NULL(subtype_index), sig_index), kExprEnd}); const uint8_t kRefTestUnrelatedNonNullableDeprecated = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_REF_TEST_DEPRECATED(WASM_STRUCT_NEW_DEFAULT(type_index), sig_index), kExprEnd}); const uint8_t kRefCastNullDeprecated = tester.DefineFunction(tester.sigs.i_v(), {}, {WASM_REF_IS_NULL(WASM_REF_CAST_DEPRECATED( WASM_REF_NULL(type_index), subtype_index)), kExprEnd}); const uint8_t kRefCastNull = tester.DefineFunction(tester.sigs.i_v(), {}, {WASM_REF_IS_NULL(WASM_REF_CAST( WASM_REF_NULL(type_index), subtype_index)), kExprEnd}); const uint8_t kRefCastUpcast = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_REF_IS_NULL( WASM_REF_CAST(WASM_STRUCT_NEW_DEFAULT(subtype_index), type_index)), kExprEnd}); const uint8_t kRefCastUpcastNullDeprecated = tester.DefineFunction(tester.sigs.i_v(), {}, {WASM_REF_IS_NULL(WASM_REF_CAST_DEPRECATED( WASM_REF_NULL(subtype_index), type_index)), kExprEnd}); const uint8_t kRefCastUpcastNull = tester.DefineFunction(tester.sigs.i_v(), {}, {WASM_REF_IS_NULL(WASM_REF_CAST( WASM_REF_NULL(subtype_index), type_index)), kExprEnd}); // Note: Casting of types from different type hierarchies is only valid for // the deprecated cast instruction. const uint8_t kRefCastUnrelatedNullable = tester.DefineFunction( tester.sigs.i_v(), {refNull(subtype_index)}, {WASM_LOCAL_SET(0, WASM_STRUCT_NEW_DEFAULT(subtype_index)), WASM_REF_IS_NULL(WASM_REF_CAST_DEPRECATED(WASM_LOCAL_GET(0), sig_index)), kExprEnd}); const uint8_t kRefCastUnrelatedNull = tester.DefineFunction(tester.sigs.i_v(), {}, {WASM_REF_IS_NULL(WASM_REF_CAST_DEPRECATED( WASM_REF_NULL(subtype_index), sig_index)), kExprEnd}); const uint8_t kRefCastUnrelatedNonNullable = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_REF_IS_NULL(WASM_REF_CAST_DEPRECATED( WASM_STRUCT_NEW_DEFAULT(type_index), sig_index)), kExprEnd}); const uint8_t kBrOnCastNull = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_BLOCK_R(refNull(subtype_index), WASM_REF_NULL(type_index), WASM_BR_ON_CAST(0, type_index, subtype_index), WASM_DROP, WASM_RETURN(WASM_I32V(0))), WASM_DROP, WASM_I32V(1), WASM_END}); // Real upcasts are invalid for br_on_cast. Casting to the same type is // however similar to an upcast. const uint8_t kBrOnCastUpcast = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_BLOCK_R(refNull(type_index), WASM_STRUCT_NEW_DEFAULT(type_index), WASM_BR_ON_CAST(0, type_index, type_index), WASM_DROP, WASM_RETURN(WASM_I32V(0))), WASM_DROP, WASM_I32V(1), WASM_END}); const uint8_t kBrOnCastUpcastNull = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_BLOCK_R(refNull(type_index), WASM_REF_NULL(type_index), WASM_BR_ON_CAST(0, type_index, type_index), WASM_DROP, WASM_RETURN(WASM_I32V(0))), WASM_DROP, WASM_I32V(1), WASM_END}); const uint8_t kBrOnCastUnrelatedNullable = tester.DefineFunction( tester.sigs.i_v(), {refNull(subtype_index)}, {WASM_LOCAL_SET(0, WASM_STRUCT_NEW_DEFAULT(subtype_index)), WASM_BLOCK_R(refNull(sig_index), WASM_LOCAL_GET(0), WASM_BR_ON_CAST_DEPRECATED(0, sig_index), WASM_DROP, WASM_RETURN(WASM_I32V(0))), WASM_DROP, WASM_I32V(1), WASM_END}); const uint8_t kBrOnCastUnrelatedNull = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_BLOCK_R(refNull(sig_index), WASM_REF_NULL(subtype_index), WASM_BR_ON_CAST_DEPRECATED(0, sig_index), WASM_DROP, WASM_RETURN(WASM_I32V(0))), WASM_DROP, WASM_I32V(1), WASM_END}); const uint8_t kBrOnCastUnrelatedNonNullable = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_BLOCK_R(refNull(sig_index), WASM_STRUCT_NEW_DEFAULT(subtype_index), WASM_BR_ON_CAST_DEPRECATED(0, sig_index), WASM_DROP, WASM_RETURN(WASM_I32V(0))), WASM_DROP, WASM_I32V(1), WASM_END}); const uint8_t kBrOnCastFailNull = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_BLOCK_R(refNull(type_index), WASM_REF_NULL(type_index), WASM_BR_ON_CAST_FAIL(0, type_index, subtype_index), WASM_DROP, WASM_RETURN(WASM_I32V(0))), WASM_DROP, WASM_I32V(1), WASM_END}); // Real upcasts are invalid for br_on_cast. Casting to the same type is // however similar to an upcast. const uint8_t kBrOnCastFailUpcast = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_BLOCK_R(refNull(subtype_index), WASM_STRUCT_NEW_DEFAULT(subtype_index), WASM_BR_ON_CAST_FAIL(0, subtype_index, subtype_index), WASM_DROP, WASM_RETURN(WASM_I32V(0))), WASM_DROP, WASM_I32V(1), WASM_END}); const uint8_t kBrOnCastFailUpcastNull = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_BLOCK_R(refNull(subtype_index), WASM_REF_NULL(subtype_index), WASM_BR_ON_CAST_FAIL(0, subtype_index, subtype_index), WASM_DROP, WASM_RETURN(WASM_I32V(0))), WASM_DROP, WASM_I32V(1), WASM_END}); const uint8_t kBrOnCastFailUnrelatedNullable = tester.DefineFunction( tester.sigs.i_v(), {refNull(subtype_index)}, {WASM_LOCAL_SET(0, WASM_STRUCT_NEW_DEFAULT(subtype_index)), WASM_BLOCK_R(refNull(subtype_index), WASM_LOCAL_GET(0), WASM_BR_ON_CAST_FAIL_DEPRECATED(0, sig_index), WASM_DROP, WASM_RETURN(WASM_I32V(0))), WASM_DROP, WASM_I32V(1), WASM_END}); const uint8_t kBrOnCastFailUnrelatedNull = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_BLOCK_R(refNull(subtype_index), WASM_REF_NULL(subtype_index), WASM_BR_ON_CAST_FAIL_DEPRECATED(0, sig_index), WASM_DROP, WASM_RETURN(WASM_I32V(0))), WASM_DROP, WASM_I32V(1), WASM_END}); const uint8_t kBrOnCastFailUnrelatedNonNullable = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_BLOCK_R(refNull(subtype_index), WASM_STRUCT_NEW_DEFAULT(subtype_index), WASM_BR_ON_CAST_FAIL_DEPRECATED(0, sig_index), WASM_DROP, WASM_RETURN(WASM_I32V(0))), WASM_DROP, WASM_I32V(1), WASM_END}); tester.CompileModule(); tester.CheckResult(kRefTestNullDeprecated, 0); tester.CheckResult(kRefTestUpcastDeprecated, 1); tester.CheckResult(kRefTestUpcastNullDeprecated, 0); tester.CheckResult(kRefTestUnrelatedNullableDeprecated, 0); tester.CheckResult(kRefTestUnrelatedNullDeprecated, 0); tester.CheckResult(kRefTestUnrelatedNonNullableDeprecated, 0); tester.CheckResult(kRefCastNullDeprecated, 1); tester.CheckHasThrown(kRefCastNull); tester.CheckResult(kRefCastUpcast, 0); tester.CheckResult(kRefCastUpcastNullDeprecated, 1); tester.CheckHasThrown(kRefCastUpcastNull); tester.CheckHasThrown(kRefCastUnrelatedNullable); tester.CheckResult(kRefCastUnrelatedNull, 1); tester.CheckHasThrown(kRefCastUnrelatedNonNullable); tester.CheckResult(kBrOnCastNull, 0); tester.CheckResult(kBrOnCastUpcast, 1); tester.CheckResult(kBrOnCastUpcastNull, 0); tester.CheckResult(kBrOnCastUnrelatedNullable, 0); tester.CheckResult(kBrOnCastUnrelatedNull, 0); tester.CheckResult(kBrOnCastUnrelatedNonNullable, 0); tester.CheckResult(kBrOnCastFailNull, 1); tester.CheckResult(kBrOnCastFailUpcast, 0); tester.CheckResult(kBrOnCastFailUpcastNull, 1); tester.CheckResult(kBrOnCastFailUnrelatedNullable, 1); tester.CheckResult(kBrOnCastFailUnrelatedNull, 1); tester.CheckResult(kBrOnCastFailUnrelatedNonNullable, 1); } WASM_COMPILED_EXEC_TEST(TrivialAbstractCasts) { // TODO(7748): Add tests for branch_on_*. WasmGCTester tester(execution_tier); uint8_t type_index = tester.DefineArray(wasm::kWasmI32, true); uint8_t struct_type_index = tester.DefineStruct({F(wasm::kWasmI32, true)}); ValueType sig_types[] = {kWasmS128, kWasmI32, kWasmF64}; FunctionSig sig(1, 2, sig_types); const uint8_t kIsArrayNull = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_REF_IS_ARRAY(WASM_REF_NULL(kAnyRefCode)), kExprEnd}); const uint8_t kIsArrayUpcast = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_REF_IS_ARRAY(WASM_ARRAY_NEW_DEFAULT(type_index, WASM_I32V(10))), kExprEnd}); const uint8_t kIsArrayUpcastNullable = tester.DefineFunction( tester.sigs.i_v(), {ValueType::RefNull(type_index)}, {WASM_LOCAL_SET(0, WASM_ARRAY_NEW_DEFAULT(type_index, WASM_I32V(10))), WASM_REF_IS_ARRAY(WASM_LOCAL_GET(0)), kExprEnd}); const uint8_t kIsArrayUpcastNull = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_REF_IS_ARRAY(WASM_REF_NULL(type_index)), kExprEnd}); const uint8_t kIsArrayUnrelated = tester.DefineFunction( tester.sigs.i_v(), {ValueType::RefNull(struct_type_index)}, {WASM_LOCAL_SET(0, WASM_STRUCT_NEW_DEFAULT(struct_type_index)), WASM_REF_IS_ARRAY(WASM_LOCAL_GET(0)), kExprEnd}); const uint8_t kIsArrayUnrelatedNull = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_REF_IS_ARRAY(WASM_REF_NULL(kI31RefCode)), kExprEnd}); const uint8_t kIsArrayUnrelatedNonNullable = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_REF_IS_ARRAY(WASM_I31_NEW(WASM_I32V(10))), kExprEnd}); const uint8_t kAsArrayNull = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_REF_IS_NULL(WASM_REF_AS_ARRAY(WASM_REF_NULL(kAnyRefCode))), kExprEnd}); const uint8_t kAsArrayUpcast = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_REF_IS_NULL(WASM_REF_AS_ARRAY( WASM_ARRAY_NEW_DEFAULT(type_index, WASM_I32V(10)))), kExprEnd}); const uint8_t kAsArrayUpcastNullable = tester.DefineFunction( tester.sigs.i_v(), {ValueType::RefNull(type_index)}, {WASM_LOCAL_SET(0, WASM_ARRAY_NEW_DEFAULT(type_index, WASM_I32V(10))), WASM_REF_IS_NULL(WASM_REF_AS_ARRAY(WASM_LOCAL_GET(0))), kExprEnd}); const uint8_t kAsArrayUpcastNull = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_REF_IS_NULL(WASM_REF_AS_ARRAY(WASM_REF_NULL(type_index))), kExprEnd}); const uint8_t kAsArrayUnrelated = tester.DefineFunction( tester.sigs.i_v(), {ValueType::RefNull(struct_type_index)}, {WASM_LOCAL_SET(0, WASM_STRUCT_NEW_DEFAULT(struct_type_index)), WASM_REF_IS_NULL(WASM_REF_AS_ARRAY(WASM_LOCAL_GET(0))), kExprEnd}); const uint8_t kAsArrayUnrelatedNull = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_REF_IS_NULL(WASM_REF_AS_ARRAY(WASM_REF_NULL(kI31RefCode))), kExprEnd}); const uint8_t kAsArrayUnrelatedNonNullable = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_REF_IS_NULL(WASM_REF_AS_ARRAY(WASM_I31_NEW(WASM_I32V(10)))), kExprEnd}); tester.CompileModule(); tester.CheckResult(kIsArrayNull, 0); tester.CheckResult(kIsArrayUpcast, 1); tester.CheckResult(kIsArrayUpcastNullable, 1); tester.CheckResult(kIsArrayUpcastNull, 0); tester.CheckResult(kIsArrayUnrelated, 0); tester.CheckResult(kIsArrayUnrelatedNull, 0); tester.CheckResult(kIsArrayUnrelatedNonNullable, 0); tester.CheckHasThrown(kAsArrayNull); tester.CheckResult(kAsArrayUpcast, 0); tester.CheckResult(kAsArrayUpcastNullable, 0); tester.CheckHasThrown(kAsArrayUpcastNull); tester.CheckHasThrown(kAsArrayUnrelated); tester.CheckHasThrown(kAsArrayUnrelatedNull); tester.CheckHasThrown(kAsArrayUnrelatedNonNullable); } WASM_COMPILED_EXEC_TEST(ArrayNewMap) { WasmGCTester tester(execution_tier); const uint8_t type_index = tester.DefineArray(kWasmI32, true); ValueType array_type = ValueType::Ref(type_index); FunctionSig sig(1, 0, &array_type); const uint8_t array_new = tester.DefineFunction( &sig, {}, {WASM_ARRAY_NEW(type_index, WASM_I32V(10), WASM_I32V(42)), kExprEnd}); tester.CompileModule(); Handle result = tester.GetResultObject(array_new).ToHandleChecked(); CHECK(result->IsWasmArray()); CHECK_EQ(Handle::cast(result)->map(), tester.instance()->managed_object_maps().get(type_index)); } WASM_COMPILED_EXEC_TEST(FunctionRefs) { WasmGCTester tester(execution_tier); const uint8_t func_index = tester.DefineFunction(tester.sigs.i_v(), {}, {WASM_I32V(42), kExprEnd}); const uint8_t sig_index = 0; const uint8_t other_sig_index = tester.DefineSignature(tester.sigs.d_d()); // This is just so func_index counts as "declared". tester.AddGlobal(ValueType::RefNull(sig_index), false, WasmInitExpr::RefFuncConst(func_index)); ValueType func_type = ValueType::RefNull(sig_index); FunctionSig sig_func(1, 0, &func_type); const uint8_t cast = tester.DefineFunction( &sig_func, {kWasmFuncRef}, {WASM_LOCAL_SET(0, WASM_REF_FUNC(func_index)), WASM_REF_CAST(WASM_LOCAL_GET(0), sig_index), kExprEnd}); const uint8_t cast_reference = tester.DefineFunction( &sig_func, {}, {WASM_REF_FUNC(sig_index), kExprEnd}); const uint8_t test_deprecated = tester.DefineFunction( tester.sigs.i_v(), {kWasmFuncRef}, {WASM_LOCAL_SET(0, WASM_REF_FUNC(func_index)), WASM_REF_TEST_DEPRECATED(WASM_LOCAL_GET(0), sig_index), kExprEnd}); const uint8_t test_fail_deprecated = tester.DefineFunction( tester.sigs.i_v(), {kWasmFuncRef}, {WASM_LOCAL_SET(0, WASM_REF_FUNC(func_index)), WASM_REF_TEST_DEPRECATED(WASM_LOCAL_GET(0), other_sig_index), kExprEnd}); const uint8_t test = tester.DefineFunction( tester.sigs.i_v(), {kWasmFuncRef}, {WASM_LOCAL_SET(0, WASM_REF_FUNC(func_index)), WASM_REF_TEST(WASM_LOCAL_GET(0), sig_index), kExprEnd}); const uint8_t test_fail = tester.DefineFunction( tester.sigs.i_v(), {kWasmFuncRef}, {WASM_LOCAL_SET(0, WASM_REF_FUNC(func_index)), WASM_REF_TEST(WASM_LOCAL_GET(0), other_sig_index), kExprEnd}); tester.CompileModule(); Handle result_cast = tester.GetResultObject(cast).ToHandleChecked(); CHECK(result_cast->IsWasmInternalFunction()); Handle cast_function = WasmInternalFunction::GetOrCreateExternal( Handle::cast(result_cast)); Handle result_cast_reference = tester.GetResultObject(cast_reference).ToHandleChecked(); CHECK(result_cast_reference->IsWasmInternalFunction()); Handle cast_function_reference = WasmInternalFunction::GetOrCreateExternal( Handle::cast(result_cast_reference)); CHECK_EQ(cast_function->code().instruction_start(), cast_function_reference->code().instruction_start()); tester.CheckResult(test_deprecated, 1); tester.CheckResult(test_fail_deprecated, 0); tester.CheckResult(test, 1); tester.CheckResult(test_fail, 0); } WASM_COMPILED_EXEC_TEST(CallRef) { WasmGCTester tester(execution_tier); uint8_t sig_index = tester.DefineSignature(tester.sigs.i_ii()); uint8_t callee = tester.DefineFunction( sig_index, {}, {WASM_I32_ADD(WASM_LOCAL_GET(0), WASM_LOCAL_GET(1)), kExprEnd}); uint8_t caller = tester.DefineFunction(tester.sigs.i_i(), {}, {WASM_CALL_REF(WASM_REF_FUNC(callee), sig_index, WASM_I32V(42), WASM_LOCAL_GET(0)), kExprEnd}); // This is just so func_index counts as "declared". tester.AddGlobal(ValueType::RefNull(sig_index), false, WasmInitExpr::RefFuncConst(callee)); tester.CompileModule(); tester.CheckResult(caller, 47, 5); } // Test that calling a function expecting any ref accepts the abstract null // type argument (nullref, nullfuncref, nullexternref). WASM_COMPILED_EXEC_TEST(CallAbstractNullTypeImplicitConversion) { const struct { ValueType super_type; ValueTypeCode sub_type_code; } null_ref_types[] = { {kWasmFuncRef, kNoFuncCode}, {kWasmEqRef, kNoneCode}, {kWasmI31Ref.AsNullable(), kNoneCode}, {kWasmStructRef.AsNullable(), kNoneCode}, {kWasmArrayRef.AsNullable(), kNoneCode}, {kWasmAnyRef, kNoneCode}, {kWasmExternRef, kNoExternCode}, {refNull(0), kNoneCode}, // struct {refNull(1), kNoneCode}, // array {refNull(2), kNoFuncCode}, // signature }; for (auto [super_type, sub_type_code] : null_ref_types) { CHECK(super_type.is_nullable()); WasmGCTester tester(execution_tier); uint8_t struct_idx = tester.DefineStruct({F(wasm::kWasmI32, true)}); CHECK_EQ(struct_idx, 0); uint8_t array_idx = tester.DefineArray(kWasmI32, true); CHECK_EQ(array_idx, 1); FunctionSig dummySig(1, 0, &kWasmI32); uint8_t signature_idx = tester.DefineSignature(&dummySig); CHECK_EQ(signature_idx, 2); ValueType ref_sig_types[] = {kWasmI32, super_type}; FunctionSig sig_ref(1, 1, ref_sig_types); uint8_t callee = tester.DefineFunction( &sig_ref, {}, {WASM_REF_IS_NULL(WASM_LOCAL_GET(0)), kExprEnd}); uint8_t caller = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_CALL_FUNCTION(callee, WASM_REF_NULL(sub_type_code)), kExprEnd}); tester.CompileModule(); tester.CheckResult(caller, 1); } } WASM_COMPILED_EXEC_TEST(CastNullRef) { WasmGCTester tester(execution_tier); uint8_t to_non_null = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_REF_IS_NULL(WASM_REF_AS_NON_NULL(WASM_REF_NULL(kNoneCode))), kExprEnd}); uint8_t to_array = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_REF_IS_NULL(WASM_REF_AS_ARRAY(WASM_REF_NULL(kNoneCode))), kExprEnd}); uint8_t to_struct = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_REF_IS_NULL(WASM_REF_AS_STRUCT(WASM_REF_NULL(kNoneCode))), kExprEnd}); uint8_t to_i31 = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_REF_IS_NULL(WASM_REF_AS_I31(WASM_REF_NULL(kNoneCode))), kExprEnd}); uint8_t struct_idx = tester.DefineStruct({F(wasm::kWasmI32, true)}); uint8_t to_struct_idx = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_REF_IS_NULL(WASM_REF_CAST(WASM_REF_NULL(kNoneCode), struct_idx)), kExprEnd}); tester.CompileModule(); // Generic casts trap on null. tester.CheckHasThrown(to_non_null); tester.CheckHasThrown(to_array); tester.CheckHasThrown(to_struct); tester.CheckHasThrown(to_i31); // ref.cast traps on null. tester.CheckHasThrown(to_struct_idx); } WASM_COMPILED_EXEC_TEST(CallReftypeParameters) { WasmGCTester tester(execution_tier); uint8_t type_index = tester.DefineStruct({F(wasm::kWasmI32, true)}); ValueType kRefType{refNull(type_index)}; ValueType sig_types[] = {kWasmI32, kRefType, kRefType, kRefType, kRefType, kWasmI32, kWasmI32, kWasmI32, kWasmI32}; FunctionSig sig(1, 8, sig_types); uint8_t adder = tester.DefineFunction( &sig, {}, {WASM_I32_ADD( WASM_STRUCT_GET(type_index, 0, WASM_LOCAL_GET(0)), WASM_I32_ADD( WASM_STRUCT_GET(type_index, 0, WASM_LOCAL_GET(1)), WASM_I32_ADD( WASM_STRUCT_GET(type_index, 0, WASM_LOCAL_GET(2)), WASM_I32_ADD( WASM_STRUCT_GET(type_index, 0, WASM_LOCAL_GET(3)), WASM_I32_ADD( WASM_LOCAL_GET(4), WASM_I32_ADD(WASM_LOCAL_GET(5), WASM_I32_ADD(WASM_LOCAL_GET(6), WASM_LOCAL_GET(7)))))))), kExprEnd}); uint8_t caller = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_CALL_FUNCTION(adder, WASM_STRUCT_NEW(type_index, WASM_I32V(2)), WASM_STRUCT_NEW(type_index, WASM_I32V(4)), WASM_STRUCT_NEW(type_index, WASM_I32V(8)), WASM_STRUCT_NEW(type_index, WASM_I32V(16)), WASM_I32V(32), WASM_I32V(64), WASM_I32V(128), WASM_I32V(256)), kExprEnd}); tester.CompileModule(); tester.CheckResult(caller, 510); } WASM_COMPILED_EXEC_TEST(AbstractTypeChecks) { WasmGCTester tester(execution_tier); uint8_t array_index = tester.DefineArray(kWasmI32, true); uint8_t struct_index = tester.DefineStruct({F(kWasmI32, true)}); uint8_t function_index = tester.DefineFunction(tester.sigs.v_v(), {}, {kExprEnd}); uint8_t sig_index = 2; // This is just so func_index counts as "declared". tester.AddGlobal(ValueType::RefNull(sig_index), false, WasmInitExpr::RefFuncConst(function_index)); uint8_t kStructCheckNull = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_REF_IS_STRUCT(WASM_REF_NULL(kAnyRefCode)), kExprEnd}); uint8_t kArrayCheckNull = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_REF_IS_ARRAY(WASM_REF_NULL(kAnyRefCode)), kExprEnd}); uint8_t kI31CheckNull = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_REF_IS_I31(WASM_REF_NULL(kAnyRefCode)), kExprEnd}); uint8_t kStructCastNull = tester.DefineFunction(tester.sigs.i_v(), {}, {WASM_REF_AS_STRUCT(WASM_REF_NULL(kAnyRefCode)), WASM_DROP, WASM_I32V(1), kExprEnd}); uint8_t kArrayCastNull = tester.DefineFunction(tester.sigs.i_v(), {}, {WASM_REF_AS_ARRAY(WASM_REF_NULL(kAnyRefCode)), WASM_DROP, WASM_I32V(1), kExprEnd}); uint8_t kI31CastNull = tester.DefineFunction(tester.sigs.i_v(), {}, {WASM_REF_AS_I31(WASM_REF_NULL(kAnyRefCode)), WASM_DROP, WASM_I32V(1), kExprEnd}); #define TYPE_CHECK(type, value) \ tester.DefineFunction(tester.sigs.i_v(), {kWasmAnyRef}, \ {WASM_LOCAL_SET(0, WASM_SEQ(value)), \ WASM_REF_IS_##type(WASM_LOCAL_GET(0)), kExprEnd}) uint8_t kStructCheckSuccess = TYPE_CHECK(STRUCT, WASM_STRUCT_NEW_DEFAULT(struct_index)); uint8_t kStructCheckFailure = TYPE_CHECK(STRUCT, WASM_I31_NEW(WASM_I32V(42))); uint8_t kArrayCheckSuccess = TYPE_CHECK(ARRAY, WASM_ARRAY_NEW_DEFAULT(array_index, WASM_I32V(10))); uint8_t kArrayCheckFailure = TYPE_CHECK(ARRAY, WASM_STRUCT_NEW_DEFAULT(struct_index)); uint8_t kI31CheckSuccess = TYPE_CHECK(I31, WASM_I31_NEW(WASM_I32V(42))); uint8_t kI31CheckFailure = TYPE_CHECK(I31, WASM_ARRAY_NEW_DEFAULT(array_index, WASM_I32V(10))); #undef TYPE_CHECK #define TYPE_CAST(type, value) \ tester.DefineFunction(tester.sigs.i_v(), {kWasmAnyRef}, \ {WASM_LOCAL_SET(0, WASM_SEQ(value)), \ WASM_REF_AS_##type(WASM_LOCAL_GET(0)), WASM_DROP, \ WASM_I32V(1), kExprEnd}) uint8_t kStructCastSuccess = TYPE_CAST(STRUCT, WASM_STRUCT_NEW_DEFAULT(struct_index)); uint8_t kStructCastFailure = TYPE_CAST(STRUCT, WASM_I31_NEW(WASM_I32V(42))); uint8_t kArrayCastSuccess = TYPE_CAST(ARRAY, WASM_ARRAY_NEW_DEFAULT(array_index, WASM_I32V(10))); uint8_t kArrayCastFailure = TYPE_CAST(ARRAY, WASM_I31_NEW(WASM_I32V(42))); uint8_t kI31CastSuccess = TYPE_CAST(I31, WASM_I31_NEW(WASM_I32V(42))); uint8_t kI31CastFailure = TYPE_CAST(I31, WASM_ARRAY_NEW_DEFAULT(array_index, WASM_I32V(10))); #undef TYPE_CAST // If the branch is not taken, we return 0. If it is taken, then the respective // type check should succeed, and we return 1. #define BR_ON(TYPE, type, value) \ tester.DefineFunction( \ tester.sigs.i_v(), {kWasmAnyRef}, \ {WASM_LOCAL_SET(0, WASM_SEQ(value)), \ WASM_REF_IS_##TYPE(WASM_BLOCK_R(kWasm##type##Ref, WASM_LOCAL_GET(0), \ WASM_BR_ON_##TYPE(0), \ WASM_RETURN(WASM_I32V(0)))), \ kExprEnd}) uint8_t kBrOnStructTaken = BR_ON(STRUCT, Struct, WASM_STRUCT_NEW_DEFAULT(struct_index)); uint8_t kBrOnStructNotTaken = BR_ON(STRUCT, Struct, WASM_REF_NULL(kNoneCode)); uint8_t kBrOnArrayTaken = BR_ON(ARRAY, Array, WASM_ARRAY_NEW_DEFAULT(array_index, WASM_I32V(10))); uint8_t kBrOnArrayNotTaken = BR_ON(ARRAY, Array, WASM_I31_NEW(WASM_I32V(42))); uint8_t kBrOnI31Taken = BR_ON(I31, I31, WASM_I31_NEW(WASM_I32V(42))); uint8_t kBrOnI31NotTaken = BR_ON(I31, I31, WASM_ARRAY_NEW_DEFAULT(array_index, WASM_I32V(10))); #undef BR_ON // If the branch is not taken, we return 1. If it is taken, then the respective // type check should fail, and we return 0. #define BR_ON_NON(TYPE, type, value) \ tester.DefineFunction( \ tester.sigs.i_v(), {kWasmAnyRef}, \ {WASM_LOCAL_SET(0, WASM_SEQ(value)), \ WASM_REF_IS_##TYPE(WASM_BLOCK_R(kWasmAnyRef, WASM_LOCAL_GET(0), \ WASM_BR_ON_NON_##TYPE(0), \ WASM_RETURN(WASM_I32V(1)))), \ kExprEnd}) uint8_t kBrOnNonStructNotTaken = BR_ON_NON(STRUCT, Struct, WASM_STRUCT_NEW_DEFAULT(struct_index)); uint8_t kBrOnNonStructTaken = BR_ON_NON(STRUCT, Struct, WASM_REF_NULL(kNoneCode)); uint8_t kBrOnNonArrayNotTaken = BR_ON_NON( ARRAY, Array, WASM_ARRAY_NEW_DEFAULT(array_index, WASM_I32V(10))); uint8_t kBrOnNonArrayTaken = BR_ON_NON(ARRAY, Array, WASM_I31_NEW(WASM_I32V(42))); uint8_t kBrOnNonI31NotTaken = BR_ON_NON(I31, I31, WASM_I31_NEW(WASM_I32V(42))); uint8_t kBrOnNonI31Taken = BR_ON_NON(I31, I31, WASM_ARRAY_NEW_DEFAULT(array_index, WASM_I32V(10))); #undef BR_ON_NON tester.CompileModule(); tester.CheckResult(kStructCheckNull, 0); tester.CheckResult(kArrayCheckNull, 0); tester.CheckResult(kI31CheckNull, 0); tester.CheckHasThrown(kStructCastNull); tester.CheckHasThrown(kArrayCastNull); tester.CheckHasThrown(kI31CastNull); tester.CheckResult(kStructCheckSuccess, 1); tester.CheckResult(kArrayCheckSuccess, 1); tester.CheckResult(kI31CheckSuccess, 1); tester.CheckResult(kStructCheckFailure, 0); tester.CheckResult(kArrayCheckFailure, 0); tester.CheckResult(kI31CheckFailure, 0); tester.CheckResult(kStructCastSuccess, 1); tester.CheckResult(kArrayCastSuccess, 1); tester.CheckResult(kI31CastSuccess, 1); tester.CheckHasThrown(kStructCastFailure); tester.CheckHasThrown(kArrayCastFailure); tester.CheckHasThrown(kI31CastFailure); tester.CheckResult(kBrOnStructTaken, 1); tester.CheckResult(kBrOnStructNotTaken, 0); tester.CheckResult(kBrOnArrayTaken, 1); tester.CheckResult(kBrOnArrayNotTaken, 0); tester.CheckResult(kBrOnI31Taken, 1); tester.CheckResult(kBrOnI31NotTaken, 0); tester.CheckResult(kBrOnNonStructTaken, 0); tester.CheckResult(kBrOnNonStructNotTaken, 1); tester.CheckResult(kBrOnNonArrayTaken, 0); tester.CheckResult(kBrOnNonArrayNotTaken, 1); tester.CheckResult(kBrOnNonI31Taken, 0); tester.CheckResult(kBrOnNonI31NotTaken, 1); } // This flushed out a few bugs, so it serves as a regression test. It can also // be modified (made to run longer) to measure performance of casts. WASM_COMPILED_EXEC_TEST(CastsBenchmark) { WasmGCTester tester(execution_tier); const uint8_t SuperType = tester.DefineStruct({F(wasm::kWasmI32, true)}); const uint8_t SubType = tester.DefineStruct( {F(wasm::kWasmI32, true), F(wasm::kWasmI32, true)}, SuperType); const uint8_t ListType = tester.DefineArray(kWasmStructRef, true); const uint8_t List = tester.AddGlobal(ValueType::RefNull(ListType), true, WasmInitExpr::RefNullConst( static_cast(ListType))); const uint32_t kListLength = 1024; const uint32_t i = 0; const uint8_t Prepare = tester.DefineFunction( tester.sigs.i_v(), {wasm::kWasmI32}, {// List = new eqref[kListLength]; WASM_GLOBAL_SET( List, WASM_ARRAY_NEW_DEFAULT(ListType, WASM_I32V(kListLength))), // for (int i = 0; i < kListLength; ) { // List[i] = new Super(i); // i++; // List[i] = new Sub(i, 0); // i++; // } WASM_LOCAL_SET(i, WASM_I32V_1(0)), WASM_LOOP( WASM_ARRAY_SET(ListType, WASM_GLOBAL_GET(List), WASM_LOCAL_GET(i), WASM_STRUCT_NEW(SuperType, WASM_LOCAL_GET(i))), WASM_LOCAL_SET(i, WASM_I32_ADD(WASM_LOCAL_GET(i), WASM_I32V_1(1))), WASM_ARRAY_SET( ListType, WASM_GLOBAL_GET(List), WASM_LOCAL_GET(i), WASM_STRUCT_NEW(SubType, WASM_LOCAL_GET(i), WASM_I32V_1(0))), WASM_LOCAL_SET(i, WASM_I32_ADD(WASM_LOCAL_GET(i), WASM_I32V_1(1))), WASM_BR_IF(0, WASM_I32_NE(WASM_LOCAL_GET(i), WASM_I32V(kListLength)))), // return 42; // Dummy value, due to test framework. WASM_I32V_1(42), kExprEnd}); const uint32_t sum = 1; // Index of the local. const uint32_t list = 2; const uint32_t kLoops = 2; const uint32_t kIterations = kLoops * kListLength; const uint8_t Main = tester.DefineFunction( tester.sigs.i_v(), { wasm::kWasmI32, wasm::kWasmI32, ValueType::RefNull(ListType), }, {WASM_LOCAL_SET(list, WASM_GLOBAL_GET(List)), // sum = 0; WASM_LOCAL_SET(sum, WASM_I32V_1(0)), // for (int i = 0; i < kIterations; i++) { // sum += ref.cast(List[i & kListLength]).x // } WASM_LOCAL_SET(i, WASM_I32V_1(0)), WASM_LOOP( WASM_LOCAL_SET( sum, WASM_I32_ADD( WASM_LOCAL_GET(sum), WASM_STRUCT_GET( SuperType, 0, WASM_REF_CAST( WASM_ARRAY_GET( ListType, WASM_LOCAL_GET(list), WASM_I32_AND(WASM_LOCAL_GET(i), WASM_I32V(kListLength - 1))), SuperType)))), WASM_LOCAL_SET(i, WASM_I32_ADD(WASM_LOCAL_GET(i), WASM_I32V_1(1))), WASM_BR_IF(0, WASM_I32_LTS(WASM_LOCAL_GET(i), WASM_I32V(kIterations)))), // return sum; WASM_LOCAL_GET(sum), kExprEnd}); tester.CompileModule(); tester.CheckResult(Prepare, 42); // Time this section to get a benchmark for subtyping checks. // Note: if you bump kIterations or kListLength, you may have to take i32 // overflow into account. tester.CheckResult(Main, (kListLength * (kListLength - 1) / 2) * kLoops); } WASM_COMPILED_EXEC_TEST(GlobalInitReferencingGlobal) { WasmGCTester tester(execution_tier); const uint8_t from = tester.AddGlobal(kWasmI32, false, WasmInitExpr(42)); const uint8_t to = tester.AddGlobal(kWasmI32, false, WasmInitExpr::GlobalGet(from)); const uint8_t func = tester.DefineFunction(tester.sigs.i_v(), {}, {WASM_GLOBAL_GET(to), kExprEnd}); tester.CompileModule(); tester.CheckResult(func, 42); } WASM_COMPILED_EXEC_TEST(GCTables) { WasmGCTester tester(execution_tier); tester.builder()->StartRecursiveTypeGroup(); uint8_t super_struct = tester.DefineStruct({F(kWasmI32, false)}); uint8_t sub_struct = tester.DefineStruct( {F(kWasmI32, false), F(kWasmI32, true)}, super_struct); FunctionSig* super_sig = FunctionSig::Build(tester.zone(), {kWasmI32}, {refNull(sub_struct)}); uint8_t super_sig_index = tester.DefineSignature(super_sig); FunctionSig* sub_sig = FunctionSig::Build(tester.zone(), {kWasmI32}, {refNull(super_struct)}); uint8_t sub_sig_index = tester.DefineSignature(sub_sig, super_sig_index); uint8_t unrelated_sig_index = tester.DefineSignature(sub_sig, super_sig_index); tester.builder()->EndRecursiveTypeGroup(); tester.DefineTable(refNull(super_sig_index), 10, 10); uint8_t super_func = tester.DefineFunction( super_sig_index, {}, {WASM_I32_ADD(WASM_STRUCT_GET(sub_struct, 0, WASM_LOCAL_GET(0)), WASM_STRUCT_GET(sub_struct, 1, WASM_LOCAL_GET(0))), WASM_END}); uint8_t sub_func = tester.DefineFunction( sub_sig_index, {}, {WASM_STRUCT_GET(super_struct, 0, WASM_LOCAL_GET(0)), WASM_END}); uint8_t setup_func = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_TABLE_SET(0, WASM_I32V(0), WASM_REF_NULL(super_sig_index)), WASM_TABLE_SET(0, WASM_I32V(1), WASM_REF_FUNC(super_func)), WASM_TABLE_SET(0, WASM_I32V(2), WASM_REF_FUNC(sub_func)), // -- WASM_I32V(0), WASM_END}); uint8_t super_struct_producer = tester.DefineFunction( FunctionSig::Build(tester.zone(), {ref(super_struct)}, {}), {}, {WASM_STRUCT_NEW(super_struct, WASM_I32V(-5)), WASM_END}); uint8_t sub_struct_producer = tester.DefineFunction( FunctionSig::Build(tester.zone(), {ref(sub_struct)}, {}), {}, {WASM_STRUCT_NEW(sub_struct, WASM_I32V(7), WASM_I32V(11)), WASM_END}); // Calling a null entry should trap. uint8_t call_null = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_CALL_INDIRECT(super_sig_index, WASM_CALL_FUNCTION0(sub_struct_producer), WASM_I32V(0)), WASM_END}); // Calling with a signature identical to the type of the table should work, // provided the entry has the same signature. uint8_t call_same_type = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_CALL_INDIRECT(super_sig_index, WASM_CALL_FUNCTION0(sub_struct_producer), WASM_I32V(1)), WASM_END}); // Calling with a signature that is a subtype of the type of the table should // work, provided the entry has the same signature. uint8_t call_subtype = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_CALL_INDIRECT(sub_sig_index, WASM_CALL_FUNCTION0(super_struct_producer), WASM_I32V(2)), WASM_END}); // Calling with a signature that is a subtype of the type of the table should // work, provided the entry has a subtype of the declared signature. uint8_t call_table_subtype_entry_subtype = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_CALL_INDIRECT(super_sig_index, WASM_CALL_FUNCTION0(sub_struct_producer), WASM_I32V(2)), WASM_END}); // Calling with a signature that is mismatched to that of the entry should // trap. uint8_t call_type_mismatch = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_CALL_INDIRECT(unrelated_sig_index, WASM_CALL_FUNCTION0(super_struct_producer), WASM_I32V(2)), WASM_END}); // Getting a table element and then calling it with call_ref should work. uint8_t table_get_and_call_ref = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_CALL_REF(WASM_TABLE_GET(0, WASM_I32V(2)), super_sig_index, WASM_CALL_FUNCTION0(sub_struct_producer)), WASM_END}); // Only here so these functions count as "declared". tester.AddGlobal(refNull(super_sig_index), false, WasmInitExpr::RefFuncConst(super_func)); tester.AddGlobal(refNull(sub_sig_index), false, WasmInitExpr::RefFuncConst(sub_func)); tester.CompileModule(); tester.CheckResult(setup_func, 0); tester.CheckHasThrown(call_null); tester.CheckResult(call_same_type, 18); tester.CheckResult(call_subtype, -5); tester.CheckResult(call_table_subtype_entry_subtype, 7); tester.CheckHasThrown(call_type_mismatch); tester.CheckResult(table_get_and_call_ref, 7); } WASM_COMPILED_EXEC_TEST(JsAccess) { WasmGCTester tester(execution_tier); const uint8_t type_index = tester.DefineStruct({F(wasm::kWasmI32, true)}); ValueType kRefType = ref(type_index); ValueType kSupertypeToI[] = {kWasmI32, kWasmStructRef}; FunctionSig sig_t_v(1, 0, &kRefType); FunctionSig sig_super_v(1, 0, &kWasmStructRef); FunctionSig sig_i_super(1, 1, kSupertypeToI); tester.DefineExportedFunction( "typed_producer", &sig_t_v, {WASM_STRUCT_NEW(type_index, WASM_I32V(42)), kExprEnd}); // Same code, different signature. tester.DefineExportedFunction( "untyped_producer", &sig_super_v, {WASM_STRUCT_NEW(type_index, WASM_I32V(42)), kExprEnd}); tester.DefineExportedFunction( "consumer", &sig_i_super, {WASM_STRUCT_GET(type_index, 0, WASM_REF_CAST(WASM_LOCAL_GET(0), type_index)), kExprEnd}); tester.CompileModule(); Isolate* isolate = tester.isolate(); TryCatch try_catch(reinterpret_cast(isolate)); for (const char* producer : {"typed_producer", "untyped_producer"}) { MaybeHandle maybe_result = tester.CallExportedFunction(producer, 0, nullptr); if (maybe_result.is_null()) { FATAL("Calling %s failed: %s", producer, *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(); } } WASM_COMPILED_EXEC_TEST(WasmExternInternalize) { WasmGCTester tester(execution_tier); const uint8_t kNull = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_REF_IS_NULL(WASM_GC_INTERNALIZE(WASM_REF_NULL(kNoExternCode))), kExprEnd}); tester.CompileModule(); tester.CheckResult(kNull, 1); } WASM_COMPILED_EXEC_TEST(WasmExternExternalize) { WasmGCTester tester(execution_tier); const uint8_t kNull = tester.DefineFunction( tester.sigs.i_v(), {}, {WASM_REF_IS_NULL(WASM_GC_EXTERNALIZE(WASM_REF_NULL(kNoneCode))), kExprEnd}); tester.CompileModule(); tester.CheckResult(kNull, 1); } } // namespace test_gc } // namespace wasm } // namespace internal } // namespace v8