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

#include "wasm/WasmMemory.h"

#include "js/Conversions.h"
#include "js/ErrorReport.h"
#include "vm/ArrayBufferObject.h"
#include "wasm/WasmCodegenTypes.h"
#include "wasm/WasmProcess.h"

using namespace js;
using namespace js::wasm;

const char* wasm::ToString(AddressType addressType) {
  switch (addressType) {
    case AddressType::I32:
      return "i32";
    case AddressType::I64:
      return "i64";
    default:
      MOZ_CRASH();
  }
}

bool wasm::ToAddressType(JSContext* cx, HandleValue value,
                         AddressType* addressType) {
  RootedString typeStr(cx, ToString(cx, value));
  if (!typeStr) {
    return false;
  }

  Rooted<JSLinearString*> typeLinearStr(cx, typeStr->ensureLinear(cx));
  if (!typeLinearStr) {
    return false;
  }

  if (StringEqualsLiteral(typeLinearStr, "i32")) {
    *addressType = AddressType::I32;
  } else if (StringEqualsLiteral(typeLinearStr, "i64")) {
    *addressType = AddressType::I64;
  } else {
    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                             JSMSG_WASM_BAD_STRING_ADDR_TYPE);
    return false;
  }
  return true;
}

bool wasm::ToPageSize(JSContext* cx, HandleValue value, PageSize* pageSize) {
  if (!value.isInt32()) {
    JS_ReportErrorASCII(cx, "page size must be an integer");
    return false;
  }
  uint32_t pageSizeBytes = uint32_t(value.toInt32());
  if (pageSizeBytes == PageSizeInBytes(PageSize::Standard)) {
    *pageSize = PageSize::Standard;
#ifdef ENABLE_WASM_CUSTOM_PAGE_SIZES
  } else if (pageSizeBytes == PageSizeInBytes(PageSize::Tiny)) {
    *pageSize = PageSize::Tiny;
#endif
  } else {
    JS_ReportErrorASCII(cx, "bad page size");
    return false;
  }
  return true;
}

