Skip to content

Commit baa6b27

Browse files
authored
Cranelift: rework MachBuffer to handle very short-deadline jumps. (#12842)
* Cranelift: rework MachBuffer to handle very short-deadline jumps. In #12811 it was reported that riscv64 compressed jumps (`c.j` instructions), with a +/- 2048-byte range, could cause panics when combined with queued-up/deferred constants in a constant pool during binary emission. Our `MachBuffer` handles single-pass machine code emission, resolution of labels, and upgrading of label ranges via "veneers" (jumps that a shorter jump can reach that themselves have a longer range). We track a pending "deadline" of all unresolved branches, and when the deadline is too close (including the max size of all veneers yet to be emitted), we emit an "island" of all veneers to resolve the deadline. After its initial design, we added support for deferred traps and constants to the `MachBuffer`. These worked by emitting their contents *before* the "island" of veneers, which turns out to be slightly nicer for code layout in some cases. Unfortunately the full implications of those additions weren't realized against the invariants of the deadline-resolution algorithm. In particular, when a new branch is added with a very short range (e.g., `c.j`), it is possible that there are *already* too many queued-up traps/constants for the range of that just-emitted branch to reach even the first possible veneer site if we start an island right away. Thus it is strictly necessary to emit the veneers before constants/traps. Unfortunately this requires some alterations to other aspects of label resolution as well: in particular, we can't resolve fixups for label references to constants before we emit those constants, and likewise for traps. Note that we do a fixpoint loop over emitting island(s) at the end of emission, so all constants/traps *will* be emitted and label references to them *will* be resolved eventually; just in the opposite order, now. No compile test because the particular reduced testcase in #12811 only worked in the `release-36.0.0` branch, and not on `main`, and it was too hard to tweak the test to hit the right case on `main` as well. In lieu of that, I've added a unit test directly to the `MachBuffer` implementation to exercise this case. Fixes #12811. * fix filetest with errant comments confusing precise-output check
1 parent 82ebbd5 commit baa6b27

2 files changed

Lines changed: 8362 additions & 34 deletions

File tree

cranelift/codegen/src/machinst/buffer.rs

Lines changed: 89 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -771,7 +771,12 @@ impl<I: VCodeInst> MachBuffer<I> {
771771
offset,
772772
kind,
773773
};
774-
self.pending_fixup_deadline = self.pending_fixup_deadline.min(fixup.deadline());
774+
self.pending_fixup_deadline = self
775+
.pending_fixup_deadline
776+
// Subtract one alignment here to the deadline to account for
777+
// extra space taken by aligning an island.
778+
.min(fixup.deadline() - I::LabelUse::ALIGN);
779+
trace!("pending_fixup_deadline = {}", self.pending_fixup_deadline);
775780
self.pending_fixup_records.push(fixup);
776781

777782
// Post-invariant: no mutations to branches/labels data structures.
@@ -1280,7 +1285,15 @@ impl<I: VCodeInst> MachBuffer<I> {
12801285
Some(fixup) => fixup.deadline().min(self.pending_fixup_deadline),
12811286
None => self.pending_fixup_deadline,
12821287
};
1283-
deadline < u32::MAX && self.worst_case_end_of_island(distance) > deadline
1288+
trace!(
1289+
"checking island_needed: cur_offset = {} deadline = {} worst_case_end_of_island = {}",
1290+
self.cur_offset(),
1291+
deadline,
1292+
self.worst_case_end_of_island(distance)
1293+
);
1294+
let needed = deadline < u32::MAX && self.worst_case_end_of_island(distance) > deadline;
1295+
trace!(" -> needed = {needed}");
1296+
needed
12841297
}
12851298

