Skip to content

perf: streamline MF2 declaration execution - #26

Merged
waywardmonkeys merged 18 commits into
mainfrom
perf/mf2-execution-storage
Sep 16, 2026
Merged

waywardmonkeys merged 18 commits into
mainfrom
perf/mf2-execution-storage

Conversation

@waywardmonkeys

@waywardmonkeys waywardmonkeys commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Make MF2 declaration execution explicit, stored once, and reusable without replaying host calls or rediscovering source intent at runtime.

  • Store resolved execution values in formatter-owned reusable storage; VM stack/local operands carry compact indices.
  • Retain exact numeric payloads and parsed options, deferring plural/ordinal projection until keyword selection actually needs it.
  • Assign declaration slots during analysis and lower syntax directly to final semantic IR.
  • Preserve eager source-order diagnostics, alias fallback provenance, and at-most-once host evaluation.
  • Preserve formatted values' semantic sources across numeric reannotation and exact string selection.
  • Collect catalog strings/functions in one traversal.
  • Remove the superseded local_eval.rs and rewrite.rs compiler paths.

The compiler plan owns declaration evaluation order, storage, and format/select use. The VM executes that plan and does not reconstruct compiler intent.

Review fixes

Regression coverage includes:

  • fused :string to numeric declarations through literals, aliases, and annotated inputs;
  • exact host-call counts and ordinary-body reference counting;
  • annotated and unannotated multi-hop alias selectors;
  • formatted number/percent/currency reannotation beyond the exact-f64 range;
  • formatted values under explicit :string selection;
  • malformed reversed pattern delimiters and short-input panic fuzzing;
  • compile-only, runtime-only, ICU, and no-std feature combinations.

Two independent Astra reviews found no remaining Must or Should issues after the fixes. The final review also exercised 55,991 short malformed inputs without a panic.

Validation

  • cargo fmt --all
  • taplo fmt
  • typos
  • cargo test --workspace --all-features — 479 core tests, 219 compiler tests, workspace green, 11 doctests
  • cargo test -p message-format --no-default-features --features compile — 372 tests, 11 doctests
  • cargo clippy --workspace --all-targets --all-features -- -D warnings
  • runtime-only and compile-only no-default-feature Clippy with warnings denied
  • cargo check -p message-format --no-default-features --features compile,icu4x
  • cargo doc --workspace --all-features --no-deps

WG scoreboard: 422/462. Of the remaining 40, 21 are unsupported harness expectations/inputs and 19 are runtime or parser-diagnostic mismatches.

The combined replacement is one atomic commit because the new storage opcodes and direct declaration lowering are one behavioral invariant; an attempted backend/frontend split compiled but failed four existing tests at the intermediate revision.

Performance

The original stored-value work materially reduced number formatting and repeated declaration costs. Subsequent declaration-heavy compiler measurements improved approximately 3% at 128 locals and were flat at smaller sizes. Later runtime/raw-match comparisons were invalidated by changing machine load, so this PR makes no additional broad speedup claim beyond the established stored-value benchmarks.

Validate `OutSlice` and `OutExpr` offsets against the verified `LITS`
string before accepting bytecode. Validating the whole chunk alone does
not make arbitrary byte slices valid UTF-8.

Keep `Catalog::literal` within the `LITS` chunk and use checked string
slicing for public offsets. Reuse the load-time UTF-8 invariant so reads
remain constant-time.

Cover split code points, empty interior slices, missing chunks, and
out-of-chunk reads without executing malformed strings.
Run each fixture once and check expected output and diagnostic multisets
independently. Preserve absent versus empty `expErrors`, report nested
selector errors, and stop accepting unrelated error aliases.

Preserve null parameters and explicitly fail unsupported parameter types
and `expParts` expectations. Report initialization failures as failures
instead of disguising them as expected spec errors.

Find the sibling WG corpus from worktrees and make `wg_scoreboard` exit
unsuccessfully when fixtures fail. Add deterministic harness regressions.

The pinned WG corpus reports 404/462 passing; remaining failures include
unsupported expectations.
Inherit `expParts` and `params` from `defaultTestProperties`, preserving
explicitly empty per-case parameters. Reject unsupported structured
values even when the parameter name is unused by the message.

This prevents default expectations and unused unsupported inputs from
silently passing. The WG baseline remains 404/462 passing.
@waywardmonkeys
waywardmonkeys force-pushed the fix/mf2-option-recovery branch from 68f2aa8 to 4a57702 Compare September 9, 2026 03:35
@waywardmonkeys
waywardmonkeys force-pushed the perf/mf2-execution-storage branch from e834943 to 296ced1 Compare September 9, 2026 03:35
@waywardmonkeys waywardmonkeys changed the title perf: reuse resolved values across message execution perf: reuse resolved values and defer numeric selection Sep 9, 2026
@waywardmonkeys
waywardmonkeys force-pushed the perf/mf2-execution-storage branch 2 times, most recently from 99837dd to 28bffdc Compare September 15, 2026 10:58
Keep numeric payloads and effective options until output formatting so
`:number`, `:integer`, and `:offset` can inherit and override options
without parsing formatted text. Preserve exact integers and separate
numeric selection from display; use checked arithmetic for offsets.

Evaluate declarations once into per-format local slots. Retain failed
resolution metadata so selection reports `BadSelector` without repeating
the original diagnostic. Let `Host` callbacks report recoverable errors
while returning a usable value, and document the API migration.

