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