Skip to content

Commit 2039f75

Browse files
committed
feat(jit): restore virtual inline frames on deopt
1 parent 080d818 commit 2039f75

5 files changed

Lines changed: 487 additions & 13 deletions

File tree

src/vm/jit/deopt.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,25 @@ pub(crate) fn exit_inputs(exit: &SsaExit) -> Vec<SsaValueId> {
4141
out.push(value);
4242
}
4343
}
44+
for frame in &exit.virtual_frames {
45+
let dirty_locals = frame
46+
.locals
47+
.iter()
48+
.zip(&frame.dirty_locals)
49+
.filter_map(|(materialization, dirty)| dirty.then_some(materialization));
50+
for materialization in frame.operand_stack.iter().chain(dirty_locals) {
51+
let value = match materialization {
52+
SsaMaterialization::Value(value)
53+
| SsaMaterialization::BoxInt(value)
54+
| SsaMaterialization::BoxFloat(value)
55+
| SsaMaterialization::BoxBool(value) => *value,
56+
SsaMaterialization::BoxHeapPtr { value, .. } => *value,
57+
};
58+
if !out.contains(&value) {
59+
out.push(value);
60+
}
61+
}
62+
}
4463
out
4564
}
4665

@@ -250,4 +269,47 @@ mod tests {
250269
})
251270
);
252271
}
272+
273+
#[test]
274+
fn exit_inputs_include_virtual_frame_values_once_in_frame_order() {
275+
use crate::vm::jit::ir::VirtualFrameSnapshot;
276+
277+
let mut builder = SsaTraceBuilder::new(0, 0);
278+
let entry = builder.entry();
279+
let caller = builder
280+
.append_param(entry, SsaValueRepr::Tagged, "caller")
281+
.unwrap();
282+
let callee_stack = builder
283+
.append_param(entry, SsaValueRepr::I64, "callee_stack")
284+
.unwrap();
285+
let callee_local = builder
286+
.append_param(entry, SsaValueRepr::Bool, "callee_local")
287+
.unwrap();
288+
let exit_id = builder.add_exit_with_virtual_frames(
289+
20,
290+
vec![SsaMaterialization::Value(caller.id)],
291+
Vec::new(),
292+
Vec::new(),
293+
vec![VirtualFrameSnapshot {
294+
prototype_id: 1,
295+
call_ip: 10,
296+
return_ip: 12,
297+
resume_ip: 20,
298+
operand_stack: vec![SsaMaterialization::BoxInt(callee_stack.id)],
299+
locals: vec![
300+
SsaMaterialization::Value(caller.id),
301+
SsaMaterialization::BoxBool(callee_local.id),
302+
],
303+
dirty_locals: vec![true, true],
304+
}],
305+
);
306+
builder
307+
.set_terminator(entry, SsaTerminator::Exit { exit: exit_id })
308+
.unwrap();
309+
let trace = builder.finish();
310+
assert_eq!(
311+
exit_inputs(&trace.exits[0]),
312+
vec![caller.id, callee_stack.id, callee_local.id]
313+
);
314+
}
253315
}

src/vm/jit/ir.rs

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,13 +500,25 @@ pub(crate) enum SsaMaterialization {
500500
BoxHeapPtr { value: SsaValueId, tag: ValueType },
501501
}
502502

503+
#[derive(Clone, Debug, PartialEq, Eq)]
504+
pub(crate) struct VirtualFrameSnapshot {
505+
pub(crate) prototype_id: u32,
506+
pub(crate) call_ip: usize,
507+
pub(crate) return_ip: usize,
508+
pub(crate) resume_ip: usize,
509+
pub(crate) operand_stack: Vec<SsaMaterialization>,
510+
pub(crate) locals: Vec<SsaMaterialization>,
511+
pub(crate) dirty_locals: Vec<bool>,
512+
}
513+
503514
#[derive(Clone, Debug, PartialEq, Eq)]
504515
pub(crate) struct SsaExit {
505516
pub(crate) id: SsaExitId,
506517
pub(crate) exit_ip: usize,
507518
pub(crate) stack: Vec<SsaMaterialization>,
508519
pub(crate) locals: Vec<SsaMaterialization>,
509520
pub(crate) dirty_locals: Vec<bool>,
521+
pub(crate) virtual_frames: Vec<VirtualFrameSnapshot>,
510522
}
511523

