summaryrefslogtreecommitdiffstats
path: root/js/src/jit/shared/CodeGenerator-shared.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/jit/shared/CodeGenerator-shared.cpp')
-rw-r--r--js/src/jit/shared/CodeGenerator-shared.cpp1865
1 files changed, 1865 insertions, 0 deletions
diff --git a/js/src/jit/shared/CodeGenerator-shared.cpp b/js/src/jit/shared/CodeGenerator-shared.cpp
new file mode 100644
index 000000000..ba5d9d2f5
--- /dev/null
+++ b/js/src/jit/shared/CodeGenerator-shared.cpp
@@ -0,0 +1,1865 @@
+/* -*- 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/shared/CodeGenerator-shared-inl.h"
+
+#include "mozilla/DebugOnly.h"
+#include "mozilla/SizePrintfMacros.h"
+
+#include "jit/CompactBuffer.h"
+#include "jit/IonCaches.h"
+#include "jit/JitcodeMap.h"
+#include "jit/JitSpewer.h"
+#include "jit/MacroAssembler.h"
+#include "jit/MIR.h"
+#include "jit/MIRGenerator.h"
+#include "jit/OptimizationTracking.h"
+#include "js/Conversions.h"
+#include "vm/TraceLogging.h"
+
+#include "jit/JitFrames-inl.h"
+#include "jit/MacroAssembler-inl.h"
+
+using namespace js;
+using namespace js::jit;
+
+using mozilla::BitwiseCast;
+using mozilla::DebugOnly;
+
+namespace js {
+namespace jit {
+
+MacroAssembler&
+CodeGeneratorShared::ensureMasm(MacroAssembler* masmArg)
+{
+ if (masmArg)
+ return *masmArg;
+ maybeMasm_.emplace();
+ return *maybeMasm_;
+}
+
+CodeGeneratorShared::CodeGeneratorShared(MIRGenerator* gen, LIRGraph* graph, MacroAssembler* masmArg)
+ : maybeMasm_(),
+ masm(ensureMasm(masmArg)),
+ gen(gen),
+ graph(*graph),
+ current(nullptr),
+ snapshots_(),
+ recovers_(),
+ deoptTable_(nullptr),
+#ifdef DEBUG
+ pushedArgs_(0),
+#endif
+ lastOsiPointOffset_(0),
+ safepoints_(graph->totalSlotCount(), (gen->info().nargs() + 1) * sizeof(Value)),
+ returnLabel_(),
+ stubSpace_(),
+ nativeToBytecodeMap_(nullptr),
+ nativeToBytecodeMapSize_(0),
+ nativeToBytecodeTableOffset_(0),
+ nativeToBytecodeNumRegions_(0),
+ nativeToBytecodeScriptList_(nullptr),
+ nativeToBytecodeScriptListLength_(0),
+ trackedOptimizationsMap_(nullptr),
+ trackedOptimizationsMapSize_(0),
+ trackedOptimizationsRegionTableOffset_(0),
+ trackedOptimizationsTypesTableOffset_(0),
+ trackedOptimizationsAttemptsTableOffset_(0),
+ osrEntryOffset_(0),
+ skipArgCheckEntryOffset_(0),
+#ifdef CHECK_OSIPOINT_REGISTERS
+ checkOsiPointRegisters(JitOptions.checkOsiPointRegisters),
+#endif
+ frameDepth_(graph->paddedLocalSlotsSize() + graph->argumentsSize()),
+ frameInitialAdjustment_(0)
+{
+ if (gen->isProfilerInstrumentationEnabled())
+ masm.enableProfilingInstrumentation();
+
+ if (gen->compilingWasm()) {
+ // Since wasm uses the system ABI which does not necessarily use a
+ // regular array where all slots are sizeof(Value), it maintains the max
+ // argument stack depth separately.
+ MOZ_ASSERT(graph->argumentSlotCount() == 0);
+ frameDepth_ += gen->wasmMaxStackArgBytes();
+
+ if (gen->usesSimd()) {
+ // If the function uses any SIMD then we may need to insert padding
+ // so that local slots are aligned for SIMD.
+ frameInitialAdjustment_ = ComputeByteAlignment(sizeof(wasm::Frame),
+ WasmStackAlignment);
+ frameDepth_ += frameInitialAdjustment_;
+ // Keep the stack aligned. Some SIMD sequences build values on the
+ // stack and need the stack aligned.
+ frameDepth_ += ComputeByteAlignment(sizeof(wasm::Frame) + frameDepth_,
+ WasmStackAlignment);
+ } else if (gen->performsCall()) {
+ // An MWasmCall does not align the stack pointer at calls sites but
+ // instead relies on the a priori stack adjustment. This must be the
+ // last adjustment of frameDepth_.
+ frameDepth_ += ComputeByteAlignment(sizeof(wasm::Frame) + frameDepth_,
+ WasmStackAlignment);
+ }
+
+ // FrameSizeClass is only used for bailing, which cannot happen in
+ // wasm code.
+ frameClass_ = FrameSizeClass::None();
+ } else {
+ frameClass_ = FrameSizeClass::FromDepth(frameDepth_);
+ }
+}
+
+bool
+CodeGeneratorShared::generatePrologue()
+{
+ MOZ_ASSERT(masm.framePushed() == 0);
+ MOZ_ASSERT(!gen->compilingWasm());
+
+#ifdef JS_USE_LINK_REGISTER
+ masm.pushReturnAddress();
+#endif
+
+ // If profiling, save the current frame pointer to a per-thread global field.
+ if (isProfilerInstrumentationEnabled())
+ masm.profilerEnterFrame(masm.getStackPointer(), CallTempReg0);
+
+ // Ensure that the Ion frame is properly aligned.
+ masm.assertStackAlignment(JitStackAlignment, 0);
+
+ // Note that this automatically sets MacroAssembler::framePushed().
+ masm.reserveStack(frameSize());
+ masm.checkStackAlignment();
+
+ emitTracelogIonStart();
+ return true;
+}
+
+bool
+CodeGeneratorShared::generateEpilogue()
+{
+ MOZ_ASSERT(!gen->compilingWasm());
+ masm.bind(&returnLabel_);
+
+ emitTracelogIonStop();
+
+ masm.freeStack(frameSize());
+ MOZ_ASSERT(masm.framePushed() == 0);
+
+ // If profiling, reset the per-thread global lastJitFrame to point to
+ // the previous frame.
+ if (isProfilerInstrumentationEnabled())
+ masm.profilerExitFrame();
+
+ masm.ret();
+
+ // On systems that use a constant pool, this is a good time to emit.
+ masm.flushBuffer();
+ return true;
+}
+
+bool
+CodeGeneratorShared::generateOutOfLineCode()
+{
+ for (size_t i = 0; i < outOfLineCode_.length(); i++) {
+ // Add native => bytecode mapping entries for OOL sites.
+ // Not enabled on wasm yet since it doesn't contain bytecode mappings.
+ if (!gen->compilingWasm()) {
+ if (!addNativeToBytecodeEntry(outOfLineCode_[i]->bytecodeSite()))
+ return false;
+ }
+
+ if (!gen->alloc().ensureBallast())
+ return false;
+
+ JitSpew(JitSpew_Codegen, "# Emitting out of line code");
+
+ masm.setFramePushed(outOfLineCode_[i]->framePushed());
+ lastPC_ = outOfLineCode_[i]->pc();
+ outOfLineCode_[i]->bind(&masm);
+
+ outOfLineCode_[i]->generate(this);
+ }
+
+ return !masm.oom();
+}
+
+void
+CodeGeneratorShared::addOutOfLineCode(OutOfLineCode* code, const MInstruction* mir)
+{
+ MOZ_ASSERT(mir);
+ addOutOfLineCode(code, mir->trackedSite());
+}
+
+void
+CodeGeneratorShared::addOutOfLineCode(OutOfLineCode* code, const BytecodeSite* site)
+{
+ code->setFramePushed(masm.framePushed());
+ code->setBytecodeSite(site);
+ MOZ_ASSERT_IF(!gen->compilingWasm(), code->script()->containsPC(code->pc()));
+ masm.propagateOOM(outOfLineCode_.append(code));
+}
+
+bool
+CodeGeneratorShared::addNativeToBytecodeEntry(const BytecodeSite* site)
+{
+ // Skip the table entirely if profiling is not enabled.
+ if (!isProfilerInstrumentationEnabled())
+ return true;
+
+ // Fails early if the last added instruction caused the macro assembler to
+ // run out of memory as continuity assumption below do not hold.
+ if (masm.oom())
+ return false;
+
+ MOZ_ASSERT(site);
+ MOZ_ASSERT(site->tree());
+ MOZ_ASSERT(site->pc());
+
+ InlineScriptTree* tree = site->tree();
+ jsbytecode* pc = site->pc();
+ uint32_t nativeOffset = masm.currentOffset();
+
+ MOZ_ASSERT_IF(nativeToBytecodeList_.empty(), nativeOffset == 0);
+
+ if (!nativeToBytecodeList_.empty()) {
+ size_t lastIdx = nativeToBytecodeList_.length() - 1;
+ NativeToBytecode& lastEntry = nativeToBytecodeList_[lastIdx];
+
+ MOZ_ASSERT(nativeOffset >= lastEntry.nativeOffset.offset());
+
+ // If the new entry is for the same inlineScriptTree and same
+ // bytecodeOffset, but the nativeOffset has changed, do nothing.
+ // The same site just generated some more code.
+ if (lastEntry.tree == tree && lastEntry.pc == pc) {
+ JitSpew(JitSpew_Profiling, " => In-place update [%" PRIuSIZE "-%" PRIu32 "]",
+ lastEntry.nativeOffset.offset(), nativeOffset);
+ return true;
+ }
+
+ // If the new entry is for the same native offset, then update the
+ // previous entry with the new bytecode site, since the previous
+ // bytecode site did not generate any native code.
+ if (lastEntry.nativeOffset.offset() == nativeOffset) {
+ lastEntry.tree = tree;
+ lastEntry.pc = pc;
+ JitSpew(JitSpew_Profiling, " => Overwriting zero-length native region.");
+
+ // This overwrite might have made the entry merge-able with a
+ // previous one. If so, merge it.
+ if (lastIdx > 0) {
+ NativeToBytecode& nextToLastEntry = nativeToBytecodeList_[lastIdx - 1];
+ if (nextToLastEntry.tree == lastEntry.tree && nextToLastEntry.pc == lastEntry.pc) {
+ JitSpew(JitSpew_Profiling, " => Merging with previous region");
+ nativeToBytecodeList_.erase(&lastEntry);
+ }
+ }
+
+ dumpNativeToBytecodeEntry(nativeToBytecodeList_.length() - 1);
+ return true;
+ }
+ }
+
+ // Otherwise, some native code was generated for the previous bytecode site.
+ // Add a new entry for code that is about to be generated.
+ NativeToBytecode entry;
+ entry.nativeOffset = CodeOffset(nativeOffset);
+ entry.tree = tree;
+ entry.pc = pc;
+ if (!nativeToBytecodeList_.append(entry))
+ return false;
+
+ JitSpew(JitSpew_Profiling, " => Push new entry.");
+ dumpNativeToBytecodeEntry(nativeToBytecodeList_.length() - 1);
+ return true;
+}
+
+void
+CodeGeneratorShared::dumpNativeToBytecodeEntries()
+{
+#ifdef JS_JITSPEW
+ InlineScriptTree* topTree = gen->info().inlineScriptTree();
+ JitSpewStart(JitSpew_Profiling, "Native To Bytecode Entries for %s:%" PRIuSIZE "\n",
+ topTree->script()->filename(), topTree->script()->lineno());
+ for (unsigned i = 0; i < nativeToBytecodeList_.length(); i++)
+ dumpNativeToBytecodeEntry(i);
+#endif
+}
+
+void
+CodeGeneratorShared::dumpNativeToBytecodeEntry(uint32_t idx)
+{
+#ifdef JS_JITSPEW
+ NativeToBytecode& ref = nativeToBytecodeList_[idx];
+ InlineScriptTree* tree = ref.tree;
+ JSScript* script = tree->script();
+ uint32_t nativeOffset = ref.nativeOffset.offset();
+ unsigned nativeDelta = 0;
+ unsigned pcDelta = 0;
+ if (idx + 1 < nativeToBytecodeList_.length()) {
+ NativeToBytecode* nextRef = &ref + 1;
+ nativeDelta = nextRef->nativeOffset.offset() - nativeOffset;
+ if (nextRef->tree == ref.tree)
+ pcDelta = nextRef->pc - ref.pc;
+ }
+ JitSpewStart(JitSpew_Profiling, " %08" PRIxSIZE " [+%-6d] => %-6ld [%-4d] {%-10s} (%s:%" PRIuSIZE,
+ ref.nativeOffset.offset(),
+ nativeDelta,
+ (long) (ref.pc - script->code()),
+ pcDelta,
+ CodeName[JSOp(*ref.pc)],
+ script->filename(), script->lineno());
+
+ for (tree = tree->caller(); tree; tree = tree->caller()) {
+ JitSpewCont(JitSpew_Profiling, " <= %s:%" PRIuSIZE, tree->script()->filename(),
+ tree->script()->lineno());
+ }
+ JitSpewCont(JitSpew_Profiling, ")");
+ JitSpewFin(JitSpew_Profiling);
+#endif
+}
+
+bool
+CodeGeneratorShared::addTrackedOptimizationsEntry(const TrackedOptimizations* optimizations)
+{
+ if (!isOptimizationTrackingEnabled())
+ return true;
+
+ MOZ_ASSERT(optimizations);
+
+ uint32_t nativeOffset = masm.currentOffset();
+
+ if (!trackedOptimizations_.empty()) {
+ NativeToTrackedOptimizations& lastEntry = trackedOptimizations_.back();
+ MOZ_ASSERT_IF(!masm.oom(), nativeOffset >= lastEntry.endOffset.offset());
+
+ // If we're still generating code for the same set of optimizations,
+ // we are done.
+ if (lastEntry.optimizations == optimizations)
+ return true;
+ }
+
+ // If we're generating code for a new set of optimizations, add a new
+ // entry.
+ NativeToTrackedOptimizations entry;
+ entry.startOffset = CodeOffset(nativeOffset);
+ entry.endOffset = CodeOffset(nativeOffset);
+ entry.optimizations = optimizations;
+ return trackedOptimizations_.append(entry);
+}
+
+void
+CodeGeneratorShared::extendTrackedOptimizationsEntry(const TrackedOptimizations* optimizations)
+{
+ if (!isOptimizationTrackingEnabled())
+ return;
+
+ uint32_t nativeOffset = masm.currentOffset();
+ NativeToTrackedOptimizations& entry = trackedOptimizations_.back();
+ MOZ_ASSERT(entry.optimizations == optimizations);
+ MOZ_ASSERT_IF(!masm.oom(), nativeOffset >= entry.endOffset.offset());
+
+ entry.endOffset = CodeOffset(nativeOffset);
+
+ // If we generated no code, remove the last entry.
+ if (nativeOffset == entry.startOffset.offset())
+ trackedOptimizations_.popBack();
+}
+
+// see OffsetOfFrameSlot
+static inline int32_t
+ToStackIndex(LAllocation* a)
+{
+ if (a->isStackSlot()) {
+ MOZ_ASSERT(a->toStackSlot()->slot() >= 1);
+ return a->toStackSlot()->slot();
+ }
+ return -int32_t(sizeof(JitFrameLayout) + a->toArgument()->index());
+}
+
+void
+CodeGeneratorShared::encodeAllocation(LSnapshot* snapshot, MDefinition* mir,
+ uint32_t* allocIndex)
+{
+ if (mir->isBox())
+ mir = mir->toBox()->getOperand(0);
+
+ MIRType type =
+ mir->isRecoveredOnBailout() ? MIRType::None :
+ mir->isUnused() ? MIRType::MagicOptimizedOut :
+ mir->type();
+
+ RValueAllocation alloc;
+
+ switch (type) {
+ case MIRType::None:
+ {
+ MOZ_ASSERT(mir->isRecoveredOnBailout());
+ uint32_t index = 0;
+ LRecoverInfo* recoverInfo = snapshot->recoverInfo();
+ MNode** it = recoverInfo->begin();
+ MNode** end = recoverInfo->end();
+ while (it != end && mir != *it) {
+ ++it;
+ ++index;
+ }
+
+ // This MDefinition is recovered, thus it should be listed in the
+ // LRecoverInfo.
+ MOZ_ASSERT(it != end && mir == *it);
+
+ // Lambda should have a default value readable for iterating over the
+ // inner frames.
+ if (mir->isLambda()) {
+ MConstant* constant = mir->toLambda()->functionOperand();
+ uint32_t cstIndex;
+ masm.propagateOOM(graph.addConstantToPool(constant->toJSValue(), &cstIndex));
+ alloc = RValueAllocation::RecoverInstruction(index, cstIndex);
+ break;
+ }
+
+ alloc = RValueAllocation::RecoverInstruction(index);
+ break;
+ }
+ case MIRType::Undefined:
+ alloc = RValueAllocation::Undefined();
+ break;
+ case MIRType::Null:
+ alloc = RValueAllocation::Null();
+ break;
+ case MIRType::Int32:
+ case MIRType::String:
+ case MIRType::Symbol:
+ case MIRType::Object:
+ case MIRType::ObjectOrNull:
+ case MIRType::Boolean:
+ case MIRType::Double:
+ {
+ LAllocation* payload = snapshot->payloadOfSlot(*allocIndex);
+ if (payload->isConstant()) {
+ MConstant* constant = mir->toConstant();
+ uint32_t index;
+ masm.propagateOOM(graph.addConstantToPool(constant->toJSValue(), &index));
+ alloc = RValueAllocation::ConstantPool(index);
+ break;
+ }
+
+ JSValueType valueType =
+ (type == MIRType::ObjectOrNull) ? JSVAL_TYPE_OBJECT : ValueTypeFromMIRType(type);
+
+ MOZ_ASSERT(payload->isMemory() || payload->isRegister());
+ if (payload->isMemory())
+ alloc = RValueAllocation::Typed(valueType, ToStackIndex(payload));
+ else if (payload->isGeneralReg())
+ alloc = RValueAllocation::Typed(valueType, ToRegister(payload));
+ else if (payload->isFloatReg())
+ alloc = RValueAllocation::Double(ToFloatRegister(payload));
+ break;
+ }
+ case MIRType::Float32:
+ case MIRType::Int8x16:
+ case MIRType::Int16x8:
+ case MIRType::Int32x4:
+ case MIRType::Float32x4:
+ case MIRType::Bool8x16:
+ case MIRType::Bool16x8:
+ case MIRType::Bool32x4:
+ {
+ LAllocation* payload = snapshot->payloadOfSlot(*allocIndex);
+ if (payload->isConstant()) {
+ MConstant* constant = mir->toConstant();
+ uint32_t index;
+ masm.propagateOOM(graph.addConstantToPool(constant->toJSValue(), &index));
+ alloc = RValueAllocation::ConstantPool(index);
+ break;
+ }
+
+ MOZ_ASSERT(payload->isMemory() || payload->isFloatReg());
+ if (payload->isFloatReg())
+ alloc = RValueAllocation::AnyFloat(ToFloatRegister(payload));
+ else
+ alloc = RValueAllocation::AnyFloat(ToStackIndex(payload));
+ break;
+ }
+ case MIRType::MagicOptimizedArguments:
+ case MIRType::MagicOptimizedOut:
+ case MIRType::MagicUninitializedLexical:
+ case MIRType::MagicIsConstructing:
+ {
+ uint32_t index;
+ JSWhyMagic why = JS_GENERIC_MAGIC;
+ switch (type) {
+ case MIRType::MagicOptimizedArguments:
+ why = JS_OPTIMIZED_ARGUMENTS;
+ break;
+ case MIRType::MagicOptimizedOut:
+ why = JS_OPTIMIZED_OUT;
+ break;
+ case MIRType::MagicUninitializedLexical:
+ why = JS_UNINITIALIZED_LEXICAL;
+ break;
+ case MIRType::MagicIsConstructing:
+ why = JS_IS_CONSTRUCTING;
+ break;
+ default:
+ MOZ_CRASH("Invalid Magic MIRType");
+ }
+
+ Value v = MagicValue(why);
+ masm.propagateOOM(graph.addConstantToPool(v, &index));
+ alloc = RValueAllocation::ConstantPool(index);
+ break;
+ }
+ default:
+ {
+ MOZ_ASSERT(mir->type() == MIRType::Value);
+ LAllocation* payload = snapshot->payloadOfSlot(*allocIndex);
+#ifdef JS_NUNBOX32
+ LAllocation* type = snapshot->typeOfSlot(*allocIndex);
+ if (type->isRegister()) {
+ if (payload->isRegister())
+ alloc = RValueAllocation::Untyped(ToRegister(type), ToRegister(payload));
+ else
+ alloc = RValueAllocation::Untyped(ToRegister(type), ToStackIndex(payload));
+ } else {
+ if (payload->isRegister())
+ alloc = RValueAllocation::Untyped(ToStackIndex(type), ToRegister(payload));
+ else
+ alloc = RValueAllocation::Untyped(ToStackIndex(type), ToStackIndex(payload));
+ }
+#elif JS_PUNBOX64
+ if (payload->isRegister())
+ alloc = RValueAllocation::Untyped(ToRegister(payload));
+ else
+ alloc = RValueAllocation::Untyped(ToStackIndex(payload));
+#endif
+ break;
+ }
+ }
+
+ // This set an extra bit as part of the RValueAllocation, such that we know
+ // that recover instruction have to be executed without wrapping the
+ // instruction in a no-op recover instruction.
+ if (mir->isIncompleteObject())
+ alloc.setNeedSideEffect();
+
+ masm.propagateOOM(snapshots_.add(alloc));
+
+ *allocIndex += mir->isRecoveredOnBailout() ? 0 : 1;
+}
+
+void
+CodeGeneratorShared::encode(LRecoverInfo* recover)
+{
+ if (recover->recoverOffset() != INVALID_RECOVER_OFFSET)
+ return;
+
+ uint32_t numInstructions = recover->numInstructions();
+ JitSpew(JitSpew_IonSnapshots, "Encoding LRecoverInfo %p (frameCount %u, instructions %u)",
+ (void*)recover, recover->mir()->frameCount(), numInstructions);
+
+ MResumePoint::Mode mode = recover->mir()->mode();
+ MOZ_ASSERT(mode != MResumePoint::Outer);
+ bool resumeAfter = (mode == MResumePoint::ResumeAfter);
+
+ RecoverOffset offset = recovers_.startRecover(numInstructions, resumeAfter);
+
+ for (MNode* insn : *recover)
+ recovers_.writeInstruction(insn);
+
+ recovers_.endRecover();
+ recover->setRecoverOffset(offset);
+ masm.propagateOOM(!recovers_.oom());
+}
+
+void
+CodeGeneratorShared::encode(LSnapshot* snapshot)
+{
+ if (snapshot->snapshotOffset() != INVALID_SNAPSHOT_OFFSET)
+ return;
+
+ LRecoverInfo* recoverInfo = snapshot->recoverInfo();
+ encode(recoverInfo);
+
+ RecoverOffset recoverOffset = recoverInfo->recoverOffset();
+ MOZ_ASSERT(recoverOffset != INVALID_RECOVER_OFFSET);
+
+ JitSpew(JitSpew_IonSnapshots, "Encoding LSnapshot %p (LRecover %p)",
+ (void*)snapshot, (void*) recoverInfo);
+
+ SnapshotOffset offset = snapshots_.startSnapshot(recoverOffset, snapshot->bailoutKind());
+
+#ifdef TRACK_SNAPSHOTS
+ uint32_t pcOpcode = 0;
+ uint32_t lirOpcode = 0;
+ uint32_t lirId = 0;
+ uint32_t mirOpcode = 0;
+ uint32_t mirId = 0;
+
+ if (LNode* ins = instruction()) {
+ lirOpcode = ins->op();
+ lirId = ins->id();
+ if (ins->mirRaw()) {
+ mirOpcode = ins->mirRaw()->op();
+ mirId = ins->mirRaw()->id();
+ if (ins->mirRaw()->trackedPc())
+ pcOpcode = *ins->mirRaw()->trackedPc();
+ }
+ }
+ snapshots_.trackSnapshot(pcOpcode, mirOpcode, mirId, lirOpcode, lirId);
+#endif
+
+ uint32_t allocIndex = 0;
+ for (LRecoverInfo::OperandIter it(recoverInfo); !it; ++it) {
+ DebugOnly<uint32_t> allocWritten = snapshots_.allocWritten();
+ encodeAllocation(snapshot, *it, &allocIndex);
+ MOZ_ASSERT_IF(!snapshots_.oom(), allocWritten + 1 == snapshots_.allocWritten());
+ }
+
+ MOZ_ASSERT(allocIndex == snapshot->numSlots());
+ snapshots_.endSnapshot();
+ snapshot->setSnapshotOffset(offset);
+ masm.propagateOOM(!snapshots_.oom());
+}
+
+bool
+CodeGeneratorShared::assignBailoutId(LSnapshot* snapshot)
+{
+ MOZ_ASSERT(snapshot->snapshotOffset() != INVALID_SNAPSHOT_OFFSET);
+
+ // Can we not use bailout tables at all?
+ if (!deoptTable_)
+ return false;
+
+ MOZ_ASSERT(frameClass_ != FrameSizeClass::None());
+
+ if (snapshot->bailoutId() != INVALID_BAILOUT_ID)
+ return true;
+
+ // Is the bailout table full?
+ if (bailouts_.length() >= BAILOUT_TABLE_SIZE)
+ return false;
+
+ unsigned bailoutId = bailouts_.length();
+ snapshot->setBailoutId(bailoutId);
+ JitSpew(JitSpew_IonSnapshots, "Assigned snapshot bailout id %u", bailoutId);
+ masm.propagateOOM(bailouts_.append(snapshot->snapshotOffset()));
+ return true;
+}
+
+bool
+CodeGeneratorShared::encodeSafepoints()
+{
+ for (SafepointIndex& index : safepointIndices_) {
+ LSafepoint* safepoint = index.safepoint();
+
+ if (!safepoint->encoded())
+ safepoints_.encode(safepoint);
+
+ index.resolve();
+ }
+
+ return !safepoints_.oom();
+}
+
+bool
+CodeGeneratorShared::createNativeToBytecodeScriptList(JSContext* cx)
+{
+ js::Vector<JSScript*, 0, SystemAllocPolicy> scriptList;
+ InlineScriptTree* tree = gen->info().inlineScriptTree();
+ for (;;) {
+ // Add script from current tree.
+ bool found = false;
+ for (uint32_t i = 0; i < scriptList.length(); i++) {
+ if (scriptList[i] == tree->script()) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ if (!scriptList.append(tree->script()))
+ return false;
+ }
+
+ // Process rest of tree
+
+ // If children exist, emit children.
+ if (tree->hasChildren()) {
+ tree = tree->firstChild();
+ continue;
+ }
+
+ // Otherwise, find the first tree up the chain (including this one)
+ // that contains a next sibling.
+ while (!tree->hasNextCallee() && tree->hasCaller())
+ tree = tree->caller();
+
+ // If we found a sibling, use it.
+ if (tree->hasNextCallee()) {
+ tree = tree->nextCallee();
+ continue;
+ }
+
+ // Otherwise, we must have reached the top without finding any siblings.
+ MOZ_ASSERT(tree->isOutermostCaller());
+ break;
+ }
+
+ // Allocate array for list.
+ JSScript** data = cx->runtime()->pod_malloc<JSScript*>(scriptList.length());
+ if (!data)
+ return false;
+
+ for (uint32_t i = 0; i < scriptList.length(); i++)
+ data[i] = scriptList[i];
+
+ // Success.
+ nativeToBytecodeScriptListLength_ = scriptList.length();
+ nativeToBytecodeScriptList_ = data;
+ return true;
+}
+
+bool
+CodeGeneratorShared::generateCompactNativeToBytecodeMap(JSContext* cx, JitCode* code)
+{
+ MOZ_ASSERT(nativeToBytecodeScriptListLength_ == 0);
+ MOZ_ASSERT(nativeToBytecodeScriptList_ == nullptr);
+ MOZ_ASSERT(nativeToBytecodeMap_ == nullptr);
+ MOZ_ASSERT(nativeToBytecodeMapSize_ == 0);
+ MOZ_ASSERT(nativeToBytecodeTableOffset_ == 0);
+ MOZ_ASSERT(nativeToBytecodeNumRegions_ == 0);
+
+ if (!createNativeToBytecodeScriptList(cx))
+ return false;
+
+ MOZ_ASSERT(nativeToBytecodeScriptListLength_ > 0);
+ MOZ_ASSERT(nativeToBytecodeScriptList_ != nullptr);
+
+ CompactBufferWriter writer;
+ uint32_t tableOffset = 0;
+ uint32_t numRegions = 0;
+
+ if (!JitcodeIonTable::WriteIonTable(
+ writer, nativeToBytecodeScriptList_, nativeToBytecodeScriptListLength_,
+ &nativeToBytecodeList_[0],
+ &nativeToBytecodeList_[0] + nativeToBytecodeList_.length(),
+ &tableOffset, &numRegions))
+ {
+ js_free(nativeToBytecodeScriptList_);
+ return false;
+ }
+
+ MOZ_ASSERT(tableOffset > 0);
+ MOZ_ASSERT(numRegions > 0);
+
+ // Writer is done, copy it to sized buffer.
+ uint8_t* data = cx->runtime()->pod_malloc<uint8_t>(writer.length());
+ if (!data) {
+ js_free(nativeToBytecodeScriptList_);
+ return false;
+ }
+
+ memcpy(data, writer.buffer(), writer.length());
+ nativeToBytecodeMap_ = data;
+ nativeToBytecodeMapSize_ = writer.length();
+ nativeToBytecodeTableOffset_ = tableOffset;
+ nativeToBytecodeNumRegions_ = numRegions;
+
+ verifyCompactNativeToBytecodeMap(code);
+
+ JitSpew(JitSpew_Profiling, "Compact Native To Bytecode Map [%p-%p]",
+ data, data + nativeToBytecodeMapSize_);
+
+ return true;
+}
+
+void
+CodeGeneratorShared::verifyCompactNativeToBytecodeMap(JitCode* code)
+{
+#ifdef DEBUG
+ MOZ_ASSERT(nativeToBytecodeScriptListLength_ > 0);
+ MOZ_ASSERT(nativeToBytecodeScriptList_ != nullptr);
+ MOZ_ASSERT(nativeToBytecodeMap_ != nullptr);
+ MOZ_ASSERT(nativeToBytecodeMapSize_ > 0);
+ MOZ_ASSERT(nativeToBytecodeTableOffset_ > 0);
+ MOZ_ASSERT(nativeToBytecodeNumRegions_ > 0);
+
+ // The pointer to the table must be 4-byte aligned
+ const uint8_t* tablePtr = nativeToBytecodeMap_ + nativeToBytecodeTableOffset_;
+ MOZ_ASSERT(uintptr_t(tablePtr) % sizeof(uint32_t) == 0);
+
+ // Verify that numRegions was encoded correctly.
+ const JitcodeIonTable* ionTable = reinterpret_cast<const JitcodeIonTable*>(tablePtr);
+ MOZ_ASSERT(ionTable->numRegions() == nativeToBytecodeNumRegions_);
+
+ // Region offset for first region should be at the start of the payload region.
+ // Since the offsets are backward from the start of the table, the first entry
+ // backoffset should be equal to the forward table offset from the start of the
+ // allocated data.
+ MOZ_ASSERT(ionTable->regionOffset(0) == nativeToBytecodeTableOffset_);
+
+ // Verify each region.
+ for (uint32_t i = 0; i < ionTable->numRegions(); i++) {
+ // Back-offset must point into the payload region preceding the table, not before it.
+ MOZ_ASSERT(ionTable->regionOffset(i) <= nativeToBytecodeTableOffset_);
+
+ // Back-offset must point to a later area in the payload region than previous
+ // back-offset. This means that back-offsets decrease monotonically.
+ MOZ_ASSERT_IF(i > 0, ionTable->regionOffset(i) < ionTable->regionOffset(i - 1));
+
+ JitcodeRegionEntry entry = ionTable->regionEntry(i);
+
+ // Ensure native code offset for region falls within jitcode.
+ MOZ_ASSERT(entry.nativeOffset() <= code->instructionsSize());
+
+ // Read out script/pc stack and verify.
+ JitcodeRegionEntry::ScriptPcIterator scriptPcIter = entry.scriptPcIterator();
+ while (scriptPcIter.hasMore()) {
+ uint32_t scriptIdx = 0, pcOffset = 0;
+ scriptPcIter.readNext(&scriptIdx, &pcOffset);
+
+ // Ensure scriptIdx refers to a valid script in the list.
+ MOZ_ASSERT(scriptIdx < nativeToBytecodeScriptListLength_);
+ JSScript* script = nativeToBytecodeScriptList_[scriptIdx];
+
+ // Ensure pcOffset falls within the script.
+ MOZ_ASSERT(pcOffset < script->length());
+ }
+
+ // Obtain the original nativeOffset and pcOffset and script.
+ uint32_t curNativeOffset = entry.nativeOffset();
+ JSScript* script = nullptr;
+ uint32_t curPcOffset = 0;
+ {
+ uint32_t scriptIdx = 0;
+ scriptPcIter.reset();
+ scriptPcIter.readNext(&scriptIdx, &curPcOffset);
+ script = nativeToBytecodeScriptList_[scriptIdx];
+ }
+
+ // Read out nativeDeltas and pcDeltas and verify.
+ JitcodeRegionEntry::DeltaIterator deltaIter = entry.deltaIterator();
+ while (deltaIter.hasMore()) {
+ uint32_t nativeDelta = 0;
+ int32_t pcDelta = 0;
+ deltaIter.readNext(&nativeDelta, &pcDelta);
+
+ curNativeOffset += nativeDelta;
+ curPcOffset = uint32_t(int32_t(curPcOffset) + pcDelta);
+
+ // Ensure that nativeOffset still falls within jitcode after delta.
+ MOZ_ASSERT(curNativeOffset <= code->instructionsSize());
+
+ // Ensure that pcOffset still falls within bytecode after delta.
+ MOZ_ASSERT(curPcOffset < script->length());
+ }
+ }
+#endif // DEBUG
+}
+
+bool
+CodeGeneratorShared::generateCompactTrackedOptimizationsMap(JSContext* cx, JitCode* code,
+ IonTrackedTypeVector* allTypes)
+{
+ MOZ_ASSERT(trackedOptimizationsMap_ == nullptr);
+ MOZ_ASSERT(trackedOptimizationsMapSize_ == 0);
+ MOZ_ASSERT(trackedOptimizationsRegionTableOffset_ == 0);
+ MOZ_ASSERT(trackedOptimizationsTypesTableOffset_ == 0);
+ MOZ_ASSERT(trackedOptimizationsAttemptsTableOffset_ == 0);
+
+ if (trackedOptimizations_.empty())
+ return true;
+
+ UniqueTrackedOptimizations unique(cx);
+ if (!unique.init())
+ return false;
+
+ // Iterate through all entries to deduplicate their optimization attempts.
+ for (size_t i = 0; i < trackedOptimizations_.length(); i++) {
+ NativeToTrackedOptimizations& entry = trackedOptimizations_[i];
+ if (!unique.add(entry.optimizations))
+ return false;
+ }
+
+ // Sort the unique optimization attempts by frequency to stabilize the
+ // attempts' indices in the compact table we will write later.
+ if (!unique.sortByFrequency(cx))
+ return false;
+
+ // Write out the ranges and the table.
+ CompactBufferWriter writer;
+ uint32_t numRegions;
+ uint32_t regionTableOffset;
+ uint32_t typesTableOffset;
+ uint32_t attemptsTableOffset;
+ if (!WriteIonTrackedOptimizationsTable(cx, writer,
+ trackedOptimizations_.begin(),
+ trackedOptimizations_.end(),
+ unique, &numRegions,
+ &regionTableOffset, &typesTableOffset,
+ &attemptsTableOffset, allTypes))
+ {
+ return false;
+ }
+
+ MOZ_ASSERT(regionTableOffset > 0);
+ MOZ_ASSERT(typesTableOffset > 0);
+ MOZ_ASSERT(attemptsTableOffset > 0);
+ MOZ_ASSERT(typesTableOffset > regionTableOffset);
+ MOZ_ASSERT(attemptsTableOffset > typesTableOffset);
+
+ // Copy over the table out of the writer's buffer.
+ uint8_t* data = cx->runtime()->pod_malloc<uint8_t>(writer.length());
+ if (!data)
+ return false;
+
+ memcpy(data, writer.buffer(), writer.length());
+ trackedOptimizationsMap_ = data;
+ trackedOptimizationsMapSize_ = writer.length();
+ trackedOptimizationsRegionTableOffset_ = regionTableOffset;
+ trackedOptimizationsTypesTableOffset_ = typesTableOffset;
+ trackedOptimizationsAttemptsTableOffset_ = attemptsTableOffset;
+
+ verifyCompactTrackedOptimizationsMap(code, numRegions, unique, allTypes);
+
+ JitSpew(JitSpew_OptimizationTracking,
+ "== Compact Native To Optimizations Map [%p-%p] size %u",
+ data, data + trackedOptimizationsMapSize_, trackedOptimizationsMapSize_);
+ JitSpew(JitSpew_OptimizationTracking,
+ " with type list of length %" PRIuSIZE ", size %" PRIuSIZE,
+ allTypes->length(), allTypes->length() * sizeof(IonTrackedTypeWithAddendum));
+
+ return true;
+}
+
+#ifdef DEBUG
+class ReadTempAttemptsVectorOp : public JS::ForEachTrackedOptimizationAttemptOp
+{
+ TempOptimizationAttemptsVector* attempts_;
+ bool oom_;
+
+ public:
+ explicit ReadTempAttemptsVectorOp(TempOptimizationAttemptsVector* attempts)
+ : attempts_(attempts), oom_(false)
+ { }
+
+ bool oom() {
+ return oom_;
+ }
+
+ void operator()(JS::TrackedStrategy strategy, JS::TrackedOutcome outcome) override {
+ if (!attempts_->append(OptimizationAttempt(strategy, outcome)))
+ oom_ = true;
+ }
+};
+
+struct ReadTempTypeInfoVectorOp : public IonTrackedOptimizationsTypeInfo::ForEachOp
+{
+ TempAllocator& alloc_;
+ TempOptimizationTypeInfoVector* types_;
+ TempTypeList accTypes_;
+ bool oom_;
+
+ public:
+ ReadTempTypeInfoVectorOp(TempAllocator& alloc, TempOptimizationTypeInfoVector* types)
+ : alloc_(alloc),
+ types_(types),
+ accTypes_(alloc),
+ oom_(false)
+ { }
+
+ bool oom() {
+ return oom_;
+ }
+
+ void readType(const IonTrackedTypeWithAddendum& tracked) override {
+ if (!accTypes_.append(tracked.type))
+ oom_ = true;
+ }
+
+ void operator()(JS::TrackedTypeSite site, MIRType mirType) override {
+ OptimizationTypeInfo ty(alloc_, site, mirType);
+ for (uint32_t i = 0; i < accTypes_.length(); i++) {
+ if (!ty.trackType(accTypes_[i]))
+ oom_ = true;
+ }
+ if (!types_->append(mozilla::Move(ty)))
+ oom_ = true;
+ accTypes_.clear();
+ }
+};
+#endif // DEBUG
+
+void
+CodeGeneratorShared::verifyCompactTrackedOptimizationsMap(JitCode* code, uint32_t numRegions,
+ const UniqueTrackedOptimizations& unique,
+ const IonTrackedTypeVector* allTypes)
+{
+#ifdef DEBUG
+ MOZ_ASSERT(trackedOptimizationsMap_ != nullptr);
+ MOZ_ASSERT(trackedOptimizationsMapSize_ > 0);
+ MOZ_ASSERT(trackedOptimizationsRegionTableOffset_ > 0);
+ MOZ_ASSERT(trackedOptimizationsTypesTableOffset_ > 0);
+ MOZ_ASSERT(trackedOptimizationsAttemptsTableOffset_ > 0);
+
+ // Table pointers must all be 4-byte aligned.
+ const uint8_t* regionTableAddr = trackedOptimizationsMap_ +
+ trackedOptimizationsRegionTableOffset_;
+ const uint8_t* typesTableAddr = trackedOptimizationsMap_ +
+ trackedOptimizationsTypesTableOffset_;
+ const uint8_t* attemptsTableAddr = trackedOptimizationsMap_ +
+ trackedOptimizationsAttemptsTableOffset_;
+ MOZ_ASSERT(uintptr_t(regionTableAddr) % sizeof(uint32_t) == 0);
+ MOZ_ASSERT(uintptr_t(typesTableAddr) % sizeof(uint32_t) == 0);
+ MOZ_ASSERT(uintptr_t(attemptsTableAddr) % sizeof(uint32_t) == 0);
+
+ // Assert that the number of entries matches up for the tables.
+ const IonTrackedOptimizationsRegionTable* regionTable =
+ (const IonTrackedOptimizationsRegionTable*) regionTableAddr;
+ MOZ_ASSERT(regionTable->numEntries() == numRegions);
+ const IonTrackedOptimizationsTypesTable* typesTable =
+ (const IonTrackedOptimizationsTypesTable*) typesTableAddr;
+ MOZ_ASSERT(typesTable->numEntries() == unique.count());
+ const IonTrackedOptimizationsAttemptsTable* attemptsTable =
+ (const IonTrackedOptimizationsAttemptsTable*) attemptsTableAddr;
+ MOZ_ASSERT(attemptsTable->numEntries() == unique.count());
+
+ // Verify each region.
+ uint32_t trackedIdx = 0;
+ for (uint32_t regionIdx = 0; regionIdx < regionTable->numEntries(); regionIdx++) {
+ // Check reverse offsets are within bounds.
+ MOZ_ASSERT(regionTable->entryOffset(regionIdx) <= trackedOptimizationsRegionTableOffset_);
+ MOZ_ASSERT_IF(regionIdx > 0, regionTable->entryOffset(regionIdx) <
+ regionTable->entryOffset(regionIdx - 1));
+
+ IonTrackedOptimizationsRegion region = regionTable->entry(regionIdx);
+
+ // Check the region range is covered by jitcode.
+ MOZ_ASSERT(region.startOffset() <= code->instructionsSize());
+ MOZ_ASSERT(region.endOffset() <= code->instructionsSize());
+
+ IonTrackedOptimizationsRegion::RangeIterator iter = region.ranges();
+ while (iter.more()) {
+ // Assert that the offsets are correctly decoded from the delta.
+ uint32_t startOffset, endOffset;
+ uint8_t index;
+ iter.readNext(&startOffset, &endOffset, &index);
+ NativeToTrackedOptimizations& entry = trackedOptimizations_[trackedIdx++];
+ MOZ_ASSERT(startOffset == entry.startOffset.offset());
+ MOZ_ASSERT(endOffset == entry.endOffset.offset());
+ MOZ_ASSERT(index == unique.indexOf(entry.optimizations));
+
+ // Assert that the type info and attempts vectors are correctly
+ // decoded. This is disabled for now if the types table might
+ // contain nursery pointers, in which case the types might not
+ // match, see bug 1175761.
+ if (!code->runtimeFromMainThread()->gc.storeBuffer.cancelIonCompilations()) {
+ IonTrackedOptimizationsTypeInfo typeInfo = typesTable->entry(index);
+ TempOptimizationTypeInfoVector tvec(alloc());
+ ReadTempTypeInfoVectorOp top(alloc(), &tvec);
+ typeInfo.forEach(top, allTypes);
+ MOZ_ASSERT_IF(!top.oom(), entry.optimizations->matchTypes(tvec));
+ }
+
+ IonTrackedOptimizationsAttempts attempts = attemptsTable->entry(index);
+ TempOptimizationAttemptsVector avec(alloc());
+ ReadTempAttemptsVectorOp aop(&avec);
+ attempts.forEach(aop);
+ MOZ_ASSERT_IF(!aop.oom(), entry.optimizations->matchAttempts(avec));
+ }
+ }
+#endif
+}
+
+void
+CodeGeneratorShared::markSafepoint(LInstruction* ins)
+{
+ markSafepointAt(masm.currentOffset(), ins);
+}
+
+void
+CodeGeneratorShared::markSafepointAt(uint32_t offset, LInstruction* ins)
+{
+ MOZ_ASSERT_IF(!safepointIndices_.empty() && !masm.oom(),
+ offset - safepointIndices_.back().displacement() >= sizeof(uint32_t));
+ masm.propagateOOM(safepointIndices_.append(SafepointIndex(offset, ins->safepoint())));
+}
+
+void
+CodeGeneratorShared::ensureOsiSpace()
+{
+ // For a refresher, an invalidation point is of the form:
+ // 1: call <target>
+ // 2: ...
+ // 3: <osipoint>
+ //
+ // The four bytes *before* instruction 2 are overwritten with an offset.
+ // Callers must ensure that the instruction itself has enough bytes to
+ // support this.
+ //
+ // The bytes *at* instruction 3 are overwritten with an invalidation jump.
+ // jump. These bytes may be in a completely different IR sequence, but
+ // represent the join point of the call out of the function.
+ //
+ // At points where we want to ensure that invalidation won't corrupt an
+ // important instruction, we make sure to pad with nops.
+ if (masm.currentOffset() - lastOsiPointOffset_ < Assembler::PatchWrite_NearCallSize()) {
+ int32_t paddingSize = Assembler::PatchWrite_NearCallSize();
+ paddingSize -= masm.currentOffset() - lastOsiPointOffset_;
+ for (int32_t i = 0; i < paddingSize; ++i)
+ masm.nop();
+ }
+ MOZ_ASSERT_IF(!masm.oom(),
+ masm.currentOffset() - lastOsiPointOffset_ >= Assembler::PatchWrite_NearCallSize());
+ lastOsiPointOffset_ = masm.currentOffset();
+}
+
+uint32_t
+CodeGeneratorShared::markOsiPoint(LOsiPoint* ins)
+{
+ encode(ins->snapshot());
+ ensureOsiSpace();
+
+ uint32_t offset = masm.currentOffset();
+ SnapshotOffset so = ins->snapshot()->snapshotOffset();
+ masm.propagateOOM(osiIndices_.append(OsiIndex(offset, so)));
+
+ return offset;
+}
+
+#ifdef CHECK_OSIPOINT_REGISTERS
+template <class Op>
+static void
+HandleRegisterDump(Op op, MacroAssembler& masm, LiveRegisterSet liveRegs, Register activation,
+ Register scratch)
+{
+ const size_t baseOffset = JitActivation::offsetOfRegs();
+
+ // Handle live GPRs.
+ for (GeneralRegisterIterator iter(liveRegs.gprs()); iter.more(); ++iter) {
+ Register reg = *iter;
+ Address dump(activation, baseOffset + RegisterDump::offsetOfRegister(reg));
+
+ if (reg == activation) {
+ // To use the original value of the activation register (that's
+ // now on top of the stack), we need the scratch register.
+ masm.push(scratch);
+ masm.loadPtr(Address(masm.getStackPointer(), sizeof(uintptr_t)), scratch);
+ op(scratch, dump);
+ masm.pop(scratch);
+ } else {
+ op(reg, dump);
+ }
+ }
+
+ // Handle live FPRs.
+ for (FloatRegisterIterator iter(liveRegs.fpus()); iter.more(); ++iter) {
+ FloatRegister reg = *iter;
+ Address dump(activation, baseOffset + RegisterDump::offsetOfRegister(reg));
+ op(reg, dump);
+ }
+}
+
+class StoreOp
+{
+ MacroAssembler& masm;
+
+ public:
+ explicit StoreOp(MacroAssembler& masm)
+ : masm(masm)
+ {}
+
+ void operator()(Register reg, Address dump) {
+ masm.storePtr(reg, dump);
+ }
+ void operator()(FloatRegister reg, Address dump) {
+ if (reg.isDouble())
+ masm.storeDouble(reg, dump);
+ else if (reg.isSingle())
+ masm.storeFloat32(reg, dump);
+#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
+ else if (reg.isSimd128())
+ masm.storeUnalignedSimd128Float(reg, dump);
+#endif
+ else
+ MOZ_CRASH("Unexpected register type.");
+ }
+};
+
+static void
+StoreAllLiveRegs(MacroAssembler& masm, LiveRegisterSet liveRegs)
+{
+ // Store a copy of all live registers before performing the call.
+ // When we reach the OsiPoint, we can use this to check nothing
+ // modified them in the meantime.
+
+ // Load pointer to the JitActivation in a scratch register.
+ AllocatableGeneralRegisterSet allRegs(GeneralRegisterSet::All());
+ Register scratch = allRegs.takeAny();
+ masm.push(scratch);
+ masm.loadJitActivation(scratch);
+
+ Address checkRegs(scratch, JitActivation::offsetOfCheckRegs());
+ masm.add32(Imm32(1), checkRegs);
+
+ StoreOp op(masm);
+ HandleRegisterDump<StoreOp>(op, masm, liveRegs, scratch, allRegs.getAny());
+
+ masm.pop(scratch);
+}
+
+class VerifyOp
+{
+ MacroAssembler& masm;
+ Label* failure_;
+
+ public:
+ VerifyOp(MacroAssembler& masm, Label* failure)
+ : masm(masm), failure_(failure)
+ {}
+
+ void operator()(Register reg, Address dump) {
+ masm.branchPtr(Assembler::NotEqual, dump, reg, failure_);
+ }
+ void operator()(FloatRegister reg, Address dump) {
+ FloatRegister scratch;
+ if (reg.isDouble()) {
+ scratch = ScratchDoubleReg;
+ masm.loadDouble(dump, scratch);
+ masm.branchDouble(Assembler::DoubleNotEqual, scratch, reg, failure_);
+ } else if (reg.isSingle()) {
+ scratch = ScratchFloat32Reg;
+ masm.loadFloat32(dump, scratch);
+ masm.branchFloat(Assembler::DoubleNotEqual, scratch, reg, failure_);
+ }
+
+ // :TODO: (Bug 1133745) Add support to verify SIMD registers.
+ }
+};
+
+void
+CodeGeneratorShared::verifyOsiPointRegs(LSafepoint* safepoint)
+{
+ // Ensure the live registers stored by callVM did not change between
+ // the call and this OsiPoint. Try-catch relies on this invariant.
+
+ // Load pointer to the JitActivation in a scratch register.
+ AllocatableGeneralRegisterSet allRegs(GeneralRegisterSet::All());
+ Register scratch = allRegs.takeAny();
+ masm.push(scratch);
+ masm.loadJitActivation(scratch);
+
+ // If we should not check registers (because the instruction did not call
+ // into the VM, or a GC happened), we're done.
+ Label failure, done;
+ Address checkRegs(scratch, JitActivation::offsetOfCheckRegs());
+ masm.branch32(Assembler::Equal, checkRegs, Imm32(0), &done);
+
+ // Having more than one VM function call made in one visit function at
+ // runtime is a sec-ciritcal error, because if we conservatively assume that
+ // one of the function call can re-enter Ion, then the invalidation process
+ // will potentially add a call at a random location, by patching the code
+ // before the return address.
+ masm.branch32(Assembler::NotEqual, checkRegs, Imm32(1), &failure);
+
+ // Set checkRegs to 0, so that we don't try to verify registers after we
+ // return from this script to the caller.
+ masm.store32(Imm32(0), checkRegs);
+
+ // Ignore clobbered registers. Some instructions (like LValueToInt32) modify
+ // temps after calling into the VM. This is fine because no other
+ // instructions (including this OsiPoint) will depend on them. Also
+ // backtracking can also use the same register for an input and an output.
+ // These are marked as clobbered and shouldn't get checked.
+ LiveRegisterSet liveRegs;
+ liveRegs.set() = RegisterSet::Intersect(safepoint->liveRegs().set(),
+ RegisterSet::Not(safepoint->clobberedRegs().set()));
+
+ VerifyOp op(masm, &failure);
+ HandleRegisterDump<VerifyOp>(op, masm, liveRegs, scratch, allRegs.getAny());
+
+ masm.jump(&done);
+
+ // Do not profile the callWithABI that occurs below. This is to avoid a
+ // rare corner case that occurs when profiling interacts with itself:
+ //
+ // When slow profiling assertions are turned on, FunctionBoundary ops
+ // (which update the profiler pseudo-stack) may emit a callVM, which
+ // forces them to have an osi point associated with them. The
+ // FunctionBoundary for inline function entry is added to the caller's
+ // graph with a PC from the caller's code, but during codegen it modifies
+ // SPS instrumentation to add the callee as the current top-most script.
+ // When codegen gets to the OSIPoint, and the callWithABI below is
+ // emitted, the codegen thinks that the current frame is the callee, but
+ // the PC it's using from the OSIPoint refers to the caller. This causes
+ // the profiler instrumentation of the callWithABI below to ASSERT, since
+ // the script and pc are mismatched. To avoid this, we simply omit
+ // instrumentation for these callWithABIs.
+
+ // Any live register captured by a safepoint (other than temp registers)
+ // must remain unchanged between the call and the OsiPoint instruction.
+ masm.bind(&failure);
+ masm.assumeUnreachable("Modified registers between VM call and OsiPoint");
+
+ masm.bind(&done);
+ masm.pop(scratch);
+}
+
+bool
+CodeGeneratorShared::shouldVerifyOsiPointRegs(LSafepoint* safepoint)
+{
+ if (!checkOsiPointRegisters)
+ return false;
+
+ if (safepoint->liveRegs().emptyGeneral() && safepoint->liveRegs().emptyFloat())
+ return false; // No registers to check.
+
+ return true;
+}
+
+void
+CodeGeneratorShared::resetOsiPointRegs(LSafepoint* safepoint)
+{
+ if (!shouldVerifyOsiPointRegs(safepoint))
+ return;
+
+ // Set checkRegs to 0. If we perform a VM call, the instruction
+ // will set it to 1.
+ AllocatableGeneralRegisterSet allRegs(GeneralRegisterSet::All());
+ Register scratch = allRegs.takeAny();
+ masm.push(scratch);
+ masm.loadJitActivation(scratch);
+ Address checkRegs(scratch, JitActivation::offsetOfCheckRegs());
+ masm.store32(Imm32(0), checkRegs);
+ masm.pop(scratch);
+}
+#endif
+
+// Before doing any call to Cpp, you should ensure that volatile
+// registers are evicted by the register allocator.
+void
+CodeGeneratorShared::callVM(const VMFunction& fun, LInstruction* ins, const Register* dynStack)
+{
+ // If we're calling a function with an out parameter type of double, make
+ // sure we have an FPU.
+ MOZ_ASSERT_IF(fun.outParam == Type_Double, GetJitContext()->runtime->jitSupportsFloatingPoint());
+
+#ifdef DEBUG
+ if (ins->mirRaw()) {
+ MOZ_ASSERT(ins->mirRaw()->isInstruction());
+ MInstruction* mir = ins->mirRaw()->toInstruction();
+ MOZ_ASSERT_IF(mir->needsResumePoint(), mir->resumePoint());
+ }
+#endif
+
+ // Stack is:
+ // ... frame ...
+ // [args]
+#ifdef DEBUG
+ MOZ_ASSERT(pushedArgs_ == fun.explicitArgs);
+ pushedArgs_ = 0;
+#endif
+
+ // Get the wrapper of the VM function.
+ JitCode* wrapper = gen->jitRuntime()->getVMWrapper(fun);
+ if (!wrapper) {
+ masm.setOOM();
+ return;
+ }
+
+#ifdef CHECK_OSIPOINT_REGISTERS
+ if (shouldVerifyOsiPointRegs(ins->safepoint()))
+ StoreAllLiveRegs(masm, ins->safepoint()->liveRegs());
+#endif
+
+ // Push an exit frame descriptor. If |dynStack| is a valid pointer to a
+ // register, then its value is added to the value of the |framePushed()| to
+ // fill the frame descriptor.
+ if (dynStack) {
+ masm.addPtr(Imm32(masm.framePushed()), *dynStack);
+ masm.makeFrameDescriptor(*dynStack, JitFrame_IonJS, ExitFrameLayout::Size());
+ masm.Push(*dynStack); // descriptor
+ } else {
+ masm.pushStaticFrameDescriptor(JitFrame_IonJS, ExitFrameLayout::Size());
+ }
+
+ // Call the wrapper function. The wrapper is in charge to unwind the stack
+ // when returning from the call. Failures are handled with exceptions based
+ // on the return value of the C functions. To guard the outcome of the
+ // returned value, use another LIR instruction.
+ uint32_t callOffset = masm.callJit(wrapper);
+ markSafepointAt(callOffset, ins);
+
+ // Remove rest of the frame left on the stack. We remove the return address
+ // which is implicitly poped when returning.
+ int framePop = sizeof(ExitFrameLayout) - sizeof(void*);
+
+ // Pop arguments from framePushed.
+ masm.implicitPop(fun.explicitStackSlots() * sizeof(void*) + framePop);
+ // Stack is:
+ // ... frame ...
+}
+
+class OutOfLineTruncateSlow : public OutOfLineCodeBase<CodeGeneratorShared>
+{
+ FloatRegister src_;
+ Register dest_;
+ bool widenFloatToDouble_;
+
+ public:
+ OutOfLineTruncateSlow(FloatRegister src, Register dest, bool widenFloatToDouble = false)
+ : src_(src), dest_(dest), widenFloatToDouble_(widenFloatToDouble)
+ { }
+
+ void accept(CodeGeneratorShared* codegen) {
+ codegen->visitOutOfLineTruncateSlow(this);
+ }
+ FloatRegister src() const {
+ return src_;
+ }
+ Register dest() const {
+ return dest_;
+ }
+ bool widenFloatToDouble() const {
+ return widenFloatToDouble_;
+ }
+
+};
+
+OutOfLineCode*
+CodeGeneratorShared::oolTruncateDouble(FloatRegister src, Register dest, MInstruction* mir)
+{
+ OutOfLineTruncateSlow* ool = new(alloc()) OutOfLineTruncateSlow(src, dest);
+ addOutOfLineCode(ool, mir);
+ return ool;
+}
+
+void
+CodeGeneratorShared::emitTruncateDouble(FloatRegister src, Register dest, MInstruction* mir)
+{
+ OutOfLineCode* ool = oolTruncateDouble(src, dest, mir);
+
+ masm.branchTruncateDoubleMaybeModUint32(src, dest, ool->entry());
+ masm.bind(ool->rejoin());
+}
+
+void
+CodeGeneratorShared::emitTruncateFloat32(FloatRegister src, Register dest, MInstruction* mir)
+{
+ OutOfLineTruncateSlow* ool = new(alloc()) OutOfLineTruncateSlow(src, dest, true);
+ addOutOfLineCode(ool, mir);
+
+ masm.branchTruncateFloat32MaybeModUint32(src, dest, ool->entry());
+ masm.bind(ool->rejoin());
+}
+
+void
+CodeGeneratorShared::visitOutOfLineTruncateSlow(OutOfLineTruncateSlow* ool)
+{
+ FloatRegister src = ool->src();
+ Register dest = ool->dest();
+
+ saveVolatile(dest);
+ masm.outOfLineTruncateSlow(src, dest, ool->widenFloatToDouble(), gen->compilingWasm());
+ restoreVolatile(dest);
+
+ masm.jump(ool->rejoin());
+}
+
+bool
+CodeGeneratorShared::omitOverRecursedCheck() const
+{
+ // If the current function makes no calls (which means it isn't recursive)
+ // and it uses only a small amount of stack space, it doesn't need a
+ // stack overflow check. Note that the actual number here is somewhat
+ // arbitrary, and codegen actually uses small bounded amounts of
+ // additional stack space in some cases too.
+ return frameSize() < 64 && !gen->performsCall();
+}
+
+void
+CodeGeneratorShared::emitWasmCallBase(LWasmCallBase* ins)
+{
+ MWasmCall* mir = ins->mir();
+
+ if (mir->spIncrement())
+ masm.freeStack(mir->spIncrement());
+
+ MOZ_ASSERT((sizeof(wasm::Frame) + masm.framePushed()) % WasmStackAlignment == 0);
+ static_assert(WasmStackAlignment >= ABIStackAlignment &&
+ WasmStackAlignment % ABIStackAlignment == 0,
+ "The wasm stack alignment should subsume the ABI-required alignment");
+
+#ifdef DEBUG
+ Label ok;
+ masm.branchTestStackPtr(Assembler::Zero, Imm32(WasmStackAlignment - 1), &ok);
+ masm.breakpoint();
+ masm.bind(&ok);
+#endif
+
+ // Save the caller's TLS register in a reserved stack slot (below the
+ // call's stack arguments) for retrieval after the call.
+ if (mir->saveTls())
+ masm.storePtr(WasmTlsReg, Address(masm.getStackPointer(), mir->tlsStackOffset()));
+
+ const wasm::CallSiteDesc& desc = mir->desc();
+ const wasm::CalleeDesc& callee = mir->callee();
+ switch (callee.which()) {
+ case wasm::CalleeDesc::Func:
+ masm.call(desc, callee.funcIndex());
+ break;
+ case wasm::CalleeDesc::Import:
+ masm.wasmCallImport(desc, callee);
+ break;
+ case wasm::CalleeDesc::WasmTable:
+ case wasm::CalleeDesc::AsmJSTable:
+ masm.wasmCallIndirect(desc, callee);
+ break;
+ case wasm::CalleeDesc::Builtin:
+ masm.call(callee.builtin());
+ break;
+ case wasm::CalleeDesc::BuiltinInstanceMethod:
+ masm.wasmCallBuiltinInstanceMethod(mir->instanceArg(), callee.builtin());
+ break;
+ }
+
+ // After return, restore the caller's TLS and pinned registers.
+ if (mir->saveTls()) {
+ masm.loadPtr(Address(masm.getStackPointer(), mir->tlsStackOffset()), WasmTlsReg);
+ masm.loadWasmPinnedRegsFromTls();
+ }
+
+ if (mir->spIncrement())
+ masm.reserveStack(mir->spIncrement());
+}
+
+void
+CodeGeneratorShared::emitPreBarrier(Register base, const LAllocation* index, int32_t offsetAdjustment)
+{
+ if (index->isConstant()) {
+ Address address(base, ToInt32(index) * sizeof(Value) + offsetAdjustment);
+ masm.patchableCallPreBarrier(address, MIRType::Value);
+ } else {
+ BaseIndex address(base, ToRegister(index), TimesEight, offsetAdjustment);
+ masm.patchableCallPreBarrier(address, MIRType::Value);
+ }
+}
+
+void
+CodeGeneratorShared::emitPreBarrier(Address address)
+{
+ masm.patchableCallPreBarrier(address, MIRType::Value);
+}
+
+Label*
+CodeGeneratorShared::labelForBackedgeWithImplicitCheck(MBasicBlock* mir)
+{
+ // If this is a loop backedge to a loop header with an implicit interrupt
+ // check, use a patchable jump. Skip this search if compiling without a
+ // script for wasm, as there will be no interrupt check instruction.
+ // Due to critical edge unsplitting there may no longer be unique loop
+ // backedges, so just look for any edge going to an earlier block in RPO.
+ if (!gen->compilingWasm() && mir->isLoopHeader() && mir->id() <= current->mir()->id()) {
+ for (LInstructionIterator iter = mir->lir()->begin(); iter != mir->lir()->end(); iter++) {
+ if (iter->isMoveGroup()) {
+ // Continue searching for an interrupt check.
+ } else {
+ // The interrupt check should be the first instruction in the
+ // loop header other than move groups.
+ MOZ_ASSERT(iter->isInterruptCheck());
+ if (iter->toInterruptCheck()->implicit())
+ return iter->toInterruptCheck()->oolEntry();
+ return nullptr;
+ }
+ }
+ }
+
+ return nullptr;
+}
+
+void
+CodeGeneratorShared::jumpToBlock(MBasicBlock* mir)
+{
+ // Skip past trivial blocks.
+ mir = skipTrivialBlocks(mir);
+
+ // No jump necessary if we can fall through to the next block.
+ if (isNextBlock(mir->lir()))
+ return;
+
+ if (Label* oolEntry = labelForBackedgeWithImplicitCheck(mir)) {
+ // Note: the backedge is initially a jump to the next instruction.
+ // It will be patched to the target block's label during link().
+ RepatchLabel rejoin;
+ CodeOffsetJump backedge = masm.backedgeJump(&rejoin, mir->lir()->label());
+ masm.bind(&rejoin);
+
+ masm.propagateOOM(patchableBackedges_.append(PatchableBackedgeInfo(backedge, mir->lir()->label(), oolEntry)));
+ } else {
+ masm.jump(mir->lir()->label());
+ }
+}
+
+Label*
+CodeGeneratorShared::getJumpLabelForBranch(MBasicBlock* block)
+{
+ // Skip past trivial blocks.
+ block = skipTrivialBlocks(block);
+
+ if (!labelForBackedgeWithImplicitCheck(block))
+ return block->lir()->label();
+
+ // We need to use a patchable jump for this backedge, but want to treat
+ // this as a normal label target to simplify codegen. Efficiency isn't so
+ // important here as these tests are extremely unlikely to be used in loop
+ // backedges, so emit inline code for the patchable jump. Heap allocating
+ // the label allows it to be used by out of line blocks.
+ Label* res = alloc().lifoAlloc()->newInfallible<Label>();
+ Label after;
+ masm.jump(&after);
+ masm.bind(res);
+ jumpToBlock(block);
+ masm.bind(&after);
+ return res;
+}
+
+// This function is not used for MIPS/MIPS64. MIPS has branchToBlock.
+#if !defined(JS_CODEGEN_MIPS32) && !defined(JS_CODEGEN_MIPS64)
+void
+CodeGeneratorShared::jumpToBlock(MBasicBlock* mir, Assembler::Condition cond)
+{
+ // Skip past trivial blocks.
+ mir = skipTrivialBlocks(mir);
+
+ if (Label* oolEntry = labelForBackedgeWithImplicitCheck(mir)) {
+ // Note: the backedge is initially a jump to the next instruction.
+ // It will be patched to the target block's label during link().
+ RepatchLabel rejoin;
+ CodeOffsetJump backedge = masm.jumpWithPatch(&rejoin, cond, mir->lir()->label());
+ masm.bind(&rejoin);
+
+ masm.propagateOOM(patchableBackedges_.append(PatchableBackedgeInfo(backedge, mir->lir()->label(), oolEntry)));
+ } else {
+ masm.j(cond, mir->lir()->label());
+ }
+}
+#endif
+
+MOZ_MUST_USE bool
+CodeGeneratorShared::addCacheLocations(const CacheLocationList& locs, size_t* numLocs,
+ size_t* curIndex)
+{
+ size_t firstIndex = runtimeData_.length();
+ size_t numLocations = 0;
+ for (CacheLocationList::iterator iter = locs.begin(); iter != locs.end(); iter++) {
+ // allocateData() ensures that sizeof(CacheLocation) is word-aligned.
+ // If this changes, we will need to pad to ensure alignment.
+ if (!allocateData(sizeof(CacheLocation), curIndex))
+ return false;
+ new (&runtimeData_[*curIndex]) CacheLocation(iter->pc, iter->script);
+ numLocations++;
+ }
+ MOZ_ASSERT(numLocations != 0);
+ *numLocs = numLocations;
+ *curIndex = firstIndex;
+ return true;
+}
+
+ReciprocalMulConstants
+CodeGeneratorShared::computeDivisionConstants(uint32_t d, int maxLog) {
+ MOZ_ASSERT(maxLog >= 2 && maxLog <= 32);
+ // In what follows, 0 < d < 2^maxLog and d is not a power of 2.
+ MOZ_ASSERT(d < (uint64_t(1) << maxLog) && (d & (d - 1)) != 0);
+
+ // Speeding up division by non power-of-2 constants is possible by
+ // calculating, during compilation, a value M such that high-order
+ // bits of M*n correspond to the result of the division of n by d.
+ // No value of M can serve this purpose for arbitrarily big values
+ // of n but, for optimizing integer division, we're just concerned
+ // with values of n whose absolute value is bounded (by fitting in
+ // an integer type, say). With this in mind, we'll find a constant
+ // M as above that works for -2^maxLog <= n < 2^maxLog; maxLog can
+ // then be 31 for signed division or 32 for unsigned division.
+ //
+ // The original presentation of this technique appears in Hacker's
+ // Delight, a book by Henry S. Warren, Jr.. A proof of correctness
+ // for our version follows; we'll denote maxLog by L in the proof,
+ // for conciseness.
+ //
+ // Formally, for |d| < 2^L, we'll compute two magic values M and s
+ // in the ranges 0 <= M < 2^(L+1) and 0 <= s <= L such that
+ // (M * n) >> (32 + s) = floor(n/d) if 0 <= n < 2^L
+ // (M * n) >> (32 + s) = ceil(n/d) - 1 if -2^L <= n < 0.
+ //
+ // Define p = 32 + s, M = ceil(2^p/d), and assume that s satisfies
+ // M - 2^p/d <= 2^(p-L)/d. (1)
+ // (Observe that p = CeilLog32(d) + L satisfies this, as the right
+ // side of (1) is at least one in this case). Then,
+ //
+ // a) If p <= CeilLog32(d) + L, then M < 2^(L+1) - 1.
+ // Proof: Indeed, M is monotone in p and, for p equal to the above
+ // value, the bounds 2^L > d >= 2^(p-L-1) + 1 readily imply that
+ // 2^p / d < 2^p/(d - 1) * (d - 1)/d
+ // <= 2^(L+1) * (1 - 1/d) < 2^(L+1) - 2.
+ // The claim follows by applying the ceiling function.
+ //
+ // b) For any 0 <= n < 2^L, floor(Mn/2^p) = floor(n/d).
+ // Proof: Put x = floor(Mn/2^p); it's the unique integer for which
+ // Mn/2^p - 1 < x <= Mn/2^p. (2)
+ // Using M >= 2^p/d on the LHS and (1) on the RHS, we get
+ // n/d - 1 < x <= n/d + n/(2^L d) < n/d + 1/d.
+ // Since x is an integer, it's not in the interval (n/d, (n+1)/d),
+ // and so n/d - 1 < x <= n/d, which implies x = floor(n/d).
+ //
+ // c) For any -2^L <= n < 0, floor(Mn/2^p) + 1 = ceil(n/d).
+ // Proof: The proof is similar. Equation (2) holds as above. Using
+ // M > 2^p/d (d isn't a power of 2) on the RHS and (1) on the LHS,
+ // n/d + n/(2^L d) - 1 < x < n/d.
+ // Using n >= -2^L and summing 1,
+ // n/d - 1/d < x + 1 < n/d + 1.
+ // Since x + 1 is an integer, this implies n/d <= x + 1 < n/d + 1.
+ // In other words, x + 1 = ceil(n/d).
+ //
+ // Condition (1) isn't necessary for the existence of M and s with
+ // the properties above. Hacker's Delight provides a slightly less
+ // restrictive condition when d >= 196611, at the cost of a 3-page
+ // proof of correctness, for the case L = 31.
+ //
+ // Note that, since d*M - 2^p = d - (2^p)%d, (1) can be written as
+ // 2^(p-L) >= d - (2^p)%d.
+ // In order to avoid overflow in the (2^p) % d calculation, we can
+ // compute it as (2^p-1) % d + 1, where 2^p-1 can then be computed
+ // without overflow as UINT64_MAX >> (64-p).
+
+ // We now compute the least p >= 32 with the property above...
+ int32_t p = 32;
+ while ((uint64_t(1) << (p-maxLog)) + (UINT64_MAX >> (64-p)) % d + 1 < d)
+ p++;
+
+ // ...and the corresponding M. For either the signed (L=31) or the
+ // unsigned (L=32) case, this value can be too large (cf. item a).
+ // Codegen can still multiply by M by multiplying by (M - 2^L) and
+ // adjusting the value afterwards, if this is the case.
+ ReciprocalMulConstants rmc;
+ rmc.multiplier = (UINT64_MAX >> (64-p))/d + 1;
+ rmc.shiftAmount = p - 32;
+
+ return rmc;
+}
+
+#ifdef JS_TRACE_LOGGING
+
+void
+CodeGeneratorShared::emitTracelogScript(bool isStart)
+{
+ if (!TraceLogTextIdEnabled(TraceLogger_Scripts))
+ return;
+
+ Label done;
+
+ AllocatableRegisterSet regs(RegisterSet::Volatile());
+ Register logger = regs.takeAnyGeneral();
+ Register script = regs.takeAnyGeneral();
+
+ masm.Push(logger);
+
+ CodeOffset patchLogger = masm.movWithPatch(ImmPtr(nullptr), logger);
+ masm.propagateOOM(patchableTraceLoggers_.append(patchLogger));
+
+ masm.branchTest32(Assembler::Zero, logger, logger, &done);
+
+ Address enabledAddress(logger, TraceLoggerThread::offsetOfEnabled());
+ masm.branch32(Assembler::Equal, enabledAddress, Imm32(0), &done);
+
+ masm.Push(script);
+
+ CodeOffset patchScript = masm.movWithPatch(ImmWord(0), script);
+ masm.propagateOOM(patchableTLScripts_.append(patchScript));
+
+ if (isStart)
+ masm.tracelogStartId(logger, script);
+ else
+ masm.tracelogStopId(logger, script);
+
+ masm.Pop(script);
+
+ masm.bind(&done);
+
+ masm.Pop(logger);
+}
+
+void
+CodeGeneratorShared::emitTracelogTree(bool isStart, uint32_t textId)
+{
+ if (!TraceLogTextIdEnabled(textId))
+ return;
+
+ Label done;
+ AllocatableRegisterSet regs(RegisterSet::Volatile());
+ Register logger = regs.takeAnyGeneral();
+
+ masm.Push(logger);
+
+ CodeOffset patchLocation = masm.movWithPatch(ImmPtr(nullptr), logger);
+ masm.propagateOOM(patchableTraceLoggers_.append(patchLocation));
+
+ masm.branchTest32(Assembler::Zero, logger, logger, &done);
+
+ Address enabledAddress(logger, TraceLoggerThread::offsetOfEnabled());
+ masm.branch32(Assembler::Equal, enabledAddress, Imm32(0), &done);
+
+ if (isStart)
+ masm.tracelogStartId(logger, textId);
+ else
+ masm.tracelogStopId(logger, textId);
+
+ masm.bind(&done);
+
+ masm.Pop(logger);
+}
+
+void
+CodeGeneratorShared::emitTracelogTree(bool isStart, const char* text,
+ TraceLoggerTextId enabledTextId)
+{
+ if (!TraceLogTextIdEnabled(enabledTextId))
+ return;
+
+ Label done;
+
+ AllocatableRegisterSet regs(RegisterSet::Volatile());
+ Register loggerReg = regs.takeAnyGeneral();
+ Register eventReg = regs.takeAnyGeneral();
+
+ masm.Push(loggerReg);
+
+ CodeOffset patchLocation = masm.movWithPatch(ImmPtr(nullptr), loggerReg);
+ masm.propagateOOM(patchableTraceLoggers_.append(patchLocation));
+
+ masm.branchTest32(Assembler::Zero, loggerReg, loggerReg, &done);
+
+ Address enabledAddress(loggerReg, TraceLoggerThread::offsetOfEnabled());
+ masm.branch32(Assembler::Equal, enabledAddress, Imm32(0), &done);
+
+ masm.Push(eventReg);
+
+ PatchableTLEvent patchEvent(masm.movWithPatch(ImmWord(0), eventReg), text);
+ masm.propagateOOM(patchableTLEvents_.append(Move(patchEvent)));
+
+ if (isStart)
+ masm.tracelogStartId(loggerReg, eventReg);
+ else
+ masm.tracelogStopId(loggerReg, eventReg);
+
+ masm.Pop(eventReg);
+
+ masm.bind(&done);
+
+ masm.Pop(loggerReg);
+}
+#endif
+
+} // namespace jit
+} // namespace js