From ee5e997df7b1b56c899554b4f8a95c536096e5e4 Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Sun, 14 Jun 2026 17:54:33 -0400 Subject: [PATCH] wat: internal-define scoping (R7RS letrec*) + c-wasm gc gap documented MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WAT — leading (define ...) forms in a lambda body now bind LOCALLY (letrec*-equivalent) instead of polluting the global env. Implementation: apply for closures pre-processes the body in three passes: 1. hoist_internal_defines walks leading defines, env_define each name to VOID in the new env, returns the extended env. 2. strip_leading_defines returns the body with the defines removed. 3. fill_internal_defines evaluates each define's value-expression in the new env (so mutual references work) and env_set the real value. (define x 1) (define (f) (define x 99) x) (f) ; → 99 (was 99, still 99) x ; → 1 (was 99 wrongly — fixed) (define (h) (define helper (lambda (x) (* x 2))) (helper 5)) helper ; → unbound (was a leaked global procedure — fixed) C-WASM — added a thorough doc-block in lumbda_wasm_entry.c covering the gc.c fallback malloc situation and three plausible real fixes (Boehm-em build, custom mark-sweep over NaN-boxed heap, generational reset). Repl tabbar already surfaces the pressure to the user. Parity corpus locks the new scoping behavior: internal-define-local — global x stays 1 internal-define-returns — f returns 99 internal-define-mutual — mutually-recursive internal defines Tests: 20 unit, 8 integration, 11 functional, 249 parity all green. --- wasm/asm/lumbda.wat | 83 +++++++++++++++++++++++++++++ wasm/c/lumbda_wasm_entry.c | 33 ++++++++++-- wasm/dist-repl/asm/lumbda-asm.wasm | Bin 21131 -> 21427 bytes wasm/tests/parity-corpus.mjs | 8 +++ www/playground/asm/lumbda-asm.wasm | Bin 21131 -> 21427 bytes www/repl/asm/lumbda-asm.wasm | Bin 21131 -> 21427 bytes 6 files changed, 121 insertions(+), 3 deletions(-) diff --git a/wasm/asm/lumbda.wat b/wasm/asm/lumbda.wat index 62d26dd..0ec5860 100644 --- a/wasm/asm/lumbda.wat +++ b/wasm/asm/lumbda.wat @@ -2247,9 +2247,92 @@ (local.set $body (i32.load offset=8 (local.get $fn))) (local.set $env (i32.load offset=12 (local.get $fn))) (local.set $new_env (call $bind_params (local.get $params) (local.get $args) (local.get $env))) + ;; R7RS internal definitions: leading (define ...) forms in a + ;; lambda body bind locally (letrec*-style) instead of polluting + ;; the global env. hoist_internal_defines returns the env extended + ;; with placeholder VOID bindings and advances $body past them; + ;; then we evaluate each define's value-expression in the new env + ;; (so mutual references work) and env_set the real value. + (local.set $new_env (call $hoist_internal_defines (local.get $body) (local.get $new_env))) + (local.set $body (call $strip_leading_defines (local.get $body))) + (call $fill_internal_defines (i32.load offset=8 (local.get $fn)) (local.get $new_env)) (return_call $eval_begin (local.get $body) (local.get $new_env)))) (global.get $VOID)) + ;; Walk leading (define ...) forms in $body; for each, add a placeholder + ;; VOID binding to $env. Returns the extended env. Body is unchanged + ;; (we strip and fill separately to keep iteration simple). + (func $hoist_internal_defines (param $body i32) (param $env i32) (result i32) + (local $form i32) + (local $head i32) + (local $rest i32) + (local $name i32) + (block $done + (loop $l + (br_if $done (i32.eqz (call $is_pair (local.get $body)))) + (local.set $form (call $car (local.get $body))) + (br_if $done (i32.eqz (call $is_pair (local.get $form)))) + (br_if $done (i32.ne (call $car (local.get $form)) (global.get $sym_define))) + (local.set $rest (call $cdr (local.get $form))) + (local.set $head (call $car (local.get $rest))) + ;; (define name expr) — head is a symbol + ;; (define (f . args) body) — head is a pair, name = car + (if (call $is_pair (local.get $head)) + (then (local.set $name (call $car (local.get $head)))) + (else (local.set $name (local.get $head)))) + (local.set $env (call $env_define (local.get $env) (local.get $name) (global.get $VOID))) + (local.set $body (call $cdr (local.get $body))) + (br $l))) + (local.get $env)) + + (func $strip_leading_defines (param $body i32) (result i32) + (local $form i32) + (block $done + (loop $l + (br_if $done (i32.eqz (call $is_pair (local.get $body)))) + (local.set $form (call $car (local.get $body))) + (br_if $done (i32.eqz (call $is_pair (local.get $form)))) + (br_if $done (i32.ne (call $car (local.get $form)) (global.get $sym_define))) + (local.set $body (call $cdr (local.get $body))) + (br $l))) + (local.get $body)) + + ;; Second pass of letrec* expansion — evaluate each internal-define's + ;; value-expression in the new env, env_set the real value. Operates + ;; on the ORIGINAL body (with defines still in it) so we know what to + ;; bind. Forms after the leading defines are skipped. + (func $fill_internal_defines (param $body i32) (param $env i32) + (local $form i32) + (local $rest i32) + (local $head i32) + (local $name i32) + (local $value_expr i32) + (local $params i32) + (local $closure_body i32) + (block $done + (loop $l + (br_if $done (i32.eqz (call $is_pair (local.get $body)))) + (local.set $form (call $car (local.get $body))) + (br_if $done (i32.eqz (call $is_pair (local.get $form)))) + (br_if $done (i32.ne (call $car (local.get $form)) (global.get $sym_define))) + (local.set $rest (call $cdr (local.get $form))) + (local.set $head (call $car (local.get $rest))) + (if (call $is_pair (local.get $head)) + (then + ;; (define (f . params) body...) → desugar to closure + (local.set $name (call $car (local.get $head))) + (local.set $params (call $cdr (local.get $head))) + (local.set $closure_body (call $cdr (local.get $rest))) + (drop (call $env_set (local.get $env) (local.get $name) + (call $make_closure (local.get $params) (local.get $closure_body) (local.get $env))))) + (else + (local.set $name (local.get $head)) + (local.set $value_expr (call $car (call $cdr (local.get $rest)))) + (drop (call $env_set (local.get $env) (local.get $name) + (call $eval (local.get $value_expr) (local.get $env)))))) + (local.set $body (call $cdr (local.get $body))) + (br $l)))) + ;; Pairwise bind params (a list of symbols, or symbol for rest) to args. (func $bind_params (param $params i32) (param $args i32) (param $env i32) (result i32) (local $new i32) diff --git a/wasm/c/lumbda_wasm_entry.c b/wasm/c/lumbda_wasm_entry.c index b4648e2..af407c5 100644 --- a/wasm/c/lumbda_wasm_entry.c +++ b/wasm/c/lumbda_wasm_entry.c @@ -1,12 +1,39 @@ /* lumbda_wasm_entry.c — Emscripten entry points for the C tier. * + * MEMORY MODEL — c-wasm tier + * --------------------------- + * The native C tier links libgc (Boehm conservative collector) and + * everything is GC-managed. The WASM build defines LUMBDA_NO_BOEHM (no + * libgc port wired up to Emscripten), so gc.c takes its plain-malloc + * fallback path. malloc returns memory; nothing ever returns it. + * + * For browser use this manifests as monotonic linear-memory growth in + * the worker. The REPL surfaces the pressure in its tabbar ("c 48M ↑") + * and the "reboot tier" button gives users a manual reclaim path — + * terminate + respawn the worker, which destroys the heap entirely. + * + * Real fixes, in order of decreasing cost: + * 1. Build bdwgc (github.com/ivmai/bdwgc) with emcc and link the + * WASM build with USE_BOEHM_GC defined. Their build system has + * a single-threaded mode that would suit our worker context. + * 2. Write a small mark-sweep over the existing NaN-boxed heap. + * The asm WAT tier already does this with a Cheney copying GC + * (lumbda_gc, gc_collect in asm/lumbda.wat). Same algorithm + * ports here once we know each lumbda struct's pointer-field + * layout — types.c is the source of truth there. + * 3. Generational reset: between top-level evals, snapshot global + * env + interns to s-expressions, tear down everything, replay. + * Crudest of the three. + * + * ENTRY POINTS + * ------------ * The JS loader provides Module.print / Module.printErr callbacks that * Emscripten routes stdout/stderr through, so output capture happens on * the JS side. We only expose: * - * lumbda_wasm_init() — set up symbols + env + stdlib - * lumbda_wasm_eval(src) — eval src, last value printed if non-void - * lumbda_wasm_free_result(p) — free a string returned to JS + * lumbda_wasm_init() - set up symbols + env + stdlib + * lumbda_wasm_eval(src) - eval src, last value printed if non-void + * lumbda_wasm_free_result(p) - free a string returned to JS * * The env is module-global so successive eval calls preserve defines. */ diff --git a/wasm/dist-repl/asm/lumbda-asm.wasm b/wasm/dist-repl/asm/lumbda-asm.wasm index f1374d1847643e9dfc6b1ec2d842626f643d0795..b937d55fc0de5f3a30914508b580aa81f8ead672 100644 GIT binary patch delta 2376 zcmZwIYitx%6bJCRJ2U&5ZTFU96%d$Vi-Krdu-FzKa3nm^H6cR$K#W#|A_0^JT9j&? zRjX7$-FAu=MY|?7HcCv*@}a>Rmr`IURjcx-S_QRYQB2VE1BoPf&Y5kA!7sDF^S|ew zxih=T?tD)q-xu*^>bs(~arstN75c~NkeJ*ejEyyqKe}dBMdj928 zHWeAx7MRKmk7f$l>};Pl-QH&DrfTYPD}Bl*EtQu27(7MsxOGOvGFIYV&jJ;9%Fv=L zfgk0T$Mm>`N0vvjmtg9VQA;y5TvKsU2ydjKbsTHyhAB)vV#yU2?T%VXNHH)oX4^ts zGg{!`_&#EE^wlkjQ73%Y?D+q~&@sySNOTxFn2y(|<-9KGW;e!$^RfA&O2)h zR*1NBpkT)2MuE-PXzBbS^~jXP^vdu(m&-=yNr=3~F^NOG0xFj$Z~C~(LPK*;CZpeGY3qrqV}kk7m8M>6FLZh17*?qBbGHaFL-chh5;rlok} z9Q?tn@?ITGL%x{&d+tL5UUIumX1cb!$Rc-PtV1@jhdaW=G*c4$ zkUq#Eo?wn64kC5PB@Q7q7*D)_^ul$-5u^%v#7mrBPmCR+Z8yl>Ha;`j4-+VEMh0La zF@aQJ60wbWBe5N+!DM0wQimzTPEMy1Q^)|A#6G4)96^ux`>W=>}i6U>>!c4QE4A@1kiS;Q1lgIkGd=554&W&v@4S%|dZELw*m ziieS2m`%Kh^uZkBB~EWA#tu_&F|mO;ml#KS;SOR8QiBp=f*VSS?VOeoJCJ^uN9;ld zY`Bx0=8n6Fu_G#E!QI3-G6?q&_aXx@pV-a~DTUmL_QOGT54)S)&pn6OXVGEkVUM6g zaF|_x6m7#1E}F5(hNJ8@cAC8(ZNM>h3avvgJB`kQlzN zJApQ!pKb5MA`8Caq6_VXuh~89Z`gh4Y&gLlWS?YTK!@QJ`!d>q)9m;$nlr#|LHpoa zb^>j{ckF%WEco8$q6>>446@VQ^aJ|@Is`wmhuCM>Bj_-kW!Lx8(4W|GbQpeSC(zmO z3%diI2Se;MdK~=99_+QLILF0B-uxRocAN$cv*YL-IL~fl|IY42>+lCVg$}_5c0W1@ nf0E@J!q|p?=&}X>tMc);74zqN=xQqK^VNuDiDcFMNxA<5D7aQg delta 2067 zcmZA0T~JhI7zgn4?w+$>XP12uqtMi|u9TML8pv0rJd-00G{a1F(M{u!GCE4?2uNym zwxT3NSUpQo?$ln;#1t1a>jl%`%!-JsP*H(Wk(yy)TEhi1F_RZP&$BBtb@%)K-{-s^ zJF|&T#nDehq}u+Gh#i0Sz!xK4VRj0=uJPrUw%2T}Jy27pyeHh5xMQ30zR1pmy^Rg~ z8XLCmYuK-J{lBcM-J^W4P}c8l+`p}sCqEQ2^ZCx&ntfXv>ua|wCl>~}YtZ*L;Hfd; zIe9pisIhfsi6-lBSudIryR%O_*LDcQ0ioTpLLL(Vr=fw^70|qjt{a+R+oF~ms#*?; ztT3FiJ@I^A(4DhcsHUMvKdvdOZK@&UajSJ@zV|>tGwg=8Xr~?wSX061;-;X18w3r% zj^kB0S0$Sg&8s{DZ_trnjW@WO|Cw9duv*tWCyrTH)~#OFE^rtJ1DdVeUT$c?@{t7J> zk%VV`g)O&31U$Oo2GJOB8*X~Ap~?7wLr zA><*RD7{-K@{o0U!{_EhVUwG!TSb2HhV@U;a`9$jN%4yU-b#;6WWH)n$4lvmS9&{LTwW;N zNf(n`2zkWnFIgkrwXT;uBU{o-S2KGZu{M@Y$kuduGgBV5f@PJW&Dv8|B`oX9vKjM) zfaXCv2(U?H3)4<)W2(dmQzJ&14q_M6NsJ+F;3CGEZell51rMfP9ZhOAxKUX1))QxuPI!zs&usxQe2!)di7m_x#0b&>MZ_+o3dO`Y z50nslxGg32Aze^L96-8Fc$}Q#i6@BRVLSNXNn!-)fsMo%(hZx4Jv@+9$o*&+oM8{K z2iaphbC!J#?S~=u9NG)#*v;qBCJb|kV#vb3D{b1%PO(p+b-2JzqBXe4PN97;!k%Q0 zvS-i|zGC0u{%dwyiu!NZakLI&Y_k`G55DCvfOf!l>>>8|>``~XXguCS-sSJ`uDKU`xs zU!+ZcVMow@xXzBFv*1^DA37JN*(vlQxWS&fxP!uP9A^3B-`U|2+GvIyL1)8Fb~pPE hc0XE!TkIs-3%A*0Xb=2JmQkS}6vC99iEw#A&c9NfG%WxC diff --git a/wasm/tests/parity-corpus.mjs b/wasm/tests/parity-corpus.mjs index 1979f3c..460ea15 100644 --- a/wasm/tests/parity-corpus.mjs +++ b/wasm/tests/parity-corpus.mjs @@ -50,6 +50,14 @@ export const CORPUS = [ { tag: "gc-survives-eval", expected: "(1 4 9 16)", src: "(let loop ((i 0)) (if (= i 200) 0 (begin (cons i i) (loop (+ i 1))))) (map (lambda (x) (* x x)) (list 1 2 3 4))" }, + // ─── Internal-define scoping (R7RS letrec*) ────────────────────── + { tag: "internal-define-local", expected: "1", + src: "(define x 1) (define (f) (define x 99) x) (f) x" }, + { tag: "internal-define-returns", expected: "99", + src: "(define (f) (define x 99) x) (f)" }, + { tag: "internal-define-mutual", expected: "#t", + src: "(define (g) (define (e? n) (if (= n 0) #t (o? (- n 1)))) (define (o? n) (if (= n 0) #f (e? (- n 1)))) (e? 6)) (g)" }, + // ─── division semantics ────────────────────────────────────────── { tag: "quotient-pos", src: "(quotient 17 5)", expected: "3" }, { tag: "quotient-neg", src: "(quotient -17 5)", expected: "-3" }, diff --git a/www/playground/asm/lumbda-asm.wasm b/www/playground/asm/lumbda-asm.wasm index f1374d1847643e9dfc6b1ec2d842626f643d0795..b937d55fc0de5f3a30914508b580aa81f8ead672 100644 GIT binary patch delta 2376 zcmZwIYitx%6bJCRJ2U&5ZTFU96%d$Vi-Krdu-FzKa3nm^H6cR$K#W#|A_0^JT9j&? zRjX7$-FAu=MY|?7HcCv*@}a>Rmr`IURjcx-S_QRYQB2VE1BoPf&Y5kA!7sDF^S|ew zxih=T?tD)q-xu*^>bs(~arstN75c~NkeJ*ejEyyqKe}dBMdj928 zHWeAx7MRKmk7f$l>};Pl-QH&DrfTYPD}Bl*EtQu27(7MsxOGOvGFIYV&jJ;9%Fv=L zfgk0T$Mm>`N0vvjmtg9VQA;y5TvKsU2ydjKbsTHyhAB)vV#yU2?T%VXNHH)oX4^ts zGg{!`_&#EE^wlkjQ73%Y?D+q~&@sySNOTxFn2y(|<-9KGW;e!$^RfA&O2)h zR*1NBpkT)2MuE-PXzBbS^~jXP^vdu(m&-=yNr=3~F^NOG0xFj$Z~C~(LPK*;CZpeGY3qrqV}kk7m8M>6FLZh17*?qBbGHaFL-chh5;rlok} z9Q?tn@?ITGL%x{&d+tL5UUIumX1cb!$Rc-PtV1@jhdaW=G*c4$ zkUq#Eo?wn64kC5PB@Q7q7*D)_^ul$-5u^%v#7mrBPmCR+Z8yl>Ha;`j4-+VEMh0La zF@aQJ60wbWBe5N+!DM0wQimzTPEMy1Q^)|A#6G4)96^ux`>W=>}i6U>>!c4QE4A@1kiS;Q1lgIkGd=554&W&v@4S%|dZELw*m ziieS2m`%Kh^uZkBB~EWA#tu_&F|mO;ml#KS;SOR8QiBp=f*VSS?VOeoJCJ^uN9;ld zY`Bx0=8n6Fu_G#E!QI3-G6?q&_aXx@pV-a~DTUmL_QOGT54)S)&pn6OXVGEkVUM6g zaF|_x6m7#1E}F5(hNJ8@cAC8(ZNM>h3avvgJB`kQlzN zJApQ!pKb5MA`8Caq6_VXuh~89Z`gh4Y&gLlWS?YTK!@QJ`!d>q)9m;$nlr#|LHpoa zb^>j{ckF%WEco8$q6>>446@VQ^aJ|@Is`wmhuCM>Bj_-kW!Lx8(4W|GbQpeSC(zmO z3%diI2Se;MdK~=99_+QLILF0B-uxRocAN$cv*YL-IL~fl|IY42>+lCVg$}_5c0W1@ nf0E@J!q|p?=&}X>tMc);74zqN=xQqK^VNuDiDcFMNxA<5D7aQg delta 2067 zcmZA0T~JhI7zgn4?w+$>XP12uqtMi|u9TML8pv0rJd-00G{a1F(M{u!GCE4?2uNym zwxT3NSUpQo?$ln;#1t1a>jl%`%!-JsP*H(Wk(yy)TEhi1F_RZP&$BBtb@%)K-{-s^ zJF|&T#nDehq}u+Gh#i0Sz!xK4VRj0=uJPrUw%2T}Jy27pyeHh5xMQ30zR1pmy^Rg~ z8XLCmYuK-J{lBcM-J^W4P}c8l+`p}sCqEQ2^ZCx&ntfXv>ua|wCl>~}YtZ*L;Hfd; zIe9pisIhfsi6-lBSudIryR%O_*LDcQ0ioTpLLL(Vr=fw^70|qjt{a+R+oF~ms#*?; ztT3FiJ@I^A(4DhcsHUMvKdvdOZK@&UajSJ@zV|>tGwg=8Xr~?wSX061;-;X18w3r% zj^kB0S0$Sg&8s{DZ_trnjW@WO|Cw9duv*tWCyrTH)~#OFE^rtJ1DdVeUT$c?@{t7J> zk%VV`g)O&31U$Oo2GJOB8*X~Ap~?7wLr zA><*RD7{-K@{o0U!{_EhVUwG!TSb2HhV@U;a`9$jN%4yU-b#;6WWH)n$4lvmS9&{LTwW;N zNf(n`2zkWnFIgkrwXT;uBU{o-S2KGZu{M@Y$kuduGgBV5f@PJW&Dv8|B`oX9vKjM) zfaXCv2(U?H3)4<)W2(dmQzJ&14q_M6NsJ+F;3CGEZell51rMfP9ZhOAxKUX1))QxuPI!zs&usxQe2!)di7m_x#0b&>MZ_+o3dO`Y z50nslxGg32Aze^L96-8Fc$}Q#i6@BRVLSNXNn!-)fsMo%(hZx4Jv@+9$o*&+oM8{K z2iaphbC!J#?S~=u9NG)#*v;qBCJb|kV#vb3D{b1%PO(p+b-2JzqBXe4PN97;!k%Q0 zvS-i|zGC0u{%dwyiu!NZakLI&Y_k`G55DCvfOf!l>>>8|>``~XXguCS-sSJ`uDKU`xs zU!+ZcVMow@xXzBFv*1^DA37JN*(vlQxWS&fxP!uP9A^3B-`U|2+GvIyL1)8Fb~pPE hc0XE!TkIs-3%A*0Xb=2JmQkS}6vC99iEw#A&c9NfG%WxC diff --git a/www/repl/asm/lumbda-asm.wasm b/www/repl/asm/lumbda-asm.wasm index f1374d1847643e9dfc6b1ec2d842626f643d0795..b937d55fc0de5f3a30914508b580aa81f8ead672 100644 GIT binary patch delta 2376 zcmZwIYitx%6bJCRJ2U&5ZTFU96%d$Vi-Krdu-FzKa3nm^H6cR$K#W#|A_0^JT9j&? zRjX7$-FAu=MY|?7HcCv*@}a>Rmr`IURjcx-S_QRYQB2VE1BoPf&Y5kA!7sDF^S|ew zxih=T?tD)q-xu*^>bs(~arstN75c~NkeJ*ejEyyqKe}dBMdj928 zHWeAx7MRKmk7f$l>};Pl-QH&DrfTYPD}Bl*EtQu27(7MsxOGOvGFIYV&jJ;9%Fv=L zfgk0T$Mm>`N0vvjmtg9VQA;y5TvKsU2ydjKbsTHyhAB)vV#yU2?T%VXNHH)oX4^ts zGg{!`_&#EE^wlkjQ73%Y?D+q~&@sySNOTxFn2y(|<-9KGW;e!$^RfA&O2)h zR*1NBpkT)2MuE-PXzBbS^~jXP^vdu(m&-=yNr=3~F^NOG0xFj$Z~C~(LPK*;CZpeGY3qrqV}kk7m8M>6FLZh17*?qBbGHaFL-chh5;rlok} z9Q?tn@?ITGL%x{&d+tL5UUIumX1cb!$Rc-PtV1@jhdaW=G*c4$ zkUq#Eo?wn64kC5PB@Q7q7*D)_^ul$-5u^%v#7mrBPmCR+Z8yl>Ha;`j4-+VEMh0La zF@aQJ60wbWBe5N+!DM0wQimzTPEMy1Q^)|A#6G4)96^ux`>W=>}i6U>>!c4QE4A@1kiS;Q1lgIkGd=554&W&v@4S%|dZELw*m ziieS2m`%Kh^uZkBB~EWA#tu_&F|mO;ml#KS;SOR8QiBp=f*VSS?VOeoJCJ^uN9;ld zY`Bx0=8n6Fu_G#E!QI3-G6?q&_aXx@pV-a~DTUmL_QOGT54)S)&pn6OXVGEkVUM6g zaF|_x6m7#1E}F5(hNJ8@cAC8(ZNM>h3avvgJB`kQlzN zJApQ!pKb5MA`8Caq6_VXuh~89Z`gh4Y&gLlWS?YTK!@QJ`!d>q)9m;$nlr#|LHpoa zb^>j{ckF%WEco8$q6>>446@VQ^aJ|@Is`wmhuCM>Bj_-kW!Lx8(4W|GbQpeSC(zmO z3%diI2Se;MdK~=99_+QLILF0B-uxRocAN$cv*YL-IL~fl|IY42>+lCVg$}_5c0W1@ nf0E@J!q|p?=&}X>tMc);74zqN=xQqK^VNuDiDcFMNxA<5D7aQg delta 2067 zcmZA0T~JhI7zgn4?w+$>XP12uqtMi|u9TML8pv0rJd-00G{a1F(M{u!GCE4?2uNym zwxT3NSUpQo?$ln;#1t1a>jl%`%!-JsP*H(Wk(yy)TEhi1F_RZP&$BBtb@%)K-{-s^ zJF|&T#nDehq}u+Gh#i0Sz!xK4VRj0=uJPrUw%2T}Jy27pyeHh5xMQ30zR1pmy^Rg~ z8XLCmYuK-J{lBcM-J^W4P}c8l+`p}sCqEQ2^ZCx&ntfXv>ua|wCl>~}YtZ*L;Hfd; zIe9pisIhfsi6-lBSudIryR%O_*LDcQ0ioTpLLL(Vr=fw^70|qjt{a+R+oF~ms#*?; ztT3FiJ@I^A(4DhcsHUMvKdvdOZK@&UajSJ@zV|>tGwg=8Xr~?wSX061;-;X18w3r% zj^kB0S0$Sg&8s{DZ_trnjW@WO|Cw9duv*tWCyrTH)~#OFE^rtJ1DdVeUT$c?@{t7J> zk%VV`g)O&31U$Oo2GJOB8*X~Ap~?7wLr zA><*RD7{-K@{o0U!{_EhVUwG!TSb2HhV@U;a`9$jN%4yU-b#;6WWH)n$4lvmS9&{LTwW;N zNf(n`2zkWnFIgkrwXT;uBU{o-S2KGZu{M@Y$kuduGgBV5f@PJW&Dv8|B`oX9vKjM) zfaXCv2(U?H3)4<)W2(dmQzJ&14q_M6NsJ+F;3CGEZell51rMfP9ZhOAxKUX1))QxuPI!zs&usxQe2!)di7m_x#0b&>MZ_+o3dO`Y z50nslxGg32Aze^L96-8Fc$}Q#i6@BRVLSNXNn!-)fsMo%(hZx4Jv@+9$o*&+oM8{K z2iaphbC!J#?S~=u9NG)#*v;qBCJb|kV#vb3D{b1%PO(p+b-2JzqBXe4PN97;!k%Q0 zvS-i|zGC0u{%dwyiu!NZakLI&Y_k`G55DCvfOf!l>>>8|>``~XXguCS-sSJ`uDKU`xs zU!+ZcVMow@xXzBFv*1^DA37JN*(vlQxWS&fxP!uP9A^3B-`U|2+GvIyL1)8Fb~pPE hc0XE!TkIs-3%A*0Xb=2JmQkS}6vC99iEw#A&c9NfG%WxC