| /* -*- 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 "jit/BaselineCompiler.h" |
| |
| #include "mozilla/Casting.h" |
| #include "mozilla/UniquePtr.h" |
| |
| #include "jit/BaselineIC.h" |
| #include "jit/BaselineJIT.h" |
| #include "jit/FixedList.h" |
| #include "jit/IonAnalysis.h" |
| #include "jit/JitcodeMap.h" |
| #include "jit/JitSpewer.h" |
| #include "jit/Linker.h" |
| #ifdef JS_ION_PERF |
| # include "jit/PerfSpewer.h" |
| #endif |
| #include "jit/SharedICHelpers.h" |
| #include "jit/VMFunctions.h" |
| #include "vm/ScopeObject.h" |
| #include "vm/TraceLogging.h" |
| |
| #include "jsscriptinlines.h" |
| |
| #include "jit/MacroAssembler-inl.h" |
| #include "vm/Interpreter-inl.h" |
| #include "vm/NativeObject-inl.h" |
| |
| using namespace js; |
| using namespace js::jit; |
| |
| using mozilla::AssertedCast; |
| |
| BaselineCompiler::BaselineCompiler(JSContext* cx, TempAllocator& alloc, JSScript* script) |
| : BaselineCompilerSpecific(cx, alloc, script), |
| yieldOffsets_(cx), |
| modifiesArguments_(false) |
| { |
| } |
| |
| bool |
| BaselineCompiler::init() |
| { |
| if (!analysis_.init(alloc_, cx->runtime()->gsnCache)) |
| return false; |
| |
| if (!labels_.init(alloc_, script->length())) |
| return false; |
| |
| for (size_t i = 0; i < script->length(); i++) |
| new (&labels_[i]) Label(); |
| |
| if (!frame.init(alloc_)) |
| 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 == script->pcToOffset(pc)) |
| return true; |
| |
| PCMappingEntry entry; |
| entry.pcOffset = script->pcToOffset(pc); |
| entry.nativeOffset = masm.currentOffset(); |
| entry.slotInfo = getStackTopSlotInfo(); |
| entry.addIndexEntry = addIndexEntry; |
| |
| return pcMappingEntries_.append(entry); |
| } |
| |
| MethodStatus |
| BaselineCompiler::compile() |
| { |
| JitSpew(JitSpew_BaselineScripts, "Baseline compiling script %s:%d (%p)", |
| script->filename(), script->lineno(), script); |
| |
| JitSpew(JitSpew_Codegen, "# Emitting baseline code for script %s:%d", |
| script->filename(), script->lineno()); |
| |
| TraceLoggerThread* logger = TraceLoggerForMainThread(cx->runtime()); |
| TraceLoggerEvent scriptEvent(logger, TraceLogger_AnnotateScripts, script); |
| AutoTraceLog logScript(logger, scriptEvent); |
| AutoTraceLog logCompile(logger, TraceLogger_BaselineCompilation); |
| |
| if (!script->ensureHasTypes(cx) || !script->ensureHasAnalyzedArgsUsage(cx)) |
| return Method_Error; |
| |
| // When a Debugger set the collectCoverageInfo flag, we recompile baseline |
| // scripts without entering the interpreter again. We have to create the |
| // ScriptCounts if they do not exist. |
| if (!script->hasScriptCounts() && cx->compartment()->collectCoverage()) { |
| if (!script->initScriptCounts(cx)) |
| return Method_Error; |
| } |
| |
| // Pin analysis info during compilation. |
| AutoEnterAnalysis autoEnterAnalysis(cx); |
| |
| MOZ_ASSERT(!script->hasBaselineScript()); |
| |
| if (!emitPrologue()) |
| return Method_Error; |
| |
| MethodStatus status = emitBody(); |
| if (status != Method_Compiled) |
| return status; |
| |
| if (!emitEpilogue()) |
| return Method_Error; |
| |
| if (!emitOutOfLinePostBarrierSlot()) |
| return Method_Error; |
| |
| Linker linker(masm); |
| if (masm.oom()) { |
| ReportOutOfMemory(cx); |
| return Method_Error; |
| } |
| |
| AutoFlushICache afc("Baseline"); |
| JitCode* code = linker.newCode<CanGC>(cx, BASELINE_CODE); |
| if (!code) |
| return Method_Error; |
| |
| JSObject* templateScope = nullptr; |
| if (script->functionNonDelazifying()) { |
| RootedFunction fun(cx, script->functionNonDelazifying()); |
| if (fun->needsCallObject()) { |
| RootedScript scriptRoot(cx, script); |
| templateScope = CallObject::createTemplateObject(cx, scriptRoot, gc::TenuredHeap); |
| if (!templateScope) |
| return Method_Error; |
| |
| if (fun->isNamedLambda()) { |
| RootedObject declEnvObject(cx, DeclEnvObject::createTemplateObject(cx, fun, TenuredObject)); |
| if (!declEnvObject) |
| return Method_Error; |
| templateScope->as<ScopeObject>().setEnclosingScope(declEnvObject); |
| } |
| } |
| } |
| |
| // 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]; |
| |
| if (entry.addIndexEntry) { |
| PCMappingIndexEntry indexEntry; |
| indexEntry.pcOffset = entry.pcOffset; |
| indexEntry.nativeOffset = entry.nativeOffset; |
| indexEntry.bufferOffset = pcEntries.length(); |
| if (!pcMappingIndexEntries.append(indexEntry)) { |
| ReportOutOfMemory(cx); |
| 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. |
| MOZ_ASSERT((entry.slotInfo.toByte() & 0x80) == 0); |
| |
| if (entry.nativeOffset == previousOffset) { |
| pcEntries.writeByte(entry.slotInfo.toByte()); |
| } else { |
| MOZ_ASSERT(entry.nativeOffset > previousOffset); |
| pcEntries.writeByte(0x80 | entry.slotInfo.toByte()); |
| pcEntries.writeUnsigned(entry.nativeOffset - previousOffset); |
| } |
| |
| previousOffset = entry.nativeOffset; |
| } |
| |
| if (pcEntries.oom()) { |
| ReportOutOfMemory(cx); |
| return Method_Error; |
| } |
| |
| // Note: There is an extra entry in the bytecode type map for the search hint, see below. |
| size_t bytecodeTypeMapEntries = script->nTypeSets() + 1; |
| |
| mozilla::UniquePtr<BaselineScript, JS::DeletePolicy<BaselineScript> > baselineScript( |
| BaselineScript::New(script, prologueOffset_.offset(), |
| epilogueOffset_.offset(), |
| profilerEnterFrameToggleOffset_.offset(), |
| profilerExitFrameToggleOffset_.offset(), |
| traceLoggerEnterToggleOffset_.offset(), |
| traceLoggerExitToggleOffset_.offset(), |
| postDebugPrologueOffset_.offset(), |
| icEntries_.length(), |
| pcMappingIndexEntries.length(), |
| pcEntries.length(), |
| bytecodeTypeMapEntries, |
| yieldOffsets_.length())); |
| if (!baselineScript) { |
| ReportOutOfMemory(cx); |
| return Method_Error; |
| } |
| |
| baselineScript->setMethod(code); |
| baselineScript->setTemplateScope(templateScope); |
| |
| JitSpew(JitSpew_BaselineScripts, "Created BaselineScript %p (raw %p) for %s:%d", |
| (void*) baselineScript.get(), (void*) code->raw(), |
| script->filename(), script->lineno()); |
| |
| #ifdef JS_ION_PERF |
| writePerfSpewerBaselineProfile(script, code); |
| #endif |
| |
| MOZ_ASSERT(pcMappingIndexEntries.length() > 0); |
| baselineScript->copyPCMappingIndexEntries(&pcMappingIndexEntries[0]); |
| |
| MOZ_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_); |
| |
| // All barriers are emitted off-by-default, toggle them on if needed. |
| if (cx->zone()->needsIncrementalBarrier()) |
| baselineScript->toggleBarriers(true); |
| |
| // If profiler instrumentation is enabled, toggle instrumentation on. |
| if (cx->runtime()->jitRuntime()->isProfilerInstrumentationEnabled(cx->runtime())) |
| baselineScript->toggleProfilerInstrumentation(true); |
| |
| AutoWritableJitCode awjc(code); |
| |
| // Patch IC loads using IC entries. |
| for (size_t i = 0; i < icLoadLabels_.length(); i++) { |
| CodeOffset label = icLoadLabels_[i].label; |
| size_t icEntry = icLoadLabels_[i].icEntry; |
| ICEntry* entryAddr = &(baselineScript->icEntry(icEntry)); |
| Assembler::PatchDataWithValueCheck(CodeLocationLabel(code, label), |
| ImmPtr(entryAddr), |
| ImmPtr((void*)-1)); |
| } |
| |
| if (modifiesArguments_) |
| baselineScript->setModifiesArguments(); |
| |
| #ifdef JS_TRACE_LOGGING |
| // Initialize the tracelogger instrumentation. |
| baselineScript->initTraceLogger(cx->runtime(), script); |
| #endif |
| |
| uint32_t* bytecodeMap = baselineScript->bytecodeTypeMap(); |
| FillBytecodeTypeMap(script, bytecodeMap); |
| |
| // The last entry in the last index found, and is used to avoid binary |
| // searches for the sought entry when queries are in linear order. |
| bytecodeMap[script->nTypeSets()] = 0; |
| |
| baselineScript->copyYieldEntries(script, yieldOffsets_); |
| |
| if (compileDebugInstrumentation_) |
| baselineScript->setHasDebugInstrumentation(); |
| |
| // Always register a native => bytecode mapping entry, since profiler can be |
| // turned on with baseline jitcode on stack, and baseline jitcode cannot be invalidated. |
| { |
| JitSpew(JitSpew_Profiling, "Added JitcodeGlobalEntry for baseline script %s:%d (%p)", |
| script->filename(), script->lineno(), baselineScript.get()); |
| |
| // Generate profiling string. |
| char* str = JitcodeGlobalEntry::createScriptString(cx, script); |
| if (!str) |
| return Method_Error; |
| |
| JitcodeGlobalEntry::BaselineEntry entry; |
| entry.init(code, code->raw(), code->rawEnd(), script, str); |
| |
| JitcodeGlobalTable* globalTable = cx->runtime()->jitRuntime()->getJitcodeGlobalTable(); |
| if (!globalTable->addEntry(entry, cx->runtime())) { |
| entry.destroy(); |
| ReportOutOfMemory(cx); |
| return Method_Error; |
| } |
| |
| // Mark the jitcode as having a bytecode map. |
| code->setHasBytecodeMap(); |
| } |
| |
| script->setBaselineScript(cx, baselineScript.release()); |
| |
| return Method_Compiled; |
| } |
| |
| void |
| BaselineCompiler::emitInitializeLocals(size_t n, const Value& v) |
| { |
| MOZ_ASSERT(frame.nlocals() > 0 && n <= frame.nlocals()); |
| |
| // Use R0 to minimize code size. If the number of locals to push is < |
| // LOOP_UNROLL_FACTOR, then the initialization pushes are emitted directly |
| // and inline. Otherwise, they're emitted in a partially unrolled loop. |
| static const size_t LOOP_UNROLL_FACTOR = 4; |
| size_t toPushExtra = n % LOOP_UNROLL_FACTOR; |
| |
| masm.moveValue(v, R0); |
| |
| // Handle any extra pushes left over by the optional unrolled loop below. |
| for (size_t i = 0; i < toPushExtra; i++) |
| masm.pushValue(R0); |
| |
| // Partially unrolled loop of pushes. |
| if (n >= LOOP_UNROLL_FACTOR) { |
| size_t toPush = n - toPushExtra; |
| MOZ_ASSERT(toPush % LOOP_UNROLL_FACTOR == 0); |
| MOZ_ASSERT(toPush >= LOOP_UNROLL_FACTOR); |
| masm.move32(Imm32(toPush), R1.scratchReg()); |
| // Emit unrolled loop with 4 pushes per iteration. |
| Label pushLoop; |
| masm.bind(&pushLoop); |
| for (size_t i = 0; i < LOOP_UNROLL_FACTOR; i++) |
| masm.pushValue(R0); |
| masm.branchSub32(Assembler::NonZero, |
| Imm32(LOOP_UNROLL_FACTOR), R1.scratchReg(), &pushLoop); |
| } |
| } |
| |
| bool |
| BaselineCompiler::emitPrologue() |
| { |
| #ifdef JS_USE_LINK_REGISTER |
| // Push link register from generateEnterJIT()'s BLR. |
| masm.pushReturnAddress(); |
| masm.checkStackAlignment(); |
| #endif |
| emitProfilerEnterFrame(); |
| |
| masm.push(BaselineFrameReg); |
| masm.moveStackPtrTo(BaselineFrameReg); |
| masm.subFromStackPtr(Imm32(BaselineFrame::Size())); |
| |
| // 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()); |
| |
| // Handle scope chain pre-initialization (in case GC gets run |
| // during stack check). For global and eval scripts, the scope |
| // chain is in R1. For function scripts, the scope chain is in |
| // the callee, nullptr is stored for now so that GC doesn't choke |
| // on a bogus ScopeChain value in the frame. |
| if (function()) |
| masm.storePtr(ImmPtr(nullptr), frame.addressOfScopeChain()); |
| else |
| masm.storePtr(R1.scratchReg(), frame.addressOfScopeChain()); |
| |
| // Functions with a large number of locals require two stack checks. |
| // The VMCall for a fallible stack check can only occur after the |
| // scope chain has been initialized, as that is required for proper |
| // exception handling if the VMCall returns false. The scope chain |
| // initialization can only happen after the UndefinedValues for the |
| // local slots have been pushed. |
| // However by that time, the stack might have grown too much. |
| // In these cases, we emit an extra, early, infallible check |
| // before pushing the locals. The early check sets a flag on the |
| // frame if the stack check fails (but otherwise doesn't throw an |
| // exception). If the flag is set, then the jitcode skips past |
| // the pushing of the locals, and directly to scope chain initialization |
| // followed by the actual stack check, which will throw the correct |
| // exception. |
| Label earlyStackCheckFailed; |
| if (needsEarlyStackCheck()) { |
| if (!emitStackCheck(/* earlyCheck = */ true)) |
| return false; |
| masm.branchTest32(Assembler::NonZero, |
| frame.addressOfFlags(), |
| Imm32(BaselineFrame::OVER_RECURSED), |
| &earlyStackCheckFailed); |
| } |
| |
| // Initialize local vars to |undefined| and lexicals to a magic value that |
| // throws on touch. |
| if (frame.nvars() > 0) |
| emitInitializeLocals(frame.nvars(), UndefinedValue()); |
| if (frame.nlexicals() > 0) |
| emitInitializeLocals(frame.nlexicals(), MagicValue(JS_UNINITIALIZED_LEXICAL)); |
| |
| if (needsEarlyStackCheck()) |
| masm.bind(&earlyStackCheckFailed); |
| |
| #ifdef JS_TRACE_LOGGING |
| if (!emitTraceLoggerEnter()) |
| return false; |
| #endif |
| |
| // Record the offset of the prologue, because Ion can bailout before |
| // the scope chain is initialized. |
| prologueOffset_ = CodeOffset(masm.currentOffset()); |
| |
| // When compiling with Debugger instrumentation, set the debuggeeness of |
| // the frame before any operation that can call into the VM. |
| emitIsDebuggeeCheck(); |
| |
| // 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 (!emitWarmUpCounterIncrement()) |
| return false; |
| |
| if (!emitArgumentTypeChecks()) |
| return false; |
| |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emitEpilogue() |
| { |
| // Record the offset of the epilogue, so we can do early return from |
| // Debugger handlers during on-stack recompile. |
| epilogueOffset_ = CodeOffset(masm.currentOffset()); |
| |
| masm.bind(&return_); |
| |
| #ifdef JS_TRACE_LOGGING |
| if (!emitTraceLoggerExit()) |
| return false; |
| #endif |
| |
| masm.moveToStackPtr(BaselineFrameReg); |
| masm.pop(BaselineFrameReg); |
| |
| emitProfilerExitFrame(); |
| |
| masm.ret(); |
| return true; |
| } |
| |
| // On input: |
| // R2.scratchReg() contains object being written to. |
| // Called with the baseline stack synced, except for R0 which is preserved. |
| // 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(); |
| AllocatableGeneralRegisterSet regs(GeneralRegisterSet::All()); |
| regs.take(R0); |
| regs.take(objReg); |
| regs.take(BaselineFrameReg); |
| Register scratch = regs.takeAny(); |
| #if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64) |
| // 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_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) |
| masm.push(ra); |
| #endif |
| masm.pushValue(R0); |
| |
| masm.setupUnalignedABICall(scratch); |
| masm.movePtr(ImmPtr(cx->runtime()), scratch); |
| masm.passABIArg(scratch); |
| masm.passABIArg(objReg); |
| masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, PostWriteBarrier)); |
| |
| masm.popValue(R0); |
| masm.ret(); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emitIC(ICStub* stub, ICEntry::Kind kind) |
| { |
| ICEntry* entry = allocateICEntry(stub, kind); |
| if (!entry) |
| return false; |
| |
| CodeOffset patchOffset; |
| EmitCallIC(&patchOffset, masm); |
| entry->setReturnOffset(CodeOffset(masm.currentOffset())); |
| if (!addICLoadLabel(patchOffset)) |
| return false; |
| |
| return true; |
| } |
| |
| typedef bool (*CheckOverRecursedWithExtraFn)(JSContext*, BaselineFrame*, uint32_t, uint32_t); |
| static const VMFunction CheckOverRecursedWithExtraInfo = |
| FunctionInfo<CheckOverRecursedWithExtraFn>(CheckOverRecursedWithExtra); |
| |
| bool |
| BaselineCompiler::emitStackCheck(bool earlyCheck) |
| { |
| Label skipCall; |
| void* limitAddr = cx->runtime()->addressOfJitStackLimit(); |
| uint32_t slotsSize = script->nslots() * sizeof(Value); |
| uint32_t tolerance = earlyCheck ? slotsSize : 0; |
| |
| masm.moveStackPtrTo(R1.scratchReg()); |
| |
| // If this is the early stack check, locals haven't been pushed yet. Adjust the |
| // stack pointer to account for the locals that would be pushed before performing |
| // the guard around the vmcall to the stack check. |
| if (earlyCheck) |
| masm.subPtr(Imm32(tolerance), R1.scratchReg()); |
| |
| // If this is the late stack check for a frame which contains an early stack check, |
| // then the early stack check might have failed and skipped past the pushing of locals |
| // on the stack. |
| // |
| // If this is a possibility, then the OVER_RECURSED flag should be checked, and the |
| // VMCall to CheckOverRecursed done unconditionally if it's set. |
| Label forceCall; |
| if (!earlyCheck && needsEarlyStackCheck()) { |
| masm.branchTest32(Assembler::NonZero, |
| frame.addressOfFlags(), |
| Imm32(BaselineFrame::OVER_RECURSED), |
| &forceCall); |
| } |
| |
| masm.branchPtr(Assembler::BelowOrEqual, AbsoluteAddress(limitAddr), R1.scratchReg(), |
| &skipCall); |
| |
| if (!earlyCheck && needsEarlyStackCheck()) |
| masm.bind(&forceCall); |
| |
| prepareVMCall(); |
| pushArg(Imm32(earlyCheck)); |
| pushArg(Imm32(tolerance)); |
| masm.loadBaselineFramePtr(BaselineFrameReg, R1.scratchReg()); |
| pushArg(R1.scratchReg()); |
| |
| CallVMPhase phase = POST_INITIALIZE; |
| if (earlyCheck) |
| phase = PRE_INITIALIZE; |
| else if (needsEarlyStackCheck()) |
| phase = CHECK_OVER_RECURSED; |
| |
| if (!callVMNonOp(CheckOverRecursedWithExtraInfo, phase)) |
| return false; |
| |
| icEntries_.back().setFakeKind(earlyCheck |
| ? ICEntry::Kind_EarlyStackCheck |
| : ICEntry::Kind_StackCheck); |
| |
| masm.bind(&skipCall); |
| return true; |
| } |
| |
| void |
| BaselineCompiler::emitIsDebuggeeCheck() |
| { |
| if (compileDebugInstrumentation_) { |
| masm.Push(BaselineFrameReg); |
| masm.setupUnalignedABICall(R0.scratchReg()); |
| masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); |
| masm.passABIArg(R0.scratchReg()); |
| masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, jit::FrameIsDebuggeeCheck)); |
| masm.Pop(BaselineFrameReg); |
| } |
| } |
| |
| typedef bool (*DebugPrologueFn)(JSContext*, BaselineFrame*, jsbytecode*, bool*); |
| static const VMFunction DebugPrologueInfo = FunctionInfo<DebugPrologueFn>(jit::DebugPrologue); |
| |
| bool |
| BaselineCompiler::emitDebugPrologue() |
| { |
| if (compileDebugInstrumentation_) { |
| // Load pointer to BaselineFrame in R0. |
| masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); |
| |
| prepareVMCall(); |
| pushArg(ImmPtr(pc)); |
| pushArg(R0.scratchReg()); |
| if (!callVM(DebugPrologueInfo)) |
| return false; |
| |
| // Fix up the fake ICEntry appended by callVM for on-stack recompilation. |
| icEntries_.back().setFakeKind(ICEntry::Kind_DebugPrologue); |
| |
| // 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); |
| } |
| |
| postDebugPrologueOffset_ = CodeOffset(masm.currentOffset()); |
| |
| return true; |
| } |
| |
| typedef bool (*InitGlobalOrEvalScopeObjectsFn)(JSContext*, BaselineFrame*); |
| static const VMFunction InitGlobalOrEvalScopeObjectsInfo = |
| FunctionInfo<InitGlobalOrEvalScopeObjectsFn>(jit::InitGlobalOrEvalScopeObjects); |
| |
| typedef bool (*InitFunctionScopeObjectsFn)(JSContext*, BaselineFrame*); |
| static const VMFunction InitFunctionScopeObjectsInfo = |
| FunctionInfo<InitFunctionScopeObjectsFn>(jit::InitFunctionScopeObjects); |
| |
| bool |
| BaselineCompiler::initScopeChain() |
| { |
| CallVMPhase phase = POST_INITIALIZE; |
| if (needsEarlyStackCheck()) |
| phase = CHECK_OVER_RECURSED; |
| |
| RootedFunction fun(cx, function()); |
| if (fun) { |
| // Use callee->environment as scope chain. Note that we do |
| // this also for needsCallObject functions, so that the scope |
| // chain slot is properly initialized if the call triggers GC. |
| Register callee = R0.scratchReg(); |
| Register scope = R1.scratchReg(); |
| masm.loadFunctionFromCalleeToken(frame.addressOfCalleeToken(), callee); |
| masm.loadPtr(Address(callee, JSFunction::offsetOfEnvironment()), scope); |
| masm.storePtr(scope, frame.addressOfScopeChain()); |
| |
| if (fun->needsCallObject()) { |
| // Call into the VM to create a new call object. |
| prepareVMCall(); |
| |
| masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); |
| pushArg(R0.scratchReg()); |
| |
| if (!callVMNonOp(InitFunctionScopeObjectsInfo, phase)) |
| return false; |
| } |
| } else if (module()) { |
| // Modules use a pre-created scope object. |
| Register scope = R1.scratchReg(); |
| masm.movePtr(ImmGCPtr(&module()->initialEnvironment()), scope); |
| masm.storePtr(scope, frame.addressOfScopeChain()); |
| } else { |
| // ScopeChain pointer in BaselineFrame has already been initialized |
| // in prologue, but we need to do two more things: |
| // |
| // 1. Check for redeclaration errors |
| // 2. Possibly create a new call object for strict eval. |
| |
| prepareVMCall(); |
| masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); |
| pushArg(R0.scratchReg()); |
| |
| if (!callVMNonOp(InitGlobalOrEvalScopeObjectsInfo, phase)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| typedef bool (*InterruptCheckFn)(JSContext*); |
| static const VMFunction InterruptCheckInfo = FunctionInfo<InterruptCheckFn>(InterruptCheck); |
| |
| bool |
| BaselineCompiler::emitInterruptCheck() |
| { |
| frame.syncStack(0); |
| |
| Label done; |
| void* interrupt = cx->runtimeAddressOfInterruptUint32(); |
| masm.branch32(Assembler::Equal, AbsoluteAddress(interrupt), Imm32(0), &done); |
| |
| prepareVMCall(); |
| if (!callVM(InterruptCheckInfo)) |
| return false; |
| |
| masm.bind(&done); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emitWarmUpCounterIncrement(bool allowOsr) |
| { |
| // Emit no warm-up counter 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 warmUpCounterAddr(scriptReg, JSScript::offsetOfWarmUpCounter()); |
| |
| masm.movePtr(ImmGCPtr(script), scriptReg); |
| masm.load32(warmUpCounterAddr, countReg); |
| masm.add32(Imm32(1), countReg); |
| masm.store32(countReg, warmUpCounterAddr); |
| |
| // If this is a loop inside a catch or finally block, increment the warmup |
| // counter but don't attempt OSR (Ion only compiles the try block). |
| if (analysis_.info(pc).loopEntryInCatchOrFinally) { |
| MOZ_ASSERT(JSOp(*pc) == JSOP_LOOPENTRY); |
| return true; |
| } |
| |
| // OSR not possible at this loop entry. |
| if (!allowOsr) { |
| MOZ_ASSERT(JSOp(*pc) == JSOP_LOOPENTRY); |
| return true; |
| } |
| |
| Label skipCall; |
| |
| const OptimizationInfo* info = IonOptimizations.get(IonOptimizations.firstLevel()); |
| uint32_t warmUpThreshold = info->compilerWarmUpThreshold(script, pc); |
| masm.branch32(Assembler::LessThan, countReg, Imm32(warmUpThreshold), &skipCall); |
| |
| masm.branchPtr(Assembler::Equal, |
| Address(scriptReg, JSScript::offsetOfIonScript()), |
| ImmPtr(ION_COMPILING_SCRIPT), &skipCall); |
| |
| // Call IC. |
| ICWarmUpCounter_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, ICStubCompiler::Engine::Baseline, |
| (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, ICStubCompiler::Engine::Baseline, |
| i + 1); |
| if (!emitNonOpIC(compiler.getStub(&stubSpace_))) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emitDebugTrap() |
| { |
| MOZ_ASSERT(compileDebugInstrumentation_); |
| MOZ_ASSERT(frame.numUnsyncedSlots() == 0); |
| |
| bool enabled = script->stepModeEnabled() || script->hasBreakpointsAt(pc); |
| |
| // Emit patchable call to debug trap handler. |
| JitCode* handler = cx->runtime()->jitRuntime()->debugTrapHandler(cx); |
| if (!handler) |
| return false; |
| mozilla::DebugOnly<CodeOffset> offset = masm.toggledCall(handler, enabled); |
| |
| #ifdef DEBUG |
| // Patchable call offset has to match the pc mapping offset. |
| PCMappingEntry& entry = pcMappingEntries_.back(); |
| MOZ_ASSERT((&offset)->offset() == entry.nativeOffset); |
| #endif |
| |
| // Add an IC entry for the return offset -> pc mapping. |
| return appendICEntry(ICEntry::Kind_DebugTrap, masm.currentOffset()); |
| } |
| |
| void |
| BaselineCompiler::emitCoverage(jsbytecode* pc) |
| { |
| PCCounts* counts = script->maybeGetPCCounts(pc); |
| if (!counts) |
| return; |
| |
| uint64_t* counterAddr = &counts->numExec(); |
| masm.inc64(AbsoluteAddress(counterAddr)); |
| } |
| |
| #ifdef JS_TRACE_LOGGING |
| bool |
| BaselineCompiler::emitTraceLoggerEnter() |
| { |
| TraceLoggerThread* logger = TraceLoggerForMainThread(cx->runtime()); |
| AllocatableRegisterSet regs(RegisterSet::Volatile()); |
| Register loggerReg = regs.takeAnyGeneral(); |
| Register scriptReg = regs.takeAnyGeneral(); |
| |
| Label noTraceLogger; |
| traceLoggerEnterToggleOffset_ = masm.toggledJump(&noTraceLogger); |
| |
| masm.Push(loggerReg); |
| masm.Push(scriptReg); |
| |
| masm.movePtr(ImmPtr(logger), loggerReg); |
| |
| // Script start. |
| masm.movePtr(ImmGCPtr(script), scriptReg); |
| masm.loadPtr(Address(scriptReg, JSScript::offsetOfBaselineScript()), scriptReg); |
| Address scriptEvent(scriptReg, BaselineScript::offsetOfTraceLoggerScriptEvent()); |
| masm.computeEffectiveAddress(scriptEvent, scriptReg); |
| masm.tracelogStartEvent(loggerReg, scriptReg); |
| |
| // Engine start. |
| masm.tracelogStartId(loggerReg, TraceLogger_Baseline, /* force = */ true); |
| |
| masm.Pop(scriptReg); |
| masm.Pop(loggerReg); |
| |
| masm.bind(&noTraceLogger); |
| |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emitTraceLoggerExit() |
| { |
| TraceLoggerThread* logger = TraceLoggerForMainThread(cx->runtime()); |
| AllocatableRegisterSet regs(RegisterSet::Volatile()); |
| Register loggerReg = regs.takeAnyGeneral(); |
| |
| Label noTraceLogger; |
| traceLoggerExitToggleOffset_ = masm.toggledJump(&noTraceLogger); |
| |
| masm.Push(loggerReg); |
| masm.movePtr(ImmPtr(logger), loggerReg); |
| |
| masm.tracelogStopId(loggerReg, TraceLogger_Baseline, /* force = */ true); |
| masm.tracelogStopId(loggerReg, TraceLogger_Scripts, /* force = */ true); |
| |
| masm.Pop(loggerReg); |
| |
| masm.bind(&noTraceLogger); |
| |
| return true; |
| } |
| #endif |
| |
| void |
| BaselineCompiler::emitProfilerEnterFrame() |
| { |
| // Store stack position to lastProfilingFrame variable, guarded by a toggled jump. |
| // Starts off initially disabled. |
| Label noInstrument; |
| CodeOffset toggleOffset = masm.toggledJump(&noInstrument); |
| masm.profilerEnterFrame(masm.getStackPointer(), R0.scratchReg()); |
| masm.bind(&noInstrument); |
| |
| // Store the start offset in the appropriate location. |
| MOZ_ASSERT(!profilerEnterFrameToggleOffset_.bound()); |
| profilerEnterFrameToggleOffset_ = toggleOffset; |
| } |
| |
| void |
| BaselineCompiler::emitProfilerExitFrame() |
| { |
| // Store previous frame to lastProfilingFrame variable, guarded by a toggled jump. |
| // Starts off initially disabled. |
| Label noInstrument; |
| CodeOffset toggleOffset = masm.toggledJump(&noInstrument); |
| masm.profilerExitFrame(); |
| masm.bind(&noInstrument); |
| |
| // Store the start offset in the appropriate location. |
| MOZ_ASSERT(!profilerExitFrameToggleOffset_.bound()); |
| profilerExitFrameToggleOffset_ = toggleOffset; |
| } |
| |
| MethodStatus |
| BaselineCompiler::emitBody() |
| { |
| MOZ_ASSERT(pc == script->code()); |
| |
| bool lastOpUnreachable = false; |
| uint32_t emittedOps = 0; |
| mozilla::DebugOnly<jsbytecode*> prevpc = pc; |
| bool compileCoverage = script->hasScriptCounts(); |
| |
| while (true) { |
| JSOp op = JSOp(*pc); |
| JitSpew(JitSpew_BaselineOp, "Compiling op @ %d: %s", |
| int(script->pcToOffset(pc)), CodeName[op]); |
| |
| BytecodeInfo* info = analysis_.maybeInfo(pc); |
| |
| // Skip unreachable ops. |
| if (!info) { |
| // Test if last instructions and stop emitting in that case. |
| pc += GetBytecodeLength(pc); |
| if (pc >= script->codeEnd()) |
| break; |
| |
| lastOpUnreachable = true; |
| prevpc = pc; |
| 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 (compileDebugInstrumentation_) |
| 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)) { |
| ReportOutOfMemory(cx); |
| return Method_Error; |
| } |
| |
| // Emit traps for breakpoints and step mode. |
| if (compileDebugInstrumentation_ && !emitDebugTrap()) |
| return Method_Error; |
| |
| // Emit code coverage code, to fill the same data as the interpreter. |
| if (compileCoverage) |
| emitCoverage(pc); |
| |
| switch (op) { |
| default: |
| JitSpew(JitSpew_BaselineAbort, "Unhandled op: %s", 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 |
| } |
| |
| // Test if last instructions and stop emitting in that case. |
| pc += GetBytecodeLength(pc); |
| if (pc >= script->codeEnd()) |
| break; |
| |
| emittedOps++; |
| lastOpUnreachable = false; |
| #ifdef DEBUG |
| prevpc = pc; |
| #endif |
| } |
| |
| MOZ_ASSERT(JSOp(*prevpc) == JSOP_RETRVAL); |
| return Method_Compiled; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_NOP() |
| { |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_LABEL() |
| { |
| 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_DUPAT() |
| { |
| frame.syncStack(0); |
| |
| // DUPAT takes a value on the stack and re-pushes it on top. It's like |
| // GETLOCAL but it addresses from the top of the stack instead of from the |
| // stack frame. |
| |
| int depth = -(GET_UINT24(pc) + 1); |
| masm.loadValue(frame.addressOfStackValue(frame.peek(depth)), R0); |
| frame.push(R0); |
| 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 BooleanValue 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 emitWarmUpCounterIncrement(LoopEntryCanIonOsr(pc)); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_VOID() |
| { |
| frame.pop(); |
| frame.push(UndefinedValue()); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_UNDEFINED() |
| { |
| // If this ever changes, change what JSOP_GIMPLICITTHIS does too. |
| 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; |
| } |
| |
| typedef bool (*ThrowUninitializedThisFn)(JSContext*, BaselineFrame* frame); |
| static const VMFunction ThrowUninitializedThisInfo = |
| FunctionInfo<ThrowUninitializedThisFn>(BaselineThrowUninitializedThis); |
| |
| bool |
| BaselineCompiler::emit_JSOP_CHECKTHIS() |
| { |
| frame.syncStack(0); |
| masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R0); |
| |
| return emitCheckThis(R0); |
| } |
| |
| bool |
| BaselineCompiler::emitCheckThis(ValueOperand val) |
| { |
| Label thisOK; |
| masm.branchTestMagic(Assembler::NotEqual, val, &thisOK); |
| |
| prepareVMCall(); |
| |
| masm.loadBaselineFramePtr(BaselineFrameReg, val.scratchReg()); |
| pushArg(val.scratchReg()); |
| |
| if (!callVM(ThrowUninitializedThisInfo)) |
| return false; |
| |
| masm.bind(&thisOK); |
| return true; |
| } |
| |
| typedef bool (*ThrowBadDerivedReturnFn)(JSContext*, HandleValue); |
| static const VMFunction ThrowBadDerivedReturnInfo = |
| FunctionInfo<ThrowBadDerivedReturnFn>(jit::ThrowBadDerivedReturn); |
| |
| bool |
| BaselineCompiler::emit_JSOP_CHECKRETURN() |
| { |
| MOZ_ASSERT(script->isDerivedClassConstructor()); |
| |
| // Load |this| in R0, return value in R1. |
| frame.popRegsAndSync(1); |
| emitLoadReturnValue(R1); |
| |
| Label done, returnOK; |
| masm.branchTestObject(Assembler::Equal, R1, &done); |
| masm.branchTestUndefined(Assembler::Equal, R1, &returnOK); |
| |
| prepareVMCall(); |
| pushArg(R1); |
| if (!callVM(ThrowBadDerivedReturnInfo)) |
| return false; |
| masm.assumeUnreachable("Should throw on bad derived constructor return"); |
| |
| masm.bind(&returnOK); |
| |
| if (!emitCheckThis(R0)) |
| return false; |
| |
| // Store |this| in the return value slot. |
| masm.storeValue(R0, frame.addressOfReturnValue()); |
| masm.or32(Imm32(BaselineFrame::HAS_RVAL), frame.addressOfFlags()); |
| |
| masm.bind(&done); |
| return true; |
| } |
| |
| typedef bool (*GetFunctionThisFn)(JSContext*, BaselineFrame*, MutableHandleValue); |
| static const VMFunction GetFunctionThisInfo = |
| FunctionInfo<GetFunctionThisFn>(jit::BaselineGetFunctionThis); |
| |
| bool |
| BaselineCompiler::emit_JSOP_FUNCTIONTHIS() |
| { |
| MOZ_ASSERT(function()); |
| MOZ_ASSERT(!function()->isArrow()); |
| |
| frame.pushThis(); |
| |
| // In strict mode code or self-hosted functions, |this| is left alone. |
| if (script->strict() || (function() && function()->isSelfHostedBuiltin())) |
| return true; |
| |
| // Load |thisv| in R0. Skip the call if it's already an object. |
| Label skipCall; |
| frame.popRegsAndSync(1); |
| masm.branchTestObject(Assembler::Equal, R0, &skipCall); |
| |
| prepareVMCall(); |
| masm.loadBaselineFramePtr(BaselineFrameReg, R1.scratchReg()); |
| |
| pushArg(R1.scratchReg()); |
| |
| if (!callVM(GetFunctionThisInfo)) |
| return false; |
| |
| masm.bind(&skipCall); |
| frame.push(R0); |
| return true; |
| } |
| |
| typedef bool (*GetNonSyntacticGlobalThisFn)(JSContext*, HandleObject, MutableHandleValue); |
| static const VMFunction GetNonSyntacticGlobalThisInfo = |
| FunctionInfo<GetNonSyntacticGlobalThisFn>(js::GetNonSyntacticGlobalThis); |
| |
| bool |
| BaselineCompiler::emit_JSOP_GLOBALTHIS() |
| { |
| frame.syncStack(0); |
| |
| if (!script->hasNonSyntacticScope()) { |
| ClonedBlockObject* globalLexical = &script->global().lexicalScope(); |
| masm.moveValue(globalLexical->thisValue(), R0); |
| frame.push(R0); |
| return true; |
| } |
| |
| prepareVMCall(); |
| |
| masm.loadPtr(frame.addressOfScopeChain(), R0.scratchReg()); |
| pushArg(R0.scratchReg()); |
| |
| if (!callVM(GetNonSyntacticGlobalThisInfo)) |
| return false; |
| |
| 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_SYMBOL() |
| { |
| unsigned which = GET_UINT8(pc); |
| JS::Symbol* sym = cx->runtime()->wellKnownSymbols->get(which); |
| frame.push(SymbolValue(sym)); |
| return true; |
| } |
| |
| typedef JSObject* (*DeepCloneObjectLiteralFn)(JSContext*, HandleObject, NewObjectKind); |
| static const VMFunction DeepCloneObjectLiteralInfo = |
| FunctionInfo<DeepCloneObjectLiteralFn>(DeepCloneObjectLiteral); |
| |
| bool |
| BaselineCompiler::emit_JSOP_OBJECT() |
| { |
| if (JS::CompartmentOptionsRef(cx).cloneSingletons()) { |
| RootedObject obj(cx, script->getObject(GET_UINT32_INDEX(pc))); |
| if (!obj) |
| return false; |
| |
| prepareVMCall(); |
| |
| pushArg(ImmWord(TenuredObject)); |
| pushArg(ImmGCPtr(obj)); |
| |
| if (!callVM(DeepCloneObjectLiteralInfo)) |
| return false; |
| |
| // Box and push return value. |
| masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0); |
| frame.push(R0); |
| return true; |
| } |
| |
| JS::CompartmentOptionsRef(cx).setSingletonsAsValues(); |
| frame.push(ObjectValue(*script->getObject(pc))); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_CALLSITEOBJ() |
| { |
| RootedObject cso(cx, script->getObject(pc)); |
| RootedObject raw(cx, script->getObject(GET_UINT32_INDEX(pc) + 1)); |
| if (!cso || !raw) |
| return false; |
| RootedValue rawValue(cx); |
| rawValue.setObject(*raw); |
| |
| if (!ProcessCallSiteObjOperation(cx, cso, raw, rawValue)) |
| return false; |
| |
| frame.push(ObjectValue(*cso)); |
| return true; |
| } |
| |
| typedef JSObject* (*CloneRegExpObjectFn)(JSContext*, JSObject*); |
| static const VMFunction CloneRegExpObjectInfo = |
| FunctionInfo<CloneRegExpObjectFn>(CloneRegExpObject); |
| |
| bool |
| BaselineCompiler::emit_JSOP_REGEXP() |
| { |
| RootedObject reObj(cx, script->getRegExp(pc)); |
| |
| prepareVMCall(); |
| 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; |
| } |
| |
| typedef JSObject* (*LambdaArrowFn)(JSContext*, HandleFunction, HandleObject, HandleValue); |
| static const VMFunction LambdaArrowInfo = FunctionInfo<LambdaArrowFn>(js::LambdaArrow); |
| |
| bool |
| BaselineCompiler::emit_JSOP_LAMBDA_ARROW() |
| { |
| // Keep pushed newTarget in R0. |
| frame.popRegsAndSync(1); |
| |
| RootedFunction fun(cx, script->getFunction(GET_UINT32_INDEX(pc))); |
| |
| prepareVMCall(); |
| masm.loadPtr(frame.addressOfScopeChain(), R2.scratchReg()); |
| |
| pushArg(R0); |
| pushArg(R2.scratchReg()); |
| pushArg(ImmGCPtr(fun)); |
| |
| if (!callVM(LambdaArrowInfo)) |
| 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::EvalNewTargetSlot: |
| MOZ_ASSERT(script->isForEval()); |
| masm.loadValue(frame.addressOfEvalNewTarget(), scratch); |
| masm.storeValue(scratch, dest); |
| break; |
| case StackValue::Stack: |
| masm.loadValue(frame.addressOfStackValue(source), scratch); |
| masm.storeValue(scratch, dest); |
| break; |
| default: |
| MOZ_CRASH("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::emit_JSOP_POW() |
| { |
| return emitBinaryArith(); |
| } |
| |
| bool |
| BaselineCompiler::emitBinaryArith() |
| { |
| // Keep top JSStack value in R0 and R2 |
| frame.popRegsAndSync(2); |
| |
| // Call IC |
| ICBinaryArith_Fallback::Compiler stubCompiler(cx, ICStubCompiler::Engine::Baseline); |
| 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, ICStubCompiler::Engine::Baseline); |
| 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, ICStubCompiler::Engine::Baseline); |
| 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, ICStubCompiler::Engine::Baseline); |
| 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.addToStackPtr(Imm32(sizeof(Value))); |
| 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_UINT32(pc); |
| MOZ_ASSERT(length <= INT32_MAX, |
| "the bytecode emitter must fail to compile code that would " |
| "produce JSOP_NEWARRAY with a length exceeding int32_t range"); |
| |
| // Pass length in R0. |
| masm.move32(Imm32(AssertedCast<int32_t>(length)), R0.scratchReg()); |
| |
| ObjectGroup* group = ObjectGroup::allocationSiteGroup(cx, script, pc, JSProto_Array); |
| if (!group) |
| return false; |
| |
| ICNewArray_Fallback::Compiler stubCompiler(cx, group); |
| if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) |
| return false; |
| |
| frame.push(R0); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_SPREADCALLARRAY() |
| { |
| return emit_JSOP_NEWARRAY(); |
| } |
| |
| typedef JSObject* (*NewArrayCopyOnWriteFn)(JSContext*, HandleArrayObject, gc::InitialHeap); |
| const VMFunction jit::NewArrayCopyOnWriteInfo = |
| FunctionInfo<NewArrayCopyOnWriteFn>(js::NewDenseCopyOnWriteArray); |
| |
| bool |
| BaselineCompiler::emit_JSOP_NEWARRAY_COPYONWRITE() |
| { |
| RootedScript scriptRoot(cx, script); |
| JSObject* obj = ObjectGroup::getOrFixupCopyOnWriteObject(cx, scriptRoot, pc); |
| if (!obj) |
| return false; |
| |
| prepareVMCall(); |
| |
| pushArg(Imm32(gc::DefaultHeap)); |
| pushArg(ImmGCPtr(obj)); |
| |
| if (!callVM(NewArrayCopyOnWriteInfo)) |
| return false; |
| |
| // Box and push return value. |
| masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0); |
| 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); |
| uint32_t index = GET_UINT32(pc); |
| MOZ_ASSERT(index <= INT32_MAX, |
| "the bytecode emitter must fail to compile code that would " |
| "produce JSOP_INITELEM_ARRAY with a length exceeding " |
| "int32_t range"); |
| masm.moveValue(Int32Value(AssertedCast<int32_t>(index)), 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); |
| |
| 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)); |
| |
| if (key == JSProto_Array) { |
| // Pass length in R0. |
| masm.move32(Imm32(0), R0.scratchReg()); |
| |
| ObjectGroup* group = ObjectGroup::allocationSiteGroup(cx, script, pc, JSProto_Array); |
| if (!group) |
| return false; |
| |
| ICNewArray_Fallback::Compiler stubCompiler(cx, group); |
| if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) |
| return false; |
| } else { |
| MOZ_ASSERT(key == JSProto_Object); |
| |
| 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_INITHIDDENELEM() |
| { |
| return emit_JSOP_INITELEM(); |
| } |
| |
| typedef bool (*MutateProtoFn)(JSContext* cx, HandlePlainObject obj, HandleValue newProto); |
| static const VMFunction MutateProtoInfo = FunctionInfo<MutateProtoFn>(MutatePrototype); |
| |
| bool |
| BaselineCompiler::emit_JSOP_MUTATEPROTO() |
| { |
| // Keep values on the stack for the decompiler. |
| frame.syncStack(0); |
| |
| masm.extractObject(frame.addressOfStackValue(frame.peek(-2)), R0.scratchReg()); |
| masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R1); |
| |
| prepareVMCall(); |
| |
| pushArg(R1); |
| pushArg(R0.scratchReg()); |
| |
| if (!callVM(MutateProtoInfo)) |
| return false; |
| |
| 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_INITLOCKEDPROP() |
| { |
| return emit_JSOP_INITPROP(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_INITHIDDENPROP() |
| { |
| return emit_JSOP_INITPROP(); |
| } |
| |
| typedef bool (*NewbornArrayPushFn)(JSContext*, HandleObject, const Value&); |
| static const VMFunction NewbornArrayPushInfo = FunctionInfo<NewbornArrayPushFn>(NewbornArrayPush); |
| |
| bool |
| BaselineCompiler::emit_JSOP_ARRAYPUSH() |
| { |
| // Keep value in R0, object in R1. |
| frame.popRegsAndSync(2); |
| masm.unboxObject(R1, R1.scratchReg()); |
| |
| prepareVMCall(); |
| |
| pushArg(R0); |
| pushArg(R1.scratchReg()); |
| |
| return callVM(NewbornArrayPushInfo); |
| } |
| |
| 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_STRICTSETELEM() |
| { |
| return emit_JSOP_SETELEM(); |
| } |
| |
| typedef bool (*DeleteElementFn)(JSContext*, HandleValue, HandleValue, bool*); |
| static const VMFunction DeleteElementStrictInfo |
| = FunctionInfo<DeleteElementFn>(DeleteElementJit<true>); |
| static const VMFunction DeleteElementNonStrictInfo |
| = FunctionInfo<DeleteElementFn>(DeleteElementJit<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); |
| |
| bool strict = JSOp(*pc) == JSOP_STRICTDELELEM; |
| if (!callVM(strict ? DeleteElementStrictInfo : DeleteElementNonStrictInfo)) |
| return false; |
| |
| masm.boxNonDouble(JSVAL_TYPE_BOOLEAN, ReturnReg, R1); |
| frame.popn(2); |
| frame.push(R1); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_STRICTDELELEM() |
| { |
| return emit_JSOP_DELELEM(); |
| } |
| |
| 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() |
| { |
| if (script->hasNonSyntacticScope()) |
| return emit_JSOP_GETNAME(); |
| |
| RootedPropertyName name(cx, script->getName(pc)); |
| |
| // These names are non-configurable on the global and cannot be shadowed. |
| 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().lexicalScope()), 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_BINDGNAME() |
| { |
| if (!script->hasNonSyntacticScope()) { |
| // We can bind name to the global lexical scope if the binding already |
| // exists, is initialized, and is writable (i.e., an initialized |
| // 'let') at compile time. |
| RootedPropertyName name(cx, script->getName(pc)); |
| Rooted<ClonedBlockObject*> globalLexical(cx, &script->global().lexicalScope()); |
| if (Shape* shape = globalLexical->lookup(cx, name)) { |
| if (shape->writable() && |
| !globalLexical->getSlot(shape->slot()).isMagic(JS_UNINITIALIZED_LEXICAL)) |
| { |
| frame.push(ObjectValue(*globalLexical)); |
| return true; |
| } |
| } else if (Shape* shape = script->global().lookup(cx, name)) { |
| // If the property does not currently exist on the global lexical |
| // scope, we can bind name to the global object if the property |
| // exists on the global and is non-configurable, as then it cannot |
| // be shadowed. |
| if (!shape->configurable()) { |
| frame.push(ObjectValue(script->global())); |
| return true; |
| } |
| } |
| |
| // Otherwise we have to use the dynamic scope chain. |
| } |
| |
| return emit_JSOP_BINDNAME(); |
| } |
| |
| 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_STRICTSETPROP() |
| { |
| return emit_JSOP_SETPROP(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_SETNAME() |
| { |
| return emit_JSOP_SETPROP(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_STRICTSETNAME() |
| { |
| return emit_JSOP_SETPROP(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_SETGNAME() |
| { |
| return emit_JSOP_SETPROP(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_STRICTSETGNAME() |
| { |
| return emit_JSOP_SETPROP(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_GETPROP() |
| { |
| // Keep object in R0. |
| frame.popRegsAndSync(1); |
| |
| // Call IC. |
| ICGetProp_Fallback::Compiler compiler(cx, ICStubCompiler::Engine::Baseline); |
| 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, bool*); |
| static const VMFunction DeletePropertyStrictInfo = |
| FunctionInfo<DeletePropertyFn>(DeletePropertyJit<true>); |
| static const VMFunction DeletePropertyNonStrictInfo = |
| FunctionInfo<DeletePropertyFn>(DeletePropertyJit<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); |
| |
| bool strict = JSOp(*pc) == JSOP_STRICTDELPROP; |
| if (!callVM(strict ? DeletePropertyStrictInfo : DeletePropertyNonStrictInfo)) |
| return false; |
| |
| masm.boxNonDouble(JSVAL_TYPE_BOOLEAN, ReturnReg, R1); |
| frame.pop(); |
| frame.push(R1); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_STRICTDELPROP() |
| { |
| return emit_JSOP_DELPROP(); |
| } |
| |
| 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(script, pc); |
| |
| Address addr; |
| if (shape->numFixedSlots() <= sc.slot()) { |
| masm.loadPtr(Address(objReg, NativeObject::offsetOfSlots()), reg); |
| return Address(reg, (sc.slot() - shape->numFixedSlots()) * sizeof(Value)); |
| } |
| |
| return Address(objReg, NativeObject::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); |
| |
| if (ionCompileable_) { |
| // No need to monitor types if we know Ion can't compile this script. |
| ICTypeMonitor_Fallback::Compiler compiler(cx, ICStubCompiler::Engine::Baseline, |
| (ICMonitoredFallbackStub*) nullptr); |
| if (!emitOpIC(compiler.getStub(&stubSpace_))) |
| return false; |
| } |
| |
| frame.push(R0); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_SETALIASEDVAR() |
| { |
| JSScript* outerScript = ScopeCoordinateFunctionScript(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); |
| |
| // Only R0 is live at this point. |
| // Scope coordinate object is already in R2.scratchReg(). |
| Register temp = R1.scratchReg(); |
| |
| Label skipBarrier; |
| masm.branchPtrInNurseryRange(Assembler::Equal, objReg, temp, &skipBarrier); |
| masm.branchValueIsNurseryObject(Assembler::NotEqual, R0, temp, &skipBarrier); |
| |
| masm.call(&postBarrierSlot_); // Won't clobber R0 |
| |
| masm.bind(&skipBarrier); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_GETNAME() |
| { |
| 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_BINDNAME() |
| { |
| frame.syncStack(0); |
| |
| if (*pc == JSOP_BINDGNAME && !script->hasNonSyntacticScope()) |
| masm.movePtr(ImmGCPtr(&script->global().lexicalScope()), R0.scratchReg()); |
| else |
| 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_GETIMPORT() |
| { |
| ModuleEnvironmentObject* env = GetModuleEnvironmentForScript(script); |
| MOZ_ASSERT(env); |
| |
| ModuleEnvironmentObject* targetEnv; |
| Shape* shape; |
| MOZ_ALWAYS_TRUE(env->lookupImport(NameToId(script->getName(pc)), &targetEnv, &shape)); |
| |
| EnsureTrackPropertyTypes(cx, targetEnv, shape->propid()); |
| |
| frame.syncStack(0); |
| |
| uint32_t slot = shape->slot(); |
| Register scratch = R0.scratchReg(); |
| masm.movePtr(ImmGCPtr(targetEnv), scratch); |
| if (slot < targetEnv->numFixedSlots()) { |
| masm.loadValue(Address(scratch, NativeObject::getFixedSlotOffset(slot)), R0); |
| } else { |
| masm.loadPtr(Address(scratch, NativeObject::offsetOfSlots()), scratch); |
| masm.loadValue(Address(scratch, (slot - targetEnv->numFixedSlots()) * sizeof(Value)), R0); |
| } |
| |
| // Imports are initialized by this point except in rare circumstances, so |
| // don't emit a check unless we have to. |
| if (targetEnv->getSlot(shape->slot()).isMagic(JS_UNINITIALIZED_LEXICAL)) |
| emitUninitializedLexicalCheck(R0); |
| |
| if (ionCompileable_) { |
| // No need to monitor types if we know Ion can't compile this script. |
| ICTypeMonitor_Fallback::Compiler compiler(cx, ICStubCompiler::Engine::Baseline, |
| (ICMonitoredFallbackStub*) nullptr); |
| if (!emitOpIC(compiler.getStub(&stubSpace_))) |
| 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; |
| } |
| |
| typedef bool (*DefVarFn)(JSContext*, HandlePropertyName, unsigned, HandleObject); |
| static const VMFunction DefVarInfo = FunctionInfo<DefVarFn>(DefVar); |
| |
| bool |
| BaselineCompiler::emit_JSOP_DEFVAR() |
| { |
| frame.syncStack(0); |
| |
| unsigned attrs = JSPROP_ENUMERATE; |
| if (!script->isForEval()) |
| attrs |= JSPROP_PERMANENT; |
| MOZ_ASSERT(attrs <= UINT32_MAX); |
| |
| masm.loadPtr(frame.addressOfScopeChain(), R0.scratchReg()); |
| |
| prepareVMCall(); |
| |
| pushArg(R0.scratchReg()); |
| pushArg(Imm32(attrs)); |
| pushArg(ImmGCPtr(script->getName(pc))); |
| |
| return callVM(DefVarInfo); |
| } |
| |
| typedef bool (*DefLexicalFn)(JSContext*, HandlePropertyName, unsigned, HandleObject); |
| static const VMFunction DefLexicalInfo = FunctionInfo<DefLexicalFn>(DefLexical); |
| |
| bool |
| BaselineCompiler::emit_JSOP_DEFCONST() |
| { |
| return emit_JSOP_DEFLET(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_DEFLET() |
| { |
| frame.syncStack(0); |
| |
| unsigned attrs = JSPROP_ENUMERATE | JSPROP_PERMANENT; |
| if (*pc == JSOP_DEFCONST) |
| attrs |= JSPROP_READONLY; |
| MOZ_ASSERT(attrs <= UINT32_MAX); |
| |
| masm.loadPtr(frame.addressOfScopeChain(), R0.scratchReg()); |
| |
| prepareVMCall(); |
| |
| pushArg(R0.scratchReg()); |
| pushArg(Imm32(attrs)); |
| pushArg(ImmGCPtr(script->getName(pc))); |
| |
| return callVM(DefLexicalInfo); |
| } |
| |
| 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, |
| HandleObject); |
| static const VMFunction InitPropGetterSetterInfo = |
| FunctionInfo<InitPropGetterSetterFn>(InitGetterSetterOperation); |
| |
| bool |
| BaselineCompiler::emitInitPropGetterSetter() |
| { |
| MOZ_ASSERT(JSOp(*pc) == JSOP_INITPROP_GETTER || |
| JSOp(*pc) == JSOP_INITHIDDENPROP_GETTER || |
| JSOp(*pc) == JSOP_INITPROP_SETTER || |
| JSOp(*pc) == JSOP_INITHIDDENPROP_SETTER); |
| |
| // Keep values on the stack for the decompiler. |
| frame.syncStack(0); |
| |
| prepareVMCall(); |
| |
| masm.extractObject(frame.addressOfStackValue(frame.peek(-1)), R0.scratchReg()); |
| masm.extractObject(frame.addressOfStackValue(frame.peek(-2)), R1.scratchReg()); |
| |
| pushArg(R0.scratchReg()); |
| pushArg(ImmGCPtr(script->getName(pc))); |
| pushArg(R1.scratchReg()); |
| pushArg(ImmPtr(pc)); |
| |
| if (!callVM(InitPropGetterSetterInfo)) |
| return false; |
| |
| frame.pop(); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_INITPROP_GETTER() |
| { |
| return emitInitPropGetterSetter(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_INITHIDDENPROP_GETTER() |
| { |
| return emitInitPropGetterSetter(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_INITPROP_SETTER() |
| { |
| return emitInitPropGetterSetter(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_INITHIDDENPROP_SETTER() |
| { |
| return emitInitPropGetterSetter(); |
| } |
| |
| typedef bool (*InitElemGetterSetterFn)(JSContext*, jsbytecode*, HandleObject, HandleValue, |
| HandleObject); |
| static const VMFunction InitElemGetterSetterInfo = |
| FunctionInfo<InitElemGetterSetterFn>(InitGetterSetterOperation); |
| |
| bool |
| BaselineCompiler::emitInitElemGetterSetter() |
| { |
| MOZ_ASSERT(JSOp(*pc) == JSOP_INITELEM_GETTER || |
| JSOp(*pc) == JSOP_INITHIDDENELEM_GETTER || |
| JSOp(*pc) == JSOP_INITELEM_SETTER || |
| JSOp(*pc) == JSOP_INITHIDDENELEM_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.extractObject(frame.addressOfStackValue(frame.peek(-1)), R1.scratchReg()); |
| |
| prepareVMCall(); |
| |
| pushArg(R1.scratchReg()); |
| pushArg(R0); |
| masm.extractObject(frame.addressOfStackValue(frame.peek(-3)), R0.scratchReg()); |
| pushArg(R0.scratchReg()); |
| pushArg(ImmPtr(pc)); |
| |
| if (!callVM(InitElemGetterSetterInfo)) |
| return false; |
| |
| frame.popn(2); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_INITELEM_GETTER() |
| { |
| return emitInitElemGetterSetter(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_INITHIDDENELEM_GETTER() |
| { |
| return emitInitElemGetterSetter(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_INITELEM_SETTER() |
| { |
| return emitInitElemGetterSetter(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_INITHIDDENELEM_SETTER() |
| { |
| return emitInitElemGetterSetter(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_INITELEM_INC() |
| { |
| // Keep the object and rhs on the stack. |
| frame.syncStack(0); |
| |
| // Load object in R0, index in R1. |
| masm.loadValue(frame.addressOfStackValue(frame.peek(-3)), R0); |
| masm.loadValue(frame.addressOfStackValue(frame.peek(-2)), R1); |
| |
| // Call IC. |
| ICSetElem_Fallback::Compiler stubCompiler(cx); |
| if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) |
| return false; |
| |
| // Pop the rhs |
| frame.pop(); |
| |
| // Increment index |
| Address indexAddr = frame.addressOfStackValue(frame.peek(-1)); |
| masm.incrementInt32Value(indexAddr); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_GETLOCAL() |
| { |
| frame.pushLocal(GET_LOCALNO(pc)); |
| return true; |
| } |
| |
| 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_LOCALNO(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 formals don't |
| // alias the arguments object. |
| if (!script->argumentsAliasesFormals()) { |
| 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); |
| masm.loadValue(frame.addressOfStackValue(frame.peek(-1)), R0); |
| masm.storeValue(R0, argAddr); |
| |
| MOZ_ASSERT(frame.numUnsyncedSlots() == 0); |
| |
| Register temp = R1.scratchReg(); |
| |
| // Reload the arguments object |
| Register reg = R2.scratchReg(); |
| masm.loadPtr(Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfArgsObj()), reg); |
| |
| Label skipBarrier; |
| |
| masm.branchPtrInNurseryRange(Assembler::Equal, reg, temp, &skipBarrier); |
| masm.branchValueIsNurseryObject(Assembler::NotEqual, R0, temp, &skipBarrier); |
| |
| masm.call(&postBarrierSlot_); |
| |
| masm.bind(&skipBarrier); |
| } |
| |
| masm.bind(&done); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_GETARG() |
| { |
| uint32_t arg = GET_ARGNO(pc); |
| return emitFormalArgAccess(arg, /* get = */ true); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_SETARG() |
| { |
| // Ionmonkey can't inline functions with SETARG with magic arguments. |
| if (!script->argsObjAliasesFormals() && script->argumentsAliasesFormals()) |
| script->setUninlineable(); |
| |
| modifiesArguments_ = true; |
| |
| uint32_t arg = GET_ARGNO(pc); |
| return emitFormalArgAccess(arg, /* get = */ false); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_NEWTARGET() |
| { |
| if (script->isForEval()) { |
| frame.pushEvalNewTarget(); |
| return true; |
| } |
| |
| MOZ_ASSERT(function()); |
| frame.syncStack(0); |
| |
| if (function()->isArrow()) { |
| // Arrow functions store their |new.target| value in an |
| // extended slot. |
| Register scratch = R0.scratchReg(); |
| masm.loadFunctionFromCalleeToken(frame.addressOfCalleeToken(), scratch); |
| masm.loadValue(Address(scratch, FunctionExtended::offsetOfArrowNewTargetSlot()), R0); |
| frame.push(R0); |
| return true; |
| } |
| |
| // if (!isConstructing()) push(undefined) |
| Label constructing, done; |
| masm.branchTestPtr(Assembler::NonZero, frame.addressOfCalleeToken(), |
| Imm32(CalleeToken_FunctionConstructing), &constructing); |
| masm.moveValue(UndefinedValue(), R0); |
| masm.jump(&done); |
| |
| masm.bind(&constructing); |
| |
| // else push(argv[Max(numActualArgs, numFormalArgs)]) |
| Register argvLen = R0.scratchReg(); |
| |
| Address actualArgs(BaselineFrameReg, BaselineFrame::offsetOfNumActualArgs()); |
| masm.loadPtr(actualArgs, argvLen); |
| |
| Label actualArgsSufficient; |
| |
| masm.branchPtr(Assembler::AboveOrEqual, argvLen, Imm32(function()->nargs()), |
| &actualArgsSufficient); |
| masm.move32(Imm32(function()->nargs()), argvLen); |
| masm.bind(&actualArgsSufficient); |
| |
| BaseValueIndex newTarget(BaselineFrameReg, argvLen, BaselineFrame::offsetOfArg(0)); |
| masm.loadValue(newTarget, R0); |
| |
| masm.bind(&done); |
| frame.push(R0); |
| |
| return true; |
| } |
| |
| typedef bool (*ThrowRuntimeLexicalErrorFn)(JSContext* cx, unsigned); |
| static const VMFunction ThrowRuntimeLexicalErrorInfo = |
| FunctionInfo<ThrowRuntimeLexicalErrorFn>(jit::ThrowRuntimeLexicalError); |
| |
| bool |
| BaselineCompiler::emitThrowConstAssignment() |
| { |
| prepareVMCall(); |
| pushArg(Imm32(JSMSG_BAD_CONST_ASSIGN)); |
| return callVM(ThrowRuntimeLexicalErrorInfo); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_THROWSETCONST() |
| { |
| return emitThrowConstAssignment(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_THROWSETALIASEDCONST() |
| { |
| return emitThrowConstAssignment(); |
| } |
| |
| bool |
| BaselineCompiler::emitUninitializedLexicalCheck(const ValueOperand& val) |
| { |
| Label done; |
| masm.branchTestMagicValue(Assembler::NotEqual, val, JS_UNINITIALIZED_LEXICAL, &done); |
| |
| prepareVMCall(); |
| pushArg(Imm32(JSMSG_UNINITIALIZED_LEXICAL)); |
| if (!callVM(ThrowRuntimeLexicalErrorInfo)) |
| return false; |
| |
| masm.bind(&done); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_CHECKLEXICAL() |
| { |
| frame.syncStack(0); |
| masm.loadValue(frame.addressOfLocal(GET_LOCALNO(pc)), R0); |
| return emitUninitializedLexicalCheck(R0); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_INITLEXICAL() |
| { |
| return emit_JSOP_SETLOCAL(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_INITGLEXICAL() |
| { |
| frame.popRegsAndSync(1); |
| frame.push(ObjectValue(script->global().lexicalScope())); |
| frame.push(R0); |
| return emit_JSOP_SETPROP(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_CHECKALIASEDLEXICAL() |
| { |
| frame.syncStack(0); |
| masm.loadValue(getScopeCoordinateAddress(R0.scratchReg()), R0); |
| return emitUninitializedLexicalCheck(R0); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_INITALIASEDLEXICAL() |
| { |
| return emit_JSOP_SETALIASEDVAR(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_UNINITIALIZED() |
| { |
| frame.push(MagicValue(JS_UNINITIALIZED_LEXICAL)); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emitCall() |
| { |
| MOZ_ASSERT(IsCallPC(pc)); |
| |
| bool construct = JSOp(*pc) == JSOP_NEW || JSOp(*pc) == JSOP_SUPERCALL; |
| uint32_t argc = GET_ARGC(pc); |
| |
| frame.syncStack(0); |
| masm.move32(Imm32(argc), R0.scratchReg()); |
| |
| // Call IC |
| ICCall_Fallback::Compiler stubCompiler(cx, /* isConstructing = */ construct, |
| /* isSpread = */ false); |
| if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) |
| return false; |
| |
| // Update FrameInfo. |
| frame.popn(2 + argc + construct); |
| frame.push(R0); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emitSpreadCall() |
| { |
| MOZ_ASSERT(IsCallPC(pc)); |
| |
| frame.syncStack(0); |
| masm.move32(Imm32(1), R0.scratchReg()); |
| |
| // Call IC |
| bool construct = JSOp(*pc) == JSOP_SPREADNEW || JSOp(*pc) == JSOP_SPREADSUPERCALL; |
| ICCall_Fallback::Compiler stubCompiler(cx, /* isConstructing = */ construct, |
| /* isSpread = */ true); |
| if (!emitOpIC(stubCompiler.getStub(&stubSpace_))) |
| return false; |
| |
| // Update FrameInfo. |
| frame.popn(3 + construct); |
| frame.push(R0); |
| return true; |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_CALL() |
| { |
| return emitCall(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_CALLITER() |
| { |
| return emitCall(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_NEW() |
| { |
| return emitCall(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_SUPERCALL() |
| { |
| return emitCall(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_FUNCALL() |
| { |
| return emitCall(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_FUNAPPLY() |
| { |
| return emitCall(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_EVAL() |
| { |
| return emitCall(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_STRICTEVAL() |
| { |
| return emitCall(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_SPREADCALL() |
| { |
| return emitSpreadCall(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_SPREADNEW() |
| { |
| return emitSpreadCall(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_SPREADSUPERCALL() |
| { |
| return emitSpreadCall(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_SPREADEVAL() |
| { |
| return emitSpreadCall(); |
| } |
| |
| bool |
| BaselineCompiler::emit_JSOP_STRICTSPREADEVAL() |
| { |
| return emitSpreadCall(); |
| } |
| |
| 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_GIMPLICITTHIS() |
| { |
| if (!script->hasNonSyntacticScope()) { |
| frame.push(UndefinedValue()); |
| return true; |
| } |
| |
| return emit_JSOP_IMPLICITTHIS(); |
| } |
| |
| 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 (*ThrowMsgFn)(JSContext*, const unsigned); |
| static const VMFunction ThrowMsgInfo = FunctionInfo<ThrowMsgFn>(js::ThrowMsgOperation); |
| |
| bool |
| BaselineCompiler::emit_JSOP_THROWMSG() |
| { |
| prepareVMCall(); |
| pushArg(Imm32(GET_UINT16(pc))); |
| return callVM(ThrowMsgInfo); |
| } |
| |
| 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); |
| } |
| |