Skip to main content

submilli_engine/codegen/
field_names.rs

1//! Per-module canonical-shape field-names registry.
2
3use std::collections::BTreeSet;
4
5use wasm_encoder::{
6    ConstExpr, GlobalSection, GlobalType, HeapType, Instruction, RefType, StorageType, ValType,
7};
8
9use crate::Type;
10use crate::codegen::intrinsics::IntrinsicTypeIndices;
11use crate::codegen::symbol_table::SymbolTable;
12use crate::codegen::{internal_failure, next_index, wasm_u32};
13use crate::compiler_error::CompilerFailure;
14
15/// Optional field names use a nominal string subtype. The name's text stays
16/// unchanged, while `in` can recover optionality after receiver-type erasure.
17#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
18pub struct FieldName {
19    pub name: String,
20    pub optional: bool,
21    pub is_accessor: bool,
22    pub is_private: bool,
23}
24
25pub fn declare_optional_name_type(
26    types: &mut wasm_encoder::TypeSection,
27    symbols: &mut SymbolTable,
28    next_type_idx: &mut u32,
29    intrinsics: IntrinsicTypeIndices,
30) -> Result<(), CompilerFailure> {
31    let type_idx = next_index(next_type_idx)?;
32    let mut fields = [intrinsics.vtable, intrinsics.raw_string]
33        .map(|index| wasm_encoder::FieldType {
34            element_type: StorageType::Val(ValType::Ref(RefType {
35                nullable: false,
36                heap_type: HeapType::Concrete(index),
37            })),
38            mutable: false,
39        })
40        .to_vec();
41    fields.push(wasm_encoder::FieldType {
42        element_type: StorageType::Val(ValType::I64),
43        mutable: true,
44    });
45    fields.push(wasm_encoder::FieldType {
46        element_type: StorageType::Val(ValType::I32),
47        mutable: true,
48    });
49    fields.push(wasm_encoder::FieldType {
50        element_type: StorageType::Val(ValType::I32),
51        mutable: false,
52    });
53    types.ty().subtype(&wasm_encoder::SubType {
54        is_final: true,
55        supertype_idx: Some(intrinsics.string),
56        composite_type: wasm_encoder::CompositeType {
57            inner: wasm_encoder::CompositeInnerType::Struct(wasm_encoder::StructType {
58                fields: fields.into(),
59            }),
60            shared: false,
61            descriptor: None,
62            describes: None,
63        },
64    });
65    symbols.record_optional_field_name_type(type_idx);
66    Ok(())
67}
68
69pub fn collect(shapes: &[Type]) -> Vec<Vec<FieldName>> {
70    let mut keys: BTreeSet<Vec<FieldName>> = BTreeSet::new();
71    for shape in shapes {
72        if let Type::Object { fields, .. } = shape {
73            keys.insert(
74                fields
75                    .iter()
76                    .map(|(name, field)| FieldName {
77                        name: name.clone(),
78                        optional: field.optional,
79                        is_accessor: false,
80                        is_private: false,
81                    })
82                    .collect(),
83            );
84        }
85    }
86    keys.into_iter().collect()
87}
88
89pub fn emit(
90    shapes: &[Vec<FieldName>],
91    globals: &mut GlobalSection,
92    symbols: &mut SymbolTable,
93    next_global_idx: &mut u32,
94    intrinsics: IntrinsicTypeIndices,
95    string_vtable_global_idx: u32,
96) -> Result<(), CompilerFailure> {
97    let field_names_val_type = ValType::Ref(RefType {
98        nullable: false,
99        heap_type: HeapType::Concrete(intrinsics.field_names),
100    });
101    let optional_name_type = symbols.optional_field_name_type()?;
102    for shape in shapes {
103        let init = build_init_expr(
104            shape,
105            intrinsics,
106            string_vtable_global_idx,
107            optional_name_type,
108        )?;
109        let global_idx = next_index(next_global_idx)?;
110        globals.global(
111            GlobalType {
112                val_type: field_names_val_type,
113                mutable: false,
114                shared: false,
115            },
116            &init,
117        );
118        symbols.record_field_names_global(shape.clone(), global_idx);
119    }
120    Ok(())
121}
122
123fn build_init_expr(
124    shape: &[FieldName],
125    intrinsics: IntrinsicTypeIndices,
126    string_vtable_global_idx: u32,
127    optional_name_type: u32,
128) -> Result<ConstExpr, CompilerFailure> {
129    let mut instrs: Vec<Instruction<'_>> = Vec::new();
130    for name in shape {
131        let code_units = crate::literal_units::literal_units(&name.name);
132        instrs.push(Instruction::GlobalGet(string_vtable_global_idx));
133        for unit in &code_units {
134            instrs.push(Instruction::I32Const(i32::from(*unit)));
135        }
136        instrs.push(Instruction::ArrayNewFixed {
137            array_type_index: intrinsics.raw_string,
138            array_size: wasm_u32(code_units.len())?,
139        });
140        instrs.push(Instruction::I64Const(0));
141        if name.optional || name.is_accessor || name.is_private {
142            instrs.push(Instruction::I32Const(if name.is_accessor {
143                -1
144            } else if name.optional {
145                0
146            } else {
147                1
148            }));
149            instrs.push(Instruction::I32Const(i32::from(name.is_private)));
150        }
151        instrs.push(Instruction::StructNew(
152            if name.optional || name.is_accessor || name.is_private {
153                optional_name_type
154            } else {
155                intrinsics.string
156            },
157        ));
158    }
159    instrs.push(Instruction::ArrayNewFixed {
160        array_type_index: intrinsics.field_names,
161        array_size: wasm_u32(shape.len())?,
162    });
163    Ok(ConstExpr::extended(instrs))
164}
165
166/// Optional names carry per-instance presence separately from the value slot.
167/// Required names remain shared immutable strings.
168pub(crate) fn emit_instance_names(
169    emitter: &mut super::function_emitter::FunctionEmitter,
170    ctx: &super::CodegenCtx,
171    names: &[FieldName],
172    present: impl Fn(&str) -> bool,
173) -> Result<(), CompilerFailure> {
174    let global = ctx
175        .symbols
176        .field_names_global_idx(names)
177        .ok_or_else(|| internal_failure("object field names were not collected"))?;
178    if !names.iter().any(|name| name.optional) {
179        emitter.instruction(Instruction::GlobalGet(global));
180        return Ok(());
181    }
182    let intrinsics = intrinsics(ctx)?;
183    let optional_name_type = ctx.symbols.optional_field_name_type()?;
184    for (index, name) in names.iter().enumerate() {
185        let index = wasm_u32(index)?.cast_signed();
186        if name.optional {
187            for field_index in [0, 1] {
188                emitter.instruction(Instruction::GlobalGet(global));
189                emitter.instruction(Instruction::I32Const(index));
190                emitter.instruction(Instruction::ArrayGet(intrinsics.field_names));
191                emitter.instruction(Instruction::StructGet {
192                    struct_type_index: intrinsics.string,
193                    field_index,
194                });
195            }
196            emitter.instruction(Instruction::I64Const(0));
197            emitter.instruction(Instruction::I32Const(i32::from(present(&name.name))));
198            emitter.instruction(Instruction::I32Const(i32::from(name.is_private)));
199            emitter.instruction(Instruction::StructNew(optional_name_type));
200        } else {
201            emitter.instruction(Instruction::GlobalGet(global));
202            emitter.instruction(Instruction::I32Const(index));
203            emitter.instruction(Instruction::ArrayGet(intrinsics.field_names));
204        }
205    }
206    emitter.instruction(Instruction::ArrayNewFixed {
207        array_type_index: intrinsics.field_names,
208        array_size: wasm_u32(names.len())?,
209    });
210    Ok(())
211}
212
213/// Push whether the name on the stack marks an internal accessor payload slot.
214pub(crate) fn emit_name_is_accessor(
215    emitter: &mut super::function_emitter::FunctionEmitter,
216    ctx: &super::CodegenCtx,
217) -> Result<(), crate::compiler_error::CompilerFailure> {
218    let name = emitter.add_anonymous_local(ctx.symbols.value_type(&Type::String)?)?;
219    let marked = ctx.symbols.optional_field_name_type()?;
220    emitter.instruction(Instruction::LocalTee(name));
221    emitter.instruction(Instruction::RefTestNonNull(HeapType::Concrete(marked)));
222    emitter.emit_if(wasm_encoder::BlockType::Result(ValType::I32));
223    emitter.instruction(Instruction::LocalGet(name));
224    emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(marked)));
225    emitter.instruction(Instruction::StructGet {
226        struct_type_index: marked,
227        field_index: 3,
228    });
229    emitter.instruction(Instruction::I32Const(-1));
230    emitter.instruction(Instruction::I32Eq);
231    emitter.emit_else();
232    emitter.instruction(Instruction::I32Const(0));
233    emitter.emit_end();
234
235    Ok(())
236}
237
238/// Push the presence flag for a field name already on the stack.
239pub(crate) fn emit_name_presence(
240    emitter: &mut super::function_emitter::FunctionEmitter,
241    ctx: &super::CodegenCtx,
242) -> Result<(), CompilerFailure> {
243    let intrinsics = intrinsics(ctx)?;
244    let optional = ctx.symbols.optional_field_name_type()?;
245    let name = emitter.add_anonymous_local(ValType::Ref(RefType {
246        nullable: false,
247        heap_type: HeapType::Concrete(intrinsics.string),
248    }))?;
249    emitter.instruction(Instruction::LocalTee(name));
250    emitter.instruction(Instruction::RefTestNonNull(HeapType::Concrete(optional)));
251    emitter.emit_if(wasm_encoder::BlockType::Result(ValType::I32));
252    emitter.instruction(Instruction::LocalGet(name));
253    emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(optional)));
254    emitter.instruction(Instruction::StructGet {
255        struct_type_index: optional,
256        field_index: 3,
257    });
258    emitter.instruction(Instruction::I32Eqz);
259    emitter.instruction(Instruction::I32Eqz);
260    emitter.emit_else();
261    emitter.instruction(Instruction::I32Const(1));
262    emitter.emit_end();
263    Ok(())
264}
265
266/// The object and index are locals; callers mark a successful store as present.
267pub(crate) fn emit_mark_present(
268    emitter: &mut super::function_emitter::FunctionEmitter,
269    ctx: &super::CodegenCtx,
270    object: u32,
271    index: u32,
272) -> Result<(), CompilerFailure> {
273    let intrinsics = intrinsics(ctx)?;
274    let optional = ctx.symbols.optional_field_name_type()?;
275    let name = emitter.add_anonymous_local(ValType::Ref(RefType {
276        nullable: false,
277        heap_type: HeapType::Concrete(intrinsics.string),
278    }))?;
279    emitter.instruction(Instruction::LocalGet(object));
280    emitter.instruction(Instruction::StructGet {
281        struct_type_index: intrinsics.object_shape,
282        field_index: 1,
283    });
284    emitter.instruction(Instruction::LocalGet(index));
285    emitter.instruction(Instruction::ArrayGet(intrinsics.field_names));
286    emitter.instruction(Instruction::LocalTee(name));
287    emitter.instruction(Instruction::RefTestNonNull(HeapType::Concrete(optional)));
288    emitter.emit_if(wasm_encoder::BlockType::Empty);
289    emitter.instruction(Instruction::LocalGet(name));
290    emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(optional)));
291    emitter.instruction(Instruction::StructGet {
292        struct_type_index: optional,
293        field_index: 3,
294    });
295    emitter.instruction(Instruction::I32Eqz);
296    emitter.emit_if(wasm_encoder::BlockType::Empty);
297    emitter.instruction(Instruction::LocalGet(name));
298    emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(optional)));
299    emitter.instruction(Instruction::I32Const(1));
300    emitter.instruction(Instruction::StructSet {
301        struct_type_index: optional,
302        field_index: 3,
303    });
304    emitter.emit_end();
305    emitter.emit_end();
306    Ok(())
307}
308
309fn intrinsics(ctx: &super::CodegenCtx) -> Result<IntrinsicTypeIndices, CompilerFailure> {
310    ctx.symbols
311        .intrinsic_type_indices()
312        .ok_or_else(|| internal_failure("intrinsic types are not declared"))
313}
314
315/// Serializer fast path for an optional slot in its own declared layout.
316pub(crate) fn emit_optional_presence(
317    function: &mut wasm_encoder::Function,
318    intrinsics: IntrinsicTypeIndices,
319    optional_name_type: u32,
320    object: u32,
321    index: u32,
322    value: u32,
323) {
324    for instruction in [
325        Instruction::LocalGet(object),
326        Instruction::RefCastNonNull(HeapType::Concrete(intrinsics.object_shape)),
327        Instruction::StructGet {
328            struct_type_index: intrinsics.object_shape,
329            field_index: 1,
330        },
331        Instruction::I32Const(index.cast_signed()),
332        Instruction::ArrayGet(intrinsics.field_names),
333        Instruction::RefCastNonNull(HeapType::Concrete(optional_name_type)),
334        Instruction::StructGet {
335            struct_type_index: optional_name_type,
336            field_index: 3,
337        },
338    ]
339    .into_iter()
340    .chain(super::nullish::is_nullish(
341        &[Instruction::LocalGet(value)],
342        intrinsics,
343    ))
344    .chain([Instruction::I32Eqz, Instruction::I32Or])
345    {
346        function.instruction(&instruction);
347    }
348}
349
350#[cfg(test)]
351mod tests {
352    use super::*;
353    use std::collections::BTreeMap;
354
355    #[test]
356    fn collect_dedupes_same_shape_with_different_field_types() {
357        use crate::ObjectField;
358        let mut a = BTreeMap::new();
359        a.insert("x".to_string(), ObjectField::required(Type::Number));
360        let mut b = BTreeMap::new();
361        b.insert("x".to_string(), ObjectField::required(Type::String));
362        let shapes = vec![
363            Type::Object {
364                index: None,
365                fields: a,
366            },
367            Type::Object {
368                index: None,
369                fields: b,
370            },
371        ];
372        let keys = collect(&shapes);
373        assert_eq!(keys.len(), 1);
374        assert_eq!(
375            keys[0],
376            vec![FieldName {
377                name: "x".to_string(),
378                optional: false,
379                is_accessor: false,
380                is_private: false,
381            }]
382        );
383    }
384
385    #[test]
386    fn collect_separates_shapes_with_different_field_names() {
387        use crate::ObjectField;
388        let mut a = BTreeMap::new();
389        a.insert("x".to_string(), ObjectField::required(Type::Number));
390        let mut b = BTreeMap::new();
391        b.insert("y".to_string(), ObjectField::required(Type::Number));
392        let shapes = vec![
393            Type::Object {
394                index: None,
395                fields: a,
396            },
397            Type::Object {
398                index: None,
399                fields: b,
400            },
401        ];
402        let keys = collect(&shapes);
403        assert_eq!(keys.len(), 2);
404        assert_eq!(
405            keys[0],
406            vec![FieldName {
407                name: "x".to_string(),
408                optional: false,
409                is_accessor: false,
410                is_private: false,
411            }]
412        );
413        assert_eq!(
414            keys[1],
415            vec![FieldName {
416                name: "y".to_string(),
417                optional: false,
418                is_accessor: false,
419                is_private: false,
420            }]
421        );
422    }
423}