Cover option inheritance, precision, exact and plural selection, dynamic
`select` errors, declaration evaluation counts, and local-slot validation.
Update conformance assertions to require exact diagnostic multisets.

The WG corpus passes 416 of 462 cases, up from 404 with the corrected
harness.
Declaration AST spans already contain document offsets. Avoid adding the
pattern body offset again when lowering declarations, so manifest errors
point at the actual function annotation.
Short-circuit function resolution when a stored operand is a fallback,
without repeating the original error or producing `BadOperand`.

Give each failed local its own fallback name when storing declarations,
so aliases render `{$a}` and `{$b}` independently. Preserve failure state
through `ExprFallback` and `StoreLocal`, then clear it before unrelated
expressions.
When a later selector rejects an exact numeric variant, try matching
plural or ordinal variants before falling back to wildcard candidates.
Reuse stored numeric values for both dispatch passes so declarations
still resolve once.

Apply the same dispatch to stored `:offset` results, and default raw
offset operands to plural selection while preserving inherited modes.
Cover variant ordering, exact precedence, and multi-selector offsets.
Keep inherited number formatting settings in their parsed form, with explicit
selection provenance, instead of cloning and reparsing an owned string map.
Compute plural operands directly from retained integers and decimals,
preserving visible precision and half-expand rounding.

Resolve integral floats within the exact integer range without allocating
text. Preserve negative zero and the existing representation of larger
floating-point inputs.
Validate each source selector before comparing variants. Keep generated
exact and plural dispatches free of repeated diagnostics, and compare
locals by private slot index instead of cloning their resolved values.
Verify both new local-selector instructions before execution.

Emit repeated default subtrees once and jump to their shared continuation,
closing intervening select scopes. Preserve exact-to-plural retry when a
later selector rejects an exact variant. Keep function metadata available
for manifest validation and document the catalog migration.
Resolve string declarations once, retaining raw text and direction metadata
until output. Later annotations consume the raw string instead of bidi
isolation characters. Compare resolved strings without numeric coercion
and let custom hosts observe string input resolution.

Carry failures as `Value::Fallback` through nested calls and local aliases.
Remove instruction-dependent failure flags so nested calls skip failed
operands even when option loads intervene. Preserve each alias fallback
name and keep independent expression diagnostics separate.

Document eager declaration evaluation and the resolved-string migration.
The WG corpus remains at 415/462 passing.
Measure repeated numeric locals, string-to-number reannotation, and a
multi-selector match that rejects an exact variant before using a plural
candidate. Reuse formatter state and resolved handles so setup is outside
the measured operation.
Enable `libm` for runtime-only ICU builds while retaining it for compiler-only builds. Keep compiler-only helpers out of runtime builds and satisfy Clippy across the no-std feature matrix.
Use ICU4X `2.3.0`, `icu_locale` `2.3.1`, and `fixed_decimal` `0.7.2`.
Refresh the corresponding lockfile graph, including the provider patch
and the upstream locale-fallback split.

Preserve optional dependencies and compiled-data feature settings. No
source adaptations or new formatting features are needed. ICU4X's Rust
`1.88` requirement matches the workspace minimum.

WG results remain unchanged at 415/462 passing.
Omit fallback-valued options from function calls while retaining whether
their keys came from variables. Expose that information through borrowed
`FunctionOptions`, so custom hosts and built-ins share the same contract
without copying an option map.

Load input option variables independently of operands. Report missing
variables and omitted options when option resolution runs; discard those
diagnostics when operand failure short-circuits the expression. Preserve
variable provenance when substituting local literals.

Keep dynamic `select` unselectable even when its value is omitted, while
allowing the resolved number to format and inherit unaffected options.
Document the callback migration and catalog recompilation requirement.

The WG corpus remains at 415/462 passing.
Store compact digit counts and box only the large ICU decimal payload. Integral resolved numbers no longer allocate, while `Value` remains 32 bytes.

`Value::Number` now contains `ResolvedNumber` directly; callers matching the variant should remove `Box` assumptions.
Keep execution-scoped values in reusable formatter storage and address them by index from the operand stack and local slots. Local loads no longer clone resolved values, and unary host calls borrow their operands.

Clear values and scratch vectors after both successful formatting and traps while retaining their capacity for the next execution.
Resolve plural and ordinal categories through `CallSelect` only when keyword matching reaches that pass. Stored numbers retain their exact payload and selection mode without eagerly computing a category during ordinary formatting.

Lower numeric local keyword matching through the stored value, and remove the VM category side channel that is no longer needed.
@waywardmonkeys
waywardmonkeys force-pushed the perf/mf2-execution-storage branch from 28bffdc to dccece5 Compare September 15, 2026 12:39
@waywardmonkeys
waywardmonkeys changed the base branch from fix/mf2-option-recovery to main September 15, 2026 12:46
@waywardmonkeys
waywardmonkeys force-pushed the perf/mf2-execution-storage branch from dccece5 to 154050e Compare September 16, 2026 02:49
@waywardmonkeys waywardmonkeys changed the title perf: reuse resolved values and defer numeric selection perf: streamline MF2 declaration execution Sep 16, 2026
@waywardmonkeys
waywardmonkeys merged commit b20f7a9 into main Sep 16, 2026
15 checks passed
@waywardmonkeys
waywardmonkeys deleted the perf/mf2-execution-storage branch September 16, 2026 03:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant