11// Copyright Kani Contributors
22// SPDX-License-Identifier: Apache-2.0 OR MIT
33
4- use lazy_static:: lazy_static;
5- use std:: sync:: Mutex ;
4+ use std:: cell:: RefCell ;
65use string_interner:: StringInterner ;
76use string_interner:: backend:: StringBackend ;
87use string_interner:: symbol:: SymbolU32 ;
@@ -23,10 +22,11 @@ use string_interner::symbol::SymbolU32;
2322#[ derive( Clone , Hash , Copy , PartialEq , Eq , PartialOrd , Ord ) ]
2423pub struct InternedString ( SymbolU32 ) ;
2524
26- // Use a `Mutex` to make this thread safe.
27- lazy_static ! {
28- static ref INTERNER : Mutex <StringInterner <StringBackend >> =
29- Mutex :: new( StringInterner :: default ( ) ) ;
25+ // This [StringInterner] is a thread local, letting us get away with less synchronization.
26+ // See the `sync` module below for a full explanation of this choice's consequences.
27+ thread_local ! {
28+ static INTERNER : RefCell <StringInterner <StringBackend >> =
29+ RefCell :: new( StringInterner :: default ( ) ) ;
3030}
3131
3232impl InternedString {
@@ -42,7 +42,7 @@ impl InternedString {
4242 /// Needed because exporting the &str backing the InternedString is blocked by lifetime rules.
4343 /// Instead, this allows users to operate on the &str when needed.
4444 pub fn map < T , F : FnOnce ( & str ) -> T > ( & self , f : F ) -> T {
45- f ( INTERNER . lock ( ) . unwrap ( ) . resolve ( self . 0 ) . unwrap ( ) )
45+ INTERNER . with_borrow ( |i| f ( i . resolve ( self . 0 ) . unwrap ( ) ) )
4646 }
4747
4848 pub fn starts_with ( & self , pattern : & str ) -> bool {
@@ -52,13 +52,13 @@ impl InternedString {
5252
5353impl std:: fmt:: Display for InternedString {
5454 fn fmt ( & self , fmt : & mut std:: fmt:: Formatter < ' _ > ) -> Result < ( ) , std:: fmt:: Error > {
55- write ! ( fmt, "{}" , INTERNER . lock ( ) . unwrap ( ) . resolve( self . 0 ) . unwrap( ) )
55+ INTERNER . with_borrow ( |i| write ! ( fmt, "{}" , i . resolve( self . 0 ) . unwrap( ) ) )
5656 }
5757}
5858/// Custom-implement Debug, so our debug logging contains meaningful strings, not numbers
5959impl std:: fmt:: Debug for InternedString {
6060 fn fmt ( & self , fmt : & mut std:: fmt:: Formatter < ' _ > ) -> Result < ( ) , std:: fmt:: Error > {
61- write ! ( fmt, "{:?}" , INTERNER . lock ( ) . unwrap ( ) . resolve( self . 0 ) . unwrap( ) )
61+ INTERNER . with_borrow ( |i| write ! ( fmt, "{:?}" , i . resolve( self . 0 ) . unwrap( ) ) )
6262 }
6363}
6464
6767 T : AsRef < str > ,
6868{
6969 fn from ( s : T ) -> InternedString {
70- InternedString ( INTERNER . lock ( ) . unwrap ( ) . get_or_intern ( s) )
70+ InternedString ( INTERNER . with_borrow_mut ( |i| i . get_or_intern ( s) ) )
7171 }
7272}
7373
7676 T : AsRef < str > ,
7777{
7878 fn eq ( & self , other : & T ) -> bool {
79- INTERNER . lock ( ) . unwrap ( ) . resolve ( self . 0 ) . unwrap ( ) == other. as_ref ( )
79+ INTERNER . with_borrow ( |i| i . resolve ( self . 0 ) . unwrap ( ) == other. as_ref ( ) )
8080 }
8181}
8282
@@ -105,6 +105,68 @@ where
105105 }
106106}
107107
108+ /// At a high level, the key design choice here is to keep our [StringInterner]s as thread locals.
109+ /// This works because whichever thread is generating `SymbolTable`s will be updating the interner in a way that
110+ /// affects serialization, but the serialization doesn't affect the interner in a way that affects generating
111+ /// `SymbolTable`s.
112+ ///
113+ /// Thus, it makes a lot of sense to have threads each maintain their own copy of a [StringInterner]. Then, when the main
114+ /// codegen thread wants to tell another thread to write a new `SymbolTable` to disk, it can just send along
115+ /// its master copy of the [StringInterner] that they can use to update theirs.
116+ ///
117+ /// To enforce this, [InternedString] is marked `!Send`--preventing them from being sent between threads
118+ /// unless they're wrapped in a [WithInterner](sync::WithInterner) type that will ensure the recieving thread updates
119+ /// its local interner to match the sending thread's.
120+ pub ( crate ) mod sync {
121+ use string_interner:: { StringInterner , backend:: StringBackend } ;
122+
123+ use crate :: { InternedString , cbmc_string:: INTERNER } ;
124+
125+ /// The value of an [InternedString] is defined based on a thread local [INTERNER] so they cannot safely
126+ /// be sent between threads.
127+ impl !Send for InternedString { }
128+
129+ /// A type that is only `!Send` because it contains types specific to a thread local [INTERNER]
130+ /// (e.g. [InternedString]s). This forces users to annotate that the types they want to wrap in [WithInterner]
131+ /// are `!Send` just for that specific reason rather than using it to make arbitrary types `Send`.
132+ ///
133+ /// # Safety
134+ ///
135+ /// Should only be implemented for types which are `!Send` **solely** because they contain information specific
136+ /// to their thread local [INTERNER] (e.g. by containing [InternedString]s).
137+ pub unsafe trait InternerSpecific { }
138+
139+ /// Since [WithInterner<T>] guarantees that the inner `T` cannot be accessed without updating the
140+ /// thread local [INTERNER] to a copy of what was used to generate `T`, it is safe to send between threads,
141+ /// even if the inner `T` contains [InternedString]s which are not [Send] on their own.
142+ unsafe impl < T : InternerSpecific > Send for WithInterner < T > { }
143+
144+ /// A type `T` bundled with the [StringInterner] that was used to generate it.
145+ ///
146+ /// The only way to access the inner `T` is by calling `into_inner()`, which will automatically
147+ /// update the current thread's interner to the interner used the generate `T`,
148+ /// ensuring interner coherence between the sending & receiving threads.
149+ pub struct WithInterner < T > {
150+ interner : StringInterner < StringBackend > ,
151+ inner : T ,
152+ }
153+
154+ impl < T > WithInterner < T > {
155+ /// Create a new wrapper of `inner` with a clone of the current thread local [INTERNER].
156+ pub fn new_with_current ( inner : T ) -> Self {
157+ let interner = INTERNER . with_borrow ( |i| i. clone ( ) ) ;
158+ WithInterner { interner, inner }
159+ }
160+
161+ /// Get the inner wrapped `T` and implicitly update the current thread local [INTERNER] with a
162+ /// copy of the one used to generate `T`.
163+ pub fn into_inner ( self ) -> T {
164+ INTERNER . with_borrow_mut ( |i| * i = self . interner ) ;
165+ self . inner
166+ }
167+ }
168+ }
169+
108170#[ cfg( test) ]
109171mod tests {
110172 use crate :: cbmc_string:: InternedString ;
0 commit comments