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::LimitedColumnNumberZeroOrigin, 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/JSContext.h"
70 #include "vm/JSFunction.h"
71 #include "vm/JSObject.h"
72 #include "vm/JSONPrinter.h" // JSONPrinter
73 #include "vm/Opcodes.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/JSObject-inl.h"
89 #include "vm/SharedImmutableStringsCache-inl.h"
90 #include "vm/Stack-inl.h"
94 using mozilla::CheckedInt
;
96 using mozilla::PodCopy
;
97 using mozilla::PointerRangeSize
;
98 using mozilla::Utf8AsUnsignedChars
;
99 using mozilla::Utf8Unit
;
101 using JS::CompileOptions
;
102 using JS::ReadOnlyCompileOptions
;
103 using JS::SourceText
;
105 bool js::BaseScript::isUsingInterpreterTrampoline(JSRuntime
* rt
) const {
106 return jitCodeRaw() == rt
->jitRuntime()->interpreterStub().value
;
109 js::ScriptSource
* js::BaseScript::maybeForwardedScriptSource() const {
110 return MaybeForwarded(sourceObject())->source();
113 void js::BaseScript::setEnclosingScript(BaseScript
* enclosingScript
) {
114 MOZ_ASSERT(enclosingScript
);
115 warmUpData_
.initEnclosingScript(enclosingScript
);
118 void js::BaseScript::setEnclosingScope(Scope
* enclosingScope
) {
119 if (warmUpData_
.isEnclosingScript()) {
120 warmUpData_
.clearEnclosingScript();
123 MOZ_ASSERT(enclosingScope
);
124 warmUpData_
.initEnclosingScope(enclosingScope
);
127 void js::BaseScript::finalize(JS::GCContext
* gcx
) {
128 // Scripts with bytecode may have optional data stored in per-runtime or
129 // per-zone maps. Note that a failed compilation must not have entries since
130 // the script itself will not be marked as having bytecode.
132 JSScript
* script
= this->asJSScript();
134 if (coverage::IsLCovEnabled()) {
135 coverage::CollectScriptCoverage(script
, true);
138 script
->destroyScriptCounts();
142 JSRuntime
* rt
= gcx
->runtime();
143 if (rt
->hasJitRuntime() && rt
->jitRuntime()->hasInterpreterEntryMap()) {
144 rt
->jitRuntime()->getInterpreterEntryMap()->remove(this);
147 rt
->geckoProfiler().onScriptFinalized(this);
151 if (zone()->scriptVTuneIdMap
) {
152 // Note: we should only get here if the VTune JIT profiler is running.
153 zone()->scriptVTuneIdMap
->remove(this);
157 if (warmUpData_
.isJitScript()) {
158 JSScript
* script
= this->asJSScript();
159 #ifdef JS_CACHEIR_SPEW
160 maybeUpdateWarmUpCount(script
);
162 script
->releaseJitScriptOnFinalize(gcx
);
165 #ifdef JS_CACHEIR_SPEW
167 maybeSpewScriptFinalWarmUpCount(this->asJSScript());
172 // We don't need to triger any barriers here, just free the memory.
173 size_t size
= data_
->allocationSize();
174 AlwaysPoison(data_
, JS_POISONED_JSSCRIPT_DATA_PATTERN
, size
,
175 MemCheckKind::MakeNoAccess
);
176 gcx
->free_(this, data_
, size
, MemoryUse::ScriptPrivateData
);
182 js::Scope
* js::BaseScript::releaseEnclosingScope() {
183 Scope
* enclosing
= warmUpData_
.toEnclosingScope();
184 warmUpData_
.clearEnclosingScope();
188 void js::BaseScript::swapData(UniquePtr
<PrivateScriptData
>& other
) {
190 RemoveCellMemory(this, data_
->allocationSize(),
191 MemoryUse::ScriptPrivateData
);
194 PrivateScriptData
* old
= data_
;
195 data_
.set(zone(), other
.release());
199 AddCellMemory(this, data_
->allocationSize(), MemoryUse::ScriptPrivateData
);
203 js::Scope
* js::BaseScript::enclosingScope() const {
204 MOZ_ASSERT(!warmUpData_
.isEnclosingScript(),
205 "Enclosing scope is not computed yet");
207 if (warmUpData_
.isEnclosingScope()) {
208 return warmUpData_
.toEnclosingScope();
211 MOZ_ASSERT(data_
, "Script doesn't seem to be compiled");
213 return gcthings()[js::GCThingIndex::outermostScopeIndex()]
218 size_t JSScript::numAlwaysLiveFixedSlots() const {
219 if (bodyScope()->is
<js::FunctionScope
>()) {
220 return bodyScope()->as
<js::FunctionScope
>().nextFrameSlot();
222 if (bodyScope()->is
<js::ModuleScope
>()) {
223 return bodyScope()->as
<js::ModuleScope
>().nextFrameSlot();
225 if (bodyScope()->is
<js::EvalScope
>() &&
226 bodyScope()->kind() == ScopeKind::StrictEval
) {
227 return bodyScope()->as
<js::EvalScope
>().nextFrameSlot();
232 unsigned JSScript::numArgs() const {
233 if (bodyScope()->is
<js::FunctionScope
>()) {
234 return bodyScope()->as
<js::FunctionScope
>().numPositionalFormalParameters();
239 bool JSScript::functionHasParameterExprs() const {
240 // Only functions have parameters.
241 js::Scope
* scope
= bodyScope();
242 if (!scope
->is
<js::FunctionScope
>()) {
245 return scope
->as
<js::FunctionScope
>().hasParameterExprs();
248 bool JSScript::isModule() const { return bodyScope()->is
<js::ModuleScope
>(); }
250 js::ModuleObject
* JSScript::module() const {
251 MOZ_ASSERT(isModule());
252 return bodyScope()->as
<js::ModuleScope
>().module();
255 bool JSScript::isGlobalCode() const {
256 return bodyScope()->is
<js::GlobalScope
>();
259 js::VarScope
* JSScript::functionExtraBodyVarScope() const {
260 MOZ_ASSERT(functionHasExtraBodyVarScope());
261 for (JS::GCCellPtr gcThing
: gcthings()) {
262 if (!gcThing
.is
<js::Scope
>()) {
265 js::Scope
* scope
= &gcThing
.as
<js::Scope
>();
266 if (scope
->kind() == js::ScopeKind::FunctionBodyVar
) {
267 return &scope
->as
<js::VarScope
>();
270 MOZ_CRASH("Function extra body var scope not found");
273 bool JSScript::needsBodyEnvironment() const {
274 for (JS::GCCellPtr gcThing
: gcthings()) {
275 if (!gcThing
.is
<js::Scope
>()) {
278 js::Scope
* scope
= &gcThing
.as
<js::Scope
>();
279 if (ScopeKindIsInBody(scope
->kind()) && scope
->hasEnvironment()) {
286 bool JSScript::isDirectEvalInFunction() const {
290 return bodyScope()->hasOnChain(js::ScopeKind::Function
);
293 // Initialize the optional arrays in the trailing allocation. This is a set of
294 // offsets that delimit each optional array followed by the arrays themselves.
295 // See comment before 'ImmutableScriptData' for more details.
296 void ImmutableScriptData::initOptionalArrays(Offset
* pcursor
,
297 uint32_t numResumeOffsets
,
298 uint32_t numScopeNotes
,
299 uint32_t numTryNotes
) {
300 Offset cursor
= (*pcursor
);
302 // The byte arrays must have already been padded.
303 MOZ_ASSERT(isAlignedOffset
<CodeNoteAlign
>(cursor
),
304 "Bytecode and source notes should be padded to keep alignment");
306 // Each non-empty optional array needs will need an offset to its end.
307 unsigned numOptionalArrays
= unsigned(numResumeOffsets
> 0) +
308 unsigned(numScopeNotes
> 0) +
309 unsigned(numTryNotes
> 0);
311 // Default-initialize the optional-offsets.
312 initElements
<Offset
>(cursor
, numOptionalArrays
);
313 cursor
+= numOptionalArrays
* sizeof(Offset
);
315 // Offset between optional-offsets table and the optional arrays. This is
316 // later used to access the optional-offsets table as well as first optional
318 optArrayOffset_
= cursor
;
320 // Each optional array that follows must store an end-offset in the offset
321 // table. Assign table entries by using this 'offsetIndex'. The index 0 is
322 // reserved for implicit value 'optArrayOffset'.
325 // Default-initialize optional 'resumeOffsets'.
326 MOZ_ASSERT(resumeOffsetsOffset() == cursor
);
327 if (numResumeOffsets
> 0) {
328 initElements
<uint32_t>(cursor
, numResumeOffsets
);
329 cursor
+= numResumeOffsets
* sizeof(uint32_t);
330 setOptionalOffset(++offsetIndex
, cursor
);
332 flagsRef().resumeOffsetsEndIndex
= offsetIndex
;
334 // Default-initialize optional 'scopeNotes'.
335 MOZ_ASSERT(scopeNotesOffset() == cursor
);
336 if (numScopeNotes
> 0) {
337 initElements
<ScopeNote
>(cursor
, numScopeNotes
);
338 cursor
+= numScopeNotes
* sizeof(ScopeNote
);
339 setOptionalOffset(++offsetIndex
, cursor
);
341 flagsRef().scopeNotesEndIndex
= offsetIndex
;
343 // Default-initialize optional 'tryNotes'
344 MOZ_ASSERT(tryNotesOffset() == cursor
);
345 if (numTryNotes
> 0) {
346 initElements
<TryNote
>(cursor
, numTryNotes
);
347 cursor
+= numTryNotes
* sizeof(TryNote
);
348 setOptionalOffset(++offsetIndex
, cursor
);
350 flagsRef().tryNotesEndIndex
= offsetIndex
;
352 MOZ_ASSERT(endOffset() == cursor
);
356 ImmutableScriptData::ImmutableScriptData(uint32_t codeLength
,
358 uint32_t numResumeOffsets
,
359 uint32_t numScopeNotes
,
360 uint32_t numTryNotes
)
361 : codeLength_(codeLength
) {
362 // Variable-length data begins immediately after ImmutableScriptData itself.
363 Offset cursor
= sizeof(ImmutableScriptData
);
365 // The following arrays are byte-aligned with additional padding to ensure
366 // that together they maintain uint32_t-alignment.
368 MOZ_ASSERT(isAlignedOffset
<CodeNoteAlign
>(cursor
));
370 // Zero-initialize 'flags'
371 MOZ_ASSERT(isAlignedOffset
<Flags
>(cursor
));
372 new (offsetToPointer
<void>(cursor
)) Flags
{};
373 cursor
+= sizeof(Flags
);
375 initElements
<jsbytecode
>(cursor
, codeLength
);
376 cursor
+= codeLength
* sizeof(jsbytecode
);
378 initElements
<SrcNote
>(cursor
, noteLength
);
379 cursor
+= noteLength
* sizeof(SrcNote
);
381 MOZ_ASSERT(isAlignedOffset
<CodeNoteAlign
>(cursor
));
384 // Initialization for remaining arrays.
385 initOptionalArrays(&cursor
, numResumeOffsets
, numScopeNotes
, numTryNotes
);
387 // Check that we correctly recompute the expected values.
388 MOZ_ASSERT(this->codeLength() == codeLength
);
389 MOZ_ASSERT(this->noteLength() == noteLength
);
392 MOZ_ASSERT(endOffset() == cursor
);
395 void js::FillImmutableFlagsFromCompileOptionsForTopLevel(
396 const ReadOnlyCompileOptions
& options
, ImmutableScriptFlags
& flags
) {
397 using ImmutableFlags
= ImmutableScriptFlagsEnum
;
399 js::FillImmutableFlagsFromCompileOptionsForFunction(options
, flags
);
401 flags
.setFlag(ImmutableFlags::TreatAsRunOnce
, options
.isRunOnce
);
402 flags
.setFlag(ImmutableFlags::NoScriptRval
, options
.noScriptRval
);
405 void js::FillImmutableFlagsFromCompileOptionsForFunction(
406 const ReadOnlyCompileOptions
& options
, ImmutableScriptFlags
& flags
) {
407 using ImmutableFlags
= ImmutableScriptFlagsEnum
;
409 flags
.setFlag(ImmutableFlags::SelfHosted
, options
.selfHostingMode
);
410 flags
.setFlag(ImmutableFlags::ForceStrict
, options
.forceStrictMode());
411 flags
.setFlag(ImmutableFlags::HasNonSyntacticScope
,
412 options
.nonSyntacticScope
);
415 // Check if flags matches to compile options for flags set by
416 // FillImmutableFlagsFromCompileOptionsForTopLevel above.
417 bool js::CheckCompileOptionsMatch(const ReadOnlyCompileOptions
& options
,
418 ImmutableScriptFlags flags
) {
419 using ImmutableFlags
= ImmutableScriptFlagsEnum
;
421 bool selfHosted
= !!(flags
& uint32_t(ImmutableFlags::SelfHosted
));
422 bool forceStrict
= !!(flags
& uint32_t(ImmutableFlags::ForceStrict
));
423 bool hasNonSyntacticScope
=
424 !!(flags
& uint32_t(ImmutableFlags::HasNonSyntacticScope
));
425 bool noScriptRval
= !!(flags
& uint32_t(ImmutableFlags::NoScriptRval
));
426 bool treatAsRunOnce
= !!(flags
& uint32_t(ImmutableFlags::TreatAsRunOnce
));
428 return options
.selfHostingMode
== selfHosted
&&
429 options
.noScriptRval
== noScriptRval
&&
430 options
.isRunOnce
== treatAsRunOnce
&&
431 options
.forceStrictMode() == forceStrict
&&
432 options
.nonSyntacticScope
== hasNonSyntacticScope
;
435 JS_PUBLIC_API
bool JS::CheckCompileOptionsMatch(
436 const ReadOnlyCompileOptions
& options
, JSScript
* script
) {
437 return js::CheckCompileOptionsMatch(options
, script
->immutableFlags());
440 bool JSScript::initScriptCounts(JSContext
* cx
) {
441 MOZ_ASSERT(!hasScriptCounts());
443 // Record all pc which are the first instruction of a basic block.
444 mozilla::Vector
<jsbytecode
*, 16, SystemAllocPolicy
> jumpTargets
;
446 js::BytecodeLocation main
= mainLocation();
447 AllBytecodesIterable
iterable(this);
448 for (auto& loc
: iterable
) {
449 if (loc
.isJumpTarget() || loc
== main
) {
450 if (!jumpTargets
.append(loc
.toRawBytecode())) {
451 ReportOutOfMemory(cx
);
457 // Initialize all PCCounts counters to 0.
458 ScriptCounts::PCCountsVector base
;
459 if (!base
.reserve(jumpTargets
.length())) {
460 ReportOutOfMemory(cx
);
464 for (size_t i
= 0; i
< jumpTargets
.length(); i
++) {
465 base
.infallibleEmplaceBack(pcToOffset(jumpTargets
[i
]));
468 // Create zone's scriptCountsMap if necessary.
469 if (!zone()->scriptCountsMap
) {
470 auto map
= cx
->make_unique
<ScriptCountsMap
>();
475 zone()->scriptCountsMap
= std::move(map
);
478 // Allocate the ScriptCounts.
479 UniqueScriptCounts sc
= cx
->make_unique
<ScriptCounts
>(std::move(base
));
484 MOZ_ASSERT(this->hasBytecode());
486 // Register the current ScriptCounts in the zone's map.
487 if (!zone()->scriptCountsMap
->putNew(this, std::move(sc
))) {
488 ReportOutOfMemory(cx
);
492 // safe to set this; we can't fail after this point.
493 setHasScriptCounts();
495 // Enable interrupts in any interpreter frames running on this script. This
496 // is used to let the interpreter increment the PCCounts, if present.
497 for (ActivationIterator
iter(cx
); !iter
.done(); ++iter
) {
498 if (iter
->isInterpreter()) {
499 iter
->asInterpreter()->enableInterruptsIfRunning(this);
506 static inline ScriptCountsMap::Ptr
GetScriptCountsMapEntry(JSScript
* script
) {
507 MOZ_ASSERT(script
->hasScriptCounts());
508 ScriptCountsMap::Ptr p
= script
->zone()->scriptCountsMap
->lookup(script
);
513 ScriptCounts
& JSScript::getScriptCounts() {
514 ScriptCountsMap::Ptr p
= GetScriptCountsMapEntry(this);
518 js::PCCounts
* ScriptCounts::maybeGetPCCounts(size_t offset
) {
519 PCCounts searched
= PCCounts(offset
);
521 std::lower_bound(pcCounts_
.begin(), pcCounts_
.end(), searched
);
522 if (elem
== pcCounts_
.end() || elem
->pcOffset() != offset
) {
528 const js::PCCounts
* ScriptCounts::maybeGetPCCounts(size_t offset
) const {
529 PCCounts searched
= PCCounts(offset
);
530 const PCCounts
* elem
=
531 std::lower_bound(pcCounts_
.begin(), pcCounts_
.end(), searched
);
532 if (elem
== pcCounts_
.end() || elem
->pcOffset() != offset
) {
538 js::PCCounts
* ScriptCounts::getImmediatePrecedingPCCounts(size_t offset
) {
539 PCCounts searched
= PCCounts(offset
);
541 std::lower_bound(pcCounts_
.begin(), pcCounts_
.end(), searched
);
542 if (elem
== pcCounts_
.end()) {
543 return &pcCounts_
.back();
545 if (elem
->pcOffset() == offset
) {
548 if (elem
!= pcCounts_
.begin()) {
554 const js::PCCounts
* ScriptCounts::maybeGetThrowCounts(size_t offset
) const {
555 PCCounts searched
= PCCounts(offset
);
556 const PCCounts
* elem
=
557 std::lower_bound(throwCounts_
.begin(), throwCounts_
.end(), searched
);
558 if (elem
== throwCounts_
.end() || elem
->pcOffset() != offset
) {
564 const js::PCCounts
* ScriptCounts::getImmediatePrecedingThrowCounts(
565 size_t offset
) const {
566 PCCounts searched
= PCCounts(offset
);
567 const PCCounts
* elem
=
568 std::lower_bound(throwCounts_
.begin(), throwCounts_
.end(), searched
);
569 if (elem
== throwCounts_
.end()) {
570 if (throwCounts_
.begin() == throwCounts_
.end()) {
573 return &throwCounts_
.back();
575 if (elem
->pcOffset() == offset
) {
578 if (elem
!= throwCounts_
.begin()) {
584 js::PCCounts
* ScriptCounts::getThrowCounts(size_t offset
) {
585 PCCounts searched
= PCCounts(offset
);
587 std::lower_bound(throwCounts_
.begin(), throwCounts_
.end(), searched
);
588 if (elem
== throwCounts_
.end() || elem
->pcOffset() != offset
) {
589 elem
= throwCounts_
.insert(elem
, searched
);
594 size_t ScriptCounts::sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf
) {
595 size_t size
= mallocSizeOf(this);
596 size
+= pcCounts_
.sizeOfExcludingThis(mallocSizeOf
);
597 size
+= throwCounts_
.sizeOfExcludingThis(mallocSizeOf
);
599 size
+= ionCounts_
->sizeOfIncludingThis(mallocSizeOf
);
604 js::PCCounts
* JSScript::maybeGetPCCounts(jsbytecode
* pc
) {
605 MOZ_ASSERT(containsPC(pc
));
606 return getScriptCounts().maybeGetPCCounts(pcToOffset(pc
));
609 const js::PCCounts
* JSScript::maybeGetThrowCounts(jsbytecode
* pc
) {
610 MOZ_ASSERT(containsPC(pc
));
611 return getScriptCounts().maybeGetThrowCounts(pcToOffset(pc
));
614 js::PCCounts
* JSScript::getThrowCounts(jsbytecode
* pc
) {
615 MOZ_ASSERT(containsPC(pc
));
616 return getScriptCounts().getThrowCounts(pcToOffset(pc
));
619 uint64_t JSScript::getHitCount(jsbytecode
* pc
) {
620 MOZ_ASSERT(containsPC(pc
));
625 ScriptCounts
& sc
= getScriptCounts();
626 size_t targetOffset
= pcToOffset(pc
);
627 const js::PCCounts
* baseCount
=
628 sc
.getImmediatePrecedingPCCounts(targetOffset
);
632 if (baseCount
->pcOffset() == targetOffset
) {
633 return baseCount
->numExec();
635 MOZ_ASSERT(baseCount
->pcOffset() < targetOffset
);
636 uint64_t count
= baseCount
->numExec();
638 const js::PCCounts
* throwCount
=
639 sc
.getImmediatePrecedingThrowCounts(targetOffset
);
643 if (throwCount
->pcOffset() <= baseCount
->pcOffset()) {
646 count
-= throwCount
->numExec();
647 targetOffset
= throwCount
->pcOffset() - 1;
651 void JSScript::addIonCounts(jit::IonScriptCounts
* ionCounts
) {
652 ScriptCounts
& sc
= getScriptCounts();
654 ionCounts
->setPrevious(sc
.ionCounts_
);
656 sc
.ionCounts_
= ionCounts
;
659 jit::IonScriptCounts
* JSScript::getIonCounts() {
660 return getScriptCounts().ionCounts_
;
663 void JSScript::releaseScriptCounts(ScriptCounts
* counts
) {
664 ScriptCountsMap::Ptr p
= GetScriptCountsMapEntry(this);
665 *counts
= std::move(*p
->value().get());
666 zone()->scriptCountsMap
->remove(p
);
667 clearHasScriptCounts();
670 void JSScript::destroyScriptCounts() {
671 if (hasScriptCounts()) {
672 ScriptCounts scriptCounts
;
673 releaseScriptCounts(&scriptCounts
);
677 void JSScript::resetScriptCounts() {
678 if (!hasScriptCounts()) {
682 ScriptCounts
& sc
= getScriptCounts();
684 for (PCCounts
& elem
: sc
.pcCounts_
) {
688 for (PCCounts
& elem
: sc
.throwCounts_
) {
693 void ScriptSourceObject::finalize(JS::GCContext
* gcx
, JSObject
* obj
) {
694 MOZ_ASSERT(gcx
->onMainThread());
695 ScriptSourceObject
* sso
= &obj
->as
<ScriptSourceObject
>();
696 sso
->source()->Release();
698 // Clear the private value, calling the release hook if necessary.
699 sso
->setPrivate(gcx
->runtime(), UndefinedValue());
702 static const JSClassOps ScriptSourceObjectClassOps
= {
703 nullptr, // addProperty
704 nullptr, // delProperty
705 nullptr, // enumerate
706 nullptr, // newEnumerate
708 nullptr, // mayResolve
709 ScriptSourceObject::finalize
, // finalize
711 nullptr, // construct
715 const JSClass
ScriptSourceObject::class_
= {
717 JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS
) | JSCLASS_FOREGROUND_FINALIZE
,
718 &ScriptSourceObjectClassOps
};
720 ScriptSourceObject
* ScriptSourceObject::create(JSContext
* cx
,
721 ScriptSource
* source
) {
722 ScriptSourceObject
* obj
=
723 NewObjectWithGivenProto
<ScriptSourceObject
>(cx
, nullptr);
728 // The matching decref is in ScriptSourceObject::finalize.
729 obj
->initReservedSlot(SOURCE_SLOT
, PrivateValue(do_AddRef(source
).take()));
731 // The slots below should be populated by a call to initFromOptions. Poison
733 obj
->initReservedSlot(ELEMENT_PROPERTY_SLOT
, MagicValue(JS_GENERIC_MAGIC
));
734 obj
->initReservedSlot(INTRODUCTION_SCRIPT_SLOT
, MagicValue(JS_GENERIC_MAGIC
));
739 [[nodiscard
]] static bool MaybeValidateFilename(
740 JSContext
* cx
, Handle
<ScriptSourceObject
*> sso
,
741 const JS::InstantiateOptions
& options
) {
742 if (!gFilenameValidationCallback
) {
746 const char* filename
= sso
->source()->filename();
747 if (!filename
|| options
.skipFilenameValidation
) {
751 if (gFilenameValidationCallback(cx
, filename
)) {
755 const char* utf8Filename
;
756 if (mozilla::IsUtf8(mozilla::MakeStringSpan(filename
))) {
757 utf8Filename
= filename
;
759 utf8Filename
= "(invalid UTF-8 filename)";
761 JS_ReportErrorNumberUTF8(cx
, GetErrorMessage
, nullptr, JSMSG_UNSAFE_FILENAME
,
767 bool ScriptSourceObject::initFromOptions(
768 JSContext
* cx
, Handle
<ScriptSourceObject
*> source
,
769 const JS::InstantiateOptions
& options
) {
770 cx
->releaseCheck(source
);
772 source
->getReservedSlot(ELEMENT_PROPERTY_SLOT
).isMagic(JS_GENERIC_MAGIC
));
773 MOZ_ASSERT(source
->getReservedSlot(INTRODUCTION_SCRIPT_SLOT
)
774 .isMagic(JS_GENERIC_MAGIC
));
776 if (!MaybeValidateFilename(cx
, source
, options
)) {
780 if (options
.deferDebugMetadata
) {
784 // Initialize the element attribute slot and introduction script slot
785 // this marks the SSO as initialized for asserts.
787 RootedString
elementAttributeName(cx
);
788 if (!initElementProperties(cx
, source
, elementAttributeName
)) {
792 RootedValue
introductionScript(cx
);
793 source
->setReservedSlot(INTRODUCTION_SCRIPT_SLOT
, introductionScript
);
799 bool ScriptSourceObject::initElementProperties(
800 JSContext
* cx
, Handle
<ScriptSourceObject
*> source
,
801 HandleString elementAttrName
) {
802 RootedValue
nameValue(cx
);
803 if (elementAttrName
) {
804 nameValue
= StringValue(elementAttrName
);
806 if (!cx
->compartment()->wrap(cx
, &nameValue
)) {
810 source
->setReservedSlot(ELEMENT_PROPERTY_SLOT
, nameValue
);
815 void ScriptSourceObject::setPrivate(JSRuntime
* rt
, const Value
& value
) {
816 // Update the private value, calling addRef/release hooks if necessary
817 // to allow the embedding to maintain a reference count for the
819 JS::AutoSuppressGCAnalysis nogc
;
820 Value prevValue
= getReservedSlot(PRIVATE_SLOT
);
821 rt
->releaseScriptPrivate(prevValue
);
822 setReservedSlot(PRIVATE_SLOT
, value
);
823 rt
->addRefScriptPrivate(value
);
826 void ScriptSourceObject::clearPrivate(JSRuntime
* rt
) {
827 // Clear the private value, calling release hook if necessary.
828 // |this| may be gray, be careful not to create edges to it.
829 JS::AutoSuppressGCAnalysis nogc
;
830 Value prevValue
= getReservedSlot(PRIVATE_SLOT
);
831 rt
->releaseScriptPrivate(prevValue
);
832 getSlotRef(PRIVATE_SLOT
).setUndefinedUnchecked();
835 class ScriptSource::LoadSourceMatcher
{
836 JSContext
* const cx_
;
837 ScriptSource
* const ss_
;
841 explicit LoadSourceMatcher(JSContext
* cx
, ScriptSource
* ss
, bool* loaded
)
842 : cx_(cx
), ss_(ss
), loaded_(loaded
) {}
844 template <typename Unit
, SourceRetrievable CanRetrieve
>
845 bool operator()(const Compressed
<Unit
, CanRetrieve
>&) const {
850 template <typename Unit
, SourceRetrievable CanRetrieve
>
851 bool operator()(const Uncompressed
<Unit
, CanRetrieve
>&) const {
856 template <typename Unit
>
857 bool operator()(const Retrievable
<Unit
>&) {
858 if (!cx_
->runtime()->sourceHook
.ref()) {
865 // The first argument is just for overloading -- its value doesn't matter.
866 if (!tryLoadAndSetSource(Unit('0'), &length
)) {
873 bool operator()(const Missing
&) const {
879 bool tryLoadAndSetSource(const Utf8Unit
&, size_t* length
) const {
881 if (!cx_
->runtime()->sourceHook
->load(cx_
, ss_
->filename(), nullptr,
882 &utf8Source
, length
)) {
891 if (!ss_
->setRetrievedSource(
892 cx_
, EntryUnits
<Utf8Unit
>(reinterpret_cast<Utf8Unit
*>(utf8Source
)),
901 bool tryLoadAndSetSource(const char16_t
&, size_t* length
) const {
902 char16_t
* utf16Source
;
903 if (!cx_
->runtime()->sourceHook
->load(cx_
, ss_
->filename(), &utf16Source
,
913 if (!ss_
->setRetrievedSource(cx_
, EntryUnits
<char16_t
>(utf16Source
),
924 bool ScriptSource::loadSource(JSContext
* cx
, ScriptSource
* ss
, bool* loaded
) {
925 return ss
->data
.match(LoadSourceMatcher(cx
, ss
, loaded
));
929 JSLinearString
* JSScript::sourceData(JSContext
* cx
, HandleScript script
) {
930 MOZ_ASSERT(script
->scriptSource()->hasSourceText());
931 return script
->scriptSource()->substring(cx
, script
->sourceStart(),
932 script
->sourceEnd());
935 bool BaseScript::appendSourceDataForToString(JSContext
* cx
, StringBuffer
& buf
) {
936 MOZ_ASSERT(scriptSource()->hasSourceText());
937 return scriptSource()->appendSubstring(cx
, buf
, toStringStart(),
941 void UncompressedSourceCache::holdEntry(AutoHoldEntry
& holder
,
942 const ScriptSourceChunk
& ssc
) {
943 MOZ_ASSERT(!holder_
);
944 holder
.holdEntry(this, ssc
);
948 void UncompressedSourceCache::releaseEntry(AutoHoldEntry
& holder
) {
949 MOZ_ASSERT(holder_
== &holder
);
953 template <typename Unit
>
954 const Unit
* UncompressedSourceCache::lookup(const ScriptSourceChunk
& ssc
,
955 AutoHoldEntry
& holder
) {
956 MOZ_ASSERT(!holder_
);
957 MOZ_ASSERT(ssc
.ss
->isCompressed
<Unit
>());
963 if (Map::Ptr p
= map_
->lookup(ssc
)) {
964 holdEntry(holder
, ssc
);
965 return static_cast<const Unit
*>(p
->value().get());
971 bool UncompressedSourceCache::put(const ScriptSourceChunk
& ssc
, SourceData data
,
972 AutoHoldEntry
& holder
) {
973 MOZ_ASSERT(!holder_
);
976 map_
= MakeUnique
<Map
>();
982 if (!map_
->put(ssc
, std::move(data
))) {
986 holdEntry(holder
, ssc
);
990 void UncompressedSourceCache::purge() {
995 for (Map::Range r
= map_
->all(); !r
.empty(); r
.popFront()) {
996 if (holder_
&& r
.front().key() == holder_
->sourceChunk()) {
997 holder_
->deferDelete(std::move(r
.front().value()));
1005 size_t UncompressedSourceCache::sizeOfExcludingThis(
1006 mozilla::MallocSizeOf mallocSizeOf
) {
1008 if (map_
&& !map_
->empty()) {
1009 n
+= map_
->shallowSizeOfIncludingThis(mallocSizeOf
);
1010 for (Map::Range r
= map_
->all(); !r
.empty(); r
.popFront()) {
1011 n
+= mallocSizeOf(r
.front().value().get());
1017 template <typename Unit
>
1018 const Unit
* ScriptSource::chunkUnits(
1019 JSContext
* cx
, UncompressedSourceCache::AutoHoldEntry
& holder
,
1021 const CompressedData
<Unit
>& c
= *compressedData
<Unit
>();
1023 ScriptSourceChunk
ssc(this, chunk
);
1024 if (const Unit
* decompressed
=
1025 cx
->caches().uncompressedSourceCache
.lookup
<Unit
>(ssc
, holder
)) {
1026 return decompressed
;
1029 size_t totalLengthInBytes
= length() * sizeof(Unit
);
1030 size_t chunkBytes
= Compressor::chunkSize(totalLengthInBytes
, chunk
);
1032 MOZ_ASSERT((chunkBytes
% sizeof(Unit
)) == 0);
1033 const size_t chunkLength
= chunkBytes
/ sizeof(Unit
);
1034 EntryUnits
<Unit
> decompressed(js_pod_malloc
<Unit
>(chunkLength
));
1035 if (!decompressed
) {
1036 JS_ReportOutOfMemory(cx
);
1040 // Compression treats input and output memory as plain ol' bytes. These
1041 // reinterpret_cast<>s accord exactly with that.
1042 if (!DecompressStringChunk(
1043 reinterpret_cast<const unsigned char*>(c
.raw
.chars()), chunk
,
1044 reinterpret_cast<unsigned char*>(decompressed
.get()), chunkBytes
)) {
1045 JS_ReportOutOfMemory(cx
);
1049 const Unit
* ret
= decompressed
.get();
1050 if (!cx
->caches().uncompressedSourceCache
.put(
1051 ssc
, ToSourceData(std::move(decompressed
)), holder
)) {
1052 JS_ReportOutOfMemory(cx
);
1058 template <typename Unit
>
1059 void ScriptSource::convertToCompressedSource(SharedImmutableString compressed
,
1060 size_t uncompressedLength
) {
1061 MOZ_ASSERT(isUncompressed
<Unit
>());
1062 MOZ_ASSERT(uncompressedData
<Unit
>()->length() == uncompressedLength
);
1064 if (data
.is
<Uncompressed
<Unit
, SourceRetrievable::Yes
>>()) {
1065 data
= SourceType(Compressed
<Unit
, SourceRetrievable::Yes
>(
1066 std::move(compressed
), uncompressedLength
));
1068 data
= SourceType(Compressed
<Unit
, SourceRetrievable::No
>(
1069 std::move(compressed
), uncompressedLength
));
1073 template <typename Unit
>
1074 void ScriptSource::performDelayedConvertToCompressedSource(
1075 ExclusiveData
<ReaderInstances
>::Guard
& g
) {
1076 // There might not be a conversion to compressed source happening at all.
1077 if (g
->pendingCompressed
.empty()) {
1081 CompressedData
<Unit
>& pending
=
1082 g
->pendingCompressed
.ref
<CompressedData
<Unit
>>();
1084 convertToCompressedSource
<Unit
>(std::move(pending
.raw
),
1085 pending
.uncompressedLength
);
1087 g
->pendingCompressed
.destroy();
1090 void ScriptSource::PinnedUnitsBase::addReader() {
1091 auto guard
= source_
->readers_
.lock();
1095 template <typename Unit
>
1096 void ScriptSource::PinnedUnitsBase::removeReader() {
1097 // Note: We use a Mutex with Exclusive access, such that no PinnedUnits
1098 // instance is live while we are compressing the source.
1099 auto guard
= source_
->readers_
.lock();
1100 MOZ_ASSERT(guard
->count
> 0);
1101 if (--guard
->count
) {
1102 source_
->performDelayedConvertToCompressedSource
<Unit
>(guard
);
1106 template <typename Unit
>
1107 ScriptSource::PinnedUnits
<Unit
>::~PinnedUnits() {
1109 removeReader
<Unit
>();
1113 template <typename Unit
>
1114 ScriptSource::PinnedUnitsIfUncompressed
<Unit
>::~PinnedUnitsIfUncompressed() {
1116 removeReader
<Unit
>();
1120 template <typename Unit
>
1121 const Unit
* ScriptSource::units(JSContext
* cx
,
1122 UncompressedSourceCache::AutoHoldEntry
& holder
,
1123 size_t begin
, size_t len
) {
1124 MOZ_ASSERT(begin
<= length());
1125 MOZ_ASSERT(begin
+ len
<= length());
1127 if (isUncompressed
<Unit
>()) {
1128 const Unit
* units
= uncompressedData
<Unit
>()->units();
1132 return units
+ begin
;
1135 if (data
.is
<Missing
>()) {
1136 MOZ_CRASH("ScriptSource::units() on ScriptSource with missing source");
1139 if (data
.is
<Retrievable
<Unit
>>()) {
1140 MOZ_CRASH("ScriptSource::units() on ScriptSource with retrievable source");
1143 MOZ_ASSERT(isCompressed
<Unit
>());
1145 // Determine first/last chunks, the offset (in bytes) into the first chunk
1146 // of the requested units, and the number of bytes in the last chunk.
1148 // Note that first and last chunk sizes are miscomputed and *must not be
1149 // used* when the first chunk is the last chunk.
1150 size_t firstChunk
, firstChunkOffset
, firstChunkSize
;
1151 size_t lastChunk
, lastChunkSize
;
1152 Compressor::rangeToChunkAndOffset(
1153 begin
* sizeof(Unit
), (begin
+ len
) * sizeof(Unit
), &firstChunk
,
1154 &firstChunkOffset
, &firstChunkSize
, &lastChunk
, &lastChunkSize
);
1155 MOZ_ASSERT(firstChunk
<= lastChunk
);
1156 MOZ_ASSERT(firstChunkOffset
% sizeof(Unit
) == 0);
1157 MOZ_ASSERT(firstChunkSize
% sizeof(Unit
) == 0);
1159 size_t firstUnit
= firstChunkOffset
/ sizeof(Unit
);
1161 // Directly return units within a single chunk. UncompressedSourceCache
1162 // and |holder| will hold the units alive past function return.
1163 if (firstChunk
== lastChunk
) {
1164 const Unit
* units
= chunkUnits
<Unit
>(cx
, holder
, firstChunk
);
1169 return units
+ firstUnit
;
1172 // Otherwise the units span multiple chunks. Copy successive chunks'
1173 // decompressed units into freshly-allocated memory to return.
1174 EntryUnits
<Unit
> decompressed(js_pod_malloc
<Unit
>(len
));
1175 if (!decompressed
) {
1176 JS_ReportOutOfMemory(cx
);
1183 // |AutoHoldEntry| is single-shot, and a holder successfully filled in
1184 // by |chunkUnits| must be destroyed before another can be used. Thus
1185 // we can't use |holder| with |chunkUnits| when |chunkUnits| is used
1186 // with multiple chunks, and we must use and destroy distinct, fresh
1187 // holders for each chunk.
1188 UncompressedSourceCache::AutoHoldEntry firstHolder
;
1189 const Unit
* units
= chunkUnits
<Unit
>(cx
, firstHolder
, firstChunk
);
1194 cursor
= std::copy_n(units
+ firstUnit
, firstChunkSize
/ sizeof(Unit
),
1195 decompressed
.get());
1198 for (size_t i
= firstChunk
+ 1; i
< lastChunk
; i
++) {
1199 UncompressedSourceCache::AutoHoldEntry chunkHolder
;
1200 const Unit
* units
= chunkUnits
<Unit
>(cx
, chunkHolder
, i
);
1205 cursor
= std::copy_n(units
, Compressor::CHUNK_SIZE
/ sizeof(Unit
), cursor
);
1209 UncompressedSourceCache::AutoHoldEntry lastHolder
;
1210 const Unit
* units
= chunkUnits
<Unit
>(cx
, lastHolder
, lastChunk
);
1215 cursor
= std::copy_n(units
, lastChunkSize
/ sizeof(Unit
), cursor
);
1218 MOZ_ASSERT(PointerRangeSize(decompressed
.get(), cursor
) == len
);
1220 // Transfer ownership to |holder|.
1221 const Unit
* ret
= decompressed
.get();
1222 holder
.holdUnits(std::move(decompressed
));
1226 template <typename Unit
>
1227 const Unit
* ScriptSource::uncompressedUnits(size_t begin
, size_t len
) {
1228 MOZ_ASSERT(begin
<= length());
1229 MOZ_ASSERT(begin
+ len
<= length());
1231 if (!isUncompressed
<Unit
>()) {
1235 const Unit
* units
= uncompressedData
<Unit
>()->units();
1239 return units
+ begin
;
1242 template <typename Unit
>
1243 ScriptSource::PinnedUnits
<Unit
>::PinnedUnits(
1244 JSContext
* cx
, ScriptSource
* source
,
1245 UncompressedSourceCache::AutoHoldEntry
& holder
, size_t begin
, size_t len
)
1246 : PinnedUnitsBase(source
) {
1247 MOZ_ASSERT(source
->hasSourceType
<Unit
>(), "must pin units of source's type");
1249 units_
= source
->units
<Unit
>(cx
, holder
, begin
, len
);
1255 template class ScriptSource::PinnedUnits
<Utf8Unit
>;
1256 template class ScriptSource::PinnedUnits
<char16_t
>;
1258 template <typename Unit
>
1259 ScriptSource::PinnedUnitsIfUncompressed
<Unit
>::PinnedUnitsIfUncompressed(
1260 ScriptSource
* source
, size_t begin
, size_t len
)
1261 : PinnedUnitsBase(source
) {
1262 MOZ_ASSERT(source
->hasSourceType
<Unit
>(), "must pin units of source's type");
1264 units_
= source
->uncompressedUnits
<Unit
>(begin
, len
);
1270 template class ScriptSource::PinnedUnitsIfUncompressed
<Utf8Unit
>;
1271 template class ScriptSource::PinnedUnitsIfUncompressed
<char16_t
>;
1273 JSLinearString
* ScriptSource::substring(JSContext
* cx
, size_t start
,
1275 MOZ_ASSERT(start
<= stop
);
1277 size_t len
= stop
- start
;
1279 return cx
->emptyString();
1281 UncompressedSourceCache::AutoHoldEntry holder
;
1283 // UTF-8 source text.
1284 if (hasSourceType
<Utf8Unit
>()) {
1285 PinnedUnits
<Utf8Unit
> units(cx
, this, holder
, start
, len
);
1286 if (!units
.asChars()) {
1290 const char* str
= units
.asChars();
1291 return NewStringCopyUTF8N(cx
, JS::UTF8Chars(str
, len
));
1294 // UTF-16 source text.
1295 PinnedUnits
<char16_t
> units(cx
, this, holder
, start
, len
);
1296 if (!units
.asChars()) {
1300 return NewStringCopyN
<CanGC
>(cx
, units
.asChars(), len
);
1303 JSLinearString
* ScriptSource::substringDontDeflate(JSContext
* cx
, size_t start
,
1305 MOZ_ASSERT(start
<= stop
);
1307 size_t len
= stop
- start
;
1309 return cx
->emptyString();
1311 UncompressedSourceCache::AutoHoldEntry holder
;
1313 // UTF-8 source text.
1314 if (hasSourceType
<Utf8Unit
>()) {
1315 PinnedUnits
<Utf8Unit
> units(cx
, this, holder
, start
, len
);
1316 if (!units
.asChars()) {
1320 const char* str
= units
.asChars();
1322 // There doesn't appear to be a non-deflating UTF-8 string creation
1323 // function -- but then again, it's not entirely clear how current
1324 // callers benefit from non-deflation.
1325 return NewStringCopyUTF8N(cx
, JS::UTF8Chars(str
, len
));
1328 // UTF-16 source text.
1329 PinnedUnits
<char16_t
> units(cx
, this, holder
, start
, len
);
1330 if (!units
.asChars()) {
1334 return NewStringCopyNDontDeflate
<CanGC
>(cx
, units
.asChars(), len
);
1337 bool ScriptSource::appendSubstring(JSContext
* cx
, StringBuffer
& buf
,
1338 size_t start
, size_t stop
) {
1339 MOZ_ASSERT(start
<= stop
);
1341 size_t len
= stop
- start
;
1342 UncompressedSourceCache::AutoHoldEntry holder
;
1344 if (hasSourceType
<Utf8Unit
>()) {
1345 PinnedUnits
<Utf8Unit
> pinned(cx
, this, holder
, start
, len
);
1346 if (!pinned
.get()) {
1349 if (len
> SourceDeflateLimit
&& !buf
.ensureTwoByteChars()) {
1353 const Utf8Unit
* units
= pinned
.get();
1354 return buf
.append(units
, len
);
1356 PinnedUnits
<char16_t
> pinned(cx
, this, holder
, start
, len
);
1357 if (!pinned
.get()) {
1360 if (len
> SourceDeflateLimit
&& !buf
.ensureTwoByteChars()) {
1364 const char16_t
* units
= pinned
.get();
1365 return buf
.append(units
, len
);
1369 JSLinearString
* ScriptSource::functionBodyString(JSContext
* cx
) {
1370 MOZ_ASSERT(isFunctionBody());
1372 size_t start
= parameterListEnd_
+ FunctionConstructorMedialSigils
.length();
1373 size_t stop
= length() - FunctionConstructorFinalBrace
.length();
1374 return substring(cx
, start
, stop
);
1377 template <typename ContextT
, typename Unit
>
1378 [[nodiscard
]] bool ScriptSource::setUncompressedSourceHelper(
1379 ContextT
* cx
, EntryUnits
<Unit
>&& source
, size_t length
,
1380 SourceRetrievable retrievable
) {
1381 auto& cache
= SharedImmutableStringsCache::getSingleton();
1383 auto uniqueChars
= SourceTypeTraits
<Unit
>::toCacheable(std::move(source
));
1384 auto deduped
= cache
.getOrCreate(std::move(uniqueChars
), length
);
1386 ReportOutOfMemory(cx
);
1390 if (retrievable
== SourceRetrievable::Yes
) {
1392 Uncompressed
<Unit
, SourceRetrievable::Yes
>(std::move(deduped
)));
1395 Uncompressed
<Unit
, SourceRetrievable::No
>(std::move(deduped
)));
1400 template <typename Unit
>
1401 [[nodiscard
]] bool ScriptSource::setRetrievedSource(JSContext
* cx
,
1402 EntryUnits
<Unit
>&& source
,
1404 MOZ_ASSERT(data
.is
<Retrievable
<Unit
>>(),
1405 "retrieved source can only overwrite the corresponding "
1406 "retrievable source");
1407 return setUncompressedSourceHelper(cx
, std::move(source
), length
,
1408 SourceRetrievable::Yes
);
1411 bool js::IsOffThreadSourceCompressionEnabled() {
1412 // If we don't have concurrent execution compression will contend with
1413 // main-thread execution, in which case we disable. Similarly we don't want to
1414 // block the thread pool if it is too small.
1415 return GetHelperThreadCPUCount() > 1 && GetHelperThreadCount() > 1 &&
1416 CanUseExtraThreads();
1419 bool ScriptSource::tryCompressOffThread(JSContext
* cx
) {
1420 // Beware: |js::SynchronouslyCompressSource| assumes that this function is
1421 // only called once, just after a script has been compiled, and it's never
1422 // called at some random time after that. If multiple calls of this can ever
1423 // occur, that function may require changes.
1425 // The SourceCompressionTask needs to record the major GC number for
1427 MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx
->runtime()));
1429 // If source compression was already attempted, do not queue a new task.
1430 if (hadCompressionTask_
) {
1434 if (!hasUncompressedSource()) {
1435 // This excludes compressed, missing, and retrievable source.
1439 // There are several cases where source compression is not a good idea:
1440 // - If the script is tiny, then compression will save little or no space.
1441 // - If there is only one core, then compression will contend with JS
1442 // execution (which hurts benchmarketing).
1444 // Otherwise, enqueue a compression task to be processed when a major
1447 if (length() < ScriptSource::MinimumCompressibleLength
||
1448 !IsOffThreadSourceCompressionEnabled()) {
1452 // Heap allocate the task. It will be freed upon compression
1453 // completing in AttachFinishedCompressedSources.
1454 auto task
= MakeUnique
<SourceCompressionTask
>(cx
->runtime(), this);
1456 ReportOutOfMemory(cx
);
1459 return EnqueueOffThreadCompression(cx
, std::move(task
));
1462 template <typename Unit
>
1463 void ScriptSource::triggerConvertToCompressedSource(
1464 SharedImmutableString compressed
, size_t uncompressedLength
) {
1465 MOZ_ASSERT(isUncompressed
<Unit
>(),
1466 "should only be triggering compressed source installation to "
1467 "overwrite identically-encoded uncompressed source");
1468 MOZ_ASSERT(uncompressedData
<Unit
>()->length() == uncompressedLength
);
1470 // If units aren't pinned -- and they probably won't be, we'd have to have a
1471 // GC in the small window of time where a |PinnedUnits| was live -- then we
1472 // can immediately convert.
1474 auto guard
= readers_
.lock();
1475 if (MOZ_LIKELY(!guard
->count
)) {
1476 convertToCompressedSource
<Unit
>(std::move(compressed
),
1477 uncompressedLength
);
1481 // Otherwise, set aside the compressed-data info. The conversion is
1482 // performed when the last |PinnedUnits| dies.
1483 MOZ_ASSERT(guard
->pendingCompressed
.empty(),
1484 "shouldn't be multiple conversions happening");
1485 guard
->pendingCompressed
.construct
<CompressedData
<Unit
>>(
1486 std::move(compressed
), uncompressedLength
);
1490 template <typename Unit
>
1491 [[nodiscard
]] bool ScriptSource::initializeWithUnretrievableCompressedSource(
1492 FrontendContext
* fc
, UniqueChars
&& compressed
, size_t rawLength
,
1493 size_t sourceLength
) {
1494 MOZ_ASSERT(data
.is
<Missing
>(), "shouldn't be double-initializing");
1495 MOZ_ASSERT(compressed
!= nullptr);
1497 auto& cache
= SharedImmutableStringsCache::getSingleton();
1498 auto deduped
= cache
.getOrCreate(std::move(compressed
), rawLength
);
1500 ReportOutOfMemory(fc
);
1506 auto guard
= readers_
.lock();
1509 "shouldn't be initializing a ScriptSource while its characters "
1510 "are pinned -- that only makes sense with a ScriptSource actively "
1515 data
= SourceType(Compressed
<Unit
, SourceRetrievable::No
>(std::move(deduped
),
1521 template bool ScriptSource::initializeWithUnretrievableCompressedSource
<
1522 Utf8Unit
>(FrontendContext
* fc
, UniqueChars
&& compressed
, size_t rawLength
,
1523 size_t sourceLength
);
1524 template bool ScriptSource::initializeWithUnretrievableCompressedSource
<
1525 char16_t
>(FrontendContext
* fc
, UniqueChars
&& compressed
, size_t rawLength
,
1526 size_t sourceLength
);
1528 template <typename Unit
>
1529 bool ScriptSource::assignSource(FrontendContext
* fc
,
1530 const ReadOnlyCompileOptions
& options
,
1531 SourceText
<Unit
>& srcBuf
) {
1532 MOZ_ASSERT(data
.is
<Missing
>(),
1533 "source assignment should only occur on fresh ScriptSources");
1535 mutedErrors_
= options
.mutedErrors();
1536 delazificationMode_
= options
.eagerDelazificationStrategy();
1538 if (options
.discardSource
) {
1542 if (options
.sourceIsLazy
) {
1543 data
= SourceType(Retrievable
<Unit
>());
1547 auto& cache
= SharedImmutableStringsCache::getSingleton();
1548 auto deduped
= cache
.getOrCreate(srcBuf
.get(), srcBuf
.length(), [&srcBuf
]() {
1549 using CharT
= typename SourceTypeTraits
<Unit
>::CharT
;
1550 return srcBuf
.ownsUnits()
1551 ? UniquePtr
<CharT
[], JS::FreePolicy
>(srcBuf
.takeChars())
1552 : DuplicateString(srcBuf
.get(), srcBuf
.length());
1555 ReportOutOfMemory(fc
);
1560 SourceType(Uncompressed
<Unit
, SourceRetrievable::No
>(std::move(deduped
)));
1564 template bool ScriptSource::assignSource(FrontendContext
* fc
,
1565 const ReadOnlyCompileOptions
& options
,
1566 SourceText
<char16_t
>& srcBuf
);
1567 template bool ScriptSource::assignSource(FrontendContext
* fc
,
1568 const ReadOnlyCompileOptions
& options
,
1569 SourceText
<Utf8Unit
>& srcBuf
);
1571 [[nodiscard
]] static bool reallocUniquePtr(UniqueChars
& unique
, size_t size
) {
1572 auto newPtr
= static_cast<char*>(js_realloc(unique
.get(), size
));
1577 // Since the realloc succeeded, unique is now holding a freed pointer.
1578 (void)unique
.release();
1579 unique
.reset(newPtr
);
1583 template <typename Unit
>
1584 void SourceCompressionTask::workEncodingSpecific() {
1585 MOZ_ASSERT(source_
->isUncompressed
<Unit
>());
1587 // Try to keep the maximum memory usage down by only allocating half the
1588 // size of the string, first.
1589 size_t inputBytes
= source_
->length() * sizeof(Unit
);
1590 size_t firstSize
= inputBytes
/ 2;
1591 UniqueChars
compressed(js_pod_malloc
<char>(firstSize
));
1596 const Unit
* chars
= source_
->uncompressedData
<Unit
>()->units();
1597 Compressor
comp(reinterpret_cast<const unsigned char*>(chars
), inputBytes
);
1602 comp
.setOutput(reinterpret_cast<unsigned char*>(compressed
.get()), firstSize
);
1604 bool reallocated
= false;
1606 if (shouldCancel()) {
1610 switch (comp
.compressMore()) {
1611 case Compressor::CONTINUE
:
1613 case Compressor::MOREOUTPUT
: {
1615 // The compressed string is longer than the original string.
1619 // The compressed output is greater than half the size of the
1620 // original string. Reallocate to the full size.
1621 if (!reallocUniquePtr(compressed
, inputBytes
)) {
1625 comp
.setOutput(reinterpret_cast<unsigned char*>(compressed
.get()),
1630 case Compressor::DONE
:
1633 case Compressor::OOM
:
1638 size_t totalBytes
= comp
.totalBytesNeeded();
1640 // Shrink the buffer to the size of the compressed data.
1641 if (!reallocUniquePtr(compressed
, totalBytes
)) {
1645 comp
.finish(compressed
.get(), totalBytes
);
1647 if (shouldCancel()) {
1651 auto& strings
= SharedImmutableStringsCache::getSingleton();
1652 resultString_
= strings
.getOrCreate(std::move(compressed
), totalBytes
);
1655 struct SourceCompressionTask::PerformTaskWork
{
1656 SourceCompressionTask
* const task_
;
1658 explicit PerformTaskWork(SourceCompressionTask
* task
) : task_(task
) {}
1660 template <typename Unit
, SourceRetrievable CanRetrieve
>
1661 void operator()(const ScriptSource::Uncompressed
<Unit
, CanRetrieve
>&) {
1662 task_
->workEncodingSpecific
<Unit
>();
1665 template <typename T
>
1666 void operator()(const T
&) {
1668 "why are we compressing missing, missing-but-retrievable, "
1669 "or already-compressed source?");
1673 void ScriptSource::performTaskWork(SourceCompressionTask
* task
) {
1674 MOZ_ASSERT(hasUncompressedSource());
1675 data
.match(SourceCompressionTask::PerformTaskWork(task
));
1678 void SourceCompressionTask::runTask() {
1679 if (shouldCancel()) {
1683 MOZ_ASSERT(source_
->hasUncompressedSource());
1685 source_
->performTaskWork(this);
1688 void SourceCompressionTask::runHelperThreadTask(
1689 AutoLockHelperThreadState
& locked
) {
1691 AutoUnlockHelperThreadState
unlock(locked
);
1696 AutoEnterOOMUnsafeRegion oomUnsafe
;
1697 if (!HelperThreadState().compressionFinishedList(locked
).append(this)) {
1698 oomUnsafe
.crash("SourceCompressionTask::runHelperThreadTask");
1703 void ScriptSource::triggerConvertToCompressedSourceFromTask(
1704 SharedImmutableString compressed
) {
1705 data
.match(TriggerConvertToCompressedSourceFromTask(this, compressed
));
1708 void SourceCompressionTask::complete() {
1709 if (!shouldCancel() && resultString_
) {
1710 source_
->triggerConvertToCompressedSourceFromTask(std::move(resultString_
));
1714 bool js::SynchronouslyCompressSource(JSContext
* cx
,
1715 JS::Handle
<BaseScript
*> script
) {
1716 // Finish all pending source compressions, including the single compression
1717 // task that may have been created (by |ScriptSource::tryCompressOffThread|)
1718 // just after the script was compiled. Because we have flushed this queue,
1719 // no code below needs to synchronize with an off-thread parse task that
1720 // assumes the immutability of a |ScriptSource|'s data.
1722 // This *may* end up compressing |script|'s source. If it does -- we test
1723 // this below -- that takes care of things. But if it doesn't, we will
1724 // synchronously compress ourselves (and as noted above, this won't race
1726 RunPendingSourceCompressions(cx
->runtime());
1728 ScriptSource
* ss
= script
->scriptSource();
1731 auto guard
= ss
->readers_
.lock();
1732 MOZ_ASSERT(guard
->count
== 0,
1733 "can't synchronously compress while source units are in use");
1737 // In principle a previously-triggered compression on a helper thread could
1738 // have already completed. If that happens, there's nothing more to do.
1739 if (ss
->hasCompressedSource()) {
1743 MOZ_ASSERT(ss
->hasUncompressedSource(),
1744 "shouldn't be compressing uncompressible source");
1746 // Use an explicit scope to delineate the lifetime of |task|, for simplicity.
1749 uint32_t sourceRefs
= ss
->refs
;
1751 MOZ_ASSERT(sourceRefs
> 0, "at least |script| here should have a ref");
1753 // |SourceCompressionTask::shouldCancel| can periodically result in source
1754 // compression being canceled if we're not careful. Guarantee that two refs
1755 // to |ss| are always live in this function (at least one preexisting and
1756 // one held by the task) so that compression is never canceled.
1757 auto task
= MakeUnique
<SourceCompressionTask
>(cx
->runtime(), ss
);
1759 ReportOutOfMemory(cx
);
1763 MOZ_ASSERT(ss
->refs
> sourceRefs
, "must have at least two refs now");
1765 // Attempt to compress. This may not succeed if OOM happens, but (because
1766 // it ordinarily happens on a helper thread) no error will ever be set here.
1767 MOZ_ASSERT(!cx
->isExceptionPending());
1768 ss
->performTaskWork(task
.get());
1769 MOZ_ASSERT(!cx
->isExceptionPending());
1771 // Convert |ss| from uncompressed to compressed data.
1774 MOZ_ASSERT(!cx
->isExceptionPending());
1777 // The only way source won't be compressed here is if OOM happened.
1778 return ss
->hasCompressedSource();
1781 void ScriptSource::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf
,
1782 JS::ScriptSourceInfo
* info
) const {
1783 info
->misc
+= mallocSizeOf(this);
1787 bool ScriptSource::startIncrementalEncoding(
1789 UniquePtr
<frontend::ExtensibleCompilationStencil
>&& initial
) {
1790 // We don't support asm.js in XDR.
1791 // Encoding failures are reported by the xdrFinalizeEncoder function.
1792 if (initial
->asmJS
) {
1796 // Remove the reference to the source, to avoid the circular reference.
1797 initial
->source
= nullptr;
1799 AutoIncrementalTimer
timer(cx
->realm()->timers
.xdrEncodingTime
);
1800 auto failureCase
= mozilla::MakeScopeExit([&] { xdrEncoder_
.reset(); });
1802 if (!xdrEncoder_
.setInitial(
1803 cx
, std::forward
<UniquePtr
<frontend::ExtensibleCompilationStencil
>>(
1805 // On encoding failure, let failureCase destroy encoder and return true
1806 // to avoid failing any currently executing script.
1810 failureCase
.release();
1814 bool ScriptSource::addDelazificationToIncrementalEncoding(
1815 JSContext
* cx
, const frontend::CompilationStencil
& stencil
) {
1816 MOZ_ASSERT(hasEncoder());
1817 AutoIncrementalTimer
timer(cx
->realm()->timers
.xdrEncodingTime
);
1818 auto failureCase
= mozilla::MakeScopeExit([&] { xdrEncoder_
.reset(); });
1820 if (!xdrEncoder_
.addDelazification(cx
, stencil
)) {
1821 // On encoding failure, let failureCase destroy encoder and return true
1822 // to avoid failing any currently executing script.
1826 failureCase
.release();
1830 bool ScriptSource::xdrFinalizeEncoder(JSContext
* cx
,
1831 JS::TranscodeBuffer
& buffer
) {
1832 if (!hasEncoder()) {
1833 JS_ReportErrorASCII(cx
, "XDR encoding failure");
1837 auto cleanup
= mozilla::MakeScopeExit([&] { xdrEncoder_
.reset(); });
1839 AutoReportFrontendContext
fc(cx
);
1840 XDRStencilEncoder
encoder(&fc
, buffer
);
1842 frontend::BorrowingCompilationStencil
borrowingStencil(
1843 xdrEncoder_
.merger_
->getResult());
1844 XDRResult res
= encoder
.codeStencil(this, borrowingStencil
);
1846 if (JS::IsTranscodeFailureResult(res
.unwrapErr())) {
1847 fc
.clearAutoReport();
1848 JS_ReportErrorASCII(cx
, "XDR encoding failure");
1855 void ScriptSource::xdrAbortEncoder() { xdrEncoder_
.reset(); }
1857 template <typename Unit
>
1858 [[nodiscard
]] bool ScriptSource::initializeUnretrievableUncompressedSource(
1859 FrontendContext
* fc
, EntryUnits
<Unit
>&& source
, size_t length
) {
1860 MOZ_ASSERT(data
.is
<Missing
>(), "must be initializing a fresh ScriptSource");
1861 return setUncompressedSourceHelper(fc
, std::move(source
), length
,
1862 SourceRetrievable::No
);
1865 template bool ScriptSource::initializeUnretrievableUncompressedSource(
1866 FrontendContext
* fc
, EntryUnits
<Utf8Unit
>&& source
, size_t length
);
1867 template bool ScriptSource::initializeUnretrievableUncompressedSource(
1868 FrontendContext
* fc
, EntryUnits
<char16_t
>&& source
, size_t length
);
1870 // Format and return a cx->pod_malloc'ed URL for a generated script like:
1871 // {filename} line {lineno} > {introducer}
1873 // foo.js line 7 > eval
1874 // indicating code compiled by the call to 'eval' on line 7 of foo.js.
1875 UniqueChars
js::FormatIntroducedFilename(const char* filename
, uint32_t lineno
,
1876 const char* introducer
) {
1877 // Compute the length of the string in advance, so we can allocate a
1878 // buffer of the right size on the first shot.
1880 // (JS_smprintf would be perfect, as that allocates the result
1881 // dynamically as it formats the string, but it won't allocate from cx,
1882 // and wants us to use a special free function.)
1884 size_t filenameLen
= strlen(filename
);
1885 size_t linenoLen
= SprintfLiteral(linenoBuf
, "%u", lineno
);
1886 size_t introducerLen
= strlen(introducer
);
1887 size_t len
= filenameLen
+ 6 /* == strlen(" line ") */ + linenoLen
+
1888 3 /* == strlen(" > ") */ + introducerLen
+ 1 /* \0 */;
1889 UniqueChars
formatted(js_pod_malloc
<char>(len
));
1894 mozilla::DebugOnly
<size_t> checkLen
= snprintf(
1895 formatted
.get(), len
, "%s line %s > %s", filename
, linenoBuf
, introducer
);
1896 MOZ_ASSERT(checkLen
== len
- 1);
1901 bool ScriptSource::initFromOptions(FrontendContext
* fc
,
1902 const ReadOnlyCompileOptions
& options
) {
1903 MOZ_ASSERT(!filename_
);
1904 MOZ_ASSERT(!introducerFilename_
);
1906 mutedErrors_
= options
.mutedErrors();
1907 delazificationMode_
= options
.eagerDelazificationStrategy();
1909 startLine_
= options
.lineno
;
1911 JS::LimitedColumnNumberZeroOrigin::fromUnlimited(options
.column
);
1912 introductionType_
= options
.introductionType
;
1913 setIntroductionOffset(options
.introductionOffset
);
1914 // The parameterListEnd_ is initialized later by setParameterListEnd, before
1915 // we expose any scripts that use this ScriptSource to the debugger.
1917 if (options
.hasIntroductionInfo
) {
1918 MOZ_ASSERT(options
.introductionType
!= nullptr);
1919 const char* filename
=
1920 options
.filename() ? options
.filename().c_str() : "<unknown>";
1921 UniqueChars formatted
= FormatIntroducedFilename(
1922 filename
, options
.introductionLineno
, options
.introductionType
);
1924 ReportOutOfMemory(fc
);
1927 if (!setFilename(fc
, std::move(formatted
))) {
1930 } else if (options
.filename()) {
1931 if (!setFilename(fc
, options
.filename().c_str())) {
1936 if (options
.introducerFilename()) {
1937 if (!setIntroducerFilename(fc
, options
.introducerFilename().c_str())) {
1945 // Use the SharedImmutableString map to deduplicate input string. The input
1946 // string must be null-terminated.
1947 template <typename SharedT
, typename CharT
>
1948 static SharedT
GetOrCreateStringZ(FrontendContext
* fc
,
1949 UniquePtr
<CharT
[], JS::FreePolicy
>&& str
) {
1950 size_t lengthWithNull
= std::char_traits
<CharT
>::length(str
.get()) + 1;
1951 auto res
= SharedImmutableStringsCache::getSingleton().getOrCreate(
1952 std::move(str
), lengthWithNull
);
1954 ReportOutOfMemory(fc
);
1959 SharedImmutableString
ScriptSource::getOrCreateStringZ(FrontendContext
* fc
,
1960 UniqueChars
&& str
) {
1961 return GetOrCreateStringZ
<SharedImmutableString
>(fc
, std::move(str
));
1964 SharedImmutableTwoByteString
ScriptSource::getOrCreateStringZ(
1965 FrontendContext
* fc
, UniqueTwoByteChars
&& str
) {
1966 return GetOrCreateStringZ
<SharedImmutableTwoByteString
>(fc
, std::move(str
));
1969 bool ScriptSource::setFilename(FrontendContext
* fc
, const char* filename
) {
1970 UniqueChars owned
= DuplicateString(fc
, filename
);
1974 return setFilename(fc
, std::move(owned
));
1977 bool ScriptSource::setFilename(FrontendContext
* fc
, UniqueChars
&& filename
) {
1978 MOZ_ASSERT(!filename_
);
1979 filename_
= getOrCreateStringZ(fc
, std::move(filename
));
1982 mozilla::HashStringKnownLength(filename_
.chars(), filename_
.length());
1988 bool ScriptSource::setIntroducerFilename(FrontendContext
* fc
,
1989 const char* filename
) {
1990 UniqueChars owned
= DuplicateString(fc
, filename
);
1994 return setIntroducerFilename(fc
, std::move(owned
));
1997 bool ScriptSource::setIntroducerFilename(FrontendContext
* fc
,
1998 UniqueChars
&& filename
) {
1999 MOZ_ASSERT(!introducerFilename_
);
2000 introducerFilename_
= getOrCreateStringZ(fc
, std::move(filename
));
2001 return bool(introducerFilename_
);
2004 bool ScriptSource::setDisplayURL(FrontendContext
* fc
, const char16_t
* url
) {
2005 UniqueTwoByteChars owned
= DuplicateString(fc
, url
);
2009 return setDisplayURL(fc
, std::move(owned
));
2012 bool ScriptSource::setDisplayURL(FrontendContext
* fc
,
2013 UniqueTwoByteChars
&& url
) {
2014 MOZ_ASSERT(!hasDisplayURL());
2016 if (url
[0] == '\0') {
2020 displayURL_
= getOrCreateStringZ(fc
, std::move(url
));
2021 return bool(displayURL_
);
2024 bool ScriptSource::setSourceMapURL(FrontendContext
* fc
, const char16_t
* url
) {
2025 UniqueTwoByteChars owned
= DuplicateString(fc
, url
);
2029 return setSourceMapURL(fc
, std::move(owned
));
2032 bool ScriptSource::setSourceMapURL(FrontendContext
* fc
,
2033 UniqueTwoByteChars
&& url
) {
2035 if (url
[0] == '\0') {
2039 sourceMapURL_
= getOrCreateStringZ(fc
, std::move(url
));
2040 return bool(sourceMapURL_
);
2043 /* static */ mozilla::Atomic
<uint32_t, mozilla::SequentiallyConsistent
>
2044 ScriptSource::idCount_
;
2047 * [SMDOC] JSScript data layout (immutable)
2049 * Script data that shareable across processes. There are no pointers (GC or
2050 * otherwise) and the data is relocatable.
2052 * Array elements Pointed to by Length
2053 * -------------- ------------- ------
2054 * jsbytecode code() codeLength()
2055 * jsscrnote notes() noteLength()
2056 * uint32_t resumeOffsets()
2057 * ScopeNote scopeNotes()
2058 * TryNote tryNotes()
2061 /* static */ CheckedInt
<uint32_t> ImmutableScriptData::sizeFor(
2062 uint32_t codeLength
, uint32_t noteLength
, uint32_t numResumeOffsets
,
2063 uint32_t numScopeNotes
, uint32_t numTryNotes
) {
2064 // Take a count of which optional arrays will be used and need offset info.
2065 unsigned numOptionalArrays
= unsigned(numResumeOffsets
> 0) +
2066 unsigned(numScopeNotes
> 0) +
2067 unsigned(numTryNotes
> 0);
2069 // Compute size including trailing arrays.
2070 CheckedInt
<uint32_t> size
= sizeof(ImmutableScriptData
);
2071 size
+= sizeof(Flags
);
2072 size
+= CheckedInt
<uint32_t>(codeLength
) * sizeof(jsbytecode
);
2073 size
+= CheckedInt
<uint32_t>(noteLength
) * sizeof(SrcNote
);
2074 size
+= CheckedInt
<uint32_t>(numOptionalArrays
) * sizeof(Offset
);
2075 size
+= CheckedInt
<uint32_t>(numResumeOffsets
) * sizeof(uint32_t);
2076 size
+= CheckedInt
<uint32_t>(numScopeNotes
) * sizeof(ScopeNote
);
2077 size
+= CheckedInt
<uint32_t>(numTryNotes
) * sizeof(TryNote
);
2082 js::UniquePtr
<ImmutableScriptData
> js::ImmutableScriptData::new_(
2083 FrontendContext
* fc
, uint32_t codeLength
, uint32_t noteLength
,
2084 uint32_t numResumeOffsets
, uint32_t numScopeNotes
, uint32_t numTryNotes
) {
2085 auto size
= sizeFor(codeLength
, noteLength
, numResumeOffsets
, numScopeNotes
,
2087 if (!size
.isValid()) {
2088 ReportAllocationOverflow(fc
);
2092 // Allocate contiguous raw buffer.
2093 void* raw
= fc
->getAllocator()->pod_malloc
<uint8_t>(size
.value());
2094 MOZ_ASSERT(uintptr_t(raw
) % alignof(ImmutableScriptData
) == 0);
2099 // Constuct the ImmutableScriptData. Trailing arrays are uninitialized but
2100 // GCPtrs are put into a safe state.
2101 UniquePtr
<ImmutableScriptData
> result(new (raw
) ImmutableScriptData(
2102 codeLength
, noteLength
, numResumeOffsets
, numScopeNotes
, numTryNotes
));
2108 MOZ_ASSERT(result
->endOffset() == size
.value());
2113 js::UniquePtr
<ImmutableScriptData
> js::ImmutableScriptData::new_(
2114 FrontendContext
* fc
, uint32_t totalSize
) {
2115 void* raw
= fc
->getAllocator()->pod_malloc
<uint8_t>(totalSize
);
2116 MOZ_ASSERT(uintptr_t(raw
) % alignof(ImmutableScriptData
) == 0);
2117 UniquePtr
<ImmutableScriptData
> result(
2118 reinterpret_cast<ImmutableScriptData
*>(raw
));
2122 bool js::ImmutableScriptData::validateLayout(uint32_t expectedSize
) {
2123 constexpr size_t HeaderSize
= sizeof(js::ImmutableScriptData
);
2124 constexpr size_t OptionalOffsetsMaxSize
= 3 * sizeof(Offset
);
2126 // Check that the optional-offsets array lies within the allocation before we
2127 // try to read from it while computing sizes. Remember that the array *ends*
2128 // at the `optArrayOffset_`.
2129 static_assert(OptionalOffsetsMaxSize
<= HeaderSize
);
2130 if (HeaderSize
> optArrayOffset_
) {
2133 if (optArrayOffset_
> expectedSize
) {
2137 // Round-trip the size computation using `CheckedInt` to detect overflow. This
2138 // should indirectly validate most alignment, size, and ordering requirments.
2139 auto size
= sizeFor(codeLength(), noteLength(), resumeOffsets().size(),
2140 scopeNotes().size(), tryNotes().size());
2141 return size
.isValid() && (size
.value() == expectedSize
);
2145 SharedImmutableScriptData
* SharedImmutableScriptData::create(
2146 FrontendContext
* fc
) {
2147 return fc
->getAllocator()->new_
<SharedImmutableScriptData
>();
2151 SharedImmutableScriptData
* SharedImmutableScriptData::createWith(
2152 FrontendContext
* fc
, js::UniquePtr
<ImmutableScriptData
>&& isd
) {
2153 MOZ_ASSERT(isd
.get());
2154 SharedImmutableScriptData
* sisd
= create(fc
);
2159 sisd
->setOwn(std::move(isd
));
2163 void JSScript::relazify(JSRuntime
* rt
) {
2164 js::Scope
* scope
= enclosingScope();
2165 UniquePtr
<PrivateScriptData
> scriptData
;
2167 // Any JIT compiles should have been released, so we already point to the
2168 // interpreter trampoline which supports lazy scripts.
2169 MOZ_ASSERT_IF(jit::HasJitBackend(), isUsingInterpreterTrampoline(rt
));
2171 // Without bytecode, the script counts are invalid so destroy them if they
2173 destroyScriptCounts();
2175 // Release the bytecode and gcthings list.
2176 // NOTE: We clear the PrivateScriptData to nullptr. This is fine because we
2177 // only allowed relazification (via AllowRelazify) if the original lazy
2178 // script we compiled from had a nullptr PrivateScriptData.
2179 swapData(scriptData
);
2182 // We should not still be in any side-tables for the debugger or
2183 // code-coverage. The finalizer will not be able to clean them up once
2184 // bytecode is released. We check in JSFunction::maybeRelazify() for these
2185 // conditions before requesting relazification.
2186 MOZ_ASSERT(!coverage::IsLCovEnabled());
2187 MOZ_ASSERT(!hasScriptCounts());
2188 MOZ_ASSERT(!hasDebugScript());
2190 // Rollback warmUpData_ to have enclosingScope.
2191 MOZ_ASSERT(warmUpData_
.isWarmUpCount(),
2192 "JitScript should already be released");
2193 warmUpData_
.resetWarmUpCount(0);
2194 warmUpData_
.initEnclosingScope(scope
);
2196 MOZ_ASSERT(isReadyForDelazification());
2199 // Takes ownership of the passed SharedImmutableScriptData and either adds it
2200 // into the runtime's SharedImmutableScriptDataTable, or frees it if a matching
2201 // entry already exists and replaces the passed RefPtr with the existing entry.
2203 bool SharedImmutableScriptData::shareScriptData(
2204 FrontendContext
* fc
, RefPtr
<SharedImmutableScriptData
>& sisd
) {
2206 MOZ_ASSERT(sisd
->refCount() == 1);
2208 SharedImmutableScriptData
* data
= sisd
.get();
2210 SharedImmutableScriptData::Hasher::Lookup
lookup(data
);
2212 Maybe
<AutoLockGlobalScriptData
> lock
;
2213 js::SharedImmutableScriptDataTable
& table
=
2214 fc
->scriptDataTableHolder()->getMaybeLocked(lock
);
2216 SharedImmutableScriptDataTable::AddPtr p
= table
.lookupForAdd(lookup
);
2218 MOZ_ASSERT(data
!= *p
);
2221 if (!table
.add(p
, data
)) {
2222 ReportOutOfMemory(fc
);
2226 // Being in the table counts as a reference on the script data.
2230 // Refs: sisd argument, SharedImmutableScriptDataTable
2231 MOZ_ASSERT(sisd
->refCount() >= 2);
2236 static void SweepScriptDataTable(SharedImmutableScriptDataTable
& table
) {
2237 // Entries are removed from the table when their reference count is one,
2238 // i.e. when the only reference to them is from the table entry.
2240 for (SharedImmutableScriptDataTable::Enum
e(table
); !e
.empty();
2242 SharedImmutableScriptData
* sharedData
= e
.front();
2243 if (sharedData
->refCount() == 1) {
2244 sharedData
->Release();
2250 void js::SweepScriptData(JSRuntime
* rt
) {
2251 SweepScriptDataTable(rt
->scriptDataTableHolder().getWithoutLock());
2253 AutoLockGlobalScriptData lock
;
2254 SweepScriptDataTable(js::globalSharedScriptDataTableHolder
.get(lock
));
2257 inline size_t PrivateScriptData::allocationSize() const { return endOffset(); }
2259 // Initialize and placement-new the trailing arrays.
2260 PrivateScriptData::PrivateScriptData(uint32_t ngcthings
)
2261 : ngcthings(ngcthings
) {
2262 // Variable-length data begins immediately after PrivateScriptData itself.
2263 // NOTE: Alignment is computed using cursor/offset so the alignment of
2264 // PrivateScriptData must be stricter than any trailing array type.
2265 Offset cursor
= sizeof(PrivateScriptData
);
2267 // Layout and initialize the gcthings array.
2269 initElements
<JS::GCCellPtr
>(cursor
, ngcthings
);
2270 cursor
+= ngcthings
* sizeof(JS::GCCellPtr
);
2274 MOZ_ASSERT(endOffset() == cursor
);
2278 PrivateScriptData
* PrivateScriptData::new_(JSContext
* cx
, uint32_t ngcthings
) {
2279 // Compute size including trailing arrays.
2280 CheckedInt
<Offset
> size
= sizeof(PrivateScriptData
);
2281 size
+= CheckedInt
<Offset
>(ngcthings
) * sizeof(JS::GCCellPtr
);
2282 if (!size
.isValid()) {
2283 ReportAllocationOverflow(cx
);
2287 // Allocate contiguous raw buffer for the trailing arrays.
2288 void* raw
= cx
->pod_malloc
<uint8_t>(size
.value());
2289 MOZ_ASSERT(uintptr_t(raw
) % alignof(PrivateScriptData
) == 0);
2294 // Constuct the PrivateScriptData. Trailing arrays are uninitialized but
2295 // GCPtrs are put into a safe state.
2296 PrivateScriptData
* result
= new (raw
) PrivateScriptData(ngcthings
);
2302 MOZ_ASSERT(result
->endOffset() == size
.value());
2308 bool PrivateScriptData::InitFromStencil(
2309 JSContext
* cx
, js::HandleScript script
,
2310 const js::frontend::CompilationAtomCache
& atomCache
,
2311 const js::frontend::CompilationStencil
& stencil
,
2312 js::frontend::CompilationGCOutput
& gcOutput
,
2313 const js::frontend::ScriptIndex scriptIndex
) {
2314 js::frontend::ScriptStencil
& scriptStencil
= stencil
.scriptData
[scriptIndex
];
2315 uint32_t ngcthings
= scriptStencil
.gcThingsLength
;
2317 MOZ_ASSERT(ngcthings
<= INDEX_LIMIT
);
2319 // Create and initialize PrivateScriptData
2320 if (!JSScript::createPrivateScriptData(cx
, script
, ngcthings
)) {
2324 js::PrivateScriptData
* data
= script
->data_
;
2326 if (!EmitScriptThingsVector(cx
, atomCache
, stencil
, gcOutput
,
2327 scriptStencil
.gcthings(stencil
),
2328 data
->gcthings())) {
2336 void PrivateScriptData::trace(JSTracer
* trc
) {
2337 for (JS::GCCellPtr
& elem
: gcthings()) {
2338 TraceManuallyBarrieredGCCellPtr(trc
, &elem
, "script-gcthing");
2343 JSScript
* JSScript::Create(JSContext
* cx
, JS::Handle
<JSFunction
*> function
,
2344 js::Handle
<ScriptSourceObject
*> sourceObject
,
2345 const SourceExtent
& extent
,
2346 js::ImmutableScriptFlags flags
) {
2347 return static_cast<JSScript
*>(
2348 BaseScript::New(cx
, function
, sourceObject
, extent
, flags
));
2352 uint32_t JSScript::vtuneMethodID() {
2353 if (!zone()->scriptVTuneIdMap
) {
2354 auto map
= MakeUnique
<ScriptVTuneIdMap
>();
2356 MOZ_CRASH("Failed to allocate ScriptVTuneIdMap");
2359 zone()->scriptVTuneIdMap
= std::move(map
);
2362 ScriptVTuneIdMap::AddPtr p
= zone()->scriptVTuneIdMap
->lookupForAdd(this);
2367 MOZ_ASSERT(this->hasBytecode());
2369 uint32_t id
= vtune::GenerateUniqueMethodID();
2370 if (!zone()->scriptVTuneIdMap
->add(p
, this, id
)) {
2371 MOZ_CRASH("Failed to add vtune method id");
2379 bool JSScript::createPrivateScriptData(JSContext
* cx
, HandleScript script
,
2380 uint32_t ngcthings
) {
2383 UniquePtr
<PrivateScriptData
> data(PrivateScriptData::new_(cx
, ngcthings
));
2388 script
->swapData(data
);
2395 bool JSScript::fullyInitFromStencil(
2396 JSContext
* cx
, const js::frontend::CompilationAtomCache
& atomCache
,
2397 const js::frontend::CompilationStencil
& stencil
,
2398 frontend::CompilationGCOutput
& gcOutput
, HandleScript script
,
2399 const js::frontend::ScriptIndex scriptIndex
) {
2400 MutableScriptFlags lazyMutableFlags
;
2401 Rooted
<Scope
*> lazyEnclosingScope(cx
);
2403 // A holder for the lazy PrivateScriptData that we must keep around in case
2404 // this process fails and we must return the script to its original state.
2406 // This is initialized by BaseScript::swapData() which will run pre-barriers
2407 // for us. On successful conversion to non-lazy script, the old script data
2408 // here will be released by the UniquePtr.
2409 Rooted
<UniquePtr
<PrivateScriptData
>> lazyData(cx
);
2411 // Whether we are a newborn script or an existing lazy script, we should
2412 // already be pointing to the interpreter trampoline.
2413 MOZ_ASSERT_IF(jit::HasJitBackend(),
2414 script
->isUsingInterpreterTrampoline(cx
->runtime()));
2416 // If we are using an existing lazy script, record enough info to be able to
2417 // rollback on failure.
2418 if (script
->isReadyForDelazification()) {
2419 lazyMutableFlags
= script
->mutableFlags_
;
2420 lazyEnclosingScope
= script
->releaseEnclosingScope();
2421 script
->swapData(lazyData
.get());
2422 MOZ_ASSERT(script
->sharedData_
== nullptr);
2425 // Restore the script to lazy state on failure. If this was a fresh script, we
2426 // just need to clear bytecode to mark script as incomplete.
2427 auto rollbackGuard
= mozilla::MakeScopeExit([&] {
2428 if (lazyEnclosingScope
) {
2429 script
->mutableFlags_
= lazyMutableFlags
;
2430 script
->warmUpData_
.initEnclosingScope(lazyEnclosingScope
);
2431 script
->swapData(lazyData
.get());
2432 script
->sharedData_
= nullptr;
2434 MOZ_ASSERT(script
->isReadyForDelazification());
2436 script
->sharedData_
= nullptr;
2440 // The counts of indexed things must be checked during code generation.
2441 MOZ_ASSERT(stencil
.scriptData
[scriptIndex
].gcThingsLength
<= INDEX_LIMIT
);
2443 // Note: These flags should already be correct when the BaseScript was
2445 MOZ_ASSERT_IF(stencil
.isInitialStencil(),
2446 script
->immutableFlags() ==
2447 stencil
.scriptExtra
[scriptIndex
].immutableFlags
);
2449 // Create and initialize PrivateScriptData
2450 if (!PrivateScriptData::InitFromStencil(cx
, script
, atomCache
, stencil
,
2451 gcOutput
, scriptIndex
)) {
2455 // Member-initializer data is computed in initial parse only. If we are
2456 // delazifying, make sure to copy it off the `lazyData` before we throw it
2458 if (script
->useMemberInitializers()) {
2459 if (stencil
.isInitialStencil()) {
2460 MemberInitializers
initializers(
2461 stencil
.scriptExtra
[scriptIndex
].memberInitializers());
2462 script
->setMemberInitializers(initializers
);
2464 script
->setMemberInitializers(lazyData
.get()->getMemberInitializers());
2468 auto* scriptData
= stencil
.sharedData
.get(scriptIndex
);
2470 script
->isGenerator() || script
->isAsync(),
2471 scriptData
->nfixed() <= frontend::ParseContext::Scope::FixedSlotLimit
);
2473 script
->initSharedData(scriptData
);
2475 // NOTE: JSScript is now constructed and should be linked in.
2476 rollbackGuard
.release();
2478 // Link Scope -> JSFunction -> BaseScript.
2479 if (script
->isFunction()) {
2480 JSFunction
* fun
= gcOutput
.getFunction(scriptIndex
);
2481 script
->bodyScope()->as
<FunctionScope
>().initCanonicalFunction(fun
);
2482 if (fun
->isIncomplete()) {
2483 fun
->initScript(script
);
2484 } else if (fun
->hasSelfHostedLazyScript()) {
2485 fun
->clearSelfHostedLazyScript();
2486 fun
->initScript(script
);
2488 // We are delazifying in-place.
2489 MOZ_ASSERT(fun
->baseScript() == script
);
2493 // NOTE: The caller is responsible for linking ModuleObjects if this is a
2496 #ifdef JS_STRUCTURED_SPEW
2497 // We want this to happen after line number initialization to allow filtering
2499 script
->setSpewEnabled(cx
->spewer().enabled(script
));
2503 script
->assertValidJumpTargets();
2506 if (coverage::IsLCovEnabled()) {
2507 if (!coverage::InitScriptCoverage(cx
, script
)) {
2515 JSScript
* JSScript::fromStencil(JSContext
* cx
,
2516 frontend::CompilationAtomCache
& atomCache
,
2517 const frontend::CompilationStencil
& stencil
,
2518 frontend::CompilationGCOutput
& gcOutput
,
2519 frontend::ScriptIndex scriptIndex
) {
2520 js::frontend::ScriptStencil
& scriptStencil
= stencil
.scriptData
[scriptIndex
];
2521 js::frontend::ScriptStencilExtra
& scriptExtra
=
2522 stencil
.scriptExtra
[scriptIndex
];
2523 MOZ_ASSERT(scriptStencil
.hasSharedData(),
2524 "Need generated bytecode to use JSScript::fromStencil");
2526 Rooted
<JSFunction
*> function(cx
);
2527 if (scriptStencil
.isFunction()) {
2528 function
= gcOutput
.getFunction(scriptIndex
);
2531 Rooted
<ScriptSourceObject
*> sourceObject(cx
, gcOutput
.sourceObject
);
2532 RootedScript
script(cx
, Create(cx
, function
, sourceObject
, scriptExtra
.extent
,
2533 scriptExtra
.immutableFlags
));
2538 if (!fullyInitFromStencil(cx
, atomCache
, stencil
, gcOutput
, script
,
2547 void JSScript::assertValidJumpTargets() const {
2548 BytecodeLocation mainLoc
= mainLocation();
2549 BytecodeLocation endLoc
= endLocation();
2550 AllBytecodesIterable
iter(this);
2551 for (BytecodeLocation loc
: iter
) {
2552 // Check jump instructions' target.
2554 BytecodeLocation target
= loc
.getJumpTarget();
2555 MOZ_ASSERT(mainLoc
<= target
&& target
< endLoc
);
2556 MOZ_ASSERT(target
.isJumpTarget());
2558 // All backward jumps must be to a JSOp::LoopHead op. This is an invariant
2559 // we want to maintain to simplify JIT compilation and bytecode analysis.
2560 MOZ_ASSERT_IF(target
< loc
, target
.is(JSOp::LoopHead
));
2561 MOZ_ASSERT_IF(target
< loc
, IsBackedgePC(loc
.toRawBytecode()));
2563 // All forward jumps must be to a JSOp::JumpTarget op.
2564 MOZ_ASSERT_IF(target
> loc
, target
.is(JSOp::JumpTarget
));
2566 // Jumps must not cross scope boundaries.
2567 MOZ_ASSERT(loc
.innermostScope(this) == target
.innermostScope(this));
2569 // Check fallthrough of conditional jump instructions.
2570 if (loc
.fallsThrough()) {
2571 BytecodeLocation fallthrough
= loc
.next();
2572 MOZ_ASSERT(mainLoc
<= fallthrough
&& fallthrough
< endLoc
);
2573 MOZ_ASSERT(fallthrough
.isJumpTarget());
2577 // Check table switch case labels.
2578 if (loc
.is(JSOp::TableSwitch
)) {
2579 BytecodeLocation target
= loc
.getTableSwitchDefaultTarget();
2582 MOZ_ASSERT(mainLoc
<= target
&& target
< endLoc
);
2583 MOZ_ASSERT(target
.is(JSOp::JumpTarget
));
2585 int32_t low
= loc
.getTableSwitchLow();
2586 int32_t high
= loc
.getTableSwitchHigh();
2588 for (int i
= 0; i
< high
- low
+ 1; i
++) {
2589 BytecodeLocation switchCase
= loc
.getTableSwitchCaseTarget(this, i
);
2590 MOZ_ASSERT(mainLoc
<= switchCase
&& switchCase
< endLoc
);
2591 MOZ_ASSERT(switchCase
.is(JSOp::JumpTarget
));
2596 // Check catch/finally blocks as jump targets.
2597 for (const TryNote
& tn
: trynotes()) {
2598 if (tn
.kind() != TryNoteKind::Catch
&& tn
.kind() != TryNoteKind::Finally
) {
2602 jsbytecode
* tryStart
= offsetToPC(tn
.start
);
2603 jsbytecode
* tryPc
= tryStart
- JSOpLength_Try
;
2604 MOZ_ASSERT(JSOp(*tryPc
) == JSOp::Try
);
2606 jsbytecode
* tryTarget
= tryStart
+ tn
.length
;
2607 MOZ_ASSERT(main() <= tryTarget
&& tryTarget
< codeEnd());
2608 MOZ_ASSERT(BytecodeIsJumpTarget(JSOp(*tryTarget
)));
2613 void JSScript::addSizeOfJitScript(mozilla::MallocSizeOf mallocSizeOf
,
2614 size_t* sizeOfJitScript
,
2615 size_t* sizeOfBaselineFallbackStubs
) const {
2616 if (!hasJitScript()) {
2620 jitScript()->addSizeOfIncludingThis(mallocSizeOf
, sizeOfJitScript
,
2621 sizeOfBaselineFallbackStubs
);
2624 js::GlobalObject
& JSScript::uninlinedGlobal() const { return global(); }
2626 unsigned js::PCToLineNumber(unsigned startLine
,
2627 JS::LimitedColumnNumberZeroOrigin startCol
,
2628 SrcNote
* notes
, SrcNote
* notesEnd
, jsbytecode
* code
,
2630 JS::LimitedColumnNumberZeroOrigin
* columnp
) {
2631 unsigned lineno
= startLine
;
2632 JS::LimitedColumnNumberZeroOrigin column
= startCol
;
2635 * Walk through source notes accumulating their deltas, keeping track of
2636 * line-number notes, until we pass the note for pc's offset within
2639 ptrdiff_t offset
= 0;
2640 ptrdiff_t target
= pc
- code
;
2641 for (SrcNoteIterator
iter(notes
, notesEnd
); !iter
.atEnd(); ++iter
) {
2642 const auto* sn
= *iter
;
2643 offset
+= sn
->delta();
2644 if (offset
> target
) {
2648 SrcNoteType type
= sn
->type();
2649 if (type
== SrcNoteType::SetLine
) {
2650 lineno
= SrcNote::SetLine::getLine(sn
, startLine
);
2651 column
= JS::LimitedColumnNumberZeroOrigin::zero();
2652 } else if (type
== SrcNoteType::SetLineColumn
) {
2653 lineno
= SrcNote::SetLineColumn::getLine(sn
, startLine
);
2654 column
= SrcNote::SetLineColumn::getColumn(sn
);
2655 } else if (type
== SrcNoteType::NewLine
) {
2657 column
= JS::LimitedColumnNumberZeroOrigin::zero();
2658 } else if (type
== SrcNoteType::NewLineColumn
) {
2660 column
= SrcNote::NewLineColumn::getColumn(sn
);
2661 } else if (type
== SrcNoteType::ColSpan
) {
2662 column
+= SrcNote::ColSpan::getSpan(sn
);
2673 unsigned js::PCToLineNumber(JSScript
* script
, jsbytecode
* pc
,
2674 JS::LimitedColumnNumberZeroOrigin
* columnp
) {
2675 /* Cope with InterpreterFrame.pc value prior to entering Interpret. */
2680 return PCToLineNumber(script
->lineno(), script
->column(), script
->notes(),
2681 script
->notesEnd(), script
->code(), pc
, columnp
);
2684 jsbytecode
* js::LineNumberToPC(JSScript
* script
, unsigned target
) {
2685 ptrdiff_t offset
= 0;
2686 ptrdiff_t best
= -1;
2687 unsigned lineno
= script
->lineno();
2688 unsigned bestdiff
= SrcNote::MaxOperand
;
2689 for (SrcNoteIterator
iter(script
->notes(), script
->notesEnd()); !iter
.atEnd();
2691 const auto* sn
= *iter
;
2693 * Exact-match only if offset is not in the prologue; otherwise use
2694 * nearest greater-or-equal line number match.
2696 if (lineno
== target
&& offset
>= ptrdiff_t(script
->mainOffset())) {
2699 if (lineno
>= target
) {
2700 unsigned diff
= lineno
- target
;
2701 if (diff
< bestdiff
) {
2706 offset
+= sn
->delta();
2707 SrcNoteType type
= sn
->type();
2708 if (type
== SrcNoteType::SetLine
) {
2709 lineno
= SrcNote::SetLine::getLine(sn
, script
->lineno());
2710 } else if (type
== SrcNoteType::SetLineColumn
) {
2711 lineno
= SrcNote::SetLineColumn::getLine(sn
, script
->lineno());
2712 } else if (type
== SrcNoteType::NewLine
||
2713 type
== SrcNoteType::NewLineColumn
) {
2721 return script
->offsetToPC(offset
);
2724 JS_PUBLIC_API
unsigned js::GetScriptLineExtent(JSScript
* script
) {
2725 unsigned lineno
= script
->lineno();
2726 unsigned maxLineNo
= lineno
;
2727 for (SrcNoteIterator
iter(script
->notes(), script
->notesEnd()); !iter
.atEnd();
2729 const auto* sn
= *iter
;
2730 SrcNoteType type
= sn
->type();
2731 if (type
== SrcNoteType::SetLine
) {
2732 lineno
= SrcNote::SetLine::getLine(sn
, script
->lineno());
2733 } else if (type
== SrcNoteType::SetLineColumn
) {
2734 lineno
= SrcNote::SetLineColumn::getLine(sn
, script
->lineno());
2735 } else if (type
== SrcNoteType::NewLine
||
2736 type
== SrcNoteType::NewLineColumn
) {
2740 if (maxLineNo
< lineno
) {
2745 return 1 + maxLineNo
- script
->lineno();
2748 #ifdef JS_CACHEIR_SPEW
2749 void js::maybeUpdateWarmUpCount(JSScript
* script
) {
2750 if (script
->needsFinalWarmUpCount()) {
2751 ScriptFinalWarmUpCountMap
* map
=
2752 script
->zone()->scriptFinalWarmUpCountMap
.get();
2753 // If needsFinalWarmUpCount is true, ScriptFinalWarmUpCountMap must have
2754 // already been created and thus must be asserted.
2756 ScriptFinalWarmUpCountMap::Ptr p
= map
->lookup(script
);
2759 std::get
<0>(p
->value()) += script
->jitScript()->warmUpCount();
2763 void js::maybeSpewScriptFinalWarmUpCount(JSScript
* script
) {
2764 if (script
->needsFinalWarmUpCount()) {
2765 ScriptFinalWarmUpCountMap
* map
=
2766 script
->zone()->scriptFinalWarmUpCountMap
.get();
2767 // If needsFinalWarmUpCount is true, ScriptFinalWarmUpCountMap must have
2768 // already been created and thus must be asserted.
2770 ScriptFinalWarmUpCountMap::Ptr p
= map
->lookup(script
);
2772 auto& tuple
= p
->value();
2773 uint32_t warmUpCount
= std::get
<0>(tuple
);
2774 SharedImmutableString
& scriptName
= std::get
<1>(tuple
);
2776 JSContext
* cx
= TlsContext
.get();
2777 cx
->spewer().enableSpewing();
2779 // In the case that we care about a script's final warmup count but the
2780 // spewer is not enabled, AutoSpewChannel automatically sets and unsets
2781 // the proper channel for the duration of spewing a health report's warm
2783 AutoSpewChannel
channel(cx
, SpewChannel::CacheIRHealthReport
, script
);
2784 jit::CacheIRHealth cih
;
2785 cih
.spewScriptFinalWarmUpCount(cx
, scriptName
.chars(), script
, warmUpCount
);
2787 script
->zone()->scriptFinalWarmUpCountMap
->remove(script
);
2788 script
->setNeedsFinalWarmUpCount(false);
2793 void js::DescribeScriptedCallerForDirectEval(JSContext
* cx
, HandleScript script
,
2794 jsbytecode
* pc
, const char** file
,
2797 bool* mutedErrors
) {
2798 MOZ_ASSERT(script
->containsPC(pc
));
2800 static_assert(JSOpLength_SpreadEval
== JSOpLength_StrictSpreadEval
,
2801 "next op after a spread must be at consistent offset");
2802 static_assert(JSOpLength_Eval
== JSOpLength_StrictEval
,
2803 "next op after a direct eval must be at consistent offset");
2805 MOZ_ASSERT(JSOp(*pc
) == JSOp::Eval
|| JSOp(*pc
) == JSOp::StrictEval
||
2806 JSOp(*pc
) == JSOp::SpreadEval
||
2807 JSOp(*pc
) == JSOp::StrictSpreadEval
);
2810 (JSOp(*pc
) == JSOp::SpreadEval
|| JSOp(*pc
) == JSOp::StrictSpreadEval
);
2811 jsbytecode
* nextpc
=
2812 pc
+ (isSpread
? JSOpLength_SpreadEval
: JSOpLength_Eval
);
2813 MOZ_ASSERT(JSOp(*nextpc
) == JSOp::Lineno
);
2815 *file
= script
->filename();
2816 *linenop
= GET_UINT32(nextpc
);
2817 *pcOffset
= script
->pcToOffset(pc
);
2818 *mutedErrors
= script
->mutedErrors();
2821 void js::DescribeScriptedCallerForCompilation(
2822 JSContext
* cx
, MutableHandleScript maybeScript
, const char** file
,
2823 uint32_t* linenop
, uint32_t* pcOffset
, bool* mutedErrors
) {
2824 NonBuiltinFrameIter
iter(cx
, cx
->realm()->principals());
2827 maybeScript
.set(nullptr);
2831 *mutedErrors
= false;
2835 *file
= iter
.filename();
2836 *linenop
= iter
.computeLine();
2837 *mutedErrors
= iter
.mutedErrors();
2839 // These values are only used for introducer fields which are debugging
2840 // information and can be safely left null for wasm frames.
2841 if (iter
.hasScript()) {
2842 maybeScript
.set(iter
.script());
2843 *pcOffset
= iter
.pc() - maybeScript
->code();
2845 maybeScript
.set(nullptr);
2850 template <typename SourceSpan
, typename TargetSpan
>
2851 void CopySpan(const SourceSpan
& source
, TargetSpan target
) {
2852 MOZ_ASSERT(source
.size() == target
.size());
2853 std::copy(source
.cbegin(), source
.cend(), target
.begin());
2857 js::UniquePtr
<ImmutableScriptData
> ImmutableScriptData::new_(
2858 FrontendContext
* fc
, uint32_t mainOffset
, uint32_t nfixed
, uint32_t nslots
,
2859 GCThingIndex bodyScopeIndex
, uint32_t numICEntries
, bool isFunction
,
2860 uint16_t funLength
, uint16_t propertyCountEstimate
,
2861 mozilla::Span
<const jsbytecode
> code
, mozilla::Span
<const SrcNote
> notes
,
2862 mozilla::Span
<const uint32_t> resumeOffsets
,
2863 mozilla::Span
<const ScopeNote
> scopeNotes
,
2864 mozilla::Span
<const TryNote
> tryNotes
) {
2865 MOZ_RELEASE_ASSERT(code
.Length() <= frontend::MaxBytecodeLength
);
2867 // There are 1-4 copies of SrcNoteType::Null appended after the source
2868 // notes. These are a combination of sentinel and padding values.
2869 static_assert(frontend::MaxSrcNotesLength
<= UINT32_MAX
- CodeNoteAlign
,
2870 "Length + CodeNoteAlign shouldn't overflow UINT32_MAX");
2871 size_t noteLength
= notes
.Length();
2872 MOZ_RELEASE_ASSERT(noteLength
<= frontend::MaxSrcNotesLength
);
2874 size_t notePaddingLength
= ComputeNotePadding(code
.Length(), noteLength
);
2876 // Allocate ImmutableScriptData
2877 js::UniquePtr
<ImmutableScriptData
> data(ImmutableScriptData::new_(
2878 fc
, code
.Length(), noteLength
+ notePaddingLength
, resumeOffsets
.Length(),
2879 scopeNotes
.Length(), tryNotes
.Length()));
2884 // Initialize POD fields
2885 data
->mainOffset
= mainOffset
;
2886 data
->nfixed
= nfixed
;
2887 data
->nslots
= nslots
;
2888 data
->bodyScopeIndex
= bodyScopeIndex
;
2889 data
->numICEntries
= numICEntries
;
2890 data
->propertyCountEstimate
= propertyCountEstimate
;
2893 data
->funLength
= funLength
;
2896 // Initialize trailing arrays
2897 CopySpan(code
, data
->codeSpan());
2898 CopySpan(notes
, data
->notesSpan().To(noteLength
));
2899 std::fill_n(data
->notes() + noteLength
, notePaddingLength
,
2900 SrcNote::padding());
2901 CopySpan(resumeOffsets
, data
->resumeOffsets());
2902 CopySpan(scopeNotes
, data
->scopeNotes());
2903 CopySpan(tryNotes
, data
->tryNotes());
2908 void ScriptWarmUpData::trace(JSTracer
* trc
) {
2909 uintptr_t tag
= data_
& TagMask
;
2911 case EnclosingScriptTag
: {
2912 BaseScript
* enclosingScript
= toEnclosingScript();
2913 BaseScript
* prior
= enclosingScript
;
2914 TraceManuallyBarrieredEdge(trc
, &enclosingScript
, "enclosingScript");
2915 if (enclosingScript
!= prior
) {
2916 setTaggedPtr
<EnclosingScriptTag
>(enclosingScript
);
2921 case EnclosingScopeTag
: {
2922 Scope
* enclosingScope
= toEnclosingScope();
2923 Scope
* prior
= enclosingScope
;
2924 TraceManuallyBarrieredEdge(trc
, &enclosingScope
, "enclosingScope");
2925 if (enclosingScope
!= prior
) {
2926 setTaggedPtr
<EnclosingScopeTag
>(enclosingScope
);
2931 case JitScriptTag
: {
2932 toJitScript()->trace(trc
);
2937 MOZ_ASSERT(isWarmUpCount());
2943 size_t JSScript::calculateLiveFixed(jsbytecode
* pc
) {
2944 size_t nlivefixed
= numAlwaysLiveFixedSlots();
2946 if (nfixed() != nlivefixed
) {
2947 Scope
* scope
= lookupScope(pc
);
2949 scope
= MaybeForwarded(scope
);
2952 // Find the nearest LexicalScope in the same script.
2953 while (scope
&& scope
->is
<WithScope
>()) {
2954 scope
= scope
->enclosing();
2956 scope
= MaybeForwarded(scope
);
2961 if (scope
->is
<LexicalScope
>()) {
2962 nlivefixed
= scope
->as
<LexicalScope
>().nextFrameSlot();
2963 } else if (scope
->is
<VarScope
>()) {
2964 nlivefixed
= scope
->as
<VarScope
>().nextFrameSlot();
2965 } else if (scope
->is
<ClassBodyScope
>()) {
2966 nlivefixed
= scope
->as
<ClassBodyScope
>().nextFrameSlot();
2971 MOZ_ASSERT(nlivefixed
<= nfixed());
2972 MOZ_ASSERT(nlivefixed
>= numAlwaysLiveFixedSlots());
2977 Scope
* JSScript::lookupScope(const jsbytecode
* pc
) const {
2978 MOZ_ASSERT(containsPC(pc
));
2980 size_t offset
= pc
- code();
2982 auto notes
= scopeNotes();
2983 Scope
* scope
= nullptr;
2985 // Find the innermost block chain using a binary search.
2987 size_t top
= notes
.size();
2989 while (bottom
< top
) {
2990 size_t mid
= bottom
+ (top
- bottom
) / 2;
2991 const ScopeNote
* note
= ¬es
[mid
];
2992 if (note
->start
<= offset
) {
2993 // Block scopes are ordered in the list by their starting offset, and
2994 // since blocks form a tree ones earlier in the list may cover the pc even
2995 // if later blocks end before the pc. This only happens when the earlier
2996 // block is a parent of the later block, so we need to check parents of
2997 // |mid| in the searched range for coverage.
2999 while (check
>= bottom
) {
3000 const ScopeNote
* checkNote
= ¬es
[check
];
3001 MOZ_ASSERT(checkNote
->start
<= offset
);
3002 if (offset
< checkNote
->start
+ checkNote
->length
) {
3003 // We found a matching block chain but there may be inner ones
3004 // at a higher block chain index than mid. Continue the binary search.
3005 if (checkNote
->index
== ScopeNote::NoScopeIndex
) {
3008 scope
= getScope(checkNote
->index
);
3012 if (checkNote
->parent
== UINT32_MAX
) {
3015 check
= checkNote
->parent
;
3026 Scope
* JSScript::innermostScope(const jsbytecode
* pc
) const {
3027 if (Scope
* scope
= lookupScope(pc
)) {
3033 void js::SetFrameArgumentsObject(JSContext
* cx
, AbstractFramePtr frame
,
3034 HandleScript script
, JSObject
* argsobj
) {
3036 * If the arguments object was optimized out by scalar replacement,
3037 * we must recreate it when we bail out. Because 'arguments' may have
3038 * already been overwritten, we must check to see if the slot already
3042 Rooted
<BindingIter
> bi(cx
, BindingIter(script
));
3043 while (bi
&& bi
.name() != cx
->names().arguments
) {
3050 if (bi
.location().kind() == BindingLocation::Kind::Environment
) {
3053 * If |arguments| lives in the call object, we should not have
3054 * optimized it. Scan the script to find the slot in the call
3055 * object that |arguments| is assigned to and verify that it
3058 jsbytecode
* pc
= script
->code();
3059 while (JSOp(*pc
) != JSOp::Arguments
) {
3060 pc
+= GetBytecodeLength(pc
);
3062 pc
+= JSOpLength_Arguments
;
3063 MOZ_ASSERT(JSOp(*pc
) == JSOp::SetAliasedVar
);
3065 EnvironmentObject
& env
= frame
.callObj().as
<EnvironmentObject
>();
3066 MOZ_ASSERT(!env
.aliasedBinding(bi
).isMagic(JS_OPTIMIZED_OUT
));
3071 MOZ_ASSERT(bi
.location().kind() == BindingLocation::Kind::Frame
);
3072 uint32_t frameSlot
= bi
.location().slot();
3073 if (frame
.unaliasedLocal(frameSlot
).isMagic(JS_OPTIMIZED_OUT
)) {
3074 frame
.unaliasedLocal(frameSlot
) = ObjectValue(*argsobj
);
3078 bool JSScript::formalIsAliased(unsigned argSlot
) {
3079 if (functionHasParameterExprs()) {
3083 for (PositionalFormalParameterIter
fi(this); fi
; fi
++) {
3084 if (fi
.argumentSlot() == argSlot
) {
3085 return fi
.closedOver();
3088 MOZ_CRASH("Argument slot not found");
3091 // Returns true if any formal argument is mapped by the arguments
3092 // object, but lives in the call object.
3093 bool JSScript::anyFormalIsForwarded() {
3094 if (!argsObjAliasesFormals()) {
3098 for (PositionalFormalParameterIter
fi(this); fi
; fi
++) {
3099 if (fi
.closedOver()) {
3106 bool JSScript::formalLivesInArgumentsObject(unsigned argSlot
) {
3107 return argsObjAliasesFormals() && !formalIsAliased(argSlot
);
3110 BaseScript::BaseScript(uint8_t* stubEntry
, JSFunction
* function
,
3111 ScriptSourceObject
* sourceObject
,
3112 const SourceExtent
& extent
, uint32_t immutableFlags
)
3113 : TenuredCellWithNonGCPointer(stubEntry
),
3114 function_(function
),
3115 sourceObject_(sourceObject
),
3117 immutableFlags_(immutableFlags
) {
3118 MOZ_ASSERT(extent_
.toStringStart
<= extent_
.sourceStart
);
3119 MOZ_ASSERT(extent_
.sourceStart
<= extent_
.sourceEnd
);
3120 MOZ_ASSERT(extent_
.sourceEnd
<= extent_
.toStringEnd
);
3124 BaseScript
* BaseScript::New(JSContext
* cx
, JS::Handle
<JSFunction
*> function
,
3125 Handle
<ScriptSourceObject
*> sourceObject
,
3126 const SourceExtent
& extent
,
3127 uint32_t immutableFlags
) {
3128 uint8_t* stubEntry
= nullptr;
3129 if (jit::HasJitBackend()) {
3130 stubEntry
= cx
->runtime()->jitRuntime()->interpreterStub().value
;
3133 MOZ_ASSERT_IF(function
,
3134 function
->compartment() == sourceObject
->compartment());
3135 MOZ_ASSERT_IF(function
, function
->realm() == sourceObject
->realm());
3137 return cx
->newCell
<BaseScript
>(stubEntry
, function
, sourceObject
, extent
,
3142 BaseScript
* BaseScript::CreateRawLazy(JSContext
* cx
, uint32_t ngcthings
,
3144 Handle
<ScriptSourceObject
*> sourceObject
,
3145 const SourceExtent
& extent
,
3146 uint32_t immutableFlags
) {
3149 BaseScript
* lazy
= New(cx
, fun
, sourceObject
, extent
, immutableFlags
);
3154 // Allocate a PrivateScriptData if it will not be empty. Lazy class
3155 // constructors that use member initializers also need PrivateScriptData for
3158 // This condition is implicit in BaseScript::hasPrivateScriptData, and should
3159 // be mirrored on InputScript::hasPrivateScriptData.
3160 if (ngcthings
|| lazy
->useMemberInitializers()) {
3161 UniquePtr
<PrivateScriptData
> data(PrivateScriptData::new_(cx
, ngcthings
));
3165 lazy
->swapData(data
);
3172 void JSScript::updateJitCodeRaw(JSRuntime
* rt
) {
3174 if (hasBaselineScript() && baselineScript()->hasPendingIonCompileTask()) {
3175 MOZ_ASSERT(!isIonCompilingOffThread());
3176 setJitCodeRaw(rt
->jitRuntime()->lazyLinkStub().value
);
3177 } else if (hasIonScript()) {
3178 jit::IonScript
* ion
= ionScript();
3179 setJitCodeRaw(ion
->method()->raw());
3180 } else if (hasBaselineScript()) {
3181 setJitCodeRaw(baselineScript()->method()->raw());
3182 } else if (hasJitScript() && js::jit::IsBaselineInterpreterEnabled()) {
3183 bool usingEntryTrampoline
= false;
3184 if (js::jit::JitOptions
.emitInterpreterEntryTrampoline
) {
3185 auto p
= rt
->jitRuntime()->getInterpreterEntryMap()->lookup(this);
3187 setJitCodeRaw(p
->value().raw());
3188 usingEntryTrampoline
= true;
3191 if (!usingEntryTrampoline
) {
3192 setJitCodeRaw(rt
->jitRuntime()->baselineInterpreter().codeRaw());
3195 setJitCodeRaw(rt
->jitRuntime()->interpreterStub().value
);
3197 MOZ_ASSERT(jitCodeRaw());
3200 bool JSScript::hasLoops() {
3201 for (const TryNote
& tn
: trynotes()) {
3209 bool JSScript::mayReadFrameArgsDirectly() {
3210 return needsArgsObj() || usesArgumentsIntrinsics() || hasRest();
3213 void JSScript::resetWarmUpCounterToDelayIonCompilation() {
3214 // Reset the warm-up count only if it's greater than the BaselineCompiler
3215 // threshold. We do this to ensure this has no effect on Baseline compilation
3216 // because we don't want scripts to get stuck in the (Baseline) interpreter in
3217 // pathological cases.
3219 if (getWarmUpCount() > jit::JitOptions
.baselineJitWarmUpThreshold
) {
3220 incWarmUpResetCounter();
3221 uint32_t newCount
= jit::JitOptions
.baselineJitWarmUpThreshold
;
3222 if (warmUpData_
.isWarmUpCount()) {
3223 warmUpData_
.resetWarmUpCount(newCount
);
3225 warmUpData_
.toJitScript()->resetWarmUpCount(newCount
);
3230 gc::AllocSite
* JSScript::createAllocSite() {
3231 return jitScript()->createAllocSite(this);
3234 #if defined(DEBUG) || defined(JS_JITSPEW)
3236 void JSScript::dump(JSContext
* cx
) {
3237 JS::Rooted
<JSScript
*> script(cx
, this);
3239 js::Sprinter
sp(cx
);
3244 DumpOptions options
;
3245 options
.runtimeData
= true;
3246 if (!dump(cx
, script
, options
, &sp
)) {
3250 fprintf(stderr
, "%s\n", sp
.string());
3253 void JSScript::dumpRecursive(JSContext
* cx
) {
3254 JS::Rooted
<JSScript
*> script(cx
, this);
3256 js::Sprinter
sp(cx
);
3261 DumpOptions options
;
3262 options
.runtimeData
= true;
3263 options
.recursive
= true;
3264 if (!dump(cx
, script
, options
, &sp
)) {
3268 fprintf(stderr
, "%s\n", sp
.string());
3271 static void DumpMutableScriptFlags(js::JSONPrinter
& json
,
3272 MutableScriptFlags mutableFlags
) {
3273 // Skip warmup data.
3274 static_assert(int(MutableScriptFlagsEnum::WarmupResets_MASK
) == 0xff);
3276 for (uint32_t i
= 0x100; i
; i
= i
<< 1) {
3277 if (uint32_t(mutableFlags
) & i
) {
3278 switch (MutableScriptFlagsEnum(i
)) {
3279 case MutableScriptFlagsEnum::HasRunOnce
:
3280 json
.value("HasRunOnce");
3282 case MutableScriptFlagsEnum::HasBeenCloned
:
3283 json
.value("HasBeenCloned");
3285 case MutableScriptFlagsEnum::HasScriptCounts
:
3286 json
.value("HasScriptCounts");
3288 case MutableScriptFlagsEnum::HasDebugScript
:
3289 json
.value("HasDebugScript");
3291 case MutableScriptFlagsEnum::AllowRelazify
:
3292 json
.value("AllowRelazify");
3294 case MutableScriptFlagsEnum::SpewEnabled
:
3295 json
.value("SpewEnabled");
3297 case MutableScriptFlagsEnum::NeedsFinalWarmUpCount
:
3298 json
.value("NeedsFinalWarmUpCount");
3300 case MutableScriptFlagsEnum::BaselineDisabled
:
3301 json
.value("BaselineDisabled");
3303 case MutableScriptFlagsEnum::IonDisabled
:
3304 json
.value("IonDisabled");
3306 case MutableScriptFlagsEnum::Uninlineable
:
3307 json
.value("Uninlineable");
3309 case MutableScriptFlagsEnum::NoEagerBaselineHint
:
3310 json
.value("NoEagerBaselineHint");
3312 case MutableScriptFlagsEnum::FailedBoundsCheck
:
3313 json
.value("FailedBoundsCheck");
3315 case MutableScriptFlagsEnum::HadLICMInvalidation
:
3316 json
.value("HadLICMInvalidation");
3318 case MutableScriptFlagsEnum::HadReorderingBailout
:
3319 json
.value("HadReorderingBailout");
3321 case MutableScriptFlagsEnum::HadEagerTruncationBailout
:
3322 json
.value("HadEagerTruncationBailout");
3324 case MutableScriptFlagsEnum::FailedLexicalCheck
:
3325 json
.value("FailedLexicalCheck");
3327 case MutableScriptFlagsEnum::HadSpeculativePhiBailout
:
3328 json
.value("HadSpeculativePhiBailout");
3330 case MutableScriptFlagsEnum::HadUnboxFoldingBailout
:
3331 json
.value("HadUnboxFoldingBailout");
3334 json
.value("Unknown(%x)", i
);
3342 bool JSScript::dump(JSContext
* cx
, JS::Handle
<JSScript
*> script
,
3343 DumpOptions
& options
, js::Sprinter
* sp
) {
3345 JSONPrinter
json(*sp
);
3349 if (const char* filename
= script
->filename()) {
3350 json
.property("file", filename
);
3352 json
.nullProperty("file");
3355 json
.property("lineno", script
->lineno());
3356 json
.property("column", script
->column().zeroOriginValue());
3358 json
.beginListProperty("immutableFlags");
3359 DumpImmutableScriptFlags(json
, script
->immutableFlags());
3362 if (options
.runtimeData
) {
3363 json
.beginListProperty("mutableFlags");
3364 DumpMutableScriptFlags(json
, script
->mutableFlags_
);
3368 if (script
->isFunction()) {
3369 JS::Rooted
<JSFunction
*> fun(cx
, script
->function());
3371 JS::Rooted
<JSAtom
*> name(cx
, fun
->displayAtom());
3373 UniqueChars bytes
= JS_EncodeStringToUTF8(cx
, name
);
3377 json
.property("functionName", bytes
.get());
3379 json
.nullProperty("functionName");
3382 json
.beginListProperty("functionFlags");
3383 DumpFunctionFlagsItems(json
, fun
->flags());
3390 if (sp
->hadOutOfMemory()) {
3394 if (!sp
->put("\n")) {
3398 if (!Disassemble(cx
, script
, /* lines = */ true, sp
)) {
3401 if (!dumpSrcNotes(cx
, script
, sp
)) {
3404 if (!dumpTryNotes(cx
, script
, sp
)) {
3407 if (!dumpScopeNotes(cx
, script
, sp
)) {
3410 if (!dumpGCThings(cx
, script
, sp
)) {
3414 if (options
.recursive
) {
3415 for (JS::GCCellPtr gcThing
: script
->gcthings()) {
3416 if (!gcThing
.is
<JSObject
>()) {
3420 JSObject
* obj
= &gcThing
.as
<JSObject
>();
3421 if (obj
->is
<JSFunction
>()) {
3422 if (!sp
->put("\n")) {
3426 JS::Rooted
<JSFunction
*> fun(cx
, &obj
->as
<JSFunction
>());
3427 if (fun
->isInterpreted()) {
3428 JS::Rooted
<JSScript
*> innerScript(
3429 cx
, JSFunction::getOrCreateScript(cx
, fun
));
3433 if (!dump(cx
, innerScript
, options
, sp
)) {
3437 if (!sp
->put("[native code]\n")) {
3449 bool JSScript::dumpSrcNotes(JSContext
* cx
, JS::Handle
<JSScript
*> script
,
3451 if (!sp
->put("\nSource notes:\n") ||
3452 !sp
->jsprintf("%4s %4s %6s %5s %6s %-16s %s\n", "ofs", "line", "column",
3453 "pc", "delta", "desc", "args") ||
3454 !sp
->put("---- ---- ------ ----- ------ ---------------- ------\n")) {
3458 unsigned offset
= 0;
3459 unsigned lineno
= script
->lineno();
3460 JS::LimitedColumnNumberZeroOrigin column
= script
->column();
3461 SrcNote
* notes
= script
->notes();
3462 SrcNote
* notesEnd
= script
->notesEnd();
3463 for (SrcNoteIterator
iter(notes
, notesEnd
); !iter
.atEnd(); ++iter
) {
3464 const auto* sn
= *iter
;
3466 unsigned delta
= sn
->delta();
3468 SrcNoteType type
= sn
->type();
3469 const char* name
= sn
->name();
3470 if (!sp
->jsprintf("%3u: %4u %6u %5u [%4u] %-16s", unsigned(sn
- notes
),
3471 lineno
, column
.zeroOriginValue(), offset
, delta
, name
)) {
3476 case SrcNoteType::Breakpoint
:
3477 case SrcNoteType::BreakpointStepSep
:
3478 case SrcNoteType::XDelta
:
3481 case SrcNoteType::ColSpan
: {
3482 JS::ColumnNumberOffset colspan
= SrcNote::ColSpan::getSpan(sn
);
3483 if (!sp
->jsprintf(" colspan %u", colspan
.value())) {
3490 case SrcNoteType::SetLine
:
3491 lineno
= SrcNote::SetLine::getLine(sn
, script
->lineno());
3492 if (!sp
->jsprintf(" lineno %u", lineno
)) {
3495 column
= JS::LimitedColumnNumberZeroOrigin::zero();
3498 case SrcNoteType::SetLineColumn
:
3499 lineno
= SrcNote::SetLineColumn::getLine(sn
, script
->lineno());
3500 column
= SrcNote::SetLineColumn::getColumn(sn
);
3501 if (!sp
->jsprintf(" lineno %u column %u", lineno
,
3502 column
.zeroOriginValue())) {
3507 case SrcNoteType::NewLine
:
3509 column
= JS::LimitedColumnNumberZeroOrigin::zero();
3512 case SrcNoteType::NewLineColumn
:
3513 column
= SrcNote::NewLineColumn::getColumn(sn
);
3514 if (!sp
->jsprintf(" column %u", column
.zeroOriginValue())) {
3522 MOZ_ASSERT_UNREACHABLE("unrecognized srcnote");
3524 if (!sp
->put("\n")) {
3532 static const char* TryNoteName(TryNoteKind kind
) {
3534 case TryNoteKind::Catch
:
3536 case TryNoteKind::Finally
:
3538 case TryNoteKind::ForIn
:
3540 case TryNoteKind::ForOf
:
3542 case TryNoteKind::Loop
:
3544 case TryNoteKind::ForOfIterClose
:
3545 return "for-of-iterclose";
3546 case TryNoteKind::Destructuring
:
3547 return "destructuring";
3550 MOZ_CRASH("Bad TryNoteKind");
3554 bool JSScript::dumpTryNotes(JSContext
* cx
, JS::Handle
<JSScript
*> script
,
3557 "\nException table:\nkind stack start end\n")) {
3561 for (const js::TryNote
& tn
: script
->trynotes()) {
3562 if (!sp
->jsprintf(" %-16s %6u %8u %8u\n", TryNoteName(tn
.kind()),
3563 tn
.stackDepth
, tn
.start
, tn
.start
+ tn
.length
)) {
3571 bool JSScript::dumpScopeNotes(JSContext
* cx
, JS::Handle
<JSScript
*> script
,
3573 if (!sp
->put("\nScope notes:\n index parent start end\n")) {
3577 for (const ScopeNote
& note
: script
->scopeNotes()) {
3578 if (note
.index
== ScopeNote::NoScopeIndex
) {
3579 if (!sp
->jsprintf("%8s ", "(none)")) {
3583 if (!sp
->jsprintf("%8u ", note
.index
.index
)) {
3587 if (note
.parent
== ScopeNote::NoScopeIndex
) {
3588 if (!sp
->jsprintf("%8s ", "(none)")) {
3592 if (!sp
->jsprintf("%8u ", note
.parent
)) {
3596 if (!sp
->jsprintf("%8u %8u\n", note
.start
, note
.start
+ note
.length
)) {
3604 bool JSScript::dumpGCThings(JSContext
* cx
, JS::Handle
<JSScript
*> script
,
3606 if (!sp
->put("\nGC things:\n index type value\n")) {
3611 for (JS::GCCellPtr gcThing
: script
->gcthings()) {
3612 if (!sp
->jsprintf("%8zu ", i
)) {
3615 if (gcThing
.is
<JS::BigInt
>()) {
3616 if (!sp
->put("BigInt ")) {
3619 gcThing
.as
<JS::BigInt
>().dump(*sp
);
3620 if (!sp
->put("\n")) {
3623 } else if (gcThing
.is
<Scope
>()) {
3624 if (!sp
->put("Scope ")) {
3627 JS::Rooted
<Scope
*> scope(cx
, &gcThing
.as
<Scope
>());
3628 if (!Scope::dumpForDisassemble(cx
, scope
, *sp
,
3632 if (!sp
->put("\n")) {
3635 } else if (gcThing
.is
<JSObject
>()) {
3636 JSObject
* obj
= &gcThing
.as
<JSObject
>();
3637 if (obj
->is
<JSFunction
>()) {
3638 if (!sp
->put("Function ")) {
3641 JS::Rooted
<JSFunction
*> fun(cx
, &obj
->as
<JSFunction
>());
3642 if (fun
->displayAtom()) {
3643 JS::Rooted
<JSAtom
*> name(cx
, fun
->displayAtom());
3644 JS::UniqueChars utf8chars
= JS_EncodeStringToUTF8(cx
, name
);
3648 if (!sp
->put(utf8chars
.get())) {
3652 if (!sp
->put("(anonymous)")) {
3657 if (fun
->hasBaseScript()) {
3658 BaseScript
* script
= fun
->baseScript();
3659 if (!sp
->jsprintf(" @ %u:%u\n", script
->lineno(),
3660 script
->column().zeroOriginValue())) {
3664 if (!sp
->put(" (no script)\n")) {
3669 if (obj
->is
<RegExpObject
>()) {
3670 if (!sp
->put("RegExp ")) {
3674 if (!sp
->put("Object ")) {
3679 JS::Rooted
<JS::Value
> objValue(cx
, ObjectValue(*obj
));
3680 JS::Rooted
<JSString
*> str(cx
, ValueToSource(cx
, objValue
));
3684 JS::UniqueChars utf8chars
= JS_EncodeStringToUTF8(cx
, str
);
3688 if (!sp
->put(utf8chars
.get())) {
3692 if (!sp
->put("\n")) {
3696 } else if (gcThing
.is
<JSString
>()) {
3697 JS::Rooted
<JSString
*> str(cx
, &gcThing
.as
<JSString
>());
3698 if (str
->isAtom()) {
3699 if (!sp
->put("Atom ")) {
3703 if (!sp
->put("String ")) {
3707 JS::UniqueChars chars
= QuoteString(cx
, str
, '"');
3711 if (!sp
->put(chars
.get())) {
3714 if (!sp
->put("\n")) {
3718 if (!sp
->put("Unknown\n")) {
3728 #endif // defined(DEBUG) || defined(JS_JITSPEW)
3730 void JSScript::AutoDelazify::holdScript(JS::HandleFunction fun
) {
3732 JSAutoRealm
ar(cx_
, fun
);
3733 script_
= JSFunction::getOrCreateScript(cx_
, fun
);
3735 oldAllowRelazify_
= script_
->allowRelazify();
3736 script_
->clearAllowRelazify();
3741 void JSScript::AutoDelazify::dropScript() {
3743 script_
->setAllowRelazify(oldAllowRelazify_
);
3748 JS::ubi::Base::Size
JS::ubi::Concrete
<BaseScript
>::size(
3749 mozilla::MallocSizeOf mallocSizeOf
) const {
3750 BaseScript
* base
= &get();
3752 Size size
= gc::Arena::thingSize(base
->getAllocKind());
3753 size
+= base
->sizeOfExcludingThis(mallocSizeOf
);
3755 // Include any JIT data if it exists.
3756 if (base
->hasJitScript()) {
3757 JSScript
* script
= base
->asJSScript();
3759 size_t jitScriptSize
= 0;
3760 size_t fallbackStubSize
= 0;
3761 script
->addSizeOfJitScript(mallocSizeOf
, &jitScriptSize
, &fallbackStubSize
);
3762 size
+= jitScriptSize
;
3763 size
+= fallbackStubSize
;
3765 size_t baselineSize
= 0;
3766 jit::AddSizeOfBaselineData(script
, mallocSizeOf
, &baselineSize
);
3767 size
+= baselineSize
;
3769 size
+= jit::SizeOfIonData(script
, mallocSizeOf
);
3772 MOZ_ASSERT(size
> 0);
3776 const char* JS::ubi::Concrete
<BaseScript
>::scriptFilename() const {
3777 return get().filename();