https://github.com/mozilla/gecko-dev
Tip revision: ae45b008041d0e05081c1cac71910b0109720215 authored by Petru-Mugurel Lingurar on 08 May 2020, 15:52:13 UTC
Bug 1633568 - Document the installation ping. r=frank, a=RyanVM
Bug 1633568 - Document the installation ping. r=frank, a=RyanVM
Tip revision: ae45b00
Bailouts.h
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* 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/JitFrames.h"
#include "jit/JSJitFrameIter.h"
#include "vm/Stack.h"
namespace js {
namespace jit {
// [SMDOC] IonMonkey Bailouts
//
// A "bailout" is the process of recovering a baseline frame from an IonFrame.
// Bailouts are implemented in js::jit::BailoutIonToBaseline, which has the
// following callers:
//
// * js::jit::Bailout - This is used when a guard fails in the Ion code
// itself; for example, an LGuardShape fails or an LAddI overflows. See
// callers of CodeGenerator::bailoutFrom() for more examples.
//
// * js::jit::ExceptionHandlerBailout - Something called from Ion code
// failed. Ion doesn't implement `catch`; it handles all exceptions by
// bailing out.
//
// * js::jit::InvalidationBailout - We returned to Ion code that was
// invalidated while it was on the stack. See "OSI" below. Ion code can be
// invalidated for several reasons: when GC evicts Ion code to save memory,
// for example, or when assumptions baked into the jitted code are
// invalidated by the VM (see callers of IonBuilder::constraints()).
//
// (Some stack inspection can be done without bailing out, including GC stack
// marking, Error object construction, and Gecko profiler sampling.)
//
// Consider the first case. When an Ion guard fails, we can't continue in
// Ion. There's no IC fallback case coming to save us; we've got a broken
// assumption baked into the code we're running. So we jump to an out-of-line
// code path that's responsible for abandoning Ion execution and resuming in
// baseline: the bailout path.
//
// We were in the midst of optimized Ion code, so bits of program state may be
// in registers or spilled to the native stack; values may be unboxed; some
// objects may have been optimized away; thanks to inlining, whole call frames
// may be missing. The bailout path must put all these pieces back together
// into the structure the baseline code expects.
//
// The data structure that makes this possible is called a *snapshot*.
// Snapshots are created during Ion codegen and associated with the IonScript;
// they tell how to recover each value in a BaselineFrame from the current
// machine state at a given point in the Ion JIT code. This is potentially
// different at every place in an Ion script where we might bail out. (See
// Snapshots.h.)
//
// The bailout path performs roughly the following steps:
//
// 1. Push a snapshot index and the frame size to the native stack.
// 2. Spill all registers.
// 3. Call js::jit::Bailout to reconstruct the baseline frame(s).
// 4. memmove() those to the right place on the native stack.
// 5. Jump to baseline code.
//
// (This last step requires baseline JIT code to have an entry point at each pc
// where an eventual Ion guard may be inserted.)
//
// When C++ code invalidates Ion code, we do on-stack invalidation, or OSI, to
// arrange for every affected Ion frame on the stack to bail out as soon as
// control returns to it. OSI patches every instruction in the JIT code that's
// at a return address currently on the stack. See InvalidateActivation.
//
//
// ## Bailout path implementation details
//
// Ion code has a lot of guards, so each bailout path must be small. Steps 2
// and 3 above are therefore implemented by a shared per-Runtime trampoline,
// rt->jitRuntime()->getGenericBailoutHandler().
//
// Naively, we could implement step 1 like:
//
// _bailout_ID_1:
// push 1
// jmp _deopt
// _bailout_ID_2:
// push 2
// jmp _deopt
// ...
// _deopt:
// push imm(FrameSize)
// call _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 JitFrames.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;
// This address is a magic number made to cause crashes while indicating that we
// are making an attempt to mark the stack during a bailout.
static const uint32_t FAKE_EXITFP_FOR_BAILOUT_ADDR = 0xba2;
static uint8_t* const FAKE_EXITFP_FOR_BAILOUT =
reinterpret_cast<uint8_t*>(FAKE_EXITFP_FOR_BAILOUT_ADDR);
static_assert(!(FAKE_EXITFP_FOR_BAILOUT_ADDR & wasm::ExitOrJitEntryFPTag),
"FAKE_EXITFP_FOR_BAILOUT could be mistaken as a low-bit tagged "
"wasm exit fp");
// 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 structure is constructed before recovering the baseline frames for a
// bailout. It records all information extracted from the stack, and which are
// needed for the JSJitFrameIter.
class BailoutFrameInfo {
MachineState machine_;
uint8_t* framePointer_;
size_t topFrameSize_;
IonScript* topIonScript_;
uint32_t snapshotOffset_;
JitActivation* activation_;
void attachOnJitActivation(const JitActivationIterator& activations);
public:
BailoutFrameInfo(const JitActivationIterator& activations, BailoutStack* sp);
BailoutFrameInfo(const JitActivationIterator& activations,
InvalidationBailoutStack* sp);
BailoutFrameInfo(const JitActivationIterator& activations,
const JSJitFrameIter& frame);
~BailoutFrameInfo();
uint8_t* fp() const { return framePointer_; }
SnapshotOffset snapshotOffset() const { return snapshotOffset_; }
const MachineState* machineState() const { return &machine_; }
size_t topFrameSize() const { return topFrameSize_; }
IonScript* ionScript() const { return topIonScript_; }
JitActivation* activation() const { return activation_; }
};
MOZ_MUST_USE bool EnsureHasEnvironmentObjects(JSContext* cx,
AbstractFramePtr fp);
struct BaselineBailoutInfo;
// Called from a bailout thunk.
MOZ_MUST_USE bool Bailout(BailoutStack* sp, BaselineBailoutInfo** info);
// Called from the invalidation thunk.
MOZ_MUST_USE bool 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.
MOZ_MUST_USE bool ExceptionHandlerBailout(JSContext* cx,
const InlineFrameIterator& frame,
ResumeFromException* rfe,
const ExceptionBailoutInfo& excInfo);
MOZ_MUST_USE bool FinishBailoutToBaseline(BaselineBailoutInfo* bailoutInfo);
void CheckFrequentBailouts(JSContext* cx, JSScript* script,
BailoutKind bailoutKind);
} // namespace jit
} // namespace js
#endif /* jit_Bailouts_h */