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