/*
 * [SMDOC] Linear memory addresses and bounds checking
 *
 * (Also see "WASM Linear Memory structure" in vm/ArrayBufferObject.cpp)
 *
 *
 * ## Memory addresses
 *
 * A memory address in an access instruction has three components, the "memory
 * base", the "address", and the "offset". The "memory base" (the HeapReg on
 * most platforms and a value loaded from the instance on x86) is a native
 * pointer to the start of the linear memory array; we'll ignore the memory base
 * in the following. The "address" is the i32 or i64 address into linear memory
 * from the WebAssembly program; it is usually variable but can be constant. The
 * "offset" is a constant immediate to the access instruction. For example,
 * consider the following instructions:
 *
 *   i32.const 128
 *   f32.load offset=8
 *
 * The address is 128; the offset is 8. The memory base is not observable to
 * wasm. Note that the address comes from the wasm value stack, but the offset
 * is an immediate.
 *
 * The "effective address" (EA) is the non-overflowed sum of the address and the
 * offset. (If the sum overflows, the program traps.) For the above, the
 * effective address is 136.
 *
 * An access has an "access size", which is the number of bytes that are
 * accessed - currently up to 16 (for V128). The highest-addressed byte to be
 * accessed is thus the byte at (address + offset + access_size - 1). Note that
 * (offset + access_size - 1) can be evaluated at compile time.
 *
 * Bounds checking ensures that the entire access is in bounds, i.e. that the
 * highest-addressed byte is within the memory's current byteLength.
 *
 *
 * ## Bounds check avoidance
 *
 * To avoid performing an addition with overflow check and a compare-and-branch
 * bounds check for every memory access, we use some tricks:
 *
 * - We allocate an access-protected guard region of size R at the end of each
 *   memory to trap out-of-bounds offsets in the range 0..R-access_size. Thus,
 *   the offset and the access size can be omitted from the bounds check, saving
 *   the add and overflow check. For example, given the following module:
 *
 *     (memory 1) ;; 1 page, 65536 bytes
 *     (func
 *       (f64.load offset=8 (i32.const 65528))
 *     )
 *
 *   As long as the address itself is bounds checked, the offset will at worst
 *   cause the access to land in the guard region and trap via signal handling:
 *
 *            Memory │ Guard Region
 *     ─ ─ ──────────┼────────┬──────── ─ ─
 *                   │ access │
 *     ─ ─ ─┬────────┼────────┴──────── ─ ─
 *          65528    65536
 *
 *   Therefore, after bounds checking the address, the offset can be added into
 *   the address without an overflow check, either directly before the access or
 *   in the access instruction itself (depending on the ISA).
 *
 *   This is the second part of the "SLOP" region as defined in "WASM Linear
 *   Memory structure" in ArrayBufferObject.cpp.
 *
 * - For 32-bit memories on 64-bit systems where we determine there is plenty of
 *   virtual memory space, we use "huge memories", in which we reserve 4GiB + R
 *   bytes of memory regardless of the memory's byteLength. Since the address
 *   itself has a 4GiB range, this allows us to skip bounds checks on the
 *   address as well. The extra R bytes of guard pages protect against
 *   out-of-bounds offsets as above.
 *
 *   The offset can be added into the pointer (using 64-bit arithmetic) either
 *   directly before the access or in the access instruction.
 *
 * In both cases, accesses with offsets greater than R-access_size must be
 * explicitly bounds checked in full, with an overflow check, since we cannot
 * rely on the guard region.
 *
 * The value of R may vary depending on the memory allocation strategy and the
 * amount of address space we can freely reserve. We do not document it here
 * lest it be absurdly out of date. Search for "OffsetGuardLimit" if you wish.
 *
 * All memories in a process use the same strategy, selected at process startup.
 * This is because the machine code embeds the strategy it's been compiled with,
 * and may later be exposed to memories originating from different modules or
 * directly from JS. If the memories did not all use the same strategy, we would
 * have to recompile the code for each case.
 *
 *
 * ## The boundsCheckLimit and the byteLength
 *
 * One would expect the boundsCheckLimit to always equal the memory's current
 * byteLength. However, because the memory can grow, this means each bounds
 * check must first load the boundsCheckLimit from the instance.
 *
 * We can sometimes avoid this load by observing that, even for non-huge
 * memories, the signal handler is the final source of truth. In any case where
 * we make a single memory reservation up front, we can set the boundsCheckLimit
 * to the maximum possible byteLength. (For example, huge memories and memories
 * with a max - anything that will NOT move on grow.)
 *
 *
 *           b.c. pass         b.c. pass         b.c. fail
 *           s.h. pass         s.h. fail         s.h. n/a
 *   ─ ─ ─────────────────┼─────────────────┼────────────── ─ ─
 *
 *   ─ ─ ─────────────────────────────────────────────────────┐
 *   ─ ─ ─────────────────│─────────────────│─────────────────│
 *                    byteLength     boundsCheckLimit     mappedSize
 *
 *   ─ ─ ─────────────────┘
 *           COMMITTED
 *                        └─────────────────┴─────────────────┘
 *                                         SLOP
 *
 *
 * Note that this works even if byteLength later grows:
 *
 *
 *                             b.c. pass         b.c. fail
 *                             s.h. pass         s.h. n/a
 *   ─ ─ ───────────────────────────────────┼────────────── ─ ─
 *
 *   ─ ─ ─────────────────────────────────────────────────────┐
 *   ─ ─ ───────────────────────────────────│─────────────────│
 *                                      byteLength        mappedSize
 *                                   boundsCheckLimit
 *
 *   ─ ─ ───────────────────────────────────┘
 *                    COMMITTED
 *                                          └─────────────────┘
 *                                                 SLOP
 *
 *
 * Therefore, the boundsCheckLimit need only be greater than byteLength, not
 * equal to byteLength, and the boundsCheckLimit need only be loaded once. This
 * is the first part of the "SLOP" region as defined in "WASM Linear Memory
 * structure" in ArrayBufferObject.cpp.
 *
 *
 * ## Size of the boundsCheckLimit
 *
 * The boundsCheckLimit that is stored in the instance is always valid and is
 * always a 64-bit value, and it is always correct to load it and use it as a
 * 64-bit value. However, in situations when the 32 upper bits are known to be
 * zero, it is also correct to load just the low 32 bits, and use that value as
 * the limit. (This does not require a different address, since the limit is
 * always little-endian when a JIT is enabled)
 *
 * On x86 and arm32 (and on any other 32-bit platform, should there ever be
 * one), we always use explicit bounds checks, and the boundsCheckLimit can
 * always be treated as a 32-bit quantity.
 *
 * On all 64-bit platforms, we may use explicit bounds checking or huge memories
 * for memory32, but must always use explicit bounds checking for memory64. If
 * the heap has a known maximum size that is less than 4GiB, then the
 * boundsCheckLimit can be treated as a 32-bit quantity; otherwise it must be
 * treated as a 64-bit quantity.
 *
 *
 * ## Constant pointers
 *
 * If the pointer is constant then the EA can be computed at compile time, and
 * if (EA + access_size) is below the initial memory size, then the bounds check
 * can always be elided.
 *
 *
 * ## Alignment checks
 *
 * On all platforms, some accesses (currently atomics) require an alignment
 * check: the EA must be naturally aligned for the datum being accessed.
 * However, we do not need to compute the EA properly, we care only about the
 * low bits - a cheap, overflowing add is fine, and if the offset is known to be
 * aligned, only the address need be checked.
 */

