On Sat, Nov 1, 2014 at 4:29 AM, Jeff Law <l...@redhat.com> wrote: > On 09/30/14 03:22, Bin Cheng wrote: > >> >> 2014-09-30 Bin Cheng<bin.ch...@arm.com> >> Mike Stump<mikest...@comcast.net> >> >> * timevar.def (TV_SCHED_FUSION): New time var. >> * passes.def (pass_sched_fusion): New pass. >> * config/arm/arm.c (TARGET_SCHED_FUSION_PRIORITY): New. >> (extract_base_offset_in_addr, fusion_load_store): New. >> (arm_sched_fusion_priority): New. >> (arm_option_override): Disable scheduling fusion on non-armv7 >> processors by default. >> * sched-int.h (struct _haifa_insn_data): New field. >> (INSN_FUSION_PRIORITY, FUSION_MAX_PRIORITY, sched_fusion): New. >> * sched-rgn.c (rest_of_handle_sched_fusion): New. >> (pass_data_sched_fusion, pass_sched_fusion): New. >> (make_pass_sched_fusion): New. >> * haifa-sched.c (sched_fusion): New. >> (insn_cost): Handle sched_fusion. >> (priority): Handle sched_fusion by calling target hook. >> (enum rfs_decision): New enum value. >> (rfs_str): New element for RFS_FUSION. >> (rank_for_schedule): Support sched_fusion. >> (schedule_insn, max_issue, prune_ready_list): Handle sched_fusion. >> (schedule_block, fix_tick_ready): Handle sched_fusion. >> * common.opt (flag_schedule_fusion): New. >> * tree-pass.h (make_pass_sched_fusion): New. >> * target.def (fusion_priority): New. >> * doc/tm.texi.in (TARGET_SCHED_FUSION_PRIORITY): New. >> * doc/tm.texi: Regenerated. >> * doc/invoke.texi (-fschedule-fusion): New. >> >> gcc/testsuite/ChangeLog >> 2014-09-30 Bin Cheng<bin.ch...@arm.com> >> >> * gcc.target/arm/ldrd-strd-pair-1.c: New test. >> * gcc.target/arm/vfp-1.c: Improve scanning string. >> >> >> sched-fusion-20140929.txt >> >> >> Index: gcc/doc/tm.texi >> =================================================================== >> --- gcc/doc/tm.texi (revision 215662) >> +++ gcc/doc/tm.texi (working copy) >> @@ -6677,6 +6677,29 @@ This hook is called by tree reassociator to determ >> parallelism required in output calculations chain. >> @end deftypefn >> >> +@deftypefn {Target Hook} void TARGET_SCHED_FUSION_PRIORITY (rtx_insn >> *@var{insn}, int @var{max_pri}, int *@var{fusion_pri}, int *@var{pri}) >> +This hook is called by scheduling fusion pass. It calculates fusion >> +priorities for each instruction passed in by parameter. The priorities >> +are returned via pointer parameters. >> + >> +@var{insn} is the instruction whose priorities need to be calculated. >> +@var{max_pri} is the maximum priority can be returned in any cases. >> +@var{fusion_pri} is the pointer parameter through which @var{insn}'s >> +fusion priority should be calculated and returned. >> +@var{pri} is the pointer parameter through which @var{insn}'s priority >> +should be calculated and returned. >> + >> +Same @var{fusion_pri} should be returned for instructions which should >> +be scheduled together. Different @var{pri} should be returned for >> +instructions with same @var{fusion_pri}. All instructions will be >> +scheduled according to the two priorities. @var{fusion_pri} is the major >> +sort key, @var{pri} is the minor sort key. All priorities calculated >> +should be between 0 (exclusive) and @var{max_pri} (inclusive). To avoid >> +false dependencies, @var{fusion_pri} of instructions which need to be >> +scheduled together should be smaller than @var{fusion_pri} of irrelevant >> +instructions. >> +@end deftypefn >> + >> @node Sections >> @section Dividing the Output into Sections (Texts, Data, @dots{}) >> @c the above section title is WAY too long. maybe cut the part between > > So I think we need to clarify that this hook is useful when fusing to > related insns, but which don't have a data dependency. Somehow we need to > describe that the insns to be fused should have the same (or +-1) priority. > It may be useful to use code from the ARM implementation to show how to use > this to pair up loads as an example. > > It may also be useful to refer to the code which reorders insns in the ready > queue for cases where we want to fuse two truly independent insns. > > >> >> + if (sched_fusion) >> + { >> + /* The instruction that has the same fusion priority as the last >> + instruction is the instruction we picked next. If that is not >> + the case, we sort ready list firstly by fusion priority, then >> + by priority, and at last by INSN_LUID. */ >> + int a = INSN_FUSION_PRIORITY (tmp); >> + int b = INSN_FUSION_PRIORITY (tmp2); >> + int last = -1; >> + >> + if (last_nondebug_scheduled_insn >> + && !NOTE_P (last_nondebug_scheduled_insn) >> + && BLOCK_FOR_INSN (tmp) >> + == BLOCK_FOR_INSN (last_nondebug_scheduled_insn)) >> + last = INSN_FUSION_PRIORITY (last_nondebug_scheduled_insn); >> + >> + if (a != last && b != last) >> + { >> + if (a == b) >> + { >> + a = INSN_PRIORITY (tmp); >> + b = INSN_PRIORITY (tmp2); >> + } >> + if (a != b) >> + return rfs_result (RFS_FUSION, b - a); >> + else >> + return rfs_result (RFS_FUSION, INSN_LUID (tmp) - INSN_LUID >> (tmp2)); > > rfs_result's signature has changed, I think you need to pass in tmp & tmp2. > You'll need to make that trivial update for all the callers. > > > Can you make those changes and repost so that I can look at the docs (I > think the implementation is fine and won't need further review). > > jeff
Hi, Thanks very much for reviewing. I refined the patch according to your comments. Also made two small changes: a) skip breaking dependency between memory access and the corresponding base-reg modifying instruction. This feature doesn't help load/store pair that much and only increases compilation time. b) a minor bug fix in arm backend hook when calculating priority for memory accesses with minus offset. I am running bootstrap/test against latest trunk, and will adapt ChangeLog once get approved generally. So how about this one? Thanks, bin
Index: gcc/timevar.def =================================================================== --- gcc/timevar.def (revision 217112) +++ gcc/timevar.def (working copy) @@ -246,6 +246,7 @@ DEFTIMEVAR (TV_IFCVT2 , "if-conversion 2") DEFTIMEVAR (TV_COMBINE_STACK_ADJUST , "combine stack adjustments") DEFTIMEVAR (TV_PEEPHOLE2 , "peephole 2") DEFTIMEVAR (TV_RENAME_REGISTERS , "rename registers") +DEFTIMEVAR (TV_SCHED_FUSION , "scheduling fusion") DEFTIMEVAR (TV_CPROP_REGISTERS , "hard reg cprop") DEFTIMEVAR (TV_SCHED2 , "scheduling 2") DEFTIMEVAR (TV_MACH_DEP , "machine dep reorg") Index: gcc/passes.def =================================================================== --- gcc/passes.def (revision 217112) +++ gcc/passes.def (working copy) @@ -402,6 +402,7 @@ along with GCC; see the file COPYING3. If not see NEXT_PASS (pass_stack_adjustments); NEXT_PASS (pass_jump2); NEXT_PASS (pass_duplicate_computed_gotos); + NEXT_PASS (pass_sched_fusion); NEXT_PASS (pass_peephole2); NEXT_PASS (pass_if_after_reload); NEXT_PASS (pass_regrename); Index: gcc/testsuite/gcc.target/arm/ldrd-strd-pair-1.c =================================================================== --- gcc/testsuite/gcc.target/arm/ldrd-strd-pair-1.c (revision 0) +++ gcc/testsuite/gcc.target/arm/ldrd-strd-pair-1.c (revision 0) @@ -0,0 +1,23 @@ +/* { dg-do compile } */ +/* { dg-require-effective-target arm_prefer_ldrd_strd } */ +/* { dg-options "-O2" } */ + +struct +{ + int x; + int y; + char c; + int d; +}a; + +int foo(int x, int y) +{ + int c; + a.x = x; + c = a.x; + a.d = c; + a.y = y; + + return 0; +} +/* { dg-final { scan-assembler "strd\t" } } */ Index: gcc/testsuite/gcc.target/arm/vfp-1.c =================================================================== --- gcc/testsuite/gcc.target/arm/vfp-1.c (revision 217112) +++ gcc/testsuite/gcc.target/arm/vfp-1.c (working copy) @@ -126,7 +126,7 @@ void test_convert () { } void test_ldst (float f[], double d[]) { - /* { dg-final { scan-assembler "vldr.32.+ \\\[r0, #1020\\\]" } } */ + /* { dg-final { scan-assembler "vldr.32.+ \\\[r0, #-?\[0-9\]+\\\]" } } */ /* { dg-final { scan-assembler "vldr.32.+ \\\[r\[0-9\], #-1020\\\]" { target { arm32 && { ! arm_thumb2_ok } } } } } */ /* { dg-final { scan-assembler "add.+ r0, #1024" } } */ /* { dg-final { scan-assembler "vstr.32.+ \\\[r\[0-9\]\\\]\n" } } */ Index: gcc/sched-int.h =================================================================== --- gcc/sched-int.h (revision 217112) +++ gcc/sched-int.h (working copy) @@ -805,6 +805,9 @@ struct _haifa_insn_data /* A priority for each insn. */ int priority; + /* The fusion priority for each insn. */ + int fusion_priority; + /* The minimum clock tick at which the insn becomes ready. This is used to note timing constraints for the insns in the pending list. */ int tick; @@ -903,6 +906,7 @@ extern vec<haifa_insn_data_def> h_i_d; /* Accessor macros for h_i_d. There are more in haifa-sched.c and sched-rgn.c. */ #define INSN_PRIORITY(INSN) (HID (INSN)->priority) +#define INSN_FUSION_PRIORITY(INSN) (HID (INSN)->fusion_priority) #define INSN_REG_PRESSURE(INSN) (HID (INSN)->reg_pressure) #define INSN_MAX_REG_PRESSURE(INSN) (HID (INSN)->max_reg_pressure) #define INSN_REG_USE_LIST(INSN) (HID (INSN)->reg_use_list) @@ -1620,6 +1624,10 @@ extern void sd_copy_back_deps (rtx_insn *, rtx_ins extern void sd_delete_dep (sd_iterator_def); extern void sd_debug_lists (rtx, sd_list_types_def); +/* Macros and declarations for scheduling fusion. */ +#define FUSION_MAX_PRIORITY (INT_MAX) +extern bool sched_fusion; + #endif /* INSN_SCHEDULING */ #endif /* GCC_SCHED_INT_H */ Index: gcc/haifa-sched.c =================================================================== --- gcc/haifa-sched.c (revision 217112) +++ gcc/haifa-sched.c (working copy) @@ -1391,6 +1391,9 @@ insn_cost (rtx_insn *insn) { int cost; + if (sched_fusion) + return 0; + if (sel_sched_p ()) { if (recog_memoized (insn) < 0) @@ -1603,6 +1606,8 @@ dep_list_size (rtx insn, sd_list_types_def list) return nodbgcount; } +bool sched_fusion; + /* Compute the priority number for INSN. */ static int priority (rtx_insn *insn) @@ -1617,7 +1622,15 @@ priority (rtx_insn *insn) { int this_priority = -1; - if (dep_list_size (insn, SD_LIST_FORW) == 0) + if (sched_fusion) + { + int this_fusion_priority; + + targetm.sched.fusion_priority (insn, FUSION_MAX_PRIORITY, + &this_fusion_priority, &this_priority); + INSN_FUSION_PRIORITY (insn) = this_fusion_priority; + } + else if (dep_list_size (insn, SD_LIST_FORW) == 0) /* ??? We should set INSN_PRIORITY to insn_cost when and insn has some forward deps but all of them are ignored by contributes_to_priority hook. At the moment we set priority of @@ -2548,7 +2561,7 @@ enum rfs_decision { RFS_SCHED_GROUP, RFS_PRESSURE_DELAY, RFS_PRESSURE_TICK, RFS_FEEDS_BACKTRACK_INSN, RFS_PRIORITY, RFS_SPECULATION, RFS_SCHED_RANK, RFS_LAST_INSN, RFS_PRESSURE_INDEX, - RFS_DEP_COUNT, RFS_TIE, RFS_N }; + RFS_DEP_COUNT, RFS_TIE, RFS_FUSION, RFS_N }; /* Corresponding strings for print outs. */ static const char *rfs_str[RFS_N] = { @@ -2556,7 +2569,7 @@ static const char *rfs_str[RFS_N] = { "RFS_SCHED_GROUP", "RFS_PRESSURE_DELAY", "RFS_PRESSURE_TICK", "RFS_FEEDS_BACKTRACK_INSN", "RFS_PRIORITY", "RFS_SPECULATION", "RFS_SCHED_RANK", "RFS_LAST_INSN", "RFS_PRESSURE_INDEX", - "RFS_DEP_COUNT", "RFS_TIE" }; + "RFS_DEP_COUNT", "RFS_TIE", "RFS_FUSION" }; /* Statistical breakdown of rank_for_schedule decisions. */ typedef struct { unsigned stats[RFS_N]; } rank_for_schedule_stats_t; @@ -2627,6 +2640,55 @@ rank_for_schedule (const void *x, const void *y) /* Make sure that priority of TMP and TMP2 are initialized. */ gcc_assert (INSN_PRIORITY_KNOWN (tmp) && INSN_PRIORITY_KNOWN (tmp2)); + if (sched_fusion) + { + /* The instruction that has the same fusion priority as the last + instruction is the instruction we picked next. If that is not + the case, we sort ready list firstly by fusion priority, then + by priority, and at last by INSN_LUID. */ + int a = INSN_FUSION_PRIORITY (tmp); + int b = INSN_FUSION_PRIORITY (tmp2); + int last = -1; + + if (last_nondebug_scheduled_insn + && !NOTE_P (last_nondebug_scheduled_insn) + && BLOCK_FOR_INSN (tmp) + == BLOCK_FOR_INSN (last_nondebug_scheduled_insn)) + last = INSN_FUSION_PRIORITY (last_nondebug_scheduled_insn); + + if (a != last && b != last) + { + if (a == b) + { + a = INSN_PRIORITY (tmp); + b = INSN_PRIORITY (tmp2); + } + if (a != b) + return rfs_result (RFS_FUSION, b - a, tmp, tmp2); + else + return rfs_result (RFS_FUSION, + INSN_LUID (tmp) - INSN_LUID (tmp2), tmp, tmp2); + } + else if (a == b) + { + gcc_assert (last_nondebug_scheduled_insn + && !NOTE_P (last_nondebug_scheduled_insn)); + last = INSN_PRIORITY (last_nondebug_scheduled_insn); + + a = abs (INSN_PRIORITY (tmp) - last); + b = abs (INSN_PRIORITY (tmp2) - last); + if (a != b) + return rfs_result (RFS_FUSION, a - b, tmp, tmp2); + else + return rfs_result (RFS_FUSION, + INSN_LUID (tmp) - INSN_LUID (tmp2), tmp, tmp2); + } + else if (a == last) + return rfs_result (RFS_FUSION, -1, tmp, tmp2); + else + return rfs_result (RFS_FUSION, 1, tmp, tmp2); + } + if (sched_pressure != SCHED_PRESSURE_NONE) { /* Prefer insn whose scheduling results in the smallest register @@ -4007,8 +4069,8 @@ schedule_insn (rtx_insn *insn) gcc_assert (INSN_TICK (insn) >= MIN_TICK); if (INSN_TICK (insn) > clock_var) /* INSN has been prematurely moved from the queue to the ready list. - This is possible only if following flag is set. */ - gcc_assert (flag_sched_stalled_insns); + This is possible only if following flags are set. */ + gcc_assert (flag_sched_stalled_insns || sched_fusion); /* ??? Probably, if INSN is scheduled prematurely, we should leave INSN_TICK untouched. This is a machine-dependent issue, actually. */ @@ -5500,6 +5562,9 @@ max_issue (struct ready_list *ready, int privilege struct choice_entry *top; rtx_insn *insn; + if (sched_fusion) + return 0; + n_ready = ready->n_ready; gcc_assert (dfa_lookahead >= 1 && privileged_n >= 0 && privileged_n <= n_ready); @@ -5848,6 +5913,9 @@ prune_ready_list (state_t temp_state, bool first_c bool sched_group_found = false; int min_cost_group = 1; + if (sched_fusion) + return; + for (i = 0; i < ready.n_ready; i++) { rtx_insn *insn = ready_element (&ready, i); @@ -6059,7 +6127,7 @@ schedule_block (basic_block *target_bb, state_t in rtx_insn *tail = PREV_INSN (next_tail); if ((current_sched_info->flags & DONT_BREAK_DEPENDENCIES) == 0 - && sched_pressure != SCHED_PRESSURE_MODEL) + && sched_pressure != SCHED_PRESSURE_MODEL && !sched_fusion) find_modifiable_mems (head, tail); /* We used to have code to avoid getting parameters moved from hard @@ -6455,7 +6523,7 @@ schedule_block (basic_block *target_bb, state_t in { memcpy (temp_state, curr_state, dfa_state_size); cost = state_transition (curr_state, insn); - if (sched_pressure != SCHED_PRESSURE_WEIGHTED) + if (sched_pressure != SCHED_PRESSURE_WEIGHTED && !sched_fusion) gcc_assert (cost < 0); if (memcmp (temp_state, curr_state, dfa_state_size) != 0) cycle_issued_insns++; @@ -7288,7 +7356,7 @@ fix_tick_ready (rtx_insn *next) INSN_TICK (next) = tick; delay = tick - clock_var; - if (delay <= 0 || sched_pressure != SCHED_PRESSURE_NONE) + if (delay <= 0 || sched_pressure != SCHED_PRESSURE_NONE || sched_fusion) delay = QUEUE_READY; change_queue_index (next, delay); Index: gcc/sched-rgn.c =================================================================== --- gcc/sched-rgn.c (revision 217112) +++ gcc/sched-rgn.c (working copy) @@ -3658,6 +3658,17 @@ rest_of_handle_sched2 (void) return 0; } +static unsigned int +rest_of_handle_sched_fusion (void) +{ +#ifdef INSN_SCHEDULING + sched_fusion = true; + schedule_insns (); + sched_fusion = false; +#endif + return 0; +} + namespace { const pass_data pass_data_live_range_shrinkage = @@ -3800,3 +3811,53 @@ make_pass_sched2 (gcc::context *ctxt) { return new pass_sched2 (ctxt); } + +namespace { + +const pass_data pass_data_sched_fusion = +{ + RTL_PASS, /* type */ + "sched_fusion", /* name */ + OPTGROUP_NONE, /* optinfo_flags */ + TV_SCHED_FUSION, /* tv_id */ + 0, /* properties_required */ + 0, /* properties_provided */ + 0, /* properties_destroyed */ + 0, /* todo_flags_start */ + TODO_df_finish, /* todo_flags_finish */ +}; + +class pass_sched_fusion : public rtl_opt_pass +{ +public: + pass_sched_fusion (gcc::context *ctxt) + : rtl_opt_pass (pass_data_sched_fusion, ctxt) + {} + + /* opt_pass methods: */ + virtual bool gate (function *); + virtual unsigned int execute (function *) + { + return rest_of_handle_sched_fusion (); + } + +}; // class pass_sched2 + +bool +pass_sched_fusion::gate (function *) +{ +#ifdef INSN_SCHEDULING + return (optimize > 0 && flag_schedule_fusion + && targetm.sched.fusion_priority != NULL); +#else + return 0; +#endif +} + +} // anon namespace + +rtl_opt_pass * +make_pass_sched_fusion (gcc::context *ctxt) +{ + return new pass_sched_fusion (ctxt); +} Index: gcc/common.opt =================================================================== --- gcc/common.opt (revision 217112) +++ gcc/common.opt (working copy) @@ -1840,6 +1840,10 @@ frename-registers Common Report Var(flag_rename_registers) Init(2) Optimization Perform a register renaming optimization pass +fschedule-fusion +Common Report Var(flag_schedule_fusion) Init(2) Optimization +Perform a target dependent instruction fusion optimization pass + freorder-blocks Common Report Var(flag_reorder_blocks) Optimization Reorder basic blocks to improve code placement Index: gcc/doc/tm.texi =================================================================== --- gcc/doc/tm.texi (revision 217112) +++ gcc/doc/tm.texi (working copy) @@ -6689,6 +6689,73 @@ This hook is called by tree reassociator to determ parallelism required in output calculations chain. @end deftypefn +@deftypefn {Target Hook} void TARGET_SCHED_FUSION_PRIORITY (rtx_insn *@var{insn}, int @var{max_pri}, int *@var{fusion_pri}, int *@var{pri}) +This hook is called by scheduling fusion pass. It calculates fusion +priorities for each instruction passed in by parameter. The priorities +are returned via pointer parameters. + +@var{insn} is the instruction whose priorities need to be calculated. +@var{max_pri} is the maximum priority can be returned in any cases. +@var{fusion_pri} is the pointer parameter through which @var{insn}'s +fusion priority should be calculated and returned. +@var{pri} is the pointer parameter through which @var{insn}'s priority +should be calculated and returned. + +Same @var{fusion_pri} should be returned for instructions which should +be scheduled together. Different @var{pri} should be returned for +instructions with same @var{fusion_pri}. @var{fusion_pri} is the major +sort key, @var{pri} is the minor sort key. All instructions will be +scheduled according to the two priorities. All priorities calculated +should be between 0 (exclusive) and @var{max_pri} (inclusive). To avoid +false dependencies, @var{fusion_pri} of instructions which need to be +scheduled together should be smaller than @var{fusion_pri} of irrelevant +instructions. + +Given below example: + + ldr r10, [r1, 4] + add r4, r4, r10 + ldr r15, [r2, 8] + sub r5, r5, r15 + ldr r11, [r1, 0] + add r4, r4, r11 + ldr r16, [r2, 12] + sub r5, r5, r16 + +On targets like ARM/AArch64, the two pairs of consecutive loads should be +merged. Since peephole pass can't help in this case unless consecutive +loads are actually next to each other in instruction flow. That's where +this scheduling fusion pass works. This hook calculates priority for each +instruction based on its fustion type, like: + + ldr r10, [r1, 4] ; fusion_pri=99, pri=96 + add r4, r4, r10 ; fusion_pri=100, pri=100 + ldr r15, [r2, 8] ; fusion_pri=98, pri=92 + sub r5, r5, r15 ; fusion_pri=100, pri=100 + ldr r11, [r1, 0] ; fusion_pri=99, pri=100 + add r4, r4, r11 ; fusion_pri=100, pri=100 + ldr r16, [r2, 12] ; fusion_pri=98, pri=88 + sub r5, r5, r16 ; fusion_pri=100, pri=100 + +Scheduling fusion pass then sorts all ready to issue instructions according +to the priorities. As a result, instructions of same fusion type will be +pushed together in instruction flow, like: + + ldr r11, [r1, 0] + ldr r10, [r1, 4] + ldr r15, [r2, 8] + ldr r16, [r2, 12] + add r4, r4, r10 + sub r5, r5, r15 + add r4, r4, r11 + sub r5, r5, r16 + +Now peephole pass can simply merge the two pairs of loads. + +This is firstly introduced on ARM/AArch64 targets, please refer to the hook +implementation for how different fusion types are supported. +@end deftypefn + @node Sections @section Dividing the Output into Sections (Texts, Data, @dots{}) @c the above section title is WAY too long. maybe cut the part between Index: gcc/doc/tm.texi.in =================================================================== --- gcc/doc/tm.texi.in (revision 217112) +++ gcc/doc/tm.texi.in (working copy) @@ -4791,6 +4791,8 @@ them: try the first ones in this list first. @hook TARGET_SCHED_REASSOCIATION_WIDTH +@hook TARGET_SCHED_FUSION_PRIORITY + @node Sections @section Dividing the Output into Sections (Texts, Data, @dots{}) @c the above section title is WAY too long. maybe cut the part between Index: gcc/doc/invoke.texi =================================================================== --- gcc/doc/invoke.texi (revision 217112) +++ gcc/doc/invoke.texi (working copy) @@ -405,7 +405,7 @@ Objective-C and Objective-C++ Dialects}. -fprofile-correction -fprofile-dir=@var{path} -fprofile-generate @gol -fprofile-generate=@var{path} @gol -fprofile-use -fprofile-use=@var{path} -fprofile-values -fprofile-reorder-functions @gol --freciprocal-math -free -frename-registers -freorder-blocks @gol +-freciprocal-math -free -frename-registers -fschedule-fusion -freorder-blocks @gol -freorder-blocks-and-partition -freorder-functions @gol -frerun-cse-after-loop -freschedule-modulo-scheduled-loops @gol -frounding-math -fsched2-use-superblocks -fsched-pressure @gol @@ -9556,6 +9556,14 @@ a ``home register''. Enabled by default with @option{-funroll-loops} and @option{-fpeel-loops}. +@item -fschedule-fusion +@opindex fschedule-fusion +Performs a target dependent pass over the instruction stream to schedule +instructions of same type together because target machine can execute them +more efficiently if they are adjacent to each other in the instruction flow. + +Enabled at levels @option{-O2}, @option{-O3}, @option{-Os}. + @item -ftracer @opindex ftracer Perform tail duplication to enlarge superblock size. This transformation Index: gcc/tree-pass.h =================================================================== --- gcc/tree-pass.h (revision 217112) +++ gcc/tree-pass.h (working copy) @@ -544,6 +544,7 @@ extern rtl_opt_pass *make_pass_branch_target_load_ extern rtl_opt_pass *make_pass_thread_prologue_and_epilogue (gcc::context *ctxt); extern rtl_opt_pass *make_pass_stack_adjustments (gcc::context *ctxt); +extern rtl_opt_pass *make_pass_sched_fusion (gcc::context *ctxt); extern rtl_opt_pass *make_pass_peephole2 (gcc::context *ctxt); extern rtl_opt_pass *make_pass_if_after_reload (gcc::context *ctxt); extern rtl_opt_pass *make_pass_regrename (gcc::context *ctxt); Index: gcc/config/arm/arm.c =================================================================== --- gcc/config/arm/arm.c (revision 217112) +++ gcc/config/arm/arm.c (working copy) @@ -310,6 +310,8 @@ static unsigned arm_add_stmt_cost (void *data, int static void arm_canonicalize_comparison (int *code, rtx *op0, rtx *op1, bool op0_preserve_value); static unsigned HOST_WIDE_INT arm_asan_shadow_offset (void); + +static void arm_sched_fusion_priority (rtx_insn *, int, int *, int*); /* Table of machine attributes. */ static const struct attribute_spec arm_attribute_table[] = @@ -707,6 +709,9 @@ static const struct attribute_spec arm_attribute_t #undef TARGET_CALL_FUSAGE_CONTAINS_NON_CALLEE_CLOBBERS #define TARGET_CALL_FUSAGE_CONTAINS_NON_CALLEE_CLOBBERS true +#undef TARGET_SCHED_FUSION_PRIORITY +#define TARGET_SCHED_FUSION_PRIORITY arm_sched_fusion_priority + struct gcc_target targetm = TARGET_INITIALIZER; /* Obstack for minipool constant handling. */ @@ -3135,6 +3140,10 @@ arm_option_override (void) if (target_slow_flash_data) arm_disable_literal_pool = true; + /* Only enable scheduling fusion for armv7 processors by default. */ + if (flag_schedule_fusion == 2 && !arm_arch7) + flag_schedule_fusion = 0; + /* Register global variables with the garbage collector. */ arm_add_gc_roots (); } @@ -32305,4 +32314,124 @@ arm_is_constant_pool_ref (rtx x) && CONSTANT_POOL_ADDRESS_P (XEXP (x, 0))); } +/* If MEM is in the form of [base+offset], extract the two parts + of address and set to BASE and OFFSET, otherwise return false + after clearing BASE and OFFSET. */ + +static bool +extract_base_offset_in_addr (rtx mem, rtx *base, rtx *offset) +{ + rtx addr; + + gcc_assert (MEM_P (mem)); + + addr = XEXP (mem, 0); + + /* Strip off const from addresses like (const (addr)). */ + if (GET_CODE (addr) == CONST) + addr = XEXP (addr, 0); + + if (GET_CODE (addr) == REG) + { + *base = addr; + *offset = const0_rtx; + return true; + } + + if (GET_CODE (addr) == PLUS + && GET_CODE (XEXP (addr, 0)) == REG + && CONST_INT_P (XEXP (addr, 1))) + { + *base = XEXP (addr, 0); + *offset = XEXP (addr, 1); + return true; + } + + *base = NULL_RTX; + *offset = NULL_RTX; + + return false; +} + +/* If INSN is a load or store of address in the form of [base+offset], + extract the two parts and set to BASE and OFFSET. IS_LOAD is set + to TRUE if it's a load. Return TRUE if INSN is such an instruction, + otherwise return FALSE. */ + +static bool +fusion_load_store (rtx_insn *insn, rtx *base, rtx *offset, bool *is_load) +{ + rtx x, dest, src; + + gcc_assert (INSN_P (insn)); + x = PATTERN (insn); + if (GET_CODE (x) != SET) + return false; + + src = SET_SRC (x); + dest = SET_DEST (x); + if (GET_CODE (src) == REG && GET_CODE (dest) == MEM) + { + *is_load = false; + extract_base_offset_in_addr (dest, base, offset); + } + else if (GET_CODE (src) == MEM && GET_CODE (dest) == REG) + { + *is_load = true; + extract_base_offset_in_addr (src, base, offset); + } + else + return false; + + return (*base != NULL_RTX && *offset != NULL_RTX); +} + +/* Implement the TARGET_SCHED_FUSION_PRIORITY hook. + + Currently we only support to fuse ldr or str instructions, so FUSION_PRI + and PRI are only calculated for these instructions. For other instruction, + FUSION_PRI and PRI are simply set to MAX_PRI. In the future, other kind + instruction fusion can be supported by returning different priorities. + + It's important that irrelevant instructions get the largest FUSION_PRI. */ + +static void +arm_sched_fusion_priority (rtx_insn *insn, int max_pri, + int *fusion_pri, int *pri) +{ + int tmp, off_val; + bool is_load; + rtx base, offset; + + gcc_assert (INSN_P (insn)); + + tmp = max_pri - 1; + if (!fusion_load_store (insn, &base, &offset, &is_load)) + { + *pri = tmp; + *fusion_pri = tmp; + return; + } + + /* Load goes first. */ + if (is_load) + *fusion_pri = tmp - 1; + else + *fusion_pri = tmp - 2; + + tmp /= 2; + + /* INSN with smaller base register goes first. */ + tmp -= ((REGNO (base) & 0xff) << 20); + + /* INSN with smaller offset goes first. */ + off_val = (int)(INTVAL (offset)); + if (off_val >= 0) + tmp -= (off_val & 0xfffff); + else + tmp += ((- off_val) & 0xfffff); + + *pri = tmp; + return; +} #include "gt-arm.h" Index: gcc/target.def =================================================================== --- gcc/target.def (revision 217112) +++ gcc/target.def (working copy) @@ -1507,6 +1507,76 @@ parallelism required in output calculations chain. int, (unsigned int opc, machine_mode mode), hook_int_uint_mode_1) +/* The following member value is a function that returns priority for + fusion of each instruction via pointer parameters. */ +DEFHOOK +(fusion_priority, +"This hook is called by scheduling fusion pass. It calculates fusion\n\ +priorities for each instruction passed in by parameter. The priorities\n\ +are returned via pointer parameters.\n\ +\n\ +@var{insn} is the instruction whose priorities need to be calculated.\n\ +@var{max_pri} is the maximum priority can be returned in any cases.\n\ +@var{fusion_pri} is the pointer parameter through which @var{insn}'s\n\ +fusion priority should be calculated and returned.\n\ +@var{pri} is the pointer parameter through which @var{insn}'s priority\n\ +should be calculated and returned.\n\ +\n\ +Same @var{fusion_pri} should be returned for instructions which should\n\ +be scheduled together. Different @var{pri} should be returned for\n\ +instructions with same @var{fusion_pri}. @var{fusion_pri} is the major\n\ +sort key, @var{pri} is the minor sort key. All instructions will be\n\ +scheduled according to the two priorities. All priorities calculated\n\ +should be between 0 (exclusive) and @var{max_pri} (inclusive). To avoid\n\ +false dependencies, @var{fusion_pri} of instructions which need to be\n\ +scheduled together should be smaller than @var{fusion_pri} of irrelevant\n\ +instructions.\n\ +\n\ +Given below example:\n\ +\n\ + ldr r10, [r1, 4]\n\ + add r4, r4, r10\n\ + ldr r15, [r2, 8]\n\ + sub r5, r5, r15\n\ + ldr r11, [r1, 0]\n\ + add r4, r4, r11\n\ + ldr r16, [r2, 12]\n\ + sub r5, r5, r16\n\ +\n\ +On targets like ARM/AArch64, the two pairs of consecutive loads should be\n\ +merged. Since peephole pass can't help in this case unless consecutive\n\ +loads are actually next to each other in instruction flow. That's where\n\ +this scheduling fusion pass works. This hook calculates priority for each\n\ +instruction based on its fustion type, like:\n\ +\n\ + ldr r10, [r1, 4] ; fusion_pri=99, pri=96 \n\ + add r4, r4, r10 ; fusion_pri=100, pri=100 \n\ + ldr r15, [r2, 8] ; fusion_pri=98, pri=92 \n\ + sub r5, r5, r15 ; fusion_pri=100, pri=100 \n\ + ldr r11, [r1, 0] ; fusion_pri=99, pri=100 \n\ + add r4, r4, r11 ; fusion_pri=100, pri=100 \n\ + ldr r16, [r2, 12] ; fusion_pri=98, pri=88 \n\ + sub r5, r5, r16 ; fusion_pri=100, pri=100 \n\ +\n\ +Scheduling fusion pass then sorts all ready to issue instructions according\n\ +to the priorities. As a result, instructions of same fusion type will be\n\ +pushed together in instruction flow, like:\n\ +\n\ + ldr r11, [r1, 0]\n\ + ldr r10, [r1, 4]\n\ + ldr r15, [r2, 8]\n\ + ldr r16, [r2, 12]\n\ + add r4, r4, r10\n\ + sub r5, r5, r15\n\ + add r4, r4, r11\n\ + sub r5, r5, r16\n\ +\n\ +Now peephole pass can simply merge the two pairs of loads.\n\ +\n\ +This is firstly introduced on ARM/AArch64 targets, please refer to the hook\n\ +implementation for how different fusion types are supported.", +void, (rtx_insn *insn, int max_pri, int *fusion_pri, int *pri), NULL) + HOOK_VECTOR_END (sched) /* Functions relating to OpenMP and Cilk Plus SIMD clones. */