Skip to content

Commit 152993b

Browse files
committed
Incremental Merging
1 parent 120b363 commit 152993b

File tree

6 files changed

+331
-65
lines changed

6 files changed

+331
-65
lines changed

src/eval/cache/incremental.rs

Lines changed: 168 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
/// A [Cache] implementation with incremental computation features.
1+
//! A [Cache] implementation with incremental computation features.
2+
use std::collections::{HashMap, HashSet};
23

34
use super::{BlackholedError, Cache, CacheIndex, Closure, Environment, IdentKind};
45
use crate::{
@@ -15,6 +16,12 @@ pub enum IncNodeState {
1516
Evaluated,
1617
}
1718

19+
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
20+
pub struct DependencyLink {
21+
id: Ident,
22+
idx: CacheIndex,
23+
}
24+
1825
/// A node in the dependent computation graph stored in [IncCache].
1926
#[derive(Debug, Clone)]
2027
pub struct IncNode {
@@ -26,8 +33,10 @@ pub struct IncNode {
2633
bty: BindingType,
2734
// The state of the node.
2835
state: IncNodeState,
36+
// Forward links to dependencies.
37+
fwdlinks: Vec<DependencyLink>,
2938
// Backlinks to nodes depending on this node.
30-
backlinks: Vec<CacheIndex>,
39+
backlinks: Vec<DependencyLink>,
3140
}
3241

3342
impl IncNode {
@@ -38,6 +47,7 @@ impl IncNode {
3847
kind,
3948
bty,
4049
state: IncNodeState::default(),
50+
fwdlinks: Vec::new(),
4151
backlinks: Vec::new(),
4252
}
4353
}
@@ -60,7 +70,7 @@ impl IncCache {
6070
idx
6171
}
6272

63-
fn revnode_as_explicit_fun<'a, I>(node: &IncNode, args: I) -> IncNode
73+
fn revnode_as_explicit_fun<'a, I>(node: &mut IncNode, args: I)
6474
where
6575
I: DoubleEndedIterator<Item = &'a Ident>,
6676
{
@@ -75,18 +85,141 @@ impl IncCache {
7585
let as_function =
7686
args.rfold(body, |built, id| RichTerm::from(Term::Fun(*id, built)));
7787

78-
IncNode::new(
79-
Closure {
80-
body: as_function,
81-
env,
82-
},
83-
node.kind,
84-
node.bty.clone(),
85-
)
88+
node.orig = Closure {
89+
body: as_function,
90+
env,
91+
}
8692
}
87-
_ => node.clone(),
93+
_ => (),
94+
}
95+
}
96+
97+
fn update_backlinks(&mut self, idx: CacheIndex) {
98+
let node = self.store.get(idx).unwrap().clone();
99+
for i in node.fwdlinks {
100+
let n = self.store.get_mut(i.idx).unwrap();
101+
n.backlinks.push(DependencyLink { id: i.id, idx: idx });
102+
}
103+
}
104+
105+
fn propagate_dirty(&mut self, idx: CacheIndex) {
106+
let mut node = self.store.get_mut(idx).unwrap();
107+
node.cached = None;
108+
node.state = IncNodeState::Suspended;
109+
110+
let mut visited = HashSet::new();
111+
let mut stack = node.backlinks.clone();
112+
113+
visited.insert(idx);
114+
115+
while !stack.is_empty() {
116+
let i = stack.pop().unwrap();
117+
visited.insert(i.idx);
118+
let mut current_node = self.store.get_mut(i.idx).unwrap();
119+
current_node.cached = None;
120+
current_node.state = IncNodeState::Suspended;
121+
stack.extend(
122+
current_node
123+
.backlinks
124+
.iter()
125+
.filter(|x| !visited.contains(&x.idx)),
126+
)
127+
}
128+
}
129+
130+
fn propagate_dirty_vec(&mut self, indices: Vec<CacheIndex>) {
131+
let mut visited = HashSet::new();
132+
let mut stack = indices;
133+
134+
while !stack.is_empty() {
135+
let i = stack.pop().unwrap();
136+
visited.insert(i);
137+
let mut current_node = self.store.get_mut(i).unwrap();
138+
current_node.cached = None;
139+
current_node.state = IncNodeState::Suspended;
140+
println!("IDX: {:?} BLs: {:?}", i, current_node.backlinks);
141+
stack.extend(
142+
current_node
143+
.backlinks
144+
.iter()
145+
.map(|x| x.idx)
146+
.filter(|x| !visited.contains(&x)),
147+
)
88148
}
89149
}
150+
151+
/* Do we need this when we can revert in place?
152+
153+
fn propagate_revert(&mut self, id: Ident, idx: CacheIndex) -> HashMap<Ident, CacheIndex> {
154+
let mut nodes_reverted = HashMap::new();
155+
156+
let mut visited = HashSet::new();
157+
let mut stack = vec![idx];
158+
159+
while !stack.is_empty() {
160+
let i = stack.pop().unwrap();
161+
visited.insert(i);
162+
163+
let idx_reverted = self.revert(&idx);
164+
//FIXME: use the actual node's id
165+
let node_id = Ident::from("TODO!");
166+
nodes_reverted.insert(node_id, idx_reverted);
167+
168+
let current_node = self.store.get(i).unwrap();
169+
170+
stack.extend(
171+
current_node
172+
.backlinks
173+
.iter()
174+
.map(|x| x.idx)
175+
.filter(|x| !visited.contains(x)),
176+
)
177+
}
178+
179+
nodes_reverted
180+
} */
181+
182+
fn smart_clone(&mut self, v: Vec<CacheIndex>) -> HashMap<CacheIndex, CacheIndex> {
183+
let mut new_indices = HashMap::new();
184+
185+
for i in v.iter() {
186+
let current_node = self.store.get(*i).unwrap().clone();
187+
new_indices.insert(*i, self.add_node(current_node));
188+
}
189+
190+
for i in new_indices.values() {
191+
let current_node = self.store.get_mut(*i).unwrap();
192+
193+
for dep in current_node.backlinks.iter_mut() {
194+
dep.idx = if let Some(idx) = new_indices.get(&dep.idx) {
195+
*idx
196+
} else {
197+
dep.idx
198+
}
199+
}
200+
201+
let mut to_be_updated = vec![];
202+
203+
for dep in current_node.fwdlinks.iter_mut() {
204+
dep.idx = if let Some(idx) = new_indices.get(&dep.idx) {
205+
*idx
206+
} else {
207+
to_be_updated.push(dep.clone());
208+
dep.idx
209+
}
210+
}
211+
212+
for dep in to_be_updated {
213+
let target_node = self.store.get_mut(dep.idx).unwrap();
214+
target_node.backlinks.push(DependencyLink {
215+
id: dep.id,
216+
idx: *i,
217+
});
218+
}
219+
}
220+
221+
new_indices
222+
}
90223
}
91224

