#include "IGenARM64.h" #include #include "common/arm64/encoding.h" #include "common/util/Assert.h" #include "goalc/emitter/IGen.h" #include "goalc/emitter/Instruction.h" #include "goalc/emitter/InstructionSet.h" #include "goalc/emitter/Register.h" #include "fmt/base.h" // https://armconverter.com/?code=ret // https://developer.arm.com/documentation/ddi0487/latest namespace emitter { namespace IGen { namespace ARM64 { const auto instr_set = emitter::InstructionSet::ARM64; using namespace emitter::ARM64; // Utility functions (not public facing instructions) // used to encode instructions and match the same API // 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 u64 upper = imm >> 12; if (upper < 4096) { return {true, static_cast(upper), true}; } } return {false, 0, false}; } // TODO - imm12 decomposition produces way too many chunks for what will be common operations, // update the instructions that we can to instead use the movz/movk pattern // // Decompose a 64-bit immediate into 16-bit chunks suitable for movz/movk. // Returns {chunk, shift} pairs where shift is one of 0,16,32,48. std::vector> decompose_into_imm16_chunks(u64 imm) { std::vector> result; if (imm == 0) { result.emplace_back(0, 0); return result; } for (u8 shift = 0; shift <= 48; shift += 16) { u16 chunk = static_cast((imm >> shift) & 0xFFFF); if (chunk != 0 || result.empty()) { result.emplace_back(chunk, shift / 16); } } return result; } // 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; u64 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; } std::vector construct_multiple_imm12_adds(int64_t imm, u32 register_id) { 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(register_id), Rn(register_id))); } return instrs; } std::vector construct_multiple_imm12_subs(int64_t imm, u32 register_id) { 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(register_id), Rn(register_id))); } return instrs; } //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; // MOVES //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; InstructionARM64 mov_gpr64_gpr64(Register dst, Register src) { ASSERT(dst.is_gpr(instr_set)); ASSERT(src.is_gpr(instr_set)); if (dst == SP || src == SP) { // use ADD with a zero immediate because ORR treats register 31 as XZR instead of SP // https://www.scs.stanford.edu/~zyedidia/arm64/mov_add_addsub_imm.html return InstructionARM64(Base(0b100100010, 9), Sh(0), Imm12(0), Rn(src.hw_id(instr_set)), Rd(dst.hw_id(instr_set))); } // https://www.scs.stanford.edu/~zyedidia/arm64/mov_orr_log_shift.html // MOV , return InstructionARM64(Base(0b10101010000, 11), Rm(src.hw_id(instr_set)), Rn(0b11111), Rd(dst.hw_id(instr_set)), Imm6(0)); } std::vector mov_gpr64_u64_instrs(Register dst, uint64_t val) { // Cannot be done in a single instruction, must combine multiple MOVZ/MOVKs std::vector instrs; const auto reg = u32(dst.hw_id(instr_set)); auto imm_chunks = decompose_into_imm16_chunks(val); for (const auto& [imm_chunk, shift] : imm_chunks) { if (shift == 0) { // https://www.scs.stanford.edu/~zyedidia/arm64/movz.html // MOVZ , #{, LSL #/16} instrs.emplace_back(arm64::encode_movz_64(reg, imm_chunk, shift)); } else { // https://www.scs.stanford.edu/~zyedidia/arm64/movk.html // MOVK , #{, LSL #/16} instrs.emplace_back(arm64::encode_movk_64(reg, imm_chunk, shift)); } } return instrs; } InstructionARM64 mov_gpr64_u64(Register dst, uint64_t val) { return InstructionARM64(mov_gpr64_u64_instrs(dst, val)); } InstructionARM64 mov_gpr64_u32(Register dst, uint64_t val) { return mov_gpr64_u64(dst, val); } InstructionARM64 mov_gpr64_s32(Register dst, int64_t val) { // preserve sign -- but we are are simply moving the bits u64 raw_val = static_cast(val); // via int64_t → sign already there return mov_gpr64_u64(dst, raw_val); } InstructionARM64 mov_gpr32_link_imm32(Register dst, u32 initial) { ASSERT(dst.is_gpr(instr_set)); // always emit both words so the linker patch size stays fixed // https://www.scs.stanford.edu/~zyedidia/arm64/movz.html // MOVZ , # auto movz = InstructionARM64(Base(0b010100101, 9), Hw(0), Imm16(initial & 0xffff), Rd(dst.hw_id(instr_set))); // https://www.scs.stanford.edu/~zyedidia/arm64/movk.html // MOVK , #, LSL #16 auto movk = InstructionARM64(Base(0b011100101, 9), Hw(1), Imm16(initial >> 16), Rd(dst.hw_id(instr_set))); return InstructionARM64(movz, movk).with_reloc(emitter::ARM64::RelocKind::MOV32); } InstructionARM64 add_gpr64_gpr64_sxtw(Register dst, Register base, Register idx) { ASSERT(dst.is_gpr(instr_set)); ASSERT(base.is_gpr(instr_set)); ASSERT(idx.is_gpr(instr_set)); // SXTW is option 0b110 with no shift // https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_ext.html // ADD , , , SXTW return InstructionARM64(Base(0b10001011001000001100000000000000, 32), Rm(idx.hw_id(instr_set)), Rn(base.hw_id(instr_set)), Rd(dst.hw_id(instr_set))); } InstructionARM64 movd_gpr32_f32(Register dst, Register src) { // https://www.scs.stanford.edu/~zyedidia/arm64/fmov_float_gen.html // Single-precision to 32-bit (sf == 0 && ftype == 00 && rmode == 00 && opcode == 110) // FMOV , ASSERT(dst.is_gpr(instr_set)); return InstructionARM64(Base(0b0001111000100110000000, 22), Rn(src.hw_id(instr_set)), Rd(dst.hw_id(instr_set))); } InstructionARM64 movd_f32_gpr32(Register dst, Register src) { // https://www.scs.stanford.edu/~zyedidia/arm64/fmov_float_gen.html // 32-bit to single-precision (sf == 0 && ftype == 00 && rmode == 00 && opcode == 111) // FMOV , ASSERT(src.is_gpr(instr_set)); return InstructionARM64(Base(0b0001111000100111000000, 22), Rn(src.hw_id(instr_set)), Rd(dst.hw_id(instr_set))); } InstructionARM64 movq_gpr64_f64(Register dst, Register src) { // https://www.scs.stanford.edu/~zyedidia/arm64/fmov_float_gen.html // Double-precision to 64-bit (sf == 1 && ftype == 01 && rmode == 00 && opcode == 110) // FMOV , ASSERT(dst.is_gpr(instr_set)); return InstructionARM64(Base(0b1001111001100110000000, 22), Rn(src.hw_id(instr_set)), Rd(dst.hw_id(instr_set))); } InstructionARM64 movq_f64_gpr64(Register dst, Register src) { // https://www.scs.stanford.edu/~zyedidia/arm64/fmov_float_gen.html // 64-bit to double-precision (sf == 1 && ftype == 01 && rmode == 00 && opcode == 111) // FMOV , ASSERT(src.is_gpr(instr_set)); return InstructionARM64(Base(0b1001111001100111000000, 22), Rn(src.hw_id(instr_set)), Rd(dst.hw_id(instr_set))); } InstructionARM64 mov_f32_f32(Register dst, Register src) { // https://www.scs.stanford.edu/~zyedidia/arm64/fmov_float.html // Single-precision (ftype == 00) // FMOV , return InstructionARM64(Base(0b0001111000100000010000, 22), Rn(src.hw_id(instr_set)), Rd(dst.hw_id(instr_set))); } //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; // GOAL Loads and Stores //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; InstructionARM64 load8s_gpr64_gpr64_plus_gpr64(Register dst, Register addr1, Register addr2) { // https://www.scs.stanford.edu/~zyedidia/arm64/ldrsb_reg.html // 64-bit with extended register offset (opc == 10 && option != 011) // LDRSB , [, (|), {}] ASSERT(dst.is_gpr(instr_set)); ASSERT(addr1.is_gpr(instr_set)); ASSERT(addr2.is_gpr(instr_set)); ASSERT(addr1 != addr2); ASSERT(addr1 != SP); ASSERT(addr2 != SP); return InstructionARM64(Base(0b0011100010100000111010, 22), Rt(dst.hw_id(instr_set)), Rn(addr1.hw_id(instr_set)), Rm(addr2.hw_id(instr_set))); } InstructionARM64 store8_gpr64_gpr64_plus_gpr64(Register addr1, Register addr2, Register value) { // https://www.scs.stanford.edu/~zyedidia/arm64/strb_reg.html // 64 bit - SXTX // strb Wt, [Xn, Xm] ASSERT(value.is_gpr(instr_set)); ASSERT(addr1.is_gpr(instr_set)); ASSERT(addr2.is_gpr(instr_set)); ASSERT(addr1 != addr2); ASSERT(addr1 != SP); ASSERT(addr2 != SP); return InstructionARM64(Base(0b0011100000100000111010, 22), Rt(value.hw_id(instr_set)), Rn(addr1.hw_id(instr_set)), Rm(addr2.hw_id(instr_set))); } // x16 is reserved as an emitter scratch register. InstructionARM64 load8s_gpr64_gpr64_plus_gpr64_plus_s8(Register dst, Register addr1, Register addr2, s64 offset) { ASSERT(dst.is_gpr(instr_set)); ASSERT(addr1.is_gpr(instr_set)); ASSERT(addr2.is_gpr(instr_set)); ASSERT(addr1 != addr2); ASSERT(addr1 != SP); ASSERT(addr2 != SP); ASSERT(offset >= INT8_MIN && offset <= INT8_MAX); // https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_ext.html // ADD , , {, {#}} return InstructionARM64({InstructionARM64(Base(0b1000101100100000111000, 22), Rd(X16), Rn(addr1.hw_id(instr_set)), Rm(addr2.hw_id(instr_set))), // https://www.scs.stanford.edu/~zyedidia/arm64/ldursb.html // LDURSB , [{, #}] InstructionARM64(Base(0b0011100010000000000000, 22), Imm9s(offset), Rt(dst.hw_id(instr_set)), Rn(X16))}); } InstructionARM64 store8_gpr64_gpr64_plus_gpr64_plus_s8(Register addr1, Register addr2, Register value, s64 offset) { ASSERT(value.is_gpr(instr_set)); ASSERT(addr1.is_gpr(instr_set)); ASSERT(addr2.is_gpr(instr_set)); ASSERT(addr1 != addr2); ASSERT(addr1 != SP); ASSERT(addr2 != SP); ASSERT(offset >= INT8_MIN && offset <= INT8_MAX); return InstructionARM64({// https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_ext.html // ADD , , {, {#}} InstructionARM64(Base(0b1000101100100000111000, 22), Rd(X16), Rn(addr1.hw_id(instr_set)), Rm(addr2.hw_id(instr_set))), // https://www.scs.stanford.edu/~zyedidia/arm64/sturb.html // STURB , [{, #}] InstructionARM64(Base(0b0011100000000000000000, 22), Imm9s(offset), Rt(value.hw_id(instr_set)), Rn(X16))}); } InstructionARM64 load8s_gpr64_gpr64_plus_gpr64_plus_s32(Register dst, Register addr1, Register addr2, s64 offset) { ASSERT(dst.is_gpr(instr_set)); ASSERT(addr1.is_gpr(instr_set)); ASSERT(addr2.is_gpr(instr_set)); ASSERT(addr1 != addr2); ASSERT(addr1 != SP); ASSERT(addr2 != SP); ASSERT(offset >= INT32_MIN && offset <= INT32_MAX); // first establish the base+index value in x16 std::vector instrs = { // https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_shift.html // ADD , , {, #} InstructionARM64(Base(0b10001011000, 11), Rd(X16), Imm6(0), Rn(addr1.hw_id(instr_set)), Rm(addr2.hw_id(instr_set))), }; if (offset < 0) { // we'll subtract instead offset = std::abs(offset); const auto sub_instrs = construct_multiple_imm12_subs(offset, X16); instrs.insert(instrs.end(), sub_instrs.begin(), sub_instrs.end()); } else { const auto add_instrs = construct_multiple_imm12_adds(offset, X16); instrs.insert(instrs.end(), add_instrs.begin(), add_instrs.end()); } // finally do the load // https://www.scs.stanford.edu/~zyedidia/arm64/ldrsb_imm.html // LDRSB , [], # instrs.emplace_back( InstructionARM64(Base(0b0011100110, 10), Imm12(0), Rt(dst.hw_id(instr_set)), Rn(X16))); return InstructionARM64(instrs); } InstructionARM64 store8_gpr64_gpr64_plus_gpr64_plus_s32(Register addr1, Register addr2, Register value, s64 offset) { ASSERT(value.is_gpr(instr_set)); ASSERT(addr1.is_gpr(instr_set)); ASSERT(addr2.is_gpr(instr_set)); ASSERT(addr1 != addr2); ASSERT(addr1 != SP); ASSERT(addr2 != SP); ASSERT(offset >= INT32_MIN && offset <= INT32_MAX); // first establish the base+index value in x16 std::vector instrs = { // https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_shift.html // ADD , , {, #} InstructionARM64(Base(0b10001011000, 11), Rd(X16), Imm6(0), Rn(addr1.hw_id(instr_set)), Rm(addr2.hw_id(instr_set))), }; if (offset < 0) { // we'll subtract instead offset = std::abs(offset); const auto sub_instrs = construct_multiple_imm12_subs(offset, X16); instrs.insert(instrs.end(), sub_instrs.begin(), sub_instrs.end()); } else { const auto add_instrs = construct_multiple_imm12_adds(offset, X16); instrs.insert(instrs.end(), add_instrs.begin(), add_instrs.end()); } // https://www.scs.stanford.edu/~zyedidia/arm64/strb_imm.html // unsigned offset // STRB , [{, #}] instrs.emplace_back( InstructionARM64(Base(0b0011100100, 10), Imm12(0), Rt(value.hw_id(instr_set)), Rn(X16))); return InstructionARM64(instrs); } InstructionARM64 load8u_gpr64_gpr64_plus_gpr64(Register dst, Register addr1, Register addr2) { // https://www.scs.stanford.edu/~zyedidia/arm64/ldrb_reg.html // SXTX extend option // LDRB , [, {, LSL }] ASSERT(dst.is_gpr(instr_set)); ASSERT(addr1.is_gpr(instr_set)); ASSERT(addr2.is_gpr(instr_set)); ASSERT(addr1 != addr2); ASSERT(addr1 != SP); ASSERT(addr2 != SP); return InstructionARM64(Base(0b0011100001100000111010, 22), Rt(dst.hw_id(instr_set)), Rn(addr1.hw_id(instr_set)), Rm(addr2.hw_id(instr_set))); } InstructionARM64 load8u_gpr64_gpr64_plus_gpr64_plus_s8(Register dst, Register addr1, Register addr2, s64 offset) { ASSERT(dst.is_gpr(instr_set)); ASSERT(addr1.is_gpr(instr_set)); ASSERT(addr2.is_gpr(instr_set)); ASSERT(addr1 != addr2); ASSERT(addr1 != SP); ASSERT(addr2 != SP); ASSERT(offset >= INT8_MIN && offset <= INT8_MAX); std::vector instrs; if (offset > 0) { instrs = { // https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_ext.html // ADD , , {, {#}} InstructionARM64(Base(0b1000101100100000111000, 22), Rd(X16), Rn(addr1.hw_id(instr_set)), Rm(addr2.hw_id(instr_set))), // https://www.scs.stanford.edu/~zyedidia/arm64/ldrb_imm.html // Unsigned offset mode // LDRB , [], # InstructionARM64(Base(0b0011100101, 10), Imm12(offset), Rt(dst.hw_id(instr_set)), Rn(X16))}; } else { // https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_ext.html // ADD , , {, {#}} instrs = {InstructionARM64(Base(0b1000101100100000111000, 22), Rd(X16), Rn(addr1.hw_id(instr_set)), Rm(addr2.hw_id(instr_set))), // https://www.scs.stanford.edu/~zyedidia/arm64/ldurb.html // LDURB , [{, #}] InstructionARM64(Base(0b0011100001000000000000, 22), Imm9s(offset), Rt(dst.hw_id(instr_set)), Rn(X16))}; } return InstructionARM64(instrs); } InstructionARM64 load8u_gpr64_gpr64_plus_gpr64_plus_s32(Register dst, Register addr1, Register addr2, s64 offset) { ASSERT(dst.is_gpr(instr_set)); ASSERT(addr1.is_gpr(instr_set)); ASSERT(addr2.is_gpr(instr_set)); ASSERT(addr1 != addr2); ASSERT(addr1 != SP); ASSERT(addr2 != SP); ASSERT(offset >= INT32_MIN && offset <= INT32_MAX); // first establish the base+index value in x16 std::vector instrs = { // https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_shift.html // ADD , , {, #} InstructionARM64(Base(0b10001011000, 11), Rd(X16), Imm6(0), Rn(addr1.hw_id(instr_set)), Rm(addr2.hw_id(instr_set))), }; // TODO - movk instead eventually if (offset < 0) { // we'll subtract instead offset = std::abs(offset); const auto sub_instrs = construct_multiple_imm12_subs(offset, X16); instrs.insert(instrs.end(), sub_instrs.begin(), sub_instrs.end()); } else { const auto add_instrs = construct_multiple_imm12_adds(offset, X16); instrs.insert(instrs.end(), add_instrs.begin(), add_instrs.end()); } // finally do the load // https://www.scs.stanford.edu/~zyedidia/arm64/ldrb_imm.html // LDRB , [], # instrs.emplace_back( InstructionARM64(Base(0b0011100101, 10), Imm12(0), Rt(dst.hw_id(instr_set)), Rn(X16))); return InstructionARM64(instrs); } InstructionARM64 load16s_gpr64_gpr64_plus_gpr64(Register dst, Register addr1, Register addr2) { // https://www.scs.stanford.edu/~zyedidia/arm64/ldrsh_reg.html // LDRSH , [, (|){, {}}] ASSERT(dst.is_gpr(instr_set)); ASSERT(addr1.is_gpr(instr_set)); ASSERT(addr2.is_gpr(instr_set)); ASSERT(addr1 != addr2); ASSERT(addr1 != SP); ASSERT(addr2 != SP); return InstructionARM64(Base(0b0111100010100000111010, 22), Rt(dst.hw_id(instr_set)), Rn(addr1.hw_id(instr_set)), Rm(addr2.hw_id(instr_set))); } InstructionARM64 store16_gpr64_gpr64_plus_gpr64(Register addr1, Register addr2, Register value) { // https://www.scs.stanford.edu/~zyedidia/arm64/strh_reg.html // STRH , [, (|){, {}}] ASSERT(value.is_gpr(instr_set)); ASSERT(addr1.is_gpr(instr_set)); ASSERT(addr2.is_gpr(instr_set)); ASSERT(addr1 != addr2); ASSERT(addr1 != SP); ASSERT(addr2 != SP); return InstructionARM64(Base(0b0111100000100000111010, 22), Rt(value.hw_id(instr_set)), Rn(addr1.hw_id(instr_set)), Rm(addr2.hw_id(instr_set))); } InstructionARM64 store16_gpr64_gpr64_plus_gpr64_plus_s8(Register addr1, Register addr2, Register value, s64 offset) { ASSERT(value.is_gpr(instr_set)); ASSERT(addr1.is_gpr(instr_set)); ASSERT(addr2.is_gpr(instr_set)); ASSERT(addr1 != addr2); ASSERT(addr1 != SP); ASSERT(addr2 != SP); ASSERT(offset >= INT8_MIN && offset <= INT8_MAX); return InstructionARM64({// https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_ext.html // ADD , , {, {#}} InstructionARM64(Base(0b1000101100100000111000, 22), Rd(X16), Rn(addr1.hw_id(instr_set)), Rm(addr2.hw_id(instr_set))), // https://www.scs.stanford.edu/~zyedidia/arm64/sturh.html // STURH , [{, #}] InstructionARM64(Base(0b0111100000000000000000, 22), Imm9s(offset), Rt(value.hw_id(instr_set)), Rn(X16))}); } InstructionARM64 store16_gpr64_gpr64_plus_gpr64_plus_s32(Register addr1, Register addr2, Register value, s64 offset) { ASSERT(value.is_gpr(instr_set)); ASSERT(addr1.is_gpr(instr_set)); ASSERT(addr2.is_gpr(instr_set)); ASSERT(addr1 != addr2); ASSERT(addr1 != SP); ASSERT(addr2 != SP); ASSERT(offset >= INT32_MIN && offset <= INT32_MAX); // first establish the base+index value in x16 std::vector instrs = { // https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_shift.html // ADD , , {, #} InstructionARM64(Base(0b10001011000, 11), Rd(X16), Imm6(0), Rn(addr1.hw_id(instr_set)), Rm(addr2.hw_id(instr_set))), }; if (offset < 0) { // we'll subtract instead offset = std::abs(offset); const auto sub_instrs = construct_multiple_imm12_subs(offset, X16); instrs.insert(instrs.end(), sub_instrs.begin(), sub_instrs.end()); } else { const auto add_instrs = construct_multiple_imm12_adds(offset, X16); instrs.insert(instrs.end(), add_instrs.begin(), add_instrs.end()); } // finally do the load // https://www.scs.stanford.edu/~zyedidia/arm64/strh_imm.html // STRH , [{, #}] instrs.emplace_back( InstructionARM64(Base(0b0111100100, 10), Imm12(0), Rt(value.hw_id(instr_set)), Rn(X16))); return InstructionARM64(instrs); } InstructionARM64 load16s_gpr64_gpr64_plus_gpr64_plus_s8(Register dst, Register addr1, Register addr2, s64 offset) { ASSERT(dst.is_gpr(instr_set)); ASSERT(addr1.is_gpr(instr_set)); ASSERT(addr2.is_gpr(instr_set)); ASSERT(addr1 != addr2); ASSERT(addr1 != SP); ASSERT(addr2 != SP); ASSERT(offset >= INT8_MIN && offset <= INT8_MAX); // https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_ext.html // ADD , , {, {#}} return InstructionARM64({InstructionARM64(Base(0b1000101100100000111000, 22), Rd(X16), Rn(addr1.hw_id(instr_set)), Rm(addr2.hw_id(instr_set))), // https://www.scs.stanford.edu/~zyedidia/arm64/ldursh.html // LDURSH , [{, #}] InstructionARM64(Base(0b0111100010000000000000, 22), Imm9s(offset), Rt(dst.hw_id(instr_set)), Rn(X16))}); } InstructionARM64 load16s_gpr64_gpr64_plus_gpr64_plus_s32(Register dst, Register addr1, Register addr2, s64 offset) { ASSERT(dst.is_gpr(instr_set)); ASSERT(addr1.is_gpr(instr_set)); ASSERT(addr2.is_gpr(instr_set)); ASSERT(addr1 != addr2); ASSERT(addr1 != SP); ASSERT(addr2 != SP); ASSERT(offset >= INT32_MIN && offset <= INT32_MAX); // first establish the base+index value in x16 std::vector instrs = { // https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_shift.html // ADD , , {, #} InstructionARM64(Base(0b10001011000, 11), Rd(X16), Imm6(0), Rn(addr1.hw_id(instr_set)), Rm(addr2.hw_id(instr_set))), }; if (offset < 0) { // we'll subtract instead offset = std::abs(offset); const auto sub_instrs = construct_multiple_imm12_subs(offset, X16); instrs.insert(instrs.end(), sub_instrs.begin(), sub_instrs.end()); } else { const auto add_instrs = construct_multiple_imm12_adds(offset, X16); instrs.insert(instrs.end(), add_instrs.begin(), add_instrs.end()); } // finally do the load // https://www.scs.stanford.edu/~zyedidia/arm64/ldrsh_imm.html // LDRSH , [], # instrs.emplace_back( InstructionARM64(Base(0b0111100110, 10), Imm12(0), Rt(dst.hw_id(instr_set)), Rn(X16))); return InstructionARM64(instrs); } InstructionARM64 load16u_gpr64_gpr64_plus_gpr64(Register dst, Register addr1, Register addr2) { // https://www.scs.stanford.edu/~zyedidia/arm64/ldrh_reg.html // SXTX extend option // LDRH , [, {, LSL }] ASSERT(dst.is_gpr(instr_set)); ASSERT(addr1.is_gpr(instr_set)); ASSERT(addr2.is_gpr(instr_set)); ASSERT(addr1 != addr2); ASSERT(addr1 != SP); ASSERT(addr2 != SP); return InstructionARM64(Base(0b0111100001100000111010, 22), Rt(dst.hw_id(instr_set)), Rn(addr1.hw_id(instr_set)), Rm(addr2.hw_id(instr_set))); } InstructionARM64 load16u_gpr64_gpr64_plus_gpr64_plus_s8(Register dst, Register addr1, Register addr2, s64 offset) { ASSERT(dst.is_gpr(instr_set)); ASSERT(addr1.is_gpr(instr_set)); ASSERT(addr2.is_gpr(instr_set)); ASSERT(addr1 != addr2); ASSERT(addr1 != SP); ASSERT(addr2 != SP); ASSERT(offset >= INT8_MIN && offset <= INT8_MAX); // LDURH covers the full signed byte offset range // https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_ext.html // ADD , , {, {#}} return InstructionARM64({InstructionARM64(Base(0b1000101100100000111000, 22), Rd(X16), Rn(addr1.hw_id(instr_set)), Rm(addr2.hw_id(instr_set))), // https://www.scs.stanford.edu/~zyedidia/arm64/ldurh.html // LDURH , [{, #}] InstructionARM64(Base(0b0111100001000000000000, 22), Imm9s(offset), Rt(dst.hw_id(instr_set)), Rn(X16))}); } InstructionARM64 load16u_gpr64_gpr64_plus_gpr64_plus_s32(Register dst, Register addr1, Register addr2, s64 offset) { ASSERT(dst.is_gpr(instr_set)); ASSERT(addr1.is_gpr(instr_set)); ASSERT(addr2.is_gpr(instr_set)); ASSERT(addr1 != addr2); ASSERT(addr1 != SP); ASSERT(addr2 != SP); ASSERT(offset >= INT32_MIN && offset <= INT32_MAX); // first establish the base+index value in x16 std::vector instrs = { // https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_shift.html // ADD , , {, #} InstructionARM64(Base(0b10001011000, 11), Rd(X16), Imm6(0), Rn(addr1.hw_id(instr_set)), Rm(addr2.hw_id(instr_set))), }; if (offset < 0) { // we'll subtract instead offset = std::abs(offset); const auto sub_instrs = construct_multiple_imm12_subs(offset, X16); instrs.insert(instrs.end(), sub_instrs.begin(), sub_instrs.end()); } else { const auto add_instrs = construct_multiple_imm12_adds(offset, X16); instrs.insert(instrs.end(), add_instrs.begin(), add_instrs.end()); } // finally do the load // https://www.scs.stanford.edu/~zyedidia/arm64/ldrh_imm.html // LDRH , [{, #}] instrs.emplace_back( InstructionARM64(Base(0b0111100101, 10), Imm12(0), Rt(dst.hw_id(instr_set)), Rn(X16))); return InstructionARM64(instrs); } InstructionARM64 load32s_gpr64_gpr64_plus_gpr64(Register dst, Register addr1, Register addr2) { // https://www.scs.stanford.edu/~zyedidia/arm64/ldrsw_reg.html // LDRSW , [, (|){, {}}] ASSERT(dst.is_gpr(instr_set)); ASSERT(addr1.is_gpr(instr_set)); ASSERT(addr2.is_gpr(instr_set)); ASSERT(addr1 != addr2); ASSERT(addr1 != SP); ASSERT(addr2 != SP); return InstructionARM64(Base(0b1011100010100000111010, 22), Rt(dst.hw_id(instr_set)), Rn(addr1.hw_id(instr_set)), Rm(addr2.hw_id(instr_set))); } InstructionARM64 store32_gpr64_gpr64_plus_gpr64(Register addr1, Register addr2, Register value) { // https://www.scs.stanford.edu/~zyedidia/arm64/str_reg_gen.html // STR , [, (|){, {}}] ASSERT(value.is_gpr(instr_set)); ASSERT(addr1.is_gpr(instr_set)); ASSERT(addr2.is_gpr(instr_set)); ASSERT(addr1 != addr2); ASSERT(addr1 != SP); ASSERT(addr2 != SP); return InstructionARM64(Base(0b1011100000100000111010, 22), Rt(value.hw_id(instr_set)), Rn(addr1.hw_id(instr_set)), Rm(addr2.hw_id(instr_set))); } InstructionARM64 load32s_gpr64_gpr64_plus_gpr64_plus_s8(Register dst, Register addr1, Register addr2, s64 offset) { ASSERT(dst.is_gpr(instr_set)); ASSERT(addr1.is_gpr(instr_set)); ASSERT(addr2.is_gpr(instr_set)); ASSERT(addr1 != addr2); ASSERT(addr1 != SP); ASSERT(addr2 != SP); ASSERT(offset >= INT8_MIN && offset <= INT8_MAX); // https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_ext.html // ADD , , {, {#}} return InstructionARM64({InstructionARM64(Base(0b1000101100100000111000, 22), Rd(X16), Rn(addr1.hw_id(instr_set)), Rm(addr2.hw_id(instr_set))), // https://www.scs.stanford.edu/~zyedidia/arm64/ldursw.html // LDURSW , [{, #}] InstructionARM64(Base(0b1011100010000000000000, 22), Imm9s(offset), Rt(dst.hw_id(instr_set)), Rn(X16))}); } InstructionARM64 store32_gpr64_gpr64_plus_gpr64_plus_s8(Register addr1, Register addr2, Register value, s64 offset) { ASSERT(value.is_gpr(instr_set)); ASSERT(addr1.is_gpr(instr_set)); ASSERT(addr2.is_gpr(instr_set)); ASSERT(addr1 != addr2); ASSERT(addr1 != SP); ASSERT(addr2 != SP); ASSERT(offset >= INT8_MIN && offset <= INT8_MAX); // first establish the base+index value in x16 std::vector instrs = { // https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_shift.html // ADD , , {, #} InstructionARM64(Base(0b10001011000, 11), Rd(X16), Imm6(0), Rn(addr1.hw_id(instr_set)), Rm(addr2.hw_id(instr_set))), }; if (offset < 0) { // we'll subtract instead offset = std::abs(offset); const auto sub_instrs = construct_multiple_imm12_subs(offset, X16); instrs.insert(instrs.end(), sub_instrs.begin(), sub_instrs.end()); } else { const auto add_instrs = construct_multiple_imm12_adds(offset, X16); instrs.insert(instrs.end(), add_instrs.begin(), add_instrs.end()); } // https://www.scs.stanford.edu/~zyedidia/arm64/str_imm_gen.html // STR , [{, #}] instrs.emplace_back( InstructionARM64(Base(0b1011100100, 10), Imm12(0), Rt(value.hw_id(instr_set)), Rn(X16))); return InstructionARM64(instrs); } InstructionARM64 load32s_gpr64_gpr64_plus_gpr64_plus_s32(Register dst, Register addr1, Register addr2, s64 offset) { ASSERT(dst.is_gpr(instr_set)); ASSERT(addr1.is_gpr(instr_set)); ASSERT(addr2.is_gpr(instr_set)); ASSERT(addr1 != addr2); ASSERT(addr1 != SP); ASSERT(addr2 != SP); ASSERT(offset >= INT32_MIN && offset <= INT32_MAX); // first establish the base+index value in x16 std::vector instrs = { // https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_shift.html // ADD , , {, #} InstructionARM64(Base(0b10001011000, 11), Rd(X16), Imm6(0), Rn(addr1.hw_id(instr_set)), Rm(addr2.hw_id(instr_set))), }; if (offset < 0) { // we'll subtract instead offset = std::abs(offset); const auto sub_instrs = construct_multiple_imm12_subs(offset, X16); instrs.insert(instrs.end(), sub_instrs.begin(), sub_instrs.end()); } else { const auto add_instrs = construct_multiple_imm12_adds(offset, X16); instrs.insert(instrs.end(), add_instrs.begin(), add_instrs.end()); } // finally do the load // https://www.scs.stanford.edu/~zyedidia/arm64/ldrsw_imm.html // LDRSW , [], # instrs.emplace_back( InstructionARM64(Base(0b1011100110, 10), Imm12(0), Rt(dst.hw_id(instr_set)), Rn(X16))); return InstructionARM64(instrs); } InstructionARM64 store32_gpr64_gpr64_plus_gpr64_plus_s32(Register addr1, Register addr2, Register value, s64 offset) { ASSERT(value.is_gpr(instr_set)); ASSERT(addr1.is_gpr(instr_set)); ASSERT(addr2.is_gpr(instr_set)); ASSERT(addr1 != addr2); ASSERT(addr1 != SP); ASSERT(addr2 != SP); ASSERT(offset >= INT32_MIN && offset <= INT32_MAX); // first establish the base+index value in x16 std::vector instrs = { // https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_shift.html // ADD , , {, #} InstructionARM64(Base(0b10001011000, 11), Rd(X16), Imm6(0), Rn(addr1.hw_id(instr_set)), Rm(addr2.hw_id(instr_set))), }; if (offset < 0) { // we'll subtract instead offset = std::abs(offset); const auto sub_instrs = construct_multiple_imm12_subs(offset, X16); instrs.insert(instrs.end(), sub_instrs.begin(), sub_instrs.end()); } else { const auto add_instrs = construct_multiple_imm12_adds(offset, X16); instrs.insert(instrs.end(), add_instrs.begin(), add_instrs.end()); } // https://www.scs.stanford.edu/~zyedidia/arm64/str_imm_gen.html // STR , [{, #}] instrs.emplace_back( InstructionARM64(Base(0b1011100100, 10), Imm12(0), Rt(value.hw_id(instr_set)), Rn(X16))); return InstructionARM64(instrs); } InstructionARM64 load32u_gpr64_gpr64_plus_gpr64(Register dst, Register addr1, Register addr2) { // https://www.scs.stanford.edu/~zyedidia/arm64/ldr_reg_gen.html // 32-bit variant, SXTX // LDR , [, (|){, {}}] ASSERT(dst.is_gpr(instr_set)); ASSERT(addr1.is_gpr(instr_set)); ASSERT(addr2.is_gpr(instr_set)); ASSERT(addr1 != addr2); ASSERT(addr1 != SP); ASSERT(addr2 != SP); return InstructionARM64(Base(0b1011100001100000111010, 22), Rt(dst.hw_id(instr_set)), Rn(addr1.hw_id(instr_set)), Rm(addr2.hw_id(instr_set))); } InstructionARM64 load32u_gpr64_gpr64_plus_gpr64_plus_s8(Register dst, Register addr1, Register addr2, s64 offset) { ASSERT(dst.is_gpr(instr_set)); ASSERT(addr1.is_gpr(instr_set)); ASSERT(addr2.is_gpr(instr_set)); ASSERT(addr1 != addr2); ASSERT(addr1 != SP); ASSERT(addr2 != SP); ASSERT(offset >= INT8_MIN && offset <= INT8_MAX); // https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_ext.html // ADD , , {, {#}} return InstructionARM64({InstructionARM64(Base(0b1000101100100000111000, 22), Rd(X16), Rn(addr1.hw_id(instr_set)), Rm(addr2.hw_id(instr_set))), // https://www.scs.stanford.edu/~zyedidia/arm64/ldur_gen.html // 32 bit // LDUR , [{, #}] InstructionARM64(Base(0b1011100001000000000000, 22), Imm9s(offset), Rt(dst.hw_id(instr_set)), Rn(X16))}); } InstructionARM64 load32u_gpr64_gpr64_plus_gpr64_plus_s32(Register dst, Register addr1, Register addr2, s64 offset) { ASSERT(dst.is_gpr(instr_set)); ASSERT(addr1.is_gpr(instr_set)); ASSERT(addr2.is_gpr(instr_set)); ASSERT(addr1 != addr2); ASSERT(addr1 != SP); ASSERT(addr2 != SP); ASSERT(offset >= INT32_MIN && offset <= INT32_MAX); // first establish the base+index value in x16 std::vector instrs = { // https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_shift.html // ADD , , {, #} InstructionARM64(Base(0b10001011000, 11), Rd(X16), Imm6(0), Rn(addr1.hw_id(instr_set)), Rm(addr2.hw_id(instr_set))), }; if (offset < 0) { // we'll subtract instead offset = std::abs(offset); const auto sub_instrs = construct_multiple_imm12_subs(offset, X16); instrs.insert(instrs.end(), sub_instrs.begin(), sub_instrs.end()); } else { const auto add_instrs = construct_multiple_imm12_adds(offset, X16); instrs.insert(instrs.end(), add_instrs.begin(), add_instrs.end()); } // finally do the load // https://www.scs.stanford.edu/~zyedidia/arm64/ldur_gen.html // 32-bit variant // LDUR , [{, #}] instrs.emplace_back(InstructionARM64(Base(0b1011100001000000000000, 22), Imm9s(0), Rt(dst.hw_id(instr_set)), Rn(X16))); return InstructionARM64(instrs); } InstructionARM64 load64_gpr64_gpr64_plus_gpr64(Register dst, Register addr1, Register addr2) { // https://www.scs.stanford.edu/~zyedidia/arm64/ldr_reg_gen.html // 64 bit mode, SXTX // LDR , [, (|){, {}}] ASSERT(dst.is_gpr(instr_set)); ASSERT(addr1.is_gpr(instr_set)); ASSERT(addr2.is_gpr(instr_set)); ASSERT(addr1 != addr2); ASSERT(addr1 != SP); ASSERT(addr2 != SP); return InstructionARM64(Base(0b1111100001100000111010, 22), Rt(dst.hw_id(instr_set)), Rn(addr1.hw_id(instr_set)), Rm(addr2.hw_id(instr_set))); } InstructionARM64 store64_gpr64_gpr64_plus_gpr64(Register addr1, Register addr2, Register value) { // https://www.scs.stanford.edu/~zyedidia/arm64/str_reg_gen.html // STR , [, (|){, {}}] ASSERT(value.is_gpr(instr_set)); ASSERT(addr1.is_gpr(instr_set)); ASSERT(addr2.is_gpr(instr_set)); ASSERT(addr1 != addr2); ASSERT(addr1 != SP); ASSERT(addr2 != SP); return InstructionARM64(Base(0b1111100000100000111010, 22), Rt(value.hw_id(instr_set)), Rn(addr1.hw_id(instr_set)), Rm(addr2.hw_id(instr_set))); } InstructionARM64 load64_gpr64_gpr64_plus_gpr64_plus_s8(Register dst, Register addr1, Register addr2, s64 offset) { ASSERT(dst.is_gpr(instr_set)); ASSERT(addr1.is_gpr(instr_set)); ASSERT(addr2.is_gpr(instr_set)); ASSERT(addr1 != addr2); ASSERT(addr1 != SP); ASSERT(addr2 != SP); ASSERT(offset >= INT8_MIN && offset <= INT8_MAX); // https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_ext.html // ADD , , {, {#}} return InstructionARM64({InstructionARM64(Base(0b1000101100100000111000, 22), Rd(X16), Rn(addr1.hw_id(instr_set)), Rm(addr2.hw_id(instr_set))), // https://www.scs.stanford.edu/~zyedidia/arm64/ldur_gen.html // 64 bit // LDUR , [{, #}] InstructionARM64(Base(0b1111100001000000000000, 22), Imm9s(offset), Rt(dst.hw_id(instr_set)), Rn(X16))}); } InstructionARM64 store64_gpr64_gpr64_plus_gpr64_plus_s8(Register addr1, Register addr2, Register value, s64 offset) { ASSERT(value.is_gpr(instr_set)); ASSERT(addr1.is_gpr(instr_set)); ASSERT(addr2.is_gpr(instr_set)); ASSERT(addr1 != addr2); ASSERT(addr1 != SP); ASSERT(addr2 != SP); ASSERT(offset >= INT8_MIN && offset <= INT8_MAX); // first establish the base+index value in x16 std::vector instrs = { // https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_shift.html // ADD , , {, #} InstructionARM64(Base(0b10001011000, 11), Rd(X16), Imm6(0), Rn(addr1.hw_id(instr_set)), Rm(addr2.hw_id(instr_set))), }; if (offset < 0) { // we'll subtract instead offset = std::abs(offset); const auto sub_instrs = construct_multiple_imm12_subs(offset, X16); instrs.insert(instrs.end(), sub_instrs.begin(), sub_instrs.end()); } else { const auto add_instrs = construct_multiple_imm12_adds(offset, X16); instrs.insert(instrs.end(), add_instrs.begin(), add_instrs.end()); } // https://www.scs.stanford.edu/~zyedidia/arm64/str_imm_gen.html // STR , [{, #}] instrs.emplace_back( InstructionARM64(Base(0b1111100100, 10), Imm12(0), Rt(value.hw_id(instr_set)), Rn(X16))); return InstructionARM64(instrs); } InstructionARM64 load64_gpr64_gpr64_plus_gpr64_plus_s32(Register dst, Register addr1, Register addr2, s64 offset) { ASSERT(dst.is_gpr(instr_set)); ASSERT(addr1.is_gpr(instr_set)); ASSERT(addr2.is_gpr(instr_set)); ASSERT(addr1 != addr2); ASSERT(addr1 != SP); ASSERT(addr2 != SP); ASSERT(offset >= INT32_MIN && offset <= INT32_MAX); // first establish the base+index value in x16 std::vector instrs = { // https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_shift.html // ADD , , {, #} InstructionARM64(Base(0b10001011000, 11), Rd(X16), Imm6(0), Rn(addr1.hw_id(instr_set)), Rm(addr2.hw_id(instr_set))), }; if (offset < 0) { // we'll subtract instead offset = std::abs(offset); const auto sub_instrs = construct_multiple_imm12_subs(offset, X16); instrs.insert(instrs.end(), sub_instrs.begin(), sub_instrs.end()); } else { const auto add_instrs = construct_multiple_imm12_adds(offset, X16); instrs.insert(instrs.end(), add_instrs.begin(), add_instrs.end()); } // finally do the load // https://www.scs.stanford.edu/~zyedidia/arm64/ldur_gen.html // 64-bit variant // LDUR , [{, #}] instrs.emplace_back(InstructionARM64(Base(0b1111100001000000000000, 22), Imm9s(0), Rt(dst.hw_id(instr_set)), Rn(X16))); return InstructionARM64(instrs); } InstructionARM64 store64_gpr64_gpr64_plus_gpr64_plus_s32(Register addr1, Register addr2, Register value, s64 offset) { ASSERT(value.is_gpr(instr_set)); ASSERT(addr1.is_gpr(instr_set)); ASSERT(addr2.is_gpr(instr_set)); ASSERT(addr1 != addr2); ASSERT(addr1 != SP); ASSERT(addr2 != SP); ASSERT(offset >= INT32_MIN && offset <= INT32_MAX); // first establish the base+index value in x16 std::vector instrs = { // https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_shift.html // ADD , , {, #} InstructionARM64(Base(0b10001011000, 11), Rd(X16), Imm6(0), Rn(addr1.hw_id(instr_set)), Rm(addr2.hw_id(instr_set))), }; if (offset < 0) { // we'll subtract instead offset = std::abs(offset); const auto sub_instrs = construct_multiple_imm12_subs(offset, X16); instrs.insert(instrs.end(), sub_instrs.begin(), sub_instrs.end()); } else { const auto add_instrs = construct_multiple_imm12_adds(offset, X16); instrs.insert(instrs.end(), add_instrs.begin(), add_instrs.end()); } // https://www.scs.stanford.edu/~zyedidia/arm64/str_imm_gen.html // STR , [{, #}] instrs.emplace_back( InstructionARM64(Base(0b1111100100, 10), Imm12(0), Rt(value.hw_id(instr_set)), Rn(X16))); return InstructionARM64(instrs); } InstructionARM64 load64_gpr64_plus_s32(Register dst_reg, int32_t offset, Register src_reg) { ASSERT(dst_reg.is_gpr(instr_set)); ASSERT(src_reg.is_gpr(instr_set)); // allow SP as the base for spill-slot addressing with immediate ADD ASSERT(offset >= INT32_MIN && offset <= INT32_MAX); std::vector instrs = { // https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_imm.html // ADD , , #{, } InstructionARM64(Base(0b100100010, 9), Sh(0), Imm12(0), Rd(X16), Rn(src_reg.hw_id(instr_set))), }; if (offset < 0) { // we'll subtract instead offset = std::abs(offset); const auto sub_instrs = construct_multiple_imm12_subs(offset, X16); instrs.insert(instrs.end(), sub_instrs.begin(), sub_instrs.end()); } else { const auto add_instrs = construct_multiple_imm12_adds(offset, X16); instrs.insert(instrs.end(), add_instrs.begin(), add_instrs.end()); } // finally do the load // https://www.scs.stanford.edu/~zyedidia/arm64/ldur_gen.html // 64-bit variant // LDUR , [{, #}] instrs.emplace_back(InstructionARM64(Base(0b1111100001000000000000, 22), Imm9s(0), Rt(dst_reg.hw_id(instr_set)), Rn(X16))); return InstructionARM64(instrs); } InstructionARM64 store64_gpr64_plus_s32(Register addr, int32_t offset, Register value) { ASSERT(value.is_gpr(instr_set)); ASSERT(addr.is_gpr(instr_set)); // allow SP as the base for immediate ADD ASSERT(offset >= INT32_MIN && offset <= INT32_MAX); std::vector instrs = { // https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_imm.html // ADD , , #{, } InstructionARM64(Base(0b100100010, 9), Sh(0), Imm12(0), Rd(X16), Rn(addr.hw_id(instr_set))), }; if (offset < 0) { // we'll subtract instead offset = std::abs(offset); const auto sub_instrs = construct_multiple_imm12_subs(offset, X16); instrs.insert(instrs.end(), sub_instrs.begin(), sub_instrs.end()); } else { const auto add_instrs = construct_multiple_imm12_adds(offset, X16); instrs.insert(instrs.end(), add_instrs.begin(), add_instrs.end()); } // https://www.scs.stanford.edu/~zyedidia/arm64/str_imm_gen.html // STR , [{, #}] instrs.emplace_back( InstructionARM64(Base(0b1111100100, 10), Imm12(0), Rt(value.hw_id(instr_set)), Rn(X16))); return InstructionARM64(instrs); } InstructionARM64 store_goal_vf(Register addr, Register value, Register off, s64 offset) { if (offset == 0) { return storevf_gpr64_plus_gpr64(value, addr, off); } else if (offset >= INT8_MIN && offset <= INT8_MAX) { return storevf_gpr64_plus_gpr64_plus_s8(value, addr, off, offset); } else if (offset >= INT32_MIN && offset <= INT32_MAX) { return storevf_gpr64_plus_gpr64_plus_s32(value, addr, off, offset); } ASSERT(false); return {0}; } InstructionARM64 store_goal_gpr(Register addr, Register value, Register off, int offset, int size) { switch (size) { case 1: if (offset == 0) { return store8_gpr64_gpr64_plus_gpr64(addr, off, value); } else if (offset >= INT8_MIN && offset <= INT8_MAX) { return store8_gpr64_gpr64_plus_gpr64_plus_s8(addr, off, value, offset); } else if (offset >= INT32_MIN && offset <= INT32_MAX) { return store8_gpr64_gpr64_plus_gpr64_plus_s32(addr, off, value, offset); } else { ASSERT(false); } case 2: if (offset == 0) { return store16_gpr64_gpr64_plus_gpr64(addr, off, value); } else if (offset >= INT8_MIN && offset <= INT8_MAX) { return store16_gpr64_gpr64_plus_gpr64_plus_s8(addr, off, value, offset); } else if (offset >= INT32_MIN && offset <= INT32_MAX) { return store16_gpr64_gpr64_plus_gpr64_plus_s32(addr, off, value, offset); } else { ASSERT(false); } case 4: if (offset == 0) { return store32_gpr64_gpr64_plus_gpr64(addr, off, value); } else if (offset >= INT8_MIN && offset <= INT8_MAX) { return store32_gpr64_gpr64_plus_gpr64_plus_s8(addr, off, value, offset); } else if (offset >= INT32_MIN && offset <= INT32_MAX) { return store32_gpr64_gpr64_plus_gpr64_plus_s32(addr, off, value, offset); } else { ASSERT(false); } case 8: if (offset == 0) { return store64_gpr64_gpr64_plus_gpr64(addr, off, value); } else if (offset >= INT8_MIN && offset <= INT8_MAX) { return store64_gpr64_gpr64_plus_gpr64_plus_s8(addr, off, value, offset); } else if (offset >= INT32_MIN && offset <= INT32_MAX) { return store64_gpr64_gpr64_plus_gpr64_plus_s32(addr, off, value, offset); } else { ASSERT(false); } default: ASSERT(false); return {0}; } } InstructionARM64 load_goal_simd128(Register dst, Register addr, Register off, int offset) { if (offset == 0) { return loadvf_gpr64_plus_gpr64(dst, addr, off); } else if (offset >= INT8_MIN && offset <= INT8_MAX) { return loadvf_gpr64_plus_gpr64_plus_s8(dst, addr, off, offset); } else if (offset >= INT32_MIN && offset <= INT32_MAX) { return loadvf_gpr64_plus_gpr64_plus_s32(dst, addr, off, offset); } else { ASSERT(false); return {0}; } } InstructionARM64 load_goal_gpr(Register dst, Register addr, Register off, int offset, int size, bool sign_extend) { switch (size) { case 1: if (offset == 0) { if (sign_extend) { return load8s_gpr64_gpr64_plus_gpr64(dst, addr, off); } else { return load8u_gpr64_gpr64_plus_gpr64(dst, addr, off); } } else if (offset >= INT8_MIN && offset <= INT8_MAX) { if (sign_extend) { return load8s_gpr64_gpr64_plus_gpr64_plus_s8(dst, addr, off, offset); } else { return load8u_gpr64_gpr64_plus_gpr64_plus_s8(dst, addr, off, offset); } } else if (offset >= INT32_MIN && offset <= INT32_MAX) { if (sign_extend) { return load8s_gpr64_gpr64_plus_gpr64_plus_s32(dst, addr, off, offset); } else { return load8u_gpr64_gpr64_plus_gpr64_plus_s32(dst, addr, off, offset); } } else { ASSERT(false); } case 2: if (offset == 0) { if (sign_extend) { return load16s_gpr64_gpr64_plus_gpr64(dst, addr, off); } else { return load16u_gpr64_gpr64_plus_gpr64(dst, addr, off); } } else if (offset >= INT8_MIN && offset <= INT8_MAX) { if (sign_extend) { return load16s_gpr64_gpr64_plus_gpr64_plus_s8(dst, addr, off, offset); } else { return load16u_gpr64_gpr64_plus_gpr64_plus_s8(dst, addr, off, offset); } } else if (offset >= INT32_MIN && offset <= INT32_MAX) { if (sign_extend) { return load16s_gpr64_gpr64_plus_gpr64_plus_s32(dst, addr, off, offset); } else { return load16u_gpr64_gpr64_plus_gpr64_plus_s32(dst, addr, off, offset); } } else { ASSERT(false); } case 4: if (offset == 0) { if (sign_extend) { return load32s_gpr64_gpr64_plus_gpr64(dst, addr, off); } else { return load32u_gpr64_gpr64_plus_gpr64(dst, addr, off); } } else if (offset >= INT8_MIN && offset <= INT8_MAX) { if (sign_extend) { return load32s_gpr64_gpr64_plus_gpr64_plus_s8(dst, addr, off, offset); } else { return load32u_gpr64_gpr64_plus_gpr64_plus_s8(dst, addr, off, offset); } } else if (offset >= INT32_MIN && offset <= INT32_MAX) { if (sign_extend) { return load32s_gpr64_gpr64_plus_gpr64_plus_s32(dst, addr, off, offset); } else { return load32u_gpr64_gpr64_plus_gpr64_plus_s32(dst, addr, off, offset); } } else { ASSERT(false); } case 8: if (offset == 0) { return load64_gpr64_gpr64_plus_gpr64(dst, addr, off); } else if (offset >= INT8_MIN && offset <= INT8_MAX) { return load64_gpr64_gpr64_plus_gpr64_plus_s8(dst, addr, off, offset); } else if (offset >= INT32_MIN && offset <= INT32_MAX) { return load64_gpr64_gpr64_plus_gpr64_plus_s32(dst, addr, off, offset); } else { ASSERT(false); } default: ASSERT(false); return {0}; } } InstructionARM64 lea_reg_plus_off32(Register dest, Register base, s64 offset) { ASSERT(dest.is_gpr(instr_set)); ASSERT(base.is_gpr(instr_set)); ASSERT(offset >= INT32_MIN && offset <= INT32_MAX); // first establish the base value in our destination register std::vector instrs = { // https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_imm.html // ADD , , #{, } InstructionARM64(Base(0b100100010, 9), Sh(0), Imm12(0), Rd(dest.hw_id(instr_set)), Rn(base.hw_id(instr_set))), }; if (offset < 0) { // we'll subtract instead offset = std::abs(offset); const auto sub_instrs = construct_multiple_imm12_subs(offset, dest.hw_id(instr_set)); instrs.insert(instrs.end(), sub_instrs.begin(), sub_instrs.end()); } else { const auto add_instrs = construct_multiple_imm12_adds(offset, dest.hw_id(instr_set)); instrs.insert(instrs.end(), add_instrs.begin(), add_instrs.end()); } return InstructionARM64(instrs); } InstructionARM64 lea_reg_plus_off8(Register dest, Register base, s64 offset) { ASSERT(dest.is_gpr(instr_set)); ASSERT(base.is_gpr(instr_set)); ASSERT(offset >= INT8_MIN && offset <= INT8_MAX); // first establish the base value in our destination register std::vector instrs = { // https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_imm.html // ADD , , #{, } InstructionARM64(Base(0b100100010, 9), Sh(0), Imm12(0), Rd(dest.hw_id(instr_set)), Rn(base.hw_id(instr_set))), }; if (offset < 0) { // we'll subtract instead offset = std::abs(offset); const auto sub_instrs = construct_multiple_imm12_subs(offset, dest.hw_id(instr_set)); instrs.insert(instrs.end(), sub_instrs.begin(), sub_instrs.end()); } else { const auto add_instrs = construct_multiple_imm12_adds(offset, dest.hw_id(instr_set)); instrs.insert(instrs.end(), add_instrs.begin(), add_instrs.end()); } return InstructionARM64(instrs); } InstructionARM64 lea_reg_plus_off(Register dest, Register base, s64 offset) { if (offset >= INT8_MIN && offset <= INT8_MAX) { return lea_reg_plus_off8(dest, base, offset); } else if (offset >= INT32_MIN && offset <= INT32_MAX) { return lea_reg_plus_off32(dest, base, offset); } else { ASSERT(false); return {0}; } } //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; // LOADS n' STORES - SIMD32 //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; // TODO - rename these to f32 InstructionARM64 store32_simd32_gpr64_plus_gpr64(Register addr1, Register addr2, Register simd_value) { ASSERT(simd_value.is_128bit_simd(instr_set)); ASSERT(addr1.is_gpr(instr_set)); ASSERT(addr2.is_gpr(instr_set)); // https://www.scs.stanford.edu/~zyedidia/arm64/str_reg_fpsimd.html // 32-bit variant // STR , [, (|){, {}}] return InstructionARM64(Base(0b1011110000100000111010, 22), Rt(simd_value.hw_id(instr_set)), Rm(addr2.hw_id(instr_set)), Rn(addr1.hw_id(instr_set))); } InstructionARM64 load32_simd32_gpr64_plus_gpr64(Register simd_dest, Register addr1, Register addr2) { ASSERT(simd_dest.is_128bit_simd(instr_set)); ASSERT(addr1.is_gpr(instr_set)); ASSERT(addr2.is_gpr(instr_set)); // https://www.scs.stanford.edu/~zyedidia/arm64/ldr_reg_fpsimd.html // 32-bit variant // LDR , [, (|){, {}}] return InstructionARM64(Base(0b1011110001100000111010, 22), Rt(simd_dest.hw_id(instr_set)), Rn(addr1.hw_id(instr_set)), Rm(addr2.hw_id(instr_set))); } InstructionARM64 store32_simd32_gpr64_plus_gpr64_plus_s8(Register addr1, Register addr2, Register simd_value, s64 offset) { ASSERT(simd_value.is_128bit_simd(instr_set)); ASSERT(addr1.is_gpr(instr_set)); ASSERT(addr2.is_gpr(instr_set)); ASSERT(offset >= INT8_MIN && offset <= INT8_MAX); // first establish the base+index value in x16 std::vector instrs = { // https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_shift.html // ADD , , {, #} InstructionARM64(Base(0b10001011000, 11), Rd(X16), Imm6(0), Rn(addr1.hw_id(instr_set)), Rm(addr2.hw_id(instr_set))), }; // TODO - optimization, if its less than imm12 we can just do an add/sub auto mov_instrs = mov_gpr64_u64_instrs(X17, std::abs(offset)); for (const auto& instr : mov_instrs) { instrs.push_back(InstructionARM64(instr)); } if (offset < 0) { instrs.push_back(sub_gpr64_gpr64(X16, X17)); } else { instrs.push_back(add_gpr64_gpr64(X16, X17)); } // https://www.scs.stanford.edu/~zyedidia/arm64/str_imm_fpsimd.html // 32-bit variant // STR , [], # instrs.emplace_back(InstructionARM64(Base(0b1011110100000000000000, 22), Imm12(0), Rt(simd_value.hw_id(instr_set)), Rn(X16))); return InstructionARM64(instrs); } InstructionARM64 load32_simd32_gpr64_plus_gpr64_plus_s8(Register simd_dest, Register addr1, Register addr2, s64 offset) { ASSERT(simd_dest.is_128bit_simd(instr_set)); ASSERT(addr1.is_gpr(instr_set)); ASSERT(addr2.is_gpr(instr_set)); ASSERT(offset >= INT8_MIN && offset <= INT8_MAX); // first establish the base+index value in x16 std::vector instrs = { // https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_shift.html // ADD , , {, #} InstructionARM64(Base(0b10001011000, 11), Rd(X16), Imm6(0), Rn(addr1.hw_id(instr_set)), Rm(addr2.hw_id(instr_set))), }; // TODO - optimization, if its less than imm12 we can just do an add/sub auto mov_instrs = mov_gpr64_u64_instrs(X17, std::abs(offset)); for (const auto& instr : mov_instrs) { instrs.push_back(InstructionARM64(instr)); } if (offset < 0) { instrs.push_back(sub_gpr64_gpr64(X16, X17)); } else { instrs.push_back(add_gpr64_gpr64(X16, X17)); } // https://www.scs.stanford.edu/~zyedidia/arm64/ldr_imm_fpsimd.html // 32-bit variant // LDR , [{, #}] instrs.emplace_back( InstructionARM64(Base(0b1011110101, 10), Imm12(0), Rt(simd_dest.hw_id(instr_set)), Rn(X16))); return InstructionARM64(instrs); } InstructionARM64 store32_simd32_gpr64_plus_gpr64_plus_s32(Register addr1, Register addr2, Register simd_value, s64 offset) { ASSERT(simd_value.is_128bit_simd(instr_set)); ASSERT(addr1.is_gpr(instr_set)); ASSERT(addr2.is_gpr(instr_set)); ASSERT(offset >= INT32_MIN && offset <= INT32_MAX); // first establish the base+index value in x16 std::vector instrs = { // https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_shift.html // ADD , , {, #} InstructionARM64(Base(0b10001011000, 11), Rd(X16), Imm6(0), Rn(addr1.hw_id(instr_set)), Rm(addr2.hw_id(instr_set))), }; // TODO - optimization, if its less than imm12 we can just do an add/sub auto mov_instrs = mov_gpr64_u64_instrs(X17, std::abs(offset)); for (const auto& instr : mov_instrs) { instrs.push_back(InstructionARM64(instr)); } if (offset < 0) { instrs.push_back(sub_gpr64_gpr64(X16, X17)); } else { instrs.push_back(add_gpr64_gpr64(X16, X17)); } // https://www.scs.stanford.edu/~zyedidia/arm64/str_imm_fpsimd.html // 32-bit variant // STR , [], # instrs.emplace_back(InstructionARM64(Base(0b1011110100000000000000, 22), Imm12(0), Rt(simd_value.hw_id(instr_set)), Rn(X16))); return InstructionARM64(instrs); } InstructionARM64 store32_simd32_gpr64_plus_s32(Register base, Register simd_value, s64 offset) { ASSERT(simd_value.is_128bit_simd(instr_set)); ASSERT(base.is_gpr(instr_set)); ASSERT(offset >= INT32_MIN && offset <= INT32_MAX); // first establish the base+index value in x16 std::vector instrs = { // https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_imm.html // ADD , , #{, } InstructionARM64(Base(0b100100010, 9), Sh(0), Imm12(0), Rd(X16), Rn(base.hw_id(instr_set))), }; // TODO - optimization, if its less than imm12 we can just do an add/sub auto mov_instrs = mov_gpr64_u64_instrs(X17, std::abs(offset)); for (const auto& instr : mov_instrs) { instrs.push_back(InstructionARM64(instr)); } if (offset < 0) { instrs.push_back(sub_gpr64_gpr64(X16, X17)); } else { instrs.push_back(add_gpr64_gpr64(X16, X17)); } // https://www.scs.stanford.edu/~zyedidia/arm64/str_imm_fpsimd.html // 32-bit variant // STR , [], # instrs.emplace_back(InstructionARM64(Base(0b1011110100000000000000, 22), Imm12(0), Rt(simd_value.hw_id(instr_set)), Rn(X16))); return InstructionARM64(instrs); } InstructionARM64 store32_simd32_gpr64_plus_s8(Register base, Register simd_value, s64 offset) { ASSERT(simd_value.is_128bit_simd(instr_set)); ASSERT(base.is_gpr(instr_set)); ASSERT(offset >= INT8_MIN && offset <= INT8_MAX); // first establish the base+index value in x16 std::vector instrs = { // https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_imm.html // ADD , , #{, } InstructionARM64(Base(0b100100010, 9), Sh(0), Imm12(0), Rd(X16), Rn(base.hw_id(instr_set))), }; // TODO - optimization, if its less than imm12 we can just do an add/sub auto mov_instrs = mov_gpr64_u64_instrs(X17, std::abs(offset)); for (const auto& instr : mov_instrs) { instrs.push_back(InstructionARM64(instr)); } if (offset < 0) { instrs.push_back(sub_gpr64_gpr64(X16, X17)); } else { instrs.push_back(add_gpr64_gpr64(X16, X17)); } // https://www.scs.stanford.edu/~zyedidia/arm64/str_imm_fpsimd.html // 32-bit variant, unsigned // STR , [], # instrs.emplace_back(InstructionARM64(Base(0b1011110100000000000000, 22), Imm12(0), Rt(simd_value.hw_id(instr_set)), Rn(X16))); return InstructionARM64(instrs); } InstructionARM64 load32_simd32_gpr64_plus_gpr64_plus_s32(Register simd_dest, Register addr1, Register addr2, s64 offset) { ASSERT(simd_dest.is_128bit_simd(instr_set)); ASSERT(addr1.is_gpr(instr_set)); ASSERT(addr2.is_gpr(instr_set)); ASSERT(offset >= INT32_MIN && offset <= INT32_MAX); // first establish the base+index value in x16 std::vector instrs = { // https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_shift.html // ADD , , {, #} InstructionARM64(Base(0b10001011000, 11), Rd(X16), Imm6(0), Rn(addr1.hw_id(instr_set)), Rm(addr2.hw_id(instr_set))), }; // TODO - optimization, if its less than imm12 we can just do an add/sub auto mov_instrs = mov_gpr64_u64_instrs(X17, std::abs(offset)); for (const auto& instr : mov_instrs) { instrs.push_back(InstructionARM64(instr)); } if (offset < 0) { instrs.push_back(sub_gpr64_gpr64(X16, X17)); } else { instrs.push_back(add_gpr64_gpr64(X16, X17)); } // https://www.scs.stanford.edu/~zyedidia/arm64/ldr_imm_fpsimd.html // 32-bit variant // LDR , [{, #}] instrs.emplace_back( InstructionARM64(Base(0b1011110101, 10), Imm12(0), Rt(simd_dest.hw_id(instr_set)), Rn(X16))); return InstructionARM64(instrs); } InstructionARM64 load32_simd32_gpr64_plus_s32(Register simd_dest, Register base, s64 offset) { ASSERT(simd_dest.is_128bit_simd(instr_set)); ASSERT(base.is_gpr(instr_set)); ASSERT(offset >= INT32_MIN && offset <= INT32_MAX); // first establish the base+index value in x16 std::vector instrs = { // https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_imm.html // ADD , , #{, } InstructionARM64(Base(0b100100010, 9), Sh(0), Imm12(0), Rd(X16), Rn(base.hw_id(instr_set))), }; // TODO - optimization, if its less than imm12 we can just do an add/sub auto mov_instrs = mov_gpr64_u64_instrs(X17, std::abs(offset)); for (const auto& instr : mov_instrs) { instrs.push_back(InstructionARM64(instr)); } if (offset < 0) { instrs.push_back(sub_gpr64_gpr64(X16, X17)); } else { instrs.push_back(add_gpr64_gpr64(X16, X17)); } // https://www.scs.stanford.edu/~zyedidia/arm64/ldr_imm_fpsimd.html // 32-bit variant // LDR , [{, #}] instrs.emplace_back( InstructionARM64(Base(0b1011110101, 10), Imm12(0), Rt(simd_dest.hw_id(instr_set)), Rn(X16))); return InstructionARM64(instrs); } InstructionARM64 load32_simd32_gpr64_plus_s8(Register simd_dest, Register base, s64 offset) { ASSERT(simd_dest.is_128bit_simd(instr_set)); ASSERT(base.is_gpr(instr_set)); ASSERT(offset >= INT8_MIN && offset <= INT8_MAX); // first establish the base+index value in x16 std::vector instrs = { // https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_imm.html // ADD , , #{, } InstructionARM64(Base(0b100100010, 9), Sh(0), Imm12(0), Rd(X16), Rn(base.hw_id(instr_set))), }; // TODO - optimization, if its less than imm12 we can just do an add/sub auto mov_instrs = mov_gpr64_u64_instrs(X17, std::abs(offset)); for (const auto& instr : mov_instrs) { instrs.push_back(InstructionARM64(instr)); } if (offset < 0) { instrs.push_back(sub_gpr64_gpr64(X16, X17)); } else { instrs.push_back(add_gpr64_gpr64(X16, X17)); } // https://www.scs.stanford.edu/~zyedidia/arm64/ldr_imm_fpsimd.html // 32-bit variant // LDR , [{, #}] instrs.emplace_back( InstructionARM64(Base(0b1011110101, 10), Imm12(0), Rt(simd_dest.hw_id(instr_set)), Rn(X16))); return InstructionARM64(instrs); } InstructionARM64 load_goal_simd32(Register simd_dest, Register addr, Register off, s64 offset) { if (offset == 0) { return load32_simd32_gpr64_plus_gpr64(simd_dest, addr, off); } else if (offset >= INT8_MIN && offset <= INT8_MAX) { return load32_simd32_gpr64_plus_gpr64_plus_s8(simd_dest, addr, off, offset); } else if (offset >= INT32_MIN && offset <= INT32_MAX) { return load32_simd32_gpr64_plus_gpr64_plus_s32(simd_dest, addr, off, offset); } else { ASSERT(false); return {0}; } } InstructionARM64 store_goal_simd32(Register addr, Register simd_value, Register off, s64 offset) { if (offset == 0) { return store32_simd32_gpr64_plus_gpr64(addr, off, simd_value); } else if (offset >= INT8_MIN && offset <= INT8_MAX) { return store32_simd32_gpr64_plus_gpr64_plus_s8(addr, off, simd_value, offset); } else if (offset >= INT32_MIN && offset <= INT32_MAX) { return store32_simd32_gpr64_plus_gpr64_plus_s32(addr, off, simd_value, offset); } else { ASSERT(false); return {0}; } } InstructionARM64 store_reg_offset_simd32(Register base, Register simd_value, s64 offset) { if (offset >= INT8_MIN && offset <= INT8_MAX) { return store32_simd32_gpr64_plus_s8(base, simd_value, offset); } else if (offset >= INT32_MIN && offset <= INT32_MAX) { return store32_simd32_gpr64_plus_s32(base, simd_value, offset); } else { ASSERT(false); return {0}; } } InstructionARM64 load_reg_offset_simd32(Register simd_dest, Register base, s64 offset) { if (offset >= INT8_MIN && offset <= INT8_MAX) { return load32_simd32_gpr64_plus_s8(simd_dest, base, offset); } else if (offset >= INT32_MIN && offset <= INT32_MAX) { return load32_simd32_gpr64_plus_s32(simd_dest, base, offset); } else { ASSERT(false); return {0}; } } //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; // LOADS n' STORES - SIMD (128-bit, QWORDS) //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; InstructionARM64 store128_gpr64_simd128(Register gpr_addr, Register simd_reg) { // https://www.scs.stanford.edu/~zyedidia/arm64/str_imm_fpsimd.html // - STR Qn, [Xn] (unsigned offset) ASSERT(gpr_addr.is_gpr(instr_set)); ASSERT(simd_reg.is_128bit_simd(instr_set)); return InstructionARM64(Base(0b0011110110, 10), Rn(gpr_addr.hw_id(instr_set)), Rt(simd_reg.hw_id(instr_set)), Imm12(0)); } InstructionARM64 store128_gpr64_simd128_s32(Register gpr_addr, Register simd_value, s64 offset) { ASSERT(gpr_addr.is_gpr(instr_set)); ASSERT(simd_value.is_128bit_simd(instr_set)); ASSERT(offset >= INT32_MIN && offset <= INT32_MAX); // first establish the base+index value in x16 std::vector instrs = { // https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_imm.html // ADD , , #{, } InstructionARM64(Base(0b100100010, 9), Sh(0), Imm12(0), Rd(X16), Rn(gpr_addr.hw_id(instr_set))), }; if (offset < 0) { // we'll subtract instead offset = std::abs(offset); const auto sub_instrs = construct_multiple_imm12_subs(offset, X16); instrs.insert(instrs.end(), sub_instrs.begin(), sub_instrs.end()); } else { const auto add_instrs = construct_multiple_imm12_adds(offset, X16); instrs.insert(instrs.end(), add_instrs.begin(), add_instrs.end()); } // https://www.scs.stanford.edu/~zyedidia/arm64/str_imm_fpsimd.html // 128-bit variant, unsigned offset // STR , [{, #}] // width 10 keeps the literal in its opcode field instrs.emplace_back( InstructionARM64(Base(0b0011110110, 10), Imm12(0), Rt(simd_value.hw_id(instr_set)), Rn(X16))); return InstructionARM64(instrs); } InstructionARM64 store128_gpr64_simd128_s8(Register gpr_addr, Register simd_value, s64 offset) { ASSERT(gpr_addr.is_gpr(instr_set)); ASSERT(simd_value.is_128bit_simd(instr_set)); ASSERT(offset >= INT8_MIN && offset <= INT8_MAX); // first establish the base+index value in x16 std::vector instrs = { // https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_imm.html // ADD , , #{, } InstructionARM64(Base(0b100100010, 9), Sh(0), Imm12(0), Rd(X16), Rn(gpr_addr.hw_id(instr_set))), }; if (offset < 0) { // we'll subtract instead offset = std::abs(offset); const auto sub_instrs = construct_multiple_imm12_subs(offset, X16); instrs.insert(instrs.end(), sub_instrs.begin(), sub_instrs.end()); } else { const auto add_instrs = construct_multiple_imm12_adds(offset, X16); instrs.insert(instrs.end(), add_instrs.begin(), add_instrs.end()); } // https://www.scs.stanford.edu/~zyedidia/arm64/str_imm_fpsimd.html // 128-bit variant, unsigned offset // STR , [{, #}] // width 10 keeps the literal in its opcode field instrs.emplace_back( InstructionARM64(Base(0b0011110110, 10), Imm12(0), Rt(simd_value.hw_id(instr_set)), Rn(X16))); return InstructionARM64(instrs); } InstructionARM64 load128_simd128_gpr64(Register simd_dest, Register gpr_addr) { // https://www.scs.stanford.edu/~zyedidia/arm64/ldr_imm_fpsimd.html // - LDR , [{, #}] ASSERT(gpr_addr.is_gpr(instr_set)); ASSERT(simd_dest.is_128bit_simd(instr_set)); return InstructionARM64(Base(0b0011110111, 10), Rn(gpr_addr.hw_id(instr_set)), Rt(simd_dest.hw_id(instr_set)), Imm12(0)); } InstructionARM64 load128_simd128_gpr64_s32(Register simd_dest, Register gpr_addr, s64 offset) { ASSERT(gpr_addr.is_gpr(instr_set)); ASSERT(simd_dest.is_128bit_simd(instr_set)); ASSERT(offset >= INT32_MIN && offset <= INT32_MAX); // first establish the base+index value in x16 std::vector instrs = { // https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_imm.html // ADD , , #{, } InstructionARM64(Base(0b100100010, 9), Sh(0), Imm12(0), Rd(X16), Rn(gpr_addr.hw_id(instr_set))), }; if (offset < 0) { // we'll subtract instead offset = std::abs(offset); const auto sub_instrs = construct_multiple_imm12_subs(offset, X16); instrs.insert(instrs.end(), sub_instrs.begin(), sub_instrs.end()); } else { const auto add_instrs = construct_multiple_imm12_adds(offset, X16); instrs.insert(instrs.end(), add_instrs.begin(), add_instrs.end()); } // https://www.scs.stanford.edu/~zyedidia/arm64/ldr_imm_fpsimd.html // - LDR , [{, #}] instrs.emplace_back( InstructionARM64(Base(0b0011110111, 10), Rn(X16), Rt(simd_dest.hw_id(instr_set)), Imm12(0))); return InstructionARM64(instrs); } InstructionARM64 load128_simd128_gpr64_s8(Register simd_dest, Register gpr_addr, s64 offset) { ASSERT(gpr_addr.is_gpr(instr_set)); ASSERT(simd_dest.is_128bit_simd(instr_set)); ASSERT(offset >= INT8_MIN && offset <= INT8_MAX); // first establish the base+index value in x16 std::vector instrs = { // https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_imm.html // ADD , , #{, } InstructionARM64(Base(0b100100010, 9), Sh(0), Imm12(0), Rd(X16), Rn(gpr_addr.hw_id(instr_set))), }; if (offset < 0) { // we'll subtract instead offset = std::abs(offset); const auto sub_instrs = construct_multiple_imm12_subs(offset, X16); instrs.insert(instrs.end(), sub_instrs.begin(), sub_instrs.end()); } else { const auto add_instrs = construct_multiple_imm12_adds(offset, X16); instrs.insert(instrs.end(), add_instrs.begin(), add_instrs.end()); } // https://www.scs.stanford.edu/~zyedidia/arm64/ldr_imm_fpsimd.html // - LDR , [{, #}] instrs.emplace_back( InstructionARM64(Base(0b0011110111, 10), Rn(X16), Rt(simd_dest.hw_id(instr_set)), Imm12(0))); return InstructionARM64(instrs); } InstructionARM64 load128_simd128_reg_offset(Register simd_dest, Register base, s64 offset) { if (offset == 0) { return load128_simd128_gpr64(simd_dest, base); } else if (offset >= INT8_MIN && offset <= INT8_MAX) { return load128_simd128_gpr64_s8(simd_dest, base, offset); } else if (offset >= INT32_MIN && offset <= INT32_MAX) { return load128_simd128_gpr64_s32(simd_dest, base, offset); } else { ASSERT(false); return {0}; } } InstructionARM64 store128_simd128_reg_offset(Register base, Register simd_val, s64 offset) { if (offset == 0) { return store128_gpr64_simd128(base, simd_val); } else if (offset >= INT8_MIN && offset <= INT8_MAX) { return store128_gpr64_simd128_s8(base, simd_val, offset); } else if (offset >= INT32_MIN && offset <= INT32_MAX) { return store128_gpr64_simd128_s32(base, simd_val, offset); } else { ASSERT(false); return {0}; } } //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; // PC relative loads and stores //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; // Implement with LDR but that has a 1MB range limit on ARM (not 2GB like on x86) // Hopefully this is fine, however it could potentially not be if this is loading static data, which // may not within 1MB of the current instruction -- that all depends on the linker layout. // // But keep it simple at first, add good assertions and we'll see what happens when we // compile for real. // TODO ARM64 - the offsets here are always 0 at the time the instruction is made, // then they are patched later. That patching also needs an assertion. // const int ARM64_LDR_MIN = -(1 << 18) * 4; // const int ARM64_LDR_MAX = ((1 << 18) - 1) * 4; //! PC-relative static access does not work when EE mappings have different bases. InstructionARM64 load64_pcRel_s32(Register dest, s64 offset) { ASSERT(dest.is_gpr(instr_set)); ASSERT_MSG(offset != 0, "PC Relative offset isn't 0 at encoding time, actually encode it properly!"); // ASSERT_MSG(offset >= ARM64_LDR_MIN && offset <= ARM64_LDR_MAX, // "PC Relative offset is too large for ARM64, fix it."); // https://www.scs.stanford.edu/~zyedidia/arm64/ldr_lit_gen.html // LDR ,