hostas_toy.sh (9107B)
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. KIT_TEST_OPTS carries "O"-prefixed levels (e.g. "O0 O1"); the 54 # corpus engine expects bare levels (KIT_OPT_LEVELS="0 1"). Strip the prefix. 55 OPTS="${KIT_TEST_OPTS:-O0 O1}" 56 OPT_LEVELS="" 57 for _o in $OPTS; do OPT_LEVELS="$OPT_LEVELS ${_o#O}"; done 58 59 # Filter ($1) preserved -> the engine filters discovery by KIT_TEST_FILTER. 60 FILTER="${1:-${KIT_TEST_FILTER:-}}" 61 export KIT_TEST_FILTER="$FILTER" 62 63 CLANG="${CLANG:-$(command -v clang 2>/dev/null || true)}" 64 65 color_red() { printf '\033[31m%s\033[0m' "$1"; } 66 color_yel() { printf '\033[33m%s\033[0m' "$1"; } 67 68 if [ ! -x "$KIT" ]; then 69 printf 'hostas-toy: %s kit missing — run "make bin"\n' "$(color_red FATAL)" >&2 70 exit 1 71 fi 72 if [ -z "$CLANG" ] || [ ! -x "$CLANG" ]; then 73 printf 'hostas-toy: %s no clang (host assembler); skipping\n' "$(color_yel SKIP)" 74 exit 0 75 fi 76 mkdir -p "$BUILD_DIR" 77 78 # Native target tuple/triple for the corpus engine + `cc -S`/`as`. 79 TEST_ARCH="${KIT_TEST_ARCH:-aa64}" 80 TEST_OBJ="${KIT_TEST_OBJ:-macho}" 81 case "$TEST_ARCH" in 82 aa64|aarch64|arm64) TEST_ARCH=aa64 ;; 83 x64|x86_64|amd64) TEST_ARCH=x64 ;; 84 rv64|riscv64) TEST_ARCH=rv64 ;; 85 esac 86 CUR_TUPLE="$TEST_ARCH-$TEST_OBJ" 87 88 # First meaningful diagnostic from an assembler's stderr. clang prints a harmless 89 # `-Wmissing-sysroot` warning first (we never link with clang, so the SDK is 90 # irrelevant), so prefer the first real `error:` line over a blind `head -1`. 91 err_reason() { 92 local f="$1" line="" 93 line=$(grep -m1 -E 'error:' "$f" 2>/dev/null | sed 's|.*error: *||') 94 [ -z "$line" ] && line=$(grep -m1 -E 'unknown directive|unknown section type' "$f" 2>/dev/null | sed 's|^[[:space:]]*||') 95 [ -z "$line" ] && line=$(head -1 "$f" 2>/dev/null | sed 's|.*: ||') 96 printf '%s' "$line" 97 } 98 # Terse first-line reason (kit's own diagnostics: "<tool>: <msg>"). 99 first_reason() { head -1 "$1" 2>/dev/null | sed 's|.*: ||'; } 100 101 # Cases blocked on a separate, known `cc -S` symbolizer gap (the round-trip lane 102 # quarantines the same set): 141 emits an unsymbolized `adrp x,0x0` for a 103 # thread-local access (TLS symbolization, tracked separately). Sidecar-driven 104 # via test/toy/cases/141_threadlocal_mutate.link.skip, read by KIT_READ_CASE 105 # under the synthetic "link" lane name so the same .link.skip the toy/roundtrip 106 # harnesses honor skips the WHOLE case here too. 107 hostas_read_case() { 108 local reason 109 if reason=$(kit_skip_sidecar "$KIT_SIDECAR_DIR" "$KIT_BASE" "" "link"); then 110 KIT_SKIP_CASE="$reason (cc -S symbolizer gap)" 111 fi 112 } 113 114 # Shared `cc -S` build for both lanes (one native .s per item). Cached per item 115 # in $KIT_WORK so lanes A and B reuse it. Sets CCS_S (path) + CCS_RC (0 ok). 116 # A cc -S failure is a lane-A failure only (lane B records nothing for the item, 117 # matching the original `continue`). 118 _ccs_build() { 119 CCS_S="$KIT_WORK/s.s" 120 [ -f "$KIT_WORK/.ccs.done" ] && { CCS_RC=$(cat "$KIT_WORK/.ccs.rc"); return "$CCS_RC"; } 121 if "$KIT" cc -S "-O$KIT_OPT" "$KIT_SRC" -o "$CCS_S" 2>"$KIT_WORK/ccs.err"; then 122 CCS_RC=0 123 else 124 CCS_RC=1 125 fi 126 echo "$CCS_RC" >"$KIT_WORK/.ccs.rc"; : >"$KIT_WORK/.ccs.done" 127 return "$CCS_RC" 128 } 129 130 # ---- lane A: kit-as -> kit ld -> run (the baseline) --------------------- 131 kit_lane_A() { 132 if ! _ccs_build; then 133 kit_fail "$KIT_NAME/kit-as" "cc -S: $(first_reason "$KIT_WORK/ccs.err")" 134 return 135 fi 136 if ! "$KIT" as "$CCS_S" -o "$KIT_WORK/a.o" 2>"$KIT_WORK/a.as.err"; then 137 kit_fail "$KIT_NAME/kit-as" "as: $(first_reason "$KIT_WORK/a.as.err")"; return 138 fi 139 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 140 kit_fail "$KIT_NAME/kit-as" "ld: $(first_reason "$KIT_WORK/a.ld.err")"; return 141 fi 142 chmod +x "$KIT_WORK/a.out" 2>/dev/null || true 143 "$KIT_WORK/a.out" >"$KIT_WORK/a.out.txt" 2>"$KIT_WORK/a.run.err"; local arc=$? 144 local exp=$((KIT_EXPECTED & 255)) # exit codes wrap at 256 (oracle 312 -> 56) 145 if [ -s "$KIT_WORK/a.run.err" ]; then 146 kit_fail "$KIT_NAME/kit-as" "run stderr: $(head -1 "$KIT_WORK/a.run.err")" 147 elif [ "$arc" -eq "$exp" ]; then 148 kit_pass "$KIT_NAME/kit-as" 149 else 150 kit_fail "$KIT_NAME/kit-as" "exit $arc != $exp" 151 fi 152 } 153 154 # ---- lane B: clang-as -> kit ld -> run (the real test) ------------------- 155 # Gated by ENFORCE_CLANG: pass+enforce -> PASS, pass+!enforce -> XPASS; 156 # fail+enforce -> FAIL, fail+!enforce -> XFAIL. kit ld on both lanes isolates 157 # the assembler as the only variable. Records NOTHING on a shared cc -S failure 158 # (matching the original `continue`). 159 kit_lane_B() { 160 if ! _ccs_build; then return; fi 161 local ok=0 reason="" exp=$((KIT_EXPECTED & 255)) 162 if ! "$CLANG" -c "$CCS_S" -o "$KIT_WORK/b.o" 2>"$KIT_WORK/b.as.err"; then 163 reason="clang as: $(err_reason "$KIT_WORK/b.as.err")" 164 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 165 reason="kit ld: $(first_reason "$KIT_WORK/b.ld.err")" 166 else 167 chmod +x "$KIT_WORK/b.out" 2>/dev/null || true 168 "$KIT_WORK/b.out" >"$KIT_WORK/b.out.txt" 2>"$KIT_WORK/b.run.err"; local brc=$? 169 if [ -s "$KIT_WORK/b.run.err" ]; then reason="run stderr: $(head -1 "$KIT_WORK/b.run.err")" 170 elif [ "$brc" -eq "$exp" ]; then ok=1 171 else reason="exit $brc != $exp"; fi 172 fi 173 if [ "$ok" -eq 1 ]; then 174 if [ "$ENFORCE_CLANG" = "1" ]; then kit_pass "$KIT_NAME/clang-as" 175 else kit_xpass "$KIT_NAME/clang-as"; fi 176 else 177 if [ "$ENFORCE_CLANG" = "1" ]; then kit_fail "$KIT_NAME/clang-as" "$reason" 178 else kit_xfail "$KIT_NAME/clang-as" "$reason"; fi 179 fi 180 } 181 182 # ---- drive the corpus ------------------------------------------------------ 183 printf 'hostas-toy: kit=%s\n' "$KIT" 184 printf 'hostas-toy: clang=%s opts="%s" enforce_clang=%s\n' "$CLANG" "$OPT_LEVELS" "$ENFORCE_CLANG" 185 186 # Lane B's XFAIL/XPASS already carry the enforce semantics; gate xpass under 187 # KIT_HOSTAS_ENFORCE_CLANG=1 is N/A here (enforce=1 means lane B counts as 188 # PASS/FAIL, not XPASS/XFAIL), so leave KIT_STRICT_XFAIL at its default. 189 KIT_LABEL=test-hostas-toy KIT_BUILD_DIR="$BUILD_DIR" \ 190 KIT_CORPUS_GLOBS="$CASES/*.toy" KIT_CORPUS_EXT=toy KIT_SIDECAR_DIR="$CASES" \ 191 KIT_LANES="A B" KIT_OPT_LEVELS="$OPT_LEVELS" KIT_TUPLES="$CUR_TUPLE" \ 192 KIT_TARGETS_EXT="" KIT_READ_CASE=hostas_read_case KIT_PARALLELIZABLE="$PAR" \ 193 kit_corpus_run 194 195 kit_summary test-hostas-toy 196 kit_exit