1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2  * vim: set ts=8 sts=2 et sw=2 tw=80:
3  * This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "jit/JitScript-inl.h"
8 
9 #include "mozilla/BinarySearch.h"
10 #include "mozilla/CheckedInt.h"
11 #include "mozilla/IntegerPrintfMacros.h"
12 #include "mozilla/ScopeExit.h"
13 
14 #include <utility>
15 
16 #include "jit/BaselineIC.h"
17 #include "jit/BytecodeAnalysis.h"
18 #include "jit/IonScript.h"
19 #include "jit/JitFrames.h"
20 #include "jit/ScriptFromCalleeToken.h"
21 #include "util/Memory.h"
22 #include "vm/BytecodeIterator.h"
23 #include "vm/BytecodeLocation.h"
24 #include "vm/BytecodeUtil.h"
25 #include "vm/FrameIter.h"  // js::OnlyJSJitFrameIter
26 #include "vm/JSScript.h"
27 #include "vm/Stack.h"
28 #include "wasm/WasmInstance.h"
29 
30 #include "gc/FreeOp-inl.h"
31 #include "jit/JSJitFrameIter-inl.h"
32 #include "vm/BytecodeIterator-inl.h"
33 #include "vm/BytecodeLocation-inl.h"
34 #include "vm/JSScript-inl.h"
35 
36 using namespace js;
37 using namespace js::jit;
38 
39 using mozilla::CheckedInt;
40 
JitScript(JSScript * script,Offset fallbackStubsOffset,Offset endOffset,const char * profileString)41 JitScript::JitScript(JSScript* script, Offset fallbackStubsOffset,
42                      Offset endOffset, const char* profileString)
43     : profileString_(profileString),
44       endOffset_(endOffset),
45       icScript_(script->getWarmUpCount(),
46                 fallbackStubsOffset - offsetOfICScript(),
47                 endOffset - offsetOfICScript(),
48                 /*depth=*/0) {
49   // Ensure the baselineScript_ and ionScript_ fields match the BaselineDisabled
50   // and IonDisabled script flags.
51   if (!script->canBaselineCompile()) {
52     setBaselineScriptImpl(script, BaselineDisabledScriptPtr);
53   }
54   if (!script->canIonCompile()) {
55     setIonScriptImpl(script, IonDisabledScriptPtr);
56   }
57 }
58 
createJitScript(JSContext * cx)59 bool JSScript::createJitScript(JSContext* cx) {
60   MOZ_ASSERT(!hasJitScript());
61   cx->check(this);
62 
63   // Scripts with a JitScript can run in the Baseline Interpreter. Make sure
64   // we don't create a JitScript for scripts we shouldn't Baseline interpret.
65   MOZ_ASSERT_IF(IsBaselineInterpreterEnabled(),
66                 CanBaselineInterpretScript(this));
67 
68   // Store the profile string in the JitScript if the profiler is enabled.
69   const char* profileString = nullptr;
70   if (cx->runtime()->geckoProfiler().enabled()) {
71     profileString = cx->runtime()->geckoProfiler().profileString(cx, this);
72     if (!profileString) {
73       return false;
74     }
75   }
76 
77   static_assert(sizeof(JitScript) % sizeof(uintptr_t) == 0,
78                 "Trailing arrays must be aligned properly");
79   static_assert(sizeof(ICEntry) % sizeof(uintptr_t) == 0,
80                 "Trailing arrays must be aligned properly");
81 
82   static_assert(
83       sizeof(JitScript) == offsetof(JitScript, icScript_) + sizeof(ICScript),
84       "icScript_ must be the last field");
85 
86   // Calculate allocation size.
87   CheckedInt<uint32_t> allocSize = sizeof(JitScript);
88   allocSize += CheckedInt<uint32_t>(numICEntries()) * sizeof(ICEntry);
89   allocSize += CheckedInt<uint32_t>(numICEntries()) * sizeof(ICFallbackStub);
90   if (!allocSize.isValid()) {
91     ReportAllocationOverflow(cx);
92     return false;
93   }
94 
95   void* raw = cx->pod_malloc<uint8_t>(allocSize.value());
96   MOZ_ASSERT(uintptr_t(raw) % alignof(JitScript) == 0);
97   if (!raw) {
98     return false;
99   }
100 
101   size_t fallbackStubsOffset =
102       sizeof(JitScript) + numICEntries() * sizeof(ICEntry);
103 
104   UniquePtr<JitScript> jitScript(new (raw) JitScript(
105       this, fallbackStubsOffset, allocSize.value(), profileString));
106 
107   // Sanity check the length computation.
108   MOZ_ASSERT(jitScript->numICEntries() == numICEntries());
109 
110   jitScript->icScript()->initICEntries(cx, this);
111 
112   warmUpData_.initJitScript(jitScript.release());
113   AddCellMemory(this, allocSize.value(), MemoryUse::JitScript);
114 
115   // We have a JitScript so we can set the script's jitCodeRaw pointer to the
116   // Baseline Interpreter code.
117   updateJitCodeRaw(cx->runtime());
118 
119   return true;
120 }
121 
maybeReleaseJitScript(JSFreeOp * fop)122 void JSScript::maybeReleaseJitScript(JSFreeOp* fop) {
123   MOZ_ASSERT(hasJitScript());
124 
125   if (zone()->jitZone()->keepJitScripts() || jitScript()->hasBaselineScript() ||
126       jitScript()->active()) {
127     return;
128   }
129 
130   releaseJitScript(fop);
131 }
132 
releaseJitScript(JSFreeOp * fop)133 void JSScript::releaseJitScript(JSFreeOp* fop) {
134   MOZ_ASSERT(hasJitScript());
135   MOZ_ASSERT(!hasBaselineScript());
136   MOZ_ASSERT(!hasIonScript());
137 
138   fop->removeCellMemory(this, jitScript()->allocBytes(), MemoryUse::JitScript);
139 
140   JitScript::Destroy(zone(), jitScript());
141   warmUpData_.clearJitScript();
142   updateJitCodeRaw(fop->runtime());
143 }
144 
releaseJitScriptOnFinalize(JSFreeOp * fop)145 void JSScript::releaseJitScriptOnFinalize(JSFreeOp* fop) {
146   MOZ_ASSERT(hasJitScript());
147 
148   if (hasIonScript()) {
149     IonScript* ion = jitScript()->clearIonScript(fop, this);
150     jit::IonScript::Destroy(fop, ion);
151   }
152 
153   if (hasBaselineScript()) {
154     BaselineScript* baseline = jitScript()->clearBaselineScript(fop, this);
155     jit::BaselineScript::Destroy(fop, baseline);
156   }
157 
158   releaseJitScript(fop);
159 }
160 
trace(JSTracer * trc)161 void JitScript::CachedIonData::trace(JSTracer* trc) {
162   TraceNullableEdge(trc, &templateEnv, "jitscript-iondata-template-env");
163 }
164 
trace(JSTracer * trc)165 void JitScript::trace(JSTracer* trc) {
166   icScript_.trace(trc);
167 
168   if (hasBaselineScript()) {
169     baselineScript()->trace(trc);
170   }
171 
172   if (hasIonScript()) {
173     ionScript()->trace(trc);
174   }
175 
176   if (hasCachedIonData()) {
177     cachedIonData().trace(trc);
178   }
179 
180   if (hasInliningRoot()) {
181     inliningRoot()->trace(trc);
182   }
183 }
184 
trace(JSTracer * trc)185 void ICScript::trace(JSTracer* trc) {
186   // Mark all IC stub codes hanging off the IC stub entries.
187   for (size_t i = 0; i < numICEntries(); i++) {
188     ICEntry& ent = icEntry(i);
189     ent.trace(trc);
190   }
191 }
192 
addInlinedChild(JSContext * cx,UniquePtr<ICScript> child,uint32_t pcOffset)193 bool ICScript::addInlinedChild(JSContext* cx, UniquePtr<ICScript> child,
194                                uint32_t pcOffset) {
195   MOZ_ASSERT(!hasInlinedChild(pcOffset));
196 
197   if (!inlinedChildren_) {
198     inlinedChildren_ = cx->make_unique<Vector<CallSite>>(cx);
199     if (!inlinedChildren_) {
200       return false;
201     }
202   }
203 
204   // First reserve space in inlinedChildren_ to ensure that if the ICScript is
205   // added to the inlining root, it can also be added to inlinedChildren_.
206   CallSite callsite(child.get(), pcOffset);
207   if (!inlinedChildren_->reserve(inlinedChildren_->length() + 1)) {
208     return false;
209   }
210   if (!inliningRoot()->addInlinedScript(std::move(child))) {
211     return false;
212   }
213   inlinedChildren_->infallibleAppend(callsite);
214   return true;
215 }
216 
findInlinedChild(uint32_t pcOffset)217 ICScript* ICScript::findInlinedChild(uint32_t pcOffset) {
218   for (auto& callsite : *inlinedChildren_) {
219     if (callsite.pcOffset_ == pcOffset) {
220       return callsite.callee_;
221     }
222   }
223   MOZ_CRASH("Inlined child expected at pcOffset");
224 }
225 
removeInlinedChild(uint32_t pcOffset)226 void ICScript::removeInlinedChild(uint32_t pcOffset) {
227   MOZ_ASSERT(inliningRoot());
228   inlinedChildren_->eraseIf([pcOffset](const CallSite& callsite) -> bool {
229     return callsite.pcOffset_ == pcOffset;
230   });
231 }
232 
hasInlinedChild(uint32_t pcOffset)233 bool ICScript::hasInlinedChild(uint32_t pcOffset) {
234   if (!inlinedChildren_) {
235     return false;
236   }
237   for (auto& callsite : *inlinedChildren_) {
238     if (callsite.pcOffset_ == pcOffset) {
239       return true;
240     }
241   }
242   return false;
243 }
244 
resetWarmUpCount(uint32_t count)245 void JitScript::resetWarmUpCount(uint32_t count) {
246   icScript_.resetWarmUpCount(count);
247   if (hasInliningRoot()) {
248     inliningRoot()->resetWarmUpCounts(count);
249   }
250 }
251 
ensureProfileString(JSContext * cx,JSScript * script)252 void JitScript::ensureProfileString(JSContext* cx, JSScript* script) {
253   MOZ_ASSERT(cx->runtime()->geckoProfiler().enabled());
254 
255   if (profileString_) {
256     return;
257   }
258 
259   AutoEnterOOMUnsafeRegion oomUnsafe;
260   profileString_ = cx->runtime()->geckoProfiler().profileString(cx, script);
261   if (!profileString_) {
262     oomUnsafe.crash("Failed to allocate profile string");
263   }
264 }
265 
266 /* static */
Destroy(Zone * zone,JitScript * script)267 void JitScript::Destroy(Zone* zone, JitScript* script) {
268   script->prepareForDestruction(zone);
269 
270   js_delete(script);
271 }
272 
273 struct FallbackStubs {
274   ICScript* const icScript_;
275 
FallbackStubsFallbackStubs276   explicit FallbackStubs(ICScript* icScript) : icScript_(icScript) {}
277 
numEntriesFallbackStubs278   size_t numEntries() const { return icScript_->numICEntries(); }
operator []FallbackStubs279   ICFallbackStub* operator[](size_t index) const {
280     return icScript_->fallbackStub(index);
281   }
282 };
283 
ComputeBinarySearchMid(FallbackStubs stubs,uint32_t pcOffset,size_t * loc)284 static bool ComputeBinarySearchMid(FallbackStubs stubs, uint32_t pcOffset,
285                                    size_t* loc) {
286   return mozilla::BinarySearchIf(
287       stubs, 0, stubs.numEntries(),
288       [pcOffset](const ICFallbackStub* stub) {
289         if (pcOffset < stub->pcOffset()) {
290           return -1;
291         }
292         if (stub->pcOffset() < pcOffset) {
293           return 1;
294         }
295         return 0;
296       },
297       loc);
298 }
299 
icEntryFromPCOffset(uint32_t pcOffset)300 ICEntry& ICScript::icEntryFromPCOffset(uint32_t pcOffset) {
301   size_t mid;
302   MOZ_ALWAYS_TRUE(ComputeBinarySearchMid(FallbackStubs(this), pcOffset, &mid));
303 
304   MOZ_ASSERT(mid < numICEntries());
305 
306   ICEntry& entry = icEntry(mid);
307   MOZ_ASSERT(fallbackStubForICEntry(&entry)->pcOffset() == pcOffset);
308   return entry;
309 }
310 
interpreterICEntryFromPCOffset(uint32_t pcOffset)311 ICEntry* ICScript::interpreterICEntryFromPCOffset(uint32_t pcOffset) {
312   // We have to return the entry to store in BaselineFrame::interpreterICEntry
313   // when resuming in the Baseline Interpreter at pcOffset. The bytecode op at
314   // pcOffset does not necessarily have an ICEntry, so we want to return the
315   // first ICEntry for which the following is true:
316   //
317   //    entry.pcOffset() >= pcOffset
318   //
319   // Fortunately, ComputeBinarySearchMid returns exactly this entry.
320 
321   size_t mid;
322   ComputeBinarySearchMid(FallbackStubs(this), pcOffset, &mid);
323 
324   if (mid < numICEntries()) {
325     ICEntry& entry = icEntry(mid);
326     MOZ_ASSERT(fallbackStubForICEntry(&entry)->pcOffset() >= pcOffset);
327     return &entry;
328   }
329 
330   // Resuming at a pc after the last ICEntry. Just return nullptr:
331   // BaselineFrame::interpreterICEntry will never be used in this case.
332   return nullptr;
333 }
334 
purgeOptimizedStubs(JSScript * script)335 void JitScript::purgeOptimizedStubs(JSScript* script) {
336   MOZ_ASSERT(script->jitScript() == this);
337 
338   Zone* zone = script->zone();
339   if (IsAboutToBeFinalizedUnbarriered(&script)) {
340     // We're sweeping and the script is dead. Don't purge optimized stubs
341     // because (1) accessing CacheIRStubInfo pointers in ICStubs is invalid
342     // because we may have swept them already when we started (incremental)
343     // sweeping and (2) it's unnecessary because this script will be finalized
344     // soon anyway.
345     return;
346   }
347 
348   JitSpew(JitSpew_BaselineIC, "Purging optimized stubs");
349 
350   icScript()->purgeOptimizedStubs(zone);
351   if (hasInliningRoot()) {
352     inliningRoot()->purgeOptimizedStubs(zone);
353   }
354 #ifdef DEBUG
355   failedICHash_.reset();
356   hasPurgedStubs_ = true;
357 #endif
358 }
359 
purgeOptimizedStubs(Zone * zone)360 void ICScript::purgeOptimizedStubs(Zone* zone) {
361   for (size_t i = 0; i < numICEntries(); i++) {
362     ICEntry& entry = icEntry(i);
363     ICStub* lastStub = entry.firstStub();
364     while (!lastStub->isFallback()) {
365       lastStub = lastStub->toCacheIRStub()->next();
366     }
367 
368     // Unlink all stubs allocated in the optimized space.
369     ICStub* stub = entry.firstStub();
370     ICCacheIRStub* prev = nullptr;
371 
372     while (stub != lastStub) {
373       if (!stub->toCacheIRStub()->allocatedInFallbackSpace()) {
374         lastStub->toFallbackStub()->unlinkStub(zone, &entry, prev,
375                                                stub->toCacheIRStub());
376         stub = stub->toCacheIRStub()->next();
377         continue;
378       }
379 
380       prev = stub->toCacheIRStub();
381       stub = stub->toCacheIRStub()->next();
382     }
383   }
384 
385 #ifdef DEBUG
386   // All remaining stubs must be allocated in the fallback space.
387   for (size_t i = 0; i < numICEntries(); i++) {
388     ICEntry& entry = icEntry(i);
389     ICStub* stub = entry.firstStub();
390     while (!stub->isFallback()) {
391       MOZ_ASSERT(stub->toCacheIRStub()->allocatedInFallbackSpace());
392       stub = stub->toCacheIRStub()->next();
393     }
394   }
395 #endif
396 }
397 
CachedIonData(EnvironmentObject * templateEnv,IonBytecodeInfo bytecodeInfo)398 JitScript::CachedIonData::CachedIonData(EnvironmentObject* templateEnv,
399                                         IonBytecodeInfo bytecodeInfo)
400     : templateEnv(templateEnv), bytecodeInfo(bytecodeInfo) {}
401 
ensureHasCachedIonData(JSContext * cx,HandleScript script)402 bool JitScript::ensureHasCachedIonData(JSContext* cx, HandleScript script) {
403   MOZ_ASSERT(script->jitScript() == this);
404 
405   if (hasCachedIonData()) {
406     return true;
407   }
408 
409   Rooted<EnvironmentObject*> templateEnv(cx);
410   if (script->function()) {
411     RootedFunction fun(cx, script->function());
412 
413     if (fun->needsNamedLambdaEnvironment()) {
414       templateEnv =
415           NamedLambdaObject::createTemplateObject(cx, fun, gc::TenuredHeap);
416       if (!templateEnv) {
417         return false;
418       }
419     }
420 
421     if (fun->needsCallObject()) {
422       templateEnv = CallObject::createTemplateObject(cx, script, templateEnv,
423                                                      gc::TenuredHeap);
424       if (!templateEnv) {
425         return false;
426       }
427     }
428   }
429 
430   IonBytecodeInfo bytecodeInfo = AnalyzeBytecodeForIon(cx, script);
431 
432   UniquePtr<CachedIonData> data =
433       cx->make_unique<CachedIonData>(templateEnv, bytecodeInfo);
434   if (!data) {
435     return false;
436   }
437 
438   cachedIonData_ = std::move(data);
439   return true;
440 }
441 
setBaselineScriptImpl(JSScript * script,BaselineScript * baselineScript)442 void JitScript::setBaselineScriptImpl(JSScript* script,
443                                       BaselineScript* baselineScript) {
444   JSRuntime* rt = script->runtimeFromMainThread();
445   setBaselineScriptImpl(rt->defaultFreeOp(), script, baselineScript);
446 }
447 
setBaselineScriptImpl(JSFreeOp * fop,JSScript * script,BaselineScript * baselineScript)448 void JitScript::setBaselineScriptImpl(JSFreeOp* fop, JSScript* script,
449                                       BaselineScript* baselineScript) {
450   if (hasBaselineScript()) {
451     BaselineScript::preWriteBarrier(script->zone(), baselineScript_);
452     fop->removeCellMemory(script, baselineScript_->allocBytes(),
453                           MemoryUse::BaselineScript);
454     baselineScript_ = nullptr;
455   }
456 
457   MOZ_ASSERT(ionScript_ == nullptr || ionScript_ == IonDisabledScriptPtr);
458 
459   baselineScript_ = baselineScript;
460   if (hasBaselineScript()) {
461     AddCellMemory(script, baselineScript_->allocBytes(),
462                   MemoryUse::BaselineScript);
463   }
464 
465   script->resetWarmUpResetCounter();
466   script->updateJitCodeRaw(fop->runtime());
467 }
468 
setIonScriptImpl(JSScript * script,IonScript * ionScript)469 void JitScript::setIonScriptImpl(JSScript* script, IonScript* ionScript) {
470   JSRuntime* rt = script->runtimeFromMainThread();
471   setIonScriptImpl(rt->defaultFreeOp(), script, ionScript);
472 }
473 
setIonScriptImpl(JSFreeOp * fop,JSScript * script,IonScript * ionScript)474 void JitScript::setIonScriptImpl(JSFreeOp* fop, JSScript* script,
475                                  IonScript* ionScript) {
476   MOZ_ASSERT_IF(ionScript != IonDisabledScriptPtr,
477                 !baselineScript()->hasPendingIonCompileTask());
478 
479   if (hasIonScript()) {
480     IonScript::preWriteBarrier(script->zone(), ionScript_);
481     fop->removeCellMemory(script, ionScript_->allocBytes(),
482                           MemoryUse::IonScript);
483     ionScript_ = nullptr;
484   }
485 
486   ionScript_ = ionScript;
487   MOZ_ASSERT_IF(hasIonScript(), hasBaselineScript());
488   if (hasIonScript()) {
489     AddCellMemory(script, ionScript_->allocBytes(), MemoryUse::IonScript);
490   }
491 
492   script->updateJitCodeRaw(fop->runtime());
493 }
494 
495 #ifdef JS_STRUCTURED_SPEW
HasEnteredCounters(ICEntry & entry)496 static bool HasEnteredCounters(ICEntry& entry) {
497   ICStub* stub = entry.firstStub();
498   if (stub && !stub->isFallback()) {
499     return true;
500   }
501   return false;
502 }
503 
JitSpewBaselineICStats(JSScript * script,const char * dumpReason)504 void jit::JitSpewBaselineICStats(JSScript* script, const char* dumpReason) {
505   MOZ_ASSERT(script->hasJitScript());
506   JSContext* cx = TlsContext.get();
507   AutoStructuredSpewer spew(cx, SpewChannel::BaselineICStats, script);
508   if (!spew) {
509     return;
510   }
511 
512   JitScript* jitScript = script->jitScript();
513   spew->property("reason", dumpReason);
514   spew->beginListProperty("entries");
515   for (size_t i = 0; i < jitScript->numICEntries(); i++) {
516     ICEntry& entry = jitScript->icEntry(i);
517     ICFallbackStub* fallback = jitScript->fallbackStub(i);
518     if (!HasEnteredCounters(entry)) {
519       continue;
520     }
521 
522     uint32_t pcOffset = fallback->pcOffset();
523     jsbytecode* pc = script->offsetToPC(pcOffset);
524 
525     unsigned column;
526     unsigned int line = PCToLineNumber(script, pc, &column);
527 
528     spew->beginObject();
529     spew->property("op", CodeName(JSOp(*pc)));
530     spew->property("pc", pcOffset);
531     spew->property("line", line);
532     spew->property("column", column);
533 
534     spew->beginListProperty("counts");
535     ICStub* stub = entry.firstStub();
536     while (stub && !stub->isFallback()) {
537       uint32_t count = stub->enteredCount();
538       spew->value(count);
539       stub = stub->toCacheIRStub()->next();
540     }
541     spew->endList();
542     spew->property("fallback_count", fallback->enteredCount());
543     spew->endObject();
544   }
545   spew->endList();
546 }
547 #endif
548 
MarkActiveJitScripts(JSContext * cx,const JitActivationIterator & activation)549 static void MarkActiveJitScripts(JSContext* cx,
550                                  const JitActivationIterator& activation) {
551   for (OnlyJSJitFrameIter iter(activation); !iter.done(); ++iter) {
552     const JSJitFrameIter& frame = iter.frame();
553     switch (frame.type()) {
554       case FrameType::BaselineJS:
555         frame.script()->jitScript()->setActive();
556         break;
557       case FrameType::Exit:
558         if (frame.exitFrame()->is<LazyLinkExitFrameLayout>()) {
559           LazyLinkExitFrameLayout* ll =
560               frame.exitFrame()->as<LazyLinkExitFrameLayout>();
561           JSScript* script =
562               ScriptFromCalleeToken(ll->jsFrame()->calleeToken());
563           script->jitScript()->setActive();
564         }
565         break;
566       case FrameType::Bailout:
567       case FrameType::IonJS: {
568         // Keep the JitScript and BaselineScript around, since bailouts from
569         // the ion jitcode need to re-enter into the Baseline code.
570         frame.script()->jitScript()->setActive();
571         for (InlineFrameIterator inlineIter(cx, &frame); inlineIter.more();
572              ++inlineIter) {
573           inlineIter.script()->jitScript()->setActive();
574         }
575         break;
576       }
577       default:;
578     }
579   }
580 }
581 
MarkActiveJitScripts(Zone * zone)582 void jit::MarkActiveJitScripts(Zone* zone) {
583   if (zone->isAtomsZone()) {
584     return;
585   }
586   JSContext* cx = TlsContext.get();
587   for (JitActivationIterator iter(cx); !iter.done(); ++iter) {
588     if (iter->compartment()->zone() == zone) {
589       MarkActiveJitScripts(cx, iter);
590     }
591   }
592 }
593 
getOrCreateInliningRoot(JSContext * cx,JSScript * script)594 InliningRoot* JitScript::getOrCreateInliningRoot(JSContext* cx,
595                                                  JSScript* script) {
596   if (!inliningRoot_) {
597     inliningRoot_ = js::MakeUnique<InliningRoot>(cx, script);
598     if (!inliningRoot_) {
599       ReportOutOfMemory(cx);
600       return nullptr;
601     }
602     icScript_.inliningRoot_ = inliningRoot_.get();
603   }
604   return inliningRoot_.get();
605 }
606 
createAllocSite(JSScript * script)607 gc::AllocSite* JitScript::createAllocSite(JSScript* script) {
608   MOZ_ASSERT(script->jitScript() == this);
609 
610   Nursery& nursery = script->runtimeFromMainThread()->gc.nursery();
611   if (!nursery.canCreateAllocSite()) {
612     // Don't block attaching an optimized stub, but don't process allocations
613     // for this site.
614     return script->zone()->unknownAllocSite();
615   }
616 
617   if (!allocSites_.reserve(allocSites_.length() + 1)) {
618     return nullptr;
619   }
620 
621   ICStubSpace* stubSpace = jitScriptStubSpace();
622   auto* site =
623       static_cast<gc::AllocSite*>(stubSpace->alloc(sizeof(gc::AllocSite)));
624   if (!site) {
625     return nullptr;
626   }
627 
628   new (site) gc::AllocSite(script->zone(), script);
629 
630   allocSites_.infallibleAppend(site);
631 
632   nursery.noteAllocSiteCreated();
633 
634   return site;
635 }
636 
resetAllocSites(bool resetNurserySites,bool resetPretenuredSites)637 bool JitScript::resetAllocSites(bool resetNurserySites,
638                                 bool resetPretenuredSites) {
639   MOZ_ASSERT(resetNurserySites || resetPretenuredSites);
640 
641   bool anyReset = false;
642 
643   for (gc::AllocSite* site : allocSites_) {
644     if ((resetNurserySites && site->initialHeap() == gc::DefaultHeap) ||
645         (resetPretenuredSites && site->initialHeap() == gc::TenuredHeap)) {
646       if (site->maybeResetState()) {
647         anyReset = true;
648       }
649     }
650   }
651 
652   return anyReset;
653 }
654 
jitScriptStubSpace()655 JitScriptICStubSpace* ICScript::jitScriptStubSpace() {
656   if (isInlined()) {
657     return inliningRoot_->jitScriptStubSpace();
658   }
659   return outerJitScript()->jitScriptStubSpace();
660 }
661 
outerJitScript()662 JitScript* ICScript::outerJitScript() {
663   MOZ_ASSERT(!isInlined());
664   uint8_t* ptr = reinterpret_cast<uint8_t*>(this);
665   return reinterpret_cast<JitScript*>(ptr - JitScript::offsetOfICScript());
666 }
667 
668 #ifdef DEBUG
669 // This hash is used to verify that we do not recompile after a
670 // TranspiledCacheIR invalidation with the exact same ICs.
671 //
672 // It should change iff an ICEntry in this ICScript (or an ICScript
673 // inlined into this ICScript) is modified such that we will make a
674 // different decision in WarpScriptOracle::maybeInlineIC. This means:
675 //
676 // 1. The hash will change if we attach a new stub.
677 // 2. The hash will change if the entered count of any CacheIR stub
678 //    other than the first changes from 0.
679 // 3. The hash will change if the entered count of the fallback stub
680 //    changes from 0.
681 //
hash()682 HashNumber ICScript::hash() {
683   HashNumber h = 0;
684   for (size_t i = 0; i < numICEntries(); i++) {
685     ICStub* stub = icEntry(i).firstStub();
686 
687     // Hash the address of the first stub.
688     h = mozilla::AddToHash(h, stub);
689 
690     // Hash whether subsequent stubs have entry count 0.
691     if (!stub->isFallback()) {
692       stub = stub->toCacheIRStub()->next();
693       while (!stub->isFallback()) {
694         h = mozilla::AddToHash(h, stub->enteredCount() == 0);
695         stub = stub->toCacheIRStub()->next();
696       }
697     }
698 
699     // Hash whether the fallback has entry count 0.
700     MOZ_ASSERT(stub->isFallback());
701     h = mozilla::AddToHash(h, stub->enteredCount() == 0);
702   }
703 
704   if (inlinedChildren_) {
705     for (auto& callsite : *inlinedChildren_) {
706       h = mozilla::AddToHash(h, callsite.callee_->hash());
707     }
708   }
709   return h;
710 }
711 #endif
712