From 044faf82611f451f689766d1b50abbc4f408951d Mon Sep 17 00:00:00 2001 From: David Anderson Date: Sat, 22 May 2010 17:09:52 -0700 Subject: [PATCH] Import method JIT 'outer' framework. --- js/src/Makefile.in | 12 +- js/src/jsapi.h | 2 + js/src/jscntxt.cpp | 6 + js/src/jscntxt.h | 29 +- js/src/jsinterp.h | 8 +- js/src/jsscript.h | 46 +- js/src/methodjit/BytecodeAnalyzer.cpp | 340 ++++++++++++++ js/src/methodjit/BytecodeAnalyzer.h | 123 +++++ js/src/methodjit/Logging.cpp | 140 ++++++ js/src/methodjit/Logging.h | 112 +++++ js/src/methodjit/MethodJIT.cpp | 618 ++++++++++++++++++++++++++ js/src/methodjit/MethodJIT.h | 231 ++++++++++ js/src/methodjit/Stubs.cpp | 272 ++++++++++++ js/src/methodjit/Stubs.h | 58 +++ js/src/shell/js.cpp | 17 +- js/src/trace-test/trace-test.py | 8 +- 16 files changed, 2007 insertions(+), 15 deletions(-) create mode 100644 js/src/methodjit/BytecodeAnalyzer.cpp create mode 100644 js/src/methodjit/BytecodeAnalyzer.h create mode 100644 js/src/methodjit/Logging.cpp create mode 100644 js/src/methodjit/Logging.h create mode 100644 js/src/methodjit/MethodJIT.cpp create mode 100644 js/src/methodjit/MethodJIT.h create mode 100644 js/src/methodjit/Stubs.cpp create mode 100644 js/src/methodjit/Stubs.h diff --git a/js/src/Makefile.in b/js/src/Makefile.in index 00cb7f6be5e..780205076f1 100644 --- a/js/src/Makefile.in +++ b/js/src/Makefile.in @@ -296,14 +296,14 @@ CPPSRCS += Assertions.cpp \ ExecutableAllocator.cpp \ ARMAssembler.cpp \ MacroAssemblerARM.cpp \ - $(NULL) -# Compiler.cpp \ - CodeGenerator.cpp \ - PICStubCompiler.cpp \ MethodJIT.cpp \ - Stubs.cpp \ BytecodeAnalyzer.cpp \ - Logging.cpp + Logging.cpp \ + Stubs.cpp \ + Compiler.cpp \ + $(NULL) +# CodeGenerator.cpp \ +# PICStubCompiler.cpp \ ifeq (86, $(findstring 86,$(TARGET_CPU))) ifeq (x86_64, $(TARGET_CPU)) diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 0a9e9309ecb..2560baad18c 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -802,6 +802,8 @@ JS_StringToVersion(const char *string); leaving that up to the embedding. */ +#define JSOPTION_METHODJIT JS_BIT(14) /* Whole-method JIT. */ + extern JS_PUBLIC_API(uint32) JS_GetOptions(JSContext *cx); diff --git a/js/src/jscntxt.cpp b/js/src/jscntxt.cpp index 9fa445f58ac..9334f8ffcda 100644 --- a/js/src/jscntxt.cpp +++ b/js/src/jscntxt.cpp @@ -523,6 +523,9 @@ JSThreadData::init() return false; #ifdef JS_TRACER InitJIT(&traceMonitor); +#endif +#ifdef JS_METHODJIT + jmData.Initialize(); #endif dtoaState = js_NewDtoaState(); if (!dtoaState) { @@ -550,6 +553,9 @@ JSThreadData::finish() propertyCache.~PropertyCache(); #if defined JS_TRACER FinishJIT(&traceMonitor); +#endif +#if defined JS_METHODJIT + jmData.Finish(); #endif stackSpace.finish(); } diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index ab0f6a04791..0155f0e8de5 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -113,6 +113,10 @@ template class Seq; } /* namespace nanojit */ +namespace JSC { + class ExecutableAllocator; +} + namespace js { /* Tracer constants. */ @@ -209,6 +213,23 @@ struct TracerState ~TracerState(); }; +namespace mjit { + struct ThreadData + { + JSC::ExecutableAllocator *execPool; + + // Scripts that have had PICs patched or PIC stubs generated. + typedef js::HashSet, js::SystemAllocPolicy> ScriptSet; + ScriptSet picScripts; + + bool Initialize(); + void Finish(); + + bool addScript(JSScript *script); + void removeScript(JSScript *script); + }; +} + /* * Storage for the execution state and store during trace execution. Generated * code depends on the fact that the globals begin |MAX_NATIVE_STACK_SLOTS| @@ -1006,6 +1027,10 @@ struct JSThreadData { js::TraceMonitor traceMonitor; #endif +#ifdef JS_METHODJIT + js::mjit::ThreadData jmData; +#endif + /* Lock-free hashed lists of scripts created by eval to garbage-collect. */ JSScript *scriptsToGC[JS_EVAL_CACHE_SIZE]; @@ -1562,6 +1587,7 @@ struct JSRuntime { #define JS_GSN_CACHE(cx) (JS_THREAD_DATA(cx)->gsnCache) #define JS_PROPERTY_CACHE(cx) (JS_THREAD_DATA(cx)->propertyCache) #define JS_TRACE_MONITOR(cx) (JS_THREAD_DATA(cx)->traceMonitor) +#define JS_METHODJIT_DATA(cx) (JS_THREAD_DATA(cx)->jmData) #define JS_SCRIPTS_TO_GC(cx) (JS_THREAD_DATA(cx)->scriptsToGC) #ifdef JS_EVAL_CACHE_METERING @@ -1722,7 +1748,7 @@ struct JSContext JS_REQUIRES_STACK JSFrameRegs *regs; - private: + public: friend class js::StackSpace; friend bool js::Interpret(JSContext *); @@ -1735,7 +1761,6 @@ struct JSContext this->regs = regs; } - public: /* Temporary arena pool used while compiling and decompiling. */ JSArenaPool tempPool; diff --git a/js/src/jsinterp.h b/js/src/jsinterp.h index dec0dc3d42e..46f1b6efaae 100644 --- a/js/src/jsinterp.h +++ b/js/src/jsinterp.h @@ -50,8 +50,8 @@ #include "jsscript.h" typedef struct JSFrameRegs { - jsbytecode *pc; /* program counter */ js::Value *sp; /* stack pointer */ + jsbytecode *pc; /* program counter */ } JSFrameRegs; /* JS stack frame flags. */ @@ -103,6 +103,12 @@ struct JSStackFrame static jsbytecode *const sInvalidPC; #endif +#if defined(JS_CPU_X86) || defined(JS_CPU_ARM) + void *ncode; /* jit return pc */ + /* Guh. Align. */ + void *align_[3]; +#endif + /* * We can't determine in advance which local variables can live on * the stack and be freed when their dynamic scope ends, and which diff --git a/js/src/jsscript.h b/js/src/jsscript.h index cd299c7a09b..e0b95cbe736 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -1,5 +1,5 @@ /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- - * vim: set ts=8 sw=4 et tw=79 ft=cpp: + * vim: set ts=4 sw=4 et tw=79 ft=cpp: * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 @@ -113,6 +113,17 @@ struct GlobalSlotArray { # define CHECK_SCRIPT_OWNER 1 #endif +#ifdef JS_METHODJIT +namespace JSC { + class ExecutablePool; +} +namespace js { + namespace mjit { + struct PICInfo; + } +} +#endif + struct JSScript { jsbytecode *code; /* bytecodes and their immediate operands */ uint32 length; /* length of code vector */ @@ -150,6 +161,28 @@ struct JSScript { #ifdef CHECK_SCRIPT_OWNER JSThread *owner; /* for thread-safe life-cycle assertions */ #endif +#ifdef JS_METHODJIT + // Note: the other pointers in this group may be non-NULL only if + // |execPool| is non-NULL. + void *ncode; /* native code compiled by the method JIT */ + void **nmap; /* maps PCs to native code */ + JSC::ExecutablePool *execPool; /* pool that contains |ncode|; script owns the pool */ + unsigned npics; /* Number of PICs in the array |pics| */ + js::mjit::PICInfo *pics; /* PICs in this script */ +# ifdef DEBUG + size_t jitLength; /* length of JIT'd code */ + + inline bool isValidJitCode(void *jcode) { + return (char*)jcode >= (char*)ncode && + (char*)jcode < (char*)ncode + jitLength; + } +# endif +#endif +#ifdef JS_TRACER + js::TraceTreeCache *trees; /* trace tree info. */ + uint32 tmGen; /* generation number from the TraceMonitor */ +#endif + uint32 tracePoints; /* number of trace points in the script */ /* Script notes are allocated right after the code. */ jssrcnote *notes() { return (jssrcnote *)(code + length); } @@ -223,6 +256,17 @@ struct JSScript { return const_cast(&emptyScriptConst); } +#ifdef JS_METHODJIT + /* + * Map the given PC to the corresponding native code address. + */ + void *pcToNative(jsbytecode *pc) { + JS_ASSERT(nmap); + JS_ASSERT(nmap[pc - code]); + return nmap[pc - code]; + } +#endif + private: /* * Use const to put this in read-only memory if possible. We are stuck with diff --git a/js/src/methodjit/BytecodeAnalyzer.cpp b/js/src/methodjit/BytecodeAnalyzer.cpp new file mode 100644 index 00000000000..b047eed8e89 --- /dev/null +++ b/js/src/methodjit/BytecodeAnalyzer.cpp @@ -0,0 +1,340 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=4 sw=4 et tw=99: + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla SpiderMonkey JavaScript 1.9 code, released + * May 28, 2008. + * + * The Initial Developer of the Original Code is + * Brendan Eich + * + * Contributor(s): + * David Anderson + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "BytecodeAnalyzer.h" +#include "jsautooplen.h" +#include "jsemit.h" + +using namespace js; + +BytecodeAnalyzer::~BytecodeAnalyzer() +{ + cx->free(ops); +} + +bool +BytecodeAnalyzer::addEdge(jsbytecode *pc, int32 offset, uint32 stackDepth) +{ + uint32 idx = (uint32)((pc + offset) - script->code); + + JS_ASSERT_IF(ops[idx].visited || ops[idx].nincoming, + ops[idx].stackDepth == stackDepth); + + if (!ops[idx].visited && !doList.append(pc + offset)) + return false; + + ops[idx].stackDepth = stackDepth; + ops[idx].nincoming++; + + return true; +} + +bool +BytecodeAnalyzer::analyze(uint32 index) +{ + jsbytecode *pc = doList[index]; + uint32 stackDepth = ops[pc - script->code].stackDepth; + +#ifdef DEBUG + bool canAssert = true; +#endif + + for (;;) { + JSOp op = JSOp(pc[0]); + OpcodeStatus &status = ops[pc - script->code]; + + if (status.visited) + return true; + +#ifdef DEBUG + if (assertDepths) { + jssrcnote *sn = js_GetSrcNote(script, pc); + if (sn && SN_TYPE(sn) == SRC_HIDDEN) + canAssert = false; + + JS_ASSERT_IF(canAssert && pc > script->main, + js_ReconstructStackDepth(cx, script, pc) == stackDepth); + } +#endif + + status.visited = true; + status.stackDepth = stackDepth; + + uint32 nuses, ndefs; + if (js_CodeSpec[op].nuses == -1) + nuses = js_GetVariableStackUses(op, pc); + else + nuses = js_CodeSpec[op].nuses; + + if (js_CodeSpec[op].ndefs == -1) + ndefs = js_GetEnterBlockStackDefs(cx, script, pc); + else + ndefs = js_CodeSpec[op].ndefs; + + JS_ASSERT(nuses <= stackDepth); + stackDepth -= nuses; + stackDepth += ndefs; + + uint32 offs; + jsbytecode *newpc; + switch (op) { + case JSOP_TRAP: + return false; + + case JSOP_DEFAULT: + case JSOP_GOTO: + offs = (pc + JSOP_GOTO_LENGTH) - script->code; + if (!ops[offs].visited && ops[offs].nincoming && !doList.append(pc + JSOP_GOTO_LENGTH)) + return false; + pc += GET_JUMP_OFFSET(pc); + ops[pc - script->code].nincoming++; + continue; + + case JSOP_DEFAULTX: + case JSOP_GOTOX: + offs = (pc + JSOP_GOTOX_LENGTH) - script->code; + if (!ops[offs].visited && ops[offs].nincoming && !doList.append(pc + JSOP_GOTOX_LENGTH)) + return false; + pc += GET_JUMPX_OFFSET(pc); + ops[pc - script->code].nincoming++; + continue; + + case JSOP_IFEQ: + case JSOP_IFNE: + if (!addEdge(pc, GET_JUMP_OFFSET(pc), stackDepth)) + return false; + break; + + case JSOP_OR: + case JSOP_AND: + /* If the jump is taken, the condition is pushed. */ + if (!addEdge(pc, GET_JUMP_OFFSET(pc), stackDepth + 1)) + return false; + break; + + case JSOP_IFEQX: + case JSOP_IFNEX: + case JSOP_ORX: + case JSOP_ANDX: + if (!addEdge(pc, GET_JUMPX_OFFSET(pc), stackDepth)) + return false; + break; + + case JSOP_CASE: + /* If the jump is taken, the extra value is not pushed. */ + if (!addEdge(pc, GET_JUMP_OFFSET(pc), stackDepth - 1)) + return false; + break; + + case JSOP_CASEX: + /* If the jump is taken, the extra value is not pushed. */ + if (!addEdge(pc, GET_JUMPX_OFFSET(pc), stackDepth - 1)) + return false; + break; + + case JSOP_GOSUB: + case JSOP_GOSUBX: + case JSOP_IFPRIMTOP: + case JSOP_FILTER: + case JSOP_ENDFILTER: + case JSOP_TABLESWITCHX: + case JSOP_LOOKUPSWITCHX: + return false; + + case JSOP_TABLESWITCH: + { + jsint def = GET_JUMP_OFFSET(pc); + if (!addEdge(pc, def, stackDepth)) + return false; + + newpc = pc + JUMP_OFFSET_LEN; + jsint low = GET_JUMP_OFFSET(newpc); + newpc += JUMP_OFFSET_LEN; + jsint high = GET_JUMP_OFFSET(newpc); + newpc += JUMP_OFFSET_LEN; + uint32 ncases = (uint32)(high - low + 1); + + for (uint32 i = 0; i < ncases; i++) { + jsint offs = GET_JUMP_OFFSET(newpc); + newpc += JUMP_OFFSET_LEN; + if (!offs) + offs = def; + if (!addEdge(pc, offs, stackDepth)) + return false; + } + pc = newpc + 1; + + break; + } + + case JSOP_LOOKUPSWITCH: + { + if (!addEdge(pc, GET_JUMP_OFFSET(pc), stackDepth)) + return false; + + newpc = pc + JUMP_OFFSET_LEN; + uint32 npairs = GET_UINT16(newpc); + newpc += UINT16_LEN; + + JS_ASSERT(npairs); + for (uint32 i = 0; i < npairs; i++) { + newpc += INDEX_LEN ; + if (!addEdge(pc, GET_JUMP_OFFSET(newpc), stackDepth)) + return false; + newpc += JUMP_OFFSET_LEN; + } + pc = newpc + 1; + + break; + } + + case JSOP_RETRVAL: + case JSOP_RETURN: + { + /* + * If there is nothing incoming, just leave. + * This is to defeat the emitter doing things like: + * leaveblock 1 + * retrval + * leaveblock 1 + * (see testNullCallee in trace-tests) + */ + JS_ASSERT(js_CodeSpec[op].length == 1); + uint32 offs = (pc + 1) - script->code; + if (ops[offs].visited || !ops[offs].nincoming) + return true; + + /* Otherwise, restore the stack depth and continue. */ + stackDepth = ops[offs].stackDepth; + break; + } + + case JSOP_THROW: + /* Control flow stops here. */ + return true; + + case JSOP_STOP: + JS_ASSERT(uint32(pc - script->code) + 1 == script->length); + return true; + + default: +#ifdef DEBUG + uint32 type = JOF_TYPE(js_CodeSpec[op].format); + JS_ASSERT(type != JOF_JUMP && type != JOF_JUMPX); +#endif + break; + } + + if (js_CodeSpec[op].length != -1) + pc += js_CodeSpec[op].length; + } +} + +bool +BytecodeAnalyzer::analyze() +{ + ops = (OpcodeStatus *)cx->malloc(sizeof(OpcodeStatus) * script->length); + if (!ops) + return false; + memset(ops, 0, sizeof(OpcodeStatus) * script->length); + + if (!doList.append(script->code)) + return false; + + if (script->trynotesOffset) { + JSTryNoteArray *tnarray = script->trynotes(); + for (unsigned i = 0; i < tnarray->length; ++i) { + JSTryNote &tn = tnarray->vector[i]; + if (tn.kind == JSTRY_ITER) + continue; + unsigned pcoff = script->main + tn.start + tn.length - script->code; + + ops[pcoff].exceptionEntry = true; + ops[pcoff].nincoming = 1; + ops[pcoff].stackDepth = tn.stackDepth; + if (!doList.append(script->code + pcoff)) + return false; + } + } + + for (size_t i = 0; i < doList.length(); i++) { + if (ops[doList[i] - script->code].visited) + continue; + if (!analyze(i)) + return false; + } + +#ifdef DEBUG + if (!assertDepths) + return true; + + /* + * In debug mode, do an extra pass to make sure each opcode was visited + * and has the correct stack depth. + */ + jsbytecode *pc = script->code; + bool canAssert = true; + for (;;) { + jssrcnote *sn = js_GetSrcNote(script, pc); + if (sn && SN_TYPE(sn) == SRC_HIDDEN) + canAssert = false; + + /* + * See comment in JSOP_RETURN/RETRVAL case. Don't assert that + * unreachable code was visited. + */ + JS_ASSERT_IF(!ops[pc - script->code].visited, + !ops[pc - script->code].nincoming); + + JS_ASSERT_IF(pc > script->main && canAssert && ops[pc - script->code].visited, + (ops[pc - script->code].stackDepth == + js_ReconstructStackDepth(cx, script, pc))); + + if (js_CodeSpec[JSOp(*pc)].length != -1) + pc += js_CodeSpec[JSOp(*pc)].length; + else + pc += js_GetVariableBytecodeLength(pc); + if (pc[0] == JSOP_STOP) + break; + } +#endif + + return true; +} + diff --git a/js/src/methodjit/BytecodeAnalyzer.h b/js/src/methodjit/BytecodeAnalyzer.h new file mode 100644 index 00000000000..7ca1137784e --- /dev/null +++ b/js/src/methodjit/BytecodeAnalyzer.h @@ -0,0 +1,123 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=4 sw=4 et tw=99: + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla SpiderMonkey JavaScript 1.9 code, released + * May 28, 2008. + * + * The Initial Developer of the Original Code is + * Brendan Eich + * + * Contributor(s): + * David Anderson + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#if !defined jsjaeger_bytecodeAnalyzer_h__ && defined JS_METHODJIT +#define jsjaeger_bytecodeAnalyzer_h__ + +#include "jsapi.h" +#include "jscntxt.h" +#include "jsscript.h" +#include "jsopcode.h" + +namespace js +{ + struct OpcodeStatus + { + bool visited; /* flag for CFG traversal */ + bool exceptionEntry; /* true iff this is a catch/finally entry point */ + bool safePoint; /* false by default */ + uint32 nincoming; /* number of CFG inedges here */ + uint32 stackDepth; /* stack depth before this opcode */ + }; + + class BytecodeAnalyzer + { + JSContext *cx; + JSScript *script; + OpcodeStatus *ops; + Vector doList; +#ifdef DEBUG + bool assertDepths; +#endif + + public: + BytecodeAnalyzer(JSContext *cx, JSScript *script) + : cx(cx), script(script), ops(NULL), + doList(ContextAllocPolicy(cx)) + { +#ifdef DEBUG + // This takes a very long time with SunSpider's string-tagcloud. + if (script->filename) { + const char *filename = script->filename; + size_t flen = strlen(filename); + const char *pat = "string-tagcloud.js"; + size_t plen = strlen(pat); + bool endswith = (plen < flen && + memcmp(filename + flen - plen, pat, plen) == 0); + assertDepths = !endswith; + } +#endif + } + ~BytecodeAnalyzer(); + + bool analyze(uint32 offs); + bool addEdge(jsbytecode *pc, int32 offset, uint32 stackDepth); + + public: + + inline const OpcodeStatus & operator [](uint32 offs) const + { + JS_ASSERT(offs < script->length); + return ops[offs]; + } + + inline OpcodeStatus & operator [](uint32 offs) + { + JS_ASSERT(offs < script->length); + return ops[offs]; + } + + inline const OpcodeStatus & operator [](jsbytecode *pc) const + { + JS_ASSERT(pc < script->code + script->length); + return ops[pc - script->code]; + } + + inline OpcodeStatus & operator [](jsbytecode *pc) + { + JS_ASSERT(pc < script->code + script->length); + return ops[pc - script->code]; + } + + bool analyze(); + }; +} + +#endif /* jsjaeger_bytecodeAnalyzer_h__ */ + diff --git a/js/src/methodjit/Logging.cpp b/js/src/methodjit/Logging.cpp new file mode 100644 index 00000000000..c7bffbf6738 --- /dev/null +++ b/js/src/methodjit/Logging.cpp @@ -0,0 +1,140 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=4 sw=4 et tw=99: + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla SpiderMonkey JavaScript 1.9 code, released + * May 28, 2008. + * + * The Initial Developer of the Original Code is + * Brendan Eich + * + * Contributor(s): + * David Anderson + * Julian Seward + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include +#include +#include "MethodJIT.h" +#include "Logging.h" + +#if defined(JS_METHODJIT_SPEW) + +static bool LoggingChecked = false; +static uint32 LoggingBits = 0; + +static const char *ChannelNames[] = +{ +#define _(name) #name, + JSPEW_CHAN_MAP(_) +#undef _ +}; + +void +js::JMCheckLogging() +{ + /* Not MT safe; races on Logging{Checked,Bits}. */ + if (LoggingChecked) + return; + LoggingChecked = true; + const char *env = getenv("JMFLAGS"); + if (!env) + return; + if (strstr(env, "help")) { + fflush(NULL); + printf( + "\n" + "usage: JMFLAGS=option,option,option,... where options can be:\n" + "\n" + " help show this message\n" + " abort/aborts ???\n" + " scripts ???\n" + " profile ???\n" +#ifdef DEBUG + " jsops JS opcodes\n" +#endif + " insns JS opcodes and generated insns\n" + " vmframe VMFrame contents\n" + " pics PIC patching activity\n" + " slowcalls Calls to slow path functions\n" + " full everything\n" + " notrace disable trace hints\n" + "\n" + ); + exit(0); + /*NOTREACHED*/ + } + if (strstr(env, "abort") || strstr(env, "aborts")) + LoggingBits |= (1 << uint32(JSpew_Abort)); + if (strstr(env, "scripts")) + LoggingBits |= (1 << uint32(JSpew_Scripts)); + if (strstr(env, "profile")) + LoggingBits |= (1 << uint32(JSpew_Prof)); +#ifdef DEBUG + if (strstr(env, "jsops")) + LoggingBits |= (1 << uint32(JSpew_JSOps)); +#endif + if (strstr(env, "insns")) + LoggingBits |= (1 << uint32(JSpew_Insns) | (1 << uint32(JSpew_JSOps))); + if (strstr(env, "vmframe")) + LoggingBits |= (1 << uint32(JSpew_VMFrame)); + if (strstr(env, "pics")) + LoggingBits |= (1 << uint32(JSpew_PICs)); + if (strstr(env, "slowcalls")) + LoggingBits |= (1 << uint32(JSpew_SlowCalls)); + if (strstr(env, "full")) + LoggingBits |= 0xFFFFFFFF; +} + +bool +js::IsJaegerSpewChannelActive(JaegerSpewChannel channel) +{ + JS_ASSERT(LoggingChecked); + return !!(LoggingBits & (1 << uint32(channel))); +} + +void +js::JaegerSpew(JaegerSpewChannel channel, const char *fmt, ...) +{ + JS_ASSERT(LoggingChecked); + + if (!(LoggingBits & (1 << uint32(channel)))) + return; + + fprintf(stdout, "[jaeger] %-7s ", ChannelNames[channel]); + + va_list ap; + va_start(ap, fmt); + vfprintf(stdout, fmt, ap); + va_end(ap); + + /* fprintf(stdout, "\n"); */ +} + +#endif + diff --git a/js/src/methodjit/Logging.h b/js/src/methodjit/Logging.h new file mode 100644 index 00000000000..792a7555800 --- /dev/null +++ b/js/src/methodjit/Logging.h @@ -0,0 +1,112 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=4 sw=4 et tw=99: + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla SpiderMonkey JavaScript 1.9 code, released + * May 28, 2008. + * + * The Initial Developer of the Original Code is + * Brendan Eich + * + * Contributor(s): + * David Anderson + * Julian Seward + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#if !defined jsjaeger_logging_h__ && defined JS_METHODJIT +#define jsjaeger_logging_h__ + +#include "prmjtime.h" + +namespace js { + +#define JSPEW_CHAN_MAP(_) \ + _(Abort) \ + _(Scripts) \ + _(Prof) \ + _(JSOps) \ + _(Insns) \ + _(VMFrame) \ + _(PICs) \ + _(SlowCalls) + +enum JaegerSpewChannel { +#define _(name) JSpew_##name, + JSPEW_CHAN_MAP(_) +#undef _ + JSpew_Terminator +}; + +#if defined(DEBUG) && !defined(JS_METHODJIT_SPEW) +# define JS_METHODJIT_SPEW +#endif + +#if defined(JS_METHODJIT_SPEW) + +void JMCheckLogging(); + +bool IsJaegerSpewChannelActive(JaegerSpewChannel channel); +void JaegerSpew(JaegerSpewChannel channel, const char *fmt, ...); + +struct Profiler { + JSInt64 t_start; + JSInt64 t_stop; + + static inline JSInt64 now() { + return PRMJ_Now(); + } + + inline void start() { + t_start = now(); + } + + inline void stop() { + t_stop = now(); + } + + inline uint32 time_ms() { + return uint32((t_stop - t_start) / PRMJ_USEC_PER_MSEC); + } + + inline uint32 time_us() { + return uint32(t_stop - t_start); + } +}; + +#else + +static inline void JaegerSpew(JaegerSpewChannel channel, const char *fmt, ...) +{ +} + +#endif + +} + +#endif + diff --git a/js/src/methodjit/MethodJIT.cpp b/js/src/methodjit/MethodJIT.cpp new file mode 100644 index 00000000000..ee5688519c0 --- /dev/null +++ b/js/src/methodjit/MethodJIT.cpp @@ -0,0 +1,618 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=4 sw=4 et tw=99: + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla SpiderMonkey JavaScript 1.9 code, released + * May 28, 2008. + * + * The Initial Developer of the Original Code is + * Brendan Eich + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "MethodJIT.h" +#include "Logging.h" +#include "assembler/jit/ExecutableAllocator.h" +#include "jstracer.h" + +using namespace js; +using namespace js::mjit; + +extern "C" void JS_FASTCALL +SetVMFrameRegs(VMFrame &f) +{ + f.oldRegs = f.cx->regs; + f.cx->setCurrentRegs(&f.regs); +} + +extern "C" void JS_FASTCALL +UnsetVMFrameRegs(VMFrame &f) +{ + *f.oldRegs = f.regs; + f.cx->setCurrentRegs(f.oldRegs); +} + +#if defined(__APPLE__) || defined(XP_WIN) +# define SYMBOL_STRING(name) "_" #name +#else +# define SYMBOL_STRING(name) #name +#endif + +JS_STATIC_ASSERT(offsetof(JSFrameRegs, sp) == 0); + +#if defined(__linux__) && defined(JS_CPU_X64) +# define SYMBOL_STRING_RELOC(name) #name "@plt" +#else +# define SYMBOL_STRING_RELOC(name) SYMBOL_STRING(name) +#endif + +#if defined(XP_MACOSX) +# define HIDE_SYMBOL(name) ".private_extern _" #name +#elif defined(__linux__) +# define HIDE_SYMBOL(name) ".hidden" #name +#else +# define HIDE_SYMBOL(name) +#endif + +#if defined(__GNUC__) + +/* If this assert fails, you need to realign VMFrame to 16 bytes. */ +#ifdef JS_CPU_ARM +JS_STATIC_ASSERT(sizeof(VMFrame) % 8 == 0); +#else +JS_STATIC_ASSERT(sizeof(VMFrame) % 16 == 0); +#endif + +# if defined(JS_CPU_X64) + +/* + * *** DANGER *** + * If these assertions break, update the constants below. + * *** DANGER *** + */ +JS_STATIC_ASSERT(offsetof(VMFrame, savedRBX) == 0x48); +JS_STATIC_ASSERT(offsetof(VMFrame, fp) == 0x30); + +asm volatile ( +".text\n" +".globl " SYMBOL_STRING(JaegerTrampoline) "\n" +SYMBOL_STRING(JaegerTrampoline) ":" "\n" + /* Prologue. */ + "pushq %rbp" "\n" + "movq %rsp, %rbp" "\n" + /* Save non-volatile registers. */ + "pushq %r12" "\n" + "pushq %r13" "\n" + "pushq %r14" "\n" + "pushq %r15" "\n" + "pushq %rbx" "\n" + + /* Build the JIT frame. + * rdi = cx + * rsi = fp + * rcx = inlineCallCount + * fp must go into rbx + */ + "pushq %rcx" "\n" + "pushq %rdi" "\n" + "pushq %rsi" "\n" + "movq %rsi, %rbx" "\n" + + /* Space for the rest of the VMFrame. */ + "subq $0x28, %rsp" "\n" + + /* Set cx->regs (requires saving rdx). */ + "pushq %rdx" "\n" + "movq %rsp, %rdi" "\n" + "call " SYMBOL_STRING_RELOC(SetVMFrameRegs) "\n" + "popq %rdx" "\n" + + /* + * Jump into into the JIT'd code. The call implicitly fills in + * the precious f.scriptedReturn member of VMFrame. + */ + "call *%rdx" "\n" + "leaq -8(%rsp), %rdi" "\n" + "call " SYMBOL_STRING_RELOC(UnsetVMFrameRegs) "\n" + + "addq $0x40, %rsp" "\n" + "popq %rbx" "\n" + "popq %r15" "\n" + "popq %r14" "\n" + "popq %r13" "\n" + "popq %r12" "\n" + "popq %rbp" "\n" + "movq $1, %rax" "\n" + "ret" "\n" +); + +asm volatile ( +".text\n" +".globl " SYMBOL_STRING(JaegerThrowpoline) "\n" +SYMBOL_STRING(JaegerThrowpoline) ":" "\n" + "movq %rsp, %rdi" "\n" + "call " SYMBOL_STRING_RELOC(js_InternalThrow) "\n" + "testq %rax, %rax" "\n" + "je throwpoline_exit" "\n" + "jmp *%rax" "\n" + "throwpoline_exit:" "\n" + "addq $0x48, %rsp" "\n" + "popq %rbx" "\n" + "popq %r15" "\n" + "popq %r14" "\n" + "popq %r13" "\n" + "popq %r12" "\n" + "popq %rbp" "\n" + "ret" "\n" +); + +asm volatile ( +".text\n" +".globl " SYMBOL_STRING(JaegerFromTracer) "\n" +SYMBOL_STRING(JaegerFromTracer) ":" "\n" + /* Restore fp reg. */ + "movq 0x30(%rsp), %rbx" "\n" + "jmp *%rax" "\n" +); + +# elif defined(JS_CPU_X86) + +/* + * *** DANGER *** + * If these assertions break, update the constants below. The throwpoline + * should have the offset of savedEBX plus 4, because it needs to clean + * up the argument. + * *** DANGER *** + */ +JS_STATIC_ASSERT(offsetof(VMFrame, savedEBX) == 0x2c); +JS_STATIC_ASSERT(offsetof(VMFrame, fp) == 0x20); + +asm volatile ( +".text\n" +".globl " SYMBOL_STRING(JaegerTrampoline) "\n" +SYMBOL_STRING(JaegerTrampoline) ":" "\n" + /* Prologue. */ + "pushl %ebp" "\n" + "movl %esp, %ebp" "\n" + /* Save non-volatile registers. */ + "pushl %esi" "\n" + "pushl %edi" "\n" + "pushl %ebx" "\n" + + /* Build the JIT frame. Push fields in order, + * then align the stack to form esp == VMFrame. */ + "pushl 20(%ebp)" "\n" + "pushl 8(%ebp)" "\n" + "pushl 12(%ebp)" "\n" + "movl 12(%ebp), %ebx" "\n" + "subl $0x1c, %esp" "\n" + + /* Jump into the JIT'd code. */ + "pushl 16(%ebp)" "\n" + "movl %esp, %ecx" "\n" + "call " SYMBOL_STRING_RELOC(SetVMFrameRegs) "\n" + "popl %edx" "\n" + + "call *%edx" "\n" + "leal -4(%esp), %ecx" "\n" + "call " SYMBOL_STRING_RELOC(UnsetVMFrameRegs) "\n" + + "addl $0x28, %esp" "\n" + "popl %ebx" "\n" + "popl %edi" "\n" + "popl %esi" "\n" + "popl %ebp" "\n" + "movl $1, %eax" "\n" + "ret" "\n" +); + +asm volatile ( +".text\n" +".globl " SYMBOL_STRING(JaegerThrowpoline) "\n" +SYMBOL_STRING(JaegerThrowpoline) ":" "\n" + /* Align the stack to 16 bytes. */ + "pushl %esp" "\n" + "pushl (%esp)" "\n" + "pushl (%esp)" "\n" + "pushl (%esp)" "\n" + "call " SYMBOL_STRING_RELOC(js_InternalThrow) "\n" + /* Bump the stack by 0x2c, as in the basic trampoline, but + * also one more word to clean up the stack for js_InternalThrow, + * and another to balance the alignment above. */ + "addl $0x10, %esp" "\n" + "testl %eax, %eax" "\n" + "je throwpoline_exit" "\n" + "jmp *%eax" "\n" + "throwpoline_exit:" "\n" + "addl $0x2c, %esp" "\n" + "popl %ebx" "\n" + "popl %edi" "\n" + "popl %esi" "\n" + "popl %ebp" "\n" + "xorl %eax, %eax" "\n" + "ret" "\n" +); + +asm volatile ( +".text\n" +".globl " SYMBOL_STRING(JaegerFromTracer) "\n" +SYMBOL_STRING(JaegerFromTracer) ":" "\n" + /* Restore frame regs. */ + "movl 0x20(%esp), %ebx" "\n" + "jmp *%eax" "\n" +); + +# elif defined(JS_CPU_ARM) + +JS_STATIC_ASSERT(offsetof(VMFrame, savedLR) == 76); +JS_STATIC_ASSERT(offsetof(VMFrame, fp) == 32); + +asm volatile ( +".text\n" +".globl " SYMBOL_STRING(JaegerFromTracer) "\n" +SYMBOL_STRING(JaegerFromTracer) ":" "\n" + /* Restore frame regs. */ + "ldr r11, [sp, #32]" "\n" + "bx r0" "\n" +); + +asm volatile ( +".text\n" +".globl " SYMBOL_STRING(JaegerTrampoline) "\n" +SYMBOL_STRING(JaegerTrampoline) ":" "\n" + /* The trampoline for ARM looks like this: + * [ lr ] \ + * [ r11 ] | + * [ r10 ] | + * [ r9 ] | Callee-saved registers. + * [ r8 ] | VFP registers d8-d15 may be required here too, but + * [ r7 ] | unconditionally preserving them might be expensive + * [ r6 ] | considering that we might not use them anyway. + * [ r5 ] | + * [ r4 ] / + * [ regs ] \ + * [ ICallCnt ] | Parameters for the compiled code (and subsequently-called routines). + * [ cx ] | + * [ fp ] | + * [ sp ] / + * [ args ] + * [ ... ] + * [ padding ] + * [ exc. ret ] <- sp + */ + + /* We push these in short groups (rather than using one big push operation) because it + * supposedly benefits Cortex-A9. TODO: Check that this is actually a benefit. */ +" push {r10,r11,lr}" "\n" +" push {r7-r9}" "\n" +" push {r4-r6}" "\n" +" mov r11, #0" "\n" /* r11 = inlineCallCount */ +" push {r11}" "\n" /* inlineCallCount */ +" push {r0}" "\n" /* cx */ +" push {r1}" "\n" /* fp */ +" mov r11, r1" "\n" /* FpReg */ + + /* Leave space for the VMFrame arguments. The largest slot appears to be 8 bytes for 32-bit + * architectures, though hard-coding this doesn't seem sensible. TODO: Use sizeof here and for + * the other targets. */ + +" sub sp, sp, #(8*4)" "\n" + +" mov r0, sp" "\n" +" push {r2}" "\n" +" blx " SYMBOL_STRING_RELOC(SetVMFrameRegs) "\n" +" pop {r2}" "\n" + + /* Call the compiled JavaScript function using r2 ('code'). */ +" bl " SYMBOL_STRING_RELOC(JaegerTrampVeneer) "\n" + + /* -------- + * Tidy up: Unwind the stack pointer, restore the callee-saved registers and then return. + */ + +" mov r0, sp" "\n" +" blx " SYMBOL_STRING_RELOC(UnsetVMFrameRegs) "\n" + + /* Skip past the parameters we pushed (such as cx and the like). */ +" add sp, sp, #(11*4)" "\n" + + /* Set a 'true' return value to indicate successful completion. */ +" mov r0, #1" "\n" + + /* We pop these in short groups (rather than using one big push operation) because it + * supposedly benefits Cortex-A9. TODO: Check that this is actually a benefit. */ +" pop {r4-r6}" "\n" +" pop {r7-r9}" "\n" +" pop {r10,r11,pc}" "\n" /* Pop lr directly into the pc to return quickly. */ +); + +asm volatile ( +".text\n" +".globl " SYMBOL_STRING(JaegerThrowpoline) "\n" +SYMBOL_STRING(JaegerThrowpoline) ":" "\n" + /* Restore 'f', as it will have been clobbered. */ +" mov r0, sp" "\n" + + /* Call the utility function that sets up the internal throw routine. */ +" blx " SYMBOL_STRING_RELOC(js_InternalThrow) "\n" + + /* If 0 was returned, just bail out as normal. Otherwise, we have a 'catch' or 'finally' clause + * to execute. */ +" cmp r0, #0" "\n" +" bxne r0" "\n" + + /* Skip past the parameters we pushed (such as cx and the like). */ +" add sp, sp, #(11*4)" "\n" + + /* We pop these in short groups (rather than using one big push operation) because it + * supposedly benefits Cortex-A9. TODO: Check that this is actually a benefit. */ +" pop {r4-r6}" "\n" +" pop {r7-r9}" "\n" +" pop {r10,r11,pc}" "\n" /* Pop lr directly into the pc to return quickly. */ +); + +asm volatile ( +".text\n" +".globl " SYMBOL_STRING(JaegerStubVeneer) "\n" +SYMBOL_STRING(JaegerStubVeneer) ":" "\n" + /* We enter this function as a veneer between a compiled method and one of the js_ stubs. We + * need to store the LR somewhere (so it can be modified in case on an exception) and then + * branch to the js_ stub as if nothing had happened. + * The arguments are identical to those for js_* except that the target function should be in + * 'ip'. TODO: This is not ABI-compliant, though it is convenient for now. I should work out + * which register to use to do this properly; r1 is likely because r0 gets VMFrame &f. */ +" str lr, [sp]" "\n" /* VMFrame->veneerReturn */ +" blx ip" "\n" +" ldr pc, [sp]" "\n" /* This should stack-predict, but only if 'sp' is used. */ +); + +asm volatile ( +".text\n" +".globl " SYMBOL_STRING(JaegerTrampVeneer) "\n" +SYMBOL_STRING(JaegerTrampVeneer) ":" "\n" + /* This is needed to store the initial scriptedReturn value, which won't + * get stored when invoking JaegerShot() into the middle of methods. + */ +" str lr, [sp, #4]" "\n" /* VMFrame->scriptedReturn */ +" bx r2" "\n" +); + +# else +# error "Unsupported CPU!" +# endif +#elif defined(_MSC_VER) + +#if defined(JS_CPU_X86) + +/* + * *** DANGER *** + * If these assertions break, update the constants below. The throwpoline + * should have the offset of savedEBX plus 4, because it needs to clean + * up the argument. + * *** DANGER *** + */ +JS_STATIC_ASSERT(offsetof(VMFrame, savedEBX) == 0x2c); +JS_STATIC_ASSERT(offsetof(VMFrame, fp) == 0x20); + +extern "C" { + + __declspec(naked) void JaegerFromTracer() + { + __asm { + mov ebx, [esp + 0x20]; + jmp eax; + } + } + + __declspec(naked) JSBool JaegerTrampoline(JSContext *cx, JSStackFrame *fp, void *code, + uintptr_t inlineCallCount) + { + __asm { + /* Prologue. */ + push ebp; + mov ebp, esp; + /* Save non-volatile registers. */ + push esi; + push edi; + push ebx; + + /* Build the JIT frame. Push fields in order, + * then align the stack to form esp == VMFrame. */ + push [ebp+20]; + push [ebp+8]; + push [ebp+12]; + mov ebx, [ebp+12]; + sub esp, 0x1c; + + /* Jump into into the JIT'd code. */ + push [ebp+16]; + mov ecx, esp; + call SetVMFrameRegs; + pop edx; + + call edx; + lea ecx, [esp-4]; + call UnsetVMFrameRegs; + + add esp, 0x28 + + pop ebx; + pop edi; + pop esi; + pop ebp; + mov eax, 1; + ret; + } + } + + extern "C" void *js_InternalThrow(js::VMFrame &f); + + __declspec(naked) void *JaegerThrowpoline(js::VMFrame *vmFrame) { + __asm { + /* Align the stack to 16 bytes. */ + push esp; + push [esp]; + push [esp]; + push [esp]; + call js_InternalThrow; + /* Bump the stack by 0x2c, as in the basic trampoline, but + * also one more word to clean up the stack for js_InternalThrow, + * and another to balance the alignment above. */ + add esp, 0x10; + test eax, eax; + je throwpoline_exit; + jmp eax; + throwpoline_exit: + add esp, 0x2c; + pop ebx; + pop edi; + pop esi; + pop ebp; + xor eax, eax + ret; + } + } +} + +#elif defined(JS_CPU_X64) + +/* + * *** DANGER *** + * If these assertions break, update the constants below. + * *** DANGER *** + */ +JS_STATIC_ASSERT(offsetof(VMFrame, savedRBX) == 0x48); +JS_STATIC_ASSERT(offsetof(VMFrame, fp) == 0x30); + +// Windows x64 uses assembler version since compiler doesn't support +// inline assembler +#else +# error "Unsupported CPU!" +#endif + +#endif /* _MSC_VER */ + +bool +ThreadData::Initialize() +{ + execPool = new JSC::ExecutableAllocator(); + if (!execPool) + return false; + + if (!picScripts.init()) { + delete execPool; + return false; + } + + return true; +} + +void +ThreadData::Finish() +{ + delete execPool; +} + +bool +ThreadData::addScript(JSScript *script) +{ + ScriptSet::AddPtr p = picScripts.lookupForAdd(script); + if (p) + return true; + return picScripts.add(p, script); +} + +void +ThreadData::removeScript(JSScript *script) +{ + ScriptSet::Ptr p = picScripts.lookup(script); + if (p) + picScripts.remove(p); +} + +extern "C" JSBool JaegerTrampoline(JSContext *cx, JSStackFrame *fp, void *code, + uintptr_t inlineCallCount); + +JSBool +mjit::JaegerShot(JSContext *cx) +{ + JS_ASSERT(cx->regs); + + JS_CHECK_RECURSION(cx, return JS_FALSE;); + + void *code; + jsbytecode *pc = cx->regs->pc; + JSStackFrame *fp = cx->fp; + JSScript *script = fp->script; + uintptr_t inlineCallCount = 0; + + JS_ASSERT(script->ncode && script->ncode != JS_UNJITTABLE_METHOD); + +#ifdef JS_TRACER + if (TRACE_RECORDER(cx)) + AbortRecording(cx, "attempt to enter method JIT while recording"); +#endif + + if (pc == script->code) + code = script->ncode; + else + code = script->nmap[pc - script->code]; + + JS_ASSERT(code); + +#ifdef JS_METHODJIT_SPEW + Profiler prof; + + JaegerSpew(JSpew_Prof, "entering jaeger script: %s, line %d\n", fp->script->filename, + fp->script->lineno); + prof.start(); +#endif + +#ifdef DEBUG + JSStackFrame *checkFp = fp; +#endif +#if 0 + uintptr_t iCC = inlineCallCount; + while (iCC--) + checkFp = checkFp->down; +#endif + + JSAutoResolveFlags rf(cx, JSRESOLVE_INFER); + JSBool ok = JaegerTrampoline(cx, fp, code, inlineCallCount); + + JS_ASSERT(checkFp == cx->fp); + +#ifdef JS_METHODJIT_SPEW + prof.stop(); + JaegerSpew(JSpew_Prof, "script run took %d ms\n", prof.time_ms()); +#endif + + return ok; +} + diff --git a/js/src/methodjit/MethodJIT.h b/js/src/methodjit/MethodJIT.h new file mode 100644 index 00000000000..8303d0efce8 --- /dev/null +++ b/js/src/methodjit/MethodJIT.h @@ -0,0 +1,231 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=4 sw=4 et tw=99: + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla SpiderMonkey JavaScript 1.9 code, released + * May 28, 2008. + * + * The Initial Developer of the Original Code is + * Brendan Eich + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#if !defined jsjaeger_h__ && defined JS_METHODJIT +#define jsjaeger_h__ + +#include "jscntxt.h" + +#include "assembler/assembler/MacroAssemblerCodeRef.h" + +#if !defined JS_CPU_X64 && \ + !defined JS_CPU_X86 && \ + !defined JS_CPU_ARM +# error "Oh no, you should define a platform so this compiles." +#endif + +namespace js { + +struct VMFrame +{ +#if defined(JS_CPU_ARM) + JSC::ReturnAddressPtr veneerReturn; +#endif + + /* This must be the first entry on CPUs which push return addresses. */ + void *scriptedReturn; + +#if defined(JS_CPU_X86) + uintptr_t padding[2]; +#elif defined(JS_CPU_ARM) + uintptr_t padding; +#endif + + union Arguments { + struct BINDNAME { + uint32 index; + JSObject *obj; + } bindname; + struct DEFVAR { + jsatomid index; + uint32 op; + } defvar; + struct LAMBDA { + uint32 index; + JSOp op; + } lambda; + struct INITPROP { + uint32 index; + unsigned defineHow; + } initprop; + struct DEFLOCALFUN { + uint32 slot; + uint32 index; + } deflocalfun; + struct TRACER { + uint32 traceId; + uint32 offs; + } tracer; + struct SETTER { + jsatomid index; + JSOp op; + } setter; + } u; + + JSFrameRegs *oldRegs; + JSFrameRegs regs; + JSStackFrame *fp; + JSContext *cx; + uintptr_t inlineCallCount; + +#if defined(JS_CPU_X86) + void *savedEBX; + void *savedEDI; + void *savedESI; + void *savedEBP; + void *savedEIP; + + inline void setReturnAddress(JSC::ReturnAddressPtr addr) { + *(reinterpret_cast(this)-1) = addr; + } + + inline JSC::ReturnAddressPtr getReturnAddress() const { + return *(reinterpret_cast(this)-1); + } + +#elif defined(JS_CPU_X64) + void *savedRBX; +#ifdef _MSC_VER + void *savedRSI; + void *savedRDI; +#endif + void *savedR15; + void *savedR14; + void *savedR13; + void *savedR12; + void *savedRBP; + void *savedRIP; + +#ifdef _MSC_VER + inline void setReturnAddress(JSC::ReturnAddressPtr addr) { + *(reinterpret_cast(this)-5) = addr; + } + + inline JSC::ReturnAddressPtr getReturnAddress() const { + return *(reinterpret_cast(this)-5); + } +#else + inline void setReturnAddress(JSC::ReturnAddressPtr addr) { + *(reinterpret_cast(this)-1) = addr; + } + + inline JSC::ReturnAddressPtr getReturnAddress() const { + return *(reinterpret_cast(this)-1); + } +#endif + +#elif defined(JS_CPU_ARM) + void *savedR4; + void *savedR5; + void *savedR6; + void *savedR7; + void *savedR8; + void *savedR9; + void *savedR10; + void *savedR11; + void *savedLR; + + inline void setReturnAddress(JSC::ReturnAddressPtr addr) { + this->veneerReturn = addr; + } + + inline JSC::ReturnAddressPtr getReturnAddress() { + return this->veneerReturn; + } + +#else +# error "The VMFrame layout isn't defined for your processor architecture!" +#endif + + JSRuntime *runtime() { return cx->runtime; } +}; + +#ifdef JS_CPU_ARM +extern "C" void JaegerStubVeneer(void); +#endif + +typedef void (JS_FASTCALL *VoidStub)(VMFrame &); +typedef void (JS_FASTCALL *VoidVpStub)(VMFrame &, jsval *); +typedef void (JS_FASTCALL *VoidStubUInt32)(VMFrame &, uint32); +typedef void (JS_FASTCALL *VoidStubInt32)(VMFrame &, int32); +typedef JSBool (JS_FASTCALL *BoolStub)(VMFrame &); +typedef void * (JS_FASTCALL *VoidPtrStub)(VMFrame &); +typedef void * (JS_FASTCALL *VoidPtrStubPC)(VMFrame &, jsbytecode *); +typedef void * (JS_FASTCALL *VoidPtrStubUInt32)(VMFrame &, uint32); +typedef JSObject * (JS_FASTCALL *JSObjStub)(VMFrame &); +typedef JSObject * (JS_FASTCALL *JSObjStubUInt32)(VMFrame &, uint32); + +#define JS_UNJITTABLE_METHOD (reinterpret_cast(-1)) + +namespace mjit { + +JSBool +JaegerShot(JSContext *cx); + +enum CompileStatus +{ + Compile_Okay, + Compile_Abort, + Compile_Error +}; + +CompileStatus +TryCompile(JSContext *cx, JSScript *script, JSObject *scopeChain); + +static inline CompileStatus +CanMethodJIT(JSContext *cx, JSScript *script, JSObject *scopeChain) +{ + if (!(cx->options & JSOPTION_METHODJIT) || script->ncode == JS_UNJITTABLE_METHOD) + return Compile_Abort; + if (script->ncode == NULL) + return TryCompile(cx, script, scopeChain); + return Compile_Okay; +} + +} /* namespace mjit */ + +} /* namespace js */ + +#ifdef _MSC_VER +extern "C" void *JaegerThrowpoline(js::VMFrame *vmFrame); +#else +extern "C" void JaegerThrowpoline(); +#endif +extern "C" void JaegerFromTracer(); + +#endif /* jsjaeger_h__ */ diff --git a/js/src/methodjit/Stubs.cpp b/js/src/methodjit/Stubs.cpp new file mode 100644 index 00000000000..a3124b714fc --- /dev/null +++ b/js/src/methodjit/Stubs.cpp @@ -0,0 +1,272 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=4 sw=4 et tw=99: + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla SpiderMonkey JavaScript 1.9 code, released + * May 28, 2008. + * + * The Initial Developer of the Original Code is + * Brendan Eich + * + * Contributor(s): + * David Anderson + * David Mandelin + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#define __STDC_LIMIT_MACROS + +#include "jscntxt.h" +#include "jsscope.h" +#include "jsobj.h" +#include "jslibmath.h" +#include "jsiter.h" +#include "jsnum.h" +#include "jsxml.h" +#include "jsstaticcheck.h" +#include "jsbool.h" +#include "assembler/assembler/MacroAssemblerCodeRef.h" +#include "jsiter.h" +#include "jstypes.h" +#include "methodjit/Stubs.h" +#include "jstracer.h" +#include "jspropertycache.h" +#include "jspropertycacheinlines.h" +#include "jsscopeinlines.h" +#include "jsscriptinlines.h" +#include "jsstrinlines.h" +#include "jsobjinlines.h" +#include "jscntxtinlines.h" + +#include "jsautooplen.h" + +using namespace js; +using namespace js::mjit; +using namespace JSC; + +#define THROW() \ + do { \ + void *ptr = JS_FUNC_TO_DATA_PTR(void *, JaegerThrowpoline); \ + f.setReturnAddress(ReturnAddressPtr(FunctionPtr(ptr))); \ + return; \ + } while (0) + +#define THROWV(v) \ + do { \ + void *ptr = JS_FUNC_TO_DATA_PTR(void *, JaegerThrowpoline); \ + f.setReturnAddress(ReturnAddressPtr(FunctionPtr(ptr))); \ + return v; \ + } while (0) + +static bool +InlineReturn(JSContext *cx) +{ + bool ok = true; + + JSStackFrame *fp = cx->fp; + + JS_ASSERT(!fp->blockChain); + JS_ASSERT(!js_IsActiveWithOrBlock(cx, fp->scopeChain, 0)); + + if (fp->script->staticLevel < JS_DISPLAY_SIZE) + cx->display[fp->script->staticLevel] = fp->displaySave; + + // Marker for debug support. + void *hookData = fp->hookData; + if (JS_UNLIKELY(hookData != NULL)) { + JSInterpreterHook hook; + JSBool status; + + hook = cx->debugHooks->callHook; + if (hook) { + /* + * Do not pass &ok directly as exposing the address inhibits + * optimizations and uninitialised warnings. + */ + status = ok; + hook(cx, fp, JS_FALSE, &status, hookData); + ok = (status == JS_TRUE); + // CHECK_INTERRUPT_HANDLER(); + } + } + + fp->putActivationObjects(cx); + + /* :TODO: version stuff */ + + if (fp->flags & JSFRAME_CONSTRUCTING && fp->rval.isPrimitive()) + fp->rval = fp->thisv; + + cx->stack().popInlineFrame(cx, fp, fp->down); + cx->regs->sp[-1] = fp->rval; + + return ok; +} + +void * JS_FASTCALL +mjit::stubs::Return(VMFrame &f) +{ + if (!f.inlineCallCount) + return f.fp->ncode; + + JSContext *cx = f.cx; + JS_ASSERT(f.fp == cx->fp); + +#ifdef DEBUG + bool wasInterp = f.fp->script->ncode == JS_UNJITTABLE_METHOD; +#endif + + bool ok = InlineReturn(cx); + + f.inlineCallCount--; + JS_ASSERT(f.regs.sp == cx->regs->sp); + f.fp = cx->fp; + + JS_ASSERT_IF(f.inlineCallCount > 1 && !wasInterp, + f.fp->down->script->isValidJitCode(f.fp->ncode)); + + if (!ok) + THROWV(NULL); + + return f.fp->ncode; +} + +static jsbytecode * +FindExceptionHandler(JSContext *cx) +{ + JSStackFrame *fp = cx->fp; + JSScript *script = fp->script; + +top: + if (cx->throwing && script->trynotesOffset) { + // The PC is updated before every stub call, so we can use it here. + unsigned offset = cx->regs->pc - script->main; + + JSTryNoteArray *tnarray = script->trynotes(); + for (unsigned i = 0; i < tnarray->length; ++i) { + JSTryNote *tn = &tnarray->vector[i]; + if (offset - tn->start >= tn->length) + continue; + if (tn->stackDepth > cx->regs->sp - fp->base()) + continue; + + jsbytecode *pc = script->main + tn->start + tn->length; + JSBool ok = js_UnwindScope(cx, tn->stackDepth, JS_TRUE); + JS_ASSERT(cx->regs->sp == fp->base() + tn->stackDepth); + + switch (tn->kind) { + case JSTRY_CATCH: + JS_ASSERT(js_GetOpcode(cx, fp->script, pc) == JSOP_ENTERBLOCK); + +#if JS_HAS_GENERATORS + /* Catch cannot intercept the closing of a generator. */ + if (JS_UNLIKELY(cx->exception.isMagic(JS_GENERATOR_CLOSING))) + break; +#endif + + /* + * Don't clear cx->throwing to save cx->exception from GC + * until it is pushed to the stack via [exception] in the + * catch block. + */ + return pc; + + case JSTRY_FINALLY: + /* + * Push (true, exception) pair for finally to indicate that + * [retsub] should rethrow the exception. + */ + cx->regs->sp[0].setBoolean(true); + cx->regs->sp[1] = cx->exception; + cx->regs->sp += 2; + cx->throwing = JS_FALSE; + return pc; + + case JSTRY_ITER: + { + /* + * This is similar to JSOP_ENDITER in the interpreter loop, + * except the code now uses the stack slot normally used by + * JSOP_NEXTITER, namely regs.sp[-1] before the regs.sp -= 2 + * adjustment and regs.sp[1] after, to save and restore the + * pending exception. + */ + AutoValueRooter tvr(cx, cx->exception); + JS_ASSERT(js_GetOpcode(cx, fp->script, pc) == JSOP_ENDITER); + cx->throwing = JS_FALSE; + ok = !!js_CloseIterator(cx, cx->regs->sp[-1]); + cx->regs->sp -= 1; + if (!ok) + goto top; + cx->throwing = JS_TRUE; + cx->exception = tvr.value(); + } + } + } + } + + return NULL; +} + +extern "C" void * +js_InternalThrow(VMFrame &f) +{ + JSContext *cx = f.cx; + + // Make sure sp is up to date. + JS_ASSERT(cx->regs == &f.regs); + + jsbytecode *pc = NULL; + for (;;) { + pc = FindExceptionHandler(cx); + if (pc) + break; + + // If |f.inlineCallCount == 0|, then we are on the 'topmost' frame (where + // topmost means the first frame called into through js_Interpret). In this + // case, we still unwind, but we shouldn't return from a JS function, because + // we're not in a JS function. + bool lastFrame = (f.inlineCallCount == 0); + js_UnwindScope(cx, 0, cx->throwing); + if (lastFrame) + break; + + JS_ASSERT(f.regs.sp == cx->regs->sp); + f.scriptedReturn = stubs::Return(f); + } + + JS_ASSERT(f.regs.sp == cx->regs->sp); + + if (!pc) { + *f.oldRegs = f.regs; + f.cx->setCurrentRegs(f.oldRegs); + return NULL; + } + + return cx->fp->script->pcToNative(pc); +} + diff --git a/js/src/methodjit/Stubs.h b/js/src/methodjit/Stubs.h new file mode 100644 index 00000000000..be086eb9fc5 --- /dev/null +++ b/js/src/methodjit/Stubs.h @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=4 sw=4 et tw=99: + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla SpiderMonkey JavaScript 1.9 code, released + * May 28, 2008. + * + * The Initial Developer of the Original Code is + * Brendan Eich + * + * Contributor(s): + * David Anderson + * David Mandelin + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jslogic_h__ +#define jslogic_h__ + +#include "MethodJIT.h" + +namespace js { +namespace mjit { +namespace stubs { + +void * JS_FASTCALL Return(VMFrame &f); + +}}} /* namespace stubs,mjit,js */ + +extern "C" void * +js_InternalThrow(js::VMFrame &f); + +#endif /* jslogic_h__ */ + diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index a891d918af7..6aa1a9ea104 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -139,7 +139,8 @@ static jsdouble MAX_TIMEOUT_INTERVAL = 1800.0; static jsdouble gTimeoutInterval = -1.0; static volatile bool gCanceled = false; -static bool enableJit = false; +static bool enableTraceJit = false; +static bool enableMethodJit = false; static JSBool SetTimeoutValue(JSContext *cx, jsdouble t); @@ -600,7 +601,8 @@ static const struct { } js_options[] = { {"anonfunfix", JSOPTION_ANONFUNFIX}, {"atline", JSOPTION_ATLINE}, - {"jit", JSOPTION_JIT}, + {"tracejit", JSOPTION_JIT}, + {"methodjit", JSOPTION_METHODJIT}, {"relimit", JSOPTION_RELIMIT}, {"strict", JSOPTION_STRICT}, {"werror", JSOPTION_WERROR}, @@ -752,7 +754,7 @@ ProcessArgs(JSContext *cx, JSObject *obj, char **argv, int argc) break; case 'j': - enableJit = !enableJit; + enableTraceJit = !enableTraceJit; JS_ToggleOptions(cx, JSOPTION_JIT); #if defined(JS_TRACER) && defined(DEBUG) js::InitJITStatsClass(cx, JS_GetGlobalObject(cx)); @@ -761,6 +763,11 @@ ProcessArgs(JSContext *cx, JSObject *obj, char **argv, int argc) #endif break; + case 'm': + enableMethodJit = !enableMethodJit; + JS_ToggleOptions(cx, JSOPTION_METHODJIT); + break; + case 'o': { if (++i == argc) @@ -4904,8 +4911,10 @@ NewContext(JSRuntime *rt) JS_SetErrorReporter(cx, my_ErrorReporter); JS_SetVersion(cx, JSVERSION_LATEST); SetContextOptions(cx); - if (enableJit) + if (enableTraceJit) JS_ToggleOptions(cx, JSOPTION_JIT); + if (enableMethodJit) + JS_ToggleOptions(cx, JSOPTION_METHODJIT); return cx; } diff --git a/js/src/trace-test/trace-test.py b/js/src/trace-test/trace-test.py index fc15ca039f7..39a75289875 100644 --- a/js/src/trace-test/trace-test.py +++ b/js/src/trace-test/trace-test.py @@ -111,7 +111,11 @@ def get_test_cmd(path, lib_dir): if not libdir_var.endswith('/'): libdir_var += '/' expr = "const platform=%r; const libdir=%r;"%(sys.platform, libdir_var) - return [ JS, '-j', '-e', expr, '-f', os.path.join(lib_dir, 'prolog.js'), + if OPTIONS.methodjit_only: + jit_flags = [ '-m' ] + else: + jit_flags = [ '-m', '-j' ] + return [ JS ] + jit_flags + [ '-e', expr, '-f', os.path.join(lib_dir, 'prolog.js'), '-f', path ] def run_test(test, lib_dir): @@ -268,6 +272,8 @@ if __name__ == '__main__': help='Run test files listed in [FILE]') op.add_option('-R', '--retest', dest='retest', metavar='FILE', help='Retest using test list file [FILE]') + op.add_option('--methodjit-only', dest='methodjit_only', action='store_true', + help='Run tests with only method compiler enabled, not tracing') op.add_option('-g', '--debug', dest='debug', action='store_true', help='Run test in gdb') op.add_option('--valgrind', dest='valgrind', action='store_true',