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