REAL
Regular Expression Algorithmic Library — constexpr C++20 regex
Loading...
Searching...
No Matches
real::detail::pattern_hints Struct Reference

Search-acceleration hints extracted from a compiled program. More...

Collaboration diagram for real::detail::pattern_hints:
[legend]

Public Attributes

std::array< char, 16 > prefix {}
 Required literal prefix (possibly truncated).
 
std::uint8_t prefix_size {}
 Valid bytes in prefix.
 
bool anchored_start {}
 \A / ^ (no multiline): only position 0.
 
bool line_anchored {}
 ^ multiline: position 0 or after \n.
 
bool first_bytes_valid {}
 False when an empty match is possible.
 
bool empty_match_possible {}
 The pattern can match the empty string (the nullable gate; conservative: assertions/lookarounds pass through, so e.g. ^$ is flagged nullable).
 
bool nullable_captured_repeat {}
 The pattern contains a CAPTURING group (not (?:...)) whose body is nullable, transitively under a quantifier (any quantifier, ? included) — e.g. (ab|)+a. AST-derived (compiler.hpp, not analyze_program/prefilter.hpp): real's engine captures the loop's last CONSUMING iteration (RE2/Rust/Go lineage) while an ECMAScript backtracker captures an extra empty final one, so real::compat routes replace/iterate to std for these patterns (real::compat::basic_regex::uses_real_traversal); conservative over-approximation (assertions/lookarounds count as nullable), so sur-flagging is safe.
 
std::int16_t single_first {-1}
 The unique possible first byte, or -1.
 
char_class first_bytes
 All possible first bytes.
 
std::int32_t greedy_class_loop {-1}
 
std::uint8_t greedy_class_loop_end {}
 Trailing end anchor on greedy_class_loop — 0 none, 1 \Z, 2 $ (which also accepts one final newline). The match must END at that limit; the route walks the run BACKWARD from it rather than scanning forward and retrying.
 
