Skip to content

Commit 6c9d7c9

Browse files
committed
Add support for any, fixed-reg and stack-only branch arguments being defined in its branch instruction
1 parent 193ba9f commit 6c9d7c9

File tree

2 files changed

+135
-4
lines changed

2 files changed

+135
-4
lines changed

doc/FASTALLOC.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,10 @@ arguments will be in their dedicated spillslots.
128128
4. At the beginning of a block, all branch parameters and livein
129129
virtual registers will be in their dedicated spillslots.
130130

131+
There is an exception to invariant 3: if a branch instruction defines
132+
the VReg used as a branch arg, then there may be no opportunity for
133+
the VReg to be placed in its spillslot.
134+
131135
# Instruction Allocation
132136

133137
To allocate a single instruction, the first step is to reset the
@@ -283,6 +287,15 @@ It's after these edits have been inserted that the parallel move
283287
resolver is then used to generate and insert edits to move from
284288
those spillslots to the spillslots of the block parameters.
285289

290+
There is an exception to the invariant - it's possible that the
291+
branch argument is defined in the same branch instruction.
292+
If the branch argument VReg has a fixed-reg constraint, the move
293+
will have to be done in the successor.
294+
If it has an stack or anywhere constraint, it is allocated directly
295+
into the block param's spillslot, so there is no need to insert moves.
296+
The other constraints, reuse and any-reg, are not supported in this
297+
case.
298+
286299
# Across Blocks
287300

288301
When a block completes processing, some VRegs will still be live.
@@ -297,6 +310,20 @@ to be in from the first instruction.
297310
All block parameters are freed, just like defs, and liveins' current
298311
allocations in `vreg_allocs` are set to their spillslots.
299312

313+
Any block parameter that receives a branch argument from a predecessor
314+
where the argument VReg was defined in the branch instruction will
315+
also need moves inserted at the block beginning because the predecessor
316+
couldn't have inserted the required moves.
317+
All predecessors branch arguments to the block are checked to see if any
318+
are defined in the same branch instruction. For all branch arguments that
319+
are defined in the branch instruction and have fixed-reg constraints, a
320+
move will be inserted from the fixed-reg to the block param's spillslot
321+
at the beginning of the block. In the case of stack and anywhere constraints,
322+
nothing is done, because in that case, the VRegs used as the branch arguments
323+
will be defined directly into the block param's spillslot. Reuse and any-reg
324+
constraints are not supported and aren't handled.
325+
326+
300327
# Edits Order
301328

302329
`regalloc2`'s outward interface guarantees that edits are in

src/fastalloc/mod.rs

