From 5cbd46557651691fe15933b595639d04057706b2 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 23 May 2025 19:46:22 -0700 Subject: [PATCH 1/5] [custom-descriptors] Branching descriptor casts Implement basic support for `br_on_cast_desc` and `br_on_cast_desc_fail` as new variants of `BrOn`. Include binary and text parsing and printing as well as validation. Also handle the new operations anywhere the compiler would otherwise complain about a non-exhaustive switch over the `BrCastOp` enum. For validation of type immediates during parsing, relax the requirement that the cast type is a subtype of the input type. Continue validating in the IR that the non-descriptor branching casts are downcasts, but do not validate this for the new descriptor casts. See https://github.com/WebAssembly/custom-descriptors/issues/37 for context. Add new validation and parsing tests in the form of spec tests in preparation for creating an upstream spec test suite. While we're at it, move the ref.get_desc tests to spec tests, add a few test cases, and fix a bug when the ref.get_desc operand is null. --- scripts/gen-s-parser.py | 6 +- src/gen-s-parser.inc | 34 ++- src/ir/ReFinalize.cpp | 14 +- src/ir/child-typer.h | 15 +- src/ir/cost.h | 17 +- src/parser/parsers.h | 8 +- src/passes/Print.cpp | 26 ++- src/wasm-binary.h | 2 + src/wasm-builder.h | 10 +- src/wasm-delegations-fields.def | 1 + src/wasm-interpreter.h | 82 +++---- src/wasm.h | 7 + src/wasm/wasm-binary.cpp | 9 +- src/wasm/wasm-ir-builder.cpp | 51 +++-- src/wasm/wasm-stack.cpp | 43 ++-- src/wasm/wasm-validator.cpp | 90 ++++++-- src/wasm/wasm.cpp | 60 ++++-- test/lit/basic/custom-descriptors.wast | 120 ++++++++++- test/lit/parse-bad-get-desc.wast | 15 -- test/lit/validation/custom-descriptors.wast | 19 -- test/spec/br_on_cast_desc.wast | 223 ++++++++++++++++++++ test/spec/ref.get_cast.wast | 55 +++++ 22 files changed, 721 insertions(+), 186 deletions(-) delete mode 100644 test/lit/parse-bad-get-desc.wast delete mode 100644 test/lit/validation/custom-descriptors.wast create mode 100644 test/spec/br_on_cast_desc.wast create mode 100644 test/spec/ref.get_cast.wast diff --git a/scripts/gen-s-parser.py b/scripts/gen-s-parser.py index 1ed7d1a189e..f9352dc4802 100755 --- a/scripts/gen-s-parser.py +++ b/scripts/gen-s-parser.py @@ -613,8 +613,10 @@ ("ref.get_desc", "makeRefGetDesc()"), ("br_on_null", "makeBrOnNull()"), ("br_on_non_null", "makeBrOnNull(true)"), - ("br_on_cast", "makeBrOnCast()"), - ("br_on_cast_fail", "makeBrOnCast(true)"), + ("br_on_cast", "makeBrOnCast(BrOnCast)"), + ("br_on_cast_fail", "makeBrOnCast(BrOnCastFail)"), + ("br_on_cast_desc", "makeBrOnCast(BrOnCastDesc)"), + ("br_on_cast_desc_fail", "makeBrOnCast(BrOnCastDescFail)"), ("struct.new", "makeStructNew(false)"), ("struct.new_default", "makeStructNew(true)"), ("struct.get", "makeStructGet()"), diff --git a/src/gen-s-parser.inc b/src/gen-s-parser.inc index 43d7f460370..ef353b0fc97 100644 --- a/src/gen-s-parser.inc +++ b/src/gen-s-parser.inc @@ -170,16 +170,38 @@ switch (buf[0]) { switch (buf[10]) { case '\0': if (op == "br_on_cast"sv) { - CHECK_ERR(makeBrOnCast(ctx, pos, annotations)); + CHECK_ERR(makeBrOnCast(ctx, pos, annotations, BrOnCast)); return Ok{}; } goto parse_error; - case '_': - if (op == "br_on_cast_fail"sv) { - CHECK_ERR(makeBrOnCast(ctx, pos, annotations, true)); - return Ok{}; + case '_': { + switch (buf[11]) { + case 'd': { + switch (buf[15]) { + case '\0': + if (op == "br_on_cast_desc"sv) { + CHECK_ERR(makeBrOnCast(ctx, pos, annotations, BrOnCastDesc)); + return Ok{}; + } + goto parse_error; + case '_': + if (op == "br_on_cast_desc_fail"sv) { + CHECK_ERR(makeBrOnCast(ctx, pos, annotations, BrOnCastDescFail)); + return Ok{}; + } + goto parse_error; + default: goto parse_error; + } + } + case 'f': + if (op == "br_on_cast_fail"sv) { + CHECK_ERR(makeBrOnCast(ctx, pos, annotations, BrOnCastFail)); + return Ok{}; + } + goto parse_error; + default: goto parse_error; } - goto parse_error; + } default: goto parse_error; } } diff --git a/src/ir/ReFinalize.cpp b/src/ir/ReFinalize.cpp index 4c098323735..2efad1bb006 100644 --- a/src/ir/ReFinalize.cpp +++ b/src/ir/ReFinalize.cpp @@ -151,7 +151,7 @@ void ReFinalize::visitRefGetDesc(RefGetDesc* curr) { curr->finalize(); } void ReFinalize::visitBrOn(BrOn* curr) { curr->finalize(); if (curr->type == Type::unreachable) { - replaceUntaken(curr->ref, nullptr); + replaceUntaken(curr->ref, curr->desc); } else { updateBreakValueType(curr->name, curr->getSentType()); } @@ -218,11 +218,11 @@ void ReFinalize::updateBreakValueType(Name name, Type type) { } // Replace an untaken branch/switch with an unreachable value. -// A condition may also exist and may or may not be unreachable. -void ReFinalize::replaceUntaken(Expression* value, Expression* condition) { +// Another child may also exist and may or may not be unreachable. +void ReFinalize::replaceUntaken(Expression* value, Expression* otherChild) { assert(value->type == Type::unreachable); auto* replacement = value; - if (condition) { + if (otherChild) { Builder builder(*getModule()); // Even if we have // (block @@ -233,10 +233,10 @@ void ReFinalize::replaceUntaken(Expression* value, Expression* condition) { // the value is unreachable, and necessary since the type of // the condition did not have an impact before (the break/switch // type was unreachable), and might not fit in. - if (condition->type.isConcrete()) { - condition = builder.makeDrop(condition); + if (otherChild->type.isConcrete()) { + otherChild = builder.makeDrop(otherChild); } - replacement = builder.makeSequence(value, condition); + replacement = builder.makeSequence(value, otherChild); assert(replacement->type.isBasic() && "Basic type expected"); } replaceCurrent(replacement); diff --git a/src/ir/child-typer.h b/src/ir/child-typer.h index c195f36141a..e588e72b1c1 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -865,16 +865,25 @@ template struct ChildTyper : OverriddenVisitor { note(&curr->ref, Type(*ht, Nullable)); } - void visitBrOn(BrOn* curr) { + void visitBrOn(BrOn* curr, std::optional target = std::nullopt) { switch (curr->op) { case BrOnNull: case BrOnNonNull: noteAnyReference(&curr->ref); return; case BrOnCast: - case BrOnCastFail: { - auto top = curr->castType.getHeapType().getTop(); + case BrOnCastFail: + case BrOnCastDesc: + case BrOnCastDescFail: { + if (!target) { + target = curr->castType; + } + auto top = target->getHeapType().getTop(); note(&curr->ref, Type(top, Nullable)); + if (curr->op == BrOnCastDesc || curr->op == BrOnCastDescFail) { + auto descriptor = *target->getHeapType().getDescriptorType(); + note(&curr->desc, Type(descriptor, Nullable)); + } return; } } diff --git a/src/ir/cost.h b/src/ir/cost.h index 5e2ebf3e9a9..cfdf3f09b1b 100644 --- a/src/ir/cost.h +++ b/src/ir/cost.h @@ -673,9 +673,20 @@ struct CostAnalyzer : public OverriddenVisitor { } CostType visitBrOn(BrOn* curr) { // BrOn of a null can be fairly fast, but anything else is a cast check. - CostType base = - curr->op == BrOnNull || curr->op == BrOnNonNull ? 2 : CastCost; - return base + nullCheckCost(curr->ref) + maybeVisit(curr->ref); + switch (curr->op) { + case BrOnNull: + case BrOnNonNull: + return 2 + nullCheckCost(curr->ref) + visit(curr->ref); + case BrOnCast: + case BrOnCastFail: + return CastCost + visit(curr->ref); + case BrOnCastDesc: + case BrOnCastDescFail: + // These are not as expensive as full casts, since they just do a + // identity check on the descriptor. + return 2 + visit(curr->ref) + visit(curr->desc); + } + WASM_UNREACHABLE("unexpected op"); } CostType visitStructNew(StructNew* curr) { CostType ret = AllocationCost + curr->operands.size(); diff --git a/src/parser/parsers.h b/src/parser/parsers.h index c80b8fba888..e7e42050f90 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -238,8 +238,7 @@ template Result<> makeBrOnNull(Ctx&, Index, const std::vector&, bool onFail = false); template -Result<> -makeBrOnCast(Ctx&, Index, const std::vector&, bool onFail = false); +Result<> makeBrOnCast(Ctx&, Index, const std::vector&, BrOnOp op); template Result<> makeStructNew(Ctx&, Index, const std::vector&, bool default_); @@ -2244,15 +2243,14 @@ template Result<> makeBrOnCast(Ctx& ctx, Index pos, const std::vector& annotations, - bool onFail) { + BrOnOp op) { auto label = labelidx(ctx); CHECK_ERR(label); auto in = reftype(ctx); CHECK_ERR(in); auto out = reftype(ctx); CHECK_ERR(out); - return ctx.makeBrOn( - pos, annotations, *label, onFail ? BrOnCastFail : BrOnCast, *in, *out); + return ctx.makeBrOn(pos, annotations, *label, op, *in, *out); } template diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 8f9763427c8..303097036f7 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -2226,7 +2226,26 @@ struct PrintExpressionContents curr->name.print(o); return; case BrOnCast: - printMedium(o, "br_on_cast "); + case BrOnCastDesc: + case BrOnCastFail: + case BrOnCastDescFail: + switch (curr->op) { + case BrOnCast: + printMedium(o, "br_on_cast"); + break; + case BrOnCastFail: + printMedium(o, "br_on_cast_fail"); + break; + case BrOnCastDesc: + printMedium(o, "br_on_cast_desc"); + break; + case BrOnCastDescFail: + printMedium(o, "br_on_cast_desc_fail"); + break; + default: + WASM_UNREACHABLE("unexpected op"); + } + o << ' '; curr->name.print(o); o << ' '; if (curr->ref->type == Type::unreachable) { @@ -2240,8 +2259,9 @@ struct PrintExpressionContents o << ' '; printType(curr->castType); return; - case BrOnCastFail: - printMedium(o, "br_on_cast_fail "); + printMedium(o, + curr->op == BrOnCastFail ? "br_on_cast_fail " + : "br_on_cast_desc_fail "); curr->name.print(o); o << ' '; if (curr->ref->type == Type::unreachable) { diff --git a/src/wasm-binary.h b/src/wasm-binary.h index fbe151bc472..ed4c5f0fcda 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1174,6 +1174,8 @@ enum ASTNodes { RefCastNull = 0x17, BrOnCast = 0x18, BrOnCastFail = 0x19, + BrOnCastDesc = 0x25, + BrOnCastDescFail = 0x26, AnyConvertExtern = 0x1a, ExternConvertAny = 0x1b, RefI31 = 0x1c, diff --git a/src/wasm-builder.h b/src/wasm-builder.h index f7eb357c112..dddf62e67fb 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -900,12 +900,18 @@ class Builder { ret->finalize(); return ret; } - BrOn* - makeBrOn(BrOnOp op, Name name, Expression* ref, Type castType = Type::none) { + BrOn* makeBrOn(BrOnOp op, + Name name, + Expression* ref, + Type castType = Type::none, + Expression* desc = nullptr) { + assert((desc && (op == BrOnCastDesc || op == BrOnCastDescFail)) || + (!desc && op != BrOnCastDesc && op != BrOnCastDescFail)); auto* ret = wasm.allocator.alloc(); ret->op = op; ret->name = name; ret->ref = ref; + ret->desc = desc; ret->castType = castType; ret->finalize(); return ret; diff --git a/src/wasm-delegations-fields.def b/src/wasm-delegations-fields.def index d2165ba9cbc..af86a2cfc90 100644 --- a/src/wasm-delegations-fields.def +++ b/src/wasm-delegations-fields.def @@ -641,6 +641,7 @@ DELEGATE_FIELD_CASE_START(BrOn) DELEGATE_FIELD_INT(BrOn, op) DELEGATE_FIELD_SCOPE_NAME_USE(BrOn, name) DELEGATE_FIELD_TYPE(BrOn, castType) +DELEGATE_FIELD_OPTIONAL_CHILD(BrOn, desc) DELEGATE_FIELD_CHILD(BrOn, ref) DELEGATE_FIELD_CASE_END(BrOn) diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 5c28265f2d5..3e49a8d640e 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -1677,48 +1677,58 @@ class ExpressionRunner : public OverriddenVisitor { Flow visitBrOn(BrOn* curr) { NOTE_ENTER("BrOn"); // BrOnCast* uses the casting infrastructure, so handle them first. - if (curr->op == BrOnCast || curr->op == BrOnCastFail) { - auto cast = doCast(curr); - if (auto* breaking = cast.getBreaking()) { - return *breaking; - } else if (auto* original = cast.getFailure()) { - if (curr->op == BrOnCast) { - return *original; + switch (curr->op) { + case BrOnCast: + case BrOnCastFail: { + auto cast = doCast(curr); + if (auto* breaking = cast.getBreaking()) { + return *breaking; + } else if (auto* original = cast.getFailure()) { + if (curr->op == BrOnCast) { + return *original; + } else { + return Flow(curr->name, *original); + } } else { - return Flow(curr->name, *original); + auto* result = cast.getSuccess(); + assert(result); + if (curr->op == BrOnCast) { + return Flow(curr->name, *result); + } else { + return *result; + } } - } else { - auto* result = cast.getSuccess(); - assert(result); - if (curr->op == BrOnCast) { - return Flow(curr->name, *result); + } + case BrOnCastDesc: + case BrOnCastDescFail: + WASM_UNREACHABLE("TODO"); + case BrOnNull: + case BrOnNonNull: { + // Otherwise we are just checking for null. + Flow flow = visit(curr->ref); + if (flow.breaking()) { + return flow; + } + const auto& value = flow.getSingleValue(); + NOTE_EVAL1(value); + if (curr->op == BrOnNull) { + // BrOnNull does not propagate the value if it takes the branch. + if (value.isNull()) { + return Flow(curr->name); + } + // If the branch is not taken, we return the non-null value. + return {value}; } else { - return *result; + // BrOnNonNull does not return a value if it does not take the branch. + if (value.isNull()) { + return Flow(); + } + // If the branch is taken, we send the non-null value. + return Flow(curr->name, value); } } } - // Otherwise we are just checking for null. - Flow flow = visit(curr->ref); - if (flow.breaking()) { - return flow; - } - const auto& value = flow.getSingleValue(); - NOTE_EVAL1(value); - if (curr->op == BrOnNull) { - // BrOnNull does not propagate the value if it takes the branch. - if (value.isNull()) { - return Flow(curr->name); - } - // If the branch is not taken, we return the non-null value. - return {value}; - } else { - // BrOnNonNull does not return a value if it does not take the branch. - if (value.isNull()) { - return Flow(); - } - // If the branch is taken, we send the non-null value. - return Flow(curr->name, value); - } + WASM_UNREACHABLE("unexpected op"); } Flow visitStructNew(StructNew* curr) { NOTE_ENTER("StructNew"); diff --git a/src/wasm.h b/src/wasm.h index 70c0617a914..0f0f699983d 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -608,6 +608,8 @@ enum BrOnOp { BrOnNonNull, BrOnCast, BrOnCastFail, + BrOnCastDesc, + BrOnCastDescFail, }; enum StringNewOp { @@ -1641,6 +1643,11 @@ class BrOn : public SpecificExpression { BrOnOp op; Name name; Expression* ref; + + // Only used for br_on_cast_desc{,_fail} + Expression* desc; + + // Only used for br_on_cast{,_desc}{,_fail} Type castType; void finalize(); diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 9bd4b3f82d8..04855de6878 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -4503,7 +4503,9 @@ Result<> WasmBinaryReader::readInst() { return builder.makeRefGetDesc(type); } case BinaryConsts::BrOnCast: - case BinaryConsts::BrOnCastFail: { + case BinaryConsts::BrOnCastFail: + case BinaryConsts::BrOnCastDesc: + case BinaryConsts::BrOnCastDescFail: { auto flags = getInt8(); auto srcNull = (flags & BinaryConsts::BrOnCastFlag::InputNullable) ? Nullable @@ -4516,7 +4518,10 @@ Result<> WasmBinaryReader::readInst() { auto [dstType, dstExact] = getHeapType(); auto in = Type(srcType, srcNull, srcExact); auto cast = Type(dstType, dstNull, dstExact); - auto kind = op == BinaryConsts::BrOnCast ? BrOnCast : BrOnCastFail; + auto kind = op == BinaryConsts::BrOnCast ? BrOnCast + : op == BinaryConsts::BrOnCastFail ? BrOnCastFail + : op == BinaryConsts::BrOnCastDesc ? BrOnCastDesc + : BrOnCastDescFail; return builder.makeBrOn(label, kind, in, cast); } case BinaryConsts::StructNew: diff --git a/src/wasm/wasm-ir-builder.cpp b/src/wasm/wasm-ir-builder.cpp index 1fb39b14ec6..1e36e753c2a 100644 --- a/src/wasm/wasm-ir-builder.cpp +++ b/src/wasm/wasm-ir-builder.cpp @@ -37,17 +37,17 @@ namespace wasm { namespace { -Result<> validateTypeAnnotation(HeapType type, Expression* child) { - if (child->type == Type::unreachable) { - return Ok{}; - } - if (!child->type.isRef() || - !HeapType::isSubType(child->type.getHeapType(), type)) { - return Err{"invalid reference type on stack"}; +Result<> validateTypeAnnotation(Type type, Expression* child) { + if (!Type::isSubType(child->type, type)) { + return Err{"invalid type on stack"}; } return Ok{}; } +Result<> validateTypeAnnotation(HeapType type, Expression* child) { + return validateTypeAnnotation(Type(type, Nullable), child); +} + } // anonymous namespace Result IRBuilder::addScratchLocal(Type type) { @@ -2029,14 +2029,29 @@ Result<> IRBuilder::makeBrOn( BrOn curr; curr.op = op; curr.castType = out; + curr.desc = nullptr; CHECK_ERR(visitBrOn(&curr)); - if (out != Type::none) { - if (!Type::isSubType(out, in)) { - return Err{"output type is not a subtype of the input type"}; - } - if (!Type::isSubType(curr.ref->type, in)) { - return Err{"expected input to match input type annotation"}; + + // Validate type immediates before we forget them. + switch (op) { + case BrOnNull: + case BrOnNonNull: + break; + case BrOnCastDesc: + case BrOnCastDescFail: { + assert(out.isRef()); + auto descriptor = out.getHeapType().getDescriptorType(); + if (!descriptor) { + return Err{"cast target must have descriptor"}; + } + CHECK_ERR(validateTypeAnnotation(out.with(*descriptor).with(Nullable), + curr.desc)); } + [[fallthrough]]; + case BrOnCast: + case BrOnCastFail: + assert(in.isRef()); + CHECK_ERR(validateTypeAnnotation(in, curr.ref)); } // Extra values need to be sent in a scratch local. @@ -2050,6 +2065,8 @@ Result<> IRBuilder::makeBrOn( case BrOnNonNull: case BrOnCast: case BrOnCastFail: + case BrOnCastDesc: + case BrOnCastDescFail: // Modeled as sending one value. if (extraArity == 0) { return Err{"br_on target does not expect a value"}; @@ -2069,6 +2086,8 @@ Result<> IRBuilder::makeBrOn( break; case BrOnCast: case BrOnCastFail: + case BrOnCastDesc: + case BrOnCastDescFail: testType = in; break; } @@ -2082,7 +2101,7 @@ Result<> IRBuilder::makeBrOn( auto name = getLabelName(label); CHECK_ERR(name); - auto* br = builder.makeBrOn(op, *name, curr.ref, out); + auto* br = builder.makeBrOn(op, *name, curr.ref, out, curr.desc); addBranchHint(br, likely); push(br); return Ok{}; @@ -2106,7 +2125,7 @@ Result<> IRBuilder::makeBrOn( // Perform the branch. CHECK_ERR(visitBrOn(&curr)); - auto* br = builder.makeBrOn(op, extraLabel, curr.ref, out); + auto* br = builder.makeBrOn(op, extraLabel, curr.ref, out, curr.desc); addBranchHint(br, likely); push(br); @@ -2127,6 +2146,7 @@ Result<> IRBuilder::makeBrOn( case BrOnNonNull: WASM_UNREACHABLE("unexpected op"); case BrOnCast: + case BrOnCastDesc: if (out.isNullable()) { resultType = Type(in.getHeapType(), NonNullable); } else { @@ -2134,6 +2154,7 @@ Result<> IRBuilder::makeBrOn( } break; case BrOnCastFail: + case BrOnCastDescFail: if (in.isNonNullable()) { resultType = Type(out.getHeapType(), NonNullable); } else { diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index d0065909905..ab94df75ab4 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -2300,27 +2300,30 @@ void BinaryInstWriter::visitBrOn(BrOn* curr) { o << U32LEB(getBreakIndex(curr->name)); return; case BrOnCast: - case BrOnCastFail: { - o << int8_t(BinaryConsts::GCPrefix); - if (curr->op == BrOnCast) { - o << U32LEB(BinaryConsts::BrOnCast); - } else { - o << U32LEB(BinaryConsts::BrOnCastFail); - } - assert(curr->ref->type.isRef()); - assert(Type::isSubType(curr->castType, curr->ref->type)); - uint8_t flags = (curr->ref->type.isNullable() ? 1 : 0) | - (curr->castType.isNullable() ? 2 : 0); - o << flags; - o << U32LEB(getBreakIndex(curr->name)); - parent.writeHeapType(curr->ref->type.getHeapType(), - curr->ref->type.getExactness()); - parent.writeHeapType(curr->castType.getHeapType(), - curr->castType.getExactness()); - return; - } + o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::BrOnCast); + break; + case BrOnCastFail: + o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::BrOnCastFail); + break; + case BrOnCastDesc: + o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::BrOnCastDesc); + break; + case BrOnCastDescFail: + o << int8_t(BinaryConsts::GCPrefix) + << U32LEB(BinaryConsts::BrOnCastDescFail); + break; } - WASM_UNREACHABLE("invalid br_on_*"); + assert(curr->ref->type.isRef()); + assert(Type::isSubType(curr->castType, curr->ref->type)); + uint8_t flags = (curr->ref->type.isNullable() ? 1 : 0) | + (curr->castType.isNullable() ? 2 : 0); + o << flags; + o << U32LEB(getBreakIndex(curr->name)); + parent.writeHeapType(curr->ref->type.getHeapType(), + curr->ref->type.getExactness()); + parent.writeHeapType(curr->castType.getHeapType(), + curr->castType.getExactness()); + return; } void BinaryInstWriter::visitStructNew(StructNew* curr) { diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 67fac8f3fb9..fd0f1ca1163 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -2976,20 +2976,20 @@ void FunctionValidator::visitRefGetDesc(RefGetDesc* curr) { } void FunctionValidator::visitBrOn(BrOn* curr) { - shouldBeTrue(getModule()->features.hasGC(), - curr, - "br_on_cast requires gc [--enable-gc]"); + shouldBeTrue( + getModule()->features.hasGC(), curr, "br_on* requires gc [--enable-gc]"); if (curr->ref->type == Type::unreachable) { return; } if (!shouldBeTrue( - curr->ref->type.isRef(), curr, "br_on_cast ref must have ref type")) { + curr->ref->type.isRef(), curr, "br_on* ref must have ref type")) { return; } - if (curr->op == BrOnCast || curr->op == BrOnCastFail) { + if (curr->op != BrOnNull && curr->op != BrOnNonNull) { + // Common validation for all br_on_cast* if (!shouldBeTrue(curr->castType.isRef(), curr, - "br_on_cast must have reference cast type")) { + "br_on_cast* must have reference cast type")) { return; } shouldBeEqual( @@ -2997,25 +2997,71 @@ void FunctionValidator::visitBrOn(BrOn* curr) { curr->ref->type.getHeapType().getBottom(), curr, "br_on_cast* target type and ref type must have a common supertype"); - shouldBeSubType( - curr->castType, - curr->ref->type, - curr, - "br_on_cast* target type must be a subtype of its input type"); - // See comment about exactness on visitRefTest. - if (!getModule()->features.hasCustomDescriptors()) { - shouldBeTrue(curr->castType.isInexact() || - curr->castType.with(Nullable) == - curr->ref->type.with(Nullable), + } + switch (curr->op) { + case BrOnNull: + case BrOnNonNull: + shouldBeEqual(curr->castType, + Type(Type::none), + curr, + "non-cast br_on* must not set intendedType field"); + break; + case BrOnCastDesc: + case BrOnCastDescFail: { + shouldBeTrue(getModule()->features.hasCustomDescriptors(), curr, - "br_on_cast* to exact type requires custom descriptors " + "br_on_cast_desc* requires custom descriptors " "[--enable-custom-descriptors]"); + if (!shouldBeTrue(curr->desc && curr->desc->type.isRef(), + curr, + "br_on_cast_desc* descriptor must have ref type")) { + return; + } + auto descriptor = curr->desc->type.getHeapType(); + if (!descriptor.isBottom()) { + auto described = descriptor.getDescribedType(); + if (!shouldBeTrue( + bool(described), + curr, + "br_on_cast_desc* descriptor should have a described type")) { + return; + } + shouldBeEqual( + *described, + curr->castType.getHeapType(), + curr, + "br_on_cast_desc* cast type should be described by descriptor"); + shouldBeEqual( + curr->castType.getExactness(), + curr->desc->type.getExactness(), + curr, + "br_on_cast_desc* cast exactness should match descriptor exactness"); + shouldBeTrue(curr->ref->type.isNullable() || + curr->castType.isNonNullable(), + curr, + "br_on_cast_desc* with non-nullable ref should have " + "non-nullable cast type"); + } + break; + } + case BrOnCast: + case BrOnCastFail: { + shouldBeSubType( + curr->castType, + curr->ref->type, + curr, + "br_on_cast* target type must be a subtype of its input type"); + // See comment about exactness on visitRefTest. + if (!getModule()->features.hasCustomDescriptors()) { + shouldBeTrue(curr->castType.isInexact() || + curr->castType.with(Nullable) == + curr->ref->type.with(Nullable), + curr, + "br_on_cast* to exact type requires custom descriptors " + "[--enable-custom-descriptors]"); + } + break; } - } else { - shouldBeEqual(curr->castType, - Type(Type::none), - curr, - "non-cast br_on* must not set intendedType field"); } noteBreak(curr->name, curr->getSentType(), curr); } diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index aa91b29da30..0823958e316 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -1077,34 +1077,59 @@ void RefGetDesc::finalize() { return; } - auto desc = ref->type.getHeapType().getDescriptorType(); - assert(desc); - type = Type(*desc, NonNullable, ref->type.getExactness()); + if (ref->type.isNull()) { + // The operation will trap. Model it as returning an uninhabitable type. + type = ref->type.with(NonNullable); + } else { + auto desc = ref->type.getHeapType().getDescriptorType(); + assert(desc); + type = Type(*desc, NonNullable, ref->type.getExactness()); + } } void BrOn::finalize() { - if (ref->type == Type::unreachable) { + if (ref->type == Type::unreachable || + (desc && desc->type == Type::unreachable)) { type = Type::unreachable; return; } if (op == BrOnCast || op == BrOnCastFail) { - // The cast type must be a subtype of the input type. If we've refined the - // input type so that this is no longer true, we can fix it by similarly - // refining the cast type in a way that will not change the cast behavior. + // If we've refined the input type so that it is no longer a subtype of the + // cast type, we can improve the cast type in a way that will not change the + // cast behavior. This satisfies the constraint we had before Custom + // Descriptors that the cast type is a subtype of the input type. castType = Type::getGreatestLowerBound(castType, ref->type); assert(castType.isRef()); + } else if (op == BrOnCastDesc || op == BrOnCastDescFail) { + if (desc->type.isNull()) { + // Cast will never be executed and the instruction will not be emitted. + // Model this with an uninhabitable cast type. + castType = desc->type.with(NonNullable); + } else { + // The cast heap type and exactness is determined by the descriptor's + // type. Its nullability can be improved if the input value is + // non-nullable. + auto heapType = desc->type.getHeapType().getDescribedType(); + assert(heapType); + auto exactness = desc->type.getExactness(); + castType = castType.with(*heapType).with(exactness); + if (ref->type.isNonNullable()) { + castType = castType.with(NonNullable); + } + } } switch (op) { case BrOnNull: // If we do not branch, we flow out the existing value as non-null. type = ref->type.with(NonNullable); - break; + return; case BrOnNonNull: // If we do not branch, we flow out nothing (the spec could also have had // us flow out the null, but it does not). type = Type::none; - break; + return; case BrOnCast: + case BrOnCastDesc: if (castType.isNullable()) { // Nulls take the branch, so the result is non-nullable. type = ref->type.with(NonNullable); @@ -1113,8 +1138,9 @@ void BrOn::finalize() { // the input is. type = ref->type; } - break; + return; case BrOnCastFail: + case BrOnCastDescFail: if (castType.isNullable()) { // Nulls do not take the branch, so the result is non-nullable only if // the input is. @@ -1123,10 +1149,9 @@ void BrOn::finalize() { // Nulls take the branch, so the result is non-nullable. type = castType; } - break; - default: - WASM_UNREACHABLE("invalid br_on_*"); + return; } + WASM_UNREACHABLE("invalid br_on_*"); } Type BrOn::getSentType() { @@ -1137,12 +1162,13 @@ Type BrOn::getSentType() { case BrOnNonNull: // If the input is unreachable, the branch is not taken, and there is no // valid type we can report as being sent. Report it as unreachable. - if (ref->type == Type::unreachable) { + if (type == Type::unreachable) { return Type::unreachable; } // BrOnNonNull sends the non-nullable type on the branch. return ref->type.with(NonNullable); case BrOnCast: + case BrOnCastDesc: // The same as the result type of br_on_cast_fail. if (castType.isNullable()) { return castType.with(ref->type.getNullability()); @@ -1150,8 +1176,9 @@ Type BrOn::getSentType() { return castType; } case BrOnCastFail: + case BrOnCastDescFail: // The same as the result type of br_on_cast (if reachable). - if (ref->type == Type::unreachable) { + if (type == Type::unreachable) { return Type::unreachable; } if (castType.isNullable()) { @@ -1159,9 +1186,8 @@ Type BrOn::getSentType() { } else { return ref->type; } - default: - WASM_UNREACHABLE("invalid br_on_*"); } + WASM_UNREACHABLE("invalid br_on_*"); } void StructNew::finalize() { diff --git a/test/lit/basic/custom-descriptors.wast b/test/lit/basic/custom-descriptors.wast index ab3b70f7ab0..4e0ed93b867 100644 --- a/test/lit/basic/custom-descriptors.wast +++ b/test/lit/basic/custom-descriptors.wast @@ -25,8 +25,12 @@ ) (rec + ;; CHECK-TEXT: (type $3 (func (param anyref (ref null $describing)))) + ;; CHECK-TEXT: (rec ;; CHECK-TEXT-NEXT: (type $shared-described (shared (descriptor $shared-describing (struct)))) + ;; CHECK-BIN: (type $3 (func (param anyref (ref null $describing)))) + ;; CHECK-BIN: (rec ;; CHECK-BIN-NEXT: (type $shared-described (shared (descriptor $shared-describing (struct)))) (type $shared-described (shared (descriptor $shared-describing (struct)))) @@ -36,10 +40,10 @@ ) - ;; CHECK-TEXT: (type $5 (func (param (ref null $described) (ref null (exact $middle))))) + ;; CHECK-TEXT: (type $6 (func (param (ref null $described) (ref null (exact $middle))))) ;; CHECK-TEXT: (global $g (ref null $described) (ref.null none)) - ;; CHECK-BIN: (type $5 (func (param (ref null $described) (ref null (exact $middle))))) + ;; CHECK-BIN: (type $6 (func (param (ref null $described) (ref null (exact $middle))))) ;; CHECK-BIN: (global $g (ref null $described) (ref.null none)) (global $g (ref null $described) (ref.null none)) @@ -47,7 +51,7 @@ ;; CHECK-BIN: (global $shared (ref null $shared-describing) (ref.null (shared none))) (global $shared (ref null $shared-describing) (ref.null (shared none))) - ;; CHECK-TEXT: (func $ref-get-desc (type $5) (param $described (ref null $described)) (param $middle-exact (ref null (exact $middle))) + ;; CHECK-TEXT: (func $ref-get-desc (type $6) (param $described (ref null $described)) (param $middle-exact (ref null (exact $middle))) ;; CHECK-TEXT-NEXT: (drop ;; CHECK-TEXT-NEXT: (block $l1 (result (ref $middle)) ;; CHECK-TEXT-NEXT: (ref.get_desc $described @@ -63,7 +67,7 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $ref-get-desc (type $5) (param $described (ref null $described)) (param $middle-exact (ref null (exact $middle))) + ;; CHECK-BIN: (func $ref-get-desc (type $6) (param $described (ref null $described)) (param $middle-exact (ref null (exact $middle))) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (block (result (ref $middle)) ;; CHECK-BIN-NEXT: (ref.get_desc $described @@ -95,6 +99,77 @@ ) ) ) + + ;; CHECK-TEXT: (func $br-on-cast-desc (type $3) (param $any anyref) (param $descriptor (ref null $describing)) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (block $l (result (ref null $middle)) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (br_on_cast_desc $l anyref (ref null $middle) + ;; CHECK-TEXT-NEXT: (local.get $any) + ;; CHECK-TEXT-NEXT: (local.get $descriptor) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $br-on-cast-desc (type $3) (param $any anyref) (param $descriptor (ref null $describing)) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (block $block (result (ref null $middle)) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (br_on_cast_desc $block anyref (ref null $middle) + ;; CHECK-BIN-NEXT: (local.get $any) + ;; CHECK-BIN-NEXT: (local.get $descriptor) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + (func $br-on-cast-desc (param $any anyref) (param $descriptor (ref null $describing)) + (drop + (block $l (result (ref null $middle)) + (br_on_cast_desc $l anyref (ref null $middle) + (local.get $any) + (local.get $descriptor) + ) + (unreachable) + ) + ) + ) + + ;; CHECK-TEXT: (func $br-on-cast-desc-fail (type $3) (param $any anyref) (param $descriptor (ref null $describing)) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (block $l (result anyref) + ;; CHECK-TEXT-NEXT: (br_on_cast_desc_fail $l anyref (ref null $middle) + ;; CHECK-TEXT-NEXT: (local.get $any) + ;; CHECK-TEXT-NEXT: (local.get $descriptor) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $br-on-cast-desc-fail (type $3) (param $any anyref) (param $descriptor (ref null $describing)) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (block $block (result anyref) + ;; CHECK-BIN-NEXT: (br_on_cast_desc_fail $block anyref (ref null $middle) + ;; CHECK-BIN-NEXT: (local.get $any) + ;; CHECK-BIN-NEXT: (local.get $descriptor) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + (func $br-on-cast-desc-fail (param $any anyref) (param $descriptor (ref null $describing)) + (drop + (block $l (result anyref) + (br_on_cast_desc_fail $l anyref (ref null $middle) + (local.get $any) + (local.get $descriptor) + ) + ) + ) + ) + + ;; TODO: upcast, unreachable ref, null ref, unreachable desc, null desc, exact desc, immediates are not refs ) ;; CHECK-BIN-NODEBUG: (rec ;; CHECK-BIN-NODEBUG-NEXT: (type $0 (descriptor $1 (struct))) @@ -103,18 +178,20 @@ ;; CHECK-BIN-NODEBUG: (type $2 (describes $1 (struct))) +;; CHECK-BIN-NODEBUG: (type $3 (func (param anyref (ref null $2)))) + ;; CHECK-BIN-NODEBUG: (rec -;; CHECK-BIN-NODEBUG-NEXT: (type $3 (shared (descriptor $4 (struct)))) +;; CHECK-BIN-NODEBUG-NEXT: (type $4 (shared (descriptor $5 (struct)))) -;; CHECK-BIN-NODEBUG: (type $4 (shared (describes $3 (struct)))) +;; CHECK-BIN-NODEBUG: (type $5 (shared (describes $4 (struct)))) -;; CHECK-BIN-NODEBUG: (type $5 (func (param (ref null $0) (ref null (exact $1))))) +;; CHECK-BIN-NODEBUG: (type $6 (func (param (ref null $0) (ref null (exact $1))))) ;; CHECK-BIN-NODEBUG: (global $global$0 (ref null $0) (ref.null none)) -;; CHECK-BIN-NODEBUG: (global $global$1 (ref null $4) (ref.null (shared none))) +;; CHECK-BIN-NODEBUG: (global $global$1 (ref null $5) (ref.null (shared none))) -;; CHECK-BIN-NODEBUG: (func $0 (type $5) (param $0 (ref null $0)) (param $1 (ref null (exact $1))) +;; CHECK-BIN-NODEBUG: (func $0 (type $6) (param $0 (ref null $0)) (param $1 (ref null (exact $1))) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (block (result (ref $1)) ;; CHECK-BIN-NODEBUG-NEXT: (ref.get_desc $0 @@ -130,3 +207,28 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $1 (type $3) (param $0 anyref) (param $1 (ref null $2)) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result (ref null $1)) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast_desc $block anyref (ref null $1) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $1) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $2 (type $3) (param $0 anyref) (param $1 (ref null $2)) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result anyref) +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast_desc_fail $block anyref (ref null $1) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $1) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) diff --git a/test/lit/parse-bad-get-desc.wast b/test/lit/parse-bad-get-desc.wast deleted file mode 100644 index 9f2ca811c9b..00000000000 --- a/test/lit/parse-bad-get-desc.wast +++ /dev/null @@ -1,15 +0,0 @@ -;; RUN: not wasm-opt %s -S -o - 2>&1 | filecheck %s - -;; CHECK: 10:7: error: expected type with descriptor - -(module - (type $no-descriptor (struct)) - - (func $ref.get_desc-no-descriptor (param (ref null $no-descriptor)) - (drop - (ref.get_desc $no-descriptor - (local.get 0) - ) - ) - ) -) diff --git a/test/lit/validation/custom-descriptors.wast b/test/lit/validation/custom-descriptors.wast deleted file mode 100644 index f304e792506..00000000000 --- a/test/lit/validation/custom-descriptors.wast +++ /dev/null @@ -1,19 +0,0 @@ -;; Test that custom descriptors instructions are validated correctly. - -;; RUN: not wasm-opt %s -all 2>&1 | filecheck %s - -;; CHECK: [wasm-validator error in function ref.get_desc-inexact] function body type must match, if function returns - -(module - (rec - (type $described (descriptor $describing (struct))) - (type $describing (describes $described (struct))) - ) - - (func $ref.get_desc-inexact (param (ref null $described)) (result (ref (exact $describing))) - ;; The result should be inexact because the input is inexact. - (ref.get_desc $described - (local.get 0) - ) - ) -) diff --git a/test/spec/br_on_cast_desc.wast b/test/spec/br_on_cast_desc.wast new file mode 100644 index 00000000000..6a9546eae0a --- /dev/null +++ b/test/spec/br_on_cast_desc.wast @@ -0,0 +1,223 @@ +(module + (rec + (type $super (sub (descriptor $super.desc (struct)))) + (type $super.desc (sub (describes $super (struct)))) + + (type $sub (sub $super (descriptor $sub.desc (struct)))) + (type $sub.desc (sub $super.desc (describes $sub (struct)))) + ) + + ;; br_on_cast_desc + + (func $br_on_cast_desc-unreachable (result anyref) + (unreachable) + (br_on_cast_desc 0 anyref (ref null $super)) + ) + (func $br_on_cast_desc-null (param anyref) (result anyref) + (br_on_cast_desc 0 anyref (ref null $super) + (ref.null none) + (ref.null none) + ) + ) + (func $br_on_cast_desc-upcast (param $sub (ref null $sub)) (param $super.desc (ref null $super.desc)) (result (ref null $super)) + (br_on_cast_desc 0 (ref null $sub) (ref null $super) + (local.get $sub) + (local.get $super.desc) + ) + ) + (func $br_on_cast_desc-exact (param $any anyref) (param $super.desc (ref null (exact $super.desc))) (result (ref null (exact $super))) + ;; The sent type exact because the descriptor is exact. + (br_on_cast_desc 0 anyref (ref null $super) + (local.get $any) + (local.get $super.desc) + ) + (unreachable) + ) + + ;; br_on_cast_desc_fail + + (func $br_on_cast_desc_fail-unreachable (result anyref) + (unreachable) + (br_on_cast_desc_fail 0 anyref (ref null $super)) + ) + (func $br_on_cast_desc_fail-null (param anyref) (result anyref) + (br_on_cast_desc_fail 0 anyref (ref null $super) + (ref.null none) + (ref.null none) + ) + ) + (func $br_on_cast_desc_fail-upcast (param $sub (ref null $sub)) (param $super.desc (ref null $super.desc)) (result (ref null $super)) + (br_on_cast_desc_fail 0 (ref null $sub) (ref null $super) + (local.get $sub) + (local.get $super.desc) + ) + ) + (func $br_on_cast_desc_fail-exact (param $any anyref) (param $super.desc (ref null (exact $super.desc))) (result anyref) + (block (result (ref null (exact $super))) + ;; The result type can be exact because the descriptor is exact. + (br_on_cast_desc_fail 1 anyref (ref null (exact $super)) + (local.get $any) + (local.get $super.desc) + ) + ) + ) +) + +(assert_malformed + ;; Input type must be a reference. + (module quote "(module (rec (type $struct (descriptor $desc (struct))) (type $desc (describes $struct (struct)))) (func (result anyref) (unreachable) (br_on_cast_desc 0 i32 (ref null $struct))))") + "expected reftype" +) + +(assert_malformed + ;; Input type must be a reference. + (module quote "(module (rec (type $struct (descriptor $desc (struct))) (type $desc (describes $struct (struct)))) (func (result anyref) (unreachable) (br_on_cast_desc_fail 0 i32 (ref null $struct))))") + "expected reftype" +) + +(assert_malformed + ;; Cast type must be a reference. + (module quote "(module (func (unreachable) (br_on_cast_desc 0 anyref i32)))") + "expected reftype" +) + +(assert_malformed + ;; Cast type must be a reference. + (module quote "(module (func (unreachable) (br_on_cast_desc_fail 0 anyref i32)))") + "expected reftype" +) + +(assert_invalid + (module + (type (struct)) + (func (result anyref) + (unreachable) + ;; Cannot do a descriptor cast to a type without a descriptor. + (br_on_cast_desc 0 anyref (ref null 0)) + ) + ) + "cast target must have descriptor" +) + +(assert_invalid + (module + (type (struct)) + (func (result anyref) + (unreachable) + ;; Cannot do a descriptor cast to a type without a descriptor. + (br_on_cast_desc_fail 0 anyref (ref null 0)) + ) + ) + "cast target must have descriptor" +) + +(assert_invalid + (module + (rec + (type (descriptor 1 (struct))) + (type (describes 0 (struct))) + ) + (func (param anyref) (result anyref) + (br_on_cast_desc 0 eqref (ref null 0) + ;; This should be an eqref but is an anyref. + (local.get 0) + (ref.null none) + ) + ) + ) + "invalid reference type on stack" +) + +(assert_invalid + (module + (rec + (type $struct (descriptor $desc (struct))) + (type $desc (describes $struct (struct))) + ) + (func (param anyref) (result anyref) + (br_on_cast_desc_fail 0 eqref (ref null $struct) + ;; This should be an eqref but is an anyref. + (local.get 0) + (ref.null none) + ) + ) + ) + "invalid reference type on stack" +) + +(assert_invalid + (module + (rec + (type $super (sub (descriptor $super.desc (struct)))) + (type $super.desc (sub (describes $super (struct)))) + + (type $sub (sub $super (descriptor $sub.desc (struct)))) + (type $sub.desc (sub $super.desc (describes $sub (struct)))) + ) + (func (param $any anyref) (param $super.desc (ref null $super.desc)) (result anyref) + (br_on_cast_desc 0 anyref (ref null $sub) + (local.get $any) + ;; This should be a $sub.desc but it is a $super.desc. + (local.get $super.desc) + ) + ) + ) + "invalid reference type on stack" +) + +(assert_invalid + (module + (rec + (type $super (sub (descriptor $super.desc (struct)))) + (type $super.desc (sub (describes $super (struct)))) + + (type $sub (sub $super (descriptor $sub.desc (struct)))) + (type $sub.desc (sub $super.desc (describes $sub (struct)))) + ) + (func (param $any anyref) (param $super.desc (ref null $super.desc)) (result anyref) + (br_on_cast_desc_fail 0 anyref (ref null $sub) + (local.get $any) + ;; This should be a $sub.desc but it is a $super.desc. + (local.get $super.desc) + ) + ) + ) + "invalid reference type on stack" +) + +(assert_invalid + (module + (rec + (type $struct (descriptor $desc (struct))) + (type $desc (describes $struct (struct))) + ) + (func (param $any anyref) (param $desc (ref null $desc)) (result (ref null (exact $struct))) + ;; The sent type is not exact because the descriptor is not exact. + (br_on_cast_desc 0 anyref (ref null $struct) + (local.get $any) + (local.get $descriptor) + ) + (unreachable) + ) + ) + "break type must be a subtype" +) + +(assert_invalid + (module + (rec + (type $struct (descriptor $desc (struct))) + (type $desc (describes $struct (struct))) + ) + (func (param $any anyref) (param $desc (ref null $desc)) (result anyref) + (block (result (ref null (exact $struct))) + ;; The result type can be exact because the descriptor is exact. + (br_on_cast_desc_fail 1 anyref (ref null (exact $struct)) + (local.get $any) + (local.get $desc) + ) + ) + ) + ) + "function body type must match" +) diff --git a/test/spec/ref.get_cast.wast b/test/spec/ref.get_cast.wast new file mode 100644 index 00000000000..4c01209ad3e --- /dev/null +++ b/test/spec/ref.get_cast.wast @@ -0,0 +1,55 @@ +(module + (rec + (type $struct (descriptor $desc (struct))) + (type $desc (describes $struct (struct))) + ) + (func $unreachable (param $struct (ref null $struct)) (result (ref $desc)) + (ref.get_desc $struct + (unreachable) + ) + ) + (func $null (param $struct (ref null $struct)) (result (ref $desc)) + (ref.get_desc $struct + (ref.null none) + ) + ) + (func $inexact (param $struct (ref null $struct)) (result (ref $desc)) + (ref.get_desc $struct + (local.get $struct) + ) + ) + (func $exact (param $struct (ref null (exact $struct))) (result (ref (exact $desc))) + (ref.get_desc $struct + (local.get $struct) + ) + ) +) + +(assert_invalid + (module + (rec + (type $struct (descriptor $desc (struct))) + (type $desc (describes $struct (struct))) + ) + (func (param $struct (ref null $struct)) (result (ref (exact $desc))) + ;; The result is not exact if the input is not exact. + (ref.get_desc $struct + (local.get $struct) + ) + ) + ) + "function body must match" +) + +(assert_invalid + (module + (type $struct (struct)) + (func (param $struct (ref null $struct)) (result anyref) + ;; The type must have a descriptor + (ref.get_desc $struct + (local.get $struct) + ) + ) + ) + "expected type with descriptor" +) From 2263d3d8ba92bb913269b0f7bf087bdeffdc0283 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 23 May 2025 22:17:48 -0700 Subject: [PATCH 2/5] fixes --- src/ir/properties.h | 2 +- src/wasm-delegations-fields.def | 25 ++++- src/wasm/wasm-stack.cpp | 7 +- test/lit/basic/custom-descriptors.wast | 148 +++++++++++++++++++++++-- 4 files changed, 167 insertions(+), 15 deletions(-) diff --git a/src/ir/properties.h b/src/ir/properties.h index 4fb0c2b2145..e522b6654ca 100644 --- a/src/ir/properties.h +++ b/src/ir/properties.h @@ -521,7 +521,7 @@ inline bool hasUnwritableTypeImmediate(Expression* curr) { #define DELEGATE_ID curr->_id #define DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD(id, field) \ - { \ + if (curr->cast()->field) { \ auto type = curr->cast()->field->type; \ if (type == Type::unreachable || type.isNull()) { \ return true; \ diff --git a/src/wasm-delegations-fields.def b/src/wasm-delegations-fields.def index af86a2cfc90..f93ebda6db6 100644 --- a/src/wasm-delegations-fields.def +++ b/src/wasm-delegations-fields.def @@ -46,6 +46,14 @@ // present (like a Return's value). If you do not define this then // DELEGATE_FIELD_CHILD is called. // +// DELEGATE_FIELD_OPTIONAL_IMMEDIATE_TYPED_CHILD(id, field) - The previous two +// cases combined. If you do not define this, but you define exactly one of +// DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD and DELEGATE_FIELD_OPTIONAL_CHILD, that +// defined macro will be called. Defining both +// DELEGATE_FIELD_IMMEDIATE_TYPE_CHILD and DELEGATE_FIELD_OPTIONAL_CHILD without +// defining DELEGATE_FIELD_OPTIONAL_IMMEDIATE_TYPED_CHILD is an error. If +// neither of the other macros are defined, then DELEGATE_FIELD_CHILD is called. +// // DELEGATE_FIELD_CHILD_VECTOR(id, field) - called for a variable-sized vector // of child pointers. If this is not defined, and DELEGATE_GET_FIELD is, then // DELEGATE_FIELD_CHILD is called on them. @@ -56,7 +64,7 @@ // DELEGATE_FIELD_INT_ARRAY(id, field) - called for a std::array of fixed size // of integer values (like a SIMD mask). If this is not defined, and // DELEGATE_GET_FIELD is, then DELEGATE_FIELD_INT is called on them. - +// // DELEGATE_FIELD_INT_VECTOR(id, field) - called for a variable-sized vector // of integer values. If this is not defined, and DELEGATE_GET_FIELD is, then // DELEGATE_FIELD_INT is called on them. @@ -113,6 +121,18 @@ #error please define DELEGATE_FIELD_CHILD(id, field) #endif +#ifndef DELEGATE_FIELD_OPTIONAL_IMMEDIATE_TYPED_CHILD +#if defined(DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD) && !defined(DELEGATE_FIELD_OPTIONAL_CHILD) +#define DELEGATE_FIELD_OPTIONAL_IMMEDIATE_TYPED_CHILD(id, field) DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD(id, field) +#elif !defined(DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD) && defined(DELEGATE_FIELD_OPTIONAL_CHILD) +#define DELEGATE_FIELD_OPTIONAL_IMMEDIATE_TYPED_CHILD(id, field) DELEGATE_FIELD_OPTIONAL_CHILD(id, field) +#elif !defined(DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD) && !defined(DELEGATE_FIELD_OPTIONAL_CHILD) +#define DELEGATE_FIELD_OPTIONAL_IMMEDIATE_TYPED_CHILD(id, field) DELEGATE_FIELD_CHILD(id, field) +#else +#error please define DELEGATE_FIELD_OPTIONAL_IMMEDIATE_TYPED_CHILD(id, field) +#endif +#endif + #ifndef DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD #define DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD(id, field) DELEGATE_FIELD_CHILD(id, field) #endif @@ -641,7 +661,7 @@ DELEGATE_FIELD_CASE_START(BrOn) DELEGATE_FIELD_INT(BrOn, op) DELEGATE_FIELD_SCOPE_NAME_USE(BrOn, name) DELEGATE_FIELD_TYPE(BrOn, castType) -DELEGATE_FIELD_OPTIONAL_CHILD(BrOn, desc) +DELEGATE_FIELD_OPTIONAL_IMMEDIATE_TYPED_CHILD(BrOn, desc) DELEGATE_FIELD_CHILD(BrOn, ref) DELEGATE_FIELD_CASE_END(BrOn) @@ -843,6 +863,7 @@ DELEGATE_FIELD_MAIN_END #undef DELEGATE_FIELD_CHILD #undef DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD #undef DELEGATE_FIELD_OPTIONAL_CHILD +#undef DELEGATE_FIELD_OPTIONAL_IMMEDIATE_TYPED_CHILD #undef DELEGATE_FIELD_CHILD_VECTOR #undef DELEGATE_FIELD_INT #undef DELEGATE_FIELD_INT_ARRAY diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index ab94df75ab4..0acaf8b0d6c 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -2290,6 +2290,11 @@ void BinaryInstWriter::visitRefGetDesc(RefGetDesc* curr) { } void BinaryInstWriter::visitBrOn(BrOn* curr) { + bool hasDesc = curr->op == BrOnCastDesc || curr->op == BrOnCastDescFail; + if (hasDesc && curr->desc->type.isNull()) { + emitUnreachable(); + return; + } switch (curr->op) { case BrOnNull: o << int8_t(BinaryConsts::BrOnNull); @@ -2314,7 +2319,7 @@ void BinaryInstWriter::visitBrOn(BrOn* curr) { break; } assert(curr->ref->type.isRef()); - assert(Type::isSubType(curr->castType, curr->ref->type)); + assert(hasDesc || Type::isSubType(curr->castType, curr->ref->type)); uint8_t flags = (curr->ref->type.isNullable() ? 1 : 0) | (curr->castType.isNullable() ? 2 : 0); o << flags; diff --git a/test/lit/basic/custom-descriptors.wast b/test/lit/basic/custom-descriptors.wast index 4e0ed93b867..7cef783b9f8 100644 --- a/test/lit/basic/custom-descriptors.wast +++ b/test/lit/basic/custom-descriptors.wast @@ -27,10 +27,14 @@ (rec ;; CHECK-TEXT: (type $3 (func (param anyref (ref null $describing)))) + ;; CHECK-TEXT: (type $4 (func (param anyref) (result anyref))) + ;; CHECK-TEXT: (rec ;; CHECK-TEXT-NEXT: (type $shared-described (shared (descriptor $shared-describing (struct)))) ;; CHECK-BIN: (type $3 (func (param anyref (ref null $describing)))) + ;; CHECK-BIN: (type $4 (func (param anyref) (result anyref))) + ;; CHECK-BIN: (rec ;; CHECK-BIN-NEXT: (type $shared-described (shared (descriptor $shared-describing (struct)))) (type $shared-described (shared (descriptor $shared-describing (struct)))) @@ -40,10 +44,14 @@ ) - ;; CHECK-TEXT: (type $6 (func (param (ref null $described) (ref null (exact $middle))))) + ;; CHECK-TEXT: (type $7 (func (param (ref null $described) (ref null (exact $middle))))) + + ;; CHECK-TEXT: (type $8 (func)) ;; CHECK-TEXT: (global $g (ref null $described) (ref.null none)) - ;; CHECK-BIN: (type $6 (func (param (ref null $described) (ref null (exact $middle))))) + ;; CHECK-BIN: (type $7 (func (param (ref null $described) (ref null (exact $middle))))) + + ;; CHECK-BIN: (type $8 (func)) ;; CHECK-BIN: (global $g (ref null $described) (ref.null none)) (global $g (ref null $described) (ref.null none)) @@ -51,7 +59,7 @@ ;; CHECK-BIN: (global $shared (ref null $shared-describing) (ref.null (shared none))) (global $shared (ref null $shared-describing) (ref.null (shared none))) - ;; CHECK-TEXT: (func $ref-get-desc (type $6) (param $described (ref null $described)) (param $middle-exact (ref null (exact $middle))) + ;; CHECK-TEXT: (func $ref-get-desc (type $7) (param $described (ref null $described)) (param $middle-exact (ref null (exact $middle))) ;; CHECK-TEXT-NEXT: (drop ;; CHECK-TEXT-NEXT: (block $l1 (result (ref $middle)) ;; CHECK-TEXT-NEXT: (ref.get_desc $described @@ -67,7 +75,7 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $ref-get-desc (type $6) (param $described (ref null $described)) (param $middle-exact (ref null (exact $middle))) + ;; CHECK-BIN: (func $ref-get-desc (type $7) (param $described (ref null $described)) (param $middle-exact (ref null (exact $middle))) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (block (result (ref $middle)) ;; CHECK-BIN-NEXT: (ref.get_desc $described @@ -100,6 +108,32 @@ ) ) + ;; CHECK-TEXT: (func $ref-get-desc-null (type $8) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (block ;; (replaces unreachable RefGetDesc we can't emit) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (ref.null none) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $ref-get-desc-null (type $8) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (ref.null none) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + (func $ref-get-desc-null + (drop + (ref.get_desc $described + (ref.null none) + ) + ) + ) + ;; CHECK-TEXT: (func $br-on-cast-desc (type $3) (param $any anyref) (param $descriptor (ref null $describing)) ;; CHECK-TEXT-NEXT: (drop ;; CHECK-TEXT-NEXT: (block $l (result (ref null $middle)) @@ -169,6 +203,65 @@ ) ) + ;; CHECK-TEXT: (func $br-on-cast-desc-null (type $4) (param $0 anyref) (result anyref) + ;; CHECK-TEXT-NEXT: (block $label (result anyref) + ;; CHECK-TEXT-NEXT: (block ;; (replaces unreachable BrOn we can't emit) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (ref.null none) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (ref.null none) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $br-on-cast-desc-null (type $4) (param $0 anyref) (result anyref) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (ref.null none) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (ref.null none) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) + (func $br-on-cast-desc-null (param anyref) (result anyref) + (br_on_cast_desc 0 anyref (ref null $described) + (ref.null none) + (ref.null none) + ) + ) + + ;; CHECK-TEXT: (func $br-on-cast-desc-fail-null (type $4) (param $0 anyref) (result anyref) + ;; CHECK-TEXT-NEXT: (block $label (result anyref) + ;; CHECK-TEXT-NEXT: (block ;; (replaces unreachable BrOn we can't emit) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (ref.null none) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (ref.null none) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $br-on-cast-desc-fail-null (type $4) (param $0 anyref) (result anyref) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (ref.null none) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (ref.null none) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) + (func $br-on-cast-desc-fail-null (param anyref) (result anyref) + (br_on_cast_desc_fail 0 anyref (ref null $described) + (ref.null none) + (ref.null none) + ) + ) + + ;; TODO: upcast, unreachable ref, null ref, unreachable desc, null desc, exact desc, immediates are not refs ) ;; CHECK-BIN-NODEBUG: (rec @@ -180,18 +273,22 @@ ;; CHECK-BIN-NODEBUG: (type $3 (func (param anyref (ref null $2)))) +;; CHECK-BIN-NODEBUG: (type $4 (func (param anyref) (result anyref))) + ;; CHECK-BIN-NODEBUG: (rec -;; CHECK-BIN-NODEBUG-NEXT: (type $4 (shared (descriptor $5 (struct)))) +;; CHECK-BIN-NODEBUG-NEXT: (type $5 (shared (descriptor $6 (struct)))) -;; CHECK-BIN-NODEBUG: (type $5 (shared (describes $4 (struct)))) +;; CHECK-BIN-NODEBUG: (type $6 (shared (describes $5 (struct)))) -;; CHECK-BIN-NODEBUG: (type $6 (func (param (ref null $0) (ref null (exact $1))))) +;; CHECK-BIN-NODEBUG: (type $7 (func (param (ref null $0) (ref null (exact $1))))) + +;; CHECK-BIN-NODEBUG: (type $8 (func)) ;; CHECK-BIN-NODEBUG: (global $global$0 (ref null $0) (ref.null none)) -;; CHECK-BIN-NODEBUG: (global $global$1 (ref null $5) (ref.null (shared none))) +;; CHECK-BIN-NODEBUG: (global $global$1 (ref null $6) (ref.null (shared none))) -;; CHECK-BIN-NODEBUG: (func $0 (type $6) (param $0 (ref null $0)) (param $1 (ref null (exact $1))) +;; CHECK-BIN-NODEBUG: (func $0 (type $7) (param $0 (ref null $0)) (param $1 (ref null (exact $1))) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (block (result (ref $1)) ;; CHECK-BIN-NODEBUG-NEXT: (ref.get_desc $0 @@ -208,7 +305,16 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $1 (type $3) (param $0 anyref) (param $1 (ref null $2)) +;; CHECK-BIN-NODEBUG: (func $1 (type $8) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $2 (type $3) (param $0 anyref) (param $1 (ref null $2)) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (block $block (result (ref null $1)) ;; CHECK-BIN-NODEBUG-NEXT: (drop @@ -222,7 +328,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $2 (type $3) (param $0 anyref) (param $1 (ref null $2)) +;; CHECK-BIN-NODEBUG: (func $3 (type $3) (param $0 anyref) (param $1 (ref null $2)) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (block $block (result anyref) ;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast_desc_fail $block anyref (ref null $1) @@ -232,3 +338,23 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $4 (type $4) (param $0 anyref) (result anyref) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $5 (type $4) (param $0 anyref) (result anyref) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) From 9ea13484718c032b7ce63285e387f664976490c4 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 23 May 2025 22:18:56 -0700 Subject: [PATCH 3/5] add tests to initial contents block list --- scripts/test/fuzzing.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index b057f84545b..7975b1074b4 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -113,6 +113,8 @@ 'vacuum-stack-switching.wast', # TODO: fuzzer support for custom descriptors 'custom-descriptors.wast', + 'br_on_cast_desc.wast', + 'ref.get_cast.wast', # TODO: fix split_wast() on tricky escaping situations like a string ending # in \\" (the " is not escaped - there is an escaped \ before it) 'string-lifting-section.wast', From 9c26a01169800e9760adebdd53e5f66375349b4e Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Sat, 24 May 2025 00:33:58 -0700 Subject: [PATCH 4/5] undo bad change --- src/wasm/wasm.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 0823958e316..2fc1238a255 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -1162,7 +1162,7 @@ Type BrOn::getSentType() { case BrOnNonNull: // If the input is unreachable, the branch is not taken, and there is no // valid type we can report as being sent. Report it as unreachable. - if (type == Type::unreachable) { + if (ref->type == Type::unreachable) { return Type::unreachable; } // BrOnNonNull sends the non-nullable type on the branch. @@ -1178,7 +1178,7 @@ Type BrOn::getSentType() { case BrOnCastFail: case BrOnCastDescFail: // The same as the result type of br_on_cast (if reachable). - if (type == Type::unreachable) { + if (ref->type == Type::unreachable) { return Type::unreachable; } if (castType.isNullable()) { From 715ea5028c57499edf4b311a1ede32c7db3b681d Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 27 May 2025 15:45:17 -0700 Subject: [PATCH 5/5] fix error message --- src/wasm/wasm-validator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index fd0f1ca1163..906f01f1b5f 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -3004,7 +3004,7 @@ void FunctionValidator::visitBrOn(BrOn* curr) { shouldBeEqual(curr->castType, Type(Type::none), curr, - "non-cast br_on* must not set intendedType field"); + "non-cast br_on* must not set castType field"); break; case BrOnCastDesc: case BrOnCastDescFail: {