diff --git a/.github/workflows/bootstrap.yml b/.github/workflows/bootstrap.yml index 5566872..1e76631 100644 --- a/.github/workflows/bootstrap.yml +++ b/.github/workflows/bootstrap.yml @@ -274,3 +274,79 @@ jobs: ${{ steps.pre.outputs.dir }}/blaise_rtl.a retention-days: 30 if-no-files-found: error + + # ====================================================================== + # FreeBSD cross-compile verification under emulation + # (docs/freebsd-x86_64-backend-design.adoc Step 8). + # + # The bootstrap job's TestRunner already runs the HOST-side static ELF + # checks (EI_OSABI / ET_EXEC / no PT_INTERP via + # TInternalLinkerE2ETests.TestCompile_FreeBSDTarget_EmitsStaticFreeBSDExe). + # Those cannot catch a wrong FreeBSD struct-stat offset or syscall number — + # only running the binary on a real FreeBSD kernel does. This job + # cross-compiles the fixtures on the Linux runner, then boots a FreeBSD VM + # (vmactions/freebsd-vm) and executes them there. + # + # The FreeBSD output is a static, freestanding raw-syscall ET_EXEC (no libc, + # no rtld), so the VM needs no extra packages to run it. + # ====================================================================== + freebsd-crosscheck: + needs: bootstrap + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Checkout + uses: actions/checkout@v6 + + # The RTL is compiled from source by the -pre binary, so the compiler + # needs no prebuilt blaise_rtl.a beside it — only the checked-out RTL + # source (present after checkout). Pull the -pre binary the bootstrap + # job just built and published. + - name: Download -pre binary + uses: actions/download-artifact@v7 + with: + name: blaise-pre-${{ github.sha }} + path: .pre + + - name: Cross-compile FreeBSD fixtures (Linux host) + run: | + set -euo pipefail + chmod +x .pre/blaise + BLAISE="$PWD/.pre/blaise" scripts/freebsd-crosscheck-build.sh + echo "Cross-built FreeBSD binaries:" + ls -l target/freebsd-crosscheck + + # Boot a FreeBSD VM and run the cross-compiled binaries there. The + # workspace is rsync-mounted into the VM, so target/freebsd-crosscheck/* + # is available inside. release pinned to a 14.x line: the struct-stat + # offsets in rtl.platform.layout.freebsd are verified against FreeBSD 14 + # (stable since FreeBSD 12). Bump when 15.x is validated. + - name: Run FreeBSD binaries under emulation + uses: vmactions/freebsd-vm@v1 + with: + release: "14.2" + usesh: true + copyback: false + run: | + set -e + cd target/freebsd-crosscheck + echo "=== uname ===" + uname -a + + echo "=== hello ===" + OUT="$(./hello)" + echo "$OUT" + [ "$OUT" = "Hello" ] || { echo "FAIL: hello output '$OUT' != 'Hello'"; exit 1; } + + echo "=== fileio ===" + OUT="$(./fileio)" + echo "$OUT" + # Flatten to a single line (newlines -> '|') so the compare stays a + # one-line shell string inside this YAML block scalar. + GOT="$(printf '%s' "$OUT" | tr '\n' '|')" + [ "$GOT" = "file-io-ok|exists-ok|done" ] || { + echo "FAIL: fileio output '$GOT' != 'file-io-ok|exists-ok|done'" + exit 1 + } + + echo "FREEBSD_EMULATION_OK" diff --git a/compiler/src/it/freebsd/fileio.pas b/compiler/src/it/freebsd/fileio.pas new file mode 100644 index 0000000..50f6b6e --- /dev/null +++ b/compiler/src/it/freebsd/fileio.pas @@ -0,0 +1,25 @@ +{ FreeBSD cross-compile emulation smoke test (docs/freebsd-x86_64-backend-design + Step 8). Exercises the file path that STATIC ELF checks cannot catch: a wrong + struct-stat offset or syscall number only shows up when the binary actually + runs on FreeBSD. Write a file, read it back, and stat it (FileExists), then + print the results. Expected output under FreeBSD: + + file-io-ok + exists-ok + done + + A wrong FreeBSD st_size / st_mode offset (they differ from Linux — see + rtl.platform.layout.freebsd) would make ReadFile or FileExists misbehave. } +program fileio; +uses sysutils; +var + S: string; +begin + WriteFile('blaise_fbsd_it.txt', 'file-io-ok'); + S := ReadFile('blaise_fbsd_it.txt'); + WriteLn(S); + if FileExists('blaise_fbsd_it.txt') then + WriteLn('exists-ok'); + DeleteFile('blaise_fbsd_it.txt'); + WriteLn('done'); +end. diff --git a/compiler/src/it/freebsd/hello.pas b/compiler/src/it/freebsd/hello.pas new file mode 100644 index 0000000..882b177 --- /dev/null +++ b/compiler/src/it/freebsd/hello.pas @@ -0,0 +1,9 @@ +{ FreeBSD cross-compile emulation smoke test (docs/freebsd-x86_64-backend-design + Step 8). Cross-compiled with --target freebsd-x86_64 on the Linux CI runner, + then executed inside a FreeBSD VM. A raw-syscall static ET_EXEC: printing + Hello and exiting 0 proves _start, argv capture, write(2) and _exit(2) work + under the real FreeBSD kernel. } +program hello; +begin + WriteLn('Hello'); +end. diff --git a/scripts/freebsd-crosscheck-build.sh b/scripts/freebsd-crosscheck-build.sh new file mode 100755 index 0000000..ad52272 --- /dev/null +++ b/scripts/freebsd-crosscheck-build.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash +# +# Cross-compile the FreeBSD emulation-check fixtures (compiler/src/it/freebsd/*.pas) +# with --target freebsd-x86_64 on a Linux host. The resulting static, freestanding +# FreeBSD ET_EXEC binaries are then run inside a FreeBSD VM (see the freebsd-crosscheck +# job in .github/workflows/bootstrap.yml) to validate the FreeBSD syscall leaf and +# struct-stat layout that host-side static ELF checks cannot catch. +# +# Usage: +# BLAISE=/path/to/blaise scripts/freebsd-crosscheck-build.sh [OUTDIR] +# +# BLAISE path to a native-backend blaise binary that supports --target +# freebsd-x86_64 (defaults to compiler/target/blaise). +# OUTDIR where to write the cross-compiled binaries (defaults to +# target/freebsd-crosscheck). +# +# The binaries CANNOT run on the Linux host (FreeBSD syscall numbers/ABI); this +# script only builds and sanity-checks the emitted ELF shape. Prints +# FREEBSD_CROSSBUILD_OK on success. +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "$ROOT" + +BLAISE="${BLAISE:-compiler/target/blaise}" +OUTDIR="${1:-target/freebsd-crosscheck}" +FIXDIR="compiler/src/it/freebsd" + +if [ ! -x "$BLAISE" ]; then + echo "error: blaise binary not found or not executable: $BLAISE" >&2 + exit 1 +fi + +mkdir -p "$OUTDIR" + +# The compiler resolves blaise_rtl.a / the RTL source relative to its own binary; +# the RTL source lives in the compiler tree. Both unit paths mirror the normal +# native build. +UNIT_ARGS=( + --unit-path runtime/src/main/pascal + --unit-path stdlib/src/main/pascal +) + +for src in "$FIXDIR"/*.pas; do + name="$(basename "$src" .pas)" + out="$OUTDIR/$name" + echo "cross-compiling $src -> $out (freebsd-x86_64)" + "$BLAISE" \ + --source "$src" \ + --backend native \ + --target freebsd-x86_64 \ + --output "$out" \ + "${UNIT_ARGS[@]}" + + # Sanity-check the emitted ELF is a FreeBSD (EI_OSABI=9) ET_EXEC. readelf is + # part of binutils, always present on the Linux runner. + osabi="$(readelf -hW "$out" | awk -F: '/OS\/ABI/ {print $2}' | xargs)" + etype="$(readelf -hW "$out" | awk -F: '/Type:/ {print $2}' | awk '{print $1}')" + case "$osabi" in + *FreeBSD*) : ;; + *) echo "error: $out is not a FreeBSD binary (OS/ABI=$osabi)" >&2; exit 1 ;; + esac + if [ "$etype" != "EXEC" ]; then + echo "error: $out is not a static ET_EXEC (Type=$etype)" >&2 + exit 1 + fi +done + +# Drop the .o intermediates the compiler leaves beside each output, so the VM +# job can run every executable in OUTDIR without tripping over object files. +rm -f "$OUTDIR"/*.o + +echo "FREEBSD_CROSSBUILD_OK"