Files
avmplus/core/InvokerCompiler.cpp
T
dmcallis 65a0592776 Initial source code drop
Source code drop for the Actionscript virtual machine in the Adobe
Flash Player for December 2013
2013-12-02 16:04:04 -08:00

454 lines
19 KiB
C++

/* -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 4 -*- */
/* vi: set ts=4 sw=4 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 "avmplus.h"
#ifdef VMCFG_NANOJIT
#include "CodegenLIR.h"
//
// JIT compiler for invoker stubs
//
namespace avmplus
{
bool InvokerCompiler::canCompileInvoker(MethodInfo* method)
{
MethodSignaturep ms = method->getMethodSignature();
int32_t rest_offset = ms->rest_offset();
int32_t param_count = ms->param_count();
if (rest_offset > int32_t((param_count+1) * sizeof(Atom)) &&
method->needRestOrArguments()) {
// situation: natively represented args need more space than provided,
// and this method uses varargs (rest args or arguments array); this means
// the declared args must grow, which requires allocating an unknown amount
// of space since we don't know (at compile time) how many extra varags are present.
//
// punt for now. to better handle this in the future, the cases are:
//
// jit function with rest args:
// the prolog will create an array with these extra args, so all
// we should need is a way to pass in the pointer to them. (tweak ABI).
//
// jit function that needs arguments:
// the prolog will create an array with all the args re-boxed as Atom,
// plus the extra ones. (the unboxed copies are still available in the callee).
//
// native function with rest args:
// the native function abi passes rest args via (argc,Atom*) parameters
// which we could adapt to here without shifting or copying, if we could
// bypass the normal Gpr/FprMethodProc ABI.
//
// native function with arguments:
// doesn't happen. native functions only support rest args.
//
// Given the current JIT and native ABI, we can't support shifting and
// copying the extra unknown number of args. With changes to the native
// ABI, we could pass a reference to the extra args without any copying.
return false;
}
return true;
}
// compiler driver
AtomMethodProc InvokerCompiler::compile(MethodInfo* method)
{
InvokerCompiler compiler(method);
compiler.generate_lir();
return (AtomMethodProc) compiler.assemble();
}
InvokerCompiler::InvokerCompiler(MethodInfo* method)
: LirHelper(method->pool())
, method(method)
, ms(method->getMethodSignature())
, maxargs_br(NULL)
, minargs_br(NULL)
{
this->method = method;
this->ms = method->getMethodSignature();
initCodeMgr(method->pool());
frag = new (*lir_alloc) Fragment(0 verbose_only(, 0));
LirBuffer* lirbuf = frag->lirbuf = new (*lir_alloc) LirBuffer(*lir_alloc);
lirbuf->abi = ABI_CDECL;
LirWriter* lirout = new (*alloc1) LirBufWriter(lirbuf, core->config.njconfig);
verbose_only(
if (verbose()) {
lirbuf->printer = new (*lir_alloc) LInsPrinter(*lir_alloc, TR_NUM_USED_ACCS);
lirbuf->printer->addrNameMap->addAddrRange(pool->core, sizeof(AvmCore), 0, "core");
}
)
debug_only(
lirout = validate2 = new (*alloc1) ValidateWriter(lirout, lirbuf->printer, "InvokerCompiler");
)
verbose_only(
if (verbose()) {
CodeMgr *codeMgr = method->pool()->codeMgr;
core->console << "compileInvoker " << method << "\n";
core->console <<
" required=" << ms->requiredParamCount() <<
" optional=" << (ms->param_count() - ms->requiredParamCount()) << "\n";
lirout = new (*alloc1) VerboseWriter(*alloc1, lirout, lirbuf->printer, &codeMgr->log);
}
)
#if defined(NANOJIT_ARM)
if (core->config.njconfig.soft_float)
{
lirout = new (*alloc1) SoftFloatFilter(lirout);
}
#endif
// add other LirWriters here
this->lirout = lirout;
emitStart(*alloc1, lirbuf, lirout);
}
// recipe for an invoke wrapper:
// Atom <generated invoker>(MethodEnv* env, int argc, Atom* argv) {
// 1. check argc: env->startCoerce(argc, env->get_ms());
// 2. unbox args: unboxCoerceArgs, unrolled for each arg
// 3. return box(call(...))
// }
// jit-compile an invoker for mi
void InvokerCompiler::generate_lir()
{
// invoker params
LIns* env_param = param(0, "env");
LIns* argc_param = p2i(param(1, "argc"));
LIns* args_param = param(2, "args");
coreAddr = InsConstPtr(core);
// if unboxing args will make them expand, allocate more space.
int32_t rest_offset = ms->rest_offset();
if (rest_offset > int32_t((ms->param_count()+1)*sizeof(Atom))) {
AvmAssert(!method->needRestOrArguments());
args_out = lirout->insAlloc(rest_offset);
} else {
// we can do in-place unboxing of args.
args_out = args_param;
}
// 1. check argc
emit_argc_check(argc_param);
// 2. unbox & coerce args
downcast_args(env_param, argc_param, args_param);
// 3. call, box result, return atom
call_method(env_param, argc_param);
// error handler for argc error
if (minargs_br || maxargs_br) {
LIns* errlabel = label();
if (minargs_br) minargs_br->setTarget(errlabel);
if (maxargs_br) maxargs_br->setTarget(errlabel);
callIns(FUNCTIONID(argcError), 2, env_param, argc_param);
}
// mark the endpoint of generated LIR with an instruction the Assembler allows at the end
frag->lastIns = livep(env_param);
// we're done with LIR generation, free up what we can.
mmfx_delete(alloc1);
alloc1 = NULL;
}
void InvokerCompiler::emit_argc_check(LIns* argc_param)
{
int min_argc = ms->requiredParamCount();
int param_count = ms->param_count();
if (min_argc == param_count && !ms->argcOk(param_count + 1)) {
// exactly param_count args required
// if (argc != param_count) goto error
maxargs_br = jnei(argc_param, param_count);
} else {
if (!ms->argcOk(param_count+1)) {
// extra params are not allowed, must check for max args
// if (argc > param_count) goto error
maxargs_br = jgti(argc_param, param_count);
}
if (min_argc > 0) {
// at least 1 param is required, so check
// if (argc < min_argc) goto error
minargs_br = jlti(argc_param, min_argc);
}
}
}
void InvokerCompiler::downcast_arg(int i, int offset, LIns* env_param, LIns* args_param)
{
BuiltinType bt = ms->paramTraitsBT(i);
if (bt != BUILTIN_any) {
LIns* atom = ldp(args_param, i*sizeof(Atom), ACCSET_OTHER);
downcast_and_store(atom, ms->paramTraits(i), env_param, offset);
} else if (copyArgs()) {
LIns* atom = ldp(args_param, i*sizeof(Atom), ACCSET_OTHER);
lirout->insStore(atom, args_out, offset, ACCSET_OTHER);
}
}
void InvokerCompiler::downcast_args(LIns* env_param, LIns* argc_param, LIns* args_param)
{
// the receiver arg (arg0) only needs to be unboxed, not coerced
verbose_only( if (verbose())
core->console << "unbox arg 0 " << ms->paramTraits(0) << "\n";
)
LIns* atom = ldp(args_param, 0, ACCSET_OTHER);
LIns* native = atomToNative(ms->paramTraitsBT(0), atom);
if (native != atom || copyArgs())
lirout->insStore(native, args_out, 0, ACCSET_OTHER);
int offset = argSize(ms, 0);
// the required args need to be coerced and unboxed
int i;
int required_count = ms->requiredParamCount();
for (i = 1; i <= required_count; i++) {
verbose_only( if (verbose())
core->console << "arg " << i << " " << ms->paramTraits(i) << "\n";
)
downcast_arg(i, offset, env_param, args_param);
offset += argSize(ms, i);
}
// optional args also need coercing and unboxing when they're present
int param_count = ms->param_count();
if (required_count < param_count) {
int optional_count = param_count - required_count;
int branch_count = 0;
LIns** branches = new (*alloc1) LIns*[optional_count];
for (; i <= param_count; i++) {
verbose_only( if (verbose())
core->console << "optional arg " << i << " " << ms->paramTraits(i) << "\n";
)
// if (argc < i) { goto done }
branches[branch_count++] = jlti(argc_param, i);
downcast_arg(i, offset, env_param, args_param);
offset += argSize(ms, i);
}
if (branch_count > 0) {
// done: patch all the optional-arg branches
LIns* done_label = label();
for (i = 0; i < branch_count; i++)
branches[i]->setTarget(done_label);
}
}
}
void* InvokerCompiler::assemble()
{
CodeMgr* codeMgr = method->pool()->codeMgr;
verbose_only(if (pool->isVerbose(LC_Liveness, method)) {
Allocator live_alloc;
LirReader in(frag->lastIns);
nanojit::live(&in, live_alloc, frag, &codeMgr->log);
})
// disable hardening features when compiling thunks
nanojit::Config cfg = core->config.njconfig;
cfg.harden_function_alignment = false;
cfg.harden_nop_insertion = false;
cfg.harden_blind_constants = false;
// Use the 'active' log if we are in verbose output mode otherwise sink the output
LogControl* log = &(codeMgr->log);
verbose_only(
SinkLogControl sink;
log = pool->isVerbose(VB_jit,method) ? log : &sink;
)
Assembler *assm = new (*lir_alloc) Assembler(codeMgr->codeAlloc, codeMgr->allocator, *lir_alloc, log, cfg);
verbose_only( StringList asmOutput(*lir_alloc); )
verbose_only( if (!pool->isVerbose(VB_raw, method)) assm->_outputCache = &asmOutput; )
LirReader bufreader(frag->lastIns);
assm->beginAssembly(frag);
assm->assemble(frag, &bufreader);
assm->endAssembly(frag);
verbose_only(
assm->_outputCache = 0;
for (Seq<char*>* p = asmOutput.get(); p != NULL; p = p->tail)
assm->outputf("%s", p->head);
);
if (!assm->error()) {
if (jit_observer)
jit_observer->notifyInvokerJITed(method, assm->codeList);
if (method->isNative()) {
PERFM_NVPROF("C++ invoker bytes", CodeAlloc::size(assm->codeList));
} else {
PERFM_NVPROF("JIT invoker bytes", CodeAlloc::size(assm->codeList));
}
return frag->code();
} else {
return NULL;
}
}
void InvokerCompiler::call_method(LIns* env_param, LIns* argc_param)
{
// We know we've called the method at least once, so method->implGPR pointer is correct.
CallInfo* call = (CallInfo*) lir_alloc->alloc(sizeof(CallInfo));
call->_isPure = 0;
call->_storeAccSet = ACCSET_STORE_ANY;
call->_abi = ABI_FUNCTION;
verbose_only( if (verbose()) {
StUTF8String name(method->getMethodName());
char *namestr = new (*lir_alloc) char[VMPI_strlen(name.c_str())+1];
VMPI_strcpy(namestr, name.c_str());
call->_name = namestr;
})
switch (ms->returnTraitsBT()) {
case BUILTIN_number:
call->_address = (uintptr_t) method->_implFPR;
call->_typesig = SIG3(D,P,I,P);
break;
case BUILTIN_int: case BUILTIN_uint: case BUILTIN_boolean:
call->_address = (uintptr_t) method->_implGPR;
call->_typesig = SIG3(I,P,I,P);
break;
#ifdef VMCFG_FLOAT
case BUILTIN_float:
call->_address = (uintptr_t) method->_implFPR;
call->_typesig = SIG3(F,P,I,P);
break;
case BUILTIN_float4:
call->_address = (uintptr_t) method->_implVECR;
call->_typesig = SIG3(F4,P,I,P);
break;
#endif // VMCFG_FLOAT
default:
call->_address = (uintptr_t) method->_implGPR;
call->_typesig = SIG3(A,P,I,P);
break;
}
LIns* result = callIns(call, 3, env_param, argc_param, args_out);
livep(args_out);
// box and return the result
retp(nativeToAtom(result, ms->returnTraits()));
}
void InvokerCompiler::downcast_and_store(LIns* atom, Traits* t, LIns* env, int offset)
{
LIns *native;
BuiltinType b = bt(t);
switch (b) {
case BUILTIN_object:
// return (atom == undefinedAtom) ? nullObjectAtom : atom;
native = choose(eqp(atom, undefinedAtom), nullObjectAtom, atom);
break;
case BUILTIN_int:
case BUILTIN_uint:
{
// int tag = atom & kAtomTypeMask;
// if(tag == kIntptrType)
// args[i] == atom >> kAtomTypeSize;
// else
// args[i] = isInt ? integer(atmo) : toUint32(atom);
LIns* tag = andp(atom, AtomConstants::kAtomTypeMask);
LIns* slow_br = lirout->insBranch(LIR_jf, eqp(tag, InsConstAtom(AtomConstants::kIntptrType)), NULL);
LIns* ival = p2i(rshp(atom, AtomConstants::kAtomTypeSize));
native = BUILTIN_int == b ? i2p(ival) : ui2p(ival);
stp(native, args_out, offset, ACCSET_OTHER);
LIns* fast_br = lirout->insBranch(LIR_j, NULL, NULL);
slow_br->setTarget(label());
native = BUILTIN_int == b ? i2p(callIns(FUNCTIONID(integer), 1, atom)) : ui2p(callIns(FUNCTIONID(toUInt32), 1, atom));
stp(native, args_out, offset, ACCSET_OTHER);
fast_br->setTarget(label());
return;
}
case BUILTIN_number:
{
// Based on code from CodegenLIR::coerceToNumber.
// * -> Number
#ifdef VMCFG_FASTPATH_FROMATOM
// double rslt;
// intptr_t val = CONVERT_TO_ATOM(arg);
// if ((val & kAtomTypeMask) != kIntptrType) goto not_intptr; # test for kIntptrType tag
// # kIntptrType
// rslt = double(val >> kAtomTypeSize); # extract integer value and convert to double
// goto done;
// not_intptr:
// if ((val & kAtomTypeMask) != kDoubleType) goto not_double; # test for kDoubleType tag
// # kDoubleType
// rslt = *(val - kDoubleType); # remove tag and dereference
// goto done;
// not_double:
// rslt = number(val); # slow path -- call helper
// done:
// result = rslt;
LIns* tag = andp(atom, AtomConstants::kAtomTypeMask);
// kIntptrType
LIns* not_intptr = lirout->insBranch(LIR_jf, eqp(tag, AtomConstants::kIntptrType), NULL);
// Note that this works on 64bit platforms only if we are careful
// to restrict the range of intptr values to those that fit within
// the integer range of the double type.
std(p2dIns(rshp(atom, AtomConstants::kAtomTypeSize)), args_out, offset, ACCSET_OTHER);
LIns* fast_path_exit1 = lirout->insBranch(LIR_j, NULL, NULL);
not_intptr->setTarget(label());
// kDoubleType
LIns* not_double = lirout->insBranch(LIR_jf, eqp(tag, AtomConstants::kDoubleType), NULL);
std(ldd(subp(atom, AtomConstants::kDoubleType), 0, ACCSET_OTHER), args_out, offset, ACCSET_OTHER);
LIns* fast_path_exit2 = lirout->insBranch(LIR_j, NULL, NULL);
not_double->setTarget(label());
std(callIns(FUNCTIONID(number), 1, atom), args_out, offset, ACCSET_OTHER);
LIns* done = label();
fast_path_exit1->setTarget(done);
fast_path_exit2->setTarget(done);
return;
#else
native = callIns(FUNCTIONID(number), 1, atom);
#endif
}
break;
#ifdef VMCFG_FLOAT
case BUILTIN_float: // TODO: I assume we don't need to inline here since we don't care about untyped performance; check assumption
native = callIns(FUNCTIONID(singlePrecisionFloat), 1, atom);
break;
case BUILTIN_float4:
callIns(FUNCTIONID(float4), 2, lea(offset, args_out), atom);
return; // not break! float4 does the "store", too.
#endif
case BUILTIN_boolean:
{
LIns* tag = andp(atom, AtomConstants::kAtomTypeMask);
LIns* not_bool = lirout->insBranch(LIR_jf, eqp(tag, AtomConstants::kBooleanType), NULL);
stp(rshp(atom, AtomConstants::kAtomTypeSize), args_out, offset, ACCSET_OTHER);
LIns* fast_path_exit = lirout->insBranch(LIR_j, NULL, NULL);
not_bool->setTarget(label());
stp(ui2p(callIns(FUNCTIONID(boolean), 1, atom)), args_out, offset, ACCSET_OTHER);
fast_path_exit->setTarget(label());
return;
}
break;
case BUILTIN_string:
native = callIns(FUNCTIONID(coerce_s), 2, InsConstPtr(t->core), atom);
break;
case BUILTIN_namespace:
native = andp(callIns(FUNCTIONID(coerce), 3, env, atom, InsConstPtr(t)), ~AtomConstants::kAtomTypeMask);
break;
case BUILTIN_void:
case BUILTIN_null:
case BUILTIN_any:
AvmAssert(false);
default:
native = downcast_obj(atom, env, t);
break;
}
lirout->insStore(native, args_out, offset, ACCSET_OTHER);
}
#ifdef NJ_VERBOSE
bool InvokerCompiler::verbose()
{
return method->pool()->isVerbose(VB_jit, method);
}
#endif
}
#endif // VMCFG_NANOJIT