Skip to content

Commit 068ffd0

Browse files
committed
Take block priority into account when merging bundles
Each merge eliminates the need for one move instruction in the final program. However a successful merge can cause a future merge to fail due to interference. Therefore we want to prioritize merges that have the highest impact: * We want to eliminate moves in loops that are executed many times. * We want to eliminate moves in split critical edge blocks, since it allows the entire block to be eliminated with jump threading.
1 parent d82fec1 commit 068ffd0

1 file changed

Lines changed: 88 additions & 31 deletions

File tree

src/ion/merge.rs

Lines changed: 88 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@
1414
1515
use super::{Env, LiveBundleIndex, SpillSet, SpillSlotIndex, VRegIndex};
1616
use crate::{
17-
ion::data_structures::{BlockparamOut, CodeRange},
18-
Function, Inst, OperandConstraint, OperandKind, PReg, ProgPoint,
17+
ion::data_structures::CodeRange, Block, Function, OperandConstraint, OperandKind, PReg,
18+
ProgPoint,
1919
};
20-
use alloc::format;
20+
use alloc::{format, vec::Vec};
2121
use smallvec::smallvec;
2222

2323
impl<'a, F: Function> Env<'a, F> {
24-
pub fn merge_bundles(&mut self, from: LiveBundleIndex, to: LiveBundleIndex) -> bool {
24+
fn merge_bundles(&mut self, from: LiveBundleIndex, to: LiveBundleIndex) -> bool {
2525
if from == to {
2626
// Merge bundle into self -- trivial merge.
2727
return true;
@@ -224,9 +224,8 @@ impl<'a, F: Function> Env<'a, F> {
224224
true
225225
}
226226

227-
pub fn merge_vreg_bundles(&mut self) {
228-
// Create a bundle for every vreg, initially.
229-
trace!("merge_vreg_bundles: creating vreg bundles");
227+
// Create a bundle for every vreg, initially.
228+
fn create_vreg_bundles(&mut self) {
230229
for vreg in 0..self.vregs.len() {
231230
let vreg = VRegIndex::new(vreg);
232231
if self.vregs[vreg].ranges.is_empty() {
@@ -290,12 +289,15 @@ impl<'a, F: Function> Env<'a, F> {
290289
});
291290
self.bundles[bundle].spillset = ssidx;
292291
}
292+
}
293293

294-
for inst in 0..self.func.num_insts() {
295-
let inst = Inst::new(inst);
294+
/// Merges bundles in the given block.
295+
fn merge_bundles_in_block(&mut self, block: Block) {
296+
let insns = self.func.block_insns(block);
296297

297-
// Attempt to merge Reuse-constraint operand outputs with the
298-
// corresponding inputs.
298+
// Attempt to merge Reuse-constraint operand outputs with the
299+
// corresponding inputs.
300+
for inst in insns.iter() {
299301
for op in self.func.inst_operands(inst) {
300302
if let OperandConstraint::Reuse(reuse_idx) = op.constraint() {
301303
let src_vreg = op.vreg();
@@ -315,26 +317,81 @@ impl<'a, F: Function> Env<'a, F> {
315317
}
316318
}
317319

318-
// Attempt to merge blockparams with their inputs.
319-
for i in 0..self.blockparam_outs.len() {
320-
let BlockparamOut {
321-
from_vreg, to_vreg, ..
322-
} = self.blockparam_outs[i];
323-
trace!(
324-
"trying to merge blockparam v{} with input v{}",
325-
to_vreg.index(),
326-
from_vreg.index()
327-
);
328-
let to_bundle = self.ranges[self.vregs[to_vreg].ranges[0].index].bundle;
329-
debug_assert!(to_bundle.is_valid());
330-
let from_bundle = self.ranges[self.vregs[from_vreg].ranges[0].index].bundle;
331-
debug_assert!(from_bundle.is_valid());
332-
trace!(
333-
" -> from bundle{} to bundle{}",
334-
from_bundle.index(),
335-
to_bundle.index()
336-
);
337-
self.merge_bundles(from_bundle, to_bundle);
320+
// Attempt to merge blockparams with their inputs from our predecessor.
321+
// Since critical edges are split we only have blockparams when we have
322+
// a single predecessor.
323+
if let &[pred] = self.func.block_preds(block) {
324+
// TODO: We're not passing the proper successor index here.
325+
let blockparams_in =
326+
self.func
327+
.branch_blockparams(pred, self.func.block_insns(pred).last(), 0);
328+
let blockparams_out = self.func.block_params(block);
329+
for (&blockparam_in, &blockparam_out) in blockparams_in.iter().zip(blockparams_out) {
330+
let from_vreg = VRegIndex::new(blockparam_out.vreg());
331+
let to_vreg = VRegIndex::new(blockparam_in.vreg());
332+
trace!(
333+
"trying to merge blockparam v{} with input v{}",
334+
to_vreg.index(),
335+
from_vreg.index()
336+
);
337+
let to_bundle = self.ranges[self.vregs[to_vreg].ranges[0].index].bundle;
338+
debug_assert!(to_bundle.is_valid());
339+
let from_bundle = self.ranges[self.vregs[from_vreg].ranges[0].index].bundle;
340+
debug_assert!(from_bundle.is_valid());
341+
trace!(
342+
" -> from bundle{} to bundle{}",
343+
from_bundle.index(),
344+
to_bundle.index()
345+
);
346+
self.merge_bundles(from_bundle, to_bundle);
347+
}
348+
}
349+
}
350+
351+
/// Get a list of blocks sorted by priority.
352+
///
353+
/// Each merge eliminates the need for one move instruction in the final
354+
/// program. However a successful merge can cause a future merge to fail
355+
/// due to interference. Therefore we want to prioritize merges that have
356+
/// the highest impact:
357+
///
358+
/// * We want to eliminate moves in loops that are executed many times.
359+
/// * We want to eliminate moves in split critical edge blocks, since it
360+
/// allows the entire block to be eliminated with jump threading.
361+
///
362+
/// The heuristic is based on `compareMBBPriority` from LLVM.
363+
fn blocks_by_merge_priority(&mut self) -> Vec<Block> {
364+
let loop_depth = |block: Block| self.cfginfo.approx_loop_depth[block.index()];
365+
let is_split_edge = |block: Block| {
366+
// A split critical edge is an artifical block which only exists for
367+
// the sake of passing blockparams to another block.
368+
self.func.block_insns(block).len() == 1 && self.func.block_succs(block).len() == 1
369+
};
370+
let num_connections =
371+
|block: Block| self.func.block_succs(block).len() + self.func.block_preds(block).len();
372+
373+
let mut blocks: Vec<_> = (0..self.func.num_blocks()).map(|i| Block::new(i)).collect();
374+
blocks.sort_by(|&a, &b| {
375+
// Prioritize deeper loops first.
376+
loop_depth(a)
377+
.cmp(&loop_depth(b))
378+
.reverse()
379+
// Prioritize critical edges to enable jump threading.
380+
.then_with(|| is_split_edge(a).cmp(&is_split_edge(b)).reverse())
381+
// Prioritize blocks which are more connected in the CFG.
382+
.then_with(|| num_connections(a).cmp(&num_connections(b)).reverse())
383+
// Otherwise follow the original block order.
384+
});
385+
blocks
386+
}
387+
388+
pub fn merge_vreg_bundles(&mut self) {
389+
trace!("merge_vreg_bundles: creating vreg bundles");
390+
391+
self.create_vreg_bundles();
392+
393+
for block in self.blocks_by_merge_priority() {
394+
self.merge_bundles_in_block(block);
338395
}
339396

340397
trace!("done merging bundles");

0 commit comments

Comments
 (0)