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