1
//! FFI boundary between the host (which owns the global term pool) and the
2
//! dynamically compiled-and-loaded rewriter library.
3
//!
4
//! # Why a vtable
5
//!
6
//! The generated rewriter is compiled as a separate `cdylib` and loaded with
7
//! `dlopen`. It therefore has its own statically-linked copy of `merc_aterm`
8
//! and, crucially, its own copy of every global (the term pool, the `dashmap`
9
//! shard tables, ...). Sharing a `#[repr(Rust)]` structure such as the term
10
//! pool across that boundary by raw pointer is unsound: Rust gives no layout
11
//! guarantee for `repr(Rust)` types across independent compilations, so the two
12
//! sides can disagree on where a field lives.
13
//!
14
//! Instead, *all* term-pool access happens on the host side. The host fills in
15
//! a [`SabreRewriteVTable`] of `extern "C-unwind"` function pointers and passes
16
//! it to the loaded library through [`set_rewrite_vtable`]. The generated code
17
//! only ever shuffles around opaque, `#[repr(C)]`, pointer-sized handles and
18
//! dispatches every real operation back into the host through the vtable. Since
19
//! only a `#[repr(C)]` vtable and `#[repr(C)]` handles cross the boundary, this
20
//! works regardless of the `rustc` version used to build either side.
21

            
22
use std::ffi::c_void;
23
use std::marker::PhantomData;
24
use std::sync::OnceLock;
25

            
26
mod host;
27

            
28
pub use host::*;
29

            
30
/// Table of host-provided operations. Every field is an `extern "C-unwind"`
31
/// function pointer into the host binary, so the ABI is stable across `rustc`
32
/// versions. The generated library never implements these; it only calls them.
33
#[repr(C)]
34
#[derive(Clone, Copy)]
35
pub struct SabreRewriteVTable {
36
    /// Returns the number of data arguments of the term.
37
    pub arity: unsafe extern "C-unwind" fn(node: *const c_void) -> usize,
38
    /// Returns the `index`-th data argument of the term.
39
    pub data_arg: unsafe extern "C-unwind" fn(node: *const c_void, index: usize) -> *const c_void,
40
    /// Returns the head data function symbol of the term.
41
    pub data_function_symbol: unsafe extern "C-unwind" fn(node: *const c_void) -> *const c_void,
42
    /// Returns the operation identifier of a data function symbol.
43
    pub operation_id: unsafe extern "C-unwind" fn(node: *const c_void) -> usize,
44
    /// Constructs (and protects) a data expression from a head symbol and `len`
45
    /// argument references. With `len == 0` the result is the constant denoted
46
    /// by the head symbol.
47
    pub create: unsafe extern "C-unwind" fn(
48
        head: *const c_void,
49
        args: *const DataExpressionRefFFI<'static>,
50
        len: usize,
51
    ) -> DataExpressionFFI,
52
    /// Protects an existing term node, returning an owned handle.
53
    pub protect: unsafe extern "C-unwind" fn(node: *const c_void) -> DataExpressionFFI,
54
    /// Releases an owned handle previously returned by `create`/`protect`.
55
    pub destroy: unsafe extern "C-unwind" fn(handle: *mut c_void),
56
}
57

            
58
/// Storage for the vtable installed by the host. Set once, at library load
59
/// time, through [`set_rewrite_vtable`].
60
static VTABLE: OnceLock<SabreRewriteVTable> = OnceLock::new();
61

            
62
/// Installs the host-provided [`SabreRewriteVTable`]. Called once by the loaded
63
/// library's `initialise` entry point.
64
///
65
/// # Safety
66
///
67
/// `vtable` must point to a valid [`SabreRewriteVTable`] whose function pointers
68
/// remain valid for the lifetime of the process (they point into the host).
69
pub unsafe fn set_rewrite_vtable(vtable: *const SabreRewriteVTable) {
70
    // The vtable is copied by value, so the caller's storage need not outlive
71
    // this call. A second call is ignored: the host installs exactly one.
72
    let _ = VTABLE.set(unsafe { *vtable });
73
}
74

            
75
/// Returns the installed vtable, panicking if the library was not initialised.
76
#[inline]
77
fn vtable() -> &'static SabreRewriteVTable {
78
    VTABLE
79
        .get()
80
        .expect("the rewrite vtable was not installed; call `initialise` first")
81
}
82

            
83
/// An owned, protected data expression. Opaque on both sides of the boundary:
84
/// `node` is the address of the shared term node (used for cheap, pure
85
/// comparisons and to derive borrows), and `handle` owns the host-side
86
/// protection that keeps the node alive.
87
#[repr(C)]
88
pub struct DataExpressionFFI {
89
    node: *const c_void,
90
    handle: *mut c_void,
91
}
92

            
93
impl DataExpressionFFI {
94
    /// Constructs a data expression from a head symbol and its arguments.
95
    pub fn create(symbol: DataExpressionRefFFI<'_>, args: &[DataExpressionRefFFI<'_>]) -> DataExpressionFFI {
96
        // SAFETY: the vtable is installed and `args` is a valid slice. The
97
        // lifetime on the references is erased for the call; the host only
98
        // reads them synchronously.
99
        unsafe { (vtable().create)(symbol.node, args.as_ptr().cast(), args.len()) }
100
    }
