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

The per-regex immutable cache the router shares across every find_iter on a regex: the byte program (klass_cp expanded to the deterministic trie) and, when the pattern is one-pass, the extractor table. More...

#include <onepass.hpp>

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

Public Member Functions

bool row_ready (std::size_t i) const noexcept
 Reads the "filled" flag for flag index i, acquiring what the filling thread released.
 
void set_row_ready (std::size_t i) noexcept
 Publishes the "filled" flag for flag index i.
 
 regex_immutables ()=default
 An empty cache: every identity key null, nothing built.
 
 regex_immutables (const regex_immutables &) noexcept
 Copies as an EMPTY cache: a copied regex is an independent regex.
 
 regex_immutables (regex_immutables &&) noexcept
 Moves as an empty cache, for the same reason as the copy constructor.
 
void invalidate_all () noexcept
 Clears EVERY identity key, so nothing built for the old program survives an assignment.
 
regex_immutablesoperator= (const regex_immutables &) noexcept
 Keeps this object's cache STORAGE but marks it invalid.
 
regex_immutablesoperator= (regex_immutables &&) noexcept
 Invalidates as the copy assignment does, and for the same reason.
 
constexpr ~regex_immutables ()
 Runtime erase of this regex's shared DFA slot (reclaims match-time caches). Constexpr paths skip the map entirely — is_constant_evaluated so dynamic_storage::compile / static_assert stay valid.
 

Public Attributes

byte_program byte_prog
 klass_cp-expanded byte program (empty until built).
 
lazy_byte_alphabet alphabet
 byte-class alphabet of byte_prog (shared by both DFAs, else recomputed per scan).
 
std::optional< onepassop_table
 one-pass extractor, present iff the pattern is one-pass.
 
byte_program il_prefix_prog
 IL: the inner-literal prefix's byte program (ineligible until built). Per-regex so the reverse DFA that spans it is a cheap shared wrapper, not a per-find_iter rebuild.
 
std::size_t il_min_haystack {}
 IL cold floor: first candidate-scan on this regex only fires at or above this size when the haystack HAS a match (0 = always). Warm scans use il_warm_floor (shared reverse DFA in shared_dfa_slot). Checked ONLY after the first memmem hit — no-match is never gated. Scaled by prefix byte-program size; see pike_vm::run_inner_literal.
 
std::vector< std::uint8_t > class_rows
 Byte-indexed membership rows, filled ON FIRST USE of each class and kept for the regex's life.
 
std::vector< std::uint8_t > cp_ascii_rows
 One 256-byte row per cp_class: its ASCII half.
 
std::vector< std::uint64_t > cp_page_rows
 One 30-word bitmap per cp_class: [U+0080, U+07FF].
 
std::atomic< std::uint64_t > row_ready_bits {0}
 "This row is filled" flags: one bit per row, three runs packed into one word, plus an overflow vector for the runs that do not fit.
 
std::vector< std::atomic< char > > row_ready_overflow
 Flags for row indices at or past row_ready_bit_capacity.
 
std::size_t cp_ascii_ready_at {0}
 Where the cp_ascii run starts in the flag index space.
 
std::size_t cp_page_ready_at {0}
 
std::atomic< const void * > rows_for {nullptr}
 prog.code.data() the rows above were SIZED for, or null. Same identity discipline as built_for, and independent of it: the rows are needed by scan routes that never build the DFA caches.
 
std::optional< ac_automatonac
 The multi-literal automaton for a fixed_alternation past the branch threshold, or empty when never built or declined (a pathological icase-fold expansion). Per REGEX, not per state: a state is fresh per search(), so holding it there rebuilds the whole automaton on every call — a fast path costing orders of magnitude more than the route it replaces.
 
std::atomic< const void * > ac_for {nullptr}
 prog.code.data() ac was built for, or null. Its OWN identity atomic, deliberately not folded into built_for — only the alternation route consults the automaton, and this cache's own history records what bundling a route-specific product into the shared flag cost every other route (see op_table_for).
 
std::atomic< const void * > op_table_for {nullptr}
 prog.code.data() op_table was built for, or null. Same identity discipline as rows_for and for the same reason: the extractor is needed only by the routes that fill captures through it, and it is by far the most expensive thing this cache holds — more than the byte program and the lazy DFA together. Bundling it into built_for makes every route that needs only the byte program pay for it, including a 2-slot pattern with no capture to extract at all.
 
std::atomic< const void * > built_for {nullptr}
 prog.code.data() this cache was built for, or null if never built / invalidated. Hot path: one atomic load. Not once_flag — assignment reuses this object under a new program; a spent once_flag would never rebuild (silent wrong matches).
 

Static Public Attributes

static constexpr std::size_t row_ready_bit_capacity {64}
 How many flag indices row_ready_bits covers; the rest live in row_ready_overflow.
 

Detailed Description

The per-regex immutable cache the router shares across every find_iter on a regex: the byte program (klass_cp expanded to the deterministic trie) and, when the pattern is one-pass, the extractor table.

