kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

o1_inline_cap.sh (5686B)


      1 #!/usr/bin/env bash
      2 # Inline-pressure cap (O1.md W4).
      3 #
      4 # At -O1 there is no SSA post-inline cleanup, so the whole-program inliner
      5 # (opt_inline) backs off when the estimated post-inline live-set
      6 # (caller pregs + callee pregs) would sit past INLINE_PRESSURE_CAP (512). The cap
      7 # is set far above any target's register file, so it only fires on callers that
      8 # are already spilling heavily; a small, low-pressure caller still inlines the
      9 # exact same callee. This is one target-independent decision, so the structural
     10 # checks run identically for aarch64, x86_64, and riscv64.
     11 #
     12 # Green:
     13 #   1. A non-tiny callee (cost above the streaming tiny cap, so only the
     14 #      whole-program inliner handles it) fuses into a SMALL caller — no call
     15 #      left, proving the cap does not over-fire on ordinary code.
     16 #   2. The SAME callee called from a HUGE / high-pressure caller is NOT fused —
     17 #      the call survives, proving the cap backs off past its threshold.
     18 #   3. An always_inline callee fuses even into the huge caller (ALWAYS bypasses
     19 #      the cap, matching the cost/growth gates).
     20 set -euo pipefail
     21 
     22 ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
     23 KIT="${KIT:-$ROOT/build/kit}"
     24 WORK="$ROOT/build/test/opt/o1_inline_cap"
     25 mkdir -p "$WORK"
     26 
     27 call_mnemonics='\b(bl|blr|callq?|jalr?)\b'
     28 
     29 # Count calls inside one function body. The disassembly has no blank separators,
     30 # so walk from the function's `<name>:` header to the next symbol header.
     31 calls_in() { # $1=disfile $2=funcname
     32   awk -v f="<$2>:" '$0 ~ f {g = 1; next} /<[A-Za-z_].*>:/ {g = 0} g' "$1" \
     33     | grep -cE "$call_mnemonics" || true
     34 }
     35 
     36 # A callee whose cost sits above the streaming tiny-inline cap (8) but below the
     37 # DEFAULT whole-program cap (20): only the whole-program inliner can fuse it, so
     38 # this isolates the W4 cap on opt_inline.
     39 HELPER='static int helper(int x){ int a=x*3; int b=a+7; int c=b^a;'\
     40 ' int d=c*x; int e=d-b; return a+b+c+d+e+x; }'
     41 # always_inline twin: must fuse regardless of pressure.
     42 AHELPER='__attribute__((always_inline)) static int ahelper(int x){'\
     43 ' int a=x*3; int b=a+7; int c=b^a; int d=c*x; int e=d-b;'\
     44 ' return a+b+c+d+e+x; }'
     45 
     46 # Emit a caller that holds ~N*2 temporaries live across the helper call, pushing
     47 # the inline-time pregs estimate well past the 512 cap. N=300 -> ~600 pregs.
     48 emit_huge_caller() { # $1=name $2=callee $3=N
     49   printf 'int %s(int *p){\n  int s = %s(p[0])' "$1" "$2"
     50   local i
     51   for ((i = 0; i < $3; i++)); do printf ' + (p[%d]*%d ^ p[%d])' "$i" "$((i + 1))" "$((i + 2))"; done
     52   printf ';\n  return s;\n}\n'
     53 }
     54 
     55 build_src() { # writes the test TU to $1
     56   {
     57     printf '%s\n' "$HELPER"
     58     printf '%s\n' "$AHELPER"
     59     # Small, low-pressure caller of the plain helper: must fuse.
     60     printf 'int small(int *p){ return helper(p[0]) + p[1]; }\n'
     61     # Huge, high-pressure caller of the plain helper: must NOT fuse.
     62     emit_huge_caller huge helper 300
     63     # Huge caller of the always_inline helper: must fuse despite pressure.
     64     emit_huge_caller huge_always ahelper 300
     65   } > "$1"
     66 }
     67 
     68 check_arch() {
     69   local triple=$1 tag=$2
     70   local src="$WORK/$tag.c" obj="$WORK/$tag.o" dis="$WORK/$tag.dis"
     71   build_src "$src"
     72   "$KIT" cc -target "$triple" -O1 -ffreestanding -std=c11 -c "$src" -o "$obj" \
     73     > "$WORK/$tag.cc.out" 2>&1
     74   "$KIT" objdump -d "$obj" > "$dis" 2>&1
     75 
     76   local cs ch ca
     77   cs=$(calls_in "$dis" small)
     78   ch=$(calls_in "$dis" huge)
     79   ca=$(calls_in "$dis" huge_always)
     80 
     81   if [ "$cs" -ne 0 ]; then
     82     printf 'o1-inline-cap FAILED: %s small caller did NOT inline helper (%s call[s]) — cap over-fired\n' \
     83       "$tag" "$cs" >&2
     84     awk '/<small>:/{g=1;next} /<[A-Za-z_].*>:/{g=0} g' "$dis" | sed 's/^/  | /' >&2
     85     exit 1
     86   fi
     87   if [ "$ch" -eq 0 ]; then
     88     printf 'o1-inline-cap FAILED: %s huge/high-pressure caller inlined helper anyway — pressure cap did not back off\n' \
     89       "$tag" >&2
     90     exit 1
     91   fi
     92   if [ "$ca" -ne 0 ]; then
     93     printf 'o1-inline-cap FAILED: %s always_inline callee NOT fused into huge caller — cap ignored ALWAYS policy\n' \
     94       "$tag" >&2
     95     exit 1
     96   fi
     97   printf 'o1-inline-cap %-8s small fused, huge backed off, always_inline fused\n' "$tag"
     98 }
     99 
    100 check_arch aarch64-linux-gnu aa64
    101 check_arch x86_64-linux-gnu  x64
    102 check_arch riscv64-linux-gnu rv64
    103 
    104 # Metric corroboration: the whole-program inliner must report a growth/pressure
    105 # refusal at -O1 for the huge caller (the cap is folded into refuse_growth), and
    106 # must still inline the small caller — i.e. the cap fires selectively, not
    107 # blanket. Run via `kit run` so KIT_METRICS surfaces the finalize-sweep counters.
    108 read -r -d '' RUN_SRC <<'EOF' || true
    109 __attribute__((noinline)) static int sink(int x){ return x; }
    110 EOF
    111 build_src "$WORK/metric.c"
    112 printf 'int main(void){ int p[512]; for(int i=0;i<512;i++) p[i]=i;'\
    113 ' return (small(p) | huge(p) | huge_always(p)) ? 0 : 0; }\n' >> "$WORK/metric.c"
    114 if ! KIT_METRICS=1 "$KIT" run -O1 "$WORK/metric.c" >"$WORK/metric.out" 2>"$WORK/metric.err"; then
    115   printf 'o1-inline-cap FAILED: `kit run -O1` on the metric TU did not exit 0\n' >&2
    116   sed 's/^/  | /' "$WORK/metric.err" >&2
    117   exit 1
    118 fi
    119 # Some inlining happened (the small caller), and some was refused (the huge one,
    120 # via the growth/pressure bucket).
    121 if ! grep -q 'opt.inline.inlined' "$WORK/metric.err"; then
    122   printf 'o1-inline-cap FAILED: opt.inline.inlined metric absent at -O1\n' >&2
    123   sed -n '1,80p' "$WORK/metric.err" >&2
    124   exit 1
    125 fi
    126 if ! grep -q 'opt.inline.refuse_growth' "$WORK/metric.err"; then
    127   printf 'o1-inline-cap FAILED: no growth/pressure refusal recorded (cap never fired)\n' >&2
    128   sed -n '1,80p' "$WORK/metric.err" >&2
    129   exit 1
    130 fi
    131 printf 'o1-inline-cap run      inlined some, refused the over-pressure caller, exit 0\n'
    132 
    133 printf 'o1-inline-cap: ok\n'