512524
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -652,6 +664,41 @@ impl SsaTrace {
652664
for materialization in exit.stack.iter().chain(exit.locals.iter()) {
653665
verify_materialization(materialization, &value_reprs)?;
654666
}
667+
let mut outer_resume_ip = None;
668+
for (frame_index, frame) in exit.virtual_frames.iter().enumerate() {
669+
if frame.dirty_locals.len() != frame.locals.len() {
670+
return Err(SsaVerifyError::VirtualFrameDirtyLocalLengthMismatch {
671+
exit: exit.id,
672+
frame: frame_index,
673+
locals: frame.locals.len(),
674+
dirty_locals: frame.dirty_locals.len(),
675+
});
676+
}
677+
if frame.call_ip >= frame.return_ip {
678+
return Err(SsaVerifyError::InvalidVirtualFrameContinuation {
679+
exit: exit.id,
680+
frame: frame_index,
681+
call_ip: frame.call_ip,
682+
return_ip: frame.return_ip,
683+
});
684+
}
685+
if outer_resume_ip.is_some_and(|resume_ip| resume_ip != frame.call_ip) {
686+
return Err(SsaVerifyError::InvalidVirtualFrameOrder {
687+
exit: exit.id,
688+
frame: frame_index,
689+
});
690+
}
691+
for materialization in frame.operand_stack.iter().chain(&frame.locals) {
692+
if matches!(materialization, SsaMaterialization::BoxHeapPtr { .. }) {
693+
return Err(SsaVerifyError::UnsupportedVirtualFrameOwnership {
694+
exit: exit.id,
695+
frame: frame_index,
696+
});
697+
}
698+
verify_materialization(materialization, &value_reprs)?;
699+
}
700+
outer_resume_ip = Some(frame.resume_ip);
701+
}
655702
}
656703

657704
Ok(())
@@ -756,6 +803,26 @@ pub(crate) enum SsaVerifyError {
756803
locals: usize,
757804
dirty_locals: usize,
758805
},
806+
VirtualFrameDirtyLocalLengthMismatch {
807+
exit: SsaExitId,
808+
frame: usize,
809+
locals: usize,
810+
dirty_locals: usize,
811+
},
812+
InvalidVirtualFrameContinuation {
813+
exit: SsaExitId,
814+
frame: usize,
815+
call_ip: usize,
816+
return_ip: usize,
817+
},
818+
InvalidVirtualFrameOrder {
819+
exit: SsaExitId,
820+
frame: usize,
821+
},
822+
UnsupportedVirtualFrameOwnership {
823+
exit: SsaExitId,
824+
frame: usize,
825+
},
759826
}
760827

