1
use std::cell::UnsafeCell;
2
use std::fmt;
3
use std::sync::Arc;
4
use std::sync::LazyLock;
5
use std::sync::Mutex;
6
use std::sync::atomic::AtomicUsize;
7
use std::time::Instant;
8

            
9
use log::debug;
10
use rustc_hash::FxHashSet;
11

            
12
use merc_io::LargeFormatter;
13
use merc_sharedmutex::GlobalBfSharedMutex;
14
use merc_sharedmutex::RecursiveLockReadGuard;
15
use merc_unsafety::ProtectionSet;
16
use merc_unsafety::StablePointer;
17
use merc_utilities::debug_trace;
18

            
19
use crate::ATermIndex;
20
use crate::ATermRef;
21
use crate::Markable;
22
use crate::Symb;
23
use crate::Symbol;
24
use crate::SymbolIndex;
25
use crate::SymbolRef;
26
use crate::Term;
27
use crate::storage::ATermStorage;
28
use crate::storage::SharedTerm;
29
use crate::storage::SymbolPool;
30

            
31
/// This is the global set of protection sets that are managed by the [crate::storage::ThreadTermPool].
32
pub static GLOBAL_TERM_POOL: LazyLock<GlobalBfSharedMutex<GlobalTermPool>> =
33
1764
    LazyLock::new(|| GlobalBfSharedMutex::new(GlobalTermPool::new()));
34

            
35
/// Enables aggressive garbage collection, which is used for testing.
36
pub(crate) const AGGRESSIVE_GC: bool = false;
37

            
38
/// A type alias for the global term pool guard
39
pub(crate) type GlobalTermPoolGuard<'a> = RecursiveLockReadGuard<'a, GlobalTermPool>;
40

            
41
/// A type alias for deletion hooks
42
type DeletionHook = Box<dyn Fn(&ATermIndex) + Sync + Send>;
43

            
44
/// The single global (singleton) term pool, accessed via [GLOBAL_TERM_POOL].
45
pub struct GlobalTermPool {
46
    /// Unique table of all terms with stable pointers for references
47
    terms: ATermStorage,
48
    /// The symbol pool for managing function symbols.
49
    symbol_pool: SymbolPool,
50
    /// The thread-specific protection sets.
51
    thread_pools: ThreadPoolList,
52
    /// A separate protection set for sendable terms, see [crate::ATermSend].
53
    send_term_protection_sets: Vec<Option<Arc<Mutex<ProtectionSet<ATermIndex>>>>>,
54

            
55
    // Data structures used for garbage collection
56
    /// Used to avoid reallocations for the markings of all terms - uses pointers as keys
57
    marked_terms: FxHashSet<ATermIndex>,
58
    /// Used to avoid reallocations for the markings of all symbols
59
    marked_symbols: FxHashSet<SymbolIndex>,
60
    /// A stack used to mark terms recursively.
61
    stack: Vec<ATermIndex>,
62

            
63
    /// Deletion hooks called whenever a term with the given head symbol is deleted.
64
    deletion_hooks: Vec<(Symbol, DeletionHook)>,
65

            
66
    /// Indicates whether automatic garbage collection is enabled.
67
    garbage_collection: bool,
68

            
69
    /// Default terms
70
    int_symbol: SymbolRef<'static>,
71
    empty_list_symbol: SymbolRef<'static>,
72
    list_symbol: SymbolRef<'static>,
73
}
74

            
75
impl GlobalTermPool {
76
1764
    fn new() -> GlobalTermPool {
77
        // Insert the default symbols, mirrors the symbols defined in mCRL2.
78
1764
        let symbol_pool = SymbolPool::new();
79
        // SAFETY: the default symbols are marked on every collection (see `collect_garbage`),
80
        // so these indices stay valid for the lifetime of the pool.
81
1764
        let int_symbol = unsafe { SymbolRef::from_index(&symbol_pool.create("<aterm_int>", 0)) };
82
1764
        let list_symbol = unsafe { SymbolRef::from_index(&symbol_pool.create("<list_constructor>", 2)) };
83
1764
        let empty_list_symbol = unsafe { SymbolRef::from_index(&symbol_pool.create("<empty_list>", 0)) };
84

            
85
1764
        GlobalTermPool {
86
1764
            terms: ATermStorage::new(),
87
1764
            symbol_pool,
88
1764
            thread_pools: ThreadPoolList(Vec::new()),
89
1764
            send_term_protection_sets: Vec::new(),
90
1764
            marked_terms: FxHashSet::default(),
91
1764
            marked_symbols: FxHashSet::default(),
92
1764
            stack: Vec::new(),
93
1764
            deletion_hooks: Vec::new(),
94
1764
            garbage_collection: true,
95
1764
            int_symbol,
96
1764
            list_symbol,
97
1764
            empty_list_symbol,
98
1764
        }
99
1764
    }
100

            
101
    /// Returns the number of terms in the pool.
102
1116389
    pub fn len(&self) -> usize {
103
1116389
        self.terms.len()
104
1116389
    }
105

            
106
    /// Returns whether the term pool is empty.
107
    pub fn is_empty(&self) -> bool {
108
        self.len() == 0
109
    }
110

            
111
    /// Creates a term storing a single integer value.
112
    ///
113
    /// Crate-private: the returned pointer is unprotected, so the caller must protect it
114
    /// (or hand it to a [crate::Return]) before the read guard it was created under drops.
115
9250344
    pub(crate) fn create_int(&self, value: usize) -> (StablePointer<SharedTerm>, bool) {
116
        // SAFETY: `int_symbol` is one of the default symbols, which are marked on every
117
        // collection, so the index stays valid for the pool's lifetime.
118
9250344
        let (index, inserted) = unsafe {
119
9250344
            self.terms
120
9250344
                .insert_int_term(SymbolRef::from_index(self.int_symbol.shared()), value)
121
9250344
        };
122

            
123
9250344
        (index, inserted)
124
9250344
    }
125

            
126
    /// Create a term from a head symbol and an iterator over its arguments
127
    ///
128
    /// Crate-private: the returned pointer is unprotected, see [Self::create_int].
129
105659004
    pub(crate) fn create_term_array<'a, 'b, 'c, S: Symb<'a, 'b>>(
130
105659004
        &'c self,
131
105659004
        symbol: &'b S,
132
105659004
        args: &'c [ATermRef<'c>],
