1
use std::hash::Hash;
2
use std::hash::Hasher;
3
use std::sync::Arc;
4
use std::sync::atomic::AtomicUsize;
5
use std::sync::atomic::Ordering;
6

            
7
use dashmap::DashMap;
8
use equivalent::Equivalent;
9
use log::debug;
10
use rustc_hash::FxBuildHasher;
11

            
12
use merc_unsafety::AllocBlock;
13
use merc_unsafety::BlockAllocatorSafe;
14
use merc_unsafety::StablePointer;
15
use merc_unsafety::StablePointerSet;
16

            
17
use crate::Symb;
18
use crate::SymbolIndex;
19
use crate::SymbolRef;
20

            
21
/// Pool for maximal sharing of function symbols, see [SymbolRef]. Ensures that function symbols
22
/// with the same name and arity point to the same [SharedSymbol] object.
23
/// Returns [crate::Symbol] that can be used to refer to the shared symbol, avoiding
24
/// garbage collection of the underlying shared symbol.
25
pub struct SymbolPool {
26
    /// Unique table of all function symbols
27
    symbols: StablePointerSet<SharedSymbol, FxBuildHasher, AllocBlock<SharedSymbol, 1024>>,
28

            
29
    /// A map from prefixes to counters that track the next available index for function symbols
30
    prefix_to_register_function_map: DashMap<String, Arc<AtomicUsize>, FxBuildHasher>,
31
}
32

            
33
impl SymbolPool {
34
    /// Creates a new empty symbol pool.
35
1764
    pub(crate) fn new() -> Self {
36
1764
        Self {
37
1764
            symbols: StablePointerSet::with_hasher_in(FxBuildHasher, AllocBlock::new()),
38
1764
            prefix_to_register_function_map: DashMap::with_hasher(FxBuildHasher),
39
1764
        }
40
1764
    }
41

            
42
    /// Creates or retrieves a function symbol with the given name and arity.
43
    ///
44
    /// Crate-private: the returned pointer is unprotected, so the caller must protect it
45
    /// before the lock it was created under is released.
46
6614758
    pub(crate) fn create<N>(&self, name: N, arity: usize) -> StablePointer<SharedSymbol>
47
6614758
    where
48
6614758
        N: Into<String> + AsRef<str>,
49
    {
50
        // Get or create symbol index
51
6614758
        let (shared_symbol, inserted) = self.symbols.insert_equiv(&SharedSymbolLookup { name, arity });
52

            
53
6614758
        if inserted {
54
110994
            // If the symbol was newly created, register its prefix.
55
110994
            // SAFETY: `shared_symbol` was just inserted and is resident in `self.symbols`.
56
110994
            self.update_prefix(unsafe { shared_symbol.deref() }.name());
57
6503764
        }
58

            
59
        // Return cloned symbol
60
6614758
        shared_symbol
61
6614758
    }
62

            
63
    /// Return the symbol of the SharedTerm for the given ATermRef
64
    pub fn symbol_name<'a>(&self, symbol: &'a SymbolRef<'a>) -> &'a str {
65
        // SAFETY: `symbol` is a `SymbolRef<'a>`, so its symbol is alive for `'a`.
66
        unsafe { symbol.shared().deref() }.name()
67
    }
68

            
69
    /// Returns the arity of the function symbol
70
    pub fn symbol_arity<'a, 'b, S: Symb<'a, 'b>>(&self, symbol: &'b S) -> usize {
71
        // SAFETY: `symbol` borrows a live symbol for the duration of the call.
72
        unsafe { symbol.shared().deref() }.arity()
73
    }
74

            
75
    /// Returns the number of symbols in the pool.
76
558230
    pub fn len(&self) -> usize {
77
558230
        self.symbols.len()
78
558230
    }
79

            
80
    /// Returns true if the pool is empty.
81
    pub fn is_empty(&self) -> bool {
82
        self.symbols.is_empty()
83
    }
84

            
85
    /// Returns the capacity of the pool.
86
    pub fn capacity(&self) -> usize {
87
        self.symbols.capacity()
88
    }
89

            
90
    /// Retain only symbols satisfying the given predicate.
91
    ///
92
    /// # Safety
93
    ///
94
    /// Removal invalidates every [`SymbolIndex`] to a removed symbol; the caller must guarantee
95
    /// that no index to a removed symbol is dereferenced afterwards.
96
558230
    pub unsafe fn retain<F>(&mut self, mut f: F)
97
558230
    where
98
558230
        F: FnMut(&SymbolIndex) -> bool,
99
    {
100
        // SAFETY: The caller guarantees that indices of removed symbols are not used again.
101
        unsafe {
102
18471917
            self.symbols.retain(|element| f(element));
103
        }
104

            
105
558230
        let removed_blocks = self.symbols.allocator_mut().remove_free_blocks();
106
558230
        debug!("Removed {} blocks from the symbol pool", removed_blocks);
107
558230
    }
108

            
109
    /// Creates a new prefix counter for the given prefix.
110
1
    pub fn create_prefix(&self, prefix: &str) -> Arc<AtomicUsize> {
111
        // Create a new counter for the prefix if it does not exist. The fast path avoids
112
        // allocating the key string on a hit; the miss path uses `entry`/`or_insert_with` so
113
        // the get-then-insert is atomic even when called concurrently through the `&self` API.
114
1
        let result = if let Some(result) = self.prefix_to_register_function_map.get(prefix) {
115
            result.clone()
116
        } else {
117
1
            self.prefix_to_register_function_map
118
1
                .entry(prefix.to_string())
119
1
                .or_insert_with(|| Arc::new(AtomicUsize::new(0)))
120
1
                .clone()
121
        };
122

            
123
        // Ensure the counter starts at a sufficiently large index
124
1
        self.get_sufficiently_large_postfix_index(prefix, &result);
125
1
        result
126
1
    }
127

            
128
    /// Removes a prefix counter from the pool.
129
    pub fn remove_prefix(&self, prefix: &str) {
130
        // Remove the prefix counter if it exists
131
        self.prefix_to_register_function_map.remove(prefix);
132
    }
133

            
134
    /// Updates the counter for a registered prefix for the newly created symbol.
135
133838
    fn update_prefix(&self, name: &str) {
136
        // Check whether there is a registered prefix p such that name equal pn where n is a number.
137
        // In that case prevent that pn will be generated as a fresh function name.
138
133838
        let start_of_index = name
139
143687
            .rfind(|c: char| !c.is_ascii_digit())
140
133838
            .map(|pos| pos + 1)
141
133838
            .unwrap_or(0);
142

            
143
133838
        if start_of_index < name.len() {
144
16976
            let potential_number = &name[start_of_index..];
145
16976
            let prefix = &name[..start_of_index];
146

            
147
16976
            if let Some(counter) = self.prefix_to_register_function_map.get(prefix)
148
1
                && let Ok(number) = potential_number.parse::<usize>()
149
1
            {
150
1
                counter.fetch_max(number + 1, Ordering::Relaxed);
151
16975
            }
152
116862
        }
153
133838
    }
154

            
155
    /// Traverse all symbols to find the maximum numeric suffix for this prefix
156
1
    fn get_sufficiently_large_postfix_index(&self, prefix: &str, counter: &Arc<AtomicUsize>) {
157
        // SAFETY: this traversal does not remove any symbol, so every yielded
158
        // reference stays valid for the duration of the loop.
159
5
        for symbol in unsafe { self.symbols.iter() } {
160
5
            let name = symbol.name();
161
5
            if name.starts_with(prefix) {
162
                // Symbol name starts with the prefix, check for numeric suffix
163
2
                let suffix_start = prefix.len();
164
2
                if suffix_start < name.len() {
165
2
                    let suffix = &name[suffix_start..];
166
2
                    if let Ok(number) = suffix.parse::<usize>() {
167
1
                        // There is a numeric suffix, update the counter if it's larger
168
1
                        counter.fetch_max(number + 1, Ordering::Relaxed);
169
1
                    }
170
                }
171
3
            }
172
        }
173
1
    }
174
}
175

            
176
/// Represents a function symbol with a name and arity.
177
#[derive(Debug, Clone, Eq, PartialEq)]
178
pub struct SharedSymbol {
179
    /// Name of the function
180
    name: String,
181
    /// Number of arguments
182
    arity: usize,
183
}
184

            
185
/// SAFETY: The `SharedSymbol` is never equal to the sentinel value.
186
unsafe impl BlockAllocatorSafe for SharedSymbol {}
187

            
188
impl SharedSymbol {
189
    /// Creates a new function symbol.
190
133838
    pub fn new<N: Into<String>>(name: N, arity: usize) -> Self {
191
133838
        Self {
192
133838
            name: name.into(),
193
133838
            arity,
194
133838
        }
195
133838
    }
196

            
197
    /// Returns the name of the function symbol
198
3305942
    pub fn name(&self) -> &str {
199
3305942
        &self.name
200
3305942
    }
201

            
202
    /// Returns the arity of the function symbol
203
4088965880
    pub fn arity(&self) -> usize {
204
4088965880
        self.arity
205
4088965880
    }
206

            
207
    /// Returns a unique index for this shared symbol
208
    pub fn index(&self) -> usize {
209
        self as *const Self as *const u8 as usize
210
    }
211
}
212

            
213
/// A cheap way to look up SharedSymbol
214
struct SharedSymbolLookup<T: Into<String> + AsRef<str>> {
215
    name: T,
216
    arity: usize,
217
}
218

            
219
impl<T: Into<String> + AsRef<str>> From<&SharedSymbolLookup<T>> for SharedSymbol {
220
110994
    fn from(lookup: &SharedSymbolLookup<T>) -> Self {
221
        // TODO: Not optimal
222
110994
        let string = lookup.name.as_ref().to_string();
223
110994
        Self::new(string, lookup.arity)
224
110994
    }
225
}
226

            
227
impl<T: Into<String> + AsRef<str>> Equivalent<SharedSymbol> for SharedSymbolLookup<T> {
228
6515071
    fn equivalent(&self, other: &SharedSymbol) -> bool {
229
6515071
        self.name.as_ref() == other.name && self.arity == other.arity
230
6515071
    }
231
}
232

            
233
/// These hash implementations should be the same as `SharedSymbol`.
234
impl<T: Into<String> + AsRef<str>> Hash for SharedSymbolLookup<T> {
235
6614758
    fn hash<H: Hasher>(&self, state: &mut H) {
236
6614758
        self.name.as_ref().hash(state);
237
6614758
        self.arity.hash(state);
238
6614758
    }
239
}
240

            
241
impl Hash for SharedSymbol {
242
172179
    fn hash<H: Hasher>(&self, state: &mut H) {
243
172179
        self.name.hash(state);
244
172179
        self.arity.hash(state);
245
172179
    }
246
}
247

            
248
#[cfg(test)]
249
mod tests {
250
    use std::sync::atomic::Ordering;
251

            
252
    use crate::Symbol;
253
    use crate::storage::THREAD_TERM_POOL;
254

            
255
    #[test]
256
1
    fn test_symbol_sharing() {
257
1
        merc_utilities::test_logger();
258

            
259
1
        let f1 = Symbol::new("f", 2);
260
1
        let f2 = Symbol::new("f", 2);
261

            
262
        // Should be the same object
263
1
        assert_eq!(f1, f2);
264
1
    }
265

            
266
    #[test]
267
1
    fn test_prefix_counter() {
268
1
        merc_utilities::test_logger();
269

            
270
1
        let _symbol = Symbol::new("x69", 0);
271
1
        let _symbol2 = Symbol::new("x_y", 0);
272

            
273
1
        let value = THREAD_TERM_POOL.with(|tp| tp.term_pool().write().expect("Lock poisoned!").register_prefix("x"));
274

            
275
1
        assert_eq!(value.load(Ordering::Relaxed), 70);
276

            
277
1
        let _symbol3 = Symbol::new("x_no_effect", 0);
278
1
        let _symbol4 = Symbol::new("x130", 0);
279

            
280
1
        assert_eq!(value.load(Ordering::Relaxed), 131);
281
1
    }
282
}