/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 *
 * Copyright 2021 Mozilla Foundation
 *
 * 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.
 */

/* [SMDOC] The WASM ABIs
 *
 * Wasm-internal ABI.
 *
 * The *Wasm-internal ABI* is the ABI a wasm function assumes when it is
 * entered, and the one it assumes when it is making a call to what it believes
 * is another wasm function.
 *
 * We pass the first function arguments in registers (GPR and FPU both) and the
 * rest on the stack, generally according to platform ABI conventions (which can
 * be hairy).  On x86-32 there are no register arguments.
 *
 * We have no callee-saves registers in the wasm-internal ABI, regardless of the
 * platform ABI conventions, though see below about InstanceReg or HeapReg.
 *
 * We return the last return value in the first return register, according to
 * platform ABI conventions.  If there is more than one return value, an area is
 * allocated in the caller's frame to receive the other return values, and the
 * address of this area is passed to the callee as the last argument.  Return
 * values except the last are stored in ascending order within this area.  Also
 * see below about alignment of this area and the values in it.
 *
 * When a function is entered, there are two incoming register values in
 * addition to the function's declared parameters: InstanceReg must have the
 * correct instance pointer, and HeapReg the correct memoryBase, for the
 * function.  (On x86-32 there is no HeapReg.)  From the instance we can get to
 * the JSContext, the instance, the MemoryBase, and many other things.  The
 * instance maps one-to-one with an instance.
 *
 * HeapReg and InstanceReg are not parameters in the usual sense, nor are they
 * callee-saves registers.  Instead they constitute global register state, the
 * purpose of which is to bias the call ABI in favor of intra-instance calls,
 * the predominant case where the caller and the callee have the same
 * InstanceReg and HeapReg values.
 *
 * With this global register state, literally no work needs to take place to
 * save and restore the instance and MemoryBase values across intra-instance
 * call boundaries.
 *
 * For inter-instance calls, in contrast, there must be an instance switch at
 * the call boundary: Before the call, the callee's instance must be loaded
 * (from a closure or from the import table), and from the instance we load the
 * callee's MemoryBase, the realm, and the JSContext.  The caller's and callee's
 * instance values must be stored into the frame (to aid unwinding), the
 * callee's realm must be stored into the JSContext, and the callee's instance
 * and MemoryBase values must be moved to appropriate registers.  After the
 * call, the caller's instance must be loaded, and from it the caller's
 * MemoryBase and realm, and the JSContext.  The realm must be stored into the
 * JSContext and the caller's instance and MemoryBase values must be moved to
 * appropriate registers.
 *
 * Direct calls to functions within the same module are always intra-instance,
 * while direct calls to imported functions are always inter-instance.  Indirect
 * calls -- call_indirect in the MVP, future call_ref and call_funcref -- may or
 * may not be intra-instance.
 *
 * call_indirect, and future call_funcref, also pass a signature value in a
 * register (even on x86-32), this is a small integer or a pointer value
 * denoting the caller's expected function signature.  The callee must compare
 * it to the value or pointer that denotes its actual signature, and trap on
 * mismatch.
 *
 * This is what the stack looks like during a call, after the callee has
 * completed the prologue:
 *
 *     |                                   |
 *     +-----------------------------------+ <-+
 *     |               ...                 |   |
 *     |      Caller's private frame       |   |
 *     +-----------------------------------+   |
 *     |   Multi-value return (optional)   |   |
 *     |               ...                 |   |
 *     +-----------------------------------+   |
 *     |       Stack args (optional)       |   |
 *     |               ...                 |   |
 *     +-----------------------------------+ -+|
 *     |          Caller instance slot     |   \
 *     |          Callee instance slot     |   | \
 *     +-----------------------------------+   |  \
 *     |       Shadowstack area (Win64)    |   |  wasm::FrameWithInstances
 *     |            (32 bytes)             |   |  /
 *     +-----------------------------------+   | /  <= SP "Before call"
 *     |          Return address           |   //   <= SP "After call"
 *     |             Saved FP          ----|--+/
 *     +-----------------------------------+ -+ <=  FP (a wasm::Frame*)
 *     |  DebugFrame, Locals, spills, etc  |
 *     |   (i.e., callee's private frame)  |
 *     |             ....                  |
 *     +-----------------------------------+    <=  SP
 *
 * The FrameWithInstances is a struct with four fields: the saved FP, the return
 * address, and the two instance slots; the shadow stack area is there only on
 * Win64 and is unused by wasm but is part of the native ABI, with which the
 * wasm ABI is mostly compatible.  The slots for caller and callee instance are
 * only populated by the instance switching code in inter-instance calls so that
 * stack unwinding can keep track of the correct instance value for each frame,
 * the instance not being obtainable from anywhere else.  Nothing in the frame
 * itself indicates directly whether the instance slots are valid - for that,
 * the return address must be used to look up a CallSite structure that carries
 * that information.
 *
 * (In Ion builds with full debug checks, we also write the callee instance to
 * its usual slot regardless of the previous logic. This allows us to recover
 * the current function's instance pointer at any time for debug checks. If it's
 * an inter-instance call, this work is merely redundant.)
 *
 * The stack area above the return address is owned by the caller, which may
 * deallocate the area on return or choose to reuse it for subsequent calls.
 * (The baseline compiler allocates and frees the stack args area and the
 * multi-value result area per call.  Ion reuses the areas and allocates them as
 * part of the overall activation frame when the procedure is entered; indeed,
 * the multi-value return area can be anywhere within the caller's private
 * frame, not necessarily directly above the stack args.)
 *
 * If the stack args area contain references, it is up to the callee's stack map
 * to name the locations where those references exist, and the caller's stack
 * map must not (redundantly) name those locations.  (The callee's ownership of
 * this area will be crucial for making tail calls work, as the types of the
 * locations can change if the callee makes a tail call.)  If pointer values are
 * spilled by anyone into the Shadowstack area they will not be traced.
 *
 * References in the multi-return area are covered by the caller's map, as these
 * slots outlive the call.
 *
 * The address "Before call", ie the part of the FrameWithInstances above the
 * Frame, must be aligned to WasmStackAlignment, and everything follows from
 * that, with padding inserted for alignment as required for stack arguments. In
 * turn WasmStackAlignment is at least as large as the largest parameter type.
 *
 * The address of the multiple-results area is currently 8-byte aligned by Ion
 * and its alignment in baseline is uncertain, see bug 1747787.  Result values
 * are stored packed within the area in fields whose size is given by
 * ResultStackSize(ValType), this breaks alignment too.  This all seems
 * underdeveloped.
 *
 * In the wasm-internal ABI, the ARM64 PseudoStackPointer (PSP) is garbage on
 * entry but must be synced with the real SP at the point the function returns.
 *
 *
 * The Wasm Builtin ABIs.
 *
 * Also see `[SMDOC] Process-wide builtin thunk set` in WasmBuiltins.cpp.
 *
 * The *Wasm-builtin ABIs* comprise the ABIs used when wasm makes calls directly
 * to the C++ runtime (but not to the JS interpreter), including instance
 * methods, helpers for operations such as 64-bit division on 32-bit systems,
 * allocation and writer barriers, conversions to/from JS values, special
 * fast-path JS imports, and trap handling.
 *
 * The callee of a builtin call will always assume the C/C++ ABI.  Therefore
 * every volatile (caller-saves) register that wasm uses must be saved across
 * the call, the stack must be aligned as for a C/C++-ABI call before the call,
 * and any ABI registers the callee expect to have specific values must be set
 * up (eg the frame pointer, if the C/C++ ABI assumes it is set).
 *
 * Most builtin calls are straightforward: the wasm caller knows that it is
 * performing a call, and so it saves live registers, moves arguments into ABI
 * locations, etc, before calling.  Abstractions in the masm make sure to pass
 * the instance pointer to an instance "method" call and to restore the
 * InstanceReg and HeapReg after the call.  In these straightforward cases,
 * calling the builtin additionally amounts to:
 *
 *  - exiting the wasm activation
 *  - adjusting parameter values to account for platform weirdness (FP arguments
 *    are handled differently in the C/C++ ABIs on ARM and x86-32 than in the
 *    Wasm ABI)
 *  - copying stack arguments into place for the C/C++ ABIs
 *  - making the call
 *  - adjusting the return values on return
 *  - re-entering the wasm activation and returning to the wasm caller
 *
 * The steps above are performed by the *builtin thunk* for the builtin and the
 * builtin itself is said to be *thunked*.  Going via the thunk is simple and,
 * except for always having to copy stack arguments on x86-32 and the extra call
 * in the thunk, close to as fast as we can make it without heroics.  Except for
 * the arithmetic helpers on 32-bit systems, most builtins are rarely used, are
 * asm.js-specific, or are expensive anyway, and the overhead of the extra call
 * doesn't matter.
 *
 * A few builtins for special purposes are *unthunked* and fall into two
 * classes: they would normally be thunked but are used in circumstances where
 * the VM is in an unusual state; or they do their work within the activation.
 *
 * In the former class, we find the debug trap handler, which must preserve all
 * live registers because it is called in contexts where live registers have not
 * been saved; argument coercion functions, which are called while a call frame
 * is being built for a JS->Wasm or Wasm->JS call; and other routines that have
 * special needs for constructing the call.  These all exit the activation, but
 * handle the exit specially.
 *
 * In the latter class, we find two functions that abandon the VM state and
 * unwind the activation, HandleThrow and HandleTrap; and some debug print
 * functions that do not affect the VM state at all.
 *
 * To summarize, when wasm calls a builtin thunk the stack will end up looking
 * like this from within the C++ code:
 *
 *      |                         |
 *      +-------------------------+
 *      |        Wasm frame       |
 *      +-------------------------+
 *      |    Thunk frame (exit)   |
 *      +-------------------------+
 *      |   Builtin frame (C++)   |
 *      +-------------------------+  <= SP
 *
 * There is an assumption in the profiler (in initFromExitFP) that an exit has
 * left precisely one frame on the stack for the thunk itself.  There may be
 * additional assumptions elsewhere, not yet found.
 *
 * Very occasionally, Wasm will call C++ without going through the builtin
 * thunks, and this can be a source of problems.  The one case I know about
 * right now is that the JS pre-barrier filtering code is called directly from
 * Wasm, see bug 1464157.
 *
 *
 * Wasm stub ABIs.
 *
 * Also see `[SMDOC] Exported wasm functions and the jit-entry stubs` in
 * WasmJS.cpp.
 *
 * The "stub ABIs" are not properly speaking ABIs themselves, but ABI
 * converters.  An "entry" stub calls in to wasm and an "exit" stub calls out
 * from wasm.  The entry stubs must convert from whatever data formats the
 * caller has to wasm formats (and in the future must provide some kind of type
 * checking for pointer types); the exit stubs convert from wasm formats to the
 * callee's expected format.
 *
 * There are different entry paths from the JS interpreter (using the C++ ABI
 * and data formats) and from jitted JS code (using the JIT ABI and data
 * formats); indeed there is a "normal" JitEntry path ("JitEntry") that will
 * perform argument and return value conversion, and the "fast" JitEntry path
 * ("DirectCallFromJit") that is only used when it is known that the JIT will
 * only pass and receive wasm-compatible data and no conversion is needed.
 *
 * Similarly, there are different exit paths to the interpreter (using the C++
 * ABI and data formats) and to JS JIT code (using the JIT ABI and data
 * formats).  Also, builtin calls described above are themselves a type of exit,
 * and builtin thunks are properly a type of exit stub.
 *
 * Data conversions are difficult because the VM is in an intermediate state
 * when they happen, we want them to be fast when possible, and some conversions
 * can re-enter both JS code and wasm code.
 */

