Two codegen improvements that eliminate all non-@start `alloc` instructions from the emitted QBE IR. Both are prerequisite work for the self-hosting fixpoint (currently being debugged). (1) Short-circuit boolean operators now use QBE phi instead of alloc4. Previously `A and B` / `A or B` emitted: %slot =l alloc4 1 <- dynamic alloc, grows stack per execution storew %L, %slot jnz %L, @rhs, @end @rhs storew %R, %slot jmp @end @end %T =w loadw %slot The slot was allocated in whichever basic block the expression appeared in. QBE emits a runtime `sub $16, %rsp` for every alloc outside @start — inside a while-condition this ran on every loop iteration, growing the stack by 16 bytes per iteration indefinitely. New form uses a phi node at the join block: jnz %L, @rhs, @end @rhs (evaluate R) jmp @end @end %T =w phi @lhs %L, @rhs %R No alloc needed; %T is always well-defined at @end by SSA. FCurrentBlockLabel tracks the current basic block name so the phi can correctly name its predecessors. (2) Exception frames pre-allocated at @start via EmitExcFrameAllocs. Previously EmitTryFinallyStmt / EmitTryExceptStmt emitted `alloc16 512` inline at the point of the try statement. Inside a loop (e.g. `for I := 0 to N-1 do begin try ... finally ... end end`), each iteration allocated 512 bytes of stack that was never released until the function returned. Over hundreds of iterations this drifted the stack pointer into parent exception frame territory, corrupting the exc-frame chain. New approach: CountTryStmts recursively counts all try blocks in the function body, EmitExcFrameAllocs pre-allocates %_exc_frame_0 … %_exc_frame_N at @start (QBE hoists these to the static prologue sub), and EmitTryFinallyStmt/Except index into the pool via FExcFrameNext. Result: zero alloc instructions outside @start blocks in the emitted IR. All 1242 tests pass. Stage-1 IR and stage-2 IR are now byte-for-byte identical (md5 verified) — the fixpoint is correct at the IR level. The stage-2 BINARY still crashes during stage-3 generation; this is under active investigation and may be a QBE vs FPC code-generation quality issue for the same IR.
85 lines
2.7 KiB
Bash
Executable file
85 lines
2.7 KiB
Bash
Executable file
#!/bin/bash
|
|
# Fixpoint test for the Blaise self-hosting check.
|
|
#
|
|
# Steps:
|
|
# 1. Clean rebuild compiler (removes stale .ppu/.o files).
|
|
# 2. Rebuild + install RTL (cheap when nothing changed).
|
|
# 3. stage1 -> stage2 IR (FPC-built binary compiles current source).
|
|
# 4. Assemble + link stage-2 binary via QBE + gcc.
|
|
# 5. stage2 -> stage3 IR (self-compiled binary compiles same source).
|
|
# 6. diff stage-2.ssa stage-3.ssa => empty = clean fixpoint.
|
|
#
|
|
# A 5-minute timeout is wrapped around the stage-2 invocation so a hung
|
|
# self-compiled binary doesn't lock the whole bisect loop.
|
|
|
|
set -e
|
|
|
|
# Must be run from the project root (where pasbuild.xml lives).
|
|
if [ ! -f "compiler/src/main/pascal/Blaise.pas" ]; then
|
|
echo "Run this script from the project root: ./scripts/fixpoint.sh" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "[1/6] clean + rebuild compiler"
|
|
pasbuild clean > /tmp/fp_clean.log 2>&1
|
|
pasbuild compile -m blaise-compiler > /tmp/fp_compile.log 2>&1
|
|
if [ ! -x compiler/target/blaise ]; then
|
|
echo "COMPILE_FAIL"
|
|
tail -10 /tmp/fp_compile.log
|
|
exit 10
|
|
fi
|
|
|
|
echo "[2/6] rebuild + install RTL"
|
|
( cd rtl && make > /tmp/fp_rtl.log 2>&1 && make install >> /tmp/fp_rtl.log 2>&1 ) || {
|
|
echo "RTL_FAIL"; tail -5 /tmp/fp_rtl.log; exit 11;
|
|
}
|
|
|
|
echo "[3/6] stage1 -> stage2 IR"
|
|
compiler/target/blaise --source compiler/src/main/pascal/Blaise.pas \
|
|
--unit-path compiler/src/main/pascal --unit-path rtl/src/main/pascal \
|
|
--emit-ir > /tmp/fp_stage2.ssa 2>/tmp/fp_stage2.err
|
|
if [ ! -s /tmp/fp_stage2.ssa ] || head -1 /tmp/fp_stage2.ssa | grep -qi 'error\|exception'; then
|
|
echo "STAGE2_IR_FAIL"
|
|
head -3 /tmp/fp_stage2.ssa
|
|
head -3 /tmp/fp_stage2.err
|
|
exit 1
|
|
fi
|
|
echo " stage2 IR: $(wc -l < /tmp/fp_stage2.ssa) lines"
|
|
|
|
echo "[4/6] assemble + link stage-2 binary"
|
|
vendor/qbe/qbe -o /tmp/fp_stage2.s /tmp/fp_stage2.ssa 2>/tmp/fp_qbe.err || {
|
|
echo "QBE_FAIL"; cat /tmp/fp_qbe.err; exit 2;
|
|
}
|
|
gcc -o /tmp/fp_blaise2 /tmp/fp_stage2.s compiler/target/blaise_rtl.a 2>/tmp/fp_gcc.err || {
|
|
echo "GCC_FAIL"; cat /tmp/fp_gcc.err; exit 3;
|
|
}
|
|
|
|
echo "[5/6] stage2 -> stage3 IR (5min timeout)"
|
|
timeout 300 /tmp/fp_blaise2 --source compiler/src/main/pascal/Blaise.pas \
|
|
--unit-path compiler/src/main/pascal --unit-path rtl/src/main/pascal \
|
|
--emit-ir > /tmp/fp_stage3.ssa 2>/tmp/fp_stage3.err
|
|
RC=$?
|
|
if [ $RC -eq 124 ]; then
|
|
echo "STAGE3_TIMEOUT"
|
|
exit 4
|
|
elif [ $RC -eq 139 ]; then
|
|
echo "STAGE3_SEGFAULT"
|
|
exit 4
|
|
elif [ $RC -ne 0 ]; then
|
|
echo "STAGE3_FAIL rc=$RC"
|
|
head -3 /tmp/fp_stage3.err
|
|
exit 5
|
|
fi
|
|
echo " stage3 IR: $(wc -l < /tmp/fp_stage3.ssa) lines"
|
|
|
|
echo "[6/6] compare"
|
|
DIFFLINES=$(diff /tmp/fp_stage2.ssa /tmp/fp_stage3.ssa | wc -l)
|
|
if [ $DIFFLINES -eq 0 ]; then
|
|
echo "FIXPOINT_OK"
|
|
exit 0
|
|
else
|
|
echo "FIXPOINT_DIFF lines=$DIFFLINES"
|
|
diff /tmp/fp_stage2.ssa /tmp/fp_stage3.ssa | head -20
|
|
exit 6
|
|
fi
|