/*
 * Copyright 2024 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.
 */

#include "ir/return-utils.h"
#include "ir/module-utils.h"
#include "wasm-builder.h"
#include "wasm-traversal.h"
#include "wasm.h"

namespace wasm::ReturnUtils {

namespace {

struct ReturnValueRemover : public PostWalker<ReturnValueRemover> {
  void visitReturn(Return* curr) {
    auto* value = curr->value;
    assert(value);
    curr->value = nullptr;
    Builder builder(*getModule());
    replaceCurrent(builder.makeSequence(builder.makeDrop(value), curr));
  }

  void visitCall(Call* curr) { handleReturnCall(curr); }
  void visitCallIndirect(CallIndirect* curr) { handleReturnCall(curr); }
  void visitCallRef(CallRef* curr) { handleReturnCall(curr); }

  template<typename T> void handleReturnCall(T* curr) {
    if (curr->isReturn) {
      Fatal() << "Cannot remove return_calls in ReturnValueRemover";
    }
  }

  void visitFunction(Function* curr) {
    if (curr->body->type.isConcrete()) {
      curr->body = Builder(*getModule()).makeDrop(curr->body);
    }
  }
};

} // anonymous namespace

void removeReturns(Function* func, Module& wasm) {
  ReturnValueRemover().walkFunctionInModule(func, &wasm);
}

ReturnInfo getInfo(Expression* curr) {
  struct Finder : PostWalker<Finder> {
    ReturnInfo info;

    void visitReturn(Return* curr) { info.hasReturn = true; }

    void visitCall(Call* curr) {
      if (curr->isReturn) {
        info.hasReturnCall = true;
      }
    }
    void visitCallIndirect(CallIndirect* curr) {
      if (curr->isReturn) {
        info.hasReturnCall = true;
      }
    }
    void visitCallRef(CallRef* curr) {
      if (curr->isReturn) {
        info.hasReturnCall = true;
      }
    }
  } finder;
  finder.walk(curr);

  // Any return call is also a return.
  if (finder.info.hasReturnCall) {
    finder.info.hasReturn = true;
  }

  return finder.info;
}

std::unordered_map<Function*, bool> findReturnCallers(Module& wasm) {
  ModuleUtils::ParallelFunctionAnalysis<bool> analysis(
    wasm, [&](Function* func, bool& hasReturnCall) {
      if (func->imported()) {
        return;
      }

      hasReturnCall = getInfo(func->body).hasReturnCall;
    });

  // Convert to an unordered map for fast lookups. TODO: Avoid a copy here.
  std::unordered_map<Function*, bool> ret;
  ret.reserve(analysis.map.size());
  for (auto& [k, v] : analysis.map) {
    ret[k] = v;
  }
  return ret;
}

} // namespace wasm::ReturnUtils
