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'