Skip to content

feat(hydro_lang): add embedded runtime helpers and embedded_flow! macro#3011

Draft
shadaj wants to merge 1 commit into
mainfrom
sandbox-61167f85-ecce-4c35-8c6b-dddaba7d43db
Draft

feat(hydro_lang): add embedded runtime helpers and embedded_flow! macro#3011
shadaj wants to merge 1 commit into
mainfrom
sandbox-61167f85-ecce-4c35-8c6b-dddaba7d43db

Conversation

@shadaj

@shadaj shadaj commented Jul 8, 2026

Copy link
Copy Markdown
Member

Upstreams a downstream app's embedded-mode runtime helpers into hydro_lang behind the
embedded_runtime feature, as the new hydro_lang::embedded module:

  • InputBuffer<T> / InputStream<T>: address-stable queue backing a Stream input.
  • CallbackSlot<T>: address-stable slot holding a scoped FnMut(T) output callback.
  • AliasedBox<T> / OwnedErasedBox: owning allocations without Box's noalias guarantee.
  • DfirRunnable: object-safe erasure for the unnameable Dfir<impl TickClosure>.
  • embedded_flow!: declares a safe, self-contained holder struct for a generated flow.

Safety/perf fixes relative to the downstream version (validated under Miri/Stacked Borrows):

  • fix: Box-move aliasing UB — holder fields were Boxes whose moves invalidate the
    flow's raw pointers into their contents; replaced with new AliasedBox (raw-pointer
    ownership, Deref only), and the cluster self-id is now owned via OwnedErasedBox.
  • fix: invoke_with left a dangling callback installed on panic, and nested invoke_with
    cleared (instead of restored) the outer callback; a drop guard now saves/restores the
    previous pointer.
  • fix: re-entrant slot invocation could alias the running callback's &mut; the callback
    pointer lives in a Cell and is taken out while it runs.
  • fix: push held a borrow of the waker cell while running foreign waker code; wakes are
    now taken-and-consumed (coalescing redundant wakes), poll_next skips waker bookkeeping
    on the ready path and uses Waker::clone_from (will_wake fast path), with a queue
    re-check to avoid lost wakeups.
  • fix: InputBuffer was auto-Send, letting safe code race pushes across threads; it is
    now !Send (PhantomData<*mut ()>).
  • fix: the holder no longer runs the flow during construction before callbacks can be
    installed (which silently discarded initialization outputs).
  • no_std-readiness: core-only primitives with const fn new, Pin-based address
    stability contracts, and alloc-dependent pieces isolated behind embedded::__private
    re-exports so the macro can later switch std -> alloc non-breakingly.

The macro is redesigned from the downstream two-variant form into a single arm with
optional sections (self_id, membership, inputs, network_in, outputs,
network_out), covering shapes the old macro could not (e.g. receive-only cluster
members, membership streams). It generates accessors, run(), and run_with(...)
(which installs all output callbacks via nested invoke_with).

test(hydro_lang): unit tests for the primitives (waker coalescing, FIFO order, move
stability, nested/panicking/re-entrant callback installs, erased drop).

test(hydro_test_embedded): embedded_flow! holder tests against the real generated flows
(capitalize, singleton input, o2o echo wiring two holders together, o2m broadcast with
membership, m2m broadcast), including re-entrant input pushes from output callbacks;
these also pass under Miri.

build(hydro_lang,hydro_std,hydro_test): post-process stageleft_tool::gen_final! output
to gate the generated stageleft registration ctor behind cfg(not(miri)), since the
ctor crate's init_array entries trip Miri's ABI checks; marked TODO for removal once
fixed upstream (hydro-project/stageleft#84).

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 8, 2026

Copy link
Copy Markdown

Deploying hydro with  Cloudflare Pages  Cloudflare Pages

Latest commit: 21713df
Status: ✅  Deploy successful!
Preview URL: https://f8fad2aa.hydroflow.pages.dev
Branch Preview URL: https://sandbox-61167f85-ecce-4c35-8.hydroflow.pages.dev

View logs

…acro

Upstreams a downstream app's embedded-mode runtime helpers into `hydro_lang` behind the
`embedded_runtime` feature, as the new `hydro_lang::embedded` module:

- `InputBuffer<T>` / `InputStream<T>`: address-stable queue backing a `Stream` input.
- `CallbackSlot<T>`: address-stable slot holding a scoped `FnMut(T)` output callback.
- `AliasedBox<T>` / `OwnedErasedBox`: owning allocations without `Box`'s noalias guarantee.
- `DfirRunnable`: object-safe erasure for the unnameable `Dfir<impl TickClosure>`.
- `embedded_flow!`: declares a safe, self-contained holder struct for a generated flow.

Safety/perf fixes relative to the downstream version (validated under Miri/Stacked Borrows):

- fix: `Box`-move aliasing UB — holder fields were `Box`es whose moves invalidate the
  flow's raw pointers into their contents; replaced with new `AliasedBox` (raw-pointer
  ownership, `Deref` only), and the cluster self-id is now owned via `OwnedErasedBox`.
- fix: `invoke_with` left a dangling callback installed on panic, and nested `invoke_with`
  cleared (instead of restored) the outer callback; a drop guard now saves/restores the
  previous pointer.
- fix: re-entrant slot invocation could alias the running callback's `&mut`; the callback
  pointer lives in a `Cell` and is taken out while it runs.
- fix: `push` held a borrow of the waker cell while running foreign waker code; wakes are
  now taken-and-consumed (coalescing redundant wakes), `poll_next` skips waker bookkeeping
  on the ready path and uses `Waker::clone_from` (`will_wake` fast path), with a queue
  re-check to avoid lost wakeups.
- fix: `InputBuffer` was auto-`Send`, letting safe code race pushes across threads; it is
  now `!Send` (`PhantomData<*mut ()>`).
- fix: the holder no longer runs the flow during construction before callbacks can be
  installed (which silently discarded initialization outputs).
- no_std-readiness: `core`-only primitives with `const fn new`, `Pin`-based address
  stability contracts, and alloc-dependent pieces isolated behind `embedded::__private`
  re-exports so the macro can later switch `std` -> `alloc` non-breakingly.

The macro is redesigned from the downstream two-variant form into a single arm with
optional sections (`self_id`, `membership`, `inputs`, `network_in`, `outputs`,
`network_out`), covering shapes the old macro could not (e.g. receive-only cluster
members, membership streams). It generates accessors, `run()`, and `run_with(...)`
(which installs all output callbacks via nested `invoke_with`).

test(hydro_lang): unit tests for the primitives (waker coalescing, FIFO order, move
stability, nested/panicking/re-entrant callback installs, erased drop).

test(hydro_test_embedded): `embedded_flow!` holder tests against the real generated flows
(capitalize, singleton input, o2o echo wiring two holders together, o2m broadcast with
membership, m2m broadcast), including re-entrant input pushes from output callbacks;
these also pass under Miri.

build(hydro_lang,hydro_std,hydro_test): post-process `stageleft_tool::gen_final!` output
to gate the generated stageleft registration ctor behind `cfg(not(miri))`, since the
`ctor` crate's init_array entries trip Miri's ABI checks; marked TODO for removal once
fixed upstream (hydro-project/stageleft#84).

Co-authored-by: Infinity 🤖 <infinity@hydro.run>
PR: #3011
@shadaj
shadaj force-pushed the sandbox-61167f85-ecce-4c35-8c6b-dddaba7d43db branch from 4fc8e91 to 21713df Compare July 8, 2026 23:43
@shadaj
shadaj changed the base branch from sandbox-f2bec072-3efe-449b-8eb4-0bf7ec6abfe1 to main July 8, 2026 23:43
@shadaj
shadaj marked this pull request as ready for review July 14, 2026 17:59
@shadaj
shadaj requested review from a team and luckyworkama July 14, 2026 17:59
@shadaj
shadaj marked this pull request as draft July 14, 2026 18:01
@shadaj

shadaj commented Jul 14, 2026

Copy link
Copy Markdown
Member Author

Marking as draft while waiting on Stageleft release.

Comment on lines +15 to +16
//! - [`AliasedBox`] / [`OwnedErasedBox`]: owned heap allocations that, unlike [`Box`], may
//! be moved while raw pointers into their contents exist.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this different from Pin<Box<T>>?

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a new hydro_lang::embedded module (behind the embedded_runtime feature) that provides safe runtime primitives and the embedded_flow! macro for driving embedded-mode generated DFIR flows, plus test coverage for both the primitives and real generated embedded flows. It also introduces a build-script workaround to gate stageleft-generated ctor registration under cfg(not(miri)) to keep Miri runs working until upstream fixes land.

Changes:

  • Add hydro_lang::embedded runtime helpers (InputBuffer, CallbackSlot, AliasedBox, OwnedErasedBox, DfirRunnable) and the embedded_flow! macro.
  • Add embedded holder tests in hydro_test_embedded exercising real generated flows and re-entrancy patterns.
  • Post-process stageleft output in multiple crates’ build.rs to gate generated ctor registration under cfg(not(miri)).

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
hydro_test/build.rs Post-processes stageleft-generated staged_deps.rs to gate ctor registration under cfg(not(miri)) for Miri compatibility.
hydro_test_embedded/src/lib.rs Adds tests for embedded_flow! holder structs against real generated embedded flows (including network wiring scenarios).
hydro_std/build.rs Same stageleft staged_deps.rs post-processing for Miri compatibility.
hydro_lang/src/lib.rs Exposes the new embedded module behind the embedded_runtime feature flag.
hydro_lang/src/embedded.rs Introduces the embedded runtime primitives, helper macros, and unit tests for the primitives.
hydro_lang/build.rs Same stageleft staged_deps.rs post-processing for Miri compatibility.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +30 to +36
//! # `no_std`
//!
//! The core primitives ([`InputBuffer`], [`CallbackSlot`]) only use `core` items, have
//! `const fn new` constructors, and communicate address stability through [`Pin`], so they
//! are compatible with future `no_std` targets using statically-allocated (fixed-size)
//! storage. Only the owning-allocation helpers ([`AliasedBox`], [`OwnedErasedBox`]) and the
//! [`embedded_flow!`](crate::embedded_flow) macro require `alloc`.
Comment thread hydro_std/build.rs
Comment on lines +8 to +12
let staged_deps =
std::path::Path::new(&std::env::var("OUT_DIR").unwrap()).join("staged_deps.rs");
let contents = std::fs::read_to_string(&staged_deps).unwrap();
std::fs::write(
&staged_deps,
Comment thread hydro_test/build.rs
Comment on lines +9 to +13
// stageleft registration ctor behind `cfg(not(miri))`.
let staged_deps =
std::path::Path::new(&std::env::var("OUT_DIR").unwrap()).join("staged_deps.rs");
let contents = std::fs::read_to_string(&staged_deps).unwrap();
std::fs::write(
Comment thread hydro_lang/build.rs
Comment on lines +24 to +28
// stageleft registration ctor behind `cfg(not(miri))`.
let staged_deps =
std::path::Path::new(&std::env::var("OUT_DIR").unwrap()).join("staged_deps.rs");
let contents = std::fs::read_to_string(&staged_deps).unwrap();
std::fs::write(
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.

3 participants