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            // Each connection receives its owned lookaside before its first SQL.
87            libsqlite3_sys::sqlite3_config(libsqlite3_sys::SQLITE_CONFIG_LOOKASIDE, 0, 0);
88            libsqlite3_sys::sqlite3_config(
89                libsqlite3_sys::SQLITE_CONFIG_PAGECACHE,
90                ptr::null_mut::<std::ffi::c_void>(),
91                0,
92                20,
93            );
94        }
95        let cache = libsqlite3_sys::sqlite3_pcache_methods2 {
96            iVersion: 1,
97            pArg: ptr::null_mut(),
98            xInit: None,
99            xShutdown: None,
100            xCreate: None,
101            xCachesize: None,
102            xPagecount: None,
103            xFetch: None,
104            xUnpin: None,
105            xRekey: None,
106            xTruncate: None,
107            xDestroy: None,
108            xShrink: None,
109        };
110        // SAFETY: the admitted phase permits this callback-free table copy.
111        // A null xInit selects the audited native cache, replacing any prior custom cache.
112        unsafe {
113            libsqlite3_sys::sqlite3_config(libsqlite3_sys::SQLITE_CONFIG_PCACHE2, &raw const cache);
114        }
115        let hooks = raw::Hooks::new();
116        // SAFETY: the same phase is retained; MALLOC copies this complete table by
117        // value and returns SQLITE_OK without invoking its callbacks.
118        unsafe {
119            libsqlite3_sys::sqlite3_config(libsqlite3_sys::SQLITE_CONFIG_MALLOC, hooks.as_ptr());
120        }
121        let scope = raw::Foreign::enter();
122        let code = unsafe { libsqlite3_sys::sqlite3_initialize() };
123        let result = if code == libsqlite3_sys::SQLITE_OK {
124            unsafe { Vfs::install() }
125        } else {
126            Err(Error::Initialization(sys::Code::new(code)))
127        };
128        if result.is_err() {
129            // SAFETY: Setup retains exclusive startup authority and rejects OMIT_WSD,
130            // the only native build path where shutdown can return a non-OK code.
131            unsafe { libsqlite3_sys::sqlite3_shutdown() };
132        }
133        drop(scope);
134        result?;
135        Ok(self.0)
136    }
137}
138
139impl Memory {
140    /// Layout retains statement bytes already aligned by stmt::bytes.
141    pub(in crate::runtime) fn new(
142        sqlite: Layout,
143        collect: usize,
144        stmts: usize,
145        aside: usize,
146        phase: std::alloc::Layout,
147    ) -> Result<(Self, heap::raw::Span, Span, Span, Span, Span), runtime::Error> {
148        if phase.align() > Span::ALIGNMENT {
149            return Err(runtime::Error::Layout);
150        }
151        let phase_bytes = Self::round(phase.size())?;
152        let rounded = Self::round(collect)?;
153        let aside = Self::round(aside)?;
154        let arena = sqlite.backing().ok_or(runtime::Error::CapacityOverflow)?;
155        let control = local::State::<crate::catalogs::Empty>::RETENTION_BYTES;
156        let start = control
157            .checked_add(phase_bytes)
158            .ok_or(runtime::Error::CapacityOverflow)?;
159        let retain = start
160            .checked_add(arena)
161            .ok_or(runtime::Error::CapacityOverflow)?;
162        let bytes = retain
163            .checked_add(rounded)
164            .and_then(|total| total.checked_add(stmts))
165            .and_then(|total| total.checked_add(aside))
166            .ok_or(runtime::Error::CapacityOverflow)?;
167        let units = bytes.div_ceil(Span::ALIGNMENT);
168        let mut memory =
169            alloc::array::Block::try_new(units, ()).map_err(runtime::Error::Allocation)?;
170        // SAFETY: a nonempty checked byte total produces at least one allocated Unit.
171        let base = unsafe { ptr::NonNull::new_unchecked(memory.as_mut_ptr().cast()) };
172        let collect = Span::new(base, retain, collect);
173        let stmts = Span::new(base, retain + rounded, stmts);
174        let aside = Span::new(base, retain + rounded + stmts.len(), aside);
175        let phase = Span::new(base, control, phase.size());
176        let start = Span::new(base, start, arena).base();
177        // SAFETY: start and Layout describe the checked SQLite partition in this allocation.
178        let sqlite = unsafe { heap::raw::Span::new(start, sqlite) };
179        Ok((Self { memory }, sqlite, collect, stmts, aside, phase))
180    }
181
182    fn round(bytes: usize) -> Result<usize, runtime::Error> {
183        bytes
184            .checked_add(Span::ALIGNMENT - 1)
185            .map(|rounded| rounded / Span::ALIGNMENT * Span::ALIGNMENT)
186            .ok_or(runtime::Error::CapacityOverflow)
187    }
188
189    pub(in crate::runtime) fn base(&mut self) -> ptr::NonNull<u8> {
190        // SAFETY: Memory always owns at least one aligned Unit.
191        unsafe { ptr::NonNull::new_unchecked(self.memory.as_mut_ptr().cast()) }
192    }
193}
194
195impl Span {
196    pub(crate) const ALIGNMENT: usize = 64;
197
198    fn new(base: ptr::NonNull<u8>, start: usize, bytes: usize) -> Self {
199        // SAFETY: Memory bounds every partition start by its checked total allocation size.
200        let base = unsafe { ptr::NonNull::new_unchecked(base.as_ptr().add(start)) };
201        Self { base, bytes }
202    }
203
204    pub(crate) const fn into_parts(self) -> (ptr::NonNull<u8>, usize) {
205        (self.base, self.bytes)
206    }
207
208    pub(crate) const fn as_mut_ptr(&mut self) -> *mut u8 {
209        self.base.as_ptr()
210    }
211
212    pub(crate) const fn len(&self) -> usize {
213        self.bytes
214    }
215
216    pub(crate) const fn base(&self) -> ptr::NonNull<u8> {
217        self.base
218    }
219}
220
221const _: () = {
222    assert!(mem::size_of::<Unit>() == Span::ALIGNMENT);
223    assert!(mem::align_of::<Unit>() == Span::ALIGNMENT);
224};