bounce.sh (9410B)
1 #!/usr/bin/env bash 2 # test/bounce/bounce.sh — format-bounce stress test, on the shared corpus 3 # harness (test/lib/kit_corpus.sh). 4 # 5 # Goal: stress kit's object read/write/reloc, archive, and partial-link 6 # paths by taking one compiled program and *bouncing* its object through 7 # chains of format conversions and toolchain transforms, then verifying 8 # the relinked executable still produces the right answer. We already have 9 # cross-arch run coverage elsewhere; this test exists to exercise the 10 # format machinery, not the arches. 11 # 12 # Matrix = case x opt x tuple (one <arch>-linux tuple per requested arch). 13 # For each (case, opt, arch) item the program is compiled to a relocatable 14 # object once, then each applicable bounce CHAIN rebuilds an executable from 15 # it (the chain axis is iterated inside the lane, like wasm's multi-result 16 # lanes): 17 # 18 # direct baseline: link prog.o + crt.o straight through 19 # macho_rt ELF -> Mach-O -> ELF (objcopy) then link 20 # coff_rt ELF -> COFF -> ELF (objcopy) then link 21 # tri_rt ELF -> Mach-O -> COFF -> ELF (chained) then link 22 # ldr `ld -r` partial-link merge of prog.o+crt.o, then final link 23 # strip_dbg objcopy --strip-debug on prog.o, then link 24 # ar `ar rcs` prog.o into an archive, on-demand link with crt.o 25 # 26 # Each chain's executable is queued through kit_queue_e and run in ONE batched 27 # container exec at flush; its exit code is compared to a host-cc reference 28 # for that case. The chain transforms + compute_ref host-reference stay 29 # LANE-LOCAL. Every lane hook writes only under $KIT_WORK and records via kit_*, 30 # so the runner is parallel-safe; KIT_BOUNCE_PARALLEL flips dispatch. 31 # 32 # Arch selection: defaults to the host-native Linux arch (fast, no 33 # emulation). Override with KIT_BOUNCE_ARCHES="aarch64 x64 rv64" to sweep 34 # others through podman/qemu. Skip is a failure unless KIT_TEST_ALLOW_SKIP=1. 35 set -u 36 37 ROOT="$(cd "$(dirname "$0")/../.." && pwd)" 38 BIN="${KIT_BIN:-$ROOT/build/kit}" 39 HOSTCC="${CC:-cc}" 40 BUILD="$ROOT/build/test/bounce" 41 CRT="$ROOT/test/bounce/crt/crt.c" 42 OPT_LEVELS="${KIT_BOUNCE_OPTS:-0 1}" 43 44 export KIT_LIB_DIR="$ROOT/test/lib" 45 # shellcheck source=../lib/kit_corpus.sh 46 . "$ROOT/test/lib/kit_corpus.sh" 47 48 rm -rf "$BUILD" 49 mkdir -p "$BUILD" 50 51 # ---- arch selection -------------------------------------------------------- 52 53 case "$(uname -m 2>/dev/null)" in 54 arm64|aarch64) NATIVE=aarch64 ;; 55 x86_64) NATIVE=x64 ;; 56 *) NATIVE="" ;; 57 esac 58 ARCHES="${KIT_BOUNCE_ARCHES:-$NATIVE}" 59 60 arch_triple() { 61 case "$1" in 62 aarch64) echo aarch64-linux ;; 63 x64) echo x86_64-linux ;; 64 rv64) echo riscv64-linux ;; 65 *) echo "" ;; 66 esac 67 } 68 arch_define() { 69 case "$1" in 70 aarch64) echo BOUNCE_AARCH64 ;; 71 x64) echo BOUNCE_X64 ;; 72 rv64) echo BOUNCE_RV64 ;; 73 *) echo "" ;; 74 esac 75 } 76 77 # ---- exec_target wiring ---------------------------------------------------- 78 79 have_podman=0; command -v podman >/dev/null 2>&1 && have_podman=1 80 have_qemu=0; QEMU_BIN="" 81 case "$(uname -m 2>/dev/null)" in arm64|aarch64) is_aarch64=1 ;; *) is_aarch64=0 ;; esac 82 export have_podman have_qemu QEMU_BIN is_aarch64 83 EXEC_TARGET_MOUNT_ROOT="$BUILD" 84 export EXEC_TARGET_MOUNT_ROOT 85 # shellcheck source=../lib/exec_target.sh 86 . "$ROOT/test/lib/exec_target.sh" 87 88 # ---- prerequisites --------------------------------------------------------- 89 90 if [ ! -x "$BIN" ]; then 91 echo "missing $BIN — run \`make bin\` first" >&2 92 exit 2 93 fi 94 if [ -z "$ARCHES" ]; then 95 kit_skip "arch" "unsupported host arch $(uname -m) — set KIT_BOUNCE_ARCHES" 96 kit_summary test-bounce; kit_exit 97 fi 98 if ! "$HOSTCC" -x c -c /dev/null -o /dev/null 2>/dev/null; then 99 kit_skip "hostcc" "host '$HOSTCC' cannot compile — set CC" 100 kit_summary test-bounce; kit_exit 101 fi 102 103 CASES_GLOB="$ROOT/test/bounce/cases/*.c" 104 # shellcheck disable=SC2086 105 if ! ls $CASES_GLOB >/dev/null 2>&1; then 106 echo "no cases under test/bounce/cases" >&2 107 exit 2 108 fi 109 110 # ---- per-case host reference (LANE-LOCAL) ---------------------------------- 111 # Reference exit code for a case = running it on the host. The cases are 112 # portable LP64 integer C, so the host result is the oracle every target 113 # and every bounce chain must reproduce. Writes only under $KIT_WORK. 114 compute_ref() { 115 local case="$1" ref_exe="$KIT_WORK/ref" drv="$KIT_WORK/refmain.c" 116 printf 'int bounce_main(void);\nint main(void){return bounce_main();}\n' >"$drv" 117 if ! "$HOSTCC" -O1 "$case" "$drv" -o "$ref_exe" 2>"$KIT_WORK/ref.err"; then 118 return 1 119 fi 120 "$ref_exe"; echo $? 121 } 122 123 # ---- bounce chains (LANE-LOCAL) -------------------------------------------- 124 # Each chain takes prog.o + crt.o and produces an executable at $out, or 125 # returns nonzero (with a diagnostic on stdout) if any transform/link fails. 126 # $1=prog.o $2=crt.o $3=out $4=workdir 127 chain_direct() { "$BIN" ld -o "$3" -e _start -static "$1" "$2" 2>&1; } 128 chain_macho_rt() { 129 "$BIN" objcopy -O mach-o "$1" "$4/m.o" 2>&1 || return 1 130 "$BIN" objcopy -O elf "$4/m.o" "$4/m2.o" 2>&1 || return 1 131 "$BIN" ld -o "$3" -e _start -static "$4/m2.o" "$2" 2>&1 132 } 133 chain_coff_rt() { 134 "$BIN" objcopy -O coff "$1" "$4/c.o" 2>&1 || return 1 135 "$BIN" objcopy -O elf "$4/c.o" "$4/c2.o" 2>&1 || return 1 136 "$BIN" ld -o "$3" -e _start -static "$4/c2.o" "$2" 2>&1 137 } 138 chain_tri_rt() { 139 "$BIN" objcopy -O mach-o "$1" "$4/t1.o" 2>&1 || return 1 140 "$BIN" objcopy -O coff "$4/t1.o" "$4/t2.o" 2>&1 || return 1 141 "$BIN" objcopy -O elf "$4/t2.o" "$4/t3.o" 2>&1 || return 1 142 "$BIN" ld -o "$3" -e _start -static "$4/t3.o" "$2" 2>&1 143 } 144 chain_ldr() { 145 "$BIN" ld -r -o "$4/merged.o" "$1" "$2" 2>&1 || return 1 146 "$BIN" ld -o "$3" -e _start -static "$4/merged.o" 2>&1 147 } 148 chain_strip_dbg() { 149 "$BIN" objcopy --strip-debug "$1" "$4/s.o" 2>&1 || return 1 150 "$BIN" ld -o "$3" -e _start -static "$4/s.o" "$2" 2>&1 151 } 152 chain_ar() { 153 rm -f "$4/lib.a" 154 "$BIN" ar rcs "$4/lib.a" "$1" 2>&1 || return 1 155 "$BIN" ld -o "$3" -e _start -static "$2" "$4/lib.a" 2>&1 156 } 157 CHAINS="direct macho_rt coff_rt tri_rt ldr strip_dbg ar" 158 159 # A chain is only meaningful when every object format it routes through is 160 # a real kit target for that arch. Support matrix: 161 # ELF all arches (Linux/freestanding) 162 # COFF x64 + aarch64 (Windows) 163 # Mach-O aarch64 only (Apple Silicon; kit does not target x64 Mach-O) 164 # Returns 0 when $chain is applicable for $arch. 165 chain_applies() { 166 local arch="$1" chain="$2" 167 case "$chain" in 168 macho_rt | tri_rt) # route through Mach-O 169 [ "$arch" = aarch64 ] ;; 170 coff_rt) # route through COFF 171 case "$arch" in aarch64 | x64) return 0 ;; *) return 1 ;; esac ;; 172 *) return 0 ;; # ELF-only: direct, ldr, strip_dbg, ar 173 esac 174 } 175 176 # ---- lane B: compile prog.o, then run every applicable chain --------------- 177 # KIT_ARCH is the corpus tuple's arch (aarch64/x64/rv64); KIT_OBJ is "linux". 178 # crt + host reference + prog.o are built here (KIT_WORK-confined). Each chain's 179 # executable is deferred via kit_queue_e and scored at flush (rc == host ref). 180 kit_lane_B() { 181 local arch="$KIT_ARCH" exec_tag="$KIT_ARCH-linux" 182 # Disambiguate result names by arch+opt (the engine's KIT_NAME does not carry 183 # the tuple), matching the original "<name>.<arch>.O<opt>.<chain>" labels. 184 local stem="$KIT_BASE.$arch.O$KIT_OPT" 185 local triple define 186 triple="$(arch_triple "$arch")" 187 define="$(arch_define "$arch")" 188 if [ -z "$triple" ]; then kit_skip "$stem" "unknown arch tag"; return; fi 189 if ! exec_target_supported "$exec_tag"; then 190 kit_skip "$stem" "no runner (need podman or qemu for $arch-linux)"; return 191 fi 192 193 # crt depends only on (arch, opt); built per-item under $KIT_WORK. 194 local crt_o="$KIT_WORK/crt.o" 195 if ! "$BIN" cc -target "$triple" -D"$define" -O"$KIT_OPT" -c "$CRT" \ 196 -o "$crt_o" 2>"$KIT_WORK/crt.err"; then 197 kit_fail "$stem" "crt.c (see $KIT_WORK/crt.err)"; return 198 fi 199 200 local ref 201 ref="$(compute_ref "$KIT_SRC")" 202 if [ -z "$ref" ]; then 203 kit_fail "$stem" "reference build (host cc; see $KIT_WORK/ref.err)"; return 204 fi 205 206 local prog_o="$KIT_WORK/prog.o" 207 if ! "$BIN" cc -target "$triple" -O"$KIT_OPT" -c "$KIT_SRC" -o "$prog_o" \ 208 2>"$KIT_WORK/prog.err"; then 209 kit_fail "$stem" "compile (see $KIT_WORK/prog.err)"; return 210 fi 211 212 local chain tag wd exe 213 for chain in $CHAINS; do 214 chain_applies "$arch" "$chain" || continue 215 tag="$stem.$chain" 216 wd="$KIT_WORK/$chain.d"; mkdir -p "$wd" 217 exe="$KIT_WORK/$chain.exe" 218 if ! "chain_$chain" "$prog_o" "$crt_o" "$exe" "$wd" \ 219 >"$KIT_WORK/$chain.link.log" 2>&1; then 220 kit_fail "$tag" "transform/link; see $KIT_WORK/$chain.link.log" 221 continue 222 fi 223 # Defer exec: the engine batches all queued cases into one container run 224 # at flush, then scores rc against the host reference (EXPECTED). 225 kit_queue_e "$tag" "$exe" \ 226 "$KIT_WORK/$chain.out" "$KIT_WORK/$chain.err" "$KIT_WORK/$chain.rc" \ 227 "$ref" "$exec_tag" 228 done 229 } 230 231 # ---- drive the corpus ------------------------------------------------------ 232 # One tuple per requested arch; opt axis from KIT_BOUNCE_OPTS; single lane B. 233 TUPLES= 234 for arch in $ARCHES; do 235 TUPLES="$TUPLES $arch-linux" 236 done 237 238 KIT_LABEL=test-bounce KIT_BUILD_DIR="$BUILD/work" \ 239 KIT_CORPUS_GLOBS="$CASES_GLOB" KIT_CORPUS_EXT=c KIT_SIDECAR_DIR="$ROOT/test/bounce/cases" \ 240 KIT_LANES="B" KIT_OPT_LEVELS="$OPT_LEVELS" KIT_TUPLES="$TUPLES" KIT_TARGETS_EXT="" \ 241 KIT_PARALLELIZABLE="${KIT_BOUNCE_PARALLEL:-1}" kit_corpus_run 242 243 kit_summary test-bounce 244 kit_exit