| /* Convert RTL to assembler code and output it, for GNU compiler. |
| Copyright (C) 1987-2021 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 |
| <http://www.gnu.org/licenses/>. */ |
| |
| /* This is the final pass of the compiler. |
| It looks at the rtl code for a function and outputs assembler code. |
| |
| Call `final_start_function' to output the assembler code for function entry, |
| `final' to output assembler code for some RTL code, |
| `final_end_function' to output assembler code for function exit. |
| If a function is compiled in several pieces, each piece is |
| output separately with `final'. |
| |
| Some optimizations are also done at this level. |
| Move instructions that were made unnecessary by good register allocation |
| are detected and omitted from the output. (Though most of these |
| are removed by the last jump pass.) |
| |
| Instructions to set the condition codes are omitted when it can be |
| seen that the condition codes already had the desired values. |
| |
| In some cases it is sufficient if the inherited condition codes |
| have related values, but this may require the following insn |
| (the one that tests the condition codes) to be modified. |
| |
| The code for the function prologue and epilogue are generated |
| directly in assembler by the target functions function_prologue and |
| function_epilogue. Those instructions never exist as rtl. */ |
| |
| #include "config.h" |
| #define INCLUDE_ALGORITHM /* reverse */ |
| #include "system.h" |
| #include "coretypes.h" |
| #include "backend.h" |
| #include "target.h" |
| #include "rtl.h" |
| #include "tree.h" |
| #include "cfghooks.h" |
| #include "df.h" |
| #include "memmodel.h" |
| #include "tm_p.h" |
| #include "insn-config.h" |
| #include "regs.h" |
| #include "emit-rtl.h" |
| #include "recog.h" |
| #include "cgraph.h" |
| #include "tree-pretty-print.h" /* for dump_function_header */ |
| #include "varasm.h" |
| #include "insn-attr.h" |
| #include "conditions.h" |
| #include "flags.h" |
| #include "output.h" |
| #include "except.h" |
| #include "rtl-error.h" |
| #include "toplev.h" /* exact_log2, floor_log2 */ |
| #include "reload.h" |
| #include "intl.h" |
| #include "cfgrtl.h" |
| #include "debug.h" |
| #include "tree-pass.h" |
| #include "tree-ssa.h" |
| #include "cfgloop.h" |
| #include "stringpool.h" |
| #include "attribs.h" |
| #include "asan.h" |
| #include "rtl-iter.h" |
| #include "print-rtl.h" |
| #include "function-abi.h" |
| #include "common/common-target.h" |
| |
| #ifdef XCOFF_DEBUGGING_INFO |
| #include "xcoffout.h" /* Needed for external data declarations. */ |
| #endif |
| |
| #include "dwarf2out.h" |
| |
| #ifdef DBX_DEBUGGING_INFO |
| #include "dbxout.h" |
| #endif |
| |
| /* Most ports don't need to define CC_STATUS_INIT. |
| So define a null default for it to save conditionalization later. */ |
| #ifndef CC_STATUS_INIT |
| #define CC_STATUS_INIT |
| #endif |
| |
| /* Is the given character a logical line separator for the assembler? */ |
| #ifndef IS_ASM_LOGICAL_LINE_SEPARATOR |
| #define IS_ASM_LOGICAL_LINE_SEPARATOR(C, STR) ((C) == ';') |
| #endif |
| |
| #ifndef JUMP_TABLES_IN_TEXT_SECTION |
| #define JUMP_TABLES_IN_TEXT_SECTION 0 |
| #endif |
| |
| /* Bitflags used by final_scan_insn. */ |
| #define SEEN_NOTE 1 |
| #define SEEN_EMITTED 2 |
| #define SEEN_NEXT_VIEW 4 |
| |
| /* Last insn processed by final_scan_insn. */ |
| static rtx_insn *debug_insn; |
| rtx_insn *current_output_insn; |
| |
| /* Line number of last NOTE. */ |
| static int last_linenum; |
| |
| /* Column number of last NOTE. */ |
| static int last_columnnum; |
| |
| /* Discriminator written to assembly. */ |
| static int last_discriminator; |
| |
| /* Discriminator to be written to assembly for current instruction. |
| Note: actual usage depends on loc_discriminator_kind setting. */ |
| static int discriminator; |
| static inline int compute_discriminator (location_t loc); |
| |
| /* Discriminator identifying current basic block among others sharing |
| the same locus. */ |
| static int bb_discriminator; |
| |
| /* Basic block discriminator for previous instruction. */ |
| static int last_bb_discriminator; |
| |
| /* Highest line number in current block. */ |
| static int high_block_linenum; |
| |
| /* Likewise for function. */ |
| static int high_function_linenum; |
| |
| /* Filename of last NOTE. */ |
| static const char *last_filename; |
| |
| /* Override filename, line and column number. */ |
| static const char *override_filename; |
| static int override_linenum; |
| static int override_columnnum; |
| static int override_discriminator; |
| |
| /* Whether to force emission of a line note before the next insn. */ |
| static bool force_source_line = false; |
| |
| extern const int length_unit_log; /* This is defined in insn-attrtab.c. */ |
| |
| /* Nonzero while outputting an `asm' with operands. |
| This means that inconsistencies are the user's fault, so don't die. |
| The precise value is the insn being output, to pass to error_for_asm. */ |
| const rtx_insn *this_is_asm_operands; |
| |
| /* Number of operands of this insn, for an `asm' with operands. */ |
| static unsigned int insn_noperands; |
| |
| /* Compare optimization flag. */ |
| |
| static rtx last_ignored_compare = 0; |
| |
| /* Assign a unique number to each insn that is output. |
| This can be used to generate unique local labels. */ |
| |
| static int insn_counter = 0; |
| |
| /* Number of unmatched NOTE_INSN_BLOCK_BEG notes we have seen. */ |
| |
| static int block_depth; |
| |
| /* Nonzero if have enabled APP processing of our assembler output. */ |
| |
| static int app_on; |
| |
| /* If we are outputting an insn sequence, this contains the sequence rtx. |
| Zero otherwise. */ |
| |
| rtx_sequence *final_sequence; |
| |
| #ifdef ASSEMBLER_DIALECT |
| |
| /* Number of the assembler dialect to use, starting at 0. */ |
| static int dialect_number; |
| #endif |
| |
| /* Nonnull if the insn currently being emitted was a COND_EXEC pattern. */ |
| rtx current_insn_predicate; |
| |
| /* True if printing into -fdump-final-insns= dump. */ |
| bool final_insns_dump_p; |
| |
| /* True if profile_function should be called, but hasn't been called yet. */ |
| static bool need_profile_function; |
| |
| static int asm_insn_count (rtx); |
| static void profile_function (FILE *); |
| static void profile_after_prologue (FILE *); |
| static bool notice_source_line (rtx_insn *, bool *); |
| static rtx walk_alter_subreg (rtx *, bool *); |
| static void output_asm_name (void); |
| static void output_alternate_entry_point (FILE *, rtx_insn *); |
| static tree get_mem_expr_from_op (rtx, int *); |
| static void output_asm_operand_names (rtx *, int *, int); |
| #ifdef LEAF_REGISTERS |
| static void leaf_renumber_regs (rtx_insn *); |
| #endif |
| static int align_fuzz (rtx, rtx, int, unsigned); |
| static void collect_fn_hard_reg_usage (void); |
| |
| /* Initialize data in final at the beginning of a compilation. */ |
| |
| void |
| init_final (const char *filename ATTRIBUTE_UNUSED) |
| { |
| app_on = 0; |
| final_sequence = 0; |
| |
| #ifdef ASSEMBLER_DIALECT |
| dialect_number = ASSEMBLER_DIALECT; |
| #endif |
| } |
| |
| /* Default target function prologue and epilogue assembler output. |
| |
| If not overridden for epilogue code, then the function body itself |
| contains return instructions wherever needed. */ |
| void |
| default_function_pro_epilogue (FILE *) |
| { |
| } |
| |
| void |
| default_function_switched_text_sections (FILE *file ATTRIBUTE_UNUSED, |
| tree decl ATTRIBUTE_UNUSED, |
| bool new_is_cold ATTRIBUTE_UNUSED) |
| { |
| } |
| |
| /* Default target hook that outputs nothing to a stream. */ |
| void |
| no_asm_to_stream (FILE *file ATTRIBUTE_UNUSED) |
| { |
| } |
| |
| /* Enable APP processing of subsequent output. |
| Used before the output from an `asm' statement. */ |
| |
| void |
| app_enable (void) |
| { |
| if (! app_on) |
| { |
| fputs (ASM_APP_ON, asm_out_file); |
| app_on = 1; |
| } |
| } |
| |
| /* Disable APP processing of subsequent output. |
| Called from varasm.c before most kinds of output. */ |
| |
| void |
| app_disable (void) |
| { |
| if (app_on) |
| { |
| fputs (ASM_APP_OFF, asm_out_file); |
| app_on = 0; |
| } |
| } |
| |
| /* Return the number of slots filled in the current |
| delayed branch sequence (we don't count the insn needing the |
| delay slot). Zero if not in a delayed branch sequence. */ |
| |
| int |
| dbr_sequence_length (void) |
| { |
| if (final_sequence != 0) |
| return XVECLEN (final_sequence, 0) - 1; |
| else |
| return 0; |
| } |
| |
| /* The next two pages contain routines used to compute the length of an insn |
| and to shorten branches. */ |
| |
| /* Arrays for insn lengths, and addresses. The latter is referenced by |
| `insn_current_length'. */ |
| |
| static int *insn_lengths; |
| |
| vec<int> insn_addresses_; |
| |
| /* Max uid for which the above arrays are valid. */ |
| static int insn_lengths_max_uid; |
| |
| /* Address of insn being processed. Used by `insn_current_length'. */ |
| int insn_current_address; |
| |
| /* Address of insn being processed in previous iteration. */ |
| int insn_last_address; |
| |
| /* known invariant alignment of insn being processed. */ |
| int insn_current_align; |
| |
| /* After shorten_branches, for any insn, uid_align[INSN_UID (insn)] |
| gives the next following alignment insn that increases the known |
| alignment, or NULL_RTX if there is no such insn. |
| For any alignment obtained this way, we can again index uid_align with |
| its uid to obtain the next following align that in turn increases the |
| alignment, till we reach NULL_RTX; the sequence obtained this way |
| for each insn we'll call the alignment chain of this insn in the following |
| comments. */ |
| |
| static rtx *uid_align; |
| static int *uid_shuid; |
| static vec<align_flags> label_align; |
| |
| /* Indicate that branch shortening hasn't yet been done. */ |
| |
| void |
| init_insn_lengths (void) |
| { |
| if (uid_shuid) |
| { |
| free (uid_shuid); |
| uid_shuid = 0; |
| } |
| if (insn_lengths) |
| { |
| free (insn_lengths); |
| insn_lengths = 0; |
| insn_lengths_max_uid = 0; |
| } |
| if (HAVE_ATTR_length) |
| INSN_ADDRESSES_FREE (); |
| if (uid_align) |
| { |
| free (uid_align); |
| uid_align = 0; |
| } |
| } |
| |
| /* Obtain the current length of an insn. If branch shortening has been done, |
| get its actual length. Otherwise, use FALLBACK_FN to calculate the |
| length. */ |
| static int |
| get_attr_length_1 (rtx_insn *insn, int (*fallback_fn) (rtx_insn *)) |
| { |
| rtx body; |
| int i; |
| int length = 0; |
| |
| if (!HAVE_ATTR_length) |
| return 0; |
| |
| if (insn_lengths_max_uid > INSN_UID (insn)) |
| return insn_lengths[INSN_UID (insn)]; |
| else |
| switch (GET_CODE (insn)) |
| { |
| case NOTE: |
| case BARRIER: |
| case CODE_LABEL: |
| case DEBUG_INSN: |
| return 0; |
| |
| case CALL_INSN: |
| case JUMP_INSN: |
| length = fallback_fn (insn); |
| break; |
| |
| case INSN: |
| body = PATTERN (insn); |
| if (GET_CODE (body) == USE || GET_CODE (body) == CLOBBER) |
| return 0; |
| |
| else if (GET_CODE (body) == ASM_INPUT || asm_noperands (body) >= 0) |
| length = asm_insn_count (body) * fallback_fn (insn); |
| else if (rtx_sequence *seq = dyn_cast <rtx_sequence *> (body)) |
| for (i = 0; i < seq->len (); i++) |
| length += get_attr_length_1 (seq->insn (i), fallback_fn); |
| else |
| length = fallback_fn (insn); |
| break; |
| |
| default: |
| break; |
| } |
| |
| #ifdef ADJUST_INSN_LENGTH |
| ADJUST_INSN_LENGTH (insn, length); |
| #endif |
| return length; |
| } |
| |
| /* Obtain the current length of an insn. If branch shortening has been done, |
| get its actual length. Otherwise, get its maximum length. */ |
| int |
| get_attr_length (rtx_insn *insn) |
| { |
| return get_attr_length_1 (insn, insn_default_length); |
| } |
| |
| /* Obtain the current length of an insn. If branch shortening has been done, |
| get its actual length. Otherwise, get its minimum length. */ |
| int |
| get_attr_min_length (rtx_insn *insn) |
| { |
| return get_attr_length_1 (insn, insn_min_length); |
| } |
| |
| /* Code to handle alignment inside shorten_branches. */ |
| |
| /* Here is an explanation how the algorithm in align_fuzz can give |
| proper results: |
| |
| Call a sequence of instructions beginning with alignment point X |
| and continuing until the next alignment point `block X'. When `X' |
| is used in an expression, it means the alignment value of the |
| alignment point. |
| |
| Call the distance between the start of the first insn of block X, and |
| the end of the last insn of block X `IX', for the `inner size of X'. |
| This is clearly the sum of the instruction lengths. |
| |
| Likewise with the next alignment-delimited block following X, which we |
| shall call block Y. |
| |
| Call the distance between the start of the first insn of block X, and |
| the start of the first insn of block Y `OX', for the `outer size of X'. |
| |
| The estimated padding is then OX - IX. |
| |
| OX can be safely estimated as |
| |
| if (X >= Y) |
| OX = round_up(IX, Y) |
| else |
| OX = round_up(IX, X) + Y - X |
| |
| Clearly est(IX) >= real(IX), because that only depends on the |
| instruction lengths, and those being overestimated is a given. |
| |
| Clearly round_up(foo, Z) >= round_up(bar, Z) if foo >= bar, so |
| we needn't worry about that when thinking about OX. |
| |
| When X >= Y, the alignment provided by Y adds no uncertainty factor |
| for branch ranges starting before X, so we can just round what we have. |
| But when X < Y, we don't know anything about the, so to speak, |
| `middle bits', so we have to assume the worst when aligning up from an |
| address mod X to one mod Y, which is Y - X. */ |
| |
| #ifndef LABEL_ALIGN |
| #define LABEL_ALIGN(LABEL) align_labels |
| #endif |
| |
| #ifndef LOOP_ALIGN |
| #define LOOP_ALIGN(LABEL) align_loops |
| #endif |
| |
| #ifndef LABEL_ALIGN_AFTER_BARRIER |
| #define LABEL_ALIGN_AFTER_BARRIER(LABEL) 0 |
| #endif |
| |
| #ifndef JUMP_ALIGN |
| #define JUMP_ALIGN(LABEL) align_jumps |
| #endif |
| |
| #ifndef ADDR_VEC_ALIGN |
| static int |
| final_addr_vec_align (rtx_jump_table_data *addr_vec) |
| { |
| int align = GET_MODE_SIZE (addr_vec->get_data_mode ()); |
| |
| if (align > BIGGEST_ALIGNMENT / BITS_PER_UNIT) |
| align = BIGGEST_ALIGNMENT / BITS_PER_UNIT; |
| return exact_log2 (align); |
| |
| } |
| |
| #define ADDR_VEC_ALIGN(ADDR_VEC) final_addr_vec_align (ADDR_VEC) |
| #endif |
| |
| #ifndef INSN_LENGTH_ALIGNMENT |
| #define INSN_LENGTH_ALIGNMENT(INSN) length_unit_log |
| #endif |
| |
| #define INSN_SHUID(INSN) (uid_shuid[INSN_UID (INSN)]) |
| |
| static int min_labelno, max_labelno; |
| |
| #define LABEL_TO_ALIGNMENT(LABEL) \ |
| (label_align[CODE_LABEL_NUMBER (LABEL) - min_labelno]) |
| |
| /* For the benefit of port specific code do this also as a function. */ |
| |
| align_flags |
| label_to_alignment (rtx label) |
| { |
| if (CODE_LABEL_NUMBER (label) <= max_labelno) |
| return LABEL_TO_ALIGNMENT (label); |
| return align_flags (); |
| } |
| |
| /* The differences in addresses |
| between a branch and its target might grow or shrink depending on |
| the alignment the start insn of the range (the branch for a forward |
| branch or the label for a backward branch) starts out on; if these |
| differences are used naively, they can even oscillate infinitely. |
| We therefore want to compute a 'worst case' address difference that |
| is independent of the alignment the start insn of the range end |
| up on, and that is at least as large as the actual difference. |
| The function align_fuzz calculates the amount we have to add to the |
| naively computed difference, by traversing the part of the alignment |
| chain of the start insn of the range that is in front of the end insn |
| of the range, and considering for each alignment the maximum amount |
| that it might contribute to a size increase. |
| |
| For casesi tables, we also want to know worst case minimum amounts of |
| address difference, in case a machine description wants to introduce |
| some common offset that is added to all offsets in a table. |
| For this purpose, align_fuzz with a growth argument of 0 computes the |
| appropriate adjustment. */ |
| |
| /* Compute the maximum delta by which the difference of the addresses of |
| START and END might grow / shrink due to a different address for start |
| which changes the size of alignment insns between START and END. |
| KNOWN_ALIGN_LOG is the alignment known for START. |
| GROWTH should be ~0 if the objective is to compute potential code size |
| increase, and 0 if the objective is to compute potential shrink. |
| The return value is undefined for any other value of GROWTH. */ |
| |
| static int |
| align_fuzz (rtx start, rtx end, int known_align_log, unsigned int growth) |
| { |
| int uid = INSN_UID (start); |
| rtx align_label; |
| int known_align = 1 << known_align_log; |
| int end_shuid = INSN_SHUID (end); |
| int fuzz = 0; |
| |
| for (align_label = uid_align[uid]; align_label; align_label = uid_align[uid]) |
| { |
| int align_addr, new_align; |
| |
| uid = INSN_UID (align_label); |
| align_addr = INSN_ADDRESSES (uid) - insn_lengths[uid]; |
| if (uid_shuid[uid] > end_shuid) |
| break; |
| align_flags alignment = LABEL_TO_ALIGNMENT (align_label); |
| new_align = 1 << alignment.levels[0].log; |
| if (new_align < known_align) |
| continue; |
| fuzz += (-align_addr ^ growth) & (new_align - known_align); |
| known_align = new_align; |
| } |
| return fuzz; |
| } |
| |
| /* Compute a worst-case reference address of a branch so that it |
| can be safely used in the presence of aligned labels. Since the |
| size of the branch itself is unknown, the size of the branch is |
| not included in the range. I.e. for a forward branch, the reference |
| address is the end address of the branch as known from the previous |
| branch shortening pass, minus a value to account for possible size |
| increase due to alignment. For a backward branch, it is the start |
| address of the branch as known from the current pass, plus a value |
| to account for possible size increase due to alignment. |
| NB.: Therefore, the maximum offset allowed for backward branches needs |
| to exclude the branch size. */ |
| |
| int |
| insn_current_reference_address (rtx_insn *branch) |
| { |
| rtx dest; |
| int seq_uid; |
| |
| if (! INSN_ADDRESSES_SET_P ()) |
| return 0; |
| |
| rtx_insn *seq = NEXT_INSN (PREV_INSN (branch)); |
| seq_uid = INSN_UID (seq); |
| if (!jump_to_label_p (branch)) |
| /* This can happen for example on the PA; the objective is to know the |
| offset to address something in front of the start of the function. |
| Thus, we can treat it like a backward branch. |
| We assume here that FUNCTION_BOUNDARY / BITS_PER_UNIT is larger than |
| any alignment we'd encounter, so we skip the call to align_fuzz. */ |
| return insn_current_address; |
| dest = JUMP_LABEL (branch); |
| |
| /* BRANCH has no proper alignment chain set, so use SEQ. |
| BRANCH also has no INSN_SHUID. */ |
| if (INSN_SHUID (seq) < INSN_SHUID (dest)) |
| { |
| /* Forward branch. */ |
| return (insn_last_address + insn_lengths[seq_uid] |
| - align_fuzz (seq, dest, length_unit_log, ~0)); |
| } |
| else |
| { |
| /* Backward branch. */ |
| return (insn_current_address |
| + align_fuzz (dest, seq, length_unit_log, ~0)); |
| } |
| } |
| |
| /* Compute branch alignments based on CFG profile. */ |
| |
| unsigned int |
| compute_alignments (void) |
| { |
| basic_block bb; |
| align_flags max_alignment; |
| |
| label_align.truncate (0); |
| |
| max_labelno = max_label_num (); |
| min_labelno = get_first_label_num (); |
| label_align.safe_grow_cleared (max_labelno - min_labelno + 1, true); |
| |
| /* If not optimizing or optimizing for size, don't assign any alignments. */ |
| if (! optimize || optimize_function_for_size_p (cfun)) |
| return 0; |
| |
| if (dump_file) |
| { |
| dump_reg_info (dump_file); |
| dump_flow_info (dump_file, TDF_DETAILS); |
| flow_loops_dump (dump_file, NULL, 1); |
| } |
| loop_optimizer_init (AVOID_CFG_MODIFICATIONS); |
| profile_count count_threshold = cfun->cfg->count_max.apply_scale |
| (1, param_align_threshold); |
| |
| if (dump_file) |
| { |
| fprintf (dump_file, "count_max: "); |
| cfun->cfg->count_max.dump (dump_file); |
| fprintf (dump_file, "\n"); |
| } |
| FOR_EACH_BB_FN (bb, cfun) |
| { |
| rtx_insn *label = BB_HEAD (bb); |
| bool has_fallthru = 0; |
| edge e; |
| edge_iterator ei; |
| |
| if (!LABEL_P (label) |
| || optimize_bb_for_size_p (bb)) |
| { |
| if (dump_file) |
| fprintf (dump_file, |
| "BB %4i loop %2i loop_depth %2i skipped.\n", |
| bb->index, |
| bb->loop_father->num, |
| bb_loop_depth (bb)); |
| continue; |
| } |
| max_alignment = LABEL_ALIGN (label); |
| profile_count fallthru_count = profile_count::zero (); |
| profile_count branch_count = profile_count::zero (); |
| |
| FOR_EACH_EDGE (e, ei, bb->preds) |
| { |
| if (e->flags & EDGE_FALLTHRU) |
| has_fallthru = 1, fallthru_count += e->count (); |
| else |
| branch_count += e->count (); |
| } |
| if (dump_file) |
| { |
| fprintf (dump_file, "BB %4i loop %2i loop_depth" |
| " %2i fall ", |
| bb->index, bb->loop_father->num, |
| bb_loop_depth (bb)); |
| fallthru_count.dump (dump_file); |
| fprintf (dump_file, " branch "); |
| branch_count.dump (dump_file); |
| if (!bb->loop_father->inner && bb->loop_father->num) |
| fprintf (dump_file, " inner_loop"); |
| if (bb->loop_father->header == bb) |
| fprintf (dump_file, " loop_header"); |
| fprintf (dump_file, "\n"); |
| } |
| if (!fallthru_count.initialized_p () || !branch_count.initialized_p ()) |
| continue; |
| |
| /* There are two purposes to align block with no fallthru incoming edge: |
| 1) to avoid fetch stalls when branch destination is near cache boundary |
| 2) to improve cache efficiency in case the previous block is not executed |
| (so it does not need to be in the cache). |
| |
| We to catch first case, we align frequently executed blocks. |
| To catch the second, we align blocks that are executed more frequently |
| than the predecessor and the predecessor is likely to not be executed |
| when function is called. */ |
| |
| if (!has_fallthru |
| && (branch_count > count_threshold |
| || (bb->count > bb->prev_bb->count.apply_scale (10, 1) |
| && (bb->prev_bb->count |
| <= ENTRY_BLOCK_PTR_FOR_FN (cfun) |
| ->count.apply_scale (1, 2))))) |
| { |
| align_flags alignment = JUMP_ALIGN (label); |
| if (dump_file) |
| fprintf (dump_file, " jump alignment added.\n"); |
| max_alignment = align_flags::max (max_alignment, alignment); |
| } |
| /* In case block is frequent and reached mostly by non-fallthru edge, |
| align it. It is most likely a first block of loop. */ |
| if (has_fallthru |
| && !(single_succ_p (bb) |
| && single_succ (bb) == EXIT_BLOCK_PTR_FOR_FN (cfun)) |
| && optimize_bb_for_speed_p (bb) |
| && branch_count + fallthru_count > count_threshold |
| && (branch_count |
| > fallthru_count.apply_scale |
| (param_align_loop_iterations, 1))) |
| { |
| align_flags alignment = LOOP_ALIGN (label); |
| if (dump_file) |
| fprintf (dump_file, " internal loop alignment added.\n"); |
| max_alignment = align_flags::max (max_alignment, alignment); |
| } |
| LABEL_TO_ALIGNMENT (label) = max_alignment; |
| } |
| |
| loop_optimizer_finalize (); |
| free_dominance_info (CDI_DOMINATORS); |
| return 0; |
| } |
| |
| /* Grow the LABEL_ALIGN array after new labels are created. */ |
| |
| static void |
| grow_label_align (void) |
| { |
| int old = max_labelno; |
| int n_labels; |
| int n_old_labels; |
| |
| max_labelno = max_label_num (); |
| |
| n_labels = max_labelno - min_labelno + 1; |
| n_old_labels = old - min_labelno + 1; |
| |
| label_align.safe_grow_cleared (n_labels, true); |
| |
| /* Range of labels grows monotonically in the function. Failing here |
| means that the initialization of array got lost. */ |
| gcc_assert (n_old_labels <= n_labels); |
| } |
| |
| /* Update the already computed alignment information. LABEL_PAIRS is a vector |
| made up of pairs of labels for which the alignment information of the first |
| element will be copied from that of the second element. */ |
| |
| void |
| update_alignments (vec<rtx> &label_pairs) |
| { |
| unsigned int i = 0; |
| rtx iter, label = NULL_RTX; |
| |
| if (max_labelno != max_label_num ()) |
| grow_label_align (); |
| |
| FOR_EACH_VEC_ELT (label_pairs, i, iter) |
| if (i & 1) |
| LABEL_TO_ALIGNMENT (label) = LABEL_TO_ALIGNMENT (iter); |
| else |
| label = iter; |
| } |
| |
| namespace { |
| |
| const pass_data pass_data_compute_alignments = |
| { |
| RTL_PASS, /* type */ |
| "alignments", /* name */ |
| OPTGROUP_NONE, /* optinfo_flags */ |
| TV_NONE, /* tv_id */ |
| 0, /* properties_required */ |
| 0, /* properties_provided */ |
| 0, /* properties_destroyed */ |
| 0, /* todo_flags_start */ |
| 0, /* todo_flags_finish */ |
| }; |
| |
| class pass_compute_alignments : public rtl_opt_pass |
| { |
| public: |
| pass_compute_alignments (gcc::context *ctxt) |
| : rtl_opt_pass (pass_data_compute_alignments, ctxt) |
| {} |
| |
| /* opt_pass methods: */ |
| virtual unsigned int execute (function *) { return compute_alignments (); } |
| |
| }; // class pass_compute_alignments |
| |
| } // anon namespace |
| |
| rtl_opt_pass * |
| make_pass_compute_alignments (gcc::context *ctxt) |
| { |
| return new pass_compute_alignments (ctxt); |
| } |
| |
| |
| /* Make a pass over all insns and compute their actual lengths by shortening |
| any branches of variable length if possible. */ |
| |
| /* shorten_branches might be called multiple times: for example, the SH |
| port splits out-of-range conditional branches in MACHINE_DEPENDENT_REORG. |
| In order to do this, it needs proper length information, which it obtains |
| by calling shorten_branches. This cannot be collapsed with |
| shorten_branches itself into a single pass unless we also want to integrate |
| reorg.c, since the branch splitting exposes new instructions with delay |
| slots. */ |
| |
| void |
| shorten_branches (rtx_insn *first) |
| { |
| rtx_insn *insn; |
| int max_uid; |
| int i; |
| rtx_insn *seq; |
| int something_changed = 1; |
| char *varying_length; |
| rtx body; |
| int uid; |
| rtx align_tab[MAX_CODE_ALIGN + 1]; |
| |
| /* Compute maximum UID and allocate label_align / uid_shuid. */ |
| max_uid = get_max_uid (); |
| |
| /* Free uid_shuid before reallocating it. */ |
| free (uid_shuid); |
| |
| uid_shuid = XNEWVEC (int, max_uid); |
| |
| if (max_labelno != max_label_num ()) |
| grow_label_align (); |
| |
| /* Initialize label_align and set up uid_shuid to be strictly |
| monotonically rising with insn order. */ |
| /* We use alignment here to keep track of the maximum alignment we want to |
| impose on the next CODE_LABEL (or the current one if we are processing |
| the CODE_LABEL itself). */ |
| |
| align_flags max_alignment; |
| |
| for (insn = get_insns (), i = 1; insn; insn = NEXT_INSN (insn)) |
| { |
| INSN_SHUID (insn) = i++; |
| if (INSN_P (insn)) |
| continue; |
| |
| if (rtx_code_label *label = dyn_cast <rtx_code_label *> (insn)) |
| { |
| /* Merge in alignments computed by compute_alignments. */ |
| align_flags alignment = LABEL_TO_ALIGNMENT (label); |
| max_alignment = align_flags::max (max_alignment, alignment); |
| |
| rtx_jump_table_data *table = jump_table_for_label (label); |
| if (!table) |
| { |
| align_flags alignment = LABEL_ALIGN (label); |
| max_alignment = align_flags::max (max_alignment, alignment); |
| } |
| /* ADDR_VECs only take room if read-only data goes into the text |
| section. */ |
| if ((JUMP_TABLES_IN_TEXT_SECTION |
| || readonly_data_section == text_section) |
| && table) |
| { |
| align_flags alignment = align_flags (ADDR_VEC_ALIGN (table)); |
| max_alignment = align_flags::max (max_alignment, alignment); |
| } |
| LABEL_TO_ALIGNMENT (label) = max_alignment; |
| max_alignment = align_flags (); |
| } |
| else if (BARRIER_P (insn)) |
| { |
| rtx_insn *label; |
| |
| for (label = insn; label && ! INSN_P (label); |
| label = NEXT_INSN (label)) |
| if (LABEL_P (label)) |
| { |
| align_flags alignment |
| = align_flags (LABEL_ALIGN_AFTER_BARRIER (insn)); |
| max_alignment = align_flags::max (max_alignment, alignment); |
| break; |
| } |
| } |
| } |
| if (!HAVE_ATTR_length) |
| return; |
| |
| /* Allocate the rest of the arrays. */ |
| insn_lengths = XNEWVEC (int, max_uid); |
| insn_lengths_max_uid = max_uid; |
| /* Syntax errors can lead to labels being outside of the main insn stream. |
| Initialize insn_addresses, so that we get reproducible results. */ |
| INSN_ADDRESSES_ALLOC (max_uid); |
| |
| varying_length = XCNEWVEC (char, max_uid); |
| |
| /* Initialize uid_align. We scan instructions |
| from end to start, and keep in align_tab[n] the last seen insn |
| that does an alignment of at least n+1, i.e. the successor |
| in the alignment chain for an insn that does / has a known |
| alignment of n. */ |
| uid_align = XCNEWVEC (rtx, max_uid); |
| |
| for (i = MAX_CODE_ALIGN + 1; --i >= 0;) |
| align_tab[i] = NULL_RTX; |
| seq = get_last_insn (); |
| for (; seq; seq = PREV_INSN (seq)) |
| { |
| int uid = INSN_UID (seq); |
| int log; |
| log = (LABEL_P (seq) ? LABEL_TO_ALIGNMENT (seq).levels[0].log : 0); |
| uid_align[uid] = align_tab[0]; |
| if (log) |
| { |
| /* Found an alignment label. */ |
| gcc_checking_assert (log < MAX_CODE_ALIGN + 1); |
| uid_align[uid] = align_tab[log]; |
| for (i = log - 1; i >= 0; i--) |
| align_tab[i] = seq; |
| } |
| } |
| |
| /* When optimizing, we start assuming minimum length, and keep increasing |
| lengths as we find the need for this, till nothing changes. |
| When not optimizing, we start assuming maximum lengths, and |
| do a single pass to update the lengths. */ |
| bool increasing = optimize != 0; |
| |
| #ifdef CASE_VECTOR_SHORTEN_MODE |
| if (optimize) |
| { |
| /* Look for ADDR_DIFF_VECs, and initialize their minimum and maximum |
| label fields. */ |
| |
| int min_shuid = INSN_SHUID (get_insns ()) - 1; |
| int max_shuid = INSN_SHUID (get_last_insn ()) + 1; |
| int rel; |
| |
| for (insn = first; insn != 0; insn = NEXT_INSN (insn)) |
| { |
| rtx min_lab = NULL_RTX, max_lab = NULL_RTX, pat; |
| int len, i, min, max, insn_shuid; |
| int min_align; |
| addr_diff_vec_flags flags; |
| |
| if (! JUMP_TABLE_DATA_P (insn) |
| || GET_CODE (PATTERN (insn)) != ADDR_DIFF_VEC) |
| continue; |
| pat = PATTERN (insn); |
| len = XVECLEN (pat, 1); |
| gcc_assert (len > 0); |
| min_align = MAX_CODE_ALIGN; |
| for (min = max_shuid, max = min_shuid, i = len - 1; i >= 0; i--) |
| { |
| rtx lab = XEXP (XVECEXP (pat, 1, i), 0); |
| int shuid = INSN_SHUID (lab); |
| if (shuid < min) |
| { |
| min = shuid; |
| min_lab = lab; |
| } |
| if (shuid > max) |
| { |
| max = shuid; |
| max_lab = lab; |
| } |
| |
| int label_alignment = LABEL_TO_ALIGNMENT (lab).levels[0].log; |
| if (min_align > label_alignment) |
| min_align = label_alignment; |
| } |
| XEXP (pat, 2) = gen_rtx_LABEL_REF (Pmode, min_lab); |
| XEXP (pat, 3) = gen_rtx_LABEL_REF (Pmode, max_lab); |
| insn_shuid = INSN_SHUID (insn); |
| rel = INSN_SHUID (XEXP (XEXP (pat, 0), 0)); |
| memset (&flags, 0, sizeof (flags)); |
| flags.min_align = min_align; |
| flags.base_after_vec = rel > insn_shuid; |
| flags.min_after_vec = min > insn_shuid; |
| flags.max_after_vec = max > insn_shuid; |
| flags.min_after_base = min > rel; |
| flags.max_after_base = max > rel; |
| ADDR_DIFF_VEC_FLAGS (pat) = flags; |
| |
| if (increasing) |
| PUT_MODE (pat, CASE_VECTOR_SHORTEN_MODE (0, 0, pat)); |
| } |
| } |
| #endif /* CASE_VECTOR_SHORTEN_MODE */ |
| |
| /* Compute initial lengths, addresses, and varying flags for each insn. */ |
| int (*length_fun) (rtx_insn *) = increasing ? insn_min_length : insn_default_length; |
| |
| for (insn_current_address = 0, insn = first; |
| insn != 0; |
| insn_current_address += insn_lengths[uid], insn = NEXT_INSN (insn)) |
| { |
| uid = INSN_UID (insn); |
| |
| insn_lengths[uid] = 0; |
| |
| if (LABEL_P (insn)) |
| { |
| int log = LABEL_TO_ALIGNMENT (insn).levels[0].log; |
| if (log) |
| { |
| int align = 1 << log; |
| int new_address = (insn_current_address + align - 1) & -align; |
| insn_lengths[uid] = new_address - insn_current_address; |
| } |
| } |
| |
| INSN_ADDRESSES (uid) = insn_current_address + insn_lengths[uid]; |
| |
| if (NOTE_P (insn) || BARRIER_P (insn) |
| || LABEL_P (insn) || DEBUG_INSN_P (insn)) |
| continue; |
| if (insn->deleted ()) |
| continue; |
| |
| body = PATTERN (insn); |
| if (rtx_jump_table_data *table = dyn_cast <rtx_jump_table_data *> (insn)) |
| { |
| /* This only takes room if read-only data goes into the text |
| section. */ |
| if (JUMP_TABLES_IN_TEXT_SECTION |
| || readonly_data_section == text_section) |
| insn_lengths[uid] = (XVECLEN (body, |
| GET_CODE (body) == ADDR_DIFF_VEC) |
| * GET_MODE_SIZE (table->get_data_mode ())); |
| /* Alignment is handled by ADDR_VEC_ALIGN. */ |
| } |
| else if (GET_CODE (body) == ASM_INPUT || asm_noperands (body) >= 0) |
| insn_lengths[uid] = asm_insn_count (body) * insn_default_length (insn); |
| else if (rtx_sequence *body_seq = dyn_cast <rtx_sequence *> (body)) |
| { |
| int i; |
| int const_delay_slots; |
| if (DELAY_SLOTS) |
| const_delay_slots = const_num_delay_slots (body_seq->insn (0)); |
| else |
| const_delay_slots = 0; |
| |
| int (*inner_length_fun) (rtx_insn *) |
| = const_delay_slots ? length_fun : insn_default_length; |
| /* Inside a delay slot sequence, we do not do any branch shortening |
| if the shortening could change the number of delay slots |
| of the branch. */ |
| for (i = 0; i < body_seq->len (); i++) |
| { |
| rtx_insn *inner_insn = body_seq->insn (i); |
| int inner_uid = INSN_UID (inner_insn); |
| int inner_length; |
| |
| if (GET_CODE (PATTERN (inner_insn)) == ASM_INPUT |
| || asm_noperands (PATTERN (inner_insn)) >= 0) |
| inner_length = (asm_insn_count (PATTERN (inner_insn)) |
| * insn_default_length (inner_insn)); |
| else |
| inner_length = inner_length_fun (inner_insn); |
| |
| insn_lengths[inner_uid] = inner_length; |
| if (const_delay_slots) |
| { |
| if ((varying_length[inner_uid] |
| = insn_variable_length_p (inner_insn)) != 0) |
| varying_length[uid] = 1; |
| INSN_ADDRESSES (inner_uid) = (insn_current_address |
| + insn_lengths[uid]); |
| } |
| else |
| varying_length[inner_uid] = 0; |
| insn_lengths[uid] += inner_length; |
| } |
| } |
| else if (GET_CODE (body) != USE && GET_CODE (body) != CLOBBER) |
| { |
| insn_lengths[uid] = length_fun (insn); |
| varying_length[uid] = insn_variable_length_p (insn); |
| } |
| |
| /* If needed, do any adjustment. */ |
| #ifdef ADJUST_INSN_LENGTH |
| ADJUST_INSN_LENGTH (insn, insn_lengths[uid]); |
| if (insn_lengths[uid] < 0) |
| fatal_insn ("negative insn length", insn); |
| #endif |
| } |
| |
| /* Now loop over all the insns finding varying length insns. For each, |
| get the current insn length. If it has changed, reflect the change. |
| When nothing changes for a full pass, we are done. */ |
| |
| while (something_changed) |
| { |
| something_changed = 0; |
| insn_current_align = MAX_CODE_ALIGN - 1; |
| for (insn_current_address = 0, insn = first; |
| insn != 0; |
| insn = NEXT_INSN (insn)) |
| { |
| int new_length; |
| #ifdef ADJUST_INSN_LENGTH |
| int tmp_length; |
| #endif |
| int length_align; |
| |
| uid = INSN_UID (insn); |
| |
| if (rtx_code_label *label = dyn_cast <rtx_code_label *> (insn)) |
| { |
| int log = LABEL_TO_ALIGNMENT (label).levels[0].log; |
| |
| #ifdef CASE_VECTOR_SHORTEN_MODE |
| /* If the mode of a following jump table was changed, we |
| may need to update the alignment of this label. */ |
| |
| if (JUMP_TABLES_IN_TEXT_SECTION |
| || readonly_data_section == text_section) |
| { |
| rtx_jump_table_data *table = jump_table_for_label (label); |
| if (table) |
| { |
| int newlog = ADDR_VEC_ALIGN (table); |
| if (newlog != log) |
| { |
| log = newlog; |
| LABEL_TO_ALIGNMENT (insn) = log; |
| something_changed = 1; |
| } |
| } |
| } |
| #endif |
| |
| if (log > insn_current_align) |
| { |
| int align = 1 << log; |
| int new_address= (insn_current_address + align - 1) & -align; |
| insn_lengths[uid] = new_address - insn_current_address; |
| insn_current_align = log; |
| insn_current_address = new_address; |
| } |
| else |
| insn_lengths[uid] = 0; |
| INSN_ADDRESSES (uid) = insn_current_address; |
| continue; |
| } |
| |
| length_align = INSN_LENGTH_ALIGNMENT (insn); |
| if (length_align < insn_current_align) |
| insn_current_align = length_align; |
| |
| insn_last_address = INSN_ADDRESSES (uid); |
| INSN_ADDRESSES (uid) = insn_current_address; |
| |
| #ifdef CASE_VECTOR_SHORTEN_MODE |
| if (optimize |
| && JUMP_TABLE_DATA_P (insn) |
| && GET_CODE (PATTERN (insn)) == ADDR_DIFF_VEC) |
| { |
| rtx_jump_table_data *table = as_a <rtx_jump_table_data *> (insn); |
| rtx body = PATTERN (insn); |
| int old_length = insn_lengths[uid]; |
| rtx_insn *rel_lab = |
| safe_as_a <rtx_insn *> (XEXP (XEXP (body, 0), 0)); |
| rtx min_lab = XEXP (XEXP (body, 2), 0); |
| rtx max_lab = XEXP (XEXP (body, 3), 0); |
| int rel_addr = INSN_ADDRESSES (INSN_UID (rel_lab)); |
| int min_addr = INSN_ADDRESSES (INSN_UID (min_lab)); |
| int max_addr = INSN_ADDRESSES (INSN_UID (max_lab)); |
| rtx_insn *prev; |
| int rel_align = 0; |
| addr_diff_vec_flags flags; |
| scalar_int_mode vec_mode; |
| |
| /* Avoid automatic aggregate initialization. */ |
| flags = ADDR_DIFF_VEC_FLAGS (body); |
| |
| /* Try to find a known alignment for rel_lab. */ |
| for (prev = rel_lab; |
| prev |
| && ! insn_lengths[INSN_UID (prev)] |
| && ! (varying_length[INSN_UID (prev)] & 1); |
| prev = PREV_INSN (prev)) |
| if (varying_length[INSN_UID (prev)] & 2) |
| { |
| rel_align = LABEL_TO_ALIGNMENT (prev).levels[0].log; |
| break; |
| } |
| |
| /* See the comment on addr_diff_vec_flags in rtl.h for the |
| meaning of the flags values. base: REL_LAB vec: INSN */ |
| /* Anything after INSN has still addresses from the last |
| pass; adjust these so that they reflect our current |
| estimate for this pass. */ |
| if (flags.base_after_vec) |
| rel_addr += insn_current_address - insn_last_address; |
| if (flags.min_after_vec) |
| min_addr += insn_current_address - insn_last_address; |
| if (flags.max_after_vec) |
| max_addr += insn_current_address - insn_last_address; |
| /* We want to know the worst case, i.e. lowest possible value |
| for the offset of MIN_LAB. If MIN_LAB is after REL_LAB, |
| its offset is positive, and we have to be wary of code shrink; |
| otherwise, it is negative, and we have to be vary of code |
| size increase. */ |
| if (flags.min_after_base) |
| { |
| /* If INSN is between REL_LAB and MIN_LAB, the size |
| changes we are about to make can change the alignment |
| within the observed offset, therefore we have to break |
| it up into two parts that are independent. */ |
| if (! flags.base_after_vec && flags.min_after_vec) |
| { |
| min_addr -= align_fuzz (rel_lab, insn, rel_align, 0); |
| min_addr -= align_fuzz (insn, min_lab, 0, 0); |
| } |
| else |
| min_addr -= align_fuzz (rel_lab, min_lab, rel_align, 0); |
| } |
| else |
| { |
| if (flags.base_after_vec && ! flags.min_after_vec) |
| { |
| min_addr -= align_fuzz (min_lab, insn, 0, ~0); |
| min_addr -= align_fuzz (insn, rel_lab, 0, ~0); |
| } |
| else |
| min_addr -= align_fuzz (min_lab, rel_lab, 0, ~0); |
| } |
| /* Likewise, determine the highest lowest possible value |
| for the offset of MAX_LAB. */ |
| if (flags.max_after_base) |
| { |
| if (! flags.base_after_vec && flags.max_after_vec) |
| { |
| max_addr += align_fuzz (rel_lab, insn, rel_align, ~0); |
| max_addr += align_fuzz (insn, max_lab, 0, ~0); |
| } |
| else |
| max_addr += align_fuzz (rel_lab, max_lab, rel_align, ~0); |
| } |
| else |
| { |
| if (flags.base_after_vec && ! flags.max_after_vec) |
| { |
| max_addr += align_fuzz (max_lab, insn, 0, 0); |
| max_addr += align_fuzz (insn, rel_lab, 0, 0); |
| } |
| else |
| max_addr += align_fuzz (max_lab, rel_lab, 0, 0); |
| } |
| vec_mode = CASE_VECTOR_SHORTEN_MODE (min_addr - rel_addr, |
| max_addr - rel_addr, body); |
| if (!increasing |
| || (GET_MODE_SIZE (vec_mode) |
| >= GET_MODE_SIZE (table->get_data_mode ()))) |
| PUT_MODE (body, vec_mode); |
| if (JUMP_TABLES_IN_TEXT_SECTION |
| || readonly_data_section == text_section) |
| { |
| insn_lengths[uid] |
| = (XVECLEN (body, 1) |
| * GET_MODE_SIZE (table->get_data_mode ())); |
| insn_current_address += insn_lengths[uid]; |
| if (insn_lengths[uid] != old_length) |
| something_changed = 1; |
| } |
| |
| continue; |
| } |
| #endif /* CASE_VECTOR_SHORTEN_MODE */ |
| |
| if (! (varying_length[uid])) |
| { |
| if (NONJUMP_INSN_P (insn) |
| && GET_CODE (PATTERN (insn)) == SEQUENCE) |
| { |
| int i; |
| |
| body = PATTERN (insn); |
| for (i = 0; i < XVECLEN (body, 0); i++) |
| { |
| rtx inner_insn = XVECEXP (body, 0, i); |
| int inner_uid = INSN_UID (inner_insn); |
| |
| INSN_ADDRESSES (inner_uid) = insn_current_address; |
| |
| insn_current_address += insn_lengths[inner_uid]; |
| } |
| } |
| else |
| insn_current_address += insn_lengths[uid]; |
| |
| continue; |
| } |
| |
| if (NONJUMP_INSN_P (insn) && GET_CODE (PATTERN (insn)) == SEQUENCE) |
| { |
| rtx_sequence *seqn = as_a <rtx_sequence *> (PATTERN (insn)); |
| int i; |
| |
| body = PATTERN (insn); |
| new_length = 0; |
| for (i = 0; i < seqn->len (); i++) |
| { |
| rtx_insn *inner_insn = seqn->insn (i); |
| int inner_uid = INSN_UID (inner_insn); |
| int inner_length; |
| |
| INSN_ADDRESSES (inner_uid) = insn_current_address; |
| |
| /* insn_current_length returns 0 for insns with a |
| non-varying length. */ |
| if (! varying_length[inner_uid]) |
| inner_length = insn_lengths[inner_uid]; |
| else |
| inner_length = insn_current_length (inner_insn); |
| |
| if (inner_length != insn_lengths[inner_uid]) |
| { |
| if (!increasing || inner_length > insn_lengths[inner_uid]) |
| { |
| insn_lengths[inner_uid] = inner_length; |
| something_changed = 1; |
| } |
| else |
| inner_length = insn_lengths[inner_uid]; |
| } |
| insn_current_address += inner_length; |
| new_length += inner_length; |
| } |
| } |
| else |
| { |
| new_length = insn_current_length (insn); |
| insn_current_address += new_length; |
| } |
| |
| #ifdef ADJUST_INSN_LENGTH |
| /* If needed, do any adjustment. */ |
| tmp_length = new_length; |
| ADJUST_INSN_LENGTH (insn, new_length); |
| insn_current_address += (new_length - tmp_length); |
| #endif |
| |
| if (new_length != insn_lengths[uid] |
| && (!increasing || new_length > insn_lengths[uid])) |
| { |
| insn_lengths[uid] = new_length; |
| something_changed = 1; |
| } |
| else |
| insn_current_address += insn_lengths[uid] - new_length; |
| } |
| /* For a non-optimizing compile, do only a single pass. */ |
| if (!increasing) |
| break; |
| } |
| crtl->max_insn_address = insn_current_address; |
| free (varying_length); |
| } |
| |
| /* Given the body of an INSN known to be generated by an ASM statement, return |
| the number of machine instructions likely to be generated for this insn. |
| This is used to compute its length. */ |
| |
| static int |
| asm_insn_count (rtx body) |
| { |
| const char *templ; |
| |
| if (GET_CODE (body) == ASM_INPUT) |
| templ = XSTR (body, 0); |
| else |
| templ = decode_asm_operands (body, NULL, NULL, NULL, NULL, NULL); |
| |
| return asm_str_count (templ); |
| } |
| |
| /* Return the number of machine instructions likely to be generated for the |
| inline-asm template. */ |
| int |
| asm_str_count (const char *templ) |
| { |
| int count = 1; |
| |
| if (!*templ) |
| return 0; |
| |
| for (; *templ; templ++) |
| if (IS_ASM_LOGICAL_LINE_SEPARATOR (*templ, templ) |
| || *templ == '\n') |
| count++; |
| |
| return count; |
| } |
| |
| /* Return true if DWARF2 debug info can be emitted for DECL. */ |
| |
| static bool |
| dwarf2_debug_info_emitted_p (tree decl) |
| { |
| /* When DWARF2 debug info is not generated internally. */ |
| if (!dwarf_debuginfo_p () && !dwarf_based_debuginfo_p ()) |
| return false; |
| |
| if (DECL_IGNORED_P (decl)) |
| return false; |
| |
| return true; |
| } |
| |
| /* Return scope resulting from combination of S1 and S2. */ |
| static tree |
| choose_inner_scope (tree s1, tree s2) |
| { |
| if (!s1) |
| return s2; |
| if (!s2) |
| return s1; |
| if (BLOCK_NUMBER (s1) > BLOCK_NUMBER (s2)) |
| return s1; |
| return s2; |
| } |
| |
| /* Emit lexical block notes needed to change scope from S1 to S2. */ |
| |
| static void |
| change_scope (rtx_insn *orig_insn, tree s1, tree s2) |
| { |
| rtx_insn *insn = orig_insn; |
| tree com = NULL_TREE; |
| tree ts1 = s1, ts2 = s2; |
| tree s; |
| |
| while (ts1 != ts2) |
| { |
| gcc_assert (ts1 && ts2); |
| if (BLOCK_NUMBER (ts1) > BLOCK_NUMBER (ts2)) |
| ts1 = BLOCK_SUPERCONTEXT (ts1); |
| else if (BLOCK_NUMBER (ts1) < BLOCK_NUMBER (ts2)) |
| ts2 = BLOCK_SUPERCONTEXT (ts2); |
| else |
| { |
| ts1 = BLOCK_SUPERCONTEXT (ts1); |
| ts2 = BLOCK_SUPERCONTEXT (ts2); |
| } |
| } |
| com = ts1; |
| |
| /* Close scopes. */ |
| s = s1; |
| while (s != com) |
| { |
| rtx_note *note = emit_note_before (NOTE_INSN_BLOCK_END, insn); |
| NOTE_BLOCK (note) = s; |
| s = BLOCK_SUPERCONTEXT (s); |
| } |
| |
| /* Open scopes. */ |
| s = s2; |
| while (s != com) |
| { |
| insn = emit_note_before (NOTE_INSN_BLOCK_BEG, insn); |
| NOTE_BLOCK (insn) = s; |
| s = BLOCK_SUPERCONTEXT (s); |
| } |
| } |
| |
| /* Rebuild all the NOTE_INSN_BLOCK_BEG and NOTE_INSN_BLOCK_END notes based |
| on the scope tree and the newly reordered instructions. */ |
| |
| static void |
| reemit_insn_block_notes (void) |
| { |
| tree cur_block = DECL_INITIAL (cfun->decl); |
| rtx_insn *insn; |
| |
| insn = get_insns (); |
| for (; insn; insn = NEXT_INSN (insn)) |
| { |
| tree this_block; |
| |
| /* Prevent lexical blocks from straddling section boundaries. */ |
| if (NOTE_P (insn)) |
| switch (NOTE_KIND (insn)) |
| { |
| case NOTE_INSN_SWITCH_TEXT_SECTIONS: |
| { |
| for (tree s = cur_block; s != DECL_INITIAL (cfun->decl); |
| s = BLOCK_SUPERCONTEXT (s)) |
| { |
| rtx_note *note = emit_note_before (NOTE_INSN_BLOCK_END, insn); |
| NOTE_BLOCK (note) = s; |
| note = emit_note_after (NOTE_INSN_BLOCK_BEG, insn); |
| NOTE_BLOCK (note) = s; |
| } |
| } |
| break; |
| |
| case NOTE_INSN_BEGIN_STMT: |
| case NOTE_INSN_INLINE_ENTRY: |
| this_block = LOCATION_BLOCK (NOTE_MARKER_LOCATION (insn)); |
| goto set_cur_block_to_this_block; |
| |
| default: |
| continue; |
| } |
| |
| if (!active_insn_p (insn)) |
| continue; |
| |
| /* Avoid putting scope notes between jump table and its label. */ |
| if (JUMP_TABLE_DATA_P (insn)) |
| continue; |
| |
| this_block = insn_scope (insn); |
| /* For sequences compute scope resulting from merging all scopes |
| of instructions nested inside. */ |
| if (rtx_sequence *body = dyn_cast <rtx_sequence *> (PATTERN (insn))) |
| { |
| int i; |
| |
| this_block = NULL; |
| for (i = 0; i < body->len (); i++) |
| this_block = choose_inner_scope (this_block, |
| insn_scope (body->insn (i))); |
| } |
| set_cur_block_to_this_block: |
| if (! this_block) |
| { |
| if (INSN_LOCATION (insn) == UNKNOWN_LOCATION) |
| continue; |
| else |
| this_block = DECL_INITIAL (cfun->decl); |
| } |
| |
| if (this_block != cur_block) |
| { |
| change_scope (insn, cur_block, this_block); |
| cur_block = this_block; |
| } |
| } |
| |
| /* change_scope emits before the insn, not after. */ |
| rtx_note *note = emit_note (NOTE_INSN_DELETED); |
| change_scope (note, cur_block, DECL_INITIAL (cfun->decl)); |
| delete_insn (note); |
| |
| reorder_blocks (); |
| } |
| |
| static const char *some_local_dynamic_name; |
| |
| /* Locate some local-dynamic symbol still in use by this function |
| so that we can print its name in local-dynamic base patterns. |
| Return null if there are no local-dynamic references. */ |
| |
| const char * |
| get_some_local_dynamic_name () |
| { |
| subrtx_iterator::array_type array; |
| rtx_insn *insn; |
| |
| if (some_local_dynamic_name) |
| return some_local_dynamic_name; |
| |
| for (insn = get_insns (); insn ; insn = NEXT_INSN (insn)) |
| if (NONDEBUG_INSN_P (insn)) |
| FOR_EACH_SUBRTX (iter, array, PATTERN (insn), ALL) |
| { |
| const_rtx x = *iter; |
| if (GET_CODE (x) == SYMBOL_REF) |
| { |
| if (SYMBOL_REF_TLS_MODEL (x) == TLS_MODEL_LOCAL_DYNAMIC) |
| return some_local_dynamic_name = XSTR (x, 0); |
| if (CONSTANT_POOL_ADDRESS_P (x)) |
| iter.substitute (get_pool_constant (x)); |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* Arrange for us to emit a source location note before any further |
| real insns or section changes, by setting the SEEN_NEXT_VIEW bit in |
| *SEEN, as long as we are keeping track of location views. The bit |
| indicates we have referenced the next view at the current PC, so we |
| have to emit it. This should be called next to the var_location |
| debug hook. */ |
| |
| static inline void |
| set_next_view_needed (int *seen) |
| { |
| if (debug_variable_location_views) |
| *seen |= SEEN_NEXT_VIEW; |
| } |
| |
| /* Clear the flag in *SEEN indicating we need to emit the next view. |
| This should be called next to the source_line debug hook. */ |
| |
| static inline void |
| clear_next_view_needed (int *seen) |
| { |
| *seen &= ~SEEN_NEXT_VIEW; |
| } |
| |
| /* Test whether we have a pending request to emit the next view in |
| *SEEN, and emit it if needed, clearing the request bit. */ |
| |
| static inline void |
| maybe_output_next_view (int *seen) |
| { |
| if ((*seen & SEEN_NEXT_VIEW) != 0) |
| { |
| clear_next_view_needed (seen); |
| (*debug_hooks->source_line) (last_linenum, last_columnnum, |
| last_filename, last_discriminator, |
| false); |
| } |
| } |
| |
| /* We want to emit param bindings (before the first begin_stmt) in the |
| initial view, if we are emitting views. To that end, we may |
| consume initial notes in the function, processing them in |
| final_start_function, before signaling the beginning of the |
| prologue, rather than in final. |
| |
| We don't test whether the DECLs are PARM_DECLs: the assumption is |
| that there will be a NOTE_INSN_BEGIN_STMT marker before any |
| non-parameter NOTE_INSN_VAR_LOCATION. It's ok if the marker is not |
| there, we'll just have more variable locations bound in the initial |
| view, which is consistent with their being bound without any code |
| that would give them a value. */ |
| |
| static inline bool |
| in_initial_view_p (rtx_insn *insn) |
| { |
| return (!DECL_IGNORED_P (current_function_decl) |
| && debug_variable_location_views |
| && insn && GET_CODE (insn) == NOTE |
| && (NOTE_KIND (insn) == NOTE_INSN_VAR_LOCATION |
| || NOTE_KIND (insn) == NOTE_INSN_DELETED)); |
| } |
| |
| /* Output assembler code for the start of a function, |
| and initialize some of the variables in this file |
| for the new function. The label for the function and associated |
| assembler pseudo-ops have already been output in `assemble_start_function'. |
| |
| FIRST is the first insn of the rtl for the function being compiled. |
| FILE is the file to write assembler code to. |
| SEEN should be initially set to zero, and it may be updated to |
| indicate we have references to the next location view, that would |
| require us to emit it at the current PC. |
| OPTIMIZE_P is nonzero if we should eliminate redundant |
| test and compare insns. */ |
| |
| static void |
| final_start_function_1 (rtx_insn **firstp, FILE *file, int *seen, |
| int optimize_p ATTRIBUTE_UNUSED) |
| { |
| block_depth = 0; |
| |
| this_is_asm_operands = 0; |
| |
| need_profile_function = false; |
| |
| last_filename = LOCATION_FILE (prologue_location); |
| last_linenum = LOCATION_LINE (prologue_location); |
| last_columnnum = LOCATION_COLUMN (prologue_location); |
| last_discriminator = discriminator = 0; |
| last_bb_discriminator = bb_discriminator = 0; |
| force_source_line = false; |
| |
| high_block_linenum = high_function_linenum = last_linenum; |
| |
| if (flag_sanitize & SANITIZE_ADDRESS) |
| asan_function_start (); |
| |
| rtx_insn *first = *firstp; |
| if (in_initial_view_p (first)) |
| { |
| do |
| { |
| final_scan_insn (first, file, 0, 0, seen); |
| first = NEXT_INSN (first); |
| } |
| while (in_initial_view_p (first)); |
| *firstp = first; |
| } |
| |
| if (!DECL_IGNORED_P (current_function_decl)) |
| debug_hooks->begin_prologue (last_linenum, last_columnnum, |
| last_filename); |
| |
| if (!dwarf2_debug_info_emitted_p (current_function_decl)) |
| dwarf2out_begin_prologue (0, 0, NULL); |
| |
| if (DECL_IGNORED_P (current_function_decl) && last_linenum && last_filename) |
| debug_hooks->set_ignored_loc (last_linenum, last_columnnum, last_filename); |
| |
| #ifdef LEAF_REG_REMAP |
| if (crtl->uses_only_leaf_regs) |
| leaf_renumber_regs (first); |
| #endif |
| |
| /* The Sun386i and perhaps other machines don't work right |
| if the profiling code comes after the prologue. */ |
| if (targetm.profile_before_prologue () && crtl->profile) |
| { |
| if (targetm.asm_out.function_prologue == default_function_pro_epilogue |
| && targetm.have_prologue ()) |
| { |
| rtx_insn *insn; |
| for (insn = first; insn; insn = NEXT_INSN (insn)) |
| if (!NOTE_P (insn)) |
| { |
| insn = NULL; |
| break; |
| } |
| else if (NOTE_KIND (insn) == NOTE_INSN_BASIC_BLOCK |
| || NOTE_KIND (insn) == NOTE_INSN_FUNCTION_BEG) |
| break; |
| else if (NOTE_KIND (insn) == NOTE_INSN_DELETED |
| || NOTE_KIND (insn) == NOTE_INSN_VAR_LOCATION) |
| continue; |
| else |
| { |
| insn = NULL; |
| break; |
| } |
| |
| if (insn) |
| need_profile_function = true; |
| else |
| profile_function (file); |
| } |
| else |
| profile_function (file); |
| } |
| |
| /* If debugging, assign block numbers to all of the blocks in this |
| function. */ |
| if (write_symbols) |
| { |
| reemit_insn_block_notes (); |
| number_blocks (current_function_decl); |
| /* We never actually put out begin/end notes for the top-level |
| block in the function. But, conceptually, that block is |
| always needed. */ |
| TREE_ASM_WRITTEN (DECL_INITIAL (current_function_decl)) = 1; |
| } |
| |
| unsigned HOST_WIDE_INT min_frame_size |
| = constant_lower_bound (get_frame_size ()); |
| if (min_frame_size > (unsigned HOST_WIDE_INT) warn_frame_larger_than_size) |
| { |
| /* Issue a warning */ |
| warning (OPT_Wframe_larger_than_, |
| "the frame size of %wu bytes is larger than %wu bytes", |
| min_frame_size, warn_frame_larger_than_size); |
| } |
| |
| /* First output the function prologue: code to set up the stack frame. */ |
| targetm.asm_out.function_prologue (file); |
| |
| /* If the machine represents the prologue as RTL, the profiling code must |
| be emitted when NOTE_INSN_PROLOGUE_END is scanned. */ |
| if (! targetm.have_prologue ()) |
| profile_after_prologue (file); |
| } |
| |
| /* This is an exported final_start_function_1, callable without SEEN. */ |
| |
| void |
| final_start_function (rtx_insn *first, FILE *file, |
| int optimize_p ATTRIBUTE_UNUSED) |
| { |
| int seen = 0; |
| final_start_function_1 (&first, file, &seen, optimize_p); |
| gcc_assert (seen == 0); |
| } |
| |
| static void |
| profile_after_prologue (FILE *file ATTRIBUTE_UNUSED) |
| { |
| if (!targetm.profile_before_prologue () && crtl->profile) |
| profile_function (file); |
| } |
| |
| static void |
| profile_function (FILE *file ATTRIBUTE_UNUSED) |
| { |
| #ifndef NO_PROFILE_COUNTERS |
| # define NO_PROFILE_COUNTERS 0 |
| #endif |
| #ifdef ASM_OUTPUT_REG_PUSH |
| rtx sval = NULL, chain = NULL; |
| |
| if (cfun->returns_struct) |
| sval = targetm.calls.struct_value_rtx (TREE_TYPE (current_function_decl), |
| true); |
| if (cfun->static_chain_decl) |
| chain = targetm.calls.static_chain (current_function_decl, true); |
| #endif /* ASM_OUTPUT_REG_PUSH */ |
| |
| if (! NO_PROFILE_COUNTERS) |
| { |
| int align = MIN (BIGGEST_ALIGNMENT, LONG_TYPE_SIZE); |
| switch_to_section (data_section); |
| ASM_OUTPUT_ALIGN (file, floor_log2 (align / BITS_PER_UNIT)); |
| targetm.asm_out.internal_label (file, "LP", current_function_funcdef_no); |
| assemble_integer (const0_rtx, LONG_TYPE_SIZE / BITS_PER_UNIT, align, 1); |
| } |
| |
| switch_to_section (current_function_section ()); |
| |
| #ifdef ASM_OUTPUT_REG_PUSH |
| if (sval && REG_P (sval)) |
| ASM_OUTPUT_REG_PUSH (file, REGNO (sval)); |
| if (chain && REG_P (chain)) |
| ASM_OUTPUT_REG_PUSH (file, REGNO (chain)); |
| #endif |
| |
| FUNCTION_PROFILER (file, current_function_funcdef_no); |
| |
| #ifdef ASM_OUTPUT_REG_PUSH |
| if (chain && REG_P (chain)) |
| ASM_OUTPUT_REG_POP (file, REGNO (chain)); |
| if (sval && REG_P (sval)) |
| ASM_OUTPUT_REG_POP (file, REGNO (sval)); |
| #endif |
| } |
| |
| /* Output assembler code for the end of a function. |
| For clarity, args are same as those of `final_start_function' |
| even though not all of them are needed. */ |
| |
| void |
| final_end_function (void) |
| { |
| app_disable (); |
| |
| if (!DECL_IGNORED_P (current_function_decl)) |
| debug_hooks->end_function (high_function_linenum); |
| |
| /* Finally, output the function epilogue: |
| code to restore the stack frame and return to the caller. */ |
| targetm.asm_out.function_epilogue (asm_out_file); |
| |
| /* And debug output. */ |
| if (!DECL_IGNORED_P (current_function_decl)) |
| debug_hooks->end_epilogue (last_linenum, last_filename); |
| |
| if (!dwarf2_debug_info_emitted_p (current_function_decl) |
| && dwarf2out_do_frame ()) |
| dwarf2out_end_epilogue (last_linenum, last_filename); |
| |
| some_local_dynamic_name = 0; |
| } |
| |
| |
| /* Dumper helper for basic block information. FILE is the assembly |
| output file, and INSN is the instruction being emitted. */ |
| |
| static void |
| dump_basic_block_info (FILE *file, rtx_insn *insn, basic_block *start_to_bb, |
| basic_block *end_to_bb, int bb_map_size, int *bb_seqn) |
| { |
| basic_block bb; |
| |
| if (!flag_debug_asm) |
| return; |
| |
| if (INSN_UID (insn) < bb_map_size |
| && (bb = start_to_bb[INSN_UID (insn)]) != NULL) |
| { |
| edge e; |
| edge_iterator ei; |
| |
| fprintf (file, "%s BLOCK %d", ASM_COMMENT_START, bb->index); |
| if (bb->count.initialized_p ()) |
| { |
| fprintf (file, ", count:"); |
| bb->count.dump (file); |
| } |
| fprintf (file, " seq:%d", (*bb_seqn)++); |
| fprintf (file, "\n%s PRED:", ASM_COMMENT_START); |
| FOR_EACH_EDGE (e, ei, bb->preds) |
| { |
| dump_edge_info (file, e, TDF_DETAILS, 0); |
| } |
| fprintf (file, "\n"); |
| } |
| if (INSN_UID (insn) < bb_map_size |
| && (bb = end_to_bb[INSN_UID (insn)]) != NULL) |
| { |
| edge e; |
| edge_iterator ei; |
| |
| fprintf (asm_out_file, "%s SUCC:", ASM_COMMENT_START); |
| FOR_EACH_EDGE (e, ei, bb->succs) |
| { |
| dump_edge_info (asm_out_file, e, TDF_DETAILS, 1); |
| } |
| fprintf (file, "\n"); |
| } |
| } |
| |
| /* Output assembler code for some insns: all or part of a function. |
| For description of args, see `final_start_function', above. */ |
| |
| static void |
| final_1 (rtx_insn *first, FILE *file, int seen, int optimize_p) |
| { |
| rtx_insn *insn, *next; |
| |
| /* Used for -dA dump. */ |
| basic_block *start_to_bb = NULL; |
| basic_block *end_to_bb = NULL; |
| int bb_map_size = 0; |
| int bb_seqn = 0; |
| |
| last_ignored_compare = 0; |
| |
| init_recog (); |
| |
| CC_STATUS_INIT; |
| |
| if (flag_debug_asm) |
| { |
| basic_block bb; |
| |
| bb_map_size = get_max_uid () + 1; |
| start_to_bb = XCNEWVEC (basic_block, bb_map_size); |
| end_to_bb = XCNEWVEC (basic_block, bb_map_size); |
| |
| /* There is no cfg for a thunk. */ |
| if (!cfun->is_thunk) |
| FOR_EACH_BB_REVERSE_FN (bb, cfun) |
| { |
| start_to_bb[INSN_UID (BB_HEAD (bb))] = bb; |
| end_to_bb[INSN_UID (BB_END (bb))] = bb; |
| } |
| } |
| |
| /* Output the insns. */ |
| for (insn = first; insn;) |
| { |
| if (HAVE_ATTR_length) |
| { |
| if ((unsigned) INSN_UID (insn) >= INSN_ADDRESSES_SIZE ()) |
| { |
| /* This can be triggered by bugs elsewhere in the compiler if |
| new insns are created after init_insn_lengths is called. */ |
| gcc_assert (NOTE_P (insn)); |
| insn_current_address = -1; |
| } |
| else |
| insn_current_address = INSN_ADDRESSES (INSN_UID (insn)); |
| /* final can be seen as an iteration of shorten_branches that |
| does nothing (since a fixed point has already been reached). */ |
| insn_last_address = insn_current_address; |
| } |
| |
| dump_basic_block_info (file, insn, start_to_bb, end_to_bb, |
| bb_map_size, &bb_seqn); |
| insn = final_scan_insn (insn, file, optimize_p, 0, &seen); |
| } |
| |
| maybe_output_next_view (&seen); |
| |
| if (flag_debug_asm) |
| { |
| free (start_to_bb); |
| free (end_to_bb); |
| } |
| |
| /* Remove CFI notes, to avoid compare-debug failures. */ |
| for (insn = first; insn; insn = next) |
| { |
| next = NEXT_INSN (insn); |
| if (NOTE_P (insn) |
| && (NOTE_KIND (insn) == NOTE_INSN_CFI |
| || NOTE_KIND (insn) == NOTE_INSN_CFI_LABEL)) |
| delete_insn (insn); |
| } |
| } |
| |
| /* This is an exported final_1, callable without SEEN. */ |
| |
| void |
| final (rtx_insn *first, FILE *file, int optimize_p) |
| { |
| /* Those that use the internal final_start_function_1/final_1 API |
| skip initial debug bind notes in final_start_function_1, and pass |
| the modified FIRST to final_1. But those that use the public |
| final_start_function/final APIs, final_start_function can't move |
| FIRST because it's not passed by reference, so if they were |
| skipped there, skip them again here. */ |
| while (in_initial_view_p (first)) |
| first = NEXT_INSN (first); |
| |
| final_1 (first, file, 0, optimize_p); |
| } |
| |
| const char * |
| get_insn_template (int code, rtx_insn *insn) |
| { |
| switch (insn_data[code].output_format) |
| { |
| case INSN_OUTPUT_FORMAT_SINGLE: |
| return insn_data[code].output.single; |
| case INSN_OUTPUT_FORMAT_MULTI: |
| return insn_data[code].output.multi[which_alternative]; |
| case INSN_OUTPUT_FORMAT_FUNCTION: |
| gcc_assert (insn); |
| return (*insn_data[code].output.function) (recog_data.operand, insn); |
| |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| /* Emit the appropriate declaration for an alternate-entry-point |
| symbol represented by INSN, to FILE. INSN is a CODE_LABEL with |
| LABEL_KIND != LABEL_NORMAL. |
| |
| The case fall-through in this function is intentional. */ |
| static void |
| output_alternate_entry_point (FILE *file, rtx_insn *insn) |
| { |
| const char *name = LABEL_NAME (insn); |
| |
| switch (LABEL_KIND (insn)) |
| { |
| case LABEL_WEAK_ENTRY: |
| #ifdef ASM_WEAKEN_LABEL |
| ASM_WEAKEN_LABEL (file, name); |
| gcc_fallthrough (); |
| #endif |
| case LABEL_GLOBAL_ENTRY: |
| targetm.asm_out.globalize_label (file, name); |
| gcc_fallthrough (); |
| case LABEL_STATIC_ENTRY: |
| #ifdef ASM_OUTPUT_TYPE_DIRECTIVE |
| ASM_OUTPUT_TYPE_DIRECTIVE (file, name, "function"); |
| #endif |
| ASM_OUTPUT_LABEL (file, name); |
| break; |
| |
| case LABEL_NORMAL: |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| /* Given a CALL_INSN, find and return the nested CALL. */ |
| static rtx |
| call_from_call_insn (rtx_call_insn *insn) |
| { |
| rtx x; |
| gcc_assert (CALL_P (insn)); |
| x = PATTERN (insn); |
| |
| while (GET_CODE (x) != CALL) |
| { |
| switch (GET_CODE (x)) |
| { |
| default: |
| gcc_unreachable (); |
| case COND_EXEC: |
| x = COND_EXEC_CODE (x); |
| break; |
| case PARALLEL: |
| x = XVECEXP (x, 0, 0); |
| break; |
| case SET: |
| x = XEXP (x, 1); |
| break; |
| } |
| } |
| return x; |
| } |
| |
| /* Print a comment into the asm showing FILENAME, LINENUM, and the |
| corresponding source line, if available. */ |
| |
| static void |
| asm_show_source (const char *filename, int linenum) |
| { |
| if (!filename) |
| return; |
| |
| char_span line = location_get_source_line (filename, linenum); |
| if (!line) |
| return; |
| |
| fprintf (asm_out_file, "%s %s:%i: ", ASM_COMMENT_START, filename, linenum); |
| /* "line" is not 0-terminated, so we must use its length. */ |
| fwrite (line.get_buffer (), 1, line.length (), asm_out_file); |
| fputc ('\n', asm_out_file); |
| } |
| |
| /* Judge if an absolute jump table is relocatable. */ |
| |
| bool |
| jumptable_relocatable (void) |
| { |
| bool relocatable = false; |
| |
| if (!CASE_VECTOR_PC_RELATIVE |
| && !targetm.asm_out.generate_pic_addr_diff_vec () |
| && targetm_common.have_named_sections) |
| relocatable = targetm.asm_out.reloc_rw_mask (); |
| |
| return relocatable; |
| } |
| |
| /* The final scan for one insn, INSN. |
| Args are same as in `final', except that INSN |
| is the insn being scanned. |
| Value returned is the next insn to be scanned. |
| |
| NOPEEPHOLES is the flag to disallow peephole processing (currently |
| used for within delayed branch sequence output). |
| |
| SEEN is used to track the end of the prologue, for emitting |
| debug information. We force the emission of a line note after |
| both NOTE_INSN_PROLOGUE_END and NOTE_INSN_FUNCTION_BEG. */ |
| |
| static rtx_insn * |
| final_scan_insn_1 (rtx_insn *insn, FILE *file, int optimize_p ATTRIBUTE_UNUSED, |
| int nopeepholes ATTRIBUTE_UNUSED, int *seen) |
| { |
| rtx_insn *next; |
| rtx_jump_table_data *table; |
| |
| insn_counter++; |
| |
| /* Ignore deleted insns. These can occur when we split insns (due to a |
| template of "#") while not optimizing. */ |
| if (insn->deleted ()) |
| return NEXT_INSN (insn); |
| |
| switch (GET_CODE (insn)) |
| { |
| case NOTE: |
| switch (NOTE_KIND (insn)) |
| { |
| case NOTE_INSN_DELETED: |
| case NOTE_INSN_UPDATE_SJLJ_CONTEXT: |
| break; |
| |
| case NOTE_INSN_SWITCH_TEXT_SECTIONS: |
| maybe_output_next_view (seen); |
| |
| output_function_exception_table (0); |
| |
| if (targetm.asm_out.unwind_emit) |
| targetm.asm_out.unwind_emit (asm_out_file, insn); |
| |
| in_cold_section_p = !in_cold_section_p; |
| |
| gcc_checking_assert (in_cold_section_p); |
| if (in_cold_section_p) |
| cold_function_name |
| = clone_function_name (current_function_decl, "cold"); |
| |
| if (dwarf2out_do_frame ()) |
| { |
| dwarf2out_switch_text_section (); |
| if (!dwarf2_debug_info_emitted_p (current_function_decl) |
| && !DECL_IGNORED_P (current_function_decl)) |
| debug_hooks->switch_text_section (); |
| } |
| else if (!DECL_IGNORED_P (current_function_decl)) |
| debug_hooks->switch_text_section (); |
| if (DECL_IGNORED_P (current_function_decl) && last_linenum |
| && last_filename) |
| debug_hooks->set_ignored_loc (last_linenum, last_columnnum, |
| last_filename); |
| |
| switch_to_section (current_function_section ()); |
| targetm.asm_out.function_switched_text_sections (asm_out_file, |
| current_function_decl, |
| in_cold_section_p); |
| /* Emit a label for the split cold section. Form label name by |
| suffixing "cold" to the original function's name. */ |
| if (in_cold_section_p) |
| { |
| #ifdef ASM_DECLARE_COLD_FUNCTION_NAME |
| ASM_DECLARE_COLD_FUNCTION_NAME (asm_out_file, |
| IDENTIFIER_POINTER |
| (cold_function_name), |
| current_function_decl); |
| #else |
| ASM_OUTPUT_LABEL (asm_out_file, |
| IDENTIFIER_POINTER (cold_function_name)); |
| #endif |
| if (dwarf2out_do_frame () |
| && cfun->fde->dw_fde_second_begin != NULL) |
| ASM_OUTPUT_LABEL (asm_out_file, cfun->fde->dw_fde_second_begin); |
| } |
| break; |
| |
| case NOTE_INSN_BASIC_BLOCK: |
| if (need_profile_function) |
| { |
| profile_function (asm_out_file); |
| need_profile_function = false; |
| } |
| |
| if (targetm.asm_out.unwind_emit) |
| targetm.asm_out.unwind_emit (asm_out_file, insn); |
| |
| bb_discriminator = NOTE_BASIC_BLOCK (insn)->discriminator; |
| break; |
| |
| case NOTE_INSN_EH_REGION_BEG: |
| ASM_OUTPUT_DEBUG_LABEL (asm_out_file, "LEHB", |
| NOTE_EH_HANDLER (insn)); |
| break; |
| |
| case NOTE_INSN_EH_REGION_END: |
| ASM_OUTPUT_DEBUG_LABEL (asm_out_file, "LEHE", |
| NOTE_EH_HANDLER (insn)); |
| break; |
| |
| case NOTE_INSN_PROLOGUE_END: |
| targetm.asm_out.function_end_prologue (file); |
| profile_after_prologue (file); |
| |
| if ((*seen & (SEEN_EMITTED | SEEN_NOTE)) == SEEN_NOTE) |
| { |
| *seen |= SEEN_EMITTED; |
| force_source_line = true; |
| } |
| else |
| *seen |= SEEN_NOTE; |
| |
| break; |
| |
| case NOTE_INSN_EPILOGUE_BEG: |
| if (!DECL_IGNORED_P (current_function_decl)) |
| (*debug_hooks->begin_epilogue) (last_linenum, last_filename); |
| targetm.asm_out.function_begin_epilogue (file); |
| break; |
| |
| case NOTE_INSN_CFI: |
| dwarf2out_emit_cfi (NOTE_CFI (insn)); |
| break; |
| |
| case NOTE_INSN_CFI_LABEL: |
| ASM_OUTPUT_DEBUG_LABEL (asm_out_file, "LCFI", |
| NOTE_LABEL_NUMBER (insn)); |
| break; |
| |
| case NOTE_INSN_FUNCTION_BEG: |
| if (need_profile_function) |
| { |
| profile_function (asm_out_file); |
| need_profile_function = false; |
| } |
| |
| app_disable (); |
| if (!DECL_IGNORED_P (current_function_decl)) |
| debug_hooks->end_prologue (last_linenum, last_filename); |
| |
| if ((*seen & (SEEN_EMITTED | SEEN_NOTE)) == SEEN_NOTE) |
| { |
| *seen |= SEEN_EMITTED; |
| force_source_line = true; |
| } |
| else |
| *seen |= SEEN_NOTE; |
| |
| break; |
| |
| case NOTE_INSN_BLOCK_BEG: |
| if (debug_info_level >= DINFO_LEVEL_NORMAL |
| || dwarf_debuginfo_p () |
| || write_symbols == VMS_DEBUG) |
| { |
| int n = BLOCK_NUMBER (NOTE_BLOCK (insn)); |
| |
| app_disable (); |
| ++block_depth; |
| high_block_linenum = last_linenum; |
| |
| /* Output debugging info about the symbol-block beginning. */ |
| if (!DECL_IGNORED_P (current_function_decl)) |
| debug_hooks->begin_block (last_linenum, n); |
| |
| /* Mark this block as output. */ |
| TREE_ASM_WRITTEN (NOTE_BLOCK (insn)) = 1; |
| BLOCK_IN_COLD_SECTION_P (NOTE_BLOCK (insn)) = in_cold_section_p; |
| } |
| if (write_symbols == DBX_DEBUG) |
| { |
| location_t *locus_ptr |
| = block_nonartificial_location (NOTE_BLOCK (insn)); |
| |
| if (locus_ptr != NULL) |
| { |
| override_filename = LOCATION_FILE (*locus_ptr); |
| override_linenum = LOCATION_LINE (*locus_ptr); |
| override_columnnum = LOCATION_COLUMN (*locus_ptr); |
| override_discriminator = compute_discriminator (*locus_ptr); |
| } |
| } |
| break; |
| |
| case NOTE_INSN_BLOCK_END: |
| maybe_output_next_view (seen); |
| |
| if (debug_info_level >= DINFO_LEVEL_NORMAL |
| || dwarf_debuginfo_p () |
| || write_symbols == VMS_DEBUG) |
| { |
| int n = BLOCK_NUMBER (NOTE_BLOCK (insn)); |
| |
| app_disable (); |
| |
| /* End of a symbol-block. */ |
| --block_depth; |
| gcc_assert (block_depth >= 0); |
| |
| if (!DECL_IGNORED_P (current_function_decl)) |
| debug_hooks->end_block (high_block_linenum, n); |
| gcc_assert (BLOCK_IN_COLD_SECTION_P (NOTE_BLOCK (insn)) |
| == in_cold_section_p); |
| } |
| if (write_symbols == DBX_DEBUG) |
| { |
| tree outer_block = BLOCK_SUPERCONTEXT (NOTE_BLOCK (insn)); |
| location_t *locus_ptr |
| = block_nonartificial_location (outer_block); |
| |
| if (locus_ptr != NULL) |
| { |
| override_filename = LOCATION_FILE (*locus_ptr); |
| override_linenum = LOCATION_LINE (*locus_ptr); |
| override_columnnum = LOCATION_COLUMN (*locus_ptr); |
| override_discriminator = compute_discriminator (*locus_ptr); |
| } |
| else |
| { |
| override_filename = NULL; |
| override_linenum = 0; |
| override_columnnum = 0; |
| override_discriminator = 0; |
| } |
| } |
| break; |
| |
| case NOTE_INSN_DELETED_LABEL: |
| /* Emit the label. We may have deleted the CODE_LABEL because |
| the label could be proved to be unreachable, though still |
| referenced (in the form of having its address taken. */ |
| ASM_OUTPUT_DEBUG_LABEL (file, "L", CODE_LABEL_NUMBER (insn)); |
| break; |
| |
| case NOTE_INSN_DELETED_DEBUG_LABEL: |
| /* Similarly, but need to use different namespace for it. */ |
| if (CODE_LABEL_NUMBER (insn) != -1) |
| ASM_OUTPUT_DEBUG_LABEL (file, "LDL", CODE_LABEL_NUMBER (insn)); |
| break; |
| |
| case NOTE_INSN_VAR_LOCATION: |
| if (!DECL_IGNORED_P (current_function_decl)) |
| { |
| debug_hooks->var_location (insn); |
| set_next_view_needed (seen); |
| } |
| break; |
| |
| case NOTE_INSN_BEGIN_STMT: |
| gcc_checking_assert (cfun->debug_nonbind_markers); |
| if (!DECL_IGNORED_P (current_function_decl) |
| && notice_source_line (insn, NULL)) |
| { |
| output_source_line: |
| (*debug_hooks->source_line) (last_linenum, last_columnnum, |
| last_filename, last_discriminator, |
| true); |
| clear_next_view_needed (seen); |
| } |
| break; |
| |
| case NOTE_INSN_INLINE_ENTRY: |
| gcc_checking_assert (cfun->debug_nonbind_markers); |
| if (!DECL_IGNORED_P (current_function_decl) |
| && notice_source_line (insn, NULL)) |
| { |
| (*debug_hooks->inline_entry) (LOCATION_BLOCK |
| (NOTE_MARKER_LOCATION (insn))); |
| goto output_source_line; |
| } |
| break; |
| |
| default: |
| gcc_unreachable (); |
| break; |
| } |
| break; |
| |
| case BARRIER: |
| break; |
| |
| case CODE_LABEL: |
| /* The target port might emit labels in the output function for |
| some insn, e.g. sh.c output_branchy_insn. */ |
| if (CODE_LABEL_NUMBER (insn) <= max_labelno) |
| { |
| align_flags alignment = LABEL_TO_ALIGNMENT (insn); |
| if (alignment.levels[0].log && NEXT_INSN (insn)) |
| { |
| #ifdef ASM_OUTPUT_MAX_SKIP_ALIGN |
| /* Output both primary and secondary alignment. */ |
| ASM_OUTPUT_MAX_SKIP_ALIGN (file, alignment.levels[0].log, |
| alignment.levels[0].maxskip); |
| ASM_OUTPUT_MAX_SKIP_ALIGN (file, alignment.levels[1].log, |
| alignment.levels[1].maxskip); |
| #else |
| #ifdef ASM_OUTPUT_ALIGN_WITH_NOP |
| ASM_OUTPUT_ALIGN_WITH_NOP (file, alignment.levels[0].log); |
| #else |
| ASM_OUTPUT_ALIGN (file, alignment.levels[0].log); |
| #endif |
| #endif |
| } |
| } |
| CC_STATUS_INIT; |
| |
| if (!DECL_IGNORED_P (current_function_decl) && LABEL_NAME (insn)) |
| debug_hooks->label (as_a <rtx_code_label *> (insn)); |
| |
| app_disable (); |
| |
| /* If this label is followed by a jump-table, make sure we put |
| the label in the read-only section. Also possibly write the |
| label and jump table together. */ |
| table = jump_table_for_label (as_a <rtx_code_label *> (insn)); |
| if (table) |
| { |
| #if defined(ASM_OUTPUT_ADDR_VEC) || defined(ASM_OUTPUT_ADDR_DIFF_VEC) |
| /* In this case, the case vector is being moved by the |
| target, so don't output the label at all. Leave that |
| to the back end macros. */ |
| #else |
| if (! JUMP_TABLES_IN_TEXT_SECTION) |
| { |
| int log_align; |
| |
| switch_to_section (targetm.asm_out.function_rodata_section |
| (current_function_decl, |
| jumptable_relocatable ())); |
| |
| #ifdef ADDR_VEC_ALIGN |
| log_align = ADDR_VEC_ALIGN (table); |
| #else |
| log_align = exact_log2 (BIGGEST_ALIGNMENT / BITS_PER_UNIT); |
| #endif |
| ASM_OUTPUT_ALIGN (file, log_align); |
| } |
| else |
| switch_to_section (current_function_section ()); |
| |
| #ifdef ASM_OUTPUT_CASE_LABEL |
| ASM_OUTPUT_CASE_LABEL (file, "L", CODE_LABEL_NUMBER (insn), table); |
| #else |
| targetm.asm_out.internal_label (file, "L", CODE_LABEL_NUMBER (insn)); |
| #endif |
| #endif |
| break; |
| } |
| if (LABEL_ALT_ENTRY_P (insn)) |
| output_alternate_entry_point (file, insn); |
| else |
| targetm.asm_out.internal_label (file, "L", CODE_LABEL_NUMBER (insn)); |
| break; |
| |
| default: |
| { |
| rtx body = PATTERN (insn); |
| int insn_code_number; |
| const char *templ; |
| bool is_stmt, *is_stmt_p; |
| |
| if (MAY_HAVE_DEBUG_MARKER_INSNS && cfun->debug_nonbind_markers) |
| { |
| is_stmt = false; |
| is_stmt_p = NULL; |
| } |
| else |
| is_stmt_p = &is_stmt; |
| |
| /* Reset this early so it is correct for ASM statements. */ |
| current_insn_predicate = NULL_RTX; |
| |
| /* An INSN, JUMP_INSN or CALL_INSN. |
| First check for special kinds that recog doesn't recognize. */ |
| |
| if (GET_CODE (body) == USE /* These are just declarations. */ |
| || GET_CODE (body) == CLOBBER) |
| break; |
| |
| /* Detect insns that are really jump-tables |
| and output them as such. */ |
| |
| if (JUMP_TABLE_DATA_P (insn)) |
| { |
| #if !(defined(ASM_OUTPUT_ADDR_VEC) || defined(ASM_OUTPUT_ADDR_DIFF_VEC)) |
| int vlen, idx; |
| #endif |
| |
| if (! JUMP_TABLES_IN_TEXT_SECTION) |
| switch_to_section (targetm.asm_out.function_rodata_section |
| (current_function_decl, |
| jumptable_relocatable ())); |
| else |
| switch_to_section (current_function_section ()); |
| |
| app_disable (); |
| |
| #if defined(ASM_OUTPUT_ADDR_VEC) || defined(ASM_OUTPUT_ADDR_DIFF_VEC) |
| if (GET_CODE (body) == ADDR_VEC) |
| { |
| #ifdef ASM_OUTPUT_ADDR_VEC |
| ASM_OUTPUT_ADDR_VEC (PREV_INSN (insn), body); |
| #else |
| gcc_unreachable (); |
| #endif |
| } |
| else |
| { |
| #ifdef ASM_OUTPUT_ADDR_DIFF_VEC |
| ASM_OUTPUT_ADDR_DIFF_VEC (PREV_INSN (insn), body); |
| #else |
| gcc_unreachable (); |
| #endif |
| } |
| #else |
| vlen = XVECLEN (body, GET_CODE (body) == ADDR_DIFF_VEC); |
| for (idx = 0; idx < vlen; idx++) |
| { |
| if (GET_CODE (body) == ADDR_VEC) |
| { |
| #ifdef ASM_OUTPUT_ADDR_VEC_ELT |
| ASM_OUTPUT_ADDR_VEC_ELT |
| (file, CODE_LABEL_NUMBER (XEXP (XVECEXP (body, 0, idx), 0))); |
| #else |
| gcc_unreachable (); |
| #endif |
| } |
| else |
| { |
| #ifdef ASM_OUTPUT_ADDR_DIFF_ELT |
| ASM_OUTPUT_ADDR_DIFF_ELT |
| (file, |
| body, |
| CODE_LABEL_NUMBER (XEXP (XVECEXP (body, 1, idx), 0)), |
| CODE_LABEL_NUMBER (XEXP (XEXP (body, 0), 0))); |
| #else |
| gcc_unreachable (); |
| #endif |
| } |
| } |
| #ifdef ASM_OUTPUT_CASE_END |
| ASM_OUTPUT_CASE_END (file, |
| CODE_LABEL_NUMBER (PREV_INSN (insn)), |
| insn); |
| #endif |
| #endif |
| |
| switch_to_section (current_function_section ()); |
| |
| if (debug_variable_location_views |
| && !DECL_IGNORED_P (current_function_decl)) |
| debug_hooks->var_location (insn); |
| |
| break; |
| } |
| /* Output this line note if it is the first or the last line |
| note in a row. */ |
| if (!DECL_IGNORED_P (current_function_decl) |
| && notice_source_line (insn, is_stmt_p)) |
| { |
| if (flag_verbose_asm) |
| asm_show_source (last_filename, last_linenum); |
| (*debug_hooks->source_line) (last_linenum, last_columnnum, |
| last_filename, last_discriminator, |
| is_stmt); |
| clear_next_view_needed (seen); |
| } |
| else |
| maybe_output_next_view (seen); |
| |
| gcc_checking_assert (!DEBUG_INSN_P (insn)); |
| |
| if (GET_CODE (body) == PARALLEL |
| && GET_CODE (XVECEXP (body, 0, 0)) == ASM_INPUT) |
| body = XVECEXP (body, 0, 0); |
| |
| if (GET_CODE (body) == ASM_INPUT) |
| { |
| const char *string = XSTR (body, 0); |
| |
| /* There's no telling what that did to the condition codes. */ |
| CC_STATUS_INIT; |
| |
| if (string[0]) |
| { |
| expanded_location loc; |
| |
| app_enable (); |
| loc = expand_location (ASM_INPUT_SOURCE_LOCATION (body)); |
| if (*loc.file && loc.line) |
| fprintf (asm_out_file, "%s %i \"%s\" 1\n", |
| ASM_COMMENT_START, loc.line, loc.file); |
| fprintf (asm_out_file, "\t%s\n", string); |
| #if HAVE_AS_LINE_ZERO |
| if (*loc.file && loc.line) |
| fprintf (asm_out_file, "%s 0 \"\" 2\n", ASM_COMMENT_START); |
| #endif |
| } |
| break; |
| } |
| |
| /* Detect `asm' construct with operands. */ |
| if (asm_noperands (body) >= 0) |
| { |
| unsigned int noperands = asm_noperands (body); |
| rtx *ops = XALLOCAVEC (rtx, noperands); |
| const char *string; |
| location_t loc; |
| expanded_location expanded; |
| |
| /* There's no telling what that did to the condition codes. */ |
| CC_STATUS_INIT; |
| |
| /* Get out the operand values. */ |
| string = decode_asm_operands (body, ops, NULL, NULL, NULL, &loc); |
| /* Inhibit dying on what would otherwise be compiler bugs. */ |
| insn_noperands = noperands; |
| this_is_asm_operands = insn; |
| expanded = expand_location (loc); |
| |
| #ifdef FINAL_PRESCAN_INSN |
| FINAL_PRESCAN_INSN (insn, ops, insn_noperands); |
| #endif |
| |
| /* Output the insn using them. */ |
| if (string[0]) |
| { |
| app_enable (); |
| if (expanded.file && expanded.line) |
| fprintf (asm_out_file, "%s %i \"%s\" 1\n", |
| ASM_COMMENT_START, expanded.line, expanded.file); |
| output_asm_insn (string, ops); |
| #if HAVE_AS_LINE_ZERO |
| if (expanded.file && expanded.line) |
| fprintf (asm_out_file, "%s 0 \"\" 2\n", ASM_COMMENT_START); |
| #endif |
| } |
| |
| if (targetm.asm_out.final_postscan_insn) |
| targetm.asm_out.final_postscan_insn (file, insn, ops, |
| insn_noperands); |
| |
| this_is_asm_operands = 0; |
| break; |
| } |
| |
| app_disable (); |
| |
| if (rtx_sequence *seq = dyn_cast <rtx_sequence *> (body)) |
| { |
| /* A delayed-branch sequence */ |
| int i; |
| |
| final_sequence = seq; |
| |
| /* The first insn in this SEQUENCE might be a JUMP_INSN that will |
| force the restoration of a comparison that was previously |
| thought unnecessary. If that happens, cancel this sequence |
| and cause that insn to be restored. */ |
| |
| next = final_scan_insn (seq->insn (0), file, 0, 1, seen); |
| if (next != seq->insn (1)) |
| { |
| final_sequence = 0; |
| return next; |
| } |
| |
| for (i = 1; i < seq->len (); i++) |
| { |
| rtx_insn *insn = seq->insn (i); |
| rtx_insn *next = NEXT_INSN (insn); |
| /* We loop in case any instruction in a delay slot gets |
| split. */ |
| do |
| insn = final_scan_insn (insn, file, 0, 1, seen); |
| while (insn != next); |
| } |
| #ifdef DBR_OUTPUT_SEQEND |
| DBR_OUTPUT_SEQEND (file); |
| #endif |
| final_sequence = 0; |
| |
| /* If the insn requiring the delay slot was a CALL_INSN, the |
| insns in the delay slot are actually executed before the |
| called function. Hence we don't preserve any CC-setting |
| actions in these insns and the CC must be marked as being |
| clobbered by the function. */ |
| if (CALL_P (seq->insn (0))) |
| { |
| CC_STATUS_INIT; |
| } |
| break; |
| } |
| |
| /* We have a real machine instruction as rtl. */ |
| |
| body = PATTERN (insn); |
| |
| /* Do machine-specific peephole optimizations if desired. */ |
| |
| if (HAVE_peephole && optimize_p && !flag_no_peephole && !nopeepholes) |
| { |
| rtx_insn *next = peephole (insn); |
| /* When peepholing, if there were notes within the peephole, |
| emit them before the peephole. */ |
| if (next != 0 && next != NEXT_INSN (insn)) |
| { |
| rtx_insn *note, *prev = PREV_INSN (insn); |
| |
| for (note = NEXT_INSN (insn); note != next; |
| note = NEXT_INSN (note)) |
| final_scan_insn (note, file, optimize_p, nopeepholes, seen); |
| |
| /* Put the notes in the proper position for a later |
| rescan. For example, the SH target can do this |
| when generating a far jump in a delayed branch |
| sequence. */ |
| note = NEXT_INSN (insn); |
| SET_PREV_INSN (note) = prev; |
| SET_NEXT_INSN (prev) = note; |
| SET_NEXT_INSN (PREV_INSN (next)) = insn; |
| SET_PREV_INSN (insn) = PREV_INSN (next); |
| SET_NEXT_INSN (insn) = next; |
| SET_PREV_INSN (next) = insn; |
| } |
| |
| /* PEEPHOLE might have changed this. */ |
| body = PATTERN (insn); |
| } |
| |
| /* Try to recognize the instruction. |
| If successful, verify that the operands satisfy the |
| constraints for the instruction. Crash if they don't, |
| since `reload' should have changed them so that they do. */ |
| |
| insn_code_number = recog_memoized (insn); |
| cleanup_subreg_operands (insn); |
| |
| /* Dump the insn in the assembly for debugging (-dAP). |
| If the final dump is requested as slim RTL, dump slim |
| RTL to the assembly file also. */ |
| if (flag_dump_rtl_in_asm) |
| { |
| print_rtx_head = ASM_COMMENT_START; |
| if (! (dump_flags & TDF_SLIM)) |
| print_rtl_single (asm_out_file, insn); |
| else |
| dump_insn_slim (asm_out_file, insn); |
| print_rtx_head = ""; |
| } |
| |
| if (! constrain_operands_cached (insn, 1)) |
| fatal_insn_not_found (insn); |
| |
| /* Some target machines need to prescan each insn before |
| it is output. */ |
| |
| #ifdef FINAL_PRESCAN_INSN |
| FINAL_PRESCAN_INSN (insn, recog_data.operand, recog_data.n_operands); |
| #endif |
| |
| if (targetm.have_conditional_execution () |
| && GET_CODE (PATTERN (insn)) == COND_EXEC) |
| current_insn_predicate = COND_EXEC_TEST (PATTERN (insn)); |
| |
| current_output_insn = debug_insn = insn; |
| |
| /* Find the proper template for this insn. */ |
| templ = get_insn_template (insn_code_number, insn); |
| |
| /* If the C code returns 0, it means that it is a jump insn |
| which follows a deleted test insn, and that test insn |
| needs to be reinserted. */ |
| if (templ == 0) |
| { |
| rtx_insn *prev; |
| |
| gcc_assert (prev_nonnote_insn (insn) == last_ignored_compare); |
| |
| /* We have already processed the notes between the setter and |
| the user. Make sure we don't process them again, this is |
| particularly important if one of the notes is a block |
| scope note or an EH note. */ |
| for (prev = insn; |
| prev != last_ignored_compare; |
| prev = PREV_INSN (prev)) |
| { |
| if (NOTE_P (prev)) |
| delete_insn (prev); /* Use delete_note. */ |
| } |
| |
| return prev; |
| } |
| |
| /* If the template is the string "#", it means that this insn must |
| be split. */ |
| if (templ[0] == '#' && templ[1] == '\0') |
| { |
| rtx_insn *new_rtx = try_split (body, insn, 0); |
| |
| /* If we didn't split the insn, go away. */ |
| if (new_rtx == insn && PATTERN (new_rtx) == body) |
| fatal_insn ("could not split insn", insn); |
| |
| /* If we have a length attribute, this instruction should have |
| been split in shorten_branches, to ensure that we would have |
| valid length info for the splitees. */ |
| gcc_assert (!HAVE_ATTR_length); |
| |
| return new_rtx; |
| } |
| |
| /* ??? This will put the directives in the wrong place if |
| get_insn_template outputs assembly directly. However calling it |
| before get_insn_template breaks if the insns is split. */ |
| if (targetm.asm_out.unwind_emit_before_insn |
| && targetm.asm_out.unwind_emit) |
| targetm.asm_out.unwind_emit (asm_out_file, insn); |
| |
| rtx_call_insn *call_insn = dyn_cast <rtx_call_insn *> (insn); |
| if (call_insn != NULL) |
| { |
| rtx x = call_from_call_insn (call_insn); |
| x = XEXP (x, 0); |
| if (x && MEM_P (x) && GET_CODE (XEXP (x, 0)) == SYMBOL_REF) |
| { |
| tree t; |
| x = XEXP (x, 0); |
| t = SYMBOL_REF_DECL (x); |
| if (t) |
| assemble_external (t); |
| } |
| } |
| |
| /* Output assembler code from the template. */ |
| output_asm_insn (templ, recog_data.operand); |
| |
| /* Some target machines need to postscan each insn after |
| it is output. */ |
| if (targetm.asm_out.final_postscan_insn) |
| targetm.asm_out.final_postscan_insn (file, insn, recog_data.operand, |
| recog_data.n_operands); |
| |
| if (!targetm.asm_out.unwind_emit_before_insn |
| && targetm.asm_out.unwind_emit) |
| targetm.asm_out.unwind_emit (asm_out_file, insn); |
| |
| /* Let the debug info back-end know about this call. We do this only |
| after the instruction has been emitted because labels that may be |
| created to reference the call instruction must appear after it. */ |
| if ((debug_variable_location_views || call_insn != NULL) |
| && !DECL_IGNORED_P (current_function_decl)) |
| debug_hooks->var_location (insn); |
| |
| current_output_insn = debug_insn = 0; |
| } |
| } |
| return NEXT_INSN (insn); |
| } |
| |
| /* This is a wrapper around final_scan_insn_1 that allows ports to |
| call it recursively without a known value for SEEN. The value is |
| saved at the outermost call, and recovered for recursive calls. |
| Recursive calls MUST pass NULL, or the same pointer if they can |
| otherwise get to it. */ |
| |
| rtx_insn * |
| final_scan_insn (rtx_insn *insn, FILE *file, int optimize_p, |
| int nopeepholes, int *seen) |
| { |
| static int *enclosing_seen; |
| static int recursion_counter; |
| |
| gcc_assert (seen || recursion_counter); |
| gcc_assert (!recursion_counter || !seen || seen == enclosing_seen); |
| |
| if (!recursion_counter++) |
| enclosing_seen = seen; |
| else if (!seen) |
| seen = enclosing_seen; |
| |
| rtx_insn *ret = final_scan_insn_1 (insn, file, optimize_p, nopeepholes, seen); |
| |
| if (!--recursion_counter) |
| enclosing_seen = NULL; |
| |
| return ret; |
| } |
| |
| |
| |
| /* Map DECLs to instance discriminators. This is allocated and |
| defined in ada/gcc-interfaces/trans.c, when compiling with -gnateS. |
| Mappings from this table are saved and restored for LTO, so |
| link-time compilation will have this map set, at least in |
| partitions containing at least one DECL with an associated instance |
| discriminator. */ |
| |
| decl_to_instance_map_t *decl_to_instance_map; |
| |
| /* Return the instance number assigned to DECL. */ |
| |
| static inline int |
| map_decl_to_instance (const_tree decl) |
| { |
| int *inst; |
| |
| if (!decl_to_instance_map || !decl || !DECL_P (decl)) |
| return 0; |
| |
| inst = decl_to_instance_map->get (decl); |
| |
| if (!inst) |
| return 0; |
| |
| return *inst; |
| } |
| |
| /* Set DISCRIMINATOR to the appropriate value, possibly derived from LOC. */ |
| |
| static inline int |
| compute_discriminator (location_t loc) |
| { |
| int discriminator; |
| |
| if (!decl_to_instance_map) |
| discriminator = bb_discriminator; |
| else |
| { |
| tree block = LOCATION_BLOCK (loc); |
| |
| while (block && TREE_CODE (block) == BLOCK |
| && !inlined_function_outer_scope_p (block)) |
| block = BLOCK_SUPERCONTEXT (block); |
| |
| tree decl; |
| |
| if (!block) |
| decl = current_function_decl; |
| else if (DECL_P (block)) |
| decl = block; |
| else |
| decl = block_ultimate_origin (block); |
| |
| discriminator = map_decl_to_instance (decl); |
| } |
| |
| return discriminator; |
| } |
| |
| /* Return whether a source line note needs to be emitted before INSN. |
| Sets IS_STMT to TRUE if the line should be marked as a possible |
| breakpoint location. */ |
| |
| static bool |
| notice_source_line (rtx_insn *insn, bool *is_stmt) |
| { |
| const char *filename; |
| int linenum, columnnum; |
| |
| if (NOTE_MARKER_P (insn)) |
| { |
| location_t loc = NOTE_MARKER_LOCATION (insn); |
| expanded_location xloc = expand_location (loc); |
| if (xloc.line == 0 |
| && (LOCATION_LOCUS (loc) == UNKNOWN_LOCATION |
| || LOCATION_LOCUS (loc) == BUILTINS_LOCATION)) |
| return false; |
| |
| filename = xloc.file; |
| linenum = xloc.line; |
| columnnum = xloc.column; |
| discriminator = compute_discriminator (loc); |
| force_source_line = true; |
| } |
| else if (override_filename) |
| { |
| filename = override_filename; |
| linenum = override_linenum; |
| columnnum = override_columnnum; |
| discriminator = override_discriminator; |
| } |
| else if (INSN_HAS_LOCATION (insn)) |
| { |
| expanded_location xloc = insn_location (insn); |
| filename = xloc.file; |
| linenum = xloc.line; |
| columnnum = xloc.column; |
| discriminator = compute_discriminator (INSN_LOCATION (insn)); |
| } |
| else |
| { |
| filename = NULL; |
| linenum = 0; |
| columnnum = 0; |
| discriminator = 0; |
| } |
| |
| if (filename == NULL) |
| return false; |
| |
| if (force_source_line |
| || filename != last_filename |
| || last_linenum != linenum |
| || (debug_column_info && last_columnnum != columnnum)) |
| { |
| force_source_line = false; |
| last_filename = filename; |
| last_linenum = linenum; |
| last_columnnum = columnnum; |
| last_discriminator = discriminator; |
| if (is_stmt) |
| *is_stmt = true; |
| high_block_linenum = MAX (last_linenum, high_block_linenum); |
| high_function_linenum = MAX (last_linenum, high_function_linenum); |
| return true; |
| } |
| |
| if (SUPPORTS_DISCRIMINATOR && last_discriminator != discriminator) |
| { |
| /* If the discriminator changed, but the line number did not, |
| output the line table entry with is_stmt false so the |
| debugger does not treat this as a breakpoint location. */ |
| last_discriminator = discriminator; |
| if (is_stmt) |
| *is_stmt = false; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /* For each operand in INSN, simplify (subreg (reg)) so that it refers |
| directly to the desired hard register. */ |
| |
| void |
| cleanup_subreg_operands (rtx_insn *insn) |
| { |
| int i; |
| bool changed = false; |
| extract_insn_cached (insn); |
| for (i = 0; i < recog_data.n_operands; i++) |
| { |
| /* The following test cannot use recog_data.operand when testing |
| for a SUBREG: the underlying object might have been changed |
| already if we are inside a match_operator expression that |
| matches the else clause. Instead we test the underlying |
| expression directly. */ |
| if (GET_CODE (*recog_data.operand_loc[i]) == SUBREG) |
| { |
| recog_data.operand[i] = alter_subreg (recog_data.operand_loc[i], true); |
| changed = true; |
| } |
| else if (GET_CODE (recog_data.operand[i]) == PLUS |
| || GET_CODE (recog_data.operand[i]) == MULT |
| || MEM_P (recog_data.operand[i])) |
| recog_data.operand[i] = walk_alter_subreg (recog_data.operand_loc[i], &changed); |
| } |
| |
| for (i = 0; i < recog_data.n_dups; i++) |
| { |
| if (GET_CODE (*recog_data.dup_loc[i]) == SUBREG) |
| { |
| *recog_data.dup_loc[i] = alter_subreg (recog_data.dup_loc[i], true); |
| changed = true; |
| } |
| else if (GET_CODE (*recog_data.dup_loc[i]) == PLUS |
| || GET_CODE (*recog_data.dup_loc[i]) == MULT |
| || MEM_P (*recog_data.dup_loc[i])) |
| *recog_data.dup_loc[i] = walk_alter_subreg (recog_data.dup_loc[i], &changed); |
| } |
| if (changed) |
| df_insn_rescan (insn); |
| } |
| |
| /* If X is a SUBREG, try to replace it with a REG or a MEM, based on |
| the thing it is a subreg of. Do it anyway if FINAL_P. */ |
| |
| rtx |
| alter_subreg (rtx *xp, bool final_p) |
| { |
| rtx x = *xp; |
| rtx y = SUBREG_REG (x); |
| |
| /* simplify_subreg does not remove subreg from volatile references. |
| We are required to. */ |
| if (MEM_P (y)) |
| { |
| poly_int64 offset = SUBREG_BYTE (x); |
| |
| /* For paradoxical subregs on big-endian machines, SUBREG_BYTE |
| contains 0 instead of the proper offset. See simplify_subreg. */ |
| if (paradoxical_subreg_p (x)) |
| offset = byte_lowpart_offset (GET_MODE (x), GET_MODE (y)); |
| |
| if (final_p) |
| *xp = adjust_address (y, GET_MODE (x), offset); |
| else |
| *xp = adjust_address_nv (y, GET_MODE (x), offset); |
| } |
| else if (REG_P (y) && HARD_REGISTER_P (y)) |
| { |
| rtx new_rtx = simplify_subreg (GET_MODE (x), y, GET_MODE (y), |
| SUBREG_BYTE (x)); |
| |
| if (new_rtx != 0) |
| *xp = new_rtx; |
| else if (final_p && REG_P (y)) |
| { |
| /* Simplify_subreg can't handle some REG cases, but we have to. */ |
| unsigned int regno; |
| poly_int64 offset; |
| |
| regno = subreg_regno (x); |
| if (subreg_lowpart_p (x)) |
| offset = byte_lowpart_offset (GET_MODE (x), GET_MODE (y)); |
| else |
| offset = SUBREG_BYTE (x); |
| *xp = gen_rtx_REG_offset (y, GET_MODE (x), regno, offset); |
| } |
| } |
| |
| return *xp; |
| } |
| |
| /* Do alter_subreg on all the SUBREGs contained in X. */ |
| |
| static rtx |
| walk_alter_subreg (rtx *xp, bool *changed) |
| { |
| rtx x = *xp; |
| switch (GET_CODE (x)) |
| { |
| case PLUS: |
| case MULT: |
| case AND: |
| XEXP (x, 0) = walk_alter_subreg (&XEXP (x, 0), changed); |
| XEXP (x, 1) = walk_alter_subreg (&XEXP (x, 1), changed); |
| break; |
| |
| case MEM: |
| case ZERO_EXTEND: |
| XEXP (x, 0) = walk_alter_subreg (&XEXP (x, 0), changed); |
| break; |
| |
| case SUBREG: |
| *changed = true; |
| return alter_subreg (xp, true); |
| |
| default: |
| break; |
| } |
| |
| return *xp; |
| } |
| |
| /* Report inconsistency between the assembler template and the operands. |
| In an `asm', it's the user's fault; otherwise, the compiler's fault. */ |
| |
| void |
| output_operand_lossage (const char *cmsgid, ...) |
| { |
| char *fmt_string; |
| char *new_message; |
| const char *pfx_str; |
| va_list ap; |
| |
| va_start (ap, cmsgid); |
| |
| pfx_str = this_is_asm_operands ? _("invalid 'asm': ") : "output_operand: "; |
| fmt_string = xasprintf ("%s%s", pfx_str, _(cmsgid)); |
| new_message = xvasprintf (fmt_string, ap); |
| |
| if (this_is_asm_operands) |
| error_for_asm (this_is_asm_operands, "%s", new_message); |
| else |
| internal_error ("%s", new_message); |
| |
| free (fmt_string); |
| free (new_message); |
| va_end (ap); |
| } |
| |
| /* Output of assembler code from a template, and its subroutines. */ |
| |
| /* Annotate the assembly with a comment describing the pattern and |
| alternative used. */ |
| |
| static void |
| output_asm_name (void) |
| { |
| if (debug_insn) |
| { |
| fprintf (asm_out_file, "\t%s %d\t", |
| ASM_COMMENT_START, INSN_UID (debug_insn)); |
| |
| fprintf (asm_out_file, "[c=%d", |
| insn_cost (debug_insn, optimize_insn_for_speed_p ())); |
| if (HAVE_ATTR_length) |
| fprintf (asm_out_file, " l=%d", |
| get_attr_length (debug_insn)); |
| fprintf (asm_out_file, "] "); |
| |
| int num = INSN_CODE (debug_insn); |
| fprintf (asm_out_file, "%s", insn_data[num].name); |
| if (insn_data[num].n_alternatives > 1) |
| fprintf (asm_out_file, "/%d", which_alternative); |
| |
| /* Clear this so only the first assembler insn |
| of any rtl insn will get the special comment for -dp. */ |
| debug_insn = 0; |
| } |
| } |
| |
| /* If OP is a REG or MEM and we can find a MEM_EXPR corresponding to it |
| or its address, return that expr . Set *PADDRESSP to 1 if the expr |
| corresponds to the address of the object and 0 if to the object. */ |
| |
| static tree |
| get_mem_expr_from_op (rtx op, int *paddressp) |
| { |
| tree expr; |
| int inner_addressp; |
| |
| *paddressp = 0; |
| |
| if (REG_P (op)) |
| return REG_EXPR (op); |
| else if (!MEM_P (op)) |
| return 0; |
| |
| if (MEM_EXPR (op) != 0) |
| return MEM_EXPR (op); |
| |
| /* Otherwise we have an address, so indicate it and look at the address. */ |
| *paddressp = 1; |
| op = XEXP (op, 0); |
| |
| /* First check if we have a decl for the address, then look at the right side |
| if it is a PLUS. Otherwise, strip off arithmetic and keep looking. |
| But don't allow the address to itself be indirect. */ |
| if ((expr = get_mem_expr_from_op (op, &inner_addressp)) && ! inner_addressp) |
| return expr; |
| else if (GET_CODE (op) == PLUS |
| && (expr = get_mem_expr_from_op (XEXP (op, 1), &inner_addressp))) |
| return expr; |
| |
| while (UNARY_P (op) |
| || GET_RTX_CLASS (GET_CODE (op)) == RTX_BIN_ARITH) |
| op = XEXP (op, 0); |
| |
| expr = get_mem_expr_from_op (op, &inner_addressp); |
| return inner_addressp ? 0 : expr; |
| } |
| |
| /* Output operand names for assembler instructions. OPERANDS is the |
| operand vector, OPORDER is the order to write the operands, and NOPS |
| is the number of operands to write. */ |
| |
| static void |
| output_asm_operand_names (rtx *operands, int *oporder, int nops) |
| { |
| int wrote = 0; |
| int i; |
| |
| for (i = 0; i < nops; i++) |
| { |
| int addressp; |
| rtx op = operands[oporder[i]]; |
| tree expr = get_mem_expr_from_op (op, &addressp); |
| |
| fprintf (asm_out_file, "%c%s", |
| wrote ? ',' : '\t', wrote ? "" : ASM_COMMENT_START); |
| wrote = 1; |
| if (expr) |
| { |
| fprintf (asm_out_file, "%s", |
| addressp ? "*" : ""); |
| print_mem_expr (asm_out_file, expr); |
| wrote = 1; |
| } |
| else if (REG_P (op) && ORIGINAL_REGNO (op) |
| && ORIGINAL_REGNO (op) != REGNO (op)) |
| fprintf (asm_out_file, " tmp%i", ORIGINAL_REGNO (op)); |
| } |
| } |
| |
| #ifdef ASSEMBLER_DIALECT |
| /* Helper function to parse assembler dialects in the asm string. |
| This is called from output_asm_insn and asm_fprintf. */ |
| static const char * |
| do_assembler_dialects (const char *p, int *dialect) |
| { |
| char c = *(p - 1); |
| |
| switch (c) |
| { |
| case '{': |
| { |
| int i; |
| |
| if (*dialect) |
| output_operand_lossage ("nested assembly dialect alternatives"); |
| else |
| *dialect = 1; |
| |
| /* If we want the first dialect, do nothing. Otherwise, skip |
| DIALECT_NUMBER of strings ending with '|'. */ |
| for (i = 0; i < dialect_number; i++) |
| { |
| while (*p && *p != '}') |
| { |
| if (*p == '|') |
| { |
| p++; |
| break; |
| } |
| |
| /* Skip over any character after a percent sign. */ |
| if (*p == '%') |
| p++; |
| if (*p) |
| p++; |
| } |
| |
| if (*p == '}') |
| break; |
| } |
| |
| if (*p == '\0') |
| output_operand_lossage ("unterminated assembly dialect alternative"); |
| } |
| break; |
| |
| case '|': |
| if (*dialect) |
| { |
| /* Skip to close brace. */ |
| do |
| { |
| if (*p == '\0') |
| { |
| output_operand_lossage ("unterminated assembly dialect alternative"); |
| break; |
| } |
| |
| /* Skip over any character after a percent sign. */ |
| if (*p == '%' && p[1]) |
| { |
| p += 2; |
| continue; |
| } |
| |
| if (*p++ == '}') |
| break; |
| } |
| while (1); |
| |
| *dialect = 0; |
| } |
| else |
| putc (c, asm_out_file); |
| break; |
| |
| case '}': |
| if (! *dialect) |
| putc (c, asm_out_file); |
| *dialect = 0; |
| break; |
| default: |
| gcc_unreachable (); |
| } |
| |
| return p; |
| } |
| #endif |
| |
| /* Output text from TEMPLATE to the assembler output file, |
| obeying %-directions to substitute operands taken from |
| the vector OPERANDS. |
| |
| %N (for N a digit) means print operand N in usual manner. |
| %lN means require operand N to be a CODE_LABEL or LABEL_REF |
| and print the label name with no punctuation. |
| %cN means require operand N to be a constant |
| and print the constant expression with no punctuation. |
| %aN means expect operand N to be a memory address |
| (not a memory reference!) and print a reference |
| to that address. |
| %nN means expect operand N to be a constant |
| and print a constant expression for minus the value |
| of the operand, with no other punctuation. */ |
| |
| void |
| output_asm_insn (const char *templ, rtx *operands) |
| { |
| const char *p; |
| int c; |
| #ifdef ASSEMBLER_DIALECT |
| int dialect = 0; |
| #endif |
| int oporder[MAX_RECOG_OPERANDS]; |
| char opoutput[MAX_RECOG_OPERANDS]; |
| int ops = 0; |
| |
| /* An insn may return a null string template |
| in a case where no assembler code is needed. */ |
| if (*templ == 0) |
| return; |
| |
| memset (opoutput, 0, sizeof opoutput); |
| p = templ; |
| putc ('\t', asm_out_file); |
| |
| #ifdef ASM_OUTPUT_OPCODE |
| ASM_OUTPUT_OPCODE (asm_out_file, p); |
| #endif |
| |
| while ((c = *p++)) |
| switch (c) |
| { |
| case '\n': |
| if (flag_verbose_asm) |
| output_asm_operand_names (operands, oporder, ops); |
| if (flag_print_asm_name) |
| output_asm_name (); |
| |
| ops = 0; |
| memset (opoutput, 0, sizeof opoutput); |
| |
| putc (c, asm_out_file); |
| #ifdef ASM_OUTPUT_OPCODE |
| while ((c = *p) == '\t') |
| { |
| putc (c, asm_out_file); |
| p++; |
| } |
| ASM_OUTPUT_OPCODE (asm_out_file, p); |
| #endif |
| break; |
| |
| #ifdef ASSEMBLER_DIALECT |
| case '{': |
| case '}': |
| case '|': |
| p = do_assembler_dialects (p, &dialect); |
| break; |
| #endif |
| |
| case '%': |
| /* %% outputs a single %. %{, %} and %| print {, } and | respectively |
| if ASSEMBLER_DIALECT defined and these characters have a special |
| meaning as dialect delimiters.*/ |
| if (*p == '%' |
| #ifdef ASSEMBLER_DIALECT |
| || *p == '{' || *p == '}' || *p == '|' |
| #endif |
| ) |
| { |
| putc (*p, asm_out_file); |
| p++; |
| } |
| /* %= outputs a number which is unique to each insn in the entire |
| compilation. This is useful for making local labels that are |
| referred to more than once in a given insn. */ |
| else if (*p == '=') |
| { |
| p++; |
| fprintf (asm_out_file, "%d", insn_counter); |
| } |
| /* % followed by a letter and some digits |
| outputs an operand in a special way depending on the letter. |
| Letters `acln' are implemented directly. |
| Other letters are passed to `output_operand' so that |
| the TARGET_PRINT_OPERAND hook can define them. */ |
| else if (ISALPHA (*p)) |
| { |
| int letter = *p++; |
| unsigned long opnum; |
| char *endptr; |
| |
| opnum = strtoul (p, &endptr, 10); |
| |
| if (endptr == p) |
| output_operand_lossage ("operand number missing " |
| "after %%-letter"); |
| else if (this_is_asm_operands && opnum >= insn_noperands) |
| output_operand_lossage ("operand number out of range"); |
| else if (letter == 'l') |
| output_asm_label (operands[opnum]); |
| else if (letter == 'a') |
| output_address (VOIDmode, operands[opnum]); |
| else if (letter == 'c') |
| { |
| if (CONSTANT_ADDRESS_P (operands[opnum])) |
| output_addr_const (asm_out_file, operands[opnum]); |
| else |
| output_operand (operands[opnum], 'c'); |
| } |
| else if (letter == 'n') |
| { |
| if (CONST_INT_P (operands[opnum])) |
| fprintf (asm_out_file, HOST_WIDE_INT_PRINT_DEC, |
| - INTVAL (operands[opnum])); |
| else |
| { |
| putc ('-', asm_out_file); |
| output_addr_const (asm_out_file, operands[opnum]); |
| } |
| } |
| else |
| output_operand (operands[opnum], letter); |
| |
| if (!opoutput[opnum]) |
| oporder[ops++] = opnum; |
| opoutput[opnum] = 1; |
| |
| p = endptr; |
| c = *p; |
| } |
| /* % followed by a digit outputs an operand the default way. */ |
| else if (ISDIGIT (*p)) |
| { |
| unsigned long opnum; |
| char *endptr; |
| |
| opnum = strtoul (p, &endptr, 10); |
| if (this_is_asm_operands && opnum >= insn_noperands) |
| output_operand_lossage ("operand number out of range"); |
| else |
| output_operand (operands[opnum], 0); |
| |
| if (!opoutput[opnum]) |
| oporder[ops++] = opnum; |
| opoutput[opnum] = 1; |
| |
| p = endptr; |
| c = *p; |
| } |
| /* % followed by punctuation: output something for that |
| punctuation character alone, with no operand. The |
| TARGET_PRINT_OPERAND hook decides what is actually done. */ |
| else if (targetm.asm_out.print_operand_punct_valid_p ((unsigned char) *p)) |
| output_operand (NULL_RTX, *p++); |
| else |
| output_operand_lossage ("invalid %%-code"); |
| break; |
| |
| default: |
| putc (c, asm_out_file); |
| } |
| |
| /* Try to keep the asm a bit more readable. */ |
| if ((flag_verbose_asm || flag_print_asm_name) && strlen (templ) < 9) |
| putc ('\t', asm_out_file); |
| |
| /* Write out the variable names for operands, if we know them. */ |
| if (flag_verbose_asm) |
| output_asm_operand_names (operands, oporder, ops); |
| if (flag_print_asm_name) |
| output_asm_name (); |
| |
| putc ('\n', asm_out_file); |
| } |
| |
| /* Output a LABEL_REF, or a bare CODE_LABEL, as an assembler symbol. */ |
| |
| void |
| output_asm_label (rtx x) |
| { |
| char buf[256]; |
| |
| if (GET_CODE (x) == LABEL_REF) |
| x = label_ref_label (x); |
| if (LABEL_P (x) |
| || (NOTE_P (x) |
| && NOTE_KIND (x) == NOTE_INSN_DELETED_LABEL)) |
| ASM_GENERATE_INTERNAL_LABEL (buf, "L", CODE_LABEL_NUMBER (x)); |
| else |
| output_operand_lossage ("'%%l' operand isn't a label"); |
| |
| assemble_name (asm_out_file, buf); |
| } |
| |
| /* Marks SYMBOL_REFs in x as referenced through use of assemble_external. */ |
| |
| void |
| mark_symbol_refs_as_used (rtx x) |
| { |
| subrtx_iterator::array_type array; |
| FOR_EACH_SUBRTX (iter, array, x, ALL) |
| { |
| const_rtx x = *iter; |
| if (GET_CODE (x) == SYMBOL_REF) |
| if (tree t = SYMBOL_REF_DECL (x)) |
| assemble_external (t); |
| } |
| } |
| |
| /* Print operand X using machine-dependent assembler syntax. |
| CODE is a non-digit that preceded the operand-number in the % spec, |
| such as 'z' if the spec was `%z3'. CODE is 0 if there was no char |
| between the % and the digits. |
| When CODE is a non-letter, X is 0. |
| |
| The meanings of the letters are machine-dependent and controlled |
| by TARGET_PRINT_OPERAND. */ |
| |
| void |
| output_operand (rtx x, int code ATTRIBUTE_UNUSED) |
| { |
| if (x && GET_CODE (x) == SUBREG) |
| x = alter_subreg (&x, true); |
| |
| /* X must not be a pseudo reg. */ |
| if (!targetm.no_register_allocation) |
| gcc_assert (!x || !REG_P (x) || REGNO (x) < FIRST_PSEUDO_REGISTER); |
| |
| targetm.asm_out.print_operand (asm_out_file, x, code); |
| |
| if (x == NULL_RTX) |
| return; |
| |
| mark_symbol_refs_as_used (x); |
| } |
| |
| /* Print a memory reference operand for address X using |
| machine-dependent assembler syntax. */ |
| |
| void |
| output_address (machine_mode mode, rtx x) |
| { |
| bool changed = false; |
| walk_alter_subreg (&x, &changed); |
| targetm.asm_out.print_operand_address (asm_out_file, mode, x); |
| } |
| |
| /* Print an integer constant expression in assembler syntax. |
| Addition and subtraction are the only arithmetic |
| that may appear in these expressions. */ |
| |
| void |
| output_addr_const (FILE *file, rtx x) |
| { |
| char buf[256]; |
| |
| restart: |
| switch (GET_CODE (x)) |
| { |
| case PC: |
| putc ('.', file); |
| break; |
| |
| case SYMBOL_REF: |
| if (SYMBOL_REF_DECL (x)) |
| assemble_external (SYMBOL_REF_DECL (x)); |
| #ifdef ASM_OUTPUT_SYMBOL_REF |
| ASM_OUTPUT_SYMBOL_REF (file, x); |
| #else |
| assemble_name (file, XSTR (x, 0)); |
| #endif |
| break; |
| |
| case LABEL_REF: |
| x = label_ref_label (x); |
| /* Fall through. */ |
| case CODE_LABEL: |
| ASM_GENERATE_INTERNAL_LABEL (buf, "L", CODE_LABEL_NUMBER (x)); |
| #ifdef ASM_OUTPUT_LABEL_REF |
| ASM_OUTPUT_LABEL_REF (file, buf); |
| #else |
| assemble_name (file, buf); |
| #endif |
| break; |
| |
| case CONST_INT: |
| fprintf (file, HOST_WIDE_INT_PRINT_DEC, INTVAL (x)); |
| break; |
| |
| case CONST: |
| /* This used to output parentheses around the expression, |
| but that does not work on the 386 (either ATT or BSD assembler). */ |
| output_addr_const (file, XEXP (x, 0)); |
| break; |
| |
| case CONST_WIDE_INT: |
| /* We do not know the mode here so we have to use a round about |
| way to build a wide-int to get it printed properly. */ |
| { |
| wide_int w = wide_int::from_array (&CONST_WIDE_INT_ELT (x, 0), |
| CONST_WIDE_INT_NUNITS (x), |
| CONST_WIDE_INT_NUNITS (x) |
| * HOST_BITS_PER_WIDE_INT, |
| false); |
| print_decs (w, file); |
| } |
| break; |
| |
| case CONST_DOUBLE: |
| if (CONST_DOUBLE_AS_INT_P (x)) |
| { |
| /* We can use %d if the number is one word and positive. */ |
| if (CONST_DOUBLE_HIGH (x)) |
| fprintf (file, HOST_WIDE_INT_PRINT_DOUBLE_HEX, |
| (unsigned HOST_WIDE_INT) CONST_DOUBLE_HIGH (x), |
| (unsigned HOST_WIDE_INT) CONST_DOUBLE_LOW (x)); |
| else if (CONST_DOUBLE_LOW (x) < 0) |
| fprintf (file, HOST_WIDE_INT_PRINT_HEX, |
| (unsigned HOST_WIDE_INT) CONST_DOUBLE_LOW (x)); |
| else |
| fprintf (file, HOST_WIDE_INT_PRINT_DEC, CONST_DOUBLE_LOW (x)); |
| } |
| else |
| /* We can't handle floating point constants; |
| PRINT_OPERAND must handle them. */ |
| output_operand_lossage ("floating constant misused"); |
| break; |
| |
| case CONST_FIXED: |
| fprintf (file, HOST_WIDE_INT_PRINT_DEC, CONST_FIXED_VALUE_LOW (x)); |
| break; |
| |
| case PLUS: |
| /* Some assemblers need integer constants to appear last (eg masm). */ |
| if (CONST_INT_P (XEXP (x, 0))) |
| { |
| output_addr_const (file, XEXP (x, 1)); |
| if (INTVAL (XEXP (x, 0)) >= 0) |
| fprintf (file, "+"); |
| output_addr_const (file, XEXP (x, 0)); |
| } |
| else |
| { |
| output_addr_const (file, XEXP (x, 0)); |
| if (!CONST_INT_P (XEXP (x, 1)) |
| || INTVAL (XEXP (x, 1)) >= 0) |
| fprintf (file, "+"); |
| output_addr_const (file, XEXP (x, 1)); |
| } |
| break; |
| |
| case MINUS: |
| /* Avoid outputting things like x-x or x+5-x, |
| since some assemblers can't handle that. */ |
| x = simplify_subtraction (x); |
| if (GET_CODE (x) != MINUS) |
| goto restart; |
| |
| output_addr_const (file, XEXP (x, 0)); |
| fprintf (file, "-"); |
| if ((CONST_INT_P (XEXP (x, 1)) && INTVAL (XEXP (x, 1)) >= 0) |
| || GET_CODE (XEXP (x, 1)) == PC |
| || GET_CODE (XEXP (x, 1)) == SYMBOL_REF) |
| output_addr_const (file, XEXP (x, 1)); |
| else |
| { |
| fputs (targetm.asm_out.open_paren, file); |
| output_addr_const (file, XEXP (x, 1)); |
| fputs (targetm.asm_out.close_paren, file); |
| } |
| break; |
| |
| case ZERO_EXTEND: |
| case SIGN_EXTEND: |
| case SUBREG: |
| case TRUNCATE: |
| output_addr_const (file, XEXP (x, 0)); |
| break; |
| |
| default: |
| if (targetm.asm_out.output_addr_const_extra (file, x)) |
| break; |
| |
| output_operand_lossage ("invalid expression as operand"); |
| } |
| } |
| |
| /* Output a quoted string. */ |
| |
| void |
| output_quoted_string (FILE *asm_file, const char *string) |
| { |
| #ifdef OUTPUT_QUOTED_STRING |
| OUTPUT_QUOTED_STRING (asm_file, string); |
| #else |
| char c; |
| |
| putc ('\"', asm_file); |
| while ((c = *string++) != 0) |
| { |
| if (ISPRINT (c)) |
| { |
| if (c == '\"' || c == '\\') |
| putc ('\\', asm_file); |
| putc (c, asm_file); |
| } |
| else |
| fprintf (asm_file, "\\%03o", (unsigned char) c); |
| } |
| putc ('\"', asm_file); |
| #endif |
| } |
| |
| /* Write a HOST_WIDE_INT number in hex form 0x1234, fast. */ |
| |
| void |
| fprint_whex (FILE *f, unsigned HOST_WIDE_INT value) |
| { |
| char buf[2 + CHAR_BIT * sizeof (value) / 4]; |
| if (value == 0) |
| putc ('0', f); |
| else |
| { |
| char *p = buf + sizeof (buf); |
| do |
| *--p = "0123456789abcdef"[value % 16]; |
| while ((value /= 16) != 0); |
| *--p = 'x'; |
| *--p = '0'; |
| fwrite (p, 1, buf + sizeof (buf) - p, f); |
| } |
| } |
| |
| /* Internal function that prints an unsigned long in decimal in reverse. |
| The output string IS NOT null-terminated. */ |
| |
| static int |
| sprint_ul_rev (char *s, unsigned long value) |
| { |
| int i = 0; |
| do |
| { |
| s[i] = "0123456789"[value % 10]; |
| value /= 10; |
| i++; |
| /* alternate version, without modulo */ |
| /* oldval = value; */ |
| /* value /= 10; */ |
| /* s[i] = "0123456789" [oldval - 10*value]; */ |
| /* i++ */ |
| } |
| while (value != 0); |
| return i; |
| } |
| |
| /* Write an unsigned long as decimal to a file, fast. */ |
| |
| void |
| fprint_ul (FILE *f, unsigned long value) |
| { |
| /* python says: len(str(2**64)) == 20 */ |
| char s[20]; |
| int i; |
| |
| i = sprint_ul_rev (s, value); |
| |
| /* It's probably too small to bother with string reversal and fputs. */ |
| do |
| { |
| i--; |
| putc (s[i], f); |
| } |
| while (i != 0); |
| } |
| |
| /* Write an unsigned long as decimal to a string, fast. |
| s must be wide enough to not overflow, at least 21 chars. |
| Returns the length of the string (without terminating '\0'). */ |
| |
| int |
| sprint_ul (char *s, unsigned long value) |
| { |
| int len = sprint_ul_rev (s, value); |
| s[len] = '\0'; |
| |
| std::reverse (s, s + len); |
| return len; |
| } |
| |
| /* A poor man's fprintf, with the added features of %I, %R, %L, and %U. |
| %R prints the value of REGISTER_PREFIX. |
| %L prints the value of LOCAL_LABEL_PREFIX. |
| %U prints the value of USER_LABEL_PREFIX. |
| %I prints the value of IMMEDIATE_PREFIX. |
| %O runs ASM_OUTPUT_OPCODE to transform what follows in the string. |
| Also supported are %d, %i, %u, %x, %X, %o, %c, %s and %%. |
| |
| We handle alternate assembler dialects here, just like output_asm_insn. */ |
| |
| void |
| asm_fprintf (FILE *file, const char *p, ...) |
| { |
| char buf[10]; |
| char *q, c; |
| #ifdef ASSEMBLER_DIALECT |
| int dialect = 0; |
| #endif |
| va_list argptr; |
| |
| va_start (argptr, p); |
| |
| buf[0] = '%'; |
| |
| while ((c = *p++)) |
| switch (c) |
| { |
| #ifdef ASSEMBLER_DIALECT |
| case '{': |
| case '}': |
| case '|': |
| p = do_assembler_dialects (p, &dialect); |
| break; |
| #endif |
| |
| case '%': |
| c = *p++; |
| q = &buf[1]; |
| while (strchr ("-+ #0", c)) |
| { |
| *q++ = c; |
| c = *p++; |
| } |
| while (ISDIGIT (c) || c == '.') |
| { |
| *q++ = c; |
| c = *p++; |
| } |
| switch (c) |
| { |
| case '%': |
| putc ('%', file); |
| break; |
| |
| case 'd': case 'i': case 'u': |
| case 'x': case 'X': case 'o': |
| case 'c': |
| *q++ = c; |
| *q = 0; |
| fprintf (file, buf, va_arg (argptr, int)); |
| break; |
| |
| case 'w': |
| /* This is a prefix to the 'd', 'i', 'u', 'x', 'X', and |
| 'o' cases, but we do not check for those cases. It |
| means that the value is a HOST_WIDE_INT, which may be |
| either `long' or `long long'. */ |
| memcpy (q, HOST_WIDE_INT_PRINT, strlen (HOST_WIDE_INT_PRINT)); |
| q += strlen (HOST_WIDE_INT_PRINT); |
| *q++ = *p++; |
| *q = 0; |
| fprintf (file, buf, va_arg (argptr, HOST_WIDE_INT)); |
| break; |
| |
| case 'l': |
| *q++ = c; |
| #ifdef HAVE_LONG_LONG |
| if (*p == 'l') |
| { |
| *q++ = *p++; |
| *q++ = *p++; |
| *q = 0; |
| fprintf (file, buf, va_arg (argptr, long long)); |
| } |
| else |
| #endif |
| { |
| *q++ = *p++; |
| *q = 0; |
| fprintf (file, buf, va_arg (argptr, long)); |
| } |
| |
| break; |
| |
| case 's': |
| *q++ = c; |
| *q = 0; |
| fprintf (file, buf, va_arg (argptr, char *)); |
| break; |
| |
| case 'O': |
| #ifdef ASM_OUTPUT_OPCODE |
| ASM_OUTPUT_OPCODE (asm_out_file, p); |
| #endif |
| break; |
| |
| case 'R': |
| #ifdef REGISTER_PREFIX |
| fprintf (file, "%s", REGISTER_PREFIX); |
| #endif |
| break; |
| |
| case 'I': |
| #ifdef IMMEDIATE_PREFIX |
| fprintf (file, "%s", IMMEDIATE_PREFIX); |
| #endif |
| break; |
| |
| case 'L': |
| #ifdef LOCAL_LABEL_PREFIX |
| fprintf (file, "%s", LOCAL_LABEL_PREFIX); |
| #endif |
| break; |
| |
| case 'U': |
| fputs (user_label_prefix, file); |
| break; |
| |
| #ifdef ASM_FPRINTF_EXTENSIONS |
| /* Uppercase letters are reserved for general use by asm_fprintf |
| and so are not available to target specific code. In order to |
| prevent the ASM_FPRINTF_EXTENSIONS macro from using them then, |
| they are defined here. As they get turned into real extensions |
| to asm_fprintf they should be removed from this list. */ |
| case 'A': case 'B': case 'C': case 'D': case 'E': |
| case 'F': case 'G': case 'H': case 'J': case 'K': |
| case 'M': case 'N': case 'P': case 'Q': case 'S': |
| case 'T': case 'V': case 'W': case 'Y': case 'Z': |
| break; |
| |
| ASM_FPRINTF_EXTENSIONS (file, argptr, p) |
| #endif |
| default: |
| gcc_unreachable (); |
| } |
| break; |
| |
| default: |
| putc (c, file); |
| } |
| va_end (argptr); |
| } |
| |
| /* Return nonzero if this function has no function calls. */ |
| |
| int |
| leaf_function_p (void) |
| { |
| rtx_insn *insn; |
| |
| /* Ensure we walk the entire function body. */ |
| gcc_assert (!in_sequence_p ()); |
| |
| /* Some back-ends (e.g. s390) want leaf functions to stay leaf |
| functions even if they call mcount. */ |
| if (crtl->profile && !targetm.keep_leaf_when_profiled ()) |
| return 0; |
| |
| for (insn = get_insns (); insn; insn = NEXT_INSN (insn)) |
| { |
| if (CALL_P (insn) |
| && ! SIBLING_CALL_P (insn) |
| && ! FAKE_CALL_P (insn)) |
| return 0; |
| if (NONJUMP_INSN_P (insn) |
| && GET_CODE (PATTERN (insn)) == SEQUENCE |
| && CALL_P (XVECEXP (PATTERN (insn), 0, 0)) |
| && ! SIBLING_CALL_P (XVECEXP (PATTERN (insn), 0, 0))) |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| /* Return 1 if branch is a forward branch. |
| Uses insn_shuid array, so it works only in the final pass. May be used by |
| output templates to customary add branch prediction hints. |
| */ |
| int |
| final_forward_branch_p (rtx_insn *insn) |
| { |
| int insn_id, label_id; |
| |
| gcc_assert (uid_shuid); |
| insn_id = INSN_SHUID (insn); |
| label_id = INSN_SHUID (JUMP_LABEL (insn)); |
| /* We've hit some insns that does not have id information available. */ |
| gcc_assert (insn_id && label_id); |
| return insn_id < label_id; |
| } |
| |
| /* On some machines, a function with no call insns |
| can run faster if it doesn't create its own register window. |
| When output, the leaf function should use only the "output" |
| registers. Ordinarily, the function would be compiled to use |
| the "input" registers to find its arguments; it is a candidate |
| for leaf treatment if it uses only the "input" registers. |
| Leaf function treatment means renumbering so the function |
| uses the "output" registers instead. */ |
| |
| #ifdef LEAF_REGISTERS |
| |
| /* Return 1 if this function uses only the registers that can be |
| safely renumbered. */ |
| |
| int |
| only_leaf_regs_used (void) |
| { |
| int i; |
| const char *const permitted_reg_in_leaf_functions = LEAF_REGISTERS; |
| |
| for (i = 0; i < FIRST_PSEUDO_REGISTER; i++) |
| if ((df_regs_ever_live_p (i) || global_regs[i]) |
| && ! permitted_reg_in_leaf_functions[i]) |
| return 0; |
| |
| if (crtl->uses_pic_offset_table |
| && pic_offset_table_rtx != 0 |
| && REG_P (pic_offset_table_rtx) |
| && ! permitted_reg_in_leaf_functions[REGNO (pic_offset_table_rtx)]) |
| return 0; |
| |
| return 1; |
| } |
| |
| /* Scan all instructions and renumber all registers into those |
| available in leaf functions. */ |
| |
| static void |
| leaf_renumber_regs (rtx_insn *first) |
| { |
| rtx_insn *insn; |
| |
| /* Renumber only the actual patterns. |
| The reg-notes can contain frame pointer refs, |
| and renumbering them could crash, and should not be needed. */ |
| for (insn = first; insn; insn = NEXT_INSN (insn)) |
| if (INSN_P (insn)) |
| leaf_renumber_regs_insn (PATTERN (insn)); |
| } |
| |
| /* Scan IN_RTX and its subexpressions, and renumber all regs into those |
| available in leaf functions. */ |
| |
| void |
| leaf_renumber_regs_insn (rtx in_rtx) |
| { |
| int i, j; |
| const char *format_ptr; |
| |
| if (in_rtx == 0) |
| return; |
| |
| /* Renumber all input-registers into output-registers. |
| renumbered_regs would be 1 for an output-register; |
| they */ |
| |
| if (REG_P (in_rtx)) |
| { |
| int newreg; |
| |
| /* Don't renumber the same reg twice. */ |
| if (in_rtx->used) |
| return; |
| |
| newreg = REGNO (in_rtx); |
| /* Don't try to renumber pseudo regs. It is possible for a pseudo reg |
| to reach here as part of a REG_NOTE. */ |
| if (newreg >= FIRST_PSEUDO_REGISTER) |
| { |
| in_rtx->used = 1; |
| return; |
| } |
| newreg = LEAF_REG_REMAP (newreg); |
| gcc_assert (newreg >= 0); |
| df_set_regs_ever_live (REGNO (in_rtx), false); |
| df_set_regs_ever_live (newreg, true); |
| SET_REGNO (in_rtx, newreg); |
| in_rtx->used = 1; |
| return; |
| } |
| |
| if (INSN_P (in_rtx)) |
| { |
| /* Inside a SEQUENCE, we find insns. |
| Renumber just the patterns of these insns, |
| just as we do for the top-level insns. */ |
| leaf_renumber_regs_insn (PATTERN (in_rtx)); |
| return; |
| } |
| |
| format_ptr = GET_RTX_FORMAT (GET_CODE (in_rtx)); |
| |
| for (i = 0; i < GET_RTX_LENGTH (GET_CODE (in_rtx)); i++) |
| switch (*format_ptr++) |
| { |
| case 'e': |
| leaf_renumber_regs_insn (XEXP (in_rtx, i)); |
| break; |
| |
| case 'E': |
| if (XVEC (in_rtx, i) != NULL) |
| for (j = 0; j < XVECLEN (in_rtx, i); j++) |
| leaf_renumber_regs_insn (XVECEXP (in_rtx, i, j)); |
| break; |
| |
| case 'S': |
| case 's': |
| case '0': |
| case 'i': |
| case 'w': |
| case 'p': |
| case 'n': |
| case 'u': |
| break; |
| |
| default: |
| gcc_unreachable (); |
| } |
| } |
| #endif |
| |
| /* Turn the RTL into assembly. */ |
| static unsigned int |
| rest_of_handle_final (void) |
| { |
| const char *fnname = get_fnname_from_decl (current_function_decl); |
| |
| /* Turn debug markers into notes if the var-tracking pass has not |
| been invoked. */ |
| if (!flag_var_tracking && MAY_HAVE_DEBUG_MARKER_INSNS) |
| delete_vta_debug_insns (false); |
| |
| assemble_start_function (current_function_decl, fnname); |
| rtx_insn *first = get_insns (); |
| int seen = 0; |
| final_start_function_1 (&first, asm_out_file, &seen, optimize); |
| final_1 (first, asm_out_file, seen, optimize); |
| if (flag_ipa_ra |
| && !lookup_attribute ("noipa", DECL_ATTRIBUTES (current_function_decl)) |
| /* Functions with naked attributes are supported only with basic asm |
| statements in the body, thus for supported use cases the information |
| on clobbered registers is not available. */ |
| && !lookup_attribute ("naked", DECL_ATTRIBUTES (current_function_decl))) |
| collect_fn_hard_reg_usage (); |
| final_end_function (); |
| |
| /* The IA-64 ".handlerdata" directive must be issued before the ".endp" |
| directive that closes the procedure descriptor. Similarly, for x64 SEH. |
| Otherwise it's not strictly necessary, but it doesn't hurt either. */ |
| output_function_exception_table (crtl->has_bb_partition ? 1 : 0); |
| |
| assemble_end_function (current_function_decl, fnname); |
| |
| /* Free up reg info memory. */ |
| free_reg_info (); |
| |
| if (! quiet_flag) |
| fflush (asm_out_file); |
| |
| /* Write DBX symbols if requested. */ |
| |
| /* Note that for those inline functions where we don't initially |
| know for certain that we will be generating an out-of-line copy, |
| the first invocation of this routine (rest_of_compilation) will |
| skip over this code by doing a `goto exit_rest_of_compilation;'. |
| Later on, wrapup_global_declarations will (indirectly) call |
| rest_of_compilation again for those inline functions that need |
| to have out-of-line copies generated. During that call, we |
| *will* be routed past here. */ |
| |
| timevar_push (TV_SYMOUT); |
| if (!DECL_IGNORED_P (current_function_decl)) |
| debug_hooks->function_decl (current_function_decl); |
| timevar_pop (TV_SYMOUT); |
| |
| /* Release the blocks that are linked to DECL_INITIAL() to free the memory. */ |
| DECL_INITIAL (current_function_decl) = error_mark_node; |
| |
| if (DECL_STATIC_CONSTRUCTOR (current_function_decl) |
| && targetm.have_ctors_dtors) |
| targetm.asm_out.constructor (XEXP (DECL_RTL (current_function_decl), 0), |
| decl_init_priority_lookup |
| (current_function_decl)); |
| if (DECL_STATIC_DESTRUCTOR (current_function_decl) |
| && targetm.have_ctors_dtors) |
| targetm.asm_out.destructor (XEXP (DECL_RTL (current_function_decl), 0), |
| decl_fini_priority_lookup |
| (current_function_decl)); |
| return 0; |
| } |
| |
| namespace { |
| |
| const pass_data pass_data_final = |
| { |
| RTL_PASS, /* type */ |
| "final", /* name */ |
| OPTGROUP_NONE, /* optinfo_flags */ |
| TV_FINAL, /* tv_id */ |
| 0, /* properties_required */ |
| 0, /* properties_provided */ |
| 0, /* properties_destroyed */ |
| 0, /* todo_flags_start */ |
| 0, /* todo_flags_finish */ |
| }; |
| |
| class pass_final : public rtl_opt_pass |
| { |
| public: |
| pass_final (gcc::context *ctxt) |
| : rtl_opt_pass (pass_data_final, ctxt) |
| {} |
| |
| /* opt_pass methods: */ |
| virtual unsigned int execute (function *) { return rest_of_handle_final (); } |
| |
| }; // class pass_final |
| |
| } // anon namespace |
| |
| rtl_opt_pass * |
| make_pass_final (gcc::context *ctxt) |
| { |
| return new pass_final (ctxt); |
| } |
| |
| |
| static unsigned int |
| rest_of_handle_shorten_branches (void) |
| { |
| /* Shorten branches. */ |
| shorten_branches (get_insns ()); |
| return 0; |
| } |
| |
| namespace { |
| |
| const pass_data pass_data_shorten_branches = |
| { |
| RTL_PASS, /* type */ |
| "shorten", /* name */ |
| OPTGROUP_NONE, /* optinfo_flags */ |
| TV_SHORTEN_BRANCH, /* tv_id */ |
| 0, /* properties_required */ |
| 0, /* properties_provided */ |
| 0, /* properties_destroyed */ |
| 0, /* todo_flags_start */ |
| 0, /* todo_flags_finish */ |
| }; |
| |
| class pass_shorten_branches : public rtl_opt_pass |
| { |
| public: |
| pass_shorten_branches (gcc::context *ctxt) |
| : rtl_opt_pass (pass_data_shorten_branches, ctxt) |
| {} |
| |
| /* opt_pass methods: */ |
| virtual unsigned int execute (function *) |
| { |
| return rest_of_handle_shorten_branches (); |
| } |
| |
| }; // class pass_shorten_branches |
| |
| } // anon namespace |
| |
| rtl_opt_pass * |
| make_pass_shorten_branches (gcc::context *ctxt) |
| { |
| return new pass_shorten_branches (ctxt); |
| } |
| |
| |
| static unsigned int |
| rest_of_clean_state (void) |
| { |
| rtx_insn *insn, *next; |
| FILE *final_output = NULL; |
| int save_unnumbered = flag_dump_unnumbered; |
| int save_noaddr = flag_dump_noaddr; |
| |
| if (flag_dump_final_insns) |
| { |
| final_output = fopen (flag_dump_final_insns, "a"); |
| if (!final_output) |
| { |
| error ("could not open final insn dump file %qs: %m", |
| flag_dump_final_insns); |
| flag_dump_final_insns = NULL; |
| } |
| else |
| { |
| flag_dump_noaddr = flag_dump_unnumbered = 1; |
| if (flag_compare_debug_opt || flag_compare_debug) |
| dump_flags |= TDF_NOUID | TDF_COMPARE_DEBUG; |
| dump_function_header (final_output, current_function_decl, |
| dump_flags); |
| final_insns_dump_p = true; |
| |
| for (insn = get_insns (); insn; insn = NEXT_INSN (insn)) |
| if (LABEL_P (insn)) |
| INSN_UID (insn) = CODE_LABEL_NUMBER (insn); |
| else |
| { |
| if (NOTE_P (insn)) |
| set_block_for_insn (insn, NULL); |
| INSN_UID (insn) = 0; |
| } |
| } |
| } |
| |
| /* It is very important to decompose the RTL instruction chain here: |
| debug information keeps pointing into CODE_LABEL insns inside the function |
| body. If these remain pointing to the other insns, we end up preserving |
| whole RTL chain and attached detailed debug info in memory. */ |
| for (insn = get_insns (); insn; insn = next) |
| { |
| next = NEXT_INSN (insn); |
| SET_NEXT_INSN (insn) = NULL; |
| SET_PREV_INSN (insn) = NULL; |
| |
| rtx_insn *call_insn = insn; |
| if (NONJUMP_INSN_P (call_insn) |
| && GET_CODE (PATTERN (call_insn)) == SEQUENCE) |
| { |
| rtx_sequence *seq = as_a <rtx_sequence *> (PATTERN (call_insn)); |
| call_insn = seq->insn (0); |
| } |
| if (CALL_P (call_insn)) |
| { |
| rtx note |
| = find_reg_note (call_insn, REG_CALL_ARG_LOCATION, NULL_RTX); |
| if (note) |
| remove_note (call_insn, note); |
| } |
| |
| if (final_output |
| && (!NOTE_P (insn) |
| || (NOTE_KIND (insn) != NOTE_INSN_VAR_LOCATION |
| && NOTE_KIND (insn) != NOTE_INSN_BEGIN_STMT |
| && NOTE_KIND (insn) != NOTE_INSN_INLINE_ENTRY |
| && NOTE_KIND (insn) != NOTE_INSN_BLOCK_BEG |
| && NOTE_KIND (insn) != NOTE_INSN_BLOCK_END |
| && NOTE_KIND (insn) != NOTE_INSN_DELETED_DEBUG_LABEL))) |
| print_rtl_single (final_output, insn); |
| } |
| |
| if (final_output) |
| { |
| flag_dump_noaddr = save_noaddr; |
| flag_dump_unnumbered = save_unnumbered; |
| final_insns_dump_p = false; |
| |
| if (fclose (final_output)) |
| { |
| error ("could not close final insn dump file %qs: %m", |
| flag_dump_final_insns); |
| flag_dump_final_insns = NULL; |
| } |
| } |
| |
| flag_rerun_cse_after_global_opts = 0; |
| reload_completed = 0; |
| epilogue_completed = 0; |
| #ifdef STACK_REGS |
| regstack_completed = 0; |
| #endif |
| |
| /* Clear out the insn_length contents now that they are no |
| longer valid. */ |
| init_insn_lengths (); |
| |
| /* Show no temporary slots allocated. */ |
| init_temp_slots (); |
| |
| free_bb_for_insn (); |
| |
| if (cfun->gimple_df) |
| delete_tree_ssa (cfun); |
| |
| /* We can reduce stack alignment on call site only when we are sure that |
| the function body just produced will be actually used in the final |
| executable. */ |
| if (flag_ipa_stack_alignment |
| && decl_binds_to_current_def_p (current_function_decl)) |
| { |
| unsigned int pref = crtl->preferred_stack_boundary; |
| if (crtl->stack_alignment_needed > crtl->preferred_stack_boundary) |
| pref = crtl->stack_alignment_needed; |
| cgraph_node::rtl_info (current_function_decl) |
| ->preferred_incoming_stack_boundary = pref; |
| } |
| |
| /* Make sure volatile mem refs aren't considered valid operands for |
| arithmetic insns. We must call this here if this is a nested inline |
| function, since the above code leaves us in the init_recog state, |
| and the function context push/pop code does not save/restore volatile_ok. |
| |
| ??? Maybe it isn't necessary for expand_start_function to call this |
| anymore if we do it here? */ |
| |
| init_recog_no_volatile (); |
| |
| /* We're done with this function. Free up memory if we can. */ |
| free_after_parsing (cfun); |
| free_after_compilation (cfun); |
| return 0; |
| } |
| |
| namespace { |
| |
| const pass_data pass_data_clean_state = |
| { |
| RTL_PASS, /* type */ |
| "*clean_state", /* name */ |
| OPTGROUP_NONE, /* optinfo_flags */ |
| TV_FINAL, /* tv_id */ |
| 0, /* properties_required */ |
| 0, /* properties_provided */ |
| PROP_rtl, /* properties_destroyed */ |
| 0, /* todo_flags_start */ |
| 0, /* todo_flags_finish */ |
| }; |
| |
| class pass_clean_state : public rtl_opt_pass |
| { |
| public: |
| pass_clean_state (gcc::context *ctxt) |
| : rtl_opt_pass (pass_data_clean_state, ctxt) |
| {} |
| |
| /* opt_pass methods: */ |
| virtual unsigned int execute (function *) |
| { |
| return rest_of_clean_state (); |
| } |
| |
| }; // class pass_clean_state |
| |
| } // anon namespace |
| |
| rtl_opt_pass * |
| make_pass_clean_state (gcc::context *ctxt) |
| { |
| return new pass_clean_state (ctxt); |
| } |
| |
| /* Return true if INSN is a call to the current function. */ |
| |
| static bool |
| self_recursive_call_p (rtx_insn *insn) |
| { |
| tree fndecl = get_call_fndecl (insn); |
| return (fndecl == current_function_decl |
| && decl_binds_to_current_def_p (fndecl)); |
| } |
| |
| /* Collect hard register usage for the current function. */ |
| |
| static void |
| collect_fn_hard_reg_usage (void) |
| { |
| rtx_insn *insn; |
| #ifdef STACK_REGS |
| int i; |
| #endif |
| struct cgraph_rtl_info *node; |
| HARD_REG_SET function_used_regs; |
| |
| /* ??? To be removed when all the ports have been fixed. */ |
| if (!targetm.call_fusage_contains_non_callee_clobbers) |
| return; |
| |
| /* Be conservative - mark fixed and global registers as used. */ |
| function_used_regs = fixed_reg_set; |
| |
| #ifdef STACK_REGS |
| /* Handle STACK_REGS conservatively, since the df-framework does not |
| provide accurate information for them. */ |
| |
| for (i = FIRST_STACK_REG; i <= LAST_STACK_REG; i++) |
| SET_HARD_REG_BIT (function_used_regs, i); |
| #endif |
| |
| for (insn = get_insns (); insn != NULL_RTX; insn = next_insn (insn)) |
| { |
| HARD_REG_SET insn_used_regs; |
| |
| if (!NONDEBUG_INSN_P (insn)) |
| continue; |
| |
| if (CALL_P (insn) |
| && !self_recursive_call_p (insn)) |
| function_used_regs |
| |= insn_callee_abi (insn).full_and_partial_reg_clobbers (); |
| |
| find_all_hard_reg_sets (insn, &insn_used_regs, false); |
| function_used_regs |= insn_used_regs; |
| |
| if (hard_reg_set_subset_p (crtl->abi->full_and_partial_reg_clobbers (), |
| function_used_regs)) |
| return; |
| } |
| |
| /* Mask out fully-saved registers, so that they don't affect equality |
| comparisons between function_abis. */ |
| function_used_regs &= crtl->abi->full_and_partial_reg_clobbers (); |
| |
| node = cgraph_node::rtl_info (current_function_decl); |
| gcc_assert (node != NULL); |
| |
| node->function_used_regs = function_used_regs; |
| } |