blob: 10c39b38f690f7da5a06a967e74f0340b8a534c9 [file] [log] [blame]
/* -*- 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/. */
#ifndef jit_shared_Assembler_shared_h
#define jit_shared_Assembler_shared_h
#include "mozilla/PodOperations.h"
#include <limits.h>
#include "asmjs/AsmJSFrameIterator.h"
#include "jit/JitAllocPolicy.h"
#include "jit/Label.h"
#include "jit/Registers.h"
#include "jit/RegisterSets.h"
#include "vm/HelperThreads.h"
#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64)
// Push return addresses callee-side.
# define JS_USE_LINK_REGISTER
#endif
#if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64)
// JS_SMALL_BRANCH means the range on a branch instruction
// is smaller than the whole address space
# define JS_SMALL_BRANCH
#endif
namespace js {
namespace jit {
namespace Disassembler {
class HeapAccess;
} // namespace Disassembler
static const uint32_t Simd128DataSize = 4 * sizeof(int32_t);
static_assert(Simd128DataSize == 4 * sizeof(int32_t), "SIMD data should be able to contain int32x4");
static_assert(Simd128DataSize == 4 * sizeof(float), "SIMD data should be able to contain float32x4");
static_assert(Simd128DataSize == 2 * sizeof(double), "SIMD data should be able to contain float64x2");
enum Scale {
TimesOne = 0,
TimesTwo = 1,
TimesFour = 2,
TimesEight = 3
};
static_assert(sizeof(JS::Value) == 8,
"required for TimesEight and 3 below to be correct");
static const Scale ValueScale = TimesEight;
static const size_t ValueShift = 3;
static inline unsigned
ScaleToShift(Scale scale)
{
return unsigned(scale);
}
static inline bool
IsShiftInScaleRange(int i)
{
return i >= TimesOne && i <= TimesEight;
}
static inline Scale
ShiftToScale(int i)
{
MOZ_ASSERT(IsShiftInScaleRange(i));
return Scale(i);
}
static inline Scale
ScaleFromElemWidth(int shift)
{
switch (shift) {
case 1:
return TimesOne;
case 2:
return TimesTwo;
case 4:
return TimesFour;
case 8:
return TimesEight;
}
MOZ_CRASH("Invalid scale");
}
// Used for 32-bit immediates which do not require relocation.
struct Imm32
{
int32_t value;
explicit Imm32(int32_t value) : value(value)
{ }
static inline Imm32 ShiftOf(enum Scale s) {
switch (s) {
case TimesOne:
return Imm32(0);
case TimesTwo:
return Imm32(1);
case TimesFour:
return Imm32(2);
case TimesEight:
return Imm32(3);
};
MOZ_CRASH("Invalid scale");
}
static inline Imm32 FactorOf(enum Scale s) {
return Imm32(1 << ShiftOf(s).value);
}
};
// Pointer-sized integer to be embedded as an immediate in an instruction.
struct ImmWord
{
uintptr_t value;
explicit ImmWord(uintptr_t value) : value(value)
{ }
};
// Used for 64-bit immediates which do not require relocation.
struct Imm64
{
uint64_t value;
explicit Imm64(uint64_t value) : value(value)
{ }
Imm32 low() const {
return Imm32(int32_t(value));
}
Imm32 hi() const {
return Imm32(int32_t(value >> 32));
}
inline Imm32 firstHalf() const;
inline Imm32 secondHalf() const;
};
#ifdef DEBUG
static inline bool
IsCompilingAsmJS()
{
// asm.js compilation pushes a JitContext with a null JSCompartment.
JitContext* jctx = MaybeGetJitContext();
return jctx && jctx->compartment == nullptr;
}
#endif
// Pointer to be embedded as an immediate in an instruction.
struct ImmPtr
{
void* value;
explicit ImmPtr(const void* value) : value(const_cast<void*>(value))
{
// To make code serialization-safe, wasm compilation should only
// compile pointer immediates using a SymbolicAddress.
MOZ_ASSERT(!IsCompilingAsmJS());
}
template <class R>
explicit ImmPtr(R (*pf)())
: value(JS_FUNC_TO_DATA_PTR(void*, pf))
{
MOZ_ASSERT(!IsCompilingAsmJS());
}
template <class R, class A1>
explicit ImmPtr(R (*pf)(A1))
: value(JS_FUNC_TO_DATA_PTR(void*, pf))
{
MOZ_ASSERT(!IsCompilingAsmJS());
}
template <class R, class A1, class A2>
explicit ImmPtr(R (*pf)(A1, A2))
: value(JS_FUNC_TO_DATA_PTR(void*, pf))
{
MOZ_ASSERT(!IsCompilingAsmJS());
}
template <class R, class A1, class A2, class A3>
explicit ImmPtr(R (*pf)(A1, A2, A3))
: value(JS_FUNC_TO_DATA_PTR(void*, pf))
{
MOZ_ASSERT(!IsCompilingAsmJS());
}
template <class R, class A1, class A2, class A3, class A4>
explicit ImmPtr(R (*pf)(A1, A2, A3, A4))
: value(JS_FUNC_TO_DATA_PTR(void*, pf))
{
MOZ_ASSERT(!IsCompilingAsmJS());
}
};
// The same as ImmPtr except that the intention is to patch this
// instruction. The initial value of the immediate is 'addr' and this value is
// either clobbered or used in the patching process.
struct PatchedImmPtr {
void* value;
explicit PatchedImmPtr()
: value(nullptr)
{ }
explicit PatchedImmPtr(const void* value)
: value(const_cast<void*>(value))
{ }
};
class AssemblerShared;
class ImmGCPtr;
// Used for immediates which require relocation.
class ImmGCPtr
{
public:
const gc::Cell* value;
explicit ImmGCPtr(const gc::Cell* ptr) : value(ptr)
{
// Nursery pointers can't be used if the main thread might be currently
// performing a minor GC.
MOZ_ASSERT_IF(ptr && !ptr->isTenured(),
!CurrentThreadIsIonCompilingSafeForMinorGC());
// asm.js shouldn't be creating GC things
MOZ_ASSERT(!IsCompilingAsmJS());
}
private:
ImmGCPtr() : value(0) {}
};
// Pointer to be embedded as an immediate that is loaded/stored from by an
// instruction.
struct AbsoluteAddress
{
void* addr;
explicit AbsoluteAddress(const void* addr)
: addr(const_cast<void*>(addr))
{
MOZ_ASSERT(!IsCompilingAsmJS());
}
AbsoluteAddress offset(ptrdiff_t delta) {
return AbsoluteAddress(((uint8_t*) addr) + delta);
}
};
// The same as AbsoluteAddress except that the intention is to patch this
// instruction. The initial value of the immediate is 'addr' and this value is
// either clobbered or used in the patching process.
struct PatchedAbsoluteAddress
{
void* addr;
explicit PatchedAbsoluteAddress()
: addr(nullptr)
{ }
explicit PatchedAbsoluteAddress(const void* addr)
: addr(const_cast<void*>(addr))
{ }
explicit PatchedAbsoluteAddress(uintptr_t addr)
: addr(reinterpret_cast<void*>(addr))
{ }
};
// Specifies an address computed in the form of a register base and a constant,
// 32-bit offset.
struct Address
{
Register base;
int32_t offset;
Address(Register base, int32_t offset) : base(base), offset(offset)
{ }
Address() { mozilla::PodZero(this); }
};
// Specifies an address computed in the form of a register base, a register
// index with a scale, and a constant, 32-bit offset.
struct BaseIndex
{
Register base;
Register index;
Scale scale;
int32_t offset;
BaseIndex(Register base, Register index, Scale scale, int32_t offset = 0)
: base(base), index(index), scale(scale), offset(offset)
{ }
BaseIndex() { mozilla::PodZero(this); }
};
// A BaseIndex used to access Values. Note that |offset| is *not* scaled by
// sizeof(Value). Use this *only* if you're indexing into a series of Values
// that aren't object elements or object slots (for example, values on the
// stack, values in an arguments object, &c.). If you're indexing into an
// object's elements or slots, don't use this directly! Use
// BaseObject{Element,Slot}Index instead.
struct BaseValueIndex : BaseIndex
{
BaseValueIndex(Register base, Register index, int32_t offset = 0)
: BaseIndex(base, index, ValueScale, offset)
{ }
};
// Specifies the address of an indexed Value within object elements from a
// base. The index must not already be scaled by sizeof(Value)!
struct BaseObjectElementIndex : BaseValueIndex
{
BaseObjectElementIndex(Register base, Register index, int32_t offset = 0)
: BaseValueIndex(base, index, offset)
{
NativeObject::elementsSizeMustNotOverflow();
}
};
// Like BaseObjectElementIndex, except for object slots.
struct BaseObjectSlotIndex : BaseValueIndex
{
BaseObjectSlotIndex(Register base, Register index)
: BaseValueIndex(base, index)
{
NativeObject::slotsSizeMustNotOverflow();
}
};
class Relocation {
public:
enum Kind {
// The target is immovable, so patching is only needed if the source
// buffer is relocated and the reference is relative.
HARDCODED,
// The target is the start of a JitCode buffer, which must be traced
// during garbage collection. Relocations and patching may be needed.
JITCODE
};
};
class RepatchLabel
{
static const int32_t INVALID_OFFSET = 0xC0000000;
int32_t offset_ : 31;
uint32_t bound_ : 1;
public:
RepatchLabel() : offset_(INVALID_OFFSET), bound_(0) {}
void use(uint32_t newOffset) {
MOZ_ASSERT(offset_ == INVALID_OFFSET);
MOZ_ASSERT(newOffset != (uint32_t)INVALID_OFFSET);
offset_ = newOffset;
}
bool bound() const {
return bound_;
}
void bind(int32_t dest) {
MOZ_ASSERT(!bound_);
MOZ_ASSERT(dest != INVALID_OFFSET);
offset_ = dest;
bound_ = true;
}
int32_t target() {
MOZ_ASSERT(bound());
int32_t ret = offset_;
offset_ = INVALID_OFFSET;
return ret;
}
int32_t offset() {
MOZ_ASSERT(!bound());
return offset_;
}
bool used() const {
return !bound() && offset_ != (INVALID_OFFSET);
}
};
// An absolute label is like a Label, except it represents an absolute
// reference rather than a relative one. Thus, it cannot be patched until after
// linking.
struct AbsoluteLabel : public LabelBase
{
public:
AbsoluteLabel()
{ }
AbsoluteLabel(const AbsoluteLabel& label) : LabelBase(label)
{ }
int32_t prev() const {
MOZ_ASSERT(!bound());
if (!used())
return INVALID_OFFSET;
return offset();
}
void setPrev(int32_t offset) {
use(offset);
}
void bind() {
bound_ = true;
// These labels cannot be used after being bound.
offset_ = -1;
}
};
class CodeOffset
{
size_t offset_;
static const size_t NOT_BOUND = size_t(-1);
public:
explicit CodeOffset(size_t offset) : offset_(offset) {}
CodeOffset() : offset_(NOT_BOUND) {}
size_t offset() const {
MOZ_ASSERT(bound());
return offset_;
}
void bind(size_t offset) {
MOZ_ASSERT(!bound());
offset_ = offset;
MOZ_ASSERT(bound());
}
bool bound() const {
return offset_ != NOT_BOUND;
}
void offsetBy(size_t delta) {
MOZ_ASSERT(bound());
MOZ_ASSERT(offset_ + delta >= offset_, "no overflow");
offset_ += delta;
}
};
// A code label contains an absolute reference to a point in the code. Thus, it
// cannot be patched until after linking.
// When the source label is resolved into a memory address, this address is
// patched into the destination address.
class CodeLabel
{
// The destination position, where the absolute reference should get
// patched into.
CodeOffset patchAt_;
// The source label (relative) in the code to where the destination should
// get patched to.
CodeOffset target_;
public:
CodeLabel()
{ }
explicit CodeLabel(const CodeOffset& patchAt)
: patchAt_(patchAt)
{ }
CodeLabel(const CodeOffset& patchAt, const CodeOffset& target)
: patchAt_(patchAt),
target_(target)
{ }
CodeOffset* patchAt() {
return &patchAt_;
}
CodeOffset* target() {
return &target_;
}
void offsetBy(size_t delta) {
patchAt_.offsetBy(delta);
target_.offsetBy(delta);
}
};
// Location of a jump or label in a generated JitCode block, relative to the
// start of the block.
class CodeOffsetJump
{
size_t offset_;
#ifdef JS_SMALL_BRANCH
size_t jumpTableIndex_;
#endif
public:
#ifdef JS_SMALL_BRANCH
CodeOffsetJump(size_t offset, size_t jumpTableIndex)
: offset_(offset), jumpTableIndex_(jumpTableIndex)
{}
size_t jumpTableIndex() const {
return jumpTableIndex_;
}
#else
CodeOffsetJump(size_t offset) : offset_(offset) {}
#endif
CodeOffsetJump() {
mozilla::PodZero(this);
}
size_t offset() const {
return offset_;
}
void fixup(MacroAssembler* masm);
};
// Absolute location of a jump or a label in some generated JitCode block.
// Can also encode a CodeOffset{Jump,Label}, such that the offset is initially
// set and the absolute location later filled in after the final JitCode is
// allocated.
class CodeLocationJump
{
uint8_t* raw_;
#ifdef DEBUG
enum State { Uninitialized, Absolute, Relative };
State state_;
void setUninitialized() {
state_ = Uninitialized;
}
void setAbsolute() {
state_ = Absolute;
}
void setRelative() {
state_ = Relative;
}
#else
void setUninitialized() const {
}
void setAbsolute() const {
}
void setRelative() const {
}
#endif
#ifdef JS_SMALL_BRANCH
uint8_t* jumpTableEntry_;
#endif
public:
CodeLocationJump() {
raw_ = nullptr;
setUninitialized();
#ifdef JS_SMALL_BRANCH
jumpTableEntry_ = (uint8_t*) 0xdeadab1e;
#endif
}
CodeLocationJump(JitCode* code, CodeOffsetJump base) {
*this = base;
repoint(code);
}
void operator = (CodeOffsetJump base) {
raw_ = (uint8_t*) base.offset();
setRelative();
#ifdef JS_SMALL_BRANCH
jumpTableEntry_ = (uint8_t*) base.jumpTableIndex();
#endif
}
void repoint(JitCode* code, MacroAssembler* masm = nullptr);
uint8_t* raw() const {
MOZ_ASSERT(state_ == Absolute);
return raw_;
}
uint8_t* offset() const {
MOZ_ASSERT(state_ == Relative);
return raw_;
}
#ifdef JS_SMALL_BRANCH
uint8_t* jumpTableEntry() const {
MOZ_ASSERT(state_ == Absolute);
return jumpTableEntry_;
}
#endif
};
class CodeLocationLabel
{
uint8_t* raw_;
#ifdef DEBUG
enum State { Uninitialized, Absolute, Relative };
State state_;
void setUninitialized() {
state_ = Uninitialized;
}
void setAbsolute() {
state_ = Absolute;
}
void setRelative() {
state_ = Relative;
}
#else
void setUninitialized() const {
}
void setAbsolute() const {
}
void setRelative() const {
}
#endif
public:
CodeLocationLabel() {
raw_ = nullptr;
setUninitialized();
}
CodeLocationLabel(JitCode* code, CodeOffset base) {
*this = base;
repoint(code);
}
explicit CodeLocationLabel(JitCode* code) {
raw_ = code->raw();
setAbsolute();
}
explicit CodeLocationLabel(uint8_t* raw) {
raw_ = raw;
setAbsolute();
}
void operator = (CodeOffset base) {
raw_ = (uint8_t*)base.offset();
setRelative();
}
ptrdiff_t operator - (const CodeLocationLabel& other) {
return raw_ - other.raw_;
}
void repoint(JitCode* code, MacroAssembler* masm = nullptr);
#ifdef DEBUG
bool isSet() const {
return state_ != Uninitialized;
}
#endif
uint8_t* raw() const {
MOZ_ASSERT(state_ == Absolute);
return raw_;
}
uint8_t* offset() const {
MOZ_ASSERT(state_ == Relative);
return raw_;
}
};
// As an invariant across architectures, within asm.js code:
// $sp % AsmJSStackAlignment = (sizeof(AsmJSFrame) + masm.framePushed) % AsmJSStackAlignment
// Thus, AsmJSFrame represents the bytes pushed after the call (which occurred
// with a AsmJSStackAlignment-aligned StackPointer) that are not included in
// masm.framePushed.
struct AsmJSFrame
{
// The caller's saved frame pointer. In non-profiling mode, internal
// asm.js-to-asm.js calls don't update fp and thus don't save the caller's
// frame pointer; the space is reserved, however, so that profiling mode can
// reuse the same function body without recompiling.
uint8_t* callerFP;
// The return address pushed by the call (in the case of ARM/MIPS the return
// address is pushed by the first instruction of the prologue).
void* returnAddress;
};
static_assert(sizeof(AsmJSFrame) == 2 * sizeof(void*), "?!");
static const uint32_t AsmJSFrameBytesAfterReturnAddress = sizeof(void*);
struct AsmJSGlobalAccess
{
CodeOffset patchAt;
unsigned globalDataOffset;
AsmJSGlobalAccess(CodeOffset patchAt, unsigned globalDataOffset)
: patchAt(patchAt), globalDataOffset(globalDataOffset)
{}
};
// Represents an instruction to be patched and the intended pointee. These
// links are accumulated in the MacroAssembler, but patching is done outside
// the MacroAssembler (in AsmJSModule::staticallyLink).
struct AsmJSAbsoluteLink
{
AsmJSAbsoluteLink(CodeOffset patchAt, wasm::SymbolicAddress target)
: patchAt(patchAt), target(target) {}
CodeOffset patchAt;
wasm::SymbolicAddress target;
};
// Represents a call from an asm.js function to another asm.js function,
// represented by the index of the callee in the Module Validator
struct AsmJSInternalCallee
{
uint32_t index;
// Provide a default constructor for embedding it in unions
AsmJSInternalCallee() = default;
explicit AsmJSInternalCallee(uint32_t calleeIndex)
: index(calleeIndex)
{}
};
// The base class of all Assemblers for all archs.
class AssemblerShared
{
wasm::CallSiteAndTargetVector callsites_;
wasm::HeapAccessVector heapAccesses_;
Vector<AsmJSGlobalAccess, 0, SystemAllocPolicy> asmJSGlobalAccesses_;
Vector<AsmJSAbsoluteLink, 0, SystemAllocPolicy> asmJSAbsoluteLinks_;
protected:
Vector<CodeLabel, 0, SystemAllocPolicy> codeLabels_;
bool enoughMemory_;
bool embedsNurseryPointers_;
public:
AssemblerShared()
: enoughMemory_(true),
embedsNurseryPointers_(false)
{}
void propagateOOM(bool success) {
enoughMemory_ &= success;
}
void setOOM() {
enoughMemory_ = false;
}
bool oom() const {
return !enoughMemory_;
}
bool embedsNurseryPointers() const {
return embedsNurseryPointers_;
}
void append(const wasm::CallSiteDesc& desc, CodeOffset label, size_t framePushed,
uint32_t targetIndex = wasm::CallSiteAndTarget::NOT_INTERNAL)
{
// framePushed does not include sizeof(AsmJSFrame), so add it in here (see
// CallSite::stackDepth).
wasm::CallSite callsite(desc, label.offset(), framePushed + sizeof(AsmJSFrame));
enoughMemory_ &= callsites_.append(wasm::CallSiteAndTarget(callsite, targetIndex));
}
wasm::CallSiteAndTargetVector& callSites() { return callsites_; }
void append(wasm::HeapAccess access) { enoughMemory_ &= heapAccesses_.append(access); }
wasm::HeapAccessVector&& extractHeapAccesses() { return Move(heapAccesses_); }
void append(AsmJSGlobalAccess access) { enoughMemory_ &= asmJSGlobalAccesses_.append(access); }
size_t numAsmJSGlobalAccesses() const { return asmJSGlobalAccesses_.length(); }
AsmJSGlobalAccess asmJSGlobalAccess(size_t i) const { return asmJSGlobalAccesses_[i]; }
void append(AsmJSAbsoluteLink link) { enoughMemory_ &= asmJSAbsoluteLinks_.append(link); }
size_t numAsmJSAbsoluteLinks() const { return asmJSAbsoluteLinks_.length(); }
AsmJSAbsoluteLink asmJSAbsoluteLink(size_t i) const { return asmJSAbsoluteLinks_[i]; }
static bool canUseInSingleByteInstruction(Register reg) { return true; }
void addCodeLabel(CodeLabel label) {
propagateOOM(codeLabels_.append(label));
}
size_t numCodeLabels() const {
return codeLabels_.length();
}
CodeLabel codeLabel(size_t i) {
return codeLabels_[i];
}
// Merge this assembler with the other one, invalidating it, by shifting all
// offsets by a delta.
bool asmMergeWith(size_t delta, const AssemblerShared& other) {
size_t i = callsites_.length();
enoughMemory_ &= callsites_.appendAll(other.callsites_);
for (; i < callsites_.length(); i++)
callsites_[i].offsetReturnAddressBy(delta);
i = heapAccesses_.length();
enoughMemory_ &= heapAccesses_.appendAll(other.heapAccesses_);
for (; i < heapAccesses_.length(); i++)
heapAccesses_[i].offsetInsnOffsetBy(delta);
i = asmJSGlobalAccesses_.length();
enoughMemory_ &= asmJSGlobalAccesses_.appendAll(other.asmJSGlobalAccesses_);
for (; i < asmJSGlobalAccesses_.length(); i++)
asmJSGlobalAccesses_[i].patchAt.offsetBy(delta);
i = asmJSAbsoluteLinks_.length();
enoughMemory_ &= asmJSAbsoluteLinks_.appendAll(other.asmJSAbsoluteLinks_);
for (; i < asmJSAbsoluteLinks_.length(); i++)
asmJSAbsoluteLinks_[i].patchAt.offsetBy(delta);
i = codeLabels_.length();
enoughMemory_ &= codeLabels_.appendAll(other.codeLabels_);
for (; i < codeLabels_.length(); i++)
codeLabels_[i].offsetBy(delta);
return !oom();
}
};
} // namespace jit
} // namespace js
#endif /* jit_shared_Assembler_shared_h */