1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
//! Prolog initialization logic.
//!
//! When using swipl-rs to embed prolog, you need to ensure that you
//! initialize SWI-Prolog before you do anything else with
//! SWI-Prolog. The one exception is registering of foreign
//! predicates, which you're allowed to do at any point.
//!
//! Functions for both prolog initialization as well as foreign
//! predicate registration are defined here.

use crate::engine::*;
use crate::fli::*;

use lazy_static::*;
use std::convert::TryInto;
use std::ffi::CString;
use std::os::raw::c_int;
use std::sync::RwLockWriteGuard;
use std::sync::{Arc, RwLock};

lazy_static! {
    static ref INITIALIZATION_STATE: Arc<RwLock<Option<Engine>>> = Arc::new(RwLock::new(None));
}

/// Activate the main prolog engine, or panic if it has alread been activated, or if SWI-Prolog was not initialized yet.
pub fn activate_main() -> EngineActivation<'static> {
    let initialized = INITIALIZATION_STATE.read().unwrap();
    unsafe { std::mem::transmute((*initialized).as_ref().unwrap().activate()) }
}

/// Check if SWI-Prolog has been initialized.
pub fn is_swipl_initialized() -> bool {
    unsafe { PL_is_initialised(std::ptr::null_mut(), std::ptr::null_mut()) != 0 }
}

/// Panic if SWI-Prolog has not been initialized.
pub fn assert_swipl_is_initialized() {
    if !is_swipl_initialized() {
        panic!("SWI-Prolog has not yet been initialized");
    }
}

static ARG0: &[u8] = b"rust-swipl\0"; // fake program name
static ARG1: &[u8] = b"--quiet\0"; // suppress swipl banner printing

/// Initialize SWI-Prolog.
///
/// This requires a borrow to a MainEngineActivator, whose lifetime will be used to
/// After initializing, the default 'main' engine is active on the
/// calling thread.  If SWI-Prolog was already initialized, this will
/// do nothing, and None will be returned. Otherwise, An
/// `EngineActivation` will be returned containing the main prolog
/// engine.
pub fn initialize_swipl() -> Option<EngineActivation<'static>> {
    if is_swipl_initialized() {
        return None;
    }

    // lock the rest of this initialization function to prevent concurrent initializers. Ideally this should happen in swipl itself, but unfortunately, it doesn't.
    let initialized = INITIALIZATION_STATE.write().unwrap();
    // There's actually a slight chance that initialization happened just now by some other thread. So check again.
    if initialized.is_some() {
        return None;
    }

    initialize_internal(initialized)
}

/// Initialize SWI-Prolog with a given saved state.
///
/// After initializing, the default 'main' engine is active on the
/// calling thread.  If SWI-Prolog was already initialized, this will
/// do nothing, and None will be returned. Otherwise, An
/// `EngineActivation` will be returned containing the main prolog
/// engine.
pub fn initialize_swipl_with_state(state: &'static [u8]) -> Option<EngineActivation<'static>> {
    if is_swipl_initialized() {
        return None;
    }

    // lock the rest of this initialization function to prevent concurrent initializers. Ideally this should happen in swipl itself, but unfortunately, it doesn't.
    let initialized = INITIALIZATION_STATE.write().unwrap();
    // There's actually a slight chance that initialization happened just now by some other thread. So check again.
    if initialized.is_some() {
        return None;
    }

    // SAFETY: Our slice is valid, thus its pointer and length are valid.
    // Also, the 'static lifetime ensures that the invariant for
    // PL_set_resource_db_mem is upheld:
    // > This implies that the data must remain accessible during the lifetime
    // > of the process if open_resource/3 is used. Future versions may provide
    // > a function to detach the resource database and cause open_resource/3
    // > to raise an exception.
    // - SWIPL FLI docs (footnote 208)
    // https://www.swi-prolog.org/pldoc/doc_for?object=c(%27PL_set_resource_db_mem%27)
    let result = unsafe { PL_set_resource_db_mem(state.as_ptr(), state.len()) };

    if result != TRUE as i32 {
        return None;
    }

    initialize_internal(initialized)
}

fn initialize_internal(
    mut initialized: RwLockWriteGuard<Option<Engine>>,
) -> Option<EngineActivation<'static>> {
    // TOOD we just pick "rust-swipl" as a fake program name here. This seems to work fine. But what we should really do is pass along the actual argv[0].
    let mut args: [*mut std::os::raw::c_char; 3] = [
        ARG0.as_ptr() as *mut std::os::raw::c_char,
        ARG1.as_ptr() as *mut std::os::raw::c_char,
        std::ptr::null_mut(),
    ];
    // unsafe justification: this initializes the swipl library and is idempotent
    // That said, there is actually a chance that some non-rust code is concurrently initializing prolog, which may lead to errors. There is unfortunately nothing that can be done about this.
    unsafe { PL_initialise(2, args.as_mut_ptr()) };
    *initialized = Some(unsafe { Engine::from_current() });

    Some(unsafe { std::mem::transmute((*initialized).as_ref().unwrap().set_activated()) })
}

