hostas_toy.sh (9224B)
1 #!/usr/bin/env bash 2 # test/asm/hostas_toy.sh — prove kit's `cc -S` is STANDARD assembly by feeding 3 # the SAME `cc -S` output to two assemblers, linking + running both, and asserting 4 # the toy exit-code oracle for each. (Type C — shared corpus harness.) 5 # 6 # Per toy case (native target; both O0 and O1): 7 # kit cc -S -> s.s (one native .s, shared by both) 8 # A /kit-as: kit as s.s | kit ld | ./a.out exit == oracle 9 # B /clang-as: clang -c s.s | kit ld | ./b.out exit == oracle 10 # 11 # The only difference between the two lanes is the ASSEMBLER, so this is a 12 # controlled test of whether kit's emitted asm is something a third-party 13 # toolchain accepts and that means the same thing. Lane A is the baseline (kit 14 # both writes and reads the text, so a private-dialect quirk can hide). Lane B is 15 # the real test: a standard assembler (clang) can't paper over such a quirk — 16 # same idea as test/asm/diff_llvm.sh for the C corpus, but checked by EXECUTION 17 # (exit code), not by matching bytes (kit and clang produce different code, so 18 # a byte/text match would be meaningless). 19 # 20 # GATED: `cc -S` is now object-format-aware (src/api/asm_emit.c's AsmSyntax 21 # vtable + the aarch64 ArchAsmOps reloc-operand hook), so on the native Mach-O 22 # target it emits clean Mach-O assembly clang/llvm-mc accept — no ELF `.type`/ 23 # `.size`, `.section __SEG,__SECT`, `.p2align`, and `sym@PAGE`/`@PAGEOFF` 24 # relocation operands. kit's own `as` parses the same Mach-O dialect (it 25 # dispatches on its target format), so BOTH lanes pass. The clang-as lane (B) 26 # gates by default; set KIT_HOSTAS_ENFORCE_CLANG=0 to demote it to XFAIL (e.g. 27 # while bringing up a new arch/format whose printer side isn't done yet). 28 # 29 # Lanes (no KIT_TEST_PATHS knob — both lanes always run; lane B's verdict is 30 # gated by KIT_HOSTAS_ENFORCE_CLANG instead): 31 # A kit-as -> kit ld -> run exit == oracle (the baseline) 32 # B clang-as -> kit ld -> run exit == oracle (the real test) 33 # Every lane hook writes only under $KIT_WORK and records via kit_*, so the runner 34 # is parallel-safe; KIT_HOSTAS_PARALLEL flips dispatch. 35 36 set -u 37 38 ROOT="$(cd "$(dirname "$0")/../.." && pwd)" 39 export KIT_LIB_DIR="$ROOT/test/lib" 40 # shellcheck source=../lib/kit_corpus.sh 41 . "$ROOT/test/lib/kit_corpus.sh" 42 43 KIT="${KIT:-$ROOT/build/kit}" 44 CASES="$ROOT/test/toy/cases" 45 BUILD_DIR="$ROOT/build/test/asm/hostas_toy" 46 ENFORCE_CLANG="${KIT_HOSTAS_ENFORCE_CLANG:-1}" 47 PAR="${KIT_HOSTAS_PARALLEL:-1}" 48 49 # The original harness exits on a_fail (+ b_efail when enforcing) only — skips 50 # (just the known 141 case) never gated. Keep that: SKIP must not fail the run. 51 KIT_SKIP_IS_FAILURE=0 52 53 # Opt axis: the original took O0/O1 spellings via KIT_TEST_OPTS; the corpus 54 # engine expands bare levels (KIT_OPT_LEVELS="0 1"). Map a KIT_TEST_OPTS 55 # override (e.g. "O0 O1" or "O0") onto bare levels to preserve the env knob. 56 OPT_LEVELS="0 1" 57 if [ -n "${KIT_TEST_OPTS:-}" ]; then 58 OPT_LEVELS="" 59 for _o in $KIT_TEST_OPTS; do OPT_LEVELS="$OPT_LEVELS ${_o#O}"; done 60 fi 61 62 # Filter ($1) preserved -> the engine filters discovery by KIT_TEST_FILTER. 63 FILTER="${1:-${KIT_TEST_FILTER:-}}" 64 export KIT_TEST_FILTER="$FILTER" 65 66 CLANG="${CLANG:-$(command -v clang 2>/dev/null || true)}" 67 68 color_red() { printf '\033[31m%s\033[0m' "$1"; } 69 color_yel() { printf '\033[33m%s\033[0m' "$1"; } 70 71 if [ ! -x "$KIT" ]; then 72 printf 'hostas-toy: %s kit missing — run "make bin"\n' "$(color_red FATAL)" >&2 73 exit 1 74 fi 75 if [ -z "$CLANG" ] || [ ! -x "$CLANG" ]; then 76 printf 'hostas-toy: %s no clang (host assembler); skipping\n' "$(color_yel SKIP)" 77 exit 0 78 fi 79 mkdir -p "$BUILD_DIR" 80 81 # Native target tuple/triple for the corpus engine + `cc -S`/`as`. 82 TEST_ARCH="${KIT_TEST_ARCH:-aa64}" 83 TEST_OBJ="${KIT_TEST_OBJ:-macho}" 84 case "$TEST_ARCH" in 85 aa64|aarch64|arm64) TEST_ARCH=aa64 ;; 86 x64|x86_64|amd64) TEST_ARCH=x64 ;; 87 rv64|riscv64) TEST_ARCH=rv64 ;; 88 esac 89 CUR_TUPLE="$TEST_ARCH-$TEST_OBJ" 90 91 # First meaningful diagnostic from an assembler's stderr. clang prints a harmless 92 # `-Wmissing-sysroot` warning first (we never link with clang, so the SDK is 93 # irrelevant), so prefer the first real `error:` line over a blind `head -1`. 94 err_reason() { 95 local f="$1" line="" 96 line=$(grep -m1 -E 'error:' "$f" 2>/dev/null | sed 's|.*error: *||') 97 [ -z "$line" ] && line=$(grep -m1 -E 'unknown directive|unknown section type' "$f" 2>/dev/null | sed 's|^[[:space:]]*||') 98 [ -z "$line" ] && line=$(head -1 "$f" 2>/dev/null | sed 's|.*: ||') 99 printf '%s' "$line" 100 } 101 # Terse first-line reason (kit's own diagnostics: "<tool>: <msg>"). 102 first_reason() { head -1 "$1" 2>/dev/null | sed 's|.*: ||'; } 103 104 # Cases blocked on a separate, known `cc -S` symbolizer gap (the round-trip lane 105 # quarantines the same set): 141 emits an unsymbolized `adrp x,0x0` for a 106 # thread-local access (TLS symbolization, tracked separately). Sidecar-driven 107 # via test/toy/cases/141_threadlocal_mutate.link.skip, read by KIT_READ_CASE 108 # under the synthetic "link" lane name so the same .link.skip the toy/roundtrip 109 # harnesses honor skips the WHOLE case here too. 110 hostas_read_case() { 111 local reason 112 if reason=$(kit_skip_sidecar "$KIT_SIDECAR_DIR" "$KIT_BASE" "" "link"); then 113 KIT_SKIP_CASE="$reason (cc -S symbolizer gap)" 114 fi 115 } 116 117 # Shared `cc -S` build for both lanes (one native .s per item). Cached per item 118 # in $KIT_WORK so lanes A and B reuse it. Sets CCS_S (path) + CCS_RC (0 ok). 119 # A cc -S failure is a lane-A failure only (lane B records nothing for the item, 120 # matching the original `continue`). 121 _ccs_build() { 122 CCS_S="$KIT_WORK/s.s" 123 [ -f "$KIT_WORK/.ccs.done" ] && { CCS_RC=$(cat "$KIT_WORK/.ccs.rc"); return "$CCS_RC"; } 124 if "$KIT" cc -S "-O$KIT_OPT" "$KIT_SRC" -o "$CCS_S" 2>"$KIT_WORK/ccs.err"; then 125 CCS_RC=0 126 else 127 CCS_RC=1 128 fi 129 echo "$CCS_RC" >"$KIT_WORK/.ccs.rc"; : >"$KIT_WORK/.ccs.done" 130 return "$CCS_RC" 131 } 132 133 # ---- lane A: kit-as -> kit ld -> run (the baseline) --------------------- 134 kit_lane_A() { 135 if ! _ccs_build; then 136 kit_fail "$KIT_NAME/kit-as" "cc -S: $(first_reason "$KIT_WORK/ccs.err")" 137 return 138 fi 139 if ! "$KIT" as "$CCS_S" -o "$KIT_WORK/a.o" 2>"$KIT_WORK/a.as.err"; then 140 kit_fail "$KIT_NAME/kit-as" "as: $(first_reason "$KIT_WORK/a.as.err")"; return 141 fi 142 if ! "$KIT" ld "$KIT_WORK/a.o" -o "$KIT_WORK/a.out" 2>"$KIT_WORK/a.ld.err" || [ -s "$KIT_WORK/a.ld.err" ]; then 143 kit_fail "$KIT_NAME/kit-as" "ld: $(first_reason "$KIT_WORK/a.ld.err")"; return 144 fi 145 chmod +x "$KIT_WORK/a.out" 2>/dev/null || true 146 "$KIT_WORK/a.out" >"$KIT_WORK/a.out.txt" 2>"$KIT_WORK/a.run.err"; local arc=$? 147 local exp=$((KIT_EXPECTED & 255)) # exit codes wrap at 256 (oracle 312 -> 56) 148 if [ -s "$KIT_WORK/a.run.err" ]; then 149 kit_fail "$KIT_NAME/kit-as" "run stderr: $(head -1 "$KIT_WORK/a.run.err")" 150 elif [ "$arc" -eq "$exp" ]; then 151 kit_pass "$KIT_NAME/kit-as" 152 else 153 kit_fail "$KIT_NAME/kit-as" "exit $arc != $exp" 154 fi 155 } 156 157 # ---- lane B: clang-as -> kit ld -> run (the real test) ------------------- 158 # Gated by ENFORCE_CLANG: pass+enforce -> PASS, pass+!enforce -> XPASS; 159 # fail+enforce -> FAIL, fail+!enforce -> XFAIL. kit ld on both lanes isolates 160 # the assembler as the only variable. Records NOTHING on a shared cc -S failure 161 # (matching the original `continue`). 162 kit_lane_B() { 163 if ! _ccs_build; then return; fi 164 local ok=0 reason="" exp=$((KIT_EXPECTED & 255)) 165 if ! "$CLANG" -c "$CCS_S" -o "$KIT_WORK/b.o" 2>"$KIT_WORK/b.as.err"; then 166 reason="clang as: $(err_reason "$KIT_WORK/b.as.err")" 167 elif ! "$KIT" ld "$KIT_WORK/b.o" -o "$KIT_WORK/b.out" 2>"$KIT_WORK/b.ld.err" || [ -s "$KIT_WORK/b.ld.err" ]; then 168 reason="kit ld: $(first_reason "$KIT_WORK/b.ld.err")" 169 else 170 chmod +x "$KIT_WORK/b.out" 2>/dev/null || true 171 "$KIT_WORK/b.out" >"$KIT_WORK/b.out.txt" 2>"$KIT_WORK/b.run.err"; local brc=$? 172 if [ -s "$KIT_WORK/b.run.err" ]; then reason="run stderr: $(head -1 "$KIT_WORK/b.run.err")" 173 elif [ "$brc" -eq "$exp" ]; then ok=1 174 else reason="exit $brc != $exp"; fi 175 fi 176 if [ "$ok" -eq 1 ]; then 177 if [ "$ENFORCE_CLANG" = "1" ]; then kit_pass "$KIT_NAME/clang-as" 178 else kit_xpass "$KIT_NAME/clang-as"; fi 179 else 180 if [ "$ENFORCE_CLANG" = "1" ]; then kit_fail "$KIT_NAME/clang-as" "$reason" 181 else kit_xfail "$KIT_NAME/clang-as" "$reason"; fi 182 fi 183 } 184 185 # ---- drive the corpus ------------------------------------------------------ 186 printf 'hostas-toy: kit=%s\n' "$KIT" 187 printf 'hostas-toy: clang=%s opts="%s" enforce_clang=%s\n' "$CLANG" "$OPT_LEVELS" "$ENFORCE_CLANG" 188 189 # Lane B's XFAIL/XPASS already carry the enforce semantics; gate xpass under 190 # KIT_HOSTAS_ENFORCE_CLANG=1 is N/A here (enforce=1 means lane B counts as 191 # PASS/FAIL, not XPASS/XFAIL), so leave KIT_STRICT_XFAIL at its default. 192 KIT_LABEL=test-hostas-toy KIT_BUILD_DIR="$BUILD_DIR" \ 193 KIT_CORPUS_GLOBS="$CASES/*.toy" KIT_CORPUS_EXT=toy KIT_SIDECAR_DIR="$CASES" \ 194 KIT_LANES="A B" KIT_OPT_LEVELS="$OPT_LEVELS" KIT_TUPLES="$CUR_TUPLE" \ 195 KIT_TARGETS_EXT="" KIT_READ_CASE=hostas_read_case KIT_PARALLELIZABLE="$PAR" \ 196 kit_corpus_run 197 198 kit_summary test-hostas-toy 199 kit_exit