Refactor Record's internal representation
This commit refactors the internal representation of `BoundKind::Record` to store a `RecordLayout` and a `Vec` of values, rather than a `Vec` of key-value pairs. This change simplifies the representation and improves efficiency by decoupling the record's structure from its specific values during compilation and analysis. The `RecordLayout` now defines the structure of the record, and the values are stored in a separate vector, ordered according to the layout. This allows for better optimization and type checking, as the record's shape is explicitly defined and immutable once created.
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
;; Benchmark: 1.9us
|
;; Benchmark: 1.3us
|
||||||
;; Benchmark-Repeat: 1211
|
;; Benchmark-Repeat: 1599
|
||||||
;; Output: ["Alice" 101 :admin "Zürich" ["Alice" "Bob"] true]
|
;; Output: ["Alice" 101 :admin "Zürich" ["Alice" "Bob"] true]
|
||||||
|
|
||||||
; ---------------------------------------------------------
|
; ---------------------------------------------------------
|
||||||
|
|||||||
@@ -265,16 +265,15 @@ impl<'a> Analyzer<'a> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
BoundKind::Record { fields } => {
|
BoundKind::Record { layout, values } => {
|
||||||
let mut new_fields = Vec::with_capacity(fields.len());
|
let mut new_values = Vec::with_capacity(values.len());
|
||||||
let mut p = Purity::Pure;
|
let mut p = Purity::Pure;
|
||||||
for (k, v) in fields {
|
for v in values {
|
||||||
let km = self.visit(Rc::new(k.clone()));
|
|
||||||
let vm = self.visit(Rc::new(v.clone()));
|
let vm = self.visit(Rc::new(v.clone()));
|
||||||
p = p.min(km.ty.purity).min(vm.ty.purity);
|
p = p.min(vm.ty.purity);
|
||||||
new_fields.push((km, vm));
|
new_values.push(vm);
|
||||||
}
|
}
|
||||||
(BoundKind::Record { fields: new_fields }, p)
|
(BoundKind::Record { layout: layout.clone(), values: new_values }, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
BoundKind::Expansion {
|
BoundKind::Expansion {
|
||||||
@@ -348,9 +347,8 @@ impl NodeExt for BoundKind<crate::ast::types::StaticType> {
|
|||||||
f(e);
|
f(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BoundKind::Record { fields } => {
|
BoundKind::Record { values, .. } => {
|
||||||
for (k, v) in fields {
|
for v in values {
|
||||||
f(k);
|
|
||||||
f(v);
|
f(v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -374,14 +374,28 @@ impl Binder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
UntypedKind::Record { fields } => {
|
UntypedKind::Record { fields } => {
|
||||||
let mut bound_fields = Vec::new();
|
let mut bound_values = Vec::new();
|
||||||
|
let mut layout_fields = Vec::new();
|
||||||
|
|
||||||
for (k, v) in fields {
|
for (k, v) in fields {
|
||||||
bound_fields.push((self.bind(k)?, self.bind(v)?));
|
let key_node = self.bind(k)?;
|
||||||
|
let val_node = self.bind(v)?;
|
||||||
|
|
||||||
|
if let BoundKind::Constant(crate::ast::types::Value::Keyword(kw)) = key_node.kind {
|
||||||
|
layout_fields.push((kw, crate::ast::types::StaticType::Any));
|
||||||
|
} else {
|
||||||
|
return Err(format!("Record keys must be keywords, found at {:?}", key_node.identity.location));
|
||||||
|
}
|
||||||
|
bound_values.push(val_node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let layout = crate::ast::types::RecordLayout::get_or_create(layout_fields);
|
||||||
|
|
||||||
Ok(self.make_node(
|
Ok(self.make_node(
|
||||||
node.identity.clone(),
|
node.identity.clone(),
|
||||||
BoundKind::Record {
|
BoundKind::Record {
|
||||||
fields: bound_fields,
|
layout,
|
||||||
|
values: bound_values,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,7 +145,8 @@ pub enum BoundKind<T = ()> {
|
|||||||
},
|
},
|
||||||
|
|
||||||
Record {
|
Record {
|
||||||
fields: Vec<RecordField<T>>,
|
layout: std::sync::Arc<crate::ast::types::RecordLayout>,
|
||||||
|
values: Vec<BoundNode<T>>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// An expanded macro call, preserving the original call for debugging and UI.
|
/// An expanded macro call, preserving the original call for debugging and UI.
|
||||||
@@ -250,7 +251,9 @@ where
|
|||||||
(BoundKind::Again { args: aa }, BoundKind::Again { args: ab }) => aa == ab,
|
(BoundKind::Again { args: aa }, BoundKind::Again { args: ab }) => aa == ab,
|
||||||
(BoundKind::Block { exprs: ea }, BoundKind::Block { exprs: eb }) => ea == eb,
|
(BoundKind::Block { exprs: ea }, BoundKind::Block { exprs: eb }) => ea == eb,
|
||||||
(BoundKind::Tuple { elements: ea }, BoundKind::Tuple { elements: eb }) => ea == eb,
|
(BoundKind::Tuple { elements: ea }, BoundKind::Tuple { elements: eb }) => ea == eb,
|
||||||
(BoundKind::Record { fields: fa }, BoundKind::Record { fields: fb }) => fa == fb,
|
(BoundKind::Record { layout: la, values: va }, BoundKind::Record { layout: lb, values: vb }) => {
|
||||||
|
std::sync::Arc::ptr_eq(la, lb) && va == vb
|
||||||
|
}
|
||||||
(
|
(
|
||||||
BoundKind::Expansion {
|
BoundKind::Expansion {
|
||||||
original_call: oa,
|
original_call: oa,
|
||||||
@@ -303,7 +306,7 @@ impl<T> BoundKind<T> {
|
|||||||
BoundKind::Again { .. } => "AGAIN".to_string(),
|
BoundKind::Again { .. } => "AGAIN".to_string(),
|
||||||
BoundKind::Block { .. } => "BLOCK".to_string(),
|
BoundKind::Block { .. } => "BLOCK".to_string(),
|
||||||
BoundKind::Tuple { elements } => format!("TUPLE({})", elements.len()),
|
BoundKind::Tuple { elements } => format!("TUPLE({})", elements.len()),
|
||||||
BoundKind::Record { fields } => format!("RECORD({})", fields.len()),
|
BoundKind::Record { values, .. } => format!("RECORD({})", values.len()),
|
||||||
BoundKind::Expansion { .. } => "EXPANSION".to_string(),
|
BoundKind::Expansion { .. } => "EXPANSION".to_string(),
|
||||||
BoundKind::Extension(ext) => ext.display_name(),
|
BoundKind::Extension(ext) => ext.display_name(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,16 +108,12 @@ impl CapturePass {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
BoundKind::Record { fields } => {
|
BoundKind::Record { layout, values } => {
|
||||||
node.kind = BoundKind::Record {
|
node.kind = BoundKind::Record {
|
||||||
fields: fields
|
layout,
|
||||||
|
values: values
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(k, v)| {
|
.map(|v| Self::transform(v, capture_map))
|
||||||
(
|
|
||||||
Self::transform(k, capture_map),
|
|
||||||
Self::transform(v, capture_map),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect(),
|
.collect(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -222,11 +222,10 @@ impl Dumper {
|
|||||||
self.indent -= 1;
|
self.indent -= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
BoundKind::Record { fields } => {
|
BoundKind::Record { layout, values } => {
|
||||||
self.log("Record", node);
|
self.log(&format!("Record (Layout: {} fields)", layout.fields.len()), node);
|
||||||
self.indent += 1;
|
self.indent += 1;
|
||||||
for (k, v) in fields {
|
for v in values {
|
||||||
self.visit(k);
|
|
||||||
self.visit(v);
|
self.visit(v);
|
||||||
}
|
}
|
||||||
self.indent -= 1;
|
self.indent -= 1;
|
||||||
|
|||||||
@@ -82,9 +82,8 @@ impl<'a, T: Clone> LambdaCollector<'a, T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BoundKind::Record { fields } => {
|
BoundKind::Record { values, .. } => {
|
||||||
for (k, v) in fields {
|
for v in values {
|
||||||
self.visit(k);
|
|
||||||
self.visit(v);
|
self.visit(v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -543,17 +543,17 @@ impl Optimizer {
|
|||||||
.collect();
|
.collect();
|
||||||
(BoundKind::Tuple { elements }, node.ty.clone())
|
(BoundKind::Tuple { elements }, node.ty.clone())
|
||||||
}
|
}
|
||||||
BoundKind::Record { ref fields } => {
|
BoundKind::Record { ref layout, ref values } => {
|
||||||
let mapped_fields: Vec<_> = fields
|
let mapped_values: Vec<_> = values
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(k, v)| (self.visit_node(k.clone(), sub, path), self.visit_node(v.clone(), sub, path)))
|
.map(|v| self.visit_node(v.clone(), sub, path))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if self.enabled && let Some(folded) = folder.try_fold_record(&mapped_fields, &node) {
|
if self.enabled && let Some(folded) = folder.try_fold_record(layout, &mapped_values, &node) {
|
||||||
return folded;
|
return folded;
|
||||||
}
|
}
|
||||||
|
|
||||||
(BoundKind::Record { fields: mapped_fields }, node.ty.clone())
|
(BoundKind::Record { layout: layout.clone(), values: mapped_values }, node.ty.clone())
|
||||||
}
|
}
|
||||||
BoundKind::Expansion {
|
BoundKind::Expansion {
|
||||||
ref original_call,
|
ref original_call,
|
||||||
|
|||||||
@@ -50,27 +50,21 @@ impl<'a> Folder<'a> {
|
|||||||
|
|
||||||
pub fn try_fold_record(
|
pub fn try_fold_record(
|
||||||
&self,
|
&self,
|
||||||
fields: &[(AnalyzedNode, AnalyzedNode)],
|
layout: &std::sync::Arc<RecordLayout>,
|
||||||
|
values: &[AnalyzedNode],
|
||||||
template: &AnalyzedNode,
|
template: &AnalyzedNode,
|
||||||
) -> Option<AnalyzedNode> {
|
) -> Option<AnalyzedNode> {
|
||||||
let mut layout_fields = Vec::with_capacity(fields.len());
|
let mut constant_values = Vec::with_capacity(values.len());
|
||||||
let mut values = Vec::with_capacity(fields.len());
|
|
||||||
|
|
||||||
for (k_node, v_node) in fields {
|
for v_node in values {
|
||||||
if let BoundKind::Constant(Value::Keyword(kw)) = &k_node.kind {
|
if let BoundKind::Constant(val) = &v_node.kind {
|
||||||
if let BoundKind::Constant(val) = &v_node.kind {
|
constant_values.push(val.clone());
|
||||||
layout_fields.push((*kw, val.static_type()));
|
|
||||||
values.push(val.clone());
|
|
||||||
} else {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let layout = RecordLayout::get_or_create(layout_fields);
|
let record_val = Value::Record(layout.clone(), Rc::new(constant_values));
|
||||||
let record_val = Value::Record(layout, Rc::new(values));
|
|
||||||
Some(self.make_constant_node(record_val, template))
|
Some(self.make_constant_node(record_val, template))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -169,17 +169,12 @@ impl SubstitutionMap {
|
|||||||
.collect();
|
.collect();
|
||||||
(BoundKind::Tuple { elements }, node.ty.clone())
|
(BoundKind::Tuple { elements }, node.ty.clone())
|
||||||
}
|
}
|
||||||
BoundKind::Record { fields } => {
|
BoundKind::Record { layout, values } => {
|
||||||
let fields = fields
|
let values = values
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(k, v)| {
|
.map(|v| self.reindex_upvalues(v, mapping))
|
||||||
(
|
|
||||||
self.reindex_upvalues(k, mapping),
|
|
||||||
self.reindex_upvalues(v, mapping),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect();
|
.collect();
|
||||||
(BoundKind::Record { fields }, node.ty.clone())
|
(BoundKind::Record { layout, values }, node.ty.clone())
|
||||||
}
|
}
|
||||||
k => (k, node.ty.clone()),
|
k => (k, node.ty.clone()),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -138,9 +138,8 @@ impl UsageInfo {
|
|||||||
self.collect(e);
|
self.collect(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BoundKind::Record { fields } => {
|
BoundKind::Record { values, .. } => {
|
||||||
for (k, v) in fields {
|
for v in values {
|
||||||
self.collect(k);
|
|
||||||
self.collect(v);
|
self.collect(v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,12 +131,12 @@ impl Specializer {
|
|||||||
let elements = elements.into_iter().map(|e| self.visit_node(e)).collect();
|
let elements = elements.into_iter().map(|e| self.visit_node(e)).collect();
|
||||||
(BoundKind::Tuple { elements }, node.ty.clone())
|
(BoundKind::Tuple { elements }, node.ty.clone())
|
||||||
}
|
}
|
||||||
BoundKind::Record { fields } => {
|
BoundKind::Record { layout, values } => {
|
||||||
let fields = fields
|
let values = values
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(k, v)| (self.visit_node(k), self.visit_node(v)))
|
.map(|v| self.visit_node(v))
|
||||||
.collect();
|
.collect();
|
||||||
(BoundKind::Record { fields }, node.ty.clone())
|
(BoundKind::Record { layout, values }, node.ty.clone())
|
||||||
}
|
}
|
||||||
BoundKind::Expansion {
|
BoundKind::Expansion {
|
||||||
original_call,
|
original_call,
|
||||||
|
|||||||
@@ -115,17 +115,15 @@ impl TCO {
|
|||||||
pattern: Box::new(Self::transform(Rc::new((**pattern).clone()), false)),
|
pattern: Box::new(Self::transform(Rc::new((**pattern).clone()), false)),
|
||||||
value: Box::new(Self::transform(Rc::new((**value).clone()), false)),
|
value: Box::new(Self::transform(Rc::new((**value).clone()), false)),
|
||||||
},
|
},
|
||||||
BoundKind::Record { fields } => {
|
BoundKind::Record { layout, values } => {
|
||||||
let new_fields = fields
|
let new_values = values
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(k, v)| {
|
.map(|v| Self::transform(Rc::new(v.clone()), false))
|
||||||
(
|
|
||||||
Self::transform(Rc::new(k.clone()), false),
|
|
||||||
Self::transform(Rc::new(v.clone()), false),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect();
|
.collect();
|
||||||
BoundKind::Record { fields: new_fields }
|
BoundKind::Record {
|
||||||
|
layout: layout.clone(),
|
||||||
|
values: new_values,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
BoundKind::Tuple { elements } => {
|
BoundKind::Tuple { elements } => {
|
||||||
let new_elements = elements
|
let new_elements = elements
|
||||||
|
|||||||
@@ -563,25 +563,23 @@ impl TypeChecker {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
BoundKind::Record { fields } => {
|
BoundKind::Record { layout, values } => {
|
||||||
let mut typed_fields = Vec::with_capacity(fields.len());
|
let mut typed_values = Vec::with_capacity(values.len());
|
||||||
let mut fields_ty = Vec::with_capacity(fields.len());
|
let mut fields_ty = Vec::with_capacity(values.len());
|
||||||
for (k, v) in fields {
|
|
||||||
let kt = self.check_node(k, ctx)?;
|
for (i, v) in values.into_iter().enumerate() {
|
||||||
let vt = self.check_node(v, ctx)?;
|
let vt = self.check_node(v, ctx)?;
|
||||||
|
fields_ty.push((layout.fields[i].0, vt.ty.clone()));
|
||||||
if let BoundKind::Constant(crate::ast::types::Value::Keyword(kw)) = &kt.kind {
|
typed_values.push(vt);
|
||||||
fields_ty.push((*kw, vt.ty.clone()));
|
|
||||||
}
|
|
||||||
|
|
||||||
typed_fields.push((kt, vt));
|
|
||||||
}
|
}
|
||||||
let layout = crate::ast::types::RecordLayout::get_or_create(fields_ty);
|
|
||||||
|
let new_layout = crate::ast::types::RecordLayout::get_or_create(fields_ty);
|
||||||
(
|
(
|
||||||
BoundKind::Record {
|
BoundKind::Record {
|
||||||
fields: typed_fields,
|
layout: new_layout.clone(),
|
||||||
|
values: typed_values,
|
||||||
},
|
},
|
||||||
StaticType::Record(layout),
|
StaticType::Record(new_layout),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+5
-13
@@ -526,20 +526,12 @@ impl VM {
|
|||||||
}
|
}
|
||||||
Ok(Value::make_tuple(vals))
|
Ok(Value::make_tuple(vals))
|
||||||
}
|
}
|
||||||
BoundKind::Record { fields } => {
|
BoundKind::Record { layout, values } => {
|
||||||
let mut keys = Vec::with_capacity(fields.len());
|
let mut evaluated_values = Vec::with_capacity(values.len());
|
||||||
let mut values = Vec::with_capacity(fields.len());
|
for v in values {
|
||||||
for (k, v) in fields {
|
evaluated_values.push(self.eval_internal(obs, v)?);
|
||||||
let key = self.eval_internal(obs, k)?;
|
|
||||||
let val = self.eval_internal(obs, v)?;
|
|
||||||
if let Value::Keyword(kw) = key {
|
|
||||||
keys.push(kw);
|
|
||||||
values.push(val);
|
|
||||||
} else {
|
|
||||||
return Err(format!("Record key must be keyword, got {}", key));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(Value::make_record(keys, values))
|
Ok(Value::Record(layout.clone(), std::rc::Rc::new(evaluated_values)))
|
||||||
}
|
}
|
||||||
BoundKind::Expansion { bound_expanded, .. } => self.eval_internal(obs, bound_expanded),
|
BoundKind::Expansion { bound_expanded, .. } => self.eval_internal(obs, bound_expanded),
|
||||||
BoundKind::Extension(ext) => Err(format!(
|
BoundKind::Extension(ext) => Err(format!(
|
||||||
|
|||||||
Reference in New Issue
Block a user