Refactor: Implement Record Layouts and Optimized Field Access
Introduces a new `RecordLayout` system for efficient and type-safe record handling. Key changes: - `RecordLayout` struct with `O(1)` field lookup using FMap optimization. - Field accessors (`.name`) are now first-class callable values. - Parser recognizes dot-prefixed identifiers as field accessors. - Binder and TypeChecker handle field accessors. - Optimizer transforms field accessor calls into specialized `GetField` nodes. - VM executes `GetField` efficiently by directly accessing record values. - Adds a new `records.myc` example showcasing record features. - Improves comparison logic for records to use pointer equality for layout.
This commit is contained in:
@@ -83,6 +83,20 @@ impl<'a> Analyzer<'a> {
|
||||
)
|
||||
}
|
||||
|
||||
BoundKind::FieldAccessor(k) => (BoundKind::FieldAccessor(*k), Purity::Pure),
|
||||
|
||||
BoundKind::GetField { rec, field } => {
|
||||
let rec_m = self.visit(Rc::new((**rec).clone()));
|
||||
let p = rec_m.ty.purity;
|
||||
(
|
||||
BoundKind::GetField {
|
||||
rec: Box::new(rec_m),
|
||||
field: *field,
|
||||
},
|
||||
p,
|
||||
)
|
||||
}
|
||||
|
||||
BoundKind::Set { addr, value } => {
|
||||
let val_m = self.visit(Rc::new((**value).clone()));
|
||||
(
|
||||
@@ -313,6 +327,9 @@ impl NodeExt for BoundKind<crate::ast::types::StaticType> {
|
||||
BoundKind::Define { value, .. } | BoundKind::Set { value, .. } => {
|
||||
f(value);
|
||||
}
|
||||
BoundKind::GetField { rec, .. } => {
|
||||
f(rec);
|
||||
}
|
||||
BoundKind::Lambda { params, body, .. } => {
|
||||
f(params);
|
||||
f(body);
|
||||
|
||||
@@ -189,6 +189,10 @@ impl Binder {
|
||||
Err("Unexpected 'Parameter' node in general binder context. This should be handled via 'bind_pattern'.".to_string())
|
||||
}
|
||||
|
||||
UntypedKind::FieldAccessor(k) => {
|
||||
Ok(self.make_node(node.identity.clone(), BoundKind::FieldAccessor(*k)))
|
||||
}
|
||||
|
||||
UntypedKind::If {
|
||||
cond,
|
||||
then_br,
|
||||
|
||||
@@ -97,6 +97,15 @@ pub enum BoundKind<T = ()> {
|
||||
captured_by: Vec<Identity>,
|
||||
},
|
||||
|
||||
/// A first-class field accessor (e.g. .name)
|
||||
FieldAccessor(crate::ast::types::Keyword),
|
||||
|
||||
/// Specialized field access (O(1) via RecordLayout)
|
||||
GetField {
|
||||
rec: Box<BoundNode<T>>,
|
||||
field: crate::ast::types::Keyword,
|
||||
},
|
||||
|
||||
If {
|
||||
cond: Box<BoundNode<T>>,
|
||||
then_br: Box<BoundNode<T>>,
|
||||
@@ -188,6 +197,10 @@ where
|
||||
captured_by: cb,
|
||||
},
|
||||
) => na == nb && aa == ab && ka == kb && va == vb && ca == cb,
|
||||
(BoundKind::FieldAccessor(a), BoundKind::FieldAccessor(b)) => a == b,
|
||||
(BoundKind::GetField { rec: ra, field: fa }, BoundKind::GetField { rec: rb, field: fb }) => {
|
||||
ra == rb && fa == fb
|
||||
}
|
||||
(
|
||||
BoundKind::If {
|
||||
cond: ca,
|
||||
@@ -273,6 +286,8 @@ impl<T> BoundKind<T> {
|
||||
};
|
||||
format!("DEF_{}({}, {:?})", k_str, name.name, addr)
|
||||
}
|
||||
BoundKind::FieldAccessor(k) => format!("FIELD_ACCESSOR(.{})", k.name()),
|
||||
BoundKind::GetField { field, .. } => format!("GET_FIELD(.{})", field.name()),
|
||||
BoundKind::If { .. } => "IF".to_string(),
|
||||
BoundKind::Destructure { .. } => "DESTRUCTURE".to_string(),
|
||||
BoundKind::Lambda {
|
||||
|
||||
@@ -47,6 +47,15 @@ impl CapturePass {
|
||||
};
|
||||
}
|
||||
|
||||
BoundKind::FieldAccessor(_) => {}
|
||||
|
||||
BoundKind::GetField { rec, field } => {
|
||||
node.kind = BoundKind::GetField {
|
||||
rec: Box::new(Self::transform(*rec, capture_map)),
|
||||
field,
|
||||
};
|
||||
}
|
||||
|
||||
BoundKind::Destructure { pattern, value } => {
|
||||
node.kind = BoundKind::Destructure {
|
||||
pattern: Box::new(Self::transform(*pattern, capture_map)),
|
||||
|
||||
@@ -66,6 +66,17 @@ impl Dumper {
|
||||
self.log(&format!("Get: {} ({:?})", name.name, addr), node)
|
||||
}
|
||||
|
||||
BoundKind::FieldAccessor(k) => {
|
||||
self.log(&format!("FieldAccessor: .{}", k.name()), node)
|
||||
}
|
||||
|
||||
BoundKind::GetField { rec, field } => {
|
||||
self.log(&format!("GetField: .{}", field.name()), node);
|
||||
self.indent += 1;
|
||||
self.visit(rec);
|
||||
self.indent -= 1;
|
||||
}
|
||||
|
||||
BoundKind::Set { addr, value } => {
|
||||
self.log(&format!("Set: {:?}", addr), node);
|
||||
self.indent += 1;
|
||||
|
||||
@@ -112,6 +112,27 @@ impl Optimizer {
|
||||
)
|
||||
}
|
||||
|
||||
BoundKind::FieldAccessor(k) => (BoundKind::FieldAccessor(k), node.ty.clone()),
|
||||
|
||||
BoundKind::GetField { ref rec, field } => {
|
||||
let rec_opt = self.visit_node((**rec).clone(), sub, path);
|
||||
|
||||
// Constant folding for Field Access
|
||||
if let BoundKind::Constant(Value::Record(layout, values)) = &rec_opt.kind
|
||||
&& let Some(idx) = layout.index_of(field)
|
||||
{
|
||||
return folder.make_constant_node(values[idx].clone(), &node);
|
||||
}
|
||||
|
||||
(
|
||||
BoundKind::GetField {
|
||||
rec: Box::new(rec_opt),
|
||||
field,
|
||||
},
|
||||
node.ty.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
BoundKind::Set { addr, value } => {
|
||||
let value = Box::new(self.visit_node(*value, sub, path));
|
||||
if let BoundKind::Constant(val) = &value.kind {
|
||||
@@ -175,6 +196,23 @@ impl Optimizer {
|
||||
let args = self.visit_node(*args, sub, path);
|
||||
|
||||
if self.enabled {
|
||||
// Optimized Field Access Transformation
|
||||
if let BoundKind::FieldAccessor(k) = &callee.kind
|
||||
&& let BoundKind::Tuple { elements } = &args.kind
|
||||
&& elements.len() == 1
|
||||
{
|
||||
let rec = elements[0].clone();
|
||||
let metrics = node.ty.clone();
|
||||
return Node {
|
||||
identity: node.identity,
|
||||
kind: BoundKind::GetField {
|
||||
rec: Box::new(rec),
|
||||
field: *k,
|
||||
},
|
||||
ty: metrics,
|
||||
};
|
||||
}
|
||||
|
||||
let mut arg_nodes = Vec::new();
|
||||
self.flatten_tuple(args.clone(), &mut arg_nodes);
|
||||
|
||||
@@ -542,11 +580,6 @@ impl Optimizer {
|
||||
self.flatten_tuple(el, into);
|
||||
}
|
||||
}
|
||||
BoundKind::Record { fields } => {
|
||||
for (_, v) in fields {
|
||||
self.flatten_tuple(v, into);
|
||||
}
|
||||
}
|
||||
BoundKind::Nop => {}
|
||||
_ => into.push(node),
|
||||
}
|
||||
|
||||
@@ -68,6 +68,9 @@ impl UsageInfo {
|
||||
self.assigned.insert(*addr);
|
||||
self.collect(value);
|
||||
}
|
||||
BoundKind::GetField { rec, .. } => {
|
||||
self.collect(rec);
|
||||
}
|
||||
BoundKind::Lambda {
|
||||
params,
|
||||
body,
|
||||
@@ -147,7 +150,10 @@ impl UsageInfo {
|
||||
BoundKind::Expansion { bound_expanded, .. } => {
|
||||
self.collect(bound_expanded);
|
||||
}
|
||||
_ => {}
|
||||
BoundKind::Again { args } => {
|
||||
self.collect(args);
|
||||
}
|
||||
BoundKind::Nop | BoundKind::FieldAccessor(_) | BoundKind::Extension(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -141,6 +141,11 @@ impl TCO {
|
||||
addr: *addr,
|
||||
name: name.clone(),
|
||||
},
|
||||
BoundKind::FieldAccessor(k) => BoundKind::FieldAccessor(*k),
|
||||
BoundKind::GetField { rec, field } => BoundKind::GetField {
|
||||
rec: Box::new(Self::transform(Rc::new((**rec).clone()), false)),
|
||||
field: *field,
|
||||
},
|
||||
BoundKind::Nop => BoundKind::Nop,
|
||||
BoundKind::Expansion {
|
||||
original_call,
|
||||
|
||||
@@ -226,9 +226,10 @@ impl TypeChecker {
|
||||
StaticType::Vector(inner, _) => (**inner).clone(),
|
||||
StaticType::Matrix(inner, _) => (**inner).clone(), // Matrix flat iteration or row? Wait. Matrix destructuring logic is tricky. But for now let's focus on Record.
|
||||
StaticType::List(inner) => (**inner).clone(),
|
||||
StaticType::Record(fields) => fields
|
||||
StaticType::Record(layout) => layout
|
||||
.fields
|
||||
.get(i)
|
||||
.map(|(_, ty)| ty.clone())
|
||||
.map(|(_, ty): &(crate::ast::types::Keyword, StaticType)| ty.clone())
|
||||
.unwrap_or(StaticType::Any),
|
||||
_ => StaticType::Any,
|
||||
};
|
||||
@@ -296,6 +297,36 @@ impl TypeChecker {
|
||||
)
|
||||
}
|
||||
|
||||
BoundKind::FieldAccessor(k) => (BoundKind::FieldAccessor(k), StaticType::FieldAccessor(k)),
|
||||
|
||||
BoundKind::GetField { rec, field } => {
|
||||
let rec_typed = self.check_node(*rec, ctx)?;
|
||||
let field_ty = match &rec_typed.ty {
|
||||
StaticType::Record(layout) => {
|
||||
if let Some(idx) = layout.index_of(field) {
|
||||
layout.fields[idx].1.clone()
|
||||
} else {
|
||||
return Err(format!("Record does not have field :{}", field.name()));
|
||||
}
|
||||
}
|
||||
StaticType::Any => StaticType::Any,
|
||||
_ => {
|
||||
return Err(format!(
|
||||
"Cannot access field :{} on non-record type {}",
|
||||
field.name(),
|
||||
rec_typed.ty
|
||||
));
|
||||
}
|
||||
};
|
||||
(
|
||||
BoundKind::GetField {
|
||||
rec: Box::new(rec_typed),
|
||||
field,
|
||||
},
|
||||
field_ty,
|
||||
)
|
||||
}
|
||||
|
||||
BoundKind::Set { addr, value } => {
|
||||
let val_typed = self.check_node(*value, ctx)?;
|
||||
let ty = val_typed.ty.clone();
|
||||
@@ -545,11 +576,12 @@ impl TypeChecker {
|
||||
|
||||
typed_fields.push((kt, vt));
|
||||
}
|
||||
let layout = crate::ast::types::RecordLayout::get_or_create(fields_ty);
|
||||
(
|
||||
BoundKind::Record {
|
||||
fields: typed_fields,
|
||||
},
|
||||
StaticType::Record(Rc::new(fields_ty)),
|
||||
StaticType::Record(layout),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -801,12 +833,12 @@ mod tests {
|
||||
#[test]
|
||||
fn test_inference_record() {
|
||||
use crate::ast::types::Keyword;
|
||||
let typed = check_source("{:x 1, :y 0.3}");
|
||||
let typed = check_source("{:x 1 :y 0.3}");
|
||||
let ty = get_ret_type(&typed);
|
||||
if let StaticType::Record(fields) = ty {
|
||||
assert_eq!(fields.len(), 2);
|
||||
assert_eq!(fields[0], (Keyword::intern("x"), StaticType::Int));
|
||||
assert_eq!(fields[1], (Keyword::intern("y"), StaticType::Float));
|
||||
if let StaticType::Record(layout) = ty {
|
||||
assert_eq!(layout.fields.len(), 2);
|
||||
assert_eq!(layout.fields[0], (Keyword::intern("x"), StaticType::Int));
|
||||
assert_eq!(layout.fields[1], (Keyword::intern("y"), StaticType::Float));
|
||||
} else {
|
||||
panic!("Expected Record, got {:?}", ty);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user