Repository: terminusdb-labs/swipl-rs (language: rust)

Issue:
Allow initializing swipl with a saved state
Per #4, it'd be nice if we could initialize the swipl library with a saved state, pulling in its predicate definitions.

For extra super bonus points, it'd be even cooler if we could somehow embed a saved state in the built binary itself.

See also #3. Combining with ideas there, maybe as part of the build process, we could be parsing .pl files, auto-extracting rust-callable predicate definitions from them, and embedding a saved state of the compiled predicates into the resulting binary.


Discussion / hints:
You can build saved states with the swipl binary, so perhaps you could add a build script helper that builds a save state and puts it in OUT_DIR? Then it can just be included as bytes and passed as a &[u8]. See mlatu-lang/mlatu#34 for how I did this (src/prolog.rs and build.rs)

Fix patch:
diff --git a/swipl/src/engine.rs b/swipl/src/engine.rs
index 9693f95823..d4ceebd21a 100644
--- a/swipl/src/engine.rs
+++ b/swipl/src/engine.rs
@@ -83,6 +83,18 @@ impl Engine {
         }
     }
 
+    /// Create a new prolog engine with a saved state. SWI-Prolog must not have
+    /// been initialized already to do so.
+    pub fn with_state(state: &'static [u8]) -> Engine {
+        if !is_swipl_initialized() {
+            // This panicks here as there is no way to create an engine with a saved state after initialization,
+            // and otherwise the user may get errors invisibly.
+            panic!("SWI-Prolog must not have been initialized already to create an engine with a saved state.");
+        }
+        initialize_swipl_with_state_noengine(state);
+        Engine::new()
+    }
+
     pub(crate) unsafe fn from_current() -> Engine {
         Engine {
             engine_ptr: current_engine_ptr(),
diff --git a/swipl/src/init.rs b/swipl/src/init.rs
index 22e49fe0cd..3a709a4715 100644
--- a/swipl/src/init.rs
+++ b/swipl/src/init.rs
@@ -15,6 +15,7 @@ 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! {
@@ -56,12 +57,55 @@ pub fn initialize_swipl() -> Option<EngineActivation<'static>> {
     }
 
     // lock the rest of this initialization function to prevent concurrent initializers. Ideally this should happen in swipl itself, but unfortunately, it doesn't.
-    let mut initialized = INITIALIZATION_STATE.write().unwrap();
+    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() as size_t) };
+
+    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 i8; 3] = [
         ARG0.as_ptr() as *mut i8,
@@ -85,6 +129,15 @@ pub fn initialize_swipl_noengine() {
     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


Test patch: