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