From c50a9da7e857488e8ba4d53f5155596e9baabf84 Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Sun, 14 Jun 2026 15:11:36 -0400 Subject: [PATCH] =?UTF-8?q?wat=20tier:=20rationals=20=E2=80=94=20(/=2067?= =?UTF-8?q?=207)=20=E2=86=92=2067/7=20+=20arithmetic=20+=20reader=20+=20pr?= =?UTF-8?q?inter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds tag-9 rational type to the asm tier. Layout [tag=9, num:i32, den:i32]. make_rational normalizes via gcd and collapses to a fixnum when den reduces to 1, so 14/2 stays as 7. Arithmetic (+, -, *, /, =, <, >, <=, >=) now promotes to rational when any argument is rational. Mixed fixnum/rational lifts the fixnum accumulator into a rational mid-loop so (+ 1 1/2) returns 3/2, not 1/2. Reader parses "67/7" literals via the existing atom path: after the numerator's digits, if '/' follows we keep reading the denominator and hand back a normalized rational. Falls through to symbol if either side isn't all digits. Printer renders rationals as "n/d". equal_p compares numbers by value (1/2 = 2/4, 3 = 6/2). is_number / number? cover both fixnums and rationals. eval now treats rationals as self-evaluating — without this, '1/3' parsed correctly but evaluated to VOID. Mandelbrot demo: switched from (/ a b) to (quotient a b) for the fixed-point math. The demo had been relying on integer truncation that '/' no longer provides on tiers with R7RS-correct rationals. Bignums still pending: 31-bit num/den overflows with huge denominators. Real lift comes with the bignum task in the C tier (which has them) or a new bignum module in the WAT. Cross-tier check still hangs on the bigger TCO-heavy sections of functional.lsp — separate from rationals. Will keep grinding. Tests: unit 20/20, integration 8/8, functional 11/11. --- wasm/app/demos/mandelbrot.lsp | 14 +- wasm/asm/lumbda.wat | 306 ++++++++++++++++++++++++++-- wasm/dist-repl/asm/lumbda-asm.wasm | Bin 16864 -> 18035 bytes wasm/dist-repl/c/lumbda-c.wasm | Bin 288669 -> 288474 bytes wasm/dist-repl/python/lumbda.py | 47 ++++- wasm/tests/functional.mjs | 4 +- www/playground/asm/lumbda-asm.wasm | Bin 16864 -> 18035 bytes www/playground/c/lumbda-c.wasm | Bin 288304 -> 288474 bytes www/playground/demos/mandelbrot.lsp | 14 +- www/playground/python/lumbda.py | 47 ++++- www/repl/asm/lumbda-asm.wasm | Bin 16864 -> 18035 bytes www/repl/c/lumbda-c.wasm | Bin 288304 -> 288474 bytes www/repl/python/lumbda.py | 47 ++++- 13 files changed, 430 insertions(+), 49 deletions(-) diff --git a/wasm/app/demos/mandelbrot.lsp b/wasm/app/demos/mandelbrot.lsp index 8265ac2..9385341 100644 --- a/wasm/app/demos/mandelbrot.lsp +++ b/wasm/app/demos/mandelbrot.lsp @@ -1,6 +1,8 @@ ; Mandelbrot — fixed-point ASCII render. ; Runs identically on Python, C, and asm WASM tiers. -; The asm tier has no float support so we scale all coords by 1024. +; The asm tier has no float support so we scale all coords by 1024 +; and use quotient (integer truncation) so we don't accidentally +; surface rationals via /. (define SCALE 1024) (define SCALE4 4096) ; 4 * SCALE (escape threshold |z|^2) @@ -10,13 +12,13 @@ (define (escape-count cx cy) (define (loop zr zi n) - (let ((zr2 (/ (* zr zr) SCALE)) - (zi2 (/ (* zi zi) SCALE))) + (let ((zr2 (quotient (* zr zr) SCALE)) + (zi2 (quotient (* zi zi) SCALE))) (cond ((>= n MAXITER) MAXITER) ((> (+ zr2 zi2) SCALE4) n) (else (loop (+ (- zr2 zi2) cx) - (+ (/ (* 2 (/ (* zr zi) SCALE)) 1) cy) + (+ (* 2 (quotient (* zr zi) SCALE)) cy) (+ n 1)))))) (loop 0 0 0)) @@ -29,11 +31,11 @@ (else (display " ")))) (define (row py) - (define cy (- (/ (* py 2048) HEIGHT) 1024)) + (define cy (- (quotient (* py 2048) HEIGHT) 1024)) (define (col px) (if (< px WIDTH) (begin - (shade (escape-count (- (/ (* px 3072) WIDTH) 2048) cy)) + (shade (escape-count (- (quotient (* px 3072) WIDTH) 2048) cy)) (col (+ px 1))) (newline))) (col 0)) diff --git a/wasm/asm/lumbda.wat b/wasm/asm/lumbda.wat index aa4d092..e59093a 100644 --- a/wasm/asm/lumbda.wat +++ b/wasm/asm/lumbda.wat @@ -57,6 +57,12 @@ (global $global_env (mut i32) (i32.const 4)) ;; NIL initially (global $initialized (mut i32) (i32.const 0)) + ;; Scratch space for to_rat: the WAT one-result calling convention is + ;; awkward for "return two values", so to_rat sets these globals and + ;; rat_*_ops read them. Single-threaded execution makes this safe. + (global $rat_tmp_n (mut i32) (i32.const 0)) + (global $rat_tmp_d (mut i32) (i32.const 1)) + ;; Pre-allocated symbol pointers for special forms — filled at init. (global $sym_quote (mut i32) (i32.const 0)) (global $sym_if (mut i32) (i32.const 0)) @@ -179,6 +185,156 @@ (i32.store offset=4 (local.get $p) (local.get $code)) (local.get $p)) + ;; ─── Rationals (tag=9) ───────────────────────────────────────── + ;; Layout: [tag=9, num: i32, den: i32]. num/den are 31-bit signed. + ;; Real bignums would lift the overflow ceiling — TODO when the WAT + ;; tier grows arbitrary-precision integers. + (func $is_rational (param $v i32) (result i32) + (if (result i32) (call $is_fixnum (local.get $v)) + (then (i32.const 0)) + (else + (if (result i32) (call $is_immediate (local.get $v)) + (then (i32.const 0)) + (else (i32.eq (call $obj_tag (local.get $v)) (i32.const 9))))))) + + (func $is_number (param $v i32) (result i32) + (if (result i32) (call $is_fixnum (local.get $v)) + (then (i32.const 1)) + (else (call $is_rational (local.get $v))))) + + (func $rat_num (param $v i32) (result i32) + (i32.load offset=4 (local.get $v))) + + (func $rat_den (param $v i32) (result i32) + (i32.load offset=8 (local.get $v))) + + ;; Euclidean gcd on signed i32. Returns a positive result. + (func $gcd_i32 (param $a i32) (param $b i32) (result i32) + (local $t i32) + (if (i32.lt_s (local.get $a) (i32.const 0)) + (then (local.set $a (i32.sub (i32.const 0) (local.get $a))))) + (if (i32.lt_s (local.get $b) (i32.const 0)) + (then (local.set $b (i32.sub (i32.const 0) (local.get $b))))) + (block $done + (loop $l + (br_if $done (i32.eqz (local.get $b))) + (local.set $t (i32.rem_s (local.get $a) (local.get $b))) + (local.set $a (local.get $b)) + (local.set $b (local.get $t)) + (br $l))) + (local.get $a)) + + ;; Build a rational from i32 num/den. Normalizes via gcd, returns a + ;; fixnum if the denominator reduces to 1. + (func $make_rational (param $num i32) (param $den i32) (result i32) + (local $g i32) + (local $p i32) + (if (i32.eqz (local.get $den)) + (then + ;; div-by-zero: leave as raw num/0 so caller can handle as error + (local.set $p (call $alloc (i32.const 12))) + (i32.store (local.get $p) (i32.const 9)) + (i32.store offset=4 (local.get $p) (local.get $num)) + (i32.store offset=8 (local.get $p) (i32.const 0)) + (return (local.get $p)))) + ;; Move sign to numerator: keep den positive. + (if (i32.lt_s (local.get $den) (i32.const 0)) + (then + (local.set $num (i32.sub (i32.const 0) (local.get $num))) + (local.set $den (i32.sub (i32.const 0) (local.get $den))))) + (local.set $g (call $gcd_i32 (local.get $num) (local.get $den))) + (if (i32.gt_s (local.get $g) (i32.const 1)) + (then + (local.set $num (i32.div_s (local.get $num) (local.get $g))) + (local.set $den (i32.div_s (local.get $den) (local.get $g))))) + ;; den == 1 collapses to a fixnum. + (if (i32.eq (local.get $den) (i32.const 1)) + (then (return (call $make_fixnum (local.get $num))))) + (local.set $p (call $alloc (i32.const 12))) + (i32.store (local.get $p) (i32.const 9)) + (i32.store offset=4 (local.get $p) (local.get $num)) + (i32.store offset=8 (local.get $p) (local.get $den)) + (local.get $p)) + + ;; Convert a number Value to (num,den) returned via globals (numerator + ;; in $rat_tmp_n, denominator in $rat_tmp_d). Fixnum → (n, 1). + (func $to_rat (param $v i32) + (if (call $is_fixnum (local.get $v)) + (then + (global.set $rat_tmp_n (call $fixnum_val (local.get $v))) + (global.set $rat_tmp_d (i32.const 1)) + (return))) + (if (call $is_rational (local.get $v)) + (then + (global.set $rat_tmp_n (call $rat_num (local.get $v))) + (global.set $rat_tmp_d (call $rat_den (local.get $v))) + (return))) + ;; Anything else falls back to 0/1. + (global.set $rat_tmp_n (i32.const 0)) + (global.set $rat_tmp_d (i32.const 1))) + + (func $rat_add (param $a i32) (param $b i32) (result i32) + (local $an i32) (local $ad i32) (local $bn i32) (local $bd i32) + (call $to_rat (local.get $a)) + (local.set $an (global.get $rat_tmp_n)) (local.set $ad (global.get $rat_tmp_d)) + (call $to_rat (local.get $b)) + (local.set $bn (global.get $rat_tmp_n)) (local.set $bd (global.get $rat_tmp_d)) + (call $make_rational + (i32.add (i32.mul (local.get $an) (local.get $bd)) + (i32.mul (local.get $bn) (local.get $ad))) + (i32.mul (local.get $ad) (local.get $bd)))) + + (func $rat_sub (param $a i32) (param $b i32) (result i32) + (local $an i32) (local $ad i32) (local $bn i32) (local $bd i32) + (call $to_rat (local.get $a)) + (local.set $an (global.get $rat_tmp_n)) (local.set $ad (global.get $rat_tmp_d)) + (call $to_rat (local.get $b)) + (local.set $bn (global.get $rat_tmp_n)) (local.set $bd (global.get $rat_tmp_d)) + (call $make_rational + (i32.sub (i32.mul (local.get $an) (local.get $bd)) + (i32.mul (local.get $bn) (local.get $ad))) + (i32.mul (local.get $ad) (local.get $bd)))) + + (func $rat_mul (param $a i32) (param $b i32) (result i32) + (local $an i32) (local $ad i32) (local $bn i32) (local $bd i32) + (call $to_rat (local.get $a)) + (local.set $an (global.get $rat_tmp_n)) (local.set $ad (global.get $rat_tmp_d)) + (call $to_rat (local.get $b)) + (local.set $bn (global.get $rat_tmp_n)) (local.set $bd (global.get $rat_tmp_d)) + (call $make_rational + (i32.mul (local.get $an) (local.get $bn)) + (i32.mul (local.get $ad) (local.get $bd)))) + + (func $rat_div (param $a i32) (param $b i32) (result i32) + (local $an i32) (local $ad i32) (local $bn i32) (local $bd i32) + (call $to_rat (local.get $a)) + (local.set $an (global.get $rat_tmp_n)) (local.set $ad (global.get $rat_tmp_d)) + (call $to_rat (local.get $b)) + (local.set $bn (global.get $rat_tmp_n)) (local.set $bd (global.get $rat_tmp_d)) + (call $make_rational + (i32.mul (local.get $an) (local.get $bd)) + (i32.mul (local.get $ad) (local.get $bn)))) + + ;; Returns 1 if a == b as rationals, else 0. + (func $rat_eq (param $a i32) (param $b i32) (result i32) + (local $an i32) (local $ad i32) (local $bn i32) (local $bd i32) + (call $to_rat (local.get $a)) + (local.set $an (global.get $rat_tmp_n)) (local.set $ad (global.get $rat_tmp_d)) + (call $to_rat (local.get $b)) + (local.set $bn (global.get $rat_tmp_n)) (local.set $bd (global.get $rat_tmp_d)) + (i32.eq (i32.mul (local.get $an) (local.get $bd)) + (i32.mul (local.get $bn) (local.get $ad)))) + + ;; Returns 1 if a < b as rationals, else 0. Denominators always positive. + (func $rat_lt (param $a i32) (param $b i32) (result i32) + (local $an i32) (local $ad i32) (local $bn i32) (local $bd i32) + (call $to_rat (local.get $a)) + (local.set $an (global.get $rat_tmp_n)) (local.set $ad (global.get $rat_tmp_d)) + (call $to_rat (local.get $b)) + (local.set $bn (global.get $rat_tmp_n)) (local.set $bd (global.get $rat_tmp_d)) + (i32.lt_s (i32.mul (local.get $an) (local.get $bd)) + (i32.mul (local.get $bn) (local.get $ad)))) + ;; ─── Vectors (tag=7) ─────────────────────────────────────────── ;; Layout: [tag=7, len, elem_0, elem_1, ...] — 8 + 4*len bytes. (func $is_vector (param $v i32) (result i32) @@ -524,6 +680,12 @@ (local $len i32) (if (call $is_fixnum (local.get $v)) (then (call $out_int (call $fixnum_val (local.get $v))) (return))) + (if (call $is_rational (local.get $v)) + (then + (call $out_int (call $rat_num (local.get $v))) + (call $out_char (i32.const 47)) ;; / + (call $out_int (call $rat_den (local.get $v))) + (return))) (if (i32.eq (local.get $v) (global.get $NIL)) (then (call $out_str (i32.const 0xF000) (i32.const 2)) (return))) ;; "()" (if (i32.eq (local.get $v) (global.get $TRUE)) @@ -696,6 +858,8 @@ (local $byte i32) (local $sym i32) (local $end_str i32) + (local $den i32) + (local $den_start i32) (call $skip_ws) (if (i32.ge_u (global.get $source_ptr) (global.get $source_end)) (then (return (global.get $VOID)))) @@ -791,7 +955,32 @@ (then (if (local.get $neg) (then (local.set $n (i32.sub (i32.const 0) (local.get $n))))) - (return (call $make_fixnum (local.get $n))))))) + (return (call $make_fixnum (local.get $n))))) + ;; Try rational form: numerator '/' denominator (e.g. 67/7). + (if (i32.and + (i32.lt_u (local.get $i) (local.get $len)) + (i32.eq (i32.load8_u (i32.add (local.get $start) (local.get $i))) (i32.const 47))) + (then + (local.set $den (i32.const 0)) + (local.set $i (i32.add (local.get $i) (i32.const 1))) ;; skip / + (local.set $den_start (local.get $i)) + (block $den_done + (loop $den_loop + (br_if $den_done (i32.ge_u (local.get $i) (local.get $len))) + (local.set $byte (i32.load8_u (i32.add (local.get $start) (local.get $i)))) + (br_if $den_done (i32.eqz (call $is_digit (local.get $byte)))) + (local.set $den (i32.add (i32.mul (local.get $den) (i32.const 10)) + (i32.sub (local.get $byte) (i32.const 48)))) + (local.set $i (i32.add (local.get $i) (i32.const 1))) + (br $den_loop))) + ;; Require: consumed all bytes AND denominator had at least 1 digit. + (if (i32.and + (i32.eq (local.get $i) (local.get $len)) + (i32.gt_s (local.get $i) (local.get $den_start))) + (then + (if (local.get $neg) + (then (local.set $n (i32.sub (i32.const 0) (local.get $n))))) + (return (call $make_rational (local.get $n) (local.get $den))))))))) ;; Symbol (return (call $intern (local.get $start) (local.get $len)))) @@ -961,9 +1150,11 @@ (local $params i32) (local $body i32) - ;; Self-evaluating: fixnum, immediate, string, char, closure, primitive + ;; Self-evaluating: fixnum, rational, immediate, string, char, closure, primitive (if (call $is_fixnum (local.get $expr)) (then (return (local.get $expr)))) + (if (call $is_rational (local.get $expr)) + (then (return (local.get $expr)))) (if (call $is_immediate (local.get $expr)) (then (return (local.get $expr)))) (if (call $is_string (local.get $expr)) @@ -1414,6 +1605,12 @@ (local $i i32) (if (i32.eq (local.get $a) (local.get $b)) (then (return (global.get $TRUE)))) + ;; Numeric values compare by value (1/2 == 1/2, fixnum 3 == 6/2). + (if (i32.and (call $is_number (local.get $a)) (call $is_number (local.get $b))) + (then + (if (call $rat_eq (local.get $a) (local.get $b)) + (then (return (global.get $TRUE)))) + (return (global.get $FALSE)))) (if (call $is_pair (local.get $a)) (then (if (i32.eqz (call $is_pair (local.get $b))) @@ -2046,6 +2243,9 @@ (local $rev i32) (local $idx i32) (local $idxt i32) + (local $any_rat i32) + (local $any_rat2 i32) + (local $any_rat3 i32) ;; Fetch first 2 args (most prims use 1 or 2). Defaults to fixnum 0. (local.set $a (call $make_fixnum (i32.const 0))) @@ -2056,25 +2256,62 @@ (if (i32.ne (call $cdr (local.get $args)) (global.get $NIL)) (then (local.set $b (call $car (call $cdr (local.get $args)))))))) - ;; + + ;; + — variadic; promotes to rational if any arg is rational. (if (i32.eq (local.get $id) (i32.const 1)) (then (local.set $sum (i32.const 0)) + (local.set $a (call $make_fixnum (i32.const 0))) (local.set $cur (local.get $args)) + (local.set $any_rat (i32.const 0)) (block $done (loop $loop (br_if $done (i32.eq (local.get $cur) (global.get $NIL))) - (local.set $sum (i32.add (local.get $sum) - (call $fixnum_val (call $car (local.get $cur))))) + (local.set $b (call $car (local.get $cur))) + (if (i32.and (call $is_rational (local.get $b)) (i32.eqz (local.get $any_rat))) + (then + ;; First rational encountered: lift the fixnum sum into a. + (local.set $a (call $make_fixnum (local.get $sum))) + (local.set $any_rat (i32.const 1)))) + (if (local.get $any_rat) + (then (local.set $a (call $rat_add (local.get $a) (local.get $b)))) + (else (local.set $sum (i32.add (local.get $sum) (call $fixnum_val (local.get $b)))))) (local.set $cur (call $cdr (local.get $cur))) (br $loop))) + (if (local.get $any_rat) + (then (return (local.get $a)))) (return (call $make_fixnum (local.get $sum))))) ;; - (if (i32.eq (local.get $id) (i32.const 2)) (then + ;; Unary case: negate. (if (i32.eq (call $cdr (local.get $args)) (global.get $NIL)) - (then (return (call $make_fixnum (i32.sub (i32.const 0) (call $fixnum_val (local.get $a))))))) + (then + (if (call $is_rational (local.get $a)) + (then (return (call $make_rational + (i32.sub (i32.const 0) (call $rat_num (local.get $a))) + (call $rat_den (local.get $a)))))) + (return (call $make_fixnum (i32.sub (i32.const 0) (call $fixnum_val (local.get $a))))))) + ;; Variadic difference. Scan rest looking for any rational. + (local.set $any_rat2 (call $is_rational (local.get $a))) + (local.set $cur (call $cdr (local.get $args))) + (block $rscan + (loop $rl + (br_if $rscan (i32.eq (local.get $cur) (global.get $NIL))) + (if (call $is_rational (call $car (local.get $cur))) + (then (local.set $any_rat2 (i32.const 1)))) + (local.set $cur (call $cdr (local.get $cur))) + (br $rl))) + (if (local.get $any_rat2) + (then + (local.set $cur (call $cdr (local.get $args))) + (block $rdone + (loop $rsub + (br_if $rdone (i32.eq (local.get $cur) (global.get $NIL))) + (local.set $a (call $rat_sub (local.get $a) (call $car (local.get $cur)))) + (local.set $cur (call $cdr (local.get $cur))) + (br $rsub))) + (return (local.get $a)))) (local.set $sum (call $fixnum_val (local.get $a))) (local.set $cur (call $cdr (local.get $args))) (block $done @@ -2090,25 +2327,43 @@ (if (i32.eq (local.get $id) (i32.const 3)) (then (local.set $sum (i32.const 1)) + (local.set $a (call $make_fixnum (i32.const 1))) (local.set $cur (local.get $args)) + (local.set $any_rat3 (i32.const 0)) (block $done (loop $loop (br_if $done (i32.eq (local.get $cur) (global.get $NIL))) - (local.set $sum (i32.mul (local.get $sum) - (call $fixnum_val (call $car (local.get $cur))))) + (local.set $b (call $car (local.get $cur))) + (if (i32.and (call $is_rational (local.get $b)) (i32.eqz (local.get $any_rat3))) + (then + (local.set $a (call $make_fixnum (local.get $sum))) + (local.set $any_rat3 (i32.const 1)))) + (if (local.get $any_rat3) + (then (local.set $a (call $rat_mul (local.get $a) (local.get $b)))) + (else (local.set $sum (i32.mul (local.get $sum) (call $fixnum_val (local.get $b)))))) (local.set $cur (call $cdr (local.get $cur))) (br $loop))) + (if (local.get $any_rat3) + (then (return (local.get $a)))) (return (call $make_fixnum (local.get $sum))))) - ;; / + ;; / — promotes int/int to rational when the result isn't integral + ;; (matches python lumbda and the C tier's recent fix). (if (i32.eq (local.get $id) (i32.const 4)) (then - (return (call $make_fixnum (i32.div_s (call $fixnum_val (local.get $a)) - (call $fixnum_val (local.get $b))))))) + (if (i32.or (call $is_rational (local.get $a)) (call $is_rational (local.get $b))) + (then (return (call $rat_div (local.get $a) (local.get $b))))) + (return (call $make_rational (call $fixnum_val (local.get $a)) + (call $fixnum_val (local.get $b)))))) ;; = (if (i32.eq (local.get $id) (i32.const 5)) (then + (if (i32.or (call $is_rational (local.get $a)) (call $is_rational (local.get $b))) + (then + (if (call $rat_eq (local.get $a) (local.get $b)) + (then (return (global.get $TRUE))) + (else (return (global.get $FALSE)))))) (if (i32.eq (call $fixnum_val (local.get $a)) (call $fixnum_val (local.get $b))) (then (return (global.get $TRUE))) (else (return (global.get $FALSE)))))) @@ -2116,6 +2371,11 @@ ;; < (if (i32.eq (local.get $id) (i32.const 6)) (then + (if (i32.or (call $is_rational (local.get $a)) (call $is_rational (local.get $b))) + (then + (if (call $rat_lt (local.get $a) (local.get $b)) + (then (return (global.get $TRUE))) + (else (return (global.get $FALSE)))))) (if (i32.lt_s (call $fixnum_val (local.get $a)) (call $fixnum_val (local.get $b))) (then (return (global.get $TRUE))) (else (return (global.get $FALSE)))))) @@ -2123,6 +2383,11 @@ ;; > (if (i32.eq (local.get $id) (i32.const 7)) (then + (if (i32.or (call $is_rational (local.get $a)) (call $is_rational (local.get $b))) + (then + (if (call $rat_lt (local.get $b) (local.get $a)) + (then (return (global.get $TRUE))) + (else (return (global.get $FALSE)))))) (if (i32.gt_s (call $fixnum_val (local.get $a)) (call $fixnum_val (local.get $b))) (then (return (global.get $TRUE))) (else (return (global.get $FALSE)))))) @@ -2130,6 +2395,12 @@ ;; <= (if (i32.eq (local.get $id) (i32.const 8)) (then + (if (i32.or (call $is_rational (local.get $a)) (call $is_rational (local.get $b))) + (then + (if (i32.or (call $rat_lt (local.get $a) (local.get $b)) + (call $rat_eq (local.get $a) (local.get $b))) + (then (return (global.get $TRUE))) + (else (return (global.get $FALSE)))))) (if (i32.le_s (call $fixnum_val (local.get $a)) (call $fixnum_val (local.get $b))) (then (return (global.get $TRUE))) (else (return (global.get $FALSE)))))) @@ -2137,6 +2408,12 @@ ;; >= (if (i32.eq (local.get $id) (i32.const 9)) (then + (if (i32.or (call $is_rational (local.get $a)) (call $is_rational (local.get $b))) + (then + (if (i32.or (call $rat_lt (local.get $b) (local.get $a)) + (call $rat_eq (local.get $a) (local.get $b))) + (then (return (global.get $TRUE))) + (else (return (global.get $FALSE)))))) (if (i32.ge_s (call $fixnum_val (local.get $a)) (call $fixnum_val (local.get $b))) (then (return (global.get $TRUE))) (else (return (global.get $FALSE)))))) @@ -2341,14 +2618,15 @@ (then (return (global.get $TRUE))) (else (return (global.get $FALSE)))))) - ;; number? (38) — fixnums only in this tier + ;; number? (38) — fixnum or rational (if (i32.eq (local.get $id) (i32.const 38)) (then - (if (call $is_fixnum (local.get $a)) + (if (call $is_number (local.get $a)) (then (return (global.get $TRUE))) (else (return (global.get $FALSE)))))) - ;; integer? (39) — same as number? here + ;; integer? (39) — fixnum only (a normalized rational with den=1 + ;; collapses to fixnum, so this implicitly handles 14/2 → 7). (if (i32.eq (local.get $id) (i32.const 39)) (then (if (call $is_fixnum (local.get $a)) diff --git a/wasm/dist-repl/asm/lumbda-asm.wasm b/wasm/dist-repl/asm/lumbda-asm.wasm index 8779c3fefca927898c6579f96596585440648073..33def254fd987c02cfd0a6a8e077ec60af76014a 100644 GIT binary patch literal 18035 zcmb`ONsOGwb;rN0x2YM-v%2OVf@F=fj(z00<2)3)r$jtuCagAn+T-+NWx z*I&;JDcZ<@?EcrQs`p;Kdi7paeP0LdrMWN&g7DhLgYmIr$HHR|hVl`47$`M(FmNR5(Q?ieW>%mftjQ8 zD|5j$rn@q?Z@T@!%>2x9a3n8vj_8$+A1zBin zK^EBXu(`l}f5awPa37@fzQc#Lavc4)QJF@`F&k6>aSjq2?ltD@wb40i<8#&~=d7)q zv$iU=Tf^{}t*4oD1#j~-U^BTuA95yjD>=aVNlv0 zY6b=oz8D^Ah9~%7LS(LiWGz?cVU{QaE)wucXAiSXb%3Ne1*~{+$+@dno+Yc|14DgX3C9J+D?wu zY&cd&BtzETo5pJ3QK8wu)ta4Q!pPGtty(i`)locDoPaC1Sf!gKS=^mvAeC;q`8=B+ z%A1d$n|snd&xWWGXNf3q)wwx(efOScyN{jSacbf?nR*zuYh6P+)v2R%VhV12S8eJGKZ;w>utFaLE}TN?SG*y-=ah zjiitpd7Gb&d@3p%PQ{2Ckg1rR3<5J+l(OhH%0p5Pmu=Wnwl-YpTV9u+@QmE5Ws!^J zqe20vNWEn5-12pEHN`wK^q~E?_Mi8HECc?~y=0lP{lH4Dd0a3vD z0}6!c4Qri2sn<7|yH=9!9OHX1@FoLRAD1~veaMTYDi3)+B#G}2XR#U9aHih0)9lj` z=2me^;hthRaTxIu8~|5{aZxo(tMX^HzS7))VrnkdkBm9!uL1Rc1YFm;QlsNkGtgCV zrMXbEYb#`Kn3WcBcXli^I>sP3p_@bIR!pDXE+Lz|EDX#OVOTq6S4j&@7Ea_c2>w8v z*Q6qIm<41BoiaOx5F0j=ic?gUs!XMwqU+G&tT+Z>o{-R=QkDn)Fqz(Ngm|b~)xK3k zY457ns;YIW)!UkzB-`JG6(-X=bkIH}q<7d|41%=a=(Y3O^>27JL;lds-5hOO) z6D7ix5R_+CB;edl%q{MV`(tKhlQzy#H>8?fmENvgDzG0{ojzzMJnd3wr=3W2wFUhO z=x3l0ixm3|$fUc?XI&ITq}?uz)~h6R6^3ZL`YRnRjMuAhS0QST*&VofazPb`O7El( z-ovLToiQn5aS7S77Ehv1obIOfPLoIG%bag0Bg~@<85>=m?(vQ!G2`PUW*bP|bDmQ7 z81s1Omq9CP#RzLfIISpbGbXJSiGxSOY%v1cSW6gMOBh*8xE+Fwq|Dq7vD$LR7z5_~ zGe*Pm)F%;i>wSmK?W)3ke%oos_vUD#KvxxLyF&ys{ri`M13>yA70MV`!~IdgC?8e& zQJG*1!<^!lyM=K6B+8_IBcWVODW{vugTb2|uwN>cVE~5ZNfJNMx(1N7J5xW{I%K;zP|?LNmM3 zjEaxvZ0xSRqFB&u_^w%{Ii#L`HgS!- z-h(CQ%vw0caOC}h3|966=n!`1^VW)4ZEAWkVg=2!y?iwG_V^qUII_3b>-)ur(!H1_ z)D?~rG5Z%0N$0WBpYaGWphL7Xok!U1GrA0j~V7nD5*MPonpi+HHBZd zXw-MCUj?#={x`S}+lX6c<#NnOmzX|F-6DQ&1| zcLrLlv_?UD16oySBL(d(Xo=FCjN;RLkYFS>Tfd|6fzWMdqH}C#9t$}DV31(l(6ECVnJaN{1Dy+*4xjm)WR5rbAof)%S0!%Uf3S(#F%^KlJ_GQk7475gs$ZD+G zdRA{$*aTN#cstJ9y3+3gi%sQ}Th#~>>x(DIA?qGl@5s=7B?pN6SlQyR8UzTlxn45M zqLIw#SRFLz90ENq_~9s*H`0tLi(iaU9c6HHVbbr<*1lQrq0z9A*w1 z*>HLbRv6A$0>gY6Oa_nK zWBx~LhMn%xaHtVf1{^mL(2SS?38ezXhPmZJrcqf%u!`5R@_wldnOQdzvg_pFB&W*+ zhQKnz$b8zZT=5H$gChP|CH)z#xCzTj$6V3TCgKr{jD^BzoeSxHUqmq4e+v7qR1;T-6)SJu$BwN7#NH5BPZ zM2nqM$tfbuBZJOxP8Jd%qcRKbJMLZQSH-SgttdJBynX6J?ovu4I_Dl3^i-S%dv_4x zJS4Ykd<_dek*$k>CTL|bQ7A6&|pONQ^NFONA{ML@E9_Fh|bjFNxQfzlA~U3eMnR3{W>m5 zY+>3c+WWJfd=bwyze_LKzUpuo@Y)*-xdO>AEmT7PgzNQ{oEfG=g^=_rin@1aJko_( z{DN!-2r?2KGInpyBi&5mk?$QD`flpOhul9&^krwp)rs8c`YDMWSIzhm67?dDqdVrL zw6~d%GtgM>)%qTPg~qS7JS3pXvJ$g9ek?J^uOj>??QK@rTT`E~*&SfGTP7C&IixJc zn=w}Qo_NF?Ci_ZE@9?Rm_}=&J#rE5N>#r zTO;gF$qkjVOI6gRFxJ(?CE1u=Bix{Aoa?oLw zE88BVSFCKjD}Lh*$PS^8oEN^T^q=a6TcPV*irt*nG|f+=-^7MuM!GD^jmz zz-~Yhb~5U@L)`EL zUn`k6c`6p->}D2YCCFlP0#6XvZaXZyBbWJNcVK2Es=-v~WDpFlxx_pvCsaZ-ldcbl zd}LH-lR>Ee%E_KnpF7jDW08}*mgS(0EuAB)E^Lw3IgIYlKAeY2&aL?&9@YDP;!f`s z@mnVK8risL{_n50SXfB9zzTlxATwVRTvX-bIFTrsNT3o+4wb`5%)Heg z*3oiEoZZ>W2XG&Jb8fV2XniBA%1GGLW(}SmaM2*)vmxedXuPV6*g6XVWhEBo-82yS zZplX7Ds)?`W6gY#-~%;WtKbG}cI(D#R@{!joo?G`ZGOhhE%%M*3N@GinSsVH*{#j; zQZFspZG$WPQ!cuXr5p5`KQnU59+Hi`~;cU}K^nOhFhs{kU+XORW zXZk)lQK5M{NywJPo^Qsr0qZ-_%e%npt)A*3c_xx;3>)yh=@1{IDOYV;oIIrMsc(*o zw)u|3Z64TZX~(b$=f12_H?qODJW{6h1&plUQ1<3gI*F;*v6h6lE1i_hPQQJwd|ZUg zlBv>-O_dIqDm7W^O?6>ms_vkvv_Z*KiMNngp8G(x^F>Z`F|^o<>8ote%5Ao}sgb=L z1C{19BhY$UV5=K+yQJL2cjEZu6lU_ z*EMF%(>`a}jxnSnE zN9(;*Z?K$uC;2NS2@c&~gnFyxZN1 z6(u`{EWw?!>Y&@_i%U12PVIH8(s8JfD-%2`t8L6d-!l*ny!gT=nbha?h7Il|{10XI zF8LYyA)jG~{D}1J1A6in)0*+iqefXm)C@X+JkHXJV2!Ugi;6XCM;`X5*Iw)AU7qzQ z!9N$aV>v-jz4raS$XD4RKcM7vWRNNcRrF!wSZ*tir=V5`sq%;lDm#*L+p8V5MooQS zRR+2@aze=Qm1qnur^4x>sF2Bfj<+X0jO%+Ao{MtSt=Wrb) zwiHCN!3BU*WQNpRP66^bA+Xmg6P(}+TD&1IeqM_=Qq(86RUVZNF1wq2OB<$N)V4S0 zguoMS$q8ZlfVQ|bCj_2wn-IkRh6{yQ|MVFCsml5eQ z;<{*u-E{Cybcf1MC%h-`(V{SuQ=K-xvmj~S$E;L7s8uKBIU&@xmcFVb36;+OzE+g? zY5fbXjB)mB`9)t2eEB6;PN%i@Wnb!O>Fd5Nw=faP@*s_QI^~-j(9$cuB(Y|QU00l5 z#o^YZ4{7Z;d@bk2veSOkldtmQe>G>~2mh_Sp1_n?IXNd*PR@ME@jB7t^%{r%_COyV zAt&+(c~I_m2HNlhFVQD>i9Eqe@+Wwu3~+j&eP1-@Pvm5;(b>({cL$pGRd!8vf0g*r zeJ^h+#$qbVmid0U&RsziQT+$LZZbWn#{Ho$&uHmKt_06nE&sSEAJXz`S`O2$mVV;< z^2S72{7*euW^-N}{>)P@^HZgdvNirqrk87rpSu?Hm@Hg%oA>iy_}+8c@|V8kR5&B0 zRNt*(R@L28UeqY7^J_?E;PuS{kCZK zfvujkK8Dv34zfluasU5zJ<`k@J0XRm>O_Y&1gMWY9nk7~-?IL^1(=N8BJ?#?v8c4WYYFED&Th7Jll6_231bSRi4EmCy z1aw?c1@wfXD(Fc?HPDwqOb6(Mq6X+G5K#j36;RCULreA;<5=KvjDo=p9gOF9N-*w2MKjFG!C|K#zm! z_EOLjpoZNFIsuC9WuT`NT@HF0l-O;cXFwHu1?V}YT?sk~YS;|)s-h<7HBjAd2fd-F z1$rA)wPT=nL5Y1oX!S)I(FZ_}gX;Dw&=X1<2c1wf0eTuVWUmH2tL=7xPJ$A94d|4j zNziFUJ3+50+66iTs@N&eo1m(_7W6i#X0HRitF-GutGrN9u{VGoS9BxjIH+oG0zC;z z?9HGPT6+uVX{Fr?dInUtw}GAqHSF!6Q(C(lwEA*vN9-M-!E5H%^U4hR7tM+>W zUsU*gfv*BL*&hgeUEvP}z6Bh$KN9#Sps_y|c>ERN@S4CUfi?RRfhT~5j@HB9U z=$tO4E2d3gO^?`MOn;Cv7I87eJJ=p^#c8Cf&QgR0nro5V0)pt!z60|~x>&6^^|Cd6@XJQkLPXzMTrHNY0fxK-AI1tD_ z*_hpny@maq>6Kn*ParQDnmFSnZFxVEM&p6J5NU>6yAQUP4lcL%b)^-rI8t{%;ACtn zhgTMsJFo{wGjJv7n`*Z$zhOay6s9`|X69)^=r@&porh-T;SWysFU(IPL13r5L<_>d ziMt(?B>bDWE!Bj76D{;$3`mwb%ly)T@NW{@T^&gLM_gyQ*V&H*p+9_(pTI~CFx8d$ zZf9u;X+q!M77^qD6HPB5;cKx8cLb6I46npV3NRrINdP9?g6oNQ$gG}(H;O{U&n$V;=YOZx6zik51Ix&3#!bobi4fAhRAO!i|v^nnAmUP&S4O- z-^B9^44c?*YSS}Ii{17kfy94Po9{f_Rr8Sp=-Zo_ryW@U1I#P|Ndsbl`G=Md@}eMx zxV;Ze$ONW3w=liZm9f4+*GD_O1%#0fOkE~zrX!rl38vBO%(Z9cr#oV0%JG~`5t#wh zJ{m}7V1|w^(wx*_DxD*pc{q_AOtLUN4J135#^S=#%<{~U4rOwLY0P&XYWp&o!ql0& zNo>RXkuH33T8iWhQ|%mHX^V+S8wfjm1W4Xs{QMlMBlaS9m^!2HJfuYuh^Z|-GPiF* zdXYpZEn}UBD3M6aP~q4R$S0=0Z(*U^X^ZA074Zj|C$fq_nJCf?S9E!h09|~P2ku-zFdOdMr5)G5mkpbaQ6cP%D z(O%mxEiK@~q#Kj8mzE%tZ%kcgd9v4GWs!7DLyODpnJ%hJ%OeXj2s{m}EG@S8i-&{z zUUP<0%*w^+plYwPywYQGd9ThS;*==+a%@JN=_YeOB7>PBPgB_>GSgUE+2?3zS?ZLJ zn!LuW>KxpGk;!d&aW_TsTOnp;(Je+2of-9FrWYQbcT{qo8TQmY&dJGrW>|;5C)a=+ zXg1}ooFCw`N>rzqELxHmjsC`NGVe=zWKIvZ#jVJXW<>Dh9`9V3P-wn1xkoCQduXY) zYj+n9w)b_GXZF*b?CHDXsGXkDNTa^#!v|-UJ322kC8Zjp9P;s#S$*gJ=t;5o7$Vh% zc|j~lnl+o;a_A3^gxfcRt7LW|=5{5>;x6-q1hTL(-eR*0vcyTorh25ae_5Q9bZly_ zAj(p5M$79-%x1(D3O^x3n;}nAKOs*W;|VTSldjFMr})_bl(s*vT)MT5j$q@wmpmpDe=H(N@T(;ae1hgife zV4$LNl=ZiSWcCDul4l-ev9UcEpdRWxf~DChn9U{b{CIPv1D$Qd0DVu}xi@pU7mmBcnvKu37qbJ|(WQxOeC9wluDQk)H92SF z6V%`bOW8QpgkY(Ju|JTTK;?)0)n$44@CHohTe_>3=>xjI2!3KDZh zB{|u+&&jml;sx|O zltwY2X>YRA-hVKkhV}N6I4w5!11|(XIIInq!K07}eR49AS{)LtK+O-q%g^i}v z8%tBh)+Fb*&amspUvhxl%d~%A@3OW&95eu^J!UAqRK9fyBSXyrP zmKBMnD@C2IQa>J6y5F1O&IH?8*}Sqty!Kr&Z@)+v0-d0lQpw~hvs>T#A@ zm?NXKmpd>~m$~-3>u#wY>~;8?h|FrmWcP~ z=*wAItXVJ^C_+0v+aBB$L}p# z-mx7fc~#E7MB45}4YVty`{VsYwkC^urI&jvolKnBP1lj^J=MwRvV8E7?4gC_0g1xo ZJ%k--%dV_1ZCvb3@x5iX4xEp?{{t#c&@})6 literal 16864 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 diff --git a/wasm/dist-repl/c/lumbda-c.wasm b/wasm/dist-repl/c/lumbda-c.wasm index e3aa27705952c69a429c5871dbe32b39f3695c4c..4f4ad5552d582d6267a5e6f8b57ed0770a94102e 100755 GIT binary patch delta 2326 zcma)6U2GIp6u#%)>2zjirk%pJ#RBEt=?1n?YC*H0LD|^=Hiby3KMw{Ic+*`pB7LM? z6p-KpKCH=!4GAKoHdlMaY7Z_?k9Ps^yktr42138A6-F8pbD%Mw;=U7t5qwS(gorkkNfYj4|efOiMuY*$R)*hSSdxS`o4gMSC_d@pz8c;VW~Yqtc-h zDI^J(4fNW-A_9TD5q3OEp2&lYky5IZuG7K_xPG~T*-SBM-Ik{wGVzMY^`Pi5g2LEb=TFV^kZmxDmjBPDNynpnCa@x&&QRJYUy@Ul`($i_zi>V*j$8g^ zA%%Xpp`T4K8FPLszC9mWUXCS6I-sm76Z>{Uy=h7#=TFD37Fd7-TVPW0**#EW@Hd~w zSNFmh&y$lCWn7suu3TLC0kjs{R#z2zU-I$HUYLYmeE_Gx#ohbh;lhqLqQmm#&Qedv zk_0$)pt!EmWgdi5#whdjU@N9Ns*Ebd*5w$LIThlRb!1BQn59r$y4ir&3rkg@`i$3L z>wZ{P+_fJH#te^2NF@15(9csqZd_?oaf+}PvVLu0{gzpH=tJVM{Q!I;EKin&X1hPR zuxfQRzSaecz{8#{s3pu7x_}@eKMZeq4t0s5r0Z9fhg7<86spI^z-dL;peB$B z*?2O_z!OKI8E+Kf65;;#W0*-n&VC6^gnQ3vn1vU6VLJAn1f#g|7+e$oOPzE#)Zy3N z@ZxxJ*7V#(oW34-XFTxYw&Sq9B2NwB2RxcJ)B&Y>XD;=CW(03mCDNoR!gZ@~S+Z0& z5|uQGJB`*_RhkTXNR7#(?rUk$GpEXRxsg*--;hs|({d^d(H`)q^Q-i6aqU@XhWluO zE$3iOxfk_&toZ(1azK*iU@b;t}He}Mq zTff7?ywyogBRlDI2GHsWa$t4P#D@%fYHDD&lH1`advlPLJM@p0S-dS^TP5pIF$rWi zvJET+BpMwC>~#gySG3HqHWHuugXQUn7;Fky23F}gmKns={g95+YBEi zg~pYID}gsu-;3v*5uP^jv_>2a@f_U~_BPMN^%tmC>;C{ZZ+B9!MZ`60LprTq2~oE@ z2q{EbMEtEg_}e7r2;qtR?f>Ra#5I4CA>xnE^g|1F{sXnx-47Lb=uc?Czx$zvCUn~1 z34G)y_^vGQmayT^T0$Rkt{{dz{jdn1yF|7&uD=D1`1#N9Ynk-k>sA)!r`!q8*WiHx Scwk%i0ML&Yy9dO@7ybpKgyt{+ delta 2426 zcmah~TWl0n7(V|wvopIpGj?jPc4<8`tWs!&wUPpcNM|G1R17T`qj-bpgBH{n8xx~+ zQ9^jrm?j)FF`*EpkeC1sS-=3r_+UcR#@hp;!3Pv2BH&i-<0mGaQT-0byaS@%B54a!6qV+<1Lca^LSwB!3jjG5lwi0%pqGvhxv4DlRs~L) zg=zJABORS)OAd7!gAXMcvw4f8kS%su$b61}AzCnHt2ghVz6ueFF6l}E@(?PzyuU=? zs<|`i-5hjd5gtuy=PW%q0oOY4%kWJ-cz#05NlFem0nBjlTi?b+X0k^m6m|1>W(Iwok0>h0}jVD9y(J*@%$+G zQS+kU7jt55oJHvb2qHzujp}cr1?9GPN=R3KIic5?W-@7?$eZ_3+si`whFacB(}m&H zs#l++Ir)KJYBczY0!0KnV&!|Oj+*R>c}V5PEJq9_Pf-IJ}Ob|+vo@!o6<+K z)ruVyI6}tKWKSiZV+(fo(Vev}0%anUm@y#*g}Ah#Xu62|m8$m#-lzt#JYTb&dW z9m<5nVEA^vrk@_>Wwxx`7MQD=b;+naRKI_i{2uRfub^b_9;y%q&<6KV8&%mdQO~P= zE8M#S`xfa54QXMM-7ZJG2XHdWHB9*IWFOuu(Q<=O=+&jlzQoZNMZ*<1i4~?=8m(@tOII&hN#i2HK zHNcm;F?Hy7YNfb3E_f}5x6e~GhK^xcS*1@cqYY^lI=K3Hkns;?T;)$vgPWX)p;CX) z3^G;k1)7P~gBPeA!(qYeFkD}G-c2e?3xN$;Ys%hwpv0Cm1~EFy*7Ad_x)z_M&vDzMv8yZ91;p&*8HCc9umKBEo!0SUYb!LQ8YR?t& z!Se5uG+!MfUIRa2a#}rb9=rWU-p;zEw?X1TR{X4Vttc@F)QTa>q{fnQeNK`(Cu#N5 p5G4V7s|L@wB?+0>dca4ZaGw5yR@fty+;C->@XzyChQ;w0{sSMI1Hu3R diff --git a/wasm/dist-repl/python/lumbda.py b/wasm/dist-repl/python/lumbda.py index 3863d12..8ed1fa0 100644 --- a/wasm/dist-repl/python/lumbda.py +++ b/wasm/dist-repl/python/lumbda.py @@ -1203,13 +1203,28 @@ _BC_FOLDABLE = { class CodeObj: """Compiled bytecode chunk.""" - __slots__ = ('instrs', 'name', 'source_map', 'ic', '_cur_line', '_self_name', '_self_params') + __slots__ = ('instrs', 'name', 'source_map', 'ic', '_cur_line', + '_self_name', '_self_params', '_scope_depth', '_self_base') def __init__(self, name=None): self.instrs = []; self.name = name self.source_map = [] # parallel to instrs: line number or None self.ic = None # inline cache (populated at runtime) self._cur_line = None # current source line during compilation + # 2026-06-14 self-tail-call frame-unwind: tracks env-frame depth at + # compile time so OP_SELF_TAIL_CALL can pop accumulated let/let*/ + # letrec/do frames before reusing our lambda body env. Without this + # a (let* (...) (loop ...)) inside (let loop ...) bloated env per + # iter — 156k-element walk hung > 5 min instead of completing in + # 1.4s. _self_base records depth at lambda body entry; _scope_depth + # is current depth; pops_needed = depth - base at tail call site. + self._scope_depth = 0 + self._self_base = 0 def emit(self, op, arg=None): + # 2026-06-14 self-tail-call frame-unwind: track env-frame depth so + # OP_SELF_TAIL_CALL knows how many let/let*/letrec/do frames sit + # between us & our lambda body env. + if op == OP_PUSH_ENV: self._scope_depth += 1 + elif op == OP_POP_ENV: self._scope_depth -= 1 idx = len(self.instrs); self.instrs.append((op, arg)) self.source_map.append(self._cur_line) return idx @@ -1529,7 +1544,12 @@ def _bc(expr, code, env, tail=False): if tail and isinstance(head, Symbol) and hasattr(code, '_self_name') and str(head) == code._self_name: params = code._self_params for arg in call_args: _bc(arg, code, env) - code.emit(OP_SELF_TAIL_CALL, (len(call_args), tuple(params))); return + # 2026-06-14 frame-unwind: pop accumulated let/let*/letrec/do frames + # before we reuse our lambda body env. Without this each iter's + # let* frame stays on the env chain — env grows linearly with iters + # & every var lookup walks an O(n) chain → effective O(n^2). + pops_needed = code._scope_depth - code._self_base + code.emit(OP_SELF_TAIL_CALL, (len(call_args), tuple(params), pops_needed)); return # --- Function call --- _bc(head, code, env) @@ -1569,6 +1589,9 @@ def _bc_lambda(body, params, rest, env, name=None, self_name=None, self_params=N if def_names: inner.emit(OP_PUSH_ENV) for nm in def_names: inner.emit(OP_VOID); inner.emit(OP_BIND, nm) + # 2026-06-14: record baseline depth after any internal-defines frame. + # Our self-tail-call unwind pops back to here, not all the way to 0. + inner._self_base = inner._scope_depth _bc_body(body_list, inner, env, tail=True) inner.emit(OP_RETURN) _peephole(inner) @@ -1832,9 +1855,14 @@ def _vm_loop(instrs, ip, stack, env, frames, vm_id): c, addr = arg if _po() != c: ip = addr elif op == OP_SELF_TAIL_CALL: - n, params = arg + # 2026-06-14: arg now (n_args, params, pops_needed). pops_needed + # unwinds accumulated let/let*/letrec/do frames before we reuse + # our lambda body env — otherwise self-tail-call from inside a + # let* bloats env per iter & every var lookup walks O(n) chain. + n, params, pops = arg if n: args_ = stack[-n:]; del stack[-n:] else: args_ = [] + for _ in range(pops): env = env.p b = env.b for p, a in zip(params, args_): b[p] = a ip = 0; stack.clear(); continue @@ -1888,9 +1916,12 @@ def _serialize_operand(val): return {'t': 'closure', 'code': _serialize_code(code), 'params': [str(p) for p in params], 'rest': str(rest) if rest else None} - # OP_SELF_TAIL_CALL: (n_args, params_tuple) + # OP_SELF_TAIL_CALL: (n_args, params_tuple, pops_needed) + if len(val) == 3 and isinstance(val[0], int) and isinstance(val[1], tuple) and isinstance(val[2], int): + return {'t': 'stc', 'n': val[0], 'p': [str(p) for p in val[1]], 'pops': val[2]} + # Backwards-compat: pre-2026-06-14 portals have 2-tuple form. if len(val) == 2 and isinstance(val[0], int) and isinstance(val[1], tuple): - return {'t': 'stc', 'n': val[0], 'p': [str(p) for p in val[1]]} + return {'t': 'stc', 'n': val[0], 'p': [str(p) for p in val[1]], 'pops': 0} return {'t': 'repr', 'v': repr(val)} def _deserialize_operand(data): @@ -1921,7 +1952,8 @@ def _deserialize_operand(data): rest = S(data['rest']) if data['rest'] else None return (code, params, rest) if t == 'stc': - return (data['n'], tuple(S(p) for p in data['p'])) + # 2026-06-14: stc now carries pops field; older portals (no pops) → 0. + return (data['n'], tuple(S(p) for p in data['p']), data.get('pops', 0)) return data def _serialize_code(code): @@ -2632,7 +2664,8 @@ def _jit_transpile(instrs, params, name, has_self_tc): val = stack.pop() if stack else 'VOID' stmts.append(('return', val)); ip+=1 elif op == OP_SELF_TAIL_CALL: - tc_n, tc_params = arg + # arg is (n_args, params, pops_needed) since 2026-06-14 + tc_n = arg[0]; tc_params = arg[1] pnames = [str(p) for p in tc_params] args = []; for _ in range(tc_n): args.insert(0, stack.pop()) diff --git a/wasm/tests/functional.mjs b/wasm/tests/functional.mjs index 663b984..cbcb0a2 100644 --- a/wasm/tests/functional.mjs +++ b/wasm/tests/functional.mjs @@ -79,8 +79,8 @@ function nativePython(demoPath) { check("page loads", await page.title() !== ""); check("editor mounted", (await page.locator(".cm-editor").count()) > 0); - check("5 program radios (4 demos + free-form)", - (await page.locator('input[name="program"]').count()) === 5); + check("6 program radios (4 demos + bend-gpu + free-form)", + (await page.locator('input[name="program"]').count()) === 6); check("4 tier radios", (await page.locator('input[name="tier"]').count()) === 4); // Test each demo on C tier (fastest, deterministic). diff --git a/www/playground/asm/lumbda-asm.wasm b/www/playground/asm/lumbda-asm.wasm index 8779c3fefca927898c6579f96596585440648073..33def254fd987c02cfd0a6a8e077ec60af76014a 100644 GIT binary patch literal 18035 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 ZJ%k--%dV_1ZCvb3@x5iX4xEp?{{t#c&@})6 literal 16864 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 diff --git a/www/playground/c/lumbda-c.wasm b/www/playground/c/lumbda-c.wasm index c0835e8d6068b7eee679d396f3d220d237ff2c0e..4f4ad5552d582d6267a5e6f8b57ed0770a94102e 100755 GIT binary patch delta 1714 zcma)6U1(fI7@ae7bNBAvdy|`H^Mf{V=4MGZHI17nTal`nVl^be)*?PtXdm5e1#Q3= zAr*tAK2&gp!3S&m&>sY8HqhJ#X-uULeNapdq83Gi_*6u}2m9dPH+QS~u~>K6nYnjo z&N<)tzHeqeZoU3V>r8vdzpcO8877?}Zgl7C0H2bq$b(qe$(wLSOyOP(PL+A&Ybe1- z>{mh;tIZsY`+74 zf9W|rp+OLy6#n^DDTL+T(y~BWcHh$C+)j)0j}~X6mZpZ5e~B>p0m$f`5MrvA>8Kh9 z1*QkgV*|K;zbCmfB0Unvf6FoEYi2E=@YzJj=z-JE7uFHlsl{X^cFlot-c^f!@XVoY zIMVLZTPY-&SBdR9`ccHf@If4Zh|mZ{=EyCTAqZM{F}G6?yQ9QQt4O&4e3_H3#$=fX zw8}`=gia2ZS=od^mjfnmObzlQ(DxU1M zMZ=_HH8Z_+Ey;r^N!FU0mHrbU^Z&M>%!_0tNIx{HirHz*8KL+x6rX1fm+%2RbKel! z^Q*L}$7vqSbO~FLF|#G~g{vEUnK3IRNV7DAym`0;FOAc0q>b_=IkDU!V;k`FFdof& z(G4Mm9ReLy3QXqwpOUwRG4_0}NY;U}s$8>j68)|#oodu=>SNe${+Pz5`a_uveNcX8vvBiA1h;FlZ1y1tvJbn@X%bk%+=rvzn!gK4* z*}rfbb5<7c_IlyT(GPK~b&47yig|k1PzPk}le;Sf-4T3LHA$Cl5pG+B%cfnnk*K7} z+|9JMuF}n5FR3+ow0$i-8mp?smK%Y(_Jn+rKwGFV#AM9V&hN0VlLOZ#+I?a ZB}{w4R*VXL86KTq2K{(4zbsyQ^DkWDM34Xg delta 1651 zcmaJ>UuYaf9G>6I-0t1&uGyI6Qky2uY)_k*SkG9lDnheDYl)Rs6w#_ss4rfsARhW4 z^aLM#Q}iHH-^_z1EeL5s*rdf8#Rs7(L8}j?KKfJ?!52Y`elvTfNo>7icXsFB_viP` zUjC+g{_F1fzKAc)zttaQ{SjRqOimGr7N1aTm=_ain=lavrR#`wA*8@mMrn5|Db#uF zW@#DNRw|ipJJCo)%It_xe6J{?p*m-(fI;4D*deCy$L1+D>b$6AFuf76kR!@8Y%>BFRofdRs z5vG={bG~iDgj*eWlG?LPtV>o-S#iioU`B{%JB*In3A-5t$-FN{Uk*@mA~g?`r+g<5 z++5$oZ@S;=wZGXx>o!Y=t-&QsTX(yR7qInVHq;~QWY$&jX+>i*ASRzP| z#V@9JZB%Uzj8oUGjs&yHnG%mgEC|fG8Vw-6Ki24c3-#{MgAaw2;-?%*?dn$49A)~q zlsu>J-=vgqcXKKI87X^zg&h>-E1dF5H!o2|~!nB-TU zGrxXBYf-3rbP2ieRId)q#gFL-h3&^r-(il;QI|O~OBdnTjyc+6j-8<-6sl0AU?*vg zBY1L-ZXb*gC=a1Tj|VB(h^qjKMnq9p%*+pb%&cK~w*54C zRb~@!s&2P*+e7txpObFKU&OhR5nG)et|Dh=~(+E16y;M^iTsZ>4=`Mnt( z-Hc#PW%TM+-YLUQL(B94f#cIFbSF5rR;UJ!XJ=`bshy+2>3NdbA1m~`*t*)PHc-9a zeETVNn

Oi(BNkKYXIY`_BtB_#Yph|BOD^!g6SlM%$||<*TyCR%u`NVOX?df5e= n MAXITER) MAXITER) ((> (+ zr2 zi2) SCALE4) n) (else (loop (+ (- zr2 zi2) cx) - (+ (/ (* 2 (/ (* zr zi) SCALE)) 1) cy) + (+ (* 2 (quotient (* zr zi) SCALE)) cy) (+ n 1)))))) (loop 0 0 0)) @@ -29,11 +31,11 @@ (else (display " ")))) (define (row py) - (define cy (- (/ (* py 2048) HEIGHT) 1024)) + (define cy (- (quotient (* py 2048) HEIGHT) 1024)) (define (col px) (if (< px WIDTH) (begin - (shade (escape-count (- (/ (* px 3072) WIDTH) 2048) cy)) + (shade (escape-count (- (quotient (* px 3072) WIDTH) 2048) cy)) (col (+ px 1))) (newline))) (col 0)) diff --git a/www/playground/python/lumbda.py b/www/playground/python/lumbda.py index 3863d12..8ed1fa0 100644 --- a/www/playground/python/lumbda.py +++ b/www/playground/python/lumbda.py @@ -1203,13 +1203,28 @@ _BC_FOLDABLE = { class CodeObj: """Compiled bytecode chunk.""" - __slots__ = ('instrs', 'name', 'source_map', 'ic', '_cur_line', '_self_name', '_self_params') + __slots__ = ('instrs', 'name', 'source_map', 'ic', '_cur_line', + '_self_name', '_self_params', '_scope_depth', '_self_base') def __init__(self, name=None): self.instrs = []; self.name = name self.source_map = [] # parallel to instrs: line number or None self.ic = None # inline cache (populated at runtime) self._cur_line = None # current source line during compilation + # 2026-06-14 self-tail-call frame-unwind: tracks env-frame depth at + # compile time so OP_SELF_TAIL_CALL can pop accumulated let/let*/ + # letrec/do frames before reusing our lambda body env. Without this + # a (let* (...) (loop ...)) inside (let loop ...) bloated env per + # iter — 156k-element walk hung > 5 min instead of completing in + # 1.4s. _self_base records depth at lambda body entry; _scope_depth + # is current depth; pops_needed = depth - base at tail call site. + self._scope_depth = 0 + self._self_base = 0 def emit(self, op, arg=None): + # 2026-06-14 self-tail-call frame-unwind: track env-frame depth so + # OP_SELF_TAIL_CALL knows how many let/let*/letrec/do frames sit + # between us & our lambda body env. + if op == OP_PUSH_ENV: self._scope_depth += 1 + elif op == OP_POP_ENV: self._scope_depth -= 1 idx = len(self.instrs); self.instrs.append((op, arg)) self.source_map.append(self._cur_line) return idx @@ -1529,7 +1544,12 @@ def _bc(expr, code, env, tail=False): if tail and isinstance(head, Symbol) and hasattr(code, '_self_name') and str(head) == code._self_name: params = code._self_params for arg in call_args: _bc(arg, code, env) - code.emit(OP_SELF_TAIL_CALL, (len(call_args), tuple(params))); return + # 2026-06-14 frame-unwind: pop accumulated let/let*/letrec/do frames + # before we reuse our lambda body env. Without this each iter's + # let* frame stays on the env chain — env grows linearly with iters + # & every var lookup walks an O(n) chain → effective O(n^2). + pops_needed = code._scope_depth - code._self_base + code.emit(OP_SELF_TAIL_CALL, (len(call_args), tuple(params), pops_needed)); return # --- Function call --- _bc(head, code, env) @@ -1569,6 +1589,9 @@ def _bc_lambda(body, params, rest, env, name=None, self_name=None, self_params=N if def_names: inner.emit(OP_PUSH_ENV) for nm in def_names: inner.emit(OP_VOID); inner.emit(OP_BIND, nm) + # 2026-06-14: record baseline depth after any internal-defines frame. + # Our self-tail-call unwind pops back to here, not all the way to 0. + inner._self_base = inner._scope_depth _bc_body(body_list, inner, env, tail=True) inner.emit(OP_RETURN) _peephole(inner) @@ -1832,9 +1855,14 @@ def _vm_loop(instrs, ip, stack, env, frames, vm_id): c, addr = arg if _po() != c: ip = addr elif op == OP_SELF_TAIL_CALL: - n, params = arg + # 2026-06-14: arg now (n_args, params, pops_needed). pops_needed + # unwinds accumulated let/let*/letrec/do frames before we reuse + # our lambda body env — otherwise self-tail-call from inside a + # let* bloats env per iter & every var lookup walks O(n) chain. + n, params, pops = arg if n: args_ = stack[-n:]; del stack[-n:] else: args_ = [] + for _ in range(pops): env = env.p b = env.b for p, a in zip(params, args_): b[p] = a ip = 0; stack.clear(); continue @@ -1888,9 +1916,12 @@ def _serialize_operand(val): return {'t': 'closure', 'code': _serialize_code(code), 'params': [str(p) for p in params], 'rest': str(rest) if rest else None} - # OP_SELF_TAIL_CALL: (n_args, params_tuple) + # OP_SELF_TAIL_CALL: (n_args, params_tuple, pops_needed) + if len(val) == 3 and isinstance(val[0], int) and isinstance(val[1], tuple) and isinstance(val[2], int): + return {'t': 'stc', 'n': val[0], 'p': [str(p) for p in val[1]], 'pops': val[2]} + # Backwards-compat: pre-2026-06-14 portals have 2-tuple form. if len(val) == 2 and isinstance(val[0], int) and isinstance(val[1], tuple): - return {'t': 'stc', 'n': val[0], 'p': [str(p) for p in val[1]]} + return {'t': 'stc', 'n': val[0], 'p': [str(p) for p in val[1]], 'pops': 0} return {'t': 'repr', 'v': repr(val)} def _deserialize_operand(data): @@ -1921,7 +1952,8 @@ def _deserialize_operand(data): rest = S(data['rest']) if data['rest'] else None return (code, params, rest) if t == 'stc': - return (data['n'], tuple(S(p) for p in data['p'])) + # 2026-06-14: stc now carries pops field; older portals (no pops) → 0. + return (data['n'], tuple(S(p) for p in data['p']), data.get('pops', 0)) return data def _serialize_code(code): @@ -2632,7 +2664,8 @@ def _jit_transpile(instrs, params, name, has_self_tc): val = stack.pop() if stack else 'VOID' stmts.append(('return', val)); ip+=1 elif op == OP_SELF_TAIL_CALL: - tc_n, tc_params = arg + # arg is (n_args, params, pops_needed) since 2026-06-14 + tc_n = arg[0]; tc_params = arg[1] pnames = [str(p) for p in tc_params] args = []; for _ in range(tc_n): args.insert(0, stack.pop()) diff --git a/www/repl/asm/lumbda-asm.wasm b/www/repl/asm/lumbda-asm.wasm index 8779c3fefca927898c6579f96596585440648073..33def254fd987c02cfd0a6a8e077ec60af76014a 100644 GIT binary patch literal 18035 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 ZJ%k--%dV_1ZCvb3@x5iX4xEp?{{t#c&@})6 literal 16864 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 diff --git a/www/repl/c/lumbda-c.wasm b/www/repl/c/lumbda-c.wasm index c0835e8d6068b7eee679d396f3d220d237ff2c0e..4f4ad5552d582d6267a5e6f8b57ed0770a94102e 100755 GIT binary patch delta 1714 zcma)6U1(fI7@ae7bNBAvdy|`H^Mf{V=4MGZHI17nTal`nVl^be)*?PtXdm5e1#Q3= zAr*tAK2&gp!3S&m&>sY8HqhJ#X-uULeNapdq83Gi_*6u}2m9dPH+QS~u~>K6nYnjo z&N<)tzHeqeZoU3V>r8vdzpcO8877?}Zgl7C0H2bq$b(qe$(wLSOyOP(PL+A&Ybe1- z>{mh;tIZsY`+74 zf9W|rp+OLy6#n^DDTL+T(y~BWcHh$C+)j)0j}~X6mZpZ5e~B>p0m$f`5MrvA>8Kh9 z1*QkgV*|K;zbCmfB0Unvf6FoEYi2E=@YzJj=z-JE7uFHlsl{X^cFlot-c^f!@XVoY zIMVLZTPY-&SBdR9`ccHf@If4Zh|mZ{=EyCTAqZM{F}G6?yQ9QQt4O&4e3_H3#$=fX zw8}`=gia2ZS=od^mjfnmObzlQ(DxU1M zMZ=_HH8Z_+Ey;r^N!FU0mHrbU^Z&M>%!_0tNIx{HirHz*8KL+x6rX1fm+%2RbKel! z^Q*L}$7vqSbO~FLF|#G~g{vEUnK3IRNV7DAym`0;FOAc0q>b_=IkDU!V;k`FFdof& z(G4Mm9ReLy3QXqwpOUwRG4_0}NY;U}s$8>j68)|#oodu=>SNe${+Pz5`a_uveNcX8vvBiA1h;FlZ1y1tvJbn@X%bk%+=rvzn!gK4* z*}rfbb5<7c_IlyT(GPK~b&47yig|k1PzPk}le;Sf-4T3LHA$Cl5pG+B%cfnnk*K7} z+|9JMuF}n5FR3+ow0$i-8mp?smK%Y(_Jn+rKwGFV#AM9V&hN0VlLOZ#+I?a ZB}{w4R*VXL86KTq2K{(4zbsyQ^DkWDM34Xg delta 1651 zcmaJ>UuYaf9G>6I-0t1&uGyI6Qky2uY)_k*SkG9lDnheDYl)Rs6w#_ss4rfsARhW4 z^aLM#Q}iHH-^_z1EeL5s*rdf8#Rs7(L8}j?KKfJ?!52Y`elvTfNo>7icXsFB_viP` zUjC+g{_F1fzKAc)zttaQ{SjRqOimGr7N1aTm=_ain=lavrR#`wA*8@mMrn5|Db#uF zW@#DNRw|ipJJCo)%It_xe6J{?p*m-(fI;4D*deCy$L1+D>b$6AFuf76kR!@8Y%>BFRofdRs z5vG={bG~iDgj*eWlG?LPtV>o-S#iioU`B{%JB*In3A-5t$-FN{Uk*@mA~g?`r+g<5 z++5$oZ@S;=wZGXx>o!Y=t-&QsTX(yR7qInVHq;~QWY$&jX+>i*ASRzP| z#V@9JZB%Uzj8oUGjs&yHnG%mgEC|fG8Vw-6Ki24c3-#{MgAaw2;-?%*?dn$49A)~q zlsu>J-=vgqcXKKI87X^zg&h>-E1dF5H!o2|~!nB-TU zGrxXBYf-3rbP2ieRId)q#gFL-h3&^r-(il;QI|O~OBdnTjyc+6j-8<-6sl0AU?*vg zBY1L-ZXb*gC=a1Tj|VB(h^qjKMnq9p%*+pb%&cK~w*54C zRb~@!s&2P*+e7txpObFKU&OhR5nG)et|Dh=~(+E16y;M^iTsZ>4=`Mnt( z-Hc#PW%TM+-YLUQL(B94f#cIFbSF5rR;UJ!XJ=`bshy+2>3NdbA1m~`*t*)PHc-9a zeETVNn

Oi(BNkKYXIY`_BtB_#Yph|BOD^!g6SlM%$||<*TyCR%u`NVOX?df5e 5 min instead of completing in + # 1.4s. _self_base records depth at lambda body entry; _scope_depth + # is current depth; pops_needed = depth - base at tail call site. + self._scope_depth = 0 + self._self_base = 0 def emit(self, op, arg=None): + # 2026-06-14 self-tail-call frame-unwind: track env-frame depth so + # OP_SELF_TAIL_CALL knows how many let/let*/letrec/do frames sit + # between us & our lambda body env. + if op == OP_PUSH_ENV: self._scope_depth += 1 + elif op == OP_POP_ENV: self._scope_depth -= 1 idx = len(self.instrs); self.instrs.append((op, arg)) self.source_map.append(self._cur_line) return idx @@ -1529,7 +1544,12 @@ def _bc(expr, code, env, tail=False): if tail and isinstance(head, Symbol) and hasattr(code, '_self_name') and str(head) == code._self_name: params = code._self_params for arg in call_args: _bc(arg, code, env) - code.emit(OP_SELF_TAIL_CALL, (len(call_args), tuple(params))); return + # 2026-06-14 frame-unwind: pop accumulated let/let*/letrec/do frames + # before we reuse our lambda body env. Without this each iter's + # let* frame stays on the env chain — env grows linearly with iters + # & every var lookup walks an O(n) chain → effective O(n^2). + pops_needed = code._scope_depth - code._self_base + code.emit(OP_SELF_TAIL_CALL, (len(call_args), tuple(params), pops_needed)); return # --- Function call --- _bc(head, code, env) @@ -1569,6 +1589,9 @@ def _bc_lambda(body, params, rest, env, name=None, self_name=None, self_params=N if def_names: inner.emit(OP_PUSH_ENV) for nm in def_names: inner.emit(OP_VOID); inner.emit(OP_BIND, nm) + # 2026-06-14: record baseline depth after any internal-defines frame. + # Our self-tail-call unwind pops back to here, not all the way to 0. + inner._self_base = inner._scope_depth _bc_body(body_list, inner, env, tail=True) inner.emit(OP_RETURN) _peephole(inner) @@ -1832,9 +1855,14 @@ def _vm_loop(instrs, ip, stack, env, frames, vm_id): c, addr = arg if _po() != c: ip = addr elif op == OP_SELF_TAIL_CALL: - n, params = arg + # 2026-06-14: arg now (n_args, params, pops_needed). pops_needed + # unwinds accumulated let/let*/letrec/do frames before we reuse + # our lambda body env — otherwise self-tail-call from inside a + # let* bloats env per iter & every var lookup walks O(n) chain. + n, params, pops = arg if n: args_ = stack[-n:]; del stack[-n:] else: args_ = [] + for _ in range(pops): env = env.p b = env.b for p, a in zip(params, args_): b[p] = a ip = 0; stack.clear(); continue @@ -1888,9 +1916,12 @@ def _serialize_operand(val): return {'t': 'closure', 'code': _serialize_code(code), 'params': [str(p) for p in params], 'rest': str(rest) if rest else None} - # OP_SELF_TAIL_CALL: (n_args, params_tuple) + # OP_SELF_TAIL_CALL: (n_args, params_tuple, pops_needed) + if len(val) == 3 and isinstance(val[0], int) and isinstance(val[1], tuple) and isinstance(val[2], int): + return {'t': 'stc', 'n': val[0], 'p': [str(p) for p in val[1]], 'pops': val[2]} + # Backwards-compat: pre-2026-06-14 portals have 2-tuple form. if len(val) == 2 and isinstance(val[0], int) and isinstance(val[1], tuple): - return {'t': 'stc', 'n': val[0], 'p': [str(p) for p in val[1]]} + return {'t': 'stc', 'n': val[0], 'p': [str(p) for p in val[1]], 'pops': 0} return {'t': 'repr', 'v': repr(val)} def _deserialize_operand(data): @@ -1921,7 +1952,8 @@ def _deserialize_operand(data): rest = S(data['rest']) if data['rest'] else None return (code, params, rest) if t == 'stc': - return (data['n'], tuple(S(p) for p in data['p'])) + # 2026-06-14: stc now carries pops field; older portals (no pops) → 0. + return (data['n'], tuple(S(p) for p in data['p']), data.get('pops', 0)) return data def _serialize_code(code): @@ -2632,7 +2664,8 @@ def _jit_transpile(instrs, params, name, has_self_tc): val = stack.pop() if stack else 'VOID' stmts.append(('return', val)); ip+=1 elif op == OP_SELF_TAIL_CALL: - tc_n, tc_params = arg + # arg is (n_args, params, pops_needed) since 2026-06-14 + tc_n = arg[0]; tc_params = arg[1] pnames = [str(p) for p in tc_params] args = []; for _ in range(tc_n): args.insert(0, stack.pop())