tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

WasmStructLayout.cpp (17623B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
      2 * vim: set ts=8 sts=2 et sw=2 tw=80:
      3 * This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "wasm/WasmStructLayout.h"
      8 
      9 #include "mozilla/DebugOnly.h"
     10 #include "mozilla/HashFunctions.h"
     11 
     12 #include "jstypes.h"  // RoundUp
     13 
     14 // This is a simple implementation of a layouter.  It places the OOL pointer
     15 // at the start of the IL payload area, regardless of whether an OOL area is
     16 // actually necessary.
     17 
     18 namespace js::wasm {
     19 
     20 //=========================================================================
     21 // BitVector
     22 
     23 // See comment in WasmStructLayout.h for meaning of "byte", "offset" and
     24 // "chunk".
     25 
     26 #ifdef DEBUG
     27 static bool Is8Aligned(uint32_t n) { return (n & 7) == 0; }
     28 
     29 static bool IsWordAligned(uintptr_t x) { return (x % sizeof(void*)) == 0; }
     30 #endif
     31 
     32 static uint32_t IndexOfLeastSignificantZeroBit(uint8_t n) {
     33  for (uint32_t i = 0; i < 8; i++) {
     34    if (((n >> i) & 1) == 0) {
     35      return i;
     36    }
     37  }
     38  MOZ_CRASH();
     39 }
     40 static uint32_t IndexOfLeastSignificantZero2Bits(uint8_t n) {
     41  for (uint32_t i = 0; i < 8; i += 2) {
     42    if (((n >> i) & 3) == 0) {
     43      return i;
     44    }
     45  }
     46  MOZ_CRASH();
     47 }
     48 static uint32_t IndexOfLeastSignificantZero4Bits(uint8_t n) {
     49  for (uint32_t i = 0; i < 8; i += 4) {
     50    if (((n >> i) & 0xF) == 0) {
     51      return i;
     52    }
     53  }
     54  MOZ_CRASH();
     55 }
     56 
     57 static uint32_t IndexOfMostSignificantOneBit(uint8_t n) {
     58  for (int32_t i = 7; i >= 0; i--) {
     59    if (((n >> i) & 1) == 1) {
     60      return uint32_t(i);
     61    }
     62  }
     63  MOZ_CRASH();
     64 }
     65 
     66 #ifdef DEBUG
     67 static uint32_t OffsetToChunkNumber(uint32_t offset) { return offset / 8; }
     68 #endif
     69 
     70 uint32_t BitVector::hashNonZero() const {
     71  mozilla::HashNumber hash(42);
     72  for (uint8_t b : chunks_) {
     73    if (b != 0) {
     74      hash = mozilla::AddToHash(hash, b);
     75    }
     76  }
     77  return uint32_t(hash);
     78 }
     79 
     80 uint32_t BitVector::totalOffset() const {
     81  if (chunks_.empty()) {
     82    return 0;
     83  }
     84  // Find the highest non-zero chunk.
     85  size_t i;
     86  for (i = chunks_.length(); i >= 1; i--) {
     87    if (chunks_[i - 1] != 0) {
     88      break;
     89    }
     90  }
     91  if (i == 0) {
     92    // There are chunks, but none got used.
     93    return 0;
     94  }
     95  i--;
     96  MOZ_ASSERT(i < chunks_.length());
     97  return 8 * uint32_t(i) + IndexOfMostSignificantOneBit(chunks_[i]) + 1;
     98 }
     99 
    100 BitVector::Result BitVector::addMoreChunks() {
    101  for (uint32_t i = 0; i < LookbackLimit / 2; i++) {
    102    if (!chunks_.append(0)) {
    103      return Result::OOM;
    104    }
    105  }
    106  return Result::OK;
    107 }
    108 
    109 BitVector::Result BitVector::init(uint32_t chunksReserved,
    110                                  uint32_t chunksTotal) {
    111  MOZ_ASSERT_IF(chunksReserved > 0, chunksReserved < chunksTotal);
    112  if (!chunks_.resize(chunksTotal)) {
    113    return Result::OOM;
    114  }
    115  for (uint32_t i = 0; i < chunksReserved; i++) {
    116    chunks_[i] = 0xFF;
    117  }
    118  for (uint32_t i = chunksReserved; i < chunksTotal; i++) {
    119    chunks_[i] = 0;
    120  }
    121  return Result::OK;
    122 }
    123 
    124 BitVector::Result BitVector::allocate(uint32_t size, uint32_t firstChunk,
    125                                      uint32_t lastChunkPlus1,
    126                                      uint32_t* offset) {
    127  MOZ_ASSERT(firstChunk < lastChunkPlus1);
    128  MOZ_ASSERT(lastChunkPlus1 <= chunks_.length());
    129 
    130  // We don't want to re-scan the entire vector every search; that's
    131  // expensive (quadratic).  Instead just re-scan the last 24 chunks and
    132  // accept that we'll miss out on the opportunity to use alignment holes
    133  // more than 192 bytes back from the current "fill point" for the struct.
    134  if (lastChunkPlus1 - firstChunk > LookbackLimit) {
    135    firstChunk = lastChunkPlus1 - LookbackLimit;
    136  }
    137 
    138  // These are arranged in order of conceptually-simplest first.
    139  switch (size) {
    140    case 8: {
    141      // Any chunk that is zero will do.
    142      for (uint32_t i = firstChunk; i < lastChunkPlus1; i++) {
    143        if (chunks_[i] == 0) {
    144          *offset = i * 8;
    145          chunks_[i] = 0xFF;
    146          return Result::OK;
    147        }
    148      }
    149      break;
    150    }
    151    case 16: {
    152      // Any chunk-pair that is zero will do.  Note this 8-aligns 16-byte
    153      // requests, but we can't avoid that because the underlying JS heap
    154      // allocator only provides 8-aligned addresses anyway.
    155      for (uint32_t i = firstChunk + 1; i < lastChunkPlus1; i++) {
    156        if (chunks_[i - 1] == 0 && chunks_[i] == 0) {
    157          *offset = (i - 1) * 8;
    158          chunks_[i - 1] = 0xFF;
    159          chunks_[i] = 0xFF;
    160          return Result::OK;
    161        }
    162      }
    163      break;
    164    }
    165    // The 4, 2 and 1-byte cases are the most complex.  We have to find a
    166    // single chunk with that many consecutive, aligned bits, as zero.
    167    case 1: {
    168      // Any chunk that has an unset bit is fine.
    169      for (uint32_t i = firstChunk; i < lastChunkPlus1; i++) {
    170        if (chunks_[i] != 0xFF) {
    171          uint32_t bitShift = IndexOfLeastSignificantZeroBit(chunks_[i]);
    172          *offset = i * 8 + bitShift;
    173          chunks_[i] |= (1 << bitShift);
    174          return Result::OK;
    175        }
    176      }
    177      break;
    178    }
    179    case 4: {
    180      // Find a chunk in which either the upper or lower half is zero.
    181      for (uint32_t i = firstChunk; i < lastChunkPlus1; i++) {
    182        if ((chunks_[i] & (0xF << 0)) == 0 || (chunks_[i] & (0xF << 4)) == 0) {
    183          uint32_t bitShift = IndexOfLeastSignificantZero4Bits(chunks_[i]);
    184          *offset = i * 8 + bitShift;
    185          chunks_[i] |= (0x0F << bitShift);
    186          return Result::OK;
    187        }
    188      }
    189      break;
    190    }
    191    case 2: {
    192      // Find a chunk in which an adjacent bit-pair is zero.
    193      for (uint32_t i = firstChunk; i < lastChunkPlus1; i++) {
    194        if ((chunks_[i] & (3 << 0)) == 0 || (chunks_[i] & (3 << 2)) == 0 ||
    195            (chunks_[i] & (3 << 4)) == 0 || (chunks_[i] & (3 << 6)) == 0) {
    196          uint32_t bitShift = IndexOfLeastSignificantZero2Bits(chunks_[i]);
    197          *offset = i * 8 + bitShift;
    198          chunks_[i] |= (3 << bitShift);
    199          return Result::OK;
    200        }
    201      }
    202      break;
    203    }
    204    default: {
    205      MOZ_CRASH();
    206    }
    207  }
    208  return Result::Fail;
    209 }
    210 
    211 // Given that `offset` was allocated by a call to `allocate`
    212 // (requesting size `size`), free up that area.
    213 void BitVector::deallocate(uint32_t offset, uint32_t size) {
    214  MOZ_ASSERT(OffsetToChunkNumber(offset + size - 1) < chunks_.length());
    215  switch (size) {
    216    case 8: {
    217      MOZ_ASSERT((offset % 8) == 0);
    218      uint32_t chunk = offset / 8;
    219      MOZ_ASSERT(chunks_[chunk] == 0xFF);
    220      chunks_[chunk] = 0;
    221      break;
    222    }
    223    case 16: {
    224      MOZ_ASSERT((offset % 8) == 0);  // re 8, see comment on ::allocate
    225      uint32_t chunk = offset / 8;
    226      MOZ_ASSERT(chunk + 1 < chunks_.length());
    227      MOZ_ASSERT(chunks_[chunk] == 0xFF);
    228      MOZ_ASSERT(chunks_[chunk + 1] == 0xFF);
    229      chunks_[chunk] = 0;
    230      chunks_[chunk + 1] = 0;
    231      break;
    232    }
    233    case 1: {
    234      uint32_t chunk = offset / 8;
    235      uint32_t shift = offset % 8;  // 0, 1, 2, 3, 4, 5, 6 or 7
    236      uint8_t mask = 1 << shift;
    237      MOZ_ASSERT((chunks_[chunk] & mask) == mask);
    238      chunks_[chunk] &= ~mask;
    239      break;
    240    }
    241    case 4: {
    242      MOZ_ASSERT((offset % 4) == 0);
    243      uint32_t chunk = offset / 8;
    244      uint32_t shift = offset % 8;  // 0 or 4
    245      uint8_t mask = 0xF << shift;
    246      MOZ_ASSERT((chunks_[chunk] & mask) == mask);
    247      chunks_[chunk] &= ~mask;
    248      break;
    249    }
    250    case 2: {
    251      MOZ_ASSERT((offset % 2) == 0);
    252      uint32_t chunk = offset / 8;
    253      uint32_t shift = offset % 8;  // 0, 2, 4 or 6
    254      uint8_t mask = 0x3 << shift;
    255      MOZ_ASSERT((chunks_[chunk] & mask) == mask);
    256      chunks_[chunk] &= ~mask;
    257      break;
    258    }
    259    default: {
    260      MOZ_CRASH();
    261    }
    262  }
    263 }
    264 
    265 //=========================================================================
    266 // FixedSizeBitVector
    267 
    268 BitVector::Result FixedSizeBitVector::init(uint32_t layoutBytesReserved,
    269                                           uint32_t layoutBytesTotal) {
    270  MOZ_ASSERT(layoutBytesTotal > 0);
    271  MOZ_ASSERT(layoutBytesReserved < layoutBytesTotal);
    272  MOZ_ASSERT(Is8Aligned(layoutBytesReserved));
    273  MOZ_ASSERT(Is8Aligned(layoutBytesTotal));
    274  chunksReserved_ = layoutBytesReserved / 8;
    275  chunksTotal_ = layoutBytesTotal / 8;
    276  return BitVector::init(chunksReserved_, chunksTotal_);
    277 }
    278 
    279 BitVector::Result FixedSizeBitVector::allocate(uint32_t size,
    280                                               uint32_t* offset) {
    281  return BitVector::allocate(size, chunksReserved_, chunksTotal_, offset);
    282 }
    283 
    284 //=========================================================================
    285 // VariableSizeBitVector
    286 
    287 BitVector::Result VariableSizeBitVector::init() {
    288  // This initial size of 1 is important in that it needs to be less than
    289  // ::LookbackLimit.
    290  return BitVector::init(0, 1 /*see ::unused()*/);
    291 }
    292 
    293 BitVector::Result VariableSizeBitVector::allocate(uint32_t size,
    294                                                  uint32_t* offset) {
    295  // First, try to find it given the chunks we already have.
    296  Result res = BitVector::allocate(size, 0, chunks_.length(), offset);
    297  if (res == Result::OOM) {
    298    return Result::OOM;
    299  }
    300  if (res == Result::OK) {
    301    used_ = true;
    302    return res;
    303  }
    304  // That failed, so add some more (uncommitted) chunks on the end of `chunks_`
    305  // and try again.  This second attempt *must* succeed since we can make the
    306  // OOL block arbitrarily large.
    307  res = addMoreChunks();
    308  if (res == Result::OOM) {
    309    return Result::OOM;
    310  }
    311  res = BitVector::allocate(size, 0, chunks_.length(), offset);
    312  if (res == Result::OOM) {
    313    return Result::OOM;
    314  }
    315  MOZ_RELEASE_ASSERT(res == Result::OK);
    316  used_ = true;
    317  return Result::OK;
    318 }
    319 
    320 bool VariableSizeBitVector::unused() const { return !used_; }
    321 
    322 uint32_t VariableSizeBitVector::totalOffset() const {
    323  uint32_t res = BitVector::totalOffset();
    324  MOZ_ASSERT(used_ == (res > 0));
    325  return res;
    326 }
    327 
    328 //=========================================================================
    329 // StructLayout
    330 
    331 bool StructLayout::init(uint32_t firstUsableILOffset, uint32_t usableILSize) {
    332  // Not actually necessary, but it would be strange if this wasn't so.
    333  MOZ_ASSERT(IsWordAligned(firstUsableILOffset));
    334  MOZ_ASSERT(IsWordAligned(usableILSize));
    335  // Must have at least enough space to hold the OOL pointer
    336  MOZ_ASSERT(usableILSize >= sizeof(void*));
    337  oolptrILO_ = InvalidOffset;
    338  BitVector::Result res = ilBitVector_.init(firstUsableILOffset,
    339                                            firstUsableILOffset + usableILSize);
    340  if (res == BitVector::Result::OOM) {
    341    return false;
    342  }
    343  res = oolBitVector_.init();
    344  if (res == BitVector::Result::OOM) {
    345    return false;
    346  }
    347  return true;
    348 }
    349 
    350 // Add a field of the specified size, and get back its access path.  The two
    351 // release assertions together guarantee that the maximum offset that could be
    352 // generated is roughly `16 * js::wasm::MaxStructFields`, so there is no need
    353 // to use checked integers in the layout computations.
    354 
    355 bool StructLayout::addField(uint32_t fieldSize, FieldAccessPath* path) {
    356  MOZ_ASSERT(fieldSize == 16 || fieldSize == 8 || fieldSize == 4 ||
    357             fieldSize == 2 || fieldSize == 1);
    358  // Guard against field-offset overflow.
    359  numFieldsProcessed_++;
    360  MOZ_RELEASE_ASSERT(numFieldsProcessed_ <= js::wasm::MaxStructFields);
    361  MOZ_RELEASE_ASSERT(fieldSize <= 16);
    362 
    363  *path = FieldAccessPath();
    364 
    365  // This is complex.  In between calls to ::addField, we maintain the
    366  // following invariant:
    367  //
    368  // (0) If the OOL area is not in use, then it is possible to allocate the OOL
    369  //     pointer in the IL area.
    370  //
    371  // With that in place, the code below deals with 4 cases:
    372  //
    373  // (1) The OOL area is unused, and both the field and a dummy OOL pointer fit
    374  //     into the IL area.  Allocate the field IL and leave the OOL area
    375  //     unused.  Because we just established that a dummy OOL pointer fits in
    376  //     IL after the field, and because of (N) below, (0) is true after the
    377  //     call.
    378  //
    379  // (2) The OOL area is unused, but the field and dummy OOL pointer don't both
    380  //     fit in the IL area.  We need to bring the OOL area into use.  Allocate
    381  //     the OOL pointer in the IL area (which due to (0) cannot fail), and
    382  //     allocate the field in the OOL area.  This is a one-time transitional
    383  //     case that separates multiple occurrences of (1) from multiple
    384  //     occurrences of (3) and (4).  This means the OOL area is now in use, so
    385  //     (0) is trivially true after the call.
    386  //
    387  // (3) The OOL area is in use, but the field fits in the IL area anyways,
    388  //     presumably because it falls into an alignment hole in the IL area.
    389  //     Just allocate it IL and leave everything else unchanged.  Since the
    390  //     OOL area is in use, (0) is trivially true after the call.
    391  //
    392  // (4) The OOL area is in use, and the field doesn't fit in the IL area.
    393  //     Allocate it in the OOL area.  Since the OOL area is in use, (0) is
    394  //     trivially true after the call.
    395  //
    396  // (N) Note: for (1) and (2) it is important to try for the allocation of the
    397  //     field first and the dummy OOL pointer second.
    398 
    399  // For cases (1) and (2) we need to back out tentative allocations.  Hash the
    400  // current state so we can later assert it is unchanged after backouts.
    401  mozilla::DebugOnly<uint32_t> initialHash = hash();
    402 
    403  // These need to agree.
    404  MOZ_ASSERT(oolBitVector_.unused() == (oolptrILO_ == InvalidOffset));
    405 
    406  // Try for Case (1)
    407  if (oolBitVector_.unused()) {
    408    uint32_t fieldOffset = InvalidOffset;
    409    BitVector::Result res = ilBitVector_.allocate(fieldSize, &fieldOffset);
    410    if (res == BitVector::Result::OOM) {
    411      return false;
    412    }
    413    // The field fits, now try for the dummy OOL pointer
    414    mozilla::DebugOnly<uint32_t> hash2 = hash();
    415    if (res == BitVector::Result::OK) {
    416      uint32_t dummyOffset = InvalidOffset;
    417      res = ilBitVector_.allocate(sizeof(void*), &dummyOffset);
    418      if (res == BitVector::Result::OOM) {
    419        return false;
    420      }
    421      if (res == BitVector::Result::OK) {
    422        // Case (1) established -- they both fit.
    423        // Back out the dummy OOL pointer allocation, and we're done.
    424        MOZ_ASSERT(fieldOffset != dummyOffset);
    425        ilBitVector_.deallocate(dummyOffset, sizeof(void*));
    426        MOZ_ASSERT(hash() == hash2);
    427        *path = FieldAccessPath(fieldOffset);
    428        return true;
    429      }
    430      // The field fits, but the OOL pointer doesn't.  Back out the field
    431      // allocation, so that we have changed nothing.
    432      ilBitVector_.deallocate(fieldOffset, fieldSize);
    433    }
    434  }
    435 
    436  // "state is unchanged from when we started"
    437  MOZ_ASSERT(hash() == initialHash);
    438  MOZ_ASSERT(oolBitVector_.unused() == (oolptrILO_ == InvalidOffset));
    439 
    440  // Try for Case (2)
    441  if (oolBitVector_.unused()) {
    442    // We need to bring the OOL area into use.  First, try to allocate the OOL
    443    // pointer field.  This must succeed (apart from OOMing) because of (1).
    444    uint32_t oolptrOffset = InvalidOffset;
    445    BitVector::Result res = ilBitVector_.allocate(sizeof(void*), &oolptrOffset);
    446    if (res == BitVector::Result::OOM) {
    447      return false;
    448    }
    449    MOZ_ASSERT(res == BitVector::Result::OK);
    450    // Case (2) established
    451    oolptrILO_ = oolptrOffset;
    452    // Allocate the field in the OOL area; it is the first item there.
    453    uint32_t fieldOffset = InvalidOffset;
    454    res = oolBitVector_.allocate(fieldSize, &fieldOffset);
    455    if (res == BitVector::Result::OOM) {
    456      return false;
    457    }
    458    // Allocation in the OOL area can't fail.
    459    MOZ_RELEASE_ASSERT(res == BitVector::Result::OK);
    460    MOZ_ASSERT(!oolBitVector_.unused());
    461    // We expect this because this is the first item in the OOL area.
    462    MOZ_ASSERT(fieldOffset == 0);
    463    *path = FieldAccessPath(oolptrILO_, fieldOffset);
    464    return true;
    465  }
    466 
    467  // "state is unchanged from when we started"
    468  MOZ_ASSERT(hash() == initialHash);
    469  MOZ_ASSERT(!oolBitVector_.unused() && oolptrILO_ != InvalidOffset);
    470 
    471  // Cases (3) and (4).  In both cases, the OOL area is in use.
    472  // Re-try allocating the field IL.  Note this is not redundant w.r.t. the
    473  // logic above, since that involved trying to allocate both the field and
    474  // the dummy OOL pointer; this only tries to allocate the field.
    475  uint32_t fieldOffset = InvalidOffset;
    476  BitVector::Result res = ilBitVector_.allocate(fieldSize, &fieldOffset);
    477  if (res == BitVector::Result::OOM) {
    478    return false;
    479  }
    480  if (res == BitVector::Result::OK) {
    481    // Case (3) established
    482    *path = FieldAccessPath(fieldOffset);
    483    return true;
    484  }
    485  // Case (4) established
    486  fieldOffset = InvalidOffset;
    487  res = oolBitVector_.allocate(fieldSize, &fieldOffset);
    488  if (res == BitVector::Result::OOM) {
    489    return false;
    490  }
    491  // Allocation in the OOL area can't fail.
    492  MOZ_RELEASE_ASSERT(res == BitVector::Result::OK);
    493  *path = FieldAccessPath(oolptrILO_, fieldOffset);
    494  return true;
    495 }
    496 
    497 uint32_t StructLayout::hash() const {
    498  uint32_t h = ilBitVector_.hashNonZero();
    499  h = (h << 16) | (h >> 16);
    500  h ^= oolBitVector_.hashNonZero();
    501  return h;
    502 }
    503 
    504 uint32_t StructLayout::totalSizeIL() const {
    505  return js::RoundUp(ilBitVector_.totalOffset(), sizeof(void*));
    506 }
    507 
    508 bool StructLayout::hasOOL() const { return !oolBitVector_.unused(); }
    509 
    510 uint32_t StructLayout::totalSizeOOL() const {
    511  MOZ_ASSERT(hasOOL());
    512  MOZ_ASSERT(oolptrILO_ != InvalidOffset);
    513  return js::RoundUp(oolBitVector_.totalOffset(), sizeof(void*));
    514 }
    515 
    516 FieldAccessPath StructLayout::oolPointerPath() const {
    517  MOZ_ASSERT(hasOOL());
    518  MOZ_ASSERT(oolptrILO_ != InvalidOffset);
    519  return FieldAccessPath(oolptrILO_);
    520 }
    521 
    522 }  // namespace js::wasm