[gcc r17-2921] sched-deps: reuse reg_last arrays across regions
Kyrylo Tkachov via Gcc-cvs <[email protected]> Tue, 4 Aug 2026 08:15:22 +0000 (GMT)
| Newsgroups | gmane.comp.gcc.cvs |
|---|---|
| Message-ID | <[email protected]> |
https://gcc.gnu.org/g:789d24cd259e0e1e8e5af512565fa59daae5307d commit r17-2921-g789d24cd259e0e1e8e5af512565fa59daae5307d Author: Kyrylo Tkachov <[email protected]> Date: Mon Jul 27 20:04:29 2026 +0200 sched-deps: reuse reg_last arrays across regions The region scheduler initializes one deps_desc for each basic block in a region. Each initialization allocates and zeroes max_reg deps_reg entries. The total allocation and zeroing work is O (max_reg * sum (RGN_NR_BLOCKS (region))). Before reload, max_reg is max_reg_num (). This work can therefore grow quadratically with function size. free_deps now empties each entry that was written. Keep these all-zero arrays in a pool keyed by max_reg and reuse them in later regions. Reset the list length fields before returning an array. Under -fchecking=2, verify that every field is zero. Drain the pool in sched_deps_finish. Selective scheduling does not return arrays to the pool because remove_from_deps can leave an untracked control-use list. Add a selftest that fills every list field and both length fields. It verifies that free_deps returns the same array through the pool with all fields cleared. Also add a compile test that drives a debug-only use through pool release and reuse under -fchecking=2. Disabling the pool makes the selftest fail. Dropping the reg_last_dirty recording from patch 2 makes the compile test ICE. On an aarch64 system, I saw a ~1.5% improvement in compile speed of gimple-match-6 at -O3. Generated assembly doesn't change. Bootstrapped and tested on aarch64-none-linux-gnu and x86_64-linux. gcc/ChangeLog: * sched-deps.cc (reg_last_pool, reg_last_pool_max_reg): New. (alloc_reg_last, release_reg_last): New. (init_deps, init_deps_reg_last): Use alloc_reg_last. (free_deps): Reset the list length fields and release reg_last to the pool. (sched_deps_finish): Release the pooled arrays. (selftest::test_reg_last_pool): New. (selftest::sched_deps_cc_tests): Run it. gcc/testsuite/ChangeLog: * gcc.dg/sched-deps-debug-1.c: New test. Signed-off-by: Kyrylo Tkachov <[email protected]> Diff: --- gcc/sched-deps.cc | 129 +++++++++++++++++++++++++++++- gcc/testsuite/gcc.dg/sched-deps-debug-1.c | 27 +++++++ 2 files changed, 153 insertions(+), 3 deletions(-) diff --git a/gcc/sched-deps.cc b/gcc/sched-deps.cc index 3a170d87b7e1..4911b65bba19 100644 --- a/gcc/sched-deps.cc +++ b/gcc/sched-deps.cc @@ -3930,6 +3930,61 @@ sched_free_deps (rtx_insn *head, rtx_insn *tail, bool resolved_p) } } +/* Pool of all-zero reg_last arrays. init_deps takes one and free_deps + returns it, so the O (MAX_REG) zeroing is paid once per pooled array rather + than once per basic block of every region. free_deps empties every entry + that was written, so an array coming back is already zero. Selective + scheduling is excluded: its remove_from_deps can drop a reg_last_in_use bit + while control_uses is still live, which would return a dirty array. */ +static vec<struct deps_reg *> reg_last_pool; +static int reg_last_pool_max_reg; + +/* Return an all-zero array of MAX_REG deps_reg, from the pool if one of the + right size is available. */ + +static struct deps_reg * +alloc_reg_last (int max_reg) +{ + if (max_reg != reg_last_pool_max_reg) + { + while (!reg_last_pool.is_empty ()) + free (reg_last_pool.pop ()); + reg_last_pool_max_reg = max_reg; + } + else if (!reg_last_pool.is_empty ()) + return reg_last_pool.pop (); + + return XCNEWVEC (struct deps_reg, max_reg); +} + +/* Give REG_LAST, an array of MAX_REG deps_reg that free_deps has just + emptied, back to the pool. */ + +static void +release_reg_last (struct deps_reg *reg_last, int max_reg) +{ + if (reg_last == NULL) + return; + + if (sel_sched_p () || max_reg != reg_last_pool_max_reg) + { + free (reg_last); + return; + } + + if (flag_checking > 1) + for (int i = 0; i < max_reg; i++) + gcc_assert (reg_last[i].uses == NULL + && reg_last[i].sets == NULL + && reg_last[i].implicit_sets == NULL + && reg_last[i].control_uses == NULL + && reg_last[i].clobbers == NULL + && reg_last[i].uses_length == 0 + && reg_last[i].clobbers_length == 0); + + reg_last_pool.safe_push (reg_last); +} + /* Initialize variables for region data dependence analysis. When LAZY_REG_LAST is true, do not allocate reg_last array of class deps_desc immediately. */ @@ -3943,7 +3998,7 @@ init_deps (class deps_desc *deps, bool lazy_reg_last) if (lazy_reg_last) deps->reg_last = NULL; else - deps->reg_last = XCNEWVEC (struct deps_reg, max_reg); + deps->reg_last = alloc_reg_last (max_reg); INIT_REG_SET (&deps->reg_last_in_use); INIT_REG_SET (&deps->reg_last_dirty); @@ -3978,7 +4033,7 @@ init_deps_reg_last (class deps_desc *deps) gcc_assert (deps && deps->max_reg > 0); gcc_assert (deps->reg_last == NULL); - deps->reg_last = XCNEWVEC (struct deps_reg, deps->max_reg); + deps->reg_last = alloc_reg_last (deps->max_reg); } @@ -3996,6 +4051,7 @@ free_deps (class deps_desc *deps) gcc_assert (deps->reg_last == NULL); return; } + int max_reg = deps->max_reg; deps->max_reg = 0; free_INSN_LIST_list (&deps->pending_read_insns); @@ -4025,13 +4081,15 @@ free_deps (class deps_desc *deps) free_INSN_LIST_list (®_last->control_uses); if (reg_last->clobbers) free_INSN_LIST_list (®_last->clobbers); + reg_last->uses_length = 0; + reg_last->clobbers_length = 0; } CLEAR_REG_SET (&deps->reg_last_in_use); CLEAR_REG_SET (&deps->reg_last_dirty); /* As we initialize reg_last lazily, it is possible that we didn't allocate it at all. */ - free (deps->reg_last); + release_reg_last (deps->reg_last, max_reg); deps->reg_last = NULL; deps = NULL; @@ -4183,6 +4241,11 @@ sched_deps_finish (void) h_d_i_d.release (); + while (!reg_last_pool.is_empty ()) + free (reg_last_pool.pop ()); + reg_last_pool.release (); + reg_last_pool_max_reg = 0; + if (true_dependency_cache) { int i; @@ -5079,12 +5142,72 @@ test_dirty_reg_last_release () bitmap_obstack_release (&test_obstack); } +/* Verify that a pooled reg_last array is empty when it is reused. */ + +static void +test_reg_last_pool () +{ + const int max_reg = 3; + ASSERT_TRUE (reg_last_pool.is_empty ()); + + bitmap_obstack test_obstack; + bitmap_obstack_initialize (&test_obstack); + + deps_desc deps = {}; + deps.max_reg = max_reg; + deps.reg_last = alloc_reg_last (max_reg); + struct deps_reg *saved_reg_last = deps.reg_last; + bitmap_initialize (&deps.reg_last_in_use, &test_obstack); + bitmap_initialize (&deps.reg_last_dirty, &test_obstack); + + deps.reg_last[0].uses = alloc_INSN_LIST (NULL_RTX, NULL_RTX); + deps.reg_last[0].sets = alloc_INSN_LIST (NULL_RTX, NULL_RTX); + deps.reg_last[0].implicit_sets = alloc_INSN_LIST (NULL_RTX, NULL_RTX); + deps.reg_last[0].clobbers = alloc_INSN_LIST (NULL_RTX, NULL_RTX); + deps.reg_last[0].uses_length = 2; + deps.reg_last[0].clobbers_length = 3; + SET_REGNO_REG_SET (&deps.reg_last_in_use, 0); + + deps.reg_last[1].control_uses = alloc_INSN_LIST (NULL_RTX, NULL_RTX); + SET_REGNO_REG_SET (&deps.reg_last_dirty, 1); + + common_sched_info_def sched_info = {}; + sched_info.sched_pass_id = SCHED_RGN_PASS; + common_sched_info_def *saved_common_sched_info = common_sched_info; + common_sched_info = &sched_info; + free_deps (&deps); + + ASSERT_EQ (0, deps.max_reg); + ASSERT_EQ (NULL, deps.reg_last); + ASSERT_EQ (1, reg_last_pool.length ()); + + struct deps_reg *reused_reg_last = alloc_reg_last (max_reg); + ASSERT_EQ (saved_reg_last, reused_reg_last); + ASSERT_TRUE (reg_last_pool.is_empty ()); + for (int i = 0; i < max_reg; ++i) + { + ASSERT_EQ (NULL, reused_reg_last[i].uses); + ASSERT_EQ (NULL, reused_reg_last[i].sets); + ASSERT_EQ (NULL, reused_reg_last[i].implicit_sets); + ASSERT_EQ (NULL, reused_reg_last[i].control_uses); + ASSERT_EQ (NULL, reused_reg_last[i].clobbers); + ASSERT_EQ (0, reused_reg_last[i].uses_length); + ASSERT_EQ (0, reused_reg_last[i].clobbers_length); + } + + free (reused_reg_last); + sched_deps_finish (); + common_sched_info = saved_common_sched_info; + bitmap_obstack_release (&test_obstack); +} + /* Run the sched-deps.cc selftests. */ void sched_deps_cc_tests () { test_dirty_reg_last_release (); + test_reg_last_pool (); } } // namespace selftest diff --git a/gcc/testsuite/gcc.dg/sched-deps-debug-1.c b/gcc/testsuite/gcc.dg/sched-deps-debug-1.c new file mode 100644 index 000000000000..2b5cb7b7ad05 --- /dev/null +++ b/gcc/testsuite/gcc.dg/sched-deps-debug-1.c @@ -0,0 +1,27 @@ +/* A debug insn records a use in a reg_last entry that reg_last_in_use does not + cover. free_deps has to release those as well, or the reg_last array it + hands back still points at the freed region's insn lists. -fchecking=2 + checks an array before it is reused. */ + +/* { dg-do compile } */ +/* { dg-options "-O2 -g -fschedule-insns -fchecking=2" } */ +/* { dg-require-effective-target scheduling } */ + +void f1 (double); +void f2 (int); + +void +foo (int type, double xx) +{ + /* On the f2 path xx is dead, so what is left of it is a debug insn. */ + if (type) + f1 (xx); + else + f2 (type); +} + +void +bar (int type) +{ + foo (type, 1.0); +}