| /* ginsn.h - GAS instruction representation. |
| Copyright (C) 2023 Free Software Foundation, Inc. |
| |
| This file is part of GAS, the GNU Assembler. |
| |
| 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, 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 GAS; see the file COPYING. If not, write to the Free |
| Software Foundation, 51 Franklin Street - Fifth Floor, Boston, MA |
| 02110-1301, USA. */ |
| |
| #include "as.h" |
| #include "subsegs.h" |
| #include "ginsn.h" |
| #include "scfi.h" |
| |
| #ifdef TARGET_USE_GINSN |
| |
| static const char *const ginsn_type_names[] = |
| { |
| #define _GINSN_TYPE_ITEM(NAME, STR) STR, |
| _GINSN_TYPES |
| #undef _GINSN_TYPE_ITEM |
| }; |
| |
| static ginsnS * |
| ginsn_alloc (void) |
| { |
| ginsnS *ginsn = XCNEW (ginsnS); |
| return ginsn; |
| } |
| |
| static ginsnS * |
| ginsn_init (enum ginsn_type type, const symbolS *sym, bool real_p) |
| { |
| ginsnS *ginsn = ginsn_alloc (); |
| ginsn->type = type; |
| ginsn->sym = sym; |
| if (real_p) |
| ginsn->flags |= GINSN_F_INSN_REAL; |
| return ginsn; |
| } |
| |
| static void |
| ginsn_cleanup (ginsnS **ginsnp) |
| { |
| ginsnS *ginsn; |
| |
| if (!ginsnp || !*ginsnp) |
| return; |
| |
| ginsn = *ginsnp; |
| if (ginsn->scfi_ops) |
| { |
| scfi_ops_cleanup (ginsn->scfi_ops); |
| ginsn->scfi_ops = NULL; |
| } |
| |
| free (ginsn); |
| *ginsnp = NULL; |
| } |
| |
| static void |
| ginsn_set_src (struct ginsn_src *src, enum ginsn_src_type type, unsigned int reg, |
| offsetT immdisp) |
| { |
| if (!src) |
| return; |
| |
| src->type = type; |
| /* Even when the use-case is SCFI, the value of reg may be > SCFI_MAX_REG_ID. |
| E.g., in AMD64, push fs etc. */ |
| src->reg = reg; |
| src->immdisp = immdisp; |
| } |
| |
| static void |
| ginsn_set_dst (struct ginsn_dst *dst, enum ginsn_dst_type type, unsigned int reg, |
| offsetT disp) |
| { |
| if (!dst) |
| return; |
| |
| dst->type = type; |
| dst->reg = reg; |
| |
| if (type == GINSN_DST_INDIRECT) |
| dst->disp = disp; |
| } |
| |
| static void |
| ginsn_set_file_line (ginsnS *ginsn, const char *file, unsigned int line) |
| { |
| if (!ginsn) |
| return; |
| |
| ginsn->file = file; |
| ginsn->line = line; |
| } |
| |
| struct ginsn_src * |
| ginsn_get_src1 (ginsnS *ginsn) |
| { |
| return &ginsn->src[0]; |
| } |
| |
| struct ginsn_src * |
| ginsn_get_src2 (ginsnS *ginsn) |
| { |
| return &ginsn->src[1]; |
| } |
| |
| struct ginsn_dst * |
| ginsn_get_dst (ginsnS *ginsn) |
| { |
| return &ginsn->dst; |
| } |
| |
| unsigned int |
| ginsn_get_src_reg (struct ginsn_src *src) |
| { |
| return src->reg; |
| } |
| |
| enum ginsn_src_type |
| ginsn_get_src_type (struct ginsn_src *src) |
| { |
| return src->type; |
| } |
| |
| offsetT |
| ginsn_get_src_disp (struct ginsn_src *src) |
| { |
| return src->immdisp; |
| } |
| |
| offsetT |
| ginsn_get_src_imm (struct ginsn_src *src) |
| { |
| return src->immdisp; |
| } |
| |
| unsigned int |
| ginsn_get_dst_reg (struct ginsn_dst *dst) |
| { |
| return dst->reg; |
| } |
| |
| enum ginsn_dst_type |
| ginsn_get_dst_type (struct ginsn_dst *dst) |
| { |
| return dst->type; |
| } |
| |
| offsetT |
| ginsn_get_dst_disp (struct ginsn_dst *dst) |
| { |
| return dst->disp; |
| } |
| |
| void |
| label_ginsn_map_insert (const symbolS *label, ginsnS *ginsn) |
| { |
| const char *name = S_GET_NAME (label); |
| str_hash_insert (frchain_now->frch_ginsn_data->label_ginsn_map, |
| name, ginsn, 0 /* noreplace. */); |
| } |
| |
| ginsnS * |
| label_ginsn_map_find (const symbolS *label) |
| { |
| const char *name = S_GET_NAME (label); |
| ginsnS *ginsn |
| = (ginsnS *) str_hash_find (frchain_now->frch_ginsn_data->label_ginsn_map, |
| name); |
| return ginsn; |
| } |
| |
| ginsnS * |
| ginsn_new_phantom (const symbolS *sym) |
| { |
| ginsnS *ginsn = ginsn_alloc (); |
| ginsn->type = GINSN_TYPE_PHANTOM; |
| ginsn->sym = sym; |
| /* By default, GINSN_F_INSN_REAL is not set in ginsn->flags. */ |
| return ginsn; |
| } |
| |
| ginsnS * |
| ginsn_new_symbol (const symbolS *sym, bool func_begin_p) |
| { |
| ginsnS *ginsn = ginsn_alloc (); |
| ginsn->type = GINSN_TYPE_SYMBOL; |
| ginsn->sym = sym; |
| if (func_begin_p) |
| ginsn->flags |= GINSN_F_FUNC_MARKER; |
| return ginsn; |
| } |
| |
| ginsnS * |
| ginsn_new_symbol_func_begin (const symbolS *sym) |
| { |
| return ginsn_new_symbol (sym, true); |
| } |
| |
| ginsnS * |
| ginsn_new_symbol_func_end (const symbolS *sym) |
| { |
| return ginsn_new_symbol (sym, false); |
| } |
| |
| ginsnS * |
| ginsn_new_symbol_user_label (const symbolS *sym) |
| { |
| ginsnS *ginsn = ginsn_new_symbol (sym, false); |
| ginsn->flags |= GINSN_F_USER_LABEL; |
| return ginsn; |
| } |
| |
| ginsnS * |
| ginsn_new_add (const symbolS *sym, bool real_p, |
| enum ginsn_src_type src1_type, unsigned int src1_reg, offsetT src1_disp, |
| enum ginsn_src_type src2_type, unsigned int src2_reg, offsetT src2_disp, |
| enum ginsn_dst_type dst_type, unsigned int dst_reg, offsetT dst_disp) |
| { |
| ginsnS *ginsn = ginsn_init (GINSN_TYPE_ADD, sym, real_p); |
| /* src info. */ |
| ginsn_set_src (&ginsn->src[0], src1_type, src1_reg, src1_disp); |
| ginsn_set_src (&ginsn->src[1], src2_type, src2_reg, src2_disp); |
| /* dst info. */ |
| ginsn_set_dst (&ginsn->dst, dst_type, dst_reg, dst_disp); |
| |
| return ginsn; |
| } |
| |
| ginsnS * |
| ginsn_new_and (const symbolS *sym, bool real_p, |
| enum ginsn_src_type src1_type, unsigned int src1_reg, offsetT src1_disp, |
| enum ginsn_src_type src2_type, unsigned int src2_reg, offsetT src2_disp, |
| enum ginsn_dst_type dst_type, unsigned int dst_reg, offsetT dst_disp) |
| { |
| ginsnS *ginsn = ginsn_init (GINSN_TYPE_AND, sym, real_p); |
| /* src info. */ |
| ginsn_set_src (&ginsn->src[0], src1_type, src1_reg, src1_disp); |
| ginsn_set_src (&ginsn->src[1], src2_type, src2_reg, src2_disp); |
| /* dst info. */ |
| ginsn_set_dst (&ginsn->dst, dst_type, dst_reg, dst_disp); |
| |
| return ginsn; |
| } |
| |
| ginsnS * |
| ginsn_new_call (const symbolS *sym, bool real_p, |
| enum ginsn_src_type src_type, unsigned int src_reg, |
| const symbolS *src_text_sym) |
| |
| { |
| ginsnS *ginsn = ginsn_init (GINSN_TYPE_CALL, sym, real_p); |
| /* src info. */ |
| ginsn_set_src (&ginsn->src[0], src_type, src_reg, 0); |
| |
| if (src_type == GINSN_SRC_SYMBOL) |
| ginsn->src[0].sym = src_text_sym; |
| |
| return ginsn; |
| } |
| |
| ginsnS * |
| ginsn_new_jump (const symbolS *sym, bool real_p, |
| enum ginsn_src_type src_type, unsigned int src_reg, |
| const symbolS *src_ginsn_sym) |
| { |
| ginsnS *ginsn = ginsn_init (GINSN_TYPE_JUMP, sym, real_p); |
| /* src info. */ |
| ginsn_set_src (&ginsn->src[0], src_type, src_reg, 0); |
| |
| if (src_type == GINSN_SRC_SYMBOL) |
| ginsn->src[0].sym = src_ginsn_sym; |
| |
| return ginsn; |
| } |
| |
| ginsnS * |
| ginsn_new_jump_cond (const symbolS *sym, bool real_p, |
| enum ginsn_src_type src_type, unsigned int src_reg, |
| const symbolS *src_ginsn_sym) |
| { |
| ginsnS *ginsn = ginsn_init (GINSN_TYPE_JUMP_COND, sym, real_p); |
| /* src info. */ |
| ginsn_set_src (&ginsn->src[0], src_type, src_reg, 0); |
| |
| if (src_type == GINSN_SRC_SYMBOL) |
| ginsn->src[0].sym = src_ginsn_sym; |
| |
| return ginsn; |
| } |
| |
| ginsnS * |
| ginsn_new_mov (const symbolS *sym, bool real_p, |
| enum ginsn_src_type src_type, unsigned int src_reg, offsetT src_disp, |
| enum ginsn_dst_type dst_type, unsigned int dst_reg, offsetT dst_disp) |
| { |
| ginsnS *ginsn = ginsn_init (GINSN_TYPE_MOV, sym, real_p); |
| /* src info. */ |
| ginsn_set_src (&ginsn->src[0], src_type, src_reg, src_disp); |
| /* dst info. */ |
| ginsn_set_dst (&ginsn->dst, dst_type, dst_reg, dst_disp); |
| |
| return ginsn; |
| } |
| |
| ginsnS * |
| ginsn_new_store (const symbolS *sym, bool real_p, |
| enum ginsn_src_type src_type, unsigned int src_reg, |
| enum ginsn_dst_type dst_type, unsigned int dst_reg, offsetT dst_disp) |
| { |
| ginsnS *ginsn = ginsn_init (GINSN_TYPE_STORE, sym, real_p); |
| /* src info. */ |
| ginsn_set_src (&ginsn->src[0], src_type, src_reg, 0); |
| /* dst info. */ |
| gas_assert (dst_type == GINSN_DST_INDIRECT); |
| ginsn_set_dst (&ginsn->dst, dst_type, dst_reg, dst_disp); |
| |
| return ginsn; |
| } |
| |
| ginsnS * |
| ginsn_new_load (const symbolS *sym, bool real_p, |
| enum ginsn_src_type src_type, unsigned int src_reg, offsetT src_disp, |
| enum ginsn_dst_type dst_type, unsigned int dst_reg) |
| { |
| ginsnS *ginsn = ginsn_init (GINSN_TYPE_LOAD, sym, real_p); |
| /* src info. */ |
| gas_assert (src_type == GINSN_SRC_INDIRECT); |
| ginsn_set_src (&ginsn->src[0], src_type, src_reg, src_disp); |
| /* dst info. */ |
| ginsn_set_dst (&ginsn->dst, dst_type, dst_reg, 0); |
| |
| return ginsn; |
| } |
| |
| ginsnS * |
| ginsn_new_sub (const symbolS *sym, bool real_p, |
| enum ginsn_src_type src1_type, unsigned int src1_reg, offsetT src1_disp, |
| enum ginsn_src_type src2_type, unsigned int src2_reg, offsetT src2_disp, |
| enum ginsn_dst_type dst_type, unsigned int dst_reg, offsetT dst_disp) |
| { |
| ginsnS *ginsn = ginsn_init (GINSN_TYPE_SUB, sym, real_p); |
| /* src info. */ |
| ginsn_set_src (&ginsn->src[0], src1_type, src1_reg, src1_disp); |
| ginsn_set_src (&ginsn->src[1], src2_type, src2_reg, src2_disp); |
| /* dst info. */ |
| ginsn_set_dst (&ginsn->dst, dst_type, dst_reg, dst_disp); |
| |
| return ginsn; |
| } |
| |
| /* PS: Note this API does not identify the displacement values of |
| src1/src2/dst. At this time, it is unnecessary for correctness to support |
| the additional argument. */ |
| |
| ginsnS * |
| ginsn_new_other (const symbolS *sym, bool real_p, |
| enum ginsn_src_type src1_type, unsigned int src1_val, |
| enum ginsn_src_type src2_type, unsigned int src2_val, |
| enum ginsn_dst_type dst_type, unsigned int dst_reg) |
| { |
| ginsnS *ginsn = ginsn_init (GINSN_TYPE_OTHER, sym, real_p); |
| /* src info. */ |
| ginsn_set_src (&ginsn->src[0], src1_type, src1_val, src1_val); |
| /* GINSN_SRC_INDIRECT src2_type is not expected. */ |
| gas_assert (src2_type != GINSN_SRC_INDIRECT); |
| ginsn_set_src (&ginsn->src[1], src2_type, src2_val, src2_val); |
| /* dst info. */ |
| ginsn_set_dst (&ginsn->dst, dst_type, dst_reg, 0); |
| |
| return ginsn; |
| } |
| |
| ginsnS * |
| ginsn_new_return (const symbolS *sym, bool real_p) |
| { |
| ginsnS *ginsn = ginsn_init (GINSN_TYPE_RETURN, sym, real_p); |
| return ginsn; |
| } |
| |
| void |
| ginsn_set_where (ginsnS *ginsn) |
| { |
| const char *file; |
| unsigned int line; |
| file = as_where (&line); |
| ginsn_set_file_line (ginsn, file, line); |
| } |
| |
| int |
| ginsn_link_next (ginsnS *ginsn, ginsnS *next) |
| { |
| int ret = 0; |
| |
| /* Avoid data corruption by limiting the scope of the API. */ |
| if (!ginsn || ginsn->next) |
| return 1; |
| |
| ginsn->next = next; |
| |
| return ret; |
| } |
| |
| bool |
| ginsn_track_reg_p (unsigned int dw2reg, enum ginsn_gen_mode gmode) |
| { |
| bool track_p = false; |
| |
| if (gmode == GINSN_GEN_SCFI && dw2reg <= SCFI_MAX_REG_ID) |
| { |
| /* FIXME - rename this to tc_ ? */ |
| track_p |= SCFI_CALLEE_SAVED_REG_P (dw2reg); |
| track_p |= (dw2reg == REG_FP); |
| track_p |= (dw2reg == REG_SP); |
| } |
| |
| return track_p; |
| } |
| |
| static bool |
| ginsn_indirect_jump_p (ginsnS *ginsn) |
| { |
| bool ret_p = false; |
| if (!ginsn) |
| return ret_p; |
| |
| ret_p = (ginsn->type == GINSN_TYPE_JUMP |
| && ginsn->src[0].type == GINSN_SRC_REG); |
| return ret_p; |
| } |
| |
| /* Return whether the GINSN is an unconditional jump to a label which is |
| defined locally in the scope of the block of insns, which are currently |
| being processed for GCFG creation. */ |
| |
| static bool |
| ginsn_direct_local_jump_p (ginsnS *ginsn) |
| { |
| bool local_p = false; |
| const symbolS *taken_label; |
| |
| if (!ginsn) |
| return local_p; |
| |
| if (ginsn->type == GINSN_TYPE_JUMP |
| && ginsn->src[0].type == GINSN_SRC_SYMBOL) |
| { |
| taken_label = ginsn->src[0].sym; |
| local_p = (label_ginsn_map_find (taken_label) != NULL); |
| } |
| return local_p; |
| } |
| |
| static char * |
| ginsn_src_print (struct ginsn_src *src) |
| { |
| int str_size = 0; |
| const size_t len = GINSN_LISTING_OPND_LEN; |
| char *src_str = XNEWVEC (char, len); |
| |
| memset (src_str, 0, len); |
| |
| switch (src->type) |
| { |
| case GINSN_SRC_REG: |
| str_size = snprintf (src_str, len, "%%r%d", ginsn_get_src_reg (src)); |
| break; |
| case GINSN_SRC_IMM: |
| str_size = snprintf (src_str, len, "%lld", |
| (long long int) ginsn_get_src_imm (src)); |
| break; |
| case GINSN_SRC_INDIRECT: |
| str_size = snprintf (src_str, len, "[%%r%d+%lld]", |
| ginsn_get_src_reg (src), |
| (long long int) ginsn_get_src_disp (src)); |
| break; |
| default: |
| break; |
| } |
| |
| gas_assert (str_size >= 0 && str_size < (int)len); |
| |
| return src_str; |
| } |
| |
| static char* |
| ginsn_dst_print (struct ginsn_dst *dst) |
| { |
| int str_size = 0; |
| const size_t len = GINSN_LISTING_OPND_LEN; |
| char *dst_str = XNEWVEC (char, len); |
| |
| memset (dst_str, 0, len); |
| |
| switch (dst->type) |
| { |
| case GINSN_DST_REG: |
| str_size = snprintf (dst_str, len, |
| "%%r%d", ginsn_get_dst_reg (dst)); |
| break; |
| case GINSN_DST_INDIRECT: |
| str_size = snprintf (dst_str, len, |
| "[%%r%d+%lld]", ginsn_get_dst_reg (dst), |
| (long long int) ginsn_get_dst_disp (dst)); |
| break; |
| default: |
| /* Other dst types are unexpected. */ |
| gas_assert (dst->type == GINSN_DST_UNKNOWN); |
| break; |
| } |
| |
| /* str_size will remain 0 when GINSN_DST_UNKNOWN. */ |
| gas_assert (str_size >= 0 && str_size < (int)len); |
| |
| return dst_str; |
| } |
| |
| static const char* |
| ginsn_type_func_marker_print (ginsnS *ginsn) |
| { |
| int id = 0; |
| static const char * const ginsn_sym_strs[] = |
| { "", "FUNC_BEGIN", "FUNC_END" }; |
| |
| if (GINSN_F_FUNC_BEGIN_P (ginsn)) |
| id = 1; |
| else if (GINSN_F_FUNC_END_P (ginsn)) |
| id = 2; |
| |
| return ginsn_sym_strs[id]; |
| } |
| |
| static char* |
| ginsn_print (ginsnS *ginsn) |
| { |
| struct ginsn_src *src; |
| struct ginsn_dst *dst; |
| int str_size = 0; |
| size_t len = GINSN_LISTING_LEN; |
| char *ginsn_str = XNEWVEC (char, len); |
| |
| memset (ginsn_str, 0, len); |
| |
| str_size = snprintf (ginsn_str, GINSN_LISTING_LEN, "ginsn: %s", |
| ginsn_type_names[ginsn->type]); |
| gas_assert (str_size >= 0 && str_size < GINSN_LISTING_LEN); |
| |
| /* For some ginsn types, no further information is printed for now. */ |
| if (ginsn->type == GINSN_TYPE_CALL |
| || ginsn->type == GINSN_TYPE_RETURN) |
| goto end; |
| else if (ginsn->type == GINSN_TYPE_SYMBOL) |
| { |
| if (GINSN_F_USER_LABEL_P (ginsn)) |
| str_size += snprintf (ginsn_str + str_size, |
| GINSN_LISTING_LEN - str_size, |
| " %s", S_GET_NAME (ginsn->sym)); |
| else |
| str_size += snprintf (ginsn_str + str_size, |
| GINSN_LISTING_LEN - str_size, |
| " %s", ginsn_type_func_marker_print (ginsn)); |
| goto end; |
| } |
| |
| /* src 1. */ |
| src = ginsn_get_src1 (ginsn); |
| char *src_buf = ginsn_src_print (src); |
| str_size += snprintf (ginsn_str + str_size, GINSN_LISTING_LEN - str_size, |
| " %s", src_buf); |
| free (src_buf); |
| gas_assert (str_size >= 0 && str_size < GINSN_LISTING_LEN); |
| |
| /* src 2. */ |
| src = ginsn_get_src2 (ginsn); |
| src_buf = ginsn_src_print (src); |
| if (strlen (src_buf)) |
| { |
| str_size += snprintf (ginsn_str + str_size, GINSN_LISTING_LEN - str_size, |
| ", %s", src_buf); |
| } |
| free (src_buf); |
| gas_assert (str_size >= 0 && str_size < GINSN_LISTING_LEN); |
| |
| /* dst. */ |
| dst = ginsn_get_dst (ginsn); |
| char *dst_buf = ginsn_dst_print (dst); |
| if (strlen (dst_buf)) |
| { |
| str_size += snprintf (ginsn_str + str_size, GINSN_LISTING_LEN - str_size, |
| ", %s", dst_buf); |
| } |
| free (dst_buf); |
| |
| end: |
| gas_assert (str_size >= 0 && str_size < GINSN_LISTING_LEN); |
| return ginsn_str; |
| } |
| |
| static void |
| gbb_cleanup (gbbS **bbp) |
| { |
| gbbS *bb = NULL; |
| |
| if (!bbp && !*bbp) |
| return; |
| |
| bb = *bbp; |
| |
| if (bb->entry_state) |
| { |
| free (bb->entry_state); |
| bb->entry_state = NULL; |
| } |
| if (bb->exit_state) |
| { |
| free (bb->exit_state); |
| bb->exit_state = NULL; |
| } |
| free (bb); |
| *bbp = NULL; |
| } |
| |
| /* Add an edge from the source bb FROM_BB to the sink bb TO_BB. */ |
| |
| static void |
| bb_add_edge (gbbS* from_bb, gbbS *to_bb) |
| { |
| gedgeS *tmpedge = NULL; |
| gedgeS *gedge; |
| bool exists = false; |
| |
| if (!from_bb || !to_bb) |
| return; |
| |
| /* Create a new edge object. */ |
| gedge = XCNEW (gedgeS); |
| gedge->dst_bb = to_bb; |
| gedge->next = NULL; |
| gedge->visited = false; |
| |
| /* Add it in. */ |
| if (from_bb->out_gedges == NULL) |
| { |
| from_bb->out_gedges = gedge; |
| from_bb->num_out_gedges++; |
| } |
| else |
| { |
| /* Get the head of the list. */ |
| tmpedge = from_bb->out_gedges; |
| while (tmpedge) |
| { |
| /* Do not add duplicate edges. Duplicated edges will cause unwanted |
| failures in the forward and backward passes for SCFI. */ |
| if (tmpedge->dst_bb == to_bb) |
| { |
| exists = true; |
| break; |
| } |
| if (tmpedge->next) |
| tmpedge = tmpedge->next; |
| else |
| break; |
| } |
| |
| if (!exists) |
| { |
| tmpedge->next = gedge; |
| from_bb->num_out_gedges++; |
| } |
| else |
| free (gedge); |
| } |
| } |
| |
| static void |
| cfg_add_bb (gcfgS *gcfg, gbbS *gbb) |
| { |
| gbbS *last_bb = NULL; |
| |
| if (!gcfg->root_bb) |
| gcfg->root_bb = gbb; |
| else |
| { |
| last_bb = gcfg->root_bb; |
| while (last_bb->next) |
| last_bb = last_bb->next; |
| |
| last_bb->next = gbb; |
| } |
| gcfg->num_gbbs++; |
| |
| gbb->id = gcfg->num_gbbs; |
| } |
| |
| static gbbS * |
| add_bb_at_ginsn (const symbolS *func, gcfgS *gcfg, ginsnS *ginsn, gbbS *prev_bb, |
| int *errp); |
| |
| /* Return the already existing basic block (if present), which begins with |
| GINSN, in the given GCFG. Return NULL otherwise. */ |
| |
| static gbbS * |
| find_bb (gcfgS *gcfg, ginsnS *ginsn) |
| { |
| gbbS *found_bb = NULL; |
| gbbS *gbb = NULL; |
| |
| if (!ginsn) |
| return found_bb; |
| |
| if (ginsn->visited) |
| { |
| cfg_for_each_bb (gcfg, gbb) |
| { |
| if (gbb->first_ginsn == ginsn) |
| { |
| found_bb = gbb; |
| break; |
| } |
| } |
| /* Must be found because ginsn is visited. */ |
| gas_assert (found_bb); |
| } |
| |
| return found_bb; |
| } |
| |
| /* Get the basic block starting at GINSN in the GCFG. |
| |
| If not already present, the function will make one, while adding an edge |
| from the PREV_BB to it. */ |
| |
| static gbbS * |
| find_or_make_bb (const symbolS *func, gcfgS *gcfg, ginsnS *ginsn, gbbS *prev_bb, |
| int *errp) |
| { |
| gbbS *found_bb = NULL; |
| |
| found_bb = find_bb (gcfg, ginsn); |
| if (!found_bb) |
| found_bb = add_bb_at_ginsn (func, gcfg, ginsn, prev_bb, errp); |
| |
| gas_assert (found_bb); |
| gas_assert (found_bb->first_ginsn == ginsn); |
| |
| return found_bb; |
| } |
| |
| /* Add basic block(s) for all reachable, unvisited ginsns, starting from GINSN, |
| to the given GCFG. Also add an edge from the PREV_BB to the root of the |
| newly added basic block(s). |
| |
| This is a recursive function which returns the root of the added basic |
| blocks. */ |
| |
| static gbbS * |
| add_bb_at_ginsn (const symbolS *func, gcfgS *gcfg, ginsnS *ginsn, gbbS *prev_bb, |
| int *errp) |
| { |
| gbbS *root_bb = NULL; |
| gbbS *current_bb = NULL; |
| ginsnS *target_ginsn = NULL; |
| const symbolS *taken_label; |
| |
| /* Create a new bb. N.B. The caller must ensure bb with this ginsn does not |
| already exist. */ |
| gas_assert (!find_bb (gcfg, ginsn)); |
| root_bb = XCNEW (gbbS); |
| cfg_add_bb (gcfg, root_bb); |
| root_bb->first_ginsn = ginsn; |
| |
| current_bb = root_bb; |
| |
| while (ginsn) |
| { |
| /* Skip these as they may be right after a GINSN_TYPE_RETURN. |
| For GINSN_TYPE_RETURN, we have already considered that as |
| end of bb, and a logical exit from function. */ |
| if (GINSN_F_FUNC_END_P (ginsn)) |
| { |
| /* Dont mark them visited yet though, leaving the option of these |
| being visited via other control flows as applicable. */ |
| ginsn = ginsn->next; |
| continue; |
| } |
| |
| if (ginsn->visited) |
| { |
| /* If the ginsn has been visited earlier, the bb must exist by now |
| in the cfg. */ |
| prev_bb = current_bb; |
| current_bb = find_bb (gcfg, ginsn); |
| gas_assert (current_bb); |
| /* Add edge from the prev_bb. */ |
| if (prev_bb) |
| bb_add_edge (prev_bb, current_bb); |
| break; |
| } |
| else if (current_bb && current_bb->first_ginsn != ginsn |
| && GINSN_F_USER_LABEL_P (ginsn)) |
| { |
| /* Create new bb starting at ginsn for (user-defined) label. This is |
| likely going to be a destination of a some control flow. */ |
| prev_bb = current_bb; |
| current_bb = find_or_make_bb (func, gcfg, ginsn, prev_bb, errp); |
| bb_add_edge (prev_bb, current_bb); |
| break; |
| } |
| |
| if (current_bb == NULL) |
| { |
| current_bb = XCNEW (gbbS); |
| cfg_add_bb (gcfg, current_bb); |
| current_bb->first_ginsn = ginsn; |
| /* Add edge for the Not Taken, or Fall-through path. */ |
| if (prev_bb) |
| bb_add_edge (prev_bb, current_bb); |
| } |
| |
| ginsn->visited = true; |
| current_bb->num_ginsns++; |
| current_bb->last_ginsn = ginsn; |
| |
| /* Note that BB is _not_ split on ginsn of type GINSN_TYPE_CALL. */ |
| if (ginsn->type == GINSN_TYPE_JUMP |
| || ginsn->type == GINSN_TYPE_JUMP_COND |
| || ginsn->type == GINSN_TYPE_RETURN) |
| { |
| /* Indirect jumps must not be seen here. The caller must have |
| already checked for that. */ |
| gas_assert (!ginsn_indirect_jump_p (ginsn)); |
| |
| /* Handle direct jumps. For unconditional direct jumps, where the |
| target is not local to the function, treat them later as similar |
| to an exit from function (in the else block). */ |
| if (ginsn->type == GINSN_TYPE_JUMP_COND |
| || ginsn_direct_local_jump_p (ginsn)) |
| { |
| gas_assert (ginsn->src[0].type == GINSN_SRC_SYMBOL); |
| taken_label = ginsn->src[0].sym; |
| gas_assert (taken_label); |
| |
| /* Preserve the prev_bb to be the source bb as we are going to |
| follow the taken path of the conditional branch soon. */ |
| prev_bb = current_bb; |
| |
| /* Follow the target on the taken path. */ |
| target_ginsn = label_ginsn_map_find (taken_label); |
| /* Add the bb for the target of the taken branch. */ |
| if (target_ginsn) |
| { |
| current_bb = find_or_make_bb (func, gcfg, target_ginsn, |
| prev_bb, errp); |
| gas_assert (prev_bb); |
| bb_add_edge (prev_bb, current_bb); |
| current_bb = NULL; |
| } |
| else |
| { |
| *errp = GCFG_JLABEL_NOT_PRESENT; |
| as_warn_where (ginsn->file, ginsn->line, |
| _("missing label '%s' in func '%s' may result in imprecise cfg"), |
| S_GET_NAME (taken_label), S_GET_NAME (func)); |
| } |
| |
| if (ginsn->type == GINSN_TYPE_JUMP_COND) |
| { |
| /* Add the bb for the fall through path. */ |
| current_bb = find_or_make_bb (func, gcfg, ginsn->next, |
| prev_bb, errp); |
| gas_assert (prev_bb); |
| bb_add_edge (prev_bb, current_bb); |
| current_bb = NULL; |
| } |
| else |
| { |
| /* Unconditional jump. Current BB has been processed. */ |
| current_bb = NULL; |
| /* We'll come back to the ginsns following these (local) |
| unconditional jmps from another path if they are indeed |
| reachable code. */ |
| break; |
| } |
| } |
| else |
| { |
| gas_assert (ginsn->type == GINSN_TYPE_RETURN |
| || (ginsn->type == GINSN_TYPE_JUMP |
| && !ginsn_direct_local_jump_p (ginsn))); |
| /* Current BB has been processed. */ |
| current_bb = NULL; |
| |
| /* We'll come back to the ginsns following GINSN_TYPE_RETURN or |
| other (non-local) unconditional jmps from another path if they |
| are indeed reachable code. */ |
| break; |
| } |
| } |
| |
| ginsn = ginsn->next; |
| } |
| |
| return root_bb; |
| } |
| |
| static int |
| gbbs_compare (const void *v1, const void *v2) |
| { |
| const gbbS *bb1 = *(const gbbS **) v1; |
| const gbbS *bb2 = *(const gbbS **) v2; |
| |
| if (bb1->first_ginsn->id < bb2->first_ginsn->id) |
| return -1; |
| else if (bb1->first_ginsn->id > bb2->first_ginsn->id) |
| return 1; |
| else if (bb1->first_ginsn->id == bb2->first_ginsn->id) |
| return 0; |
| |
| return 0; |
| } |
| |
| /* Synthesize DWARF CFI and emit it. */ |
| |
| static int |
| ginsn_pass_execute_scfi (const symbolS *func, gcfgS *gcfg, gbbS *root_bb) |
| { |
| int err = scfi_synthesize_dw2cfi (func, gcfg, root_bb); |
| if (!err) |
| scfi_emit_dw2cfi (func); |
| |
| return err; |
| } |
| |
| /* Traverse the list of ginsns for the function and warn if some |
| ginsns are not visited. |
| |
| FIXME - this code assumes the caller has already performed a pass over |
| ginsns such that the reachable ginsns are already marked. Revisit this - we |
| should ideally make this pass self-sufficient. */ |
| |
| static int |
| ginsn_pass_warn_unreachable_code (const symbolS *func, |
| gcfgS *gcfg ATTRIBUTE_UNUSED, |
| ginsnS *root_ginsn) |
| { |
| ginsnS *ginsn; |
| bool unreach_p = false; |
| |
| if (!gcfg || !func || !root_ginsn) |
| return 0; |
| |
| ginsn = root_ginsn; |
| |
| while (ginsn) |
| { |
| /* Some ginsns of type GINSN_TYPE_SYMBOL remain unvisited. Some |
| may even be excluded from the CFG as they are not reachable, given |
| their function, e.g., user labels after return machine insn. */ |
| if (!ginsn->visited |
| && !GINSN_F_FUNC_END_P (ginsn) |
| && !GINSN_F_USER_LABEL_P (ginsn)) |
| { |
| unreach_p = true; |
| break; |
| } |
| ginsn = ginsn->next; |
| } |
| |
| if (unreach_p) |
| as_warn_where (ginsn->file, ginsn->line, |
| _("GINSN: found unreachable code in func '%s'"), |
| S_GET_NAME (func)); |
| |
| return unreach_p; |
| } |
| |
| void |
| gcfg_get_bbs_in_prog_order (gcfgS *gcfg, gbbS **prog_order_bbs) |
| { |
| uint64_t i = 0; |
| gbbS *gbb; |
| |
| if (!prog_order_bbs) |
| return; |
| |
| cfg_for_each_bb (gcfg, gbb) |
| { |
| gas_assert (i < gcfg->num_gbbs); |
| prog_order_bbs[i++] = gbb; |
| } |
| |
| qsort (prog_order_bbs, gcfg->num_gbbs, sizeof (gbbS *), gbbs_compare); |
| } |
| |
| /* Build the control flow graph for the ginsns of the function. |
| |
| It is important that the target adds an appropriate ginsn: |
| - GINSN_TYPE_JUMP, |
| - GINSN_TYPE_JUMP_COND, |
| - GINSN_TYPE_CALL, |
| - GINSN_TYPE_RET |
| at the associated points in the function. The correctness of the CFG |
| depends on the accuracy of these 'change of flow instructions'. */ |
| |
| gcfgS * |
| gcfg_build (const symbolS *func, int *errp) |
| { |
| gcfgS *gcfg; |
| ginsnS *first_ginsn; |
| |
| gcfg = XCNEW (gcfgS); |
| first_ginsn = frchain_now->frch_ginsn_data->gins_rootP; |
| add_bb_at_ginsn (func, gcfg, first_ginsn, NULL /* prev_bb. */, errp); |
| |
| return gcfg; |
| } |
| |
| void |
| gcfg_cleanup (gcfgS **gcfgp) |
| { |
| gcfgS *cfg; |
| gbbS *bb, *next_bb; |
| gedgeS *edge, *next_edge; |
| |
| if (!gcfgp || !*gcfgp) |
| return; |
| |
| cfg = *gcfgp; |
| bb = gcfg_get_rootbb (cfg); |
| |
| while (bb) |
| { |
| next_bb = bb->next; |
| |
| /* Cleanup all the edges. */ |
| edge = bb->out_gedges; |
| while (edge) |
| { |
| next_edge = edge->next; |
| free (edge); |
| edge = next_edge; |
| } |
| |
| gbb_cleanup (&bb); |
| bb = next_bb; |
| } |
| |
| free (cfg); |
| *gcfgp = NULL; |
| } |
| |
| gbbS * |
| gcfg_get_rootbb (gcfgS *gcfg) |
| { |
| gbbS *rootbb = NULL; |
| |
| if (!gcfg || !gcfg->num_gbbs) |
| return NULL; |
| |
| rootbb = gcfg->root_bb; |
| |
| return rootbb; |
| } |
| |
| void |
| gcfg_print (const gcfgS *gcfg, FILE *outfile) |
| { |
| gbbS *gbb = NULL; |
| gedgeS *gedge = NULL; |
| uint64_t total_ginsns = 0; |
| |
| cfg_for_each_bb(gcfg, gbb) |
| { |
| fprintf (outfile, "BB [%" PRIu64 "] with num insns: %" PRIu64, |
| gbb->id, gbb->num_ginsns); |
| fprintf (outfile, " [insns: %u to %u]\n", |
| gbb->first_ginsn->line, gbb->last_ginsn->line); |
| total_ginsns += gbb->num_ginsns; |
| bb_for_each_edge(gbb, gedge) |
| fprintf (outfile, " outgoing edge to %" PRIu64 "\n", |
| gedge->dst_bb->id); |
| } |
| fprintf (outfile, "\nTotal ginsns in all GBBs = %" PRIu64 "\n", |
| total_ginsns); |
| } |
| |
| void |
| frch_ginsn_data_init (const symbolS *func, symbolS *start_addr, |
| enum ginsn_gen_mode gmode) |
| { |
| /* FIXME - error out if prev object is not free'd ? */ |
| frchain_now->frch_ginsn_data = XCNEW (struct frch_ginsn_data); |
| |
| frchain_now->frch_ginsn_data->mode = gmode; |
| /* Annotate with the current function symbol. */ |
| frchain_now->frch_ginsn_data->func = func; |
| /* Create a new start address symbol now. */ |
| frchain_now->frch_ginsn_data->start_addr = start_addr; |
| /* Assume the set of ginsn are apt for CFG creation, by default. */ |
| frchain_now->frch_ginsn_data->gcfg_apt_p = true; |
| |
| frchain_now->frch_ginsn_data->label_ginsn_map = str_htab_create (); |
| } |
| |
| void |
| frch_ginsn_data_cleanup (void) |
| { |
| ginsnS *ginsn = NULL; |
| ginsnS *next_ginsn = NULL; |
| |
| ginsn = frchain_now->frch_ginsn_data->gins_rootP; |
| while (ginsn) |
| { |
| next_ginsn = ginsn->next; |
| ginsn_cleanup (&ginsn); |
| ginsn = next_ginsn; |
| } |
| |
| if (frchain_now->frch_ginsn_data->label_ginsn_map) |
| htab_delete (frchain_now->frch_ginsn_data->label_ginsn_map); |
| |
| free (frchain_now->frch_ginsn_data); |
| frchain_now->frch_ginsn_data = NULL; |
| } |
| |
| /* Append GINSN to the list of ginsns for the current function being |
| assembled. */ |
| |
| int |
| frch_ginsn_data_append (ginsnS *ginsn) |
| { |
| ginsnS *last = NULL; |
| ginsnS *temp = NULL; |
| uint64_t id = 0; |
| |
| if (!ginsn) |
| return 1; |
| |
| if (frchain_now->frch_ginsn_data->gins_lastP) |
| id = frchain_now->frch_ginsn_data->gins_lastP->id; |
| |
| /* Do the necessary preprocessing on the set of input GINSNs: |
| - Update each ginsn with its ID. |
| While you iterate, also keep gcfg_apt_p updated by checking whether any |
| ginsn is inappropriate for GCFG creation. */ |
| temp = ginsn; |
| while (temp) |
| { |
| temp->id = ++id; |
| |
| if (ginsn_indirect_jump_p (temp)) |
| frchain_now->frch_ginsn_data->gcfg_apt_p = false; |
| |
| if (listing & LISTING_GINSN_SCFI) |
| listing_newline (ginsn_print (temp)); |
| |
| /* The input GINSN may be a linked list of multiple ginsns chained |
| together. Find the last ginsn in the input chain of ginsns. */ |
| last = temp; |
| |
| temp = temp->next; |
| } |
| |
| /* Link in the ginsn to the tail. */ |
| if (!frchain_now->frch_ginsn_data->gins_rootP) |
| frchain_now->frch_ginsn_data->gins_rootP = ginsn; |
| else |
| ginsn_link_next (frchain_now->frch_ginsn_data->gins_lastP, ginsn); |
| |
| frchain_now->frch_ginsn_data->gins_lastP = last; |
| |
| return 0; |
| } |
| |
| enum ginsn_gen_mode |
| frch_ginsn_gen_mode (void) |
| { |
| enum ginsn_gen_mode gmode = GINSN_GEN_NONE; |
| |
| if (frchain_now->frch_ginsn_data) |
| gmode = frchain_now->frch_ginsn_data->mode; |
| |
| return gmode; |
| } |
| |
| int |
| ginsn_data_begin (const symbolS *func) |
| { |
| ginsnS *ginsn; |
| |
| /* The previous block of asm must have been processed by now. */ |
| if (frchain_now->frch_ginsn_data) |
| as_bad (_("GINSN process for prev func not done")); |
| |
| /* FIXME - hard code the mode to GINSN_GEN_SCFI. |
| This can be changed later when other passes on ginsns are formalised. */ |
| frch_ginsn_data_init (func, symbol_temp_new_now (), GINSN_GEN_SCFI); |
| |
| /* Create and insert ginsn with function begin marker. */ |
| ginsn = ginsn_new_symbol_func_begin (func); |
| frch_ginsn_data_append (ginsn); |
| |
| return 0; |
| } |
| |
| int |
| ginsn_data_end (const symbolS *label) |
| { |
| ginsnS *ginsn; |
| gbbS *root_bb; |
| gcfgS *gcfg = NULL; |
| const symbolS *func; |
| int err = 0; |
| |
| if (!frchain_now->frch_ginsn_data) |
| return err; |
| |
| /* Insert Function end marker. */ |
| ginsn = ginsn_new_symbol_func_end (label); |
| frch_ginsn_data_append (ginsn); |
| |
| func = frchain_now->frch_ginsn_data->func; |
| |
| /* Build the cfg of ginsn(s) of the function. */ |
| if (!frchain_now->frch_ginsn_data->gcfg_apt_p) |
| { |
| as_bad (_("untraceable control flow for func '%s'"), |
| S_GET_NAME (func)); |
| goto end; |
| } |
| |
| gcfg = gcfg_build (func, &err); |
| |
| root_bb = gcfg_get_rootbb (gcfg); |
| if (!root_bb) |
| { |
| as_bad (_("Bad cfg of ginsn of func '%s'"), S_GET_NAME (func)); |
| goto end; |
| } |
| |
| /* Execute the desired passes on ginsns. */ |
| err = ginsn_pass_execute_scfi (func, gcfg, root_bb); |
| if (err) |
| goto end; |
| |
| /* Other passes, e.g., warn for unreachable code can be enabled too. */ |
| ginsn = frchain_now->frch_ginsn_data->gins_rootP; |
| err = ginsn_pass_warn_unreachable_code (func, gcfg, ginsn); |
| |
| end: |
| if (gcfg) |
| gcfg_cleanup (&gcfg); |
| frch_ginsn_data_cleanup (); |
| |
| return err; |
| } |
| |
| /* Add GINSN_TYPE_SYMBOL type ginsn for user-defined labels. These may be |
| branch targets, and hence are necessary for control flow graph. */ |
| |
| void |
| ginsn_frob_label (const symbolS *label) |
| { |
| ginsnS *label_ginsn; |
| const char *file; |
| unsigned int line; |
| |
| if (frchain_now->frch_ginsn_data) |
| { |
| /* PS: Note how we keep the actual LABEL symbol as ginsn->sym. |
| Take care to avoid inadvertent updates or cleanups of symbols. */ |
| label_ginsn = ginsn_new_symbol_user_label (label); |
| /* Keep the location updated. */ |
| file = as_where (&line); |
| ginsn_set_file_line (label_ginsn, file, line); |
| |
| frch_ginsn_data_append (label_ginsn); |
| |
| label_ginsn_map_insert (label, label_ginsn); |
| } |
| } |
| |
| const symbolS * |
| ginsn_data_func_symbol (void) |
| { |
| const symbolS *func = NULL; |
| |
| if (frchain_now->frch_ginsn_data) |
| func = frchain_now->frch_ginsn_data->func; |
| |
| return func; |
| } |
| |
| #else |
| |
| int |
| ginsn_data_begin (const symbolS *func ATTRIBUTE_UNUSED) |
| { |
| as_bad (_("ginsn unsupported for target")); |
| return 1; |
| } |
| |
| int |
| ginsn_data_end (const symbolS *label ATTRIBUTE_UNUSED) |
| { |
| as_bad (_("ginsn unsupported for target")); |
| return 1; |
| } |
| |
| void |
| ginsn_frob_label (const symbolS *sym ATTRIBUTE_UNUSED) |
| { |
| return; |
| } |
| |
| const symbolS * |
| ginsn_data_func_symbol (void) |
| { |
| return NULL; |
| } |
| |
| #endif /* TARGET_USE_GINSN. */ |