/* 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. */