use super::*;
#[derive(Clone, Copy)]
struct Prefix {
first: [u8; LEADING_BRANCHES],
second: [u8; LEADING_BRANCHES],
members: usize,
}
impl Vm<'_, '_, '_, '_> {
pub(super) fn end_candidate(&mut self) -> Result<bool, ExecError> {
let descriptor = self.program.words[8] & 0xff_ffff;
if descriptor == 0 {
return Ok(true);
}
self.charge(1)?;
let mut end = self
.input
.cursor_at(self.input.len_utf16())
.ok_or(ExecError::InvalidProgram)?;
let Some(unit) = end.previous_unit() else {
return Ok(false);
};
Ok(unit >= 128
|| (descriptor != 1
&& u32::from(unit) >= (descriptor >> 8) & 255
&& u32::from(unit) <= (descriptor >> 16) & 255))
}
pub(super) fn start_candidate(&mut self) {
self.state.phase = if self.program.words[7] != 0 && self.input.original_bytes().is_some() {
Phase::Candidate
} else {
Phase::Initialize(0)
};
}
fn prefix(&self, leading: usize) -> Prefix {
if leading == LEADING_ALTERNATION as usize {
self.branch_pairs()
} else {
self.literal_pairs()
}
}
fn branch_pairs(&self) -> Prefix {
let mut prefix = Prefix {
first: [0; LEADING_BRANCHES],
second: [0; LEADING_BRANCHES],
members: 0,
};
let mut pending = [0usize; LEADING_BRANCHES];
let mut depth = 0;
let mut pc = self.program.leading_pc();
loop {
let [op, a, b] = self.program.instruction(pc);
if op == SPLIT {
if depth == pending.len() {
prefix.members = 0;
return prefix;
}
pending[depth] = b as usize;
depth += 1;
pc = a as usize;
continue;
}
if prefix.members == prefix.first.len() {
prefix.members = 0;
return prefix;
}
prefix.first[prefix.members] = self.program.instruction(pc)[1] as u8;
prefix.second[prefix.members] = self.program.instruction(pc + 1)[1] as u8;
prefix.members += 1;
if depth == 0 {
return prefix;
}
depth -= 1;
pc = pending[depth];
}
}
fn literal_pairs(&self) -> Prefix {
let mut prefix = Prefix {
first: [0; LEADING_BRANCHES],
second: [0; LEADING_BRANCHES],
members: 0,
};
let pc = self.program.leading_pc();
let one = self.program.instruction(pc)[1] as u8;
let two = self.program.instruction(pc + 1)[1] as u8;
if !self.program.leading_fold() {
prefix.first[0] = one;
prefix.second[0] = two;
prefix.members = 1;
return prefix;
}
for a in [one.to_ascii_lowercase(), one.to_ascii_uppercase()] {
for b in [two.to_ascii_lowercase(), two.to_ascii_uppercase()] {
let held = prefix.first[..prefix.members]
.iter()
.zip(&prefix.second[..prefix.members])
.any(|(&f, &s)| f == a && s == b);
if !held {
prefix.first[prefix.members] = a;
prefix.second[prefix.members] = b;
prefix.members += 1;
}
}
}
prefix
}
fn alternation_matches(&mut self, bytes: &[u8], at: usize) -> Result<bool, ExecError> {
let mut pending = [0usize; LEADING_BRANCHES];
let mut depth = 0;
let mut pc = self.program.leading_pc();
loop {
self.charge(1)?;
let [op, a, b] = self.program.instruction(pc);
if op == SPLIT {
if depth == pending.len() {
return Ok(true);
}
pending[depth] = b as usize;
depth += 1;
pc = a as usize;
continue;
}
let mut matched = true;
let mut next = pc;
while self.program.instruction(next)[0] == CHAR {
self.charge(1)?;
let want = self.program.instruction(next)[1];
match bytes.get(at + (next - pc)) {
Some(&byte) if u32::from(byte) == want => next += 1,
_ => {
matched = false;
break;
}
}
}
if matched {
return Ok(true);
}
if depth == 0 {
return Ok(false);
}
depth -= 1;
pc = pending[depth];
}
}
fn leading_matches(
&mut self,
bytes: &[u8],
at: usize,
leading: usize,
) -> Result<Option<bool>, ExecError> {
if leading == LEADING_ALTERNATION as usize {
return self.alternation_matches(bytes, at).map(Some);
}
let Some(window) = bytes.get(at..at + leading) else {
self.charge(1)?;
return Ok(None);
};
self.charge(leading)?;
let pc = self.program.leading_pc();
let fold = self.program.leading_fold();
for (i, &byte) in window.iter().enumerate() {
let want = self.program.instruction(pc + i)[1];
let same = if fold {
byte.eq_ignore_ascii_case(&(want as u8))
} else {
u32::from(byte) == want
};
if !same {
return Ok(Some(false));
}
}
Ok(Some(true))
}
pub(super) fn candidate_step(&mut self, available: usize) -> Result<(), ExecError> {
let Some(bytes) = self.input.ascii_bytes() else {
return self.mixed_candidate_step(available);
};
let start = self.cursor.position();
let descriptor = self.program.words[7];
if descriptor == 1 || start == bytes.len() {
self.charge(1)?;
self.state.phase = Phase::Finished(false);
return Ok(());
}
let lo = (descriptor >> 8) as u8;
let hi = (descriptor >> 16) as u8;
let limit = if self.program.words[2] & Y != 0 {
1
} else {
4096
};
let mut count = (bytes.len() - start)
.min(available.min(limit))
.min(self.budget.remaining());
if count == 0 {
self.charge(1)?;
return Err(ExecError::InvalidProgram);
}
if self.program.admission_forward() && self.state.required_at != UNSET {
if start > self.state.required_at {
self.charge(1)?;
self.state.phase = Phase::BoundPrepare {
from: self.state.required_from,
};
return Ok(());
}
count = count.min(self.state.required_at + 1 - start);
}
let leading = self.program.leading();
let prefix = (leading != 0).then(|| self.prefix(leading));
let mut scanned = 0;
let found = loop {
let (found, inspected) = match &prefix {
Some(p) if p.members != 0 => first_in_pairs(
&bytes[start + scanned..],
count - scanned,
&p.first[..p.members],
&p.second[..p.members],
),
_ => first_in_range::<false, false>(&bytes[start + scanned..start + count], lo, hi),
};
self.charge(inspected)?;
let Some(index) = found else { break None };
let at = start + scanned + index;
if leading == 0 {
break Some(at);
}
let outcome = self.leading_matches(bytes, at, leading)?;
match outcome {
Some(true) => break Some(at),
None => {
self.state.phase = Phase::Finished(false);
return Ok(());
}
Some(false) => {}
}
scanned += index + 1;
if scanned >= count {
break None;
}
};
if let Some(at) = found {
if self.program.words[2] & Y != 0 && at != self.state.requested_start {
self.state.phase = Phase::Finished(false);
} else {
self.cursor = self.input.cursor_at(at).ok_or(ExecError::InvalidProgram)?;
self.state.start = self.cursor.mark();
self.state.verified = if leading >= 2 { leading } else { 0 };
self.state.phase = Phase::Initialize(0);
}
} else if self.program.words[2] & Y != 0 || start + count == bytes.len() {
self.state.phase = Phase::Finished(false);
} else {
self.cursor = self
.input
.cursor_at(start + count)
.ok_or(ExecError::InvalidProgram)?;
}
Ok(())
}
#[inline(never)]
fn mixed_candidate_step(&mut self, available: usize) -> Result<(), ExecError> {
let Some(bytes) = self.cursor.byte_tail() else {
self.charge(1)?;
self.state.phase = Phase::Initialize(0);
return Ok(());
};
if bytes.is_empty() {
self.charge(1)?;
self.state.phase = Phase::Finished(false);
return Ok(());
}
let descriptor = self.program.words[7];
let (lo, hi) = if descriptor == 1 {
(128, 127)
} else {
((descriptor >> 8) as u8, (descriptor >> 16) as u8)
};
let sticky = self.program.words[2] & Y != 0;
let count = bytes
.len()
.min(available.min(if sticky { 1 } else { 256 }))
.min(self.budget.remaining());
if count == 0 {
self.charge(1)?;
return Err(ExecError::InvalidProgram);
}
let (found, inspected) = first_in_range::<true, true>(&bytes[..count], lo, hi);
self.charge(inspected)?;
if let Some(index) = found {
if !self.cursor.skip_ascii(index) {
return Err(ExecError::InvalidProgram);
}
self.state.start = self.cursor.mark();
self.state.phase = Phase::Initialize(0);
} else if sticky || count == bytes.len() {
self.state.phase = Phase::Finished(false);
} else if !self.cursor.skip_ascii(count) {
return Err(ExecError::InvalidProgram);
}
Ok(())
}
}
pub(super) fn first_in_pairs(
bytes: &[u8],
limit: usize,
first: &[u8],
second: &[u8],
) -> (Option<usize>, usize) {
const HIGH: u64 = 0x8080_8080_8080_8080;
const ONES: u64 = 0x0101_0101_0101_0101;
const LANES: usize = 32;
const BLOCK_MIN: usize = 256;
let mut base = 0;
while limit >= BLOCK_MIN && base + LANES <= limit && base + LANES < bytes.len() {
let block: [u8; LANES] = bytes[base..base + LANES].try_into().unwrap();
let after: [u8; LANES] = bytes[base + 1..base + 1 + LANES].try_into().unwrap();
let mut hit = [0u8; LANES];
for (&one, &two) in first.iter().zip(second) {
for lane in 0..LANES {
hit[lane] |= u8::from(block[lane] == one) & u8::from(after[lane] == two);
}
}
for (word, marks) in hit.chunks_exact(8).enumerate() {
let marks = u64::from_le_bytes(marks.try_into().unwrap());
if marks != 0 {
let lane = word * 8 + marks.trailing_zeros() as usize / 8;
return (Some(base + lane), base + lane + 1);
}
}
base += LANES;
}
while base + 8 <= limit && base + 9 <= bytes.len() {
let word = u64::from_le_bytes(bytes[base..base + 8].try_into().unwrap());
let after = u64::from_le_bytes(bytes[base + 1..base + 9].try_into().unwrap());
let mut hit = 0;
for (&one, &two) in first.iter().zip(second) {
let a = word ^ (u64::from(one) * ONES);
let b = after ^ (u64::from(two) * ONES);
hit |= a.wrapping_sub(ONES) & !a & b.wrapping_sub(ONES) & !b & HIGH;
}
if hit != 0 {
let lane = hit.trailing_zeros() as usize / 8;
return (Some(base + lane), base + lane + 1);
}
base += 8;
}
while base < limit {
let byte = bytes[base];
let next = bytes.get(base + 1).copied();
if first
.iter()
.zip(second)
.any(|(&one, &two)| byte == one && next == Some(two))
{
return (Some(base), base + 1);
}
base += 1;
}
(None, limit)
}
pub(super) fn first_in_range<const MIXED: bool, const STOP_NON_ASCII: bool>(
bytes: &[u8],
lo: u8,
hi: u8,
) -> (Option<usize>, usize) {
if (bytes[0] >= lo && bytes[0] <= hi) || (STOP_NON_ASCII && bytes[0] >= 128) {
return (Some(0), 1);
}
let mut i = 1;
const HIGH: u64 = 0x8080_8080_8080_8080;
const ONES: u64 = 0x0101_0101_0101_0101;
let lower = u64::from(lo) * ONES;
let upper = u64::from(hi) * ONES;
while i + 8 <= bytes.len() {
let word = u64::from_le_bytes(bytes[i..i + 8].try_into().unwrap());
let lanes = if MIXED { word & !HIGH } else { word };
let range =
((lanes | HIGH).wrapping_sub(lower)) & ((upper | HIGH).wrapping_sub(lanes)) & HIGH;
let mask = if STOP_NON_ASCII {
range | (word & HIGH)
} else if MIXED {
range & !word
} else {
range
};
if mask != 0 {
let at = i + mask.trailing_zeros() as usize / 8;
return (Some(at), at + 1);
}
i += 8;
}
while i < bytes.len() {
if (bytes[i] >= lo && bytes[i] <= hi) || (STOP_NON_ASCII && bytes[i] >= 128) {
return (Some(i), i + 1);
}
i += 1;
}
(None, bytes.len())
}
#[cfg(test)]
mod tests {
use super::first_in_range;
#[test]
fn mixed_word_scan_stops_at_every_non_ascii_lane() {
for (lo, hi) in [
(0, 0),
(0, 127),
(65, 90),
(110, 110),
(127, 127),
(128, 127),
] {
for byte in 0..=255u8 {
for position in 0..33 {
let mut bytes = [b'x'; 33];
bytes[position] = byte;
let want = bytes.iter().position(|&b| b >= 128 || (b >= lo && b <= hi));
let (got, inspected) = first_in_range::<true, true>(&bytes, lo, hi);
assert_eq!(got, want, "{lo}..{hi}, byte {byte}, position {position}");
assert_eq!(inspected, want.map_or(bytes.len(), |at| at + 1));
}
}
}
}
#[test]
fn mixed_range_membership_rejects_high_bytes_without_cross_lane_borrows() {
for (lo, hi) in [(0, 0), (0, 127), (65, 90), (97, 102), (127, 127)] {
for byte in 0..=255u8 {
for position in 0..33 {
let mut bytes = [0u8; 33];
for (i, b) in bytes.iter_mut().enumerate() {
*b = (i * 73 + usize::from(byte) * 19) as u8;
}
bytes[position] = byte;
let want = bytes.iter().position(|&b| b >= lo && b <= hi);
let (got, inspected) = first_in_range::<true, false>(&bytes, lo, hi);
assert_eq!(got, want, "{lo}..{hi}, byte {byte}, position {position}");
assert_eq!(inspected, want.map_or(bytes.len(), |at| at + 1));
}
}
}
}
#[test]
fn ascii_range_words_match_scalar_lanes() {
for lo in 0..128u8 {
for hi in lo..128 {
for position in 0..17 {
let mut bytes = [0u8; 17];
for (i, b) in bytes.iter_mut().enumerate() {
*b = ((i * 37 + position * 13) % 128) as u8;
}
let want = bytes.iter().position(|&b| b >= lo && b <= hi);
let (got, inspected) = first_in_range::<false, false>(&bytes, lo, hi);
assert_eq!(got, want, "range {lo}..{hi}, rotation {position}");
assert!((1..=bytes.len()).contains(&inspected));
if let Some(at) = got {
assert!(at < inspected);
}
}
}
}
}
}