@@ -200,6 +200,81 @@ pub struct InstructionBlock {
200
200
pub terminator : BlockTerminator ,
201
201
}
202
202
203
+ /// PreviousNodes is a structure which helps maintain ordering among instructions which operate on a given frame.
204
+ /// It works similarly to a multiple-reader-single-writer queue, where an instruction which "uses" a frame is like
205
+ /// a writer and an instruction which blocks that frame is like a reader. Multiple instructions may concurrently
206
+ /// block a frame, but an instruction may not use a frame while it is concurrently used or blocked.
207
+ ///
208
+ /// ## Examples
209
+ ///
210
+ /// Note that "depends on" is equivalent to "must execute after completion of".
211
+ ///
212
+ /// ```text
213
+ /// user --> user # a second user takes a dependency on the first
214
+ ///
215
+ /// user --> blocker # multiple blockers take a dependency on the most recent user
216
+ /// \-> blocker
217
+ /// \-> blocker
218
+ ///
219
+ /// blocker --> user --> blocker # users and blockers take dependencies on one another,
220
+ /// # but blockers do not depend on other blocking instructions
221
+ /// ```
222
+ struct PreviousNodes {
223
+ using : Option < ScheduledGraphNode > ,
224
+ blocking : HashSet < ScheduledGraphNode > ,
225
+ }
226
+
227
+ impl Default for PreviousNodes {
228
+ /// The default value for [PreviousNodes] is useful in that, if no previous nodes have been recorded
229
+ /// as using a frame, we should consider that the start of the instruction block "blocks" use of that frame
230
+ /// (in other words, this instruction cannot be scheduled prior to the start of the instruction block).
231
+ fn default ( ) -> Self {
232
+ Self {
233
+ using : None ,
234
+ blocking : vec ! [ ScheduledGraphNode :: BlockStart ] . into_iter ( ) . collect ( ) ,
235
+ }
236
+ }
237
+ }
238
+
239
+ impl PreviousNodes {
240
+ /// Register a node as using a frame, and return the instructions on which it should depend/wait for scheduling (if any).
241
+ ///
242
+ /// A node which uses a frame will block on any previous user or blocker of the frame, much like a writer in a read-write lock.
243
+ fn get_dependencies_for_next_user (
244
+ & mut self ,
245
+ node : ScheduledGraphNode ,
246
+ ) -> HashSet < ScheduledGraphNode > {
247
+ let mut result = std:: mem:: take ( & mut self . blocking ) ;
248
+ if let Some ( previous_user) = self . using . replace ( node) {
249
+ result. insert ( previous_user) ;
250
+ }
251
+
252
+ result
253
+ }
254
+
255
+ /// Register a node as blocking a frame, and return the instructions on which it should depend/wait for scheduling (if any).
256
+ ///
257
+ /// A node which blocks a frame will block on any previous user of the frame, but not concurrent blockers.
258
+ ///
259
+ /// If the frame is currently blocked by other nodes, it will add itself to the list of blockers,
260
+ /// much like a reader in a read-write lock.
261
+ fn get_dependency_for_next_blocker (
262
+ & mut self ,
263
+ node : ScheduledGraphNode ,
264
+ ) -> Option < ScheduledGraphNode > {
265
+ self . blocking . insert ( node) ;
266
+ self . using
267
+ }
268
+
269
+ /// Consume the [PreviousNodes] and return all nodes within.
270
+ pub fn drain ( mut self ) -> HashSet < ScheduledGraphNode > {
271
+ if let Some ( using) = self . using {
272
+ self . blocking . insert ( using) ;
273
+ }
274
+ self . blocking
275
+ }
276
+ }
277
+
203
278
impl InstructionBlock {
204
279
pub fn build (
205
280
instructions : Vec < Instruction > ,
@@ -213,8 +288,7 @@ impl InstructionBlock {
213
288
let mut last_classical_instruction = ScheduledGraphNode :: BlockStart ;
214
289
215
290
// Store the instruction index of the last instruction to block that frame
216
- let mut last_instruction_by_frame: HashMap < FrameIdentifier , ScheduledGraphNode > =
217
- HashMap :: new ( ) ;
291
+ let mut last_instruction_by_frame: HashMap < FrameIdentifier , PreviousNodes > = HashMap :: new ( ) ;
218
292
219
293
// Store memory access reads and writes. Key is memory region name.
220
294
// NOTE: this may be refined to serialize by memory region offset rather than by entire region.
@@ -236,21 +310,32 @@ impl InstructionBlock {
236
310
let used_frames = program
237
311
. get_frames_for_instruction ( instruction, false )
238
312
. unwrap_or_default ( ) ;
313
+
239
314
let blocked_frames = program
240
315
. get_frames_for_instruction ( instruction, true )
241
316
. unwrap_or_default ( ) ;
242
317
243
- // Take a dependency on any previous instructions to _block_ a frame which this instruction _uses_.
244
- for frame in used_frames {
245
- let previous_node_id = last_instruction_by_frame
246
- . get ( frame)
247
- . unwrap_or ( & ScheduledGraphNode :: BlockStart ) ;
248
- add_dependency ! ( graph, * previous_node_id => node, ExecutionDependency :: ReferenceFrame ) ;
318
+ let blocked_but_not_used_frames = blocked_frames. difference ( & used_frames) ;
319
+
320
+ for frame in & used_frames {
321
+ let previous_node_ids = last_instruction_by_frame
322
+ . entry ( ( * frame) . clone ( ) )
323
+ . or_default ( )
324
+ . get_dependencies_for_next_user ( node) ;
325
+
326
+ for previous_node_id in previous_node_ids {
327
+ add_dependency ! ( graph, previous_node_id => node, ExecutionDependency :: ReferenceFrame ) ;
328
+ }
249
329
}
250
330
251
- // We mark all "blocked" frames as such for later instructions to take a dependency on
252
- for frame in blocked_frames {
253
- last_instruction_by_frame. insert ( frame. clone ( ) , node) ;
331
+ for frame in blocked_but_not_used_frames {
332
+ if let Some ( previous_node_id) = last_instruction_by_frame
333
+ . entry ( ( * frame) . clone ( ) )
334
+ . or_default ( )
335
+ . get_dependency_for_next_blocker ( node)
336
+ {
337
+ add_dependency ! ( graph, previous_node_id => node, ExecutionDependency :: ReferenceFrame ) ;
338
+ }
254
339
}
255
340
256
341
Ok ( ( ) )
@@ -295,8 +380,10 @@ impl InstructionBlock {
295
380
// does not terminate until these are complete
296
381
add_dependency ! ( graph, last_classical_instruction => ScheduledGraphNode :: BlockEnd , ExecutionDependency :: StableOrdering ) ;
297
382
298
- for ( _, last_instruction) in last_instruction_by_frame {
299
- add_dependency ! ( graph, last_instruction => ScheduledGraphNode :: BlockEnd , ExecutionDependency :: ReferenceFrame ) ;
383
+ for previous_nodes in last_instruction_by_frame. into_values ( ) {
384
+ for node in previous_nodes. drain ( ) {
385
+ add_dependency ! ( graph, node => ScheduledGraphNode :: BlockEnd , ExecutionDependency :: ReferenceFrame ) ;
386
+ }
300
387
}
301
388
302
389
// Examine all "pending" memory operations for all regions
0 commit comments