summaryrefslogtreecommitdiffstats
path: root/js/src/jit/arm64/Assembler-arm64.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/jit/arm64/Assembler-arm64.cpp')
-rw-r--r--js/src/jit/arm64/Assembler-arm64.cpp670
1 files changed, 670 insertions, 0 deletions
diff --git a/js/src/jit/arm64/Assembler-arm64.cpp b/js/src/jit/arm64/Assembler-arm64.cpp
new file mode 100644
index 000000000..3d032cebd
--- /dev/null
+++ b/js/src/jit/arm64/Assembler-arm64.cpp
@@ -0,0 +1,670 @@
+/* -*- 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/arm64/Assembler-arm64.h"
+
+#include "mozilla/DebugOnly.h"
+#include "mozilla/MathAlgorithms.h"
+
+#include "jscompartment.h"
+#include "jsutil.h"
+
+#include "gc/Marking.h"
+
+#include "jit/arm64/Architecture-arm64.h"
+#include "jit/arm64/MacroAssembler-arm64.h"
+#include "jit/ExecutableAllocator.h"
+#include "jit/JitCompartment.h"
+
+#include "gc/StoreBuffer-inl.h"
+
+using namespace js;
+using namespace js::jit;
+
+using mozilla::CountLeadingZeroes32;
+using mozilla::DebugOnly;
+
+// Note this is used for inter-wasm calls and may pass arguments and results
+// in floating point registers even if the system ABI does not.
+
+ABIArg
+ABIArgGenerator::next(MIRType type)
+{
+ switch (type) {
+ case MIRType::Int32:
+ case MIRType::Pointer:
+ if (intRegIndex_ == NumIntArgRegs) {
+ current_ = ABIArg(stackOffset_);
+ stackOffset_ += sizeof(uintptr_t);
+ break;
+ }
+ current_ = ABIArg(Register::FromCode(intRegIndex_));
+ intRegIndex_++;
+ break;
+
+ case MIRType::Float32:
+ case MIRType::Double:
+ if (floatRegIndex_ == NumFloatArgRegs) {
+ current_ = ABIArg(stackOffset_);
+ stackOffset_ += sizeof(double);
+ break;
+ }
+ current_ = ABIArg(FloatRegister(floatRegIndex_,
+ type == MIRType::Double ? FloatRegisters::Double
+ : FloatRegisters::Single));
+ floatRegIndex_++;
+ break;
+
+ default:
+ MOZ_CRASH("Unexpected argument type");
+ }
+ return current_;
+}
+
+namespace js {
+namespace jit {
+
+void
+Assembler::finish()
+{
+ armbuffer_.flushPool();
+
+ // The extended jump table is part of the code buffer.
+ ExtendedJumpTable_ = emitExtendedJumpTable();
+ Assembler::FinalizeCode();
+
+ // The jump relocation table starts with a fixed-width integer pointing
+ // to the start of the extended jump table.
+ // Space for this integer is allocated by Assembler::addJumpRelocation()
+ // before writing the first entry.
+ // Don't touch memory if we saw an OOM error.
+ if (jumpRelocations_.length() && !oom()) {
+ MOZ_ASSERT(jumpRelocations_.length() >= sizeof(uint32_t));
+ *(uint32_t*)jumpRelocations_.buffer() = ExtendedJumpTable_.getOffset();
+ }
+}
+
+BufferOffset
+Assembler::emitExtendedJumpTable()
+{
+ if (!pendingJumps_.length() || oom())
+ return BufferOffset();
+
+ armbuffer_.flushPool();
+ armbuffer_.align(SizeOfJumpTableEntry);
+
+ BufferOffset tableOffset = armbuffer_.nextOffset();
+
+ for (size_t i = 0; i < pendingJumps_.length(); i++) {
+ // Each JumpTableEntry is of the form:
+ // LDR ip0 [PC, 8]
+ // BR ip0
+ // [Patchable 8-byte constant low bits]
+ // [Patchable 8-byte constant high bits]
+ DebugOnly<size_t> preOffset = size_t(armbuffer_.nextOffset().getOffset());
+
+ ldr(vixl::ip0, ptrdiff_t(8 / vixl::kInstructionSize));
+ br(vixl::ip0);
+
+ DebugOnly<size_t> prePointer = size_t(armbuffer_.nextOffset().getOffset());
+ MOZ_ASSERT_IF(!oom(), prePointer - preOffset == OffsetOfJumpTableEntryPointer);
+
+ brk(0x0);
+ brk(0x0);
+
+ DebugOnly<size_t> postOffset = size_t(armbuffer_.nextOffset().getOffset());
+
+ MOZ_ASSERT_IF(!oom(), postOffset - preOffset == SizeOfJumpTableEntry);
+ }
+
+ if (oom())
+ return BufferOffset();
+
+ return tableOffset;
+}
+
+void
+Assembler::executableCopy(uint8_t* buffer)
+{
+ // Copy the code and all constant pools into the output buffer.
+ armbuffer_.executableCopy(buffer);
+
+ // Patch any relative jumps that target code outside the buffer.
+ // The extended jump table may be used for distant jumps.
+ for (size_t i = 0; i < pendingJumps_.length(); i++) {
+ RelativePatch& rp = pendingJumps_[i];
+
+ if (!rp.target) {
+ // The patch target is nullptr for jumps that have been linked to
+ // a label within the same code block, but may be repatched later
+ // to jump to a different code block.
+ continue;
+ }
+
+ Instruction* target = (Instruction*)rp.target;
+ Instruction* branch = (Instruction*)(buffer + rp.offset.getOffset());
+ JumpTableEntry* extendedJumpTable =
+ reinterpret_cast<JumpTableEntry*>(buffer + ExtendedJumpTable_.getOffset());
+ if (branch->BranchType() != vixl::UnknownBranchType) {
+ if (branch->IsTargetReachable(target)) {
+ branch->SetImmPCOffsetTarget(target);
+ } else {
+ JumpTableEntry* entry = &extendedJumpTable[i];
+ branch->SetImmPCOffsetTarget(entry->getLdr());
+ entry->data = target;
+ }
+ } else {
+ // Currently a two-instruction call, it should be possible to optimize this
+ // into a single instruction call + nop in some instances, but this will work.
+ }
+ }
+}
+
+BufferOffset
+Assembler::immPool(ARMRegister dest, uint8_t* value, vixl::LoadLiteralOp op, ARMBuffer::PoolEntry* pe)
+{
+ uint32_t inst = op | Rt(dest);
+ const size_t numInst = 1;
+ const unsigned sizeOfPoolEntryInBytes = 4;
+ const unsigned numPoolEntries = sizeof(value) / sizeOfPoolEntryInBytes;
+ return allocEntry(numInst, numPoolEntries, (uint8_t*)&inst, value, pe);
+}
+
+BufferOffset
+Assembler::immPool64(ARMRegister dest, uint64_t value, ARMBuffer::PoolEntry* pe)
+{
+ return immPool(dest, (uint8_t*)&value, vixl::LDR_x_lit, pe);
+}
+
+BufferOffset
+Assembler::immPool64Branch(RepatchLabel* label, ARMBuffer::PoolEntry* pe, Condition c)
+{
+ MOZ_CRASH("immPool64Branch");
+}
+
+BufferOffset
+Assembler::fImmPool(ARMFPRegister dest, uint8_t* value, vixl::LoadLiteralOp op)
+{
+ uint32_t inst = op | Rt(dest);
+ const size_t numInst = 1;
+ const unsigned sizeOfPoolEntryInBits = 32;
+ const unsigned numPoolEntries = dest.size() / sizeOfPoolEntryInBits;
+ return allocEntry(numInst, numPoolEntries, (uint8_t*)&inst, value);
+}
+
+BufferOffset
+Assembler::fImmPool64(ARMFPRegister dest, double value)
+{
+ return fImmPool(dest, (uint8_t*)&value, vixl::LDR_d_lit);
+}
+BufferOffset
+Assembler::fImmPool32(ARMFPRegister dest, float value)
+{
+ return fImmPool(dest, (uint8_t*)&value, vixl::LDR_s_lit);
+}
+
+void
+Assembler::bind(Label* label, BufferOffset targetOffset)
+{
+ // Nothing has seen the label yet: just mark the location.
+ // If we've run out of memory, don't attempt to modify the buffer which may
+ // not be there. Just mark the label as bound to the (possibly bogus)
+ // targetOffset.
+ if (!label->used() || oom()) {
+ label->bind(targetOffset.getOffset());
+ return;
+ }
+
+ // Get the most recent instruction that used the label, as stored in the label.
+ // This instruction is the head of an implicit linked list of label uses.
+ BufferOffset branchOffset(label);
+
+ while (branchOffset.assigned()) {
+ // Before overwriting the offset in this instruction, get the offset of
+ // the next link in the implicit branch list.
+ BufferOffset nextOffset = NextLink(branchOffset);
+
+ // Linking against the actual (Instruction*) would be invalid,
+ // since that Instruction could be anywhere in memory.
+ // Instead, just link against the correct relative offset, assuming
+ // no constant pools, which will be taken into consideration
+ // during finalization.
+ ptrdiff_t relativeByteOffset = targetOffset.getOffset() - branchOffset.getOffset();
+ Instruction* link = getInstructionAt(branchOffset);
+
+ // This branch may still be registered for callbacks. Stop tracking it.
+ vixl::ImmBranchType branchType = link->BranchType();
+ vixl::ImmBranchRangeType branchRange = Instruction::ImmBranchTypeToRange(branchType);
+ if (branchRange < vixl::NumShortBranchRangeTypes) {
+ BufferOffset deadline(branchOffset.getOffset() +
+ Instruction::ImmBranchMaxForwardOffset(branchRange));
+ armbuffer_.unregisterBranchDeadline(branchRange, deadline);
+ }
+
+ // Is link able to reach the label?
+ if (link->IsPCRelAddressing() || link->IsTargetReachable(link + relativeByteOffset)) {
+ // Write a new relative offset into the instruction.
+ link->SetImmPCOffsetTarget(link + relativeByteOffset);
+ } else {
+ // This is a short-range branch, and it can't reach the label directly.
+ // Verify that it branches to a veneer: an unconditional branch.
+ MOZ_ASSERT(getInstructionAt(nextOffset)->BranchType() == vixl::UncondBranchType);
+ }
+
+ branchOffset = nextOffset;
+ }
+
+ // Bind the label, so that future uses may encode the offset immediately.
+ label->bind(targetOffset.getOffset());
+}
+
+void
+Assembler::bind(RepatchLabel* label)
+{
+ // Nothing has seen the label yet: just mark the location.
+ // If we've run out of memory, don't attempt to modify the buffer which may
+ // not be there. Just mark the label as bound to nextOffset().
+ if (!label->used() || oom()) {
+ label->bind(nextOffset().getOffset());
+ return;
+ }
+ int branchOffset = label->offset();
+ Instruction* inst = getInstructionAt(BufferOffset(branchOffset));
+ inst->SetImmPCOffsetTarget(inst + nextOffset().getOffset() - branchOffset);
+}
+
+void
+Assembler::trace(JSTracer* trc)
+{
+ for (size_t i = 0; i < pendingJumps_.length(); i++) {
+ RelativePatch& rp = pendingJumps_[i];
+ if (rp.kind == Relocation::JITCODE) {
+ JitCode* code = JitCode::FromExecutable((uint8_t*)rp.target);
+ TraceManuallyBarrieredEdge(trc, &code, "masmrel32");
+ MOZ_ASSERT(code == JitCode::FromExecutable((uint8_t*)rp.target));
+ }
+ }
+
+ // TODO: Trace.
+#if 0
+ if (tmpDataRelocations_.length())
+ ::TraceDataRelocations(trc, &armbuffer_, &tmpDataRelocations_);
+#endif
+}
+
+void
+Assembler::addJumpRelocation(BufferOffset src, Relocation::Kind reloc)
+{
+ // Only JITCODE relocations are patchable at runtime.
+ MOZ_ASSERT(reloc == Relocation::JITCODE);
+
+ // The jump relocation table starts with a fixed-width integer pointing
+ // to the start of the extended jump table. But, we don't know the
+ // actual extended jump table offset yet, so write a 0 which we'll
+ // patch later in Assembler::finish().
+ if (!jumpRelocations_.length())
+ jumpRelocations_.writeFixedUint32_t(0);
+
+ // Each entry in the table is an (offset, extendedTableIndex) pair.
+ jumpRelocations_.writeUnsigned(src.getOffset());
+ jumpRelocations_.writeUnsigned(pendingJumps_.length());
+}
+
+void
+Assembler::addPendingJump(BufferOffset src, ImmPtr target, Relocation::Kind reloc)
+{
+ MOZ_ASSERT(target.value != nullptr);
+
+ if (reloc == Relocation::JITCODE)
+ addJumpRelocation(src, reloc);
+
+ // This jump is not patchable at runtime. Extended jump table entry requirements
+ // cannot be known until finalization, so to be safe, give each jump and entry.
+ // This also causes GC tracing of the target.
+ enoughMemory_ &= pendingJumps_.append(RelativePatch(src, target.value, reloc));
+}
+
+size_t
+Assembler::addPatchableJump(BufferOffset src, Relocation::Kind reloc)
+{
+ MOZ_CRASH("TODO: This is currently unused (and untested)");
+ if (reloc == Relocation::JITCODE)
+ addJumpRelocation(src, reloc);
+
+ size_t extendedTableIndex = pendingJumps_.length();
+ enoughMemory_ &= pendingJumps_.append(RelativePatch(src, nullptr, reloc));
+ return extendedTableIndex;
+}
+
+void
+PatchJump(CodeLocationJump& jump_, CodeLocationLabel label, ReprotectCode reprotect)
+{
+ MOZ_CRASH("PatchJump");
+}
+
+void
+Assembler::PatchDataWithValueCheck(CodeLocationLabel label, PatchedImmPtr newValue,
+ PatchedImmPtr expected)
+{
+ Instruction* i = (Instruction*)label.raw();
+ void** pValue = i->LiteralAddress<void**>();
+ MOZ_ASSERT(*pValue == expected.value);
+ *pValue = newValue.value;
+}
+
+void
+Assembler::PatchDataWithValueCheck(CodeLocationLabel label, ImmPtr newValue, ImmPtr expected)
+{
+ PatchDataWithValueCheck(label, PatchedImmPtr(newValue.value), PatchedImmPtr(expected.value));
+}
+
+void
+Assembler::ToggleToJmp(CodeLocationLabel inst_)
+{
+ Instruction* i = (Instruction*)inst_.raw();
+ MOZ_ASSERT(i->IsAddSubImmediate());
+
+ // Refer to instruction layout in ToggleToCmp().
+ int imm19 = (int)i->Bits(23, 5);
+ MOZ_ASSERT(vixl::is_int19(imm19));
+
+ b(i, imm19, Always);
+}
+
+void
+Assembler::ToggleToCmp(CodeLocationLabel inst_)
+{
+ Instruction* i = (Instruction*)inst_.raw();
+ MOZ_ASSERT(i->IsCondB());
+
+ int imm19 = i->ImmCondBranch();
+ // bit 23 is reserved, and the simulator throws an assertion when this happens
+ // It'll be messy to decode, but we can steal bit 30 or bit 31.
+ MOZ_ASSERT(vixl::is_int18(imm19));
+
+ // 31 - 64-bit if set, 32-bit if unset. (OK!)
+ // 30 - sub if set, add if unset. (OK!)
+ // 29 - SetFlagsBit. Must be set.
+ // 22:23 - ShiftAddSub. (OK!)
+ // 10:21 - ImmAddSub. (OK!)
+ // 5:9 - First source register (Rn). (OK!)
+ // 0:4 - Destination Register. Must be xzr.
+
+ // From the above, there is a safe 19-bit contiguous region from 5:23.
+ Emit(i, vixl::ThirtyTwoBits | vixl::AddSubImmediateFixed | vixl::SUB | Flags(vixl::SetFlags) |
+ Rd(vixl::xzr) | (imm19 << vixl::Rn_offset));
+}
+
+void
+Assembler::ToggleCall(CodeLocationLabel inst_, bool enabled)
+{
+ const Instruction* first = reinterpret_cast<Instruction*>(inst_.raw());
+ Instruction* load;
+ Instruction* call;
+
+ // There might be a constant pool at the very first instruction.
+ first = first->skipPool();
+
+ // Skip the stack pointer restore instruction.
+ if (first->IsStackPtrSync())
+ first = first->InstructionAtOffset(vixl::kInstructionSize)->skipPool();
+
+ load = const_cast<Instruction*>(first);
+
+ // The call instruction follows the load, but there may be an injected
+ // constant pool.
+ call = const_cast<Instruction*>(load->InstructionAtOffset(vixl::kInstructionSize)->skipPool());
+
+ if (call->IsBLR() == enabled)
+ return;
+
+ if (call->IsBLR()) {
+ // If the second instruction is blr(), then wehave:
+ // ldr x17, [pc, offset]
+ // blr x17
+ MOZ_ASSERT(load->IsLDR());
+ // We want to transform this to:
+ // adr xzr, [pc, offset]
+ // nop
+ int32_t offset = load->ImmLLiteral();
+ adr(load, xzr, int32_t(offset));
+ nop(call);
+ } else {
+ // We have:
+ // adr xzr, [pc, offset] (or ldr x17, [pc, offset])
+ // nop
+ MOZ_ASSERT(load->IsADR() || load->IsLDR());
+ MOZ_ASSERT(call->IsNOP());
+ // Transform this to:
+ // ldr x17, [pc, offset]
+ // blr x17
+ int32_t offset = (int)load->ImmPCRawOffset();
+ MOZ_ASSERT(vixl::is_int19(offset));
+ ldr(load, ScratchReg2_64, int32_t(offset));
+ blr(call, ScratchReg2_64);
+ }
+}
+
+class RelocationIterator
+{
+ CompactBufferReader reader_;
+ uint32_t tableStart_;
+ uint32_t offset_;
+ uint32_t extOffset_;
+
+ public:
+ explicit RelocationIterator(CompactBufferReader& reader)
+ : reader_(reader)
+ {
+ // The first uint32_t stores the extended table offset.
+ tableStart_ = reader_.readFixedUint32_t();
+ }
+
+ bool read() {
+ if (!reader_.more())
+ return false;
+ offset_ = reader_.readUnsigned();
+ extOffset_ = reader_.readUnsigned();
+ return true;
+ }
+
+ uint32_t offset() const {
+ return offset_;
+ }
+ uint32_t extendedOffset() const {
+ return extOffset_;
+ }
+};
+
+static JitCode*
+CodeFromJump(JitCode* code, uint8_t* jump)
+{
+ const Instruction* inst = (const Instruction*)jump;
+ uint8_t* target;
+
+ // We're expecting a call created by MacroAssembler::call(JitCode*).
+ // It looks like:
+ //
+ // ldr scratch, [pc, offset]
+ // blr scratch
+ //
+ // If the call has been toggled by ToggleCall(), it looks like:
+ //
+ // adr xzr, [pc, offset]
+ // nop
+ //
+ // There might be a constant pool at the very first instruction.
+ // See also ToggleCall().
+ inst = inst->skipPool();
+
+ // Skip the stack pointer restore instruction.
+ if (inst->IsStackPtrSync())
+ inst = inst->InstructionAtOffset(vixl::kInstructionSize)->skipPool();
+
+ if (inst->BranchType() != vixl::UnknownBranchType) {
+ // This is an immediate branch.
+ target = (uint8_t*)inst->ImmPCOffsetTarget();
+ } else if (inst->IsLDR()) {
+ // This is an ldr+blr call that is enabled. See ToggleCall().
+ mozilla::DebugOnly<const Instruction*> nextInst =
+ inst->InstructionAtOffset(vixl::kInstructionSize)->skipPool();
+ MOZ_ASSERT(nextInst->IsNOP() || nextInst->IsBLR());
+ target = (uint8_t*)inst->Literal64();
+ } else if (inst->IsADR()) {
+ // This is a disabled call: adr+nop. See ToggleCall().
+ mozilla::DebugOnly<const Instruction*> nextInst =
+ inst->InstructionAtOffset(vixl::kInstructionSize)->skipPool();
+ MOZ_ASSERT(nextInst->IsNOP());
+ ptrdiff_t offset = inst->ImmPCRawOffset() << vixl::kLiteralEntrySizeLog2;
+ // This is what Literal64 would do with the corresponding ldr.
+ memcpy(&target, inst + offset, sizeof(target));
+ } else {
+ MOZ_CRASH("Unrecognized jump instruction.");
+ }
+
+ // If the jump is within the code buffer, it uses the extended jump table.
+ if (target >= code->raw() && target < code->raw() + code->instructionsSize()) {
+ MOZ_ASSERT(target + Assembler::SizeOfJumpTableEntry <= code->raw() + code->instructionsSize());
+
+ uint8_t** patchablePtr = (uint8_t**)(target + Assembler::OffsetOfJumpTableEntryPointer);
+ target = *patchablePtr;
+ }
+
+ return JitCode::FromExecutable(target);
+}
+
+void
+Assembler::TraceJumpRelocations(JSTracer* trc, JitCode* code, CompactBufferReader& reader)
+{
+ RelocationIterator iter(reader);
+ while (iter.read()) {
+ JitCode* child = CodeFromJump(code, code->raw() + iter.offset());
+ TraceManuallyBarrieredEdge(trc, &child, "rel32");
+ MOZ_ASSERT(child == CodeFromJump(code, code->raw() + iter.offset()));
+ }
+}
+
+static void
+TraceDataRelocations(JSTracer* trc, uint8_t* buffer, CompactBufferReader& reader)
+{
+ while (reader.more()) {
+ size_t offset = reader.readUnsigned();
+ Instruction* load = (Instruction*)&buffer[offset];
+
+ // The only valid traceable operation is a 64-bit load to an ARMRegister.
+ // Refer to movePatchablePtr() for generation.
+ MOZ_ASSERT(load->Mask(vixl::LoadLiteralMask) == vixl::LDR_x_lit);
+
+ uintptr_t* literalAddr = load->LiteralAddress<uintptr_t*>();
+ uintptr_t literal = *literalAddr;
+
+ // All pointers on AArch64 will have the top bits cleared.
+ // If those bits are not cleared, this must be a Value.
+ if (literal >> JSVAL_TAG_SHIFT) {
+ Value v = Value::fromRawBits(literal);
+ TraceManuallyBarrieredEdge(trc, &v, "ion-masm-value");
+ if (*literalAddr != v.asRawBits()) {
+ // Only update the code if the value changed, because the code
+ // is not writable if we're not moving objects.
+ *literalAddr = v.asRawBits();
+ }
+
+ // TODO: When we can, flush caches here if a pointer was moved.
+ continue;
+ }
+
+ // No barriers needed since the pointers are constants.
+ TraceManuallyBarrieredGenericPointerEdge(trc, reinterpret_cast<gc::Cell**>(literalAddr),
+ "ion-masm-ptr");
+
+ // TODO: Flush caches at end?
+ }
+}
+
+void
+Assembler::TraceDataRelocations(JSTracer* trc, JitCode* code, CompactBufferReader& reader)
+{
+ ::TraceDataRelocations(trc, code->raw(), reader);
+}
+
+void
+Assembler::FixupNurseryObjects(JSContext* cx, JitCode* code, CompactBufferReader& reader,
+ const ObjectVector& nurseryObjects)
+{
+
+ MOZ_ASSERT(!nurseryObjects.empty());
+
+ uint8_t* buffer = code->raw();
+ bool hasNurseryPointers = false;
+
+ while (reader.more()) {
+ size_t offset = reader.readUnsigned();
+ Instruction* ins = (Instruction*)&buffer[offset];
+
+ uintptr_t* literalAddr = ins->LiteralAddress<uintptr_t*>();
+ uintptr_t literal = *literalAddr;
+
+ if (literal >> JSVAL_TAG_SHIFT)
+ continue; // This is a Value.
+
+ if (!(literal & 0x1))
+ continue;
+
+ uint32_t index = literal >> 1;
+ JSObject* obj = nurseryObjects[index];
+ *literalAddr = uintptr_t(obj);
+
+ // Either all objects are still in the nursery, or all objects are tenured.
+ MOZ_ASSERT_IF(hasNurseryPointers, IsInsideNursery(obj));
+
+ if (!hasNurseryPointers && IsInsideNursery(obj))
+ hasNurseryPointers = true;
+ }
+
+ if (hasNurseryPointers)
+ cx->runtime()->gc.storeBuffer.putWholeCell(code);
+}
+
+void
+Assembler::PatchInstructionImmediate(uint8_t* code, PatchedImmPtr imm)
+{
+ MOZ_CRASH("PatchInstructionImmediate()");
+}
+
+void
+Assembler::retarget(Label* label, Label* target)
+{
+ if (label->used()) {
+ if (target->bound()) {
+ bind(label, BufferOffset(target));
+ } else if (target->used()) {
+ // The target is not bound but used. Prepend label's branch list
+ // onto target's.
+ BufferOffset labelBranchOffset(label);
+
+ // Find the head of the use chain for label.
+ BufferOffset next = NextLink(labelBranchOffset);
+ while (next.assigned()) {
+ labelBranchOffset = next;
+ next = NextLink(next);
+ }
+
+ // Then patch the head of label's use chain to the tail of target's
+ // use chain, prepending the entire use chain of target.
+ SetNextLink(labelBranchOffset, BufferOffset(target));
+ target->use(label->offset());
+ } else {
+ // The target is unbound and unused. We can just take the head of
+ // the list hanging off of label, and dump that into target.
+ DebugOnly<uint32_t> prev = target->use(label->offset());
+ MOZ_ASSERT((int32_t)prev == Label::INVALID_OFFSET);
+ }
+ }
+ label->reset();
+}
+
+} // namespace jit
+} // namespace js