ci: optional selfhost-loop workflow + determinism script
Add tools/selfhost_loop.sh with path-normalized C and stripped ELF checks (bootstrap ×2). Wire GitHub Actions (manual/weekly/main paths) outside default make test. Raise selfhost lexer maxTokens for large files.
This commit is contained in:
@@ -0,0 +1,67 @@
|
|||||||
|
# Optional / not on every PR: bootstrap selfhost determinism (make selfhost-loop).
|
||||||
|
# Triggers: manual, weekly, or push to main that touches compiler/stdlib/loop.
|
||||||
|
# Fixed-point buxc2→buxc3 is opt-in via BUX_SELFHOST_FIXED_POINT=1 (experimental).
|
||||||
|
name: selfhost-loop
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
fixed_point:
|
||||||
|
description: "Also run experimental buxc2→buxc3 fixed-point"
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
schedule:
|
||||||
|
# Sundays 06:00 UTC
|
||||||
|
- cron: "0 6 * * 0"
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
paths:
|
||||||
|
- "src/**"
|
||||||
|
- "lib/**"
|
||||||
|
- "rt/**"
|
||||||
|
- "bootstrap/**"
|
||||||
|
- "tools/selfhost_loop.sh"
|
||||||
|
- "Makefile"
|
||||||
|
- ".github/workflows/selfhost-loop.yml"
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: selfhost-loop-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
selfhost-loop:
|
||||||
|
name: bootstrap determinism
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 45
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Nim
|
||||||
|
uses: jiro4989/setup-nim-action@v2
|
||||||
|
with:
|
||||||
|
nim-version: "2.0.x"
|
||||||
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Install build deps
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y --no-install-recommends gcc make binutils libssl-dev
|
||||||
|
|
||||||
|
- name: Selfhost loop
|
||||||
|
run: |
|
||||||
|
if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ "${{ inputs.fixed_point }}" = "true" ]; then
|
||||||
|
export BUX_SELFHOST_FIXED_POINT=1
|
||||||
|
echo "Fixed-point mode enabled (experimental)"
|
||||||
|
fi
|
||||||
|
make selfhost-loop
|
||||||
|
|
||||||
|
- name: Upload C on failure
|
||||||
|
if: failure()
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: selfhost-loop-c
|
||||||
|
path: |
|
||||||
|
build/selfhost-loop-a/build/main.c
|
||||||
|
build/selfhost-loop-b/build/main.c
|
||||||
|
if-no-files-found: ignore
|
||||||
@@ -142,40 +142,11 @@ fmt: build
|
|||||||
@./$(OUT) fmt apps/
|
@./$(OUT) fmt apps/
|
||||||
@echo "Formatted lib/ examples/ src/ tests/ apps/"
|
@echo "Formatted lib/ examples/ src/ tests/ apps/"
|
||||||
|
|
||||||
|
# Fixed-point: bootstrap buxc → buxc2 → buxc3 (path-normalized C + stripped ELF).
|
||||||
|
# Slow; not part of default `make test`. Optional CI: .github/workflows/selfhost-loop.yml
|
||||||
selfhost-loop: build
|
selfhost-loop: build
|
||||||
@echo "=== Selfhost loop: bootstrap determinism check ==="
|
@chmod +x tools/selfhost_loop.sh
|
||||||
@echo "Build A..."
|
@tools/selfhost_loop.sh
|
||||||
@rm -rf build/selfhost-loop-a
|
|
||||||
@mkdir -p build/selfhost-loop-a/src
|
|
||||||
@cp src/*.bux build/selfhost-loop-a/src/
|
|
||||||
@cp src/bux.toml build/selfhost-loop-a/
|
|
||||||
@mv build/selfhost-loop-a/src/main.bux build/selfhost-loop-a/src/Main.bux 2>/dev/null || true
|
|
||||||
@cd build/selfhost-loop-a && ../../$(OUT) build
|
|
||||||
@echo "Build B..."
|
|
||||||
@rm -rf build/selfhost-loop-b
|
|
||||||
@mkdir -p build/selfhost-loop-b/src
|
|
||||||
@cp src/*.bux build/selfhost-loop-b/src/
|
|
||||||
@cp src/bux.toml build/selfhost-loop-b/
|
|
||||||
@mv build/selfhost-loop-b/src/main.bux build/selfhost-loop-b/src/Main.bux 2>/dev/null || true
|
|
||||||
@cd build/selfhost-loop-b && ../../$(OUT) build
|
|
||||||
@echo ""
|
|
||||||
@echo "Comparing C output..."
|
|
||||||
@if diff build/selfhost-loop-a/build/main.c build/selfhost-loop-b/build/main.c > /dev/null 2>&1; then \
|
|
||||||
echo " C output: IDENTICAL ✓"; \
|
|
||||||
else \
|
|
||||||
echo " C output: DIFFERENT ✗"; \
|
|
||||||
fi
|
|
||||||
@echo "Comparing ELF binaries (stripped)..."
|
|
||||||
@cp build/selfhost-loop-a/build/buxc2 /tmp/buxc2_a && strip -d /tmp/buxc2_a
|
|
||||||
@cp build/selfhost-loop-b/build/buxc2 /tmp/buxc2_b && strip -d /tmp/buxc2_b
|
|
||||||
@if diff /tmp/buxc2_a /tmp/buxc2_b > /dev/null 2>&1; then \
|
|
||||||
echo " ELF binary: IDENTICAL ✓"; \
|
|
||||||
echo "=== Selfhost loop PASSED ==="; \
|
|
||||||
else \
|
|
||||||
echo " ELF binary: DIFFERENT ✗"; \
|
|
||||||
echo "=== Selfhost loop FAILED ==="; \
|
|
||||||
exit 1; \
|
|
||||||
fi
|
|
||||||
|
|
||||||
lsp: tools/bux-lsp
|
lsp: tools/bux-lsp
|
||||||
@echo "LSP server ready at tools/bux-lsp"
|
@echo "LSP server ready at tools/bux-lsp"
|
||||||
|
|||||||
@@ -349,8 +349,10 @@ make selfhost
|
|||||||
./buxc search greet
|
./buxc search greet
|
||||||
make test-registry # add greet → install → build temp app
|
make test-registry # add greet → install → build temp app
|
||||||
|
|
||||||
# Verify selfhost binary parity (buxc2 → buxc3, identical)
|
# Selfhost determinism (optional CI job; not in default `make test`)
|
||||||
make selfhost-loop
|
make selfhost-loop
|
||||||
|
# Experimental fixed-point (buxc2 → buxc3):
|
||||||
|
# BUX_SELFHOST_FIXED_POINT=1 make selfhost-loop
|
||||||
|
|
||||||
# Clean build artifacts
|
# Clean build artifacts
|
||||||
make clean
|
make clean
|
||||||
|
|||||||
@@ -189,6 +189,17 @@ summary table and exits:
|
|||||||
|
|
||||||
Use `Std::Test` module for assertions inside test code.
|
Use `Std::Test` module for assertions inside test code.
|
||||||
|
|
||||||
|
### Selfhost loop (optional CI)
|
||||||
|
```bash
|
||||||
|
make selfhost-loop # bootstrap builds src/ twice; C+ELF match
|
||||||
|
BUX_SELFHOST_FIXED_POINT=1 make selfhost-loop # also buxc2→buxc3 (experimental)
|
||||||
|
```
|
||||||
|
Not part of default `make test`. GitHub Actions workflow
|
||||||
|
`.github/workflows/selfhost-loop.yml` runs it on:
|
||||||
|
- manual `workflow_dispatch`
|
||||||
|
- weekly schedule
|
||||||
|
- pushes to `main` that touch `src/`, `lib/`, bootstrap, or the loop script
|
||||||
|
|
||||||
### Format (`bux fmt`)
|
### Format (`bux fmt`)
|
||||||
```bash
|
```bash
|
||||||
./buxc fmt examples/hello.bux # reformat one file
|
./buxc fmt examples/hello.bux # reformat one file
|
||||||
|
|||||||
+22
-5
@@ -1,7 +1,7 @@
|
|||||||
# Bux — План към „добър“ език (v0.5 → v1.0)
|
# Bux — План към „добър“ език (v0.5 → v1.0)
|
||||||
|
|
||||||
> **Дата:** 2026-07-19
|
> **Дата:** 2026-07-19
|
||||||
> **Текущо:** v0.5.x — HirNode sourceFile #line, selfhost CI, **LSP 0.12 path rename**, Nexus KA
|
> **Текущо:** v0.5.x — HirNode sourceFile, **optional selfhost-loop CI**, LSP 0.12, Nexus KA
|
||||||
> **Цел:** Език, с който се пишат реални проекти комфортно, безопасно (по избор) и с надежден toolchain.
|
> **Цел:** Език, с който се пишат реални проекти комфортно, безопасно (по избор) и с надежден toolchain.
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -754,9 +754,26 @@ A (stdlib ergonomics) → B (compiler holes) → C (ownership depth)
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Сесия 48 (optional CI: selfhost-loop)
|
||||||
|
|
||||||
|
1. **`tools/selfhost_loop.sh`**: bootstrap determinism (buxc × 2)
|
||||||
|
- path-normalized `#line` C compare (abs path / build-dir noise stripped)
|
||||||
|
- stripped ELF compare; **fails** on real C or ELF mismatch
|
||||||
|
2. **Lexer:** `maxTokens` 32k → 131k (hir_lower was at the ceiling for buxc2 parse)
|
||||||
|
3. **Optional fixed-point** `BUX_SELFHOST_FIXED_POINT=1`: buxc2 → buxc3
|
||||||
|
- experimental; selfhost still diverges on full compiler C emit
|
||||||
|
4. **GitHub Actions** `.github/workflows/selfhost-loop.yml`
|
||||||
|
- `workflow_dispatch` (+ optional fixed_point input)
|
||||||
|
- weekly cron (Sunday 06:00 UTC)
|
||||||
|
- push to `main` when `src/` / `lib/` / loop script change
|
||||||
|
- **not** on every PR / not in default `make test`
|
||||||
|
5. Verified: `make selfhost-loop` PASS (determinism)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Следващи стъпки
|
## Следващи стъпки
|
||||||
|
|
||||||
1. Optional: selfhost-loop as optional CI job (slow)
|
1. LSP find-implementations request (dedicated, beyond call hierarchy)
|
||||||
2. LSP find-implementations request (dedicated, beyond call hierarchy)
|
2. Workspace-wide import path index without open documents (optional polish)
|
||||||
3. Workspace-wide import path index without open documents (optional polish)
|
3. Expr/Stmt-level sourceFile if macros / cross-file inlining land
|
||||||
4. Expr/Stmt-level sourceFile if macros / cross-file inlining land
|
4. Fix selfhost C backend so buxc2→buxc3 fixed-point is green
|
||||||
|
|||||||
+2
-1
@@ -42,7 +42,8 @@ module Lexer {
|
|||||||
// Lexer state
|
// Lexer state
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
const maxTokens: int = 32768;
|
// hir_lower.bux alone is ~32k+ tokens; keep headroom for larger units / growth
|
||||||
|
const maxTokens: int = 131072;
|
||||||
const maxDiags: int = 128;
|
const maxDiags: int = 128;
|
||||||
|
|
||||||
struct LexerDiag {
|
struct LexerDiag {
|
||||||
|
|||||||
Executable
+147
@@ -0,0 +1,147 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Selfhost loop (CI-optional / not in default `make test`).
|
||||||
|
#
|
||||||
|
# Default mode — bootstrap determinism (fast, required):
|
||||||
|
# buxc builds src/ twice → path-normalized main.c + stripped ELF must match.
|
||||||
|
#
|
||||||
|
# Optional fixed-point (slow, experimental — set BUX_SELFHOST_FIXED_POINT=1):
|
||||||
|
# buxc → buxc2 → buxc3; compare gen1 vs gen2. Currently may fail until
|
||||||
|
# selfhost C backend matches bootstrap on full compiler sources.
|
||||||
|
#
|
||||||
|
# Usage: tools/selfhost_loop.sh
|
||||||
|
# make selfhost-loop
|
||||||
|
set -euo pipefail
|
||||||
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
cd "$ROOT"
|
||||||
|
|
||||||
|
OUT="${OUT:-buxc}"
|
||||||
|
export BUX_STDLIB="${BUX_STDLIB:-$ROOT/lib}"
|
||||||
|
unset BUX_DEBUG_FILE || true
|
||||||
|
unset BUX_NO_LINE || true
|
||||||
|
|
||||||
|
if [[ ! -x "$ROOT/$OUT" ]]; then
|
||||||
|
echo "=== building bootstrap ($OUT) ==="
|
||||||
|
make -C "$ROOT" build
|
||||||
|
fi
|
||||||
|
|
||||||
|
A="$ROOT/build/selfhost-loop-a"
|
||||||
|
B="$ROOT/build/selfhost-loop-b"
|
||||||
|
TMP=$(mktemp -d)
|
||||||
|
trap 'rm -rf "$TMP"' EXIT
|
||||||
|
|
||||||
|
prepare_tree() {
|
||||||
|
local dest="$1"
|
||||||
|
rm -rf "$dest"
|
||||||
|
mkdir -p "$dest/src"
|
||||||
|
cp src/*.bux "$dest/src/"
|
||||||
|
cp src/bux.toml "$dest/"
|
||||||
|
if [[ -f "$dest/src/main.bux" ]]; then
|
||||||
|
mv "$dest/src/main.bux" "$dest/src/Main.bux"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Normalize #line paths so different build dirs / abs roots don't fail the diff.
|
||||||
|
normalize_c() {
|
||||||
|
local src="$1" dst="$2"
|
||||||
|
sed -E \
|
||||||
|
-e 's|#line ([0-9]+) "[^"]*/(src/[^"]+\.bux)"|#line \1 "\2"|g' \
|
||||||
|
-e 's|#line ([0-9]+) "[^"]*/(lib/[^"]+\.bux)"|#line \1 "\2"|g' \
|
||||||
|
-e 's|#line ([0-9]+) "[^"]+/([^"/]+\.bux)"|#line \1 "\2"|g' \
|
||||||
|
"$src" > "$dst"
|
||||||
|
}
|
||||||
|
|
||||||
|
compare_c_and_elf() {
|
||||||
|
local label_a="$1" bin_a="$2" c_a="$3"
|
||||||
|
local label_b="$4" bin_b="$5" c_b="$6"
|
||||||
|
local ok=0
|
||||||
|
|
||||||
|
echo "--- Compare path-normalized C ($label_a vs $label_b) ---"
|
||||||
|
normalize_c "$c_a" "$TMP/main_a.c"
|
||||||
|
normalize_c "$c_b" "$TMP/main_b.c"
|
||||||
|
if diff -q "$TMP/main_a.c" "$TMP/main_b.c" >/dev/null 2>&1; then
|
||||||
|
echo " C output: IDENTICAL ✓ (paths normalized)"
|
||||||
|
else
|
||||||
|
echo " C output: DIFFERENT ✗"
|
||||||
|
diff -u "$TMP/main_a.c" "$TMP/main_b.c" | head -60 || true
|
||||||
|
ok=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "--- Compare stripped ELF ($label_a vs $label_b) ---"
|
||||||
|
cp "$bin_a" "$TMP/elf_a"
|
||||||
|
cp "$bin_b" "$TMP/elf_b"
|
||||||
|
strip -d "$TMP/elf_a" 2>/dev/null || strip "$TMP/elf_a"
|
||||||
|
strip -d "$TMP/elf_b" 2>/dev/null || strip "$TMP/elf_b"
|
||||||
|
if diff -q "$TMP/elf_a" "$TMP/elf_b" >/dev/null 2>&1; then
|
||||||
|
echo " ELF binary: IDENTICAL ✓"
|
||||||
|
else
|
||||||
|
echo " ELF binary: DIFFERENT ✗"
|
||||||
|
ls -la "$TMP/elf_a" "$TMP/elf_b"
|
||||||
|
ok=1
|
||||||
|
fi
|
||||||
|
return $ok
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Mode 1: bootstrap determinism (always)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
echo "=== Selfhost loop: bootstrap determinism (buxc × 2) ==="
|
||||||
|
echo "--- Build A (bootstrap) ---"
|
||||||
|
prepare_tree "$A"
|
||||||
|
(cd "$A" && "$ROOT/$OUT" build)
|
||||||
|
echo "--- Build B (bootstrap) ---"
|
||||||
|
prepare_tree "$B"
|
||||||
|
(cd "$B" && "$ROOT/$OUT" build)
|
||||||
|
|
||||||
|
if ! compare_c_and_elf \
|
||||||
|
"A" "$A/build/buxc2" "$A/build/main.c" \
|
||||||
|
"B" "$B/build/buxc2" "$B/build/main.c"
|
||||||
|
then
|
||||||
|
echo "=== Selfhost loop FAILED (bootstrap determinism) ==="
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "=== Bootstrap determinism PASSED ==="
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Mode 2: fixed-point buxc2 → buxc3 (optional)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
if [[ "${BUX_SELFHOST_FIXED_POINT:-0}" != "1" ]]; then
|
||||||
|
echo ""
|
||||||
|
echo "Note: fixed-point buxc2→buxc3 skipped (set BUX_SELFHOST_FIXED_POINT=1 to run)."
|
||||||
|
echo "=== Selfhost loop PASSED ==="
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Fixed-point: buxc2 → buxc3 (experimental) ==="
|
||||||
|
BUXC2="$A/build/buxc2"
|
||||||
|
if [[ ! -x "$BUXC2" ]]; then
|
||||||
|
echo "error: buxc2 missing at $BUXC2" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Rebuild B with buxc2
|
||||||
|
prepare_tree "$B"
|
||||||
|
set +e
|
||||||
|
(cd "$B" && "$BUXC2" build)
|
||||||
|
fp_status=$?
|
||||||
|
set -e
|
||||||
|
if [[ $fp_status -ne 0 ]]; then
|
||||||
|
echo "=== Fixed-point FAILED (buxc2 could not build gen2) ==="
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
BUXC3="$B/build/buxc2"
|
||||||
|
if [[ ! -x "$BUXC3" ]]; then
|
||||||
|
echo "error: gen2 binary missing" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! compare_c_and_elf \
|
||||||
|
"buxc2" "$BUXC2" "$A/build/main.c" \
|
||||||
|
"buxc3" "$BUXC3" "$B/build/main.c"
|
||||||
|
then
|
||||||
|
echo "=== Fixed-point FAILED (gen1 vs gen2 mismatch) ==="
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "=== Selfhost loop PASSED (determinism + fixed-point) ==="
|
||||||
Reference in New Issue
Block a user