blaise/scripts/fixpoint.sh

153 lines
5.4 KiB
Bash
Raw Permalink Normal View History

fix(codegen): eliminate dynamic stack allocs — phi for short-circuit bools, hoist exc frames 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.
2026-05-06 13:47:52 +03:00
#!/bin/bash
# Fixpoint test for the Blaise self-hosting check.
#
# Uses the most recent release binary as stage-1 (no FPC dependency).
#
fix(codegen): eliminate dynamic stack allocs — phi for short-circuit bools, hoist exc frames 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.
2026-05-06 13:47:52 +03:00
# Steps:
# 1. Find stage-1 binary (latest release or explicit $STAGE1).
fix(codegen): eliminate dynamic stack allocs — phi for short-circuit bools, hoist exc frames 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.
2026-05-06 13:47:52 +03:00
# 2. Rebuild + install RTL (cheap when nothing changed).
# 3. stage-1 -> stage-2 IR.
fix(codegen): eliminate dynamic stack allocs — phi for short-circuit bools, hoist exc frames 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.
2026-05-06 13:47:52 +03:00
# 4. Assemble + link stage-2 binary via QBE + gcc.
# 5. stage-2 -> stage-3 IR (5-minute timeout).
fix(codegen): eliminate dynamic stack allocs — phi for short-circuit bools, hoist exc frames 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.
2026-05-06 13:47:52 +03:00
# 6. diff stage-2.ssa stage-3.ssa => empty = clean fixpoint.
#
# If stage-2 != stage-3 (expected when stage-1 has older codegen),
# the script automatically builds stage-3, generates stage-4 IR,
# and checks stage-3 == stage-4 as the true fixpoint.
fix(codegen): eliminate dynamic stack allocs — phi for short-circuit bools, hoist exc frames 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.
2026-05-06 13:47:52 +03:00
set -e
if [ ! -f "compiler/src/main/pascal/Blaise.pas" ]; then
echo "Run this script from the project root: ./scripts/fixpoint.sh" >&2
exit 1
fi
# Stage-1 binary: honour $STAGE1, otherwise pick the latest release.
if [ -n "$STAGE1" ]; then
STAGE1_BIN="$STAGE1"
elif [ -d releases ]; then
LATEST=$(ls -d releases/v* 2>/dev/null | sort -V | tail -1)
STAGE1_BIN="$LATEST/blaise"
fi
if [ ! -x "$STAGE1_BIN" ]; then
echo "No stage-1 binary found. Set STAGE1=/path/to/blaise or add a release." >&2
fix(codegen): eliminate dynamic stack allocs — phi for short-circuit bools, hoist exc frames 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.
2026-05-06 13:47:52 +03:00
exit 10
fi
echo "stage-1: $STAGE1_BIN"
fix(codegen): eliminate dynamic stack allocs — phi for short-circuit bools, hoist exc frames 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.
2026-05-06 13:47:52 +03:00
2026-07-04 21:44:54 +03:00
# The runtime Makefile defaults BLAISE to ../compiler/target/blaise, which may
# not exist yet — e.g. straight after scripts/rolling-bootstrap.sh, which
# installs only to releases/ and leaves the live compiler/target/ empty.
# Drive the runtime build with the stage-1 binary explicitly so fixpoint never
# depends on a pre-existing compiler/target/blaise. make runs inside runtime/,
# so BLAISE must be an absolute path.
ABS_STAGE1=$(readlink -f "$STAGE1_BIN")
2026-07-04 21:44:54 +03:00
echo "[1/5] rebuild + install runtime (BLAISE=$ABS_STAGE1)"
( cd runtime && make clean > /tmp/fp_rtl.log 2>&1 \
&& make BLAISE="$ABS_STAGE1" >> /tmp/fp_rtl.log 2>&1 \
&& make BLAISE="$ABS_STAGE1" install >> /tmp/fp_rtl.log 2>&1 ) || {
if [ -f compiler/target/blaise_rtl.a ]; then
echo " stage-1 too old for runtime; using existing blaise_rtl.a"
else
echo "RUNTIME_FAIL"; tail -5 /tmp/fp_rtl.log; exit 11;
fi
fix(codegen): eliminate dynamic stack allocs — phi for short-circuit bools, hoist exc frames 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.
2026-05-06 13:47:52 +03:00
}
2026-07-04 21:44:54 +03:00
# Resolve the RTL archive for the link steps below. `make install` copies it
# to compiler/target/, but fall back to the runtime build dir so the link
# never fails on a tree where compiler/target/ was not populated.
if [ -f compiler/target/blaise_rtl.a ]; then
RTL_ARCHIVE=compiler/target/blaise_rtl.a
elif [ -f runtime/target/blaise_rtl.a ]; then
RTL_ARCHIVE=runtime/target/blaise_rtl.a
else
echo "RUNTIME_FAIL: blaise_rtl.a not found after runtime build"; exit 11
fi
echo "[2/5] stage-1 -> stage-2 IR"
"$STAGE1_BIN" --source compiler/src/main/pascal/Blaise.pas \
--unit-path compiler/src/main/pascal --unit-path runtime/src/main/pascal --unit-path stdlib/src/main/pascal \
fix(codegen): eliminate dynamic stack allocs — phi for short-circuit bools, hoist exc frames 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.
2026-05-06 13:47:52 +03:00
--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 " stage-2 IR: $(wc -l < /tmp/fp_stage2.ssa) lines"
fix(codegen): eliminate dynamic stack allocs — phi for short-circuit bools, hoist exc frames 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.
2026-05-06 13:47:52 +03:00
echo "[3/5] assemble + link stage-2 binary"
fix(codegen): eliminate dynamic stack allocs — phi for short-circuit bools, hoist exc frames 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.
2026-05-06 13:47:52 +03:00
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;
}
2026-07-04 21:44:54 +03:00
gcc -o /tmp/fp_blaise2 /tmp/fp_stage2.s "$RTL_ARCHIVE" 2>/tmp/fp_gcc.err || {
echo "GCC_FAIL"; cat /tmp/fp_gcc.err; exit 3;
fix(codegen): eliminate dynamic stack allocs — phi for short-circuit bools, hoist exc frames 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.
2026-05-06 13:47:52 +03:00
}
echo "[4/5] stage-2 -> stage-3 IR (5min timeout)"
fix(codegen): eliminate dynamic stack allocs — phi for short-circuit bools, hoist exc frames 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.
2026-05-06 13:47:52 +03:00
timeout 300 /tmp/fp_blaise2 --source compiler/src/main/pascal/Blaise.pas \
--unit-path compiler/src/main/pascal --unit-path runtime/src/main/pascal --unit-path stdlib/src/main/pascal \
fix(codegen): eliminate dynamic stack allocs — phi for short-circuit bools, hoist exc frames 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.
2026-05-06 13:47:52 +03:00
--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 " stage-3 IR: $(wc -l < /tmp/fp_stage3.ssa) lines"
fix(codegen): eliminate dynamic stack allocs — phi for short-circuit bools, hoist exc frames 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.
2026-05-06 13:47:52 +03:00
echo "[5/5] compare stage-2 vs stage-3"
fix(codegen): eliminate dynamic stack allocs — phi for short-circuit bools, hoist exc frames 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.
2026-05-06 13:47:52 +03:00
DIFFLINES=$(diff /tmp/fp_stage2.ssa /tmp/fp_stage3.ssa | wc -l)
if [ $DIFFLINES -eq 0 ]; then
echo "FIXPOINT_OK"
exit 0
fi
echo "stage-2 != stage-3 ($DIFFLINES diff lines) — bootstrap gap from older stage-1"
echo " extending to stage-4 for true fixpoint check..."
echo "[+1] assemble + link stage-3 binary"
vendor/qbe/qbe -o /tmp/fp_stage3.s /tmp/fp_stage3.ssa 2>/tmp/fp_qbe3.err || {
echo "QBE3_FAIL"; cat /tmp/fp_qbe3.err; exit 2;
}
2026-07-04 21:44:54 +03:00
gcc -o /tmp/fp_blaise3 /tmp/fp_stage3.s "$RTL_ARCHIVE" 2>/tmp/fp_gcc3.err || {
echo "GCC3_FAIL"; cat /tmp/fp_gcc3.err; exit 3;
}
echo "[+2] stage-3 -> stage-4 IR (5min timeout)"
timeout 300 /tmp/fp_blaise3 --source compiler/src/main/pascal/Blaise.pas \
--unit-path compiler/src/main/pascal --unit-path runtime/src/main/pascal --unit-path stdlib/src/main/pascal \
--emit-ir > /tmp/fp_stage4.ssa 2>/tmp/fp_stage4.err
RC=$?
if [ $RC -eq 124 ]; then
echo "STAGE4_TIMEOUT"
exit 4
elif [ $RC -eq 139 ]; then
echo "STAGE4_SEGFAULT"
exit 4
elif [ $RC -ne 0 ]; then
echo "STAGE4_FAIL rc=$RC"
head -3 /tmp/fp_stage4.err
exit 5
fi
echo " stage-4 IR: $(wc -l < /tmp/fp_stage4.ssa) lines"
echo "[+3] compare stage-3 vs stage-4"
DIFFLINES=$(diff /tmp/fp_stage3.ssa /tmp/fp_stage4.ssa | wc -l)
if [ $DIFFLINES -eq 0 ]; then
echo "FIXPOINT_OK (achieved at stage-3/stage-4)"
exit 0
fix(codegen): eliminate dynamic stack allocs — phi for short-circuit bools, hoist exc frames 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.
2026-05-06 13:47:52 +03:00
else
echo "FIXPOINT_DIFF lines=$DIFFLINES"
diff /tmp/fp_stage3.ssa /tmp/fp_stage4.ssa | head -20
fix(codegen): eliminate dynamic stack allocs — phi for short-circuit bools, hoist exc frames 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.
2026-05-06 13:47:52 +03:00
exit 6
fi