| /* |
| * Copyright 2026 WebAssembly Community Group participants |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| // |
| // Removes casts (ref.cast and ref.as_non_null) on values that flow into a slot |
| // whose declared type the uncast value already satisfies. For example: |
| // |
| // (func $callee (param $s (ref $Super)) ..) |
| // .. |
| // (call $callee |
| // (ref.cast (ref $Sub) (local.get $x)) ;; $x is (ref $Super) |
| // ) |
| // |
| // The cast is not needed for validation, and in traps-never-happen mode it can |
| // be assumed to succeed, so it can be removed: |
| // |
| // (call $callee |
| // (local.get $x) |
| // ) |
| // |
| // All value-flowing slots discovered by SubtypingDiscoverer (locals, globals, |
| // call parameters, function results, struct fields, array elements, control |
| // flow branches and fallthroughs, etc.) are handled. Casts on the input of a |
| // ref.test whose outcome is not determined by the input's type are removed as |
| // well, as they do not affect the result. |
| // |
| // Unlike the cast removals done in OptimizeInstructions, which never lose type |
| // information, the casts removed here may narrow the value beyond the slot |
| // type. That narrower type is information that refining passes (GUFA, |
| // signature-refining, type-refining, local-subtyping, etc.) could use to refine |
| // the slot itself, so this pass is meant to run late in the pipeline, after |
| // those passes had their chance. It should still be followed by a round of |
| // general optimizations, as removing the casts can make functions identical |
| // (helping duplicate-function-elimination) or smaller (helping inlining). |
| // |
| // Requires traps-never-happen mode, as removing a cast removes a possible trap. |
| // |
| |
| #include <unordered_map> |
| |
| #include "ir/effects.h" |
| #include "ir/gc-type-utils.h" |
| #include "ir/intrinsics.h" |
| #include "ir/subtype-exprs.h" |
| #include "pass.h" |
| #include "passes/passes.h" |
| #include "wasm-traversal.h" |
| #include "wasm.h" |
| |
| namespace wasm { |
| |
| namespace { |
| |
| struct StripRefiningCasts |
| : public WalkerPass< |
| ControlFlowWalker<StripRefiningCasts, |
| SubtypingDiscoverer<StripRefiningCasts>>> { |
| using Super = |
| WalkerPass<ControlFlowWalker<StripRefiningCasts, |
| SubtypingDiscoverer<StripRefiningCasts>>>; |
| |
| bool isFunctionParallel() override { return true; } |
| |
| std::unique_ptr<Pass> create() override { |
| return std::make_unique<StripRefiningCasts>(); |
| } |
| |
| void runOnFunction(Module* module, Function* func) override { |
| if (getPassOptions().trapsNeverHappen) { |
| Super::runOnFunction(module, func); |
| } |
| } |
| |
| // Map from expression slot to the greatest lower bound of all types it must |
| // be a subtype of (e.g. a switch value must be a subtype of all its targets). |
| std::unordered_map<Expression**, Type> requiredTypes; |
| |
| void noteSubtype(Type, Type) {} |
| void noteSubtype(HeapType, HeapType) {} |
| void noteSubtype(Type, Expression*) {} |
| void noteSubtype(Expression*& sub, Type super) { |
| auto [it, inserted] = requiredTypes.insert({&sub, super}); |
| if (!inserted) { |
| it->second = Type::getGreatestLowerBound(it->second, super); |
| } |
| } |
| void noteSubtype(Expression*& sub, Expression* super) { |
| // TODO: Propagate the required type of |super| itself through fallthrough |
| // expressions (e.g. blocks, loops, ifs, selects, br_ifs) and re-finalize |
| // them, rather than constraining |sub| by |super|'s current refined type. |
| noteSubtype(sub, super->type); |
| } |
| void noteNonFlowSubtype(Expression*, Type) {} |
| void noteCast(HeapType, Type) {} |
| void noteCast(Expression*, Type) {} |
| void noteCast(Expression*, Expression*) {} |
| |
| // Skips casts on |input| while the uncast value is a subtype of |slotType|. |
| void skipCasts(Expression*& input, Type slotType) { |
| while (true) { |
| if (auto* as = input->dynCast<RefAs>()) { |
| if (as->op == RefAsNonNull && |
| Type::isSubType(as->value->type, slotType)) { |
| input = as->value; |
| continue; |
| } |
| } else if (auto* cast = input->dynCast<RefCast>()) { |
| // Removing a descriptor cast also removes the descriptor operand, which |
| // we can only do if it has no side effects. |
| // TODO: Use ChildLocalizer to preserve side-effecting descriptors in a |
| // block and still remove the cast. |
| if ((!cast->desc || |
| !EffectAnalyzer(getPassOptions(), *getModule(), cast->desc) |
| .hasSideEffects()) && |
| Type::isSubType(cast->ref->type, slotType)) { |
| input = cast->ref; |
| continue; |
| } |
| } |
| break; |
| } |
| } |
| |
| void visitCall(Call* curr) { |
| // call.without.effects operands must also satisfy the signature of the |
| // target function operand, not just the import's declared signature. |
| if (Intrinsics(*getModule()).isCallWithoutEffects(curr)) { |
| return; |
| } |
| Super::visitCall(curr); |
| } |
| |
| void visitRefTest(RefTest* curr) { |
| // If the type of the input determines the outcome, leave the test to |
| // OptimizeInstructions, which will use that type to resolve it. |
| // (This also leaves exact tests alone when custom descriptors are disabled, |
| // as those are then only valid on inputs of the same exact type, which |
| // determines the outcome.) |
| if (GCTypeUtils::evaluateCastCheck(curr->ref->type, curr->castType) != |
| GCTypeUtils::Unknown) { |
| return; |
| } |
| // Any input in the hierarchy of the cast type is valid. Nullability does |
| // not matter either: a removed cast is assumed to succeed, so it only ever |
| // passes its input through unchanged. |
| noteSubtype(curr->ref, |
| Type(curr->castType.getHeapType().getTop(), Nullable)); |
| } |
| |
| void visitFunction(Function* func) { |
| Super::visitFunction(func); |
| for (auto& [slot, type] : requiredTypes) { |
| skipCasts(*slot, type); |
| } |
| } |
| }; |
| |
| } // anonymous namespace |
| |
| Pass* createStripRefiningCastsPass() { return new StripRefiningCasts(); } |
| |
| } // namespace wasm |