blob: 64d3cdf6494859e61d379ecefc6e61fa8801b01c [file] [log] [blame]
/*
* Copyright (C) 2012, 2016 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.
*/
#include "config.h"
#if USE(ARM64_DISASSEMBLER)
#include "A64DOpcode.h"
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
namespace JSC { namespace ARM64Disassembler {
A64DOpcode::OpcodeGroup* A64DOpcode::opcodeTable[32];
const char* const A64DOpcode::s_conditionNames[16] = {
"eq", "ne", "hs", "lo", "mi", "pl", "vs", "vc",
"hi", "ls", "ge", "lt", "gt", "le", "al", "ne"
};
const char* const A64DOpcode::s_optionName[8] = {
"uxtb", "uxth", "uxtw", "uxtx", "sxtb", "sxth", "sxtw", "sxtx"
};
const char* const A64DOpcode::s_shiftNames[4] = {
"lsl", "lsr", "asl", "ror"
};
const char A64DOpcode::s_FPRegisterPrefix[5] = {
'b', 'h', 's', 'd', 'q'
};
struct OpcodeGroupInitializer {
unsigned m_opcodeGroupNumber;
uint32_t m_mask;
uint32_t m_pattern;
const char* (*m_format)(A64DOpcode*);
};
#define OPCODE_GROUP_ENTRY(groupIndex, groupClass) \
{ groupIndex, groupClass::mask, groupClass::pattern, groupClass::format }
static const OpcodeGroupInitializer opcodeGroupList[] = {
OPCODE_GROUP_ENTRY(0x08, A64DOpcodeLoadStoreRegisterPair),
OPCODE_GROUP_ENTRY(0x08, A64DOpcodeLoadStoreExclusive),
OPCODE_GROUP_ENTRY(0x09, A64DOpcodeLoadStoreRegisterPair),
OPCODE_GROUP_ENTRY(0x0a, A64DOpcodeLogicalShiftedRegister),
OPCODE_GROUP_ENTRY(0x0b, A64DOpcodeAddSubtractExtendedRegister),
OPCODE_GROUP_ENTRY(0x0b, A64DOpcodeAddSubtractShiftedRegister),
OPCODE_GROUP_ENTRY(0x11, A64DOpcodeAddSubtractImmediate),
OPCODE_GROUP_ENTRY(0x12, A64DOpcodeMoveWide),
OPCODE_GROUP_ENTRY(0x12, A64DOpcodeLogicalImmediate),
OPCODE_GROUP_ENTRY(0x13, A64DOpcodeBitfield),
OPCODE_GROUP_ENTRY(0x13, A64DOpcodeExtract),
OPCODE_GROUP_ENTRY(0x14, A64DOpcodeUnconditionalBranchImmediate),
OPCODE_GROUP_ENTRY(0x14, A64DOpcodeConditionalBranchImmediate),
OPCODE_GROUP_ENTRY(0x14, A64DOpcodeCompareAndBranchImmediate),
OPCODE_GROUP_ENTRY(0x14, A64OpcodeExceptionGeneration),
OPCODE_GROUP_ENTRY(0x15, A64DOpcodeUnconditionalBranchImmediate),
OPCODE_GROUP_ENTRY(0x15, A64DOpcodeConditionalBranchImmediate),
OPCODE_GROUP_ENTRY(0x15, A64DOpcodeCompareAndBranchImmediate),
OPCODE_GROUP_ENTRY(0x15, A64DOpcodeHint),
OPCODE_GROUP_ENTRY(0x15, A64DOpcodeSystemSync),
OPCODE_GROUP_ENTRY(0x15, A64DOpcodeMSRImmediate),
OPCODE_GROUP_ENTRY(0x15, A64DOpcodeMSROrMRSRegister),
OPCODE_GROUP_ENTRY(0x16, A64DOpcodeUnconditionalBranchImmediate),
OPCODE_GROUP_ENTRY(0x16, A64DOpcodeUnconditionalBranchRegister),
OPCODE_GROUP_ENTRY(0x16, A64DOpcodeTestAndBranchImmediate),
OPCODE_GROUP_ENTRY(0x17, A64DOpcodeUnconditionalBranchImmediate),
OPCODE_GROUP_ENTRY(0x17, A64DOpcodeUnconditionalBranchRegister),
OPCODE_GROUP_ENTRY(0x17, A64DOpcodeTestAndBranchImmediate),
OPCODE_GROUP_ENTRY(0x18, A64DOpcodeLoadStoreImmediate),
OPCODE_GROUP_ENTRY(0x18, A64DOpcodeLoadStoreRegisterOffset),
OPCODE_GROUP_ENTRY(0x18, A64DOpcodeLoadStoreAuthenticated),
OPCODE_GROUP_ENTRY(0x19, A64DOpcodeLoadStoreUnsignedImmediate),
OPCODE_GROUP_ENTRY(0x1a, A64DOpcodeConditionalSelect),
OPCODE_GROUP_ENTRY(0x1a, A64DOpcodeDataProcessing1Source),
OPCODE_GROUP_ENTRY(0x1a, A64DOpcodeDataProcessing2Source),
OPCODE_GROUP_ENTRY(0x1b, A64DOpcodeDataProcessing3Source),
OPCODE_GROUP_ENTRY(0x1c, A64DOpcodeLoadStoreImmediate),
OPCODE_GROUP_ENTRY(0x1c, A64DOpcodeLoadStoreRegisterOffset),
OPCODE_GROUP_ENTRY(0x1d, A64DOpcodeLoadStoreUnsignedImmediate),
OPCODE_GROUP_ENTRY(0x1e, A64DOpcodeFloatingPointCompare),
OPCODE_GROUP_ENTRY(0x1e, A64DOpcodeFloatingPointConditionalSelect),
OPCODE_GROUP_ENTRY(0x1e, A64DOpcodeFloatingPointDataProcessing2Source),
OPCODE_GROUP_ENTRY(0x1e, A64DOpcodeFloatingPointDataProcessing1Source),
OPCODE_GROUP_ENTRY(0x1e, A64DOpcodeFloatingFixedPointConversions),
OPCODE_GROUP_ENTRY(0x1e, A64DOpcodeFloatingPointIntegerConversions),
};
bool A64DOpcode::s_initialized = false;
void A64DOpcode::init()
{
if (s_initialized)
return;
OpcodeGroup* lastGroups[32];
for (unsigned i = 0; i < 32; i++) {
opcodeTable[i] = 0;
lastGroups[i] = 0;
}
for (unsigned i = 0; i < sizeof(opcodeGroupList) / sizeof(struct OpcodeGroupInitializer); i++) {
OpcodeGroup* newOpcodeGroup = new OpcodeGroup(opcodeGroupList[i].m_mask, opcodeGroupList[i].m_pattern, opcodeGroupList[i].m_format);
uint32_t opcodeGroupNumber = opcodeGroupList[i].m_opcodeGroupNumber;
if (!opcodeTable[opcodeGroupNumber])
opcodeTable[opcodeGroupNumber] = newOpcodeGroup;
else
lastGroups[opcodeGroupNumber]->setNext(newOpcodeGroup);
lastGroups[opcodeGroupNumber] = newOpcodeGroup;
}
s_initialized = true;
}
void A64DOpcode::setPCAndOpcode(uint32_t* newPC, uint32_t newOpcode)
{
m_currentPC = newPC;
m_opcode = newOpcode;
m_bufferOffset = 0;
m_formatBuffer[0] = '\0';
}
const char* A64DOpcode::disassemble(uint32_t* currentPC)
{
setPCAndOpcode(currentPC, *currentPC);
OpcodeGroup* opGroup = opcodeTable[opcodeGroupNumber(m_opcode)];
while (opGroup) {
if (opGroup->matches(m_opcode))
return opGroup->format(this);
opGroup = opGroup->next();
}
return A64DOpcode::format();
}
void A64DOpcode::bufferPrintf(const char* format, ...)
{
if (m_bufferOffset >= bufferSize)
return;
va_list argList;
va_start(argList, format);
m_bufferOffset += vsnprintf(m_formatBuffer + m_bufferOffset, bufferSize - m_bufferOffset, format, argList);
va_end(argList);
}
const char* A64DOpcode::format()
{
bufferPrintf(" .long %08x", m_opcode);
return m_formatBuffer;
}
void A64DOpcode::appendRegisterName(unsigned registerNumber, bool is64Bit)
{
if (registerNumber == 29) {
bufferPrintf(is64Bit ? "fp" : "wfp");
return;
}
if (registerNumber == 30) {
bufferPrintf(is64Bit ? "lr" : "wlr");
return;
}
bufferPrintf("%c%u", is64Bit ? 'x' : 'w', registerNumber);
}
void A64DOpcode::appendFPRegisterName(unsigned registerNumber, unsigned registerSize)
{
bufferPrintf("%c%u", FPRegisterPrefix(registerSize), registerNumber);
}
const char* const A64DOpcodeAddSubtract::s_opNames[4] = { "add", "adds", "sub", "subs" };
const char* A64DOpcodeAddSubtractImmediate::format()
{
if (isCMP())
appendInstructionName(cmpName());
else {
if (isMovSP())
appendInstructionName("mov");
else
appendInstructionName(opName());
appendSPOrRegisterName(rd(), is64Bit());
appendSeparator();
}
appendSPOrRegisterName(rn(), is64Bit());
if (!isMovSP()) {
appendSeparator();
appendUnsignedImmediate(immed12());
if (shift()) {
appendSeparator();
appendString(shift() == 1 ? "lsl" : "reserved");
}
}
return m_formatBuffer;
}
const char* A64DOpcodeAddSubtractExtendedRegister::format()
{
if (immediate3() > 4)
return A64DOpcode::format();
if (isCMP())
appendInstructionName(cmpName());
else {
appendInstructionName(opName());
appendSPOrRegisterName(rd(), is64Bit());
appendSeparator();
}
appendSPOrRegisterName(rn(), is64Bit());
appendSeparator();
appendZROrRegisterName(rm(), is64Bit() && ((option() & 0x3) == 0x3));
appendSeparator();
if (option() == 0x2 && ((rd() == 31) || (rn() == 31)))
appendString("lsl");
else
appendString(optionName());
if (immediate3()) {
appendCharacter(' ');
appendUnsignedImmediate(immediate3());
}
return m_formatBuffer;
}
const char* A64DOpcodeAddSubtractShiftedRegister::format()
{
if (!is64Bit() && immediate6() & 0x20)
return A64DOpcode::format();
if (shift() == 0x3)
return A64DOpcode::format();
if (isCMP())
appendInstructionName(cmpName());
else {
if (isNeg())
appendInstructionName(cmpName());
else
appendInstructionName(opName());
appendSPOrRegisterName(rd(), is64Bit());
appendSeparator();
}
if (!isNeg()) {
appendRegisterName(rn(), is64Bit());
appendSeparator();
}
appendZROrRegisterName(rm(), is64Bit());
if (immediate6()) {
appendSeparator();
appendShiftType(shift());
appendUnsignedImmediate(immediate6());
}
return m_formatBuffer;
}
const char* const A64DOpcodeBitfield::s_opNames[3] = { "sbfm", "bfm", "ubfm" };
const char* const A64DOpcodeBitfield::s_extendPseudoOpNames[3][3] = {
{ "sxtb", "sxth", "sxtw" }, { 0, 0, 0} , { "uxtb", "uxth", "uxtw" } };
const char* const A64DOpcodeBitfield::s_insertOpNames[3] = { "sbfiz", "bfi", "ubfiz" };
const char* const A64DOpcodeBitfield::s_extractOpNames[3] = { "sbfx", "bfxil", "ubfx" };
const char* A64DOpcodeBitfield::format()
{
if (opc() == 0x3)
return A64DOpcode::format();
if (is64Bit() != nBit())
return A64DOpcode::format();
if (!is64Bit() && ((immediateR() & 0x20) || (immediateS() & 0x20)))
return A64DOpcode::format();
if (!(opc() & 0x1) && !immediateR()) {
// [un]signed {btye,half-word,word} extend
bool isSTXType = false;
if (immediateS() == 7) {
appendInstructionName(extendPseudoOpNames(0));
isSTXType = true;
} else if (immediateS() == 15) {
appendInstructionName(extendPseudoOpNames(1));
isSTXType = true;
} else if (immediateS() == 31 && is64Bit() && !opc()) {
appendInstructionName(extendPseudoOpNames(2));
isSTXType = true;
}
if (isSTXType) {
appendRegisterName(rd(), is64Bit());
appendSeparator();
appendRegisterName(rn(), false);
return m_formatBuffer;
}
}
if (!(opc() & 0x1) && ((immediateS() & 0x1f) == 0x1f) && (is64Bit() == (immediateS() >> 5))) {
// asr/lsr
appendInstructionName(!opc() ? "asr" : "lsr");
appendRegisterName(rd(), is64Bit());
appendSeparator();
appendRegisterName(rn(), is64Bit());
appendSeparator();
appendUnsignedImmediate(immediateR());
return m_formatBuffer;
}
if (opc() == 0x2 && (immediateS() + 1) == immediateR()) {
// lsl
appendInstructionName("lsl");
appendRegisterName(rd(), is64Bit());
appendSeparator();
appendRegisterName(rn(), is64Bit());
appendSeparator();
appendUnsignedImmediate((is64Bit() ? 64u : 32u) - immediateR());
return m_formatBuffer;
}
if (immediateS() < immediateR()) {
if (opc() != 1 || rn() != 0x1f) {
// bit field insert
appendInstructionName(insertOpNames());
appendRegisterName(rd(), is64Bit());
appendSeparator();
appendRegisterName(rn(), is64Bit());
appendSeparator();
appendUnsignedImmediate((is64Bit() ? 64u : 32u) - immediateR());
appendSeparator();
appendUnsignedImmediate(immediateS() + 1);
return m_formatBuffer;
}
appendInstructionName(opName());
appendRegisterName(rd(), is64Bit());
appendSeparator();
appendRegisterName(rn(), is64Bit());
appendSeparator();
appendUnsignedImmediate(immediateR());
appendSeparator();
appendUnsignedImmediate(immediateS());
return m_formatBuffer;
}
// bit field extract
appendInstructionName(extractOpNames());
appendRegisterName(rd(), is64Bit());
appendSeparator();
appendRegisterName(rn(), is64Bit());
appendSeparator();
appendUnsignedImmediate(immediateR());
appendSeparator();
appendUnsignedImmediate(immediateS() - immediateR() + 1);
return m_formatBuffer;
}
const char* A64DOpcodeCompareAndBranchImmediate::format()
{
appendInstructionName(opBit() ? "cbnz" : "cbz");
appendRegisterName(rt(), is64Bit());
appendSeparator();
appendPCRelativeOffset(m_currentPC, static_cast<int32_t>(immediate19()));
return m_formatBuffer;
}
const char* A64DOpcodeConditionalBranchImmediate::format()
{
bufferPrintf(" b.%-5.5s", conditionName(condition()));
appendPCRelativeOffset(m_currentPC, static_cast<int32_t>(immediate19()));
return m_formatBuffer;
}
const char* const A64DOpcodeConditionalSelect::s_opNames[4] = {
"csel", "csinc", "csinv", "csneg"
};
const char* A64DOpcodeConditionalSelect::format()
{
if (sBit())
return A64DOpcode::format();
if (op2() & 0x2)
return A64DOpcode::format();
if (rn() == rm() && (opNum() == 1 || opNum() == 2)) {
if (rn() == 31) {
appendInstructionName((opNum() == 1) ? "cset" : "csetm");
appendRegisterName(rd(), is64Bit());
} else {
appendInstructionName((opNum() == 1) ? "cinc" : "cinv");
appendRegisterName(rd(), is64Bit());
appendSeparator();
appendZROrRegisterName(rn(), is64Bit());
}
appendSeparator();
appendString(conditionName(condition() ^ 0x1));
return m_formatBuffer;
}
appendInstructionName(opName());
appendRegisterName(rd(), is64Bit());
appendSeparator();
appendZROrRegisterName(rn(), is64Bit());
appendSeparator();
appendZROrRegisterName(rm(), is64Bit());
appendSeparator();
appendString(conditionName(condition()));
return m_formatBuffer;
}
const char* const A64DOpcodeDataProcessing1Source::s_opNames[8] = {
"rbit", "rev16", "rev32", "rev", "clz", "cls", 0, 0
};
const char* const A64DOpcodeDataProcessing1Source::s_pacAutOpNames[18] = {
"pacia", "pacib", "pacda", "pacdb", "autia", "autib", "autda", "autdb",
"paciza", "pacizb", "pacdza", "pacdzb", "autiza", "autizb", "autdza", "autdzb",
"xpaci", "xpacd"
};
const char* A64DOpcodeDataProcessing1Source::format()
{
if (sBit())
return A64DOpcode::format();
if (opCode2() == 1 && is64Bit() && opCode() <= 0x1001) {
if (opCode() <= 0x00111 || rt() == 0x11111) {
appendInstructionName(s_pacAutOpNames[opCode()]);
appendZROrRegisterName(rd(), is64Bit());
if (opCode() <= 0x00111) {
appendSeparator();
appendZROrRegisterName(rn(), is64Bit());
}
return m_formatBuffer;
}
return A64DOpcode::format();
}
if (opCode2())
return A64DOpcode::format();
if (opCode() & 0x38)
return A64DOpcode::format();
if ((opCode() & 0x3e) == 0x6)
return A64DOpcode::format();
if (is64Bit() && opCode() == 0x3)
return A64DOpcode::format();
if (!is64Bit() && opCode() == 0x2)
appendInstructionName("rev");
else
appendInstructionName(opName());
appendZROrRegisterName(rd(), is64Bit());
appendSeparator();
appendZROrRegisterName(rn(), is64Bit());
return m_formatBuffer;
}
const char* const A64DOpcodeDataProcessing2Source::s_opNames[16] = {
// We use the pseudo-op names for the shift/rotate instructions
0, 0, "udiv", "sdiv", 0, 0, 0, 0,
"lsl", "lsr", "asr", "ror", 0, "pacga", 0, 0
};
const char* A64DOpcodeDataProcessing2Source::format()
{
if (sBit())
return A64DOpcode::format();
if (!(opCode() & 0x3e))
return A64DOpcode::format();
if (opCode() & 0x30)
return A64DOpcode::format();
if ((opCode() & 0x3c) == 0x4)
return A64DOpcode::format();
const char* opcodeName = opName();
if (!opcodeName)
return A64DOpcode::format();
appendInstructionName(opcodeName);
appendZROrRegisterName(rd(), is64Bit());
appendSeparator();
appendZROrRegisterName(rn(), is64Bit());
appendSeparator();
appendZROrRegisterName(rm(), is64Bit());
return m_formatBuffer;
}
const char* const A64DOpcodeDataProcessing3Source::s_opNames[16] = {
"madd", "msub", "smaddl", "smsubl", "smulh", 0, 0, 0,
0, 0, "umaddl", "umsubl", "umulh", 0, 0, 0
};
const char* const A64DOpcodeDataProcessing3Source::s_pseudoOpNames[16] = {
"mul", "mneg", "smull", "smnegl", "smulh", 0, 0, 0,
0, 0, "umull", "umnegl", "umulh", 0, 0, 0
};
const char* A64DOpcodeDataProcessing3Source::format()
{
if (op54())
return A64DOpcode::format();
if (opNum() > 12)
return A64DOpcode::format();
if (!is64Bit() && opNum() > 1)
return A64DOpcode::format();
if (!opName())
return A64DOpcode::format();
if ((opNum() & 0x4) && (ra() != 31))
return A64DOpcode::format();
appendInstructionName(opName());
appendZROrRegisterName(rd(), is64Bit());
appendSeparator();
bool srcOneAndTwoAre64Bit = is64Bit() & !(opNum() & 0x2);
appendZROrRegisterName(rn(), srcOneAndTwoAre64Bit);
appendSeparator();
appendZROrRegisterName(rm(), srcOneAndTwoAre64Bit);
if (ra() != 31) {
appendSeparator();
appendRegisterName(ra(), is64Bit());
}
return m_formatBuffer;
}
const char* A64OpcodeExceptionGeneration::format()
{
const char* opname = 0;
if (!op2()) {
switch (opc()) {
case 0x0: // SVC, HVC & SMC
switch (ll()) {
case 0x1:
opname = "svc";
break;
case 0x2:
opname = "hvc";
break;
case 0x3:
opname = "smc";
break;
}
break;
case 0x1: // BRK
if (!ll())
opname = "brk";
break;
case 0x2: // HLT
if (!ll())
opname = "hlt";
break;
case 0x5: // DPCS1-3
switch (ll()) {
case 0x1:
opname = "dpcs1";
break;
case 0x2:
opname = "dpcs2";
break;
case 0x3:
opname = "dpcs3";
break;
}
break;
}
}
if (!opname)
return A64DOpcode::format();
appendInstructionName(opname);
appendUnsignedImmediate(immediate16());
return m_formatBuffer;
}
const char* A64DOpcodeExtract::format()
{
if (op21() || o0Bit())
return A64DOpcode::format();
if (is64Bit() != nBit())
return A64DOpcode::format();
if (!is64Bit() && (immediateS() & 0x20))
return A64DOpcode::format();
bool isROR = rn() == rm();
const char* opName = (isROR) ? "ror" : "extr";
appendInstructionName(opName);
appendZROrRegisterName(rd(), is64Bit());
appendSeparator();
appendZROrRegisterName(rn(), is64Bit());
if (!isROR) {
appendSeparator();
appendZROrRegisterName(rm(), is64Bit());
}
appendSeparator();
appendUnsignedImmediate(immediateS());
return m_formatBuffer;
}
const char* A64DOpcodeFloatingPointCompare::format()
{
if (mBit())
return A64DOpcode::format();
if (sBit())
return A64DOpcode::format();
if (type() & 0x2)
return A64DOpcode::format();
if (op())
return A64DOpcode::format();
if (opCode2() & 0x7)
return A64DOpcode::format();
appendInstructionName(opName());
unsigned registerSize = type() + 2;
appendFPRegisterName(rn(), registerSize);
appendSeparator();
if (opCode2() & 0x8)
bufferPrintf("#0.0");
else
appendFPRegisterName(rm(), registerSize);
return m_formatBuffer;
}
const char* A64DOpcodeFloatingPointConditionalSelect::format()
{
if (mBit())
return A64DOpcode::format();
if (sBit())
return A64DOpcode::format();
if (type() & 0x2)
return A64DOpcode::format();
appendInstructionName(opName());
unsigned registerSize = type() + 2;
appendFPRegisterName(rd(), registerSize);
appendSeparator();
appendFPRegisterName(rn(), registerSize);
appendSeparator();
appendFPRegisterName(rm(), registerSize);
appendSeparator();
appendString(conditionName(condition()));
return m_formatBuffer;
}
const char* const A64DOpcodeFloatingPointDataProcessing1Source::s_opNames[16] = {
"fmov", "fabs", "fneg", "fsqrt", "fcvt", "fcvt", 0, "fcvt",
"frintn", "frintp", "frintm", "frintz", "frinta", 0, "frintx", "frinti"
};
const char* A64DOpcodeFloatingPointDataProcessing1Source::format()
{
if (mBit())
return A64DOpcode::format();
if (sBit())
return A64DOpcode::format();
if (opNum() > 16)
return A64DOpcode::format();
switch (type()) {
case 0:
if ((opNum() == 0x4) || (opNum() == 0x6) || (opNum() == 0xd))
return A64DOpcode::format();
break;
case 1:
if ((opNum() == 0x5) || (opNum() == 0x6) || (opNum() == 0xd))
return A64DOpcode::format();
break;
case 2:
return A64DOpcode::format();
case 3:
if ((opNum() < 0x4) || (opNum() > 0x5))
return A64DOpcode::format();
break;
}
appendInstructionName(opName());
if ((opNum() >= 0x4) && (opNum() <= 0x7)) {
unsigned srcRegisterSize = type() ^ 0x2; // 0:s, 1:d & 3:h
unsigned destRegisterSize = (opNum() & 0x3) ^ 0x2;
appendFPRegisterName(rd(), destRegisterSize);
appendSeparator();
appendFPRegisterName(rn(), srcRegisterSize);
} else {
unsigned registerSize = type() + 2;
appendFPRegisterName(rd(), registerSize);
appendSeparator();
appendFPRegisterName(rn(), registerSize);
}
return m_formatBuffer;
}
const char* const A64DOpcodeFloatingPointDataProcessing2Source::s_opNames[16] = {
"fmul", "fdiv", "fadd", "fsub", "fmax", "fmin", "fmaxnm", "fminnm", "fnmul"
};
const char* A64DOpcodeFloatingPointDataProcessing2Source::format()
{
if (mBit())
return A64DOpcode::format();
if (sBit())
return A64DOpcode::format();
if (type() & 0x2)
return A64DOpcode::format();
if (opNum() > 8)
return A64DOpcode::format();
appendInstructionName(opName());
unsigned registerSize = type() + 2;
appendFPRegisterName(rd(), registerSize);
appendSeparator();
appendFPRegisterName(rn(), registerSize);
appendSeparator();
appendFPRegisterName(rm(), registerSize);
return m_formatBuffer;
}
const char* const A64DOpcodeFloatingFixedPointConversions::s_opNames[4] = {
"fcvtzs", "fcvtzu", "scvtf", "ucvtf"
};
const char* A64DOpcodeFloatingFixedPointConversions::format()
{
if (sBit())
return A64DOpcode::format();
if (type() & 0x2)
return A64DOpcode::format();
if (opcode() & 0x4)
return A64DOpcode::format();
if (!(rmode() & 0x1) && !(opcode() & 0x6))
return A64DOpcode::format();
if ((rmode() & 0x1) && (opcode() & 0x6) == 0x2)
return A64DOpcode::format();
if (!(rmode() & 0x2) && !(opcode() & 0x6))
return A64DOpcode::format();
if ((rmode() & 0x2) && (opcode() & 0x6) == 0x2)
return A64DOpcode::format();
if (!is64Bit() && scale() >= 32)
return A64DOpcode::format();
appendInstructionName(opName());
unsigned FPRegisterSize = type() + 2;
bool destIsFP = !rmode();
if (destIsFP) {
appendFPRegisterName(rd(), FPRegisterSize);
appendSeparator();
appendRegisterName(rn(), is64Bit());
} else {
appendRegisterName(rd(), is64Bit());
appendSeparator();
appendFPRegisterName(rn(), FPRegisterSize);
}
appendSeparator();
appendUnsignedImmediate(64 - scale());
return m_formatBuffer;
}
const char* const A64DOpcodeFloatingPointIntegerConversions::s_opNames[32] = {
"fcvtns", "fcvtnu", "scvtf", "ucvtf", "fcvtas", "fcvtau", "fmov", "fmov",
"fcvtps", "fcvtpu", 0, 0, 0, 0, "fmov", "fmov",
"fcvtms", "fcvtmu", 0, 0, 0, 0, 0, 0,
"fcvtzs", "fcvtzu", 0, 0, 0, 0, "fjcvtzs", 0
};
const char* A64DOpcodeFloatingPointIntegerConversions::format()
{
if (sBit())
return A64DOpcode::format();
if (type() == 0x3)
return A64DOpcode::format();
if (((rmode() & 0x1) || (rmode() & 0x2)) && (((opcode() & 0x6) == 0x2) || ((opcode() & 0x6) == 0x4)))
return A64DOpcode::format();
if ((type() == 0x2) && (!(opcode() & 0x4) || ((opcode() & 0x6) == 0x4)))
return A64DOpcode::format();
if (!type() && (rmode() & 0x1) && ((opcode() & 0x6) == 0x6))
return A64DOpcode::format();
if (is64Bit() && type() == 0x2 && ((opNum() & 0xe) == 0x6))
return A64DOpcode::format();
if (!opName())
return A64DOpcode::format();
if ((opNum() & 0x1e) == 0xe) {
// Handle fmov to/from upper half of quad separately
if (!is64Bit() || (type() != 0x2))
return A64DOpcode::format();
appendInstructionName(opName());
if (opcode() & 0x1) {
// fmov Vd.D[1], Xn
bufferPrintf("V%u.D[1]", rd());
appendSeparator();
appendZROrRegisterName(rn());
} else {
// fmov Xd, Vn.D[1]
appendZROrRegisterName(rd());
appendSeparator();
bufferPrintf("V%u.D[1]", rn());
}
return m_formatBuffer;
}
appendInstructionName(opName());
unsigned FPRegisterSize = type() + 2;
bool destIsFP = ((opNum() == 2) || (opNum() == 3) || (opNum() == 7));
if (destIsFP) {
appendFPRegisterName(rd(), FPRegisterSize);
appendSeparator();
appendZROrRegisterName(rn(), is64Bit());
} else {
appendZROrRegisterName(rd(), is64Bit());
appendSeparator();
appendFPRegisterName(rn(), FPRegisterSize);
}
return m_formatBuffer;
}
const char* A64DOpcodeMSRImmediate::format()
{
const char* pstateField = nullptr;
if (!op1() && (op2() == 0x5))
pstateField = "spsel";
if ((op1() == 0x3) && (op2() == 0x6))
pstateField = "daifset";
if ((op1() == 0x3) && (op2() == 0x7))
pstateField = "daifclr";
if (!!op1() && !(op2() & 0x4))
return A64DOpcode::format();
if (!pstateField)
return A64DOpcode::format();
appendInstructionName("msr");
appendString(pstateField);
appendSeparator();
appendUnsignedImmediate(crM());
return m_formatBuffer;
}
const char* A64DOpcodeMSROrMRSRegister::format()
{
appendInstructionName(opName());
if (lBit()) {
appendZROrRegisterName(rt());
appendSeparator();
}
bufferPrintf("S%u_%u_C%u_C%u_%u", op0(), op1(), crN(), crM(), op2());
if (!lBit()) {
appendSeparator();
appendZROrRegisterName(rt());
}
const char* systemRegisterName = nullptr;
switch (systemRegister()) {
case 0b1101100000000001:
systemRegisterName = "ctr_el0";
break;
case 0b1101101000010000:
systemRegisterName = "nzcv";
break;
case 0b1101101000010001:
systemRegisterName = "daif";
break;
case 0b1101101000100000:
systemRegisterName = "fpcr";
break;
case 0b1101101000100001:
systemRegisterName = "fpsr";
break;
case 0b1101111010000010:
systemRegisterName = "tpidr_el0";
break;
case 0b1101111010000011:
systemRegisterName = "tpidrr0_el0";
break;
}
if (systemRegisterName) {
appendString(" ; ");
appendString(systemRegisterName);
}
return m_formatBuffer;
}
const char* const A64DOpcodeHint::s_opNames[32] = {
"nop", "yield", "wfe", "wfi", "sev", "sevl", 0, "xpaclri",
"pacia1716", 0, "pacib1716", 0, "autia1716", 0, "autib1716", 0,
0, 0, 0, 0, 0, 0, 0, 0,
"paciaz", "paciasp", "pacibz", "pacibsp", "autiaz", "autiasp", "autibz", "autibsp"
};
const char* A64DOpcodeHint::format()
{
appendInstructionName(opName());
if (immediate7() >= 32 || !s_opNames[immediate7()])
appendUnsignedImmediate(immediate7());
return m_formatBuffer;
}
const char* A64DOpcodeHint::opName()
{
const char* opName = (immediate7() < 32 ? s_opNames[immediate7()] : 0);
if (!opName)
return "hint";
return opName;
}
const char* const A64DOpcodeSystemSync::s_opNames[8] = {
0, 0, "clrex", 0, "dsb", "dmb", "isb", 0
};
const char* const A64DOpcodeSystemSync::s_optionNames[16] = {
0, "oshld", "oshst", "osh", 0, "nshld", "nshst", "nsh",
0, "ishld", "ishst", "ish", 0, "ld", "st", "sy"
};
const char* A64DOpcodeSystemSync::format()
{
const char* thisOpName = opName();
if (!thisOpName)
return A64DOpcode::format();
appendInstructionName(thisOpName);
if (op2() & 0x2) {
if (crM() != 0xf) {
appendCharacter('#');
appendUnsignedImmediate(crM());
}
} else {
const char* thisOption = option();
if (thisOption)
appendString(thisOption);
else
appendUnsignedImmediate(crM());
}
return m_formatBuffer;
}
const char* const A64DOpcodeLoadStoreExclusive::s_opNames[64] = {
"stxrb", "stlxrb", 0, 0, "ldxrb", "ldaxrb", 0, 0,
0, "stlrb", 0, 0, 0, "ldarb", 0, 0,
"stxrh", "stlxrh", 0, 0, "ldxrh", "ldaxrh", 0, 0,
0, "stlrh", 0, 0, 0, "ldarh", 0, 0,
"stxr", "stlxr", "stxp", "stlxp", "ldxr", "ldaxr", "ldxp", "ldaxp",
0, "stlr", 0, 0, 0, "ldar", 0, 0,
"stxr", "stlxr", "stxp", "stlxp", "ldxr", "ldaxr", "ldxp", "ldaxp",
0, "stlr", 0, 0, 0, "ldar", 0, 0
};
const char* A64DOpcodeLoadStoreExclusive::format()
{
if (o2() && !o1() && !o0())
return A64DOpcode::format();
if (o2() && o1())
return A64DOpcode::format();
if ((size() < 2) && o1())
return A64DOpcode::format();
if (loadBit() && (rs() != 0x1f))
return A64DOpcode::format();
if (!isPairOp() && (rt2() != 0x1f))
return A64DOpcode::format();
const char* thisOpName = opName();
if (!thisOpName)
return A64DOpcode::format();
appendInstructionName(thisOpName);
if (!loadBit()) {
appendZROrRegisterName(rs(), size() == 0x3);
appendSeparator();
}
appendZROrRegisterName(rt(), size() == 0x3);
appendSeparator();
if (isPairOp()) {
appendZROrRegisterName(rt2(), size() == 0x3);
appendSeparator();
}
appendCharacter('[');
appendSPOrRegisterName(rn());
appendCharacter(']');
return m_formatBuffer;
}
// A zero in an entry of the table means the instruction is Unallocated
const char* const A64DOpcodeLoadStore::s_opNames[32] = {
"strb", "ldrb", "ldrsb", "ldrsb", "str", "ldr", "str", "ldr",
"strh", "ldrh", "ldrsh", "ldrsh", "str", "ldr", 0, 0,
"str", "ldr", "ldrsw", 0, "str", "ldr", 0, 0,
"str", "ldr", 0, 0, "str", "ldr", 0, 0
};
// A zero in an entry of the table means the instruction is Unallocated
const char* const A64DOpcodeLoadStoreImmediate::s_unprivilegedOpNames[32] = {
"sttrb", "ldtrb", "ldtrsb", "ldtrsb", 0, 0, 0, 0,
"sttrh", "ldtrh", "ldtrsh", "ldtrsh", 0, 0, 0, 0,
"sttr", "ldtr", "ldtrsw", 0, 0, 0, 0, 0,
"sttr", "ldtr", 0, 0, 0, 0, 0, 0
};
// A zero in an entry of the table means the instruction is Unallocated
const char* const A64DOpcodeLoadStoreImmediate::s_unscaledOpNames[32] = {
"sturb", "ldurb", "ldursb", "ldursb", "stur", "ldur", "stur", "ldur",
"sturh", "ldurh", "ldursh", "ldursh", "stur", "ldur", 0, 0,
"stur", "ldur", "ldursw", 0, "stur", "ldur", 0, 0,
"stur", "ldur", "prfum", 0, "stur", "ldur", 0, 0
};
const char* A64DOpcodeLoadStoreImmediate::format()
{
const char* thisOpName;
if (type() & 0x1)
thisOpName = opName();
else if (!type())
thisOpName = unscaledOpName();
else
thisOpName = unprivilegedOpName();
if (!thisOpName)
return A64DOpcode::format();
appendInstructionName(thisOpName);
if (vBit())
appendFPRegisterName(rt(), size());
else if (!opc())
appendZROrRegisterName(rt(), is64BitRT());
else
appendRegisterName(rt(), is64BitRT());
appendSeparator();
appendCharacter('[');
appendSPOrRegisterName(rn());
switch (type()) {
case 0: // Unscaled Immediate
if (immediate9()) {
appendSeparator();
appendSignedImmediate(immediate9());
}
appendCharacter(']');
break;
case 1: // Immediate Post-Indexed
appendCharacter(']');
if (immediate9()) {
appendSeparator();
appendSignedImmediate(immediate9());
}
break;
case 2: // Unprivileged
if (immediate9()) {
appendSeparator();
appendSignedImmediate(immediate9());
}
appendCharacter(']');
break;
case 3: // Immediate Pre-Indexed
if (immediate9()) {
appendSeparator();
appendSignedImmediate(immediate9());
}
appendCharacter(']');
appendCharacter('!');
break;
}
return m_formatBuffer;
}
const char* A64DOpcodeLoadStoreRegisterOffset::format()
{
const char* thisOpName = opName();
if (!thisOpName)
return A64DOpcode::format();
if (!(option() & 0x2))
return A64DOpcode::format();
appendInstructionName(thisOpName);
unsigned scale;
if (vBit()) {
appendFPRegisterName(rt(), size());
scale = ((opc() & 2)<<1) | size();
} else {
if (!opc())
appendZROrRegisterName(rt(), is64BitRT());
else
appendRegisterName(rt(), is64BitRT());
scale = size();
}
appendSeparator();
appendCharacter('[');
appendSPOrRegisterName(rn());
if (rm() != 31) {
appendSeparator();
appendRegisterName(rm(), (option() & 0x3) == 0x3);
unsigned shift = sBit() ? scale : 0;
if (option() == 0x3) {
if (shift) {
appendSeparator();
appendString("lsl ");
appendUnsignedImmediate(shift);
}
} else {
appendSeparator();
appendString(optionName());
if (shift)
appendUnsignedImmediate(shift);
}
}
appendCharacter(']');
return m_formatBuffer;
}
const char* const A64DOpcodeLoadStoreAuthenticated::s_opNames[2] = {
"ldraa", "ldrab"
};
const char* A64DOpcodeLoadStoreAuthenticated::format()
{
appendInstructionName(opName());
appendRegisterName(rt());
appendSeparator();
appendCharacter('[');
appendSPOrRegisterName(rn());
if (wBit() || immediate10()) {
appendSeparator();
appendSignedImmediate(immediate10() << size());
}
appendCharacter(']');
if (wBit())
appendCharacter('!');
return m_formatBuffer;
}
const char* A64DOpcodeLoadStoreRegisterPair::opName()
{
if (!vBit() && lBit() && size() == 0x1)
return "ldpsw";
if (lBit())
return "ldp";
return "stp";
}
const char* A64DOpcodeLoadStoreRegisterPair::format()
{
const char* thisOpName = opName();
if (size() == 0x3)
return A64DOpcode::format();
if ((offsetMode() < 0x1) || (offsetMode() > 0x3))
return A64DOpcode::format();
if ((offsetMode() == 0x1) && !vBit() && !lBit())
return A64DOpcode::format();
appendInstructionName(thisOpName);
unsigned offsetShift;
if (vBit()) {
appendFPRegisterName(rt(), size());
appendSeparator();
appendFPRegisterName(rt2(), size());
offsetShift = size() + 2;
} else {
if (!lBit())
appendZROrRegisterName(rt(), is64Bit());
else
appendRegisterName(rt(), is64Bit());
appendSeparator();
if (!lBit())
appendZROrRegisterName(rt2(), is64Bit());
else
appendRegisterName(rt2(), is64Bit());
offsetShift = (size() >> 1) + 2;
}
appendSeparator();
appendCharacter('[');
appendSPOrRegisterName(rn());
int offset = immediate7() << offsetShift;
if (offsetMode() == 1) {
appendCharacter(']');
appendSeparator();
appendSignedImmediate(offset);
} else {
appendSeparator();
appendSignedImmediate(offset);
appendCharacter(']');
if (offsetMode() == 0x3)
appendCharacter('!');
}
return m_formatBuffer;
}
const char* A64DOpcodeLoadStoreUnsignedImmediate::format()
{
const char* thisOpName = opName();
if (!thisOpName)
return A64DOpcode::format();
appendInstructionName(thisOpName);
unsigned scale;
if (vBit()) {
appendFPRegisterName(rt(), size());
scale = ((opc() & 2)<<1) | size();
} else {
if (!opc())
appendZROrRegisterName(rt(), is64BitRT());
else
appendRegisterName(rt(), is64BitRT());
scale = size();
}
appendSeparator();
appendCharacter('[');
appendSPOrRegisterName(rn());
if (immediate12()) {
appendSeparator();
appendUnsignedImmediate(immediate12() << scale);
}
appendCharacter(']');
return m_formatBuffer;
}
// A zero in an entry of the table means the instruction is Unallocated
const char* const A64DOpcodeLogical::s_opNames[8] = {
"and", "bic", "orr", "orn", "eor", "eon", "ands", "bics"
};
const char* A64DOpcodeLogicalShiftedRegister::format()
{
if (!is64Bit() && immediate6() & 0x20)
return A64DOpcode::format();
if (isTst())
appendInstructionName("tst");
else {
if (isMov())
appendInstructionName(nBit() ? "mvn" : "mov");
else
appendInstructionName(opName(opNumber()));
appendZROrRegisterName(rd(), is64Bit());
appendSeparator();
}
if (!isMov()) {
appendZROrRegisterName(rn(), is64Bit());
appendSeparator();
}
appendZROrRegisterName(rm(), is64Bit());
if (immediate6()) {
appendSeparator();
appendShiftType(shift());
appendUnsignedImmediate(immediate6());
}
return m_formatBuffer;
}
static unsigned highestBitSet(unsigned value)
{
unsigned result = 0;
while (value >>= 1)
result++;
return result;
}
static uint64_t rotateRight(uint64_t value, unsigned width, unsigned shift)
{
uint64_t result = value;
if (shift)
result = (value >> (shift % width)) | (value << (width - shift));
return result;
}
static uint64_t replicate(uint64_t value, unsigned width)
{
uint64_t result = 0;
for (unsigned totalBits = 0; totalBits < 64; totalBits += width)
result = (result << width) | value;
return result;
}
const char* A64DOpcodeLogicalImmediate::format()
{
if (!is64Bit() && nBit())
return A64DOpcode::format();
unsigned len = highestBitSet(nBit() << 6 | (immediateS() ^ 0x3f));
unsigned levels = (1 << len) - 1; // len number of 1 bits starting at LSB
if ((immediateS() & levels) == levels)
return A64DOpcode::format();
unsigned r = immediateR() & levels;
unsigned s = immediateS() & levels;
unsigned eSize = 1 << len;
uint64_t pattern = rotateRight((1ull << (s + 1)) - 1, eSize, r);
uint64_t immediate = replicate(pattern, eSize);
if (!is64Bit())
immediate &= 0xffffffffull;
if (isTst())
appendInstructionName("tst");
else {
if (isMov())
appendInstructionName("mov");
else
appendInstructionName(opName(opNumber()));
appendRegisterName(rd(), is64Bit());
appendSeparator();
}
if (!isMov()) {
appendRegisterName(rn(), is64Bit());
appendSeparator();
}
appendUnsignedImmediate64(immediate);
return m_formatBuffer;
}
const char* const A64DOpcodeMoveWide::s_opNames[4] = { "movn", 0, "movz", "movk" };
const char* A64DOpcodeMoveWide::format()
{
if (opc() == 1)
return A64DOpcode::format();
if (!is64Bit() && hw() >= 2)
return A64DOpcode::format();
if (!opc() && (!immediate16() || !hw()) && (is64Bit() || immediate16() != 0xffff)) {
// MOV pseudo op for MOVN
appendInstructionName("mov");
appendRegisterName(rd(), is64Bit());
appendSeparator();
if (is64Bit()) {
int64_t amount = immediate16() << (hw() * 16);
amount = ~amount;
appendSignedImmediate64(amount);
} else {
int32_t amount = immediate16() << (hw() * 16);
amount = ~amount;
appendSignedImmediate(amount);
}
} else {
appendInstructionName(opName());
appendRegisterName(rd(), is64Bit());
appendSeparator();
appendUnsignedHexImmediate(immediate16());
if (hw()) {
appendSeparator();
appendShiftAmount(hw());
}
}
return m_formatBuffer;
}
const char* A64DOpcodeTestAndBranchImmediate::format()
{
appendInstructionName(opBit() ? "tbnz" : "tbz");
appendRegisterName(rt());
appendSeparator();
appendUnsignedImmediate(bitNumber());
appendSeparator();
appendPCRelativeOffset(m_currentPC, static_cast<int32_t>(immediate14()));
return m_formatBuffer;
}
const char* A64DOpcodeUnconditionalBranchImmediate::format()
{
appendInstructionName(op() ? "bl" : "b");
appendPCRelativeOffset(m_currentPC, static_cast<int32_t>(immediate26()));
return m_formatBuffer;
}
const char* const A64DOpcodeUnconditionalBranchRegister::s_opNames[8] = { "br", "blr", "ret", "", "eret", "drps", "", "" };
const char* const A64DOpcodeUnconditionalBranchRegister::s_AuthOpNames[20] = {
"braaz", "brabz", "blraaz", "blrabz", "retaa", "retab", 0, 0,
"eretaa", "eretab", 0, 0, 0, 0, 0, 0,
"braa", "brab", "blraa", "blrab"
};
const char* A64DOpcodeUnconditionalBranchRegister::authOpName()
{
unsigned opCode = authOpCode();
if (opCode >= 20)
return 0;
return s_AuthOpNames[opCode];
}
const char* A64DOpcodeUnconditionalBranchRegister::format()
{
unsigned opcValue = opc();
if (op2() == 0x1f && (op3() & 0x3e) == 0x2) {
const char* opName = authOpName();
if (!opName)
return A64DOpcode::format();
if (rn() != 0x1f && (opcValue == 0x2 || opcValue == 0x4))
return A64DOpcode::format();
appendInstructionName(opName);
if ((opcValue & 0x7) <= 0x1)
appendRegisterName(rn());
if (opcValue & 0x8) {
appendSeparator();
appendRegisterName(rm());
}
return m_formatBuffer;
}
if (opcValue == 3 || opcValue > 5)
return A64DOpcode::format();
if (((opcValue & 0xe) == 0x4) && rn() != 0x1f)
return A64DOpcode::format();
appendInstructionName(opName());
if (opcValue <= 2)
appendRegisterName(rn());
return m_formatBuffer;
}
} } // namespace JSC::ARM64Disassembler
#endif // USE(ARM64_DISASSEMBLER)