Source code drop for the Actionscript virtual machine in the Adobe Flash Player for December 2013
714 lines
28 KiB
C++
714 lines
28 KiB
C++
/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 2 -*- */
|
|
/* vi: set ts=2 sw=2 expandtab: (add to ~/.vimrc: set modeline modelines=5) */
|
|
/* 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/. */
|
|
|
|
#include "hm-main.h"
|
|
#ifdef VMCFG_HALFMOON
|
|
|
|
namespace halfmoon {
|
|
using avmplus::Exception;
|
|
using avmplus::ExceptionFrame;
|
|
using avmplus::kCatchAction_Rethrow;
|
|
using avmplus::Verifier;
|
|
|
|
inline static void dbgDumpIr(InstrGraph *ir, const char *banner, Context *cxt);
|
|
inline static bool dbgInlineSkip(MethodInfo *method_info);
|
|
inline static bool dbgInlineOnly(MethodInfo *method_info);
|
|
inline static void dbgInlineCallsite(CallStmt2 *call, Context *cxt, InstrGraph *callee_ir);
|
|
inline static void dbgPrintGraph(Context *cxt,InstrGraph *ir, const char *title);
|
|
struct InlineWorklistItem;
|
|
inline static void dbgPrintInlineWorklist(Context *cxt, SeqBuilder<InlineWorklistItem>& work);
|
|
inline static void dbgDumpIr(Instr *call_site, InstrGraph* outer_ir, InstrGraph *callee_ir);
|
|
|
|
static bool inline_enable_verbose = false; //enable_verbose!=0;
|
|
static bool INLINE_ALWAYS_VERBOSE = false;
|
|
|
|
//make debug printout code less invasive
|
|
#ifdef AVMPLUS_VERBOSE
|
|
#define INLINE_VERBOSE(CTX, ...) if (inline_enable_verbose){ CTX << __VA_ARGS__ << "\n";}
|
|
#else
|
|
#define INLINE_VERBOSE(CTX,...) /*nada*/
|
|
inline static void dbgDumpIr(InstrGraph *, const char *, Context *){}
|
|
inline static bool dbgInlineSkip(MethodInfo *method_info){(void)method_info; return false;}
|
|
inline static bool dbgInlineOnly(MethodInfo *){ return false;};
|
|
inline static void dbgPrintGraph(Context *,InstrGraph *, const char *){}
|
|
inline static void dbgPrintInlineWorklist(Context *, SeqBuilder<InlineWorklistItem>& ){}
|
|
inline static void dbgInlineCallsite(CallStmt2 *, Context *, InstrGraph *){}
|
|
inline static void dbgDumpIr(Instr *, InstrGraph* , InstrGraph *){};
|
|
#endif /*AVMPLUS_VERBOSE*/
|
|
|
|
/**
|
|
*
|
|
* @param call_site the call statement to be delved into
|
|
* @return the MethodInfo of the target of the call.
|
|
*/
|
|
inline MethodInfo * targetOfCallSite(CallStmt2 *call_site) {
|
|
//The first parm of the callmethod is the target of the call.
|
|
//the value ought to be of type kTypeEnv.
|
|
//it represents a MethodEnv that we can ask questions of (like finality)
|
|
//
|
|
assert(shape(call_site) == CALLSTMT2_SHAPE);
|
|
const Use& target = call_site->param_in(); //target of call stmt is methodenv
|
|
const Type *target_type = type(target); //type is method_env
|
|
assert(isEnv(target_type)); //better be a methodenv!
|
|
MethodInfo* method_info = getMethod(target_type);
|
|
return method_info;
|
|
}
|
|
|
|
/**
|
|
* Are we able to inline the given call instruction?
|
|
* Meaning do we have the machinery in place to compile
|
|
* the method and does our inlining support this type
|
|
* of call.
|
|
* Note: this checks properties of the callee to see if the inling plumbing
|
|
* can handle it, NOT whether it is correct to inline the callee at a specific callsite!
|
|
*/
|
|
bool ableToInline(Context *cxt, CallStmt2* call) {
|
|
(void)cxt; //release build
|
|
MethodInfo* method_info = targetOfCallSite(call); //getMethod(type(call->param_in()));
|
|
|
|
if (!halfmoon::canCompile(method_info)){
|
|
INLINE_VERBOSE(cxt->out, " not able to inline because not halfmoon.canCompile: " << method_info );
|
|
return false;
|
|
}
|
|
|
|
//TOOD: teach downstream inlining logic to fill in default parameters from methodSig
|
|
if (method_info->hasOptional()){
|
|
if (method_info->getMethodSignature()->param_count() != call->arg_count()-2) {
|
|
INLINE_VERBOSE(cxt->out, " not able to inline because non-specified optional arguments: " << method_info );
|
|
return false;
|
|
}
|
|
}
|
|
if (!method_info->declaringScope()){
|
|
INLINE_VERBOSE(cxt->out, " not able to inline because not declaring scope: " << method_info );
|
|
return false;
|
|
}
|
|
if (method_info->needRestOrArguments()){
|
|
INLINE_VERBOSE(cxt->out, " not able to inline because needRestOrArguments: " << method_info );
|
|
return false;
|
|
}
|
|
if (dbgInlineSkip(method_info)) {
|
|
INLINE_VERBOSE(cxt->out, " skip inline because listed in DEBUG env var: " << method_info );
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* Is it semantically correct to statically inline the callee?
|
|
* (statically means without a guard)
|
|
* If the function itself is final, then can inline.
|
|
* If the class defining the function is final, then can inline.
|
|
*
|
|
* @param cxt compilation context -- info like what method we're currently compiling.
|
|
* @param call_site callsite to be considered
|
|
* @return true if call at callsite can be statically inlined
|
|
*/
|
|
static bool isCorrectToStaticallyInline(Context *cxt, CallStmt2* call_site) {
|
|
(void)cxt; //release build
|
|
|
|
switch(kind(call_site)) {
|
|
case HR_callmethod:
|
|
break;
|
|
case HR_callstatic:
|
|
return false; // these are always true?
|
|
case HR_callinterface:
|
|
return false; //I guess these will these always be speculative(?)
|
|
default:
|
|
AvmAssert(false); //not a callsite
|
|
}
|
|
MethodInfo* method_info = targetOfCallSite(call_site);
|
|
Traits *declaring_traits = method_info->declaringTraits(); //class method is in.
|
|
if (declaring_traits->final) {
|
|
INLINE_VERBOSE(cxt->out, "correctToInline returns true for: " << method_info << " because class is final" );
|
|
return true;
|
|
}
|
|
//if method is declared final then even if receiver
|
|
//is of a derived class the method cannot have been overriden,
|
|
//so it's safe to inline it
|
|
//
|
|
if (method_info->isFinal()) {
|
|
INLINE_VERBOSE(cxt->out, "correctToInline returns true for: " << method_info << " because method is final" );
|
|
return true;
|
|
}
|
|
INLINE_VERBOSE(cxt->out, "correctToInline returns true false: " << method_info);
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
* @param cxt Context of compilation.. now including "calling context", which,
|
|
* inthe case of inlining, points to the Context of the caller of the
|
|
* method currently being scanned for inlinable callsites. The list of
|
|
* Contest structs are sort of like a stack traceback, except for nested inlines.
|
|
* (Probably a better way to think of them is as a path through the call graph
|
|
* of an inline nest. If the callee appears on the path it's recursive)
|
|
* @param call_site points to a call instruction being considered as an
|
|
* inlining candidate.
|
|
* @return true if the callsite represents a recursion, either directly
|
|
* (eg. factorial method calls itself) or indirectly, where the callsite
|
|
* calls a method that.
|
|
*/
|
|
bool isRecursiveCallsite(Context *cxt, CallStmt2* call_site) {
|
|
MethodInfo* method_info = targetOfCallSite(call_site);
|
|
|
|
//if method is calling itself it's obviously recursive
|
|
if (method_info == cxt->method) {
|
|
INLINE_VERBOSE(cxt->out, "isRecursiveCallsite returns true for: " << method_info << " because callsite is recursive" );
|
|
return true;
|
|
}
|
|
//if method is about to call a method somewhere on its call chain, it's recursive
|
|
for (const Context *aContext = cxt->caller; aContext != NULL; aContext = aContext->caller) {
|
|
if (method_info == aContext->method) {
|
|
INLINE_VERBOSE(cxt->out, "isRecursiveCallsite returns true for: " << method_info << " because callsite is recursive" );
|
|
return true; //gotcha. method
|
|
}
|
|
}
|
|
return false; //got to end of chain without finding self, so not recursive
|
|
}
|
|
|
|
|
|
/**
|
|
* Fire up the JIT and compile a method.
|
|
* motivating use case was for inlining.
|
|
*
|
|
* @param cxt
|
|
* @param m method to compile
|
|
* @return InstrGraph of the freshly compiled method, or null if compilation
|
|
* could not be done.
|
|
*/
|
|
InstrGraph* compileCallee(Context *cxt, CallStmt2* call_site) {
|
|
assert(ableToInline(cxt, call_site));
|
|
MethodInfo* method_info = targetOfCallSite(call_site);
|
|
InstrGraph* ir = JitManager::init(method_info->pool())->ir(method_info);
|
|
|
|
if (ir){
|
|
INLINE_VERBOSE(cxt->out, "found IR (compiled previously) for: " << method_info->getMethodName());
|
|
return ir;
|
|
}
|
|
// Code copied from way up stack: BaseExecMgr::verifyJ2()
|
|
MethodSignaturep method_signature = method_info->getMethodSignature();
|
|
assert(halfmoon::canCompile(method_info));
|
|
|
|
//create a new verifier pipeline that will compile the callee
|
|
//JitWriter instance will carry Context instance from
|
|
//this compilation to "child" compilation, where it will
|
|
//appear as the "calling context". In this way the nested compilations
|
|
//will see a chain of Context instances, sort of like a stacktrace,
|
|
//as methods are inlined
|
|
//
|
|
JitWriter jit(method_info, cxt);
|
|
CodeWriter* volatile vcoder = &jit; // Volatile for setjmp safety.
|
|
|
|
Verifier verifier(method_info, method_signature, cxt->toplevel, cxt->abc_env);
|
|
AvmCore *core = method_info->pool()->core;
|
|
TRY(core, kCatchAction_Rethrow) {
|
|
INLINE_VERBOSE(cxt->out, "inliner requests compilation of: " << method_info->getMethodName());
|
|
verifier.verify(vcoder);
|
|
} CATCH (Exception *exception) {
|
|
verifier.~Verifier(); // Clean up verifier.
|
|
vcoder->cleanup(); // Cleans up all coders.
|
|
// todo: fixme: this doesn't come close to cleaning up all the in-flight HM state.
|
|
core->throwException(exception);
|
|
}
|
|
END_CATCH
|
|
END_TRY
|
|
InstrGraph *callee_ir = jit.ir();
|
|
return callee_ir;
|
|
}
|
|
|
|
|
|
/**
|
|
* An InlineWorklistItem saves the details we will need to inline callee_ir
|
|
* into callsite. Original thought was to link these into a call graph of
|
|
* inlined methods
|
|
*/
|
|
struct InlineWorklistItem {
|
|
explicit InlineWorklistItem(CallStmt2* call)
|
|
: call_site(call)
|
|
, callee_ir(0)
|
|
{ }
|
|
CallStmt2* call_site; //the callsite
|
|
InstrGraph* callee_ir; //cache compile IR for the method
|
|
};
|
|
|
|
|
|
/// TODO comment
|
|
///
|
|
void propagateTypesInliner(InstrGraph* callee_ir, CallStmt2 *call_site) {
|
|
assert(kind(callee_ir->begin) == HR_start);
|
|
StartInstr* start_instr = cast<StartInstr>(callee_ir->begin);
|
|
|
|
// Watch out: same impedance mismatch as in connectInputs
|
|
int param_count = start_instr->paramc;
|
|
// the two extras are effect and methodEnv
|
|
assert(call_site->arg_count() == param_count - 2);
|
|
|
|
Allocator scratch;
|
|
const Type** types = new (scratch) const Type*[param_count];
|
|
types[0] = type(call_site->effect_in()); //type(item.effect_in);
|
|
types[1] = type(call_site->param_in()); //type(item.env_in);
|
|
for (int i = 0, n = call_site->arg_count(); i < n; ++i){
|
|
types[i+2] = type(call_site->arg(i));
|
|
}
|
|
propagateTypes(callee_ir, types);
|
|
}
|
|
|
|
/**
|
|
* @param callee_start_instr first instruction in a function. Defines
|
|
* formal parameters
|
|
* @param call_site specific callsite to be replaced with inline. Uses actuals.
|
|
* connectInputs hooks the defs of the actuals to the uses of the formal parameters of the callee.
|
|
*
|
|
* this is almost identical to what we do to expand a statment template EXCEPT that the callmethod
|
|
* statement takes extra arguments (like the one for the methodname)
|
|
*
|
|
* BEFORE: defs of arguments used by call statement
|
|
*
|
|
* D2:----+ //D2, D1 reconnected to parms of start..
|
|
* D1: + |
|
|
* | | +----------------+
|
|
* | | | start (parms) |
|
|
* v v | |
|
|
* callsite(args) | |
|
|
* 1 <+ |
|
|
* 2 <---+
|
|
*
|
|
* AFTER: definers of arguments connected to users of formal parameters.
|
|
*
|
|
* D2:----+ //D2, D1 reconnected to uses of formal parameters
|
|
* D1: + |
|
|
* | |
|
|
* | |
|
|
* | +-----------------------+
|
|
* +-----------------------+ |
|
|
* callsite | |
|
|
* 1 <+ |
|
|
* 2 <---+
|
|
*
|
|
*/
|
|
void connectInputs(StartInstr* callee_start_instr, CallStmt2 *call_site) {
|
|
// CallStmt2s have an extra use (the disp_id), so the call site arguments are
|
|
// not congruent with start's defs:
|
|
//
|
|
// callmethod: (effect, object, [N extra args])
|
|
// start: (effect, object, [N extra params])
|
|
// TODO handle rest
|
|
assert(call_site->arg_count() == callee_start_instr->paramc - 2);
|
|
|
|
// start instruction doesn't count effect amongst its "data parameters"
|
|
// ie. data_param(0) is not the effect
|
|
// whereas for method calls use(0) is the effect..
|
|
|
|
int num_data_actual_args = call_site->info->num_uses -1;
|
|
int extra_formal_parameters = callee_start_instr->data_param_count() - num_data_actual_args;
|
|
AvmAssert(extra_formal_parameters == 0); //TODO: make sure true for all callsites.
|
|
|
|
// Special case the effect Def of the start instruction
|
|
copyUses(callee_start_instr->effect_out(), def(call_site->effect_in()));
|
|
|
|
// For each formal parameter of the callee function (once EFFECT has been
|
|
// special cased), connect each use (in the callee function
|
|
// body) to the def of the corresponding argument on the callsite.
|
|
//
|
|
for (int i = 0; i < num_data_actual_args; ++i){
|
|
//TODO: add data_param() to callstmt family? +1 depends on innards callstm
|
|
const Use& an_arg = call_site->use(i+1);
|
|
Def * def_of_actual_arg = def(an_arg);
|
|
Def *corresponding_formal_parm = callee_start_instr->data_param(i+extra_formal_parameters);
|
|
//TODO: how do i distinguish between "FinalDefaultClass[O]~" and FinalDefaultClass[A]~"
|
|
assert(type(def_of_actual_arg)->isSubtypeOf(*type(corresponding_formal_parm)));
|
|
if (!corresponding_formal_parm->isUsed()) {
|
|
continue; //formal has no uses nothing to do
|
|
}
|
|
copyUses(corresponding_formal_parm,def_of_actual_arg);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Clone the graph, which means copy all the instructions into the "outer"
|
|
* graph into which we are inlining. No use/def connections
|
|
* are made (so at this point callee code is there, but dead).
|
|
*
|
|
* @param callee_ir instruction graph to be inlined
|
|
* @param outer_ir instruction graph containing destination of inlining.
|
|
* @param p_start OUT parameter. ignored on input, set to point to start
|
|
* instruction in outer ir.
|
|
* @param p_ret OUT paramter. ignored on input, set to point to ret instruction
|
|
* in outer ir.
|
|
*/
|
|
void cloneCallee(InstrGraph* callee_ir, InstrGraph* outer_ir,
|
|
StartInstr** p_start, StopInstr** p_return,
|
|
StopInstr** p_throw) {
|
|
Copier copier(callee_ir, outer_ir);
|
|
*p_start = copier.copy(cast<StartInstr>(callee_ir->begin));
|
|
*p_return = copier.copy(callee_ir->returnStmt());
|
|
*p_throw = copier.copy(callee_ir->throwStmt());
|
|
}
|
|
|
|
//
|
|
// Inline policy
|
|
//
|
|
// The contract that an inliner must follow is to call shouldAttemptInline()
|
|
// passing the containing method (m), the candidate for inlining and the
|
|
// number of potential candidates that might be inlined in the containing method.
|
|
//
|
|
// If this method returns false, it is expected that attempts to inline the
|
|
// candidate, should be abandoned.
|
|
//
|
|
// If true is returned, shouldInline() is to be called (once the IR is
|
|
// available) for final validation that the candidate is to be inlined.
|
|
//
|
|
//
|
|
bool shouldAttemptInline(Context* cxt,
|
|
MethodInfo* /*candidate*/,
|
|
uint32_t potentialInlineCount) {
|
|
// if we are nesting too deep then blanket disable
|
|
if (cxt->inline_depth > 2)
|
|
return false;
|
|
// if the # of inlines for the method is high then lets blanket disable
|
|
if (potentialInlineCount > 2000)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* <Pre>
|
|
* there are several steps to inlining a method.
|
|
* aa) find the methodInfo corresponding to the callee.
|
|
* a) recompile the callee. (todo: build cache of previously compiled callees)
|
|
* 0) clone the callee IR and locate the beginning and end of the callee function.
|
|
* 0a if the callee has no flow (ie. its body is only one bb) replaceStmt does the plumbing.
|
|
* o/w locate the bb at the head of the callee (entry_list) and the bb at the tail (ret_list).
|
|
* 1) split the calling basic block at the callsite
|
|
* 2) graft entry_list into the "outer" ir before the callsite
|
|
* 3) graft ret_list into the "outer" ir after the callsite
|
|
* 4) reconnect the uses and defs such that
|
|
* 4a) defs formerly feeding the callsite now feed uses in the callee.
|
|
* 4b) uses in the caller formerly fed by the return result of the
|
|
* callsite are now fed by defs in the callee that formerly fed the
|
|
* return statement of the callee.
|
|
*
|
|
* CALLER CALLEE
|
|
* + prev_ +-----------+ <-----+
|
|
* | |entry_list | |
|
|
* +-prev_ +------------+ <---+ | | ... | |
|
|
* | | | | | | | |
|
|
* | | | | +-----> +-----------+ next_ +
|
|
* | | P | |
|
|
* | +- callsite -+ | <<<arbitrary flow within callee >>>
|
|
* | | N | |
|
|
* | | | | +-prev_- +-------- -+ <------+
|
|
* | | | | | | ret_list | |
|
|
* +----> +------------+next_+ | | ... | |
|
|
* | | | |
|
|
* +-----> +-----------+ next_ -+
|
|
* outputs: instruction lists have been sucked into the block and the instruction at pos has been deleted:
|
|
*
|
|
* +-prev_ +------------+ <---+
|
|
* | | | |
|
|
* | | P | |
|
|
* | +------------+ |
|
|
* | | entry_list | |
|
|
* | | ... | |
|
|
* | | |.....|......branch at end of entry_list has succs in cloned callee
|
|
* +---> +------------+next_+
|
|
*
|
|
* cloned CFG branched to by entry_list
|
|
* and branches to ret_list..
|
|
*
|
|
* +prev_ +------------+ <---+
|
|
* | | ret_list | .... |...... block at head of ret_list has preds in cloned callee
|
|
* | | ... | |
|
|
* | +------------+ |
|
|
* | | N | |
|
|
* | | | |
|
|
* +----> +------------+ next_+
|
|
*
|
|
* Connecting inputs to outputs
|
|
*
|
|
* Dc stmt (effect=) // stmt defining effect value in (dD1) in of callsite
|
|
* ...
|
|
* D2 op (val=) // instr defining data value in (dD2) of callsite
|
|
* ...
|
|
*
|
|
* C callmethod (dDeffect dD2val) -- (effect=+ val=*) -> iU1,iU2
|
|
* ...
|
|
* U1 stmt (dCeffect) //stmt consuming effect_out of callsite
|
|
* ...
|
|
* U2 instr (dCval) //instruction consuming data value out of callsite
|
|
*
|
|
* 1) we must connect the defs of D1 and D2 to uses in the head op(in place of the callsite)
|
|
* 2) we must connect defs feeding the return statement in the cloned callee graph with uses at U1, U2
|
|
*
|
|
* D2:----+ //D2, D1 reconnected to parms of start.. (See reconnectInputs)
|
|
* D1: + |
|
|
* | | +----------------+
|
|
* | | | start (parm1) |
|
|
* v v
|
|
* callsite <<<arbitrary flow within callee >>>
|
|
* | | +----------------+
|
|
* | | | ret_list |
|
|
* | | | ... |
|
|
* | | A: ------+ // def reconnected to uses at U1 (See reconnectOutputs)
|
|
* U1 <+ | C: --+ | // def reconnected to use at U2
|
|
* U2 <---+ v v
|
|
* ret (d)
|
|
*
|
|
* inliner starts with a graph for an entire function.
|
|
* it begins with a "start" instruction, which has an effect def, a return
|
|
* result, and zero or more parms.
|
|
*
|
|
* \param outer_ir is the "main" ir we want to inline into
|
|
* \param callee_ir is the graph we just created by compiling the callee
|
|
* \param callsite_stmt
|
|
*
|
|
* </pre>
|
|
*/
|
|
void inlineCallsite(InstrGraph* outer_ir, InstrFactory* factory, InstrGraph *callee_ir,
|
|
const InlineWorklistItem& item) {
|
|
Instr* callsite = item.call_site;
|
|
|
|
assert(kind(callee_ir->begin) == HR_start);
|
|
assert(checkOneEnd(callee_ir, callee_ir->end, HR_return));
|
|
assert(checkOneEnd(outer_ir, outer_ir->exit, HR_throw));
|
|
|
|
dbgDumpIr(callsite, outer_ir, callee_ir);
|
|
|
|
|
|
|
|
// 0. evaluate the types on the inner_ir according to the input types
|
|
// at the call site (outer_stmt).
|
|
// what does this mean for inlining? freshly compiled graph needs types
|
|
// propagated to it. (That's how we push the context of the caller down into
|
|
// the callee)
|
|
propagateTypesInliner(callee_ir, item.call_site);
|
|
printTerseInstrList(callee_ir->begin, outer_ir, "begin block of callee");
|
|
|
|
// 1. Copy the IR of the callee into outer_ir. find the start_instr and
|
|
// return instruction. After this, all the instructions we touch are
|
|
// in outer_ir.
|
|
Copier copier(callee_ir, outer_ir);
|
|
|
|
//copy the callees start,return,throw instructions.. just floating between graphs (?)
|
|
StartInstr* start_instr = copier.copy(cast<StartInstr>(callee_ir->begin));
|
|
StopInstr* return_instr = copier.copy(callee_ir->returnStmt());
|
|
StopInstr* throw_instr = copier.copy(callee_ir->throwStmt());
|
|
|
|
// 2. Connect uses of inner_ir's formal parameters (defs in its start_instr)
|
|
// to the defs that are inputs to callsite, then unlink start_instr.
|
|
connectInputs(start_instr, item.call_site);
|
|
InstrRange start_block(start_instr);
|
|
|
|
// 3. Connect uses of return results to defs of values returned, then
|
|
// unlink return_instr.
|
|
InstrRange return_block = outer_ir->blockRange(return_instr);
|
|
printTerseInstrList(return_block, outer_ir,
|
|
"outer ret_block: block cloned from return block of callee ir");
|
|
connectOuterUsesToInnerUses(return_instr, callsite);
|
|
|
|
// 4. do something with inner throw
|
|
outer_ir->exit = joinStops(outer_ir, factory, cast<StopInstr>(outer_ir->exit),
|
|
throw_instr);
|
|
|
|
// 4. Replace call instruction with body of callee.
|
|
outer_ir->inlineBlocks(callsite, start_block, return_block);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param cxt JIT compilation context
|
|
* @param ir HMIR of a method to scan for callsites, and inline those that match policy
|
|
* @return false if nothing inlined
|
|
*/
|
|
bool inlineGraph(Context *cxt, InstrGraph* ir, const char *title) {
|
|
(void)title;
|
|
assert(checkPruned(ir) && checkSSA(ir));
|
|
|
|
String *caller_name = cxt->method->getMethodName();
|
|
|
|
inline_enable_verbose = INLINE_ALWAYS_VERBOSE || (enable_verbose!=0);
|
|
inline_enable_verbose |= debugIsInSkiplist("DEBUG_INLINE_VERBOSITY_CALLER", caller_name );
|
|
|
|
if (!dbgInlineOnly(cxt->method))
|
|
return false; // Do not consider methods other than those listed.
|
|
|
|
// Make an inline worklist. Can't just scan and inline as inlining munches
|
|
// the control flow graph and the iterators get confused. Scan and mark
|
|
// for now, hold off on scanning the inlined code again -- at least only
|
|
// when we know there is no recursion.
|
|
Allocator scratch;
|
|
SeqBuilder<InlineWorklistItem> work(scratch);
|
|
|
|
uint32_t inlineCount = 0;
|
|
for (EachBlock b(ir); !b.empty(); b.popFront()) {
|
|
for (InstrRange i(b.front()); !i.empty(); i.popFront()) {
|
|
Instr* instr = i.front();
|
|
switch (kind(instr)) {
|
|
case HR_callmethod: {
|
|
CallStmt2 *call = cast<CallStmt2>(instr);
|
|
if (isRecursiveCallsite(cxt,call)) {
|
|
break;
|
|
}
|
|
if (ableToInline(cxt,call) && isCorrectToStaticallyInline(cxt, call)) {
|
|
inlineCount++;
|
|
work.add(InlineWorklistItem(call));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
dbgPrintInlineWorklist(cxt, work);
|
|
|
|
bool did_ir_change = false;
|
|
|
|
//inline the call sites on work list if mgr policy says so.
|
|
//compiling a callee may recurse (a very long way around) into inliner.
|
|
InstrFactory factory(ir);
|
|
for (SeqRange<InlineWorklistItem> iter(work); !iter.empty(); iter.popFront()) {
|
|
InlineWorklistItem& item = iter.front();
|
|
CallStmt2 *call = cast<CallStmt2>(item.call_site);
|
|
MethodInfo* target = targetOfCallSite(call);
|
|
if (!item.callee_ir && shouldAttemptInline(cxt, target, inlineCount)) {
|
|
item.callee_ir = compileCallee(cxt, call); //NB. potentially recursive
|
|
//StringBuffer buf(cxt->toplevel->core());
|
|
//buf << "inline: " << target->getMethodName();
|
|
//printf("%s\n", buf.c_str()); fflush(NULL);
|
|
dbgInlineCallsite(call,cxt, item.callee_ir);
|
|
inlineCallsite(ir, &factory, item.callee_ir, item); //REAL WORK
|
|
did_ir_change = true;
|
|
}
|
|
}
|
|
|
|
dbgDumpIr(ir, "after-inlining", cxt );
|
|
|
|
pruneGraph(ir);
|
|
assert(checkPruned(ir));
|
|
|
|
dbgDumpIr(ir, "after-pruning", cxt );
|
|
|
|
assert(checkSSA(ir));
|
|
return did_ir_change;
|
|
}
|
|
|
|
//#ifdef AVMPLUS_VERBOSE #ifdef AVMPLUS_VERBOSE #ifdef AVMPLUS_VERBOSE #ifdef A
|
|
#ifdef AVMPLUS_VERBOSE
|
|
|
|
inline static void dbgDumpIr(InstrGraph *ir, const char *banner, Context *cxt) {
|
|
if (!inline_enable_verbose)
|
|
return;
|
|
cxt->out << "ir for " << cxt->method->getMethodName() << banner << "\n";
|
|
listCfg(cxt->out, ir);
|
|
dbgPrintGraph(cxt,ir, banner);
|
|
}
|
|
|
|
/**
|
|
* for detective work..
|
|
* env DEBUG_INLINE_CANDIDATE set means inline only those candidates.
|
|
* (Useful for narrowing down problems)
|
|
* env method name listed in DEBUG_INLINE_SKIP indicates that we should never inline it.
|
|
* (Useful for working a specific problem with a specific callee)
|
|
*/
|
|
inline static bool dbgInlineSkip(MethodInfo *method_info) {
|
|
//when DEBUG_INLINE_CANDIDATE env is set, allow ONLY inlines of methods listed.
|
|
const char *env = VMPI_getenv("DEBUG_INLINE_CANDIDATE");
|
|
if (env != NULL && *env != 0) {
|
|
return debugIsInSkiplist("DEBUG_INLINE_CANDIDATE", method_info->getMethodName());
|
|
}
|
|
//otherwise, if DEBUG_INLINE_SKIP contains the name, skip it.
|
|
//use like this:
|
|
//env DEBUG_INLINE_SKIP="`cat inline-skip-list.txt`" INLINE=1 avmshell -Dnodebugger xx.abc
|
|
//
|
|
return debugIsInSkiplist("DEBUG_INLINE_SKIP", method_info->getMethodName());
|
|
}
|
|
|
|
/**
|
|
* for detective work.. env DEBUG_INLINE_ONLY indicates
|
|
* that we should only consider callsites in the the named methods.
|
|
* returns true when env vars suggest we should skip examining callsites of the method for inlining.
|
|
*/
|
|
inline static bool dbgInlineOnly(MethodInfo *method_info){
|
|
const char *env = VMPI_getenv("DEBUG_INLINE_ONLY");
|
|
if (!env)
|
|
return true; // if not set, behave normally, inline
|
|
return debugIsInSkiplist("DEBUG_INLINE_ONLY", method_info->getMethodName());
|
|
}
|
|
|
|
/**
|
|
* verbose output to help follow what inliner is doing.
|
|
*/
|
|
inline static void dbgInlineCallsite(CallStmt2 *call, Context *cxt, InstrGraph *callee_ir/*InlineWorklistItem& item*/){
|
|
|
|
if (inline_enable_verbose) {
|
|
MethodInfo* mi = targetOfCallSite(call);
|
|
cxt->out << "Compiled IR to be inlined for " << mi << "\n";
|
|
listCfg(cxt->out, callee_ir);
|
|
}
|
|
if (parseEnv("DEBUG_PRINT_INLINES", 0) != 0) {
|
|
//print the names of methods that we are to inline
|
|
const char *tn = type(call->param_in())->name;
|
|
String *body = cxt->method->getMethodName();
|
|
cxt->out << "inline: " << tn << " into: " << body << "\n";
|
|
for (const Context *aContext = cxt->caller; aContext != NULL; aContext = aContext->caller) {
|
|
cxt->out << " " << aContext->method << "\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
inline static void dbgDumpIr(Instr *call_site, InstrGraph* outer_ir, InstrGraph *callee_ir) {
|
|
PrintWriter &pw = outer_ir->lattice.console();
|
|
if (inline_enable_verbose) {
|
|
pw << "===========\ninline into callsite: ";
|
|
printInstr(pw, call_site);
|
|
pw << "--------outer_ir:\n";
|
|
listCfg(pw, outer_ir);
|
|
pw << "--------callee_ir:\n";
|
|
listCfg(pw, callee_ir);
|
|
pw << "============\n";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* verbose output to dump out the work list
|
|
*/
|
|
inline static void dbgPrintInlineWorklist(Context *cxt, SeqBuilder<InlineWorklistItem>& work)
|
|
{
|
|
//print out the worklist
|
|
if (inline_enable_verbose) {
|
|
if (work.isEmpty()) {
|
|
cxt->out << "worklist empty, no callsites to inline\n";
|
|
}
|
|
cxt->out << "inliner worklist{";
|
|
for (SeqRange<InlineWorklistItem> iter(work); !iter.empty(); iter.popFront()) {
|
|
cxt->out << targetOfCallSite(iter.front().call_site)->getMethodName() << ", "; //"\n";
|
|
}
|
|
cxt->out << "}end inliner worklist\n";
|
|
|
|
|
|
}
|
|
}
|
|
|
|
/*
|
|
* print out one of our lovely gml files to show what we did to the IR
|
|
*/
|
|
inline static void dbgPrintGraph(Context *cxt, InstrGraph *ir, const char *title){
|
|
char file_name[512];
|
|
static int fnum = 0;
|
|
VMPI_snprintf(file_name, 512,"%s-%d", title, fnum++); //groan, no snprintf on windows
|
|
cxt->out << file_name << " for gml ir view of " << cxt->method << " " << title << "\n";
|
|
printGraph(cxt, ir, file_name);
|
|
}
|
|
|
|
#endif/*AVMPLUS_VERBOSE*/
|
|
|
|
}//namespace
|
|
#endif // VMCFG_HALFMOON
|