#ifndef wasm_frame_h
#define wasm_frame_h

#include "mozilla/Assertions.h"

#include <stddef.h>
#include <stdint.h>
#include <type_traits>

#include "jit/Registers.h"  // For js::jit::ShadowStackSpace

namespace js {
namespace wasm {

class Instance;

// Bit tag set when exiting wasm code in JitActivation's exitFP.
constexpr uintptr_t ExitFPTag = 0x1;

// wasm::Frame represents the bytes pushed by the call instruction and the
// fixed prologue generated by wasm::GenerateCallablePrologue.
//
// Across all architectures it is assumed that, before the call instruction, the
// stack pointer is WasmStackAlignment-aligned. Thus after the prologue, and
// before the function has made its stack reservation, the stack alignment is
// sizeof(Frame) % WasmStackAlignment.
//
// During MacroAssembler code generation, the bytes pushed after the wasm::Frame
// are counted by masm.framePushed. Thus, the stack alignment at any point in
// time is (sizeof(wasm::Frame) + masm.framePushed) % WasmStackAlignment.

class Frame {
  // See GenerateCallableEpilogue for why this must be
  // the first field of wasm::Frame (in a downward-growing stack).
  // It's either the caller's Frame*, for wasm callers, or the JIT caller frame
  // plus a tag otherwise.
  uint8_t* callerFP_;

