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