kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

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