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

#ifndef wasm_abi_stack_h
#define wasm_abi_stack_h

#include "abi.h"
#include "asmjs/shared-constants.h"
#include "ir/find_all.h"
#include "ir/global-utils.h"
#include "shared-constants.h"
#include "wasm-builder.h"
#include "wasm.h"

namespace wasm {

namespace ABI {

enum { StackAlign = 16 };

inline Index stackAlign(Index size) {
  return (size + StackAlign - 1) & -StackAlign;
}

// Allocate some space on the stack, and assign it to a local.
// The local will have the same constant value in all the function, so you can
// just local.get it anywhere there.
//
// FIXME: This function assumes that the stack grows upward, per the convention
// used by fastcomp.  The stack grows downward when using the WASM backend.

inline void
getStackSpace(Index local, Function* func, Index size, Module& wasm) {
  // Attempt to locate the stack pointer by recognizing code idioms
  // used by Emscripten.  First, look for a global initialized to an
  // imported variable named "STACKTOP" in environment "env".
  auto* stackPointer =
    GlobalUtils::getGlobalInitializedToImport(wasm, ENV, "STACKTOP");
  // Starting with Emscripten 1.38.24, the stack pointer variable is
  // initialized with a literal constant, eliminating the import that
  // we used to locate the stack pointer by name.  We must match a more
  // complicated idiom, expecting to see the module structured as follows:
  //
  //(module
  //  ...
  //  (export "stackSave" (func $stackSave))
  //  ...
  //  (func $stackSave (; 410 ;) (; has Stack IR ;) (result i32)
  //    (global.get $STACKTOP)
  //  )
  //  ...
  //)
  if (!stackPointer) {
    auto* stackSaveFunctionExport = wasm.getExportOrNull("stackSave");
    if (stackSaveFunctionExport &&
        stackSaveFunctionExport->kind == ExternalKind::Function) {
      auto* stackSaveFunction =
        wasm.getFunction(stackSaveFunctionExport->value);
      assert(!stackSaveFunction->imported());
      auto* globalGet = stackSaveFunction->body->dynCast<GlobalGet>();
      if (globalGet) {
        stackPointer = wasm.getGlobal(globalGet->name);
      }
    }
  }
  if (!stackPointer) {
    Fatal() << "getStackSpace: failed to find the stack pointer";
  }
  // align the size
  size = stackAlign(size);
  // TODO: find existing stack usage, and add on top of that - carefully
  Builder builder(wasm);
  auto* block = builder.makeBlock();
  block->list.push_back(builder.makeLocalSet(
    local, builder.makeGlobalGet(stackPointer->name, PointerType)));
  // TODO: add stack max check
  Expression* added;
  if (PointerType == Type::i32) {
    added = builder.makeBinary(AddInt32,
                               builder.makeLocalGet(local, PointerType),
                               builder.makeConst(Literal(int32_t(size))));
  } else {
    WASM_UNREACHABLE("unhandled PointerType");
  }
  block->list.push_back(builder.makeGlobalSet(stackPointer->name, added));
  auto makeStackRestore = [&]() {
    return builder.makeGlobalSet(stackPointer->name,
                                 builder.makeLocalGet(local, PointerType));
  };
  // add stack restores to the returns
  FindAllPointers<Return> finder(func->body);
  for (auto** ptr : finder.list) {
    auto* ret = (*ptr)->cast<Return>();
    if (ret->value && ret->value->type != Type::unreachable) {
      // handle the returned value
      auto* block = builder.makeBlock();
      auto temp = builder.addVar(func, ret->value->type);
      block->list.push_back(builder.makeLocalSet(temp, ret->value));
      block->list.push_back(makeStackRestore());
      block->list.push_back(
        builder.makeReturn(builder.makeLocalGet(temp, ret->value->type)));
      block->finalize();
      *ptr = block;
    } else {
      // restore, then return
      *ptr = builder.makeSequence(makeStackRestore(), ret);
    }
  }
  // add stack restores to the body
  if (func->body->type == Type::none) {
    block->list.push_back(func->body);
    block->list.push_back(makeStackRestore());
  } else if (func->body->type == Type::unreachable) {
    block->list.push_back(func->body);
    // no need to restore the old stack value, we're gone anyhow
  } else {
    // save the return value
    auto temp = builder.addVar(func, func->sig.results);
    block->list.push_back(builder.makeLocalSet(temp, func->body));
    block->list.push_back(makeStackRestore());
    block->list.push_back(builder.makeLocalGet(temp, func->sig.results));
  }
  block->finalize();
  func->body = block;
}

} // namespace ABI

} // namespace wasm

#endif // wasm_abi_stack_h