92225
impl Cache for IncCache {
@@ -204,9 +337,12 @@ impl Cache for IncCache {
204337
kind: node.kind,
205338
bty: node.bty.clone(),
206339
state: node.state,
340+
fwdlinks: node.fwdlinks.clone(),
207341
backlinks: node.backlinks.clone(),
208342
};
209343

344+
// TODO: Should this push the dependencies?
345+
210346
self.add_node(new_node)
211347
}
212348

@@ -224,13 +360,19 @@ impl Cache for IncCache {
224360
BindingType::Revertible(ref deps) => match deps {
225361
FieldDeps::Unknown => new_cached.env.extend(rec_env.iter().cloned()),
226362
FieldDeps::Known(deps) if deps.is_empty() => (),
227-
FieldDeps::Known(deps) => new_cached
228-
.env
229-
.extend(rec_env.iter().filter(|(id, _)| deps.contains(id)).cloned()),
363+
FieldDeps::Known(deps) => {
364+
let deps = rec_env.iter().filter(|(id, _)| deps.contains(id)).cloned();
365+
node.fwdlinks = deps
366+
.clone()
367+
.map(|(id, idx)| DependencyLink { id, idx })
368+
.collect();
369+
new_cached.env.extend(deps);
370+
}
230371
},
231372
}
232373

233374
node.cached = Some(new_cached);
375+
self.update_backlinks(*idx);
234376
}
235377

