/* 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. */
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;
/* 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. */
/* 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 tail 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 (ginsn->visited)
{
cfg_for_each_bb (gcfg, gbb)
{
if (gbb->first_ginsn == ginsn)
{
found_bb = gbb;
break;
}
}
/* Must be found if ginsn is visited. */
gas_assert (found_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))
{
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 && GINSN_F_USER_LABEL_P (ginsn))
{
/* Create new bb starting at this label ginsn. */
prev_bb = current_bb;
find_or_make_bb (func, gcfg, ginsn, prev_bb, errp);
break;
}
if (current_bb == NULL)
{
/* Create a new bb. */
current_bb = XCNEW (gbbS);
cfg_add_bb (gcfg, current_bb);
/* Add edge for the Not Taken, or Fall-through path. */
if (prev_bb)
bb_add_edge (prev_bb, current_bb);
}
if (current_bb->first_ginsn == NULL)
current_bb->first_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 or direct jumps to symbols non-local to the
function must not be seen here. The caller must have already
checked for that. */
gas_assert (!ginsn_indirect_jump_p (ginsn));
if (ginsn->type == GINSN_TYPE_JUMP)
gas_assert (ginsn_direct_local_jump_p (ginsn));
/* Direct Jumps. May include conditional or unconditional change of
flow. What is important for CFG creation is that the target be
local to function. */
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 dominator 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)
find_or_make_bb (func, gcfg, target_ginsn, prev_bb, errp);
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));
}
/* Add the bb for the fall through path. */
find_or_make_bb (func, gcfg, ginsn->next, prev_bb, errp);
}
else if (ginsn->type == GINSN_TYPE_RETURN)
{
/* We'll come back to the ginsns following GINSN_TYPE_RETURN
from another path if they are indeed reachable code. */
break;
}
/* Current BB has been processed. */
current_bb = NULL;
}
ginsn = ginsn->next;
}
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. */
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));
/* 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'. */
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;
/* 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 (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);
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);
/* 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. */
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);