plum

#treesitter#compiler#wasm

git clone https://git.pyrossh.dev/plum

A statically typed, imperative programming language inspired by rust, python


0e39618Peter John 2026-08-10T12:51:35+05:30
feat(plum-wasm-codegen): migrate structs, enums, strings, and closures to wasm-gc
Cargo.lock CHANGED
@@ -4,20 +4,20 @@ version = 4
4
4
 
5
5
  [[package]]
6
6
  name = "addr2line"
7
- version = "0.24.2"
7
+ version = "0.25.1"
8
8
  source = "registry+https://github.com/rust-lang/crates.io-index"
9
- checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
9
+ checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b"
10
10
  dependencies = [
11
- "gimli 0.31.1",
11
+ "gimli 0.32.3",
12
12
  ]
13
13
 
14
14
  [[package]]
15
15
  name = "addr2line"
16
- version = "0.25.1"
16
+ version = "0.26.1"
17
17
  source = "registry+https://github.com/rust-lang/crates.io-index"
18
- checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b"
18
+ checksum = "59317f77929f0e679d39364702289274de2f0f0b22cbf50b2b8cff2169a0b27a"
19
19
  dependencies = [
20
- "gimli 0.32.3",
20
+ "gimli 0.33.0",
21
21
  ]
22
22
 
23
23
  [[package]]
@@ -89,7 +89,7 @@ version = "1.1.5"
89
89
  source = "registry+https://github.com/rust-lang/crates.io-index"
90
90
  checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
91
91
  dependencies = [
92
- "windows-sys 0.61.2",
92
+ "windows-sys",
93
93
  ]
94
94
 
95
95
  [[package]]
@@ -100,7 +100,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
100
100
  dependencies = [
101
101
  "anstyle",
102
102
  "once_cell_polyfill",
103
- "windows-sys 0.61.2",
103
+ "windows-sys",
104
104
  ]
105
105
 
106
106
  [[package]]
@@ -109,15 +109,6 @@ version = "1.0.104"
109
109
  source = "registry+https://github.com/rust-lang/crates.io-index"
110
110
  checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
111
111
 
112
- [[package]]
113
- name = "ar_archive_writer"
114
- version = "0.5.2"
115
- source = "registry+https://github.com/rust-lang/crates.io-index"
116
- checksum = "4087686b4b0a3427190bae57a1d9a478dbb2d40c5dc1bd6e2b6d797913bdd348"
117
- dependencies = [
118
- "object 0.37.3",
119
- ]
120
-
121
112
  [[package]]
122
113
  name = "arbitrary"
123
114
  version = "1.4.2"
