kit

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

o1p_const_divmul.sh (5303B)


      1 #!/usr/bin/env bash
      2 # Structural checks for the -O1 constant-operand integer divide/multiply fold
      3 # (O1-PATTERNS.md L9), in src/opt/pass_simplify.c (opt_simplify_local):
      4 #
      5 #   A. A `sizeof(X)/sizeof(Y)`-style const divide -- where kit materializes both
      6 #      constants into registers (`movz`) and issues a runtime `udiv` -- folds at
      7 #      compile time to a single `movz` of the quotient: NO `udiv`/`sdiv`.
      8 #   B. The Lua growvector guard shape `(SIZE_MAX / sizeof(T)) >= N` (N also a
      9 #      compile-time constant) folds the udiv to a constant, then the always-true
     10 #      compare is decided too, collapsing the whole function to a `movz #1`.
     11 #   C. A divide-by-1 whose `1` survives in a (callee-saved) register across a
     12 #      call -- so it reaches the divide as a REGISTER operand, the lparser
     13 #      udiv-by-1 shape -- folds away: no `udiv`.
     14 #   D. NEGATIVE (the load-bearing guard): a divide whose divisor is a known
     15 #      compile-time ZERO must NOT be folded; the runtime `sdiv`/`udiv` stays so
     16 #      the program's division-fault behavior is preserved. Likewise a divide by a
     17 #      genuinely runtime value keeps its div.
     18 #
     19 # The fold is a same-block forward LOAD_IMM->known-value tracker feeding the
     20 # existing identity folds + a const-op-const fold; it keeps -O1 linear (one
     21 # forward scan per block, no SSA). The checks pin the resulting disassembly on
     22 # aarch64 (the reference backend), where the patterns have stable mnemonics.
     23 set -euo pipefail
     24 
     25 ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
     26 KIT="${KIT:-$ROOT/build/kit}"
     27 WORK="$ROOT/build/test/opt/o1p_const_divmul"
     28 mkdir -p "$WORK"
     29 
     30 SRC="$WORK/case.c"
     31 cat > "$SRC" <<'EOF'
     32 #include <stdint.h>
     33 struct Big { char a[40]; };
     34 struct Small { char a[8]; };
     35 extern void barrier(void);
     36 
     37 /* A: sizeof/sizeof const divide -> quotient (40/8 == 5). */
     38 unsigned long arr_count(void) {
     39   return sizeof(struct Big) / sizeof(struct Small);
     40 }
     41 /* B: SIZE_MAX/sizeof(T) >= N growvector guard, N a constant -> always true. */
     42 int growvec_guard(void) {
     43   return (SIZE_MAX / sizeof(struct Small)) >= 1000000;
     44 }
     45 /* C: divide-by-1 where the 1 reaches the udiv as a register (lives across a
     46  *    call), so the inline-immediate path can't see it -- still folds away. */
     47 unsigned long div_by_one_reg(unsigned long x) {
     48   unsigned long one = 1;
     49   barrier();
     50   return x / one;
     51 }
     52 /* D-zero: divisor is a compile-time ZERO -> MUST keep the div (trap behavior). */
     53 int div_by_zero(int x) {
     54   int z = 0;
     55   return x / z;
     56 }
     57 /* D-runtime: divisor is a runtime value -> MUST keep the div. */
     58 int div_runtime(int x, int y) { return x / y; }
     59 EOF
     60 
     61 OBJ="$WORK/case.o"
     62 "$KIT" cc -target aarch64-linux-gnu -O1 -std=c11 -c "$SRC" \
     63   -o "$OBJ" > "$WORK/cc.out" 2>&1
     64 "$KIT" objdump -d "$OBJ" > "$WORK/dis.out" 2>&1
     65 
     66 fn_body() { # $1 = symbol name -> stdout body
     67   awk -v want="$1" '
     68     $0 ~ ("^[0-9a-f]+ <" want ">:") { in_fn = 1; next }
     69     /^[0-9a-f]+ </ { in_fn = 0 }
     70     in_fn { print }
     71   ' "$WORK/dis.out"
     72 }
     73 
     74 fail() {
     75   printf 'o1p_const_divmul FAILED: %s\n' "$1" >&2
     76   printf '  --- disassembly ---\n' >&2
     77   sed 's/^/  | /' "$WORK/dis.out" >&2
     78   exit 1
     79 }
     80 
     81 # A: arr_count -- no div, and the quotient 5 is materialized (movz ...,0x5).
     82 A="$(fn_body _arr_count)"
     83 [ -n "$A" ] || A="$(fn_body arr_count)"
     84 [ -n "$A" ] || fail "arr_count not found"
     85 if printf '%s\n' "$A" | grep -Eq '\b[us]div\b'; then
     86   fail "arr_count kept a div (const sizeof/sizeof divide not folded)"
     87 fi
     88 printf '%s\n' "$A" | grep -Eq 'movz[[:space:]]+x[0-9]+, 0x5\b' \
     89   || fail "arr_count did not materialize the folded quotient 5 (movz xN,0x5)"
     90 
     91 # B: growvec_guard -- the udiv is gone, the always-true compare is decided, so
     92 #    the whole function collapses to `movz wN,#1` (no div, no cmp, no cset).
     93 B="$(fn_body _growvec_guard)"
     94 [ -n "$B" ] || B="$(fn_body growvec_guard)"
     95 [ -n "$B" ] || fail "growvec_guard not found"
     96 if printf '%s\n' "$B" | grep -Eq '\b[us]div\b'; then
     97   fail "growvec_guard kept a div (SIZE_MAX guard udiv not folded)"
     98 fi
     99 if printf '%s\n' "$B" | grep -Eq '\b(cmp|cset)\b'; then
    100   fail "growvec_guard kept a compare (always-true guard not decided)"
    101 fi
    102 printf '%s\n' "$B" | grep -Eq 'movz[[:space:]]+w[0-9]+, 0x1\b' \
    103   || fail "growvec_guard did not fold to a constant 1"
    104 
    105 # C: div_by_one_reg -- no div survives (divide-by-1 folded to a copy).
    106 C="$(fn_body _div_by_one_reg)"
    107 [ -n "$C" ] || C="$(fn_body div_by_one_reg)"
    108 [ -n "$C" ] || fail "div_by_one_reg not found"
    109 if printf '%s\n' "$C" | grep -Eq '\b[us]div\b'; then
    110   fail "div_by_one_reg kept a udiv (register divide-by-1 not folded)"
    111 fi
    112 
    113 # D-zero NEGATIVE: div_by_zero MUST still emit the div -- folding /0 would
    114 #    silently drop the fault. This is the critical correctness guard.
    115 DZ="$(fn_body _div_by_zero)"
    116 [ -n "$DZ" ] || DZ="$(fn_body div_by_zero)"
    117 [ -n "$DZ" ] || fail "div_by_zero not found"
    118 printf '%s\n' "$DZ" | grep -Eq '\b[us]div\b' \
    119   || fail "div_by_zero lost its div -- /0 was folded (FAULT BEHAVIOR BROKEN)"
    120 
    121 # D-runtime NEGATIVE: div_runtime MUST still emit the div.
    122 DR="$(fn_body _div_runtime)"
    123 [ -n "$DR" ] || DR="$(fn_body div_runtime)"
    124 [ -n "$DR" ] || fail "div_runtime not found"
    125 printf '%s\n' "$DR" | grep -Eq '\b[us]div\b' \
    126   || fail "div_runtime lost its div (a runtime divisor was wrongly folded)"
    127 
    128 printf 'o1p_const_divmul: OK (sizeof/sizeof fold, SIZE_MAX guard, reg div-by-1; /0 + runtime div kept)\n'