Source code drop for the Actionscript virtual machine in the Adobe Flash Player for December 2013
569 lines
17 KiB
C++
569 lines
17 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 "LirHelper.h"
|
|
|
|
#ifdef VMCFG_SHARK
|
|
#include <dlfcn.h> // for dlopen and dlsym
|
|
#endif
|
|
|
|
namespace avmplus
|
|
{
|
|
|
|
bool neverReturns(const CallInfo* call)
|
|
{
|
|
return call == FUNCTIONID(throwAtom) ||
|
|
call == FUNCTIONID(npe) ||
|
|
call == FUNCTIONID(upe) ||
|
|
call == FUNCTIONID(mop_rangeCheckFailed);
|
|
}
|
|
|
|
CodeMgr* initCodeMgr(PoolObject *pool)
|
|
{
|
|
if (!pool->codeMgr) {
|
|
CodeMgr *mgr = mmfx_new( CodeMgr(&(pool->core->config.njconfig)) );
|
|
pool->codeMgr = mgr;
|
|
#ifdef NJ_VERBOSE
|
|
mgr->log.core = pool->core;
|
|
mgr->log.lcbits = pool->verbose_vb;
|
|
#endif
|
|
}
|
|
return pool->codeMgr;
|
|
}
|
|
|
|
LirHelper::LirHelper(PoolObject* pool) :
|
|
pool(pool),
|
|
core(pool->core),
|
|
alloc1(mmfx_new(Allocator())),
|
|
lir_alloc(mmfx_new(Allocator())),
|
|
jit_observer(((BaseExecMgr*)core->exec)->jit_observer),
|
|
#ifdef NANOJIT_IA32
|
|
use_cmov(pool->core->config.njconfig.i386_use_cmov)
|
|
#else
|
|
use_cmov(true)
|
|
#endif
|
|
{ }
|
|
|
|
LirHelper::~LirHelper()
|
|
{
|
|
cleanup();
|
|
}
|
|
|
|
void LirHelper::cleanup()
|
|
{
|
|
mmfx_delete( alloc1 );
|
|
alloc1 = NULL;
|
|
mmfx_delete( lir_alloc );
|
|
lir_alloc = NULL;
|
|
}
|
|
|
|
// call
|
|
LIns* LirHelper::callIns(const CallInfo *ci, uint32_t argc, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, argc);
|
|
LIns* ins = vcallIns(ci, argc, ap);
|
|
va_end(ap);
|
|
return ins;
|
|
}
|
|
|
|
LIns* LirHelper::vcallIns(const CallInfo *ci, uint32_t argc, va_list ap)
|
|
{
|
|
AvmAssert(argc <= MAXARGS);
|
|
AvmAssert(argc == ci->count_args());
|
|
LIns* argIns[MAXARGS];
|
|
for (uint32_t i=0; i < argc; i++)
|
|
argIns[argc-i-1] = va_arg(ap, LIns*);
|
|
LIns* ins = lirout->insCall(ci, argIns);
|
|
|
|
// for non-returning functions ensure that we signify this fact
|
|
// by terminating control-flow with a ret.
|
|
if (neverReturns(ci))
|
|
lirout->ins1(LIR_retp, InsConstPtr(0));
|
|
NanoAssert(!ins->isInReg());
|
|
return ins;
|
|
}
|
|
|
|
LIns* LirHelper::nativeToAtom(LIns* native, Traits* t)
|
|
{
|
|
switch (bt(t)) {
|
|
#ifdef VMCFG_FLOAT
|
|
case BUILTIN_float:
|
|
// always allocFloat; we don't currently have the option to represent a float
|
|
// as part of the atom, like doubles/numbers can
|
|
return callIns(FUNCTIONID(floatToAtom), 2, coreAddr, native);
|
|
|
|
case BUILTIN_float4:
|
|
{
|
|
LIns* local = lirout->insAlloc(sizeof(float4_t));
|
|
stf4(native, local, 0, ACCSET_OTHER);
|
|
return callIns(FUNCTIONID(float4ToAtom), 2, coreAddr, lea(0, local));
|
|
}
|
|
#endif
|
|
|
|
case BUILTIN_number:
|
|
SSE2_ONLY(if(core->config.njconfig.i386_sse2) {
|
|
return callIns(FUNCTIONID(doubleToAtom_sse2), 2, coreAddr, native);
|
|
})
|
|
|
|
return callIns(FUNCTIONID(doubleToAtom), 2, coreAddr, native);
|
|
|
|
case BUILTIN_any:
|
|
case BUILTIN_object:
|
|
case BUILTIN_void:
|
|
return native; // value already represented as Atom
|
|
|
|
case BUILTIN_int:
|
|
if (native->isImmI()) {
|
|
int32_t val = native->immI();
|
|
if (atomIsValidIntptrValue(val))
|
|
return InsConstAtom(atomFromIntptrValue(val), native->isTainted());
|
|
}
|
|
return callIns(FUNCTIONID(intToAtom), 2, coreAddr, native);
|
|
|
|
case BUILTIN_uint:
|
|
if (native->isImmI()) {
|
|
uint32_t val = native->immI();
|
|
if (atomIsValidIntptrValue_u(val))
|
|
return InsConstAtom(atomFromIntptrValue_u(val), native->isTainted());
|
|
}
|
|
return callIns(FUNCTIONID(uintToAtom), 2, coreAddr, native);
|
|
|
|
case BUILTIN_boolean:
|
|
return ui2p(addi(lshi(native, 3), kBooleanType));
|
|
|
|
case BUILTIN_string:
|
|
return addp(native, kStringType);
|
|
|
|
case BUILTIN_namespace:
|
|
return addp(native, kNamespaceType);
|
|
|
|
default:
|
|
return addp(native, kObjectType);
|
|
}
|
|
}
|
|
|
|
LIns* LirHelper::atomToNative(BuiltinType bt, LIns* atom)
|
|
{
|
|
switch (bt)
|
|
{
|
|
case BUILTIN_any:
|
|
case BUILTIN_object:
|
|
case BUILTIN_void:
|
|
return atom;
|
|
|
|
#ifdef VMCFG_FLOAT
|
|
case BUILTIN_float4:
|
|
if (atom->isImmP()) {
|
|
float4_t f4val;
|
|
AvmCore::float4(&f4val, (Atom)atom->immP());
|
|
return lirout->insImmF4(f4val, atom->isTainted());
|
|
} else {
|
|
return ldf4(atom, -AtomConstants::kSpecialBibopType, ACCSET_OTHER);
|
|
}
|
|
|
|
case BUILTIN_float:
|
|
if (atom->isImmP())
|
|
return lirout->insImmF(AvmCore::atomToFloat((Atom)atom->immP()), atom->isTainted());
|
|
else
|
|
return ldf(atom, -AtomConstants::kSpecialBibopType, ACCSET_OTHER);
|
|
#endif
|
|
case BUILTIN_number:
|
|
if (atom->isImmP())
|
|
return lirout->insImmD(AvmCore::number_d((Atom)atom->immP()), atom->isTainted());
|
|
else
|
|
return callIns(FUNCTIONID(number_d), 1, atom);
|
|
|
|
case BUILTIN_int:
|
|
if (atom->isImmP())
|
|
return InsConst(AvmCore::integer_i((Atom)atom->immP()), atom->isTainted());
|
|
else
|
|
return callIns(FUNCTIONID(integer_i), 1, atom);
|
|
|
|
case BUILTIN_uint:
|
|
if (atom->isImmP())
|
|
return InsConst(AvmCore::integer_u((Atom)atom->immP()), atom->isTainted());
|
|
else
|
|
return callIns(FUNCTIONID(integer_u), 1, atom);
|
|
|
|
case BUILTIN_boolean:
|
|
if (atom->isImmI())
|
|
return InsConst((int32_t)atomGetBoolean((Atom)atom->immP()));
|
|
else
|
|
return p2i(rshup(atom, 3));
|
|
|
|
default:
|
|
// pointer type
|
|
if (atom->isImmP())
|
|
return InsConstPtr(atomPtr((Atom)atom->immP()));
|
|
else
|
|
return andp(atom, ~7);
|
|
}
|
|
|
|
#ifdef __GNUC__
|
|
return 0;// satisfy GCC, although we should never get here
|
|
#endif
|
|
}
|
|
|
|
void LirHelper::emitStart(Allocator& alloc, LirBuffer *lirbuf, LirWriter* &lirout) {
|
|
(void)alloc; (void)lirbuf;
|
|
debug_only(
|
|
// catch problems before they hit the writer pipeline
|
|
lirout = validate1 = new (alloc) ValidateWriter(lirout, lirbuf->printer, "emitStart");
|
|
)
|
|
lirout->ins0(LIR_start);
|
|
|
|
// create params for saved regs -- processor specific
|
|
for (int i=0; i < NumSavedRegs; i++) {
|
|
LIns *p = lirout->insParam(i, 1); (void) p;
|
|
verbose_only(if (lirbuf->printer)
|
|
lirbuf->printer->lirNameMap->addName(p,
|
|
regNames[REGNUM(RegAlloc::savedRegs[i])]);)
|
|
}
|
|
}
|
|
|
|
void LirHelper::liveAlloc(LIns* alloc)
|
|
{
|
|
if (alloc->isop(LIR_allocp))
|
|
livep(alloc);
|
|
}
|
|
|
|
LIns* LirHelper::stForTraits(Traits *t, LIns* val, LIns* p, int32_t d, AccSet accSet)
|
|
{
|
|
switch (bt(t)) {
|
|
case BUILTIN_number:
|
|
return std(val, p, d, accSet);
|
|
#ifdef VMCFG_FLOAT
|
|
case BUILTIN_float:
|
|
return stf(val, p, d, accSet);
|
|
case BUILTIN_float4:
|
|
return stf4(val, p, d, accSet);
|
|
#endif
|
|
case BUILTIN_int:
|
|
case BUILTIN_uint:
|
|
case BUILTIN_boolean:
|
|
return sti(val, p, d, accSet);
|
|
default:
|
|
return stp(val, p, d, accSet);
|
|
}
|
|
}
|
|
|
|
LIns* LirHelper::ldForTraits(Traits *t, LIns* p, int32_t d, AccSet accSet)
|
|
{
|
|
switch (bt(t)) {
|
|
case BUILTIN_number:
|
|
return ldd(p, d, accSet);
|
|
#ifdef VMCFG_FLOAT
|
|
case BUILTIN_float:
|
|
return ldf(p, d, accSet);
|
|
case BUILTIN_float4:
|
|
return ldf4(p, d, accSet);
|
|
#endif
|
|
case BUILTIN_int:
|
|
case BUILTIN_uint:
|
|
case BUILTIN_boolean:
|
|
return ldi(p, d, accSet);
|
|
default:
|
|
return ldp(p, d, accSet);
|
|
}
|
|
}
|
|
|
|
// check valid pointer and unbox it (returns ScriptObject*)
|
|
LIns* LirHelper::downcast_obj(LIns* atom, LIns* env, Traits* t)
|
|
{
|
|
callIns(FUNCTIONID(coerceobj_atom), 3, env, atom, InsConstPtr(t));
|
|
return andp(atom, ~7);
|
|
}
|
|
|
|
int32_t LirHelper::argSize(MethodSignaturep ms, int i)
|
|
{
|
|
return avmplus::argSize(ms->paramTraits(i));
|
|
}
|
|
|
|
#ifdef NJ_VERBOSE
|
|
void AvmLogControl::printf( const char* format, ... )
|
|
{
|
|
AvmAssert(core!=NULL);
|
|
|
|
va_list vargs;
|
|
va_start(vargs, format);
|
|
|
|
char str[1024];
|
|
VMPI_vsnprintf(str, sizeof(str), format, vargs);
|
|
va_end(vargs);
|
|
|
|
core->console << str;
|
|
}
|
|
#endif
|
|
|
|
#ifdef VMCFG_FLOAT
|
|
LIns* LirHelper::BasicLIREmitter::operator()(enum BuiltinType bt, LIns* oper1,LIns* oper2) const
|
|
{
|
|
switch(bt){
|
|
case BUILTIN_number: return _lh->binaryIns(_dblOp, oper1, oper2);
|
|
case BUILTIN_any: if(_call) return _lh->callIns(_call, 3, _lh->coreAddr, oper1, oper2); // else, fall through - a call is unexpected
|
|
default: return TODO("Unexpected builtin type InstructionEmiter, binary op");
|
|
}
|
|
}
|
|
|
|
LIns* LirHelper::BasicLIREmitter::operator()(enum BuiltinType bt,LIns* oper) const
|
|
{
|
|
switch(bt){
|
|
case BUILTIN_number: return _lh->lirout->ins1(_dblOp, oper);
|
|
case BUILTIN_any: if(_call) return _lh->callIns(_call, 2, _lh->coreAddr, oper); // else, fall through - a call is unexpected
|
|
default: return TODO("Unexpected builtin type InstructionEmiter, unary op");
|
|
}
|
|
}
|
|
|
|
LIns* LirHelper::ModuloLIREmitter::operator()(enum BuiltinType bt, LIns* op1,LIns* op2) const
|
|
{
|
|
switch(bt){
|
|
case BUILTIN_number: return _lh->callIns(FUNCTIONID(mod), 2, op1, op2 );
|
|
case BUILTIN_any: return _lh->callIns(FUNCTIONID(op_modulo), 3, _lh->coreAddr, op1, op2);
|
|
default: return TODO("Unexpected builtin type in ModuloLIREmitter");
|
|
}
|
|
}
|
|
|
|
LIns* LirHelper::IncrementLIREmitter::operator()(enum BuiltinType bt,LIns* oper) const
|
|
{
|
|
switch(bt){
|
|
case BUILTIN_number: return _lh->binaryIns(LIR_addd, oper, _lh->lirout->insImmD(_shouldDecrement?-1:1));
|
|
case BUILTIN_any:
|
|
{
|
|
/* Note: we add with 1.0f/-1.0f, to make sure that the results stays float if operand is float */
|
|
const CallInfo* addfunc = _lh->pool->hasFloatSupport() ? FUNCTIONID(op_add) : FUNCTIONID(op_add_nofloat);
|
|
return _lh->callIns( addfunc, 3, _lh->coreAddr, oper, _lh->InsConstAtom(_shouldDecrement? _lh->core->kFltMinusOne:_lh->core->kFltOne ));
|
|
}
|
|
default: return TODO("Unexpected builtin type in IncrementLIREmitter");
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
} // end namespace avmplus
|
|
|
|
//
|
|
// The following methods implement Service Provider API's defined in
|
|
// nanojit, which must be implemented by the nanojit embedder.
|
|
//
|
|
namespace nanojit
|
|
{
|
|
int StackFilter::getTop(LIns* /*br*/) {
|
|
AvmAssert(false);
|
|
return 0;
|
|
}
|
|
|
|
#ifdef NJ_VERBOSE
|
|
void LInsPrinter::formatGuard(InsBuf*, LIns*) {
|
|
AvmAssert(false);
|
|
}
|
|
void LInsPrinter::formatGuardXov(InsBuf*, LIns*) {
|
|
AvmAssert(false);
|
|
}
|
|
|
|
const char* LInsPrinter::accNames[] = {
|
|
"v", // (1 << 0) == ACCSET_VARS
|
|
"t", // (1 << 1) == ACCSET_TAGS
|
|
"o", // (1 << 2) == ACCSET_OTHER
|
|
"?", "?", "?", "?", "?", "?", "?", "?", // 3..10 (unused)
|
|
"?", "?", "?", "?", "?", "?", "?", "?", "?", "?", // 11..20 (unused)
|
|
"?", "?", "?", "?", "?", "?", "?", "?", "?", "?", // 21..30 (unused)
|
|
"?" // 31 (unused)
|
|
};
|
|
#endif
|
|
|
|
// Always returns requested storage upon normal return.
|
|
// OOM abort occurs if space is not available.
|
|
void* Allocator::allocChunk(size_t size) {
|
|
return mmfx_alloc(size);
|
|
}
|
|
|
|
void Allocator::freeChunk(void* p) {
|
|
mmfx_free(p);
|
|
}
|
|
|
|
void Allocator::postReset() {
|
|
}
|
|
|
|
#ifdef VMCFG_SHARK
|
|
|
|
#if defined VMCFG_64BIT
|
|
static const char* JIT_SO_PATH = "/tmp/jit64.so";
|
|
#elif defined VMCFG_32BIT
|
|
static const char* JIT_SO_PATH = "/tmp/jit32.so";
|
|
#else
|
|
# error "unsupported system"
|
|
#endif
|
|
|
|
// TODO: lock access to these statics.
|
|
static void *sHandle = NULL;
|
|
static char *sStart = NULL;
|
|
static char *sEnd = NULL;
|
|
static char *sCur = NULL;
|
|
size_t overage = 0;
|
|
|
|
void* CodeAlloc::allocCodeChunk(size_t nbytes)
|
|
{
|
|
if (!sHandle) {
|
|
sHandle = dlopen(JIT_SO_PATH, RTLD_NOW);
|
|
if (sHandle) {
|
|
// Get the bounds of the code area by looking for known symbols,
|
|
// then make that area writable.
|
|
sStart = (char*) dlsym(sHandle, "_jitStart");
|
|
sEnd = (char*) dlsym(sHandle, "_jitEnd");
|
|
if (!mprotect(sStart, sEnd - sStart,
|
|
PROT_READ | PROT_WRITE | PROT_EXEC))
|
|
sCur = sStart;
|
|
} else {
|
|
const char *err = dlerror();
|
|
fprintf(stderr, "dlopen error: %s\n", err);
|
|
// will fall through to AVMPI_allocateCodeMemory below.
|
|
}
|
|
}
|
|
|
|
if (sCur) {
|
|
char *next = sCur + nbytes;
|
|
if (next < sEnd) {
|
|
char *alloc = sCur;
|
|
sCur = next;
|
|
return alloc;
|
|
}
|
|
}
|
|
|
|
// Out of space in jit.so, fall back to AVMPI to get code mem.
|
|
overage += nbytes;
|
|
fprintf(stderr, "out of jit.so memory, allocated %luKB from system\n",
|
|
overage / 1024);
|
|
char* mem = (char*) AVMPI_allocateCodeMemory(nbytes);
|
|
mprotect((maddr_ptr) mem, (unsigned int) nbytes,
|
|
PROT_EXEC | PROT_READ | PROT_WRITE);
|
|
|
|
return mem;
|
|
}
|
|
|
|
void CodeAlloc::freeCodeChunk(void* addr, size_t nbytes)
|
|
{
|
|
if (addr < sStart || addr >= sEnd) {
|
|
// Did not come from DL region.
|
|
AVMPI_freeCodeMemory(addr, nbytes);
|
|
}
|
|
}
|
|
|
|
void CodeAlloc::markCodeChunkExec(void* /*addr*/, size_t /*nbytes*/) {
|
|
}
|
|
|
|
void CodeAlloc::markCodeChunkWrite(void* /*addr*/, size_t /*nbytes*/) {
|
|
}
|
|
|
|
#else // !VMCFG_SHARK
|
|
|
|
void* CodeAlloc::allocCodeChunk(size_t nbytes) {
|
|
return AVMPI_allocateCodeMemory(nbytes);
|
|
}
|
|
|
|
void CodeAlloc::freeCodeChunk(void* addr, size_t nbytes) {
|
|
AVMPI_freeCodeMemory(addr, nbytes);
|
|
}
|
|
|
|
void CodeAlloc::markCodeChunkExec(void* addr, size_t nbytes) {
|
|
//printf("protect %d %p\n", (int)nbytes, addr);
|
|
AVMPI_makeCodeMemoryExecutable(addr, nbytes, true); // RX
|
|
}
|
|
|
|
void CodeAlloc::markCodeChunkWrite(void* addr, size_t nbytes) {
|
|
//printf("unprotect %d %p\n", (int)nbytes, addr);
|
|
AVMPI_makeCodeMemoryExecutable(addr, nbytes, false); // RW
|
|
}
|
|
#endif
|
|
|
|
#ifdef DEBUG
|
|
// Note this method should only be called during debug builds.
|
|
bool CodeAlloc::checkChunkMark(void* addr, size_t nbytes, bool isExec) {
|
|
bool b = true;
|
|
|
|
// iterate over each page checking permission bits
|
|
size_t psize = VMPI_getVMPageSize();
|
|
uintptr_t last = alignTo((uintptr_t)addr + nbytes-1, psize);
|
|
uintptr_t start = (uintptr_t)addr;
|
|
for( uintptr_t n=start; b && n<=last; n += psize) {
|
|
#ifdef AVMPLUS_WIN32
|
|
/* windows */
|
|
MEMORY_BASIC_INFORMATION buf;
|
|
VMPI_memset(&buf, 0, sizeof(MEMORY_BASIC_INFORMATION));
|
|
SIZE_T sz = VirtualQuery((LPCVOID)n, &buf, sizeof(buf));
|
|
NanoAssert(sz > 0);
|
|
b = isExec ? buf.Protect == PAGE_EXECUTE_READ
|
|
: buf.Protect == PAGE_READWRITE;
|
|
NanoAssert(b);
|
|
#elif defined(__MACH30__) && !defined(VMCFG_SHARK)
|
|
/* mach / osx */
|
|
vm_address_t vmaddr = (vm_address_t)n;
|
|
vm_size_t vmsize = psize;
|
|
vm_region_basic_info_data_64_t inf;
|
|
mach_msg_type_number_t infoCnt = sizeof(vm_region_basic_info_data_64_t);
|
|
mach_port_t port;
|
|
VMPI_memset(&inf, 0, infoCnt);
|
|
kern_return_t err = vm_region_64(mach_task_self(), &vmaddr, &vmsize, VM_REGION_BASIC_INFO_64, (vm_region_info_t)&inf, &infoCnt, &port);
|
|
NanoAssert(err == KERN_SUCCESS);
|
|
b = isExec ? inf.protection == (VM_PROT_READ | VM_PROT_EXECUTE)
|
|
: inf.protection == (VM_PROT_READ | VM_PROT_WRITE);
|
|
NanoAssert(b);
|
|
#else
|
|
(void)psize;
|
|
(void)last;
|
|
(void)start;
|
|
(void)n;
|
|
(void)isExec;
|
|
#endif
|
|
}
|
|
return b;
|
|
}
|
|
|
|
void ValidateWriter::checkAccSet(LOpcode op, LIns* base, int32_t disp, AccSet accSet)
|
|
{
|
|
(void)disp;
|
|
LIns* vars = checkAccSetExtras ? (LIns*)checkAccSetExtras[0] : 0;
|
|
LIns* tags = checkAccSetExtras ? (LIns*)checkAccSetExtras[1] : 0;
|
|
|
|
// not enough to check base == xxx , since cse or lirbuffer may split (ld/st,b,d) into (ld/st,(addp,b,d),0)
|
|
bool isTags = (base == tags) ||
|
|
( (base->opcode() == LIR_addp) && (base->oprnd1() == tags) );
|
|
bool isVars = (base == vars) ||
|
|
( (base->opcode() == LIR_addp) && (base->oprnd1() == vars) );
|
|
bool isUnknown = !isTags && !isVars;
|
|
|
|
bool ok;
|
|
|
|
NanoAssert(accSet != ACCSET_NONE);
|
|
switch (accSet) {
|
|
case avmplus::ACCSET_VARS: ok = isVars; break;
|
|
case avmplus::ACCSET_TAGS: ok = isTags; break;
|
|
case avmplus::ACCSET_OTHER: ok = isUnknown; break;
|
|
default:
|
|
// This assertion will fail if any single-region AccSets aren't covered
|
|
// by the switch -- only multi-region AccSets should be handled here.
|
|
AvmAssert(compressAccSet(accSet).val == MINI_ACCSET_MULTIPLE.val);
|
|
ok = true;
|
|
break;
|
|
}
|
|
|
|
if (!ok) {
|
|
InsBuf b1, b2;
|
|
printer->formatIns(&b1, base);
|
|
VMPI_snprintf(b2.buf, b2.len, "but the base pointer (%s) doesn't match", b1.buf);
|
|
errorAccSet(lirNames[op], accSet, b2.buf);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
#endif // VMCFG_NANOJIT
|