1use std::collections::BTreeSet;
4
5use wasm_encoder::{
6 BlockType, ConstExpr, Function, GlobalSection, GlobalType, HeapType, Instruction, RefType,
7 ValType,
8};
9
10use crate::codegen::intrinsics::IntrinsicTypeIndices;
11use crate::codegen::symbol_table::SymbolTable;
12use crate::codegen::{GuardedBodies, internal_failure, next_index, wasm_u32};
13use crate::compiler_error::CompilerFailure;
14use crate::{ObjectField, Shape, Type, TypeInfoIndex, TypeInfoTable};
15
16pub fn collect_object_shapes<'a>(
17 dependency_shapes: impl IntoIterator<Item = &'a Shape>,
18 own_shapes: &[Shape],
19) -> Vec<Type> {
20 let mut entries: BTreeSet<Type> = BTreeSet::new();
22 for shape in dependency_shapes {
23 entries.insert(canonical_type(shape));
24 }
25 for shape in own_shapes {
26 entries.insert(canonical_type(shape));
27 }
28
29 let mut sorted: Vec<Type> = Vec::new();
32 let mut visited: BTreeSet<Type> = BTreeSet::new();
33 let keys: Vec<Type> = entries.iter().cloned().collect();
34 for ty in &keys {
35 topo_visit_kind(ty, &entries, &mut visited, &mut sorted);
36 }
37 sorted
38 .into_iter()
39 .filter(|ty| matches!(ty, Type::Object { .. }))
40 .collect()
41}
42
43fn canonical_type(shape: &Shape) -> Type {
44 match shape {
45 Shape::Object { fields, index } => Type::Object {
46 index: index.clone(),
47 fields: fields.clone(),
48 },
49 Shape::Array(elem) => Type::Array(elem.clone()),
50 Shape::Tuple(elements) => Type::Tuple(elements.clone()),
51 Shape::Union(members) => Type::Union(members.clone()),
53 }
54}
55
56fn topo_visit_kind(
57 ty: &Type,
58 entries: &BTreeSet<Type>,
59 visited: &mut BTreeSet<Type>,
60 sorted: &mut Vec<Type>,
61) {
62 if visited.contains(ty) {
63 return;
64 }
65 let Some(ty) = entries.get(ty) else {
66 return;
68 };
69 visited.insert(ty.clone());
70 match ty {
72 Type::Object { fields, .. } => {
73 for inner in fields.values() {
74 topo_visit_kind(inner.ty.peel(), entries, visited, sorted);
75 }
76 }
77 Type::Array(elem) => {
78 topo_visit_kind(elem.peel(), entries, visited, sorted);
79 }
80 Type::Tuple(elements) => {
81 for inner in elements {
82 topo_visit_kind(inner.peel(), entries, visited, sorted);
83 }
84 }
85 Type::Union(members) => {
86 for m in members {
87 topo_visit_kind(m.peel(), entries, visited, sorted);
88 }
89 }
90 _ => {}
91 }
92 sorted.push(ty.clone());
93}
94
95#[derive(Clone, Debug)]
98pub struct UserSubtype {
99 pub ty: Type,
100 pub to_string_func: u32,
101 pub to_json_func: u32,
102 pub equals_func: u32,
103 pub hash_func: u32,
104 pub guarded_bodies: GuardedBodies,
105 pub vtable_global_idx: u32,
106}
107
108impl UserSubtype {
109 fn fields(&self) -> Result<&std::collections::BTreeMap<String, ObjectField>, CompilerFailure> {
110 match &self.ty {
111 Type::Object { fields, .. } => Ok(fields),
112 _ => Err(internal_failure(
113 "a structural subtype was allocated for a non-object type",
114 )),
115 }
116 }
117}
118
119pub fn allocate_methods(
121 ty: &Type,
122 next_func_idx: &mut u32,
123) -> Result<UserSubtype, CompilerFailure> {
124 Ok(UserSubtype {
125 ty: ty.clone(),
126 to_string_func: next_index(next_func_idx)?,
127 to_json_func: next_index(next_func_idx)?,
128 equals_func: next_index(next_func_idx)?,
129 hash_func: next_index(next_func_idx)?,
130 guarded_bodies: GuardedBodies {
132 to_json: next_index(next_func_idx)?,
133 equals: next_index(next_func_idx)?,
134 hash: next_index(next_func_idx)?,
135 },
136 vtable_global_idx: 0, })
138}
139
140pub fn needs_host_object_to_json_adapter(types: &[Type]) -> bool {
141 types.iter().any(|ty| match ty.peel() {
142 Type::Object { fields, .. } => !fields.contains_key("toJson"),
143 _ => false,
144 })
145}
146
147pub fn emit_method_function_entries(
149 functions: &mut wasm_encoder::FunctionSection,
150 subtypes: &[UserSubtype],
151 intrinsics: IntrinsicTypeIndices,
152) {
153 for _ in subtypes {
154 functions.function(intrinsics.to_string_fn);
155 functions.function(intrinsics.to_json_fn);
156 functions.function(intrinsics.equals_fn);
157 functions.function(intrinsics.hash_fn);
158 functions.function(intrinsics.to_json_fn);
159 functions.function(intrinsics.equals_fn);
160 functions.function(intrinsics.hash_fn);
161 }
162}
163
164pub fn emit_vtable_globals(
165 globals: &mut GlobalSection,
166 subtypes: &mut [UserSubtype],
167 symbols: &mut SymbolTable,
168 next_global_idx: &mut u32,
169 intrinsics: IntrinsicTypeIndices,
170) -> Result<(), CompilerFailure> {
171 for subtype in subtypes.iter_mut() {
172 let init = ConstExpr::extended([
173 Instruction::RefFunc(subtype.to_string_func),
174 Instruction::RefFunc(subtype.to_json_func),
175 Instruction::RefFunc(subtype.equals_func),
176 Instruction::RefFunc(subtype.hash_func),
177 Instruction::StructNew(intrinsics.vtable),
178 ]);
179 globals.global(
180 GlobalType {
181 val_type: ref_to(intrinsics.vtable),
182 mutable: false,
183 shared: false,
184 },
185 &init,
186 );
187 let idx = next_index(next_global_idx)?;
188 subtype.vtable_global_idx = idx;
189 symbols.record_vtable_global(subtype.ty.clone(), idx);
190 }
191 Ok(())
192}
193
194pub fn emit_method_bodies(
195 code: &mut wasm_encoder::CodeSection,
196 subtypes: &[UserSubtype],
197 symbols: &SymbolTable,
198 type_info: &TypeInfoTable,
199 type_info_index: &TypeInfoIndex,
200 pkg_string_global_idx: Option<u32>,
201) -> Result<(), crate::compiler_error::CompilerFailure> {
202 let intrinsics = symbols
203 .intrinsic_type_indices()
204 .ok_or_else(|| internal_failure("intrinsic types are not declared"))?;
205 let string_vtable_global_idx = symbols
206 .prelude_global_idx("string_vtable")
207 .ok_or_else(|| internal_failure("string_vtable is not imported from the prelude"))?;
208 let string_concat_func_idx = symbols
209 .prelude_func_idx("string_concat")
210 .ok_or_else(|| internal_failure("string_concat is not imported from the prelude"))?;
211 let string_eq_func_idx = symbols
212 .prelude_func_idx("string_eq")
213 .ok_or_else(|| internal_failure("string_eq is not imported from the prelude"))?;
214
215 let object_vtable_global = symbols
216 .prelude_global_idx("object_vtable")
217 .ok_or_else(|| internal_failure("object_vtable is not imported from the prelude"))?;
218 for subtype in subtypes {
219 code.function(&emit_subtype_to_string_body(
220 subtype,
221 intrinsics,
222 symbols,
223 string_vtable_global_idx,
224 object_vtable_global,
225 )?);
226
227 let bodies = subtype.guarded_bodies;
228 for (body, params, result) in [
229 (bodies.to_json, 1, ref_to(intrinsics.string)),
230 (bodies.equals, 2, ValType::I32),
231 (bodies.hash, 1, ValType::I32),
232 ] {
233 code.function(&super::vtable_walk::guarded_body(
234 body, params, result, symbols,
235 )?);
236 }
237 let host_json_type = type_info_index
240 .object_type_id(type_info, &subtype.ty)
241 .filter(|_| type_info.supports_host_json_object(&subtype.ty));
242 code.function(&emit_subtype_to_json_body(
243 subtype,
244 intrinsics,
245 symbols,
246 host_json_type,
247 pkg_string_global_idx,
248 string_concat_func_idx,
249 string_vtable_global_idx,
250 )?);
251
252 code.function(&emit_subtype_equals_body(
253 subtype,
254 intrinsics,
255 symbols,
256 string_eq_func_idx,
257 object_vtable_global,
258 )?);
259
260 code.function(&emit_subtype_hash_body(
261 subtype,
262 intrinsics,
263 symbols,
264 object_vtable_global,
265 )?);
266 }
267 Ok(())
268}
269
270fn emit_subtype_to_string_body(
271 subtype: &UserSubtype,
272 intrinsics: IntrinsicTypeIndices,
273 symbols: &SymbolTable,
274 string_vtable_global_idx: u32,
275 object_vtable_global: u32,
276) -> Result<Function, CompilerFailure> {
277 let fields = subtype.fields()?;
278
279 if !fields.contains_key("toString") {
280 let mut f = Function::new(std::iter::empty());
281 emit_indexed_object_dispatch(
282 &mut f,
283 intrinsics,
284 object_vtable_global,
285 IndexedSlot::ToString,
286 );
287 crate::codegen::intrinsics::push_string_literal(
288 &mut f,
289 intrinsics,
290 string_vtable_global_idx,
291 "[object Object]",
292 )?;
293 f.instruction(&Instruction::End);
294 return Ok(f);
295 }
296
297 emit_conversion_override_body(subtype, intrinsics, symbols, ConversionSlot::ToString)
298}
299
300fn emit_subtype_to_json_body(
301 subtype: &UserSubtype,
302 intrinsics: IntrinsicTypeIndices,
303 symbols: &SymbolTable,
304 host_json_type: Option<crate::TypeInfoId>,
305 pkg_string_global_idx: Option<u32>,
306 string_concat_func_idx: u32,
307 string_vtable_global_idx: u32,
308) -> Result<Function, CompilerFailure> {
309 let fields = subtype.fields()?;
310
311 if fields.contains_key("toJson") {
312 return emit_conversion_override_body(subtype, intrinsics, symbols, ConversionSlot::ToJson);
313 }
314
315 let Some(type_id) = host_json_type else {
316 return emit_subtype_to_json_vtable_body(
317 subtype,
318 intrinsics,
319 string_concat_func_idx,
320 string_vtable_global_idx,
321 symbols,
322 );
323 };
324 let pkg_string_global_idx = pkg_string_global_idx
325 .ok_or_else(|| internal_failure("host object serialization needs the package string"))?;
326 let stringify_func_idx = symbols
327 .func_idx(&crate::mangle::host(
328 crate::runtime::JSON_MODULE_NAME,
329 "stringifyTypedObject",
330 ))
331 .ok_or_else(|| internal_failure("submilli:json.stringifyTypedObject is not imported"))?;
332
333 let mut f = Function::new(std::iter::empty());
334
335 f.instruction(&Instruction::GlobalGet(string_vtable_global_idx));
336
337 f.instruction(&Instruction::GlobalGet(pkg_string_global_idx));
338 f.instruction(&Instruction::RefAsNonNull);
339 f.instruction(&Instruction::StructGet {
340 struct_type_index: intrinsics.string,
341 field_index: 1,
342 });
343 f.instruction(&Instruction::I32Const(type_id.as_u32().cast_signed()));
344 f.instruction(&Instruction::LocalGet(0));
345 f.instruction(&Instruction::RefCastNonNull(HeapType::Concrete(
346 intrinsics.object_shape,
347 )));
348 f.instruction(&Instruction::Call(stringify_func_idx));
349 f.instruction(&Instruction::I64Const(0));
350 f.instruction(&Instruction::StructNew(intrinsics.string));
351 f.instruction(&Instruction::End);
352 Ok(f)
353}
354
355pub(super) fn emit_grown_object_to_json(
357 function: &mut Function,
358 intrinsics: IntrinsicTypeIndices,
359 symbols: &SymbolTable,
360 original_len: u32,
361) -> Result<(), CompilerFailure> {
362 let dynamic_serializer = symbols
363 .prelude_func_idx("ObjectConstructor##toJson")
364 .ok_or_else(|| internal_failure("the dynamic object serializer is not imported"))?;
365 function.instruction(&Instruction::LocalGet(0));
366 function.instruction(&Instruction::RefCastNonNull(HeapType::Concrete(
367 intrinsics.object_shape,
368 )));
369 function.instruction(&Instruction::StructGet {
370 struct_type_index: intrinsics.object_shape,
371 field_index: 1,
372 });
373 function.instruction(&Instruction::ArrayLen);
374 function.instruction(&Instruction::I32Const(original_len.cast_signed()));
375 function.instruction(&Instruction::I32GtU);
376 function.instruction(&Instruction::If(BlockType::Empty));
377 function.instruction(&Instruction::LocalGet(0));
378 function.instruction(&Instruction::Call(dynamic_serializer));
379 function.instruction(&Instruction::Return);
380 function.instruction(&Instruction::End);
381 Ok(())
382}
383
384fn emit_subtype_to_json_vtable_body(
385 subtype: &UserSubtype,
386 intrinsics: IntrinsicTypeIndices,
387 string_concat_func_idx: u32,
388 string_vtable_global_idx: u32,
389 symbols: &SymbolTable,
390) -> Result<Function, CompilerFailure> {
391 let fields = subtype.fields()?;
392
393 let object_shape_ref = ref_to(intrinsics.object_shape);
394 let string_ref = ref_to(intrinsics.string);
395 let object_null_ref = ref_null(intrinsics.object);
396 let to_json_fn_ref = ref_to(intrinsics.to_json_fn);
397
398 let locals: Vec<(u32, ValType)> = vec![
399 (1, object_shape_ref),
400 (1, string_ref),
401 (1, object_null_ref),
402 (1, to_json_fn_ref),
403 (1, ValType::I32), ];
405 let mut f = Function::new(locals);
406 emit_grown_object_to_json(&mut f, intrinsics, symbols, wasm_u32(fields.len())?)?;
407 let self_t = 1u32;
408 let acc = 2u32;
409 let elem = 3u32;
410 let tj_fn = 4u32;
411 let first_local = 5u32;
412
413 f.instruction(&Instruction::LocalGet(0));
414 f.instruction(&Instruction::RefCastNonNull(HeapType::Concrete(
415 intrinsics.object_shape,
416 )));
417 f.instruction(&Instruction::LocalSet(self_t));
418
419 if fields.is_empty() {
420 push_inline_string(
421 &mut f,
422 "{}",
423 intrinsics.string,
424 intrinsics.raw_string,
425 string_vtable_global_idx,
426 )?;
427 f.instruction(&Instruction::End);
428 return Ok(f);
429 }
430
431 push_inline_string(
432 &mut f,
433 "{",
434 intrinsics.string,
435 intrinsics.raw_string,
436 string_vtable_global_idx,
437 )?;
438 f.instruction(&Instruction::LocalSet(acc));
439
440 f.instruction(&Instruction::I32Const(1));
441 f.instruction(&Instruction::LocalSet(first_local));
442
443 for (idx, (field_name, field)) in fields.iter().enumerate() {
444 let idx = wasm_u32(idx)?;
445 let key_no_comma = format!("\"{}\":", json_escape_key(field_name));
446 let key_with_comma = format!(",\"{}\":", json_escape_key(field_name));
447
448 f.instruction(&Instruction::LocalGet(self_t));
449 f.instruction(&Instruction::StructGet {
450 struct_type_index: intrinsics.object_shape,
451 field_index: 2,
452 });
453 f.instruction(&Instruction::I32Const(idx.cast_signed()));
454 f.instruction(&Instruction::ArrayGet(intrinsics.object_fields));
455 f.instruction(&Instruction::LocalSet(elem));
456
457 for instruction in
458 super::nullish::is_undefined_or_closure(&[Instruction::LocalGet(elem)], intrinsics)
459 {
460 f.instruction(&instruction);
461 }
462 f.instruction(&Instruction::I32Eqz);
463 f.instruction(&Instruction::If(BlockType::Empty));
464
465 if field.optional {
466 super::field_names::emit_optional_presence(
467 &mut f,
468 intrinsics,
469 symbols.optional_field_name_type()?,
470 self_t,
471 idx,
472 elem,
473 );
474 f.instruction(&Instruction::If(BlockType::Empty));
475 }
476
477 f.instruction(&Instruction::LocalGet(acc));
478 f.instruction(&Instruction::LocalGet(first_local));
479 f.instruction(&Instruction::If(BlockType::Result(string_ref)));
480 push_inline_string(
481 &mut f,
482 &key_no_comma,
483 intrinsics.string,
484 intrinsics.raw_string,
485 string_vtable_global_idx,
486 )?;
487 f.instruction(&Instruction::Else);
488 push_inline_string(
489 &mut f,
490 &key_with_comma,
491 intrinsics.string,
492 intrinsics.raw_string,
493 string_vtable_global_idx,
494 )?;
495 f.instruction(&Instruction::End);
496 f.instruction(&Instruction::Call(string_concat_func_idx));
497 f.instruction(&Instruction::LocalSet(acc));
498
499 f.instruction(&Instruction::LocalGet(acc));
503 f.instruction(&Instruction::LocalGet(elem));
504 f.instruction(&Instruction::RefIsNull);
505 f.instruction(&Instruction::If(BlockType::Result(string_ref)));
506 push_inline_string(
507 &mut f,
508 "null",
509 intrinsics.string,
510 intrinsics.raw_string,
511 string_vtable_global_idx,
512 )?;
513 f.instruction(&Instruction::Else);
514 emit_field_value_to_json(&mut f, elem, tj_fn, intrinsics);
515 f.instruction(&Instruction::End);
516 f.instruction(&Instruction::Call(string_concat_func_idx));
517 f.instruction(&Instruction::LocalSet(acc));
518
519 f.instruction(&Instruction::I32Const(0));
520 f.instruction(&Instruction::LocalSet(first_local));
521
522 if field.optional {
523 f.instruction(&Instruction::End);
524 }
525 f.instruction(&Instruction::End);
526 }
527
528 f.instruction(&Instruction::LocalGet(acc));
529 push_inline_string(
530 &mut f,
531 "}",
532 intrinsics.string,
533 intrinsics.raw_string,
534 string_vtable_global_idx,
535 )?;
536 f.instruction(&Instruction::Call(string_concat_func_idx));
537 f.instruction(&Instruction::End);
538 Ok(f)
539}
540
541#[derive(Clone, Copy)]
543enum ConversionSlot {
544 ToString,
545 ToJson,
546}
547
548impl ConversionSlot {
549 fn field_name(self) -> &'static str {
550 match self {
551 Self::ToString => "toString",
552 Self::ToJson => "toJson",
553 }
554 }
555
556 fn vtable_field(self) -> u32 {
557 match self {
558 Self::ToString => 0,
559 Self::ToJson => 1,
560 }
561 }
562
563 fn function_type(self, intrinsics: IntrinsicTypeIndices) -> u32 {
564 match self {
565 Self::ToString => intrinsics.to_string_fn,
566 Self::ToJson => intrinsics.to_json_fn,
567 }
568 }
569}
570
571fn emit_conversion_override_body(
578 subtype: &UserSubtype,
579 intrinsics: IntrinsicTypeIndices,
580 symbols: &SymbolTable,
581 slot: ConversionSlot,
582) -> Result<Function, CompilerFailure> {
583 let object_vtable_global = symbols
584 .prelude_global_idx("object_vtable")
585 .ok_or_else(|| internal_failure("object_vtable is not imported from the prelude"))?;
586 let conversion_sig = crate::codegen::closures::ClosureSig {
587 arity: 0,
588 is_void: false,
589 };
590 let closure_struct_idx = symbols
591 .closure_struct_type_idx(conversion_sig)
592 .ok_or_else(|| {
593 internal_failure(
594 "closure struct for `() => string` registered when any shape declares a \
595 conversion override (collect_from_dependencies walks every shape field type)",
596 )
597 })?;
598 let closure_func_type_idx = symbols
599 .closure_func_type_idx(conversion_sig)
600 .ok_or_else(|| {
601 internal_failure(
602 "closure funcref type for `() => string` registered alongside its struct",
603 )
604 })?;
605 let field_slot = field_index(&subtype.ty, slot.field_name())?
606 .ok_or_else(|| internal_failure("an object shape lost its conversion field slot"))?;
607
608 let mut f = Function::new([
612 (1, ref_to(closure_struct_idx)),
613 (
614 1,
615 ValType::Ref(RefType {
616 nullable: false,
617 heap_type: HeapType::ANY,
618 }),
619 ),
620 ]);
621 let closure = 1u32;
622 let env = 2u32;
623
624 f.instruction(&Instruction::Block(BlockType::Result(ref_to(
627 intrinsics.string,
628 ))));
629 f.instruction(&Instruction::Block(BlockType::Result(ValType::Ref(
630 RefType {
631 nullable: true,
632 heap_type: HeapType::Concrete(intrinsics.object),
633 },
634 ))));
635 f.instruction(&Instruction::LocalGet(0));
636 f.instruction(&Instruction::RefCastNonNull(HeapType::Concrete(
637 intrinsics.object_shape,
638 )));
639 f.instruction(&Instruction::StructGet {
640 struct_type_index: intrinsics.object_shape,
641 field_index: 2,
642 });
643 f.instruction(&Instruction::I32Const(field_slot.cast_signed()));
644 f.instruction(&Instruction::ArrayGet(intrinsics.object_fields));
645 f.instruction(&Instruction::BrOnCastFail {
646 relative_depth: 0,
647 from_ref_type: RefType {
648 nullable: true,
649 heap_type: HeapType::Concrete(intrinsics.object),
650 },
651 to_ref_type: RefType {
652 nullable: false,
653 heap_type: HeapType::Concrete(closure_struct_idx),
654 },
655 });
656 f.instruction(&Instruction::LocalSet(closure));
657 f.instruction(&Instruction::LocalGet(closure));
658 f.instruction(&Instruction::StructGet {
659 struct_type_index: closure_struct_idx,
660 field_index: 2,
661 });
662 if let Some(wrapper) = symbols.this_environment_type {
664 for instruction in super::this_binding::binding_instructions(wrapper, env, 0) {
665 f.instruction(&instruction);
666 }
667 }
668 f.instruction(&Instruction::LocalGet(closure));
669 f.instruction(&Instruction::StructGet {
670 struct_type_index: closure_struct_idx,
671 field_index: 1,
672 });
673 f.instruction(&Instruction::CallRef(closure_func_type_idx));
674 f.instruction(&Instruction::RefCastNonNull(HeapType::Concrete(
677 intrinsics.string,
678 )));
679 f.instruction(&Instruction::Br(1));
680 f.instruction(&Instruction::End);
681 f.instruction(&Instruction::Drop);
682 f.instruction(&Instruction::LocalGet(0));
683 f.instruction(&Instruction::GlobalGet(object_vtable_global));
684 f.instruction(&Instruction::StructGet {
685 struct_type_index: intrinsics.vtable,
686 field_index: slot.vtable_field(),
687 });
688 f.instruction(&Instruction::CallRef(slot.function_type(intrinsics)));
689 f.instruction(&Instruction::End);
690 f.instruction(&Instruction::End);
691 Ok(f)
692}
693
694#[derive(Clone, Copy)]
696enum IndexedSlot {
697 ToString = 0,
698 Equals = 2,
699 Hash = 3,
700}
701
702impl IndexedSlot {
703 fn operands(self) -> u32 {
705 match self {
706 IndexedSlot::Equals => 2,
707 IndexedSlot::ToString | IndexedSlot::Hash => 1,
708 }
709 }
710}
711
712fn emit_indexed_object_dispatch(
716 body: &mut Function,
717 intrinsics: IntrinsicTypeIndices,
718 vtable_global: u32,
719 slot: IndexedSlot,
720) {
721 let operands = slot.operands();
722 for operand in 0..operands {
723 body.instruction(&Instruction::LocalGet(operand));
724 body.instruction(&Instruction::RefCastNonNull(HeapType::Concrete(
725 intrinsics.object_shape,
726 )));
727 body.instruction(&Instruction::StructGet {
728 struct_type_index: intrinsics.object_shape,
729 field_index: 3,
730 });
731 body.instruction(&Instruction::RefIsNull);
732 body.instruction(&Instruction::If(BlockType::Result(ValType::I32)));
733 body.instruction(&Instruction::I32Const(0));
734 body.instruction(&Instruction::Else);
735 body.instruction(&Instruction::LocalGet(operand));
736 body.instruction(&Instruction::RefCastNonNull(HeapType::Concrete(
737 intrinsics.object_shape,
738 )));
739 body.instruction(&Instruction::StructGet {
740 struct_type_index: intrinsics.object_shape,
741 field_index: 3,
742 });
743 body.instruction(&Instruction::RefCastNonNull(HeapType::Concrete(
744 intrinsics.raw_index_array,
745 )));
746 body.instruction(&Instruction::I32Const(1));
747 body.instruction(&Instruction::ArrayGet(intrinsics.raw_index_array));
748 body.instruction(&Instruction::End);
749 }
750 if operands == 2 {
751 body.instruction(&Instruction::I32Or);
752 }
753 body.instruction(&Instruction::If(BlockType::Empty));
754 for operand in 0..operands {
755 body.instruction(&Instruction::LocalGet(operand));
756 }
757 body.instruction(&Instruction::GlobalGet(vtable_global));
758 body.instruction(&Instruction::StructGet {
759 struct_type_index: intrinsics.vtable,
760 field_index: slot as u32,
761 });
762 body.instruction(&Instruction::CallRef(match slot {
763 IndexedSlot::ToString => intrinsics.to_string_fn,
764 IndexedSlot::Equals => intrinsics.equals_fn,
765 IndexedSlot::Hash => intrinsics.hash_fn,
766 }));
767 body.instruction(&Instruction::Return);
768 body.instruction(&Instruction::End);
769}
770
771fn emit_subtype_equals_body(
772 subtype: &UserSubtype,
773 intrinsics: IntrinsicTypeIndices,
774 symbols: &SymbolTable,
775 string_eq_func_idx: u32,
776 object_vtable_global: u32,
777) -> Result<Function, CompilerFailure> {
778 let fields = subtype.fields()?;
779
780 let object_shape_ref = ref_to(intrinsics.object_shape);
781 let object_ref = ref_to(intrinsics.object);
782 let object_null_ref = ref_null(intrinsics.object);
783 let equals_fn_ref = ref_to(intrinsics.equals_fn);
784
785 let locals: Vec<(u32, ValType)> = vec![
793 (2, object_shape_ref),
794 (1, object_ref),
795 (1, equals_fn_ref),
796 (2, object_null_ref),
797 ];
798 let mut f = Function::new(locals);
799 let (a, b) = (0u32, 1u32);
800 let (a_t, b_t) = (2u32, 3u32);
801 let field_lhs = 4u32;
802 let eq_fn = 5u32;
803 let field_lhs_null = 6u32;
804 let field_rhs_null = 7u32;
805
806 emit_equals_type_guard(&mut f, intrinsics.object_shape);
809
810 f.instruction(&Instruction::LocalGet(1));
816 f.instruction(&Instruction::StructGet {
817 struct_type_index: intrinsics.object,
818 field_index: 0,
819 });
820 f.instruction(&Instruction::RefTestNonNull(HeapType::Concrete(
821 intrinsics.class_vtable,
822 )));
823 f.instruction(&Instruction::If(BlockType::Empty));
824 f.instruction(&Instruction::I32Const(0));
825 f.instruction(&Instruction::Return);
826 f.instruction(&Instruction::End);
827
828 f.instruction(&Instruction::LocalGet(a));
829 f.instruction(&Instruction::LocalGet(b));
830 f.instruction(&Instruction::RefEq);
831 f.instruction(&Instruction::If(BlockType::Empty));
832 f.instruction(&Instruction::I32Const(1));
833 f.instruction(&Instruction::Return);
834 f.instruction(&Instruction::End);
835
836 f.instruction(&Instruction::LocalGet(a));
837 f.instruction(&Instruction::RefCastNonNull(HeapType::Concrete(
838 intrinsics.object_shape,
839 )));
840 f.instruction(&Instruction::LocalSet(a_t));
841 f.instruction(&Instruction::LocalGet(b));
842 f.instruction(&Instruction::RefCastNonNull(HeapType::Concrete(
843 intrinsics.object_shape,
844 )));
845 f.instruction(&Instruction::LocalSet(b_t));
846
847 emit_indexed_object_dispatch(
848 &mut f,
849 intrinsics,
850 object_vtable_global,
851 IndexedSlot::Equals,
852 );
853
854 emit_shape_guard(
855 &mut f,
856 wasm_u32(fields.len())?,
857 intrinsics,
858 string_eq_func_idx,
859 (a_t, b_t),
860 );
861
862 for (slot, field) in fields.values().enumerate() {
863 let field_index = wasm_u32(slot)?;
864
865 for object in [a_t, b_t] {
866 emit_field_presence(
867 &mut f,
868 intrinsics,
869 symbols.optional_field_name_type()?,
870 object,
871 field_index,
872 );
873 }
874 f.instruction(&Instruction::I32Ne);
875 f.instruction(&Instruction::If(BlockType::Empty));
876 f.instruction(&Instruction::I32Const(0));
877 f.instruction(&Instruction::Return);
878 f.instruction(&Instruction::End);
879
880 emit_field_compare(
881 &mut f,
882 intrinsics.object_shape,
883 field_index,
884 &field.ty,
885 intrinsics,
886 (a_t, b_t, field_lhs, eq_fn, field_lhs_null, field_rhs_null),
887 )?;
888
889 f.instruction(&Instruction::I32Eqz);
890 f.instruction(&Instruction::If(BlockType::Empty));
891 f.instruction(&Instruction::I32Const(0));
892 f.instruction(&Instruction::Return);
893 f.instruction(&Instruction::End);
894 }
895
896 f.instruction(&Instruction::I32Const(1));
897 f.instruction(&Instruction::End);
898 Ok(f)
899}
900
901fn emit_field_presence(
904 body: &mut Function,
905 intrinsics: IntrinsicTypeIndices,
906 optional_name_type: u32,
907 object: u32,
908 slot: u32,
909) {
910 let load_name = |body: &mut Function| {
911 body.instruction(&Instruction::LocalGet(object));
912 body.instruction(&Instruction::StructGet {
913 struct_type_index: intrinsics.object_shape,
914 field_index: 1,
915 });
916 body.instruction(&Instruction::I32Const(slot.cast_signed()));
917 body.instruction(&Instruction::ArrayGet(intrinsics.field_names));
918 };
919 load_name(body);
920 body.instruction(&Instruction::RefTestNonNull(HeapType::Concrete(
921 optional_name_type,
922 )));
923 body.instruction(&Instruction::If(BlockType::Result(ValType::I32)));
924 load_name(body);
925 body.instruction(&Instruction::RefCastNonNull(HeapType::Concrete(
926 optional_name_type,
927 )));
928 body.instruction(&Instruction::StructGet {
929 struct_type_index: optional_name_type,
930 field_index: 3,
931 });
932 body.instruction(&Instruction::Else);
933 body.instruction(&Instruction::I32Const(1));
934 body.instruction(&Instruction::End);
935 let load_value = [
936 Instruction::LocalGet(object),
937 Instruction::StructGet {
938 struct_type_index: intrinsics.object_shape,
939 field_index: 2,
940 },
941 Instruction::I32Const(slot.cast_signed()),
942 Instruction::ArrayGet(intrinsics.object_fields),
943 ];
944 for instruction in super::nullish::is_nullish(&load_value, intrinsics) {
945 body.instruction(&instruction);
946 }
947 body.instruction(&Instruction::I32Eqz);
948 body.instruction(&Instruction::I32Or);
949}
950
951fn emit_field_compare(
959 f: &mut Function,
960 object_shape_idx: u32,
961 field_index: u32,
962 field_ty: &Type,
963 intrinsics: IntrinsicTypeIndices,
964 locals: (u32, u32, u32, u32, u32, u32),
965) -> Result<(), CompilerFailure> {
966 let (a_t, b_t, field_lhs, eq_fn, field_lhs_null, field_rhs_null) = locals;
967 reject_unrepresentable_field(field_ty)?;
968 emit_nullable_field_compare(
969 f,
970 object_shape_idx,
971 field_index,
972 intrinsics,
973 (field_lhs, eq_fn, field_lhs_null, field_rhs_null, a_t, b_t),
974 );
975 Ok(())
976}
977
978fn emit_subtype_hash_body(
980 subtype: &UserSubtype,
981 intrinsics: IntrinsicTypeIndices,
982 symbols: &SymbolTable,
983 object_vtable_global: u32,
984) -> Result<Function, CompilerFailure> {
985 let fields = subtype.fields()?;
986
987 let object_shape_ref = ref_to(intrinsics.object_shape);
988 let object_ref = ref_to(intrinsics.object);
989 let object_null_ref = ref_null(intrinsics.object);
990 let hash_fn_ref = ref_to(intrinsics.hash_fn);
991
992 let locals: Vec<(u32, ValType)> = vec![
999 (1, object_shape_ref),
1000 (1, ValType::I32),
1001 (1, object_ref),
1002 (1, hash_fn_ref),
1003 (1, object_null_ref),
1004 ];
1005 let mut f = Function::new(locals);
1006 let self_param = 0u32;
1007 let self_t = 1u32;
1008 let hash = 2u32;
1009 let field_obj = 3u32;
1010 let hash_fn = 4u32;
1011 let f_null = 5u32;
1012 f.instruction(&Instruction::LocalGet(self_param));
1013 f.instruction(&Instruction::RefCastNonNull(HeapType::Concrete(
1014 intrinsics.object_shape,
1015 )));
1016 f.instruction(&Instruction::LocalSet(self_t));
1017
1018 emit_indexed_object_dispatch(&mut f, intrinsics, object_vtable_global, IndexedSlot::Hash);
1019
1020 f.instruction(&Instruction::I32Const(0x811c9dc5u32.cast_signed()));
1022 f.instruction(&Instruction::LocalSet(hash));
1023
1024 for (slot, (name, field)) in fields.iter().enumerate() {
1025 let field_index = wasm_u32(slot)?;
1026 if field.optional {
1027 f.instruction(&Instruction::LocalGet(self_t));
1028 f.instruction(&Instruction::StructGet {
1029 struct_type_index: intrinsics.object_shape,
1030 field_index: 2,
1031 });
1032 f.instruction(&Instruction::I32Const(field_index.cast_signed()));
1033 f.instruction(&Instruction::ArrayGet(intrinsics.object_fields));
1034 f.instruction(&Instruction::LocalSet(f_null));
1035 super::field_names::emit_optional_presence(
1036 &mut f,
1037 intrinsics,
1038 symbols.optional_field_name_type()?,
1039 self_t,
1040 field_index,
1041 f_null,
1042 );
1043 f.instruction(&Instruction::If(BlockType::Empty));
1044 }
1045 emit_field_hash(
1046 &mut f,
1047 intrinsics.object_shape,
1048 field_index,
1049 &field.ty,
1050 intrinsics,
1051 (self_t, field_obj, hash_fn, f_null),
1052 )?;
1053 let name_hash = crate::runtime::prelude::vtable::hash_utf16_units(
1055 crate::literal_units::literal_units(name),
1056 );
1057 f.instruction(&Instruction::I32Const(13));
1058 f.instruction(&Instruction::I32Rotl);
1059 f.instruction(&Instruction::I32Const(name_hash.cast_signed()));
1060 f.instruction(&Instruction::I32Xor);
1061 f.instruction(&Instruction::LocalGet(hash));
1062 f.instruction(&Instruction::I32Add);
1063 f.instruction(&Instruction::LocalSet(hash));
1064 if field.optional {
1065 f.instruction(&Instruction::End);
1066 }
1067 }
1068
1069 f.instruction(&Instruction::LocalGet(hash));
1070 f.instruction(&Instruction::End);
1071 Ok(f)
1072}
1073
1074fn emit_field_hash(
1077 f: &mut Function,
1078 object_shape_idx: u32,
1079 field_index: u32,
1080 field_ty: &Type,
1081 intrinsics: IntrinsicTypeIndices,
1082 locals: (u32, u32, u32, u32),
1083) -> Result<(), CompilerFailure> {
1084 reject_unrepresentable_field(field_ty)?;
1085 emit_nullable_field_hash(f, object_shape_idx, field_index, intrinsics, locals);
1086 Ok(())
1087}
1088
1089fn reject_unrepresentable_field(field_ty: &Type) -> Result<(), CompilerFailure> {
1092 let field_ty = field_ty.peel();
1093 match field_ty {
1094 Type::Error | Type::Alias { .. } | Type::Refined { .. } | Type::Readonly(_) => {
1095 Err(internal_failure(format!(
1096 "an object field of type `{field_ty}` reached structural equality or hashing"
1097 )))
1098 }
1099 _ => Ok(()),
1100 }
1101}
1102
1103fn emit_nullable_field_hash(
1106 f: &mut Function,
1107 object_shape_idx: u32,
1108 field_index: u32,
1109 intrinsics: IntrinsicTypeIndices,
1110 locals: (u32, u32, u32, u32),
1111) {
1112 let (self_t, field_obj, hash_fn, f_null) = locals;
1113 f.instruction(&Instruction::LocalGet(self_t));
1114 f.instruction(&Instruction::StructGet {
1115 struct_type_index: object_shape_idx,
1116 field_index: 2,
1117 });
1118 f.instruction(&Instruction::I32Const(field_index.cast_signed()));
1119 f.instruction(&Instruction::ArrayGet(intrinsics.object_fields));
1120 f.instruction(&Instruction::LocalSet(f_null));
1121
1122 f.instruction(&Instruction::LocalGet(f_null));
1123 f.instruction(&Instruction::RefIsNull);
1124 f.instruction(&Instruction::If(BlockType::Result(ValType::I32)));
1125 f.instruction(&Instruction::I32Const(0));
1126 f.instruction(&Instruction::Else);
1127 f.instruction(&Instruction::LocalGet(f_null));
1128 f.instruction(&Instruction::RefAsNonNull);
1129 f.instruction(&Instruction::LocalTee(field_obj));
1130 f.instruction(&Instruction::StructGet {
1131 struct_type_index: intrinsics.object,
1132 field_index: 0,
1133 });
1134 f.instruction(&Instruction::StructGet {
1135 struct_type_index: intrinsics.vtable,
1136 field_index: 3,
1137 });
1138 f.instruction(&Instruction::LocalSet(hash_fn));
1139 f.instruction(&Instruction::LocalGet(field_obj));
1140 f.instruction(&Instruction::LocalGet(hash_fn));
1141 f.instruction(&Instruction::CallRef(intrinsics.hash_fn));
1142 f.instruction(&Instruction::End);
1143}
1144
1145fn emit_nullable_field_compare(
1147 f: &mut Function,
1148 object_shape_idx: u32,
1149 field_index: u32,
1150 intrinsics: IntrinsicTypeIndices,
1151 locals: (u32, u32, u32, u32, u32, u32),
1152) {
1153 let (field_lhs, eq_fn, field_lhs_null, field_rhs_null, a_t, b_t) = locals;
1154 f.instruction(&Instruction::LocalGet(a_t));
1155 f.instruction(&Instruction::StructGet {
1156 struct_type_index: object_shape_idx,
1157 field_index: 2,
1158 });
1159 f.instruction(&Instruction::I32Const(field_index.cast_signed()));
1160 f.instruction(&Instruction::ArrayGet(intrinsics.object_fields));
1161 f.instruction(&Instruction::LocalSet(field_lhs_null));
1162
1163 f.instruction(&Instruction::LocalGet(b_t));
1164 f.instruction(&Instruction::StructGet {
1165 struct_type_index: object_shape_idx,
1166 field_index: 2,
1167 });
1168 f.instruction(&Instruction::I32Const(field_index.cast_signed()));
1169 f.instruction(&Instruction::ArrayGet(intrinsics.object_fields));
1170 f.instruction(&Instruction::LocalSet(field_rhs_null));
1171
1172 f.instruction(&Instruction::LocalGet(field_lhs_null));
1173 f.instruction(&Instruction::RefIsNull);
1174 f.instruction(&Instruction::If(BlockType::Result(ValType::I32)));
1175 f.instruction(&Instruction::LocalGet(field_rhs_null));
1176 f.instruction(&Instruction::RefIsNull);
1177 f.instruction(&Instruction::Else);
1178 f.instruction(&Instruction::LocalGet(field_rhs_null));
1179 f.instruction(&Instruction::RefIsNull);
1180 f.instruction(&Instruction::If(BlockType::Result(ValType::I32)));
1181 f.instruction(&Instruction::I32Const(0));
1182 f.instruction(&Instruction::Else);
1183 f.instruction(&Instruction::LocalGet(field_lhs_null));
1184 f.instruction(&Instruction::RefAsNonNull);
1185 f.instruction(&Instruction::LocalTee(field_lhs));
1186 f.instruction(&Instruction::StructGet {
1187 struct_type_index: intrinsics.object,
1188 field_index: 0,
1189 });
1190 f.instruction(&Instruction::StructGet {
1191 struct_type_index: intrinsics.vtable,
1192 field_index: 2,
1193 });
1194 f.instruction(&Instruction::LocalSet(eq_fn));
1195 f.instruction(&Instruction::LocalGet(field_lhs));
1196 f.instruction(&Instruction::LocalGet(field_rhs_null));
1197 f.instruction(&Instruction::RefAsNonNull);
1198 f.instruction(&Instruction::LocalGet(eq_fn));
1199 f.instruction(&Instruction::CallRef(intrinsics.equals_fn));
1200 f.instruction(&Instruction::End);
1201 f.instruction(&Instruction::End);
1202}
1203
1204pub fn declared_method_funcs(subtypes: &[UserSubtype]) -> Vec<u32> {
1206 subtypes
1207 .iter()
1208 .flat_map(|s| [s.to_string_func, s.to_json_func, s.equals_func, s.hash_func])
1209 .collect()
1210}
1211
1212pub fn field_index(ty: &Type, field_name: &str) -> Result<Option<u32>, CompilerFailure> {
1214 let Type::Object { fields, .. } = ty else {
1215 return Ok(None);
1216 };
1217 fields
1218 .keys()
1219 .position(|k| k == field_name)
1220 .map(wasm_u32)
1221 .transpose()
1222}
1223
1224fn emit_field_value_to_json(
1225 f: &mut Function,
1226 elem: u32,
1227 tj_fn: u32,
1228 intrinsics: IntrinsicTypeIndices,
1229) {
1230 f.instruction(&Instruction::LocalGet(elem));
1231 f.instruction(&Instruction::RefAsNonNull);
1232 f.instruction(&Instruction::StructGet {
1233 struct_type_index: intrinsics.object,
1234 field_index: 0,
1235 });
1236 f.instruction(&Instruction::StructGet {
1237 struct_type_index: intrinsics.vtable,
1238 field_index: 1,
1239 });
1240 f.instruction(&Instruction::LocalSet(tj_fn));
1241
1242 f.instruction(&Instruction::LocalGet(elem));
1243 f.instruction(&Instruction::RefAsNonNull);
1244 f.instruction(&Instruction::LocalGet(tj_fn));
1245 f.instruction(&Instruction::CallRef(intrinsics.to_json_fn));
1246}
1247
1248fn push_inline_string(
1249 f: &mut Function,
1250 s: &str,
1251 string_idx: u32,
1252 raw_string_idx: u32,
1253 string_vtable_global_idx: u32,
1254) -> Result<(), CompilerFailure> {
1255 f.instruction(&Instruction::GlobalGet(string_vtable_global_idx));
1256 for b in s.bytes() {
1257 f.instruction(&Instruction::I32Const(i32::from(b)));
1258 }
1259 f.instruction(&Instruction::ArrayNewFixed {
1260 array_type_index: raw_string_idx,
1261 array_size: wasm_u32(s.len())?,
1262 });
1263 f.instruction(&Instruction::I64Const(0));
1264 f.instruction(&Instruction::StructNew(string_idx));
1265 Ok(())
1266}
1267
1268pub(crate) fn json_escape_key(s: &str) -> String {
1269 let mut out = String::with_capacity(s.len());
1270 for ch in s.chars() {
1271 match ch {
1272 '\\' => out.push_str("\\\\"),
1273 '"' => out.push_str("\\\""),
1274 '\n' => out.push_str("\\n"),
1275 '\r' => out.push_str("\\r"),
1276 '\t' => out.push_str("\\t"),
1277 c if c.is_control() => out.push_str(&format!("\\u{:04x}", u32::from(c))),
1278 c => out.push(c),
1279 }
1280 }
1281 out
1282}
1283
1284fn ref_to(idx: u32) -> ValType {
1285 ValType::Ref(RefType {
1286 nullable: false,
1287 heap_type: HeapType::Concrete(idx),
1288 })
1289}
1290
1291fn ref_null(idx: u32) -> ValType {
1292 ValType::Ref(RefType {
1293 nullable: true,
1294 heap_type: HeapType::Concrete(idx),
1295 })
1296}
1297
1298fn emit_equals_type_guard(f: &mut Function, type_idx: u32) {
1303 f.instruction(&Instruction::LocalGet(1));
1304 f.instruction(&Instruction::RefTestNonNull(HeapType::Concrete(type_idx)));
1305 f.instruction(&Instruction::I32Eqz);
1306 f.instruction(&Instruction::If(BlockType::Empty));
1307 f.instruction(&Instruction::I32Const(0));
1308 f.instruction(&Instruction::Return);
1309 f.instruction(&Instruction::End);
1310}
1311
1312fn emit_shape_guard(
1322 f: &mut Function,
1323 n_fields: u32,
1324 intrinsics: IntrinsicTypeIndices,
1325 string_eq_func_idx: u32,
1326 (a_t, b_t): (u32, u32),
1327) {
1328 let load_names = |f: &mut Function, side: u32| {
1329 f.instruction(&Instruction::LocalGet(side));
1330 f.instruction(&Instruction::StructGet {
1331 struct_type_index: intrinsics.object_shape,
1332 field_index: 1,
1333 });
1334 };
1335 let load_name_at = |f: &mut Function, side: u32, i: u32| {
1336 load_names(f, side);
1337 f.instruction(&Instruction::I32Const(i.cast_signed()));
1338 f.instruction(&Instruction::ArrayGet(intrinsics.field_names));
1339 };
1340
1341 load_names(f, a_t);
1342 load_names(f, b_t);
1343 f.instruction(&Instruction::RefEq);
1344 f.instruction(&Instruction::I32Eqz);
1345 f.instruction(&Instruction::If(BlockType::Empty));
1346
1347 load_names(f, b_t);
1348 f.instruction(&Instruction::ArrayLen);
1349 f.instruction(&Instruction::I32Const(n_fields.cast_signed()));
1350 f.instruction(&Instruction::I32Ne);
1351 f.instruction(&Instruction::If(BlockType::Empty));
1352 f.instruction(&Instruction::I32Const(0));
1353 f.instruction(&Instruction::Return);
1354 f.instruction(&Instruction::End);
1355
1356 for i in 0..n_fields {
1357 load_name_at(f, a_t, i);
1358 load_name_at(f, b_t, i);
1359 f.instruction(&Instruction::Call(string_eq_func_idx));
1360 f.instruction(&Instruction::I32Eqz);
1361 f.instruction(&Instruction::If(BlockType::Empty));
1362 f.instruction(&Instruction::I32Const(0));
1363 f.instruction(&Instruction::Return);
1364 f.instruction(&Instruction::End);
1365 }
1366
1367 f.instruction(&Instruction::End);
1368}