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/. */
8 * JS script operations.
11 #include "vm/JSScript-inl.h"
13 #include "mozilla/ArrayUtils.h"
14 #include "mozilla/CheckedInt.h"
15 #include "mozilla/DebugOnly.h"
16 #include "mozilla/Maybe.h"
17 #include "mozilla/MemoryReporting.h"
18 #include "mozilla/PodOperations.h"
19 #include "mozilla/ScopeExit.h"
20 #include "mozilla/Span.h" // mozilla::{Span,Span}
21 #include "mozilla/Sprintf.h"
22 #include "mozilla/Utf8.h"
23 #include "mozilla/Vector.h"
28 #include <type_traits>
33 #include "frontend/BytecodeSection.h"
34 #include "frontend/CompilationStencil.h" // frontend::CompilationStencil
35 #include "frontend/FrontendContext.h" // AutoReportFrontendContext
36 #include "frontend/ParseContext.h"
37 #include "frontend/SourceNotes.h" // SrcNote, SrcNoteType, SrcNoteIterator
38 #include "frontend/Stencil.h" // DumpFunctionFlagsItems, DumpImmutableScriptFlags
39 #include "frontend/StencilXdr.h" // XDRStencilEncoder
40 #include "gc/GCContext.h"
41 #include "jit/BaselineJIT.h"
42 #include "jit/CacheIRHealth.h"
44 #include "jit/IonScript.h"
45 #include "jit/JitCode.h"
46 #include "jit/JitOptions.h"
47 #include "jit/JitRuntime.h"
48 #include "js/CharacterEncoding.h" // JS_EncodeStringToUTF8
49 #include "js/ColumnNumber.h" // JS::LimitedColumnNumberOneOrigin, JS::ColumnNumberOneOrigin, JS::ColumnNumberOffset
50 #include "js/CompileOptions.h"
51 #include "js/experimental/SourceHook.h"
52 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
53 #include "js/HeapAPI.h" // JS::GCCellPtr
54 #include "js/MemoryMetrics.h"
55 #include "js/Printer.h" // js::GenericPrinter, js::Fprinter, js::Sprinter, js::QuoteString
56 #include "js/Transcoding.h"
57 #include "js/UniquePtr.h"
58 #include "js/Utility.h" // JS::UniqueChars
59 #include "js/Value.h" // JS::Value
60 #include "util/Poison.h"
61 #include "util/StringBuffer.h"
62 #include "util/Text.h"
63 #include "vm/BigIntType.h" // JS::BigInt
64 #include "vm/BytecodeIterator.h"
65 #include "vm/BytecodeLocation.h"
66 #include "vm/BytecodeUtil.h" // Disassemble
67 #include "vm/Compression.h"
68 #include "vm/HelperThreadState.h" // js::RunPendingSourceCompressions
69 #include "vm/JSFunction.h"
70 #include "vm/JSObject.h"
71 #include "vm/JSONPrinter.h" // JSONPrinter
72 #include "vm/Opcodes.h"
73 #include "vm/PortableBaselineInterpret.h"
74 #include "vm/Scope.h" // Scope
75 #include "vm/SharedImmutableStringsCache.h"
76 #include "vm/StencilEnums.h" // TryNote, TryNoteKind, ScopeNote
77 #include "vm/StringType.h" // JSString, JSAtom
78 #include "vm/Time.h" // AutoIncrementalTimer
79 #include "vm/ToSource.h" // JS::ValueToSource
81 # include "vtune/VTuneWrapper.h"
84 #include "gc/Marking-inl.h"
85 #include "vm/BytecodeIterator-inl.h"
86 #include "vm/BytecodeLocation-inl.h"
87 #include "vm/Compartment-inl.h"
88 #include "vm/JSContext-inl.h"
89 #include "vm/JSObject-inl.h"
90 #include "vm/SharedImmutableStringsCache-inl.h"
91 #include "vm/Stack-inl.h"
95 using mozilla::CheckedInt
;
97 using mozilla::PodCopy
;
98 using mozilla::PointerRangeSize
;
99 using mozilla::Utf8AsUnsignedChars
;
100 using mozilla::Utf8Unit
;
102 using JS::CompileOptions
;
103 using JS::ReadOnlyCompileOptions
;
104 using JS::SourceText
;
106 bool js::BaseScript::isUsingInterpreterTrampoline(JSRuntime
* rt
) const {
107 return jitCodeRaw() == rt
->jitRuntime()->interpreterStub().value
;
110 js::ScriptSource
* js::BaseScript::maybeForwardedScriptSource() const {
111 return MaybeForwarded(sourceObject())->source();
114 void js::BaseScript::setEnclosingScript(BaseScript
* enclosingScript
) {
115 MOZ_ASSERT(enclosingScript
);
116 warmUpData_
.initEnclosingScript(enclosingScript
);
119 void js::BaseScript::setEnclosingScope(Scope
* enclosingScope
) {
120 if (warmUpData_
.isEnclosingScript()) {
121 warmUpData_
.clearEnclosingScript();
124 MOZ_ASSERT(enclosingScope
);
125 warmUpData_
.initEnclosingScope(enclosingScope
);
128 void js::BaseScript::finalize(JS::GCContext
* gcx
) {
129 // Scripts with bytecode may have optional data stored in per-runtime or
130 // per-zone maps. Note that a failed compilation must not have entries since
131 // the script itself will not be marked as having bytecode.
133 JSScript
* script
= this->asJSScript();
135 if (coverage::IsLCovEnabled()) {
136 coverage::CollectScriptCoverage(script
, true);
139 script
->destroyScriptCounts();
143 JSRuntime
* rt
= gcx
->runtime();
144 if (rt
->hasJitRuntime() && rt
->jitRuntime()->hasInterpreterEntryMap()) {
145 rt
->jitRuntime()->getInterpreterEntryMap()->remove(this);
148 rt
->geckoProfiler().onScriptFinalized(this);
152 if (zone()->scriptVTuneIdMap
) {
153 // Note: we should only get here if the VTune JIT profiler is running.
154 zone()->scriptVTuneIdMap
->remove(this);
158 if (warmUpData_
.isJitScript()) {
159 JSScript
* script
= this->asJSScript();
160 #ifdef JS_CACHEIR_SPEW
161 maybeUpdateWarmUpCount(script
);
163 script
->releaseJitScriptOnFinalize(gcx
);
166 #ifdef JS_CACHEIR_SPEW
168 maybeSpewScriptFinalWarmUpCount(this->asJSScript());
173 // We don't need to triger any barriers here, just free the memory.
174 size_t size
= data_
->allocationSize();
175 AlwaysPoison(data_
, JS_POISONED_JSSCRIPT_DATA_PATTERN
, size
,
176 MemCheckKind::MakeNoAccess
);
177 gcx
->free_(this, data_
, size
, MemoryUse::ScriptPrivateData
);
183 js::Scope
* js::BaseScript::releaseEnclosingScope() {
184 Scope
* enclosing
= warmUpData_
.toEnclosingScope();
185 warmUpData_
.clearEnclosingScope();
189 void js::BaseScript::swapData(UniquePtr
<PrivateScriptData
>& other
) {
191 RemoveCellMemory(this, data_
->allocationSize(),
192 MemoryUse::ScriptPrivateData
);
195 PrivateScriptData
* old
= data_
;
196 data_
.set(zone(), other
.release());
200 AddCellMemory(this, data_
->allocationSize(), MemoryUse::ScriptPrivateData
);
204 js::Scope
* js::BaseScript::enclosingScope() const {
205 MOZ_ASSERT(!warmUpData_
.isEnclosingScript(),
206 "Enclosing scope is not computed yet");
208 if (warmUpData_
.isEnclosingScope()) {
209 return warmUpData_
.toEnclosingScope();
212 MOZ_ASSERT(data_
, "Script doesn't seem to be compiled");
214 return gcthings()[js::GCThingIndex::outermostScopeIndex()]
219 size_t JSScript::numAlwaysLiveFixedSlots() const {
220 if (bodyScope()->is
<js::FunctionScope
>()) {
221 return bodyScope()->as
<js::FunctionScope
>().nextFrameSlot();
223 if (bodyScope()->is
<js::ModuleScope
>()) {
224 return bodyScope()->as
<js::ModuleScope
>().nextFrameSlot();
226 if (bodyScope()->is
<js::EvalScope
>() &&
227 bodyScope()->kind() == ScopeKind::StrictEval
) {
228 return bodyScope()->as
<js::EvalScope
>().nextFrameSlot();
233 unsigned JSScript::numArgs() const {
234 if (bodyScope()->is
<js::FunctionScope
>()) {
235 return bodyScope()->as
<js::FunctionScope
>().numPositionalFormalParameters();
240 bool JSScript::functionHasParameterExprs() const {
241 // Only functions have parameters.
242 js::Scope
* scope
= bodyScope();
243 if (!scope
->is
<js::FunctionScope
>()) {
246 return scope
->as
<js::FunctionScope
>().hasParameterExprs();
249 bool JSScript::isModule() const { return bodyScope()->is
<js::ModuleScope
>(); }
251 js::ModuleObject
* JSScript::module() const {
252 MOZ_ASSERT(isModule());
253 return bodyScope()->as
<js::ModuleScope
>().module();
256 bool JSScript::isGlobalCode() const {
257 return bodyScope()->is
<js::GlobalScope
>();
260 js::VarScope
* JSScript::functionExtraBodyVarScope() const {
261 MOZ_ASSERT(functionHasExtraBodyVarScope());
262 for (JS::GCCellPtr gcThing
: gcthings()) {
263 if (!gcThing
.is
<js::Scope
>()) {
266 js::Scope
* scope
= &gcThing
.as
<js::Scope
>();
267 if (scope
->kind() == js::ScopeKind::FunctionBodyVar
) {
268 return &scope
->as
<js::VarScope
>();
271 MOZ_CRASH("Function extra body var scope not found");
274 bool JSScript::needsBodyEnvironment() const {
275 for (JS::GCCellPtr gcThing
: gcthings()) {
276 if (!gcThing
.is
<js::Scope
>()) {
279 js::Scope
* scope
= &gcThing
.as
<js::Scope
>();
280 if (ScopeKindIsInBody(scope
->kind()) && scope
->hasEnvironment()) {
287 bool JSScript::isDirectEvalInFunction() const {
291 return bodyScope()->hasOnChain(js::ScopeKind::Function
);
294 // Initialize the optional arrays in the trailing allocation. This is a set of
295 // offsets that delimit each optional array followed by the arrays themselves.
296 // See comment before 'ImmutableScriptData' for more details.
297 void ImmutableScriptData::initOptionalArrays(Offset
* pcursor
,
298 uint32_t numResumeOffsets
,
299 uint32_t numScopeNotes
,
300 uint32_t numTryNotes
) {
301 Offset cursor
= (*pcursor
);
303 // The byte arrays must have already been padded.
304 MOZ_ASSERT(isAlignedOffset
<CodeNoteAlign
>(cursor
),
305 "Bytecode and source notes should be padded to keep alignment");
307 // Each non-empty optional array needs will need an offset to its end.
308 unsigned numOptionalArrays
= unsigned(numResumeOffsets
> 0) +
309 unsigned(numScopeNotes
> 0) +
310 unsigned(numTryNotes
> 0);
312 // Default-initialize the optional-offsets.
313 initElements
<Offset
>(cursor
, numOptionalArrays
);
314 cursor
+= numOptionalArrays
* sizeof(Offset
);
316 // Offset between optional-offsets table and the optional arrays. This is
317 // later used to access the optional-offsets table as well as first optional
319 optArrayOffset_
= cursor
;
321 // Each optional array that follows must store an end-offset in the offset
322 // table. Assign table entries by using this 'offsetIndex'. The index 0 is
323 // reserved for implicit value 'optArrayOffset'.
326 // Default-initialize optional 'resumeOffsets'.
327 MOZ_ASSERT(resumeOffsetsOffset() == cursor
);
328 if (numResumeOffsets
> 0) {
329 initElements
<uint32_t>(cursor
, numResumeOffsets
);
330 cursor
+= numResumeOffsets
* sizeof(uint32_t);
331 setOptionalOffset(++offsetIndex
, cursor
);
333 flagsRef().resumeOffsetsEndIndex
= offsetIndex
;
335 // Default-initialize optional 'scopeNotes'.
336 MOZ_ASSERT(scopeNotesOffset() == cursor
);
337 if (numScopeNotes
> 0) {
338 initElements
<ScopeNote
>(cursor
, numScopeNotes
);
339 cursor
+= numScopeNotes
* sizeof(ScopeNote
);
340 setOptionalOffset(++offsetIndex
, cursor
);
342 flagsRef().scopeNotesEndIndex
= offsetIndex
;
344 // Default-initialize optional 'tryNotes'
345 MOZ_ASSERT(tryNotesOffset() == cursor
);
346 if (numTryNotes
> 0) {
347 initElements
<TryNote
>(cursor
, numTryNotes
);
348 cursor
+= numTryNotes
* sizeof(TryNote
);
349 setOptionalOffset(++offsetIndex
, cursor
);
351 flagsRef().tryNotesEndIndex
= offsetIndex
;
353 MOZ_ASSERT(endOffset() == cursor
);
357 ImmutableScriptData::ImmutableScriptData(uint32_t codeLength
,
359 uint32_t numResumeOffsets
,
360 uint32_t numScopeNotes
,
361 uint32_t numTryNotes
)
362 : codeLength_(codeLength
) {
363 // Variable-length data begins immediately after ImmutableScriptData itself.
364 Offset cursor
= sizeof(ImmutableScriptData
);
366 // The following arrays are byte-aligned with additional padding to ensure
367 // that together they maintain uint32_t-alignment.
369 MOZ_ASSERT(isAlignedOffset
<CodeNoteAlign
>(cursor
));
371 // Zero-initialize 'flags'
372 MOZ_ASSERT(isAlignedOffset
<Flags
>(cursor
));
373 new (offsetToPointer
<void>(cursor
)) Flags
{};
374 cursor
+= sizeof(Flags
);
376 initElements
<jsbytecode
>(cursor
, codeLength
);
377 cursor
+= codeLength
* sizeof(jsbytecode
);
379 initElements
<SrcNote
>(cursor
, noteLength
);
380 cursor
+= noteLength
* sizeof(SrcNote
);
382 MOZ_ASSERT(isAlignedOffset
<CodeNoteAlign
>(cursor
));
385 // Initialization for remaining arrays.
386 initOptionalArrays(&cursor
, numResumeOffsets
, numScopeNotes
, numTryNotes
);
388 // Check that we correctly recompute the expected values.
389 MOZ_ASSERT(this->codeLength() == codeLength
);
390 MOZ_ASSERT(this->noteLength() == noteLength
);
393 MOZ_ASSERT(endOffset() == cursor
);
396 void js::FillImmutableFlagsFromCompileOptionsForTopLevel(
397 const ReadOnlyCompileOptions
& options
, ImmutableScriptFlags
& flags
) {
398 using ImmutableFlags
= ImmutableScriptFlagsEnum
;
400 js::FillImmutableFlagsFromCompileOptionsForFunction(options
, flags
);
402 flags
.setFlag(ImmutableFlags::TreatAsRunOnce
, options
.isRunOnce
);
403 flags
.setFlag(ImmutableFlags::NoScriptRval
, options
.noScriptRval
);
406 void js::FillImmutableFlagsFromCompileOptionsForFunction(
407 const ReadOnlyCompileOptions
& options
, ImmutableScriptFlags
& flags
) {
408 using ImmutableFlags
= ImmutableScriptFlagsEnum
;
410 flags
.setFlag(ImmutableFlags::SelfHosted
, options
.selfHostingMode
);
411 flags
.setFlag(ImmutableFlags::ForceStrict
, options
.forceStrictMode());
412 flags
.setFlag(ImmutableFlags::HasNonSyntacticScope
,
413 options
.nonSyntacticScope
);
416 // Check if flags matches to compile options for flags set by
417 // FillImmutableFlagsFromCompileOptionsForTopLevel above.
418 bool js::CheckCompileOptionsMatch(const ReadOnlyCompileOptions
& options
,
419 ImmutableScriptFlags flags
) {
420 using ImmutableFlags
= ImmutableScriptFlagsEnum
;
422 bool selfHosted
= !!(flags
& uint32_t(ImmutableFlags::SelfHosted
));
423 bool forceStrict
= !!(flags
& uint32_t(ImmutableFlags::ForceStrict
));
424 bool hasNonSyntacticScope
=
425 !!(flags
& uint32_t(ImmutableFlags::HasNonSyntacticScope
));
426 bool noScriptRval
= !!(flags
& uint32_t(ImmutableFlags::NoScriptRval
));
427 bool treatAsRunOnce
= !!(flags
& uint32_t(ImmutableFlags::TreatAsRunOnce
));
429 return options
.selfHostingMode
== selfHosted
&&
430 options
.noScriptRval
== noScriptRval
&&
431 options
.isRunOnce
== treatAsRunOnce
&&
432 options
.forceStrictMode() == forceStrict
&&
433 options
.nonSyntacticScope
== hasNonSyntacticScope
;
436 JS_PUBLIC_API
bool JS::CheckCompileOptionsMatch(
437 const ReadOnlyCompileOptions
& options
, JSScript
* script
) {
438 return js::CheckCompileOptionsMatch(options
, script
->immutableFlags());
441 bool JSScript::initScriptCounts(JSContext
* cx
) {
442 MOZ_ASSERT(!hasScriptCounts());
444 // Record all pc which are the first instruction of a basic block.
445 mozilla::Vector
<jsbytecode
*, 16, SystemAllocPolicy
> jumpTargets
;
447 js::BytecodeLocation main
= mainLocation();
448 AllBytecodesIterable
iterable(this);
449 for (auto& loc
: iterable
) {
450 if (loc
.isJumpTarget() || loc
== main
) {
451 if (!jumpTargets
.append(loc
.toRawBytecode())) {
452 ReportOutOfMemory(cx
);
458 // Initialize all PCCounts counters to 0.
459 ScriptCounts::PCCountsVector base
;
460 if (!base
.reserve(jumpTargets
.length())) {
461 ReportOutOfMemory(cx
);
465 for (size_t i
= 0; i
< jumpTargets
.length(); i
++) {
466 base
.infallibleEmplaceBack(pcToOffset(jumpTargets
[i
]));
469 // Create zone's scriptCountsMap if necessary.
470 if (!zone()->scriptCountsMap
) {
471 auto map
= cx
->make_unique
<ScriptCountsMap
>();
476 zone()->scriptCountsMap
= std::move(map
);
479 // Allocate the ScriptCounts.
480 UniqueScriptCounts sc
= cx
->make_unique
<ScriptCounts
>(std::move(base
));
485 MOZ_ASSERT(this->hasBytecode());
487 // Register the current ScriptCounts in the zone's map.
488 if (!zone()->scriptCountsMap
->putNew(this, std::move(sc
))) {
489 ReportOutOfMemory(cx
);
493 // safe to set this; we can't fail after this point.
494 setHasScriptCounts();
496 // Enable interrupts in any interpreter frames running on this script. This
497 // is used to let the interpreter increment the PCCounts, if present.
498 for (ActivationIterator
iter(cx
); !iter
.done(); ++iter
) {
499 if (iter
->isInterpreter()) {
500 iter
->asInterpreter()->enableInterruptsIfRunning(this);
507 static inline ScriptCountsMap::Ptr
GetScriptCountsMapEntry(JSScript
* script
) {
508 MOZ_ASSERT(script
->hasScriptCounts());
509 ScriptCountsMap::Ptr p
= script
->zone()->scriptCountsMap
->lookup(script
);
514 ScriptCounts
& JSScript::getScriptCounts() {
515 ScriptCountsMap::Ptr p
= GetScriptCountsMapEntry(this);
519 js::PCCounts
* ScriptCounts::maybeGetPCCounts(size_t offset
) {
520 PCCounts searched
= PCCounts(offset
);
522 std::lower_bound(pcCounts_
.begin(), pcCounts_
.end(), searched
);
523 if (elem
== pcCounts_
.end() || elem
->pcOffset() != offset
) {
529 const js::PCCounts
* ScriptCounts::maybeGetPCCounts(size_t offset
) const {
530 PCCounts searched
= PCCounts(offset
);
531 const PCCounts
* elem
=
532 std::lower_bound(pcCounts_
.begin(), pcCounts_
.end(), searched
);
533 if (elem
== pcCounts_
.end() || elem
->pcOffset() != offset
) {
539 js::PCCounts
* ScriptCounts::getImmediatePrecedingPCCounts(size_t offset
) {
540 PCCounts searched
= PCCounts(offset
);
542 std::lower_bound(pcCounts_
.begin(), pcCounts_
.end(), searched
);
543 if (elem
== pcCounts_
.end()) {
544 return &pcCounts_
.back();
546 if (elem
->pcOffset() == offset
) {
549 if (elem
!= pcCounts_
.begin()) {
555 const js::PCCounts
* ScriptCounts::maybeGetThrowCounts(size_t offset
) const {
556 PCCounts searched
= PCCounts(offset
);
557 const PCCounts
* elem
=
558 std::lower_bound(throwCounts_
.begin(), throwCounts_
.end(), searched
);
559 if (elem
== throwCounts_
.end() || elem
->pcOffset() != offset
) {
565 const js::PCCounts
* ScriptCounts::getImmediatePrecedingThrowCounts(
566 size_t offset
) const {
567 PCCounts searched
= PCCounts(offset
);
568 const PCCounts
* elem
=
569 std::lower_bound(throwCounts_
.begin(), throwCounts_
.end(), searched
);
570 if (elem
== throwCounts_
.end()) {
571 if (throwCounts_
.begin() == throwCounts_
.end()) {
574 return &throwCounts_
.back();
576 if (elem
->pcOffset() == offset
) {
579 if (elem
!= throwCounts_
.begin()) {
585 js::PCCounts
* ScriptCounts::getThrowCounts(size_t offset
) {
586 PCCounts searched
= PCCounts(offset
);
588 std::lower_bound(throwCounts_
.begin(), throwCounts_
.end(), searched
);
589 if (elem
== throwCounts_
.end() || elem
->pcOffset() != offset
) {
590 elem
= throwCounts_
.insert(elem
, searched
);
595 size_t ScriptCounts::sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf
) {
596 size_t size
= mallocSizeOf(this);
597 size
+= pcCounts_
.sizeOfExcludingThis(mallocSizeOf
);
598 size
+= throwCounts_
.sizeOfExcludingThis(mallocSizeOf
);
600 size
+= ionCounts_
->sizeOfIncludingThis(mallocSizeOf
);
605 js::PCCounts
* JSScript::maybeGetPCCounts(jsbytecode
* pc
) {
606 MOZ_ASSERT(containsPC(pc
));
607 return getScriptCounts().maybeGetPCCounts(pcToOffset(pc
));
610 const js::PCCounts
* JSScript::maybeGetThrowCounts(jsbytecode
* pc
) {
611 MOZ_ASSERT(containsPC(pc
));
612 return getScriptCounts().maybeGetThrowCounts(pcToOffset(pc
));
615 js::PCCounts
* JSScript::getThrowCounts(jsbytecode
* pc
) {
616 MOZ_ASSERT(containsPC(pc
));
617 return getScriptCounts().getThrowCounts(pcToOffset(pc
));
620 uint64_t JSScript::getHitCount(jsbytecode
* pc
) {
621 MOZ_ASSERT(containsPC(pc
));
626 ScriptCounts
& sc
= getScriptCounts();
627 size_t targetOffset
= pcToOffset(pc
);
628 const js::PCCounts
* baseCount
=
629 sc
.getImmediatePrecedingPCCounts(targetOffset
);
633 if (baseCount
->pcOffset() == targetOffset
) {
634 return baseCount
->numExec();
636 MOZ_ASSERT(baseCount
->pcOffset() < targetOffset
);
637 uint64_t count
= baseCount
->numExec();
639 const js::PCCounts
* throwCount
=
640 sc
.getImmediatePrecedingThrowCounts(targetOffset
);
644 if (throwCount
->pcOffset() <= baseCount
->pcOffset()) {
647 count
-= throwCount
->numExec();
648 targetOffset
= throwCount
->pcOffset() - 1;
652 void JSScript::addIonCounts(jit::IonScriptCounts
* ionCounts
) {
653 ScriptCounts
& sc
= getScriptCounts();
655 ionCounts
->setPrevious(sc
.ionCounts_
);
657 sc
.ionCounts_
= ionCounts
;
660 jit::IonScriptCounts
* JSScript::getIonCounts() {
661 return getScriptCounts().ionCounts_
;
664 void JSScript::releaseScriptCounts(ScriptCounts
* counts
) {
665 ScriptCountsMap::Ptr p
= GetScriptCountsMapEntry(this);
666 *counts
= std::move(*p
->value().get());
667 zone()->scriptCountsMap
->remove(p
);
668 clearHasScriptCounts();
671 void JSScript::destroyScriptCounts() {
672 if (hasScriptCounts()) {
673 ScriptCounts scriptCounts
;
674 releaseScriptCounts(&scriptCounts
);
678 void JSScript::resetScriptCounts() {
679 if (!hasScriptCounts()) {
683 ScriptCounts
& sc
= getScriptCounts();
685 for (PCCounts
& elem
: sc
.pcCounts_
) {
689 for (PCCounts
& elem
: sc
.throwCounts_
) {
694 void ScriptSourceObject::finalize(JS::GCContext
* gcx
, JSObject
* obj
) {
695 MOZ_ASSERT(gcx
->onMainThread());
696 ScriptSourceObject
* sso
= &obj
->as
<ScriptSourceObject
>();
697 sso
->source()->Release();
699 // Clear the private value, calling the release hook if necessary.
700 sso
->setPrivate(gcx
->runtime(), UndefinedValue());
703 static const JSClassOps ScriptSourceObjectClassOps
= {
704 nullptr, // addProperty
705 nullptr, // delProperty
706 nullptr, // enumerate
707 nullptr, // newEnumerate
709 nullptr, // mayResolve
710 ScriptSourceObject::finalize
, // finalize
712 nullptr, // construct
716 const JSClass
ScriptSourceObject::class_
= {
718 JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS
) | JSCLASS_FOREGROUND_FINALIZE
,
719 &ScriptSourceObjectClassOps
};
721 ScriptSourceObject
* ScriptSourceObject::create(JSContext
* cx
,
722 ScriptSource
* source
) {
723 ScriptSourceObject
* obj
=
724 NewObjectWithGivenProto
<ScriptSourceObject
>(cx
, nullptr);
729 // The matching decref is in ScriptSourceObject::finalize.
730 obj
->initReservedSlot(SOURCE_SLOT
, PrivateValue(do_AddRef(source
).take()));
732 // The slots below should be populated by a call to initFromOptions. Poison
734 obj
->initReservedSlot(ELEMENT_PROPERTY_SLOT
, MagicValue(JS_GENERIC_MAGIC
));
735 obj
->initReservedSlot(INTRODUCTION_SCRIPT_SLOT
, MagicValue(JS_GENERIC_MAGIC
));
740 [[nodiscard
]] static bool MaybeValidateFilename(
741 JSContext
* cx
, Handle
<ScriptSourceObject
*> sso
,
742 const JS::InstantiateOptions
& options
) {
743 if (!gFilenameValidationCallback
) {
747 const char* filename
= sso
->source()->filename();
748 if (!filename
|| options
.skipFilenameValidation
) {
752 if (gFilenameValidationCallback(cx
, filename
)) {
756 const char* utf8Filename
;
757 if (mozilla::IsUtf8(mozilla::MakeStringSpan(filename
))) {
758 utf8Filename
= filename
;
760 utf8Filename
= "(invalid UTF-8 filename)";
762 JS_ReportErrorNumberUTF8(cx
, GetErrorMessage
, nullptr, JSMSG_UNSAFE_FILENAME
,
768 bool ScriptSourceObject::initFromOptions(
769 JSContext
* cx
, Handle
<ScriptSourceObject
*> source
,
770 const JS::InstantiateOptions
& options
) {
771 cx
->releaseCheck(source
);
773 source
->getReservedSlot(ELEMENT_PROPERTY_SLOT
).isMagic(JS_GENERIC_MAGIC
));
774 MOZ_ASSERT(source
->getReservedSlot(INTRODUCTION_SCRIPT_SLOT
)
775 .isMagic(JS_GENERIC_MAGIC
));
777 if (!MaybeValidateFilename(cx
, source
, options
)) {
781 if (options
.deferDebugMetadata
) {
785 // Initialize the element attribute slot and introduction script slot
786 // this marks the SSO as initialized for asserts.
788 RootedString
elementAttributeName(cx
);
789 if (!initElementProperties(cx
, source
, elementAttributeName
)) {
793 RootedValue
introductionScript(cx
);
794 source
->setReservedSlot(INTRODUCTION_SCRIPT_SLOT
, introductionScript
);
800 bool ScriptSourceObject::initElementProperties(
801 JSContext
* cx
, Handle
<ScriptSourceObject
*> source
,
802 HandleString elementAttrName
) {
803 RootedValue
nameValue(cx
);
804 if (elementAttrName
) {
805 nameValue
= StringValue(elementAttrName
);
807 if (!cx
->compartment()->wrap(cx
, &nameValue
)) {
811 source
->setReservedSlot(ELEMENT_PROPERTY_SLOT
, nameValue
);
816 void ScriptSourceObject::setPrivate(JSRuntime
* rt
, const Value
& value
) {
817 // Update the private value, calling addRef/release hooks if necessary
818 // to allow the embedding to maintain a reference count for the
820 JS::AutoSuppressGCAnalysis nogc
;
821 Value prevValue
= getReservedSlot(PRIVATE_SLOT
);
822 rt
->releaseScriptPrivate(prevValue
);
823 setReservedSlot(PRIVATE_SLOT
, value
);
824 rt
->addRefScriptPrivate(value
);
827 void ScriptSourceObject::clearPrivate(JSRuntime
* rt
) {
828 // Clear the private value, calling release hook if necessary.
829 // |this| may be gray, be careful not to create edges to it.
830 JS::AutoSuppressGCAnalysis nogc
;
831 Value prevValue
= getReservedSlot(PRIVATE_SLOT
);
832 rt
->releaseScriptPrivate(prevValue
);
833 getSlotRef(PRIVATE_SLOT
).setUndefinedUnchecked();
836 class ScriptSource::LoadSourceMatcher
{
837 JSContext
* const cx_
;
838 ScriptSource
* const ss_
;
842 explicit LoadSourceMatcher(JSContext
* cx
, ScriptSource
* ss
, bool* loaded
)
843 : cx_(cx
), ss_(ss
), loaded_(loaded
) {}
845 template <typename Unit
, SourceRetrievable CanRetrieve
>
846 bool operator()(const Compressed
<Unit
, CanRetrieve
>&) const {
851 template <typename Unit
, SourceRetrievable CanRetrieve
>
852 bool operator()(const Uncompressed
<Unit
, CanRetrieve
>&) const {
857 template <typename Unit
>
858 bool operator()(const Retrievable
<Unit
>&) {
859 if (!cx_
->runtime()->sourceHook
.ref()) {
866 // The first argument is just for overloading -- its value doesn't matter.
867 if (!tryLoadAndSetSource(Unit('0'), &length
)) {
874 bool operator()(const Missing
&) const {
880 bool tryLoadAndSetSource(const Utf8Unit
&, size_t* length
) const {
882 if (!cx_
->runtime()->sourceHook
->load(cx_
, ss_
->filename(), nullptr,
883 &utf8Source
, length
)) {
892 if (!ss_
->setRetrievedSource(
893 cx_
, EntryUnits
<Utf8Unit
>(reinterpret_cast<Utf8Unit
*>(utf8Source
)),
902 bool tryLoadAndSetSource(const char16_t
&, size_t* length
) const {
903 char16_t
* utf16Source
;
904 if (!cx_
->runtime()->sourceHook
->load(cx_
, ss_
->filename(), &utf16Source
,
914 if (!ss_
->setRetrievedSource(cx_
, EntryUnits
<char16_t
>(utf16Source
),
925 bool ScriptSource::loadSource(JSContext
* cx
, ScriptSource
* ss
, bool* loaded
) {
926 return ss
->data
.match(LoadSourceMatcher(cx
, ss
, loaded
));
930 JSLinearString
* JSScript::sourceData(JSContext
* cx
, HandleScript script
) {
931 MOZ_ASSERT(script
->scriptSource()->hasSourceText());
932 return script
->scriptSource()->substring(cx
, script
->sourceStart(),
933 script
->sourceEnd());
936 bool BaseScript::appendSourceDataForToString(JSContext
* cx
, StringBuffer
& buf
) {
937 MOZ_ASSERT(scriptSource()->hasSourceText());
938 return scriptSource()->appendSubstring(cx
, buf
, toStringStart(),
942 void UncompressedSourceCache::holdEntry(AutoHoldEntry
& holder
,
943 const ScriptSourceChunk
& ssc
) {
944 MOZ_ASSERT(!holder_
);
945 holder
.holdEntry(this, ssc
);
949 void UncompressedSourceCache::releaseEntry(AutoHoldEntry
& holder
) {
950 MOZ_ASSERT(holder_
== &holder
);
954 template <typename Unit
>
955 const Unit
* UncompressedSourceCache::lookup(const ScriptSourceChunk
& ssc
,
956 AutoHoldEntry
& holder
) {
957 MOZ_ASSERT(!holder_
);
958 MOZ_ASSERT(ssc
.ss
->isCompressed
<Unit
>());
964 if (Map::Ptr p
= map_
->lookup(ssc
)) {
965 holdEntry(holder
, ssc
);
966 return static_cast<const Unit
*>(p
->value().get());
972 bool UncompressedSourceCache::put(const ScriptSourceChunk
& ssc
, SourceData data
,
973 AutoHoldEntry
& holder
) {
974 MOZ_ASSERT(!holder_
);
977 map_
= MakeUnique
<Map
>();
983 if (!map_
->put(ssc
, std::move(data
))) {
987 holdEntry(holder
, ssc
);
991 void UncompressedSourceCache::purge() {
996 for (Map::Range r
= map_
->all(); !r
.empty(); r
.popFront()) {
997 if (holder_
&& r
.front().key() == holder_
->sourceChunk()) {
998 holder_
->deferDelete(std::move(r
.front().value()));
1006 size_t UncompressedSourceCache::sizeOfExcludingThis(
1007 mozilla::MallocSizeOf mallocSizeOf
) {
1009 if (map_
&& !map_
->empty()) {
1010 n
+= map_
->shallowSizeOfIncludingThis(mallocSizeOf
);
1011 for (Map::Range r
= map_
->all(); !r
.empty(); r
.popFront()) {
1012 n
+= mallocSizeOf(r
.front().value().get());
1018 template <typename Unit
>
1019 const Unit
* ScriptSource::chunkUnits(
1020 JSContext
* cx
, UncompressedSourceCache::AutoHoldEntry
& holder
,
1022 const CompressedData
<Unit
>& c
= *compressedData
<Unit
>();
1024 ScriptSourceChunk
ssc(this, chunk
);
1025 if (const Unit
* decompressed
=
1026 cx
->caches().uncompressedSourceCache
.lookup
<Unit
>(ssc
, holder
)) {
1027 return decompressed
;
1030 size_t totalLengthInBytes
= length() * sizeof(Unit
);
1031 size_t chunkBytes
= Compressor::chunkSize(totalLengthInBytes
, chunk
);
1033 MOZ_ASSERT((chunkBytes
% sizeof(Unit
)) == 0);
1034 const size_t chunkLength
= chunkBytes
/ sizeof(Unit
);
1035 EntryUnits
<Unit
> decompressed(js_pod_malloc
<Unit
>(chunkLength
));
1036 if (!decompressed
) {
1037 JS_ReportOutOfMemory(cx
);
1041 // Compression treats input and output memory as plain ol' bytes. These
1042 // reinterpret_cast<>s accord exactly with that.
1043 if (!DecompressStringChunk(
1044 reinterpret_cast<const unsigned char*>(c
.raw
.chars()), chunk
,
1045 reinterpret_cast<unsigned char*>(decompressed
.get()), chunkBytes
)) {
1046 JS_ReportOutOfMemory(cx
);
1050 const Unit
* ret
= decompressed
.get();
1051 if (!cx
->caches().uncompressedSourceCache
.put(
1052 ssc
, ToSourceData(std::move(decompressed
)), holder
)) {
1053 JS_ReportOutOfMemory(cx
);
1059 template <typename Unit
>
1060 void ScriptSource::convertToCompressedSource(SharedImmutableString compressed
,
1061 size_t uncompressedLength
) {
1062 MOZ_ASSERT(isUncompressed
<Unit
>());
1063 MOZ_ASSERT(uncompressedData
<Unit
>()->length() == uncompressedLength
);
1065 if (data
.is
<Uncompressed
<Unit
, SourceRetrievable::Yes
>>()) {
1066 data
= SourceType(Compressed
<Unit
, SourceRetrievable::Yes
>(
1067 std::move(compressed
), uncompressedLength
));
1069 data
= SourceType(Compressed
<Unit
, SourceRetrievable::No
>(
1070 std::move(compressed
), uncompressedLength
));
1074 template <typename Unit
>
1075 void ScriptSource::performDelayedConvertToCompressedSource(
1076 ExclusiveData
<ReaderInstances
>::Guard
& g
) {
1077 // There might not be a conversion to compressed source happening at all.
1078 if (g
->pendingCompressed
.empty()) {
1082 CompressedData
<Unit
>& pending
=
1083 g
->pendingCompressed
.ref
<CompressedData
<Unit
>>();
1085 convertToCompressedSource
<Unit
>(std::move(pending
.raw
),
1086 pending
.uncompressedLength
);
1088 g
->pendingCompressed
.destroy();
1091 void ScriptSource::PinnedUnitsBase::addReader() {
1092 auto guard
= source_
->readers_
.lock();
1096 template <typename Unit
>
1097 void ScriptSource::PinnedUnitsBase::removeReader() {
1098 // Note: We use a Mutex with Exclusive access, such that no PinnedUnits
1099 // instance is live while we are compressing the source.
1100 auto guard
= source_
->readers_
.lock();
1101 MOZ_ASSERT(guard
->count
> 0);
1102 if (--guard
->count
) {
1103 source_
->performDelayedConvertToCompressedSource
<Unit
>(guard
);
1107 template <typename Unit
>
1108 ScriptSource::PinnedUnits
<Unit
>::~PinnedUnits() {
1110 removeReader
<Unit
>();
1114 template <typename Unit
>
1115 ScriptSource::PinnedUnitsIfUncompressed
<Unit
>::~PinnedUnitsIfUncompressed() {
1117 removeReader
<Unit
>();
1121 template <typename Unit
>
1122 const Unit
* ScriptSource::units(JSContext
* cx
,
1123 UncompressedSourceCache::AutoHoldEntry
& holder
,
1124 size_t begin
, size_t len
) {
1125 MOZ_ASSERT(begin
<= length());
1126 MOZ_ASSERT(begin
+ len
<= length());
1128 if (isUncompressed
<Unit
>()) {
1129 const Unit
* units
= uncompressedData
<Unit
>()->units();
1133 return units
+ begin
;
1136 if (data
.is
<Missing
>()) {
1137 MOZ_CRASH("ScriptSource::units() on ScriptSource with missing source");
1140 if (data
.is
<Retrievable
<Unit
>>()) {
1141 MOZ_CRASH("ScriptSource::units() on ScriptSource with retrievable source");
1144 MOZ_ASSERT(isCompressed
<Unit
>());
1146 // Determine first/last chunks, the offset (in bytes) into the first chunk
1147 // of the requested units, and the number of bytes in the last chunk.
1149 // Note that first and last chunk sizes are miscomputed and *must not be
1150 // used* when the first chunk is the last chunk.
1151 size_t firstChunk
, firstChunkOffset
, firstChunkSize
;
1152 size_t lastChunk
, lastChunkSize
;
1153 Compressor::rangeToChunkAndOffset(
1154 begin
* sizeof(Unit
), (begin
+ len
) * sizeof(Unit
), &firstChunk
,
1155 &firstChunkOffset
, &firstChunkSize
, &lastChunk
, &lastChunkSize
);
1156 MOZ_ASSERT(firstChunk
<= lastChunk
);
1157 MOZ_ASSERT(firstChunkOffset
% sizeof(Unit
) == 0);
1158 MOZ_ASSERT(firstChunkSize
% sizeof(Unit
) == 0);
1160 size_t firstUnit
= firstChunkOffset
/ sizeof(Unit
);
1162 // Directly return units within a single chunk. UncompressedSourceCache
1163 // and |holder| will hold the units alive past function return.
1164 if (firstChunk
== lastChunk
) {
1165 const Unit
* units
= chunkUnits
<Unit
>(cx
, holder
, firstChunk
);
1170 return units
+ firstUnit
;
1173 // Otherwise the units span multiple chunks. Copy successive chunks'
1174 // decompressed units into freshly-allocated memory to return.
1175 EntryUnits
<Unit
> decompressed(js_pod_malloc
<Unit
>(len
));
1176 if (!decompressed
) {
1177 JS_ReportOutOfMemory(cx
);
1184 // |AutoHoldEntry| is single-shot, and a holder successfully filled in
1185 // by |chunkUnits| must be destroyed before another can be used. Thus
1186 // we can't use |holder| with |chunkUnits| when |chunkUnits| is used
1187 // with multiple chunks, and we must use and destroy distinct, fresh
1188 // holders for each chunk.
1189 UncompressedSourceCache::AutoHoldEntry firstHolder
;
1190 const Unit
* units
= chunkUnits
<Unit
>(cx
, firstHolder
, firstChunk
);
1195 cursor
= std::copy_n(units
+ firstUnit
, firstChunkSize
/ sizeof(Unit
),
1196 decompressed
.get());
1199 for (size_t i
= firstChunk
+ 1; i
< lastChunk
; i
++) {
1200 UncompressedSourceCache::AutoHoldEntry chunkHolder
;
1201 const Unit
* units
= chunkUnits
<Unit
>(cx
, chunkHolder
, i
);
1206 cursor
= std::copy_n(units
, Compressor::CHUNK_SIZE
/ sizeof(Unit
), cursor
);
1210 UncompressedSourceCache::AutoHoldEntry lastHolder
;
1211 const Unit
* units
= chunkUnits
<Unit
>(cx
, lastHolder
, lastChunk
);
1216 cursor
= std::copy_n(units
, lastChunkSize
/ sizeof(Unit
), cursor
);
1219 MOZ_ASSERT(PointerRangeSize(decompressed
.get(), cursor
) == len
);
1221 // Transfer ownership to |holder|.
1222 const Unit
* ret
= decompressed
.get();
1223 holder
.holdUnits(std::move(decompressed
));
1227 template <typename Unit
>
1228 const Unit
* ScriptSource::uncompressedUnits(size_t begin
, size_t len
) {
1229 MOZ_ASSERT(begin
<= length());
1230 MOZ_ASSERT(begin
+ len
<= length());
1232 if (!isUncompressed
<Unit
>()) {
1236 const Unit
* units
= uncompressedData
<Unit
>()->units();
1240 return units
+ begin
;
1243 template <typename Unit
>
1244 ScriptSource::PinnedUnits
<Unit
>::PinnedUnits(
1245 JSContext
* cx
, ScriptSource
* source
,
1246 UncompressedSourceCache::AutoHoldEntry
& holder
, size_t begin
, size_t len
)
1247 : PinnedUnitsBase(source
) {
1248 MOZ_ASSERT(source
->hasSourceType
<Unit
>(), "must pin units of source's type");
1250 units_
= source
->units
<Unit
>(cx
, holder
, begin
, len
);
1256 template class ScriptSource::PinnedUnits
<Utf8Unit
>;
1257 template class ScriptSource::PinnedUnits
<char16_t
>;
1259 template <typename Unit
>
1260 ScriptSource::PinnedUnitsIfUncompressed
<Unit
>::PinnedUnitsIfUncompressed(
1261 ScriptSource
* source
, size_t begin
, size_t len
)
1262 : PinnedUnitsBase(source
) {
1263 MOZ_ASSERT(source
->hasSourceType
<Unit
>(), "must pin units of source's type");
1265 units_
= source
->uncompressedUnits
<Unit
>(begin
, len
);
1271 template class ScriptSource::PinnedUnitsIfUncompressed
<Utf8Unit
>;
1272 template class ScriptSource::PinnedUnitsIfUncompressed
<char16_t
>;
1274 JSLinearString
* ScriptSource::substring(JSContext
* cx
, size_t start
,
1276 MOZ_ASSERT(start
<= stop
);
1278 size_t len
= stop
- start
;
1280 return cx
->emptyString();
1282 UncompressedSourceCache::AutoHoldEntry holder
;
1284 // UTF-8 source text.
1285 if (hasSourceType
<Utf8Unit
>()) {
1286 PinnedUnits
<Utf8Unit
> units(cx
, this, holder
, start
, len
);
1287 if (!units
.asChars()) {
1291 const char* str
= units
.asChars();
1292 return NewStringCopyUTF8N(cx
, JS::UTF8Chars(str
, len
));
1295 // UTF-16 source text.
1296 PinnedUnits
<char16_t
> units(cx
, this, holder
, start
, len
);
1297 if (!units
.asChars()) {
1301 return NewStringCopyN
<CanGC
>(cx
, units
.asChars(), len
);
1304 JSLinearString
* ScriptSource::substringDontDeflate(JSContext
* cx
, size_t start
,
1306 MOZ_ASSERT(start
<= stop
);
1308 size_t len
= stop
- start
;
1310 return cx
->emptyString();
1312 UncompressedSourceCache::AutoHoldEntry holder
;
1314 // UTF-8 source text.
1315 if (hasSourceType
<Utf8Unit
>()) {
1316 PinnedUnits
<Utf8Unit
> units(cx
, this, holder
, start
, len
);
1317 if (!units
.asChars()) {
1321 const char* str
= units
.asChars();
1323 // There doesn't appear to be a non-deflating UTF-8 string creation
1324 // function -- but then again, it's not entirely clear how current
1325 // callers benefit from non-deflation.
1326 return NewStringCopyUTF8N(cx
, JS::UTF8Chars(str
, len
));
1329 // UTF-16 source text.
1330 PinnedUnits
<char16_t
> units(cx
, this, holder
, start
, len
);
1331 if (!units
.asChars()) {
1335 return NewStringCopyNDontDeflate
<CanGC
>(cx
, units
.asChars(), len
);
1338 bool ScriptSource::appendSubstring(JSContext
* cx
, StringBuffer
& buf
,
1339 size_t start
, size_t stop
) {
1340 MOZ_ASSERT(start
<= stop
);
1342 size_t len
= stop
- start
;
1343 UncompressedSourceCache::AutoHoldEntry holder
;
1345 if (hasSourceType
<Utf8Unit
>()) {
1346 PinnedUnits
<Utf8Unit
> pinned(cx
, this, holder
, start
, len
);
1347 if (!pinned
.get()) {
1350 if (len
> SourceDeflateLimit
&& !buf
.ensureTwoByteChars()) {
1354 const Utf8Unit
* units
= pinned
.get();
1355 return buf
.append(units
, len
);
1357 PinnedUnits
<char16_t
> pinned(cx
, this, holder
, start
, len
);
1358 if (!pinned
.get()) {
1361 if (len
> SourceDeflateLimit
&& !buf
.ensureTwoByteChars()) {
1365 const char16_t
* units
= pinned
.get();
1366 return buf
.append(units
, len
);
1370 JSLinearString
* ScriptSource::functionBodyString(JSContext
* cx
) {
1371 MOZ_ASSERT(isFunctionBody());
1373 size_t start
= parameterListEnd_
+ FunctionConstructorMedialSigils
.length();
1374 size_t stop
= length() - FunctionConstructorFinalBrace
.length();
1375 return substring(cx
, start
, stop
);
1378 template <typename ContextT
, typename Unit
>
1379 [[nodiscard
]] bool ScriptSource::setUncompressedSourceHelper(
1380 ContextT
* cx
, EntryUnits
<Unit
>&& source
, size_t length
,
1381 SourceRetrievable retrievable
) {
1382 auto& cache
= SharedImmutableStringsCache::getSingleton();
1384 auto uniqueChars
= SourceTypeTraits
<Unit
>::toCacheable(std::move(source
));
1385 auto deduped
= cache
.getOrCreate(std::move(uniqueChars
), length
);
1387 ReportOutOfMemory(cx
);
1391 if (retrievable
== SourceRetrievable::Yes
) {
1393 Uncompressed
<Unit
, SourceRetrievable::Yes
>(std::move(deduped
)));
1396 Uncompressed
<Unit
, SourceRetrievable::No
>(std::move(deduped
)));
1401 template <typename Unit
>
1402 [[nodiscard
]] bool ScriptSource::setRetrievedSource(JSContext
* cx
,
1403 EntryUnits
<Unit
>&& source
,
1405 MOZ_ASSERT(data
.is
<Retrievable
<Unit
>>(),
1406 "retrieved source can only overwrite the corresponding "
1407 "retrievable source");
1408 return setUncompressedSourceHelper(cx
, std::move(source
), length
,
1409 SourceRetrievable::Yes
);
1412 bool js::IsOffThreadSourceCompressionEnabled() {
1413 // If we don't have concurrent execution compression will contend with
1414 // main-thread execution, in which case we disable. Similarly we don't want to
1415 // block the thread pool if it is too small.
1416 return GetHelperThreadCPUCount() > 1 && GetHelperThreadCount() > 1 &&
1417 CanUseExtraThreads();
1420 bool ScriptSource::tryCompressOffThread(JSContext
* cx
) {
1421 // Beware: |js::SynchronouslyCompressSource| assumes that this function is
1422 // only called once, just after a script has been compiled, and it's never
1423 // called at some random time after that. If multiple calls of this can ever
1424 // occur, that function may require changes.
1426 // The SourceCompressionTask needs to record the major GC number for
1428 MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx
->runtime()));
1430 // If source compression was already attempted, do not queue a new task.
1431 if (hadCompressionTask_
) {
1435 if (!hasUncompressedSource()) {
1436 // This excludes compressed, missing, and retrievable source.
1440 // There are several cases where source compression is not a good idea:
1441 // - If the script is tiny, then compression will save little or no space.
1442 // - If there is only one core, then compression will contend with JS
1443 // execution (which hurts benchmarketing).
1445 // Otherwise, enqueue a compression task to be processed when a major
1448 if (length() < ScriptSource::MinimumCompressibleLength
||
1449 !IsOffThreadSourceCompressionEnabled()) {
1453 // Heap allocate the task. It will be freed upon compression
1454 // completing in AttachFinishedCompressedSources.
1455 auto task
= MakeUnique
<SourceCompressionTask
>(cx
->runtime(), this);
1457 ReportOutOfMemory(cx
);
1460 return EnqueueOffThreadCompression(cx
, std::move(task
));
1463 template <typename Unit
>
1464 void ScriptSource::triggerConvertToCompressedSource(
1465 SharedImmutableString compressed
, size_t uncompressedLength
) {
1466 MOZ_ASSERT(isUncompressed
<Unit
>(),
1467 "should only be triggering compressed source installation to "
1468 "overwrite identically-encoded uncompressed source");
1469 MOZ_ASSERT(uncompressedData
<Unit
>()->length() == uncompressedLength
);
1471 // If units aren't pinned -- and they probably won't be, we'd have to have a
1472 // GC in the small window of time where a |PinnedUnits| was live -- then we
1473 // can immediately convert.
1475 auto guard
= readers_
.lock();
1476 if (MOZ_LIKELY(!guard
->count
)) {
1477 convertToCompressedSource
<Unit
>(std::move(compressed
),
1478 uncompressedLength
);
1482 // Otherwise, set aside the compressed-data info. The conversion is
1483 // performed when the last |PinnedUnits| dies.
1484 MOZ_ASSERT(guard
->pendingCompressed
.empty(),
1485 "shouldn't be multiple conversions happening");
1486 guard
->pendingCompressed
.construct
<CompressedData
<Unit
>>(
1487 std::move(compressed
), uncompressedLength
);
1491 template <typename Unit
>
1492 [[nodiscard
]] bool ScriptSource::initializeWithUnretrievableCompressedSource(
1493 FrontendContext
* fc
, UniqueChars
&& compressed
, size_t rawLength
,
1494 size_t sourceLength
) {
1495 MOZ_ASSERT(data
.is
<Missing
>(), "shouldn't be double-initializing");
1496 MOZ_ASSERT(compressed
!= nullptr);
1498 auto& cache
= SharedImmutableStringsCache::getSingleton();
1499 auto deduped
= cache
.getOrCreate(std::move(compressed
), rawLength
);
1501 ReportOutOfMemory(fc
);
1507 auto guard
= readers_
.lock();
1510 "shouldn't be initializing a ScriptSource while its characters "
1511 "are pinned -- that only makes sense with a ScriptSource actively "
1516 data
= SourceType(Compressed
<Unit
, SourceRetrievable::No
>(std::move(deduped
),
1522 template bool ScriptSource::initializeWithUnretrievableCompressedSource
<
1523 Utf8Unit
>(FrontendContext
* fc
, UniqueChars
&& compressed
, size_t rawLength
,
1524 size_t sourceLength
);
1525 template bool ScriptSource::initializeWithUnretrievableCompressedSource
<
1526 char16_t
>(FrontendContext
* fc
, UniqueChars
&& compressed
, size_t rawLength
,
1527 size_t sourceLength
);
1529 template <typename Unit
>
1530 bool ScriptSource::assignSource(FrontendContext
* fc
,
1531 const ReadOnlyCompileOptions
& options
,
1532 SourceText
<Unit
>& srcBuf
) {
1533 MOZ_ASSERT(data
.is
<Missing
>(),
1534 "source assignment should only occur on fresh ScriptSources");
1536 mutedErrors_
= options
.mutedErrors();
1537 delazificationMode_
= options
.eagerDelazificationStrategy();
1539 if (options
.discardSource
) {
1543 if (options
.sourceIsLazy
) {
1544 data
= SourceType(Retrievable
<Unit
>());
1548 auto& cache
= SharedImmutableStringsCache::getSingleton();
1549 auto deduped
= cache
.getOrCreate(srcBuf
.get(), srcBuf
.length(), [&srcBuf
]() {
1550 using CharT
= typename SourceTypeTraits
<Unit
>::CharT
;
1551 return srcBuf
.ownsUnits()
1552 ? UniquePtr
<CharT
[], JS::FreePolicy
>(srcBuf
.takeChars())
1553 : DuplicateString(srcBuf
.get(), srcBuf
.length());
1556 ReportOutOfMemory(fc
);
1561 SourceType(Uncompressed
<Unit
, SourceRetrievable::No
>(std::move(deduped
)));
1565 template bool ScriptSource::assignSource(FrontendContext
* fc
,
1566 const ReadOnlyCompileOptions
& options
,
1567 SourceText
<char16_t
>& srcBuf
);
1568 template bool ScriptSource::assignSource(FrontendContext
* fc
,
1569 const ReadOnlyCompileOptions
& options
,
1570 SourceText
<Utf8Unit
>& srcBuf
);
1572 [[nodiscard
]] static bool reallocUniquePtr(UniqueChars
& unique
, size_t size
) {
1573 auto newPtr
= static_cast<char*>(js_realloc(unique
.get(), size
));
1578 // Since the realloc succeeded, unique is now holding a freed pointer.
1579 (void)unique
.release();
1580 unique
.reset(newPtr
);
1584 template <typename Unit
>
1585 void SourceCompressionTask::workEncodingSpecific() {
1586 MOZ_ASSERT(source_
->isUncompressed
<Unit
>());
1588 // Try to keep the maximum memory usage down by only allocating half the
1589 // size of the string, first.
1590 size_t inputBytes
= source_
->length() * sizeof(Unit
);
1591 size_t firstSize
= inputBytes
/ 2;
1592 UniqueChars
compressed(js_pod_malloc
<char>(firstSize
));
1597 const Unit
* chars
= source_
->uncompressedData
<Unit
>()->units();
1598 Compressor
comp(reinterpret_cast<const unsigned char*>(chars
), inputBytes
);
1603 comp
.setOutput(reinterpret_cast<unsigned char*>(compressed
.get()), firstSize
);
1605 bool reallocated
= false;
1607 if (shouldCancel()) {
1611 switch (comp
.compressMore()) {
1612 case Compressor::CONTINUE
:
1614 case Compressor::MOREOUTPUT
: {
1616 // The compressed string is longer than the original string.
1620 // The compressed output is greater than half the size of the
1621 // original string. Reallocate to the full size.
1622 if (!reallocUniquePtr(compressed
, inputBytes
)) {
1626 comp
.setOutput(reinterpret_cast<unsigned char*>(compressed
.get()),
1631 case Compressor::DONE
:
1634 case Compressor::OOM
:
1639 size_t totalBytes
= comp
.totalBytesNeeded();
1641 // Shrink the buffer to the size of the compressed data.
1642 if (!reallocUniquePtr(compressed
, totalBytes
)) {
1646 comp
.finish(compressed
.get(), totalBytes
);
1648 if (shouldCancel()) {
1652 auto& strings
= SharedImmutableStringsCache::getSingleton();
1653 resultString_
= strings
.getOrCreate(std::move(compressed
), totalBytes
);
1656 struct SourceCompressionTask::PerformTaskWork
{
1657 SourceCompressionTask
* const task_
;
1659 explicit PerformTaskWork(SourceCompressionTask
* task
) : task_(task
) {}
1661 template <typename Unit
, SourceRetrievable CanRetrieve
>
1662 void operator()(const ScriptSource::Uncompressed
<Unit
, CanRetrieve
>&) {
1663 task_
->workEncodingSpecific
<Unit
>();
1666 template <typename T
>
1667 void operator()(const T
&) {
1669 "why are we compressing missing, missing-but-retrievable, "
1670 "or already-compressed source?");
1674 void ScriptSource::performTaskWork(SourceCompressionTask
* task
) {
1675 MOZ_ASSERT(hasUncompressedSource());
1676 data
.match(SourceCompressionTask::PerformTaskWork(task
));
1679 void SourceCompressionTask::runTask() {
1680 if (shouldCancel()) {
1684 MOZ_ASSERT(source_
->hasUncompressedSource());
1686 source_
->performTaskWork(this);
1689 void SourceCompressionTask::runHelperThreadTask(
1690 AutoLockHelperThreadState
& locked
) {
1692 AutoUnlockHelperThreadState
unlock(locked
);
1697 AutoEnterOOMUnsafeRegion oomUnsafe
;
1698 if (!HelperThreadState().compressionFinishedList(locked
).append(this)) {
1699 oomUnsafe
.crash("SourceCompressionTask::runHelperThreadTask");
1704 void ScriptSource::triggerConvertToCompressedSourceFromTask(
1705 SharedImmutableString compressed
) {
1706 data
.match(TriggerConvertToCompressedSourceFromTask(this, compressed
));
1709 void SourceCompressionTask::complete() {
1710 if (!shouldCancel() && resultString_
) {
1711 source_
->triggerConvertToCompressedSourceFromTask(std::move(resultString_
));
1715 bool js::SynchronouslyCompressSource(JSContext
* cx
,
1716 JS::Handle
<BaseScript
*> script
) {
1717 // Finish all pending source compressions, including the single compression
1718 // task that may have been created (by |ScriptSource::tryCompressOffThread|)
1719 // just after the script was compiled. Because we have flushed this queue,
1720 // no code below needs to synchronize with an off-thread parse task that
1721 // assumes the immutability of a |ScriptSource|'s data.
1723 // This *may* end up compressing |script|'s source. If it does -- we test
1724 // this below -- that takes care of things. But if it doesn't, we will
1725 // synchronously compress ourselves (and as noted above, this won't race
1727 RunPendingSourceCompressions(cx
->runtime());
1729 ScriptSource
* ss
= script
->scriptSource();
1732 auto guard
= ss
->readers_
.lock();
1733 MOZ_ASSERT(guard
->count
== 0,
1734 "can't synchronously compress while source units are in use");
1738 // In principle a previously-triggered compression on a helper thread could
1739 // have already completed. If that happens, there's nothing more to do.
1740 if (ss
->hasCompressedSource()) {
1744 MOZ_ASSERT(ss
->hasUncompressedSource(),
1745 "shouldn't be compressing uncompressible source");
1747 // Use an explicit scope to delineate the lifetime of |task|, for simplicity.
1750 uint32_t sourceRefs
= ss
->refs
;
1752 MOZ_ASSERT(sourceRefs
> 0, "at least |script| here should have a ref");
1754 // |SourceCompressionTask::shouldCancel| can periodically result in source
1755 // compression being canceled if we're not careful. Guarantee that two refs
1756 // to |ss| are always live in this function (at least one preexisting and
1757 // one held by the task) so that compression is never canceled.
1758 auto task
= MakeUnique
<SourceCompressionTask
>(cx
->runtime(), ss
);
1760 ReportOutOfMemory(cx
);
1764 MOZ_ASSERT(ss
->refs
> sourceRefs
, "must have at least two refs now");
1766 // Attempt to compress. This may not succeed if OOM happens, but (because
1767 // it ordinarily happens on a helper thread) no error will ever be set here.
1768 MOZ_ASSERT(!cx
->isExceptionPending());
1769 ss
->performTaskWork(task
.get());
1770 MOZ_ASSERT(!cx
->isExceptionPending());
1772 // Convert |ss| from uncompressed to compressed data.
1775 MOZ_ASSERT(!cx
->isExceptionPending());
1778 // The only way source won't be compressed here is if OOM happened.
1779 return ss
->hasCompressedSource();
1782 void ScriptSource::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf
,
1783 JS::ScriptSourceInfo
* info
) const {
1784 info
->misc
+= mallocSizeOf(this);
1788 bool ScriptSource::startIncrementalEncoding(
1790 UniquePtr
<frontend::ExtensibleCompilationStencil
>&& initial
) {
1791 // We don't support asm.js in XDR.
1792 // Encoding failures are reported by the xdrFinalizeEncoder function.
1793 if (initial
->asmJS
) {
1797 // Remove the reference to the source, to avoid the circular reference.
1798 initial
->source
= nullptr;
1800 AutoIncrementalTimer
timer(cx
->realm()->timers
.xdrEncodingTime
);
1801 auto failureCase
= mozilla::MakeScopeExit([&] { xdrEncoder_
.reset(); });
1803 if (!xdrEncoder_
.setInitial(
1804 cx
, std::forward
<UniquePtr
<frontend::ExtensibleCompilationStencil
>>(
1806 // On encoding failure, let failureCase destroy encoder and return true
1807 // to avoid failing any currently executing script.
1811 failureCase
.release();
1815 bool ScriptSource::addDelazificationToIncrementalEncoding(
1816 JSContext
* cx
, const frontend::CompilationStencil
& stencil
) {
1817 MOZ_ASSERT(hasEncoder());
1818 AutoIncrementalTimer
timer(cx
->realm()->timers
.xdrEncodingTime
);
1819 auto failureCase
= mozilla::MakeScopeExit([&] { xdrEncoder_
.reset(); });
1821 if (!xdrEncoder_
.addDelazification(cx
, stencil
)) {
1822 // On encoding failure, let failureCase destroy encoder and return true
1823 // to avoid failing any currently executing script.
1827 failureCase
.release();
1831 bool ScriptSource::xdrFinalizeEncoder(JSContext
* cx
,
1832 JS::TranscodeBuffer
& buffer
) {
1833 if (!hasEncoder()) {
1834 JS_ReportErrorASCII(cx
, "XDR encoding failure");
1838 auto cleanup
= mozilla::MakeScopeExit([&] { xdrEncoder_
.reset(); });
1840 AutoReportFrontendContext
fc(cx
);
1841 XDRStencilEncoder
encoder(&fc
, buffer
);
1843 frontend::BorrowingCompilationStencil
borrowingStencil(
1844 xdrEncoder_
.merger_
->getResult());
1845 XDRResult res
= encoder
.codeStencil(this, borrowingStencil
);
1847 if (JS::IsTranscodeFailureResult(res
.unwrapErr())) {
1848 fc
.clearAutoReport();
1849 JS_ReportErrorASCII(cx
, "XDR encoding failure");
1856 void ScriptSource::xdrAbortEncoder() { xdrEncoder_
.reset(); }
1858 template <typename Unit
>
1859 [[nodiscard
]] bool ScriptSource::initializeUnretrievableUncompressedSource(
1860 FrontendContext
* fc
, EntryUnits
<Unit
>&& source
, size_t length
) {
1861 MOZ_ASSERT(data
.is
<Missing
>(), "must be initializing a fresh ScriptSource");
1862 return setUncompressedSourceHelper(fc
, std::move(source
), length
,
1863 SourceRetrievable::No
);
1866 template bool ScriptSource::initializeUnretrievableUncompressedSource(
1867 FrontendContext
* fc
, EntryUnits
<Utf8Unit
>&& source
, size_t length
);
1868 template bool ScriptSource::initializeUnretrievableUncompressedSource(
1869 FrontendContext
* fc
, EntryUnits
<char16_t
>&& source
, size_t length
);
1871 // Format and return a cx->pod_malloc'ed URL for a generated script like:
1872 // {filename} line {lineno} > {introducer}
1874 // foo.js line 7 > eval
1875 // indicating code compiled by the call to 'eval' on line 7 of foo.js.
1876 UniqueChars
js::FormatIntroducedFilename(const char* filename
, uint32_t lineno
,
1877 const char* introducer
) {
1878 // Compute the length of the string in advance, so we can allocate a
1879 // buffer of the right size on the first shot.
1881 // (JS_smprintf would be perfect, as that allocates the result
1882 // dynamically as it formats the string, but it won't allocate from cx,
1883 // and wants us to use a special free function.)
1885 size_t filenameLen
= strlen(filename
);
1886 size_t linenoLen
= SprintfLiteral(linenoBuf
, "%u", lineno
);
1887 size_t introducerLen
= strlen(introducer
);
1888 size_t len
= filenameLen
+ 6 /* == strlen(" line ") */ + linenoLen
+
1889 3 /* == strlen(" > ") */ + introducerLen
+ 1 /* \0 */;
1890 UniqueChars
formatted(js_pod_malloc
<char>(len
));
1895 mozilla::DebugOnly
<size_t> checkLen
= snprintf(
1896 formatted
.get(), len
, "%s line %s > %s", filename
, linenoBuf
, introducer
);
1897 MOZ_ASSERT(checkLen
== len
- 1);
1902 bool ScriptSource::initFromOptions(FrontendContext
* fc
,
1903 const ReadOnlyCompileOptions
& options
) {
1904 MOZ_ASSERT(!filename_
);
1905 MOZ_ASSERT(!introducerFilename_
);
1907 mutedErrors_
= options
.mutedErrors();
1908 delazificationMode_
= options
.eagerDelazificationStrategy();
1910 startLine_
= options
.lineno
;
1911 startColumn_
= JS::LimitedColumnNumberOneOrigin::fromUnlimited(
1912 JS::ColumnNumberOneOrigin(options
.column
));
1913 introductionType_
= options
.introductionType
;
1914 setIntroductionOffset(options
.introductionOffset
);
1915 // The parameterListEnd_ is initialized later by setParameterListEnd, before
1916 // we expose any scripts that use this ScriptSource to the debugger.
1918 if (options
.hasIntroductionInfo
) {
1919 MOZ_ASSERT(options
.introductionType
!= nullptr);
1920 const char* filename
=
1921 options
.filename() ? options
.filename().c_str() : "<unknown>";
1922 UniqueChars formatted
= FormatIntroducedFilename(
1923 filename
, options
.introductionLineno
, options
.introductionType
);
1925 ReportOutOfMemory(fc
);
1928 if (!setFilename(fc
, std::move(formatted
))) {
1931 } else if (options
.filename()) {
1932 if (!setFilename(fc
, options
.filename().c_str())) {
1937 if (options
.introducerFilename()) {
1938 if (!setIntroducerFilename(fc
, options
.introducerFilename().c_str())) {
1946 // Use the SharedImmutableString map to deduplicate input string. The input
1947 // string must be null-terminated.
1948 template <typename SharedT
, typename CharT
>
1949 static SharedT
GetOrCreateStringZ(FrontendContext
* fc
,
1950 UniquePtr
<CharT
[], JS::FreePolicy
>&& str
) {
1951 size_t lengthWithNull
= std::char_traits
<CharT
>::length(str
.get()) + 1;
1952 auto res
= SharedImmutableStringsCache::getSingleton().getOrCreate(
1953 std::move(str
), lengthWithNull
);
1955 ReportOutOfMemory(fc
);
1960 SharedImmutableString
ScriptSource::getOrCreateStringZ(FrontendContext
* fc
,
1961 UniqueChars
&& str
) {
1962 return GetOrCreateStringZ
<SharedImmutableString
>(fc
, std::move(str
));
1965 SharedImmutableTwoByteString
ScriptSource::getOrCreateStringZ(
1966 FrontendContext
* fc
, UniqueTwoByteChars
&& str
) {
1967 return GetOrCreateStringZ
<SharedImmutableTwoByteString
>(fc
, std::move(str
));
1970 bool ScriptSource::setFilename(FrontendContext
* fc
, const char* filename
) {
1971 UniqueChars owned
= DuplicateString(fc
, filename
);
1975 return setFilename(fc
, std::move(owned
));
1978 bool ScriptSource::setFilename(FrontendContext
* fc
, UniqueChars
&& filename
) {
1979 MOZ_ASSERT(!filename_
);
1980 filename_
= getOrCreateStringZ(fc
, std::move(filename
));
1983 mozilla::HashStringKnownLength(filename_
.chars(), filename_
.length());
1989 bool ScriptSource::setIntroducerFilename(FrontendContext
* fc
,
1990 const char* filename
) {
1991 UniqueChars owned
= DuplicateString(fc
, filename
);
1995 return setIntroducerFilename(fc
, std::move(owned
));
1998 bool ScriptSource::setIntroducerFilename(FrontendContext
* fc
,
1999 UniqueChars
&& filename
) {
2000 MOZ_ASSERT(!introducerFilename_
);
2001 introducerFilename_
= getOrCreateStringZ(fc
, std::move(filename
));
2002 return bool(introducerFilename_
);
2005 bool ScriptSource::setDisplayURL(FrontendContext
* fc
, const char16_t
* url
) {
2006 UniqueTwoByteChars owned
= DuplicateString(fc
, url
);
2010 return setDisplayURL(fc
, std::move(owned
));
2013 bool ScriptSource::setDisplayURL(FrontendContext
* fc
,
2014 UniqueTwoByteChars
&& url
) {
2015 MOZ_ASSERT(!hasDisplayURL());
2017 if (url
[0] == '\0') {
2021 displayURL_
= getOrCreateStringZ(fc
, std::move(url
));
2022 return bool(displayURL_
);
2025 bool ScriptSource::setSourceMapURL(FrontendContext
* fc
, const char16_t
* url
) {
2026 UniqueTwoByteChars owned
= DuplicateString(fc
, url
);
2030 return setSourceMapURL(fc
, std::move(owned
));
2033 bool ScriptSource::setSourceMapURL(FrontendContext
* fc
,
2034 UniqueTwoByteChars
&& url
) {
2036 if (url
[0] == '\0') {
2040 sourceMapURL_
= getOrCreateStringZ(fc
, std::move(url
));
2041 return bool(sourceMapURL_
);
2044 /* static */ mozilla::Atomic
<uint32_t, mozilla::SequentiallyConsistent
>
2045 ScriptSource::idCount_
;
2048 * [SMDOC] JSScript data layout (immutable)
2050 * Script data that shareable across processes. There are no pointers (GC or
2051 * otherwise) and the data is relocatable.
2053 * Array elements Pointed to by Length
2054 * -------------- ------------- ------
2055 * jsbytecode code() codeLength()
2056 * jsscrnote notes() noteLength()
2057 * uint32_t resumeOffsets()
2058 * ScopeNote scopeNotes()
2059 * TryNote tryNotes()
2062 /* static */ CheckedInt
<uint32_t> ImmutableScriptData::sizeFor(
2063 uint32_t codeLength
, uint32_t noteLength
, uint32_t numResumeOffsets
,
2064 uint32_t numScopeNotes
, uint32_t numTryNotes
) {
2065 // Take a count of which optional arrays will be used and need offset info.
2066 unsigned numOptionalArrays
= unsigned(numResumeOffsets
> 0) +
2067 unsigned(numScopeNotes
> 0) +
2068 unsigned(numTryNotes
> 0);
2070 // Compute size including trailing arrays.
2071 CheckedInt
<uint32_t> size
= sizeof(ImmutableScriptData
);
2072 size
+= sizeof(Flags
);
2073 size
+= CheckedInt
<uint32_t>(codeLength
) * sizeof(jsbytecode
);
2074 size
+= CheckedInt
<uint32_t>(noteLength
) * sizeof(SrcNote
);
2075 size
+= CheckedInt
<uint32_t>(numOptionalArrays
) * sizeof(Offset
);
2076 size
+= CheckedInt
<uint32_t>(numResumeOffsets
) * sizeof(uint32_t);
2077 size
+= CheckedInt
<uint32_t>(numScopeNotes
) * sizeof(ScopeNote
);
2078 size
+= CheckedInt
<uint32_t>(numTryNotes
) * sizeof(TryNote
);
2083 js::UniquePtr
<ImmutableScriptData
> js::ImmutableScriptData::new_(
2084 FrontendContext
* fc
, uint32_t codeLength
, uint32_t noteLength
,
2085 uint32_t numResumeOffsets
, uint32_t numScopeNotes
, uint32_t numTryNotes
) {
2086 auto size
= sizeFor(codeLength
, noteLength
, numResumeOffsets
, numScopeNotes
,
2088 if (!size
.isValid()) {
2089 ReportAllocationOverflow(fc
);
2093 // Allocate contiguous raw buffer.
2094 void* raw
= fc
->getAllocator()->pod_malloc
<uint8_t>(size
.value());
2095 MOZ_ASSERT(uintptr_t(raw
) % alignof(ImmutableScriptData
) == 0);
2100 // Constuct the ImmutableScriptData. Trailing arrays are uninitialized but
2101 // GCPtrs are put into a safe state.
2102 UniquePtr
<ImmutableScriptData
> result(new (raw
) ImmutableScriptData(
2103 codeLength
, noteLength
, numResumeOffsets
, numScopeNotes
, numTryNotes
));
2109 MOZ_ASSERT(result
->endOffset() == size
.value());
2114 js::UniquePtr
<ImmutableScriptData
> js::ImmutableScriptData::new_(
2115 FrontendContext
* fc
, uint32_t totalSize
) {
2116 void* raw
= fc
->getAllocator()->pod_malloc
<uint8_t>(totalSize
);
2117 MOZ_ASSERT(uintptr_t(raw
) % alignof(ImmutableScriptData
) == 0);
2118 UniquePtr
<ImmutableScriptData
> result(
2119 reinterpret_cast<ImmutableScriptData
*>(raw
));
2123 bool js::ImmutableScriptData::validateLayout(uint32_t expectedSize
) {
2124 constexpr size_t HeaderSize
= sizeof(js::ImmutableScriptData
);
2125 constexpr size_t OptionalOffsetsMaxSize
= 3 * sizeof(Offset
);
2127 // Check that the optional-offsets array lies within the allocation before we
2128 // try to read from it while computing sizes. Remember that the array *ends*
2129 // at the `optArrayOffset_`.
2130 static_assert(OptionalOffsetsMaxSize
<= HeaderSize
);
2131 if (HeaderSize
> optArrayOffset_
) {
2134 if (optArrayOffset_
> expectedSize
) {
2138 // Round-trip the size computation using `CheckedInt` to detect overflow. This
2139 // should indirectly validate most alignment, size, and ordering requirments.
2140 auto size
= sizeFor(codeLength(), noteLength(), resumeOffsets().size(),
2141 scopeNotes().size(), tryNotes().size());
2142 return size
.isValid() && (size
.value() == expectedSize
);
2146 SharedImmutableScriptData
* SharedImmutableScriptData::create(
2147 FrontendContext
* fc
) {
2148 return fc
->getAllocator()->new_
<SharedImmutableScriptData
>();
2152 SharedImmutableScriptData
* SharedImmutableScriptData::createWith(
2153 FrontendContext
* fc
, js::UniquePtr
<ImmutableScriptData
>&& isd
) {
2154 MOZ_ASSERT(isd
.get());
2155 SharedImmutableScriptData
* sisd
= create(fc
);
2160 sisd
->setOwn(std::move(isd
));
2164 void JSScript::relazify(JSRuntime
* rt
) {
2165 js::Scope
* scope
= enclosingScope();
2166 UniquePtr
<PrivateScriptData
> scriptData
;
2168 // Any JIT compiles should have been released, so we already point to the
2169 // interpreter trampoline which supports lazy scripts.
2170 MOZ_ASSERT_IF(jit::HasJitBackend(), isUsingInterpreterTrampoline(rt
));
2172 // Without bytecode, the script counts are invalid so destroy them if they
2174 destroyScriptCounts();
2176 // Release the bytecode and gcthings list.
2177 // NOTE: We clear the PrivateScriptData to nullptr. This is fine because we
2178 // only allowed relazification (via AllowRelazify) if the original lazy
2179 // script we compiled from had a nullptr PrivateScriptData.
2180 swapData(scriptData
);
2183 // We should not still be in any side-tables for the debugger or
2184 // code-coverage. The finalizer will not be able to clean them up once
2185 // bytecode is released. We check in JSFunction::maybeRelazify() for these
2186 // conditions before requesting relazification.
2187 MOZ_ASSERT(!coverage::IsLCovEnabled());
2188 MOZ_ASSERT(!hasScriptCounts());
2189 MOZ_ASSERT(!hasDebugScript());
2191 // Rollback warmUpData_ to have enclosingScope.
2192 MOZ_ASSERT(warmUpData_
.isWarmUpCount(),
2193 "JitScript should already be released");
2194 warmUpData_
.resetWarmUpCount(0);
2195 warmUpData_
.initEnclosingScope(scope
);
2197 MOZ_ASSERT(isReadyForDelazification());
2200 // Takes ownership of the passed SharedImmutableScriptData and either adds it
2201 // into the runtime's SharedImmutableScriptDataTable, or frees it if a matching
2202 // entry already exists and replaces the passed RefPtr with the existing entry.
2204 bool SharedImmutableScriptData::shareScriptData(
2205 FrontendContext
* fc
, RefPtr
<SharedImmutableScriptData
>& sisd
) {
2207 MOZ_ASSERT(sisd
->refCount() == 1);
2209 SharedImmutableScriptData
* data
= sisd
.get();
2211 SharedImmutableScriptData::Hasher::Lookup
lookup(data
);
2213 Maybe
<AutoLockGlobalScriptData
> lock
;
2214 js::SharedImmutableScriptDataTable
& table
=
2215 fc
->scriptDataTableHolder()->getMaybeLocked(lock
);
2217 SharedImmutableScriptDataTable::AddPtr p
= table
.lookupForAdd(lookup
);
2219 MOZ_ASSERT(data
!= *p
);
2222 if (!table
.add(p
, data
)) {
2223 ReportOutOfMemory(fc
);
2227 // Being in the table counts as a reference on the script data.
2231 // Refs: sisd argument, SharedImmutableScriptDataTable
2232 MOZ_ASSERT(sisd
->refCount() >= 2);
2237 static void SweepScriptDataTable(SharedImmutableScriptDataTable
& table
) {
2238 // Entries are removed from the table when their reference count is one,
2239 // i.e. when the only reference to them is from the table entry.
2241 for (SharedImmutableScriptDataTable::Enum
e(table
); !e
.empty();
2243 SharedImmutableScriptData
* sharedData
= e
.front();
2244 if (sharedData
->refCount() == 1) {
2245 sharedData
->Release();
2251 void js::SweepScriptData(JSRuntime
* rt
) {
2252 SweepScriptDataTable(rt
->scriptDataTableHolder().getWithoutLock());
2254 AutoLockGlobalScriptData lock
;
2255 SweepScriptDataTable(js::globalSharedScriptDataTableHolder
.get(lock
));
2258 inline size_t PrivateScriptData::allocationSize() const { return endOffset(); }
2260 // Initialize and placement-new the trailing arrays.
2261 PrivateScriptData::PrivateScriptData(uint32_t ngcthings
)
2262 : ngcthings(ngcthings
) {
2263 // Variable-length data begins immediately after PrivateScriptData itself.
2264 // NOTE: Alignment is computed using cursor/offset so the alignment of
2265 // PrivateScriptData must be stricter than any trailing array type.
2266 Offset cursor
= sizeof(PrivateScriptData
);
2268 // Layout and initialize the gcthings array.
2270 initElements
<JS::GCCellPtr
>(cursor
, ngcthings
);
2271 cursor
+= ngcthings
* sizeof(JS::GCCellPtr
);
2275 MOZ_ASSERT(endOffset() == cursor
);
2279 PrivateScriptData
* PrivateScriptData::new_(JSContext
* cx
, uint32_t ngcthings
) {
2280 // Compute size including trailing arrays.
2281 CheckedInt
<Offset
> size
= sizeof(PrivateScriptData
);
2282 size
+= CheckedInt
<Offset
>(ngcthings
) * sizeof(JS::GCCellPtr
);
2283 if (!size
.isValid()) {
2284 ReportAllocationOverflow(cx
);
2288 // Allocate contiguous raw buffer for the trailing arrays.
2289 void* raw
= cx
->pod_malloc
<uint8_t>(size
.value());
2290 MOZ_ASSERT(uintptr_t(raw
) % alignof(PrivateScriptData
) == 0);
2295 // Constuct the PrivateScriptData. Trailing arrays are uninitialized but
2296 // GCPtrs are put into a safe state.
2297 PrivateScriptData
* result
= new (raw
) PrivateScriptData(ngcthings
);
2303 MOZ_ASSERT(result
->endOffset() == size
.value());
2309 bool PrivateScriptData::InitFromStencil(
2310 JSContext
* cx
, js::HandleScript script
,
2311 const js::frontend::CompilationAtomCache
& atomCache
,
2312 const js::frontend::CompilationStencil
& stencil
,
2313 js::frontend::CompilationGCOutput
& gcOutput
,
2314 const js::frontend::ScriptIndex scriptIndex
) {
2315 js::frontend::ScriptStencil
& scriptStencil
= stencil
.scriptData
[scriptIndex
];
2316 uint32_t ngcthings
= scriptStencil
.gcThingsLength
;
2318 MOZ_ASSERT(ngcthings
<= INDEX_LIMIT
);
2320 // Create and initialize PrivateScriptData
2321 if (!JSScript::createPrivateScriptData(cx
, script
, ngcthings
)) {
2325 js::PrivateScriptData
* data
= script
->data_
;
2327 if (!EmitScriptThingsVector(cx
, atomCache
, stencil
, gcOutput
,
2328 scriptStencil
.gcthings(stencil
),
2329 data
->gcthings())) {
2337 void PrivateScriptData::trace(JSTracer
* trc
) {
2338 for (JS::GCCellPtr
& elem
: gcthings()) {
2339 TraceManuallyBarrieredGCCellPtr(trc
, &elem
, "script-gcthing");
2344 JSScript
* JSScript::Create(JSContext
* cx
, JS::Handle
<JSFunction
*> function
,
2345 js::Handle
<ScriptSourceObject
*> sourceObject
,
2346 const SourceExtent
& extent
,
2347 js::ImmutableScriptFlags flags
) {
2348 return static_cast<JSScript
*>(
2349 BaseScript::New(cx
, function
, sourceObject
, extent
, flags
));
2353 uint32_t JSScript::vtuneMethodID() {
2354 if (!zone()->scriptVTuneIdMap
) {
2355 auto map
= MakeUnique
<ScriptVTuneIdMap
>();
2357 MOZ_CRASH("Failed to allocate ScriptVTuneIdMap");
2360 zone()->scriptVTuneIdMap
= std::move(map
);
2363 ScriptVTuneIdMap::AddPtr p
= zone()->scriptVTuneIdMap
->lookupForAdd(this);
2368 MOZ_ASSERT(this->hasBytecode());
2370 uint32_t id
= vtune::GenerateUniqueMethodID();
2371 if (!zone()->scriptVTuneIdMap
->add(p
, this, id
)) {
2372 MOZ_CRASH("Failed to add vtune method id");
2380 bool JSScript::createPrivateScriptData(JSContext
* cx
, HandleScript script
,
2381 uint32_t ngcthings
) {
2384 UniquePtr
<PrivateScriptData
> data(PrivateScriptData::new_(cx
, ngcthings
));
2389 script
->swapData(data
);
2396 bool JSScript::fullyInitFromStencil(
2397 JSContext
* cx
, const js::frontend::CompilationAtomCache
& atomCache
,
2398 const js::frontend::CompilationStencil
& stencil
,
2399 frontend::CompilationGCOutput
& gcOutput
, HandleScript script
,
2400 const js::frontend::ScriptIndex scriptIndex
) {
2401 MutableScriptFlags lazyMutableFlags
;
2402 Rooted
<Scope
*> lazyEnclosingScope(cx
);
2404 // A holder for the lazy PrivateScriptData that we must keep around in case
2405 // this process fails and we must return the script to its original state.
2407 // This is initialized by BaseScript::swapData() which will run pre-barriers
2408 // for us. On successful conversion to non-lazy script, the old script data
2409 // here will be released by the UniquePtr.
2410 Rooted
<UniquePtr
<PrivateScriptData
>> lazyData(cx
);
2412 // Whether we are a newborn script or an existing lazy script, we should
2413 // already be pointing to the interpreter trampoline.
2414 MOZ_ASSERT_IF(jit::HasJitBackend(),
2415 script
->isUsingInterpreterTrampoline(cx
->runtime()));
2417 // If we are using an existing lazy script, record enough info to be able to
2418 // rollback on failure.
2419 if (script
->isReadyForDelazification()) {
2420 lazyMutableFlags
= script
->mutableFlags_
;
2421 lazyEnclosingScope
= script
->releaseEnclosingScope();
2422 script
->swapData(lazyData
.get());
2423 MOZ_ASSERT(script
->sharedData_
== nullptr);
2426 // Restore the script to lazy state on failure. If this was a fresh script, we
2427 // just need to clear bytecode to mark script as incomplete.
2428 auto rollbackGuard
= mozilla::MakeScopeExit([&] {
2429 if (lazyEnclosingScope
) {
2430 script
->mutableFlags_
= lazyMutableFlags
;
2431 script
->warmUpData_
.initEnclosingScope(lazyEnclosingScope
);
2432 script
->swapData(lazyData
.get());
2433 script
->sharedData_
= nullptr;
2435 MOZ_ASSERT(script
->isReadyForDelazification());
2437 script
->sharedData_
= nullptr;
2441 // The counts of indexed things must be checked during code generation.
2442 MOZ_ASSERT(stencil
.scriptData
[scriptIndex
].gcThingsLength
<= INDEX_LIMIT
);
2444 // Note: These flags should already be correct when the BaseScript was
2446 MOZ_ASSERT_IF(stencil
.isInitialStencil(),
2447 script
->immutableFlags() ==
2448 stencil
.scriptExtra
[scriptIndex
].immutableFlags
);
2450 // Create and initialize PrivateScriptData
2451 if (!PrivateScriptData::InitFromStencil(cx
, script
, atomCache
, stencil
,
2452 gcOutput
, scriptIndex
)) {
2456 // Member-initializer data is computed in initial parse only. If we are
2457 // delazifying, make sure to copy it off the `lazyData` before we throw it
2459 if (script
->useMemberInitializers()) {
2460 if (stencil
.isInitialStencil()) {
2461 MemberInitializers
initializers(
2462 stencil
.scriptExtra
[scriptIndex
].memberInitializers());
2463 script
->setMemberInitializers(initializers
);
2465 script
->setMemberInitializers(lazyData
.get()->getMemberInitializers());
2468 auto* scriptData
= stencil
.sharedData
.get(scriptIndex
);
2469 script
->initSharedData(scriptData
);
2471 // NOTE: JSScript is now constructed and should be linked in.
2472 rollbackGuard
.release();
2474 // Link Scope -> JSFunction -> BaseScript.
2475 if (script
->isFunction()) {
2476 JSFunction
* fun
= gcOutput
.getFunction(scriptIndex
);
2477 script
->bodyScope()->as
<FunctionScope
>().initCanonicalFunction(fun
);
2478 if (fun
->isIncomplete()) {
2479 fun
->initScript(script
);
2480 } else if (fun
->hasSelfHostedLazyScript()) {
2481 fun
->clearSelfHostedLazyScript();
2482 fun
->initScript(script
);
2484 // We are delazifying in-place.
2485 MOZ_ASSERT(fun
->baseScript() == script
);
2489 // NOTE: The caller is responsible for linking ModuleObjects if this is a
2492 #ifdef JS_STRUCTURED_SPEW
2493 // We want this to happen after line number initialization to allow filtering
2495 script
->setSpewEnabled(cx
->spewer().enabled(script
));
2499 script
->assertValidJumpTargets();
2502 if (coverage::IsLCovEnabled()) {
2503 if (!coverage::InitScriptCoverage(cx
, script
)) {
2511 JSScript
* JSScript::fromStencil(JSContext
* cx
,
2512 frontend::CompilationAtomCache
& atomCache
,
2513 const frontend::CompilationStencil
& stencil
,
2514 frontend::CompilationGCOutput
& gcOutput
,
2515 frontend::ScriptIndex scriptIndex
) {
2516 js::frontend::ScriptStencil
& scriptStencil
= stencil
.scriptData
[scriptIndex
];
2517 js::frontend::ScriptStencilExtra
& scriptExtra
=
2518 stencil
.scriptExtra
[scriptIndex
];
2519 MOZ_ASSERT(scriptStencil
.hasSharedData(),
2520 "Need generated bytecode to use JSScript::fromStencil");
2522 Rooted
<JSFunction
*> function(cx
);
2523 if (scriptStencil
.isFunction()) {
2524 function
= gcOutput
.getFunction(scriptIndex
);
2527 Rooted
<ScriptSourceObject
*> sourceObject(cx
, gcOutput
.sourceObject
);
2528 RootedScript
script(cx
, Create(cx
, function
, sourceObject
, scriptExtra
.extent
,
2529 scriptExtra
.immutableFlags
));
2534 if (!fullyInitFromStencil(cx
, atomCache
, stencil
, gcOutput
, script
,
2543 void JSScript::assertValidJumpTargets() const {
2544 BytecodeLocation mainLoc
= mainLocation();
2545 BytecodeLocation endLoc
= endLocation();
2546 AllBytecodesIterable
iter(this);
2547 for (BytecodeLocation loc
: iter
) {
2548 // Check jump instructions' target.
2550 BytecodeLocation target
= loc
.getJumpTarget();
2551 MOZ_ASSERT(mainLoc
<= target
&& target
< endLoc
);
2552 MOZ_ASSERT(target
.isJumpTarget());
2554 // All backward jumps must be to a JSOp::LoopHead op. This is an invariant
2555 // we want to maintain to simplify JIT compilation and bytecode analysis.
2556 MOZ_ASSERT_IF(target
< loc
, target
.is(JSOp::LoopHead
));
2557 MOZ_ASSERT_IF(target
< loc
, IsBackedgePC(loc
.toRawBytecode()));
2559 // All forward jumps must be to a JSOp::JumpTarget op.
2560 MOZ_ASSERT_IF(target
> loc
, target
.is(JSOp::JumpTarget
));
2562 // Jumps must not cross scope boundaries.
2563 MOZ_ASSERT(loc
.innermostScope(this) == target
.innermostScope(this));
2565 // Check fallthrough of conditional jump instructions.
2566 if (loc
.fallsThrough()) {
2567 BytecodeLocation fallthrough
= loc
.next();
2568 MOZ_ASSERT(mainLoc
<= fallthrough
&& fallthrough
< endLoc
);
2569 MOZ_ASSERT(fallthrough
.isJumpTarget());
2573 // Check table switch case labels.
2574 if (loc
.is(JSOp::TableSwitch
)) {
2575 BytecodeLocation target
= loc
.getTableSwitchDefaultTarget();
2578 MOZ_ASSERT(mainLoc
<= target
&& target
< endLoc
);
2579 MOZ_ASSERT(target
.is(JSOp::JumpTarget
));
2581 int32_t low
= loc
.getTableSwitchLow();
2582 int32_t high
= loc
.getTableSwitchHigh();
2584 for (int i
= 0; i
< high
- low
+ 1; i
++) {
2585 BytecodeLocation switchCase
= loc
.getTableSwitchCaseTarget(this, i
);
2586 MOZ_ASSERT(mainLoc
<= switchCase
&& switchCase
< endLoc
);
2587 MOZ_ASSERT(switchCase
.is(JSOp::JumpTarget
));
2592 // Check catch/finally blocks as jump targets.
2593 for (const TryNote
& tn
: trynotes()) {
2594 if (tn
.kind() != TryNoteKind::Catch
&& tn
.kind() != TryNoteKind::Finally
) {
2598 jsbytecode
* tryStart
= offsetToPC(tn
.start
);
2599 jsbytecode
* tryPc
= tryStart
- JSOpLength_Try
;
2600 MOZ_ASSERT(JSOp(*tryPc
) == JSOp::Try
);
2602 jsbytecode
* tryTarget
= tryStart
+ tn
.length
;
2603 MOZ_ASSERT(main() <= tryTarget
&& tryTarget
< codeEnd());
2604 MOZ_ASSERT(BytecodeIsJumpTarget(JSOp(*tryTarget
)));
2609 void JSScript::addSizeOfJitScript(mozilla::MallocSizeOf mallocSizeOf
,
2610 size_t* sizeOfJitScript
,
2611 size_t* sizeOfAllocSites
) const {
2612 if (!hasJitScript()) {
2616 jitScript()->addSizeOfIncludingThis(mallocSizeOf
, sizeOfJitScript
,
2620 js::GlobalObject
& JSScript::uninlinedGlobal() const { return global(); }
2622 unsigned js::PCToLineNumber(unsigned startLine
,
2623 JS::LimitedColumnNumberOneOrigin startCol
,
2624 SrcNote
* notes
, SrcNote
* notesEnd
, jsbytecode
* code
,
2626 JS::LimitedColumnNumberOneOrigin
* columnp
) {
2627 unsigned lineno
= startLine
;
2628 JS::LimitedColumnNumberOneOrigin column
= startCol
;
2631 * Walk through source notes accumulating their deltas, keeping track of
2632 * line-number notes, until we pass the note for pc's offset within
2635 ptrdiff_t offset
= 0;
2636 ptrdiff_t target
= pc
- code
;
2637 for (SrcNoteIterator
iter(notes
, notesEnd
); !iter
.atEnd(); ++iter
) {
2638 const auto* sn
= *iter
;
2639 offset
+= sn
->delta();
2640 if (offset
> target
) {
2644 SrcNoteType type
= sn
->type();
2645 if (type
== SrcNoteType::SetLine
) {
2646 lineno
= SrcNote::SetLine::getLine(sn
, startLine
);
2647 column
= JS::LimitedColumnNumberOneOrigin();
2648 } else if (type
== SrcNoteType::SetLineColumn
) {
2649 lineno
= SrcNote::SetLineColumn::getLine(sn
, startLine
);
2650 column
= SrcNote::SetLineColumn::getColumn(sn
);
2651 } else if (type
== SrcNoteType::NewLine
) {
2653 column
= JS::LimitedColumnNumberOneOrigin();
2654 } else if (type
== SrcNoteType::NewLineColumn
) {
2656 column
= SrcNote::NewLineColumn::getColumn(sn
);
2657 } else if (type
== SrcNoteType::ColSpan
) {
2658 column
+= SrcNote::ColSpan::getSpan(sn
);
2669 unsigned js::PCToLineNumber(JSScript
* script
, jsbytecode
* pc
,
2670 JS::LimitedColumnNumberOneOrigin
* columnp
) {
2671 /* Cope with InterpreterFrame.pc value prior to entering Interpret. */
2676 return PCToLineNumber(
2677 script
->lineno(), JS::LimitedColumnNumberOneOrigin(script
->column()),
2678 script
->notes(), script
->notesEnd(), script
->code(), pc
, columnp
);
2681 jsbytecode
* js::LineNumberToPC(JSScript
* script
, unsigned target
) {
2682 ptrdiff_t offset
= 0;
2683 ptrdiff_t best
= -1;
2684 unsigned lineno
= script
->lineno();
2685 unsigned bestdiff
= SrcNote::MaxOperand
;
2686 for (SrcNoteIterator
iter(script
->notes(), script
->notesEnd()); !iter
.atEnd();
2688 const auto* sn
= *iter
;
2690 * Exact-match only if offset is not in the prologue; otherwise use
2691 * nearest greater-or-equal line number match.
2693 if (lineno
== target
&& offset
>= ptrdiff_t(script
->mainOffset())) {
2696 if (lineno
>= target
) {
2697 unsigned diff
= lineno
- target
;
2698 if (diff
< bestdiff
) {
2703 offset
+= sn
->delta();
2704 SrcNoteType type
= sn
->type();
2705 if (type
== SrcNoteType::SetLine
) {
2706 lineno
= SrcNote::SetLine::getLine(sn
, script
->lineno());
2707 } else if (type
== SrcNoteType::SetLineColumn
) {
2708 lineno
= SrcNote::SetLineColumn::getLine(sn
, script
->lineno());
2709 } else if (type
== SrcNoteType::NewLine
||
2710 type
== SrcNoteType::NewLineColumn
) {
2718 return script
->offsetToPC(offset
);
2721 JS_PUBLIC_API
unsigned js::GetScriptLineExtent(JSScript
* script
) {
2722 unsigned lineno
= script
->lineno();
2723 unsigned maxLineNo
= lineno
;
2724 for (SrcNoteIterator
iter(script
->notes(), script
->notesEnd()); !iter
.atEnd();
2726 const auto* sn
= *iter
;
2727 SrcNoteType type
= sn
->type();
2728 if (type
== SrcNoteType::SetLine
) {
2729 lineno
= SrcNote::SetLine::getLine(sn
, script
->lineno());
2730 } else if (type
== SrcNoteType::SetLineColumn
) {
2731 lineno
= SrcNote::SetLineColumn::getLine(sn
, script
->lineno());
2732 } else if (type
== SrcNoteType::NewLine
||
2733 type
== SrcNoteType::NewLineColumn
) {
2737 if (maxLineNo
< lineno
) {
2742 return 1 + maxLineNo
- script
->lineno();
2745 #ifdef JS_CACHEIR_SPEW
2746 void js::maybeUpdateWarmUpCount(JSScript
* script
) {
2747 if (script
->needsFinalWarmUpCount()) {
2748 ScriptFinalWarmUpCountMap
* map
=
2749 script
->zone()->scriptFinalWarmUpCountMap
.get();
2750 // If needsFinalWarmUpCount is true, ScriptFinalWarmUpCountMap must have
2751 // already been created and thus must be asserted.
2753 ScriptFinalWarmUpCountMap::Ptr p
= map
->lookup(script
);
2756 std::get
<0>(p
->value()) += script
->jitScript()->warmUpCount();
2760 void js::maybeSpewScriptFinalWarmUpCount(JSScript
* script
) {
2761 if (script
->needsFinalWarmUpCount()) {
2762 ScriptFinalWarmUpCountMap
* map
=
2763 script
->zone()->scriptFinalWarmUpCountMap
.get();
2764 // If needsFinalWarmUpCount is true, ScriptFinalWarmUpCountMap must have
2765 // already been created and thus must be asserted.
2767 ScriptFinalWarmUpCountMap::Ptr p
= map
->lookup(script
);
2769 auto& tuple
= p
->value();
2770 uint32_t warmUpCount
= std::get
<0>(tuple
);
2771 SharedImmutableString
& scriptName
= std::get
<1>(tuple
);
2773 JSContext
* cx
= TlsContext
.get();
2774 cx
->spewer().enableSpewing();
2776 // In the case that we care about a script's final warmup count but the
2777 // spewer is not enabled, AutoSpewChannel automatically sets and unsets
2778 // the proper channel for the duration of spewing a health report's warm
2780 AutoSpewChannel
channel(cx
, SpewChannel::CacheIRHealthReport
, script
);
2781 jit::CacheIRHealth cih
;
2782 cih
.spewScriptFinalWarmUpCount(cx
, scriptName
.chars(), script
, warmUpCount
);
2784 script
->zone()->scriptFinalWarmUpCountMap
->remove(script
);
2785 script
->setNeedsFinalWarmUpCount(false);
2790 void js::DescribeScriptedCallerForDirectEval(JSContext
* cx
, HandleScript script
,
2791 jsbytecode
* pc
, const char** file
,
2794 bool* mutedErrors
) {
2795 MOZ_ASSERT(script
->containsPC(pc
));
2797 static_assert(JSOpLength_SpreadEval
== JSOpLength_StrictSpreadEval
,
2798 "next op after a spread must be at consistent offset");
2799 static_assert(JSOpLength_Eval
== JSOpLength_StrictEval
,
2800 "next op after a direct eval must be at consistent offset");
2802 MOZ_ASSERT(JSOp(*pc
) == JSOp::Eval
|| JSOp(*pc
) == JSOp::StrictEval
||
2803 JSOp(*pc
) == JSOp::SpreadEval
||
2804 JSOp(*pc
) == JSOp::StrictSpreadEval
);
2807 (JSOp(*pc
) == JSOp::SpreadEval
|| JSOp(*pc
) == JSOp::StrictSpreadEval
);
2808 jsbytecode
* nextpc
=
2809 pc
+ (isSpread
? JSOpLength_SpreadEval
: JSOpLength_Eval
);
2810 MOZ_ASSERT(JSOp(*nextpc
) == JSOp::Lineno
);
2812 *file
= script
->filename();
2813 *linenop
= GET_UINT32(nextpc
);
2814 *pcOffset
= script
->pcToOffset(pc
);
2815 *mutedErrors
= script
->mutedErrors();
2818 void js::DescribeScriptedCallerForCompilation(
2819 JSContext
* cx
, MutableHandleScript maybeScript
, const char** file
,
2820 uint32_t* linenop
, uint32_t* pcOffset
, bool* mutedErrors
) {
2821 NonBuiltinFrameIter
iter(cx
, cx
->realm()->principals());
2824 maybeScript
.set(nullptr);
2828 *mutedErrors
= false;
2832 *file
= iter
.filename();
2833 *linenop
= iter
.computeLine();
2834 *mutedErrors
= iter
.mutedErrors();
2836 // These values are only used for introducer fields which are debugging
2837 // information and can be safely left null for wasm frames.
2838 if (iter
.hasScript()) {
2839 maybeScript
.set(iter
.script());
2840 *pcOffset
= iter
.pc() - maybeScript
->code();
2842 maybeScript
.set(nullptr);
2847 template <typename SourceSpan
, typename TargetSpan
>
2848 void CopySpan(const SourceSpan
& source
, TargetSpan target
) {
2849 MOZ_ASSERT(source
.size() == target
.size());
2850 std::copy(source
.cbegin(), source
.cend(), target
.begin());
2854 js::UniquePtr
<ImmutableScriptData
> ImmutableScriptData::new_(
2855 FrontendContext
* fc
, uint32_t mainOffset
, uint32_t nfixed
, uint32_t nslots
,
2856 GCThingIndex bodyScopeIndex
, uint32_t numICEntries
, bool isFunction
,
2857 uint16_t funLength
, uint16_t propertyCountEstimate
,
2858 mozilla::Span
<const jsbytecode
> code
, mozilla::Span
<const SrcNote
> notes
,
2859 mozilla::Span
<const uint32_t> resumeOffsets
,
2860 mozilla::Span
<const ScopeNote
> scopeNotes
,
2861 mozilla::Span
<const TryNote
> tryNotes
) {
2862 MOZ_RELEASE_ASSERT(code
.Length() <= frontend::MaxBytecodeLength
);
2864 // There are 1-4 copies of SrcNoteType::Null appended after the source
2865 // notes. These are a combination of sentinel and padding values.
2866 static_assert(frontend::MaxSrcNotesLength
<= UINT32_MAX
- CodeNoteAlign
,
2867 "Length + CodeNoteAlign shouldn't overflow UINT32_MAX");
2868 size_t noteLength
= notes
.Length();
2869 MOZ_RELEASE_ASSERT(noteLength
<= frontend::MaxSrcNotesLength
);
2871 size_t notePaddingLength
= ComputeNotePadding(code
.Length(), noteLength
);
2873 // Allocate ImmutableScriptData
2874 js::UniquePtr
<ImmutableScriptData
> data(ImmutableScriptData::new_(
2875 fc
, code
.Length(), noteLength
+ notePaddingLength
, resumeOffsets
.Length(),
2876 scopeNotes
.Length(), tryNotes
.Length()));
2881 // Initialize POD fields
2882 data
->mainOffset
= mainOffset
;
2883 data
->nfixed
= nfixed
;
2884 data
->nslots
= nslots
;
2885 data
->bodyScopeIndex
= bodyScopeIndex
;
2886 data
->numICEntries
= numICEntries
;
2887 data
->propertyCountEstimate
= propertyCountEstimate
;
2890 data
->funLength
= funLength
;
2893 // Initialize trailing arrays
2894 CopySpan(code
, data
->codeSpan());
2895 CopySpan(notes
, data
->notesSpan().To(noteLength
));
2896 std::fill_n(data
->notes() + noteLength
, notePaddingLength
,
2897 SrcNote::padding());
2898 CopySpan(resumeOffsets
, data
->resumeOffsets());
2899 CopySpan(scopeNotes
, data
->scopeNotes());
2900 CopySpan(tryNotes
, data
->tryNotes());
2905 void ScriptWarmUpData::trace(JSTracer
* trc
) {
2906 uintptr_t tag
= data_
& TagMask
;
2908 case EnclosingScriptTag
: {
2909 BaseScript
* enclosingScript
= toEnclosingScript();
2910 BaseScript
* prior
= enclosingScript
;
2911 TraceManuallyBarrieredEdge(trc
, &enclosingScript
, "enclosingScript");
2912 if (enclosingScript
!= prior
) {
2913 setTaggedPtr
<EnclosingScriptTag
>(enclosingScript
);
2918 case EnclosingScopeTag
: {
2919 Scope
* enclosingScope
= toEnclosingScope();
2920 Scope
* prior
= enclosingScope
;
2921 TraceManuallyBarrieredEdge(trc
, &enclosingScope
, "enclosingScope");
2922 if (enclosingScope
!= prior
) {
2923 setTaggedPtr
<EnclosingScopeTag
>(enclosingScope
);
2928 case JitScriptTag
: {
2929 toJitScript()->trace(trc
);
2934 MOZ_ASSERT(isWarmUpCount());
2940 size_t JSScript::calculateLiveFixed(jsbytecode
* pc
) {
2941 size_t nlivefixed
= numAlwaysLiveFixedSlots();
2943 if (nfixed() != nlivefixed
) {
2944 Scope
* scope
= lookupScope(pc
);
2946 scope
= MaybeForwarded(scope
);
2949 // Find the nearest LexicalScope in the same script.
2950 while (scope
&& scope
->is
<WithScope
>()) {
2951 scope
= scope
->enclosing();
2953 scope
= MaybeForwarded(scope
);
2958 if (scope
->is
<LexicalScope
>()) {
2959 nlivefixed
= scope
->as
<LexicalScope
>().nextFrameSlot();
2960 } else if (scope
->is
<VarScope
>()) {
2961 nlivefixed
= scope
->as
<VarScope
>().nextFrameSlot();
2962 } else if (scope
->is
<ClassBodyScope
>()) {
2963 nlivefixed
= scope
->as
<ClassBodyScope
>().nextFrameSlot();
2968 MOZ_ASSERT(nlivefixed
<= nfixed());
2969 MOZ_ASSERT(nlivefixed
>= numAlwaysLiveFixedSlots());
2974 Scope
* JSScript::lookupScope(const jsbytecode
* pc
) const {
2975 MOZ_ASSERT(containsPC(pc
));
2977 size_t offset
= pc
- code();
2979 auto notes
= scopeNotes();
2980 Scope
* scope
= nullptr;
2982 // Find the innermost block chain using a binary search.
2984 size_t top
= notes
.size();
2986 while (bottom
< top
) {
2987 size_t mid
= bottom
+ (top
- bottom
) / 2;
2988 const ScopeNote
* note
= ¬es
[mid
];
2989 if (note
->start
<= offset
) {
2990 // Block scopes are ordered in the list by their starting offset, and
2991 // since blocks form a tree ones earlier in the list may cover the pc even
2992 // if later blocks end before the pc. This only happens when the earlier
2993 // block is a parent of the later block, so we need to check parents of
2994 // |mid| in the searched range for coverage.
2996 while (check
>= bottom
) {
2997 const ScopeNote
* checkNote
= ¬es
[check
];
2998 MOZ_ASSERT(checkNote
->start
<= offset
);
2999 if (offset
< checkNote
->start
+ checkNote
->length
) {
3000 // We found a matching block chain but there may be inner ones
3001 // at a higher block chain index than mid. Continue the binary search.
3002 if (checkNote
->index
== ScopeNote::NoScopeIndex
) {
3005 scope
= getScope(checkNote
->index
);
3009 if (checkNote
->parent
== UINT32_MAX
) {
3012 check
= checkNote
->parent
;
3023 Scope
* JSScript::innermostScope(const jsbytecode
* pc
) const {
3024 if (Scope
* scope
= lookupScope(pc
)) {
3030 void js::SetFrameArgumentsObject(JSContext
* cx
, AbstractFramePtr frame
,
3031 HandleScript script
, JSObject
* argsobj
) {
3033 * If the arguments object was optimized out by scalar replacement,
3034 * we must recreate it when we bail out. Because 'arguments' may have
3035 * already been overwritten, we must check to see if the slot already
3039 Rooted
<BindingIter
> bi(cx
, BindingIter(script
));
3040 while (bi
&& bi
.name() != cx
->names().arguments
) {
3047 if (bi
.location().kind() == BindingLocation::Kind::Environment
) {
3050 * If |arguments| lives in the call object, we should not have
3051 * optimized it. Scan the script to find the slot in the call
3052 * object that |arguments| is assigned to and verify that it
3055 jsbytecode
* pc
= script
->code();
3056 while (JSOp(*pc
) != JSOp::Arguments
) {
3057 pc
+= GetBytecodeLength(pc
);
3059 pc
+= JSOpLength_Arguments
;
3060 MOZ_ASSERT(JSOp(*pc
) == JSOp::SetAliasedVar
);
3062 EnvironmentObject
& env
= frame
.callObj().as
<EnvironmentObject
>();
3063 MOZ_ASSERT(!env
.aliasedBinding(bi
).isMagic(JS_OPTIMIZED_OUT
));
3068 MOZ_ASSERT(bi
.location().kind() == BindingLocation::Kind::Frame
);
3069 uint32_t frameSlot
= bi
.location().slot();
3070 if (frame
.unaliasedLocal(frameSlot
).isMagic(JS_OPTIMIZED_OUT
)) {
3071 frame
.unaliasedLocal(frameSlot
) = ObjectValue(*argsobj
);
3075 bool JSScript::formalIsAliased(unsigned argSlot
) {
3076 if (functionHasParameterExprs()) {
3080 for (PositionalFormalParameterIter
fi(this); fi
; fi
++) {
3081 if (fi
.argumentSlot() == argSlot
) {
3082 return fi
.closedOver();
3085 MOZ_CRASH("Argument slot not found");
3088 // Returns true if any formal argument is mapped by the arguments
3089 // object, but lives in the call object.
3090 bool JSScript::anyFormalIsForwarded() {
3091 if (!argsObjAliasesFormals()) {
3095 for (PositionalFormalParameterIter
fi(this); fi
; fi
++) {
3096 if (fi
.closedOver()) {
3103 bool JSScript::formalLivesInArgumentsObject(unsigned argSlot
) {
3104 return argsObjAliasesFormals() && !formalIsAliased(argSlot
);
3107 BaseScript::BaseScript(uint8_t* stubEntry
, JSFunction
* function
,
3108 ScriptSourceObject
* sourceObject
,
3109 const SourceExtent
& extent
, uint32_t immutableFlags
)
3110 : TenuredCellWithNonGCPointer(stubEntry
),
3111 function_(function
),
3112 sourceObject_(sourceObject
),
3114 immutableFlags_(immutableFlags
) {
3115 MOZ_ASSERT(extent_
.toStringStart
<= extent_
.sourceStart
);
3116 MOZ_ASSERT(extent_
.sourceStart
<= extent_
.sourceEnd
);
3117 MOZ_ASSERT(extent_
.sourceEnd
<= extent_
.toStringEnd
);
3121 BaseScript
* BaseScript::New(JSContext
* cx
, JS::Handle
<JSFunction
*> function
,
3122 Handle
<ScriptSourceObject
*> sourceObject
,
3123 const SourceExtent
& extent
,
3124 uint32_t immutableFlags
) {
3125 uint8_t* stubEntry
= nullptr;
3126 if (jit::HasJitBackend()) {
3127 stubEntry
= cx
->runtime()->jitRuntime()->interpreterStub().value
;
3130 MOZ_ASSERT_IF(function
,
3131 function
->compartment() == sourceObject
->compartment());
3132 MOZ_ASSERT_IF(function
, function
->realm() == sourceObject
->realm());
3134 return cx
->newCell
<BaseScript
>(stubEntry
, function
, sourceObject
, extent
,
3139 BaseScript
* BaseScript::CreateRawLazy(JSContext
* cx
, uint32_t ngcthings
,
3141 Handle
<ScriptSourceObject
*> sourceObject
,
3142 const SourceExtent
& extent
,
3143 uint32_t immutableFlags
) {
3146 BaseScript
* lazy
= New(cx
, fun
, sourceObject
, extent
, immutableFlags
);
3151 // Allocate a PrivateScriptData if it will not be empty. Lazy class
3152 // constructors that use member initializers also need PrivateScriptData for
3155 // This condition is implicit in BaseScript::hasPrivateScriptData, and should
3156 // be mirrored on InputScript::hasPrivateScriptData.
3157 if (ngcthings
|| lazy
->useMemberInitializers()) {
3158 UniquePtr
<PrivateScriptData
> data(PrivateScriptData::new_(cx
, ngcthings
));
3162 lazy
->swapData(data
);
3169 #ifdef ENABLE_PORTABLE_BASELINE_INTERP
3170 // This is an arbitrary non-null pointer that we use as a placeholder
3171 // for scripts that can be run in PBL: the rest of the engine expects
3172 // a "non-null jitcode pointer" but we'll never actually call it. We
3173 // have to ensure alignment to keep GC happy.
3174 static uint8_t* const PBLJitCodePtr
= reinterpret_cast<uint8_t*>(8);
3177 void JSScript::updateJitCodeRaw(JSRuntime
* rt
) {
3179 if (hasBaselineScript() && baselineScript()->hasPendingIonCompileTask()) {
3180 MOZ_ASSERT(!isIonCompilingOffThread());
3181 setJitCodeRaw(rt
->jitRuntime()->lazyLinkStub().value
);
3182 } else if (hasIonScript()) {
3183 jit::IonScript
* ion
= ionScript();
3184 setJitCodeRaw(ion
->method()->raw());
3185 } else if (hasBaselineScript()) {
3186 setJitCodeRaw(baselineScript()->method()->raw());
3187 } else if (hasJitScript() && js::jit::IsBaselineInterpreterEnabled()) {
3188 bool usingEntryTrampoline
= false;
3189 if (js::jit::JitOptions
.emitInterpreterEntryTrampoline
) {
3190 auto p
= rt
->jitRuntime()->getInterpreterEntryMap()->lookup(this);
3192 setJitCodeRaw(p
->value().raw());
3193 usingEntryTrampoline
= true;
3196 if (!usingEntryTrampoline
) {
3197 setJitCodeRaw(rt
->jitRuntime()->baselineInterpreter().codeRaw());
3199 #ifdef ENABLE_PORTABLE_BASELINE_INTERP
3200 } else if (hasJitScript() &&
3201 js::jit::IsPortableBaselineInterpreterEnabled()) {
3202 // The portable baseline interpreter does not dispatch on this
3203 // pointer, but it needs to be non-null to trigger the appropriate
3204 // code-paths, so we set it to a placeholder value here.
3205 setJitCodeRaw(PBLJitCodePtr
);
3206 #endif // ENABLE_PORTABLE_BASELINE_INTERP
3207 } else if (!js::jit::IsBaselineInterpreterEnabled()) {
3208 setJitCodeRaw(nullptr);
3210 setJitCodeRaw(rt
->jitRuntime()->interpreterStub().value
);
3212 MOZ_ASSERT_IF(!js::jit::IsPortableBaselineInterpreterEnabled(), jitCodeRaw());
3215 bool JSScript::hasLoops() {
3216 for (const TryNote
& tn
: trynotes()) {
3224 bool JSScript::mayReadFrameArgsDirectly() {
3225 return needsArgsObj() || usesArgumentsIntrinsics() || hasRest();
3228 void JSScript::resetWarmUpCounterToDelayIonCompilation() {
3229 // Reset the warm-up count only if it's greater than the BaselineCompiler
3230 // threshold. We do this to ensure this has no effect on Baseline compilation
3231 // because we don't want scripts to get stuck in the (Baseline) interpreter in
3232 // pathological cases.
3234 if (getWarmUpCount() > jit::JitOptions
.baselineJitWarmUpThreshold
) {
3235 incWarmUpResetCounter();
3236 uint32_t newCount
= jit::JitOptions
.baselineJitWarmUpThreshold
;
3237 if (warmUpData_
.isWarmUpCount()) {
3238 warmUpData_
.resetWarmUpCount(newCount
);
3240 warmUpData_
.toJitScript()->resetWarmUpCount(newCount
);
3245 #if defined(DEBUG) || defined(JS_JITSPEW)
3247 void JSScript::dump(JSContext
* cx
) {
3248 JS::Rooted
<JSScript
*> script(cx
, this);
3250 js::Sprinter
sp(cx
);
3255 DumpOptions options
;
3256 options
.runtimeData
= true;
3257 if (!dump(cx
, script
, options
, &sp
)) {
3261 JS::UniqueChars str
= sp
.release();
3265 fprintf(stderr
, "%s\n", str
.get());
3268 void JSScript::dumpRecursive(JSContext
* cx
) {
3269 JS::Rooted
<JSScript
*> script(cx
, this);
3271 js::Sprinter
sp(cx
);
3276 DumpOptions options
;
3277 options
.runtimeData
= true;
3278 options
.recursive
= true;
3279 if (!dump(cx
, script
, options
, &sp
)) {
3283 JS::UniqueChars str
= sp
.release();
3287 fprintf(stderr
, "%s\n", str
.get());
3290 static void DumpMutableScriptFlags(js::JSONPrinter
& json
,
3291 MutableScriptFlags mutableFlags
) {
3292 // Skip warmup data.
3293 static_assert(int(MutableScriptFlagsEnum::WarmupResets_MASK
) == 0xff);
3295 for (uint32_t i
= 0x100; i
; i
= i
<< 1) {
3296 if (uint32_t(mutableFlags
) & i
) {
3297 switch (MutableScriptFlagsEnum(i
)) {
3298 case MutableScriptFlagsEnum::HasRunOnce
:
3299 json
.value("HasRunOnce");
3301 case MutableScriptFlagsEnum::HasBeenCloned
:
3302 json
.value("HasBeenCloned");
3304 case MutableScriptFlagsEnum::HasScriptCounts
:
3305 json
.value("HasScriptCounts");
3307 case MutableScriptFlagsEnum::HasDebugScript
:
3308 json
.value("HasDebugScript");
3310 case MutableScriptFlagsEnum::AllowRelazify
:
3311 json
.value("AllowRelazify");
3313 case MutableScriptFlagsEnum::SpewEnabled
:
3314 json
.value("SpewEnabled");
3316 case MutableScriptFlagsEnum::NeedsFinalWarmUpCount
:
3317 json
.value("NeedsFinalWarmUpCount");
3319 case MutableScriptFlagsEnum::BaselineDisabled
:
3320 json
.value("BaselineDisabled");
3322 case MutableScriptFlagsEnum::IonDisabled
:
3323 json
.value("IonDisabled");
3325 case MutableScriptFlagsEnum::Uninlineable
:
3326 json
.value("Uninlineable");
3328 case MutableScriptFlagsEnum::NoEagerBaselineHint
:
3329 json
.value("NoEagerBaselineHint");
3331 case MutableScriptFlagsEnum::FailedBoundsCheck
:
3332 json
.value("FailedBoundsCheck");
3334 case MutableScriptFlagsEnum::HadLICMInvalidation
:
3335 json
.value("HadLICMInvalidation");
3337 case MutableScriptFlagsEnum::HadReorderingBailout
:
3338 json
.value("HadReorderingBailout");
3340 case MutableScriptFlagsEnum::HadEagerTruncationBailout
:
3341 json
.value("HadEagerTruncationBailout");
3343 case MutableScriptFlagsEnum::FailedLexicalCheck
:
3344 json
.value("FailedLexicalCheck");
3346 case MutableScriptFlagsEnum::HadSpeculativePhiBailout
:
3347 json
.value("HadSpeculativePhiBailout");
3349 case MutableScriptFlagsEnum::HadUnboxFoldingBailout
:
3350 json
.value("HadUnboxFoldingBailout");
3353 json
.value("Unknown(%x)", i
);
3361 bool JSScript::dump(JSContext
* cx
, JS::Handle
<JSScript
*> script
,
3362 DumpOptions
& options
, js::StringPrinter
* sp
) {
3364 JSONPrinter
json(*sp
);
3368 if (const char* filename
= script
->filename()) {
3369 json
.property("file", filename
);
3371 json
.nullProperty("file");
3374 json
.property("lineno", script
->lineno());
3375 json
.property("column", script
->column().oneOriginValue());
3377 json
.beginListProperty("immutableFlags");
3378 DumpImmutableScriptFlags(json
, script
->immutableFlags());
3381 if (options
.runtimeData
) {
3382 json
.beginListProperty("mutableFlags");
3383 DumpMutableScriptFlags(json
, script
->mutableFlags_
);
3387 if (script
->isFunction()) {
3388 JS::Rooted
<JSFunction
*> fun(cx
, script
->function());
3390 JS::Rooted
<JSAtom
*> name(cx
, fun
->fullDisplayAtom());
3392 UniqueChars bytes
= JS_EncodeStringToUTF8(cx
, name
);
3396 json
.property("functionName", bytes
.get());
3398 json
.nullProperty("functionName");
3401 json
.beginListProperty("functionFlags");
3402 DumpFunctionFlagsItems(json
, fun
->flags());
3409 if (sp
->hadOutOfMemory()) {
3410 sp
->forwardOutOfMemory();
3416 if (!Disassemble(cx
, script
, /* lines = */ true, sp
)) {
3419 if (!dumpSrcNotes(cx
, script
, sp
)) {
3422 if (!dumpTryNotes(cx
, script
, sp
)) {
3425 if (!dumpScopeNotes(cx
, script
, sp
)) {
3428 if (!dumpGCThings(cx
, script
, sp
)) {
3432 if (options
.recursive
) {
3433 for (JS::GCCellPtr gcThing
: script
->gcthings()) {
3434 if (!gcThing
.is
<JSObject
>()) {
3438 JSObject
* obj
= &gcThing
.as
<JSObject
>();
3439 if (obj
->is
<JSFunction
>()) {
3442 JS::Rooted
<JSFunction
*> fun(cx
, &obj
->as
<JSFunction
>());
3443 if (fun
->isInterpreted()) {
3444 JS::Rooted
<JSScript
*> innerScript(
3445 cx
, JSFunction::getOrCreateScript(cx
, fun
));
3449 if (!dump(cx
, innerScript
, options
, sp
)) {
3453 sp
->put("[native code]\n");
3463 bool JSScript::dumpSrcNotes(JSContext
* cx
, JS::Handle
<JSScript
*> script
,
3464 js::GenericPrinter
* sp
) {
3465 sp
->put("\nSource notes:\n");
3466 sp
->printf("%4s %4s %6s %5s %6s %-16s %s\n", "ofs", "line", "column", "pc",
3467 "delta", "desc", "args");
3468 sp
->put("---- ---- ------ ----- ------ ---------------- ------\n");
3469 unsigned offset
= 0;
3470 unsigned lineno
= script
->lineno();
3471 JS::LimitedColumnNumberOneOrigin column
= script
->column();
3472 SrcNote
* notes
= script
->notes();
3473 SrcNote
* notesEnd
= script
->notesEnd();
3474 for (SrcNoteIterator
iter(notes
, notesEnd
); !iter
.atEnd(); ++iter
) {
3475 const auto* sn
= *iter
;
3477 unsigned delta
= sn
->delta();
3479 SrcNoteType type
= sn
->type();
3480 const char* name
= sn
->name();
3481 sp
->printf("%3u: %4u %6u %5u [%4u] %-16s", unsigned(sn
- notes
), lineno
,
3482 column
.oneOriginValue(), offset
, delta
, name
);
3485 case SrcNoteType::Breakpoint
:
3486 case SrcNoteType::BreakpointStepSep
:
3487 case SrcNoteType::XDelta
:
3490 case SrcNoteType::ColSpan
: {
3491 JS::ColumnNumberOffset colspan
= SrcNote::ColSpan::getSpan(sn
);
3492 sp
->printf(" colspan %u", colspan
.value());
3497 case SrcNoteType::SetLine
:
3498 lineno
= SrcNote::SetLine::getLine(sn
, script
->lineno());
3499 sp
->printf(" lineno %u", lineno
);
3500 column
= JS::LimitedColumnNumberOneOrigin();
3503 case SrcNoteType::SetLineColumn
:
3504 lineno
= SrcNote::SetLineColumn::getLine(sn
, script
->lineno());
3505 column
= SrcNote::SetLineColumn::getColumn(sn
);
3506 sp
->printf(" lineno %u column %u", lineno
, column
.oneOriginValue());
3509 case SrcNoteType::NewLine
:
3511 column
= JS::LimitedColumnNumberOneOrigin();
3514 case SrcNoteType::NewLineColumn
:
3515 column
= SrcNote::NewLineColumn::getColumn(sn
);
3516 sp
->printf(" column %u", column
.oneOriginValue());
3521 MOZ_ASSERT_UNREACHABLE("unrecognized srcnote");
3529 static const char* TryNoteName(TryNoteKind kind
) {
3531 case TryNoteKind::Catch
:
3533 case TryNoteKind::Finally
:
3535 case TryNoteKind::ForIn
:
3537 case TryNoteKind::ForOf
:
3539 case TryNoteKind::Loop
:
3541 case TryNoteKind::ForOfIterClose
:
3542 return "for-of-iterclose";
3543 case TryNoteKind::Destructuring
:
3544 return "destructuring";
3547 MOZ_CRASH("Bad TryNoteKind");
3551 bool JSScript::dumpTryNotes(JSContext
* cx
, JS::Handle
<JSScript
*> script
,
3552 js::GenericPrinter
* sp
) {
3553 sp
->put("\nException table:\nkind stack start end\n");
3555 for (const js::TryNote
& tn
: script
->trynotes()) {
3556 sp
->printf(" %-16s %6u %8u %8u\n", TryNoteName(tn
.kind()), tn
.stackDepth
,
3557 tn
.start
, tn
.start
+ tn
.length
);
3563 bool JSScript::dumpScopeNotes(JSContext
* cx
, JS::Handle
<JSScript
*> script
,
3564 js::GenericPrinter
* sp
) {
3565 sp
->put("\nScope notes:\n index parent start end\n");
3567 for (const ScopeNote
& note
: script
->scopeNotes()) {
3568 if (note
.index
== ScopeNote::NoScopeIndex
) {
3569 sp
->printf("%8s ", "(none)");
3571 sp
->printf("%8u ", note
.index
.index
);
3573 if (note
.parent
== ScopeNote::NoScopeIndex
) {
3574 sp
->printf("%8s ", "(none)");
3576 sp
->printf("%8u ", note
.parent
);
3578 sp
->printf("%8u %8u\n", note
.start
, note
.start
+ note
.length
);
3584 bool JSScript::dumpGCThings(JSContext
* cx
, JS::Handle
<JSScript
*> script
,
3585 js::GenericPrinter
* sp
) {
3586 sp
->put("\nGC things:\n index type value\n");
3589 for (JS::GCCellPtr gcThing
: script
->gcthings()) {
3590 sp
->printf("%8zu ", i
);
3591 if (gcThing
.is
<JS::BigInt
>()) {
3593 gcThing
.as
<JS::BigInt
>().dump(*sp
);
3595 } else if (gcThing
.is
<Scope
>()) {
3597 JS::Rooted
<Scope
*> scope(cx
, &gcThing
.as
<Scope
>());
3598 if (!Scope::dumpForDisassemble(cx
, scope
, *sp
,
3603 } else if (gcThing
.is
<JSObject
>()) {
3604 JSObject
* obj
= &gcThing
.as
<JSObject
>();
3605 if (obj
->is
<JSFunction
>()) {
3606 sp
->put("Function ");
3607 JS::Rooted
<JSFunction
*> fun(cx
, &obj
->as
<JSFunction
>());
3608 if (fun
->fullDisplayAtom()) {
3609 JS::Rooted
<JSAtom
*> name(cx
, fun
->fullDisplayAtom());
3610 JS::UniqueChars utf8chars
= JS_EncodeStringToUTF8(cx
, name
);
3614 sp
->put(utf8chars
.get());
3616 sp
->put("(anonymous)");
3619 if (fun
->hasBaseScript()) {
3620 BaseScript
* script
= fun
->baseScript();
3621 sp
->printf(" @ %u:%u\n", script
->lineno(),
3622 script
->column().oneOriginValue());
3624 sp
->put(" (no script)\n");
3627 if (obj
->is
<RegExpObject
>()) {
3633 JS::Rooted
<JS::Value
> objValue(cx
, ObjectValue(*obj
));
3634 JS::Rooted
<JSString
*> str(cx
, ValueToSource(cx
, objValue
));
3638 JS::UniqueChars utf8chars
= JS_EncodeStringToUTF8(cx
, str
);
3642 sp
->put(utf8chars
.get());
3645 } else if (gcThing
.is
<JSString
>()) {
3646 JS::Rooted
<JSString
*> str(cx
, &gcThing
.as
<JSString
>());
3647 if (str
->isAtom()) {
3652 JS::UniqueChars chars
= QuoteString(cx
, str
, '"');
3656 sp
->put(chars
.get());
3659 sp
->put("Unknown\n");
3667 #endif // defined(DEBUG) || defined(JS_JITSPEW)
3669 void JSScript::AutoDelazify::holdScript(JS::HandleFunction fun
) {
3671 JSAutoRealm
ar(cx_
, fun
);
3672 script_
= JSFunction::getOrCreateScript(cx_
, fun
);
3674 oldAllowRelazify_
= script_
->allowRelazify();
3675 script_
->clearAllowRelazify();
3680 void JSScript::AutoDelazify::dropScript() {
3682 script_
->setAllowRelazify(oldAllowRelazify_
);
3687 JS::ubi::Base::Size
JS::ubi::Concrete
<BaseScript
>::size(
3688 mozilla::MallocSizeOf mallocSizeOf
) const {
3689 BaseScript
* base
= &get();
3691 Size size
= gc::Arena::thingSize(base
->getAllocKind());
3692 size
+= base
->sizeOfExcludingThis(mallocSizeOf
);
3694 // Include any JIT data if it exists.
3695 if (base
->hasJitScript()) {
3696 JSScript
* script
= base
->asJSScript();
3698 size_t jitScriptSize
= 0;
3699 size_t allocSitesSize
= 0;
3700 script
->addSizeOfJitScript(mallocSizeOf
, &jitScriptSize
, &allocSitesSize
);
3701 size
+= jitScriptSize
;
3702 size
+= allocSitesSize
;
3704 size_t baselineSize
= 0;
3705 jit::AddSizeOfBaselineData(script
, mallocSizeOf
, &baselineSize
);
3706 size
+= baselineSize
;
3708 size
+= jit::SizeOfIonData(script
, mallocSizeOf
);
3711 MOZ_ASSERT(size
> 0);
3715 const char* JS::ubi::Concrete
<BaseScript
>::scriptFilename() const {
3716 return get().filename();