12861299
/// Returns the maximal offset that islands can reach if `distance` more
@@ -1320,6 +1333,11 @@ impl<I: VCodeInst> MachBuffer<I> {
13201333
distance: CodeOffset,
13211334
ctrl_plane: &mut ControlPlane,
13221335
) {
1336+
trace!(
1337+
"emitting island at {}, distance = {distance}",
1338+
self.cur_offset()
1339+
);
1340+
13231341
// We're going to purge fixups, so no latest-branch editing can happen
13241342
// anymore.
13251343
self.latest_branches.clear();
@@ -1336,41 +1354,29 @@ impl<I: VCodeInst> MachBuffer<I> {
13361354
}
13371355

13381356
let forced_threshold = self.worst_case_end_of_island(distance);
1339-
1340-
// First flush out all traps/constants so we have more labels in case
1341-
// fixups are applied against these labels.
1357+
trace!("forced_threshold = {forced_threshold}");
1358+
1359+
// Emit traps/constants after the island: with potentially
1360+
// unbounded pending constants/traps and potentially small
1361+
// deadlines, it would otherwise be possible to emit a
1362+
// small-range jump, have a nearby deadline *before* the end
1363+
// of pending constants/traps, and not be able to emit a
1364+
// veneer in time.
13421365
//
1343-
// Note that traps are placed first since this typically happens at the
1344-
// end of the function and for disassemblers we try to keep all the code
1345-
// contiguously together.
1346-
for MachLabelTrap { label, code, loc } in mem::take(&mut self.pending_traps) {
1347-
// If this trap has source information associated with it then
1348-
// emit this information for the trap instruction going out now too.
1349-
if let Some(loc) = loc {
1350-
self.start_srcloc(loc);
1351-
}
1352-
self.align_to(I::LabelUse::ALIGN);
1353-
self.bind_label(label, ctrl_plane);
1354-
self.add_trap(code);
1355-
self.put_data(I::TRAP_OPCODE);
1356-
if loc.is_some() {
1357-
self.end_srcloc();
1358-
}
1359-
}
1360-
1361-
for constant in mem::take(&mut self.pending_constants) {
1362-
let MachBufferConstant { align, size, .. } = self.constants[constant];
1363-
let label = self.constants[constant].upcoming_label.take().unwrap();
1364-
self.align_to(align);
1365-
self.bind_label(label, ctrl_plane);
1366-
self.used_constants.push((constant, self.cur_offset()));
1367-
self.get_appended_space(size);
1368-
}
1366+
// Fixups whose labels aren't yet defined (e.g. references to
1367+
// pending constants/traps) are simply deferred here; they'll
1368+
// be resolved in the next island or in the final fixup pass
1369+
// at the end of emission.
13691370

13701371
// Either handle all pending fixups because they're ready or move them
13711372
// onto the `BinaryHeap` tracking all pending fixups if they aren't
13721373
// ready.
13731374
assert!(self.latest_branches.is_empty());
1375+
trace!(
1376+
"About to handle fixups at offset {}: {:?}",
1377+
self.cur_offset(),
1378+
self.pending_fixup_records
1379+
);
13741380
for fixup in mem::take(&mut self.pending_fixup_records) {
13751381
if self.should_apply_fixup(&fixup, forced_threshold) {
13761382
self.handle_fixup(fixup, force_veneers, forced_threshold);
@@ -1380,7 +1386,11 @@ impl<I: VCodeInst> MachBuffer<I> {
13801386
}
13811387
self.pending_fixup_deadline = u32::MAX;
13821388
while let Some(fixup) = self.fixup_records.peek() {
1383-
trace!("emit_island: fixup {:?}", fixup);
1389+
trace!(
1390+
"emit_island: fixup {:?} deadline {}",
1391+
fixup,
1392+
fixup.deadline()
1393+
);
13841394

13851395
// If this fixup shouldn't be applied, that means its label isn't
13861396
// defined yet and there'll be remaining space to apply a veneer if
@@ -1395,14 +1405,58 @@ impl<I: VCodeInst> MachBuffer<I> {
13951405
self.handle_fixup(fixup, force_veneers, forced_threshold);
13961406
}
13971407

1408+
// Now emit pending traps and constants.
1409+
//
1410+
// Note that traps are placed first since this typically happens at the
1411+
// end of the function and for disassemblers we try to keep all the code
1412+
// contiguously together.
1413+
trace!("emitting pending traps: {:?}", self.pending_traps);
1414+
for MachLabelTrap { label, code, loc } in mem::take(&mut self.pending_traps) {
1415+
// If this trap has source information associated with it then
1416+
// emit this information for the trap instruction going out now too.
1417+
if let Some(loc) = loc {
1418+
self.start_srcloc(loc);
1419+
}
1420+
self.align_to(I::LabelUse::ALIGN);
1421+
self.bind_label(label, ctrl_plane);
1422+
self.add_trap(code);
1423+
self.put_data(I::TRAP_OPCODE);
1424+
if loc.is_some() {
1425+
self.end_srcloc();
1426+
}
1427+
}
1428+
1429+
trace!("emitting pending constants: {:?}", self.pending_constants);
1430+
for constant in mem::take(&mut self.pending_constants) {
1431+
let MachBufferConstant { align, size, .. } = self.constants[constant];
1432+
let label = self.constants[constant].upcoming_label.take().unwrap();
1433+
self.align_to(align);
1434+
self.bind_label(label, ctrl_plane);
1435+
self.used_constants.push((constant, self.cur_offset()));
1436+
self.get_appended_space(size);
1437+
}
1438+
13981439
if let Some(loc) = cur_loc {
13991440
self.start_srcloc(loc);
14001441
}
14011442
}
14021443

14031444
fn should_apply_fixup(&self, fixup: &MachLabelFixup<I>, forced_threshold: CodeOffset) -> bool {
14041445
let label_offset = self.resolve_label_offset(fixup.label);
1405-
label_offset != UNKNOWN_LABEL_OFFSET || fixup.deadline() < forced_threshold
1446+
trace!(
1447+
"should_apply_fixup: fixup {fixup:?} label_offset {label_offset} deadline {} forced_threshold {forced_threshold} supports_veneer {}",
1448+
fixup.deadline(),
1449+
fixup.kind.supports_veneer()
1450+
);
1451+
let result = (label_offset != UNKNOWN_LABEL_OFFSET)
1452+
|| ((fixup.deadline() < forced_threshold) && fixup.kind.supports_veneer());
1453+
trace!(
1454+
" -> {}, {}, {} -> {result}",
1455+
label_offset != UNKNOWN_LABEL_OFFSET,
1456+
fixup.deadline() < forced_threshold,
1457+
fixup.kind.supports_veneer()
1458+
);
1459+
result
14061460
}
14071461

14081462
fn handle_fixup(
@@ -1525,7 +1579,7 @@ impl<I: VCodeInst> MachBuffer<I> {
15251579
// `emit_island()` will emit any pending veneers and constants, and
15261580
// as a side-effect, will also take care of any fixups with resolved
15271581
// labels eagerly.
1528-
self.emit_island_maybe_forced(force_veneers, u32::MAX, ctrl_plane);
1582+
self.emit_island_maybe_forced(force_veneers, 0, ctrl_plane);
15291583
}
15301584

15311585
// Ensure that all labels have been fixed up after the last island is emitted. This is a
@@ -1991,6 +2045,7 @@ struct MachBufferConstant {
19912045

19922046
/// A trap that is deferred to the next time an island is emitted for either
19932047
/// traps, constants, or fixups.
2048+
#[derive(Debug)]
19942049
struct MachLabelTrap {
19952050
/// This label will refer to the trap's offset.
19962051
label: MachLabel,

0 commit comments

Comments
 (0)