Skip to content

Commit ff48dcd

Browse files
committed
Rework cranelift-egraph to use more arenas.
This moves to a strategy based on `hashbrown::raw::RawTable` and "eq-with-context" / "hash-with-context" traits, used to allow nodes to be stored once in a `BumpVec` (which encapsulates a range in a shared `Vec`) and then used in an eclass, with keys in the deduplication hashtable referring to an eclass and enode index in that eclass. This moves back to the enodes-without-IDs strategy used in `egg`, which makes the graph rebuild simpler, but retains the single-storage / no-cloning property. Along the way, carrying through the eq-with-context / hash-with-context to the `Node` itself, we can remove the `&mut [Id]` and `&mut [Type]` slices and replace with `BumpVec`s. Actually they could be made `NonGrowingBumpVec`s for 8 bytes instead of 12; that is future work. This should allow equivalent algorithmic approaches to `egg` but without the separate allocations; all allocations for nodes are now within the entity-component-system-style large `Vec`s.
1 parent 80b6e65 commit ff48dcd

File tree

12 files changed

+978
-177
lines changed

12 files changed

+978
-177
lines changed

‎Cargo.lock

+8-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎cranelift/codegen/Cargo.toml

-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ gimli = { version = "0.26.0", default-features = false, features = ["write"], op
2626
smallvec = { version = "1.6.1" }
2727
regalloc2 = { version = "0.2.0", features = ["checker"] }
2828
souper-ir = { version = "2.1.0", optional = true }
29-
bumpalo = { version = "3.10.0" }
3029
# It is a goal of the cranelift-codegen crate to have minimal external dependencies.
3130
# Please don't add any unless they are essential to the task of creating binary
3231
# machine code. Integration tests that need external dependencies can be

‎cranelift/codegen/src/context.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ use crate::verifier::{verify_context, VerifierErrors, VerifierResult};
3333
#[cfg(feature = "souper-harvest")]
3434
use alloc::string::String;
3535
use alloc::vec::Vec;
36-
use bumpalo::Bump;
3736

3837
#[cfg(feature = "souper-harvest")]
3938
use crate::souper_harvest::do_souper_harvest;
@@ -195,8 +194,7 @@ impl Context {
195194
"About to optimize with egraph phase:\n{}",
196195
self.func.display()
197196
);
198-
let bump = Bump::new();
199-
let mut eg = FuncEGraph::new(&self.func, &self.domtree, &bump);
197+
let mut eg = FuncEGraph::new(&self.func, &self.domtree);
200198
eg.extract(&self.func);
201199
eg.elaborate(&mut self.func);
202200
log::debug!("After egraph optimization:\n{}", self.func.display());

‎cranelift/codegen/src/egg.rs

+40-27
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ use crate::{
77
ir::{Block, Function, InstructionImms, Type},
88
};
99
use alloc::vec::Vec;
10-
use bumpalo::Bump;
1110
use core::ops::Range;
1211
use cranelift_egraph::{EGraph, Id};
12+
use cranelift_entity::EntityList;
1313
use cranelift_entity::SecondaryMap;
1414

1515
mod domtree;
@@ -19,14 +19,13 @@ mod node;
1919

2020
use elaborate::Elaborator;
2121
use extract::Extractor;
22-
use node::Node;
22+
use node::{Node, NodeCtx};
2323

2424
pub struct FuncEGraph<'a> {
2525
domtree: &'a DominatorTree,
26-
egraph: EGraph<Node<'a>>,
27-
/// Backing arena for eclass argument arrays and type arrays for
28-
/// nodes.
29-
bump: &'a Bump,
26+
egraph: EGraph<NodeCtx>,
27+
/// "node context", containing arenas for node data.
28+
node_ctx: NodeCtx,
3029
/// Ranges in `side_effect_ids` for sequences of side-effecting
3130
/// eclasses per block.
3231
side_effects: SecondaryMap<Block, Range<u32>>,
@@ -43,11 +42,11 @@ impl<'a> FuncEGraph<'a> {
4342
/// Create a new EGraph for the given function. Requires the
4443
/// domtree to be precomputed as well; the domtree is used for
4544
/// scheduling when lowering out of the egraph.
46-
pub fn new(func: &Function, domtree: &'a DominatorTree, bump: &'a Bump) -> FuncEGraph<'a> {
45+
pub fn new(func: &Function, domtree: &'a DominatorTree) -> FuncEGraph<'a> {
4746
let mut this = Self {
4847
domtree,
4948
egraph: EGraph::new(),
50-
bump,
49+
node_ctx: NodeCtx::default(),
5150
side_effects: SecondaryMap::with_default(0..0),
5251
side_effect_ids: vec![],
5352
blockparams: SecondaryMap::with_default(0..0),
@@ -68,11 +67,14 @@ impl<'a> FuncEGraph<'a> {
6867
let blockparam_start = self.blockparam_ids_tys.len() as u32;
6968
for (i, &value) in func.dfg.block_params(block).iter().enumerate() {
7069
let ty = func.dfg.value_type(value);
71-
let param = self.egraph.add(Node::Param {
72-
block,
73-
index: i as u32,
74-
ty,
75-
});
70+
let param = self.egraph.add(
71+
Node::Param {
72+
block,
73+
index: i as u32,
74+
ty,
75+
},
76+
&mut self.node_ctx,
77+
);
7678
value_to_id.insert(value, param);
7779
self.blockparam_ids_tys.push((param, ty));
7880
}
@@ -86,20 +88,22 @@ impl<'a> FuncEGraph<'a> {
8688
|| func.dfg[inst].opcode().can_store();
8789

8890
// Build args from SSA values.
89-
let args = self
90-
.bump
91-
.alloc_slice_fill_iter(func.dfg.inst_args(inst).iter().map(|&arg| {
91+
let args = EntityList::from_iter(
92+
func.dfg.inst_args(inst).iter().map(|&arg| {
9293
let arg = func.dfg.resolve_aliases(arg);
9394
*value_to_id
9495
.get(&arg)
9596
.expect("Must have seen def before this use")
96-
}));
97+
}),
98+
&mut self.node_ctx.args,
99+
);
97100

98101
let results = func.dfg.inst_results(inst);
99102

100103
let types = self
101-
.bump
102-
.alloc_slice_fill_iter(results.iter().map(|&val| func.dfg.value_type(val)));
104+
.node_ctx
105+
.types
106+
.from_iter(results.iter().map(|&val| func.dfg.value_type(val)));
103107

104108
// Create the egraph node.
105109
let op = InstructionImms::from(&func.dfg[inst]);
@@ -115,7 +119,7 @@ impl<'a> FuncEGraph<'a> {
115119
} else {
116120
Node::Pure { op, args, types }
117121
};
118-
let id = self.egraph.add(node);
122+
let id = self.egraph.add(node, &mut self.node_ctx);
119123

120124
if side_effect {
121125
self.side_effect_ids.push(id);
@@ -131,11 +135,14 @@ impl<'a> FuncEGraph<'a> {
131135
debug_assert!(many_results.len() > 1);
132136
for (i, &result) in many_results.iter().enumerate() {
133137
let ty = func.dfg.value_type(result);
134-
let projection = self.egraph.add(Node::Result {
135-
value: id,
136-
result: i,
137-
ty,
138-
});
138+
let projection = self.egraph.add(
139+
Node::Result {
140+
value: id,
141+
result: i,
142+
ty,
143+
},
144+
&mut self.node_ctx,
145+
);
139146
value_to_id.insert(result, projection);
140147
}
141148
}
@@ -214,7 +221,7 @@ impl<'a> FuncEGraph<'a> {
214221
let side_effect = self.side_effect_ids[side_effect as usize];
215222
let present = self
216223
.extractor
217-
.visit_eclass(&self.egraph, side_effect)
224+
.visit_eclass(&self.egraph, side_effect, &self.node_ctx)
218225
.is_some();
219226
debug_assert!(present);
220227
}
@@ -241,7 +248,13 @@ impl<'a> FuncEGraph<'a> {
241248
/// the Id-to-Value map and available to all dominated blocks and
242249
/// for the rest of this block. (This subsumes GVN.)
243250
pub fn elaborate(&mut self, func: &mut Function) {
244-
let mut elab = Elaborator::new(func, self.domtree, &self.egraph, &self.extractor);
251+
let mut elab = Elaborator::new(
252+
func,
253+
self.domtree,
254+
&self.egraph,
255+
&self.node_ctx,
256+
&self.extractor,
257+
);
245258
elab.elaborate(
246259
|block| {
247260
let blockparam_range = self.blockparams[block].clone();

‎cranelift/codegen/src/egg/elaborate.rs

+10-6
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
44
use super::domtree::DomTreeWithChildren;
55
use super::extract::Extractor;
6-
use super::node::Node;
6+
use super::node::{Node, NodeCtx};
77
use crate::dominator_tree::DominatorTree;
88
use crate::ir::{Block, Function, SourceLoc, Type, Value, ValueList};
99
use crate::scoped_hash_map::ScopedHashMap;
@@ -13,7 +13,8 @@ use smallvec::SmallVec;
1313
pub(crate) struct Elaborator<'a> {
1414
func: &'a mut Function,
1515
domtree: &'a DominatorTree,
16-
egraph: &'a EGraph<Node<'a>>,
16+
node_ctx: &'a NodeCtx,
17+
egraph: &'a EGraph<NodeCtx>,
1718
extractor: &'a Extractor,
1819
id_to_value: ScopedHashMap<Id, IdValue>,
1920
cur_block: Option<Block>,
@@ -31,13 +32,15 @@ impl<'a> Elaborator<'a> {
3132
pub(crate) fn new(
3233
func: &'a mut Function,
3334
domtree: &'a DominatorTree,
34-
egraph: &'a EGraph<Node>,
35+
egraph: &'a EGraph<NodeCtx>,
36+
node_ctx: &'a NodeCtx,
3537
extractor: &'a Extractor,
3638
) -> Self {
3739
Self {
3840
func,
3941
domtree,
4042
egraph,
43+
node_ctx,
4144
extractor,
4245
id_to_value: ScopedHashMap::new(),
4346
cur_block: None,
@@ -69,7 +72,7 @@ impl<'a> Elaborator<'a> {
6972
};
7073
let inst = self.func.dfg.make_inst(instdata);
7174
self.func.srclocs[inst] = srcloc;
72-
for &ty in result_tys.iter() {
75+
for &ty in result_tys.as_slice(&self.node_ctx.types) {
7376
self.func.dfg.append_result(inst, ty);
7477
}
7578
self.func.layout.append_inst(inst, self.cur_block.unwrap());
@@ -111,8 +114,9 @@ impl<'a> Elaborator<'a> {
111114

112115
// We're going to need to emit this operator. First, elaborate
113116
// all args, recursively.
114-
let args: SmallVec<[Value; 8]> = node
115-
.children()
117+
let args: SmallVec<[Value; 8]> = self
118+
.node_ctx
119+
.children(node)
116120
.iter()
117121
.map(|&id| self.elaborate_eclass_use(id))
118122
.map(|idvalue| match idvalue {

‎cranelift/codegen/src/egg/extract.rs

+22-22
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
//! Extraction phase: pick one enode per eclass, avoiding loops.
22
3-
use super::node::Node;
3+
use super::node::{Node, NodeCtx};
44
use crate::fx::FxHashMap;
5-
use cranelift_egraph::{EGraph, Id, Language, NodeId};
5+
use cranelift_egraph::{EGraph, Id, Language};
66

77
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
88
enum EclassState {
99
Visiting,
10-
Visited { cost: u32, node: NodeId },
10+
Visited { cost: u32, node_idx: u32 },
1111
Deleted,
1212
}
1313

@@ -25,7 +25,12 @@ impl Extractor {
2525

2626
/// Visit an eclass. Return `None` if deleted, or Some(cost) if
2727
/// present.
28-
pub(crate) fn visit_eclass(&mut self, egraph: &EGraph<Node>, id: Id) -> Option<u32> {
28+
pub(crate) fn visit_eclass(
29+
&mut self,
30+
egraph: &EGraph<NodeCtx>,
31+
id: Id,
32+
ctx: &NodeCtx,
33+
) -> Option<u32> {
2934
if let Some(state) = self.eclass_state.get(&id) {
3035
match state {
3136
EclassState::Visiting => {
@@ -43,25 +48,20 @@ impl Extractor {
4348
self.eclass_state.insert(id, EclassState::Visiting);
4449

4550
let mut best_cost_and_node = None;
46-
for (node_id, node) in egraph.enodes(id) {
47-
let this_cost = self.visit_enode(egraph, node);
51+
for (node_idx, node) in egraph.enodes(id).iter().enumerate() {
52+
let this_cost = self.visit_enode(egraph, node, ctx);
4853
best_cost_and_node = match (best_cost_and_node, this_cost) {
4954
(None, None) => None,
50-
(None, Some(c)) => Some((c, node_id)),
51-
(Some((c1, _)), Some(c2)) if c2 < c1 => Some((c2, node_id)),
52-
(Some((c1, node_id1)), _) => Some((c1, node_id1)),
55+
(None, Some(c)) => Some((c, node_idx as u32)),
56+
(Some((c1, _)), Some(c2)) if c2 < c1 => Some((c2, node_idx as u32)),
57+
(Some((c1, node_idx1)), _) => Some((c1, node_idx1)),
5358
};
5459
}
5560

5661
match best_cost_and_node {
57-
Some((cost, node_id)) => {
58-
self.eclass_state.insert(
59-
id,
60-
EclassState::Visited {
61-
cost,
62-
node: node_id,
63-
},
64-
);
62+
Some((cost, node_idx)) => {
63+
self.eclass_state
64+
.insert(id, EclassState::Visited { cost, node_idx });
6565
Some(cost)
6666
}
6767
None => {
@@ -71,19 +71,19 @@ impl Extractor {
7171
}
7272
}
7373

74-
fn visit_enode(&mut self, egraph: &EGraph<Node<'_>>, node: &Node) -> Option<u32> {
74+
fn visit_enode(&mut self, egraph: &EGraph<NodeCtx>, node: &Node, ctx: &NodeCtx) -> Option<u32> {
7575
let mut cost = node.cost() as u32;
76-
for &arg in node.children() {
77-
let arg_cost = self.visit_eclass(egraph, arg)?;
76+
for &arg in ctx.children(node) {
77+
let arg_cost = self.visit_eclass(egraph, arg, ctx)?;
7878
cost += arg_cost;
7979
}
8080
Some(cost)
8181
}
8282

83-
pub(crate) fn get_node<'a>(&'a self, egraph: &'a EGraph<Node<'a>>, id: Id) -> Option<&'a Node> {
83+
pub(crate) fn get_node<'a>(&'a self, egraph: &'a EGraph<NodeCtx>, id: Id) -> Option<&'a Node> {
8484
match self.eclass_state.get(&id)? {
8585
&EclassState::Visiting => unreachable!(),
86-
&EclassState::Visited { node, .. } => Some(egraph.enode(node)),
86+
&EclassState::Visited { node_idx, .. } => Some(&egraph.enodes(id)[node_idx as usize]),
8787
&EclassState::Deleted => None,
8888
}
8989
}

0 commit comments

Comments
 (0)