Skip to content

Commit c2ad04f

Browse files
committed
YJIT: Invoke PosMarker callbacks only with solid positions
Previously, PosMarker callbacks ran even when the assembler failed to assemble its contents due to insufficient space. This was problematic because when Assembler::compile() failed, the callbacks were given positions that have no valid code, contrary to general expectation. For example, we use a PosMarker callback to record VM instruction boundaries and patch in jumps to exits in case the guest program starts tracing, however, previously, we could record a location near the end of the code block, where there is no space to patch in jumps. I suspect this is the cause of the recent occurrences of rare random failures on GitHub Actions with the invariants.rs:529 "can rewrite existing code" message. `--yjit-perf` also uses PosMarker and had a similar issue. Buffer the list of callbacks to fire, and only fire them when all code in the assembler are written out successfully. It's more intuitive this way.
1 parent 8d0eb87 commit c2ad04f

File tree

3 files changed

+59
-6
lines changed

3 files changed

+59
-6
lines changed

yjit/src/backend/arm64/mod.rs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -819,6 +819,9 @@ impl Assembler
819819
// List of GC offsets
820820
let mut gc_offsets: Vec<u32> = Vec::new();
821821

822+
// Buffered list of PosMarker callbacks to fire if codegen is successful
823+
let mut pos_markers: Vec<(usize, CodePtr)> = vec![];
824+
822825
// For each instruction
823826
let start_write_pos = cb.get_write_pos();
824827
let mut insn_idx: usize = 0;
@@ -838,8 +841,8 @@ impl Assembler
838841
cb.write_label(target.unwrap_label_idx());
839842
},
840843
// Report back the current position in the generated code
841-
Insn::PosMarker(pos_marker) => {
842-
pos_marker(cb.get_write_ptr());
844+
Insn::PosMarker(..) => {
845+
pos_markers.push((insn_idx, cb.get_write_ptr()))
843846
}
844847
Insn::BakeString(text) => {
845848
for byte in text.as_bytes() {
@@ -1205,7 +1208,21 @@ impl Assembler
12051208
}
12061209
}
12071210

1208-
Ok(gc_offsets)
1211+
// Error if we couldn't write out everything
1212+
if cb.has_dropped_bytes() {
1213+
return Err(EmitError::OutOfMemory)
1214+
} else {
1215+
// No bytes dropped, so the pos markers point to valid code
1216+
for (insn_idx, pos) in pos_markers {
1217+
if let Insn::PosMarker(callback) = self.insns.get(insn_idx).unwrap() {
1218+
callback(pos);
1219+
} else {
1220+
panic!("non-PosMarker in pos_markers insn_idx={insn_idx} {self:?}");
1221+
}
1222+
}
1223+
1224+
return Ok(gc_offsets)
1225+
}
12091226
}
12101227

12111228
/// Optimize and compile the stored instructions

yjit/src/backend/tests.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,3 +310,22 @@ fn test_cmp_8_bit() {
310310

311311
asm.compile_with_num_regs(&mut cb, 1);
312312
}
313+
314+
#[test]
315+
fn test_no_pos_marker_callback_when_compile_fails() {
316+
// When compilation fails (e.g. when out of memory), the code written out is malformed, so
317+
// we don't want to invoke the pos_marker callbacks with positions where there is malformed
318+
// code.
319+
let mut asm = Assembler::new();
320+
321+
// Markers around code to exhaust memory limit
322+
let fail_if_called = |_code_ptr| panic!("pos_marker callback should not be called");
323+
asm.pos_marker(fail_if_called);
324+
let zero = asm.load(0.into());
325+
let sum = asm.add(zero, 500.into());
326+
asm.store(Opnd::mem(64, SP, 8), sum);
327+
asm.pos_marker(fail_if_called);
328+
329+
let cb = &mut CodeBlock::new_dummy(8);
330+
assert!(asm.compile(cb, None).is_none(), "should fail due to tiny size limit");
331+
}

yjit/src/backend/x86_64/mod.rs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,9 @@ impl Assembler
457457
// List of GC offsets
458458
let mut gc_offsets: Vec<u32> = Vec::new();
459459

460+
// Buffered list of PosMarker callbacks to fire if codegen is successful
461+
let mut pos_markers: Vec<(usize, CodePtr)> = vec![];
462+
460463
// For each instruction
461464
let start_write_pos = cb.get_write_pos();
462465
let mut insn_idx: usize = 0;
@@ -479,8 +482,8 @@ impl Assembler
479482
},
480483

481484
// Report back the current position in the generated code
482-
Insn::PosMarker(pos_marker) => {
483-
pos_marker(cb.get_write_ptr());
485+
Insn::PosMarker(..) => {
486+
pos_markers.push((insn_idx, cb.get_write_ptr()));
484487
},
485488

486489
Insn::BakeString(text) => {
@@ -817,7 +820,21 @@ impl Assembler
817820
}
818821
}
819822

820-
Some(gc_offsets)
823+
// error if we couldn't write out everything
824+
if cb.has_dropped_bytes() {
825+
return None
826+
} else {
827+
// No bytes dropped, so the pos markers point to valid code
828+
for (insn_idx, pos) in pos_markers {
829+
if let Insn::PosMarker(callback) = self.insns.get(insn_idx).unwrap() {
830+
callback(pos);
831+
} else {
832+
panic!("non-PosMarker in pos_markers insn_idx={insn_idx} {self:?}");
833+
}
834+
}
835+
836+
return Some(gc_offsets)
837+
}
821838
}
822839

823840
/// Optimize and compile the stored instructions

0 commit comments

Comments
 (0)