From 3800271b1b3d96eb5252bae8ddc9c8a3940b40b2 Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Sun, 14 Jun 2026 12:41:36 -0400 Subject: [PATCH] wat iter-3: vectors + hash tables (19 new primitives) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two new heap tags: 7 = vector [tag, len, elem_0, elem_1, ...] 8 + 4*len bytes 8 = hashtable [tag, count, alist_ptr] 12 bytes Vector primitives (88-95): vector, vector?, make-vector, vector-length, vector-ref, vector-set!, vector->list, list->vector Hash table primitives (96-106): make-hash-table, hash-table?, hash-table-set!, hash-table-ref, hash-table-ref/default, hash-table-delete!, hash-table-exists?, hash-table-size, hash-table-keys, hash-table-values, hash-table->alist Hash table lookup is linear (equal? on each key) — fine for browser-scale demos. Same linear-scan caveat as the symbol intern; would warrant a real hash function at scale. print_value now renders vectors as #(a b c) and hashtables as #. --- wasm/asm/lumbda.wat | 388 ++++++++++++++++++++++++++++- www/playground/asm/lumbda-asm.wasm | Bin 11503 -> 13446 bytes 2 files changed, 387 insertions(+), 1 deletion(-) diff --git a/wasm/asm/lumbda.wat b/wasm/asm/lumbda.wat index 2538060..78e2982 100644 --- a/wasm/asm/lumbda.wat +++ b/wasm/asm/lumbda.wat @@ -166,6 +166,117 @@ (i32.store offset=4 (local.get $p) (local.get $code)) (local.get $p)) + ;; ─── Vectors (tag=7) ─────────────────────────────────────────── + ;; Layout: [tag=7, len, elem_0, elem_1, ...] — 8 + 4*len bytes. + (func $is_vector (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 7))))))) + + (func $make_vector_raw (param $len i32) (result i32) + (local $p i32) + (local.set $p (call $alloc (i32.add (i32.const 8) (i32.mul (local.get $len) (i32.const 4))))) + (i32.store (local.get $p) (i32.const 7)) + (i32.store offset=4 (local.get $p) (local.get $len)) + (local.get $p)) + + (func $vector_len (param $v i32) (result i32) + (i32.load offset=4 (local.get $v))) + + (func $vector_get (param $v i32) (param $i i32) (result i32) + (i32.load (i32.add (i32.add (local.get $v) (i32.const 8)) (i32.mul (local.get $i) (i32.const 4))))) + + (func $vector_put (param $v i32) (param $i i32) (param $val i32) + (i32.store (i32.add (i32.add (local.get $v) (i32.const 8)) (i32.mul (local.get $i) (i32.const 4))) (local.get $val))) + + ;; ─── Hash tables (tag=8) ─────────────────────────────────────── + ;; Layout: [tag=8, count, alist_ptr]. alist is a list of (key . val) pairs. + ;; Lookup is linear — fine for browser demos; would be MOAD-0001 at scale, + ;; same caveat as my linear symbol intern. + (func $is_hashtable (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 8))))))) + + (func $make_hashtable (result i32) + (local $p i32) + (local.set $p (call $alloc (i32.const 12))) + (i32.store (local.get $p) (i32.const 8)) + (i32.store offset=4 (local.get $p) (i32.const 0)) + (i32.store offset=8 (local.get $p) (global.get $NIL)) + (local.get $p)) + + (func $ht_count (param $h i32) (result i32) + (i32.load offset=4 (local.get $h))) + + (func $ht_alist (param $h i32) (result i32) + (i32.load offset=8 (local.get $h))) + + (func $ht_set_alist (param $h i32) (param $a i32) + (i32.store offset=8 (local.get $h) (local.get $a))) + + (func $ht_set_count (param $h i32) (param $n i32) + (i32.store offset=4 (local.get $h) (local.get $n))) + + ;; Find binding for key in hashtable, returns the (k.v) pair or FALSE. + (func $ht_find (param $h i32) (param $key i32) (result i32) + (local $cur i32) + (local $pair i32) + (local.set $cur (call $ht_alist (local.get $h))) + (block $done + (loop $l + (br_if $done (i32.eqz (call $is_pair (local.get $cur)))) + (local.set $pair (call $car (local.get $cur))) + (if (i32.eq (call $equal_p (call $car (local.get $pair)) (local.get $key)) (global.get $TRUE)) + (then (return (local.get $pair)))) + (local.set $cur (call $cdr (local.get $cur))) + (br $l))) + (global.get $FALSE)) + + (func $ht_set (param $h i32) (param $key i32) (param $val i32) + (local $found i32) + (local.set $found (call $ht_find (local.get $h) (local.get $key))) + (if (i32.eq (local.get $found) (global.get $FALSE)) + (then + (call $ht_set_alist (local.get $h) + (call $make_pair + (call $make_pair (local.get $key) (local.get $val)) + (call $ht_alist (local.get $h)))) + (call $ht_set_count (local.get $h) + (i32.add (call $ht_count (local.get $h)) (i32.const 1)))) + (else + (call $set_cdr (local.get $found) (local.get $val))))) + + ;; Remove binding for key, returns 1 if removed, 0 if not present. + (func $ht_delete (param $h i32) (param $key i32) (result i32) + (local $cur i32) + (local $prev i32) + (local $pair i32) + (local.set $cur (call $ht_alist (local.get $h))) + (local.set $prev (global.get $NIL)) + (block $done + (loop $l + (br_if $done (i32.eqz (call $is_pair (local.get $cur)))) + (local.set $pair (call $car (local.get $cur))) + (if (i32.eq (call $equal_p (call $car (local.get $pair)) (local.get $key)) (global.get $TRUE)) + (then + (if (i32.eq (local.get $prev) (global.get $NIL)) + (then (call $ht_set_alist (local.get $h) (call $cdr (local.get $cur)))) + (else (call $set_cdr (local.get $prev) (call $cdr (local.get $cur))))) + (call $ht_set_count (local.get $h) + (i32.sub (call $ht_count (local.get $h)) (i32.const 1))) + (return (i32.const 1)))) + (local.set $prev (local.get $cur)) + (local.set $cur (call $cdr (local.get $cur))) + (br $l))) + (i32.const 0)) + (func $char_code (param $v i32) (result i32) (i32.load offset=4 (local.get $v))) @@ -430,9 +541,33 @@ (call $print_list_items (local.get $v)) (call $out_char (i32.const 41)) ;; ) (return))) + (if (call $is_vector (local.get $v)) + (then + (call $out_str (i32.const 0xF050) (i32.const 2)) ;; "#(" + (call $print_vector_items (local.get $v)) + (call $out_char (i32.const 41)) ;; ) + (return))) + (if (call $is_hashtable (local.get $v)) + (then + (call $out_str (i32.const 0xF060) (i32.const 12)) ;; "#" + (return))) ;; closure / primitive (call $out_str (i32.const 0xF030) (i32.const 11))) ;; "" + (func $print_vector_items (param $v i32) + (local $len i32) + (local $i i32) + (local.set $len (call $vector_len (local.get $v))) + (local.set $i (i32.const 0)) + (block $done + (loop $l + (br_if $done (i32.ge_u (local.get $i) (local.get $len))) + (call $print_value (call $vector_get (local.get $v) (local.get $i))) + (local.set $i (i32.add (local.get $i) (i32.const 1))) + (if (i32.lt_u (local.get $i) (local.get $len)) + (then (call $out_char (i32.const 32)))) + (br $l)))) + (func $print_list_items (param $p i32) (block $done (loop $loop @@ -1659,6 +1794,111 @@ (br $l2))) (local.get $out)) + ;; ─── Vector / hashtable helpers ──────────────────────────────── + + (func $list_to_vector_op (param $list i32) (result i32) + (local $len i32) + (local $cur i32) + (local $v i32) + (local $i i32) + (local.set $len (i32.const 0)) + (local.set $cur (local.get $list)) + (block $d1 + (loop $l1 + (br_if $d1 (i32.eqz (call $is_pair (local.get $cur)))) + (local.set $len (i32.add (local.get $len) (i32.const 1))) + (local.set $cur (call $cdr (local.get $cur))) + (br $l1))) + (local.set $v (call $make_vector_raw (local.get $len))) + (local.set $cur (local.get $list)) + (local.set $i (i32.const 0)) + (block $d2 + (loop $l2 + (br_if $d2 (i32.eqz (call $is_pair (local.get $cur)))) + (call $vector_put (local.get $v) (local.get $i) (call $car (local.get $cur))) + (local.set $i (i32.add (local.get $i) (i32.const 1))) + (local.set $cur (call $cdr (local.get $cur))) + (br $l2))) + (local.get $v)) + + (func $vector_to_list_op (param $v i32) (result i32) + (local $len i32) + (local $i i32) + (local $head i32) + (local $tail i32) + (local $new i32) + (local.set $len (call $vector_len (local.get $v))) + (local.set $head (global.get $NIL)) + (local.set $tail (global.get $NIL)) + (local.set $i (i32.const 0)) + (block $done + (loop $l + (br_if $done (i32.ge_u (local.get $i) (local.get $len))) + (local.set $new (call $make_pair (call $vector_get (local.get $v) (local.get $i)) (global.get $NIL))) + (if (i32.eq (local.get $head) (global.get $NIL)) + (then (local.set $head (local.get $new)) + (local.set $tail (local.get $new))) + (else (call $set_cdr (local.get $tail) (local.get $new)) + (local.set $tail (local.get $new)))) + (local.set $i (i32.add (local.get $i) (i32.const 1))) + (br $l))) + (local.get $head)) + + (func $make_vector_filled (param $n i32) (param $init i32) (result i32) + (local $v i32) + (local $i i32) + (local.set $v (call $make_vector_raw (local.get $n))) + (local.set $i (i32.const 0)) + (block $done + (loop $l + (br_if $done (i32.ge_u (local.get $i) (local.get $n))) + (call $vector_put (local.get $v) (local.get $i) (local.get $init)) + (local.set $i (i32.add (local.get $i) (i32.const 1))) + (br $l))) + (local.get $v)) + + (func $ht_keys_op (param $h i32) (result i32) + (local $cur i32) + (local $head i32) + (local $tail i32) + (local $new i32) + (local.set $cur (call $ht_alist (local.get $h))) + (local.set $head (global.get $NIL)) + (local.set $tail (global.get $NIL)) + (block $done + (loop $l + (br_if $done (i32.eqz (call $is_pair (local.get $cur)))) + (local.set $new (call $make_pair (call $car (call $car (local.get $cur))) (global.get $NIL))) + (if (i32.eq (local.get $head) (global.get $NIL)) + (then (local.set $head (local.get $new)) + (local.set $tail (local.get $new))) + (else (call $set_cdr (local.get $tail) (local.get $new)) + (local.set $tail (local.get $new)))) + (local.set $cur (call $cdr (local.get $cur))) + (br $l))) + (local.get $head)) + + (func $ht_values_op (param $h i32) (result i32) + (local $cur i32) + (local $head i32) + (local $tail i32) + (local $new i32) + (local.set $cur (call $ht_alist (local.get $h))) + (local.set $head (global.get $NIL)) + (local.set $tail (global.get $NIL)) + (block $done + (loop $l + (br_if $done (i32.eqz (call $is_pair (local.get $cur)))) + (local.set $new (call $make_pair (call $cdr (call $car (local.get $cur))) (global.get $NIL))) + (if (i32.eq (local.get $head) (global.get $NIL)) + (then (local.set $head (local.get $new)) + (local.set $tail (local.get $new))) + (else (call $set_cdr (local.get $tail) (local.get $new)) + (local.set $tail (local.get $new)))) + (local.set $cur (call $cdr (local.get $cur))) + (br $l))) + (local.get $head)) + ;; ─── Primitives ──────────────────────────────────────────────── (func $apply_primitive (param $id i32) (param $args i32) (result i32) (local $a i32) @@ -2265,6 +2505,111 @@ (if (i32.eq (local.get $id) (i32.const 87)) (then (return (call $string_join_op (local.get $a) (local.get $b))))) + ;; vector (88) — (vector a b c ...) returns a vector with the args + (if (i32.eq (local.get $id) (i32.const 88)) + (then (return (call $list_to_vector_op (local.get $args))))) + + ;; vector? (89) + (if (i32.eq (local.get $id) (i32.const 89)) + (then + (if (call $is_vector (local.get $a)) + (then (return (global.get $TRUE))) + (else (return (global.get $FALSE)))))) + + ;; make-vector (90) — (make-vector n [init]) + (if (i32.eq (local.get $id) (i32.const 90)) + (then (return (call $make_vector_filled + (call $fixnum_val (local.get $a)) + (local.get $b))))) + + ;; vector-length (91) + (if (i32.eq (local.get $id) (i32.const 91)) + (then (return (call $make_fixnum (call $vector_len (local.get $a)))))) + + ;; vector-ref (92) + (if (i32.eq (local.get $id) (i32.const 92)) + (then (return (call $vector_get (local.get $a) (call $fixnum_val (local.get $b)))))) + + ;; vector-set! (93) — (vector-set! v i val) + (if (i32.eq (local.get $id) (i32.const 93)) + (then + (call $vector_put (local.get $a) + (call $fixnum_val (local.get $b)) + (call $car (call $cdr (call $cdr (local.get $args))))) + (return (global.get $VOID)))) + + ;; vector->list (94) + (if (i32.eq (local.get $id) (i32.const 94)) + (then (return (call $vector_to_list_op (local.get $a))))) + + ;; list->vector (95) + (if (i32.eq (local.get $id) (i32.const 95)) + (then (return (call $list_to_vector_op (local.get $a))))) + + ;; make-hash-table (96) + (if (i32.eq (local.get $id) (i32.const 96)) + (then (return (call $make_hashtable)))) + + ;; hash-table? (97) + (if (i32.eq (local.get $id) (i32.const 97)) + (then + (if (call $is_hashtable (local.get $a)) + (then (return (global.get $TRUE))) + (else (return (global.get $FALSE)))))) + + ;; hash-table-set! (98) — (hash-table-set! h k v) + (if (i32.eq (local.get $id) (i32.const 98)) + (then + (call $ht_set (local.get $a) (local.get $b) + (call $car (call $cdr (call $cdr (local.get $args))))) + (return (global.get $VOID)))) + + ;; hash-table-ref (99) — (hash-table-ref h k) — returns value or FALSE + (if (i32.eq (local.get $id) (i32.const 99)) + (then + (local.set $sum (call $ht_find (local.get $a) (local.get $b))) + (if (i32.eq (local.get $sum) (global.get $FALSE)) + (then (return (global.get $FALSE)))) + (return (call $cdr (local.get $sum))))) + + ;; hash-table-ref/default (100) — (hash-table-ref/default h k def) + (if (i32.eq (local.get $id) (i32.const 100)) + (then + (local.set $sum (call $ht_find (local.get $a) (local.get $b))) + (if (i32.eq (local.get $sum) (global.get $FALSE)) + (then (return (call $car (call $cdr (call $cdr (local.get $args))))))) + (return (call $cdr (local.get $sum))))) + + ;; hash-table-delete! (101) + (if (i32.eq (local.get $id) (i32.const 101)) + (then + (drop (call $ht_delete (local.get $a) (local.get $b))) + (return (global.get $VOID)))) + + ;; hash-table-exists? (102) + (if (i32.eq (local.get $id) (i32.const 102)) + (then + (local.set $sum (call $ht_find (local.get $a) (local.get $b))) + (if (i32.eq (local.get $sum) (global.get $FALSE)) + (then (return (global.get $FALSE)))) + (return (global.get $TRUE)))) + + ;; hash-table-size (103) + (if (i32.eq (local.get $id) (i32.const 103)) + (then (return (call $make_fixnum (call $ht_count (local.get $a)))))) + + ;; hash-table-keys (104) — fresh list of keys + (if (i32.eq (local.get $id) (i32.const 104)) + (then (return (call $ht_keys_op (local.get $a))))) + + ;; hash-table-values (105) — fresh list of values + (if (i32.eq (local.get $id) (i32.const 105)) + (then (return (call $ht_values_op (local.get $a))))) + + ;; hash-table->alist (106) + (if (i32.eq (local.get $id) (i32.const 106)) + (then (return (call $ht_alist (local.get $a))))) + (global.get $VOID)) ;; ─── Init ────────────────────────────────────────────────────── @@ -2275,6 +2620,8 @@ (data (i32.const 0xF020) "#f") (data (i32.const 0xF030) "") (data (i32.const 0xF040) " . ") + (data (i32.const 0xF050) "#(") + (data (i32.const 0xF060) "#") ;; Helper to intern from a fixed-string region. We embed literal symbols ;; in dataregions 0xE000+ and intern them at init. @@ -2393,6 +2740,26 @@ (data (i32.const 0xE45C) "string->number") (data (i32.const 0xE46C) "string-contains") (data (i32.const 0xE47C) "string-join") + ;; Vectors & hash tables. + (data (i32.const 0xE490) "vector") + (data (i32.const 0xE498) "vector?") + (data (i32.const 0xE4A0) "make-vector") + (data (i32.const 0xE4AC) "vector-length") + (data (i32.const 0xE4BC) "vector-ref") + (data (i32.const 0xE4C8) "vector-set!") + (data (i32.const 0xE4D4) "vector->list") + (data (i32.const 0xE4E4) "list->vector") + (data (i32.const 0xE4F4) "make-hash-table") + (data (i32.const 0xE504) "hash-table?") + (data (i32.const 0xE510) "hash-table-set!") + (data (i32.const 0xE520) "hash-table-ref") + (data (i32.const 0xE530) "hash-table-ref/default") + (data (i32.const 0xE548) "hash-table-delete!") + (data (i32.const 0xE55C) "hash-table-exists?") + (data (i32.const 0xE570) "hash-table-size") + (data (i32.const 0xE580) "hash-table-keys") + (data (i32.const 0xE590) "hash-table-values") + (data (i32.const 0xE5A4) "hash-table->alist") (func $bind_prim (param $name_ptr i32) (param $name_len i32) (param $id i32) (local $sym i32) @@ -2511,7 +2878,26 @@ (call $bind_prim (i32.const 0xE44C) (i32.const 14) (i32.const 84)) ;; number->string (call $bind_prim (i32.const 0xE45C) (i32.const 14) (i32.const 85)) ;; string->number (call $bind_prim (i32.const 0xE46C) (i32.const 15) (i32.const 86)) ;; string-contains - (call $bind_prim (i32.const 0xE47C) (i32.const 11) (i32.const 87))) ;; string-join + (call $bind_prim (i32.const 0xE47C) (i32.const 11) (i32.const 87)) ;; string-join + (call $bind_prim (i32.const 0xE490) (i32.const 6) (i32.const 88)) ;; vector + (call $bind_prim (i32.const 0xE498) (i32.const 7) (i32.const 89)) ;; vector? + (call $bind_prim (i32.const 0xE4A0) (i32.const 11) (i32.const 90)) ;; make-vector + (call $bind_prim (i32.const 0xE4AC) (i32.const 13) (i32.const 91)) ;; vector-length + (call $bind_prim (i32.const 0xE4BC) (i32.const 10) (i32.const 92)) ;; vector-ref + (call $bind_prim (i32.const 0xE4C8) (i32.const 11) (i32.const 93)) ;; vector-set! + (call $bind_prim (i32.const 0xE4D4) (i32.const 12) (i32.const 94)) ;; vector->list + (call $bind_prim (i32.const 0xE4E4) (i32.const 12) (i32.const 95)) ;; list->vector + (call $bind_prim (i32.const 0xE4F4) (i32.const 15) (i32.const 96)) ;; make-hash-table + (call $bind_prim (i32.const 0xE504) (i32.const 11) (i32.const 97)) ;; hash-table? + (call $bind_prim (i32.const 0xE510) (i32.const 15) (i32.const 98)) ;; hash-table-set! + (call $bind_prim (i32.const 0xE520) (i32.const 14) (i32.const 99)) ;; hash-table-ref + (call $bind_prim (i32.const 0xE530) (i32.const 22) (i32.const 100)) ;; hash-table-ref/default + (call $bind_prim (i32.const 0xE548) (i32.const 18) (i32.const 101)) ;; hash-table-delete! + (call $bind_prim (i32.const 0xE55C) (i32.const 18) (i32.const 102)) ;; hash-table-exists? + (call $bind_prim (i32.const 0xE570) (i32.const 15) (i32.const 103)) ;; hash-table-size + (call $bind_prim (i32.const 0xE580) (i32.const 15) (i32.const 104)) ;; hash-table-keys + (call $bind_prim (i32.const 0xE590) (i32.const 17) (i32.const 105)) ;; hash-table-values + (call $bind_prim (i32.const 0xE5A4) (i32.const 17) (i32.const 106))) ;; hash-table->alist ;; ─── Public eval entry ───────────────────────────────────────── ;; JS writes UTF-8 source into 0x20000 and calls lumbda_eval(len). diff --git a/www/playground/asm/lumbda-asm.wasm b/www/playground/asm/lumbda-asm.wasm index 17e87179b8feaf6c253c53dad1d0d36bdab3aa27..5c8c6ad00e8559c56361d66a0f59896e6a120139 100644 GIT binary patch literal 13446 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

5Oi7A@sV+QuOiBmA zlnYZ4ECdz9ROivfy!aJW!&KUHa~<(n3WrG{rZFO=1C7nkiwjdcFiJ-@ghN3j5Jy?P z_Fq_-#fK>&CTTA$z@~emuS#+^BsHr$&C;9;Mv9ji_R~zvK0f2P)G^ca+`Z1psb{9CTi;u*fVyVZ zl)aoE;Iqn9rx@?{q}CbzuL z&mC$X=qyeiWH=Sk564kE^U9+%`mT>3nq2ItUFb@gG)5(q`=?s^!M)v6Ht{j0R2`Ot zXpqur*0^x!ZH^+^Q-f>7x-d(l|DZ=cL4%sv~5v zP)=!OJH^%vIil+)R9(~XJoOW5uQ8tCay2E`G(E>#lTut-(>0K|U=7QE9~qYdGp-ee z-25iedJL9Dkv79U&bXU{dTrM9adjzFZgbfn^Lk2Aduf_HMlCn%`Wh!X)Q}Fjh{`_D w(0PLRTVQ7D{NT)!Um@5i`2*a;ohQ*W)!(e`lg|HnS?a*&C@{d^+ZLbxANsV2%m4rY literal 11503 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