Skip to main content

rumtk_arena/
cpu.rs

1/*
2 *     rumtk attempts to implement HL7 and medical protocols for interoperability in medicine.
3 *     This toolkit aims to be reliable, simple, performant, and standards compliant.
4 *     Copyright (C) 2026  Luis M. Santos, M.D. <lsantos@medicalmasses.com>
5 *     Copyright (C) 2026  MedicalMasses L.L.C. <contact@medicalmasses.com>
6 *
7 *     This program is free software: you can redistribute it and/or modify
8 *     it under the terms of the GNU General Public License as published by
9 *     the Free Software Foundation, either version 3 of the License, or
10 *     (at your option) any later version.
11 *
12 *     This program is distributed in the hope that it will be useful,
13 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
14 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 *     GNU General Public License for more details.
16 *
17 *     You should have received a copy of the GNU General Public License
18 *     along with this program.  If not, see <https://www.gnu.org/licenses/>.
19 */
20use crate::base::RUMVec;
21pub use branches::{likely as cpu_likely_branch, prefetch_read_data, unlikely as cpu_unlikely_branch};
22pub use std::simd::prelude::*;
23
24pub const CPU_L1_PREFETCH: i32 = 0;
25pub const CPU_L2_PREFETCH: i32 = 1;
26pub const CPU_L3_PREFETCH: i32 = 2;
27pub const CPU_NONTEMPORAL_PREFETCH: i32 = 3;
28pub const CPU_L1_CACHE_LINE_SIZE: usize = 64; // Number of bytes in a typical x86_64 CPU L1 cache line.
29pub const CPU_L1_CACHE_SIZE: usize = 32 * 1024; // Number of bytes in a typical x86_64 CPU L1 cache per core.
30pub const CPU_PAGE_SIZE: usize = 4 * 1024; // Typical CPU page size
31pub const CPU_SIMD_64_SIZE: usize = 64;
32pub const CPU_SIMD_32_SIZE: usize = 32;
33pub const CPU_SIMD_16_SIZE: usize = 16;
34pub const CPU_SIMD_8_SIZE: usize = 8;
35pub const CPU_SEARCH_WINDOW_1024_SIZE: usize = 1024;
36pub const CPU_SEARCH_WINDOW_512_SIZE: usize = 512;
37pub const CPU_SEARCH_WINDOW_256_SIZE: usize = 256;
38pub const CPU_SEARCH_WINDOW_128_SIZE: usize = 128;
39pub const CPU_SEARCH_WINDOW_64_SIZE: usize = 64;
40pub const CPU_SEARCH_WINDOW_32_SIZE: usize = 32;
41pub const CPU_SEARCH_WINDOW_16_SIZE: usize = 16;
42
43
44#[cfg(feature = "simd")]
45pub type u8xN<const SEARCH_WINDOW_SIZE: usize> = Simd<u8, SEARCH_WINDOW_SIZE>;
46
47////////////////////////////////////////CPU CACHE HINTS///////////////////////////////
48#[inline]
49pub fn cpu_l3_prefetch(data: *const u8) {
50    prefetch_read_data::<u8, CPU_L3_PREFETCH>(data);
51}
52
53#[inline]
54pub fn cpu_l2_prefetch(data: *const u8) {
55    prefetch_read_data::<u8, CPU_L2_PREFETCH>(data);
56}
57
58#[inline]
59pub fn cpu_l1_prefetch(data: *const u8) {
60    prefetch_read_data::<u8, CPU_L1_PREFETCH>(data);
61}
62
63#[inline(always)]
64pub fn cpu_slice_to_array<const SLICE_SIZE: usize>(chunk: &[u8]) -> &[u8; SLICE_SIZE] {
65    chunk.try_into().expect("length mismatch")
66}
67
68#[inline(always)]
69pub fn cpu_slice_to_array_padded<const SLICE_SIZE: usize, const PAD: u8>(chunk: &[u8]) -> [u8; SLICE_SIZE] {
70    let mut result = [PAD; SLICE_SIZE];
71    result[..chunk.len()].copy_from_slice(chunk);
72    result
73}
74
75#[cfg(feature = "simd")]
76#[inline(always)]
77pub fn cpu_slice_to_simd<const SLICE_SIZE: usize, const PAD: u8>(chunk: &[u8]) -> u8xN<SLICE_SIZE> {
78    u8xN::from_array(cpu_slice_to_array_padded::<SLICE_SIZE, PAD>(chunk))
79}
80
81#[inline(always)]
82pub fn cpu_slice_splat<const SLICE_SIZE: usize>(input: &[u8]) -> [u8; SLICE_SIZE] {
83    let mut result = [0u8; SLICE_SIZE];
84
85    for chunk in result.chunks_mut(input.len()) {
86        chunk.copy_from_slice(&input[..chunk.len()]);
87    }
88
89    result
90}
91
92////////////////////////////////////////SIMD MASKS//////////////////////////////////////////
93#[cfg(feature = "simd")]
94#[inline(always)]
95pub fn cpu_simd_shift_right_n<const LANE_SIZE: usize, const SHIFT: usize, const PAD: u8>(item: &u8xN<LANE_SIZE>) -> u8xN<LANE_SIZE> {
96    item.shift_elements_right::<SHIFT>(PAD)
97}
98
99#[cfg(feature = "simd")]
100#[inline(always)]
101pub fn cpu_simd_masks<const LANE_SIZE: usize>(pattern: &[u8]) -> RUMVec<u8xN<LANE_SIZE>> {
102    let mask = u8xN::<LANE_SIZE>::from_array(cpu_slice_splat(pattern));
103    let mut masks = RUMVec::<u8xN<LANE_SIZE>>::with_capacity(pattern.len());
104
105    masks.push(mask);
106
107    for i in 1..pattern.len() {
108        let shifted = cpu_simd_shift_right_n::<LANE_SIZE, 1, 0>(&mask);
109        masks.push(shifted);
110    }
111
112    masks
113}
114
115////////////////////////////////////////SEARCH FOR NEEDLE IN HAYSTACK///////////////////////////////
116
117#[inline(always)]
118pub fn cpu_find_fallback(chunk: &[u8], byte: u8) -> Option<usize> {
119    chunk.iter().position(|c| *c==byte)
120}
121
122#[cfg(feature = "simd")]
123#[inline]
124fn cpu_find_simd_avx2_n<const SEARCH_WINDOW_SIZE: usize>(chunk: &[u8], target: u8xN<SEARCH_WINDOW_SIZE>) -> Option<usize> {
125    let data_vec = cpu_slice_to_simd::<SEARCH_WINDOW_SIZE, 0>(chunk);
126    let mask = data_vec.simd_eq(target);
127
128    if mask.any() {
129        let bitmask = mask.to_bitmask();
130        let lane_i = bitmask.trailing_zeros() as usize;
131        return Some(lane_i);
132    }
133
134    None
135}
136
137#[cfg(feature = "simd")]
138#[inline]
139pub fn cpu_find_simd_n<const LANE_SIZE: usize>
140(
141    chunk: &[u8],
142    byte: u8,
143) -> Option<usize>
144{
145    let mask = u8xN::<LANE_SIZE>::splat(byte);
146    let mut indx = 0;
147
148    for window in chunk.chunks(LANE_SIZE) {
149        match cpu_find_simd_avx2_n::<LANE_SIZE>(window, mask) {
150            Some(lane_i) => {
151                return Some(indx + lane_i)
152            },
153            None => {
154                indx += LANE_SIZE;
155                continue
156            },
157        }
158    }
159
160    None
161}
162
163#[cfg(feature = "simd")]
164#[inline]
165pub fn cpu_find(window: &[u8], byte: u8) -> Option<usize> {
166    cpu_find_simd_n::<CPU_SIMD_64_SIZE>(
167        window,
168        byte,
169    )
170}
171
172#[cfg(not(feature = "simd"))]
173#[inline]
174pub fn cpu_find(window: &[u8], byte: u8) -> Option<usize> {
175    cpu_find_fallback(
176        window,
177        byte,
178    )
179}
180
181/////////////////////////////Replacement Helpers///////////////////////////////
182#[inline(always)]
183pub fn cpu_replace_fallback(data: &mut [u8], pattern: u8, replacement: u8) {
184    for i in 0..data.len() {
185        if data[i] == pattern {
186            data[i] = replacement;
187        }
188    }
189}
190
191#[cfg(feature = "simd")]
192#[inline(always)]
193pub fn cpu_find_replace_simd_n<const LANE_SIZE: usize>(chunk: &mut [u8], pattern: u8xN<LANE_SIZE>, replacement: u8xN<LANE_SIZE>) {
194    let simd_chunk = u8xN::<LANE_SIZE>::from_array(cpu_slice_to_array_padded::<LANE_SIZE, 0>(chunk));
195    let bitmask = simd_chunk.simd_eq(pattern);
196
197    if bitmask.any() {
198        replacement.store_select(chunk, bitmask);
199    }
200}
201
202#[cfg(feature = "simd")]
203#[inline(always)]
204pub fn cpu_replace_simd_n<const LANE_SIZE: usize>(data: &mut [u8], pattern: u8, replacement: u8) {
205    let mask = u8xN::<LANE_SIZE>::splat(pattern);
206    let simd_replacement = u8xN::<LANE_SIZE>::splat(replacement);
207
208    for chunk in data.chunks_mut(LANE_SIZE) {
209        cpu_find_replace_simd_n::<LANE_SIZE>(chunk, mask, simd_replacement);
210    }
211}
212
213#[cfg(feature = "simd")]
214#[inline(always)]
215pub fn cpu_replace_byte(data: &mut [u8], pattern: u8, replacement: u8) {
216    cpu_replace_simd_n::<CPU_SIMD_64_SIZE>(data, pattern, replacement)
217}
218
219#[cfg(not(feature = "simd"))]
220#[inline(always)]
221pub fn cpu_replace_byte(data: &mut [u8], pattern: u8, replacement: u8) {
222    cpu_replace_fallback(data, pattern, replacement)
223}
224
225/////////////////////////////GATHER ALL INDICES OF NEEDLE IN HAYSTACK///////////////////////////////
226pub type CPUTokenStackIndex<const LANE_SIZE: usize> = [u32; LANE_SIZE];
227pub type CPUTokenRelativeStackInfo<const LANE_SIZE: usize> = (usize, CPUTokenStackIndex<LANE_SIZE>);
228pub type CPUTokenIndexCollection = RUMVec<u32>;
229pub type CPUTokenIndexSet = (u8, RUMVec<u32>);
230pub type CPUTokenSet = (u8, u32);
231pub type CPUTokenSetCollection = RUMVec<CPUTokenSet>;
232
233#[inline(always)]
234pub fn cpu_collect_fallback(chunk: &[u8], byte: u8, offset: usize) -> CPUTokenIndexCollection {
235    let mut results: CPUTokenStackIndex<CPU_SIMD_64_SIZE> = [0; CPU_SIMD_64_SIZE];
236    let mut length = 0;
237
238    for i in 0..chunk.len() {
239        if chunk[i]==byte {
240            let pos = (offset + i) as usize;
241            results[length] = pos as u32;
242            length += 1;
243        }
244    }
245
246    CPUTokenIndexCollection::from(&results[..length])
247}
248
249#[cfg(feature = "simd")]
250#[inline]
251fn cpu_collect_simd_avx2_n<const LANE_SIZE: usize>(data_vec: &u8xN<LANE_SIZE>, target: u8xN<LANE_SIZE>, offset: usize) -> Option<CPUTokenRelativeStackInfo<LANE_SIZE>> {
252    let mut results: CPUTokenStackIndex<LANE_SIZE> = [0; LANE_SIZE];
253    let mut length = 0;
254
255    let mask = data_vec.simd_eq(target);
256
257    if cpu_unlikely_branch(mask.any()) {
258        let items = mask.to_array();
259
260        for i in 0..items.len() {
261            if cpu_unlikely_branch(items[i]) {
262                let pos = (offset + i) as usize;
263                results[length] = pos as u32;
264                length += 1;
265            }
266        }
267
268        return Some((length, results));
269    }
270
271    None
272}
273
274#[cfg(feature = "simd")]
275#[inline]
276pub fn cpu_collect_simd_n<const LANE_SIZE: usize>
277(
278    chunk: &[u8],
279    byte: u8,
280    offset: usize
281) -> CPUTokenIndexCollection
282{
283    let mask = u8xN::<LANE_SIZE>::splat(byte);
284    let (prefix, middle, postfix) = chunk.as_simd::<LANE_SIZE>();
285
286    let mut local_offset: usize = offset;
287    let data: CPUTokenIndexCollection = cpu_collect_fallback(prefix, byte, local_offset);
288    let mut positions: CPUTokenIndexCollection = CPUTokenIndexCollection::from(data);
289    local_offset += prefix.len();
290
291    for window in middle.into_iter() {
292        match cpu_collect_simd_avx2_n::<LANE_SIZE>(window, mask, local_offset) {
293            Some((len, data)) => {
294                positions.extend_from_slice(&data[..len]);
295            },
296            None => {},
297        };
298        local_offset += LANE_SIZE;
299    }
300
301    let data: CPUTokenIndexCollection = cpu_collect_fallback(postfix, byte, local_offset);
302    positions.extend_from_slice(&data);
303
304    positions
305}
306
307#[cfg(feature = "simd")]
308#[inline]
309pub fn cpu_collect(window: &[u8], byte: u8, offset: usize) -> CPUTokenIndexSet {
310    let indx = cpu_collect_simd_n::<CPU_SIMD_64_SIZE>(
311        window,
312        byte,
313        offset
314    );
315    (byte, indx)
316}
317
318#[cfg(not(feature = "simd"))]
319#[inline]
320pub fn cpu_collect(window: &[u8], byte: u8, offset: usize) -> CPUTokenIndexSet {
321    let indx = cpu_collect_fallback(
322        window,
323        byte,
324        offset
325    );
326    (byte, indx)
327}
328
329#[inline]
330pub fn cpu_tokenize<const WINDOW_SIZE: usize>(haystack: &[u8], bytes: &[u8]) -> CPUTokenSetCollection
331{
332    let mut results = CPUTokenSetCollection::with_capacity(haystack.len() * size_of::<CPUTokenSet>());
333    let mut offset = 0;
334
335    for window in haystack.chunks(WINDOW_SIZE) {
336        for byte in bytes {
337            let (b, indx) = cpu_collect(window, *byte, offset);
338
339            if !indx.is_empty() {
340                for tok_indx in indx {
341                    results.push((b, tok_indx));
342                }
343            }
344        }
345        offset += window.len();
346    }
347
348    results.sort_unstable_by(|a,b| a.1.cmp(&b.1));
349
350    results
351}
352
353#[inline]
354pub fn cpu_tokenize_rev<const WINDOW_SIZE: usize>(haystack: &[u8], bytes: &[u8]) -> CPUTokenSetCollection
355{
356    let reversed: Vec<u8> = bytes.iter().rev().cloned().collect();
357    cpu_tokenize::<WINDOW_SIZE>(haystack, &reversed)
358}