Skip to main content

submilli_engine/codegen/
field_name_strings.rs

1//! Per-field-name `(ref $string)` Wasm globals.
2//!
3//! Sharing a single global per name makes `ref.eq` correctly identify fields
4//! at runtime — identity equality on the `$string` allocation.
5
6use std::collections::BTreeSet;
7
8use wasm_encoder::{ConstExpr, GlobalSection, GlobalType, HeapType, Instruction, RefType, ValType};
9
10use crate::Type;
11use crate::codegen::intrinsics::IntrinsicTypeIndices;
12use crate::codegen::symbol_table::SymbolTable;
13use crate::codegen::{next_index, wasm_u32};
14use crate::compiler_error::CompilerFailure;
15
16/// `extra_names` injects VTable interface method names (Iterator, Iterable, …)
17/// that don't appear in any object literal — without them dispatch chains can't
18/// find the per-name string global they expect.
19pub fn collect(shapes: &[Type], extra_names: &[String]) -> Vec<String> {
20    let mut names: BTreeSet<String> = BTreeSet::new();
21    for shape in shapes {
22        if let Type::Object { fields, .. } = shape {
23            for k in fields.keys() {
24                names.insert(k.clone());
25            }
26        }
27    }
28    for name in extra_names {
29        names.insert(name.clone());
30    }
31    names.into_iter().collect()
32}
33
34pub fn emit(
35    names: &[String],
36    globals: &mut GlobalSection,
37    symbols: &mut SymbolTable,
38    next_global_idx: &mut u32,
39    intrinsics: IntrinsicTypeIndices,
40    string_vtable_global_idx: u32,
41) -> Result<(), CompilerFailure> {
42    let string_val_type = ValType::Ref(RefType {
43        nullable: false,
44        heap_type: HeapType::Concrete(intrinsics.string),
45    });
46    for name in names {
47        let init = build_init_expr(name, intrinsics, string_vtable_global_idx)?;
48        let global_idx = next_index(next_global_idx)?;
49        globals.global(
50            GlobalType {
51                val_type: string_val_type,
52                mutable: false,
53                shared: false,
54            },
55            &init,
56        );
57        symbols.record_field_name_string_global(name.clone(), global_idx);
58    }
59    Ok(())
60}
61
62fn build_init_expr(
63    name: &str,
64    intrinsics: IntrinsicTypeIndices,
65    string_vtable_global_idx: u32,
66) -> Result<ConstExpr, CompilerFailure> {
67    let mut instrs: Vec<Instruction<'_>> = Vec::new();
68    let code_units = crate::literal_units::literal_units(name);
69    instrs.push(Instruction::GlobalGet(string_vtable_global_idx));
70    for unit in &code_units {
71        instrs.push(Instruction::I32Const(i32::from(*unit)));
72    }
73    instrs.push(Instruction::ArrayNewFixed {
74        array_type_index: intrinsics.raw_string,
75        array_size: wasm_u32(code_units.len())?,
76    });
77    instrs.push(Instruction::I64Const(0));
78    instrs.push(Instruction::StructNew(intrinsics.string));
79    Ok(ConstExpr::extended(instrs))
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85    use std::collections::BTreeMap;
86
87    #[test]
88    fn collect_union_of_field_names_alphabetical() {
89        use crate::ObjectField;
90        let mut a = BTreeMap::new();
91        a.insert("kind".to_string(), ObjectField::required(Type::Number));
92        a.insert("radius".to_string(), ObjectField::required(Type::Number));
93        let mut b = BTreeMap::new();
94        b.insert("kind".to_string(), ObjectField::required(Type::Number));
95        b.insert("height".to_string(), ObjectField::required(Type::Number));
96        b.insert("width".to_string(), ObjectField::required(Type::Number));
97        let shapes = vec![
98            Type::Object {
99                index: None,
100                fields: a,
101            },
102            Type::Object {
103                index: None,
104                fields: b,
105            },
106        ];
107        let names = collect(&shapes, &[]);
108        assert_eq!(
109            names,
110            vec![
111                "height".to_string(),
112                "kind".to_string(),
113                "radius".to_string(),
114                "width".to_string(),
115            ]
116        );
117    }
118}