BaselineCodeGen.h (20478B)
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 #ifndef jit_BaselineCodeGen_h 8 #define jit_BaselineCodeGen_h 9 10 #include "jit/BaselineFrameInfo.h" 11 #include "jit/BytecodeAnalysis.h" 12 #include "jit/CompileWrappers.h" 13 #include "jit/FixedList.h" 14 #include "jit/MacroAssembler.h" 15 #include "jit/PerfSpewer.h" 16 17 namespace js { 18 19 class NamedLambdaObject; 20 21 namespace jit { 22 23 class BaselineSnapshot; 24 25 enum class ScriptGCThingType { 26 Atom, 27 String, 28 RegExp, 29 Object, 30 Function, 31 Scope, 32 BigInt 33 }; 34 35 // Base class for BaselineCompiler and BaselineInterpreterGenerator. The Handler 36 // template is a class storing fields/methods that are interpreter or compiler 37 // specific. This can be combined with template specialization of methods in 38 // this class to specialize behavior. 39 template <typename Handler> 40 class BaselineCodeGen { 41 protected: 42 Handler handler; 43 44 CompileRuntime* runtime; 45 MacroAssembler& masm; 46 47 typename Handler::FrameInfoT& frame; 48 49 // Shared epilogue code to return to the caller. 50 NonAssertingLabel return_; 51 52 NonAssertingLabel postBarrierSlot_; 53 54 // Prologue code where we resume for Ion prologue bailouts. 55 NonAssertingLabel bailoutPrologue_; 56 57 CodeOffset profilerEnterFrameToggleOffset_; 58 CodeOffset profilerExitFrameToggleOffset_; 59 60 // Early Ion bailouts will enter at this address. This is after frame 61 // construction and before environment chain is initialized. 62 CodeOffset bailoutPrologueOffset_; 63 64 // Baseline Interpreter can enter Baseline Compiler code at this address. This 65 // is right after the warm-up counter check in the prologue. 66 CodeOffset warmUpCheckPrologueOffset_; 67 68 uint32_t pushedBeforeCall_ = 0; 69 #ifdef DEBUG 70 bool inCall_ = false; 71 #endif 72 73 template <typename... HandlerArgs> 74 explicit BaselineCodeGen(TempAllocator& alloc, MacroAssembler& masmArg, 75 CompileRuntime* runtimeArg, HandlerArgs&&... args); 76 77 template <typename T> 78 void pushArg(const T& t) { 79 masm.Push(t); 80 } 81 82 // Pushes the current script as argument for a VM function. 83 void pushScriptArg(); 84 85 // Pushes the bytecode pc as argument for a VM function. 86 void pushBytecodePCArg(); 87 88 // Pushes a name/object/scope associated with the current bytecode op (and 89 // stored in the script) as argument for a VM function. 90 void loadScriptGCThing(ScriptGCThingType type, Register dest, 91 Register scratch); 92 void loadScriptGCThingInternal(ScriptGCThingType type, Register dest, 93 Register scratch); 94 void pushScriptGCThingArg(ScriptGCThingType type, Register scratch1, 95 Register scratch2); 96 void pushScriptNameArg(Register scratch1, Register scratch2); 97 98 // Pushes a bytecode operand as argument for a VM function. 99 void pushUint8BytecodeOperandArg(Register scratch); 100 void pushUint16BytecodeOperandArg(Register scratch); 101 102 void loadInt32LengthBytecodeOperand(Register dest); 103 void loadNumFormalArguments(Register dest); 104 105 // Loads the current JSScript* in dest. 106 void loadScript(Register dest); 107 // Loads the current JitScript* in dest 108 void loadJitScript(Register dest); 109 110 void saveInterpreterPCReg(); 111 void restoreInterpreterPCReg(); 112 113 // Subtracts |script->nslots() * sizeof(Value)| from reg. 114 void subtractScriptSlotsSize(Register reg, Register scratch); 115 116 // Loads the resume entries of the current BaselineScript* in dest 117 void loadBaselineScriptResumeEntries(Register dest, Register scratch); 118 119 // Jump to the script's resume entry indicated by resumeIndex. 120 void jumpToResumeEntry(Register resumeIndex, Register scratch1, 121 Register scratch2); 122 123 // Load the global's lexical environment. 124 void loadGlobalLexicalEnvironment(Register dest); 125 void pushGlobalLexicalEnvironmentValue(ValueOperand scratch); 126 127 // Load the |this|-value from the global's lexical environment. 128 void loadGlobalThisValue(ValueOperand dest); 129 130 // Computes the frame size. See BaselineFrame::debugFrameSize_. 131 void computeFrameSize(Register dest); 132 133 void prepareVMCall(); 134 135 void storeFrameSizeAndPushDescriptor(uint32_t argSize, Register scratch); 136 137 enum class CallVMPhase { BeforePushingLocals, AfterPushingLocals }; 138 bool callVMInternal(VMFunctionId id, RetAddrEntry::Kind kind, 139 CallVMPhase phase); 140 141 template <typename Fn, Fn fn> 142 bool callVM(RetAddrEntry::Kind kind = RetAddrEntry::Kind::CallVM, 143 CallVMPhase phase = CallVMPhase::AfterPushingLocals); 144 145 template <typename Fn, Fn fn> 146 bool callVMNonOp(CallVMPhase phase = CallVMPhase::AfterPushingLocals) { 147 return callVM<Fn, fn>(RetAddrEntry::Kind::NonOpCallVM, phase); 148 } 149 150 template <typename T> 151 void emitGuardedCallPreBarrierAnyZone(const T& address, MIRType type, 152 Register scratch) { 153 MOZ_ASSERT_IF(handler.realmIndependentJitcode(), !masm.maybeRealm()); 154 masm.guardedCallPreBarrierAnyZone(address, type, scratch); 155 } 156 157 // ifDebuggee should be a function emitting code for when the script is a 158 // debuggee script. ifNotDebuggee (if present) is called to emit code for 159 // non-debuggee scripts. 160 template <typename F1, typename F2> 161 [[nodiscard]] bool emitDebugInstrumentation( 162 const F1& ifDebuggee, const mozilla::Maybe<F2>& ifNotDebuggee); 163 template <typename F> 164 [[nodiscard]] bool emitDebugInstrumentation(const F& ifDebuggee) { 165 return emitDebugInstrumentation(ifDebuggee, mozilla::Maybe<F>()); 166 } 167 168 bool emitSuspend(JSOp op); 169 170 [[nodiscard]] bool emitAfterYieldDebugInstrumentation(Register scratch); 171 [[nodiscard]] bool emitDebugAfterYield(); 172 173 // ifSet should be a function emitting code for when the script has |flag| 174 // set. ifNotSet emits code for when the flag isn't set. 175 template <typename F1, typename F2> 176 [[nodiscard]] bool emitTestScriptFlag(JSScript::ImmutableFlags flag, 177 const F1& ifSet, const F2& ifNotSet, 178 Register scratch); 179 180 // If |script->hasFlag(flag) == value|, execute the code emitted by |emit|. 181 template <typename F> 182 [[nodiscard]] bool emitTestScriptFlag(JSScript::ImmutableFlags flag, 183 bool value, const F& emit, 184 Register scratch); 185 template <typename F> 186 [[nodiscard]] bool emitTestScriptFlag(JSScript::MutableFlags flag, bool value, 187 const F& emit, Register scratch); 188 189 [[nodiscard]] bool emitEnterGeneratorCode(Register script, 190 Register resumeIndex, 191 Register scratch); 192 193 void emitInterpJumpToResumeEntry(Register script, Register resumeIndex, 194 Register scratch); 195 void emitJumpToInterpretOpLabel(); 196 197 [[nodiscard]] bool emitCheckThis(ValueOperand val, bool reinit = false); 198 void emitLoadReturnValue(ValueOperand val); 199 void emitGetAliasedVar(ValueOperand dest); 200 [[nodiscard]] bool emitGetAliasedDebugVar(ValueOperand dest); 201 202 [[nodiscard]] bool emitNextIC(); 203 [[nodiscard]] bool emitInterruptCheck(); 204 [[nodiscard]] bool emitWarmUpCounterIncrement(); 205 206 [[nodiscard]] bool emitTrialInliningCheck(Register count, Register icScript, 207 Register scratch); 208 209 #define EMIT_OP(op, ...) bool emit_##op(); 210 FOR_EACH_OPCODE(EMIT_OP) 211 #undef EMIT_OP 212 213 // JSOp::Pos, JSOp::Neg, JSOp::BitNot, JSOp::Inc, JSOp::Dec, JSOp::ToNumeric. 214 [[nodiscard]] bool emitUnaryArith(); 215 216 // JSOp::BitXor, JSOp::Lsh, JSOp::Add etc. 217 [[nodiscard]] bool emitBinaryArith(); 218 219 // Handles JSOp::Lt, JSOp::Gt, and friends 220 [[nodiscard]] bool emitCompare(); 221 222 [[nodiscard]] bool emitConstantStrictEq(JSOp op); 223 224 // Handles JSOp::NewObject and JSOp::NewInit. 225 [[nodiscard]] bool emitNewObject(); 226 227 // For a JOF_JUMP op, jumps to the op's jump target. 228 void emitJump(); 229 230 // For a JOF_JUMP op, jumps to the op's jump target depending on the Value 231 // in |val|. 232 void emitTestBooleanTruthy(bool branchIfTrue, ValueOperand val); 233 234 // Converts |val| to an index in the jump table and stores this in |dest| 235 // or branches to the default pc if not int32 or out-of-range. 236 void emitGetTableSwitchIndex(ValueOperand val, Register dest, 237 Register scratch1, Register scratch2); 238 239 // Jumps to the target of a table switch based on |key| and the 240 // firstResumeIndex stored in JSOp::TableSwitch. 241 void emitTableSwitchJump(Register key, Register scratch1, Register scratch2); 242 243 [[nodiscard]] bool emitReturn(); 244 245 [[nodiscard]] bool emitTest(bool branchIfTrue); 246 [[nodiscard]] bool emitAndOr(bool branchIfTrue); 247 [[nodiscard]] bool emitCoalesce(); 248 249 [[nodiscard]] bool emitCall(JSOp op); 250 [[nodiscard]] bool emitSpreadCall(JSOp op); 251 252 [[nodiscard]] bool emitDelElem(bool strict); 253 [[nodiscard]] bool emitDelProp(bool strict); 254 [[nodiscard]] bool emitSetElemSuper(bool strict); 255 [[nodiscard]] bool emitSetPropSuper(bool strict); 256 257 // Try to bake in the result of BindUnqualifiedGName instead of using an IC. 258 // Return true if we managed to optimize the op. 259 bool tryOptimizeBindUnqualifiedGlobalName(); 260 261 [[nodiscard]] bool emitInitPropGetterSetter(); 262 [[nodiscard]] bool emitInitElemGetterSetter(); 263 264 [[nodiscard]] bool emitFormalArgAccess(JSOp op); 265 266 [[nodiscard]] bool emitUninitializedLexicalCheck(const ValueOperand& val); 267 268 [[nodiscard]] bool emitIsMagicValue(); 269 270 void getEnvironmentCoordinateObject(Register reg); 271 Address getEnvironmentCoordinateAddressFromObject(Register objReg, 272 Register reg); 273 Address getEnvironmentCoordinateAddress(Register reg); 274 275 [[nodiscard]] bool emitPrologue(); 276 [[nodiscard]] bool emitEpilogue(); 277 [[nodiscard]] bool emitStackCheck(); 278 [[nodiscard]] bool emitDebugPrologue(); 279 [[nodiscard]] bool emitDebugEpilogue(); 280 281 [[nodiscard]] bool initEnvironmentChain(); 282 283 [[nodiscard]] bool emitHandleCodeCoverageAtPrologue(); 284 285 void emitInitFrameFields(Register nonFunctionEnv); 286 [[nodiscard]] bool emitIsDebuggeeCheck(); 287 void emitInitializeLocals(); 288 289 void emitProfilerEnterFrame(); 290 void emitProfilerExitFrame(); 291 292 void emitOutOfLinePostBarrierSlot(); 293 }; 294 295 using RetAddrEntryVector = js::Vector<RetAddrEntry, 16, SystemAllocPolicy>; 296 using AllocSiteIndexVector = js::Vector<uint32_t, 16, SystemAllocPolicy>; 297 298 // Interface used by BaselineCodeGen for BaselineCompiler. 299 class BaselineCompilerHandler { 300 CompilerFrameInfo frame_; 301 TempAllocator& alloc_; 302 BytecodeAnalysis analysis_; 303 #ifdef DEBUG 304 const MacroAssembler& masm_; 305 #endif 306 FixedList<Label> labels_; 307 RetAddrEntryVector retAddrEntries_; 308 AllocSiteIndexVector allocSiteIndices_; 309 310 // Native code offsets for OSR at JSOp::LoopHead ops. 311 using OSREntryVector = 312 Vector<BaselineScript::OSREntry, 16, SystemAllocPolicy>; 313 OSREntryVector osrEntries_; 314 315 JSScript* script_; 316 jsbytecode* pc_; 317 318 size_t nargs_; 319 320 JSObject* globalLexicalEnvironment_; 321 JSObject* globalThis_; 322 323 CallObject* callObjectTemplate_; 324 NamedLambdaObject* namedLambdaTemplate_; 325 326 // Index of the current ICEntry in the script's JitScript. 327 uint32_t icEntryIndex_; 328 329 uint32_t baseWarmUpThreshold_; 330 331 bool compileDebugInstrumentation_; 332 bool ionCompileable_; 333 334 bool compilingOffThread_ = false; 335 336 bool needsEnvAllocSite_ = false; 337 338 public: 339 using FrameInfoT = CompilerFrameInfo; 340 341 BaselineCompilerHandler(MacroAssembler& masm, TempAllocator& alloc, 342 BaselineSnapshot* snapshot); 343 344 [[nodiscard]] bool init(); 345 346 CompilerFrameInfo& frame() { return frame_; } 347 348 jsbytecode* pc() const { return pc_; } 349 jsbytecode* maybePC() const { return pc_; } 350 351 void moveToNextPC() { pc_ += GetBytecodeLength(pc_); } 352 Label* labelOf(jsbytecode* pc) { return &labels_[script_->pcToOffset(pc)]; } 353 354 bool isDefinitelyLastOp() const { return pc_ == script_->lastPC(); } 355 356 bool shouldEmitDebugEpilogueAtReturnOp() const { 357 // The JIT uses the return address -> pc mapping and bakes in the pc 358 // argument so the DebugEpilogue call needs to be part of the returning 359 // bytecode op for this to work. 360 return true; 361 } 362 363 JSScript* script() const { return script_; } 364 JSScript* maybeScript() const { return script_; } 365 366 size_t nargs() const { 367 MOZ_ASSERT(isFunction()); 368 return nargs_; 369 } 370 CallObject* callObjectTemplate() const { return callObjectTemplate_; } 371 NamedLambdaObject* namedLambdaTemplate() const { 372 return namedLambdaTemplate_; 373 } 374 375 bool isFunction() const { return !!script_->function(); } 376 377 ModuleObject* module() const { return script_->module(); } 378 379 bool compileDebugInstrumentation() const { 380 return compileDebugInstrumentation_; 381 } 382 383 bool maybeIonCompileable() const { return ionCompileable_; } 384 void setIonCompileable(bool value) { ionCompileable_ = value; } 385 386 uint32_t icEntryIndex() const { return icEntryIndex_; } 387 void moveToNextICEntry() { icEntryIndex_++; } 388 389 BytecodeAnalysis& analysis() { return analysis_; } 390 391 RetAddrEntryVector& retAddrEntries() { return retAddrEntries_; } 392 OSREntryVector& osrEntries() { return osrEntries_; } 393 394 [[nodiscard]] bool recordCallRetAddr(RetAddrEntry::Kind kind, 395 uint32_t retOffset); 396 397 // If a script has more |nslots| than this the stack check must account 398 // for these slots explicitly. 399 bool mustIncludeSlotsInStackCheck() const { 400 static constexpr size_t NumSlotsLimit = 128; 401 return script()->nslots() > NumSlotsLimit; 402 } 403 404 bool canHaveFixedSlots() const { return script()->nfixed() != 0; } 405 406 JSObject* maybeGlobalLexicalEnvironment() const { 407 return globalLexicalEnvironment_; 408 } 409 JSObject* globalThis() const { return globalThis_; } 410 411 uint32_t baseWarmUpThreshold() const { return baseWarmUpThreshold_; } 412 413 void maybeDisableIon(); 414 415 [[nodiscard]] bool addAllocSiteIndex(uint32_t entryIndex) { 416 return allocSiteIndices_.append(entryIndex); 417 } 418 void createAllocSites(); 419 420 bool compilingOffThread() const { return compilingOffThread_; } 421 void setCompilingOffThread() { compilingOffThread_ = true; } 422 423 bool addEnvAllocSite() { 424 needsEnvAllocSite_ = true; 425 return true; 426 } 427 428 bool realmIndependentJitcode() const { 429 return JS::Prefs::experimental_self_hosted_cache() && 430 script()->selfHosted(); 431 } 432 }; 433 434 using BaselineCompilerCodeGen = BaselineCodeGen<BaselineCompilerHandler>; 435 436 class BaselineCompiler final : private BaselineCompilerCodeGen { 437 // Native code offsets for bytecode ops in the script's resume offsets list. 438 ResumeOffsetEntryVector resumeOffsetEntries_; 439 440 // Native code offsets for debug traps if the script is compiled with debug 441 // instrumentation. 442 using DebugTrapEntryVector = 443 Vector<BaselineScript::DebugTrapEntry, 0, SystemAllocPolicy>; 444 DebugTrapEntryVector debugTrapEntries_; 445 446 CodeOffset profilerPushToggleOffset_; 447 448 BaselinePerfSpewer perfSpewer_; 449 450 public: 451 BaselineCompiler(TempAllocator& alloc, CompileRuntime* runtime, 452 MacroAssembler& masm, BaselineSnapshot* snapshot); 453 [[nodiscard]] bool init(); 454 455 static bool PrepareToCompile(JSContext* cx, Handle<JSScript*> script, 456 bool compileDebugInstrumentation); 457 458 MethodStatus compile(JSContext* cx); 459 MethodStatus compileOffThread(); 460 461 bool finishCompile(JSContext* cx); 462 463 bool compileDebugInstrumentation() const { 464 return handler.compileDebugInstrumentation(); 465 } 466 467 private: 468 bool compileImpl(); 469 470 bool emitBody(); 471 472 [[nodiscard]] bool emitDebugTrap(); 473 }; 474 475 // Interface used by BaselineCodeGen for BaselineInterpreterGenerator. 476 class BaselineInterpreterHandler { 477 InterpreterFrameInfo frame_; 478 479 // Entry point to start interpreting a bytecode op. No registers are live. PC 480 // is loaded from the frame. 481 NonAssertingLabel interpretOp_; 482 483 // Like interpretOp_ but at this point the PC is expected to be in 484 // InterpreterPCReg. 485 NonAssertingLabel interpretOpWithPCReg_; 486 487 // Offsets of toggled jumps for debugger instrumentation. 488 using CodeOffsetVector = Vector<uint32_t, 0, SystemAllocPolicy>; 489 CodeOffsetVector debugInstrumentationOffsets_; 490 491 // Offsets of toggled jumps for code coverage instrumentation. 492 CodeOffsetVector codeCoverageOffsets_; 493 NonAssertingLabel codeCoverageAtPrologueLabel_; 494 NonAssertingLabel codeCoverageAtPCLabel_; 495 496 // Offsets of IC calls for IsIonInlinableOp ops, for Ion bailouts. 497 BaselineInterpreter::ICReturnOffsetVector icReturnOffsets_; 498 499 // Offsets of some callVMs for BaselineDebugModeOSR. 500 BaselineInterpreter::CallVMOffsets callVMOffsets_; 501 502 // The current JSOp we are emitting interpreter code for. 503 mozilla::Maybe<JSOp> currentOp_; 504 505 public: 506 using FrameInfoT = InterpreterFrameInfo; 507 508 explicit BaselineInterpreterHandler(MacroAssembler& masm); 509 510 InterpreterFrameInfo& frame() { return frame_; } 511 512 Label* interpretOpLabel() { return &interpretOp_; } 513 Label* interpretOpWithPCRegLabel() { return &interpretOpWithPCReg_; } 514 515 Label* codeCoverageAtPrologueLabel() { return &codeCoverageAtPrologueLabel_; } 516 Label* codeCoverageAtPCLabel() { return &codeCoverageAtPCLabel_; } 517 518 CodeOffsetVector& debugInstrumentationOffsets() { 519 return debugInstrumentationOffsets_; 520 } 521 CodeOffsetVector& codeCoverageOffsets() { return codeCoverageOffsets_; } 522 523 BaselineInterpreter::ICReturnOffsetVector& icReturnOffsets() { 524 return icReturnOffsets_; 525 } 526 527 void setCurrentOp(JSOp op) { currentOp_.emplace(op); } 528 void resetCurrentOp() { currentOp_.reset(); } 529 mozilla::Maybe<JSOp> currentOp() const { return currentOp_; } 530 531 // Interpreter doesn't know the script and pc statically. 532 jsbytecode* maybePC() const { return nullptr; } 533 bool isDefinitelyLastOp() const { return false; } 534 JSScript* maybeScript() const { return nullptr; } 535 536 bool shouldEmitDebugEpilogueAtReturnOp() const { 537 // The interpreter doesn't use the return address -> pc mapping and doesn't 538 // bake in bytecode PCs so it can emit a shared DebugEpilogue call instead 539 // of duplicating it for every return op. 540 return false; 541 } 542 543 [[nodiscard]] bool addDebugInstrumentationOffset(CodeOffset offset); 544 545 const BaselineInterpreter::CallVMOffsets& callVMOffsets() const { 546 return callVMOffsets_; 547 } 548 549 [[nodiscard]] bool recordCallRetAddr(RetAddrEntry::Kind kind, 550 uint32_t retOffset); 551 552 bool maybeIonCompileable() const { return true; } 553 554 // The interpreter doesn't know the number of slots statically so we always 555 // include them. 556 bool mustIncludeSlotsInStackCheck() const { return true; } 557 558 bool canHaveFixedSlots() const { return true; } 559 JSObject* maybeGlobalLexicalEnvironment() const { return nullptr; } 560 561 bool addEnvAllocSite() { return false; } // Not supported. 562 563 bool realmIndependentJitcode() const { return true; } 564 }; 565 566 using BaselineInterpreterCodeGen = BaselineCodeGen<BaselineInterpreterHandler>; 567 568 class BaselineInterpreterGenerator final : private BaselineInterpreterCodeGen { 569 // Offsets of patchable call instructions for debugger breakpoints/stepping. 570 Vector<uint32_t, 0, SystemAllocPolicy> debugTrapOffsets_; 571 572 // Offsets of move instructions for tableswitch base address. 573 Vector<CodeOffset, 0, SystemAllocPolicy> tableLabels_; 574 575 // Offset of the first tableswitch entry. 576 uint32_t tableOffset_ = 0; 577 578 // Offset of the code to start interpreting a bytecode op. 579 uint32_t interpretOpOffset_ = 0; 580 581 // Like interpretOpOffset_ but skips the debug trap for the current op. 582 uint32_t interpretOpNoDebugTrapOffset_ = 0; 583 584 // Offset of the jump (tail call) to the debug trap handler trampoline code. 585 // When the debugger is enabled, NOPs are patched to calls to this location. 586 uint32_t debugTrapHandlerOffset_ = 0; 587 588 BaselineInterpreterPerfSpewer perfSpewer_; 589 590 public: 591 explicit BaselineInterpreterGenerator(JSContext* cx, TempAllocator& alloc, 592 MacroAssembler& masm); 593 594 [[nodiscard]] bool generate(JSContext* cx, BaselineInterpreter& interpreter); 595 596 private: 597 [[nodiscard]] bool emitInterpreterLoop(); 598 [[nodiscard]] bool emitDebugTrap(); 599 600 void emitOutOfLineCodeCoverageInstrumentation(); 601 }; 602 603 } // namespace jit 604 } // namespace js 605 606 #endif /* jit_BaselineCodeGen_h */