236378
fn saturate<'a, I: DoubleEndedIterator<Item = &'a Ident> + Clone>(
@@ -239,7 +381,7 @@ impl Cache for IncCache {
239381
env: &mut Environment,
240382
fields: I,
241383
) -> RichTerm {
242-
let node = self.store.get(idx).unwrap();
384+
let node = self.store.get_mut(idx).unwrap();
243385

244386
let mut deps_filter: Box<dyn FnMut(&&Ident) -> bool> = match node.bty.clone() {
245387
BindingType::Revertible(FieldDeps::Known(deps)) => {
@@ -249,13 +391,10 @@ impl Cache for IncCache {
249391
BindingType::Normal => Box::new(|_: &&Ident| false),
250392
};
251393

252-
let node_as_function = self.add_node(IncCache::revnode_as_explicit_fun(
253-
node,
254-
fields.clone().filter(&mut deps_filter),
255-
));
394+
IncCache::revnode_as_explicit_fun(node, fields.clone().filter(&mut deps_filter));
256395

257396
let fresh_var = Ident::fresh();
258-
env.insert(fresh_var, node_as_function);
397+
env.insert(fresh_var, idx);
259398

260399
let as_function_closurized = RichTerm::from(Term::Var(fresh_var));
261400
let args = fields.filter_map(|id| deps_filter(&id).then(|| RichTerm::from(Term::Var(*id))));
@@ -264,4 +403,12 @@ impl Cache for IncCache {
264403
RichTerm::from(Term::App(partial_app, arg))
265404
})
266405
}
406+
407+
fn smart_clone(&mut self, v: Vec<CacheIndex>) -> HashMap<CacheIndex, CacheIndex> {
408+
self.smart_clone(v)
409+
}
410+
411+
fn propagate_dirty(&mut self, indices: Vec<CacheIndex>) {
412+
self.propagate_dirty_vec(indices);
413+
}
267414
}

src/eval/cache/lazy.rs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@ use crate::{
44
identifier::Ident,
55
term::{record::FieldDeps, BindingType, RichTerm, Term},
66
};
7-
use std::cell::{Ref, RefCell, RefMut};
87
use std::rc::{Rc, Weak};
8+
use std::{
9+
cell::{Ref, RefCell, RefMut},
10+
collections::HashMap,
11+
};
912

1013
/// The state of a thunk.
1114
///
@@ -355,7 +358,7 @@ impl ThunkData {
355358
/// inside a record may be invalidated by merging, and thus need to store the unaltered original
356359
/// expression. Those aspects are handled and discussed in more detail in
357360
/// [InnerThunkData].
358-
#[derive(Clone, Debug, PartialEq)]
361+
#[derive(Clone, Debug)]
359362
pub struct Thunk {
360363
data: Rc<RefCell<ThunkData>>,
361364
ident_kind: IdentKind,
@@ -560,6 +563,21 @@ impl Thunk {
560563
self.data.borrow().deps()
561564
}
562565
}
566+
567+
impl PartialEq for Thunk {
568+
fn eq(&self, other: &Self) -> bool {
569+
self.data.as_ptr() == other.data.as_ptr() && self.ident_kind == other.ident_kind
570+
}
571+
}
572+
573+
impl Eq for Thunk {}
574+
575+
impl std::hash::Hash for Thunk {
576+
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
577+
let raw_ptr = self.data.as_ptr();
578+
(self.ident_kind, raw_ptr).hash(state)
579+
}
580+
}
563581
/// A thunk update frame.
564582
///
565583
/// A thunk update frame is put on the stack whenever a variable is entered, such that once this
@@ -692,4 +710,15 @@ impl Cache for CBNCache {
692710
) -> Result<Self::UpdateIndex, BlackholedError> {
693711
idx.mk_update_frame()
694712
}
713+
714+
fn smart_clone(
715+
&mut self,
716+
v: Vec<CacheIndex>,
717+
) -> std::collections::HashMap<CacheIndex, CacheIndex> {
718+
v.into_iter()
719+
.map(|idx| (idx.clone(), self.revert(&idx)))
720+
.collect()
721+
}
722+
723+
fn propagate_dirty(&mut self, indices: Vec<CacheIndex>) {}
695724
}

src/eval/cache/mod.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::collections::HashMap;
2+
13
/// The Nickel generic evaluation cache. This module abstracts away the details for managing
24
/// suspended computations and their memoization strategies.
35
///
@@ -11,15 +13,15 @@ use crate::{
1113
};
1214

1315
pub mod lazy;
14-
//pub mod incremental;
16+
// pub mod incremental;
1517

1618
/// An index to a specific item stored in the cache
1719
pub type CacheIndex = lazy::Thunk;
18-
//pub type CacheIndex = usize;
20+
// pub type CacheIndex = usize;
1921

2022
/// The current Cache implementation
2123
pub type CacheImpl = lazy::CBNCache;
22-
//pub type CacheImpl = incremental::IncCache;
24+
// pub type CacheImpl = incremental::IncCache;
2325

2426
/// A black-holed node was accessed, which would lead to infinite recursion.
2527
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
@@ -94,4 +96,8 @@ pub trait Cache: Clone {
9496
&mut self,
9597
idx: &mut CacheIndex,
9698
) -> Result<Self::UpdateIndex, BlackholedError>;
99+
100+
fn smart_clone(&mut self, v: Vec<CacheIndex>) -> HashMap<CacheIndex, CacheIndex>;
101+
102+
fn propagate_dirty(&mut self, indices: Vec<CacheIndex>);
97103
}

0 commit comments

Comments
 (0)