Skip to main content

submilli_engine/codegen/
intrinsics.rs

1//! Canonical intrinsic Wasm types every Submilli module hard-codes locally.
2//! Both the prelude and consumer codegen call [`declare_intrinsic_types`] to guarantee
3//! identical bytes; WasmGC structural canonicalization then unifies cross-module references.
4
5use wasm_encoder::{
6    CompositeInnerType, CompositeType, FieldType, FuncType, HeapType, RefType, StorageType,
7    StructType, SubType, TypeSection, ValType,
8};
9#[derive(Clone, Copy, Debug)]
10pub struct IntrinsicTypeIndices {
11    pub undefined: u32,
12    pub raw_string: u32,
13    pub vtable: u32,
14    pub object: u32,
15    pub string: u32,
16    pub boxed_number: u32,
17    pub boxed_boolean: u32,
18    pub field_names: u32,
19    pub object_shape: u32,
20    pub object_fields: u32,
21    pub to_string_fn: u32,
22    pub to_json_fn: u32,
23    pub equals_fn: u32,
24    pub hash_fn: u32,
25    pub field_getter: u32,
26    pub field_setter: u32,
27    pub raw_array: u32,
28    pub array: u32,
29    pub raw_uint8_array: u32,
30    pub uint8_array: u32,
31    pub closure: u32,
32    pub class_vtable: u32,
33    pub error_vtable: u32,
34    pub error: u32,
35    pub raw_bigint: u32,
36    pub bigint: u32,
37    pub regex_capture_array: u32,
38    pub regex_match: u32,
39    pub regex: u32,
40    pub regex_match_box: u32,
41    pub temporal_instant: u32,
42    pub temporal_duration: u32,
43    pub temporal_zdt: u32,
44    pub raw_index_array: u32,
45    pub map: u32,
46    pub set: u32,
47    pub url: u32,
48    pub fs_stat: u32,
49    pub fs_peek: u32,
50    pub fs_dir_entry: u32,
51    pub fs_info: u32,
52    pub fs_file_writer: u32,
53    pub http_response: u32,
54    pub http_download_result: u32,
55    pub session_entry: u32,
56    pub session_page: u32,
57    pub fs_mount_info: u32,
58
59    pub temporal_plain_date: u32,
60    pub temporal_plain_time: u32,
61    pub temporal_plain_date_time: u32,
62    pub temporal_plain_year_month: u32,
63    pub temporal_plain_month_day: u32,
64}
65
66/// Number of types [`declare_intrinsic_types`] emits — the first free type index
67/// in every module.
68pub const INTRINSIC_TYPE_COUNT: u32 = 52;
69
70pub fn declare_intrinsic_types(types: &mut TypeSection) -> IntrinsicTypeIndices {
71    let undefined = 51u32;
72    let raw_string = 0u32;
73    let vtable = 1u32;
74    let object = 2u32;
75    let string = 3u32;
76    let boxed_number = 4u32;
77    let boxed_boolean = 5u32;
78    let field_names = 6u32;
79    let object_fields = 7u32;
80    let object_shape = 8u32;
81    let to_string_fn = 9u32;
82    let to_json_fn = 10u32;
83    let equals_fn = 11u32;
84    let hash_fn = 12u32;
85    let field_getter = 13u32;
86    let field_setter = 14u32;
87    let raw_array = 15u32;
88    let array = 16u32;
89    let raw_uint8_array = 17u32;
90    let uint8_array = 18u32;
91    let closure = 19u32;
92    // `$ClassVTable` — nominal-identity base for class vtables (SUB-631). A
93    // self-referential singleton rec group `(sub $VTable (4 funcrefs, parent
94    // (ref null $ClassVTable), default-JSON i32))`. Only class vtables (and `$Error_vtable`)
95    // subtype it; every other vtable stays a plain `$VTable` subtype, so a
96    // `ref.cast (ref $ClassVTable)` classifies "is a class instance" and the
97    // parent field carries the `extends` chain for `instanceof`'s ref.eq walk.
98    let class_vtable = 20u32;
99    // `$Error` class pair, one rec group `(rec $Error_vtable $Error)`. Shaped
100    // byte-for-byte like the user-class emitter's output for
101    // `class Error { message: string; name: string }` (see
102    // `classes.rs::{vtable_subtype, struct_subtype}`) so a user
103    // `class MyError extends Error` reconstructs the parent via the ordinary
104    // class path and canonicalization unifies it with this pair. Fields live
105    // in the object-fields payload: slot 0 = message, slot 1 = name.
106    let error_vtable = 21u32;
107    let error = 22u32;
108    // bigint runtime types. Standalone (like `$Array` /
109    // `$Uint8Array`) — they reference rec-group members (`$Object`,
110    // `$VTable`) by index, no cyclic dep with this type's fields.
111    let raw_bigint = 23u32;
112    let bigint = 24u32;
113    // regex runtime types. Two standalone (`$RegExpCaptureArray`,
114    // `$RegExpMatch`) outside the `$Object` rec group so the host
115    // can allocate them via `wasmtime::StructType::with_finality_and_supertype`
116    // (they are not part of the recursive `$Object`/`$VTable` group). `$regex`
117    // is a standard `$Object` subtype declared standalone.
118    let regex_capture_array = 25u32;
119    let regex_match = 26u32;
120    let regex = 27u32;
121    let regex_match_box = 28u32;
122    // Temporal carrier types. All three standalone $Object
123    // subtypes — the runtime doesn't need to construct them via
124    // wasmtime's host-side `StructType::with_finality_and_supertype`
125    // (host returns the carrier fields, the prelude wrapper assembles
126    // the struct in Wasm), so the rec-group rules aren't a concern.
127    let temporal_instant = 29u32;
128    let temporal_duration = 30u32;
129    let temporal_zdt = 31u32;
130    let raw_index_array = 32u32;
131    let map = 33u32;
132    let set = 34u32;
133    let url = 35u32;
134    let fs_stat = 41u32;
135    let fs_peek = 42u32;
136    let fs_dir_entry = 43u32;
137    let fs_info = 44u32;
138    let fs_file_writer = 45u32;
139    let http_response = 46u32;
140    let http_download_result = 47u32;
141    let session_entry = 48u32;
142    let session_page = 49u32;
143    let fs_mount_info = 50u32;
144
145    let temporal_plain_date = 36u32;
146    let temporal_plain_time = 37u32;
147    let temporal_plain_date_time = 38u32;
148    let temporal_plain_year_month = 39u32;
149    let temporal_plain_month_day = 40u32;
150
151    // $rawString standalone — matches the runtime host's standalone
152    // ArrayType registration so cross-module canonicalization aligns.
153    types.ty().array(&StorageType::I16, true);
154
155    // $VTable / $Object / $string / boxed primitives / four method
156    // sigs — closed reference cycle (the methods reference $string
157    // and $Object), emitted as one rec group.
158    types.ty().rec(vec![
159        // $VTable — references the four method-signature types below
160        // by their forthcoming indices (forward refs in a rec group).
161        substruct(
162            vec![
163                fieldtype_ref(to_string_fn),
164                fieldtype_ref(to_json_fn),
165                fieldtype_ref(equals_fn),
166                fieldtype_ref(hash_fn),
167            ],
168            None,
169        ),
170        substruct(vec![fieldtype_ref(vtable)], None),
171        SubType {
172            is_final: false,
173            supertype_idx: Some(object),
174            composite_type: CompositeType {
175                inner: CompositeInnerType::Struct(StructType {
176                    fields: vec![
177                        fieldtype_ref(vtable),
178                        FieldType {
179                            element_type: StorageType::Val(ref_to(raw_string)),
180                            mutable: false,
181                        },
182                        FieldType {
183                            element_type: StorageType::Val(ValType::I64),
184                            mutable: true,
185                        },
186                    ]
187                    .into_boxed_slice(),
188                }),
189                shared: false,
190                descriptor: None,
191                describes: None,
192            },
193        },
194        SubType {
195            is_final: false,
196            supertype_idx: Some(object),
197            composite_type: CompositeType {
198                inner: CompositeInnerType::Struct(StructType {
199                    fields: vec![
200                        fieldtype_ref(vtable),
201                        FieldType {
202                            element_type: StorageType::Val(ValType::F64),
203                            mutable: false,
204                        },
205                    ]
206                    .into_boxed_slice(),
207                }),
208                shared: false,
209                descriptor: None,
210                describes: None,
211            },
212        },
213        SubType {
214            is_final: false,
215            supertype_idx: Some(object),
216            composite_type: CompositeType {
217                inner: CompositeInnerType::Struct(StructType {
218                    fields: vec![
219                        fieldtype_ref(vtable),
220                        FieldType {
221                            element_type: StorageType::Val(ValType::I32),
222                            mutable: false,
223                        },
224                    ]
225                    .into_boxed_slice(),
226                }),
227                shared: false,
228                descriptor: None,
229                describes: None,
230            },
231        },
232        // $field_names — `(array (ref $string))`. Per-shape canonical field-name list.
233        SubType {
234            is_final: false,
235            supertype_idx: None,
236            composite_type: CompositeType {
237                inner: CompositeInnerType::Array(wasm_encoder::ArrayType(FieldType {
238                    element_type: StorageType::Val(ref_to(string)),
239                    mutable: true,
240                })),
241                shared: false,
242                descriptor: None,
243                describes: None,
244            },
245        },
246        SubType {
247            is_final: false,
248            supertype_idx: None,
249            composite_type: CompositeType {
250                inner: CompositeInnerType::Array(wasm_encoder::ArrayType(FieldType {
251                    element_type: StorageType::Val(ref_null(object)),
252                    mutable: true,
253                })),
254                shared: false,
255                descriptor: None,
256                describes: None,
257            },
258        },
259        SubType {
260            is_final: false,
261            supertype_idx: Some(object),
262            composite_type: CompositeType {
263                inner: CompositeInnerType::Struct(StructType {
264                    fields: vec![
265                        fieldtype_ref(vtable),
266                        FieldType {
267                            element_type: StorageType::Val(ref_to(field_names)),
268                            mutable: true,
269                        },
270                        FieldType {
271                            element_type: StorageType::Val(ref_to(object_fields)),
272                            mutable: true,
273                        },
274                        FieldType {
275                            element_type: StorageType::Val(ValType::Ref(RefType::ANYREF)),
276                            mutable: true,
277                        },
278                    ]
279                    .into_boxed_slice(),
280                }),
281                shared: false,
282                descriptor: None,
283                describes: None,
284            },
285        },
286        // $toStringFn/$toJsonFn return (ref $string) intra-group — forces $string into this rec group.
287        subfunc(vec![ref_to(object)], vec![ref_to(string)]),
288        subfunc(vec![ref_to(object)], vec![ref_to(string)]),
289        subfunc(vec![ref_to(object), ref_to(object)], vec![ValType::I32]),
290        subfunc(vec![ref_to(object)], vec![ValType::I32]),
291        subfunc(
292            vec![ref_to(object_shape), ref_to(string)],
293            vec![ref_null(object)],
294        ),
295        subfunc(
296            vec![ref_to(object_shape), ref_to(string), ref_null(object)],
297            vec![],
298        ),
299    ]);
300
301    // $rawArray standalone — slot nullable so array.new_default works; language never stores null.
302    types.ty().array(
303        &StorageType::Val(ValType::Ref(RefType {
304            nullable: true,
305            heap_type: HeapType::Concrete(object),
306        })),
307        true,
308    );
309
310    // $Array retains spare backing capacity; field 2 is its logical length.
311    types.ty().subtype(&SubType {
312        is_final: false,
313        supertype_idx: Some(object),
314        composite_type: CompositeType {
315            inner: CompositeInnerType::Struct(StructType {
316                fields: vec![
317                    fieldtype_ref(vtable),
318                    FieldType {
319                        element_type: StorageType::Val(ref_to(raw_array)),
320                        mutable: true,
321                    },
322                    FieldType {
323                        element_type: StorageType::Val(ValType::I32),
324                        mutable: true,
325                    },
326                ]
327                .into_boxed_slice(),
328            }),
329            shared: false,
330            descriptor: None,
331            describes: None,
332        },
333    });
334
335    // $rawUint8Array standalone — packed `(array (mut i8))`. Standalone
336    // (like $rawString / $rawArray) so it canonicalizes with the host
337    // side's standalone `ArrayType::new` registration.
338    types.ty().array(&StorageType::I8, true);
339
340    // $Uint8Array field 1 is immutable — unlike $Array, no slot swap; mutation goes through array.set.
341    types.ty().subtype(&SubType {
342        is_final: false,
343        supertype_idx: Some(object),
344        composite_type: CompositeType {
345            inner: CompositeInnerType::Struct(StructType {
346                fields: vec![
347                    fieldtype_ref(vtable),
348                    FieldType {
349                        element_type: StorageType::Val(ref_to(raw_uint8_array)),
350                        mutable: false,
351                    },
352                ]
353                .into_boxed_slice(),
354            }),
355            shared: false,
356            descriptor: None,
357            describes: None,
358        },
359    });
360
361    // $Closure — non-final base; a single ref.test (ref $Closure) classifies any closure regardless of signature.
362    types.ty().subtype(&SubType {
363        is_final: false,
364        supertype_idx: Some(object),
365        composite_type: CompositeType {
366            inner: CompositeInnerType::Struct(StructType {
367                fields: vec![fieldtype_ref(vtable)].into_boxed_slice(),
368            }),
369            shared: false,
370            descriptor: None,
371            describes: None,
372        },
373    });
374    // `(rec $ClassVTable)` — its own rec group (the self-referential parent
375    // field needs one); see the index-table comment above.
376    types.ty().rec(vec![substruct(
377        vec![
378            fieldtype_ref(to_string_fn),
379            fieldtype_ref(to_json_fn),
380            fieldtype_ref(equals_fn),
381            fieldtype_ref(hash_fn),
382            FieldType {
383                element_type: StorageType::Val(ref_null(class_vtable)),
384                mutable: false,
385            },
386            FieldType {
387                element_type: StorageType::Val(ValType::I32),
388                mutable: false,
389            },
390        ],
391        Some(vtable),
392    )]);
393    // `(rec $Error_vtable $Error)` — see the index-table comment above for the
394    // shape constraint (must mirror `classes.rs::{vtable_subtype, struct_subtype}`).
395    types.ty().rec(vec![
396        substruct(
397            vec![
398                fieldtype_ref(to_string_fn),
399                fieldtype_ref(to_json_fn),
400                fieldtype_ref(equals_fn),
401                fieldtype_ref(hash_fn),
402                FieldType {
403                    element_type: StorageType::Val(ref_null(class_vtable)),
404                    mutable: false,
405                },
406                FieldType {
407                    element_type: StorageType::Val(ValType::I32),
408                    mutable: false,
409                },
410            ],
411            Some(class_vtable),
412        ),
413        substruct(
414            vec![
415                fieldtype_ref(error_vtable),
416                FieldType {
417                    mutable: true,
418                    ..fieldtype_ref(field_names)
419                },
420                FieldType {
421                    mutable: true,
422                    ..fieldtype_ref(object_fields)
423                },
424                FieldType {
425                    element_type: StorageType::Val(ValType::Ref(RefType::ANYREF)),
426                    mutable: true,
427                },
428                FieldType {
429                    element_type: StorageType::Val(ValType::I64),
430                    mutable: true,
431                },
432            ],
433            Some(object_shape),
434        ),
435    ]);
436
437    // $rawBigInt standalone — little-endian i64 limb array; standalone so host ArrayType::new canonicalizes.
438    types.ty().array(&StorageType::Val(ValType::I64), true);
439
440    // $bigint: field 1 = sign (−1/0/1, zero has sign==0); field 2 = immutable limbs (arithmetic returns fresh bigint).
441    types.ty().subtype(&SubType {
442        is_final: false,
443        supertype_idx: Some(object),
444        composite_type: CompositeType {
445            inner: CompositeInnerType::Struct(StructType {
446                fields: vec![
447                    fieldtype_ref(vtable),
448                    FieldType {
449                        element_type: StorageType::Val(ValType::I32),
450                        mutable: false,
451                    },
452                    FieldType {
453                        element_type: StorageType::Val(ref_to(raw_bigint)),
454                        mutable: false,
455                    },
456                ]
457                .into_boxed_slice(),
458            }),
459            shared: false,
460            descriptor: None,
461            describes: None,
462        },
463    });
464
465    // $RegExpCaptureArray standalone — nullable elements encode unmatched captures; standalone for host canonicalization.
466    types.ty().array(
467        &StorageType::Val(ValType::Ref(RefType {
468            nullable: true,
469            heap_type: HeapType::Concrete(raw_string),
470        })),
471        true,
472    );
473
474    // $RegExpMatch — standalone final struct (NOT $Object subtype); host allocates
475    // it via StructType::with_finality_and_supertype.
476    // Fields: 0=match text, 1=index, 2=input, 3=numbered captures, 4=named k/v pairs, 5=next_last_index.
477    types.ty().subtype(&SubType {
478        is_final: true,
479        supertype_idx: None,
480        composite_type: CompositeType {
481            inner: CompositeInnerType::Struct(StructType {
482                fields: vec![
483                    FieldType {
484                        element_type: StorageType::Val(ref_to(raw_string)),
485                        mutable: false,
486                    },
487                    FieldType {
488                        element_type: StorageType::Val(ValType::I32),
489                        mutable: false,
490                    },
491                    FieldType {
492                        element_type: StorageType::Val(ref_to(raw_string)),
493                        mutable: false,
494                    },
495                    FieldType {
496                        element_type: StorageType::Val(ref_to(regex_capture_array)),
497                        mutable: false,
498                    },
499                    FieldType {
500                        element_type: StorageType::Val(ref_to(regex_capture_array)),
501                        mutable: false,
502                    },
503                    FieldType {
504                        element_type: StorageType::Val(ValType::I32),
505                        mutable: false,
506                    },
507                ]
508                .into_boxed_slice(),
509            }),
510            shared: false,
511            descriptor: None,
512            describes: None,
513        },
514    });
515
516    // $regex field 1 = externref(ChargedRegex); GC reclaim runs Drop, releasing charged bytes from TenantLimits.
517    types.ty().subtype(&SubType {
518        is_final: false,
519        supertype_idx: Some(object),
520        composite_type: CompositeType {
521            inner: CompositeInnerType::Struct(StructType {
522                fields: vec![
523                    fieldtype_ref(vtable),
524                    FieldType {
525                        element_type: StorageType::Val(ValType::Ref(RefType::EXTERNREF)),
526                        mutable: false,
527                    },
528                    FieldType {
529                        element_type: StorageType::Val(ValType::I32),
530                        mutable: true,
531                    },
532                    FieldType {
533                        element_type: StorageType::Val(ref_to(string)),
534                        mutable: false,
535                    },
536                    FieldType {
537                        element_type: StorageType::Val(ref_to(string)),
538                        mutable: false,
539                    },
540                    FieldType {
541                        element_type: StorageType::Val(ValType::I32),
542                        mutable: false,
543                    },
544                    FieldType {
545                        element_type: StorageType::Val(ValType::I64),
546                        mutable: true,
547                    },
548                ]
549                .into_boxed_slice(),
550            }),
551            shared: false,
552            descriptor: None,
553            describes: None,
554        },
555    });
556
557    // $RegExpMatchBox: fields 1=match, 2=index, 3=input, 4=numbered captures, 5=alternating name/value array.
558    types.ty().subtype(&SubType {
559        is_final: false,
560        supertype_idx: Some(object),
561        composite_type: CompositeType {
562            inner: CompositeInnerType::Struct(StructType {
563                fields: vec![
564                    fieldtype_ref(vtable),
565                    FieldType {
566                        element_type: StorageType::Val(ref_to(string)),
567                        mutable: false,
568                    },
569                    FieldType {
570                        element_type: StorageType::Val(ValType::I32),
571                        mutable: false,
572                    },
573                    FieldType {
574                        element_type: StorageType::Val(ref_to(string)),
575                        mutable: false,
576                    },
577                    FieldType {
578                        element_type: StorageType::Val(ref_to(regex_capture_array)),
579                        mutable: false,
580                    },
581                    FieldType {
582                        element_type: StorageType::Val(ref_to(regex_capture_array)),
583                        mutable: false,
584                    },
585                    FieldType {
586                        element_type: StorageType::Val(ValType::I64),
587                        mutable: true,
588                    },
589                ]
590                .into_boxed_slice(),
591            }),
592            shared: false,
593            descriptor: None,
594            describes: None,
595        },
596    });
597
598    // $temporal_instant: field 1 = signed seconds since epoch, field 2 = subsecond nanos (0..=999_999_999).
599    types.ty().subtype(&SubType {
600        is_final: false,
601        supertype_idx: Some(object),
602        composite_type: CompositeType {
603            inner: CompositeInnerType::Struct(StructType {
604                fields: vec![
605                    fieldtype_ref(vtable),
606                    FieldType {
607                        element_type: StorageType::Val(ValType::I64),
608                        mutable: false,
609                    },
610                    FieldType {
611                        element_type: StorageType::Val(ValType::I32),
612                        mutable: false,
613                    },
614                ]
615                .into_boxed_slice(),
616            }),
617            shared: false,
618            descriptor: None,
619            describes: None,
620        },
621    });
622
623    // $temporal_duration: years/months/weeks/days as i32; hours..nanoseconds as i64 (avoids overflow for ns).
624    types.ty().subtype(&SubType {
625        is_final: false,
626        supertype_idx: Some(object),
627        composite_type: CompositeType {
628            inner: CompositeInnerType::Struct(StructType {
629                fields: vec![
630                    fieldtype_ref(vtable),
631                    FieldType {
632                        element_type: StorageType::Val(ValType::I32),
633                        mutable: false,
634                    }, // years
635                    FieldType {
636                        element_type: StorageType::Val(ValType::I32),
637                        mutable: false,
638                    }, // months
639                    FieldType {
640                        element_type: StorageType::Val(ValType::I32),
641                        mutable: false,
642                    }, // weeks
643                    FieldType {
644                        element_type: StorageType::Val(ValType::I32),
645                        mutable: false,
646                    }, // days
647                    FieldType {
648                        element_type: StorageType::Val(ValType::I64),
649                        mutable: false,
650                    }, // hours
651                    FieldType {
652                        element_type: StorageType::Val(ValType::I64),
653                        mutable: false,
654                    }, // minutes
655                    FieldType {
656                        element_type: StorageType::Val(ValType::I64),
657                        mutable: false,
658                    }, // seconds
659                    FieldType {
660                        element_type: StorageType::Val(ValType::I64),
661                        mutable: false,
662                    }, // milliseconds
663                    FieldType {
664                        element_type: StorageType::Val(ValType::I64),
665                        mutable: false,
666                    }, // microseconds
667                    FieldType {
668                        element_type: StorageType::Val(ValType::I64),
669                        mutable: false,
670                    }, // nanoseconds
671                ]
672                .into_boxed_slice(),
673            }),
674            shared: false,
675            descriptor: None,
676            describes: None,
677        },
678    });
679
680    // $temporal_zdt: epoch fields, zone ID, and host-owned resolved Zoned value.
681    types.ty().subtype(&SubType {
682        is_final: false,
683        supertype_idx: Some(object),
684        composite_type: CompositeType {
685            inner: CompositeInnerType::Struct(StructType {
686                fields: vec![
687                    fieldtype_ref(vtable),
688                    FieldType {
689                        element_type: StorageType::Val(ValType::I64),
690                        mutable: false,
691                    },
692                    FieldType {
693                        element_type: StorageType::Val(ValType::I32),
694                        mutable: false,
695                    },
696                    FieldType {
697                        element_type: StorageType::Val(ref_to(string)),
698                        mutable: false,
699                    },
700                    FieldType {
701                        element_type: StorageType::Val(ValType::Ref(RefType {
702                            nullable: false,
703                            heap_type: HeapType::EXTERN,
704                        })),
705                        mutable: false,
706                    },
707                ]
708                .into_boxed_slice(),
709            }),
710            shared: false,
711            descriptor: None,
712            describes: None,
713        },
714    });
715
716    // Generic collections use erased element slots but distinct backing
717    // structs. Keeping their canonical types in every module lets runtime
718    // interface guards distinguish Map and Set from unrelated host objects.
719    types.ty().array(&StorageType::Val(ValType::I32), true);
720    let bucket_field = FieldType {
721        element_type: StorageType::Val(ref_to(raw_array)),
722        mutable: true,
723    };
724    let order_field = FieldType {
725        element_type: StorageType::Val(ref_to(raw_index_array)),
726        mutable: true,
727    };
728    let mutable_i32 = FieldType {
729        element_type: StorageType::Val(ValType::I32),
730        mutable: true,
731    };
732    types.ty().subtype(&SubType {
733        is_final: false,
734        supertype_idx: Some(object),
735        composite_type: CompositeType {
736            inner: CompositeInnerType::Struct(StructType {
737                fields: vec![
738                    fieldtype_ref(vtable),
739                    bucket_field,
740                    bucket_field,
741                    mutable_i32,
742                    order_field,
743                    mutable_i32,
744                    order_field,
745                    order_field,
746                    FieldType {
747                        element_type: StorageType::Val(ValType::I64),
748                        mutable: true,
749                    },
750                ]
751                .into_boxed_slice(),
752            }),
753            shared: false,
754            descriptor: None,
755            describes: None,
756        },
757    });
758    types.ty().subtype(&SubType {
759        is_final: false,
760        supertype_idx: Some(object),
761        composite_type: CompositeType {
762            inner: CompositeInnerType::Struct(StructType {
763                fields: vec![
764                    fieldtype_ref(vtable),
765                    bucket_field,
766                    mutable_i32,
767                    order_field,
768                    mutable_i32,
769                    order_field,
770                    order_field,
771                    FieldType {
772                        element_type: StorageType::Val(ValType::I64),
773                        mutable: true,
774                    },
775                ]
776                .into_boxed_slice(),
777            }),
778            shared: false,
779            descriptor: None,
780            describes: None,
781        },
782    });
783
784    // `$UrlBacking` mirrors `stdlib::url::url_backing_struct`. Keeping the
785    // exact final layout here lets a checked interface read distinguish URL
786    // values from every other host-built `$Object` subtype before direct
787    // property dispatch sees the receiver.
788    types.ty().subtype(&SubType {
789        is_final: true,
790        supertype_idx: Some(object),
791        composite_type: CompositeType {
792            inner: CompositeInnerType::Struct(StructType {
793                fields: vec![
794                    fieldtype_ref(vtable),
795                    fieldtype_ref(string),
796                    fieldtype_ref(string),
797                    // port: `number | undefined`
798                    FieldType {
799                        element_type: StorageType::Val(ref_null(object)),
800                        mutable: false,
801                    },
802                    fieldtype_ref(string),
803                    FieldType {
804                        element_type: StorageType::Val(ref_null(object)),
805                        mutable: false,
806                    },
807                    // fragment: `string | undefined`
808                    FieldType {
809                        element_type: StorageType::Val(ref_null(object)),
810                        mutable: false,
811                    },
812                ]
813                .into_boxed_slice(),
814            }),
815            shared: false,
816            descriptor: None,
817            describes: None,
818        },
819    });
820
821    // Temporal plain-value carriers are host-created standalone `$Object`
822    // subtypes. Their immutable civil fields are all i32 values; declaring the
823    // exact layouts here gives erased interface boundaries nominally precise
824    // representation tests without exposing the backing structs to user code.
825    let temporal_i32 = FieldType {
826        element_type: StorageType::Val(ValType::I32),
827        mutable: false,
828    };
829    for civil_field_count in [3usize, 4, 7, 2] {
830        let mut fields = Vec::with_capacity(civil_field_count + 1);
831        fields.push(fieldtype_ref(vtable));
832        fields.extend(std::iter::repeat_n(temporal_i32, civil_field_count));
833        types.ty().subtype(&substruct(fields, Some(object)));
834    }
835    // PlainMonthDay would otherwise canonicalize with PlainYearMonth (both
836    // carry two i32s). A hidden immutable marker makes the host backing
837    // nominally distinct while leaving its public field indices unchanged.
838    types.ty().subtype(&substruct(
839        vec![
840            fieldtype_ref(vtable),
841            temporal_i32,
842            temporal_i32,
843            FieldType {
844                element_type: StorageType::Val(ValType::I64),
845                mutable: false,
846            },
847        ],
848        Some(object),
849    ));
850
851    // Exact standalone carriers for direct host interfaces. The hidden i64
852    // markers distinguish fs interfaces whose public payload layouts coincide.
853    let host_f64 = FieldType {
854        element_type: StorageType::Val(ValType::F64),
855        mutable: false,
856    };
857    let host_i64 = FieldType {
858        element_type: StorageType::Val(ValType::I64),
859        mutable: false,
860    };
861    let host_externref = FieldType {
862        element_type: StorageType::Val(ValType::EXTERNREF),
863        mutable: false,
864    };
865    let host_object = FieldType {
866        element_type: StorageType::Val(ref_null(object)),
867        mutable: false,
868    };
869    // fs_stat
870    types.ty().subtype(&SubType {
871        is_final: true,
872        ..substruct(
873            vec![
874                fieldtype_ref(vtable),
875                fieldtype_ref(string),
876                host_f64,
877                host_f64,
878            ],
879            Some(object),
880        )
881    });
882    // fs_peek
883    types.ty().subtype(&SubType {
884        is_final: true,
885        ..substruct(
886            vec![
887                fieldtype_ref(vtable),
888                fieldtype_ref(string),
889                fieldtype_ref(string),
890                fieldtype_ref(string),
891                host_f64,
892            ],
893            Some(object),
894        )
895    });
896    // fs_dir_entry
897    types.ty().subtype(&SubType {
898        is_final: true,
899        ..substruct(
900            vec![
901                fieldtype_ref(vtable),
902                fieldtype_ref(string),
903                fieldtype_ref(string),
904                fieldtype_ref(string),
905                host_f64,
906                host_i64,
907            ],
908            Some(object),
909        )
910    });
911    // fs_info
912    types.ty().subtype(&SubType {
913        is_final: true,
914        ..substruct(
915            vec![
916                fieldtype_ref(vtable),
917                fieldtype_ref(string),
918                host_f64,
919                fieldtype_ref(string),
920                fieldtype_ref(string),
921                fieldtype_ref(array),
922                host_i64,
923            ],
924            Some(object),
925        )
926    });
927    // fs_file_writer
928    types.ty().subtype(&SubType {
929        is_final: true,
930        ..substruct(vec![fieldtype_ref(vtable), host_externref], Some(object))
931    });
932    // http_response
933    types.ty().subtype(&SubType {
934        is_final: true,
935        ..substruct(
936            vec![
937                fieldtype_ref(vtable),
938                fieldtype_ref(string),
939                host_object,
940                temporal_i32,
941                host_f64,
942                fieldtype_ref(string),
943                fieldtype_ref(string),
944            ],
945            Some(object),
946        )
947    });
948    // http_download_result
949    types.ty().subtype(&SubType {
950        is_final: true,
951        ..substruct(
952            vec![
953                fieldtype_ref(vtable),
954                host_f64,
955                fieldtype_ref(string),
956                host_f64,
957                fieldtype_ref(string),
958                fieldtype_ref(string),
959                host_f64,
960            ],
961            Some(object),
962        )
963    });
964    // session_entry
965    types.ty().subtype(&SubType {
966        is_final: true,
967        ..substruct(
968            vec![fieldtype_ref(vtable), fieldtype_ref(string), host_f64],
969            Some(object),
970        )
971    });
972    // session_page: `nextCursor` is `string | undefined`
973    types.ty().subtype(&SubType {
974        is_final: true,
975        ..substruct(
976            vec![
977                fieldtype_ref(vtable),
978                fieldtype_ref(array),
979                FieldType {
980                    element_type: StorageType::Val(ref_null(object)),
981                    mutable: false,
982                },
983            ],
984            Some(object),
985        )
986    });
987    // fs_mount_info
988    types.ty().subtype(&SubType {
989        is_final: true,
990        ..substruct(
991            vec![
992                fieldtype_ref(vtable),
993                fieldtype_ref(string),
994                fieldtype_ref(string),
995                fieldtype_ref(string),
996                fieldtype_ref(string),
997                host_f64,
998                temporal_i32,
999            ],
1000            Some(object),
1001        )
1002    });
1003
1004    // A separate final subtype makes undefined distinguishable from every
1005    // boxed primitive and from a plain object after generic erasure.
1006    types.ty().subtype(&SubType {
1007        is_final: true,
1008        ..substruct(
1009            vec![
1010                fieldtype_ref(vtable),
1011                FieldType {
1012                    element_type: StorageType::Val(ValType::I64),
1013                    mutable: false,
1014                },
1015            ],
1016            Some(object),
1017        )
1018    });
1019
1020    IntrinsicTypeIndices {
1021        undefined,
1022        raw_string,
1023        vtable,
1024        object,
1025        string,
1026        boxed_number,
1027        boxed_boolean,
1028        field_names,
1029        object_shape,
1030        object_fields,
1031        to_string_fn,
1032        to_json_fn,
1033        equals_fn,
1034        hash_fn,
1035        field_getter,
1036        field_setter,
1037        raw_array,
1038        array,
1039        raw_uint8_array,
1040        uint8_array,
1041        closure,
1042        class_vtable,
1043        error_vtable,
1044        error,
1045        raw_bigint,
1046        bigint,
1047        regex_capture_array,
1048        regex_match,
1049        regex,
1050        regex_match_box,
1051        temporal_instant,
1052        temporal_duration,
1053        temporal_zdt,
1054        raw_index_array,
1055        map,
1056        set,
1057        url,
1058        fs_stat,
1059        fs_peek,
1060        fs_dir_entry,
1061        fs_info,
1062        fs_file_writer,
1063        http_response,
1064        http_download_result,
1065        session_entry,
1066        session_page,
1067        fs_mount_info,
1068        temporal_plain_date,
1069        temporal_plain_time,
1070        temporal_plain_date_time,
1071        temporal_plain_year_month,
1072        temporal_plain_month_day,
1073    }
1074}
1075
1076/// Child → parent for every intrinsic type [`declare_intrinsic_types`] gives a
1077/// supertype, so coercion can tell an implicit upcast from one needing a
1078/// `ref.cast` (`SymbolTable::ref_fits_slot`).
1079///
1080/// A second spelling of what the ordered index table above already encodes —
1081/// which is the price of leaving that table alone. The
1082/// `intrinsic_supertypes_match_declarations` test re-parses the emitted section
1083/// and holds the two together.
1084pub(crate) fn intrinsic_supertypes(
1085    indices: IntrinsicTypeIndices,
1086) -> impl IntoIterator<Item = (u32, u32)> {
1087    [
1088        (indices.undefined, indices.object),
1089        (indices.string, indices.object),
1090        (indices.boxed_number, indices.object),
1091        (indices.boxed_boolean, indices.object),
1092        (indices.object_shape, indices.object),
1093        (indices.array, indices.object),
1094        (indices.uint8_array, indices.object),
1095        (indices.closure, indices.object),
1096        (indices.class_vtable, indices.vtable),
1097        (indices.error_vtable, indices.class_vtable),
1098        (indices.error, indices.object_shape),
1099        (indices.bigint, indices.object),
1100        (indices.regex, indices.object),
1101        (indices.regex_match_box, indices.object),
1102        (indices.temporal_instant, indices.object),
1103        (indices.temporal_duration, indices.object),
1104        (indices.temporal_zdt, indices.object),
1105        (indices.map, indices.object),
1106        (indices.set, indices.object),
1107        (indices.url, indices.object),
1108        (indices.fs_stat, indices.object),
1109        (indices.fs_peek, indices.object),
1110        (indices.fs_dir_entry, indices.object),
1111        (indices.fs_info, indices.object),
1112        (indices.fs_file_writer, indices.object),
1113        (indices.http_response, indices.object),
1114        (indices.http_download_result, indices.object),
1115        (indices.session_entry, indices.object),
1116        (indices.session_page, indices.object),
1117        (indices.fs_mount_info, indices.object),
1118        (indices.temporal_plain_date, indices.object),
1119        (indices.temporal_plain_time, indices.object),
1120        (indices.temporal_plain_date_time, indices.object),
1121        (indices.temporal_plain_year_month, indices.object),
1122        (indices.temporal_plain_month_day, indices.object),
1123    ]
1124}
1125
1126/// Append instructions building a fresh `(ref $string)` for a short
1127/// codegen-owned literal: string vtable + `array.new_fixed` of its UTF-16 units.
1128pub(crate) fn push_string_literal(
1129    f: &mut wasm_encoder::Function,
1130    intrinsics: IntrinsicTypeIndices,
1131    string_vtable_global_idx: u32,
1132    text: &str,
1133) -> Result<(), crate::compiler_error::CompilerFailure> {
1134    use wasm_encoder::Instruction;
1135    f.instruction(&Instruction::GlobalGet(string_vtable_global_idx));
1136    let mut len = 0usize;
1137    for unit in text.encode_utf16() {
1138        f.instruction(&Instruction::I32Const(i32::from(unit)));
1139        len = len.saturating_add(1);
1140    }
1141    f.instruction(&Instruction::ArrayNewFixed {
1142        array_type_index: intrinsics.raw_string,
1143        array_size: super::wasm_u32(len)?,
1144    });
1145    f.instruction(&Instruction::I64Const(0));
1146    f.instruction(&Instruction::StructNew(intrinsics.string));
1147    Ok(())
1148}
1149
1150fn ref_to(idx: u32) -> ValType {
1151    ValType::Ref(RefType {
1152        nullable: false,
1153        heap_type: HeapType::Concrete(idx),
1154    })
1155}
1156
1157fn ref_null(idx: u32) -> ValType {
1158    ValType::Ref(RefType {
1159        nullable: true,
1160        heap_type: HeapType::Concrete(idx),
1161    })
1162}
1163
1164fn fieldtype_ref(idx: u32) -> FieldType {
1165    FieldType {
1166        element_type: StorageType::Val(ref_to(idx)),
1167        mutable: false,
1168    }
1169}
1170
1171fn substruct(fields: Vec<FieldType>, supertype: Option<u32>) -> SubType {
1172    SubType {
1173        is_final: false,
1174        supertype_idx: supertype,
1175        composite_type: CompositeType {
1176            inner: CompositeInnerType::Struct(StructType {
1177                fields: fields.into_boxed_slice(),
1178            }),
1179            shared: false,
1180            descriptor: None,
1181            describes: None,
1182        },
1183    }
1184}
1185
1186fn subfunc(params: Vec<ValType>, results: Vec<ValType>) -> SubType {
1187    SubType {
1188        is_final: false,
1189        supertype_idx: None,
1190        composite_type: CompositeType {
1191            inner: CompositeInnerType::Func(FuncType::new(params, results)),
1192            shared: false,
1193            descriptor: None,
1194            describes: None,
1195        },
1196    }
1197}
1198
1199#[cfg(test)]
1200mod tests {
1201    use std::collections::BTreeMap;
1202
1203    use super::{declare_intrinsic_types, intrinsic_supertypes};
1204    use wasm_encoder::{Module, TypeSection};
1205    use wasmparser::{Parser, Payload, Validator};
1206
1207    #[test]
1208    fn intrinsic_supertypes_match_declarations() {
1209        let mut module = Module::new();
1210        let mut types = TypeSection::new();
1211        let indices = declare_intrinsic_types(&mut types);
1212        module.section(&types);
1213        let bytes = module.finish();
1214
1215        let mut declared: BTreeMap<u32, u32> = BTreeMap::new();
1216        let mut next_idx = 0u32;
1217        for payload in Parser::new(0).parse_all(&bytes) {
1218            let Payload::TypeSection(reader) = payload.expect("parse intrinsic module") else {
1219                continue;
1220            };
1221            for group in reader {
1222                for sub in group.expect("read rec group").into_types() {
1223                    if let Some(parent) = sub.supertype_idx {
1224                        let parent = parent
1225                            .as_module_index()
1226                            .expect("intrinsic supertypes are module-level indices");
1227                        declared.insert(next_idx, parent);
1228                    }
1229                    next_idx += 1;
1230                }
1231            }
1232        }
1233
1234        let table: BTreeMap<u32, u32> = intrinsic_supertypes(indices).into_iter().collect();
1235        assert_eq!(
1236            table, declared,
1237            "intrinsic_supertypes is out of step with declare_intrinsic_types",
1238        );
1239    }
1240
1241    #[test]
1242    fn intrinsic_types_self_contained_and_validate() {
1243        let mut module = Module::new();
1244        let mut types = TypeSection::new();
1245        let indices = declare_intrinsic_types(&mut types);
1246        module.section(&types);
1247
1248        let bytes = module.finish();
1249        Validator::new()
1250            .validate_all(&bytes)
1251            .expect("intrinsic types validate as a stand-alone module");
1252
1253        assert_eq!(indices.raw_string, 0);
1254        assert_eq!(indices.vtable, 1);
1255        assert_eq!(indices.object, 2);
1256        assert_eq!(indices.string, 3);
1257        assert_eq!(indices.boxed_number, 4);
1258        assert_eq!(indices.boxed_boolean, 5);
1259        assert_eq!(indices.field_names, 6);
1260        assert_eq!(indices.object_fields, 7);
1261        assert_eq!(indices.object_shape, 8);
1262        assert_eq!(indices.to_string_fn, 9);
1263        assert_eq!(indices.to_json_fn, 10);
1264        assert_eq!(indices.equals_fn, 11);
1265        assert_eq!(indices.hash_fn, 12);
1266        assert_eq!(indices.field_getter, 13);
1267        assert_eq!(indices.field_setter, 14);
1268        assert_eq!(indices.raw_array, 15);
1269        assert_eq!(indices.array, 16);
1270        assert_eq!(indices.raw_uint8_array, 17);
1271        assert_eq!(indices.uint8_array, 18);
1272        assert_eq!(indices.closure, 19);
1273        assert_eq!(indices.class_vtable, 20);
1274        assert_eq!(indices.error_vtable, 21);
1275        assert_eq!(indices.error, 22);
1276        assert_eq!(indices.raw_bigint, 23);
1277        assert_eq!(indices.bigint, 24);
1278        assert_eq!(indices.regex_capture_array, 25);
1279        assert_eq!(indices.regex_match, 26);
1280        assert_eq!(indices.regex, 27);
1281        assert_eq!(indices.regex_match_box, 28);
1282        assert_eq!(indices.temporal_instant, 29);
1283        assert_eq!(indices.temporal_duration, 30);
1284        assert_eq!(indices.temporal_zdt, 31);
1285        assert_eq!(indices.raw_index_array, 32);
1286        assert_eq!(indices.map, 33);
1287        assert_eq!(indices.set, 34);
1288        assert_eq!(indices.url, 35);
1289        assert_eq!(indices.temporal_plain_date, 36);
1290        assert_eq!(indices.temporal_plain_time, 37);
1291        assert_eq!(indices.temporal_plain_date_time, 38);
1292        assert_eq!(indices.temporal_plain_year_month, 39);
1293        assert_eq!(indices.temporal_plain_month_day, 40);
1294        assert_eq!(indices.fs_mount_info, 50);
1295        assert_eq!(indices.undefined, 51);
1296        assert_eq!(super::INTRINSIC_TYPE_COUNT, 52);
1297    }
1298}