1
//! Host-side implementation of the [`SabreRewriteVTable`].
2
//!
3
//! These functions are the only place where the global term pool is touched.
4
//! They are compiled into the host binary; the host hands their addresses to
5
//! the loaded rewriter library via [`crate::set_rewrite_vtable`]. Every
6
//! function therefore runs in the host's address space, using the host's
7
//! `merc_aterm` globals, regardless of which crate's code called it.
8

            
9
use std::ffi::c_void;
10
use std::mem::ManuallyDrop;
11
use std::ptr::NonNull;
12
use std::ptr::slice_from_raw_parts_mut;
13

            
14
use merc_aterm::ATermIndex;
15
use merc_aterm::ATermRef;
16
use merc_aterm::Symb;
17
use merc_aterm::SymbolRef;
18
use merc_aterm::Term;
19
use merc_aterm::storage::SharedTerm;
20
use merc_data::DataApplication;
21
use merc_data::DataExpression;
22
use merc_data::DataExpressionRef;
23
use merc_unsafety::SliceDst;
24
use merc_unsafety::StablePointer;
25

            
26
use crate::DataExpressionFFI;
27
use crate::DataExpressionRefFFI;
28
use crate::SabreRewriteVTable;
29

            
30
/// Wraps a raw shared term node address as a transient [`ATermIndex`].
31
///
32
/// # Safety
33
///
34
/// `node` must be the address of a live `SharedTerm` owned by the global term
35
/// pool. The returned index is a non-owning view: it must not outlive the node.
36
3160
unsafe fn node_to_index(node: *const c_void) -> ATermIndex {
37
    unsafe {
38
        // `SharedTerm` is a slice DST, so its pointer is wide. Recover the
39
        // slice length from the symbol header stored inline at offset 0,
40
        // reading it by reference so the symbol's debug reference counter is
41
        // untouched (equivalent to what `Erasable::unerase` does).
42
3160
        let symbol: &SymbolRef<'static> = &*(node as *const SymbolRef<'static>);
43
3160
        let len = symbol.arity();
44

            
45
3160
        let slice = NonNull::new_unchecked(slice_from_raw_parts_mut(node as *mut (), len));
46
3160
        StablePointer::from_ptr(<SharedTerm as SliceDst>::retype(slice))
47
    }
48
3160
}
49

            
50
/// Returns the shared term node address backing a term reference.
51
2049
fn node_of<'a, T: Term<'a, 'a>>(term: &T) -> *const c_void {
52
2049
    term.shared().ptr().as_ptr() as *const c_void
