tor-browser

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

WasmStructLayout.h (13770B)


      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 #ifndef wasm_WasmStructLayout_h
      8 #define wasm_WasmStructLayout_h
      9 
     10 #include "mozilla/Assertions.h"
     11 #include "mozilla/Vector.h"
     12 
     13 #include <stdint.h>
     14 
     15 #include "vm/MallocProvider.h"
     16 #include "wasm/WasmConstants.h"  // MaxStructFields
     17 
     18 // [SMDOC] Wasm Struct Layout Overview
     19 //
     20 // (1) Struct layout is almost entirely decoupled from the details of
     21 //     WasmStructObject.  Layout needs only to know the fixed-header-size and
     22 //     inline-payload-size for WasmStructObject.  To avoid header-file cycle
     23 //     complexity, the values WasmStructObject_Size_ASSUMED and
     24 //     WasmStructObject_MaxInlineBytes_ASSUMED are defined in this file.  These
     25 //     assumptions are made safe by static assertions in WasmGcObject.h; see
     26 //     comments there.
     27 //
     28 // (2) A structure layout consists of a FieldAccessPath value (see below) for
     29 //     each field, together with the total object size and inline payload size
     30 //     for the WasmStructObject, and possibly the total OOL size required.
     31 //
     32 // (3) Struct layout info is stored in `class wasm::StructType`.  It is
     33 //     computed early in the compilation process, by `StructType::init`, when
     34 //     decoding the module environment.  The struct's AllocKind is also
     35 //     computed.
     36 //
     37 // (4) Structs that do not need an OOL pointer are not forced to have one.
     38 //     Whether one is required can be determined by calling
     39 //     `StructType::hasOOL`.  If one is present, its offset relative to the
     40 //     start of the WasmStructObject is stored in
     41 //     `StructType::oolPointerOffset_`.
     42 //
     43 // (5) When generating code for field accesses, info (4) is not needed.
     44 //     Instead a field access is described by its FieldAccessPath; this is all
     45 //     that is needed (apart from the field's type) to generate the relevant
     46 //     loads/stores.
     47 //
     48 // (6) StructType is the single point of truth for struct layouts.  However,
     49 //     for performance reasons, at instantiation time, some fields of a
     50 //     StructType are copied into the associated TypeDefInstanceData's
     51 //     `cached.strukt` union, mainly so as to make struct allocation faster.
     52 //     This copying is done by `Instance::init`.
     53 //
     54 // (7) WasmStructObject::createStructIL and related machinery look at fields in
     55 //     the TypeDefInstanceData to get relevant run-time info (the AllocKind,
     56 //     etc).
     57 //
     58 // (8) At run-time, it may be necessary to find the OOL pointer for arbitrary
     59 //     WasmStructObjects, mostly in the GC-support routines.  This can be
     60 //     obtained (at some expense) from
     61 //     `WasmStructObject::hasOOLPointer/getOOLPointer` and related methods.
     62 //
     63 // (9) Note: the allocation machinery will ensure that fields of size 1, 2, 4
     64 //     and 8 bytes are naturally aligned.  However, 16 byte fields are only
     65 //     guaranteed 8 byte alignment.  This is because the underlying heap
     66 //     allocator only provides 8 byte alignment, so even if 16 byte fields were
     67 //     16-aligned relative to the start of a WasmStructObject, there's no
     68 //     guarantee they would be 16-aligned when actually written to the heap.
     69 //
     70 // (10) The struct layout mechanism itself, as used in (3), and implemented by
     71 //      StructLayout::addField, works as follows:
     72 //
     73 //      - it contains a fixed-sized bit vector that tracks commitments to the
     74 //        WasmStructObject
     75 //
     76 //      - it contains a variable-sized bit vector that tracks commitments to
     77 //        the OOL storage block
     78 //
     79 //      - the bit vectors make it possible to find and opportunistically fill
     80 //        alignment holes that would otherwise go unused.  They also
     81 //        (critically) make it possible to back out tentative allocations,
     82 //        which is needed to ensure we can always fit an OOL pointer field in
     83 //        the IL storage area if we later need to.
     84 //
     85 //      See StructLayout::addField comments for the exact algorithm.
     86 
     87 namespace js::wasm {
     88 
     89 // These values are defined by WasmStructObject's layout but are needed early
     90 // in the compilation pipeline in order to compute struct layouts.  Rather than
     91 // create a header file cycle involving WasmGcObject.h, WasmTypeDef.h and this
     92 // file, it seems simpler to assume what they are and static_assert this is
     93 // correct on WasmGcObject.h.  These values are expected to change rarely, if
     94 // ever.
     95 //
     96 // See comment on static_assert involving these in WasmGcObject.h.
     97 const size_t WasmStructObject_Size_ASSUMED = 16;
     98 
     99 #ifdef JS_64BIT
    100 const size_t WasmStructObject_MaxInlineBytes_ASSUMED = 136;
    101 #else
    102 const size_t WasmStructObject_MaxInlineBytes_ASSUMED = 128;
    103 #endif
    104 
    105 //=========================================================================
    106 // FieldAccessPath
    107 
    108 // FieldAccessPath describes the offsets needed to access a field in a
    109 // StructType.  It contains either one or two values.
    110 //
    111 // Let `obj` be a `WasmStructObject*`.  Then, for a field with path `p`:
    112 //
    113 // * if !p.hasOOL(), the data is at obj + p.ilOffset().
    114 //
    115 // * if p.hasOOL(), let oolptr = *(obj + p.ilOffset()).
    116 //   The data is at oolptr + p.oolOffset().
    117 //
    118 // It is implied from this that the `ilOffset()` values incorporate the fixed
    119 // header size of WasmStructObject; that does not need to be added on here.
    120 
    121 class FieldAccessPath {
    122  uint32_t path_;
    123  static constexpr uint32_t ILBits = 9;
    124  static constexpr uint32_t OOLBits = 32 - ILBits;
    125  static constexpr uint32_t MaxValidILOffset = (1 << ILBits) - 1;
    126  static constexpr uint32_t MaxValidOOLOffset = (1 << OOLBits) - 1 - 1;
    127  static constexpr uint32_t InvalidOOLOffset = MaxValidOOLOffset + 1;
    128  uint32_t getIL() const { return path_ & MaxValidILOffset; }
    129  uint32_t getOOL() const { return path_ >> ILBits; }
    130  // Ensure ILBits is sufficient for any valid IL offset.
    131  static_assert((WasmStructObject_Size_ASSUMED +
    132                 WasmStructObject_MaxInlineBytes_ASSUMED) < MaxValidILOffset);
    133  // A crude check to ensure that OOLBits is sufficient for any situation,
    134  // assuming a worst-case future scenario where fields can be up to 64 bytes
    135  // long (eg for Intel AVX512 fields).
    136  static_assert(js::wasm::MaxStructFields * 64 < MaxValidOOLOffset);
    137 
    138 public:
    139  FieldAccessPath() : path_(0) {}
    140  explicit FieldAccessPath(uint32_t offsetIL)
    141      : path_((InvalidOOLOffset << ILBits) | offsetIL) {
    142    MOZ_ASSERT(offsetIL <= MaxValidILOffset);
    143  }
    144  FieldAccessPath(uint32_t offsetIL, uint32_t offsetOOL)
    145      : path_((offsetOOL << ILBits) | offsetIL) {
    146    MOZ_ASSERT(offsetIL <= MaxValidILOffset);
    147    MOZ_ASSERT(offsetOOL <= MaxValidOOLOffset);
    148  }
    149  bool hasOOL() const { return getOOL() != InvalidOOLOffset; }
    150  uint32_t ilOffset() const { return getIL(); }
    151  uint32_t oolOffset() const {
    152    MOZ_ASSERT(hasOOL());
    153    return getOOL();
    154  }
    155 };
    156 
    157 static_assert(sizeof(FieldAccessPath) == sizeof(uint32_t));
    158 
    159 //=========================================================================
    160 // BitVector -- helper class for StructLayout
    161 
    162 // BitVector is a base class that does the heavy lifting for WasmStructObject
    163 // and OOL-storage layout.  It provides a bit-vector which keeps track of
    164 // committed space inside the object being laid out.
    165 
    166 // In what follows, "byte" and "offset" refer to those entities in the
    167 // WasmStructObject and/or its OOL area, that we are laying out.  Whereas
    168 // "chunk" refers to refers to a single byte in `BitVector::chunks_`; this in
    169 // turn covers 8 bytes of object allocation.  Confusingly, chunks are actually
    170 // stored as uint8_t's; hence it is important to be clear about the naming.
    171 //
    172 // Within a chunk (a uint8_t), the least significant bit (bit 0) tracks the
    173 // lowest address in the resulting layout, and bit 7 tracks the address 7 bytes
    174 // higher.
    175 
    176 class BitVector {
    177 public:
    178  enum class Result { OOM, OK, Fail };
    179 
    180 private:
    181  // This controls how many chunks we are prepared to look back in order to
    182  // find an allocation.  See comments at its use points below.
    183  static const uint32_t LookbackLimit = 24;
    184  // See comment on ::addMoreChunks.
    185  static_assert(LookbackLimit > (2 * 3));
    186  // For the same reason, the LookbackLimit needs to cover an area at least as
    187  // large as the worst-case inline capacity.
    188  static_assert(LookbackLimit > WasmStructObject_MaxInlineBytes_ASSUMED / 8);
    189 
    190 public:
    191  // The chunks.  Using 64 as the in-line vector size implies that the
    192  // BitVector covers 512 bytes of object allocation, so it will never allocate
    193  // (C++) heap when laying out WasmStructObject itself (as that's smaller than
    194  // 512 bytes) and only rarely when laying out an OOL block.
    195  mozilla::Vector<uint8_t, 64, SystemAllocPolicy> chunks_;
    196 
    197  // Hash the non-zero chunks in the vector.  The non-zero part means the
    198  // result is invariant to the number of trailing zero chunks it has.
    199  uint32_t hashNonZero() const;
    200 
    201  // Returns the the highest index (plus one) tracked in the object being laid
    202  // out.  For this to be efficient requires that `chunks_` does not have a
    203  // very long tail of uncommitted chunks, which is (another reason) why
    204  // `addMoreChunks` is relatively conservative.
    205  uint32_t totalOffset() const;
    206 
    207  // Add some, but not many, extra chunks to `chunks_`; but at a minimum 3, so
    208  // as to be able to cover a worst-case maximum-size (16 byte) allocation plus
    209  // 8 byte alignment hole, that would otherwise fail.  Note it is crucial that
    210  // we don't add more than the LookbackLimit, since that could cause the logic
    211  // that uses it in ::findAlignedWRK to not allocate at the start of the OOL
    212  // area; hence addMoreChunks (somewhat arbitrarily) divides it by two.
    213  Result addMoreChunks();
    214 
    215  // Initialize `chunks_` so as to hold `chunksTotal` chunks, of which the
    216  // first `chunksReserved` are unavailable for allocation.
    217  Result init(uint32_t chunksReserved, uint32_t chunksTotal);
    218 
    219  // Search through the area `chunks_[firstChunk, lastChunkPlus1)` to find a
    220  // naturally-aligned hole of size `size` (for sizes 1, 2, 4 and 8; and
    221  // 8-aligned for size 16).  If successful, mark the hole as occupied and
    222  // return the offset of the lowest byte of the hole.
    223  Result allocate(uint32_t size, uint32_t firstChunk, uint32_t lastChunkPlus1,
    224                  uint32_t* offset);
    225 
    226  // Given that `offset` was allocated by a call to `allocate`
    227  // (requesting size `size`), free up that area.
    228  void deallocate(uint32_t offset, uint32_t size);
    229 };
    230 
    231 //=========================================================================
    232 // FixedSizeBitVector -- helper class for StructLayout
    233 
    234 // FixedSizeBitVector is a specialization of BitVector that is used to track
    235 // layout commitments in WasmStructObject itself.
    236 
    237 class FixedSizeBitVector : public BitVector {
    238  uint32_t chunksTotal_ = 0;
    239  uint32_t chunksReserved_ = 0;
    240 
    241 public:
    242  // Initialize.  `layoutBytesReserved` is the number of bytes unavailable at
    243  // the start of the layout.  `layoutBytesTotal` is the maximum total number
    244  // of bytes in the layout, including the reserved area.
    245  Result init(uint32_t layoutBytesReserved, uint32_t layoutBytesTotal);
    246 
    247  // Ask for an allocation.  This can fail if a suitable hole can't be found.
    248  Result allocate(uint32_t size, uint32_t* offset);
    249 };
    250 
    251 //=========================================================================
    252 // VariableSizedBitVector -- helper class for StructLayout
    253 
    254 // VariableSizeBitVector is a specialization of BitVector that is used to
    255 // track layout commitments in the OOL block.
    256 
    257 class VariableSizeBitVector : public BitVector {
    258  bool used_ = false;
    259 
    260 public:
    261  // Initialize.
    262  Result init();
    263 
    264  // Ask for an allocation.  This can never fail because the OOL block can be
    265  // made arbitrarily large.
    266  Result allocate(uint32_t size, uint32_t* offset);
    267 
    268  // Ask whether any allocations at all have been made so far.
    269  bool unused() const;
    270 
    271  // This has the same behaviour as the BitVector version.
    272  uint32_t totalOffset() const;
    273 };
    274 
    275 //=========================================================================
    276 // StructLayout, the top level interface for structure layout machinery.
    277 
    278 class StructLayout {
    279  //---------------- Interface ----------------
    280 public:
    281  // Initialises the layouter.  `firstUsableILOffset` is the first allowable
    282  // payload byte offset in the IL object; offsets prior to that are considered
    283  // reserved.  `usableILSize` is the maximum allowed size of the IL payload
    284  // area.
    285  bool init(uint32_t firstUsableILOffset, uint32_t usableILSize);
    286 
    287  // Add a field of the specified size, and get back its access path, or
    288  // `false` to indicate OOM.
    289  bool addField(uint32_t fieldSize, FieldAccessPath* path);
    290 
    291  // Return the total IL object size (including reserved area) so far, rounded
    292  // up to an integral number of words.
    293  uint32_t totalSizeIL() const;
    294 
    295  // Returns true iff an OOL area is needed.
    296  bool hasOOL() const;
    297 
    298  // Returns the total OOL block size so far, rounded up to an integral number
    299  // of words.  Invalid to call if `!hasOOL()`.
    300  uint32_t totalSizeOOL() const;
    301 
    302  // Returns the access path in the IL area, to get the OOL pointer.
    303  // Invalid to call if `!hasOOL()`.
    304  FieldAccessPath oolPointerPath() const;
    305 
    306  //---------------- Implementation ----------------
    307 private:
    308  static const uint32_t InvalidOffset = 0xFFFFFFFF;
    309 
    310  // These change as fields are added:
    311  // BitVectors for the IL and OOL areas.
    312  FixedSizeBitVector ilBitVector_;
    313  VariableSizeBitVector oolBitVector_;
    314  uint32_t oolptrILO_ = InvalidOffset;
    315  // The total number of fields processed so far
    316  uint32_t numFieldsProcessed_ = 0;
    317 
    318  uint32_t hash() const;
    319 };
    320 
    321 }  // namespace js::wasm
    322 
    323 #endif /* wasm_WasmStructLayout_h */