> On 25 Aug 2026, at 12:36, Richard Biener <[email protected]> wrote: > > On Mon, Aug 24, 2026 at 8:40 PM <[email protected]> wrote: >> >> From: Kyrylo Tkachov <[email protected]> >> >> branch_prob () cannot place a counter on an abnormal edge. It >> represents the flow of a computed goto with fake edges. One fake edge >> goes from the entry block to each target. Another goes from the >> dispatch block to the exit block. branch_prob () also marks the >> abnormal edges as ignored. An ignored edge that is not on the spanning >> tree is not instrumented. The graph solver does not infer its count. >> Its count therefore remains at the default zero, whatever the program >> did. >> >> compute_branch_probabilities () then turns that zero into a precise >> never. A precise never is proof of deadness. >> propagate_unlikely_bbs_forward () cannot reach any dispatch target from >> the entry block, and determine_unlikely_bbs () zeroes the measured >> counts of the whole region. For a threaded interpreter that region is >> the entire hot loop. Thus, -fprofile-use reads measured block counts >> and then discards them. >> >> For >> >> static const void *tab[] = { &&L_ADD, ... }; >> goto *tab[*pc++]; >> >> -fdump-ipa-profile shows 5000 executions for the dispatch block and 1000 >> for each handler. The optimized dump then reads >> >> ;; basic block 5, loop depth 1, count 0 (precise), probably never executed >> >> for every one of them. >> >> Record the probability of an ignored edge outside the spanning tree as a >> guess. A guessed never is still a zero probability. It does not invent >> a split between the dispatch targets. A target with a measured zero >> count also stays at zero. The guessed probability stops the unmeasured >> edge count from being evidence that the target is dead. The same dump >> now reads >> >> ;; basic block 5, loop depth 1, count 5000 (precise), maybe hot >> >> The rule is applied per edge and attributes no flow. It does not depend >> on how many dispatch blocks a function has. It also needs no notion of >> which dispatch a target belongs to. >> >> Evaluated on aarch64 (NVIDIA Vera) with CPython 3.14.5 at -O3. CPython >> was configured with --enable-optimizations and --with-lto. In >> Python/ceval.c, the share of the basic blocks of >> _PyEval_EvalFrameDefault left with a precise zero count falls from 98.3% >> to 23.1%. During the full-program LTO link, the function grows by 0.7% >> and the interpreter text grows by 0.1%. A fixed-work >> pyperformance-derived workload takes 2.5% fewer cycles. >> >> Bootstrapped and tested on aarch64-none-linux-gnu. > > Wouldn't UNINITIALIZED be what actually happens? We simply don't know > due to lack of counters. Or do we ICE on that later, requiring sth else than > UNINITIALIZED?
Yes, UNINITIALIZED describes the information we have, but it cannot remain in that state here. The earlier profile-estimation pass has already set cfun->cfg->full_profile. I tested setting these edge probabilities to UNINITIALIZED. The cleanup after branch_prob () then reports “probability of edge ... not initialized” and fails with verify_flow_info failed. Thanks, Kyrill > >> gcc/ChangeLog: >> >> * profile.cc (compute_branch_probabilities): Mark probabilities of >> ignored edges outside the spanning tree as guessed. >> >> gcc/testsuite/ChangeLog: >> >> * gcc.dg/tree-prof/comp-goto-2.c: New test. >> >> Signed-off-by: Kyrylo Tkachov <[email protected]> >> --- >> gcc/profile.cc | 5 ++ >> gcc/testsuite/gcc.dg/tree-prof/comp-goto-2.c | 61 ++++++++++++++++++++ >> 2 files changed, 66 insertions(+) >> create mode 100644 gcc/testsuite/gcc.dg/tree-prof/comp-goto-2.c >> >> diff --git a/gcc/profile.cc b/gcc/profile.cc >> index 5ae64d4043f..1683794e5e3 100644 >> --- a/gcc/profile.cc >> +++ b/gcc/profile.cc >> @@ -719,6 +719,11 @@ compute_branch_probabilities (unsigned cfg_checksum, >> unsigned lineno_checksum) >> bool prev_never = e->probability == profile_probability::never >> (); >> e->probability = profile_probability::probability_in_gcov_type >> (edge_gcov_count (e), bb_gcov_count (bb)); >> + /* Ignored edges outside the spanning tree have neither a >> counter >> + nor an inferred count. Do not treat their zero count as a >> + precise never. */ >> + if (EDGE_INFO (e)->ignore && !EDGE_INFO (e)->on_tree) >> + e->probability = e->probability.guessed (); >> if (e->probability == profile_probability::never () >> && !prev_never >> && flag_profile_partial_training) >> diff --git a/gcc/testsuite/gcc.dg/tree-prof/comp-goto-2.c >> b/gcc/testsuite/gcc.dg/tree-prof/comp-goto-2.c >> new file mode 100644 >> index 00000000000..d005b52bc60 >> --- /dev/null >> +++ b/gcc/testsuite/gcc.dg/tree-prof/comp-goto-2.c >> @@ -0,0 +1,61 @@ >> +/* The ignored edges out of a computed goto are outside the spanning tree, >> + so their zero count used to become a precise "never" and mark every >> opcode >> + handler as dead. Check that the measured counts survive -fprofile-use. >> */ >> +/* { dg-options "-O2 -fdump-tree-optimized-blocks-details" } */ >> +/* { dg-require-effective-target indirect_jumps } */ >> +/* { dg-require-effective-target label_values } */ >> + >> +enum { OP_ADD, OP_SUB, OP_MUL, OP_STORE, OP_HALT, NOPS }; >> + >> +unsigned char code[8]; >> +long mem[4] = { 1, 2, 3, 4 }; >> + >> +__attribute__((noinline)) long >> +interp (const unsigned char *pc) >> +{ >> + static const void *const tab[NOPS] >> + = { &&L_ADD, &&L_SUB, &&L_MUL, &&L_STORE, &&L_HALT }; >> + long acc = 0; >> + >> +#define DISPATCH() goto *tab[*pc++] >> + >> + DISPATCH (); >> +L_ADD: >> + acc += mem[*pc++]; >> + DISPATCH (); >> +L_SUB: >> + acc -= mem[*pc++]; >> + DISPATCH (); >> +L_MUL: >> + acc *= 3; >> + DISPATCH (); >> +L_STORE: >> + mem[*pc++] = acc; >> + DISPATCH (); >> +L_HALT: >> + return acc; >> +} >> + >> +int >> +main (void) >> +{ >> + long r = 0; >> + int i; >> + >> + code[0] = OP_ADD; code[1] = 1; >> + code[2] = OP_SUB; code[3] = 2; >> + code[4] = OP_MUL; >> + code[5] = OP_STORE; code[6] = 3; >> + code[7] = OP_HALT; >> + >> + for (i = 0; i < 1000; i++) >> + r += interp (code); >> + >> + return r == 0; >> +} >> + >> +/* No block of the dispatch loop may be left with a precise zero count. */ >> +/* { dg-final-use-not-autofdo { scan-tree-dump-not "count 0 \\(precise" >> "optimized" } } */ >> +/* The ignored non-spanning-tree edges carry no measurement, so they must be >> + a guess. */ >> +/* { dg-final-use-not-autofdo { scan-tree-dump "never \\(guessed\\)" >> "optimized" } } */ >> -- >> 2.50.1 (Apple Git-155) >>
