1
+ use genmc_sys:: AssumeType ;
1
2
use rustc_middle:: ty;
2
3
use tracing:: debug;
3
4
4
5
use crate :: concurrency:: genmc:: MAX_ACCESS_SIZE ;
5
6
use crate :: concurrency:: thread:: EvalContextExt as _;
6
7
use crate :: * ;
7
8
9
+ impl GenmcCtx {
10
+ /// Handle a user thread getting blocked.
11
+ /// This may happen due to an manual `assume` statement added by a user
12
+ /// or added by some automated program transformation, e.g., for spinloops.
13
+ fn handle_assume_block < ' tcx > (
14
+ & self ,
15
+ machine : & MiriMachine < ' tcx > ,
16
+ assume_type : AssumeType ,
17
+ ) -> InterpResult < ' tcx > {
18
+ debug ! ( "GenMC: assume statement, blocking active thread." ) ;
19
+ self . handle
20
+ . borrow_mut ( )
21
+ . pin_mut ( )
22
+ . handle_assume_block ( self . active_thread_genmc_tid ( machine) , assume_type) ;
23
+ interp_ok ( ( ) )
24
+ }
25
+ }
26
+
8
27
// Handling of code intercepted by Miri in GenMC mode, such as assume statement or `std::sync::Mutex`.
9
28
10
29
impl < ' tcx > EvalContextExtPriv < ' tcx > for crate :: MiriInterpCx < ' tcx > { }
@@ -23,6 +42,36 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
23
42
panic ! ( "{} is a diagnostic item expected to have {} arguments" , instance, N ) ;
24
43
}
25
44
45
+ /**** Blocking functionality ****/
46
+
47
+ /// Handle a thread getting blocked by a user assume (not an automatically generated assume).
48
+ /// Unblocking this thread in the current execution will cause a panic.
49
+ /// Miri does not provide GenMC with the annotations to determine when to unblock the thread, so it should never be unblocked.
50
+ fn handle_user_assume_block ( & mut self ) -> InterpResult < ' tcx > {
51
+ let this = self . eval_context_mut ( ) ;
52
+ debug ! (
53
+ "GenMC: block thread {:?} due to failing assume statement." ,
54
+ this. machine. threads. active_thread( )
55
+ ) ;
56
+ assert ! ( this. machine. threads. active_thread_ref( ) . is_enabled( ) ) ;
57
+ // Block the thread on the GenMC side.
58
+ let genmc_ctx = this. machine . data_race . as_genmc_ref ( ) . unwrap ( ) ;
59
+ genmc_ctx. handle_assume_block ( & this. machine , AssumeType :: User ) ?;
60
+ // Block the thread on the Miri side.
61
+ this. block_thread (
62
+ BlockReason :: Genmc ,
63
+ None ,
64
+ callback ! (
65
+ @capture<' tcx> { }
66
+ |_this, unblock: UnblockKind | {
67
+ assert_eq!( unblock, UnblockKind :: Ready ) ;
68
+ unreachable!( "GenMC should never unblock a thread blocked by an `assume`." ) ;
69
+ }
70
+ ) ,
71
+ ) ;
72
+ interp_ok ( ( ) )
73
+ }
74
+
26
75
fn intercept_mutex_lock ( & mut self , mutex : MPlaceTy < ' tcx > ) -> InterpResult < ' tcx > {
27
76
debug ! ( "GenMC: handling Mutex::lock()" ) ;
28
77
let this = self . eval_context_mut ( ) ;
@@ -47,13 +96,15 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
47
96
result. is_reset, result. is_lock_acquired
48
97
) ;
49
98
if result. is_lock_acquired {
99
+ // NOTE: We don't write anything back to Miri's memory, the Mutex state is handled only by GenMC.
50
100
return interp_ok ( ( ) ) ;
51
101
}
52
- // If we did not acquire the mutex and GenMC did not tell us to reset and try again, we have a deadlock .
102
+ // We either did not acquire the mutex, or GenMC informed us to reset and try the lock again later, so we block the current thread .
53
103
if !result. is_reset {
54
- throw_machine_stop ! ( TerminationInfo :: Deadlock ) ;
104
+ // GenMC expects us to block the thread if we did get a `reset`.
105
+ genmc_ctx. handle_assume_block ( & this. machine , AssumeType :: Spinloop ) ?;
55
106
}
56
- // NOTE: We don't write anything back to Miri's memory, the Mutex state is handled only by GenMC .
107
+ // Block the thread on the Miri side .
57
108
this. block_thread (
58
109
crate :: BlockReason :: Genmc ,
59
110
None ,
@@ -78,9 +129,11 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
78
129
throw_ub_format!( "{}" , error. to_string_lossy( ) ) ;
79
130
}
80
131
assert!( !result. is_reset, "We should not get a reset on the second lock attempt." ) ;
81
- // If we did not acquire the mutex and GenMC did not tell us to reset and try again, we have a deadlock .
132
+ // If we did not acquire the mutex and GenMC did not tell us to reset and try again, we block this thread for the remainder of the current execution .
82
133
if !result. is_lock_acquired {
83
- throw_machine_stop!( TerminationInfo :: Deadlock ) ;
134
+ debug!( "GenMC: Mutex::lock failed to acquire the Mutex twice, permanently blocking thread." ) ;
135
+ // This is equivalent to a user inserted `assume(false)`.
136
+ this. handle_user_assume_block( ) ?;
84
137
}
85
138
interp_ok( ( ) )
86
139
}
@@ -116,7 +169,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
116
169
"GenMC: Mutex::try_lock(): is_reset: {}, is_lock_acquired: {}" ,
117
170
result. is_reset, result. is_lock_acquired
118
171
) ;
119
- assert ! ( !result. is_reset) ;
172
+ assert ! ( !result. is_reset, "GenMC returned 'reset' for a mutex lock multiple times." ) ;
120
173
// Write the return value of try_lock, i.e., whether we acquired the mutex.
121
174
this. write_scalar ( Scalar :: from_bool ( result. is_lock_acquired ) , dest) ?;
122
175
// NOTE: We don't write anything back to Miri's memory where the Mutex is located, that state is handled only by GenMC.
@@ -187,19 +240,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
187
240
if condition_bool {
188
241
return interp_ok ( ( ) ) ;
189
242
}
190
- let genmc_ctx = this. machine . data_race . as_genmc_ref ( ) . unwrap ( ) ;
191
- genmc_ctx. handle_assume_block ( & this. machine ) ?;
192
- this. block_thread (
193
- BlockReason :: Genmc ,
194
- None ,
195
- callback ! (
196
- @capture<' tcx> { }
197
- |_this, unblock: UnblockKind | {
198
- assert_eq!( unblock, UnblockKind :: Ready ) ;
199
- unreachable!( "GenMC should never unblock a thread blocked by an `assume`." ) ;
200
- }
201
- ) ,
202
- ) ;
203
- interp_ok ( ( ) )
243
+ this. handle_user_assume_block ( )
204
244
}
205
245
}
0 commit comments