@@ -31,6 +31,11 @@ cfg_if::cfg_if! {
3131 if #[ cfg( windows) ] {
3232 mod windows;
3333 use windows as imp;
34+ } else if #[ cfg( all( feature = "uffd" , target_os = "linux" ) ) ] {
35+ mod uffd;
36+ use uffd as imp;
37+ use imp:: { PageFaultHandler , reset_guard_page} ;
38+ use std:: sync:: atomic:: { AtomicBool , Ordering } ;
3439 } else if #[ cfg( target_os = "linux" ) ] {
3540 mod linux;
3641 use linux as imp;
@@ -335,6 +340,9 @@ impl Iterator for BasePointerIterator {
335340/// structure depending on the limits used to create the pool.
336341///
337342/// The pool maintains a free list for fast instance allocation.
343+ ///
344+ /// The userfault handler relies on how instances are stored in the mapping,
345+ /// so make sure the uffd implementation is kept up-to-date.
338346#[ derive( Debug ) ]
339347struct InstancePool {
340348 mapping : Mmap ,
@@ -472,6 +480,10 @@ impl Drop for InstancePool {
472480///
473481/// Each index into the pool returns an iterator over the base addresses
474482/// of the instance's linear memories.
483+ ///
484+ ///
485+ /// The userfault handler relies on how memories are stored in the mapping,
486+ /// so make sure the uffd implementation is kept up-to-date.
475487#[ derive( Debug ) ]
476488struct MemoryPool {
477489 mapping : Mmap ,
@@ -524,6 +536,9 @@ impl MemoryPool {
524536///
525537/// Each index into the pool returns an iterator over the base addresses
526538/// of the instance's tables.
539+ ///
540+ /// The userfault handler relies on how tables are stored in the mapping,
541+ /// so make sure the uffd implementation is kept up-to-date.
527542#[ derive( Debug ) ]
528543struct TablePool {
529544 mapping : Mmap ,
@@ -588,13 +603,18 @@ impl TablePool {
588603///
589604/// The top of the stack (starting stack pointer) is returned when a stack is allocated
590605/// from the pool.
606+ ///
607+ /// The userfault handler relies on how stacks are stored in the mapping,
608+ /// so make sure the uffd implementation is kept up-to-date.
591609#[ derive( Debug ) ]
592610struct StackPool {
593611 mapping : Mmap ,
594612 stack_size : usize ,
595613 max_instances : usize ,
596614 page_size : usize ,
597615 free_list : Mutex < Vec < usize > > ,
616+ #[ cfg( all( feature = "uffd" , target_os = "linux" ) ) ]
617+ faulted_guard_pages : Arc < [ AtomicBool ] > ,
598618}
599619
600620impl StackPool {
@@ -623,6 +643,11 @@ impl StackPool {
623643 max_instances,
624644 page_size,
625645 free_list : Mutex :: new ( ( 0 ..max_instances) . collect ( ) ) ,
646+ #[ cfg( all( feature = "uffd" , target_os = "linux" ) ) ]
647+ faulted_guard_pages : std:: iter:: repeat_with ( || false . into ( ) )
648+ . take ( max_instances)
649+ . collect :: < Vec < _ > > ( )
650+ . into ( ) ,
626651 } )
627652 }
628653
@@ -647,11 +672,25 @@ impl StackPool {
647672 . as_mut_ptr ( )
648673 . add ( ( index * self . stack_size ) + self . page_size ) ;
649674
650- // Make the stack accessible (excluding the guard page)
651- if !make_accessible ( bottom_of_stack, size_without_guard) {
652- return Err ( FiberStackError :: Resource (
653- "failed to make instance memory accessible" . into ( ) ,
654- ) ) ;
675+ cfg_if:: cfg_if! {
676+ if #[ cfg( all( feature = "uffd" , target_os = "linux" ) ) ] {
677+ // Check to see if a guard page needs to be reset
678+ if self . faulted_guard_pages[ index] . swap( false , Ordering :: SeqCst ) {
679+ if !reset_guard_page( bottom_of_stack. sub( self . page_size) , self . page_size) {
680+ return Err ( FiberStackError :: Resource (
681+ "failed to reset stack guard page" . into( ) ,
682+ ) ) ;
683+ }
684+ }
685+
686+ } else {
687+ // Make the stack accessible (excluding the guard page)
688+ if !make_accessible( bottom_of_stack, size_without_guard) {
689+ return Err ( FiberStackError :: Resource (
690+ "failed to make instance memory accessible" . into( ) ,
691+ ) ) ;
692+ }
693+ }
655694 }
656695
657696 // The top of the stack should be returned
@@ -697,6 +736,8 @@ pub struct PoolingInstanceAllocator {
697736 memories : mem:: ManuallyDrop < MemoryPool > ,
698737 tables : mem:: ManuallyDrop < TablePool > ,
699738 stacks : mem:: ManuallyDrop < StackPool > ,
739+ #[ cfg( all( feature = "uffd" , target_os = "linux" ) ) ]
740+ _fault_handler : PageFaultHandler ,
700741}
701742
702743impl PoolingInstanceAllocator {
@@ -744,6 +785,9 @@ impl PoolingInstanceAllocator {
744785 let tables = TablePool :: new ( & module_limits, & instance_limits) ?;
745786 let stacks = StackPool :: new ( & instance_limits, stack_size) ?;
746787
788+ #[ cfg( all( feature = "uffd" , target_os = "linux" ) ) ]
789+ let _fault_handler = PageFaultHandler :: new ( & instances, & memories, & tables, & stacks) ?;
790+
747791 Ok ( Self {
748792 strategy,
749793 module_limits,
@@ -752,6 +796,8 @@ impl PoolingInstanceAllocator {
752796 memories : mem:: ManuallyDrop :: new ( memories) ,
753797 tables : mem:: ManuallyDrop :: new ( tables) ,
754798 stacks : mem:: ManuallyDrop :: new ( stacks) ,
799+ #[ cfg( all( feature = "uffd" , target_os = "linux" ) ) ]
800+ _fault_handler,
755801 } )
756802 }
757803
@@ -800,14 +846,28 @@ impl PoolingInstanceAllocator {
800846 ) -> Result < ( ) , InstantiationError > {
801847 let module = instance. module . as_ref ( ) ;
802848
849+ // Reset all guard pages before clearing the previous memories
850+ #[ cfg( all( feature = "uffd" , target_os = "linux" ) ) ]
851+ for ( _, m) in instance. memories . iter ( ) {
852+ m. reset_guard_pages ( )
853+ . map_err ( InstantiationError :: Resource ) ?;
854+ }
855+
803856 instance. memories . clear ( ) ;
804857
805858 for plan in
806859 ( & module. memory_plans . values ( ) . as_slice ( ) [ module. num_imported_memories ..] ) . iter ( )
807860 {
808861 instance. memories . push (
809- Memory :: new_static ( plan, memories. next ( ) . unwrap ( ) , max_pages, make_accessible)
810- . map_err ( InstantiationError :: Resource ) ?,
862+ Memory :: new_static (
863+ plan,
864+ memories. next ( ) . unwrap ( ) ,
865+ max_pages,
866+ make_accessible,
867+ #[ cfg( all( feature = "uffd" , target_os = "linux" ) ) ]
868+ reset_guard_page,
869+ )
870+ . map_err ( InstantiationError :: Resource ) ?,
811871 ) ;
812872 }
813873
@@ -826,7 +886,6 @@ impl PoolingInstanceAllocator {
826886 let module = instance. module . as_ref ( ) ;
827887
828888 instance. tables . clear ( ) ;
829-
830889 for plan in ( & module. table_plans . values ( ) . as_slice ( ) [ module. num_imported_tables ..] ) . iter ( ) {
831890 let base = tables. next ( ) . unwrap ( ) ;
832891
@@ -852,7 +911,8 @@ impl PoolingInstanceAllocator {
852911
853912impl Drop for PoolingInstanceAllocator {
854913 fn drop ( & mut self ) {
855- // There are manually dropped for the future uffd implementation
914+ // Manually drop the pools before the fault handler (if uffd is enabled)
915+ // This ensures that any fault handler thread monitoring the pool memory terminates
856916 unsafe {
857917 mem:: ManuallyDrop :: drop ( & mut self . instances ) ;
858918 mem:: ManuallyDrop :: drop ( & mut self . memories ) ;
0 commit comments