  // The return address pushed by the call (in the case of ARM/MIPS the return
  // address is pushed by the first instruction of the prologue).
  void* returnAddress_;

 public:
  static constexpr uint32_t callerFPOffset() {
    return offsetof(Frame, callerFP_);
  }
  static constexpr uint32_t returnAddressOffset() {
    return offsetof(Frame, returnAddress_);
  }

  uint8_t* returnAddress() const {
    return reinterpret_cast<uint8_t*>(returnAddress_);
  }

  void** addressOfReturnAddress() {
    return reinterpret_cast<void**>(&returnAddress_);
  }

  uint8_t* rawCaller() const { return callerFP_; }

  Frame* wasmCaller() const { return reinterpret_cast<Frame*>(callerFP_); }

  uint8_t* jitEntryCaller() const { return callerFP_; }

  static const Frame* fromUntaggedWasmExitFP(const void* savedFP) {
    MOZ_ASSERT(!isExitFP(savedFP));
    return reinterpret_cast<const Frame*>(savedFP);
  }

  static bool isExitFP(const void* fp) {
    return reinterpret_cast<uintptr_t>(fp) & ExitFPTag;
  }

  static uint8_t* untagExitFP(const void* fp) {
    MOZ_ASSERT(isExitFP(fp));
    return reinterpret_cast<uint8_t*>(reinterpret_cast<uintptr_t>(fp) &
                                      ~ExitFPTag);
  }

