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