1
use std::alloc::Layout;
2
use std::alloc::LayoutError;
3
use std::fmt;
4
use std::hash::Hash;
5
use std::mem::ManuallyDrop;
6
use std::ptr;
7
use std::ptr::NonNull;
8
use std::ptr::slice_from_raw_parts_mut;
9

            
10
use equivalent::Equivalent;
11
use merc_unsafety::Erasable;
12
use merc_unsafety::ErasedPtr;
13
use merc_unsafety::SliceDst;
14
use merc_unsafety::repr_c;
15

            
16
use crate::ATermRef;
17
use crate::Symb;
18
use crate::SymbolRef;
19
use crate::Term;
20

            
21
/// The underlying type of terms that are actually shared.
22
///
23
/// # Details
24
///
25
/// Uses a C representation and is a dynamically sized type for compact memory
26
/// usage. This allows us to avoid storing the length and capacity of an
27
/// underlying vector. As such this is even more compact than `smallvec`.
28
#[repr(C)]
29
pub struct SharedTerm {
30
    symbol: SymbolRef<'static>,
31
    annotated: bool,
32
    arguments: [TermOrAnnotation],
33
}
34

            
35
impl Drop for SharedTerm {
36
    fn drop(&mut self) {
37
        // Drop all term arguments by manually calling drop on ManuallyDrop wrappers
38
        // We only need to drop terms, not the annotation index
39
        let length = self.arguments().len();
40
        for arg in &mut self.arguments[0..length] {
41
            unsafe {
42
                ManuallyDrop::drop(&mut arg.term);
43
            }
44
        }
45
    }
46
}
47

            
48
impl PartialEq for SharedTerm {
49
47764
    fn eq(&self, other: &Self) -> bool {
50
47764
        self.symbol == other.symbol && self.annotation() == other.annotation() && self.arguments() == other.arguments()
51
47764
    }
52
}
53

            
54
impl Eq for SharedTerm {}
55

            
56
/// This is used to store the annotation as argument of a term without consuming additional memory for terms that have no annotation.
57
#[repr(C)]
58
pub union TermOrAnnotation {
59
    term: ManuallyDrop<ATermRef<'static>>,
60
    index: usize,
61
}
62

            
63
/// Note that the length is stored in the symbol's arity
64
unsafe impl SliceDst for SharedTerm {
65
562887
    fn layout_for(len: usize) -> Result<Layout, LayoutError> {
66
562887
        let header_layout = Layout::new::<SymbolRef<'static>>();
67
562887
        let annotated_layout = Layout::new::<bool>();
68
562887
        let slice_layout = Layout::array::<ATermRef<'static>>(len)?;
69

            
70
562887
        repr_c(&[header_layout, annotated_layout, slice_layout])
71
562887
    }
72

            
73
562886
    fn retype(ptr: std::ptr::NonNull<[()]>) -> NonNull<Self> {
74
562886
        unsafe { NonNull::new_unchecked(ptr.as_ptr() as *mut _) }
75
562886
    }
76

            
77
    fn length(&self) -> usize {
78
        self.symbol().arity()
79
    }
80
}
81

            
82
unsafe impl Erasable for SharedTerm {
83
    fn erase(this: NonNull<Self>) -> ErasedPtr {
84
        this.cast()
85
    }
86

            
87
    unsafe fn unerase(this: ErasedPtr) -> NonNull<Self> {
88
        unsafe {
89
            let symbol: SymbolRef = ptr::read(this.as_ptr().cast());
90
            let len = symbol.arity();
91

            
92
            let raw = NonNull::new_unchecked(slice_from_raw_parts_mut(this.as_ptr().cast(), len));
93
            Self::retype(raw)
94
        }
95
    }
96
}
97

            
98
impl fmt::Debug for SharedTerm {
99
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100
        write!(
101
            f,
102
            "SharedTerm {{ symbol: {:?}, arguments: {:?}, annotation: {:?} }}",
103
            self.symbol,
104
            self.arguments(),
105
            self.annotation()
106
        )
107
    }
108
}
109

            
110
impl SharedTerm {
111
6725016004
    pub fn symbol(&self) -> &SymbolRef<'_> {
112
6725016004
        &self.symbol
113
6725016004
    }
