Bump workspace version 0.4.0 → 0.4.1.
Patch release: dispatch hot-path performance only. No public API
changes; every chart that compiled against 0.4.0 compiles unchanged
and behaves identically. CHANGELOG.md captures the per-pass writeup
under the 0.4.1 heading.
Headlines (deepest dispatch in the bench suite):
h8_cross_tree -82% instructions / -72% cycles vs pre_lca_fix
bubble_top -22% instructions / -6% cycles vs handler walk
terminate_event -22% instructions / -23% cycles vs PARENT walk
The runtime LCA helper, is_up_transition, the per-call heapless::Vec
ancestry build, the runtime PARENT bubble walk, and (on timer-free
charts) the entire TimerTable machinery are all gone. Callgrind
reports the entire HSM machinery as a single inlined `step` symbol.
Bench suite expanded from 7 to 13 benches: bubble_leaf/mid/top
quantify handler resolution at varying ancestor depths,
timer_fire_dispatch covers the only path that exercises pop_expired's
non-empty branch, terminate_event covers do_terminate, and
bubble_cross_tree covers the realistic HSM worst case (deep bubble +
deep cross-tree transition).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Six codegen-side optimization passes against step() and friends. No
user-facing API changes; all 296 tests pass under no-default, tokio,
embassy, and tokio+journal. Cumulative on h8_cross_tree (deepest
dispatch in the suite): -82% instructions / -72% cycles vs the
pre_lca_fix baseline.
* __PATH_DATA + __PATH_RANGE: per-(from, to) precomputed exit/enter
sequences. Up/self/lateral semantics fold into one indexed lookup;
removes lca() and is_up_transition() runtime helpers and the
per-call heapless::Vec<u16, 16> ancestry build. Hierarchy depth no
longer bounded by Vec capacity.
* __DISPATCH[(state, event)]: precomputed handler resolution. The
runtime __PARENT bubble walk + per-hop __handler_lookup call in
step() collapses to one indexed lookup. Bubble resolution is now
O(1) regardless of depth -- bubble_top (4-hop) is within 3
instructions of bubble_leaf (handler on current).
__handler_lookup retained for timer dispatch.
* __TERMINATE_DATA: per-state precomputed exit chain (root inclusive
-- chart-root entry/exit actions are valid). do_terminate() indexes
the slice instead of walking __PARENT, and uses Deque::clear()
(O(1) in heapless 0.8) instead of pop_front-loop. terminate_event
bench: -22.5% instr / -23.4% cycles.
* TimerTable empty-skip: pop_expired / min_remaining / decrement /
cancel_state each get inlined is_empty() early-out with the
non-empty body marked #[cold].
* Codegen-static timer-machinery elide: when chart has no Duration
triggers, every TimerTable call is omitted from step() and
exit_state(). step() returns None directly. dispatch_trigger and
the timer-fired arms remain but are unreachable; LLVM DCEs them.
* TimerEntry.remaining_ns: u128 -> u64 (~584-year ceiling). Halves
entry size; replaces u128 saturating-sub with u64. The smaller
machine struct improves cache density on every bench, even
timer-free ones.
Bench coverage: four new benches added to cover paths the original
suite missed -- bubble_leaf/mid/top (handler resolution at varying
ancestor depths), timer_fire_dispatch (only bench exercising
pop_expired's non-empty branch and dispatch_trigger), terminate_event
(only bench exercising do_terminate), bubble_cross_tree (deep bubble
+ 10-state cross-tree, the realistic HSM worst case).
See hsmc/benches/baseline.txt for the per-pass delta blocks and
docs/003. latency-analysis.md sections 1, 9, and 12 for the
cost-surface analysis.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The codegen now emits one `__chart_observe!` invocation per observable
atom. The macro fans out at compile time to whichever sinks are enabled:
the in-memory journal (`feature = "journal"`), `defmt` (`trace-defmt`),
`log` (`trace-log`), and/or `tracing` (`trace-tracing`). Trace and
journal share the same vocabulary by construction — they cannot
diverge. With no sink feature on, every site expands to `()` (zero
overhead). Multiple trace backends now compose; previously they were
mutually exclusive.
Trace lines use a logfmt-style format with a `[statechart:Name]` prefix
and `key=value` pairs, machine-parseable and greppable. Twenty verbs
cover every observable atom — entries/exits with begin/end pairs,
actions with kind, durings, transitions with the reason that triggered
them, event lifecycle (received/delivered/dropped/queued/failed),
timers, and termination.
BREAKING CHANGES (chart syntax unchanged; observability surface only):
- `TraceEvent::TransitionFired` now carries `reason: TransitionReason`
showing whether the transition was driven by an event, a timer, or
internal logic.
- New `TraceEvent` variants for phase pairing: `EnterBegan`, `ExitBegan`,
`TransitionComplete`, `EventReceived`.
- Trace prefix changed from `[Name]` to `[statechart:Name]`.
- The chart-level `trace;` keyword is parsed for backwards compatibility
but no longer gates emission.
- `__chart_trace!` / `__chart_journal!` macros removed (doc-hidden).
Verification: 159 integration tests pass, 0 missed mutants, miri 68
tests clean, Creusot 14/14 VCs discharged. Zero overhead confirmed via
`cargo expand` — every observation site expands to `{}` with no sinks
enabled.
Bump workspace version 0.2.0 → 0.3.0.
CHANGELOG: documents the additions in this release —
- `journal` Cargo feature with full TraceEvent enum + machine API
- root state targetable by chart name
- 113 deterministic-flow tests
- verification/ workspace member with mechanical Creusot proofs
- docs/002 formal semantics doc
- README "How a chart behaves" building-block section
- mise + just tooling overhaul (set shell, set script-interpreter)
README: integrates the semantics from docs/002 as eight building-
block rules that build on each other (active path, default-descent,
event bubbling, transitions, up-transition rule, entry/exit
ordering, timer lifecycle, emit / during / termination). Adds an
explicit "what hsmc deliberately does NOT have" table with what to
use instead. New "Verification" section pointing at `just verify`.
No behavior changes for v0.1 / v0.2 charts — every existing chart
compiles unchanged.
A transition whose target is already active (an ancestor of the
innermost active state) now unwinds only the subtree strictly below
the target. The target's entry/exit actions do not fire and its
default() does not re-descend — you cannot re-enter a state you never
left. The old behavior could loop infinitely when a child transitioned
to its own parent.
Self-transitions (target == innermost) and lateral transitions are
unchanged. See spec §2.6 and tests §T2.8 / T2.8b / T2.8c.
Also in this release:
* Full workspace justfile (check, test, coverage, mutants, pre-release)
* Unit tests covering all __private internals; `cargo mutants` now exits
clean (38 caught, 178 unviable, 0 missed, 0 timeouts).
* .config/nextest.toml with slow-timeout/terminate-after so hung tests
fail fast rather than wedging cargo-mutants runs.
BREAKING CHANGE: transition-to-ancestor semantics. Machines that relied
on the old re-entry behavior of up-transitions will observe different
exit/entry action sequences.
Add LICENSE-MIT and LICENSE-APACHE at workspace root and in each member package so the dual MIT OR Apache-2.0 license ships in the published .crate.
Fill in publish metadata (authors, repository, homepage, readme, keywords, categories) across the workspace and both members.
Add CHANGELOG.md with a 0.1.0 initial release entry.
Fix clippy lints: unused enumerate index, collapsible if, Default impls for TimerTable, SimRadio, and Ctx, use RangeInclusive::contains.
Exclude examples/microwave_tui.rs from the published package. The file stays in-tree but has known unawaited async log futures that need a dedicated pass before it ships.
Apply cargo fmt across the workspace.