From 5ce86d50a134cbc6a6bc0cea248125113204b0d3 Mon Sep 17 00:00:00 2001 From: Tyler Wilding Date: Sat, 11 Apr 2026 15:34:39 -0400 Subject: [PATCH] goalc/arm: adds and subtracts --- goalc/emitter/IGenARM64.cpp | 155 +++++++++++++++++++++++++----------- goalc/emitter/Instruction.h | 31 +++++++- 2 files changed, 138 insertions(+), 48 deletions(-) diff --git a/goalc/emitter/IGenARM64.cpp b/goalc/emitter/IGenARM64.cpp index 6a245f4d00..846e87acbb 100644 --- a/goalc/emitter/IGenARM64.cpp +++ b/goalc/emitter/IGenARM64.cpp @@ -652,83 +652,146 @@ InstructionARM64 jmp_r64(Register reg) { // INTEGER MATH //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -// NOTE: ARM can actually handle 12-bit immediate values, so if it's actually worth it, we -// could leverage these instructions for more than just 8-bit values InstructionARM64 sub_gpr64_imm8s(Register reg, int64_t imm) { - // You cannot subtract or add with a negative immediate in ARM - // therefore depending on the value of the immediate, we use a different instruction - ASSERT(reg.is_gpr(instr_set)); - if (imm < 0) { - return add_gpr64_imm8s(reg, std::abs(imm)); - } - // https://www.scs.stanford.edu/~zyedidia/arm64/sub_addsub_imm.html - // - SUB , , #imm12 {, LSL #12} - // - using a shift of 0 here (last bit in the base) - return InstructionARM64(Base(0b1101000100, 10), Imm12(imm), Rn(reg.id()), Rd(reg.id())); + return sub_gpr64_imm(reg, imm); } -// NOTE: ARM can actually handle 12-bit immediate values, so if it's actually worth it, we -// could leverage these instructions for more than just 8-bit values InstructionARM64 add_gpr64_imm8s(Register reg, int64_t imm) { - // You cannot subtract or add with a negative immediate in ARM - // therefore depending on the value of the immediate, we use a different instruction - ASSERT(reg.is_gpr(instr_set)); - if (imm < 0) { - return sub_gpr64_imm8s(reg, abs(imm)); - } - // https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_imm.html - // ADD , , #{, } - return InstructionARM64(Base(0b1001000100, 10), Imm12(imm), Rn(reg.id()), Rd(reg.id())); + return add_gpr64_imm(reg, imm); } InstructionARM64 sub_gpr64_imm32s(Register reg, int64_t imm) { - // ARM64 does not support this kind of single-instruction - ASSERT_MSG(false, "sub_gpr64_imm32s not supported on ARM64"); - return InstructionARM64(0b0); + return sub_gpr64_imm(reg, imm); } InstructionARM64 add_gpr64_imm32s(Register reg, int64_t imm) { - // ARM64 does not support this kind of single-instruction - ASSERT_MSG(false, "sub_gpr64_imm32s not supported on ARM64"); - return InstructionARM64(0b0); + return add_gpr64_imm(reg, imm); +} + +// Checks whether or not an immediate can be represented in 12 unsigned bits, either: +// - plain [0-4095] immediate +// - imm << 12 (some multiple of 4096) +std::tuple can_encode_single_imm12(u64 imm) { + if (imm < 4096) { + return {true, static_cast(imm), false}; + } + if ((imm & 0xFFF) == 0) { // divisible by 4096 + uint32_t upper = imm >> 12; + if (upper < 4096) { + return {true, static_cast(upper), true}; + } + } + return {false, 0, false}; +} + +// Given a larger than u12 immediate, decompose it into multiple (shifted or not) +// immediates that can be used to emit multiple instructions to produce the desired outcome +std::vector> decompose_into_imm12_chunks(u64 imm) { + std ::vector> result; + u32 upper = imm >> 12; + while (upper > 0) { + u16 chunk = (upper > 4095) ? 4095 : static_cast(upper); + result.emplace_back(chunk, true); + upper -= chunk; + } + + u16 lower = imm & 0xFFF; + if (lower > 0) { + result.emplace_back(lower, false); + } + return result; } InstructionARM64 add_gpr64_imm(Register reg, int64_t imm) { - ASSERT_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + ASSERT(reg.is_gpr(instr_set)); + if (imm < 0) { + sub_gpr64_imm(reg, std::abs(imm)); + } + // Check to see if we can represent this subtraction in a single instruction + // if not, then we need to emit multiple partial instructions + const auto [is_single_instr, imm12, needs_shift] = can_encode_single_imm12(imm); + if (is_single_instr) { + // https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_imm.html + // ADD , , #{, } + return InstructionARM64(Base(0b100100010, 9), Sh(needs_shift ? 1 : 0), Imm12(imm12), + Rd(reg.id()), Rn(reg.id())); + } else { + const auto chunks = decompose_into_imm12_chunks(imm); + std::vector instrs; + for (const auto& [_imm12, _needs_shift] : chunks) { + // https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_imm.html + // ADD , , #{, } + instrs.emplace_back(InstructionARM64(Base(0b100100010, 9), Sh(_needs_shift ? 1 : 0), + Imm12(_imm12), Rd(reg.id()), Rn(reg.id()))); + } + return InstructionARM64(instrs); + } } InstructionARM64 sub_gpr64_imm(Register reg, int64_t imm) { - ASSERT_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + ASSERT(reg.is_gpr(instr_set)); + if (imm < 0) { + add_gpr64_imm(reg, std::abs(imm)); + } + // Check to see if we can represent this subtraction in a single instruction + // if not, then we need to emit multiple partial instructions + const auto [is_single_instr, imm12, needs_shift] = can_encode_single_imm12(imm); + if (is_single_instr) { + // https://www.scs.stanford.edu/~zyedidia/arm64/sub_addsub_imm.html + // SUB , , #{, } + return InstructionARM64(Base(0b110100010, 9), Sh(needs_shift ? 1 : 0), Imm12(imm12), + Rd(reg.id()), Rn(reg.id())); + } else { + const auto chunks = decompose_into_imm12_chunks(imm); + std::vector instrs; + for (const auto& [_imm12, _needs_shift] : chunks) { + // https://www.scs.stanford.edu/~zyedidia/arm64/sub_addsub_imm.html + // SUB , , #{, } + instrs.emplace_back(InstructionARM64(Base(0b110100010, 9), Sh(_needs_shift ? 1 : 0), + Imm12(_imm12), Rd(reg.id()), Rn(reg.id()))); + } + return InstructionARM64(instrs); + } } InstructionARM64 add_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 , , {, #} + return InstructionARM64(Base(0b10001011000, 11), Rd(dst.id()), Imm6(0), Rn(dst.id()), + Rm(src.id())); } InstructionARM64 sub_gpr64_gpr64(Register dst, Register src) { - ASSERT_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + // https://www.scs.stanford.edu/~zyedidia/arm64/sub_addsub_shift.html + // SUB , , {, #} + return InstructionARM64(Base(0b11001011000, 11), Rd(dst.id()), Imm6(0), Rn(dst.id()), + Rm(src.id())); } InstructionARM64 imul_gpr32_gpr32(Register dst, Register src) { - ASSERT_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + // https://www.scs.stanford.edu/~zyedidia/arm64/mul_madd.html + // MUL , , + return InstructionARM64(Base(0b0001101100000000011111, 22), Rd(dst.id()), Rn(dst.id()), + Rm(src.id())); } InstructionARM64 imul_gpr64_gpr64(Register dst, Register src) { - ASSERT_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + // https://www.scs.stanford.edu/~zyedidia/arm64/mul_madd.html + // MUL , , + return InstructionARM64(Base(0b1001101100000000011111, 22), Rd(dst.id()), Rn(dst.id()), + Rm(src.id())); } InstructionARM64 idiv_gpr32(Register reg) { + // divides on x86 are annoying, its one of many that involve hard-coded register src/destinations, + // deal with it last ASSERT_MSG(false, "not yet implemented"); return InstructionARM64(0b0); } InstructionARM64 unsigned_div_gpr32(Register reg) { + // divides on x86 are annoying, its one of many that involve hard-coded register src/destinations, + // deal with it last ASSERT_MSG(false, "not yet implemented"); return InstructionARM64(0b0); } @@ -743,13 +806,15 @@ InstructionARM64 cdq() { } InstructionARM64 movsx_r64_r32(Register dst, Register src) { - ASSERT_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + // https://www.scs.stanford.edu/~zyedidia/arm64/sxtw_sbfm.html + // SXTW , + return InstructionARM64(Base(0b1001001101000000011111, 22), Rd(dst.id()), Rn(src.id())); } InstructionARM64 cmp_gpr64_gpr64(Register a, Register b) { - ASSERT_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + // https://www.scs.stanford.edu/~zyedidia/arm64/cmp_subs_addsub_ext.html + // CMP , {, {#}} + return InstructionARM64(Base(0b11101011001000000000000000011111, 32), Rn(a.id()), Rn(b.id())); } //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/goalc/emitter/Instruction.h b/goalc/emitter/Instruction.h index 03043e640f..699bf763aa 100644 --- a/goalc/emitter/Instruction.h +++ b/goalc/emitter/Instruction.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include "common/common_types.h" @@ -44,19 +45,33 @@ constexpr u32 Base(u32 value, u32 width) { return value << (32 - width); } +constexpr Field Sh(u32 x) { + ASSERT(x >= 0 && x <= ((2 ^ 1) - 1)); + return Field{(x & 1) << 22}; +} + +constexpr Field Shift(u32 x) { + ASSERT(x >= 0 && x <= ((2 ^ 2) - 1)); + return Field{(x & 1) << 22}; +} + constexpr Field Rd(u32 x) { + ASSERT(x >= 0 && x <= ((2 ^ 5) - 1)); return Field{(x & 31) << 0}; } constexpr Field Rt(u32 x) { + ASSERT(x >= 0 && x <= ((2 ^ 5) - 1)); return Field{(x & 31) << 0}; } constexpr Field Rn(u32 x) { + ASSERT(x >= 0 && x <= ((2 ^ 5) - 1)); return Field{(x & 31) << 5}; } constexpr Field Rm(u32 x) { + ASSERT(x >= 0 && x <= ((2 ^ 5) - 1)); return Field{(x & 31) << 16}; } @@ -111,10 +126,10 @@ struct InstructionARM64 : InstructionImpl { // // To do so, the instruction can optionally include multiple encodings // all of which are emitted at once. - static constexpr int kMaxInstrs = 4; + static constexpr int kMaxInstrs = 64; u32 encodings[kMaxInstrs]{}; - uint8_t count = 0; + u8 count = 0; InstructionARM64() = delete; @@ -131,7 +146,7 @@ struct InstructionARM64 : InstructionImpl { constexpr InstructionARM64(const Instrs&... instrs) requires(std::is_same_v && ...) { - uint8_t idx = 0; + u8 idx = 0; auto append = [&](const InstructionARM64& i) { for (uint8_t j = 0; j < i.count; ++j) { encodings[idx++] = i.encodings[j]; @@ -141,6 +156,16 @@ struct InstructionARM64 : InstructionImpl { count = idx; } + InstructionARM64(std::span instrs) { + u8 idx = 0; + for (const auto& i : instrs) { + for (uint8_t j = 0; j < i.count; ++j) { + encodings[idx++] = i.encodings[j]; + } + } + count = idx; + } + uint8_t emit(uint8_t* buffer) const { if (count == 1 && encodings[0] == 0) { return 0;