blob: ba57564a1d5e1493f014b17f2247deae6f38ca02 [file] [log] [blame]
/*
* Copyright (C) 2009, 2010, 2012 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef LinkBuffer_h
#define LinkBuffer_h
#include <Platform.h>
#if ENABLE(ASSEMBLER)
#define DUMP_LINK_STATISTICS 0
#define DUMP_CODE 0
#define GLOBAL_THUNK_ID reinterpret_cast<void*>(static_cast<intptr_t>(-1))
#define REGEXP_CODE_ID reinterpret_cast<void*>(static_cast<intptr_t>(-2))
#include "JITCompilationEffort.h"
#include "MacroAssembler.h"
#include "Options.h"
#include <wtf/DataLog.h>
#include <wtf/Noncopyable.h>
namespace JSC {
class JSGlobalData;
template <typename T>
struct DefaultExecutableOffsetCalculator {
template <typename Assembler>
static T applyOffset(Assembler *, T src) { return src; }
};
// LinkBuffer:
//
// This class assists in linking code generated by the macro assembler, once code generation
// has been completed, and the code has been copied to is final location in memory. At this
// time pointers to labels within the code may be resolved, and relative offsets to external
// addresses may be fixed.
//
// Specifically:
// * Jump objects may be linked to external targets,
// * The address of Jump objects may taken, such that it can later be relinked.
// * The return address of a Call may be acquired.
// * The address of a Label pointing into the code may be resolved.
// * The value referenced by a DataLabel may be set.
//
template <typename MacroAssembler, template <typename T> class ExecutableOffsetCalculator>
class LinkBufferBase {
WTF_MAKE_NONCOPYABLE(LinkBufferBase)
typedef MacroAssemblerCodeRef CodeRef;
typedef MacroAssemblerCodePtr CodePtr;
typedef typename MacroAssembler::Label Label;
typedef typename MacroAssembler::Jump Jump;
typedef typename MacroAssembler::PatchableJump PatchableJump;
typedef typename MacroAssembler::JumpList JumpList;
typedef typename MacroAssembler::Call Call;
typedef typename MacroAssembler::DataLabelCompact DataLabelCompact;
typedef typename MacroAssembler::DataLabel32 DataLabel32;
typedef typename MacroAssembler::DataLabelPtr DataLabelPtr;
typedef typename MacroAssembler::ConvertibleLoadLabel ConvertibleLoadLabel;
public:
LinkBufferBase(JSGlobalData& globalData, MacroAssembler* masm, JITCompilationEffort effort = JITCompilationMustSucceed)
: m_size(0)
, m_code(0)
, m_assembler(masm)
, m_globalData(&globalData)
#ifndef NDEBUG
, m_completed(false)
, m_effort(effort)
#endif
{
#ifdef NDEBUG
UNUSED_PARAM(effort)
#endif
// Simon: Moved this to the sub-classes linkCode(ownerUID, effort);
}
~LinkBufferBase()
{
ASSERT(m_completed || (!m_executableMemory && m_effort == JITCompilationCanFail));
}
bool didFailToAllocate() const
{
return !m_executableMemory;
}
bool isValid() const
{
return !didFailToAllocate();
}
// These methods are used to link or set values at code generation time.
void link(Call call, FunctionPtr function)
{
ASSERT(call.isFlagSet(Call::Linkable));
call.m_label = applyOffset(call.m_label);
MacroAssembler::linkCall(code(), call, function);
}
void link(Jump jump, CodeLocationLabel label)
{
jump.m_label = applyOffset(jump.m_label);
MacroAssembler::linkJump(code(), jump, label);
}
void link(JumpList list, CodeLocationLabel label)
{
for (unsigned i = 0; i < list.m_jumps.size(); ++i)
link(list.m_jumps[i], label);
}
void patch(DataLabelPtr label, void* value)
{
AssemblerLabel target = applyOffset(label.m_label);
MacroAssembler::linkPointer(code(), target, value);
}
void patch(DataLabelPtr label, CodeLocationLabel value)
{
AssemblerLabel target = applyOffset(label.m_label);
MacroAssembler::linkPointer(code(), target, value.executableAddress());
}
// These methods are used to obtain handles to allow the code to be relinked / repatched later.
CodeLocationCall locationOf(Call call)
{
ASSERT(call.isFlagSet(Call::Linkable));
ASSERT(!call.isFlagSet(Call::Near));
return CodeLocationCall(MacroAssembler::getLinkerAddress(code(), applyOffset(call.m_label)));
}
CodeLocationNearCall locationOfNearCall(Call call)
{
ASSERT(call.isFlagSet(Call::Linkable));
ASSERT(call.isFlagSet(Call::Near));
return CodeLocationNearCall(MacroAssembler::getLinkerAddress(code(), applyOffset(call.m_label)));
}
CodeLocationLabel locationOf(PatchableJump jump)
{
return CodeLocationLabel(MacroAssembler::getLinkerAddress(code(), applyOffset(jump.m_jump.m_label)));
}
CodeLocationLabel locationOf(Label label)
{
return CodeLocationLabel(MacroAssembler::getLinkerAddress(code(), applyOffset(label.m_label)));
}
CodeLocationDataLabelPtr locationOf(DataLabelPtr label)
{
return CodeLocationDataLabelPtr(MacroAssembler::getLinkerAddress(code(), applyOffset(label.m_label)));
}
CodeLocationDataLabel32 locationOf(DataLabel32 label)
{
return CodeLocationDataLabel32(MacroAssembler::getLinkerAddress(code(), applyOffset(label.m_label)));
}
CodeLocationDataLabelCompact locationOf(DataLabelCompact label)
{
return CodeLocationDataLabelCompact(MacroAssembler::getLinkerAddress(code(), applyOffset(label.m_label)));
}
CodeLocationConvertibleLoad locationOf(ConvertibleLoadLabel label)
{
return CodeLocationConvertibleLoad(MacroAssembler::getLinkerAddress(code(), applyOffset(label.m_label)));
}
// This method obtains the return address of the call, given as an offset from
// the start of the code.
unsigned returnAddressOffset(Call call)
{
call.m_label = applyOffset(call.m_label);
return MacroAssembler::getLinkerCallReturnOffset(call);
}
uint32_t offsetOf(Label label)
{
return applyOffset(label.m_label).m_offset;
}
// Upon completion of all patching 'FINALIZE_CODE()' should be called once to
// complete generation of the code. Alternatively, call
// finalizeCodeWithoutDisassembly() directly if you have your own way of
// displaying disassembly.
inline CodeRef finalizeCodeWithoutDisassembly();
inline CodeRef finalizeCodeWithDisassembly(const char *jitKind, const char* func);
CodePtr trampolineAt(Label label)
{
return CodePtr(MacroAssembler::AssemblerType_T::getRelocatedAddress(code(), applyOffset(label.m_label)));
}
void* debugAddress()
{
return m_code;
}
size_t debugSize()
{
return m_size;
}
inline void makeExecutable();
private:
template <typename T> T applyOffset(T src)
{
return ExecutableOffsetCalculator<T>::applyOffset(m_assembler, src);
}
protected:
// Keep this private! - the underlying code should only be obtained externally via finalizeCode().
void* code()
{
return m_code;
}
inline void linkCode(void* ownerUID, JITCompilationEffort);
virtual void performFinalization();
#if DUMP_LINK_STATISTICS
static void dumpLinkStatistics(void* code, size_t initialSize, size_t finalSize);
#endif
#if DUMP_CODE
static void dumpCode(void* code, size_t);
#endif
RefPtr<ExecutableMemoryHandle> m_executableMemory;
size_t m_size;
void* m_code;
MacroAssembler* m_assembler;
JSGlobalData* m_globalData;
protected:
#ifndef NDEBUG
bool m_completed;
JITCompilationEffort m_effort;
#endif
};
#define FINALIZE_CODE_IF(condition, linkBufferReference, jitKind, func) \
(UNLIKELY((condition)) \
? ((linkBufferReference).finalizeCodeWithDisassembly (jitKind, func)) \
: (linkBufferReference).finalizeCodeWithoutDisassembly())
// Use this to finalize code, like so:
//
// CodeRef code = FINALIZE_CODE(linkBuffer, ("my super thingy number %d", number));
//
// Which, in disassembly mode, will print:
//
// Generated JIT code for my super thingy number 42:
// Code at [0x123456, 0x234567]:
// 0x123456: mov $0, 0
// 0x12345a: ret
//
// ... and so on.
//
// Note that the dataLogFArgumentsForHeading are only evaluated when showDisassembly
// is true, so you can hide expensive disassembly-only computations inside there.
#define FINALIZE_CODE(linkBufferReference, jitKind, func) \
FINALIZE_CODE_IF(Options::showDisassembly(), linkBufferReference, jitKind, func)
#define FINALIZE_DFG_CODE(linkBufferReference, jitKind, func) \
FINALIZE_CODE_IF((Options::showDisassembly() || Options::showDFGDisassembly()), linkBufferReference, jitKind, func)
template <typename MacroAssembler, template <typename T> class ExecutableOffsetCalculator>
inline typename LinkBufferBase<MacroAssembler, ExecutableOffsetCalculator>::CodeRef LinkBufferBase<MacroAssembler, ExecutableOffsetCalculator>::finalizeCodeWithoutDisassembly()
{
performFinalization();
return CodeRef(m_executableMemory);
}
template <typename MacroAssembler, template <typename T> class ExecutableOffsetCalculator>
inline typename LinkBufferBase<MacroAssembler, ExecutableOffsetCalculator>::CodeRef LinkBufferBase<MacroAssembler, ExecutableOffsetCalculator>::finalizeCodeWithDisassembly(const char *jitKind, const char* func)
{
ASSERT(Options::showDisassembly() || Options::showDFGDisassembly());
CodeRef result = finalizeCodeWithoutDisassembly();
dataLogF("Generated %s code for function %s:", jitKind, func);
dataLogF(
#if OS(WINDOWS)
" Code at [0x%p, 0x%p):",
#else
" Code at [%p, %p):",
#endif
result.code().executableAddress(), static_cast<char*>(result.code().executableAddress()) + result.size());
disassemble(result.code(), m_size, " ", WTF::dataFile());
return result;
}
template <typename MacroAssembler, template <typename T> class ExecutableOffsetCalculator>
inline void LinkBufferBase<MacroAssembler, ExecutableOffsetCalculator>::linkCode(void* ownerUID, JITCompilationEffort effort)
{
UNUSED_PARAM(ownerUID)
UNUSED_PARAM(effort)
ASSERT(!m_code);
m_executableMemory = m_assembler->m_assembler.executableCopy(*m_globalData, ownerUID, effort);
if (!m_executableMemory)
return;
m_code = m_executableMemory->start();
m_size = m_assembler->m_assembler.codeSize();
ASSERT(m_code);
}
template <typename MacroAssembler, template <typename T> class ExecutableOffsetCalculator>
void LinkBufferBase<MacroAssembler, ExecutableOffsetCalculator>::performFinalization()
{
// NOTE: This function is specialized in LinkBuffer<MacroAssemblerARMv7>
#ifndef NDEBUG
ASSERT(!m_completed);
ASSERT(isValid());
m_completed = true;
#endif
ASSERT(m_size <= INT_MAX);
MacroAssembler::cacheFlush(code(), m_size);
}
template <typename MacroAssembler, template <typename T> class ExecutableOffsetCalculator>
inline void LinkBufferBase<MacroAssembler, ExecutableOffsetCalculator>::makeExecutable()
{
ExecutableAllocator::makeExecutable(code(), static_cast<int>(m_size));
}
template <typename MacroAssembler>
class LinkBuffer : public LinkBufferBase<MacroAssembler, DefaultExecutableOffsetCalculator>
{
public:
LinkBuffer(JSGlobalData& globalData, MacroAssembler* masm, void* ownerUID, JITCompilationEffort effort = JITCompilationMustSucceed)
: LinkBufferBase<MacroAssembler, DefaultExecutableOffsetCalculator>(globalData, masm, effort)
{
this->linkCode(ownerUID, effort);
}
};
#if CPU(ARM_THUMB2) || CPU(ARM64)
template <typename T>
struct BranchCompactingExecutableOffsetCalculator {
template <typename Assembler>
static T applyOffset(Assembler *as, T src) {
src.m_offset -= as->executableOffsetFor(src.m_offset);
return src;
}
};
template <typename MacroAssembler>
class BranchCompactingLinkBuffer : public LinkBufferBase<MacroAssembler, BranchCompactingExecutableOffsetCalculator>
{
public:
BranchCompactingLinkBuffer(JSGlobalData& globalData, MacroAssembler* masm, void* ownerUID, JITCompilationEffort effort = JITCompilationMustSucceed)
: LinkBufferBase<MacroAssembler, BranchCompactingExecutableOffsetCalculator>(globalData, masm, effort)
{
linkCode(ownerUID, effort);
}
virtual void performFinalization() override final;
inline void makeExecutable();
inline void linkCode(void* ownerUID, JITCompilationEffort);
private:
using Base = LinkBufferBase<MacroAssembler, BranchCompactingExecutableOffsetCalculator>;
#ifndef NDEBUG
using Base::m_completed;
#endif
using Base::isValid;
using Base::code;
using Base::m_code;
using Base::m_size;
using Base::m_assembler;
using Base::m_executableMemory;
using Base::m_globalData;
using LinkRecord = typename MacroAssembler::LinkRecord;
using JumpLinkType = typename MacroAssembler::JumpLinkType;
size_t m_initialSize = 0;
};
template <typename MacroAssembler>
void BranchCompactingLinkBuffer<MacroAssembler>::performFinalization()
{
#ifndef NDEBUG
ASSERT(!m_completed);
ASSERT(isValid());
this->m_completed = true;
#endif
MacroAssembler::cacheFlush(code(), m_size);
}
template <typename MacroAssembler>
inline void BranchCompactingLinkBuffer<MacroAssembler>::makeExecutable()
{
ExecutableAllocator::makeExecutable(code(), m_initialSize);
}
template <typename MacroAssembler>
inline void BranchCompactingLinkBuffer<MacroAssembler>::linkCode(void* ownerUID, JITCompilationEffort effort)
{
UNUSED_PARAM(ownerUID)
UNUSED_PARAM(effort)
ASSERT(!m_code);
m_initialSize = m_assembler->m_assembler.codeSize();
m_executableMemory = m_globalData->executableAllocator.allocate(*m_globalData, m_initialSize, ownerUID, effort);
if (!m_executableMemory)
return;
m_code = (uint8_t*)m_executableMemory->start();
ASSERT(m_code);
ExecutableAllocator::makeWritable(m_code, m_initialSize);
uint8_t* inData = (uint8_t*)m_assembler->unlinkedCode();
uint8_t* outData = reinterpret_cast<uint8_t*>(m_code);
int readPtr = 0;
int writePtr = 0;
Vector<LinkRecord, 0, UnsafeVectorOverflow>& jumpsToLink = m_assembler->jumpsToLink();
unsigned jumpCount = unsigned(jumpsToLink.size());
for (unsigned i = 0; i < jumpCount; ++i) {
int offset = readPtr - writePtr;
ASSERT(!(offset & 1));
// Copy the instructions from the last jump to the current one.
unsigned regionSize = unsigned(jumpsToLink[i].from() - readPtr);
uint16_t* copySource = reinterpret_cast_ptr<uint16_t*>(inData + readPtr);
uint16_t* copyEnd = reinterpret_cast_ptr<uint16_t*>(inData + readPtr + regionSize);
uint16_t* copyDst = reinterpret_cast_ptr<uint16_t*>(outData + writePtr);
ASSERT(!(regionSize % 2));
ASSERT(!(readPtr % 2));
ASSERT(!(writePtr % 2));
while (copySource != copyEnd)
*copyDst++ = *copySource++;
m_assembler->recordLinkOffsets(readPtr, jumpsToLink[i].from(), offset);
readPtr += regionSize;
writePtr += regionSize;
// Calculate absolute address of the jump target, in the case of backwards
// branches we need to be precise, forward branches we are pessimistic
const uint8_t* target;
if (jumpsToLink[i].to() >= jumpsToLink[i].from())
target = outData + jumpsToLink[i].to() - offset; // Compensate for what we have collapsed so far
else
target = outData + jumpsToLink[i].to() - m_assembler->executableOffsetFor(jumpsToLink[i].to());
JumpLinkType jumpLinkType = m_assembler->computeJumpType(jumpsToLink[i], outData + writePtr, target);
// Compact branch if we can...
if (m_assembler->canCompact(jumpsToLink[i].type())) {
// Step back in the write stream
int32_t delta = m_assembler->jumpSizeDelta(jumpsToLink[i].type(), jumpLinkType);
if (delta) {
writePtr -= delta;
m_assembler->recordLinkOffsets(jumpsToLink[i].from() - delta, readPtr, readPtr - writePtr);
}
}
jumpsToLink[i].setFrom(writePtr);
}
// Copy everything after the last jump
memcpy(outData + writePtr, inData + readPtr, m_initialSize - readPtr);
m_assembler->recordLinkOffsets(readPtr, unsigned(m_initialSize), readPtr - writePtr);
for (unsigned i = 0; i < jumpCount; ++i) {
uint8_t* location = outData + jumpsToLink[i].from();
uint8_t* target = outData + jumpsToLink[i].to() - m_assembler->executableOffsetFor(jumpsToLink[i].to());
m_assembler->link(jumpsToLink[i], location, target);
}
jumpsToLink.clear();
m_size = writePtr + m_initialSize - readPtr;
m_executableMemory->shrink(m_size);
}
#if CPU(ARM_THUMB2)
template <>
class LinkBuffer<JSC::MacroAssembler<MacroAssemblerARMv7>> : public BranchCompactingLinkBuffer<JSC::MacroAssembler<MacroAssemblerARMv7>>
{
public:
LinkBuffer(JSGlobalData& globalData, JSC::MacroAssembler<MacroAssemblerARMv7>* masm, void* ownerUID, JITCompilationEffort effort = JITCompilationMustSucceed)
: BranchCompactingLinkBuffer<JSC::MacroAssembler<MacroAssemblerARMv7>>(globalData, masm, ownerUID, effort)
{}
};
#endif
#if CPU(ARM64)
template <>
class LinkBuffer<JSC::MacroAssembler<MacroAssemblerARM64>> : public BranchCompactingLinkBuffer<JSC::MacroAssembler<MacroAssemblerARM64>>
{
public:
LinkBuffer(JSGlobalData& globalData, JSC::MacroAssembler<MacroAssemblerARM64>* masm, void* ownerUID, JITCompilationEffort effort = JITCompilationMustSucceed)
: BranchCompactingLinkBuffer<JSC::MacroAssembler<JSC::MacroAssemblerARM64>>(globalData, masm, ownerUID, effort)
{}
};
#endif
#endif
#if CPU(ARM_THUMB2)
typedef LinkBuffer<MacroAssembler<MacroAssemblerARMv7>> DefaultLinkBuffer;
#elif CPU(ARM64)
typedef LinkBuffer<MacroAssembler<MacroAssemblerARM64>> DefaultLinkBuffer;
#elif CPU(MIPS)
typedef LinkBuffer<MacroAssembler<MacroAssemblerMIPS>> DefaultLinkBuffer;
#elif CPU(X86)
typedef LinkBuffer<MacroAssembler<MacroAssemblerX86>> DefaultLinkBuffer;
#elif CPU(X86_64)
typedef LinkBuffer<MacroAssembler<MacroAssemblerX86_64>> DefaultLinkBuffer;
#endif
} // namespace JSC
#endif // ENABLE(ASSEMBLER)
#endif // LinkBuffer_h