kit

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

vm.sh (11342B)


      1 #!/usr/bin/env bash
      2 # test/toy/vm.sh — run the .toy corpus as real *hosted* programs inside the
      3 # FreeBSD and Windows VMs, asserting each case's `.expected` exit code.
      4 #
      5 # The VM counterpart to test/toy/run.sh's X lane: X cross-compiles a
      6 # freestanding ELF and runs it under qemu-user/podman with a kit-built _start
      7 # stub; here we link a full hosted binary against a real OS sysroot (FreeBSD
      8 # base.txz extract, or the llvm-mingw UCRT sysroot) and execute it on the genuine
      9 # OS in a VM — exercising the whole hosted path (ABI, CRT startup, the platform
     10 # loader, syscalls/Win32) the Linux lanes cannot.
     11 #
     12 # Execution goes through the shared seam test/lib/exec_target.sh (the
     13 # `<arch>-freebsd` / `<arch>-windows` tags): this script only compiles each case
     14 # and queues it; exec_target/exec_vm own VM boot/reuse, the batched run-batch
     15 # transport, and shutdown-at-exit. Compilation happens for every applicable case
     16 # regardless of VM availability (so a codegen/link bug is caught even with no VM);
     17 # only execution is gated on a runner.
     18 #
     19 #   usage: test/toy/vm.sh <os> [name_filter]      os: freebsd | windows | macos
     20 #
     21 # macos is the Rosetta variant: on an Apple-silicon host it cross-compiles each
     22 # case to x86_64-apple-darwin and runs it via Rosetta 2 (exec_target's x64-macos
     23 # tag — no VM). Same staging/skip/flush machinery as the VM backends.
     24 #
     25 # env: KIT, KIT_TOY_FREEBSD_ARCHES (amd64 aarch64 riscv64),
     26 #   KIT_TOY_WINDOWS_ARCHES (x64 aarch64), KIT_OPT_LEVELS (0 1),
     27 #   KIT_FREEBSD_LINK (static|dynamic|both), KIT_TEST_FILTER, KIT_TOY_VM_KEEP_UP.
     28 #
     29 # Skips (missing sysroot/VM, arch-inapplicable cases) are non-fatal — only a real
     30 # FAIL gates the exit. Genuine codegen gaps are left RED on purpose; opt a case
     31 # out only with a committed <name>.<os>.skip sidecar when truly inapplicable.
     32 
     33 set -u
     34 
     35 ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
     36 KIT="${KIT:-$ROOT/build/kit}"
     37 TEST_DIR="$ROOT/test/toy"
     38 BUILD_DIR="$ROOT/build/test/toy-vm"
     39 
     40 # shellcheck source=../lib/kit_sh_report.sh
     41 . "$ROOT/test/lib/kit_sh_report.sh"
     42 # shellcheck source=../lib/exec_target.sh
     43 . "$ROOT/test/lib/exec_target.sh"
     44 kit_report_init
     45 
     46 # Honor the legacy keep-up knob through exec_vm's teardown gate, and shut any VM
     47 # we booted down at exit.
     48 [ "${KIT_TOY_VM_KEEP_UP:-0}" = 1 ] && export EXEC_VM_KEEP_UP=1
     49 trap exec_target_teardown_all EXIT
     50 
     51 OS="${1:-}"
     52 FILTER="${2:-${KIT_TEST_FILTER:-}}"
     53 case "$OS" in
     54   freebsd|windows|macos) ;;
     55   *) echo "usage: $0 <freebsd|windows|macos> [name_filter]" >&2; exit 2 ;;
     56 esac
     57 
     58 OPT_LEVELS="${KIT_OPT_LEVELS:-0 1}"
     59 FREEBSD_ARCHES="${KIT_TOY_FREEBSD_ARCHES:-amd64 aarch64 riscv64}"
     60 WINDOWS_ARCHES="${KIT_TOY_WINDOWS_ARCHES:-x64 aarch64}"
     61 # macOS runs a *cross*-arch (non-host) toy binary via Rosetta 2 on an
     62 # Apple-silicon host — defaults to x64 (the x86_64-on-arm64 case). The
     63 # native-arch macOS corpus is run in-process by test/toy/run.sh, not here.
     64 MACOS_ARCHES="${KIT_TOY_MACOS_ARCHES:-x64}"
     65 MACOS_SDK=""   # resolved (xcrun) in the macos drive branch; needed only by
     66                # <name>.link.hosted cases (e.g. thread-locals → _tlv_bootstrap)
     67 LINK="${KIT_FREEBSD_LINK:-both}"
     68 
     69 if [ ! -x "$KIT" ]; then
     70   echo "$OS: kit binary not found at $KIT (run 'make bin')" >&2
     71   exit 2
     72 fi
     73 
     74 shopt -s nullglob
     75 
     76 # ---- target / sysroot resolution -------------------------------------------
     77 triple_for() {
     78   case "$1/$2" in
     79     freebsd/amd64)   echo x86_64-freebsd ;;
     80     freebsd/aarch64) echo aarch64-freebsd ;;
     81     freebsd/riscv64) echo riscv64-freebsd ;;
     82     windows/x64)     echo x86_64-windows ;;
     83     windows/aarch64) echo aarch64-windows ;;
     84     macos/x64)       echo x86_64-apple-darwin ;;
     85     macos/aarch64)   echo arm64-apple-darwin ;;
     86     *) echo "" ;;
     87   esac
     88 }
     89 
     90 sysroot_for() {
     91   case "$1" in
     92     freebsd) "$ROOT/scripts/freebsd_sysroot.sh" path "$2" 2>/dev/null ;;
     93     windows) "$ROOT/scripts/llvm_mingw_sysroot.sh" path "$2" 2>/dev/null ;;
     94     macos)   echo "" ;;  # .toy cases are freestanding (Mach-O LC_MAIN); no sysroot
     95   esac
     96 }
     97 
     98 # The basename suffix this (os,arch) "owns" — arch-only cases named *_x64 /
     99 # *_aa64 / *_rv64 use intrinsics that only lower on that arch (cf. run.sh).
    100 arch_suffix_for() {
    101   case "$1/$2" in
    102     freebsd/amd64|windows/x64|macos/x64) echo _x64 ;;
    103     */aarch64)                 echo _aa64 ;;
    104     freebsd/riscv64)           echo _rv64 ;;
    105     *) echo "" ;;
    106   esac
    107 }
    108 
    109 # case_skip_reason OS ARCH NAME SRC -> echoes a skip reason, or "" if applicable.
    110 case_skip_reason() {
    111   local os="$1" arch="$2" name="$3" src="$4" own
    112   if [ -e "${src%.toy}.link.skip" ]; then head -n1 "${src%.toy}.link.skip"; return; fi
    113   if [ -e "${src%.toy}.$os.skip" ]; then head -n1 "${src%.toy}.$os.skip"; return; fi
    114   if [ "$arch" != aarch64 ] && grep -q 'asmnop' "$src" 2>/dev/null; then
    115     echo "asmnop is aa64-only"; return; fi
    116   own="$(arch_suffix_for "$os" "$arch")"
    117   case "$name" in
    118     *_aa64) [ "$own" = _aa64 ] || { echo "aa64-only case"; return; } ;;
    119     *_x64)  [ "$own" = _x64 ]  || { echo "x64-only case"; return; } ;;
    120     *_rv64) [ "$own" = _rv64 ] || { echo "rv64-only case"; return; } ;;
    121   esac
    122   echo ""
    123 }
    124 
    125 modes_for() {
    126   if [ "$1" = windows ]; then echo ucrt; return; fi
    127   if [ "$1" = macos ]; then echo default; return; fi  # freestanding, no link split
    128   case "$LINK" in
    129     static) echo static ;;
    130     dynamic) echo dynamic ;;
    131     both|*) echo static dynamic ;;
    132   esac
    133 }
    134 
    135 cc_extra_flags() {
    136   case "$1/$2" in
    137     freebsd/static)  echo -static ;;
    138     freebsd/dynamic) echo ;;
    139     windows/ucrt)    echo -mconsole ;;
    140   esac
    141 }
    142 
    143 # ---- compile + record (no exec here) ---------------------------------------
    144 # Parallel arrays of staged cases awaiting execution.
    145 TF_LABEL=(); TF_EXP=(); TF_EXE=(); TF_OUT=(); TF_ERR=(); TF_RC=(); TF_TAG=()
    146 
    147 STAGED_N=0
    148 stage_arch() {
    149   local os="$1" arch="$2" triple="$3" sysroot="$4" stage="$5"
    150   local ext="" id=0 name base reason opt mode label out cc_err expected link_flag
    151   [ "$os" = windows ] && ext=".exe"
    152   local sysroot_flag=()
    153   [ -n "$sysroot" ] && sysroot_flag=(--sysroot "$sysroot")
    154   rm -rf "$stage"; mkdir -p "$stage"
    155 
    156   for src in "$TEST_DIR"/cases/*.toy; do
    157     base="$(basename "${src%.toy}")"
    158     [ -n "$FILTER" ] && case "$base" in *"$FILTER"*) ;; *) continue ;; esac
    159     reason="$(case_skip_reason "$os" "$arch" "$base" "$src")"
    160     if [ -n "$reason" ]; then kit_skip "$base/$os-$arch" "$reason"; continue; fi
    161     expected=0
    162     [ -f "${src%.toy}.expected" ] && expected="$(cat "${src%.toy}.expected")"
    163     # macOS .toy cases link freestanding (Mach-O LC_MAIN) by default; a
    164     # <name>.link.hosted sidecar forces a hosted link (kit cc -lc against the
    165     # SDK) for cases needing the libSystem runtime (e.g. _tlv_bootstrap for
    166     # thread-locals), mirroring test/toy/run.sh's path L.
    167     link_flag=()
    168     if [ "$os" = macos ] && [ -e "${src%.toy}.link.hosted" ]; then
    169       if [ -z "$MACOS_SDK" ]; then
    170         kit_skip "$base/$os-$arch" "needs macOS SDK for hosted link (xcrun --show-sdk-path)"; continue; fi
    171       link_flag=(--sysroot "$MACOS_SDK" -lc)
    172     fi
    173     for opt in $OPT_LEVELS; do
    174       for mode in $(modes_for "$os"); do
    175         label="$base/$os-$arch-$mode-O$opt"
    176         out="$stage/$id$ext"; cc_err="$stage/$id.cc.err"
    177         # shellcheck disable=SC2046
    178         if ! "$KIT" cc "-O$opt" -target "$triple" "${sysroot_flag[@]}" "${link_flag[@]}" \
    179               $(cc_extra_flags "$os" "$mode") "$src" -o "$out" \
    180               > "$stage/$id.cc.out" 2> "$cc_err"; then
    181           kit_fail "$label" "kit cc -target $triple failed"
    182           sed 's/^/    | /' "$cc_err"; continue
    183         fi
    184         if [ -s "$cc_err" ]; then
    185           kit_fail "$label" "kit cc -target $triple wrote stderr"
    186           sed 's/^/    | /' "$cc_err"; continue
    187         fi
    188         TF_LABEL+=("$label"); TF_EXP+=("$expected"); TF_EXE+=("$out")
    189         TF_OUT+=("$stage/$id.out"); TF_ERR+=("$stage/$id.err")
    190         TF_RC+=("$stage/$id.rc"); TF_TAG+=("$arch-$os")
    191         id=$((id + 1))
    192       done
    193     done
    194   done
    195   STAGED_N=$id
    196 }
    197 
    198 # ---- drive -----------------------------------------------------------------
    199 mkdir -p "$BUILD_DIR/$OS"
    200 
    201 # Phase 1: compile every applicable case for every arch (catches codegen/link
    202 # bugs even with no VM).
    203 case "$OS" in
    204   freebsd)
    205     printf 'toy-vm freebsd: arches="%s" opts="%s" link=%s\n' \
    206       "$FREEBSD_ARCHES" "$OPT_LEVELS" "$LINK"
    207     ARCHES="$FREEBSD_ARCHES" ;;
    208   windows)
    209     printf 'toy-vm windows: arches="%s" opts="%s"\n' "$WINDOWS_ARCHES" "$OPT_LEVELS"
    210     ARCHES="$WINDOWS_ARCHES" ;;
    211   macos)
    212     MACOS_SDK="$(xcrun --sdk macosx --show-sdk-path 2>/dev/null || true)"
    213     printf 'toy-vm macos(rosetta): arches="%s" opts="%s" sdk=%s\n' \
    214       "$MACOS_ARCHES" "$OPT_LEVELS" "${MACOS_SDK:-<none>}"
    215     ARCHES="$MACOS_ARCHES" ;;
    216 esac
    217 
    218 for arch in $ARCHES; do
    219   triple="$(triple_for "$OS" "$arch")"
    220   if [ -z "$triple" ]; then kit_skip_na "toy/$OS-$arch" "unknown arch"; continue; fi
    221   sysroot="$(sysroot_for "$OS" "$arch")"
    222   case "$OS" in
    223     freebsd)
    224       if [ -z "$sysroot" ] || [ ! -d "$sysroot/usr/include" ]; then
    225         kit_skip "toy/$OS-$arch" "missing sysroot (scripts/freebsd_sysroot.sh $arch)"; continue; fi
    226       case "$LINK" in static|both) [ -f "$sysroot/usr/lib/libc.a" ] || {
    227         kit_skip "toy/$OS-$arch" "missing $sysroot/usr/lib/libc.a"; continue; } ;; esac
    228       case "$LINK" in dynamic|both) [ -f "$sysroot/lib/libc.so.7" ] || {
    229         kit_skip "toy/$OS-$arch" "missing $sysroot/lib/libc.so.7"; continue; } ;; esac
    230       ;;
    231     windows)
    232       if [ -z "$sysroot" ] || [ ! -r "$sysroot/include/windows.h" ] ||
    233          [ ! -r "$sysroot/lib/libucrt.a" ]; then
    234         kit_skip "toy/$OS-$arch" "missing UCRT sysroot (scripts/llvm_mingw_sysroot.sh prepare $arch)"; continue; fi
    235       ;;
    236     macos)
    237       : # freestanding .toy cases — no sysroot prerequisite; exec gated on Rosetta
    238       ;;
    239   esac
    240   stage_arch "$OS" "$arch" "$triple" "$sysroot" "$BUILD_DIR/$OS/$arch"
    241 done
    242 
    243 # Phase 2: execute via the shared seam. Queue every staged case whose tag has a
    244 # runner; SKIP (do not FAIL) cases for a tag with no VM. One flush drains all
    245 # tags, booting each VM lazily and reusing it across tags (one Windows VM serves
    246 # both arches); the EXIT trap tears the VMs down.
    247 n="${#TF_LABEL[@]}"
    248 i=0
    249 while [ "$i" -lt "$n" ]; do
    250   tag="${TF_TAG[$i]}"
    251   if exec_target_supported "$tag"; then
    252     exec_target_queue "$tag" "${TF_LABEL[$i]}" "${TF_EXE[$i]}" \
    253       "${TF_OUT[$i]}" "${TF_ERR[$i]}" "${TF_RC[$i]}"
    254   else
    255     kit_skip "${TF_LABEL[$i]}" "no runner for $tag"
    256     TF_RC[$i]=""   # mark "not executed" so the result loop skips it
    257   fi
    258   i=$((i + 1))
    259 done
    260 
    261 exec_target_flush
    262 
    263 # Compare each executed case's exit code to its oracle (8-bit mask: the .toy
    264 # oracle is a POSIX exit status; exec_vm already masks Windows codes when writing
    265 # .rc, so this is a no-op there but keeps FreeBSD >255 returns honest).
    266 i=0
    267 while [ "$i" -lt "$n" ]; do
    268   rcfile="${TF_RC[$i]}"
    269   if [ -z "$rcfile" ]; then i=$((i + 1)); continue; fi   # skipped above
    270   label="${TF_LABEL[$i]}"; exp=$(( ${TF_EXP[$i]} & 255 ))
    271   rc="$(cat "$rcfile" 2>/dev/null || echo 127)"
    272   case "$rc" in
    273     ''|*[!0-9-]*)
    274       kit_fail "$label" "binary did not run in VM (rc=$rc)" ;;
    275     *)
    276       if [ "$(( rc & 255 ))" -eq "$exp" ] 2>/dev/null; then kit_pass "$label"
    277       else kit_fail "$label" "expected rc $exp, got $rc"; fi ;;
    278   esac
    279   i=$((i + 1))
    280 done
    281 
    282 KIT_SKIP_IS_FAILURE=0
    283 kit_summary "toy-vm-$OS"
    284 kit_exit