| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
| * vim: set ts=8 sts=4 et sw=4 tw=99: |
| * This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
| |
| #include "BaselineJIT.h" |
| #include "BaselineIC.h" |
| #include "BaselineHelpers.h" |
| #include "BaselineCompiler.h" |
| #include "FixedList.h" |
| #include "IonLinker.h" |
| #include "IonSpewer.h" |
| #include "VMFunctions.h" |
| #include "IonFrames-inl.h" |
| |
| #include "jsopcodeinlines.h" |
| |
| #include "vm/Interpreter-inl.h" |
| |
| using namespace js; |
| using namespace js::jit; |
| |
| BaselineCompiler::BaselineCompiler(JSContext *cx, HandleScript script) |
| : BaselineCompilerSpecific(cx, script), |
| return_(new HeapLabel()) |
| #ifdef JSGC_GENERATIONAL |
| , postBarrierSlot_(new HeapLabel()) |
| #endif |
| { |
| } |
| |
| bool |
| BaselineCompiler::init() |
| { |
| if (!analysis_.init()) |
| return false; |
| |
| if (!labels_.init(script->length)) |
| return false; |
| |
| for (size_t i = 0; i < script->length; i++) |
| new (&labels_[i]) Label(); |
| |
| if (!frame.init()) |
| return false; |
| |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::addPCMappingEntry(bool addIndexEntry) |
| { |
| // Don't add multiple entries for a single pc. |
| size_t nentries = pcMappingEntries_.length(); |
| if (nentries > 0 && pcMappingEntries_[nentries - 1].pcOffset == unsigned(pc - script->code)) |
| return true; |
| |
| PCMappingEntry entry; |
| entry.pcOffset = pc - script->code; |
| entry.nativeOffset = masm.currentOffset(); |
| entry.slotInfo = getStackTopSlotInfo(); |
| entry.addIndexEntry = addIndexEntry; |
| |
| return pcMappingEntries_.append(entry); |
| } |
| |
| MethodStatus |
| BaselineCompiler::compile() |
| { |
| IonSpew(IonSpew_BaselineScripts, "Baseline compiling script %s:%d (%p)", |
| script->filename(), script->lineno, script.get()); |
| |
| if (cx->typeInferenceEnabled() && !script->ensureHasBytecodeTypeMap(cx)) |
| return Method_Error; |
| |
| // Only need to analyze scripts which are marked |argumensHasVarBinding|, to |
| // compute |needsArgsObj| flag. |
| if (script->argumentsHasVarBinding()) { |
| if (!script->ensureRanAnalysis(cx)) |
| return Method_Error; |
| } |
| |
| // Pin analysis info during compilation. |
| types::AutoEnterAnalysis autoEnterAnalysis(cx); |
| |
| if (!emitPrologue()) |
| return Method_Error; |
| |
| MethodStatus status = emitBody(); |
| if (status != Method_Compiled) |
| return status; |
| |
| if (!emitEpilogue()) |
| return Method_Error; |
| |
| #ifdef JSGC_GENERATIONAL |
| if (!emitOutOfLinePostBarrierSlot()) |
| return Method_Error; |
| #endif |
| |
| if (masm.oom()) |
| return Method_Error; |
| |
| Linker linker(masm); |
| IonCode *code = linker.newCode(cx, JSC::BASELINE_CODE); |
| if (!code) |
| return Method_Error; |
| |
| JS_ASSERT(!script->hasBaselineScript()); |
| |
| // Encode the pc mapping table. See PCMappingIndexEntry for |
| // more information. |
| Vector<PCMappingIndexEntry> pcMappingIndexEntries(cx); |
| CompactBufferWriter pcEntries; |
| uint32_t previousOffset = 0; |
| |
| for (size_t i = 0; i < pcMappingEntries_.length(); i++) { |
| PCMappingEntry &entry = pcMappingEntries_[i]; |
| entry.fixupNativeOffset(masm); |
| |
| if (entry.addIndexEntry) { |
| PCMappingIndexEntry indexEntry; |
| indexEntry.pcOffset = entry.pcOffset; |
| indexEntry.nativeOffset = entry.nativeOffset; |
| indexEntry.bufferOffset = pcEntries.length(); |
| if (!pcMappingIndexEntries.append(indexEntry)) |
| return Method_Error; |
| previousOffset = entry.nativeOffset; |
| } |
| |
| // Use the high bit of the SlotInfo byte to indicate the |
| // native code offset (relative to the previous op) > 0 and |
| // comes next in the buffer. |
| JS_ASSERT((entry.slotInfo.toByte() & 0x80) == 0); |
| |
| if (entry.nativeOffset == previousOffset) { |
| pcEntries.writeByte(entry.slotInfo.toByte()); |
| } else { |
| JS_ASSERT(entry.nativeOffset > previousOffset); |
| pcEntries.writeByte(0x80 | entry.slotInfo.toByte()); |
| pcEntries.writeUnsigned(entry.nativeOffset - previousOffset); |
| } |
| |
| previousOffset = entry.nativeOffset; |
| } |
| |
| if (pcEntries.oom()) |
| return Method_Error; |
| |
| prologueOffset_.fixup(&masm); |
| spsPushToggleOffset_.fixup(&masm); |
| |
| BaselineScript *baselineScript = BaselineScript::New(cx, prologueOffset_.offset(), |
| spsPushToggleOffset_.offset(), |
| icEntries_.length(), |
| pcMappingIndexEntries.length(), |
| pcEntries.length()); |
| if (!baselineScript) |
| return Method_Error; |
| |
| baselineScript->setMethod(code); |
| |
| script->setBaselineScript(baselineScript); |
| |
| IonSpew(IonSpew_BaselineScripts, "Created BaselineScript %p (raw %p) for %s:%d", |
| (void *) script->baselineScript(), (void *) code->raw(), |
| script->filename(), script->lineno); |
| |
| JS_ASSERT(pcMappingIndexEntries.length() > 0); |
| baselineScript->copyPCMappingIndexEntries(&pcMappingIndexEntries[0]); |
| |
| JS_ASSERT(pcEntries.length() > 0); |
| baselineScript->copyPCMappingEntries(pcEntries); |
| |
| // Copy IC entries |
| if (icEntries_.length()) |
| baselineScript->copyICEntries(script, &icEntries_[0], masm); |
| |
| // Adopt fallback stubs from the compiler into the baseline script. |
| baselineScript->adoptFallbackStubs(&stubSpace_); |
| |
| // Patch IC loads using IC entries |
| for (size_t i = 0; i < icLoadLabels_.length(); i++) { |
| CodeOffsetLabel label = icLoadLabels_[i].label; |
| label.fixup(&masm); |
| size_t icEntry = icLoadLabels_[i].icEntry; |
| ICEntry *entryAddr = &(baselineScript->icEntry(icEntry)); |
| Assembler::patchDataWithValueCheck(CodeLocationLabel(code, label), |
| ImmWord(uintptr_t(entryAddr)), |
| ImmWord(uintptr_t(-1))); |
| } |
| |
| // All barriers are emitted off-by-default, toggle them on if needed. |
| if (cx->zone()->needsBarrier()) |
| baselineScript->toggleBarriers(true); |
| |
| // All SPS instrumentation is emitted toggled off. Toggle them on if needed. |
| if (cx->runtime()->spsProfiler.enabled()) |
| baselineScript->toggleSPS(true); |
| |
| return Method_Compiled; |
| } |
| |
| bool |
| BaselineCompiler::emitPrologue() |
| { |
| masm.push(BaselineFrameReg); |
| masm.mov(BaselineStackReg, BaselineFrameReg); |
| |
| masm.subPtr(Imm32(BaselineFrame::Size()), BaselineStackReg); |
| masm.checkStackAlignment(); |
| |
| // Initialize BaselineFrame. For eval scripts, the scope chain |
| // is passed in R1, so we have to be careful not to clobber |
| // it. |
| |
| // Initialize BaselineFrame::flags. |
| uint32_t flags = 0; |
| if (script->isForEval()) |
| flags |= BaselineFrame::EVAL; |
| masm.store32(Imm32(flags), frame.addressOfFlags()); |
| |
| if (script->isForEval()) |
| masm.storePtr(ImmGCPtr(script), frame.addressOfEvalScript()); |
| |
| // Initialize locals to |undefined|. Use R0 to minimize code size. |
| if (frame.nlocals() > 0) { |
| masm.moveValue(UndefinedValue(), R0); |
| for (size_t i = 0; i < frame.nlocals(); i++) |
| masm.pushValue(R0); |
| } |
| |
| // Record the offset of the prologue, because Ion can bailout before |
| // the scope chain is initialized. |
| prologueOffset_ = masm.currentOffset(); |
| |
| // Initialize the scope chain before any operation that may |
| // call into the VM and trigger a GC. |
| if (!initScopeChain()) |
| return false; |
| |
| if (!emitStackCheck()) |
| return false; |
| |
| if (!emitDebugPrologue()) |
| return false; |
| |
| if (!emitUseCountIncrement()) |
| return false; |
| |
| if (!emitArgumentTypeChecks()) |
| return false; |
| |
| if (!emitSPSPush()) |
| return false; |
| |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emitEpilogue() |
| { |
| masm.bind(return_); |
| |
| // Pop SPS frame if necessary |
| emitSPSPop(); |
| |
| masm.mov(BaselineFrameReg, BaselineStackReg); |
| masm.pop(BaselineFrameReg); |
| |
| masm.ret(); |
| return true; |
| } |
| |
| #ifdef JSGC_GENERATIONAL |
| // On input: |
| // R2.scratchReg() contains object being written to. |
| // R1.scratchReg() contains slot index being written to. |
| // Otherwise, baseline stack will be synced, so all other registers are usable as scratch. |
| // This calls: |
| // void PostWriteBarrier(JSRuntime *rt, JSObject *obj); |
| bool |
| BaselineCompiler::emitOutOfLinePostBarrierSlot() |
| { |
| masm.bind(postBarrierSlot_); |
| |
| Register objReg = R2.scratchReg(); |
| GeneralRegisterSet regs(GeneralRegisterSet::All()); |
| regs.take(objReg); |
| regs.take(BaselineFrameReg); |
| Register scratch = regs.takeAny(); |
| #if defined(JS_CPU_ARM) |
| // On ARM, save the link register before calling. It contains the return |
| // address. The |masm.ret()| later will pop this into |pc| to return. |
| masm.push(lr); |
| #elif defined(JS_CPU_MIPS) |
| masm.push(ra); |
| #endif |
| |
| masm.setupUnalignedABICall(2, scratch); |
| masm.movePtr(ImmWord(cx->runtime()), scratch); |
| masm.passABIArg(scratch); |
| masm.passABIArg(objReg); |
| masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, PostWriteBarrier)); |
| |
| masm.ret(); |
| return true; |
| } |
| #endif // JSGC_GENERATIONAL |
| |
| bool |
| BaselineCompiler::emitIC(ICStub *stub, bool isForOp) |
| { |
| ICEntry *entry = allocateICEntry(stub, isForOp); |
| if (!entry) |
| return false; |
| |
| CodeOffsetLabel patchOffset; |
| EmitCallIC(&patchOffset, masm); |
| entry->setReturnOffset(masm.currentOffset()); |
| if (!addICLoadLabel(patchOffset)) |
| return false; |
| |
| return true; |
| } |
| |
| typedef bool (*DebugPrologueFn)(JSContext *, BaselineFrame *, JSBool *); |
| static const VMFunction DebugPrologueInfo = FunctionInfo<DebugPrologueFn>(jit::DebugPrologue); |
| |
| bool |
| BaselineCompiler::emitDebugPrologue() |
| { |
| if (!debugMode_) |
| return true; |
| |
| // Load pointer to BaselineFrame in R0. |
| masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); |
| |
| prepareVMCall(); |
| pushArg(R0.scratchReg()); |
| if (!callVM(DebugPrologueInfo)) |
| return false; |
| |
| // If the stub returns |true|, we have to return the value stored in the |
| // frame's return value slot. |
| Label done; |
| masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, &done); |
| { |
| masm.loadValue(frame.addressOfReturnValue(), JSReturnOperand); |
| masm.jump(return_); |
| } |
| masm.bind(&done); |
| return true; |
| } |
| |
| typedef bool (*StrictEvalPrologueFn)(JSContext *, BaselineFrame *); |
| static const VMFunction StrictEvalPrologueInfo = |
| FunctionInfo<StrictEvalPrologueFn>(jit::StrictEvalPrologue); |
| |
| typedef bool (*HeavyweightFunPrologueFn)(JSContext *, BaselineFrame *); |
| static const VMFunction HeavyweightFunPrologueInfo = |
| FunctionInfo<HeavyweightFunPrologueFn>(jit::HeavyweightFunPrologue); |
| |
| bool |
| BaselineCompiler::initScopeChain() |
| { |
| RootedFunction fun(cx, function()); |
| if (fun) { |
| // Use callee->environment as scope chain. Note that we do |
| // this also for heavy-weight functions, so that the scope |
| // chain slot is properly initialized if the call triggers GC. |
| Register callee = R0.scratchReg(); |
| Register scope = R1.scratchReg(); |
| masm.loadPtr(frame.addressOfCallee(), callee); |
| masm.loadPtr(Address(callee, JSFunction::offsetOfEnvironment()), scope); |
| masm.storePtr(scope, frame.addressOfScopeChain()); |
| |
| if (fun->isHeavyweight()) { |
| // Call into the VM to create a new call object. |
| prepareVMCall(); |
| |
| masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); |
| pushArg(R0.scratchReg()); |
| |
| if (!callVM(HeavyweightFunPrologueInfo)) |
| return false; |
| } |
| } else { |
| // For global and eval scripts, the scope chain is in R1. |
| masm.storePtr(R1.scratchReg(), frame.addressOfScopeChain()); |
| |
| if (script->isForEval() && script->strict) { |
| // Strict eval needs its own call object. |
| prepareVMCall(); |
| |
| masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); |
| pushArg(R0.scratchReg()); |
| |
| if (!callVM(StrictEvalPrologueInfo)) |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| typedef bool (*ReportOverRecursedFn)(JSContext *); |
| static const VMFunction CheckOverRecursedInfo = |
| FunctionInfo<ReportOverRecursedFn>(CheckOverRecursed); |
| |
| bool |
| BaselineCompiler::emitStackCheck() |
| { |
| Label skipCall; |
| uintptr_t *limitAddr = &cx->runtime()->mainThread.ionStackLimit; |
| masm.loadPtr(AbsoluteAddress(limitAddr), R0.scratchReg()); |
| masm.branchPtr(Assembler::AboveOrEqual, BaselineStackReg, R0.scratchReg(), &skipCall); |
| |
| prepareVMCall(); |
| if (!callVM(CheckOverRecursedInfo)) |
| return false; |
| |
| masm.bind(&skipCall); |
| return true; |
| } |
| |
| typedef bool (*InterruptCheckFn)(JSContext *); |
| static const VMFunction InterruptCheckInfo = FunctionInfo<InterruptCheckFn>(InterruptCheck); |
| |
| bool |
| BaselineCompiler::emitInterruptCheck() |
| { |
| frame.syncStack(0); |
| |
| Label done; |
| void *interrupt = (void *)&cx->compartment()->rt->interrupt; |
| masm.branch32(Assembler::Equal, AbsoluteAddress(interrupt), Imm32(0), &done); |
| |
| prepareVMCall(); |
| if (!callVM(InterruptCheckInfo)) |
| return false; |
| |
| masm.bind(&done); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emitUseCountIncrement() |
| { |
| // Emit no use count increments or bailouts if Ion is not |
| // enabled, or if the script will never be Ion-compileable |
| |
| if (!ionCompileable_ && !ionOSRCompileable_) |
| return true; |
| |
| Register scriptReg = R2.scratchReg(); |
| Register countReg = R0.scratchReg(); |
| Address useCountAddr(scriptReg, JSScript::offsetOfUseCount()); |
| |
| masm.movePtr(ImmGCPtr(script), scriptReg); |
| masm.load32(useCountAddr, countReg); |
| masm.add32(Imm32(1), countReg); |
| masm.store32(countReg, useCountAddr); |
| |
| Label skipCall; |
| |
| uint32_t minUses = UsesBeforeIonRecompile(script, pc); |
| masm.branch32(Assembler::LessThan, countReg, Imm32(minUses), &skipCall); |
| |
| masm.branchPtr(Assembler::Equal, |
| Address(scriptReg, JSScript::offsetOfIonScript()), |
| ImmWord(ION_COMPILING_SCRIPT), &skipCall); |
| |
| // Call IC. |
| ICUseCount_Fallback::Compiler stubCompiler(cx); |
| if (!emitNonOpIC(stubCompiler.getStub(&stubSpace_))) |
| return false; |
| |
| masm.bind(&skipCall); |
| |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emitArgumentTypeChecks() |
| { |
| if (!function()) |
| return true; |
| |
| frame.pushThis(); |
| frame.popRegsAndSync(1); |
| |
| ICTypeMonitor_Fallback::Compiler compiler(cx, (uint32_t) 0); |
| if (!emitNonOpIC(compiler.getStub(&stubSpace_))) |
| return false; |
| |
| for (size_t i = 0; i < function()->nargs; i++) { |
| frame.pushArg(i); |
| frame.popRegsAndSync(1); |
| |
| ICTypeMonitor_Fallback::Compiler compiler(cx, i + 1); |
| if (!emitNonOpIC(compiler.getStub(&stubSpace_))) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emitDebugTrap() |
| { |
| JS_ASSERT(debugMode_); |
| JS_ASSERT(frame.numUnsyncedSlots() == 0); |
| |
| bool enabled = script->stepModeEnabled() || script->hasBreakpointsAt(pc); |
| |
| // Emit patchable call to debug trap handler. |
| IonCode *handler = cx->compartment()->ionCompartment()->debugTrapHandler(cx); |
| mozilla::DebugOnly<CodeOffsetLabel> offset = masm.toggledCall(handler, enabled); |
| |
| #ifdef DEBUG |
| // Patchable call offset has to match the pc mapping offset. |
| PCMappingEntry &entry = pcMappingEntries_[pcMappingEntries_.length() - 1]; |
| JS_ASSERT((&offset)->offset() == entry.nativeOffset); |
| #endif |
| |
| // Add an IC entry for the return offset -> pc mapping. |
| ICEntry icEntry(pc - script->code, false); |
| icEntry.setReturnOffset(masm.currentOffset()); |
| if (!icEntries_.append(icEntry)) |
| return false; |
| |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emitSPSPush() |
| { |
| // Enter the IC, guarded by a toggled jump (initially disabled). |
| Label noPush; |
| CodeOffsetLabel toggleOffset = masm.toggledJump(&noPush); |
| JS_ASSERT(frame.numUnsyncedSlots() == 0); |
| ICProfiler_Fallback::Compiler compiler(cx); |
| if (!emitNonOpIC(compiler.getStub(&stubSpace_))) |
| return false; |
| masm.bind(&noPush); |
| |
| // Store the start offset in the appropriate location. |
| JS_ASSERT(spsPushToggleOffset_.offset() == 0); |
| spsPushToggleOffset_ = toggleOffset; |
| return true; |
| } |
| |
| void |
| BaselineCompiler::emitSPSPop() |
| { |
| // If profiler entry was pushed on this frame, pop it. |
| Label noPop; |
| masm.branchTest32(Assembler::Zero, frame.addressOfFlags(), |
| Imm32(BaselineFrame::HAS_PUSHED_SPS_FRAME), &noPop); |
| masm.spsPopFrameSafe(&cx->runtime()->spsProfiler, R1.scratchReg()); |
| masm.bind(&noPop); |
| } |
| |
| MethodStatus |
| BaselineCompiler::emitBody() |
| { |
| JS_ASSERT(pc == script->code); |
| |
| bool lastOpUnreachable = false; |
| uint32_t emittedOps = 0; |
| |
| while (true) { |
| JSOp op = JSOp(*pc); |
| IonSpew(IonSpew_BaselineOp, "Compiling op @ %d: %s", |
| int(pc - script->code), js_CodeName[op]); |
| |
| BytecodeInfo *info = analysis_.maybeInfo(pc); |
| |
| // Skip unreachable ops. |
| if (!info) { |
| if (op == JSOP_STOP) |
| break; |
| pc += GetBytecodeLength(pc); |
| lastOpUnreachable = true; |
| continue; |
| } |
| |
| // Fully sync the stack if there are incoming jumps. |
| if (info->jumpTarget) { |
| frame.syncStack(0); |
| frame.setStackDepth(info->stackDepth); |
| } |
| |
| // Always sync in debug mode. |
| if (debugMode_) |
| frame.syncStack(0); |
| |
| // At the beginning of any op, at most the top 2 stack-values are unsynced. |
| if (frame.stackDepth() > 2) |
| frame.syncStack(2); |
| |
| frame.assertValidState(*info); |
| |
| masm.bind(labelOf(pc)); |
| |
| // Add a PC -> native mapping entry for the current op. These entries are |
| // used when we need the native code address for a given pc, for instance |
| // for bailouts from Ion, the debugger and exception handling. See |
| // PCMappingIndexEntry for more information. |
| bool addIndexEntry = (pc == script->code || lastOpUnreachable || emittedOps > 100); |
| if (addIndexEntry) |
| emittedOps = 0; |
| if (!addPCMappingEntry(addIndexEntry)) |
| return Method_Error; |
| |
| // Emit traps for breakpoints and step mode. |
| if (debugMode_ && !emitDebugTrap()) |
| return Method_Error; |
| |
| switch (op) { |
| default: |
| IonSpew(IonSpew_BaselineAbort, "Unhandled op: %s", js_CodeName[op]); |
| return Method_CantCompile; |
| |
| #define EMIT_OP(OP) \ |
| case OP: \ |
| if (!this->emit_##OP()) \ |
| return Method_Error; \ |
| break; |
| OPCODE_LIST(EMIT_OP) |
| #undef EMIT_OP |
| } |
| |
| if (op == JSOP_STOP) |
| break; |
| |
| pc += GetBytecodeLength(pc); |
| emittedOps++; |
| lastOpUnreachable = false; |
| } |
| |
| JS_ASSERT(JSOp(*pc) == JSOP_STOP); |
| return Method_Compiled; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_NOP() |
| { |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_LABEL() |
| { |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_NOTEARG() |
| { |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_POP() |
| { |
| frame.pop(); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_POPN() |
| { |
| frame.popn(GET_UINT16(pc)); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_DUP() |
| { |
| // Keep top stack value in R0, sync the rest so that we can use R1. We use |
| // separate registers because every register can be used by at most one |
| // StackValue. |
| frame.popRegsAndSync(1); |
| masm.moveValue(R0, R1); |
| |
| // inc/dec ops use DUP followed by ONE, ADD. Push R0 last to avoid a move. |
| frame.push(R1); |
| frame.push(R0); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_DUP2() |
| { |
| frame.syncStack(0); |
| |
| masm.loadValue(frame.addressOfStackValue(frame.peek(-2)), R0); |
| masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R1); |
| |
| frame.push(R0); |
| frame.push(R1); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_SWAP() |
| { |
| // Keep top stack values in R0 and R1. |
| frame.popRegsAndSync(2); |
| |
| frame.push(R1); |
| frame.push(R0); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_PICK() |
| { |
| frame.syncStack(0); |
| |
| // Pick takes a value on the stack and moves it to the top. |
| // For instance, pick 2: |
| // before: A B C D E |
| // after : A B D E C |
| |
| // First, move value at -(amount + 1) into R0. |
| int depth = -(GET_INT8(pc) + 1); |
| masm.loadValue(frame.addressOfStackValue(frame.peek(depth)), R0); |
| |
| // Move the other values down. |
| depth++; |
| for (; depth < 0; depth++) { |
| Address source = frame.addressOfStackValue(frame.peek(depth)); |
| Address dest = frame.addressOfStackValue(frame.peek(depth - 1)); |
| masm.loadValue(source, R1); |
| masm.storeValue(R1, dest); |
| } |
| |
| // Push R0. |
| frame.pop(); |
| frame.push(R0); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_GOTO() |
| { |
| frame.syncStack(0); |
| |
| jsbytecode *target = pc + GET_JUMP_OFFSET(pc); |
| masm.jump(labelOf(target)); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emitToBoolean() |
| { |
| Label skipIC; |
| masm.branchTestBoolean(Assembler::Equal, R0, &skipIC); |
| |
| // Call IC |
| ICToBool_Fallback::Compiler stubCompiler(cx); |
| if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) |
| return false; |
| |
| masm.bind(&skipIC); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emitTest(bool branchIfTrue) |
| { |
| bool knownBoolean = frame.peek(-1)->isKnownBoolean(); |
| |
| // Keep top stack value in R0. |
| frame.popRegsAndSync(1); |
| |
| if (!knownBoolean && !emitToBoolean()) |
| return false; |
| |
| // IC will leave a JSBool value (guaranteed) in R0, just need to branch on it. |
| masm.branchTestBooleanTruthy(branchIfTrue, R0, labelOf(pc + GET_JUMP_OFFSET(pc))); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_IFEQ() |
| { |
| return emitTest(false); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_IFNE() |
| { |
| return emitTest(true); |
| } |
| |
| bool |
| BaselineCompiler::emitAndOr(bool branchIfTrue) |
| { |
| bool knownBoolean = frame.peek(-1)->isKnownBoolean(); |
| |
| // AND and OR leave the original value on the stack. |
| frame.syncStack(0); |
| |
| masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R0); |
| if (!knownBoolean && !emitToBoolean()) |
| return false; |
| |
| masm.branchTestBooleanTruthy(branchIfTrue, R0, labelOf(pc + GET_JUMP_OFFSET(pc))); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_AND() |
| { |
| return emitAndOr(false); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_OR() |
| { |
| return emitAndOr(true); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_NOT() |
| { |
| bool knownBoolean = frame.peek(-1)->isKnownBoolean(); |
| |
| // Keep top stack value in R0. |
| frame.popRegsAndSync(1); |
| |
| if (!knownBoolean && !emitToBoolean()) |
| return false; |
| |
| masm.notBoolean(R0); |
| |
| frame.push(R0, JSVAL_TYPE_BOOLEAN); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_POS() |
| { |
| // Keep top stack value in R0. |
| frame.popRegsAndSync(1); |
| |
| // Inline path for int32 and double. |
| Label done; |
| masm.branchTestNumber(Assembler::Equal, R0, &done); |
| |
| // Call IC. |
| ICToNumber_Fallback::Compiler stubCompiler(cx); |
| if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) |
| return false; |
| |
| masm.bind(&done); |
| frame.push(R0); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_LOOPHEAD() |
| { |
| return emitInterruptCheck(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_LOOPENTRY() |
| { |
| frame.syncStack(0); |
| return emitUseCountIncrement(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_VOID() |
| { |
| frame.pop(); |
| frame.push(UndefinedValue()); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_UNDEFINED() |
| { |
| frame.push(UndefinedValue()); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_HOLE() |
| { |
| frame.push(MagicValue(JS_ELEMENTS_HOLE)); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_NULL() |
| { |
| frame.push(NullValue()); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_THIS() |
| { |
| // Keep this value in R0 |
| frame.pushThis(); |
| |
| // In strict mode function or self-hosted function, |this| is left alone. |
| if (!function() || function()->strict() || function()->isSelfHostedBuiltin()) |
| return true; |
| |
| Label skipIC; |
| // Keep |thisv| in R0 |
| frame.popRegsAndSync(1); |
| // If |this| is already an object, skip the IC. |
| masm.branchTestObject(Assembler::Equal, R0, &skipIC); |
| |
| // Call IC |
| ICThis_Fallback::Compiler stubCompiler(cx); |
| if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) |
| return false; |
| |
| masm.storeValue(R0, frame.addressOfThis()); |
| |
| // R0 is new pushed |this| value. |
| masm.bind(&skipIC); |
| frame.push(R0); |
| |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_TRUE() |
| { |
| frame.push(BooleanValue(true)); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_FALSE() |
| { |
| frame.push(BooleanValue(false)); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_ZERO() |
| { |
| frame.push(Int32Value(0)); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_ONE() |
| { |
| frame.push(Int32Value(1)); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_INT8() |
| { |
| frame.push(Int32Value(GET_INT8(pc))); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_INT32() |
| { |
| frame.push(Int32Value(GET_INT32(pc))); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_UINT16() |
| { |
| frame.push(Int32Value(GET_UINT16(pc))); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_UINT24() |
| { |
| frame.push(Int32Value(GET_UINT24(pc))); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_DOUBLE() |
| { |
| frame.push(script->getConst(GET_UINT32_INDEX(pc))); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_STRING() |
| { |
| frame.push(StringValue(script->getAtom(pc))); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_OBJECT() |
| { |
| frame.push(ObjectValue(*script->getObject(pc))); |
| return true; |
| } |
| |
| typedef JSObject *(*CloneRegExpObjectFn)(JSContext *, JSObject *, JSObject *); |
| static const VMFunction CloneRegExpObjectInfo = |
| FunctionInfo<CloneRegExpObjectFn>(CloneRegExpObject); |
| |
| bool |
| BaselineCompiler::emit_JSOP_REGEXP() |
| { |
| RootedObject reObj(cx, script->getRegExp(GET_UINT32_INDEX(pc))); |
| RootedObject proto(cx, script->global().getOrCreateRegExpPrototype(cx)); |
| if (!proto) |
| return false; |
| |
| prepareVMCall(); |
| |
| pushArg(ImmGCPtr(proto)); |
| pushArg(ImmGCPtr(reObj)); |
| |
| if (!callVM(CloneRegExpObjectInfo)) |
| return false; |
| |
| // Box and push return value. |
| masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0); |
| frame.push(R0); |
| return true; |
| } |
| |
| typedef JSObject *(*LambdaFn)(JSContext *, HandleFunction, HandleObject); |
| static const VMFunction LambdaInfo = FunctionInfo<LambdaFn>(js::Lambda); |
| |
| bool |
| BaselineCompiler::emit_JSOP_LAMBDA() |
| { |
| RootedFunction fun(cx, script->getFunction(GET_UINT32_INDEX(pc))); |
| |
| prepareVMCall(); |
| masm.loadPtr(frame.addressOfScopeChain(), R0.scratchReg()); |
| |
| pushArg(R0.scratchReg()); |
| pushArg(ImmGCPtr(fun)); |
| |
| if (!callVM(LambdaInfo)) |
| return false; |
| |
| // Box and push return value. |
| masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0); |
| frame.push(R0); |
| return true; |
| } |
| |
| void |
| BaselineCompiler::storeValue(const StackValue *source, const Address &dest, |
| const ValueOperand &scratch) |
| { |
| switch (source->kind()) { |
| case StackValue::Constant: |
| masm.storeValue(source->constant(), dest); |
| break; |
| case StackValue::Register: |
| masm.storeValue(source->reg(), dest); |
| break; |
| case StackValue::LocalSlot: |
| masm.loadValue(frame.addressOfLocal(source->localSlot()), scratch); |
| masm.storeValue(scratch, dest); |
| break; |
| case StackValue::ArgSlot: |
| masm.loadValue(frame.addressOfArg(source->argSlot()), scratch); |
| masm.storeValue(scratch, dest); |
| break; |
| case StackValue::ThisSlot: |
| masm.loadValue(frame.addressOfThis(), scratch); |
| masm.storeValue(scratch, dest); |
| break; |
| case StackValue::Stack: |
| masm.loadValue(frame.addressOfStackValue(source), scratch); |
| masm.storeValue(scratch, dest); |
| break; |
| default: |
| JS_NOT_REACHED("Invalid kind"); |
| } |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_BITOR() |
| { |
| return emitBinaryArith(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_BITXOR() |
| { |
| return emitBinaryArith(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_BITAND() |
| { |
| return emitBinaryArith(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_LSH() |
| { |
| return emitBinaryArith(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_RSH() |
| { |
| return emitBinaryArith(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_URSH() |
| { |
| return emitBinaryArith(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_ADD() |
| { |
| return emitBinaryArith(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_SUB() |
| { |
| return emitBinaryArith(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_MUL() |
| { |
| return emitBinaryArith(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_DIV() |
| { |
| return emitBinaryArith(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_MOD() |
| { |
| return emitBinaryArith(); |
| } |
| |
| bool |
| BaselineCompiler::emitBinaryArith() |
| { |
| // Keep top JSStack value in R0 and R2 |
| frame.popRegsAndSync(2); |
| |
| // Call IC |
| ICBinaryArith_Fallback::Compiler stubCompiler(cx); |
| if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) |
| return false; |
| |
| // Mark R0 as pushed stack value. |
| frame.push(R0); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emitUnaryArith() |
| { |
| // Keep top stack value in R0. |
| frame.popRegsAndSync(1); |
| |
| // Call IC |
| ICUnaryArith_Fallback::Compiler stubCompiler(cx); |
| if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) |
| return false; |
| |
| // Mark R0 as pushed stack value. |
| frame.push(R0); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_BITNOT() |
| { |
| return emitUnaryArith(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_NEG() |
| { |
| return emitUnaryArith(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_LT() |
| { |
| return emitCompare(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_LE() |
| { |
| return emitCompare(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_GT() |
| { |
| return emitCompare(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_GE() |
| { |
| return emitCompare(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_EQ() |
| { |
| return emitCompare(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_NE() |
| { |
| return emitCompare(); |
| } |
| |
| bool |
| BaselineCompiler::emitCompare() |
| { |
| // CODEGEN |
| |
| // Keep top JSStack value in R0 and R1. |
| frame.popRegsAndSync(2); |
| |
| // Call IC. |
| ICCompare_Fallback::Compiler stubCompiler(cx); |
| if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) |
| return false; |
| |
| // Mark R0 as pushed stack value. |
| frame.push(R0, JSVAL_TYPE_BOOLEAN); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_STRICTEQ() |
| { |
| return emitCompare(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_STRICTNE() |
| { |
| return emitCompare(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_CONDSWITCH() |
| { |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_CASE() |
| { |
| frame.popRegsAndSync(2); |
| frame.push(R0); |
| frame.syncStack(0); |
| |
| // Call IC. |
| ICCompare_Fallback::Compiler stubCompiler(cx); |
| if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) |
| return false; |
| |
| Register payload = masm.extractInt32(R0, R0.scratchReg()); |
| jsbytecode *target = pc + GET_JUMP_OFFSET(pc); |
| |
| Label done; |
| masm.branch32(Assembler::Equal, payload, Imm32(0), &done); |
| { |
| // Pop the switch value if the case matches. |
| masm.addPtr(Imm32(sizeof(Value)), StackPointer); |
| masm.jump(labelOf(target)); |
| } |
| masm.bind(&done); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_DEFAULT() |
| { |
| frame.pop(); |
| return emit_JSOP_GOTO(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_LINENO() |
| { |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_NEWARRAY() |
| { |
| frame.syncStack(0); |
| |
| uint32_t length = GET_UINT24(pc); |
| RootedTypeObject type(cx); |
| if (!types::UseNewTypeForInitializer(cx, script, pc, JSProto_Array)) { |
| type = types::TypeScript::InitObject(cx, script, pc, JSProto_Array); |
| if (!type) |
| return false; |
| } |
| |
| // Pass length in R0, type in R1. |
| masm.move32(Imm32(length), R0.scratchReg()); |
| masm.movePtr(ImmGCPtr(type), R1.scratchReg()); |
| |
| ICNewArray_Fallback::Compiler stubCompiler(cx); |
| if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) |
| return false; |
| |
| frame.push(R0); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_INITELEM_ARRAY() |
| { |
| // Keep the object and rhs on the stack. |
| frame.syncStack(0); |
| |
| // Load object in R0, index in R1. |
| masm.loadValue(frame.addressOfStackValue(frame.peek(-2)), R0); |
| masm.moveValue(Int32Value(GET_UINT24(pc)), R1); |
| |
| // Call IC. |
| ICSetElem_Fallback::Compiler stubCompiler(cx); |
| if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) |
| return false; |
| |
| // Pop the rhs, so that the object is on the top of the stack. |
| frame.pop(); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_NEWOBJECT() |
| { |
| frame.syncStack(0); |
| |
| RootedTypeObject type(cx); |
| if (!types::UseNewTypeForInitializer(cx, script, pc, JSProto_Object)) { |
| type = types::TypeScript::InitObject(cx, script, pc, JSProto_Object); |
| if (!type) |
| return false; |
| } |
| |
| RootedObject baseObject(cx, script->getObject(pc)); |
| RootedObject templateObject(cx, CopyInitializerObject(cx, baseObject, TenuredObject)); |
| if (!templateObject) |
| return false; |
| |
| if (type) { |
| templateObject->setType(type); |
| } else { |
| if (!JSObject::setSingletonType(cx, templateObject)) |
| return false; |
| } |
| |
| // Pass base object in R0. |
| masm.movePtr(ImmGCPtr(templateObject), R0.scratchReg()); |
| |
| ICNewObject_Fallback::Compiler stubCompiler(cx); |
| if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) |
| return false; |
| |
| frame.push(R0); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_NEWINIT() |
| { |
| frame.syncStack(0); |
| JSProtoKey key = JSProtoKey(GET_UINT8(pc)); |
| |
| RootedTypeObject type(cx); |
| if (!types::UseNewTypeForInitializer(cx, script, pc, key)) { |
| type = types::TypeScript::InitObject(cx, script, pc, key); |
| if (!type) |
| return false; |
| } |
| |
| if (key == JSProto_Array) { |
| // Pass length in R0, type in R1. |
| masm.move32(Imm32(0), R0.scratchReg()); |
| masm.movePtr(ImmGCPtr(type), R1.scratchReg()); |
| |
| ICNewArray_Fallback::Compiler stubCompiler(cx); |
| if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) |
| return false; |
| } else { |
| JS_ASSERT(key == JSProto_Object); |
| |
| RootedObject templateObject(cx); |
| templateObject = NewBuiltinClassInstance(cx, &ObjectClass, TenuredObject); |
| if (!templateObject) |
| return false; |
| |
| if (type) { |
| templateObject->setType(type); |
| } else { |
| if (!JSObject::setSingletonType(cx, templateObject)) |
| return false; |
| } |
| |
| // Pass base object in R0. |
| masm.movePtr(ImmGCPtr(templateObject), R0.scratchReg()); |
| |
| ICNewObject_Fallback::Compiler stubCompiler(cx); |
| if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) |
| return false; |
| } |
| |
| frame.push(R0); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_INITELEM() |
| { |
| // Store RHS in the scratch slot. |
| storeValue(frame.peek(-1), frame.addressOfScratchValue(), R2); |
| frame.pop(); |
| |
| // Keep object and index in R0 and R1. |
| frame.popRegsAndSync(2); |
| |
| // Push the object to store the result of the IC. |
| frame.push(R0); |
| frame.syncStack(0); |
| |
| // Keep RHS on the stack. |
| frame.pushScratchValue(); |
| |
| // Call IC. |
| ICSetElem_Fallback::Compiler stubCompiler(cx); |
| if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) |
| return false; |
| |
| // Pop the rhs, so that the object is on the top of the stack. |
| frame.pop(); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_INITPROP() |
| { |
| // Keep lhs in R0, rhs in R1. |
| frame.popRegsAndSync(2); |
| |
| // Push the object to store the result of the IC. |
| frame.push(R0); |
| frame.syncStack(0); |
| |
| // Call IC. |
| ICSetProp_Fallback::Compiler compiler(cx); |
| return emitOpIC(compiler.getStub(&stubSpace_)); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_ENDINIT() |
| { |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_GETELEM() |
| { |
| // Keep top two stack values in R0 and R1. |
| frame.popRegsAndSync(2); |
| |
| // Call IC. |
| ICGetElem_Fallback::Compiler stubCompiler(cx); |
| if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) |
| return false; |
| |
| // Mark R0 as pushed stack value. |
| frame.push(R0); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_CALLELEM() |
| { |
| return emit_JSOP_GETELEM(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_SETELEM() |
| { |
| // Store RHS in the scratch slot. |
| storeValue(frame.peek(-1), frame.addressOfScratchValue(), R2); |
| frame.pop(); |
| |
| // Keep object and index in R0 and R1. |
| frame.popRegsAndSync(2); |
| |
| // Keep RHS on the stack. |
| frame.pushScratchValue(); |
| |
| // Call IC. |
| ICSetElem_Fallback::Compiler stubCompiler(cx); |
| if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) |
| return false; |
| |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_ENUMELEM() |
| { |
| // ENUMELEM is a SETELEM with a different stack arrangement. |
| // Instead of: OBJ ID RHS |
| // The stack is: RHS OBJ ID |
| |
| // Keep object and index in R0 and R1, and keep RHS on the stack. |
| frame.popRegsAndSync(2); |
| |
| // Call IC. |
| ICSetElem_Fallback::Compiler stubCompiler(cx); |
| if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) |
| return false; |
| |
| frame.pop(); |
| return true; |
| } |
| |
| typedef bool (*DeleteElementFn)(JSContext *, HandleValue, HandleValue, JSBool *); |
| static const VMFunction DeleteElementStrictInfo = FunctionInfo<DeleteElementFn>(DeleteElement<true>); |
| static const VMFunction DeleteElementNonStrictInfo = FunctionInfo<DeleteElementFn>(DeleteElement<false>); |
| |
| bool |
| BaselineCompiler::emit_JSOP_DELELEM() |
| { |
| // Keep values on the stack for the decompiler. |
| frame.syncStack(0); |
| masm.loadValue(frame.addressOfStackValue(frame.peek(-2)), R0); |
| masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R1); |
| |
| prepareVMCall(); |
| |
| pushArg(R1); |
| pushArg(R0); |
| |
| if (!callVM(script->strict ? DeleteElementStrictInfo : DeleteElementNonStrictInfo)) |
| return false; |
| |
| masm.boxNonDouble(JSVAL_TYPE_BOOLEAN, ReturnReg, R1); |
| frame.popn(2); |
| frame.push(R1); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_IN() |
| { |
| frame.popRegsAndSync(2); |
| |
| ICIn_Fallback::Compiler stubCompiler(cx); |
| if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) |
| return false; |
| |
| frame.push(R0); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_GETGNAME() |
| { |
| RootedPropertyName name(cx, script->getName(pc)); |
| |
| if (name == cx->names().undefined) { |
| frame.push(UndefinedValue()); |
| return true; |
| } |
| if (name == cx->names().NaN) { |
| frame.push(cx->runtime()->NaNValue); |
| return true; |
| } |
| if (name == cx->names().Infinity) { |
| frame.push(cx->runtime()->positiveInfinityValue); |
| return true; |
| } |
| |
| frame.syncStack(0); |
| |
| masm.movePtr(ImmGCPtr(&script->global()), R0.scratchReg()); |
| |
| // Call IC. |
| ICGetName_Fallback::Compiler stubCompiler(cx); |
| if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) |
| return false; |
| |
| // Mark R0 as pushed stack value. |
| frame.push(R0); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_CALLGNAME() |
| { |
| return emit_JSOP_GETGNAME(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_BINDGNAME() |
| { |
| frame.push(ObjectValue(script->global())); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_SETPROP() |
| { |
| // Keep lhs in R0, rhs in R1. |
| frame.popRegsAndSync(2); |
| |
| // Call IC. |
| ICSetProp_Fallback::Compiler compiler(cx); |
| if (!emitOpIC(compiler.getStub(&stubSpace_))) |
| return false; |
| |
| // The IC will return the RHS value in R0, mark it as pushed value. |
| frame.push(R0); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_SETNAME() |
| { |
| return emit_JSOP_SETPROP(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_SETGNAME() |
| { |
| return emit_JSOP_SETPROP(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_GETPROP() |
| { |
| // Keep object in R0. |
| frame.popRegsAndSync(1); |
| |
| // Call IC. |
| ICGetProp_Fallback::Compiler compiler(cx); |
| if (!emitOpIC(compiler.getStub(&stubSpace_))) |
| return false; |
| |
| // Mark R0 as pushed stack value. |
| frame.push(R0); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_CALLPROP() |
| { |
| return emit_JSOP_GETPROP(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_LENGTH() |
| { |
| return emit_JSOP_GETPROP(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_GETXPROP() |
| { |
| return emit_JSOP_GETPROP(); |
| } |
| |
| typedef bool (*DeletePropertyFn)(JSContext *, HandleValue, HandlePropertyName, JSBool *); |
| static const VMFunction DeletePropertyStrictInfo = FunctionInfo<DeletePropertyFn>(DeleteProperty<true>); |
| static const VMFunction DeletePropertyNonStrictInfo = FunctionInfo<DeletePropertyFn>(DeleteProperty<false>); |
| |
| bool |
| BaselineCompiler::emit_JSOP_DELPROP() |
| { |
| // Keep value on the stack for the decompiler. |
| frame.syncStack(0); |
| masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R0); |
| |
| prepareVMCall(); |
| |
| pushArg(ImmGCPtr(script->getName(pc))); |
| pushArg(R0); |
| |
| if (!callVM(script->strict ? DeletePropertyStrictInfo : DeletePropertyNonStrictInfo)) |
| return false; |
| |
| masm.boxNonDouble(JSVAL_TYPE_BOOLEAN, ReturnReg, R1); |
| frame.pop(); |
| frame.push(R1); |
| return true; |
| } |
| |
| void |
| BaselineCompiler::getScopeCoordinateObject(Register reg) |
| { |
| ScopeCoordinate sc(pc); |
| |
| masm.loadPtr(frame.addressOfScopeChain(), reg); |
| for (unsigned i = sc.hops; i; i--) |
| masm.extractObject(Address(reg, ScopeObject::offsetOfEnclosingScope()), reg); |
| } |
| |
| Address |
| BaselineCompiler::getScopeCoordinateAddressFromObject(Register objReg, Register reg) |
| { |
| ScopeCoordinate sc(pc); |
| Shape *shape = ScopeCoordinateToStaticScopeShape(cx, script, pc); |
| |
| Address addr; |
| if (shape->numFixedSlots() <= sc.slot) { |
| masm.loadPtr(Address(objReg, JSObject::offsetOfSlots()), reg); |
| return Address(reg, (sc.slot - shape->numFixedSlots()) * sizeof(Value)); |
| } |
| |
| return Address(objReg, JSObject::getFixedSlotOffset(sc.slot)); |
| } |
| |
| Address |
| BaselineCompiler::getScopeCoordinateAddress(Register reg) |
| { |
| getScopeCoordinateObject(reg); |
| return getScopeCoordinateAddressFromObject(reg, reg); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_GETALIASEDVAR() |
| { |
| frame.syncStack(0); |
| |
| Address address = getScopeCoordinateAddress(R0.scratchReg()); |
| masm.loadValue(address, R0); |
| |
| ICTypeMonitor_Fallback::Compiler compiler(cx, (ICMonitoredFallbackStub *) NULL); |
| if (!emitOpIC(compiler.getStub(&stubSpace_))) |
| return false; |
| |
| frame.push(R0); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_CALLALIASEDVAR() |
| { |
| return emit_JSOP_GETALIASEDVAR(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_SETALIASEDVAR() |
| { |
| JSScript *outerScript = ScopeCoordinateFunctionScript(cx, script, pc); |
| if (outerScript && outerScript->treatAsRunOnce) { |
| // Type updates for this operation might need to be tracked, so treat |
| // this as a SETPROP. |
| |
| // Load rhs into R1. |
| frame.syncStack(1); |
| frame.popValue(R1); |
| |
| // Load and box lhs into R0. |
| getScopeCoordinateObject(R2.scratchReg()); |
| masm.tagValue(JSVAL_TYPE_OBJECT, R2.scratchReg(), R0); |
| |
| // Call SETPROP IC. |
| ICSetProp_Fallback::Compiler compiler(cx); |
| if (!emitOpIC(compiler.getStub(&stubSpace_))) |
| return false; |
| |
| // The IC will return the RHS value in R0, mark it as pushed value. |
| frame.push(R0); |
| return true; |
| } |
| |
| // Keep rvalue in R0. |
| frame.popRegsAndSync(1); |
| Register objReg = R2.scratchReg(); |
| |
| getScopeCoordinateObject(objReg); |
| Address address = getScopeCoordinateAddressFromObject(objReg, R1.scratchReg()); |
| masm.patchableCallPreBarrier(address, MIRType_Value); |
| masm.storeValue(R0, address); |
| frame.push(R0); |
| |
| #ifdef JSGC_GENERATIONAL |
| // Fully sync the stack if post-barrier is needed. |
| // Scope coordinate object is already in R2.scratchReg(). |
| frame.syncStack(0); |
| |
| Nursery &nursery = cx->runtime()->gcNursery; |
| Label skipBarrier; |
| Label isTenured; |
| masm.branchTestObject(Assembler::NotEqual, R0, &skipBarrier); |
| masm.branchPtr(Assembler::Below, objReg, ImmWord(nursery.start()), &isTenured); |
| masm.branchPtr(Assembler::Below, objReg, ImmWord(nursery.heapEnd()), &skipBarrier); |
| |
| masm.bind(&isTenured); |
| masm.call(postBarrierSlot_); |
| |
| masm.bind(&skipBarrier); |
| #endif |
| |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_NAME() |
| { |
| frame.syncStack(0); |
| |
| masm.loadPtr(frame.addressOfScopeChain(), R0.scratchReg()); |
| |
| // Call IC. |
| ICGetName_Fallback::Compiler stubCompiler(cx); |
| if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) |
| return false; |
| |
| // Mark R0 as pushed stack value. |
| frame.push(R0); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_CALLNAME() |
| { |
| return emit_JSOP_NAME(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_BINDNAME() |
| { |
| frame.syncStack(0); |
| |
| masm.loadPtr(frame.addressOfScopeChain(), R0.scratchReg()); |
| |
| // Call IC. |
| ICBindName_Fallback::Compiler stubCompiler(cx); |
| if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) |
| return false; |
| |
| // Mark R0 as pushed stack value. |
| frame.push(R0); |
| return true; |
| } |
| |
| typedef bool (*DeleteNameFn)(JSContext *, HandlePropertyName, HandleObject, |
| MutableHandleValue); |
| static const VMFunction DeleteNameInfo = FunctionInfo<DeleteNameFn>(DeleteNameOperation); |
| |
| bool |
| BaselineCompiler::emit_JSOP_DELNAME() |
| { |
| frame.syncStack(0); |
| masm.loadPtr(frame.addressOfScopeChain(), R0.scratchReg()); |
| |
| prepareVMCall(); |
| |
| pushArg(R0.scratchReg()); |
| pushArg(ImmGCPtr(script->getName(pc))); |
| |
| if (!callVM(DeleteNameInfo)) |
| return false; |
| |
| frame.push(R0); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_GETINTRINSIC() |
| { |
| frame.syncStack(0); |
| |
| ICGetIntrinsic_Fallback::Compiler stubCompiler(cx); |
| if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) |
| return false; |
| |
| frame.push(R0); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_CALLINTRINSIC() |
| { |
| return emit_JSOP_GETINTRINSIC(); |
| } |
| |
| typedef bool (*DefVarOrConstFn)(JSContext *, HandlePropertyName, unsigned, HandleObject); |
| static const VMFunction DefVarOrConstInfo = FunctionInfo<DefVarOrConstFn>(DefVarOrConst); |
| |
| bool |
| BaselineCompiler::emit_JSOP_DEFVAR() |
| { |
| frame.syncStack(0); |
| |
| unsigned attrs = JSPROP_ENUMERATE; |
| if (!script->isForEval()) |
| attrs |= JSPROP_PERMANENT; |
| if (JSOp(*pc) == JSOP_DEFCONST) |
| attrs |= JSPROP_READONLY; |
| JS_ASSERT(attrs <= UINT32_MAX); |
| |
| masm.loadPtr(frame.addressOfScopeChain(), R0.scratchReg()); |
| |
| prepareVMCall(); |
| |
| pushArg(R0.scratchReg()); |
| pushArg(Imm32(attrs)); |
| pushArg(ImmGCPtr(script->getName(pc))); |
| |
| return callVM(DefVarOrConstInfo); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_DEFCONST() |
| { |
| return emit_JSOP_DEFVAR(); |
| } |
| |
| typedef bool (*SetConstFn)(JSContext *, HandlePropertyName, HandleObject, HandleValue); |
| static const VMFunction SetConstInfo = FunctionInfo<SetConstFn>(SetConst); |
| |
| bool |
| BaselineCompiler::emit_JSOP_SETCONST() |
| { |
| frame.popRegsAndSync(1); |
| frame.push(R0); |
| frame.syncStack(0); |
| |
| masm.loadPtr(frame.addressOfScopeChain(), R1.scratchReg()); |
| |
| prepareVMCall(); |
| |
| pushArg(R0); |
| pushArg(R1.scratchReg()); |
| pushArg(ImmGCPtr(script->getName(pc))); |
| |
| return callVM(SetConstInfo); |
| } |
| |
| typedef bool (*DefFunOperationFn)(JSContext *, HandleScript, HandleObject, HandleFunction); |
| static const VMFunction DefFunOperationInfo = FunctionInfo<DefFunOperationFn>(DefFunOperation); |
| |
| bool |
| BaselineCompiler::emit_JSOP_DEFFUN() |
| { |
| RootedFunction fun(cx, script->getFunction(GET_UINT32_INDEX(pc))); |
| |
| frame.syncStack(0); |
| masm.loadPtr(frame.addressOfScopeChain(), R0.scratchReg()); |
| |
| prepareVMCall(); |
| |
| pushArg(ImmGCPtr(fun)); |
| pushArg(R0.scratchReg()); |
| pushArg(ImmGCPtr(script)); |
| |
| return callVM(DefFunOperationInfo); |
| } |
| |
| typedef bool (*InitPropGetterSetterFn)(JSContext *, jsbytecode *, HandleObject, HandlePropertyName, |
| HandleValue); |
| static const VMFunction InitPropGetterSetterInfo = |
| FunctionInfo<InitPropGetterSetterFn>(InitGetterSetterOperation); |
| |
| bool |
| BaselineCompiler::emitInitPropGetterSetter() |
| { |
| JS_ASSERT(JSOp(*pc) == JSOP_INITPROP_GETTER || |
| JSOp(*pc) == JSOP_INITPROP_SETTER); |
| |
| // Load value in R0 but keep it on the stack for the decompiler. |
| frame.syncStack(0); |
| masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R0); |
| |
| prepareVMCall(); |
| |
| pushArg(R0); |
| pushArg(ImmGCPtr(script->getName(pc))); |
| masm.extractObject(frame.addressOfStackValue(frame.peek(-2)), R0.scratchReg()); |
| pushArg(R0.scratchReg()); |
| pushArg(ImmWord(pc)); |
| |
| if (!callVM(InitPropGetterSetterInfo)) |
| return false; |
| |
| frame.pop(); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_INITPROP_GETTER() |
| { |
| return emitInitPropGetterSetter(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_INITPROP_SETTER() |
| { |
| return emitInitPropGetterSetter(); |
| } |
| |
| typedef bool (*InitElemGetterSetterFn)(JSContext *, jsbytecode *, HandleObject, HandleValue, |
| HandleValue); |
| static const VMFunction InitElemGetterSetterInfo = |
| FunctionInfo<InitElemGetterSetterFn>(InitGetterSetterOperation); |
| |
| bool |
| BaselineCompiler::emitInitElemGetterSetter() |
| { |
| JS_ASSERT(JSOp(*pc) == JSOP_INITELEM_GETTER || |
| JSOp(*pc) == JSOP_INITELEM_SETTER); |
| |
| // Load index and value in R0 and R1, but keep values on the stack for the |
| // decompiler. |
| frame.syncStack(0); |
| masm.loadValue(frame.addressOfStackValue(frame.peek(-2)), R0); |
| masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R1); |
| |
| prepareVMCall(); |
| |
| pushArg(R1); |
| pushArg(R0); |
| masm.extractObject(frame.addressOfStackValue(frame.peek(-3)), R0.scratchReg()); |
| pushArg(R0.scratchReg()); |
| pushArg(ImmWord(pc)); |
| |
| if (!callVM(InitElemGetterSetterInfo)) |
| return false; |
| |
| frame.popn(2); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_INITELEM_GETTER() |
| { |
| return emitInitElemGetterSetter(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_INITELEM_SETTER() |
| { |
| return emitInitElemGetterSetter(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_GETLOCAL() |
| { |
| uint32_t local = GET_SLOTNO(pc); |
| |
| if (local >= frame.nlocals()) { |
| // Destructuring assignments may use GETLOCAL to access stack values. |
| frame.syncStack(0); |
| masm.loadValue(Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfLocal(local)), R0); |
| frame.push(R0); |
| return true; |
| } |
| |
| frame.pushLocal(local); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_CALLLOCAL() |
| { |
| return emit_JSOP_GETLOCAL(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_SETLOCAL() |
| { |
| // Ensure no other StackValue refers to the old value, for instance i + (i = 3). |
| // This also allows us to use R0 as scratch below. |
| frame.syncStack(1); |
| |
| uint32_t local = GET_SLOTNO(pc); |
| storeValue(frame.peek(-1), frame.addressOfLocal(local), R0); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emitFormalArgAccess(uint32_t arg, bool get) |
| { |
| // Fast path: the script does not use |arguments|, or is strict. In strict |
| // mode, formals do not alias the arguments object. |
| if (!script->argumentsHasVarBinding() || script->strict) { |
| if (get) { |
| frame.pushArg(arg); |
| } else { |
| // See the comment in emit_JSOP_SETLOCAL. |
| frame.syncStack(1); |
| storeValue(frame.peek(-1), frame.addressOfArg(arg), R0); |
| } |
| |
| return true; |
| } |
| |
| // Sync so that we can use R0. |
| frame.syncStack(0); |
| |
| // If the script is known to have an arguments object, we can just use it. |
| // Else, we *may* have an arguments object (because we can't invalidate |
| // when needsArgsObj becomes |true|), so we have to test HAS_ARGS_OBJ. |
| Label done; |
| if (!script->needsArgsObj()) { |
| Label hasArgsObj; |
| masm.branchTest32(Assembler::NonZero, frame.addressOfFlags(), |
| Imm32(BaselineFrame::HAS_ARGS_OBJ), &hasArgsObj); |
| if (get) |
| masm.loadValue(frame.addressOfArg(arg), R0); |
| else |
| storeValue(frame.peek(-1), frame.addressOfArg(arg), R0); |
| masm.jump(&done); |
| masm.bind(&hasArgsObj); |
| } |
| |
| // Load the arguments object data vector. |
| Register reg = R2.scratchReg(); |
| masm.loadPtr(Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfArgsObj()), reg); |
| masm.loadPrivate(Address(reg, ArgumentsObject::getDataSlotOffset()), reg); |
| |
| // Load/store the argument. |
| Address argAddr(reg, ArgumentsData::offsetOfArgs() + arg * sizeof(Value)); |
| if (get) { |
| masm.loadValue(argAddr, R0); |
| frame.push(R0); |
| } else { |
| masm.patchableCallPreBarrier(argAddr, MIRType_Value); |
| storeValue(frame.peek(-1), argAddr, R0); |
| } |
| |
| masm.bind(&done); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_GETARG() |
| { |
| uint32_t arg = GET_SLOTNO(pc); |
| return emitFormalArgAccess(arg, /* get = */ true); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_CALLARG() |
| { |
| return emit_JSOP_GETARG(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_SETARG() |
| { |
| uint32_t arg = GET_SLOTNO(pc); |
| return emitFormalArgAccess(arg, /* get = */ false); |
| } |
| |
| bool |
| BaselineCompiler::emitCall() |
| { |
| JS_ASSERT(js_CodeSpec[*pc].format & JOF_INVOKE); |
| |
| uint32_t argc = GET_ARGC(pc); |
| |
| frame.syncStack(0); |
| masm.mov(Imm32(argc), R0.scratchReg()); |
| |
| // Call IC |
| ICCall_Fallback::Compiler stubCompiler(cx, /* isConstructing = */ JSOp(*pc) == JSOP_NEW); |
| if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) |
| return false; |
| |
| // Update FrameInfo. |
| frame.popn(argc + 2); |
| frame.push(R0); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_CALL() |
| { |
| return emitCall(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_NEW() |
| { |
| return emitCall(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_FUNCALL() |
| { |
| return emitCall(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_FUNAPPLY() |
| { |
| return emitCall(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_EVAL() |
| { |
| return emitCall(); |
| } |
| |
| typedef bool (*ImplicitThisFn)(JSContext *, HandleObject, HandlePropertyName, |
| MutableHandleValue); |
| static const VMFunction ImplicitThisInfo = FunctionInfo<ImplicitThisFn>(ImplicitThisOperation); |
| |
| bool |
| BaselineCompiler::emit_JSOP_IMPLICITTHIS() |
| { |
| frame.syncStack(0); |
| masm.loadPtr(frame.addressOfScopeChain(), R0.scratchReg()); |
| |
| prepareVMCall(); |
| |
| pushArg(ImmGCPtr(script->getName(pc))); |
| pushArg(R0.scratchReg()); |
| |
| if (!callVM(ImplicitThisInfo)) |
| return false; |
| |
| frame.push(R0); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_INSTANCEOF() |
| { |
| frame.popRegsAndSync(2); |
| |
| ICInstanceOf_Fallback::Compiler stubCompiler(cx); |
| if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) |
| return false; |
| |
| frame.push(R0); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_TYPEOF() |
| { |
| frame.popRegsAndSync(1); |
| |
| ICTypeOf_Fallback::Compiler stubCompiler(cx); |
| if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) |
| return false; |
| |
| frame.push(R0); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_TYPEOFEXPR() |
| { |
| return emit_JSOP_TYPEOF(); |
| } |
| |
| typedef bool (*SetCallFn)(JSContext *); |
| static const VMFunction SetCallInfo = FunctionInfo<SetCallFn>(js::SetCallOperation); |
| |
| bool |
| BaselineCompiler::emit_JSOP_SETCALL() |
| { |
| prepareVMCall(); |
| return callVM(SetCallInfo); |
| } |
| |
| typedef bool (*ThrowFn)(JSContext *, HandleValue); |
| static const VMFunction ThrowInfo = FunctionInfo<ThrowFn>(js::Throw); |
| |
| bool |
| BaselineCompiler::emit_JSOP_THROW() |
| { |
| // Keep value to throw in R0. |
| frame.popRegsAndSync(1); |
| |
| prepareVMCall(); |
| pushArg(R0); |
| |
| return callVM(ThrowInfo); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_TRY() |
| { |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_FINALLY() |
| { |
| // JSOP_FINALLY has a def count of 2, but these values are already on the |
| // stack (they're pushed by JSOP_GOSUB). Update the compiler's stack state. |
| frame.setStackDepth(frame.stackDepth() + 2); |
| |
| // To match the interpreter, emit an interrupt check at the start of the |
| // finally block. |
| return emitInterruptCheck(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_GOSUB() |
| { |
| // Push |false| so that RETSUB knows the value on top of the |
| // stack is not an exception but the offset to the op following |
| // this GOSUB. |
| frame.push(BooleanValue(false)); |
| |
| int32_t nextOffset = GetNextPc(pc) - script->code; |
| frame.push(Int32Value(nextOffset)); |
| |
| // Jump to the finally block. |
| frame.syncStack(0); |
| jsbytecode *target = pc + GET_JUMP_OFFSET(pc); |
| masm.jump(labelOf(target)); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_RETSUB() |
| { |
| frame.popRegsAndSync(2); |
| |
| ICRetSub_Fallback::Compiler stubCompiler(cx); |
| return emitOpIC(stubCompiler.getStub(&stubSpace_)); |
| } |
| |
| typedef bool (*EnterBlockFn)(JSContext *, BaselineFrame *, Handle<StaticBlockObject *>); |
| static const VMFunction EnterBlockInfo = FunctionInfo<EnterBlockFn>(jit::EnterBlock); |
| |
| bool |
| BaselineCompiler::emitEnterBlock() |
| { |
| StaticBlockObject &blockObj = script->getObject(pc)->as<StaticBlockObject>(); |
| |
| if (JSOp(*pc) == JSOP_ENTERBLOCK) { |
| for (size_t i = 0; i < blockObj.slotCount(); i++) |
| frame.push(UndefinedValue()); |
| |
| // Pushed values will be accessed using GETLOCAL and SETLOCAL, so ensure |
| // they are synced. |
| frame.syncStack(0); |
| } |
| |
| // Call a stub to push the block on the block chain. |
| prepareVMCall(); |
| masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); |
| |
| pushArg(ImmGCPtr(&blockObj)); |
| pushArg(R0.scratchReg()); |
| |
| return callVM(EnterBlockInfo); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_ENTERBLOCK() |
| { |
| return emitEnterBlock(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_ENTERLET0() |
| { |
| return emitEnterBlock(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_ENTERLET1() |
| { |
| return emitEnterBlock(); |
| } |
| |
| typedef bool (*LeaveBlockFn)(JSContext *, BaselineFrame *); |
| static const VMFunction LeaveBlockInfo = FunctionInfo<LeaveBlockFn>(jit::LeaveBlock); |
| |
| bool |
| BaselineCompiler::emitLeaveBlock() |
| { |
| // Call a stub to pop the block from the block chain. |
| prepareVMCall(); |
| |
| masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); |
| pushArg(R0.scratchReg()); |
| |
| return callVM(LeaveBlockInfo); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_LEAVEBLOCK() |
| { |
| if (!emitLeaveBlock()) |
| return false; |
| |
| // Pop slots pushed by JSOP_ENTERBLOCK. |
| frame.popn(GET_UINT16(pc)); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_LEAVEBLOCKEXPR() |
| { |
| if (!emitLeaveBlock()) |
| return false; |
| |
| // Pop slots pushed by JSOP_ENTERBLOCK, but leave the topmost value |
| // on the stack. |
| frame.popRegsAndSync(1); |
| frame.popn(GET_UINT16(pc)); |
| frame.push(R0); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_LEAVEFORLETIN() |
| { |
| if (!emitLeaveBlock()) |
| return false; |
| |
| // Another op will pop the slots (after the enditer). |
| return true; |
| } |
| |
| typedef bool (*GetAndClearExceptionFn)(JSContext *, MutableHandleValue); |
| static const VMFunction GetAndClearExceptionInfo = |
| FunctionInfo<GetAndClearExceptionFn>(GetAndClearException); |
| |
| bool |
| BaselineCompiler::emit_JSOP_EXCEPTION() |
| { |
| prepareVMCall(); |
| |
| if (!callVM(GetAndClearExceptionInfo)) |
| return false; |
| |
| frame.push(R0); |
| return true; |
| } |
| |
| typedef bool (*OnDebuggerStatementFn)(JSContext *, BaselineFrame *, jsbytecode *pc, JSBool *); |
| static const VMFunction OnDebuggerStatementInfo = |
| FunctionInfo<OnDebuggerStatementFn>(jit::OnDebuggerStatement); |
| |
| bool |
| BaselineCompiler::emit_JSOP_DEBUGGER() |
| { |
| prepareVMCall(); |
| pushArg(ImmWord(pc)); |
| |
| masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); |
| pushArg(R0.scratchReg()); |
| |
| if (!callVM(OnDebuggerStatementInfo)) |
| return false; |
| |
| // If the stub returns |true|, return the frame's return value. |
| Label done; |
| masm.branchTest32(Assembler::Zero, ReturnReg, ReturnReg, &done); |
| { |
| masm.loadValue(frame.addressOfReturnValue(), JSReturnOperand); |
| masm.jump(return_); |
| } |
| masm.bind(&done); |
| return true; |
| } |
| |
| typedef bool (*DebugEpilogueFn)(JSContext *, BaselineFrame *, JSBool); |
| static const VMFunction DebugEpilogueInfo = FunctionInfo<DebugEpilogueFn>(jit::DebugEpilogue); |
| |
| bool |
| BaselineCompiler::emitReturn() |
| { |
| if (debugMode_) { |
| // Move return value into the frame's rval slot. |
| masm.storeValue(JSReturnOperand, frame.addressOfReturnValue()); |
| masm.or32(Imm32(BaselineFrame::HAS_RVAL), frame.addressOfFlags()); |
| |
| // Load BaselineFrame pointer in R0. |
| frame.syncStack(0); |
| masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); |
| |
| prepareVMCall(); |
| pushArg(Imm32(1)); |
| pushArg(R0.scratchReg()); |
| if (!callVM(DebugEpilogueInfo)) |
| return false; |
| |
| masm.loadValue(frame.addressOfReturnValue(), JSReturnOperand); |
| } |
| |
| if (JSOp(*pc) != JSOP_STOP) { |
| // JSOP_STOP is immediately followed by the return label, so we don't |
| // need a jump. |
| masm.jump(return_); |
| } |
| |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_RETURN() |
| { |
| JS_ASSERT(frame.stackDepth() == 1); |
| |
| frame.popValue(JSReturnOperand); |
| return emitReturn(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_STOP() |
| { |
| JS_ASSERT(frame.stackDepth() == 0); |
| |
| masm.moveValue(UndefinedValue(), JSReturnOperand); |
| |
| if (!script->noScriptRval) { |
| // Return the value in the return value slot, if any. |
| Label done; |
| Address flags = frame.addressOfFlags(); |
| masm.branchTest32(Assembler::Zero, flags, Imm32(BaselineFrame::HAS_RVAL), &done); |
| masm.loadValue(frame.addressOfReturnValue(), JSReturnOperand); |
| masm.bind(&done); |
| } |
| |
| return emitReturn(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_RETRVAL() |
| { |
| return emit_JSOP_STOP(); |
| } |
| |
| typedef bool (*ToIdFn)(JSContext *, HandleScript, jsbytecode *, HandleValue, HandleValue, |
| MutableHandleValue); |
| static const VMFunction ToIdInfo = FunctionInfo<ToIdFn>(js::ToIdOperation); |
| |
| bool |
| BaselineCompiler::emit_JSOP_TOID() |
| { |
| // Load index in R0, but keep values on the stack for the decompiler. |
| frame.syncStack(0); |
| masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R0); |
| |
| // No-op if index is int32. |
| Label done; |
| masm.branchTestInt32(Assembler::Equal, R0, &done); |
| |
| prepareVMCall(); |
| |
| masm.loadValue(frame.addressOfStackValue(frame.peek(-2)), R1); |
| |
| pushArg(R0); |
| pushArg(R1); |
| pushArg(ImmWord(pc)); |
| pushArg(ImmGCPtr(script)); |
| |
| if (!callVM(ToIdInfo)) |
| return false; |
| |
| masm.bind(&done); |
| frame.pop(); // Pop index. |
| frame.push(R0); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_TABLESWITCH() |
| { |
| frame.popRegsAndSync(1); |
| |
| // Call IC. |
| ICTableSwitch::Compiler compiler(cx, pc); |
| return emitOpIC(compiler.getStub(&stubSpace_)); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_ITER() |
| { |
| frame.popRegsAndSync(1); |
| |
| ICIteratorNew_Fallback::Compiler compiler(cx); |
| if (!emitOpIC(compiler.getStub(&stubSpace_))) |
| return false; |
| |
| frame.push(R0); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_MOREITER() |
| { |
| frame.syncStack(0); |
| masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R0); |
| |
| ICIteratorMore_Fallback::Compiler compiler(cx); |
| if (!emitOpIC(compiler.getStub(&stubSpace_))) |
| return false; |
| |
| frame.push(R0); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_ITERNEXT() |
| { |
| frame.syncStack(0); |
| masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R0); |
| |
| ICIteratorNext_Fallback::Compiler compiler(cx); |
| if (!emitOpIC(compiler.getStub(&stubSpace_))) |
| return false; |
| |
| frame.push(R0); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_ENDITER() |
| { |
| frame.popRegsAndSync(1); |
| |
| ICIteratorClose_Fallback::Compiler compiler(cx); |
| return emitOpIC(compiler.getStub(&stubSpace_)); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_SETRVAL() |
| { |
| // Store to the frame's return value slot. |
| storeValue(frame.peek(-1), frame.addressOfReturnValue(), R2); |
| masm.or32(Imm32(BaselineFrame::HAS_RVAL), frame.addressOfFlags()); |
| frame.pop(); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_CALLEE() |
| { |
| JS_ASSERT(function()); |
| frame.syncStack(0); |
| masm.loadPtr(frame.addressOfCallee(), R0.scratchReg()); |
| masm.tagValue(JSVAL_TYPE_OBJECT, R0.scratchReg(), R0); |
| frame.push(R0); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_POPV() |
| { |
| return emit_JSOP_SETRVAL(); |
| } |
| |
| typedef bool (*NewArgumentsObjectFn)(JSContext *, BaselineFrame *, MutableHandleValue); |
| static const VMFunction NewArgumentsObjectInfo = |
| FunctionInfo<NewArgumentsObjectFn>(jit::NewArgumentsObject); |
| |
| bool |
| BaselineCompiler::emit_JSOP_ARGUMENTS() |
| { |
| frame.syncStack(0); |
| |
| Label done; |
| if (!script->argumentsHasVarBinding() || !script->needsArgsObj()) { |
| // We assume the script does not need an arguments object. However, this |
| // assumption can be invalidated later, see argumentsOptimizationFailed |
| // in JSScript. Because we can't invalidate baseline JIT code, we set a |
| // flag on BaselineScript when that happens and guard on it here. |
| masm.moveValue(MagicValue(JS_OPTIMIZED_ARGUMENTS), R0); |
| |
| // Load script->baseline. |
| Register scratch = R1.scratchReg(); |
| masm.movePtr(ImmGCPtr(script), scratch); |
| masm.loadPtr(Address(scratch, JSScript::offsetOfBaselineScript()), scratch); |
| |
| // If we don't need an arguments object, skip the VM call. |
| masm.branchTest32(Assembler::Zero, Address(scratch, BaselineScript::offsetOfFlags()), |
| Imm32(BaselineScript::NEEDS_ARGS_OBJ), &done); |
| } |
| |
| prepareVMCall(); |
| |
| masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); |
| pushArg(R0.scratchReg()); |
| |
| if (!callVM(NewArgumentsObjectInfo)) |
| return false; |
| |
| masm.bind(&done); |
| frame.push(R0); |
| return true; |
| } |
| |
| typedef bool (*RunOnceScriptPrologueFn)(JSContext *, HandleScript); |
| static const VMFunction RunOnceScriptPrologueInfo = |
| FunctionInfo<RunOnceScriptPrologueFn>(js::RunOnceScriptPrologue); |
| |
| bool |
| BaselineCompiler::emit_JSOP_RUNONCE() |
| { |
| frame.syncStack(0); |
| |
| prepareVMCall(); |
| |
| masm.movePtr(ImmGCPtr(script), R0.scratchReg()); |
| pushArg(R0.scratchReg()); |
| |
| return callVM(RunOnceScriptPrologueInfo); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_REST() |
| { |
| frame.syncStack(0); |
| |
| RootedTypeObject type(cx, types::TypeScript::InitObject(cx, script, pc, JSProto_Array)); |
| if (!type) |
| return false; |
| masm.movePtr(ImmGCPtr(type), R0.scratchReg()); |
| |
| ICRest_Fallback::Compiler stubCompiler(cx); |
| if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) |
| return false; |
| |
| frame.push(R0); |
| return true; |
| } |