  static uint8_t* addExitFPTag(const Frame* fp) {
    MOZ_ASSERT(!isExitFP(fp));
    return reinterpret_cast<uint8_t*>(reinterpret_cast<uintptr_t>(fp) |
                                      ExitFPTag);
  }
};

static_assert(!std::is_polymorphic_v<Frame>, "Frame doesn't need a vtable.");
static_assert(sizeof(Frame) == 2 * sizeof(void*),
              "Frame is a two pointer structure");

class FrameWithShadowStackSpace : public Frame {
 protected:
  // `ShadowStackSpace` bytes will be allocated here on Win64, at higher
  // addresses than Frame and at lower addresses than the instance fields.
  uint8_t shadowStackSpace_[js::jit::ShadowStackSpace];
};

class FrameWithInstances
    : public std::conditional_t<js::jit::ShadowStackSpace >= 1,
                                FrameWithShadowStackSpace, Frame> {
  // The instance area MUST be two pointers exactly.
  Instance* calleeInstance_;
  Instance* callerInstance_;

 public:
  Instance* calleeInstance() { return calleeInstance_; }
  Instance* callerInstance() { return callerInstance_; }

  constexpr static uint32_t sizeOfInstanceFields() {
    return sizeof(wasm::FrameWithInstances) - sizeof(wasm::Frame) -
           js::jit::ShadowStackSpace;
  }

  constexpr static uint32_t sizeOfInstanceFieldsAndShadowStack() {
    return sizeof(wasm::FrameWithInstances) - sizeof(wasm::Frame);
  }

  constexpr static uint32_t calleeInstanceOffset() {
    return offsetof(FrameWithInstances, calleeInstance_);
  }

  constexpr static uint32_t calleeInstanceOffsetWithoutFrame() {
    return calleeInstanceOffset() - sizeof(wasm::Frame);
  }

  constexpr static uint32_t callerInstanceOffset() {
    return offsetof(FrameWithInstances, callerInstance_);
  }

  constexpr static uint32_t callerInstanceOffsetWithoutFrame() {
    return callerInstanceOffset() - sizeof(wasm::Frame);
  }
};

static_assert(FrameWithInstances::calleeInstanceOffsetWithoutFrame() ==
                  js::jit::ShadowStackSpace,
              "Callee instance stored right above the return address.");
static_assert(FrameWithInstances::callerInstanceOffsetWithoutFrame() ==
                  js::jit::ShadowStackSpace + sizeof(void*),
              "Caller instance stored right above the callee instance.");

static_assert(FrameWithInstances::sizeOfInstanceFields() == 2 * sizeof(void*),
              "There are only two additional slots");

#if defined(JS_CODEGEN_ARM64)
static_assert(sizeof(Frame) % 16 == 0, "frame is aligned");
#endif

}  // namespace wasm
}  // namespace js

#endif  // wasm_frame_h