133
105659004
    ) -> (StablePointer<SharedTerm>, bool) {
134
105659004
        self.terms.insert(symbol, args)
135
105659004
    }
136

            
137
    /// Create a function symbol
138
    ///
139
    /// Crate-private: `protect` receives an unprotected index, see [Self::create_int].
140
6609466
    pub(crate) fn create_symbol<P, N>(&self, name: N, arity: usize, protect: P) -> Symbol
141
6609466
    where
142
6609466
        P: FnOnce(SymbolIndex) -> Symbol,
143
6609466
        N: Into<String> + AsRef<str>,
144
    {
145
6609466
        protect(self.symbol_pool.create(name, arity))
146
6609466
    }
147

            
148
    /// Registers a new thread term pool.
149
    ///
150
    /// # Safety
151
    ///
152
    /// Note that the returned `Arc<UnsafeCell<...>>` is not Send or Sync, so it
153
    /// *must* be protected through other means.
154
    #[allow(clippy::arc_with_non_send_sync)]
155
1768
    pub(crate) fn register_thread_term_pool(
156
1768
        &mut self,
157
1768
    ) -> (
158
1768
        Arc<UnsafeCell<SharedTermProtection>>,
159
1768
        Arc<Mutex<ProtectionSet<ATermIndex>>>,
160
1768
    ) {
161
1768
        let protection = Arc::new(UnsafeCell::new(SharedTermProtection {
162
1768
            term_protection_set: ProtectionSet::new(),
163
1768
            symbol_protection_set: ProtectionSet::new(),
164
1768
            container_protection_set: ProtectionSet::new(),
165
1768
            index: self.thread_pools.len(),
166
1768
        }));
167

            
168
1768
        debug!("Registered thread_local protection set(s) {}", self.thread_pools.len());
169
1768
        self.thread_pools.push(Some(protection.clone()));
170

            
171
1768
        let protection_set = Arc::new(Mutex::new(ProtectionSet::new()));
172
1768
        self.send_term_protection_sets.push(Some(protection_set.clone()));
173

            
174
1768
        (protection, protection_set)
175
1768
    }
176

            
177
    /// Deregisters a thread pool.
178
    ///
179
    /// The `send_term_protection_sets` slot is deliberately left in place: a