761828
pub(crate) struct SsaTraceBuilder {
@@ -842,6 +909,17 @@ impl SsaTraceBuilder {
842909
stack: Vec<SsaMaterialization>,
843910
locals: Vec<SsaMaterialization>,
844911
dirty_locals: Vec<bool>,
912+
) -> SsaExitId {
913+
self.add_exit_with_virtual_frames(exit_ip, stack, locals, dirty_locals, Vec::new())
914+
}
915+
916+
pub(crate) fn add_exit_with_virtual_frames(
917+
&mut self,
918+
exit_ip: usize,
919+
stack: Vec<SsaMaterialization>,
920+
locals: Vec<SsaMaterialization>,
921+
dirty_locals: Vec<bool>,
922+
virtual_frames: Vec<VirtualFrameSnapshot>,
845923
) -> SsaExitId {
846924
let id = SsaExitId::new(self.trace.exits.len() as u32);
847925
self.trace.exits.push(SsaExit {
@@ -850,6 +928,7 @@ impl SsaTraceBuilder {
850928
stack,
851929
locals,
852930
dirty_locals,
931+
virtual_frames,
853932
});
854933
id
855934
}
@@ -1320,4 +1399,123 @@ mod tests {
13201399
})
13211400
);
13221401
}
1402+
1403+
fn trace_with_virtual_frame(frame: VirtualFrameSnapshot) -> (SsaTrace, SsaExitId) {
1404+
let mut builder = SsaTraceBuilder::new(1, 0);
1405+
let entry = builder.entry();
1406+
let exit = builder.add_exit_with_virtual_frames(
1407+
20,
1408+
Vec::new(),
1409+
Vec::new(),
1410+
Vec::new(),
1411+
vec![frame],
1412+
);
1413+
builder
1414+
.set_terminator(entry, SsaTerminator::Exit { exit })
1415+
.unwrap();
1416+
(builder.finish(), exit)
1417+
}
1418+
1419+
#[test]
1420+
fn verifier_accepts_scalar_virtual_frame_snapshot() {
1421+
let mut builder = SsaTraceBuilder::new(1, 0);
1422+
let entry = builder.entry();
1423+
let local = builder
1424+
.append_param(entry, SsaValueRepr::I64, "inline_local")
1425+
.unwrap();
1426+
let exit = builder.add_exit_with_virtual_frames(
1427+
20,
1428+
Vec::new(),
1429+
Vec::new(),
1430+
Vec::new(),
1431+
vec![VirtualFrameSnapshot {
1432+
prototype_id: 3,
1433+
call_ip: 10,
1434+
return_ip: 12,
1435+
resume_ip: 20,
1436+
operand_stack: Vec::new(),
1437+
locals: vec![SsaMaterialization::BoxInt(local.id)],
1438+
dirty_locals: vec![true],
1439+
}],
1440+
);
1441+
builder
1442+
.set_terminator(entry, SsaTerminator::Exit { exit })
1443+
.unwrap();
1444+
assert_eq!(builder.finish().verify(), Ok(()));
1445+
}
1446+
1447+
#[test]
1448+
fn verifier_rejects_malformed_virtual_frame_metadata() {
1449+
let (trace, exit) = trace_with_virtual_frame(VirtualFrameSnapshot {
1450+
prototype_id: 3,
1451+
call_ip: 12,
1452+
return_ip: 12,
1453+
resume_ip: 20,
1454+
operand_stack: Vec::new(),
1455+
locals: Vec::new(),
1456+
dirty_locals: Vec::new(),
1457+
});
1458+
assert_eq!(
1459+
trace.verify(),
1460+
Err(SsaVerifyError::InvalidVirtualFrameContinuation {
1461+
exit,
1462+
frame: 0,
1463+
call_ip: 12,
1464+
return_ip: 12,
1465+
})
1466+
);
1467+
1468+
let (trace, exit) = trace_with_virtual_frame(VirtualFrameSnapshot {
1469+
prototype_id: 3,
1470+
call_ip: 10,
1471+
return_ip: 12,
1472+
resume_ip: 20,
1473+
operand_stack: Vec::new(),
1474+
locals: Vec::new(),
1475+
dirty_locals: vec![true],
1476+
});
1477+
assert_eq!(
1478+
trace.verify(),
1479+
Err(SsaVerifyError::VirtualFrameDirtyLocalLengthMismatch {
1480+
exit,
1481+
frame: 0,
1482+
locals: 0,
1483+
dirty_locals: 1,
1484+
})
1485+
);
1486+
}
1487+
1488+
#[test]
1489+
fn verifier_rejects_heap_pointer_transfer_in_virtual_frame() {
1490+
let mut builder = SsaTraceBuilder::new(1, 0);
1491+
let entry = builder.entry();
1492+
let value = builder
1493+
.append_param(entry, SsaValueRepr::HeapPtr(ValueType::Array), "array")
1494+
.unwrap();
1495+
let exit = builder.add_exit_with_virtual_frames(
1496+
20,
1497+
Vec::new(),
1498+
Vec::new(),
1499+
Vec::new(),
1500+
vec![VirtualFrameSnapshot {
1501+
prototype_id: 3,
1502+
call_ip: 10,
1503+
return_ip: 12,
1504+
resume_ip: 20,
1505+
operand_stack: Vec::new(),
1506+
locals: vec![SsaMaterialization::BoxHeapPtr {
1507+
value: value.id,
1508+
tag: ValueType::Array,
1509+
}],
1510+
dirty_locals: vec![true],
1511+
}],
1512+
);
1513+
builder
1514+
.set_terminator(entry, SsaTerminator::Exit { exit })
1515+
.unwrap();
1516+
assert_eq!(
1517+
builder.finish().verify(),
1518+
Err(SsaVerifyError::UnsupportedVirtualFrameOwnership { exit, frame: 0 })
1519+
);
1520+
}
13231521
}

0 commit comments

Comments
 (0)