diff --git a/src/compiler/wasm-compiler.cc b/src/compiler/wasm-compiler.cc index eff3b93c9f..2a08667253 100644 --- a/src/compiler/wasm-compiler.cc +++ b/src/compiler/wasm-compiler.cc @@ -5548,6 +5548,27 @@ Node* WasmGraphBuilder::RefCast(Node* object, Node* rtt, return gasm_->WasmTypeCast(object, rtt, config); } +Node* WasmGraphBuilder::RefCastAbstract(Node* object, wasm::HeapType type, + wasm::WasmCodePosition position) { + bool is_nullable = + compiler::NodeProperties::GetType(object).AsWasm().type.is_nullable(); + switch (type.representation()) { + case wasm::HeapType::kEq: + return RefAsEq(object, is_nullable, position); + case wasm::HeapType::kI31: + return RefAsI31(object, position); + case wasm::HeapType::kStruct: + return RefAsStruct(object, is_nullable, position); + case wasm::HeapType::kArray: + return RefAsArray(object, is_nullable, position); + case wasm::HeapType::kAny: + // Any may never need a cast as it is either implicitly convertible or + // never convertible for any given type. + default: + UNREACHABLE(); + } +} + void WasmGraphBuilder::BrOnCast(Node* object, Node* rtt, WasmTypeCheckConfig config, Node** match_control, Node** match_effect, @@ -5572,6 +5593,17 @@ Node* WasmGraphBuilder::RefIsEq(Node* object, bool object_can_be_null, return done.PhiAt(0); } +Node* WasmGraphBuilder::RefAsEq(Node* object, bool object_can_be_null, + wasm::WasmCodePosition position) { + bool null_succeeds = false; + auto done = gasm_->MakeLabel(); + EqCheck(object, object_can_be_null, CastCallbacks(&done, position), + null_succeeds); + gasm_->Goto(&done); + gasm_->Bind(&done); + return object; +} + Node* WasmGraphBuilder::RefIsStruct(Node* object, bool object_can_be_null, bool null_succeeds) { auto done = gasm_->MakeLabel(MachineRepresentation::kWord32); diff --git a/src/compiler/wasm-compiler.h b/src/compiler/wasm-compiler.h index e00d9ee006..724649d4bd 100644 --- a/src/compiler/wasm-compiler.h +++ b/src/compiler/wasm-compiler.h @@ -497,10 +497,14 @@ class WasmGraphBuilder { Node* RefTestAbstract(Node* object, wasm::HeapType type, bool null_succeeds); Node* RefCast(Node* object, Node* rtt, WasmTypeCheckConfig config, wasm::WasmCodePosition position); + Node* RefCastAbstract(Node* object, wasm::HeapType type, + wasm::WasmCodePosition position); void BrOnCast(Node* object, Node* rtt, WasmTypeCheckConfig config, Node** match_control, Node** match_effect, Node** no_match_control, Node** no_match_effect); Node* RefIsEq(Node* object, bool object_can_be_null, bool null_succeeds); + Node* RefAsEq(Node* object, bool object_can_be_null, + wasm::WasmCodePosition position); Node* RefIsStruct(Node* object, bool object_can_be_null, bool null_succeeds); Node* RefAsStruct(Node* object, bool object_can_be_null, wasm::WasmCodePosition position); diff --git a/src/compiler/wasm-gc-lowering.cc b/src/compiler/wasm-gc-lowering.cc index c4fda4ee3c..b88ae6fccf 100644 --- a/src/compiler/wasm-gc-lowering.cc +++ b/src/compiler/wasm-gc-lowering.cc @@ -154,10 +154,18 @@ Reduction WasmGCLowering::ReduceWasmTypeCast(Node* node) { auto end_label = gasm_.MakeLabel(); if (object_can_be_null) { - gasm_.GotoIf(gasm_.TaggedEqual(object, Null()), &end_label, - BranchHint::kFalse); + Node* is_null = gasm_.TaggedEqual(object, Null()); + if (config.null_succeeds) { + gasm_.GotoIf(is_null, &end_label, BranchHint::kFalse); + } else { + gasm_.TrapIf(is_null, TrapId::kTrapIllegalCast); + } } + // TODO(7748): Only perform SMI check if source type may contain i31, any or + // extern. + gasm_.TrapIf(gasm_.IsI31(object), TrapId::kTrapIllegalCast); + Node* map = gasm_.LoadMap(object); // First, check if types happen to be equal. This has been shown to give large diff --git a/src/wasm/baseline/liftoff-compiler.cc b/src/wasm/baseline/liftoff-compiler.cc index b7802ebdf8..b19ed3d452 100644 --- a/src/wasm/baseline/liftoff-compiler.cc +++ b/src/wasm/baseline/liftoff-compiler.cc @@ -2679,7 +2679,8 @@ class LiftoffCompiler { __ AssertUnreachable(AbortReason::kUnexpectedReturnFromWasmTrap); } - void AssertNull(FullDecoder* decoder, const Value& arg, Value* result) { + void AssertNullImpl(FullDecoder* decoder, const Value& arg, Value* result, + LiftoffCondition cond) { LiftoffRegList pinned; LiftoffRegister obj = pinned.set(__ PopToRegister(pinned)); Label* trap_label = @@ -2688,12 +2689,20 @@ class LiftoffCompiler { LoadNullValue(null.gp(), pinned); { FREEZE_STATE(trapping); - __ emit_cond_jump(kUnequal, trap_label, kRefNull, obj.gp(), null.gp(), + __ emit_cond_jump(cond, trap_label, kRefNull, obj.gp(), null.gp(), trapping); } __ PushRegister(kRefNull, obj); } + void AssertNull(FullDecoder* decoder, const Value& arg, Value* result) { + AssertNullImpl(decoder, arg, result, kUnequal); + } + + void AssertNotNull(FullDecoder* decoder, const Value& arg, Value* result) { + AssertNullImpl(decoder, arg, result, kEqual); + } + void NopForTestingUnsupportedInLiftoff(FullDecoder* decoder) { unsupported(decoder, kOtherReason, "testing opcode"); } @@ -6010,7 +6019,7 @@ class LiftoffCompiler { } void RefCast(FullDecoder* decoder, const Value& obj, const Value& rtt, - Value* result) { + Value* result, bool null_succeeds) { if (v8_flags.experimental_wasm_assume_ref_cast_succeeds) { // Just drop the rtt. __ DropValues(1); @@ -6028,13 +6037,33 @@ class LiftoffCompiler { { FREEZE_STATE(frozen); + NullSucceeds on_null = null_succeeds ? kNullSucceeds : kNullFails; SubtypeCheck(decoder->module_, obj_reg.gp(), obj.type, rtt_reg.gp(), - rtt.type, scratch_null, scratch2, trap_label, kNullSucceeds, + rtt.type, scratch_null, scratch2, trap_label, on_null, frozen); } __ PushRegister(obj.type.kind(), obj_reg); } + void RefCastAbstract(FullDecoder* decoder, const Value& obj, HeapType type, + Value* result_val) { + switch (type.representation()) { + case HeapType::kEq: + return RefAsEq(decoder, obj, result_val); + case HeapType::kI31: + return RefAsI31(decoder, obj, result_val); + case HeapType::kStruct: + return RefAsStruct(decoder, obj, result_val); + case HeapType::kArray: + return RefAsArray(decoder, obj, result_val); + case HeapType::kAny: + // Any may never need a cast as it is either implicitly convertible or + // never convertible for any given type. + default: + UNREACHABLE(); + } + } + void BrOnCast(FullDecoder* decoder, const Value& obj, const Value& rtt, Value* /* result_on_branch */, uint32_t depth) { // Avoid having sequences of branches do duplicate work. @@ -6242,6 +6271,10 @@ class LiftoffCompiler { (this->*type_checker)(check, frozen); } + void RefAsEq(FullDecoder* decoder, const Value& object, Value* result) { + AbstractTypeCast<&LiftoffCompiler::EqCheck>(object, decoder, kRef); + } + void RefAsStruct(FullDecoder* decoder, const Value& object, Value* /* result */) { AbstractTypeCast<&LiftoffCompiler::StructCheck>(object, decoder, kRef); diff --git a/src/wasm/function-body-decoder-impl.h b/src/wasm/function-body-decoder-impl.h index 7934a506e5..4f5536a7a0 100644 --- a/src/wasm/function-body-decoder-impl.h +++ b/src/wasm/function-body-decoder-impl.h @@ -1070,8 +1070,11 @@ struct ControlBase : public PcForErrors { bool null_succeeds) \ F(RefTestAbstract, const Value& obj, HeapType type, Value* result, \ bool null_succeeds) \ - F(RefCast, const Value& obj, const Value& rtt, Value* result) \ + F(RefCast, const Value& obj, const Value& rtt, Value* result, \ + bool null_succeeds) \ + F(RefCastAbstract, const Value& obj, HeapType type, Value* result) \ F(AssertNull, const Value& obj, Value* result) \ + F(AssertNotNull, const Value& obj, Value* result) \ F(BrOnCast, const Value& obj, const Value& rtt, Value* result_on_branch, \ uint32_t depth) \ F(BrOnCastFail, const Value& obj, const Value& rtt, \ @@ -2119,6 +2122,7 @@ class WasmDecoder : public Decoder { if (io) io->BranchDepth(imm); return length + imm.length; } + case kExprRefCast: case kExprRefTest: case kExprRefTestNull: { HeapTypeImmediate imm(WasmFeatures::All(), decoder, @@ -2127,7 +2131,7 @@ class WasmDecoder : public Decoder { return length + imm.length; } case kExprRefTestDeprecated: - case kExprRefCast: + case kExprRefCastDeprecated: case kExprRefCastNop: { IndexImmediate imm(decoder, pc + length, "type index"); if (io) io->TypeIndex(imm); @@ -2358,6 +2362,7 @@ class WasmDecoder : public Decoder { case kExprRefTestNull: case kExprRefTestDeprecated: case kExprRefCast: + case kExprRefCastDeprecated: case kExprRefCastNop: case kExprBrOnCast: case kExprBrOnCastFail: @@ -4794,6 +4799,84 @@ class WasmFullDecoder : public WasmDecoder { Push(value); return opcode_length; } + case kExprRefCast: { + NON_CONST_ONLY + HeapTypeImmediate imm( + this->enabled_, this, this->pc_ + opcode_length, this->module_); + if (!VALIDATE(this->ok())) return 0; + opcode_length += imm.length; + + std::optional rtt; + HeapType target_type = imm.type; + if (imm.type.is_index()) { + rtt = CreateValue(ValueType::Rtt(imm.type.ref_index())); + CALL_INTERFACE_IF_OK_AND_REACHABLE(RttCanon, imm.type.ref_index(), + &rtt.value()); + Push(rtt.value()); + } + + Value obj = Peek(rtt.has_value() ? 1 : 0); + if (!VALIDATE((obj.type.is_object_reference() && + IsSameTypeHierarchy(obj.type.heap_type(), target_type, + this->module_)) || + obj.type.is_bottom())) { + this->DecodeError( + obj.pc(), + "Invalid types for ref.cast: %s of type %s has to " + "be in the same reference type hierarchy as (ref %s)", + SafeOpcodeNameAt(obj.pc()), obj.type.name().c_str(), + target_type.name().c_str()); + return 0; + } + + // TODO(mliedtke): Add support for ref.cast null. + bool null_succeeds = false; + Value value = CreateValue(ValueType::RefMaybeNull( + imm.type, (obj.type.is_bottom() || !null_succeeds) + ? kNonNullable + : obj.type.nullability())); + if (current_code_reachable_and_ok_) { + // This logic ensures that code generation can assume that functions + // can only be cast to function types, and data objects to data types. + if (V8_UNLIKELY(TypeCheckAlwaysSucceeds(obj, target_type))) { + // Drop the rtt from the stack, then forward the object value to the + // result. + if (rtt.has_value()) { + CALL_INTERFACE(Drop); + } + if (obj.type.is_nullable() && !null_succeeds) { + CALL_INTERFACE(AssertNotNull, obj, &value); + } else { + CALL_INTERFACE(Forward, obj, &value); + } + } else if (V8_UNLIKELY(TypeCheckAlwaysFails(obj, target_type, + null_succeeds))) { + if (rtt.has_value()) { + CALL_INTERFACE(Drop); + } + // Unrelated types. The only way this will not trap is if the object + // is null. + if (obj.type.is_nullable() && null_succeeds) { + // Drop rtt from the stack, then assert that obj is null. + CALL_INTERFACE(AssertNull, obj, &value); + } else { + CALL_INTERFACE(Trap, TrapReason::kTrapIllegalCast); + // We know that the following code is not reachable, but according + // to the spec it technically is. Set it to spec-only reachable. + SetSucceedingCodeDynamicallyUnreachable(); + } + } else { + if (rtt.has_value()) { + CALL_INTERFACE(RefCast, obj, rtt.value(), &value, null_succeeds); + } else { + CALL_INTERFACE(RefCastAbstract, obj, target_type, &value); + } + } + } + Drop(1 + rtt.has_value()); + Push(value); + return opcode_length; + } case kExprRefTestNull: case kExprRefTest: { NON_CONST_ONLY @@ -4944,7 +5027,7 @@ class WasmFullDecoder : public WasmDecoder { Push(value); return opcode_length; } - case kExprRefCast: { + case kExprRefCastDeprecated: { NON_CONST_ONLY IndexImmediate imm(this, this->pc_ + opcode_length, "type index"); @@ -4990,7 +5073,8 @@ class WasmFullDecoder : public WasmDecoder { SetSucceedingCodeDynamicallyUnreachable(); } } else { - CALL_INTERFACE(RefCast, obj, rtt, &value); + bool null_succeeds = true; + CALL_INTERFACE(RefCast, obj, rtt, &value, null_succeeds); } } Drop(2); diff --git a/src/wasm/graph-builder-interface.cc b/src/wasm/graph-builder-interface.cc index e73cef442e..844291dcd1 100644 --- a/src/wasm/graph-builder-interface.cc +++ b/src/wasm/graph-builder-interface.cc @@ -477,6 +477,12 @@ class WasmGraphBuildingInterface { Forward(decoder, obj, result); } + void AssertNotNull(FullDecoder* decoder, const Value& obj, Value* result) { + builder_->TrapIfTrue(wasm::TrapReason::kTrapIllegalCast, + builder_->IsNull(obj.node), decoder->position()); + Forward(decoder, obj, result); + } + void NopForTestingUnsupportedInLiftoff(FullDecoder* decoder) {} void Select(FullDecoder* decoder, const Value& cond, const Value& fval, @@ -1254,9 +1260,7 @@ class WasmGraphBuildingInterface { } void RefCast(FullDecoder* decoder, const Value& object, const Value& rtt, - Value* result) { - // TODO(mliedtke): Should be a parameter for generic ref.cast instructions. - const bool null_succeeds = false; + Value* result, bool null_succeeds) { WasmTypeCheckConfig config = ComputeWasmTypeCheckConfig( object.type, rtt.type, decoder->module_, null_succeeds); TFNode* cast_node = v8_flags.experimental_wasm_assume_ref_cast_succeeds @@ -1266,6 +1270,15 @@ class WasmGraphBuildingInterface { SetAndTypeNode(result, cast_node); } + void RefCastAbstract(FullDecoder* decoder, const Value& object, + wasm::HeapType type, Value* result) { + TFNode* node = object.node; + if (!v8_flags.experimental_wasm_assume_ref_cast_succeeds) { + node = builder_->RefCastAbstract(object.node, type, decoder->position()); + } + SetAndTypeNode(result, builder_->TypeGuard(node, result->type)); + } + template diff --git a/src/wasm/wasm-opcodes.h b/src/wasm/wasm-opcodes.h index 7f2dd5a48e..17ed53a197 100644 --- a/src/wasm/wasm-opcodes.h +++ b/src/wasm/wasm-opcodes.h @@ -710,7 +710,8 @@ bool V8_EXPORT_PRIVATE IsJSCompatibleSignature(const FunctionSig* sig, V(RefTest, 0xfb40, _, "ref.test") \ V(RefTestNull, 0xfb48, _, "ref.test null") \ V(RefTestDeprecated, 0xfb44, _, "ref.test") \ - V(RefCast, 0xfb45, _, "ref.cast") \ + V(RefCast, 0xfb41, _, "ref.cast") \ + V(RefCastDeprecated, 0xfb45, _, "ref.cast") \ V(BrOnCast, 0xfb46, _, "br_on_cast") \ V(BrOnCastFail, 0xfb47, _, "br_on_cast_fail") \ V(RefCastNop, 0xfb4c, _, "ref.cast_nop") \ diff --git a/test/cctest/wasm/test-gc.cc b/test/cctest/wasm/test-gc.cc index 8c269e9bc0..395d756ea7 100644 --- a/test/cctest/wasm/test-gc.cc +++ b/test/cctest/wasm/test-gc.cc @@ -592,15 +592,26 @@ WASM_COMPILED_EXEC_TEST(BrOnCast) { WASM_GC_OP(kExprStructGet), type_index, 0, WASM_LOCAL_GET(0), kExprI32Add, kExprEnd}); + const byte 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, 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 byte 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, type_index), WASM_GC_OP(kExprRefCast), type_index, - - WASM_LOCAL_SET(0, WASM_I32V(222))), // Final result. + {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, type_index), WASM_GC_OP(kExprRefCast), + type_index), // Traps WASM_DROP, WASM_LOCAL_GET(0), kExprEnd}); const byte kTypedAfterBranch = tester.DefineFunction( @@ -620,7 +631,8 @@ WASM_COMPILED_EXEC_TEST(BrOnCast) { tester.CompileModule(); tester.CheckResult(kTestStructStatic, 222); - tester.CheckResult(kTestNull, 222); + tester.CheckResult(kTestNullDeprecated, 222); + tester.CheckHasThrown(kTestNull); tester.CheckResult(kTypedAfterBranch, 42); } @@ -1213,6 +1225,11 @@ WASM_COMPILED_EXEC_TEST(RefTrivialCastsStatic) { {WASM_REF_TEST_DEPRECATED(WASM_STRUCT_NEW_DEFAULT(type_index), sig_index), kExprEnd}); + const byte kRefCastNullDeprecated = + tester.DefineFunction(tester.sigs.i_v(), {}, + {WASM_REF_IS_NULL(WASM_REF_CAST_DEPRECATED( + WASM_REF_NULL(type_index), subtype_index)), + kExprEnd}); const byte kRefCastNull = tester.DefineFunction(tester.sigs.i_v(), {}, {WASM_REF_IS_NULL(WASM_REF_CAST( @@ -1223,24 +1240,32 @@ WASM_COMPILED_EXEC_TEST(RefTrivialCastsStatic) { {WASM_REF_IS_NULL( WASM_REF_CAST(WASM_STRUCT_NEW_DEFAULT(subtype_index), type_index)), kExprEnd}); + const byte kRefCastUpcastNullDeprecated = + tester.DefineFunction(tester.sigs.i_v(), {}, + {WASM_REF_IS_NULL(WASM_REF_CAST_DEPRECATED( + WASM_REF_NULL(subtype_index), type_index)), + kExprEnd}); const byte 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 byte 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(WASM_LOCAL_GET(0), sig_index)), - kExprEnd}); - const byte kRefCastUnrelatedNull = tester.DefineFunction( - tester.sigs.i_v(), {}, - {WASM_REF_IS_NULL(WASM_REF_CAST(WASM_REF_NULL(subtype_index), sig_index)), + WASM_REF_IS_NULL(WASM_REF_CAST_DEPRECATED(WASM_LOCAL_GET(0), sig_index)), kExprEnd}); + const byte kRefCastUnrelatedNull = + tester.DefineFunction(tester.sigs.i_v(), {}, + {WASM_REF_IS_NULL(WASM_REF_CAST_DEPRECATED( + WASM_REF_NULL(subtype_index), sig_index)), + kExprEnd}); const byte kRefCastUnrelatedNonNullable = tester.DefineFunction( tester.sigs.i_v(), {}, - {WASM_REF_IS_NULL( - WASM_REF_CAST(WASM_STRUCT_NEW_DEFAULT(type_index), sig_index)), + {WASM_REF_IS_NULL(WASM_REF_CAST_DEPRECATED( + WASM_STRUCT_NEW_DEFAULT(type_index), sig_index)), kExprEnd}); const byte kBrOnCastNull = tester.DefineFunction( @@ -1340,9 +1365,11 @@ WASM_COMPILED_EXEC_TEST(RefTrivialCastsStatic) { tester.CheckResult(kRefTestUnrelatedNullDeprecated, 0); tester.CheckResult(kRefTestUnrelatedNonNullableDeprecated, 0); - tester.CheckResult(kRefCastNull, 1); + tester.CheckResult(kRefCastNullDeprecated, 1); + tester.CheckHasThrown(kRefCastNull); tester.CheckResult(kRefCastUpcast, 0); - tester.CheckResult(kRefCastUpcastNull, 1); + tester.CheckResult(kRefCastUpcastNullDeprecated, 1); + tester.CheckHasThrown(kRefCastUpcastNull); tester.CheckHasThrown(kRefCastUnrelatedNullable); tester.CheckResult(kRefCastUnrelatedNull, 1); tester.CheckHasThrown(kRefCastUnrelatedNonNullable); @@ -1621,8 +1648,8 @@ WASM_COMPILED_EXEC_TEST(CastNullRef) { tester.CheckHasThrown(to_array); tester.CheckHasThrown(to_struct); tester.CheckHasThrown(to_i31); - // Static ref.cast succeeds. - tester.CheckResult(to_struct_idx, 1); + // ref.cast traps on null. + tester.CheckHasThrown(to_struct_idx); } WASM_COMPILED_EXEC_TEST(CallReftypeParameters) { diff --git a/test/common/wasm/wasm-macro-gen.h b/test/common/wasm/wasm-macro-gen.h index 66462abd5b..65115917a9 100644 --- a/test/common/wasm/wasm-macro-gen.h +++ b/test/common/wasm/wasm-macro-gen.h @@ -525,6 +525,8 @@ inline uint16_t ExtractPrefixedOpcodeBytes(WasmOpcode opcode) { ref, WASM_GC_OP(kExprRefTestDeprecated), static_cast(typeidx) #define WASM_REF_TEST(ref, typeidx) \ ref, WASM_GC_OP(kExprRefTest), static_cast(typeidx) +#define WASM_REF_CAST_DEPRECATED(ref, typeidx) \ + ref, WASM_GC_OP(kExprRefCastDeprecated), static_cast(typeidx) #define WASM_REF_CAST(ref, typeidx) \ ref, WASM_GC_OP(kExprRefCast), static_cast(typeidx) // Takes a reference value from the value stack to allow sequences of diff --git a/test/mjsunit/wasm/gc-casts-from-any.js b/test/mjsunit/wasm/gc-casts-from-any.js index 56ca60acca..7761f61437 100644 --- a/test/mjsunit/wasm/gc-casts-from-any.js +++ b/test/mjsunit/wasm/gc-casts-from-any.js @@ -58,6 +58,15 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js'); kGCPrefix, kExprExternInternalize, kGCPrefix, kExprRefTestNull, typeCode, ]).exportFunc(); + + builder.addFunction(`refCast${typeName}`, + makeSig([kWasmExternRef], [kWasmExternRef])) + .addBody([ + kExprLocalGet, 0, + kGCPrefix, kExprExternInternalize, + kGCPrefix, kExprRefCast, typeCode, + kGCPrefix, kExprExternExternalize, + ]).exportFunc(); }); var instance = builder.instantiate(); @@ -134,4 +143,84 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js'); assertEquals([1, 1], wasm.refTestAny(wasm.createFuncRef())); assertEquals([1, 1], wasm.refTestAny(1)); // ref.i31 assertEquals([1, 1], wasm.refTestAny({'JavaScript': 'Object'})); + + // ref.cast + let structSuperObj = wasm.createStructSuper(); + let structSubObj = wasm.createStructSub(); + let arrayObj = wasm.createArray(); + let jsObj = {'JavaScript': 'Object'}; + let funcObj = wasm.createFuncRef(); + + assertTraps(kTrapIllegalCast, () => wasm.refCastStructSuper(null)); + assertTraps(kTrapIllegalCast, () => wasm.refCastStructSuper(undefined)); + assertSame(structSuperObj, wasm.refCastStructSuper(structSuperObj)); + assertSame(structSubObj, wasm.refCastStructSuper(structSubObj)); + assertTraps(kTrapIllegalCast, () => wasm.refCastStructSuper(arrayObj)); + assertTraps(kTrapIllegalCast, () => wasm.refCastStructSuper(funcObj)); + assertTraps(kTrapIllegalCast, () => wasm.refCastStructSuper(1)); + assertTraps(kTrapIllegalCast, () => wasm.refCastStructSuper(jsObj)); + + assertTraps(kTrapIllegalCast, () => wasm.refCastStructSub(null)); + assertTraps(kTrapIllegalCast, () => wasm.refCastStructSub(undefined)); + assertTraps(kTrapIllegalCast, () => wasm.refCastStructSub(structSuperObj)); + assertSame(structSubObj, wasm.refCastStructSub(structSubObj)); + assertTraps(kTrapIllegalCast, () => wasm.refCastStructSub(arrayObj)); + assertTraps(kTrapIllegalCast, () => wasm.refCastStructSub(funcObj)); + assertTraps(kTrapIllegalCast, () => wasm.refCastStructSub(1)); + assertTraps(kTrapIllegalCast, () => wasm.refCastStructSub(jsObj)); + + assertTraps(kTrapIllegalCast, () => wasm.refCastArray(null)); + assertTraps(kTrapIllegalCast, () => wasm.refCastArray(undefined)); + assertTraps(kTrapIllegalCast, () => wasm.refCastArray(structSuperObj)); + assertTraps(kTrapIllegalCast, () => wasm.refCastArray(structSubObj)); + assertSame(arrayObj, wasm.refCastArray(arrayObj)); + assertTraps(kTrapIllegalCast, () => wasm.refCastArray(funcObj)); + assertTraps(kTrapIllegalCast, () => wasm.refCastArray(1)); + assertTraps(kTrapIllegalCast, () => wasm.refCastArray(jsObj)); + + + assertTraps(kTrapIllegalCast, () => wasm.refCastI31(null)); + assertTraps(kTrapIllegalCast, () => wasm.refCastI31(undefined)); + assertTraps(kTrapIllegalCast, () => wasm.refCastI31(structSuperObj)); + assertTraps(kTrapIllegalCast, () => wasm.refCastI31(structSubObj)); + assertTraps(kTrapIllegalCast, () => wasm.refCastI31(arrayObj)); + assertTraps(kTrapIllegalCast, () => wasm.refCastI31(funcObj)); + assertEquals(1, wasm.refCastI31(1)); + assertTraps(kTrapIllegalCast, () => wasm.refCastI31(jsObj)); + + assertTraps(kTrapIllegalCast, () => wasm.refCastAnyArray(null)); + assertTraps(kTrapIllegalCast, () => wasm.refCastAnyArray(undefined)); + assertTraps(kTrapIllegalCast, () => wasm.refCastAnyArray(structSuperObj)); + assertTraps(kTrapIllegalCast, () => wasm.refCastAnyArray(structSubObj)); + assertSame(arrayObj, wasm.refCastAnyArray(arrayObj)); + assertTraps(kTrapIllegalCast, () => wasm.refCastAnyArray(funcObj)); + assertTraps(kTrapIllegalCast, () => wasm.refCastAnyArray(1)); + assertTraps(kTrapIllegalCast, () => wasm.refCastAnyArray(jsObj)); + + assertTraps(kTrapIllegalCast, () => wasm.refCastStruct(null)); + assertTraps(kTrapIllegalCast, () => wasm.refCastStruct(undefined)); + assertSame(structSuperObj, wasm.refCastStruct(structSuperObj)); + assertSame(structSubObj, wasm.refCastStruct(structSubObj)); + assertTraps(kTrapIllegalCast, () => wasm.refCastStruct(arrayObj)); + assertTraps(kTrapIllegalCast, () => wasm.refCastStruct(funcObj)); + assertTraps(kTrapIllegalCast, () => wasm.refCastStruct(1)); + assertTraps(kTrapIllegalCast, () => wasm.refCastStruct(jsObj)); + + assertTraps(kTrapIllegalCast, () => wasm.refCastEq(null)); + assertTraps(kTrapIllegalCast, () => wasm.refCastEq(undefined)); + assertSame(structSuperObj, wasm.refCastEq(structSuperObj)); + assertSame(structSubObj, wasm.refCastEq(structSubObj)); + assertSame(arrayObj, wasm.refCastEq(arrayObj)); + assertTraps(kTrapIllegalCast, () => wasm.refCastEq(funcObj)); + assertEquals(1, wasm.refCastEq(1)); + assertTraps(kTrapIllegalCast, () => wasm.refCastEq(jsObj)); + + assertTraps(kTrapIllegalCast, () => wasm.refCastAny(null)); + assertSame(undefined, wasm.refCastAny(undefined)); + assertSame(structSuperObj, wasm.refCastAny(structSuperObj)); + assertSame(structSubObj, wasm.refCastAny(structSubObj)); + assertSame(arrayObj, wasm.refCastAny(arrayObj)); + assertSame(funcObj, wasm.refCastAny(funcObj)); + assertEquals(1, wasm.refCastAny(1)); + assertSame(jsObj, wasm.refCastAny(jsObj)); })(); diff --git a/test/mjsunit/wasm/gc-casts-invalid.js b/test/mjsunit/wasm/gc-casts-invalid.js index 236d6148bf..ba22b71f03 100644 --- a/test/mjsunit/wasm/gc-casts-invalid.js +++ b/test/mjsunit/wasm/gc-casts-invalid.js @@ -25,6 +25,7 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js'); let casts = [ kExprRefTest, kExprRefTestNull, + kExprRefCast, ]; for (let [source_type, target_type_imm] of types) { @@ -33,10 +34,11 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js'); assertEquals(struct, builder.addStruct([makeField(kWasmI32, true)])); assertEquals(array, builder.addArray(kWasmI32)); assertEquals(sig, builder.addType(makeSig([kWasmI32], []))); - builder.addFunction('refTest', makeSig([kWasmI32], [source_type])) + builder.addFunction('refTest', makeSig([source_type], [])) .addBody([ kExprLocalGet, 0, kGCPrefix, cast, target_type_imm, + kExprDrop, ]); assertThrows(() => builder.instantiate(), diff --git a/test/mjsunit/wasm/gc-casts-subtypes.js b/test/mjsunit/wasm/gc-casts-subtypes.js index ea94987a1e..1e4e4d6a78 100644 --- a/test/mjsunit/wasm/gc-casts-subtypes.js +++ b/test/mjsunit/wasm/gc-casts-subtypes.js @@ -6,8 +6,8 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js'); -// Test casting null from one type to another using ref.test. -(function RefTestNull() { +// Test casting null from one type to another using ref.test & ref.cast. +(function RefCastFromNull() { print(arguments.callee.name); let builder = new WasmModuleBuilder(); let structSuper = builder.addStruct([makeField(kWasmI32, true)]); @@ -36,13 +36,19 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js'); ]; for (let [sourceType, targetType, testName] of tests) { - builder.addFunction('testNull' + testName, - makeSig([], [kWasmI32])) + builder.addFunction('testNull' + testName, makeSig([], [kWasmI32])) .addLocals(wasmRefNullType(sourceType), 1) .addBody([ kExprLocalGet, 0, kGCPrefix, kExprRefTest, targetType & kLeb128Mask, ]).exportFunc(); + builder.addFunction('castNull' + testName, makeSig([], [])) + .addLocals(wasmRefNullType(sourceType), 1) + .addBody([ + kExprLocalGet, 0, + kGCPrefix, kExprRefCast, targetType & kLeb128Mask, + kExprDrop, + ]).exportFunc(); } let instance = builder.instantiate(); @@ -50,6 +56,7 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js'); for (let [sourceType, targetType, testName] of tests) { assertEquals(0, wasm['testNull' + testName]()); + assertTraps(kTrapIllegalCast, wasm['castNull' + testName]); } })(); @@ -80,12 +87,56 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js'); assertEquals([1, 0, 1, 1], wasm.testFromFuncRef(wasm.fctSub)); })(); +(function RefCastFuncRef() { + print(arguments.callee.name); + let builder = new WasmModuleBuilder(); + let sigSuper = builder.addType(makeSig([kWasmI32], [])); + let sigSub = builder.addType(makeSig([kWasmI32], []), sigSuper); + + builder.addFunction('fctSuper', sigSuper).addBody([]).exportFunc(); + builder.addFunction('fctSub', sigSub).addBody([]).exportFunc(); + builder.addFunction('castToFuncRef', makeSig([kWasmFuncRef], [kWasmFuncRef])) + .addBody([kExprLocalGet, 0, kGCPrefix, kExprRefCast, kFuncRefCode]) + .exportFunc(); + builder.addFunction('castToNullFuncRef', + makeSig([kWasmFuncRef], [kWasmFuncRef])) + .addBody([kExprLocalGet, 0, kGCPrefix, kExprRefCast, kNullFuncRefCode]) + .exportFunc(); + builder.addFunction('castToSuper', makeSig([kWasmFuncRef], [kWasmFuncRef])) + .addBody([kExprLocalGet, 0, kGCPrefix, kExprRefCast, sigSuper]) + .exportFunc(); + builder.addFunction('castToSub', makeSig([kWasmFuncRef], [kWasmFuncRef])) + .addBody([kExprLocalGet, 0, kGCPrefix, kExprRefCast, sigSub]) + .exportFunc(); + + let instance = builder.instantiate(); + let wasm = instance.exports; + let jsFct = new WebAssembly.Function( + {parameters:['i32', 'i32'], results: ['i32']}, + function mul(a, b) { return a * b; }); + assertSame(jsFct, wasm.castToFuncRef(jsFct)); + assertSame(wasm.fctSuper, wasm.castToFuncRef(wasm.fctSuper)); + assertSame(wasm.fctSub, wasm.castToFuncRef(wasm.fctSub)); + + assertTraps(kTrapIllegalCast, () => wasm.castToNullFuncRef(jsFct)); + assertTraps(kTrapIllegalCast, () => wasm.castToNullFuncRef(wasm.fctSuper)); + assertTraps(kTrapIllegalCast, () => wasm.castToNullFuncRef(wasm.fctSub)); + + assertTraps(kTrapIllegalCast, () => wasm.castToSuper(jsFct)); + assertSame(wasm.fctSuper, wasm.castToSuper(wasm.fctSuper)); + assertSame(wasm.fctSub, wasm.castToSuper(wasm.fctSub)); + + assertTraps(kTrapIllegalCast, () => wasm.castToSub(jsFct)); + assertTraps(kTrapIllegalCast, () => wasm.castToSub(wasm.fctSuper)); + assertSame(wasm.fctSub, wasm.castToSub(wasm.fctSub)); +})(); + (function RefTestExternRef() { print(arguments.callee.name); let builder = new WasmModuleBuilder(); builder.addFunction('testExternRef', - makeSig([kWasmExternRef], [kWasmI32, kWasmI32,])) + makeSig([kWasmExternRef], [kWasmI32, kWasmI32])) .addBody([ kExprLocalGet, 0, kGCPrefix, kExprRefTest, kExternRefCode, kExprLocalGet, 0, kGCPrefix, kExprRefTest, kNullExternRefCode, @@ -100,6 +151,36 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js'); assertEquals([1, 0], wasm.testExternRef(wasm.testExternRef)); })(); +(function RefCastExternRef() { + print(arguments.callee.name); + let builder = new WasmModuleBuilder(); + + builder.addFunction('castToExternRef', + makeSig([kWasmExternRef], [kWasmExternRef])) + .addBody([kExprLocalGet, 0, kGCPrefix, kExprRefCast, kExternRefCode]) + .exportFunc(); + builder.addFunction('castToNullExternRef', + makeSig([kWasmExternRef], [kWasmExternRef])) + .addBody([kExprLocalGet, 0, kGCPrefix, kExprRefCast, kNullExternRefCode]) + .exportFunc(); + + let instance = builder.instantiate(); + let wasm = instance.exports; + assertTraps(kTrapIllegalCast, () => wasm.castToExternRef(null)); + assertEquals(undefined, wasm.castToExternRef(undefined)); + assertEquals(1, wasm.castToExternRef(1)); + let obj = {}; + assertSame(obj, wasm.castToExternRef(obj)); + assertSame(wasm.castToExternRef, wasm.castToExternRef(wasm.castToExternRef)); + + assertTraps(kTrapIllegalCast, () => wasm.castToNullExternRef(null)); + assertTraps(kTrapIllegalCast, () => wasm.castToNullExternRef(undefined)); + assertTraps(kTrapIllegalCast, () => wasm.castToNullExternRef(1)); + assertTraps(kTrapIllegalCast, () => wasm.castToNullExternRef(obj)); + assertTraps(kTrapIllegalCast, + () => wasm.castToNullExternRef(wasm.castToExternRef)); +})(); + (function RefTestAnyRefHierarchy() { print(arguments.callee.name); let builder = new WasmModuleBuilder(); @@ -136,6 +217,7 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js'); source: 'any', values: ['nullref', 'i31ref', 'structSuper', 'structSub', 'array'], targets: { + any: ['i31ref', 'structSuper', 'structSub', 'array'], eq: ['i31ref', 'structSuper', 'structSub', 'array'], struct: ['structSuper', 'structSub'], anyArray: ['array'], @@ -225,6 +307,15 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js'); kExprCallRef, creatorType, kGCPrefix, kExprRefTestNull, heapType, ]).exportFunc(); + + builder.addFunction(`cast_${test.source}_to_${target}`, + makeSig([wasmRefType(creatorType)], [kWasmI32])) + .addBody([ + kExprLocalGet, 0, + kExprCallRef, creatorType, + kGCPrefix, kExprRefCast, heapType, + kExprRefIsNull, // We can't expose the cast object to JS in most cases. + ]).exportFunc(); } } @@ -242,6 +333,14 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js'); res = wasm[`test_null_${test.source}_to_${target}`](create_value); assertEquals( (validValues.includes(value) || value == "nullref") ? 1 : 0, res); + + print(`Test ref.cast: ${test.source}(${value}) -> ${target}`); + let cast = wasm[`cast_${test.source}_to_${target}`]; + if (validValues.includes(value)) { + assertEquals(0, cast(create_value)); + } else { + assertTraps(kTrapIllegalCast, () => cast(create_value)); + } } } } diff --git a/test/mjsunit/wasm/gc-optimizations.js b/test/mjsunit/wasm/gc-optimizations.js index a063892956..c05516de2f 100644 --- a/test/mjsunit/wasm/gc-optimizations.js +++ b/test/mjsunit/wasm/gc-optimizations.js @@ -468,8 +468,9 @@ d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js"); .addBody([ // Cast from struct_a to struct_b via common base type struct_super. kExprLocalGet, 0, - kGCPrefix, kExprRefCast, struct_super, - kGCPrefix, kExprRefCast, struct_b, // annotated as 'ref null none' + // TODO(7748): Replace cast op with "ref.cast null". + kGCPrefix, kExprRefCastDeprecated, struct_super, + kGCPrefix, kExprRefCastDeprecated, struct_b, // annotated as 'ref null none' kExprRefIsNull, ]); @@ -514,7 +515,8 @@ d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js"); // local.get 0 is known to be null until end of block. kExprLocalGet, 0, // This cast is a no-op and shold be optimized away. - kGCPrefix, kExprRefCast, struct_b, + // TODO(7748): Replace with "ref.cast null". + kGCPrefix, kExprRefCastDeprecated, struct_b, kExprEnd, kExprRefIsNull, ]); diff --git a/test/mjsunit/wasm/wasm-module-builder.js b/test/mjsunit/wasm/wasm-module-builder.js index 0d08ca61d1..ef3c562932 100644 --- a/test/mjsunit/wasm/wasm-module-builder.js +++ b/test/mjsunit/wasm/wasm-module-builder.js @@ -510,7 +510,8 @@ let kExprI31GetU = 0x22; let kExprRefTest = 0x40; let kExprRefTestNull = 0x48; let kExprRefTestDeprecated = 0x44; -let kExprRefCast = 0x45; +let kExprRefCast = 0x41; +let kExprRefCastDeprecated = 0x45; let kExprBrOnCast = 0x46; let kExprBrOnCastFail = 0x47; let kExprRefCastNop = 0x4c; diff --git a/test/unittests/wasm/function-body-decoder-unittest.cc b/test/unittests/wasm/function-body-decoder-unittest.cc index 7aee0c68c8..b818f5ac70 100644 --- a/test/unittests/wasm/function-body-decoder-unittest.cc +++ b/test/unittests/wasm/function-body-decoder-unittest.cc @@ -1160,8 +1160,9 @@ TEST_F(FunctionBodyDecoderTest, UnreachableRefTypes) { ExpectValidates(sigs.i_v(), {WASM_UNREACHABLE, WASM_GC_OP(kExprRefTest), kEqRefCode}); - ExpectValidates(sigs.v_v(), {WASM_UNREACHABLE, WASM_GC_OP(kExprRefCast), - struct_index, kExprDrop}); + ExpectValidates(sigs.v_v(), + {WASM_UNREACHABLE, WASM_GC_OP(kExprRefCastDeprecated), + struct_index, kExprDrop}); ExpectValidates(sigs.v_v(), {WASM_UNREACHABLE, WASM_GC_OP(kExprRefCast), struct_index, kExprDrop}); @@ -1170,6 +1171,9 @@ TEST_F(FunctionBodyDecoderTest, UnreachableRefTypes) { ExpectValidates(&sig_v_s, {WASM_UNREACHABLE, WASM_LOCAL_GET(0), kExprBrOnNull, 0, kExprCallFunction, struct_consumer}); + ExpectValidates( + FunctionSig::Build(zone(), {struct_type}, {}), + {WASM_UNREACHABLE, WASM_GC_OP(kExprRefCastDeprecated), struct_index}); ExpectValidates(FunctionSig::Build(zone(), {struct_type}, {}), {WASM_UNREACHABLE, WASM_GC_OP(kExprRefCast), struct_index}); @@ -4331,7 +4335,7 @@ TEST_F(FunctionBodyDecoderTest, RefTestCast) { HeapType from_heap = HeapType(std::get<0>(test)); HeapType to_heap = HeapType(std::get<1>(test)); bool should_pass = std::get<2>(test); - bool should_pass_ref_test = std::get<3>(test); + bool should_pass_new_ops = std::get<3>(test); SCOPED_TRACE("from_heap = " + from_heap.name() + ", to_heap = " + to_heap.name()); @@ -4346,7 +4350,8 @@ TEST_F(FunctionBodyDecoderTest, RefTestCast) { ExpectValidates(&test_sig, {WASM_REF_TEST_DEPRECATED(WASM_LOCAL_GET(0), WASM_HEAP_TYPE(to_heap))}); - ExpectValidates(&cast_sig, {WASM_REF_CAST(WASM_LOCAL_GET(0), + ExpectValidates(&cast_sig, + {WASM_REF_CAST_DEPRECATED(WASM_LOCAL_GET(0), WASM_HEAP_TYPE(to_heap))}); } else { std::string error_message = @@ -4358,22 +4363,29 @@ TEST_F(FunctionBodyDecoderTest, RefTestCast) { WASM_HEAP_TYPE(to_heap))}, kAppendEnd, ("ref.test" + error_message).c_str()); ExpectFailure(&cast_sig, - {WASM_REF_CAST(WASM_LOCAL_GET(0), WASM_HEAP_TYPE(to_heap))}, + {WASM_REF_CAST_DEPRECATED(WASM_LOCAL_GET(0), + WASM_HEAP_TYPE(to_heap))}, kAppendEnd, ("ref.cast" + error_message).c_str()); } - if (should_pass_ref_test) { + if (should_pass_new_ops) { ExpectValidates(&test_sig, {WASM_REF_TEST(WASM_LOCAL_GET(0), WASM_HEAP_TYPE(to_heap))}); + ExpectValidates(&cast_sig, {WASM_REF_CAST(WASM_LOCAL_GET(0), + WASM_HEAP_TYPE(to_heap))}); } else { std::string error_message = - "Invalid types for ref.test: local.get of type " + - cast_reps[1].name() + + "local.get of type " + cast_reps[1].name() + " has to be in the same reference type hierarchy as (ref " + to_heap.name() + ")"; ExpectFailure(&test_sig, {WASM_REF_TEST(WASM_LOCAL_GET(0), WASM_HEAP_TYPE(to_heap))}, - kAppendEnd, error_message.c_str()); + kAppendEnd, + ("Invalid types for ref.test: " + error_message).c_str()); + ExpectFailure(&cast_sig, + {WASM_REF_CAST(WASM_LOCAL_GET(0), WASM_HEAP_TYPE(to_heap))}, + kAppendEnd, + ("Invalid types for ref.cast: " + error_message).c_str()); } } @@ -4389,10 +4401,15 @@ TEST_F(FunctionBodyDecoderTest, RefTestCast) { "Invalid types for ref.test: i32.const of type i32 has to be " "in the same reference type hierarchy as (ref 0)"); ExpectFailure(sigs.v_v(), - {WASM_REF_CAST(WASM_I32V(1), array_heap), kExprDrop}, + {WASM_REF_CAST_DEPRECATED(WASM_I32V(1), array_heap), kExprDrop}, kAppendEnd, "ref.cast[0] expected subtype of (ref null func), (ref null " "struct) or (ref null array), found i32.const of type i32"); + ExpectFailure(sigs.v_v(), + {WASM_REF_CAST(WASM_I32V(1), array_heap), kExprDrop}, + kAppendEnd, + "Invalid types for ref.cast: i32.const of type i32 has to be " + "in the same reference type hierarchy as (ref 0)"); } TEST_F(FunctionBodyDecoderTest, BrOnCastOrCastFail) { @@ -4407,6 +4424,10 @@ TEST_F(FunctionBodyDecoderTest, BrOnCastOrCastFail) { ValueType supertype = ValueType::RefNull(super_struct); ValueType subtype = ValueType::RefNull(sub_struct); + ExpectValidates( + FunctionSig::Build(this->zone(), {kWasmI32, subtype}, {supertype}), + {WASM_I32V(42), WASM_LOCAL_GET(0), WASM_BR_ON_CAST(0, sub_struct), + WASM_GC_OP(kExprRefCast), sub_struct}); ExpectValidates( FunctionSig::Build(this->zone(), {kWasmI32, subtype}, {supertype}), {WASM_I32V(42), WASM_LOCAL_GET(0), WASM_BR_ON_CAST(0, sub_struct),