goalc/arm: another batch of instructions

This commit is contained in:
Tyler Wilding
2026-04-07 23:40:50 -04:00
parent 352f1538b8
commit 2126e47381
9 changed files with 219 additions and 98 deletions
+6 -3
View File
@@ -712,15 +712,18 @@ void IR_IntegerMath::do_codegen_x86(emitter::ObjectGenerator* gen,
ASSERT(!m_arg);
break;
case IntegerMathKind::SHLV_64:
gen->add_instr(IGen::shl_gpr64_cl(*gen, get_reg(m_dest, allocs, irec)), irec);
gen->add_instr(IGen::shl_gpr64_reg(*gen, get_reg(m_dest, allocs, irec)), irec);
// TODO ARM - x86 forces you to use CL, which is dumb, but the register allocator
// has that logic baked in somewhere
// ARM has no such constraint, so we should be able to use any register for the shift amount
ASSERT(get_reg(m_arg, allocs, irec) == emitter::RCX);
break;
case IntegerMathKind::SHRV_64:
gen->add_instr(IGen::shr_gpr64_cl(*gen, get_reg(m_dest, allocs, irec)), irec);
gen->add_instr(IGen::shr_gpr64_reg(*gen, get_reg(m_dest, allocs, irec)), irec);
ASSERT(get_reg(m_arg, allocs, irec) == emitter::RCX);
break;
case IntegerMathKind::SARV_64:
gen->add_instr(IGen::sar_gpr64_cl(*gen, get_reg(m_dest, allocs, irec)), irec);
gen->add_instr(IGen::sar_gpr64_reg(*gen, get_reg(m_dest, allocs, irec)), irec);
ASSERT(get_reg(m_arg, allocs, irec) == emitter::RCX);
break;
case IntegerMathKind::SHL_64:
+4 -4
View File
@@ -693,12 +693,12 @@ Instruction not_gpr64(const ObjectGenerator& gen, Register reg) {
IGEN_DISPATCH(not_gpr64, reg);
}
Instruction shl_gpr64_cl(const ObjectGenerator& gen, Register reg) {
IGEN_DISPATCH(shl_gpr64_cl, reg);
Instruction shl_gpr64_reg(const ObjectGenerator& gen, Register reg) {
IGEN_DISPATCH(shl_gpr64_reg, reg);
}
Instruction shr_gpr64_cl(const ObjectGenerator& gen, Register reg) {
IGEN_DISPATCH(shr_gpr64_cl, reg);
Instruction shr_gpr64_reg(const ObjectGenerator& gen, Register reg) {
IGEN_DISPATCH(shr_gpr64_reg, reg);
}
Instruction sar_gpr64_cl(const ObjectGenerator& gen, Register reg) {
+8 -6
View File
@@ -618,19 +618,21 @@ Instruction not_gpr64(const ObjectGenerator& gen, Register reg);
//;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
/*!
* Shift 64-bit gpr left by CL register
* Shift 64-bit gpr left by a shift amount in a register (ie. forced to be CL register on x86)
*/
Instruction shl_gpr64_cl(const ObjectGenerator& gen, Register reg);
Instruction shl_gpr64_reg(const ObjectGenerator& gen, Register reg, Register shift_reg);
/*!
* Shift 64-bit gpr right (logical) by CL register
* Shift 64-bit gpr right (logical) by a shift amount in a register (ie. forced to be CL register on
* x86)
*/
Instruction shr_gpr64_cl(const ObjectGenerator& gen, Register reg);
Instruction shr_gpr64_reg(const ObjectGenerator& gen, Register reg, Register shift_reg);
/*!
* Shift 64-bit gpr right (arithmetic) by CL register
* Shift 64-bit gpr right (arithmetic) a shift amount in a register (ie. forced to be CL register on
* x86)
*/
Instruction sar_gpr64_cl(const ObjectGenerator& gen, Register reg);
Instruction sar_gpr64_reg(const ObjectGenerator& gen, Register reg, Register shift_reg);
/*!
* Shift 64-ptr left (logical) by the constant shift amount "sa".
+78 -31
View File
@@ -757,57 +757,93 @@ InstructionARM64 cmp_gpr64_gpr64(Register a, Register b) {
//;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
InstructionARM64 or_gpr64_gpr64(Register dst, Register src) {
ASSERT_MSG(false, "not yet implemented");
return InstructionARM64(0b0);
// https://www.scs.stanford.edu/~zyedidia/arm64/orr_log_shift.html
// ORR <Xd>, <Xn>, <Xm>{, <shift> #<amount>}
ASSERT(dst.is_gpr(instr_set));
ASSERT(src.is_gpr(instr_set));
return InstructionARM64(Base(0b10101010000, 11), Rd(dst.id()), Rn(dst.id()), Rm(src.id()));
}
InstructionARM64 and_gpr64_gpr64(Register dst, Register src) {
ASSERT_MSG(false, "not yet implemented");
return InstructionARM64(0b0);
// https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_shift.html
// ADD <Xd>, <Xn>, <Xm>{, <shift> #<amount>}
ASSERT(dst.is_gpr(instr_set));
ASSERT(src.is_gpr(instr_set));
return InstructionARM64(Base(0b10001011000, 11), Rd(dst.id()), Rn(dst.id()), Rm(src.id()));
}
InstructionARM64 xor_gpr64_gpr64(Register dst, Register src) {
ASSERT_MSG(false, "not yet implemented");
return InstructionARM64(0b0);
// https://www.scs.stanford.edu/~zyedidia/arm64/eor_log_shift.html
// EOR <Xd>, <Xn>, <Xm>{, <shift> #<amount>}
ASSERT(dst.is_gpr(instr_set));
ASSERT(src.is_gpr(instr_set));
return InstructionARM64(Base(0b11001010000, 11), Rd(dst.id()), Rn(dst.id()), Rm(src.id()));
}
InstructionARM64 not_gpr64(Register reg) {
ASSERT_MSG(false, "not yet implemented");
return InstructionARM64(0b0);
// https://www.scs.stanford.edu/~zyedidia/arm64/mvn_orn_log_shift.html
// MVN <Xd>, <Xm>{, <shift> #<amount>}
// ==
// ORN <Xd>, XZR, <Xm>{, <shift> #<amount>}
ASSERT(reg.is_gpr(instr_set));
return InstructionARM64(Base(0b101010100010000000000011111, 27), Rd(reg.id()), Rm(reg.id()));
}
//;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
// SHIFTS
//;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
InstructionARM64 shl_gpr64_cl(Register reg) {
ASSERT_MSG(false, "not yet implemented");
return InstructionARM64(0b0);
InstructionARM64 shl_gpr64_reg(Register reg, Register shift_reg) {
// https://www.scs.stanford.edu/~zyedidia/arm64/lsl_lslv.html
// LSL <Xd>, <Xn>, <Xm>
ASSERT(reg.is_gpr(instr_set));
ASSERT(shift_reg.is_gpr(instr_set));
return InstructionARM64(Base(0b1001101011000000001000, 22), Rd(reg.id()), Rn(reg.id()),
Rm(shift_reg.id()));
}
InstructionARM64 shr_gpr64_cl(Register reg) {
ASSERT_MSG(false, "not yet implemented");
return InstructionARM64(0b0);
InstructionARM64 shr_gpr64_reg(Register reg, Register shift_reg) {
// https://www.scs.stanford.edu/~zyedidia/arm64/lsr_lsrv.html
// LSR <Xd>, <Xn>, <Xm>
ASSERT(reg.is_gpr(instr_set));
ASSERT(shift_reg.is_gpr(instr_set));
return InstructionARM64(Base(0b1001101011000000001001, 22), Rd(reg.id()), Rn(reg.id()),
Rm(shift_reg.id()));
}
InstructionARM64 sar_gpr64_cl(Register reg) {
ASSERT_MSG(false, "not yet implemented");
return InstructionARM64(0b0);
InstructionARM64 sar_gpr64_reg(Register reg, Register shift_reg) {
// https://www.scs.stanford.edu/~zyedidia/arm64/asr_asrv.html
// ASR <Xd>, <Xn>, <Xm>
ASSERT(reg.is_gpr(instr_set));
ASSERT(shift_reg.is_gpr(instr_set));
return InstructionARM64(Base(0b1001101011000000001010, 22), Rd(reg.id()), Rn(reg.id()),
Rm(shift_reg.id()));
}
InstructionARM64 shl_gpr64_u8(Register reg, uint8_t sa) {
ASSERT_MSG(false, "not yet implemented");
return InstructionARM64(0b0);
// https://www.scs.stanford.edu/~zyedidia/arm64/lsl_ubfm.html
// LSL <Xd>, <Xn>, #<shift>
ASSERT(sa < 63);
ASSERT(reg.is_gpr(instr_set));
return InstructionARM64(Base(0b1101001101, 10), Rd(reg.id()), Rn(reg.id()), Immr((64 - sa) & 63),
Imms(63 - sa));
}
InstructionARM64 shr_gpr64_u8(Register reg, uint8_t sa) {
ASSERT_MSG(false, "not yet implemented");
return InstructionARM64(0b0);
// https://www.scs.stanford.edu/~zyedidia/arm64/lsr_ubfm.html
// LSR <Xd>, <Xn>, #<shift>
// sf 1 0 1 0 0 1 1 0 N
ASSERT(sa < 63);
ASSERT(reg.is_gpr(instr_set));
return InstructionARM64(Base(0b1101001101000000111111, 22), Rd(reg.id()), Rn(reg.id()), Immr(sa));
}
InstructionARM64 sar_gpr64_u8(Register reg, uint8_t sa) {
ASSERT_MSG(false, "not yet implemented");
return InstructionARM64(0b0);
// https://www.scs.stanford.edu/~zyedidia/arm64/asr_sbfm.html
// ASR <Xd>, <Xn>, #<shift>
ASSERT(sa < 63);
ASSERT(reg.is_gpr(instr_set));
return InstructionARM64(Base(0b1001001101000000111111, 22), Rd(reg.id()), Rn(reg.id()), Immr(sa));
}
//;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -946,21 +982,30 @@ InstructionARM64 null() {
/////////////////////////////
InstructionARM64 nop_vf() {
ASSERT_MSG(false, "not yet implemented");
return InstructionARM64(0b0);
// Not sure if this one was even needed for x86, but it does not really exist on ARM64
// just use a normal nop
return nop();
}
InstructionARM64 wait_vf() {
ASSERT_MSG(false, "not yet implemented");
return InstructionARM64(0b0);
// Another instruction that doesnt really map to arm64 because there is no annoying
// x87 FPU behaviour
return nop();
}
InstructionARM64 mov_vf_vf(Register dst, Register src) {
ASSERT_MSG(false, "not yet implemented");
return InstructionARM64(0b0);
// https://www.scs.stanford.edu/~zyedidia/arm64/mov_orr_advsimd_reg.html
// MOV <Vd>.<T>, <Vn>.<T>
// Q <T>
// 0 8B
// 1 16B
ASSERT(dst.is_128bit_simd(instr_set));
ASSERT(src.is_128bit_simd(instr_set));
return InstructionARM64(Base(0b0100111010100000000111, 22), Rd(dst.id()), Rn(src.id()));
}
InstructionARM64 loadvf_gpr64_plus_gpr64(Register dst, Register addr1, Register addr2) {
// https://www.scs.stanford.edu/~zyedidia/arm64/ldr_reg_fpsimd.html
ASSERT_MSG(false, "not yet implemented");
return InstructionARM64(0b0);
}
@@ -1030,8 +1075,10 @@ InstructionARM64 splat_vf(Register dst, Register src, Register::VF_ELEMENT eleme
}
InstructionARM64 xor_vf(Register dst, Register src1, Register src2) {
ASSERT_MSG(false, "not yet implemented");
return InstructionARM64(0b0);
// https://www.scs.stanford.edu/~zyedidia/arm64/eor_advsimd.html
// EOR <Vd>.<T>, <Vn>.<T>, <Vm>.<T>
return InstructionARM64(Base(0b0110111000100000000111, 22), Rn(src1.id()), Rm(src2.id()),
Rd(dst.id()));
}
InstructionARM64 sub_vf(Register dst, Register src1, Register src2) {
+9 -7
View File
@@ -477,19 +477,21 @@ InstructionARM64 not_gpr64(Register reg);
//;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
/*!
* Shift 64-bit gpr left by CL register
* Shift 64-bit gpr left by a shift amount in a register (ie. forced to be CL register on x86)
*/
InstructionARM64 shl_gpr64_cl(Register reg);
InstructionARM64 shl_gpr64_reg(Register reg, Register shift_reg);
/*!
* Shift 64-bit gpr right (logical) by CL register
* Shift 64-bit gpr right (logical) by a shift amount in a register (ie. forced to be CL register on
* x86)
*/
InstructionARM64 shr_gpr64_cl(Register reg);
InstructionARM64 shr_gpr64_reg(Register reg, Register shift_reg);
/*!
* Shift 64-bit gpr right (arithmetic) by CL register
* Shift 64-bit gpr right (arithmetic) a shift amount in a register (ie. forced to be CL register on
* x86)
*/
InstructionARM64 sar_gpr64_cl(Register reg);
InstructionARM64 sar_gpr64_reg(Register reg, Register shift_reg);
/*!
* Shift 64-ptr left (logical) by the constant shift amount "sa".
@@ -800,4 +802,4 @@ InstructionARM64 vpshufhw(Register dst, Register src, u8 imm);
InstructionARM64 vpackuswb(Register dst, Register src0, Register src1);
} // namespace ARM64
} // namespace IGen
} // namespace emitter
} // namespace emitter
+7 -4
View File
@@ -1569,21 +1569,24 @@ InstructionX86 not_gpr64(Register reg) {
return instr;
}
InstructionX86 shl_gpr64_cl(Register reg) {
InstructionX86 shl_gpr64_reg(Register reg, Register _) {
// x86 is forced to use CL
ASSERT(reg.is_gpr(instr_set));
InstructionX86 instr(0xd3);
instr.set_modrm_and_rex(4, reg.hw_id(instr_set), 3, true);
return instr;
}
InstructionX86 shr_gpr64_cl(Register reg) {
InstructionX86 shr_gpr64_reg(Register reg, Register _) {
// x86 is forced to use CL
ASSERT(reg.is_gpr(instr_set));
InstructionX86 instr(0xd3);
instr.set_modrm_and_rex(5, reg.hw_id(instr_set), 3, true);
return instr;
}
InstructionX86 sar_gpr64_cl(Register reg) {
InstructionX86 sar_gpr64_reg(Register reg, Register _) {
// x86 is forced to use CL
ASSERT(reg.is_gpr(instr_set));
InstructionX86 instr(0xd3);
instr.set_modrm_and_rex(7, reg.hw_id(instr_set), 3, true);
@@ -2447,4 +2450,4 @@ InstructionX86 vpackuswb(Register dst, Register src0, Register src1) {
}
} // namespace X86
} // namespace IGen
} // namespace emitter
} // namespace emitter
+9 -7
View File
@@ -477,19 +477,21 @@ InstructionX86 not_gpr64(Register reg);
//;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
/*!
* Shift 64-bit gpr left by CL register
* Shift 64-bit gpr left by a shift amount in a register (ie. forced to be CL register on x86)
*/
InstructionX86 shl_gpr64_cl(Register reg);
InstructionX86 shl_gpr64_reg(Register reg, Register shift_reg);
/*!
* Shift 64-bit gpr right (logical) by CL register
* Shift 64-bit gpr right (logical) by a shift amount in a register (ie. forced to be CL register on
* x86)
*/
InstructionX86 shr_gpr64_cl(Register reg);
InstructionX86 shr_gpr64_reg(Register reg, Register shift_reg);
/*!
* Shift 64-bit gpr right (arithmetic) by CL register
* Shift 64-bit gpr right (arithmetic) a shift amount in a register (ie. forced to be CL register on
* x86)
*/
InstructionX86 sar_gpr64_cl(Register reg);
InstructionX86 sar_gpr64_reg(Register reg, Register shift_reg);
/*!
* Shift 64-ptr left (logical) by the constant shift amount "sa".
@@ -800,4 +802,4 @@ InstructionX86 vpshufhw(Register dst, Register src, u8 imm);
InstructionX86 vpackuswb(Register dst, Register src0, Register src1);
} // namespace X86
} // namespace IGen
} // namespace emitter
} // namespace emitter
+57 -15
View File
@@ -61,45 +61,87 @@ constexpr Field Rm(u32 x) {
}
constexpr Field Imm6(u32 x) {
ASSERT(x >= 0 && x <= ((2 ^ 6) - 1));
return Field{(x & 0b111111) << 10};
}
constexpr Field Imm9(s32 x) {
ASSERT(x >= 0 && x <= ((2 ^ 9) - 1));
return Field{(static_cast<uint32_t>(x) & 0b111111111) << 12};
}
constexpr Field Imm12(u32 x) {
ASSERT(x >= 0 && x <= 4095);
ASSERT(x >= 0 && x <= ((2 ^ 12) - 1));
return Field{(static_cast<uint32_t>(x) & 0b111111111111) << 10};
}
constexpr Field Imms(u32 x) {
ASSERT(x >= 0 && x <= ((2 ^ 6) - 1));
return Field{(static_cast<uint32_t>(x) & 0b111111) << 10};
}
constexpr Field Immr(u32 x) {
ASSERT(x >= 0 && x <= ((2 ^ 6) - 1));
return Field{(static_cast<uint32_t>(x) & 0b111111) << 16};
}
} // namespace ARM64
struct InstructionARM64 : InstructionImpl<InstructionARM64> {
// The ARM instruction stream is a sequence of word-aligned words. Each ARM instruction is a
// single 32-bit word in that stream.
// Info:
// - https://yurichev.com/mirrors/ARMv8-A_Architecture_Reference_Manual_(Issue_A.a).pdf
// - https://www.scs.stanford.edu/~zyedidia/arm64/
// - https://armconverter.com/?lock=arm64&code=STR+X0,+[SP,+%23-8]!
u32 encoding;
// The ARM instruction stream is a sequence of word-aligned words.
// Each ARM instruction is a single 32-bit word in that stream.
//
// Some x86 instructions are not possible to represent in ARM in a single instruction
// however, in order to not have to overhaul things at the IR level,
// it feels preferably to instead allow an instruction to emit multiple instructions if needed
//
// To do so, the instruction can optionally include multiple encodings
// all of which are emitted at once.
static constexpr int kMaxInstrs = 4;
u32 encodings[kMaxInstrs]{};
uint8_t count = 0;
InstructionARM64() = delete;
// --- single instruction ---
template <typename... Fs>
constexpr InstructionARM64(uint32_t base, Fs... fields) : encoding((base | ... | fields.bits)) {
static_assert((std::is_same_v<Fs, emitter::ARM64::Field> && ...),
"All operands must be Field types");
constexpr InstructionARM64(uint32_t base, Fs... fields) {
static_assert((std::is_same_v<Fs, emitter::ARM64::Field> && ...));
encodings[0] = (base | ... | fields.bits);
count = 1;
}
// --- multi instruction (variadic) ---
template <typename... Instrs>
constexpr InstructionARM64(const Instrs&... instrs)
requires(std::is_same_v<Instrs, InstructionARM64> && ...)
{
uint8_t idx = 0;
auto append = [&](const InstructionARM64& i) {
for (uint8_t j = 0; j < i.count; ++j) {
encodings[idx++] = i.encodings[j];
}
};
(append(instrs), ...);
count = idx;
}
uint8_t emit(uint8_t* buffer) const {
if (encoding == 0) {
if (count == 1 && encodings[0] == 0) {
return 0;
}
memcpy(buffer, &encoding, 4);
return 4;
memcpy(buffer, encodings, count * 4);
return count * 4;
}
uint8_t length() const { return 4; }
uint8_t length() const {
if (count == 1 && encodings[0] == 0) {
return 0;
}
return count * 4;
}
// TODO ARM - all placeholders, no idea if this is even relevant, if not, get rid of it all
int get_imm_size() const { return 0; }
int offset_of_imm() const { return 0; }
+41 -21
View File
@@ -85,10 +85,10 @@ enum ARM64_REG : s8 {
X17, // temp, not-saved
X18, // temp, not-saved
x19, // saved TODO purpose?, R12
x20, // pp, R13
x21, // st, R14
x22, // offset, TODO purpose?, R15
X19, // saved TODO purpose?, R12
X20, // pp, R13
X21, // st, R14
X22, // offset, TODO purpose?, R15
X23, // unused, callee saved
X24, // unused, callee saved
X25, // unused, callee saved
@@ -103,22 +103,39 @@ enum ARM64_REG : s8 {
// quadword registers, equivalent to XMMs
// the convention in arm64 is the callee preserves all Q values
// at the same time though, the caller should not depend on this convention!
Q0 = 0,
Q1,
Q2,
Q3,
Q4,
Q5,
Q6,
Q7,
Q8,
Q9,
Q10,
Q11,
Q12,
Q13,
Q14,
Q15
V0 = 0,
V1,
V2,
V3,
V4,
V5,
V6,
V7,
V8,
V9,
V10,
V11,
V12,
V13,
V14,
V15,
// TODO ARM - we'll want to check at runtime if the platform has 16 V registers, or 32
V16,
V17,
V18,
V19,
V20,
V21,
V22,
V23,
V24,
V25,
V26,
V27,
V28,
V29,
V30,
V31,
};
class Register {
@@ -128,11 +145,14 @@ class Register {
// intentionally not explicit so we can use X86_REGs in place of Registers
Register(int id) : m_id(id) {}
// TODO ARM64 - this assertion isn't as useful for ARM
// since Q/V registers are not unique in terms of their id
// instead it is the instruction itself that deduces what set of registers to use
bool is_128bit_simd(emitter::InstructionSet instr_set) const {
if (instr_set == emitter::InstructionSet::X86) {
return m_id >= XMM0 && m_id <= XMM15;
} else if (instr_set == emitter::InstructionSet::ARM64) {
return m_id >= Q0 && m_id <= Q15;
return m_id >= V0 && m_id <= V31;
} else {
ASSERT_MSG(false, "is_128bit_simd: instruction set not supported");
}