#include "IGenARM64.h" #include #include "goalc/emitter/Instruction.h" #include "goalc/emitter/InstructionSet.h" #include "goalc/emitter/Register.h" // https://armconverter.com/?code=ret // https://developer.arm.com/documentation/ddi0487/latest // TODO ARM64 - just silencing errors while things are not implemented obviously #pragma GCC diagnostic ignored "-Wunused-parameter" 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}; } // 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) { // https://www.scs.stanford.edu/~zyedidia/arm64/mov_orr_log_shift.html // MOV , ASSERT(dst.is_gpr(instr_set)); ASSERT(src.is_gpr(instr_set)); return InstructionARM64(Base(0b10101010000, 11), Rm(src.id()), Rn(0b11111), Rd(dst.id()), Imm6(0)); } InstructionARM64 mov_gpr64_u64(Register dst, uint64_t val) { // Cannot be done in a single instruction, must combine multiple MOVZ/MOVKs std::vector instrs; bool emitted_movz = false; for (int i = 0; i < 4; i++) { u16 chunk = (val >> (i * 16)) & 0xFFFF; if (!emitted_movz && chunk != 0) { // https://www.scs.stanford.edu/~zyedidia/arm64/movz.html // MOVZ , #{, LSL #/16} instrs.emplace_back( InstructionARM64(Base(0b110100101, 9), Hw(i), Imm16(chunk), Rd(dst.id()))); emitted_movz = true; } else if (emitted_movz && chunk != 0) { // https://www.scs.stanford.edu/~zyedidia/arm64/movk.html // MOVK , #{, LSL #/16} instrs.emplace_back( InstructionARM64(Base(0b111100101, 9), Hw(i), Imm16(chunk), Rd(dst.id()))); } } if (!emitted_movz) { // https://www.scs.stanford.edu/~zyedidia/arm64/movz.html // MOVZ , #{, LSL #0} instrs.emplace_back(InstructionARM64(Base(0b110100101, 9), Hw(0), Imm16(0), Rd(dst.id()))); } return InstructionARM64(instrs); } 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 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.id()), Rd(dst.id())); } 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.id()), Rd(dst.id())); } 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.id()), Rd(dst.id())); } 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.id()), Rd(dst.id())); } 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.id()), Rd(dst.id())); } //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; // 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.id()), Rn(addr1.id()), Rm(addr2.id())); } 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.id()), Rn(addr1.id()), Rm(addr2.id())); } // TODO ARM64 - x16 needs to be reserved, started leveraging it here // yes it would be possible to only reserve it _sometimes_, but keep things simple // we have SO many more registers already over x86, 1 less isn't going to be that big of a deal 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); return InstructionARM64( {// https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_ext.html // ADD , , {, {#}} InstructionARM64(Base(0b1000101100100000111000, 22), Rd(X16), Rn(addr1.id()), Rm(addr2.id())), // https://www.scs.stanford.edu/~zyedidia/arm64/ldursb.html // LDURSB , [{, #}] InstructionARM64(Base(0b0011100010000000000000, 22), Imm9s(offset), Rt(dst.id()), 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.id()), Rm(addr2.id())), // https://www.scs.stanford.edu/~zyedidia/arm64/sturb.html // STURB , [{, #}] InstructionARM64(Base(0b0011100000000000000000, 22), Imm9s(offset), Rt(value.id()), 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.id()), Rm(addr2.id())), }; 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.id()), 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.id()), Rm(addr2.id())), }; 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.id()), 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.id()), Rn(addr1.id()), Rm(addr2.id())); } 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.id()), Rm(addr2.id())), // https://www.scs.stanford.edu/~zyedidia/arm64/ldrb_imm.html // Unsigned offset mode // LDRB , [], # InstructionARM64(Base(0b0011100101, 10), Imm12(offset), Rt(dst.id()), Rn(X16))}; } else { instrs = { // https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_ext.html // ADD , , {, {#}} InstructionARM64(Base(0b1000101100100000111000, 22), Rd(X16), Rn(addr1.id()), Rm(addr2.id())), // https://www.scs.stanford.edu/~zyedidia/arm64/ldurb.html // LDURB , [{, #}] InstructionARM64(Base(0b0011100001000000000000, 22), Imm9s(offset), Rt(dst.id()), 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.id()), Rm(addr2.id())), }; 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(offset), Rt(dst.id()), 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.id()), Rn(addr1.id()), Rm(addr2.id())); } 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(0b0111100000100000000010, 22), Rt(value.id()), Rn(addr1.id()), Rm(addr2.id())); } 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.id()), Rm(addr2.id())), // https://www.scs.stanford.edu/~zyedidia/arm64/sturh.html // STURH , [{, #}] InstructionARM64(Base(0b0111100000000000000000, 22), Imm9s(offset), Rt(value.id()), 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.id()), Rm(addr2.id())), }; 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.id()), 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); return InstructionARM64( {// https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_ext.html // ADD , , {, {#}} InstructionARM64(Base(0b1000101100100000111000, 22), Rd(X16), Rn(addr1.id()), Rm(addr2.id())), // https://www.scs.stanford.edu/~zyedidia/arm64/ldursh.html // LDURSH , [{, #}] InstructionARM64(Base(0b0111100010000000000000, 22), Imm9s(offset), Rt(dst.id()), 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.id()), Rm(addr2.id())), }; 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.id()), 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.id()), Rn(addr1.id()), Rm(addr2.id())); } 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); 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.id()), Rm(addr2.id())), // https://www.scs.stanford.edu/~zyedidia/arm64/ldrh_imm.html // Unsigned offset mode // LDRH , [{, #}] InstructionARM64(Base(0b0111100101, 10), Imm12(offset), Rt(dst.id()), Rn(X16))}; } else { instrs = { // https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_ext.html // ADD , , {, {#}} InstructionARM64(Base(0b1000101100100000111000, 22), Rd(X16), Rn(addr1.id()), Rm(addr2.id())), // https://www.scs.stanford.edu/~zyedidia/arm64/ldurh.html // LDURH , [{, #}] InstructionARM64(Base(0b0111100001000000000000, 22), Imm9s(offset), Rt(dst.id()), Rn(X16))}; } return InstructionARM64(instrs); } 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.id()), Rm(addr2.id())), }; 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(offset), Rt(dst.id()), 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.id()), Rn(addr1.id()), Rm(addr2.id())); } 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.id()), Rn(addr1.id()), Rm(addr2.id())); } 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); return InstructionARM64( {// https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_ext.html // ADD , , {, {#}} InstructionARM64(Base(0b1000101100100000111000, 22), Rd(X16), Rn(addr1.id()), Rm(addr2.id())), // https://www.scs.stanford.edu/~zyedidia/arm64/ldursw.html // LDURSW , [{, #}] InstructionARM64(Base(0b1011100010000000000000, 22), Imm9s(offset), Rt(dst.id()), 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.id()), Rm(addr2.id())), }; 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.id()), 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.id()), Rm(addr2.id())), }; 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.id()), 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.id()), Rm(addr2.id())), }; 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.id()), 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 // 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(0b1011100001100000000010, 22), Rt(dst.id()), Rn(addr1.id()), Rm(addr2.id())); } 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); return InstructionARM64( {// https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_ext.html // ADD , , {, {#}} InstructionARM64(Base(0b1000101100100000111000, 22), Rd(X16), Rn(addr1.id()), Rm(addr2.id())), // https://www.scs.stanford.edu/~zyedidia/arm64/ldur_gen.html // 32 bit // LDUR , [{, #}] InstructionARM64(Base(0b1011100001000000000000, 22), Imm9s(offset), Rt(dst.id()), 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.id()), Rm(addr2.id())), }; 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/ldr_imm_gen.html // 32-bit variant // LDR , [], # instrs.emplace_back( InstructionARM64(Base(0b1011100001000000000001, 22), Imm9s(0), Rt(dst.id()), 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 // 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(0b1111100001100000000010, 22), Rt(dst.id()), Rn(addr1.id()), Rm(addr2.id())); } 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.id()), Rn(addr1.id()), Rm(addr2.id())); } 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); return InstructionARM64( {// https://www.scs.stanford.edu/~zyedidia/arm64/add_addsub_ext.html // ADD , , {, {#}} InstructionARM64(Base(0b1000101100100000111000, 22), Rd(X16), Rn(addr1.id()), Rm(addr2.id())), // https://www.scs.stanford.edu/~zyedidia/arm64/ldur_gen.html // 64 bit // LDUR , [{, #}] InstructionARM64(Base(0b1111100001000000000000, 22), Imm9s(offset), Rt(dst.id()), 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.id()), Rm(addr2.id())), }; 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.id()), 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.id()), Rm(addr2.id())), }; 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/ldr_imm_gen.html // 64-bit variant // LDR , [], # instrs.emplace_back( InstructionARM64(Base(0b1111100001000000000001, 22), Imm9s(0), Rt(dst.id()), 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.id()), Rm(addr2.id())), }; 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.id()), 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)); ASSERT(src_reg != SP); 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.id())), }; 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/ldr_imm_gen.html // 64-bit variant // LDR , [], # instrs.emplace_back( InstructionARM64(Base(0b1111100001000000000001, 22), Imm9s(0), Rt(dst_reg.id()), 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)); ASSERT(addr != SP); 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.id())), }; 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.id()), 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_xmm128(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.id()), Rn(base.id())), }; if (offset < 0) { // we'll subtract instead offset = std::abs(offset); const auto sub_instrs = construct_multiple_imm12_subs(offset, dest.id()); instrs.insert(instrs.end(), sub_instrs.begin(), sub_instrs.end()); } else { const auto add_instrs = construct_multiple_imm12_adds(offset, dest.id()); 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.id()), Rn(base.id())), }; if (offset < 0) { // we'll subtract instead offset = std::abs(offset); const auto sub_instrs = construct_multiple_imm12_subs(offset, dest.id()); instrs.insert(instrs.end(), sub_instrs.begin(), sub_instrs.end()); } else { const auto add_instrs = construct_multiple_imm12_adds(offset, dest.id()); 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 - XMM32 //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; // TODO - rename these to f32 instead of xmm InstructionARM64 store32_xmm32_gpr64_plus_gpr64(Register addr1, Register addr2, Register xmm_value) { ASSERT(xmm_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(0b1011110000100000000010, 22), Rt(xmm_value.id()), Rm(addr1.id()), Rn(addr2.id())); } InstructionARM64 load32_xmm32_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.id()), Rm(addr1.id()), Rn(addr2.id())); } InstructionARM64 store32_xmm32_gpr64_plus_gpr64_plus_s8(Register addr1, Register addr2, Register xmm_value, s64 offset) { ASSERT(xmm_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.id()), Rm(addr2.id())), }; 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 // 32-bit variant // STR , [], # instrs.emplace_back( InstructionARM64(Base(0b1011110100000000000001, 22), Imm12(0), Rt(xmm_value.id()), Rn(X16))); return InstructionARM64(instrs); } InstructionARM64 load32_xmm32_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.id()), Rm(addr2.id())), }; 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 // 32-bit variant // LDR , [{, #}] instrs.emplace_back( InstructionARM64(Base(0b1011110101, 10), Imm12(0), Rt(simd_dest.id()), Rn(X16))); return InstructionARM64(instrs); } InstructionARM64 store32_xmm32_gpr64_plus_gpr64_plus_s32(Register addr1, Register addr2, Register xmm_value, s64 offset) { ASSERT(xmm_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.id()), Rm(addr2.id())), }; 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 // 32-bit variant // STR , [], # instrs.emplace_back( InstructionARM64(Base(0b1011110100000000000001, 22), Imm12(0), Rt(xmm_value.id()), Rn(X16))); return InstructionARM64(instrs); } InstructionARM64 store32_xmm32_gpr64_plus_s32(Register base, Register xmm_value, s64 offset) { ASSERT(xmm_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.id())), }; 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 // 32-bit variant // STR , [], # instrs.emplace_back( InstructionARM64(Base(0b1011110100000000000001, 22), Imm12(0), Rt(xmm_value.id()), Rn(X16))); return InstructionARM64(instrs); } InstructionARM64 store32_xmm32_gpr64_plus_s8(Register base, Register xmm_value, s64 offset) { ASSERT(xmm_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.id())), }; 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 // 32-bit variant, unsigned // STR , [], # instrs.emplace_back( InstructionARM64(Base(0b1011110100000000000001, 22), Imm12(0), Rt(xmm_value.id()), Rn(X16))); return InstructionARM64(instrs); } InstructionARM64 load32_xmm32_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.id()), Rm(addr2.id())), }; 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 // 32-bit variant // LDR , [{, #}] instrs.emplace_back( InstructionARM64(Base(0b1011110101, 10), Imm12(0), Rt(simd_dest.id()), Rn(X16))); return InstructionARM64(instrs); } InstructionARM64 load32_xmm32_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.id())), }; 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 // 32-bit variant // LDR , [{, #}] instrs.emplace_back( InstructionARM64(Base(0b1011110101, 10), Imm12(0), Rt(simd_dest.id()), Rn(X16))); return InstructionARM64(instrs); } InstructionARM64 load32_xmm32_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.id())), }; 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 // 32-bit variant // LDR , [{, #}] instrs.emplace_back( InstructionARM64(Base(0b1011110101, 10), Imm12(0), Rt(simd_dest.id()), Rn(X16))); return InstructionARM64(instrs); } InstructionARM64 load_goal_xmm32(Register simd_dest, Register addr, Register off, s64 offset) { if (offset == 0) { return load32_xmm32_gpr64_plus_gpr64(simd_dest, addr, off); } else if (offset >= INT8_MIN && offset <= INT8_MAX) { return load32_xmm32_gpr64_plus_gpr64_plus_s8(simd_dest, addr, off, offset); } else if (offset >= INT32_MIN && offset <= INT32_MAX) { return load32_xmm32_gpr64_plus_gpr64_plus_s32(simd_dest, addr, off, offset); } else { ASSERT(false); return {0}; } } InstructionARM64 store_goal_xmm32(Register addr, Register xmm_value, Register off, s64 offset) { if (offset == 0) { return store32_xmm32_gpr64_plus_gpr64(addr, off, xmm_value); } else if (offset >= INT8_MIN && offset <= INT8_MAX) { return store32_xmm32_gpr64_plus_gpr64_plus_s8(addr, off, xmm_value, offset); } else if (offset >= INT32_MIN && offset <= INT32_MAX) { return store32_xmm32_gpr64_plus_gpr64_plus_s32(addr, off, xmm_value, offset); } else { ASSERT(false); return {0}; } } InstructionARM64 store_reg_offset_xmm32(Register base, Register xmm_value, s64 offset) { if (offset >= INT8_MIN && offset <= INT8_MAX) { return store32_xmm32_gpr64_plus_s8(base, xmm_value, offset); } else if (offset >= INT32_MIN && offset <= INT32_MAX) { return store32_xmm32_gpr64_plus_s32(base, xmm_value, offset); } else { ASSERT(false); return {0}; } } InstructionARM64 load_reg_offset_xmm32(Register simd_dest, Register base, s64 offset) { if (offset >= INT8_MIN && offset <= INT8_MAX) { return load32_xmm32_gpr64_plus_s8(simd_dest, base, offset); } else if (offset >= INT32_MIN && offset <= INT32_MAX) { return load32_xmm32_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)); // TODO ARM64 - this assertion isn't as useful for ARM // since Q registers are not unique in terms of their id return InstructionARM64(Base(0b0011110110, 10), Rn(gpr_addr.id()), Rt(simd_reg.id()), Imm12(0)); } InstructionARM64 store128_gpr64_simd128_s32(Register gpr_addr, Register xmm_value, s64 offset) { ASSERT(gpr_addr.is_gpr(instr_set)); ASSERT(xmm_value.is_128bit_simd( instr_set)); // TODO ARM64 - this assertion isn't as useful for ARM // since Q registers are not unique in terms of their id 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.id())), }; 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 , [{, #}] instrs.emplace_back( InstructionARM64(Base(0b0011110110, 22), Imm12(0), Rt(xmm_value.id()), Rn(X16))); return InstructionARM64(instrs); } InstructionARM64 store128_gpr64_simd128_s8(Register gpr_addr, Register xmm_value, s64 offset) { ASSERT(gpr_addr.is_gpr(instr_set)); ASSERT(xmm_value.is_128bit_simd( instr_set)); // TODO ARM64 - this assertion isn't as useful for ARM // since Q registers are not unique in terms of their id 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.id())), }; 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 , [{, #}] instrs.emplace_back( InstructionARM64(Base(0b0011110110, 22), Imm12(0), Rt(xmm_value.id()), 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)); // TODO ARM64 - this assertion isn't as useful for ARM // since Q registers are not unique in terms of their id return InstructionARM64(Base(0b0011110111, 10), Rn(gpr_addr.id()), Rt(simd_dest.id()), 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)); // TODO ARM64 - this assertion isn't as useful for ARM // since Q registers are not unique in terms of their id 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.id())), }; 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.id()), 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)); // TODO ARM64 - this assertion isn't as useful for ARM // since Q registers are not unique in terms of their id 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.id())), }; 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.id()), Imm12(0))); return InstructionARM64(instrs); } InstructionARM64 load128_xmm128_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_xmm128_reg_offset(Register base, Register xmm_val, s64 offset) { if (offset == 0) { return store128_gpr64_simd128(base, xmm_val); } else if (offset >= INT8_MIN && offset <= INT8_MAX) { return store128_gpr64_simd128_s8(base, xmm_val, offset); } else if (offset >= INT32_MIN && offset <= INT32_MAX) { return store128_gpr64_simd128_s32(base, xmm_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; 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 ,