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 )?);
225
226 let bodies = subtype.guarded_bodies;
227 for (body, params, result) in [
228 (bodies.to_json, 1, ref_to(intrinsics.string)),
229 (bodies.equals, 2, ValType::I32),
230 (bodies.hash, 1, ValType::I32),
231 ] {
232 code.function(&super::vtable_walk::guarded_body(
233 body, params, result, symbols,
234 )?);
235 }
236 let host_json_type = type_info_index
239 .object_type_id(type_info, &subtype.ty)
240 .filter(|_| type_info.supports_host_json_object(&subtype.ty));
241 code.function(&emit_subtype_to_json_body(
242 subtype,
243 intrinsics,
244 symbols,
245 host_json_type,
246 pkg_string_global_idx,
247 string_concat_func_idx,
248 string_vtable_global_idx,
249 )?);
250
251 code.function(&emit_subtype_equals_body(
252 subtype,
253 intrinsics,
254 symbols,
255 string_eq_func_idx,
256 object_vtable_global,
257 )?);
258
259 code.function(&emit_subtype_hash_body(
260 subtype,
261 intrinsics,
262 symbols,
263 object_vtable_global,
264 )?);
265 }
266 Ok(())
267}
268
269fn emit_subtype_to_string_body(
270 subtype: &UserSubtype,
271 intrinsics: IntrinsicTypeIndices,
272 symbols: &SymbolTable,
273 string_vtable_global_idx: u32,
274) -> Result<Function, CompilerFailure> {
275 let fields = subtype.fields()?;
276
277 if !fields.contains_key("toString") {
278 let mut f = Function::new(std::iter::empty());
279 crate::codegen::intrinsics::push_string_literal(
280 &mut f,
281 intrinsics,
282 string_vtable_global_idx,
283 "[object Object]",
284 )?;
285 f.instruction(&Instruction::End);
286 return Ok(f);
287 }
288
289 emit_conversion_override_body(subtype, intrinsics, symbols, ConversionSlot::ToString)
290}
291
292fn emit_subtype_to_json_body(
293 subtype: &UserSubtype,
294 intrinsics: IntrinsicTypeIndices,
295 symbols: &SymbolTable,
296 host_json_type: Option<crate::TypeInfoId>,
297 pkg_string_global_idx: Option<u32>,
298 string_concat_func_idx: u32,
299 string_vtable_global_idx: u32,
300) -> Result<Function, CompilerFailure> {
301 let fields = subtype.fields()?;
302
303 if fields.contains_key("toJson") {
304 return emit_conversion_override_body(subtype, intrinsics, symbols, ConversionSlot::ToJson);
305 }
306
307 let Some(type_id) = host_json_type else {
308 return emit_subtype_to_json_vtable_body(
309 subtype,
310 intrinsics,
311 string_concat_func_idx,
312 string_vtable_global_idx,
313 symbols,
314 );
315 };
316 let pkg_string_global_idx = pkg_string_global_idx
317 .ok_or_else(|| internal_failure("host object serialization needs the package string"))?;
318 let stringify_func_idx = symbols
319 .func_idx(&crate::mangle::host(
320 crate::runtime::JSON_MODULE_NAME,
321 "stringifyTypedObject",
322 ))
323 .ok_or_else(|| internal_failure("submilli:json.stringifyTypedObject is not imported"))?;
324
325 let mut f = Function::new(std::iter::empty());
326
327 f.instruction(&Instruction::GlobalGet(string_vtable_global_idx));
328
329 f.instruction(&Instruction::GlobalGet(pkg_string_global_idx));
330 f.instruction(&Instruction::RefAsNonNull);
331 f.instruction(&Instruction::StructGet {
332 struct_type_index: intrinsics.string,
333 field_index: 1,
334 });
335 f.instruction(&Instruction::I32Const(type_id.as_u32().cast_signed()));
336 f.instruction(&Instruction::LocalGet(0));
337 f.instruction(&Instruction::RefCastNonNull(HeapType::Concrete(
338 intrinsics.object_shape,
339 )));
340 f.instruction(&Instruction::Call(stringify_func_idx));
341 f.instruction(&Instruction::I64Const(0));
342 f.instruction(&Instruction::StructNew(intrinsics.string));
343 f.instruction(&Instruction::End);
344 Ok(f)
345}
346
347pub(super) fn emit_grown_object_to_json(
349 function: &mut Function,
350 intrinsics: IntrinsicTypeIndices,
351 symbols: &SymbolTable,
352 original_len: u32,
353) -> Result<(), CompilerFailure> {
354 let dynamic_serializer = symbols
355 .prelude_func_idx("ObjectConstructor##toJson")
356 .ok_or_else(|| internal_failure("the dynamic object serializer is not imported"))?;
357 function.instruction(&Instruction::LocalGet(0));
358 function.instruction(&Instruction::RefCastNonNull(HeapType::Concrete(
359 intrinsics.object_shape,
360 )));
361 function.instruction(&Instruction::StructGet {
362 struct_type_index: intrinsics.object_shape,
363 field_index: 1,
364 });
365 function.instruction(&Instruction::ArrayLen);
366 function.instruction(&Instruction::I32Const(original_len.cast_signed()));
367 function.instruction(&Instruction::I32GtU);
368 function.instruction(&Instruction::If(BlockType::Empty));
369 function.instruction(&Instruction::LocalGet(0));
370 function.instruction(&Instruction::Call(dynamic_serializer));
371 function.instruction(&Instruction::Return);
372 function.instruction(&Instruction::End);
373 Ok(())
374}
375
376fn emit_subtype_to_json_vtable_body(
377 subtype: &UserSubtype,
378 intrinsics: IntrinsicTypeIndices,
379 string_concat_func_idx: u32,
380 string_vtable_global_idx: u32,
381 symbols: &SymbolTable,
382) -> Result<Function, CompilerFailure> {
383 let fields = subtype.fields()?;
384
385 let object_shape_ref = ref_to(intrinsics.object_shape);
386 let string_ref = ref_to(intrinsics.string);
387 let object_null_ref = ref_null(intrinsics.object);
388 let to_json_fn_ref = ref_to(intrinsics.to_json_fn);
389
390 let locals: Vec<(u32, ValType)> = vec![
391 (1, object_shape_ref),
392 (1, string_ref),
393 (1, object_null_ref),
394 (1, to_json_fn_ref),
395 (1, ValType::I32), ];
397 let mut f = Function::new(locals);
398 emit_grown_object_to_json(&mut f, intrinsics, symbols, wasm_u32(fields.len())?)?;
399 let self_t = 1u32;
400 let acc = 2u32;
401 let elem = 3u32;
402 let tj_fn = 4u32;
403 let first_local = 5u32;
404
405 f.instruction(&Instruction::LocalGet(0));
406 f.instruction(&Instruction::RefCastNonNull(HeapType::Concrete(
407 intrinsics.object_shape,
408 )));
409 f.instruction(&Instruction::LocalSet(self_t));
410
411 if fields.is_empty() {
412 push_inline_string(
413 &mut f,
414 "{}",
415 intrinsics.string,
416 intrinsics.raw_string,
417 string_vtable_global_idx,
418 )?;
419 f.instruction(&Instruction::End);
420 return Ok(f);
421 }
422
423 push_inline_string(
424 &mut f,
425 "{",
426 intrinsics.string,
427 intrinsics.raw_string,
428 string_vtable_global_idx,
429 )?;
430 f.instruction(&Instruction::LocalSet(acc));
431
432 f.instruction(&Instruction::I32Const(1));
433 f.instruction(&Instruction::LocalSet(first_local));
434
435 for (idx, (field_name, field)) in fields.iter().enumerate() {
436 let idx = wasm_u32(idx)?;
437 let key_no_comma = format!("\"{}\":", json_escape_key(field_name));
438 let key_with_comma = format!(",\"{}\":", json_escape_key(field_name));
439
440 f.instruction(&Instruction::LocalGet(self_t));
441 f.instruction(&Instruction::StructGet {
442 struct_type_index: intrinsics.object_shape,
443 field_index: 2,
444 });
445 f.instruction(&Instruction::I32Const(idx.cast_signed()));
446 f.instruction(&Instruction::ArrayGet(intrinsics.object_fields));
447 f.instruction(&Instruction::LocalSet(elem));
448
449 for instruction in
450 super::nullish::is_undefined_or_closure(&[Instruction::LocalGet(elem)], intrinsics)
451 {
452 f.instruction(&instruction);
453 }
454 f.instruction(&Instruction::I32Eqz);
455 f.instruction(&Instruction::If(BlockType::Empty));
456
457 if field.optional {
458 super::field_names::emit_optional_presence(
459 &mut f,
460 intrinsics,
461 symbols.optional_field_name_type()?,
462 self_t,
463 idx,
464 elem,
465 );
466 f.instruction(&Instruction::If(BlockType::Empty));
467 }
468
469 f.instruction(&Instruction::LocalGet(acc));
470 f.instruction(&Instruction::LocalGet(first_local));
471 f.instruction(&Instruction::If(BlockType::Result(string_ref)));
472 push_inline_string(
473 &mut f,
474 &key_no_comma,
475 intrinsics.string,
476 intrinsics.raw_string,
477 string_vtable_global_idx,
478 )?;
479 f.instruction(&Instruction::Else);
480 push_inline_string(
481 &mut f,
482 &key_with_comma,
483 intrinsics.string,
484 intrinsics.raw_string,
485 string_vtable_global_idx,
486 )?;
487 f.instruction(&Instruction::End);
488 f.instruction(&Instruction::Call(string_concat_func_idx));
489 f.instruction(&Instruction::LocalSet(acc));
490
491 f.instruction(&Instruction::LocalGet(acc));
495 f.instruction(&Instruction::LocalGet(elem));
496 f.instruction(&Instruction::RefIsNull);
497 f.instruction(&Instruction::If(BlockType::Result(string_ref)));
498 push_inline_string(
499 &mut f,
500 "null",
501 intrinsics.string,
502 intrinsics.raw_string,
503 string_vtable_global_idx,
504 )?;
505 f.instruction(&Instruction::Else);
506 emit_field_value_to_json(&mut f, elem, tj_fn, intrinsics);
507 f.instruction(&Instruction::End);
508 f.instruction(&Instruction::Call(string_concat_func_idx));
509 f.instruction(&Instruction::LocalSet(acc));
510
511 f.instruction(&Instruction::I32Const(0));
512 f.instruction(&Instruction::LocalSet(first_local));
513
514 if field.optional {
515 f.instruction(&Instruction::End);
516 }
517 f.instruction(&Instruction::End);
518 }
519
520 f.instruction(&Instruction::LocalGet(acc));
521 push_inline_string(
522 &mut f,
523 "}",
524 intrinsics.string,
525 intrinsics.raw_string,
526 string_vtable_global_idx,
527 )?;
528 f.instruction(&Instruction::Call(string_concat_func_idx));
529 f.instruction(&Instruction::End);
530 Ok(f)
531}
532
533#[derive(Clone, Copy)]
535enum ConversionSlot {
536 ToString,
537 ToJson,
538}
539
540impl ConversionSlot {
541 fn field_name(self) -> &'static str {
542 match self {
543 Self::ToString => "toString",
544 Self::ToJson => "toJson",
545 }
546 }
547
548 fn vtable_field(self) -> u32 {
549 match self {
550 Self::ToString => 0,
551 Self::ToJson => 1,
552 }
553 }
554
555 fn function_type(self, intrinsics: IntrinsicTypeIndices) -> u32 {
556 match self {
557 Self::ToString => intrinsics.to_string_fn,
558 Self::ToJson => intrinsics.to_json_fn,
559 }
560 }
561}
562
563fn emit_conversion_override_body(
570 subtype: &UserSubtype,
571 intrinsics: IntrinsicTypeIndices,
572 symbols: &SymbolTable,
573 slot: ConversionSlot,
574) -> Result<Function, CompilerFailure> {
575 let object_vtable_global = symbols
576 .prelude_global_idx("object_vtable")
577 .ok_or_else(|| internal_failure("object_vtable is not imported from the prelude"))?;
578 let conversion_sig = crate::codegen::closures::ClosureSig {
579 arity: 0,
580 is_void: false,
581 };
582 let closure_struct_idx = symbols
583 .closure_struct_type_idx(conversion_sig)
584 .ok_or_else(|| {
585 internal_failure(
586 "closure struct for `() => string` registered when any shape declares a \
587 conversion override (collect_from_dependencies walks every shape field type)",
588 )
589 })?;
590 let closure_func_type_idx = symbols
591 .closure_func_type_idx(conversion_sig)
592 .ok_or_else(|| {
593 internal_failure(
594 "closure funcref type for `() => string` registered alongside its struct",
595 )
596 })?;
597 let field_slot = field_index(&subtype.ty, slot.field_name())?
598 .ok_or_else(|| internal_failure("an object shape lost its conversion field slot"))?;
599
600 let mut f = Function::new([
604 (1, ref_to(closure_struct_idx)),
605 (
606 1,
607 ValType::Ref(RefType {
608 nullable: false,
609 heap_type: HeapType::ANY,
610 }),
611 ),
612 ]);
613 let closure = 1u32;
614 let env = 2u32;
615
616 f.instruction(&Instruction::Block(BlockType::Result(ref_to(
619 intrinsics.string,
620 ))));
621 f.instruction(&Instruction::Block(BlockType::Result(ValType::Ref(
622 RefType {
623 nullable: true,
624 heap_type: HeapType::Concrete(intrinsics.object),
625 },
626 ))));
627 f.instruction(&Instruction::LocalGet(0));
628 f.instruction(&Instruction::RefCastNonNull(HeapType::Concrete(
629 intrinsics.object_shape,
630 )));
631 f.instruction(&Instruction::StructGet {
632 struct_type_index: intrinsics.object_shape,
633 field_index: 2,
634 });
635 f.instruction(&Instruction::I32Const(field_slot.cast_signed()));
636 f.instruction(&Instruction::ArrayGet(intrinsics.object_fields));
637 f.instruction(&Instruction::BrOnCastFail {
638 relative_depth: 0,
639 from_ref_type: RefType {
640 nullable: true,
641 heap_type: HeapType::Concrete(intrinsics.object),
642 },
643 to_ref_type: RefType {
644 nullable: false,
645 heap_type: HeapType::Concrete(closure_struct_idx),
646 },
647 });
648 f.instruction(&Instruction::LocalSet(closure));
649 f.instruction(&Instruction::LocalGet(closure));
650 f.instruction(&Instruction::StructGet {
651 struct_type_index: closure_struct_idx,
652 field_index: 2,
653 });
654 if let Some(wrapper) = symbols.this_environment_type {
656 for instruction in super::this_binding::binding_instructions(wrapper, env, 0) {
657 f.instruction(&instruction);
658 }
659 }
660 f.instruction(&Instruction::LocalGet(closure));
661 f.instruction(&Instruction::StructGet {
662 struct_type_index: closure_struct_idx,
663 field_index: 1,
664 });
665 f.instruction(&Instruction::CallRef(closure_func_type_idx));
666 f.instruction(&Instruction::RefCastNonNull(HeapType::Concrete(
669 intrinsics.string,
670 )));
671 f.instruction(&Instruction::Br(1));
672 f.instruction(&Instruction::End);
673 f.instruction(&Instruction::Drop);
674 f.instruction(&Instruction::LocalGet(0));
675 f.instruction(&Instruction::GlobalGet(object_vtable_global));
676 f.instruction(&Instruction::StructGet {
677 struct_type_index: intrinsics.vtable,
678 field_index: slot.vtable_field(),
679 });
680 f.instruction(&Instruction::CallRef(slot.function_type(intrinsics)));
681 f.instruction(&Instruction::End);
682 f.instruction(&Instruction::End);
683 Ok(f)
684}
685
686fn emit_indexed_object_dispatch(
689 body: &mut Function,
690 intrinsics: IntrinsicTypeIndices,
691 vtable_global: u32,
692 slot: u32,
693) {
694 let operands = if slot == 2 { 2 } else { 1 };
695 for operand in 0..operands {
696 body.instruction(&Instruction::LocalGet(operand));
697 body.instruction(&Instruction::RefCastNonNull(HeapType::Concrete(
698 intrinsics.object_shape,
699 )));
700 body.instruction(&Instruction::StructGet {
701 struct_type_index: intrinsics.object_shape,
702 field_index: 3,
703 });
704 body.instruction(&Instruction::RefIsNull);
705 body.instruction(&Instruction::If(BlockType::Result(ValType::I32)));
706 body.instruction(&Instruction::I32Const(0));
707 body.instruction(&Instruction::Else);
708 body.instruction(&Instruction::LocalGet(operand));
709 body.instruction(&Instruction::RefCastNonNull(HeapType::Concrete(
710 intrinsics.object_shape,
711 )));
712 body.instruction(&Instruction::StructGet {
713 struct_type_index: intrinsics.object_shape,
714 field_index: 3,
715 });
716 body.instruction(&Instruction::RefCastNonNull(HeapType::Concrete(
717 intrinsics.raw_index_array,
718 )));
719 body.instruction(&Instruction::I32Const(1));
720 body.instruction(&Instruction::ArrayGet(intrinsics.raw_index_array));
721 body.instruction(&Instruction::End);
722 }
723 if operands == 2 {
724 body.instruction(&Instruction::I32Or);
725 }
726 body.instruction(&Instruction::If(BlockType::Empty));
727 for operand in 0..operands {
728 body.instruction(&Instruction::LocalGet(operand));
729 }
730 body.instruction(&Instruction::GlobalGet(vtable_global));
731 body.instruction(&Instruction::StructGet {
732 struct_type_index: intrinsics.vtable,
733 field_index: slot,
734 });
735 body.instruction(&Instruction::CallRef(if slot == 2 {
736 intrinsics.equals_fn
737 } else {
738 intrinsics.hash_fn
739 }));
740 body.instruction(&Instruction::Return);
741 body.instruction(&Instruction::End);
742}
743
744fn emit_subtype_equals_body(
745 subtype: &UserSubtype,
746 intrinsics: IntrinsicTypeIndices,
747 symbols: &SymbolTable,
748 string_eq_func_idx: u32,
749 object_vtable_global: u32,
750) -> Result<Function, CompilerFailure> {
751 let fields = subtype.fields()?;
752
753 let object_shape_ref = ref_to(intrinsics.object_shape);
754 let object_ref = ref_to(intrinsics.object);
755 let object_null_ref = ref_null(intrinsics.object);
756 let equals_fn_ref = ref_to(intrinsics.equals_fn);
757
758 let locals: Vec<(u32, ValType)> = vec![
766 (2, object_shape_ref),
767 (1, object_ref),
768 (1, equals_fn_ref),
769 (2, object_null_ref),
770 ];
771 let mut f = Function::new(locals);
772 let (a, b) = (0u32, 1u32);
773 let (a_t, b_t) = (2u32, 3u32);
774 let field_lhs = 4u32;
775 let eq_fn = 5u32;
776 let field_lhs_null = 6u32;
777 let field_rhs_null = 7u32;
778
779 emit_equals_type_guard(&mut f, intrinsics.object_shape);
782
783 f.instruction(&Instruction::LocalGet(1));
789 f.instruction(&Instruction::StructGet {
790 struct_type_index: intrinsics.object,
791 field_index: 0,
792 });
793 f.instruction(&Instruction::RefTestNonNull(HeapType::Concrete(
794 intrinsics.class_vtable,
795 )));
796 f.instruction(&Instruction::If(BlockType::Empty));
797 f.instruction(&Instruction::I32Const(0));
798 f.instruction(&Instruction::Return);
799 f.instruction(&Instruction::End);
800
801 f.instruction(&Instruction::LocalGet(a));
802 f.instruction(&Instruction::LocalGet(b));
803 f.instruction(&Instruction::RefEq);
804 f.instruction(&Instruction::If(BlockType::Empty));
805 f.instruction(&Instruction::I32Const(1));
806 f.instruction(&Instruction::Return);
807 f.instruction(&Instruction::End);
808
809 f.instruction(&Instruction::LocalGet(a));
810 f.instruction(&Instruction::RefCastNonNull(HeapType::Concrete(
811 intrinsics.object_shape,
812 )));
813 f.instruction(&Instruction::LocalSet(a_t));
814 f.instruction(&Instruction::LocalGet(b));
815 f.instruction(&Instruction::RefCastNonNull(HeapType::Concrete(
816 intrinsics.object_shape,
817 )));
818 f.instruction(&Instruction::LocalSet(b_t));
819
820 emit_indexed_object_dispatch(&mut f, intrinsics, object_vtable_global, 2);
821
822 emit_shape_guard(
823 &mut f,
824 wasm_u32(fields.len())?,
825 intrinsics,
826 string_eq_func_idx,
827 (a_t, b_t),
828 );
829
830 for (slot, field) in fields.values().enumerate() {
831 let field_index = wasm_u32(slot)?;
832
833 for object in [a_t, b_t] {
834 emit_field_presence(
835 &mut f,
836 intrinsics,
837 symbols.optional_field_name_type()?,
838 object,
839 field_index,
840 );
841 }
842 f.instruction(&Instruction::I32Ne);
843 f.instruction(&Instruction::If(BlockType::Empty));
844 f.instruction(&Instruction::I32Const(0));
845 f.instruction(&Instruction::Return);
846 f.instruction(&Instruction::End);
847
848 emit_field_compare(
849 &mut f,
850 intrinsics.object_shape,
851 field_index,
852 &field.ty,
853 intrinsics,
854 (a_t, b_t, field_lhs, eq_fn, field_lhs_null, field_rhs_null),
855 )?;
856
857 f.instruction(&Instruction::I32Eqz);
858 f.instruction(&Instruction::If(BlockType::Empty));
859 f.instruction(&Instruction::I32Const(0));
860 f.instruction(&Instruction::Return);
861 f.instruction(&Instruction::End);
862 }
863
864 f.instruction(&Instruction::I32Const(1));
865 f.instruction(&Instruction::End);
866 Ok(f)
867}
868
869fn emit_field_presence(
872 body: &mut Function,
873 intrinsics: IntrinsicTypeIndices,
874 optional_name_type: u32,
875 object: u32,
876 slot: u32,
877) {
878 let load_name = |body: &mut Function| {
879 body.instruction(&Instruction::LocalGet(object));
880 body.instruction(&Instruction::StructGet {
881 struct_type_index: intrinsics.object_shape,
882 field_index: 1,
883 });
884 body.instruction(&Instruction::I32Const(slot.cast_signed()));
885 body.instruction(&Instruction::ArrayGet(intrinsics.field_names));
886 };
887 load_name(body);
888 body.instruction(&Instruction::RefTestNonNull(HeapType::Concrete(
889 optional_name_type,
890 )));
891 body.instruction(&Instruction::If(BlockType::Result(ValType::I32)));
892 load_name(body);
893 body.instruction(&Instruction::RefCastNonNull(HeapType::Concrete(
894 optional_name_type,
895 )));
896 body.instruction(&Instruction::StructGet {
897 struct_type_index: optional_name_type,
898 field_index: 3,
899 });
900 body.instruction(&Instruction::Else);
901 body.instruction(&Instruction::I32Const(1));
902 body.instruction(&Instruction::End);
903 let load_value = [
904 Instruction::LocalGet(object),
905 Instruction::StructGet {
906 struct_type_index: intrinsics.object_shape,
907 field_index: 2,
908 },
909 Instruction::I32Const(slot.cast_signed()),
910 Instruction::ArrayGet(intrinsics.object_fields),
911 ];
912 for instruction in super::nullish::is_nullish(&load_value, intrinsics) {
913 body.instruction(&instruction);
914 }
915 body.instruction(&Instruction::I32Eqz);
916 body.instruction(&Instruction::I32Or);
917}
918
919fn emit_field_compare(
927 f: &mut Function,
928 object_shape_idx: u32,
929 field_index: u32,
930 field_ty: &Type,
931 intrinsics: IntrinsicTypeIndices,
932 locals: (u32, u32, u32, u32, u32, u32),
933) -> Result<(), CompilerFailure> {
934 let (a_t, b_t, field_lhs, eq_fn, field_lhs_null, field_rhs_null) = locals;
935 reject_unrepresentable_field(field_ty)?;
936 emit_nullable_field_compare(
937 f,
938 object_shape_idx,
939 field_index,
940 intrinsics,
941 (field_lhs, eq_fn, field_lhs_null, field_rhs_null, a_t, b_t),
942 );
943 Ok(())
944}
945
946fn emit_subtype_hash_body(
948 subtype: &UserSubtype,
949 intrinsics: IntrinsicTypeIndices,
950 symbols: &SymbolTable,
951 object_vtable_global: u32,
952) -> Result<Function, CompilerFailure> {
953 let fields = subtype.fields()?;
954
955 let object_shape_ref = ref_to(intrinsics.object_shape);
956 let object_ref = ref_to(intrinsics.object);
957 let object_null_ref = ref_null(intrinsics.object);
958 let hash_fn_ref = ref_to(intrinsics.hash_fn);
959
960 let locals: Vec<(u32, ValType)> = vec![
967 (1, object_shape_ref),
968 (1, ValType::I32),
969 (1, object_ref),
970 (1, hash_fn_ref),
971 (1, object_null_ref),
972 ];
973 let mut f = Function::new(locals);
974 let self_param = 0u32;
975 let self_t = 1u32;
976 let hash = 2u32;
977 let field_obj = 3u32;
978 let hash_fn = 4u32;
979 let f_null = 5u32;
980 f.instruction(&Instruction::LocalGet(self_param));
981 f.instruction(&Instruction::RefCastNonNull(HeapType::Concrete(
982 intrinsics.object_shape,
983 )));
984 f.instruction(&Instruction::LocalSet(self_t));
985
986 emit_indexed_object_dispatch(&mut f, intrinsics, object_vtable_global, 3);
987
988 f.instruction(&Instruction::I32Const(0x811c9dc5u32.cast_signed()));
990 f.instruction(&Instruction::LocalSet(hash));
991
992 for (slot, (name, field)) in fields.iter().enumerate() {
993 let field_index = wasm_u32(slot)?;
994 if field.optional {
995 f.instruction(&Instruction::LocalGet(self_t));
996 f.instruction(&Instruction::StructGet {
997 struct_type_index: intrinsics.object_shape,
998 field_index: 2,
999 });
1000 f.instruction(&Instruction::I32Const(field_index.cast_signed()));
1001 f.instruction(&Instruction::ArrayGet(intrinsics.object_fields));
1002 f.instruction(&Instruction::LocalSet(f_null));
1003 super::field_names::emit_optional_presence(
1004 &mut f,
1005 intrinsics,
1006 symbols.optional_field_name_type()?,
1007 self_t,
1008 field_index,
1009 f_null,
1010 );
1011 f.instruction(&Instruction::If(BlockType::Empty));
1012 }
1013 emit_field_hash(
1014 &mut f,
1015 intrinsics.object_shape,
1016 field_index,
1017 &field.ty,
1018 intrinsics,
1019 (self_t, field_obj, hash_fn, f_null),
1020 )?;
1021 let name_hash = crate::runtime::prelude::vtable::hash_utf16_units(
1023 crate::literal_units::literal_units(name),
1024 );
1025 f.instruction(&Instruction::I32Const(13));
1026 f.instruction(&Instruction::I32Rotl);
1027 f.instruction(&Instruction::I32Const(name_hash.cast_signed()));
1028 f.instruction(&Instruction::I32Xor);
1029 f.instruction(&Instruction::LocalGet(hash));
1030 f.instruction(&Instruction::I32Add);
1031 f.instruction(&Instruction::LocalSet(hash));
1032 if field.optional {
1033 f.instruction(&Instruction::End);
1034 }
1035 }
1036
1037 f.instruction(&Instruction::LocalGet(hash));
1038 f.instruction(&Instruction::End);
1039 Ok(f)
1040}
1041
1042fn emit_field_hash(
1045 f: &mut Function,
1046 object_shape_idx: u32,
1047 field_index: u32,
1048 field_ty: &Type,
1049 intrinsics: IntrinsicTypeIndices,
1050 locals: (u32, u32, u32, u32),
1051) -> Result<(), CompilerFailure> {
1052 reject_unrepresentable_field(field_ty)?;
1053 emit_nullable_field_hash(f, object_shape_idx, field_index, intrinsics, locals);
1054 Ok(())
1055}
1056
1057fn reject_unrepresentable_field(field_ty: &Type) -> Result<(), CompilerFailure> {
1060 let field_ty = field_ty.peel();
1061 match field_ty {
1062 Type::Error | Type::Alias { .. } | Type::Refined { .. } | Type::Readonly(_) => {
1063 Err(internal_failure(format!(
1064 "an object field of type `{field_ty}` reached structural equality or hashing"
1065 )))
1066 }
1067 _ => Ok(()),
1068 }
1069}
1070
1071fn emit_nullable_field_hash(
1074 f: &mut Function,
1075 object_shape_idx: u32,
1076 field_index: u32,
1077 intrinsics: IntrinsicTypeIndices,
1078 locals: (u32, u32, u32, u32),
1079) {
1080 let (self_t, field_obj, hash_fn, f_null) = locals;
1081 f.instruction(&Instruction::LocalGet(self_t));
1082 f.instruction(&Instruction::StructGet {
1083 struct_type_index: object_shape_idx,
1084 field_index: 2,
1085 });
1086 f.instruction(&Instruction::I32Const(field_index.cast_signed()));
1087 f.instruction(&Instruction::ArrayGet(intrinsics.object_fields));
1088 f.instruction(&Instruction::LocalSet(f_null));
1089
1090 f.instruction(&Instruction::LocalGet(f_null));
1091 f.instruction(&Instruction::RefIsNull);
1092 f.instruction(&Instruction::If(BlockType::Result(ValType::I32)));
1093 f.instruction(&Instruction::I32Const(0));
1094 f.instruction(&Instruction::Else);
1095 f.instruction(&Instruction::LocalGet(f_null));
1096 f.instruction(&Instruction::RefAsNonNull);
1097 f.instruction(&Instruction::LocalTee(field_obj));
1098 f.instruction(&Instruction::StructGet {
1099 struct_type_index: intrinsics.object,
1100 field_index: 0,
1101 });
1102 f.instruction(&Instruction::StructGet {
1103 struct_type_index: intrinsics.vtable,
1104 field_index: 3,
1105 });
1106 f.instruction(&Instruction::LocalSet(hash_fn));
1107 f.instruction(&Instruction::LocalGet(field_obj));
1108 f.instruction(&Instruction::LocalGet(hash_fn));
1109 f.instruction(&Instruction::CallRef(intrinsics.hash_fn));
1110 f.instruction(&Instruction::End);
1111}
1112
1113fn emit_nullable_field_compare(
1115 f: &mut Function,
1116 object_shape_idx: u32,
1117 field_index: u32,
1118 intrinsics: IntrinsicTypeIndices,
1119 locals: (u32, u32, u32, u32, u32, u32),
1120) {
1121 let (field_lhs, eq_fn, field_lhs_null, field_rhs_null, a_t, b_t) = locals;
1122 f.instruction(&Instruction::LocalGet(a_t));
1123 f.instruction(&Instruction::StructGet {
1124 struct_type_index: object_shape_idx,
1125 field_index: 2,
1126 });
1127 f.instruction(&Instruction::I32Const(field_index.cast_signed()));
1128 f.instruction(&Instruction::ArrayGet(intrinsics.object_fields));
1129 f.instruction(&Instruction::LocalSet(field_lhs_null));
1130
1131 f.instruction(&Instruction::LocalGet(b_t));
1132 f.instruction(&Instruction::StructGet {
1133 struct_type_index: object_shape_idx,
1134 field_index: 2,
1135 });
1136 f.instruction(&Instruction::I32Const(field_index.cast_signed()));
1137 f.instruction(&Instruction::ArrayGet(intrinsics.object_fields));
1138 f.instruction(&Instruction::LocalSet(field_rhs_null));
1139
1140 f.instruction(&Instruction::LocalGet(field_lhs_null));
1141 f.instruction(&Instruction::RefIsNull);
1142 f.instruction(&Instruction::If(BlockType::Result(ValType::I32)));
1143 f.instruction(&Instruction::LocalGet(field_rhs_null));
1144 f.instruction(&Instruction::RefIsNull);
1145 f.instruction(&Instruction::Else);
1146 f.instruction(&Instruction::LocalGet(field_rhs_null));
1147 f.instruction(&Instruction::RefIsNull);
1148 f.instruction(&Instruction::If(BlockType::Result(ValType::I32)));
1149 f.instruction(&Instruction::I32Const(0));
1150 f.instruction(&Instruction::Else);
1151 f.instruction(&Instruction::LocalGet(field_lhs_null));
1152 f.instruction(&Instruction::RefAsNonNull);
1153 f.instruction(&Instruction::LocalTee(field_lhs));
1154 f.instruction(&Instruction::StructGet {
1155 struct_type_index: intrinsics.object,
1156 field_index: 0,
1157 });
1158 f.instruction(&Instruction::StructGet {
1159 struct_type_index: intrinsics.vtable,
1160 field_index: 2,
1161 });
1162 f.instruction(&Instruction::LocalSet(eq_fn));
1163 f.instruction(&Instruction::LocalGet(field_lhs));
1164 f.instruction(&Instruction::LocalGet(field_rhs_null));
1165 f.instruction(&Instruction::RefAsNonNull);
1166 f.instruction(&Instruction::LocalGet(eq_fn));
1167 f.instruction(&Instruction::CallRef(intrinsics.equals_fn));
1168 f.instruction(&Instruction::End);
1169 f.instruction(&Instruction::End);
1170}
1171
1172pub fn declared_method_funcs(subtypes: &[UserSubtype]) -> Vec<u32> {
1174 subtypes
1175 .iter()
1176 .flat_map(|s| [s.to_string_func, s.to_json_func, s.equals_func, s.hash_func])
1177 .collect()
1178}
1179
1180pub fn field_index(ty: &Type, field_name: &str) -> Result<Option<u32>, CompilerFailure> {
1182 let Type::Object { fields, .. } = ty else {
1183 return Ok(None);
1184 };
1185 fields
1186 .keys()
1187 .position(|k| k == field_name)
1188 .map(wasm_u32)
1189 .transpose()
1190}
1191
1192fn emit_field_value_to_json(
1193 f: &mut Function,
1194 elem: u32,
1195 tj_fn: u32,
1196 intrinsics: IntrinsicTypeIndices,
1197) {
1198 f.instruction(&Instruction::LocalGet(elem));
1199 f.instruction(&Instruction::RefAsNonNull);
1200 f.instruction(&Instruction::StructGet {
1201 struct_type_index: intrinsics.object,
1202 field_index: 0,
1203 });
1204 f.instruction(&Instruction::StructGet {
1205 struct_type_index: intrinsics.vtable,
1206 field_index: 1,
1207 });
1208 f.instruction(&Instruction::LocalSet(tj_fn));
1209
1210 f.instruction(&Instruction::LocalGet(elem));
1211 f.instruction(&Instruction::RefAsNonNull);
1212 f.instruction(&Instruction::LocalGet(tj_fn));
1213 f.instruction(&Instruction::CallRef(intrinsics.to_json_fn));
1214}
1215
1216fn push_inline_string(
1217 f: &mut Function,
1218 s: &str,
1219 string_idx: u32,
1220 raw_string_idx: u32,
1221 string_vtable_global_idx: u32,
1222) -> Result<(), CompilerFailure> {
1223 f.instruction(&Instruction::GlobalGet(string_vtable_global_idx));
1224 for b in s.bytes() {
1225 f.instruction(&Instruction::I32Const(i32::from(b)));
1226 }
1227 f.instruction(&Instruction::ArrayNewFixed {
1228 array_type_index: raw_string_idx,
1229 array_size: wasm_u32(s.len())?,
1230 });
1231 f.instruction(&Instruction::I64Const(0));
1232 f.instruction(&Instruction::StructNew(string_idx));
1233 Ok(())
1234}
1235
1236pub(crate) fn json_escape_key(s: &str) -> String {
1237 let mut out = String::with_capacity(s.len());
1238 for ch in s.chars() {
1239 match ch {
1240 '\\' => out.push_str("\\\\"),
1241 '"' => out.push_str("\\\""),
1242 '\n' => out.push_str("\\n"),
1243 '\r' => out.push_str("\\r"),
1244 '\t' => out.push_str("\\t"),
1245 c if c.is_control() => out.push_str(&format!("\\u{:04x}", u32::from(c))),
1246 c => out.push(c),
1247 }
1248 }
1249 out
1250}
1251
1252fn ref_to(idx: u32) -> ValType {
1253 ValType::Ref(RefType {
1254 nullable: false,
1255 heap_type: HeapType::Concrete(idx),
1256 })
1257}
1258
1259fn ref_null(idx: u32) -> ValType {
1260 ValType::Ref(RefType {
1261 nullable: true,
1262 heap_type: HeapType::Concrete(idx),
1263 })
1264}
1265
1266fn emit_equals_type_guard(f: &mut Function, type_idx: u32) {
1271 f.instruction(&Instruction::LocalGet(1));
1272 f.instruction(&Instruction::RefTestNonNull(HeapType::Concrete(type_idx)));
1273 f.instruction(&Instruction::I32Eqz);
1274 f.instruction(&Instruction::If(BlockType::Empty));
1275 f.instruction(&Instruction::I32Const(0));
1276 f.instruction(&Instruction::Return);
1277 f.instruction(&Instruction::End);
1278}
1279
1280fn emit_shape_guard(
1290 f: &mut Function,
1291 n_fields: u32,
1292 intrinsics: IntrinsicTypeIndices,
1293 string_eq_func_idx: u32,
1294 (a_t, b_t): (u32, u32),
1295) {
1296 let load_names = |f: &mut Function, side: u32| {
1297 f.instruction(&Instruction::LocalGet(side));
1298 f.instruction(&Instruction::StructGet {
1299 struct_type_index: intrinsics.object_shape,
1300 field_index: 1,
1301 });
1302 };
1303 let load_name_at = |f: &mut Function, side: u32, i: u32| {
1304 load_names(f, side);
1305 f.instruction(&Instruction::I32Const(i.cast_signed()));
1306 f.instruction(&Instruction::ArrayGet(intrinsics.field_names));
1307 };
1308
1309 load_names(f, a_t);
1310 load_names(f, b_t);
1311 f.instruction(&Instruction::RefEq);
1312 f.instruction(&Instruction::I32Eqz);
1313 f.instruction(&Instruction::If(BlockType::Empty));
1314
1315 load_names(f, b_t);
1316 f.instruction(&Instruction::ArrayLen);
1317 f.instruction(&Instruction::I32Const(n_fields.cast_signed()));
1318 f.instruction(&Instruction::I32Ne);
1319 f.instruction(&Instruction::If(BlockType::Empty));
1320 f.instruction(&Instruction::I32Const(0));
1321 f.instruction(&Instruction::Return);
1322 f.instruction(&Instruction::End);
1323
1324 for i in 0..n_fields {
1325 load_name_at(f, a_t, i);
1326 load_name_at(f, b_t, i);
1327 f.instruction(&Instruction::Call(string_eq_func_idx));
1328 f.instruction(&Instruction::I32Eqz);
1329 f.instruction(&Instruction::If(BlockType::Empty));
1330 f.instruction(&Instruction::I32Const(0));
1331 f.instruction(&Instruction::Return);
1332 f.instruction(&Instruction::End);
1333 }
1334
1335 f.instruction(&Instruction::End);
1336}