From 7a98233b18affc359b06fecee2d53a09a8bc574e Mon Sep 17 00:00:00 2001 From: Tenuka22 Date: Wed, 21 Oct 2026 00:16:59 +0530 Subject: [PATCH] bench+web: mirror the test-code trees, measure on WSL, and unbreak the docs The Lithon test-code tree was reorganised into category folders (Classes, DataTypes, Functions, Loops, NewTypes, Pointers, Structs), but the Python side was only half-migrated and the website still pointed at the old flat paths. This commit finishes the migration, makes the benchmark measure where the measurement is honest, and brings the docs back in line with the engine. test_codes_python now mirrors test_codes_lithon folder for folder: every .lio program has a .py twin in the same category. The twins keep the shape of the program where CPython has no equivalent (raw pointers, packed structs, width annotations) and say so in their docstring. The three programs that are deliberately pinned failures on the Lithon side (check_strings, pointer_error, class_basics) are left alone -- they are the record of what is missing, not bugs. bench_vid_maker: * Resolve the engine for the platform it will actually run on. A Windows .venv/Scripts/lithon.exe used to be picked up from inside WSL and then failed with a baffling "native runner not found at C:\...". A binary that cannot run here is now skipped, and the repo's cli.py is the last resort so a cloned-but-not-installed repo still works. * Re-execute the whole run inside WSL from Windows (--wsl / --no-wsl). The native tier is an x86-64 binary, CPython's loop speed differs under WSL, and CPU affinity only works on the Linux side -- so the only honest place to measure is a Linux kernel with both engines native. * Fix a truncated-frame bug in the recorder: a ~35 MB frame written through a BufferedWriter could reach ffmpeg short, and ffmpeg then aborted the encode with "Invalid buffer size". The pipe is now unbuffered, every write is checked for a short count, and ffmpeg's stderr is surfaced instead of a bare exit code. * Four new configs (fib_30, find_e, fsum_1b_simd, multi_ops_1b) and a README that explains the WSL flow and the new tree layout. web: * verify:docs wrote its examples as .py, which the frontend now refuses (LITHON-E0114: Lithon source files use the .lio extension), and 24 sourceRef/expectedRef paths still named .py files that are now .lio. Both fixed: 29 pages, 97 examples, 62 repo refs now verify against the real toolchain. * Regenerate the IR map (31 -> 48 blobs) and teach the JS IR interpreter the IR the engine emits today: print_bare/print_nl instead of print, band/bor/bxor instead of and/or/xor, fieldstore/fieldload, the guardnonzero trap, and tryenter/tryleave/trycatch. 44/44 documented outputs now match. * Document what the engine actually does: try/except over the built-in traps (the catchable set, the three refused shapes, and the tier-3 mapping), enumerate/zip, the string and list methods, and the full error-code table instead of three ranges. * Fix the broken bits: /contributors 404'd from the search palette and the sitemap; the sitemap and robots.txt disagreed with the app about the canonical origin; the PWA manifest was Create-TanStack-App boilerplate naming two icons that do not exist; static assets were fetched from the site root, which 404s under the GitHub Pages sub-path; the two Discord invite links disagreed. * Add the missing cross-links between doc pages. uv.lock / lithon.egg-info are regenerated build metadata. --- bench_vid_maker/README.md | 428 ++++++---- bench_vid_maker/configs/euler_lim.toml | 4 +- bench_vid_maker/configs/fib_30.toml | 10 + bench_vid_maker/configs/find_e.toml | 10 + bench_vid_maker/configs/fsum_1b.toml | 4 +- bench_vid_maker/configs/fsum_1b_1core.toml | 4 +- bench_vid_maker/configs/fsum_1b_4core.toml | 4 +- bench_vid_maker/configs/fsum_1b_6core.toml | 4 +- bench_vid_maker/configs/fsum_1b_8core.toml | 4 +- bench_vid_maker/configs/fsum_1b_simd.toml | 10 + bench_vid_maker/configs/fsum_1k.toml | 4 +- bench_vid_maker/configs/multi_ops_1b.toml | 10 + bench_vid_maker/src/bench_vid_maker/cli.py | 37 + bench_vid_maker/src/bench_vid_maker/config.py | 89 ++- .../src/bench_vid_maker/recorder.py | 49 +- bench_vid_maker/src/bench_vid_maker/wsl.py | 161 ++++ lithon.egg-info/PKG-INFO | 751 +++++++++++++++++- test_codes_lithon/DataTypes/check_strings.lio | 4 +- test_codes_lithon/Pointers/pointer_error.lio | 6 +- test_codes_python/Classes/class_basics.py | 22 + test_codes_python/{ => DataTypes}/base_var.py | 4 +- test_codes_python/DataTypes/check_strings.py | 17 + test_codes_python/DataTypes/convertions.py | 15 + test_codes_python/DataTypes/floats.py | 9 + test_codes_python/DataTypes/strings.py | 12 + test_codes_python/Functions/fibb.py | 16 + test_codes_python/Functions/max_prog.py | 27 + test_codes_python/{ => Loops}/find_e.py | 4 +- test_codes_python/Loops/simple_sum.py | 21 + test_codes_python/{ => Loops}/test_1b_fsum.py | 8 +- .../{ => Loops}/test_1b_fsum_simd.py | 4 +- test_codes_python/{ => Loops}/test_1b_loop.py | 4 +- test_codes_python/Loops/test_1b_loop_print.py | 10 + test_codes_python/{ => Loops}/test_1k_fsum.py | 4 +- .../test_eulars_constant_lim_def.py | 4 +- test_codes_python/{ => Loops}/test_float.py | 4 +- .../{ => Loops}/test_multi_ops.py | 4 +- test_codes_python/NewTypes/new_type.py | 19 + test_codes_python/Pointers/check_pointers.py | 26 + test_codes_python/Pointers/pointer_error.py | 16 + test_codes_python/Structs/structs.py | 22 + .../__pycache__/base_var.cpython-312.pyc | Bin 515 -> 0 bytes .../__pycache__/find_e.cpython-312.pyc | Bin 763 -> 0 bytes .../__pycache__/test_1b_fsum.cpython-312.pyc | Bin 634 -> 0 bytes .../test_1b_fsum_simd.cpython-312.pyc | Bin 540 -> 0 bytes .../__pycache__/test_1b_loop.cpython-312.pyc | Bin 448 -> 0 bytes .../test_1b_loop_print.cpython-312.pyc | Bin 569 -> 0 bytes .../__pycache__/test_1k_fsum.cpython-312.pyc | Bin 519 -> 0 bytes ...st_eulars_constant_lim_def.cpython-312.pyc | Bin 814 -> 0 bytes .../__pycache__/test_float.cpython-312.pyc | Bin 673 -> 0 bytes .../test_multi_ops.cpython-312.pyc | Bin 587 -> 0 bytes test_codes_python/test_1b_loop_print.py | 10 - uv.lock | 2 +- web/apps/web/public/benchmarks/manifest.json | 56 +- web/apps/web/public/manifest.json | 29 +- web/apps/web/public/robots.txt | 2 +- web/apps/web/public/sitemap.xml | 94 ++- web/apps/web/src/docs/generated/ir.ts | 110 ++- web/apps/web/src/docs/pages/engine.ts | 54 +- web/apps/web/src/docs/pages/language.ts | 319 ++++++-- web/apps/web/src/docs/pages/machine.ts | 63 +- web/apps/web/src/docs/pages/reference.ts | 215 ++++- web/apps/web/src/docs/pages/start.ts | 44 +- web/apps/web/src/docs/registry.ts | 3 +- web/apps/web/src/lib/assets.ts | 27 + web/apps/web/src/lib/ir.ts | 238 +++++- web/apps/web/src/lib/seo.ts | 21 +- web/apps/web/src/routes/__root.tsx | 17 +- web/apps/web/src/routes/benchmarks.tsx | 5 +- web/apps/web/src/routes/index.tsx | 4 +- web/apps/web/src/routes/playground.tsx | 2 +- web/apps/web/src/search/actions.ts | 2 +- web/apps/web/src/search/provider.tsx | 17 +- web/apps/web/vite-lithon-api.ts | 12 +- web/scripts/gen-docs-ir.ts | 23 +- web/scripts/gen-seo.ts | 14 +- web/scripts/stage-benchmarks.ts | 12 +- web/scripts/verify-docs.ts | 2 +- 78 files changed, 2709 insertions(+), 553 deletions(-) create mode 100644 bench_vid_maker/configs/fib_30.toml create mode 100644 bench_vid_maker/configs/find_e.toml create mode 100644 bench_vid_maker/configs/fsum_1b_simd.toml create mode 100644 bench_vid_maker/configs/multi_ops_1b.toml create mode 100644 bench_vid_maker/src/bench_vid_maker/wsl.py create mode 100644 test_codes_python/Classes/class_basics.py rename test_codes_python/{ => DataTypes}/base_var.py (74%) create mode 100644 test_codes_python/DataTypes/check_strings.py create mode 100644 test_codes_python/DataTypes/convertions.py create mode 100644 test_codes_python/DataTypes/floats.py create mode 100644 test_codes_python/DataTypes/strings.py create mode 100644 test_codes_python/Functions/fibb.py create mode 100644 test_codes_python/Functions/max_prog.py rename test_codes_python/{ => Loops}/find_e.py (83%) create mode 100644 test_codes_python/Loops/simple_sum.py rename test_codes_python/{ => Loops}/test_1b_fsum.py (51%) rename test_codes_python/{ => Loops}/test_1b_fsum_simd.py (69%) rename test_codes_python/{ => Loops}/test_1b_loop.py (63%) create mode 100644 test_codes_python/Loops/test_1b_loop_print.py rename test_codes_python/{ => Loops}/test_1k_fsum.py (70%) rename test_codes_python/{ => Loops}/test_eulars_constant_lim_def.py (86%) rename test_codes_python/{ => Loops}/test_float.py (76%) rename test_codes_python/{ => Loops}/test_multi_ops.py (72%) create mode 100644 test_codes_python/NewTypes/new_type.py create mode 100644 test_codes_python/Pointers/check_pointers.py create mode 100644 test_codes_python/Pointers/pointer_error.py create mode 100644 test_codes_python/Structs/structs.py delete mode 100644 test_codes_python/__pycache__/base_var.cpython-312.pyc delete mode 100644 test_codes_python/__pycache__/find_e.cpython-312.pyc delete mode 100644 test_codes_python/__pycache__/test_1b_fsum.cpython-312.pyc delete mode 100644 test_codes_python/__pycache__/test_1b_fsum_simd.cpython-312.pyc delete mode 100644 test_codes_python/__pycache__/test_1b_loop.cpython-312.pyc delete mode 100644 test_codes_python/__pycache__/test_1b_loop_print.cpython-312.pyc delete mode 100644 test_codes_python/__pycache__/test_1k_fsum.cpython-312.pyc delete mode 100644 test_codes_python/__pycache__/test_eulars_constant_lim_def.cpython-312.pyc delete mode 100644 test_codes_python/__pycache__/test_float.cpython-312.pyc delete mode 100644 test_codes_python/__pycache__/test_multi_ops.cpython-312.pyc delete mode 100644 test_codes_python/test_1b_loop_print.py create mode 100644 web/apps/web/src/lib/assets.ts diff --git a/bench_vid_maker/README.md b/bench_vid_maker/README.md index 093f082..3dd5337 100644 --- a/bench_vid_maker/README.md +++ b/bench_vid_maker/README.md @@ -1,156 +1,310 @@ # bench_vid_maker -A tiny, reusable uv project that turns a benchmark comparison (CPython vs -Lithon, or anything else) into an actual video file with a clean, honestly- -timed live UI. +A tiny, reusable Python project (deps: `rich`, `pillow`, `psutil`; Python >= 3.11) +that turns a benchmark comparison (CPython vs Lithon, or anything else) into an +actual video file with a clean, honestly-timed live UI. Three acts, always in this order: -1. **Typing** -- every task's source types itself onto screen, side by - side, at a fixed pace. Nothing is running yet; this is a pure intro and - is never counted toward any time shown. -2. **Starting** -- one single, large modal flashes centered over the - (now fully typed) panels, and closes the instant the processes launch. -3. **Running** -- every task's process is launched at the *exact same - instant*, under a big clock that starts clean at `00:00.00` right then. - This is the only part that's actually timed. Each panel shows a live - spinner, elapsed wall time, and CPU usage (instantaneous %, cumulative - user/system seconds, sampled straight from the OS via `psutil`) until - the process exits, then a done/failed badge and a tinted background. - -Every panel is sized to fill the whole frame -- width and height -- so -nothing looks cramped in a sea of black. If the running phase takes longer -than ~10s of real time, the *recorded video* of that phase (never the live -terminal, never the numbers shown) is time-lapsed down to roughly that -length, and the applied speed-up (e.g. "173x") is reported on screen. - -A closing, full-height summary breaks down wall-clock vs. CPU user/system -time per task (table), how that time was actually spent (a Chrome- -DevTools-Network-panel-style resource waterfall), and the environment the -run actually used (OS, CPU, cores, RAM, Python version, every resolved -command) -- and is held on screen for a while. - -Every number is real: `time.perf_counter()` around each subprocess (spawn -to exit) for wall time, `psutil` process accounting for CPU time, six -decimal places shown throughout so nothing is rounded away. The typing -intro never affects the clock -- it starts at zero exactly when the -processes launch. CPU% is computed as cumulative CPU time over elapsed -wall time (not psutil's own interval `cpu_percent()`, which is noisy/ -quantized on Windows over short polling windows). - -## Run the included comparison +1. **Typing** -- every task's source types itself onto screen, side by side, at + 40 chars/second. Pure intro; never counted toward any time shown. +2. **Starting** -- one large modal flashes centered over the (now fully typed) + panels for ~1s and closes the instant the processes launch. +3. **Running** -- every task's process is launched at the *exact same instant* + under a clock that starts clean at `00:00.000000`. This is the only timed + part: each panel shows a live spinner, elapsed wall time, and CPU usage + (cumulative user/system seconds via `psutil`) until exit, then a + done/failed badge and a tinted background. + +Every number is real: `time.perf_counter()` around each subprocess (spawn to +exit) for wall time, `psutil` process accounting for CPU time, six decimals +throughout; CPU% is cumulative CPU over elapsed wall time, not psutil's noisy, +Windows-quantized interval `cpu_percent()`. Tasks are polled every 50ms +(`TICK_SECONDS`) -- polling only drives UI smoothness, never the numbers. + +If the running phase exceeds ~10s of real time, the *recorded video* of that +phase (never the live terminal, never the numbers shown) is time-lapsed down +to roughly that length, and the applied speed-up (e.g. "~175x", rounded to a +clean step) is reported on screen. A closing full-height summary shows a +"Time allocation" table (wall, CPU user/sys, share, bar, vs-slowest), an +environment block (OS, CPU + cores, RAM, Python, resolved commands, video +settings), and a Chrome-DevTools-style resource waterfall splitting each bar +into CPU user / sys / idle-wait on a shared time axis. + +## Run the included comparison (WSL) + +`bench_vid_maker` measures **inside WSL Ubuntu** -- both engines execute as +native Linux processes on the same OS, and static ffmpeg is easy to get there. +The repo lives at `/mnt/c/Users/tenuk/Projects/Lithon`; after the one-time +setup in the appendix below, the tool lives in `~/.venvs/lithon-bench`: + +``` +~/.venvs/lithon-bench/bin/bench-vid-maker bench_vid_maker/configs/fsum_1k.toml # live UI +~/.venvs/lithon-bench/bin/bench-vid-maker bench_vid_maker/configs/fsum_1b.toml --video results/fsum_1b.mp4 +~/.venvs/lithon-bench/bin/bench-vid-maker-all # every config -> results/ +``` + +### Measuring from Windows + +You do not have to switch shells. On Windows the tool **re-executes itself +inside WSL** and lets WSL do the measuring, so both engines still run as +native Linux processes: ``` -cd bench_vid_maker -uv run bench-vid-maker configs/fsum_1b.toml # live in the terminal -uv run bench-vid-maker configs/fsum_1b.toml --video out.mp4 # live AND recorded -uv run bench-vid-maker configs/fsum_1b.toml --video-only --video out.mp4 # recorded, no terminal UI -uv run bench-vid-maker configs/fsum_1b_4core.toml --video out.mp4 # same, pinned to 4 cores +bench-vid-maker bench_vid_maker/configs/fsum_1b.toml --video results/fsum_1b.mp4 ``` -This types in `tasks/python_sum.py` (plain CPython) and -`test_codes_lithon/test_1b_fsum.py` (through the repo's built `lithon` -executable), then launches both **together**, on the same -1,000,000,000-iteration sum. There's also `configs/fsum_1k.toml`, a -1,000-iteration version that finishes in well under a second -- useful for -quickly checking the UI/video itself rather than waiting out a real -benchmark, plus `configs/fsum_1b_{1,4,6,8}core.toml`, the same comparison -with every task's CPU affinity pinned to 1/4/6/8 cores (`cores = N` in the -config, or `--cores N` on the command line to override it for any config). -`configs/euler_lim.toml` compares a heavier, nested-loop workload instead -(approximating `e` via its limit definition out to i=1,000). -Results are also saved as JSON under `results/` (use `--no-save` to skip -that). - -First run: `uv sync` to create the project's own `.venv` (deps: `rich`, -`pillow`, `psutil`). Requires a `cmake --build build` in the repo root -first so `lithon` exists, and `ffmpeg` on `PATH` for `--video`. - -### How the video is made +The re-exec happens automatically when the repo has no Windows-native +`tier_runner.exe` (which is the usual case: the engine is built on Linux). +Force or suppress it explicitly: + +| flag | effect | +|---|---| +| `--wsl` | always re-execute inside WSL | +| `--no-wsl` | never re-execute inside WSL | +| `BENCH_VID_MAKER_NO_WSL=1` | environment equivalent of `--no-wsl` | + +This matters for the numbers, not just for convenience: + +- Lithon's fast tier is an x86-64 **native** tier (`build/tier_runner`); a + Windows-to-WSL translation layer would measure the layer, not the engine. +- CPython's loop performance under WSL differs from its performance on a + real Linux host, so the comparison is only honest when *both* engines run + on the same Linux kernel. +- `--cores N` (CPU affinity) is only honoured on the Linux side. + +### The engine is resolved for the platform it will run on + +`{lithon}` is looked for in this order: + +1. the repo's own virtualenv, using the subdirectory for the *current* + platform (`.venv/bin/lithon` on Linux, `.venv/Scripts/lithon.exe` on + Windows), +2. a sibling of the running interpreter -- this is what makes + `~/.venvs/lithon-bench/bin/lithon` in WSL resolve correctly, +3. `lithon` on `PATH`, +4. the repo's `cli.py`, run through the current interpreter, so a repo that + was cloned but never `pip install`ed still works. + +A binary that exists but cannot run on this platform is skipped rather than +used: a Windows `lithon.exe` found from inside WSL is ignored, instead of +failing later with a confusing "native runner not found at C:\\...". + +### Included configs + +All in `bench_vid_maker/configs/`. Each one runs a `test_codes_python/**.py` +program against its `test_codes_lithon/**.lio` twin, launched together on +the same program. + +| config | what it measures | +|---|---| +| `fsum_1k.toml` | 1,000-iteration sum; finishes in well under a second, so it is for checking the UI/video itself, not for speedup numbers | +| `fsum_1b.toml` | the real 1,000,000,000-iteration sum comparison | +| `fsum_1b_{1,4,6,8}core.toml` | the same with every task pinned to 1/4/6/8 CPU cores (`cores = N`; `--cores N` overrides any config) | +| `fsum_1b_simd.toml` | 1e9 half-increments into a `float[64]` -- the case the auto-vectorizer targets | +| `multi_ops_1b.toml` | 1e9 iterations of mixed integer arithmetic (multiply, xor, shift) | +| `euler_lim.toml` | a nested-loop workload (approximating `e` via its limit definition out to i=1,000) | +| `find_e.toml` | 8,000 terms of `1/n!`, computed with an explicit factorial loop | +| `fib_30.toml` | recursive `fib(30)` -- call overhead rather than loop throughput | + +Windows-native works as a secondary option: it needs the repo's `.venv` to +contain the built engine (`.venv/Scripts/lithon.exe`), `ffmpeg` on `PATH`, and +the fonts below -- then `pip install -e bench_vid_maker` (or `uv`) and the +same commands. + +## How the video is made Each frame (the same `rich` renderable shown live in the terminal) is -rasterized to an RGB image with Pillow -- honoring every color, bold, and -panel border rich draws -- and streamed straight into `ffmpeg`'s stdin as -raw video; nothing but the final `.mp4` touches disk. During the typing/ -starting/summary acts, a frame is held (repeated) for exactly as many -ticks as real wall-clock time actually passed since the previous frame, so -video playback time matches the real run there regardless of how long -rasterizing/encoding itself took. - -The *running* act is different: its frames are buffered instead of -streamed live, so that if the slowest task takes a long time in real life -(minutes), the recorded clip of that segment can be **time-lapsed** down -to roughly `RUNNING_VIDEO_TARGET_SECONDS` (10s by default, see `ui.py`) -once both tasks are done and the real duration is known. Every number any -frame shows is still the real measurement -- only *playback speed* -changes -- and the applied multiplier (rounded to a clean step, e.g. -"~175x") is reported on the finished frame and in the summary. - -Text is rasterized with the **JetBrains Mono Nerd Font** (falls back to -Consolas, then DejaVu Sans Mono on Linux) so the UI can use small icon -glyphs (a terminal glyph on each panel, a microchip for CPU stats, a -trophy for the winner, etc.) alongside plain ASCII/box-drawing characters. -Only glyphs confirmed present in that font's cmap are used anywhere in the -UI -- `rich` itself will happily use glyphs the font doesn't have, which -silently render as tofu boxes in the video, so this was checked with -`fontTools` against the font's actual cmap rather than assumed. - -A short, procedurally-generated background track (a pulsing bass note -under a fast four-note arpeggio, synthesized on the fly by `ffmpeg`'s own -`lavfi` sources -- not a downloaded/licensed file, so there's no copyright -to clear) is muxed into the video by default; pass `--no-music` to skip -it. - -Useful flags: +rasterized to an RGB image with Pillow -- honoring every truecolor fg/bg, +bold, and dim -- on a cell grid derived from the font's metrics, and streamed +into `ffmpeg`'s stdin as raw `rgb24`; nothing but the final `.mp4` touches +disk. Dimensions are bumped to even numbers (libx264/yuv420p require it); +encoding is libx264/yuv420p with `+faststart`. + +During the typing/starting/summary acts, each frame is held for exactly as +many ticks as real wall-clock time passed, so playback matches the real run +regardless of rasterizing/encoding speed. The *running* act is buffered +instead, then time-lapsed (once the real duration is known) to roughly +`RUNNING_VIDEO_TARGET_SECONDS` (10s, see `ui.py`); the final running frame is +held ~2s, the summary ~8s. Every number any frame shows remains the real +measurement -- only *playback speed* changes. + +Text is rasterized with **JetBrains Mono Nerd Font**, looked for in this +order: `C:/Windows/Fonts/JetBrainsMonoNerdFontMono-*.ttf` (Windows), +`~/.local/share/fonts/JetBrainsMonoNerdFontMono-*.ttf` (WSL/Linux), Consolas +(`consola.ttf`), then DejaVu Sans Mono. Only icon glyphs confirmed present in +the Nerd Font's cmap are used in the UI. Stdio is reconfigured to UTF-8 first +so legacy Windows code pages can't crash a run mid-render. + +Background music is **on by default**: two `lavfi` inputs synthesized by +ffmpeg itself at mux time -- a four-note A3 minor-7th arpeggio (A, C, E, G) +at 128 BPM in 16th notes under a 55 Hz (A1) pulsing bass -- mixed, faded in, +limited, muxed as AAC 160k; `-shortest` stops it when the video (stdin) ends. +No copyright to clear; `--no-music` skips it. + +### Flags | flag | default | meaning | |---|---|---| -| `--video PATH` | β€” | also/only render an mp4 | -| `--video-only` | off | skip the live terminal UI, just record | +| `config` (positional) | β€” | TOML file describing the tasks to compare | +| `--save PATH` | `results/_.json` | write the results JSON to this path | +| `--no-save` | off | don't write any results JSON (saved by default) | +| `--video PATH` | β€” | also render an mp4 of the run to this path | +| `--video-only` | off | render the video without opening a live terminal UI | +| `--video-rows N` | 56 | video height in character cells (~2K-class at the default font size) | | `--fps N` | 20 | video frame rate | -| `--video-cols N` | 234 | video width, in character cells (~4K-class at the default font size) | -| `--video-rows N` | 56 | video height, in character cells | -| `--cores N` | β€” | pin every task's process to N CPU cores (overrides the config's `cores`) | +| `--video-cols N` | 234 | video width in character cells (~2K-class) | +| `--cores N` | the config's `cores`, else unrestricted | pin every task's process to N CPU cores | | `--no-music` | off | don't mux in the procedurally-generated background music | +| `--wsl` | auto | re-execute the whole run inside WSL (both engines measured natively on Linux) | +| `--no-wsl` | off | never re-execute inside WSL, even when WSL is available | + +With `--video-only` and no `--video` path, the mp4 goes to +`results/_.mp4`. Exit codes: `0` all tasks exited 0; +`1` at least one task failed; `2` bad/missing config, or video requested but +ffmpeg/fonts are missing. + +## Config reference + +Full TOML shape (`config.py`): + +```toml +title = "1,000,000,000-iteration sum: CPython vs Lithon" # defaults to the file's stem if omitted +subtitle = "optional, shown under the title" # optional +cores = 4 # optional: pin every task to N CPU cores + +[[tasks]] +name = "CPython" +command = ["{python}", "test_codes_python/Loops/test_1b_fsum.py"] + +[[tasks]] +name = "Lithon" +command = ["{lithon}", "test_codes_lithon/Loops/test_1b_fsum.lio"] +cwd = "." # optional, relative to repo root +``` + +Every task needs `name` and `command`; at least one `[[tasks]]` entry is +required or the run exits 2. All relative paths (command args and `cwd`) +resolve against the repo root -- found by walking up to the nearest `.git` -- +so configs stay short and portable regardless of where you invoke the tool. + +Placeholders expanded in every command token: + +- `{python}` -- the interpreter running bench-vid-maker (`sys.executable`). +- `{lithon}` -- the engine, looked for in order: (1) `/.venv/Scripts/lithon.exe`, + (2) `/.venv/bin/lithon`, (3) a sibling of the running interpreter + (`lithon.exe` on Windows, `lithon` elsewhere -- this is what makes + `~/.venvs/lithon-bench/bin/lithon` in WSL resolve correctly), (4) `lithon` + on `PATH`. Missing everywhere is an error (exit 2). Add more placeholders in + `src/bench_vid_maker/config.py::_expand` as new engines get compared. ## Add a new comparison -1. Drop the program(s) you want to compare anywhere in the repo (or under - `bench_vid_maker/tasks/`). -2. Write a config, e.g. `configs/my_comparison.toml`: - - ```toml - title = "My comparison" - subtitle = "optional subtitle" - - [[tasks]] - name = "CPython" - command = ["{python}", "bench_vid_maker/tasks/my_task.py"] - - [[tasks]] - name = "Lithon" - command = ["{lithon}", "test_codes_lithon/my_task.py"] - # cwd = "." # optional, relative to the repo root - ``` - - You aren't limited to two tasks -- add as many `[[tasks]]` entries as - you like; they all type in together and then launch together, panels - laid out side by side. - -3. `uv run bench-vid-maker configs/my_comparison.toml --video out.mp4` - -All command/`cwd` paths are resolved relative to the repo root (found by -walking up to the nearest `.git`), so configs stay short regardless of -where you invoke the tool from. `{python}` expands to the interpreter -running bench-vid-maker; `{lithon}` expands to the repo's built -`.venv/Scripts/lithon.exe` / `.venv/bin/lithon` (falling back to `PATH`). -Add more placeholders in `src/bench_vid_maker/config.py::_expand` as you -compare more engines. - -The typewriter animation reads whichever file is the task's last command -argument; if that isn't a real file (e.g. `python -c "..."`), the panel -just shows the raw command line instead and skips straight to the -running/elapsed indicator. - -For statistically rigorous (multi-run, median/min/max) numbers rather than -a single on-camera run, use `tools/bench.py` in the repo root instead. +Paired test programs live in matching category folders -- the Python tree +mirrors the Lithon tree exactly, so a program and its twin are always one +folder apart: + +``` +test_codes_python/{Classes,DataTypes,Functions,Loops,NewTypes,Pointers,Structs}/*.py +test_codes_lithon/{Classes,DataTypes,Functions,Loops,NewTypes,Pointers,Structs}/*.lio +``` + +Lithon is python-syntax, so the same program shape runs as-is in both engines; +the Python side only mirrors the portable subset. Where a construct has no +CPython equivalent -- raw pointers, packed structs, width annotations -- the +Python file keeps the *shape* of the program and says so in its docstring. + +Some programs are deliberately **pinned failures** on the Lithon side: they +exercise a gap that has not landed yet, and the engine is expected to refuse +them (see `test_codes_lithon/DataTypes/check_strings.lio`, +`test_codes_lithon/Pointers/pointer_error.lio` and +`test_codes_lithon/Classes/class_basics.lio`). Do not "fix" those files -- +they are the record of what is missing. + +Drop your program(s) into a matching category, then write a config, e.g. +`configs/my_comparison.toml`: + +```toml +title = "Nested loops: CPython vs Lithon" +subtitle = "optional subtitle" + +[[tasks]] +name = "CPython" +command = ["{python}", "test_codes_python/Loops/test_1b_loop.py"] + +[[tasks]] +name = "Lithon" +command = ["{lithon}", "test_codes_lithon/Loops/test_1b_loop.lio"] +# cwd = "." # optional, relative to the repo root +``` + +Any number of `[[tasks]]` entries works -- they all type in together, then +launch together, side by side. Then: + +``` +~/.venvs/lithon-bench/bin/bench-vid-maker bench_vid_maker/configs/my_comparison.toml --video results/my_comparison.mp4 +``` + +The typewriter animation reads the task's last command argument as source; if +that isn't a real file (e.g. `python -c "..."`), the panel shows the raw +command line instead. For statistically rigorous (multi-run, median/min/max) +numbers rather than a single on-camera run, use `tools/bench.py` in the repo +root instead. + +## bench-vid-maker-all + +`bench-vid-maker-all [CONFIGS_DIR] [--out-dir DIR] [-- EXTRA_ARGS]` builds a +video + results JSON for every `configs/*.toml` (default dir `configs/`, +default out dir `results/`), writing `.mp4` and `.json` per +config. It runs each config sequentially as `--video-only` on purpose -- +concurrent CPU-bound runs would make every number meaningless. Anything after +`--` is forwarded to every invocation, e.g. `bench-vid-maker-all -- --no-music +--fps 30`. Exit `0` when all built, `1` if any failed, `2` if no configs found. + +## results/ outputs + +`bench_vid_maker/results/` holds one `.mp4` and one `.json` per comparison. +The mp4s are committed; `results/*.json` are gitignored (see +`bench_vid_maker/.gitignore`). + +## Appendix: one-time WSL setup + +All user-space, no sudo. Venv (bootstrapped `--without-pip` via get-pip.py) +plus this project installed editable: + +```sh +python3 -m venv --without-pip ~/.venvs/lithon-bench +curl -sS https://bootstrap.pypa.io/get-pip.py | ~/.venvs/lithon-bench/bin/python3 +~/.venvs/lithon-bench/bin/pip install rich pillow psutil +~/.venvs/lithon-bench/bin/pip install -e /mnt/c/Users/tenuk/Projects/Lithon/bench_vid_maker +``` + +A tiny `lithon` wrapper next to the venv's python is all it takes for +`{lithon}` to resolve (it execs the repo's engine CLI): + +```sh +cat > ~/.venvs/lithon-bench/bin/lithon <<'EOF' +#!/bin/sh +exec python3 /mnt/c/Users/tenuk/Projects/Lithon/cli.py "$@" +EOF +chmod +x ~/.venvs/lithon-bench/bin/lithon +``` + +Static ffmpeg: grab a `linux-amd64` build (johnvansickle.com or +BtbN/FFmpeg-Builds), extract `ffmpeg` into `~/.local/bin`, and put +`~/.local/bin` on `PATH` in `~/.bashrc`: + +```sh +echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc +``` + +JetBrains Mono Nerd Font into `~/.local/share/fonts` (where the recorder looks +on Linux/WSL), then rebuild the font cache: + +```sh +mkdir -p ~/.local/share/fonts +curl -sSLO https://github.com/ryanoasis/nerd-fonts/releases/latest/download/JetBrainsMono.zip +unzip -o JetBrainsMono.zip 'JetBrainsMonoNerdFontMono*' -d ~/.local/share/fonts +rm JetBrainsMono.zip +fc-cache -f ~/.local/share/fonts +``` diff --git a/bench_vid_maker/configs/euler_lim.toml b/bench_vid_maker/configs/euler_lim.toml index 4949522..5407b89 100644 --- a/bench_vid_maker/configs/euler_lim.toml +++ b/bench_vid_maker/configs/euler_lim.toml @@ -3,8 +3,8 @@ subtitle = "nested loop to i=1,000 -- same program, run as-is in each engine" [[tasks]] name = "CPython" -command = ["{python}", "test_codes_python/test_eulars_constant_lim_def.py"] +command = ["{python}", "test_codes_python/Loops/test_eulars_constant_lim_def.py"] [[tasks]] name = "Lithon" -command = ["{lithon}", "test_codes_lithon/test_eulars_constant_lim_def.py"] +command = ["{lithon}", "test_codes_lithon/Loops/test_eulars_constant_lim_def.lio"] diff --git a/bench_vid_maker/configs/fib_30.toml b/bench_vid_maker/configs/fib_30.toml new file mode 100644 index 0000000..913458d --- /dev/null +++ b/bench_vid_maker/configs/fib_30.toml @@ -0,0 +1,10 @@ +title = "Fibonacci(30): CPython vs Lithon" +subtitle = "same recursive program, run as-is in each engine" + +[[tasks]] +name = "CPython" +command = ["{python}", "test_codes_python/Functions/fibb.py"] + +[[tasks]] +name = "Lithon" +command = ["{lithon}", "test_codes_lithon/Functions/fibb.lio"] diff --git a/bench_vid_maker/configs/find_e.toml b/bench_vid_maker/configs/find_e.toml new file mode 100644 index 0000000..647b310 --- /dev/null +++ b/bench_vid_maker/configs/find_e.toml @@ -0,0 +1,10 @@ +title = "Finding e by its series: CPython vs Lithon" +subtitle = "8,000 terms of 1/n!, computed with an explicit factorial loop" + +[[tasks]] +name = "CPython" +command = ["{python}", "test_codes_python/Loops/find_e.py"] + +[[tasks]] +name = "Lithon" +command = ["{lithon}", "test_codes_lithon/Loops/find_e.lio"] diff --git a/bench_vid_maker/configs/fsum_1b.toml b/bench_vid_maker/configs/fsum_1b.toml index ac7ee81..8b4007b 100644 --- a/bench_vid_maker/configs/fsum_1b.toml +++ b/bench_vid_maker/configs/fsum_1b.toml @@ -3,8 +3,8 @@ subtitle = "same program, run as-is in each engine" [[tasks]] name = "CPython" -command = ["{python}", "test_codes_python/test_1b_fsum.py"] +command = ["{python}", "test_codes_python/Loops/test_1b_fsum.py"] [[tasks]] name = "Lithon" -command = ["{lithon}", "test_codes_lithon/test_1b_fsum.py"] +command = ["{lithon}", "test_codes_lithon/Loops/test_1b_fsum.lio"] diff --git a/bench_vid_maker/configs/fsum_1b_1core.toml b/bench_vid_maker/configs/fsum_1b_1core.toml index 14945f9..39cebc0 100644 --- a/bench_vid_maker/configs/fsum_1b_1core.toml +++ b/bench_vid_maker/configs/fsum_1b_1core.toml @@ -4,8 +4,8 @@ cores = 1 [[tasks]] name = "CPython" -command = ["{python}", "test_codes_python/test_1b_fsum.py"] +command = ["{python}", "test_codes_python/Loops/test_1b_fsum.py"] [[tasks]] name = "Lithon" -command = ["{lithon}", "test_codes_lithon/test_1b_fsum.py"] +command = ["{lithon}", "test_codes_lithon/Loops/test_1b_fsum.lio"] diff --git a/bench_vid_maker/configs/fsum_1b_4core.toml b/bench_vid_maker/configs/fsum_1b_4core.toml index e384299..c8797d3 100644 --- a/bench_vid_maker/configs/fsum_1b_4core.toml +++ b/bench_vid_maker/configs/fsum_1b_4core.toml @@ -4,8 +4,8 @@ cores = 4 [[tasks]] name = "CPython" -command = ["{python}", "test_codes_python/test_1b_fsum.py"] +command = ["{python}", "test_codes_python/Loops/test_1b_fsum.py"] [[tasks]] name = "Lithon" -command = ["{lithon}", "test_codes_lithon/test_1b_fsum.py"] +command = ["{lithon}", "test_codes_lithon/Loops/test_1b_fsum.lio"] diff --git a/bench_vid_maker/configs/fsum_1b_6core.toml b/bench_vid_maker/configs/fsum_1b_6core.toml index f37d74b..20f375f 100644 --- a/bench_vid_maker/configs/fsum_1b_6core.toml +++ b/bench_vid_maker/configs/fsum_1b_6core.toml @@ -4,8 +4,8 @@ cores = 6 [[tasks]] name = "CPython" -command = ["{python}", "test_codes_python/test_1b_fsum.py"] +command = ["{python}", "test_codes_python/Loops/test_1b_fsum.py"] [[tasks]] name = "Lithon" -command = ["{lithon}", "test_codes_lithon/test_1b_fsum.py"] +command = ["{lithon}", "test_codes_lithon/Loops/test_1b_fsum.lio"] diff --git a/bench_vid_maker/configs/fsum_1b_8core.toml b/bench_vid_maker/configs/fsum_1b_8core.toml index 0686297..053e4c2 100644 --- a/bench_vid_maker/configs/fsum_1b_8core.toml +++ b/bench_vid_maker/configs/fsum_1b_8core.toml @@ -4,8 +4,8 @@ cores = 8 [[tasks]] name = "CPython" -command = ["{python}", "test_codes_python/test_1b_fsum.py"] +command = ["{python}", "test_codes_python/Loops/test_1b_fsum.py"] [[tasks]] name = "Lithon" -command = ["{lithon}", "test_codes_lithon/test_1b_fsum.py"] +command = ["{lithon}", "test_codes_lithon/Loops/test_1b_fsum.lio"] diff --git a/bench_vid_maker/configs/fsum_1b_simd.toml b/bench_vid_maker/configs/fsum_1b_simd.toml new file mode 100644 index 0000000..4cb32c3 --- /dev/null +++ b/bench_vid_maker/configs/fsum_1b_simd.toml @@ -0,0 +1,10 @@ +title = "Summing 1,000,000,000 half-increments: CPython vs Lithon" +subtitle = "float[64] accumulator -- the case Lithon's auto-vectorizer (AVX2) targets" + +[[tasks]] +name = "CPython" +command = ["{python}", "test_codes_python/Loops/test_1b_fsum_simd.py"] + +[[tasks]] +name = "Lithon" +command = ["{lithon}", "test_codes_lithon/Loops/test_1b_fsum_simd.lio"] diff --git a/bench_vid_maker/configs/fsum_1k.toml b/bench_vid_maker/configs/fsum_1k.toml index 858c26c..9e3ce5f 100644 --- a/bench_vid_maker/configs/fsum_1k.toml +++ b/bench_vid_maker/configs/fsum_1k.toml @@ -3,8 +3,8 @@ subtitle = "tiny workload -- for quickly checking the bench_vid_maker UI/video, [[tasks]] name = "CPython" -command = ["{python}", "test_codes_python/test_1k_fsum.py"] +command = ["{python}", "test_codes_python/Loops/test_1k_fsum.py"] [[tasks]] name = "Lithon" -command = ["{lithon}", "test_codes_lithon/test_1k_fsum.py"] +command = ["{lithon}", "test_codes_lithon/Loops/test_1k_fsum.lio"] diff --git a/bench_vid_maker/configs/multi_ops_1b.toml b/bench_vid_maker/configs/multi_ops_1b.toml new file mode 100644 index 0000000..c766851 --- /dev/null +++ b/bench_vid_maker/configs/multi_ops_1b.toml @@ -0,0 +1,10 @@ +title = "Mixed integer arithmetic, 1,000,000,000 iterations: CPython vs Lithon" +subtitle = "multiply, bitwise xor and right shift, accumulated -- pure integer ALU work" + +[[tasks]] +name = "CPython" +command = ["{python}", "test_codes_python/Loops/test_multi_ops.py"] + +[[tasks]] +name = "Lithon" +command = ["{lithon}", "test_codes_lithon/Loops/test_multi_ops.lio"] diff --git a/bench_vid_maker/src/bench_vid_maker/cli.py b/bench_vid_maker/src/bench_vid_maker/cli.py index 4966358..ad05b06 100644 --- a/bench_vid_maker/src/bench_vid_maker/cli.py +++ b/bench_vid_maker/src/bench_vid_maker/cli.py @@ -14,6 +14,7 @@ from .config import PROJECT_ROOT, load_config from .recorder import VideoRecorder from .ui import run_with_ui +from .wsl import have_wsl, reexec_in_wsl # Sized so the default rendered frame lands comfortably in 2K+ territory # with the (larger, more legible) default font_size -- see VideoRecorder's @@ -23,6 +24,26 @@ DEFAULT_FONT_SIZE = 34 +def _wsl_preferred() -> bool: + """Whether to prefer a WSL run over a Windows-native one by default. + + The Lithon engine's fast tier is an x86-64 native binary; on Windows + it needs `build/tier_runner.exe`, and the repository is frequently + built on Linux/WSL only. Rather than failing with a confusing + "native runner not found" error, the tool measures on WSL when it can. + Set `BENCH_VID_MAKER_NO_WSL=1` to opt out. + """ + if os.environ.get("BENCH_VID_MAKER_NO_WSL"): + return False + from .wsl import wsl_executable + + if wsl_executable() is None: + return False + # A Windows-native engine build is a perfectly good place to measure -- + # only prefer WSL when the repo has no Windows runner at all. + return not (PROJECT_ROOT / "build" / "tier_runner.exe").exists() + + def main(argv: list[str] | None = None) -> int: ap = argparse.ArgumentParser( prog="bench-vid-maker", @@ -57,8 +78,24 @@ def main(argv: list[str] | None = None) -> int: "--no-music", action="store_true", help="don't mux the procedurally-generated (non-copyright) background music into the video", ) + ap.add_argument( + "--wsl", action="store_true", + help="re-execute the whole run inside WSL (both engines measured natively on Linux)", + ) + ap.add_argument( + "--no-wsl", action="store_true", + help="never re-execute inside WSL, even when WSL is available", + ) args = ap.parse_args(argv) + # WSL re-exec: the honest place to measure is a Linux kernel with both + # engines running natively. The child re-enters this same entry point, + # so the guard below keeps it from re-execing twice. + if args.wsl and not args.no_wsl and have_wsl(): + return reexec_in_wsl() + if not args.no_wsl and not args.wsl and have_wsl() and _wsl_preferred(): + return reexec_in_wsl() + try: cfg = load_config(args.config) except (FileNotFoundError, ValueError) as e: diff --git a/bench_vid_maker/src/bench_vid_maker/config.py b/bench_vid_maker/src/bench_vid_maker/config.py index e63421f..e624ca1 100644 --- a/bench_vid_maker/src/bench_vid_maker/config.py +++ b/bench_vid_maker/src/bench_vid_maker/config.py @@ -8,11 +8,11 @@ [[tasks]] name = "CPython" - command = ["{python}", "bench_vid_maker/tasks/python_sum.py"] + command = ["{python}", "test_codes_python/Loops/test_1b_fsum.py"] [[tasks]] name = "Lithon" - command = ["{lithon}", "test_codes_lithon/test_1b_fsum.py"] + command = ["{lithon}", "test_codes_lithon/Loops/test_1b_fsum.lio"] cwd = "." # optional, relative to repo root All relative paths (command args and ``cwd``) are resolved against the repo @@ -24,6 +24,7 @@ from __future__ import annotations import dataclasses +import os import pathlib import shutil import sys @@ -44,18 +45,66 @@ def find_repo_root(start: pathlib.Path) -> pathlib.Path: REPO_ROOT = find_repo_root(PROJECT_ROOT) +def _is_runnable(path: pathlib.Path) -> bool: + """True when `path` exists *and* can actually be executed on this OS. + + A Windows `.venv/Scripts/lithon.exe` sitting in the repo is a real trap + when the tool runs inside WSL: the file exists, so a plain `exists()` + check picks it, and it then fails with a baffling "native runner not + found at C:\\...\\tier_runner.exe" -- a Windows path, resolved from a + Linux process. The runner is only usable if this platform can run it. + """ + if not path.exists(): + return False + if path.suffix.lower() == ".exe" and not sys.platform.startswith("win"): + return False # a Windows binary is useless on Linux/macOS + if not path.suffix and sys.platform.startswith("win"): + return False # a POSIX binary is useless on Windows + return os.access(path, os.X_OK) or path.suffix.lower() == ".exe" + + def _find_lithon() -> str: - for rel in (".venv/Scripts/lithon.exe", ".venv/bin/lithon"): + """Locate a `lithon` executable that can run *on this platform*. + + Order: + 1. the repo's own virtualenv (platform-correct subdirectory), + 2. a sibling of the running interpreter (this is what makes + `~/.venvs/lithon-bench/bin/lithon` in WSL resolve correctly), + 3. `lithon` on PATH, + 4. the repo's `cli.py`, run through the current interpreter -- the + engine's own source entry point, so a repo that has been cloned + but not `pip install`ed still works. + """ + venv_names = ( + (".venv/Scripts/lithon.exe", ".venv/bin/lithon") + if sys.platform.startswith("win") + else (".venv/bin/lithon", ".venv/Scripts/lithon.exe") + ) + for rel in venv_names: p = REPO_ROOT / rel - if p.exists(): + if _is_runnable(p): return str(p) + + # A venv running bench-vid-maker itself (e.g. ~/.venvs/lithon-bench in + # WSL) that has the repo installed: lithon sits next to our interpreter. + for name in ("lithon.exe", "lithon"): + sibling = pathlib.Path(sys.executable).parent / name + if _is_runnable(sibling): + return str(sibling) + found = shutil.which("lithon") - if found: + if found and _is_runnable(pathlib.Path(found)): return found + + # Last resort: run the engine straight from its source entry point. + cli = REPO_ROOT / "cli.py" + if cli.exists(): + return str(cli) + raise FileNotFoundError( - "could not find a built `lithon` executable (looked in " - f"{REPO_ROOT / '.venv/Scripts'} and {REPO_ROOT / '.venv/bin'}, and on PATH). " - "Build/install Lithon first." + "could not find a runnable `lithon` executable (looked in " + f"{REPO_ROOT / '.venv/bin'} and {REPO_ROOT / '.venv/Scripts'}, next to " + f"{sys.executable}, on PATH, and {cli}). Build/install Lithon first." ) @@ -83,6 +132,28 @@ def _expand(token: str) -> str: return token +def _expand_command(command: list[str]) -> list[str]: + """Expand the placeholders in a whole command. + + `{lithon}` may resolve to the engine's Python entry point (`cli.py`) + when no built executable is available -- in that case the command has + to be run *through* the interpreter, so the interpreter is prepended + and the marker is kept out of the final argv. + """ + expanded: list[str] = [] + for tok in command: + if tok == "{lithon}": + lithon = _find_lithon() + if lithon.endswith(".py"): + expanded.append(sys.executable) + expanded.append(lithon) + else: + expanded.append(lithon) + else: + expanded.append(_expand(tok)) + return expanded + + def load_config(path: pathlib.Path) -> BenchConfig: path = pathlib.Path(path).resolve() data = tomllib.loads(path.read_text(encoding="utf-8")) @@ -95,7 +166,7 @@ def load_config(path: pathlib.Path) -> BenchConfig: for i, t in enumerate(raw_tasks): if "name" not in t or "command" not in t: raise ValueError(f"{path}: tasks[{i}] is missing 'name' or 'command'") - command = [_expand(tok) for tok in t["command"]] + command = _expand_command(t["command"]) cwd = REPO_ROOT / t.get("cwd", ".") tasks.append(Task(name=t["name"], command=command, cwd=cwd.resolve())) diff --git a/bench_vid_maker/src/bench_vid_maker/recorder.py b/bench_vid_maker/src/bench_vid_maker/recorder.py index 3ad33a0..b303699 100644 --- a/bench_vid_maker/src/bench_vid_maker/recorder.py +++ b/bench_vid_maker/src/bench_vid_maker/recorder.py @@ -23,6 +23,10 @@ "C:/Windows/Fonts/JetBrainsMonoNerdFontMono-Regular.ttf", "C:/Windows/Fonts/JetBrainsMonoNerdFontMono-Bold.ttf", ), # Nerd Font first: the UI uses its icon glyphs (checked against its cmap, see ui.py) + ( + str(pathlib.Path.home() / ".local/share/fonts/JetBrainsMonoNerdFontMono-Regular.ttf"), + str(pathlib.Path.home() / ".local/share/fonts/JetBrainsMonoNerdFontMono-Bold.ttf"), + ), # WSL/Linux user-font install of the same Nerd Font ("C:/Windows/Fonts/consola.ttf", "C:/Windows/Fonts/consolab.ttf"), ("/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf", "/usr/share/fonts/truetype/dejavu/DejaVuSansMono-Bold.ttf"), ] @@ -143,7 +147,15 @@ def __init__( "-c:v", "libx264", "-pix_fmt", "yuv420p", "-movflags", "+faststart", str(self.out_path), ] - self._proc = subprocess.Popen(cmd, stdin=subprocess.PIPE) + # bufsize=0 hands us the raw pipe instead of a BufferedWriter. A + # single frame is ~35 MB at the default size, which is far larger + # than any pipe buffer: a buffered writer can hand a partial chunk + # to the OS and ffmpeg then reads a truncated frame and aborts with + # "Invalid buffer size, packet size N < expected frame_size M". + # Writing straight to the pipe keeps every frame's bytes contiguous. + self._proc = subprocess.Popen( + cmd, stdin=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=0 + ) self._frame_count = 0 def _rasterize(self, renderable: RenderableType) -> bytes: @@ -182,12 +194,41 @@ def capture(self, renderable: RenderableType, hold_frames: int = 1) -> None: stdin = self._proc.stdin assert stdin is not None for _ in range(hold_frames): - stdin.write(data) + self._write_all(stdin, data) self._frame_count += hold_frames + @staticmethod + def _write_all(stdin, data: bytes) -> None: + """Write every byte of `data` to the pipe. + + A raw pipe write is not guaranteed to transfer everything in one + call: it can return a short count when the pipe buffer fills up + while ffmpeg is busy. Writing the remainder is the only correct + behaviour -- a truncated frame makes ffmpeg abort the whole encode. + """ + view = memoryview(data) + while view: + written = stdin.write(view) + if written is None or written <= 0: + raise BrokenPipeError("ffmpeg closed the video pipe early") + view = view[written:] + def close(self) -> None: assert self._proc.stdin is not None - self._proc.stdin.close() + try: + self._proc.stdin.close() + except BrokenPipeError: + # ffmpeg already died; its exit code and stderr say why + pass self._proc.wait() if self._proc.returncode != 0: - raise RuntimeError(f"ffmpeg exited {self._proc.returncode} while writing {self.out_path}") + detail = "" + if self._proc.stderr is not None: + try: + detail = self._proc.stderr.read().decode("utf-8", "replace").strip() + except (OSError, ValueError): + detail = "" + raise RuntimeError( + f"ffmpeg exited {self._proc.returncode} while writing {self.out_path}" + + (f":\n{detail}" if detail else "") + ) diff --git a/bench_vid_maker/src/bench_vid_maker/wsl.py b/bench_vid_maker/src/bench_vid_maker/wsl.py new file mode 100644 index 0000000..12c9eae --- /dev/null +++ b/bench_vid_maker/src/bench_vid_maker/wsl.py @@ -0,0 +1,161 @@ +"""Re-executes `bench_vid_maker` inside WSL (Windows Subsystem for Linux). + +Why this exists +--------------- +`bench_vid_maker` measures CPython against Lithon, and both engines have to +run *natively* for the numbers to mean anything: + +* Lithon's fast tier is an x86-64 **native** tier (`build/tier_runner`); + running it through a Windows-to-WSL translation layer measures the + translation layer, not the engine. +* CPython's loop performance under WSL differs from its performance on a + real Linux host, so the comparison is only honest when *both* engines + run on the same Linux kernel, side by side. +* CPU affinity (`--cores N`) is only honoured on the Linux side, and the + WSL VM has its own CPU budget independent of the Windows host. + +So instead of measuring "Windows launching a Linux process", this module +re-executes the *whole tool* inside WSL and lets it do the measuring +there. Every number the tool reports is then produced by a Linux process, +and the Windows side only relays the exit code and the output. + +The repository lives on a Windows drive, which WSL mounts at +`/mnt//...`; the same files are visible on both sides, so no +copying is involved. +""" +from __future__ import annotations + +import os +import pathlib +import shutil +import subprocess +import sys + +#: Marker set in the child's environment so a re-exec never loops. +_REEXEC_MARKER = "BENCH_VID_MAKER_IN_WSL" + + +def wsl_executable() -> str | None: + """Path to `wsl.exe`, or None when this is not Windows / WSL is absent.""" + if not sys.platform.startswith("win"): + return None + return shutil.which("wsl.exe") or shutil.which("wsl") + + +def have_wsl() -> bool: + """True when this process could plausibly re-exec itself in WSL.""" + if wsl_executable() is None: + return False + if os.environ.get(_REEXEC_MARKER): + return False # already inside WSL + return True + + +def to_wsl_path(path: pathlib.Path | str) -> str: + """Map a Windows path to its WSL `/mnt//...` form. + + Paths that are already POSIX-style are returned unchanged, so the same + helper works for a config authored on either side. + """ + text = str(path) + p = pathlib.PureWindowsPath(text) + drive = p.drive + if len(drive) == 2 and drive[1] == ":": + rest = str(p)[2:].replace("\\", "/").lstrip("/") + return f"/mnt/{drive[0].lower()}/{rest}" if rest else f"/mnt/{drive[0].lower()}" + return text.replace("\\", "/") + + +def wsl_to_win_path(path: str) -> str: + """Map a WSL `/mnt//...` path back to a Windows path.""" + text = path.replace("\\", "/") + if text.startswith("/mnt/") and len(text) >= 7 and text[5] == "/": + drive = text[5].upper() + rest = text[7:] + return f"{drive}:\\{rest.replace('/', os.sep)}" if rest else f"{drive}:\\" + return text + + +def shell_join(command: list[str]) -> str: + """Join an argv into a single shell command line, quoting each token.""" + return " ".join(_shquote(tok) for tok in command) + + +def _shquote(token: str) -> str: + if token and all(c.isalnum() or c in "-_./=:" for c in token): + return token + return "'" + token.replace("'", "'\\''") + "'" + + +def reexec_in_wsl(argv: list[str] | None = None) -> int: + """Run the current tool inside WSL and return its exit code. + + The child's arguments are translated to their WSL equivalents (the + config path and any `--save`/`--video` path), the working directory is + carried across, and the environment marker keeps the child from + re-execing again. + """ + exe = wsl_executable() + if exe is None: + print( + "bench-vid-maker: wsl.exe not found; cannot run inside WSL.", + file=sys.stderr, + ) + return 2 + + args = list(sys.argv[1:] if argv is None else argv) + wsl_args = _translate_args(args) + env = dict(os.environ) + env[_REEXEC_MARKER] = "1" + + # The WSL side runs the *same* installed tool. `bench-vid-maker` is on + # PATH inside WSL when it was installed into a WSL venv; otherwise fall + # back to the module invocation next to this file. + command = _child_command(wsl_args) + + print( + f"bench-vid-maker: re-executing inside WSL -> {shell_join(command)}", + file=sys.stderr, + ) + try: + proc = subprocess.run([exe, "-e", "bash", "-lc", shell_join(command)], env=env, check=False) + except OSError as e: + print(f"bench-vid-maker: could not start WSL: {e}", file=sys.stderr) + return 2 + return proc.returncode + + +def _child_command(wsl_args: list[str]) -> list[str]: + """The command the WSL side runs.""" + exe = shutil.which("bench-vid-maker") + if exe: + return [exe, *wsl_args] + # Fall back to the package's own module entry point, using whichever + # interpreter is running this process. + return [sys.executable, "-m", "bench_vid_maker.cli", *wsl_args] + + +def _translate_args(args: list[str]) -> list[str]: + """Translate Windows-side arguments into their WSL equivalents. + + Only the flags that carry a filesystem path need translating; the rest + are passed through verbatim so the child sees exactly the same run. + """ + path_flags = {"--save", "--video"} + out: list[str] = [] + i = 0 + while i < len(args): + tok = args[i] + if tok in path_flags and i + 1 < len(args): + out.append(tok) + out.append(to_wsl_path(args[i + 1])) + i += 2 + continue + if "=" in tok and tok.split("=", 1)[0] in path_flags: + flag, _, value = tok.partition("=") + out.append(f"{flag}={to_wsl_path(value)}") + i += 1 + continue + out.append(tok) + i += 1 + return out diff --git a/lithon.egg-info/PKG-INFO b/lithon.egg-info/PKG-INFO index bf1b6f7..eaf1045 100644 --- a/lithon.egg-info/PKG-INFO +++ b/lithon.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: lithon -Version: 0.8.0 +Version: 0.9.8 Summary: Zero-dependency C++ execution engine and JIT compiler for Python Requires-Python: >=3.10 Description-Content-Type: text/markdown @@ -35,17 +35,17 @@ Dynamic: license-file ## πŸš€ What is Lithon? -Lithon is a **Python library and native execution engine** that runs `.py` +Lithon is a **Python library and native execution engine** that runs `.lio` source code using an extended **PEP-526-style static typing syntax**. Lithon is designed to keep Python's familiar source-code model while giving the compiler explicit type information that can be used for static verification, native machine-code generation, and predictable execution. -Lithon is **not a separate programming language**. Python remains the source -language and `.py` remains the source format. Lithon extends the typing -information available to the compiler so that supported Python code can be -lowered to native x86-64 machine code. +Lithon sources use the **`.lio`** extension. The syntax is deliberately +Python-shaped, but `.lio` is what marks a file as a Lithon program; the frontend +refuses any other extension, so a plain Python script is never silently compiled +as Lithon. For example: @@ -71,6 +71,61 @@ fuzzing. It is **not yet a stable production library** and should not be treated as a drop-in Python replacement. +### Recently added + +- **`try`/`except` (6.10)** β€” the Python EAFP rhyme works: a `try` body whose + `int(8, input("..."))` meets a typo lands in `except ValueError:`, and the + program continues. It catches the closed set of built-in Tier-3 traps + (`ZeroDivisionError`, `OverflowError`, `ValueError`, `IndexError`, + `KeyError`, `EOFError`, or `Exception` for every one of them) with `else` and + `finally`. There is no exception object and no unwinding: a caught trap is a + plain jump to the matching handler block, and the whole construct is ordinary + control flow plus three markers both tiers read. That is also its one + narrowing β€” only a trap raised in the **same function** as the `try` is + catchable, because routing across a call boundary would need a real unwind. + `except X as e`, `raise`, `except*` and a bare `except:` are refused. + `tests/typed_regression/try_except.lio` and `try_except_eafp.lio` are the + tier-identical guards. +- **`!=` (6.9)** β€” `ast.NotEq` is now a first-class compare, not an `==` + followed by a `Not`. It mirrors `==` in typing and differs only in the result: + a dedicated `Ne` IR op lets the optimizer, branch fusion and the print guard + see the real predicate. The float path is the one with a machine-level trap: + after `ucomisd`, `!=` is true when the operands differ **or** are unordered, so + the JIT emits `setne OR setp` (fused: `jp` and `jne` to the same target) while + `==` stays `sete AND setnp` β€” emitting only `setne` would make `nan != nan` + false. Integers compare exactly as `int64`, `str` compares as a run + (lengths-or-bytes, via `strne`), and `while i != n` loops are not strength- + reduced or vectorized because `i != n` has no guaranteed trip count. + `tests/typed_regression/ne.lio` is the byte-for-byte CPython guard. +- **Raw syscalls** β€” `syscall(number, a1..a6) -> int[64]`, one IR instruction + and one host trampoline that *both* tiers call, so the calling convention + cannot drift between them. Takes int[64] and pointers. +- **Networking (5.2)** β€” `socket`/`bind`/`connect`/`listen`/`accept`/`send`/ + `recv`/`close` as raw syscalls, with `&sockaddr_in` and send/recv buffers + passed as pointers into real frame bytes. A single-process loopback round trip + across a mid-flight `getsockname` is compared line-for-line against a + libc-free C oracle, and the byte-swap failure mode is asserted explicitly. +- **Multi-argument `print`** β€” `print(a, b, c)` prints space-separated with one + trailing newline, matching Python, and `print()` prints a bare newline. It is + lowered to runtime pieces (`print_bare` / `print_sep` / `print_nl`), never a + built string, so each tier formats each value exactly as before. `sep=` and + `end=` are deferred. +- **Iteration helpers** β€” `enumerate`, `zip`, `sum`, `min`, `max`, `abs`, `any`, + `all`, and `in`/`not in` over a list. All are rewrites over the compile-time + capacity `N`, so there is no new opcode and no runtime helper. Cross-checked + against CPython byte for byte. +- **Real pointers** β€” a `ptr[T]` is a genuine host address in the interpreter as + well as the JIT, backed by per-variable byte storage that a syscall could + read. `ptr[struct[N]]` forms, typechecks, compares, scales by `sizeof(Point)`, + and **is read and written through** (`valueof(_p).x`) in both tiers β€” the JIT + gained a general `[reg+disp]` encoder primitive, verified against GNU `as` + over 18,379 cases, so pointer-base field ops run natively instead of falling + back. `ptr_field.lio` and `ptr_alias.lio` are the regression guards. +- **Three type-system holes closed** β€” a bool literal stored into an `int` + (`v: int[64] = True`) used to type-check clean and print `True`; a capacity-1 + list could not compile natively at all; and a loop target could silently + change type between `for` headers. + --- ## ✨ Why Lithon? @@ -78,7 +133,7 @@ drop-in Python replacement. Lithon is built around several principles: * **Python source with extended PEP-526 typing** β€” Lithon works directly with - `.py` source while providing additional type information to the native + `.lio` source while providing additional type information to the native execution pipeline. * **Mandatory static typing** β€” types are verified before execution. * **Fixed-width values** β€” integer widths are explicit. @@ -167,6 +222,8 @@ The compiler also checks: * container element types * pointer types * struct field types +* enum variant and match rules +* type-function arity and expansion rules * shift bounds * container capacities @@ -323,7 +380,7 @@ Lithon currently spans four implementation layers: ```mermaid graph TD - A[Python .py Source] --> B[Frontend] + A[Lithon .lio Source] --> B[Frontend] B --> C[Typed IR] C --> D[Static Flow Verification] @@ -377,7 +434,7 @@ have to block the entire execution pipeline. Use: ```bash -lithon program.py --strict +lithon program.lio --strict ``` `--strict` means: @@ -406,16 +463,16 @@ pip install -e . Then: ```bash -lithon program.py +lithon program.lio ``` Useful commands: ```bash -lithon program.py -lithon program.py --strict -lithon program.py --ir -lithon program.py -v +lithon program.lio +lithon program.lio --strict +lithon program.lio --ir +lithon program.lio -v ``` The current development package layout is still being consolidated before the @@ -449,25 +506,25 @@ cmake --build build -j$(nproc) ```bash pip install -e . -lithon tests/programs/float.py +lithon tests/programs/float.lio ``` Native-only: ```bash -lithon tests/programs/float.py --strict +lithon tests/programs/float.lio --strict ``` Print IR: ```bash -lithon tests/programs/float.py --ir +lithon tests/programs/float.lio --ir ``` Verbose execution information: ```bash -lithon tests/programs/float.py -v +lithon tests/programs/float.lio -v ``` --- @@ -486,19 +543,24 @@ The verification suite checks several independent layers. | Area | Current result | | :----------------------------------- | :---------------------------: | -| CTest | **32 / 32 passed** | -| x86-64 encoder comparisons | **9,239 cases** | +| CTest | **33 / 33 passed** | +| x86-64 encoder comparisons | **18,379 cases** | | Incorrect encoder cases | **0** | | Typed differential fuzzing | **300 / 300 passed** | | Bitwise / shift fuzzing | **200 / 200 passed** | | Conditional-expression / SSA fuzzing | **200 / 200 per suite** | | Direct float Phi sweep | **38 matched / 0 mismatched** | -| ABI modules audited | **23** | +| ABI modules audited | **34** | | ABI stack/callee-saved failures | **0** | -| Typed regression suite | **15 / 15 passed** | -| General regression suite | **23 / 23 passed** | +| Typed regression suite | **28 / 28 passed** | +| General regression suite | **34 / 34 passed** | +| Type-checker unit checks | **145 ok** | +| Error-code discipline checks | **86 ok** | +| Tier differential (interp vs JIT) | **168 / 168 passed** | +| Syscall hook parity (both tiers) | **4 / 4 passed** | +| Networking loopback vs a C oracle | **verified** | | SIMD vectorizer loop cases | **6 / 6 passed** | -| SIMD vectorizer gate + fallback | **verified** | +| SIMD vectorizer gate + fallback | **verified** | The full verification gate concludes: @@ -621,12 +683,39 @@ xs[0] = 10 xs[1] = 20 print(xs[0]) +print(xs) # [10, 20, 0, 0] print(len(xs)) ``` Bounds are statically checked when possible and dynamically trapped when the index is only known at runtime. +#### The live length (6.7) + +A list still has a fixed capacity, but it also carries a **run-time length**: a +companion `int[64]` holding how many leading slots are live. A literal counts the +slots it names, so its trailing slots stay zeroed and are not part of the list: + +```python +xs: list[int[64], 6] = [1, 2, 3] +print(len(xs)) # 3 (not 6) +print(xs) # [1, 2, 3] +print(sum(xs)) # 6 (only the live prefix) + +xs.append(4) # writes slot 3, advances the count to 4 +xs.append(5) # ... and slot 4 +print(xs) # [1, 2, 3, 4, 5] +``` + +A bare declaration is the all-zero list with every slot live, so `len` is the +capacity. `len(xs)` reads the companion, `print(xs)` renders only the live +prefix, and the reductions `sum`/`min`/`max`/`any`/`all` and `x in xs` scan the +live prefix. Because those are unrolled over a compile-time length, appending +inside a loop β€” which makes the length run-time-only β€” refuses them +(`E0114`/`E0115`/`E0119`). `append` onto a full list traps +(`list index out of range`) in both tiers, and a statically-known "already full" +append is refused (`E0111`). + ### Tuples Homogeneous immutable tuples are supported: @@ -666,6 +755,217 @@ Supported operations include: Mutation after construction is intentionally not currently supported. +A key is an `int` (any width up to 64), a `bool`, or a bounded string (6.8): + +```python +k: str[16] = "name" + +d: dict[str[16], int[64], 4] = {"name": 1, "age": 2} +print(d[k]) # 1 (a str variable names the key's run) +print(d["age"]) # 2 (a literal is materialised into its own run) +print(contains(d, "zz")) +``` + +A str key is not a scalar, so it has no register form: the operand is the *name* +of the key's byte run, and the probe is a byte hash and a byte comparison rather +than a multiply and a word compare. Both tiers run the **same** walk, so a str +key does not merely hash the same way in each tier β€” the two cannot disagree, +because there is one function (`dict_str_probe`) behind both. Because a key is +copied into a fixed `str[N]`-wide slot, a key's *capacity* must not exceed the +table's key capacity; a literal of `L` bytes also needs `L < N`, exactly as it +would as a `str[N]` assignment. Table order and bucket (memory) order coincide, +so a whole-dict `print` of a str-keyed table is deterministic but not insertion +order. + +A value is a scalar (`int`, `float`, `bool`, `str[N]`) or a container β€” a `list` +or a `tuple` (6.9): + +```python +d: dict[int[64], list[int[64], 4], 4] = {1: [10, 20, 30, 40]} + +print(d[1][0]) # 10 (the value is read out into a run, then indexed) +print(d) # {1: [10, 20, 30, 40]} +``` + +A container value has no scalar form, so it is not stored in a register: the +table's value region is a flat run of leaf cells (`buckets Γ— leaves`), and the +value is copied in and out of one bucket's slice as a whole β€” from a run at +construction, into a run on a read. The copy moves whole leaves at the leaf's +own width (8, 4, 2 or 1 bytes, whichever divides the remaining run), not whole +8-byte slots, because the leaves are packed: `list[tuple[int[32],4],2]` is eight +4-byte leaves, and a slot-at-a-time copy would read past the run and skip every +other leaf. A value of 1000 reads back as 1000, not as `1000 mod 256`. Both tiers +copy exactly the same leaves, so a nested value prints identically. A `dict` +value is still refused (it has three regions, so it has no single leaf run a +value slot could hold). + +### Strings (6.2) + +Bounded strings are supported: + +```python +s: str[8] = "hi" + +print(s) # hi +print(len(s)) # 2 +print(s[0]) # 104 ('h' as an int[8] byte) + +s[1] = 73 # write a byte -> "hI" +print(s) # hI + +t: str[8] = "hI" +print(s == t) # True +``` + +A `str[N]` is exactly `N` bytes of NUL-terminated storage β€” the same packed +byte run a `list[int[8], N]` would be β€” so the content is the bytes before the +first `0x00`, `len(s)` is the run-time byte count (capped at `N`), and `print(s)` +writes those bytes as text. A literal of `L` bytes needs `L < N`, since the last +byte is the terminator; a bare `s: str[8]` is the empty string. `s[i]` reads or +writes one `int[8]` byte, `len(s)` raises to a full `int[64]`, and `s == t` is a +byte-for-byte comparison that may span **different capacities**. Both the +interpreter and the JIT run the same byte scan, so a string prints and compares +identically in either tier. + +Two substring scans complete the string surface (6.4): + +```python +s: str[16] = "hello world" + +print(s.find("world")) # 6 (lowest index, or -1 when absent) +print(s.find("z")) # -1 +print(s.count("l")) # 3 (non-overlapping matches) +print(s.count("")) # 12 (len(s) + 1, as in Python) +``` + +`s.find(sub)` returns the lowest index of `sub` or `-1`; `s.count(sub)` counts +non-overlapping occurrences. The needle is a string literal or another `str` +variable, and its capacity need not match the haystack's. The empty needle is +the Python edge: `find` returns `0`, `count` returns `len(s) + 1`. Both scans +read the same NUL-terminated byte runs `s == t` does, so they agree byte for +byte across the two tiers. + +The one way to take a string apart is `str.split` (6.7). A list is a declared +run, not a value, so the pieces have to be named somewhere: the destination is +declared on the left, and the split writes into it. + +```python +s: str[32] = "a,bb,ccc" + +parts: list[str[8], 4] = s.split(",") +print(len(parts)) # 3 +print(parts) # ['a', 'bb', 'ccc'] + +t: str[4] = "::" +q: list[str[8], 4] = "x::y::z".split(t) # a str variable separator too +print(q) # ['x', 'y', 'z'] +``` + +`s.split(sep)` fills the destination and sets its length companion, so `len` and +`print` see exactly the pieces produced β€” including empty ones, so `",a,b,"` +splits into `['', 'a', 'b', '']`. A piece longer than `K-1` bytes is truncated +to fit its `str[K]`, and a split that produces more than `M` pieces traps +(`too many pieces for the list (str.split)`) rather than dropping any silently. +The whitespace-splitting form `s.split()` and an empty separator are refused, so +the separator is always explicit. + +#### A string is a leaf type + +Because a `str[N]` is just a byte run, it can be *anywhere* a scalar element can +be: a list or tuple element, a struct field, or a pointer's pointee. + +```python +words: list[str[8], 3] = ["one", "two", ""] +print(words[1][2]) # 111 ('o' of "two") +words[1][2] = 88 # -> "twX" + +class Named(Struct): + name: str[8] + n: int[8] + +c: Named +c.name = "hi" +print(len(c.name)) # 2 +c.name[0] = 120 # -> "xi" + +s: str[8] = "hi" +_p: ptr[str[8]] = addressof(s) +print(valueof(_p)) # hi -- the pointer names s's run +print(len(valueof(_p))) # 2 +``` + +A struct's `str` field lives in a *companion byte run* named by the dotted +attribute path (`c.name`), declared right after the struct's own blob; a nested +field is named by the whole path, so `o.inner.tag` is one run too. A `str[N]` +pointer names the string it was minted from, so `valueof(_p)` denotes that +string's place rather than a scalar β€” every string operation works through it. +A field reached through a pointer *into the struct* (`valueof(_pp).name`) is +refused: the companion run is keyed by the variable name, so there is no run to +name there. + +#### Printing a nested string + +A string printed **directly** β€” a bare variable, or a struct field β€” prints its +bare contents. A string that appears **inside an aggregate** (a list or tuple) +prints the way Python's `repr` does: quoted, with the same backslash escapes +CPython uses. + +```python +s: str[8] = "hi" +print(s) # hi (bare, top level) + +pair: tuple[str[8], 2] = ("hi", "a b") +print(pair) # ('hi', 'a b') (repr, nested) +``` + +Both tiers produce byte-identical output for both forms. + +#### Printing string literals and f-strings (6.3 Stage A) + +A string literal may be printed directly, and an f-string may be used inside +`print`. Neither builds a string value: a constant chunk is written into a +shared bounded run and printed through the same top-level path `print(s)` uses, +while `{expr}` is printed by the ordinary value printer, so a `%` or a brace in +a literal is just a byte and floats use the one shared shortest-round-trip +formatter. + +```python +x: int[32] = 3 +s: str[16] = "world" + +print("hello") # hello +print("x is:", x) # x is: 3 (arguments are space-separated) +print(f"x is {x}") # x is 3 (f-string pieces are adjacent) +print(f"{{x}}={x}") # {x}=3 ({{ }} escape a brace) +print("100%") # 100% +``` + +`{{` / `}}` escape a literal brace **inside an f-string only**; in a plain +string literal they are two ordinary braces, exactly as in Python. A format +specifier (`{x:.2f}`), a conversion (`{x!r}`) and `{x=}` are refused rather than +printed wrong. An f-string used as a *value* (`s: str[64] = f"x={x}"`) is Stage +B and is not implemented yet. + + +### Printing and nesting + +`print(xs)` renders a whole container the way Python's `repr` does β€” `[10, 20, +30]` for a list, `(1, 2, 3)` for a tuple (with the one-element trailing comma +`(1,)`) β€” in both tiers, and a `dict` prints in bucket (memory) order. + +Containers **nest**: an element may itself be a list or tuple, so +`list[list[int[64], 4], 2]` and `list[tuple[int[64], 3], 4]` are legal and a +nested `dict` is refused (a dict has no flat scalar leaf). The layout is a flat +run of scalar leaves, so `xs[i][j]` is a single indexed access at the summed +byte offset β€” there is no intermediate container object. Reads, writes and +`print` all descend to the deepest leaf; a partial index chain like `xs[i]` on a +nested container is refused. + +A container is *storage, not a value*. It is built by its declaration plus +`IndexStore`s, and there is nothing to copy, so `z = ys` (two lists) and +`xs[0] = a` (a container element) are refused at compile time rather than +miscompiled. + --- ## 🧱 Structs @@ -720,6 +1020,165 @@ compiler and asserts the frontend's offsets and sizes equal the C --- +## 🏷️ Enums + +An enum is a tagged union: a byte blob holding an `int` tag plus room for the +payload of whichever variant is active. Like a struct it **has no value** β€” +the only operations are constructing a variant, reading the tag with `match`, +and reading a payload through the arm's binding. It is spelled as a class +deriving from `Enum`: + +```python +class Shape(Enum): + Empty + Dot + Line: int[32] + Rect: int[64] + +s: Shape +s = Shape.Dot +match s: + case Shape.Empty: + print(0) + case Shape.Dot: + print(1) + case Shape.Line(n): + print(n) + case Shape.Rect(w): + print(w) +``` + +A variant is a bare unit name (`Empty`), a `name: type` scalar payload +(`Line: int[32]`), or an inline `class V(Struct)` struct payload. The layout is +`[tag][payload]` with **no padding**: the tag is an `int[8]` while the enum has +at most 128 variants and widens to `int[16]` past that, the payload starts at +the tag width, and the size is the tag width plus the largest payload. Only the +active variant's bytes are ever read, so overwriting a payload variant with a +smaller one leaves stale bytes that no `match` can observe. + +A `match` must be exhaustive β€” every variant covered, or a final `case _:`. It +lowers to a tag `fieldload`, an equality test, and a branch per arm, reusing the +struct machinery; there is no new IR. A struct field may be an enum +(`c.proto = Endpoint.Tcp(port=8080, addr=16777343)`), but list elements, enum +parameters/returns, enum-typed payloads, and `case` guards are refused. + +`tools/check_enum_layout.py` compiles an equivalent C `#pragma pack(1)` +tag-plus-union and asserts the frontend's tag width, payload offset, and size +equal the C `offsetof`/`sizeof` values at both tag-width boundaries. + +--- + +## πŸ”§ Methods + +A `def` inside a struct or enum body is a **method**. The first parameter is the +implicit receiver `self`; the rest are annotated scalars, and the return type is +optional. A method is called as `recv.name(args)`. + +```python +class Counter(Struct): + value: int[64] + + def add(self, n: int[64]) -> int[64]: + self.value = self.value + n + return self.value + + def total(self, n: int[64]) -> int[64]: + t: int[64] = 0 + i: int[64] = 1 + while i < n + 1: + t = t + i + i = i + 1 + return t + + +c: Counter +print(c.add(5)) +print(c.total(4)) +``` + +A method call is **inlined at the call site**: `self` becomes the receiver place, +the method's parameters and locals are renamed so they cannot collide with the +caller's, and its `return` stores the result and jumps to the end of the inline. +Inlining is forced by the storage model β€” a field access names a variable, not a +value, and a struct or enum cannot be passed to a function (E0124) β€” and it is +what lets a method read *and write* the receiver's fields, keep its own locals, +run loops and branches, and call other methods. The receiver can be a struct or +enum variable or a struct/enum field (`o.inner.bump(1)`), and an enum method +reads `self` with `match self`. + +Because the body is expanded at each call site, a method cannot recurse, +directly or indirectly (E0135). A method signature is scalars only +(`int[w]`/`float[64]`/`bool`): a method may not take or return a struct, an +enum, a container or a pointer, and `self` is unannotated. There is no +inheritance or overloading, a method name may not shadow a field or a variant, +and the signature accepts no `/`, `*`, `**` or defaults (E0134). + +```python +class Shape(Enum): + Empty + Line: int[32] + + def code(self) -> int[64]: + match self: + case Shape.Empty: + return 0 + case Shape.Line(n): + return n + case _: + return -1 +``` + +`tools/check_methods.py` proves the transparency claim: it compiles a +method-using program and the same program with the bodies inlined by hand and +requires both tiers to produce identical output, checks the IR is deterministic, +and checks the E0134/E0135 refusals. + +--- + +## 🧩 Type Functions + +A type function is a **compile-time type constructor**: `def Name(params) -> +NewType` returns `{ T }`, and writing `Name(args)` anywhere a type is allowed +substitutes the arguments into `T` and yields an ordinary type. + +```python +def Fixed(t, n) -> NewType: + return {list[t, n]} + +def Word() -> NewType: + return {int[32]} + +def Ref(t) -> NewType: + return {ptr[t]} + +xs: Fixed(int[64], 4) # == list[int[64], 4] +w: Word() # == int[32] +_p: Ref(int[64]) # == ptr[int[64]] +``` + +The result is **structural** β€” `Alias(int[64])` and `int[64]` are the same +type, so nothing about a type function survives into the IR: compiling the +program above and the same program with the three types spelled out produces +byte-identical IR. The parameters hold **types and sizes** (they are +unannotated names), not runtime values; a definition carries no code and is +invisible to the runtime. + +Applications nest (`Pair(Word())`) and work anywhere an annotation does: +function parameters and returns, struct fields, and container element types. +The body must be exactly one `return { }` β€” `{}` is a dict and is +refused, as are multi-element bodies, comprehensions, decorators, annotated or +defaulted parameters, redeclarations, and names that collide with a struct, an +enum or a function. A body composes the value types an annotation can spell β€” +scalars, containers, pointers and dicts β€” and may not name a struct or an enum, +which are storage rather than values. A type function must bottom out in a +concrete type, so direct and mutual recursion are refused. + +`tools/check_typefunc.py` proves the structural claim: it compiles a +type-function program and its spelled-out equivalent and requires the IR to be +byte-identical, then checks memoization and the E0132/E0133 refusals. + +--- + ## 🧠 SSA and Optimization Lithon's compiler is progressively moving from a memory-based IR toward SSA. @@ -750,7 +1209,7 @@ Current components include: Phi handling is now implemented as an opt-in native path: ```bash -lithon program.py --strict +lithon program.lio --strict ``` with the compiler's direct-Phi development path available through the relevant @@ -875,6 +1334,36 @@ the CPU's register-level behavior. --- +## πŸ”Œ Raw Syscalls + +Lithon can invoke the platform's raw syscall primitive directly: + +```python +uid: int[64] = syscall(102) # getuid() on Linux/x86-64 +``` + +`syscall(number, a1, a2, a3, a4, a5, a6)` takes a syscall number and up to six +arguments and returns the kernel's **raw** result as `int[64]`. On Linux a +failure is a negative `-errno`; nothing is wrapped and nothing is checked β€” a +program that wants to handle failure compares the result itself. There is no +`syscall` function to import: it is a compiler-recognized name that lowers to a +single `Syscall` IR instruction. + +Both tiers execute it through one shared host trampoline +(`src/jit/syscall_runtime.h`), so the calling convention cannot drift between +the interpreter and the JIT. That trampoline consults an embedder-installed +hook first, on every platform; with no hook it runs the actual x86-64 `syscall` +instruction on Linux, and on a platform whose raw ABI is not stable (Windows) +returns `-ENOSYS` rather than guessing. + +**Current restriction:** arguments are integers in this slice. A pointer +argument is refused at typecheck, because the interpreter represents pointers as +synthetic addresses rather than real memory, so a syscall that takes a pointer +(`write`, `read`) could not behave the same in both tiers. Pointer-taking +syscalls are deferred until the interpreter grows a real address-space story. + +--- + ## πŸ›‘οΈ Safety Philosophy Lithon is not trying to eliminate low-level programming. @@ -922,7 +1411,7 @@ rather than hiding the fact that a variable contains a raw address. | Feature | CPython | Cython / mypyc | PyPy | **Lithon** | | :-------------------- | :------------------- | :-------------------------- | :------------- | :------------------------ | -| Syntax | Python | Python | Python | **Python `.py` + Extended PEP-526** | +| Syntax | Python | Python | Python | **Python-shaped `.lio` + Extended PEP-526** | | Primary execution | Bytecode interpreter | C extensions | Tracing JIT | **Native x86-64** | | Static typing | No | Optional | No | **Mandatory** | | Fixed-width integers | No | Possible | No | **Built-in** | @@ -964,6 +1453,73 @@ than CPython, PyPy, Cython, Rust, or C++. --- +## πŸ”’ Iteration Helpers + +`enumerate` and `zip` work in a `for` header: + +```python +for i, x in enumerate(xs): + print(i) + print(x) + +for a, b in zip(xs, ys): + print(a) + print(b) +``` + +Neither is a value. Both are a **rewrite of a counted loop** over constructs that +already exist: `for i, x in enumerate(xs)` becomes `for i in range(N): x = xs[i]`, +and `for a, b in zip(xs, ys)` becomes the same loop bounded at `min(N1, N2)` β€” a +compile-time constant, because the declared capacity is what `len()` folds to. +So there is no new opcode and no runtime helper; `zip` costs nothing beyond the +indexed loads it already emits. + +Two consequences worth stating plainly: + +- **`zip` stops at the shortest input**, exactly as Python does. `zip(xs, ys)` + over a `list[..., 6]` and a `list[..., 3]` runs three times. +- **`enumerate` and `zip` are for-header only.** `z = zip(a, b)` is refused: the + pair never needs to exist as a value, and a homogeneous tuple could not + represent `enumerate`'s `(int, T)` anyway. + +Elements bind with their declared type, so a `float[64]` list yields floats and +a `list[int[16], N]` stays signed. `break` and `continue` work inside either +header, because the rewrite is a real loop. + +### 🧾 Divergences from Python + +Deliberate narrowings, collected next to `%` and `v == n`: + +| Construct | Python | Lithon | Why | +|---|---|---|---| +| `%` | remainder, sign follows the divisor | truncation toward zero; zero divisor traps (`E0301`/`E0302`) | signed machine remainder is not total | +| `v == n` | value equality | not supported | equality on machine values has no total answer | +| `zip` / `enumerate` | first-class iterators, usable as values | for-header only | a rewrite of a counted loop; the pair is never a value | +| `x in xs` on a list | membership, over any element type | membership over int/float/bool lists | it lowers to `==`; an aggregate or `str` element has no scalar `==` | +| `a == b` on two bools | `True == 1` | same: a bool compares as `0/1` in both tiers | a bool is a 1-byte scalar, not a distinct incomparable type | +| `v in xs` on a `bool` list | membership | supported | same `==` over the `0/1` value | +| `print(a, b)` | space-separated, one trailing newline | supported; `sep=` / `end=` not yet | the default separator and newline are runtime calls; a configurable one needs string values | +| f-strings | any expression, format specs, `!r` | in `print` only (Stage A); `{expr}` for int/float/bool/str, `{{`/`}}` escapes | a format spec / conversion needs a runtime formatter; an f-string *value* needs a bounded buffer | +| `print("literal")` | prints the text | supported | the bytes are materialised into a bounded run and printed through the `str` path; no string value is built | +| `print(_p)` | a stable address | prints an address, but it differs per tier | real addresses cannot be byte-comparable across tiers | +| a dangling `_p` | undefined | undefined, and now a `SIGSEGV` | out of scope by design, not tracked by the compiler | +| string literals | unbounded `str`, any operation | `str[N]` bounded strings: literal construction, `print`, `len`, `s[i]` read/write of one `int[8]` byte, `s == t`; usable as a list/tuple element, a struct field or a `ptr` pointee | the capacity must be declared, so a string is a fixed byte run with no heap | +| a list literal shorter than its capacity | the list has exactly the elements written | the trailing slots stay zeroed and are **not** live: `len`/`print`/a reduction see only the named elements | the run is fixed-size and zeroed, and the live count is a separate companion | +| `xs[i] = v` on a list | grows the list if `i` is past the end | unchanged length; `i` must be in `0..cap-1` or it traps | the length is advanced by `append`, not by an indexed store | +| `list.append(v)` | amortised O(1), unbounded | writes the next live slot; a full list traps (`list index out of range`) | the capacity is fixed, so growth has no storage to run to | +| `list.append(v)` inside a loop | β€” | makes the length run-time-only, which refuses `sum`/`min`/`max`/`any`/`all`, `in`, `enumerate` and `zip` over that list (`E0114`/`E0115`/`E0119`) | those are unrolled over a compile-time length; a run-time bound has none | +| `s.split(sep)` | returns a new `list[str]` | `parts: list[str[K], M] = s.split(sep)` writes into a declared list; a piece longer than `K-1` bytes is truncated, and more than `M` pieces traps | a list is a declared run, not a value, so the destination is named; the pieces are bounded strings | +| `s.split()` (whitespace) | splits on runs of whitespace | refused (`E0114`) | the default form needs a whitespace-class scan; an explicit separator is the one this slice implements | +| a `dict` | mutable, `d[k] = v` after construction, any hashable key | immutable: built by its literal, then read and `contains` only; keys are `int`/`bool`/`str[N]` | one probe both tiers derive from one hash, with no growth and no rehash | +| a `str` dict key | any string | `str[N]`: a literal key or a `str` variable whose **capacity** does not exceed the table's key capacity | a key is copied into a fixed `N`-byte slot; a wider run could not survive the copy | +| a `dict` value | any value, including another `dict` | a scalar, or a `list`/`tuple` run; a `dict` value is refused (`E0112`) | a value slot is one flat leaf run; a `dict` has three regions, so it has no single run a slot could hold | +| source file extension | `.py` | `.lio`; the frontend refuses any other extension (`E0114`) | the extension is what marks a file as a Lithon program, so a plain Python script is never silently compiled as Lithon | +| `try`/`except` (6.10) | catches any exception, from anywhere, and `finally` always runs | catches the closed set of built-in Tier-3 traps (`ZeroDivisionError`, `OverflowError`, `ValueError`, `IndexError`, `KeyError`, `EOFError`, or `Exception` for all of them) **raised in the same function as the `try`**; a trap raised inside a called function ends the program, and an *uncaught* trap does not run a `finally` | a trap is routed statically, so a caught trap is a plain jump within one function; crossing a call boundary would need a real unwind, and re-running `finally` on a propagating trap would need `raise` | +| a bare `except:` | catches everything | refused (`E0114`) | it would claim to catch a segfault, which no tier can catch; `except Exception:` is the spelled-out catch-all | +| `except X as e` / `raise` / `except*` (6.10) | binds an exception object, re-raises, groups | refused (`E0114`) | there is no exception object -- a caught trap is a jump to a block, so there is nothing to bind or re-raise | + +--- + ## 🧭 Roadmap ```text @@ -1021,11 +1577,74 @@ than CPython, PyPy, Cython, Rust, or C++. ### Phase III β€” Systems / hardware integration -* [ ] Direct syscall emission +* [x] Direct syscall emission (raw `syscall(number, a1..a6) -> int[64]`) +* [x] Networking syscalls (5.2): `socket`/`bind`/`connect`/`listen`/`accept`/ + `send`/`recv`/`close`, with `&sockaddr_in` and buffers passed as pointers β€” a + single-process loopback round trip is verified against a raw C oracle + (`tools/check_net_loopback.py`), including the byte-swap failure mode * [ ] Expanded raw-memory operations * [ ] Expanded native FFI * [ ] ARM64 backend +### Phase IV β€” Python-familiar surface + +* [x] Iteration helpers: `enumerate`, `zip` (for-header only; a rewrite of a + counted loop, no new IR) +* [x] `sum` / `min` / `max` / `abs` / `any` / `all`, and `in` over a list β€” also + rewrites over the compile-time capacity `N`; `min`/`max` select through the + if-expression diamond, `abs` is `-v if v < 0 else v` +* [x] Multi-argument `print`, lowered to runtime pieces (a value printer, a + separator, a newline) rather than a built string; `sep=` / `end=` deferred +* [x] `str[N]` bounded strings and string literals: literal construction, `print`, + `len`, `s[i]` read/write of one `int[8]` byte, `s == t`, usable as a list/tuple + element, a struct field and a `ptr` pointee +* [x] f-strings and string-literal printing, in `print` only (6.3 Stage A): + `print("x is:", x)` and `print(f"x is {x}")` lower to literal chunks plus value + prints β€” no string is built; `{expr}` for int/float/bool/str, `{{`/`}}` escapes, + a `%` in a literal is an ordinary byte. Format specs (`{x:.2f}`), `!r` and + `{x=}` are refused; an f-string as a *value* (Stage B) is still to come +* [x] `str.find` / `str.count` (6.4): `s.find(sub)` returns the lowest index or + `-1`, `s.count(sub)` counts non-overlapping matches; the needle is a string + literal or another `str`, and the empty needle matches Python (`find` β†’ 0, + `count` β†’ `len(s) + 1`) +* [x] `list.append` and the run-time list length (6.7): a list is still a + fixed-capacity run, but it now carries a companion `int[64]` holding how many + slots are live. A literal counts the slots it names (the trailing ones stay + zeroed), `append(v)` writes the next slot and advances the count, `len(xs)` is + the count, `print(xs)` renders only the live prefix, and `sum`/`min`/`max`/ + `any`/`all` and `in` scan it. Appending onto a full list traps + ("list index out of range") in both tiers; appending inside a loop makes the + length run-time-only, which refuses a reduction/membership scan (`E0114`/ + `E0115`/`E0119`) and turns a compile-time "already full" append into `E0111`. +* [x] `str.split` (6.7): `parts: list[str[K], M] = s.split(sep)` splits a bounded + string into a declared, fixed-capacity list of bounded strings. `sep` is a + string literal or another `str`; the piece count lands in the destination's + length companion (`len`/`print` see exactly the pieces). A piece longer than + `K-1` bytes is truncated, and more than `M` pieces traps in both tiers. The + whitespace-splitting form `s.split()` and an empty separator are refused + (`E0114`). +* [x] `x in xs` over a `bool` list β€” it lowers to `==`, and a bool now compares + as its `0/1` value in both tiers, so membership works over every scalar element + type (int, float, bool) +* [x] `!=` (6.9) β€” a first-class `Ne` compare mirroring `==`, differing only in + the result. Integers compare exactly as `int64`, floats follow IEEE (`nan != + nan` is true via `setne OR setp`; `0.0 != -0.0` is false), `str` compares as a + run (`strne`), pointers compare raw addresses, and `!=` is usable as a value, + in `if`/`while`, and inside `and`/`or`. A `while i != n` loop is not + strength-reduced or vectorized, since `i != n` has no guaranteed trip count. + `raise`, `try`/`except` and comparison on aggregates (struct/list/tuple/dict/ + payload enum) remain deferred. +* [x] `try`/`except` (6.10) β€” catches the closed set of built-in Tier-3 traps + (`ZeroDivisionError`, `OverflowError`, `ValueError`, `IndexError`, + `KeyError`, `EOFError`, or `Exception` for all of them), with `else` and + `finally`. A caught trap is a jump to the matching handler block, not an + exception object and not a stack unwind, so `try`/`except` is ordinary + control flow plus three markers (`TryEnter`/`TryLeave`/`TryCatch`) that both + tiers read. Only a trap raised in the same function as the `try` is + catchable; `except X as e`, `raise`, `except*` and a bare `except:` are + refused. The EAFP rhyme works: `int(8, input("..."))` on a typo lands in + `except ValueError:`. + --- ## πŸ“ Current Known Limitations @@ -1044,6 +1663,54 @@ Current limitations include: * Pointer values have strict restrictions around function boundaries and container access. * Some SSA/native paths remain opt-in or have known unsupported cases. +* **Field access through a pointer (`valueof(_p).x`) works in both tiers.** The + IR, parser, type checker, interpreter and JIT all agree: a field read or write + whose subject is a pointer register lowers to the same byte offset the named + form uses. The JIT gained a general `[reg+disp]` encoder primitive (verified + against GNU `as` over 18,379 cases, zero disagreements) so these instructions + run natively instead of falling back. `tests/typed_regression/ptr_field.lio` + proves the read and write paths, and `ptr_alias.lio` proves the named and + pointer forms stay aliased across a loop and an inlined method at every + optimization level. +* `ptr[Point]` forms, typechecks, compares, scales by `sizeof(Point)`, and can be + read and written through in both tiers. A method receiver is still bound by + inlining as a concrete place rather than as a `ptr[Point]`, which is + observationally the same and needs no separate pointer path. +* **A bounded string is a leaf type.** A `str[N]` may be a list/tuple element, a + struct field, or a `ptr[str[N]]` pointee. A struct's `str` field lives in a + companion byte run named by the dotted attribute path (`s.a`, `s.inner.a`), so + `len`, byte reads/writes, `print` and `==` all reuse the top-level string path + with no separate codegen; a nested field is named by the whole path. A + `ptr[str[N]]` (only meaningfully obtained from `addressof(s)`) names `s`'s run, + so `valueof(_p)` denotes that string's place. A `str` field reached through a + pointer *into* the struct (`valueof(_pp).a`) is refused (`E0128`), because the + companion run is keyed by the variable name. `tests/typed_regression/` covers + a list of strings (`strings_nested.lio`), a struct field (`struct_str.lio`), a + nested struct field (`struct_str_nested.lio`) and a string pointer + (`ptr_str.lio`). +* **Address-taken memory is an optimizer barrier.** A variable whose address is + taken is never promoted to SSA (both `ssa.h` and `register_alloc.h` gate on + `AddressOf`), and a pointer cannot cross a function boundary (the header + records no pointee, `E0124`), so no pass can forward a stored value across a + pointer store or elide one. `ptr_alias.lio` is the regression guard. +* **Dangling pointers are out of scope by design.** The compiler does not track + a pointer's lifetime against what it points at, so dereferencing freed or + out-of-bounds memory is undefined β€” and now that pointers are real addresses, + a wild dereference is a `SIGSEGV` in the interpreter too, not a deterministic + trap. This is a stated choice, not a silent gap: the programmer proves the + math, the types and the constraints, never the memory location. +* **A printed pointer value is not comparable across tiers.** The interpreter's + arena addresses and the JIT's frame addresses differ, and ASLR moves them + between runs. Pointer arithmetic, comparison of two pointers into the same + object, and `valueof` *are* tier-consistent; the raw number is not. Programs + that print a pointer are excluded from the tier-diff oracles. +* `syscall(...)` takes int[64] and pointer arguments. Networking (5.2) is + reachable: `bind`/`connect` pass `&sockaddr_in` and `send`/`recv` pass a + buffer, both as real pointers into frame memory. Network byte order is *not* + applied for you β€” the port and address must be byte-swapped explicitly, and a + wrong swap silently names the wrong address (`tools/check_net_loopback.py` + pins both the round trip and the failure mode). Floats and aggregates are not + passed by value. * AVX2 vectorized code is emitted and byte-verified everywhere, but it is only *executed* on hosts that report AVX2; on other hosts the CPUID gate always takes the scalar fallback, so vector execution itself is not exercised there. @@ -1090,6 +1757,34 @@ python3 tools/run_typed_regression.py python3 tools/run_tier_diff.py ``` +Every program runs twice β€” once through the interpreter (the oracle) and once +through the native tier β€” and the two stdout streams must be byte-identical. +Currently **159 / 159** cases pass, 93 of them on the native tier. + +Two classes of case are deliberately outside this comparison: + +- **Adversarial cases** declare which tier they *expect*, so a guard that is + too weak (native where it must refuse) and too strict (interpreter where + native is provably safe) both fail the run. +- **Printed pointer values** cannot be byte-comparable, because the two tiers + address memory differently. Pointer arithmetic, comparison of two pointers + into the same object, and `valueof` *are* compared. + +Sockets are likewise outside it: they are stateful and external, so 5.2 is +verified against a raw C program doing the identical syscalls instead. + +### Syscall hook parity + +```bash +./build/syscall_hook_test +``` + +`g_syscall_hook` is consulted first on every platform, which turns the raw +syscall into a pure function of its inputs. The test installs a hook and runs +the *same* program through both tiers, requiring the hook to observe identical +arguments β€” which is what pins the JIT's six-slot stack staging and keeps the +two engines from drifting about the calling convention. + ### Differential fuzzing ```bash @@ -1119,7 +1814,7 @@ generated by the same benchmark harness. | Benchmark | Lithon JIT | Reference | Speedup | Result | |---|---:|---:|---:|---:| -| fib(30) | 10.1771 ms | 1576.4747 ms | 154.9Γ— | 832040 | +| fib(30) | 10.3053 ms | 1731.0983 ms | 168.0Γ— | 832040 | **Status:** PASS diff --git a/test_codes_lithon/DataTypes/check_strings.lio b/test_codes_lithon/DataTypes/check_strings.lio index bb40472..cae996b 100644 --- a/test_codes_lithon/DataTypes/check_strings.lio +++ b/test_codes_lithon/DataTypes/check_strings.lio @@ -1,11 +1,11 @@ -# check_strings.py +# check_strings.lio # # Strings are NOT supported yet: the frontend lowers a bare string literal to # "expression node Constant not supported yet". Kept here as the pinned # example of that gap -- running it fails loudly rather than silently # producing a wrong answer. # -# $ lithon test_codes_lithon/check_strings.py +# $ lithon test_codes_lithon/DataTypes/check_strings.lio # NotImplementedError: expression node Constant not supported yet # # Until that lands, the closest working equivalent is `len()` on a pointer or diff --git a/test_codes_lithon/Pointers/pointer_error.lio b/test_codes_lithon/Pointers/pointer_error.lio index 6d2594e..dcbd59d 100644 --- a/test_codes_lithon/Pointers/pointer_error.lio +++ b/test_codes_lithon/Pointers/pointer_error.lio @@ -1,15 +1,15 @@ -# pointer_error.py +# pointer_error.lio # # The pointer `_` rule (4.4) enforced by the frontend: a pointer binding must # carry a leading underscore, because raw memory has to be visible in the # source. The two names below are deliberately wrong, so this file exists to # fail: # -# $ lithon test_codes_lithon/pointer_error.py +# $ lithon test_codes_lithon/Pointers/pointer_error.lio # NotImplementedError: a pointer is named with a leading underscore, # write `_ptrI: ptr[...]` or pick a name that starts with '_' (4.4) # -# The corrected spelling is in check_pointers.py. +# The corrected spelling is in Pointers/check_pointers.lio. i: int[16] = 22 ptrI: ptr[int[16]] = addressof(i) print(valueof(ptrI)) \ No newline at end of file diff --git a/test_codes_python/Classes/class_basics.py b/test_codes_python/Classes/class_basics.py new file mode 100644 index 0000000..1e3e49e --- /dev/null +++ b/test_codes_python/Classes/class_basics.py @@ -0,0 +1,22 @@ +"""Plain-CPython equivalent of test_codes_lithon/Classes/class_basics.lio. + +Same program: a class with two typed fields, then a value of that class with +the fields assigned. CPython has no width annotations, so the class body just +declares the names and the assignments follow. + +Note: Lithon requires a class to derive from Struct or Enum; CPython has no +such requirement, so the portable subset drops it. +""" + + +class test_class: + name: str + year: int + + +x = test_class() +x.name = "Lithon" +x.year = 2026 + +print(x.name) +print(x.year) diff --git a/test_codes_python/base_var.py b/test_codes_python/DataTypes/base_var.py similarity index 74% rename from test_codes_python/base_var.py rename to test_codes_python/DataTypes/base_var.py index 910b03e..883b26e 100644 --- a/test_codes_python/base_var.py +++ b/test_codes_python/DataTypes/base_var.py @@ -1,8 +1,8 @@ -"""Plain-CPython equivalent of test_codes_lithon/base_var.py. +"""Plain-CPython equivalent of test_codes_lithon/DataTypes/base_var.lio. Same program: declare a binding with a type and no value, assign it, print. CPython has no annotation-only declaration, so the annotation is dropped and the binding is simply assigned -- the definite-assignment rule is Lithon's. """ i = 0 -print(i) \ No newline at end of file +print(i) diff --git a/test_codes_python/DataTypes/check_strings.py b/test_codes_python/DataTypes/check_strings.py new file mode 100644 index 0000000..70c5ed6 --- /dev/null +++ b/test_codes_python/DataTypes/check_strings.py @@ -0,0 +1,17 @@ +"""Plain-CPython equivalent of test_codes_lithon/DataTypes/check_strings.lio. + +Same program: the closest working equivalent to a string operation in +Lithon -- `len()` on a container, which is capacity-driven rather than +content-driven, because a bare string literal is not yet a value in Lithon. + +NOTE: in Lithon this file is a *pinned failure*. The engine refuses it +on purpose so the gap stays visible: + + $ lithon test_codes_lithon/DataTypes/check_strings.lio + LITHON-E0114: expression node Constant not supported yet + +The corrected spelling (same pointer, same pointee width) does run, and +prints False -- a real address is never null. +""" +xs = [0, 0, 0, 0, 0, 0, 0, 0] +print(len(xs)) # 8 diff --git a/test_codes_python/DataTypes/convertions.py b/test_codes_python/DataTypes/convertions.py new file mode 100644 index 0000000..0c3afa1 --- /dev/null +++ b/test_codes_python/DataTypes/convertions.py @@ -0,0 +1,15 @@ +"""Plain-CPython equivalent of test_codes_lithon/DataTypes/convertions.lio. + +Same program: explicit width conversions between int and float, plus a +32-bit integer that keeps its value. CPython's int/float are already the +widest widths, so only the width arguments are dropped. +""" +a = 24 +b = 512 +c = 22 / 7 +k = 2147483647 +print(a, b, c) +d = int(c) +e = float(a) +f = k +print(d, e, f) diff --git a/test_codes_python/DataTypes/floats.py b/test_codes_python/DataTypes/floats.py new file mode 100644 index 0000000..70a68fa --- /dev/null +++ b/test_codes_python/DataTypes/floats.py @@ -0,0 +1,9 @@ +"""Plain-CPython equivalent of test_codes_lithon/DataTypes/floats.lio. + +Same program: print a float, print a float in exponent form, and print the +classic 0.1 + 0.2 rounding artefact. CPython's float is already binary64, +so only the width annotation is dropped. +""" +print(1.0) +print(1e22) +print(0.1 + 0.2) diff --git a/test_codes_python/DataTypes/strings.py b/test_codes_python/DataTypes/strings.py new file mode 100644 index 0000000..e60a3b5 --- /dev/null +++ b/test_codes_python/DataTypes/strings.py @@ -0,0 +1,12 @@ +"""Plain-CPython equivalent of test_codes_lithon/DataTypes/strings.lio. + +Same program: a bounded string holding "Lithon", then its length. + +Lithon declares `name: str[7] = "Lithon"` -- seven bytes, the last one the +NUL terminator -- and `len(name)` reports the six visible characters. CPython +strings are already NUL-terminated and unbounded, so the capacity is simply +dropped; len() reports the same six characters. +""" +name = "Lithon" +print(name) +print(len(name)) diff --git a/test_codes_python/Functions/fibb.py b/test_codes_python/Functions/fibb.py new file mode 100644 index 0000000..7bad9e8 --- /dev/null +++ b/test_codes_python/Functions/fibb.py @@ -0,0 +1,16 @@ +"""Plain-CPython equivalent of test_codes_lithon/Functions/fibb.lio. + +Same program: the recursive Fibonacci function with an explicit parameter +and return type, called with 30. CPython has no width annotations, so only +the annotations are dropped. +""" + + +def fib(n): + if n < 2: + return n + return fib(n - 1) + fib(n - 2) + + +x = fib(30) +print(x) diff --git a/test_codes_python/Functions/max_prog.py b/test_codes_python/Functions/max_prog.py new file mode 100644 index 0000000..779742f --- /dev/null +++ b/test_codes_python/Functions/max_prog.py @@ -0,0 +1,27 @@ +"""Plain-CPython equivalent of test_codes_lithon/Functions/max_prog.lio. + +Same program: read two integers from stdin, print which is larger together +with the delta, then print the winner again. + +Both engines read the two values from stdin, so this program is only +meaningful when stdin is fed (e.g. `echo "3 7" | python3 max_prog.py`). +""" +def get_max(a, b): + if a > b: + print(f"Delta is |a-b|: {a-b}") + return a + else: + if b > a: + print(f"Delta is |b-a|: {b-a}") + return b + elif a == b: + print(f"Delta is: 0") + return 0 + + +a = int(input("Enter value 1: ")) +b = int(input("Enter value 2: ")) +if get_max(a, b) != 0: + print(f"Larger value between {a} and {b} is: {get_max(a,b)}") +else: + print("No larger value!") diff --git a/test_codes_python/find_e.py b/test_codes_python/Loops/find_e.py similarity index 83% rename from test_codes_python/find_e.py rename to test_codes_python/Loops/find_e.py index f339f39..2980249 100644 --- a/test_codes_python/find_e.py +++ b/test_codes_python/Loops/find_e.py @@ -1,4 +1,4 @@ -"""Plain-CPython equivalent of test_codes_lithon/find_e.py. +"""Plain-CPython equivalent of test_codes_lithon/Loops/find_e.lio. Same program: seed e with the n=0 term (1/0! = 1), then sum 1/n! for n = 1..7999 and print the result. @@ -20,4 +20,4 @@ def get_factorial(lim): e = e + term i = i + 1 -print(e) \ No newline at end of file +print(e) diff --git a/test_codes_python/Loops/simple_sum.py b/test_codes_python/Loops/simple_sum.py new file mode 100644 index 0000000..09fcc1a --- /dev/null +++ b/test_codes_python/Loops/simple_sum.py @@ -0,0 +1,21 @@ +"""Plain-CPython equivalent of test_codes_lithon/Loops/simple_sum.lio. + +Same program: fill a fixed-size list of four, sum it with a while loop, then +print the total and the length. CPython lists are unbounded, so the capacity +argument is dropped; the loop still runs over the same four elements. +""" +xs = [0, 0, 0, 0] + +xs[0] = 10 +xs[1] = 20 +xs[2] = 30 +xs[3] = 40 + +total = 0 +k = 0 +while k < len(xs): + total = total + xs[k] + k = k + 1 + +print(total) +print(len(xs)) diff --git a/test_codes_python/test_1b_fsum.py b/test_codes_python/Loops/test_1b_fsum.py similarity index 51% rename from test_codes_python/test_1b_fsum.py rename to test_codes_python/Loops/test_1b_fsum.py index 4df8c9c..cdf65f9 100644 --- a/test_codes_python/test_1b_fsum.py +++ b/test_codes_python/Loops/test_1b_fsum.py @@ -1,13 +1,11 @@ -"""Plain-CPython equivalent of test_codes_lithon/test_1b_fsum.py. +"""Plain-CPython equivalent of test_codes_lithon/Loops/test_1b_fsum.lio. -Same program -- accumulate i over range(1_000_000_000) and print the sum. +Same program -- accumulate i over range(1,000,000,000) and print the sum. The only difference is the Lithon `int[64]` width annotations, which CPython's int already provides natively and has no syntax for. - - CPython ~188 s Β· Lithon native ~1.3 s """ i = 0 j = 0 for i in range(1000000000): j += i -print(j) \ No newline at end of file +print(j) diff --git a/test_codes_python/test_1b_fsum_simd.py b/test_codes_python/Loops/test_1b_fsum_simd.py similarity index 69% rename from test_codes_python/test_1b_fsum_simd.py rename to test_codes_python/Loops/test_1b_fsum_simd.py index ef33142..23be293 100644 --- a/test_codes_python/test_1b_fsum_simd.py +++ b/test_codes_python/Loops/test_1b_fsum_simd.py @@ -1,4 +1,4 @@ -"""Plain-CPython equivalent of test_codes_lithon/test_1b_fsum_simd.py. +"""Plain-CPython equivalent of test_codes_lithon/Loops/test_1b_fsum_simd.lio. Same program: 1e9 iterations of a float[64] accumulator, adding 0.5 each time. Lithon runs this through the auto-vectorizer's AVX2 path. @@ -7,4 +7,4 @@ j = 0.0 for i in range(1000000000): j += 0.5 -print(j) \ No newline at end of file +print(j) diff --git a/test_codes_python/test_1b_loop.py b/test_codes_python/Loops/test_1b_loop.py similarity index 63% rename from test_codes_python/test_1b_loop.py rename to test_codes_python/Loops/test_1b_loop.py index 437c1fb..2a78796 100644 --- a/test_codes_python/test_1b_loop.py +++ b/test_codes_python/Loops/test_1b_loop.py @@ -1,8 +1,8 @@ -"""Plain-CPython equivalent of test_codes_lithon/test_1b_loop.py. +"""Plain-CPython equivalent of test_codes_lithon/Loops/test_1b_loop.lio. Same program: a 1e9-iteration while loop that increments a counter and prints nothing -- measures pure loop overhead. """ i = 0 while i < 1000000000: - i = i + 1 \ No newline at end of file + i = i + 1 diff --git a/test_codes_python/Loops/test_1b_loop_print.py b/test_codes_python/Loops/test_1b_loop_print.py new file mode 100644 index 0000000..2c8f2dd --- /dev/null +++ b/test_codes_python/Loops/test_1b_loop_print.py @@ -0,0 +1,10 @@ +"""Plain-CPython equivalent of test_codes_lithon/Loops/test_1b_loop_print.lio. + +Same program as Loops/test_1b_loop.py, but printing the counter each iteration. +This one is dominated by output, and is kept because the difference between +the two isolates loop cost from print cost. +""" +i = 0 +while i < 1000000000: + i = i + 1 + print(i) diff --git a/test_codes_python/test_1k_fsum.py b/test_codes_python/Loops/test_1k_fsum.py similarity index 70% rename from test_codes_python/test_1k_fsum.py rename to test_codes_python/Loops/test_1k_fsum.py index e8a5a0a..9222a2f 100644 --- a/test_codes_python/test_1k_fsum.py +++ b/test_codes_python/Loops/test_1k_fsum.py @@ -1,4 +1,4 @@ -"""Plain-CPython equivalent of test_codes_lithon/test_1k_fsum.py. +"""Plain-CPython equivalent of test_codes_lithon/Loops/test_1k_fsum.lio. Same program over 1,000 iterations -- tiny workload, used for checking the benchmark UI and video recorder rather than for real speedup numbers. @@ -7,4 +7,4 @@ j = 0 for i in range(1000): j += i -print(j) \ No newline at end of file +print(j) diff --git a/test_codes_python/test_eulars_constant_lim_def.py b/test_codes_python/Loops/test_eulars_constant_lim_def.py similarity index 86% rename from test_codes_python/test_eulars_constant_lim_def.py rename to test_codes_python/Loops/test_eulars_constant_lim_def.py index aa258a5..ee2a331 100644 --- a/test_codes_python/test_eulars_constant_lim_def.py +++ b/test_codes_python/Loops/test_eulars_constant_lim_def.py @@ -1,5 +1,5 @@ """Plain-CPython equivalent of -test_codes_lithon/test_eulars_constant_lim_def.py. +test_codes_lithon/Loops/test_eulars_constant_lim_def.lio. Same program: approximate e via the limit definition (1 + 1/k)**k, built up as an explicit nested product since Lithon has no `**` operator, keeping the @@ -20,4 +20,4 @@ if e > le: le = e -print(le) \ No newline at end of file +print(le) diff --git a/test_codes_python/test_float.py b/test_codes_python/Loops/test_float.py similarity index 76% rename from test_codes_python/test_float.py rename to test_codes_python/Loops/test_float.py index c5dfe85..022c560 100644 --- a/test_codes_python/test_float.py +++ b/test_codes_python/Loops/test_float.py @@ -1,4 +1,4 @@ -"""Plain-CPython equivalent of test_codes_lithon/test_float.py. +"""Plain-CPython equivalent of test_codes_lithon/Loops/test_float.lio. Same program: a zero-argument function returning float[64] that adds 1.0 to a local, called three times. CPython's float is already binary64, so only @@ -14,4 +14,4 @@ def f(): print(f()) print(f()) -print(f()) \ No newline at end of file +print(f()) diff --git a/test_codes_python/test_multi_ops.py b/test_codes_python/Loops/test_multi_ops.py similarity index 72% rename from test_codes_python/test_multi_ops.py rename to test_codes_python/Loops/test_multi_ops.py index 54f34cf..b889875 100644 --- a/test_codes_python/test_multi_ops.py +++ b/test_codes_python/Loops/test_multi_ops.py @@ -1,4 +1,4 @@ -"""Plain-CPython equivalent of test_codes_lithon/test_multi_ops.py. +"""Plain-CPython equivalent of test_codes_lithon/Loops/test_multi_ops.lio. Same program: 1e9 iterations of mixed integer arithmetic -- a multiply, a bitwise xor, and a right shift -- accumulated into a total. @@ -11,4 +11,4 @@ total += (i * 31) ^ (i >> 3) i += 1 -print(total) \ No newline at end of file +print(total) diff --git a/test_codes_python/NewTypes/new_type.py b/test_codes_python/NewTypes/new_type.py new file mode 100644 index 0000000..1a800d1 --- /dev/null +++ b/test_codes_python/NewTypes/new_type.py @@ -0,0 +1,19 @@ +"""Plain-CPython equivalent of test_codes_lithon/NewTypes/new_type.lio. + +Same program: a type function that takes a list of widths and produces a +concrete container type, then a value of that type is declared and printed. + +Lithon's type functions are compile-time type constructors -- they expand +structurally into the underlying type. CPython has no such construct, so the +portable subset keeps the *shape* of the program: the same field names, the +same nested list/tuple structure, the same printed result. +""" +def custom(a, b, c, d, e): + # Lithon: dict[str[e], list[tuple[int[d], a], b], c] + # The widths are values here, not types, so the same nested shape is + # built explicitly. + return {"test": [(1, 2, 3, 4), (4, 3, 2, 1)]} + + +t = custom(4, 2, 1, 32, 16) +print(t) diff --git a/test_codes_python/Pointers/check_pointers.py b/test_codes_python/Pointers/check_pointers.py new file mode 100644 index 0000000..1fd73c2 --- /dev/null +++ b/test_codes_python/Pointers/check_pointers.py @@ -0,0 +1,26 @@ +"""Plain-CPython equivalent of test_codes_lithon/Pointers/check_pointers.lio. + +Same program: take the address of a variable, read the value back, walk the +pointer by a fixed offset, alias it through a second pointer, and watch the +original change through the alias. + +CPython has no raw addresses, so the portable subset models the *shape* of +the program: a one-element cell stands in for the pointed-to storage, and +reads/writes go through that cell. The two addresses the Lithon program +prints are host-dependent and have no CPython equivalent, so they are +dropped; every deterministic value matches. +""" +i = 16 + +# Lithon: _ptrI: ptr[int[16]] = addressof(i) +# A one-element cell stands in for the pointee's storage. +_ptrI = [i] + +print(_ptrI[0]) # 16 -- valueof(_ptrI) +print(0) # 0 -- valueof(_ptrI-100): memory before the cell +_ptrK = _ptrI # a second pointer to the same cell +t = _ptrI[0] # t: int[16] = i (a copy of the value) +i = 64 +_ptrI[0] = i # the alias sees the change: it is the same cell +print(t) # 16 -- the copy did not change +print(_ptrK[0]) # 64 -- valueof(_ptrK) diff --git a/test_codes_python/Pointers/pointer_error.py b/test_codes_python/Pointers/pointer_error.py new file mode 100644 index 0000000..828ceb7 --- /dev/null +++ b/test_codes_python/Pointers/pointer_error.py @@ -0,0 +1,16 @@ +"""Plain-CPython equivalent of test_codes_lithon/Pointers/pointer_error.lio. + +NOTE: in Lithon this file is a *pinned failure*. The engine refuses it on +purpose, so the naming rule stays visible: + + $ lithon test_codes_lithon/Pointers/pointer_error.lio + LITHON-E0122: a pointer is named with a leading underscore, + write `_ptrI: ptr[...]` or pick a name that starts with '_' (4.4) + +The corrected spelling is in Pointers/check_pointers.lio. CPython has no +pointer naming rule, so this file runs the corrected shape here to keep the +pair side by side. +""" +i = 22 +_ptrI = [i] +print(_ptrI[0]) diff --git a/test_codes_python/Structs/structs.py b/test_codes_python/Structs/structs.py new file mode 100644 index 0000000..b3eff37 --- /dev/null +++ b/test_codes_python/Structs/structs.py @@ -0,0 +1,22 @@ +"""Plain-CPython equivalent of test_codes_lithon/Structs/structs.lio. + +Same program: a struct with a bounded string field and an int field, then a +value of that struct with the fields assigned. + +CPython has no width annotations, so the field types are dropped and the +class body just declares the names. Lithon packs the fields with no +padding; the field values are the same either way. +""" + + +class LithonStruct: + name: str + year: int + + +Lithon = LithonStruct() +Lithon.name = "Lithon" +Lithon.year = 2026 + +print(Lithon.name) +print(Lithon.year) diff --git a/test_codes_python/__pycache__/base_var.cpython-312.pyc b/test_codes_python/__pycache__/base_var.cpython-312.pyc deleted file mode 100644 index c16a55c30dfd10f5ab57268100e40614c7e5b91c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 515 zcmYjOy-or_5Z>iP@FKm1#abB>4m%4%Y-o)kF*f9q%fgPZc(;4*E|}|Rd;c9pHZ${m^Ucq`H5ws7;~%fnb4tjk4|W!8o%Jy~PehOb5p+N+1S&vZ zknN3N+qNkvj;4~&=ommm0(IU6rVwhackvRG3i=ZFO_SDy9p-T zdLCCwJMN@bkydH36)zJlW;JsYFS+9|X_+uO&q4S`p+|Q^AY^5hqsFEwQlhA=7jP?; zbP#QT%x`UGDR{NZa^F+i4!``tuA0HJmLx)tl4MEcvRY34Ox7O!>GnGDjRj+42WoZ~ z_l=&y$k}+Kg4w@2=H;E^e|KEgyO|c)_QD*XnExSrgwHCVlzvyLbZ>P?s{0H2PO1z3 JST{8Ue*yc`q7?uD diff --git a/test_codes_python/__pycache__/find_e.cpython-312.pyc b/test_codes_python/__pycache__/find_e.cpython-312.pyc deleted file mode 100644 index b08ba705acb792f7a5c1b5cfbe6ec0e0d1d3369f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 763 zcmYjP&ubGw6n?WmlI_N%37)M}XsH2B8xSPHs-e9prBFN=0?U$_B-?Iw+u4nh(8A&m zNVJD|Qg3<`{}S;a;w9|CiwEf~wKq?`S=-=)dA#?&dEcA&F<-J-6VU7*pLf3!fS)qC zF#S9@b1`@W4C=thbEp%h)D@=IRjx8^PXQ1MOi_Rn%=H1@{e(=?1{r|5#ucu~YF)X& z|LY{j3)rc1<)Vi`KUGW*K5Vc!EsO>T2b8xmK5IpF)VaaTsFPUtLB7lnL>?S&tiX4$cV9MSm zijzOYcz|O5s|`@_BbcG&81Wq_s$0JjCYatmk<1^VsKl!4bYrb$H^V?qp!835quO{F za1k^@?)CN>+rr=F2sa)iLSS_!&3+8e@fRHPn$jG^O6;lnPtzs%p zTNd-1mX(Z%36F^rYf|)BPjnRWX9@Z7gnU43-0u3U=khzEfT`R*c!X}M5<*TDjcC7f zI>}G-pc@gHXga!7dbsqogKvI)B|)m`e+42$mb`2P)*ok3~_xT4uUW{h>*d_Bd;M-UF6!7;CksSpuyV zupmg2G-j5A05#znG?RP$a@X&4I?K}pCPVBJsAD4z1)KK}9VO!oLNOe2&1HZTrmWv9 z)nOO$Pu8yQ?81QvZGG%4% diff --git a/test_codes_python/__pycache__/test_1b_fsum_simd.cpython-312.pyc b/test_codes_python/__pycache__/test_1b_fsum_simd.cpython-312.pyc deleted file mode 100644 index ccef8b65acf7cedcb75a78b1bdd01986cb86efe3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 540 zcmZ8eziSjh6rR~1x!lUxXce33Bw$t}MlgniBMDfD9EcLm3(L6kZg(!bJL~)i+*OxC zNReLfKM?;C6)YugA&QmVNjfWME{TO-@#cGP-uK>{H=jD42wCiX`!KqW5&G$zOZXS$ zY{!x}h@n1WxR06F_dEnX^WR&A!w%YSA=JmoRC7@&SBR2fA*g%ozucKE8c;;eFqFvK%(8 zBw->=k_PhzkD5@@DuX7hC9m{5bml~VcDL6D&lE^C&`_C!K~IX2ZKnq3W7+7xt<>|a zok#xLO0&383T6t}lviw3_cLnEnwgI={_O=gm_;aDnBvo*HN{`sSC1!O+Dp^!X%HS? WJ9>1~o%txb@dJfZ`g&2et@1A?L7t`n diff --git a/test_codes_python/__pycache__/test_1b_loop.cpython-312.pyc b/test_codes_python/__pycache__/test_1b_loop.cpython-312.pyc deleted file mode 100644 index 9ee0cb77770a4a4dff0f077165e2e978bf5a4945..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 448 zcmYjM%Sr<=6rJ=n?P&iY+ZI~s(kf!Eb9i`S=8!U6m=0G zn&}%&ZW@#w8SNj3Za0RxVXl%NJ$6FEc&*(T>OgP|7YQ4X5V*$T3~Nx@^92Rvg)G&# zR!rm63x$a5@v!bVMj*Ib833VmXP%a3*7N<&92s<2$;@oeqr&&R z8DbCCGSxr#+fDaafmE&rp7h<06zAY;|yOE$t2z#<*0@G8f sYSto)5c5b~T-wI|1&#`dW&i*H diff --git a/test_codes_python/__pycache__/test_1b_loop_print.cpython-312.pyc b/test_codes_python/__pycache__/test_1b_loop_print.cpython-312.pyc deleted file mode 100644 index 774588d1d79be8cccaa5debef1bde6f8701e48d8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 569 zcmZ8eO=}cE5bd7b$xa4y(jQPB6XHs)DuECb4?z};Aj`tgJ6)UY>`eFAU6U|51yNYR zyWl?v{u1>M#65WNl-o*fp6s5DIar78*Y#d~@cR44M#_A={`vLzDQE0gc>Igb4Y=O* z;1iQ<$RrX6s%aa9mVujL zMKH!()DGK*QcWgkQRnE>bc(3cFo#Z!mz%=3aS+z_lO4gtnsT*J`|hV#!6n7MeSUlu zEPJupS@g0@nmo%ETrJ{u`3rU(WPhIRKN`JqXx)fV*R#>Unq$n#jSgCyeD>D3sx@|% z`QNxj@}xAfE^ya|FAsCwjz5+i&iU^s=H2BT7O$Q2%eCHo>#e>_*5`X4;`6;9-SzqX Rch8qG>m~E8_u3}D>M!!)uu=d3 diff --git a/test_codes_python/__pycache__/test_1k_fsum.cpython-312.pyc b/test_codes_python/__pycache__/test_1k_fsum.cpython-312.pyc deleted file mode 100644 index fffaf00f6cb106bf21bc6b9103d08b092cd162cb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 519 zcmYLGziS&Y7?tiv5?^s4L#Hg?8i*YSI+aoq5(*xvQ1X|AOx@ZW3Z+Z4xn%1Uo!h1lBz@o0dmrfOrrnN+!`pAylQt#fN7MX+|F^h1 zaK%T$$$)S=pl*2{L7#h9&e7n29Ip~Gpy(kzYrD#O-wKG+1o)t#Wae)MXP^5y6SA}4 zpV?GNz|%@hS&q_z8i7S)<3w>ZaW0zs?Ip95#UoP{-E!8AqC-|7lv<57D?m+A!_HQ( z*8^cuGb@xd(CL5`at7~|&T_^07E}f~jFg5X#UvAQ3^v8+1Z9#IOlNTP3Yg?D6&w|4 zOqAwsu$z~3qP@8Ua+aw@qxp772 zrE-3P%3IvPDP&wAQcNYEd_~k+o1=E8;Crt8sOCyUX zjN?o@Q6Sn_AaNYe>Azzh3EhK&r0`uxSRkU#xvL;pD=laVqBn>n)L{9uJ$uwRTpQBSw0(5_C^>n0{POtLr|H}KbX*{n g8()j1;l0=WkzLu>#x}8L&t?cU2ODq7aRvMT24RTpmH+?% diff --git a/test_codes_python/__pycache__/test_float.cpython-312.pyc b/test_codes_python/__pycache__/test_float.cpython-312.pyc deleted file mode 100644 index d9bd64652457c0094e9522d410b4ff0108411443..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 673 zcmbVK!D*{y^g5#go;u8^Nmu_2yMo*Q0pM*KRi=80`C(lZTX$AHKMXwG~+; zNInrxjtQry{*i=Aa^w_ZJ9x#{*qqmtEms2k>Dplqr${OKHqof)` zv-@~==Lxt8bHF&aa3{VEPDc!Kow2+R2)W?+zz6|OPKAwOb>L0AQ~+s#gk*4%&_+~}eZJdU!ZZ%YjD6Jjo|BcR>%4S9_?!^*QG z(hCm9SYTWnd?r0w(^u9z55M;Zd&wg!j7^+S<+EgH^ohuvO&&CMVfU|rMPu-?b7f#n zM`j&`HZbiCggH6yY|ihUcQ!r@KDLo;Zq4s6a6k@wVckwsuCp|4cIsAPq;kezZrUDN z2zBS~RP!u=e+>@@mDTK|yuQKR+;)8DfV diff --git a/test_codes_python/__pycache__/test_multi_ops.cpython-312.pyc b/test_codes_python/__pycache__/test_multi_ops.cpython-312.pyc deleted file mode 100644 index 48d7f41cf34d0fd38ad9a1854e2178aa6d48f924..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 587 zcmYjOOKTKC5U!q`*~gF-;z7vCj<?~D+sVxI*xjSqJ&lU6 zf(LI#|AF{Ne4KI%a}dE(ZX0s*WcNga1yx;L_0?Bh)!)0_5Uf1<@pbzO0r=%E|HS|6 z96z?sd*CnxPKJbgL(gghJtR9GfLcTA^Ts>?u%|!o0L)jcb^#oU2es#&;>p`J575WP zXWP*Ufmw5-HUBb5n8lt^;C>ajgRBPRxoqIC@sQB!;*d|;M>P*his z@_Jb(k^h%bwXdg=7YW`_XDp@bt{+&lY!E_@JfHZ>5Q4KMIq(B(+MP1^*=#M=U*dt^ xS+I8(-i-HF_t=MX`(3*4)AF{(EmmLLDQ_+VXtfvXukff1q*>zY6=i=L{s63Lt`YzM diff --git a/test_codes_python/test_1b_loop_print.py b/test_codes_python/test_1b_loop_print.py deleted file mode 100644 index 4033028..0000000 --- a/test_codes_python/test_1b_loop_print.py +++ /dev/null @@ -1,10 +0,0 @@ -"""Plain-CPython equivalent of test_codes_lithon/test_1b_loop_print.py. - -Same program as test_1b_loop.py, but printing the counter each iteration. -This one is dominated by output, and is kept because the difference between -the two isolates loop cost from print cost. -""" -i = 0 -while i < 1000000000: - i = i + 1 - print(i) \ No newline at end of file diff --git a/uv.lock b/uv.lock index 9418af2..626420c 100644 --- a/uv.lock +++ b/uv.lock @@ -4,5 +4,5 @@ requires-python = ">=3.10" [[package]] name = "lithon" -version = "0.8.0" +version = "0.9.8" source = { editable = "." } diff --git a/web/apps/web/public/benchmarks/manifest.json b/web/apps/web/public/benchmarks/manifest.json index 7ee0905..1560eb1 100644 --- a/web/apps/web/public/benchmarks/manifest.json +++ b/web/apps/web/public/benchmarks/manifest.json @@ -13,9 +13,9 @@ "stdout": "2.7169239322355985\n", "returncode": 0, "command": [ - "test_codes_python/test_eulars_constant_lim_def.py" + "test_codes_python/Loops/test_eulars_constant_lim_def.py" ], - "script": "test_codes_python/test_eulars_constant_lim_def.py" + "script": "test_codes_python/Loops/test_eulars_constant_lim_def.py" }, { "name": "Lithon", @@ -23,9 +23,9 @@ "stdout": "2.7169239322355985\n", "returncode": 0, "command": [ - "test_codes_lithon/test_eulars_constant_lim_def.py" + "test_codes_lithon/Loops/test_eulars_constant_lim_def.lio" ], - "script": "test_codes_lithon/test_eulars_constant_lim_def.py" + "script": "test_codes_lithon/Loops/test_eulars_constant_lim_def.lio" } ] }, @@ -43,9 +43,9 @@ "stdout": "499999999500000000\n", "returncode": 0, "command": [ - "test_codes_python/test_1b_fsum.py" + "test_codes_python/Loops/test_1b_fsum.py" ], - "script": "test_codes_python/test_1b_fsum.py" + "script": "test_codes_python/Loops/test_1b_fsum.py" }, { "name": "Lithon", @@ -53,9 +53,9 @@ "stdout": "499999999500000000\n", "returncode": 0, "command": [ - "test_codes_lithon/test_1b_fsum.py" + "test_codes_lithon/Loops/test_1b_fsum.lio" ], - "script": "test_codes_lithon/test_1b_fsum.py" + "script": "test_codes_lithon/Loops/test_1b_fsum.lio" } ] }, @@ -73,9 +73,9 @@ "stdout": "499999999500000000\n", "returncode": 0, "command": [ - "test_codes_python/test_1b_fsum.py" + "test_codes_python/Loops/test_1b_fsum.py" ], - "script": "test_codes_python/test_1b_fsum.py" + "script": "test_codes_python/Loops/test_1b_fsum.py" }, { "name": "Lithon", @@ -83,9 +83,9 @@ "stdout": "499999999500000000\n", "returncode": 0, "command": [ - "test_codes_lithon/test_1b_fsum.py" + "test_codes_lithon/Loops/test_1b_fsum.lio" ], - "script": "test_codes_lithon/test_1b_fsum.py" + "script": "test_codes_lithon/Loops/test_1b_fsum.lio" } ] }, @@ -103,9 +103,9 @@ "stdout": "499999999500000000\n", "returncode": 0, "command": [ - "test_codes_python/test_1b_fsum.py" + "test_codes_python/Loops/test_1b_fsum.py" ], - "script": "test_codes_python/test_1b_fsum.py" + "script": "test_codes_python/Loops/test_1b_fsum.py" }, { "name": "Lithon", @@ -113,9 +113,9 @@ "stdout": "499999999500000000\n", "returncode": 0, "command": [ - "test_codes_lithon/test_1b_fsum.py" + "test_codes_lithon/Loops/test_1b_fsum.lio" ], - "script": "test_codes_lithon/test_1b_fsum.py" + "script": "test_codes_lithon/Loops/test_1b_fsum.lio" } ] }, @@ -133,9 +133,9 @@ "stdout": "499999999500000000\n", "returncode": 0, "command": [ - "test_codes_python/test_1b_fsum.py" + "test_codes_python/Loops/test_1b_fsum.py" ], - "script": "test_codes_python/test_1b_fsum.py" + "script": "test_codes_python/Loops/test_1b_fsum.py" }, { "name": "Lithon", @@ -143,9 +143,9 @@ "stdout": "499999999500000000\n", "returncode": 0, "command": [ - "test_codes_lithon/test_1b_fsum.py" + "test_codes_lithon/Loops/test_1b_fsum.lio" ], - "script": "test_codes_lithon/test_1b_fsum.py" + "script": "test_codes_lithon/Loops/test_1b_fsum.lio" } ] }, @@ -163,9 +163,9 @@ "stdout": "499999999500000000\n", "returncode": 0, "command": [ - "test_codes_python/test_1b_fsum.py" + "test_codes_python/Loops/test_1b_fsum.py" ], - "script": "test_codes_python/test_1b_fsum.py" + "script": "test_codes_python/Loops/test_1b_fsum.py" }, { "name": "Lithon", @@ -173,9 +173,9 @@ "stdout": "499999999500000000\n", "returncode": 0, "command": [ - "test_codes_lithon/test_1b_fsum.py" + "test_codes_lithon/Loops/test_1b_fsum.lio" ], - "script": "test_codes_lithon/test_1b_fsum.py" + "script": "test_codes_lithon/Loops/test_1b_fsum.lio" } ] }, @@ -193,9 +193,9 @@ "stdout": "499500\n", "returncode": 0, "command": [ - "test_codes_python/test_1k_fsum.py" + "test_codes_python/Loops/test_1k_fsum.py" ], - "script": "test_codes_python/test_1k_fsum.py" + "script": "test_codes_python/Loops/test_1k_fsum.py" }, { "name": "Lithon", @@ -203,9 +203,9 @@ "stdout": "499500\n", "returncode": 0, "command": [ - "test_codes_lithon/test_1k_fsum.py" + "test_codes_lithon/Loops/test_1k_fsum.lio" ], - "script": "test_codes_lithon/test_1k_fsum.py" + "script": "test_codes_lithon/Loops/test_1k_fsum.lio" } ] } diff --git a/web/apps/web/public/manifest.json b/web/apps/web/public/manifest.json index 078ef50..50971e3 100644 --- a/web/apps/web/public/manifest.json +++ b/web/apps/web/public/manifest.json @@ -1,25 +1,24 @@ { - "short_name": "TanStack App", - "name": "Create TanStack App Sample", + "name": "Lithon", + "short_name": "Lithon", + "description": "A zero-dependency, dual-tier execution engine that runs statically typed Python as native x86-64.", "icons": [ { - "src": "favicon.ico", - "sizes": "64x64 32x32 24x24 16x16", - "type": "image/x-icon" - }, - { - "src": "logo192.png", - "type": "image/png", - "sizes": "192x192" + "src": "favicon.svg", + "sizes": "any", + "type": "image/svg+xml", + "purpose": "any" }, { - "src": "logo512.png", - "type": "image/png", - "sizes": "512x512" + "src": "favicon.ico", + "sizes": "64x64 32x32 24x32 16x16", + "type": "image/x-icon" } ], "start_url": ".", + "scope": ".", "display": "standalone", - "theme_color": "#000000", - "background_color": "#ffffff" + "orientation": "any", + "theme_color": "#121212", + "background_color": "#121212" } diff --git a/web/apps/web/public/robots.txt b/web/apps/web/public/robots.txt index 2ce9f09..da07271 100644 --- a/web/apps/web/public/robots.txt +++ b/web/apps/web/public/robots.txt @@ -2,4 +2,4 @@ User-agent: * Allow: / Disallow: /api/ -Sitemap: https://lithon-lang.pages.dev/sitemap.xml +Sitemap: https://project-lithon.github.io/Lithon/sitemap.xml diff --git a/web/apps/web/public/sitemap.xml b/web/apps/web/public/sitemap.xml index 07fbe25..3e65939 100644 --- a/web/apps/web/public/sitemap.xml +++ b/web/apps/web/public/sitemap.xml @@ -1,182 +1,202 @@ - https://lithon-lang.pages.dev/ + https://project-lithon.github.io/Lithon/ weekly 1.0 - https://lithon-lang.pages.dev/docs + https://project-lithon.github.io/Lithon/docs weekly 0.9 - https://lithon-lang.pages.dev/playground + https://project-lithon.github.io/Lithon/playground weekly 0.7 - https://lithon-lang.pages.dev/benchmarks + https://project-lithon.github.io/Lithon/benchmarks weekly 0.7 - https://lithon-lang.pages.dev/roadmap + https://project-lithon.github.io/Lithon/roadmap weekly 0.6 - https://lithon-lang.pages.dev/use-cases + https://project-lithon.github.io/Lithon/use-cases weekly 0.6 - https://lithon-lang.pages.dev/contributors + https://project-lithon.github.io/Lithon/roadmap/language weekly 0.6 - https://lithon-lang.pages.dev/roadmap/language + https://project-lithon.github.io/Lithon/roadmap/phase-1-dual-tier weekly 0.6 - https://lithon-lang.pages.dev/roadmap/phase-1-dual-tier + https://project-lithon.github.io/Lithon/roadmap/phase-2-aot weekly 0.6 - https://lithon-lang.pages.dev/roadmap/phase-2-aot + https://project-lithon.github.io/Lithon/roadmap/phase-3-systems weekly 0.6 - https://lithon-lang.pages.dev/roadmap/phase-3-systems + https://project-lithon.github.io/Lithon/roadmap/verification weekly 0.6 - https://lithon-lang.pages.dev/roadmap/verification + https://project-lithon.github.io/Lithon/docs/welcome weekly - 0.6 + 0.8 + + + https://project-lithon.github.io/Lithon/docs/hello-world + weekly + 0.8 + + + https://project-lithon.github.io/Lithon/docs/installation + weekly + 0.8 + + + https://project-lithon.github.io/Lithon/docs/mental-model + weekly + 0.8 + + + https://project-lithon.github.io/Lithon/docs/types + weekly + 0.8 - https://lithon-lang.pages.dev/docs/welcome + https://project-lithon.github.io/Lithon/docs/declarations weekly 0.8 - https://lithon-lang.pages.dev/docs/hello-world + https://project-lithon.github.io/Lithon/docs/control-flow weekly 0.8 - https://lithon-lang.pages.dev/docs/installation + https://project-lithon.github.io/Lithon/docs/functions weekly 0.8 - https://lithon-lang.pages.dev/docs/mental-model + https://project-lithon.github.io/Lithon/docs/operators weekly 0.8 - https://lithon-lang.pages.dev/docs/types + https://project-lithon.github.io/Lithon/docs/containers weekly 0.8 - https://lithon-lang.pages.dev/docs/declarations + https://project-lithon.github.io/Lithon/docs/pointers weekly 0.8 - https://lithon-lang.pages.dev/docs/control-flow + https://project-lithon.github.io/Lithon/docs/structs weekly 0.8 - https://lithon-lang.pages.dev/docs/functions + https://project-lithon.github.io/Lithon/docs/enums weekly 0.8 - https://lithon-lang.pages.dev/docs/operators + https://project-lithon.github.io/Lithon/docs/methods weekly 0.8 - https://lithon-lang.pages.dev/docs/containers + https://project-lithon.github.io/Lithon/docs/type-functions weekly 0.8 - https://lithon-lang.pages.dev/docs/pointers + https://project-lithon.github.io/Lithon/docs/syscalls weekly 0.8 - https://lithon-lang.pages.dev/docs/architecture + https://project-lithon.github.io/Lithon/docs/architecture weekly 0.8 - https://lithon-lang.pages.dev/docs/dual-tier + https://project-lithon.github.io/Lithon/docs/dual-tier weekly 0.8 - https://lithon-lang.pages.dev/docs/typechecking + https://project-lithon.github.io/Lithon/docs/typechecking weekly 0.8 - https://lithon-lang.pages.dev/docs/pipeline + https://project-lithon.github.io/Lithon/docs/pipeline weekly 0.8 - https://lithon-lang.pages.dev/docs/simd + https://project-lithon.github.io/Lithon/docs/simd weekly 0.8 - https://lithon-lang.pages.dev/docs/errors + https://project-lithon.github.io/Lithon/docs/errors weekly 0.8 - https://lithon-lang.pages.dev/docs/builtins + https://project-lithon.github.io/Lithon/docs/builtins weekly 0.8 - https://lithon-lang.pages.dev/docs/semantics + https://project-lithon.github.io/Lithon/docs/semantics weekly 0.8 - https://lithon-lang.pages.dev/docs/verification + https://project-lithon.github.io/Lithon/docs/verification weekly 0.8 - https://lithon-lang.pages.dev/docs/limits + https://project-lithon.github.io/Lithon/docs/limits weekly 0.8 - https://lithon-lang.pages.dev/docs/source-to-machine + https://project-lithon.github.io/Lithon/docs/source-to-machine weekly 0.8 - https://lithon-lang.pages.dev/docs/registers-abi + https://project-lithon.github.io/Lithon/docs/registers-abi weekly 0.8 - https://lithon-lang.pages.dev/docs/instruction-encoding + https://project-lithon.github.io/Lithon/docs/instruction-encoding weekly 0.8 diff --git a/web/apps/web/src/docs/generated/ir.ts b/web/apps/web/src/docs/generated/ir.ts index 849161e..5e3052f 100644 --- a/web/apps/web/src/docs/generated/ir.ts +++ b/web/apps/web/src/docs/generated/ir.ts @@ -2,66 +2,52 @@ // Every entry is real output from src/frontend/frontend.py. export const EXAMPLE_IR: Record = { - "47db5edf": - "function __main__():\r\nblock0:\r\n %0 = const_i64 10\r\n store x, %0 : int[64]\r\n %1 = const_i64 20\r\n store y, %1 : int[64]\r\n %2 = load x\r\n %3 = load y\r\n %4 = add %2, %3\r\n store result, %4 : int[64]\r\n %5 = load result\r\n call print, %5\r\n return", - "39ae587c": - "function __main__():\r\nblock0:\r\n %0 = const_i64 10\r\n store x, %0 : int[64]\r\n %1 = const_i64 20\r\n store y, %1 : int[64]\r\n %2 = load x\r\n %3 = load y\r\n %4 = add %2, %3\r\n store z, %4 : int[64]\r\n %5 = load z\r\n call print, %5\r\n %6 = load x\r\n %7 = load y\r\n %8 = sub %6, %7\r\n call print, %8\r\n %9 = load x\r\n %10 = load y\r\n %11 = mul %9, %10\r\n call print, %11\r\n return", - cac171bf: - "function __main__():\r\nblock0:\r\n %0 = const_i64 10\r\n store x, %0\r\n %1 = load x\r\n call print, %1\r\n return", - "2a8807c9": - "function __main__():\r\nblock0:\r\n %0 = const_i64 7\r\n store i8, %0 : int[8]\r\n %1 = const_i64 7\r\n store i16, %1 : int[16]\r\n %2 = const_i64 7\r\n store i32, %2 : int[32]\r\n %3 = const_i64 7\r\n store i64, %3 : int[64]\r\n %4 = const_f64 0.5\r\n store f64, %4 : float[64]\r\n %5 = const_bool 1\r\n store b, %5 : bool\r\n %6 = load i64\r\n call print, %6\r\n return", - "7cbe74a6": - "function __main__():\r\nblock0:\r\n %0 = const_i64 5\r\n store small, %0 : int[8]\r\n %1 = load small\r\n store big, %1 : int[64]\r\n %2 = load big\r\n call print, %2\r\n return", - "206f6bf0": - "function __main__():\r\nblock0:\r\n %0 = const_i64 5\r\n store wide, %0 : int[64]\r\n %1 = load wide\r\n store narrow, %1 : int[8]\r\n %2 = load narrow\r\n call print, %2\r\n return", - "1a324420": - "function __main__():\r\nblock0:\r\n %0 = const_i64 0\r\n store j, %0 : float[64]\r\n %1 = load j\r\n call print, %1\r\n return", - aeb1dcc6: - "function __main__():\r\nblock0:\r\n %0 = const_f64 0.0\r\n store j, %0 : float[64]\r\n %1 = load j\r\n call print, %1\r\n return", - "96262f13": - "function __main__():\r\nblock0:\r\n %0 = const_i64 10\r\n store i, %0 : int[64]\r\n %1 = const_f64 2.5\r\n store f, %1 : float[64]\r\n %2 = load i\r\n %3 = load f\r\n %4 = add %2, %3\r\n call print, %4\r\n %5 = load f\r\n %6 = load i\r\n %7 = add %5, %6\r\n call print, %7\r\n %8 = load i\r\n %9 = const_i64 4\r\n %10 = div %8, %9\r\n call print, %10\r\n return", - c2b675d3: - "function __main__():\r\nblock0:\r\n %0 = const_i64 10\r\n store x, %0 : int[64]\r\n %1 = const_i64 20\r\n store x, %1 : int[64]\r\n %2 = load x\r\n call print, %2\r\n return", - a78d2c85: - "function __main__():\r\nblock0:\r\n %0 = const_i64 10\r\n store x, %0 : int[64]\r\n %1 = const_i64 20\r\n store x, %1 : int[64]\r\n %2 = load x\r\n store narrow, %2 : int[8]\r\n %3 = load narrow\r\n call print, %3\r\n return", - e1f84f23: - "function __main__():\r\nblock0:\r\n %0 = const_i64 5\r\n store i, %0 : int[8]\r\n %1 = load i\r\n call print, %1\r\n return", - bba9661c: - "function __main__():\r\nblock0:\r\n %0 = load i\r\n call print, %0\r\n return", - f00439de: - "function __main__():\r\nblock0:\r\n %0 = const_i64 1\r\n store x, %0 : int[64]\r\n %1 = load x\r\n %2 = const_i64 3\r\n %3 = lt %1, %2\r\n branch %3, block1, block2\r\nblock1:\r\n %4 = const_i64 2\r\n store y, %4 : int[64]\r\n jump block2\r\nblock2:\r\n %5 = load y\r\n call print, %5\r\n return", - "9443a6ad": - "function __main__():\r\nblock0:\r\n %0 = const_i64 0\r\n store i, %0 : int[64]\r\n %1 = const_i64 0\r\n store total, %1 : int[64]\r\n jump block1\r\nblock1:\r\n %2 = load i\r\n %3 = const_i64 10\r\n %4 = lt %2, %3\r\n branch %4, block2, block3\r\nblock2:\r\n %5 = load total\r\n %6 = load i\r\n %7 = add %5, %6\r\n store total, %7 : int[64]\r\n %8 = load i\r\n %9 = load i\r\n %10 = const_i64 1\r\n %11 = add %9, %10\r\n store i, %11 : int[64]\r\n jump block1\r\nblock3:\r\n %12 = load total\r\n call print, %12\r\n return", - "311879c3": - "function __main__():\r\nblock0:\r\n %0 = const_i64 5\r\n store x, %0 : int[64]\r\n %1 = const_i64 2\r\n store y, %1 : int[64]\r\n %2 = load x\r\n %3 = load y\r\n %4 = gt %2, %3\r\n branch %4, block1, block2\r\nblock1:\r\n %5 = load x\r\n store __ifexpr0, %5\r\n jump block3\r\nblock2:\r\n %6 = load y\r\n store __ifexpr0, %6\r\n jump block3\r\nblock3:\r\n %7 = load __ifexpr0\r\n call print, %7\r\n %8 = load x\r\n %9 = load y\r\n %10 = lt %8, %9\r\n branch %10, block4, block5\r\nblock4:\r\n %11 = const_i64 1\r\n store __ifexpr1, %11\r\n jump block6\r\nblock5:\r\n %12 = load x\r\n %13 = const_i64 5\r\n %14 = eq %12, %13\r\n branch %14, block7, block8\r\nblock7:\r\n %15 = const_i64 2\r\n store __ifexpr2, %15\r\n jump block9\r\nblock8:\r\n %16 = const_i64 3\r\n store __ifexpr2, %16\r\n jump block9\r\nblock9:\r\n %17 = load __ifexpr2\r\n store __ifexpr1, %17\r\n jump block6\r\nblock6:\r\n %18 = load __ifexpr1\r\n call print, %18\r\n return", - ace9c7cc: - "function add(a:int[64], b:int[64]):\r\nblock0:\r\n %0 = load a\r\n %1 = load b\r\n %2 = add %0, %1\r\n return %2\r\n return\r\n\r\nfunction __main__():\r\nblock0:\r\n %0 = const_i64 3\r\n %1 = const_i64 4\r\n %2 = call add, %0, %1\r\n store x, %2 : int[64]\r\n %3 = load x\r\n call print, %3\r\n return", - "8c105dca": - "function __main__():\r\nblock0:\r\n %0 = const_i64 -7\r\n store a, %0 : int[64]\r\n %1 = const_i64 3\r\n store b, %1 : int[64]\r\n %2 = load a\r\n %3 = load b\r\n %4 = mod %2, %3\r\n call print, %4\r\n %5 = const_i64 7\r\n %6 = const_i64 3\r\n %7 = mod %5, %6\r\n call print, %7\r\n %8 = const_i64 -7\r\n %9 = const_i64 3\r\n %10 = mod %8, %9\r\n call print, %10\r\n return", - c6fd2aba: - "function __main__():\r\nblock0:\r\n %0 = const_i64 1\r\n store a, %0 : int[64]\r\n %1 = const_i64 2\r\n store b, %1 : int[64]\r\n %2 = const_f64 1.5\r\n store c, %2 : float[64]\r\n %3 = load a\r\n %4 = load b\r\n %5 = lt %3, %4\r\n call print, %5\r\n %6 = load b\r\n %7 = load a\r\n %8 = gt %6, %7\r\n call print, %8\r\n %9 = load a\r\n %10 = load a\r\n %11 = eq %9, %10\r\n call print, %11\r\n %12 = load c\r\n %13 = load b\r\n %14 = lt %12, %13\r\n call print, %14\r\n return", - "5ba53d35": - "function __main__():\r\nblock0:\r\n %0 = const_f64 2.5\r\n %1 = const_i64 1\r\n %2 = band %0, %1\r\n call print, %2\r\n return", - f8490a90: - "function __main__():\r\nblock0:\r\n %0 = const_i64 1\r\n %1 = const_i64 64\r\n %2 = shl %0, %1\r\n call print, %2\r\n return", - "349eae74": - "function __main__():\r\nblock0:\r\n %0 = const_i64 1\r\n %1 = const_i64 10\r\n %2 = shl %0, %1\r\n store x, %2 : int[64]\r\n %3 = load x\r\n %4 = const_i64 3\r\n %5 = shr %3, %4\r\n store y, %5 : int[64]\r\n %6 = load x\r\n %7 = const_i64 255\r\n %8 = band %6, %7\r\n store z, %8 : int[64]\r\n %9 = load x\r\n call print, %9\r\n %10 = load y\r\n call print, %10\r\n %11 = load z\r\n call print, %11\r\n return", - "6666db5c": - "function __main__():\r\nblock0:\r\n %0 = const_i64 9223372036854775807\r\n store x, %0 : int[64]\r\n %1 = const_i64 1\r\n store y, %1 : int[64]\r\n %2 = load x\r\n %3 = load y\r\n %4 = wrapadd %2, %3\r\n store z, %4 : int[64]\r\n %5 = load z\r\n call print, %5\r\n %6 = const_i64 -9223372036854775808\r\n store a, %6 : int[64]\r\n %7 = const_i64 1\r\n store b, %7 : int[64]\r\n %8 = load a\r\n %9 = load b\r\n %10 = wrapsub %8, %9\r\n store c, %10 : int[64]\r\n %11 = load c\r\n call print, %11\r\n %12 = const_i64 3037000500\r\n store p, %12 : int[64]\r\n %13 = const_i64 3037000500\r\n store q, %13 : int[64]\r\n %14 = load p\r\n %15 = load q\r\n %16 = wrapmul %14, %15\r\n store r, %16 : int[64]\r\n %17 = load r\r\n call print, %17\r\n return", - fe363d56: - "function __main__():\r\nblock0:\r\n store xs : list[int[64], 6]\r\n %0 = const_i64 0\r\n %1 = const_i64 10\r\n IndexStore xs, %0, %1\r\n %2 = const_i64 1\r\n %3 = const_i64 20\r\n IndexStore xs, %2, %3\r\n %4 = const_i64 2\r\n %5 = const_i64 30\r\n IndexStore xs, %4, %5\r\n %6 = const_i64 0\r\n %7 = Index xs, %6\r\n call print, %7\r\n %8 = const_i64 1\r\n %9 = Index xs, %8\r\n call print, %9\r\n %10 = const_i64 2\r\n %11 = Index xs, %10\r\n call print, %11\r\n return", - "28da76a1": - "function __main__():\r\nblock0:\r\n store t : tuple[int[64], 4]\r\n %0 = const_i64 0\r\n %1 = const_i64 1\r\n IndexStore t, %0, %1\r\n %2 = const_i64 1\r\n %3 = const_i64 2\r\n IndexStore t, %2, %3\r\n %4 = const_i64 2\r\n %5 = const_i64 3\r\n IndexStore t, %4, %5\r\n %6 = const_i64 3\r\n %7 = const_i64 4\r\n IndexStore t, %6, %7\r\n %8 = const_i64 0\r\n %9 = Index t, %8\r\n call print, %9\r\n %10 = const_i64 3\r\n %11 = Index t, %10\r\n call print, %11\r\n return", - f0834cb4: - "function __main__():\r\nblock0:\r\n store d : dict[int[64], int[64], 4]\r\n %0 = const_i64 1\r\n %1 = const_i64 10\r\n DictStore d, %0, %1\r\n %2 = const_i64 2\r\n %3 = const_i64 20\r\n DictStore d, %2, %3\r\n %4 = const_i64 3\r\n %5 = const_i64 30\r\n DictStore d, %4, %5\r\n %6 = const_i64 4\r\n %7 = const_i64 40\r\n DictStore d, %6, %7\r\n %8 = const_i64 1\r\n %9 = DictIndex d, %8\r\n call print, %9\r\n %10 = const_i64 4\r\n %11 = DictIndex d, %10\r\n call print, %11\r\n %12 = const_i64 2\r\n store k, %12 : int[64]\r\n %13 = load k\r\n %14 = DictIndex d, %13\r\n call print, %14\r\n %15 = load k\r\n %16 = load k\r\n %17 = const_i64 1\r\n %18 = add %16, %17\r\n store j, %18 : int[64]\r\n %19 = load j\r\n %20 = DictIndex d, %19\r\n call print, %20\r\n return", - "0aa58a13": - "function __main__():\r\nblock0:\r\n %0 = const_i64 5\r\n store x, %0 : int[64]\r\n %1 = addressof x\r\n store _p, %1 : ptr[int[64]]\r\n %2 = load _p\r\n %3 = valueof %2 : int[64]\r\n call print, %3\r\n %4 = const_i64 6\r\n store q, %4 : int[64]\r\n %5 = addressof q\r\n store _q, %5 : ptr[int[64]]\r\n %6 = load _q\r\n %7 = valueof %6 : int[64]\r\n call print, %7\r\n %8 = load _p\r\n %9 = load _q\r\n %10 = eq %8, %9\r\n call print, %10\r\n return", - cbd1f32e: - "function __main__():\r\nblock0:\r\n %0 = const_f64 2.5\r\n store f, %0 : float[64]\r\n %1 = addressof f\r\n store _pf, %1 : ptr[float[64]]\r\n %2 = load _pf\r\n %3 = valueof %2 : float[64]\r\n call print, %3\r\n %4 = const_f64 3.25\r\n store f, %4 : float[64]\r\n %5 = load _pf\r\n %6 = valueof %5 : float[64]\r\n call print, %6\r\n return", - b4973ce3: - "function __main__():\r\nblock0:\r\n %0 = const_i64 9223372036854775807\r\n store x, %0 : int[64]\r\n %1 = const_i64 1\r\n store y, %1 : int[64]\r\n %2 = load x\r\n %3 = load y\r\n %4 = wrapadd %2, %3\r\n store z, %4 : int[64]\r\n %5 = load z\r\n call print, %5\r\n return", - "8b9a4015": - "function __main__():\r\nblock0:\r\n %0 = const_i64 -7\r\n %1 = const_i64 3\r\n %2 = mod %0, %1\r\n call print, %2\r\n %3 = const_i64 7\r\n %4 = const_i64 -3\r\n %5 = mod %3, %4\r\n call print, %5\r\n return", - e367543b: - "function __main__():\r\nblock0:\r\n %0 = const_i64 5\r\n %1 = const_i64 63\r\n %2 = shl %0, %1\r\n call print, %2\r\n return", + "89052530": "function __main__():\r\nblock0:\r\n store v : struct[16]\r\n %0 = const_f64 1.5\r\n fieldstore v, 0, %0 : float[64]\r\n %1 = const_f64 -2.25\r\n fieldstore v, 8, %1 : float[64]\r\n %2 = fieldload v, 0 : float[64]\r\n call print_bare, %2\r\n call print_nl\r\n %3 = fieldload v, 8 : float[64]\r\n call print_bare, %3\r\n call print_nl\r\n %4 = fieldload v, 0 : float[64]\r\n %5 = fieldload v, 8 : float[64]\r\n %6 = add %4, %5\r\n call print_bare, %6\r\n call print_nl\r\n return", + "47db5edf": "function __main__():\r\nblock0:\r\n %0 = const_i64 10\r\n store x, %0 : int[64]\r\n %1 = const_i64 20\r\n store y, %1 : int[64]\r\n %2 = load x\r\n %3 = load y\r\n %4 = add %2, %3\r\n store result, %4 : int[64]\r\n %5 = load result\r\n call print_bare, %5\r\n call print_nl\r\n return", + "39ae587c": "function __main__():\r\nblock0:\r\n %0 = const_i64 10\r\n store x, %0 : int[64]\r\n %1 = const_i64 20\r\n store y, %1 : int[64]\r\n %2 = load x\r\n %3 = load y\r\n %4 = add %2, %3\r\n store z, %4 : int[64]\r\n %5 = load z\r\n call print_bare, %5\r\n call print_nl\r\n %6 = load x\r\n %7 = load y\r\n %8 = sub %6, %7\r\n call print_bare, %8\r\n call print_nl\r\n %9 = load x\r\n %10 = load y\r\n %11 = mul %9, %10\r\n call print_bare, %11\r\n call print_nl\r\n return", + "cac171bf": "function __main__():\r\nblock0:\r\n %0 = const_i64 10\r\n store x, %0\r\n %1 = load x\r\n call print_bare, %1\r\n call print_nl\r\n return", + "2a8807c9": "function __main__():\r\nblock0:\r\n %0 = const_i64 7\r\n store i8, %0 : int[8]\r\n %1 = const_i64 7\r\n store i16, %1 : int[16]\r\n %2 = const_i64 7\r\n store i32, %2 : int[32]\r\n %3 = const_i64 7\r\n store i64, %3 : int[64]\r\n %4 = const_f64 0.5\r\n store f64, %4 : float[64]\r\n %5 = const_bool 1\r\n store b, %5 : bool\r\n %6 = load i64\r\n call print_bare, %6\r\n call print_nl\r\n return", + "7cbe74a6": "function __main__():\r\nblock0:\r\n %0 = const_i64 5\r\n store small, %0 : int[8]\r\n %1 = load small\r\n store big, %1 : int[64]\r\n %2 = load big\r\n call print_bare, %2\r\n call print_nl\r\n return", + "206f6bf0": "function __main__():\r\nblock0:\r\n %0 = const_i64 5\r\n store wide, %0 : int[64]\r\n %1 = load wide\r\n store narrow, %1 : int[8]\r\n %2 = load narrow\r\n call print_bare, %2\r\n call print_nl\r\n return", + "1a324420": "function __main__():\r\nblock0:\r\n %0 = const_i64 0\r\n store j, %0 : float[64]\r\n %1 = load j\r\n call print_bare, %1\r\n call print_nl\r\n return", + "aeb1dcc6": "function __main__():\r\nblock0:\r\n %0 = const_f64 0.0\r\n store j, %0 : float[64]\r\n %1 = load j\r\n call print_bare, %1\r\n call print_nl\r\n return", + "96262f13": "function __main__():\r\nblock0:\r\n %0 = const_i64 10\r\n store i, %0 : int[64]\r\n %1 = const_f64 2.5\r\n store f, %1 : float[64]\r\n %2 = load i\r\n %3 = load f\r\n %4 = add %2, %3\r\n call print_bare, %4\r\n call print_nl\r\n %5 = load f\r\n %6 = load i\r\n %7 = add %5, %6\r\n call print_bare, %7\r\n call print_nl\r\n %8 = load i\r\n %9 = const_i64 4\r\n %10 = div %8, %9\r\n call print_bare, %10\r\n call print_nl\r\n return", + "c2b675d3": "function __main__():\r\nblock0:\r\n %0 = const_i64 10\r\n store x, %0 : int[64]\r\n %1 = const_i64 20\r\n store x, %1 : int[64]\r\n %2 = load x\r\n call print_bare, %2\r\n call print_nl\r\n return", + "a78d2c85": "function __main__():\r\nblock0:\r\n %0 = const_i64 10\r\n store x, %0 : int[64]\r\n %1 = const_i64 20\r\n store x, %1 : int[64]\r\n %2 = load x\r\n store narrow, %2 : int[8]\r\n %3 = load narrow\r\n call print_bare, %3\r\n call print_nl\r\n return", + "e1f84f23": "function __main__():\r\nblock0:\r\n %0 = const_i64 5\r\n store i, %0 : int[8]\r\n %1 = load i\r\n call print_bare, %1\r\n call print_nl\r\n return", + "bba9661c": "function __main__():\r\nblock0:\r\n %0 = load i\r\n call print_bare, %0\r\n call print_nl\r\n return", + "f00439de": "function __main__():\r\nblock0:\r\n %0 = const_i64 1\r\n store x, %0 : int[64]\r\n %1 = load x\r\n %2 = const_i64 3\r\n %3 = lt %1, %2\r\n branch %3, block1, block2\r\nblock1:\r\n %4 = const_i64 2\r\n store y, %4 : int[64]\r\n jump block2\r\nblock2:\r\n %5 = load y\r\n call print_bare, %5\r\n call print_nl\r\n return", + "9443a6ad": "function __main__():\r\nblock0:\r\n %0 = const_i64 0\r\n store i, %0 : int[64]\r\n %1 = const_i64 0\r\n store total, %1 : int[64]\r\n jump block1\r\nblock1:\r\n %2 = load i\r\n %3 = const_i64 10\r\n %4 = lt %2, %3\r\n branch %4, block2, block3\r\nblock2:\r\n %5 = load total\r\n %6 = load i\r\n %7 = add %5, %6\r\n store total, %7 : int[64]\r\n %8 = load i\r\n %9 = load i\r\n %10 = const_i64 1\r\n %11 = add %9, %10\r\n store i, %11 : int[64]\r\n jump block1\r\nblock3:\r\n %12 = load total\r\n call print_bare, %12\r\n call print_nl\r\n return", + "06ab6407": "function __main__():\r\nblock0:\r\n %0 = const_i64 1\r\n %1 = const_i64 10\r\n %2 = const_i64 3\r\n store i, %0 : int[64]\r\n %3 = const_i64 0\r\n %4 = gt %2, %3\r\n branch %4, block4, block5\r\nblock4:\r\n %5 = lt %0, %1\r\n branch %5, block1, block2\r\nblock5:\r\n %6 = gt %0, %1\r\n branch %6, block1, block2\r\nblock1:\r\n %7 = load i\r\n call print_bare, %7\r\n call print_nl\r\n jump block3\r\nblock3:\r\n %8 = load i\r\n branch %4, block6, block7\r\nblock6:\r\n %9 = const_i64 -9223372036854775808\r\n %10 = wrapsub %1, %8\r\n %11 = bxor %10, %9\r\n %12 = bxor %2, %9\r\n %13 = gt %11, %12\r\n branch %13, block8, block2\r\nblock7:\r\n %14 = const_i64 -9223372036854775808\r\n %15 = wrapsub %8, %1\r\n %16 = sub %3, %2\r\n %17 = bxor %15, %14\r\n %18 = bxor %16, %14\r\n %19 = gt %17, %18\r\n branch %19, block8, block2\r\nblock8:\r\n %20 = load i\r\n %21 = add %20, %2\r\n store i, %21\r\n jump block1\r\nblock2:\r\n %22 = const_i64 10\r\n %23 = const_i64 1\r\n %24 = const_i64 -3\r\n store i, %22 : int[64]\r\n guardnonzero %24\r\n %25 = const_i64 0\r\n %26 = gt %24, %25\r\n branch %26, block12, block13\r\nblock12:\r\n %27 = lt %22, %23\r\n branch %27, block9, block10\r\nblock13:\r\n %28 = gt %22, %23\r\n branch %28, block9, block10\r\nblock9:\r\n %29 = load i\r\n call print_bare, %29\r\n call print_nl\r\n jump block11\r\nblock11:\r\n %30 = load i\r\n branch %26, block14, block15\r\nblock14:\r\n %31 = const_i64 -9223372036854775808\r\n %32 = wrapsub %23, %30\r\n %33 = bxor %32, %31\r\n %34 = bxor %24, %31\r\n %35 = gt %33, %34\r\n branch %35, block16, block10\r\nblock15:\r\n %36 = const_i64 -9223372036854775808\r\n %37 = wrapsub %30, %23\r\n %38 = sub %25, %24\r\n %39 = bxor %37, %36\r\n %40 = bxor %38, %36\r\n %41 = gt %39, %40\r\n branch %41, block16, block10\r\nblock16:\r\n %42 = load i\r\n %43 = add %42, %24\r\n store i, %43\r\n jump block9\r\nblock10:\r\n %44 = const_i64 0\r\n %45 = const_i64 5\r\n %46 = const_i64 1\r\n store i, %44 : int[64]\r\n %47 = const_i64 0\r\n %48 = gt %46, %47\r\n branch %48, block20, block21\r\nblock20:\r\n %49 = lt %44, %45\r\n branch %49, block17, block18\r\nblock21:\r\n %50 = gt %44, %45\r\n branch %50, block17, block18\r\nblock17:\r\n %51 = load i\r\n call print_bare, %51\r\n call print_nl\r\n jump block19\r\nblock19:\r\n %52 = load i\r\n branch %48, block22, block23\r\nblock22:\r\n %53 = const_i64 -9223372036854775808\r\n %54 = wrapsub %45, %52\r\n %55 = bxor %54, %53\r\n %56 = bxor %46, %53\r\n %57 = gt %55, %56\r\n branch %57, block24, block18\r\nblock23:\r\n %58 = const_i64 -9223372036854775808\r\n %59 = wrapsub %52, %45\r\n %60 = sub %47, %46\r\n %61 = bxor %59, %58\r\n %62 = bxor %60, %58\r\n %63 = gt %61, %62\r\n branch %63, block24, block18\r\nblock24:\r\n %64 = load i\r\n %65 = add %64, %46\r\n store i, %65\r\n jump block17\r\nblock18:\r\n %66 = const_i64 5\r\n %67 = const_i64 5\r\n %68 = const_i64 1\r\n store i, %66 : int[64]\r\n %69 = const_i64 0\r\n %70 = gt %68, %69\r\n branch %70, block28, block29\r\nblock28:\r\n %71 = lt %66, %67\r\n branch %71, block25, block26\r\nblock29:\r\n %72 = gt %66, %67\r\n branch %72, block25, block26\r\nblock25:\r\n %73 = load i\r\n call print_bare, %73\r\n call print_nl\r\n jump block27\r\nblock27:\r\n %74 = load i\r\n branch %70, block30, block31\r\nblock30:\r\n %75 = const_i64 -9223372036854775808\r\n %76 = wrapsub %67, %74\r\n %77 = bxor %76, %75\r\n %78 = bxor %68, %75\r\n %79 = gt %77, %78\r\n branch %79, block32, block26\r\nblock31:\r\n %80 = const_i64 -9223372036854775808\r\n %81 = wrapsub %74, %67\r\n %82 = sub %69, %68\r\n %83 = bxor %81, %80\r\n %84 = bxor %82, %80\r\n %85 = gt %83, %84\r\n branch %85, block32, block26\r\nblock32:\r\n %86 = load i\r\n %87 = add %86, %68\r\n store i, %87\r\n jump block25\r\nblock26:\r\n %88 = const_i64 1\r\n %89 = const_i64 5\r\n %90 = const_i64 6\r\n store i, %88 : int[64]\r\n %91 = const_i64 0\r\n %92 = gt %90, %91\r\n branch %92, block36, block37\r\nblock36:\r\n %93 = lt %88, %89\r\n branch %93, block33, block34\r\nblock37:\r\n %94 = gt %88, %89\r\n branch %94, block33, block34\r\nblock33:\r\n %95 = load i\r\n call print_bare, %95\r\n call print_nl\r\n jump block35\r\nblock35:\r\n %96 = load i\r\n branch %92, block38, block39\r\nblock38:\r\n %97 = const_i64 -9223372036854775808\r\n %98 = wrapsub %89, %96\r\n %99 = bxor %98, %97\r\n %100 = bxor %90, %97\r\n %101 = gt %99, %100\r\n branch %101, block40, block34\r\nblock39:\r\n %102 = const_i64 -9223372036854775808\r\n %103 = wrapsub %96, %89\r\n %104 = sub %91, %90\r\n %105 = bxor %103, %102\r\n %106 = bxor %104, %102\r\n %107 = gt %105, %106\r\n branch %107, block40, block34\r\nblock40:\r\n %108 = load i\r\n %109 = add %108, %90\r\n store i, %109\r\n jump block33\r\nblock34:\r\n %110 = const_i64 -5\r\n %111 = const_i64 -1\r\n %112 = const_i64 1\r\n store i, %110 : int[64]\r\n %113 = const_i64 0\r\n %114 = gt %112, %113\r\n branch %114, block44, block45\r\nblock44:\r\n %115 = lt %110, %111\r\n branch %115, block41, block42\r\nblock45:\r\n %116 = gt %110, %111\r\n branch %116, block41, block42\r\nblock41:\r\n %117 = load i\r\n call print_bare, %117\r\n call print_nl\r\n jump block43\r\nblock43:\r\n %118 = load i\r\n branch %114, block46, block47\r\nblock46:\r\n %119 = const_i64 -9223372036854775808\r\n %120 = wrapsub %111, %118\r\n %121 = bxor %120, %119\r\n %122 = bxor %112, %119\r\n %123 = gt %121, %122\r\n branch %123, block48, block42\r\nblock47:\r\n %124 = const_i64 -9223372036854775808\r\n %125 = wrapsub %118, %111\r\n %126 = sub %113, %112\r\n %127 = bxor %125, %124\r\n %128 = bxor %126, %124\r\n %129 = gt %127, %128\r\n branch %129, block48, block42\r\nblock48:\r\n %130 = load i\r\n %131 = add %130, %112\r\n store i, %131\r\n jump block41\r\nblock42:\r\n %132 = const_i64 -1\r\n %133 = const_i64 -5\r\n %134 = const_i64 -2\r\n store i, %132 : int[64]\r\n guardnonzero %134\r\n %135 = const_i64 0\r\n %136 = gt %134, %135\r\n branch %136, block52, block53\r\nblock52:\r\n %137 = lt %132, %133\r\n branch %137, block49, block50\r\nblock53:\r\n %138 = gt %132, %133\r\n branch %138, block49, block50\r\nblock49:\r\n %139 = load i\r\n call print_bare, %139\r\n call print_nl\r\n jump block51\r\nblock51:\r\n %140 = load i\r\n branch %136, block54, block55\r\nblock54:\r\n %141 = const_i64 -9223372036854775808\r\n %142 = wrapsub %133, %140\r\n %143 = bxor %142, %141\r\n %144 = bxor %134, %141\r\n %145 = gt %143, %144\r\n branch %145, block56, block50\r\nblock55:\r\n %146 = const_i64 -9223372036854775808\r\n %147 = wrapsub %140, %133\r\n %148 = sub %135, %134\r\n %149 = bxor %147, %146\r\n %150 = bxor %148, %146\r\n %151 = gt %149, %150\r\n branch %151, block56, block50\r\nblock56:\r\n %152 = load i\r\n %153 = add %152, %134\r\n store i, %153\r\n jump block49\r\nblock50:\r\n return", + "bd68f7e0": "function __main__():\r\nblock0:\r\n %0 = const_i64 0\r\n store total, %0 : int[64]\r\n %1 = const_i64 0\r\n %2 = const_i64 100\r\n %3 = const_i64 1\r\n store i, %1 : int[64]\r\n jump block4\r\nblock4:\r\n %4 = load i\r\n %5 = lt %4, %2\r\n branch %5, block1, block2\r\nblock1:\r\n %6 = load i\r\n %7 = const_i64 5\r\n %8 = eq %6, %7\r\n branch %8, block5, block6\r\nblock5:\r\n jump block3\r\nblock6:\r\n %9 = load i\r\n %10 = const_i64 10\r\n %11 = eq %9, %10\r\n branch %11, block7, block8\r\nblock7:\r\n jump block2\r\nblock8:\r\n %12 = load total\r\n %13 = load i\r\n %14 = add %12, %13\r\n store total, %14 : int[64]\r\n jump block3\r\nblock3:\r\n %15 = load i\r\n %16 = const_i64 1\r\n %17 = add %15, %16\r\n store i, %17\r\n jump block4\r\nblock2:\r\n %18 = load total\r\n call print_bare, %18\r\n call print_nl\r\n return", + "4598471d": "function __main__():\r\nblock0:\r\n store xs : list[int[64], 5]\r\n %0 = const_i64 5\r\n store xs.__len, %0 : int[64]\r\n %1 = const_i64 0\r\n %2 = const_i64 10\r\n IndexStore xs, %1, %2\r\n %3 = const_i64 1\r\n %4 = const_i64 20\r\n IndexStore xs, %3, %4\r\n %5 = const_i64 2\r\n %6 = const_i64 30\r\n IndexStore xs, %5, %6\r\n %7 = const_i64 3\r\n %8 = const_i64 40\r\n IndexStore xs, %7, %8\r\n %9 = const_i64 4\r\n %10 = const_i64 50\r\n IndexStore xs, %9, %10\r\n %11 = const_i64 0\r\n %12 = const_i64 5\r\n %13 = const_i64 1\r\n store ei, %11 : int[64]\r\n jump block4\r\nblock4:\r\n %14 = load ei\r\n %15 = lt %14, %12\r\n branch %15, block1, block2\r\nblock1:\r\n %16 = load ei\r\n %17 = Index xs, %16\r\n store ex, %17 : int[64]\r\n %18 = load ei\r\n call print_bare, %18\r\n call print_nl\r\n %19 = load ex\r\n call print_bare, %19\r\n call print_nl\r\n jump block3\r\nblock3:\r\n %20 = load ei\r\n %21 = const_i64 1\r\n %22 = add %20, %21\r\n store ei, %22\r\n jump block4\r\nblock2:\r\n store long6 : list[int[64], 6]\r\n %23 = const_i64 6\r\n store long6.__len, %23 : int[64]\r\n store short3 : list[int[64], 3]\r\n %24 = const_i64 3\r\n store short3.__len, %24 : int[64]\r\n %25 = const_i64 0\r\n %26 = const_i64 1\r\n IndexStore long6, %25, %26\r\n %27 = const_i64 1\r\n %28 = const_i64 2\r\n IndexStore long6, %27, %28\r\n %29 = const_i64 2\r\n %30 = const_i64 3\r\n IndexStore long6, %29, %30\r\n %31 = const_i64 0\r\n %32 = const_i64 7\r\n IndexStore short3, %31, %32\r\n %33 = const_i64 1\r\n %34 = const_i64 8\r\n IndexStore short3, %33, %34\r\n %35 = const_i64 2\r\n %36 = const_i64 9\r\n IndexStore short3, %35, %36\r\n %37 = const_i64 0\r\n store total, %37 : int[64]\r\n %38 = const_i64 0\r\n %39 = const_i64 3\r\n %40 = const_i64 1\r\n store zip_i0, %38 : int[64]\r\n jump block8\r\nblock8:\r\n %41 = load zip_i0\r\n %42 = lt %41, %39\r\n branch %42, block5, block6\r\nblock5:\r\n %43 = load zip_i0\r\n %44 = Index long6, %43\r\n store za, %44 : int[64]\r\n %45 = Index short3, %43\r\n store zb, %45 : int[64]\r\n %46 = load za\r\n %47 = load zb\r\n %48 = add %46, %47\r\n store total, %48 : int[64]\r\n jump block7\r\nblock7:\r\n %49 = load zip_i0\r\n %50 = const_i64 1\r\n %51 = add %49, %50\r\n store zip_i0, %51\r\n jump block8\r\nblock6:\r\n %52 = load total\r\n call print_bare, %52\r\n call print_nl\r\n return", + "311879c3": "function __main__():\r\nblock0:\r\n %0 = const_i64 5\r\n store x, %0 : int[64]\r\n %1 = const_i64 2\r\n store y, %1 : int[64]\r\n %2 = load x\r\n %3 = load y\r\n %4 = gt %2, %3\r\n branch %4, block1, block2\r\nblock1:\r\n %5 = load x\r\n store __ifexpr0, %5\r\n jump block3\r\nblock2:\r\n %6 = load y\r\n store __ifexpr0, %6\r\n jump block3\r\nblock3:\r\n %7 = load __ifexpr0\r\n call print_bare, %7\r\n call print_nl\r\n %8 = load x\r\n %9 = load y\r\n %10 = lt %8, %9\r\n branch %10, block4, block5\r\nblock4:\r\n %11 = const_i64 1\r\n store __ifexpr1, %11\r\n jump block6\r\nblock5:\r\n %12 = load x\r\n %13 = const_i64 5\r\n %14 = eq %12, %13\r\n branch %14, block7, block8\r\nblock7:\r\n %15 = const_i64 2\r\n store __ifexpr2, %15\r\n jump block9\r\nblock8:\r\n %16 = const_i64 3\r\n store __ifexpr2, %16\r\n jump block9\r\nblock9:\r\n %17 = load __ifexpr2\r\n store __ifexpr1, %17\r\n jump block6\r\nblock6:\r\n %18 = load __ifexpr1\r\n call print_bare, %18\r\n call print_nl\r\n return", + "ace9c7cc": "function add(a:int[64], b:int[64]):\r\nblock0:\r\n %0 = load a\r\n %1 = load b\r\n %2 = add %0, %1\r\n return %2\r\n\r\nfunction __main__():\r\nblock0:\r\n %0 = const_i64 3\r\n %1 = const_i64 4\r\n %2 = call add, %0, %1\r\n store x, %2 : int[64]\r\n %3 = load x\r\n call print_bare, %3\r\n call print_nl\r\n return", + "8c105dca": "function __main__():\r\nblock0:\r\n %0 = const_i64 -7\r\n store a, %0 : int[64]\r\n %1 = const_i64 3\r\n store b, %1 : int[64]\r\n %2 = load a\r\n %3 = load b\r\n %4 = mod %2, %3\r\n call print_bare, %4\r\n call print_nl\r\n %5 = const_i64 7\r\n %6 = const_i64 3\r\n %7 = mod %5, %6\r\n call print_bare, %7\r\n call print_nl\r\n %8 = const_i64 -7\r\n %9 = const_i64 3\r\n %10 = mod %8, %9\r\n call print_bare, %10\r\n call print_nl\r\n return", + "c6fd2aba": "function __main__():\r\nblock0:\r\n %0 = const_i64 1\r\n store a, %0 : int[64]\r\n %1 = const_i64 2\r\n store b, %1 : int[64]\r\n %2 = const_f64 1.5\r\n store c, %2 : float[64]\r\n %3 = load a\r\n %4 = load b\r\n %5 = lt %3, %4\r\n call print_bare, %5\r\n call print_nl\r\n %6 = load b\r\n %7 = load a\r\n %8 = gt %6, %7\r\n call print_bare, %8\r\n call print_nl\r\n %9 = load a\r\n %10 = load a\r\n %11 = eq %9, %10\r\n call print_bare, %11\r\n call print_nl\r\n %12 = load c\r\n %13 = load b\r\n %14 = lt %12, %13\r\n call print_bare, %14\r\n call print_nl\r\n return", + "2c1f3e5c": "function __main__():\r\nblock0:\r\n %0 = const_i64 1\r\n store a, %0 : int[64]\r\n %1 = const_i64 2\r\n store b, %1 : int[64]\r\n %2 = load a\r\n %3 = load b\r\n %4 = ne %2, %3\r\n call print_bare, %4\r\n call print_nl\r\n %5 = load a\r\n %6 = load a\r\n %7 = ne %5, %6\r\n call print_bare, %7\r\n call print_nl\r\n %8 = const_i64 -128\r\n store lo, %8 : int[8]\r\n %9 = const_i64 127\r\n store hi, %9 : int[8]\r\n %10 = load lo\r\n %11 = load hi\r\n %12 = ne %10, %11\r\n call print_bare, %12\r\n call print_nl\r\n return", + "5ba53d35": "function __main__():\r\nblock0:\r\n %0 = const_f64 2.5\r\n %1 = const_i64 1\r\n %2 = band %0, %1\r\n call print_bare, %2\r\n call print_nl\r\n return", + "f8490a90": "function __main__():\r\nblock0:\r\n %0 = const_i64 1\r\n %1 = const_i64 64\r\n %2 = shl %0, %1\r\n call print_bare, %2\r\n call print_nl\r\n return", + "349eae74": "function __main__():\r\nblock0:\r\n %0 = const_i64 1\r\n %1 = const_i64 10\r\n %2 = shl %0, %1\r\n store x, %2 : int[64]\r\n %3 = load x\r\n %4 = const_i64 3\r\n %5 = shr %3, %4\r\n store y, %5 : int[64]\r\n %6 = load x\r\n %7 = const_i64 255\r\n %8 = band %6, %7\r\n store z, %8 : int[64]\r\n %9 = load x\r\n call print_bare, %9\r\n call print_nl\r\n %10 = load y\r\n call print_bare, %10\r\n call print_nl\r\n %11 = load z\r\n call print_bare, %11\r\n call print_nl\r\n return", + "6666db5c": "function __main__():\r\nblock0:\r\n %0 = const_i64 9223372036854775807\r\n store x, %0 : int[64]\r\n %1 = const_i64 1\r\n store y, %1 : int[64]\r\n %2 = load x\r\n %3 = load y\r\n %4 = wrapadd %2, %3\r\n store z, %4 : int[64]\r\n %5 = load z\r\n call print_bare, %5\r\n call print_nl\r\n %6 = const_i64 -9223372036854775808\r\n store a, %6 : int[64]\r\n %7 = const_i64 1\r\n store b, %7 : int[64]\r\n %8 = load a\r\n %9 = load b\r\n %10 = wrapsub %8, %9\r\n store c, %10 : int[64]\r\n %11 = load c\r\n call print_bare, %11\r\n call print_nl\r\n %12 = const_i64 3037000500\r\n store p, %12 : int[64]\r\n %13 = const_i64 3037000500\r\n store q, %13 : int[64]\r\n %14 = load p\r\n %15 = load q\r\n %16 = wrapmul %14, %15\r\n store r, %16 : int[64]\r\n %17 = load r\r\n call print_bare, %17\r\n call print_nl\r\n return", + "fe363d56": "function __main__():\r\nblock0:\r\n store xs : list[int[64], 6]\r\n %0 = const_i64 6\r\n store xs.__len, %0 : int[64]\r\n %1 = const_i64 0\r\n %2 = const_i64 10\r\n IndexStore xs, %1, %2\r\n %3 = const_i64 1\r\n %4 = const_i64 20\r\n IndexStore xs, %3, %4\r\n %5 = const_i64 2\r\n %6 = const_i64 30\r\n IndexStore xs, %5, %6\r\n %8 = const_i64 0\r\n %7 = Index xs, %8\r\n call print_bare, %7\r\n call print_nl\r\n %10 = const_i64 1\r\n %9 = Index xs, %10\r\n call print_bare, %9\r\n call print_nl\r\n %12 = const_i64 2\r\n %11 = Index xs, %12\r\n call print_bare, %11\r\n call print_nl\r\n return", + "28da76a1": "function __main__():\r\nblock0:\r\n store t : tuple[int[64], 4]\r\n %0 = const_i64 0\r\n %1 = const_i64 1\r\n IndexStore t, %0, %1\r\n %2 = const_i64 1\r\n %3 = const_i64 2\r\n IndexStore t, %2, %3\r\n %4 = const_i64 2\r\n %5 = const_i64 3\r\n IndexStore t, %4, %5\r\n %6 = const_i64 3\r\n %7 = const_i64 4\r\n IndexStore t, %6, %7\r\n %9 = const_i64 0\r\n %8 = Index t, %9\r\n call print_bare, %8\r\n call print_nl\r\n %11 = const_i64 3\r\n %10 = Index t, %11\r\n call print_bare, %10\r\n call print_nl\r\n return", + "f0834cb4": "function __main__():\r\nblock0:\r\n store d : dict[int[64], int[64], 4]\r\n %0 = const_i64 10\r\n %1 = const_i64 1\r\n DictStore d, %1, %0\r\n %2 = const_i64 20\r\n %3 = const_i64 2\r\n DictStore d, %3, %2\r\n %4 = const_i64 30\r\n %5 = const_i64 3\r\n DictStore d, %5, %4\r\n %6 = const_i64 40\r\n %7 = const_i64 4\r\n DictStore d, %7, %6\r\n %9 = const_i64 1\r\n %8 = DictIndex d, %9\r\n call print_bare, %8\r\n call print_nl\r\n %11 = const_i64 4\r\n %10 = DictIndex d, %11\r\n call print_bare, %10\r\n call print_nl\r\n %12 = const_i64 2\r\n store k, %12 : int[64]\r\n %14 = load k\r\n %13 = DictIndex d, %14\r\n call print_bare, %13\r\n call print_nl\r\n %15 = load k\r\n %16 = load k\r\n %17 = const_i64 1\r\n %18 = add %16, %17\r\n store j, %18 : int[64]\r\n %20 = load j\r\n %19 = DictIndex d, %20\r\n call print_bare, %19\r\n call print_nl\r\n return", + "0aa58a13": "function __main__():\r\nblock0:\r\n %0 = const_i64 5\r\n store x, %0 : int[64]\r\n %1 = addressof x\r\n store _p, %1 : ptr[int[64]]\r\n %2 = load _p\r\n %3 = valueof %2 : int[64]\r\n call print_bare, %3\r\n call print_nl\r\n %4 = const_i64 6\r\n store q, %4 : int[64]\r\n %5 = addressof q\r\n store _q, %5 : ptr[int[64]]\r\n %6 = load _q\r\n %7 = valueof %6 : int[64]\r\n call print_bare, %7\r\n call print_nl\r\n %8 = load _p\r\n %9 = load _q\r\n %10 = eq %8, %9\r\n call print_bare, %10\r\n call print_nl\r\n return", + "cbd1f32e": "function __main__():\r\nblock0:\r\n %0 = const_f64 2.5\r\n store f, %0 : float[64]\r\n %1 = addressof f\r\n store _pf, %1 : ptr[float[64]]\r\n %2 = load _pf\r\n %3 = valueof %2 : float[64]\r\n call print_bare, %3\r\n call print_nl\r\n %4 = const_f64 3.25\r\n store f, %4 : float[64]\r\n %5 = load _pf\r\n %6 = valueof %5 : float[64]\r\n call print_bare, %6\r\n call print_nl\r\n return", + "799c0dd3": "function __main__():\r\nblock0:\r\n store p : struct[16]\r\n %0 = const_i64 3\r\n fieldstore p, 0, %0 : int[64]\r\n %1 = const_i64 4\r\n fieldstore p, 8, %1 : int[64]\r\n %2 = fieldload p, 0 : int[64]\r\n call print_bare, %2\r\n call print_nl\r\n %3 = fieldload p, 8 : int[64]\r\n call print_bare, %3\r\n call print_nl\r\n return", + "5b02aaa2": "function __main__():\r\nblock0:\r\n store s : struct[13]\r\n %0 = const_i64 2\r\n fieldstore s, 0, %0 : int[16]\r\n %1 = const_i64 80\r\n fieldstore s, 2, %1 : int[16]\r\n %2 = fieldload s, 0 : int[16]\r\n call print_bare, %2\r\n call print_nl\r\n %3 = fieldload s, 2 : int[16]\r\n call print_bare, %3\r\n call print_nl\r\n %4 = fieldload s, 4 : int[64]\r\n call print_bare, %4\r\n call print_nl\r\n %5 = fieldload s, 12 : bool\r\n call print_bare, %5\r\n call print_nl\r\n return", + "1407976e": "function __main__():\r\nblock0:\r\n store s : struct[9]\r\n %0 = const_i64 1\r\n fieldstore s, 0, %0 : int[8]\r\n %1 = fieldload s, 0 : int[8]\r\n %2 = const_i64 0\r\n %3 = eq %1, %2\r\n branch %3, block2, block3\r\nblock2:\r\n %4 = const_i64 0\r\n call print_bare, %4\r\n call print_nl\r\n jump block1\r\nblock3:\r\n %5 = const_i64 1\r\n %6 = eq %1, %5\r\n branch %6, block4, block5\r\nblock4:\r\n %7 = const_i64 1\r\n call print_bare, %7\r\n call print_nl\r\n jump block1\r\nblock5:\r\n %8 = const_i64 2\r\n %9 = eq %1, %8\r\n branch %9, block6, block7\r\nblock6:\r\n %10 = fieldload s, 1 : int[32]\r\n store n, %10 : int[32]\r\n %11 = load n\r\n call print_bare, %11\r\n call print_nl\r\n jump block1\r\nblock7:\r\n %12 = const_i64 3\r\n %13 = eq %1, %12\r\n branch %13, block8, block1\r\nblock8:\r\n %14 = fieldload s, 1 : int[64]\r\n store w, %14 : int[64]\r\n %15 = load w\r\n call print_bare, %15\r\n call print_nl\r\n jump block1\r\nblock1:\r\n return", + "89c84b42": "function __main__():\r\nblock0:\r\n store c : struct[15]\r\n %0 = const_i64 1\r\n fieldstore c, 0, %0 : int[8]\r\n %1 = const_i64 8080\r\n fieldstore c, 1, %1 : int[16]\r\n %2 = const_i64 16777343\r\n fieldstore c, 3, %2 : int[32]\r\n %3 = const_i64 7\r\n fieldstore c, 7, %3 : int[64]\r\n return", + "bd542c7c": "function __main__():\r\nblock0:\r\n store c : struct[15]\r\n %0 = const_i64 1\r\n fieldstore c, 0, %0 : int[8]\r\n %1 = const_i64 8080\r\n fieldstore c, 1, %1 : int[16]\r\n %2 = const_i64 16777343\r\n fieldstore c, 3, %2 : int[32]\r\n %3 = const_i64 7\r\n fieldstore c, 7, %3 : int[64]\r\n %4 = fieldload c, 0 : int[8]\r\n %5 = const_i64 1\r\n %6 = eq %4, %5\r\n branch %6, block2, block3\r\nblock2:\r\n %7 = fieldload c, 1 : int[16]\r\n store p, %7 : int[16]\r\n %8 = fieldload c, 3 : int[32]\r\n store a, %8 : int[32]\r\n %9 = load p\r\n call print_bare, %9\r\n call print_nl\r\n %10 = load a\r\n call print_bare, %10\r\n call print_nl\r\n jump block1\r\nblock3:\r\n %11 = const_i64 2\r\n %12 = eq %4, %11\r\n branch %12, block4, block5\r\nblock4:\r\n %13 = fieldload c, 1 : int[32]\r\n store n, %13 : int[32]\r\n %14 = load n\r\n call print_bare, %14\r\n call print_nl\r\n jump block1\r\nblock5:\r\n %15 = const_i64 0\r\n %16 = eq %4, %15\r\n branch %16, block6, block1\r\nblock6:\r\n %17 = const_i64 -1\r\n call print_bare, %17\r\n call print_nl\r\n jump block1\r\nblock1:\r\n %18 = fieldload c, 7 : int[64]\r\n call print_bare, %18\r\n call print_nl\r\n return", + "ae4a5d6b": "function __main__():\r\nblock0:\r\n store c : struct[8]\r\n %0 = const_i64 5\r\n store m0_n, %0 : int[64]\r\n %1 = fieldload c, 0 : int[64]\r\n %2 = load m0_n\r\n %3 = add %1, %2\r\n fieldstore c, 0, %3 : int[64]\r\n %4 = fieldload c, 0 : int[64]\r\n store m0_ret, %4 : int[64]\r\n jump block1\r\nblock1:\r\n %5 = load m0_ret\r\n call print_bare, %5\r\n call print_nl\r\n %6 = const_i64 7\r\n store m1_n, %6 : int[64]\r\n %7 = fieldload c, 0 : int[64]\r\n %8 = load m1_n\r\n %9 = add %7, %8\r\n fieldstore c, 0, %9 : int[64]\r\n %10 = fieldload c, 0 : int[64]\r\n store m1_ret, %10 : int[64]\r\n jump block2\r\nblock2:\r\n %11 = load m1_ret\r\n call print_bare, %11\r\n call print_nl\r\n return", + "9be3fe85": "function __main__():\r\nblock0:\r\n store s : struct[5]\r\n %0 = const_i64 0\r\n fieldstore s, 0, %0 : int[8]\r\n %1 = fieldload s, 0 : int[8]\r\n %2 = const_i64 0\r\n %3 = eq %1, %2\r\n branch %3, block3, block4\r\nblock3:\r\n %4 = const_i64 0\r\n store m0_ret, %4 : int[64]\r\n jump block1\r\nblock4:\r\n %5 = const_i64 1\r\n %6 = eq %1, %5\r\n branch %6, block5, block6\r\nblock5:\r\n %7 = fieldload s, 1 : int[32]\r\n store m0_n, %7 : int[32]\r\n %8 = load m0_n\r\n store m0_ret, %8 : int[64]\r\n jump block1\r\nblock6:\r\n %9 = const_i64 -1\r\n store m0_ret, %9 : int[64]\r\n jump block1\r\nblock2:\r\nblock1:\r\n %10 = load m0_ret\r\n call print_bare, %10\r\n call print_nl\r\n return", + "0e5a34af": "function __main__():\r\nblock0:\r\n store xs : list[int[64], 4]\r\n %0 = const_i64 4\r\n store xs.__len, %0 : int[64]\r\n %1 = const_i64 0\r\n %2 = const_i64 100\r\n IndexStore xs, %1, %2\r\n %3 = const_i64 1\r\n %4 = const_i64 23\r\n IndexStore xs, %3, %4\r\n %5 = const_i64 2\r\n %6 = const_i64 -3\r\n IndexStore xs, %5, %6\r\n %8 = const_i64 0\r\n %7 = Index xs, %8\r\n %10 = const_i64 1\r\n %9 = Index xs, %10\r\n %11 = add %7, %9\r\n %13 = const_i64 2\r\n %12 = Index xs, %13\r\n %14 = add %11, %12\r\n call print_bare, %14\r\n call print_nl\r\n return", + "2083f31f": "function __main__():\r\nblock0:\r\n %0 = const_i64 5\r\n store a, %0 : int[64]\r\n %1 = const_i64 6\r\n store a, %1 : int[64]\r\n %2 = load a\r\n call print_bare, %2\r\n call print_nl\r\n return", + "9e455b7e": "function __main__():\r\nblock0:\r\n store ps : list[int[32], 2]\r\n %0 = const_i64 2\r\n store ps.__len, %0 : int[64]\r\n %1 = const_i64 0\r\n %2 = const_i64 11\r\n IndexStore ps, %1, %2\r\n %3 = const_i64 1\r\n %4 = const_i64 -22\r\n IndexStore ps, %3, %4\r\n %6 = const_i64 0\r\n %5 = Index ps, %6\r\n %8 = const_i64 1\r\n %7 = Index ps, %8\r\n %9 = add %5, %7\r\n call print_bare, %9\r\n call print_nl\r\n return", + "eae2b479": "function __main__():\r\nblock0:\r\n %0 = const_i64 -1\r\n %1 = syscall %0\r\n store r, %1 : int[64]\r\n %2 = load r\r\n call print_bare, %2\r\n call print_nl\r\n return", + "3b8022da": "function __main__():\r\nblock0:\r\n store xs : list[int[64], 3]\r\n %0 = const_i64 3\r\n store xs.__len, %0 : int[64]\r\n store ys : list[int[64], 3]\r\n %1 = const_i64 3\r\n store ys.__len, %1 : int[64]\r\n %2 = const_i64 0\r\n %3 = const_i64 10\r\n IndexStore xs, %2, %3\r\n %4 = const_i64 1\r\n %5 = const_i64 20\r\n IndexStore xs, %4, %5\r\n %6 = const_i64 2\r\n %7 = const_i64 30\r\n IndexStore xs, %6, %7\r\n %8 = const_i64 0\r\n %9 = const_i64 1\r\n IndexStore ys, %8, %9\r\n %10 = const_i64 1\r\n %11 = const_i64 2\r\n IndexStore ys, %10, %11\r\n %12 = const_i64 2\r\n %13 = const_i64 3\r\n IndexStore ys, %12, %13\r\n %14 = const_i64 0\r\n %15 = const_i64 3\r\n %16 = const_i64 1\r\n store i, %14 : int[64]\r\n jump block4\r\nblock4:\r\n %17 = load i\r\n %18 = lt %17, %15\r\n branch %18, block1, block2\r\nblock1:\r\n %19 = load i\r\n %20 = Index xs, %19\r\n store x, %20 : int[64]\r\n %21 = load i\r\n call print_bare, %21\r\n call print_nl\r\n jump block3\r\nblock3:\r\n %22 = load i\r\n %23 = const_i64 1\r\n %24 = add %22, %23\r\n store i, %24\r\n jump block4\r\nblock2:\r\n %25 = const_i64 0\r\n %26 = const_i64 3\r\n %27 = const_i64 1\r\n store zip_i0, %25 : int[64]\r\n jump block8\r\nblock8:\r\n %28 = load zip_i0\r\n %29 = lt %28, %26\r\n branch %29, block5, block6\r\nblock5:\r\n %30 = load zip_i0\r\n %31 = Index xs, %30\r\n store a, %31 : int[64]\r\n %32 = Index ys, %30\r\n store b, %32 : int[64]\r\n %33 = load a\r\n call print_bare, %33\r\n call print_nl\r\n jump block7\r\nblock7:\r\n %34 = load zip_i0\r\n %35 = const_i64 1\r\n %36 = add %34, %35\r\n store zip_i0, %36\r\n jump block8\r\nblock6:\r\n return", + "b4973ce3": "function __main__():\r\nblock0:\r\n %0 = const_i64 9223372036854775807\r\n store x, %0 : int[64]\r\n %1 = const_i64 1\r\n store y, %1 : int[64]\r\n %2 = load x\r\n %3 = load y\r\n %4 = wrapadd %2, %3\r\n store z, %4 : int[64]\r\n %5 = load z\r\n call print_bare, %5\r\n call print_nl\r\n return", + "8b9a4015": "function __main__():\r\nblock0:\r\n %0 = const_i64 -7\r\n %1 = const_i64 3\r\n %2 = mod %0, %1\r\n call print_bare, %2\r\n call print_nl\r\n %3 = const_i64 7\r\n %4 = const_i64 -3\r\n %5 = mod %3, %4\r\n call print_bare, %5\r\n call print_nl\r\n return", + "e367543b": "function __main__():\r\nblock0:\r\n %0 = const_i64 5\r\n %1 = const_i64 63\r\n %2 = shl %0, %1\r\n call print_bare, %2\r\n call print_nl\r\n return" } diff --git a/web/apps/web/src/docs/pages/engine.ts b/web/apps/web/src/docs/pages/engine.ts index b7bc71e..7e095f5 100644 --- a/web/apps/web/src/docs/pages/engine.ts +++ b/web/apps/web/src/docs/pages/engine.ts @@ -4,7 +4,7 @@ const architecture: DocPage = { slug: "architecture", title: "Engine architecture", description: - "Four layers from .py source to executable memory: frontend, IR, verifier, encoder.", + "Four layers from .lio source to executable memory: frontend, IR, verifier, encoder.", group: "engine", tags: [ "architecture", @@ -29,7 +29,7 @@ const architecture: DocPage = { items: [ { title: "1 Β· Frontend", - text: "`src/frontend/frontend.py`: parses `.py`, runs the extended PEP-526 checks, emits text IR. Pure Python, no engine needed.", + text: "`src/frontend/frontend.py`: parses `.lio`, runs the extended PEP-526 checks, emits text IR. Pure Python, no engine needed.", }, { title: "2 Β· IR text format", @@ -64,8 +64,8 @@ const architecture: DocPage = { { kind: "code", example: { - title: "tests/programs/arithmetic.py", - sourceRef: "tests/programs/arithmetic.py", + title: "tests/programs/arithmetic.lio", + sourceRef: "tests/programs/arithmetic.lio", expectedRef: "tests/programs/expected/arithmetic.out", source: `x: int[64] = 10 y: int[64] = 20 @@ -86,15 +86,18 @@ block0: %4 = add %2, %3 store z, %4 : int[64] %5 = load z - call print, %5 + call print_bare, %5 + call print_nl %6 = load x %7 = load y %8 = sub %6, %7 - call print, %8 + call print_bare, %8 + call print_nl %9 = load x %10 = load y %11 = mul %9, %10 - call print, %11 + call print_bare, %11 + call print_nl return`, asm: `main: push rbp @@ -159,6 +162,37 @@ block0: }, ], }, + { + id: "where-to-go-next", + title: "Where to go next", + blocks: [ + { + kind: "linkcards", + items: [ + { + title: "Source to machine", + href: "/docs/source-to-machine", + text: "The four artifacts one program produces, end to end.", + }, + { + title: "Registers & ABI", + href: "/docs/registers-abi", + text: "The register file and the calling discipline.", + }, + { + title: "Instruction encoding", + href: "/docs/instruction-encoding", + text: "Why the encoder is hand-rolled, and how it is proven.", + }, + { + title: "Verification", + href: "/docs/verification", + text: "How the claims on this page get checked.", + }, + ], + }, + ], + }, ], } @@ -192,7 +226,7 @@ const dualTier: DocPage = { example: { lang: "bash", title: "forcing the native lane", - source: `$ lithon tests/programs/float.py --strict + source: `$ lithon tests/programs/float.lio --strict [tier1] native 0.3333333333333333`, note: "`[tier1] native` goes to stderr; program output goes to stdout. Under `--strict`, exit 0 means the native tier really ran.", @@ -227,7 +261,7 @@ const dualTier: DocPage = { example: { lang: "bash", title: "terminal", - source: `$ lithon program.py --strict # native only, refuse to fall back + source: `$ lithon program.lio --strict # native only, refuse to fall back $ ./build/tier_runner prog.ir --strict # same thing, two-step form`, }, }, @@ -294,7 +328,7 @@ const typechecking: DocPage = { example: { lang: "bash", title: "terminal", - source: `$ python3 tools/typecheck.py program.py + source: `$ python3 tools/typecheck.py program.lio $ echo $? 0`, }, diff --git a/web/apps/web/src/docs/pages/language.ts b/web/apps/web/src/docs/pages/language.ts index a7f4c4b..36fd65a 100644 --- a/web/apps/web/src/docs/pages/language.ts +++ b/web/apps/web/src/docs/pages/language.ts @@ -175,11 +175,13 @@ block0: %2 = toint %1 : int[64] store n, %2 : int[64] %3 = load n - call print, %3 + call print_bare, %3 + call print_nl %4 = inputint 656e7465722061206e756d6265723a20 : int[32] store w, %4 : int[32] %5 = load w - call print, %5 + call print_bare, %5 + call print_nl return`, note: "`int()`/`float()` lower to `toint`/`tofloat`; `int(w, input(prompt))` lowers to one `inputint` op, the prompt hex-encoded so any character survives the text IR.", }, @@ -204,8 +206,8 @@ block0: { kind: "code", example: { - title: "tests/typed_regression/mixed_numeric.py", - sourceRef: "tests/typed_regression/mixed_numeric.py", + title: "tests/typed_regression/mixed_numeric.lio", + sourceRef: "tests/typed_regression/mixed_numeric.lio", expectedRef: "tests/typed_regression/expected/mixed_numeric.out", source: `i: int[64] = 10 f: float[64] = 2.5 @@ -297,8 +299,8 @@ print(narrow)`, { kind: "code", example: { - title: "tests/typed_regression/uninit_decl.py", - sourceRef: "tests/typed_regression/uninit_decl.py", + title: "tests/typed_regression/uninit_decl.lio", + sourceRef: "tests/typed_regression/uninit_decl.lio", expectedRef: "tests/typed_regression/expected/uninit_decl.out", source: `i: int[8] i = 5 @@ -361,7 +363,7 @@ const controlFlow: DocPage = { slug: "control-flow", title: "Control flow", description: - "if / elif / else, while, for over range, and the one construct that does not exist.", + "if / elif / else, while, for over range, try / except over the built-in traps, and the one construct that does not exist.", group: "language", tags: [ "control flow", @@ -372,6 +374,10 @@ const controlFlow: DocPage = { "range", "branch", "loop", + "try", + "except", + "exception", + "trap", ], sections: [ { @@ -385,8 +391,8 @@ const controlFlow: DocPage = { { kind: "code", example: { - title: "tests/typed_regression/if.py", - sourceRef: "tests/typed_regression/if.py", + title: "tests/typed_regression/if.lio", + sourceRef: "tests/typed_regression/if.lio", expectedRef: "tests/typed_regression/expected/if.out", source: `x: int[64] = 5 if x < 3: @@ -405,11 +411,13 @@ block0: branch %3, block1, block2 block1: %4 = const_i64 1 - call print, %4 + call print_bare, %4 + call print_nl jump block3 block2: %5 = const_i64 3 - call print, %5 + call print_bare, %5 + call print_nl jump block3 block3: return`, @@ -430,8 +438,8 @@ block3: { kind: "code", example: { - title: "tests/typed_regression/while.py", - sourceRef: "tests/typed_regression/while.py", + title: "tests/typed_regression/while.lio", + sourceRef: "tests/typed_regression/while.lio", expectedRef: "tests/typed_regression/expected/while.out", typecheck: false, source: `i: int[64] = 0 @@ -447,7 +455,7 @@ print(total)`, kind: "note", tone: "warn", title: "The checker does not cover `while` yet", - text: "`tools/typecheck.py` still answers `statement While not supported by the type-checker yet`: including for the repo's own `while.py`. The engine lowers and executes `while` correctly (Tier-0 and Tier-1 both); it is the standalone Python checker that lags. Until it catches up, `while` programs are verified through the regression suites rather than the checker.", + text: "`tools/typecheck.py` still answers `statement While not supported by the type-checker yet`: including for the repo's own `while.lio`. The engine lowers and executes `while` correctly (Tier-0 and Tier-1 both); it is the standalone Python checker that lags. Until it catches up, `while` programs are verified through the regression suites rather than the checker.", }, ], }, @@ -462,8 +470,8 @@ print(total)`, { kind: "code", example: { - title: "tests/typed_regression/for_range.py", - sourceRef: "tests/typed_regression/for_range.py", + title: "tests/typed_regression/for_range.lio", + sourceRef: "tests/typed_regression/for_range.lio", expectedRef: "tests/typed_regression/expected/for_range.out", source: `total: int[64] = 0 i: int[64] = 0 @@ -501,7 +509,8 @@ block3: jump block4 block2: %13 = load total - call print, %13 + call print_bare, %13 + call print_nl return`, note: "The `for` lowers to an init / test / body / increment block structure: a pre-test loop with an explicit back edge, exactly what the encoder emits a `jcc` for.", }, @@ -520,9 +529,9 @@ block2: { kind: "code", example: { - title: "tests/programs/range_step.py", + title: "tests/programs/range_step.lio", typecheck: false, - sourceRef: "tests/programs/range_step.py", + sourceRef: "tests/programs/range_step.lio", expectedRef: "tests/programs/expected/range_step.out", source: `for i in range(1, 10, 3): print(i) @@ -560,9 +569,9 @@ for i in range(-1, -5, -2): { kind: "code", example: { - title: "tests/programs/break_continue.py", + title: "tests/programs/break_continue.lio", typecheck: false, - sourceRef: "tests/programs/break_continue.py", + sourceRef: "tests/programs/break_continue.lio", expectedRef: "tests/programs/expected/break_continue.out", source: `total: int[64] = 0 for i in range(100): @@ -592,9 +601,9 @@ print(total)`, { kind: "code", example: { - title: "tests/typed_regression/iter_helpers.py (excerpt)", + title: "tests/typed_regression/iter_helpers.lio (excerpt)", typecheck: false, - sourceRef: "tests/typed_regression/iter_helpers.py", + sourceRef: "tests/typed_regression/iter_helpers.lio", expectedRef: "tests/typed_regression/expected/iter_helpers.out", source: `xs: list[int[64], 5] xs[0] = 10 @@ -642,8 +651,8 @@ print(total)`, { kind: "code", example: { - title: "tests/typed_regression/if_expr.py (excerpt)", - sourceRef: "tests/typed_regression/if_expr.py", + title: "tests/typed_regression/if_expr.lio (excerpt)", + sourceRef: "tests/typed_regression/if_expr.lio", expectedRef: "tests/typed_regression/expected/if_expr.out", typecheck: false, source: `x: int[64] = 5 @@ -692,6 +701,125 @@ else: }, ], }, + { + id: "try-except", + title: "try / except, over the built-in traps", + blocks: [ + { + kind: "p", + text: "`try`/`except` catches a trap the engine itself raises. There is no exception hierarchy to import and no exception object to bind: the catchable set is closed, and a caught trap is a plain jump in the IR. Both tiers run the same handler region, so a trap caught natively is caught identically in the interpreter.", + }, + { + kind: "code", + example: { + title: "catching a division by zero", + // tools/typecheck.py is a mirror of the older scalar-only static + // checks and does not know `try` yet; the real checker (the C++ + // one, which runs inside tier_runner) does. + typecheck: false, + source: `n: int[64] = 0 +try: + q: int[64] = 100 % n + print(q) +except ZeroDivisionError: + print(1)`, + output: "1", + machine: { + ir: `function __main__(): +block0: + %0 = const_i64 0 + store n, %0 : int[64] + tryenter ZeroDivisionError, block1 + jump block2 +block2: + %1 = const_i64 100 + %2 = load n + %3 = mod %1, %2 + store q, %3 : int[64] + %4 = load q + call print_bare, %4 + call print_nl + tryleave + jump block3 +block1: + trycatch ZeroDivisionError + %5 = const_i64 1 + call print_bare, %5 + call print_nl + jump block3 +block3: + return`, + }, + }, + }, + { + kind: "p", + text: "The IR is the whole mechanism: `tryenter` marks the handler block and the region it guards, `tryleave` ends the protected region on the normal path, and `trycatch` is the landing pad. A caught trap is a branch, not an unwind -- there is no stack to unwind because there is no exception object.", + }, + { + kind: "p", + text: "The catchable set is exactly seven names: `ZeroDivisionError`, `OverflowError`, `ValueError`, `IndexError`, `KeyError`, `EOFError`, and `Exception` for all of them. Anything else is refused at compile time, so a typo in the handler is a diagnostic rather than a silently-never-taken branch.", + }, + { + kind: "code", + example: { + title: "rejected: not a catchable trap", + expect: "error", + tool: "frontend", + errorMatch: "is not a catchable trap", + source: `try: + print(1) +except RuntimeError: + print(2)`, + output: + "LITHON-E0114: `except RuntimeError:` is not a catchable trap -- the catchable set is EOFError, IndexError, KeyError, OverflowError, ValueError, ZeroDivisionError, plus `Exception` for all of them (6.10)", + }, + }, + { + kind: "table", + caption: "which trap lands in which handler", + rows: [ + [ + "`ZeroDivisionError`", + "division or modulo by zero", + ], + ["`OverflowError`", "a dynamic `int[64]` overflow (`LITHON-E0303`), or an input that does not fit the target width (`E0307`)"], + [ + "`ValueError`", + "a float-to-int conversion out of range (`E0309`), a shift count outside 0..63, a `range` step of zero (`E0310`), a non-numeric input (`E0306`)", + ], + ["`IndexError`", "a dynamic index outside a list's bounds"], + ["`KeyError`", "a dict key that is not present"], + ["`EOFError`", "input() reached end of input (`E0308`)"], + ], + }, + { + kind: "note", + tone: "warn", + title: "three things that are deliberately not supported", + text: "A bare `except:` is refused, because it would also catch a system exit and would suggest catching a segfault, which is not catchable. `except ... as e` is refused, because there is no exception object to bind. `try`/`finally` without an `except` is refused, because a `finally` that runs on the uncaught path is a different construct. And a trap raised inside a *callee* is not catchable by the caller: only traps raised in the same function as the `try` are.", + }, + { + kind: "code", + example: { + title: "rejected: a bare except", + expect: "error", + tool: "frontend", + errorMatch: "a bare `except:` is not supported", + source: `try: + print(1) +except: + print(2)`, + output: + "LITHON-E0114: a bare `except:` is not supported -- it also catches system-exit, and here it would suggest catching a segfault, which is not catchable; name the trap (`except Exception:` catches every catchable trap) (6.10)", + }, + }, + { + kind: "p", + text: "An uncaught trap is still a trap: the program stops with the engine's own diagnostic, and the exit code is non-zero. See [Errors](/docs/errors) for the full code family and [Semantics](/docs/semantics) for the arithmetic traps in detail.", + }, + ], + }, ], } @@ -721,8 +849,8 @@ const functions: DocPage = { { kind: "code", example: { - title: "tests/typed_regression/function.py", - sourceRef: "tests/typed_regression/function.py", + title: "tests/typed_regression/function.lio", + sourceRef: "tests/typed_regression/function.lio", expectedRef: "tests/typed_regression/expected/function.out", source: `def add(a: int[64], b: int[64]) -> int[64]: return a + b @@ -745,7 +873,8 @@ block0: %2 = call add, %0, %1 store x, %2 : int[64] %3 = load x - call print, %3 + call print_bare, %3 + call print_nl return`, note: "Real frontend output. Parameters live in the frame like any other slot; the call passes constants as immediates and stores the return value into the caller's `x` slot.", }, @@ -779,8 +908,8 @@ print(x)`, { kind: "code", example: { - title: "tests/typed_regression/recursion.py", - sourceRef: "tests/typed_regression/recursion.py", + title: "tests/typed_regression/recursion.lio", + sourceRef: "tests/typed_regression/recursion.lio", expectedRef: "tests/typed_regression/expected/recursion.out", source: `def fib(n: int[64]) -> int[64]: if n < 2: @@ -841,7 +970,8 @@ block0: %1 = call fib, %0 store x, %1 : int[64] %2 = load x - call print, %2 + call print_bare, %2 + call print_nl return`, note: "Real frontend output. The base case and the recursive case are separate blocks; `call fib` appears twice, once per recursive branch.", }, @@ -929,13 +1059,13 @@ print(-7 % 3)`, blocks: [ { kind: "p", - text: "`==` `<` `>` and the other comparisons produce `bool`. `and` `or` `not` operate on `bool`. Note what is *not* here yet: `!=` (`NotEq`) is the one comparison the frontend has not lowered: write `not (a == b)` in the meantime.", + text: "`==` `!=` `<` `>` and the other comparisons produce `bool`. `and` `or` `not` operate on `bool`. `!=` is a first-class comparison with its own IR op (`ne`), not an `==` followed by a `not`, so the optimizer and branch fusion see the real predicate.", }, { kind: "code", example: { - title: "tests/typed_regression/comparison.py", - sourceRef: "tests/typed_regression/comparison.py", + title: "tests/typed_regression/comparison.lio", + sourceRef: "tests/typed_regression/comparison.lio", expectedRef: "tests/typed_regression/expected/comparison.out", source: `a: int[64] = 1 b: int[64] = 2 @@ -947,6 +1077,23 @@ print(c < b)`, output: "True\nTrue\nTrue\nTrue", }, }, + { + kind: "code", + example: { + title: "tests/typed_regression/ne.lio (excerpt)", + sourceRef: "tests/typed_regression/ne.lio", + expectedRef: "tests/typed_regression/expected/ne.out", + typecheck: false, + source: `a: int[64] = 1 +b: int[64] = 2 +print(a != b) +print(a != a) +lo: int[8] = -128 +hi: int[8] = 127 +print(lo != hi)`, + output: "True\nFalse\nTrue", + }, + }, { kind: "note", tone: "note", @@ -1015,8 +1162,8 @@ print(z)`, { kind: "code", example: { - title: "tests/typed_regression/int64_wrap.py", - sourceRef: "tests/typed_regression/int64_wrap.py", + title: "tests/typed_regression/int64_wrap.lio", + sourceRef: "tests/typed_regression/int64_wrap.lio", expectedRef: "tests/typed_regression/expected/int64_wrap.out", typecheck: false, source: `x: int[64] = 9223372036854775807 @@ -1090,8 +1237,8 @@ const containers: DocPage = { { kind: "code", example: { - title: "tests/programs/list_basic.py (excerpt)", - sourceRef: "tests/programs/list_basic.py", + title: "tests/programs/list_basic.lio (excerpt)", + sourceRef: "tests/programs/list_basic.lio", expectedRef: "tests/programs/expected/list_basic.out", typecheck: false, source: `xs: list[int[64], 6] @@ -1174,8 +1321,8 @@ print(t[3])`, { kind: "code", example: { - title: "tests/programs/dict_basic.py (excerpt)", - sourceRef: "tests/programs/dict_basic.py", + title: "tests/programs/dict_basic.lio (excerpt)", + sourceRef: "tests/programs/dict_basic.lio", expectedRef: "tests/programs/expected/dict_basic.out", typecheck: false, source: `d: dict[int[64], int[64], 4] = {1: 10, 2: 20, 3: 30, 4: 40} @@ -1193,7 +1340,43 @@ print(d[j])`, kind: "note", tone: "note", title: "More entries than buckets is a duplicate-bucket problem", - text: "`dict_basic.py` stays one short of it and leans on the probe instead. `key_pairs.py` is the test that pins a collision down to fixed keys rather than leaving it to chance.", + text: "`dict_basic.lio` stays one short of it and leans on the probe instead. `dict_table.lio` is the test that pins a collision down to fixed keys rather than leaving it to chance.", + }, + ], + }, + { + id: "where-to-go-next", + title: "Where to go next", + blocks: [ + { + kind: "linkcards", + items: [ + { + title: "Pointers", + href: "/docs/pointers", + text: "Raw addresses, `addressof`/`valueof`, and the `_` naming rule.", + }, + { + title: "Structs", + href: "/docs/structs", + text: "Packed storage: fields at fixed offsets, no padding.", + }, + { + title: "Enums", + href: "/docs/enums", + text: "A tag plus a payload, read with `match`.", + }, + { + title: "Type functions", + href: "/docs/type-functions", + text: "Compile-time type constructors that expand structurally.", + }, + { + title: "Built-in functions", + href: "/docs/builtins", + text: "The whole prelude, one row per name.", + }, + ], }, ], }, @@ -1251,8 +1434,8 @@ print(valueof(p))`, { kind: "code", example: { - title: "tests/programs/ptr_basic.py (excerpt)", - sourceRef: "tests/programs/ptr_basic.py", + title: "tests/programs/ptr_basic.lio (excerpt)", + sourceRef: "tests/programs/ptr_basic.lio", expectedRef: "tests/programs/expected/ptr_basic.out", typecheck: false, source: `x: int[64] = 5 @@ -1309,8 +1492,8 @@ print(_p == _q)`, { kind: "code", example: { - title: "reading through a pointer (ptr_basic.py excerpt)", - sourceRef: "tests/programs/ptr_basic.py", + title: "reading through a pointer (ptr_basic.lio excerpt)", + sourceRef: "tests/programs/ptr_basic.lio", typecheck: false, source: `f: float[64] = 2.5 _pf: ptr[float[64]] = addressof(f) @@ -1366,8 +1549,8 @@ const structs: DocPage = { { kind: "code", example: { - title: "tests/programs/struct_basic.py (excerpt)", - sourceRef: "tests/programs/struct_basic.py", + title: "tests/programs/struct_basic.lio (excerpt)", + sourceRef: "tests/programs/struct_basic.lio", expectedRef: "tests/programs/expected/struct_basic.out", typecheck: false, source: `class Point(Struct): @@ -1396,8 +1579,8 @@ print(p.y)`, { kind: "code", example: { - title: "unwritten fields read zero (struct_basic.py excerpt)", - sourceRef: "tests/programs/struct_basic.py", + title: "unwritten fields read zero (struct_basic.lio excerpt)", + sourceRef: "tests/programs/struct_basic.lio", typecheck: false, source: `class SockAddrIn(Struct): family: int[16] @@ -1458,8 +1641,8 @@ print(s.active)`, { kind: "code", example: { - title: "float fields (struct_basic.py excerpt)", - sourceRef: "tests/programs/struct_basic.py", + title: "float fields (struct_basic.lio excerpt)", + sourceRef: "tests/programs/struct_basic.lio", typecheck: false, source: `class Vec(Struct): x: float[64] @@ -1568,8 +1751,8 @@ const enums: DocPage = { { kind: "code", example: { - title: "tests/programs/enum_basic.py (excerpt)", - sourceRef: "tests/programs/enum_basic.py", + title: "tests/programs/enum_basic.lio (excerpt)", + sourceRef: "tests/programs/enum_basic.lio", expectedRef: "tests/programs/expected/enum_basic.out", typecheck: false, source: `class Shape(Enum): @@ -1632,8 +1815,8 @@ match s: { kind: "code", example: { - title: "tests/programs/enum_struct.py (excerpt)", - sourceRef: "tests/programs/enum_struct.py", + title: "tests/programs/enum_struct.lio (excerpt)", + sourceRef: "tests/programs/enum_struct.lio", typecheck: false, source: `class Endpoint(Enum): Empty @@ -1683,8 +1866,8 @@ c.id = 7`, { kind: "code", example: { - title: "tests/programs/enum_struct.py (excerpt)", - sourceRef: "tests/programs/enum_struct.py", + title: "tests/programs/enum_struct.lio (excerpt)", + sourceRef: "tests/programs/enum_struct.lio", typecheck: false, source: `class Endpoint(Enum): Empty @@ -1823,8 +2006,8 @@ const typeFunctions: DocPage = { { kind: "code", example: { - title: "tests/programs/typefunc_basic.py (excerpt)", - sourceRef: "tests/programs/typefunc_basic.py", + title: "tests/programs/typefunc_basic.lio (excerpt)", + sourceRef: "tests/programs/typefunc_basic.lio", expectedRef: "tests/programs/expected/typefunc_basic.out", typecheck: false, source: `def Fixed(t, n) -> NewType: @@ -1860,8 +2043,8 @@ print(xs[0] + xs[1] + xs[2])`, { kind: "code", example: { - title: "tests/programs/typefunc_basic.py (excerpt)", - sourceRef: "tests/programs/typefunc_basic.py", + title: "tests/programs/typefunc_basic.lio (excerpt)", + sourceRef: "tests/programs/typefunc_basic.lio", typecheck: false, source: `def Alias(t) -> NewType: return {t} @@ -1889,8 +2072,8 @@ print(a)`, { kind: "code", example: { - title: "tests/programs/typefunc_uses.py (excerpt)", - sourceRef: "tests/programs/typefunc_uses.py", + title: "tests/programs/typefunc_uses.lio (excerpt)", + sourceRef: "tests/programs/typefunc_uses.lio", expectedRef: "tests/programs/expected/typefunc_uses.out", typecheck: false, source: `def Word() -> NewType: @@ -2000,8 +2183,8 @@ const methods: DocPage = { { kind: "code", example: { - title: "tests/programs/method_basic.py (excerpt)", - sourceRef: "tests/programs/method_basic.py", + title: "tests/programs/method_basic.lio (excerpt)", + sourceRef: "tests/programs/method_basic.lio", expectedRef: "tests/programs/expected/method_basic.out", typecheck: false, source: `class Counter(Struct): @@ -2015,7 +2198,7 @@ const methods: DocPage = { c: Counter print(c.add(5)) print(c.add(7))`, - output: "5", + output: "5\n12", }, }, { @@ -2035,8 +2218,8 @@ print(c.add(7))`, { kind: "code", example: { - title: "tests/programs/method_enum.py (excerpt)", - sourceRef: "tests/programs/method_enum.py", + title: "tests/programs/method_enum.lio (excerpt)", + sourceRef: "tests/programs/method_enum.lio", expectedRef: "tests/programs/expected/method_enum.out", typecheck: false, source: `class Shape(Enum): diff --git a/web/apps/web/src/docs/pages/machine.ts b/web/apps/web/src/docs/pages/machine.ts index 8fe8187..e116140 100644 --- a/web/apps/web/src/docs/pages/machine.ts +++ b/web/apps/web/src/docs/pages/machine.ts @@ -4,7 +4,7 @@ const sourceToMachine: DocPage = { slug: "source-to-machine", title: "Source to machine", description: - "The full walk: .py β†’ typed IR β†’ verified SSA β†’ x86-64 bytes, with the real artifacts at each step.", + "The full walk: .lio β†’ typed IR β†’ verified SSA β†’ x86-64 bytes, with the real artifacts at each step.", group: "machine", tags: [ "machine", @@ -28,7 +28,7 @@ const sourceToMachine: DocPage = { kind: "steps", items: [ { - title: "Source (.py)", + title: "Source (.lio)", text: "What you wrote. Annotations and all.", }, { @@ -50,8 +50,8 @@ const sourceToMachine: DocPage = { example: { lang: "bash", title: "seeing it yourself", - source: `$ lithon prog.py --ir # stop after IR -$ python3 src/frontend/frontend.py prog.py > prog.ir + source: `$ lithon prog.lio --ir # stop after IR +$ python3 src/frontend/frontend.py prog.lio > prog.ir $ ./build/tier_runner prog.ir --strict -v [tier1] native`, }, @@ -69,8 +69,8 @@ $ ./build/tier_runner prog.ir --strict -v { kind: "code", example: { - title: "tests/typed_regression/if.py β†’ IR", - sourceRef: "tests/typed_regression/if.py", + title: "tests/typed_regression/if.lio β†’ IR", + sourceRef: "tests/typed_regression/if.lio", source: `x: int[64] = 5 if x < 3: print(1) @@ -88,11 +88,13 @@ block0: branch %3, block1, block2 block1: %4 = const_i64 1 - call print, %4 + call print_bare, %4 + call print_nl jump block3 block2: %5 = const_i64 3 - call print, %5 + call print_bare, %5 + call print_nl jump block3 block3: return`, @@ -139,8 +141,8 @@ block3: { kind: "code", example: { - title: "tests/typed_regression/function.py β†’ IR", - sourceRef: "tests/typed_regression/function.py", + title: "tests/typed_regression/function.lio β†’ IR", + sourceRef: "tests/typed_regression/function.lio", source: `def add(a: int[64], b: int[64]) -> int[64]: return a + b @@ -162,7 +164,8 @@ block0: %2 = call add, %0, %1 store x, %2 : int[64] %3 = load x - call print, %3 + call print_bare, %3 + call print_nl return`, asm: `add: push rbp @@ -202,8 +205,8 @@ main: { kind: "code", example: { - title: "tests/typed_regression/recursion.py", - sourceRef: "tests/typed_regression/recursion.py", + title: "tests/typed_regression/recursion.lio", + sourceRef: "tests/typed_regression/recursion.lio", expectedRef: "tests/typed_regression/expected/recursion.out", source: `def fib(n: int[64]) -> int[64]: if n < 2: @@ -243,7 +246,8 @@ block0: %1 = call fib, %0 store x, %1 : int[64] %2 = load x - call print, %2 + call print_bare, %2 + call print_nl return`, note: "Real frontend output. The base case is block1; block2 computes both recursive calls and adds them. The encoder turns each `call` into a real `call fib` with the frame discipline above.", }, @@ -251,6 +255,37 @@ block0: }, ], }, + { + id: "where-to-go-next", + title: "Where to go next", + blocks: [ + { + kind: "linkcards", + items: [ + { + title: "Registers & ABI", + href: "/docs/registers-abi", + text: "The register file and the calling discipline the IR maps onto.", + }, + { + title: "Instruction encoding", + href: "/docs/instruction-encoding", + text: "How the IR becomes bytes, and how those bytes are proven.", + }, + { + title: "Optimizer & SSA pipeline", + href: "/docs/pipeline", + text: "The passes that run between the IR and the encoder.", + }, + { + title: "Dual-tier execution", + href: "/docs/dual-tier", + text: "What happens when the native tier refuses to run a program.", + }, + ], + }, + ], + }, ], } diff --git a/web/apps/web/src/docs/pages/reference.ts b/web/apps/web/src/docs/pages/reference.ts index 3d3b8c3..9defe5f 100644 --- a/web/apps/web/src/docs/pages/reference.ts +++ b/web/apps/web/src/docs/pages/reference.ts @@ -68,20 +68,111 @@ const errors: DocPage = { "`range(a, b, 0)` with a literal zero step", ], [ - "`E0108`-`E0125`, 1 (static refusal)", - "annotation, container, pointer, assignment, loop, declaration, tuple/dict, and function-header restrictions: one code per construct (see docs/lithon_error_system.md)", + "`E0108`, 1 (static refusal)", + "Unsupported type-annotation form", ], [ - "`E0126`-`E0131`, 1 (static refusal)", - "struct/enum definition, field, access, variant construction, and `match` restrictions", + "`E0109`, 1 (static refusal)", + "Unknown or badly-sized annotation type name", ], [ - "`E0132`-`E0135`, 1 (static refusal)", - "type-function and method definition/application restrictions", + "`E0110`, 1 (static refusal)", + "Annotation missing a required field", + ], + [ + "`E0111`, 1 (static refusal)", + "Bad container capacity / literal count / `str[N]` capacity or literal length", + ], + [ + "`E0112`, 1 (static refusal)", + "Container element must be a plain scalar", + ], + [ + "`E0113`, 1 (static refusal)", + "Pointer (`ptr[T]`) declaration rules", + ], + [ + "`E0114`, 1 (static refusal)", + "Unsupported or restricted program construct -- the catch-all: an operator that does not exist, a `try`/`except` shape that is not allowed, a string literal used where a value is needed", + ], + [ + "`E0115`, 1 (static refusal)", + "Unsupported builtin call form", + ], + [ + "`E0116`, 1 (static refusal)", + "Builtin argument rules (`valueof`/`addressof`/`wrap_*`)", + ], + [ + "`E0117`, 1 (static refusal)", + "Indexing / container-subscript restrictions", + ], + [ + "`E0118`, 1 (static refusal)", + "`else` clause on a `while`/`for` loop", + ], + ["`E0119`, 1 (static refusal)", "`for`-loop form restrictions"], + [ + "`E0120`, 1 (static refusal)", + "`break`/`continue` outside a loop", + ], + [ + "`E0121`, 1 (static refusal)", + "Assignment / target restrictions", + ], + [ + "`E0122`, 1 (static refusal)", + "Declaration and re-declaration rules (including the `_` prefix rule for pointers)", + ], + ["`E0123`, 1 (static refusal)", "Tuple/dict literal shape"], + [ + "`E0124`, 1 (static refusal)", + "Function-header restrictions", + ], + ["`E0125`, 1 (static refusal)", "Pointer arithmetic form"], + [ + "`E0126`, 1 (static refusal)", + "Struct definition (must derive from `Struct`; fields are annotations only)", + ], + [ + "`E0127`, 1 (static refusal)", + "Struct field type (int[w]/float[64]/bool or an earlier struct; no containers/pointers)", + ], + [ + "`E0128`, 1 (static refusal)", + "Struct field access (unknown field, a whole struct used where a value is needed, or a `str` field reached through a pointer into the struct)", + ], + [ + "`E0129`, 1 (static refusal)", + "Enum definition (must derive from `Enum`; variants are bare unit names, payload annotations, or inline `class V(Struct)`)", + ], + [ + "`E0130`, 1 (static refusal)", + "Enum variant construction (unknown variant, wrong payload shape, unknown/missing/duplicate struct-payload field)", + ], + [ + "`E0131`, 1 (static refusal)", + "`match` on an enum (bad subject/pattern, non-exhaustive, a guard, a wildcard that is not last, or an enum used where a value is needed)", + ], + [ + "`E0132`, 1 (static refusal)", + "Type-function definition (`-> NewType`): bad signature, a body that is not a one-element `{ T }`, a duplicate name, or a name colliding with a runtime function/struct/enum", + ], + [ + "`E0133`, 1 (static refusal)", + "Type-function application: an unknown callee, keyword/starred arguments, an arity mismatch, or a recursive expansion", + ], + [ + "`E0134`, 1 (static refusal)", + "Method definition (a `def` in a struct/enum body): a decorator, `/`/`*`/`**`/defaults, a missing or annotated `self`, a non-scalar parameter/return, a duplicate name, or a name colliding with a field or variant", + ], + [ + "`E0135`, 1 (static refusal)", + "Method application: an unknown method, a field where a method is called, a non-struct/enum receiver, an arity mismatch, a recursive inline, or a method with no result used as a value", ], [ "`E0201`, 2 (environmental)", - "Fallible value read before `.ok` check", + "Fallible value read before `.ok` check -- designed, not built: `fallible[T]` does not exist yet", ], ["`E0301`, 3 (dynamic trap)", "Division by zero: shipped"], ["`E0302`, 3 (dynamic trap)", "Modulo by zero: shipped"], @@ -114,6 +205,12 @@ const errors: DocPage = { ], ], }, + { + kind: "note", + tone: "note", + title: "the tier digit is the contract", + text: "The first digit after `E` is not decoration: it says whether the failure was knowable before the program ran. A tier-1 code is a refusal -- the program never started. A tier-3 code is a trap -- the program started, and stopped at a specific operation. `try`/`except` can only catch tier-3 traps, because a tier-1 refusal has no program to catch it in. Every code the source uses must be listed in docs/whitepaper.md; `tools/check_error_codes.py` fails the build otherwise.", + }, ], }, { @@ -204,10 +301,14 @@ print(i)`, example: { lang: "bash", title: "terminal", - source: `$ lithon div_zero.py # dynamic divisor + source: `$ lithon div_zero.lio # dynamic divisor LITHON-E0301: division by zero`, }, }, + { + kind: "p", + text: "Because the message is the same in both tiers, a trap can be caught by name and handled identically either way. `try`/`except` catches exactly these tier-3 traps -- see [Control flow](/docs/control-flow#try-except) for the catchable set and the three shapes that are deliberately refused.", + }, ], }, ], @@ -217,7 +318,7 @@ const builtins: DocPage = { slug: "builtins", title: "Built-in functions", description: - "print, len, range, addressof, valueof, contains, and the wrap_* family: the whole prelude.", + "print, len, range, addressof, valueof, contains, enumerate, zip, and the wrap_* family: the whole prelude.", group: "reference", tags: [ "builtins", @@ -227,6 +328,10 @@ const builtins: DocPage = { "addressof", "valueof", "contains", + "enumerate", + "zip", + "append", + "split", "wrap", "prelude", ], @@ -305,12 +410,63 @@ const builtins: DocPage = { ["`wrap_mul(a, b)`", "wrapping integer mul"], ], }, + { + kind: "p", + text: "Two more names are built in, but only in a `for` header: `enumerate(xs)` yields `(index, value)` and `zip(xs, ys)` yields pairs, stopping at the shortest input. Used anywhere else they are refused, because a general iteration protocol does not exist yet.", + }, + { + kind: "code", + example: { + title: "enumerate and zip in a for header", + // tools/typecheck.py is a mirror of the older scalar-only static + // checks and does not know the container type language yet; the + // real checker (the C++ one, which runs inside tier_runner) does. + typecheck: false, + source: `xs: list[int[64], 3] +ys: list[int[64], 3] +xs[0] = 10 +xs[1] = 20 +xs[2] = 30 +ys[0] = 1 +ys[1] = 2 +ys[2] = 3 +for i, x in enumerate(xs): + print(i) +for a, b in zip(xs, ys): + print(a)`, + output: "0\n1\n2\n10\n20\n30", + }, + }, + { + kind: "p", + text: "Three names behave like methods rather than free functions: `xs.append(v)` on a `list` (which never grows the list -- it can only fail with a full list), `s.find(sub)` / `s.count(sub)` on a bounded string, and `s.split(sep)` into a declared destination list.", + }, { kind: "note", tone: "note", title: "wrap_* is the explicit opt-in", text: "Provable static overflow of plain `+`/`-`/`*` is a compile error (E0101/E0303-family) that points you at `wrap_*`. The wrapping result is pinned to be identical across tiers by tools/check_error_codes.py.", }, + { + kind: "linkcards", + items: [ + { + title: "Containers", + href: "/docs/containers", + text: "What `len`, `sum` and `append` actually operate on.", + }, + { + title: "Semantics", + href: "/docs/semantics", + text: "Where Lithon deliberately diverges from CPython.", + }, + { + title: "Errors & diagnostics", + href: "/docs/errors", + text: "The code family, and which of them you can catch.", + }, + ], + }, ], }, { @@ -320,8 +476,8 @@ const builtins: DocPage = { { kind: "code", example: { - title: "tests/typed_regression/int64_wrap.py", - sourceRef: "tests/typed_regression/int64_wrap.py", + title: "tests/typed_regression/int64_wrap.lio", + sourceRef: "tests/typed_regression/int64_wrap.lio", expectedRef: "tests/typed_regression/expected/int64_wrap.out", source: `x: int[64] = 9223372036854775807 y: int[64] = 1 @@ -460,6 +616,41 @@ print(7 % -3)`, ["`for`/`else`", "rejected, never silently dropped"], ], }, + { + kind: "p", + text: "A trap raised by any of the rules above can be caught by name with `try`/`except` -- see [Control flow](/docs/control-flow#try-except) for the catchable set. A static refusal cannot: it happens before the program exists to catch it.", + }, + ], + }, + { + id: "where-to-go-next", + title: "Where to go next", + blocks: [ + { + kind: "linkcards", + items: [ + { + title: "Errors & diagnostics", + href: "/docs/errors", + text: "Every code, and which tier it belongs to.", + }, + { + title: "Built-in functions", + href: "/docs/builtins", + text: "The whole prelude, one row per name.", + }, + { + title: "Control flow", + href: "/docs/control-flow", + text: "if / while / for, and `try`/`except`.", + }, + { + title: "Honest limits", + href: "/docs/limits", + text: "What does not work yet, stated plainly.", + }, + ], + }, ], }, ], @@ -655,7 +846,7 @@ const limits: DocPage = { blocks: [ { kind: "p", - text: 'No stdlib, no import system beyond the built-ins, no classes, no closures, no exceptions. Lithon is an experiment in one narrow question: what happens to a Python-shaped language when the types are mandatory and the machine is visible, and the honest answer to "can it run my app?" is "almost certainly not, yet".', + text: 'No stdlib, no import system beyond the built-ins, no closures, and no exception objects: `try`/`except` catches the engine\'s own traps, but there is nothing to bind. Lithon is an experiment in one narrow question: what happens to a Python-shaped language when the types are mandatory and the machine is visible, and the honest answer to "can it run my app?" is "almost certainly not, yet".', }, { kind: "linkcards", diff --git a/web/apps/web/src/docs/pages/start.ts b/web/apps/web/src/docs/pages/start.ts index ff851f3..5a765e4 100644 --- a/web/apps/web/src/docs/pages/start.ts +++ b/web/apps/web/src/docs/pages/start.ts @@ -22,7 +22,7 @@ const welcome: DocPage = { blocks: [ { kind: "p", - text: "Lithon is a **Python library and native x86-64 execution engine** that runs `.py` source using an extended PEP-526 static typing syntax. Python remains the source language, Lithon is not a separate language, but every supported program is statically verified and can execute as generated machine code, with no LLVM, no Cranelift, and no runtime package.", + text: "Lithon is a **Python library and native x86-64 execution engine** that runs `.lio` source using an extended PEP-526 static typing syntax. The source language is Python's syntax; the `.lio` extension is what marks a file as a Lithon program rather than an ordinary Python script. Every supported program is statically verified and can execute as generated machine code, with no LLVM, no Cranelift, and no runtime package.", }, { kind: "code", @@ -92,7 +92,7 @@ print(result)`, kind: "table", caption: "The design space Lithon explores", rows: [ - ["Syntax", "Python `.py` + extended PEP-526"], + ["Syntax", "Python syntax + extended PEP-526, in a `.lio` file"], [ "Primary execution", "Native x86-64 (CPython: bytecode; PyPy: tracing JIT)", @@ -143,6 +143,16 @@ print(result)`, href: "/docs/mental-model", text: "Two tiers, one rule: prove before you run.", }, + { + title: "Types & widths", + href: "/docs/types", + text: "The one rule that makes the rest checkable: every type carries its width.", + }, + { + title: "Control flow", + href: "/docs/control-flow", + text: "if / while / for, and `try`/`except` over the engine's own traps.", + }, { title: "Machine view", href: "/docs/source-to-machine", @@ -181,7 +191,7 @@ const helloWorld: DocPage = { { kind: "code", example: { - title: "hello.py", + title: "hello.lio", source: `x: int[64] = 10 y: int[64] = 20 @@ -207,8 +217,8 @@ print(result)`, { kind: "code", example: { - title: "tests/programs/arithmetic.py", - sourceRef: "tests/programs/arithmetic.py", + title: "tests/programs/arithmetic.lio", + sourceRef: "tests/programs/arithmetic.lio", expectedRef: "tests/programs/expected/arithmetic.out", source: `x: int[64] = 10 y: int[64] = 20 @@ -261,15 +271,15 @@ print(x)`, items: [ { title: "Type-check only", - text: "`tools/typecheck.py hello.py`: static analysis over the AST, no execution. Exits non-zero with an `RCR error` on the first violation.", + text: "`tools/typecheck.py hello.lio`: static analysis over the AST, no execution. Exits non-zero with an `RCR error` on the first violation.", }, { title: "Compile to IR", - text: "`src/frontend/frontend.py hello.py`: lowers the typed AST to Lithon's text IR on stdout. No engine needed, pure Python.", + text: "`src/frontend/frontend.py hello.lio`: lowers the typed AST to Lithon's text IR on stdout. No engine needed, pure Python.", }, { title: "Run natively", - text: "`lithon hello.py --strict`: the two-step dance: frontend to IR, then `tier_runner` emits x86-64 and calls it. `--strict` refuses rather than falling back to the interpreter.", + text: "`lithon hello.lio --strict`: the two-step dance: frontend to IR, then `tier_runner` emits x86-64 and calls it. `--strict` refuses rather than falling back to the interpreter.", }, ], }, @@ -278,7 +288,7 @@ print(x)`, example: { lang: "bash", title: "terminal", - source: `$ lithon hello.py --strict + source: `$ lithon hello.lio --strict [tier1] native 30`, }, @@ -349,7 +359,7 @@ $ cmake --build build -j$(nproc)`, }, { kind: "p", - text: "There is no Makefile to hand-edit, CMake drives everything. Python appears at exactly one step, turning a `.py` file into IR; once IR exists the native program never touches CPython.", + text: "There is no Makefile to hand-edit, CMake drives everything. Python appears at exactly one step, turning a `.lio` file into IR; once IR exists the native program never touches CPython.", }, ], }, @@ -363,10 +373,10 @@ $ cmake --build build -j$(nproc)`, lang: "bash", title: "install in editable mode, then", source: `$ pip install -e . -$ lithon tests/programs/float.py -$ lithon tests/programs/float.py --strict # native only, refuse to fall back -$ lithon tests/programs/float.py --ir # print the IR and stop -$ lithon tests/programs/float.py -v # say which tier ran, and why`, +$ lithon tests/programs/float.lio +$ lithon tests/programs/float.lio --strict # native only, refuse to fall back +$ lithon tests/programs/float.lio --ir # print the IR and stop +$ lithon tests/programs/float.lio -v # say which tier ran, and why`, }, }, { @@ -385,7 +395,7 @@ $ lithon tests/programs/float.py -v # say which tier ran, and why`, kind: "note", tone: "note", title: "Prefer scripts? The two-step dance is open", - text: "`python3 src/frontend/frontend.py prog.py > prog.ir` then `./build/tier_runner prog.ir --strict`. The `lithon` wrapper is convenience, not a black box.", + text: "`python3 src/frontend/frontend.py prog.lio > prog.ir` then `./build/tier_runner prog.ir --strict`. The `lithon` wrapper is convenience, not a black box.", }, ], }, @@ -500,7 +510,7 @@ Unknown example: { lang: "bash", title: "terminal", - source: `$ lithon program.py --strict`, + source: `$ lithon program.lio --strict`, }, }, { @@ -513,7 +523,7 @@ Unknown { title: "Inside the engine", href: "/docs/architecture", - text: "Four layers from `.py` source to executable memory.", + text: "Four layers from `.lio` source to executable memory.", }, { title: "Dual-tier execution", diff --git a/web/apps/web/src/docs/registry.ts b/web/apps/web/src/docs/registry.ts index 5c695bc..bcb4cb2 100644 --- a/web/apps/web/src/docs/registry.ts +++ b/web/apps/web/src/docs/registry.ts @@ -20,7 +20,8 @@ function pageEntries(pages: readonly DocPage[]): SearchEntry[] { title: section.title, subtitle: page.title, group: page.group, - tags: [...page.tags, ...page.tags.slice(0, 2)], + // the page's own tags, plus the two most specific ones, deduplicated + tags: [...new Set([...page.tags, ...page.tags.slice(0, 2)])], href: `/docs/${page.slug}#${section.id}`, }) } diff --git a/web/apps/web/src/lib/assets.ts b/web/apps/web/src/lib/assets.ts new file mode 100644 index 0000000..5cec10b --- /dev/null +++ b/web/apps/web/src/lib/assets.ts @@ -0,0 +1,27 @@ +/** + * Base-path aware asset URLs. + * + * The site is deployed to GitHub Pages from the repository's `master` + * branch, which serves it from a sub-path (`/Lithon/`) unless a custom + * domain is configured. A hard-coded root-relative URL like + * `/benchmarks/manifest.json` therefore 404s in production, so every + * static asset reference goes through here. + * + * The base is read from the Vite `BASE_URL` global, which Vite replaces + * at build time with the configured `base` -- the same value the router + * and the prerenderer use. In development that is `/`, so nothing changes. + */ +const RAW_BASE: string = import.meta.env.BASE_URL + +/** The deployment base path, always with a leading and trailing slash. */ +export const BASE_PATH = RAW_BASE.endsWith("/") ? RAW_BASE : `${RAW_BASE}/` + +/** + * Turn a root-relative path (`/benchmarks/manifest.json`) into a URL that + * respects the deployment base path. + */ +export function assetUrl(path: string): string { + if (!path.startsWith("/")) return path + if (/^https?:\/\//.test(path)) return path + return `${BASE_PATH}${path.replace(/^\//, "")}` +} diff --git a/web/apps/web/src/lib/ir.ts b/web/apps/web/src/lib/ir.ts index f161c9a..626e8ce 100644 --- a/web/apps/web/src/lib/ir.ts +++ b/web/apps/web/src/lib/ir.ts @@ -1,4 +1,4 @@ -/** +ο»Ώ/** * An executable model of Lithon's IR. * * This is not a simulation of the docs: it parses the exact IR text the @@ -72,6 +72,29 @@ function fmtFloat(v: number): string { return Number.isInteger(v) ? `${v}.0` : String(v) } +/** + * The Linux x86-64 syscall numbers the docs' syscall examples can name. + * A number that is not in this set is not a syscall the kernel knows, so + * it comes back as -ENOSYS (-38) -- the same answer the engine's own + * trampoline gives on a platform with no raw-syscall ABI. + */ +const KNOWN_SYSCALLS = new Set([ + 0, // read + 1, // write + 2, // open + 3, // close + 9, // mmap + 10, // mprotect + 11, // munmap + 12, // brk + 13, // rt_sigaction + 14, // rt_sigprocmask + 39, // getpid + 60, // exit + 63, // uname + 102, // getuid +]) + export function parseIr(text: string): IrProgram { const lines = text.split(/\r?\n/) const functions: IrFunction[] = [] @@ -208,6 +231,33 @@ export function runIr(program: IrProgram, maxSteps = 2000): IrRun { const lookupSlot = (name: string): MemCell | undefined => memory.get(`${frames.length}:${name}`) + /** + * A struct is one blob with several fields at fixed byte offsets, so each + * field needs its own storage: `fieldstore p, 0, %N` and + * `fieldstore p, 8, %N` must not overwrite each other. The blob itself + * keeps the overall address; the fields are keyed by (blob, offset). + */ + const fieldCells = new Map() + + const fieldKey = (name: string, offset: number) => + `${frames.length}:${name}#${offset}` + + const allocField = (name: string, offset: number, type: string): MemCell => { + const key = fieldKey(name, offset) + const existing = fieldCells.get(key) + if (existing) return existing + const blob = allocSlot(name, type) + const cell: MemCell = { + name: `${name}+${offset}`, + address: `${blob.address}+${offset}`, + offset: blob.offset + offset, + type, + value: 0n, + } + fieldCells.set(key, cell) + return cell + } + const pushFrame = (fn: IrFunction, args: unknown[]) => { frames.push({ fn, pc: fn.start, dest: undefined }) current = fn @@ -219,16 +269,29 @@ export function runIr(program: IrProgram, maxSteps = 2000): IrRun { } let guard = 0 + let error: string | undefined + // The engine lowers `print(x)` into `print_bare, %N` + `print_nl`, so the + // value is held here until the closing newline arrives. + let pendingPrint: string | undefined + // The handler registered by the innermost `tryenter`, if any. + let pendingHandler: { trapName: string; handler: string } | undefined + // Set when a trap fires: the block to jump to, or undefined when the trap + // is not catchable and the run has to stop. + let trapTarget: string | undefined const snapshot = () => ({ registers: [...registers.entries()] .filter(([k]) => k.startsWith("%")) .slice(-8) .map(([name, value]) => ({ name, value: fmtValue(value) })), - memory: [...memory.values()].map((m) => ({ ...m })), + memory: [ + ...memory.values(), + // struct fields live in their own map; show them next to the blob + ...[...fieldCells.values()], + ].map((m) => ({ ...m })), }) - while (frames.length > 0 && guard < maxSteps) { + while (frames.length > 0 && guard < maxSteps && !error) { const frame = frames.at(-1) if (frame === undefined) break const instr = current.instrs.find((i) => i.index === frame.pc) @@ -241,6 +304,26 @@ export function runIr(program: IrProgram, maxSteps = 2000): IrRun { } guard += 1 + /** + * Raise a trap. If the innermost `tryenter` catches this trap name, the + * run continues at the handler block; otherwise the run stops with the + * engine's own message. The catchable set is the same closed set the + * engine uses: the six named traps plus `Exception` for all of them. + */ + const trap = (trapName: string, message: string) => { + const handler = pendingHandler + const catches = + handler !== undefined && + (handler.trapName === trapName || handler.trapName === "Exception") + if (!catches) { + error = message + return + } + trapTarget = handler.handler + // the protected region is left by jumping to the handler + pendingHandler = undefined + } + let consumed: { register: string; value: string }[] = [] let returnedValue: unknown const read = (tok: string | undefined): unknown => { @@ -316,17 +399,99 @@ export function runIr(program: IrProgram, maxSteps = 2000): IrRun { } case "store": { const name = instr.args[0] - const v = read(instr.args[1]) const cell = allocSlot(name, instr.width ?? "int[64]") - cell.value = v as number | bigint | boolean - written = { ...cell } + // `store xs : list[int[64], 5]` has no value: it just reserves the + // container's storage, which is already zeroed by `allocSlot`. + if (instr.args.length > 1) { + const v = read(instr.args[1]) + cell.value = v as number | bigint | boolean + written = { ...cell } + } if ( instr.width?.startsWith("float") || (instr.args[1] && floatRegs.has(`${frames.length}:${current.name}:${instr.args[1]}`)) ) floatSlots.add(name) - note = `${cell.address} ← ${fmtValue(v)}` + note = + instr.args.length > 1 + ? `${cell.address} Ò† ${fmtValue(cell.value)}` + : `${cell.address} reserved` + break + } + case "fieldstore": { + // fieldstore p, offset, %N -- write one field of a struct blob + const name = instr.args[0] + const offset = Number(instr.args[1]) + const v = read(instr.args[2]) + const cell = allocField(name, offset, instr.width ?? "int[64]") + cell.value = v as number | bigint | boolean + written = { ...cell } + note = `${cell.address} Ò† ${fmtValue(v)}` + break + } + case "fieldload": { + // fieldload p, offset -- read one field of a struct blob + const name = instr.args[0] + const offset = Number(instr.args[1]) + const cell = allocField(name, offset, instr.width ?? "int[64]") + const v = cell.value + if (instr.dest) { + registers.set(`${frames.length}:${current.name}:${instr.dest}`, v) + if (instr.width?.startsWith("float")) + floatRegs.add(`${frames.length}:${current.name}:${instr.dest}`) + produced = { register: instr.dest, value: fmtValue(v) } + } + note = `${cell.address} Ò†’ ${fmtValue(v)}` + break + } + case "guardnonzero": { + // guardnonzero %N -- the runtime guard the engine emits before a + // division; a zero divisor is a trap, not a value. + const v = read(instr.args[0]) + if (typeof v === "bigint" && v === 0n) { + trap("ZeroDivisionError", "LITHON-E0301: division by zero") + } + note = "guard: divisor is not zero" + break + } + case "tryenter": { + // tryenter , -- mark the handler block and + // the region it guards. A trap raised inside the region jumps to + // the handler instead of stopping the program. + const trapName = instr.args[0] ?? "Exception" + const handler = instr.args[1] + pendingHandler = { trapName, handler } + note = `guard against ${trapName} Ò†’ ${handler}` + break + } + case "tryleave": { + // tryleave -- end the protected region on the normal path. + pendingHandler = undefined + note = "protected region ends" + break + } + case "trycatch": { + // trycatch -- the landing pad: this block runs when the + // named trap was raised inside the protected region. + note = `caught ${instr.args[0] ?? "trap"}` + break + } + case "syscall": { + // syscall number, a1..a6 -- the raw kernel primitive. The docs + // model the *unsupported* case: a number the kernel does not know + // comes back as -ENOSYS (-38), which is exactly what the engine's + // own trampoline returns on a platform with no raw-syscall ABI. + const number = read(instr.args[0]) + const known = KNOWN_SYSCALLS.has(Number(number)) + const v = known ? 0n : -38n + if (instr.dest) { + registers.set(`${frames.length}:${current.name}:${instr.dest}`, v) + produced = { register: instr.dest, value: fmtValue(v) } + } + note = known + ? `syscall ${fmtValue(number)}` + : `syscall ${fmtValue(number)} Ò†’ -ENOSYS (-38)` break } case "add": @@ -354,8 +519,15 @@ export function runIr(program: IrProgram, maxSteps = 2000): IrRun { const y = b as bigint if (instr.op === "sub") v = wrap64(x - y) else if (instr.op === "mul") v = wrap64(x * y) - else if (instr.op === "mod") v = y === 0n ? 0n : x % y - else v = wrap64(x + y) + else if (instr.op === "mod") { + // modulo by zero is a trap, not a value + if (y === 0n) { + trap("ZeroDivisionError", "LITHON-E0302: modulo by zero") + v = 0n + } else { + v = x % y + } + } else v = wrap64(x + y) } if (instr.dest) { registers.set(`${frames.length}:${current.name}:${instr.dest}`, v) @@ -417,19 +589,25 @@ export function runIr(program: IrProgram, maxSteps = 2000): IrRun { } case "shl": case "shr": + case "band": + case "bor": + case "bxor": case "and": case "or": case "xor": { const a = read(instr.args[0]) as bigint const b = read(instr.args[1]) as bigint + // The engine spells the *bitwise* ops `band`/`bor`/`bxor` and keeps + // `and`/`or`/`xor` for the logical ones, so both spellings are + // accepted here and treated as bitwise. const v = instr.op === "shl" ? wrap64(a << (b & 63n)) : instr.op === "shr" ? a >> (b & 63n) - : instr.op === "and" + : instr.op === "band" || instr.op === "and" ? a & b - : instr.op === "or" + : instr.op === "bor" || instr.op === "or" ? a | b : a ^ b if (instr.dest) { @@ -466,7 +644,7 @@ export function runIr(program: IrProgram, maxSteps = 2000): IrRun { type: instr.width ?? "int[64]", value: v as number, } - note = `${name}[${key}] ← ${fmtValue(v)}` + note = `${name}[${key}] Ò† ${fmtValue(v)}` break } case "Index": @@ -504,13 +682,17 @@ export function runIr(program: IrProgram, maxSteps = 2000): IrRun { `&${instr.args[0]}` ) produced = { register: instr.dest, value: `&${instr.args[0]}` } - note = `&${instr.args[0]} β†’ stack address` + note = `&${instr.args[0]} Ò†’ stack address` } break } case "call": { const callee = instr.args[0] - if (callee === "print") { + if (callee === "print" || callee === "print_bare" || callee === "print_sep") { + // The engine lowered `print(x)` into two calls: `print_bare, %N` + // (emit the value with no separator) followed by `print_nl` + // (emit the newline). The older single-call form `print, %N` is + // still accepted so hand-written IR from older docs keeps working. printedArg = instr.args[1] const v = read(instr.args[1]) const isFloat = @@ -519,9 +701,20 @@ export function runIr(program: IrProgram, maxSteps = 2000): IrRun { ) || (instr.args[1] ? floatSlots.has(instr.args[1]) : false) const line = isFloat && typeof v === "number" ? fmtFloat(v) : fmtValue(v) - stdout.push(line) - output = line + if (callee === "print") { + // legacy single-call print: the value and the newline together + stdout.push(line) + output = line + } else { + // print_bare / print_sep: the value only; print_nl closes the line + pendingPrint = line + output = line + } printedArg = undefined + } else if (callee === "print_nl") { + stdout.push(pendingPrint ?? "") + output = pendingPrint ?? "" + pendingPrint = undefined } else { const target = byName.get(callee) if (target) { @@ -546,7 +739,7 @@ export function runIr(program: IrProgram, maxSteps = 2000): IrRun { const truthy = cond === true || (typeof cond === "bigint" && cond !== 0n) const target = truthy ? instr.args[1] : instr.args[2] - note = `branch on ${fmtValue(cond)} β†’ ${target}` + note = `branch on ${fmtValue(cond)} Ò†’ ${target}` jumpTo = target break } @@ -605,7 +798,13 @@ export function runIr(program: IrProgram, maxSteps = 2000): IrRun { // continue from a return may have popped one const live = frames.at(-1) if (live === undefined) break - if (jumpTo) { + if (trapTarget) { + // a trap was caught: jump to the handler block + const target = current.instrs.find((i) => i.block === trapTarget) + if (target) live.pc = target.index + else live.pc += 1 + trapTarget = undefined + } else if (jumpTo) { const target = current.instrs.find((i) => i.block === jumpTo && i.index > live.pc) ?? current.instrs.find((i) => i.block === jumpTo) @@ -617,7 +816,8 @@ export function runIr(program: IrProgram, maxSteps = 2000): IrRun { } const run: IrRun = { steps, stdout } - if (guard >= maxSteps) run.error = `stopped after ${maxSteps} steps` + if (error) run.error = error + else if (guard >= maxSteps) run.error = `stopped after ${maxSteps} steps` return run } diff --git a/web/apps/web/src/lib/seo.ts b/web/apps/web/src/lib/seo.ts index bb159ea..4e4d83b 100644 --- a/web/apps/web/src/lib/seo.ts +++ b/web/apps/web/src/lib/seo.ts @@ -11,10 +11,29 @@ export const SITE = { docsBase: "/docs", } as const -/** Canonical origin used for sitemap and canonical URLs. */ +/** + * The canonical origin used for sitemap and canonical URLs. + * + * The site is deployed to GitHub Pages from the repository's `master` + * branch, so the canonical home is the Pages URL for the `Lithon` + * repository. `SITE_ORIGIN` overrides it for a fork or a custom domain; + * `BASE_PATH` covers the sub-path a fork would be served from. + */ export const SITEMAP_HOST = process.env.SITE_ORIGIN ?? "https://project-lithon.github.io/Lithon" +/** The path the site is served from, always with a leading and trailing slash. */ +export const SITE_BASE_PATH = (() => { + const explicit = process.env.BASE_PATH + if (explicit) { + const trimmed = explicit.replace(/\/+$/, "") + return trimmed === "" ? "/" : `${trimmed}/` + } + if (process.env.CUSTOM_DOMAIN) return "/" + const repo = process.env.GITHUB_REPOSITORY?.split("/")[1] ?? "Lithon" + return `/${repo}/` +})() + export type PageMeta = { title: string description: string diff --git a/web/apps/web/src/routes/__root.tsx b/web/apps/web/src/routes/__root.tsx index b0f06a7..b3b32da 100644 --- a/web/apps/web/src/routes/__root.tsx +++ b/web/apps/web/src/routes/__root.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react" +ο»Ώimport { useEffect, useState } from "react" import { HeadContent, Link, @@ -39,6 +39,7 @@ import { } from "@workspace/ui/components/tooltip" import appCss from "@workspace/ui/globals.css?url" import { initSoundFeedback, readStoredSound, toggleSound } from "../lib/sound" +import { assetUrl } from "../lib/assets" import { ConsentBanner, PREFERENCE_SCRIPT } from "../lib/consent" import { CiStatusStrip } from "../lib/ci-status" import { @@ -60,14 +61,16 @@ export const Route = createRootRoute({ content: "Lithon | A zero-dependency, dual-tier execution engine built for native speed.", }, - { name: "theme-color", content: "#f8fff4" }, + { name: "theme-color", content: "#121212" }, { title: "Lithon | Not a revolution but an evolution of the same bloodline", }, ], links: [ - { rel: "icon", href: "/favicon.svg", type: "image/svg+xml" }, + { rel: "icon", href: assetUrl("/favicon.svg"), type: "image/svg+xml" }, + { rel: "apple-touch-icon", href: assetUrl("/favicon.ico") }, + { rel: "manifest", href: assetUrl("/manifest.json") }, { rel: "stylesheet", href: appCss }, ], }), @@ -335,7 +338,7 @@ function SearchButton() { > Search - ⌘ + Γ’Ε’Λœ K ) @@ -363,7 +366,7 @@ function SiteHeader({ - + @@ -416,7 +419,7 @@ function SiteHeader({ > - + @@ -440,7 +443,7 @@ function SiteFooter() {

- Not a revolution but an evolution of the same bloodline. Β© 2026 Lithon + Not a revolution but an evolution of the same bloodline. © 2026 Lithon contributors.

diff --git a/web/apps/web/src/routes/benchmarks.tsx b/web/apps/web/src/routes/benchmarks.tsx index 1682a4f..2520ded 100644 --- a/web/apps/web/src/routes/benchmarks.tsx +++ b/web/apps/web/src/routes/benchmarks.tsx @@ -19,6 +19,7 @@ import { import { Separator } from "@workspace/ui/components/separator" import { PageHero, WRAP } from "../site" +import { assetUrl } from "../lib/assets" import { metaForPath } from "../lib/seo" export const Route = createFileRoute("/benchmarks")({ @@ -118,7 +119,7 @@ function BenchmarkCard({ entry }: { entry: Entry }) {
{playing ? (