> On Aug 13, 2026, at 01:07, Kyrylo Tkachov <[email protected]> wrote:
>
> Hello,
>
>> On 12 Aug 2026, at 12:39, wangjue <[email protected]> wrote:
>>
>> From: juewang <[email protected]>
>>
>> seq_cost treats every instruction in a replacement sequence as serial.
>> For superscalar targets this can overestimate the cost of independent
>> register operations and reject profitable if-conversion.
>>
>
> I like the idea, and it makes sense.
> One thing I’ve been hitting with my if-conversion work recently is that
> if-conversion increasing the dependency chain length is what’s hurting wide
> cores. Adding more instructions that can go parallel is not a problem
> otherwise.
Hi Jue, [please correct me if I guessed your first name wrong]
I've also reviewed this, and it generally looks good to me. Some questions and
comments below.
>
>> Estimate the dependency level of simple, single-cycle register operations
>> and cost each level using the target issue rate. Keep the existing serial
>> cost for size optimization and for sequences that need resource or alias
>> analysis.
>>
>
> I notice that mips has a pretty elaborate mips_seq_time mechanism. I wouldn’t
> advocate using it, just found it interesting when looking around.
>
>> gcc/ChangeLog:
>>
>> * ifcvt.cc (noce_parallel_cost_node): New.
>> (noce_parallel_seq_cost): New.
>> (default_noce_conversion_profitable_p): Use it.
>> ---
>> gcc/ifcvt.cc | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++-
>> 1 file changed, 69 insertions(+), 1 deletion(-)
>>
>> diff --git a/gcc/ifcvt.cc b/gcc/ifcvt.cc
>> index 5ea25f8fbe7..25874f006e6 100644
>> --- a/gcc/ifcvt.cc
>> +++ b/gcc/ifcvt.cc
>> @@ -803,6 +803,74 @@ noce_reversed_cond_code (struct noce_if_info *if_info)
>> return reversed_comparison_code (if_info->cond, if_info->jump);
>> }
>>
>> +/* A destination and its dependency level in a noce sequence. */
>> +
>> +struct noce_parallel_cost_node
>> +{
>> + rtx dest;
>> + unsigned int level;
>> +};
>> +
>> +/* Estimate the cost of SEQ using the target issue rate for independent,
>> + single-cycle register operations. */
>> +
>> +static unsigned int
>> +noce_parallel_seq_cost (rtx_insn *seq, bool speed_p)
>> +{
>> + unsigned int serial_cost = seq_cost (seq, speed_p);
>> +
>> + if (!speed_p)
>> + return serial_cost;
>> +
>> + unsigned int issue_rate = targetm.sched.issue_rate ();
>
> You first need to check that targetm.sched.issue_rate is non-null.
> I wonder if the schedule issue_rate is too coarse. The midend also uses
> reassociation width as an estimate of CPU parallelism. On aarch64 at least we
> describe different widths for int, fp, and vector reassociation.
> Have you considered using that hook instead?
> Thanks,
> Kyrill
>
>> + if (issue_rate <= 1)
>> + return serial_cost;
>> +
>> + auto_vec<noce_parallel_cost_node> nodes;
>> + auto_vec<unsigned int> insns_per_level;
>> +
>> + for (rtx_insn *insn = seq; insn; insn = NEXT_INSN (insn))
>> + {
>> + if (!NONDEBUG_INSN_P (insn))
>> + continue;
>> +
>> + rtx set = single_set (insn);
>> + if (!set)
>> + return serial_cost;
>> +
>> + rtx dest = SET_DEST (set);
>> + rtx src = SET_SRC (set);
>> +
>> + if (!REG_P (dest)
>> + || contains_mem_rtx_p (src)
>> + || side_effects_p (src)
>> + || may_trap_p (src)
>> + || set_rtx_cost (set, speed_p) != COSTS_N_INSNS (1))
>> + return serial_cost;
>> +
>> + unsigned int level = 0;
>> + for (unsigned int i = 0; i < nodes.length (); ++i)
>> + if (reg_overlap_mentioned_p (nodes[i].dest, src))
>> + level = MAX (level, nodes[i].level + 1);
>> +
>> + if (insns_per_level.length () <= level)
>> + insns_per_level.safe_grow_cleared (level + 1, true);
>> + ++insns_per_level[level];
>> +
>> + noce_parallel_cost_node node = { dest, level };
>> + nodes.safe_push (node);
>> + }
Could you, please, add comments as to what the above loop analyzes? After
staring at it for 5 minutes I'm /guessing/ that it searches for the longest
chain of instructions that set/use the same register? And then use the cost of
the longest chain as the "parallel" cost of the whole sequence?
>> +
>> + if (nodes.is_empty ())
>> + return serial_cost;
If I'm reading the code right, the only way "nodes" can be empty AND we reached
the above point is when all insns in the sequence are DEBUG_INSN. If that's
correct, I would appreciate a comment that the above is a corner-case check.
>> +
>> + unsigned int parallel_cost = 0;
>> + for (unsigned int i = 0; i < insns_per_level.length (); ++i)
>> + parallel_cost += COSTS_N_INSNS (CEIL (insns_per_level[i], issue_rate));
>> +
>> + return MIN (serial_cost, parallel_cost);
It would be interesting to have two data points here:
1. How often (percentage) the above analysis succeeds and we calculate
parallel_cost.
E.g., is it
- "parallel_cost calculation is successful in 10% of noce_parallel_seq_cost()
invocations."
or is it
- "parallel_cost calculation is successful in .1% of noce_parallel_seq_cost()
invocations."
?
2. What is the histogram of how far parallel_cost is from serial_cost?
E.g., something like this:
- 1.0 >= parallel_cost/serial_cost > 0.8: 50% probability
- 0.8 >= parallel_cost/serial_cost > 0.6: 25% probability
- 0.6 >= parallel_cost/serial_cost > 0.4: 15% probability
- 0.4 >= parallel_cost/serial_cost > 0.2: 8% probability
- 0.2 >= parallel_cost/serial_cost > 0.0: 2% probability
Having these data points (from, e.g., SPEC CPU or GCC bootstrap builds) will
set expectations for the future, when the next developer guesses whether it's
OK that parallel_cost is calculated only in 0.0001% of invocations.
Thanks!
--
Maxim Kuvyrkov
Garden City Compilers