chore: import upstream snapshot with attribution
Python CI / Python bindings (ubuntu-latest) (push) Failing after 0s
Build & Publish / Build Neovim aarch64-linux-android (push) Failing after 0s
Build & Publish / Build Neovim aarch64-unknown-linux-gnu (push) Failing after 0s
Build & Publish / Build MCP x86_64-unknown-linux-musl (push) Failing after 1s
Build & Publish / Build Python wheels aarch64 (ubuntu-latest) (push) Failing after 0s
Build & Publish / Build Python wheels x86_64 (ubuntu-latest) (push) Failing after 1s
Build & Publish / Build Python sdist (push) Failing after 1s
Rust CI / Fuzz Tests (ubuntu-latest) (push) Failing after 1s
Rust CI / Build i686-unknown-linux-gnu (push) Failing after 0s
Rust CI / cargo clippy (push) Failing after 1s
e2e Tests / e2e (ubuntu-latest) (push) Failing after 1s
Lua CI / luacheck lint (push) Failing after 1s
Build & Publish / Build Neovim aarch64-unknown-linux-musl (push) Failing after 1s
Build & Publish / Build Neovim x86_64-unknown-linux-gnu (push) Failing after 0s
Build & Publish / Build Neovim x86_64-unknown-linux-musl (push) Failing after 1s
Build & Publish / Build C FFI aarch64-linux-android (push) Failing after 0s
Build & Publish / Build C FFI aarch64-unknown-linux-gnu (push) Failing after 0s
Build & Publish / Build C FFI aarch64-unknown-linux-musl (push) Failing after 1s
Build & Publish / Build C FFI x86_64-unknown-linux-gnu (push) Failing after 0s
Build & Publish / Build C FFI x86_64-unknown-linux-musl (push) Failing after 1s
Build & Publish / Build MCP aarch64-unknown-linux-gnu (push) Failing after 1s
Build & Publish / Build MCP aarch64-unknown-linux-musl (push) Failing after 0s
Build & Publish / Build MCP x86_64-unknown-linux-gnu (push) Failing after 1s
Spelling / Spell Check with Typos (push) Failing after 0s
Rust CI / Test (ubuntu-latest) (push) Failing after 0s
Rust CI / cargo fmt (push) Failing after 0s
Stylua / Check lua files using Stylua (push) Failing after 1s
Lua CI / lua-language-server type check (push) Failing after 12m29s
e2e Tests / e2e (alpine-musl) (push) Successful in 57m31s
e2e Tests / e2e (macos-latest) (push) Has been cancelled
Build & Publish / Build C FFI aarch64-apple-darwin (push) Has been cancelled
Rust CI / Test (windows-latest) (push) Has been cancelled
e2e Tests / e2e (windows-latest) (push) Has been cancelled
Nix CI / check (push) Has been cancelled
Python CI / Python bindings (macos-latest) (push) Has been cancelled
Python CI / Python bindings (windows-latest) (push) Has been cancelled
Build & Publish / Build Neovim aarch64-apple-darwin (push) Has been cancelled
Build & Publish / Build Neovim aarch64-pc-windows-msvc (push) Has been cancelled
Build & Publish / Build Neovim x86_64-apple-darwin (push) Has been cancelled
Build & Publish / Build Neovim x86_64-pc-windows-msvc (push) Has been cancelled
Build & Publish / Build C FFI aarch64-pc-windows-msvc (push) Has been cancelled
Build & Publish / Build C FFI x86_64-apple-darwin (push) Has been cancelled
Build & Publish / Build C FFI x86_64-pc-windows-msvc (push) Has been cancelled
Build & Publish / Build MCP aarch64-apple-darwin (push) Has been cancelled
Build & Publish / Build MCP aarch64-pc-windows-msvc (push) Has been cancelled
Build & Publish / Build MCP x86_64-apple-darwin (push) Has been cancelled
Build & Publish / Build MCP x86_64-pc-windows-msvc (push) Has been cancelled
Build & Publish / Build Python wheels aarch64 (macos-latest) (push) Has been cancelled
Build & Publish / Build Python wheels x86_64 (macos-latest) (push) Has been cancelled
Build & Publish / Build Python wheels x86_64 (windows-latest) (push) Has been cancelled
Build & Publish / Release (push) Has been cancelled
Build & Publish / Publish Python wheels to PyPI (push) Has been cancelled
Build & Publish / Publish Rust crates (push) Has been cancelled
Build & Publish / Publish npm packages (push) Has been cancelled
Rust CI / Fuzz Tests (windows-latest) (push) Has been cancelled
Rust CI / Test (macos-latest) (push) Has been cancelled
Rust CI / Fuzz Tests (macos-latest) (push) Has been cancelled
Python CI / Python bindings (ubuntu-latest) (push) Failing after 0s
Build & Publish / Build Neovim aarch64-linux-android (push) Failing after 0s
Build & Publish / Build Neovim aarch64-unknown-linux-gnu (push) Failing after 0s
Build & Publish / Build MCP x86_64-unknown-linux-musl (push) Failing after 1s
Build & Publish / Build Python wheels aarch64 (ubuntu-latest) (push) Failing after 0s
Build & Publish / Build Python wheels x86_64 (ubuntu-latest) (push) Failing after 1s
Build & Publish / Build Python sdist (push) Failing after 1s
Rust CI / Fuzz Tests (ubuntu-latest) (push) Failing after 1s
Rust CI / Build i686-unknown-linux-gnu (push) Failing after 0s
Rust CI / cargo clippy (push) Failing after 1s
e2e Tests / e2e (ubuntu-latest) (push) Failing after 1s
Lua CI / luacheck lint (push) Failing after 1s
Build & Publish / Build Neovim aarch64-unknown-linux-musl (push) Failing after 1s
Build & Publish / Build Neovim x86_64-unknown-linux-gnu (push) Failing after 0s
Build & Publish / Build Neovim x86_64-unknown-linux-musl (push) Failing after 1s
Build & Publish / Build C FFI aarch64-linux-android (push) Failing after 0s
Build & Publish / Build C FFI aarch64-unknown-linux-gnu (push) Failing after 0s
Build & Publish / Build C FFI aarch64-unknown-linux-musl (push) Failing after 1s
Build & Publish / Build C FFI x86_64-unknown-linux-gnu (push) Failing after 0s
Build & Publish / Build C FFI x86_64-unknown-linux-musl (push) Failing after 1s
Build & Publish / Build MCP aarch64-unknown-linux-gnu (push) Failing after 1s
Build & Publish / Build MCP aarch64-unknown-linux-musl (push) Failing after 0s
Build & Publish / Build MCP x86_64-unknown-linux-gnu (push) Failing after 1s
Spelling / Spell Check with Typos (push) Failing after 0s
Rust CI / Test (ubuntu-latest) (push) Failing after 0s
Rust CI / cargo fmt (push) Failing after 0s
Stylua / Check lua files using Stylua (push) Failing after 1s
Lua CI / lua-language-server type check (push) Failing after 12m29s
e2e Tests / e2e (alpine-musl) (push) Successful in 57m31s
e2e Tests / e2e (macos-latest) (push) Has been cancelled
Build & Publish / Build C FFI aarch64-apple-darwin (push) Has been cancelled
Rust CI / Test (windows-latest) (push) Has been cancelled
e2e Tests / e2e (windows-latest) (push) Has been cancelled
Nix CI / check (push) Has been cancelled
Python CI / Python bindings (macos-latest) (push) Has been cancelled
Python CI / Python bindings (windows-latest) (push) Has been cancelled
Build & Publish / Build Neovim aarch64-apple-darwin (push) Has been cancelled
Build & Publish / Build Neovim aarch64-pc-windows-msvc (push) Has been cancelled
Build & Publish / Build Neovim x86_64-apple-darwin (push) Has been cancelled
Build & Publish / Build Neovim x86_64-pc-windows-msvc (push) Has been cancelled
Build & Publish / Build C FFI aarch64-pc-windows-msvc (push) Has been cancelled
Build & Publish / Build C FFI x86_64-apple-darwin (push) Has been cancelled
Build & Publish / Build C FFI x86_64-pc-windows-msvc (push) Has been cancelled
Build & Publish / Build MCP aarch64-apple-darwin (push) Has been cancelled
Build & Publish / Build MCP aarch64-pc-windows-msvc (push) Has been cancelled
Build & Publish / Build MCP x86_64-apple-darwin (push) Has been cancelled
Build & Publish / Build MCP x86_64-pc-windows-msvc (push) Has been cancelled
Build & Publish / Build Python wheels aarch64 (macos-latest) (push) Has been cancelled
Build & Publish / Build Python wheels x86_64 (macos-latest) (push) Has been cancelled
Build & Publish / Build Python wheels x86_64 (windows-latest) (push) Has been cancelled
Build & Publish / Release (push) Has been cancelled
Build & Publish / Publish Python wheels to PyPI (push) Has been cancelled
Build & Publish / Publish Rust crates (push) Has been cancelled
Build & Publish / Publish npm packages (push) Has been cancelled
Rust CI / Fuzz Tests (windows-latest) (push) Has been cancelled
Rust CI / Test (macos-latest) (push) Has been cancelled
Rust CI / Fuzz Tests (macos-latest) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,612 @@
|
||||
use ahash::AHashMap;
|
||||
use smallvec::SmallVec;
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// SIMD chunk size in bytes (matches NEON/SSE2 register width).
|
||||
/// This must stay in sync with neo_frizbee's internal chunk size.
|
||||
pub(crate) const SIMD_CHUNK_BYTES: usize = 16;
|
||||
|
||||
/// 4 chunks = 64 bytes inline, covers ~85% of paths without heap fallback.
|
||||
const INLINE_CHUNKS: usize = 4;
|
||||
|
||||
pub(crate) type ChunkIndices = SmallVec<[u32; INLINE_CHUNKS]>;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct ArenaPtr(pub(crate) *const u8);
|
||||
|
||||
// SAFETY: The arena is a read-only immutable part of file sync
|
||||
unsafe impl Send for ArenaPtr {}
|
||||
unsafe impl Sync for ArenaPtr {}
|
||||
|
||||
impl ArenaPtr {
|
||||
#[inline]
|
||||
pub fn new(ptr: *const u8) -> Self {
|
||||
Self(ptr)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn null() -> Self {
|
||||
Self(std::ptr::null())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn as_ptr(self) -> *const u8 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for ArenaPtr {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "--arena-raw-pointer-0x({:?})", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C, align(16))]
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) struct SimdChunk(pub(crate) [u8; SIMD_CHUNK_BYTES]);
|
||||
|
||||
impl Default for SimdChunk {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self([0u8; SIMD_CHUNK_BYTES])
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for SimdChunk {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
// Show the actual bytes, trimming trailing zeros for readability
|
||||
let end = self.0.iter().rposition(|&b| b != 0).map_or(0, |i| i + 1);
|
||||
write!(f, "SimdChunk({:?})", &self.0[..end])
|
||||
}
|
||||
}
|
||||
|
||||
pub use crate::constants::PATH_BUF_SIZE;
|
||||
|
||||
/// Chunk pointer capacity needed for the longest path the platform allows.
|
||||
pub(crate) const MAX_PATH_CHUNKS: usize = PATH_BUF_SIZE.div_ceil(SIMD_CHUNK_BYTES);
|
||||
|
||||
/// Indices into a shared `SimdChunk` arena representing a file path.
|
||||
///
|
||||
/// All read methods require an explicit `arena_base` pointer from the owning
|
||||
/// `ChunkedPathStore`. The struct itself contains no raw pointers to the arena
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ChunkedString {
|
||||
indices: ChunkIndices,
|
||||
pub byte_len: u16,
|
||||
/// Byte offset where the filename begins. 0 for root-level files.
|
||||
pub filename_offset: u16,
|
||||
}
|
||||
|
||||
impl ChunkedString {
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
indices: SmallVec::new(),
|
||||
byte_len: 0,
|
||||
filename_offset: 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn new(indices: ChunkIndices, byte_len: u16, filename_offset: u16) -> Self {
|
||||
Self {
|
||||
indices,
|
||||
byte_len,
|
||||
filename_offset,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn chunk_count(&self) -> usize {
|
||||
self.indices.len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn resolve_ptrs<'a>(&self, arena: ArenaPtr, buf: &'a mut [*const u8]) -> &'a [*const u8] {
|
||||
let count = self.indices.len().min(buf.len());
|
||||
let base = arena.as_ptr();
|
||||
for (i, &idx) in self.indices[..count].iter().enumerate() {
|
||||
buf[i] = unsafe { base.add(idx as usize * SIMD_CHUNK_BYTES) };
|
||||
}
|
||||
&buf[..count]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn write_slice_to_vec(
|
||||
indices: &[u32],
|
||||
base: *const u8,
|
||||
offset_in_chunk: usize,
|
||||
len: usize,
|
||||
vec: &mut Vec<u8>,
|
||||
) {
|
||||
let mut written = 0usize;
|
||||
for (i, &idx) in indices.iter().enumerate() {
|
||||
let src = unsafe { base.add(idx as usize * SIMD_CHUNK_BYTES) };
|
||||
let chunk_bytes = unsafe { core::slice::from_raw_parts(src, SIMD_CHUNK_BYTES) };
|
||||
let start = if i == 0 { offset_in_chunk } else { 0 };
|
||||
let end = SIMD_CHUNK_BYTES.min(start + (len - written));
|
||||
vec.extend_from_slice(&chunk_bytes[start..end]);
|
||||
written += end - start;
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the filename portion as a `Cow<str>`.
|
||||
///
|
||||
/// When the filename starts at a chunk boundary and fits in one chunk we
|
||||
/// borrow directly from the arena (zero-copy). Otherwise we allocate.
|
||||
/// Filenames are almost always <=16 bytes so the fast path dominates.
|
||||
#[inline]
|
||||
pub fn filename_cow<'a>(&self, arena: ArenaPtr) -> Cow<'a, str> {
|
||||
let fname_offset = self.filename_offset as usize;
|
||||
let fname_len = self.byte_len as usize - fname_offset;
|
||||
if fname_len == 0 {
|
||||
return Cow::Borrowed("");
|
||||
}
|
||||
|
||||
let base = arena.as_ptr();
|
||||
let start_chunk = fname_offset / SIMD_CHUNK_BYTES;
|
||||
let offset_in_chunk = fname_offset % SIMD_CHUNK_BYTES;
|
||||
|
||||
if offset_in_chunk == 0 && fname_len <= SIMD_CHUNK_BYTES {
|
||||
let ptr = unsafe { base.add(self.indices[start_chunk] as usize * SIMD_CHUNK_BYTES) };
|
||||
let slice = unsafe { core::slice::from_raw_parts(ptr, fname_len) };
|
||||
return Cow::Borrowed(unsafe { core::str::from_utf8_unchecked(slice) });
|
||||
}
|
||||
|
||||
let mut out = String::with_capacity(fname_len);
|
||||
let needed_chunks = chunks_needed(offset_in_chunk + fname_len);
|
||||
Self::write_slice_to_vec(
|
||||
&self.indices[start_chunk..start_chunk + needed_chunks],
|
||||
base,
|
||||
offset_in_chunk,
|
||||
fname_len,
|
||||
unsafe { out.as_mut_vec() },
|
||||
);
|
||||
Cow::Owned(out)
|
||||
}
|
||||
|
||||
/// Truncates at `buf.len()` if exceeded -- use `[u8; PATH_BUF_SIZE]` to avoid.
|
||||
#[inline]
|
||||
pub fn read_to_buf<'a>(&self, arena: ArenaPtr, buf: &'a mut [u8]) -> &'a str {
|
||||
let total = (self.byte_len as usize).min(buf.len());
|
||||
let usable_chunks = total.div_ceil(SIMD_CHUNK_BYTES);
|
||||
let chunks_to_copy = usable_chunks.min(self.indices.len());
|
||||
let base = arena.as_ptr();
|
||||
|
||||
for (i, &idx) in self.indices[..chunks_to_copy].iter().enumerate() {
|
||||
let src = unsafe { base.add(idx as usize * SIMD_CHUNK_BYTES) };
|
||||
let dst_offset = i * SIMD_CHUNK_BYTES;
|
||||
let take = SIMD_CHUNK_BYTES.min(total - dst_offset);
|
||||
|
||||
unsafe {
|
||||
core::ptr::copy_nonoverlapping(src, buf.as_mut_ptr().add(dst_offset), take);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe { core::str::from_utf8_unchecked(&buf[..total]) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn write_dir_to(&self, arena: ArenaPtr, out: &mut String) {
|
||||
out.clear();
|
||||
|
||||
let dir_len = self.filename_offset as usize;
|
||||
out.reserve(dir_len);
|
||||
let dir_chunks = chunks_needed(dir_len).min(self.indices.len());
|
||||
let base = arena.as_ptr();
|
||||
let vec = unsafe { out.as_mut_vec() };
|
||||
for (i, &idx) in self.indices[..dir_chunks].iter().enumerate() {
|
||||
let src = unsafe { base.add(idx as usize * SIMD_CHUNK_BYTES) };
|
||||
let take = SIMD_CHUNK_BYTES.min(dir_len - i * SIMD_CHUNK_BYTES);
|
||||
vec.extend_from_slice(unsafe { core::slice::from_raw_parts(src, take) });
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn write_filename_to(&self, arena: ArenaPtr, out: &mut String) {
|
||||
out.clear();
|
||||
|
||||
let fname_offset = self.filename_offset as usize;
|
||||
let fname_len = self.byte_len as usize - fname_offset;
|
||||
out.reserve(fname_len);
|
||||
let start_chunk = fname_offset / SIMD_CHUNK_BYTES;
|
||||
let offset_in_chunk = fname_offset % SIMD_CHUNK_BYTES;
|
||||
let needed_chunks = chunks_needed(offset_in_chunk + fname_len);
|
||||
Self::write_slice_to_vec(
|
||||
&self.indices[start_chunk..start_chunk + needed_chunks],
|
||||
arena.as_ptr(),
|
||||
offset_in_chunk,
|
||||
fname_len,
|
||||
unsafe { out.as_mut_vec() },
|
||||
);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn write_to_string(&self, arena: ArenaPtr, out: &mut String) {
|
||||
out.clear();
|
||||
|
||||
let total = self.byte_len as usize;
|
||||
if total == 0 {
|
||||
return;
|
||||
}
|
||||
out.reserve(total);
|
||||
let base = arena.as_ptr();
|
||||
let vec = unsafe { out.as_mut_vec() };
|
||||
for (i, &idx) in self.indices.iter().enumerate() {
|
||||
let src = unsafe { base.add(idx as usize * SIMD_CHUNK_BYTES) };
|
||||
let take = SIMD_CHUNK_BYTES.min(total - i * SIMD_CHUNK_BYTES);
|
||||
vec.extend_from_slice(unsafe { core::slice::from_raw_parts(src, take) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for ChunkedString {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("ChunkedString")
|
||||
.field("indices", &self.indices.as_slice())
|
||||
.field("chunks", &self.indices.len())
|
||||
.field("byte_len", &self.byte_len)
|
||||
.field("filename_offset", &self.filename_offset)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
const fn chunks_needed(byte_len: usize) -> usize {
|
||||
if byte_len == 0 {
|
||||
0
|
||||
} else {
|
||||
byte_len.div_ceil(SIMD_CHUNK_BYTES)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct ChunkedPathStore {
|
||||
arena: Vec<SimdChunk>,
|
||||
}
|
||||
|
||||
// SAFETY: arena is immutable after construction. Pointers derived from it are
|
||||
// only read during scoring (no mutation, no reallocation).
|
||||
unsafe impl Send for ChunkedPathStore {}
|
||||
unsafe impl Sync for ChunkedPathStore {}
|
||||
|
||||
impl ChunkedPathStore {
|
||||
pub fn heap_bytes(&self) -> usize {
|
||||
self.arena.len() * SIMD_CHUNK_BYTES
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn unique_chunks(&self) -> usize {
|
||||
self.arena.len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn as_arena_ptr(&self) -> ArenaPtr {
|
||||
ArenaPtr::new(self.arena.as_ptr() as *const u8)
|
||||
}
|
||||
}
|
||||
|
||||
/// At runtime the builder should be split out from the store after `finish()`.
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct ChunkedPathStoreBuilder {
|
||||
arena: Vec<SimdChunk>,
|
||||
chunk_dedup: AHashMap<[u8; SIMD_CHUNK_BYTES], u32>,
|
||||
}
|
||||
|
||||
impl ChunkedPathStoreBuilder {
|
||||
pub fn new(estimated_files: usize) -> Self {
|
||||
let est_chunks = estimated_files * INLINE_CHUNKS; // we know that most of repos will fit
|
||||
// most paths into 64 = 16 * INLINE_CHUNKS
|
||||
Self {
|
||||
arena: Vec::with_capacity(est_chunks),
|
||||
chunk_dedup: AHashMap::with_capacity(est_chunks),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn finish(self) -> ChunkedPathStore {
|
||||
ChunkedPathStore { arena: self.arena }
|
||||
}
|
||||
|
||||
pub fn as_arena_ptr(&self) -> ArenaPtr {
|
||||
ArenaPtr::new(self.arena.as_ptr() as *const u8)
|
||||
}
|
||||
|
||||
/// Like [`add_file_immediate`] but for directory paths where the entire
|
||||
/// string is the "directory" portion (filename_offset == byte_len).
|
||||
pub fn add_dir_immediate(&mut self, dir_rel_path: &str) -> ChunkedString {
|
||||
self.add_file_immediate(dir_rel_path, dir_rel_path.len() as u16)
|
||||
}
|
||||
|
||||
pub fn add_file_immediate(&mut self, rel_path: &str, filename_offset: u16) -> ChunkedString {
|
||||
let path_bytes = rel_path.as_bytes();
|
||||
let byte_len = rel_path.len();
|
||||
let mut indices = ChunkIndices::with_capacity(chunks_needed(byte_len));
|
||||
|
||||
for chunk in path_bytes.chunks(SIMD_CHUNK_BYTES) {
|
||||
let mut chunk_bytes = [0u8; SIMD_CHUNK_BYTES];
|
||||
chunk_bytes[..chunk.len()].copy_from_slice(chunk);
|
||||
|
||||
let arena_idx = match self.chunk_dedup.get(&chunk_bytes) {
|
||||
Some(&idx) => idx,
|
||||
None => {
|
||||
let idx = self.arena.len() as u32;
|
||||
self.arena.push(SimdChunk(chunk_bytes));
|
||||
self.chunk_dedup.insert(chunk_bytes, idx);
|
||||
idx
|
||||
}
|
||||
};
|
||||
|
||||
indices.push(arena_idx);
|
||||
}
|
||||
|
||||
ChunkedString::new(indices, byte_len as u16, filename_offset)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn build_chunked_path_store_from_strings(
|
||||
rel_paths: &[String],
|
||||
files: &[crate::types::FileItem],
|
||||
) -> (ChunkedPathStore, Vec<ChunkedString>) {
|
||||
assert_eq!(rel_paths.len(), files.len());
|
||||
let mut builder = ChunkedPathStoreBuilder::new(rel_paths.len());
|
||||
let strings: Vec<ChunkedString> = rel_paths
|
||||
.iter()
|
||||
.zip(files.iter())
|
||||
.map(|(rel_path, file)| builder.add_file_immediate(rel_path, file.path.filename_offset))
|
||||
.collect();
|
||||
(builder.finish(), strings)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn make_file_item(path: &str) -> crate::types::FileItem {
|
||||
let filename_start = path
|
||||
.rfind(std::path::is_separator)
|
||||
.map(|i| i + 1)
|
||||
.unwrap_or(0) as u16;
|
||||
crate::types::FileItem::new_raw(filename_start, 0, 0, None, false)
|
||||
}
|
||||
|
||||
fn build_test_store(
|
||||
paths: &[&str],
|
||||
) -> (
|
||||
ChunkedPathStore,
|
||||
Vec<ChunkedString>,
|
||||
Vec<crate::types::FileItem>,
|
||||
) {
|
||||
let mut files: Vec<crate::types::FileItem> =
|
||||
paths.iter().map(|p| make_file_item(p)).collect();
|
||||
let path_strings: Vec<String> = paths.iter().map(|p| p.to_string()).collect();
|
||||
let (store, strings) = build_chunked_path_store_from_strings(&path_strings, &files);
|
||||
for (i, file) in files.iter_mut().enumerate() {
|
||||
file.set_path(strings[i].clone());
|
||||
}
|
||||
(store, strings, files)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chunked_store_empty() {
|
||||
let (store, strings, _files) = build_test_store(&[]);
|
||||
assert_eq!(strings.len(), 0);
|
||||
assert_eq!(store.unique_chunks(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chunked_store_basic() {
|
||||
let (store, strings, _files) =
|
||||
build_test_store(&["src/lib.rs", "src/main.rs", "Cargo.toml"]);
|
||||
let arena = store.as_arena_ptr();
|
||||
|
||||
assert_eq!(strings.len(), 3);
|
||||
assert!(store.unique_chunks() >= 2);
|
||||
|
||||
let mut buf = [0u8; 512];
|
||||
assert_eq!(
|
||||
strings[0].read_to_buf(arena, &mut buf).len(),
|
||||
"src/lib.rs".len()
|
||||
);
|
||||
assert_eq!(
|
||||
strings[2].read_to_buf(arena, &mut buf).len(),
|
||||
"Cargo.toml".len()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chunked_string_full_path() {
|
||||
let (store, strings, _files) = build_test_store(&["src/components/Button.tsx"]);
|
||||
let arena = store.as_arena_ptr();
|
||||
let cs = &strings[0];
|
||||
|
||||
let mut buf = [0u8; 512];
|
||||
assert_eq!(cs.read_to_buf(arena, &mut buf), "src/components/Button.tsx");
|
||||
assert_eq!(cs.byte_len, 25);
|
||||
assert_eq!(cs.filename_offset, 15);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chunked_string_dir_and_filename() {
|
||||
let (store, strings, _files) = build_test_store(&["src/components/Button.tsx"]);
|
||||
let arena = store.as_arena_ptr();
|
||||
let cs = &strings[0];
|
||||
|
||||
let mut s = String::new();
|
||||
cs.write_dir_to(arena, &mut s);
|
||||
assert_eq!(s, "src/components/");
|
||||
cs.write_filename_to(arena, &mut s);
|
||||
assert_eq!(s, "Button.tsx");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chunked_string_root_file() {
|
||||
let (store, strings, _files) = build_test_store(&["Cargo.toml"]);
|
||||
let arena = store.as_arena_ptr();
|
||||
let cs = &strings[0];
|
||||
|
||||
let mut s = String::new();
|
||||
cs.write_dir_to(arena, &mut s);
|
||||
assert_eq!(s, "");
|
||||
cs.write_filename_to(arena, &mut s);
|
||||
assert_eq!(s, "Cargo.toml");
|
||||
let mut buf = [0u8; 512];
|
||||
assert_eq!(cs.read_to_buf(arena, &mut buf), "Cargo.toml");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chunked_string_resolve_ptrs() {
|
||||
let (store, strings, _files) = build_test_store(&["src/components/Button.tsx"]);
|
||||
let arena = store.as_arena_ptr();
|
||||
let cs = &strings[0];
|
||||
|
||||
let mut ptrs = [std::ptr::null::<u8>(); MAX_PATH_CHUNKS];
|
||||
let resolved = cs.resolve_ptrs(arena, &mut ptrs);
|
||||
assert_eq!(resolved.len(), 2); // 25 bytes = 2 chunks
|
||||
|
||||
// Verify we can read back the bytes
|
||||
let mut reconstructed = Vec::new();
|
||||
for (i, &ptr) in resolved.iter().enumerate() {
|
||||
let chunk = unsafe { std::slice::from_raw_parts(ptr, SIMD_CHUNK_BYTES) };
|
||||
let start = i * SIMD_CHUNK_BYTES;
|
||||
let take = SIMD_CHUNK_BYTES.min(25 - start);
|
||||
reconstructed.extend_from_slice(&chunk[..take]);
|
||||
}
|
||||
assert_eq!(
|
||||
std::str::from_utf8(&reconstructed).unwrap(),
|
||||
"src/components/Button.tsx"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_resolve_ptrs_path_exceeding_512_bytes() {
|
||||
// Regression: a fixed 32-ptr buffer covered only 512 bytes while
|
||||
// PATH_BUF_SIZE (libc::PATH_MAX) allows longer paths, panicking with
|
||||
// "index out of bounds: the len is 32 but the index is 32"
|
||||
let mut path = String::new();
|
||||
while path.len() < 600 {
|
||||
path.push_str("deeply_nested_directory_segment/");
|
||||
}
|
||||
path.push_str("needle_file.rs");
|
||||
assert!(path.len() > 512 && path.len() < PATH_BUF_SIZE);
|
||||
|
||||
let (store, strings, _files) = build_test_store(&[path.as_str()]);
|
||||
let arena = store.as_arena_ptr();
|
||||
let cs = &strings[0];
|
||||
assert!(cs.chunk_count() > 32, "path must span more than 32 chunks");
|
||||
|
||||
let mut ptrs = [std::ptr::null::<u8>(); MAX_PATH_CHUNKS];
|
||||
let resolved = cs.resolve_ptrs(arena, &mut ptrs);
|
||||
|
||||
// Truncation is not acceptable either: it silently drops the tail of
|
||||
// the path (including the filename here) from fuzzy matching.
|
||||
assert_eq!(
|
||||
resolved.len(),
|
||||
cs.chunk_count(),
|
||||
"resolve_ptrs must resolve every chunk of a PATH_MAX-legal path"
|
||||
);
|
||||
|
||||
let total = cs.byte_len as usize;
|
||||
let mut reconstructed = Vec::with_capacity(total);
|
||||
for (i, &ptr) in resolved.iter().enumerate() {
|
||||
let take = SIMD_CHUNK_BYTES.min(total - i * SIMD_CHUNK_BYTES);
|
||||
reconstructed.extend_from_slice(unsafe { std::slice::from_raw_parts(ptr, take) });
|
||||
}
|
||||
assert_eq!(std::str::from_utf8(&reconstructed).unwrap(), path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filename_cow_mid_chunk() {
|
||||
let (store, strings, _files) = build_test_store(&["src/components/Button.tsx"]);
|
||||
let arena = store.as_arena_ptr();
|
||||
let cs = &strings[0];
|
||||
|
||||
assert_eq!(cs.filename_offset, 15);
|
||||
assert_eq!(cs.byte_len, 25);
|
||||
|
||||
let fname = cs.filename_cow(arena);
|
||||
assert_eq!(&*fname, "Button.tsx");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filename_cow_chunk_aligned() {
|
||||
let path = "0123456789abcdef/file.txt";
|
||||
let (store, strings, _files) = build_test_store(&[path]);
|
||||
let arena = store.as_arena_ptr();
|
||||
let cs = &strings[0];
|
||||
|
||||
assert_eq!(cs.filename_offset, 17);
|
||||
let fname = cs.filename_cow(arena);
|
||||
assert_eq!(&*fname, "file.txt");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filename_cow_root_file() {
|
||||
let (store, strings, _files) = build_test_store(&["Cargo.toml"]);
|
||||
let arena = store.as_arena_ptr();
|
||||
let cs = &strings[0];
|
||||
|
||||
assert_eq!(cs.filename_offset, 0);
|
||||
let fname = cs.filename_cow(arena);
|
||||
assert_eq!(&*fname, "Cargo.toml");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chunked_string_long_path() {
|
||||
let path = "very/deeply/nested/directory/structure/with/many/levels/file.txt";
|
||||
let (store, strings, _files) = build_test_store(&[path]);
|
||||
let arena = store.as_arena_ptr();
|
||||
let cs = &strings[0];
|
||||
|
||||
let mut buf = [0u8; 512];
|
||||
assert_eq!(cs.read_to_buf(arena, &mut buf), path);
|
||||
assert!(
|
||||
cs.chunk_count() <= 6,
|
||||
"should fit inline in ChunkIndices (INLINE_CHUNKS={})",
|
||||
INLINE_CHUNKS
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chunked_string_clone() {
|
||||
let (store, strings, _files) = build_test_store(&["src/main.rs"]);
|
||||
let arena = store.as_arena_ptr();
|
||||
let cs = &strings[0];
|
||||
let cs2 = cs.clone();
|
||||
|
||||
let mut buf1 = [0u8; 512];
|
||||
let mut buf2 = [0u8; 512];
|
||||
assert_eq!(
|
||||
cs.read_to_buf(arena, &mut buf1),
|
||||
cs2.read_to_buf(arena, &mut buf2)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_chunked_string_full_path_roundtrip() {
|
||||
let paths = [
|
||||
"src/components/Button.tsx",
|
||||
"src/components/ui/DatePicker.tsx",
|
||||
"very/deeply/nested/directory/structure/file.txt",
|
||||
"Cargo.toml",
|
||||
"a.rs",
|
||||
];
|
||||
let (store, strings, _files) = build_test_store(&paths);
|
||||
let arena = store.as_arena_ptr();
|
||||
|
||||
for (i, expected) in paths.iter().enumerate() {
|
||||
let mut buf = [0u8; 512];
|
||||
let got = strings[i].read_to_buf(arena, &mut buf);
|
||||
assert_eq!(got, *expected, "full path roundtrip failed for file {i}");
|
||||
|
||||
let mut ds = String::new();
|
||||
let mut fs = String::new();
|
||||
strings[i].write_dir_to(arena, &mut ds);
|
||||
strings[i].write_filename_to(arena, &mut fs);
|
||||
assert_eq!(
|
||||
format!("{ds}{fs}"),
|
||||
*expected,
|
||||
"dir+fname mismatch for file {i}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user