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