https://gcc.gnu.org/bugzilla/show_bug.cgi?id=126913

            Bug ID: 126913
           Summary: -E column padding is quadratic and its readability
                    rationale does not hold
           Product: gcc
           Version: 16.1.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: preprocessor
          Assignee: unassigned at gcc dot gnu.org
          Reporter: ismael at linux dot com
  Target Milestone: ---

cpp pads with spaces so a token lands in its original source column. The
documented reason is "so the output is easy to read".

The padding does not fire only at line starts. Every linemarker restarts it,
and an object-like macro from a system header emits a linemarker pair
mid-line. So a long line with M such macros pays the column cost M times:
output is O(M * line_length).

Reproducer:

  for n in 1000 2000 4000 8000; do
    { printf '#include <stdbool.h>\nstatic const _Bool a[] = {'
      awk -v n=$n 'BEGIN{for(i=0;i<n;i++) printf "true,"}'
      printf '0};\n'; } > b$n.c
    gcc -E b$n.c -o b$n.i
    echo "N=$n src=$(stat -c %s b$n.c) -E=$(stat -c %s b$n.i)"
  done

  N=1000  src=5055   -E=5093292
  N=2000  src=10055  -E=20186292
  N=4000  src=20055  -E=80372292
  N=8000  src=40055  -E=320744292

Doubling N quadruples output. 40 KB in, 320 MB out, ~99.9% spaces.

Two conditions are required. The macro must be object-like: function-like
UINT8_C(c)->c yields the caller's own token, no context switch, no padding.
And it must come from a system header: an identical user-defined macro does
not trigger it.

Real case: zig 0.16.0's generated zig2.c (222 MB, longest line 575,257
chars, 223,092 true/false from <stdbool.h>). Under ccache, which runs a
standalone cc -E on a direct-mode miss, the .i reached 34,209,846,741 bytes
and filled a 100 GB filesystem, wedging cc1 in D state on ENOSPC. The last
gigabyte was one unbroken run of spaces.

--------------------------------------------------------------------------
On the rationale
--------------------------------------------------------------------------

Nobody reads column 287,000. The feature buys nothing past roughly the width
of a terminal, and past that it is pure cost — here, three orders of
magnitude of whitespace over real token content, with no diagnostic.

-E output is overwhelmingly machine input now: ccache, distcc/icecream,
--save-temps, editor tooling. Optimising its byte layout for a human reader
is the wrong default, and doing so unboundedly is a denial of service.

Suggested fix, cheapest first:

  1. Cap padding at some sane width (~200 cols) and emit a single space
     beyond it.
  2. Or re-pad only at genuine source line starts, not after linemarkers
     emitted mid-line for an expansion context switch.

-ftrack-macro-expansion=0 avoids it today (N=4000: 8,205 bytes, linemarkers
intact, byte-identical object), which locates the trigger in the expansion
location tracking.

--------------------------------------------------------------------------

Nothing filed for this: 0 hits across all comment text for "appears in the
same column" and variants, and ~17 quicksearch queries over
component:preprocessor found nothing matching. Adjacent but distinct:

  PR 53525   track-macro-expansion performance regression. Same subsystem;
             CPU, not output size.
  PR 124679  cpp to infinite memory on systemtap-generated C. Sibling
             generated-code case; RAM in collect_args, function-like macros.
  PR 60723   Line directives with incorrect system header flag. Same
             machinery, correctness only.

Reply via email to