hostas_cross.sh (15709B)
1 #!/usr/bin/env bash 2 # test/asm/hostas_cross.sh — cross-compile + cross-exec extension of the 3 # host-assembler lane (test/asm/hostas_toy.sh) to ELF Linux targets. 4 # (Type C — shared corpus harness; one kit_corpus_run per ELF target.) 5 # 6 # Where hostas_toy.sh proves `cc -S` on the *native* target, this proves it 7 # CROSS: for each ELF target (aarch64/x86_64/riscv64-linux) it emits ONE 8 # `cc -S`, feeds it to BOTH kit-as and a host assembler (clang), links each 9 # into a static ELF with `kit ld`, and runs it under podman/qemu — exit must 10 # match the toy oracle. The assembler is the only variable between the two 11 # lanes, judged by EXECUTION (kit and clang emit different code, so a 12 # byte/text match would be meaningless), exactly like hostas_toy.sh. 13 # 14 # Per toy case (each target; both O0 and O1): 15 # kit cc -S -target <triple> -> s.s (shared by both) 16 # A /kit-as: kit as -target | kit ld -static -> run exit == oracle 17 # B /clang-as: clang --target -c | kit ld -static -> run exit == oracle 18 # 19 # Executable shape: a STATIC, non-PIE ELF (`kit ld -static`) linked with the 20 # freestanding crt test/link/harness/start.c compiled `-Dtest_main=main`, so 21 # `_start` runs ctors then calls the toy's `main` and exits with its return 22 # (the oracle) via a raw syscall — no libc/loader needed, so any same-arch 23 # Linux image runs it. Execution uses the shared test/lib/exec_target.sh helper. 24 # Lane A (and, under ENFORCE_CLANG=1, lane B) defers exec to one batched 25 # `podman run` per target via the corpus engine's kit_queue_e; lane B under 26 # ENFORCE_CLANG=0 runs inline (bounded) so its exec verdict can be XFAIL/XPASS. 27 # 28 # Self-probing: each target is SKIPPED (not failed) unless the host has (1) a 29 # clang cross-compiler for it, (2) a runner (podman/qemu) per exec_target, (3) a 30 # working `kit cc -S | kit as` round-trip for that arch, and (4) a bounded 31 # exec smoke that returns the oracle. So the harness runs green on whatever the 32 # host supports and self-extends as gaps close. All three ELF targets pass BOTH 33 # lanes end-to-end (936/936 = 312 cases x {O0,O1} x 3 arches, ENFORCE_CLANG). 34 # Code locations that an encoding-divergent assembler must recompute — switch 35 # jump-table entries and `&&label` address-takes — are referenced via per-block 36 # local symbols emitted by codegen (mc_label_symbol): the jump table is 37 # `.quad .Lcfblk.*` (R_ABS64) and the address-take a standard PC-relative reloc 38 # against the same symbol (x86-64 leaq/R_PC32, aarch64 adrp+add, riscv64 39 # auipc+addi/%pcrel). So the references are genuinely relocatable on every arch 40 # and clang's encoding choices (movabs vs mov-imm32, jmp rel32 vs rel8, RVC 41 # compression) can't shift a baked offset onto the wrong instruction. 42 # Execution under qemu-user (x86_64/riscv64 in their podman containers) is the 43 # sole judge — kit and clang emit different code, so a byte/text match would be 44 # meaningless. The batched runner caps each case (EXEC_CASE_TIMEOUT) so one 45 # hanging binary can't wedge the whole container. 46 # 47 # Override the matrix with KIT_HOSTAS_CROSS_TARGETS="tag:triple ..." and the 48 # clang-as gate with KIT_HOSTAS_ENFORCE_CLANG=0 (demote lane B to XFAIL). 49 # Every lane hook writes only under $KIT_WORK and records via kit_*, so the runner 50 # is parallel-safe; KIT_HOSTAS_PARALLEL flips dispatch. 51 52 set -u 53 54 ROOT="$(cd "$(dirname "$0")/../.." && pwd)" 55 export KIT_LIB_DIR="$ROOT/test/lib" 56 # shellcheck source=../lib/kit_corpus.sh 57 . "$ROOT/test/lib/kit_corpus.sh" 58 59 KIT="${KIT:-$ROOT/build/kit}" 60 CASES="$ROOT/test/toy/cases" 61 BUILD_DIR="$ROOT/build/test/asm/hostas_cross" 62 START_SRC="$ROOT/test/link/harness/start.c" 63 ENFORCE_CLANG="${KIT_HOSTAS_ENFORCE_CLANG:-1}" 64 EXEC_SMOKE_TIMEOUT="${KIT_HOSTAS_EXEC_TIMEOUT:-45}" 65 PAR="${KIT_HOSTAS_PARALLEL:-1}" 66 67 # Opt axis. KIT_TEST_OPTS carries "O"-prefixed levels (e.g. "O0 O1"); the 68 # corpus engine expects bare levels (KIT_OPT_LEVELS="0 1"). Strip the prefix. 69 OPTS="${KIT_TEST_OPTS:-O0 O1}" 70 OPT_LEVELS="" 71 for _o in $OPTS; do OPT_LEVELS="$OPT_LEVELS ${_o#O}"; done 72 73 # "tag:triple" — tag is exec_target.sh's <arch>-<os> spelling. All three ELF 74 # targets are in the gating default (each SKIPs cleanly if its clang cross 75 # target or container runner is unavailable). Narrow the matrix with 76 # KIT_HOSTAS_CROSS_TARGETS, e.g. KIT_HOSTAS_CROSS_TARGETS="x64-linux:x86_64-linux-gnu". 77 TARGETS="${KIT_HOSTAS_CROSS_TARGETS:-aarch64-linux:aarch64-linux-gnu x64-linux:x86_64-linux-gnu rv64-linux:riscv64-linux-gnu}" 78 79 # Filter ($1) preserved -> the engine filters discovery by KIT_TEST_FILTER. 80 FILTER="${1:-${KIT_TEST_FILTER:-}}" 81 export KIT_TEST_FILTER="$FILTER" 82 83 # The original exits on a_fail (+ b_efail when enforcing) only — skips (the known 84 # 141 case) never gated. Keep that: SKIP must not fail the run. 85 KIT_SKIP_IS_FAILURE=0 86 87 CLANG="${CLANG:-$(command -v clang 2>/dev/null || true)}" 88 89 color_red() { printf '\033[31m%s\033[0m' "$1"; } 90 color_grn() { printf '\033[32m%s\033[0m' "$1"; } 91 color_yel() { printf '\033[33m%s\033[0m' "$1"; } 92 93 if [ ! -x "$KIT" ]; then 94 printf 'hostas-cross: %s kit missing — run "make bin"\n' "$(color_red FATAL)" >&2 95 exit 1 96 fi 97 if [ -z "$CLANG" ] || [ ! -x "$CLANG" ]; then 98 printf 'hostas-cross: %s no clang (host assembler); skipping\n' "$(color_yel SKIP)" 99 exit 0 100 fi 101 mkdir -p "$BUILD_DIR" 102 103 # ---- exec_target.sh wiring (mirrors test/link/run.sh detection) ------------ 104 have_qemu=0 105 QEMU_BIN="$(command -v qemu-aarch64-static 2>/dev/null || command -v qemu-aarch64 2>/dev/null || true)" 106 [ -n "$QEMU_BIN" ] && have_qemu=1 107 QEMU_RV64_BIN="$(command -v qemu-riscv64-static 2>/dev/null || command -v qemu-riscv64 2>/dev/null || true)" 108 have_podman=0 109 command -v podman >/dev/null 2>&1 && have_podman=1 110 arch_raw="$(uname -m 2>/dev/null || true)" 111 is_aarch64=0 112 { [ "$arch_raw" = "aarch64" ] || [ "$arch_raw" = "arm64" ]; } && is_aarch64=1 113 export have_qemu QEMU_BIN QEMU_RV64_BIN have_podman is_aarch64 114 # Pin arch-explicit images so podman doesn't resolve an ambiguous multi-arch 115 # tag to the wrong variant (the bare `alpine:latest` in local storage can map 116 # to a non-host arch). Overridable; the binaries are static/freestanding so any 117 # same-arch Linux image with /bin/sh works. Absent images fail --pull=never, 118 # which the per-target exec smoke turns into a clean SKIP. 119 : "${RUN_AARCH64_IMAGE:=docker.io/arm64v8/alpine:latest}" 120 : "${RUN_X64_IMAGE:=docker.io/amd64/alpine:latest}" 121 : "${RUN_RV64_IMAGE:=docker.io/riscv64/alpine:edge}" 122 export RUN_AARCH64_IMAGE RUN_X64_IMAGE RUN_RV64_IMAGE 123 EXEC_TARGET_MOUNT_ROOT="$BUILD_DIR" 124 export EXEC_TARGET_MOUNT_ROOT 125 # shellcheck source=../lib/exec_target.sh 126 . "$ROOT/test/lib/exec_target.sh" 127 128 # Same TLS-symbolization skip as the sibling lanes — driven by the shared 129 # sidecar 141_threadlocal_mutate.link.skip via KIT_READ_CASE (skips the WHOLE 130 # case here too, matching hostas_toy.sh). 131 hostas_read_case() { 132 local reason 133 if reason=$(kit_skip_sidecar "$KIT_SIDECAR_DIR" "$KIT_BASE" "" "link"); then 134 KIT_SKIP_CASE="$reason (cc -S symbolizer gap)" 135 fi 136 } 137 138 oracle() { 139 local name="$1" exp=0 140 [ -f "$CASES/$name.expected" ] && exp=$(head -n1 "$CASES/$name.expected") 141 echo $((exp & 255)) 142 } 143 144 # First real `error:` line from an assembler's stderr (clang prints a harmless 145 # -Wmissing-sysroot warning first — we never link with clang, so the SDK is 146 # irrelevant). 147 err_reason() { 148 local f="$1" line="" 149 line=$(grep -m1 -E 'error:|fatal:' "$f" 2>/dev/null | sed 's|.*\(error\|fatal\): *||') 150 [ -z "$line" ] && line=$(head -1 "$f" 2>/dev/null | sed 's|.*: ||') 151 printf '%s' "$line" 152 } 153 154 # Bounded exec: run exe via exec_target under a wall-clock cap so a wedged 155 # emulator (e.g. a riscv64 qemu-user that never returns) downgrades a target to 156 # SKIP instead of hanging the whole harness. Sets SMOKE_RC (124 == timed out). 157 bounded_exec() { 158 local to="$1" tag="$2" exe="$3" out="$4" err="$5" 159 local rc0="$exe.rc0" 160 rm -f "$rc0" 161 ( exec_target_run "$tag" "$exe" "$out" "$err"; echo "$RUN_RC" >"$rc0" ) & 162 local pid=$! waited=0 163 while kill -0 "$pid" 2>/dev/null; do 164 sleep 1; waited=$((waited+1)) 165 if [ "$waited" -ge "$to" ]; then 166 kill -9 "$pid" 2>/dev/null; wait "$pid" 2>/dev/null 167 SMOKE_RC=124; return 168 fi 169 done 170 wait "$pid" 2>/dev/null 171 SMOKE_RC=124 172 [ -f "$rc0" ] && SMOKE_RC="$(cat "$rc0")" 173 } 174 175 # Link a relocatable object + the target crt into a static ELF. Echoes nothing; 176 # returns nonzero (and leaves stderr in $3) on failure or a non-empty linker 177 # diagnostic (warnings are treated as failures, as in hostas_toy.sh). 178 kit_ld_static() { 179 local obj="$1" out="$2" lderr="$3" crt="$4" 180 "$KIT" ld -static "$obj" "$crt" -o "$out" 2>"$lderr" || return 1 181 [ -s "$lderr" ] && return 1 182 return 0 183 } 184 185 # ---- shared `cc -S -target` build for both lanes --------------------------- 186 # One cross .s per item, cached in $KIT_WORK so lanes A and B reuse it. A cc -S 187 # failure is a lane-A failure only (lane B records nothing for the item). 188 # Reads the per-target $TGT_TRIPLE (set before each target's kit_corpus_run). 189 _ccs_build() { 190 CCS_S="$KIT_WORK/s.s" 191 [ -f "$KIT_WORK/.ccs.done" ] && { CCS_RC=$(cat "$KIT_WORK/.ccs.rc"); return "$CCS_RC"; } 192 if "$KIT" cc -S "-O$KIT_OPT" -target "$TGT_TRIPLE" "$KIT_SRC" -o "$CCS_S" 2>"$KIT_WORK/ccs.err"; then 193 CCS_RC=0 194 else 195 CCS_RC=1 196 fi 197 echo "$CCS_RC" >"$KIT_WORK/.ccs.rc"; : >"$KIT_WORK/.ccs.done" 198 return "$CCS_RC" 199 } 200 201 # ---- lane A: kit-as -> kit ld -static -> run (deferred batched exec) ---- 202 # Reads per-target $TGT_TRIPLE, $TGT_TAG, $TGT_CRT (set before kit_corpus_run). 203 kit_lane_A() { 204 if ! _ccs_build; then 205 kit_fail "$TGT_TAG/$KIT_NAME/kit-as" "cc -S: $(err_reason "$KIT_WORK/ccs.err")" 206 return 207 fi 208 if ! "$KIT" as -target "$TGT_TRIPLE" "$CCS_S" -o "$KIT_WORK/a.o" 2>"$KIT_WORK/a.as.err"; then 209 kit_fail "$TGT_TAG/$KIT_NAME/kit-as" "$(err_reason "$KIT_WORK/a.as.err")"; return 210 fi 211 if ! kit_ld_static "$KIT_WORK/a.o" "$KIT_WORK/a.out" "$KIT_WORK/a.ld.err" "$TGT_CRT"; then 212 kit_fail "$TGT_TAG/$KIT_NAME/kit-as ld" "$(err_reason "$KIT_WORK/a.ld.err")"; return 213 fi 214 kit_queue_e "$TGT_TAG/$KIT_NAME/kit-as" "$KIT_WORK/a.out" \ 215 "$KIT_WORK/a.run.out" "$KIT_WORK/a.run.err" "$KIT_WORK/a.rc" \ 216 "$KIT_EXPECTED" "$TGT_TAG" 217 } 218 219 # ---- lane B: clang-as -> kit ld -static -> run --------------------------- 220 # Gated by ENFORCE_CLANG. Build-stage failures are recorded inline (FAIL when 221 # enforcing, XFAIL otherwise). The exec stage: when enforcing, defer to the 222 # batched flush via kit_queue_e (pass/fail). When NOT enforcing, run inline 223 # (bounded) so the exec verdict can be XPASS (pass) / XFAIL (fail). 224 kit_lane_B() { 225 if ! _ccs_build; then return; fi 226 local name="$TGT_TAG/$KIT_NAME/clang-as" 227 if ! "$CLANG" --target="$TGT_TRIPLE" -c "$CCS_S" -o "$KIT_WORK/b.o" 2>"$KIT_WORK/b.as.err"; then 228 if [ "$ENFORCE_CLANG" = "1" ]; then kit_fail "$name" "$(err_reason "$KIT_WORK/b.as.err")" 229 else kit_xfail "$name" "$(err_reason "$KIT_WORK/b.as.err")"; fi 230 return 231 fi 232 if ! kit_ld_static "$KIT_WORK/b.o" "$KIT_WORK/b.out" "$KIT_WORK/b.ld.err" "$TGT_CRT"; then 233 if [ "$ENFORCE_CLANG" = "1" ]; then kit_fail "$name" "ld: $(err_reason "$KIT_WORK/b.ld.err")" 234 else kit_xfail "$name"; fi 235 return 236 fi 237 if [ "$ENFORCE_CLANG" = "1" ]; then 238 kit_queue_e "$name" "$KIT_WORK/b.out" \ 239 "$KIT_WORK/b.run.out" "$KIT_WORK/b.run.err" "$KIT_WORK/b.rc" \ 240 "$KIT_EXPECTED" "$TGT_TAG" 241 return 242 fi 243 # ENFORCE_CLANG=0: inline bounded exec -> XPASS/XFAIL. 244 bounded_exec "${EXEC_CASE_TIMEOUT:-20}" "$TGT_TAG" "$KIT_WORK/b.out" \ 245 "$KIT_WORK/b.run.out" "$KIT_WORK/b.run.err" 246 if [ "$SMOKE_RC" = "$((KIT_EXPECTED & 255))" ]; then kit_xpass "$name" 247 else kit_xfail "$name"; fi 248 } 249 250 # ---- per-target capability gates (SKIP-TGT, never FAIL) -------------------- 251 # Returns 0 if the target is runnable (and sets TGT_CRT); prints a single 252 # SKIP-TGT line and returns 1 otherwise. Mirrors the original gate chain. 253 target_gate() { 254 local tag="$1" triple="$2" tdir="$3" 255 if ! "$CLANG" --target="$triple" -c -x c - -o /dev/null </dev/null 2>/dev/null; then 256 printf ' %s %s — no clang cross target\n' "$(color_yel SKIP-TGT)" "$tag"; return 1 257 fi 258 if ! exec_target_supported "$tag"; then 259 printf ' %s %s — no runner (podman/qemu)\n' "$(color_yel SKIP-TGT)" "$tag"; return 1 260 fi 261 TGT_CRT="$tdir/start.o" 262 if ! "$CLANG" --target="$triple" -O1 -ffreestanding -fno-stack-protector \ 263 -fno-PIC -fno-pie -Dtest_main=main -c "$START_SRC" -o "$TGT_CRT" 2>"$tdir/crt.err"; then 264 printf ' %s %s — crt build failed: %s\n' "$(color_yel SKIP-TGT)" "$tag" "$(err_reason "$tdir/crt.err")"; return 1 265 fi 266 267 # Pick a representative non-skip case for the smokes (first case without a 268 # 141-style .link.skip sidecar). 269 local smoke="" s n 270 for s in "$CASES"/*.toy; do 271 n="$(basename "$s" .toy)" 272 kit_skip_sidecar "$CASES" "$n" "" "link" >/dev/null && continue 273 smoke="$n"; break 274 done 275 local sd="$tdir/_smoke"; mkdir -p "$sd" 276 if ! "$KIT" cc -S -O0 -target "$triple" "$CASES/$smoke.toy" -o "$sd/s.s" 2>"$sd/ccs.err"; then 277 printf ' %s %s — cc -S failed: %s\n' "$(color_yel SKIP-TGT)" "$tag" "$(err_reason "$sd/ccs.err")"; return 1 278 fi 279 if ! "$KIT" as -target "$triple" "$sd/s.s" -o "$sd/a.o" 2>"$sd/as.err"; then 280 printf ' %s %s — cc -S|as gap: %s\n' "$(color_yel SKIP-TGT)" "$tag" "$(err_reason "$sd/as.err")"; return 1 281 fi 282 if ! kit_ld_static "$sd/a.o" "$sd/a.out" "$sd/ld.err" "$TGT_CRT"; then 283 printf ' %s %s — kit ld -static failed: %s\n' "$(color_yel SKIP-TGT)" "$tag" "$(err_reason "$sd/ld.err")"; return 1 284 fi 285 bounded_exec "$EXEC_SMOKE_TIMEOUT" "$tag" "$sd/a.out" "$sd/run.out" "$sd/run.err" 286 local sexp; sexp=$(oracle "$smoke") 287 if [ "$SMOKE_RC" != "$sexp" ]; then 288 local reason="exit $SMOKE_RC != $sexp" 289 [ "$SMOKE_RC" = "124" ] && reason="exec timed out (>${EXEC_SMOKE_TIMEOUT}s)" 290 printf ' %s %s — exec smoke: %s\n' "$(color_yel SKIP-TGT)" "$tag" "$reason"; return 1 291 fi 292 return 0 293 } 294 295 # ---- drive the corpus, one target at a time -------------------------------- 296 printf 'hostas-cross: kit=%s\n' "$KIT" 297 printf 'hostas-cross: clang=%s opts="%s" enforce_clang=%s podman=%s\n' \ 298 "$CLANG" "$OPT_LEVELS" "$ENFORCE_CLANG" "$have_podman" 299 printf 'hostas-cross: targets="%s"\n' "$TARGETS" 300 301 tgt_run=0 302 shopt -s nullglob 303 for entry in $TARGETS; do 304 TGT_TAG="${entry%%:*}"; TGT_TRIPLE="${entry##*:}" 305 tdir="$BUILD_DIR/$TGT_TAG"; rm -rf "$tdir"; mkdir -p "$tdir" 306 if ! target_gate "$TGT_TAG" "$TGT_TRIPLE" "$tdir"; then continue; fi 307 308 tgt_run=$((tgt_run + 1)) 309 printf ' %s %s (%s) — running corpus\n' "$(color_grn TGT)" "$TGT_TAG" "$TGT_TRIPLE" 310 # TGT_TAG / TGT_TRIPLE / TGT_CRT are set; lane hooks (incl. parallel 311 # workers, forked after this point) read them. One kit_corpus_run per target, 312 # like elf's A/B/C layers — counters accumulate into the shared summary. 313 export TGT_TAG TGT_TRIPLE TGT_CRT 314 KIT_LABEL=test-hostas-cross KIT_BUILD_DIR="$tdir" \ 315 KIT_CORPUS_GLOBS="$CASES/*.toy" KIT_CORPUS_EXT=toy KIT_SIDECAR_DIR="$CASES" \ 316 KIT_LANES="A B" KIT_OPT_LEVELS="$OPT_LEVELS" KIT_TUPLES="$TGT_TAG" \ 317 KIT_TARGETS_EXT="" KIT_READ_CASE=hostas_read_case KIT_PARALLELIZABLE="$PAR" \ 318 kit_corpus_run 319 done 320 shopt -u nullglob 321 322 printf '\n' 323 if [ "$tgt_run" -eq 0 ]; then 324 printf 'hostas-cross: no target ran (all SKIP-TGT) — needs a clang cross target + podman/qemu + a working cc -S|as for some ELF arch.\n' 325 fi 326 327 kit_summary test-hostas-cross 328 kit_exit