From 76941379e90222212c2e26fa5a0eda330e266510 Mon Sep 17 00:00:00 2001 From: Tyler Wilding Date: Fri, 3 Jul 2026 19:40:29 -0400 Subject: [PATCH] goalc: Finish converting all load/store instructions to ARM64 (#4334) All that remains (8 instructions) are division, and NEON instructions that require me to convert the x86 control byte to NEON `TBL` values. Those handful of instructions can be done later while doing the next steps (finally something more interesting than just encoding instructions). --- .github/workflows/offline-tests.yaml | 9 +- goalc/emitter/CodeTester.h | 30 +- goalc/emitter/IGenARM64.cpp | 1857 ++++++++++++++--- goalc/emitter/IGenX86.cpp | 7 +- goalc/emitter/Instruction.h | 10 + test/CMakeLists.txt | 5 +- test/emitter_util.cpp | 83 + test/emitter_util.h | 21 + test/test_emitter_arm64.cpp | 44 + test/test_emitter_avx.cpp | 2 + test/test_emitter_neon.cpp | 0 ...{test_emitter.cpp => test_emitter_x86.cpp} | 264 +-- 12 files changed, 1885 insertions(+), 447 deletions(-) create mode 100644 test/emitter_util.cpp create mode 100644 test/emitter_util.h create mode 100644 test/test_emitter_arm64.cpp create mode 100644 test/test_emitter_neon.cpp rename test/{test_emitter.cpp => test_emitter_x86.cpp} (94%) diff --git a/.github/workflows/offline-tests.yaml b/.github/workflows/offline-tests.yaml index ada9c151b9..54b01b6f2b 100644 --- a/.github/workflows/offline-tests.yaml +++ b/.github/workflows/offline-tests.yaml @@ -1,4 +1,4 @@ -name: 🧪 Offline Tests +name: Tests on: push: @@ -18,9 +18,10 @@ concurrency: jobs: validate-author: - name: Verify Permissions + name: Verify Offline Test Permissions runs-on: self-hosted - if: github.repository == 'open-goal/jak-project' + if: github.repository == 'open-goal/jak-project' && + (github.event_name != 'issue_comment' || github.event.issue.pull_request != null) timeout-minutes: 5 outputs: trusted: ${{ steps.verify.outputs.trusted }} @@ -77,7 +78,7 @@ jobs: rm -rf ./.tmp-ci offline-tests: - name: Run Tests + name: 🧪 Run Offline Tests needs: validate-author if: needs.validate-author.outputs.trusted == 'true' && github.repository == 'open-goal/jak-project' runs-on: self-hosted diff --git a/goalc/emitter/CodeTester.h b/goalc/emitter/CodeTester.h index 7db9c3d716..6c1ceb1387 100644 --- a/goalc/emitter/CodeTester.h +++ b/goalc/emitter/CodeTester.h @@ -19,6 +19,11 @@ #include "goalc/emitter/InstructionSet.h" #include "goalc/emitter/ObjectGenerator.h" +#ifdef OS_POSIX +#include +#elif _WIN32 +#include "third-party/mman/mman.h" +#endif namespace emitter { class CodeTester { @@ -49,12 +54,29 @@ class CodeTester { */ template T execute_ret(u64 in0, u64 in1, u64 in2, u64 in3) { +#if defined(__aarch64__) + // allegedly needed because ARM requires flushing after writing new instructions + // on x86 it does nothing + __builtin___clear_cache((char*)code_buffer, (char*)code_buffer + code_buffer_size); +#endif // clang-format off - u64 result_u64 = ((u64(*)(u64, u64, u64, u64))code_buffer)(in0, in1, in2, in3); +#if defined(__APPLE__) && defined(__aarch64__) + // TODO - we may need to switch to using pthread_jit_write_protect_np + // there may also be issues if multiple threasd are involved + // but this seems to work so keep it simple until something proves otherwise. + mprotect(code_buffer, code_buffer_capacity, PROT_EXEC | PROT_READ); + u64 result_u64 = ((u64(*)(u64, u64, u64, u64))code_buffer)(in0, in1, in2, in3); + mprotect(code_buffer, code_buffer_capacity, PROT_WRITE | PROT_READ); + T result_T; + memcpy(&result_T, &result_u64, sizeof(T)); + return result_T; +#else + u64 result_u64 = ((u64(*)(u64, u64, u64, u64))code_buffer)(in0, in1, in2, in3); + T result_T; + memcpy(&result_T, &result_u64, sizeof(T)); + return result_T; +#endif // clang-format on - T result_T; - memcpy(&result_T, &result_u64, sizeof(T)); - return result_T; } /*! diff --git a/goalc/emitter/IGenARM64.cpp b/goalc/emitter/IGenARM64.cpp index e70d5eef54..0d3e39ec37 100644 --- a/goalc/emitter/IGenARM64.cpp +++ b/goalc/emitter/IGenARM64.cpp @@ -16,13 +16,74 @@ namespace emitter { namespace IGen { namespace ARM64 { -//;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -// MOVES -//;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 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))); + } +} + +//;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +// MOVES +//;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + InstructionARM64 mov_gpr64_gpr64(Register dst, Register src) { // https://www.scs.stanford.edu/~zyedidia/arm64/mov_orr_log_shift.html // MOV , @@ -119,250 +180,924 @@ InstructionARM64 load8s_gpr64_gpr64_plus_gpr64(Register dst, Register addr1, Reg 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 - ASSERT_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + // 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_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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) { - ASSERT_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + // 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_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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) { - ASSERT_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + // 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) { - ASSERT_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + // 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_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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) { - ASSERT_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + // 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_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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) { - ASSERT_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + // 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) { - ASSERT_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + // 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_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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) { - ASSERT_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + // 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_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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) { - ASSERT_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + // 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) { - ASSERT_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + // 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_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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) { - ASSERT_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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) { - ASSERT_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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) { - ASSERT_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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, @@ -371,110 +1106,455 @@ InstructionARM64 load_goal_gpr(Register dst, int offset, int size, bool sign_extend) { - ASSERT_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); -} - -InstructionARM64 lea_reg_plus_off32(Register dest, Register base, s64 offset) { - ASSERT_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); -} - -InstructionARM64 lea_reg_plus_off8(Register dest, Register base, s64 offset) { - ASSERT_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); -} - -InstructionARM64 lea_reg_plus_off(Register dest, Register base, s64 offset) { - ASSERT_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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) { - ASSERT_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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) { - ASSERT_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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) { - ASSERT_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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) { - ASSERT_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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}; + } } //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -492,13 +1572,61 @@ InstructionARM64 store128_gpr64_simd128(Register gpr_addr, Register simd_reg) { } InstructionARM64 store128_gpr64_simd128_s32(Register gpr_addr, Register xmm_value, s64 offset) { - ASSERT_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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) { @@ -512,23 +1640,85 @@ InstructionARM64 load128_simd128_gpr64(Register simd_dest, Register gpr_addr) { } InstructionARM64 load128_simd128_gpr64_s32(Register simd_dest, Register gpr_addr, s64 offset) { - ASSERT_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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) { - ASSERT_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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) { - ASSERT_MSG(false, "not yet implemented"); - return InstructionARM64(0b0); + 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}; + } } //;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -539,16 +1729,21 @@ InstructionARM64 store128_xmm128_reg_offset(Register base, Register xmm_val, s64 // 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. // -// TODO ARM - But keep it simple at first, add good assertions and we'll see what happens when we +// But keep it simple at first, add good assertions and we'll see what happens when we // compile for real. -const int ARM64_LDR_MIN = -(1 << 18) * 4; -const int ARM64_LDR_MAX = ((1 << 18) - 1) * 4; +// 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 >= ARM64_LDR_MIN && offset <= ARM64_LDR_MAX, - "PC Relative offset is too large for ARM64, fix it."); + 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 ,