/* -*- 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 * Jan de Mooij * * 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 "jsnum.h" #include "jsbool.h" #include "jsemit.h" #include "jsiter.h" #include "Compiler.h" #include "StubCalls.h" #include "MonoIC.h" #include "PolyIC.h" #include "ICChecker.h" #include "Retcon.h" #include "assembler/jit/ExecutableAllocator.h" #include "assembler/assembler/LinkBuffer.h" #include "FrameState-inl.h" #include "jsobjinlines.h" #include "jsscriptinlines.h" #include "InlineFrameAssembler.h" #include "jscompartment.h" #include "jsobjinlines.h" #include "jsopcodeinlines.h" #include "jshotloop.h" #include "jsautooplen.h" using namespace js; using namespace js::mjit; #if defined(JS_POLYIC) || defined(JS_MONOIC) using namespace js::mjit::ic; #endif #define RETURN_IF_OOM(retval) \ JS_BEGIN_MACRO \ if (oomInVector || masm.oom() || stubcc.masm.oom()) \ return retval; \ JS_END_MACRO #if defined(JS_METHODJIT_SPEW) static const char *OpcodeNames[] = { # define OPDEF(op,val,name,token,length,nuses,ndefs,prec,format) #name, # include "jsopcode.tbl" # undef OPDEF }; #endif mjit::Compiler::Compiler(JSContext *cx, JSStackFrame *fp) : BaseCompiler(cx), fp(fp), script(fp->script()), scopeChain(&fp->scopeChain()), globalObj(scopeChain->getGlobal()), fun(fp->isFunctionFrame() && !fp->isEvalFrame() ? fp->fun() : NULL), isConstructing(fp->isConstructing()), analysis(NULL), jumpMap(NULL), savedTraps(NULL), frame(cx, script, fun, masm), branchPatches(CompilerAllocPolicy(cx, *thisFromCtor())), #if defined JS_MONOIC getGlobalNames(CompilerAllocPolicy(cx, *thisFromCtor())), setGlobalNames(CompilerAllocPolicy(cx, *thisFromCtor())), callICs(CompilerAllocPolicy(cx, *thisFromCtor())), equalityICs(CompilerAllocPolicy(cx, *thisFromCtor())), traceICs(CompilerAllocPolicy(cx, *thisFromCtor())), #endif #if defined JS_POLYIC pics(CompilerAllocPolicy(cx, *thisFromCtor())), getElemICs(CompilerAllocPolicy(cx, *thisFromCtor())), setElemICs(CompilerAllocPolicy(cx, *thisFromCtor())), #endif callPatches(CompilerAllocPolicy(cx, *thisFromCtor())), callSites(CompilerAllocPolicy(cx, *thisFromCtor())), doubleList(CompilerAllocPolicy(cx, *thisFromCtor())), jumpTables(CompilerAllocPolicy(cx, *thisFromCtor())), jumpTableOffsets(CompilerAllocPolicy(cx, *thisFromCtor())), stubcc(cx, *thisFromCtor(), frame, script), debugMode_(cx->compartment->debugMode), #if defined JS_TRACER addTraceHints(cx->traceJitEnabled), #endif oomInVector(false), applyTricks(NoApplyTricks) { } CompileStatus mjit::Compiler::compile() { JS_ASSERT_IF(isConstructing, !script->jitCtor); JS_ASSERT_IF(!isConstructing, !script->jitNormal); JITScript **jit = isConstructing ? &script->jitCtor : &script->jitNormal; void **checkAddr = isConstructing ? &script->jitArityCheckCtor : &script->jitArityCheckNormal; CompileStatus status = performCompilation(jit); if (status == Compile_Okay) { // Global scripts don't have an arity check entry. That's okay, we // just need a pointer so the VM can quickly decide whether this // method can be JIT'd or not. Global scripts cannot be IC'd, since // they have no functions, so there is no danger. *checkAddr = (*jit)->arityCheckEntry ? (*jit)->arityCheckEntry : (*jit)->invokeEntry; } else { *checkAddr = JS_UNJITTABLE_SCRIPT; } return status; } #define CHECK_STATUS(expr) \ JS_BEGIN_MACRO \ CompileStatus status_ = (expr); \ if (status_ != Compile_Okay) { \ if (oomInVector || masm.oom() || stubcc.masm.oom()) \ js_ReportOutOfMemory(cx); \ return status_; \ } \ JS_END_MACRO CompileStatus mjit::Compiler::performCompilation(JITScript **jitp) { JaegerSpew(JSpew_Scripts, "compiling script (file \"%s\") (line \"%d\") (length \"%d\")\n", script->filename, script->lineno, script->length); analyze::Script analysis; PodZero(&analysis); analysis.analyze(cx, script); if (analysis.OOM()) { js_ReportOutOfMemory(cx); return Compile_Error; } if (analysis.failed()) { JaegerSpew(JSpew_Abort, "couldn't analyze bytecode; probably switchX or OOM\n"); return Compile_Abort; } this->analysis = &analysis; if (!frame.init()) { js_ReportOutOfMemory(cx); return Compile_Error; } jumpMap = (Label *)cx->malloc(sizeof(Label) * script->length); if (!jumpMap) { js_ReportOutOfMemory(cx); return Compile_Error; } #ifdef DEBUG for (uint32 i = 0; i < script->length; i++) jumpMap[i] = Label(); #endif #ifdef JS_METHODJIT_SPEW Profiler prof; prof.start(); #endif /* Initialize PC early so stub calls in the prologue can be fallible. */ PC = script->code; #ifdef JS_METHODJIT script->debugMode = debugMode(); #endif for (uint32 i = 0; i < script->nClosedVars; i++) frame.setClosedVar(script->getClosedVar(i)); for (uint32 i = 0; i < script->nClosedArgs; i++) frame.setClosedArg(script->getClosedArg(i)); CHECK_STATUS(generatePrologue()); CHECK_STATUS(generateMethod()); CHECK_STATUS(generateEpilogue()); CHECK_STATUS(finishThisUp(jitp)); #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", (*jitp)->code.m_code.executableAddress(), (*jitp)->code.m_size); return Compile_Okay; } #undef CHECK_STATUS mjit::Compiler::~Compiler() { cx->free(jumpMap); cx->free(savedTraps); } CompileStatus JS_NEVER_INLINE mjit::TryCompile(JSContext *cx, JSStackFrame *fp) { JS_ASSERT(cx->fp() == fp); #if JS_HAS_SHARP_VARS if (fp->script()->hasSharps) return Compile_Abort; #endif // Ensure that constructors have at least one slot. if (fp->isConstructing() && !fp->script()->nslots) fp->script()->nslots++; Compiler cc(cx, fp); return cc.compile(); } bool mjit::Compiler::loadOldTraps(const Vector &sites) { savedTraps = (bool *)cx->calloc(sizeof(bool) * script->length); if (!savedTraps) return false; for (size_t i = 0; i < sites.length(); i++) { const CallSite &site = sites[i]; if (site.isTrap()) savedTraps[site.pcOffset] = true; } return true; } CompileStatus mjit::Compiler::generatePrologue() { invokeLabel = masm.label(); /* * If there is no function, then this can only be called via JaegerShot(), * which expects an existing frame to be initialized like the interpreter. */ if (fun) { Jump j = masm.jump(); /* * Entry point #2: The caller has partially constructed a frame, and * either argc >= nargs or the arity check has corrected the frame. */ invokeLabel = masm.label(); Label fastPath = masm.label(); /* Store this early on so slow paths can access it. */ masm.storePtr(ImmPtr(fun), Address(JSFrameReg, JSStackFrame::offsetOfExec())); { /* * Entry point #3: The caller has partially constructed a frame, * but argc might be != nargs, so an arity check might be called. * * This loops back to entry point #2. */ arityLabel = stubcc.masm.label(); Jump argMatch = stubcc.masm.branch32(Assembler::Equal, JSParamReg_Argc, Imm32(fun->nargs)); stubcc.crossJump(argMatch, fastPath); if (JSParamReg_Argc != Registers::ArgReg1) stubcc.masm.move(JSParamReg_Argc, Registers::ArgReg1); /* Slow path - call the arity check function. Returns new fp. */ stubcc.masm.storePtr(ImmPtr(fun), Address(JSFrameReg, JSStackFrame::offsetOfExec())); stubcc.masm.storePtr(JSFrameReg, FrameAddress(offsetof(VMFrame, regs.fp))); OOL_STUBCALL(stubs::FixupArity); stubcc.masm.move(Registers::ReturnReg, JSFrameReg); stubcc.crossJump(stubcc.masm.jump(), fastPath); } /* * Guard that there is enough stack space. Note we include the size of * a second frame, to ensure we can create a frame from call sites. */ masm.addPtr(Imm32((script->nslots + VALUES_PER_STACK_FRAME * 2) * sizeof(Value)), JSFrameReg, Registers::ReturnReg); Jump stackCheck = masm.branchPtr(Assembler::AboveOrEqual, Registers::ReturnReg, FrameAddress(offsetof(VMFrame, stackLimit))); /* If the stack check fails... */ { stubcc.linkExitDirect(stackCheck, stubcc.masm.label()); OOL_STUBCALL(stubs::HitStackQuota); stubcc.crossJump(stubcc.masm.jump(), masm.label()); } /* * Set locals to undefined, as in initCallFrameLatePrologue. * Skip locals which aren't closed and are known to be defined before used, * :FIXME: bug 604541: write undefined if we might be using the tracer, so it works. */ for (uint32 i = 0; i < script->nfixed; i++) { if (analysis->localHasUseBeforeDef(i) || addTraceHints) { Address local(JSFrameReg, sizeof(JSStackFrame) + i * sizeof(Value)); masm.storeValue(UndefinedValue(), local); } } /* Create the call object. */ if (fun->isHeavyweight()) { prepareStubCall(Uses(0)); INLINE_STUBCALL(stubs::GetCallObject); } j.linkTo(masm.label(), &masm); if (analysis->usesScopeChain() && !fun->isHeavyweight()) { /* * Load the scope chain into the frame if necessary. The scope chain * is always set for global and eval frames, and will have been set by * GetCallObject for heavyweight function frames. */ RegisterID t0 = Registers::ReturnReg; Jump hasScope = masm.branchTest32(Assembler::NonZero, FrameFlagsAddress(), Imm32(JSFRAME_HAS_SCOPECHAIN)); masm.loadPayload(Address(JSFrameReg, JSStackFrame::offsetOfCallee(fun)), t0); masm.loadPtr(Address(t0, offsetof(JSObject, parent)), t0); masm.storePtr(t0, Address(JSFrameReg, JSStackFrame::offsetOfScopeChain())); hasScope.linkTo(masm.label(), &masm); } } if (isConstructing) constructThis(); if (debugMode() || Probes::callTrackingActive(cx)) INLINE_STUBCALL(stubs::EnterScript); return Compile_Okay; } CompileStatus mjit::Compiler::generateEpilogue() { return Compile_Okay; } CompileStatus mjit::Compiler::finishThisUp(JITScript **jitp) { RETURN_IF_OOM(Compile_Error); for (size_t i = 0; i < branchPatches.length(); i++) { Label label = labelOf(branchPatches[i].pc); branchPatches[i].jump.linkTo(label, &masm); } #ifdef JS_CPU_ARM masm.forceFlushConstantPool(); stubcc.masm.forceFlushConstantPool(); #endif JaegerSpew(JSpew_Insns, "## Fast code (masm) size = %u, Slow code (stubcc) size = %u.\n", masm.size(), stubcc.size()); size_t totalSize = masm.size() + stubcc.size() + doubleList.length() * sizeof(double) + jumpTableOffsets.length() * sizeof(void *); JSC::ExecutablePool *execPool = getExecPool(script, totalSize); if (!execPool) { js_ReportOutOfMemory(cx); return Compile_Error; } uint8 *result = (uint8 *)execPool->alloc(totalSize); if (!result) { execPool->release(); js_ReportOutOfMemory(cx); return Compile_Error; } JSC::ExecutableAllocator::makeWritable(result, totalSize); masm.executableCopy(result); stubcc.masm.executableCopy(result + masm.size()); JSC::LinkBuffer fullCode(result, totalSize); JSC::LinkBuffer stubCode(result + masm.size(), stubcc.size()); size_t nNmapLive = 0; for (size_t i = 0; i < script->length; i++) { analyze::Bytecode *opinfo = analysis->maybeCode(i); if (opinfo && opinfo->safePoint) nNmapLive++; } /* Please keep in sync with JITScript::scriptDataSize! */ size_t totalBytes = sizeof(JITScript) + sizeof(NativeMapEntry) * nNmapLive + #if defined JS_MONOIC sizeof(ic::GetGlobalNameIC) * getGlobalNames.length() + sizeof(ic::SetGlobalNameIC) * setGlobalNames.length() + sizeof(ic::CallICInfo) * callICs.length() + sizeof(ic::EqualityICInfo) * equalityICs.length() + sizeof(ic::TraceICInfo) * traceICs.length() + #endif #if defined JS_POLYIC sizeof(ic::PICInfo) * pics.length() + sizeof(ic::GetElementIC) * getElemICs.length() + sizeof(ic::SetElementIC) * setElemICs.length() + #endif sizeof(CallSite) * callSites.length(); uint8 *cursor = (uint8 *)cx->calloc(totalBytes); if (!cursor) { execPool->release(); js_ReportOutOfMemory(cx); return Compile_Error; } JITScript *jit = new(cursor) JITScript; cursor += sizeof(JITScript); jit->code = JSC::MacroAssemblerCodeRef(result, execPool, masm.size() + stubcc.size()); jit->invokeEntry = result; jit->singleStepMode = script->singleStepMode; if (fun) { jit->arityCheckEntry = stubCode.locationOf(arityLabel).executableAddress(); jit->fastEntry = fullCode.locationOf(invokeLabel).executableAddress(); } /* * WARNING: mics(), callICs() et al depend on the ordering of these * variable-length sections. See JITScript's declaration for details. */ /* Build the pc -> ncode mapping. */ NativeMapEntry *jitNmap = (NativeMapEntry *)cursor; jit->nNmapPairs = nNmapLive; cursor += sizeof(NativeMapEntry) * jit->nNmapPairs; size_t ix = 0; if (jit->nNmapPairs > 0) { for (size_t i = 0; i < script->length; i++) { analyze::Bytecode *opinfo = analysis->maybeCode(i); if (opinfo && opinfo->safePoint) { Label L = jumpMap[i]; JS_ASSERT(L.isValid()); jitNmap[ix].bcOff = i; jitNmap[ix].ncode = (uint8 *)(result + masm.distanceOf(L)); ix++; } } } JS_ASSERT(ix == jit->nNmapPairs); #if defined JS_MONOIC ic::GetGlobalNameIC *getGlobalNames_ = (ic::GetGlobalNameIC *)cursor; jit->nGetGlobalNames = getGlobalNames.length(); cursor += sizeof(ic::GetGlobalNameIC) * jit->nGetGlobalNames; for (size_t i = 0; i < jit->nGetGlobalNames; i++) { ic::GetGlobalNameIC &to = getGlobalNames_[i]; GetGlobalNameICInfo &from = getGlobalNames[i]; from.copyTo(to, fullCode, stubCode); int offset = fullCode.locationOf(from.load) - to.fastPathStart; to.loadStoreOffset = offset; JS_ASSERT(to.loadStoreOffset == offset); stubCode.patch(from.addrLabel, &to); } ic::SetGlobalNameIC *setGlobalNames_ = (ic::SetGlobalNameIC *)cursor; jit->nSetGlobalNames = setGlobalNames.length(); cursor += sizeof(ic::SetGlobalNameIC) * jit->nSetGlobalNames; for (size_t i = 0; i < jit->nSetGlobalNames; i++) { ic::SetGlobalNameIC &to = setGlobalNames_[i]; SetGlobalNameICInfo &from = setGlobalNames[i]; from.copyTo(to, fullCode, stubCode); to.slowPathStart = stubCode.locationOf(from.slowPathStart); int offset = fullCode.locationOf(from.store).labelAtOffset(0) - to.fastPathStart; to.loadStoreOffset = offset; JS_ASSERT(to.loadStoreOffset == offset); to.hasExtraStub = 0; to.objConst = from.objConst; to.shapeReg = from.shapeReg; to.objReg = from.objReg; to.vr = from.vr; offset = fullCode.locationOf(from.shapeGuardJump) - to.fastPathStart; to.inlineShapeJump = offset; JS_ASSERT(to.inlineShapeJump == offset); offset = fullCode.locationOf(from.fastPathRejoin) - to.fastPathStart; to.fastRejoinOffset = offset; JS_ASSERT(to.fastRejoinOffset == offset); stubCode.patch(from.addrLabel, &to); } ic::CallICInfo *jitCallICs = (ic::CallICInfo *)cursor; jit->nCallICs = callICs.length(); cursor += sizeof(ic::CallICInfo) * jit->nCallICs; for (size_t i = 0; i < jit->nCallICs; i++) { jitCallICs[i].reset(); jitCallICs[i].funGuard = fullCode.locationOf(callICs[i].funGuard); jitCallICs[i].funJump = fullCode.locationOf(callICs[i].funJump); jitCallICs[i].slowPathStart = stubCode.locationOf(callICs[i].slowPathStart); /* Compute the hot call offset. */ uint32 offset = fullCode.locationOf(callICs[i].hotJump) - fullCode.locationOf(callICs[i].funGuard); jitCallICs[i].hotJumpOffset = offset; JS_ASSERT(jitCallICs[i].hotJumpOffset == offset); /* Compute the join point offset. */ offset = fullCode.locationOf(callICs[i].joinPoint) - fullCode.locationOf(callICs[i].funGuard); jitCallICs[i].joinPointOffset = offset; JS_ASSERT(jitCallICs[i].joinPointOffset == offset); /* Compute the OOL call offset. */ offset = stubCode.locationOf(callICs[i].oolCall) - stubCode.locationOf(callICs[i].slowPathStart); jitCallICs[i].oolCallOffset = offset; JS_ASSERT(jitCallICs[i].oolCallOffset == offset); /* Compute the OOL jump offset. */ offset = stubCode.locationOf(callICs[i].oolJump) - stubCode.locationOf(callICs[i].slowPathStart); jitCallICs[i].oolJumpOffset = offset; JS_ASSERT(jitCallICs[i].oolJumpOffset == offset); /* Compute the start of the OOL IC call. */ offset = stubCode.locationOf(callICs[i].icCall) - stubCode.locationOf(callICs[i].slowPathStart); jitCallICs[i].icCallOffset = offset; JS_ASSERT(jitCallICs[i].icCallOffset == offset); /* Compute the slow join point offset. */ offset = stubCode.locationOf(callICs[i].slowJoinPoint) - stubCode.locationOf(callICs[i].slowPathStart); jitCallICs[i].slowJoinOffset = offset; JS_ASSERT(jitCallICs[i].slowJoinOffset == offset); /* Compute the join point offset for continuing on the hot path. */ offset = stubCode.locationOf(callICs[i].hotPathLabel) - stubCode.locationOf(callICs[i].funGuard); jitCallICs[i].hotPathOffset = offset; JS_ASSERT(jitCallICs[i].hotPathOffset == offset); jitCallICs[i].pc = callICs[i].pc; jitCallICs[i].frameSize = callICs[i].frameSize; jitCallICs[i].funObjReg = callICs[i].funObjReg; jitCallICs[i].funPtrReg = callICs[i].funPtrReg; stubCode.patch(callICs[i].addrLabel1, &jitCallICs[i]); stubCode.patch(callICs[i].addrLabel2, &jitCallICs[i]); } ic::EqualityICInfo *jitEqualityICs = (ic::EqualityICInfo *)cursor; jit->nEqualityICs = equalityICs.length(); cursor += sizeof(ic::EqualityICInfo) * jit->nEqualityICs; for (size_t i = 0; i < jit->nEqualityICs; i++) { uint32 offs = uint32(equalityICs[i].jumpTarget - script->code); JS_ASSERT(jumpMap[offs].isValid()); jitEqualityICs[i].target = fullCode.locationOf(jumpMap[offs]); jitEqualityICs[i].stubEntry = stubCode.locationOf(equalityICs[i].stubEntry); jitEqualityICs[i].stubCall = stubCode.locationOf(equalityICs[i].stubCall); jitEqualityICs[i].stub = equalityICs[i].stub; jitEqualityICs[i].lvr = equalityICs[i].lvr; jitEqualityICs[i].rvr = equalityICs[i].rvr; jitEqualityICs[i].tempReg = equalityICs[i].tempReg; jitEqualityICs[i].cond = equalityICs[i].cond; if (equalityICs[i].jumpToStub.isSet()) jitEqualityICs[i].jumpToStub = fullCode.locationOf(equalityICs[i].jumpToStub.get()); jitEqualityICs[i].fallThrough = fullCode.locationOf(equalityICs[i].fallThrough); stubCode.patch(equalityICs[i].addrLabel, &jitEqualityICs[i]); } ic::TraceICInfo *jitTraceICs = (ic::TraceICInfo *)cursor; jit->nTraceICs = traceICs.length(); cursor += sizeof(ic::TraceICInfo) * jit->nTraceICs; for (size_t i = 0; i < jit->nTraceICs; i++) { jitTraceICs[i].initialized = traceICs[i].initialized; if (!traceICs[i].initialized) continue; uint32 offs = uint32(traceICs[i].jumpTarget - script->code); JS_ASSERT(jumpMap[offs].isValid()); jitTraceICs[i].traceHint = fullCode.locationOf(traceICs[i].traceHint); jitTraceICs[i].jumpTarget = fullCode.locationOf(jumpMap[offs]); jitTraceICs[i].stubEntry = stubCode.locationOf(traceICs[i].stubEntry); jitTraceICs[i].traceData = NULL; #ifdef DEBUG jitTraceICs[i].jumpTargetPC = traceICs[i].jumpTarget; #endif jitTraceICs[i].hasSlowTraceHint = traceICs[i].slowTraceHint.isSet(); if (traceICs[i].slowTraceHint.isSet()) jitTraceICs[i].slowTraceHint = stubCode.locationOf(traceICs[i].slowTraceHint.get()); #ifdef JS_TRACER jitTraceICs[i].loopCounterStart = GetHotloop(cx); #endif jitTraceICs[i].loopCounter = jitTraceICs[i].loopCounterStart - cx->compartment->backEdgeCount(traceICs[i].jumpTarget); stubCode.patch(traceICs[i].addrLabel, &jitTraceICs[i]); } #endif /* JS_MONOIC */ for (size_t i = 0; i < callPatches.length(); i++) { CallPatchInfo &patch = callPatches[i]; if (patch.hasFastNcode) fullCode.patch(patch.fastNcodePatch, fullCode.locationOf(patch.joinPoint)); if (patch.hasSlowNcode) stubCode.patch(patch.slowNcodePatch, fullCode.locationOf(patch.joinPoint)); } #ifdef JS_POLYIC ic::GetElementIC *jitGetElems = (ic::GetElementIC *)cursor; jit->nGetElems = getElemICs.length(); cursor += sizeof(ic::GetElementIC) * jit->nGetElems; for (size_t i = 0; i < jit->nGetElems; i++) { ic::GetElementIC &to = jitGetElems[i]; GetElementICInfo &from = getElemICs[i]; new (&to) ic::GetElementIC(); from.copyTo(to, fullCode, stubCode); to.typeReg = from.typeReg; to.objReg = from.objReg; to.idRemat = from.id; if (from.typeGuard.isSet()) { int inlineTypeGuard = fullCode.locationOf(from.typeGuard.get()) - fullCode.locationOf(from.fastPathStart); to.inlineTypeGuard = inlineTypeGuard; JS_ASSERT(to.inlineTypeGuard == inlineTypeGuard); } int inlineClaspGuard = fullCode.locationOf(from.claspGuard) - fullCode.locationOf(from.fastPathStart); to.inlineClaspGuard = inlineClaspGuard; JS_ASSERT(to.inlineClaspGuard == inlineClaspGuard); stubCode.patch(from.paramAddr, &to); } ic::SetElementIC *jitSetElems = (ic::SetElementIC *)cursor; jit->nSetElems = setElemICs.length(); cursor += sizeof(ic::SetElementIC) * jit->nSetElems; for (size_t i = 0; i < jit->nSetElems; i++) { ic::SetElementIC &to = jitSetElems[i]; SetElementICInfo &from = setElemICs[i]; new (&to) ic::SetElementIC(); from.copyTo(to, fullCode, stubCode); to.strictMode = script->strictModeCode; to.vr = from.vr; to.objReg = from.objReg; to.objRemat = from.objRemat.toInt32(); JS_ASSERT(to.objRemat == from.objRemat.toInt32()); to.hasConstantKey = from.key.isConstant(); if (from.key.isConstant()) to.keyValue = from.key.index(); else to.keyReg = from.key.reg(); int inlineClaspGuard = fullCode.locationOf(from.claspGuard) - fullCode.locationOf(from.fastPathStart); to.inlineClaspGuard = inlineClaspGuard; JS_ASSERT(to.inlineClaspGuard == inlineClaspGuard); int inlineHoleGuard = fullCode.locationOf(from.holeGuard) - fullCode.locationOf(from.fastPathStart); to.inlineHoleGuard = inlineHoleGuard; JS_ASSERT(to.inlineHoleGuard == inlineHoleGuard); CheckIsStubCall(to.slowPathCall.labelAtOffset(0)); to.volatileMask = from.volatileMask; JS_ASSERT(to.volatileMask == from.volatileMask); stubCode.patch(from.paramAddr, &to); } ic::PICInfo *jitPics = (ic::PICInfo *)cursor; jit->nPICs = pics.length(); cursor += sizeof(ic::PICInfo) * jit->nPICs; for (size_t i = 0; i < jit->nPICs; i++) { new (&jitPics[i]) ic::PICInfo(); pics[i].copyTo(jitPics[i], fullCode, stubCode); pics[i].copySimpleMembersTo(jitPics[i]); jitPics[i].shapeGuard = masm.distanceOf(pics[i].shapeGuard) - masm.distanceOf(pics[i].fastPathStart); JS_ASSERT(jitPics[i].shapeGuard == masm.distanceOf(pics[i].shapeGuard) - masm.distanceOf(pics[i].fastPathStart)); jitPics[i].shapeRegHasBaseShape = true; jitPics[i].pc = pics[i].pc; if (pics[i].kind == ic::PICInfo::SET || pics[i].kind == ic::PICInfo::SETMETHOD) { jitPics[i].u.vr = pics[i].vr; } else if (pics[i].kind != ic::PICInfo::NAME) { if (pics[i].hasTypeCheck) { int32 distance = stubcc.masm.distanceOf(pics[i].typeCheck) - stubcc.masm.distanceOf(pics[i].slowPathStart); JS_ASSERT(distance <= 0); jitPics[i].u.get.typeCheckOffset = distance; } } stubCode.patch(pics[i].paramAddr, &jitPics[i]); } #endif /* Link fast and slow paths together. */ stubcc.fixCrossJumps(result, masm.size(), masm.size() + stubcc.size()); /* Patch all double references. */ size_t doubleOffset = masm.size() + stubcc.size(); double *doubleVec = (double *)(result + doubleOffset); for (size_t i = 0; i < doubleList.length(); i++) { DoublePatch &patch = doubleList[i]; doubleVec[i] = patch.d; if (patch.ool) stubCode.patch(patch.label, &doubleVec[i]); else fullCode.patch(patch.label, &doubleVec[i]); } /* Generate jump tables. */ void **jumpVec = (void **)(doubleVec + doubleList.length()); for (size_t i = 0; i < jumpTableOffsets.length(); i++) { uint32 offset = jumpTableOffsets[i]; JS_ASSERT(jumpMap[offset].isValid()); jumpVec[i] = (void *)(result + masm.distanceOf(jumpMap[offset])); } /* Patch jump table references. */ for (size_t i = 0; i < jumpTables.length(); i++) { JumpTable &jumpTable = jumpTables[i]; fullCode.patch(jumpTable.label, &jumpVec[jumpTable.offsetIndex]); } /* Patch all outgoing calls. */ masm.finalize(fullCode); stubcc.masm.finalize(stubCode); JSC::ExecutableAllocator::makeExecutable(result, masm.size() + stubcc.size()); JSC::ExecutableAllocator::cacheFlush(result, masm.size() + stubcc.size()); /* Build the table of call sites. */ CallSite *jitCallSites = (CallSite *)cursor; jit->nCallSites = callSites.length(); cursor += sizeof(CallSite) * jit->nCallSites; for (size_t i = 0; i < jit->nCallSites; i++) { CallSite &to = jitCallSites[i]; InternalCallSite &from = callSites[i]; uint32 codeOffset = from.ool ? masm.size() + from.returnOffset : from.returnOffset; to.initialize(codeOffset, from.pc - script->code, from.id); } JS_ASSERT(size_t(cursor - (uint8*)jit) == totalBytes); *jitp = jit; /* We tolerate a race in the stats. */ cx->runtime->mjitMemoryUsed += totalSize + totalBytes; return Compile_Okay; } class SrcNoteLineScanner { ptrdiff_t offset; jssrcnote *sn; public: SrcNoteLineScanner(jssrcnote *sn) : offset(SN_DELTA(sn)), sn(sn) {} bool firstOpInLine(ptrdiff_t relpc) { while ((offset < relpc) && !SN_IS_TERMINATOR(sn)) { sn = SN_NEXT(sn); offset += SN_DELTA(sn); } while ((offset == relpc) && !SN_IS_TERMINATOR(sn)) { JSSrcNoteType type = (JSSrcNoteType) SN_TYPE(sn); if (type == SRC_SETLINE || type == SRC_NEWLINE) return true; sn = SN_NEXT(sn); offset += SN_DELTA(sn); } return false; } }; #ifdef DEBUG #define SPEW_OPCODE() \ JS_BEGIN_MACRO \ if (IsJaegerSpewChannelActive(JSpew_JSOps)) { \ JaegerSpew(JSpew_JSOps, " %2d ", frame.stackDepth()); \ js_Disassemble1(cx, script, PC, PC - script->code, \ JS_TRUE, stdout); \ } \ JS_END_MACRO; #else #define SPEW_OPCODE() #endif /* DEBUG */ #define BEGIN_CASE(name) case name: #define END_CASE(name) \ JS_BEGIN_MACRO \ PC += name##_LENGTH; \ JS_END_MACRO; \ break; CompileStatus mjit::Compiler::generateMethod() { mjit::AutoScriptRetrapper trapper(cx, script); SrcNoteLineScanner scanner(script->notes()); for (;;) { JSOp op = JSOp(*PC); int trap = stubs::JSTRAP_NONE; if (op == JSOP_TRAP) { if (!trapper.untrap(PC)) return Compile_Error; op = JSOp(*PC); trap |= stubs::JSTRAP_TRAP; } if (script->singleStepMode && scanner.firstOpInLine(PC - script->code)) trap |= stubs::JSTRAP_SINGLESTEP; analyze::Bytecode *opinfo = analysis->maybeCode(PC); if (!opinfo) { if (op == JSOP_STOP) break; if (js_CodeSpec[op].length != -1) PC += js_CodeSpec[op].length; else PC += js_GetVariableBytecodeLength(PC); continue; } frame.setInTryBlock(opinfo->inTryBlock); if (opinfo->jumpTarget || trap) { frame.syncAndForgetEverything(opinfo->stackDepth); opinfo->safePoint = true; } jumpMap[uint32(PC - script->code)] = masm.label(); SPEW_OPCODE(); JS_ASSERT(frame.stackDepth() == opinfo->stackDepth); if (trap) { prepareStubCall(Uses(0)); masm.move(Imm32(trap), Registers::ArgReg1); Call cl = emitStubCall(JS_FUNC_TO_DATA_PTR(void *, stubs::Trap)); InternalCallSite site(masm.callReturnOffset(cl), PC, CallSite::MAGIC_TRAP_ID, true, false); addCallSite(site); } else if (savedTraps && savedTraps[PC - script->code]) { // Normally when we patch return addresses, we have generated the // same exact code at that site. For example, patching a stub call's // return address will resume at the same stub call. // // In the case we're handling here, we could potentially be // recompiling to remove a trap, and therefore we won't generate // a call to the trap. However, we could be re-entering from that // trap. The callsite will be missing, and fixing the stack will // fail! Worse, we can't just put a label here, because on some // platforms the stack needs to be adjusted when returning from // the old trap call. // // To deal with this, we add a small bit of code in the OOL path // that will adjust the stack and jump back into the script. // Note that this uses MAGIC_TRAP_ID, which is necessary for // repatching to detect the callsite as identical to the return // address. // // Unfortunately, this means that if a bytecode is ever trapped, // we will always generate a CallSite (either Trapped or not) for // every debug recompilation of the script thereafter. The reason // is that MAGIC_TRAP_ID callsites always propagate to the next // recompilation. That's okay, and not worth fixing - it's a small // amount of memory. uint32 offset = stubcc.masm.distanceOf(stubcc.masm.label()); if (Assembler::ReturnStackAdjustment) { stubcc.masm.addPtr(Imm32(Assembler::ReturnStackAdjustment), Assembler::stackPointerRegister); } stubcc.crossJump(stubcc.masm.jump(), masm.label()); InternalCallSite site(offset, PC, CallSite::MAGIC_TRAP_ID, false, true); addCallSite(site); } /********************** * BEGIN COMPILER OPS * **********************/ switch (op) { BEGIN_CASE(JSOP_NOP) END_CASE(JSOP_NOP) BEGIN_CASE(JSOP_PUSH) frame.push(UndefinedValue()); END_CASE(JSOP_PUSH) BEGIN_CASE(JSOP_POPV) BEGIN_CASE(JSOP_SETRVAL) { RegisterID reg = frame.allocReg(); masm.load32(FrameFlagsAddress(), reg); masm.or32(Imm32(JSFRAME_HAS_RVAL), reg); masm.store32(reg, FrameFlagsAddress()); frame.freeReg(reg); FrameEntry *fe = frame.peek(-1); frame.storeTo(fe, Address(JSFrameReg, JSStackFrame::offsetOfReturnValue()), true); frame.pop(); } END_CASE(JSOP_POPV) BEGIN_CASE(JSOP_RETURN) emitReturn(frame.peek(-1)); END_CASE(JSOP_RETURN) BEGIN_CASE(JSOP_GOTO) { /* :XXX: this isn't really necessary if we follow the branch. */ frame.syncAndForgetEverything(); Jump j = masm.jump(); if (!jumpAndTrace(j, PC + GET_JUMP_OFFSET(PC))) return Compile_Error; } END_CASE(JSOP_GOTO) BEGIN_CASE(JSOP_IFEQ) BEGIN_CASE(JSOP_IFNE) if (!jsop_ifneq(op, PC + GET_JUMP_OFFSET(PC))) return Compile_Error; END_CASE(JSOP_IFNE) BEGIN_CASE(JSOP_ARGUMENTS) /* * For calls of the form 'f.apply(x, arguments)' we can avoid * creating an args object by having ic::SplatApplyArgs pull * directly from the stack. To do this, we speculate here that * 'apply' actually refers to js_fun_apply. If this is not true, * the slow path in JSOP_FUNAPPLY will create the args object. */ if (canUseApplyTricks()) applyTricks = LazyArgsObj; else jsop_arguments(); frame.pushSynced(); END_CASE(JSOP_ARGUMENTS) BEGIN_CASE(JSOP_FORARG) iterNext(); frame.storeArg(GET_SLOTNO(PC), true); frame.pop(); END_CASE(JSOP_FORARG) BEGIN_CASE(JSOP_FORLOCAL) iterNext(); frame.storeLocal(GET_SLOTNO(PC), true); frame.pop(); END_CASE(JSOP_FORLOCAL) BEGIN_CASE(JSOP_DUP) frame.dup(); END_CASE(JSOP_DUP) BEGIN_CASE(JSOP_DUP2) frame.dup2(); END_CASE(JSOP_DUP2) BEGIN_CASE(JSOP_BITOR) BEGIN_CASE(JSOP_BITXOR) BEGIN_CASE(JSOP_BITAND) jsop_bitop(op); END_CASE(JSOP_BITAND) BEGIN_CASE(JSOP_LT) BEGIN_CASE(JSOP_LE) BEGIN_CASE(JSOP_GT) BEGIN_CASE(JSOP_GE) BEGIN_CASE(JSOP_EQ) BEGIN_CASE(JSOP_NE) { /* Detect fusions. */ jsbytecode *next = &PC[JSOP_GE_LENGTH]; JSOp fused = JSOp(*next); if ((fused != JSOP_IFEQ && fused != JSOP_IFNE) || analysis->jumpTarget(next)) fused = JSOP_NOP; /* Get jump target, if any. */ jsbytecode *target = NULL; if (fused != JSOP_NOP) target = next + GET_JUMP_OFFSET(next); BoolStub stub = NULL; switch (op) { case JSOP_LT: stub = stubs::LessThan; break; case JSOP_LE: stub = stubs::LessEqual; break; case JSOP_GT: stub = stubs::GreaterThan; break; case JSOP_GE: stub = stubs::GreaterEqual; break; case JSOP_EQ: stub = stubs::Equal; break; case JSOP_NE: stub = stubs::NotEqual; break; default: JS_NOT_REACHED("WAT"); break; } FrameEntry *rhs = frame.peek(-1); FrameEntry *lhs = frame.peek(-2); /* Check for easy cases that the parser does not constant fold. */ if (lhs->isConstant() && rhs->isConstant()) { /* Primitives can be trivially constant folded. */ const Value &lv = lhs->getValue(); const Value &rv = rhs->getValue(); if (lv.isPrimitive() && rv.isPrimitive()) { bool result = compareTwoValues(cx, op, lv, rv); frame.pop(); frame.pop(); if (!target) { frame.push(Value(BooleanValue(result))); } else { if (fused == JSOP_IFEQ) result = !result; /* Branch is never taken, don't bother doing anything. */ if (result) { frame.syncAndForgetEverything(); Jump j = masm.jump(); if (!jumpAndTrace(j, target)) return Compile_Error; } } } else { if (!emitStubCmpOp(stub, target, fused)) return Compile_Error; } } else { /* Anything else should go through the fast path generator. */ if (!jsop_relational(op, stub, target, fused)) return Compile_Error; } /* Advance PC manually. */ JS_STATIC_ASSERT(JSOP_LT_LENGTH == JSOP_GE_LENGTH); JS_STATIC_ASSERT(JSOP_LE_LENGTH == JSOP_GE_LENGTH); JS_STATIC_ASSERT(JSOP_GT_LENGTH == JSOP_GE_LENGTH); JS_STATIC_ASSERT(JSOP_EQ_LENGTH == JSOP_GE_LENGTH); JS_STATIC_ASSERT(JSOP_NE_LENGTH == JSOP_GE_LENGTH); PC += JSOP_GE_LENGTH; if (fused != JSOP_NOP) { SPEW_OPCODE(); PC += JSOP_IFNE_LENGTH; } break; } END_CASE(JSOP_GE) BEGIN_CASE(JSOP_LSH) jsop_bitop(op); END_CASE(JSOP_LSH) BEGIN_CASE(JSOP_RSH) jsop_rsh(); END_CASE(JSOP_RSH) BEGIN_CASE(JSOP_URSH) jsop_bitop(op); END_CASE(JSOP_URSH) BEGIN_CASE(JSOP_ADD) jsop_binary(op, stubs::Add); END_CASE(JSOP_ADD) BEGIN_CASE(JSOP_SUB) jsop_binary(op, stubs::Sub); END_CASE(JSOP_SUB) BEGIN_CASE(JSOP_MUL) jsop_binary(op, stubs::Mul); END_CASE(JSOP_MUL) BEGIN_CASE(JSOP_DIV) jsop_binary(op, stubs::Div); END_CASE(JSOP_DIV) BEGIN_CASE(JSOP_MOD) jsop_mod(); END_CASE(JSOP_MOD) BEGIN_CASE(JSOP_NOT) jsop_not(); END_CASE(JSOP_NOT) BEGIN_CASE(JSOP_BITNOT) { FrameEntry *top = frame.peek(-1); if (top->isConstant() && top->getValue().isPrimitive()) { int32_t i; ValueToECMAInt32(cx, top->getValue(), &i); i = ~i; frame.pop(); frame.push(Int32Value(i)); } else { jsop_bitnot(); } } END_CASE(JSOP_BITNOT) BEGIN_CASE(JSOP_NEG) { FrameEntry *top = frame.peek(-1); if (top->isConstant() && top->getValue().isPrimitive()) { double d; ValueToNumber(cx, top->getValue(), &d); d = -d; frame.pop(); frame.push(NumberValue(d)); } else { jsop_neg(); } } END_CASE(JSOP_NEG) BEGIN_CASE(JSOP_POS) jsop_pos(); END_CASE(JSOP_POS) BEGIN_CASE(JSOP_DELNAME) { uint32 index = fullAtomIndex(PC); JSAtom *atom = script->getAtom(index); prepareStubCall(Uses(0)); masm.move(ImmPtr(atom), Registers::ArgReg1); INLINE_STUBCALL(stubs::DelName); frame.pushSynced(); } END_CASE(JSOP_DELNAME) BEGIN_CASE(JSOP_DELPROP) { uint32 index = fullAtomIndex(PC); JSAtom *atom = script->getAtom(index); prepareStubCall(Uses(1)); masm.move(ImmPtr(atom), Registers::ArgReg1); INLINE_STUBCALL(STRICT_VARIANT(stubs::DelProp)); frame.pop(); frame.pushSynced(); } END_CASE(JSOP_DELPROP) BEGIN_CASE(JSOP_DELELEM) prepareStubCall(Uses(2)); INLINE_STUBCALL(STRICT_VARIANT(stubs::DelElem)); frame.popn(2); frame.pushSynced(); END_CASE(JSOP_DELELEM) BEGIN_CASE(JSOP_TYPEOF) BEGIN_CASE(JSOP_TYPEOFEXPR) jsop_typeof(); END_CASE(JSOP_TYPEOF) BEGIN_CASE(JSOP_VOID) frame.pop(); frame.push(UndefinedValue()); END_CASE(JSOP_VOID) BEGIN_CASE(JSOP_INCNAME) if (!jsop_nameinc(op, STRICT_VARIANT(stubs::IncName), fullAtomIndex(PC))) return Compile_Error; break; END_CASE(JSOP_INCNAME) BEGIN_CASE(JSOP_INCGNAME) jsop_gnameinc(op, STRICT_VARIANT(stubs::IncGlobalName), fullAtomIndex(PC)); break; END_CASE(JSOP_INCGNAME) BEGIN_CASE(JSOP_INCPROP) if (!jsop_propinc(op, STRICT_VARIANT(stubs::IncProp), fullAtomIndex(PC))) return Compile_Error; break; END_CASE(JSOP_INCPROP) BEGIN_CASE(JSOP_INCELEM) jsop_eleminc(op, STRICT_VARIANT(stubs::IncElem)); END_CASE(JSOP_INCELEM) BEGIN_CASE(JSOP_DECNAME) if (!jsop_nameinc(op, STRICT_VARIANT(stubs::DecName), fullAtomIndex(PC))) return Compile_Error; break; END_CASE(JSOP_DECNAME) BEGIN_CASE(JSOP_DECGNAME) jsop_gnameinc(op, STRICT_VARIANT(stubs::DecGlobalName), fullAtomIndex(PC)); break; END_CASE(JSOP_DECGNAME) BEGIN_CASE(JSOP_DECPROP) if (!jsop_propinc(op, STRICT_VARIANT(stubs::DecProp), fullAtomIndex(PC))) return Compile_Error; break; END_CASE(JSOP_DECPROP) BEGIN_CASE(JSOP_DECELEM) jsop_eleminc(op, STRICT_VARIANT(stubs::DecElem)); END_CASE(JSOP_DECELEM) BEGIN_CASE(JSOP_NAMEINC) if (!jsop_nameinc(op, STRICT_VARIANT(stubs::NameInc), fullAtomIndex(PC))) return Compile_Error; break; END_CASE(JSOP_NAMEINC) BEGIN_CASE(JSOP_GNAMEINC) jsop_gnameinc(op, STRICT_VARIANT(stubs::GlobalNameInc), fullAtomIndex(PC)); break; END_CASE(JSOP_GNAMEINC) BEGIN_CASE(JSOP_PROPINC) if (!jsop_propinc(op, STRICT_VARIANT(stubs::PropInc), fullAtomIndex(PC))) return Compile_Error; break; END_CASE(JSOP_PROPINC) BEGIN_CASE(JSOP_ELEMINC) jsop_eleminc(op, STRICT_VARIANT(stubs::ElemInc)); END_CASE(JSOP_ELEMINC) BEGIN_CASE(JSOP_NAMEDEC) if (!jsop_nameinc(op, STRICT_VARIANT(stubs::NameDec), fullAtomIndex(PC))) return Compile_Error; break; END_CASE(JSOP_NAMEDEC) BEGIN_CASE(JSOP_GNAMEDEC) jsop_gnameinc(op, STRICT_VARIANT(stubs::GlobalNameDec), fullAtomIndex(PC)); break; END_CASE(JSOP_GNAMEDEC) BEGIN_CASE(JSOP_PROPDEC) if (!jsop_propinc(op, STRICT_VARIANT(stubs::PropDec), fullAtomIndex(PC))) return Compile_Error; break; END_CASE(JSOP_PROPDEC) BEGIN_CASE(JSOP_ELEMDEC) jsop_eleminc(op, STRICT_VARIANT(stubs::ElemDec)); END_CASE(JSOP_ELEMDEC) BEGIN_CASE(JSOP_GETTHISPROP) /* Push thisv onto stack. */ jsop_this(); if (!jsop_getprop(script->getAtom(fullAtomIndex(PC)))) return Compile_Error; END_CASE(JSOP_GETTHISPROP); BEGIN_CASE(JSOP_GETARGPROP) /* Push arg onto stack. */ frame.pushArg(GET_SLOTNO(PC)); if (!jsop_getprop(script->getAtom(fullAtomIndex(&PC[ARGNO_LEN])))) return Compile_Error; END_CASE(JSOP_GETARGPROP) BEGIN_CASE(JSOP_GETLOCALPROP) frame.pushLocal(GET_SLOTNO(PC)); if (!jsop_getprop(script->getAtom(fullAtomIndex(&PC[SLOTNO_LEN])))) return Compile_Error; END_CASE(JSOP_GETLOCALPROP) BEGIN_CASE(JSOP_GETPROP) if (!jsop_getprop(script->getAtom(fullAtomIndex(PC)))) return Compile_Error; END_CASE(JSOP_GETPROP) BEGIN_CASE(JSOP_LENGTH) if (!jsop_length()) return Compile_Error; END_CASE(JSOP_LENGTH) BEGIN_CASE(JSOP_GETELEM) if (!jsop_getelem(false)) return Compile_Error; END_CASE(JSOP_GETELEM) BEGIN_CASE(JSOP_SETELEM) { jsbytecode *next = &PC[JSOP_SETELEM_LENGTH]; bool pop = (JSOp(*next) == JSOP_POP && !analysis->jumpTarget(next)); if (!jsop_setelem(pop)) return Compile_Error; } END_CASE(JSOP_SETELEM); BEGIN_CASE(JSOP_CALLNAME) prepareStubCall(Uses(0)); masm.move(Imm32(fullAtomIndex(PC)), Registers::ArgReg1); INLINE_STUBCALL(stubs::CallName); frame.pushSynced(); frame.pushSynced(); END_CASE(JSOP_CALLNAME) BEGIN_CASE(JSOP_EVAL) { JaegerSpew(JSpew_Insns, " --- EVAL --- \n"); emitEval(GET_ARGC(PC)); JaegerSpew(JSpew_Insns, " --- END EVAL --- \n"); } END_CASE(JSOP_EVAL) BEGIN_CASE(JSOP_CALL) BEGIN_CASE(JSOP_FUNAPPLY) BEGIN_CASE(JSOP_FUNCALL) { JaegerSpew(JSpew_Insns, " --- SCRIPTED CALL --- \n"); inlineCallHelper(GET_ARGC(PC), false); JaegerSpew(JSpew_Insns, " --- END SCRIPTED CALL --- \n"); } END_CASE(JSOP_CALL) BEGIN_CASE(JSOP_NAME) jsop_name(script->getAtom(fullAtomIndex(PC))); END_CASE(JSOP_NAME) BEGIN_CASE(JSOP_DOUBLE) { uint32 index = fullAtomIndex(PC); double d = script->getConst(index).toDouble(); frame.push(Value(DoubleValue(d))); } END_CASE(JSOP_DOUBLE) BEGIN_CASE(JSOP_STRING) { JSAtom *atom = script->getAtom(fullAtomIndex(PC)); JSString *str = ATOM_TO_STRING(atom); frame.push(Value(StringValue(str))); } END_CASE(JSOP_STRING) BEGIN_CASE(JSOP_ZERO) frame.push(Valueify(JSVAL_ZERO)); END_CASE(JSOP_ZERO) BEGIN_CASE(JSOP_ONE) frame.push(Valueify(JSVAL_ONE)); END_CASE(JSOP_ONE) BEGIN_CASE(JSOP_NULL) frame.push(NullValue()); END_CASE(JSOP_NULL) BEGIN_CASE(JSOP_THIS) jsop_this(); END_CASE(JSOP_THIS) BEGIN_CASE(JSOP_FALSE) frame.push(Value(BooleanValue(false))); END_CASE(JSOP_FALSE) BEGIN_CASE(JSOP_TRUE) frame.push(Value(BooleanValue(true))); END_CASE(JSOP_TRUE) BEGIN_CASE(JSOP_OR) BEGIN_CASE(JSOP_AND) if (!jsop_andor(op, PC + GET_JUMP_OFFSET(PC))) return Compile_Error; END_CASE(JSOP_AND) BEGIN_CASE(JSOP_TABLESWITCH) #if defined JS_CPU_ARM /* Need to implement jump(BaseIndex) for ARM */ frame.syncAndForgetEverything(); masm.move(ImmPtr(PC), Registers::ArgReg1); /* prepareStubCall() is not needed due to syncAndForgetEverything() */ INLINE_STUBCALL(stubs::TableSwitch); frame.pop(); masm.jump(Registers::ReturnReg); #else if (!jsop_tableswitch(PC)) return Compile_Error; #endif PC += js_GetVariableBytecodeLength(PC); break; END_CASE(JSOP_TABLESWITCH) BEGIN_CASE(JSOP_LOOKUPSWITCH) frame.syncAndForgetEverything(); masm.move(ImmPtr(PC), Registers::ArgReg1); /* prepareStubCall() is not needed due to syncAndForgetEverything() */ INLINE_STUBCALL(stubs::LookupSwitch); frame.pop(); masm.jump(Registers::ReturnReg); PC += js_GetVariableBytecodeLength(PC); break; END_CASE(JSOP_LOOKUPSWITCH) BEGIN_CASE(JSOP_STRICTEQ) jsop_stricteq(op); END_CASE(JSOP_STRICTEQ) BEGIN_CASE(JSOP_STRICTNE) jsop_stricteq(op); END_CASE(JSOP_STRICTNE) BEGIN_CASE(JSOP_ITER) if (!iter(PC[1])) return Compile_Error; END_CASE(JSOP_ITER) BEGIN_CASE(JSOP_MOREITER) /* At the byte level, this is always fused with IFNE or IFNEX. */ if (!iterMore()) return Compile_Error; break; END_CASE(JSOP_MOREITER) BEGIN_CASE(JSOP_ENDITER) iterEnd(); END_CASE(JSOP_ENDITER) BEGIN_CASE(JSOP_POP) frame.pop(); END_CASE(JSOP_POP) BEGIN_CASE(JSOP_NEW) { JaegerSpew(JSpew_Insns, " --- NEW OPERATOR --- \n"); inlineCallHelper(GET_ARGC(PC), true); JaegerSpew(JSpew_Insns, " --- END NEW OPERATOR --- \n"); } END_CASE(JSOP_NEW) BEGIN_CASE(JSOP_GETARG) BEGIN_CASE(JSOP_CALLARG) { frame.pushArg(GET_SLOTNO(PC)); if (op == JSOP_CALLARG) frame.push(UndefinedValue()); } END_CASE(JSOP_GETARG) BEGIN_CASE(JSOP_BINDGNAME) jsop_bindgname(); END_CASE(JSOP_BINDGNAME) BEGIN_CASE(JSOP_SETARG) { jsbytecode *next = &PC[JSOP_SETLOCAL_LENGTH]; bool pop = JSOp(*next) == JSOP_POP && !analysis->jumpTarget(next); frame.storeArg(GET_SLOTNO(PC), pop); if (pop) { frame.pop(); PC += JSOP_SETARG_LENGTH + JSOP_POP_LENGTH; break; } } END_CASE(JSOP_SETARG) BEGIN_CASE(JSOP_GETLOCAL) { uint32 slot = GET_SLOTNO(PC); frame.pushLocal(slot); } END_CASE(JSOP_GETLOCAL) BEGIN_CASE(JSOP_SETLOCAL) { jsbytecode *next = &PC[JSOP_SETLOCAL_LENGTH]; bool pop = JSOp(*next) == JSOP_POP && !analysis->jumpTarget(next); frame.storeLocal(GET_SLOTNO(PC), pop); if (pop) { frame.pop(); PC += JSOP_SETLOCAL_LENGTH + JSOP_POP_LENGTH; break; } } END_CASE(JSOP_SETLOCAL) BEGIN_CASE(JSOP_SETLOCALPOP) frame.storeLocal(GET_SLOTNO(PC), true); frame.pop(); END_CASE(JSOP_SETLOCALPOP) BEGIN_CASE(JSOP_UINT16) frame.push(Value(Int32Value((int32_t) GET_UINT16(PC)))); END_CASE(JSOP_UINT16) BEGIN_CASE(JSOP_NEWINIT) jsop_newinit(); END_CASE(JSOP_NEWINIT) BEGIN_CASE(JSOP_NEWARRAY) jsop_newinit(); END_CASE(JSOP_NEWARRAY) BEGIN_CASE(JSOP_NEWOBJECT) jsop_newinit(); END_CASE(JSOP_NEWOBJECT) BEGIN_CASE(JSOP_ENDINIT) END_CASE(JSOP_ENDINIT) BEGIN_CASE(JSOP_INITMETHOD) jsop_initmethod(); frame.pop(); END_CASE(JSOP_INITMETHOD) BEGIN_CASE(JSOP_INITPROP) jsop_initprop(); frame.pop(); END_CASE(JSOP_INITPROP) BEGIN_CASE(JSOP_INITELEM) jsop_initelem(); frame.popn(2); END_CASE(JSOP_INITELEM) BEGIN_CASE(JSOP_INCARG) BEGIN_CASE(JSOP_DECARG) BEGIN_CASE(JSOP_ARGINC) BEGIN_CASE(JSOP_ARGDEC) { jsbytecode *next = &PC[JSOP_ARGINC_LENGTH]; bool popped = false; if (JSOp(*next) == JSOP_POP && !analysis->jumpTarget(next)) popped = true; jsop_arginc(op, GET_SLOTNO(PC), popped); PC += JSOP_ARGINC_LENGTH; if (popped) PC += JSOP_POP_LENGTH; break; } END_CASE(JSOP_ARGDEC) BEGIN_CASE(JSOP_INCLOCAL) BEGIN_CASE(JSOP_DECLOCAL) BEGIN_CASE(JSOP_LOCALINC) BEGIN_CASE(JSOP_LOCALDEC) { jsbytecode *next = &PC[JSOP_LOCALINC_LENGTH]; bool popped = false; if (JSOp(*next) == JSOP_POP && !analysis->jumpTarget(next)) popped = true; /* These manually advance the PC. */ jsop_localinc(op, GET_SLOTNO(PC), popped); PC += JSOP_LOCALINC_LENGTH; if (popped) PC += JSOP_POP_LENGTH; break; } END_CASE(JSOP_LOCALDEC) BEGIN_CASE(JSOP_FORNAME) jsop_forname(script->getAtom(fullAtomIndex(PC))); END_CASE(JSOP_FORNAME) BEGIN_CASE(JSOP_FORGNAME) jsop_forgname(script->getAtom(fullAtomIndex(PC))); END_CASE(JSOP_FORGNAME) BEGIN_CASE(JSOP_FORPROP) jsop_forprop(script->getAtom(fullAtomIndex(PC))); END_CASE(JSOP_FORPROP) BEGIN_CASE(JSOP_FORELEM) // This opcode is for the decompiler; it is succeeded by an // ENUMELEM, which performs the actual array store. iterNext(); END_CASE(JSOP_FORELEM) BEGIN_CASE(JSOP_BINDNAME) jsop_bindname(script->getAtom(fullAtomIndex(PC)), true); END_CASE(JSOP_BINDNAME) BEGIN_CASE(JSOP_SETPROP) if (!jsop_setprop(script->getAtom(fullAtomIndex(PC)), true)) return Compile_Error; END_CASE(JSOP_SETPROP) BEGIN_CASE(JSOP_SETNAME) BEGIN_CASE(JSOP_SETMETHOD) if (!jsop_setprop(script->getAtom(fullAtomIndex(PC)), true)) return Compile_Error; END_CASE(JSOP_SETNAME) BEGIN_CASE(JSOP_THROW) prepareStubCall(Uses(1)); INLINE_STUBCALL(stubs::Throw); frame.pop(); END_CASE(JSOP_THROW) BEGIN_CASE(JSOP_IN) prepareStubCall(Uses(2)); INLINE_STUBCALL(stubs::In); frame.popn(2); frame.takeReg(Registers::ReturnReg); frame.pushTypedPayload(JSVAL_TYPE_BOOLEAN, Registers::ReturnReg); END_CASE(JSOP_IN) BEGIN_CASE(JSOP_INSTANCEOF) if (!jsop_instanceof()) return Compile_Error; END_CASE(JSOP_INSTANCEOF) BEGIN_CASE(JSOP_EXCEPTION) prepareStubCall(Uses(0)); INLINE_STUBCALL(stubs::Exception); frame.pushSynced(); END_CASE(JSOP_EXCEPTION) BEGIN_CASE(JSOP_LINENO) END_CASE(JSOP_LINENO) BEGIN_CASE(JSOP_ENUMELEM) // Normally, SETELEM transforms the stack // from: OBJ ID VALUE // to: VALUE // // Here, the stack transition is // from: VALUE OBJ ID // to: // So we make the stack look like a SETELEM, and re-use it. // Before: VALUE OBJ ID // After: VALUE OBJ ID VALUE frame.dupAt(-3); // Before: VALUE OBJ ID VALUE // After: VALUE VALUE if (!jsop_setelem(true)) return Compile_Error; // Before: VALUE VALUE // After: frame.popn(2); END_CASE(JSOP_ENUMELEM) BEGIN_CASE(JSOP_BLOCKCHAIN) END_CASE(JSOP_BLOCKCHAIN) BEGIN_CASE(JSOP_NULLBLOCKCHAIN) END_CASE(JSOP_NULLBLOCKCHAIN) BEGIN_CASE(JSOP_CONDSWITCH) /* No-op for the decompiler. */ END_CASE(JSOP_CONDSWITCH) BEGIN_CASE(JSOP_DEFFUN) { uint32 index = fullAtomIndex(PC); JSFunction *innerFun = script->getFunction(index); if (fun && script->bindings.hasBinding(cx, innerFun->atom)) frame.syncAndForgetEverything(); prepareStubCall(Uses(0)); masm.move(ImmPtr(innerFun), Registers::ArgReg1); INLINE_STUBCALL(STRICT_VARIANT(stubs::DefFun)); } END_CASE(JSOP_DEFFUN) BEGIN_CASE(JSOP_DEFVAR) BEGIN_CASE(JSOP_DEFCONST) { uint32 index = fullAtomIndex(PC); JSAtom *atom = script->getAtom(index); prepareStubCall(Uses(0)); masm.move(ImmPtr(atom), Registers::ArgReg1); INLINE_STUBCALL(stubs::DefVarOrConst); } END_CASE(JSOP_DEFVAR) BEGIN_CASE(JSOP_SETCONST) { uint32 index = fullAtomIndex(PC); JSAtom *atom = script->getAtom(index); if (fun && script->bindings.hasBinding(cx, atom)) frame.syncAndForgetEverything(); prepareStubCall(Uses(1)); masm.move(ImmPtr(atom), Registers::ArgReg1); INLINE_STUBCALL(stubs::SetConst); } END_CASE(JSOP_SETCONST) BEGIN_CASE(JSOP_DEFLOCALFUN_FC) { uint32 slot = GET_SLOTNO(PC); JSFunction *fun = script->getFunction(fullAtomIndex(&PC[SLOTNO_LEN])); prepareStubCall(Uses(frame.frameSlots())); masm.move(ImmPtr(fun), Registers::ArgReg1); INLINE_STUBCALL(stubs::DefLocalFun_FC); frame.takeReg(Registers::ReturnReg); frame.pushTypedPayload(JSVAL_TYPE_OBJECT, Registers::ReturnReg); frame.storeLocal(slot, true); frame.pop(); } END_CASE(JSOP_DEFLOCALFUN_FC) BEGIN_CASE(JSOP_LAMBDA) { JSFunction *fun = script->getFunction(fullAtomIndex(PC)); JSObjStubFun stub = stubs::Lambda; uint32 uses = 0; jsbytecode *pc2 = AdvanceOverBlockchainOp(PC + JSOP_LAMBDA_LENGTH); JSOp next = JSOp(*pc2); if (next == JSOP_INITMETHOD) { stub = stubs::LambdaForInit; } else if (next == JSOP_SETMETHOD) { stub = stubs::LambdaForSet; uses = 1; } else if (fun->joinable()) { if (next == JSOP_CALL) { stub = stubs::LambdaJoinableForCall; uses = frame.frameSlots(); } else if (next == JSOP_NULL) { stub = stubs::LambdaJoinableForNull; } } prepareStubCall(Uses(uses)); masm.move(ImmPtr(fun), Registers::ArgReg1); if (stub == stubs::Lambda) { INLINE_STUBCALL(stub); } else { jsbytecode *savedPC = PC; PC = pc2; INLINE_STUBCALL(stub); PC = savedPC; } frame.takeReg(Registers::ReturnReg); frame.pushTypedPayload(JSVAL_TYPE_OBJECT, Registers::ReturnReg); } END_CASE(JSOP_LAMBDA) BEGIN_CASE(JSOP_TRY) frame.syncAndForgetEverything(); END_CASE(JSOP_TRY) BEGIN_CASE(JSOP_GETFCSLOT) BEGIN_CASE(JSOP_CALLFCSLOT) { uintN index = GET_UINT16(PC); // Load the callee's payload into a register. frame.pushCallee(); RegisterID reg = frame.copyDataIntoReg(frame.peek(-1)); frame.pop(); // obj->getFlatClosureUpvars() masm.loadPtr(Address(reg, offsetof(JSObject, slots)), reg); Address upvarAddress(reg, JSObject::JSSLOT_FLAT_CLOSURE_UPVARS * sizeof(Value)); masm.loadPrivate(upvarAddress, reg); // push ((Value *) reg)[index] frame.freeReg(reg); frame.push(Address(reg, index * sizeof(Value))); if (op == JSOP_CALLFCSLOT) frame.push(UndefinedValue()); } END_CASE(JSOP_CALLFCSLOT) BEGIN_CASE(JSOP_ARGSUB) prepareStubCall(Uses(0)); masm.move(Imm32(GET_ARGNO(PC)), Registers::ArgReg1); INLINE_STUBCALL(stubs::ArgSub); frame.pushSynced(); END_CASE(JSOP_ARGSUB) BEGIN_CASE(JSOP_ARGCNT) prepareStubCall(Uses(0)); INLINE_STUBCALL(stubs::ArgCnt); frame.pushSynced(); END_CASE(JSOP_ARGCNT) BEGIN_CASE(JSOP_DEFLOCALFUN) { uint32 slot = GET_SLOTNO(PC); JSFunction *fun = script->getFunction(fullAtomIndex(&PC[SLOTNO_LEN])); prepareStubCall(Uses(0)); masm.move(ImmPtr(fun), Registers::ArgReg1); INLINE_STUBCALL(stubs::DefLocalFun); frame.takeReg(Registers::ReturnReg); frame.pushTypedPayload(JSVAL_TYPE_OBJECT, Registers::ReturnReg); frame.storeLocal(slot, true); frame.pop(); } END_CASE(JSOP_DEFLOCALFUN) BEGIN_CASE(JSOP_RETRVAL) emitReturn(NULL); END_CASE(JSOP_RETRVAL) BEGIN_CASE(JSOP_GETGNAME) BEGIN_CASE(JSOP_CALLGNAME) jsop_getgname(fullAtomIndex(PC)); if (op == JSOP_CALLGNAME) frame.push(UndefinedValue()); END_CASE(JSOP_GETGNAME) BEGIN_CASE(JSOP_SETGNAME) jsop_setgname(script->getAtom(fullAtomIndex(PC)), true); END_CASE(JSOP_SETGNAME) BEGIN_CASE(JSOP_REGEXP) { JSObject *regex = script->getRegExp(fullAtomIndex(PC)); prepareStubCall(Uses(0)); masm.move(ImmPtr(regex), Registers::ArgReg1); INLINE_STUBCALL(stubs::RegExp); frame.takeReg(Registers::ReturnReg); frame.pushTypedPayload(JSVAL_TYPE_OBJECT, Registers::ReturnReg); } END_CASE(JSOP_REGEXP) BEGIN_CASE(JSOP_OBJECT) { JSObject *object = script->getObject(fullAtomIndex(PC)); RegisterID reg = frame.allocReg(); masm.move(ImmPtr(object), reg); frame.pushTypedPayload(JSVAL_TYPE_OBJECT, reg); } END_CASE(JSOP_OBJECT) BEGIN_CASE(JSOP_CALLPROP) if (!jsop_callprop(script->getAtom(fullAtomIndex(PC)))) return Compile_Error; END_CASE(JSOP_CALLPROP) BEGIN_CASE(JSOP_UINT24) frame.push(Value(Int32Value((int32_t) GET_UINT24(PC)))); END_CASE(JSOP_UINT24) BEGIN_CASE(JSOP_CALLELEM) jsop_getelem(true); END_CASE(JSOP_CALLELEM) BEGIN_CASE(JSOP_STOP) /* Safe point! */ emitReturn(NULL); goto done; END_CASE(JSOP_STOP) BEGIN_CASE(JSOP_GETXPROP) if (!jsop_xname(script->getAtom(fullAtomIndex(PC)))) return Compile_Error; END_CASE(JSOP_GETXPROP) BEGIN_CASE(JSOP_ENTERBLOCK) enterBlock(script->getObject(fullAtomIndex(PC))); END_CASE(JSOP_ENTERBLOCK); BEGIN_CASE(JSOP_LEAVEBLOCK) leaveBlock(); END_CASE(JSOP_LEAVEBLOCK) BEGIN_CASE(JSOP_CALLLOCAL) frame.pushLocal(GET_SLOTNO(PC)); frame.push(UndefinedValue()); END_CASE(JSOP_CALLLOCAL) BEGIN_CASE(JSOP_INT8) frame.push(Value(Int32Value(GET_INT8(PC)))); END_CASE(JSOP_INT8) BEGIN_CASE(JSOP_INT32) frame.push(Value(Int32Value(GET_INT32(PC)))); END_CASE(JSOP_INT32) BEGIN_CASE(JSOP_HOLE) frame.push(MagicValue(JS_ARRAY_HOLE)); END_CASE(JSOP_HOLE) BEGIN_CASE(JSOP_LAMBDA_FC) { JSFunction *fun = script->getFunction(fullAtomIndex(PC)); prepareStubCall(Uses(frame.frameSlots())); masm.move(ImmPtr(fun), Registers::ArgReg1); INLINE_STUBCALL(stubs::FlatLambda); frame.takeReg(Registers::ReturnReg); frame.pushTypedPayload(JSVAL_TYPE_OBJECT, Registers::ReturnReg); } END_CASE(JSOP_LAMBDA_FC) BEGIN_CASE(JSOP_TRACE) BEGIN_CASE(JSOP_NOTRACE) { if (analysis->jumpTarget(PC)) interruptCheckHelper(); } END_CASE(JSOP_TRACE) BEGIN_CASE(JSOP_DEBUGGER) prepareStubCall(Uses(0)); masm.move(ImmPtr(PC), Registers::ArgReg1); INLINE_STUBCALL(stubs::Debugger); END_CASE(JSOP_DEBUGGER) BEGIN_CASE(JSOP_UNBRAND) jsop_unbrand(); END_CASE(JSOP_UNBRAND) BEGIN_CASE(JSOP_UNBRANDTHIS) jsop_this(); jsop_unbrand(); frame.pop(); END_CASE(JSOP_UNBRANDTHIS) BEGIN_CASE(JSOP_GETGLOBAL) BEGIN_CASE(JSOP_CALLGLOBAL) jsop_getglobal(GET_SLOTNO(PC)); if (op == JSOP_CALLGLOBAL) frame.push(UndefinedValue()); END_CASE(JSOP_GETGLOBAL) default: /* Sorry, this opcode isn't implemented yet. */ #ifdef JS_METHODJIT_SPEW JaegerSpew(JSpew_Abort, "opcode %s not handled yet (%s line %d)\n", OpcodeNames[op], script->filename, js_PCToLineNumber(cx, script, PC)); #endif return Compile_Abort; } /********************** * END COMPILER OPS * **********************/ #ifdef DEBUG frame.assertValidRegisterState(); #endif } done: return Compile_Okay; } #undef END_CASE #undef BEGIN_CASE JSC::MacroAssembler::Label mjit::Compiler::labelOf(jsbytecode *pc) { uint32 offs = uint32(pc - script->code); JS_ASSERT(jumpMap[offs].isValid()); return jumpMap[offs]; } uint32 mjit::Compiler::fullAtomIndex(jsbytecode *pc) { return GET_SLOTNO(pc); /* If we ever enable INDEXBASE garbage, use this below. */ #if 0 return GET_SLOTNO(pc) + (atoms - script->atomMap.vector); #endif } bool mjit::Compiler::knownJump(jsbytecode *pc) { return pc < PC; } void * mjit::Compiler::findCallSite(const CallSite &callSite) { JS_ASSERT(callSite.pcOffset < script->length); JITScript *jit = script->getJIT(fp->isConstructing()); uint8* ilPath = (uint8 *)jit->code.m_code.executableAddress(); uint8* oolPath = ilPath + masm.size(); for (uint32 i = 0; i < callSites.length(); i++) { InternalCallSite &cs = callSites[i]; if (cs.pc == script->code + callSite.pcOffset && cs.id == callSite.id) { if (cs.ool) return oolPath + cs.returnOffset; return ilPath + cs.returnOffset; } } /* We have no idea where to patch up to. */ JS_NOT_REACHED("Call site vanished."); return NULL; } bool mjit::Compiler::jumpInScript(Jump j, jsbytecode *pc) { JS_ASSERT(pc >= script->code && uint32(pc - script->code) < script->length); if (pc < PC) { j.linkTo(jumpMap[uint32(pc - script->code)], &masm); return true; } return branchPatches.append(BranchPatch(j, pc)); } void mjit::Compiler::jsop_getglobal(uint32 index) { JS_ASSERT(globalObj); uint32 slot = script->getGlobalSlot(index); RegisterID reg = frame.allocReg(); Address address = masm.objSlotRef(globalObj, reg, slot); frame.freeReg(reg); frame.push(address); } void mjit::Compiler::emitFinalReturn(Assembler &masm) { masm.loadPtr(Address(JSFrameReg, JSStackFrame::offsetOfncode()), Registers::ReturnReg); masm.jump(Registers::ReturnReg); } // Emits code to load a return value of the frame into the scripted-ABI // type & data register pair. If the return value is in fp->rval, then |fe| // is NULL. Otherwise, |fe| contains the return value. // // If reading from fp->rval, |undefined| is loaded optimistically, before // checking if fp->rval is set in the frame flags and loading that instead. // // Otherwise, if |masm| is the inline path, it is loaded as efficiently as // the FrameState can manage. If |masm| is the OOL path, the value is simply // loaded from its slot in the frame, since the caller has guaranteed it's // been synced. // void mjit::Compiler::loadReturnValue(Assembler *masm, FrameEntry *fe) { RegisterID typeReg = JSReturnReg_Type; RegisterID dataReg = JSReturnReg_Data; if (fe) { // If using the OOL assembler, the caller signifies that the |fe| is // synced, but not to rely on its register state. if (masm != &this->masm) { if (fe->isConstant()) { stubcc.masm.loadValueAsComponents(fe->getValue(), typeReg, dataReg); } else { Address rval(frame.addressOf(fe)); if (fe->isTypeKnown()) { stubcc.masm.loadPayload(rval, dataReg); stubcc.masm.move(ImmType(fe->getKnownType()), typeReg); } else { stubcc.masm.loadValueAsComponents(rval, typeReg, dataReg); } } } else { frame.loadForReturn(fe, typeReg, dataReg, Registers::ReturnReg); } } else { // Load a return value from POPV or SETRVAL into the return registers, // otherwise return undefined. masm->loadValueAsComponents(UndefinedValue(), typeReg, dataReg); if (analysis->usesReturnValue()) { Jump rvalClear = masm->branchTest32(Assembler::Zero, FrameFlagsAddress(), Imm32(JSFRAME_HAS_RVAL)); Address rvalAddress(JSFrameReg, JSStackFrame::offsetOfReturnValue()); masm->loadValueAsComponents(rvalAddress, typeReg, dataReg); rvalClear.linkTo(masm->label(), masm); } } } // This ensures that constructor return values are an object. If a non-object // is returned, either explicitly or implicitly, the newly created object is // loaded out of the frame. Otherwise, the explicitly returned object is kept. // void mjit::Compiler::fixPrimitiveReturn(Assembler *masm, FrameEntry *fe) { JS_ASSERT(isConstructing); bool ool = (masm != &this->masm); Address thisv(JSFrameReg, JSStackFrame::offsetOfThis(fun)); // We can just load |thisv| if either of the following is true: // (1) There is no explicit return value, AND fp->rval is not used. // (2) There is an explicit return value, and it's known to be primitive. if ((!fe && !analysis->usesReturnValue()) || (fe && fe->isTypeKnown() && fe->getKnownType() != JSVAL_TYPE_OBJECT)) { if (ool) masm->loadValueAsComponents(thisv, JSReturnReg_Type, JSReturnReg_Data); else frame.loadThisForReturn(JSReturnReg_Type, JSReturnReg_Data, Registers::ReturnReg); return; } // If the type is known to be an object, just load the return value as normal. if (fe && fe->isTypeKnown() && fe->getKnownType() == JSVAL_TYPE_OBJECT) { loadReturnValue(masm, fe); return; } // There's a return value, and its type is unknown. Test the type and load // |thisv| if necessary. loadReturnValue(masm, fe); Jump j = masm->testObject(Assembler::Equal, JSReturnReg_Type); masm->loadValueAsComponents(thisv, JSReturnReg_Type, JSReturnReg_Data); j.linkTo(masm->label(), masm); } // Loads the return value into the scripted ABI register pair, such that JS // semantics in constructors are preserved. // void mjit::Compiler::emitReturnValue(Assembler *masm, FrameEntry *fe) { if (isConstructing) fixPrimitiveReturn(masm, fe); else loadReturnValue(masm, fe); } void mjit::Compiler::emitReturn(FrameEntry *fe) { JS_ASSERT_IF(!fun, JSOp(*PC) == JSOP_STOP); /* Only the top of the stack can be returned. */ JS_ASSERT_IF(fe, fe == frame.peek(-1)); if (debugMode() || Probes::callTrackingActive(cx)) { prepareStubCall(Uses(0)); INLINE_STUBCALL(stubs::LeaveScript); } /* * If there's a function object, deal with the fact that it can escape. * Note that after we've placed the call object, all tracked state can * be thrown away. This will happen anyway because the next live opcode * (if any) must have an incoming edge. * * However, it's an optimization to throw it away early - the tracker * won't be spilled on further exits or join points. */ if (fun) { if (fun->isHeavyweight()) { /* There will always be a call object. */ prepareStubCall(Uses(fe ? 1 : 0)); INLINE_STUBCALL(stubs::PutActivationObjects); } else { /* if (hasCallObj() || hasArgsObj()) stubs::PutActivationObjects() */ Jump putObjs = masm.branchTest32(Assembler::NonZero, Address(JSFrameReg, JSStackFrame::offsetOfFlags()), Imm32(JSFRAME_HAS_CALL_OBJ | JSFRAME_HAS_ARGS_OBJ)); stubcc.linkExit(putObjs, Uses(frame.frameSlots())); stubcc.leave(); OOL_STUBCALL(stubs::PutActivationObjects); emitReturnValue(&stubcc.masm, fe); emitFinalReturn(stubcc.masm); } } else { if (fp->isEvalFrame() && script->strictModeCode) { /* There will always be a call object. */ prepareStubCall(Uses(fe ? 1 : 0)); INLINE_STUBCALL(stubs::PutStrictEvalCallObject); } } emitReturnValue(&masm, fe); emitFinalReturn(masm); frame.discardFrame(); } void mjit::Compiler::prepareStubCall(Uses uses) { JaegerSpew(JSpew_Insns, " ---- STUB CALL, SYNCING FRAME ---- \n"); frame.syncAndKill(Registers(Registers::TempRegs), uses); JaegerSpew(JSpew_Insns, " ---- FRAME SYNCING DONE ---- \n"); } JSC::MacroAssembler::Call mjit::Compiler::emitStubCall(void *ptr) { JaegerSpew(JSpew_Insns, " ---- CALLING STUB ---- \n"); Call cl = masm.fallibleVMCall(ptr, PC, frame.stackDepth() + script->nfixed); JaegerSpew(JSpew_Insns, " ---- END STUB CALL ---- \n"); return cl; } void mjit::Compiler::interruptCheckHelper() { RegisterID reg = frame.allocReg(); /* * Bake in and test the address of the interrupt counter for the runtime. * This is faster than doing two additional loads for the context's * thread data, but will cause this thread to run slower if there are * pending interrupts on some other thread. For non-JS_THREADSAFE builds * we can skip this, as there is only one flag to poll. */ #ifdef JS_THREADSAFE void *interrupt = (void*) &cx->runtime->interruptCounter; #else void *interrupt = (void*) &JS_THREAD_DATA(cx)->interruptFlags; #endif #if defined(JS_CPU_X86) || defined(JS_CPU_ARM) Jump jump = masm.branch32(Assembler::NotEqual, AbsoluteAddress(interrupt), Imm32(0)); #else /* Handle processors that can't load from absolute addresses. */ masm.move(ImmPtr(interrupt), reg); Jump jump = masm.branchTest32(Assembler::NonZero, Address(reg, 0)); #endif stubcc.linkExitDirect(jump, stubcc.masm.label()); #ifdef JS_THREADSAFE /* * Do a slightly slower check for an interrupt on this thread. * We don't want this thread to slow down excessively if the pending * interrupt is on another thread. */ stubcc.masm.loadPtr(FrameAddress(offsetof(VMFrame, cx)), reg); stubcc.masm.loadPtr(Address(reg, offsetof(JSContext, thread)), reg); Address flag(reg, offsetof(JSThread, data.interruptFlags)); Jump noInterrupt = stubcc.masm.branchTest32(Assembler::Zero, flag); #endif frame.sync(stubcc.masm, Uses(0)); stubcc.masm.move(ImmPtr(PC), Registers::ArgReg1); OOL_STUBCALL(stubs::Interrupt); stubcc.rejoin(Changes(0)); #ifdef JS_THREADSAFE stubcc.linkRejoin(noInterrupt); #endif frame.freeReg(reg); } void mjit::Compiler::addReturnSite(Label joinPoint, uint32 id) { InternalCallSite site(masm.distanceOf(joinPoint), PC, id, false, false); addCallSite(site); } void mjit::Compiler::emitUncachedCall(uint32 argc, bool callingNew) { CallPatchInfo callPatch; RegisterID r0 = Registers::ReturnReg; VoidPtrStubUInt32 stub = callingNew ? stubs::UncachedNew : stubs::UncachedCall; frame.syncAndKill(Registers(Registers::AvailRegs), Uses(argc + 2)); prepareStubCall(Uses(argc + 2)); masm.move(Imm32(argc), Registers::ArgReg1); INLINE_STUBCALL(stub); Jump notCompiled = masm.branchTestPtr(Assembler::Zero, r0, r0); masm.loadPtr(FrameAddress(offsetof(VMFrame, regs.fp)), JSFrameReg); callPatch.hasFastNcode = true; callPatch.fastNcodePatch = masm.storePtrWithPatch(ImmPtr(NULL), Address(JSFrameReg, JSStackFrame::offsetOfncode())); masm.jump(r0); callPatch.joinPoint = masm.label(); addReturnSite(callPatch.joinPoint, __LINE__); masm.loadPtr(Address(JSFrameReg, JSStackFrame::offsetOfPrev()), JSFrameReg); frame.popn(argc + 2); frame.takeReg(JSReturnReg_Type); frame.takeReg(JSReturnReg_Data); frame.pushRegs(JSReturnReg_Type, JSReturnReg_Data); stubcc.linkExitDirect(notCompiled, stubcc.masm.label()); stubcc.rejoin(Changes(0)); callPatches.append(callPatch); } static bool IsLowerableFunCallOrApply(jsbytecode *pc) { #ifdef JS_MONOIC return (*pc == JSOP_FUNCALL && GET_ARGC(pc) >= 1) || (*pc == JSOP_FUNAPPLY && GET_ARGC(pc) == 2); #else return false; #endif } void mjit::Compiler::checkCallApplySpeculation(uint32 callImmArgc, uint32 speculatedArgc, FrameEntry *origCallee, FrameEntry *origThis, MaybeRegisterID origCalleeType, RegisterID origCalleeData, MaybeRegisterID origThisType, RegisterID origThisData, Jump *uncachedCallSlowRejoin, CallPatchInfo *uncachedCallPatch) { JS_ASSERT(IsLowerableFunCallOrApply(PC)); /* * if (origCallee.isObject() && * origCallee.toObject().isFunction && * origCallee.toObject().getFunctionPrivate() == js_fun_{call,apply}) */ MaybeJump isObj; if (origCalleeType.isSet()) isObj = masm.testObject(Assembler::NotEqual, origCalleeType.reg()); Jump isFun = masm.testFunction(Assembler::NotEqual, origCalleeData); masm.loadObjPrivate(origCalleeData, origCalleeData); Native native = *PC == JSOP_FUNCALL ? js_fun_call : js_fun_apply; Jump isNative = masm.branchPtr(Assembler::NotEqual, Address(origCalleeData, JSFunction::offsetOfNativeOrScript()), ImmPtr(JS_FUNC_TO_DATA_PTR(void *, native))); /* * If speculation fails, we can't use the ic, since it is compiled on the * assumption that speculation succeeds. Instead, just do an uncached call. */ { if (isObj.isSet()) stubcc.linkExitDirect(isObj.getJump(), stubcc.masm.label()); stubcc.linkExitDirect(isFun, stubcc.masm.label()); stubcc.linkExitDirect(isNative, stubcc.masm.label()); int32 frameDepthAdjust; if (applyTricks == LazyArgsObj) { OOL_STUBCALL(stubs::Arguments); frameDepthAdjust = +1; } else { frameDepthAdjust = 0; } stubcc.masm.move(Imm32(callImmArgc), Registers::ArgReg1); JaegerSpew(JSpew_Insns, " ---- BEGIN SLOW CALL CODE ---- \n"); OOL_STUBCALL_LOCAL_SLOTS(JS_FUNC_TO_DATA_PTR(void *, stubs::UncachedCall), frame.localSlots() + frameDepthAdjust); JaegerSpew(JSpew_Insns, " ---- END SLOW CALL CODE ---- \n"); RegisterID r0 = Registers::ReturnReg; Jump notCompiled = stubcc.masm.branchTestPtr(Assembler::Zero, r0, r0); stubcc.masm.loadPtr(FrameAddress(offsetof(VMFrame, regs.fp)), JSFrameReg); Address ncodeAddr(JSFrameReg, JSStackFrame::offsetOfncode()); uncachedCallPatch->hasSlowNcode = true; uncachedCallPatch->slowNcodePatch = stubcc.masm.storePtrWithPatch(ImmPtr(NULL), ncodeAddr); stubcc.masm.jump(r0); addReturnSite(masm.label(), __LINE__); notCompiled.linkTo(stubcc.masm.label(), &stubcc.masm); /* * inlineCallHelper will link uncachedCallSlowRejoin to the join point * at the end of the ic. At that join point, the return value of the * call is assumed to be in registers, so load them before jumping. */ JaegerSpew(JSpew_Insns, " ---- BEGIN SLOW RESTORE CODE ---- \n"); Address rval = frame.addressOf(origCallee); /* vp[0] == rval */ stubcc.masm.loadValueAsComponents(rval, JSReturnReg_Type, JSReturnReg_Data); *uncachedCallSlowRejoin = stubcc.masm.jump(); JaegerSpew(JSpew_Insns, " ---- END SLOW RESTORE CODE ---- \n"); } /* * For simplicity, we don't statically specialize calls to * ic::SplatApplyArgs based on applyTricks. Rather, this state is * communicated dynamically through the VMFrame. */ if (*PC == JSOP_FUNAPPLY) { masm.store32(Imm32(applyTricks == LazyArgsObj), FrameAddress(offsetof(VMFrame, u.call.lazyArgsObj))); } } /* This predicate must be called before the current op mutates the FrameState. */ bool mjit::Compiler::canUseApplyTricks() { JS_ASSERT(*PC == JSOP_ARGUMENTS); jsbytecode *nextpc = PC + JSOP_ARGUMENTS_LENGTH; return *nextpc == JSOP_FUNAPPLY && IsLowerableFunCallOrApply(nextpc) && !analysis->jumpTarget(nextpc) && !debugMode(); } /* See MonoIC.cpp, CallCompiler for more information on call ICs. */ void mjit::Compiler::inlineCallHelper(uint32 callImmArgc, bool callingNew) { /* Check for interrupts on function call */ interruptCheckHelper(); int32 speculatedArgc; if (applyTricks == LazyArgsObj) { frame.pop(); speculatedArgc = 1; } else { speculatedArgc = callImmArgc; } FrameEntry *origCallee = frame.peek(-(speculatedArgc + 2)); FrameEntry *origThis = frame.peek(-(speculatedArgc + 1)); /* 'this' does not need to be synced for constructing. */ if (callingNew) frame.discardFe(origThis); /* * From the presence of JSOP_FUN{CALL,APPLY}, we speculate that we are * going to call js_fun_{call,apply}. Normally, this call would go through * js::Invoke to ultimately call 'this'. We can do much better by having * the callIC cache and call 'this' directly. However, if it turns out that * we are not actually calling js_fun_call, the callIC must act as normal. */ bool lowerFunCallOrApply = IsLowerableFunCallOrApply(PC); /* * Currently, constant values are not functions, so don't even try to * optimize. This lets us assume that callee/this have regs below. */ #ifdef JS_MONOIC if (debugMode() || origCallee->isConstant() || origCallee->isNotType(JSVAL_TYPE_OBJECT) || (lowerFunCallOrApply && (origThis->isConstant() || origThis->isNotType(JSVAL_TYPE_OBJECT)))) { #endif if (applyTricks == LazyArgsObj) { /* frame.pop() above reset us to pre-JSOP_ARGUMENTS state */ jsop_arguments(); frame.pushSynced(); } emitUncachedCall(callImmArgc, callingNew); return; #ifdef JS_MONOIC } /* Initialized by both branches below. */ CallGenInfo callIC(PC); CallPatchInfo callPatch; MaybeRegisterID icCalleeType; /* type to test for function-ness */ RegisterID icCalleeData; /* data to call */ Address icRvalAddr; /* return slot on slow-path rejoin */ /* * IC space must be reserved (using RESERVE_IC_SPACE or RESERVE_OOL_SPACE) between the * following labels (as used in finishThisUp): * - funGuard -> hotJump * - funGuard -> joinPoint * - funGuard -> hotPathLabel * - slowPathStart -> oolCall * - slowPathStart -> oolJump * - slowPathStart -> icCall * - slowPathStart -> slowJoinPoint * Because the call ICs are fairly long (compared to PICs), we don't reserve the space in each * path until the first usage of funGuard (for the in-line path) or slowPathStart (for the * out-of-line path). */ /* Initialized only on lowerFunCallOrApply branch. */ Jump uncachedCallSlowRejoin; CallPatchInfo uncachedCallPatch; { MaybeRegisterID origCalleeType, maybeOrigCalleeData; RegisterID origCalleeData; /* Get the callee in registers. */ frame.ensureFullRegs(origCallee, &origCalleeType, &maybeOrigCalleeData); origCalleeData = maybeOrigCalleeData.reg(); PinRegAcrossSyncAndKill p1(frame, origCalleeData), p2(frame, origCalleeType); if (lowerFunCallOrApply) { MaybeRegisterID origThisType, maybeOrigThisData; RegisterID origThisData; { /* Get thisv in registers. */ frame.ensureFullRegs(origThis, &origThisType, &maybeOrigThisData); origThisData = maybeOrigThisData.reg(); PinRegAcrossSyncAndKill p3(frame, origThisData), p4(frame, origThisType); /* Leaves pinned regs untouched. */ frame.syncAndKill(Registers(Registers::AvailRegs), Uses(speculatedArgc + 2)); } checkCallApplySpeculation(callImmArgc, speculatedArgc, origCallee, origThis, origCalleeType, origCalleeData, origThisType, origThisData, &uncachedCallSlowRejoin, &uncachedCallPatch); icCalleeType = origThisType; icCalleeData = origThisData; icRvalAddr = frame.addressOf(origThis); /* * For f.call(), since we compile the ic under the (checked) * assumption that call == js_fun_call, we still have a static * frame size. For f.apply(), the frame size depends on the dynamic * length of the array passed to apply. */ if (*PC == JSOP_FUNCALL) callIC.frameSize.initStatic(frame.localSlots(), speculatedArgc - 1); else callIC.frameSize.initDynamic(); } else { /* Leaves pinned regs untouched. */ frame.syncAndKill(Registers(Registers::AvailRegs), Uses(speculatedArgc + 2)); icCalleeType = origCalleeType; icCalleeData = origCalleeData; icRvalAddr = frame.addressOf(origCallee); callIC.frameSize.initStatic(frame.localSlots(), speculatedArgc); } } /* Test the type if necessary. Failing this always takes a really slow path. */ MaybeJump notObjectJump; if (icCalleeType.isSet()) notObjectJump = masm.testObject(Assembler::NotEqual, icCalleeType.reg()); /* * For an optimized apply, keep icCalleeData and funPtrReg in a * callee-saved registers for the subsequent ic::SplatApplyArgs call. */ Registers tempRegs; if (callIC.frameSize.isDynamic() && !Registers::isSaved(icCalleeData)) { RegisterID x = tempRegs.takeRegInMask(Registers::SavedRegs); masm.move(icCalleeData, x); icCalleeData = x; } else { tempRegs.takeReg(icCalleeData); } RegisterID funPtrReg = tempRegs.takeRegInMask(Registers::SavedRegs); /* Reserve space just before initialization of funGuard. */ RESERVE_IC_SPACE(masm); /* * Guard on the callee identity. This misses on the first run. If the * callee is scripted, compiled/compilable, and argc == nargs, then this * guard is patched, and the compiled code address is baked in. */ Jump j = masm.branchPtrWithPatch(Assembler::NotEqual, icCalleeData, callIC.funGuard); callIC.funJump = j; /* Reserve space just before initialization of slowPathStart. */ RESERVE_OOL_SPACE(stubcc.masm); Jump rejoin1, rejoin2; { RESERVE_OOL_SPACE(stubcc.masm); stubcc.linkExitDirect(j, stubcc.masm.label()); callIC.slowPathStart = stubcc.masm.label(); /* * Test if the callee is even a function. If this doesn't match, we * take a _really_ slow path later. */ Jump notFunction = stubcc.masm.testFunction(Assembler::NotEqual, icCalleeData); /* Test if the function is scripted. */ RegisterID tmp = tempRegs.takeAnyReg(); stubcc.masm.loadObjPrivate(icCalleeData, funPtrReg); stubcc.masm.load16(Address(funPtrReg, offsetof(JSFunction, flags)), tmp); stubcc.masm.and32(Imm32(JSFUN_KINDMASK), tmp); Jump isNative = stubcc.masm.branch32(Assembler::Below, tmp, Imm32(JSFUN_INTERPRETED)); tempRegs.putReg(tmp); /* * N.B. After this call, the frame will have a dynamic frame size. * Check after the function is known not to be a native so that the * catch-all/native path has a static depth. */ if (callIC.frameSize.isDynamic()) OOL_STUBCALL(ic::SplatApplyArgs); /* * No-op jump that gets patched by ic::New/Call to the stub generated * by generateFullCallStub. */ Jump toPatch = stubcc.masm.jump(); toPatch.linkTo(stubcc.masm.label(), &stubcc.masm); callIC.oolJump = toPatch; callIC.icCall = stubcc.masm.label(); /* * At this point the function is definitely scripted, so we try to * compile it and patch either funGuard/funJump or oolJump. This code * is only executed once. */ callIC.addrLabel1 = stubcc.masm.moveWithPatch(ImmPtr(NULL), Registers::ArgReg1); void *icFunPtr = JS_FUNC_TO_DATA_PTR(void *, callingNew ? ic::New : ic::Call); if (callIC.frameSize.isStatic()) callIC.oolCall = OOL_STUBCALL_LOCAL_SLOTS(icFunPtr, frame.localSlots()); else callIC.oolCall = OOL_STUBCALL_LOCAL_SLOTS(icFunPtr, -1); callIC.funObjReg = icCalleeData; callIC.funPtrReg = funPtrReg; /* * The IC call either returns NULL, meaning call completed, or a * function pointer to jump to. Caveat: Must restore JSFrameReg * because a new frame has been pushed. */ rejoin1 = stubcc.masm.branchTestPtr(Assembler::Zero, Registers::ReturnReg, Registers::ReturnReg); if (callIC.frameSize.isStatic()) stubcc.masm.move(Imm32(callIC.frameSize.staticArgc()), JSParamReg_Argc); else stubcc.masm.load32(FrameAddress(offsetof(VMFrame, u.call.dynamicArgc)), JSParamReg_Argc); stubcc.masm.loadPtr(FrameAddress(offsetof(VMFrame, regs.fp)), JSFrameReg); callPatch.hasSlowNcode = true; callPatch.slowNcodePatch = stubcc.masm.storePtrWithPatch(ImmPtr(NULL), Address(JSFrameReg, JSStackFrame::offsetOfncode())); stubcc.masm.jump(Registers::ReturnReg); /* * This ool path is the catch-all for everything but scripted function * callees. For native functions, ic::NativeNew/NativeCall will repatch * funGaurd/funJump with a fast call stub. All other cases * (non-function callable objects and invalid callees) take the slow * path through js::Invoke. */ if (notObjectJump.isSet()) stubcc.linkExitDirect(notObjectJump.get(), stubcc.masm.label()); notFunction.linkTo(stubcc.masm.label(), &stubcc.masm); isNative.linkTo(stubcc.masm.label(), &stubcc.masm); callIC.addrLabel2 = stubcc.masm.moveWithPatch(ImmPtr(NULL), Registers::ArgReg1); OOL_STUBCALL(callingNew ? ic::NativeNew : ic::NativeCall); rejoin2 = stubcc.masm.jump(); } /* * If the call site goes to a closure over the same function, it will * generate an out-of-line stub that joins back here. */ callIC.hotPathLabel = masm.label(); uint32 flags = 0; if (callingNew) flags |= JSFRAME_CONSTRUCTING; InlineFrameAssembler inlFrame(masm, callIC, flags); callPatch.hasFastNcode = true; callPatch.fastNcodePatch = inlFrame.assemble(NULL); callIC.hotJump = masm.jump(); callIC.joinPoint = callPatch.joinPoint = masm.label(); addReturnSite(callPatch.joinPoint, __LINE__); if (lowerFunCallOrApply) uncachedCallPatch.joinPoint = callIC.joinPoint; masm.loadPtr(Address(JSFrameReg, JSStackFrame::offsetOfPrev()), JSFrameReg); /* * We've placed hotJump, joinPoint and hotPathLabel, and no other labels are located by offset * in the in-line path so we can check the IC space now. */ CHECK_IC_SPACE(); frame.popn(speculatedArgc + 2); frame.takeReg(JSReturnReg_Type); frame.takeReg(JSReturnReg_Data); frame.pushRegs(JSReturnReg_Type, JSReturnReg_Data); /* * Now that the frame state is set, generate the rejoin path. Note that, if * lowerFunCallOrApply, we cannot just call 'stubcc.rejoin' since the return * value has been placed at vp[1] which is not the stack address associated * with frame.peek(-1). */ callIC.slowJoinPoint = stubcc.masm.label(); rejoin1.linkTo(callIC.slowJoinPoint, &stubcc.masm); rejoin2.linkTo(callIC.slowJoinPoint, &stubcc.masm); JaegerSpew(JSpew_Insns, " ---- BEGIN SLOW RESTORE CODE ---- \n"); stubcc.masm.loadValueAsComponents(icRvalAddr, JSReturnReg_Type, JSReturnReg_Data); stubcc.crossJump(stubcc.masm.jump(), masm.label()); JaegerSpew(JSpew_Insns, " ---- END SLOW RESTORE CODE ---- \n"); CHECK_OOL_SPACE(); if (lowerFunCallOrApply) stubcc.crossJump(uncachedCallSlowRejoin, masm.label()); callICs.append(callIC); callPatches.append(callPatch); if (lowerFunCallOrApply) callPatches.append(uncachedCallPatch); applyTricks = NoApplyTricks; #endif } /* * This function must be called immediately after any instruction which could * cause a new JSStackFrame to be pushed and could lead to a new debug trap * being set. This includes any API callbacks and any scripted or native call. */ void mjit::Compiler::addCallSite(const InternalCallSite &site) { callSites.append(site); } void mjit::Compiler::restoreFrameRegs(Assembler &masm) { masm.loadPtr(FrameAddress(offsetof(VMFrame, regs.fp)), JSFrameReg); } bool mjit::Compiler::compareTwoValues(JSContext *cx, JSOp op, const Value &lhs, const Value &rhs) { JS_ASSERT(lhs.isPrimitive()); JS_ASSERT(rhs.isPrimitive()); if (lhs.isString() && rhs.isString()) { int32 cmp; CompareStrings(cx, lhs.toString(), rhs.toString(), &cmp); switch (op) { case JSOP_LT: return cmp < 0; case JSOP_LE: return cmp <= 0; case JSOP_GT: return cmp > 0; case JSOP_GE: return cmp >= 0; case JSOP_EQ: return cmp == 0; case JSOP_NE: return cmp != 0; default: JS_NOT_REACHED("NYI"); } } else { double ld, rd; /* These should be infallible w/ primitives. */ ValueToNumber(cx, lhs, &ld); ValueToNumber(cx, rhs, &rd); switch(op) { case JSOP_LT: return ld < rd; case JSOP_LE: return ld <= rd; case JSOP_GT: return ld > rd; case JSOP_GE: return ld >= rd; case JSOP_EQ: /* fall through */ case JSOP_NE: /* Special case null/undefined/void comparisons. */ if (lhs.isNullOrUndefined()) { if (rhs.isNullOrUndefined()) return op == JSOP_EQ; return op == JSOP_NE; } if (rhs.isNullOrUndefined()) return op == JSOP_NE; /* Normal return. */ return (op == JSOP_EQ) ? (ld == rd) : (ld != rd); default: JS_NOT_REACHED("NYI"); } } JS_NOT_REACHED("NYI"); return false; } bool mjit::Compiler::emitStubCmpOp(BoolStub stub, jsbytecode *target, JSOp fused) { prepareStubCall(Uses(2)); INLINE_STUBCALL(stub); frame.pop(); frame.pop(); if (!target) { frame.takeReg(Registers::ReturnReg); frame.pushTypedPayload(JSVAL_TYPE_BOOLEAN, Registers::ReturnReg); return true; } JS_ASSERT(fused == JSOP_IFEQ || fused == JSOP_IFNE); frame.syncAndForgetEverything(); Assembler::Condition cond = (fused == JSOP_IFEQ) ? Assembler::Zero : Assembler::NonZero; Jump j = masm.branchTest32(cond, Registers::ReturnReg, Registers::ReturnReg); return jumpAndTrace(j, target); } void mjit::Compiler::jsop_setprop_slow(JSAtom *atom, bool usePropCache) { prepareStubCall(Uses(2)); masm.move(ImmPtr(atom), Registers::ArgReg1); if (usePropCache) INLINE_STUBCALL(STRICT_VARIANT(stubs::SetName)); else INLINE_STUBCALL(STRICT_VARIANT(stubs::SetPropNoCache)); JS_STATIC_ASSERT(JSOP_SETNAME_LENGTH == JSOP_SETPROP_LENGTH); frame.shimmy(1); } void mjit::Compiler::jsop_getprop_slow(JSAtom *atom, bool usePropCache) { prepareStubCall(Uses(1)); if (usePropCache) { INLINE_STUBCALL(stubs::GetProp); } else { masm.move(ImmPtr(atom), Registers::ArgReg1); INLINE_STUBCALL(stubs::GetPropNoCache); } frame.pop(); frame.pushSynced(); } bool mjit::Compiler::jsop_callprop_slow(JSAtom *atom) { prepareStubCall(Uses(1)); masm.move(ImmPtr(atom), Registers::ArgReg1); INLINE_STUBCALL(stubs::CallProp); frame.pop(); frame.pushSynced(); frame.pushSynced(); return true; } bool mjit::Compiler::jsop_length() { FrameEntry *top = frame.peek(-1); if (top->isTypeKnown() && top->getKnownType() == JSVAL_TYPE_STRING) { if (top->isConstant()) { JSString *str = top->getValue().toString(); Value v; v.setNumber(uint32(str->length())); frame.pop(); frame.push(v); } else { RegisterID str = frame.ownRegForData(top); masm.loadPtr(Address(str, JSString::offsetOfLengthAndFlags()), str); masm.urshift32(Imm32(JSString::LENGTH_SHIFT), str); frame.pop(); frame.pushTypedPayload(JSVAL_TYPE_INT32, str); } return true; } #if defined JS_POLYIC return jsop_getprop(cx->runtime->atomState.lengthAtom); #else prepareStubCall(Uses(1)); INLINE_STUBCALL(stubs::Length); frame.pop(); frame.pushSynced(); return true; #endif } #ifdef JS_MONOIC void mjit::Compiler::passMICAddress(GlobalNameICInfo &ic) { ic.addrLabel = stubcc.masm.moveWithPatch(ImmPtr(NULL), Registers::ArgReg1); } #endif #if defined JS_POLYIC void mjit::Compiler::passICAddress(BaseICInfo *ic) { ic->paramAddr = stubcc.masm.moveWithPatch(ImmPtr(NULL), Registers::ArgReg1); } bool mjit::Compiler::jsop_getprop(JSAtom *atom, bool doTypeCheck, bool usePropCache) { FrameEntry *top = frame.peek(-1); /* If the incoming type will never PIC, take slow path. */ if (top->isTypeKnown() && top->getKnownType() != JSVAL_TYPE_OBJECT) { JS_ASSERT_IF(atom == cx->runtime->atomState.lengthAtom, top->getKnownType() != JSVAL_TYPE_STRING); jsop_getprop_slow(atom, usePropCache); return true; } /* * These two must be loaded first. The objReg because the string path * wants to read it, and the shapeReg because it could cause a spill that * the string path wouldn't sink back. */ RegisterID objReg = Registers::ReturnReg; RegisterID shapeReg = Registers::ReturnReg; if (atom == cx->runtime->atomState.lengthAtom) { objReg = frame.copyDataIntoReg(top); shapeReg = frame.allocReg(); } RESERVE_IC_SPACE(masm); PICGenInfo pic(ic::PICInfo::GET, JSOp(*PC), usePropCache); /* Guard that the type is an object. */ Label typeCheck; if (doTypeCheck && !top->isTypeKnown()) { RegisterID reg = frame.tempRegForType(top); pic.typeReg = reg; /* Start the hot path where it's easy to patch it. */ pic.fastPathStart = masm.label(); Jump j = masm.testObject(Assembler::NotEqual, reg); typeCheck = masm.label(); RETURN_IF_OOM(false); pic.typeCheck = stubcc.linkExit(j, Uses(1)); pic.hasTypeCheck = true; } else { pic.fastPathStart = masm.label(); pic.hasTypeCheck = false; pic.typeReg = Registers::ReturnReg; } if (atom != cx->runtime->atomState.lengthAtom) { objReg = frame.copyDataIntoReg(top); shapeReg = frame.allocReg(); } pic.shapeReg = shapeReg; pic.atom = atom; /* Guard on shape. */ masm.loadShape(objReg, shapeReg); pic.shapeGuard = masm.label(); DataLabel32 inlineShapeLabel; Jump j = masm.branch32WithPatch(Assembler::NotEqual, shapeReg, Imm32(int32(JSObjectMap::INVALID_SHAPE)), inlineShapeLabel); Label inlineShapeJump = masm.label(); RESERVE_OOL_SPACE(stubcc.masm); pic.slowPathStart = stubcc.linkExit(j, Uses(1)); stubcc.leave(); passICAddress(&pic); pic.slowPathCall = OOL_STUBCALL(ic::GetProp); CHECK_OOL_SPACE(); /* Load the base slot address. */ Label dslotsLoadLabel = masm.loadPtrWithPatchToLEA(Address(objReg, offsetof(JSObject, slots)), objReg); /* Copy the slot value to the expression stack. */ Address slot(objReg, 1 << 24); frame.pop(); Label fastValueLoad = masm.loadValueWithAddressOffsetPatch(slot, shapeReg, objReg); pic.fastPathRejoin = masm.label(); RETURN_IF_OOM(false); /* Initialize op labels. */ GetPropLabels &labels = pic.getPropLabels(); labels.setDslotsLoad(masm, pic.fastPathRejoin, dslotsLoadLabel); labels.setInlineShapeData(masm, pic.shapeGuard, inlineShapeLabel); labels.setValueLoad(masm, pic.fastPathRejoin, fastValueLoad); if (pic.hasTypeCheck) labels.setInlineTypeJump(masm, pic.fastPathStart, typeCheck); #ifdef JS_CPU_X64 labels.setInlineShapeJump(masm, inlineShapeLabel, inlineShapeJump); #else labels.setInlineShapeJump(masm, pic.shapeGuard, inlineShapeJump); #endif pic.objReg = objReg; frame.pushRegs(shapeReg, objReg); stubcc.rejoin(Changes(1)); pics.append(pic); return true; } bool mjit::Compiler::jsop_callprop_generic(JSAtom *atom) { FrameEntry *top = frame.peek(-1); /* * These two must be loaded first. The objReg because the string path * wants to read it, and the shapeReg because it could cause a spill that * the string path wouldn't sink back. */ RegisterID objReg = frame.copyDataIntoReg(top); RegisterID shapeReg = frame.allocReg(); PICGenInfo pic(ic::PICInfo::CALL, JSOp(*PC), true); pic.pc = PC; /* Guard that the type is an object. */ pic.typeReg = frame.copyTypeIntoReg(top); RESERVE_IC_SPACE(masm); /* Start the hot path where it's easy to patch it. */ pic.fastPathStart = masm.label(); /* * Guard that the value is an object. This part needs some extra gunk * because the leave() after the shape guard will emit a jump from this * path to the final call. We need a label in between that jump, which * will be the target of patched jumps in the PIC. */ Jump typeCheckJump = masm.testObject(Assembler::NotEqual, pic.typeReg); Label typeCheck = masm.label(); RETURN_IF_OOM(false); pic.typeCheck = stubcc.linkExit(typeCheckJump, Uses(1)); pic.hasTypeCheck = true; pic.objReg = objReg; pic.shapeReg = shapeReg; pic.atom = atom; /* * Store the type and object back. Don't bother keeping them in registers, * since a sync will be needed for the upcoming call. */ uint32 thisvSlot = frame.localSlots(); Address thisv = Address(JSFrameReg, sizeof(JSStackFrame) + thisvSlot * sizeof(Value)); #if defined JS_NUNBOX32 masm.storeValueFromComponents(pic.typeReg, pic.objReg, thisv); #elif defined JS_PUNBOX64 masm.orPtr(pic.objReg, pic.typeReg); masm.storePtr(pic.typeReg, thisv); #endif frame.freeReg(pic.typeReg); /* Guard on shape. */ masm.loadShape(objReg, shapeReg); pic.shapeGuard = masm.label(); DataLabel32 inlineShapeLabel; Jump j = masm.branch32WithPatch(Assembler::NotEqual, shapeReg, Imm32(int32(JSObjectMap::INVALID_SHAPE)), inlineShapeLabel); Label inlineShapeJump = masm.label(); /* Slow path. */ RESERVE_OOL_SPACE(stubcc.masm); pic.slowPathStart = stubcc.linkExit(j, Uses(1)); stubcc.leave(); passICAddress(&pic); pic.slowPathCall = OOL_STUBCALL(ic::CallProp); CHECK_OOL_SPACE(); /* Adjust the frame. None of this will generate code. */ frame.pop(); frame.pushRegs(shapeReg, objReg); frame.pushSynced(); /* Load the base slot address. */ Label dslotsLoadLabel = masm.loadPtrWithPatchToLEA(Address(objReg, offsetof(JSObject, slots)), objReg); /* Copy the slot value to the expression stack. */ Address slot(objReg, 1 << 24); Label fastValueLoad = masm.loadValueWithAddressOffsetPatch(slot, shapeReg, objReg); pic.fastPathRejoin = masm.label(); RETURN_IF_OOM(false); /* * Initialize op labels. We use GetPropLabels here because we have the same patching * requirements for CallProp. */ GetPropLabels &labels = pic.getPropLabels(); labels.setDslotsLoadOffset(masm.differenceBetween(pic.fastPathRejoin, dslotsLoadLabel)); labels.setInlineShapeOffset(masm.differenceBetween(pic.shapeGuard, inlineShapeLabel)); labels.setValueLoad(masm, pic.fastPathRejoin, fastValueLoad); labels.setInlineTypeJump(masm, pic.fastPathStart, typeCheck); #ifdef JS_CPU_X64 labels.setInlineShapeJump(masm, inlineShapeLabel, inlineShapeJump); #else labels.setInlineShapeJump(masm, pic.shapeGuard, inlineShapeJump); #endif stubcc.rejoin(Changes(2)); pics.append(pic); return true; } bool mjit::Compiler::jsop_callprop_str(JSAtom *atom) { if (!script->compileAndGo) { jsop_callprop_slow(atom); return true; } /* * Bake in String.prototype. This is safe because of compileAndGo. * We must pass an explicit scope chain only because JSD calls into * here via the recompiler with a dummy context, and we need to use * the global object for the script we are now compiling. */ JSObject *obj; if (!js_GetClassPrototype(cx, &fp->scopeChain(), JSProto_String, &obj)) return false; /* Force into a register because getprop won't expect a constant. */ RegisterID reg = frame.allocReg(); masm.move(ImmPtr(obj), reg); frame.pushTypedPayload(JSVAL_TYPE_OBJECT, reg); /* Get the property. */ if (!jsop_getprop(atom)) return false; /* Perform a swap. */ frame.dup2(); frame.shift(-3); frame.shift(-1); /* 4) Test if the function can take a primitive. */ #ifdef DEBUG FrameEntry *funFe = frame.peek(-2); #endif JS_ASSERT(!funFe->isTypeKnown()); /* * See bug 584579 - need to forget string type, since wrapping could * create an object. forgetType() alone is not valid because it cannot be * used on copies or constants. */ RegisterID strReg; FrameEntry *strFe = frame.peek(-1); if (strFe->isConstant()) { strReg = frame.allocReg(); masm.move(ImmPtr(strFe->getValue().toString()), strReg); } else { strReg = frame.ownRegForData(strFe); } frame.pop(); frame.pushTypedPayload(JSVAL_TYPE_STRING, strReg); frame.forgetType(frame.peek(-1)); return true; } bool mjit::Compiler::jsop_callprop_obj(JSAtom *atom) { FrameEntry *top = frame.peek(-1); PICGenInfo pic(ic::PICInfo::CALL, JSOp(*PC), true); JS_ASSERT(top->isTypeKnown()); JS_ASSERT(top->getKnownType() == JSVAL_TYPE_OBJECT); RESERVE_IC_SPACE(masm); pic.pc = PC; pic.fastPathStart = masm.label(); pic.hasTypeCheck = false; pic.typeReg = Registers::ReturnReg; RegisterID objReg = frame.copyDataIntoReg(top); RegisterID shapeReg = frame.allocReg(); pic.shapeReg = shapeReg; pic.atom = atom; /* Guard on shape. */ masm.loadShape(objReg, shapeReg); pic.shapeGuard = masm.label(); DataLabel32 inlineShapeLabel; Jump j = masm.branch32WithPatch(Assembler::NotEqual, shapeReg, Imm32(int32(JSObjectMap::INVALID_SHAPE)), inlineShapeLabel); Label inlineShapeJump = masm.label(); /* Slow path. */ RESERVE_OOL_SPACE(stubcc.masm); pic.slowPathStart = stubcc.linkExit(j, Uses(1)); stubcc.leave(); passICAddress(&pic); pic.slowPathCall = OOL_STUBCALL(ic::CallProp); CHECK_OOL_SPACE(); /* Load the base slot address. */ Label dslotsLoadLabel = masm.loadPtrWithPatchToLEA(Address(objReg, offsetof(JSObject, slots)), objReg); /* Copy the slot value to the expression stack. */ Address slot(objReg, 1 << 24); Label fastValueLoad = masm.loadValueWithAddressOffsetPatch(slot, shapeReg, objReg); pic.fastPathRejoin = masm.label(); pic.objReg = objReg; /* * 1) Dup the |this| object. * 2) Push the property value onto the stack. * 3) Move the value below the dup'd |this|, uncopying it. This could * generate code, thus the fastPathRejoin label being prior. This is safe * as a stack transition, because JSOP_CALLPROP has JOF_TMPSLOT. It is * also safe for correctness, because if we know the LHS is an object, it * is the resulting vp[1]. */ frame.dup(); frame.pushRegs(shapeReg, objReg); frame.shift(-2); /* * Assert correctness of hardcoded offsets. * No type guard: type is asserted. */ RETURN_IF_OOM(false); GetPropLabels &labels = pic.getPropLabels(); labels.setDslotsLoadOffset(masm.differenceBetween(pic.fastPathRejoin, dslotsLoadLabel)); labels.setInlineShapeOffset(masm.differenceBetween(pic.shapeGuard, inlineShapeLabel)); labels.setValueLoad(masm, pic.fastPathRejoin, fastValueLoad); #ifdef JS_CPU_X64 labels.setInlineShapeJump(masm, inlineShapeLabel, inlineShapeJump); #else labels.setInlineShapeJump(masm, pic.shapeGuard, inlineShapeJump); #endif stubcc.rejoin(Changes(2)); pics.append(pic); return true; } bool mjit::Compiler::jsop_callprop(JSAtom *atom) { FrameEntry *top = frame.peek(-1); /* If the incoming type will never PIC, take slow path. */ if (top->isTypeKnown() && top->getKnownType() != JSVAL_TYPE_OBJECT) { if (top->getKnownType() == JSVAL_TYPE_STRING) return jsop_callprop_str(atom); return jsop_callprop_slow(atom); } if (top->isTypeKnown()) return jsop_callprop_obj(atom); return jsop_callprop_generic(atom); } bool mjit::Compiler::jsop_setprop(JSAtom *atom, bool usePropCache) { FrameEntry *lhs = frame.peek(-2); FrameEntry *rhs = frame.peek(-1); /* If the incoming type will never PIC, take slow path. */ if (lhs->isTypeKnown() && lhs->getKnownType() != JSVAL_TYPE_OBJECT) { jsop_setprop_slow(atom, usePropCache); return true; } JSOp op = JSOp(*PC); ic::PICInfo::Kind kind = (op == JSOP_SETMETHOD) ? ic::PICInfo::SETMETHOD : ic::PICInfo::SET; PICGenInfo pic(kind, op, usePropCache); pic.atom = atom; RESERVE_IC_SPACE(masm); RESERVE_OOL_SPACE(stubcc.masm); /* Guard that the type is an object. */ Jump typeCheck; if (!lhs->isTypeKnown()) { RegisterID reg = frame.tempRegForType(lhs); pic.typeReg = reg; /* Start the hot path where it's easy to patch it. */ pic.fastPathStart = masm.label(); Jump j = masm.testObject(Assembler::NotEqual, reg); pic.typeCheck = stubcc.linkExit(j, Uses(2)); stubcc.leave(); stubcc.masm.move(ImmPtr(atom), Registers::ArgReg1); if (usePropCache) OOL_STUBCALL(STRICT_VARIANT(stubs::SetName)); else OOL_STUBCALL(STRICT_VARIANT(stubs::SetPropNoCache)); typeCheck = stubcc.masm.jump(); pic.hasTypeCheck = true; } else { pic.fastPathStart = masm.label(); pic.hasTypeCheck = false; pic.typeReg = Registers::ReturnReg; } /* Get the object into a mutable register. */ RegisterID objReg = frame.copyDataIntoReg(lhs); pic.objReg = objReg; /* Get info about the RHS and pin it. */ ValueRemat vr; frame.pinEntry(rhs, vr); pic.vr = vr; RegisterID shapeReg = frame.allocReg(); pic.shapeReg = shapeReg; frame.unpinEntry(vr); /* Guard on shape. */ masm.loadShape(objReg, shapeReg); pic.shapeGuard = masm.label(); DataLabel32 inlineShapeData; Jump j = masm.branch32WithPatch(Assembler::NotEqual, shapeReg, Imm32(int32(JSObjectMap::INVALID_SHAPE)), inlineShapeData); Label afterInlineShapeJump = masm.label(); /* Slow path. */ { pic.slowPathStart = stubcc.linkExit(j, Uses(2)); stubcc.leave(); passICAddress(&pic); pic.slowPathCall = OOL_STUBCALL(ic::SetProp); CHECK_OOL_SPACE(); } /* Load dslots. */ Label dslotsLoadLabel = masm.loadPtrWithPatchToLEA(Address(objReg, offsetof(JSObject, slots)), objReg); /* Store RHS into object slot. */ Address slot(objReg, 1 << 24); DataLabel32 inlineValueStore = masm.storeValueWithAddressOffsetPatch(vr, slot); pic.fastPathRejoin = masm.label(); frame.freeReg(objReg); frame.freeReg(shapeReg); /* "Pop under", taking out object (LHS) and leaving RHS. */ frame.shimmy(1); /* Finish slow path. */ { if (pic.hasTypeCheck) typeCheck.linkTo(stubcc.masm.label(), &stubcc.masm); stubcc.rejoin(Changes(1)); } RETURN_IF_OOM(false); SetPropLabels &labels = pic.setPropLabels(); labels.setInlineShapeData(masm, pic.shapeGuard, inlineShapeData); labels.setDslotsLoad(masm, pic.fastPathRejoin, dslotsLoadLabel, vr); labels.setInlineValueStore(masm, pic.fastPathRejoin, inlineValueStore, vr); labels.setInlineShapeJump(masm, pic.shapeGuard, afterInlineShapeJump); pics.append(pic); return true; } void mjit::Compiler::jsop_name(JSAtom *atom) { PICGenInfo pic(ic::PICInfo::NAME, JSOp(*PC), true); RESERVE_IC_SPACE(masm); pic.shapeReg = frame.allocReg(); pic.objReg = frame.allocReg(); pic.typeReg = Registers::ReturnReg; pic.atom = atom; pic.hasTypeCheck = false; pic.fastPathStart = masm.label(); /* There is no inline implementation, so we always jump to the slow path or to a stub. */ pic.shapeGuard = masm.label(); Jump inlineJump = masm.jump(); { RESERVE_OOL_SPACE(stubcc.masm); pic.slowPathStart = stubcc.linkExit(inlineJump, Uses(0)); stubcc.leave(); passICAddress(&pic); pic.slowPathCall = OOL_STUBCALL(ic::Name); CHECK_OOL_SPACE(); } pic.fastPathRejoin = masm.label(); /* Initialize op labels. */ ScopeNameLabels &labels = pic.scopeNameLabels(); labels.setInlineJump(masm, pic.fastPathStart, inlineJump); frame.pushRegs(pic.shapeReg, pic.objReg); stubcc.rejoin(Changes(1)); pics.append(pic); } bool mjit::Compiler::jsop_xname(JSAtom *atom) { PICGenInfo pic(ic::PICInfo::XNAME, JSOp(*PC), true); FrameEntry *fe = frame.peek(-1); if (fe->isNotType(JSVAL_TYPE_OBJECT)) { return jsop_getprop(atom); } if (!fe->isTypeKnown()) { Jump notObject = frame.testObject(Assembler::NotEqual, fe); stubcc.linkExit(notObject, Uses(1)); } RESERVE_IC_SPACE(masm); pic.shapeReg = frame.allocReg(); pic.objReg = frame.copyDataIntoReg(fe); pic.typeReg = Registers::ReturnReg; pic.atom = atom; pic.hasTypeCheck = false; pic.fastPathStart = masm.label(); /* There is no inline implementation, so we always jump to the slow path or to a stub. */ pic.shapeGuard = masm.label(); Jump inlineJump = masm.jump(); { RESERVE_OOL_SPACE(stubcc.masm); pic.slowPathStart = stubcc.linkExit(inlineJump, Uses(1)); stubcc.leave(); passICAddress(&pic); pic.slowPathCall = OOL_STUBCALL(ic::XName); CHECK_OOL_SPACE(); } pic.fastPathRejoin = masm.label(); RETURN_IF_OOM(false); /* Initialize op labels. */ ScopeNameLabels &labels = pic.scopeNameLabels(); labels.setInlineJumpOffset(masm.differenceBetween(pic.fastPathStart, inlineJump)); frame.pop(); frame.pushRegs(pic.shapeReg, pic.objReg); stubcc.rejoin(Changes(1)); pics.append(pic); return true; } void mjit::Compiler::jsop_bindname(JSAtom *atom, bool usePropCache) { PICGenInfo pic(ic::PICInfo::BIND, JSOp(*PC), usePropCache); // This code does not check the frame flags to see if scopeChain has been // set. Rather, it relies on the up-front analysis statically determining // whether BINDNAME can be used, which reifies the scope chain at the // prologue. JS_ASSERT(analysis->usesScopeChain()); pic.shapeReg = frame.allocReg(); pic.objReg = frame.allocReg(); pic.typeReg = Registers::ReturnReg; pic.atom = atom; pic.hasTypeCheck = false; RESERVE_IC_SPACE(masm); pic.fastPathStart = masm.label(); Address parent(pic.objReg, offsetof(JSObject, parent)); masm.loadPtr(Address(JSFrameReg, JSStackFrame::offsetOfScopeChain()), pic.objReg); pic.shapeGuard = masm.label(); Jump inlineJump = masm.branchPtr(Assembler::NotEqual, masm.payloadOf(parent), ImmPtr(0)); { RESERVE_OOL_SPACE(stubcc.masm); pic.slowPathStart = stubcc.linkExit(inlineJump, Uses(0)); stubcc.leave(); passICAddress(&pic); pic.slowPathCall = OOL_STUBCALL(ic::BindName); CHECK_OOL_SPACE(); } pic.fastPathRejoin = masm.label(); /* Initialize op labels. */ BindNameLabels &labels = pic.bindNameLabels(); labels.setInlineJump(masm, pic.shapeGuard, inlineJump); frame.pushTypedPayload(JSVAL_TYPE_OBJECT, pic.objReg); frame.freeReg(pic.shapeReg); stubcc.rejoin(Changes(1)); pics.append(pic); } #else /* !JS_POLYIC */ void mjit::Compiler::jsop_name(JSAtom *atom) { prepareStubCall(Uses(0)); INLINE_STUBCALL(stubs::Name); frame.pushSynced(); } bool mjit::Compiler::jsop_xname(JSAtom *atom) { return jsop_getprop(atom); } bool mjit::Compiler::jsop_getprop(JSAtom *atom, bool typecheck, bool usePropCache) { jsop_getprop_slow(atom, usePropCache); return true; } bool mjit::Compiler::jsop_callprop(JSAtom *atom) { return jsop_callprop_slow(atom); } bool mjit::Compiler::jsop_setprop(JSAtom *atom, bool usePropCache) { jsop_setprop_slow(atom, usePropCache); return true; } void mjit::Compiler::jsop_bindname(JSAtom *atom, bool usePropCache) { RegisterID reg = frame.allocReg(); Address scopeChain(JSFrameReg, JSStackFrame::offsetOfScopeChain()); masm.loadPtr(scopeChain, reg); Address address(reg, offsetof(JSObject, parent)); Jump j = masm.branchPtr(Assembler::NotEqual, masm.payloadOf(address), ImmPtr(0)); stubcc.linkExit(j, Uses(0)); stubcc.leave(); if (usePropCache) { OOL_STUBCALL(stubs::BindName); } else { stubcc.masm.move(ImmPtr(atom), Registers::ArgReg1); OOL_STUBCALL(stubs::BindNameNoCache); } frame.pushTypedPayload(JSVAL_TYPE_OBJECT, reg); stubcc.rejoin(Changes(1)); } #endif void mjit::Compiler::jsop_this() { frame.pushThis(); /* * In strict mode code, we don't wrap 'this'. * In direct-call eval code, we wrapped 'this' before entering the eval. * In global code, 'this' is always an object. */ if (fun && !script->strictModeCode) { FrameEntry *thisFe = frame.peek(-1); if (!thisFe->isTypeKnown()) { Jump notObj = frame.testObject(Assembler::NotEqual, thisFe); stubcc.linkExit(notObj, Uses(1)); stubcc.leave(); OOL_STUBCALL(stubs::This); stubcc.rejoin(Changes(1)); // Now we know that |this| is an object. frame.pop(); frame.learnThisIsObject(); frame.pushThis(); } JS_ASSERT(thisFe->isType(JSVAL_TYPE_OBJECT)); } } void mjit::Compiler::jsop_gnameinc(JSOp op, VoidStubAtom stub, uint32 index) { JSAtom *atom = script->getAtom(index); #if defined JS_MONOIC jsbytecode *next = &PC[JSOP_GNAMEINC_LENGTH]; bool pop = (JSOp(*next) == JSOP_POP) && !analysis->jumpTarget(next); int amt = (op == JSOP_GNAMEINC || op == JSOP_INCGNAME) ? -1 : 1; if (pop || (op == JSOP_INCGNAME || op == JSOP_DECGNAME)) { /* These cases are easy, the original value is not observed. */ jsop_getgname(index); // V frame.push(Int32Value(amt)); // V 1 /* Use sub since it calls ValueToNumber instead of string concat. */ jsop_binary(JSOP_SUB, stubs::Sub); // N+1 jsop_bindgname(); // V+1 OBJ frame.dup2(); // V+1 OBJ V+1 OBJ frame.shift(-3); // OBJ OBJ V+1 frame.shift(-1); // OBJ V+1 jsop_setgname(atom, false); // V+1 if (pop) frame.pop(); } else { /* The pre-value is observed, making this more tricky. */ jsop_getgname(index); // V jsop_pos(); // N frame.dup(); // N N frame.push(Int32Value(-amt)); // N N 1 jsop_binary(JSOP_ADD, stubs::Add); // N N+1 jsop_bindgname(); // N N+1 OBJ frame.dup2(); // N N+1 OBJ N+1 OBJ frame.shift(-3); // N OBJ OBJ N+1 frame.shift(-1); // N OBJ N+1 jsop_setgname(atom, false); // N N+1 frame.pop(); // N } if (pop) PC += JSOP_POP_LENGTH; #else prepareStubCall(Uses(0)); masm.move(ImmPtr(atom), Registers::ArgReg1); INLINE_STUBCALL(stub); frame.pushSynced(); #endif PC += JSOP_GNAMEINC_LENGTH; } bool mjit::Compiler::jsop_nameinc(JSOp op, VoidStubAtom stub, uint32 index) { JSAtom *atom = script->getAtom(index); #if defined JS_POLYIC jsbytecode *next = &PC[JSOP_NAMEINC_LENGTH]; bool pop = (JSOp(*next) == JSOP_POP) && !analysis->jumpTarget(next); int amt = (op == JSOP_NAMEINC || op == JSOP_INCNAME) ? -1 : 1; if (pop || (op == JSOP_INCNAME || op == JSOP_DECNAME)) { /* These cases are easy, the original value is not observed. */ jsop_name(atom); // V frame.push(Int32Value(amt)); // V 1 /* Use sub since it calls ValueToNumber instead of string concat. */ jsop_binary(JSOP_SUB, stubs::Sub); // N+1 jsop_bindname(atom, false); // V+1 OBJ frame.dup2(); // V+1 OBJ V+1 OBJ frame.shift(-3); // OBJ OBJ V+1 frame.shift(-1); // OBJ V+1 if (!jsop_setprop(atom, false)) return false; // V+1 if (pop) frame.pop(); } else { /* The pre-value is observed, making this more tricky. */ jsop_name(atom); // V jsop_pos(); // N frame.dup(); // N N frame.push(Int32Value(-amt)); // N N 1 jsop_binary(JSOP_ADD, stubs::Add); // N N+1 jsop_bindname(atom, false); // N N+1 OBJ frame.dup2(); // N N+1 OBJ N+1 OBJ frame.shift(-3); // N OBJ OBJ N+1 frame.shift(-1); // N OBJ N+1 if (!jsop_setprop(atom, false)) return false; // N N+1 frame.pop(); // N } if (pop) PC += JSOP_POP_LENGTH; #else prepareStubCall(Uses(0)); masm.move(ImmPtr(atom), Registers::ArgReg1); INLINE_STUBCALL(stub); frame.pushSynced(); #endif PC += JSOP_NAMEINC_LENGTH; return true; } bool mjit::Compiler::jsop_propinc(JSOp op, VoidStubAtom stub, uint32 index) { JSAtom *atom = script->getAtom(index); #if defined JS_POLYIC FrameEntry *objFe = frame.peek(-1); if (!objFe->isTypeKnown() || objFe->getKnownType() == JSVAL_TYPE_OBJECT) { jsbytecode *next = &PC[JSOP_PROPINC_LENGTH]; bool pop = (JSOp(*next) == JSOP_POP) && !analysis->jumpTarget(next); int amt = (op == JSOP_PROPINC || op == JSOP_INCPROP) ? -1 : 1; if (pop || (op == JSOP_INCPROP || op == JSOP_DECPROP)) { /* These cases are easy, the original value is not observed. */ frame.dup(); // OBJ OBJ if (!jsop_getprop(atom)) return false; // OBJ V frame.push(Int32Value(amt)); // OBJ V 1 /* Use sub since it calls ValueToNumber instead of string concat. */ jsop_binary(JSOP_SUB, stubs::Sub); // OBJ V+1 if (!jsop_setprop(atom, false)) return false; // V+1 if (pop) frame.pop(); } else { /* The pre-value is observed, making this more tricky. */ frame.dup(); // OBJ OBJ if (!jsop_getprop(atom)) return false; // OBJ V jsop_pos(); // OBJ N frame.dup(); // OBJ N N frame.push(Int32Value(-amt)); // OBJ N N 1 jsop_binary(JSOP_ADD, stubs::Add); // OBJ N N+1 frame.dupAt(-3); // OBJ N N+1 OBJ frame.dupAt(-2); // OBJ N N+1 OBJ N+1 if (!jsop_setprop(atom, false)) return false; // OBJ N N+1 N+1 frame.popn(2); // OBJ N frame.shimmy(1); // N } if (pop) PC += JSOP_POP_LENGTH; } else #endif { prepareStubCall(Uses(1)); masm.move(ImmPtr(atom), Registers::ArgReg1); INLINE_STUBCALL(stub); frame.pop(); frame.pushSynced(); } PC += JSOP_PROPINC_LENGTH; return true; } bool mjit::Compiler::iter(uintN flags) { FrameEntry *fe = frame.peek(-1); /* * Stub the call if this is not a simple 'for in' loop or if the iterated * value is known to not be an object. */ if ((flags != JSITER_ENUMERATE) || fe->isNotType(JSVAL_TYPE_OBJECT)) { prepareStubCall(Uses(1)); masm.move(Imm32(flags), Registers::ArgReg1); INLINE_STUBCALL(stubs::Iter); frame.pop(); frame.pushSynced(); return true; } if (!fe->isTypeKnown()) { Jump notObject = frame.testObject(Assembler::NotEqual, fe); stubcc.linkExit(notObject, Uses(1)); } RegisterID reg = frame.tempRegForData(fe); frame.pinReg(reg); RegisterID ioreg = frame.allocReg(); /* Will hold iterator JSObject */ RegisterID nireg = frame.allocReg(); /* Will hold NativeIterator */ RegisterID T1 = frame.allocReg(); RegisterID T2 = frame.allocReg(); frame.unpinReg(reg); /* Fetch the most recent iterator. */ masm.loadPtr(&script->compartment->nativeIterCache.last, ioreg); /* Test for NULL. */ Jump nullIterator = masm.branchTest32(Assembler::Zero, ioreg, ioreg); stubcc.linkExit(nullIterator, Uses(1)); /* Get NativeIterator from iter obj. */ masm.loadObjPrivate(ioreg, nireg); /* Test for active iterator. */ Address flagsAddr(nireg, offsetof(NativeIterator, flags)); masm.load32(flagsAddr, T1); Jump activeIterator = masm.branchTest32(Assembler::NonZero, T1, Imm32(JSITER_ACTIVE|JSITER_UNREUSABLE)); stubcc.linkExit(activeIterator, Uses(1)); /* Compare shape of object with iterator. */ masm.loadShape(reg, T1); masm.loadPtr(Address(nireg, offsetof(NativeIterator, shapes_array)), T2); masm.load32(Address(T2, 0), T2); Jump mismatchedObject = masm.branch32(Assembler::NotEqual, T1, T2); stubcc.linkExit(mismatchedObject, Uses(1)); /* Compare shape of object's prototype with iterator. */ masm.loadPtr(Address(reg, offsetof(JSObject, proto)), T1); masm.loadShape(T1, T1); masm.loadPtr(Address(nireg, offsetof(NativeIterator, shapes_array)), T2); masm.load32(Address(T2, sizeof(uint32)), T2); Jump mismatchedProto = masm.branch32(Assembler::NotEqual, T1, T2); stubcc.linkExit(mismatchedProto, Uses(1)); /* * Compare object's prototype's prototype with NULL. The last native * iterator will always have a prototype chain length of one * (i.e. it must be a plain object), so we do not need to generate * a loop here. */ masm.loadPtr(Address(reg, offsetof(JSObject, proto)), T1); masm.loadPtr(Address(T1, offsetof(JSObject, proto)), T1); Jump overlongChain = masm.branchPtr(Assembler::NonZero, T1, T1); stubcc.linkExit(overlongChain, Uses(1)); /* Found a match with the most recent iterator. Hooray! */ /* Mark iterator as active. */ masm.storePtr(reg, Address(nireg, offsetof(NativeIterator, obj))); masm.load32(flagsAddr, T1); masm.or32(Imm32(JSITER_ACTIVE), T1); masm.store32(T1, flagsAddr); /* Chain onto the active iterator stack. */ masm.loadPtr(FrameAddress(offsetof(VMFrame, cx)), T1); masm.loadPtr(Address(T1, offsetof(JSContext, enumerators)), T2); masm.storePtr(T2, Address(nireg, offsetof(NativeIterator, next))); masm.storePtr(ioreg, Address(T1, offsetof(JSContext, enumerators))); frame.freeReg(nireg); frame.freeReg(T1); frame.freeReg(T2); stubcc.leave(); stubcc.masm.move(Imm32(flags), Registers::ArgReg1); OOL_STUBCALL(stubs::Iter); /* Push the iterator object. */ frame.pop(); frame.pushTypedPayload(JSVAL_TYPE_OBJECT, ioreg); stubcc.rejoin(Changes(1)); return true; } /* * This big nasty function emits a fast-path for native iterators, producing * a temporary value on the stack for FORLOCAL,ARG,GLOBAL,etc ops to use. */ void mjit::Compiler::iterNext() { FrameEntry *fe = frame.peek(-1); RegisterID reg = frame.tempRegForData(fe); /* Is it worth trying to pin this longer? Prolly not. */ frame.pinReg(reg); RegisterID T1 = frame.allocReg(); frame.unpinReg(reg); /* Test clasp */ Jump notFast = masm.testObjClass(Assembler::NotEqual, reg, &js_IteratorClass); stubcc.linkExit(notFast, Uses(1)); /* Get private from iter obj. */ masm.loadObjPrivate(reg, T1); RegisterID T3 = frame.allocReg(); RegisterID T4 = frame.allocReg(); /* Test for a value iterator, which could come through an Iterator object. */ masm.load32(Address(T1, offsetof(NativeIterator, flags)), T3); notFast = masm.branchTest32(Assembler::NonZero, T3, Imm32(JSITER_FOREACH)); stubcc.linkExit(notFast, Uses(1)); RegisterID T2 = frame.allocReg(); /* Get cursor. */ masm.loadPtr(Address(T1, offsetof(NativeIterator, props_cursor)), T2); /* Test if the jsid is a string. */ masm.loadPtr(T2, T3); masm.move(T3, T4); masm.andPtr(Imm32(JSID_TYPE_MASK), T4); notFast = masm.branchTestPtr(Assembler::NonZero, T4, T4); stubcc.linkExit(notFast, Uses(1)); /* It's safe to increase the cursor now. */ masm.addPtr(Imm32(sizeof(jsid)), T2, T4); masm.storePtr(T4, Address(T1, offsetof(NativeIterator, props_cursor))); frame.freeReg(T4); frame.freeReg(T1); frame.freeReg(T2); stubcc.leave(); OOL_STUBCALL(stubs::IterNext); frame.pushUntypedPayload(JSVAL_TYPE_STRING, T3); /* Join with the stub call. */ stubcc.rejoin(Changes(1)); } bool mjit::Compiler::iterMore() { FrameEntry *fe = frame.peek(-1); RegisterID reg = frame.tempRegForData(fe); frame.pinReg(reg); RegisterID T1 = frame.allocReg(); frame.unpinReg(reg); /* Test clasp */ Jump notFast = masm.testObjClass(Assembler::NotEqual, reg, &js_IteratorClass); stubcc.linkExitForBranch(notFast); /* Get private from iter obj. */ masm.loadObjPrivate(reg, T1); /* Test that the iterator supports fast iteration. */ notFast = masm.branchTest32(Assembler::NonZero, Address(T1, offsetof(NativeIterator, flags)), Imm32(JSITER_FOREACH)); stubcc.linkExitForBranch(notFast); /* Get props_cursor, test */ RegisterID T2 = frame.allocReg(); frame.syncAndForgetEverything(); masm.loadPtr(Address(T1, offsetof(NativeIterator, props_cursor)), T2); masm.loadPtr(Address(T1, offsetof(NativeIterator, props_end)), T1); Jump jFast = masm.branchPtr(Assembler::LessThan, T2, T1); jsbytecode *target = &PC[JSOP_MOREITER_LENGTH]; JSOp next = JSOp(*target); JS_ASSERT(next == JSOP_IFNE || next == JSOP_IFNEX); target += (next == JSOP_IFNE) ? GET_JUMP_OFFSET(target) : GET_JUMPX_OFFSET(target); stubcc.leave(); OOL_STUBCALL(stubs::IterMore); Jump j = stubcc.masm.branchTest32(Assembler::NonZero, Registers::ReturnReg, Registers::ReturnReg); PC += JSOP_MOREITER_LENGTH; PC += js_CodeSpec[next].length; stubcc.rejoin(Changes(1)); return jumpAndTrace(jFast, target, &j); } void mjit::Compiler::iterEnd() { FrameEntry *fe= frame.peek(-1); RegisterID reg = frame.tempRegForData(fe); frame.pinReg(reg); RegisterID T1 = frame.allocReg(); frame.unpinReg(reg); /* Test clasp */ Jump notIterator = masm.testObjClass(Assembler::NotEqual, reg, &js_IteratorClass); stubcc.linkExit(notIterator, Uses(1)); /* Get private from iter obj. */ masm.loadObjPrivate(reg, T1); RegisterID T2 = frame.allocReg(); /* Load flags. */ Address flagAddr(T1, offsetof(NativeIterator, flags)); masm.loadPtr(flagAddr, T2); /* Test for a normal enumerate iterator. */ Jump notEnumerate = masm.branchTest32(Assembler::Zero, T2, Imm32(JSITER_ENUMERATE)); stubcc.linkExit(notEnumerate, Uses(1)); /* Clear active bit. */ masm.and32(Imm32(~JSITER_ACTIVE), T2); masm.storePtr(T2, flagAddr); /* Reset property cursor. */ masm.loadPtr(Address(T1, offsetof(NativeIterator, props_array)), T2); masm.storePtr(T2, Address(T1, offsetof(NativeIterator, props_cursor))); /* Advance enumerators list. */ masm.loadPtr(FrameAddress(offsetof(VMFrame, cx)), T2); masm.loadPtr(Address(T1, offsetof(NativeIterator, next)), T1); masm.storePtr(T1, Address(T2, offsetof(JSContext, enumerators))); frame.freeReg(T1); frame.freeReg(T2); stubcc.leave(); OOL_STUBCALL(stubs::EndIter); frame.pop(); stubcc.rejoin(Changes(1)); } void mjit::Compiler::jsop_eleminc(JSOp op, VoidStub stub) { prepareStubCall(Uses(2)); INLINE_STUBCALL(stub); frame.popn(2); frame.pushSynced(); } void mjit::Compiler::jsop_getgname_slow(uint32 index) { prepareStubCall(Uses(0)); INLINE_STUBCALL(stubs::GetGlobalName); frame.pushSynced(); } void mjit::Compiler::jsop_bindgname() { if (script->compileAndGo && globalObj) { frame.push(ObjectValue(*globalObj)); return; } /* :TODO: this is slower than it needs to be. */ prepareStubCall(Uses(0)); INLINE_STUBCALL(stubs::BindGlobalName); frame.takeReg(Registers::ReturnReg); frame.pushTypedPayload(JSVAL_TYPE_OBJECT, Registers::ReturnReg); } void mjit::Compiler::jsop_getgname(uint32 index) { /* Optimize undefined, NaN and Infinity. */ JSAtom *atom = script->getAtom(index); if (atom == cx->runtime->atomState.typeAtoms[JSTYPE_VOID]) { frame.push(UndefinedValue()); return; } if (atom == cx->runtime->atomState.NaNAtom) { frame.push(cx->runtime->NaNValue); return; } if (atom == cx->runtime->atomState.InfinityAtom) { frame.push(cx->runtime->positiveInfinityValue); return; } #if defined JS_MONOIC jsop_bindgname(); FrameEntry *fe = frame.peek(-1); JS_ASSERT(fe->isTypeKnown() && fe->getKnownType() == JSVAL_TYPE_OBJECT); GetGlobalNameICInfo ic; RESERVE_IC_SPACE(masm); RegisterID objReg; Jump shapeGuard; ic.usePropertyCache = true; ic.fastPathStart = masm.label(); if (fe->isConstant()) { JSObject *obj = &fe->getValue().toObject(); frame.pop(); JS_ASSERT(obj->isNative()); objReg = frame.allocReg(); masm.load32FromImm(&obj->objShape, objReg); shapeGuard = masm.branch32WithPatch(Assembler::NotEqual, objReg, Imm32(int32(JSObjectMap::INVALID_SHAPE)), ic.shape); masm.move(ImmPtr(obj), objReg); } else { objReg = frame.ownRegForData(fe); frame.pop(); RegisterID reg = frame.allocReg(); masm.loadShape(objReg, reg); shapeGuard = masm.branch32WithPatch(Assembler::NotEqual, reg, Imm32(int32(JSObjectMap::INVALID_SHAPE)), ic.shape); frame.freeReg(reg); } stubcc.linkExit(shapeGuard, Uses(0)); stubcc.leave(); passMICAddress(ic); ic.slowPathCall = OOL_STUBCALL(ic::GetGlobalName); /* Garbage value. */ uint32 slot = 1 << 24; masm.loadPtr(Address(objReg, offsetof(JSObject, slots)), objReg); Address address(objReg, slot); /* Allocate any register other than objReg. */ RegisterID treg = frame.allocReg(); /* After dreg is loaded, it's safe to clobber objReg. */ RegisterID dreg = objReg; ic.load = masm.loadValueWithAddressOffsetPatch(address, treg, dreg); frame.pushRegs(treg, dreg); stubcc.rejoin(Changes(1)); getGlobalNames.append(ic); #else jsop_getgname_slow(index); #endif } void mjit::Compiler::jsop_setgname_slow(JSAtom *atom, bool usePropertyCache) { prepareStubCall(Uses(2)); masm.move(ImmPtr(atom), Registers::ArgReg1); if (usePropertyCache) INLINE_STUBCALL(STRICT_VARIANT(stubs::SetGlobalName)); else INLINE_STUBCALL(STRICT_VARIANT(stubs::SetGlobalNameNoCache)); frame.popn(2); frame.pushSynced(); } void mjit::Compiler::jsop_setgname(JSAtom *atom, bool usePropertyCache) { #if defined JS_MONOIC FrameEntry *objFe = frame.peek(-2); FrameEntry *fe = frame.peek(-1); JS_ASSERT_IF(objFe->isTypeKnown(), objFe->getKnownType() == JSVAL_TYPE_OBJECT); SetGlobalNameICInfo ic; frame.pinEntry(fe, ic.vr); Jump shapeGuard; RESERVE_IC_SPACE(masm); ic.fastPathStart = masm.label(); if (objFe->isConstant()) { JSObject *obj = &objFe->getValue().toObject(); JS_ASSERT(obj->isNative()); ic.objReg = frame.allocReg(); ic.shapeReg = ic.objReg; ic.objConst = true; masm.load32FromImm(&obj->objShape, ic.shapeReg); shapeGuard = masm.branch32WithPatch(Assembler::NotEqual, ic.shapeReg, Imm32(int32(JSObjectMap::INVALID_SHAPE)), ic.shape); masm.move(ImmPtr(obj), ic.objReg); } else { ic.objReg = frame.copyDataIntoReg(objFe); ic.shapeReg = frame.allocReg(); ic.objConst = false; masm.loadShape(ic.objReg, ic.shapeReg); shapeGuard = masm.branch32WithPatch(Assembler::NotEqual, ic.shapeReg, Imm32(int32(JSObjectMap::INVALID_SHAPE)), ic.shape); frame.freeReg(ic.shapeReg); } ic.shapeGuardJump = shapeGuard; ic.slowPathStart = stubcc.linkExit(shapeGuard, Uses(2)); stubcc.leave(); passMICAddress(ic); ic.slowPathCall = OOL_STUBCALL(ic::SetGlobalName); /* Garbage value. */ uint32 slot = 1 << 24; ic.usePropertyCache = usePropertyCache; masm.loadPtr(Address(ic.objReg, offsetof(JSObject, slots)), ic.objReg); Address address(ic.objReg, slot); if (ic.vr.isConstant()) { ic.store = masm.storeValueWithAddressOffsetPatch(ic.vr.value(), address); } else if (ic.vr.isTypeKnown()) { ic.store = masm.storeValueWithAddressOffsetPatch(ImmType(ic.vr.knownType()), ic.vr.dataReg(), address); } else { ic.store = masm.storeValueWithAddressOffsetPatch(ic.vr.typeReg(), ic.vr.dataReg(), address); } frame.freeReg(ic.objReg); frame.unpinEntry(ic.vr); frame.shimmy(1); stubcc.rejoin(Changes(1)); ic.fastPathRejoin = masm.label(); setGlobalNames.append(ic); #else jsop_setgname_slow(atom, usePropertyCache); #endif } void mjit::Compiler::jsop_setelem_slow() { prepareStubCall(Uses(3)); INLINE_STUBCALL(STRICT_VARIANT(stubs::SetElem)); frame.popn(3); frame.pushSynced(); } void mjit::Compiler::jsop_getelem_slow() { prepareStubCall(Uses(2)); INLINE_STUBCALL(stubs::GetElem); frame.popn(2); frame.pushSynced(); } void mjit::Compiler::jsop_unbrand() { prepareStubCall(Uses(1)); INLINE_STUBCALL(stubs::Unbrand); } bool mjit::Compiler::jsop_instanceof() { FrameEntry *lhs = frame.peek(-2); FrameEntry *rhs = frame.peek(-1); // The fast path applies only when both operands are objects. if (rhs->isNotType(JSVAL_TYPE_OBJECT) || lhs->isNotType(JSVAL_TYPE_OBJECT)) { prepareStubCall(Uses(2)); INLINE_STUBCALL(stubs::InstanceOf); frame.popn(2); frame.takeReg(Registers::ReturnReg); frame.pushTypedPayload(JSVAL_TYPE_BOOLEAN, Registers::ReturnReg); return true; } MaybeJump firstSlow; if (!rhs->isTypeKnown()) { Jump j = frame.testObject(Assembler::NotEqual, rhs); stubcc.linkExit(j, Uses(2)); RegisterID reg = frame.tempRegForData(rhs); j = masm.testFunction(Assembler::NotEqual, reg); stubcc.linkExit(j, Uses(2)); } /* Test for bound functions. */ RegisterID obj = frame.tempRegForData(rhs); Jump isBound = masm.branchTest32(Assembler::NonZero, Address(obj, offsetof(JSObject, flags)), Imm32(JSObject::BOUND_FUNCTION)); { stubcc.linkExit(isBound, Uses(2)); stubcc.leave(); OOL_STUBCALL(stubs::InstanceOf); firstSlow = stubcc.masm.jump(); } /* This is sadly necessary because the error case needs the object. */ frame.dup(); if (!jsop_getprop(cx->runtime->atomState.classPrototypeAtom, false)) return false; /* Primitive prototypes are invalid. */ rhs = frame.peek(-1); Jump j = frame.testPrimitive(Assembler::Equal, rhs); stubcc.linkExit(j, Uses(3)); /* Allocate registers up front, because of branchiness. */ obj = frame.copyDataIntoReg(lhs); RegisterID proto = frame.copyDataIntoReg(rhs); RegisterID temp = frame.allocReg(); MaybeJump isFalse; if (!lhs->isTypeKnown()) isFalse = frame.testPrimitive(Assembler::Equal, lhs); Address protoAddr(obj, offsetof(JSObject, proto)); Label loop = masm.label(); /* Walk prototype chain, break out on NULL or hit. */ masm.loadPayload(protoAddr, obj); Jump isFalse2 = masm.branchTestPtr(Assembler::Zero, obj, obj); Jump isTrue = masm.branchPtr(Assembler::NotEqual, obj, proto); isTrue.linkTo(loop, &masm); masm.move(Imm32(1), temp); isTrue = masm.jump(); if (isFalse.isSet()) isFalse.getJump().linkTo(masm.label(), &masm); isFalse2.linkTo(masm.label(), &masm); masm.move(Imm32(0), temp); isTrue.linkTo(masm.label(), &masm); frame.freeReg(proto); frame.freeReg(obj); stubcc.leave(); OOL_STUBCALL(stubs::FastInstanceOf); frame.popn(3); frame.pushTypedPayload(JSVAL_TYPE_BOOLEAN, temp); if (firstSlow.isSet()) firstSlow.getJump().linkTo(stubcc.masm.label(), &stubcc.masm); stubcc.rejoin(Changes(1)); return true; } void mjit::Compiler::emitEval(uint32 argc) { /* Check for interrupts on function call */ interruptCheckHelper(); frame.syncAndKill(Registers(Registers::AvailRegs), Uses(argc + 2)); prepareStubCall(Uses(argc + 2)); masm.move(Imm32(argc), Registers::ArgReg1); INLINE_STUBCALL(stubs::Eval); frame.popn(argc + 2); frame.pushSynced(); } void mjit::Compiler::jsop_arguments() { prepareStubCall(Uses(0)); INLINE_STUBCALL(stubs::Arguments); } void mjit::Compiler::jsop_newinit() { bool isArray; unsigned count = 0; JSObject *baseobj = NULL; switch (*PC) { case JSOP_NEWINIT: isArray = (PC[1] == JSProto_Array); break; case JSOP_NEWARRAY: isArray = true; count = GET_UINT24(PC); break; case JSOP_NEWOBJECT: isArray = false; baseobj = script->getObject(fullAtomIndex(PC)); break; default: JS_NOT_REACHED("Bad op"); return; } prepareStubCall(Uses(0)); if (isArray) { masm.move(Imm32(count), Registers::ArgReg1); INLINE_STUBCALL(stubs::NewInitArray); } else { masm.move(ImmPtr(baseobj), Registers::ArgReg1); INLINE_STUBCALL(stubs::NewInitObject); } frame.takeReg(Registers::ReturnReg); frame.pushInitializerObject(Registers::ReturnReg, *PC == JSOP_NEWARRAY, baseobj); } /* * Note: This function emits tracer hooks into the OOL path. This means if * it is used in the middle of an in-progress slow path, the stream will be * hopelessly corrupted. Take care to only call this before linkExits() and * after rejoin()s. */ bool mjit::Compiler::jumpAndTrace(Jump j, jsbytecode *target, Jump *slow) { // XXX refactor this little bit #ifndef JS_TRACER if (!jumpInScript(j, target)) return false; if (slow) { if (!stubcc.jumpInScript(*slow, target)) return false; } #else if (!addTraceHints || target >= PC || (JSOp(*target) != JSOP_TRACE && JSOp(*target) != JSOP_NOTRACE) #ifdef JS_MONOIC || GET_UINT16(target) == BAD_TRACEIC_INDEX #endif ) { if (!jumpInScript(j, target)) return false; if (slow && !stubcc.jumpInScript(*slow, target)) return false; return true; } # if JS_MONOIC TraceGenInfo ic; ic.initialized = true; ic.stubEntry = stubcc.masm.label(); ic.jumpTarget = target; ic.traceHint = j; if (slow) ic.slowTraceHint = *slow; uint16 index = GET_UINT16(target); if (traceICs.length() <= index) if (!traceICs.resize(index+1)) return false; # endif Label traceStart = stubcc.masm.label(); /* * We make a trace IC even if the trace is currently disabled, in case it is * enabled later, but set up the jumps so that InvokeTracer is initially skipped. */ if (JSOp(*target) == JSOP_TRACE) { stubcc.linkExitDirect(j, traceStart); if (slow) slow->linkTo(traceStart, &stubcc.masm); } else { if (!jumpInScript(j, target)) return false; if (slow && !stubcc.jumpInScript(*slow, target)) return false; } # if JS_MONOIC ic.addrLabel = stubcc.masm.moveWithPatch(ImmPtr(NULL), Registers::ArgReg1); traceICs[index] = ic; Jump nonzero = stubcc.masm.branchSub32(Assembler::NonZero, Imm32(1), Address(Registers::ArgReg1, offsetof(TraceICInfo, loopCounter))); stubcc.jumpInScript(nonzero, target); # endif /* Save and restore compiler-tracked PC, so cx->regs is right in InvokeTracer. */ { jsbytecode* pc = PC; PC = target; OOL_STUBCALL(stubs::InvokeTracer); PC = pc; } Jump no = stubcc.masm.branchTestPtr(Assembler::Zero, Registers::ReturnReg, Registers::ReturnReg); if (!stubcc.jumpInScript(no, target)) return false; restoreFrameRegs(stubcc.masm); stubcc.masm.jump(Registers::ReturnReg); #endif return true; } void mjit::Compiler::enterBlock(JSObject *obj) { // If this is an exception entry point, then jsl_InternalThrow has set // VMFrame::fp to the correct fp for the entry point. We need to copy // that value here to FpReg so that FpReg also has the correct sp. // Otherwise, we would simply be using a stale FpReg value. // Additionally, we check the interrupt flag to allow interrupting // deeply nested exception handling. if (analysis->getCode(PC).exceptionEntry) { restoreFrameRegs(masm); interruptCheckHelper(); } uint32 oldFrameDepth = frame.localSlots(); /* For now, don't bother doing anything for this opcode. */ frame.syncAndForgetEverything(); masm.move(ImmPtr(obj), Registers::ArgReg1); uint32 n = js_GetEnterBlockStackDefs(cx, script, PC); INLINE_STUBCALL(stubs::EnterBlock); frame.enterBlock(n); uintN base = JSSLOT_FREE(&js_BlockClass); uintN count = OBJ_BLOCK_COUNT(cx, obj); uintN limit = base + count; for (uintN slot = base, i = 0; slot < limit; slot++, i++) { const Value &v = obj->getSlotRef(slot); if (v.isBoolean() && v.toBoolean()) frame.setClosedVar(oldFrameDepth + i); } } void mjit::Compiler::leaveBlock() { /* * Note: After bug 535912, we can pass the block obj directly, inline * PutBlockObject, and do away with the muckiness in PutBlockObject. */ uint32 n = js_GetVariableStackUses(JSOP_LEAVEBLOCK, PC); JSObject *obj = script->getObject(fullAtomIndex(PC + UINT16_LEN)); prepareStubCall(Uses(n)); masm.move(ImmPtr(obj), Registers::ArgReg1); INLINE_STUBCALL(stubs::LeaveBlock); frame.leaveBlock(n); } // Creates the new object expected for constructors, and places it in |thisv|. // It is broken down into the following operations: // CALLEE // GETPROP "prototype" // IFPRIMTOP: // NULL // call js_CreateThisFromFunctionWithProto(...) // bool mjit::Compiler::constructThis() { JS_ASSERT(isConstructing); // Load the callee. frame.pushCallee(); // Get callee.prototype. if (!jsop_getprop(cx->runtime->atomState.classPrototypeAtom, false, false)) return false; // Reach into the proto Value and grab a register for its data. FrameEntry *protoFe = frame.peek(-1); RegisterID protoReg = frame.ownRegForData(protoFe); // Now, get the type. If it's not an object, set protoReg to NULL. Jump isNotObject = frame.testObject(Assembler::NotEqual, protoFe); stubcc.linkExitDirect(isNotObject, stubcc.masm.label()); stubcc.masm.move(ImmPtr(NULL), protoReg); stubcc.crossJump(stubcc.masm.jump(), masm.label()); // Done with the protoFe. frame.pop(); prepareStubCall(Uses(0)); if (protoReg != Registers::ArgReg1) masm.move(protoReg, Registers::ArgReg1); INLINE_STUBCALL(stubs::CreateThis); frame.freeReg(protoReg); return true; } bool mjit::Compiler::jsop_tableswitch(jsbytecode *pc) { #if defined JS_CPU_ARM JS_NOT_REACHED("Implement jump(BaseIndex) for ARM"); return true; #else jsbytecode *originalPC = pc; uint32 defaultTarget = GET_JUMP_OFFSET(pc); pc += JUMP_OFFSET_LEN; jsint low = GET_JUMP_OFFSET(pc); pc += JUMP_OFFSET_LEN; jsint high = GET_JUMP_OFFSET(pc); pc += JUMP_OFFSET_LEN; int numJumps = high + 1 - low; JS_ASSERT(numJumps >= 0); /* * If there are no cases, this is a no-op. The default case immediately * follows in the bytecode and is always taken. */ if (numJumps == 0) { frame.pop(); return true; } FrameEntry *fe = frame.peek(-1); if (fe->isNotType(JSVAL_TYPE_INT32) || numJumps > 256) { frame.syncAndForgetEverything(); masm.move(ImmPtr(originalPC), Registers::ArgReg1); /* prepareStubCall() is not needed due to forgetEverything() */ INLINE_STUBCALL(stubs::TableSwitch); frame.pop(); masm.jump(Registers::ReturnReg); return true; } RegisterID dataReg; if (fe->isConstant()) { JS_ASSERT(fe->isType(JSVAL_TYPE_INT32)); dataReg = frame.allocReg(); masm.move(Imm32(fe->getValue().toInt32()), dataReg); } else { dataReg = frame.copyDataIntoReg(fe); } RegisterID reg = frame.allocReg(); frame.syncAndForgetEverything(); MaybeJump notInt; if (!fe->isType(JSVAL_TYPE_INT32)) notInt = masm.testInt32(Assembler::NotEqual, frame.addressOf(fe)); JumpTable jt; jt.offsetIndex = jumpTableOffsets.length(); jt.label = masm.moveWithPatch(ImmPtr(NULL), reg); jumpTables.append(jt); for (int i = 0; i < numJumps; i++) { uint32 target = GET_JUMP_OFFSET(pc); if (!target) target = defaultTarget; uint32 offset = (originalPC + target) - script->code; jumpTableOffsets.append(offset); pc += JUMP_OFFSET_LEN; } if (low != 0) masm.sub32(Imm32(low), dataReg); Jump defaultCase = masm.branch32(Assembler::AboveOrEqual, dataReg, Imm32(numJumps)); BaseIndex jumpTarget(reg, dataReg, Assembler::ScalePtr); masm.jump(jumpTarget); if (notInt.isSet()) { stubcc.linkExitDirect(notInt.get(), stubcc.masm.label()); stubcc.leave(); stubcc.masm.move(ImmPtr(originalPC), Registers::ArgReg1); OOL_STUBCALL(stubs::TableSwitch); stubcc.masm.jump(Registers::ReturnReg); } frame.pop(); return jumpAndTrace(defaultCase, originalPC + defaultTarget); #endif } void mjit::Compiler::jsop_callelem_slow() { prepareStubCall(Uses(2)); INLINE_STUBCALL(stubs::CallElem); frame.popn(2); frame.pushSynced(); frame.pushSynced(); } void mjit::Compiler::jsop_forprop(JSAtom *atom) { // Before: ITER OBJ // After: ITER OBJ ITER frame.dupAt(-2); // Before: ITER OBJ ITER // After: ITER OBJ ITER VALUE iterNext(); // Before: ITER OBJ ITER VALUE // After: ITER OBJ VALUE frame.shimmy(1); // Before: ITER OBJ VALUE // After: ITER VALUE jsop_setprop(atom, false); // Before: ITER VALUE // After: ITER frame.pop(); } void mjit::Compiler::jsop_forname(JSAtom *atom) { // Before: ITER // After: ITER SCOPEOBJ jsop_bindname(atom, false); jsop_forprop(atom); } void mjit::Compiler::jsop_forgname(JSAtom *atom) { // Before: ITER // After: ITER GLOBAL jsop_bindgname(); // Before: ITER GLOBAL // After: ITER GLOBAL ITER frame.dupAt(-2); // Before: ITER GLOBAL ITER // After: ITER GLOBAL ITER VALUE iterNext(); // Before: ITER GLOBAL ITER VALUE // After: ITER GLOBAL VALUE frame.shimmy(1); // Before: ITER GLOBAL VALUE // After: ITER VALUE jsop_setgname(atom, false); // Before: ITER VALUE // After: ITER frame.pop(); }