@@ -161,9 +152,9 @@ dependencies = [
161
152
 
162
153
  [[package]]
163
154
  name = "base64"
164
- version = "0.21.7"
155
+ version = "0.22.1"
165
156
  source = "registry+https://github.com/rust-lang/crates.io-index"
166
- checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
157
+ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
167
158
 
168
159
  [[package]]
169
160
  name = "bitflags"
@@ -189,12 +180,6 @@ dependencies = [
189
180
  "allocator-api2",
190
181
  ]
191
182
 
192
- [[package]]
193
- name = "byteorder"
194
- version = "1.5.0"
195
- source = "registry+https://github.com/rust-lang/crates.io-index"
196
- checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
197
-
198
183
  [[package]]
199
184
  name = "cc"
200
185
  version = "1.3.0"
@@ -270,9 +255,9 @@ checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
270
255
 
271
256
  [[package]]
272
257
  name = "cpp_demangle"
273
- version = "0.4.5"
258
+ version = "0.5.1"
274
259
  source = "registry+https://github.com/rust-lang/crates.io-index"
275
- checksum = "f2bb79cb74d735044c972aae58ed0aaa9a837e85b01106a54c39e42e97f62253"
260
+ checksum = "0667304c32ea56cb4cd6d2d7c0cfe9a2f8041229db8c033af7f8d69492429def"
276
261
  dependencies = [
277
262
  "cfg-if",
278
263
  ]
@@ -286,32 +271,53 @@ dependencies = [
286
271
  "libc",
287
272
  ]
288
273
 
274
+ [[package]]
275
+ name = "cranelift-assembler-x64"
276
+ version = "0.134.3"
277
+ source = "registry+https://github.com/rust-lang/crates.io-index"
278
+ checksum = "d552bd33b7a56dc70aeb1e1c960e51a218fa0db50f23873b500a310379450b2d"
279
+ dependencies = [
280
+ "cranelift-assembler-x64-meta",
281
+ ]
282
+
283
+ [[package]]
284
+ name = "cranelift-assembler-x64-meta"
285
+ version = "0.134.3"
286
+ source = "registry+https://github.com/rust-lang/crates.io-index"
287
+ checksum = "078e80e4c222279e3330f6aa1a256ca77ddf156d4453166a9f09defedc4594dd"
288
+ dependencies = [
289
+ "cranelift-srcgen",
290
+ ]
291
+
289
292
  [[package]]
290
293
  name = "cranelift-bforest"
291
- version = "0.115.1"
294
+ version = "0.134.3"
292
295
  source = "registry+https://github.com/rust-lang/crates.io-index"
293
- checksum = "88c1d02b72b6c411c0a2e92b25ed791ad5d071184193c08a34aa0fdcdf000b72"
296
+ checksum = "820ce15d4ad3562d613c31a67b6d0434d403e7091a68d1349903842f7d31737e"
294
297
  dependencies = [
295
298
  "cranelift-entity",
299
+ "wasmtime-internal-core",
296
300
  ]
297
301
 
298
302
  [[package]]
299
303
  name = "cranelift-bitset"
300
- version = "0.115.1"
304
+ version = "0.134.3"
301
305
  source = "registry+https://github.com/rust-lang/crates.io-index"
302
- checksum = "720b93bd86ebbb23ebfb2db1ed44d54b2ecbdbb2d034d485bc64aa605ee787ab"
306
+ checksum = "61bca563d4b86d285928d9e27f97f27039bb33a0fc524fa130d7d0c106bf8ab3"
303
307
  dependencies = [
304
308
  "serde",
305
309
  "serde_derive",
310
+ "wasmtime-internal-core",
306
311
  ]
307
312
 
308
313
  [[package]]
309
314
  name = "cranelift-codegen"
310
- version = "0.115.1"
315
+ version = "0.134.3"
311
316
  source = "registry+https://github.com/rust-lang/crates.io-index"
312
- checksum = "aed3d2d9914d30b460eedd7fd507720203023997bef71452ce84873f9c93537c"
317
+ checksum = "709f4b7c0fb57d952658d5b5c07fbdc4149acd7b7f0de9678ae754b9c981949a"
313
318
  dependencies = [
314
319
  "bumpalo",
320
+ "cranelift-assembler-x64",
315
321
  "cranelift-bforest",
316
322
  "cranelift-bitset",
317
323
  "cranelift-codegen-meta",
@@ -319,58 +325,70 @@ dependencies = [
319
325
  "cranelift-control",
320
326
  "cranelift-entity",
321
327
  "cranelift-isle",
322
- "gimli 0.31.1",
328
+ "gimli 0.33.0",
323
- "hashbrown 0.14.5",
329
+ "hashbrown 0.17.1",
330
+ "libm",
324
331
  "log",
332
+ "postcard",
333
+ "pulley-interpreter",
325
334
  "regalloc2",
326
335
  "rustc-hash",
327
336
  "serde",
337
+ "serde_derive",
338
+ "sha2",
328
339
  "smallvec",
329
340
  "target-lexicon",
341
+ "wasmtime-internal-core",
330
342
  ]
331
343
 
332
344
  [[package]]
333
345
  name = "cranelift-codegen-meta"
334
- version = "0.115.1"
346
+ version = "0.134.3"
335
347
  source = "registry+https://github.com/rust-lang/crates.io-index"
336
- checksum = "888c188d32263ec9e048873ff0b68c700933600d553f4412417916828be25f8e"
348
+ checksum = "903dc8915af62aad1d9d0f39a5968d33fa80b9aa899ed6f56e47f40ca4512e1e"
337
349
  dependencies = [
350
+ "cranelift-assembler-x64-meta",
338
351
  "cranelift-codegen-shared",
352
+ "cranelift-srcgen",
353
+ "heck",
354
+ "pulley-interpreter",
339
355
  ]
340
356
 
341
357
  [[package]]
342
358
  name = "cranelift-codegen-shared"
343
- version = "0.115.1"
359
+ version = "0.134.3"
344
360
  source = "registry+https://github.com/rust-lang/crates.io-index"
345
- checksum = "4ddd5f4114d04ce7e073dd74e2ad16541fc61970726fcc8b2d5644a154ee4127"
361
+ checksum = "0522d74c227e49f3fd49ab055311486ae4f09083262b66705bed676952491469"
346
362
 
347
363
  [[package]]
348
364
  name = "cranelift-control"
349
- version = "0.115.1"
365
+ version = "0.134.3"
350
366
  source = "registry+https://github.com/rust-lang/crates.io-index"
351
- checksum = "92cc4c98d6a4256a1600d93ccd3536f3e77da9b4ca2c279de786ac22876e67d6"
367
+ checksum = "66560ea1c5cef72e170b18e46d263dba2d3169c9d39e8cafdab2173c6362cc1a"
352
368
  dependencies = [
353
369
  "arbitrary",
354
370
  ]
355
371
 
356
372
  [[package]]
357
373
  name = "cranelift-entity"
358
- version = "0.115.1"
374
+ version = "0.134.3"
359
375
  source = "registry+https://github.com/rust-lang/crates.io-index"
360
- checksum = "760af4b5e051b5f82097a27274b917e3751736369fa73660513488248d27f23d"
376
+ checksum = "b62ef5b17cc814d27e96b66a5b46da0e4ce2b8ac55a6d478048bb99d04b05526"
361
377
  dependencies = [
362
378
  "cranelift-bitset",
363
379
  "serde",
364
380
  "serde_derive",
381
+ "wasmtime-internal-core",
365
382
  ]
366
383
 
367
384
  [[package]]
368
385
  name = "cranelift-frontend"
369
- version = "0.115.1"
386
+ version = "0.134.3"
370
387
  source = "registry+https://github.com/rust-lang/crates.io-index"
371
- checksum = "c0bf77ec0f470621655ec7539860b5c620d4f91326654ab21b075b83900f8831"
388
+ checksum = "a87e0aaa39dbf70693b348a221e45904111704ee8f9fef140498471005f9842d"
372
389
  dependencies = [
373
390
  "cranelift-codegen",
391
+ "hashbrown 0.17.1",
374
392
  "log",
375
393
  "smallvec",
376
394
  "target-lexicon",
@@ -378,21 +396,27 @@ dependencies = [
378
396
 
379
397
  [[package]]
380
398
  name = "cranelift-isle"
381
- version = "0.115.1"
399
+ version = "0.134.3"
382
400
  source = "registry+https://github.com/rust-lang/crates.io-index"
383
- checksum = "4b665d0a6932c421620be184f9fc7f7adaf1b0bc2fa77bb7ac5177c49abf645b"
401
+ checksum = "cf79003ebfa1eed5e87f3b84446ad5236f540268289960e14f387dc9b28e40b7"
384
402
 
385
403
  [[package]]
386
404
  name = "cranelift-native"
387
- version = "0.115.1"
405
+ version = "0.134.3"
388
406
  source = "registry+https://github.com/rust-lang/crates.io-index"
389
- checksum = "bb2e75d1bd43dfec10924798f15e6474f1dbf63b0024506551aa19394dbe72ab"
407
+ checksum = "05bf4f235743c81e67ee4db617c5a4a0b65d58d3f0cfc575ee0f1a4e0cd58273"
390
408
  dependencies = [
391
409
  "cranelift-codegen",
392
410
  "libc",
393
411
  "target-lexicon",
394
412
  ]
395
413
 
414
+ [[package]]
415
+ name = "cranelift-srcgen"
416
+ version = "0.134.3"
417
+ source = "registry+https://github.com/rust-lang/crates.io-index"
418
+ checksum = "f6977c2a71ab1e0d1e62f966b411a498aa04c4dce47d93d52f8a360a06058922"
419
+
396
420
  [[package]]
397
421
  name = "crc32fast"
398
422
  version = "1.5.0"
@@ -523,15 +547,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
523
547
  checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
524
548
  dependencies = [
525
549
  "libc",
526
- "windows-sys 0.61.2",
550
+ "windows-sys",
527
551
  ]
528
552
 
529
- [[package]]
530
- name = "fallible-iterator"
531
- version = "0.3.0"
532
- source = "registry+https://github.com/rust-lang/crates.io-index"
533
- checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
534
-
535
553
  [[package]]
536
554
  name = "fastrand"
537
555
  version = "2.5.0"
@@ -544,11 +562,23 @@ version = "0.1.9"
544
562
  source = "registry+https://github.com/rust-lang/crates.io-index"
545
563
  checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
546
564
 
565
+ [[package]]
566
+ name = "fixedbitset"
567
+ version = "0.4.2"
568
+ source = "registry+https://github.com/rust-lang/crates.io-index"
569
+ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
570
+
571
+ [[package]]
572
+ name = "fnv"
573
+ version = "1.0.7"
574
+ source = "registry+https://github.com/rust-lang/crates.io-index"
575
+ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
576
+
547
577
  [[package]]
548
578
  name = "foldhash"
549
- version = "0.1.5"
579
+ version = "0.2.0"
550
580
  source = "registry+https://github.com/rust-lang/crates.io-index"
551
- checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
581
+ checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
552
582
 
553
583
  [[package]]
554
584
  name = "futures"
@@ -638,25 +668,17 @@ dependencies = [
638
668
  "slab",
639
669
  ]
640
670
 
641
- [[package]]
642
- name = "fxhash"
643
- version = "0.2.1"
644
- source = "registry+https://github.com/rust-lang/crates.io-index"
645
- checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
646
- dependencies = [
647
- "byteorder",
648
- ]
649
-
650
671
  [[package]]
651
672
  name = "fxprof-processed-profile"
652
- version = "0.6.0"
673
+ version = "0.8.1"
653
674
  source = "registry+https://github.com/rust-lang/crates.io-index"
654
- checksum = "27d12c0aed7f1e24276a241aadc4cb8ea9f83000f34bc062b7cc2d51e3b0fabd"
675
+ checksum = "25234f20a3ec0a962a61770cfe39ecf03cb529a6e474ad8cff025ed497eda557"
655
676
  dependencies = [
656
677
  "bitflags",
657
678
  "debugid",
658
- "fxhash",
679
+ "rustc-hash",
659
680
  "serde",
681
+ "serde_derive",
660
682
  "serde_json",
661
683
  ]
662
684
 
@@ -694,20 +716,21 @@ dependencies = [
694
716
 
695
717
  [[package]]
696
718
  name = "gimli"
697
- version = "0.31.1"
719
+ version = "0.32.3"
698
720
  source = "registry+https://github.com/rust-lang/crates.io-index"
699
- checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
721
+ checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
700
- dependencies = [
701
- "fallible-iterator",
702
- "indexmap",
703
- "stable_deref_trait",
704
- ]
705
722
 
706
723
  [[package]]
707
724
  name = "gimli"
708
- version = "0.32.3"
725
+ version = "0.33.0"
709
726
  source = "registry+https://github.com/rust-lang/crates.io-index"
710
- checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
727
+ checksum = "0bf7f043f89559805f8c7cacc432749b2fa0d0a0a9ee46ce47164ed5ba7f126c"
728
+ dependencies = [
729
+ "fnv",
730
+ "hashbrown 0.16.1",
731
+ "indexmap",
732
+ "stable_deref_trait",
733
+ ]
711
734
 
712
735
  [[package]]
713
736
  name = "hashbrown"
@@ -721,19 +744,20 @@ dependencies = [
721
744
 
722
745
  [[package]]
723
746
  name = "hashbrown"
724
- version = "0.15.5"
747
+ version = "0.16.1"
725
748
  source = "registry+https://github.com/rust-lang/crates.io-index"
726
- checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
749
+ checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
727
- dependencies = [
728
- "foldhash",
729
- "serde",
730
- ]
731
750
 
732
751
  [[package]]
733
752
  name = "hashbrown"
734
753
  version = "0.17.1"
735
754
  source = "registry+https://github.com/rust-lang/crates.io-index"
736
755
  checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
756
+ dependencies = [
757
+ "foldhash",
758
+ "serde",
759
+ "serde_core",
760
+ ]
737
761
 
738
762
  [[package]]
739
763
  name = "heck"
@@ -782,9 +806,9 @@ dependencies = [
782
806
 
783
807
  [[package]]
784
808
  name = "itertools"
785
- version = "0.12.1"
809
+ version = "0.14.0"
786
810
  source = "registry+https://github.com/rust-lang/crates.io-index"
787
- checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
811
+ checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
788
812
  dependencies = [
789
813
  "either",
790
814
  ]
@@ -868,12 +892,6 @@ dependencies = [
868
892
  "libc",
869
893
  ]
870
894
 
871
- [[package]]
872
- name = "linux-raw-sys"
873
- version = "0.4.15"
874
- source = "registry+https://github.com/rust-lang/crates.io-index"
875
- checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
876
-
877
895
  [[package]]
878
896
  name = "linux-raw-sys"
879
897
  version = "0.12.1"
@@ -888,12 +906,9 @@ checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
888
906
 
889
907
  [[package]]
890
908
  name = "mach2"
891
- version = "0.4.3"
909
+ version = "0.6.0"
892
910
  source = "registry+https://github.com/rust-lang/crates.io-index"
893
- checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44"
911
+ checksum = "dae608c151f68243f2b000364e1f7b186d9c29845f7d2d85bd31b9ad77ad552b"
894
- dependencies = [
895
- "libc",
896
- ]
897
912
 
898
913
  [[package]]
899
914
  name = "memchr"
@@ -907,7 +922,7 @@ version = "0.6.5"
907
922
  source = "registry+https://github.com/rust-lang/crates.io-index"
908
923
  checksum = "ad38eb12aea514a0466ea40a80fd8cc83637065948eb4a426e4aa46261175227"
909
924
  dependencies = [
910
- "rustix 1.1.4",
925
+ "rustix",
911
926
  ]
912
927
 
913
928
  [[package]]
@@ -951,22 +966,22 @@ dependencies = [
951
966
 
952
967
  [[package]]
953
968
  name = "object"
954
- version = "0.36.7"
969
+ version = "0.37.3"
955
970
  source = "registry+https://github.com/rust-lang/crates.io-index"
956
- checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
971
+ checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe"
957
972
  dependencies = [
958
- "crc32fast",
959
- "hashbrown 0.15.5",
960
- "indexmap",
961
973
  "memchr",
962
974
  ]
963
975
 
964
976
  [[package]]
965
977
  name = "object"
966
- version = "0.37.3"
978
+ version = "0.39.1"
967
979
  source = "registry+https://github.com/rust-lang/crates.io-index"
968
- checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe"
980
+ checksum = "2e5a6c098c7a3b6547378093f5cc30bc54fd361ce711e05293a5cc589562739b"
969
981
  dependencies = [
982
+ "crc32fast",
983
+ "hashbrown 0.17.1",
984
+ "indexmap",
970
985
  "memchr",
971
986
  ]
972
987
 
@@ -989,10 +1004,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
989
1004
  checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d"
990
1005
 
991
1006
  [[package]]
992
- name = "paste"
1007
+ name = "petgraph"
993
- version = "1.0.15"
1008
+ version = "0.6.5"
994
1009
  source = "registry+https://github.com/rust-lang/crates.io-index"
995
- checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
1010
+ checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db"
1011
+ dependencies = [
1012
+ "fixedbitset",
1013
+ "indexmap",
1014
+ ]
996
1015
 
997
1016
  [[package]]
998
1017
  name = "pin-project-lite"
@@ -1094,24 +1113,26 @@ dependencies = [
1094
1113
  ]
1095
1114
 
1096
1115
  [[package]]
1097
- name = "psm"
1116
+ name = "pulley-interpreter"
1098
- version = "0.1.31"
1117
+ version = "47.0.3"
1099
1118
  source = "registry+https://github.com/rust-lang/crates.io-index"
1100
- checksum = "645dbe486e346d9b5de3ef16ede18c26e6c70ad97418f4874b8b1889d6e761ea"
1119
+ checksum = "bc5c8c21ea032e4efbdf2d067dc45171779dbe0c8ecf20ef4a57efa7474f2b0a"
1101
1120
  dependencies = [
1102
- "ar_archive_writer",
1121
+ "cranelift-bitset",
1103
- "cc",
1122
+ "log",
1123
+ "pulley-macros",
1124
+ "wasmtime-internal-core",
1104
1125
  ]
1105
1126
 
1106
1127
  [[package]]
1107
- name = "pulley-interpreter"
1128
+ name = "pulley-macros"
1108
- version = "28.0.1"
1129
+ version = "47.0.3"
1109
1130
  source = "registry+https://github.com/rust-lang/crates.io-index"
1110
- checksum = "8324e531de91a3c25021a30fb7862d39cc516b61fbb801176acb5ff279ea887b"
1131
+ checksum = "9f10925455d5dde962e3604eade797ba5488644c8ee14a44191e2f2b58980574"
1111
1132
  dependencies = [
1112
- "cranelift-bitset",
1113
- "log",
1133
+ "proc-macro2",
1114
- "sptr",
1134
+ "quote",
1135
+ "syn 2.0.119",
1115
1136
  ]
1116
1137
 
1117
1138
  [[package]]
@@ -1162,15 +1183,16 @@ dependencies = [
1162
1183
 
1163
1184
  [[package]]
1164
1185
  name = "regalloc2"
1165
- version = "0.11.2"
1186
+ version = "0.15.2"
1166
1187
  source = "registry+https://github.com/rust-lang/crates.io-index"
1167
- checksum = "dc06e6b318142614e4a48bc725abbf08ff166694835c43c9dae5a9009704639a"
1188
+ checksum = "757712e8e61590d6d4f5d563483755538b5aa13467837a3b41cd9832509a7f85"
1168
1189
  dependencies = [
1169
1190
  "allocator-api2",
1170
1191
  "bumpalo",
1171
- "hashbrown 0.15.5",
1192
+ "hashbrown 0.17.1",
1172
1193
  "log",
1173
1194
  "rustc-hash",
1195
+ "serde",
1174
1196
  "smallvec",
1175
1197
  ]
1176
1198
 
@@ -1215,19 +1237,6 @@ version = "2.1.3"
1215
1237
  source = "registry+https://github.com/rust-lang/crates.io-index"
1216
1238
  checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
1217
1239
 
1218
- [[package]]
1219
- name = "rustix"
1220
- version = "0.38.44"
1221
- source = "registry+https://github.com/rust-lang/crates.io-index"
1222
- checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
1223
- dependencies = [
1224
- "bitflags",
1225
- "errno",
1226
- "libc",
1227
- "linux-raw-sys 0.4.15",
1228
- "windows-sys 0.59.0",
1229
- ]
1230
-
1231
1240
  [[package]]
1232
1241
  name = "rustix"
1233
1242
  version = "1.1.4"
@@ -1237,8 +1246,8 @@ dependencies = [
1237
1246
  "bitflags",
1238
1247
  "errno",
1239
1248
  "libc",
1240
- "linux-raw-sys 0.12.1",
1249
+ "linux-raw-sys",
1241
- "windows-sys 0.61.2",
1250
+ "windows-sys",
1242
1251
  ]
1243
1252
 
1244
1253
  [[package]]
@@ -1303,11 +1312,11 @@ dependencies = [
1303
1312
 
1304
1313
  [[package]]
1305
1314
  name = "serde_spanned"
1306
- version = "0.6.9"
1315
+ version = "1.1.1"
1307
1316
  source = "registry+https://github.com/rust-lang/crates.io-index"
1308
- checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
1317
+ checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
1309
1318
  dependencies = [
1310
- "serde",
1319
+ "serde_core",
1311
1320
  ]
1312
1321
 
1313
1322
  [[package]]
@@ -1342,12 +1351,6 @@ dependencies = [
1342
1351
  "serde",
1343
1352
  ]
1344
1353
 
1345
- [[package]]
1346
- name = "sptr"
1347
- version = "0.3.2"
1348
- source = "registry+https://github.com/rust-lang/crates.io-index"
1349
- checksum = "3b9b39299b249ad65f3b7e96443bad61c02ca5cd3589f46cb6d610a0fd6c0d6a"
1350
-
1351
1354
  [[package]]
1352
1355
  name = "stable_deref_trait"
1353
1356
  version = "1.2.1"
@@ -1411,9 +1414,9 @@ dependencies = [
1411
1414
 
1412
1415
  [[package]]
1413
1416
  name = "target-lexicon"
1414
- version = "0.12.16"
1417
+ version = "0.13.5"
1415
1418
  source = "registry+https://github.com/rust-lang/crates.io-index"
1416
- checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
1419
+ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
1417
1420
 
1418
1421
  [[package]]
1419
1422
  name = "tempfile"
@@ -1424,8 +1427,8 @@ dependencies = [
1424
1427
  "fastrand",
1425
1428
  "getrandom 0.4.3",
1426
1429
  "once_cell",
1427
- "rustix 1.1.4",
1430
+ "rustix",
1428
- "windows-sys 0.61.2",
1431
+ "windows-sys",
1429
1432
  ]
1430
1433
 
1431
1434
  [[package]]
@@ -1443,8 +1446,8 @@ version = "0.4.4"
1443
1446
  source = "registry+https://github.com/rust-lang/crates.io-index"
1444
1447
  checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874"
1445
1448
  dependencies = [
1446
- "rustix 1.1.4",
1449
+ "rustix",
1447
- "windows-sys 0.61.2",
1450
+ "windows-sys",
1448
1451
  ]
1449
1452
 
1450
1453
  [[package]]
@@ -1520,14 +1523,17 @@ dependencies = [
1520
1523
 
1521
1524
  [[package]]
1522
1525
  name = "toml"
1523
- version = "0.8.23"
1526
+ version = "0.9.12+spec-1.1.0"
1524
1527
  source = "registry+https://github.com/rust-lang/crates.io-index"
1525
- checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
1528
+ checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863"
1526
1529
  dependencies = [
1530
+ "indexmap",
1527
- "serde",
1531
+ "serde_core",
1528
1532
  "serde_spanned",
1533
+ "toml_datetime 0.7.5+spec-1.1.0",
1529
- "toml_datetime",
1534
+ "toml_parser",
1530
- "toml_edit",
1535
+ "toml_writer",
1536
+ "winnow 0.7.15",
1531
1537
  ]
1532
1538
 
1533
1539
  [[package]]
@@ -1535,8 +1541,14 @@ name = "toml_datetime"
1535
1541
  version = "0.6.11"
1536
1542
  source = "registry+https://github.com/rust-lang/crates.io-index"
1537
1543
  checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
1544
+
1545
+ [[package]]
1546
+ name = "toml_datetime"
1547
+ version = "0.7.5+spec-1.1.0"
1548
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1549
+ checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347"
1538
1550
  dependencies = [
1539
- "serde",
1551
+ "serde_core",
1540
1552
  ]
1541
1553
 
1542
1554
  [[package]]
@@ -1546,11 +1558,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
1546
1558
  checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
1547
1559
  dependencies = [
1548
1560
  "indexmap",
1549
- "serde",
1550
- "serde_spanned",
1551
- "toml_datetime",
1561
+ "toml_datetime 0.6.11",
1552
1562
  "toml_write",
1563
+ "winnow 0.7.15",
1564
+ ]
1565
+
1566
+ [[package]]
1567
+ name = "toml_parser"
1568
+ version = "1.1.3+spec-1.1.0"
1569
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1570
+ checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56"
1571
+ dependencies = [
1553
- "winnow",
1572
+ "winnow 1.0.4",
1554
1573
  ]
1555
1574
 
1556
1575
  [[package]]
@@ -1559,6 +1578,12 @@ version = "0.1.2"
1559
1578
  source = "registry+https://github.com/rust-lang/crates.io-index"
1560
1579
  checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
1561
1580
 
1581
+ [[package]]
1582
+ name = "toml_writer"
1583
+ version = "1.1.2+spec-1.1.0"
1584
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1585
+ checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2"
1586
+
1562
1587
  [[package]]
1563
1588
  name = "topiary-core"
1564
1589
  version = "0.7.3"
@@ -1668,12 +1693,6 @@ version = "0.2.2"
1668
1693
  source = "registry+https://github.com/rust-lang/crates.io-index"
1669
1694
  checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
1670
1695
 
1671
- [[package]]
1672
- name = "unicode-xid"
1673
- version = "0.2.6"
1674
- source = "registry+https://github.com/rust-lang/crates.io-index"
1675
- checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
1676
-
1677
1696
  [[package]]
1678
1697
  name = "utf8parse"
1679
1698
  version = "0.2.2"
@@ -1773,6 +1792,23 @@ dependencies = [
1773
1792
  "unicode-ident",
1774
1793
  ]
1775
1794
 
1795
+ [[package]]
1796
+ name = "wasm-compose"
1797
+ version = "0.252.0"
1798
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1799
+ checksum = "d59b710751a35d54732a63851cdfacbfb2266b7160ce174faf269d3f4e84e31b"
1800
+ dependencies = [
1801
+ "anyhow",
1802
+ "heck",
1803
+ "indexmap",
1804
+ "log",
1805
+ "petgraph",
1806
+ "smallvec",
1807
+ "wasm-encoder 0.252.0",
1808
+ "wasmparser 0.252.0",
1809
+ "wat",
1810
+ ]
1811
+
1776
1812
  [[package]]
1777
1813
  name = "wasm-encoder"
1778
1814
  version = "0.220.1"
@@ -1785,12 +1821,12 @@ dependencies = [
1785
1821
 
1786
1822
  [[package]]
1787
1823
  name = "wasm-encoder"
1788
- version = "0.221.3"
1824
+ version = "0.252.0"
1789
1825
  source = "registry+https://github.com/rust-lang/crates.io-index"
1790
- checksum = "dc8444fe4920de80a4fe5ab564fff2ae58b6b73166b89751f8c6c93509da32e5"
1826
+ checksum = "8185ae345fa5687c054626ff9a50e7089797a343d9904d1dc9820eb4c4d3196f"
1791
1827
  dependencies = [
1792
- "leb128",
1828
+ "leb128fmt",
1793
- "wasmparser 0.221.3",
1829
+ "wasmparser 0.252.0",
1794
1830
  ]
1795
1831
 
1796
1832
  [[package]]
@@ -1819,12 +1855,12 @@ dependencies = [
1819
1855
 
1820
1856
  [[package]]
1821
1857
  name = "wasmparser"
1822
- version = "0.221.3"
1858
+ version = "0.252.0"
1823
1859
  source = "registry+https://github.com/rust-lang/crates.io-index"
1824
- checksum = "d06bfa36ab3ac2be0dee563380147a5b81ba10dd8885d7fbbc9eb574be67d185"
1860
+ checksum = "d3eb099dcadcde5be9eef55e3a337128efd4e44b4c93122487e4d2e4e1c6627c"
1825
1861
  dependencies = [
1826
1862
  "bitflags",
1827
- "hashbrown 0.15.5",
1863
+ "hashbrown 0.17.1",
1828
1864
  "indexmap",
1829
1865
  "semver",
1830
1866
  "serde",
@@ -1843,223 +1879,236 @@ dependencies = [
1843
1879
 
1844
1880
  [[package]]
1845
1881
  name = "wasmprinter"
1846
- version = "0.221.3"
1882
+ version = "0.252.0"
1847
1883
  source = "registry+https://github.com/rust-lang/crates.io-index"
1848
- checksum = "7343c42a97f2926c7819ff81b64012092ae954c5d83ddd30c9fcdefd97d0b283"
1884
+ checksum = "7142797de29b35ab8dbf15c00f55fda75d409da4c423a8ab8bd6b667a785824b"
1849
1885
  dependencies = [
1850
1886
  "anyhow",
1851
1887
  "termcolor",
1852
- "wasmparser 0.221.3",
1888
+ "wasmparser 0.252.0",
1853
1889
  ]
1854
1890
 
1855
1891
  [[package]]
1856
1892
  name = "wasmtime"
1857
- version = "28.0.1"
1893
+ version = "47.0.3"
1858
1894
  source = "registry+https://github.com/rust-lang/crates.io-index"
1859
- checksum = "edd30973c65eceb0f37dfcc430d83abd5eb24015fdfcab6912f52949287e04f0"
1895
+ checksum = "c80ca6098e0d4d06886d91d7f2cc3cb6623eb583c4c0ab3c89cbfb6098c8586c"
1860
1896
  dependencies = [
1861
- "addr2line 0.24.2",
1897
+ "addr2line 0.26.1",
1862
- "anyhow",
1863
1898
  "async-trait",
1864
1899
  "bitflags",
1865
1900
  "bumpalo",
1866
1901
  "cc",
1867
1902
  "cfg-if",
1868
1903
  "encoding_rs",
1904
+ "futures",
1869
1905
  "fxprof-processed-profile",
1870
- "gimli 0.31.1",
1906
+ "gimli 0.33.0",
1871
- "hashbrown 0.14.5",
1872
- "indexmap",
1873
1907
  "ittapi",
1874
1908
  "libc",
1875
- "libm",
1876
1909
  "log",
1877
1910
  "mach2",
1878
1911
  "memfd",
1879
- "object 0.36.7",
1912
+ "object 0.39.1",
1880
1913
  "once_cell",
1881
- "paste",
1882
1914
  "postcard",
1883
- "psm",
1884
1915
  "pulley-interpreter",
1885
1916
  "rayon",
1886
- "rustix 0.38.44",
1917
+ "rustix",
1887
1918
  "semver",
1888
1919
  "serde",
1889
1920
  "serde_derive",
1890
1921
  "serde_json",
1891
1922
  "smallvec",
1892
- "sptr",
1893
1923
  "target-lexicon",
1924
+ "tempfile",
1925
+ "wasm-compose",
1894
- "wasm-encoder 0.221.3",
1926
+ "wasm-encoder 0.252.0",
1895
- "wasmparser 0.221.3",
1927
+ "wasmparser 0.252.0",
1896
- "wasmtime-asm-macros",
1897
- "wasmtime-cache",
1898
- "wasmtime-component-macro",
1899
- "wasmtime-component-util",
1900
- "wasmtime-cranelift",
1901
1928
  "wasmtime-environ",
1929
+ "wasmtime-internal-cache",
1930
+ "wasmtime-internal-component-macro",
1931
+ "wasmtime-internal-component-util",
1932
+ "wasmtime-internal-core",
1933
+ "wasmtime-internal-cranelift",
1902
- "wasmtime-fiber",
1934
+ "wasmtime-internal-fiber",
1903
- "wasmtime-jit-debug",
1935
+ "wasmtime-internal-jit-debug",
1904
- "wasmtime-jit-icache-coherence",
1936
+ "wasmtime-internal-jit-icache-coherence",
1905
- "wasmtime-slab",
1937
+ "wasmtime-internal-unwinder",
1906
- "wasmtime-versioned-export-macros",
1938
+ "wasmtime-internal-versioned-export-macros",
1907
- "wasmtime-winch",
1908
1939
  "wat",
1909
- "windows-sys 0.59.0",
1940
+ "windows-sys",
1941
+ "wit-parser",
1910
1942
  ]
1911
1943
 
1912
1944
  [[package]]
1913
- name = "wasmtime-asm-macros"
1945
+ name = "wasmtime-environ"
1914
- version = "28.0.1"
1946
+ version = "47.0.3"
1915
1947
  source = "registry+https://github.com/rust-lang/crates.io-index"
1916
- checksum = "c6c21dd30d1f3f93ee390ac1a7ec304ecdbfdab6390e1add41a1f52727b0992b"
1948
+ checksum = "134f9d136d29c76f6c1b4c9b468e97a2efc38c7d49fd188e39b64870b2fea701"
1917
1949
  dependencies = [
1950
+ "anyhow",
1951
+ "cpp_demangle",
1952
+ "cranelift-bforest",
1953
+ "cranelift-bitset",
1954
+ "cranelift-entity",
1955
+ "gimli 0.33.0",
1956
+ "hashbrown 0.17.1",
1957
+ "indexmap",
1918
- "cfg-if",
1958
+ "log",
1959
+ "object 0.39.1",
1960
+ "postcard",
1961
+ "rustc-demangle",
1962
+ "semver",
1963
+ "serde",
1964
+ "serde_derive",
1965
+ "sha2",
1966
+ "smallvec",
1967
+ "target-lexicon",
1968
+ "wasm-encoder 0.252.0",
1969
+ "wasmparser 0.252.0",
1970
+ "wasmprinter",
1971
+ "wasmtime-internal-component-util",
1972
+ "wasmtime-internal-core",
1919
1973
  ]
1920
1974
 
1921
1975
  [[package]]
1922
- name = "wasmtime-cache"
1976
+ name = "wasmtime-internal-cache"
1923
- version = "28.0.1"
1977
+ version = "47.0.3"
1924
1978
  source = "registry+https://github.com/rust-lang/crates.io-index"
1925
- checksum = "cabd563cfbfe75c5bf514081f624ca8d18391a37520d8c794abce702474e688c"
1979
+ checksum = "65db2eb1bfc5371a3b4107dbf539d0b93d05016fc29625b1648146b47db02071"
1926
1980
  dependencies = [
1927
- "anyhow",
1928
1981
  "base64",
1929
1982
  "directories-next",
1930
1983
  "log",
1931
1984
  "postcard",
1932
- "rustix 0.38.44",
1985
+ "rustix",
1933
1986
  "serde",
1934
1987
  "serde_derive",
1935
1988
  "sha2",
1936
1989
  "toml",
1990
+ "wasmtime-environ",
1937
- "windows-sys 0.59.0",
1991
+ "windows-sys",
1938
1992
  "zstd",
1939
1993
  ]
1940
1994
 
1941
1995
  [[package]]
1942
- name = "wasmtime-component-macro"
1996
+ name = "wasmtime-internal-component-macro"
1943
- version = "28.0.1"
1997
+ version = "47.0.3"
1944
1998
  source = "registry+https://github.com/rust-lang/crates.io-index"
1945
- checksum = "9f948a6ef3119d52c9f12936970de28ddf3f9bea04bc65571f4a92d2e5ab38f4"
1999
+ checksum = "2bf7b91fed3fc34781d57f9c6654ea2513bf0a99f7b0b0523c00de1e6efebe1c"
1946
2000
  dependencies = [
1947
2001
  "anyhow",
1948
2002
  "proc-macro2",
1949
2003
  "quote",
1950
2004
  "syn 2.0.119",
1951
- "wasmtime-component-util",
2005
+ "wasmtime-internal-component-util",
1952
- "wasmtime-wit-bindgen",
2006
+ "wasmtime-internal-wit-bindgen",
1953
2007
  "wit-parser",
1954
2008
  ]
1955
2009
 
1956
2010
  [[package]]
1957
- name = "wasmtime-component-util"
2011
+ name = "wasmtime-internal-component-util"
1958
- version = "28.0.1"
2012
+ version = "47.0.3"
1959
2013
  source = "registry+https://github.com/rust-lang/crates.io-index"
1960
- checksum = "b9275aa01ceaaa2fa6c0ecaa5267518d80b9d6e9ae7c7ea42f4c6e073e6a69ef"
2014
+ checksum = "fc8a678149885cae00289f806fbe74c7863084cf74cace0b8dc73602279400e1"
1961
2015
 
1962
2016
  [[package]]
1963
- name = "wasmtime-cranelift"
2017
+ name = "wasmtime-internal-core"
1964
- version = "28.0.1"
2018
+ version = "47.0.3"
1965
2019
  source = "registry+https://github.com/rust-lang/crates.io-index"
1966
- checksum = "0701a44a323267aae4499672dae422b266cee3135a23b640972ec8c0e10a44a2"
2020
+ checksum = "8a0092c4b9d070ac5e278b6d0db10f5e71214190f0347ca57502b4692f796321"
1967
2021
  dependencies = [
1968
2022
  "anyhow",
2023
+ "hashbrown 0.17.1",
2024
+ "libm",
2025
+ "serde",
2026
+ ]
2027
+
2028
+ [[package]]
2029
+ name = "wasmtime-internal-cranelift"
2030
+ version = "47.0.3"
2031
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2032
+ checksum = "6851ebc9e03cab23d9821d2b2505d380bdfe38f35ccec945247b05274d85c98b"
2033
+ dependencies = [
1969
2034
  "cfg-if",
1970
2035
  "cranelift-codegen",
1971
2036
  "cranelift-control",
1972
2037
  "cranelift-entity",
1973
2038
  "cranelift-frontend",
1974
2039
  "cranelift-native",
1975
- "gimli 0.31.1",
2040
+ "gimli 0.33.0",
1976
- "itertools 0.12.1",
2041
+ "itertools 0.14.0",
1977
2042
  "log",
1978
- "object 0.36.7",
2043
+ "object 0.39.1",
2044
+ "pulley-interpreter",
1979
2045
  "smallvec",
1980
2046
  "target-lexicon",
1981
- "thiserror 1.0.69",
2047
+ "thiserror 2.0.19",
1982
- "wasmparser 0.221.3",
2048
+ "wasmparser 0.252.0",
1983
2049
  "wasmtime-environ",
2050
+ "wasmtime-internal-core",
2051
+ "wasmtime-internal-unwinder",
1984
- "wasmtime-versioned-export-macros",
2052
+ "wasmtime-internal-versioned-export-macros",
1985
- ]
1986
-
1987
- [[package]]
1988
- name = "wasmtime-environ"
1989
- version = "28.0.1"
1990
- source = "registry+https://github.com/rust-lang/crates.io-index"
1991
- checksum = "264c968c1b81d340355ece2be0bc31a10f567ccb6ce08512c3b7d10e26f3cbe5"
1992
- dependencies = [
1993
- "anyhow",
1994
- "cpp_demangle",
1995
- "cranelift-bitset",
1996
- "cranelift-entity",
1997
- "gimli 0.31.1",
1998
- "indexmap",
1999
- "log",
2000
- "object 0.36.7",
2001
- "postcard",
2002
- "rustc-demangle",
2003
- "semver",
2004
- "serde",
2005
- "serde_derive",
2006
- "smallvec",
2007
- "target-lexicon",
2008
- "wasm-encoder 0.221.3",
2009
- "wasmparser 0.221.3",
2010
- "wasmprinter",
2011
- "wasmtime-component-util",
2012
2053
  ]
2013
2054
 
2014
2055
  [[package]]
2015
- name = "wasmtime-fiber"
2056
+ name = "wasmtime-internal-fiber"
2016
- version = "28.0.1"
2057
+ version = "47.0.3"
2017
2058
  source = "registry+https://github.com/rust-lang/crates.io-index"
2018
- checksum = "78505221fd5bd7b07b4e1fa2804edea49dc231e626ad6861adc8f531812973e6"
2059
+ checksum = "9b26da6d5f60d4c438da70bba3553fe810a840533a64156be287dca2081c6991"
2019
2060
  dependencies = [
2020
- "anyhow",
2021
2061
  "cc",
2022
2062
  "cfg-if",
2063
+ "libc",
2023
- "rustix 0.38.44",
2064
+ "rustix",
2024
- "wasmtime-asm-macros",
2065
+ "wasmtime-environ",
2025
- "wasmtime-versioned-export-macros",
2066
+ "wasmtime-internal-versioned-export-macros",
2026
- "windows-sys 0.59.0",
2067
+ "windows-sys",
2027
2068
  ]
2028
2069
 
2029
2070
  [[package]]
2030
- name = "wasmtime-jit-debug"
2071
+ name = "wasmtime-internal-jit-debug"
2031
- version = "28.0.1"
2072
+ version = "47.0.3"
2032
2073
  source = "registry+https://github.com/rust-lang/crates.io-index"
2033
- checksum = "0cec0a8e5620ae71bfcaaec78e3076be5b6ebf869f4e6191925d73242224a915"
2074
+ checksum = "ed621ba25d7bf78b7edd7b4749abbb27c2e5cdba836c9504a424ee74b6c23149"
2034
2075
  dependencies = [
2076
+ "cc",
2035
- "object 0.36.7",
2077
+ "object 0.39.1",
2036
- "rustix 0.38.44",
2078
+ "rustix",
2037
- "wasmtime-versioned-export-macros",
2079
+ "wasmtime-internal-versioned-export-macros",
2038
2080
  ]
2039
2081
 
2040
2082
  [[package]]
2041
- name = "wasmtime-jit-icache-coherence"
2083
+ name = "wasmtime-internal-jit-icache-coherence"
2042
- version = "28.0.1"
2084
+ version = "47.0.3"
2043
2085
  source = "registry+https://github.com/rust-lang/crates.io-index"
2044
- checksum = "9bedb677ca1b549d98f95e9e1f9251b460090d99a2c196a0614228c064bf2e59"
2086
+ checksum = "5684ba160951baad06a725696f3c590e2fb0e8067c2aebee27bf7f9259058e85"
2045
2087
  dependencies = [
2046
- "anyhow",
2047
2088
  "cfg-if",
2048
2089
  "libc",
2090
+ "wasmtime-internal-core",
2049
- "windows-sys 0.59.0",
2091
+ "windows-sys",
2050
2092
  ]
2051
2093
 
2052
2094
  [[package]]
2053
- name = "wasmtime-slab"
2095
+ name = "wasmtime-internal-unwinder"
2054
- version = "28.0.1"
2096
+ version = "47.0.3"
2055
2097
  source = "registry+https://github.com/rust-lang/crates.io-index"
2056
- checksum = "564905638c132c275d365c1fa074f0b499790568f43148d29de84ccecfb5cb31"
2098
+ checksum = "112eead527bffa8ff0646a11fb4339a9d52ddd1da2b9a6fce4aff84c815dd94f"
2099
+ dependencies = [
2100
+ "cfg-if",
2101
+ "cranelift-codegen",
2102
+ "log",
2103
+ "object 0.39.1",
2104
+ "wasmtime-environ",
2105
+ ]
2057
2106
 
2058
2107
  [[package]]
2059
- name = "wasmtime-versioned-export-macros"
2108
+ name = "wasmtime-internal-versioned-export-macros"
2060
- version = "28.0.1"
2109
+ version = "47.0.3"
2061
2110
  source = "registry+https://github.com/rust-lang/crates.io-index"
2062
- checksum = "1e91092e6cf77390eeccee273846a9327f3e8f91c3c6280f60f37809f0e62d29"
2111
+ checksum = "153592e0bed824fc13c6696203fa1bd7bd20eb355316475e39a55f081ef80eca"
2063
2112
  dependencies = [
2064
2113
  "proc-macro2",
2065
2114
  "quote",
@@ -2067,29 +2116,13 @@ dependencies = [
2067
2116
  ]
2068
2117
 
2069
2118
  [[package]]
2070
- name = "wasmtime-winch"
2119
+ name = "wasmtime-internal-wit-bindgen"
2071
- version = "28.0.1"
2120
+ version = "47.0.3"
2072
- source = "registry+https://github.com/rust-lang/crates.io-index"
2073
- checksum = "b111d909dc604c741bd8ac2f4af373eaa5c68c34b5717271bcb687688212cef8"
2074
- dependencies = [
2075
- "anyhow",
2076
- "cranelift-codegen",
2077
- "gimli 0.31.1",
2078
- "object 0.36.7",
2079
- "target-lexicon",
2080
- "wasmparser 0.221.3",
2081
- "wasmtime-cranelift",
2082
- "wasmtime-environ",
2083
- "winch-codegen",
2084
- ]
2085
-
2086
- [[package]]
2087
- name = "wasmtime-wit-bindgen"
2088
- version = "28.0.1"
2089
2121
  source = "registry+https://github.com/rust-lang/crates.io-index"
2090
- checksum = "5f38f7a5eb2f06f53fe943e7fb8bf4197f7cf279f1bc52c0ce56e9d3ffd750a4"
2122
+ checksum = "c456ad6e81e0f46abfeca43687d18ea15e289c395b262ea6e0023e2682e088be"
2091
2123
  dependencies = [
2092
2124
  "anyhow",
2125
+ "bitflags",
2093
2126
  "heck",
2094
2127
  "indexmap",
2095
2128
  "wit-parser",
@@ -2149,7 +2182,7 @@ version = "0.1.11"
2149
2182
  source = "registry+https://github.com/rust-lang/crates.io-index"
2150
2183
  checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
2151
2184
  dependencies = [
2152
- "windows-sys 0.61.2",
2185
+ "windows-sys",
2153
2186
  ]
2154
2187
 
2155
2188
  [[package]]
@@ -2158,38 +2191,12 @@ version = "0.4.0"
2158
2191
  source = "registry+https://github.com/rust-lang/crates.io-index"
2159
2192
  checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
2160
2193
 
2161
- [[package]]
2162
- name = "winch-codegen"
2163
- version = "28.0.1"
2164
- source = "registry+https://github.com/rust-lang/crates.io-index"
2165
- checksum = "6232f40a795be2ce10fc761ed3b403825126a60d12491ac556ea104a932fd18a"
2166
- dependencies = [
2167
- "anyhow",
2168
- "cranelift-codegen",
2169
- "gimli 0.31.1",
2170
- "regalloc2",
2171
- "smallvec",
2172
- "target-lexicon",
2173
- "wasmparser 0.221.3",
2174
- "wasmtime-cranelift",
2175
- "wasmtime-environ",
2176
- ]
2177
-
2178
2194
  [[package]]
2179
2195
  name = "windows-link"
2180
2196
  version = "0.2.1"
2181
2197
  source = "registry+https://github.com/rust-lang/crates.io-index"
2182
2198
  checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
2183
2199
 
2184
- [[package]]
2185
- name = "windows-sys"
2186
- version = "0.59.0"
2187
- source = "registry+https://github.com/rust-lang/crates.io-index"
2188
- checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
2189
- dependencies = [
2190
- "windows-targets",
2191
- ]
2192
-
2193
2200
  [[package]]
2194
2201
  name = "windows-sys"
2195
2202
  version = "0.61.2"
@@ -2199,70 +2206,6 @@ dependencies = [
2199
2206
  "windows-link",
2200
2207
  ]
2201
2208
 
2202
- [[package]]
2203
- name = "windows-targets"
2204
- version = "0.52.6"
2205
- source = "registry+https://github.com/rust-lang/crates.io-index"
2206
- checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
2207
- dependencies = [
2208
- "windows_aarch64_gnullvm",
2209
- "windows_aarch64_msvc",
2210
- "windows_i686_gnu",
2211
- "windows_i686_gnullvm",
2212
- "windows_i686_msvc",
2213
- "windows_x86_64_gnu",
2214
- "windows_x86_64_gnullvm",
2215
- "windows_x86_64_msvc",
2216
- ]
2217
-
2218
- [[package]]
2219
- name = "windows_aarch64_gnullvm"
2220
- version = "0.52.6"
2221
- source = "registry+https://github.com/rust-lang/crates.io-index"
2222
- checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
2223
-
2224
- [[package]]
2225
- name = "windows_aarch64_msvc"
2226
- version = "0.52.6"
2227
- source = "registry+https://github.com/rust-lang/crates.io-index"
2228
- checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
2229
-
2230
- [[package]]
2231
- name = "windows_i686_gnu"
2232
- version = "0.52.6"
2233
- source = "registry+https://github.com/rust-lang/crates.io-index"
2234
- checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
2235
-
2236
- [[package]]
2237
- name = "windows_i686_gnullvm"
2238
- version = "0.52.6"
2239
- source = "registry+https://github.com/rust-lang/crates.io-index"
2240
- checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
2241
-
2242
- [[package]]
2243
- name = "windows_i686_msvc"
2244
- version = "0.52.6"
2245
- source = "registry+https://github.com/rust-lang/crates.io-index"
2246
- checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
2247
-
2248
- [[package]]
2249
- name = "windows_x86_64_gnu"
2250
- version = "0.52.6"
2251
- source = "registry+https://github.com/rust-lang/crates.io-index"
2252
- checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
2253
-
2254
- [[package]]
2255
- name = "windows_x86_64_gnullvm"
2256
- version = "0.52.6"
2257
- source = "registry+https://github.com/rust-lang/crates.io-index"
2258
- checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
2259
-
2260
- [[package]]
2261
- name = "windows_x86_64_msvc"
2262
- version = "0.52.6"
2263
- source = "registry+https://github.com/rust-lang/crates.io-index"
2264
- checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
2265
-
2266
2209
  [[package]]
2267
2210
  name = "winnow"
2268
2211
  version = "0.7.15"
@@ -2272,13 +2215,20 @@ dependencies = [
2272
2215
  "memchr",
2273
2216
  ]
2274
2217
 
2218
+ [[package]]
2219
+ name = "winnow"
2220
+ version = "1.0.4"
2221
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2222
+ checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81"
2223
+
2275
2224
  [[package]]
2276
2225
  name = "wit-parser"
2277
- version = "0.221.3"
2226
+ version = "0.252.0"
2278
2227
  source = "registry+https://github.com/rust-lang/crates.io-index"
2279
- checksum = "896112579ed56b4a538b07a3d16e562d101ff6265c46b515ce0c701eef16b2ac"
2228
+ checksum = "4266bea110371c620ccf3201c5023676046bc4556e5c7cfb5d500bda5ebc162d"
2280
2229
  dependencies = [
2281
2230
  "anyhow",
2231
+ "hashbrown 0.17.1",
2282
2232
  "id-arena",
2283
2233
  "indexmap",
2284
2234
  "log",
@@ -2286,8 +2236,8 @@ dependencies = [
2286
2236
  "serde",
2287
2237
  "serde_derive",
2288
2238
  "serde_json",
2289
- "unicode-xid",
2239
+ "unicode-ident",
2290
- "wasmparser 0.221.3",
2240
+ "wasmparser 0.252.0",
2291
2241
  ]
2292
2242
 
2293
2243
  [[package]]
docs/superpowers/plans/2026-07-25-wasm-gc-migration.md CHANGED
@@ -36,31 +36,31 @@
36
36
  - Consumes: nothing (bottom of this plan's dependency chain).
37
37
  - Produces: every `wasmtime::Engine`/`Config` used in tests has the GC proposal enabled; a new type-section emitter that, given the checker's monomorphized `Source`, emits one wasm-gc `struct` per concrete class, one abstract-supertype-plus-per-variant-subtype set per enum (Bool included, built in exactly like the existing hardcoded `True`/`False` `EnumVariantInfo` registration), and one `array<i8>` type for `Str` — **wired up but not yet consumed by any real codegen path**, so all EXISTING tests (still running through the untouched bump-allocator codegen) stay green. This task is purely additive.
38
38
 
39
- - [ ] **Step 1: Confirm wasmtime's GC `Config` actually works empirically, first**
39
+ - [x] **Step 1: Confirm wasmtime's GC `Config` actually works empirically, first**
40
40
 
41
41
  Before writing any type-emitter code, write a throwaway test (in `codegen_tests.rs`, can be deleted/replaced by Task 2) that: builds a `wasmtime::Config`, calls `.wasm_gc(true)` (and `.wasm_function_references(true)` — check by reading `wasmtime-28.0.1`'s `config.rs`, vendored at `~/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/wasmtime-28.0.1/src/config.rs`, whether GC has an undeclared dependency on function-references that isn't auto-enabled), constructs an `Engine` from it, and hand-encodes (via `wasm_encoder` directly, no plum involved) the smallest possible module with one GC struct type, one function that does `struct.new_default` + returns it typed as `(ref null $T)`, and confirms it validates AND instantiates+runs under that `Engine`. If this fails or exhibits a real engine limitation, that finding drives every subsequent step — report it before proceeding, don't route around it silently.
42
42
 
43
- - [ ] **Step 2: Update the test harness's `Engine`/`Config` construction**
43
+ - [x] **Step 2: Update the test harness's `Engine`/`Config` construction**
44
44
 
45
45
  Read the current `Engine::default()` (or equivalent) call sites in `codegen_tests.rs` and `examples_test.rs` (search `wasmtime::Engine`) and replace with the `Config` confirmed working in Step 1. Every test in both files must still pass unchanged (they're still exercising the untouched bump-allocator codegen at this point) — this step is purely "can a GC-enabled engine still run today's non-GC output," which it should, since enabling a feature doesn't retroactively require using it.
46
46
 
47
- - [ ] **Step 3: Read the current type-registration surface before extending it**
47
+ - [x] **Step 3: Read the current type-registration surface before extending it**
48
48
 
49
49
  Read `plum-checker`'s `ClassEnv`/`EnumVariants`/`EnumVariantInfo` definitions (`plum-checker/src/lib.rs`) and `plum-wasm-codegen`'s `CompileCtx`/`LocalCtx` (`plum-wasm-codegen/src/lib.rs`) exact current fields — this plan's earlier research read them as of the enum-discriminant-values plan's completion, but re-verify, since intervening work may have touched them.
50
50
 
51
- - [ ] **Step 4: Add a GC type registry, populated but unconsumed**
51
+ - [x] **Step 4: Add a GC type registry, populated but unconsumed**
52
52
 
53
53
  Add a new struct (e.g. `GcTypeRegistry`) that, given the monomorphized `ast::Source` plus the existing `ClassEnv`/`EnumVariants` tables, assigns and records a wasm type-section index for: every concrete class name, every enum's abstract supertype, every enum variant's concrete subtype, and one shared `array<i8>` index for `Str`. Emit these into a real `TypeSection` via `CoreTypeEncoder::struct_`/`array`/`subtype` (structs need `SubType { is_final: false, supertype_idx: None, ... }` for a class since it needs no subtyping, but an enum's supertype needs `is_final: false` so variants CAN subtype it, and each variant subtype needs `SubType { supertype_idx: Some(super_idx), ... }`) — group the whole enum's supertype+variants in one `rec` group (`CoreTypeEncoder::rec`) since wasm-gc subtyping requires supertype and subtype to be declared in the same recursive group or supertype declared earlier in an earlier group; read `wasm-encoder`'s `rec`/`subtype` doc comments (`~/.cargo/registry/.../wasm-encoder-0.220.1/src/core/types.rs`) to get the ordering constraint exactly right. Thread the resulting index tables through `CompileCtx` as new fields (e.g. `class_type_idx: HashMap<String, u32>`, `enum_super_type_idx: HashMap<String, u32>`, `variant_type_idx: HashMap<String, u32>`, `str_type_idx: u32`), populated in `compileSource` but not read by anything else yet.
54
54
 
55
- - [ ] **Step 5: Add a corresponding Rust test proving the type section is well-formed**
55
+ - [x] **Step 5: Add a corresponding Rust test proving the type section is well-formed**
56
56
 
57
57
  Add a test that compiles a small source (one class, one enum with a payload variant and a payload-free variant) through `compileSource`, and asserts the resulting bytes validate via `wasmparser::validate` under the GC-enabled config from Step 1 — this proves the new type section alone (still unused by real codegen) is well-formed, before Task 2 starts depending on it.
58
58
 
59
- - [ ] **Step 6: Run full verification**
59
+ - [x] **Step 6: Run full verification**
60
60
 
61
61
  `cargo build --workspace --all-targets` then `cargo test --workspace`. Expected: 100% pass, identical results to before this task — nothing behavioral changed yet.
62
62
 
63
- - [ ] **Step 7: Commit**
63
+ - [x] **Step 7: Commit**
64
64
 
65
65
  ```bash
66
66
  git add plum-wasm-codegen/src/lib.rs plum-wasm-codegen/tests/codegen_tests.rs plum-wasm-codegen/tests/examples_test.rs plum-wasm-codegen/Cargo.toml
@@ -81,105 +81,105 @@ This task is large enough that its steps are grouped by concern. Within each con
81
81
 
82
82
  #### 2a. Bool/enum singletons and control-flow conversion (do this first — nearly everything else depends on being able to produce/consume a Bool)
83
83
 
84
- - [ ] **Step 1: Read `compileSource`'s current initialization sequence, `compileStmt`'s `If`/`While` arms, `boolean_operator`/comparison codegen, and `assert` codegen**
84
+ - [x] **Step 1: Read `compileSource`'s current initialization sequence, `compileStmt`'s `If`/`While` arms, `boolean_operator`/comparison codegen, and `assert` codegen**
85
85
 
86
86
  These are all in `plum-wasm-codegen/src/lib.rs` — find them by searching `ast::Stmt::If`, `ast::Stmt::While`, `ast::Expr::Bool(`, `ast::Expr::Compare(`, `ast::Stmt::Assert`.
87
87
 
88
- - [ ] **Step 2: Emit one global per payload-free variant across the whole program (including `True`/`False`)**
88
+ - [x] **Step 2: Emit one global per payload-free variant across the whole program (including `True`/`False`)**
89
89
 
90
90
  In `compileSource`, after the type registry (Task 1) is built, walk every enum's variants (plus the built-in `Bool`) and for each payload-free one, emit a `global` (mutable: false is ideal but wasm-gc struct globals may need an initializer expression capable of `struct.new` — read `wasm_encoder`'s `ConstExpr`/`GlobalSection` to confirm whether a GC `struct.new` is legal inside a global's constant-initializer expression under this wasm-gc revision; if not, fall back to a `start` function that runs once and stores each singleton into a mutable global before `main`/any export can run). Record `variant_name -> global_index` in a new `CompileCtx` field.
91
91
 
92
- - [ ] **Step 3: Rewrite conditional control flow to convert Bool refs to i32 via `ref.test`**
92
+ - [x] **Step 3: Rewrite conditional control flow to convert Bool refs to i32 via `ref.test`**
93
93
 
94
94
  Wherever a `Bool`-typed value currently feeds `Instruction::If`/`BrIf` unchanged (it's already an i32 today), insert `Instruction::RefTestNonNull(HeapType::Concrete(true_variant_type_idx))` immediately before it. This applies to at least: `if`/`else if` conditions, `while` conditions, `assert`, and short-circuit `&&`/`||` (which currently, per existing codegen, likely already lower to nested `If` blocks for short-circuiting — read the current `boolean_operator` codegen to confirm the exact shape before changing it).
95
95
 
96
- - [ ] **Step 4: Rewrite comparison operators to produce a Bool ref from a native i32 predicate**
96
+ - [x] **Step 4: Rewrite comparison operators to produce a Bool ref from a native i32 predicate**
97
97
 
98
98
  Currently a comparison (`==`, `<`, etc.) directly leaves an i32 on the stack as the Bool result. Change it to: compute the native i32 predicate as today, then `Instruction::If(BlockType::Result(bool_ref_type))` / push `GlobalGet(true_singleton)` / `Else` / `GlobalGet(false_singleton)` / `End`, so the result is a proper `Bool` ref matching every other Bool-producing expression.
99
99
 
100
- - [ ] **Step 5: Update every OTHER payload-free bare-variant construction site to use its singleton global instead of allocating**
100
+ - [x] **Step 5: Update every OTHER payload-free bare-variant construction site to use its singleton global instead of allocating**
101
101
 
102
102
  Search for the current handling of `ast::Expr::TypeName(n)` where `info.field_types.is_empty()` (bare tag construction) — this currently does `Instruction::I32Const(info.tag)`; change it to `Instruction::GlobalGet(singleton_idx)`.
103
103
 
104
- - [ ] **Step 6: Build and run affected tests only, iterating**
104
+ - [x] **Step 6: Build and run affected tests only, iterating**
105
105
 
106
106
  `cargo build -p plum-wasm-codegen` first (fast compile-only check — expect many errors from downstream code still assuming i32 Bool/tags, which is fine, that's 2b/2c/2d's job). Do not attempt `cargo test` yet — the crate won't fully compile again until struct/enum construction (2b) and match dispatch (2d) also land, since they're mutually dependent on the same representation.
107
107
 
108
108
  #### 2b. Struct/class construction and field access
109
109
 
110
- - [ ] **Step 1: Read the current `ast::Expr::ClassCall` construction arm and the `Attribute`/`Field` read/write codegen**
110
+ - [x] **Step 1: Read the current `ast::Expr::ClassCall` construction arm and the `Attribute`/`Field` read/write codegen**
111
111
 
112
112
  Search `ast::Expr::ClassCall`, `ast::AttrKind::Field`, `ast::AssignTarget::Field`.
113
113
 
114
- - [ ] **Step 2: Rewrite construction to `struct.new`**
114
+ - [x] **Step 2: Rewrite construction to `struct.new`**
115
115
 
116
116
  For each `ClassCall`, look up the class's declared field order (not `call.fields`'s written order — construction must push values in DECLARATION order for `struct.new`). For each declared field, find the matching `call.fields` entry by name and `compileExpr` its value onto the stack; once every field's value is pushed, emit `Instruction::StructNew(class_type_idx)`. The `classcall_scratch` local is very likely no longer needed for this case (no bump-pointer bookkeeping requires holding an intermediate address — verify by reading whether anything else in the current code relies on that scratch slot for `ClassCall` specifically before deleting its allocation for this case).
117
117
 
118
- - [ ] **Step 3: Rewrite field reads to `struct.get`**
118
+ - [x] **Step 3: Rewrite field reads to `struct.get`**
119
119
 
120
120
  Replace the current class-field-read arm's `emitLoad` at a byte offset with `compileExpr` (pushes the object ref) followed by `Instruction::StructGet { struct_type_index: class_type_idx, field_index }`.
121
121
 
122
- - [ ] **Step 4: Rewrite field writes (`self.x = ...`, chained `a.b.c = ...`) to `struct.set`**
122
+ - [x] **Step 4: Rewrite field writes (`self.x = ...`, chained `a.b.c = ...`) to `struct.set`**
123
123
 
124
124
  Same shape: compile the object ref, compile the new value, `Instruction::StructSet { struct_type_index, field_index }`. `StructSet`'s stack order — verify from `wasm-encoder`'s encode impl or the wasm-gc spec whether it expects `(struct_ref, value)` or `(value, struct_ref)` push order; get this from the actual instruction encoding, not assumption, since getting it backwards produces a type-valid-looking-in-isolation-but-wrong-at-runtime bug that validation may not always catch depending on other stack contents.
125
125
 
126
- - [ ] **Step 5: Rewrite discriminant-enum field access (`self.n` on an `enum Foo(n: Int)`)** analogously, through `ctx.enum_params`, using `StructGet`/`StructSet` against the enum's own struct layout with the `field_idx + 1` tag-slot offset convention preserved conceptually (though the "+1 for tag" byte-offset trick is a linear-memory artifact — under wasm-gc each variant subtype simply has its own explicit field list starting at field index 0 for the first real field, no tag slot needed at all, since the tag IS the type itself now, discoverable via `ref.test`, not a stored value read at a fixed offset).
126
+ - [x] **Step 5: Rewrite discriminant-enum field access (`self.n` on an `enum Foo(n: Int)`)** analogously, through `ctx.enum_params`, using `StructGet`/`StructSet` against the enum's own struct layout with the `field_idx + 1` tag-slot offset convention preserved conceptually (though the "+1 for tag" byte-offset trick is a linear-memory artifact — under wasm-gc each variant subtype simply has its own explicit field list starting at field index 0 for the first real field, no tag slot needed at all, since the tag IS the type itself now, discoverable via `ref.test`, not a stored value read at a fixed offset).
127
127
 
128
128
  #### 2c. String migration to `array<i8>`
129
129
 
130
- - [ ] **Step 1: Read the current string literal encoding, `registerStringConcatHelper`, `registerIntToStringHelper`, and string interpolation codegen**
130
+ - [x] **Step 1: Read the current string literal encoding, `registerStringConcatHelper`, `registerIntToStringHelper`, and string interpolation codegen**
131
131
 
132
132
  Search `compile_static_string` (or equivalent), `registerStringConcatHelper`, `registerIntToStringHelper`.
133
133
 
134
- - [ ] **Step 2: Static string literals become passive-data-segment `array.new_data`**
134
+ - [x] **Step 2: Static string literals become passive-data-segment `array.new_data`**
135
135
 
136
136
  Each string literal's bytes go into one passive `DataSection::passive(bytes)` segment (no active memory offset — confirm `wasm_encoder`'s `DataSection::passive` doesn't require a preceding memory section to exist at all, per this plan's Decision 4). At the literal's use site, emit `Instruction::ArrayNewData { array_type_index: str_type_idx, array_data_index }`.
137
137
 
138
- - [ ] **Step 3: Rewrite `registerStringConcatHelper` against `array.new`/`array.copy`**
138
+ - [x] **Step 3: Rewrite `registerStringConcatHelper` against `array.new`/`array.copy`**
139
139
 
140
140
  Read the exact current helper body first. The new version needs the combined length (`array.len` on both inputs, or track lengths already known from a length-prefix design if you keep one — decide whether `Str`'s `array<i8>` needs an explicit stored length or relies on `array.len`, and note that GC arrays DO track their own length natively, so a manual length prefix is no longer needed at all, simplifying this over the current length-prefixed-blob design), allocates a new array of that combined size (`array.new_default` sized dynamically — check whether `array.new_default` accepts a non-constant size operand; it does, per the wasm-gc spec, size is a runtime i32 operand), then two `Instruction::ArrayCopy { array_type_index_dst, array_type_index_src }` calls to copy each source's bytes into the destination at the right offset.
141
141
 
142
- - [ ] **Step 4: Rewrite `registerIntToStringHelper` similarly** — read its current byte-writing loop and adapt each byte store to `array.set` (or build into a fixed-size scratch and `array.new_data`-style copy, whichever the current algorithm's shape most directly maps to).
142
+ - [x] **Step 4: Rewrite `registerIntToStringHelper` similarly** — read its current byte-writing loop and adapt each byte store to `array.set` (or build into a fixed-size scratch and `array.new_data`-style copy, whichever the current algorithm's shape most directly maps to).
143
143
 
144
- - [ ] **Step 5: Update string interpolation codegen** to call the rewritten helpers; verify `Str`-typed class/enum fields (now `ref null $StrArray` instead of i32) round-trip correctly through 2b/2d's `struct.get`/`struct.set` and `ref.test`/variant-subtype field lists.
144
+ - [x] **Step 5: Update string interpolation codegen** to call the rewritten helpers; verify `Str`-typed class/enum fields (now `ref null $StrArray` instead of i32) round-trip correctly through 2b/2d's `struct.get`/`struct.set` and `ref.test`/variant-subtype field lists.
145
145
 
146
146
  #### 2d. Match dispatch via `ref.test`/`br_on_cast`
147
147
 
148
- - [ ] **Step 1: Read `compileMatch`, `compileMatchArmsMulti`, `compileCasePositions`, `compileVariantEqArm` (or its current name — it may have been renamed during the camelCase pass), `compileVariantConstructorArm`, and `compileFieldPatterns` in full before changing any of them** — this is the most intricate part of the existing codegen (recursive multi-subject, multi-position, nested-constructor-pattern dispatch) and the part most likely to have subtle behavioral requirements not obvious from a partial read.
148
+ - [x] **Step 1: Read `compileMatch`, `compileMatchArmsMulti`, `compileCasePositions`, `compileVariantEqArm` (or its current name — it may have been renamed during the camelCase pass), `compileVariantConstructorArm`, and `compileFieldPatterns` in full before changing any of them** — this is the most intricate part of the existing codegen (recursive multi-subject, multi-position, nested-constructor-pattern dispatch) and the part most likely to have subtle behavioral requirements not obvious from a partial read.
149
149
 
150
- - [ ] **Step 2: Replace the `HEAP_BASE` range-check + tag-load-and-compare pattern with `ref.test`**
150
+ - [x] **Step 2: Replace the `HEAP_BASE` range-check + tag-load-and-compare pattern with `ref.test`**
151
151
 
152
152
  Every place that currently does "range-check against `HEAP_BASE`, then conditionally load+compare a tag" (both the top-level bare-Name/Class-pattern arms and the nested `compileFieldPatterns` equivalents) becomes a single `Instruction::RefTestNonNull(HeapType::Concrete(variant_type_idx))` (or `RefTestNullable` if the value can legitimately be null in context — decide per call site) directly on the subject ref, producing the same "did this match" i32 the existing `If`/`Else` structure around it already expects — the surrounding control-flow shape (fall through to `compileMatchArmsMulti(rest, ...)` on mismatch) doesn't need to change, only how the boolean test itself is computed.
153
153
 
154
- - [ ] **Step 3: Replace payload-field extraction with `ref.cast` + `struct.get`**
154
+ - [x] **Step 3: Replace payload-field extraction with `ref.cast` + `struct.get`**
155
155
 
156
156
  Where a matched constructor pattern currently loads a field via `emitLoad(field_vt, offset)` from the raw pointer, first `Instruction::RefCastNonNull(HeapType::Concrete(variant_type_idx))` (narrowing the statically-typed-as-supertype subject to its concrete variant subtype — the match arm's binding scope is exactly where this narrowing is valid) then `Instruction::StructGet { struct_type_index: variant_type_idx, field_index }`.
157
157
 
158
- - [ ] **Step 4: Consider (but don't require) using `br_on_cast` for the top-level dispatch chain** — the spec's Components section suggests `ref.test`/`br_on_cast` as the replacement mechanism; a straightforward `ref.test` + `if/else` chain (mirroring today's control-flow shape exactly, per Step 2) is very likely simpler to get right than restructuring into `br_on_cast`'s branch-table-like shape, and is equally correct. Only reach for `br_on_cast` if the `if/else` chain form turns out to hit a real wasm-gc validation or structural limitation — don't restructure working control flow for its own sake.
158
+ - [x] **Step 4: Consider (but don't require) using `br_on_cast` for the top-level dispatch chain** — the spec's Components section suggests `ref.test`/`br_on_cast` as the replacement mechanism; a straightforward `ref.test` + `if/else` chain (mirroring today's control-flow shape exactly, per Step 2) is very likely simpler to get right than restructuring into `br_on_cast`'s branch-table-like shape, and is equally correct. Only reach for `br_on_cast` if the `if/else` chain form turns out to hit a real wasm-gc validation or structural limitation — don't restructure working control flow for its own sake.
159
159
 
160
160
  #### 2e. Closures
161
161
 
162
- - [ ] **Step 1: Read `compileClosureLiteral`, `compileClosureCall`, `compileClosureBody`, and the `Collector`'s closure-discovery pass in full.**
162
+ - [x] **Step 1: Read `compileClosureLiteral`, `compileClosureCall`, `compileClosureBody`, and the `Collector`'s closure-discovery pass in full.**
163
163
 
164
- - [ ] **Step 2: Give every closure literal its own env `struct` type**, one field per captured variable in `free_vars` order, field types matching each captured variable's own type (which may itself now be a `ref` type post-2b/2c). Register these in the type registry alongside classes/enums.
164
+ - [x] **Step 2: Give every closure literal its own env `struct` type**, one field per captured variable in `free_vars` order, field types matching each captured variable's own type (which may itself now be a `ref` type post-2b/2c). Register these in the type registry alongside classes/enums.
165
165
 
166
- - [ ] **Step 3: Rewrite `compileClosureLiteral`**: build the env via `struct.new` (pushing each captured local's current value, per Decision 3 — snapshot-by-value semantics are unchanged, only the allocation mechanism changes), then build the 2-field closure struct `{i32 table_index, anyref env}` via `struct.new`, casting/widening the concrete env ref to `anyref` for storage (a supertype widening — confirm whether `wasm-encoder`/wasm-gc requires an explicit instruction for this or whether it's implicit at the type-checking level when a subtype value is used where a supertype/`anyref` is expected; if implicit, no extra instruction is needed).
166
+ - [x] **Step 3: Rewrite `compileClosureLiteral`**: build the env via `struct.new` (pushing each captured local's current value, per Decision 3 — snapshot-by-value semantics are unchanged, only the allocation mechanism changes), then build the 2-field closure struct `{i32 table_index, anyref env}` via `struct.new`, casting/widening the concrete env ref to `anyref` for storage (a supertype widening — confirm whether `wasm-encoder`/wasm-gc requires an explicit instruction for this or whether it's implicit at the type-checking level when a subtype value is used where a supertype/`anyref` is expected; if implicit, no extra instruction is needed).
167
167
 
168
- - [ ] **Step 4: Rewrite `compileClosureBody`**: its first instructions must `RefCastNonNull` (or the nullable variant, matching the declared param type) the incoming `anyref` env parameter down to `(ref $ThisClosuresEnvType)` before any captured-field reads, then use `struct.get` for each.
168
+ - [x] **Step 4: Rewrite `compileClosureBody`**: its first instructions must `RefCastNonNull` (or the nullable variant, matching the declared param type) the incoming `anyref` env parameter down to `(ref $ThisClosuresEnvType)` before any captured-field reads, then use `struct.get` for each.
169
169
 
170
- - [ ] **Step 5: Update the shared `call_indirect` function-type signatures** (`fnParamTypeToWasmSig`/`ClosureSigKey` and wherever `call_indirect` types are registered/deduped) so the env-pointer parameter position is `ValType::Ref(RefType::ANYREF)` instead of `ValType::I32`, everywhere this shape is constructed.
170
+ - [x] **Step 5: Update the shared `call_indirect` function-type signatures** (`fnParamTypeToWasmSig`/`ClosureSigKey` and wherever `call_indirect` types are registered/deduped) so the env-pointer parameter position is `ValType::Ref(RefType::ANYREF)` instead of `ValType::I32`, everywhere this shape is constructed.
171
171
 
172
172
  #### 2f. Retire the bump allocator entirely
173
173
 
174
- - [ ] **Step 1: Remove `HEAP_BASE`, `STRING_BASE`, `bump_global` (the field, its threading through `CompileCtx`/`LocalCtx`, and its initialization in `compileSource`), and the `addMemory`/memory-section code in `WasmModule::finish`** once `cargo build -p plum-wasm-codegen` shows nothing references them anymore. If anything still does, that's a sign an earlier 2a–2e step was missed or incomplete — go back, don't leave a partial bump-allocator fallback path.
175
- - [ ] **Step 2: Remove now-dead scratch-slot bookkeeping** (`classcall_scratch` etc., in `Collector` and `LocalCtx`) for any use case fully replaced by direct stack-order `struct.new` construction (per 2b Step 2) — but only what's actually provably dead; `match`'s subject-holding scratch locals are still needed (now typed as `ref null` instead of `i32`), don't remove those.
174
+ - [x] **Step 1: Remove `HEAP_BASE`, `STRING_BASE`, `bump_global` (the field, its threading through `CompileCtx`/`LocalCtx`, and its initialization in `compileSource`), and the `addMemory`/memory-section code in `WasmModule::finish`** once `cargo build -p plum-wasm-codegen` shows nothing references them anymore. If anything still does, that's a sign an earlier 2a–2e step was missed or incomplete — go back, don't leave a partial bump-allocator fallback path.
175
+ - [x] **Step 2: Remove now-dead scratch-slot bookkeeping** (`classcall_scratch` etc., in `Collector` and `LocalCtx`) for any use case fully replaced by direct stack-order `struct.new` construction (per 2b Step 2) — but only what's actually provably dead; `match`'s subject-holding scratch locals are still needed (now typed as `ref null` instead of `i32`), don't remove those.
176
176
 
177
177
  #### 2g. Full verification for Task 2
178
178
 
179
- - [ ] **Step 1: `cargo build --workspace --all-targets`** — iterate until clean.
180
- - [ ] **Step 2: Update every existing `codegen_tests.rs`/`examples_test.rs` test that encoded assumptions about the old representation** (e.g. anything asserting raw byte layout, or relying on `HEAP_BASE`-adjacent behavior) — re-verify each still asserts the right OBSERVABLE behavior (a program's computed result), not the old mechanism.
181
- - [ ] **Step 3: `cargo test --workspace`** — expected: 100% pass. This is the real gate for this entire task; do not commit Task 2 before this is green.
182
- - [ ] **Step 4: Commit**
179
+ - [x] **Step 1: `cargo build --workspace --all-targets`** — iterate until clean.
180
+ - [x] **Step 2: Update every existing `codegen_tests.rs`/`examples_test.rs` test that encoded assumptions about the old representation** (e.g. anything asserting raw byte layout, or relying on `HEAP_BASE`-adjacent behavior) — re-verify each still asserts the right OBSERVABLE behavior (a program's computed result), not the old mechanism.
181
+ - [x] **Step 3: `cargo test --workspace`** — expected: 100% pass. This is the real gate for this entire task; do not commit Task 2 before this is green.
182
+ - [x] **Step 4: Commit**
183
183
 
184
184
  ```bash
185
185
  git add plum-wasm-codegen/src plum-wasm-codegen/tests
plum-wasm-codegen/Cargo.toml CHANGED
@@ -12,4 +12,4 @@ plum-checker = { path = "../plum-checker" }
12
12
  wasmparser = "0.220"
13
13
  tree-sitter = "0.26"
14
14
  tree-sitter-plum = { path = "../tooling/tree-sitter-plum" }
15
- wasmtime = "28"
15
+ wasmtime = "47"
plum-wasm-codegen/src/lib.rs CHANGED
@@ -9,12 +9,6 @@ use plum_core::ast;
9
9
  use plum_checker::types::{PlumType, TypeEnv, TypeScheme};
10
10
  use plum_checker::{ClassEnv, MethodEnv, EnumVariants, EnumVariantInfo, EnumParams};
11
11
 
12
- /// Bump-allocated heap for class instances starts at the second 64KiB page so it can
13
- /// never collide with the (small, compile-time-sized) string literal data area below it.
14
- const HEAP_BASE: u32 = 65536;
15
- /// String literal data (length-prefixed UTF-8 blobs) is laid out from here upward.
16
- const STRING_BASE: u32 = 8;
17
-
18
12
  /// One entry in the module's type section. Wasm's type section is a SINGLE shared
19
13
  /// index space for function types AND (once wasm-gc is in play) composite
20
14
  /// struct/array types — `Rec` entries occupy as many consecutive indices as they
@@ -28,6 +22,14 @@ enum TypeEntry {
28
22
  Rec(Vec<SubType>),
29
23
  }
30
24
 
25
+ /// One entry in the module's data section. Like the type section, data segments
26
+ /// share ONE index space regardless of kind — a `Passive` segment's index (needed by
27
+ /// `array.new_data`) is its position among ALL segments, active or passive.
28
+ enum DataSegmentEntry {
29
+ Active(u32, Vec<u8>),
30
+ Passive(Vec<u8>),
31
+ }
32
+
31
33
  pub struct WasmModule {
32
34
  types: Vec<TypeEntry>,
33
35
  /// Running count of type-section INDICES assigned so far — NOT the same as
@@ -37,15 +39,15 @@ pub struct WasmModule {
37
39
  imports: Vec<(String, String, u32)>,
38
40
  functions: Vec<(u32, Vec<u8>)>,
39
41
  exports: Vec<(String, ExportKind, u32)>,
40
- memories: Vec<MemoryType>,
41
42
  globals: Vec<(ValType, bool, Vec<u8>)>,
42
- data_segments: Vec<(u32, Vec<u8>)>,
43
+ data_segments: Vec<DataSegmentEntry>,
43
44
  /// Function indices, in table order — the single funcref table used for
44
45
  /// closure `call_indirect` dispatch. Index into this vec IS the table index.
45
46
  table_elements: Vec<u32>,
46
47
  pub func_import_count: u32,
47
48
  pub func_count: u32,
48
49
  global_count: u32,
50
+ start_function: Option<u32>,
49
51
  }
50
52
 
51
53
  impl WasmModule {
@@ -56,13 +58,13 @@ impl WasmModule {
56
58
  imports: Vec::new(),
57
59
  functions: Vec::new(),
58
60
  exports: Vec::new(),
59
- memories: Vec::new(),
60
61
  globals: Vec::new(),
61
62
  data_segments: Vec::new(),
62
63
  table_elements: Vec::new(),
63
64
  func_import_count: 0,
64
65
  func_count: 0,
65
66
  global_count: 0,
67
+ start_function: None,
66
68
  }
67
69
  }
68
70
 
@@ -105,18 +107,6 @@ impl WasmModule {
105
107
  self.exports.push((name.to_string(), kind, idx));
106
108
  }
107
109
 
108
- pub fn addMemory(&mut self, min: u64, max: Option<u64>) -> u32 {
109
- let idx = self.memories.len() as u32;
110
- self.memories.push(MemoryType {
111
- minimum: min,
112
- maximum: max,
113
- memory64: false,
114
- shared: false,
115
- page_size_log2: None,
116
- });
117
- idx
118
- }
119
-
120
110
  pub fn addGlobal(&mut self, val_type: ValType, mutable: bool, init: &[u8]) -> u32 {
121
111
  let idx = self.global_count;
122
112
  self.globals.push((val_type, mutable, init.to_vec()));
@@ -125,7 +115,27 @@ impl WasmModule {
125
115
  }
126
116
 
127
117
  pub fn addDataSegment(&mut self, offset: u32, data: &[u8]) {
128
- self.data_segments.push((offset, data.to_vec()));
118
+ self.data_segments.push(DataSegmentEntry::Active(offset, data.to_vec()));
119
+ }
120
+
121
+ /// Adds a passive segment (no implicit memory-init offset) and returns its index
122
+ /// in the shared active/passive data-segment index space — the index `array.new_data`
123
+ /// needs to reference it. Passive segments require a `DataCountSection` (see `finish`).
124
+ pub fn addPassiveDataSegment(&mut self, data: &[u8]) -> u32 {
125
+ let idx = self.data_segments.len() as u32;
126
+ self.data_segments.push(DataSegmentEntry::Passive(data.to_vec()));
127
+ idx
128
+ }
129
+
130
+ /// Registers `func_idx` to run once automatically at instantiation, before any
131
+ /// export is callable — needed because `struct.new` (and therefore constructing
132
+ /// any GC singleton, like the pre-allocated payload-free enum variants) is not
133
+ /// allowed inside a `global`'s own const-expr initializer (confirmed empirically
134
+ /// in Task 1 — `wasmtimeGcConfigAllowsStructNewInGlobalConstExpr` fails), so those
135
+ /// globals are declared `mutable` with a `ref.null` initial value and populated
136
+ /// here instead.
137
+ pub fn setStartFunction(&mut self, func_idx: u32) {
138
+ self.start_function = Some(func_idx);
129
139
  }
130
140
 
131
141
  /// Registers `func_idx` as the next slot in the single funcref table used for
@@ -184,15 +194,6 @@ impl WasmModule {
184
194
  module.section(&tables);
185
195
  }
186
196
 
187
- // Memory section
188
- if !self.memories.is_empty() {
189
- let mut mem = MemorySection::new();
190
- for mt in &self.memories {
191
- mem.memory(*mt);
192
- }
193
- module.section(&mem);
194
- }
195
-
196
197
  // Global section
197
198
  if !self.globals.is_empty() {
198
199
  let mut globals = GlobalSection::new();
@@ -215,6 +216,11 @@ impl WasmModule {
215
216
  module.section(&exports);
216
217
  }
217
218
 
219
+ // Start section
220
+ if let Some(func_idx) = self.start_function {
221
+ module.section(&StartSection { function_index: func_idx });
222
+ }
223
+
218
224
  // Element section
219
225
  if !self.table_elements.is_empty() {
220
226
  let mut elements = ElementSection::new();
@@ -223,6 +229,13 @@ impl WasmModule {
223
229
  module.section(&elements);
224
230
  }
225
231
 
232
+ // DataCount section — required whenever `array.new_data`/`data.drop` reference
233
+ // a passive segment, and must appear before the code section.
234
+ let has_passive = self.data_segments.iter().any(|e| matches!(e, DataSegmentEntry::Passive(_)));
235
+ if has_passive {
236
+ module.section(&DataCountSection { count: self.data_segments.len() as u32 });
237
+ }
238
+
226
239
  // Code section
227
240
  if !self.functions.is_empty() {
228
241
  let mut code = CodeSection::new();
@@ -235,9 +248,16 @@ impl WasmModule {
235
248
  // Data section
236
249
  if !self.data_segments.is_empty() {
237
250
  let mut data = DataSection::new();
238
- for (offset, bytes) in &self.data_segments {
251
+ for entry in &self.data_segments {
252
+ match entry {
253
+ DataSegmentEntry::Active(offset, bytes) => {
239
- let offset_expr = ConstExpr::i32_const(*offset as i32);
254
+ let offset_expr = ConstExpr::i32_const(*offset as i32);
240
- data.active(0, &offset_expr, bytes.iter().copied());
255
+ data.active(0, &offset_expr, bytes.iter().copied());
256
+ }
257
+ DataSegmentEntry::Passive(bytes) => {
258
+ data.passive(bytes.iter().copied());
259
+ }
260
+ }
241
261
  }
242
262
  module.section(&data);
243
263
  }
@@ -260,15 +280,20 @@ pub struct FuncSig {
260
280
 
261
281
  /// Everything codegen needs to know about one closure *literal* found in the program.
262
282
  /// wasm has no native closures: each literal `|v| body` becomes its own real wasm
263
- /// function (registered in the funcref table), and a closure *value* is a single i32
283
+ /// function (registered in the funcref table), and a closure *value* is a `ref` to
264
- /// pointer to a heap pair `{table_index: i32 @0, env_pointer: i32 @8}`. The env is a
284
+ /// the shared `{table_idx: i32, env: anyref}` struct (`GcTypeRegistry::closure_type_idx`).
285
+ /// `env` is a `ref.cast` of this closure literal's OWN env struct type
265
- /// separate heap allocation: one 8-byte slot per captured (free) variable, in
286
+ /// (`env_type_idx`), one field per captured (free) variable in `free_vars` order.
266
- /// `free_vars` order.
267
287
  pub struct ClosureInfo {
268
288
  /// Reserved wasm function index for this closure's compiled body.
269
289
  pub func_idx: u32,
270
- /// Index of `func_idx` in the funcref table (the `i32` stored at struct offset 0).
290
+ /// Index of `func_idx` in the funcref table (the `i32` stored in the closure
291
+ /// struct's `table_idx` field).
271
292
  pub table_idx: u32,
293
+ /// This closure literal's own env struct type index (fields = `free_vars`, in
294
+ /// order) — assigned once all closures are discovered, alongside every other
295
+ /// closure's env type and the shared closure-value struct, in one `rec` group.
296
+ pub env_type_idx: u32,
272
297
  /// Closure param val types (NOT including the implicit leading env pointer).
273
298
  pub param_vts: Vec<ValType>,
274
299
  /// Closure param plum types (for the closure body's own type env).
@@ -276,7 +301,7 @@ pub struct ClosureInfo {
276
301
  /// Closure return val type (`None` for a `Unit`-returning closure).
277
302
  pub ret_vt: Option<ValType>,
278
303
  /// Free variables captured by value, in a stable (first-appearance) order; the
279
- /// index into this vec IS the variable's env-struct slot (offset = idx * 8).
304
+ /// index into this vec IS the variable's field index in the env struct.
280
305
  pub free_vars: Vec<(String, PlumType)>,
281
306
  }
282
307
 
@@ -293,7 +318,6 @@ pub struct CompileCtx<'a> {
293
318
  pub enum_variants: EnumVariants,
294
319
  pub enum_params: EnumParams,
295
320
  pub global_env: TypeEnv,
296
- pub bump_global: u32,
297
321
  /// Closure literal (keyed by `&Expr::Closure` pointer identity) -> its `ClosureInfo`.
298
322
  pub closures: HashMap<usize, ClosureInfo>,
299
323
  /// The AST of each discovered closure literal, keyed the same way, so its body can
@@ -301,48 +325,42 @@ pub struct CompileCtx<'a> {
301
325
  pub closure_asts: HashMap<usize, &'a ast::Closure>,
302
326
  /// Closure wasm signature -> function-type index, for `call_indirect` at call sites.
303
327
  pub closure_call_types: HashMap<ClosureSigKey, u32>,
304
- /// Top-level function name -> static data offset of its zero-capture "trampoline"
328
+ /// Top-level function name -> global index holding its zero-capture "trampoline"
305
- /// closure struct `{table_idx, env_ptr=0}`, for using a plain named function
329
+ /// closure struct `{table_idx, env=null}`, for using a plain named function
306
330
  /// wherever a `fn(...)`-typed value is expected (e.g. `each(double)`). Since the
307
- /// struct has no captures it never changes, so it's baked into a data segment
331
+ /// struct has no captures it never changes, so it's built once by the shared
308
- /// once at compile time instead of being bump-allocated per reference.
332
+ /// `start` function instead of being reconstructed per reference.
309
333
  pub named_fn_values: HashMap<String, u32>,
310
- /// Shared runtime helper `(a: i32, b: i32) -> i32`: bump-allocates a new
334
+ /// Shared runtime helper `(a: ref Str, b: ref Str) -> ref Str`: allocates a new
311
- /// length-prefixed string exactly long enough to hold `a`'s bytes followed by
335
+ /// `array<i8>` exactly long enough to hold `a`'s bytes followed by `b`'s, for
312
- /// `b`'s, for lowering string interpolation (`"{expr}"`).
336
+ /// lowering string interpolation (`"{expr}"`).
313
337
  pub string_concat_func: u32,
314
- /// Shared runtime helper `(n: i64) -> i32`: bump-allocates a new length-prefixed
338
+ /// Shared runtime helper `(n: i64) -> ref Str`: allocates a new `array<i8>`
315
- /// string holding `n`'s decimal representation, for interpolating an `Int`.
339
+ /// holding `n`'s decimal representation, for interpolating an `Int`.
316
340
  pub int_to_string_func: u32,
317
- /// wasm-gc type-section indices for this program's classes/enums/Str (Task 1 of
341
+ /// wasm-gc type-section indices for this program's classes/enums/Str/closures
318
- /// docs/superpowers/plans/2026-07-25-wasm-gc-migration.md) — populated but not
342
+ /// every value's real representation (see `docs/superpowers/plans/2026-07-25-wasm-gc-migration.md`).
319
- /// yet consumed by any codegen below; the bump allocator remains the real
320
- /// representation until Task 2 lands.
321
343
  pub gc_types: GcTypeRegistry,
344
+ /// Payload-free variant name (True/False/None/...) -> the global index holding
345
+ /// its one pre-allocated instance (see this migration plan's Decision 2).
346
+ pub singleton_globals: HashMap<String, u32>,
322
347
  }
323
348
 
324
- /// Per-module state that accumulates as function bodies are compiled: the running
349
+ /// Per-module state that accumulates as function bodies are compiled: every static
350
+ /// string literal's bytes, staged here (rather than added straight to `WasmModule`)
351
+ /// so `compileStaticString` can know a segment's final passive-data-section index —
325
- /// offset for placing string literal bytes, and the resulting data segments.
352
+ /// its position among ALL staged segments before that section is actually
353
+ /// assembled at the end of `compileSource`.
326
354
  struct ModuleState {
327
- next_string_offset: u32,
328
- data_segments: Vec<(u32, Vec<u8>)>,
355
+ passive_segments: Vec<Vec<u8>>,
329
356
  }
330
357
 
331
358
  struct LocalCtx<'a> {
332
359
  locals: HashMap<String, u32>,
333
- /// First local index reserved for `ClassCall` scratch temporaries.
334
- classcall_scratch_base: u32,
335
- /// `ClassCall` expr identity (pointer address) -> scratch slot offset.
336
- classcall_scratch: HashMap<usize, u32>,
337
360
  /// First local index reserved for `match` subject scratch temporaries.
338
361
  match_scratch_base: u32,
339
362
  /// `Match` stmt identity (pointer address) -> scratch slot offset.
340
363
  match_scratch_index: HashMap<usize, u32>,
341
- /// First local index reserved for closure-construction scratch temporaries; each
342
- /// closure literal uses two consecutive slots (env struct ptr, closure struct ptr).
343
- closure_scratch_base: u32,
344
- /// `Closure` expr identity (pointer address) -> offset of its first (of two) slots.
345
- closure_scratch: HashMap<usize, u32>,
346
364
  /// First local index reserved for nested-constructor-pattern scratch temporaries
347
365
  /// (`Some(Some(v))`'s inner `Some(v)`); the outermost pattern uses a
348
366
  /// `match_scratch` slot instead, so this only covers depth >= 1.
@@ -366,6 +384,8 @@ struct LocalCtx<'a> {
366
384
  methods: &'a MethodEnv,
367
385
  enum_variants: &'a EnumVariants,
368
386
  enum_params: &'a EnumParams,
387
+ gc_types: &'a GcTypeRegistry,
388
+ singleton_globals: &'a HashMap<String, u32>,
369
389
  /// Tracks each binding's inferred type as compilation proceeds through
370
390
  /// statements in order, mirroring `plum-checker`'s own env evolution — needed
371
391
  /// to resolve `Attribute`/`ClassCall` targets and pick the right load/store width.
@@ -379,7 +399,6 @@ struct LocalCtx<'a> {
379
399
  /// and would fall back to its old TVar-defaults-to-Int behavior, disagreeing
380
400
  /// with the (now correct) signature the closure's body was actually compiled with.
381
401
  closure_local_sigs: RefCell<HashMap<String, ClosureSigKey>>,
382
- bump_global: u32,
383
402
  }
384
403
 
385
404
  fn fnKey(f: &ast::Fn) -> String {
@@ -399,36 +418,77 @@ fn paramTypeName(pt: &ast::ParamType) -> &str {
399
418
  }
400
419
  }
401
420
 
421
+ thread_local! {
422
+ /// The current `compileSource` call's wasm-gc type registry. `compileSource` is
423
+ /// the sole entry point and is never re-entrant/concurrent within one thread, so
424
+ /// a thread-local avoids threading an explicit `&GcTypeRegistry` parameter through
425
+ /// every one of `plumTypeToValtype`/`astTypeToWasm`'s ~25 call sites (many several
426
+ /// functions removed from anywhere a `CompileCtx`/`LocalCtx` is in scope, e.g.
427
+ /// function-signature registration that runs before any `LocalCtx` exists). Set
428
+ /// once near the top of `compileSource`, before anything below reads it.
429
+ static CURRENT_GC_TYPES: RefCell<Option<GcTypeRegistry>> = const { RefCell::new(None) };
430
+ }
431
+
432
+ fn withGcTypes<R>(f: impl FnOnce(&GcTypeRegistry) -> R) -> R {
433
+ CURRENT_GC_TYPES.with(|c| {
434
+ let borrow = c.borrow();
435
+ let registry = borrow.as_ref().expect("internal codegen error: GC type registry read before compileSource initialized it");
436
+ f(registry)
437
+ })
438
+ }
439
+
440
+ /// Resolves an `ast::Type`/`ast::ParamType`'s bare name (e.g. from a function
441
+ /// signature, before any `PlumType`/checker involvement) to its wasm-gc `ValType`.
402
442
  fn astTypeToWasm(name: &str) -> Option<ValType> {
403
443
  match name {
404
444
  "Int" => Some(ValType::I64),
405
445
  "Float" => Some(ValType::F64),
406
- "Bool" => Some(ValType::I32),
407
- "Str" => Some(ValType::I32),
408
446
  "Unit" => None,
447
+ "Bool" => Some(plumTypeToValtype(&PlumType::TBool)),
448
+ "Str" => Some(plumTypeToValtype(&PlumType::TStr)),
409
- _ => Some(ValType::I32), // class instance / unmodeled type: pointer
449
+ other => Some(plumTypeToValtype(&PlumType::TNamed(other.to_string()))),
410
450
  }
411
451
  }
412
452
 
453
+ /// A `ref null $Ty` — every heap value (class instance, enum/Bool variant, Str
454
+ /// array, closure struct) is nullable-by-convention, matching how a bump-allocator
455
+ /// i32 pointer could be "null" (0) too; nothing in this codegen currently relies on
456
+ /// non-nullable refs for an optimization, so nullable everywhere keeps this simple.
457
+ fn gcRef(idx: u32) -> ValType {
458
+ ValType::Ref(RefType { nullable: true, heap_type: HeapType::Concrete(idx) })
459
+ }
460
+
413
461
  fn plumTypeToValtype(t: &PlumType) -> ValType {
414
462
  match t {
415
463
  PlumType::TInt => ValType::I64,
416
464
  PlumType::TFloat => ValType::F64,
465
+ PlumType::TBool => withGcTypes(|r| gcRef(*r.enum_super_type_idx.get("Bool").expect("Bool must be registered"))),
466
+ PlumType::TStr => withGcTypes(|r| gcRef(r.str_type_idx)),
467
+ PlumType::TNamed(name) => withGcTypes(|r| {
468
+ match r.class_type_idx.get(name).or_else(|| r.enum_super_type_idx.get(name)) {
469
+ Some(idx) => gcRef(*idx),
470
+ // Genuinely unmodeled type name (not a real class/enum) — permissive
471
+ // fallback, matching this function's pre-wasm-gc "unmodeled type:
472
+ // pointer" behavior; codegen sites that actually need a concrete
473
+ // struct type still resolve it themselves and error clearly if absent.
474
+ None => ValType::Ref(RefType::ANYREF),
475
+ }
476
+ }),
417
- // A closure value is a single i32 pointer to its heap-allocated
477
+ // A closure value is a 2-field `{table_index: i32, env: anyref}` struct
478
+ // its OWN concrete GC type, registered per Task 2e (closures), not through
418
- // `{table_index, env_pointer}` pair, so `TFun` is an i32 like every other
479
+ // this general resolver; `anyref` here is a safe placeholder used only
480
+ // where a closure's exact struct type isn't being constructed/destructured
481
+ // directly (e.g. deciding a local's storage class), matching `TVariadic`'s
419
- // heap reference (`TBool`/`TStr`/`TNamed`).
482
+ // existing "not modeled as a concrete shape here" treatment.
420
- PlumType::TBool | PlumType::TStr | PlumType::TNamed(_) | PlumType::TFun(_, _) | PlumType::TVariadic(_) => ValType::I32,
483
+ PlumType::TFun(_, _) | PlumType::TVariadic(_) => ValType::Ref(RefType::ANYREF),
421
484
  PlumType::TVar(_) | PlumType::TUnit => ValType::I64,
422
485
  }
423
486
  }
424
487
 
425
488
  /// Type-section indices for every wasm-gc composite type this program's monomorphized
426
489
  /// classes/enums/`Str` need. Populated once in `compileSource` from the checker's
427
- /// global tables. Per Task 1 of docs/superpowers/plans/2026-07-25-wasm-gc-migration.md,
428
- /// this is currently populated but NOT YET consumed by construction/field-access/match
429
- /// codegen (that's Task 2) — its job right now is only to prove the type section itself
430
- /// is well-formed under the new representation, alongside the still-untouched
431
- /// bump-allocator codegen path.
490
+ /// global tables.
491
+ #[derive(Clone)]
432
492
  pub struct GcTypeRegistry {
433
493
  /// Concrete class/struct name -> its one wasm-gc `struct` type index.
434
494
  pub class_type_idx: HashMap<String, u32>,
@@ -442,6 +502,16 @@ pub struct GcTypeRegistry {
442
502
  pub variant_type_idx: HashMap<String, u32>,
443
503
  /// The single shared `array<i8>` type index every `Str` value uses.
444
504
  pub str_type_idx: u32,
505
+ /// The single shared closure-value struct type index — `{table_idx: i32, env:
506
+ /// anyref}` — every closure (and zero-capture "trampoline") uses regardless of
507
+ /// its own captures; only assigned once closures are discovered (see
508
+ /// `compileSource`), so `0` until then (nothing reads it earlier).
509
+ pub closure_type_idx: u32,
510
+ /// Wasm value type -> the shared `array<T>` type index used to pass a `...T`
511
+ /// variadic argument pack as one GC array (replacing the old bump-allocated
512
+ /// `[count][elem...]` blob — `array.len` replaces the explicit count). Populated
513
+ /// alongside `closure_type_idx`, from every `ParamType::Variadic` in the program.
514
+ pub variadic_array_type_idx: HashMap<ValType, u32>,
445
515
  }
446
516
 
447
517
  /// Resolves a plum type to the wasm-gc `ValType` its values are represented as, given
@@ -484,11 +554,12 @@ fn buildGcTypeRegistry(
484
554
  source: &ast::Source,
485
555
  classes: &ClassEnv,
486
556
  enum_variants: &EnumVariants,
557
+ enum_params: &EnumParams,
487
558
  ) -> GcTypeRegistry {
488
559
  enum Slot {
489
560
  Str,
490
561
  Class(String),
491
- EnumSuper,
562
+ EnumSuper(String),
492
563
  Variant(String),
493
564
  }
494
565
 
@@ -519,7 +590,7 @@ fn buildGcTypeRegistry(
519
590
  }
520
591
  for (enum_name, variant_names) in &enum_decls {
521
592
  enum_super_type_idx.insert(enum_name.clone(), slots.len() as u32);
522
- slots.push(Slot::EnumSuper);
593
+ slots.push(Slot::EnumSuper(enum_name.clone()));
523
594
  for vname in variant_names {
524
595
  variant_type_idx.insert(vname.clone(), slots.len() as u32);
525
596
  slots.push(Slot::Variant(vname.clone()));
@@ -534,6 +605,8 @@ fn buildGcTypeRegistry(
534
605
  enum_super_type_idx,
535
606
  variant_type_idx,
536
607
  str_type_idx: 0,
608
+ closure_type_idx: 0,
609
+ variadic_array_type_idx: HashMap::new(),
537
610
  };
538
611
 
539
612
  // Pass 2: build the real SubType for every slot, now that every cross-reference
@@ -559,16 +632,36 @@ fn buildGcTypeRegistry(
559
632
  composite_type: CompositeType { inner: CompositeInnerType::Struct(StructType { fields: field_types.into() }), shared: false },
560
633
  }
561
634
  }
635
+ // An ORDINARY enum's supertype declares zero fields (each variant adds its
636
+ // own distinct payload fields below it). A DISCRIMINANT enum (`enum
637
+ // Foo(n: Int) = ...`) is different: every variant shares the EXACT SAME
638
+ // field list (`plum-checker::buildGlobalTables` already gives every variant
639
+ // of such an enum identical `field_types`, equal to the shared params), so
640
+ // the supertype declares those fields directly — this is what lets `self.n`
641
+ // field access work on the plain supertype-typed reference with a
642
+ // `struct.get`, no `ref.cast` to one arbitrary variant required (which would
643
+ // trap at runtime whenever `self` isn't actually THAT variant).
562
- Slot::EnumSuper => SubType {
644
+ Slot::EnumSuper(enum_name) => {
645
+ let params = enum_params.get(enum_name).cloned().unwrap_or_default();
646
+ let field_types: Vec<FieldType> = params.iter().map(|(_, ty)| FieldType {
647
+ element_type: StorageType::Val(plumTypeToGcValtype(ty, &registry)),
648
+ mutable: false,
649
+ }).collect();
650
+ SubType {
563
- is_final: false,
651
+ is_final: false,
564
- supertype_idx: None,
652
+ supertype_idx: None,
565
- composite_type: CompositeType { inner: CompositeInnerType::Struct(StructType { fields: Vec::new().into() }), shared: false },
653
+ composite_type: CompositeType { inner: CompositeInnerType::Struct(StructType { fields: field_types.into() }), shared: false },
566
- },
654
+ }
655
+ }
567
656
  Slot::Variant(vname) => {
568
657
  let info = enum_variants.get(vname)
569
658
  .unwrap_or_else(|| panic!("internal codegen error: variant '{}' missing from EnumVariants", vname));
570
659
  let super_idx = *registry.enum_super_type_idx.get(&info.enum_name)
571
660
  .unwrap_or_else(|| panic!("internal codegen error: enum '{}' missing its supertype slot", info.enum_name));
661
+ // For a discriminant enum, `info.field_types` is ALREADY identical to the
662
+ // supertype's own fields (see the `EnumSuper` arm above) — a variant with
663
+ // zero ADDED fields beyond its supertype is still valid wasm-gc
664
+ // subtyping, so no special-casing is needed here.
572
665
  let field_types: Vec<FieldType> = info.field_types.iter().map(|ty| FieldType {
573
666
  element_type: StorageType::Val(plumTypeToGcValtype(ty, &registry)),
574
667
  mutable: false,
@@ -587,10 +680,10 @@ fn buildGcTypeRegistry(
587
680
  }
588
681
 
589
682
  /// Maps an `ast::ParamType::Fn(params, ret)` to the wasm signature of the *closure
590
- /// function* it compiles to: an implicit leading `env_ptr: I32`, then one param per
683
+ /// function* it compiles to: an implicit leading `env: anyref`, then one param per
591
684
  /// declared param type, returning `ret`'s val type (or nothing for `Unit`).
592
685
  fn fnParamTypeToWasmSig(params: &[ast::Type], ret: &Option<Box<ast::Type>>) -> (Vec<ValType>, Option<ValType>) {
593
- let mut vts = vec![ValType::I32]; // env pointer
686
+ let mut vts = vec![ValType::Ref(RefType::ANYREF)]; // env pointer
594
687
  for p in params {
595
688
  vts.push(astTypeToWasm(&p.name).unwrap_or(ValType::I32));
596
689
  }
@@ -641,17 +734,71 @@ pub fn compileSource(source: &ast::Source) -> Result<Vec<u8>, String> {
641
734
  let (global_env, classes, methods, enum_variants, enum_params) = plum_checker::buildGlobalTables(source);
642
735
 
643
736
  let mut module = WasmModule::new();
737
+
738
+ let mut gc_types = buildGcTypeRegistry(&mut module, source, &classes, &enum_variants, &enum_params);
739
+ CURRENT_GC_TYPES.with(|c| *c.borrow_mut() = Some(gc_types.clone()));
740
+
741
+ // Register one shared `array<T>` GC type per distinct wasm value type used by a
742
+ // `...T` variadic parameter anywhere in the program (almost always just one, e.g.
743
+ // `Int...`) — replaces the old bump-allocated `[count][elem...]` blob (`array.len`
744
+ // replaces the explicit count word). Must happen before function signatures are
745
+ // registered below, since a variadic param's wasm type is this array's `ref`.
746
+ let mut variadic_elem_vts: Vec<ValType> = Vec::new();
747
+ for item in &source.items {
748
+ if let ast::Item::Fn(f) = item {
749
+ for p in &f.params {
750
+ if let ast::ParamType::Variadic(t) = &p.ty {
751
+ let vt = astTypeToWasm(&t.name).unwrap_or(ValType::I64);
752
+ if !variadic_elem_vts.contains(&vt) {
753
+ variadic_elem_vts.push(vt);
754
+ }
755
+ }
756
+ }
757
+ }
758
+ }
759
+ let variadic_array_subtypes: Vec<SubType> = variadic_elem_vts.iter().map(|vt| SubType {
760
+ is_final: true,
644
- module.addMemory(2, None);
761
+ supertype_idx: None,
762
+ composite_type: CompositeType {
763
+ inner: CompositeInnerType::Array(ArrayType(FieldType { element_type: StorageType::Val(*vt), mutable: false })),
764
+ shared: false,
765
+ },
766
+ }).collect();
767
+ if !variadic_array_subtypes.is_empty() {
768
+ let indices = module.addGcTypes(variadic_array_subtypes);
769
+ for (vt, idx) in variadic_elem_vts.iter().zip(indices) {
770
+ gc_types.variadic_array_type_idx.insert(*vt, idx);
771
+ }
645
- module.addExport("memory", ExportKind::Memory, 0);
772
+ CURRENT_GC_TYPES.with(|c| *c.borrow_mut() = Some(gc_types.clone()));
773
+ }
774
+
775
+ // Pre-allocate one instance of every payload-free variant (True/False/None/...)
776
+ // as a global, populated once by a `start` function rather than reconstructed on
777
+ // every reference — see this migration plan's Decision 2. `struct.new` isn't
778
+ // allowed inside a global's own const-expr initializer (confirmed empirically in
779
+ // Task 1), so each global starts `ref.null` and a `start` function fills it in
780
+ // before any export is callable. The zero-capture "trampoline" closures
781
+ // registered below (once closures are discovered) append to this SAME start
782
+ // function, so its body isn't finalized/patched into the module until then.
783
+ let mut singleton_globals: HashMap<String, u32> = HashMap::new();
784
+ let mut start_body = Vec::new();
785
+ for (name, info) in &enum_variants {
786
+ if !info.field_types.is_empty() {
787
+ continue;
788
+ }
789
+ let variant_idx = *gc_types.variant_type_idx.get(name)
790
+ .unwrap_or_else(|| panic!("internal codegen error: payload-free variant '{}' missing from GC type registry", name));
646
- let mut bump_init = Vec::new();
791
+ let mut init = Vec::new();
647
- Instruction::I32Const(HEAP_BASE as i32).encode(&mut bump_init);
792
+ Instruction::RefNull(HeapType::Concrete(variant_idx)).encode(&mut init);
648
- let bump_global = module.addGlobal(ValType::I32, true, &bump_init);
793
+ let global_idx = module.addGlobal(gcRef(variant_idx), true, &init);
649
-
794
+ singleton_globals.insert(name.clone(), global_idx);
795
+
650
- // Task 1 of the wasm-gc migration: declare the wasm-gc type section entries for
796
+ Instruction::StructNewDefault(variant_idx).encode(&mut start_body);
651
- // every class/enum/Str up front. Purely additive right now — nothing below reads
652
- // `gc_types` yet, so the bump-allocator codegen this function still emits is
797
+ Instruction::GlobalSet(global_idx).encode(&mut start_body);
653
- // completely unaffected; this only proves the new type section is well-formed.
798
+ }
654
- let gc_types = buildGcTypeRegistry(&mut module, source, &classes, &enum_variants);
799
+ let start_type_idx = module.addType(&[], &[]);
800
+ let start_func_idx = module.addFunction(start_type_idx, &[]);
801
+ module.setStartFunction(start_func_idx);
655
802
 
656
803
  let mut func_ids: HashMap<String, u32> = HashMap::new();
657
804
  let mut func_sigs: HashMap<String, FuncSig> = HashMap::new();
@@ -664,12 +811,17 @@ pub fn compileSource(source: &ast::Source) -> Result<Vec<u8>, String> {
664
811
  for item in &source.items {
665
812
  if let ast::Item::Fn(f) = item {
666
813
  let mut param_types: Vec<ValType> = Vec::new();
667
- if f.type_param.is_some() {
814
+ if let Some(recv) = &f.type_param {
668
- param_types.push(ValType::I32);
815
+ param_types.push(astTypeToWasm(recv).unwrap_or(ValType::I32));
669
816
  }
670
817
  for p in &f.params {
671
818
  let vt = match &p.ty {
672
- ast::ParamType::Variadic(_) => ValType::I32,
819
+ ast::ParamType::Variadic(t) => {
820
+ let elem_vt = astTypeToWasm(&t.name).unwrap_or(ValType::I64);
821
+ let arr_idx = *gc_types.variadic_array_type_idx.get(&elem_vt)
822
+ .expect("internal codegen error: variadic array type must be pre-registered for every elem type in the program");
823
+ gcRef(arr_idx)
824
+ }
673
825
  other => astTypeToWasm(paramTypeName(other)).unwrap_or(ValType::I32),
674
826
  };
675
827
  param_types.push(vt);
@@ -737,10 +889,43 @@ pub fn compileSource(source: &ast::Source) -> Result<Vec<u8>, String> {
737
889
  named_fn_refs.extend(walker.named_fn_refs);
738
890
  }
739
891
 
892
+ // Register every closure literal's own env struct type, plus the ONE shared
893
+ // closure-value struct `{table_idx: i32, env: anyref}` every closure (and
894
+ // trampoline, below) wraps its env in — declared together as a single `rec`
895
+ // group now that every closure's free-variable types are known. The shared
896
+ // struct is always slot 0; closure `i`'s env type is slot `i + 1`.
897
+ let mut closure_subtypes: Vec<SubType> = vec![SubType {
898
+ is_final: true,
899
+ supertype_idx: None,
900
+ composite_type: CompositeType {
901
+ inner: CompositeInnerType::Struct(StructType {
902
+ fields: vec![
903
+ FieldType { element_type: StorageType::Val(ValType::I32), mutable: false },
904
+ FieldType { element_type: StorageType::Val(ValType::Ref(RefType::ANYREF)), mutable: false },
905
+ ].into(),
906
+ }),
907
+ shared: false,
908
+ },
909
+ }];
910
+ for rc in &raw_closures {
911
+ let field_types: Vec<FieldType> = rc.free_vars.iter().map(|(_, ty)| FieldType {
912
+ element_type: StorageType::Val(plumTypeToValtype(ty)),
913
+ mutable: false,
914
+ }).collect();
915
+ closure_subtypes.push(SubType {
916
+ is_final: true,
917
+ supertype_idx: None,
918
+ composite_type: CompositeType { inner: CompositeInnerType::Struct(StructType { fields: field_types.into() }), shared: false },
919
+ });
920
+ }
921
+ let closure_type_indices = module.addGcTypes(closure_subtypes);
922
+ gc_types.closure_type_idx = closure_type_indices[0];
923
+ CURRENT_GC_TYPES.with(|c| *c.borrow_mut() = Some(gc_types.clone()));
924
+
740
925
  let mut closures: HashMap<usize, ClosureInfo> = HashMap::new();
741
926
  let mut closure_asts: HashMap<usize, &ast::Closure> = HashMap::new();
742
- for rc in &raw_closures {
927
+ for (i, rc) in raw_closures.iter().enumerate() {
743
- let mut sig_params = vec![ValType::I32]; // env pointer
928
+ let mut sig_params = vec![ValType::Ref(RefType::ANYREF)]; // env pointer
744
929
  sig_params.extend(rc.param_vts.iter().copied());
745
930
  let sig_key: ClosureSigKey = (sig_params.clone(), rc.ret_vt);
746
931
  let type_idx = match closure_call_types.get(&sig_key) {
@@ -757,6 +942,7 @@ pub fn compileSource(source: &ast::Source) -> Result<Vec<u8>, String> {
757
942
  closures.insert(rc.ptr, ClosureInfo {
758
943
  func_idx,
759
944
  table_idx,
945
+ env_type_idx: closure_type_indices[i + 1],
760
946
  param_vts: rc.param_vts.clone(),
761
947
  param_ptypes: rc.param_ptypes.clone(),
762
948
  ret_vt: rc.ret_vt,
@@ -767,19 +953,17 @@ pub fn compileSource(source: &ast::Source) -> Result<Vec<u8>, String> {
767
953
 
768
954
  // Register a zero-capture "trampoline" closure for every top-level function
769
955
  // referenced as a bare value (e.g. `each(double)`): a real wasm function with the
770
- // closure calling convention `(env_ptr, ...real_params) -> ret` that ignores its
956
+ // closure calling convention `(env, ...real_params) -> ret` that ignores its env
771
- // env pointer and forwards straight to the real function, plus a funcref-table
957
+ // and forwards straight to the real function, plus a funcref-table entry for it.
772
- // entry for it. Since it never captures anything, its `{table_idx, env_ptr=0}`
958
+ // Since it never captures anything, its `{table_idx, env=null}` closure struct is
773
- // closure struct is a compile-time constant — baked into a data segment once
959
+ // a compile-time constant — like every payload-free enum variant, built once by
774
- // rather than bump-allocated at every reference site.
960
+ // the shared `start` function and stored in its own global.
775
961
  let mut named_fn_values: HashMap<String, u32> = HashMap::new();
776
- let mut named_fn_data_segments: Vec<(u32, Vec<u8>)> = Vec::new();
777
- let mut next_static_offset = STRING_BASE;
778
962
  for name in &named_fn_refs {
779
963
  let sig = func_sigs.get(name).expect("named fn ref must be a registered top-level function");
780
964
  let real_func_idx = *func_ids.get(name).expect("named fn ref must be a registered top-level function");
781
965
 
782
- let mut sig_params = vec![ValType::I32]; // env pointer
966
+ let mut sig_params = vec![ValType::Ref(RefType::ANYREF)]; // env pointer
783
967
  sig_params.extend(sig.params.iter().copied());
784
968
  let sig_key: ClosureSigKey = (sig_params.clone(), sig.ret);
785
969
  let type_idx = match closure_call_types.get(&sig_key) {
@@ -795,34 +979,47 @@ pub fn compileSource(source: &ast::Source) -> Result<Vec<u8>, String> {
795
979
  let mut tbody = Vec::new();
796
980
  tbody.push(0u8); // no locals beyond the params already in the signature
797
981
  for i in 0..sig.params.len() {
798
- Instruction::LocalGet((i + 1) as u32).encode(&mut tbody); // local 0 is env_ptr, ignored
982
+ Instruction::LocalGet((i + 1) as u32).encode(&mut tbody); // local 0 is env, ignored
799
983
  }
800
984
  Instruction::Call(real_func_idx).encode(&mut tbody);
801
985
  Instruction::End.encode(&mut tbody);
802
986
  let func_idx = module.addFunction(type_idx, &tbody);
803
987
  let table_idx = module.addTableElement(func_idx);
804
988
 
805
- let offset = next_static_offset;
806
- next_static_offset += 8;
807
- let mut bytes = Vec::with_capacity(8);
989
+ let mut init = Vec::new();
990
+ Instruction::RefNull(HeapType::Concrete(gc_types.closure_type_idx)).encode(&mut init);
808
- bytes.extend_from_slice(&(table_idx as i32).to_le_bytes());
991
+ let global_idx = module.addGlobal(gcRef(gc_types.closure_type_idx), true, &init);
809
- bytes.extend_from_slice(&0i32.to_le_bytes());
810
- named_fn_data_segments.push((offset, bytes));
811
- named_fn_values.insert(name.clone(), offset);
992
+ named_fn_values.insert(name.clone(), global_idx);
993
+
994
+ Instruction::I32Const(table_idx as i32).encode(&mut start_body);
995
+ Instruction::RefNull(HeapType::ANY).encode(&mut start_body);
996
+ Instruction::StructNew(gc_types.closure_type_idx).encode(&mut start_body);
997
+ Instruction::GlobalSet(global_idx).encode(&mut start_body);
812
998
  }
813
999
 
1000
+ // The `start` function is now complete — every payload-free variant singleton
1001
+ // (above) and every trampoline closure (above) has appended its own init code.
1002
+ // It declares no locals of its own, but a function body's raw bytes must still
1003
+ // start with the (empty) locals-declaration vector — a single `0x00` — before
1004
+ // the instruction stream, exactly like every other compiled function body below.
1005
+ Instruction::End.encode(&mut start_body);
1006
+ let mut final_start_body = vec![0u8];
1007
+ final_start_body.extend(start_body);
1008
+ let start_slot = (start_func_idx - module.func_import_count) as usize;
1009
+ module.functions[start_slot].1 = final_start_body;
1010
+
814
1011
  // Runtime helpers backing string interpolation (`"{expr}"`): always registered
815
1012
  // (unconditionally, for simplicity) since they're cheap and self-contained.
816
- let string_concat_func = registerStringConcatHelper(&mut module, bump_global);
1013
+ let string_concat_func = registerStringConcatHelper(&mut module, gc_types.str_type_idx);
817
- let int_to_string_func = registerIntToStringHelper(&mut module, bump_global);
1014
+ let int_to_string_func = registerIntToStringHelper(&mut module, gc_types.str_type_idx);
818
1015
 
819
1016
  let ctx = CompileCtx {
820
- func_ids, func_sigs, classes, methods, enum_variants, enum_params, global_env, bump_global,
1017
+ func_ids, func_sigs, classes, methods, enum_variants, enum_params, global_env,
821
1018
  closures, closure_asts, closure_call_types, named_fn_values,
822
- string_concat_func, int_to_string_func, gc_types,
1019
+ string_concat_func, int_to_string_func, gc_types, singleton_globals,
823
1020
  };
824
1021
 
825
- let mut state = ModuleState { next_string_offset: next_static_offset, data_segments: named_fn_data_segments };
1022
+ let mut state = ModuleState { passive_segments: Vec::new() };
826
1023
 
827
1024
  let mut compiled_bodies: Vec<(String, Vec<u8>)> = Vec::new();
828
1025
  for f in &fns {
@@ -849,8 +1046,12 @@ pub fn compileSource(source: &ast::Source) -> Result<Vec<u8>, String> {
849
1046
  module.functions[slot].1 = body.clone();
850
1047
  }
851
1048
 
1049
+ // Flush every staged string literal into the module in the SAME order they were
1050
+ // staged — `compileStaticString` already baked each one's index (its position in
1051
+ // `state.passive_segments` at staging time) into an `array.new_data` instruction,
1052
+ // so that order must be preserved exactly for those indices to still be correct.
852
- for (offset, bytes) in &state.data_segments {
1053
+ for bytes in &state.passive_segments {
853
- module.addDataSegment(*offset, bytes);
1054
+ module.addPassiveDataSegment(bytes);
854
1055
  }
855
1056
 
856
1057
  if let Some(&main_idx) = ctx.func_ids.get("main") {
@@ -860,110 +1061,59 @@ pub fn compileSource(source: &ast::Source) -> Result<Vec<u8>, String> {
860
1061
  Ok(module.finish())
861
1062
  }
862
1063
 
863
- /// Registers `__string_concat(a: i32, b: i32) -> i32`, a hand-written runtime
1064
+ /// Registers `__string_concat(a: ref Str, b: ref Str) -> ref Str`, a hand-written
864
- /// helper backing string interpolation: bump-allocates a new length-prefixed
1065
+ /// runtime helper backing string interpolation. `Str` is a wasm-gc `array<i8>`,
865
- /// string (`[len: u32][bytes]`, matching a literal string's own layout) exactly
866
- /// long enough for `a`'s bytes followed by `b`'s, and copies them in. There's no
1066
+ /// which tracks its own length (`array.len`) building the concatenation is:
867
- /// bulk-memory `memory.copy` assumed available, so each byte is copied by an
1067
+ /// allocate a new array sized `len(a) + len(b)`, then two `array.copy`s (a
868
- /// explicit loop.
1068
+ /// whole-array-in-one-instruction bulk copy).
869
- fn registerStringConcatHelper(module: &mut WasmModule, bump_global: u32) -> u32 {
1069
+ fn registerStringConcatHelper(module: &mut WasmModule, str_type_idx: u32) -> u32 {
1070
+ let str_ref = gcRef(str_type_idx);
870
- let type_idx = module.addType(&[ValType::I32, ValType::I32], &[ValType::I32]);
1071
+ let type_idx = module.addType(&[str_ref, str_ref], &[str_ref]);
871
1072
 
872
- // locals: 0=a (param), 1=b (param), 2=len_a, 3=len_b, 4=result, 5=i
1073
+ // locals: 0=a (param), 1=b (param), 2=len_a, 3=len_b, 4=result
873
1074
  const A: u32 = 0;
874
1075
  const B: u32 = 1;
875
1076
  const LEN_A: u32 = 2;
876
1077
  const LEN_B: u32 = 3;
877
1078
  const RESULT: u32 = 4;
878
- const I: u32 = 5;
879
1079
 
880
1080
  let mut body = Vec::new();
881
- body.extend(encodeLeb128U32(1)); // one locals group
1081
+ body.extend(encodeLeb128U32(2)); // two locals groups
882
- body.extend(encodeLeb128U32(4)); // 4 locals (len_a, len_b, result, i)
1082
+ body.extend(encodeLeb128U32(2)); // len_a, len_b: i32
883
1083
  ValType::I32.encode(&mut body);
1084
+ body.extend(encodeLeb128U32(1)); // result: ref
1085
+ str_ref.encode(&mut body);
884
1086
 
885
- // len_a = load(a); len_b = load(b)
1087
+ // len_a = array.len(a); len_b = array.len(b)
886
1088
  Instruction::LocalGet(A).encode(&mut body);
887
- Instruction::I32Load(MemArg { offset: 0, align: 2, memory_index: 0 }).encode(&mut body);
1089
+ Instruction::ArrayLen.encode(&mut body);
888
1090
  Instruction::LocalSet(LEN_A).encode(&mut body);
889
1091
  Instruction::LocalGet(B).encode(&mut body);
890
- Instruction::I32Load(MemArg { offset: 0, align: 2, memory_index: 0 }).encode(&mut body);
1092
+ Instruction::ArrayLen.encode(&mut body);
891
1093
  Instruction::LocalSet(LEN_B).encode(&mut body);
892
1094
 
893
- // result = bump; bump += 4 + len_a + len_b
1095
+ // result = array.new_default(str_type_idx, len_a + len_b)
894
- Instruction::GlobalGet(bump_global).encode(&mut body);
895
- Instruction::LocalSet(RESULT).encode(&mut body);
896
- Instruction::GlobalGet(bump_global).encode(&mut body);
897
1096
  Instruction::LocalGet(LEN_A).encode(&mut body);
898
1097
  Instruction::LocalGet(LEN_B).encode(&mut body);
899
1098
  Instruction::I32Add.encode(&mut body);
900
- Instruction::I32Const(4).encode(&mut body);
1099
+ Instruction::ArrayNewDefault(str_type_idx).encode(&mut body);
901
- Instruction::I32Add.encode(&mut body);
902
- Instruction::I32Add.encode(&mut body);
903
- Instruction::GlobalSet(bump_global).encode(&mut body);
1100
+ Instruction::LocalSet(RESULT).encode(&mut body);
904
1101
 
905
- // store combined length at result+0
1102
+ // array.copy(dst: result, dst_offset: 0, src: a, src_offset: 0, len: len_a)
906
1103
  Instruction::LocalGet(RESULT).encode(&mut body);
907
- Instruction::LocalGet(LEN_A).encode(&mut body);
908
- Instruction::LocalGet(LEN_B).encode(&mut body);
909
- Instruction::I32Add.encode(&mut body);
910
- Instruction::I32Store(MemArg { offset: 0, align: 2, memory_index: 0 }).encode(&mut body);
911
-
912
- // for (i = 0; i < len_a; i++) result[4+i] = a[4+i]
913
1104
  Instruction::I32Const(0).encode(&mut body);
914
- Instruction::LocalSet(I).encode(&mut body);
915
- Instruction::Block(BlockType::Empty).encode(&mut body);
916
- Instruction::Loop(BlockType::Empty).encode(&mut body);
917
- Instruction::LocalGet(I).encode(&mut body);
918
- Instruction::LocalGet(LEN_A).encode(&mut body);
919
- Instruction::I32GeU.encode(&mut body);
920
- Instruction::BrIf(1).encode(&mut body);
921
- // address = result + i
922
- Instruction::LocalGet(RESULT).encode(&mut body);
923
- Instruction::LocalGet(I).encode(&mut body);
924
- Instruction::I32Add.encode(&mut body);
925
- // value = load8u(a + i, offset 4)
926
1105
  Instruction::LocalGet(A).encode(&mut body);
927
- Instruction::LocalGet(I).encode(&mut body);
928
- Instruction::I32Add.encode(&mut body);
929
- Instruction::I32Load8U(MemArg { offset: 4, align: 0, memory_index: 0 }).encode(&mut body);
930
- Instruction::I32Store8(MemArg { offset: 4, align: 0, memory_index: 0 }).encode(&mut body);
931
- Instruction::LocalGet(I).encode(&mut body);
932
- Instruction::I32Const(1).encode(&mut body);
933
- Instruction::I32Add.encode(&mut body);
934
- Instruction::LocalSet(I).encode(&mut body);
935
- Instruction::Br(0).encode(&mut body);
936
- Instruction::End.encode(&mut body);
937
- Instruction::End.encode(&mut body);
938
-
939
- // for (i = 0; i < len_b; i++) result[4+len_a+i] = b[4+i]
940
1106
  Instruction::I32Const(0).encode(&mut body);
941
- Instruction::LocalSet(I).encode(&mut body);
942
- Instruction::Block(BlockType::Empty).encode(&mut body);
943
- Instruction::Loop(BlockType::Empty).encode(&mut body);
944
- Instruction::LocalGet(I).encode(&mut body);
945
- Instruction::LocalGet(LEN_B).encode(&mut body);
1107
+ Instruction::LocalGet(LEN_A).encode(&mut body);
946
- Instruction::I32GeU.encode(&mut body);
947
- Instruction::BrIf(1).encode(&mut body);
1108
+ Instruction::ArrayCopy { array_type_index_dst: str_type_idx, array_type_index_src: str_type_idx }.encode(&mut body);
1109
+
948
- // address = result + len_a + i
1110
+ // array.copy(dst: result, dst_offset: len_a, src: b, src_offset: 0, len: len_b)
949
1111
  Instruction::LocalGet(RESULT).encode(&mut body);
950
1112
  Instruction::LocalGet(LEN_A).encode(&mut body);
951
- Instruction::I32Add.encode(&mut body);
952
- Instruction::LocalGet(I).encode(&mut body);
953
- Instruction::I32Add.encode(&mut body);
954
- // value = load8u(b + i, offset 4)
955
1113
  Instruction::LocalGet(B).encode(&mut body);
956
- Instruction::LocalGet(I).encode(&mut body);
957
- Instruction::I32Add.encode(&mut body);
958
- Instruction::I32Load8U(MemArg { offset: 4, align: 0, memory_index: 0 }).encode(&mut body);
959
- Instruction::I32Store8(MemArg { offset: 4, align: 0, memory_index: 0 }).encode(&mut body);
960
- Instruction::LocalGet(I).encode(&mut body);
961
- Instruction::I32Const(1).encode(&mut body);
1114
+ Instruction::I32Const(0).encode(&mut body);
962
- Instruction::I32Add.encode(&mut body);
963
- Instruction::LocalSet(I).encode(&mut body);
1115
+ Instruction::LocalGet(LEN_B).encode(&mut body);
964
- Instruction::Br(0).encode(&mut body);
1116
+ Instruction::ArrayCopy { array_type_index_dst: str_type_idx, array_type_index_src: str_type_idx }.encode(&mut body);
965
- Instruction::End.encode(&mut body);
966
- Instruction::End.encode(&mut body);
967
1117
 
968
1118
  Instruction::LocalGet(RESULT).encode(&mut body);
969
1119
  Instruction::End.encode(&mut body);
@@ -971,31 +1121,34 @@ fn registerStringConcatHelper(module: &mut WasmModule, bump_global: u32) -> u32
971
1121
  module.addFunction(type_idx, &body)
972
1122
  }
973
1123
 
974
- /// Registers `__int_to_string(n: i64) -> i32`, a hand-written runtime helper
1124
+ /// Registers `__int_to_string(n: i64) -> ref Str`, a hand-written runtime helper
975
- /// backing string interpolation: bump-allocates a new length-prefixed string
1125
+ /// backing string interpolation: allocates a new `array<i8>` holding `n`'s decimal
976
- /// holding `n`'s decimal representation (handling a leading `-` for negatives, and
1126
+ /// representation (handling a leading `-` for negatives, and `0` correctly via a
977
- /// `0` correctly via a do-while digit count that always runs at least once).
1127
+ /// do-while digit count that always runs at least once).
978
- fn registerIntToStringHelper(module: &mut WasmModule, bump_global: u32) -> u32 {
1128
+ fn registerIntToStringHelper(module: &mut WasmModule, str_type_idx: u32) -> u32 {
1129
+ let str_ref = gcRef(str_type_idx);
979
- let type_idx = module.addType(&[ValType::I64], &[ValType::I32]);
1130
+ let type_idx = module.addType(&[ValType::I64], &[str_ref]);
980
1131
 
981
- // locals: 0=n (param, i64), 1=is_neg (i32), 2=abs_n (i64), 3=count (i32),
1132
+ // locals: 0=n (param, i64), 1=is_neg (i32), 2=count (i32), 3=total_len (i32),
982
- // 4=total_len (i32), 5=result (i32), 6=pos (i32), 7=temp (i64)
1133
+ // 4=pos (i32), 5=result (ref), 6=abs_n (i64), 7=temp (i64)
983
- // Locals are declared as one group of 5 `i32`s followed by one group of 2
1134
+ // Locals are declared as one group of 4 `i32`s, one group of 1 `ref`, then one
984
- // `i64`s (see below), so indices must stay grouped by type in that same order —
1135
+ // group of 2 `i64`s (see below), so indices must stay grouped by type in that
985
- // NOT in whatever order reads best logically.
1136
+ // same order — NOT in whatever order reads best logically.
986
1137
  const N: u32 = 0;
987
1138
  const IS_NEG: u32 = 1;
988
1139
  const COUNT: u32 = 2;
989
1140
  const TOTAL_LEN: u32 = 3;
1141
+ const POS: u32 = 4;
990
- const RESULT: u32 = 4;
1142
+ const RESULT: u32 = 5;
991
- const POS: u32 = 5;
992
1143
  const ABS_N: u32 = 6;
993
1144
  const TEMP: u32 = 7;
994
1145
 
995
1146
  let mut body = Vec::new();
996
- body.extend(encodeLeb128U32(2)); // two locals groups
1147
+ body.extend(encodeLeb128U32(3)); // three locals groups
997
- body.extend(encodeLeb128U32(5)); // is_neg, count, total_len, result, pos: i32
1148
+ body.extend(encodeLeb128U32(4)); // is_neg, count, total_len, pos: i32
998
1149
  ValType::I32.encode(&mut body);
1150
+ body.extend(encodeLeb128U32(1)); // result: ref
1151
+ str_ref.encode(&mut body);
999
1152
  body.extend(encodeLeb128U32(2)); // abs_n, temp: i64
1000
1153
  ValType::I64.encode(&mut body);
1001
1154
 
@@ -1042,39 +1195,27 @@ fn registerIntToStringHelper(module: &mut WasmModule, bump_global: u32) -> u32 {
1042
1195
  Instruction::I32Add.encode(&mut body);
1043
1196
  Instruction::LocalSet(TOTAL_LEN).encode(&mut body);
1044
1197
 
1198
+ // result = array.new_default(str_type_idx, total_len) — no length prefix needed,
1045
- // result = bump; bump += 4 + total_len
1199
+ // `array.len` reads it back natively.
1046
- Instruction::GlobalGet(bump_global).encode(&mut body);
1047
- Instruction::LocalSet(RESULT).encode(&mut body);
1048
- Instruction::GlobalGet(bump_global).encode(&mut body);
1049
- Instruction::LocalGet(TOTAL_LEN).encode(&mut body);
1050
- Instruction::I32Const(4).encode(&mut body);
1051
- Instruction::I32Add.encode(&mut body);
1052
- Instruction::I32Add.encode(&mut body);
1053
- Instruction::GlobalSet(bump_global).encode(&mut body);
1054
-
1055
- // store total_len at result+0
1056
- Instruction::LocalGet(RESULT).encode(&mut body);
1057
1200
  Instruction::LocalGet(TOTAL_LEN).encode(&mut body);
1058
- Instruction::I32Store(MemArg { offset: 0, align: 2, memory_index: 0 }).encode(&mut body);
1201
+ Instruction::ArrayNewDefault(str_type_idx).encode(&mut body);
1202
+ Instruction::LocalSet(RESULT).encode(&mut body);
1059
1203
 
1060
- // pos = 4 + total_len; temp = abs_n
1204
+ // pos = total_len; temp = abs_n
1061
- Instruction::I32Const(4).encode(&mut body);
1062
1205
  Instruction::LocalGet(TOTAL_LEN).encode(&mut body);
1063
- Instruction::I32Add.encode(&mut body);
1064
1206
  Instruction::LocalSet(POS).encode(&mut body);
1065
1207
  Instruction::LocalGet(ABS_N).encode(&mut body);
1066
1208
  Instruction::LocalSet(TEMP).encode(&mut body);
1067
1209
 
1068
- // do { pos--; result[pos] = '0' + temp % 10; temp /= 10 } while (pos > 4 + is_neg)
1210
+ // do { pos--; result[pos] = '0' + temp % 10; temp /= 10 } while (pos > is_neg)
1069
1211
  Instruction::Loop(BlockType::Empty).encode(&mut body);
1070
1212
  Instruction::LocalGet(POS).encode(&mut body);
1071
1213
  Instruction::I32Const(1).encode(&mut body);
1072
1214
  Instruction::I32Sub.encode(&mut body);
1073
1215
  Instruction::LocalSet(POS).encode(&mut body);
1074
- // address = result + pos
1216
+ // array.set(result, pos, value) stack order [array_ref, index, value]
1075
1217
  Instruction::LocalGet(RESULT).encode(&mut body);
1076
1218
  Instruction::LocalGet(POS).encode(&mut body);
1077
- Instruction::I32Add.encode(&mut body);
1078
1219
  // value = '0' + (temp % 10)
1079
1220
  Instruction::LocalGet(TEMP).encode(&mut body);
1080
1221
  Instruction::I64Const(10).encode(&mut body);
@@ -1082,29 +1223,26 @@ fn registerIntToStringHelper(module: &mut WasmModule, bump_global: u32) -> u32 {
1082
1223
  Instruction::I64Const(48).encode(&mut body);
1083
1224
  Instruction::I64Add.encode(&mut body);
1084
1225
  Instruction::I32WrapI64.encode(&mut body);
1085
- Instruction::I32Store8(MemArg { offset: 0, align: 0, memory_index: 0 }).encode(&mut body);
1226
+ Instruction::ArraySet(str_type_idx).encode(&mut body);
1086
1227
  // temp /= 10
1087
1228
  Instruction::LocalGet(TEMP).encode(&mut body);
1088
1229
  Instruction::I64Const(10).encode(&mut body);
1089
1230
  Instruction::I64DivS.encode(&mut body);
1090
1231
  Instruction::LocalSet(TEMP).encode(&mut body);
1091
- // while (pos > 4 + is_neg)
1232
+ // while (pos > is_neg)
1092
1233
  Instruction::LocalGet(POS).encode(&mut body);
1093
- Instruction::I32Const(4).encode(&mut body);
1094
1234
  Instruction::LocalGet(IS_NEG).encode(&mut body);
1095
- Instruction::I32Add.encode(&mut body);
1096
1235
  Instruction::I32GtU.encode(&mut body);
1097
1236
  Instruction::BrIf(0).encode(&mut body);
1098
1237
  Instruction::End.encode(&mut body);
1099
1238
 
1100
- // if (is_neg) result[4] = '-'
1239
+ // if (is_neg) result[0] = '-'
1101
1240
  Instruction::LocalGet(IS_NEG).encode(&mut body);
1102
1241
  Instruction::If(BlockType::Empty).encode(&mut body);
1103
1242
  Instruction::LocalGet(RESULT).encode(&mut body);
1104
- Instruction::I32Const(4).encode(&mut body);
1243
+ Instruction::I32Const(0).encode(&mut body);
1105
- Instruction::I32Add.encode(&mut body);
1106
1244
  Instruction::I32Const(45).encode(&mut body); // '-'
1107
- Instruction::I32Store8(MemArg { offset: 0, align: 0, memory_index: 0 }).encode(&mut body);
1245
+ Instruction::ArraySet(str_type_idx).encode(&mut body);
1108
1246
  Instruction::End.encode(&mut body);
1109
1247
 
1110
1248
  Instruction::LocalGet(RESULT).encode(&mut body);
@@ -1757,18 +1895,22 @@ struct Collector<'a> {
1757
1895
  cctx: plum_checker::CheckCtx<'a>,
1758
1896
  named: Vec<(String, PlumType)>,
1759
1897
  named_set: std::collections::HashSet<String>,
1760
- classcall_scratch: HashMap<usize, u32>,
1761
1898
  /// One scratch-local type per subject (usually one, more for `match a, b, ...`).
1762
1899
  match_scratch: HashMap<usize, Vec<PlumType>>,
1763
- next_classcall_slot: u32,
1764
- /// `Closure` expr identity -> offset of its first (of two) construction scratch slots.
1765
- closure_scratch: HashMap<usize, u32>,
1766
- next_closure_slot: u32,
1767
- /// `CasePattern::Class` identity -> its scratch local, for a constructor pattern
1900
+ /// `CasePattern::Class` identity -> its scratch local. Covers EVERY constructor
1901
+ /// pattern, including top-level ones — under wasm-gc, matching `Some(v)` needs a
1902
+ /// `ref.cast` from the subject's static supertype down to the concrete variant
1903
+ /// type before any `struct.get` on it validates, and that narrowed value needs
1904
+ /// its OWN local (declared with the concrete variant's ref type) distinct from
1905
+ /// the original wide-typed subject local, which stays declared at the
1906
+ /// supertype's type for the whole function. (Before wasm-gc, this only covered
1768
- /// *nested* inside another constructor pattern's fields (`Some(Some(v))`) the
1907
+ /// patterns nested inside another constructor pattern's fields, depth >= 1,
1769
- /// outermost, per-subject pattern already has a scratch local via `match_scratch`
1908
+ /// since a plain i32 pointer needed no per-pattern static type at all.)
1770
- /// / `all_subjects`, so this only covers depth >= 1.
1771
1909
  nested_class_scratch: HashMap<usize, u32>,
1910
+ /// Slot number (the `u32` values in `nested_class_scratch`) -> the variant name
1911
+ /// it narrows to, so its scratch local can be declared with that variant's exact
1912
+ /// concrete ref type instead of a uniform placeholder type.
1913
+ nested_class_scratch_types: Vec<String>,
1772
1914
  next_nested_class_slot: u32,
1773
1915
  /// `For` stmt identity (pointer address) -> a slot number; each slot reserves 2
1774
1916
  /// consecutive `i32` scratch locals for variadic iteration (`for v in nums`):
@@ -1791,7 +1933,7 @@ impl<'a> Collector<'a> {
1791
1933
  /// its correct field type, and reserves a scratch local for every `Class`
1792
1934
  /// sub-pattern found *nested* inside another constructor pattern's fields (the
1793
1935
  /// outermost, per-subject pattern doesn't need one — see `nested_class_scratch`).
1794
- fn collectPattern(&mut self, pat: &ast::CasePattern, ty: &PlumType, top_level: bool) {
1936
+ fn collectPattern(&mut self, pat: &ast::CasePattern, ty: &PlumType) {
1795
1937
  match pat {
1796
1938
  ast::CasePattern::Name(n) => {
1797
1939
  let is_variant = n.chars().next().map(|c| c.is_uppercase()).unwrap_or(false)
@@ -1801,16 +1943,15 @@ impl<'a> Collector<'a> {
1801
1943
  }
1802
1944
  }
1803
1945
  ast::CasePattern::Class { name, fields } => {
1804
- if !top_level {
1805
- let key = pat as *const ast::CasePattern as usize;
1946
+ let key = pat as *const ast::CasePattern as usize;
1806
- let slot = self.next_nested_class_slot;
1947
+ let slot = self.next_nested_class_slot;
1807
- self.next_nested_class_slot += 1;
1948
+ self.next_nested_class_slot += 1;
1808
- self.nested_class_scratch.insert(key, slot);
1949
+ self.nested_class_scratch.insert(key, slot);
1809
- }
1950
+ self.nested_class_scratch_types.push(name.clone());
1810
1951
  if let Some(info) = self.cctx.enum_variants.get(name) {
1811
1952
  let field_types = info.field_types.clone();
1812
1953
  for (f, fty) in fields.iter().zip(field_types.iter()) {
1813
- self.collectPattern(f, fty, false);
1954
+ self.collectPattern(f, fty);
1814
1955
  }
1815
1956
  }
1816
1957
  }
@@ -1900,7 +2041,7 @@ impl<'a> Collector<'a> {
1900
2041
  for case in &m.cases {
1901
2042
  let saved = self.env.clone();
1902
2043
  for (pat, subject_ty) in case.patterns.iter().zip(subject_types.iter()) {
1903
- self.collectPattern(pat, subject_ty, true);
2044
+ self.collectPattern(pat, subject_ty);
1904
2045
  }
1905
2046
  self.walkBlock(&case.body);
1906
2047
  self.env = saved;
@@ -1913,9 +2054,6 @@ impl<'a> Collector<'a> {
1913
2054
  fn walkExpr(&mut self, expr: &ast::Expr) {
1914
2055
  match expr {
1915
2056
  ast::Expr::ClassCall(call) => {
1916
- let idx = self.next_classcall_slot;
1917
- self.next_classcall_slot += 1;
1918
- self.classcall_scratch.insert(expr as *const ast::Expr as usize, idx);
1919
2057
  for fa in &call.fields {
1920
2058
  self.walkExpr(&fa.value);
1921
2059
  }
@@ -1941,18 +2079,6 @@ impl<'a> Collector<'a> {
1941
2079
  self.walkExpr(&t.else_);
1942
2080
  }
1943
2081
  ast::Expr::FnCall(call) => {
1944
- let carries_payload = self.cctx.enum_variants.get(&call.name)
1945
- .map(|info| !info.field_types.is_empty())
1946
- .unwrap_or(false);
1947
- let is_variadic_call = matches!(
1948
- plum_checker::lookup(&self.env, &call.name),
1949
- Ok(PlumType::TFun(params, _)) if matches!(params.last(), Some(PlumType::TVariadic(_)))
1950
- );
1951
- if carries_payload || is_variadic_call {
1952
- let idx = self.next_classcall_slot;
1953
- self.next_classcall_slot += 1;
1954
- self.classcall_scratch.insert(expr as *const ast::Expr as usize, idx);
1955
- }
1956
2082
  for arg in &call.args {
1957
2083
  self.walkArg(arg);
1958
2084
  }
@@ -1965,34 +2091,15 @@ impl<'a> Collector<'a> {
1965
2091
  }
1966
2092
  }
1967
2093
  }
1968
- ast::Expr::TypeName(n) => {
2094
+ ast::Expr::TypeName(_) => {}
1969
- // A bare discriminant-variant reference (e.g. `READ_MIN_OCCURANCES`) heap-
1970
- // allocates via the SAME `compileVariantConstruction` path a payload-carrying
1971
- // `FnCall` variant construction uses (see the `Expr::TypeName` arm in
1972
- // `compileExpr`), so it needs the same scratch slot registered here.
1973
- let carries_baked_in_payload = self.cctx.enum_variants.get(n)
1974
- .map(|info| !info.values.is_empty())
1975
- .unwrap_or(false);
1976
- if carries_baked_in_payload {
1977
- let idx = self.next_classcall_slot;
1978
- self.next_classcall_slot += 1;
1979
- self.classcall_scratch.insert(expr as *const ast::Expr as usize, idx);
1980
- }
1981
- }
1982
2095
  ast::Expr::Int(_)
1983
2096
  | ast::Expr::Float(_)
1984
2097
  | ast::Expr::String(_)
1985
2098
  | ast::Expr::Self_
1986
2099
  | ast::Expr::Var(_) => {}
1987
- // A closure literal needs two construction scratch slots in the *enclosing*
2100
+ // A closure literal's body has its own locals, belonging to the separate
1988
- // function (env struct ptr, closure struct ptr). Its body's own locals are
1989
- // NOT this function's they belong to the separate closure function so we
2101
+ // closure function it compiles to nothing to recurse into here.
1990
- // don't recurse into the body here.
1991
- ast::Expr::Closure(cl) => {
2102
+ ast::Expr::Closure(_) => {}
1992
- let base = self.next_closure_slot;
1993
- self.next_closure_slot += 2;
1994
- self.closure_scratch.insert(cl.as_ref() as *const ast::Closure as usize, base);
1995
- }
1996
2103
  }
1997
2104
  }
1998
2105
 
@@ -2030,12 +2137,9 @@ fn compileFnBody(f: &ast::Fn, ctx: &CompileCtx, state: &mut ModuleState) -> Resu
2030
2137
  cctx: checkCtxOf(&ctx.classes, &ctx.methods, &ctx.enum_variants, &ctx.enum_params),
2031
2138
  named: Vec::new(),
2032
2139
  named_set: Default::default(),
2033
- classcall_scratch: HashMap::new(),
2034
2140
  match_scratch: HashMap::new(),
2035
- next_classcall_slot: 0,
2036
- closure_scratch: HashMap::new(),
2037
- next_closure_slot: 0,
2038
2141
  nested_class_scratch: HashMap::new(),
2142
+ nested_class_scratch_types: Vec::new(),
2039
2143
  next_nested_class_slot: 0,
2040
2144
  variadic_for_scratch: HashMap::new(),
2041
2145
  next_variadic_for_slot: 0,
@@ -2067,13 +2171,6 @@ fn compileFnBody(f: &ast::Fn, ctx: &CompileCtx, state: &mut ModuleState) -> Resu
2067
2171
  idx += 1;
2068
2172
  }
2069
2173
 
2070
- let classcall_scratch_base = idx;
2071
- let classcall_count = collector.classcall_scratch.values().copied().max().map(|m| m + 1).unwrap_or(0);
2072
- for _ in 0..classcall_count {
2073
- groups.push(ValType::I32);
2074
- idx += 1;
2075
- }
2076
-
2077
2174
  let match_scratch_base = idx;
2078
2175
  let mut match_scratch_index: HashMap<usize, u32> = HashMap::new();
2079
2176
  for (ptr, types) in collector.match_scratch.iter() {
@@ -2085,9 +2182,14 @@ fn compileFnBody(f: &ast::Fn, ctx: &CompileCtx, state: &mut ModuleState) -> Resu
2085
2182
  }
2086
2183
 
2087
2184
  let nested_class_scratch_base = idx;
2185
+ // Each slot is declared with its OWN concrete variant ref type (not a uniform
2186
+ // placeholder) — `struct.get` on a constructor-pattern match requires the local
2088
- let nested_class_scratch_count = collector.nested_class_scratch.values().copied().max().map(|m| m + 1).unwrap_or(0);
2187
+ // holding the narrowed (`ref.cast`) value to be statically typed as that exact
2188
+ // variant, and different slots very likely narrow to different variants.
2089
- for _ in 0..nested_class_scratch_count {
2189
+ for vname in &collector.nested_class_scratch_types {
2190
+ let variant_idx = withGcTypes(|r| *r.variant_type_idx.get(vname)
2191
+ .unwrap_or_else(|| panic!("internal codegen error: variant '{}' missing from the GC type registry", vname)));
2090
- groups.push(ValType::I32);
2192
+ groups.push(gcRef(variant_idx));
2091
2193
  idx += 1;
2092
2194
  }
2093
2195
 
@@ -2099,12 +2201,6 @@ fn compileFnBody(f: &ast::Fn, ctx: &CompileCtx, state: &mut ModuleState) -> Resu
2099
2201
  idx += 2;
2100
2202
  }
2101
2203
 
2102
- let closure_scratch_base = idx;
2103
- let closure_scratch_count = collector.closure_scratch.values().copied().max().map(|m| m + 2).unwrap_or(0);
2104
- for _ in 0..closure_scratch_count {
2105
- groups.push(ValType::I32);
2106
- }
2107
-
2108
2204
  if groups.is_empty() {
2109
2205
  body.push(0);
2110
2206
  } else {
@@ -2117,12 +2213,8 @@ fn compileFnBody(f: &ast::Fn, ctx: &CompileCtx, state: &mut ModuleState) -> Resu
2117
2213
 
2118
2214
  let local_ctx = LocalCtx {
2119
2215
  locals,
2120
- classcall_scratch_base,
2121
- classcall_scratch: collector.classcall_scratch,
2122
2216
  match_scratch_base,
2123
2217
  match_scratch_index,
2124
- closure_scratch_base,
2125
- closure_scratch: collector.closure_scratch,
2126
2218
  nested_class_scratch_base,
2127
2219
  nested_class_scratch: collector.nested_class_scratch,
2128
2220
  variadic_for_scratch_base,
@@ -2138,9 +2230,10 @@ fn compileFnBody(f: &ast::Fn, ctx: &CompileCtx, state: &mut ModuleState) -> Resu
2138
2230
  methods: &ctx.methods,
2139
2231
  enum_variants: &ctx.enum_variants,
2140
2232
  enum_params: &ctx.enum_params,
2233
+ gc_types: &ctx.gc_types,
2234
+ singleton_globals: &ctx.singleton_globals,
2141
2235
  type_env: RefCell::new(base_env),
2142
2236
  closure_local_sigs: RefCell::new(HashMap::new()),
2143
- bump_global: ctx.bump_global,
2144
2237
  };
2145
2238
 
2146
2239
  let result_vt = retTypeToWasm(f.returns.as_ref());
@@ -2228,6 +2321,35 @@ fn compileStmtInValuePosition(
2228
2321
  /// (each branch is `BlockType::Empty`, nothing left on the stack) or `Some(vt)` when this
2229
2322
  /// `if` is in value position — every branch must then leave a `vt` value on the stack, which
2230
2323
  /// requires an `else` (a value can't be produced on a path that doesn't exist).
2324
+ /// Compiles a `Bool`-typed expression, then a `ref.test` against the `True` variant's
2325
+ /// concrete type, leaving a plain `i32` (1/0) on the stack. Every place a `Bool` value
2326
+ /// drives wasm's OWN native control flow (`if`/`br_if`, which require a raw `i32`
2327
+ /// condition, not a `ref`) goes through this — see this migration plan's Decision 1
2328
+ /// (Bool is a full wasm-gc struct, no special-casing).
2329
+ fn compileBoolConditionAsI32(expr: &ast::Expr, body: &mut Vec<u8>, ctx: &LocalCtx, state: &mut ModuleState) -> Result<(), String> {
2330
+ compileExpr(expr, body, ctx, state)?;
2331
+ let true_idx = *ctx.gc_types.variant_type_idx.get("True")
2332
+ .expect("internal codegen error: True must be registered in the GC type registry");
2333
+ Instruction::RefTestNonNull(HeapType::Concrete(true_idx)).encode(body);
2334
+ Ok(())
2335
+ }
2336
+
2337
+ /// Given an `i32` boolean (1/0) already on the stack, converts it into a `Bool` ref by
2338
+ /// selecting the pre-allocated `True`/`False` singleton (this migration plan's
2339
+ /// Decision 2) — the reverse of `compileBoolConditionAsI32`. Used wherever a native
2340
+ /// wasm comparison/logical-op instruction just left a raw `i32` predicate on the
2341
+ /// stack that needs to become a proper `Bool` value.
2342
+ fn pushBoolRefFromI32Flag(body: &mut Vec<u8>, ctx: &LocalCtx) {
2343
+ let bool_ref_ty = plumTypeToValtype(&PlumType::TBool);
2344
+ let true_global = *ctx.singleton_globals.get("True").expect("internal codegen error: True singleton global missing");
2345
+ let false_global = *ctx.singleton_globals.get("False").expect("internal codegen error: False singleton global missing");
2346
+ Instruction::If(BlockType::Result(bool_ref_ty)).encode(body);
2347
+ Instruction::GlobalGet(true_global).encode(body);
2348
+ Instruction::Else.encode(body);
2349
+ Instruction::GlobalGet(false_global).encode(body);
2350
+ Instruction::End.encode(body);
2351
+ }
2352
+
2231
2353
  fn compileIf(
2232
2354
  if_: &ast::If,
2233
2355
  result_vt: Option<ValType>,
@@ -2241,13 +2363,13 @@ fn compileIf(
2241
2363
  );
2242
2364
  }
2243
2365
  let bt = blockTypeFor(result_vt);
2244
- compileExpr(&if_.condition, body, ctx, state)?;
2366
+ compileBoolConditionAsI32(&if_.condition, body, ctx, state)?;
2245
2367
  Instruction::If(bt).encode(body);
2246
2368
  compileCaseBody(&if_.body, result_vt, body, ctx, state)?;
2247
2369
  if !if_.else_ifs.is_empty() || if_.else_.is_some() {
2248
2370
  Instruction::Else.encode(body);
2249
2371
  for ei in &if_.else_ifs {
2250
- compileExpr(&ei.condition, body, ctx, state)?;
2372
+ compileBoolConditionAsI32(&ei.condition, body, ctx, state)?;
2251
2373
  Instruction::If(bt).encode(body);
2252
2374
  compileCaseBody(&ei.body, result_vt, body, ctx, state)?;
2253
2375
  Instruction::Else.encode(body);
@@ -2309,7 +2431,7 @@ fn compileStmt(stmt: &ast::Stmt, body: &mut Vec<u8>, ctx: &LocalCtx, state: &mut
2309
2431
  if let ast::Expr::Closure(cl) = value {
2310
2432
  let key = cl.as_ref() as *const ast::Closure as usize;
2311
2433
  if let Some(info) = ctx.closures.get(&key) {
2312
- let mut sig_params = vec![ValType::I32];
2434
+ let mut sig_params = vec![ValType::Ref(RefType::ANYREF)];
2313
2435
  sig_params.extend(info.param_vts.iter().copied());
2314
2436
  ctx.closure_local_sigs.borrow_mut().insert(name.clone(), (sig_params, info.ret_vt));
2315
2437
  }
@@ -2325,19 +2447,18 @@ fn compileStmt(stmt: &ast::Stmt, body: &mut Vec<u8>, ctx: &LocalCtx, state: &mut
2325
2447
  .classes
2326
2448
  .get(&class_name)
2327
2449
  .ok_or_else(|| format!("codegen: unknown class '{}'", class_name))?;
2328
- let (field_idx, field_ty) = fields
2450
+ let field_idx = fields
2329
2451
  .iter()
2330
2452
  .position(|(n, _)| n == field_name)
2331
- .map(|i| (i, fields[i].1.clone()))
2332
2453
  .ok_or_else(|| format!("codegen: no field '{}' on class '{}'", field_name, class_name))?;
2454
+ let class_type_idx = *ctx.gc_types.class_type_idx.get(&class_name)
2455
+ .ok_or_else(|| format!("codegen: class '{}' missing from the GC type registry", class_name))?;
2456
+ // struct.set expects [(ref null $t) value] on the stack (ref
2457
+ // pushed first/deeper, value second/on top) — same push order
2458
+ // this already used for the old memory store.
2333
2459
  compileExpr(object, body, ctx, state)?;
2334
2460
  compileExpr(value, body, ctx, state)?;
2335
- let offset = (field_idx as u64) * 8;
2336
- match plumTypeToValtype(&field_ty) {
2337
- ValType::I64 => Instruction::I64Store(MemArg { offset, align: 3, memory_index: 0 }).encode(body),
2338
- ValType::F64 => Instruction::F64Store(MemArg { offset, align: 3, memory_index: 0 }).encode(body),
2339
- _ => Instruction::I32Store(MemArg { offset, align: 2, memory_index: 0 }).encode(body),
2461
+ Instruction::StructSet { struct_type_index: class_type_idx, field_index: field_idx as u32 }.encode(body);
2340
- };
2341
2462
  }
2342
2463
  }
2343
2464
  }
@@ -2355,7 +2476,7 @@ fn compileStmt(stmt: &ast::Stmt, body: &mut Vec<u8>, ctx: &LocalCtx, state: &mut
2355
2476
  ast::Stmt::While(w) => {
2356
2477
  Instruction::Block(BlockType::Empty).encode(body);
2357
2478
  Instruction::Loop(BlockType::Empty).encode(body);
2358
- compileExpr(&w.condition, body, ctx, state)?;
2479
+ compileBoolConditionAsI32(&w.condition, body, ctx, state)?;
2359
2480
  Instruction::I32Eqz.encode(body);
2360
2481
  Instruction::BrIf(1).encode(body);
2361
2482
  compileBlock(&w.body, body, ctx, state)?;
@@ -2412,11 +2533,15 @@ fn compileStmt(stmt: &ast::Stmt, body: &mut Vec<u8>, ctx: &LocalCtx, state: &mut
2412
2533
  let count_local = ctx.variadic_for_scratch_base + slot * 2;
2413
2534
  let index_local = count_local + 1;
2414
2535
  let elem_vt = plumTypeToValtype(&elem_ty);
2536
+ let array_type_idx = *ctx
2537
+ .gc_types
2538
+ .variadic_array_type_idx
2539
+ .get(&elem_vt)
2540
+ .ok_or_else(|| "internal codegen error: no variadic array type registered for this elem type".to_string())?;
2415
2541
 
2416
- // count_local = i32.wrap_i64(load_i64([nums + 0]))
2542
+ // count_local = array.len(iter)
2417
2543
  compileExpr(&f.iter, body, ctx, state)?;
2418
- Instruction::I64Load(MemArg { offset: 0, align: 3, memory_index: 0 }).encode(body);
2419
- Instruction::I32WrapI64.encode(body);
2544
+ Instruction::ArrayLen.encode(body);
2420
2545
  Instruction::LocalSet(count_local).encode(body);
2421
2546
 
2422
2547
  // index_local = 0
@@ -2430,15 +2555,10 @@ fn compileStmt(stmt: &ast::Stmt, body: &mut Vec<u8>, ctx: &LocalCtx, state: &mut
2430
2555
  Instruction::I32GeS.encode(body);
2431
2556
  Instruction::BrIf(1).encode(body);
2432
2557
 
2433
- // var = load_elem([nums + 8 + index * 8])
2558
+ // var = array.get(iter, index)
2434
2559
  compileExpr(&f.iter, body, ctx, state)?;
2435
- Instruction::I32Const(8).encode(body);
2436
- Instruction::I32Add.encode(body);
2437
2560
  Instruction::LocalGet(index_local).encode(body);
2438
- Instruction::I32Const(8).encode(body);
2561
+ Instruction::ArrayGet(array_type_idx).encode(body);
2439
- Instruction::I32Mul.encode(body);
2440
- Instruction::I32Add.encode(body);
2441
- emitLoad(elem_vt, 0, body);
2442
2562
  Instruction::LocalSet(var_idx).encode(body);
2443
2563
 
2444
2564
  compileBlock(&f.body, body, ctx, state)?;
@@ -2472,7 +2592,7 @@ fn compileStmt(stmt: &ast::Stmt, body: &mut Vec<u8>, ctx: &LocalCtx, state: &mut
2472
2592
  compileMatch(m, body, ctx, state, None)?;
2473
2593
  }
2474
2594
  ast::Stmt::Assert(e) => {
2475
- compileExpr(e, body, ctx, state)?;
2595
+ compileBoolConditionAsI32(e, body, ctx, state)?;
2476
2596
  Instruction::I32Eqz.encode(body);
2477
2597
  Instruction::If(BlockType::Empty).encode(body);
2478
2598
  Instruction::Unreachable.encode(body);
@@ -2531,7 +2651,7 @@ fn exprHasResult(expr: &ast::Expr, ctx: &LocalCtx) -> bool {
2531
2651
  /// for `match a, b, ...` it's the full cross product (e.g. `Bool, Bool` needs all
2532
2652
  /// 4 combinations named, matching `libs/std/bool.plum`'s `and`/`or`).
2533
2653
  fn matchCoversEveryEnumVariant(cases: &[ast::Case], subject_vts: &[ValType], ctx: &LocalCtx) -> bool {
2534
- if subject_vts.is_empty() || subject_vts.iter().any(|vt| *vt != ValType::I32) {
2654
+ if subject_vts.is_empty() || subject_vts.iter().any(|vt| !matches!(vt, ValType::Ref(_))) {
2535
2655
  return false;
2536
2656
  }
2537
2657
  let n = subject_vts.len();
@@ -2727,7 +2847,7 @@ fn compileCasePositions(
2727
2847
  ast::CasePattern::String(_) => Err("codegen: string match patterns are not yet supported".to_string()),
2728
2848
  ast::CasePattern::Float(_) => Err("codegen: float match patterns are not yet supported".to_string()),
2729
2849
  ast::CasePattern::Class { name, fields } => {
2730
- compileVariantConstructorArm(name, fields, subject_vt, scratch_local, case, pos, all_subjects, rest, result_vt, exhaustive_fallback, body, ctx, state)
2850
+ compileVariantConstructorArm(pat, name, fields, subject_vt, scratch_local, case, pos, all_subjects, rest, result_vt, exhaustive_fallback, body, ctx, state)
2731
2851
  }
2732
2852
  }
2733
2853
  }
@@ -2747,23 +2867,19 @@ fn compileVariantEqArm(
2747
2867
  ctx: &LocalCtx,
2748
2868
  state: &mut ModuleState,
2749
2869
  ) -> Result<(), String> {
2750
- let info = ctx
2751
- .enum_variants
2870
+ ctx.enum_variants
2752
2871
  .get(name)
2753
2872
  .ok_or_else(|| format!("codegen: unknown enum variant '{}'", name))?;
2754
- if subject_vt != ValType::I32 {
2873
+ if !matches!(subject_vt, ValType::Ref(_)) {
2755
2874
  return Err(format!("codegen: enum tag pattern '{}' against a non-enum subject", name));
2756
2875
  }
2757
- let tag = info.tag;
2876
+ let variant_idx = *ctx.gc_types.variant_type_idx.get(name)
2877
+ .ok_or_else(|| format!("codegen: variant '{}' missing from the GC type registry", name))?;
2878
+ // A single ref.test against the variant's exact concrete type replaces the old
2879
+ // "range-check against HEAP_BASE, then conditionally load+compare a tag" dance —
2880
+ // there's no tag to load at all anymore, the type itself IS the discriminant.
2758
2881
  Instruction::LocalGet(scratch_local).encode(body);
2759
- if !info.field_types.is_empty() {
2760
- // A payload-carrying variant's scratch local holds a heap pointer, not the
2761
- // tag itself (unlike a payload-free variant, which stores the tag directly
2762
- // as its runtime value) — dereference slot 0 to compare against the tag.
2763
- emitLoad(ValType::I32, 0, body);
2764
- }
2765
- Instruction::I32Const(tag).encode(body);
2882
+ Instruction::RefTestNonNull(HeapType::Concrete(variant_idx)).encode(body);
2766
- Instruction::I32Eq.encode(body);
2767
2883
  Instruction::If(blockTypeFor(result_vt)).encode(body);
2768
2884
  compileCasePositions(case, pos + 1, all_subjects, rest, result_vt, exhaustive_fallback, body, ctx, state)?;
2769
2885
  Instruction::Else.encode(body);
@@ -2772,8 +2888,10 @@ fn compileVariantEqArm(
2772
2888
  Ok(())
2773
2889
  }
2774
2890
 
2891
+ #[allow(clippy::too_many_arguments)]
2775
2892
  #[allow(clippy::too_many_arguments)]
2776
2893
  fn compileVariantConstructorArm(
2894
+ pat: &ast::CasePattern,
2777
2895
  name: &str,
2778
2896
  fields: &[ast::CasePattern],
2779
2897
  subject_vt: ValType,
@@ -2792,7 +2910,7 @@ fn compileVariantConstructorArm(
2792
2910
  .enum_variants
2793
2911
  .get(name)
2794
2912
  .ok_or_else(|| format!("codegen: unknown enum variant '{}'", name))?;
2795
- if subject_vt != ValType::I32 {
2913
+ if !matches!(subject_vt, ValType::Ref(_)) {
2796
2914
  return Err(format!("codegen: constructor pattern '{}' against a non-enum subject", name));
2797
2915
  }
2798
2916
  if fields.len() != info.field_types.len() {
@@ -2801,28 +2919,33 @@ fn compileVariantConstructorArm(
2801
2919
  name, info.field_types.len(), fields.len()
2802
2920
  ));
2803
2921
  }
2804
- let tag = info.tag;
2922
+ let variant_idx = *ctx.gc_types.variant_type_idx.get(name)
2923
+ .ok_or_else(|| format!("codegen: variant '{}' missing from the GC type registry", name))?;
2805
2924
  let field_types = info.field_types.clone();
2806
2925
 
2807
- // A constructor pattern can only match if the runtime subject is actually
2926
+ // This pattern's own narrowly-typed scratch local (declared with variant's exact
2808
- // a heap pointer (payload variants are always >= HEAP_BASE); a
2809
- // payload-free sibling variant is a small int tag, and loading i32 from
2927
+ // concrete ref type see `nested_class_scratch_types`), distinct from
2928
+ // `scratch_local` (which stays declared at the subject's wide supertype type for
2810
- // that address would read unrelated/zeroed memory instead of a real tag.
2929
+ // the whole function). `struct.get` on the fields below requires this narrowed
2930
+ // static type; the old bump-allocator version needed no such narrowing since
2811
- // Guard with a range check before doing the I32Load.
2931
+ // every heap reference was a uniformly-typed, untyped-at-the-wasm-level i32.
2932
+ let narrow_key = pat as *const ast::CasePattern as usize;
2812
- Instruction::LocalGet(scratch_local).encode(body);
2933
+ let narrow_slot = *ctx.nested_class_scratch.get(&narrow_key)
2813
- Instruction::I32Const(HEAP_BASE as i32).encode(body);
2934
+ .ok_or_else(|| "internal codegen error: missing constructor-pattern scratch slot".to_string())?;
2814
- Instruction::I32GeU.encode(body);
2815
- Instruction::If(BlockType::Result(ValType::I32)).encode(body);
2935
+ let narrow_local = ctx.nested_class_scratch_base + narrow_slot;
2936
+
2937
+ // A single ref.test against the variant's exact concrete type replaces the old
2938
+ // "range-check against HEAP_BASE, then conditionally load+compare a tag" dance.
2816
2939
  Instruction::LocalGet(scratch_local).encode(body);
2817
- Instruction::I32Load(MemArg { offset: 0, align: 2, memory_index: 0 }).encode(body);
2940
+ Instruction::RefTestNonNull(HeapType::Concrete(variant_idx)).encode(body);
2818
- Instruction::I32Const(tag).encode(body);
2819
- Instruction::I32Eq.encode(body);
2820
- Instruction::Else.encode(body);
2821
- Instruction::I32Const(0).encode(body);
2822
- Instruction::End.encode(body);
2823
2941
  Instruction::If(blockTypeFor(result_vt)).encode(body);
2942
+ // Narrow the subject down to this variant's concrete type before destructuring
2943
+ // its fields — valid here specifically because the ref.test just above proved it.
2944
+ Instruction::LocalGet(scratch_local).encode(body);
2945
+ Instruction::RefCastNonNull(HeapType::Concrete(variant_idx)).encode(body);
2946
+ Instruction::LocalSet(narrow_local).encode(body);
2824
2947
  compileFieldPatterns(
2825
- fields, &field_types, 0, scratch_local, rest, all_subjects, result_vt, exhaustive_fallback, body, ctx, state,
2948
+ fields, &field_types, 0, narrow_local, variant_idx, rest, all_subjects, result_vt, exhaustive_fallback, body, ctx, state,
2826
2949
  &mut |body, state| compileCasePositions(case, pos + 1, all_subjects, rest, result_vt, exhaustive_fallback, body, ctx, state),
2827
2950
  )?;
2828
2951
  Instruction::Else.encode(body);
@@ -2838,11 +2961,13 @@ fn compileVariantConstructorArm(
2838
2961
  /// position). A mismatch at any field — at any nesting depth — falls through to
2839
2962
  /// `compileMatchArmsMulti(rest, ...)`, exactly like a top-level pattern mismatch.
2840
2963
  #[allow(clippy::too_many_arguments)]
2964
+ #[allow(clippy::too_many_arguments)]
2841
2965
  fn compileFieldPatterns(
2842
2966
  fields: &[ast::CasePattern],
2843
2967
  field_types: &[PlumType],
2844
2968
  fpos: usize,
2845
2969
  container_local: u32,
2970
+ container_type_idx: u32,
2846
2971
  rest: &[ast::Case],
2847
2972
  all_subjects: &[(ValType, u32)],
2848
2973
  result_vt: Option<ValType>,
@@ -2857,12 +2982,10 @@ fn compileFieldPatterns(
2857
2982
  }
2858
2983
  let pat = &fields[fpos];
2859
2984
  let field_ty = &field_types[fpos];
2860
- let field_vt = plumTypeToValtype(field_ty);
2861
- let offset = ((fpos + 1) as u64) * 8;
2862
2985
 
2863
2986
  match pat {
2864
2987
  ast::CasePattern::Wildcard => {
2865
- compileFieldPatterns(fields, field_types, fpos + 1, container_local, rest, all_subjects, result_vt, exhaustive_fallback, body, ctx, state, on_match)
2988
+ compileFieldPatterns(fields, field_types, fpos + 1, container_local, container_type_idx, rest, all_subjects, result_vt, exhaustive_fallback, body, ctx, state, on_match)
2866
2989
  }
2867
2990
  ast::CasePattern::Name(n) if !(n.chars().next().map(|c| c.is_uppercase()).unwrap_or(false) && ctx.enum_variants.contains_key(n)) => {
2868
2991
  // A plain binding always matches this field; load it straight into its
@@ -2873,47 +2996,42 @@ fn compileFieldPatterns(
2873
2996
  .copied()
2874
2997
  .ok_or_else(|| format!("internal codegen error: missing binding local '{}'", n))?;
2875
2998
  Instruction::LocalGet(container_local).encode(body);
2876
- emitLoad(field_vt, offset, body);
2999
+ Instruction::StructGet { struct_type_index: container_type_idx, field_index: fpos as u32 }.encode(body);
2877
3000
  Instruction::LocalSet(idx).encode(body);
2878
3001
  ctx.type_env.borrow_mut().insert(n.to_string(), TypeScheme::mono(field_ty.clone()));
2879
- compileFieldPatterns(fields, field_types, fpos + 1, container_local, rest, all_subjects, result_vt, exhaustive_fallback, body, ctx, state, on_match)
3002
+ compileFieldPatterns(fields, field_types, fpos + 1, container_local, container_type_idx, rest, all_subjects, result_vt, exhaustive_fallback, body, ctx, state, on_match)
2880
3003
  }
2881
3004
  ast::CasePattern::Name(n) => {
2882
3005
  // An uppercase, payload-free variant name used as a field pattern (e.g.
2883
3006
  // matching a nested `None` rather than binding a name to it).
2884
- let info = ctx
2885
- .enum_variants
3007
+ ctx.enum_variants
2886
3008
  .get(n)
2887
3009
  .ok_or_else(|| format!("codegen: unknown enum variant '{}'", n))?;
2888
- if field_vt != ValType::I32 {
3010
+ if !matches!(plumTypeToValtype(field_ty), ValType::Ref(_)) {
2889
3011
  return Err(format!("codegen: enum tag pattern '{}' against a non-enum field", n));
2890
3012
  }
3013
+ let variant_idx = *ctx.gc_types.variant_type_idx.get(n)
3014
+ .ok_or_else(|| format!("codegen: variant '{}' missing from the GC type registry", n))?;
2891
3015
  Instruction::LocalGet(container_local).encode(body);
2892
- emitLoad(field_vt, offset, body);
2893
- if !info.field_types.is_empty() {
2894
- // Payload-carrying nested variant: the just-loaded field is a heap
2895
- // pointer, not the tag dereference slot 0 to compare against it.
3016
+ Instruction::StructGet { struct_type_index: container_type_idx, field_index: fpos as u32 }.encode(body);
2896
- emitLoad(ValType::I32, 0, body);
2897
- }
2898
- Instruction::I32Const(info.tag).encode(body);
3017
+ Instruction::RefTestNonNull(HeapType::Concrete(variant_idx)).encode(body);
2899
- Instruction::I32Eq.encode(body);
2900
3018
  Instruction::If(blockTypeFor(result_vt)).encode(body);
2901
- compileFieldPatterns(fields, field_types, fpos + 1, container_local, rest, all_subjects, result_vt, exhaustive_fallback, body, ctx, state, on_match)?;
3019
+ compileFieldPatterns(fields, field_types, fpos + 1, container_local, container_type_idx, rest, all_subjects, result_vt, exhaustive_fallback, body, ctx, state, on_match)?;
2902
3020
  Instruction::Else.encode(body);
2903
3021
  compileMatchArmsMulti(rest, all_subjects, result_vt, exhaustive_fallback, body, ctx, state)?;
2904
3022
  Instruction::End.encode(body);
2905
3023
  Ok(())
2906
3024
  }
2907
3025
  ast::CasePattern::Int(n) => {
2908
- if field_vt != ValType::I64 {
3026
+ if plumTypeToValtype(field_ty) != ValType::I64 {
2909
3027
  return Err("codegen: integer match pattern against a non-Int field".to_string());
2910
3028
  }
2911
3029
  Instruction::LocalGet(container_local).encode(body);
2912
- emitLoad(field_vt, offset, body);
3030
+ Instruction::StructGet { struct_type_index: container_type_idx, field_index: fpos as u32 }.encode(body);
2913
3031
  Instruction::I64Const(*n).encode(body);
2914
3032
  Instruction::I64Eq.encode(body);
2915
3033
  Instruction::If(blockTypeFor(result_vt)).encode(body);
2916
- compileFieldPatterns(fields, field_types, fpos + 1, container_local, rest, all_subjects, result_vt, exhaustive_fallback, body, ctx, state, on_match)?;
3034
+ compileFieldPatterns(fields, field_types, fpos + 1, container_local, container_type_idx, rest, all_subjects, result_vt, exhaustive_fallback, body, ctx, state, on_match)?;
2917
3035
  Instruction::Else.encode(body);
2918
3036
  compileMatchArmsMulti(rest, all_subjects, result_vt, exhaustive_fallback, body, ctx, state)?;
2919
3037
  Instruction::End.encode(body);
@@ -2926,7 +3044,7 @@ fn compileFieldPatterns(
2926
3044
  .enum_variants
2927
3045
  .get(name)
2928
3046
  .ok_or_else(|| format!("codegen: unknown enum variant '{}'", name))?;
2929
- if field_vt != ValType::I32 {
3047
+ if !matches!(plumTypeToValtype(field_ty), ValType::Ref(_)) {
2930
3048
  return Err(format!("codegen: constructor pattern '{}' against a non-enum field", name));
2931
3049
  }
2932
3050
  if inner_fields.len() != info.field_types.len() {
@@ -2935,35 +3053,27 @@ fn compileFieldPatterns(
2935
3053
  name, info.field_types.len(), inner_fields.len()
2936
3054
  ));
2937
3055
  }
3056
+ let inner_variant_idx = *ctx.gc_types.variant_type_idx.get(name)
3057
+ .ok_or_else(|| format!("codegen: variant '{}' missing from the GC type registry", name))?;
2938
3058
  let key = pat as *const ast::CasePattern as usize;
2939
3059
  let slot = *ctx
2940
3060
  .nested_class_scratch
2941
3061
  .get(&key)
2942
3062
  .ok_or_else(|| "internal codegen error: missing nested constructor pattern scratch slot".to_string())?;
2943
3063
  let nested_local = ctx.nested_class_scratch_base + slot;
2944
- let tag = info.tag;
2945
3064
  let inner_field_types = info.field_types.clone();
2946
3065
 
2947
3066
  Instruction::LocalGet(container_local).encode(body);
2948
- emitLoad(field_vt, offset, body);
2949
- Instruction::LocalSet(nested_local).encode(body);
2950
-
2951
- // Same heap-pointer guard as the top-level constructor check: a
2952
- // payload-free sibling variant is a small int tag, not a pointer.
2953
- Instruction::LocalGet(nested_local).encode(body);
2954
- Instruction::I32Const(HEAP_BASE as i32).encode(body);
3067
+ Instruction::StructGet { struct_type_index: container_type_idx, field_index: fpos as u32 }.encode(body);
2955
- Instruction::I32GeU.encode(body);
2956
- Instruction::If(BlockType::Result(ValType::I32)).encode(body);
3068
+ Instruction::RefTestNonNull(HeapType::Concrete(inner_variant_idx)).encode(body);
2957
- Instruction::LocalGet(nested_local).encode(body);
2958
- Instruction::I32Load(MemArg { offset: 0, align: 2, memory_index: 0 }).encode(body);
2959
- Instruction::I32Const(tag).encode(body);
2960
- Instruction::I32Eq.encode(body);
2961
- Instruction::Else.encode(body);
2962
- Instruction::I32Const(0).encode(body);
2963
- Instruction::End.encode(body);
2964
3069
  Instruction::If(blockTypeFor(result_vt)).encode(body);
3070
+ // Narrow into `nested_local` now that ref.test just above proved it's safe.
3071
+ Instruction::LocalGet(container_local).encode(body);
3072
+ Instruction::StructGet { struct_type_index: container_type_idx, field_index: fpos as u32 }.encode(body);
3073
+ Instruction::RefCastNonNull(HeapType::Concrete(inner_variant_idx)).encode(body);
3074
+ Instruction::LocalSet(nested_local).encode(body);
2965
- compileFieldPatterns(inner_fields, &inner_field_types, 0, nested_local, rest, all_subjects, result_vt, exhaustive_fallback, body, ctx, state, &mut |body, state| {
3075
+ compileFieldPatterns(inner_fields, &inner_field_types, 0, nested_local, inner_variant_idx, rest, all_subjects, result_vt, exhaustive_fallback, body, ctx, state, &mut |body, state| {
2966
- compileFieldPatterns(fields, field_types, fpos + 1, container_local, rest, all_subjects, result_vt, exhaustive_fallback, body, ctx, state, on_match)
3076
+ compileFieldPatterns(fields, field_types, fpos + 1, container_local, container_type_idx, rest, all_subjects, result_vt, exhaustive_fallback, body, ctx, state, on_match)
2967
3077
  })?;
2968
3078
  Instruction::Else.encode(body);
2969
3079
  compileMatchArmsMulti(rest, all_subjects, result_vt, exhaustive_fallback, body, ctx, state)?;
@@ -2994,13 +3104,13 @@ fn compileExpr(expr: &ast::Expr, body: &mut Vec<u8>, ctx: &LocalCtx, state: &mut
2994
3104
  Some(idx) => Instruction::LocalGet(*idx).encode(body),
2995
3105
  // Not a local: a bare reference to a top-level function used as a
2996
3106
  // value (e.g. `each(double)`) — push its zero-capture trampoline
2997
- // closure's (compile-time-constant) struct pointer.
3107
+ // closure's (pre-built by the `start` function) global.
2998
3108
  None => {
2999
- let offset = *ctx
3109
+ let global_idx = *ctx
3000
3110
  .named_fn_values
3001
3111
  .get(name.as_str())
3002
3112
  .ok_or_else(|| format!("undeclared variable '{}'", name))?;
3003
- Instruction::I32Const(offset as i32).encode(body);
3113
+ Instruction::GlobalGet(global_idx).encode(body);
3004
3114
  }
3005
3115
  }
3006
3116
  }
@@ -3044,16 +3154,18 @@ fn compileExpr(expr: &ast::Expr, body: &mut Vec<u8>, ctx: &LocalCtx, state: &mut
3044
3154
  }
3045
3155
  }
3046
3156
  ast::Expr::Bool(b) => {
3047
- compileExpr(&b.left, body, ctx, state)?;
3157
+ compileBoolConditionAsI32(&b.left, body, ctx, state)?;
3048
- compileExpr(&b.right, body, ctx, state)?;
3158
+ compileBoolConditionAsI32(&b.right, body, ctx, state)?;
3049
3159
  match b.op {
3050
3160
  ast::BoolOp::And => Instruction::I32And.encode(body),
3051
3161
  ast::BoolOp::Or => Instruction::I32Or.encode(body),
3052
- }
3162
+ };
3163
+ pushBoolRefFromI32Flag(body, ctx);
3053
3164
  }
3054
3165
  ast::Expr::Not(inner) => {
3055
- compileExpr(inner, body, ctx, state)?;
3166
+ compileBoolConditionAsI32(inner, body, ctx, state)?;
3056
3167
  Instruction::I32Eqz.encode(body);
3168
+ pushBoolRefFromI32Flag(body, ctx);
3057
3169
  }
3058
3170
  ast::Expr::Compare(c) => {
3059
3171
  let is_float = matches!(inferLocalType(&c.left, ctx), PlumType::TFloat);
@@ -3080,10 +3192,11 @@ fn compileExpr(expr: &ast::Expr, body: &mut Vec<u8>, ctx: &LocalCtx, state: &mut
3080
3192
  }
3081
3193
  .encode(body);
3082
3194
  }
3195
+ pushBoolRefFromI32Flag(body, ctx);
3083
3196
  }
3084
3197
  ast::Expr::Ternary(t) => {
3085
3198
  let result_vt = plumTypeToValtype(&inferLocalType(&t.then, ctx));
3086
- compileExpr(&t.condition, body, ctx, state)?;
3199
+ compileBoolConditionAsI32(&t.condition, body, ctx, state)?;
3087
3200
  Instruction::If(BlockType::Result(result_vt)).encode(body);
3088
3201
  compileExpr(&t.then, body, ctx, state)?;
3089
3202
  Instruction::Else.encode(body);
@@ -3121,34 +3234,16 @@ fn compileExpr(expr: &ast::Expr, body: &mut Vec<u8>, ctx: &LocalCtx, state: &mut
3121
3234
  compileExpr(argExprOf(arg), body, ctx, state)?;
3122
3235
  }
3123
3236
  let trailing: Vec<&ast::Expr> = call.args.iter().skip(fixed_count).map(argExprOf).collect();
3124
- let count = trailing.len() as i32;
3125
- let size = 8 * (count + 1);
3126
-
3127
- let scratch_key = expr as *const ast::Expr as usize;
3128
- let scratch_idx = *ctx
3129
- .classcall_scratch
3130
- .get(&scratch_key)
3131
- .ok_or_else(|| "internal codegen error: missing variadic-call scratch slot".to_string())?;
3132
- let scratch_local = ctx.classcall_scratch_base + scratch_idx;
3133
-
3134
- Instruction::GlobalGet(ctx.bump_global).encode(body);
3135
- Instruction::LocalSet(scratch_local).encode(body);
3136
- Instruction::GlobalGet(ctx.bump_global).encode(body);
3137
- Instruction::I32Const(size).encode(body);
3138
- Instruction::I32Add.encode(body);
3139
- Instruction::GlobalSet(ctx.bump_global).encode(body);
3140
-
3141
- Instruction::LocalGet(scratch_local).encode(body);
3142
- Instruction::I64Const(count as i64).encode(body);
3143
- emitStore(ValType::I64, 0, body);
3144
-
3145
3237
  let elem_vt = plumTypeToValtype(&elem_ty);
3238
+ let array_type_idx = *ctx
3239
+ .gc_types
3240
+ .variadic_array_type_idx
3241
+ .get(&elem_vt)
3242
+ .ok_or_else(|| "internal codegen error: no variadic array type registered for this elem type".to_string())?;
3146
- for (i, arg_expr) in trailing.iter().enumerate() {
3243
+ for arg_expr in &trailing {
3147
- Instruction::LocalGet(scratch_local).encode(body);
3148
3244
  compileExpr(arg_expr, body, ctx, state)?;
3149
- emitStore(elem_vt, 8 * (i as u64 + 1), body);
3150
3245
  }
3151
- Instruction::LocalGet(scratch_local).encode(body);
3246
+ Instruction::ArrayNewFixed { array_type_index: array_type_idx, array_size: trailing.len() as u32 }.encode(body);
3152
3247
 
3153
3248
  let func_idx = ctx
3154
3249
  .func_ids
@@ -3179,7 +3274,9 @@ fn compileExpr(expr: &ast::Expr, body: &mut Vec<u8>, ctx: &LocalCtx, state: &mut
3179
3274
  }
3180
3275
  ast::Expr::TypeName(n) => match ctx.enum_variants.get(n) {
3181
3276
  Some(info) if info.field_types.is_empty() => {
3277
+ let global_idx = *ctx.singleton_globals.get(n)
3278
+ .unwrap_or_else(|| panic!("internal codegen error: payload-free variant '{}' has no singleton global", n));
3182
- Instruction::I32Const(info.tag).encode(body);
3279
+ Instruction::GlobalGet(global_idx).encode(body);
3183
3280
  }
3184
3281
  Some(info) if !info.values.is_empty() => {
3185
3282
  // A discriminant variant's own declared literal values ARE its
@@ -3195,45 +3292,24 @@ fn compileExpr(expr: &ast::Expr, body: &mut Vec<u8>, ctx: &LocalCtx, state: &mut
3195
3292
  None => return Err(format!("codegen: type name '{}' is not yet supported as a value", n)),
3196
3293
  },
3197
3294
  ast::Expr::ClassCall(call) => {
3295
+ // struct.new needs every field value pushed in DECLARATION order (not
3296
+ // `call.fields`'s written order) immediately before the single
3297
+ // construction instruction — no intermediate scratch pointer needed at
3298
+ // all, unlike the old bump-pointer-then-store approach.
3198
3299
  let fields = ctx
3199
3300
  .classes
3200
3301
  .get(&call.type_name)
3201
3302
  .ok_or_else(|| format!("codegen: unknown class '{}'", call.type_name))?
3202
3303
  .clone();
3203
- let size = (fields.len() as i32) * 8;
3204
-
3205
- let scratch_key = expr as *const ast::Expr as usize;
3304
+ let class_type_idx = *ctx.gc_types.class_type_idx.get(&call.type_name)
3206
- let scratch_idx = *ctx
3207
- .classcall_scratch
3208
- .get(&scratch_key)
3209
- .ok_or_else(|| "internal codegen error: missing classcall scratch slot".to_string())?;
3305
+ .ok_or_else(|| format!("codegen: class '{}' missing from the GC type registry", call.type_name))?;
3210
- let scratch_local = ctx.classcall_scratch_base + scratch_idx;
3211
-
3212
- // base = bump_global; bump_global += size (runtime bump allocation, so every
3213
- // call — even inside a loop — gets a fresh, non-overlapping region).
3214
- Instruction::GlobalGet(ctx.bump_global).encode(body);
3215
- Instruction::LocalSet(scratch_local).encode(body);
3216
- Instruction::GlobalGet(ctx.bump_global).encode(body);
3217
- Instruction::I32Const(size).encode(body);
3218
- Instruction::I32Add.encode(body);
3219
- Instruction::GlobalSet(ctx.bump_global).encode(body);
3220
3306
 
3221
- for fa in &call.fields {
3307
+ for (field_name, _) in &fields {
3222
- let (field_idx, field_ty) = fields
3308
+ let fa = call.fields.iter().find(|fa| &fa.name == field_name)
3223
- .iter()
3224
- .position(|(n, _)| n == &fa.name)
3225
- .map(|i| (i, fields[i].1.clone()))
3226
- .ok_or_else(|| format!("codegen: unknown field '{}' on class '{}'", fa.name, call.type_name))?;
3309
+ .ok_or_else(|| format!("codegen: class '{}' missing field '{}'", call.type_name, field_name))?;
3227
- Instruction::LocalGet(scratch_local).encode(body);
3228
3310
  compileExpr(&fa.value, body, ctx, state)?;
3229
- let offset = (field_idx as u64) * 8;
3230
- match plumTypeToValtype(&field_ty) {
3231
- ValType::I64 => Instruction::I64Store(MemArg { offset, align: 3, memory_index: 0 }).encode(body),
3232
- ValType::F64 => Instruction::F64Store(MemArg { offset, align: 3, memory_index: 0 }).encode(body),
3233
- _ => Instruction::I32Store(MemArg { offset, align: 2, memory_index: 0 }).encode(body),
3234
- };
3235
3311
  }
3236
- Instruction::LocalGet(scratch_local).encode(body);
3312
+ Instruction::StructNew(class_type_idx).encode(body);
3237
3313
  }
3238
3314
  ast::Expr::Attribute(attr) => {
3239
3315
  let obj_ty = inferLocalType(&attr.object, ctx);
@@ -3245,39 +3321,33 @@ fn compileExpr(expr: &ast::Expr, body: &mut Vec<u8>, ctx: &LocalCtx, state: &mut
3245
3321
  };
3246
3322
  match ctx.classes.get(&class_name) {
3247
3323
  Some(fields) => {
3248
- let (field_idx, field_ty) = fields
3324
+ let field_idx = fields
3249
3325
  .iter()
3250
3326
  .position(|(n, _)| n == field_name)
3251
- .map(|i| (i, fields[i].1.clone()))
3252
3327
  .ok_or_else(|| format!("codegen: no field '{}' on class '{}'", field_name, class_name))?;
3328
+ let class_type_idx = *ctx.gc_types.class_type_idx.get(&class_name)
3329
+ .ok_or_else(|| format!("codegen: class '{}' missing from the GC type registry", class_name))?;
3253
3330
  compileExpr(&attr.object, body, ctx, state)?;
3254
- let offset = (field_idx as u64) * 8;
3255
- match plumTypeToValtype(&field_ty) {
3256
- ValType::I64 => Instruction::I64Load(MemArg { offset, align: 3, memory_index: 0 }).encode(body),
3331
+ Instruction::StructGet { struct_type_index: class_type_idx, field_index: field_idx as u32 }.encode(body);
3257
- ValType::F64 => Instruction::F64Load(MemArg { offset, align: 3, memory_index: 0 }).encode(body),
3258
- _ => Instruction::I32Load(MemArg { offset, align: 2, memory_index: 0 }).encode(body),
3259
- };
3260
3332
  }
3261
- // Not a class: fall back to a discriminant enum's shared params.
3333
+ // Not a class: fall back to a discriminant enum's shared params,
3334
+ // declared directly on the enum's SUPERTYPE (see
3335
+ // `buildGcTypeRegistry`'s `EnumSuper` arm) — no `ref.cast` to any
3262
- // Slot 0 is always the variant's tag, so field N lives at slot N+1 —
3336
+ // particular variant needed, since every variant has the exact
3263
- // NOT the same offset a class field of the same index would use.
3337
+ // same field list as the supertype itself.
3264
3338
  None => {
3265
3339
  let params = ctx
3266
3340
  .enum_params
3267
3341
  .get(&class_name)
3268
3342
  .ok_or_else(|| format!("codegen: unknown class '{}'", class_name))?;
3269
- let (field_idx, field_ty) = params
3343
+ let field_idx = params
3270
3344
  .iter()
3271
3345
  .position(|(n, _)| n == field_name)
3272
- .map(|i| (i, params[i].1.clone()))
3273
3346
  .ok_or_else(|| format!("codegen: no field '{}' on enum '{}'", field_name, class_name))?;
3347
+ let super_type_idx = *ctx.gc_types.enum_super_type_idx.get(&class_name)
3348
+ .ok_or_else(|| format!("codegen: enum '{}' missing from the GC type registry", class_name))?;
3274
3349
  compileExpr(&attr.object, body, ctx, state)?;
3275
- let offset = ((field_idx + 1) as u64) * 8;
3276
- match plumTypeToValtype(&field_ty) {
3277
- ValType::I64 => Instruction::I64Load(MemArg { offset, align: 3, memory_index: 0 }).encode(body),
3350
+ Instruction::StructGet { struct_type_index: super_type_idx, field_index: field_idx as u32 }.encode(body);
3278
- ValType::F64 => Instruction::F64Load(MemArg { offset, align: 3, memory_index: 0 }).encode(body),
3279
- _ => Instruction::I32Load(MemArg { offset, align: 2, memory_index: 0 }).encode(body),
3280
- };
3281
3351
  }
3282
3352
  }
3283
3353
  }
@@ -3328,13 +3398,13 @@ fn compileExpr(expr: &ast::Expr, body: &mut Vec<u8>, ctx: &LocalCtx, state: &mut
3328
3398
  }
3329
3399
 
3330
3400
  /// Compiles a variant-construction call. A payload-free variant (`None`, called as
3331
- /// `None()` rather than used bare) is just its tag. A payload variant bump-allocates
3401
+ /// `None()` rather than used bare) is its pre-allocated singleton global. A payload
3332
- /// `[tag: i32][field0][field1]...` (8-byte stride per slot, matching class field
3402
+ /// variant pushes its field values in order and does `struct.new` into its own
3333
- /// layout) and leaves the base pointer on the stack.
3403
+ /// concrete variant type.
3334
3404
  fn compileVariantConstruction(
3335
3405
  info: &EnumVariantInfo,
3336
3406
  call: &ast::FnCall,
3337
- expr: &ast::Expr,
3407
+ _expr: &ast::Expr,
3338
3408
  body: &mut Vec<u8>,
3339
3409
  ctx: &LocalCtx,
3340
3410
  state: &mut ModuleState,
@@ -3345,78 +3415,45 @@ fn compileVariantConstruction(
3345
3415
  call.name, info.field_types.len(), call.args.len()
3346
3416
  ));
3347
3417
  }
3418
+ let variant_type_idx = *ctx.gc_types.variant_type_idx.get(&call.name)
3419
+ .ok_or_else(|| format!("codegen: variant '{}' missing from the GC type registry", call.name))?;
3348
3420
  if info.field_types.is_empty() {
3421
+ // Pre-allocated singleton (this migration plan's Decision 2) — not a fresh
3422
+ // struct.new per reference.
3423
+ let global_idx = *ctx.singleton_globals.get(&call.name)
3424
+ .ok_or_else(|| format!("codegen: payload-free variant '{}' has no singleton global", call.name))?;
3349
- Instruction::I32Const(info.tag).encode(body);
3425
+ Instruction::GlobalGet(global_idx).encode(body);
3350
3426
  return Ok(());
3351
3427
  }
3352
3428
 
3429
+ // struct.new needs every field value pushed, in order, immediately before the
3353
- let size = (1 + info.field_types.len() as i32) * 8;
3430
+ // single construction instruction positional args already match field
3354
- let scratch_key = expr as *const ast::Expr as usize;
3431
+ // declaration order (unlike named class-field construction), so no reordering
3355
- let scratch_idx = *ctx
3432
+ // is needed here.
3356
- .classcall_scratch
3357
- .get(&scratch_key)
3433
+ for arg in &call.args {
3358
- .ok_or_else(|| "internal codegen error: missing variant-call scratch slot".to_string())?;
3359
- let scratch_local = ctx.classcall_scratch_base + scratch_idx;
3360
-
3361
- Instruction::GlobalGet(ctx.bump_global).encode(body);
3362
- Instruction::LocalSet(scratch_local).encode(body);
3363
- Instruction::GlobalGet(ctx.bump_global).encode(body);
3364
- Instruction::I32Const(size).encode(body);
3365
- Instruction::I32Add.encode(body);
3366
- Instruction::GlobalSet(ctx.bump_global).encode(body);
3367
-
3368
- Instruction::LocalGet(scratch_local).encode(body);
3369
- Instruction::I32Const(info.tag).encode(body);
3370
- Instruction::I32Store(MemArg { offset: 0, align: 2, memory_index: 0 }).encode(body);
3371
-
3372
- for (i, (arg, field_ty)) in call.args.iter().zip(info.field_types.iter()).enumerate() {
3373
3434
  let arg_expr = match arg {
3374
3435
  ast::Arg::Positional(e) => e,
3375
3436
  ast::Arg::Keyword { value, .. } => value,
3376
3437
  ast::Arg::Pair { value, .. } => value,
3377
3438
  };
3378
- Instruction::LocalGet(scratch_local).encode(body);
3379
3439
  compileExpr(arg_expr, body, ctx, state)?;
3380
- let offset = ((i + 1) as u64) * 8;
3381
- match plumTypeToValtype(field_ty) {
3382
- ValType::I64 => Instruction::I64Store(MemArg { offset, align: 3, memory_index: 0 }).encode(body),
3383
- ValType::F64 => Instruction::F64Store(MemArg { offset, align: 3, memory_index: 0 }).encode(body),
3384
- _ => Instruction::I32Store(MemArg { offset, align: 2, memory_index: 0 }).encode(body),
3385
- };
3386
3440
  }
3387
- Instruction::LocalGet(scratch_local).encode(body);
3441
+ Instruction::StructNew(variant_type_idx).encode(body);
3388
3442
  Ok(())
3389
3443
  }
3390
3444
 
3391
- /// Emits a width-appropriate store for a heap slot: `[addr, value]` -> memory.
3392
- fn emitStore(vt: ValType, offset: u64, body: &mut Vec<u8>) {
3393
- match vt {
3394
- ValType::I64 => Instruction::I64Store(MemArg { offset, align: 3, memory_index: 0 }),
3395
- ValType::F64 => Instruction::F64Store(MemArg { offset, align: 3, memory_index: 0 }),
3396
- _ => Instruction::I32Store(MemArg { offset, align: 2, memory_index: 0 }),
3397
- }.encode(body);
3398
- }
3399
-
3400
- /// Emits a width-appropriate load for a heap slot: `[addr]` -> value.
3401
- fn emitLoad(vt: ValType, offset: u64, body: &mut Vec<u8>) {
3402
- match vt {
3403
- ValType::I64 => Instruction::I64Load(MemArg { offset, align: 3, memory_index: 0 }),
3404
- ValType::F64 => Instruction::F64Load(MemArg { offset, align: 3, memory_index: 0 }),
3405
- _ => Instruction::I32Load(MemArg { offset, align: 2, memory_index: 0 }),
3406
- }.encode(body);
3407
- }
3408
-
3409
- /// Emits a static (compile-time-known) length-prefixed string as a fresh data
3445
+ /// Emits a static (compile-time-known) string literal as a fresh passive data
3410
- /// segment, pushing its address.
3446
+ /// segment, pushing a `Str` array built from it via `array.new_data`. `Str`'s wasm-gc
3447
+ /// representation is a plain `array<i8>` (see Decision 4 of the wasm-gc migration
3448
+ /// plan) — no length prefix needed, unlike the old bump-allocator layout.
3411
3449
  fn compileStaticString(text: &str, body: &mut Vec<u8>, state: &mut ModuleState) {
3412
3450
  let bytes = text.as_bytes();
3413
- let offset = state.next_string_offset;
3414
- let mut data = Vec::with_capacity(4 + bytes.len());
3415
- data.extend_from_slice(&(bytes.len() as u32).to_le_bytes());
3416
- data.extend_from_slice(bytes);
3417
- state.next_string_offset += data.len() as u32;
3451
+ let data_index = state.passive_segments.len() as u32;
3418
- state.data_segments.push((offset, data));
3452
+ state.passive_segments.push(bytes.to_vec());
3453
+ let str_type_idx = withGcTypes(|r| r.str_type_idx);
3454
+ Instruction::I32Const(0).encode(body);
3419
- Instruction::I32Const(offset as i32).encode(body);
3455
+ Instruction::I32Const(bytes.len() as i32).encode(body);
3456
+ Instruction::ArrayNewData { array_type_index: str_type_idx, array_data_index: data_index }.encode(body);
3420
3457
  }
3421
3458
 
3422
3459
  /// Lowers a string literal that contains at least one `{expr}` interpolation.
@@ -3447,8 +3484,9 @@ fn compileInterpolatedString(
3447
3484
  Instruction::Call(ctx.int_to_string_func).encode(body);
3448
3485
  }
3449
3486
  PlumType::TBool => {
3487
+ let str_ref = withGcTypes(|r| gcRef(r.str_type_idx));
3450
- compileExpr(expr, body, ctx, state)?;
3488
+ compileBoolConditionAsI32(expr, body, ctx, state)?;
3451
- Instruction::If(BlockType::Result(ValType::I32)).encode(body);
3489
+ Instruction::If(BlockType::Result(str_ref)).encode(body);
3452
3490
  compileStaticString("True", body, state);
3453
3491
  Instruction::Else.encode(body);
3454
3492
  compileStaticString("False", body, state);
@@ -3474,9 +3512,10 @@ fn compileInterpolatedString(
3474
3512
  Ok(())
3475
3513
  }
3476
3514
 
3477
- /// Compiles a closure *literal* at its construction site. Snapshots each captured free
3515
+ /// Compiles a closure *literal* at its construction site. Snapshots each captured
3478
- /// variable's CURRENT value into a fresh heap env struct, then builds the 2-word closure
3516
+ /// free variable's CURRENT value into a fresh env struct (`struct.new`), then wraps
3517
+ /// it with the closure's table index into the shared `{table_idx, env}` closure
3479
- /// struct `{table_index @0, env_pointer @8}` and leaves its pointer on the stack.
3518
+ /// struct and leaves its ref on the stack.
3480
3519
  fn compileClosureLiteral(
3481
3520
  cl: &ast::Closure,
3482
3521
  body: &mut Vec<u8>,
@@ -3488,58 +3527,29 @@ fn compileClosureLiteral(
3488
3527
  .closures
3489
3528
  .get(&key)
3490
3529
  .ok_or_else(|| "internal codegen error: closure literal was not discovered by the discovery pre-pass".to_string())?;
3491
- let scratch_off = *ctx
3530
+
3492
- .closure_scratch
3493
- .get(&key)
3494
- .ok_or_else(|| "internal codegen error: missing closure scratch slot".to_string())?;
3495
- let env_scratch = ctx.closure_scratch_base + scratch_off;
3496
- let closure_scratch = env_scratch + 1;
3497
-
3498
- // Bump-allocate the env struct: one 8-byte slot per captured variable.
3531
+ // Build the closure struct directly on the stack: push table_idx, then build the
3499
- let env_size = (info.free_vars.len() as i32) * 8;
3532
+ // env struct (snapshotting each captured free variable's current value from the
3500
- Instruction::GlobalGet(ctx.bump_global).encode(body);
3533
+ // enclosing function's local), then wrap both into the shared closure struct.
3501
- Instruction::LocalSet(env_scratch).encode(body);
3502
- Instruction::GlobalGet(ctx.bump_global).encode(body);
3503
- Instruction::I32Const(env_size).encode(body);
3534
+ Instruction::I32Const(info.table_idx as i32).encode(body);
3504
- Instruction::I32Add.encode(body);
3505
- Instruction::GlobalSet(ctx.bump_global).encode(body);
3506
-
3507
- // Snapshot each free variable's current value from the enclosing function's local.
3508
- for (i, (name, ty)) in info.free_vars.iter().enumerate() {
3535
+ for (name, _) in &info.free_vars {
3509
3536
  let local_idx = *ctx
3510
3537
  .locals
3511
3538
  .get(name)
3512
3539
  .ok_or_else(|| format!("codegen: captured variable '{}' is not a local in the enclosing scope", name))?;
3513
- Instruction::LocalGet(env_scratch).encode(body);
3514
3540
  Instruction::LocalGet(local_idx).encode(body);
3515
- emitStore(plumTypeToValtype(ty), (i as u64) * 8, body);
3516
3541
  }
3517
-
3518
- // Bump-allocate the 2-word closure struct.
3519
- Instruction::GlobalGet(ctx.bump_global).encode(body);
3520
- Instruction::LocalSet(closure_scratch).encode(body);
3521
- Instruction::GlobalGet(ctx.bump_global).encode(body);
3522
- Instruction::I32Const(16).encode(body);
3523
- Instruction::I32Add.encode(body);
3524
- Instruction::GlobalSet(ctx.bump_global).encode(body);
3525
-
3526
- // table_index @ offset 0
3527
- Instruction::LocalGet(closure_scratch).encode(body);
3528
- Instruction::I32Const(info.table_idx as i32).encode(body);
3542
+ Instruction::StructNew(info.env_type_idx).encode(body);
3529
- Instruction::I32Store(MemArg { offset: 0, align: 2, memory_index: 0 }).encode(body);
3530
- // env_pointer @ offset 8
3531
- Instruction::LocalGet(closure_scratch).encode(body);
3543
+ Instruction::StructNew(ctx.gc_types.closure_type_idx).encode(body);
3532
- Instruction::LocalGet(env_scratch).encode(body);
3533
- Instruction::I32Store(MemArg { offset: 8, align: 2, memory_index: 0 }).encode(body);
3534
-
3535
- // The closure value is its struct pointer.
3536
- Instruction::LocalGet(closure_scratch).encode(body);
3537
3544
  Ok(())
3538
3545
  }
3539
3546
 
3540
- /// Compiles a call to a closure-typed local via `call_indirect`. Stack order matches the
3547
+ /// Compiles a call to a closure-typed local via `call_indirect`. The local is
3548
+ /// statically typed `anyref` (closures share that placeholder type — see
3549
+ /// `plumTypeToValtype`), so every read of its fields `ref.cast`s down to the shared
3550
+ /// concrete closure struct type first. Stack order matches the closure function's
3541
- /// closure function's signature `(env_ptr, ...args)`: push the env pointer, then each
3551
+ /// signature `(env, ...args)`: push the env, then each argument, then the table
3542
- /// argument, then the table index (the `call_indirect` operand).
3552
+ /// index (the `call_indirect` operand).
3543
3553
  fn compileClosureCall(
3544
3554
  call: &ast::FnCall,
3545
3555
  body: &mut Vec<u8>,
@@ -3550,6 +3560,7 @@ fn compileClosureCall(
3550
3560
  .locals
3551
3561
  .get(&call.name)
3552
3562
  .ok_or_else(|| format!("codegen: closure '{}' is not a local", call.name))?;
3563
+ let closure_type_idx = ctx.gc_types.closure_type_idx;
3553
3564
 
3554
3565
  // Prefer the exact signature recorded when this local was assigned a closure
3555
3566
  // *literal* (see `closure_local_sigs`) — it's already correct. Otherwise (e.g.
@@ -3562,7 +3573,7 @@ fn compileClosureCall(
3562
3573
  PlumType::TFun(p, r) => (p, *r),
3563
3574
  other => return Err(format!("codegen: '{}' is not callable (type {:?})", call.name, other)),
3564
3575
  };
3565
- let mut sig_params = vec![ValType::I32]; // env pointer
3576
+ let mut sig_params = vec![ValType::Ref(RefType::ANYREF)]; // env pointer
3566
3577
  for p in &param_ptypes {
3567
3578
  sig_params.push(plumTypeToValtype(p));
3568
3579
  }
@@ -3578,16 +3589,18 @@ fn compileClosureCall(
3578
3589
  .get(&sig_key)
3579
3590
  .ok_or_else(|| format!("internal codegen error: no call_indirect type for closure '{}'", call.name))?;
3580
3591
 
3581
- // env pointer (struct offset 8)
3592
+ // env (closure struct field 1)
3582
3593
  Instruction::LocalGet(closure_local).encode(body);
3594
+ Instruction::RefCastNonNull(HeapType::Concrete(closure_type_idx)).encode(body);
3583
- Instruction::I32Load(MemArg { offset: 8, align: 2, memory_index: 0 }).encode(body);
3595
+ Instruction::StructGet { struct_type_index: closure_type_idx, field_index: 1 }.encode(body);
3584
3596
  // real arguments
3585
3597
  for arg in &call.args {
3586
3598
  compileExpr(argExprOf(arg), body, ctx, state)?;
3587
3599
  }
3588
- // table index (struct offset 0) — the call_indirect operand
3600
+ // table index (closure struct field 0) — the call_indirect operand
3589
3601
  Instruction::LocalGet(closure_local).encode(body);
3602
+ Instruction::RefCastNonNull(HeapType::Concrete(closure_type_idx)).encode(body);
3590
- Instruction::I32Load(MemArg { offset: 0, align: 2, memory_index: 0 }).encode(body);
3603
+ Instruction::StructGet { struct_type_index: closure_type_idx, field_index: 0 }.encode(body);
3591
3604
  Instruction::CallIndirect { type_index, table_index: 0 }.encode(body);
3592
3605
  Ok(())
3593
3606
  }
@@ -3617,12 +3630,9 @@ fn compileClosureBody(
3617
3630
  cctx: checkCtxOf(&ctx.classes, &ctx.methods, &ctx.enum_variants, &ctx.enum_params),
3618
3631
  named: Vec::new(),
3619
3632
  named_set: Default::default(),
3620
- classcall_scratch: HashMap::new(),
3621
3633
  match_scratch: HashMap::new(),
3622
- next_classcall_slot: 0,
3623
- closure_scratch: HashMap::new(),
3624
- next_closure_slot: 0,
3625
3634
  nested_class_scratch: HashMap::new(),
3635
+ nested_class_scratch_types: Vec::new(),
3626
3636
  next_nested_class_slot: 0,
3627
3637
  variadic_for_scratch: HashMap::new(),
3628
3638
  next_variadic_for_slot: 0,
@@ -3654,13 +3664,6 @@ fn compileClosureBody(
3654
3664
  idx += 1;
3655
3665
  }
3656
3666
 
3657
- let classcall_scratch_base = idx;
3658
- let classcall_count = collector.classcall_scratch.values().copied().max().map(|m| m + 1).unwrap_or(0);
3659
- for _ in 0..classcall_count {
3660
- groups.push(ValType::I32);
3661
- idx += 1;
3662
- }
3663
-
3664
3667
  let match_scratch_base = idx;
3665
3668
  let mut match_scratch_index: HashMap<usize, u32> = HashMap::new();
3666
3669
  for (ptr, types) in collector.match_scratch.iter() {
@@ -3672,9 +3675,14 @@ fn compileClosureBody(
3672
3675
  }
3673
3676
 
3674
3677
  let nested_class_scratch_base = idx;
3678
+ // Each slot is declared with its OWN concrete variant ref type (not a uniform
3679
+ // placeholder) — `struct.get` on a constructor-pattern match requires the local
3675
- let nested_class_scratch_count = collector.nested_class_scratch.values().copied().max().map(|m| m + 1).unwrap_or(0);
3680
+ // holding the narrowed (`ref.cast`) value to be statically typed as that exact
3681
+ // variant, and different slots very likely narrow to different variants.
3676
- for _ in 0..nested_class_scratch_count {
3682
+ for vname in &collector.nested_class_scratch_types {
3683
+ let variant_idx = withGcTypes(|r| *r.variant_type_idx.get(vname)
3684
+ .unwrap_or_else(|| panic!("internal codegen error: variant '{}' missing from the GC type registry", vname)));
3677
- groups.push(ValType::I32);
3685
+ groups.push(gcRef(variant_idx));
3678
3686
  idx += 1;
3679
3687
  }
3680
3688
 
@@ -3686,12 +3694,6 @@ fn compileClosureBody(
3686
3694
  idx += 2;
3687
3695
  }
3688
3696
 
3689
- let closure_scratch_base = idx;
3690
- let closure_scratch_count = collector.closure_scratch.values().copied().max().map(|m| m + 2).unwrap_or(0);
3691
- for _ in 0..closure_scratch_count {
3692
- groups.push(ValType::I32);
3693
- }
3694
-
3695
3697
  if groups.is_empty() {
3696
3698
  body.push(0);
3697
3699
  } else {
@@ -3702,22 +3704,20 @@ fn compileClosureBody(
3702
3704
  }
3703
3705
  }
3704
3706
 
3705
- // Restore each captured free variable from the env struct (local 0) at entry.
3707
+ // Restore each captured free variable from the env struct (local 0, statically
3708
+ // `anyref` — `ref.cast` down to THIS closure's own concrete env type) at entry.
3706
- for (i, (name, ty)) in info.free_vars.iter().enumerate() {
3709
+ for (i, (name, _)) in info.free_vars.iter().enumerate() {
3707
3710
  let local_idx = *locals.get(name).expect("free var local was assigned above");
3708
3711
  Instruction::LocalGet(0).encode(&mut body); // env pointer
3709
- emitLoad(plumTypeToValtype(ty), (i as u64) * 8, &mut body);
3712
+ Instruction::RefCastNonNull(HeapType::Concrete(info.env_type_idx)).encode(&mut body);
3713
+ Instruction::StructGet { struct_type_index: info.env_type_idx, field_index: i as u32 }.encode(&mut body);
3710
3714
  Instruction::LocalSet(local_idx).encode(&mut body);
3711
3715
  }
3712
3716
 
3713
3717
  let local_ctx = LocalCtx {
3714
3718
  locals,
3715
- classcall_scratch_base,
3716
- classcall_scratch: collector.classcall_scratch,
3717
3719
  match_scratch_base,
3718
3720
  match_scratch_index,
3719
- closure_scratch_base,
3720
- closure_scratch: collector.closure_scratch,
3721
3721
  nested_class_scratch_base,
3722
3722
  nested_class_scratch: collector.nested_class_scratch,
3723
3723
  variadic_for_scratch_base,
@@ -3733,9 +3733,10 @@ fn compileClosureBody(
3733
3733
  methods: &ctx.methods,
3734
3734
  enum_variants: &ctx.enum_variants,
3735
3735
  enum_params: &ctx.enum_params,
3736
+ gc_types: &ctx.gc_types,
3737
+ singleton_globals: &ctx.singleton_globals,
3736
3738
  type_env: RefCell::new(base_env),
3737
3739
  closure_local_sigs: RefCell::new(HashMap::new()),
3738
- bump_global: ctx.bump_global,
3739
3740
  };
3740
3741
 
3741
3742
  compileBlockAsFnBody(&cl.body, &mut body, &local_ctx, state, info.ret_vt)?;
plum-wasm-codegen/tests/codegen_tests.rs CHANGED
@@ -362,24 +362,34 @@ fn runMainF64(bytes: &[u8]) -> f64 {
362
362
  main.call(&mut store, ()).expect("main should not trap")
363
363
  }
364
364
 
365
- /// Runs a `() -> Str`-returning `main`, reading the length-prefixed
365
+ /// Runs a `() -> Str`-returning `main`, reading the returned `array<i8>` GC value
366
+ /// back out byte-by-byte via wasmtime's host-side GC ref API (`Str` has no length
367
+ /// prefix of its own now — `array.len` is native, see Decision 4 of the wasm-gc
368
+ /// migration plan) — untyped `Func::call` is used because `main`'s wasm return type
366
- /// `[len: u32][utf8 bytes]` string back out of the module's exported memory.
369
+ /// is a concrete `(ref $Str)`, not one `get_typed_func` can name directly.
367
370
  fn runMainStr(bytes: &[u8]) -> String {
368
371
  let engine = gcEngine();
369
372
  let module = wasmtime::Module::new(&engine, bytes).expect("module should be loadable");
370
373
  let mut store = wasmtime::Store::new(&engine, ());
371
374
  let instance = wasmtime::Instance::new(&mut store, &module, &[]).expect("module should instantiate");
372
- let main = instance
373
- .get_typed_func::<(), i32>(&mut store, "main")
374
- .expect("main should have signature () -> i32");
375
- let ptr = main.call(&mut store, ()).expect("main should not trap") as u32 as usize;
376
- let memory = instance.get_memory(&mut store, "memory").expect("module should export memory");
375
+ let main = instance.get_func(&mut store, "main").expect("module should export main");
377
- let mut len_bytes = [0u8; 4];
376
+ let mut results = [wasmtime::Val::null_any_ref()];
378
- memory.read(&store, ptr, &mut len_bytes).expect("string length read should be in bounds");
377
+ main.call(&mut store, &[], &mut results).expect("main should not trap");
378
+ let any_ref = match &results[0] {
379
+ wasmtime::Val::AnyRef(Some(r)) => *r,
380
+ other => panic!("main should return a non-null anyref (Str), got {:?}", other),
381
+ };
382
+ let array = any_ref.unwrap_array(&store).expect("Str's returned anyref should be a GC array");
383
+ let len = array.len(&store).expect("array.len should succeed");
379
- let len = u32::from_le_bytes(len_bytes) as usize;
384
+ let mut bytes_out = Vec::with_capacity(len as usize);
380
- let mut str_bytes = vec![0u8; len];
385
+ for i in 0..len {
381
- memory.read(&store, ptr + 4, &mut str_bytes).expect("string bytes read should be in bounds");
386
+ let byte = match array.get(&mut store, i).expect("array.get should succeed") {
387
+ wasmtime::Val::I32(b) => b as u8,
388
+ other => panic!("Str array element should be i32, got {:?}", other),
389
+ };
390
+ bytes_out.push(byte);
391
+ }
382
- String::from_utf8(str_bytes).expect("string bytes should be valid utf8")
392
+ String::from_utf8(bytes_out).expect("string bytes should be valid utf8")
383
393
  }
384
394
 
385
395
  #[test]