diff --git a/cli/src/main.rs b/cli/src/main.rs index 18df73567d..efc0f1fe1e 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -25,6 +25,53 @@ use std::process::ExitCode; use crate::cli::{Command, Options}; fn main() -> ExitCode { + // Show the size of the Term structure + println!( + "Size of Term: {}", + std::mem::size_of::() + ); + println!( + "Size of Label: {}", + std::mem::size_of::() + ); + println!( + "Size of Label: {}", + std::mem::size_of::() + ); + println!( + "Size of RecordData: {}", + std::mem::size_of::() + ); + + println!( + "Size of UnaryOp: {}", + std::mem::size_of::() + ); + println!( + "Size of BinaryOp: {}", + std::mem::size_of::() + ); + println!( + "Size of NAryOp: {}", + std::mem::size_of::() + ); + println!( + "Size of TypeAnnotation: {}", + std::mem::size_of::() + ); + println!( + "Size of Type: {}", + std::mem::size_of::() + ); + println!( + "Size of ParseError: {}", + std::mem::size_of::() + ); + println!( + "Size of EvalError: {}", + std::mem::size_of::() + ); + #[cfg(feature = "metrics")] let metrics = metrics::Recorder::install(); diff --git a/core/src/cache.rs b/core/src/cache.rs index e5f962f3ea..30dd1684a7 100644 --- a/core/src/cache.rs +++ b/core/src/cache.rs @@ -10,7 +10,7 @@ use crate::position::TermPos; use crate::program::FieldPath; use crate::stdlib::{self as nickel_stdlib, StdlibModule}; use crate::term::array::Array; -use crate::term::record::{Field, RecordData}; +use crate::term::record::Field; use crate::term::{RichTerm, SharedTerm, Term}; use crate::transform::import_resolution; use crate::typ::UnboundTypeVariableError; @@ -697,7 +697,8 @@ impl Cache { if state < EntryState::Transforming { match SharedTerm::make_mut(&mut term.term) { - Term::Record(RecordData { ref mut fields, .. }) => { + Term::Record(ref mut data) => { + let fields = &mut data.fields; let map_res: Result<_, UnboundTypeVariableError> = std::mem::take(fields) .into_iter() diff --git a/core/src/eval/fixpoint.rs b/core/src/eval/fixpoint.rs index f4a80900e2..8c429c59c9 100644 --- a/core/src/eval/fixpoint.rs +++ b/core/src/eval/fixpoint.rs @@ -93,7 +93,7 @@ pub fn rec_env<'a, I: Iterator, C: Cache>( }; let closure = Closure { - body: RichTerm::from(Term::RuntimeError(error)), + body: RichTerm::from(Term::RuntimeError(Box::new(error))), env: Environment::new(), }; @@ -164,5 +164,5 @@ pub fn revert(cache: &mut C, record_data: RecordData) -> Term { // At run-time, we don't care about `RecordDeps`, because this information is already stored in // the cache (thunks in call-by-need mode). We set it to `None`. - Term::RecRecord(record_data, Vec::new(), None) + Term::RecRecord(Box::new(record_data), Vec::new(), None) } diff --git a/core/src/eval/merge.rs b/core/src/eval/merge.rs index 2ffd4abdd8..4bc7f8a7ac 100644 --- a/core/src/eval/merge.rs +++ b/core/src/eval/merge.rs @@ -245,7 +245,11 @@ pub fn merge( // exactly the same, but can't be shadowed. let eq_contract = mk_app!(stdlib::internals::stdlib_contract_equal(), t1); let result = mk_app!( - mk_term::op2(BinaryOp::ContractApply, eq_contract, Term::Lbl(label)), + mk_term::op2( + BinaryOp::ContractApply, + eq_contract, + Term::Lbl(Box::new(label)) + ), t2 ) .with_pos(pos_op); @@ -289,7 +293,7 @@ pub fn merge( // of raising a blame error as for a delayed contract error, which can't be // caught in user-code, we return an `'Error {..}` value instead. return Ok(Closure::atomic_closure( - mk_term::enum_variant("Error", Term::Record(RecordData::with_field_values([ + mk_term::enum_variant("Error", Term::Record(Box::new(RecordData::with_field_values([ ("message".into(), mk_term::string(format!("extra field{plural} {fields_list}"))), ("notes".into(), Term::Array([ mk_term::string("Have you misspelled a field?"), @@ -300,7 +304,7 @@ pub fn merge( `{some_field | SomeContract, ..}`, to make it accept extra fields." ), ].into_iter().collect(), Default::default()).into()) - ]))))); + ])))))); } _ => (), }; @@ -359,7 +363,7 @@ pub fn merge( // of program transformations. At this point, the interpreter doesn't care // about them anymore, and dependencies are stored at the level of revertible // cache elements directly. - Term::RecRecord(RecordData::new(m, attrs, None), Vec::new(), None), + Term::RecRecord(Box::new(RecordData::new(m, attrs, None)), Vec::new(), None), final_pos, ), env: Environment::new(), diff --git a/core/src/eval/mod.rs b/core/src/eval/mod.rs index 93c0da0441..cbb438d6b0 100644 --- a/core/src/eval/mod.rs +++ b/core/src/eval/mod.rs @@ -73,6 +73,7 @@ //! probably suboptimal for a functional language and is unable to collect cyclic data, which may //! appear inside recursive records. A dedicated garbage collector is probably something to //! consider at some point. +use crate::term::RecordInsertData; use crate::{ cache::{Cache as ImportCache, Envs, ImportResolver}, closurize::{closurize_rec_record, Closurize}, @@ -446,25 +447,37 @@ impl VirtualMachine { // a definition, we complete the error with the additional information of where // it was accessed: let Closure { body, env } = self.cache.get(idx); - let body = match_sharedterm!(match (body.term) { - Term::RuntimeError(EvalError::MissingFieldDef { - id, - metadata, - pos_record, - pos_access: TermPos::None, - }) => RichTerm::new( - Term::RuntimeError(EvalError::MissingFieldDef { + + let body = match body.term.as_ref() { + Term::RuntimeError(error) if matches!(**error, EvalError::MissingFieldDef { .. }) => { + let term = body.term.into_owned(); + // unreachable(): we checked in the pattern that we are precisely in this case + let Term::RuntimeError(error) = term else { + unreachable!(); + }; + // unreachable(): we checked in the pattern that we are precisely in this case + let EvalError::MissingFieldDef { id, metadata, pos_record, - pos_access: pos, - }), - pos, - ), - _ => { - body + pos_access: TermPos::None, + } = *error + else { + unreachable!() + }; + + RichTerm::new( + Term::RuntimeError(Box::new(EvalError::MissingFieldDef { + id, + metadata, + pos_record, + pos_access: pos, + })), + pos, + ) } - }); + _ => body, + }; Ok(Closure { body, env }) } @@ -541,7 +554,7 @@ impl VirtualMachine { // This operation should not be allowed to evaluate a sealed term return Err(EvalError::BlameError { evaluated_arg: label.get_evaluated_arg(&self.cache), - label, + label: *label, call_stack: self.call_stack.clone(), }); } @@ -688,7 +701,7 @@ impl VirtualMachine { Term::Record(data) if !data.attrs.closurized => { Closure { body: RichTerm::new( - Term::Record(data.closurize(&mut self.cache, env)), + Term::Record(Box::new(data.closurize(&mut self.cache, env))), pos, ), env: Environment::new(), @@ -704,9 +717,15 @@ impl VirtualMachine { // type, once we have a different representation for runtime evaluation, // instead of relying on invariants. But for now, we have to live with it. let (mut static_part, dyn_fields) = if !data.attrs.closurized { - closurize_rec_record(&mut self.cache, data, dyn_fields, deps, env) + closurize_rec_record( + &mut self.cache, + *data, + dyn_fields, + deps.map(|deps| *deps), + env, + ) } else { - (data, dyn_fields) + (*data, dyn_fields) }; let rec_env = @@ -733,7 +752,7 @@ impl VirtualMachine { // recursive environment only contains the static fields, and not the dynamic // fields. let extended = dyn_fields.into_iter().fold( - RichTerm::new(Term::Record(static_part), pos), + RichTerm::new(Term::Record(Box::new(static_part)), pos), |acc, (name_as_term, mut field)| { let pos = field .value @@ -751,12 +770,12 @@ impl VirtualMachine { } = field; let extend = mk_term::op2( - BinaryOp::RecordInsert { + BinaryOp::RecordInsert(Box::new(RecordInsertData { metadata, pending_contracts, ext_kind, op_kind: RecordOpKind::ConsiderAllFields, - }, + })), name_as_term, acc, ); @@ -828,10 +847,10 @@ impl VirtualMachine { } } Term::ParseError(parse_error) => { - return Err(EvalError::ParseError(parse_error)); + return Err(EvalError::ParseError(*parse_error)); } Term::RuntimeError(error) => { - return Err(error); + return Err(*error); } // For now, we simply erase annotations at runtime. They aren't accessible anyway // (as opposed to field metadata) and don't change the operational semantics, as @@ -1226,8 +1245,8 @@ pub fn subst( let t = subst(cache, t, initial_env, env); RichTerm::new(Term::Sealed(i, t, lbl), pos) } - Term::Record(record) => { - let mut record = record + Term::Record(mut record) => { + *record = record .map_defined_values(|_, value| subst(cache, value, initial_env, env)); // [^subst-closurized-false]: After substitution, there's no closure in here anymore. @@ -1239,8 +1258,8 @@ pub fn subst( RichTerm::new(Term::Record(record), pos) } - Term::RecRecord(record, dyn_fields, deps) => { - let mut record = record + Term::RecRecord(mut record, dyn_fields, deps) => { + *record = record .map_defined_values(|_, value| subst(cache, value, initial_env, env)); // see [^subst-closurized-false] diff --git a/core/src/eval/operation.rs b/core/src/eval/operation.rs index d6a0fa1168..252c48d7b5 100644 --- a/core/src/eval/operation.rs +++ b/core/src/eval/operation.rs @@ -309,7 +309,7 @@ impl VirtualMachine { UnaryOp::Blame => match_sharedterm!(match (t) { Term::Lbl(label) => Err(EvalError::BlameError { evaluated_arg: label.get_evaluated_arg(&self.cache), - label, + label: *label, call_stack: std::mem::take(&mut self.call_stack), }), _ => mk_type_error!("Label"), @@ -648,7 +648,7 @@ impl VirtualMachine { Ok(Closure { body: RichTerm::new( - Term::Record(RecordData { fields, ..record }), + Term::Record(Box::new(RecordData { fields, ..*record })), pos_op_inh, ), env: Environment::new(), @@ -1085,7 +1085,7 @@ impl VirtualMachine { }); let cont = RichTerm::new( - Term::Record(RecordData { fields, ..record }), + Term::Record(Box::new(RecordData { fields, ..*record })), pos.into_inherited(), ); @@ -1165,7 +1165,7 @@ impl VirtualMachine { let mut empty = RecordData::empty(); empty.sealed_tail = r.sealed_tail; Ok(Closure { - body: RichTerm::new(Term::Record(empty), pos_op.into_inherited()), + body: RichTerm::new(Term::Record(Box::new(empty)), pos_op.into_inherited()), env, }) } @@ -1941,7 +1941,7 @@ impl VirtualMachine { .and_then(|field| field.value) .map(|v| v.term.into_owned()) { - label = label.with_diagnostic_message(msg.into_inner()); + *label = label.with_diagnostic_message(msg.into_inner()); } if let Some(notes_term) = record_data @@ -1968,7 +1968,7 @@ impl VirtualMachine { }) .collect::, _>>()?; - label = label.with_diagnostic_notes(notes); + *label = label.with_diagnostic_notes(notes); } } @@ -2180,12 +2180,14 @@ impl VirtualMachine { _ => mk_type_error!("String", 1, t1, pos1), }) } - BinaryOp::RecordInsert { - metadata, - pending_contracts, - ext_kind, - op_kind, - } => { + BinaryOp::RecordInsert(data) => { + let RecordInsertData { + metadata, + pending_contracts, + ext_kind, + op_kind, + } = *data; + if let Term::Str(id) = &*t1 { match_sharedterm!(match (t2) { Term::Record(record) => { @@ -2229,7 +2231,8 @@ impl VirtualMachine { )) } _ => Ok(Closure { - body: Term::Record(RecordData { fields, ..record }).into(), + body: Term::Record(Box::new(RecordData { fields, ..*record })) + .into(), env: env2, }), } @@ -2272,7 +2275,7 @@ impl VirtualMachine { _ => { // We reconstruct the record's data to have access to // `data.field_names()` - let record = RecordData { fields, ..record }; + let record = RecordData { fields, ..*record }; Err(EvalError::FieldMissing { id: id.into(), @@ -2286,7 +2289,7 @@ impl VirtualMachine { } else { Ok(Closure { body: RichTerm::new( - Term::Record(RecordData { fields, ..record }), + Term::Record(Box::new(RecordData { fields, ..*record })), pos_op_inh, ), env: env2, @@ -2683,7 +2686,7 @@ impl VirtualMachine { RuntimeContract::push_dedup( &mut attrs.pending_contracts, &final_env, - RuntimeContract::new(contract, lbl), + RuntimeContract::new(contract, *lbl), &final_env, ); @@ -2734,7 +2737,7 @@ impl VirtualMachine { for (id, field) in record_data.fields.iter_mut() { let runtime_ctr = RuntimeContract { contract: contract_at_field(*id), - label: label.clone(), + label: (*label).clone(), }; RuntimeContract::push_dedup( @@ -2753,7 +2756,7 @@ impl VirtualMachine { // We want recursive occurrences of fields to pick this new value as // well: hence, we need to recompute the fixpoint, which is done by // `fixpoint::revert`. - let reverted = super::fixpoint::revert(&mut self.cache, record_data); + let reverted = super::fixpoint::revert(&mut self.cache, *record_data); Ok(Closure { body: RichTerm::new(reverted, pos2), @@ -2771,12 +2774,13 @@ impl VirtualMachine { return mk_type_error!("String", 1, t1.into(), pos1); }; - let Term::Lbl(label) = t2 else { + let Term::Lbl(mut label) = t2 else { return mk_type_error!("String", 2, t2.into(), pos2); }; + *label = label.with_diagnostic_message(message.into_inner()); Ok(Closure::atomic_closure(RichTerm::new( - Term::Lbl(label.with_diagnostic_message(message.into_inner())), + Term::Lbl(label), pos_op_inh, ))) } @@ -2814,12 +2818,14 @@ impl VirtualMachine { }) .collect::, _>>()?; - let Term::Lbl(label) = t2 else { + let Term::Lbl(mut label) = t2 else { return mk_type_error!("Label", 2, t2.into(), pos2); }; + *label = label.with_diagnostic_notes(notes); + Ok(Closure::atomic_closure(RichTerm::new( - Term::Lbl(label.with_diagnostic_notes(notes)), + Term::Lbl(label), pos_op_inh, ))) } @@ -2831,12 +2837,13 @@ impl VirtualMachine { return mk_type_error!("String", 1, t1.into(), pos1); }; - let Term::Lbl(label) = t2 else { + let Term::Lbl(mut label) = t2 else { return mk_type_error!("Label", 2, t2.into(), pos2); }; + *label = label.append_diagnostic_note(note.into_inner()); Ok(Closure::atomic_closure(RichTerm::new( - Term::Lbl(label.append_diagnostic_note(note.into_inner())), + Term::Lbl(label), pos2.into_inherited(), ))) } @@ -2875,17 +2882,17 @@ impl VirtualMachine { right, } = split::split(record1.fields, record2.fields); - let left_only = Term::Record(RecordData { + let left_only = Term::Record(Box::new(RecordData { fields: left, sealed_tail: record1.sealed_tail, attrs: record1.attrs, - }); + })); - let right_only = Term::Record(RecordData { + let right_only = Term::Record(Box::new(RecordData { fields: right, sealed_tail: record2.sealed_tail, attrs: record2.attrs, - }); + })); let (center1, center2): (IndexMap, IndexMap) = center @@ -2893,20 +2900,20 @@ impl VirtualMachine { .map(|(id, (left, right))| ((id, left), (id, right))) .unzip(); - let left_center = Term::Record(RecordData { + let left_center = Term::Record(Box::new(RecordData { fields: center1, sealed_tail: None, attrs: RecordAttrs::default().closurized(), - }); + })); - let right_center = Term::Record(RecordData { + let right_center = Term::Record(Box::new(RecordData { fields: center2, sealed_tail: None, attrs: RecordAttrs::default().closurized(), - }); + })); Ok(Closure::atomic_closure(RichTerm::new( - Term::Record(RecordData { + Term::Record(Box::new(RecordData { fields: IndexMap::from([ ( LocIdent::from("left_only"), @@ -2927,7 +2934,7 @@ impl VirtualMachine { ]), attrs: RecordAttrs::default().closurized(), sealed_tail: None, - }), + })), pos_op_inh, ))) } @@ -2973,11 +2980,11 @@ impl VirtualMachine { record1.attrs.open = record1.attrs.open || record2.attrs.open; Ok(Closure::atomic_closure(RichTerm::new( - Term::Record(RecordData { + Term::Record(Box::new(RecordData { fields: record1.fields, attrs: record1.attrs, sealed_tail, - }), + })), pos_op_inh, ))) } @@ -3122,7 +3129,7 @@ impl VirtualMachine { }, env3, pos_op, - MergeMode::Contract(lbl), + MergeMode::Contract(*lbl), &mut self.call_stack, ) } @@ -3199,7 +3206,7 @@ impl VirtualMachine { let fields = tail.fields.keys().map(|s| s.ident()).collect(); r.sealed_tail = Some(record::SealedTail::new( *s, - label.clone(), + (**label).clone(), tail_closurized, fields, )); @@ -3255,13 +3262,13 @@ impl VirtualMachine { debug_assert!(args.next().is_none()); match (&*a1, &*a2, &*a3) { - (Term::SealingKey(s), Term::Lbl(l), Term::Record(r)) => r + (Term::SealingKey(key), Term::Lbl(label), Term::Record(record)) => record .clone() .sealed_tail - .and_then(|t| t.unseal(s).cloned()) + .and_then(|t| t.unseal(key).cloned()) .ok_or_else(|| EvalError::BlameError { - evaluated_arg: l.get_evaluated_arg(&self.cache), - label: l.clone(), + evaluated_arg: label.get_evaluated_arg(&self.cache), + label: (**label).clone(), call_stack: std::mem::take(&mut self.call_stack), }) .map(|t| Closure { body: t, env: env3 }), @@ -3522,7 +3529,7 @@ impl RecPriority { cache.map_at_index(idx, |cache, inner| match inner.body.as_ref() { Term::Record(record_data) => self.propagate_in_record( cache, - record_data.clone(), + (**record_data).clone(), &inner.env, pos, ), @@ -3550,7 +3557,7 @@ impl RecPriority { .collect(); Closure { - body: RichTerm::new(Term::Record(record), pos), + body: RichTerm::new(Term::Record(Box::new(record)), pos), env: new_env, } } @@ -3564,7 +3571,7 @@ impl RecPriority { pos: TermPos, ) -> Closure { match st.into_owned() { - Term::Record(record_data) => self.propagate_in_record(cache, record_data, &env, pos), + Term::Record(record_data) => self.propagate_in_record(cache, *record_data, &env, pos), t => Closure { body: RichTerm::new(t, pos), env, diff --git a/core/src/parser/utils.rs b/core/src/parser/utils.rs index 57d71e2ea3..394314fe7f 100644 --- a/core/src/parser/utils.rs +++ b/core/src/parser/utils.rs @@ -157,10 +157,10 @@ impl FieldDef { let mut fields = IndexMap::new(); fields.insert(id, acc); Field::from(RichTerm::new( - Term::Record(RecordData { + Term::Record(Box::new(RecordData { fields, ..Default::default() - }), + })), pos, )) } @@ -172,10 +172,10 @@ impl FieldDef { let mut fields = IndexMap::new(); fields.insert(id, acc); Field::from(RichTerm::new( - Term::Record(RecordData { + Term::Record(Box::new(RecordData { fields, ..Default::default() - }), + })), pos, )) } else { @@ -184,7 +184,11 @@ impl FieldDef { // `RecRecord` to handle dynamic fields at evaluation time rather than // right here Field::from(RichTerm::new( - Term::RecRecord(RecordData::empty(), vec![(exp, acc)], None), + Term::RecRecord( + Box::new(RecordData::empty()), + vec![(exp, acc)], + None, + ), pos, )) } @@ -359,7 +363,7 @@ impl AttachTerm for TypeAnnotation { } let pos = rt.pos; - RichTerm::new(Term::Annotated(self, rt), pos) + RichTerm::new(Term::Annotated(Box::new(self), rt), pos) } } @@ -497,7 +501,7 @@ where }); Term::RecRecord( - RecordData::new(static_fields, attrs, None), + Box::new(RecordData::new(static_fields, attrs, None)), dynamic_fields, None, ) @@ -536,11 +540,11 @@ fn merge_fields(id_span: RawSpan, field1: Field, field2: Field) -> Field { for (id, (field1, field2)) in center.into_iter() { fields.insert(id, merge_fields(id_span, field1, field2)); } - Term::Record(RecordData::new( + Term::Record(Box::new(RecordData::new( fields, RecordAttrs::combine(rd1.attrs, rd2.attrs), None, - )) + ))) .into() } (t1, t2) => mk_term::op2( @@ -643,7 +647,7 @@ pub fn mk_let( pub fn mk_fun(pat: Pattern, body: RichTerm) -> Term { match pat.data { PatternData::Any(id) => Term::Fun(id, body), - _ => Term::FunPattern(pat, body), + _ => Term::FunPattern(Box::new(pat), body), } } diff --git a/core/src/pretty.rs b/core/src/pretty.rs index 67d7820f03..67ff7a12a0 100644 --- a/core/src/pretty.rs +++ b/core/src/pretty.rs @@ -863,7 +863,7 @@ where LetPattern(pattern, rt, body) => docs![ allocator, "let ", - pattern, + &**pattern, if let Annotated(annot, _) = rt.as_ref() { annot.pretty(allocator) } else { diff --git a/core/src/term/make/builder.rs b/core/src/term/make/builder.rs index 4326f54c5d..03f6e59c53 100644 --- a/core/src/term/make/builder.rs +++ b/core/src/term/make/builder.rs @@ -223,10 +223,10 @@ where let fst = it.next().unwrap(); let content = it.rev().fold(content, |acc, id| { - record::Field::from(RichTerm::from(Term::Record(RecordData { + record::Field::from(RichTerm::from(Term::Record(Box::new(RecordData { fields: [(LocIdent::from(id), acc)].into(), ..Default::default() - }))) + })))) }); (fst.into(), content) @@ -262,7 +262,7 @@ where } } } - Term::Record(RecordData::new(static_fields, attrs, None)) + Term::Record(Box::new(RecordData::new(static_fields, attrs, None))) } impl Record { diff --git a/core/src/term/mod.rs b/core/src/term/mod.rs index 3e50f8fbb7..9e6f798ffb 100644 --- a/core/src/term/mod.rs +++ b/core/src/term/mod.rs @@ -101,11 +101,13 @@ pub enum Term { /// A destructuring function. #[serde(skip)] - FunPattern(Pattern, RichTerm), + FunPattern(Box, RichTerm), /// A blame label. + /// + /// Label are boxed to minimize the overall size of term. #[serde(skip)] - Lbl(Label), + Lbl(Box