kit

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

vm.sh (9311B)


      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
     20 #
     21 # env: KIT, KIT_TOY_FREEBSD_ARCHES (amd64 aarch64 riscv64),
     22 #   KIT_TOY_WINDOWS_ARCHES (x64 aarch64), KIT_OPT_LEVELS (0 1),
     23 #   KIT_FREEBSD_LINK (static|dynamic|both), KIT_TEST_FILTER, KIT_TOY_VM_KEEP_UP.
     24 #
     25 # Skips (missing sysroot/VM, arch-inapplicable cases) are non-fatal — only a real
     26 # FAIL gates the exit. Genuine codegen gaps are left RED on purpose; opt a case
     27 # out only with a committed <name>.<os>.skip sidecar when truly inapplicable.
     28 
     29 set -u
     30 
     31 ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
     32 KIT="${KIT:-$ROOT/build/kit}"
     33 TEST_DIR="$ROOT/test/toy"
     34 BUILD_DIR="$ROOT/build/test/toy-vm"
     35 
     36 # shellcheck source=../lib/kit_sh_report.sh
     37 . "$ROOT/test/lib/kit_sh_report.sh"
     38 # shellcheck source=../lib/exec_target.sh
     39 . "$ROOT/test/lib/exec_target.sh"
     40 kit_report_init
     41 
     42 # Honor the legacy keep-up knob through exec_vm's teardown gate, and shut any VM
     43 # we booted down at exit.
     44 [ "${KIT_TOY_VM_KEEP_UP:-0}" = 1 ] && export EXEC_VM_KEEP_UP=1
     45 trap exec_target_teardown_all EXIT
     46 
     47 OS="${1:-}"
     48 FILTER="${2:-${KIT_TEST_FILTER:-}}"
     49 case "$OS" in
     50   freebsd|windows) ;;
     51   *) echo "usage: $0 <freebsd|windows> [name_filter]" >&2; exit 2 ;;
     52 esac
     53 
     54 OPT_LEVELS="${KIT_OPT_LEVELS:-0 1}"
     55 FREEBSD_ARCHES="${KIT_TOY_FREEBSD_ARCHES:-amd64 aarch64 riscv64}"
     56 WINDOWS_ARCHES="${KIT_TOY_WINDOWS_ARCHES:-x64 aarch64}"
     57 LINK="${KIT_FREEBSD_LINK:-both}"
     58 
     59 if [ ! -x "$KIT" ]; then
     60   echo "$OS: kit binary not found at $KIT (run 'make bin')" >&2
     61   exit 2
     62 fi
     63 
     64 shopt -s nullglob
     65 
     66 # ---- target / sysroot resolution -------------------------------------------
     67 triple_for() {
     68   case "$1/$2" in
     69     freebsd/amd64)   echo x86_64-freebsd ;;
     70     freebsd/aarch64) echo aarch64-freebsd ;;
     71     freebsd/riscv64) echo riscv64-freebsd ;;
     72     windows/x64)     echo x86_64-windows ;;
     73     windows/aarch64) echo aarch64-windows ;;
     74     *) echo "" ;;
     75   esac
     76 }
     77 
     78 sysroot_for() {
     79   case "$1" in
     80     freebsd) "$ROOT/scripts/freebsd_sysroot.sh" path "$2" 2>/dev/null ;;
     81     windows) "$ROOT/scripts/llvm_mingw_sysroot.sh" path "$2" 2>/dev/null ;;
     82   esac
     83 }
     84 
     85 # The basename suffix this (os,arch) "owns" — arch-only cases named *_x64 /
     86 # *_aa64 / *_rv64 use intrinsics that only lower on that arch (cf. run.sh).
     87 arch_suffix_for() {
     88   case "$1/$2" in
     89     freebsd/amd64|windows/x64) echo _x64 ;;
     90     */aarch64)                 echo _aa64 ;;
     91     freebsd/riscv64)           echo _rv64 ;;
     92     *) echo "" ;;
     93   esac
     94 }
     95 
     96 # case_skip_reason OS ARCH NAME SRC -> echoes a skip reason, or "" if applicable.
     97 case_skip_reason() {
     98   local os="$1" arch="$2" name="$3" src="$4" own
     99   if [ -e "${src%.toy}.link.skip" ]; then head -n1 "${src%.toy}.link.skip"; return; fi
    100   if [ -e "${src%.toy}.$os.skip" ]; then head -n1 "${src%.toy}.$os.skip"; return; fi
    101   if [ "$arch" != aarch64 ] && grep -q 'asmnop' "$src" 2>/dev/null; then
    102     echo "asmnop is aa64-only"; return; fi
    103   own="$(arch_suffix_for "$os" "$arch")"
    104   case "$name" in
    105     *_aa64) [ "$own" = _aa64 ] || { echo "aa64-only case"; return; } ;;
    106     *_x64)  [ "$own" = _x64 ]  || { echo "x64-only case"; return; } ;;
    107     *_rv64) [ "$own" = _rv64 ] || { echo "rv64-only case"; return; } ;;
    108   esac
    109   echo ""
    110 }
    111 
    112 modes_for() {
    113   if [ "$1" = windows ]; then echo ucrt; return; fi
    114   case "$LINK" in
    115     static) echo static ;;
    116     dynamic) echo dynamic ;;
    117     both|*) echo static dynamic ;;
    118   esac
    119 }
    120 
    121 cc_extra_flags() {
    122   case "$1/$2" in
    123     freebsd/static)  echo -static ;;
    124     freebsd/dynamic) echo ;;
    125     windows/ucrt)    echo -mconsole ;;
    126   esac
    127 }
    128 
    129 # ---- compile + record (no exec here) ---------------------------------------
    130 # Parallel arrays of staged cases awaiting execution.
    131 TF_LABEL=(); TF_EXP=(); TF_EXE=(); TF_OUT=(); TF_ERR=(); TF_RC=(); TF_TAG=()
    132 
    133 STAGED_N=0
    134 stage_arch() {
    135   local os="$1" arch="$2" triple="$3" sysroot="$4" stage="$5"
    136   local ext="" id=0 name base reason opt mode label out cc_err expected
    137   [ "$os" = windows ] && ext=".exe"
    138   rm -rf "$stage"; mkdir -p "$stage"
    139 
    140   for src in "$TEST_DIR"/cases/*.toy; do
    141     base="$(basename "${src%.toy}")"
    142     [ -n "$FILTER" ] && case "$base" in *"$FILTER"*) ;; *) continue ;; esac
    143     reason="$(case_skip_reason "$os" "$arch" "$base" "$src")"
    144     if [ -n "$reason" ]; then kit_skip "$base/$os-$arch" "$reason"; continue; fi
    145     expected=0
    146     [ -f "${src%.toy}.expected" ] && expected="$(cat "${src%.toy}.expected")"
    147     for opt in $OPT_LEVELS; do
    148       for mode in $(modes_for "$os"); do
    149         label="$base/$os-$arch-$mode-O$opt"
    150         out="$stage/$id$ext"; cc_err="$stage/$id.cc.err"
    151         # shellcheck disable=SC2046
    152         if ! "$KIT" cc "-O$opt" -target "$triple" --sysroot "$sysroot" \
    153               $(cc_extra_flags "$os" "$mode") "$src" -o "$out" \
    154               > "$stage/$id.cc.out" 2> "$cc_err"; then
    155           kit_fail "$label" "kit cc -target $triple failed"
    156           sed 's/^/    | /' "$cc_err"; continue
    157         fi
    158         if [ -s "$cc_err" ]; then
    159           kit_fail "$label" "kit cc -target $triple wrote stderr"
    160           sed 's/^/    | /' "$cc_err"; continue
    161         fi
    162         TF_LABEL+=("$label"); TF_EXP+=("$expected"); TF_EXE+=("$out")
    163         TF_OUT+=("$stage/$id.out"); TF_ERR+=("$stage/$id.err")
    164         TF_RC+=("$stage/$id.rc"); TF_TAG+=("$arch-$os")
    165         id=$((id + 1))
    166       done
    167     done
    168   done
    169   STAGED_N=$id
    170 }
    171 
    172 # ---- drive -----------------------------------------------------------------
    173 mkdir -p "$BUILD_DIR/$OS"
    174 
    175 # Phase 1: compile every applicable case for every arch (catches codegen/link
    176 # bugs even with no VM).
    177 if [ "$OS" = freebsd ]; then
    178   printf 'toy-vm freebsd: arches="%s" opts="%s" link=%s\n' \
    179     "$FREEBSD_ARCHES" "$OPT_LEVELS" "$LINK"
    180   ARCHES="$FREEBSD_ARCHES"
    181 else
    182   printf 'toy-vm windows: arches="%s" opts="%s"\n' "$WINDOWS_ARCHES" "$OPT_LEVELS"
    183   ARCHES="$WINDOWS_ARCHES"
    184 fi
    185 
    186 for arch in $ARCHES; do
    187   triple="$(triple_for "$OS" "$arch")"
    188   if [ -z "$triple" ]; then kit_skip_na "toy/$OS-$arch" "unknown arch"; continue; fi
    189   sysroot="$(sysroot_for "$OS" "$arch")"
    190   if [ "$OS" = freebsd ]; then
    191     if [ -z "$sysroot" ] || [ ! -d "$sysroot/usr/include" ]; then
    192       kit_skip "toy/$OS-$arch" "missing sysroot (scripts/freebsd_sysroot.sh $arch)"; continue; fi
    193     case "$LINK" in static|both) [ -f "$sysroot/usr/lib/libc.a" ] || {
    194       kit_skip "toy/$OS-$arch" "missing $sysroot/usr/lib/libc.a"; continue; } ;; esac
    195     case "$LINK" in dynamic|both) [ -f "$sysroot/lib/libc.so.7" ] || {
    196       kit_skip "toy/$OS-$arch" "missing $sysroot/lib/libc.so.7"; continue; } ;; esac
    197   else
    198     if [ -z "$sysroot" ] || [ ! -r "$sysroot/include/windows.h" ] ||
    199        [ ! -r "$sysroot/lib/libucrt.a" ]; then
    200       kit_skip "toy/$OS-$arch" "missing UCRT sysroot (scripts/llvm_mingw_sysroot.sh prepare $arch)"; continue; fi
    201   fi
    202   stage_arch "$OS" "$arch" "$triple" "$sysroot" "$BUILD_DIR/$OS/$arch"
    203 done
    204 
    205 # Phase 2: execute via the shared seam. Queue every staged case whose tag has a
    206 # runner; SKIP (do not FAIL) cases for a tag with no VM. One flush drains all
    207 # tags, booting each VM lazily and reusing it across tags (one Windows VM serves
    208 # both arches); the EXIT trap tears the VMs down.
    209 n="${#TF_LABEL[@]}"
    210 i=0
    211 while [ "$i" -lt "$n" ]; do
    212   tag="${TF_TAG[$i]}"
    213   if exec_target_supported "$tag"; then
    214     exec_target_queue "$tag" "${TF_LABEL[$i]}" "${TF_EXE[$i]}" \
    215       "${TF_OUT[$i]}" "${TF_ERR[$i]}" "${TF_RC[$i]}"
    216   else
    217     kit_skip "${TF_LABEL[$i]}" "no runner for $tag"
    218     TF_RC[$i]=""   # mark "not executed" so the result loop skips it
    219   fi
    220   i=$((i + 1))
    221 done
    222 
    223 exec_target_flush
    224 
    225 # Compare each executed case's exit code to its oracle (8-bit mask: the .toy
    226 # oracle is a POSIX exit status; exec_vm already masks Windows codes when writing
    227 # .rc, so this is a no-op there but keeps FreeBSD >255 returns honest).
    228 i=0
    229 while [ "$i" -lt "$n" ]; do
    230   rcfile="${TF_RC[$i]}"
    231   if [ -z "$rcfile" ]; then i=$((i + 1)); continue; fi   # skipped above
    232   label="${TF_LABEL[$i]}"; exp=$(( ${TF_EXP[$i]} & 255 ))
    233   rc="$(cat "$rcfile" 2>/dev/null || echo 127)"
    234   case "$rc" in
    235     ''|*[!0-9-]*)
    236       kit_fail "$label" "binary did not run in VM (rc=$rc)" ;;
    237     *)
    238       if [ "$(( rc & 255 ))" -eq "$exp" ] 2>/dev/null; then kit_pass "$label"
    239       else kit_fail "$label" "expected rc $exp, got $rc"; fi ;;
    240   esac
    241   i=$((i + 1))
    242 done
    243 
    244 KIT_SKIP_IS_FAILURE=0
    245 kit_summary "toy-vm-$OS"
    246 kit_exit