michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #ifndef jit_Bailouts_h michael@0: #define jit_Bailouts_h michael@0: michael@0: #include "jstypes.h" michael@0: michael@0: #include "jit/IonFrames.h" michael@0: #include "jit/JitFrameIterator.h" michael@0: #include "vm/Stack.h" michael@0: michael@0: namespace js { michael@0: namespace jit { michael@0: michael@0: // A "bailout" is a condition in which we need to recover an interpreter frame michael@0: // from an IonFrame. Bailouts can happen for the following reasons: michael@0: // (1) A deoptimization guard, for example, an add overflows or a type check michael@0: // fails. michael@0: // (2) A check or assumption held by the JIT is invalidated by the VM, and michael@0: // JIT code must be thrown away. This includes the GC possibly deciding michael@0: // to evict live JIT code, or a Type Inference reflow. michael@0: // michael@0: // Note that bailouts as described here do not include normal Ion frame michael@0: // inspection, for example, if an exception must be built or the GC needs to michael@0: // scan an Ion frame for gcthings. michael@0: // michael@0: // The second type of bailout needs a different name - "deoptimization" or michael@0: // "deep bailout". Here we are concerned with eager (or maybe "shallow") michael@0: // bailouts, that happen from JIT code. These happen from guards, like: michael@0: // michael@0: // cmp [obj + shape], 0x50M37TH1NG michael@0: // jmp _bailout michael@0: // michael@0: // The bailout target needs to somehow translate the Ion frame (whose state michael@0: // will differ at each program point) to an interpreter frame. This state is michael@0: // captured into the IonScript's snapshot buffer, and for each bailout we know michael@0: // which snapshot corresponds to its state. michael@0: // michael@0: // Roughly, the following needs to happen at the bailout target. michael@0: // (1) Move snapshot ID into a known stack location (registers cannot be michael@0: // mutated). michael@0: // (2) Spill all registers to the stack. michael@0: // (3) Call a Bailout() routine, whose argument is the stack pointer. michael@0: // (4) Bailout() will find the IonScript on the stack, use the snapshot ID michael@0: // to find the structure of the frame, and then use the stack and spilled michael@0: // registers to perform frame conversion. michael@0: // (5) Bailout() returns, and the JIT must immediately return to the michael@0: // interpreter (all frames are converted at once). michael@0: // michael@0: // (2) and (3) are implemented by a trampoline held in the compartment. michael@0: // Naively, we could implement (1) like: michael@0: // michael@0: // _bailout_ID_1: michael@0: // push 1 michael@0: // jmp _global_bailout_handler michael@0: // _bailout_ID_2: michael@0: // push 2 michael@0: // jmp _global_bailout_handler michael@0: // michael@0: // This takes about 10 extra bytes per guard. On some platforms, we can reduce michael@0: // this overhead to 4 bytes by creating a global jump table, shared again in michael@0: // the compartment: michael@0: // michael@0: // call _global_bailout_handler michael@0: // call _global_bailout_handler michael@0: // call _global_bailout_handler michael@0: // call _global_bailout_handler michael@0: // ... michael@0: // _global_bailout_handler: michael@0: // michael@0: // In the bailout handler, we can recompute which entry in the table was michael@0: // selected by subtracting the return addressed pushed by the call, from the michael@0: // start of the table, and then dividing by the size of a (call X) entry in the michael@0: // table. This gives us a number in [0, TableSize), which we call a michael@0: // "BailoutId". michael@0: // michael@0: // Then, we can provide a per-script mapping from BailoutIds to snapshots, michael@0: // which takes only four bytes per entry. michael@0: // michael@0: // This strategy does not work as given, because the bailout handler has no way michael@0: // to compute the location of an IonScript. Currently, we do not use frame michael@0: // pointers. To account for this we segregate frames into a limited set of michael@0: // "frame sizes", and create a table for each frame size. We also have the michael@0: // option of not using bailout tables, for platforms or situations where the michael@0: // 10 byte cost is more optimal than a bailout table. See IonFrames.h for more michael@0: // detail. michael@0: michael@0: static const BailoutId INVALID_BAILOUT_ID = BailoutId(-1); michael@0: michael@0: // Keep this arbitrarily small for now, for testing. michael@0: static const uint32_t BAILOUT_TABLE_SIZE = 16; michael@0: michael@0: // Bailout return codes. michael@0: // N.B. the relative order of these values is hard-coded into ::GenerateBailoutThunk. michael@0: static const uint32_t BAILOUT_RETURN_OK = 0; michael@0: static const uint32_t BAILOUT_RETURN_FATAL_ERROR = 1; michael@0: static const uint32_t BAILOUT_RETURN_OVERRECURSED = 2; michael@0: michael@0: class JitCompartment; michael@0: michael@0: // BailoutStack is an architecture specific pointer to the stack, given by the michael@0: // bailout handler. michael@0: class BailoutStack; michael@0: class InvalidationBailoutStack; michael@0: michael@0: // Must be implemented by each architecture. michael@0: michael@0: // This iterator is constructed at a time where there is no exit frame at the michael@0: // moment. They must be initialized to the first JS frame instead of the exit michael@0: // frame as usually done with JitFrameIterator. michael@0: class IonBailoutIterator : public JitFrameIterator michael@0: { michael@0: MachineState machine_; michael@0: uint32_t snapshotOffset_; michael@0: size_t topFrameSize_; michael@0: IonScript *topIonScript_; michael@0: michael@0: public: michael@0: IonBailoutIterator(const JitActivationIterator &activations, BailoutStack *sp); michael@0: IonBailoutIterator(const JitActivationIterator &activations, InvalidationBailoutStack *sp); michael@0: IonBailoutIterator(const JitActivationIterator &activations, const JitFrameIterator &frame); michael@0: michael@0: SnapshotOffset snapshotOffset() const { michael@0: JS_ASSERT(topIonScript_); michael@0: return snapshotOffset_; michael@0: } michael@0: const MachineState &machineState() const { michael@0: return machine_; michael@0: } michael@0: size_t topFrameSize() const { michael@0: JS_ASSERT(topIonScript_); michael@0: return topFrameSize_; michael@0: } michael@0: IonScript *ionScript() const { michael@0: if (topIonScript_) michael@0: return topIonScript_; michael@0: return JitFrameIterator::ionScript(); michael@0: } michael@0: michael@0: void dump() const; michael@0: }; michael@0: michael@0: bool EnsureHasScopeObjects(JSContext *cx, AbstractFramePtr fp); michael@0: michael@0: struct BaselineBailoutInfo; michael@0: michael@0: // Called from a bailout thunk. Returns a BAILOUT_* error code. michael@0: uint32_t Bailout(BailoutStack *sp, BaselineBailoutInfo **info); michael@0: michael@0: // Called from the invalidation thunk. Returns a BAILOUT_* error code. michael@0: uint32_t InvalidationBailout(InvalidationBailoutStack *sp, size_t *frameSizeOut, michael@0: BaselineBailoutInfo **info); michael@0: michael@0: class ExceptionBailoutInfo michael@0: { michael@0: size_t frameNo_; michael@0: jsbytecode *resumePC_; michael@0: size_t numExprSlots_; michael@0: michael@0: public: michael@0: ExceptionBailoutInfo(size_t frameNo, jsbytecode *resumePC, size_t numExprSlots) michael@0: : frameNo_(frameNo), michael@0: resumePC_(resumePC), michael@0: numExprSlots_(numExprSlots) michael@0: { } michael@0: michael@0: ExceptionBailoutInfo() michael@0: : frameNo_(0), michael@0: resumePC_(nullptr), michael@0: numExprSlots_(0) michael@0: { } michael@0: michael@0: bool catchingException() const { michael@0: return !!resumePC_; michael@0: } michael@0: bool propagatingIonExceptionForDebugMode() const { michael@0: return !resumePC_; michael@0: } michael@0: michael@0: size_t frameNo() const { michael@0: MOZ_ASSERT(catchingException()); michael@0: return frameNo_; michael@0: } michael@0: jsbytecode *resumePC() const { michael@0: MOZ_ASSERT(catchingException()); michael@0: return resumePC_; michael@0: } michael@0: size_t numExprSlots() const { michael@0: MOZ_ASSERT(catchingException()); michael@0: return numExprSlots_; michael@0: } michael@0: }; michael@0: michael@0: // Called from the exception handler to enter a catch or finally block. michael@0: // Returns a BAILOUT_* error code. michael@0: uint32_t ExceptionHandlerBailout(JSContext *cx, const InlineFrameIterator &frame, michael@0: ResumeFromException *rfe, michael@0: const ExceptionBailoutInfo &excInfo, michael@0: bool *overrecursed); michael@0: michael@0: uint32_t FinishBailoutToBaseline(BaselineBailoutInfo *bailoutInfo); michael@0: michael@0: bool CheckFrequentBailouts(JSContext *cx, JSScript *script); michael@0: michael@0: } // namespace jit michael@0: } // namespace js michael@0: michael@0: #endif /* jit_Bailouts_h */