@@ -14,14 +14,32 @@ namespace rsx
1414 rsx::simple_array<MM_block> g_deferred_mprotect_queue;
1515 shared_mutex g_mprotect_queue_lock;
1616
17+ void mm_sanitize_queue_internal ()
18+ {
19+ u32 w = 0 , r = 0 ;
20+ for (; r < g_deferred_mprotect_queue.size (); ++r)
21+ {
22+ auto & block = g_deferred_mprotect_queue[r];
23+ if (!block.range .valid ())
24+ {
25+ continue ;
26+ }
27+ g_deferred_mprotect_queue[w++] = block;
28+ }
29+ g_deferred_mprotect_queue.resize (w);
30+ }
31+
1732 void mm_flush_mprotect_queue_internal (u32 count)
1833 {
1934 AUDIT (count <= g_deferred_mprotect_queue.size ());
2035
2136 for (u32 i = 0 ; i < count; ++i)
2237 {
23- const auto & block = g_deferred_mprotect_queue[i];
38+ auto & block = g_deferred_mprotect_queue[i];
39+ ensure (block.range .valid ());
40+
2441 utils::memory_protect (reinterpret_cast <void *>(block.range .start ), block.range .length (), block.prot );
42+ block.range .invalidate ();
2543 }
2644
2745 const u32 remaining = g_deferred_mprotect_queue.size () - count;
@@ -31,9 +49,8 @@ namespace rsx
3149 return ;
3250 }
3351
34- // Pop count entries from the queue
35- std::memmove (g_deferred_mprotect_queue.data (), g_deferred_mprotect_queue.data () + count, remaining * sizeof (MM_block));
36- g_deferred_mprotect_queue.resize (remaining);
52+ // Pop processed entries from the queue
53+ mm_sanitize_queue_internal ();
3754 }
3855
3956 // Reverse scan to find the latest overlapping MM conflict. The result is a count of prefix blocks.
@@ -53,9 +70,57 @@ namespace rsx
5370
5471 void mm_defer_mprotect_internal (u64 start, u64 length, utils::protection prot)
5572 {
56- // We could stack and merge requests here, but that is more trouble than it is truly worth.
57- // A fresh call to memory_protect only takes a few nanoseconds of setup overhead, it is not worth the risk of hanging because of conflicts.
58- g_deferred_mprotect_queue.push_back ({ utils::address_range64::start_length (start, length), prot });
73+ const auto range = utils::address_range64::start_length (start, length);
74+ bool has_invalid = false ;
75+ bool is_merged = false ;
76+
77+ // Attempt a merge first. The queue length is short but the time taken to run mprotect is very high in comparison.
78+ for (auto it = g_deferred_mprotect_queue.rbegin ();
79+ it != g_deferred_mprotect_queue.rend ();
80+ ++it)
81+ {
82+ auto & block = *it;
83+ if (!block.touches (range))
84+ {
85+ continue ;
86+ }
87+
88+ if (block.prot != prot)
89+ {
90+ // Optimization. If our new range swallows the old one, replace it.
91+ if (block.range .inside (range))
92+ {
93+ block.range .invalidate ();
94+ has_invalid = true ;
95+ continue ;
96+ }
97+
98+ if (!block.overlaps (range))
99+ {
100+ // Adjacent. Skip.
101+ continue ;
102+ }
103+
104+ // Preserve ordering. Do not proceed with merge.
105+ break ;
106+ }
107+
108+ block.merge (range);
109+ is_merged = true ;
110+ break ;
111+ }
112+
113+ if (has_invalid)
114+ {
115+ mm_sanitize_queue_internal ();
116+ }
117+
118+ if (is_merged)
119+ {
120+ return ;
121+ }
122+
123+ g_deferred_mprotect_queue.push_back ({ range, prot });
59124 }
60125
61126 void mm_protect (void * ptr, u64 length, utils::protection prot)
@@ -75,9 +140,24 @@ namespace rsx
75140 if (prot == utils::protection::rw || prot == utils::protection::wx)
76141 {
77142 // Basically an unlock op. Flush the conflicting prefix block if any overlap is detected.
78- if (const u32 count = mm_find_conflict_internal (FN (x.overlaps (range))))
143+ if (u32 count = mm_find_conflict_internal (FN (x.overlaps (range))))
79144 {
80- mm_flush_mprotect_queue_internal (count);
145+ // Check for degenerate ranges that we'll be crushing
146+ for (auto pblock = &g_deferred_mprotect_queue[count - 1 ];
147+ count > 0 && pblock->range .inside (range);
148+ count--, pblock--)
149+ {
150+ pblock->range .invalidate ();
151+ }
152+
153+ if (count)
154+ {
155+ mm_flush_mprotect_queue_internal (count);
156+ }
157+ else
158+ {
159+ mm_sanitize_queue_internal ();
160+ }
81161 }
82162
83163 utils::memory_protect (ptr, length, prot);
0 commit comments