Built under program-identity invalidation (built_for), so a const regex used from many threads builds race-free, and assigning onto a warmed regex rebuilds for the new program.

The mutable lazy-DFA transition caches live in a process-wide side table (shared_dfa_slot), keyed by this object's address and guarded by a per-slot mutex — so this struct stays free of std::mutex and of the members one would bring. Address reuse of the immutables object invalidates the slot via reset_shared_dfas; a program change at the same address is caught by built_for (see pike_vm::ensure_immutables). The destructor erases this address's map entry, so match-time caches never outlive the regex.

Constructor & Destructor Documentation

◆ regex_immutables()

real::detail::regex_immutables::regex_immutables ( const regex_immutables )
inlinenoexcept

Copies as an EMPTY cache: a copied regex is an independent regex.

The cache body is never transferred — it is a pure runtime accelerator and cheap to rebuild — so the copy starts with built_for and rows_for null and rebuilds on its own first routed search.

Member Function Documentation

◆ invalidate_all()

void real::detail::regex_immutables::invalidate_all ( )
inlinenoexcept

Clears EVERY identity key, so nothing built for the old program survives an assignment.

There are four keys and every one of them must be cleared, because the identity check they perform can PASS on a stale cache: copy-assigning the program's vector reuses its buffer, so code.data() is unchanged and a key left pointing at it still matches. A cache kept that way then serves the PREVIOUS pattern's product – for ac_for, the previous alternation's automaton, which answers matches on a subject the new pattern does not match and none on a subject it does. Silent wrong answers in both directions, not a crash.

That is why the invariant is "an assignment invalidates every cache" rather than "every cache with a known reproducer": one of the four has no reproducer today and is cleared all the same.

◆ operator=() [1/2]

regex_immutables & real::detail::regex_immutables::operator= ( const regex_immutables )
inlinenoexcept

Keeps this object's cache STORAGE but marks it invalid.

The destination's program is already the new one by the time storage assignment reaches here, so this cannot rebuild from the source's program and must not inherit its built state: clearing built_for forces pike_vm's ensure_immutables to rebuild. Self-assignment-safe.

Returns
*this.

◆ operator=() [2/2]

regex_immutables & real::detail::regex_immutables::operator= ( regex_immutables &&  )
inlinenoexcept

Invalidates as the copy assignment does, and for the same reason.

Returns
*this.

◆ row_ready()

bool real::detail::regex_immutables::row_ready ( std::size_t  i) const
inlinenoexcept

Reads the "filled" flag for flag index i, acquiring what the filling thread released.

Parameters
[in]iFlag index: a class row, or a cp_ascii / cp_page row at its own run's offset.
Returns
true once the row's contents are visible to this thread.

◆ set_row_ready()

void real::detail::regex_immutables::set_row_ready ( std::size_t  i)
inlinenoexcept

Publishes the "filled" flag for flag index i.

Called only by the thread holding immut_build_mu, so the read-modify-write on the bit word cannot race another writer.

Parameters
[in]iFlag index, in the same space row_ready reads.

Member Data Documentation

◆ class_rows

std::vector<std::uint8_t> real::detail::regex_immutables::class_rows

Byte-indexed membership rows, filled ON FIRST USE of each class and kept for the regex's life.

Deriving a 256-entry row into the VM state charges every short search, because search() builds a fresh state and the derivation lands in it. Filling every row at compile instead charges patterns that never touch most of their classes – a negated class interns a dozen and a short search reads one. Per class, on demand, cached per regex is the only arrangement that pays for neither.

THREAD SAFETY. rows_for identifies the program the rows were sized for, exactly as built_for does for the rest of this cache, so a copied or reassigned regex re-sizes rather than reading a stale table. A row's flag is release-stored after the row is filled under immut_build_mu and acquire-loaded before the row is read; only the lock holder that observed the flag clear ever writes a row, so a published row is immutable and readers race with no one. One 256-byte row per interned BYTE class.

◆ cp_page_ready_at

std::size_t real::detail::regex_immutables::cp_page_ready_at {0}

As cp_ascii_ready_at, for cp_page.

◆ row_ready_bits

std::atomic<std::uint64_t> real::detail::regex_immutables::row_ready_bits {0}

"This row is filled" flags: one bit per row, three runs packed into one word, plus an overflow vector for the runs that do not fit.

A fresh real::regex pays this whole block per construction, so the bit word matters more than it looks: as three separate std::vector<std::atomic<char>>, a pattern with no cp_class still allocates two one-element vectors, and first use pays for both. Bits cover the runs that fit in one word and allocate nothing; row_ready_overflow carries the rest. The check is not on a hot path — the VM state remembers its last verified row, so this is read on a miss, not per call.

A std::vector of atomics rather than a unique_ptr array: unique_ptr's destructor is not constexpr, and this struct must stay a literal type for real::regex to be usable in a constant expression. std::atomic_ref over a plain vector would say it more directly but is absent from this clang's libc++.


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