use std::collections::HashMap;
use crate::wasm::WasmError;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WasmFieldType {
I32,
I64,
F32,
F64,
Ptr,
}
impl WasmFieldType {
#[cfg(feature = "wasm-compile")]
pub fn to_val_type(self) -> wasm_encoder::ValType {
match self {
WasmFieldType::I32 | WasmFieldType::Ptr => wasm_encoder::ValType::I32,
WasmFieldType::I64 => wasm_encoder::ValType::I64,
WasmFieldType::F32 => wasm_encoder::ValType::F32,
WasmFieldType::F64 => wasm_encoder::ValType::F64,
}
}
pub fn size(self) -> u32 {
match self {
WasmFieldType::I32 | WasmFieldType::F32 | WasmFieldType::Ptr => 4,
WasmFieldType::I64 | WasmFieldType::F64 => 8,
}
}
pub fn alignment(self) -> u32 {
self.size() }
pub fn alignment_log2(self) -> u32 {
self.alignment().trailing_zeros()
}
}
impl std::fmt::Display for WasmFieldType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
WasmFieldType::I32 => write!(f, "i32"),
WasmFieldType::I64 => write!(f, "i64"),
WasmFieldType::F32 => write!(f, "f32"),
WasmFieldType::F64 => write!(f, "f64"),
WasmFieldType::Ptr => write!(f, "ptr"),
}
}
}
#[derive(Debug, Clone)]
pub struct FieldLayout {
pub name: String,
pub offset: u32,
pub size: u32,
pub alignment: u32,
pub wasm_type: WasmFieldType,
pub is_reference: bool,
pub nested_layout: Option<Box<GeneLayout>>,
}
impl FieldLayout {
pub fn primitive(name: impl Into<String>, offset: u32, wasm_type: WasmFieldType) -> Self {
Self {
name: name.into(),
offset,
size: wasm_type.size(),
alignment: wasm_type.alignment(),
wasm_type,
is_reference: false,
nested_layout: None,
}
}
pub fn reference(name: impl Into<String>, offset: u32) -> Self {
Self {
name: name.into(),
offset,
size: 4,
alignment: 4,
wasm_type: WasmFieldType::Ptr,
is_reference: true,
nested_layout: None,
}
}
pub fn inline(name: impl Into<String>, offset: u32, layout: GeneLayout) -> Self {
Self {
name: name.into(),
offset,
size: layout.total_size,
alignment: layout.alignment,
wasm_type: WasmFieldType::I32,
is_reference: false,
nested_layout: Some(Box::new(layout)),
}
}
}
#[derive(Debug, Clone)]
pub struct GeneLayout {
pub name: String,
pub fields: Vec<FieldLayout>,
pub total_size: u32,
pub alignment: u32,
}
impl GeneLayout {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
fields: Vec::new(),
total_size: 0,
alignment: 1,
}
}
pub fn get_field(&self, name: &str) -> Option<&FieldLayout> {
self.fields.iter().find(|f| f.name == name)
}
pub fn get_field_offset(&self, name: &str) -> Option<u32> {
self.get_field(name).map(|f| f.offset)
}
pub fn type_id(&self) -> u32 {
let mut hash = 0u32;
for byte in self.name.bytes() {
hash = hash.wrapping_mul(31).wrapping_add(byte as u32);
}
hash
}
pub fn has_references(&self) -> bool {
self.fields.iter().any(|f| f.is_reference)
}
pub fn pointer_offsets(&self) -> Vec<u32> {
self.fields
.iter()
.filter(|f| f.is_reference)
.map(|f| f.offset)
.collect()
}
pub fn field_count(&self) -> usize {
self.fields.len()
}
pub fn is_empty(&self) -> bool {
self.fields.is_empty()
}
}
impl Default for GeneLayout {
fn default() -> Self {
Self::new("")
}
}
#[derive(Debug, Default, Clone)]
pub struct GeneLayoutRegistry {
layouts: HashMap<String, GeneLayout>,
}
impl GeneLayoutRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn register(&mut self, layout: GeneLayout) {
self.layouts.insert(layout.name.clone(), layout);
}
pub fn get(&self, name: &str) -> Option<&GeneLayout> {
self.layouts.get(name)
}
pub fn contains(&self, name: &str) -> bool {
self.layouts.contains_key(name)
}
pub fn len(&self) -> usize {
self.layouts.len()
}
pub fn is_empty(&self) -> bool {
self.layouts.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = (&String, &GeneLayout)> {
self.layouts.iter()
}
pub fn names(&self) -> Vec<String> {
self.layouts.keys().cloned().collect()
}
pub fn remove(&mut self, name: &str) -> Option<GeneLayout> {
self.layouts.remove(name)
}
pub fn clear(&mut self) {
self.layouts.clear();
}
}
#[derive(Debug, Clone)]
pub struct EnumDef {
pub name: String,
pub variants: Vec<EnumVariantDef>,
}
#[derive(Debug, Clone)]
pub struct EnumVariantDef {
pub name: String,
pub index: i32,
}
impl EnumDef {
pub fn new(name: String, variant_names: Vec<String>) -> Self {
let variants = variant_names
.into_iter()
.enumerate()
.map(|(i, name)| EnumVariantDef {
name,
index: i as i32,
})
.collect();
Self { name, variants }
}
pub fn with_discriminants(name: String, variants: Vec<(String, i32)>) -> Self {
let variants = variants
.into_iter()
.map(|(name, index)| EnumVariantDef { name, index })
.collect();
Self { name, variants }
}
pub fn get_variant_index(&self, variant_name: &str) -> Option<i32> {
self.variants
.iter()
.find(|v| v.name == variant_name)
.map(|v| v.index)
}
pub fn has_variant(&self, variant_name: &str) -> bool {
self.variants.iter().any(|v| v.name == variant_name)
}
pub fn variant_count(&self) -> usize {
self.variants.len()
}
}
#[derive(Debug, Default, Clone)]
pub struct EnumRegistry {
enums: HashMap<String, EnumDef>,
}
impl EnumRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn register(&mut self, enum_def: EnumDef) {
self.enums.insert(enum_def.name.clone(), enum_def);
}
pub fn register_enum(&mut self, name: &str, variant_names: Vec<String>) {
self.register(EnumDef::new(name.to_string(), variant_names));
}
pub fn get(&self, name: &str) -> Option<&EnumDef> {
self.enums.get(name)
}
pub fn contains(&self, name: &str) -> bool {
self.enums.contains_key(name)
}
pub fn get_variant_index(&self, enum_name: &str, variant_name: &str) -> Option<i32> {
self.enums
.get(enum_name)
.and_then(|e| e.get_variant_index(variant_name))
}
pub fn has_variant(&self, enum_name: &str, variant_name: &str) -> bool {
self.enums
.get(enum_name)
.map(|e| e.has_variant(variant_name))
.unwrap_or(false)
}
pub fn len(&self) -> usize {
self.enums.len()
}
pub fn is_empty(&self) -> bool {
self.enums.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = (&String, &EnumDef)> {
self.enums.iter()
}
pub fn clear(&mut self) {
self.enums.clear();
}
}
#[inline]
pub fn align_up(offset: u32, alignment: u32) -> u32 {
debug_assert!(
alignment.is_power_of_two(),
"alignment must be a power of 2"
);
(offset + alignment - 1) & !(alignment - 1)
}
#[derive(Debug, Clone)]
struct TypeInfo {
wasm_type: WasmFieldType,
size: u32,
alignment: u32,
is_reference: bool,
nested_layout: Option<Box<GeneLayout>>,
}
fn type_to_wasm_info(
type_expr: &crate::ast::TypeExpr,
registry: &GeneLayoutRegistry,
) -> Result<TypeInfo, WasmError> {
use crate::ast::TypeExpr;
match type_expr {
TypeExpr::Named(name) => match name.as_str() {
"Int32" | "i32" => Ok(TypeInfo {
wasm_type: WasmFieldType::I32,
size: 4,
alignment: 4,
is_reference: false,
nested_layout: None,
}),
"Int64" | "i64" => Ok(TypeInfo {
wasm_type: WasmFieldType::I64,
size: 8,
alignment: 8,
is_reference: false,
nested_layout: None,
}),
"Float32" | "f32" => Ok(TypeInfo {
wasm_type: WasmFieldType::F32,
size: 4,
alignment: 4,
is_reference: false,
nested_layout: None,
}),
"Float64" | "f64" => Ok(TypeInfo {
wasm_type: WasmFieldType::F64,
size: 8,
alignment: 8,
is_reference: false,
nested_layout: None,
}),
"Bool" | "bool" => Ok(TypeInfo {
wasm_type: WasmFieldType::I32,
size: 4,
alignment: 4,
is_reference: false,
nested_layout: None,
}),
"Char" | "char" => Ok(TypeInfo {
wasm_type: WasmFieldType::I32,
size: 4,
alignment: 4,
is_reference: false,
nested_layout: None,
}),
"String" => Ok(TypeInfo {
wasm_type: WasmFieldType::Ptr,
size: 4,
alignment: 4,
is_reference: true,
nested_layout: None,
}),
_ if name.starts_with('&') => {
Ok(TypeInfo {
wasm_type: WasmFieldType::Ptr,
size: 4,
alignment: 4,
is_reference: true,
nested_layout: None,
})
}
gene_name => {
if let Some(layout) = registry.get(gene_name) {
Ok(TypeInfo {
wasm_type: WasmFieldType::I32, size: layout.total_size,
alignment: layout.alignment,
is_reference: false,
nested_layout: Some(Box::new(layout.clone())),
})
} else {
Err(WasmError::new(format!("Unknown type: {}", gene_name)))
}
}
},
TypeExpr::Generic { name, args: _ } => {
if name.starts_with('&') {
Ok(TypeInfo {
wasm_type: WasmFieldType::Ptr,
size: 4,
alignment: 4,
is_reference: true,
nested_layout: None,
})
} else if name == "List" || name == "Vec" || name == "Option" {
Ok(TypeInfo {
wasm_type: WasmFieldType::Ptr,
size: 4,
alignment: 4,
is_reference: true,
nested_layout: None,
})
} else {
Err(WasmError::new(format!(
"Generic types not yet fully supported: {}",
name
)))
}
}
TypeExpr::Function { .. } => {
Ok(TypeInfo {
wasm_type: WasmFieldType::I32,
size: 4,
alignment: 4,
is_reference: false,
nested_layout: None,
})
}
TypeExpr::Tuple(types) => {
if types.is_empty() {
Ok(TypeInfo {
wasm_type: WasmFieldType::I32,
size: 0,
alignment: 1,
is_reference: false,
nested_layout: None,
})
} else {
Err(WasmError::new("Tuple types not yet supported".to_string()))
}
}
TypeExpr::Never => {
Err(WasmError::new(
"Never type cannot have a memory layout".to_string(),
))
}
TypeExpr::Enum { .. } => {
Ok(TypeInfo {
wasm_type: WasmFieldType::I32,
size: 4,
alignment: 4,
is_reference: false,
nested_layout: None,
})
}
}
}
pub fn compute_gene_layout(
gene: &crate::ast::Gen,
registry: &GeneLayoutRegistry,
) -> Result<GeneLayout, WasmError> {
use crate::ast::Statement;
let mut fields = Vec::new();
let mut current_offset = 0u32;
let mut max_alignment = 1u32;
if let Some(parent_name) = &gene.extends {
let parent_layout = registry.get(parent_name).ok_or_else(|| {
WasmError::new(format!(
"Gene '{}' extends unknown parent '{}'",
gene.name, parent_name
))
})?;
for parent_field in &parent_layout.fields {
fields.push(parent_field.clone());
}
current_offset = parent_layout.total_size;
max_alignment = parent_layout.alignment;
}
for stmt in &gene.statements {
if let Statement::HasField(field_box) = stmt {
let field = field_box.as_ref();
let type_info = type_to_wasm_info(&field.type_, registry)?;
current_offset = align_up(current_offset, type_info.alignment);
let field_layout = FieldLayout {
name: field.name.clone(),
offset: current_offset,
size: type_info.size,
alignment: type_info.alignment,
wasm_type: type_info.wasm_type,
is_reference: type_info.is_reference,
nested_layout: type_info.nested_layout,
};
fields.push(field_layout);
current_offset += type_info.size;
max_alignment = max_alignment.max(type_info.alignment);
}
}
let total_size = if max_alignment > 0 {
align_up(current_offset, max_alignment)
} else {
current_offset
};
Ok(GeneLayout {
name: gene.name.clone(),
fields,
total_size,
alignment: max_alignment,
})
}
#[derive(Debug, Clone)]
pub struct GeneTypeDescriptor {
pub size: u32,
pub pointer_offsets: Vec<u32>,
pub has_references: bool,
}
impl GeneLayout {
pub fn to_gc_descriptor(&self) -> GeneTypeDescriptor {
let pointer_offsets = self.pointer_offsets();
GeneTypeDescriptor {
size: self.total_size,
has_references: !pointer_offsets.is_empty(),
pointer_offsets,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::{Gen, HasField, Span, Statement, TypeExpr, Visibility};
fn make_field(name: &str, type_name: &str) -> Statement {
Statement::HasField(Box::new(HasField {
name: name.to_string(),
type_: TypeExpr::Named(type_name.to_string()),
default: None,
constraint: None,
span: Span::default(),
}))
}
fn make_gene(name: &str, statements: Vec<Statement>) -> Gen {
Gen {
name: name.to_string(),
visibility: Visibility::Private,
extends: None,
statements,
exegesis: "Test gene".to_string(),
span: Span::default(),
}
}
#[test]
fn test_wasm_field_type_sizes() {
assert_eq!(WasmFieldType::I32.size(), 4);
assert_eq!(WasmFieldType::I64.size(), 8);
assert_eq!(WasmFieldType::F32.size(), 4);
assert_eq!(WasmFieldType::F64.size(), 8);
assert_eq!(WasmFieldType::Ptr.size(), 4);
}
#[test]
fn test_wasm_field_type_alignments() {
assert_eq!(WasmFieldType::I32.alignment(), 4);
assert_eq!(WasmFieldType::I64.alignment(), 8);
assert_eq!(WasmFieldType::F32.alignment(), 4);
assert_eq!(WasmFieldType::F64.alignment(), 8);
assert_eq!(WasmFieldType::Ptr.alignment(), 4);
}
#[test]
fn test_align_up() {
assert_eq!(align_up(0, 4), 0);
assert_eq!(align_up(1, 4), 4);
assert_eq!(align_up(4, 4), 4);
assert_eq!(align_up(5, 4), 8);
assert_eq!(align_up(0, 8), 0);
assert_eq!(align_up(4, 8), 8);
assert_eq!(align_up(8, 8), 8);
assert_eq!(align_up(9, 8), 16);
}
#[test]
fn test_simple_point_layout() {
let gene = make_gene(
"Point",
vec![make_field("x", "Float64"), make_field("y", "Float64")],
);
let registry = GeneLayoutRegistry::new();
let layout = compute_gene_layout(&gene, ®istry).unwrap();
assert_eq!(layout.name, "Point");
assert_eq!(layout.total_size, 16);
assert_eq!(layout.alignment, 8);
assert_eq!(layout.fields.len(), 2);
assert_eq!(layout.fields[0].name, "x");
assert_eq!(layout.fields[0].offset, 0);
assert_eq!(layout.fields[0].wasm_type, WasmFieldType::F64);
assert_eq!(layout.fields[1].name, "y");
assert_eq!(layout.fields[1].offset, 8);
assert_eq!(layout.fields[1].wasm_type, WasmFieldType::F64);
}
#[test]
fn test_mixed_types_with_padding() {
let gene = make_gene(
"Entity",
vec![
make_field("id", "Int32"),
make_field("position", "Float64"),
make_field("active", "Bool"),
make_field("score", "Int32"),
],
);
let registry = GeneLayoutRegistry::new();
let layout = compute_gene_layout(&gene, ®istry).unwrap();
assert_eq!(layout.name, "Entity");
assert_eq!(layout.alignment, 8);
assert_eq!(layout.fields[0].offset, 0);
assert_eq!(layout.fields[1].offset, 8);
assert_eq!(layout.fields[2].offset, 16);
assert_eq!(layout.fields[3].offset, 20);
assert_eq!(layout.total_size, 24);
}
#[test]
fn test_gene_layout_registry() {
let mut registry = GeneLayoutRegistry::new();
let point_layout = GeneLayout {
name: "Point".to_string(),
fields: vec![],
total_size: 16,
alignment: 8,
};
registry.register(point_layout);
assert!(registry.contains("Point"));
assert!(!registry.contains("Rectangle"));
assert_eq!(registry.len(), 1);
let retrieved = registry.get("Point").unwrap();
assert_eq!(retrieved.total_size, 16);
}
#[test]
fn test_nested_gene_layout() {
let point_gene = make_gene(
"Point",
vec![make_field("x", "Float64"), make_field("y", "Float64")],
);
let mut registry = GeneLayoutRegistry::new();
let point_layout = compute_gene_layout(&point_gene, ®istry).unwrap();
registry.register(point_layout);
let rect_gene = make_gene(
"Rectangle",
vec![
make_field("top_left", "Point"),
make_field("width", "Float64"),
make_field("height", "Float64"),
],
);
let rect_layout = compute_gene_layout(&rect_gene, ®istry).unwrap();
assert_eq!(rect_layout.name, "Rectangle");
assert_eq!(rect_layout.alignment, 8);
assert_eq!(rect_layout.total_size, 32);
assert_eq!(rect_layout.fields[0].offset, 0);
assert_eq!(rect_layout.fields[0].size, 16);
assert!(rect_layout.fields[0].nested_layout.is_some());
assert_eq!(rect_layout.fields[1].offset, 16);
assert_eq!(rect_layout.fields[2].offset, 24);
}
#[test]
fn test_reference_type_layout() {
let gene = Gen {
name: "Node".to_string(),
visibility: Visibility::Private,
extends: None,
statements: vec![
make_field("value", "Int64"),
Statement::HasField(Box::new(HasField {
name: "next".to_string(),
type_: TypeExpr::Named("&Node".to_string()),
default: None,
constraint: None,
span: Span::default(),
})),
],
exegesis: "Test".to_string(),
span: Span::default(),
};
let registry = GeneLayoutRegistry::new();
let layout = compute_gene_layout(&gene, ®istry).unwrap();
assert_eq!(layout.fields[0].name, "value");
assert_eq!(layout.fields[0].offset, 0);
assert_eq!(layout.fields[0].wasm_type, WasmFieldType::I64);
assert!(!layout.fields[0].is_reference);
assert_eq!(layout.fields[1].name, "next");
assert_eq!(layout.fields[1].offset, 8);
assert_eq!(layout.fields[1].wasm_type, WasmFieldType::Ptr);
assert!(layout.fields[1].is_reference);
assert_eq!(layout.total_size, 16);
}
#[test]
fn test_gc_descriptor() {
let layout = GeneLayout {
name: "Node".to_string(),
fields: vec![
FieldLayout {
name: "value".to_string(),
offset: 0,
size: 8,
alignment: 8,
wasm_type: WasmFieldType::I64,
is_reference: false,
nested_layout: None,
},
FieldLayout {
name: "next".to_string(),
offset: 8,
size: 4,
alignment: 4,
wasm_type: WasmFieldType::Ptr,
is_reference: true,
nested_layout: None,
},
FieldLayout {
name: "prev".to_string(),
offset: 12,
size: 4,
alignment: 4,
wasm_type: WasmFieldType::Ptr,
is_reference: true,
nested_layout: None,
},
],
total_size: 16,
alignment: 8,
};
let descriptor = layout.to_gc_descriptor();
assert_eq!(descriptor.size, 16);
assert!(descriptor.has_references);
assert_eq!(descriptor.pointer_offsets, vec![8, 12]);
}
#[test]
fn test_empty_gene() {
let gene = make_gene("Empty", vec![]);
let registry = GeneLayoutRegistry::new();
let layout = compute_gene_layout(&gene, ®istry).unwrap();
assert_eq!(layout.total_size, 0);
assert_eq!(layout.alignment, 1);
assert!(layout.is_empty());
}
#[test]
fn test_field_layout_constructors() {
let primitive = FieldLayout::primitive("x", 0, WasmFieldType::F64);
assert_eq!(primitive.size, 8);
assert_eq!(primitive.alignment, 8);
assert!(!primitive.is_reference);
let reference = FieldLayout::reference("next", 8);
assert_eq!(reference.size, 4);
assert_eq!(reference.wasm_type, WasmFieldType::Ptr);
assert!(reference.is_reference);
}
#[test]
fn test_gene_inheritance_layout() {
let animal_gene = make_gene("Animal", vec![make_field("age", "Int64")]);
let mut registry = GeneLayoutRegistry::new();
let animal_layout = compute_gene_layout(&animal_gene, ®istry).unwrap();
assert_eq!(animal_layout.name, "Animal");
assert_eq!(animal_layout.total_size, 8);
assert_eq!(animal_layout.fields.len(), 1);
assert_eq!(animal_layout.fields[0].name, "age");
assert_eq!(animal_layout.fields[0].offset, 0);
registry.register(animal_layout);
let dog_gene = Gen {
name: "Dog".to_string(),
visibility: Visibility::Private,
extends: Some("Animal".to_string()),
statements: vec![make_field("breed_id", "Int64")],
exegesis: "Test gene".to_string(),
span: Span::default(),
};
let dog_layout = compute_gene_layout(&dog_gene, ®istry).unwrap();
assert_eq!(dog_layout.name, "Dog");
assert_eq!(dog_layout.total_size, 16); assert_eq!(dog_layout.alignment, 8);
assert_eq!(dog_layout.fields.len(), 2);
assert_eq!(dog_layout.fields[0].name, "age");
assert_eq!(dog_layout.fields[0].offset, 0);
assert_eq!(dog_layout.fields[1].name, "breed_id");
assert_eq!(dog_layout.fields[1].offset, 8);
}
#[test]
fn test_gene_inheritance_unknown_parent() {
let dog_gene = Gen {
name: "Dog".to_string(),
visibility: Visibility::Private,
extends: Some("Animal".to_string()),
statements: vec![make_field("breed_id", "Int64")],
exegesis: "Test gene".to_string(),
span: Span::default(),
};
let registry = GeneLayoutRegistry::new();
let result = compute_gene_layout(&dog_gene, ®istry);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.message.contains("extends unknown parent"));
assert!(err.message.contains("Animal"));
}
}