53
2049
}
54

            
55
/// Wraps an owned [`DataExpression`] as a [`DataExpressionFFI`] handle.
56
729
fn into_ffi(expr: DataExpression) -> DataExpressionFFI {
57
729
    let node = node_of(&expr);
58
729
    DataExpressionFFI {
59
729
        node,
60
729
        handle: Box::into_raw(Box::new(expr)) as *mut c_void,
61
729
    }
62
729
}
63

            
64
7
unsafe extern "C-unwind" fn host_arity(node: *const c_void) -> usize {
65
    unsafe {
66
7
        let index = node_to_index(node);
67
7
        let term = ATermRef::from_index(&index);
68
7
        DataExpressionRef::from(term).data_arguments().len()
69
    }
70
7
}
71

            
72
754
unsafe extern "C-unwind" fn host_data_arg(node: *const c_void, index: usize) -> *const c_void {
73
    unsafe {
74
754
        let term_index = node_to_index(node);
75
754
        let term = ATermRef::from_index(&term_index);
76
754
        node_of(&DataExpressionRef::from(term).data_arg(index))
77
    }
78
754
}
79

            
80
565
unsafe extern "C-unwind" fn host_data_function_symbol(node: *const c_void) -> *const c_void {
81
    unsafe {
82
565
        let index = node_to_index(node);
83
565
        let term = ATermRef::from_index(&index);
84
565
        node_of(&DataExpressionRef::from(term).data_function_symbol())
85
    }
86
565
}
87

            
88
559
unsafe extern "C-unwind" fn host_operation_id(node: *const c_void) -> usize {
89
    unsafe {
90
559
        let index = node_to_index(node);
91
559
        ATermRef::from_index(&index).index()
92
    }
93
559
}
94

            
95
364
unsafe extern "C-unwind" fn host_create(
96
364
    head: *const c_void,
97
364
    args: *const DataExpressionRefFFI<'static>,
98
364
    len: usize,
99
364
) -> DataExpressionFFI {
100
    unsafe {
101
364
        let head_index = node_to_index(head);
102
364
        let head_term = ATermRef::from_index(&head_index);
103

            
104
364
        if len == 0 {
105
            // A constant data expression is the function symbol itself.
106
6
            return into_ffi(DataExpressionRef::from(head_term).protect());
107
358
        }
108

            
109
        // Reconstruct argument references. The index wrappers must outlive the
110
        // ATermRefs derived from them, so keep them in a separate vector.
111
358
        let arg_refs = std::slice::from_raw_parts(args, len);
112
546
        let arg_indices: Vec<ATermIndex> = arg_refs.iter().map(|a| node_to_index(a.shared())).collect();
113
546
        let arg_terms: Vec<ATermRef<'_>> = arg_indices.iter().map(|i| ATermRef::from_index(i)).collect();
114

            
115
358
        let application = DataApplication::with_args(&head_term, &arg_terms);
116
358
        into_ffi(application.into())
117
    }
118
364
}
119

            
120
365
unsafe extern "C-unwind" fn host_protect(node: *const c_void) -> DataExpressionFFI {
121
    unsafe {
122
365
        let index = node_to_index(node);
123
365
        let term = ATermRef::from_index(&index);
124
365
        into_ffi(DataExpressionRef::from(term).protect())
125
    }
126
365
}
127

            
128
728
unsafe extern "C-unwind" fn host_destroy(handle: *mut c_void) {
129
728
    if !handle.is_null() {
130
728
        drop(unsafe { Box::from_raw(handle as *mut DataExpression) });
131
728
    }
132
728
}
133

            
134
/// Builds the vtable of host operations. The returned function pointers refer to
135
/// the host's monomorphised code, so passing this to a loaded library makes that
136
/// library perform all term-pool work in the host's address space.
137
1
pub fn rewrite_vtable() -> SabreRewriteVTable {
138
1
    SabreRewriteVTable {
139
1
        arity: host_arity,
140
1
        data_arg: host_data_arg,
141
1
        data_function_symbol: host_data_function_symbol,
142
1
        operation_id: host_operation_id,
143
1
        create: host_create,
144
1
        protect: host_protect,
145
1
        destroy: host_destroy,
146
1
    }
147
1
}
148

            
149
/// Builds a borrowed reference handle for a term owned by the host.
150
1
pub fn data_expression_ref_from_term<'a, T: Term<'a, 'a>>(term: &T) -> DataExpressionRefFFI<'a> {
151
1
    DataExpressionRefFFI::from_raw(node_of(term))
152
1
}
153

            
154
/// Consumes a handle returned across the boundary and recovers the owned
155
/// [`DataExpression`], transferring its protection to the caller.
156
///
157
/// # Safety
158
///
159
/// `expr` must be an owned handle produced by [`rewrite_vtable`]'s `create`,
160
/// `protect`, or the generated `rewrite` entry point, and must not be used
161
/// afterwards.
162
1
pub unsafe fn into_data_expression(expr: DataExpressionFFI) -> DataExpression {
163
    // Avoid the `Drop` impl, which would `destroy` the handle we are taking.
164
1
    let expr = ManuallyDrop::new(expr);
165
1
    *unsafe { Box::from_raw(expr.handle as *mut DataExpression) }
166
1
}