Skip to main content

cartel_sqlite/runtime/raw/
mod.rs

1mod vfs;
2
3use crate::{
4    runtime::{self, Error, heap, local},
5    sys,
6};
7use heap::layout::Layout;
8use o3::alloc;
9use std::{mem, ptr};
10
11#[repr(C, align(64))]
12struct Unit {
13    bytes: [mem::MaybeUninit<u8>; Span::ALIGNMENT],
14}
15
16pub(crate) struct Memory {
17    memory: alloc::array::Block<mem::MaybeUninit<Unit>, usize>,
18}
19
20pub(crate) struct Span {
21    base: ptr::NonNull<u8>,
22    bytes: usize,
23}
24
25/// Exclusive native custody lasts for the process once allocator hooks are installed, even if installation fails; copying or dropping `Runtime` does not release it.
26///
27/// Arena-backed native handles and allocations must remain exclusively owned by their originating worker until release; raw calls and other wrappers must preserve this custody and Cartel's admitted execution. Foreign initialization and VFS allocations retain their std allocation family and audited native synchronization.
28///
29/// After allocator hook installation, callers must not reconfigure or replace the allocator, page cache, VFS or callbacks, enable URI interpretation, shared caches or native workers, install unaudited extensions, or publish arena-backed storage into global native state.
30#[must_use]
31pub struct Permit(pub(super) ());
32
33/// Exclusive installation authority for a build with infallible native shutdown.
34struct Setup(Permit);
35
36impl Permit {
37    /// # Safety
38    /// SQLite must be uninitialized, and no other thread may call SQLite until this permit is consumed by `Runtime::install` and that call returns; the caller must uphold [`Permit`]'s process-lifetime custody contract.
39    /// The initial default VFS, mutex methods, logging callbacks and auto-extension registry must belong to the audited native implementation without unaudited callbacks or extensions; allocator and page-cache tables replaced without execution may already be installed.
40    pub unsafe fn exclusive() -> Self {
41        Self(())
42    }
43
44    pub(super) fn initialize(self) -> Result<Self, Error> {
45        if unsafe {
46            libsqlite3_sys::sqlite3_compileoption_used(c"ENABLE_MEMORY_MANAGEMENT".as_ptr())
47        } != 0
48        {
49            return Err(Error::GlobalCache);
50        }
51        if unsafe {
52            libsqlite3_sys::sqlite3_compileoption_used(c"DISABLE_PAGECACHE_OVERFLOW_STATS".as_ptr())
53        } == 0
54        {
55            return Err(Error::CacheStats);
56        }
57        if unsafe { libsqlite3_sys::sqlite3_compileoption_used(c"ENABLE_STMTVTAB".as_ptr()) } != 0 {
58            return Err(Error::StatementVtab);
59        }
60        if unsafe { libsqlite3_sys::sqlite3_compileoption_used(c"OMIT_WSD".as_ptr()) } != 0 {
61            return Err(Error::WritableStatic);
62        }
63        if unsafe { libsqlite3_sys::sqlite3_compileoption_used(c"ENABLE_SQLLOG".as_ptr()) } != 0 {
64            return Err(Error::StatementLog);
65        }
66        Setup(self).initialize()
67    }
68}
69
70impl Setup {
71    fn initialize(self) -> Result<Permit, Error> {
72        use heap::raw;
73        use vfs::Vfs;
74
75        let code =
76            unsafe { libsqlite3_sys::sqlite3_config(libsqlite3_sys::SQLITE_CONFIG_MULTITHREAD) };
77        if code != libsqlite3_sys::SQLITE_OK {
78            return Err(Error::Threading(sys::Code::new(code)));
79        }
80        // SAFETY: MULTITHREAD admitted the uninitialized phase, and Permit excludes
81        // concurrent calls. These native cases only copy values, invoke no callbacks,
82        // and return SQLITE_OK without initializing SQLite.
83        unsafe {
84            libsqlite3_sys::sqlite3_config(libsqlite3_sys::SQLITE_CONFIG_URI, 0);
85            libsqlite3_sys::sqlite3_config(libsqlite3_sys::SQLITE_CONFIG_MEMSTATUS, 0);
86            libsqlite3_sys::sqlite3_config(
87                libsqlite3_sys::SQLITE_CONFIG_PAGECACHE,
88                ptr::null_mut::<std::ffi::c_void>(),
89                0,
90                20,
91            );
92        }
93        let cache = libsqlite3_sys::sqlite3_pcache_methods2 {
94            iVersion: 1,
95            pArg: ptr::null_mut(),
96            xInit: None,
97            xShutdown: None,
98            xCreate: None,
99            xCachesize: None,
100            xPagecount: None,
101            xFetch: None,
102            xUnpin: None,
103            xRekey: None,
104            xTruncate: None,
105            xDestroy: None,
106            xShrink: None,
107        };
108        // SAFETY: the admitted phase permits this callback-free table copy.
109        // A null xInit selects the audited native cache, replacing any prior custom cache.
110        unsafe {
111            libsqlite3_sys::sqlite3_config(libsqlite3_sys::SQLITE_CONFIG_PCACHE2, &raw const cache);
112        }
113        let hooks = raw::Hooks::new();
114        // SAFETY: the same phase is retained; MALLOC copies this complete table by
115        // value and returns SQLITE_OK without invoking its callbacks.
116        unsafe {
117            libsqlite3_sys::sqlite3_config(libsqlite3_sys::SQLITE_CONFIG_MALLOC, hooks.as_ptr());
118        }
119        let scope = raw::Foreign::enter();
120        let code = unsafe { libsqlite3_sys::sqlite3_initialize() };
121        let result = if code == libsqlite3_sys::SQLITE_OK {
122            unsafe { Vfs::install() }
123        } else {
124            Err(Error::Initialization(sys::Code::new(code)))
125        };
126        if result.is_err() {
127            // SAFETY: Setup retains exclusive startup authority and rejects OMIT_WSD,
128            // the only native build path where shutdown can return a non-OK code.
129            unsafe { libsqlite3_sys::sqlite3_shutdown() };
130        }
131        drop(scope);
132        result?;
133        Ok(self.0)
134    }
135}
136
137impl Memory {
138    /// Layout retains statement bytes already aligned by stmt::bytes.
139    pub(in crate::runtime) fn new(
140        sqlite: Layout,
141        collect: usize,
142        stmts: usize,
143        aside: usize,
144        phase: std::alloc::Layout,
145    ) -> Result<(Self, heap::raw::Span, Span, Span, Span, Span), runtime::Error> {
146        if phase.align() > Span::ALIGNMENT {
147            return Err(runtime::Error::Layout);
148        }
149        let phase_bytes = Self::round(phase.size())?;
150        let rounded = Self::round(collect)?;
151        let aside = Self::round(aside)?;
152        let arena = sqlite.backing().ok_or(runtime::Error::CapacityOverflow)?;
153        let control = local::State::<crate::catalogs::Empty>::RETENTION_BYTES;
154        let start = control
155            .checked_add(phase_bytes)
156            .ok_or(runtime::Error::CapacityOverflow)?;
157        let retain = start
158            .checked_add(arena)
159            .ok_or(runtime::Error::CapacityOverflow)?;
160        let bytes = retain
161            .checked_add(rounded)
162            .and_then(|total| total.checked_add(stmts))
163            .and_then(|total| total.checked_add(aside))
164            .ok_or(runtime::Error::CapacityOverflow)?;
165        let units = bytes.div_ceil(Span::ALIGNMENT);
166        let mut memory =
167            alloc::array::Block::try_new(units, ()).map_err(runtime::Error::Allocation)?;
168        // SAFETY: a nonempty checked byte total produces at least one allocated Unit.
169        let base = unsafe { ptr::NonNull::new_unchecked(memory.as_mut_ptr().cast()) };
170        let collect = Span::new(base, retain, collect);
171        let stmts = Span::new(base, retain + rounded, stmts);
172        let aside = Span::new(base, retain + rounded + stmts.len(), aside);
173        let phase = Span::new(base, control, phase.size());
174        let start = Span::new(base, start, arena).base();
175        // SAFETY: start and Layout describe the checked SQLite partition in this allocation.
176        let sqlite = unsafe { heap::raw::Span::new(start, sqlite) };
177        Ok((Self { memory }, sqlite, collect, stmts, aside, phase))
178    }
179
180    fn round(bytes: usize) -> Result<usize, runtime::Error> {
181        bytes
182            .checked_add(Span::ALIGNMENT - 1)
183            .map(|rounded| rounded / Span::ALIGNMENT * Span::ALIGNMENT)
184            .ok_or(runtime::Error::CapacityOverflow)
185    }
186
187    pub(in crate::runtime) fn base(&mut self) -> ptr::NonNull<u8> {
188        // SAFETY: Memory always owns at least one aligned Unit.
189        unsafe { ptr::NonNull::new_unchecked(self.memory.as_mut_ptr().cast()) }
190    }
191}
192
193impl Span {
194    pub(crate) const ALIGNMENT: usize = 64;
195
196    fn new(base: ptr::NonNull<u8>, start: usize, bytes: usize) -> Self {
197        // SAFETY: Memory bounds every partition start by its checked total allocation size.
198        let base = unsafe { ptr::NonNull::new_unchecked(base.as_ptr().add(start)) };
199        Self { base, bytes }
200    }
201
202    pub(crate) const fn into_parts(self) -> (ptr::NonNull<u8>, usize) {
203        (self.base, self.bytes)
204    }
205
206    pub(crate) const fn as_mut_ptr(&mut self) -> *mut u8 {
207        self.base.as_ptr()
208    }
209
210    pub(crate) const fn len(&self) -> usize {
211        self.bytes
212    }
213
214    pub(crate) const fn base(&self) -> ptr::NonNull<u8> {
215        self.base
216    }
217}
218
219const _: () = {
220    assert!(mem::size_of::<Unit>() == Span::ALIGNMENT);
221    assert!(mem::align_of::<Unit>() == Span::ALIGNMENT);
222};