From 1744c8e5697f1b8e2e458b36c205301475fe5629 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Thu, 16 Apr 2026 19:51:00 -0700 Subject: [PATCH 1/3] [NFC] Simplify printing of unreachable replacements When an instruction has a type immediate that we cannot print because the expression it is supposed to come from has unreachable or null type, we instead print an unreachable block with a comment saying what instruction we failed to print. We previously handled this via different code paths for type immediates that come from child expressions that type immediates that come from the printed expression's own return type. Unify these code paths in the printer by improving `Properties::hasUnwritableTypeImmediate` to handle both cases. Also fix the printing of `ShallowExpression` to check for unwritable type immediates first to avoid assertion failures. --- src/ir/properties.h | 23 ++++++++++++-- src/passes/Print.cpp | 74 +++----------------------------------------- 2 files changed, 25 insertions(+), 72 deletions(-) diff --git a/src/ir/properties.h b/src/ir/properties.h index e52902597a0..8d1f0faaf84 100644 --- a/src/ir/properties.h +++ b/src/ir/properties.h @@ -521,8 +521,8 @@ inline MemoryOrder getMemoryOrder(Expression* curr) { } // Whether this instruction will be unwritable in the text and binary formats -// because it requires a type index immediate giving the type of a child that -// has unreachable or null type, and therefore does not have a type index. +// because it requires a type index immediate computed from an expression with +// unreachable or null type, and therefore no type index. inline bool hasUnwritableTypeImmediate(Expression* curr) { #define DELEGATE_ID curr->_id @@ -552,6 +552,25 @@ inline bool hasUnwritableTypeImmediate(Expression* curr) { #include "wasm-delegations-fields.def" + if (curr->type == Type::unreachable) { + if (curr->is() || curr->is() || + curr->is() || curr->is() || + curr->is() || curr->is() || + curr->is()) { + return true; + } + if (auto* cast = curr->dynCast()) { + if (!cast->desc) { + return true; + } + if (!cast->desc->type.isRef()) { + return true; + } + if (!cast->desc->type.getHeapType().getDescribedType()) { + return true; + }; + } + } return false; } diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 11a73315b74..68981953211 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -320,63 +320,6 @@ struct PrintSExpression : public UnifiedExpressionVisitor { void visitTryTable(TryTable* curr); void printUnreachableReplacement(Expression* curr); - bool maybePrintUnreachableReplacement(Expression* curr, Type type); - void visitRefCast(RefCast* curr) { - if ((curr->desc && curr->desc->type != Type::unreachable) || - !maybePrintUnreachableReplacement(curr, curr->type)) { - visitExpression(curr); - } - } - void visitStructNew(StructNew* curr) { - if (!maybePrintUnreachableReplacement(curr, curr->type)) { - visitExpression(curr); - } - } - void visitArrayNew(ArrayNew* curr) { - if (!maybePrintUnreachableReplacement(curr, curr->type)) { - visitExpression(curr); - } - } - void visitArrayNewData(ArrayNewData* curr) { - if (!maybePrintUnreachableReplacement(curr, curr->type)) { - visitExpression(curr); - } - } - void visitArrayNewElem(ArrayNewElem* curr) { - if (!maybePrintUnreachableReplacement(curr, curr->type)) { - visitExpression(curr); - } - } - void visitArrayNewFixed(ArrayNewFixed* curr) { - if (!maybePrintUnreachableReplacement(curr, curr->type)) { - visitExpression(curr); - } - } - void visitContNew(ContNew* curr) { - if (!maybePrintUnreachableReplacement(curr, curr->type)) { - visitExpression(curr); - } - } - void visitContBind(ContBind* curr) { - if (!maybePrintUnreachableReplacement(curr, curr->type)) { - visitExpression(curr); - } - } - void visitResume(Resume* curr) { - if (!maybePrintUnreachableReplacement(curr, curr->type)) { - visitExpression(curr); - } - } - void visitResumeThrow(ResumeThrow* curr) { - if (!maybePrintUnreachableReplacement(curr, curr->type)) { - visitExpression(curr); - } - } - void visitStackSwitch(StackSwitch* curr) { - if (!maybePrintUnreachableReplacement(curr, curr->type)) { - visitExpression(curr); - } - } // Module-level visitors void handleSignature(Function* curr, bool printImplicitNames = false); @@ -3143,19 +3086,6 @@ void PrintSExpression::printUnreachableReplacement(Expression* curr) { decIndent(); } -bool PrintSExpression::maybePrintUnreachableReplacement(Expression* curr, - Type type) { - // When we cannot print an instruction because the child from which it's - // supposed to get a type immediate is unreachable, then we print a - // semantically-equivalent block that drops each of the children and ends in - // an unreachable. - if (type == Type::unreachable) { - printUnreachableReplacement(curr); - return true; - } - return false; -} - static bool requiresExplicitFuncType(HeapType type) { // When the `(type $f)` in a function's typeuse is omitted, the typeuse // matches or declares an MVP function type. When the intended type is not an @@ -4009,6 +3939,10 @@ std::ostream& operator<<(std::ostream& o, wasm::ModuleExpression pair) { } std::ostream& operator<<(std::ostream& o, wasm::ShallowExpression expression) { + if (Properties::hasUnwritableTypeImmediate(expression.expr)) { + o << "(; unreachable " << getExpressionName(expression.expr) << " ;)"; + return o; + } wasm::PrintSExpression printer(o); printer.setModule(expression.module); wasm::PrintExpressionContents(printer).visit(expression.expr); From f2cea8b5d0f05ab57bf6863f34a00dd8660afe30 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 17 Apr 2026 18:50:17 -0700 Subject: [PATCH 2/3] stray semicolon --- src/ir/properties.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ir/properties.h b/src/ir/properties.h index 8d1f0faaf84..8392fc88b71 100644 --- a/src/ir/properties.h +++ b/src/ir/properties.h @@ -568,7 +568,7 @@ inline bool hasUnwritableTypeImmediate(Expression* curr) { } if (!cast->desc->type.getHeapType().getDescribedType()) { return true; - }; + } } } return false; From 490747d7d5473d2164f781374abc00d922d078f5 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 17 Apr 2026 19:34:54 -0700 Subject: [PATCH 3/3] do it with macros --- src/ir/properties.h | 37 ++++++++++++++++----------------- src/wasm-delegations-fields.def | 17 +++++++++++++++ 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/ir/properties.h b/src/ir/properties.h index 8392fc88b71..4b4126d750d 100644 --- a/src/ir/properties.h +++ b/src/ir/properties.h @@ -534,6 +534,24 @@ inline bool hasUnwritableTypeImmediate(Expression* curr) { } \ } +#define DELEGATE_IMMEDIATE_TYPED_RESULT(id) \ + if (curr->type == Type::unreachable) { \ + if constexpr (id::SpecificId == Expression::Id::RefCastId) { \ + auto* cast = curr->cast(); \ + if (!cast->desc) { \ + return true; \ + } \ + if (!cast->desc->type.isRef()) { \ + return true; \ + } \ + if (!cast->desc->type.getHeapType().getDescribedType()) { \ + return true; \ + } \ + return false; \ + } \ + return true; \ + } + #define DELEGATE_FIELD_CHILD(id, field) #define DELEGATE_FIELD_CHILD_VECTOR(id, field) #define DELEGATE_FIELD_INT(id, field) @@ -552,25 +570,6 @@ inline bool hasUnwritableTypeImmediate(Expression* curr) { #include "wasm-delegations-fields.def" - if (curr->type == Type::unreachable) { - if (curr->is() || curr->is() || - curr->is() || curr->is() || - curr->is() || curr->is() || - curr->is()) { - return true; - } - if (auto* cast = curr->dynCast()) { - if (!cast->desc) { - return true; - } - if (!cast->desc->type.isRef()) { - return true; - } - if (!cast->desc->type.getHeapType().getDescribedType()) { - return true; - } - } - } return false; } diff --git a/src/wasm-delegations-fields.def b/src/wasm-delegations-fields.def index 142d1e7be70..432f4d832c9 100644 --- a/src/wasm-delegations-fields.def +++ b/src/wasm-delegations-fields.def @@ -29,6 +29,10 @@ // // DELEGATE_END(id) - called at the end of a case. // +// DELEGATE_IMMEDIATE_TYPED_RESULT(id) - does not actually represent a field, +// but is called for expressions whose result types are used to compute type +// immediates. Defining this is optional. +// // DELEGATE_GET_FIELD(id, field) - called to get a field by its name. This must // know the object on which to get it, so it is just useful for the case // where you operate on a single such object, but in that case it is nice @@ -117,6 +121,10 @@ #define DELEGATE_END(id) #endif +#ifndef DELEGATE_IMMEDIATE_TYPED_RESULT +#define DELEGATE_IMMEDIATE_TYPED_RESULT(id) +#endif + #ifndef DELEGATE_FIELD_CHILD #error please define DELEGATE_FIELD_CHILD(id, field) #endif @@ -659,6 +667,7 @@ DELEGATE_FIELD_CHILD(RefTest, ref) DELEGATE_FIELD_CASE_END(RefTest) DELEGATE_FIELD_CASE_START(RefCast) +DELEGATE_IMMEDIATE_TYPED_RESULT(RefCast) DELEGATE_FIELD_OPTIONAL_IMMEDIATE_TYPED_CHILD(RefCast, desc) DELEGATE_FIELD_CHILD(RefCast, ref) DELEGATE_FIELD_CASE_END(RefCast) @@ -676,6 +685,7 @@ DELEGATE_FIELD_CHILD(BrOn, ref) DELEGATE_FIELD_CASE_END(BrOn) DELEGATE_FIELD_CASE_START(StructNew) +DELEGATE_IMMEDIATE_TYPED_RESULT(StructNew) DELEGATE_FIELD_OPTIONAL_CHILD(StructNew, desc) DELEGATE_FIELD_CHILD_VECTOR(StructNew, operands) DELEGATE_FIELD_CASE_END(StructNew) @@ -711,23 +721,27 @@ DELEGATE_FIELD_INT(StructCmpxchg, order) DELEGATE_FIELD_CASE_END(StructCmpxchg) DELEGATE_FIELD_CASE_START(ArrayNew) +DELEGATE_IMMEDIATE_TYPED_RESULT(ArrayNew) DELEGATE_FIELD_CHILD(ArrayNew, size) DELEGATE_FIELD_OPTIONAL_CHILD(ArrayNew, init) DELEGATE_FIELD_CASE_END(ArrayNew) DELEGATE_FIELD_CASE_START(ArrayNewData) +DELEGATE_IMMEDIATE_TYPED_RESULT(ArrayNewData) DELEGATE_FIELD_NAME_KIND(ArrayNewData, segment, ModuleItemKind::DataSegment) DELEGATE_FIELD_CHILD(ArrayNewData, size) DELEGATE_FIELD_CHILD(ArrayNewData, offset) DELEGATE_FIELD_CASE_END(ArrayNewData) DELEGATE_FIELD_CASE_START(ArrayNewElem) +DELEGATE_IMMEDIATE_TYPED_RESULT(ArrayNewElem) DELEGATE_FIELD_NAME_KIND(ArrayNewElem, segment, ModuleItemKind::ElementSegment) DELEGATE_FIELD_CHILD(ArrayNewElem, size) DELEGATE_FIELD_CHILD(ArrayNewElem, offset) DELEGATE_FIELD_CASE_END(ArrayNewElem) DELEGATE_FIELD_CASE_START(ArrayNewFixed) +DELEGATE_IMMEDIATE_TYPED_RESULT(ArrayNewFixed) DELEGATE_FIELD_CHILD_VECTOR(ArrayNewFixed, values) DELEGATE_FIELD_CASE_END(ArrayNewFixed) @@ -866,10 +880,12 @@ DELEGATE_FIELD_CHILD(StringSliceWTF, ref) DELEGATE_FIELD_CASE_END(StringSliceWTF) DELEGATE_FIELD_CASE_START(ContNew) +DELEGATE_IMMEDIATE_TYPED_RESULT(ContNew) DELEGATE_FIELD_CHILD(ContNew, func) DELEGATE_FIELD_CASE_END(ContNew) DELEGATE_FIELD_CASE_START(ContBind) +DELEGATE_IMMEDIATE_TYPED_RESULT(ContBind) DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD(ContBind, cont) DELEGATE_FIELD_CHILD_VECTOR(ContBind, operands) DELEGATE_FIELD_CASE_END(ContBind) @@ -921,6 +937,7 @@ DELEGATE_FIELD_MAIN_END #undef DELEGATE_ID #undef DELEGATE_START #undef DELEGATE_END +#undef DELEGATE_IMMEDIATE_TYPED_RESULT #undef DELEGATE_FIELD_CHILD #undef DELEGATE_FIELD_IMMEDIATE_TYPED_CHILD #undef DELEGATE_FIELD_OPTIONAL_CHILD