114

            
115
451557754
    pub fn arguments(&self) -> &[ATermRef<'static>] {
116
        unsafe {
117
451557754
            if self.annotated {
118
3782844
                std::mem::transmute::<&[TermOrAnnotation], &[ATermRef<'static>]>(
119
3782844
                    &self.arguments[0..self.arguments.len() - 1],
120
3782844
                )
121
            } else {
122
447774910
                std::mem::transmute::<&[TermOrAnnotation], &[ATermRef<'static>]>(&self.arguments)
123
            }
124
        }
125
451557754
    }
126

            
127
58472779
    pub fn annotation(&self) -> Option<usize> {
128
58472779
        if self.annotated {
129
            unsafe {
130
7524378
                Some(
131
7524378
                    self.arguments
132
7524378
                        .last()
133
7524378
                        .expect("For annotated terms the last argument should store the annotation")
134
7524378
                        .index,
135
7524378
                )
136
            }
137
        } else {
138
50948401
            None
139
        }
140
58472779
    }
141

            
142
    /// Returns a unique index for this shared term.
143
127509490
    pub fn index(&self) -> usize {
144
127509490
        self as *const Self as *const u8 as usize
145
127509490
    }
146

            
147
    /// Returns the length for a [SharedTermLookup]
148
53794955
    pub(crate) fn length_for(object: &SharedTermLookup) -> usize {
149
53794955
        object.arguments.len() + if object.annotation.is_some() { 1 } else { 0 }
150
53794955
    }
151

            
152
    /// Constructs an uninitialised ptr from a [SharedTermLookup]
153
562886
    pub(crate) unsafe fn construct(ptr: *mut SharedTerm, object: &SharedTermLookup) {
154
562886
        let header_layout = Layout::new::<SymbolRef<'static>>();
155
562886
        let annotated_layout = Layout::new::<bool>();
156
562886
        let slice_layout =
157
562886
            Layout::array::<ATermRef<'static>>(object.arguments.len()).expect("Layout should not exceed isize");
158

            
159
562886
        let (header_layout, annotated_offset) = header_layout
160
562886
            .extend(annotated_layout)
161
562886
            .expect("Layout should not exceed isize");
162
562886
        let (_, slice_offset) = header_layout
163
562886
            .extend(slice_layout)
164
562886
            .expect("Layout should not exceed isize");
165

            
166
        unsafe {
167
562886
            ptr.cast::<SymbolRef<'static>>()
168
562886
                .write(SymbolRef::from_index(object.symbol.shared()));
169
562886
            let annotated = object.annotation.is_some();
170
562886
            ptr.byte_offset(annotated_offset as isize)
171
562886
                .cast::<bool>()
172
562886
                .write(annotated);
173

            
174
1800341
            for (index, argument) in object.arguments.iter().enumerate() {
175
1800341
                ptr.byte_offset(slice_offset as isize)
176
1800341
                    .cast::<TermOrAnnotation>()
177
1800341
                    .add(index)
178
1800341
                    .write(TermOrAnnotation {
179
1800341
                        term: ManuallyDrop::new(ATermRef::from_index(argument.shared())),
180
1800341
                    });
181
1800341
            }
182

            
183
562886
            if let Some(value) = object.annotation {
184
1885
                ptr.byte_offset(slice_offset as isize)
185
1885
                    .cast::<TermOrAnnotation>()
186
1885
                    .add(object.arguments.len())
187
1885
                    .write(TermOrAnnotation { index: value });
188
561001
            }
189
        }
190
562886
    }
191
}
192

            
193
impl Hash for SharedTerm {
194
1412715
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
195
1412715
        self.symbol.hash(state);
196
1412715
        self.arguments().hash(state);
197
1412715
        self.annotation().hash(state);
198
1412715
    }
199
}
200

            
201
/// A cheap reference to the elements of a shared term that can be used for
202
/// lookup of terms without allocating.
203
pub(crate) struct SharedTermLookup<'a> {
204
    pub(crate) symbol: SymbolRef<'a>,
205
    pub(crate) arguments: &'a [ATermRef<'a>],
206
    pub(crate) annotation: Option<usize>,
207
}
208

            
209
impl Equivalent<SharedTerm> for SharedTermLookup<'_> {
210
53415364
    fn equivalent(&self, other: &SharedTerm) -> bool {
211
53415364
        self.symbol == other.symbol && self.arguments == other.arguments() && self.annotation == other.annotation()
212
53415364
    }
213
}
214

            
215
/// This Hash implement must be the same as for [SharedTerm]
216
impl Hash for SharedTermLookup<'_> {
217
53794955
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
218
53794955
        self.symbol.hash(state);
219
53794955
        self.arguments.hash(state);
220
53794955
        self.annotation.hash(state);
221
53794955
    }
222
}
223

            
224
#[cfg(test)]
225
mod tests {
226
    use allocator_api2::alloc::Global;
227
    use merc_unsafety::AllocatorDst;
228

            
229
    use crate::ATerm;
230
    use crate::Symbol;
231
    use crate::Term;
232

            
233
    use super::*;
234

            
235
    // #[test]
236
    // fn test_shared_symbol_size() {
237
    //     // Cannot be a const assertion since the size depends on the length.
238
    //     assert_eq!(
239
    //         SharedTerm::layout_for(0)
240
    //             .expect("The layout should not overflow")
241
    //             .size(),
242
    //         1 * std::mem::size_of::<usize>(),
243
    //         "A SharedTerm without arguments should be the same size as the Symbol"
244
    //     );
245

            
246
    //     assert_eq!(
247
    //         SharedTerm::layout_for(2)
248
    //             .expect("The layout should not overflow")
249
    //             .size(),
250
    //         3 * std::mem::size_of::<usize>(),
251
    //         "A SharedTerm with arity two should be the same size as the Symbol and two ATermRef arguments"
252
    //     );
253
    // }
254

            
255
    #[test]
256
1
    fn test_shared_term_lookup() {
257
1
        let symbol = Symbol::new("a", 2);
258

            
259
1
        let term = ATerm::constant(&Symbol::new("b", 0));
260

            
261
1
        let lookup = SharedTermLookup {
262
1
            symbol: symbol.copy(),
263
1
            arguments: &[term.copy(), term.copy()],
264
1
            annotation: None,
265
1
        };
266

            
267
1
        let ptr = Global.allocate_slice_dst(2).expect("Could not allocate slice dst");
268

            
269
        unsafe {
270
1
            SharedTerm::construct(ptr.as_ptr(), &lookup);
271
1
            assert_eq!(
272
1
                *ptr.as_ref().symbol(),
273
1
                symbol.copy(),
274
                "The symbol should match the lookup symbol"
275
            );
276
1
            assert_eq!(
277
1
                ptr.as_ref().arguments()[0],
278
1
                term.copy(),
279
                "The arguments should match the lookup arguments"
280
            );
281
1
            assert_eq!(
282
1
                ptr.as_ref().arguments()[1],
283
1
                term.copy(),
284
                "The arguments should match the lookup arguments"
285
            );
286
        }
287

            
288
1
        Global.deallocate_slice_dst(ptr, 2);
289
1
    }
290
}