Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/js/src/wasm/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 17 kB image not shown  

Quelle  WasmStructLayout.cpp   Sprache: C

 

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


#include "wasm/WasmStructLayout.h"

#include "mozilla/DebugOnly.h"
#include "mozilla/HashFunctions.h"

#include "jstypes.h"  // RoundUp

// This is a simple implementation of a layouter.  It places the OOL pointer
// at the start of the IL payload area, regardless of whether an OOL area is
// actually necessary.

namespace js::wasm {

//=========================================================================
// BitVector

// See comment in WasmStructLayout.h for meaning of "byte", "offset" and
// "chunk".

#ifdef DEBUG
static bool Is8Aligned(uint32_t n) { return (n & 7) == 0; }

static bool IsWordAligned(uintptr_t x) { return (x % sizeof(void*)) == 0; }
#endif

static uint32_t IndexOfLeastSignificantZeroBit(uint8_t n) {
  for (uint32_t i = 0; i < 8; i++) {
    if (((n >> i) & 1) == 0) {
      return i;
    }
  }
  MOZ_CRASH();
}
static uint32_t IndexOfLeastSignificantZero2Bits(uint8_t n) {
  for (uint32_t i = 0; i < 8; i += 2) {
    if (((n >> i) & 3) == 0) {
      return i;
    }
  }
  MOZ_CRASH();
}
static uint32_t IndexOfLeastSignificantZero4Bits(uint8_t n) {
  for (uint32_t i = 0; i < 8; i += 4) {
    if (((n >> i) & 0xF) == 0) {
      return i;
    }
  }
  MOZ_CRASH();
}

static uint32_t IndexOfMostSignificantOneBit(uint8_t n) {
  for (int32_t i = 7; i >= 0; i--) {
    if (((n >> i) & 1) == 1) {
      return uint32_t(i);
    }
  }
  MOZ_CRASH();
}

#ifdef DEBUG
static uint32_t OffsetToChunkNumber(uint32_t offset) { return offset / 8; }
#endif

uint32_t BitVector::hashNonZero() const {
  mozilla::HashNumber hash(42);
  for (uint8_t b : chunks_) {
    if (b != 0) {
      hash = mozilla::AddToHash(hash, b);
    }
  }
  return uint32_t(hash);
}

uint32_t BitVector::totalOffset() const {
  if (chunks_.empty()) {
    return 0;
  }
  // Find the highest non-zero chunk.
  size_t i;
  for (i = chunks_.length(); i >= 1; i--) {
    if (chunks_[i - 1] != 0) {
      break;
    }
  }
  if (i == 0) {
    // There are chunks, but none got used.
    return 0;
  }
  i--;
  MOZ_ASSERT(i < chunks_.length());
  return 8 * uint32_t(i) + IndexOfMostSignificantOneBit(chunks_[i]) + 1;
}

BitVector::Result BitVector::addMoreChunks() {
  for (uint32_t i = 0; i < LookbackLimit / 2; i++) {
    if (!chunks_.append(0)) {
      return Result::OOM;
    }
  }
  return Result::OK;
}

BitVector::Result BitVector::init(uint32_t chunksReserved,
                                  uint32_t chunksTotal) {
  MOZ_ASSERT_IF(chunksReserved > 0, chunksReserved < chunksTotal);
  if (!chunks_.resize(chunksTotal)) {
    return Result::OOM;
  }
  for (uint32_t i = 0; i < chunksReserved; i++) {
    chunks_[i] = 0xFF;
  }
  for (uint32_t i = chunksReserved; i < chunksTotal; i++) {
    chunks_[i] = 0;
  }
  return Result::OK;
}

BitVector::Result BitVector::allocate(uint32_t size, uint32_t firstChunk,
                                      uint32_t lastChunkPlus1,
                                      uint32_t* offset) {
  MOZ_ASSERT(firstChunk < lastChunkPlus1);
  MOZ_ASSERT(lastChunkPlus1 <= chunks_.length());

  // We don't want to re-scan the entire vector every search; that's
  // expensive (quadratic).  Instead just re-scan the last 24 chunks and
  // accept that we'll miss out on the opportunity to use alignment holes
  // more than 192 bytes back from the current "fill point" for the struct.
  if (lastChunkPlus1 - firstChunk > LookbackLimit) {
    firstChunk = lastChunkPlus1 - LookbackLimit;
  }

  // These are arranged in order of conceptually-simplest first.
  switch (size) {
    case 8: {
      // Any chunk that is zero will do.
      for (uint32_t i = firstChunk; i < lastChunkPlus1; i++) {
        if (chunks_[i] == 0) {
          *offset = i * 8;
          chunks_[i] = 0xFF;
          return Result::OK;
        }
      }
      break;
    }
    case 16: {
      // Any chunk-pair that is zero will do.  Note this 8-aligns 16-byte
      // requests, but we can't avoid that because the underlying JS heap
      // allocator only provides 8-aligned addresses anyway.
      for (uint32_t i = firstChunk + 1; i < lastChunkPlus1; i++) {
        if (chunks_[i - 1] == 0 && chunks_[i] == 0) {
          *offset = (i - 1) * 8;
          chunks_[i - 1] = 0xFF;
          chunks_[i] = 0xFF;
          return Result::OK;
        }
      }
      break;
    }
    // The 4, 2 and 1-byte cases are the most complex.  We have to find a
    // single chunk with that many consecutive, aligned bits, as zero.
    case 1: {
      // Any chunk that has an unset bit is fine.
      for (uint32_t i = firstChunk; i < lastChunkPlus1; i++) {
        if (chunks_[i] != 0xFF) {
          uint32_t bitShift = IndexOfLeastSignificantZeroBit(chunks_[i]);
          *offset = i * 8 + bitShift;
          chunks_[i] |= (1 << bitShift);
          return Result::OK;
        }
      }
      break;
    }
    case 4: {
      // Find a chunk in which either the upper or lower half is zero.
      for (uint32_t i = firstChunk; i < lastChunkPlus1; i++) {
        if ((chunks_[i] & (0xF << 0)) == 0 || (chunks_[i] & (0xF << 4)) == 0) {
          uint32_t bitShift = IndexOfLeastSignificantZero4Bits(chunks_[i]);
          *offset = i * 8 + bitShift;
          chunks_[i] |= (0x0F << bitShift);
          return Result::OK;
        }
      }
      break;
    }
    case 2: {
      // Find a chunk in which an adjacent bit-pair is zero.
      for (uint32_t i = firstChunk; i < lastChunkPlus1; i++) {
        if ((chunks_[i] & (3 << 0)) == 0 || (chunks_[i] & (3 << 2)) == 0 ||
            (chunks_[i] & (3 << 4)) == 0 || (chunks_[i] & (3 << 6)) == 0) {
          uint32_t bitShift = IndexOfLeastSignificantZero2Bits(chunks_[i]);
          *offset = i * 8 + bitShift;
          chunks_[i] |= (3 << bitShift);
          return Result::OK;
        }
      }
      break;
    }
    default: {
      MOZ_CRASH();
    }
  }
  return Result::Fail;
}

// Given that `offset` was allocated by a call to `allocate`
// (requesting size `size`), free up that area.
void BitVector::deallocate(uint32_t offset, uint32_t size) {
  MOZ_ASSERT(OffsetToChunkNumber(offset + size - 1) < chunks_.length());
  switch (size) {
    case 8: {
      MOZ_ASSERT((offset % 8) == 0);
      uint32_t chunk = offset / 8;
      MOZ_ASSERT(chunks_[chunk] == 0xFF);
      chunks_[chunk] = 0;
      break;
    }
    case 16: {
      MOZ_ASSERT((offset % 8) == 0);  // re 8, see comment on ::allocate
      uint32_t chunk = offset / 8;
      MOZ_ASSERT(chunk + 1 < chunks_.length());
      MOZ_ASSERT(chunks_[chunk] == 0xFF);
      MOZ_ASSERT(chunks_[chunk + 1] == 0xFF);
      chunks_[chunk] = 0;
      chunks_[chunk + 1] = 0;
      break;
    }
    case 1: {
      uint32_t chunk = offset / 8;
      uint32_t shift = offset % 8;  // 0, 1, 2, 3, 4, 5, 6 or 7
      uint8_t mask = 1 << shift;
      MOZ_ASSERT((chunks_[chunk] & mask) == mask);
      chunks_[chunk] &= ~mask;
      break;
    }
    case 4: {
      MOZ_ASSERT((offset % 4) == 0);
      uint32_t chunk = offset / 8;
      uint32_t shift = offset % 8;  // 0 or 4
      uint8_t mask = 0xF << shift;
      MOZ_ASSERT((chunks_[chunk] & mask) == mask);
      chunks_[chunk] &= ~mask;
      break;
    }
    case 2: {
      MOZ_ASSERT((offset % 2) == 0);
      uint32_t chunk = offset / 8;
      uint32_t shift = offset % 8;  // 0, 2, 4 or 6
      uint8_t mask = 0x3 << shift;
      MOZ_ASSERT((chunks_[chunk] & mask) == mask);
      chunks_[chunk] &= ~mask;
      break;
    }
    default: {
      MOZ_CRASH();
    }
  }
}

//=========================================================================
// FixedSizeBitVector

BitVector::Result FixedSizeBitVector::init(uint32_t layoutBytesReserved,
                                           uint32_t layoutBytesTotal) {
  MOZ_ASSERT(layoutBytesTotal > 0);
  MOZ_ASSERT(layoutBytesReserved < layoutBytesTotal);
  MOZ_ASSERT(Is8Aligned(layoutBytesReserved));
  MOZ_ASSERT(Is8Aligned(layoutBytesTotal));
  chunksReserved_ = layoutBytesReserved / 8;
  chunksTotal_ = layoutBytesTotal / 8;
  return BitVector::init(chunksReserved_, chunksTotal_);
}

BitVector::Result FixedSizeBitVector::allocate(uint32_t size,
                                               uint32_t* offset) {
  return BitVector::allocate(size, chunksReserved_, chunksTotal_, offset);
}

//=========================================================================
// VariableSizeBitVector

BitVector::Result VariableSizeBitVector::init() {
  // This initial size of 1 is important in that it needs to be less than
  // ::LookbackLimit.
  return BitVector::init(0, 1 /*see ::unused()*/);
}

BitVector::Result VariableSizeBitVector::allocate(uint32_t size,
                                                  uint32_t* offset) {
  // First, try to find it given the chunks we already have.
  Result res = BitVector::allocate(size, 0, chunks_.length(), offset);
  if (res == Result::OOM) {
    return Result::OOM;
  }
  if (res == Result::OK) {
    used_ = true;
    return res;
  }
  // That failed, so add some more (uncommitted) chunks on the end of `chunks_`
  // and try again.  This second attempt *must* succeed since we can make the
  // OOL block arbitrarily large.
  res = addMoreChunks();
  if (res == Result::OOM) {
    return Result::OOM;
  }
  res = BitVector::allocate(size, 0, chunks_.length(), offset);
  if (res == Result::OOM) {
    return Result::OOM;
  }
  MOZ_RELEASE_ASSERT(res == Result::OK);
  used_ = true;
  return Result::OK;
}

bool VariableSizeBitVector::unused() const { return !used_; }

uint32_t VariableSizeBitVector::totalOffset() const {
  uint32_t res = BitVector::totalOffset();
  MOZ_ASSERT(used_ == (res > 0));
  return res;
}

//=========================================================================
// StructLayout

bool StructLayout::init(uint32_t firstUsableILOffset, uint32_t usableILSize) {
  // Not actually necessary, but it would be strange if this wasn't so.
  MOZ_ASSERT(IsWordAligned(firstUsableILOffset));
  MOZ_ASSERT(IsWordAligned(usableILSize));
  // Must have at least enough space to hold the OOL pointer
  MOZ_ASSERT(usableILSize >= sizeof(void*));
  oolptrILO_ = InvalidOffset;
  BitVector::Result res = ilBitVector_.init(firstUsableILOffset,
                                            firstUsableILOffset + usableILSize);
  if (res == BitVector::Result::OOM) {
    return false;
  }
  res = oolBitVector_.init();
  if (res == BitVector::Result::OOM) {
    return false;
  }
  return true;
}

// Add a field of the specified size, and get back its access path.  The two
// release assertions together guarantee that the maximum offset that could be
// generated is roughly `16 * js::wasm::MaxStructFields`, so there is no need
// to use checked integers in the layout computations.

bool StructLayout::addField(uint32_t fieldSize, FieldAccessPath* path) {
  MOZ_ASSERT(fieldSize == 16 || fieldSize == 8 || fieldSize == 4 ||
             fieldSize == 2 || fieldSize == 1);
  // Guard against field-offset overflow.
  numFieldsProcessed_++;
  MOZ_RELEASE_ASSERT(numFieldsProcessed_ <= js::wasm::MaxStructFields);
  MOZ_RELEASE_ASSERT(fieldSize <= 16);

  *path = FieldAccessPath();

  // This is complex.  In between calls to ::addField, we maintain the
  // following invariant:
  //
  // (0) If the OOL area is not in use, then it is possible to allocate the OOL
  //     pointer in the IL area.
  //
  // With that in place, the code below deals with 4 cases:
  //
  // (1) The OOL area is unused, and both the field and a dummy OOL pointer fit
  //     into the IL area.  Allocate the field IL and leave the OOL area
  //     unused.  Because we just established that a dummy OOL pointer fits in
  //     IL after the field, and because of (N) below, (0) is true after the
  //     call.
  //
  // (2) The OOL area is unused, but the field and dummy OOL pointer don't both
  //     fit in the IL area.  We need to bring the OOL area into use.  Allocate
  //     the OOL pointer in the IL area (which due to (0) cannot fail), and
  //     allocate the field in the OOL area.  This is a one-time transitional
  //     case that separates multiple occurrences of (1) from multiple
  //     occurrences of (3) and (4).  This means the OOL area is now in use, so
  //     (0) is trivially true after the call.
  //
  // (3) The OOL area is in use, but the field fits in the IL area anyways,
  //     presumably because it falls into an alignment hole in the IL area.
  //     Just allocate it IL and leave everything else unchanged.  Since the
  //     OOL area is in use, (0) is trivially true after the call.
  //
  // (4) The OOL area is in use, and the field doesn't fit in the IL area.
  //     Allocate it in the OOL area.  Since the OOL area is in use, (0) is
  //     trivially true after the call.
  //
  // (N) Note: for (1) and (2) it is important to try for the allocation of the
  //     field first and the dummy OOL pointer second.

  // For cases (1) and (2) we need to back out tentative allocations.  Hash the
  // current state so we can later assert it is unchanged after backouts.
  mozilla::DebugOnly<uint32_t> initialHash = hash();

  // These need to agree.
  MOZ_ASSERT(oolBitVector_.unused() == (oolptrILO_ == InvalidOffset));

  // Try for Case (1)
  if (oolBitVector_.unused()) {
    uint32_t fieldOffset = InvalidOffset;
    BitVector::Result res = ilBitVector_.allocate(fieldSize, &fieldOffset);
    if (res == BitVector::Result::OOM) {
      return false;
    }
    // The field fits, now try for the dummy OOL pointer
    mozilla::DebugOnly<uint32_t> hash2 = hash();
    if (res == BitVector::Result::OK) {
      uint32_t dummyOffset = InvalidOffset;
      res = ilBitVector_.allocate(sizeof(void*), &dummyOffset);
      if (res == BitVector::Result::OOM) {
        return false;
      }
      if (res == BitVector::Result::OK) {
        // Case (1) established -- they both fit.
        // Back out the dummy OOL pointer allocation, and we're done.
        MOZ_ASSERT(fieldOffset != dummyOffset);
        ilBitVector_.deallocate(dummyOffset, sizeof(void*));
        MOZ_ASSERT(hash() == hash2);
        *path = FieldAccessPath(fieldOffset);
        return true;
      }
      // The field fits, but the OOL pointer doesn't.  Back out the field
      // allocation, so that we have changed nothing.
      ilBitVector_.deallocate(fieldOffset, fieldSize);
    }
  }

  // "state is unchanged from when we started"
  MOZ_ASSERT(hash() == initialHash);
  MOZ_ASSERT(oolBitVector_.unused() == (oolptrILO_ == InvalidOffset));

  // Try for Case (2)
  if (oolBitVector_.unused()) {
    // We need to bring the OOL area into use.  First, try to allocate the OOL
    // pointer field.  This must succeed (apart from OOMing) because of (1).
    uint32_t oolptrOffset = InvalidOffset;
    BitVector::Result res = ilBitVector_.allocate(sizeof(void*), &oolptrOffset);
    if (res == BitVector::Result::OOM) {
      return false;
    }
    MOZ_ASSERT(res == BitVector::Result::OK);
    // Case (2) established
    oolptrILO_ = oolptrOffset;
    // Allocate the field in the OOL area; it is the first item there.
    uint32_t fieldOffset = InvalidOffset;
    res = oolBitVector_.allocate(fieldSize, &fieldOffset);
    if (res == BitVector::Result::OOM) {
      return false;
    }
    // Allocation in the OOL area can't fail.
    MOZ_RELEASE_ASSERT(res == BitVector::Result::OK);
    MOZ_ASSERT(!oolBitVector_.unused());
    // We expect this because this is the first item in the OOL area.
    MOZ_ASSERT(fieldOffset == 0);
    *path = FieldAccessPath(oolptrILO_, fieldOffset);
    return true;
  }

  // "state is unchanged from when we started"
  MOZ_ASSERT(hash() == initialHash);
  MOZ_ASSERT(!oolBitVector_.unused() && oolptrILO_ != InvalidOffset);

  // Cases (3) and (4).  In both cases, the OOL area is in use.
  // Re-try allocating the field IL.  Note this is not redundant w.r.t. the
  // logic above, since that involved trying to allocate both the field and
  // the dummy OOL pointer; this only tries to allocate the field.
  uint32_t fieldOffset = InvalidOffset;
  BitVector::Result res = ilBitVector_.allocate(fieldSize, &fieldOffset);
  if (res == BitVector::Result::OOM) {
    return false;
  }
  if (res == BitVector::Result::OK) {
    // Case (3) established
    *path = FieldAccessPath(fieldOffset);
    return true;
  }
  // Case (4) established
  fieldOffset = InvalidOffset;
  res = oolBitVector_.allocate(fieldSize, &fieldOffset);
  if (res == BitVector::Result::OOM) {
    return false;
  }
  // Allocation in the OOL area can't fail.
  MOZ_RELEASE_ASSERT(res == BitVector::Result::OK);
  *path = FieldAccessPath(oolptrILO_, fieldOffset);
  return true;
}

uint32_t StructLayout::hash() const {
  uint32_t h = ilBitVector_.hashNonZero();
  h = (h << 16) | (h >> 16);
  h ^= oolBitVector_.hashNonZero();
  return h;
}

uint32_t StructLayout::totalSizeIL() const {
  return js::RoundUp(ilBitVector_.totalOffset(), sizeof(void*));
}

bool StructLayout::hasOOL() const { return !oolBitVector_.unused(); }

uint32_t StructLayout::totalSizeOOL() const {
  MOZ_ASSERT(hasOOL());
  MOZ_ASSERT(oolptrILO_ != InvalidOffset);
  return js::RoundUp(oolBitVector_.totalOffset(), sizeof(void*));
}

FieldAccessPath StructLayout::oolPointerPath() const {
  MOZ_ASSERT(hasOOL());
  MOZ_ASSERT(oolptrILO_ != InvalidOffset);
  return FieldAccessPath(oolptrILO_);
}

}  // namespace js::wasm

Messung V0.5 in Prozent
C=79 H=84 G=81

¤ Dauer der Verarbeitung: 0.13 Sekunden  (vorverarbeitet am  2026-09-30) ¤

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