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