Skip to main content

cartel_sqlite/runtime/raw/
mod.rs

1mod vfs;
2
3use crate::{
4    runtime::{self, Error, heap},
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::methods();
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, &raw const hooks);
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 retain = phase_bytes
156            .checked_add(arena)
157            .ok_or(runtime::Error::CapacityOverflow)?;
158        let bytes = retain
159            .checked_add(rounded)
160            .and_then(|total| total.checked_add(stmts))
161            .and_then(|total| total.checked_add(aside))
162            .ok_or(runtime::Error::CapacityOverflow)?;
163        let units = bytes.div_ceil(Span::ALIGNMENT);
164        let mut memory =
165            alloc::array::Block::try_new(units, ()).map_err(runtime::Error::Allocation)?;
166        // SAFETY: a nonempty checked byte total produces at least one allocated Unit.
167        let base = unsafe { ptr::NonNull::new_unchecked(memory.as_mut_ptr().cast()) };
168        let collect = Span::new(base, retain, collect);
169        let stmts = Span::new(base, retain + rounded, stmts);
170        let aside = Span::new(base, retain + rounded + stmts.len(), aside);
171        let phase = Span::new(base, 0, phase.size());
172        let start = Span::new(base, phase_bytes, arena).base();
173        // SAFETY: start and Layout describe the checked SQLite partition in this allocation.
174        let sqlite = unsafe { heap::raw::Span::new(start, sqlite) };
175        Ok((
176            Self { _memory: memory },
177            sqlite,
178            collect,
179            stmts,
180            aside,
181            phase,
182        ))
183    }
184
185    fn round(bytes: usize) -> Result<usize, runtime::Error> {
186        bytes
187            .checked_add(Span::ALIGNMENT - 1)
188            .map(|rounded| rounded / Span::ALIGNMENT * Span::ALIGNMENT)
189            .ok_or(runtime::Error::CapacityOverflow)
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};