/* read-rtl-function.cc - Reader for RTL function dumps
Copyright (C) 2016-2024 Free Software Foundation, Inc.
This file is part of GCC.
GCC 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.
GCC 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 GCC; see the file COPYING3. If not see
. */
#include "config.h"
#include "system.h"
#include "coretypes.h"
#include "target.h"
#include "tree.h"
#include "diagnostic.h"
#include "read-md.h"
#include "rtl.h"
#include "cfghooks.h"
#include "stringpool.h"
#include "function.h"
#include "tree-cfg.h"
#include "cfg.h"
#include "basic-block.h"
#include "cfgrtl.h"
#include "memmodel.h"
#include "emit-rtl.h"
#include "cgraph.h"
#include "tree-pass.h"
#include "toplev.h"
#include "varasm.h"
#include "read-rtl-function.h"
#include "selftest.h"
#include "selftest-rtl.h"
#include "regs.h"
#include "function-abi.h"
/* Forward decls. */
class function_reader;
class fixup;
/* Edges are recorded when parsing the "insn-chain" directive,
and created at the end when all the blocks ought to exist.
This struct records an "edge-from" or "edge-to" directive seen
at LOC, which will be turned into an actual CFG edge once
the "insn-chain" is fully parsed. */
class deferred_edge
{
public:
deferred_edge (file_location loc, int src_bb_idx, int dest_bb_idx, int flags)
: m_loc (loc), m_src_bb_idx (src_bb_idx), m_dest_bb_idx (dest_bb_idx),
m_flags (flags)
{}
file_location m_loc;
int m_src_bb_idx;
int m_dest_bb_idx;
int m_flags;
};
/* Subclass of rtx_reader for reading function dumps. */
class function_reader : public rtx_reader
{
public:
function_reader ();
~function_reader ();
/* Overridden vfuncs of class md_reader. */
void handle_unknown_directive (file_location, const char *) final override;
/* Overridden vfuncs of class rtx_reader. */
rtx read_rtx_operand (rtx x, int idx) final override;
void handle_any_trailing_information (rtx x) final override;
rtx postprocess (rtx) final override;
const char *finalize_string (char *stringbuf) final override;
rtx_insn **get_insn_by_uid (int uid);
tree parse_mem_expr (const char *desc);
private:
void parse_function ();
void create_function ();
void parse_param ();
void parse_insn_chain ();
void parse_block ();
int parse_bb_idx ();
void parse_edge (basic_block block, bool from);
rtx_insn *parse_insn (file_location loc, const char *name);
void parse_cfg (file_location loc);
void parse_crtl (file_location loc);
void create_edges ();
int parse_enum_value (int num_values, const char *const *strings);
void read_rtx_operand_u (rtx x, int idx);
void read_rtx_operand_i_or_n (rtx x, int idx, char format_char);
rtx read_rtx_operand_r (rtx x);
rtx extra_parsing_for_operand_code_0 (rtx x, int idx);
void add_fixup_insn_uid (file_location loc, rtx insn, int operand_idx,
int insn_uid);
void add_fixup_note_insn_basic_block (file_location loc, rtx insn,
int operand_idx, int bb_idx);
void add_fixup_source_location (file_location loc, rtx_insn *insn,
const char *filename, int lineno, int colno);
void add_fixup_expr (file_location loc, rtx x,
const char *desc);
rtx consolidate_singletons (rtx x);
rtx parse_rtx ();
void maybe_read_location (rtx_insn *insn);
void handle_insn_uids ();
void apply_fixups ();
private:
struct uid_hash : int_hash {};
hash_map m_insns_by_uid;
auto_vec m_fixups;
rtx_insn *m_first_insn;
auto_vec m_fake_scope;
char *m_name;
bool m_have_crtl_directive;
basic_block m_bb_to_insert_after;
auto_vec m_deferred_edges;
int m_highest_bb_idx;
};
/* Abstract base class for recording post-processing steps that must be
done after reading a .rtl file. */
class fixup
{
public:
/* Constructor for a fixup at LOC affecting X. */
fixup (file_location loc, rtx x)
: m_loc (loc), m_rtx (x)
{}
virtual ~fixup () {}
virtual void apply (function_reader *reader) const = 0;
protected:
file_location m_loc;
rtx m_rtx;
};
/* An abstract subclass of fixup for post-processing steps that
act on a specific operand of a specific instruction. */
class operand_fixup : public fixup
{
public:
/* Constructor for a fixup at LOC affecting INSN's operand
with index OPERAND_IDX. */
operand_fixup (file_location loc, rtx insn, int operand_idx)
: fixup (loc, insn), m_operand_idx (operand_idx)
{}
protected:
int m_operand_idx;
};
/* A concrete subclass of operand_fixup: fixup an rtx_insn *
field based on an integer UID. */
class fixup_insn_uid : public operand_fixup
{
public:
/* Constructor for a fixup at LOC affecting INSN's operand
with index OPERAND_IDX. Record INSN_UID as the uid. */
fixup_insn_uid (file_location loc, rtx insn, int operand_idx, int insn_uid)
: operand_fixup (loc, insn, operand_idx),
m_insn_uid (insn_uid)
{}
void apply (function_reader *reader) const final override;
private:
int m_insn_uid;
};
/* A concrete subclass of operand_fixup: fix up a
NOTE_INSN_BASIC_BLOCK based on an integer block ID. */
class fixup_note_insn_basic_block : public operand_fixup
{
public:
fixup_note_insn_basic_block (file_location loc, rtx insn, int operand_idx,
int bb_idx)
: operand_fixup (loc, insn, operand_idx),
m_bb_idx (bb_idx)
{}
void apply (function_reader *reader) const final override;
private:
int m_bb_idx;
};
/* A concrete subclass of fixup (not operand_fixup): fix up
the expr of an rtx (REG or MEM) based on a textual dump. */
class fixup_expr : public fixup
{
public:
fixup_expr (file_location loc, rtx x, const char *desc)
: fixup (loc, x),
m_desc (xstrdup (desc))
{}
~fixup_expr () { free (m_desc); }
void apply (function_reader *reader) const final override;
private:
char *m_desc;
};
/* Return a textual description of the operand of INSN with
index OPERAND_IDX. */
static const char *
get_operand_name (rtx insn, int operand_idx)
{
gcc_assert (is_a (insn));
switch (operand_idx)
{
case 0:
return "PREV_INSN";
case 1:
return "NEXT_INSN";
default:
return NULL;
}
}
/* Fixup an rtx_insn * field based on an integer UID, as read by READER. */
void
fixup_insn_uid::apply (function_reader *reader) const
{
rtx_insn **insn_from_uid = reader->get_insn_by_uid (m_insn_uid);
if (insn_from_uid)
XEXP (m_rtx, m_operand_idx) = *insn_from_uid;
else
{
const char *op_name = get_operand_name (m_rtx, m_operand_idx);
if (op_name)
error_at (m_loc,
"insn with UID %i not found for operand %i (`%s') of insn %i",
m_insn_uid, m_operand_idx, op_name, INSN_UID (m_rtx));
else
error_at (m_loc,
"insn with UID %i not found for operand %i of insn %i",
m_insn_uid, m_operand_idx, INSN_UID (m_rtx));
}
}
/* Fix up a NOTE_INSN_BASIC_BLOCK based on an integer block ID. */
void
fixup_note_insn_basic_block::apply (function_reader *) const
{
basic_block bb = BASIC_BLOCK_FOR_FN (cfun, m_bb_idx);
gcc_assert (bb);
NOTE_BASIC_BLOCK (m_rtx) = bb;
}
/* Fix up the expr of an rtx (REG or MEM) based on a textual dump
read by READER. */
void
fixup_expr::apply (function_reader *reader) const
{
tree expr = reader->parse_mem_expr (m_desc);
switch (GET_CODE (m_rtx))
{
case REG:
set_reg_attrs_for_decl_rtl (expr, m_rtx);
break;
case MEM:
set_mem_expr (m_rtx, expr);
break;
default:
gcc_unreachable ();
}
}
/* Strip trailing whitespace from DESC. */
static void
strip_trailing_whitespace (char *desc)
{
char *terminator = desc + strlen (desc);
while (desc < terminator)
{
terminator--;
if (ISSPACE (*terminator))
*terminator = '\0';
else
break;
}
}
/* Return the numeric value n for GET_NOTE_INSN_NAME (n) for STRING,
or fail if STRING isn't recognized. */
static int
parse_note_insn_name (const char *string)
{
for (int i = 0; i < NOTE_INSN_MAX; i++)
if (strcmp (string, GET_NOTE_INSN_NAME (i)) == 0)
return i;
fatal_with_file_and_line ("unrecognized NOTE_INSN name: `%s'", string);
}
/* Return the register number for NAME, or return -1 if it isn't
recognized. */
static int
lookup_reg_by_dump_name (const char *name)
{
for (int i = 0; i < FIRST_PSEUDO_REGISTER; i++)
if (reg_names[i][0]
&& ! strcmp (name, reg_names[i]))
return i;
/* Also lookup virtuals. */
if (!strcmp (name, "virtual-incoming-args"))
return VIRTUAL_INCOMING_ARGS_REGNUM;
if (!strcmp (name, "virtual-stack-vars"))
return VIRTUAL_STACK_VARS_REGNUM;
if (!strcmp (name, "virtual-stack-dynamic"))
return VIRTUAL_STACK_DYNAMIC_REGNUM;
if (!strcmp (name, "virtual-outgoing-args"))
return VIRTUAL_OUTGOING_ARGS_REGNUM;
if (!strcmp (name, "virtual-cfa"))
return VIRTUAL_CFA_REGNUM;
if (!strcmp (name, "virtual-preferred-stack-boundary"))
return VIRTUAL_PREFERRED_STACK_BOUNDARY_REGNUM;
/* TODO: handle "virtual-reg-%d". */
/* In compact mode, pseudos are printed with '< and '>' wrapping the regno,
offseting it by (LAST_VIRTUAL_REGISTER + 1), so that the
first non-virtual pseudo is dumped as "<0>". */
if (name[0] == '<' && name[strlen (name) - 1] == '>')
{
int dump_num = atoi (name + 1);
return dump_num + LAST_VIRTUAL_REGISTER + 1;
}
/* Not found. */
return -1;
}
/* class function_reader : public rtx_reader */
/* function_reader's constructor. */
function_reader::function_reader ()
: rtx_reader (true),
m_first_insn (NULL),
m_name (NULL),
m_have_crtl_directive (false),
m_bb_to_insert_after (NULL),
m_highest_bb_idx (EXIT_BLOCK)
{
}
/* function_reader's destructor. */
function_reader::~function_reader ()
{
int i;
fixup *f;
FOR_EACH_VEC_ELT (m_fixups, i, f)
delete f;
free (m_name);
}
/* Implementation of rtx_reader::handle_unknown_directive,
for parsing the remainder of a directive with name NAME
seen at START_LOC.
Require a top-level "function" directive, as emitted by
print_rtx_function, and parse it. */
void
function_reader::handle_unknown_directive (file_location start_loc,
const char *name)
{
if (strcmp (name, "function"))
fatal_at (start_loc, "expected 'function'");
if (flag_lto)
error ("%<__RTL%> function cannot be compiled with %<-flto%>");
parse_function ();
}
/* Parse the output of print_rtx_function (or hand-written data in the
same format), having already parsed the "(function" heading, and
finishing immediately before the final ")".
The "param" and "crtl" clauses are optional. */
void
function_reader::parse_function ()
{
m_name = xstrdup (read_string (0));
create_function ();
while (1)
{
int c = read_skip_spaces ();
if (c == ')')
{
unread_char (c);
break;
}
unread_char (c);
require_char ('(');
file_location loc = get_current_location ();
struct md_name directive;
read_name (&directive);
if (strcmp (directive.string, "param") == 0)
parse_param ();
else if (strcmp (directive.string, "insn-chain") == 0)
parse_insn_chain ();
else if (strcmp (directive.string, "crtl") == 0)
parse_crtl (loc);
else
fatal_with_file_and_line ("unrecognized directive: %s",
directive.string);
}
handle_insn_uids ();
apply_fixups ();
/* Rebuild the JUMP_LABEL field of any JUMP_INSNs in the chain, and the
LABEL_NUSES of any CODE_LABELs.
This has to happen after apply_fixups, since only after then do
LABEL_REFs have their label_ref_label set up. */
rebuild_jump_labels (get_insns ());
crtl->init_stack_alignment ();
}
/* Set up state for the function *before* fixups are applied.
Create "cfun" and a decl for the function.
By default, every function decl is hardcoded as
int test_1 (int i, int j, int k);
Set up various other state:
- the cfg and basic blocks (edges are created later, *after* fixups
are applied).
- add the function to the callgraph. */
void
function_reader::create_function ()
{
/* We start in cfgrtl mode, rather than cfglayout mode. */
rtl_register_cfg_hooks ();
/* When run from selftests or "rtl1", cfun is NULL.
When run from "cc1" for a C function tagged with __RTL, cfun is the
tagged function. */
if (!cfun)
{
tree fn_name = get_identifier (m_name ? m_name : "test_1");
tree int_type = integer_type_node;
tree return_type = int_type;
tree arg_types[3] = {int_type, int_type, int_type};
tree fn_type = build_function_type_array (return_type, 3, arg_types);
tree fndecl = build_decl (UNKNOWN_LOCATION, FUNCTION_DECL, fn_name, fn_type);
tree resdecl = build_decl (UNKNOWN_LOCATION, RESULT_DECL, NULL_TREE,
return_type);
DECL_ARTIFICIAL (resdecl) = 1;
DECL_IGNORED_P (resdecl) = 1;
DECL_RESULT (fndecl) = resdecl;
allocate_struct_function (fndecl, false);
/* This sets cfun. */
current_function_decl = fndecl;
}
gcc_assert (cfun);
gcc_assert (current_function_decl);
tree fndecl = current_function_decl;
/* Mark this function as being specified as __RTL. */
cfun->curr_properties |= PROP_rtl;
/* cc1 normally inits DECL_INITIAL (fndecl) to be error_mark_node.
Create a dummy block for it. */
DECL_INITIAL (fndecl) = make_node (BLOCK);
cfun->curr_properties = (PROP_cfg | PROP_rtl);
/* Do we need this to force cgraphunit.cc to output the function? */
DECL_EXTERNAL (fndecl) = 0;
DECL_PRESERVE_P (fndecl) = 1;
/* Add to cgraph. */
cgraph_node::finalize_function (fndecl, false);
/* Create bare-bones cfg. This creates the entry and exit blocks. */
init_empty_tree_cfg_for_function (cfun);
ENTRY_BLOCK_PTR_FOR_FN (cfun)->flags |= BB_RTL;
EXIT_BLOCK_PTR_FOR_FN (cfun)->flags |= BB_RTL;
init_rtl_bb_info (ENTRY_BLOCK_PTR_FOR_FN (cfun));
init_rtl_bb_info (EXIT_BLOCK_PTR_FOR_FN (cfun));
m_bb_to_insert_after = ENTRY_BLOCK_PTR_FOR_FN (cfun);
}
/* Look within the params of FNDECL for a param named NAME.
Return NULL_TREE if one isn't found. */
static tree
find_param_by_name (tree fndecl, const char *name)
{
for (tree arg = DECL_ARGUMENTS (fndecl); arg; arg = TREE_CHAIN (arg))
if (id_equal (DECL_NAME (arg), name))
return arg;
return NULL_TREE;
}
/* Parse the content of a "param" directive, having already parsed the
"(param". Consume the trailing ')'. */
void
function_reader::parse_param ()
{
require_char_ws ('"');
file_location loc = get_current_location ();
char *name = read_quoted_string ();
/* Lookup param by name. */
tree t_param = find_param_by_name (cfun->decl, name);
if (!t_param)
fatal_at (loc, "param not found: %s", name);
/* Parse DECL_RTL. */
require_char_ws ('(');
require_word_ws ("DECL_RTL");
DECL_WRTL_CHECK (t_param)->decl_with_rtl.rtl = parse_rtx ();
require_char_ws (')');
/* Parse DECL_RTL_INCOMING. */
require_char_ws ('(');
require_word_ws ("DECL_RTL_INCOMING");
DECL_INCOMING_RTL (t_param) = parse_rtx ();
require_char_ws (')');
require_char_ws (')');
}
/* Parse zero or more child insn elements within an
"insn-chain" element. Consume the trailing ')'. */
void
function_reader::parse_insn_chain ()
{
while (1)
{
int c = read_skip_spaces ();
file_location loc = get_current_location ();
if (c == ')')
break;
else if (c == '(')
{
struct md_name directive;
read_name (&directive);
if (strcmp (directive.string, "block") == 0)
parse_block ();
else
parse_insn (loc, directive.string);
}
else
fatal_at (loc, "expected '(' or ')'");
}
create_edges ();
}
/* Parse zero or more child directives (edges and insns) within a
"block" directive, having already parsed the "(block " heading.
Consume the trailing ')'. */
void
function_reader::parse_block ()
{
/* Parse the index value from the dump. This will be an integer;
we don't support "entry" or "exit" here (unlike for edges). */
struct md_name name;
read_name (&name);
int bb_idx = atoi (name.string);
/* The term "index" has two meanings for basic blocks in a CFG:
(a) the "index" field within struct basic_block_def.
(b) the index of a basic_block within the cfg's x_basic_block_info
vector, as accessed via BASIC_BLOCK_FOR_FN.
These can get out-of-sync when basic blocks are optimized away.
They get back in sync by "compact_blocks".
We reconstruct cfun->cfg->x_basic_block_info->address () pointed
vector elements with NULL values in it for any missing basic blocks,
so that (a) == (b) for all of the blocks we create. The
doubly-linked list of basic blocks (next_bb/prev_bb) skips over
these "holes". */
if (m_highest_bb_idx < bb_idx)
m_highest_bb_idx = bb_idx;
size_t new_size = m_highest_bb_idx + 1;
if (basic_block_info_for_fn (cfun)->length () < new_size)
vec_safe_grow_cleared (basic_block_info_for_fn (cfun), new_size, true);
last_basic_block_for_fn (cfun) = new_size;
/* Create the basic block.
We can't call create_basic_block and use the regular RTL block-creation
hooks, since this creates NOTE_INSN_BASIC_BLOCK instances. We don't
want to do that; we want to use the notes we were provided with. */
basic_block bb = alloc_block ();
init_rtl_bb_info (bb);
bb->index = bb_idx;
bb->flags = BB_NEW | BB_RTL;
link_block (bb, m_bb_to_insert_after);
m_bb_to_insert_after = bb;
n_basic_blocks_for_fn (cfun)++;
SET_BASIC_BLOCK_FOR_FN (cfun, bb_idx, bb);
BB_SET_PARTITION (bb, BB_UNPARTITIONED);
/* Handle insns, edge-from and edge-to directives. */
while (1)
{
int c = read_skip_spaces ();
file_location loc = get_current_location ();
if (c == ')')
break;
else if (c == '(')
{
struct md_name directive;
read_name (&directive);
if (strcmp (directive.string, "edge-from") == 0)
parse_edge (bb, true);
else if (strcmp (directive.string, "edge-to") == 0)
parse_edge (bb, false);
else
{
rtx_insn *insn = parse_insn (loc, directive.string);
set_block_for_insn (insn, bb);
if (!BB_HEAD (bb))
BB_HEAD (bb) = insn;
BB_END (bb) = insn;
}
}
else
fatal_at (loc, "expected '(' or ')'");
}
}
/* Subroutine of function_reader::parse_edge.
Parse a basic block index, handling "entry" and "exit". */
int
function_reader::parse_bb_idx ()
{
struct md_name name;
read_name (&name);
if (strcmp (name.string, "entry") == 0)
return ENTRY_BLOCK;
if (strcmp (name.string, "exit") == 0)
return EXIT_BLOCK;
return atoi (name.string);
}
/* Subroutine of parse_edge_flags.
Parse TOK, a token such as "FALLTHRU", converting to the flag value.
Issue an error if the token is unrecognized. */
static int
parse_edge_flag_token (const char *tok)
{
#define DEF_EDGE_FLAG(NAME,IDX) \
do { \
if (strcmp (tok, #NAME) == 0) \
return EDGE_##NAME; \
} while (0);
#include "cfg-flags.def"
#undef DEF_EDGE_FLAG
error ("unrecognized edge flag: %qs", tok);
return 0;
}
/* Subroutine of function_reader::parse_edge.
Parse STR and convert to a flag value (or issue an error).
The parser uses strtok and hence modifiers STR in-place. */
static int
parse_edge_flags (char *str)
{
int result = 0;
char *tok = strtok (str, "| ");
while (tok)
{
result |= parse_edge_flag_token (tok);
tok = strtok (NULL, "| ");
}
return result;
}
/* Parse an "edge-from" or "edge-to" directive within the "block"
directive for BLOCK, having already parsed the "(edge" heading.
Consume the final ")". Record the edge within m_deferred_edges.
FROM is true for an "edge-from" directive, false for an "edge-to"
directive. */
void
function_reader::parse_edge (basic_block block, bool from)
{
gcc_assert (block);
int this_bb_idx = block->index;
file_location loc = get_current_location ();
int other_bb_idx = parse_bb_idx ();
/* "(edge-from 2)" means src = 2, dest = this_bb_idx, whereas
"(edge-to 3)" means src = this_bb_idx, dest = 3. */
int src_idx = from ? other_bb_idx : this_bb_idx;
int dest_idx = from ? this_bb_idx : other_bb_idx;
/* Optional "(flags)". */
int flags = 0;
int c = read_skip_spaces ();
if (c == '(')
{
require_word_ws ("flags");
require_char_ws ('"');
char *str = read_quoted_string ();
flags = parse_edge_flags (str);
require_char_ws (')');
}
else
unread_char (c);
require_char_ws (')');
/* This BB already exists, but the other BB might not yet.
For now, save the edges, and create them at the end of insn-chain
processing. */
/* For now, only process the (edge-from) to this BB, and (edge-to)
that go to the exit block.
FIXME: we don't yet verify that the edge-from and edge-to directives
are consistent. */
if (from || dest_idx == EXIT_BLOCK)
m_deferred_edges.safe_push (deferred_edge (loc, src_idx, dest_idx, flags));
}
/* Parse an rtx instruction, having parsed the opening and parenthesis, and
name NAME, seen at START_LOC, by calling read_rtx_code, calling
set_first_insn and set_last_insn as appropriate, and
adding the insn to the insn chain.
Consume the trailing ')'. */
rtx_insn *
function_reader::parse_insn (file_location start_loc, const char *name)
{
rtx x = read_rtx_code (name);
if (!x)
fatal_at (start_loc, "expected insn type; got '%s'", name);
rtx_insn *insn = dyn_cast (x);
if (!insn)
fatal_at (start_loc, "expected insn type; got '%s'", name);
/* Consume the trailing ')'. */
require_char_ws (')');
rtx_insn *last_insn = get_last_insn ();
/* Add "insn" to the insn chain. */
if (last_insn)
{
gcc_assert (NEXT_INSN (last_insn) == NULL);
SET_NEXT_INSN (last_insn) = insn;
}
SET_PREV_INSN (insn) = last_insn;
/* Add it to the sequence. */
set_last_insn (insn);
if (!m_first_insn)
{
m_first_insn = insn;
set_first_insn (insn);
}
if (rtx_code_label *label = dyn_cast (insn))
maybe_set_max_label_num (label);
return insn;
}
/* Postprocessing subroutine for parse_insn_chain: all the basic blocks
should have been created by now; create the edges that were seen. */
void
function_reader::create_edges ()
{
int i;
deferred_edge *de;
FOR_EACH_VEC_ELT (m_deferred_edges, i, de)
{
/* The BBs should already have been created by parse_block. */
basic_block src = BASIC_BLOCK_FOR_FN (cfun, de->m_src_bb_idx);
if (!src)
fatal_at (de->m_loc, "error: block index %i not found",
de->m_src_bb_idx);
basic_block dst = BASIC_BLOCK_FOR_FN (cfun, de->m_dest_bb_idx);
if (!dst)
fatal_at (de->m_loc, "error: block with index %i not found",
de->m_dest_bb_idx);
unchecked_make_edge (src, dst, de->m_flags);
}
}
/* Parse a "crtl" directive, having already parsed the "(crtl" heading
at location LOC.
Consume the final ")". */
void
function_reader::parse_crtl (file_location loc)
{
if (m_have_crtl_directive)
error_at (loc, "more than one 'crtl' directive");
m_have_crtl_directive = true;
/* return_rtx. */
require_char_ws ('(');
require_word_ws ("return_rtx");
crtl->return_rtx = parse_rtx ();
require_char_ws (')');
require_char_ws (')');
}
/* Parse operand IDX of X, returning X, or an equivalent rtx
expression (for consolidating singletons).
This is an overridden implementation of rtx_reader::read_rtx_operand for
function_reader, handling various extra data printed by print_rtx,
and sometimes calling the base class implementation. */
rtx
function_reader::read_rtx_operand (rtx x, int idx)
{
RTX_CODE code = GET_CODE (x);
const char *format_ptr = GET_RTX_FORMAT (code);
const char format_char = format_ptr[idx];
struct md_name name;
/* Override the regular parser for some format codes. */
switch (format_char)
{
case 'e':
if (idx == 7 && CALL_P (x))
{
m_in_call_function_usage = true;
rtx tem = rtx_reader::read_rtx_operand (x, idx);
m_in_call_function_usage = false;
return tem;
}
else
return rtx_reader::read_rtx_operand (x, idx);
break;
case 'u':
read_rtx_operand_u (x, idx);
/* Don't run regular parser for 'u'. */
return x;
case 'i':
case 'n':
read_rtx_operand_i_or_n (x, idx, format_char);
/* Don't run regular parser for these codes. */
return x;
case 'B':
gcc_assert (is_compact ());
/* Compact mode doesn't store BBs. */
/* Don't run regular parser. */
return x;
case 'r':
/* Don't run regular parser for 'r'. */
return read_rtx_operand_r (x);
default:
break;
}
/* Call base class implementation. */
x = rtx_reader::read_rtx_operand (x, idx);
/* Handle any additional parsing needed to handle what the dump
could contain. */
switch (format_char)
{
case '0':
x = extra_parsing_for_operand_code_0 (x, idx);
break;
case 'w':
if (!is_compact ())
{
/* Strip away the redundant hex dump of the value. */
require_char_ws ('[');
read_name (&name);
require_char_ws (']');
}
break;
default:
break;
}
return x;
}
/* Parse operand IDX of X, of code 'u', when reading function dumps.
The RTL file recorded the ID of an insn (or 0 for NULL); we
must store this as a pointer, but the insn might not have
been loaded yet. Store the ID away for now, via a fixup. */
void
function_reader::read_rtx_operand_u (rtx x, int idx)
{
/* In compact mode, the PREV/NEXT insn uids are not dumped, so skip
the "uu" when reading. */
if (is_compact () && GET_CODE (x) != LABEL_REF)
return;
struct md_name name;
file_location loc = read_name (&name);
int insn_id = atoi (name.string);
if (insn_id)
add_fixup_insn_uid (loc, x, idx, insn_id);
}
/* Read a name, looking for a match against a string found in array
STRINGS of size NUM_VALUES.
Return the index of the matched string, or emit an error. */
int
function_reader::parse_enum_value (int num_values, const char *const *strings)
{
struct md_name name;
read_name (&name);
for (int i = 0; i < num_values; i++)
{
if (strcmp (name.string, strings[i]) == 0)
return i;
}
error ("unrecognized enum value: %qs", name.string);
return 0;
}
/* Parse operand IDX of X, of code 'i' or 'n' (as specified by FORMAT_CHAR).
Special-cased handling of these, for reading function dumps. */
void
function_reader::read_rtx_operand_i_or_n (rtx x, int idx,
char format_char)
{
/* Handle some of the extra information that print_rtx
can write out for these cases. */
/* print_rtx only writes out operand 5 for notes
for NOTE_KIND values NOTE_INSN_DELETED_LABEL
and NOTE_INSN_DELETED_DEBUG_LABEL. */
if (idx == 5 && NOTE_P (x))
return;
if (idx == 4 && INSN_P (x))
{
maybe_read_location (as_a (x));
return;
}
/* INSN_CODEs aren't printed in compact mode, so don't attempt to
parse them. */
if (is_compact ()
&& INSN_P (x)
&& &INSN_CODE (x) == &XINT (x, idx))
{
INSN_CODE (x) = -1;
return;
}
/* Handle UNSPEC and UNSPEC_VOLATILE's operand 1. */
#if !defined(GENERATOR_FILE) && NUM_UNSPECV_VALUES > 0
if (idx == 1
&& GET_CODE (x) == UNSPEC_VOLATILE)
{
XINT (x, 1)
= parse_enum_value (NUM_UNSPECV_VALUES, unspecv_strings);
return;
}
#endif
#if !defined(GENERATOR_FILE) && NUM_UNSPEC_VALUES > 0
if (idx == 1
&& (GET_CODE (x) == UNSPEC
|| GET_CODE (x) == UNSPEC_VOLATILE))
{
XINT (x, 1)
= parse_enum_value (NUM_UNSPEC_VALUES, unspec_strings);
return;
}
#endif
struct md_name name;
read_name (&name);
int value;
if (format_char == 'n')
value = parse_note_insn_name (name.string);
else
value = atoi (name.string);
XINT (x, idx) = value;
}
/* Parse the 'r' operand of X, returning X, or an equivalent rtx
expression (for consolidating singletons).
Special-cased handling of code 'r' for reading function dumps. */
rtx
function_reader::read_rtx_operand_r (rtx x)
{
struct md_name name;
file_location loc = read_name (&name);
int regno = lookup_reg_by_dump_name (name.string);
if (regno == -1)
fatal_at (loc, "unrecognized register: '%s'", name.string);
set_regno_raw (x, regno, 1);
/* Consolidate singletons. */
x = consolidate_singletons (x);
ORIGINAL_REGNO (x) = regno;
/* Parse extra stuff at end of 'r'.
We may have zero, one, or two sections marked by square
brackets. */
int ch = read_skip_spaces ();
bool expect_original_regno = false;
if (ch == '[')
{
file_location loc = get_current_location ();
char *desc = read_until ("]", true);
strip_trailing_whitespace (desc);
const char *desc_start = desc;
/* If ORIGINAL_REGNO (rtx) != regno, we will have:
"orig:%i", ORIGINAL_REGNO (rtx).
Consume it, we don't set ORIGINAL_REGNO, since we can
get that from the 2nd copy later. */
if (startswith (desc, "orig:"))
{
expect_original_regno = true;
desc_start += 5;
/* Skip to any whitespace following the integer. */
const char *space = strchr (desc_start, ' ');
if (space)
desc_start = space + 1;
}
/* Any remaining text may be the REG_EXPR. Alternatively we have
no REG_ATTRS, and instead we have ORIGINAL_REGNO. */
if (ISDIGIT (*desc_start))
{
/* Assume we have ORIGINAL_REGNO. */
ORIGINAL_REGNO (x) = atoi (desc_start);
}
else
{
/* Assume we have REG_EXPR. */
add_fixup_expr (loc, x, desc_start);
}
free (desc);
}
else
unread_char (ch);
if (expect_original_regno)
{
require_char_ws ('[');
char *desc = read_until ("]", true);
ORIGINAL_REGNO (x) = atoi (desc);
free (desc);
}
return x;
}
/* Additional parsing for format code '0' in dumps, handling a variety
of special-cases in print_rtx, when parsing operand IDX of X.
Return X, or possibly a reallocated copy of X. */
rtx
function_reader::extra_parsing_for_operand_code_0 (rtx x, int idx)
{
RTX_CODE code = GET_CODE (x);
int c;
struct md_name name;
if (idx == 1 && code == SYMBOL_REF)
{
/* Possibly wrote " [flags %#x]", SYMBOL_REF_FLAGS (in_rtx). */
c = read_skip_spaces ();
if (c == '[')
{
file_location loc = read_name (&name);
if (strcmp (name.string, "flags"))
error_at (loc, "was expecting `%s'", "flags");
read_name (&name);
SYMBOL_REF_FLAGS (x) = strtol (name.string, NULL, 16);
/* The standard RTX_CODE_SIZE (SYMBOL_REF) used when allocating
x doesn't have space for the block_symbol information, so
we must reallocate it if this flag is set. */
if (SYMBOL_REF_HAS_BLOCK_INFO_P (x))
{
/* Emulate the allocation normally done by
varasm.cc:create_block_symbol. */
unsigned int size = RTX_HDR_SIZE + sizeof (struct block_symbol);
rtx new_x = (rtx) ggc_internal_alloc (size);
/* Copy data over from the smaller SYMBOL_REF. */
memcpy (new_x, x, RTX_CODE_SIZE (SYMBOL_REF));
x = new_x;
/* We can't reconstruct SYMBOL_REF_BLOCK; set it to NULL. */
SYMBOL_REF_BLOCK (x) = NULL;
/* Zero the offset. */
SYMBOL_REF_BLOCK_OFFSET (x) = 0;
}
require_char (']');
}
else
unread_char (c);
/* If X had a non-NULL SYMBOL_REF_DECL,
rtx_writer::print_rtx_operand_code_0 would have dumped it
using print_node_brief.
Skip the content for now. */
c = read_skip_spaces ();
if (c == '<')
{
while (1)
{
char ch = read_char ();
if (ch == '>')
break;
}
}
else
unread_char (c);
}
else if (idx == 3 && code == NOTE)
{
/* Note-specific data appears for operand 3, which annoyingly
is before the enum specifying which kind of note we have
(operand 4). */
c = read_skip_spaces ();
if (c == '[')
{
/* Possibly data for a NOTE_INSN_BASIC_BLOCK, of the form:
[bb %d]. */
file_location bb_loc = read_name (&name);
if (strcmp (name.string, "bb"))
error_at (bb_loc, "was expecting `%s'", "bb");
read_name (&name);
int bb_idx = atoi (name.string);
add_fixup_note_insn_basic_block (bb_loc, x, idx,
bb_idx);
require_char_ws (']');
}
else
unread_char (c);
}
return x;
}
/* Implementation of rtx_reader::handle_any_trailing_information.
Handle the various additional information that print-rtl.cc can
write after the regular fields, when parsing X. */
void
function_reader::handle_any_trailing_information (rtx x)
{
struct md_name name;
switch (GET_CODE (x))
{
case MEM:
{
int ch;
require_char_ws ('[');
read_name (&name);
set_mem_alias_set (x, atoi (name.string));
/* We have either a MEM_EXPR, or a space. */
if (peek_char () != ' ')
{
file_location loc = get_current_location ();
char *desc = read_until (" +", false);
add_fixup_expr (loc, consolidate_singletons (x), desc);
free (desc);
}
else
read_char ();
/* We may optionally have '+' for MEM_OFFSET_KNOWN_P. */
ch = read_skip_spaces ();
if (ch == '+')
{
read_name (&name);
set_mem_offset (x, atoi (name.string));
}
else
unread_char (ch);
/* Handle optional " S" for MEM_SIZE. */
ch = read_skip_spaces ();
if (ch == 'S')
{
read_name (&name);
set_mem_size (x, atoi (name.string));
}
else
unread_char (ch);
/* Handle optional " A" for MEM_ALIGN. */
ch = read_skip_spaces ();
if (ch == 'A' && peek_char () != 'S')
{
read_name (&name);
set_mem_align (x, atoi (name.string));
}
else
unread_char (ch);
/* Handle optional " AS" for MEM_ADDR_SPACE. */
ch = read_skip_spaces ();
if (ch == 'A' && peek_char () == 'S')
{
read_char ();
read_name (&name);
set_mem_addr_space (x, atoi (name.string));
}
else
unread_char (ch);
require_char (']');
}
break;
case CODE_LABEL:
/* Assume that LABEL_NUSES was not dumped. */
/* TODO: parse LABEL_KIND. */
/* For now, skip until closing ')'. */
do
{
char ch = read_char ();
if (ch == ')')
{
unread_char (ch);
break;
}
}
while (1);
break;
default:
break;
}
}
/* Parse a tree dump for a MEM_EXPR in DESC and turn it back into a tree.
We handle "" and param names within cfun, but for anything else
we "cheat" by building a global VAR_DECL of type "int" with that name
(returning the same global for a name if we see the same name more
than once). */
tree
function_reader::parse_mem_expr (const char *desc)
{
tree fndecl = cfun->decl;
if (strcmp (desc, "") == 0)
return DECL_RESULT (fndecl);
tree param = find_param_by_name (fndecl, desc);
if (param)
return param;
/* Search within decls we already created.
FIXME: use a hash rather than linear search. */
int i;
tree t;
FOR_EACH_VEC_ELT (m_fake_scope, i, t)
if (id_equal (DECL_NAME (t), desc))
return t;
/* Not found? Create it.
This allows mimicking of real data but avoids having to specify
e.g. names of locals, params etc.
Though this way we don't know if we have a PARM_DECL vs a VAR_DECL,
and we don't know the types. Fake it by making everything be
a VAR_DECL of "int" type. */
t = build_decl (UNKNOWN_LOCATION, VAR_DECL,
get_identifier (desc),
integer_type_node);
m_fake_scope.safe_push (t);
return t;
}
/* Record that at LOC we saw an insn uid INSN_UID for the operand with index
OPERAND_IDX within INSN, so that the pointer value can be fixed up in
later post-processing. */
void
function_reader::add_fixup_insn_uid (file_location loc, rtx insn, int operand_idx,
int insn_uid)
{
m_fixups.safe_push (new fixup_insn_uid (loc, insn, operand_idx, insn_uid));
}
/* Record that at LOC we saw an basic block index BB_IDX for the operand with index
OPERAND_IDX within INSN, so that the pointer value can be fixed up in
later post-processing. */
void
function_reader::add_fixup_note_insn_basic_block (file_location loc, rtx insn,
int operand_idx, int bb_idx)
{
m_fixups.safe_push (new fixup_note_insn_basic_block (loc, insn, operand_idx,
bb_idx));
}
/* Placeholder hook for recording source location information seen in a dump.
This is empty for now. */
void
function_reader::add_fixup_source_location (file_location, rtx_insn *,
const char *, int, int)
{
}
/* Record that at LOC we saw textual description DESC of the MEM_EXPR or REG_EXPR
of INSN, so that the fields can be fixed up in later post-processing. */
void
function_reader::add_fixup_expr (file_location loc, rtx insn,
const char *desc)
{
gcc_assert (desc);
/* Fail early if the RTL reader erroneously hands us an int. */
gcc_assert (!ISDIGIT (desc[0]));
m_fixups.safe_push (new fixup_expr (loc, insn, desc));
}
/* Helper function for consolidate_reg. Return the global rtx for
the register with regno REGNO. */
static rtx
lookup_global_register (int regno)
{
/* We can't use a switch here, as some of the REGNUMs might not be constants
for some targets. */
if (regno == STACK_POINTER_REGNUM)
return stack_pointer_rtx;
else if (regno == FRAME_POINTER_REGNUM)
return frame_pointer_rtx;
else if (regno == HARD_FRAME_POINTER_REGNUM)
return hard_frame_pointer_rtx;
else if (regno == ARG_POINTER_REGNUM)
return arg_pointer_rtx;
else if (regno == VIRTUAL_INCOMING_ARGS_REGNUM)
return virtual_incoming_args_rtx;
else if (regno == VIRTUAL_STACK_VARS_REGNUM)
return virtual_stack_vars_rtx;
else if (regno == VIRTUAL_STACK_DYNAMIC_REGNUM)
return virtual_stack_dynamic_rtx;
else if (regno == VIRTUAL_OUTGOING_ARGS_REGNUM)
return virtual_outgoing_args_rtx;
else if (regno == VIRTUAL_CFA_REGNUM)
return virtual_cfa_rtx;
else if (regno == VIRTUAL_PREFERRED_STACK_BOUNDARY_REGNUM)
return virtual_preferred_stack_boundary_rtx;
#ifdef return_ADDRESS_POINTER_REGNUM
else if (regno == RETURN_ADDRESS_POINTER_REGNUM)
return return_address_pointer_rtx;
#endif
return NULL;
}
/* Ensure that the backend can cope with a REG with regno REGNO.
Normally REG instances are created by gen_reg_rtx which updates
regno_reg_rtx, growing it as necessary.
The REG instances created from the dumpfile weren't created this
way, so we need to manually update regno_reg_rtx. */
static void
ensure_regno (int regno)
{
if (reg_rtx_no < regno + 1)
reg_rtx_no = regno + 1;
crtl->emit.ensure_regno_capacity ();
gcc_assert (regno < crtl->emit.regno_pointer_align_length);
}
/* Helper function for consolidate_singletons, for handling REG instances.
Given REG instance X of some regno, return the singleton rtx for that
regno, if it exists, or X. */
static rtx
consolidate_reg (rtx x)
{
gcc_assert (GET_CODE (x) == REG);
unsigned int regno = REGNO (x);
ensure_regno (regno);
/* Some register numbers have their rtx created in init_emit_regs
e.g. stack_pointer_rtx for STACK_POINTER_REGNUM.
Consolidate on this. */
rtx global_reg = lookup_global_register (regno);
if (global_reg)
return global_reg;
/* Populate regno_reg_rtx if necessary. */
if (regno_reg_rtx[regno] == NULL)
regno_reg_rtx[regno] = x;
/* Use it. */
gcc_assert (GET_CODE (regno_reg_rtx[regno]) == REG);
gcc_assert (REGNO (regno_reg_rtx[regno]) == regno);
if (GET_MODE (x) == GET_MODE (regno_reg_rtx[regno]))
return regno_reg_rtx[regno];
return x;
}
/* When reading RTL function dumps, we must consolidate some
rtx so that we use singletons where singletons are expected
(e.g. we don't want multiple "(const_int 0 [0])" rtx, since
these are tested via pointer equality against const0_rtx.
Return the equivalent singleton rtx for X, if any, otherwise X. */
rtx
function_reader::consolidate_singletons (rtx x)
{
if (!x)
return x;
switch (GET_CODE (x))
{
case PC: return pc_rtx;
case RETURN: return ret_rtx;
case SIMPLE_RETURN: return simple_return_rtx;
case REG:
return consolidate_reg (x);
case CONST_INT:
return gen_rtx_CONST_INT (GET_MODE (x), INTVAL (x));
case CONST_VECTOR:
return gen_rtx_CONST_VECTOR (GET_MODE (x), XVEC (x, 0));
default:
break;
}
return x;
}
/* Parse an rtx directive, including both the opening/closing parentheses,
and the name. */
rtx
function_reader::parse_rtx ()
{
require_char_ws ('(');
struct md_name directive;
read_name (&directive);
rtx result
= consolidate_singletons (read_rtx_code (directive.string));
require_char_ws (')');
return result;
}
/* Implementation of rtx_reader::postprocess for reading function dumps.
Return the equivalent singleton rtx for X, if any, otherwise X. */
rtx
function_reader::postprocess (rtx x)
{
return consolidate_singletons (x);
}
/* Implementation of rtx_reader::finalize_string for reading function dumps.
Make a GC-managed copy of STRINGBUF. */
const char *
function_reader::finalize_string (char *stringbuf)
{
return ggc_strdup (stringbuf);
}
/* Attempt to parse optional location information for insn INSN, as
potentially written out by rtx_writer::print_rtx_operand_code_i.
We look for a quoted string followed by a colon. */
void
function_reader::maybe_read_location (rtx_insn *insn)
{
file_location loc = get_current_location ();
/* Attempt to parse a quoted string. */
int ch = read_skip_spaces ();
if (ch == '"')
{
char *filename = read_quoted_string ();
require_char (':');
struct md_name line_num;
read_name (&line_num);
int column = 0;
int ch = read_char ();
if (ch == ':')
{
struct md_name column_num;
read_name (&column_num);
column = atoi (column_num.string);
}
else
unread_char (ch);
add_fixup_source_location (loc, insn, filename,
atoi (line_num.string),
column);
}
else
unread_char (ch);
}
/* Postprocessing subroutine of function_reader::parse_function.
Populate m_insns_by_uid. */
void
function_reader::handle_insn_uids ()
{
/* Locate the currently assigned INSN_UID values, storing
them in m_insns_by_uid. */
int max_uid = 0;
for (rtx_insn *insn = get_insns (); insn; insn = NEXT_INSN (insn))
{
if (m_insns_by_uid.get (INSN_UID (insn)))
error ("duplicate insn UID: %i", INSN_UID (insn));
m_insns_by_uid.put (INSN_UID (insn), insn);
if (INSN_UID (insn) > max_uid)
max_uid = INSN_UID (insn);
}
/* Ensure x_cur_insn_uid is 1 more than the biggest insn UID seen.
This is normally updated by the various make_*insn_raw functions. */
crtl->emit.x_cur_insn_uid = max_uid + 1;
}
/* Apply all of the recorded fixups. */
void
function_reader::apply_fixups ()
{
int i;
fixup *f;
FOR_EACH_VEC_ELT (m_fixups, i, f)
f->apply (this);
}
/* Given a UID value, try to locate a pointer to the corresponding
rtx_insn *, or NULL if it can't be found. */
rtx_insn **
function_reader::get_insn_by_uid (int uid)
{
return m_insns_by_uid.get (uid);
}
/* Run the RTL dump parser, parsing a dump located at PATH.
Return true iff the file was successfully parsed. */
bool
read_rtl_function_body (const char *path)
{
initialize_rtl ();
crtl->abi = &default_function_abi;
init_emit ();
init_varasm_status ();
function_reader reader;
if (!reader.read_file (path))
return false;
return true;
}
/* Run the RTL dump parser on the range of lines between START_LOC and
END_LOC (including those lines). */
bool
read_rtl_function_body_from_file_range (location_t start_loc,
location_t end_loc)
{
expanded_location exploc_start = expand_location (start_loc);
expanded_location exploc_end = expand_location (end_loc);
if (exploc_start.file != exploc_end.file)
{
error_at (end_loc, "start/end of RTL fragment are in different files");
return false;
}
if (exploc_start.line >= exploc_end.line)
{
error_at (end_loc,
"start of RTL fragment must be on an earlier line than end");
return false;
}
initialize_rtl ();
crtl->abi = &fndecl_abi (cfun->decl).base_abi ();
init_emit ();
init_varasm_status ();
function_reader reader;
if (!reader.read_file_fragment (exploc_start.file, exploc_start.line,
exploc_end.line - 1))
return false;
return true;
}
#if CHECKING_P
namespace selftest {
/* Verify that parse_edge_flags works. */
static void
test_edge_flags ()
{
/* parse_edge_flags modifies its input (due to strtok), so we must make
a copy of the literals. */
#define ASSERT_PARSE_EDGE_FLAGS(EXPECTED, STR) \
do { \
char *str = xstrdup (STR); \
ASSERT_EQ (EXPECTED, parse_edge_flags (str)); \
free (str); \
} while (0)
ASSERT_PARSE_EDGE_FLAGS (0, "");
ASSERT_PARSE_EDGE_FLAGS (EDGE_FALLTHRU, "FALLTHRU");
ASSERT_PARSE_EDGE_FLAGS (EDGE_ABNORMAL_CALL, "ABNORMAL_CALL");
ASSERT_PARSE_EDGE_FLAGS (EDGE_ABNORMAL | EDGE_ABNORMAL_CALL,
"ABNORMAL | ABNORMAL_CALL");
#undef ASSERT_PARSE_EDGE_FLAGS
}
/* Verify that lookup_reg_by_dump_name works. */
static void
test_parsing_regnos ()
{
ASSERT_EQ (-1, lookup_reg_by_dump_name ("this is not a register"));
/* Verify lookup of virtual registers. */
ASSERT_EQ (VIRTUAL_INCOMING_ARGS_REGNUM,
lookup_reg_by_dump_name ("virtual-incoming-args"));
ASSERT_EQ (VIRTUAL_STACK_VARS_REGNUM,
lookup_reg_by_dump_name ("virtual-stack-vars"));
ASSERT_EQ (VIRTUAL_STACK_DYNAMIC_REGNUM,
lookup_reg_by_dump_name ("virtual-stack-dynamic"));
ASSERT_EQ (VIRTUAL_OUTGOING_ARGS_REGNUM,
lookup_reg_by_dump_name ("virtual-outgoing-args"));
ASSERT_EQ (VIRTUAL_CFA_REGNUM,
lookup_reg_by_dump_name ("virtual-cfa"));
ASSERT_EQ (VIRTUAL_PREFERRED_STACK_BOUNDARY_REGNUM,
lookup_reg_by_dump_name ("virtual-preferred-stack-boundary"));
/* Verify lookup of non-virtual pseudos. */
ASSERT_EQ (LAST_VIRTUAL_REGISTER + 1, lookup_reg_by_dump_name ("<0>"));
ASSERT_EQ (LAST_VIRTUAL_REGISTER + 2, lookup_reg_by_dump_name ("<1>"));
}
/* Verify that edge E is as expected, with the src and dest basic blocks
having indices EXPECTED_SRC_IDX and EXPECTED_DEST_IDX respectively, and
the edge having flags equal to EXPECTED_FLAGS.
Use LOC as the effective location when reporting failures. */
static void
assert_edge_at (const location &loc, edge e, int expected_src_idx,
int expected_dest_idx, int expected_flags)
{
ASSERT_EQ_AT (loc, expected_src_idx, e->src->index);
ASSERT_EQ_AT (loc, expected_dest_idx, e->dest->index);
ASSERT_EQ_AT (loc, expected_flags, e->flags);
}
/* Verify that edge EDGE is as expected, with the src and dest basic blocks
having indices EXPECTED_SRC_IDX and EXPECTED_DEST_IDX respectively, and
the edge having flags equal to EXPECTED_FLAGS. */
#define ASSERT_EDGE(EDGE, EXPECTED_SRC_IDX, EXPECTED_DEST_IDX, \
EXPECTED_FLAGS) \
assert_edge_at (SELFTEST_LOCATION, EDGE, EXPECTED_SRC_IDX, \
EXPECTED_DEST_IDX, EXPECTED_FLAGS)
/* Verify that we can load RTL dumps. */
static void
test_loading_dump_fragment_1 ()
{
// TODO: filter on target?
rtl_dump_test t (SELFTEST_LOCATION, locate_file ("asr_div1.rtl"));
/* Verify that the insns were loaded correctly. */
rtx_insn *insn_1 = get_insns ();
ASSERT_TRUE (insn_1);
ASSERT_EQ (1, INSN_UID (insn_1));
ASSERT_EQ (INSN, GET_CODE (insn_1));
ASSERT_EQ (SET, GET_CODE (PATTERN (insn_1)));
ASSERT_EQ (NULL, PREV_INSN (insn_1));
rtx_insn *insn_2 = NEXT_INSN (insn_1);
ASSERT_TRUE (insn_2);
ASSERT_EQ (2, INSN_UID (insn_2));
ASSERT_EQ (INSN, GET_CODE (insn_2));
ASSERT_EQ (insn_1, PREV_INSN (insn_2));
ASSERT_EQ (NULL, NEXT_INSN (insn_2));
/* Verify that registers were loaded correctly. */
rtx insn_1_dest = SET_DEST (PATTERN (insn_1));
ASSERT_EQ (REG, GET_CODE (insn_1_dest));
ASSERT_EQ ((LAST_VIRTUAL_REGISTER + 1) + 2, REGNO (insn_1_dest));
rtx insn_1_src = SET_SRC (PATTERN (insn_1));
ASSERT_EQ (LSHIFTRT, GET_CODE (insn_1_src));
rtx reg = XEXP (insn_1_src, 0);
ASSERT_EQ (REG, GET_CODE (reg));
ASSERT_EQ (LAST_VIRTUAL_REGISTER + 1, REGNO (reg));
/* Verify that get_insn_by_uid works. */
ASSERT_EQ (insn_1, get_insn_by_uid (1));
ASSERT_EQ (insn_2, get_insn_by_uid (2));
/* Verify that basic blocks were created. */
ASSERT_EQ (2, BLOCK_FOR_INSN (insn_1)->index);
ASSERT_EQ (2, BLOCK_FOR_INSN (insn_2)->index);
/* Verify that the CFG was recreated. */
ASSERT_TRUE (cfun);
verify_three_block_rtl_cfg (cfun);
basic_block bb2 = BASIC_BLOCK_FOR_FN (cfun, 2);
ASSERT_TRUE (bb2 != NULL);
ASSERT_EQ (BB_RTL, bb2->flags & BB_RTL);
ASSERT_EQ (2, bb2->index);
ASSERT_EQ (insn_1, BB_HEAD (bb2));
ASSERT_EQ (insn_2, BB_END (bb2));
}
/* Verify loading another RTL dump. */
static void
test_loading_dump_fragment_2 ()
{
rtl_dump_test t (SELFTEST_LOCATION, locate_file ("simple-cse.rtl"));
rtx_insn *insn_1 = get_insn_by_uid (1);
rtx_insn *insn_2 = get_insn_by_uid (2);
rtx_insn *insn_3 = get_insn_by_uid (3);
rtx set1 = single_set (insn_1);
ASSERT_NE (NULL, set1);
rtx set2 = single_set (insn_2);
ASSERT_NE (NULL, set2);
rtx set3 = single_set (insn_3);
ASSERT_NE (NULL, set3);
rtx src1 = SET_SRC (set1);
ASSERT_EQ (PLUS, GET_CODE (src1));
rtx src2 = SET_SRC (set2);
ASSERT_EQ (PLUS, GET_CODE (src2));
/* Both src1 and src2 refer to "(reg:SI %0)".
Verify that we have pointer equality. */
rtx lhs1 = XEXP (src1, 0);
rtx lhs2 = XEXP (src2, 0);
ASSERT_EQ (lhs1, lhs2);
/* Verify that the CFG was recreated. */
ASSERT_TRUE (cfun);
verify_three_block_rtl_cfg (cfun);
}
/* Verify that CODE_LABEL insns are loaded correctly. */
static void
test_loading_labels ()
{
rtl_dump_test t (SELFTEST_LOCATION, locate_file ("example-labels.rtl"));
rtx_insn *insn_100 = get_insn_by_uid (100);
ASSERT_EQ (CODE_LABEL, GET_CODE (insn_100));
ASSERT_EQ (100, INSN_UID (insn_100));
ASSERT_EQ (NULL, LABEL_NAME (insn_100));
ASSERT_EQ (0, LABEL_NUSES (insn_100));
ASSERT_EQ (30, CODE_LABEL_NUMBER (insn_100));
rtx_insn *insn_200 = get_insn_by_uid (200);
ASSERT_EQ (CODE_LABEL, GET_CODE (insn_200));
ASSERT_EQ (200, INSN_UID (insn_200));
ASSERT_STREQ ("some_label_name", LABEL_NAME (insn_200));
ASSERT_EQ (0, LABEL_NUSES (insn_200));
ASSERT_EQ (40, CODE_LABEL_NUMBER (insn_200));
/* Ensure that the presence of CODE_LABEL_NUMBER == 40
means that the next label num to be handed out will be 41. */
ASSERT_EQ (41, max_label_num ());
/* Ensure that label names read from a dump are GC-managed
and are found through the insn. */
ggc_collect (GGC_COLLECT_FORCE);
ASSERT_TRUE (ggc_marked_p (insn_200));
ASSERT_TRUE (ggc_marked_p (LABEL_NAME (insn_200)));
}
/* Verify that the loader copes with an insn with a mode. */
static void
test_loading_insn_with_mode ()
{
rtl_dump_test t (SELFTEST_LOCATION, locate_file ("insn-with-mode.rtl"));
rtx_insn *insn = get_insns ();
ASSERT_EQ (INSN, GET_CODE (insn));
/* Verify that the "TI" mode was set from "insn:TI". */
ASSERT_EQ (TImode, GET_MODE (insn));
}
/* Verify that the loader copes with a jump_insn to a label_ref. */
static void
test_loading_jump_to_label_ref ()
{
rtl_dump_test t (SELFTEST_LOCATION, locate_file ("jump-to-label-ref.rtl"));
rtx_insn *jump_insn = get_insn_by_uid (1);
ASSERT_EQ (JUMP_INSN, GET_CODE (jump_insn));
rtx_insn *barrier = get_insn_by_uid (2);
ASSERT_EQ (BARRIER, GET_CODE (barrier));
rtx_insn *code_label = get_insn_by_uid (100);
ASSERT_EQ (CODE_LABEL, GET_CODE (code_label));
/* Verify the jump_insn. */
ASSERT_EQ (4, BLOCK_FOR_INSN (jump_insn)->index);
ASSERT_EQ (SET, GET_CODE (PATTERN (jump_insn)));
/* Ensure that the "(pc)" is using the global singleton. */
ASSERT_RTX_PTR_EQ (pc_rtx, SET_DEST (PATTERN (jump_insn)));
rtx label_ref = SET_SRC (PATTERN (jump_insn));
ASSERT_EQ (LABEL_REF, GET_CODE (label_ref));
ASSERT_EQ (code_label, label_ref_label (label_ref));
ASSERT_EQ (code_label, JUMP_LABEL (jump_insn));
/* Verify the code_label. */
ASSERT_EQ (5, BLOCK_FOR_INSN (code_label)->index);
ASSERT_EQ (NULL, LABEL_NAME (code_label));
ASSERT_EQ (1, LABEL_NUSES (code_label));
/* Verify the generated CFG. */
/* Locate blocks. */
basic_block entry = ENTRY_BLOCK_PTR_FOR_FN (cfun);
ASSERT_TRUE (entry != NULL);
ASSERT_EQ (ENTRY_BLOCK, entry->index);
basic_block exit = EXIT_BLOCK_PTR_FOR_FN (cfun);
ASSERT_TRUE (exit != NULL);
ASSERT_EQ (EXIT_BLOCK, exit->index);
basic_block bb4 = (*cfun->cfg->x_basic_block_info)[4];
basic_block bb5 = (*cfun->cfg->x_basic_block_info)[5];
ASSERT_EQ (4, bb4->index);
ASSERT_EQ (5, bb5->index);
/* Entry block. */
ASSERT_EQ (NULL, entry->preds);
ASSERT_EQ (1, entry->succs->length ());
ASSERT_EDGE ((*entry->succs)[0], 0, 4, EDGE_FALLTHRU);
/* bb4. */
ASSERT_EQ (1, bb4->preds->length ());
ASSERT_EDGE ((*bb4->preds)[0], 0, 4, EDGE_FALLTHRU);
ASSERT_EQ (1, bb4->succs->length ());
ASSERT_EDGE ((*bb4->succs)[0], 4, 5, 0x0);
/* bb5. */
ASSERT_EQ (1, bb5->preds->length ());
ASSERT_EDGE ((*bb5->preds)[0], 4, 5, 0x0);
ASSERT_EQ (1, bb5->succs->length ());
ASSERT_EDGE ((*bb5->succs)[0], 5, 1, EDGE_FALLTHRU);
/* Exit block. */
ASSERT_EQ (1, exit->preds->length ());
ASSERT_EDGE ((*exit->preds)[0], 5, 1, EDGE_FALLTHRU);
ASSERT_EQ (NULL, exit->succs);
}
/* Verify that the loader copes with a jump_insn to a label_ref
marked "return". */
static void
test_loading_jump_to_return ()
{
rtl_dump_test t (SELFTEST_LOCATION, locate_file ("jump-to-return.rtl"));
rtx_insn *jump_insn = get_insn_by_uid (1);
ASSERT_EQ (JUMP_INSN, GET_CODE (jump_insn));
ASSERT_RTX_PTR_EQ (ret_rtx, JUMP_LABEL (jump_insn));
}
/* Verify that the loader copes with a jump_insn to a label_ref
marked "simple_return". */
static void
test_loading_jump_to_simple_return ()
{
rtl_dump_test t (SELFTEST_LOCATION,
locate_file ("jump-to-simple-return.rtl"));
rtx_insn *jump_insn = get_insn_by_uid (1);
ASSERT_EQ (JUMP_INSN, GET_CODE (jump_insn));
ASSERT_RTX_PTR_EQ (simple_return_rtx, JUMP_LABEL (jump_insn));
}
/* Verify that the loader copes with a NOTE_INSN_BASIC_BLOCK. */
static void
test_loading_note_insn_basic_block ()
{
rtl_dump_test t (SELFTEST_LOCATION,
locate_file ("note_insn_basic_block.rtl"));
rtx_insn *note = get_insn_by_uid (1);
ASSERT_EQ (NOTE, GET_CODE (note));
ASSERT_EQ (2, BLOCK_FOR_INSN (note)->index);
ASSERT_EQ (NOTE_INSN_BASIC_BLOCK, NOTE_KIND (note));
ASSERT_EQ (2, NOTE_BASIC_BLOCK (note)->index);
ASSERT_EQ (BASIC_BLOCK_FOR_FN (cfun, 2), NOTE_BASIC_BLOCK (note));
}
/* Verify that the loader copes with a NOTE_INSN_DELETED. */
static void
test_loading_note_insn_deleted ()
{
rtl_dump_test t (SELFTEST_LOCATION, locate_file ("note-insn-deleted.rtl"));
rtx_insn *note = get_insn_by_uid (1);
ASSERT_EQ (NOTE, GET_CODE (note));
ASSERT_EQ (NOTE_INSN_DELETED, NOTE_KIND (note));
}
/* Verify that the const_int values are consolidated, since
pointer equality corresponds to value equality.
TODO: do this for all in CASE_CONST_UNIQUE. */
static void
test_loading_const_int ()
{
rtl_dump_test t (SELFTEST_LOCATION, locate_file ("const-int.rtl"));
/* Verify that const_int values below MAX_SAVED_CONST_INT use
the global values. */
ASSERT_EQ (const0_rtx, SET_SRC (PATTERN (get_insn_by_uid (1))));
ASSERT_EQ (const1_rtx, SET_SRC (PATTERN (get_insn_by_uid (2))));
ASSERT_EQ (constm1_rtx, SET_SRC (PATTERN (get_insn_by_uid (3))));
/* Verify that other const_int values are consolidated. */
rtx int256 = gen_rtx_CONST_INT (SImode, 256);
ASSERT_EQ (int256, SET_SRC (PATTERN (get_insn_by_uid (4))));
}
/* Verify that the loader copes with a SYMBOL_REF. */
static void
test_loading_symbol_ref ()
{
rtl_dump_test t (SELFTEST_LOCATION, locate_file ("symbol-ref.rtl"));
rtx_insn *insn = get_insns ();
rtx high = SET_SRC (PATTERN (insn));
ASSERT_EQ (HIGH, GET_CODE (high));
rtx symbol_ref = XEXP (high, 0);
ASSERT_EQ (SYMBOL_REF, GET_CODE (symbol_ref));
/* Verify that "[flags 0xc0]" was parsed. */
ASSERT_EQ (0xc0, SYMBOL_REF_FLAGS (symbol_ref));
/* TODO: we don't yet load SYMBOL_REF_DECL. */
}
/* Verify that the loader can rebuild a CFG. */
static void
test_loading_cfg ()
{
rtl_dump_test t (SELFTEST_LOCATION, locate_file ("cfg-test.rtl"));
ASSERT_STREQ ("cfg_test", IDENTIFIER_POINTER (DECL_NAME (cfun->decl)));
ASSERT_TRUE (cfun);
ASSERT_TRUE (cfun->cfg != NULL);
ASSERT_EQ (6, n_basic_blocks_for_fn (cfun));
ASSERT_EQ (6, n_edges_for_fn (cfun));
/* The "fake" basic blocks. */
basic_block entry = ENTRY_BLOCK_PTR_FOR_FN (cfun);
ASSERT_TRUE (entry != NULL);
ASSERT_EQ (ENTRY_BLOCK, entry->index);
basic_block exit = EXIT_BLOCK_PTR_FOR_FN (cfun);
ASSERT_TRUE (exit != NULL);
ASSERT_EQ (EXIT_BLOCK, exit->index);
/* The "real" basic blocks. */
basic_block bb2 = (*cfun->cfg->x_basic_block_info)[2];
basic_block bb3 = (*cfun->cfg->x_basic_block_info)[3];
basic_block bb4 = (*cfun->cfg->x_basic_block_info)[4];
basic_block bb5 = (*cfun->cfg->x_basic_block_info)[5];
ASSERT_EQ (2, bb2->index);
ASSERT_EQ (3, bb3->index);
ASSERT_EQ (4, bb4->index);
ASSERT_EQ (5, bb5->index);
/* Verify connectivity. */
/* Entry block. */
ASSERT_EQ (NULL, entry->preds);
ASSERT_EQ (1, entry->succs->length ());
ASSERT_EDGE ((*entry->succs)[0], 0, 2, EDGE_FALLTHRU);
/* bb2. */
ASSERT_EQ (1, bb2->preds->length ());
ASSERT_EDGE ((*bb2->preds)[0], 0, 2, EDGE_FALLTHRU);
ASSERT_EQ (2, bb2->succs->length ());
ASSERT_EDGE ((*bb2->succs)[0], 2, 3, EDGE_TRUE_VALUE);
ASSERT_EDGE ((*bb2->succs)[1], 2, 4, EDGE_FALSE_VALUE);
/* bb3. */
ASSERT_EQ (1, bb3->preds->length ());
ASSERT_EDGE ((*bb3->preds)[0], 2, 3, EDGE_TRUE_VALUE);
ASSERT_EQ (1, bb3->succs->length ());
ASSERT_EDGE ((*bb3->succs)[0], 3, 5, EDGE_FALLTHRU);
/* bb4. */
ASSERT_EQ (1, bb4->preds->length ());
ASSERT_EDGE ((*bb4->preds)[0], 2, 4, EDGE_FALSE_VALUE);
ASSERT_EQ (1, bb4->succs->length ());
ASSERT_EDGE ((*bb4->succs)[0], 4, 5, EDGE_FALLTHRU);
/* bb5. */
ASSERT_EQ (2, bb5->preds->length ());
ASSERT_EDGE ((*bb5->preds)[0], 3, 5, EDGE_FALLTHRU);
ASSERT_EDGE ((*bb5->preds)[1], 4, 5, EDGE_FALLTHRU);
ASSERT_EQ (1, bb5->succs->length ());
ASSERT_EDGE ((*bb5->succs)[0], 5, 1, EDGE_FALLTHRU);
/* Exit block. */
ASSERT_EQ (1, exit->preds->length ());
ASSERT_EDGE ((*exit->preds)[0], 5, 1, EDGE_FALLTHRU);
ASSERT_EQ (NULL, exit->succs);
}
/* Verify that the loader copes with sparse block indices.
This testcase loads a file with a "(block 42)". */
static void
test_loading_bb_index ()
{
rtl_dump_test t (SELFTEST_LOCATION, locate_file ("bb-index.rtl"));
ASSERT_STREQ ("test_bb_index", IDENTIFIER_POINTER (DECL_NAME (cfun->decl)));
ASSERT_TRUE (cfun);
ASSERT_TRUE (cfun->cfg != NULL);
ASSERT_EQ (3, n_basic_blocks_for_fn (cfun));
ASSERT_EQ (43, basic_block_info_for_fn (cfun)->length ());
ASSERT_EQ (2, n_edges_for_fn (cfun));
ASSERT_EQ (NULL, (*cfun->cfg->x_basic_block_info)[41]);
basic_block bb42 = (*cfun->cfg->x_basic_block_info)[42];
ASSERT_NE (NULL, bb42);
ASSERT_EQ (42, bb42->index);
}
/* Verify that function_reader::handle_any_trailing_information correctly
parses all the possible items emitted for a MEM. */
static void
test_loading_mem ()
{
rtl_dump_test t (SELFTEST_LOCATION, locate_file ("mem.rtl"));
ASSERT_STREQ ("test_mem", IDENTIFIER_POINTER (DECL_NAME (cfun->decl)));
ASSERT_TRUE (cfun);
/* Verify parsing of "[42 i+17 S8 A128 AS5]". */
rtx_insn *insn_1 = get_insn_by_uid (1);
rtx set1 = single_set (insn_1);
rtx mem1 = SET_DEST (set1);
ASSERT_EQ (42, MEM_ALIAS_SET (mem1));
/* "+17". */
ASSERT_TRUE (MEM_OFFSET_KNOWN_P (mem1));
ASSERT_KNOWN_EQ (17, MEM_OFFSET (mem1));
/* "S8". */
ASSERT_KNOWN_EQ (8, MEM_SIZE (mem1));
/* "A128. */
ASSERT_EQ (128, MEM_ALIGN (mem1));
/* "AS5. */
ASSERT_EQ (5, MEM_ADDR_SPACE (mem1));
/* Verify parsing of "43 i+18 S9 AS6"
(an address space without an alignment). */
rtx_insn *insn_2 = get_insn_by_uid (2);
rtx set2 = single_set (insn_2);
rtx mem2 = SET_DEST (set2);
ASSERT_EQ (43, MEM_ALIAS_SET (mem2));
/* "+18". */
ASSERT_TRUE (MEM_OFFSET_KNOWN_P (mem2));
ASSERT_KNOWN_EQ (18, MEM_OFFSET (mem2));
/* "S9". */
ASSERT_KNOWN_EQ (9, MEM_SIZE (mem2));
/* "AS6. */
ASSERT_EQ (6, MEM_ADDR_SPACE (mem2));
}
/* Verify that "repeated xN" is read correctly. */
static void
test_loading_repeat ()
{
rtl_dump_test t (SELFTEST_LOCATION, locate_file ("repeat.rtl"));
rtx_insn *insn_1 = get_insn_by_uid (1);
ASSERT_EQ (PARALLEL, GET_CODE (PATTERN (insn_1)));
ASSERT_EQ (64, XVECLEN (PATTERN (insn_1), 0));
for (int i = 0; i < 64; i++)
ASSERT_EQ (const0_rtx, XVECEXP (PATTERN (insn_1), 0, i));
}
/* Run all of the selftests within this file. */
void
read_rtl_function_cc_tests ()
{
test_edge_flags ();
test_parsing_regnos ();
test_loading_dump_fragment_1 ();
test_loading_dump_fragment_2 ();
test_loading_labels ();
test_loading_insn_with_mode ();
test_loading_jump_to_label_ref ();
test_loading_jump_to_return ();
test_loading_jump_to_simple_return ();
test_loading_note_insn_basic_block ();
test_loading_note_insn_deleted ();
test_loading_const_int ();
test_loading_symbol_ref ();
test_loading_cfg ();
test_loading_bb_index ();
test_loading_mem ();
test_loading_repeat ();
}
} // namespace selftest
#endif /* #if CHECKING_P */