Skip to content

Commit 55fd30e

Browse files
committed
fix: Wrong pointer
1 parent bf557ce commit 55fd30e

3 files changed

Lines changed: 254 additions & 68 deletions

File tree

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/fff-core/src/score.rs

Lines changed: 72 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::{
22
constraints::apply_constraints,
33
git::is_modified_status,
44
path_utils::calculate_distance_penalty,
5-
simd_path::ArenaPtr,
5+
simd_path::{ArenaPtr, SimdChunk},
66
sort_buffer::{sort_by_key_with_buffer, sort_with_buffer},
77
types::{FileItem, Score, ScoringContext},
88
};
@@ -26,6 +26,21 @@ impl<'a> FileItems<'a> {
2626
}
2727
}
2828

29+
/// Resolve a FileItem's chunked path into frizbee's pointer buffer.
30+
/// Returns `Some((chunk_count, byte_len))` or `None` for deleted files.
31+
#[inline]
32+
fn resolve_file_chunks(
33+
file: &FileItem,
34+
arena: ArenaPtr,
35+
buf: &mut [*const u8; 32],
36+
) -> Option<(usize, u16)> {
37+
if file.is_deleted() {
38+
return None;
39+
}
40+
let ptrs = file.path.resolve_ptrs(arena, buf);
41+
Some((ptrs.len(), file.path.byte_len))
42+
}
43+
2944
#[inline]
3045
fn match_fuzzy_parts(
3146
fuzzy_parts: &[&str],
@@ -45,39 +60,34 @@ fn match_fuzzy_parts(
4560
return vec![];
4661
}
4762

48-
// Collect paths from the arena into a temporary Vec<String> for frizbee matching.
49-
// This is the bridge between arena-stored paths and frizbee's Matchable trait.
50-
// Deleted files get an empty string so their index stays aligned.
51-
let paths: Vec<String> = match working_files {
52-
FileItems::All(files) => files
53-
.iter()
54-
.map(|f| {
55-
if f.is_deleted() {
56-
String::new()
57-
} else {
58-
f.relative_path_from_arena(arena)
59-
}
60-
})
61-
.collect(),
62-
FileItems::Filtered(files) => files
63-
.iter()
64-
.map(|f| {
65-
if f.is_deleted() {
66-
String::new()
67-
} else {
68-
f.relative_path_from_arena(arena)
69-
}
70-
})
71-
.collect(),
63+
let resolve = |file: &FileItem, buf: &mut [*const u8; 32]| -> Option<(usize, u16)> {
64+
resolve_file_chunks(file, arena, buf)
7265
};
7366

74-
let first_part_matches =
75-
neo_frizbee::match_list_parallel(valid_parts[0], &paths, options, max_threads);
67+
let first_part_matches = match working_files {
68+
FileItems::All(files) => neo_frizbee::match_list_parallel_resolved(
69+
valid_parts[0],
70+
files,
71+
&resolve,
72+
options,
73+
max_threads,
74+
),
75+
FileItems::Filtered(files) => neo_frizbee::match_list_parallel_resolved(
76+
valid_parts[0],
77+
files.as_slice(),
78+
&|file_ref: &&FileItem, buf: &mut [*const u8; 32]| {
79+
resolve_file_chunks(file_ref, arena, buf)
80+
},
81+
options,
82+
max_threads,
83+
),
84+
};
7685

7786
if valid_parts.len() == 1 {
7887
return first_part_matches;
7988
}
8089

90+
let mut path_buf = [0u8; crate::simd_path::PATH_BUF_SIZE];
8191
let mut matches = first_part_matches;
8292
for part in valid_parts[1..].iter() {
8393
let mut part_options = *options;
@@ -87,9 +97,8 @@ fn match_fuzzy_parts(
8797
.into_iter()
8898
.filter_map(|mut m| {
8999
let file = working_files.index(m.index as usize);
90-
let mut buf = String::with_capacity(64);
91-
file.write_relative_path_from_arena(arena, &mut buf);
92-
let part_matches = neo_frizbee::match_list(part, &[buf.as_str()], &part_options);
100+
let path_str = file.path.read_to_buf(arena, &mut path_buf);
101+
let part_matches = neo_frizbee::match_list(part, &[path_str], &part_options);
93102
let part_match = part_matches.first()?;
94103

95104
let total = (m.score as u32).saturating_add(part_match.score as u32);
@@ -194,7 +203,7 @@ fn match_and_score_in_arena<'a>(
194203
{
195204
vec![]
196205
} else {
197-
let mut fallback_filenames: Vec<String> = Vec::new();
206+
let mut fallback_items: Vec<&FileItem> = Vec::new();
198207

199208
for (i, path_match) in path_matches.iter().enumerate() {
200209
let file = working_files.index(path_match.index as usize);
@@ -203,20 +212,38 @@ fn match_and_score_in_arena<'a>(
203212

204213
if match_start_approx < filename_start {
205214
fallback_indices.push(i as u32);
206-
fallback_filenames.push(file.file_name_from_arena(arena));
215+
fallback_items.push(file);
207216
}
208217
}
209218

210-
if fallback_filenames.is_empty() {
219+
if fallback_items.is_empty() {
211220
vec![]
212221
} else {
213-
let fallback_refs: Vec<&str> = fallback_filenames.iter().map(String::as_str).collect();
214-
let mut matches = neo_frizbee::match_list_parallel(
222+
let mut matches = neo_frizbee::match_list_parallel_resolved(
215223
fuzzy_parts[0],
216-
&fallback_refs,
224+
&fallback_items,
225+
&|item, chunk_buf| -> Option<(usize, u16)> {
226+
// pretty ugly way to express the map_init but it's fine for now
227+
// can't do stack here as the buffer needs to escape this region but can be one
228+
// per thread
229+
thread_local! {
230+
static SCRATCH: std::cell::UnsafeCell<SimdChunk> =
231+
const { std::cell::UnsafeCell::new(SimdChunk([0u8; crate::simd_path::SIMD_CHUNK_BYTES])) };
232+
}
233+
234+
SCRATCH.with(|cell| {
235+
let scratch = unsafe { &mut (*cell.get()).0 };
236+
let (ptrs, fname_len) =
237+
item.path.resolve_filename_ptrs(arena, chunk_buf, scratch);
238+
if fname_len == 0 {
239+
return None;
240+
}
241+
Some((ptrs.len(), fname_len))
242+
})
243+
},
217244
&options,
218-
if path_matches.len() > 10_000 {
219-
context.max_threads
245+
if path_matches.len() > 4096 {
246+
context.max_threads.div_ceil(2048)
220247
} else {
221248
1
222249
},
@@ -233,6 +260,8 @@ fn match_and_score_in_arena<'a>(
233260
let mut next_filename_match_cursor = 0;
234261
let mut dir_buf = String::with_capacity(64);
235262
let mut fname_buf = String::with_capacity(32);
263+
let mut path_buf = [0u8; crate::simd_path::PATH_BUF_SIZE];
264+
236265
let results: Vec<_> = path_matches
237266
.into_iter()
238267
.enumerate()
@@ -340,7 +369,7 @@ fn match_and_score_in_arena<'a>(
340369
// Uses suffix overlap — bytes matching from the end. A full prefix
341370
// match is just the 100% coverage case, so no separate branch needed.
342371
let path_alignment_bonus = if query_contains_path_separator {
343-
let rel_path = file.relative_path_from_arena(arena);
372+
let rel_path = file.path.read_to_buf(arena, &mut path_buf);
344373
let path_bytes = rel_path.as_bytes();
345374
let common_suffix = main_needle
346375
.iter()
@@ -595,28 +624,7 @@ mod tests {
595624
use crate::types::PaginationArgs;
596625
use fff_query_parser::QueryParser;
597626

598-
// ── Helpers ──────────────────────────────────────────────────────────
599-
600-
fn create_test_file(path: &str, score: i32, modified: u64) -> (FileItem, Score) {
601-
let file = FileItem::new_for_test(path, 0, modified, None, false);
602-
let score_obj = Score {
603-
total: score,
604-
base_score: score,
605-
filename_bonus: 0,
606-
distance_penalty: 0,
607-
special_filename_bonus: 0,
608-
current_file_penalty: 0,
609-
frecency_boost: 0,
610-
git_status_boost: 0,
611-
exact_match: false,
612-
match_type: "test",
613-
combo_match_boost: 0,
614-
path_alignment_bonus: 0,
615-
};
616-
(file, score_obj)
617-
}
618-
619-
fn build_test_files(specs: &[(&str, i32, u64)]) -> (Vec<(FileItem, Score)>, ArenaPtr) {
627+
fn make_test_files(specs: &[(&str, i32, u64)]) -> (Vec<(FileItem, Score)>, ArenaPtr) {
620628
let path_strings: Vec<String> = specs.iter().map(|(p, _, _)| p.to_string()).collect();
621629
let items: Vec<FileItem> = specs
622630
.iter()
@@ -659,7 +667,7 @@ mod tests {
659667
#[test]
660668
fn test_partial_sort_descending() {
661669
// Create test data with known scores
662-
let (test_data, arena) = build_test_files(&[
670+
let (test_data, arena) = make_test_files(&[
663671
("file1.rs", 100, 1000),
664672
("file2.rs", 200, 2000),
665673
("file3.rs", 50, 3000),
@@ -716,7 +724,7 @@ mod tests {
716724
#[test]
717725
fn test_partial_sort_with_same_scores() {
718726
// Test tiebreaker with modified time
719-
let (test_data, _arena) = build_test_files(&[
727+
let (test_data, _arena) = make_test_files(&[
720728
("file1.rs", 100, 5000), // Same score, older
721729
("file2.rs", 100, 8000), // Same score, newer
722730
("file3.rs", 100, 3000), // Same score, oldest
@@ -767,7 +775,7 @@ mod tests {
767775
#[test]
768776
fn test_no_partial_sort_for_small_results() {
769777
// When results.len() <= threshold, should use regular sort
770-
let (test_data, arena) = build_test_files(&[
778+
let (test_data, arena) = make_test_files(&[
771779
("file1.rs", 100, 1000),
772780
("file2.rs", 200, 2000),
773781
("file3.rs", 50, 3000),

0 commit comments

Comments
 (0)