6#include "tpde/AssemblerElf.hpp"
7#include "tpde/AssignmentPartRef.hpp"
8#include "tpde/CompilerBase.hpp"
9#include "tpde/DWARF.hpp"
10#include "tpde/ELF.hpp"
11#include "tpde/arm64/FunctionWriterA64.hpp"
12#include "tpde/base.hpp"
13#include "tpde/util/SmallVector.hpp"
14#include "tpde/util/misc.hpp"
20#if defined(ASM) || defined(ASMNC) || defined(ASMC)
21 #error Got definition for ASM macros from somewhere else. Maybe you included compilers for multiple architectures?
25#define ASMC(compiler, op, ...) \
26 ((compiler)->text_writer.write_inst(de64_##op(__VA_ARGS__)))
28#define ASM(...) ASMC(this, __VA_ARGS__)
30#define ASMNC(op, ...) \
31 (this->text_writer.write_inst_unchecked(de64_##op(__VA_ARGS__)))
33#define ASMIFC(compiler, op, ...) \
34 ((compiler)->text_writer.try_write_inst(de64_##op(__VA_ARGS__)))
36#define ASMIF(...) ASMIFC(this, __VA_ARGS__)
111 constexpr explicit AsmReg() : Reg((u8)0xFF) {}
113 constexpr AsmReg(
const REG
id) : Reg((u8)id) {}
115 constexpr AsmReg(
const Reg base) : Reg(base) {}
117 constexpr explicit AsmReg(
const u64
id) : Reg(id) {
118 assert(
id <= SP || (
id >= V0 &&
id <= V31));
121 operator DA_GReg()
const {
123 return DA_GReg{reg_id};
126 operator DA_GRegZR()
const {
128 assert(reg_id != SP);
129 return DA_GRegZR{reg_id};
132 operator DA_GRegSP()
const {
133 assert(reg_id <= SP);
134 return DA_GRegSP{reg_id};
137 operator DA_VReg()
const {
138 assert(reg_id >= V0 && reg_id <= V31);
139 return DA_VReg{
static_cast<u8
>(reg_id - V0)};
144 create_bitmask(
const std::initializer_list<AsmReg::REG> regs) {
146 for (
const auto reg : regs) {
153constexpr static u64 create_bitmask(
const std::array<AsmReg, N> regs) {
155 for (
const auto reg : regs) {
156 set |= 1ull << reg.id();
162class CCAssignerAAPCS :
public CCAssigner {
163 static constexpr CCInfo Info{
166 0xFFFF'FFFF'FFFF'FFFF &
167 ~create_bitmask({AsmReg::SP, AsmReg::FP, AsmReg::R16, AsmReg::R17}),
169 .callee_saved_regs = create_bitmask({
189 .arg_regs = create_bitmask({
213 u32 ngrn = 0, nsrn = 0, nsaa = 0;
214 u32 ret_ngrn = 0, ret_nsrn = 0;
217 CCAssignerAAPCS() : CCAssigner(Info) {}
219 void reset()
override { ngrn = nsrn = nsaa = ret_ngrn = ret_nsrn = 0; }
221 void assign_arg(CCAssignment &arg)
override {
222 if (arg.byval) [[unlikely]] {
223 nsaa = util::align_up(nsaa, arg.align < 8 ? 8 : arg.align);
224 arg.stack_off = nsaa;
229 if (arg.sret) [[unlikely]] {
230 arg.reg = AsmReg{AsmReg::R8};
234 if (arg.bank == RegBank{0}) {
236 ngrn = util::align_up(ngrn, 2);
238 if (ngrn + arg.consecutive < 8) {
239 arg.reg = Reg{AsmReg::R0 + ngrn};
243 nsaa = util::align_up(nsaa, arg.align < 8 ? 8 : arg.align);
244 arg.stack_off = nsaa;
248 if (nsrn + arg.consecutive < 8) {
249 arg.reg = Reg{AsmReg::V0 + nsrn};
253 u32 size = util::align_up(arg.size, 8);
254 nsaa = util::align_up(nsaa, size);
255 arg.stack_off = nsaa;
261 u32 get_stack_size()
override {
return nsaa; }
263 void assign_ret(CCAssignment &arg)
override {
264 assert(!arg.byval && !arg.sret);
265 if (arg.bank == RegBank{0}) {
267 ret_ngrn = util::align_up(ret_ngrn, 2);
269 if (ret_ngrn + arg.consecutive < 8) {
270 arg.reg = Reg{AsmReg::R0 + ret_ngrn};
276 if (ret_nsrn + arg.consecutive < 8) {
277 arg.reg = Reg{AsmReg::V0 + ret_nsrn};
286struct PlatformConfig : CompilerConfigDefault {
287 using Assembler = tpde::elf::AssemblerElfA64;
288 using AsmReg = tpde::a64::AsmReg;
292 static constexpr RegBank GP_BANK{0};
293 static constexpr RegBank FP_BANK{1};
294 static constexpr bool FRAME_INDEXING_NEGATIVE =
false;
295 static constexpr u32 PLATFORM_POINTER_SIZE = 8;
296 static constexpr u32 NUM_BANKS = 2;
300template <IRAdaptor Adaptor,
302 template <
typename,
typename,
typename>
typename BaseTy =
304 typename Config = PlatformConfig>
305struct CompilerA64 : BaseTy<Adaptor, Derived, Config> {
306 using Base = BaseTy<Adaptor, Derived, Config>;
308 using IRValueRef =
typename Base::IRValueRef;
309 using IRBlockRef =
typename Base::IRBlockRef;
310 using IRFuncRef =
typename Base::IRFuncRef;
312 using ScratchReg =
typename Base::ScratchReg;
313 using ValuePartRef =
typename Base::ValuePartRef;
314 using ValuePart =
typename Base::ValuePart;
315 using GenericValuePart =
typename Base::GenericValuePart;
317 using RegisterFile =
typename Base::RegisterFile;
319 using CallArg =
typename Base::CallArg;
326 static constexpr u32 NUM_FIXED_ASSIGNMENTS[PlatformConfig::NUM_BANKS] = {5,
331 static constexpr u32 MaxStaticAllocaSize = 0x100000;
333 enum CPU_FEATURES : u32 {
337 CPU_FEATURES cpu_feats = CPU_BASELINE;
344 u64 fixed_assignment_nonallocatable_mask =
345 create_bitmask({AsmReg::R0, AsmReg::R1});
346 u32 func_start_off = 0u, func_prologue_alloc = 0u;
350 AsmReg func_arg_stack_add_reg = AsmReg::make_invalid();
356 u32 scalar_arg_count = 0xFFFF'FFFF, vec_arg_count = 0xFFFF'FFFF;
357 u32 reg_save_frame_off = 0;
358 util::SmallVector<u32, 8> func_ret_offs = {};
361 class CallBuilder :
public Base::template CallBuilderBase<CallBuilder> {
362 u32 stack_adjust_off = 0;
366 void set_stack_used();
371 : Base::template CallBuilderBase<
CallBuilder>(compiler, assigner) {}
373 void add_arg_byval(ValuePart &vp, CCAssignment &cca);
374 void add_arg_stack(ValuePart &vp, CCAssignment &cca);
375 void call_impl(std::variant<SymRef, ValuePart> &&);
381 const CPU_FEATURES cpu_features = CPU_BASELINE)
382 : Base{adaptor}, cpu_feats(cpu_features) {
383 static_assert(std::is_base_of_v<CompilerA64, Derived>);
386 void start_func(u32) {}
397 void finish_func(u32 func_idx);
401 void gen_func_epilog();
403 void spill_reg(
const AsmReg reg,
const u32 frame_off,
const u32 size);
405 void load_from_stack(AsmReg dst,
408 bool sign_extend =
false);
410 void load_address_of_stack_var(AsmReg dst, AssignmentPartRef ap);
414 void load_off(AsmReg dst, AsmReg base, u32 off, u32 size,
bool sext =
false);
418 void store_off(AsmReg base, u32 off, AsmReg src, u32 size);
420 void mov(AsmReg dst, AsmReg src, u32 size);
422 GenericValuePart val_spill_slot(AssignmentPartRef ap) {
423 assert(ap.stack_valid() && !ap.variable_ref());
424 return typename GenericValuePart::Expr(AsmReg::R29, ap.frame_off());
427 AsmReg gval_expr_as_reg(GenericValuePart &gv);
444 assert(size <=
sizeof(const_u64));
448 AsmReg select_fixed_assignment_reg(AssignmentPartRef, IRValueRef);
486 constexpr Jump(
Kind kind) : kind(kind), cmp_is_32(false), test_bit(0) {
487 assert(kind !=
Cbz && kind !=
Cbnz && kind !=
Tbz && kind !=
Tbnz);
491 constexpr Jump(
Kind kind, AsmReg cmp_reg,
bool cmp_is_32)
492 : kind(kind), cmp_reg(cmp_reg), cmp_is_32(cmp_is_32), test_bit(0) {
493 assert(kind ==
Cbz || kind ==
Cbnz);
497 constexpr Jump(
Kind kind, AsmReg cmp_reg, u8 test_bit)
498 : kind(kind), cmp_reg(cmp_reg), cmp_is_32(false), test_bit(test_bit) {
499 assert(kind ==
Tbz || kind ==
Tbnz);
502 constexpr Jump change_kind(
Kind new_kind)
const {
509 Jump invert_jump(Jump jmp);
510 Jump swap_jump(Jump jmp);
526 Jump cc, AsmReg dst, AsmReg true_select, AsmReg false_select,
bool is_64);
533 ASM(BFIx, dst, src, lsb, width);
537 ASM(UBFIZx, dst, src, lsb, width);
551 std::span<CallArg> arguments,
552 typename Base::ValueRef *result,
553 bool variable_args =
false);
557 void switch_emit_cmp(AsmReg cmp_reg,
564 void switch_emit_cmpeq(Label case_label,
570 FunctionWriterBase::JumpTable *switch_create_jump_table(Label default_label,
577 void switch_emit_binary_step(Label case_label,
588 bool has_cpu_feats(CPU_FEATURES feats)
const {
589 return ((cpu_feats & feats) == feats);
593template <IRAdaptor Adaptor,
595 template <
typename,
typename,
typename>
class BaseTy,
597void CompilerA64<Adaptor, Derived, BaseTy, Config>::CallBuilder::
599 if (stack_adjust_off == 0) {
600 this->compiler.text_writer.ensure_space(16);
601 stack_adjust_off = this->compiler.text_writer.offset();
602 this->compiler.text_writer.cur_ptr() += 4;
606template <IRAdaptor Adaptor,
608 template <
typename,
typename,
typename>
class BaseTy,
610void CompilerA64<Adaptor, Derived, BaseTy, Config>::CallBuilder::add_arg_byval(
611 ValuePart &vp, CCAssignment &cca) {
612 AsmReg ptr_reg = vp.load_to_reg(&this->compiler);
614 ScratchReg scratch{&this->compiler};
615 AsmReg tmp = scratch.alloc_gp();
620 while (size - off >= 8) {
621 this->compiler.load_off(tmp, ptr_reg, off, 8);
622 this->compiler.store_off(AsmReg{AsmReg::SP}, cca.stack_off + off, tmp, 8);
625 if (size - off >= 4) {
626 this->compiler.load_off(tmp, ptr_reg, off, 4);
627 this->compiler.store_off(AsmReg{AsmReg::SP}, cca.stack_off + off, tmp, 4);
630 if (size - off >= 2) {
631 this->compiler.load_off(tmp, ptr_reg, off, 2);
632 this->compiler.store_off(AsmReg{AsmReg::SP}, cca.stack_off + off, tmp, 2);
635 if (size - off >= 1) {
636 this->compiler.load_off(tmp, ptr_reg, off, 1);
637 this->compiler.store_off(AsmReg{AsmReg::SP}, cca.stack_off + off, tmp, 1);
641template <IRAdaptor Adaptor,
643 template <
typename,
typename,
typename>
class BaseTy,
645void CompilerA64<Adaptor, Derived, BaseTy, Config>::CallBuilder::add_arg_stack(
646 ValuePart &vp, CCAssignment &cca) {
648 auto reg = vp.cur_reg_or_load(&this->compiler);
649 this->compiler.store_off(AsmReg{AsmReg::SP}, cca.stack_off, reg, cca.size);
652template <IRAdaptor Adaptor,
654 template <
typename,
typename,
typename>
class BaseTy,
656void CompilerA64<Adaptor, Derived, BaseTy, Config>::CallBuilder::call_impl(
657 std::variant<SymRef, ValuePart> &&target) {
659 if (stack_adjust_off != 0) {
660 auto *text_data = this->compiler.text_writer.begin_ptr();
661 u32 *write_ptr =
reinterpret_cast<u32 *
>(text_data + stack_adjust_off);
662 u32 stack_size = this->assigner.get_stack_size();
663 sub = util::align_up(stack_size, stack_size < 0x1000 ? 0x10 : 0x1000);
664 *write_ptr = de64_SUBxi(DA_SP, DA_SP, sub);
666 assert(this->assigner.get_stack_size() == 0);
671 auto fp_regs = RegisterFile::bank_regs(Config::FP_BANK);
672 auto fp_csrs = fp_regs & this->assigner.get_ccinfo().callee_saved_regs;
673 auto used_fp_csrs = fp_csrs & this->compiler.register_file.used;
674 for (
auto reg_id : util::BitSetIterator<>{used_fp_csrs}) {
676 ValLocalIdx local_idx = this->compiler.register_file.reg_local_idx(reg);
677 auto part = this->compiler.register_file.reg_part(reg);
678 AssignmentPartRef ap{this->compiler.val_assignment(local_idx), part};
679 if (ap.part_size() > 8) {
680 this->compiler.evict(ap);
684 if (
auto *sym = std::get_if<SymRef>(&target)) {
685 ASMC(&this->compiler, BL, 0);
686 this->compiler.reloc_text(
687 *sym, elf::R_AARCH64_CALL26, this->compiler.text_writer.offset() - 4);
689 ValuePart &tvp = std::get<ValuePart>(target);
690 if (tvp.can_salvage()) {
691 ASMC(&this->compiler, BLR, tvp.salvage(&this->compiler));
693 AsmReg reg = this->compiler.permanent_scratch_reg;
694 tvp.reload_into_specific_fixed(&this->compiler, reg);
695 ASMC(&this->compiler, BLR, reg);
697 tvp.reset(&this->compiler);
700 if (stack_adjust_off != 0) {
701 ASMC(&this->compiler, ADDxi, DA_SP, DA_SP, sub);
705template <IRAdaptor Adaptor,
707 template <
typename,
typename,
typename>
typename BaseTy,
710 CCAssigner *cc_assigner) {
711 func_ret_offs.clear();
712 func_start_off = this->text_writer.offset();
714 const CCInfo &cc_info = cc_assigner->get_ccinfo();
721 this->stack.frame_size = 16;
723 auto csr = cc_info.callee_saved_regs;
724 auto csr_gp = csr & this->register_file.bank_regs(Config::GP_BANK);
725 auto csr_fp = csr & this->register_file.bank_regs(Config::FP_BANK);
726 u32 gp_saves = std::popcount(csr_gp);
727 u32 fp_saves = std::popcount(csr_fp);
729 u32 reg_save_size = 4 * ((gp_saves + 1) / 2 + (fp_saves + 1) / 2);
731 this->stack.frame_size += util::align_up(gp_saves * 8 + fp_saves * 8, 16);
734 func_prologue_alloc = reg_save_size + 12;
735 this->text_writer.ensure_space(func_prologue_alloc);
736 this->text_writer.cur_ptr() += func_prologue_alloc;
741 if (this->adaptor->cur_is_vararg()) [[unlikely]] {
742 this->stack.frame_used =
true;
743 reg_save_frame_off = this->stack.frame_size;
747 this->stack.frame_size += 8 * 8 + 8 * 16 + 16;
748 this->text_writer.ensure_space(4 * 8);
749 ASMNC(STPx, DA_GP(0), DA_GP(1), DA_SP, reg_save_frame_off);
750 ASMNC(STPx, DA_GP(2), DA_GP(3), DA_SP, reg_save_frame_off + 16);
751 ASMNC(STPx, DA_GP(4), DA_GP(5), DA_SP, reg_save_frame_off + 32);
752 ASMNC(STPx, DA_GP(6), DA_GP(7), DA_SP, reg_save_frame_off + 48);
753 ASMNC(STPq, DA_V(0), DA_V(1), DA_SP, reg_save_frame_off + 64);
754 ASMNC(STPq, DA_V(2), DA_V(3), DA_SP, reg_save_frame_off + 96);
755 ASMNC(STPq, DA_V(4), DA_V(5), DA_SP, reg_save_frame_off + 128);
756 ASMNC(STPq, DA_V(6), DA_V(7), DA_SP, reg_save_frame_off + 160);
759 this->func_arg_stack_add_off = ~0u;
764 template <
typename,
typename,
typename>
typename BaseTy,
768 ValuePart &&vp, CCAssignment cca) {
769 if (cca.reg.valid()) [[likely]] {
770 vp.set_value_reg(
this, cca.reg);
774 this->register_file.allocatable |= u64{1} << cca.reg.id();
778 AsmReg dst = vp.alloc_reg(
this);
780 AsmReg stack_reg = AsmReg::R17;
782 assert(!(this->register_file.allocatable & (u64{1} << stack_reg.id())) &&
783 "x17 must not be allocatable");
784 if (this->func_arg_stack_add_off == ~0u) {
785 this->func_arg_stack_add_off = this->text_writer.offset();
786 this->func_arg_stack_add_reg = stack_reg;
788 ASM(ADDxi, stack_reg, DA_SP, 0);
792 if (!ASMIF(ADDxi, dst, stack_reg, cca.stack_off)) {
794 ASM(ADDx, dst, stack_reg, dst);
797 this->
load_off(dst, stack_reg, cca.stack_off, cca.size);
804 template <
typename,
typename,
typename>
typename BaseTy,
807 CCAssigner *cc_assigner) {
811 if (this->adaptor->cur_is_vararg()) [[unlikely]] {
812 this->stack.frame_used =
true;
813 AsmReg stack_reg = AsmReg::R17;
815 assert(!(this->register_file.allocatable & (u64{1} << stack_reg.id())) &&
816 "x17 must not be allocatable");
817 if (this->func_arg_stack_add_off == ~0u) {
818 this->func_arg_stack_add_off = this->text_writer.offset();
819 this->func_arg_stack_add_reg = stack_reg;
821 ASMC(
this, ADDxi, stack_reg, DA_SP, 0);
823 ASM(ADDxi, stack_reg, stack_reg, cc_assigner->get_stack_size());
824 ASM(STRxu, stack_reg, DA_GP(29), this->reg_save_frame_off + 192);
829 const CCInfo &cc_info = cc_assigner->get_ccinfo();
830 auto arg_regs = this->register_file.allocatable & cc_info.arg_regs;
831 u32 ngrn = 8 - util::cnt_lz<u16>((arg_regs & 0xff) << 8 | 0x80);
832 u32 nsrn = 8 - util::cnt_lz<u16>(((arg_regs >> 32) & 0xff) << 8 | 0x80);
833 this->scalar_arg_count = ngrn;
834 this->vec_arg_count = nsrn;
840 template <
typename,
typename,
typename>
typename BaseTy,
842void CompilerA64<Adaptor, Derived, BaseTy, Config>::finish_func(u32 func_idx) {
843 auto csr =
derived()->cur_cc_assigner()->get_ccinfo().callee_saved_regs;
844 u64 saved_regs = this->register_file.clobbered & csr;
846 auto stack_reg = DA_SP;
847 if (this->stack.has_dynamic_alloca) {
848 stack_reg = DA_GP(29);
851 auto final_frame_size = util::align_up(this->stack.frame_size, 16);
852 if (final_frame_size > 4095) {
854 final_frame_size = util::align_up(final_frame_size, 4096);
855 assert(final_frame_size < 16 * 1024 * 1024);
858 bool needs_stack_frame =
859 this->stack.frame_used || this->stack.generated_call ||
860 this->stack.has_dynamic_alloca || saved_regs != 0 ||
861 (this->register_file.clobbered & (u64{1} << AsmReg::LR));
863 this->text_writer.eh_begin_fde(this->get_personality_sym());
865 u32 prologue_size = 0;
866 if (needs_stack_frame) [[likely]] {
868 util::SmallVector<u32, 16> prologue;
874 if (!func_ret_offs.empty() && final_frame_size <= 0x1f8) {
875 this->text_writer.eh_write_inst(dwarf::DW_CFA_remember_state);
877 this->text_writer.eh_write_inst(dwarf::DW_CFA_advance_loc, 1);
878 this->text_writer.eh_write_inst(dwarf::DW_CFA_def_cfa_offset,
880 if (final_frame_size <= 0x1f8) {
882 de64_STPx_pre(DA_GP(29), DA_GP(30), DA_SP, -
int(final_frame_size)));
883 prologue.push_back(de64_MOV_SPx(DA_GP(29), DA_SP));
885 if (!func_ret_offs.empty()) {
886 this->text_writer.eh_write_inst(dwarf::DW_CFA_remember_state);
888 prologue.push_back(de64_SUBxi(DA_SP, DA_SP, final_frame_size));
889 prologue.push_back(de64_STPx(DA_GP(29), DA_GP(30), DA_SP, 0));
890 prologue.push_back(de64_MOV_SPx(DA_GP(29), DA_SP));
894 auto fde_prologue_adv_off = this->text_writer.eh_writer.size();
895 this->text_writer.eh_write_inst(dwarf::DW_CFA_advance_loc, 0);
896 this->text_writer.eh_write_inst(dwarf::DW_CFA_def_cfa_register,
897 dwarf::a64::DW_reg_fp);
898 this->text_writer.eh_write_inst(
899 dwarf::DW_CFA_offset, dwarf::a64::DW_reg_fp, final_frame_size / 8);
900 this->text_writer.eh_write_inst(
901 dwarf::DW_CFA_offset, dwarf::a64::DW_reg_lr, final_frame_size / 8 - 1);
903 AsmReg last_reg = AsmReg::make_invalid();
905 for (
auto reg : util::BitSetIterator{saved_regs}) {
906 u8 dwarf_base = reg < 32 ? dwarf::a64::DW_reg_x0 : dwarf::a64::DW_reg_v0;
907 u8 dwarf_reg = dwarf_base + reg % 32;
908 u32 cfa_off = (final_frame_size - frame_off) / 8 - last_reg.valid();
909 if ((dwarf_reg & dwarf::DWARF_CFI_PRIMARY_OPCODE_MASK) == 0) {
910 this->text_writer.eh_write_inst(
911 dwarf::DW_CFA_offset, dwarf_reg, cfa_off);
913 this->text_writer.eh_write_inst(
914 dwarf::DW_CFA_offset_extended, dwarf_reg, cfa_off);
917 if (last_reg.valid()) {
918 const auto reg_bank = this->register_file.reg_bank(AsmReg{reg});
919 const auto last_bank = this->register_file.reg_bank(last_reg);
920 if (reg_bank == last_bank) {
921 if (reg_bank == Config::GP_BANK) {
923 de64_STPx(last_reg, AsmReg{reg}, stack_reg, frame_off));
926 de64_STPd(last_reg, AsmReg{reg}, stack_reg, frame_off));
929 last_reg = AsmReg::make_invalid();
931 assert(last_bank == Config::GP_BANK && reg_bank == Config::FP_BANK);
932 prologue.push_back(de64_STRxu(last_reg, stack_reg, frame_off));
934 last_reg = AsmReg{reg};
937 last_reg = AsmReg{reg};
941 if (last_reg.valid()) {
942 if (this->register_file.reg_bank(last_reg) == Config::GP_BANK) {
943 prologue.push_back(de64_STRxu(last_reg, stack_reg, frame_off));
945 assert(this->register_file.reg_bank(last_reg) == Config::FP_BANK);
946 prologue.push_back(de64_STRdu(last_reg, stack_reg, frame_off));
950 assert(prologue.size() *
sizeof(u32) <= func_prologue_alloc);
952 assert(prologue.size() < 0x4c);
953 this->text_writer.eh_writer.data()[fde_prologue_adv_off] =
954 dwarf::DW_CFA_advance_loc | (prologue.size() - 1);
956 std::memcpy(this->text_writer.begin_ptr() + func_start_off,
958 prologue.size() *
sizeof(u32));
960 prologue_size = prologue.size() *
sizeof(u32);
965 u32 *inst_ptr =
reinterpret_cast<u32 *
>(raw_inst_ptr);
966 if (needs_stack_frame) {
967 *inst_ptr = de64_ADDxi(func_arg_stack_add_reg, DA_SP, final_frame_size);
969 *inst_ptr = de64_MOV_SPx(func_arg_stack_add_reg, DA_SP);
973 if (!func_ret_offs.empty()) {
974 u8 *text_data = this->text_writer.begin_ptr();
975 if (func_ret_offs.back() == this->text_writer.offset() - 4) {
976 this->text_writer.cur_ptr() -= 4;
977 func_ret_offs.pop_back();
979 for (
auto ret_off : func_ret_offs) {
980 u32 *write_ptr =
reinterpret_cast<u32 *
>(text_data + ret_off);
981 *write_ptr = de64_B((this->text_writer.offset() - ret_off) / 4);
985 this->text_writer.ensure_space(prologue_size + 4);
987 if (this->stack.has_dynamic_alloca) {
988 ASMNC(MOV_SPx, DA_SP, DA_GP(29));
991 AsmReg last_reg = AsmReg::make_invalid();
993 for (
auto reg : util::BitSetIterator{saved_regs}) {
994 if (last_reg.valid()) {
995 const auto reg_bank = this->register_file.reg_bank(AsmReg{reg});
996 const auto last_bank = this->register_file.reg_bank(last_reg);
997 if (reg_bank == last_bank) {
998 if (reg_bank == Config::GP_BANK) {
999 ASMNC(LDPx, last_reg, AsmReg{reg}, stack_reg, frame_off);
1001 ASMNC(LDPd, last_reg, AsmReg{reg}, stack_reg, frame_off);
1004 last_reg = AsmReg::make_invalid();
1006 assert(last_bank == Config::GP_BANK && reg_bank == Config::FP_BANK);
1007 ASMNC(LDRxu, last_reg, stack_reg, frame_off);
1009 last_reg = AsmReg{reg};
1014 last_reg = AsmReg{reg};
1017 if (last_reg.valid()) {
1018 if (this->register_file.reg_bank(last_reg) == Config::GP_BANK) {
1019 ASMNC(LDRxu, last_reg, stack_reg, frame_off);
1021 ASMNC(LDRdu, last_reg, stack_reg, frame_off);
1024 if (needs_stack_frame) {
1025 u32 body_start = func_start_off + func_prologue_alloc;
1026 this->text_writer.eh_advance(this->text_writer.offset() - body_start + 4);
1027 this->text_writer.eh_write_inst(dwarf::DW_CFA_restore_state);
1028 if (final_frame_size <= 0x1f8) {
1029 ASMNC(LDPx_post, DA_GP(29), DA_GP(30), DA_SP, final_frame_size);
1032 ASMNC(LDPx, DA_GP(29), DA_GP(30), DA_SP, 0);
1034 ASMNC(ADDxi, DA_SP, DA_SP, final_frame_size);
1035 this->text_writer.eh_write_inst(dwarf::DW_CFA_advance_loc, 1);
1036 this->text_writer.eh_write_inst(dwarf::DW_CFA_def_cfa_offset, 0);
1040 ASMNC(RET, DA_GP(30));
1044 this->text_writer.remove_prologue_bytes(func_start_off + prologue_size,
1045 func_prologue_alloc - prologue_size);
1046 auto func_size = this->text_writer.offset() - func_start_off;
1047 auto func_sym = this->func_syms[func_idx];
1048 auto func_sec = this->text_writer.get_sec_ref();
1049 this->assembler.sym_def(func_sym, func_sec, func_start_off, func_size);
1050 this->text_writer.eh_end_fde();
1051 this->text_writer.except_encode_func();
1054template <IRAdaptor Adaptor,
1056 template <
typename,
typename,
typename>
typename BaseTy,
1058void CompilerA64<Adaptor, Derived, BaseTy, Config>::gen_func_epilog() {
1060 func_ret_offs.push_back(this->text_writer.offset());
1061 this->text_writer.ensure_space(4);
1062 this->text_writer.cur_ptr() += 4;
1065template <IRAdaptor Adaptor,
1067 template <
typename,
typename,
typename>
typename BaseTy,
1069void CompilerA64<Adaptor, Derived, BaseTy, Config>::spill_reg(
1070 const AsmReg reg,
const u32 frame_off,
const u32 size) {
1071 assert(this->stack.frame_used);
1072 assert((size & (size - 1)) == 0);
1073 assert(util::align_up(frame_off, size) == frame_off);
1074 this->
store_off(AsmReg{AsmReg::FP}, frame_off, reg, size);
1077template <IRAdaptor Adaptor,
1079 template <
typename,
typename,
typename>
typename BaseTy,
1081void CompilerA64<Adaptor, Derived, BaseTy, Config>::load_from_stack(
1083 const i32 frame_off,
1085 const bool sign_extend) {
1086 assert(this->stack.frame_used);
1087 assert((size & (size - 1)) == 0);
1088 assert(util::align_up(frame_off, size) == frame_off);
1089 this->
load_off(dst, AsmReg{AsmReg::FP}, frame_off, size, sign_extend);
1092template <IRAdaptor Adaptor,
1094 template <
typename,
typename,
typename>
typename BaseTy,
1096void CompilerA64<Adaptor, Derived, BaseTy, Config>::load_address_of_stack_var(
1097 const AsmReg dst,
const AssignmentPartRef ap) {
1098 assert(this->stack.frame_used);
1099 auto frame_off = ap.variable_stack_off();
1100 assert(frame_off >= 0);
1101 if (!ASMIF(ADDxi, dst, DA_GP(29), frame_off)) {
1103 ASM(ADDx_uxtw, dst, DA_GP(29), dst, 0);
1107template <IRAdaptor Adaptor,
1109 template <
typename,
typename,
typename>
typename BaseTy,
1112 AsmReg dst, AsmReg base, u32 off, u32 size,
bool sext) {
1113 assert(size > 0 && (size & (size - 1)) == 0 &&
"size must be power of two");
1114 u32 off_mask = 0xfff * size;
1115 if (off & ~off_mask) [[unlikely]] {
1117 AsmReg old_base = base;
1119 if (ASMIF(ADDxi, base, old_base, off & ~off_mask)) {
1123 ASM(ADDx, base, base, old_base);
1128 this->text_writer.ensure_space(4);
1129 if (dst.id() <= AsmReg::R30) {
1132 case 1: ASMNC(LDRBu, dst, base, off);
break;
1133 case 2: ASMNC(LDRHu, dst, base, off);
break;
1134 case 4: ASMNC(LDRwu, dst, base, off);
break;
1135 case 8: ASMNC(LDRxu, dst, base, off);
break;
1136 default: TPDE_UNREACHABLE(
"invalid register size");
1140 case 1: ASMNC(LDRSBwu, dst, base, off);
break;
1141 case 2: ASMNC(LDRSHwu, dst, base, off);
break;
1142 case 4: ASMNC(LDRSWxu, dst, base, off);
break;
1143 case 8: ASMNC(LDRxu, dst, base, off);
break;
1144 default: TPDE_UNREACHABLE(
"invalid register size");
1153 case 1: ASMNC(LDRbu, dst, base, off);
break;
1154 case 2: ASMNC(LDRhu, dst, base, off);
break;
1155 case 4: ASMNC(LDRsu, dst, base, off);
break;
1156 case 8: ASMNC(LDRdu, dst, base, off);
break;
1157 case 16: ASMNC(LDRqu, dst, base, off);
break;
1158 default: TPDE_UNREACHABLE(
"invalid register size");
1164 template <
typename,
typename,
typename>
typename BaseTy,
1170 this->text_writer.ensure_space(8);
1172 assert(size > 0 && (size & (size - 1)) == 0 &&
"size must be power of two");
1173 u32 off_mask = 0xfff * size;
1174 if (off & ~off_mask) [[unlikely]] {
1186 if (src.id() <= AsmReg::R30) {
1188 case 1: ASMNC(STRBu, src, base, off);
break;
1189 case 2: ASMNC(STRHu, src, base, off);
break;
1190 case 4: ASMNC(STRwu, src, base, off);
break;
1191 case 8: ASMNC(STRxu, src, base, off);
break;
1192 default: TPDE_UNREACHABLE(
"invalid srcister size");
1196 case 1: ASMNC(STRbu, src, base, off);
break;
1197 case 2: ASMNC(STRhu, src, base, off);
break;
1198 case 4: ASMNC(STRsu, src, base, off);
break;
1199 case 8: ASMNC(STRdu, src, base, off);
break;
1200 case 16: ASMNC(STRqu, src, base, off);
break;
1201 default: TPDE_UNREACHABLE(
"invalid register size");
1208 template <
typename,
typename,
typename>
typename BaseTy,
1210void CompilerA64<Adaptor, Derived, BaseTy, Config>::mov(
const AsmReg dst,
1213 this->text_writer.ensure_space(4);
1214 assert(dst.valid());
1215 assert(src.valid());
1216 if (dst.id() <= AsmReg::SP && src.id() <= AsmReg::SP) {
1217 assert(dst.id() != AsmReg::SP && src.id() != AsmReg::SP);
1219 ASMNC(MOVx, dst, src);
1221 ASMNC(MOVw, dst, src);
1223 }
else if (dst.id() >= AsmReg::V0 && src.id() >= AsmReg::V0) {
1224 ASMNC(ORR16b, dst, src, src);
1225 }
else if (dst.id() <= AsmReg::SP) {
1226 assert(dst.id() != AsmReg::SP);
1228 assert(src.id() >= AsmReg::V0);
1231 ASMNC(FMOVws, dst, src);
1233 ASMNC(FMOVxd, dst, src);
1237 assert(src.id() <= AsmReg::R30);
1238 assert(dst.id() >= AsmReg::V0);
1241 ASMNC(FMOVsw, dst, src);
1243 ASMNC(FMOVdx, dst, src);
1248template <IRAdaptor Adaptor,
1250 template <
typename,
typename,
typename>
typename BaseTy,
1252AsmReg CompilerA64<Adaptor, Derived, BaseTy, Config>::gval_expr_as_reg(
1253 GenericValuePart &gv) {
1254 auto &expr = std::get<typename GenericValuePart::Expr>(gv.state);
1256 ScratchReg scratch{
derived()};
1257 if (!expr.has_base() && !expr.has_index()) {
1258 AsmReg dst = scratch.alloc_gp();
1259 derived()->materialize_constant(expr.disp, Config::GP_BANK, 8, dst);
1261 }
else if (!expr.has_base() && expr.has_index()) {
1262 AsmReg index_reg = expr.index_reg();
1263 if (std::holds_alternative<ScratchReg>(expr.index)) {
1264 scratch = std::move(std::get<ScratchReg>(expr.index));
1266 (void)scratch.alloc_gp();
1268 AsmReg dst = scratch.cur_reg();
1269 if ((expr.scale & (expr.scale - 1)) == 0) {
1270 const auto shift = util::cnt_tz<u64>(expr.scale);
1271 ASM(LSLxi, dst, index_reg, shift);
1274 derived()->materialize_constant(expr.scale, Config::GP_BANK, 8, tmp2);
1275 ASM(MULx, dst, index_reg, tmp2);
1277 }
else if (expr.has_base() && expr.has_index()) {
1278 AsmReg base_reg = expr.base_reg();
1279 AsmReg index_reg = expr.index_reg();
1280 if (std::holds_alternative<ScratchReg>(expr.base)) {
1281 scratch = std::move(std::get<ScratchReg>(expr.base));
1282 }
else if (std::holds_alternative<ScratchReg>(expr.index)) {
1283 scratch = std::move(std::get<ScratchReg>(expr.index));
1285 (void)scratch.alloc_gp();
1287 AsmReg dst = scratch.cur_reg();
1288 if ((expr.scale & (expr.scale - 1)) == 0) {
1289 const auto shift = util::cnt_tz<u64>(expr.scale);
1290 ASM(ADDx_lsl, dst, base_reg, index_reg, shift);
1293 derived()->materialize_constant(expr.scale, Config::GP_BANK, 8, tmp2);
1294 ASM(MADDx, dst, index_reg, tmp2, base_reg);
1296 }
else if (expr.has_base() && !expr.has_index()) {
1297 AsmReg base_reg = expr.base_reg();
1298 if (std::holds_alternative<ScratchReg>(expr.base)) {
1299 scratch = std::move(std::get<ScratchReg>(expr.base));
1301 (void)scratch.alloc_gp();
1303 AsmReg dst = scratch.cur_reg();
1304 if (expr.disp != 0 && ASMIF(ADDxi, dst, base_reg, expr.disp)) {
1306 }
else if (dst != base_reg) {
1307 ASM(MOVx, dst, base_reg);
1310 TPDE_UNREACHABLE(
"inconsistent GenericValuePart::Expr");
1313 AsmReg dst = scratch.cur_reg();
1314 if (expr.disp != 0) {
1315 if (!ASMIF(ADDxi, dst, dst, expr.disp)) {
1317 derived()->materialize_constant(expr.disp, Config::GP_BANK, 8, tmp2);
1318 ASM(ADDx, dst, dst, tmp2);
1322 gv.state = std::move(scratch);
1326template <IRAdaptor Adaptor,
1328 template <
typename,
typename,
typename>
typename BaseTy,
1331 u64 size, u32 align, ValuePart &res) {
1332 assert(this->stack.has_dynamic_alloca &&
1333 "function marked as not having dynamic allocas can't have alloca");
1334 assert(align != 0 && (align & (align - 1)) == 0 &&
"invalid alignment");
1335 size = tpde::util::align_up(size, 16);
1336 AsmReg res_reg = res.alloc_reg(
this);
1337 if (size >= 0x10'0000) {
1340 ASM(SUBx_uxtx, res_reg, DA_SP, tmp, 0);
1341 }
else if (size >= 0x1000) {
1342 ASM(SUBxi, res_reg, DA_SP, size & 0xff'f000);
1344 ASM(SUBxi, res_reg, res_reg, size & 0xfff);
1347 ASM(SUBxi, res_reg, DA_SP, size & 0xfff);
1352 ASM(ANDxi, res_reg, res_reg, ~(u64{align} - 1));
1356 ASM(MOV_SPx, DA_SP, res_reg);
1362 template <
typename,
typename,
typename>
typename BaseTy,
1365 u64 elem_size, ValuePart &&count, u32 align, ValuePart &res) {
1366 assert(this->stack.has_dynamic_alloca &&
1367 "function marked as not having dynamic allocas can't have alloca");
1368 assert(align != 0 && (align & (align - 1)) == 0 &&
"invalid alignment");
1369 AsmReg size_reg = count.has_reg() ? count.cur_reg() : count.load_to_reg(
this);
1370 AsmReg res_reg = res.alloc_try_reuse(
this, count);
1372 if (elem_size == 0) {
1373 ASM(MOVZw, res_reg, 0);
1374 }
else if ((elem_size & (elem_size - 1)) == 0) {
1375 const auto shift = util::cnt_tz(elem_size);
1377 ASM(SUBx_uxtx, res_reg, DA_SP, size_reg, shift);
1379 ASM(LSLxi, res_reg, size_reg, shift);
1380 ASM(SUBx_uxtx, res_reg, DA_SP, res_reg, 0);
1385 ASM(MULx, res_reg, size_reg, tmp);
1386 ASM(SUBx_uxtx, res_reg, DA_SP, res_reg, 0);
1389 align = align > 16 ? align : 16;
1390 if (elem_size & (align - 1)) {
1391 ASM(ANDxi, res_reg, res_reg, ~(u64{align} - 1));
1394 ASM(MOV_SPx, DA_SP, res_reg);
1399 template <
typename,
typename,
typename>
typename BaseTy,
1402 const u64 *data,
const RegBank bank,
const u32 size, AsmReg dst) {
1403 this->text_writer.ensure_space(5 * 4);
1405 const auto const_u64 = data[0];
1406 if (bank == Config::GP_BANK) {
1408 if (const_u64 == 0) {
1409 ASMNC(MOVZw, dst, 0);
1413 this->text_writer.cur_ptr() +=
1415 de64_MOVconst(
reinterpret_cast<u32 *
>(this->text_writer.cur_ptr()),
1421 assert(bank == Config::FP_BANK);
1424 if (ASMIF(FMOVsi, dst, std::bit_cast<float>((u32)const_u64))) {
1426 }
else if (ASMIF(MOVId, dst,
static_cast<u32
>(const_u64))) {
1429 }
else if (size == 8) {
1430 if (ASMIF(FMOVdi, dst, std::bit_cast<double>(const_u64))) {
1432 }
else if (ASMIF(MOVId, dst, const_u64)) {
1435 }
else if (size == 16) {
1436 const auto high_u64 = data[1];
1437 if (const_u64 == high_u64 && ASMIF(MOVI2d, dst, const_u64)) {
1439 }
else if (high_u64 == 0 && ASMIF(MOVId, dst, const_u64)) {
1460 auto rodata = this->assembler.get_default_section(SectionKind::ReadOnly);
1461 std::span<const u8> raw_data{
reinterpret_cast<const u8 *
>(data), size};
1462 auto sym = this->assembler.sym_def_data(
1464 this->text_writer.ensure_space(8);
1466 sym, elf::R_AARCH64_ADR_PREL_PG_HI21, this->text_writer.offset(), 0);
1469 sym, elf::R_AARCH64_LDST128_ABS_LO12_NC, this->text_writer.offset(), 0);
1474 TPDE_FATAL(
"unable to materialize constant");
1479 template <
typename,
typename,
typename>
typename BaseTy,
1482 CompilerA64<Adaptor, Derived, BaseTy, Config>::select_fixed_assignment_reg(
1483 AssignmentPartRef ap, IRValueRef) {
1484 RegBank bank = ap.bank();
1485 if (bank == Config::FP_BANK && ap.part_size() > 8) {
1488 return AsmReg::make_invalid();
1492 assert(bank.id() <= Config::NUM_BANKS);
1493 auto reg_mask = this->register_file.bank_regs(bank);
1494 reg_mask &= ~fixed_assignment_nonallocatable_mask;
1496 const auto find_possible_regs = [
this,
1497 reg_mask](
const u64 preferred_regs) -> u64 {
1499 u64 free_regs = this->register_file.allocatable & ~this->register_file.used;
1500 return free_regs & preferred_regs & reg_mask;
1504 auto csr =
derived()->cur_cc_assigner()->get_ccinfo().callee_saved_regs;
1505 if (!this->stack.is_leaf_function) {
1507 possible_regs = find_possible_regs(csr);
1511 possible_regs = find_possible_regs(~csr);
1512 if (possible_regs == 0) {
1514 possible_regs = find_possible_regs(csr);
1518 if (possible_regs == 0) {
1519 return AsmReg::make_invalid();
1523 if ((possible_regs & ~this->register_file.used) != 0) {
1524 return AsmReg{util::cnt_tz(possible_regs & ~this->register_file.used)};
1527 for (
const auto reg_id : util::BitSetIterator<>{possible_regs}) {
1528 const auto reg = AsmReg{reg_id};
1530 assert(!this->register_file.is_fixed(reg));
1532 const auto local_idx = this->register_file.reg_local_idx(reg);
1533 const auto part = this->register_file.reg_part(reg);
1534 assert(local_idx != Base::INVALID_VAL_LOCAL_IDX);
1536 auto *assignment = this->val_assignment(local_idx);
1537 auto ap = AssignmentPartRef{assignment, part};
1538 if (ap.modified()) {
1545 return AsmReg::make_invalid();
1548template <IRAdaptor Adaptor,
1550 template <
typename,
typename,
typename>
class BaseTy,
1552typename CompilerA64<Adaptor, Derived, BaseTy, Config>::Jump
1553 CompilerA64<Adaptor, Derived, BaseTy, Config>::invert_jump(
Jump jmp) {
1574 default: TPDE_UNREACHABLE(
"invalid jump kind");
1578template <IRAdaptor Adaptor,
1580 template <
typename,
typename,
typename>
typename BaseTy,
1582typename CompilerA64<Adaptor, Derived, BaseTy, Config>::Jump
1583 CompilerA64<Adaptor, Derived, BaseTy, Config>::swap_jump(
Jump jmp) {
1604 default: TPDE_UNREACHABLE(
"invalid jump kind for swap_jump");
1608template <IRAdaptor Adaptor,
1610 template <
typename,
typename,
typename>
typename BaseTy,
1613 Jump jmp, Label target_label) {
1614 const auto is_pending = this->text_writer.label_is_pending(target_label);
1615 this->text_writer.ensure_space(4);
1619 this->text_writer.label_ref(target_label,
1620 this->text_writer.offset() - 4,
1621 LabelFixupKind::AARCH64_BR);
1623 const auto label_off = this->text_writer.label_offset(target_label);
1624 const auto cur_off = this->text_writer.offset();
1625 assert(cur_off >= label_off);
1626 const auto diff = cur_off - label_off;
1627 assert((diff & 0b11) == 0);
1628 assert(diff < 128 * 1024 * 1024);
1630 ASMNC(B, -
static_cast<ptrdiff_t
>(diff) / 4);
1638 const auto label_off = this->text_writer.label_offset(target_label);
1639 const auto cur_off = this->text_writer.offset();
1640 assert(cur_off >= label_off);
1641 off = cur_off - label_off;
1642 assert((off & 0b11) == 0);
1643 assert(off < 128 * 1024 * 1024);
1646 if (off <= 1024 * 1024) {
1647 auto imm19 = -
static_cast<ptrdiff_t
>(off) / 4;
1649 if (jmp.cmp_is_32) {
1650 ASMNC(CBZw, jmp.cmp_reg, imm19);
1652 ASMNC(CBZx, jmp.cmp_reg, imm19);
1655 if (jmp.cmp_is_32) {
1656 ASMNC(CBNZw, jmp.cmp_reg, imm19);
1658 ASMNC(CBNZx, jmp.cmp_reg, imm19);
1663 this->text_writer.label_ref(target_label,
1664 this->text_writer.offset() - 4,
1665 LabelFixupKind::AARCH64_COND_BR);
1668 assert(!is_pending);
1669 this->text_writer.ensure_space(2 * 4);
1672 if (jmp.cmp_is_32) {
1673 ASMNC(CBNZw, jmp.cmp_reg, 2);
1675 ASMNC(CBNZx, jmp.cmp_reg, 2);
1678 if (jmp.cmp_is_32) {
1679 ASMNC(CBZw, jmp.cmp_reg, 2);
1681 ASMNC(CBZx, jmp.cmp_reg, 2);
1685 ASMNC(B, -
static_cast<ptrdiff_t
>(off + 4) / 4);
1693 const auto label_off = this->text_writer.label_offset(target_label);
1694 const auto cur_off = this->text_writer.offset();
1695 assert(cur_off >= label_off);
1696 off = cur_off - label_off;
1697 assert((off & 0b11) == 0);
1698 assert(off < 128 * 1024 * 1024);
1701 if (off <= 32 * 1024) {
1702 auto imm14 = -
static_cast<ptrdiff_t
>(off) / 4;
1704 ASMNC(TBZ, jmp.cmp_reg, jmp.test_bit, imm14);
1706 ASMNC(TBNZ, jmp.cmp_reg, jmp.test_bit, imm14);
1710 this->text_writer.label_ref(target_label,
1711 this->text_writer.offset() - 4,
1712 LabelFixupKind::AARCH64_TEST_BR);
1715 assert(!is_pending);
1716 this->text_writer.ensure_space(2 * 4);
1720 ASMNC(TBNZ, jmp.cmp_reg, jmp.test_bit, 2);
1722 ASMNC(TBZ, jmp.cmp_reg, jmp.test_bit, 2);
1725 ASMNC(B, -
static_cast<ptrdiff_t
>(off + 4) / 4);
1730 Da64Cond cond, cond_compl;
1788 default: TPDE_UNREACHABLE(
"invalid jump kind");
1794 const auto label_off = this->text_writer.label_offset(target_label);
1795 const auto cur_off = this->text_writer.offset();
1796 assert(cur_off >= label_off);
1797 off = cur_off - label_off;
1798 assert((off & 0b11) == 0);
1799 assert(off < 128 * 1024 * 1024);
1802 if (off <= 1024 * 1024) {
1803 ASMNC(BCOND, cond, -
static_cast<ptrdiff_t
>(off) / 4);
1806 this->text_writer.label_ref(target_label,
1807 this->text_writer.offset() - 4,
1808 LabelFixupKind::AARCH64_COND_BR);
1811 assert(!is_pending);
1812 this->text_writer.ensure_space(2 * 4);
1815 ASMNC(BCOND, cond_compl, 2);
1817 ASMNC(B, -
static_cast<ptrdiff_t
>(off + 4) / 4);
1822 template <
typename,
typename,
typename>
class BaseTy,
1841 default: TPDE_UNREACHABLE(
"invalid jump kind for conversion to Da64Cond");
1847 template <
typename,
typename,
typename>
class BaseTy,
1850 Jump cc, AsmReg dst) {
1856 template <
typename,
typename,
typename>
class BaseTy,
1859 Jump cc, AsmReg dst) {
1864 template <
typename,
typename,
typename>
class BaseTy,
1867 Jump cc, AsmReg dst, AsmReg true_select, AsmReg false_select,
bool is_64) {
1868 this->text_writer.ensure_space(4);
1871 ASMNC(CSELx, dst, true_select, false_select, cond);
1873 ASMNC(CSELw, dst, true_select, false_select, cond);
1879 template <
typename,
typename,
typename>
class BaseTy,
1882 AsmReg dst, AsmReg src,
bool sign, u32 from, u32 to) {
1883 assert(from < to && to <= 64);
1887 ASM(SBFXw, dst, src, 0, from);
1889 ASM(SBFXx, dst, src, 0, from);
1893 ASM(UBFXw, dst, src, 0, from);
1895 ASM(UBFXx, dst, src, 0, from);
1902 template <
typename,
typename,
typename>
typename BaseTy,
1905 std::variant<SymRef, ValuePart> &&target,
1906 std::span<CallArg> arguments,
1907 typename Base::ValueRef *result,
1911 for (
auto &arg : arguments) {
1912 cb.add_arg(std::move(arg));
1914 cb.call(std::move(target));
1916 cb.add_ret(*result);
1922 template <
typename,
typename,
typename>
typename BaseTy,
1924void CompilerA64<Adaptor, Derived, BaseTy, Config>::switch_emit_cmp(
1925 AsmReg cmp_reg, AsmReg tmp_reg, u64 case_value,
bool width_is_32) {
1927 if (!ASMIF(CMPwi, cmp_reg, case_value)) {
1929 ASM(CMPw, cmp_reg, tmp_reg);
1932 if (!ASMIF(CMPxi, cmp_reg, case_value)) {
1934 ASM(CMPx, cmp_reg, tmp_reg);
1939template <IRAdaptor Adaptor,
1941 template <
typename,
typename,
typename>
typename BaseTy,
1943void CompilerA64<Adaptor, Derived, BaseTy, Config>::switch_emit_cmpeq(
1949 switch_emit_cmp(cmp_reg, tmp_reg, case_value, width_is_32);
1953template <IRAdaptor Adaptor,
1955 template <
typename,
typename,
typename>
typename BaseTy,
1957FunctionWriterBase::JumpTable *
1958 CompilerA64<Adaptor, Derived, BaseTy, Config>::switch_create_jump_table(
1959 Label default_label,
1965 if (low_bound > 0) {
1967 if (!ASMIF(SUBwi, cmp_reg, cmp_reg, low_bound)) {
1969 ASM(SUBw, cmp_reg, cmp_reg, tmp_reg);
1972 if (!ASMIF(SUBxi, cmp_reg, cmp_reg, low_bound)) {
1974 ASM(SUBx, cmp_reg, cmp_reg, tmp_reg);
1978 switch_emit_cmp(cmp_reg, tmp_reg, high_bound - low_bound, width_is_32);
1981 u64 range = high_bound - low_bound + 1;
1982 return &this->text_writer.create_jump_table(
1983 range, cmp_reg, tmp_reg, width_is_32);
1986template <IRAdaptor Adaptor,
1988 template <
typename,
typename,
typename>
typename BaseTy,
1990void CompilerA64<Adaptor, Derived, BaseTy, Config>::switch_emit_binary_step(
1997 switch_emit_cmpeq(case_label, cmp_reg, tmp_reg, case_value, width_is_32);
2001template <IRAdaptor Adaptor,
2003 template <
typename,
typename,
typename>
typename BaseTy,
2005CompilerA64<Adaptor, Derived, BaseTy, Config>::ScratchReg
2007 SymRef sym, TLSModel model) {
2010 case TLSModel::GlobalDynamic: {
2011 assert(!this->stack.is_leaf_function);
2012 this->stack.generated_call =
true;
2013 ScratchReg r0_scratch{
this};
2014 AsmReg r0 = r0_scratch.alloc_specific(AsmReg::R0);
2015 ScratchReg r1_scratch{
this};
2016 AsmReg r1 = r1_scratch.alloc_specific(AsmReg::R1);
2019 if (this->register_file.is_used(Reg{AsmReg::LR})) {
2023 this->text_writer.ensure_space(0x18);
2025 sym, elf::R_AARCH64_TLSDESC_ADR_PAGE21, this->text_writer.offset(), 0);
2026 ASMNC(ADRP, r0, 0, 0);
2028 sym, elf::R_AARCH64_TLSDESC_LD64_LO12, this->text_writer.offset(), 0);
2029 ASMNC(LDRxu, r1, r0, 0);
2031 sym, elf::R_AARCH64_TLSDESC_ADD_LO12, this->text_writer.offset(), 0);
2032 ASMNC(ADDxi, r0, r0, 0);
2034 sym, elf::R_AARCH64_TLSDESC_CALL, this->text_writer.offset(), 0);
2036 ASMNC(MRS, r1, 0xde82);
2038 ASMNC(ADDx, r0, r1, r0);
@ LOCAL
Symbol with local linkage, must be defined.
Helper class to write function text.
AArch64 AAPCS calling convention.
Helper class for building call sequences.
CallBuilder(Derived &compiler, CCAssigner &assigner)
Constructor.
Helper class to write function text for AArch64.
The IRAdaptor specifies the interface with which the IR-independent parts of the compiler interact wi...
constexpr Jump(Kind kind, AsmReg cmp_reg, bool cmp_is_32)
Cbz/Cbnz branch.
@ Tbnz
Test single bit and branch if not zero (Xn register)
@ Jge
Signed greater than or equal (N == V)
@ Jls
Unsigned lower or same (!(C == 1 && Z == 0))
@ Jhi
Unsigned higher (C == 1 && Z == 0)
@ Tbz
Test single bit and branch if zero (Xn register)
@ Jmi
Minus, negative (N == 1)
@ Jlo
Unsigned lower (C == 0)
@ Jlt
Signed less than (N != V)
@ Jgt
Signed greater than (Z == 0 && N == V)
@ Jhs
Unsigned higher or same (C == 1)
@ Jvc
No Overflow (V == 0)
@ Cbnz
Compare and branch if not zero (Wn or Xn register)
@ Jle
Signed lessthan or equal (!(Z == 0 && N == V))
@ Jcc
Carry clear (C == 0)
@ Jpl
Plus, positive or zero (N == 0)
@ Cbz
Compare and branch if zero (Wn or Xn register)
constexpr Jump(Kind kind, AsmReg cmp_reg, u8 test_bit)
Tbz/Tbnz branch.
constexpr Jump(Kind kind)
Unconditional or conditional branch based on flags.
constexpr Jump()
Unconditional branch.
Compiler mixin for targeting AArch64.
u32 func_arg_stack_add_off
Offset to the add sp, sp, XXX instruction that the argument handling uses to access stack arguments i...
void alloca_fixed(u64 size, u32 align, ValuePart &res)
Dynamic alloca of a fixed-size region.
void generate_raw_bfiz(AsmReg dst, AsmReg src, u32 lsb, u32 width)
Bitfield insert in zero. src is not modified.
void generate_raw_intext(AsmReg dst, AsmReg src, bool sign, u32 from, u32 to)
Integer extension. src is not modified.
AsmReg permanent_scratch_reg
Permanent scratch register, e.g.
std::optional< i32 > prologue_assign_arg_part(ValuePart &&vp, CCAssignment cca)
Assign argument part.
void load_off(AsmReg dst, AsmReg base, u32 off, u32 size, bool sext=false)
Load from an base+offset into register, using permanent_scratch_reg for large offsets.
void generate_raw_mask(Jump cc, AsmReg dst)
Set all bits of dst to 1 if cc is true, otherwise set dst to zero.
void generate_raw_bfi(AsmReg dst, AsmReg src, u32 lsb, u32 width)
Bitfield insert. src is not modified.
void generate_call(std::variant< SymRef, ValuePart > &&target, std::span< CallArg > arguments, typename Base::ValueRef *result, bool variable_args=false)
Generate a function call.
void generate_raw_jump(Jump jmp, Label target)
Generate jump instruction to target label.
void materialize_constant(const u64 *data, RegBank bank, u32 size, AsmReg dst)
Materialize constant into a register.
void generate_raw_set(Jump cc, AsmReg dst)
Set dst to 1 if cc is true, otherwise set it to zero.
Da64Cond jump_to_cond(Jump jmp)
Convert jump condition to disarms Da64Cond.
void prologue_begin(CCAssigner *cc_assigner)
Begin prologue, prepare for assigning arguments.
void generate_raw_select(Jump cc, AsmReg dst, AsmReg true_select, AsmReg false_select, bool is_64)
Moves true_select into dst if cc is true, otherwise move false_select into dst.
ScratchReg tls_get_addr(SymRef sym, TLSModel model)
Generate code sequence to load address of sym into a register.
void store_off(AsmReg base, u32 off, AsmReg src, u32 size)
Store a register to base+offset, using permanent_scratch_reg for large offsets.
void prologue_end(CCAssigner *cc_assigner)
Finish prologue.
void alloca_dynamic(u64 elem_size, ValuePart &&count, u32 align, ValuePart &res)
Dynamic alloca of a dynamically-sized region (elem_size * count bytes).
void materialize_constant(u64 const_u64, RegBank bank, u32 size, AsmReg dst)
Materialize constant into a register.