Files
five/tests/frb/run.sh
CharlesKWON dca7bb22e5 fix(gengo): count nested LOCALs into the function frame
Function-entry Frame() allocation counted only top-level LOCAL
declarations from fn.Body. Mid-function LOCALs hidden inside an
IF / FOR / WHILE / DO CASE / SWITCH / SEQUENCE block weren't
included, so the runtime allocated a frame too small to hold them.
Subsequent reads/writes via PopLocalFast / PushLocalFast / LocalAdd
to those slot indices then either silently scribbled past the frame
(read-back saw NIL) or panicked with "local variable index out of
range" once the index exceeded the underlying slice.

This is the underlying bug behind frb_demo Section 4 — the
`LOCAL ch := Channel(1)` declared inside `IF pAsync != NIL` got
slot N+1 from the codegen but the runtime only allocated N. The
Channel value was scribbled past the frame, ChReceive then read
NIL from a non-existent slot, and the goroutine's ChSend(49) had
nowhere to land.

New helper gen_util.go::countLocalsInStmts walks every nested body
(IF + ElseIfs + ElseBody, ForStmt, ForEachStmt, DoWhileStmt,
SeqStmt's Body + RecoverBody, SwitchStmt's Cases + Otherwise) and
totals every ScopeLocal VarDecl. The function-emit caller adds this
to the top-level count before sizing the Frame.

Test fixture (tests/frb/test_frb_goroutine.prg) reproduces the
demo Section 4 shape — `LOCAL ch := Channel(1)` inside IF, then
`Go("WORKER", ch, 7)`, then ChReceive(ch). Wired into the FRB
runner so it stands at 6/6.

Other gates green:
  go test ./...      : PASS
  FiveSql2 SQL:1999  : 43/43
  Harbour compat     : 56/56
  std.ch suite       : 15/15
  FRB suite          : 6/6

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 07:05:22 +09:00

74 lines
2.4 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# FRB regression runner. Each test exercises a different aspect of
# Five's runtime compilation / loading pipeline:
#
# frb_simple — fixture PRG built into a pcode FRB module.
# test_frb_simple — load `frb_simple.frb`, call its functions.
# test_frb_pcode_load — load mathlib (multi-function pcode FRB).
# test_frb_compile — FrbCompile / FrbExec — in-memory compile.
# test_frb_loop — FOR loop accumulators (`+=` and `:=`).
# test_frb_step — FOR ... STEP -1 / STEP 2 in pcode mode.
# test_frb_goroutine — Go("WORKER", ...) launches dynamic FRB
# function in a goroutine + Channel rendezvous.
#
# Builds frb_simple.frb (and mathlib_pc.frb if needed) into the
# scratch dir before running the loaders.
set -e
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
FIVE="$ROOT/five"
[ -x "$FIVE" ] || { echo "five not built — run 'go build -o five ./cmd/five'" >&2; exit 2; }
work="$(mktemp -d)"
trap 'rm -rf "$work"' EXIT
# Pre-build pcode FRB fixtures the loader tests refer to.
"$FIVE" frb "$ROOT/tests/frb/frb_simple.prg" -o /tmp/frb_simple.frb --pcode >/dev/null
"$FIVE" frb "$ROOT/examples/frb_mathlib.prg" -o /tmp/mathlib_pc.frb --pcode >/dev/null
# Test files in the order they should run. test_frb_compile
# exercises the in-process compiler, which has no external
# dependencies — runs from any directory.
TESTS=(
test_frb_simple
test_frb_pcode_load
test_frb_compile
test_frb_loop
test_frb_step
test_frb_goroutine
)
pass=0
fail=0
for name in "${TESTS[@]}"; do
src="$ROOT/tests/frb/${name}.prg"
bin="$work/${name}"
if ! "$FIVE" build "$src" -o "$bin" >/dev/null 2>"$work/${name}.err"; then
echo "FAIL build $name"
sed 's/^/ /' "$work/${name}.err"
fail=$((fail+1))
continue
fi
if ! out="$("$bin" 2>&1)"; then
echo "FAIL run $name"
echo "$out" | sed 's/^/ /'
fail=$((fail+1))
continue
fi
if echo "$out" | grep -qE 'FAIL|expect.*got|panic'; then
echo "FAIL assert $name"
echo "$out" | sed 's/^/ /'
fail=$((fail+1))
continue
fi
echo "PASS $name"
pass=$((pass+1))
done
echo
echo "================================================================"
echo " Results: $pass / $((pass+fail)) passed"
echo "================================================================"
[ $fail -eq 0 ]