/// Initialize SWI-Prolog and immediately deactivate the main thread engine.
///
/// If SWI-Prolog was already initialized, this will do nothing.
pub fn initialize_swipl_noengine() {
    let activation = initialize_swipl();
    // dropping the activation will deactivate the engine
    std::mem::drop(activation);
}

/// Initialize SWI-Prolog with a saved state and immediately deactivate the main thread engine.
///
/// If SWI-Prolog was already initialized, this will do nothing.
pub fn initialize_swipl_with_state_noengine(state: &'static [u8]) {
    let activation = initialize_swipl_with_state(state);
    // dropping the activation will deactivate the engine
    std::mem::drop(activation);
}

/// Reactivate the main engine.
///
/// This is only available if the rust library was originally
/// responsible for initializing the SWI-Prolog environment, and
/// the main engine has since been deactivated. If initialization
/// happened external to the library, there is no safe way to get
/// hold of the main engine. This will result in a panic.
pub fn reactivate_swipl() -> EngineActivation<'static> {
    let initialized = INITIALIZATION_STATE.read().unwrap();

    if let Some(engine) = initialized.as_ref() {
        unsafe { std::mem::transmute(engine.activate()) }
    } else {
        panic!("swipl-rs cannot reactiate the main engine because SWI-Prolog was not initialized, or initialized externally.");
    }
}

/// Register a foreign predicate.
///
/// This function is used by the `predicates!` macro to implement
/// predicate registration.
///
/// # Safety
/// This is only safe to call for functions which implement a foreign
/// predicate.
pub unsafe fn register_foreign_in_module(
    module: Option<&str>,
    name: &str,
    arity: u16,
    deterministic: bool,
    meta: Option<&str>,
    function_ptr: unsafe extern "C" fn(terms: term_t, arity: c_int, control: control_t) -> isize,
) -> bool {
    if meta.is_some() && meta.unwrap().len() != arity as usize {
        panic!("supplied a meta argument that is not of equal length to the arity");
    }

    // We get a handle to the read guard of initialization state.
    // This ensures that we're either in the pre-initialization state
    // or the post-initialization state, but not currently
    // initializing.
    // if unitialized, no further checks are needed.
    // but if initialized, we need to ensure we're actually on an engine currently.
    if is_swipl_initialized() && current_engine_ptr().is_null() {
        panic!("Tried to register a foreign predicate in a context where swipl is initialized, but no engine is active.");
    }

    let c_module = module.map(|module| CString::new(module).unwrap());
    let c_name = CString::new(name).unwrap();
    let c_meta = meta.map(|m| CString::new(m).unwrap());
    let mut flags = PL_FA_VARARGS;
    if !deterministic {
        flags |= PL_FA_NONDETERMINISTIC;
    }

    // an unfortunate need for transmute to make the fli eat the pointer
    let converted_function_ptr = std::mem::transmute(function_ptr);
    let c_module_ptr = c_module
        .as_ref()
        .map(|m| m.as_ptr())
        .unwrap_or(std::ptr::null_mut());

    PL_register_foreign_in_module(
        c_module_ptr,
        c_name.as_ptr(),
        arity as c_int,
        Some(converted_function_ptr),
        flags.try_into().unwrap(),
        c_meta.map(|m| m.as_ptr()).unwrap_or_else(std::ptr::null),
    ) == 1
}