CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
964 lines
31 KiB
C
964 lines
31 KiB
C
/* tc-aarch64-ginsn.c -- Ginsn generation for the AArch64 ISA
|
|
|
|
Copyright (C) 2024 Free Software Foundation, Inc.
|
|
|
|
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
|
|
|
|
/* Invalid DWARF register number. Used when WZR / XZR is seen. */
|
|
#define GINSN_DW2_REGNUM_INVALID (~0U)
|
|
|
|
/* 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. If OPND is an address,
|
|
the returned register is the base register. If OPND spans multiple
|
|
registers, the returned register is the first of those registers. */
|
|
|
|
static unsigned int
|
|
ginsn_dw2_regnum (aarch64_opnd_info *opnd)
|
|
{
|
|
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_ADDRESS:
|
|
dw2reg_num = opnd->addr.base_regno;
|
|
break;
|
|
case AARCH64_OPND_CLASS_INT_REG:
|
|
case AARCH64_OPND_CLASS_MODIFIED_REG:
|
|
/* Use an invalid DWARF register value in case of WZR, else this will be an
|
|
incorrect dependency on REG_SP. */
|
|
if (aarch64_zero_register_p (opnd))
|
|
dw2reg_num = GINSN_DW2_REGNUM_INVALID;
|
|
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;
|
|
default:
|
|
as_bad ("Unexpected operand class in ginsn_dw2_regnum");
|
|
break;
|
|
}
|
|
|
|
return dw2reg_num;
|
|
}
|
|
|
|
static bool
|
|
ginsn_dw2_regnum_invalid_p (unsigned int opnd_reg)
|
|
{
|
|
return (opnd_reg == GINSN_DW2_REGNUM_INVALID);
|
|
}
|
|
|
|
/* 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];
|
|
|
|
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);
|
|
|
|
dst_reg = ginsn_dw2_regnum (dst);
|
|
opnd_reg = ginsn_dw2_regnum (opnd);
|
|
|
|
if (ginsn_dw2_regnum_invalid_p (dst_reg)
|
|
|| ginsn_dw2_regnum_invalid_p (opnd_reg))
|
|
return ginsn;
|
|
|
|
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);
|
|
src1_reg = ginsn_dw2_regnum (src1);
|
|
src2_reg = ginsn_dw2_regnum (src2);
|
|
|
|
if (ginsn_dw2_regnum_invalid_p (dst_reg)
|
|
|| ginsn_dw2_regnum_invalid_p (src1_reg)
|
|
|| ginsn_dw2_regnum_invalid_p (src2_reg))
|
|
return ginsn;
|
|
|
|
/* ATM, shift amount, if any, cannot be represented in the GINSN_TYPE_ADD or
|
|
GINSN_TYPE_SUB. As the extra information does not impact SCFI
|
|
correctness, skip generating ginsn for these cases. Note
|
|
TBD_GINSN_INFO_LOSS. */
|
|
if (src2->shifter.kind != AARCH64_MOD_NONE
|
|
&& (src2->shifter.kind != AARCH64_MOD_LSL || src2->shifter.amount != 0))
|
|
return ginsn;
|
|
|
|
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;
|
|
ginsnS *ginsn_mem = NULL;
|
|
unsigned int opnd1_reg, opnd2_reg, addr_reg;
|
|
offsetT offset, mem_offset;
|
|
unsigned int width = 8;
|
|
bool load_p = false;
|
|
bool store_p = false;
|
|
bool other_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 == ldstnapair_offs
|
|
|| opcode->iclass == ldstpair_off);
|
|
gas_assert (aarch64_num_of_operands (opcode) == 3);
|
|
|
|
opnd1 = &base->operands[0];
|
|
opnd2 = &base->operands[1];
|
|
addr = &base->operands[2];
|
|
|
|
load_p = aarch64_opcode_subclass_p (opcode, F_LDST_LOAD);
|
|
store_p = aarch64_opcode_subclass_p (opcode, F_LDST_STORE);
|
|
other_p = aarch64_opcode_subclass_p (opcode, F_SUBCLASS_OTHER);
|
|
gas_assert (load_p || store_p || other_p);
|
|
|
|
addr_reg = ginsn_dw2_regnum (addr);
|
|
gas_assert (!addr->addr.offset.is_reg);
|
|
mem_offset = addr->addr.offset.imm;
|
|
|
|
offset = mem_offset;
|
|
/* 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);
|
|
|
|
/* 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 = 0;
|
|
}
|
|
|
|
/* Insns like ldpsw (marked with subclass F_SUBCLASS_OTHER) do not need to
|
|
generate any load or store for SCFI purposes. Next, enforce that for CFI
|
|
purposes, the width of save / restore operation has to be 8 bytes or more.
|
|
However, the address processing component may have updated the stack
|
|
pointer. At least, emit that ginsn and return. Also note,
|
|
TBD_GINSN_GEN_NOT_SCFI. */
|
|
if (other_p || aarch64_get_qualifier_esize (opnd1->qualifier) < 8)
|
|
return ginsn_ind;
|
|
|
|
opnd1_reg = ginsn_dw2_regnum (opnd1);
|
|
opnd2_reg = ginsn_dw2_regnum (opnd2);
|
|
/* Save / restore of WZR is not of interest for SCFI. Exit now if both
|
|
registers are not of interest. */
|
|
if (ginsn_dw2_regnum_invalid_p (opnd1_reg)
|
|
&& ginsn_dw2_regnum_invalid_p (opnd2_reg))
|
|
return ginsn_ind;
|
|
|
|
if (opnd1->qualifier == AARCH64_OPND_QLF_S_Q)
|
|
{
|
|
width = 16;
|
|
if (target_big_endian)
|
|
offset += 8;
|
|
}
|
|
|
|
/* Load store pair where only one of the opnd registers is a zero register
|
|
is possible. E.g., stp xzr, x19, [sp, #16]. */
|
|
if (!ginsn_dw2_regnum_invalid_p (opnd1_reg))
|
|
{
|
|
if (store_p)
|
|
{
|
|
ginsn_mem1 = ginsn_new_store (insn_end_sym, false,
|
|
GINSN_SRC_REG, opnd1_reg,
|
|
GINSN_DST_INDIRECT, addr_reg, offset);
|
|
ginsn_set_where (ginsn_mem1);
|
|
}
|
|
else
|
|
{
|
|
ginsn_mem1 = ginsn_new_load (insn_end_sym, false,
|
|
GINSN_SRC_INDIRECT, addr_reg, offset,
|
|
GINSN_DST_REG, opnd1_reg);
|
|
ginsn_set_where (ginsn_mem1);
|
|
}
|
|
/* Keep track of the last memory ginsn created so far. */
|
|
ginsn_mem = ginsn_mem1;
|
|
}
|
|
if (!ginsn_dw2_regnum_invalid_p (opnd2_reg))
|
|
{
|
|
if (store_p)
|
|
{
|
|
ginsn_mem2 = ginsn_new_store (insn_end_sym, false,
|
|
GINSN_SRC_REG, opnd2_reg,
|
|
GINSN_DST_INDIRECT, addr_reg,
|
|
offset + width);
|
|
ginsn_set_where (ginsn_mem2);
|
|
}
|
|
else
|
|
{
|
|
ginsn_mem2 = ginsn_new_load (insn_end_sym, false,
|
|
GINSN_SRC_INDIRECT, addr_reg, offset + width,
|
|
GINSN_DST_REG, opnd2_reg);
|
|
ginsn_set_where (ginsn_mem2);
|
|
}
|
|
/* Keep track of the last memory ginsn created so far. */
|
|
ginsn_mem = ginsn_mem2;
|
|
}
|
|
|
|
if (!ginsn_dw2_regnum_invalid_p (opnd1_reg)
|
|
&& !ginsn_dw2_regnum_invalid_p (opnd2_reg))
|
|
goto link_two_ginsn_mem;
|
|
else
|
|
goto link_one_ginsn_mem;
|
|
|
|
link_one_ginsn_mem:
|
|
/* Link the list of ginsns created. */
|
|
if (addr->addr.preind && addr->addr.writeback)
|
|
gas_assert (!ginsn_link_next (ginsn_ind, ginsn_mem));
|
|
|
|
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;
|
|
|
|
link_two_ginsn_mem:
|
|
/* 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_mem1 && ginsn_mem2 && ginsn_mem1 != ginsn_mem2);
|
|
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 load_p = false;
|
|
bool store_p = false;
|
|
bool other_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];
|
|
|
|
load_p = aarch64_opcode_subclass_p (opcode, F_LDST_LOAD);
|
|
store_p = aarch64_opcode_subclass_p (opcode, F_LDST_STORE);
|
|
other_p = aarch64_opcode_subclass_p (opcode, F_SUBCLASS_OTHER);
|
|
gas_assert (load_p || store_p || other_p);
|
|
|
|
addr_reg = ginsn_dw2_regnum (addr);
|
|
|
|
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;
|
|
}
|
|
|
|
offset = mem_offset;
|
|
/* 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);
|
|
|
|
/* 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 = 0;
|
|
}
|
|
|
|
/* Insns like stg, prfm, ldrsw etc. (marked with subclass F_SUBCLASS_OTHER)
|
|
do not need to generate any load / store ginsns for SCFI purposes. Next,
|
|
enforce that for CFI purposes, the width of save / restore operation has
|
|
to be 8 bytes or more. That said, the address processing component may
|
|
have updated the stack pointer. At least, emit that ginsn and return.
|
|
Also note, TBD_GINSN_GEN_NOT_SCFI. */
|
|
if (other_p || aarch64_get_qualifier_esize (opnd1->qualifier) < 8)
|
|
return ginsn_ind;
|
|
|
|
opnd_reg = ginsn_dw2_regnum (opnd1);
|
|
/* Save / restore of WZR is not of interest for SCFI. */
|
|
if (ginsn_dw2_regnum_invalid_p (opnd_reg))
|
|
return ginsn_ind;
|
|
|
|
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
|
|
&& (inst.reloc.type == BFD_RELOC_AARCH64_CALL26
|
|
|| inst.reloc.type == BFD_RELOC_AARCH64_JUMP26))
|
|
{
|
|
if (inst.reloc.exp.X_add_number)
|
|
{
|
|
/* A non-zero addend in b/bl target makes control-flow tracking
|
|
difficult. Skip SCFI for now. */
|
|
as_bad (_("SCFI: %#x op with non-zero addend to sym not supported"),
|
|
opcode->opcode);
|
|
return ginsn;
|
|
}
|
|
/* b or bl. */
|
|
src_symbol = inst.reloc.exp.X_add_symbol;
|
|
src_type = GINSN_SRC_SYMBOL;
|
|
}
|
|
else if (opcode->iclass == branch_reg
|
|
&& aarch64_num_of_operands (opcode) >= 1)
|
|
{
|
|
/* 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]);
|
|
}
|
|
else
|
|
/* Skip insns like branch imm. */
|
|
return ginsn;
|
|
|
|
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 = NULL;
|
|
const symbolS *src_symbol;
|
|
enum ginsn_src_type src_type;
|
|
|
|
aarch64_inst *base = &inst.base;
|
|
const aarch64_opcode *opcode = base->opcode;
|
|
|
|
if (inst.reloc.type == BFD_RELOC_AARCH64_BRANCH19
|
|
|| inst.reloc.type == BFD_RELOC_AARCH64_TSTBR14)
|
|
{
|
|
if (inst.reloc.exp.X_add_number)
|
|
{
|
|
/* A non-zero addend in target makes control-flow tracking
|
|
difficult. Skip SCFI for now. */
|
|
as_bad (_("SCFI: %#x op with non-zero addend to sym not supported"),
|
|
opcode->opcode);
|
|
return ginsn;
|
|
}
|
|
|
|
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 with reg opnd. */
|
|
|
|
static ginsnS *
|
|
aarch64_ginsn_mov_reg (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);
|
|
src_reg = ginsn_dw2_regnum (src);
|
|
src_type = GINSN_SRC_REG;
|
|
|
|
/* FIXME Explicitly bar GINSN_TYPE_MOV with a GINSN_DW2_REGNUM_INVALID in src
|
|
or dest at this time. This can be removed later when SCFI machinery is
|
|
more robust to deal with GINSN_DW2_REGNUM_INVALID. */
|
|
if (ginsn_dw2_regnum_invalid_p (dst_reg)
|
|
|| ginsn_dw2_regnum_invalid_p (src_reg))
|
|
return ginsn;
|
|
|
|
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;
|
|
}
|
|
|
|
/* Generate ginsn for mov instructions with imm opnd. */
|
|
|
|
static ginsnS *
|
|
aarch64_ginsn_mov_imm (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];
|
|
|
|
/* 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;
|
|
|
|
dst_reg = ginsn_dw2_regnum (dst);
|
|
/* FIXME Explicitly bar GINSN_TYPE_MOV which write to WZR / XZR at this time.
|
|
This can be removed later when SCFI machinery is more robust to deal with
|
|
GINSN_DW2_REGNUM_INVALID. */
|
|
if (ginsn_dw2_regnum_invalid_p (dst_reg))
|
|
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
|
|
/* Skip now and handle via aarch64_ginsn_unhandled () code path. */
|
|
return ginsn;
|
|
|
|
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 opnd_reg;
|
|
int num_opnds = 0;
|
|
bool dp_tag_only_p = false;
|
|
|
|
aarch64_inst *base = &inst.base;
|
|
const aarch64_opcode *opcode = base->opcode;
|
|
|
|
/* ATM, whitelisting operations with no operands does not seem to be
|
|
necessary. In fact, whitelisting insns like ERET will be dangerous for
|
|
SCFI. So, return false now and bar any such insns from being whitelisted
|
|
altogether. */
|
|
num_opnds = aarch64_num_of_operands (opcode);
|
|
if (!num_opnds)
|
|
return false;
|
|
|
|
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);
|
|
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. */
|
|
dp_tag_only_p = aarch64_opcode_subclass_p (opcode, F_DP_TAG_ONLY);
|
|
if (dp_tag_only_p)
|
|
skip_p = true;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return skip_p;
|
|
}
|
|
|
|
enum aarch64_ginsn_unhandled_code
|
|
{
|
|
AARCH64_GINSN_UNHANDLED_NONE,
|
|
AARCH64_GINSN_UNHANDLED_DEST_REG,
|
|
AARCH64_GINSN_UNHANDLED_CFG,
|
|
AARCH64_GINSN_UNHANDLED_STACKOP,
|
|
AARCH64_GINSN_UNHANDLED_UNEXPECTED,
|
|
};
|
|
|
|
/* Check the input insn for its impact on the correctness of the synthesized
|
|
CFI. Returns an error code to the caller. */
|
|
|
|
static enum aarch64_ginsn_unhandled_code
|
|
aarch64_ginsn_unhandled (void)
|
|
{
|
|
enum aarch64_ginsn_unhandled_code 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;
|
|
unsigned int opnd_reg;
|
|
|
|
/* All change of flow instructions (COFI) are important for SCFI.
|
|
N.B. New iclasses for COFI when defined must be added here too. */
|
|
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. Except ldstnapair_offs from the afore-mentioned
|
|
list, these iclasses do not seem to be amenable to being used for
|
|
save/restore ops either. */
|
|
else if (opcode->iclass == ldstpair_off
|
|
|| opcode->iclass == ldstnapair_offs
|
|
|| opcode->iclass == ldstpair_indexed
|
|
|| opcode->iclass == ldst_imm9
|
|
|| opcode->iclass == ldst_imm10
|
|
|| opcode->iclass == ldst_pos)
|
|
{
|
|
addr = &base->operands[num_opnds - 1];
|
|
addr_reg = ginsn_dw2_regnum (addr);
|
|
if (addr_reg == REG_SP || addr_reg == REG_FP)
|
|
{
|
|
/* For all skipped memory operations, check if an update to REG_SP or
|
|
REG_FP is involved. */
|
|
if ((addr->addr.postind || addr->addr.preind) && addr->addr.writeback)
|
|
err = AARCH64_GINSN_UNHANDLED_STACKOP;
|
|
/* Also check if a save / restore of a callee-saved register has been
|
|
missed. */
|
|
else if (!aarch64_opcode_subclass_p (opcode, F_SUBCLASS_OTHER))
|
|
{
|
|
for (int i = 0; i < num_opnds - 1; i++)
|
|
{
|
|
opnd = &base->operands[i];
|
|
opnd_reg = ginsn_dw2_regnum (opnd);
|
|
if (aarch64_scfi_callee_saved_p (opnd_reg)
|
|
&& aarch64_get_qualifier_esize (opnd->qualifier) >= 8)
|
|
{
|
|
err = AARCH64_GINSN_UNHANDLED_STACKOP;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/* 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. */
|
|
else if (opcode->iclass == sve_misc)
|
|
{
|
|
opnd = &base->operands[0];
|
|
addr = &base->operands[num_opnds - 1];
|
|
addr_reg = ginsn_dw2_regnum (addr);
|
|
opnd_reg = ginsn_dw2_regnum (opnd);
|
|
/* For all skipped memory operations, check if an update to REG_SP or
|
|
REG_FP is involved. */
|
|
if (aarch64_get_operand_class (addr->type) == AARCH64_OPND_CLASS_ADDRESS
|
|
&& (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))
|
|
{
|
|
dw2_regnum = ginsn_dw2_regnum (dest);
|
|
|
|
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)
|
|
{
|
|
enum aarch64_ginsn_unhandled_code err = 0;
|
|
ginsnS *ginsn = NULL;
|
|
unsigned int dw2_regnum;
|
|
aarch64_opnd_info *dest = NULL;
|
|
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_reg (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);
|
|
/* Note how addg, subg involving tags have F_SUBCLASS_OTHER flag. These
|
|
insns will see a GINSN_TYPE_OTHER created for them if the destination
|
|
register is of interest via the aarch64_ginsn_unhandled ()
|
|
codepath. */
|
|
break;
|
|
|
|
case movewide:
|
|
ginsn = aarch64_ginsn_mov_imm (insn_end_sym);
|
|
break;
|
|
|
|
case ldst_imm9:
|
|
case ldst_pos:
|
|
ginsn = aarch64_ginsn_ldstr (insn_end_sym);
|
|
break;
|
|
|
|
case ldstpair_indexed:
|
|
case ldstpair_off:
|
|
case ldstnapair_offs:
|
|
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];
|
|
dw2_regnum = ginsn_dw2_regnum (dest);
|
|
/* Sanity check. */
|
|
gas_assert (!ginsn_dw2_regnum_invalid_p (dw2_regnum));
|
|
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. */
|
|
|