Skip to content

Precompiled Scanner Patterns

Status

Accepted. Additive Rust API extension to ADR-001, following ADR-008.

Context

A ScannerPatternCache already shares compiled patterns within one process. Consumers also need to reuse that work across process starts and browser sessions, and publish compiled patterns alongside TextMate grammars. Decoding an entire grammar eagerly can cost more than compiling the few patterns a highlight needs. Issue #444 records the motivating Ferriki prototype and its first-use measurements.

Decision

The optional precompiled feature adds an indexed archive source to ScannerPatternCache. Opening an archive retains its bytes and checks its index. A cache miss decodes only the requested pattern, validates it, and rebuilds its process-local search accelerators. An absent, incompatible, or invalid entry falls back to another source and then the normal compiler. Resolved end/while backreferences need no special handling: a pattern absent from the archive compiles as before.

The explicit binary schema belongs to the scanner extension. It does not change C-ported compiler or VM control flow. Its format version is independent of the crate version. An incompatible bytecode layout or meaning, Unicode table revision, seek encoding, or optimizer semantic change requires a format version bump. Readers reject unsupported versions; they do not guess at a migration. Patch releases that preserve these contracts can share archives. The schema uses fixed integer widths and explicit variant tags, never Rust memory layout or pointers. Binary membership maps take 32 bytes; nonbinary optimizer skip maps use run-length encoding where smaller than raw bytes.

Store the prefilter's SeekCode in each program. Deriving it again would require the discarded tuned tree and repeat a material part of compilation. Lazy DFA and meta automata remain runtime work. Finders, Aho-Corasick plans, trie nodes, and bytecode-derived search plans are rebuilt from checked data; tree-derived literal-prefix and required-literal facts are retained. There is no serialized automaton or function pointer.

Keys include pattern text, syntax, compile options, rewrite mode, prefilter mode, and the producing process's case-fold, capture, nesting, AST-budget, and user-defined-property settings. Prefilter build capability also matches; a build without the DFA feature cannot supply a seek to one with it. Warmup scheduling is excluded because it does not change a compiled pattern. Callout programs are omitted: registered functions belong to their process. The existing requirement to clear a cache after changing global compile settings still applies.

The codec adds no dependencies (ADR-009). Serde over the full internal object graph would couple the format to implementation fields and include runtime objects that need rebuilding anyway. A separate explicit schema makes version changes reviewable and keeps ordinary builds unchanged.

Consequences

Archives are optional accelerators. The caller keeps original pattern text and can discard an incompatible archive without breaking grammar loading. Cache len counts decoded patterns; clearing it keeps attached archive sources. Removing sources releases their bytes once their last shared handle is dropped. Already-built scanners retain their compiled programs.

The loader bounds byte lengths, allocations, instruction counts and seek depth, and checks opcode/payload pairings, jump and table targets, captures, repeats, class ranges, fused lookbehind bodies, and trie/fold references. Checksums detect accidental corruption; validation still runs with a recomputed checksum. These checks do not establish that a producer's program implements its claimed pattern text. Consumers must obtain archives from a trusted producer, just as they obtain their grammar definitions. The format is not an authentication or sandbox boundary.

compat_prefilter_differential exercises archive round trips over the existing compat pairs; precompiled_scanner replays captured C++, SCSS and Java scanner calls. The loader fuzz target mutates both raw archives and valid archives with recomputed checksums. scanner_compile_bench compares fresh-cache compilation with archive indexing, per-pattern decoding, assembly, and teardown. These construction measurements exclude downloads and do not predict full-page highlighting latency.