180
    /// still-live ATermSend created on this thread may outlive it and
181
    /// must keep being marked.
182
1768
    pub(crate) fn deregister_thread_pool(&mut self, index: usize) {
183
1768
        debug!("Removed thread_local protection set(s) {index}");
184
1768
        if let Some(entry) = self.thread_pools.get_mut(index) {
185
1768
            *entry = None;
186
1768
        }
187
1768
    }
188

            
189
    /// Triggers garbage collection if necessary and returns an updated counter for the thread local pool.
190
558159
    pub(crate) fn trigger_garbage_collection(&mut self) -> usize {
191
558159
        if self.garbage_collection {
192
558159
            // Garbage collection is enabled.
193
558159
            self.collect_garbage();
194
558159
        }
195

            
196
558159
        if AGGRESSIVE_GC {
197
            return 1;
198
558159
        }
199

            
200
558159
        self.len()
201
558159
    }
202

            
203
    /// Returns a counter for the unique numeric suffix of the given prefix.
204
1
    pub fn register_prefix(&self, prefix: &str) -> Arc<AtomicUsize> {
205
1
        self.symbol_pool.create_prefix(prefix)
206
1
    }
207

            
208
    /// Removes the registration of a prefix from the symbol pool.
209
    pub fn remove_prefix(&self, prefix: &str) {
210
        self.symbol_pool.remove_prefix(prefix)
211
    }
212

            
213
    /// Register a deletion hook that is called whenever a term is deleted with the given symbol.
214
    ///
215
    /// The hook runs in the middle of garbage collection, when the dying term's arguments may
216
    /// already have been deallocated by an earlier pass over a different arity table. Hooks must
217
    /// therefore not dereference the term's arguments; they may only use the term's address and
218
    /// its head symbol. For the same reason a hook must not create terms or otherwise re-enter the
219
    /// pool mutably (e.g. trigger another collection): the sweep holds `&mut self` throughout.
220
    pub fn register_deletion_hook<F>(&mut self, symbol: SymbolRef<'static>, hook: F)
221
    where
222
        F: Fn(&ATermIndex) + Sync + Send + 'static,
223
    {
224
        self.deletion_hooks.push((symbol.protect(), Box::new(hook)));
225
    }
226

            
227
    /// Enables or disables automatic garbage collection.
228
    pub fn automatic_garbage_collection(&mut self, enabled: bool) {
229
        self.garbage_collection = enabled;
230
    }
231

            
232
    /// Collects garbage terms.
233
558230
    pub fn collect_garbage(&mut self) {
234
        // Mark the default symbols
235
        // SAFETY: mark-set entries only live for the duration of this collection pass
236
        // (the sets are drained by the sweep below), and a marked symbol is by
237
        // definition retained by the sweep.
238
558230
        unsafe {
239
558230
            self.marked_symbols.insert(self.int_symbol.shared().copy());
240
558230
            self.marked_symbols.insert(self.list_symbol.shared().copy());
241
558230
            self.marked_symbols.insert(self.empty_list_symbol.shared().copy());
242
558230
        }
243

            
244
558230
        let mut marker = Marker {
245
558230
            marked_terms: &mut self.marked_terms,
246
558230
            marked_symbols: &mut self.marked_symbols,
247
558230
            stack: &mut self.stack,
248
558230
        };
249

            
250
558230
        let mark_time = Instant::now();
251

            
252
        // Loop through all protection sets and mark the terms.
253
558230
        for pool in self.thread_pools.iter().flatten() {
254
            // SAFETY: We have exclusive access to the global term pool, so no other thread can modify the protection sets.
255
558230
            let pool = unsafe { &mut *pool.get() };
256

            
257
10381392
            for (_root, symbol) in pool.symbol_protection_set.iter() {
258
10380992
                debug_trace!("Marking root {_root} symbol {symbol:?}");
259
10380992
                // Remove all symbols that are not protected
260
10380992
                // SAFETY: the protection set keeps the symbol alive, and the mark-set
261
10380992
                // entry is dropped when the sweep below finishes.
262
10380992
                marker.marked_symbols.insert(unsafe { symbol.copy() });
263
10380992
            }
264

            
265
55316676
            for (_root, term) in pool.term_protection_set.iter() {
266
55316676
                debug_trace!("Marking root {_root} term {term:?}");
267
55316676
                unsafe {
268
55316676
                    ATermRef::from_index(term).mark(&mut marker);
269
55316676
                }
270
            }
271

            
272
            // Marking a `Protected` container goes through `GcMutex::lock`, which takes a
273
            // `read_recursive` on the same lock we already hold for writing here.
274
18917104
            for (_, container) in pool.container_protection_set.iter() {
275
18917104
                container.mark(&mut marker);
276
18917104
            }
277
        }
278

            
279
558232
        for pool in self.send_term_protection_sets.iter().flatten() {
280
558232
            let pool = pool.lock().expect("Lock poisoned!");
281
558232
            for (_root, term) in pool.iter() {
282
25
                debug_trace!("Marking sendable term {term:?}");
283
25
                unsafe {
284
25
                    ATermRef::from_index(term).mark(&mut marker);
285
25
                }
286
            }
287
        }
288

            
289
        // Reclaim send-term protection sets whose owning thread has exited and that hold no
290
        // outstanding ATermSend.
291
558232
        for slot in self.send_term_protection_sets.iter_mut() {
292
558232
            if slot.as_ref().is_some_and(|set| Arc::strong_count(set) == 1) {
293
                *slot = None;
294
558232
            }
295
        }
296

            
297
558230
        let mark_time_elapsed = mark_time.elapsed();
298
558230
        let collect_time = Instant::now();
299

            
300
558230
        let num_of_terms = self.len();
301
558230
        let num_of_symbols = self.symbol_pool.len();
302

            
303
        // Delete all terms that are not marked.
304
        // SAFETY: Marking visited every root in every protection set while holding the exclusive
305
        // lock, so unmarked terms have no live references that could be dereferenced afterwards.
306
        unsafe {
307
39228331
            self.terms.retain(|term| {
308
39228331
                if !self.marked_terms.contains(term) {
309
19692147
                    debug_trace!("Dropping term: {:?}", term);
310

            
311
                    // Call the deletion hooks for the term
312
19692147
                    for (symbol, hook) in &self.deletion_hooks {
313
                        // `term` is still resident in `self.terms` during the
314
                        // retain predicate, so its pointee is valid to read
315
                        // (covered by the enclosing `unsafe` block).
316
                        if symbol == term.deref().symbol() {
317
                            debug_trace!("Calling deletion hook for term: {:?}", term);
318
                            hook(term);
319
                        }
320
                    }
321

            
322
19692147
                    return false;
323
19536184
                }
324

            
325
19536184
                true
326
39228331
            });
327
        }
328

            
329
        // We ensure that every removed symbol is not used anymore.
330
        // SAFETY: Unmarked symbols are not referenced by any marked term or protection set root.
331
        unsafe {
332
18471917
            self.symbol_pool.retain(|symbol| {
333
18471917
                if !self.marked_symbols.contains(symbol) {
334
63673
                    debug_trace!("Dropping symbol: {:?}", symbol);
335
63673
                    return false;
336
18408244
                }
337

            
338
18408244
                true
339
18471917
            });
340
        }
341

            
342
558230
        debug!(
343
            "Garbage collection: marking took {}ms, collection took {}ms, {} terms and {} symbols removed",
344
            mark_time_elapsed.as_millis(),
345
            collect_time.elapsed().as_millis(),
346
            num_of_terms - self.len(),
347
            num_of_symbols - self.symbol_pool.len()
348
        );
349

            
350
558230
        debug!("{}", self.metrics());
351

            
352
        // Print information from the protection sets.
353
558230
        for pool in self.thread_pools.iter().flatten() {
354
            // SAFETY: We have exclusive access to the global term pool, so no other thread can modify the protection sets.
355
558230
            let pool = unsafe { &mut *pool.get() };
356
558230
            debug!("{}", pool.metrics());
357
        }
358

            
359
        // Clear marking data structures
360
558230
        self.marked_terms.clear();
361
558230
        self.marked_symbols.clear();
362
558230
        self.stack.clear();
363
558230
    }
364

            
365
    /// Returns the metrics of the term pool, can be formatted and written to output.
366
    pub fn metrics(&self) -> TermPoolMetrics<'_> {
367
        TermPoolMetrics(self)
368
    }
369

            
370
    /// Marks the given term as being reachable.
371
    ///
372
    /// # Safety
373
    ///
374
    /// Should only be called during garbage collection.
375
    pub unsafe fn mark_term(&mut self, term: &ATermRef<'_>) {
376
        // Ensure that the global term pool is locked for writing.
377
        let mut marker = Marker {
378
            marked_terms: &mut self.marked_terms,
379
            marked_symbols: &mut self.marked_symbols,
380
            stack: &mut self.stack,
381
        };
382
        term.mark(&mut marker);
383
    }
384

            
385
    /// Returns integer function symbol.
386
1768
    pub(crate) fn get_int_symbol(&self) -> &SymbolRef<'static> {
387
1768
        &self.int_symbol
388
1768
    }
389

            
390
    /// Returns integer function symbol.
391
1768
    pub(crate) fn get_list_symbol(&self) -> &SymbolRef<'static> {
392
1768
        &self.list_symbol
393
1768
    }
394

            
395
    /// Returns integer function symbol.
396
1768
    pub(crate) fn get_empty_list_symbol(&self) -> &SymbolRef<'static> {
397
1768
        &self.empty_list_symbol
398
1768
    }
399
}
400

            
401
pub struct TermPoolMetrics<'a>(&'a GlobalTermPool);
402

            
403
impl fmt::Display for TermPoolMetrics<'_> {
404
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
405
        write!(
406
            f,
407
            "There are {} terms, and {} symbols",
408
            self.0.terms.len(),
409
            self.0.symbol_pool.len()
410
        )
411
    }
412
}
413

            
414
/// A newtype wrapping the per-thread protection-set list stored inside
415
/// [`GlobalTermPool`].
416
///
417
/// # Safety
418
///
419
/// Note that [`UnsafeCell`] is not [`Sync`], but we explicitly only use this in
420
/// `&mut self` contexts, so we can safely implement `Sync` for this wrapper.
421
struct ThreadPoolList(Vec<Option<Arc<UnsafeCell<SharedTermProtection>>>>);
422

            
423
// SAFETY: See the safety documentation on `ThreadPoolList`.
424
unsafe impl Sync for ThreadPoolList {}
425
unsafe impl Send for ThreadPoolList {}
426

            
427
impl std::ops::Deref for ThreadPoolList {
428
    type Target = Vec<Option<Arc<UnsafeCell<SharedTermProtection>>>>;
429

            
430
1118228
    fn deref(&self) -> &Self::Target {
431
1118228
        &self.0
432
1118228
    }
433
}
434

            
435
impl std::ops::DerefMut for ThreadPoolList {
436
3536
    fn deref_mut(&mut self) -> &mut Self::Target {
437
3536
        &mut self.0
438
3536
    }
439
}
440

            
441
/// A struct that contains the protection sets for a thread, as well as the
442
/// index of the thread pool in the global term pool.
443
pub struct SharedTermProtection {
444
    /// Protection set for terms
445
    pub term_protection_set: ProtectionSet<ATermIndex>,
446
    /// Protection set to prevent garbage collection of symbols
447
    pub symbol_protection_set: ProtectionSet<SymbolIndex>,
448
    /// Protection set for containers
449
    pub container_protection_set: ProtectionSet<Arc<dyn Markable + Sync + Send>>,
450
    /// Index in global pool's thread pools list
451
    pub index: usize,
452
}
453

            
454
impl SharedTermProtection {
455
    /// Returns the metrics of the term pool, can be formatted and written to output.
456
    pub fn metrics(&self) -> ProtectionMetrics<'_> {
457
        ProtectionMetrics(self)
458
    }
459
}
460

            
461
/// A struct that can be used to print the performance of the protection sets.
462
pub struct ProtectionMetrics<'a>(&'a SharedTermProtection);
463

            
464
impl fmt::Display for ProtectionMetrics<'_> {
465
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
466
        writeln!(
467
            f,
468
            "Protection set {} has {} roots, max {} and {} insertions",
469
            self.0.index,
470
            LargeFormatter(self.0.term_protection_set.len()),
471
            LargeFormatter(self.0.term_protection_set.maximum_size()),
472
            LargeFormatter(self.0.term_protection_set.number_of_insertions())
473
        )?;
474

            
475
        writeln!(
476
            f,
477
            "Containers: {} roots, max {} and {} insertions",
478
            LargeFormatter(self.0.container_protection_set.len()),
479
            LargeFormatter(self.0.container_protection_set.maximum_size()),
480
            LargeFormatter(self.0.container_protection_set.number_of_insertions()),
481
        )?;
482

            
483
        write!(
484
            f,
485
            "Symbols: {} roots, max {} and {} insertions",
486
            LargeFormatter(self.0.symbol_protection_set.len()),
487
            LargeFormatter(self.0.symbol_protection_set.maximum_size()),
488
            LargeFormatter(self.0.symbol_protection_set.number_of_insertions()),
489
        )
490
    }
491
}
492

            
493
/// Helper struct to pass private data required to mark term recursively.
494
pub struct Marker<'a> {
495
    marked_terms: &'a mut FxHashSet<ATermIndex>,
496
    marked_symbols: &'a mut FxHashSet<SymbolIndex>,
497
    stack: &'a mut Vec<ATermIndex>,
498
}
499

            
500
impl Marker<'_> {
501
    // Marks the given term as being reachable.
502
108239034
    pub fn mark(&mut self, term: &ATermRef<'_>) {
503
        // SAFETY: all copies below go into the mark sets and the work stack, which are
504
        // drained before the collection pass ends, and a marked term or symbol is by
505
        // definition retained by the sweep; `term` itself is alive for the borrow.
506
        unsafe {
507
108239034
            if !self.marked_terms.contains(term.shared()) {
508
5134698
                self.stack.push(term.shared().copy());
509

            
510
24670882
                while let Some(term) = self.stack.pop() {
511
                    // Each term should be marked.
512
19536184
                    self.marked_terms.insert(term.copy());
513

            
514
                    // Mark the function symbol.
515
19536184
                    self.marked_symbols.insert(term.deref().symbol().shared().copy());
516

            
517
                    // For some terms, such as ATermInt, we must ONLY consider the valid arguments (indicated by the arity)
518
37988321
                    for arg in term.deref().arguments()[0..term.deref().symbol().arity()].iter() {
519
                        // Skip if unnecessary, otherwise mark before pushing to stack since it can be shared.
520
37988321
                        if !self.marked_terms.contains(arg.shared()) {
521
14401486
                            self.marked_terms.insert(arg.shared().copy());
522
14401486
                            self.marked_symbols.insert(arg.get_head_symbol().shared().copy());
523
14401486
                            self.stack.push(arg.shared().copy());
524
23586835
                        }
525
                    }
526
                }
527
103104336
            }
528
        }
529
108239034
    }
530

            
531
    /// Marks the given symbol as being reachable.
532
6805999
    pub fn mark_symbol(&mut self, symbol: &SymbolRef<'_>) {
533
        // SAFETY: see `mark`; the entry is dropped when the sweep finishes and a marked
534
        // symbol is retained by it.
535
6805999
        self.marked_symbols.insert(unsafe { symbol.shared().copy() });
536
6805999
    }
537
}
538

            
539
#[cfg(test)]
540
mod tests {
541
    use std::collections::HashMap;
542

            
543
    use merc_utilities::random_test;
544

            
545
    use crate::ATerm;
546
    use crate::Symbol;
547
    use crate::Term;
548
    use crate::random_term;
549

            
550
    #[test]
551
    #[cfg_attr(miri, ignore)]
552
1
    fn test_maximal_sharing() {
553
100
        random_test(100, |rng| {
554
100
            let mut terms = HashMap::new();
555

            
556
100
            for _ in 0..1000 {
557
100000
                let term = random_term(rng, &[("f".into(), 2), ("g".into(), 1)], &["a".to_string()], 10);
558

            
559
100000
                let representation = format!("{}", term);
560
100000
                if let Some(entry) = terms.get(&representation) {
561
45744
                    assert_eq!(term, *entry, "There is another term with the same representation");
562
54256
                } else {
563
54256
                    terms.insert(representation, term);
564
54256
                }
565
            }
566
100
        });
567
1
    }
568

            
569
    #[test]
570
    #[should_panic]
571
1
    fn test_term_out_of_bound_arity() {
572
1
        let c = ATerm::constant(&Symbol::new("a", 0));
573

            
574
1
        let t = ATerm::with_args(&Symbol::new("f", 1), &[c.copy(), c.copy()]);
575

            
576
        // Currently we check on access
577
1
        let _ = t.arg(1);
578
1
    }
579
}