@@ -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 ) ]
504515pub ( 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
761828pub ( 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