SharedStencil.h (34717B)
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 vm_SharedStencil_h 8 #define vm_SharedStencil_h 9 10 #include "mozilla/Assertions.h" // MOZ_ASSERT, MOZ_CRASH 11 #include "mozilla/Atomics.h" // mozilla::{Atomic, SequentiallyConsistent} 12 #include "mozilla/CheckedInt.h" // mozilla::CheckedInt 13 #include "mozilla/HashFunctions.h" // mozilla::HahNumber, mozilla::HashBytes 14 #include "mozilla/HashTable.h" // mozilla::HashSet 15 #include "mozilla/MemoryReporting.h" // mozilla::MallocSizeOf 16 #include "mozilla/RefPtr.h" // RefPtr 17 #include "mozilla/Span.h" // mozilla::Span 18 19 #include <stddef.h> // size_t 20 #include <stdint.h> // uint8_t, uint16_t, uint32_t 21 22 #include "frontend/SourceNotes.h" // js::SrcNote 23 #include "frontend/TypedIndex.h" // js::frontend::TypedIndex 24 25 #include "js/AllocPolicy.h" // js::SystemAllocPolicy 26 #include "js/ColumnNumber.h" // JS::LimitedColumnNumberOneOrigin 27 #include "js/TypeDecls.h" // JSContext,jsbytecode 28 #include "js/UniquePtr.h" // js::UniquePtr 29 #include "js/Vector.h" // js::Vector 30 #include "util/EnumFlags.h" // js::EnumFlags 31 #include "util/TrailingArray.h" // js::TrailingArray 32 #include "vm/GeneratorAndAsyncKind.h" // GeneratorKind, FunctionAsyncKind 33 #include "vm/StencilEnums.h" // js::{TryNoteKind,ImmutableScriptFlagsEnum,MutableScriptFlagsEnum} 34 35 // 36 // Data structures shared between Stencil and the VM. 37 // 38 39 namespace js { 40 41 class FrontendContext; 42 43 namespace frontend { 44 class StencilXDR; 45 } // namespace frontend 46 47 // Index into gcthings array. 48 class GCThingIndexType; 49 class GCThingIndex : public frontend::TypedIndex<GCThingIndexType> { 50 // Delegate constructors; 51 using Base = frontend::TypedIndex<GCThingIndexType>; 52 using Base::Base; 53 54 public: 55 static constexpr GCThingIndex outermostScopeIndex() { 56 return GCThingIndex(0); 57 } 58 59 static constexpr GCThingIndex invalid() { return GCThingIndex(UINT32_MAX); } 60 61 GCThingIndex next() const { return GCThingIndex(index + 1); } 62 }; 63 64 /* 65 * Exception handling record. 66 */ 67 struct TryNote { 68 uint32_t kind_; /* one of TryNoteKind */ 69 uint32_t stackDepth; /* stack depth upon exception handler entry */ 70 uint32_t start; /* start of the try statement or loop relative 71 to script->code() */ 72 uint32_t length; /* length of the try statement or loop */ 73 74 TryNote(uint32_t kind, uint32_t stackDepth, uint32_t start, uint32_t length) 75 : kind_(kind), stackDepth(stackDepth), start(start), length(length) {} 76 77 TryNote() = default; 78 79 TryNoteKind kind() const { return TryNoteKind(kind_); } 80 81 bool isLoop() const { 82 switch (kind()) { 83 case TryNoteKind::Loop: 84 case TryNoteKind::ForIn: 85 case TryNoteKind::ForOf: 86 return true; 87 case TryNoteKind::Catch: 88 case TryNoteKind::Finally: 89 case TryNoteKind::ForOfIterClose: 90 case TryNoteKind::Destructuring: 91 return false; 92 } 93 MOZ_CRASH("Unexpected try note kind"); 94 } 95 }; 96 97 // A block scope has a range in bytecode: it is entered at some offset, and left 98 // at some later offset. Scopes can be nested. Given an offset, the 99 // ScopeNote containing that offset whose with the highest start value 100 // indicates the block scope. The block scope list is sorted by increasing 101 // start value. 102 // 103 // It is possible to leave a scope nonlocally, for example via a "break" 104 // statement, so there may be short bytecode ranges in a block scope in which we 105 // are popping the block chain in preparation for a goto. These exits are also 106 // nested with respect to outer scopes. The scopes in these exits are indicated 107 // by the "index" field, just like any other block. If a nonlocal exit pops the 108 // last block scope, the index will be NoScopeIndex. 109 // 110 struct ScopeNote { 111 // Sentinel index for no Scope. 112 static constexpr GCThingIndex NoScopeIndex = GCThingIndex::invalid(); 113 114 // Sentinel index for no ScopeNote. 115 static const uint32_t NoScopeNoteIndex = UINT32_MAX; 116 117 // Index of the js::Scope in the script's gcthings array, or NoScopeIndex if 118 // there is no block scope in this range. 119 GCThingIndex index; 120 121 // Bytecode offset at which this scope starts relative to script->code(). 122 uint32_t start = 0; 123 124 // Length of bytecode span this scope covers. 125 uint32_t length = 0; 126 127 // Index of parent block scope in notes, or NoScopeNoteIndex. 128 uint32_t parent = 0; 129 }; 130 131 // Range of characters in scriptSource which contains a script's source, 132 // that is, the range used by the Parser to produce a script. 133 // 134 // For most functions the fields point to the following locations. 135 // 136 // function * foo(a, b) { return a + b; } 137 // ^ ^ ^ 138 // | | | 139 // | sourceStart sourceEnd 140 // | | 141 // toStringStart toStringEnd 142 // 143 // For the special case of class constructors, the spec requires us to use an 144 // alternate definition of toStringStart / toStringEnd. 145 // 146 // class C { constructor() { this.field = 42; } } 147 // ^ ^ ^ ^ 148 // | | | | 149 // | sourceStart sourceEnd | 150 // | | 151 // toStringStart toStringEnd 152 // 153 // Implicit class constructors use the following definitions. 154 // 155 // class C { someMethod() { } } 156 // ^ ^ 157 // | | 158 // sourceStart sourceEnd 159 // | | 160 // toStringStart toStringEnd 161 // 162 // Field initializer lambdas are internal details of the engine, but we still 163 // provide a sensible definition of these values. 164 // 165 // class C { static field = 1 } 166 // class C { field = 1 } 167 // class C { somefield } 168 // ^ ^ 169 // | | 170 // sourceStart sourceEnd 171 // 172 // The non-static private class methods (including getters and setters) ALSO 173 // create a hidden initializer lambda in addition to the method itself. These 174 // lambdas are not exposed directly to script. 175 // 176 // class C { #field() { } } 177 // class C { get #field() { } } 178 // class C { async #field() { } } 179 // class C { * #field() { } } 180 // ^ ^ 181 // | | 182 // sourceStart sourceEnd 183 // 184 // NOTE: These are counted in Code Units from the start of the script source. 185 // 186 // Also included in the SourceExtent is the line and column numbers of the 187 // sourceStart position. Compilation options may specify the initial line and 188 // column number. 189 // 190 // NOTE: Column number may saturate and must not be used as unique identifier. 191 struct SourceExtent { 192 SourceExtent() = default; 193 194 SourceExtent(uint32_t sourceStart, uint32_t sourceEnd, uint32_t toStringStart, 195 uint32_t toStringEnd, uint32_t lineno, 196 JS::LimitedColumnNumberOneOrigin column) 197 : sourceStart(sourceStart), 198 sourceEnd(sourceEnd), 199 toStringStart(toStringStart), 200 toStringEnd(toStringEnd), 201 lineno(lineno), 202 column(column) {} 203 204 static SourceExtent makeGlobalExtent(uint32_t len) { 205 return SourceExtent(0, len, 0, len, 1, JS::LimitedColumnNumberOneOrigin()); 206 } 207 208 static SourceExtent makeGlobalExtent( 209 uint32_t len, uint32_t lineno, JS::LimitedColumnNumberOneOrigin column) { 210 return SourceExtent(0, len, 0, len, lineno, column); 211 } 212 213 // FunctionKey is an encoded position of a function within the source text 214 // that is unique and reproducible. 215 using FunctionKey = uint32_t; 216 static constexpr FunctionKey NullFunctionKey = 0; 217 218 uint32_t sourceStart = 0; 219 uint32_t sourceEnd = 0; 220 uint32_t toStringStart = 0; 221 uint32_t toStringEnd = 0; 222 223 // Line and column of |sourceStart_| position. 224 // Line number (1-origin). 225 uint32_t lineno = 1; 226 // Column number in UTF-16 code units. 227 JS::LimitedColumnNumberOneOrigin column; 228 229 FunctionKey toFunctionKey() const { 230 // In eval("x=>1"), the arrow function will have a sourceStart of 0 which 231 // conflicts with the NullFunctionKey, so shift all keys by 1 instead. 232 auto result = sourceStart + 1; 233 MOZ_ASSERT(result != NullFunctionKey); 234 return result; 235 } 236 }; 237 238 class ImmutableScriptFlags : public EnumFlags<ImmutableScriptFlagsEnum> { 239 public: 240 ImmutableScriptFlags() = default; 241 242 explicit ImmutableScriptFlags(FieldType rawFlags) : EnumFlags(rawFlags) {} 243 244 operator FieldType() const { return flags_; } 245 }; 246 247 class MutableScriptFlags : public EnumFlags<MutableScriptFlagsEnum> { 248 public: 249 MutableScriptFlags() = default; 250 251 MutableScriptFlags& operator&=(const FieldType rhs) { 252 flags_ &= rhs; 253 return *this; 254 } 255 256 MutableScriptFlags& operator|=(const FieldType rhs) { 257 flags_ |= rhs; 258 return *this; 259 } 260 261 operator FieldType() const { return flags_; } 262 }; 263 264 #define GENERIC_FLAGS_READ_ONLY(Field, Enum) \ 265 [[nodiscard]] bool hasFlag(Enum flag) const { return Field.hasFlag(flag); } 266 267 #define GENERIC_FLAGS_READ_WRITE(Field, Enum) \ 268 [[nodiscard]] bool hasFlag(Enum flag) const { return Field.hasFlag(flag); } \ 269 void setFlag(Enum flag, bool b = true) { Field.setFlag(flag, b); } \ 270 void clearFlag(Enum flag) { Field.clearFlag(flag); } 271 272 #define GENERIC_FLAG_GETTER(enumName, lowerName, name) \ 273 bool lowerName() const { return hasFlag(enumName::name); } 274 275 #define GENERIC_FLAG_GETTER_SETTER(enumName, lowerName, name) \ 276 GENERIC_FLAG_GETTER(enumName, lowerName, name) \ 277 void set##name() { setFlag(enumName::name); } \ 278 void set##name(bool b) { setFlag(enumName::name, b); } \ 279 void clear##name() { clearFlag(enumName::name); } 280 281 #define IMMUTABLE_SCRIPT_FLAGS_WITH_ACCESSORS(_) \ 282 _(ImmutableFlags, isForEval, IsForEval) \ 283 _(ImmutableFlags, isModule, IsModule) \ 284 _(ImmutableFlags, isFunction, IsFunction) \ 285 _(ImmutableFlags, selfHosted, SelfHosted) \ 286 _(ImmutableFlags, forceStrict, ForceStrict) \ 287 _(ImmutableFlags, hasNonSyntacticScope, HasNonSyntacticScope) \ 288 _(ImmutableFlags, noScriptRval, NoScriptRval) \ 289 _(ImmutableFlags, treatAsRunOnce, TreatAsRunOnce) \ 290 _(ImmutableFlags, strict, Strict) \ 291 _(ImmutableFlags, hasModuleGoal, HasModuleGoal) \ 292 _(ImmutableFlags, hasInnerFunctions, HasInnerFunctions) \ 293 _(ImmutableFlags, hasDirectEval, HasDirectEval) \ 294 _(ImmutableFlags, bindingsAccessedDynamically, BindingsAccessedDynamically) \ 295 _(ImmutableFlags, hasCallSiteObj, HasCallSiteObj) \ 296 _(ImmutableFlags, isAsync, IsAsync) \ 297 _(ImmutableFlags, isGenerator, IsGenerator) \ 298 _(ImmutableFlags, funHasExtensibleScope, FunHasExtensibleScope) \ 299 _(ImmutableFlags, functionHasThisBinding, FunctionHasThisBinding) \ 300 _(ImmutableFlags, needsHomeObject, NeedsHomeObject) \ 301 _(ImmutableFlags, isDerivedClassConstructor, IsDerivedClassConstructor) \ 302 _(ImmutableFlags, isSyntheticFunction, IsSyntheticFunction) \ 303 _(ImmutableFlags, useMemberInitializers, UseMemberInitializers) \ 304 _(ImmutableFlags, hasRest, HasRest) \ 305 _(ImmutableFlags, needsFunctionEnvironmentObjects, \ 306 NeedsFunctionEnvironmentObjects) \ 307 _(ImmutableFlags, functionHasExtraBodyVarScope, \ 308 FunctionHasExtraBodyVarScope) \ 309 _(ImmutableFlags, shouldDeclareArguments, ShouldDeclareArguments) \ 310 _(ImmutableFlags, needsArgsObj, NeedsArgsObj) \ 311 _(ImmutableFlags, hasMappedArgsObj, HasMappedArgsObj) \ 312 _(ImmutableFlags, isInlinableLargeFunction, IsInlinableLargeFunction) \ 313 _(ImmutableFlags, functionHasNewTargetBinding, FunctionHasNewTargetBinding) \ 314 _(ImmutableFlags, usesArgumentsIntrinsics, UsesArgumentsIntrinsics) \ 315 \ 316 GeneratorKind generatorKind() const { \ 317 return isGenerator() ? GeneratorKind::Generator \ 318 : GeneratorKind::NotGenerator; \ 319 } \ 320 \ 321 FunctionAsyncKind asyncKind() const { \ 322 return isAsync() ? FunctionAsyncKind::AsyncFunction \ 323 : FunctionAsyncKind::SyncFunction; \ 324 } \ 325 \ 326 bool isRelazifiable() const { \ 327 /* \ 328 ** A script may not be relazifiable if parts of it can be entrained in \ 329 ** interesting ways: \ 330 ** - Scripts with inner-functions or direct-eval (which can add \ 331 ** inner-functions) should not be relazified as their Scopes may be \ 332 ** part of another scope-chain. \ 333 ** - Generators and async functions may be re-entered in complex ways so \ 334 ** don't discard bytecode. The JIT resume code assumes this. \ 335 ** - Functions with template literals must always return the same object \ 336 ** instance so must not discard it by relazifying. \ 337 */ \ 338 return !hasInnerFunctions() && !hasDirectEval() && !isGenerator() && \ 339 !isAsync() && !hasCallSiteObj(); \ 340 } 341 342 #define RO_IMMUTABLE_SCRIPT_FLAGS(Field) \ 343 using ImmutableFlags = ImmutableScriptFlagsEnum; \ 344 \ 345 GENERIC_FLAGS_READ_ONLY(Field, ImmutableFlags) \ 346 IMMUTABLE_SCRIPT_FLAGS_WITH_ACCESSORS(GENERIC_FLAG_GETTER) 347 348 #define MUTABLE_SCRIPT_FLAGS_WITH_ACCESSORS(_) \ 349 _(MutableFlags, hasRunOnce, HasRunOnce) \ 350 _(MutableFlags, hasScriptCounts, HasScriptCounts) \ 351 _(MutableFlags, hasDebugScript, HasDebugScript) \ 352 _(MutableFlags, allowRelazify, AllowRelazify) \ 353 _(MutableFlags, spewEnabled, SpewEnabled) \ 354 _(MutableFlags, needsFinalWarmUpCount, NeedsFinalWarmUpCount) \ 355 _(MutableFlags, failedBoundsCheck, FailedBoundsCheck) \ 356 _(MutableFlags, hadLICMInvalidation, HadLICMInvalidation) \ 357 _(MutableFlags, hadReorderingBailout, HadReorderingBailout) \ 358 _(MutableFlags, hadEagerTruncationBailout, HadEagerTruncationBailout) \ 359 _(MutableFlags, hadUnboxFoldingBailout, HadUnboxFoldingBailout) \ 360 _(MutableFlags, baselineDisabled, BaselineDisabled) \ 361 _(MutableFlags, ionDisabled, IonDisabled) \ 362 _(MutableFlags, uninlineable, Uninlineable) \ 363 _(MutableFlags, noEagerBaselineHint, NoEagerBaselineHint) \ 364 _(MutableFlags, failedLexicalCheck, FailedLexicalCheck) \ 365 _(MutableFlags, hadSpeculativePhiBailout, HadSpeculativePhiBailout) 366 367 #define RW_MUTABLE_SCRIPT_FLAGS(Field) \ 368 using MutableFlags = MutableScriptFlagsEnum; \ 369 \ 370 GENERIC_FLAGS_READ_WRITE(Field, MutableFlags) \ 371 MUTABLE_SCRIPT_FLAGS_WITH_ACCESSORS(GENERIC_FLAG_GETTER_SETTER) 372 373 // [SMDOC] JSScript data layout (immutable) 374 // 375 // ImmutableScriptData stores variable-length script data that may be shared 376 // between scripts with the same bytecode, even across different GC Zones. 377 // Abstractly this structure consists of multiple (optional) arrays that are 378 // exposed as mozilla::Span<T>. These arrays exist in a single heap allocation. 379 // 380 // Under the hood, ImmutableScriptData is a fixed-size header class followed 381 // the various array bodies interleaved with metadata to compactly encode the 382 // bounds. These arrays have varying requirements for alignment, performance, 383 // and jit-friendliness which leads to the complex indexing system below. 384 // 385 // Note: The '----' separators are for readability only. 386 // 387 // ---- 388 // <ImmutableScriptData itself> 389 // ---- 390 // (REQUIRED) Flags structure 391 // (REQUIRED) Array of jsbytecode constituting code() 392 // (REQUIRED) Array of SrcNote constituting notes() 393 // ---- 394 // (OPTIONAL) Array of uint32_t optional-offsets 395 // optArrayOffset: 396 // ---- 397 // L0: 398 // (OPTIONAL) Array of uint32_t constituting resumeOffsets() 399 // L1: 400 // (OPTIONAL) Array of ScopeNote constituting scopeNotes() 401 // L2: 402 // (OPTIONAL) Array of TryNote constituting tryNotes() 403 // L3: 404 // ---- 405 // 406 // NOTE: The notes() array must have been padded such that 407 // flags/code/notes together have uint32_t alignment. 408 // 409 // The labels shown are recorded as byte-offsets relative to 'this'. This is to 410 // reduce memory as well as make ImmutableScriptData easier to share across 411 // processes. 412 // 413 // The L0/L1/L2/L3 labels indicate the start and end of the optional arrays. 414 // Some of these labels may refer to the same location if the array between 415 // them is empty. Each unique label position has an offset stored in the 416 // optional-offsets table. Note that we also avoid entries for labels that 417 // match 'optArrayOffset'. This saves memory when arrays are empty. 418 // 419 // The flags() data indicates (for each optional array) which entry from the 420 // optional-offsets table marks the *end* of array. The array starts where the 421 // previous array ends (with the first array beginning at 'optArrayOffset'). 422 // The optional-offset table is addressed at negative indices from 423 // 'optArrayOffset'. 424 // 425 // In general, the length of each array is computed from subtracting the start 426 // offset of the array from the start offset of the subsequent array. The 427 // notable exception is that bytecode length is stored explicitly. 428 class alignas(uint32_t) ImmutableScriptData final 429 : public TrailingArray<ImmutableScriptData> { 430 private: 431 Offset optArrayOffset_ = 0; 432 433 // Length of bytecode 434 uint32_t codeLength_ = 0; 435 436 public: 437 // Offset of main entry point from code, after predef'ing prologue. 438 uint32_t mainOffset = 0; 439 440 // Fixed frame slots. 441 uint32_t nfixed = 0; 442 443 // Slots plus maximum stack depth. 444 uint32_t nslots = 0; 445 446 // Index into the gcthings array of the body scope. 447 GCThingIndex bodyScopeIndex; 448 449 // Number of IC entries to allocate in JitScript for Baseline ICs. 450 uint32_t numICEntries = 0; 451 452 // ES6 function length. 453 uint16_t funLength = 0; 454 455 // Property Count estimate 456 uint16_t propertyCountEstimate = 0; 457 458 // NOTE: The raw bytes of this structure are used for hashing so use explicit 459 // padding values as needed for predicatable results across compilers 460 461 private: 462 struct Flags { 463 uint8_t resumeOffsetsEndIndex : 2; 464 uint8_t scopeNotesEndIndex : 2; 465 uint8_t tryNotesEndIndex : 2; 466 uint8_t _unused : 2; 467 }; 468 static_assert(sizeof(Flags) == sizeof(uint8_t), 469 "Structure packing is broken"); 470 471 // Offsets (in bytes) from 'this' to each component array. The delta between 472 // each offset and the next offset is the size of each array and is defined 473 // even if an array is empty. 474 Offset flagOffset() const { return offsetOfCode() - sizeof(Flags); } 475 Offset codeOffset() const { return offsetOfCode(); } 476 Offset noteOffset() const { return offsetOfCode() + codeLength_; } 477 Offset optionalOffsetsOffset() const { 478 // Determine the location to beginning of optional-offsets array by looking 479 // at index for try-notes. 480 // 481 // optionalOffsetsOffset(): 482 // (OPTIONAL) tryNotesEndOffset 483 // (OPTIONAL) scopeNotesEndOffset 484 // (OPTIONAL) resumeOffsetsEndOffset 485 // optArrayOffset_: 486 // .... 487 unsigned numOffsets = flags().tryNotesEndIndex; 488 MOZ_ASSERT(numOffsets >= flags().scopeNotesEndIndex); 489 MOZ_ASSERT(numOffsets >= flags().resumeOffsetsEndIndex); 490 491 return optArrayOffset_ - (numOffsets * sizeof(Offset)); 492 } 493 Offset resumeOffsetsOffset() const { return optArrayOffset_; } 494 Offset scopeNotesOffset() const { 495 return getOptionalOffset(flags().resumeOffsetsEndIndex); 496 } 497 Offset tryNotesOffset() const { 498 return getOptionalOffset(flags().scopeNotesEndIndex); 499 } 500 Offset endOffset() const { 501 return getOptionalOffset(flags().tryNotesEndIndex); 502 } 503 504 void initOptionalArrays(Offset* cursor, uint32_t numResumeOffsets, 505 uint32_t numScopeNotes, uint32_t numTryNotes); 506 507 // Initialize to GC-safe state 508 ImmutableScriptData(uint32_t codeLength, uint32_t noteLength, 509 uint32_t numResumeOffsets, uint32_t numScopeNotes, 510 uint32_t numTryNotes); 511 512 void setOptionalOffset(int index, Offset offset) { 513 MOZ_ASSERT(index > 0); 514 MOZ_ASSERT(offset != optArrayOffset_, "Do not store implicit offset"); 515 offsetToPointer<Offset>(optArrayOffset_)[-index] = offset; 516 } 517 Offset getOptionalOffset(int index) const { 518 // The index 0 represents (implicitly) the offset 'optArrayOffset_'. 519 if (index == 0) { 520 return optArrayOffset_; 521 } 522 523 ImmutableScriptData* this_ = const_cast<ImmutableScriptData*>(this); 524 return this_->offsetToPointer<Offset>(optArrayOffset_)[-index]; 525 } 526 527 public: 528 static js::UniquePtr<ImmutableScriptData> new_( 529 FrontendContext* fc, uint32_t mainOffset, uint32_t nfixed, 530 uint32_t nslots, GCThingIndex bodyScopeIndex, uint32_t numICEntries, 531 bool isFunction, uint16_t funLength, uint16_t propertyCountEstimate, 532 mozilla::Span<const jsbytecode> code, mozilla::Span<const SrcNote> notes, 533 mozilla::Span<const uint32_t> resumeOffsets, 534 mozilla::Span<const ScopeNote> scopeNotes, 535 mozilla::Span<const TryNote> tryNotes); 536 537 static js::UniquePtr<ImmutableScriptData> new_( 538 FrontendContext* fc, uint32_t codeLength, uint32_t noteLength, 539 uint32_t numResumeOffsets, uint32_t numScopeNotes, uint32_t numTryNotes); 540 541 static js::UniquePtr<ImmutableScriptData> new_(FrontendContext* fc, 542 uint32_t totalSize); 543 544 // Validate internal offsets of the data structure seems reasonable. This is 545 // for diagnositic purposes only to detect severe corruption. This is not a 546 // security boundary! 547 bool validateLayout(uint32_t expectedSize); 548 549 private: 550 static mozilla::CheckedInt<uint32_t> sizeFor(uint32_t codeLength, 551 uint32_t noteLength, 552 uint32_t numResumeOffsets, 553 uint32_t numScopeNotes, 554 uint32_t numTryNotes); 555 556 public: 557 // The code() and note() arrays together maintain an target alignment by 558 // padding the source notes with padding bytes. This allows arrays with 559 // stricter alignment requirements to follow them. 560 static constexpr size_t CodeNoteAlign = sizeof(uint32_t); 561 562 // Compute number of padding notes to pad out source notes with. 563 static uint32_t ComputeNotePadding(uint32_t codeLength, uint32_t noteLength) { 564 uint32_t flagLength = sizeof(Flags); 565 uint32_t paddingLength = 566 CodeNoteAlign - (flagLength + codeLength + noteLength) % CodeNoteAlign; 567 568 if (paddingLength == CodeNoteAlign) { 569 return 0; 570 } 571 572 return paddingLength; 573 } 574 575 // Span over all raw bytes in this struct and its trailing arrays. 576 mozilla::Span<const uint8_t> immutableData() const { 577 size_t allocSize = endOffset(); 578 return mozilla::Span{reinterpret_cast<const uint8_t*>(this), allocSize}; 579 } 580 581 private: 582 Flags& flagsRef() { return *offsetToPointer<Flags>(flagOffset()); } 583 const Flags& flags() const { 584 return const_cast<ImmutableScriptData*>(this)->flagsRef(); 585 } 586 587 public: 588 uint32_t codeLength() const { return codeLength_; } 589 jsbytecode* code() { return offsetToPointer<jsbytecode>(codeOffset()); } 590 mozilla::Span<jsbytecode> codeSpan() { return {code(), codeLength()}; } 591 592 uint32_t noteLength() const { 593 return numElements<SrcNote>(noteOffset(), optionalOffsetsOffset()); 594 } 595 SrcNote* notes() { return offsetToPointer<SrcNote>(noteOffset()); } 596 mozilla::Span<SrcNote> notesSpan() { return {notes(), noteLength()}; } 597 598 mozilla::Span<uint32_t> resumeOffsets() { 599 return mozilla::Span{offsetToPointer<uint32_t>(resumeOffsetsOffset()), 600 offsetToPointer<uint32_t>(scopeNotesOffset())}; 601 } 602 mozilla::Span<ScopeNote> scopeNotes() { 603 return mozilla::Span{offsetToPointer<ScopeNote>(scopeNotesOffset()), 604 offsetToPointer<ScopeNote>(tryNotesOffset())}; 605 } 606 mozilla::Span<TryNote> tryNotes() { 607 return mozilla::Span{offsetToPointer<TryNote>(tryNotesOffset()), 608 offsetToPointer<TryNote>(endOffset())}; 609 } 610 611 // Expose offsets to the JITs. 612 static constexpr size_t offsetOfCode() { 613 return sizeof(ImmutableScriptData) + sizeof(Flags); 614 } 615 static constexpr size_t offsetOfResumeOffsetsOffset() { 616 // Resume-offsets are the first optional array if they exist. Locate the 617 // array with the 'optArrayOffset_' field. 618 static_assert(sizeof(Offset) == sizeof(uint32_t), 619 "JIT expect Offset to be uint32_t"); 620 return offsetof(ImmutableScriptData, optArrayOffset_); 621 } 622 static constexpr size_t offsetOfNfixed() { 623 return offsetof(ImmutableScriptData, nfixed); 624 } 625 static constexpr size_t offsetOfNslots() { 626 return offsetof(ImmutableScriptData, nslots); 627 } 628 static constexpr size_t offsetOfFunLength() { 629 return offsetof(ImmutableScriptData, funLength); 630 } 631 632 // ImmutableScriptData has trailing data so isn't copyable or movable. 633 ImmutableScriptData(const ImmutableScriptData&) = delete; 634 ImmutableScriptData& operator=(const ImmutableScriptData&) = delete; 635 }; 636 637 // Wrapper type for ImmutableScriptData to allow sharing across a JSRuntime. 638 // 639 // Note: This is distinct from ImmutableScriptData because it contains a mutable 640 // ref-count while the ImmutableScriptData may live in read-only memory. 641 // 642 // Note: This is *not* directly inlined into the SharedImmutableScriptDataTable 643 // because scripts point directly to object and table resizing moves 644 // entries. This allows for fast finalization by decrementing the 645 // ref-count directly without doing a hash-table lookup. 646 class SharedImmutableScriptData { 647 static constexpr uint32_t IsExternalFlag = 0x80000000; 648 static constexpr uint32_t RefCountBits = 0x7FFFFFFF; 649 650 // This class is reference counted as follows: each pointer from a JSScript 651 // counts as one reference plus there may be one reference from the shared 652 // script data table. 653 mozilla::Atomic<uint32_t, mozilla::SequentiallyConsistent> 654 refCountAndExternalFlags_ = {}; 655 656 mozilla::HashNumber hash_; 657 ImmutableScriptData* isd_ = nullptr; 658 659 // End of fields. 660 661 friend class ::JSScript; 662 friend class js::frontend::StencilXDR; 663 664 public: 665 SharedImmutableScriptData() = default; 666 667 ~SharedImmutableScriptData() { reset(); } 668 669 private: 670 bool isExternal() const { return refCountAndExternalFlags_ & IsExternalFlag; } 671 void setIsExternal() { refCountAndExternalFlags_ |= IsExternalFlag; } 672 void unsetIsExternal() { refCountAndExternalFlags_ &= RefCountBits; } 673 674 void reset() { 675 if (isd_ && !isExternal()) { 676 js_delete(isd_); 677 } 678 isd_ = nullptr; 679 } 680 681 mozilla::HashNumber calculateHash() const { 682 mozilla::Span<const uint8_t> immutableData = isd_->immutableData(); 683 return mozilla::HashBytes(immutableData.data(), immutableData.size()); 684 } 685 686 public: 687 // Hash over the contents of SharedImmutableScriptData and its 688 // ImmutableScriptData. 689 struct Hasher; 690 691 uint32_t refCount() const { return refCountAndExternalFlags_ & RefCountBits; } 692 void AddRef() { refCountAndExternalFlags_++; } 693 694 private: 695 uint32_t decrementRef() { 696 MOZ_ASSERT(refCount() != 0); 697 return --refCountAndExternalFlags_ & RefCountBits; 698 } 699 700 public: 701 void Release() { 702 uint32_t remain = decrementRef(); 703 if (remain == 0) { 704 reset(); 705 js_free(this); 706 } 707 } 708 709 static constexpr size_t offsetOfISD() { 710 return offsetof(SharedImmutableScriptData, isd_); 711 } 712 713 private: 714 static SharedImmutableScriptData* create(FrontendContext* fc); 715 716 public: 717 static SharedImmutableScriptData* createWith( 718 FrontendContext* fc, js::UniquePtr<ImmutableScriptData>&& isd); 719 720 size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) { 721 size_t isdSize = isExternal() ? 0 : mallocSizeOf(isd_); 722 return mallocSizeOf(this) + isdSize; 723 } 724 725 // SharedImmutableScriptData has trailing data so isn't copyable or movable. 726 SharedImmutableScriptData(const SharedImmutableScriptData&) = delete; 727 SharedImmutableScriptData& operator=(const SharedImmutableScriptData&) = 728 delete; 729 730 static bool shareScriptData(FrontendContext* fc, 731 RefPtr<SharedImmutableScriptData>& sisd); 732 733 size_t immutableDataLength() const { return isd_->immutableData().Length(); } 734 uint32_t nfixed() const { return isd_->nfixed; } 735 736 ImmutableScriptData* get() { return isd_; } 737 mozilla::HashNumber hash() const { return hash_; } 738 739 void setOwn(js::UniquePtr<ImmutableScriptData>&& isd) { 740 MOZ_ASSERT(!isd_); 741 isd_ = isd.release(); 742 unsetIsExternal(); 743 744 hash_ = calculateHash(); 745 } 746 747 void setOwn(js::UniquePtr<ImmutableScriptData>&& isd, 748 mozilla::HashNumber hash) { 749 MOZ_ASSERT(!isd_); 750 isd_ = isd.release(); 751 unsetIsExternal(); 752 753 MOZ_ASSERT(hash == calculateHash()); 754 hash_ = hash; 755 } 756 757 void setExternal(ImmutableScriptData* isd) { 758 MOZ_ASSERT(!isd_); 759 isd_ = isd; 760 setIsExternal(); 761 762 hash_ = calculateHash(); 763 } 764 765 void setExternal(ImmutableScriptData* isd, mozilla::HashNumber hash) { 766 MOZ_ASSERT(!isd_); 767 isd_ = isd; 768 setIsExternal(); 769 770 MOZ_ASSERT(hash == calculateHash()); 771 hash_ = hash; 772 } 773 }; 774 775 // Matches SharedImmutableScriptData objects that have the same atoms as well as 776 // contain the same bytes in their ImmutableScriptData. 777 struct SharedImmutableScriptData::Hasher { 778 using Lookup = RefPtr<SharedImmutableScriptData>; 779 780 static mozilla::HashNumber hash(const Lookup& l) { return l->hash(); } 781 782 static bool match(SharedImmutableScriptData* entry, const Lookup& lookup) { 783 return (entry->isd_->immutableData() == lookup->isd_->immutableData()); 784 } 785 }; 786 787 using SharedImmutableScriptDataTable = 788 mozilla::HashSet<SharedImmutableScriptData*, 789 SharedImmutableScriptData::Hasher, SystemAllocPolicy>; 790 791 struct MemberInitializers { 792 #ifdef ENABLE_DECORATORS 793 static constexpr size_t NumBits = 30; 794 #else 795 static constexpr size_t NumBits = 31; 796 #endif 797 static constexpr uint32_t MaxInitializers = BitMask(NumBits); 798 799 #ifdef DEBUG 800 bool valid = false; 801 #endif 802 803 bool hasPrivateBrand : 1; 804 805 #ifdef ENABLE_DECORATORS 806 bool hasDecorators : 1; 807 #endif 808 809 // This struct will eventually have a vector of constant values for optimizing 810 // field initializers. 811 uint32_t numMemberInitializers : NumBits; 812 813 MemberInitializers(bool hasPrivateBrand, 814 #ifdef ENABLE_DECORATORS 815 bool hasDecorators, 816 #endif 817 uint32_t numMemberInitializers) 818 : 819 #ifdef DEBUG 820 valid(true), 821 #endif 822 hasPrivateBrand(hasPrivateBrand), 823 #ifdef ENABLE_DECORATORS 824 hasDecorators(hasDecorators), 825 #endif 826 numMemberInitializers(numMemberInitializers) { 827 #ifdef ENABLE_DECORATORS 828 MOZ_ASSERT( 829 this->numMemberInitializers == numMemberInitializers, 830 "numMemberInitializers should easily fit in the 30-bit bitfield"); 831 #else 832 MOZ_ASSERT( 833 this->numMemberInitializers == numMemberInitializers, 834 "numMemberInitializers should easily fit in the 31-bit bitfield"); 835 #endif 836 } 837 838 static MemberInitializers Invalid() { return MemberInitializers(); } 839 840 // Singleton to use for class constructors that do not have to initialize any 841 // fields. This is used when we elide the trivial data but still need a valid 842 // set to stop scope walking. 843 static const MemberInitializers& Empty() { 844 static const MemberInitializers zeroInitializers(false, 845 #ifdef ENABLE_DECORATORS 846 false, 847 #endif 848 0); 849 return zeroInitializers; 850 } 851 852 uint32_t serialize() const { 853 #ifdef ENABLE_DECORATORS 854 auto serialised = (hasPrivateBrand << (NumBits + 1)) | 855 hasDecorators << NumBits | numMemberInitializers; 856 return serialised; 857 #else 858 return (hasPrivateBrand << NumBits) | numMemberInitializers; 859 #endif 860 } 861 862 static MemberInitializers deserialize(uint32_t bits) { 863 #ifdef ENABLE_DECORATORS 864 return MemberInitializers((bits & Bit(NumBits + 1)) != 0, 865 (bits & Bit(NumBits)) != 0, 866 bits & BitMask(NumBits)); 867 #else 868 return MemberInitializers((bits & Bit(NumBits)) != 0, 869 bits & BitMask(NumBits)); 870 #endif 871 } 872 873 private: 874 MemberInitializers() 875 : 876 #ifdef DEBUG 877 valid(false), 878 #endif 879 hasPrivateBrand(false), 880 #ifdef ENABLE_DECORATORS 881 hasDecorators(false), 882 #endif 883 numMemberInitializers(0) { 884 } 885 }; 886 887 // See JSOp::Lambda for interepretation of this index. 888 using FunctionDeclaration = GCThingIndex; 889 // Defined here to avoid #include cycle with Stencil.h. 890 using FunctionDeclarationVector = 891 Vector<FunctionDeclaration, 0, js::SystemAllocPolicy>; 892 893 } // namespace js 894 895 #endif /* vm_SharedStencil_h */