1use super::*;
23#[cfg(test)]
4mod tests;
56/// Finds all newlines, multi-byte characters, and non-narrow characters in a
7/// SourceFile.
8///
9/// This function will use an SSE2 enhanced implementation if hardware support
10/// is detected at runtime.
11pub(crate) fn analyze_source_file(src: &str) -> (Vec<RelativeBytePos>, Vec<MultiByteChar>) {
12let mut lines = <[_]>::into_vec(::alloc::boxed::box_new([RelativeBytePos::from_u32(0)]))vec![RelativeBytePos::from_u32(0)];
13let mut multi_byte_chars = ::alloc::vec::Vec::new()vec![];
1415// Calls the right implementation, depending on hardware support available.
16analyze_source_file_dispatch(src, &mut lines, &mut multi_byte_chars);
1718// The code above optimistically registers a new line *after* each \n
19 // it encounters. If that point is already outside the source_file, remove
20 // it again.
21if let Some(&last_line_start) = lines.last() {
22let source_file_end = RelativeBytePos::from_usize(src.len());
23if !(source_file_end >= last_line_start) {
::core::panicking::panic("assertion failed: source_file_end >= last_line_start")
};assert!(source_file_end >= last_line_start);
24if last_line_start == source_file_end {
25lines.pop();
26 }
27 }
2829 (lines, multi_byte_chars)
30}
3132cfg_select! {
33 any(target_arch = "x86", target_arch = "x86_64") => {
34fn analyze_source_file_dispatch(
35 src: &str,
36 lines: &mut Vec<RelativeBytePos>,
37 multi_byte_chars: &mut Vec<MultiByteChar>,
38 ) {
39if true || ::std_detect::detect::__is_feature_detected::sse2()is_x86_feature_detected!("sse2") {
40unsafe {
41analyze_source_file_sse2(src, lines, multi_byte_chars);
42 }
43 } else {
44analyze_source_file_generic(
45src,
46src.len(),
47RelativeBytePos::from_u32(0),
48lines,
49multi_byte_chars,
50 );
51 }
52 }
5354/// Checks 16 byte chunks of text at a time. If the chunk contains
55 /// something other than printable ASCII characters and newlines, the
56 /// function falls back to the generic implementation. Otherwise it uses
57 /// SSE2 intrinsics to quickly find all newlines.
58#[target_feature(enable = "sse2")]
59unsafe fn analyze_source_file_sse2(
60 src: &str,
61 lines: &mut Vec<RelativeBytePos>,
62 multi_byte_chars: &mut Vec<MultiByteChar>,
63 ) {
64#[cfg(target_arch = "x86")]
65use std::arch::x86::*;
66#[cfg(target_arch = "x86_64")]
67use std::arch::x86_64::*;
6869const CHUNK_SIZE: usize = 16;
7071let (chunks, tail) = src.as_bytes().as_chunks::<CHUNK_SIZE>();
7273// This variable keeps track of where we should start decoding a
74 // chunk. If a multi-byte character spans across chunk boundaries,
75 // we need to skip that part in the next chunk because we already
76 // handled it.
77let mut intra_chunk_offset = 0;
7879for (chunk_index, chunk) in chunks.iter().enumerate() {
80// We don't know if the pointer is aligned to 16 bytes, so we
81 // use `loadu`, which supports unaligned loading.
82let chunk = unsafe { _mm_loadu_si128(chunk.as_ptr() as *const __m128i) };
8384// For each character in the chunk, see if its byte value is < 0,
85 // which indicates that it's part of a UTF-8 char.
86let multibyte_test = _mm_cmplt_epi8(chunk, _mm_set1_epi8(0));
87// Create a bit mask from the comparison results.
88let multibyte_mask = _mm_movemask_epi8(multibyte_test);
8990// If the bit mask is all zero, we only have ASCII chars here:
91if multibyte_mask == 0 {
92if !(intra_chunk_offset == 0) {
::core::panicking::panic("assertion failed: intra_chunk_offset == 0")
};assert!(intra_chunk_offset == 0);
9394// Check for newlines in the chunk
95let newlines_test = _mm_cmpeq_epi8(chunk, _mm_set1_epi8(b'\n' as i8));
96let mut newlines_mask = _mm_movemask_epi8(newlines_test);
9798let output_offset = RelativeBytePos::from_usize(chunk_index * CHUNK_SIZE + 1);
99100while newlines_mask != 0 {
101let index = newlines_mask.trailing_zeros();
102103 lines.push(RelativeBytePos(index) + output_offset);
104105// Clear the bit, so we can find the next one.
106newlines_mask &= newlines_mask - 1;
107 }
108 } else {
109// The slow path.
110 // There are multibyte chars in here, fallback to generic decoding.
111let scan_start = chunk_index * CHUNK_SIZE + intra_chunk_offset;
112 intra_chunk_offset = analyze_source_file_generic(
113&src[scan_start..],
114 CHUNK_SIZE - intra_chunk_offset,
115 RelativeBytePos::from_usize(scan_start),
116 lines,
117 multi_byte_chars,
118 );
119 }
120 }
121122// There might still be a tail left to analyze
123let tail_start = src.len() - tail.len() + intra_chunk_offset;
124if tail_start < src.len() {
125analyze_source_file_generic(
126&src[tail_start..],
127src.len() - tail_start,
128RelativeBytePos::from_usize(tail_start),
129lines,
130multi_byte_chars,
131 );
132 }
133 }
134 }
135 target_arch = "loongarch64" => {
136fn analyze_source_file_dispatch(
137 src: &str,
138 lines: &mut Vec<RelativeBytePos>,
139 multi_byte_chars: &mut Vec<MultiByteChar>,
140 ) {
141use std::arch::is_loongarch_feature_detected;
142143if is_loongarch_feature_detected!("lsx") {
144unsafe {
145 analyze_source_file_lsx(src, lines, multi_byte_chars);
146 }
147 } else {
148 analyze_source_file_generic(
149 src,
150 src.len(),
151 RelativeBytePos::from_u32(0),
152 lines,
153 multi_byte_chars,
154 );
155 }
156 }
157158/// Checks 16 byte chunks of text at a time. If the chunk contains
159 /// something other than printable ASCII characters and newlines, the
160 /// function falls back to the generic implementation. Otherwise it uses
161 /// LSX intrinsics to quickly find all newlines.
162#[target_feature(enable = "lsx")]
163unsafe fn analyze_source_file_lsx(
164 src: &str,
165 lines: &mut Vec<RelativeBytePos>,
166 multi_byte_chars: &mut Vec<MultiByteChar>,
167 ) {
168use std::arch::loongarch64::*;
169170const CHUNK_SIZE: usize = 16;
171172let (chunks, tail) = src.as_bytes().as_chunks::<CHUNK_SIZE>();
173174// This variable keeps track of where we should start decoding a
175 // chunk. If a multi-byte character spans across chunk boundaries,
176 // we need to skip that part in the next chunk because we already
177 // handled it.
178let mut intra_chunk_offset = 0;
179180for (chunk_index, chunk) in chunks.iter().enumerate() {
181// All LSX memory instructions support unaligned access, so using
182 // vld is fine.
183let chunk = unsafe { lsx_vld::<0>(chunk.as_ptr() as *const i8) };
184185// For each character in the chunk, see if its byte value is < 0,
186 // which indicates that it's part of a UTF-8 char.
187let multibyte_mask = lsx_vmskltz_b(chunk);
188// Create a bit mask from the comparison results.
189let multibyte_mask = lsx_vpickve2gr_w::<0>(multibyte_mask);
190191// If the bit mask is all zero, we only have ASCII chars here:
192if multibyte_mask == 0 {
193assert!(intra_chunk_offset == 0);
194195// Check for newlines in the chunk
196let newlines_test = lsx_vseqi_b::<{b'\n' as i32}>(chunk);
197let newlines_mask = lsx_vmskltz_b(newlines_test);
198let mut newlines_mask = lsx_vpickve2gr_w::<0>(newlines_mask);
199200let output_offset = RelativeBytePos::from_usize(chunk_index * CHUNK_SIZE + 1);
201202while newlines_mask != 0 {
203let index = newlines_mask.trailing_zeros();
204205 lines.push(RelativeBytePos(index) + output_offset);
206207// Clear the bit, so we can find the next one.
208newlines_mask &= newlines_mask - 1;
209 }
210 } else {
211// The slow path.
212 // There are multibyte chars in here, fallback to generic decoding.
213let scan_start = chunk_index * CHUNK_SIZE + intra_chunk_offset;
214 intra_chunk_offset = analyze_source_file_generic(
215&src[scan_start..],
216 CHUNK_SIZE - intra_chunk_offset,
217 RelativeBytePos::from_usize(scan_start),
218 lines,
219 multi_byte_chars,
220 );
221 }
222 }
223224// There might still be a tail left to analyze
225let tail_start = src.len() - tail.len() + intra_chunk_offset;
226if tail_start < src.len() {
227 analyze_source_file_generic(
228&src[tail_start..],
229 src.len() - tail_start,
230 RelativeBytePos::from_usize(tail_start),
231 lines,
232 multi_byte_chars,
233 );
234 }
235 }
236 }
237_ => {
238// The target (or compiler version) does not support vector instructions
239 // our specialized implementations need (x86 SSE2, loongarch64 LSX)...
240fn analyze_source_file_dispatch(
241 src: &str,
242 lines: &mut Vec<RelativeBytePos>,
243 multi_byte_chars: &mut Vec<MultiByteChar>,
244 ) {
245 analyze_source_file_generic(
246 src,
247 src.len(),
248 RelativeBytePos::from_u32(0),
249 lines,
250 multi_byte_chars,
251 );
252 }
253 }
254}
255256// `scan_len` determines the number of bytes in `src` to scan. Note that the
257// function can read past `scan_len` if a multi-byte character start within the
258// range but extends past it. The overflow is returned by the function.
259fn analyze_source_file_generic(
260 src: &str,
261 scan_len: usize,
262 output_offset: RelativeBytePos,
263 lines: &mut Vec<RelativeBytePos>,
264 multi_byte_chars: &mut Vec<MultiByteChar>,
265) -> usize {
266if !(src.len() >= scan_len) {
::core::panicking::panic("assertion failed: src.len() >= scan_len")
};assert!(src.len() >= scan_len);
267let mut i = 0;
268let src_bytes = src.as_bytes();
269270while i < scan_len {
271let byte = unsafe {
272// We verified that i < scan_len <= src.len()
273*src_bytes.get_unchecked(i)
274 };
275276// How much to advance in order to get to the next UTF-8 char in the
277 // string.
278let mut char_len = 1;
279280if byte == b'\n' {
281let pos = RelativeBytePos::from_usize(i) + output_offset;
282 lines.push(pos + RelativeBytePos(1));
283 } else if byte >= 128 {
284// This is the beginning of a multibyte char. Just decode to `char`.
285let c = src[i..].chars().next().unwrap();
286 char_len = c.len_utf8();
287288let pos = RelativeBytePos::from_usize(i) + output_offset;
289if !(2..=4).contains(&char_len) {
::core::panicking::panic("assertion failed: (2..=4).contains(&char_len)")
};assert!((2..=4).contains(&char_len));
290let mbc = MultiByteChar { pos, bytes: char_len as u8 };
291 multi_byte_chars.push(mbc);
292 }
293294 i += char_len;
295 }
296297i - scan_len298}