author | David Anderson <danderson@mozilla.com> |
Thu, 27 May 2010 21:26:19 -0700 | |
changeset 52611 | 4b73e56e7acba439f9119356c9d0e68862609546 |
parent 52610 | bc48920f686328efa2cf293305feb79bdb214ace |
child 52612 | d907911777bbc7df41486d480540338477d96d78 |
push id | 1 |
push user | root |
push date | Tue, 26 Apr 2011 22:38:44 +0000 |
treeherder | mozilla-beta@bfdb6e623a36 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
milestone | 1.9.3a5pre |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
|
--- a/js/src/methodjit/BaseAssembler.h +++ b/js/src/methodjit/BaseAssembler.h @@ -39,56 +39,165 @@ * ***** END LICENSE BLOCK ***** */ #if !defined jsjaeger_baseassembler_h__ && defined JS_METHODJIT #define jsjaeger_baseassembler_h__ #include "jscntxt.h" #include "jstl.h" #include "assembler/assembler/MacroAssemblerCodeRef.h" #include "assembler/assembler/MacroAssembler.h" +#include "assembler/assembler/RepatchBuffer.h" +#include "assembler/moco/MocoStubs.h" +#include "methodjit/MethodJIT.h" +#include "methodjit/MachineRegs.h" namespace js { namespace mjit { +struct FrameAddress : JSC::MacroAssembler::Address +{ + FrameAddress(int32 offset) + : Address(JSC::MacroAssembler::stackPointerRegister, offset) + { } +}; + class BaseAssembler : public JSC::MacroAssembler { struct CallPatch { CallPatch(ptrdiff_t distance, void *fun) : distance(distance), fun(fun) { } ptrdiff_t distance; JSC::FunctionPtr fun; }; + /* Need a temp reg that is not ArgReg1. */ +#if defined(JS_CPU_X86) || defined(JS_CPU_ARM) + static const RegisterID ClobberInCall = JSC::X86Registers::ecx; +#elif defined(JS_CPU_ARM) + static const RegisterID ClobberInCall = JSC::ARMRegisters::r2; +#endif + /* :TODO: OOM */ Label startLabel; Vector<CallPatch, 64, SystemAllocPolicy> callPatches; public: BaseAssembler() : callPatches(SystemAllocPolicy()) { startLabel = label(); } + /* + * FpReg is used to home the current JSStackFrame*. + */ +#if defined(JS_CPU_X86) || defined(JS_CPU_X64) + static const RegisterID FpReg = JSC::X86Registers::ebx; +#elif defined(JS_CPU_ARM) + static const RegisterID FpReg = JSC::X86Registers::r11; +#endif + + /* + * Prepares for a stub call. + */ + void * getCallTarget(void *fun) { +#ifdef JS_CPU_ARM + /* + * Insert a veneer for ARM to allow it to catch exceptions. There is no + * reliable way to determine the location of the return address on the + * stack, so it cannot be hijacked. + * + * :TODO: It wouldn't surprise me if GCC always pushes LR first. In that + * case, this looks like the x86-style call, and we can hijack the stack + * slot accordingly, thus avoiding the cost of a veneer. This should be + * investigated. + */ + + void *pfun = JS_FUNC_TO_DATA_PTR(void *, JaegerStubVeneer); + + /* + * We put the real target address into IP, as this won't conflict with + * the EABI argument-passing mechanism. Technically, this isn't ABI- + * compliant. + */ + move(Imm32(intptr_t(fun)), ARMRegisters::ip); +#else + /* + * Architectures that push the return address to an easily-determined + * location on the stack can hijack C++'s return mechanism by overwriting + * that address, so a veneer is not required. + */ + void *pfun = fun; +#endif + return pfun; + } + + +#define STUB_CALL_TYPE(type) \ + Call stubCall(type stub, jsbytecode *pc, uint32 fd) { \ + return stubCall(JS_FUNC_TO_DATA_PTR(void *, stub), \ + pc, fd); \ + } + + STUB_CALL_TYPE(JSObjStub); + +#undef STUB_CALL_TYPE + + Call stubCall(void *ptr, jsbytecode *pc, uint32 frameDepth) { + JS_STATIC_ASSERT(ClobberInCall != Registers::ArgReg1); + + void *pfun = getCallTarget(ptr); + + /* PC -> regs->pc :( */ + storePtr(ImmPtr(pc), + FrameAddress(offsetof(VMFrame, regs) + offsetof(JSFrameRegs, pc))); + + /* sp = fp + slots() + stackDepth */ + addPtr(Imm32(sizeof(JSStackFrame) + frameDepth * sizeof(jsval)), + FpReg, + ClobberInCall); + + /* regs->sp = sp */ + storePtr(ClobberInCall, + FrameAddress(offsetof(VMFrame, regs) + offsetof(JSFrameRegs, sp))); + + /* VMFrame -> ArgReg0 */ + move(MacroAssembler::stackPointerRegister, Registers::ArgReg0); + + return call(pfun); + } + Call call(void *fun) { #if defined(_MSC_VER) && defined(_M_X64) masm.subPtr(JSC::MacroAssembler::Imm32(32), JSC::MacroAssembler::stackPointerRegister); #endif Call cl = JSC::MacroAssembler::call(); #if defined(_MSC_VER) && defined(_M_X64) masm.addPtr(JSC::MacroAssembler::Imm32(32), JSC::MacroAssembler::stackPointerRegister); #endif callPatches.append(CallPatch(differenceBetween(startLabel, cl), fun)); return cl; } + + void finalize(uint8 *ncode) { + JSC::JITCode jc(ncode, size()); + JSC::CodeBlock cb(jc); + JSC::RepatchBuffer repatchBuffer(&cb); + + for (size_t i = 0; i < callPatches.length(); i++) { + JSC::MacroAssemblerCodePtr cp(ncode + callPatches[i].distance); + repatchBuffer.relink(JSC::CodeLocationCall(cp), callPatches[i].fun); + } + } }; } /* namespace js */ } /* namespace mjit */ #endif +
--- a/js/src/methodjit/Compiler.cpp +++ b/js/src/methodjit/Compiler.cpp @@ -34,17 +34,18 @@ * 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 "Compiler.h" -#include "assembler/assembler/LinkBuffer.h" +#include "StubCalls.h" +#include "assembler/jit/ExecutableAllocator.h" #include "jsautooplen.h" using namespace js; using namespace js::mjit; #if defined(JS_METHODJIT_SPEW) static const char *OpcodeNames[] = { @@ -120,29 +121,43 @@ mjit::Compiler::Compile() CHECK_STATUS(finishThisUp()); #ifdef JS_METHODJIT_SPEW prof.stop(); JaegerSpew(JSpew_Prof, "compilation took %d us\n", prof.time_us()); #endif JaegerSpew(JSpew_Scripts, "successfully compiled (code \"%p\") (size \"%ld\")\n", - (void*)script->ncode, 0); //cr.m_size); + (void*)script->ncode, masm.size() + stubcc.size()); //cr.m_size); return Compile_Okay; } #undef CHECK_STATUS mjit::Compiler::~Compiler() { cx->free(jumpMap); } CompileStatus +mjit::TryCompile(JSContext *cx, JSScript *script, JSFunction *fun, JSObject *scopeChain) +{ + Compiler cc(cx, script, fun, scopeChain); + + JS_ASSERT(!script->ncode); + + CompileStatus status = cc.Compile(); + if (status != Compile_Okay) + script->ncode = JS_UNJITTABLE_METHOD; + + return status; +} + +CompileStatus mjit::Compiler::generatePrologue() { #ifdef JS_CPU_ARM /* * Unlike x86/x64, the return address is not pushed on the stack. To * compensate, we store the LR back into the stack on entry. This means * it's really done twice when called via the trampoline, but it's only * one instruction so probably not a big deal. @@ -152,16 +167,55 @@ mjit::Compiler::generatePrologue() * except for inline calls. */ masm.storePtr(ARMRegisters::lr, FrameAddress(offsetof(VMFrame, scriptedReturn))); #endif return Compile_Okay; } +CompileStatus +mjit::Compiler::generateEpilogue() +{ + return Compile_Okay; +} + +CompileStatus +mjit::Compiler::finishThisUp() +{ + for (size_t i = 0; i < branchPatches.length(); i++) { + Label label = labelOf(branchPatches[i].pc); + branchPatches[i].jump.linkTo(label, &masm); + } + + JSC::ExecutablePool *execPool = getExecPool(masm.size() + stubcc.size()); + if (!execPool) + return Compile_Abort; + + uint8 *result = (uint8 *)execPool->alloc(masm.size() + stubcc.size()); + JSC::ExecutableAllocator::makeWritable(result, masm.size() + stubcc.size()); + memcpy(result, masm.buffer(), masm.size()); + memcpy(result + masm.size(), stubcc.buffer(), stubcc.size()); + + /* Patch up stub calls. */ + masm.finalize(result); + stubcc.finalize(result + masm.size()); + + JSC::ExecutableAllocator::makeExecutable(result, masm.size() + stubcc.size()); + JSC::ExecutableAllocator::cacheFlush(result, masm.size() + stubcc.size()); + + script->ncode = result; +#ifdef DEBUG + script->jitLength = masm.size() + stubcc.size(); +#endif + script->execPool = execPool; + + return Compile_Okay; +} + #define BEGIN_CASE(name) case name: #define END_CASE(name) \ JS_BEGIN_MACRO \ PC += name##_LENGTH; \ JS_END_MACRO; \ break; CompileStatus @@ -240,24 +294,31 @@ mjit::Compiler::generateMethod() BEGIN_CASE(JSOP_UINT16) frame.push(Value(Int32Tag((int32_t) GET_UINT16(PC)))); END_CASE(JSOP_UINT16) BEGIN_CASE(JSOP_BINDNAME) jsop_bindname(fullAtomIndex(PC)); END_CASE(JSOP_BINDNAME) + BEGIN_CASE(JSOP_SETNAME) + prepareStubCall(); + masm.move(Imm32(fullAtomIndex(PC)), Registers::ArgReg1); + stubCall(stubs::SetName, Uses(2), Defs(1)); + frame.pop(); + END_CASE(JSOP_SETNAME) + BEGIN_CASE(JSOP_UINT24) frame.push(Value(Int32Tag((int32_t) GET_UINT24(PC)))); END_CASE(JSOP_UINT24) BEGIN_CASE(JSOP_STOP) /* Safe point! */ cg.storeJsval(Value(UndefinedTag()), - Address(FrameState::FpReg, + Address(Assembler::FpReg, offsetof(JSStackFrame, rval))); emitReturn(); goto done; END_CASE(JSOP_STOP) BEGIN_CASE(JSOP_INT8) frame.push(Value(Int32Tag(GET_INT8(PC)))); END_CASE(JSOP_INT8) @@ -330,61 +391,16 @@ mjit::Compiler::jumpInScript(Jump j, jsb /* :TODO: OOM failure possible here. */ if (pc < PC) j.linkTo(jumpMap[uint32(pc - script->code)], &masm); else branchPatches.append(BranchPatch(j, pc)); } -CompileStatus -mjit::Compiler::generateEpilogue() -{ - return Compile_Okay; -} - -CompileStatus -mjit::Compiler::finishThisUp() -{ - for (size_t i = 0; i < branchPatches.length(); i++) { - Label label = labelOf(branchPatches[i].pc); - branchPatches[i].jump.linkTo(label, &masm); - } - - JSC::ExecutablePool *execPool = getExecPool(masm.size()); - if (!execPool) - return Compile_Abort; - - JSC::LinkBuffer patchBuffer(&masm, execPool); - JSC::MacroAssemblerCodeRef cr = patchBuffer.finalizeCode(); - - script->ncode = cr.m_code.executableAddress(); -#ifdef DEBUG - script->jitLength = masm.size(); -#endif - script->execPool = cr.m_executablePool; - cr.m_executablePool = NULL; - - return Compile_Okay; -} - -CompileStatus -mjit::TryCompile(JSContext *cx, JSScript *script, JSFunction *fun, JSObject *scopeChain) -{ - Compiler cc(cx, script, fun, scopeChain); - - JS_ASSERT(!script->ncode); - - CompileStatus status = cc.Compile(); - if (status != Compile_Okay) - script->ncode = JS_UNJITTABLE_METHOD; - - return status; -} - void mjit::Compiler::jsop_setglobal(uint32 index) { JS_ASSERT(globalObj); uint32 slot = script->getGlobalSlot(index); FrameEntry *fe = frame.peek(-1); bool popped = PC[JSOP_SETGLOBAL_LENGTH] == JSOP_POP; @@ -425,8 +441,28 @@ void mjit::Compiler::emitReturn() { #if defined(JS_CPU_ARM) masm.loadPtr(FrameAddress(offsetof(VMFrame, scriptedReturn)), ARMRegisters::lr); #endif masm.ret(); } +void +mjit::Compiler::prepareStubCall() +{ + JaegerSpew(JSpew_Insns, " ---- SLOW CALL, SYNCING FRAME ---- \n"); + frame.sync(); + JaegerSpew(JSpew_Insns, " ---- KILLING TEMP REGS ---- \n"); + frame.killSyncedRegs(Registers::TempRegs); + JaegerSpew(JSpew_Insns, " ---- FRAME SYNCING DONE ---- \n"); +} + +JSC::MacroAssembler::Call +mjit::Compiler::stubCall(void *ptr, Uses uses, Defs defs) +{ + frame.forget(uses.nuses); + JaegerSpew(JSpew_Insns, " ---- CALLING STUB ---- \n"); + Call cl = masm.stubCall(ptr, PC, frame.stackDepth() + script->nfixed); + JaegerSpew(JSpew_Insns, " ---- END SLOW CALL ---- \n"); + return cl; +} +
--- a/js/src/methodjit/Compiler.h +++ b/js/src/methodjit/Compiler.h @@ -48,30 +48,46 @@ #include "StubCompiler.h" namespace js { namespace mjit { class Compiler { typedef JSC::MacroAssembler::Label Label; + typedef JSC::MacroAssembler::Imm32 Imm32; typedef JSC::MacroAssembler::ImmPtr ImmPtr; typedef JSC::MacroAssembler::RegisterID RegisterID; typedef JSC::MacroAssembler::Address Address; typedef JSC::MacroAssembler::Jump Jump; + typedef JSC::MacroAssembler::Call Call; struct BranchPatch { BranchPatch(const Jump &j, jsbytecode *pc) : jump(j), pc(pc) { } Jump jump; jsbytecode *pc; }; + struct Uses { + Uses(uint32 nuses) + : nuses(nuses) + { } + uint32 nuses; + }; + + struct Defs { + Defs(uint32 ndefs) + : ndefs(ndefs) + { } + uint32 ndefs; + }; + JSContext *cx; JSScript *script; JSObject *scopeChain; JSObject *globalObj; JSFunction *fun; BytecodeAnalyzer analysis; Label *jumpMap; jsbytecode *PC; @@ -87,16 +103,17 @@ class Compiler enum { LengthAtomIndex = uint32(-2) }; Compiler(JSContext *cx, JSScript *script, JSFunction *fun, JSObject *scopeChain); ~Compiler(); CompileStatus Compile(); jsbytecode *getPC() { return PC; } + Label getLabel() { return masm.label(); } private: CompileStatus generatePrologue(); CompileStatus generateMethod(); CompileStatus generateEpilogue(); CompileStatus finishThisUp(); /* Non-emitting helpers. */ @@ -105,14 +122,27 @@ class Compiler void jumpInScript(Jump j, jsbytecode *pc); JSC::ExecutablePool *getExecPool(size_t size); /* Opcode handlers. */ void jsop_bindname(uint32 index); void jsop_setglobal(uint32 index); void jsop_getglobal(uint32 index); void emitReturn(); + +#define STUB_CALL_TYPE(type) \ + Call stubCall(type stub, Uses uses, Defs defs) { \ + return stubCall(JS_FUNC_TO_DATA_PTR(void *, stub), uses, defs); \ + } + + STUB_CALL_TYPE(JSObjStub); + STUB_CALL_TYPE(VoidStubUInt32); + +#undef STUB_CALL_TYPE + void prepareStubCall(); + Call stubCall(void *ptr, Uses uses, Defs defs); }; } /* namespace js */ } /* namespace mjit */ #endif +
--- a/js/src/methodjit/MachineRegs.h +++ b/js/src/methodjit/MachineRegs.h @@ -142,16 +142,26 @@ struct Registers { Registers() : freeMask(AvailRegs) { } Registers(uint32 freeMask) : freeMask(freeMask) { } + Registers(const Registers &other) + : freeMask(other.freeMask) + { } + + Registers & operator =(const Registers &other) + { + freeMask = other.freeMask; + return *this; + } + void reset() { freeMask = AvailRegs; } bool anyRegsFree() { return !!freeMask; }
--- a/js/src/methodjit/RematInfo.h +++ b/js/src/methodjit/RematInfo.h @@ -69,16 +69,17 @@ struct RematInfo { synced_ = false; } void setMemory() { synced_ = true; location_ = PhysLoc_Memory; } + void setSynced() { synced_ = true; } void setConstant() { location_ = PhysLoc_Constant; } bool isCopy() { return location_ == PhysLoc_Copy; } bool isConstant() { return location_ == PhysLoc_Constant; } bool inRegister() { return location_ == PhysLoc_Register; } bool inMemory() { return location_ == PhysLoc_Memory; } RegisterID reg() { return reg_; }
--- a/js/src/methodjit/StubCalls.cpp +++ b/js/src/methodjit/StubCalls.cpp @@ -288,8 +288,239 @@ js_InternalThrow(VMFrame &f) *f.oldRegs = f.regs; f.cx->setCurrentRegs(f.oldRegs); return NULL; } return cx->fp->script->pcToNative(pc); } +#define NATIVE_SET(cx,obj,sprop,entry,vp) \ + JS_BEGIN_MACRO \ + if (sprop->hasDefaultSetter() && \ + (sprop)->slot != SPROP_INVALID_SLOT && \ + !obj->scope()->brandedOrHasMethodBarrier()) { \ + /* Fast path for, e.g., plain Object instance properties. */ \ + obj->setSlot(sprop->slot, *vp); \ + } else { \ + if (!js_NativeSet(cx, obj, sprop, false, vp)) \ + THROW(); \ + } \ + JS_END_MACRO + +static inline JSObject * +ValueToObject(JSContext *cx, Value *vp) +{ + if (vp->isObject()) + return &vp->asObject(); + if (!js_ValueToNonNullObject(cx, *vp, vp)) + return NULL; + return &vp->asObject(); +} + +void JS_FASTCALL +mjit::stubs::SetName(VMFrame &f, uint32 index) +{ + JSContext *cx = f.cx; + + Value &rref = f.regs.sp[-1]; + Value &lref = f.regs.sp[-2]; + JSObject *obj = ValueToObject(cx, &lref); + if (!obj) + THROW(); + + do { + PropertyCache *cache = &JS_PROPERTY_CACHE(cx); + + /* + * Probe the property cache, specializing for two important + * set-property cases. First: + * + * function f(a, b, c) { + * var o = {p:a, q:b, r:c}; + * return o; + * } + * + * or similar real-world cases, which evolve a newborn native + * object predicatably through some bounded number of property + * additions. And second: + * + * o.p = x; + * + * in a frequently executed method or loop body, where p will + * (possibly after the first iteration) always exist in native + * object o. + */ + PropertyCacheEntry *entry; + JSObject *obj2; + JSAtom *atom; + if (cache->testForSet(cx, f.regs.pc, obj, &entry, &obj2, &atom)) { + /* + * Fast property cache hit, only partially confirmed by + * testForSet. We know that the entry applies to regs.pc and + * that obj's shape matches. + * + * The entry predicts either a new property to be added + * directly to obj by this set, or on an existing "own" + * property, or on a prototype property that has a setter. + */ + JS_ASSERT(entry->vword.isSprop()); + JSScopeProperty *sprop = entry->vword.toSprop(); + JS_ASSERT_IF(sprop->isDataDescriptor(), sprop->writable()); + JS_ASSERT_IF(sprop->hasSlot(), entry->vcapTag() == 0); + + JSScope *scope = obj->scope(); + JS_ASSERT(!scope->sealed()); + + /* + * Fastest path: check whether the cached sprop is already + * in scope and call NATIVE_SET and break to get out of the + * do-while(0). But we can call NATIVE_SET only if obj owns + * scope or sprop is shared. + */ + bool checkForAdd; + if (!sprop->hasSlot()) { + if (entry->vcapTag() == 0 || + ((obj2 = obj->getProto()) && + obj2->isNative() && + obj2->shape() == entry->vshape())) { + goto fast_set_propcache_hit; + } + + /* The cache entry doesn't apply. vshape mismatch. */ + checkForAdd = false; + } else if (!scope->isSharedEmpty()) { + if (sprop == scope->lastProperty() || scope->hasProperty(sprop)) { + fast_set_propcache_hit: + PCMETER(cache->pchits++); + PCMETER(cache->setpchits++); + NATIVE_SET(cx, obj, sprop, entry, &rref); + break; + } + checkForAdd = sprop->hasSlot() && sprop->parent == scope->lastProperty(); + } else { + /* + * We check that cx own obj here and will continue to + * own it after js_GetMutableScope returns so we can + * continue to skip JS_UNLOCK_OBJ calls. + */ + JS_ASSERT(CX_OWNS_OBJECT_TITLE(cx, obj)); + scope = js_GetMutableScope(cx, obj); + JS_ASSERT(CX_OWNS_OBJECT_TITLE(cx, obj)); + if (!scope) + THROW(); + checkForAdd = !sprop->parent; + } + + uint32 slot; + if (checkForAdd && + entry->vshape() == cx->runtime->protoHazardShape && + sprop->hasDefaultSetter() && + (slot = sprop->slot) == scope->freeslot) { + /* + * Fast path: adding a plain old property that was once + * at the frontier of the property tree, whose slot is + * next to claim among the allocated slots in obj, + * where scope->table has not been created yet. + * + * We may want to remove hazard conditions above and + * inline compensation code here, depending on + * real-world workloads. + */ + PCMETER(cache->pchits++); + PCMETER(cache->addpchits++); + + /* + * Beware classes such as Function that use the + * reserveSlots hook to allocate a number of reserved + * slots that may vary with obj. + */ + if (slot < obj->numSlots() && + !obj->getClass()->reserveSlots) { + ++scope->freeslot; + } else { + if (!js_AllocSlot(cx, obj, &slot)) + THROW(); + } + + /* + * If this obj's number of reserved slots differed, or + * if something created a hash table for scope, we must + * pay the price of JSScope::putProperty. + * + * (A reserveSlots hook can cause scopes of the same + * shape to have different freeslot values. This is + * what causes the slot != sprop->slot case. See + * js_GetMutableScope.) + */ + if (slot != sprop->slot || scope->table) { + JSScopeProperty *sprop2 = + scope->putProperty(cx, sprop->id, + sprop->getter(), sprop->setter(), + slot, sprop->attributes(), + sprop->getFlags(), sprop->shortid); + if (!sprop2) { + js_FreeSlot(cx, obj, slot); + THROW(); + } + sprop = sprop2; + } else { + scope->extend(cx, sprop); + } + + /* + * No method change check here because here we are + * adding a new property, not updating an existing + * slot's value that might contain a method of a + * branded scope. + */ + TRACE_2(SetPropHit, entry, sprop); + obj->lockedSetSlot(slot, rref); + + /* + * Purge the property cache of the id we may have just + * shadowed in obj's scope and proto chains. We do this + * after unlocking obj's scope to avoid lock nesting. + */ + js_PurgeScopeChain(cx, obj, sprop->id); + break; + } + PCMETER(cache->setpcmisses++); + atom = NULL; + } else if (!atom) { + /* + * Slower property cache hit, fully confirmed by testForSet (in + * the slow path, via fullTest). + */ + JSScopeProperty *sprop = NULL; + if (obj == obj2) { + sprop = entry->vword.toSprop(); + JS_ASSERT(sprop->writable()); + JS_ASSERT(!obj2->scope()->sealed()); + NATIVE_SET(cx, obj, sprop, entry, &rref); + } + if (sprop) + break; + } + + if (!atom) + atom = f.fp->script->getAtom(index); + jsid id = ATOM_TO_JSID(atom); + if (entry && JS_LIKELY(obj->map->ops->setProperty == js_SetProperty)) { + uintN defineHow; + JSOp op = JSOp(*f.regs.pc); + if (op == JSOP_SETMETHOD) + defineHow = JSDNP_CACHE_RESULT | JSDNP_SET_METHOD; + else if (op == JSOP_SETNAME) + defineHow = JSDNP_CACHE_RESULT | JSDNP_UNQUALIFIED; + else + defineHow = JSDNP_CACHE_RESULT; + if (!js_SetPropertyHelper(cx, obj, id, defineHow, &rref)) + THROW(); + } else { + if (!obj->setProperty(cx, id, &rref)) + THROW(); + } + } while (0); + + f.regs.sp[-2] = f.regs.sp[-1]; +} +
--- a/js/src/methodjit/StubCalls.h +++ b/js/src/methodjit/StubCalls.h @@ -44,16 +44,17 @@ #include "MethodJIT.h" namespace js { namespace mjit { namespace stubs { void * JS_FASTCALL Return(VMFrame &f); JSObject * JS_FASTCALL BindName(VMFrame &f); +void JS_FASTCALL SetName(VMFrame &f, uint32 index); }}} /* namespace stubs,mjit,js */ extern "C" void * js_InternalThrow(js::VMFrame &f); #endif /* jslogic_h__ */
--- a/js/src/methodjit/StubCompiler.cpp +++ b/js/src/methodjit/StubCompiler.cpp @@ -52,84 +52,42 @@ StubCompiler::StubCompiler(JSContext *cx void StubCompiler::linkExit(Jump j) { /* :TODO: oom check */ exits.append(ExitPatch(j, masm.label())); } void -StubCompiler::syncAndSpill() +StubCompiler::leave() { - frame.sync(masm); + JaegerSpew(JSpew_Insns, " ---- BEGIN STUB SPILL CODE ---- \n"); + frame.sync(masm, snapshot); + JaegerSpew(JSpew_Insns, " ---- END STUB SPILL CODE ---- \n"); + JaegerSpew(JSpew_Insns, " ---- BEGIN STUB CALL CODE ---- \n"); } -void * -StubCompiler::getCallTarget(void *fun) +void +StubCompiler::rejoin(uint32 invalidationDepth) { -#ifdef JS_CPU_ARM - /* - * Insert a veneer for ARM to allow it to catch exceptions. There is no - * reliable way to determine the location of the return address on the - * stack, so it cannot be hijacked. - * - * :TODO: It wouldn't surprise me if GCC always pushes LR first. In that - * case, this looks like the x86-style call, and we can hijack the stack - * slot accordingly, thus avoiding the cost of a veneer. This should be - * investigated. - */ - - void *pfun = JS_FUNC_TO_DATA_PTR(void *, JaegerStubVeneer); - - /* - * We put the real target address into IP, as this won't conflict with - * the EABI argument-passing mechanism. Technically, this isn't ABI- - * compliant. - */ - masm.move(Imm32(intptr_t(fun)), ARMRegisters::ip); -#else - /* - * Architectures that push the return address to an easily-determined - * location on the stack can hijack C++'s return mechanism by overwriting - * that address, so a veneer is not required. - */ - void *pfun = fun; -#endif - return pfun; + JaegerSpew(JSpew_Insns, " ---- BEGIN STUB RESTORE CODE ---- \n"); + frame.merge(masm, snapshot, invalidationDepth); + JaegerSpew(JSpew_Insns, " ---- END STUB RESTORE CODE ---- \n"); } typedef JSC::MacroAssembler::RegisterID RegisterID; typedef JSC::MacroAssembler::ImmPtr ImmPtr; typedef JSC::MacroAssembler::Imm32 Imm32; -/* Need a temp reg that is not ArgReg1. */ -#if defined(JS_CPU_X86) || defined(JS_CPU_ARM) -static const RegisterID ClobberInCall = JSC::X86Registers::ecx; -#elif defined(JS_CPU_ARM) -static const RegisterID ClobberInCall = JSC::ARMRegisters::r2; -#endif - -JS_STATIC_ASSERT(ClobberInCall != Registers::ArgReg1); - JSC::MacroAssembler::Call -StubCompiler::scall(void *ptr) +StubCompiler::stubCall(void *ptr) { - void *pfun = getCallTarget(ptr); - - /* PC -> regs->pc :( */ - masm.storePtr(ImmPtr(cc.getPC()), - FrameAddress(offsetof(VMFrame, regs) + offsetof(JSFrameRegs, pc))); - - /* sp = fp + slots() + stackDepth */ - masm.addPtr(Imm32(sizeof(JSStackFrame) + - (frame.stackDepth() + script->nfixed) * sizeof(jsval)), - FrameState::FpReg, ClobberInCall); - - /* regs->sp = sp */ - masm.storePtr(ClobberInCall, - FrameAddress(offsetof(VMFrame, regs) + offsetof(JSFrameRegs, sp))); - - /* VMFrame -> ArgReg0 */ - masm.move(Assembler::stackPointerRegister, Registers::ArgReg0); - - return masm.call(pfun); + Call cl = masm.stubCall(ptr, cc.getPC(), frame.stackDepth() + script->nfixed); + JaegerSpew(JSpew_Insns, " ---- END STUB CALL CODE ---- \n"); + return cl; } +void +StubCompiler::finalize(uint8* ncode) +{ + masm.finalize(ncode); +} +
--- a/js/src/methodjit/StubCompiler.h +++ b/js/src/methodjit/StubCompiler.h @@ -47,20 +47,16 @@ #include "methodjit/nunbox/Assembler.h" #include "CodeGenIncludes.h" namespace js { namespace mjit { class Compiler; -struct StubCallInfo { - uint32 numSpills; -}; - class StubCompiler { typedef JSC::MacroAssembler::Call Call; typedef JSC::MacroAssembler::Jump Jump; typedef JSC::MacroAssembler::Label Label; struct ExitPatch { ExitPatch(Jump from, Label to) @@ -72,27 +68,58 @@ class StubCompiler }; JSContext *cx; Compiler &cc; FrameState &frame; JSScript *script; Assembler masm; Vector<ExitPatch, 64, SystemAllocPolicy> exits; + RegSnapshot snapshot; public: StubCompiler(JSContext *cx, mjit::Compiler &cc, FrameState &frame, JSScript *script); - void linkExit(Jump j); - void syncAndSpill(); - Call call(JSObjStub stub) { - return scall(JS_FUNC_TO_DATA_PTR(void *, stub)); + + size_t size() { + return masm.size(); + } + + uint8 *buffer() { + return masm.buffer(); + } + +#define STUB_CALL_TYPE(type) \ + Call call(type stub) { \ + return stubCall(JS_FUNC_TO_DATA_PTR(void *, stub)); \ } + STUB_CALL_TYPE(JSObjStub); + +#undef STUB_CALL_TYPE + + /* Patches a jump target into the slow path. */ + void linkExit(Jump j); + + /* + * Emits code into the slow-path stream that sync all outstanding state + * to memory. + */ + void leave(); + + /* + * Rejoins slow-path code back to the fast-path. The invalidation param + * specifies how many stack slots below sp must not be reloaded from + * registers. + */ + void rejoin(uint32 invalidationDepth); + + /* Finish all native code patching. */ + void finalize(uint8 *ncode); + private: - Call scall(void *ptr); - void *getCallTarget(void *fun); + Call stubCall(void *ptr); }; } /* namepsace mjit */ } /* namespace js */ #endif /* jsstub_compiler_h__ */
--- a/js/src/methodjit/nunbox/Assembler.h +++ b/js/src/methodjit/nunbox/Assembler.h @@ -42,15 +42,46 @@ #include "methodjit/BaseAssembler.h" namespace js { namespace mjit { class Assembler : public BaseAssembler { + static const uint32 PAYLOAD_OFFSET = 0; + static const uint32 TAG_OFFSET = 4; + public: + Address payloadOf(Address address) { + return address; + } + + void loadTypeTag(Address address, RegisterID reg) { + load32(Address(address.base, address.offset + TAG_OFFSET), reg); + } + + void storeTypeTag(Imm32 imm, Address address) { + store32(imm, Address(address.base, address.offset + TAG_OFFSET)); + } + + void storeTypeTag(RegisterID reg, Address address) { + store32(reg, Address(address.base, address.offset + TAG_OFFSET)); + } + + void loadData32(Address address, RegisterID reg) { + load32(Address(address.base, address.offset + PAYLOAD_OFFSET), reg); + } + + void storeData32(Imm32 imm, Address address) { + store32(imm, Address(address.base, address.offset + PAYLOAD_OFFSET)); + } + + void storeData32(RegisterID reg, Address address) { + store32(reg, Address(address.base, address.offset + PAYLOAD_OFFSET)); + } }; } /* namespace js */ } /* namespace mjit */ #endif +
--- a/js/src/methodjit/nunbox/FastOps.cpp +++ b/js/src/methodjit/nunbox/FastOps.cpp @@ -41,31 +41,27 @@ #include "methodjit/Compiler.h" #include "methodjit/StubCalls.h" #include "jsautooplen.h" using namespace js; using namespace js::mjit; -static const uint32 TAG_OFFSET = 4; -static const uint32 PAYLOAD_OFFSET = 0; - void mjit::Compiler::jsop_bindname(uint32 index) { RegisterID reg = frame.allocReg(); - masm.loadPtr(Address(FrameState::FpReg, offsetof(JSStackFrame, scopeChain)), reg); + masm.loadPtr(Address(Assembler::FpReg, offsetof(JSStackFrame, scopeChain)), reg); Address address(reg, offsetof(JSObject, fslots) + JSSLOT_PARENT * sizeof(jsval)); - masm.load32(Address(address.base, address.offset + PAYLOAD_OFFSET), reg); - Jump j = masm.branchTestPtr(Assembler::Zero, reg, reg); + Jump j = masm.branchPtr(Assembler::NotEqual, masm.payloadOf(address), ImmPtr(0)); - { - stubcc.linkExit(j); - stubcc.syncAndSpill(); - stubcc.call(stubs::BindName); - } + stubcc.linkExit(j); + stubcc.leave(); + stubcc.call(stubs::BindName); frame.pushObject(reg); + + stubcc.rejoin(1); }
--- a/js/src/methodjit/nunbox/FrameEntry.h +++ b/js/src/methodjit/nunbox/FrameEntry.h @@ -67,21 +67,22 @@ class FrameEntry return Valueify(v_.asBits); } bool isTypeConstant() { return type.isConstant(); } uint32 getTypeTag() { -#if 0 - return v_.mask; -#else return v_.s.mask32; -#endif + } + + uint32 getPayload32() { + JS_ASSERT(!Valueify(v_.asBits).isDouble()); + return v_.s.payload.u32; } uint32 copyOf() { JS_ASSERT(type.isCopy() || data.isCopy()); return index_; } private:
--- a/js/src/methodjit/nunbox/FrameState.cpp +++ b/js/src/methodjit/nunbox/FrameState.cpp @@ -102,61 +102,133 @@ FrameState::invalidate(FrameEntry *fe) if (!fe->type.synced()) { JS_NOT_REACHED("wat"); } if (!fe->data.synced()) { JS_NOT_REACHED("wat2"); } fe->type.setMemory(); fe->data.setMemory(); - fe->copies = 0; +} + +void +FrameState::reset(FrameEntry *fe) +{ + fe->type.setMemory(); + fe->data.setMemory(); } void FrameState::flush() { for (FrameEntry *fe = base; fe < sp; fe++) invalidate(fe); } void -FrameState::syncRegister(Assembler &masm, RegisterID reg, const RegState &state) const +FrameState::killSyncedRegs(uint32 mask) { - JS_NOT_REACHED("bleh"); + /* Subtract any regs we haven't allocated. */ + Registers regs(mask & ~(regalloc.freeMask)); + + while (regs.anyRegsFree()) { + RegisterID reg = regs.allocReg(); + if (!regstate[reg].tracked) + continue; + regstate[reg].tracked = false; + regalloc.freeReg(reg); + FrameEntry &fe = base[regstate[reg].index]; + RematInfo *mat; + if (regstate[reg].part == RegState::Part_Type) + mat = &fe.type; + else + mat = &fe.data; + JS_ASSERT(mat->inRegister() && mat->reg() == reg); + JS_ASSERT(mat->synced()); + mat->setMemory(); + } } void FrameState::syncType(FrameEntry *fe, Assembler &masm) const { + JS_ASSERT(!fe->type.synced() && !fe->type.inMemory()); + if (fe->type.isConstant()) + masm.storeTypeTag(Imm32(fe->getTypeTag()), addressOf(fe)); + else + masm.storeTypeTag(fe->type.reg(), addressOf(fe)); + fe->type.setSynced(); } void FrameState::syncData(FrameEntry *fe, Assembler &masm) const { + JS_ASSERT(!fe->data.synced() && !fe->data.inMemory()); + if (fe->data.isConstant()) + masm.storeData32(Imm32(fe->getPayload32()), addressOf(fe)); + else + masm.storeData32(fe->data.reg(), addressOf(fe)); + fe->data.setSynced(); } void -FrameState::sync(Assembler &masm) const +FrameState::sync(Assembler &masm, RegSnapshot &snapshot) const { for (FrameEntry *fe = base; fe < sp; fe++) { if (fe->type.needsSync()) syncType(fe, masm); if (fe->data.needsSync()) syncData(fe, masm); } + + JS_STATIC_ASSERT(sizeof(snapshot.regs) == sizeof(regstate)); + JS_STATIC_ASSERT(sizeof(RegState) == sizeof(uint32)); + memcpy(snapshot.regs, regstate, sizeof(regstate)); + snapshot.alloc = regalloc; } void -FrameState::restoreTempRegs(Assembler &masm) const +FrameState::merge(Assembler &masm, const RegSnapshot &snapshot, uint32 invalidationDepth) const { -#if 0 - /* Get a mask of all allocated registers that must be synced. */ - Registers temps = regalloc.freeMask & Registers::TempRegs; + uint32 threshold = uint32(sp - base) - invalidationDepth; + + /* + * We must take care not to accidentally clobber registers while merging + * state. For example, if slot entry #1 has moved from EAX to EDX, and + * slot entry #2 has moved from EDX to EAX, then the transition cannot + * be completed with: + * mov eax, edx + * mov edx, eax + * + * This case doesn't need to be super fast, but we do want to detect it + * quickly. To do this, we create a register allocator with the snapshot's + * used registers, and incrementally free them. + */ + Registers depends(snapshot.alloc); + + for (uint32 i = 0; i < MacroAssembler::TotalRegisters; i++) { + if (!regstate[i].tracked) + continue; - while (temps.anyRegsFree()) { - RegisterID reg = temps.allocReg(); - if (!regstate[reg].tracked) - continue; - syncRegister(masm, reg, regstate[reg]); + if (regstate[i].index >= threshold) { + /* + * If the register is within the invalidation threshold, do a + * normal load no matter what. This is guaranteed to be safe + * because nothing above the invalidation threshold will yet + * be in a register. + */ + FrameEntry *fe = &base[regstate[i].index]; + Address address = addressOf(fe); + RegisterID reg = RegisterID(i); + if (regstate[i].part == RegState::Part_Type) { + masm.loadTypeTag(address, reg); + } else { + /* :TODO: assert better */ + JS_ASSERT(fe->isTypeConstant()); + masm.loadData32(address, reg); + } + } else if (regstate[i].index != snapshot.regs[i].index || + regstate[i].part != snapshot.regs[i].part) { + JS_NOT_REACHED("say WAT"); + } } -#endif }
--- a/js/src/methodjit/nunbox/FrameState.h +++ b/js/src/methodjit/nunbox/FrameState.h @@ -47,176 +47,201 @@ namespace js { namespace mjit { enum TypeInfo { Type_Unknown }; -struct FrameAddress : JSC::MacroAssembler::Address -{ - FrameAddress(int32 offset) - : Address(JSC::MacroAssembler::stackPointerRegister, offset) +struct RegState { + enum ValuePart { + Part_Type, + Part_Data + }; + + RegState() + : tracked(false) { } + + RegState(ValuePart part, uint32 index, bool spillable) + : index(index), part(part), spillable(spillable), tracked(true) + { } + + uint16 index : 16; + ValuePart part : 1; + bool spillable : 1; + bool tracked : 1; +}; + +struct RegSnapshot { + RegState regs[JSC::MacroAssembler::TotalRegisters]; + Registers alloc; }; class FrameState { typedef JSC::MacroAssembler::RegisterID RegisterID; typedef JSC::MacroAssembler::Address Address; + typedef JSC::MacroAssembler::Imm32 Imm32; typedef JSC::MacroAssembler MacroAssembler; - struct RegState { - enum ValuePart { - Part_Type, - Part_Data - }; - - uint32 index; - ValuePart part; - bool spillable; - bool tracked; - }; - public: FrameState(JSContext *cx, JSScript *script, Assembler &masm) : cx(cx), script(script), masm(masm), base(NULL) { } ~FrameState(); -#if defined(JS_CPU_X86) || defined(JS_CPU_X64) - static const RegisterID FpReg = JSC::X86Registers::ebx; -#elif defined(JS_CPU_ARM) - static const RegisterID FpReg = JSC::X86Registers::r11; -#endif - public: bool init(uint32 nargs); RegisterID allocReg() { if (!regalloc.anyRegsFree()) evictSomething(); RegisterID reg = regalloc.allocReg(); regstate[reg].tracked = false; return reg; } void freeReg(RegisterID reg) { JS_ASSERT(!regstate[reg].tracked); regalloc.freeReg(reg); } + void incSp() { + sp++; + } + + void decSp() { + sp--; + } + /* * Push a type register, unsycned, with unknown payload. */ void pushUnknownType(RegisterID reg) { sp[0].type.setRegister(reg); sp[0].data.setMemory(); sp[0].copies = 0; - regstate[reg].tracked = true; - regstate[reg].index = uint32(sp - base); - regstate[reg].part = RegState::Part_Type; - sp++; + regstate[reg] = RegState(RegState::Part_Type, uint32(sp - base), true); + incSp(); } void push(const Value &v) { push(Jsvalify(v)); } void push(const jsval &v) { sp[0].setConstant(v); sp[0].copies = 0; - sp++; + incSp(); JS_ASSERT(sp - locals <= script->nslots); } void pushObject(RegisterID reg) { sp[0].type.setConstant(); sp[0].v_.s.mask32 = JSVAL_MASK32_NONFUNOBJ; sp[0].data.setRegister(reg); sp[0].copies = 0; - regstate[reg].tracked = true; - regstate[reg].part = RegState::Part_Data; - regstate[reg].index = uint32(sp - base); - sp++; + regstate[reg] = RegState(RegState::Part_Data, uint32(sp - base), true); + incSp(); } FrameEntry *peek(int32 depth) { JS_ASSERT(depth < 0); JS_ASSERT(sp + depth >= locals + script->nfixed); return &sp[depth]; } void pop() { FrameEntry *vi = peek(-1); if (!vi->isConstant()) { if (vi->type.inRegister()) regalloc.freeReg(vi->type.reg()); if (vi->data.inRegister()) regalloc.freeReg(vi->data.reg()); } - sp--; + decSp(); } Address topOfStack() { - return Address(FpReg, sizeof(JSStackFrame) + - (script->nfixed + stackDepth()) * sizeof(Value)); + return Address(Assembler::FpReg, sizeof(JSStackFrame) + + (script->nfixed + stackDepth()) * sizeof(Value)); } uint32 stackDepth() { return sp - (locals + script->nfixed); } RegisterID tempRegForType(FrameEntry *fe) { JS_ASSERT(!fe->type.isConstant()); if (fe->type.inRegister()) return fe->type.reg(); JS_NOT_REACHED("wat"); } - Address addressOf(FrameEntry *fe) { + Address addressOf(FrameEntry *fe) const { JS_ASSERT(fe >= locals); if (fe >= locals) { - return Address(FpReg, sizeof(JSStackFrame) + - (fe - locals) * script->nfixed); + return Address(Assembler::FpReg, sizeof(JSStackFrame) + + (fe - locals) * sizeof(Value)); } - return Address(FpReg, 0); + return Address(Assembler::FpReg, 0); } void forceStackDepth(uint32 newDepth) { uint32 oldDepth = stackDepth(); FrameEntry *spBase = locals + script->nfixed; sp = spBase + newDepth; if (oldDepth <= newDepth) return; memset(spBase, 0, sizeof(FrameEntry) * (newDepth - oldDepth)); } + void forget(uint32 depth) { + JS_ASSERT(sp - depth >= locals + script->nfixed); + } + + void sync() { + for (FrameEntry *fe = base; fe < sp; fe++) { + if (fe->type.needsSync()) { + syncType(fe, masm); + fe->type.setSynced(); + } + if (fe->data.needsSync()) { + syncData(fe, masm); + fe->type.setSynced(); + } + } + } + void flush(); + void killSyncedRegs(uint32 mask); void assertValidRegisterState(); - void sync(Assembler &masm) const; + void sync(Assembler &masm, RegSnapshot &snapshot) const; + void merge(Assembler &masm, const RegSnapshot &snapshot, uint32 invalidationDepth) const; void restoreTempRegs(Assembler &masm) const; private: void syncType(FrameEntry *fe, Assembler &masm) const; void syncData(FrameEntry *fe, Assembler &masm) const; - void syncRegister(Assembler &masm, RegisterID reg, const RegState &state) const; void evictSomething(); void invalidate(FrameEntry *fe); + void reset(FrameEntry *fe); RegisterID getDataReg(FrameEntry *vi, FrameEntry *backing); private: JSContext *cx; JSScript *script; Assembler &masm; FrameEntry *base; FrameEntry *locals; FrameEntry *args; FrameEntry *sp; + Registers regalloc; RegState regstate[MacroAssembler::TotalRegisters]; }; } /* namespace mjit */ } /* namespace js */ #endif /* jsjaeger_framestate_h__ */