pub mod style;
use super::boundedness::{Bounded, Unbounded};
use super::stream::{Ordering, Retries};
use crate::location::{Location, NoTick, Tick};
#[doc(hidden)]
#[macro_export]
macro_rules! __sliced_parse_uses__ {
(
@uses [$($uses:tt)*]
@states [$($states:tt)*]
let $name:ident = use:: $invocation:expr; $($rest:tt)*
) => {
$crate::__sliced_parse_uses__!(
@uses [$($uses)* { $name, $invocation, $invocation }]
@states [$($states)*]
$($rest)*
)
};
(
@uses [$($uses:tt)*]
@states [$($states:tt)*]
let $name:ident = use($($args:expr),* $(,)?); $($rest:tt)*
) => {
$crate::__sliced_parse_uses__!(
@uses [$($uses)* { $name, $crate::macro_support::copy_span::copy_span!($($args,)* default)($($args),*), $($args),* }]
@states [$($states)*]
$($rest)*
)
};
(
@uses [$($uses:tt)*]
@states [$($states:tt)*]
let mut $name:ident = use:: $style:ident $(::<$ty:ty>)? ($($args:expr)?); $($rest:tt)*
) => {
$crate::__sliced_parse_uses__!(
@uses [$($uses)*]
@states [$($states)* { $name, $style, (($($ty)?), ($($args)?)) }]
$($rest)*
)
};
(
@uses []
@states [$({ $state_name:ident, $state_style:ident, $state_arg:tt })+]
$($body:tt)*
) => {
{
compile_error!("sliced! requires at least one `let name = use(...)` statement to determine the tick")
}
};
(
@uses [$({ $use_name:ident, $invocation:expr, $($invocation_spans:expr),* })+]
@states [$({ $state_name:ident, $state_style:ident, (($($state_ty:ty)?), ($($state_arg:expr)?)) })*]
$($body:tt)*
) => {
{
use $crate::live_collections::sliced::style::*;
let __styled = (
$($invocation,)+
);
let __tick = $crate::live_collections::sliced::Slicable::create_tick(&__styled.0);
let __backtraces = {
use $crate::compile::ir::backtrace::__macro_get_backtrace;
(
$($crate::macro_support::copy_span::copy_span!($($invocation_spans,)* {
__macro_get_backtrace(1)
}),)+
)
};
let __sliced = $crate::live_collections::sliced::Slicable::slice(__styled, &__tick, __backtraces);
let (
$($use_name,)+
) = __sliced;
let (__handles, __states) = $crate::live_collections::sliced::unzip_cycles((
$($crate::live_collections::sliced::style::$state_style$(::<$state_ty, _>)?(& __tick, $($state_arg)?),)*
));
let (
$(mut $state_name,)*
) = __states;
let __body_result = {
$($body)*
};
let __final_states = (
$($state_name,)*
);
$crate::live_collections::sliced::complete_cycles(__handles, __final_states);
$crate::live_collections::sliced::Unslicable::unslice(__body_result)
}
};
}
#[macro_export]
macro_rules! __sliced__ {
($($tt:tt)*) => {
$crate::__sliced_parse_uses__!(
@uses []
@states []
$($tt)*
)
};
}
pub use crate::__sliced__ as sliced;
pub fn yield_atomic<T>(t: T) -> style::Atomic<T> {
style::Atomic {
collection: t,
nondet: crate::nondet::NonDet,
}
}
pub trait Slicable<'a, L: Location<'a>> {
type Slice;
type Backtrace;
fn get_location(&self) -> &L;
fn create_tick(&self) -> Tick<L> {
self.get_location().try_tick().unwrap()
}
fn slice(self, tick: &Tick<L>, backtrace: Self::Backtrace) -> Self::Slice;
}
pub trait Unslicable {
type Unsliced;
fn unslice(self) -> Self::Unsliced;
}
#[doc(hidden)]
pub trait UnzipCycles {
type Handles;
type States;
fn unzip(self) -> (Self::Handles, Self::States);
}
#[doc(hidden)]
pub fn unzip_cycles<T: UnzipCycles>(cycles: T) -> (T::Handles, T::States) {
cycles.unzip()
}
#[doc(hidden)]
pub trait CompleteCycles<States> {
fn complete(self, states: States);
}
#[doc(hidden)]
pub fn complete_cycles<H: CompleteCycles<S>, S>(handles: H, states: S) {
handles.complete(states);
}
impl<'a, L: Location<'a>> Slicable<'a, L> for () {
type Slice = ();
type Backtrace = ();
fn get_location(&self) -> &L {
unreachable!()
}
fn slice(self, _tick: &Tick<L>, _backtrace: Self::Backtrace) -> Self::Slice {}
}
impl Unslicable for () {
type Unsliced = ();
fn unslice(self) -> Self::Unsliced {}
}
macro_rules! impl_slicable_for_tuple {
($($T:ident, $T_bt:ident, $idx:tt),+) => {
impl<'a, L: Location<'a>, $($T: Slicable<'a, L>),+> Slicable<'a, L> for ($($T,)+) {
type Slice = ($($T::Slice,)+);
type Backtrace = ($($T::Backtrace,)+);
fn get_location(&self) -> &L {
self.0.get_location()
}
#[expect(non_snake_case, reason = "macro codegen")]
fn slice(self, tick: &Tick<L>, backtrace: Self::Backtrace) -> Self::Slice {
let ($($T,)+) = self;
let ($($T_bt,)+) = backtrace;
($($T.slice(tick, $T_bt),)+)
}
}
impl<$($T: Unslicable),+> Unslicable for ($($T,)+) {
type Unsliced = ($($T::Unsliced,)+);
#[expect(non_snake_case, reason = "macro codegen")]
fn unslice(self) -> Self::Unsliced {
let ($($T,)+) = self;
($($T.unslice(),)+)
}
}
};
}
#[cfg(stageleft_runtime)]
impl_slicable_for_tuple!(S1, S1_bt, 0);
#[cfg(stageleft_runtime)]
impl_slicable_for_tuple!(S1, S1_bt, 0, S2, S2_bt, 1);
#[cfg(stageleft_runtime)]
impl_slicable_for_tuple!(S1, S1_bt, 0, S2, S2_bt, 1, S3, S3_bt, 2);
#[cfg(stageleft_runtime)]
impl_slicable_for_tuple!(S1, S1_bt, 0, S2, S2_bt, 1, S3, S3_bt, 2, S4, S4_bt, 3);
#[cfg(stageleft_runtime)]
impl_slicable_for_tuple!(
S1, S1_bt, 0, S2, S2_bt, 1, S3, S3_bt, 2, S4, S4_bt, 3, S5, S5_bt, 4
);
#[cfg(stageleft_runtime)]
impl_slicable_for_tuple!(
S1, S1_bt, 0, S2, S2_bt, 1, S3, S3_bt, 2, S4, S4_bt, 3, S5, S5_bt, 4, S6, S6_bt, 5
);
#[cfg(stageleft_runtime)]
impl_slicable_for_tuple!(
S1, S1_bt, 0, S2, S2_bt, 1, S3, S3_bt, 2, S4, S4_bt, 3, S5, S5_bt, 4, S6, S6_bt, 5, S7, S7_bt,
6
);
#[cfg(stageleft_runtime)]
impl_slicable_for_tuple!(
S1, S1_bt, 0, S2, S2_bt, 1, S3, S3_bt, 2, S4, S4_bt, 3, S5, S5_bt, 4, S6, S6_bt, 5, S7, S7_bt,
6, S8, S8_bt, 7
);
#[cfg(stageleft_runtime)]
impl_slicable_for_tuple!(
S1, S1_bt, 0, S2, S2_bt, 1, S3, S3_bt, 2, S4, S4_bt, 3, S5, S5_bt, 4, S6, S6_bt, 5, S7, S7_bt,
6, S8, S8_bt, 7, S9, S9_bt, 8
);
#[cfg(stageleft_runtime)]
impl_slicable_for_tuple!(
S1, S1_bt, 0, S2, S2_bt, 1, S3, S3_bt, 2, S4, S4_bt, 3, S5, S5_bt, 4, S6, S6_bt, 5, S7, S7_bt,
6, S8, S8_bt, 7, S9, S9_bt, 8, S10, S10_bt, 9
);
#[cfg(stageleft_runtime)]
impl_slicable_for_tuple!(
S1, S1_bt, 0, S2, S2_bt, 1, S3, S3_bt, 2, S4, S4_bt, 3, S5, S5_bt, 4, S6, S6_bt, 5, S7, S7_bt,
6, S8, S8_bt, 7, S9, S9_bt, 8, S10, S10_bt, 9, S11, S11_bt, 10
);
#[cfg(stageleft_runtime)]
impl_slicable_for_tuple!(
S1, S1_bt, 0, S2, S2_bt, 1, S3, S3_bt, 2, S4, S4_bt, 3, S5, S5_bt, 4, S6, S6_bt, 5, S7, S7_bt,
6, S8, S8_bt, 7, S9, S9_bt, 8, S10, S10_bt, 9, S11, S11_bt, 10, S12, S12_bt, 11
);
macro_rules! impl_cycles_for_tuple {
($($H:ident, $S:ident, $idx:tt),*) => {
impl<$($H, $S),*> UnzipCycles for ($(($H, $S),)*) {
type Handles = ($($H,)*);
type States = ($($S,)*);
#[expect(clippy::allow_attributes, reason = "macro codegen")]
#[allow(non_snake_case, reason = "macro codegen")]
fn unzip(self) -> (Self::Handles, Self::States) {
let ($($H,)*) = self;
(
($($H.0,)*),
($($H.1,)*),
)
}
}
impl<$($H: crate::forward_handle::CompleteCycle<$S>, $S),*> CompleteCycles<($($S,)*)> for ($($H,)*) {
#[expect(clippy::allow_attributes, reason = "macro codegen")]
#[allow(non_snake_case, reason = "macro codegen")]
fn complete(self, states: ($($S,)*)) {
let ($($H,)*) = self;
let ($($S,)*) = states;
$($H.complete_next_tick($S);)*
}
}
};
}
#[cfg(stageleft_runtime)]
impl_cycles_for_tuple!();
#[cfg(stageleft_runtime)]
impl_cycles_for_tuple!(H1, S1, 0);
#[cfg(stageleft_runtime)]
impl_cycles_for_tuple!(H1, S1, 0, H2, S2, 1);
#[cfg(stageleft_runtime)]
impl_cycles_for_tuple!(H1, S1, 0, H2, S2, 1, H3, S3, 2);
#[cfg(stageleft_runtime)]
impl_cycles_for_tuple!(H1, S1, 0, H2, S2, 1, H3, S3, 2, H4, S4, 3);
#[cfg(stageleft_runtime)]
impl_cycles_for_tuple!(H1, S1, 0, H2, S2, 1, H3, S3, 2, H4, S4, 3, H5, S5, 4);
#[cfg(stageleft_runtime)]
impl_cycles_for_tuple!(
H1, S1, 0, H2, S2, 1, H3, S3, 2, H4, S4, 3, H5, S5, 4, H6, S6, 5
);
#[cfg(stageleft_runtime)]
impl_cycles_for_tuple!(
H1, S1, 0, H2, S2, 1, H3, S3, 2, H4, S4, 3, H5, S5, 4, H6, S6, 5, H7, S7, 6
);
#[cfg(stageleft_runtime)]
impl_cycles_for_tuple!(
H1, S1, 0, H2, S2, 1, H3, S3, 2, H4, S4, 3, H5, S5, 4, H6, S6, 5, H7, S7, 6, H8, S8, 7
);
#[cfg(stageleft_runtime)]
impl_cycles_for_tuple!(
H1, S1, 0, H2, S2, 1, H3, S3, 2, H4, S4, 3, H5, S5, 4, H6, S6, 5, H7, S7, 6, H8, S8, 7, H9, S9,
8
);
#[cfg(stageleft_runtime)]
impl_cycles_for_tuple!(
H1, S1, 0, H2, S2, 1, H3, S3, 2, H4, S4, 3, H5, S5, 4, H6, S6, 5, H7, S7, 6, H8, S8, 7, H9, S9,
8, H10, S10, 9
);
#[cfg(stageleft_runtime)]
impl_cycles_for_tuple!(
H1, S1, 0, H2, S2, 1, H3, S3, 2, H4, S4, 3, H5, S5, 4, H6, S6, 5, H7, S7, 6, H8, S8, 7, H9, S9,
8, H10, S10, 9, H11, S11, 10
);
#[cfg(stageleft_runtime)]
impl_cycles_for_tuple!(
H1, S1, 0, H2, S2, 1, H3, S3, 2, H4, S4, 3, H5, S5, 4, H6, S6, 5, H7, S7, 6, H8, S8, 7, H9, S9,
8, H10, S10, 9, H11, S11, 10, H12, S12, 11
);
impl<'a, T, L: Location<'a>, O: Ordering, R: Retries> Unslicable
for super::Stream<T, Tick<L>, Bounded, O, R>
{
type Unsliced = super::Stream<T, L, Unbounded, O, R>;
fn unslice(self) -> Self::Unsliced {
self.all_ticks()
}
}
impl<'a, T, L: Location<'a>> Unslicable for super::Singleton<T, Tick<L>, Bounded> {
type Unsliced = super::Singleton<T, L, Unbounded>;
fn unslice(self) -> Self::Unsliced {
self.latest()
}
}
impl<'a, T, L: Location<'a>> Unslicable for super::Optional<T, Tick<L>, Bounded> {
type Unsliced = super::Optional<T, L, Unbounded>;
fn unslice(self) -> Self::Unsliced {
self.latest()
}
}
impl<'a, K, V, L: Location<'a>, O: Ordering, R: Retries> Unslicable
for super::KeyedStream<K, V, Tick<L>, Bounded, O, R>
{
type Unsliced = super::KeyedStream<K, V, L, Unbounded, O, R>;
fn unslice(self) -> Self::Unsliced {
self.all_ticks()
}
}
impl<'a, T, L: Location<'a> + NoTick, O: Ordering, R: Retries> Unslicable
for style::Atomic<super::Stream<T, Tick<L>, Bounded, O, R>>
{
type Unsliced = super::Stream<T, crate::location::Atomic<L>, Unbounded, O, R>;
fn unslice(self) -> Self::Unsliced {
self.collection.all_ticks_atomic()
}
}
impl<'a, T, L: Location<'a> + NoTick> Unslicable
for style::Atomic<super::Singleton<T, Tick<L>, Bounded>>
{
type Unsliced = super::Singleton<T, crate::location::Atomic<L>, Unbounded>;
fn unslice(self) -> Self::Unsliced {
self.collection.latest_atomic()
}
}
impl<'a, T, L: Location<'a> + NoTick> Unslicable
for style::Atomic<super::Optional<T, Tick<L>, Bounded>>
{
type Unsliced = super::Optional<T, crate::location::Atomic<L>, Unbounded>;
fn unslice(self) -> Self::Unsliced {
self.collection.latest_atomic()
}
}
impl<'a, K, V, L: Location<'a> + NoTick, O: Ordering, R: Retries> Unslicable
for style::Atomic<super::KeyedStream<K, V, Tick<L>, Bounded, O, R>>
{
type Unsliced = super::KeyedStream<K, V, crate::location::Atomic<L>, Unbounded, O, R>;
fn unslice(self) -> Self::Unsliced {
self.collection.all_ticks_atomic()
}
}
#[cfg(feature = "sim")]
#[cfg(test)]
mod tests {
use stageleft::q;
use super::sliced;
use crate::location::Location;
use crate::nondet::nondet;
use crate::prelude::FlowBuilder;
#[test]
fn sim_state_counter() {
let mut flow = FlowBuilder::new();
let node = flow.process::<()>();
let (input_send, input) = node.sim_input::<i32, _, _>();
let out_recv = sliced! {
let batch = use(input, nondet!());
let mut counter = use::state(|l| l.singleton(q!(0)));
let new_count = counter.clone().zip(batch.count())
.map(q!(|(old, add)| old + add));
counter = new_count.clone();
new_count.into_stream()
}
.sim_output();
flow.sim().exhaustive(async || {
input_send.send(1);
assert_eq!(out_recv.next().await.unwrap(), 1);
input_send.send(1);
assert_eq!(out_recv.next().await.unwrap(), 2);
input_send.send(1);
assert_eq!(out_recv.next().await.unwrap(), 3);
});
}
#[cfg(feature = "sim")]
#[test]
fn sim_state_null_optional() {
use crate::live_collections::Optional;
use crate::live_collections::boundedness::Bounded;
use crate::location::{Location, Tick};
let mut flow = FlowBuilder::new();
let node = flow.process::<()>();
let (input_send, input) = node.sim_input::<i32, _, _>();
let out_recv = sliced! {
let batch = use(input, nondet!());
let mut prev = use::state_null::<Optional<i32, Tick<_>, Bounded>>();
let output = prev.clone().unwrap_or(prev.location().singleton(q!(-1)));
prev = batch.first();
output.into_stream()
}
.sim_output();
flow.sim().exhaustive(async || {
input_send.send(10);
assert_eq!(out_recv.next().await.unwrap(), -1);
input_send.send(20);
assert_eq!(out_recv.next().await.unwrap(), 10);
input_send.send(30);
assert_eq!(out_recv.next().await.unwrap(), 20);
});
}
#[test]
fn sim_state_source_iter() {
let mut flow = FlowBuilder::new();
let node = flow.process::<()>();
let (input_send, input) = node.sim_input::<i32, _, _>();
let out_recv = sliced! {
let batch = use(input, nondet!());
let mut items = use::state(|l| l.source_iter(q!([10, 20])));
let output = items.clone();
items = batch;
output
}
.sim_output();
flow.sim().exhaustive(async || {
input_send.send(3);
let mut results = vec![];
results.push(out_recv.next().await.unwrap());
results.push(out_recv.next().await.unwrap());
results.sort();
assert_eq!(results, vec![10, 20]);
input_send.send(4);
assert_eq!(out_recv.next().await.unwrap(), 3);
input_send.send(5);
assert_eq!(out_recv.next().await.unwrap(), 4);
});
}
#[test]
fn sim_sliced_atomic_keyed_stream() {
let mut flow = FlowBuilder::new();
let node = flow.process::<()>();
let (input_send, input) = node.sim_input::<(i32, i32), _, _>();
let atomic_keyed_input = input.into_keyed().atomic();
let accumulated_inputs = atomic_keyed_input
.clone()
.assume_ordering(nondet!())
.fold(
q!(|| 0),
q!(|curr, new| {
*curr += new;
}),
);
let out_recv = sliced! {
let atomic_keyed_input = use::atomic(atomic_keyed_input, nondet!());
let accumulated_inputs = use::atomic(accumulated_inputs, nondet!());
accumulated_inputs.join_keyed_stream(atomic_keyed_input)
.map(q!(|(sum, _input)| sum))
.entries()
}
.assume_ordering_trusted(nondet!())
.sim_output();
flow.sim().exhaustive(async || {
input_send.send((1, 1));
assert_eq!(out_recv.next().await.unwrap(), (1, 1));
input_send.send((1, 2));
assert_eq!(out_recv.next().await.unwrap(), (1, 3));
input_send.send((2, 1));
assert_eq!(out_recv.next().await.unwrap(), (2, 1));
input_send.send((1, 3));
assert_eq!(out_recv.next().await.unwrap(), (1, 6));
});
}
}