aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gas/config/tc-aarch64-ginsn.c797
-rw-r--r--gas/config/tc-aarch64.c15
-rw-r--r--gas/config/tc-aarch64.h21
3 files changed, 833 insertions, 0 deletions
diff --git a/gas/config/tc-aarch64-ginsn.c b/gas/config/tc-aarch64-ginsn.c
new file mode 100644
index 0000000..dbee8df
--- /dev/null
+++ b/gas/config/tc-aarch64-ginsn.c
@@ -0,0 +1,797 @@
+/* tc-aarch64-ginsn.c -- Ginsn generation for the AArch64 ISA
+
+ Copyright (C) 2024 Free Software Foundation, Inc.
+ Contributed by ARM Ltd.
+
+ This file is part of GAS.
+
+ GAS is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the license, or
+ (at your option) any later version.
+
+ GAS is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING3. If not,
+ see <http://www.gnu.org/licenses/>. */
+
+/* This file contains the implementation of the ginsn creation for aarch64
+ instructions. Most functions will read the aarch64_instruction inst
+ object, but none should need to modify it. */
+
+#ifdef OBJ_ELF
+
+/* DWARF register number for R1. Used as dummy value when WZR. */
+#define GINSN_DW2_REGNUM_R1_DUMMY 1
+
+/* Return whether the given register number is a callee-saved register for
+ SCFI purposes.
+
+ Apart from the callee-saved GPRs, SCFI always tracks SP, FP and LR
+ additionally. As for the FP/Advanced SIMD registers, v8-v15 are
+ callee-saved. */
+
+bool
+aarch64_scfi_callee_saved_p (unsigned int dw2reg_num)
+{
+ /* PS: Ensure SCFI_MAX_REG_ID is the max DWARF register number to cover
+ all the registers here. */
+ if (dw2reg_num == REG_SP /* x31. */
+ || dw2reg_num == REG_FP /* x29. */
+ || dw2reg_num == REG_LR /* x30. */
+ || (dw2reg_num >= 19 && dw2reg_num <= 28) /* x19 - x28. */
+ || (dw2reg_num >= 72 && dw2reg_num <= 79) /* v8 - v15. */)
+ return true;
+
+ return false;
+}
+
+/* Get the DWARF register number for the given OPND.
+ Whether 31 is used to encode WZR or SP is specified via SP_ALLOWED_P.
+
+ The caller must decide the value of SP_ALLOWED_P based on the instruction
+ encoding. */
+
+static unsigned int
+ginsn_dw2_regnum (aarch64_opnd_info *opnd, bool sp_allowed_p)
+{
+ enum aarch64_operand_class opnd_class;
+ unsigned int dw2reg_num = 0;
+
+ opnd_class = aarch64_get_operand_class (opnd->type);
+
+ switch (opnd_class)
+ {
+ case AARCH64_OPND_CLASS_FP_REG:
+ dw2reg_num = opnd->reg.regno + 64;
+ break;
+ case AARCH64_OPND_CLASS_SVE_REGLIST:
+ dw2reg_num = opnd->reglist.first_regno + 64;
+ break;
+ case AARCH64_OPND_CLASS_MODIFIED_REG:
+ case AARCH64_OPND_CLASS_INT_REG:
+ case AARCH64_OPND_CLASS_ADDRESS:
+ /* Use a dummy register value in case of WZR, else this will be an
+ incorrect dependency on REG_SP. */
+ if (!sp_allowed_p && opnd->reg.regno == REG_SP)
+ dw2reg_num = GINSN_DW2_REGNUM_R1_DUMMY;
+ else
+ /* For GPRs of our interest (callee-saved regs, SP, FP, LR),
+ DWARF register number is the same as AArch64 register number. */
+ dw2reg_num = opnd->reg.regno;
+ break;
+ case AARCH64_OPND_CLASS_SYSTEM:
+ /* For prfm etc., where a opnd->type AARCH64_OPND_PRFOP is seen. */
+ dw2reg_num = GINSN_DW2_REGNUM_R1_DUMMY;
+ break;
+ default:
+ as_bad ("Unexpected value in ginsn_dw2_regnum");
+ break;
+ }
+
+ return dw2reg_num;
+}
+
+/* Generate ginsn for addsub instructions with immediate opnd. */
+
+static ginsnS *
+aarch64_ginsn_addsub_imm (const symbolS *insn_end_sym)
+{
+ ginsnS *ginsn = NULL;
+ bool add_p, sub_p;
+ offsetT src_imm = 0;
+ unsigned int dst_reg, opnd_reg;
+ aarch64_opnd_info *dst, *opnd;
+ ginsnS *(*ginsn_func) (const symbolS *, bool,
+ enum ginsn_src_type, unsigned int, offsetT,
+ enum ginsn_src_type, unsigned int, offsetT,
+ enum ginsn_dst_type, unsigned int, offsetT);
+
+ aarch64_inst *base = &inst.base;
+ const aarch64_opcode *opcode = base->opcode;
+
+ add_p = aarch64_opcode_subclass_p (opcode, F_ARITH_ADD);
+ sub_p = aarch64_opcode_subclass_p (opcode, F_ARITH_SUB);
+ gas_assert (add_p || sub_p);
+ ginsn_func = add_p ? ginsn_new_add : ginsn_new_sub;
+
+ gas_assert (aarch64_num_of_operands (opcode) == 3);
+ dst = &base->operands[0];
+ opnd = &base->operands[1];
+
+ dst_reg = ginsn_dw2_regnum (dst, true);
+
+ if (aarch64_gas_internal_fixup_p () && inst.reloc.exp.X_op == O_constant)
+ src_imm = inst.reloc.exp.X_add_number;
+ /* For any other relocation type, e.g., in add reg, reg, symbol, skip now
+ and handle via aarch64_ginsn_unhandled () code path. */
+ else if (inst.reloc.type != BFD_RELOC_UNUSED)
+ return ginsn;
+ /* FIXME - verify the understanding and remove assert. */
+ else
+ gas_assert (0);
+
+ opnd_reg = ginsn_dw2_regnum (opnd, true);
+
+ ginsn = ginsn_func (insn_end_sym, true,
+ GINSN_SRC_REG, opnd_reg, 0,
+ GINSN_SRC_IMM, 0, src_imm,
+ GINSN_DST_REG, dst_reg, 0);
+ ginsn_set_where (ginsn);
+
+ return ginsn;
+}
+
+/* Generate ginsn for addsub instructions with reg opnd. */
+
+static ginsnS *
+aarch64_ginsn_addsub_reg (const symbolS *insn_end_sym)
+{
+ ginsnS *ginsn = NULL;
+ bool add_p, sub_p;
+ unsigned int dst_reg, src1_reg, src2_reg;
+ aarch64_opnd_info *dst, *src1, *src2;
+ ginsnS *(*ginsn_func) (const symbolS *, bool,
+ enum ginsn_src_type, unsigned int, offsetT,
+ enum ginsn_src_type, unsigned int, offsetT,
+ enum ginsn_dst_type, unsigned int, offsetT);
+
+ aarch64_inst *base = &inst.base;
+ const aarch64_opcode *opcode = base->opcode;
+
+ add_p = aarch64_opcode_subclass_p (opcode, F_ARITH_ADD);
+ sub_p = aarch64_opcode_subclass_p (opcode, F_ARITH_SUB);
+ gas_assert (add_p || sub_p);
+ ginsn_func = add_p ? ginsn_new_add : ginsn_new_sub;
+
+ gas_assert (aarch64_num_of_operands (opcode) == 3);
+ dst = &base->operands[0];
+ src1 = &base->operands[1];
+ src2 = &base->operands[2];
+
+ dst_reg = ginsn_dw2_regnum (dst, true);
+ src1_reg = ginsn_dw2_regnum (src1, true);
+ src2_reg = ginsn_dw2_regnum (src2, false);
+
+ ginsn = ginsn_func (insn_end_sym, true,
+ GINSN_SRC_REG, src1_reg, 0,
+ GINSN_SRC_REG, src2_reg, 0,
+ GINSN_DST_REG, dst_reg, 0);
+ ginsn_set_where (ginsn);
+
+ return ginsn;
+}
+
+/* Generate ginsn for the load pair and store pair instructions. */
+
+static ginsnS *
+aarch64_ginsn_ldstp (const symbolS *insn_end_sym)
+{
+ ginsnS *ginsn = NULL;
+ ginsnS *ginsn_ind = NULL;
+ ginsnS *ginsn_mem1 = NULL;
+ ginsnS *ginsn_mem2 = NULL;
+ unsigned int opnd_reg, addr_reg;
+ offsetT offset, mem_offset;
+ unsigned int width = 8;
+ bool store_p = false;
+
+ aarch64_opnd_info *opnd1, *opnd2, *addr;
+ aarch64_inst *base = &inst.base;
+ const aarch64_opcode *opcode = base->opcode;
+
+ /* This function is for handling ldp / stp ops only. */
+ gas_assert (opcode->iclass == ldstpair_indexed
+ || opcode->iclass == ldstpair_off);
+ gas_assert (aarch64_num_of_operands (opcode) == 3);
+
+ opnd1 = &base->operands[0];
+ opnd2 = &base->operands[1];
+ addr = &base->operands[2];
+ store_p = ((opcode->flags & F_SUBCLASS) == F_LDST_STORE);
+
+ addr_reg = ginsn_dw2_regnum (addr, true);
+ gas_assert (!addr->addr.offset.is_reg);
+ mem_offset = addr->addr.offset.imm;
+
+ /* ldstp may load or store two 32-bit words or two 64-bit doublewords. */
+ if (opnd1->qualifier == AARCH64_OPND_QLF_W
+ || opnd1->qualifier == AARCH64_OPND_QLF_S_S)
+ width = 4;
+
+ /* Handle address calculation. */
+ if ((addr->addr.preind || addr->addr.postind) && addr->addr.writeback)
+ {
+ /* Pre-indexed store, e.g., stp x29, x30, [sp, -128]!
+ Pre-indexed addressing is like offset addressing, except that
+ the base pointer is updated as a result of the instruction.
+
+ Post-indexed store, e.g., stp x29, x30, [sp],128
+ Post-index addressing is useful for popping off the stack. The
+ instruction loads the value from the location pointed at by the stack
+ pointer, and then moves the stack pointer on to the next full location
+ in the stack. */
+ ginsn_ind = ginsn_new_add (insn_end_sym, false,
+ GINSN_SRC_REG, addr_reg, 0,
+ GINSN_SRC_IMM, 0, mem_offset,
+ GINSN_DST_REG, addr_reg, 0);
+ ginsn_set_where (ginsn_ind);
+ }
+
+ /* Save / restore of WZR is not of interest for SCFI. However, the address
+ processing component may have updated the stack pointer. At least, emit
+ that ginsn and return. PS: opnd_reg will be GINSN_DW2_REGNUM_R1_DUMMY by
+ now; Check explicitly the regno for WZR. Also note,
+ TBD_GINSN_GEN_NOT_SCFI. */
+ if (opnd1->reg.regno == REG_SP /* WZR. */)
+ return ginsn_ind;
+
+ /* With post-index addressing, the value is loaded from the
+ address in the base pointer, and then the pointer is updated.
+ With pre-index addressing, the addr computation has already
+ been explicitly done. */
+ offset = mem_offset;
+ if ((addr->addr.postind || addr->addr.preind) && addr->addr.writeback)
+ offset = 0;
+
+ if (opnd1->qualifier == AARCH64_OPND_QLF_S_Q)
+ {
+ width = 16;
+ if (target_big_endian)
+ offset += 8;
+ }
+
+ opnd_reg = ginsn_dw2_regnum (opnd1, false);
+ if (store_p)
+ {
+ ginsn_mem1 = ginsn_new_store (insn_end_sym, false,
+ GINSN_SRC_REG, opnd_reg,
+ GINSN_DST_INDIRECT, addr_reg, offset);
+ ginsn_set_where (ginsn_mem1);
+
+ opnd_reg = ginsn_dw2_regnum (opnd2, false);
+ ginsn_mem2 = ginsn_new_store (insn_end_sym, false,
+ GINSN_SRC_REG, opnd_reg,
+ GINSN_DST_INDIRECT, addr_reg,
+ offset + width);
+ ginsn_set_where (ginsn_mem2);
+ }
+ else
+ {
+ opnd_reg = ginsn_dw2_regnum (opnd1, false);
+ ginsn_mem1 = ginsn_new_load (insn_end_sym, false,
+ GINSN_SRC_INDIRECT, addr_reg, offset,
+ GINSN_DST_REG, opnd_reg);
+ ginsn_set_where (ginsn_mem1);
+
+ opnd_reg = ginsn_dw2_regnum (opnd2, false);
+ ginsn_mem2 = ginsn_new_load (insn_end_sym, false,
+ GINSN_SRC_INDIRECT, addr_reg, offset + width,
+ GINSN_DST_REG, opnd_reg);
+ ginsn_set_where (ginsn_mem2);
+ }
+
+ /* Link the list of ginsns created. */
+ if (addr->addr.preind && addr->addr.writeback)
+ gas_assert (!ginsn_link_next (ginsn_ind, ginsn_mem1));
+
+ gas_assert (!ginsn_link_next (ginsn_mem1, ginsn_mem2));
+
+ if (addr->addr.postind && addr->addr.writeback)
+ gas_assert (!ginsn_link_next (ginsn_mem2, ginsn_ind));
+
+ /* Make note of the first instruction in the list. */
+ ginsn = (addr->addr.preind && addr->addr.writeback) ? ginsn_ind : ginsn_mem1;
+ return ginsn;
+}
+
+/* Generate ginsn for load and store instructions. */
+
+static ginsnS *
+aarch64_ginsn_ldstr (const symbolS *insn_end_sym)
+{
+ ginsnS *ginsn = NULL;
+ ginsnS *ginsn_ind = NULL;
+ ginsnS *ginsn_mem = NULL;
+ unsigned int opnd_reg, addr_reg;
+ offsetT offset, mem_offset;
+ bool store_p = false;
+
+ aarch64_opnd_info *opnd1, *addr;
+ aarch64_inst *base = &inst.base;
+ const aarch64_opcode *opcode = base->opcode;
+
+ /* This function is for handling ldr, str ops only. */
+ gas_assert (opcode->iclass == ldst_imm9 || opcode->iclass == ldst_pos);
+ gas_assert (aarch64_num_of_operands (opcode) == 2);
+
+ opnd1 = &base->operands[0];
+ addr = &base->operands[1];
+ store_p = ((opcode->flags & F_SUBCLASS) == F_LDST_STORE);
+
+ addr_reg = ginsn_dw2_regnum (addr, true);
+
+ /* STR <Xt>, [<Xn|SP>, (<Wm>|<Xm>){, <extend> {<amount>}}].
+ LDR <Xt>, [<Xn|SP>], #<simm>. */
+ opnd_reg = ginsn_dw2_regnum (opnd1, false);
+
+ if (aarch64_gas_internal_fixup_p () && inst.reloc.exp.X_op == O_constant)
+ mem_offset = inst.reloc.exp.X_add_number;
+ else
+ {
+ gas_assert (!addr->addr.offset.is_reg);
+ mem_offset = addr->addr.offset.imm;
+ }
+
+ /* Handle address calculation. */
+ if ((addr->addr.preind || addr->addr.postind) && addr->addr.writeback)
+ {
+ ginsn_ind = ginsn_new_add (insn_end_sym, false,
+ GINSN_SRC_REG, addr_reg, 0,
+ GINSN_SRC_IMM, 0, mem_offset,
+ GINSN_DST_REG, addr_reg, 0);
+ ginsn_set_where (ginsn_ind);
+ }
+
+ /* Save / restore of WZR is not of interest for SCFI. However, the address
+ processing component may have updated the stack pointer. At least, emit
+ that ginsn and return. PS: opnd_reg will be GINSN_DW2_REGNUM_R1_DUMMY by
+ now; Check explicitly the regno for WZR. Also note,
+ TBD_GINSN_GEN_NOT_SCFI. */
+ if (opnd1->reg.regno == REG_SP /* WZR. */)
+ return ginsn_ind;
+
+ /* With post-index addressing, the value is loaded from the
+ address in the base pointer, and then the pointer is updated.
+ With pre-index addressing, the addr computation has already
+ been explicitly done. */
+ offset = mem_offset;
+ if ((addr->addr.postind || addr->addr.preind) && addr->addr.writeback)
+ offset = 0;
+ if (target_big_endian && opnd1->qualifier == AARCH64_OPND_QLF_S_Q)
+ offset += 8;
+
+ if (store_p)
+ ginsn_mem = ginsn_new_store (insn_end_sym, false,
+ GINSN_SRC_REG, opnd_reg,
+ GINSN_DST_INDIRECT, addr_reg, offset);
+ else
+ ginsn_mem = ginsn_new_load (insn_end_sym, false,
+ GINSN_SRC_INDIRECT, addr_reg, offset,
+ GINSN_DST_REG, opnd_reg);
+ ginsn_set_where (ginsn_mem);
+
+ if (addr->addr.preind && addr->addr.writeback)
+ gas_assert (!ginsn_link_next (ginsn_ind, ginsn_mem));
+ else if (addr->addr.postind && addr->addr.writeback)
+ gas_assert (!ginsn_link_next (ginsn_mem, ginsn_ind));
+
+ /* Make note of the first instruction in the list. */
+ ginsn = (addr->addr.preind && addr->addr.writeback) ? ginsn_ind : ginsn_mem;
+
+ return ginsn;
+}
+
+/* Generate ginsn for unconditional branch instructions. */
+
+static ginsnS *
+aarch64_ginsn_branch_uncond (const symbolS *insn_end_sym)
+{
+ ginsnS *ginsn = NULL;
+ const symbolS *src_symbol = NULL;
+ enum ginsn_src_type src_type = GINSN_SRC_UNKNOWN;
+ unsigned int src_reg = 0;
+
+ aarch64_inst *base = &inst.base;
+ const aarch64_opcode *opcode = base->opcode;
+
+ if (opcode->iclass == branch_imm)
+ {
+ /* b or bl. opcode 0x14000000 or 0x94000000. */
+ gas_assert (inst.reloc.type == BFD_RELOC_AARCH64_CALL26
+ || inst.reloc.type == BFD_RELOC_AARCH64_JUMP26);
+ src_symbol = inst.reloc.exp.X_add_symbol;
+ src_type = GINSN_SRC_SYMBOL;
+ }
+ else if (aarch64_num_of_operands (opcode) >= 1)
+ {
+ gas_assert (opcode->iclass == branch_reg);
+ /* Some insns (e.g., braa, blraa etc.) may have > 1 operands. For
+ current SCFI implementation, it suffices however to simply pass
+ the information about the first source. Although, strictly speaking,
+ (if reg) the source info is currently of no material use either. */
+ src_type = GINSN_SRC_REG;
+ src_reg = ginsn_dw2_regnum (&base->operands[0], false);
+ }
+
+ if (aarch64_opcode_subclass_p (opcode, F_BRANCH_CALL))
+ {
+ gas_assert (src_type != GINSN_SRC_UNKNOWN);
+ ginsn = ginsn_new_call (insn_end_sym, true,
+ src_type, src_reg, src_symbol);
+ }
+ else if (aarch64_opcode_subclass_p (opcode, F_BRANCH_RET))
+ /* TBD_GINSN_REPRESENTATION_LIMIT. The following function to create a
+ GINSN_TYPE_RETURN does not allow src info ATM. */
+ ginsn = ginsn_new_return (insn_end_sym, true);
+ else
+ ginsn = ginsn_new_jump (insn_end_sym, true,
+ src_type, src_reg, src_symbol);
+
+ ginsn_set_where (ginsn);
+
+ return ginsn;
+}
+
+/* Generate ginsn for conditional branch instructions. */
+
+static ginsnS *
+aarch64_ginsn_branch_cond (const symbolS *insn_end_sym)
+{
+ ginsnS *ginsn;
+ const symbolS *src_symbol;
+ enum ginsn_src_type src_type;
+
+ gas_assert (inst.reloc.type == BFD_RELOC_AARCH64_BRANCH19
+ || inst.reloc.type == BFD_RELOC_AARCH64_TSTBR14);
+
+ src_symbol = inst.reloc.exp.X_add_symbol;
+ src_type = GINSN_SRC_SYMBOL;
+ ginsn = ginsn_new_jump_cond (insn_end_sym, true, src_type, 0, src_symbol);
+ ginsn_set_where (ginsn);
+
+ return ginsn;
+}
+
+/* Generate ginsn for mov instructions. */
+
+static ginsnS *
+aarch64_ginsn_mov (const symbolS *insn_end_sym)
+{
+ ginsnS *ginsn = NULL;
+ unsigned int src_reg = 0, dst_reg;
+ aarch64_opnd_info *src, *dst;
+ offsetT src_imm = 0;
+ enum ginsn_src_type src_type;
+
+ aarch64_inst *base = &inst.base;
+ const aarch64_opcode *opcode = base->opcode;
+
+ gas_assert (aarch64_num_of_operands (opcode) == 2);
+
+ dst = &base->operands[0];
+ src = &base->operands[1];
+
+ dst_reg = ginsn_dw2_regnum (dst, true);
+
+ /* For some mov ops, e.g., movn, movk, or movz, there may optionally be more
+ work than just a simple mov. Skip handling these mov altogether and let
+ the aarch64_ginsn_unhandled () alert if these insns affect SCFI
+ correctness. TBD_GINSN_GEN_NOT_SCFI. */
+ if (src->type == AARCH64_OPND_HALF)
+ return ginsn;
+
+ if (src->type == AARCH64_OPND_IMM_MOV
+ && aarch64_gas_internal_fixup_p () && inst.reloc.exp.X_op == O_constant)
+ {
+ src_imm = inst.reloc.exp.X_add_number;
+ src_type = GINSN_SRC_IMM;
+ }
+ else
+ {
+ /* mov x27, sp. */
+ src_reg = ginsn_dw2_regnum (src, true);
+ src_type = GINSN_SRC_REG;
+ }
+
+ ginsn = ginsn_new_mov (insn_end_sym, false,
+ src_type, src_reg, src_imm,
+ GINSN_DST_REG, dst_reg, 0);
+ ginsn_set_where (ginsn);
+
+ return ginsn;
+}
+
+/* Check if an instruction is whitelisted.
+
+ An instruction is a candidate for whitelisting if not generating ginsn for
+ it, does not affect SCFI correctness.
+
+ TBD_GINSN_GEN_NOT_SCFI. This function assumes GINSN_GEN_SCFI is in effect.
+ When other ginsn_gen_mode are added, this will need fixing. */
+
+static bool
+aarch64_ginsn_safe_to_skip_p (void)
+{
+ bool skip_p = false;
+ aarch64_opnd_info *opnd = NULL;
+ unsigned int dw2_regnum;
+ unsigned int opnd_reg;
+ int num_opnds = 0;
+
+ aarch64_inst *base = &inst.base;
+ const aarch64_opcode *opcode = base->opcode;
+
+ /* ATM, whitelisting operations with no operands does not seem to be
+ necessary. */
+ num_opnds = aarch64_num_of_operands (opcode);
+ if (!num_opnds)
+ return skip_p;
+
+ opnd = &base->operands[0];
+
+ switch (opcode->iclass)
+ {
+ case ldst_regoff:
+ /* It is not expected to have reg offset based ld/st ops to be used
+ for reg save and restore operations. Warn the user though. */
+ opnd_reg = ginsn_dw2_regnum (opnd, false);
+ if (aarch64_scfi_callee_saved_p (opnd_reg))
+ {
+ skip_p = true;
+ as_warn ("SCFI: ignored probable save/restore op with reg offset");
+ }
+ break;
+
+ case dp_2src:
+ /* irg insn needs to be explicitly whitelisted. This is because the
+ dest is Rd_SP, but irg insn affects the tag only. To detect irg
+ insn, avoid an opcode-based check, however. */
+ if (opnd->type == AARCH64_OPND_Rd_SP)
+ {
+ dw2_regnum = ginsn_dw2_regnum (opnd, true);
+ if (dw2_regnum == REG_SP)
+ skip_p = true;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return skip_p;
+}
+
+#define AARCH64_GINSN_UNHANDLED_NONE 0
+#define AARCH64_GINSN_UNHANDLED_DEST_REG 1
+#define AARCH64_GINSN_UNHANDLED_CFG 2
+#define AARCH64_GINSN_UNHANDLED_STACKOP 3
+#define AARCH64_GINSN_UNHANDLED_UNEXPECTED 4
+
+/* Check the input insn for its impact on the correctness of the synthesized
+ CFI. Returns an error code to the caller. */
+
+static int
+aarch64_ginsn_unhandled (void)
+{
+ int err = AARCH64_GINSN_UNHANDLED_NONE;
+ aarch64_inst *base = &inst.base;
+ const aarch64_opcode *opcode = base->opcode;
+ aarch64_opnd_info *dest = &base->operands[0];
+ int num_opnds = aarch64_num_of_operands (opcode);
+ aarch64_opnd_info *addr;
+ unsigned int dw2_regnum;
+ unsigned int addr_reg;
+ aarch64_opnd_info *opnd = NULL;
+ unsigned int opnd_reg;
+ bool sp_allowed_p = false;
+
+ /* All change of flow instructions are important for SCFI. */
+ if (opcode->iclass == condbranch
+ || opcode->iclass == compbranch
+ || opcode->iclass == testbranch
+ || opcode->iclass == branch_imm
+ || opcode->iclass == branch_reg)
+ err = AARCH64_GINSN_UNHANDLED_CFG;
+ /* Also, any memory instructions that may involve an update to the stack
+ pointer or save/restore of callee-saved registers must not be skipped.
+ Note that, some iclasses cannot be used to push or pop stack because of
+ disallowed writeback: ldst_unscaled, ldst_regoff, ldst_unpriv, ldstexcl,
+ loadlit, ldstnapair_offs. FIXME double-check.
+ Also, these iclasses do not seem to be amenable to being used for
+ save/restore ops either. FIXME double-check. */
+ else if (opcode->iclass == ldstpair_off
+ || opcode->iclass == ldstpair_indexed
+ || opcode->iclass == ldst_imm9
+ || opcode->iclass == ldst_imm10
+ || opcode->iclass == ldst_pos
+ /* STR Zn are especially complicated as they do not store in the same byte
+ order for big-endian: STR Qn stores as a 128-bit integer (MSB first),
+ whereas STR Zn stores as a stream of bytes (LSB first). FIXME Simply punt
+ on the big-endian and little-endian SVE PCS case for now. */
+ || opcode->iclass == sve_misc)
+ {
+ opnd = &base->operands[0];
+ addr = &base->operands[num_opnds - 1];
+ addr_reg = ginsn_dw2_regnum (addr, true);
+ opnd_reg = ginsn_dw2_regnum (opnd, false);
+ /* For all skipped memory operations, check if an update to REG_SP or
+ REG_FP is involved. */
+ if ((addr_reg == REG_SP || addr_reg == REG_FP)
+ && (((addr->addr.postind || addr->addr.preind) && addr->addr.writeback)
+ || aarch64_scfi_callee_saved_p (opnd_reg)))
+
+ err = AARCH64_GINSN_UNHANDLED_STACKOP;
+ }
+ /* Finally, irrespective of the iclass, check if the missed instructions are
+ affecting REG_SP or REG_FP. */
+ else if (dest && (dest->type == AARCH64_OPND_Rd
+ || dest->type == AARCH64_OPND_Rd_SP))
+ {
+ sp_allowed_p = (dest->type == AARCH64_OPND_Rd_SP) ? true : false;
+ dw2_regnum = ginsn_dw2_regnum (dest, sp_allowed_p);
+
+ if (dw2_regnum == REG_SP || dw2_regnum == REG_FP)
+ err = AARCH64_GINSN_UNHANDLED_DEST_REG;
+ }
+
+ return err;
+}
+
+/* Generate one or more generic GAS instructions, a.k.a, ginsns for the
+ current machine instruction.
+
+ Returns the head of linked list of ginsn(s) added, if success; Returns NULL
+ if failure.
+
+ The input ginsn_gen_mode GMODE determines the set of minimal necessary
+ ginsns necessary for correctness of any passes applicable for that mode.
+ For supporting the GINSN_GEN_SCFI generation mode, following is the list of
+ machine instructions that must be translated into the corresponding ginsns
+ to ensure correctness of SCFI:
+ - All instructions affecting the two registers that could potentially
+ be used as the base register for CFA tracking. For SCFI, the base
+ register for CFA tracking is limited to REG_SP and REG_FP only.
+ - All change of flow instructions: conditional and unconditional
+ branches, call and return from functions.
+ - All instructions that can potentially be a register save / restore
+ operations.
+ - All instructions that may update the stack pointer: pre-indexed and
+ post-indexed stack operations with writeback.
+
+ The function currently supports GINSN_GEN_SCFI ginsn generation mode only.
+ To support other generation modes will require work on this target-specific
+ process of creation of ginsns:
+ - Some of such places are tagged with TBD_GINSN_GEN_NOT_SCFI to serve as
+ possible starting points.
+ - Also note that ginsn representation may need enhancements. Specifically,
+ note some TBD_GINSN_INFO_LOSS and TBD_GINSN_REPRESENTATION_LIMIT markers.
+ */
+
+static ginsnS *
+aarch64_ginsn_new (const symbolS *insn_end_sym, enum ginsn_gen_mode gmode)
+{
+ int err = 0;
+ ginsnS *ginsn = NULL;
+ unsigned int dw2_regnum;
+ aarch64_opnd_info *dest = NULL;
+ bool sp_allowed_p = false;
+ aarch64_inst *base = &inst.base;
+ const aarch64_opcode *opcode = base->opcode;
+
+ /* Currently supports generation of selected ginsns, sufficient for
+ the use-case of SCFI only. To remove this condition will require
+ work on this target-specific process of creation of ginsns. Some
+ of such places are tagged with TBD_GINSN_GEN_NOT_SCFI to serve as
+ examples. */
+ if (gmode != GINSN_GEN_SCFI)
+ return ginsn;
+
+ switch (opcode->iclass)
+ {
+ case addsub_ext:
+ /* TBD_GINSN_GEN_NOT_SCFI: other insns are not of interest for SCFI. */
+ if (aarch64_opcode_subclass_p (opcode, F_ARITH_ADD)
+ || aarch64_opcode_subclass_p (opcode, F_ARITH_SUB))
+ ginsn = aarch64_ginsn_addsub_reg (insn_end_sym);
+ break;
+
+ case addsub_imm:
+ if (aarch64_opcode_subclass_p (opcode, F_ARITH_MOV))
+ ginsn = aarch64_ginsn_mov (insn_end_sym);
+ else if (aarch64_opcode_subclass_p (opcode, F_ARITH_ADD)
+ || aarch64_opcode_subclass_p (opcode, F_ARITH_SUB))
+ ginsn = aarch64_ginsn_addsub_imm (insn_end_sym);
+ break;
+
+ case movewide:
+ ginsn = aarch64_ginsn_mov (insn_end_sym);
+ break;
+
+ case ldst_imm9:
+ case ldst_pos:
+ ginsn = aarch64_ginsn_ldstr (insn_end_sym);
+ break;
+
+ case ldstpair_indexed:
+ case ldstpair_off:
+ ginsn = aarch64_ginsn_ldstp (insn_end_sym);
+ break;
+
+ case branch_imm:
+ case branch_reg:
+ ginsn = aarch64_ginsn_branch_uncond (insn_end_sym);
+ break;
+
+ case compbranch:
+ /* Although cbz/cbnz has an additional operand and are functionally
+ distinct from conditional branches, it is fine to use the same ginsn
+ type for both from the perspective of SCFI. */
+ case testbranch:
+ case condbranch:
+ ginsn = aarch64_ginsn_branch_cond (insn_end_sym);
+ break;
+
+ default:
+ /* TBD_GINSN_GEN_NOT_SCFI: Skip all other opcodes uninteresting for
+ GINSN_GEN_SCFI mode. */
+ break;
+ }
+
+ if (!ginsn && !aarch64_ginsn_safe_to_skip_p ())
+ {
+ /* For all unhandled insns, check that they no not impact SCFI
+ correctness. */
+ err = aarch64_ginsn_unhandled ();
+ switch (err)
+ {
+ case AARCH64_GINSN_UNHANDLED_NONE:
+ break;
+ case AARCH64_GINSN_UNHANDLED_DEST_REG:
+ /* Not all writes to REG_FP are harmful in context of SCFI. Simply
+ generate a GINSN_TYPE_OTHER with destination set to the
+ appropriate register. The SCFI machinery will bail out if this
+ ginsn affects SCFI correctness. */
+ dest = &base->operands[0];
+ sp_allowed_p = (dest->type == AARCH64_OPND_Rd_SP) ? true : false;
+ dw2_regnum = ginsn_dw2_regnum (dest, sp_allowed_p);
+ ginsn = ginsn_new_other (insn_end_sym, true,
+ GINSN_SRC_IMM, 0,
+ GINSN_SRC_IMM, 0,
+ GINSN_DST_REG, dw2_regnum);
+ ginsn_set_where (ginsn);
+ break;
+ case AARCH64_GINSN_UNHANDLED_CFG:
+ case AARCH64_GINSN_UNHANDLED_STACKOP:
+ as_bad (_("SCFI: unhandled op %#x may cause incorrect CFI"),
+ opcode->opcode);
+ break;
+ case AARCH64_GINSN_UNHANDLED_UNEXPECTED:
+ as_bad (_("SCFI: unexpected op %#x may cause incorrect CFI"),
+ opcode->opcode);
+ break;
+ default:
+ abort ();
+ break;
+ }
+ }
+
+ return ginsn;
+}
+
+#endif /* OBJ_ELF. */
+
diff --git a/gas/config/tc-aarch64.c b/gas/config/tc-aarch64.c
index 42c03bd..220b37a 100644
--- a/gas/config/tc-aarch64.c
+++ b/gas/config/tc-aarch64.c
@@ -33,6 +33,7 @@
#include "dw2gencfi.h"
#include "sframe.h"
#include "gen-sframe.h"
+#include "scfi.h"
#endif
#include "dw2gencfi.h"
@@ -8591,6 +8592,10 @@ dump_opcode_operands (const aarch64_opcode *opcode)
}
#endif /* DEBUG_AARCH64 */
+#ifdef OBJ_ELF
+# include "tc-aarch64-ginsn.c"
+#endif
+
/* This is the guts of the machine-dependent assembler. STR points to a
machine dependent instruction. This function is supposed to emit
the frags/bytes it assembles to. */
@@ -8728,6 +8733,16 @@ md_assemble (char *str)
output_inst (copy);
}
+#ifdef OBJ_ELF
+ if (flag_synth_cfi)
+ {
+ ginsnS *ginsn;
+ ginsn = aarch64_ginsn_new (symbol_temp_new_now (),
+ frch_ginsn_gen_mode ());
+ frch_ginsn_data_append (ginsn);
+ }
+#endif
+
/* Issue non-fatal messages if any. */
output_operand_error_report (str, true);
return;
diff --git a/gas/config/tc-aarch64.h b/gas/config/tc-aarch64.h
index 1b8bada..4906a6e 100644
--- a/gas/config/tc-aarch64.h
+++ b/gas/config/tc-aarch64.h
@@ -263,6 +263,27 @@ extern void aarch64_after_parse_args (void);
#ifdef OBJ_ELF
+#define TARGET_USE_GINSN 1
+/* Allow GAS to synthesize DWARF CFI for hand-written asm.
+ PS: TARGET_USE_CFIPOP is a pre-condition. */
+#define TARGET_USE_SCFI 1
+/* Identify the maximum DWARF register number of all the registers being
+ tracked for SCFI. This is the last DWARF register number of the set
+ of SP, FP, and all callee-saved registers. For Aarch64, this means 79
+ because FP/Advanced SIMD v8-v15 are also callee-saved registers. */
+# define SCFI_MAX_REG_ID 79
+/* Identify the DWARF register number of the frame-pointer register. */
+# define REG_FP 29
+/* Identify the DWARF register number of the link register. */
+# define REG_LR 30
+/* Identify the DWARF register number of the stack-pointer register. */
+# define REG_SP 31
+
+#define SCFI_INIT_CFA_OFFSET 0
+
+#define SCFI_CALLEE_SAVED_REG_P(dw2reg) aarch64_scfi_callee_saved_p (dw2reg)
+extern bool aarch64_scfi_callee_saved_p (uint32_t dw2reg_num);
+
/* Whether SFrame stack trace info is supported. */
extern bool aarch64_support_sframe_p (void);
#define support_sframe_p aarch64_support_sframe_p