Refactor Record and List to use TupleData
The `Value::List` and `Value::Record` variants have been consolidated into a single `Value::Tuple` variant. This new variant uses a `TupleData` struct to store values and an optional `Rc<Vec<Keyword>>` for keys, allowing it to represent both ordered lists/tuples and key-value records. This change simplifies the internal representation and improves performance by allowing schema sharing for records. It also includes updates to the compiler, runtime, and tests to reflect the new structure.
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
;; Record to Tuple Mapping Test
|
;; Record to Tuple Mapping Test
|
||||||
;; Output: 30
|
;; Output: 30
|
||||||
|
;; Benchmark: 700ns
|
||||||
(do
|
(do
|
||||||
(def rec {:x 10 :y 20})
|
(def rec {:x 10 :y 20})
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
;; Benchmark: Record Destructuring Performance
|
||||||
|
;; This test calls a function that destructures a record 100.000 times.
|
||||||
|
;; Current implementation is now zero-allocation for destructuring.
|
||||||
|
;; Output: {:a 1, :b 2}
|
||||||
|
;; Benchmark: 5.3ms
|
||||||
|
(do
|
||||||
|
(def process (fn [[x y]]
|
||||||
|
(+ x y)))
|
||||||
|
|
||||||
|
(def rec {:a 1 :b 2})
|
||||||
|
|
||||||
|
(def loop (fn [n acc]
|
||||||
|
(if (= n 0)
|
||||||
|
acc
|
||||||
|
(loop (- n 1) (process rec)))))
|
||||||
|
|
||||||
|
(loop 10000 0)
|
||||||
|
rec
|
||||||
|
)
|
||||||
@@ -112,7 +112,13 @@ impl RecordBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn build(self) -> Value {
|
pub fn build(self) -> Value {
|
||||||
Value::Record(Rc::new(self.fields))
|
let mut keys = Vec::with_capacity(self.fields.len());
|
||||||
|
let mut values = Vec::with_capacity(self.fields.len());
|
||||||
|
for (k, v) in self.fields {
|
||||||
|
keys.push(k);
|
||||||
|
values.push(v);
|
||||||
|
}
|
||||||
|
Value::make_record(keys, values)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,20 +201,26 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check runtime behavior
|
// Check runtime behavior
|
||||||
if let Value::Record(fields) = wrapped {
|
if let Value::Tuple(t) = wrapped {
|
||||||
let greet_fn = &fields.iter().find(|(k, _)| *k == Keyword::intern("greet")).unwrap().1;
|
if let Some(keys) = &t.keys {
|
||||||
if let Value::Function(f) = greet_fn {
|
let idx = keys.iter().position(|k| *k == Keyword::intern("greet")).unwrap();
|
||||||
let res = f(vec![]);
|
let greet_fn = &t.values[idx];
|
||||||
if let Value::Text(s) = res {
|
|
||||||
assert_eq!(&*s, "Hello Alice");
|
if let Value::Function(f) = greet_fn {
|
||||||
|
let res = f(vec![]);
|
||||||
|
if let Value::Text(s) = res {
|
||||||
|
assert_eq!(&*s, "Hello Alice");
|
||||||
|
} else {
|
||||||
|
panic!("Expected Text result");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
panic!("Expected Text result");
|
panic!("Expected Function value for method");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
panic!("Expected Function value for method");
|
panic!("Expected Record keys");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
panic!("Expected Record value");
|
panic!("Expected Tuple value");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,15 +237,19 @@ mod tests {
|
|||||||
|
|
||||||
if let Value::Function(f) = factory_val {
|
if let Value::Function(f) = factory_val {
|
||||||
let instance = f(vec![Value::Text("Bob".into()), Value::Int(40)]);
|
let instance = f(vec![Value::Text("Bob".into()), Value::Int(40)]);
|
||||||
if let Value::Record(fields) = instance {
|
if let Value::Tuple(t) = instance {
|
||||||
let greet_fn = &fields.iter().find(|(k, _)| *k == Keyword::intern("greet")).unwrap().1;
|
if let Some(keys) = &t.keys {
|
||||||
if let Value::Function(gf) = greet_fn {
|
let idx = keys.iter().position(|k| *k == Keyword::intern("greet")).unwrap();
|
||||||
let res = gf(vec![]);
|
let greet_fn = &t.values[idx];
|
||||||
if let Value::Text(s) = res {
|
|
||||||
assert_eq!(&*s, "Hello Bob");
|
if let Value::Function(gf) = greet_fn {
|
||||||
} else { panic!("Wrong return type"); }
|
let res = gf(vec![]);
|
||||||
}
|
if let Value::Text(s) = res {
|
||||||
} else { panic!("Factory should return Record"); }
|
assert_eq!(&*s, "Hello Bob");
|
||||||
|
} else { panic!("Wrong return type"); }
|
||||||
|
}
|
||||||
|
} else { panic!("Expected keys"); }
|
||||||
|
} else { panic!("Factory should return Tuple/Record"); }
|
||||||
} else { panic!("Factory is not a function"); }
|
} else { panic!("Factory is not a function"); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+67
-47
@@ -55,6 +55,14 @@ pub trait Object: fmt::Debug {
|
|||||||
fn as_any(&self) -> &dyn Any;
|
fn as_any(&self) -> &dyn Any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Internal storage for Tuples (and Records) to allow sharing schema (keys) between instances.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct TupleData {
|
||||||
|
pub values: Vec<Value>,
|
||||||
|
/// Optional names for slots. If present, this is semantically a Record.
|
||||||
|
pub keys: Option<Rc<Vec<Keyword>>>,
|
||||||
|
}
|
||||||
|
|
||||||
/// Core data value in Myc Script (similar to TDataValue)
|
/// Core data value in Myc Script (similar to TDataValue)
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum Value {
|
pub enum Value {
|
||||||
@@ -65,8 +73,7 @@ pub enum Value {
|
|||||||
DateTime(i64),
|
DateTime(i64),
|
||||||
Text(Rc<str>),
|
Text(Rc<str>),
|
||||||
Keyword(Keyword),
|
Keyword(Keyword),
|
||||||
List(Rc<Vec<Value>>),
|
Tuple(Rc<TupleData>), // Replaces List and Record
|
||||||
Record(Rc<Vec<(Keyword, Value)>>),
|
|
||||||
Function(Rc<dyn Fn(Vec<Value>) -> Value>),
|
Function(Rc<dyn Fn(Vec<Value>) -> Value>),
|
||||||
Object(Rc<dyn Object>), // For compiled Closures and other opaque types
|
Object(Rc<dyn Object>), // For compiled Closures and other opaque types
|
||||||
Cell(Rc<RefCell<Value>>), // Boxed value for captures
|
Cell(Rc<RefCell<Value>>), // Boxed value for captures
|
||||||
@@ -217,6 +224,14 @@ impl Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn make_list(values: Vec<Value>) -> Self {
|
||||||
|
Value::Tuple(Rc::new(TupleData { values, keys: None }))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn make_record(keys: Vec<Keyword>, values: Vec<Value>) -> Self {
|
||||||
|
Value::Tuple(Rc::new(TupleData { values, keys: Some(Rc::new(keys)) }))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn static_type(&self) -> StaticType {
|
pub fn static_type(&self) -> StaticType {
|
||||||
match self {
|
match self {
|
||||||
Value::Void => StaticType::Void,
|
Value::Void => StaticType::Void,
|
||||||
@@ -226,41 +241,45 @@ impl Value {
|
|||||||
Value::DateTime(_) => StaticType::DateTime,
|
Value::DateTime(_) => StaticType::DateTime,
|
||||||
Value::Text(_) => StaticType::Text,
|
Value::Text(_) => StaticType::Text,
|
||||||
Value::Keyword(_) => StaticType::Keyword,
|
Value::Keyword(_) => StaticType::Keyword,
|
||||||
Value::List(l) => {
|
Value::Tuple(t) => {
|
||||||
if l.is_empty() {
|
if let Some(keys) = &t.keys {
|
||||||
return StaticType::Vector(Box::new(StaticType::Any), 0);
|
// It's a Record
|
||||||
}
|
let mut fields = Vec::with_capacity(t.values.len());
|
||||||
|
for (i, v) in t.values.iter().enumerate() {
|
||||||
let element_types: Vec<_> = l.iter().map(|v| v.static_type()).collect();
|
fields.push((keys[i], v.static_type()));
|
||||||
|
|
||||||
// Check for Homogeneity (Vector)
|
|
||||||
let first_ty = &element_types[0];
|
|
||||||
let all_same = element_types.iter().all(|t| t == first_ty);
|
|
||||||
|
|
||||||
if all_same {
|
|
||||||
match first_ty {
|
|
||||||
StaticType::Vector(inner, len) => {
|
|
||||||
// Possible Matrix
|
|
||||||
StaticType::Matrix(inner.clone(), vec![l.len(), *len])
|
|
||||||
},
|
|
||||||
StaticType::Matrix(inner, shape) => {
|
|
||||||
let mut new_shape = vec![l.len()];
|
|
||||||
new_shape.extend(shape);
|
|
||||||
StaticType::Matrix(inner.clone(), new_shape)
|
|
||||||
},
|
|
||||||
_ => StaticType::Vector(Box::new(first_ty.clone()), l.len())
|
|
||||||
}
|
}
|
||||||
|
StaticType::Record(Rc::new(fields))
|
||||||
} else {
|
} else {
|
||||||
StaticType::Tuple(element_types)
|
// It's a List/Tuple
|
||||||
|
let l = &t.values;
|
||||||
|
if l.is_empty() {
|
||||||
|
return StaticType::Vector(Box::new(StaticType::Any), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let element_types: Vec<_> = l.iter().map(|v| v.static_type()).collect();
|
||||||
|
|
||||||
|
// Check for Homogeneity (Vector)
|
||||||
|
let first_ty = &element_types[0];
|
||||||
|
let all_same = element_types.iter().all(|t| t == first_ty);
|
||||||
|
|
||||||
|
if all_same {
|
||||||
|
match first_ty {
|
||||||
|
StaticType::Vector(inner, len) => {
|
||||||
|
// Possible Matrix
|
||||||
|
StaticType::Matrix(inner.clone(), vec![l.len(), *len])
|
||||||
|
},
|
||||||
|
StaticType::Matrix(inner, shape) => {
|
||||||
|
let mut new_shape = vec![l.len()];
|
||||||
|
new_shape.extend(shape);
|
||||||
|
StaticType::Matrix(inner.clone(), new_shape)
|
||||||
|
},
|
||||||
|
_ => StaticType::Vector(Box::new(first_ty.clone()), l.len())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
StaticType::Tuple(element_types)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Value::Record(r) => {
|
|
||||||
let mut fields = Vec::with_capacity(r.len());
|
|
||||||
for (k, v) in r.iter() {
|
|
||||||
fields.push((*k, v.static_type()));
|
|
||||||
}
|
|
||||||
StaticType::Record(Rc::new(fields))
|
|
||||||
},
|
|
||||||
Value::Function(_) => StaticType::Any, // Dynamic function
|
Value::Function(_) => StaticType::Any, // Dynamic function
|
||||||
Value::Object(o) => StaticType::Object(o.type_name()),
|
Value::Object(o) => StaticType::Object(o.type_name()),
|
||||||
Value::Cell(c) => c.borrow().static_type(),
|
Value::Cell(c) => c.borrow().static_type(),
|
||||||
@@ -284,21 +303,22 @@ impl fmt::Display for Value {
|
|||||||
},
|
},
|
||||||
Value::Text(t) => write!(f, "\"{}\"", t),
|
Value::Text(t) => write!(f, "\"{}\"", t),
|
||||||
Value::Keyword(k) => write!(f, ":{}", k.name()),
|
Value::Keyword(k) => write!(f, ":{}", k.name()),
|
||||||
Value::List(l) => {
|
Value::Tuple(t) => {
|
||||||
write!(f, "[")?;
|
if let Some(keys) = &t.keys {
|
||||||
for (i, val) in l.iter().enumerate() {
|
write!(f, "{{")?;
|
||||||
if i > 0 { write!(f, " ")?; }
|
for i in 0..t.values.len() {
|
||||||
write!(f, "{}", val)?;
|
if i > 0 { write!(f, ", ")?; }
|
||||||
|
write!(f, ":{} {}", keys[i].name(), t.values[i])?;
|
||||||
|
}
|
||||||
|
write!(f, "}}")
|
||||||
|
} else {
|
||||||
|
write!(f, "[")?;
|
||||||
|
for (i, val) in t.values.iter().enumerate() {
|
||||||
|
if i > 0 { write!(f, " ")?; }
|
||||||
|
write!(f, "{}", val)?;
|
||||||
|
}
|
||||||
|
write!(f, "]")
|
||||||
}
|
}
|
||||||
write!(f, "]")
|
|
||||||
},
|
|
||||||
Value::Record(r) => {
|
|
||||||
write!(f, "{{")?;
|
|
||||||
for (i, (k, v)) in r.iter().enumerate() {
|
|
||||||
if i > 0 { write!(f, ", ")?; }
|
|
||||||
write!(f, ":{} {}", k.name(), v)?;
|
|
||||||
}
|
|
||||||
write!(f, "}}")
|
|
||||||
},
|
},
|
||||||
Value::Function(_) => write!(f, "<native fn>"),
|
Value::Function(_) => write!(f, "<native fn>"),
|
||||||
Value::Object(o) => write!(f, "<{}>", o.type_name()),
|
Value::Object(o) => write!(f, "<{}>", o.type_name()),
|
||||||
|
|||||||
+15
-28
@@ -313,22 +313,24 @@ macro_rules! dispatch_eval {
|
|||||||
for e in elements {
|
for e in elements {
|
||||||
vals.push($self.$eval_method($($observer,)? e)?);
|
vals.push($self.$eval_method($($observer,)? e)?);
|
||||||
}
|
}
|
||||||
Ok(Value::List(Rc::new(vals)))
|
Ok(Value::make_list(vals))
|
||||||
},
|
},
|
||||||
|
|
||||||
BoundKind::Map { entries } => {
|
BoundKind::Map { entries } => {
|
||||||
let mut map = Vec::with_capacity(entries.len());
|
let mut keys = Vec::with_capacity(entries.len());
|
||||||
|
let mut values = Vec::with_capacity(entries.len());
|
||||||
for (k, v) in entries {
|
for (k, v) in entries {
|
||||||
let key = $self.$eval_method($($observer,)? k)?;
|
let key = $self.$eval_method($($observer,)? k)?;
|
||||||
let val = $self.$eval_method($($observer,)? v)?;
|
let val = $self.$eval_method($($observer,)? v)?;
|
||||||
|
|
||||||
if let Value::Keyword(kw) = key {
|
if let Value::Keyword(kw) = key {
|
||||||
map.push((kw, val));
|
keys.push(kw);
|
||||||
|
values.push(val);
|
||||||
} else {
|
} else {
|
||||||
return Err(format!("Map key must be keyword, got {}", key));
|
return Err(format!("Map key must be keyword, got {}", key));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(Value::Record(Rc::new(map)))
|
Ok(Value::make_record(keys, values))
|
||||||
},
|
},
|
||||||
|
|
||||||
BoundKind::Expansion { original_call: _, bound_expanded } => {
|
BoundKind::Expansion { original_call: _, bound_expanded } => {
|
||||||
@@ -599,8 +601,8 @@ impl VM {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn flatten_value(val: Value, into: &mut Vec<Value>) {
|
fn flatten_value(val: Value, into: &mut Vec<Value>) {
|
||||||
if let Value::List(l) = val {
|
if let Value::Tuple(t) = val {
|
||||||
for item in l.iter() {
|
for item in t.values.iter() {
|
||||||
Self::flatten_value(item.clone(), into);
|
Self::flatten_value(item.clone(), into);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -677,30 +679,15 @@ impl VM {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
BoundKind::Tuple { elements } => {
|
BoundKind::Tuple { elements } => {
|
||||||
// If the current value at offset is a List or Record, we dive into it.
|
// If the current value at offset is a Tuple (List or Record), we dive into it.
|
||||||
// Otherwise, we assume the sequence was already flattened (e.g. by Specializer).
|
// Otherwise, we assume the sequence was already flattened (e.g. by Specializer).
|
||||||
if let Some(val) = values.get(*offset) {
|
if let Some(Value::Tuple(t)) = values.get(*offset) {
|
||||||
match val {
|
*offset += 1;
|
||||||
Value::List(l) => {
|
let mut sub_offset = 0;
|
||||||
*offset += 1;
|
for el in elements {
|
||||||
let mut sub_offset = 0;
|
self.unpack(el, &t.values, &mut sub_offset)?;
|
||||||
for el in elements {
|
|
||||||
self.unpack(el, l, &mut sub_offset)?;
|
|
||||||
}
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
Value::Record(r) => {
|
|
||||||
*offset += 1;
|
|
||||||
let mut sub_offset = 0;
|
|
||||||
// Treat record values as a tuple sequence in definition order
|
|
||||||
let record_values: Vec<_> = r.iter().map(|(_, v)| v.clone()).collect();
|
|
||||||
for el in elements {
|
|
||||||
self.unpack(el, &record_values, &mut sub_offset)?;
|
|
||||||
}
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
for el in elements {
|
for el in elements {
|
||||||
|
|||||||
+11
-2
@@ -66,11 +66,11 @@ pub fn run_functional_tests() -> Vec<TestResult> {
|
|||||||
pub fn run_benchmarks(update: bool) -> Vec<BenchmarkResult> {
|
pub fn run_benchmarks(update: bool) -> Vec<BenchmarkResult> {
|
||||||
let mut results = Vec::new();
|
let mut results = Vec::new();
|
||||||
let entries = fs::read_dir("examples").unwrap();
|
let entries = fs::read_dir("examples").unwrap();
|
||||||
let env = Environment::new();
|
|
||||||
let is_release = !cfg!(debug_assertions);
|
let is_release = !cfg!(debug_assertions);
|
||||||
let baseline_re = Regex::new(r";; Benchmark: ([\d\.]+\w+)").unwrap();
|
let baseline_re = Regex::new(r";; Benchmark: ([\d\.]+\w+)").unwrap();
|
||||||
|
|
||||||
for entry in entries.filter_map(|e| e.ok()) {
|
for entry in entries.filter_map(|e| e.ok()) {
|
||||||
|
let env = Environment::new(); // Fresh environment per benchmark for isolation
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
if path.extension().is_some_and(|ext| ext == "myc") {
|
if path.extension().is_some_and(|ext| ext == "myc") {
|
||||||
let content = fs::read_to_string(&path).unwrap();
|
let content = fs::read_to_string(&path).unwrap();
|
||||||
@@ -81,7 +81,16 @@ pub fn run_benchmarks(update: bool) -> Vec<BenchmarkResult> {
|
|||||||
// Prepare: Compile and Link once outside the measurement loop
|
// Prepare: Compile and Link once outside the measurement loop
|
||||||
let linked_node = match env.compile(&content).map(|c| env.link(c)) {
|
let linked_node = match env.compile(&content).map(|c| env.link(c)) {
|
||||||
Ok(node) => node,
|
Ok(node) => node,
|
||||||
Err(_) => continue, // Skip scripts that don't compile
|
Err(e) => {
|
||||||
|
results.push(BenchmarkResult {
|
||||||
|
name,
|
||||||
|
median: Duration::ZERO,
|
||||||
|
baseline: None,
|
||||||
|
diff_pct: None,
|
||||||
|
status: format!("COMPILE ERROR: {}", e),
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Measure: Only the VM execution
|
// Measure: Only the VM execution
|
||||||
|
|||||||
Reference in New Issue
Block a user