Builds on the source-built-RTL link path. Two correctness fixes plus the fixpoint-script migration that takes the self-host pipeline off blaise_rtl.a. 1. Symbol-based dedup. A program that uses an RTL unit (e.g. `uses classes` pulls runtime.arc) compiles that unit into its own object cache, so EnsureRTLObjects must not supply a second copy or the link double-defines it. The dedup now compares the DEFINED symbols of the caller's objects against each RTL object (via the ELF reader) and skips one only when fully redundant — matching the old archive's member selection. An earlier filename-only check wrongly suppressed the real bare-symbol provider when a same-named but differently-mangled stale object was present. 2. Race-free build. EnsureRTLObjects now builds into a per-process private directory (rtl/build-<pid>/) and atomically renames each finished object into the shared cache. Parallel builders (e.g. e2e suites warming a cold cache) no longer corrupt each other: a reader never sees a half-written object, and two builders just publish identical results. The private dir is cleaned up afterwards. getpid/rmdir are declared directly rather than via GRtlPlatform to avoid a unit-init-order crash in the driver. 3. Fixpoint scripts off the archive. New scripts/build-rtl-objects.sh builds the RTL from source (the script-level equivalent of EnsureRTLObjects), with --exclude-defined-by so a whole-program --emit-ir/--emit-asm dump (which inlines the RTL units the compiler uses) is not handed a duplicate copy. fixpoint.sh and fixpoint-native.sh link via this helper instead of blaise_rtl.a; fixpoint-native-internal.sh and fixpoint-warmcache.sh no longer copy the archive beside the compiler (the driver source-builds the RTL). All four fixpoints green (FIXPOINT_OK, NATIVE_FIXPOINT_OK, NATIVE_INTERNAL_OK, WARMCACHE_FIXPOINT_OK); full suite green (3829 tests) under QBE- and native-built runners; four cold-cache parallel suite runs pass. The runtime Makefile / ar / make install are still present; removing them is the final Stage-3 step.
151 lines
5.9 KiB
Bash
Executable file
151 lines
5.9 KiB
Bash
Executable file
#!/bin/bash
|
|
# Internal-toolchain conformance check for the Blaise self-hosting toolchain.
|
|
#
|
|
# WHAT IT GUARDS:
|
|
# The DEFAULT native pipeline since v0.12.0 is the in-process internal
|
|
# assembler (blaise.assembler.x86_64) + internal linker (blaise.linker.elf):
|
|
# source -> native .s -> internal assembler -> .o -> internal linker ->
|
|
# executable, with zero external tools (only distro CRT objects are read).
|
|
# This script asserts that the fully-internal pipeline produces a binary
|
|
# that BEHAVES identically to the trusted all-external (gcc assemble + gcc
|
|
# link) reference.
|
|
#
|
|
# WHY IT EXISTS, separately from fixpoint-native.sh:
|
|
# fixpoint-native.sh validates native CODEGEN by diffing the emitted .s
|
|
# text, but it assembles + links that text with EXTERNAL gcc. Neither the
|
|
# internal assembler nor the internal linker is on that path, so a defect
|
|
# in either silently passes both fixpoints (this is exactly how the
|
|
# sret-Result field-read bug hid; see bugs.txt 2026-06-16). This script is
|
|
# the only fixpoint-level guard that drives both internal tools end to end.
|
|
#
|
|
# WHY IT IS A DIFFERENTIAL CHECK, NOT A SELF-HOSTING FIXPOINT:
|
|
# The internal assembler currently buffers the whole assembly + ELF
|
|
# object in memory, so assembling the compiler's own ~631k-line .s needs
|
|
# ~53 GB and OOM-kills. A true "compile the compiler with itself via the
|
|
# internal toolchain" fixpoint is therefore not feasible until the internal
|
|
# assembler streams its output. Instead we compile a representative program
|
|
# that exercises the bug-prone paths (record-return sret-Result field reads,
|
|
# immutable string literals) BOTH ways and assert the internal-toolchain
|
|
# binary BEHAVES identically (stdout + exit code) to the all-external
|
|
# reference. Note: the internal and external tools may emit
|
|
# different-but-valid encodings / section layouts, so a byte-level compare
|
|
# would false-positive; behavioural equivalence is the sound invariant.
|
|
#
|
|
# Requires: a native compiler at compiler/target/blaise (run the QBE
|
|
# fixpoint or `pasbuild compile` first). The RTL is source-built by the
|
|
# driver, so no blaise_rtl.a is needed.
|
|
|
|
set -e
|
|
|
|
if [ ! -f "compiler/src/main/pascal/Blaise.pas" ]; then
|
|
echo "Run this script from the project root: ./scripts/fixpoint-native-internal.sh" >&2
|
|
exit 1
|
|
fi
|
|
|
|
COMPILER="${1:-compiler/target/blaise}"
|
|
if [ ! -x "$COMPILER" ]; then
|
|
echo "Compiler not found: $COMPILER" >&2
|
|
echo "Build it first: pasbuild compile -m blaise-compiler --compiler ..." >&2
|
|
exit 10
|
|
fi
|
|
|
|
# No RTL archive needed: the compiler driver source-builds the RTL into its
|
|
# own object cache (EnsureRTLObjects) and links it directly. The RTL source
|
|
# lives in compiler/src/main/pascal (resolved automatically by the driver).
|
|
if [ ! -f compiler/src/main/pascal/runtime.arc.pas ]; then
|
|
echo "RTL source not found under compiler/src/main/pascal" >&2
|
|
exit 11
|
|
fi
|
|
|
|
UNIT_ARGS="--unit-path compiler/src/main/pascal --unit-path stdlib/src/main/pascal"
|
|
PROBE=/tmp/fpni_probe.pas
|
|
|
|
# Representative program. Exercises the sret-Result field-read path (a
|
|
# record-returning function reads one Result field into another) plus many
|
|
# immutable string literals (each emits a `.long -1` header the internal
|
|
# assembler must encode correctly).
|
|
cat > "$PROBE" <<'PROBE_EOF'
|
|
program fpni_probe;
|
|
|
|
type
|
|
TPoint = record
|
|
X: Int64;
|
|
Y: Int64;
|
|
Name: string;
|
|
end;
|
|
|
|
function MakePoint(AX, AY: Int64; const AName: string): TPoint;
|
|
begin
|
|
Result.X := AX;
|
|
Result.Y := AY;
|
|
Result.Name := AName;
|
|
if Result.X >= Result.Y then
|
|
Result.Name := AName + '-wide'
|
|
else
|
|
Result.Name := AName + '-tall';
|
|
end;
|
|
|
|
var
|
|
P: TPoint;
|
|
I: Integer;
|
|
Total: Int64;
|
|
begin
|
|
Total := 0;
|
|
for I := 0 to 4 do
|
|
begin
|
|
P := MakePoint(I, I * 2, 'pt');
|
|
Total := Total + P.X + P.Y;
|
|
WriteLn(P.Name, ' ', P.X, ',', P.Y);
|
|
end;
|
|
WriteLn('total=', Total);
|
|
WriteLn('immutable string literals and record return survived');
|
|
end.
|
|
PROBE_EOF
|
|
|
|
echo "compiler: $COMPILER"
|
|
|
|
echo "[1/3] compile probe with INTERNAL assembler + INTERNAL linker (default pipeline)"
|
|
if ! "$COMPILER" --source "$PROBE" $UNIT_ARGS \
|
|
--backend native --assembler internal --linker internal \
|
|
--output /tmp/fpni_int 2>/tmp/fpni_int.err; then
|
|
echo "INTERNAL_COMPILE_FAIL"; head -5 /tmp/fpni_int.err; exit 2
|
|
fi
|
|
|
|
echo "[2/3] compile probe with EXTERNAL assembler + EXTERNAL linker (reference)"
|
|
if ! "$COMPILER" --source "$PROBE" $UNIT_ARGS \
|
|
--backend native --assembler external --linker external \
|
|
--output /tmp/fpni_ext 2>/tmp/fpni_ext.err; then
|
|
echo "EXTERNAL_COMPILE_FAIL"; head -5 /tmp/fpni_ext.err; exit 3
|
|
fi
|
|
|
|
echo "[3/3] run both and compare stdout + exit code"
|
|
# Behavioural equivalence is the sound invariant: the internal and external
|
|
# toolchains may emit DIFFERENT-but-valid encodings / section layouts (so a
|
|
# byte-level .text/.rodata compare would false-positive), but a binary built
|
|
# by the internal assembler + internal linker must BEHAVE identically to the
|
|
# all-external reference. The sret-Result bug broke exactly this (corrupted
|
|
# string headers => wrong / garbage output), so stdout + exit code catch it.
|
|
# Disable -e around the runs: a miscompiled internal binary may crash
|
|
# (e.g. SIGSEGV from a corrupted string header), and we must REPORT that as
|
|
# a guard failure rather than let -e abort the script mid-diagnostic.
|
|
set +e
|
|
/tmp/fpni_int > /tmp/fpni_int.out 2>&1; INT_RC=$?
|
|
/tmp/fpni_ext > /tmp/fpni_ext.out 2>&1; EXT_RC=$?
|
|
set -e
|
|
if [ "$INT_RC" -ne "$EXT_RC" ]; then
|
|
echo "EXIT_MISMATCH internal=$INT_RC external=$EXT_RC"
|
|
echo " (internal-assembler binary misbehaved — likely a codegen/assembler regression)"
|
|
echo "--- internal stdout ---"; cat /tmp/fpni_int.out
|
|
echo "--- external stdout ---"; cat /tmp/fpni_ext.out
|
|
exit 6
|
|
fi
|
|
if ! diff -q /tmp/fpni_int.out /tmp/fpni_ext.out >/dev/null; then
|
|
echo "OUTPUT_MISMATCH (internal assembler produced a binary that behaves differently)"
|
|
echo "--- internal ---"; cat /tmp/fpni_int.out
|
|
echo "--- external ---"; cat /tmp/fpni_ext.out
|
|
exit 6
|
|
fi
|
|
|
|
echo "NATIVE_INTERNAL_OK"
|
|
exit 0
|