@ 261106.8
current
/*
 * 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.
 */

//
// Optimizes using the @binaryen.noreturn intrinsic: when a function is marked
// as not returning, we can place an unreachable right after it. DCE can then
// remove code.
//
// Note that this pass does not add new markings of the intrinsic. Another pass
// (DeadArgumentElimination) is a convenient place to do that.
//

#include "ir/intrinsics.h"
#include "ir/utils.h"
#include "pass.h"
#include "wasm-builder.h"
#include "wasm.h"

namespace wasm {

struct OptimizeNoReturn
  : public WalkerPass<PostWalker<OptimizeNoReturn,
                                 UnifiedExpressionVisitor<OptimizeNoReturn>>> {

  using Super = WalkerPass<
    PostWalker<OptimizeNoReturn, UnifiedExpressionVisitor<OptimizeNoReturn>>>;
  bool isFunctionParallel() override { return true; }

  std::unique_ptr<Pass> create() override {
    return std::make_unique<OptimizeNoReturn>();
  }

  // To avoid adding unneeded unreachables, see if a call to a noreturn function
  // is followed by an unreachable already, or a drop and then an unreachable.
  // In both those cases we don't need to add an unreachable ourselves. To track
  // that, we keep note of the last call and drop.
  Expression** callp = nullptr;
  Expression** dropp = nullptr;

  void visitExpression(Expression* curr) {
    if (auto* call = curr->dynCast<Call>()) {
      if (callp) {
        // There was a call before us, handle it first.
        addUnreachable();
      }

      // No need to add an unreachable after an already-unreachable call (like a
      // return call, or one with an unreachable operand).
      if (call->type == Type::unreachable) {
        return;
      }

      auto* func = getModule()->getFunctionOrNull(call->target);
      if (!func) {
        // No target: this can happen during Asyncify or if a code generator is
        // optimizing something before the entire module is ready.
        return;
      }

      if (Intrinsics::getAnnotations(func).noReturn) {
        callp = getCurrentPointer();
      }
      return;
    }

    if (!callp) {
      // We are not right after a relevant call, so there is nothing to do.
      return;
    }

    if (curr->dynCast<Drop>()) {
      // We are the call's drop.
      dropp = getCurrentPointer();
      return;
    } else if (curr->dynCast<Unreachable>()) {
      // We are an unreachable after the call (or maybe the call + drop). We
      // don't need to add anything.
      callp = nullptr;
      dropp = nullptr;
      return;
    }

    // Something else, so we need to add an unreachable here.
    addUnreachable();
  }

  bool refinalize = false;

  void visitFunction(Function* curr) {
    // The walk ended, but perhaps it ended on something that needs an
    // unreachable.
    if (callp) {
      addUnreachable();
    }

    if (refinalize) {
      ReFinalize().walkFunctionInModule(curr, getModule());
    }
  }

  void addUnreachable() {
    Builder builder(*getModule());
    if (dropp) {
      // Put the unreachable after the drop (so the call stays dropped); other
      // passes can remove the return value entirely.
      *dropp = builder.makeSequence(*dropp, builder.makeUnreachable());
      dropp = nullptr;
    } else {
      // Put the unreachable after the call, dropping it if needed (if its
      // result was used before, not dropped, then we need to add a drop here).
      auto* call = builder.dropIfConcretelyTyped(*callp);
      *callp = builder.makeSequence(call, builder.makeUnreachable());
    }
    callp = nullptr;

    refinalize = true;
  }
};

Pass* createOptimizeNoReturnPass() { return new OptimizeNoReturn(); }

} // namespace wasm