std::uint16_t greedy_class_loop_min {1}
 Minimum run length (bytes) for greedy_class_loop – 1 for a bare X+, k for the X{k,} desugaring (k mandatory copies of the same atom then a loop of it, compiler.hpp's emit_repeat).
 
std::int32_t greedy_cp_class {-1}
 cp_class index if the whole pattern is a code-point class klass_cp (optionally +), else -1.
 
bool greedy_cp_class_plus {}
 
std::uint8_t greedy_cp_class_end {}
 Trailing end anchor on greedy_cp_class — 0 none, 1 \Z, 2 $ (which also accepts one final newline). Same meaning as greedy_class_loop_end.
 
std::uint16_t greedy_cp_class_min {1}
 Minimum run length (CODE POINTS, not bytes) for greedy_cp_class when greedy_cp_class_plus is set – see greedy_class_loop_min.
 
std::int16_t greedy_group_start {-1}
 For a class-loop wrapped in one capturing group ((\w+), ([a-z]+)): the group's start slot to mirror the whole-match start into (-1 = no enveloping group).
 
std::int16_t greedy_group_end {-1}
 The enveloping group's end slot (mirrors the whole-match end).
 
bool fixed_shape {}
 Whole pattern is a fixed-width byte/klass sequence (no branches/asserts/captures).
 
std::int32_t codepoint_class_ascii {-1}
 ASCII-class index when the whole pattern is ./negated-class (optionally +), else -1.
 
bool codepoint_class_plus {}
 The codepoint_class_ascii pattern is a greedy + loop (vs a single codepoint).
 
bool fixed_alternation {}
 Whole pattern is an alternation of straight-line branches (no captures/asserts).
 
std::uint8_t exact_literal_len {}
 Length of the pure-literal match, or 0.
 
std::array< char, 6 > stop_set {}
 For a whole-pattern class+ run whose accepted set has a small (<= 6-byte) complement: the STOP bytes (the complement), driving the memchr-cascade run scan. Empty unless stop_set_size is set. Placed last so it never shifts the hot fields' offsets.
 
std::uint8_t stop_set_size {}
 Members in stop_set — 0 when the complement is too large, else 1..6.
 
std::array< char, 8 > small_set {}
 The 2..8 possible first bytes, enumerated (the memchr-cascade set for alternation search mode: the L-SIMD masked block scan) in place of the bitmap loop. Empty unless small_set_size is set. Placed here (cold, alongside stop_set — the same memchr-cascade family) rather than near first_bytes, its earlier position: that spot was ahead of the greedy_class_loop* fields the class-loop fast path reads every call, and raising this array's cap from four to eight moved those hot fields across a cache line – a pure layout effect, not a logic change, but measurable. This array is written once at compile and read only by the alternation route, so it costs nothing to keep it out of the hot prefix.
 
std::uint8_t small_set_size {}
 Members in small_set — 0 when not a small set, else 2..8.
 
std::int16_t rare_byte {-1}
 A required literal byte at a fixed offset from the match start that is statically rarer than the pattern's first-byte set (the date - at offset 4). The search jumps by memchr-ing this one byte (SIMD) and back-verifies from found - rare_offset, instead of the per-byte first-byte bitmap loop on a common class. -1 when no such byte was found.
 
std::uint8_t rare_offset {}
 The fixed byte offset of rare_byte from the match start.
 
std::int16_t rare_disc {-1}
 Rare discriminant with optional mono-byte before it (URL https?://…). Search: memchr(rare_disc) (SIMD single-byte) → back-verify [prefix][opt?][disc][after] → return match start. Prefer this over a weak literal prefix (http) when the disc is rarer than the first-byte filter. Unlike rare_byte, the disc offset from match start is not fixed when rare_disc_opt is set (s? → colon at 4 or 5). -1 = unarmed.
 
std::array< char, 8 > rare_disc_prefix {}
 Required bytes before the optional (e.g. http).
 
std::uint8_t rare_disc_prefix_len {}
 Length of rare_disc_prefix.
 
std::int16_t rare_disc_opt {-1}
 Optional mono-byte before the disc (s), or -1.
 
std::array< char, 4 > rare_disc_after {}
 Fixed bytes after the disc (e.g. //).
 
std::uint8_t rare_disc_after_len {}
 Length of rare_disc_after.
 
std::array< std::uint8_t, 16 > inner_literal {}
 A required inner literal every match must contain (the memmem candidate the inner-literal prefilter scans for), and how many top-level children precede it — the prefix the prefilter reverse-matches from a candidate back to the match start. inner_literal_len == 0 = none; inner_literal_prefix == 0 = the literal is at the head (reverse is the identity), -1 = it is nested with no clean prefix boundary. Filled at compile from the AST (raw bytes, so pattern_hints — a core type — need not know the frontend literal type). pike_vm::run dispatches on these through its inner-literal route.
 
std::uint8_t inner_literal_len {}
 Bytes held in inner_literal; 0 means the pattern has no required inner literal.
 
std::int32_t inner_literal_prefix {-1}
 Top-level children before the literal; 0 = at the head, -1 = nested with no clean boundary.
 
bool capture_free_walk {false}
 Every save in this program writes slot 0 or slot 1, so a thread's whole capture state is the group-0 START and nothing else.
 
std::uint8_t fixed_shape_lo0 {}
 For a fixed_shape run that is also HOMOGENEOUS – every position accepts the identical byte set, itself expressible as <= 2 contiguous ranges ([0-9a-f]{8}, \d{4}) – the shared range bounds and run length, driving the SIMD scan+verify (SSE2/NEON) in run_fixed_shape. fixed_shape_simd_len is 0 when not eligible (mixed-class shapes, a run > 16 bytes, or a class needing > 2 ranges stay scalar).
 
std::uint8_t fixed_shape_hi0 {}
 First range's high byte.
 
std::uint8_t fixed_shape_lo1 {1}
 lo1 > hi1 (default 1 > 0) encodes "no second range".
 
std::uint8_t fixed_shape_hi1 {}
 Second range's high byte, when one is present.
 
std::uint8_t fixed_shape_simd_len {}
 The run length (1..16) when eligible, else 0.
 
std::uint8_t fs_end_anchor {}
 A fixed_shape whose trailing end anchor was peeled — 0 none, 1 \Z (strict end), 2 $ (end, or just before ONE final newline: Python's semantics, and why ^X$ is NOT fullmatch(X)).
 
std::uint8_t reserved_layout_hold [1] {}
 The remaining held byte; see fs_end_anchor.
 
std::int16_t trailing_lookaround {-1}
 Trailing lookaround on a groupless greedy class+ body ([a-z]+(?=[a-z]), [0-9]+(?![0-9]), …). Index into lookarounds; -1 = not this shape. trailing_la_class holds the body's class index. Intentionally does not set greedy_class_loop (left −1) so the pure class+ call site stays a single compare — sharing that selector would make every [a-z]+ search branch on this shape too, which was measured to cost more than the route saves.
 
std::int32_t trailing_la_class {-1}
 Class index for trailing_lookaround body; −1 if unset.
 
std::array< char, 8 > possessive_prefix {}
 possessive fast-path hints – additive, mirrors greedy_class_loop / greedy_cp_class / prefix. Scope: UNBOUNDED possessive loops only (X*+/X++, an opcode-level self-loop via jump back to itself) – a bounded count (X{n,m}+) has no "any start within the run reaches the identical body end" invariant (the upper bound can cut different start positions at genuinely different lengths – see the per-attempt-independence divergence pinned in test_static.cpp's (a){2,4}+b), so a linear-time skip-based search cannot be built for it here; it stays on the general VM. At most one leading mandatory copy (min in {0,1}); min >= 2 also stays on the general VM. A non-empty possessive_prefix additionally requires (checked at recognition time, prefilter.hpp) that the loop's class excludes the prefix's AND suffix's leading byte – the invariant that makes the delimited/"quoted" runner's skip-to-body-end retry provably linear rather than quadratic on adversarial input (id=[a-z0-9]*+; fails this and stays general – alphanumeric prefix bytes are members of the loop's own class).
 
std::uint8_t possessive_prefix_size {}
 Meaningful bytes of possessive_prefix; 0 selects the bare/suffixed shape, non-zero the delimited one.
 
std::array< char, 8 > possessive_suffix {}
 Required literal AFTER the loop (0 len = none; e.g. the 'x' in \d++x).
 
std::uint8_t possessive_suffix_size {}
 Meaningful bytes of possessive_suffix; 0 means the loop has no required suffix.
 
class_ref possessive_class {}
 The loop body's class, typed by opcode.
 
std::int16_t possessive_group_start {-1}
 Enveloping single capture group's start slot (mirrors greedy_group_start), -1 = none.
 
std::int16_t possessive_group_end {-1}
 The enveloping group's end slot.
 
bool possessive_min_nonzero {}
 True for X++/X{1,}+ (one mandatory copy present) — a search candidate MUST be in-class; false for X*+ (min 0), where a zero-length body is also a valid candidate anywhere.
 
std::uint8_t wb_lead {}
 Optional leading/trailing word-boundary wrap on fixed_shape / fixed_alternation / exact_literal. 0 = none; 1 = \b (assert_kind::word_boundary); 2 = \B (assert_kind::not_word_boundary). Verified in O(1) at the match start/end after the body fast-path accepts a candidate.
 
std::uint8_t wb_trail {}
 
bool wb_lead_maximal_run {}
 True when a genuine leading \b was dropped by the DROP rule (a maximal greedy/possessive run can only legitimately START where the preceding character is non-word, so the runtime check is redundant – resolve_class_wb_hints). That argument silently assumes "preceding character absent" means the TRUE start of the text – sound for mode::full/mode::prefix and for search()'s own internal position-0 seed, but NOT for a caller-supplied search(text, pos, ...) with pos > 0: pos restricts where a match may START, it does not assert that text[pos - 1] is absent or non-word (Python's own re.search(pat, text, pos) does not treat pos as a virtual string start for \b purposes – verified against the live oracle, and confirmed asymmetric with endpos, which DOES act as a virtual end for a trailing \b, so only the lead side needs this guard). When true, a fast-path runner whose very first search-mode candidate coincides with start itself (no forward scan past a genuine non-word byte occurred) must fall back to an explicit assertion_holds check at that one position before accepting it — found live by differential fuzzing (\b\w+ search'd from pos>0 wrongly matched with no boundary at pos).
 
std::uint8_t body_pc {1}
 First consuming (byte/klass) or branch pc for fixed_shape / alternation after save 0 and an optional lead \b/\B. Default 1 (no lead wrap).
 
std::uint16_t alternation_branch_count {}
 Branch count for fixed_alternation (0 when unset). Read only by the alternation route to pick a runtime STRATEGY past a literal-count threshold (Aho-Corasick) – small_set is untouched either way (still capped 2..8, still the sub-threshold scan). Placed LAST (own reason small_set / stop_set are also placed away from the hot prefix): inserting anywhere earlier reflows every field after it, including wb_lead / body_pc themselves, which fixed_shape / alternation / exact_literal all read on every match. An insertion right after small_set_size does worse than shift them: it grows the whole struct through an alignment cascade off inner_literal_prefix. Appending after the last field leaves sizeof and every existing offset untouched.
 
std::uint8_t inner_literal_prefix_skip {}
 Number of leading top-level children peeled before the IL reverse-prefix (a lead \b/\B on \b\w+@\w+\b). build_prefix_ast skips this many children then takes inner_literal_prefix body children. Appended last (same placement rule as alternation_branch_count) so hot-field offsets stay put.
 
bool literal_one_search {}
 One find_prefix answers the whole exact-literal search: every per-match step the general run_exact_literal loop takes is provably redundant for this program.
 
std::uint8_t fs_pair_width {}
 HETEROGENEOUS fixed-shape pair filter: two positions, each with its own <= 2-range accepted set, used to reject 16 candidate starts per vector compare.
 
std::uint8_t fs_pair_off_a {}
 Offset of the first probed position within the shape.
 
std::uint8_t fs_pair_off_b {}
 Offset of the second probed position.
 
std::uint8_t fs_pair_a_lo0 {}
 Position A, first range's low byte.
 
std::uint8_t fs_pair_a_hi0 {}
 Position A, first range's high byte.
 
std::uint8_t fs_pair_a_lo1 {1}
 Position A, second range's low byte; lo1 > hi1 encodes "no second range".
 
std::uint8_t fs_pair_a_hi1 {}
 Position A, second range's high byte.
 
std::uint8_t fs_pair_b_lo0 {}
 Position B, first range's low byte.
 
std::uint8_t fs_pair_b_hi0 {}
 Position B, first range's high byte.
 
std::uint8_t fs_pair_b_lo1 {1}
 Position B, second range's low byte; same "no second range" convention.
 
std::uint8_t fs_pair_b_hi1 {}
 Position B, second range's high byte.
 
std::int32_t il_rev_class {-1}
 IL reverse-by-class: set when the inner-literal PREFIX is exactly one greedy class loop ([a-z]+@…, \w+-…, \d+\.…), so the match start for a candidate literal at h is the start of the class run ending at h — a backward scan, no automaton.
 
bool il_rev_is_cp {}
 il_rev_class indexes cp_classes (a klass_cp loop) rather than classes.
 
std::int32_t il_fwd_class {-1}
 IL two-run confirm: set when the WHOLE pattern is class+ <literal> class+ (capture groups around either run are transparent), so a confirmed candidate needs no match engine at all.
 
bool il_fwd_is_cp {}
 il_fwd_class indexes cp_classes rather than classes.
 
bool il_cp_shape_eligible {}
 IL fixed code-point shape: the whole pattern is a fixed SEQUENCE of code-point atoms and literal bytes — \d{4}-\d{2}-\d{2} and its kin — with no loop anywhere.
 
std::uint8_t il_cp_prefix_cps {}
 Code points before the literal in il_cp_shape_eligible.
 
std::uint16_t greedy_cp_class_max {}
 Upper bound, in CODE POINTS, on greedy_cp_class's run, or 0 for unbounded.
 
std::int32_t single_class {-1}
 Class index when the WHOLE pattern is a bare single byte-class – [a-z], [aeiou], [0-9] with no quantifier at all – else -1.
 

Detailed Description

Search-acceleration hints extracted from a compiled program.

Filled by analyze_program (prefilter.hpp). The engine consults them to skip positions that cannot start a match and to take fast paths; they never change what matches, only how fast.

Note
The field order here is a hot prefix followed by per-route data, and it is load-bearing. Offsets 0..86 hold what the dispatch and the common routes read on every search — the required prefix, first_bytes, the greedy_* selectors, fixed_shape, fixed_alternation. The remaining ~145 bytes are per-route: inner_literal, the fixed_shape_* widths, possessive_prefix, the rare_* discriminants. A given pattern takes one route, so that tail is cold for it while being hot for whichever route it does take — which is why the tail is ordered away from the prefix rather than moved behind an indirection.

Two consequences, both easy to undo by accident. Add new fields at the END: an insertion higher up reflows everything after it, and both small_set's growth and alternation_branch_count's placement record a case where doing otherwise moved hot fields across a cache line — a pure layout effect, no logic change, and measurable on patterns that read none of the new field. And do not "split this into hot and cold structs" as a fresh idea: that split is what this order already is. Removing the tail from program_view would shrink the view at the price of an indirection on every route that reads it, where materialising the dynamic view removes the whole per-search construction and costs none.

Member Data Documentation

◆ capture_free_walk

bool real::detail::pattern_hints::capture_free_walk {false}

Every save in this program writes slot 0 or slot 1, so a thread's whole capture state is the group-0 START and nothing else.

STRUCTURAL, not empirical: compile() emits save 0 at pc 0 and save 1 immediately before match, and emits no other save unless the pattern has a capture GROUP. So "no save past slot 1" is exactly "no groups" – but it is derived here by SCANNING the code rather than from group_count, so it holds for any producer of a program, including one added later.

What it licenses: the epsilon walk need not carry a refcounted capture block per branch. save 0 sits at pc 0, so every thread one add_thread call adds shares one start – either the one passed in, or pos if the walk crossed the head – and both are single std::size_t values in the call frame. That is why this must be a GUARD and not an assumption: a program whose save 0 sat behind a split would let one branch inherit its sibling's start, which is a wrong ANSWER rather than a slow one (tests/frontend/test_index_and_range_limits.cpp).

◆ exact_literal_len

std::uint8_t real::detail::pattern_hints::exact_literal_len {}

Length of the pure-literal match, or 0.

Non-zero when the whole pattern is a fixed literal (the prefix bytes are the entire match content, possibly with internal group saves but no branches or further consuming ops). Enables a direct slot-replay bypass of the full Pike VM — the major win for "search for a fixed string".

◆ fixed_shape_lo0

std::uint8_t real::detail::pattern_hints::fixed_shape_lo0 {}

For a fixed_shape run that is also HOMOGENEOUS – every position accepts the identical byte set, itself expressible as <= 2 contiguous ranges ([0-9a-f]{8}, \d{4}) – the shared range bounds and run length, driving the SIMD scan+verify (SSE2/NEON) in run_fixed_shape. fixed_shape_simd_len is 0 when not eligible (mixed-class shapes, a run > 16 bytes, or a class needing > 2 ranges stay scalar).

First range's low byte.

◆ fs_end_anchor

std::uint8_t real::detail::pattern_hints::fs_end_anchor {}

A fixed_shape whose trailing end anchor was peeled — 0 none, 1 \Z (strict end), 2 $ (end, or just before ONE final newline: Python's semantics, and why ^X$ is NOT fullmatch(X)).

Only ever set together with anchored_start, and that pairing is the whole reason the end test is cheap: with the start pinned there is exactly ONE candidate position, so a failed end test owes no retry. A trailing $ WITHOUT ^ stays on the general VM.

This byte and the one below it are the pair held where retired fields sat, so that removing dead code did not reflow every field after it — see this struct's layout note. One of the two is now spent on something real; reclaiming the other is a measurement, not a cleanup.

◆ fs_pair_width

std::uint8_t real::detail::pattern_hints::fs_pair_width {}

HETEROGENEOUS fixed-shape pair filter: two positions, each with its own <= 2-range accepted set, used to reject 16 candidate starts per vector compare.

fixed_shape_simd_len only fires when EVERY position accepts the identical set (a homogeneous shape like [0-9a-f]{8}), because there the "all L lanes in range" test is the verify. A shape whose positions differ — (?i)cafe (position 0 accepts {c,C}, position 3 {e,E}), [0-9]{2}:[0-9]{2}, \w\d\w\d — got no vector help at all and scanned one byte at a time. This pair filters instead of verifying: the two chosen positions must both match, then the ordinary per-position walk confirms the candidate.

The pair is the two most selective positions (smallest accepted-set cardinality, ties broken toward the widest separation, so the two probes decorrelate). fs_pair_width is the shape's byte width, 0 when ineligible — set only when fixed_shape_simd_len is 0 (the homogeneous path already has its own, better, fused scan), the width is 2..16, and every position resolved to 1..2 ranges. lo1 > hi1 encodes "no second range", the same convention as fixed_shape_lo1. Appended last (same placement rule as alternation_branch_count). The shape's byte width (2..16); 0 when the pair filter is ineligible.

◆ greedy_class_loop

std::int32_t real::detail::pattern_hints::greedy_class_loop {-1}

Class index if the whole pattern is "class+", else -1.

◆ greedy_cp_class_max

std::uint16_t real::detail::pattern_hints::greedy_cp_class_max {}

Upper bound, in CODE POINTS, on greedy_cp_class's run, or 0 for unbounded.

\w+ and \w{8,} are unbounded and leave this 0; \w{8} sets it to 8, which is what lets the route accept a counted repeat at all. Without it the recognizer had to decline X{k} — the route knew how to extend greedily and bound the result from BELOW, and an exact count needs the run stopped from above, since \w{8} over a nine-letter word matches the first eight and not the nine.

APPENDED LAST, and that placement is the change rather than a detail: this struct's hot fields are read per call on the class-loop path, and inserting a field mid-struct has twice cost this project double-digit regressions on patterns that read none of it (see alternation_branch_count and the six IL fields above).

◆ greedy_cp_class_plus

bool real::detail::pattern_hints::greedy_cp_class_plus {}

The greedy_cp_class pattern is a greedy + loop (vs a single code point).

◆ il_cp_shape_eligible

bool real::detail::pattern_hints::il_cp_shape_eligible {}

IL fixed code-point shape: the whole pattern is a fixed SEQUENCE of code-point atoms and literal bytes — \d{4}-\d{2}-\d{2} and its kin — with no loop anywhere.

fixed_shape and its own route already cover the case where that sequence is fixed-width in BYTES, which a klass_cp never is (a Unicode \d matches multi-byte digits). But the code-point COUNT is fixed, so the match start is still arithmetic: step il_cp_prefix_cps code points back from the candidate literal, then one forward walk verifies every atom and fills every capture. No loop means no reverse walk to bound and no engine to run.

◆ il_fwd_class

std::int32_t real::detail::pattern_hints::il_fwd_class {-1}

IL two-run confirm: set when the WHOLE pattern is class+ <literal> class+ (capture groups around either run are transparent), so a confirmed candidate needs no match engine at all.

With il_rev_class placing the start by walking the prefix class back from the literal, the rest of the match is the suffix class run forward from the literal's end — and every capture slot is one of four positions (s, the literal's start, the literal's end, e). What this buys is aimed at a storage with NO per-regex cache: a dynamic regex confirms a candidate through its lazy DFA and one-pass table, while static_regex has neither and falls back to the general VM – most of its work then being thread management and the copy-on-write capture pool, on a pattern that may have no capture groups at all. This shape confirms without an engine, so that cost disappears rather than being reduced.

-1 when the suffix is not a single greedy class loop reaching the end of the pattern.

◆ il_rev_class

std::int32_t real::detail::pattern_hints::il_rev_class {-1}

IL reverse-by-class: set when the inner-literal PREFIX is exactly one greedy class loop ([a-z]+@…, \w+-…, \d+\.…), so the match start for a candidate literal at h is the start of the class run ending at h — a backward scan, no automaton.

This is the middle case between a fixed_shape program (fixed-width throughout, so the start is arithmetic) and the general reverse pass (a reverse DFA over the prefix sub-program, which lives in the per-regex immutables). It needs neither: no sub-program, no DFA, no allocation, so a storage with no immutables — static_regex — can run it, which is the point. Leftmost semantics hold because the run's beginning IS the leftmost start for that candidate, and confirm_at verifies forward regardless, so a rejected candidate costs an advance and never a wrong match.

-1 when the prefix is not that shape (a fixed repeat count has no split, so \d{4}-… is excluded here and stays on the general path).

◆ inner_literal

std::array<std::uint8_t, 16> real::detail::pattern_hints::inner_literal {}

A required inner literal every match must contain (the memmem candidate the inner-literal prefilter scans for), and how many top-level children precede it — the prefix the prefilter reverse-matches from a candidate back to the match start. inner_literal_len == 0 = none; inner_literal_prefix == 0 = the literal is at the head (reverse is the identity), -1 = it is nested with no clean prefix boundary. Filled at compile from the AST (raw bytes, so pattern_hints — a core type — need not know the frontend literal type). pike_vm::run dispatches on these through its inner-literal route.

The literal's bytes, the first inner_literal_len of which are meaningful.

◆ literal_one_search

bool real::detail::pattern_hints::literal_one_search {}

One find_prefix answers the whole exact-literal search: every per-match step the general run_exact_literal loop takes is provably redundant for this program.

Set when ALL of: exact_literal_len >= 2 (a 1-byte literal goes through find_byte, not find_prefix); prefix_size == exact_literal_len (so find_prefix matches the whole literal, making literal_at's re-compare redundant); the program carries no assert_position (an exact-literal program legally may — \bdog, ^dog, dog\b, see extract_prefix — and each occurrence must then be checked at its own position, which is exactly the retry the general loop exists for); and neither anchored_start, line_anchored nor rare_disc is armed (each takes an earlier branch in next_candidate, so the candidate would not come from find_prefix).

Folded into one bit deliberately: every term is a property of the compiled program, never of the subject, so evaluating them per match charges the shapes that FAIL the guard for a decision that cannot change between matches. The caller adds only slot_count == 2, which lives on the program rather than here. Assertion-freeness is computed directly from code and deliberately not inferred from wb_lead / anchored_start / line_anchored — an assert kind those hints do not represent would make that inference silently unsound. Appended last (same placement rule as alternation_branch_count).

◆ possessive_prefix

std::array<char, 8> real::detail::pattern_hints::possessive_prefix {}

possessive fast-path hints – additive, mirrors greedy_class_loop / greedy_cp_class / prefix. Scope: UNBOUNDED possessive loops only (X*+/X++, an opcode-level self-loop via jump back to itself) – a bounded count (X{n,m}+) has no "any start within the run reaches the identical body end" invariant (the upper bound can cut different start positions at genuinely different lengths – see the per-attempt-independence divergence pinned in test_static.cpp's (a){2,4}+b), so a linear-time skip-based search cannot be built for it here; it stays on the general VM. At most one leading mandatory copy (min in {0,1}); min >= 2 also stays on the general VM. A non-empty possessive_prefix additionally requires (checked at recognition time, prefilter.hpp) that the loop's class excludes the prefix's AND suffix's leading byte – the invariant that makes the delimited/"quoted" runner's skip-to-body-end retry provably linear rather than quadratic on adversarial input (id=[a-z0-9]*+; fails this and stays general – alphanumeric prefix bytes are members of the loop's own class).

Required literal BEFORE the loop (0 len = none; the delimited/"quoted" shape).

◆ rare_disc

std::int16_t real::detail::pattern_hints::rare_disc {-1}

Rare discriminant with optional mono-byte before it (URL https?://…). Search: memchr(rare_disc) (SIMD single-byte) → back-verify [prefix][opt?][disc][after] → return match start. Prefer this over a weak literal prefix (http) when the disc is rarer than the first-byte filter. Unlike rare_byte, the disc offset from match start is not fixed when rare_disc_opt is set (s? → colon at 4 or 5). -1 = unarmed.

Discriminant byte (e.g. :).

◆ single_class

std::int32_t real::detail::pattern_hints::single_class {-1}

Class index when the WHOLE pattern is a bare single byte-class – [a-z], [aeiou], [0-9] with no quantifier at all – else -1.

greedy_class_loop describes class+ and carries no "single" flag, unlike its two neighbours (greedy_cp_class_plus, codepoint_class_plus). Without this field the unquantified form matches no batchable selector and crosses a full route entry PER match, where all three class routes cross one per BATCH – which made a single-byte class slower per byte than ., a pattern that matches at every position.

A SEPARATE field rather than a flag on greedy_class_loop, and that is not a stylistic choice: sharing that selector forces every pure class+ call site to branch on the other shape too, which was measured to cost more than it saves (see trailing_lookaround, which declines to arm it for the same reason).

Scope is the 4-opcode program exactly (save 0, klass, save 1, match): no capture wrap, no \b wrap, no anchor. A single literal BYTE (a) is excluded – it takes exact_literal, a different route with its own memchr scan.

APPENDED LAST, per this struct's placement rule.

◆ wb_lead

std::uint8_t real::detail::pattern_hints::wb_lead {}

Optional leading/trailing word-boundary wrap on fixed_shape / fixed_alternation / exact_literal. 0 = none; 1 = \b (assert_kind::word_boundary); 2 = \B (assert_kind::not_word_boundary). Verified in O(1) at the match start/end after the body fast-path accepts a candidate.

Leading wrap: 0 none, 1 \b, 2 \B — asserted at the match start.

◆ wb_trail

std::uint8_t real::detail::pattern_hints::wb_trail {}

Trailing wrap, same encoding as wb_lead — asserted at the match end.


The documentation for this struct was generated from the following file: