cranelift_codegen/egraph.rs
1//! Support for egraphs represented in the DataFlowGraph.
2
3use crate::alias_analysis::{AliasAnalysis, LastStores};
4use crate::ctxhash::{CtxEq, CtxHash, CtxHashMap};
5use crate::cursor::{Cursor, CursorPosition, FuncCursor};
6use crate::dominator_tree::DominatorTree;
7use crate::egraph::domtree::DomTreeWithChildren;
8use crate::egraph::elaborate::Elaborator;
9use crate::fx::FxHashSet;
10use crate::inst_predicates::{is_mergeable_for_egraph, is_pure_for_egraph};
11use crate::ir::{
12 Block, DataFlowGraph, Function, Inst, InstructionData, Type, Value, ValueDef, ValueListPool,
13};
14use crate::loop_analysis::LoopAnalysis;
15use crate::opts::generated_code::ContextIter;
16use crate::opts::IsleContext;
17use crate::scoped_hash_map::{Entry as ScopedEntry, ScopedHashMap};
18use crate::trace;
19use crate::unionfind::UnionFind;
20use cranelift_entity::packed_option::ReservedValue;
21use cranelift_entity::SecondaryMap;
22use std::hash::Hasher;
23
24mod cost;
25mod domtree;
26mod elaborate;
27
28/// Pass over a Function that does the whole aegraph thing.
29///
30/// - Removes non-skeleton nodes from the Layout.
31/// - Performs a GVN-and-rule-application pass over all Values
32/// reachable from the skeleton, potentially creating new Union
33/// nodes (i.e., an aegraph) so that some values have multiple
34/// representations.
35/// - Does "extraction" on the aegraph: selects the best value out of
36/// the tree-of-Union nodes for each used value.
37/// - Does "scoped elaboration" on the aegraph: chooses one or more
38/// locations for pure nodes to become instructions again in the
39/// layout, as forced by the skeleton.
40///
41/// At the beginning and end of this pass, the CLIF should be in a
42/// state that passes the verifier and, additionally, has no Union
43/// nodes. During the pass, Union nodes may exist, and instructions in
44/// the layout may refer to results of instructions that are not
45/// placed in the layout.
46pub struct EgraphPass<'a> {
47 /// The function we're operating on.
48 func: &'a mut Function,
49 /// Dominator tree, used for elaboration pass.
50 domtree: &'a DominatorTree,
51 /// Alias analysis, used during optimization.
52 alias_analysis: &'a mut AliasAnalysis<'a>,
53 /// "Domtree with children": like `domtree`, but with an explicit
54 /// list of children, rather than just parent pointers.
55 domtree_children: DomTreeWithChildren,
56 /// Loop analysis results, used for built-in LICM during
57 /// elaboration.
58 loop_analysis: &'a LoopAnalysis,
59 /// Which canonical Values do we want to rematerialize in each
60 /// block where they're used?
61 ///
62 /// (A canonical Value is the *oldest* Value in an eclass,
63 /// i.e. tree of union value-nodes).
64 remat_values: FxHashSet<Value>,
65 /// Stats collected while we run this pass.
66 pub(crate) stats: Stats,
67 /// Union-find that maps all members of a Union tree (eclass) back
68 /// to the *oldest* (lowest-numbered) `Value`.
69 eclasses: UnionFind<Value>,
70}
71
72/// Context passed through node insertion and optimization.
73pub(crate) struct OptimizeCtx<'opt, 'analysis>
74where
75 'analysis: 'opt,
76{
77 // Borrowed from EgraphPass:
78 pub(crate) func: &'opt mut Function,
79 pub(crate) value_to_opt_value: &'opt mut SecondaryMap<Value, Value>,
80 pub(crate) gvn_map: &'opt mut CtxHashMap<(Type, InstructionData), Value>,
81 pub(crate) effectful_gvn_map: &'opt mut ScopedHashMap<(Type, InstructionData), Value>,
82 pub(crate) eclasses: &'opt mut UnionFind<Value>,
83 pub(crate) remat_values: &'opt mut FxHashSet<Value>,
84 pub(crate) stats: &'opt mut Stats,
85 pub(crate) alias_analysis: &'opt mut AliasAnalysis<'analysis>,
86 pub(crate) alias_analysis_state: &'opt mut LastStores,
87 // Held locally during optimization of one node (recursively):
88 pub(crate) rewrite_depth: usize,
89 pub(crate) subsume_values: FxHashSet<Value>,
90}
91
92/// For passing to `insert_pure_enode`. Sometimes the enode already
93/// exists as an Inst (from the original CLIF), and sometimes we're in
94/// the middle of creating it and want to avoid inserting it if
95/// possible until we know we need it.
96pub(crate) enum NewOrExistingInst {
97 New(InstructionData, Type),
98 Existing(Inst),
99}
100
101impl NewOrExistingInst {
102 fn get_inst_key<'a>(&'a self, dfg: &'a DataFlowGraph) -> (Type, InstructionData) {
103 match self {
104 NewOrExistingInst::New(data, ty) => (*ty, *data),
105 NewOrExistingInst::Existing(inst) => {
106 let ty = dfg.ctrl_typevar(*inst);
107 (ty, dfg.insts[*inst].clone())
108 }
109 }
110 }
111}
112
113impl<'opt, 'analysis> OptimizeCtx<'opt, 'analysis>
114where
115 'analysis: 'opt,
116{
117 /// Optimization of a single instruction.
118 ///
119 /// This does a few things:
120 /// - Looks up the instruction in the GVN deduplication map. If we
121 /// already have the same instruction somewhere else, with the
122 /// same args, then we can alias the original instruction's
123 /// results and omit this instruction entirely.
124 /// - Note that we do this canonicalization based on the
125 /// instruction with its arguments as *canonical* eclass IDs,
126 /// that is, the oldest (smallest index) `Value` reachable in
127 /// the tree-of-unions (whole eclass). This ensures that we
128 /// properly canonicalize newer nodes that use newer "versions"
129 /// of a value that are still equal to the older versions.
130 /// - If the instruction is "new" (not deduplicated), then apply
131 /// optimization rules:
132 /// - All of the mid-end rules written in ISLE.
133 /// - Store-to-load forwarding.
134 /// - Update the value-to-opt-value map, and update the eclass
135 /// union-find, if we rewrote the value to different form(s).
136 pub(crate) fn insert_pure_enode(&mut self, inst: NewOrExistingInst) -> Value {
137 // Create the external context for looking up and updating the
138 // GVN map. This is necessary so that instructions themselves
139 // do not have to carry all the references or data for a full
140 // `Eq` or `Hash` impl.
141 let gvn_context = GVNContext {
142 union_find: self.eclasses,
143 value_lists: &self.func.dfg.value_lists,
144 };
145
146 self.stats.pure_inst += 1;
147 if let NewOrExistingInst::New(..) = inst {
148 self.stats.new_inst += 1;
149 }
150
151 // Does this instruction already exist? If so, add entries to
152 // the value-map to rewrite uses of its results to the results
153 // of the original (existing) instruction. If not, optimize
154 // the new instruction.
155 if let Some(&orig_result) = self
156 .gvn_map
157 .get(&inst.get_inst_key(&self.func.dfg), &gvn_context)
158 {
159 self.stats.pure_inst_deduped += 1;
160 if let NewOrExistingInst::Existing(inst) = inst {
161 debug_assert_eq!(self.func.dfg.inst_results(inst).len(), 1);
162 let result = self.func.dfg.first_result(inst);
163 self.value_to_opt_value[result] = orig_result;
164 self.eclasses.union(result, orig_result);
165 self.stats.union += 1;
166 result
167 } else {
168 orig_result
169 }
170 } else {
171 // Now actually insert the InstructionData and attach
172 // result value (exactly one).
173 let (inst, result, ty) = match inst {
174 NewOrExistingInst::New(data, typevar) => {
175 let inst = self.func.dfg.make_inst(data);
176 // TODO: reuse return value?
177 self.func.dfg.make_inst_results(inst, typevar);
178 let result = self.func.dfg.first_result(inst);
179 // Add to eclass unionfind.
180 self.eclasses.add(result);
181 // New inst. We need to do the analysis of its result.
182 (inst, result, typevar)
183 }
184 NewOrExistingInst::Existing(inst) => {
185 let result = self.func.dfg.first_result(inst);
186 let ty = self.func.dfg.ctrl_typevar(inst);
187 (inst, result, ty)
188 }
189 };
190
191 let opt_value = self.optimize_pure_enode(inst);
192 let gvn_context = GVNContext {
193 union_find: self.eclasses,
194 value_lists: &self.func.dfg.value_lists,
195 };
196 self.gvn_map.insert(
197 (ty, self.func.dfg.insts[inst].clone()),
198 opt_value,
199 &gvn_context,
200 );
201 self.value_to_opt_value[result] = opt_value;
202 opt_value
203 }
204 }
205
206 /// Optimizes an enode by applying any matching mid-end rewrite
207 /// rules (or store-to-load forwarding, which is a special case),
208 /// unioning together all possible optimized (or rewritten) forms
209 /// of this expression into an eclass and returning the `Value`
210 /// that represents that eclass.
211 fn optimize_pure_enode(&mut self, inst: Inst) -> Value {
212 // A pure node always has exactly one result.
213 let orig_value = self.func.dfg.first_result(inst);
214
215 let mut isle_ctx = IsleContext { ctx: self };
216
217 // Limit rewrite depth. When we apply optimization rules, they
218 // may create new nodes (values) and those are, recursively,
219 // optimized eagerly as soon as they are created. So we may
220 // have more than one ISLE invocation on the stack. (This is
221 // necessary so that as the toplevel builds the
222 // right-hand-side expression bottom-up, it uses the "latest"
223 // optimized values for all the constituent parts.) To avoid
224 // infinite or problematic recursion, we bound the rewrite
225 // depth to a small constant here.
226 const REWRITE_LIMIT: usize = 5;
227 if isle_ctx.ctx.rewrite_depth > REWRITE_LIMIT {
228 isle_ctx.ctx.stats.rewrite_depth_limit += 1;
229 return orig_value;
230 }
231 isle_ctx.ctx.rewrite_depth += 1;
232
233 // Invoke the ISLE toplevel constructor, getting all new
234 // values produced as equivalents to this value.
235 trace!("Calling into ISLE with original value {}", orig_value);
236 isle_ctx.ctx.stats.rewrite_rule_invoked += 1;
237 let mut optimized_values =
238 crate::opts::generated_code::constructor_simplify(&mut isle_ctx, orig_value);
239
240 // Create a union of all new values with the original (or
241 // maybe just one new value marked as "subsuming" the
242 // original, if present.)
243 let mut union_value = orig_value;
244 while let Some(optimized_value) = optimized_values.next(&mut isle_ctx) {
245 trace!(
246 "Returned from ISLE for {}, got {:?}",
247 orig_value,
248 optimized_value
249 );
250 if optimized_value == orig_value {
251 trace!(" -> same as orig value; skipping");
252 continue;
253 }
254 if isle_ctx.ctx.subsume_values.contains(&optimized_value) {
255 // Merge in the unionfind so canonicalization
256 // still works, but take *only* the subsuming
257 // value, and break now.
258 isle_ctx.ctx.eclasses.union(optimized_value, union_value);
259 union_value = optimized_value;
260 break;
261 }
262
263 let old_union_value = union_value;
264 union_value = isle_ctx
265 .ctx
266 .func
267 .dfg
268 .union(old_union_value, optimized_value);
269 isle_ctx.ctx.stats.union += 1;
270 trace!(" -> union: now {}", union_value);
271 isle_ctx.ctx.eclasses.add(union_value);
272 isle_ctx
273 .ctx
274 .eclasses
275 .union(old_union_value, optimized_value);
276 isle_ctx.ctx.eclasses.union(old_union_value, union_value);
277 }
278
279 isle_ctx.ctx.rewrite_depth -= 1;
280
281 union_value
282 }
283
284 /// Optimize a "skeleton" instruction, possibly removing
285 /// it. Returns `true` if the instruction should be removed from
286 /// the layout.
287 fn optimize_skeleton_inst(&mut self, inst: Inst) -> bool {
288 self.stats.skeleton_inst += 1;
289
290 // First, can we try to deduplicate? We need to keep some copy
291 // of the instruction around because it's side-effecting, but
292 // we may be able to reuse an earlier instance of it.
293 if is_mergeable_for_egraph(self.func, inst) {
294 let result = self.func.dfg.inst_results(inst)[0];
295 trace!(" -> mergeable side-effecting op {}", inst);
296
297 // Does this instruction already exist? If so, add entries to
298 // the value-map to rewrite uses of its results to the results
299 // of the original (existing) instruction. If not, optimize
300 // the new instruction.
301 //
302 // Note that we use the "effectful GVN map", which is
303 // scoped: because effectful ops are not removed from the
304 // skeleton (`Layout`), we need to be mindful of whether
305 // our current position is dominated by an instance of the
306 // instruction. (See #5796 for details.)
307 let ty = self.func.dfg.ctrl_typevar(inst);
308 match self
309 .effectful_gvn_map
310 .entry((ty, self.func.dfg.insts[inst].clone()))
311 {
312 ScopedEntry::Occupied(o) => {
313 let orig_result = *o.get();
314 // Hit in GVN map -- reuse value.
315 self.value_to_opt_value[result] = orig_result;
316 self.eclasses.union(orig_result, result);
317 trace!(" -> merges result {} to {}", result, orig_result);
318 true
319 }
320 ScopedEntry::Vacant(v) => {
321 // Otherwise, insert it into the value-map.
322 self.value_to_opt_value[result] = result;
323 v.insert(result);
324 trace!(" -> inserts as new (no GVN)");
325 false
326 }
327 }
328 }
329 // Otherwise, if a load or store, process it with the alias
330 // analysis to see if we can optimize it (rewrite in terms of
331 // an earlier load or stored value).
332 else if let Some(new_result) =
333 self.alias_analysis
334 .process_inst(self.func, self.alias_analysis_state, inst)
335 {
336 self.stats.alias_analysis_removed += 1;
337 let result = self.func.dfg.first_result(inst);
338 trace!(
339 " -> inst {} has result {} replaced with {}",
340 inst,
341 result,
342 new_result
343 );
344 self.value_to_opt_value[result] = new_result;
345 true
346 }
347 // Otherwise, generic side-effecting op -- always keep it, and
348 // set its results to identity-map to original values.
349 else {
350 // Set all results to identity-map to themselves
351 // in the value-to-opt-value map.
352 for &result in self.func.dfg.inst_results(inst) {
353 self.value_to_opt_value[result] = result;
354 self.eclasses.add(result);
355 }
356 false
357 }
358 }
359}
360
361impl<'a> EgraphPass<'a> {
362 /// Create a new EgraphPass.
363 pub fn new(
364 func: &'a mut Function,
365 domtree: &'a DominatorTree,
366 loop_analysis: &'a LoopAnalysis,
367 alias_analysis: &'a mut AliasAnalysis<'a>,
368 ) -> Self {
369 let num_values = func.dfg.num_values();
370 let domtree_children = DomTreeWithChildren::new(func, domtree);
371 Self {
372 func,
373 domtree,
374 domtree_children,
375 loop_analysis,
376 alias_analysis,
377 stats: Stats::default(),
378 eclasses: UnionFind::with_capacity(num_values),
379 remat_values: FxHashSet::default(),
380 }
381 }
382
383 /// Run the process.
384 pub fn run(&mut self) {
385 self.remove_pure_and_optimize();
386
387 trace!("egraph built:\n{}\n", self.func.display());
388 if cfg!(feature = "trace-log") {
389 for (value, def) in self.func.dfg.values_and_defs() {
390 trace!(" -> {} = {:?}", value, def);
391 match def {
392 ValueDef::Result(i, 0) => {
393 trace!(" -> {} = {:?}", i, self.func.dfg.insts[i]);
394 }
395 _ => {}
396 }
397 }
398 }
399 trace!("stats: {:?}", self.stats);
400 self.elaborate();
401 }
402
403 /// Remove pure nodes from the `Layout` of the function, ensuring
404 /// that only the "side-effect skeleton" remains, and also
405 /// optimize the pure nodes. This is the first step of
406 /// egraph-based processing and turns the pure CFG-based CLIF into
407 /// a CFG skeleton with a sea of (optimized) nodes tying it
408 /// together.
409 ///
410 /// As we walk through the code, we eagerly apply optimization
411 /// rules; at any given point we have a "latest version" of an
412 /// eclass of possible representations for a `Value` in the
413 /// original program, which is itself a `Value` at the root of a
414 /// union-tree. We keep a map from the original values to these
415 /// optimized values. When we encounter any instruction (pure or
416 /// side-effecting skeleton) we rewrite its arguments to capture
417 /// the "latest" optimized forms of these values. (We need to do
418 /// this as part of this pass, and not later using a finished map,
419 /// because the eclass can continue to be updated and we need to
420 /// only refer to its subset that exists at this stage, to
421 /// maintain acyclicity.)
422 fn remove_pure_and_optimize(&mut self) {
423 let mut cursor = FuncCursor::new(self.func);
424 let mut value_to_opt_value: SecondaryMap<Value, Value> =
425 SecondaryMap::with_default(Value::reserved_value());
426 // Map from instruction to value for hash-consing of pure ops
427 // into the egraph. This can be a standard (non-scoped)
428 // hashmap because pure ops have no location: they are
429 // "outside of" control flow.
430 //
431 // Note also that we keep the controlling typevar (the `Type`
432 // in the tuple below) because it may disambiguate
433 // instructions that are identical except for type.
434 let mut gvn_map: CtxHashMap<(Type, InstructionData), Value> =
435 CtxHashMap::with_capacity(cursor.func.dfg.num_values());
436 // Map from instruction to value for GVN'ing of effectful but
437 // idempotent ops, which remain in the side-effecting
438 // skeleton. This needs to be scoped because we cannot
439 // deduplicate one instruction to another that is in a
440 // non-dominating block.
441 //
442 // Note that we can use a ScopedHashMap here without the
443 // "context" (as needed by CtxHashMap) because in practice the
444 // ops we want to GVN have all their args inline. Equality on
445 // the InstructionData itself is conservative: two insts whose
446 // struct contents compare shallowly equal are definitely
447 // identical, but identical insts in a deep-equality sense may
448 // not compare shallowly equal, due to list indirection. This
449 // is fine for GVN, because it is still sound to skip any
450 // given GVN opportunity (and keep the original instructions).
451 //
452 // As above, we keep the controlling typevar here as part of
453 // the key: effectful instructions may (as for pure
454 // instructions) be differentiated only on the type.
455 let mut effectful_gvn_map: ScopedHashMap<(Type, InstructionData), Value> =
456 ScopedHashMap::new();
457
458 // In domtree preorder, visit blocks. (TODO: factor out an
459 // iterator from this and elaborator.)
460 let root = self.domtree_children.root();
461 enum StackEntry {
462 Visit(Block),
463 Pop,
464 }
465 let mut block_stack = vec![StackEntry::Visit(root)];
466 while let Some(entry) = block_stack.pop() {
467 match entry {
468 StackEntry::Visit(block) => {
469 // We popped this block; push children
470 // immediately, then process this block.
471 block_stack.push(StackEntry::Pop);
472 block_stack
473 .extend(self.domtree_children.children(block).map(StackEntry::Visit));
474 effectful_gvn_map.increment_depth();
475
476 trace!("Processing block {}", block);
477 cursor.set_position(CursorPosition::Before(block));
478
479 let mut alias_analysis_state = self.alias_analysis.block_starting_state(block);
480
481 for ¶m in cursor.func.dfg.block_params(block) {
482 trace!("creating initial singleton eclass for blockparam {}", param);
483 self.eclasses.add(param);
484 value_to_opt_value[param] = param;
485 }
486 while let Some(inst) = cursor.next_inst() {
487 trace!("Processing inst {}", inst);
488
489 // While we're passing over all insts, create initial
490 // singleton eclasses for all result and blockparam
491 // values. Also do initial analysis of all inst
492 // results.
493 for &result in cursor.func.dfg.inst_results(inst) {
494 trace!("creating initial singleton eclass for {}", result);
495 self.eclasses.add(result);
496 }
497
498 // Rewrite args of *all* instructions using the
499 // value-to-opt-value map.
500 cursor.func.dfg.resolve_aliases_in_arguments(inst);
501 cursor.func.dfg.map_inst_values(inst, |_, arg| {
502 let new_value = value_to_opt_value[arg];
503 trace!("rewriting arg {} of inst {} to {}", arg, inst, new_value);
504 debug_assert_ne!(new_value, Value::reserved_value());
505 new_value
506 });
507
508 // Build a context for optimization, with borrows of
509 // state. We can't invoke a method on `self` because
510 // we've borrowed `self.func` mutably (as
511 // `cursor.func`) so we pull apart the pieces instead
512 // here.
513 let mut ctx = OptimizeCtx {
514 func: cursor.func,
515 value_to_opt_value: &mut value_to_opt_value,
516 gvn_map: &mut gvn_map,
517 effectful_gvn_map: &mut effectful_gvn_map,
518 eclasses: &mut self.eclasses,
519 rewrite_depth: 0,
520 subsume_values: FxHashSet::default(),
521 remat_values: &mut self.remat_values,
522 stats: &mut self.stats,
523 alias_analysis: self.alias_analysis,
524 alias_analysis_state: &mut alias_analysis_state,
525 };
526
527 if is_pure_for_egraph(ctx.func, inst) {
528 // Insert into GVN map and optimize any new nodes
529 // inserted (recursively performing this work for
530 // any nodes the optimization rules produce).
531 let inst = NewOrExistingInst::Existing(inst);
532 ctx.insert_pure_enode(inst);
533 // We've now rewritten all uses, or will when we
534 // see them, and the instruction exists as a pure
535 // enode in the eclass, so we can remove it.
536 cursor.remove_inst_and_step_back();
537 } else {
538 if ctx.optimize_skeleton_inst(inst) {
539 cursor.remove_inst_and_step_back();
540 }
541 }
542 }
543 }
544 StackEntry::Pop => {
545 effectful_gvn_map.decrement_depth();
546 }
547 }
548 }
549 }
550
551 /// Scoped elaboration: compute a final ordering of op computation
552 /// for each block and update the given Func body. After this
553 /// runs, the function body is back into the state where every
554 /// Inst with an used result is placed in the layout (possibly
555 /// duplicated, if our code-motion logic decides this is the best
556 /// option).
557 ///
558 /// This works in concert with the domtree. We do a preorder
559 /// traversal of the domtree, tracking a scoped map from Id to
560 /// (new) Value. The map's scopes correspond to levels in the
561 /// domtree.
562 ///
563 /// At each block, we iterate forward over the side-effecting
564 /// eclasses, and recursively generate their arg eclasses, then
565 /// emit the ops themselves.
566 ///
567 /// To use an eclass in a given block, we first look it up in the
568 /// scoped map, and get the Value if already present. If not, we
569 /// need to generate it. We emit the extracted enode for this
570 /// eclass after recursively generating its args. Eclasses are
571 /// thus computed "as late as possible", but then memoized into
572 /// the Id-to-Value map and available to all dominated blocks and
573 /// for the rest of this block. (This subsumes GVN.)
574 fn elaborate(&mut self) {
575 let mut elaborator = Elaborator::new(
576 self.func,
577 self.domtree,
578 &self.domtree_children,
579 self.loop_analysis,
580 &mut self.remat_values,
581 &mut self.eclasses,
582 &mut self.stats,
583 );
584 elaborator.elaborate();
585
586 self.check_post_egraph();
587 }
588
589 #[cfg(debug_assertions)]
590 fn check_post_egraph(&self) {
591 // Verify that no union nodes are reachable from inst args,
592 // and that all inst args' defining instructions are in the
593 // layout.
594 for block in self.func.layout.blocks() {
595 for inst in self.func.layout.block_insts(block) {
596 self.func
597 .dfg
598 .inst_values(inst)
599 .for_each(|arg| match self.func.dfg.value_def(arg) {
600 ValueDef::Result(i, _) => {
601 debug_assert!(self.func.layout.inst_block(i).is_some());
602 }
603 ValueDef::Union(..) => {
604 panic!("egraph union node {} still reachable at {}!", arg, inst);
605 }
606 _ => {}
607 })
608 }
609 }
610 }
611
612 #[cfg(not(debug_assertions))]
613 fn check_post_egraph(&self) {}
614}
615
616/// Implementation of external-context equality and hashing on
617/// InstructionData. This allows us to deduplicate instructions given
618/// some context that lets us see its value lists and the mapping from
619/// any value to "canonical value" (in an eclass).
620struct GVNContext<'a> {
621 value_lists: &'a ValueListPool,
622 union_find: &'a UnionFind<Value>,
623}
624
625impl<'a> CtxEq<(Type, InstructionData), (Type, InstructionData)> for GVNContext<'a> {
626 fn ctx_eq(
627 &self,
628 (a_ty, a_inst): &(Type, InstructionData),
629 (b_ty, b_inst): &(Type, InstructionData),
630 ) -> bool {
631 a_ty == b_ty
632 && a_inst.eq(b_inst, self.value_lists, |value| {
633 self.union_find.find(value)
634 })
635 }
636}
637
638impl<'a> CtxHash<(Type, InstructionData)> for GVNContext<'a> {
639 fn ctx_hash<H: Hasher>(&self, state: &mut H, (ty, inst): &(Type, InstructionData)) {
640 std::hash::Hash::hash(&ty, state);
641 inst.hash(state, self.value_lists, |value| self.union_find.find(value));
642 }
643}
644
645/// Statistics collected during egraph-based processing.
646#[derive(Clone, Debug, Default)]
647pub(crate) struct Stats {
648 pub(crate) pure_inst: u64,
649 pub(crate) pure_inst_deduped: u64,
650 pub(crate) skeleton_inst: u64,
651 pub(crate) alias_analysis_removed: u64,
652 pub(crate) new_inst: u64,
653 pub(crate) union: u64,
654 pub(crate) subsume: u64,
655 pub(crate) remat: u64,
656 pub(crate) rewrite_rule_invoked: u64,
657 pub(crate) rewrite_depth_limit: u64,
658 pub(crate) elaborate_visit_node: u64,
659 pub(crate) elaborate_memoize_hit: u64,
660 pub(crate) elaborate_memoize_miss: u64,
661 pub(crate) elaborate_memoize_miss_remat: u64,
662 pub(crate) elaborate_licm_hoist: u64,
663 pub(crate) elaborate_func: u64,
664 pub(crate) elaborate_func_pre_insts: u64,
665 pub(crate) elaborate_func_post_insts: u64,
666}