Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  WasmMemory.cpp   Sprache: C

 

/*
 * 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.
 *
 * Asm.js memories are limited to 2GB even on 64-bit platforms, and we can
 * therefore always assume a 32-bit bounds check limit for asm.js.
 *
 *
 * ## 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:
//  - To avoid OOM in ArrayBuffer::prepareForAsmJS, asm.js continues to use the
//    original ArrayBuffer allocation which has no guard region at all.
//  - 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;
}

Messung V0.5 in Prozent
C=95 H=90 G=92

¤ Dauer der Verarbeitung: 0.17 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Statistik
#Sources=1127926
#Domains=2039723