101

            
102
    /// Constructs the constant denoted by the given data function symbol.
103
    pub fn constant(symbol: DataExpressionRefFFI<'_>) -> DataExpressionFFI {
104
        Self::create(symbol, &[])
105
    }
106

            
107
    /// Returns a borrowed reference to this expression.
108
    pub fn copy(&self) -> DataExpressionRefFFI<'_> {
109
        DataExpressionRefFFI::from_raw(self.node)
110
    }
111

            
112
    /// Returns a fresh owned, protected copy of this expression.
113
    pub fn protect(&self) -> DataExpressionFFI {
114
        unsafe { (vtable().protect)(self.node) }
115
    }
116

            
117
    /// Returns the shared term node address, used for maximal-sharing equality.
118
    pub fn shared(&self) -> *const c_void {
119
        self.node
120
    }
121
}
122

            
123
impl Drop for DataExpressionFFI {
124
    fn drop(&mut self) {
125
        if !self.handle.is_null() {
126
            unsafe { (vtable().destroy)(self.handle) }
127
        }
128
    }
129
}
130

            
131
/// A borrowed reference to a data expression: just the address of the shared
132
/// term node. Valid for as long as something keeps that node protected.
133
#[repr(C)]
134
#[derive(Clone, Copy)]
135
pub struct DataExpressionRefFFI<'a> {
136
    node: *const c_void,
137
    _marker: PhantomData<&'a ()>,
138
}
139

            
140
impl<'a> DataExpressionRefFFI<'a> {
141
    /// Builds a reference from a raw shared term node address.
142
    ///
143
    /// Generated code uses this to refer to symbols and terms whose addresses
144
    /// were baked in at code-generation time.
145
    ///
146
    /// # Safety
147
    ///
148
    /// `node` must be the address of a live shared term node that stays
149
    /// protected from garbage collection for the lifetime `'a`.
150
    pub unsafe fn from_ptr(node: usize) -> Self {
151
        Self::from_raw(node as *const c_void)
152
    }
153

            
154
    /// Builds a reference from a raw shared term node pointer.
155
1
    pub(crate) fn from_raw(node: *const c_void) -> Self {
156
1
        Self {
157
1
            node,
158
1
            _marker: PhantomData,
159
1
        }
160
1
    }
161

            
162
    /// Returns the shared term node address, used for maximal-sharing equality.
163
546
    pub fn shared(&self) -> *const c_void {
164
546
        self.node
165
546
    }
166

            
167
    /// Returns a copy of this reference.
168
    pub fn copy(&self) -> DataExpressionRefFFI<'a> {
169
        *self
170
    }
171

            
172
    /// Returns the number of data arguments of this expression.
173
    pub fn arity(&self) -> usize {
174
        unsafe { (vtable().arity)(self.node) }
175
    }
176

            
177
    /// Returns the `index`-th data argument of this expression.
178
    pub fn data_arg(&self, index: usize) -> DataExpressionRefFFI<'a> {
179
        DataExpressionRefFFI::from_raw(unsafe { (vtable().data_arg)(self.node, index) })
180
    }
181

            
182
    /// Returns the head data function symbol of this expression.
183
    pub fn data_function_symbol(&self) -> DataFunctionSymbolRefFFI<'a> {
184
        DataFunctionSymbolRefFFI::from_raw(unsafe { (vtable().data_function_symbol)(self.node) })
185
    }
186

            
187
    /// Protects this expression, returning an owned handle.
188
    pub fn protect(&self) -> DataExpressionFFI {
189
        unsafe { (vtable().protect)(self.node) }
190
    }
191
}
192

            
193
/// A borrowed reference to a data function symbol.
194
#[repr(C)]
195
#[derive(Clone, Copy)]
196
pub struct DataFunctionSymbolRefFFI<'a> {
197
    node: *const c_void,
198
    _marker: PhantomData<&'a ()>,
199
}
200

            
201
impl<'a> DataFunctionSymbolRefFFI<'a> {
202
    /// Builds a reference from a raw shared term node pointer.
203
    pub(crate) fn from_raw(node: *const c_void) -> Self {
204
        Self {
205
            node,
206
            _marker: PhantomData,
207
        }
208
    }
209

            
210
    /// Returns the operation identifier of this data function symbol.
211
    pub fn operation_id(&self) -> usize {
212
        unsafe { (vtable().operation_id)(self.node) }
213
    }
214
}
215

            
216
impl<'a> From<DataFunctionSymbolRefFFI<'a>> for DataExpressionRefFFI<'a> {
217
    fn from(symbol: DataFunctionSymbolRefFFI<'a>) -> Self {
218
        // A data function symbol is itself a data expression.
219
        DataExpressionRefFFI::from_raw(symbol.node)
220
    }
221
}