Lines changed: 108 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -827,6 +827,17 @@ impl<'a, F: Function> Env<'a, F> {
827827
.iter()
828828
.enumerate()
829829
{
830+
if self
831+
.func
832+
.inst_operands(inst)
833+
.iter()
834+
.find(|op| op.vreg() == *vreg && op.kind() == OperandKind::Def)
835+
.is_some()
836+
{
837+
// vreg is defined in this instruction, so it's dead already.
838+
// Can't move it.
839+
continue;
840+
}
830841
let succ_params = self.func.block_params(*succ);
831842
let succ_param_vreg = succ_params[pos];
832843
if self.vreg_spillslots[succ_param_vreg.vreg()].is_invalid() {
@@ -1061,6 +1072,41 @@ impl<'a, F: Function> Env<'a, F> {
10611072
Operand::new(op.vreg(), reused_op.constraint(), op.kind(), op.pos());
10621073
trace!("allocating reuse op {op} as {new_reuse_op}");
10631074
self.process_operand_allocation(inst, new_reuse_op, op_idx)?;
1075+
} else if self.func.is_branch(inst) {
1076+
// If the defined vreg is used as a branch arg and it has an
1077+
// any or stack constraint, define it into the block param spillslot
1078+
let mut param_spillslot = None;
1079+
'outer: for (succ_idx, succ) in
1080+
self.func.block_succs(block).iter().cloned().enumerate()
1081+
{
1082+
for (param_idx, branch_arg_vreg) in self
1083+
.func
1084+
.branch_blockparams(block, inst, succ_idx)
1085+
.iter()
1086+
.cloned()
1087+
.enumerate()
1088+
{
1089+
if op.vreg() == branch_arg_vreg {
1090+
if matches!(
1091+
op.constraint(),
1092+
OperandConstraint::Any | OperandConstraint::Stack
1093+
) {
1094+
let block_param = self.func.block_params(succ)[param_idx];
1095+
param_spillslot = Some(self.get_spillslot(block_param));
1096+
}
1097+
break 'outer;
1098+
}
1099+
}
1100+
}
1101+
if let Some(param_spillslot) = param_spillslot {
1102+
let spillslot = self.vreg_spillslots[op.vreg().vreg()];
1103+
self.vreg_spillslots[op.vreg().vreg()] = param_spillslot;
1104+
let op = Operand::new(op.vreg(), OperandConstraint::Stack, op.kind(), op.pos());
1105+
self.process_operand_allocation(inst, op, op_idx)?;
1106+
self.vreg_spillslots[op.vreg().vreg()] = spillslot;
1107+
} else {
1108+
self.process_operand_allocation(inst, op, op_idx)?;
1109+
}
10641110
} else {
10651111
self.process_operand_allocation(inst, op, op_idx)?;
10661112
}
@@ -1159,13 +1205,10 @@ impl<'a, F: Function> Env<'a, F> {
11591205
// be none at this point.
11601206
continue;
11611207
}
1162-
if self.vreg_spillslots[vreg.vreg()].is_invalid() {
1163-
self.vreg_spillslots[vreg.vreg()] = self.stack.allocstack(vreg.class());
1164-
}
11651208
// The allocation where the vreg is expected to be before
11661209
// the first instruction.
11671210
let prev_alloc = self.vreg_allocs[vreg.vreg()];
1168-
let slot = Allocation::stack(self.vreg_spillslots[vreg.vreg()]);
1211+
let slot = Allocation::stack(self.get_spillslot(vreg));
11691212
self.vreg_to_live_inst_range[vreg.vreg()].2 = slot;
11701213
self.vreg_to_live_inst_range[vreg.vreg()].0 = ProgPoint::before(first_inst);
11711214
trace!("{} is a block param. Freeing it", vreg);
@@ -1260,6 +1303,67 @@ impl<'a, F: Function> Env<'a, F> {
12601303
InstPosition::Before,
12611304
);
12621305
}
1306+
// Reset this, in case a fixed reg used by a branch arg defined on the branch
1307+
// is used as a scratch reg in the previous loop.
1308+
self.edits.scratch_regs = self.edits.dedicated_scratch_regs.clone();
1309+
1310+
let get_succ_idx_of_pred = |pred, func: &F| {
1311+
for (idx, pred_succ) in func.block_succs(pred).iter().enumerate() {
1312+
if *pred_succ == block {
1313+
return idx;
1314+
}
1315+
}
1316+
unreachable!(
1317+
"{:?} was not found in the successor list of its predecessor {:?}",
1318+
block, pred
1319+
);
1320+
};
1321+
trace!(
1322+
"Checking for predecessor branch args defined in the branch with fixed-reg constraint"
1323+
);
1324+
for (param_idx, block_param) in self.func.block_params(block).iter().cloned().enumerate() {
1325+
// Block param is never used. Don't bother.
1326+
if self.vreg_spillslots[block_param.vreg()].is_invalid() {
1327+
continue;
1328+
}
1329+
for pred in self.func.block_preds(block).iter().cloned() {
1330+
let pred_last_inst = self.func.block_insns(pred).last();
1331+
let curr_block_succ_idx = get_succ_idx_of_pred(pred, self.func);
1332+
let branch_arg_for_param =
1333+
self.func
1334+
.branch_blockparams(pred, pred_last_inst, curr_block_succ_idx)[param_idx];
1335+
// If the branch arg is defined in the branch instruction, the move will have to be done
1336+
// here, instead of at the end of the predecessor block.
1337+
let move_from = self.func.inst_operands(pred_last_inst)
1338+
.iter()
1339+
.find_map(|op| if op.kind() == OperandKind::Def && op.vreg() == branch_arg_for_param {
1340+
match op.constraint() {
1341+
OperandConstraint::FixedReg(reg) => {
1342+
trace!("Found one for branch arg {branch_arg_for_param} and param {block_param} in reg {reg}");
1343+
Some(Allocation::reg(reg))
1344+
},
1345+
// In these cases, the vreg is defined directly into the block param
1346+
// spillslot.
1347+
OperandConstraint::Stack | OperandConstraint::Any => None,
1348+
constraint => panic!("fastalloc does not support using any-reg or reuse constraints ({}) defined on a branch instruction as a branch arg on the same instruction", constraint),
1349+
}
1350+
} else {
1351+
None
1352+
});
1353+
if let Some(from) = move_from {
1354+
let to = Allocation::stack(self.vreg_spillslots[block_param.vreg()]);
1355+
trace!("Inserting edit to move from {from} to {to}");
1356+
self.add_move(
1357+
first_inst,
1358+
from,
1359+
to,
1360+
block_param.class(),
1361+
InstPosition::Before,
1362+
)?;
1363+
}
1364+
break;
1365+
}
1366+
}
12631367
if trace_enabled!() {
12641368
self.log_post_reload_at_begin_state(block);
12651369
}

0 commit comments

Comments
 (0)