blob: d1341f906a42983bae3b1ca76c8c1d16ad02ce5c [file] [log] [blame]
/*
* Copyright (C) 2013-2015 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef FTLOSRExit_h
#define FTLOSRExit_h
#if ENABLE(FTL_JIT)
#include "CodeOrigin.h"
#include "DFGExitProfile.h"
#include "DFGOSRExitBase.h"
#include "FTLAbbreviatedTypes.h"
#include "FTLExitTimeObjectMaterialization.h"
#include "FTLExitValue.h"
#include "FTLFormattedValue.h"
#include "FTLStackMaps.h"
#include "FTLStackmapArgumentList.h"
#include "HandlerInfo.h"
#include "MethodOfGettingAValueProfile.h"
#include "Operands.h"
#include "Reg.h"
#include "ValueProfile.h"
#include "VirtualRegister.h"
namespace JSC {
class TrackedReferences;
namespace FTL {
// Tracks one OSR exit site within the FTL JIT. OSR exit in FTL works by deconstructing
// the crazy that is OSR down to simple SSA CFG primitives that any compiler backend
// (including of course LLVM) can grok and do meaningful things to. An exit is just a
// conditional branch in the emitted code where one destination is the continuation and
// the other is a basic block that performs a no-return tail-call to an exit thunk.
// This thunk takes as its arguments the live non-constant not-already-accounted-for
// bytecode state. To appreciate how this works consider the following JavaScript
// program, and its lowering down to LLVM IR including the relevant exits:
//
// function foo(o) {
// var a = o.a; // predicted int
// var b = o.b;
// var c = o.c; // NB this is dead
// a = a | 5; // our example OSR exit: need to check if a is an int
// return a + b;
// }
//
// Just consider the "a | 5". In the DFG IR, this looks like:
//
// BitOr(Check:Int32:@a, Int32:5)
//
// Where @a is the node for the value of the 'a' variable. Conceptually, this node can
// be further broken down to the following (note that this particular lowering never
// actually happens - we skip this step and go straight to LLVM IR - but it's still
// useful to see this):
//
// exitIf(@a is not int32);
// continuation;
//
// Where 'exitIf()' is a function that will exit if the argument is true, and
// 'continuation' is the stuff that we will do after the exitIf() check. (Note that
// FTL refers to 'exitIf()' as 'speculate()', which is in line with DFG terminology.)
// This then gets broken down to the following LLVM IR, assuming that %0 is the LLVM
// value corresponding to variable 'a', and %1 is the LLVM value for variable 'b':
//
// %2 = ... // the predictate corresponding to '@a is not int32'
// br i1 %2, label %3, label %4
// ; <label>:3
// call void exitThunk1(%0, %1) // pass 'a' and 'b', since they're both live-in-bytecode
// unreachable
// ; <label>:4
// ... // code for the continuation
//
// Where 'exitThunk1' is the IR to get the exit thunk for *this* OSR exit. Each OSR
// exit will appear to LLVM to have a distinct exit thunk.
//
// Note that this didn't have to pass '5', 'o', or 'c' to the exit thunk. 5 is a
// constant and the DFG already knows that, and can already tell the OSR exit machinery
// what that contant is and which bytecode variables (if any) it needs to be dropped
// into. This is conveyed to the exit statically, via the OSRExit data structure below.
// See the code for ExitValue for details. 'o' is an argument, and arguments are always
// "flushed" - if you never assign them then their values are still in the argument
// stack slots, and if you do assign them then we eagerly store them into those slots.
// 'c' is dead in bytecode, and the DFG knows this; we statically tell the exit thunk
// that it's dead and don't have to pass anything. The exit thunk will "initialize" its
// value to Undefined.
//
// This approach to OSR exit has a number of virtues:
//
// - It is an entirely unsurprising representation for a compiler that already groks
// CFG-like IRs for C-like languages. All existing analyses and transformations just
// work.
//
// - It lends itself naturally to modern approaches to code motion. For example, you
// could sink operations from above the exit to below it, if you just duplicate the
// operation into the OSR exit block. This is both legal and desirable. It works
// because the backend sees the OSR exit block as being no different than any other,
// and LLVM already supports sinking if it sees that a value is only partially used.
// Hence there exists a value that dominates the exit but is only used by the exit
// thunk and not by the continuation, sinking ought to kick in for that value.
// Hoisting operations from below it to above it is also possible, for similar
// reasons.
//
// - The no-return tail-call to the OSR exit thunk can be subjected to specialized
// code-size reduction optimizations, though this is optional. For example, instead
// of actually emitting a call along with all that goes with it (like placing the
// arguments into argument position), the backend could choose to simply inform us
// where it had placed the arguments and expect the callee (i.e. the exit thunk) to
// figure it out from there. It could also tell us what we need to do to pop stack,
// although again, it doesn't have to; it could just emit that code normally. We do
// all of these things through the patchpoint/stackmap LLVM intrinsics.
//
// - It could be extended to allow the backend to do its own exit hoisting, by using
// intrinsics (or meta-data, or something) to inform the backend that it's safe to
// make the predicate passed to 'exitIf()' more truthy.
enum class ExceptionType : uint8_t {
None,
CCallException,
JSCall,
GetById,
GetByIdCallOperation,
PutById,
PutByIdCallOperation,
LazySlowPath,
BinaryOpGenerator,
};
struct OSRExitDescriptor {
OSRExitDescriptor(
ExitKind, ExceptionType, DataFormat profileDataFormat, MethodOfGettingAValueProfile,
CodeOrigin, CodeOrigin originForProfile,
unsigned numberOfArguments, unsigned numberOfLocals);
bool isExceptionHandler() const;
ExitKind m_kind;
ExceptionType m_exceptionType;
CodeOrigin m_codeOrigin;
CodeOrigin m_codeOriginForExitProfile;
CodeOrigin m_semanticCodeOriginForCallFrameHeader;
// The first argument to the exit call may be a value we wish to profile.
// If that's the case, the format will be not Invalid and we'll have a
// method of getting a value profile. Note that all of the ExitArgument's
// are already aware of this possible off-by-one, so there is no need to
// correct them.
DataFormat m_profileDataFormat;
MethodOfGettingAValueProfile m_valueProfile;
Operands<ExitValue> m_values;
Bag<ExitTimeObjectMaterialization> m_materializations;
uint32_t m_stackmapID;
HandlerInfo m_baselineExceptionHandler;
bool m_isInvalidationPoint : 1;
void validateReferences(const TrackedReferences&);
};
struct OSRExit : public DFG::OSRExitBase {
OSRExit(OSRExitDescriptor&, uint32_t stackmapRecordIndex);
OSRExitDescriptor& m_descriptor;
MacroAssemblerCodeRef m_code;
// Offset within the exit stubs of the stub for this exit.
unsigned m_patchableCodeOffset;
// Offset within Stackmap::records
uint32_t m_stackmapRecordIndex;
ExceptionType m_exceptionType;
RegisterSet registersToPreserveForCallThatMightThrow;
CodeLocationJump codeLocationForRepatch(CodeBlock* ftlCodeBlock) const;
void considerAddingAsFrequentExitSite(CodeBlock* profiledCodeBlock)
{
OSRExitBase::considerAddingAsFrequentExitSite(profiledCodeBlock, ExitFromFTL);
}
void gatherRegistersToSpillForCallIfException(StackMaps&, StackMaps::Record&);
void spillRegistersToSpillSlot(CCallHelpers&, int32_t stackSpillSlot);
void recoverRegistersFromSpillSlot(CCallHelpers& jit, int32_t stackSpillSlot);
bool willArriveAtOSRExitFromGenericUnwind() const;
bool willArriveAtExitFromIndirectExceptionCheck() const;
bool willArriveAtOSRExitFromCallOperation() const;
bool needsRegisterRecoveryOnGenericUnwindOSRExitPath() const;
};
} } // namespace JSC::FTL
#endif // ENABLE(FTL_JIT)
#endif // FTLOSRExit_h