@@ -3,7 +3,7 @@
3use crate::ast::{Expr, Pattern};
3use crate::ast::{Expr, Pattern};
4use num_bigint::BigInt;
4use num_bigint::BigInt;
5use std::collections::{BTreeMap, BTreeSet, HashMap};
5use std::collections::{BTreeMap, BTreeSet, HashMap};
6−use std::rc::Rc;
6+use std::rc::{Rc, Weak};
8pub type RecordMap = BTreeMap<String, Value>;
8pub type RecordMap = BTreeMap<String, Value>;
@@ -764,10 +764,10 @@ pub struct Env {
764 frame: Option<Rc<Frame>>,
764 frame: Option<Rc<Frame>>,
767−/// A large acyclic let can have one frame per binding. Release unique
767+/// A large acyclic let or a collected recursive cycle can release a long
768+/// chain of parent frames. Release unique frames iteratively, keeping the
768−/// frames iteratively, keeping the next parent alive while its child's
769+/// next parent alive while its child's values drop, so closures capturing
769−/// values drop, so closures capturing earlier frames cannot recurse down
770+/// earlier frames cannot recurse down that chain either (§4.1).
770−/// that chain either (§4.1).
771impl Drop for Env {
771impl Drop for Env {
772 fn drop(&mut self) {
772 fn drop(&mut self) {
773 let mut next = self.frame.take();
773 let mut next = self.frame.take();
@@ -820,16 +820,20 @@ impl Env {
820 /// extend with a recursive frame (for `let` blocks) binding `names`,
820 /// extend with a recursive frame (for `let` blocks) binding `names`,
821 /// whose values are pushed to the returned cell in the same order
821 /// whose values are pushed to the returned cell in the same order
822 pub fn extend_rec(&self, names: Rc<[String]>) -> (Env, Rc<std::cell::RefCell<Vec<Value>>>) {
822 pub fn extend_rec(&self, names: Rc<[String]>) -> (Env, Rc<std::cell::RefCell<Vec<Value>>>) {
823+ // Collect before allocating: `self` and all values the evaluator is
824+ // using are still ordinary strong roots during this check.
825+ maybe_collect_cycles();
823 let cell = Rc::new(std::cell::RefCell::new(Vec::with_capacity(names.len())));
826 let cell = Rc::new(std::cell::RefCell::new(Vec::with_capacity(names.len())));
826− frame: Some(Rc::new(Frame {
827+ let frame = Rc::new(Frame {
827− kind: FrameKind::Rec(names, cell.clone()),
828+ kind: FrameKind::Rec(names, cell.clone()),
828− parent: self.clone(),
829+ parent: self.clone(),
830− },
831+ RECURSIVE_FRAMES.with(|registry| {
831− cell,
832+ let mut registry = registry.borrow_mut();
832− )
833+ registry.frames.push(Rc::downgrade(&frame));
834+ registry.allocations += 1;
836+ (Env { frame: Some(frame) }, cell)
835 pub fn lookup(&self, name: &str) -> Option<Value> {
839 pub fn lookup(&self, name: &str) -> Option<Value> {
@@ -903,6 +907,302 @@ impl Env {
910+// A recursive environment owns its bindings, and a closure in those bindings
911+// may own the environment again. Weak captures would lose an escaping
912+// closure's bindings, so these are real strong references. Collect their
913+// unreachable cycles by subtracting the references inside their owned graph
914+// from each node's reference count. References from outside that graph are
915+// roots: returned values, the interpreter, and the running machine's states
916+// and continuations. No explicit evaluator root stack is needed.
918+// Only recursive frames can introduce cycles in language values. The
919+// registry is weak, and all traversal and marking below use explicit work
920+// lists, including through shared lists, records, and function wrappers.
922+struct RecursiveFrames {
923+ frames: Vec<Weak<Frame>>,
925+ next_collection: usize,
926+ without_environments: HashMap<(u8, usize), EnvironmentFree>,
927+ cached_since_pruning: usize,
932+ static RECURSIVE_FRAMES: std::cell::RefCell<RecursiveFrames> =
933+ std::cell::RefCell::new(RecursiveFrames::default());
936+const MIN_CYCLE_BATCH: usize = 256;
938+fn maybe_collect_cycles() {
939+ let due = RECURSIVE_FRAMES.with(|registry| {
940+ let registry = registry.borrow();
941+ !registry.collecting
942+ && registry.allocations >= registry.next_collection.max(MIN_CYCLE_BATCH)
949+/// Reclaim dead recursive environments. The caller must retain any result
950+/// it wants to return while this runs, as an ordinary strong reference.
951+pub(crate) fn collect_cycles() {
952+ RECURSIVE_FRAMES.with(|shared| {
953+ let mut registry = {
954+ let mut registry = shared.borrow_mut();
955+ if registry.collecting { return; }
956+ let previous = std::mem::take(&mut *registry);
957+ registry.collecting = true;
960+ // Do not hold the registry borrow while dropping values. Opaque
961+ // Rust backend captures can run destructors that allocate frames or
962+ // drop another interpreter; such nested collections are deferred.
963+ let mut graph = CycleGraph::default();
964+ for frame in ®istry.frames {
965+ if let Some(frame) = frame.upgrade() {
966+ graph.add(CycleNode::Frame(frame));
969+ graph.trace(®istry.without_environments);
970+ registry.cached_since_pruning += graph.cache_environment_free(&mut registry.without_environments);
972+ // Release the collector's temporary roots before pruning the weak
973+ // registry. Live recursive frames set the next batch size so a
974+ // growing set of escaping closures costs amortized linear work.
976+ registry.frames.retain(|frame| frame.strong_count() != 0);
977+ registry.allocations = 0;
978+ registry.next_collection = registry.frames.len().max(MIN_CYCLE_BATCH);
979+ // Immutable repository trees usually hold no functions. Remember
980+ // that once, without keeping their values alive. Pruning is batched
981+ // too: a large, live tree must not be scanned on every collection.
982+ if registry.frames.is_empty() {
983+ registry.without_environments.clear();
984+ registry.cached_since_pruning = 0;
985+ } else if registry.cached_since_pruning >=
986+ (registry.without_environments.len() / 2).max(MIN_CYCLE_BATCH)
988+ registry.without_environments.retain(|_, cached| cached.is_alive());
989+ registry.cached_since_pruning = 0;
991+ let mut pending = shared.borrow_mut();
992+ registry.frames.append(&mut pending.frames);
993+ registry.allocations += pending.allocations;
994+ *pending = registry;
998+// These containers are immutable while shared. Weak references both avoid
999+// extending their lives and prevent address reuse from giving a false hit.
1000+// Rc::make_mut dissociates weak pointers before it can change a container.
1001+enum EnvironmentFree {
1002+ List(Weak<Vec<Value>>),
1003+ Record(Weak<RecordMap>),
1006+impl EnvironmentFree {
1007+ fn is_alive(&self) -> bool {
1009+ Self::List(v) => v.strong_count() != 0,
1010+ Self::Record(v) => v.strong_count() != 0,
1017+ Bindings(Rc<std::cell::RefCell<Vec<Value>>>),
1018+ Function(Rc<FunVal>),
1019+ List(Rc<Vec<Value>>),
1020+ Record(Rc<RecordMap>),
1024+ fn key(&self) -> (u8, usize) {
1026+ Self::Frame(v) => (0, Rc::as_ptr(v) as usize),
1027+ Self::Bindings(v) => (1, Rc::as_ptr(v) as usize),
1028+ Self::Function(v) => (2, Rc::as_ptr(v) as usize),
1029+ Self::List(v) => (3, Rc::as_ptr(v) as usize),
1030+ Self::Record(v) => (4, Rc::as_ptr(v) as usize),
1034+ fn strong_count(&self) -> usize {
1036+ Self::Frame(v) => Rc::strong_count(v),
1037+ Self::Bindings(v) => Rc::strong_count(v),
1038+ Self::Function(v) => Rc::strong_count(v),
1039+ Self::List(v) => Rc::strong_count(v),
1040+ Self::Record(v) => Rc::strong_count(v),
1044+ fn children(&self) -> Vec<CycleNode> {
1045+ let mut out = Vec::new();
1046+ let mut value = |v: &Value| match v {
1047+ Value::Fun(f) => out.push(Self::Function(f.clone())),
1048+ // A list view owns its whole backing vector, including the
1049+ // prefix that `tail`/`drop` no longer exposes.
1050+ Value::List(xs) => out.push(Self::List(xs.items.clone())),
1051+ Value::Record(fields) => out.push(Self::Record(fields.clone())),
1052+ // Backend thunks are opaque Rust closures. Their references,
1053+ // if any, remain external roots; never guess what they own.
1057+ Self::Frame(frame) => {
1058+ match &frame.kind {
1059+ FrameKind::Small(bindings) => {
1060+ for (_, v) in bindings { value(v); }
1062+ FrameKind::Big(bindings) => {
1063+ for v in bindings.values() { value(v); }
1065+ FrameKind::Rec(_, bindings) => {
1066+ out.push(Self::Bindings(bindings.clone()));
1069+ if let Some(parent) = &frame.parent.frame {
1070+ out.push(Self::Frame(parent.clone()));
1073+ Self::Bindings(bindings) => {
1074+ for v in bindings.borrow().iter() { value(v); }
1076+ Self::Function(f) => {
1078+ FunVal::Closure { env, applied_args, .. } => {
1079+ for v in applied_args { value(v); }
1080+ if let Some(frame) = &env.frame {
1081+ out.push(Self::Frame(frame.clone()));
1084+ FunVal::Builtin { args, .. } => {
1085+ for v in args { value(v); }
1087+ FunVal::OrFun(a, b) | FunVal::ComposeLazy(a, b) => {
1091+ FunVal::Labelled(_, f) => value(f),
1094+ Self::List(items) => {
1095+ for v in items.iter() { value(v); }
1097+ Self::Record(fields) => {
1098+ for v in fields.values() { value(v); }
1107+ // Exactly one temporary strong reference per node. All temporary edge
1108+ // clones are consumed by `add` before reference counts are examined.
1109+ nodes: Vec<CycleNode>,
1110+ indices: HashMap<(u8, usize), usize>,
1111+ edges: Vec<Vec<usize>>,
1112+ incoming: Vec<usize>,
1116+ fn add(&mut self, node: CycleNode) -> usize {
1117+ let key = node.key();
1118+ if let Some(&index) = self.indices.get(&key) {
1121+ let index = self.nodes.len();
1122+ self.indices.insert(key, index);
1123+ self.nodes.push(node);
1124+ self.edges.push(Vec::new());
1125+ self.incoming.push(0);
1129+ fn trace(&mut self, without_environments: &HashMap<(u8, usize), EnvironmentFree>) {
1131+ while index < self.nodes.len() {
1132+ for child in self.nodes[index].children() {
1133+ if without_environments.contains_key(&child.key()) {
1136+ let next = self.add(child);
1137+ self.edges[index].push(next);
1138+ self.incoming[next] += 1;
1144+ fn cache_environment_free(&self, cache: &mut HashMap<(u8, usize), EnvironmentFree>) -> usize {
1145+ let mut parents = vec![Vec::new(); self.nodes.len()];
1146+ for (index, edges) in self.edges.iter().enumerate() {
1147+ for &next in edges { parents[next].push(index); }
1149+ let mut reaches_frame = vec![false; self.nodes.len()];
1150+ let mut work = Vec::new();
1151+ for (index, node) in self.nodes.iter().enumerate() {
1152+ if matches!(node, CycleNode::Frame(_)) {
1153+ reaches_frame[index] = true;
1157+ while let Some(index) = work.pop() {
1158+ for &parent in &parents[index] {
1159+ if !reaches_frame[parent] {
1160+ reaches_frame[parent] = true;
1166+ for (index, node) in self.nodes.iter().enumerate() {
1167+ if !reaches_frame[index] {
1168+ let cached = match node {
1169+ CycleNode::List(v) => EnvironmentFree::List(Rc::downgrade(v)),
1170+ CycleNode::Record(v) => EnvironmentFree::Record(Rc::downgrade(v)),
1173+ if cache.insert(node.key(), cached).is_none() { added += 1; }
1179+ fn collect(&self) {
1180+ let mut reachable = vec![false; self.nodes.len()];
1181+ let mut work = Vec::new();
1182+ for (index, node) in self.nodes.iter().enumerate() {
1183+ if node.strong_count() > self.incoming[index] + 1 {
1184+ reachable[index] = true;
1188+ while let Some(index) = work.pop() {
1189+ for &next in &self.edges[index] {
1190+ if !reachable[next] {
1191+ reachable[next] = true;
1196+ for (index, node) in self.nodes.iter().enumerate() {
1197+ if !reachable[index] {
1198+ if let CycleNode::Bindings(bindings) = node {
1199+ bindings.borrow_mut().clear();
906// ----------------------------------------------------------------------
1206// ----------------------------------------------------------------------
907// structural equality (§4.5); comparing functions is a crash
1207// structural equality (§4.5); comparing functions is a crash
908// ----------------------------------------------------------------------
1208// ----------------------------------------------------------------------