perf(core): promote per region, promote BEGIN loops, keep loops off the memory stack

Promotion was all-or-nothing per word, so one `.` or one host call put the
whole body -- hot loops included -- on the memory data stack, where a
loop-carried add costs 2.2 ns/iteration instead of 0.31. The stack simulator
now runs over each promotable stretch of a word; BEGIN/UNTIL, BEGIN/AGAIN and
BEGIN/WHILE/REPEAT join DO/LOOP as promotable when the construct is provably
stack-neutral; and the inliner no longer moves a loop-bearing callee into a
caller that can never be promoted.

Fixes a bug the BEGIN work uncovered, present since promotion was introduced
and shipped in 0.2.6: the loop fixup and the IF join copied locals one slot at
a time in index order, so a body that permutes the stack lost a value --
`: C 3 4 2 0 DO SWAP LOOP . . ;` printed `4 4` where gforth prints `4 3`.

Four of five benchmarks now beat sf64: Factorial 0.29x, Collatz 0.30x,
NestedLoops 0.27x, GCD 0.67x. Only Fibonacci is behind, at 1.24x. Also scale
GCD, Factorial and NestedLoops, which ran in 14-51 us where scatter and fixed
costs dominated -- that is what exposed GCD as a loss and pointed at BEGIN.
WS-014, WS-015, WS-016, WS-019.
This commit is contained in:
Oleksandr Kozachuk
2026-08-09 17:25:21 +02:00
parent fc34bd9b24
commit b8dcc021a2
6 changed files with 581 additions and 52 deletions
+12 -12
View File
@@ -746,37 +746,37 @@ fn perf_benchmarks() -> Vec<PerfBenchmark> {
verify: "25 FIB",
expected: 75025,
samples: 5,
max_ratio: 0.65,
max_ratio: 0.17,
},
PerfBenchmark {
name: "Factorial(12)x10K",
name: "Factorial(12)x100K",
define: ": FACT 1 SWAP 1+ 1 ?DO I * LOOP ; \
: FACT-BENCH 10000 0 DO 12 FACT DROP LOOP ;",
: FACT-BENCH 100000 0 DO 12 FACT DROP LOOP ;",
run_code: "FACT-BENCH",
verify: "12 FACT",
expected: 479001600,
samples: 5,
max_ratio: 0.75,
max_ratio: 0.12,
},
PerfBenchmark {
name: "GCD-bench(500)",
name: "GCD-bench(20K)",
define: ": GCD BEGIN DUP WHILE TUCK MOD REPEAT DROP ; \
: GCD-BENCH 0 DO 10000 I 1+ GCD DROP LOOP ;",
run_code: "500 GCD-BENCH",
run_code: "20000 GCD-BENCH",
verify: "48 36 GCD",
expected: 12,
samples: 5,
max_ratio: 0.70,
max_ratio: 0.45,
},
PerfBenchmark {
name: "NestedLoops(50)",
name: "NestedLoops(50)x1K",
define: ": NESTED 0 SWAP 0 DO I 0 ?DO I J + DROP LOOP LOOP ; \
: NESTED-BENCH 100 0 DO 50 NESTED DROP LOOP ;",
: NESTED-BENCH 1000 0 DO 50 NESTED DROP LOOP ;",
run_code: "NESTED-BENCH",
verify: "5 NESTED",
expected: 0,
samples: 3,
max_ratio: 0.20,
samples: 5,
max_ratio: 0.11,
},
PerfBenchmark {
name: "Collatz(2K)",
@@ -788,7 +788,7 @@ fn perf_benchmarks() -> Vec<PerfBenchmark> {
verify: "27 COLLATZ",
expected: 111,
samples: 3,
max_ratio: 0.45,
max_ratio: 0.08,
},
]
}