// Bounds checks always compare the base of the memory access with the bounds
// check limit. If the memory access is unaligned, this means that, even if the
// bounds check succeeds, a few bytes of the access can extend past the end of
// memory. To guard against this, extra space is included in the guard region to
// catch the overflow. MaxMemoryAccessSize is a conservative approximation of
// the maximum guard space needed to catch all unaligned overflows.
//
// Also see "Linear memory addresses and bounds checking" above.

static const unsigned MaxMemoryAccessSize = LitVal::sizeofLargestValue();

// All plausible targets must be able to do at least IEEE754 double
// loads/stores, hence the lower limit of 8.  Some Intel processors support
// AVX-512 loads/stores, hence the upper limit of 64.
static_assert(MaxMemoryAccessSize >= 8, "MaxMemoryAccessSize too low");
static_assert(MaxMemoryAccessSize <= 64, "MaxMemoryAccessSize too high");
static_assert((MaxMemoryAccessSize & (MaxMemoryAccessSize - 1)) == 0,
              "MaxMemoryAccessSize is not a power of two");

#ifdef WASM_SUPPORTS_HUGE_MEMORY

static_assert(MaxMemoryAccessSize <= HugeUnalignedGuardPage,
              "rounded up to static page size");
static_assert(HugeOffsetGuardLimit < UINT32_MAX,
              "checking for overflow against OffsetGuardLimit is enough.");

// We have only tested huge memory on x64, arm64 and riscv64.
#  if !(defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_ARM64) || \
        defined(JS_CODEGEN_RISCV64))
#    error "Not an expected configuration"
#  endif

#endif

// On !WASM_SUPPORTS_HUGE_MEMORY platforms:
//  - For WebAssembly memories, an additional GuardSize is mapped after the
//    accessible region of the memory to catch folded (base+offset) accesses
//    where `offset < OffsetGuardLimit` as well as the overflow from unaligned
//    accesses, as described above for MaxMemoryAccessSize.

static const size_t OffsetGuardLimit =
    StandardPageSizeBytes - MaxMemoryAccessSize;

static_assert(MaxMemoryAccessSize < GuardSize,
              "Guard page handles partial out-of-bounds");
static_assert(OffsetGuardLimit < UINT32_MAX,
              "checking for overflow against OffsetGuardLimit is enough.");

uint64_t wasm::GetMaxOffsetGuardLimit(bool hugeMemory, PageSize sz) {
#ifndef ENABLE_WASM_CUSTOM_PAGE_SIZES
  MOZ_ASSERT(sz == PageSize::Standard);
#endif

  uint64_t guardLimit = sz == PageSize::Standard ? OffsetGuardLimit : 0;
#ifdef WASM_SUPPORTS_HUGE_MEMORY
  return hugeMemory ? HugeOffsetGuardLimit : guardLimit;
#else
  return guardLimit;
#endif
}

// Assert that our minimum offset guard limit covers our inline
// memory.copy/fill optimizations.
static const size_t MinOffsetGuardLimit = OffsetGuardLimit;
static_assert(MaxInlineMemoryCopyLength < MinOffsetGuardLimit, "precondition");
static_assert(MaxInlineMemoryFillLength < MinOffsetGuardLimit, "precondition");

