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/WarpOracle.h"
8 
9 #include "mozilla/IntegerPrintfMacros.h"
10 #include "mozilla/ScopeExit.h"
11 
12 #include <algorithm>
13 
14 #include "jit/CacheIR.h"
15 #include "jit/CacheIRCompiler.h"
16 #include "jit/CacheIROpsGenerated.h"
17 #include "jit/CompileInfo.h"
18 #include "jit/InlineScriptTree.h"
19 #include "jit/JitRealm.h"
20 #include "jit/JitScript.h"
21 #include "jit/JitSpewer.h"
22 #include "jit/MIRGenerator.h"
23 #include "jit/TypeData.h"
24 #include "jit/WarpBuilder.h"
25 #include "jit/WarpCacheIRTranspiler.h"
26 #include "vm/BuiltinObjectKind.h"
27 #include "vm/BytecodeIterator.h"
28 #include "vm/BytecodeLocation.h"
29 #include "vm/GetterSetter.h"
30 #include "vm/Opcodes.h"
31 
32 #include "jit/InlineScriptTree-inl.h"
33 #include "vm/BytecodeIterator-inl.h"
34 #include "vm/BytecodeLocation-inl.h"
35 #include "vm/EnvironmentObject-inl.h"
36 #include "vm/Interpreter-inl.h"
37 
38 using namespace js;
39 using namespace js::jit;
40 
41 using mozilla::Maybe;
42 
43 // WarpScriptOracle creates a WarpScriptSnapshot for a single JSScript. Note
44 // that a single WarpOracle can use multiple WarpScriptOracles when scripts are
45 // inlined.
46 class MOZ_STACK_CLASS WarpScriptOracle {
47   JSContext* cx_;
48   WarpOracle* oracle_;
49   MIRGenerator& mirGen_;
50   TempAllocator& alloc_;
51   HandleScript script_;
52   const CompileInfo* info_;
53   ICScript* icScript_;
54 
55   // Index of the next ICEntry for getICEntry. This assumes the script's
56   // bytecode is processed from first to last instruction.
57   uint32_t icEntryIndex_ = 0;
58 
59   template <typename... Args>
abort(Args &&...args)60   mozilla::GenericErrorResult<AbortReason> abort(Args&&... args) {
61     return oracle_->abort(script_, args...);
62   }
63 
64   AbortReasonOr<WarpEnvironment> createEnvironment();
65   AbortReasonOr<Ok> maybeInlineIC(WarpOpSnapshotList& snapshots,
66                                   BytecodeLocation loc);
67   AbortReasonOr<bool> maybeInlineCall(WarpOpSnapshotList& snapshots,
68                                       BytecodeLocation loc, ICCacheIRStub* stub,
69                                       ICFallbackStub* fallbackStub,
70                                       uint8_t* stubDataCopy);
71   AbortReasonOr<bool> maybeInlinePolymorphicTypes(WarpOpSnapshotList& snapshots,
72                                                   BytecodeLocation loc,
73                                                   ICCacheIRStub* firstStub,
74                                                   ICFallbackStub* fallbackStub);
75   [[nodiscard]] bool replaceNurseryPointers(ICCacheIRStub* stub,
76                                             const CacheIRStubInfo* stubInfo,
77                                             uint8_t* stubDataCopy);
78 
79  public:
WarpScriptOracle(JSContext * cx,WarpOracle * oracle,HandleScript script,const CompileInfo * info,ICScript * icScript)80   WarpScriptOracle(JSContext* cx, WarpOracle* oracle, HandleScript script,
81                    const CompileInfo* info, ICScript* icScript)
82       : cx_(cx),
83         oracle_(oracle),
84         mirGen_(oracle->mirGen()),
85         alloc_(mirGen_.alloc()),
86         script_(script),
87         info_(info),
88         icScript_(icScript) {}
89 
90   AbortReasonOr<WarpScriptSnapshot*> createScriptSnapshot();
91 
92   ICEntry& getICEntryAndFallback(BytecodeLocation loc,
93                                  ICFallbackStub** fallback);
94 };
95 
WarpOracle(JSContext * cx,MIRGenerator & mirGen,HandleScript outerScript)96 WarpOracle::WarpOracle(JSContext* cx, MIRGenerator& mirGen,
97                        HandleScript outerScript)
98     : cx_(cx),
99       mirGen_(mirGen),
100       alloc_(mirGen.alloc()),
101       outerScript_(outerScript) {}
102 
abort(HandleScript script,AbortReason r)103 mozilla::GenericErrorResult<AbortReason> WarpOracle::abort(HandleScript script,
104                                                            AbortReason r) {
105   auto res = mirGen_.abort(r);
106   JitSpew(JitSpew_IonAbort, "aborted @ %s", script->filename());
107   return res;
108 }
109 
abort(HandleScript script,AbortReason r,const char * message,...)110 mozilla::GenericErrorResult<AbortReason> WarpOracle::abort(HandleScript script,
111                                                            AbortReason r,
112                                                            const char* message,
113                                                            ...) {
114   va_list ap;
115   va_start(ap, message);
116   auto res = mirGen_.abortFmt(r, message, ap);
117   va_end(ap);
118   JitSpew(JitSpew_IonAbort, "aborted @ %s", script->filename());
119   return res;
120 }
121 
addScriptSnapshot(WarpScriptSnapshot * scriptSnapshot)122 void WarpOracle::addScriptSnapshot(WarpScriptSnapshot* scriptSnapshot) {
123   scriptSnapshots_.insertBack(scriptSnapshot);
124 }
125 
createSnapshot()126 AbortReasonOr<WarpSnapshot*> WarpOracle::createSnapshot() {
127 #ifdef JS_JITSPEW
128   const char* mode;
129   if (outerScript_->hasIonScript()) {
130     mode = "Recompiling";
131   } else {
132     mode = "Compiling";
133   }
134   JitSpew(JitSpew_IonScripts,
135           "Warp %s script %s:%u:%u (%p) (warmup-counter=%" PRIu32 ",%s%s)",
136           mode, outerScript_->filename(), outerScript_->lineno(),
137           outerScript_->column(), static_cast<JSScript*>(outerScript_),
138           outerScript_->getWarmUpCount(),
139           outerScript_->isGenerator() ? " isGenerator" : "",
140           outerScript_->isAsync() ? " isAsync" : "");
141 #endif
142 
143   MOZ_ASSERT(outerScript_->hasJitScript());
144   ICScript* icScript = outerScript_->jitScript()->icScript();
145   WarpScriptOracle scriptOracle(cx_, this, outerScript_, &mirGen_.outerInfo(),
146                                 icScript);
147 
148   WarpScriptSnapshot* scriptSnapshot;
149   MOZ_TRY_VAR(scriptSnapshot, scriptOracle.createScriptSnapshot());
150 
151   // Insert the outermost scriptSnapshot at the front of the list.
152   scriptSnapshots_.insertFront(scriptSnapshot);
153 
154   bool recordFinalWarmUpCount = false;
155 #ifdef JS_CACHEIR_SPEW
156   recordFinalWarmUpCount = outerScript_->needsFinalWarmUpCount();
157 #endif
158 
159   auto* snapshot = new (alloc_.fallible())
160       WarpSnapshot(cx_, alloc_, std::move(scriptSnapshots_), bailoutInfo_,
161                    recordFinalWarmUpCount);
162   if (!snapshot) {
163     return abort(outerScript_, AbortReason::Alloc);
164   }
165 
166   if (!snapshot->nurseryObjects().appendAll(nurseryObjects_)) {
167     return abort(outerScript_, AbortReason::Alloc);
168   }
169 
170 #ifdef JS_JITSPEW
171   if (JitSpewEnabled(JitSpew_WarpSnapshots)) {
172     Fprinter& out = JitSpewPrinter();
173     snapshot->dump(out);
174   }
175 #endif
176 
177 #ifdef DEBUG
178   // When transpiled CacheIR bails out, we do not want to recompile
179   // with the exact same data and get caught in an invalidation loop.
180   //
181   // To avoid this, we store a hash of the stub pointers and entry
182   // counts in this snapshot, save that hash in the JitScript if we
183   // have a TranspiledCacheIR bailout, and assert that the hash has
184   // changed when we recompile.
185   //
186   // Note: this assertion catches potential performance issues.
187   // Failing this assertion is not a correctness/security problem.
188   // We therefore ignore cases involving OOM, stack overflow, or
189   // stubs purged by GC.
190   HashNumber hash = icScript->hash();
191   if (outerScript_->jitScript()->hasFailedICHash()) {
192     HashNumber oldHash = outerScript_->jitScript()->getFailedICHash();
193     MOZ_ASSERT_IF(hash == oldHash, cx_->hadNondeterministicException());
194   }
195   snapshot->setICHash(hash);
196 #endif
197 
198   return snapshot;
199 }
200 
201 template <typename T, typename... Args>
AddOpSnapshot(TempAllocator & alloc,WarpOpSnapshotList & snapshots,uint32_t offset,Args &&...args)202 [[nodiscard]] static bool AddOpSnapshot(TempAllocator& alloc,
203                                         WarpOpSnapshotList& snapshots,
204                                         uint32_t offset, Args&&... args) {
205   T* snapshot = new (alloc.fallible()) T(offset, std::forward<Args>(args)...);
206   if (!snapshot) {
207     return false;
208   }
209 
210   snapshots.insertBack(snapshot);
211   return true;
212 }
213 
AddWarpGetImport(TempAllocator & alloc,WarpOpSnapshotList & snapshots,uint32_t offset,JSScript * script,PropertyName * name)214 [[nodiscard]] static bool AddWarpGetImport(TempAllocator& alloc,
215                                            WarpOpSnapshotList& snapshots,
216                                            uint32_t offset, JSScript* script,
217                                            PropertyName* name) {
218   ModuleEnvironmentObject* env = GetModuleEnvironmentForScript(script);
219   MOZ_ASSERT(env);
220 
221   mozilla::Maybe<PropertyInfo> prop;
222   ModuleEnvironmentObject* targetEnv;
223   MOZ_ALWAYS_TRUE(env->lookupImport(NameToId(name), &targetEnv, &prop));
224 
225   uint32_t numFixedSlots = targetEnv->numFixedSlots();
226   uint32_t slot = prop->slot();
227 
228   // In the rare case where this import hasn't been initialized already (we have
229   // an import cycle where modules reference each other's imports), we need a
230   // check.
231   bool needsLexicalCheck =
232       targetEnv->getSlot(slot).isMagic(JS_UNINITIALIZED_LEXICAL);
233 
234   return AddOpSnapshot<WarpGetImport>(alloc, snapshots, offset, targetEnv,
235                                       numFixedSlots, slot, needsLexicalCheck);
236 }
237 
getICEntryAndFallback(BytecodeLocation loc,ICFallbackStub ** fallback)238 ICEntry& WarpScriptOracle::getICEntryAndFallback(BytecodeLocation loc,
239                                                  ICFallbackStub** fallback) {
240   const uint32_t offset = loc.bytecodeToOffset(script_);
241 
242   do {
243     *fallback = icScript_->fallbackStub(icEntryIndex_);
244     icEntryIndex_++;
245   } while ((*fallback)->pcOffset() < offset);
246 
247   MOZ_ASSERT((*fallback)->pcOffset() == offset);
248   return icScript_->icEntry(icEntryIndex_ - 1);
249 }
250 
createEnvironment()251 AbortReasonOr<WarpEnvironment> WarpScriptOracle::createEnvironment() {
252   // Don't do anything if the script doesn't use the environment chain.
253   // Always make an environment chain if the script needs an arguments object
254   // because ArgumentsObject construction requires the environment chain to be
255   // passed in.
256   if (!script_->jitScript()->usesEnvironmentChain() &&
257       !script_->needsArgsObj()) {
258     return WarpEnvironment(NoEnvironment());
259   }
260 
261   if (ModuleObject* module = script_->module()) {
262     JSObject* obj = &module->initialEnvironment();
263     return WarpEnvironment(ConstantObjectEnvironment(obj));
264   }
265 
266   JSFunction* fun = script_->function();
267   if (!fun) {
268     // For global scripts without a non-syntactic global scope, the environment
269     // chain is the global lexical environment.
270     MOZ_ASSERT(!script_->isForEval());
271     MOZ_ASSERT(!script_->hasNonSyntacticScope());
272     JSObject* obj = &script_->global().lexicalEnvironment();
273     return WarpEnvironment(ConstantObjectEnvironment(obj));
274   }
275 
276   // Parameter expression-induced extra var environment not yet handled.
277   if (fun->needsExtraBodyVarEnvironment()) {
278     return abort(AbortReason::Disable, "Extra var environment unsupported");
279   }
280 
281   JSObject* templateEnv = script_->jitScript()->templateEnvironment();
282 
283   CallObject* callObjectTemplate = nullptr;
284   if (fun->needsCallObject()) {
285     callObjectTemplate = &templateEnv->as<CallObject>();
286   }
287 
288   NamedLambdaObject* namedLambdaTemplate = nullptr;
289   if (fun->needsNamedLambdaEnvironment()) {
290     if (callObjectTemplate) {
291       templateEnv = templateEnv->enclosingEnvironment();
292     }
293     namedLambdaTemplate = &templateEnv->as<NamedLambdaObject>();
294   }
295 
296   return WarpEnvironment(
297       FunctionEnvironment(callObjectTemplate, namedLambdaTemplate));
298 }
299 
createScriptSnapshot()300 AbortReasonOr<WarpScriptSnapshot*> WarpScriptOracle::createScriptSnapshot() {
301   MOZ_ASSERT(script_->hasJitScript());
302 
303   if (!script_->jitScript()->ensureHasCachedIonData(cx_, script_)) {
304     return abort(AbortReason::Error);
305   }
306 
307   if (script_->jitScript()->hasTryFinally()) {
308     return abort(AbortReason::Disable, "Try-finally not supported");
309   }
310 
311   if (script_->failedBoundsCheck()) {
312     oracle_->bailoutInfo().setFailedBoundsCheck();
313   }
314   if (script_->failedLexicalCheck()) {
315     oracle_->bailoutInfo().setFailedLexicalCheck();
316   }
317 
318   WarpEnvironment environment{NoEnvironment()};
319   MOZ_TRY_VAR(environment, createEnvironment());
320 
321   // Unfortunately LinkedList<> asserts the list is empty in its destructor.
322   // Clear the list if we abort compilation.
323   WarpOpSnapshotList opSnapshots;
324   auto autoClearOpSnapshots =
325       mozilla::MakeScopeExit([&] { opSnapshots.clear(); });
326 
327   ModuleObject* moduleObject = nullptr;
328 
329   // Analyze the bytecode. Abort compilation for unsupported ops and create
330   // WarpOpSnapshots.
331   for (BytecodeLocation loc : AllBytecodesIterable(script_)) {
332     JSOp op = loc.getOp();
333     uint32_t offset = loc.bytecodeToOffset(script_);
334     switch (op) {
335       case JSOp::Arguments: {
336         MOZ_ASSERT(script_->needsArgsObj());
337         bool mapped = script_->hasMappedArgsObj();
338         ArgumentsObject* templateObj =
339             script_->realm()->maybeArgumentsTemplateObject(mapped);
340         if (!AddOpSnapshot<WarpArguments>(alloc_, opSnapshots, offset,
341                                           templateObj)) {
342           return abort(AbortReason::Alloc);
343         }
344         break;
345       }
346       case JSOp::RegExp: {
347         bool hasShared = loc.getRegExp(script_)->hasShared();
348         if (!AddOpSnapshot<WarpRegExp>(alloc_, opSnapshots, offset,
349                                        hasShared)) {
350           return abort(AbortReason::Alloc);
351         }
352         break;
353       }
354 
355       case JSOp::FunctionThis:
356         if (!script_->strict() && script_->hasNonSyntacticScope()) {
357           // Abort because MBoxNonStrictThis doesn't support non-syntactic
358           // scopes (a deprecated SpiderMonkey mechanism). If this becomes an
359           // issue we could support it by refactoring GetFunctionThis to not
360           // take a frame pointer and then call that.
361           return abort(AbortReason::Disable,
362                        "JSOp::FunctionThis with non-syntactic scope");
363         }
364         break;
365 
366       case JSOp::GlobalThis:
367         if (script_->hasNonSyntacticScope()) {
368           // We don't compile global scripts with a non-syntactic scope, but
369           // we can end up here when we're compiling an arrow function.
370           return abort(AbortReason::Disable,
371                        "JSOp::GlobalThis with non-syntactic scope");
372         }
373         break;
374 
375       case JSOp::BuiltinObject: {
376         // If we already resolved this built-in we can bake it in.
377         auto kind = loc.getBuiltinObjectKind();
378         if (JSObject* proto = MaybeGetBuiltinObject(cx_->global(), kind)) {
379           if (!AddOpSnapshot<WarpBuiltinObject>(alloc_, opSnapshots, offset,
380                                                 proto)) {
381             return abort(AbortReason::Alloc);
382           }
383         }
384         break;
385       }
386 
387       case JSOp::GetIntrinsic: {
388         // If we already cloned this intrinsic we can bake it in.
389         PropertyName* name = loc.getPropertyName(script_);
390         Value val;
391         if (cx_->global()->maybeExistingIntrinsicValue(name, &val)) {
392           if (!AddOpSnapshot<WarpGetIntrinsic>(alloc_, opSnapshots, offset,
393                                                val)) {
394             return abort(AbortReason::Alloc);
395           }
396         }
397         break;
398       }
399 
400       case JSOp::ImportMeta: {
401         if (!moduleObject) {
402           moduleObject = GetModuleObjectForScript(script_);
403           MOZ_ASSERT(moduleObject->isTenured());
404         }
405         break;
406       }
407 
408       case JSOp::CallSiteObj: {
409         // Prepare the object so that WarpBuilder can just push it as constant.
410         if (!ProcessCallSiteObjOperation(cx_, script_, loc.toRawBytecode())) {
411           return abort(AbortReason::Error);
412         }
413         break;
414       }
415 
416       case JSOp::GetImport: {
417         PropertyName* name = loc.getPropertyName(script_);
418         if (!AddWarpGetImport(alloc_, opSnapshots, offset, script_, name)) {
419           return abort(AbortReason::Alloc);
420         }
421         break;
422       }
423 
424       case JSOp::Lambda:
425       case JSOp::LambdaArrow: {
426         JSFunction* fun = loc.getFunction(script_);
427         if (IsAsmJSModule(fun)) {
428           return abort(AbortReason::Disable, "asm.js module function lambda");
429         }
430 
431         if (!AddOpSnapshot<WarpLambda>(alloc_, opSnapshots, offset,
432                                        fun->baseScript(), fun->flags(),
433                                        fun->nargs())) {
434           return abort(AbortReason::Alloc);
435         }
436         break;
437       }
438 
439       case JSOp::GetElemSuper: {
440 #if defined(JS_CODEGEN_X86)
441         // x86 does not have enough registers if profiling is enabled.
442         if (mirGen_.instrumentedProfiling()) {
443           return abort(AbortReason::Disable,
444                        "GetElemSuper with profiling is not supported on x86");
445         }
446 #endif
447         MOZ_TRY(maybeInlineIC(opSnapshots, loc));
448         break;
449       }
450 
451       case JSOp::Rest: {
452         if (Shape* shape = script_->global().maybeArrayShape()) {
453           if (!AddOpSnapshot<WarpRest>(alloc_, opSnapshots, offset, shape)) {
454             return abort(AbortReason::Alloc);
455           }
456         }
457         break;
458       }
459 
460       case JSOp::BindGName: {
461         RootedGlobalObject global(cx_, &script_->global());
462         RootedPropertyName name(cx_, loc.getPropertyName(script_));
463         if (JSObject* env = MaybeOptimizeBindGlobalName(cx_, global, name)) {
464           MOZ_ASSERT(env->isTenured());
465           if (!AddOpSnapshot<WarpBindGName>(alloc_, opSnapshots, offset, env)) {
466             return abort(AbortReason::Alloc);
467           }
468         } else {
469           MOZ_TRY(maybeInlineIC(opSnapshots, loc));
470         }
471         break;
472       }
473 
474       case JSOp::GetName:
475       case JSOp::GetGName:
476       case JSOp::GetProp:
477       case JSOp::GetElem:
478       case JSOp::SetProp:
479       case JSOp::StrictSetProp:
480       case JSOp::Call:
481       case JSOp::CallIgnoresRv:
482       case JSOp::CallIter:
483       case JSOp::FunCall:
484       case JSOp::FunApply:
485       case JSOp::New:
486       case JSOp::SuperCall:
487       case JSOp::SpreadCall:
488       case JSOp::ToNumeric:
489       case JSOp::Pos:
490       case JSOp::Inc:
491       case JSOp::Dec:
492       case JSOp::Neg:
493       case JSOp::BitNot:
494       case JSOp::Iter:
495       case JSOp::Eq:
496       case JSOp::Ne:
497       case JSOp::Lt:
498       case JSOp::Le:
499       case JSOp::Gt:
500       case JSOp::Ge:
501       case JSOp::StrictEq:
502       case JSOp::StrictNe:
503       case JSOp::BindName:
504       case JSOp::Add:
505       case JSOp::Sub:
506       case JSOp::Mul:
507       case JSOp::Div:
508       case JSOp::Mod:
509       case JSOp::Pow:
510       case JSOp::BitAnd:
511       case JSOp::BitOr:
512       case JSOp::BitXor:
513       case JSOp::Lsh:
514       case JSOp::Rsh:
515       case JSOp::Ursh:
516       case JSOp::In:
517       case JSOp::HasOwn:
518       case JSOp::CheckPrivateField:
519       case JSOp::Instanceof:
520       case JSOp::GetPropSuper:
521       case JSOp::InitProp:
522       case JSOp::InitLockedProp:
523       case JSOp::InitHiddenProp:
524       case JSOp::InitElem:
525       case JSOp::InitHiddenElem:
526       case JSOp::InitElemInc:
527       case JSOp::SetName:
528       case JSOp::StrictSetName:
529       case JSOp::SetGName:
530       case JSOp::StrictSetGName:
531       case JSOp::InitGLexical:
532       case JSOp::SetElem:
533       case JSOp::StrictSetElem:
534       case JSOp::ToPropertyKey:
535       case JSOp::OptimizeSpreadCall:
536       case JSOp::Typeof:
537       case JSOp::TypeofExpr:
538       case JSOp::NewObject:
539       case JSOp::NewInit:
540       case JSOp::NewArray:
541       case JSOp::JumpIfFalse:
542       case JSOp::JumpIfTrue:
543       case JSOp::And:
544       case JSOp::Or:
545       case JSOp::Not:
546         MOZ_TRY(maybeInlineIC(opSnapshots, loc));
547         break;
548 
549       case JSOp::Nop:
550       case JSOp::NopDestructuring:
551       case JSOp::TryDestructuring:
552       case JSOp::Lineno:
553       case JSOp::DebugLeaveLexicalEnv:
554       case JSOp::Undefined:
555       case JSOp::Void:
556       case JSOp::Null:
557       case JSOp::Hole:
558       case JSOp::Uninitialized:
559       case JSOp::IsConstructing:
560       case JSOp::False:
561       case JSOp::True:
562       case JSOp::Zero:
563       case JSOp::One:
564       case JSOp::Int8:
565       case JSOp::Uint16:
566       case JSOp::Uint24:
567       case JSOp::Int32:
568       case JSOp::Double:
569       case JSOp::ResumeIndex:
570       case JSOp::BigInt:
571       case JSOp::String:
572       case JSOp::Symbol:
573       case JSOp::Pop:
574       case JSOp::PopN:
575       case JSOp::Dup:
576       case JSOp::Dup2:
577       case JSOp::DupAt:
578       case JSOp::Swap:
579       case JSOp::Pick:
580       case JSOp::Unpick:
581       case JSOp::GetLocal:
582       case JSOp::SetLocal:
583       case JSOp::InitLexical:
584       case JSOp::GetArg:
585       case JSOp::SetArg:
586       case JSOp::JumpTarget:
587       case JSOp::LoopHead:
588       case JSOp::Case:
589       case JSOp::Default:
590       case JSOp::Coalesce:
591       case JSOp::Goto:
592       case JSOp::DebugCheckSelfHosted:
593       case JSOp::DynamicImport:
594       case JSOp::ToString:
595       case JSOp::GlobalOrEvalDeclInstantiation:
596       case JSOp::BindVar:
597       case JSOp::MutateProto:
598       case JSOp::Callee:
599       case JSOp::ToAsyncIter:
600       case JSOp::ObjWithProto:
601       case JSOp::GetAliasedVar:
602       case JSOp::SetAliasedVar:
603       case JSOp::InitAliasedLexical:
604       case JSOp::EnvCallee:
605       case JSOp::MoreIter:
606       case JSOp::EndIter:
607       case JSOp::IsNoIter:
608       case JSOp::DelProp:
609       case JSOp::StrictDelProp:
610       case JSOp::DelElem:
611       case JSOp::StrictDelElem:
612       case JSOp::SetFunName:
613       case JSOp::PushLexicalEnv:
614       case JSOp::PopLexicalEnv:
615       case JSOp::FreshenLexicalEnv:
616       case JSOp::RecreateLexicalEnv:
617       case JSOp::PushClassBodyEnv:
618       case JSOp::ImplicitThis:
619       case JSOp::GImplicitThis:
620       case JSOp::CheckClassHeritage:
621       case JSOp::CheckThis:
622       case JSOp::CheckThisReinit:
623       case JSOp::Generator:
624       case JSOp::AfterYield:
625       case JSOp::FinalYieldRval:
626       case JSOp::AsyncResolve:
627       case JSOp::CheckResumeKind:
628       case JSOp::CanSkipAwait:
629       case JSOp::MaybeExtractAwaitValue:
630       case JSOp::AsyncAwait:
631       case JSOp::Await:
632       case JSOp::CheckReturn:
633       case JSOp::CheckLexical:
634       case JSOp::CheckAliasedLexical:
635       case JSOp::InitHomeObject:
636       case JSOp::SuperBase:
637       case JSOp::SuperFun:
638       case JSOp::InitElemArray:
639       case JSOp::InitPropGetter:
640       case JSOp::InitPropSetter:
641       case JSOp::InitHiddenPropGetter:
642       case JSOp::InitHiddenPropSetter:
643       case JSOp::InitElemGetter:
644       case JSOp::InitElemSetter:
645       case JSOp::InitHiddenElemGetter:
646       case JSOp::InitHiddenElemSetter:
647       case JSOp::NewTarget:
648       case JSOp::Object:
649       case JSOp::CheckIsObj:
650       case JSOp::CheckObjCoercible:
651       case JSOp::FunWithProto:
652       case JSOp::SpreadNew:
653       case JSOp::SpreadSuperCall:
654       case JSOp::Debugger:
655       case JSOp::TableSwitch:
656       case JSOp::Exception:
657       case JSOp::Throw:
658       case JSOp::ThrowSetConst:
659       case JSOp::SetRval:
660       case JSOp::GetRval:
661       case JSOp::Return:
662       case JSOp::RetRval:
663       case JSOp::InitialYield:
664       case JSOp::Yield:
665       case JSOp::ResumeKind:
666       case JSOp::ThrowMsg:
667       case JSOp::Try:
668         // Supported by WarpBuilder. Nothing to do.
669         break;
670 
671         // Unsupported ops. Don't use a 'default' here, we want to trigger a
672         // compiler warning when adding a new JSOp.
673 #define DEF_CASE(OP) case JSOp::OP:
674         WARP_UNSUPPORTED_OPCODE_LIST(DEF_CASE)
675 #undef DEF_CASE
676 #ifdef DEBUG
677         return abort(AbortReason::Disable, "Unsupported opcode: %s",
678                      CodeName(op));
679 #else
680         return abort(AbortReason::Disable, "Unsupported opcode: %u",
681                      uint8_t(op));
682 #endif
683     }
684   }
685 
686   auto* scriptSnapshot = new (alloc_.fallible()) WarpScriptSnapshot(
687       script_, environment, std::move(opSnapshots), moduleObject);
688   if (!scriptSnapshot) {
689     return abort(AbortReason::Alloc);
690   }
691 
692   autoClearOpSnapshots.release();
693   return scriptSnapshot;
694 }
695 
LineNumberAndColumn(HandleScript script,BytecodeLocation loc,unsigned * line,unsigned * column)696 static void LineNumberAndColumn(HandleScript script, BytecodeLocation loc,
697                                 unsigned* line, unsigned* column) {
698 #ifdef DEBUG
699   *line = PCToLineNumber(script, loc.toRawBytecode(), column);
700 #else
701   *line = script->lineno();
702   *column = script->column();
703 #endif
704 }
705 
maybeInlineIC(WarpOpSnapshotList & snapshots,BytecodeLocation loc)706 AbortReasonOr<Ok> WarpScriptOracle::maybeInlineIC(WarpOpSnapshotList& snapshots,
707                                                   BytecodeLocation loc) {
708   // Do one of the following:
709   //
710   // * If the Baseline IC has a single ICStub we can inline, add a WarpCacheIR
711   //   snapshot to transpile it to MIR.
712   //
713   // * If that single ICStub is a call IC with a known target, instead add a
714   //   WarpInline snapshot to transpile the guards to MIR and inline the target.
715   //
716   // * If the Baseline IC is cold (never executed), add a WarpBailout snapshot
717   //   so that we can collect information in Baseline.
718   //
719   // * Else, don't add a snapshot and rely on WarpBuilder adding an Ion IC.
720 
721   MOZ_ASSERT(loc.opHasIC());
722 
723   // Don't create snapshots when testing ICs.
724   if (JitOptions.forceInlineCaches) {
725     return Ok();
726   }
727 
728   ICFallbackStub* fallbackStub;
729   const ICEntry& entry = getICEntryAndFallback(loc, &fallbackStub);
730   ICStub* firstStub = entry.firstStub();
731 
732   uint32_t offset = loc.bytecodeToOffset(script_);
733 
734   // Clear the used-by-transpiler flag on the IC. It can still be set from a
735   // previous compilation because we don't clear the flag on every IC when
736   // invalidating.
737   fallbackStub->clearUsedByTranspiler();
738 
739   if (firstStub == fallbackStub) {
740     [[maybe_unused]] unsigned line, column;
741     LineNumberAndColumn(script_, loc, &line, &column);
742 
743     // No optimized stubs.
744     JitSpew(JitSpew_WarpTranspiler,
745             "fallback stub (entered-count: %" PRIu32
746             ") for JSOp::%s @ %s:%u:%u",
747             fallbackStub->enteredCount(), CodeName(loc.getOp()),
748             script_->filename(), line, column);
749 
750     // If the fallback stub was used but there's no optimized stub, use an IC.
751     if (fallbackStub->enteredCount() != 0) {
752       return Ok();
753     }
754 
755     // Cold IC. Bailout to collect information.
756     if (!AddOpSnapshot<WarpBailout>(alloc_, snapshots, offset)) {
757       return abort(AbortReason::Alloc);
758     }
759     return Ok();
760   }
761 
762   ICCacheIRStub* stub = firstStub->toCacheIRStub();
763 
764   // Don't transpile if there are other stubs with entered-count > 0. Counters
765   // are reset when a new stub is attached so this means the stub that was added
766   // most recently didn't handle all cases.
767   // If this code is changed, ICScript::hash may also need changing.
768   bool firstStubHandlesAllCases = true;
769   for (ICStub* next = stub->next(); next; next = next->maybeNext()) {
770     if (next->enteredCount() != 0) {
771       firstStubHandlesAllCases = false;
772       break;
773     }
774   }
775 
776   if (!firstStubHandlesAllCases) {
777     // In some polymorphic cases, we can generate better code than the
778     // default fallback if we know the observed types of the operands
779     // and their relative frequency.
780     if (ICSupportsPolymorphicTypeData(loc.getOp()) &&
781         fallbackStub->enteredCount() == 0) {
782       bool inlinedPolymorphicTypes = false;
783       MOZ_TRY_VAR(
784           inlinedPolymorphicTypes,
785           maybeInlinePolymorphicTypes(snapshots, loc, stub, fallbackStub));
786       if (inlinedPolymorphicTypes) {
787         return Ok();
788       }
789     }
790 
791     [[maybe_unused]] unsigned line, column;
792     LineNumberAndColumn(script_, loc, &line, &column);
793 
794     JitSpew(JitSpew_WarpTranspiler,
795             "multiple active stubs for JSOp::%s @ %s:%u:%u",
796             CodeName(loc.getOp()), script_->filename(), line, column);
797     return Ok();
798   }
799 
800   const CacheIRStubInfo* stubInfo = stub->stubInfo();
801   const uint8_t* stubData = stub->stubDataStart();
802 
803   // Only create a snapshot if all opcodes are supported by the transpiler.
804   CacheIRReader reader(stubInfo);
805   while (reader.more()) {
806     CacheOp op = reader.readOp();
807     CacheIROpInfo opInfo = CacheIROpInfos[size_t(op)];
808     reader.skip(opInfo.argLength);
809 
810     if (!opInfo.transpile) {
811       [[maybe_unused]] unsigned line, column;
812       LineNumberAndColumn(script_, loc, &line, &column);
813 
814       MOZ_ASSERT(
815           fallbackStub->trialInliningState() != TrialInliningState::Inlined,
816           "Trial-inlined stub not supported by transpiler");
817 
818       // Unsupported CacheIR opcode.
819       JitSpew(JitSpew_WarpTranspiler,
820               "unsupported CacheIR opcode %s for JSOp::%s @ %s:%u:%u",
821               CacheIROpNames[size_t(op)], CodeName(loc.getOp()),
822               script_->filename(), line, column);
823       return Ok();
824     }
825 
826     // While on the main thread, ensure code stubs exist for ops that require
827     // them.
828     switch (op) {
829       case CacheOp::CallRegExpMatcherResult:
830         if (!cx_->realm()->jitRealm()->ensureRegExpMatcherStubExists(cx_)) {
831           return abort(AbortReason::Error);
832         }
833         break;
834       case CacheOp::CallRegExpSearcherResult:
835         if (!cx_->realm()->jitRealm()->ensureRegExpSearcherStubExists(cx_)) {
836           return abort(AbortReason::Error);
837         }
838         break;
839       case CacheOp::CallRegExpTesterResult:
840         if (!cx_->realm()->jitRealm()->ensureRegExpTesterStubExists(cx_)) {
841           return abort(AbortReason::Error);
842         }
843         break;
844       default:
845         break;
846     }
847   }
848 
849   // Copy the ICStub data to protect against the stub being unlinked or mutated.
850   // We don't need to copy the CacheIRStubInfo: because we store and trace the
851   // stub's JitCode*, the baselineCacheIRStubCodes_ map in JitZone will keep it
852   // alive.
853   uint8_t* stubDataCopy = nullptr;
854   size_t bytesNeeded = stubInfo->stubDataSize();
855   if (bytesNeeded > 0) {
856     stubDataCopy = alloc_.allocateArray<uint8_t>(bytesNeeded);
857     if (!stubDataCopy) {
858       return abort(AbortReason::Alloc);
859     }
860 
861     // Note: nursery pointers are handled below so we don't need to trigger any
862     // GC barriers and can do a bitwise copy.
863     std::copy_n(stubData, bytesNeeded, stubDataCopy);
864 
865     if (!replaceNurseryPointers(stub, stubInfo, stubDataCopy)) {
866       return abort(AbortReason::Alloc);
867     }
868   }
869 
870   JitCode* jitCode = stub->jitCode();
871 
872   if (fallbackStub->trialInliningState() == TrialInliningState::Inlined) {
873     bool inlinedCall;
874     MOZ_TRY_VAR(inlinedCall, maybeInlineCall(snapshots, loc, stub, fallbackStub,
875                                              stubDataCopy));
876     if (inlinedCall) {
877       return Ok();
878     }
879   }
880 
881   if (!AddOpSnapshot<WarpCacheIR>(alloc_, snapshots, offset, jitCode, stubInfo,
882                                   stubDataCopy)) {
883     return abort(AbortReason::Alloc);
884   }
885 
886   fallbackStub->setUsedByTranspiler();
887 
888   return Ok();
889 }
890 
maybeInlineCall(WarpOpSnapshotList & snapshots,BytecodeLocation loc,ICCacheIRStub * stub,ICFallbackStub * fallbackStub,uint8_t * stubDataCopy)891 AbortReasonOr<bool> WarpScriptOracle::maybeInlineCall(
892     WarpOpSnapshotList& snapshots, BytecodeLocation loc, ICCacheIRStub* stub,
893     ICFallbackStub* fallbackStub, uint8_t* stubDataCopy) {
894   Maybe<InlinableOpData> inlineData = FindInlinableOpData(stub, loc);
895   if (inlineData.isNothing() || !inlineData->icScript) {
896     return false;
897   }
898 
899   RootedFunction targetFunction(cx_, inlineData->target);
900   if (!TrialInliner::canInline(targetFunction, script_, loc)) {
901     return false;
902   }
903 
904   RootedScript targetScript(cx_, targetFunction->nonLazyScript());
905   ICScript* icScript = inlineData->icScript;
906 
907   // Add the inlined script to the inline script tree.
908   LifoAlloc* lifoAlloc = alloc_.lifoAlloc();
909   InlineScriptTree* inlineScriptTree = info_->inlineScriptTree()->addCallee(
910       &alloc_, loc.toRawBytecode(), targetScript);
911   if (!inlineScriptTree) {
912     return abort(AbortReason::Alloc);
913   }
914 
915   // Create a CompileInfo for the inlined script.
916   jsbytecode* osrPc = nullptr;
917   bool needsArgsObj = targetScript->needsArgsObj();
918   CompileInfo* info = lifoAlloc->new_<CompileInfo>(
919       mirGen_.runtime, targetScript, targetFunction, osrPc, needsArgsObj,
920       inlineScriptTree);
921   if (!info) {
922     return abort(AbortReason::Alloc);
923   }
924 
925   // Take a snapshot of the CacheIR.
926   uint32_t offset = loc.bytecodeToOffset(script_);
927   JitCode* jitCode = stub->jitCode();
928   const CacheIRStubInfo* stubInfo = stub->stubInfo();
929   WarpCacheIR* cacheIRSnapshot = new (alloc_.fallible())
930       WarpCacheIR(offset, jitCode, stubInfo, stubDataCopy);
931   if (!cacheIRSnapshot) {
932     return abort(AbortReason::Alloc);
933   }
934 
935   // Take a snapshot of the inlined script (which may do more
936   // inlining recursively).
937   WarpScriptOracle scriptOracle(cx_, oracle_, targetScript, info, icScript);
938 
939   AbortReasonOr<WarpScriptSnapshot*> maybeScriptSnapshot =
940       scriptOracle.createScriptSnapshot();
941 
942   if (maybeScriptSnapshot.isErr()) {
943     JitSpew(JitSpew_WarpTranspiler, "Can't create snapshot for JSOp::%s",
944             CodeName(loc.getOp()));
945 
946     switch (maybeScriptSnapshot.unwrapErr()) {
947       case AbortReason::Disable: {
948         // If the target script can't be warp-compiled, mark it as
949         // uninlineable, clean up, and fall through to the non-inlined path.
950         ICEntry* entry = icScript_->icEntryForStub(fallbackStub);
951         fallbackStub->setTrialInliningState(TrialInliningState::Failure);
952         fallbackStub->unlinkStub(cx_->zone(), entry, /*prev=*/nullptr, stub);
953         targetScript->setUninlineable();
954         info_->inlineScriptTree()->removeCallee(inlineScriptTree);
955         icScript_->removeInlinedChild(loc.bytecodeToOffset(script_));
956         return false;
957       }
958       case AbortReason::Error:
959       case AbortReason::Alloc:
960         return Err(maybeScriptSnapshot.unwrapErr());
961       default:
962         MOZ_CRASH("Unexpected abort reason");
963     }
964   }
965 
966   WarpScriptSnapshot* scriptSnapshot = maybeScriptSnapshot.unwrap();
967   oracle_->addScriptSnapshot(scriptSnapshot);
968 
969   if (!AddOpSnapshot<WarpInlinedCall>(alloc_, snapshots, offset,
970                                       cacheIRSnapshot, scriptSnapshot, info)) {
971     return abort(AbortReason::Alloc);
972   }
973   fallbackStub->setUsedByTranspiler();
974   return true;
975 }
976 
977 struct TypeFrequency {
978   TypeData typeData_;
979   uint32_t successCount_;
TypeFrequencyTypeFrequency980   TypeFrequency(TypeData typeData, uint32_t successCount)
981       : typeData_(typeData), successCount_(successCount) {}
982 
983   // Sort highest frequency first.
operator <TypeFrequency984   bool operator<(const TypeFrequency& other) const {
985     return other.successCount_ < successCount_;
986   }
987 };
988 
maybeInlinePolymorphicTypes(WarpOpSnapshotList & snapshots,BytecodeLocation loc,ICCacheIRStub * firstStub,ICFallbackStub * fallbackStub)989 AbortReasonOr<bool> WarpScriptOracle::maybeInlinePolymorphicTypes(
990     WarpOpSnapshotList& snapshots, BytecodeLocation loc,
991     ICCacheIRStub* firstStub, ICFallbackStub* fallbackStub) {
992   MOZ_ASSERT(ICSupportsPolymorphicTypeData(loc.getOp()));
993 
994   // We use polymorphic type data if there are multiple active stubs,
995   // all of which have type data available.
996   Vector<TypeFrequency, 6, SystemAllocPolicy> candidates;
997   for (ICStub* stub = firstStub; !stub->isFallback();
998        stub = stub->maybeNext()) {
999     ICCacheIRStub* cacheIRStub = stub->toCacheIRStub();
1000     uint32_t successCount =
1001         cacheIRStub->enteredCount() - cacheIRStub->next()->enteredCount();
1002     if (successCount == 0) {
1003       continue;
1004     }
1005     TypeData types = cacheIRStub->typeData();
1006     if (!types.hasData()) {
1007       return false;
1008     }
1009     if (!candidates.append(TypeFrequency(types, successCount))) {
1010       return abort(AbortReason::Alloc);
1011     }
1012   }
1013   if (candidates.length() < 2) {
1014     return false;
1015   }
1016 
1017   // Sort candidates by success frequency.
1018   std::sort(candidates.begin(), candidates.end());
1019 
1020   TypeDataList list;
1021   for (auto& candidate : candidates) {
1022     list.addTypeData(candidate.typeData_);
1023   }
1024 
1025   uint32_t offset = loc.bytecodeToOffset(script_);
1026   if (!AddOpSnapshot<WarpPolymorphicTypes>(alloc_, snapshots, offset, list)) {
1027     return abort(AbortReason::Alloc);
1028   }
1029 
1030   return true;
1031 }
1032 
replaceNurseryPointers(ICCacheIRStub * stub,const CacheIRStubInfo * stubInfo,uint8_t * stubDataCopy)1033 bool WarpScriptOracle::replaceNurseryPointers(ICCacheIRStub* stub,
1034                                               const CacheIRStubInfo* stubInfo,
1035                                               uint8_t* stubDataCopy) {
1036   // If the stub data contains nursery object pointers, replace them with the
1037   // corresponding nursery index. See WarpObjectField.
1038   //
1039   // Also asserts non-object fields don't contain nursery pointers.
1040 
1041   uint32_t field = 0;
1042   size_t offset = 0;
1043   while (true) {
1044     StubField::Type fieldType = stubInfo->fieldType(field);
1045     switch (fieldType) {
1046       case StubField::Type::RawInt32:
1047       case StubField::Type::RawPointer:
1048       case StubField::Type::RawInt64:
1049       case StubField::Type::AllocSite:
1050         break;
1051       case StubField::Type::Shape:
1052         static_assert(std::is_convertible_v<Shape*, gc::TenuredCell*>,
1053                       "Code assumes shapes are tenured");
1054         break;
1055       case StubField::Type::GetterSetter:
1056         static_assert(std::is_convertible_v<GetterSetter*, gc::TenuredCell*>,
1057                       "Code assumes GetterSetters are tenured");
1058         break;
1059       case StubField::Type::Symbol:
1060         static_assert(std::is_convertible_v<JS::Symbol*, gc::TenuredCell*>,
1061                       "Code assumes symbols are tenured");
1062         break;
1063       case StubField::Type::BaseScript:
1064         static_assert(std::is_convertible_v<BaseScript*, gc::TenuredCell*>,
1065                       "Code assumes scripts are tenured");
1066         break;
1067       case StubField::Type::JSObject: {
1068         JSObject* obj =
1069             stubInfo->getStubField<ICCacheIRStub, JSObject*>(stub, offset);
1070         if (IsInsideNursery(obj)) {
1071           uint32_t nurseryIndex;
1072           if (!oracle_->registerNurseryObject(obj, &nurseryIndex)) {
1073             return false;
1074           }
1075           uintptr_t oldWord = WarpObjectField::fromObject(obj).rawData();
1076           uintptr_t newWord =
1077               WarpObjectField::fromNurseryIndex(nurseryIndex).rawData();
1078           stubInfo->replaceStubRawWord(stubDataCopy, offset, oldWord, newWord);
1079         }
1080         break;
1081       }
1082       case StubField::Type::String: {
1083 #ifdef DEBUG
1084         JSString* str =
1085             stubInfo->getStubField<ICCacheIRStub, JSString*>(stub, offset);
1086         MOZ_ASSERT(!IsInsideNursery(str));
1087 #endif
1088         break;
1089       }
1090       case StubField::Type::Id: {
1091 #ifdef DEBUG
1092         // jsid never contains nursery-allocated things.
1093         jsid id = stubInfo->getStubField<ICCacheIRStub, jsid>(stub, offset);
1094         MOZ_ASSERT_IF(id.isGCThing(),
1095                       !IsInsideNursery(id.toGCCellPtr().asCell()));
1096 #endif
1097         break;
1098       }
1099       case StubField::Type::Value: {
1100 #ifdef DEBUG
1101         Value v =
1102             stubInfo->getStubField<ICCacheIRStub, JS::Value>(stub, offset);
1103         MOZ_ASSERT_IF(v.isGCThing(), !IsInsideNursery(v.toGCThing()));
1104 #endif
1105         break;
1106       }
1107       case StubField::Type::Limit:
1108         return true;  // Done.
1109     }
1110     field++;
1111     offset += StubField::sizeInBytes(fieldType);
1112   }
1113 }
1114 
registerNurseryObject(JSObject * obj,uint32_t * nurseryIndex)1115 bool WarpOracle::registerNurseryObject(JSObject* obj, uint32_t* nurseryIndex) {
1116   MOZ_ASSERT(IsInsideNursery(obj));
1117 
1118   auto p = nurseryObjectsMap_.lookupForAdd(obj);
1119   if (p) {
1120     *nurseryIndex = p->value();
1121     return true;
1122   }
1123 
1124   if (!nurseryObjects_.append(obj)) {
1125     return false;
1126   }
1127   *nurseryIndex = nurseryObjects_.length() - 1;
1128   return nurseryObjectsMap_.add(p, obj, *nurseryIndex);
1129 }
1130