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