wasm::Pages wasm::MaxMemoryPages(AddressType t, PageSize pageSize) {
#ifdef JS_64BIT
  MOZ_ASSERT_IF(t == AddressType::I64, !IsHugeMemoryEnabled(t, pageSize));
  size_t desired = MaxMemoryPagesValidation(t, pageSize);
  size_t actual =
      ArrayBufferObject::ByteLengthLimit / PageSizeInBytes(pageSize);
  return wasm::Pages::fromPageCount(std::min(desired, actual), pageSize);
#else
  // On 32-bit systems, the heap limit must be representable in the nonnegative
  // range of an int32_t, which means the maximum heap size as observed by wasm
  // code is one wasm page less than 2GB.
  MOZ_ASSERT(ArrayBufferObject::ByteLengthLimit >=
             INT32_MAX / PageSizeInBytes(pageSize));
  return wasm::Pages::fromPageCount(INT32_MAX / PageSizeInBytes(pageSize),
                                    pageSize);
#endif
}

size_t wasm::MaxMemoryBoundsCheckLimit(AddressType t, PageSize pageSize) {
  return MaxMemoryBytes(t, pageSize);
}

Pages wasm::ClampedMaxPages(AddressType t, Pages initialPages,
                            const mozilla::Maybe<Pages>& sourceMaxPages,
                            bool useHugeMemory) {
  PageSize pageSize = initialPages.pageSize();
  Pages clampedMaxPages = Pages::forPageSize(pageSize);

  if (sourceMaxPages.isSome()) {
    // There is a specified maximum, clamp it to the implementation limit of
    // maximum pages
    clampedMaxPages =
        std::min(*sourceMaxPages, wasm::MaxMemoryPages(t, pageSize));

#ifndef JS_64BIT
    static_assert(sizeof(uintptr_t) == 4, "assuming not 64 bit implies 32 bit");

    // On 32-bit platforms, prevent applications specifying a large max (like
    // MaxMemoryPages()) from unintentially OOMing the browser: they just want
    // "a lot of memory". Maintain the invariant that initialPages <=
    // clampedMaxPages.
    static const uint64_t OneGib = 1 << 30;
    const Pages OneGibPages = Pages::fromByteLengthExact(OneGib, pageSize);

    Pages clampedPages = std::max(OneGibPages, initialPages);
    clampedMaxPages = std::min(clampedPages, clampedMaxPages);
#endif
  } else {
    // There is not a specified maximum, fill it in with the implementation
    // limit of maximum pages
    clampedMaxPages = wasm::MaxMemoryPages(t, pageSize);
  }

  // Double-check our invariants
  MOZ_RELEASE_ASSERT(sourceMaxPages.isNothing() ||
                     clampedMaxPages <= *sourceMaxPages);
  MOZ_RELEASE_ASSERT(clampedMaxPages <= wasm::MaxMemoryPages(t, pageSize));
  MOZ_RELEASE_ASSERT(initialPages <= clampedMaxPages);

  return clampedMaxPages;
}

size_t wasm::ComputeMappedSize(wasm::Pages clampedMaxPages) {
  // Caller is responsible to ensure that clampedMaxPages has been clamped to
  // implementation limits.
  size_t maxSize = clampedMaxPages.byteLength();

  // For tiny page sizes, round up the mapped size to a multiple of the
  // system page size after clamping.
#ifdef ENABLE_WASM_CUSTOM_PAGE_SIZES
  if (clampedMaxPages.pageSize() == wasm::PageSize::Tiny) {
    mozilla::CheckedInt<size_t> length(maxSize);

    if (length.value() % gc::SystemPageSize() != 0) {
      length += ComputeByteAlignment(length.value(), gc::SystemPageSize());
      // This should be valid because of previous clamping.
      MOZ_RELEASE_ASSERT(length.isValid());
      MOZ_ASSERT(length.value() % gc::SystemPageSize() == 0);
      maxSize = length.value();
    }

    MOZ_ASSERT(maxSize <= clampedMaxPages.byteLength() + GuardSize);
  }
#endif

  MOZ_ASSERT(maxSize % gc::SystemPageSize() == 0);
  MOZ_ASSERT(GuardSize % gc::SystemPageSize() == 0);
  if (clampedMaxPages.pageSize() == PageSize::Standard) {
    maxSize += GuardSize;
  } else {
#ifdef ENABLE_WASM_CUSTOM_PAGE_SIZES
    // In the case of a tiny page, we omit the guard page size
    // because we can't use guard pages for tiny page bounds checks.
    MOZ_ASSERT(clampedMaxPages.pageSize() == PageSize::Tiny);
#else
    MOZ_CRASH();
#endif
  }

  return maxSize;
}
