redundant_copy_ext.sh (3375B)
1 #!/usr/bin/env bash 2 # Structural checks for the -O1 redundant copy / extension canonicalizations: 3 # 4 # A. simplify_addr_of: addr_of [base + 0] (the `&*p` / `p+0` idiom) folds to 5 # a copy, so it no longer lowers to a `add base, #0` register move that 6 # copy-prop must then chase. 7 # B. convert_noop: a same-width, same-class convert (e.g. a pointer 8 # bitcast `int* -> char*`) folds to a copy that propagates away. 9 # C. try_combine_exts: a ZEXT of a plain (zero-extending) narrow load is 10 # redundant -- the load already cleared the upper bits -- so it collapses 11 # to a copy and usually disappears. 12 # 13 # All three are local, target-agnostic, and keep -O1 linear. The checks below 14 # pin the resulting disassembly on aarch64 (the reference backend), where the 15 # patterns have stable mnemonics (`add xN, xM, #0`, `uxtb wD, wS` after a 16 # zero-extending `ldrb`). 17 set -euo pipefail 18 19 ROOT="$(cd "$(dirname "$0")/../.." && pwd)" 20 KIT="${KIT:-$ROOT/build/kit}" 21 WORK="$ROOT/build/test/opt/redundant_copy_ext" 22 mkdir -p "$WORK" 23 24 SRC="$WORK/case.c" 25 cat > "$SRC" <<'EOF' 26 struct T { long a, b; }; 27 /* A: struct copy through `&*p` -- the frontend records addr_of[base+0]. */ 28 void copy_struct(struct T *d, struct T *s) { *d = *s; } 29 /* B: a pointer bitcast that records a same-width convert. */ 30 unsigned char *as_bytes(unsigned long *p) { return (unsigned char *)p; } 31 /* C: integer promotion of a freshly loaded narrow unsigned value. */ 32 int low_nibble_is_four(const unsigned char *p) { return (p[0] & 0xf) == 4; } 33 EOF 34 35 OBJ="$WORK/case.o" 36 "$KIT" cc -target aarch64-linux-gnu -O1 -std=c11 -c "$SRC" \ 37 -o "$OBJ" > "$WORK/cc.out" 2>&1 38 "$KIT" objdump -d "$OBJ" > "$WORK/dis.out" 2>&1 39 40 fn_body() { # $1 = symbol name -> stdout body 41 awk -v want="$1" ' 42 $0 ~ ("^[0-9a-f]+ <" want ">:") { in_fn = 1; next } 43 /^[0-9a-f]+ </ { in_fn = 0 } 44 in_fn { print } 45 ' "$WORK/dis.out" 46 } 47 48 fail() { 49 printf 'redundant_copy_ext FAILED: %s\n' "$1" >&2 50 printf ' --- disassembly ---\n' >&2 51 sed 's/^/ | /' "$WORK/dis.out" >&2 52 exit 1 53 } 54 55 # A: no `add xN, xM, #0` move-as-add anywhere (every copy_struct / as_bytes 56 # address pass should be a real load/store or a copy that propagated away). 57 if grep -Eq 'add\sx[0-9]+, x[0-9]+, #0$' "$WORK/dis.out"; then 58 fail "found 'add xN, xM, #0' (unfolded addr_of[base+0] copy)" 59 fi 60 61 # B: as_bytes is a pure pointer bitcast -- the whole body is prologue/epilogue 62 # plus a single move of the argument into the return register; there must be 63 # no convert/extend instruction for the cast. 64 B="$(fn_body _as_bytes)" 65 [ -n "$B" ] || B="$(fn_body as_bytes)" 66 [ -n "$B" ] || fail "as_bytes not found in disassembly" 67 if printf '%s\n' "$B" | grep -Eq '\b(uxtb|uxth|uxtw|sxtb|sxth|sxtw)\b'; then 68 fail "as_bytes pointer bitcast emitted an extension" 69 fi 70 71 # C: low_nibble_is_four loads a byte (zero-extending ldrb) and must NOT follow 72 # it with a redundant uxtb -- the ZEXT collapsed into the load. 73 C="$(fn_body _low_nibble_is_four)" 74 [ -n "$C" ] || C="$(fn_body low_nibble_is_four)" 75 [ -n "$C" ] || fail "low_nibble_is_four not found in disassembly" 76 printf '%s\n' "$C" | grep -Eq '\bldrb\b' || fail "expected a ldrb in low_nibble_is_four" 77 if printf '%s\n' "$C" | grep -Eq '\buxtb\b'; then 78 fail "redundant uxtb after zero-extending ldrb survived" 79 fi 80 81 printf 'redundant_copy_ext: OK (addr_of[base+0], pointer bitcast, zext-of-load)\n'