1use std::future::Future;
43use std::io::Error;
44use std::pin::Pin;
45
46use bytes::{Bytes, BytesMut};
47use dfir_lang::diagnostic::Diagnostics;
48use dfir_lang::graph::{AsCodeOptions, DfirGraph};
49use futures::{Sink, Stream};
50use proc_macro2::Span;
51use quote::quote;
52use serde::Serialize;
53use serde::de::DeserializeOwned;
54use slotmap::SparseSecondaryMap;
55use stageleft::{QuotedWithContext, q};
56
57use super::deploy_provider::{ClusterSpec, Deploy, ExternalSpec, Node, ProcessSpec, RegisterPort};
58use crate::compile::builder::ExternalPortId;
59use crate::location::dynamic::LocationId;
60use crate::location::member_id::TaglessMemberId;
61use crate::location::{LocationKey, MembershipEvent, NetworkHint};
62
63pub enum EmbeddedDeploy {}
67
68#[derive(Clone)]
70pub struct EmbeddedNode {
71 pub fn_name: String,
73 pub location_key: LocationKey,
75}
76
77impl Node for EmbeddedNode {
78 type Port = ();
79 type Meta = ();
80 type InstantiateEnv = EmbeddedInstantiateEnv;
81
82 fn next_port(&self) -> Self::Port {}
83
84 fn update_meta(&self, _meta: &Self::Meta) {}
85
86 fn instantiate(
87 &self,
88 _env: &mut Self::InstantiateEnv,
89 _meta: &mut Self::Meta,
90 _graph: DfirGraph,
91 _extra_stmts: &[syn::Stmt],
92 _sidecars: &[syn::Expr],
93 _as_code_options: &AsCodeOptions,
94 ) {
95 }
97}
98
99impl<'a> RegisterPort<'a, EmbeddedDeploy> for EmbeddedNode {
100 fn register(&self, _external_port_id: ExternalPortId, _port: Self::Port) {
101 panic!("EmbeddedDeploy does not support external ports");
102 }
103
104 #[expect(clippy::manual_async_fn, reason = "false positive, involves lifetimes")]
105 fn as_bytes_bidi(
106 &self,
107 _external_port_id: ExternalPortId,
108 ) -> impl Future<
109 Output = super::deploy_provider::DynSourceSink<Result<BytesMut, Error>, Bytes, Error>,
110 > + 'a {
111 async { panic!("EmbeddedDeploy does not support external ports") }
112 }
113
114 #[expect(clippy::manual_async_fn, reason = "false positive, involves lifetimes")]
115 fn as_bincode_bidi<InT, OutT>(
116 &self,
117 _external_port_id: ExternalPortId,
118 ) -> impl Future<Output = super::deploy_provider::DynSourceSink<OutT, InT, Error>> + 'a
119 where
120 InT: Serialize + 'static,
121 OutT: DeserializeOwned + 'static,
122 {
123 async { panic!("EmbeddedDeploy does not support external ports") }
124 }
125
126 #[expect(clippy::manual_async_fn, reason = "false positive, involves lifetimes")]
127 fn as_bincode_sink<T>(
128 &self,
129 _external_port_id: ExternalPortId,
130 ) -> impl Future<Output = Pin<Box<dyn Sink<T, Error = Error>>>> + 'a
131 where
132 T: Serialize + 'static,
133 {
134 async { panic!("EmbeddedDeploy does not support external ports") }
135 }
136
137 #[expect(clippy::manual_async_fn, reason = "false positive, involves lifetimes")]
138 fn as_bincode_source<T>(
139 &self,
140 _external_port_id: ExternalPortId,
141 ) -> impl Future<Output = Pin<Box<dyn Stream<Item = T>>>> + 'a
142 where
143 T: DeserializeOwned + 'static,
144 {
145 async { panic!("EmbeddedDeploy does not support external ports") }
146 }
147}
148
149impl<S: Into<String>> ProcessSpec<'_, EmbeddedDeploy> for S {
150 fn build(self, location_key: LocationKey, _name_hint: &str) -> EmbeddedNode {
151 EmbeddedNode {
152 fn_name: self.into(),
153 location_key,
154 }
155 }
156}
157
158impl<S: Into<String>> ClusterSpec<'_, EmbeddedDeploy> for S {
159 fn build(self, location_key: LocationKey, _name_hint: &str) -> EmbeddedNode {
160 EmbeddedNode {
161 fn_name: self.into(),
162 location_key,
163 }
164 }
165}
166
167impl<S: Into<String>> ExternalSpec<'_, EmbeddedDeploy> for S {
168 fn build(self, location_key: LocationKey, _name_hint: &str) -> EmbeddedNode {
169 EmbeddedNode {
170 fn_name: self.into(),
171 location_key,
172 }
173 }
174}
175
176#[derive(Clone)]
178struct EmbeddedNetworkPort {
179 name: String,
180 is_tagged: bool,
182 external_type: Option<syn::Type>,
184 peer: LocationKey,
186 transport: syn::Type,
188 serialization: syn::Type,
189 config: String,
190}
191
192impl EmbeddedNetworkPort {
193 fn new(
194 name: &str,
195 is_tagged: bool,
196 external_type: Option<&syn::Type>,
197 peer: LocationKey,
198 networking_info: &crate::networking::NetworkingInfo,
199 ) -> Self {
200 let (transport, serialization, config) =
201 channel_config_types(networking_info, external_type.is_some());
202 EmbeddedNetworkPort {
203 name: name.to_owned(),
204 is_tagged,
205 external_type: external_type.cloned(),
206 peer,
207 transport,
208 serialization,
209 config,
210 }
211 }
212}
213
214fn channel_config_types(
217 networking_info: &crate::networking::NetworkingInfo,
218 external: bool,
219) -> (syn::Type, syn::Type, String) {
220 use crate::compile::ir::{StreamOrder, StreamRetry};
221 use crate::networking::{EmbeddedFault, NetworkingInfo, TcpFault, UdpFault};
222 let root = crate::staging_util::get_this_crate();
223 let fault = |name: &str| -> syn::Type {
224 let ident = syn::Ident::new(name, Span::call_site());
225 syn::parse_quote!(#root::networking::#ident)
226 };
227 let transport: syn::Type = match networking_info {
228 NetworkingInfo::Tcp { fault: f } => {
229 let f = fault(match f {
230 TcpFault::FailStop => "FailStop",
231 TcpFault::Lossy => "Lossy",
232 TcpFault::LossyDelayedForever => "LossyDelayedForever",
233 });
234 syn::parse_quote!(#root::networking::Tcp<#f>)
235 }
236 NetworkingInfo::Udp { fault: f } => {
237 let f = fault(match f {
238 UdpFault::Lossy => "Lossy",
239 UdpFault::LossyDelayedForever => "LossyDelayedForever",
240 });
241 syn::parse_quote!(#root::networking::Udp<#f>)
242 }
243 NetworkingInfo::Embedded {
244 retries,
245 ordering,
246 fault: f,
247 } => {
248 let retries: syn::Type = match retries {
249 StreamRetry::ExactlyOnce => {
250 syn::parse_quote!(#root::live_collections::stream::ExactlyOnce)
251 }
252 StreamRetry::AtLeastOnce => {
253 syn::parse_quote!(#root::live_collections::stream::AtLeastOnce)
254 }
255 };
256 let ordering: syn::Type = match ordering {
257 StreamOrder::TotalOrder => {
258 syn::parse_quote!(#root::live_collections::stream::TotalOrder)
259 }
260 StreamOrder::NoOrder => syn::parse_quote!(#root::live_collections::stream::NoOrder),
261 };
262 let f = fault(match f {
263 EmbeddedFault::FailStop => "FailStop",
264 EmbeddedFault::Lossy => "Lossy",
265 EmbeddedFault::LossyDelayedForever => "LossyDelayedForever",
266 });
267 syn::parse_quote!(#root::networking::EmbeddedTransport<#retries, #ordering, #f>)
268 }
269 };
270 let serialization: syn::Type = if external {
271 syn::parse_quote!(#root::networking::Embedded)
272 } else {
273 syn::parse_quote!(#root::networking::Bincode)
274 };
275 (
276 transport,
277 serialization,
278 config_doc(networking_info, external),
279 )
280}
281
282fn config_doc(networking_info: &crate::networking::NetworkingInfo, external: bool) -> String {
284 use crate::compile::ir::{StreamOrder, StreamRetry};
285 use crate::networking::{EmbeddedFault, NetworkingInfo, TcpFault, UdpFault};
286 let serialization = if external { "" } else { ".bincode()" };
287 match networking_info {
288 NetworkingInfo::Tcp { fault } => {
289 let fault = match fault {
290 TcpFault::FailStop => ".fail_stop()",
291 TcpFault::Lossy => ".lossy(nondet)",
292 TcpFault::LossyDelayedForever => ".lossy_delayed_forever()",
293 };
294 format!("TCP{fault}{serialization}")
295 }
296 NetworkingInfo::Udp { fault } => {
297 let fault = match fault {
298 UdpFault::Lossy => ".lossy(nondet)",
299 UdpFault::LossyDelayedForever => ".lossy_delayed_forever()",
300 };
301 format!("UDP{fault}{serialization}")
302 }
303 NetworkingInfo::Embedded {
304 retries,
305 ordering,
306 fault,
307 } => {
308 let reorder = match ordering {
310 StreamOrder::TotalOrder => ".never_reorders()",
311 StreamOrder::NoOrder => ".may_reorder()",
312 };
313 let duplicate = match retries {
314 StreamRetry::ExactlyOnce => ".never_duplicates()",
315 StreamRetry::AtLeastOnce => ".may_duplicate()",
316 };
317 let loss = match fault {
318 EmbeddedFault::FailStop => ".never_drops()",
319 EmbeddedFault::Lossy => ".may_drop(nondet)",
320 EmbeddedFault::LossyDelayedForever => ".may_delay_forever()",
321 };
322 format!("EMBEDDED{reorder}{duplicate}{loss}")
323 }
324 }
325}
326
327#[derive(Clone)]
329struct EmbeddedStreamPort {
330 ident: syn::Ident,
331 element_type: syn::Type,
332}
333
334#[derive(Default)]
341pub struct EmbeddedInstantiateEnv {
342 inputs: SparseSecondaryMap<LocationKey, Vec<EmbeddedStreamPort>>,
343 singleton_inputs: SparseSecondaryMap<LocationKey, Vec<(syn::Ident, syn::Type)>>,
344 outputs: SparseSecondaryMap<LocationKey, Vec<EmbeddedStreamPort>>,
345 network_outputs: SparseSecondaryMap<LocationKey, Vec<EmbeddedNetworkPort>>,
347 network_inputs: SparseSecondaryMap<LocationKey, Vec<EmbeddedNetworkPort>>,
349 membership_streams: SparseSecondaryMap<LocationKey, Vec<LocationKey>>,
351 boundary: BoundaryGuarantees,
352}
353
354#[derive(Clone, Debug)]
356struct EmbeddedStreamGuarantees {
357 order: crate::compile::ir::StreamOrder,
358 retry: crate::compile::ir::StreamRetry,
359}
360
361impl EmbeddedStreamGuarantees {
362 fn of(metadata: &crate::compile::ir::HydroIrMetadata, what: &str) -> Self {
363 use crate::compile::ir::CollectionKind;
364 match &metadata.collection_kind {
365 CollectionKind::Stream { order, retry, .. }
366 | CollectionKind::KeyedStream {
367 value_order: order,
368 value_retry: retry,
369 ..
370 } => EmbeddedStreamGuarantees {
371 order: order.clone(),
372 retry: retry.clone(),
373 },
374 kind => panic!("{what} must be a stream or keyed stream, not {kind:?}"),
375 }
376 }
377}
378
379#[derive(Clone, Debug)]
381struct EmbeddedNetworkGuarantees {
382 sent: EmbeddedStreamGuarantees,
383 received: EmbeddedStreamGuarantees,
384}
385
386#[derive(Default)]
389struct BoundaryGuarantees {
390 inputs: std::collections::HashMap<(LocationKey, String), EmbeddedStreamGuarantees>,
391 outputs: std::collections::HashMap<(LocationKey, String), EmbeddedStreamGuarantees>,
392 networks: std::collections::HashMap<String, EmbeddedNetworkGuarantees>,
393}
394
395impl BoundaryGuarantees {
396 fn collect(ir: &mut [crate::compile::ir::HydroRoot]) -> Self {
397 use crate::compile::ir::{HydroNode, HydroRoot, HydroSource};
398 let location_key = |id: &LocationId, what: &str| match id.root() {
399 LocationId::Process(key) | LocationId::Cluster(key) => *key,
400 _ => panic!("{what} must be on a process or cluster"),
401 };
402 let mut boundary = BoundaryGuarantees::default();
403 let cell = std::cell::RefCell::new(&mut boundary);
404 crate::compile::ir::transform_bottom_up(
405 ir,
406 &mut |root| {
407 if let HydroRoot::EmbeddedOutput { ident, input, .. } = root {
408 let what = "an embedded output";
409 cell.borrow_mut().outputs.insert(
410 (
411 location_key(&input.metadata().location_id, what),
412 ident.to_string(),
413 ),
414 EmbeddedStreamGuarantees::of(input.metadata(), what),
415 );
416 }
417 },
418 &mut |node| match node {
419 HydroNode::Source {
420 source: HydroSource::Embedded(ident),
421 metadata,
422 } => {
423 let what = "an embedded input";
424 cell.borrow_mut().inputs.insert(
425 (location_key(&metadata.location_id, what), ident.to_string()),
426 EmbeddedStreamGuarantees::of(metadata, what),
427 );
428 }
429 HydroNode::Network {
430 name: Some(name),
431 input,
432 metadata,
433 ..
434 } => {
435 let what = "a network channel's stream";
436 cell.borrow_mut().networks.insert(
437 name.clone(),
438 EmbeddedNetworkGuarantees {
439 sent: EmbeddedStreamGuarantees::of(input.metadata(), what),
440 received: EmbeddedStreamGuarantees::of(metadata, what),
441 },
442 );
443 }
444 _ => {}
445 },
446 false,
447 );
448 boundary
449 }
450
451 fn input(&self, location_key: LocationKey, ident: &syn::Ident) -> EmbeddedStreamGuarantees {
452 self.inputs[&(location_key, ident.to_string())].clone()
453 }
454
455 fn output(&self, location_key: LocationKey, ident: &syn::Ident) -> EmbeddedStreamGuarantees {
456 self.outputs[&(location_key, ident.to_string())].clone()
457 }
458
459 fn network(&self, name: &str) -> EmbeddedNetworkGuarantees {
460 self.networks[name].clone()
461 }
462}
463
464impl<'a> Deploy<'a> for EmbeddedDeploy {
465 type Meta = ();
466 type InstantiateEnv = EmbeddedInstantiateEnv;
467
468 type Process = EmbeddedNode;
469 type Cluster = EmbeddedNode;
470 type External = EmbeddedNode;
471
472 const SUPPORTS_EXTERNAL_SERIALIZATION: bool = true;
473
474 fn o2o_sink_source(
475 env: &mut Self::InstantiateEnv,
476 p1: &Self::Process,
477 _p1_port: &(),
478 p2: &Self::Process,
479 _p2_port: &(),
480 name: Option<&str>,
481 networking_info: &crate::networking::NetworkingInfo,
482 external_types: Option<(&syn::Type, &syn::Type)>,
483 ) -> (syn::Expr, syn::Expr) {
484 let name = name.expect(
485 "EmbeddedDeploy o2o networking requires a channel name. Use `TCP.name(\"my_channel\")` to provide one.",
486 );
487
488 let sink_ident = syn::Ident::new(&format!("__network_out_{name}"), Span::call_site());
489 let source_ident = syn::Ident::new(&format!("__network_in_{name}"), Span::call_site());
490
491 env.network_outputs
492 .entry(p1.location_key)
493 .unwrap()
494 .or_default()
495 .push(EmbeddedNetworkPort::new(
496 name,
497 false,
498 external_types.map(|(i, _)| i),
499 p2.location_key,
500 networking_info,
501 ));
502 env.network_inputs
503 .entry(p2.location_key)
504 .unwrap()
505 .or_default()
506 .push(EmbeddedNetworkPort::new(
507 name,
508 false,
509 external_types.map(|(_, o)| o),
510 p1.location_key,
511 networking_info,
512 ));
513
514 (
515 syn::parse_quote!(__root_dfir_rs::sinktools::for_each(#sink_ident)),
516 syn::parse_quote!(#source_ident),
517 )
518 }
519
520 fn o2o_connect(
521 _p1: &Self::Process,
522 _p1_port: &(),
523 _p2: &Self::Process,
524 _p2_port: &(),
525 ) -> Box<dyn FnOnce()> {
526 Box::new(|| {})
527 }
528
529 fn o2m_sink_source(
530 env: &mut Self::InstantiateEnv,
531 p1: &Self::Process,
532 _p1_port: &(),
533 c2: &Self::Cluster,
534 _c2_port: &(),
535 name: Option<&str>,
536 networking_info: &crate::networking::NetworkingInfo,
537 external_types: Option<(&syn::Type, &syn::Type)>,
538 ) -> (syn::Expr, syn::Expr) {
539 let name = name.expect("EmbeddedDeploy o2m networking requires a channel name.");
540 let sink_ident = syn::Ident::new(&format!("__network_out_{name}"), Span::call_site());
541 let source_ident = syn::Ident::new(&format!("__network_in_{name}"), Span::call_site());
542 env.network_outputs
543 .entry(p1.location_key)
544 .unwrap()
545 .or_default()
546 .push(EmbeddedNetworkPort::new(
547 name,
548 true,
549 external_types.map(|(i, _)| i),
550 c2.location_key,
551 networking_info,
552 ));
553 env.network_inputs
554 .entry(c2.location_key)
555 .unwrap()
556 .or_default()
557 .push(EmbeddedNetworkPort::new(
558 name,
559 false,
560 external_types.map(|(_, o)| o),
561 p1.location_key,
562 networking_info,
563 ));
564 (
565 syn::parse_quote!(__root_dfir_rs::sinktools::for_each(#sink_ident)),
566 syn::parse_quote!(#source_ident),
567 )
568 }
569
570 fn o2m_connect(
571 _p1: &Self::Process,
572 _p1_port: &(),
573 _c2: &Self::Cluster,
574 _c2_port: &(),
575 ) -> Box<dyn FnOnce()> {
576 Box::new(|| {})
577 }
578
579 fn m2o_sink_source(
580 env: &mut Self::InstantiateEnv,
581 c1: &Self::Cluster,
582 _c1_port: &(),
583 p2: &Self::Process,
584 _p2_port: &(),
585 name: Option<&str>,
586 networking_info: &crate::networking::NetworkingInfo,
587 external_types: Option<(&syn::Type, &syn::Type)>,
588 ) -> (syn::Expr, syn::Expr) {
589 let name = name.expect("EmbeddedDeploy m2o networking requires a channel name.");
590 let sink_ident = syn::Ident::new(&format!("__network_out_{name}"), Span::call_site());
591 let source_ident = syn::Ident::new(&format!("__network_in_{name}"), Span::call_site());
592 env.network_outputs
593 .entry(c1.location_key)
594 .unwrap()
595 .or_default()
596 .push(EmbeddedNetworkPort::new(
597 name,
598 false,
599 external_types.map(|(i, _)| i),
600 p2.location_key,
601 networking_info,
602 ));
603 env.network_inputs
604 .entry(p2.location_key)
605 .unwrap()
606 .or_default()
607 .push(EmbeddedNetworkPort::new(
608 name,
609 true,
610 external_types.map(|(_, o)| o),
611 c1.location_key,
612 networking_info,
613 ));
614 (
615 syn::parse_quote!(__root_dfir_rs::sinktools::for_each(#sink_ident)),
616 syn::parse_quote!(#source_ident),
617 )
618 }
619
620 fn m2o_connect(
621 _c1: &Self::Cluster,
622 _c1_port: &(),
623 _p2: &Self::Process,
624 _p2_port: &(),
625 ) -> Box<dyn FnOnce()> {
626 Box::new(|| {})
627 }
628
629 fn m2m_sink_source(
630 env: &mut Self::InstantiateEnv,
631 c1: &Self::Cluster,
632 _c1_port: &(),
633 c2: &Self::Cluster,
634 _c2_port: &(),
635 name: Option<&str>,
636 networking_info: &crate::networking::NetworkingInfo,
637 external_types: Option<(&syn::Type, &syn::Type)>,
638 ) -> (syn::Expr, syn::Expr) {
639 let name = name.expect("EmbeddedDeploy m2m networking requires a channel name.");
640 let sink_ident = syn::Ident::new(&format!("__network_out_{name}"), Span::call_site());
641 let source_ident = syn::Ident::new(&format!("__network_in_{name}"), Span::call_site());
642 env.network_outputs
643 .entry(c1.location_key)
644 .unwrap()
645 .or_default()
646 .push(EmbeddedNetworkPort::new(
647 name,
648 true,
649 external_types.map(|(i, _)| i),
650 c2.location_key,
651 networking_info,
652 ));
653 env.network_inputs
654 .entry(c2.location_key)
655 .unwrap()
656 .or_default()
657 .push(EmbeddedNetworkPort::new(
658 name,
659 true,
660 external_types.map(|(_, o)| o),
661 c1.location_key,
662 networking_info,
663 ));
664 (
665 syn::parse_quote!(__root_dfir_rs::sinktools::for_each(#sink_ident)),
666 syn::parse_quote!(#source_ident),
667 )
668 }
669
670 fn m2m_connect(
671 _c1: &Self::Cluster,
672 _c1_port: &(),
673 _c2: &Self::Cluster,
674 _c2_port: &(),
675 ) -> Box<dyn FnOnce()> {
676 Box::new(|| {})
677 }
678
679 fn e2o_many_source(
680 _extra_stmts: &mut Vec<syn::Stmt>,
681 _p2: &Self::Process,
682 _p2_port: &(),
683 _codec_type: &syn::Type,
684 _shared_handle: String,
685 ) -> syn::Expr {
686 panic!("EmbeddedDeploy does not support networking (e2o)")
687 }
688
689 fn e2o_many_sink(_shared_handle: String) -> syn::Expr {
690 panic!("EmbeddedDeploy does not support networking (e2o)")
691 }
692
693 fn e2o_source(
694 _extra_stmts: &mut Vec<syn::Stmt>,
695 _p1: &Self::External,
696 _p1_port: &(),
697 _p2: &Self::Process,
698 _p2_port: &(),
699 _codec_type: &syn::Type,
700 _shared_handle: String,
701 ) -> syn::Expr {
702 panic!("EmbeddedDeploy does not support networking (e2o)")
703 }
704
705 fn e2o_connect(
706 _p1: &Self::External,
707 _p1_port: &(),
708 _p2: &Self::Process,
709 _p2_port: &(),
710 _many: bool,
711 _server_hint: NetworkHint,
712 ) -> Box<dyn FnOnce()> {
713 panic!("EmbeddedDeploy does not support networking (e2o)")
714 }
715
716 fn o2e_sink(
717 _p1: &Self::Process,
718 _p1_port: &(),
719 _p2: &Self::External,
720 _p2_port: &(),
721 _shared_handle: String,
722 ) -> syn::Expr {
723 panic!("EmbeddedDeploy does not support networking (o2e)")
724 }
725
726 #[expect(
727 unreachable_code,
728 reason = "panic before q! which is only for return type"
729 )]
730 fn cluster_ids(
731 _of_cluster: LocationKey,
732 ) -> impl QuotedWithContext<'a, &'a [TaglessMemberId], ()> + Clone + 'a {
733 panic!("EmbeddedDeploy does not support cluster IDs");
734 q!(unreachable!("EmbeddedDeploy does not support cluster IDs"))
735 }
736
737 fn cluster_self_id() -> impl QuotedWithContext<'a, TaglessMemberId, ()> + Clone + 'a {
738 super::embedded_runtime::embedded_cluster_self_id()
739 }
740
741 fn cluster_membership_stream(
742 env: &mut Self::InstantiateEnv,
743 at_location: &LocationId,
744 location_id: &LocationId,
745 ) -> impl QuotedWithContext<'a, Box<dyn Stream<Item = (TaglessMemberId, MembershipEvent)> + Unpin>, ()>
746 {
747 let at_key = match at_location {
748 LocationId::Process(key) | LocationId::Cluster(key) => *key,
749 _ => panic!("cluster_membership_stream must be called from a process or cluster"),
750 };
751 let cluster_key = match location_id {
752 LocationId::Cluster(key) => *key,
753 _ => panic!("cluster_membership_stream target must be a cluster"),
754 };
755 let vec = env.membership_streams.entry(at_key).unwrap().or_default();
756 let idx = if let Some(pos) = vec.iter().position(|k| *k == cluster_key) {
757 pos
758 } else {
759 vec.push(cluster_key);
760 vec.len() - 1
761 };
762
763 super::embedded_runtime::embedded_cluster_membership_stream(idx)
764 }
765
766 fn register_embedded_stream_input(
767 env: &mut Self::InstantiateEnv,
768 location_key: LocationKey,
769 ident: &syn::Ident,
770 element_type: &syn::Type,
771 ) {
772 env.inputs
773 .entry(location_key)
774 .unwrap()
775 .or_default()
776 .push(EmbeddedStreamPort {
777 ident: ident.clone(),
778 element_type: element_type.clone(),
779 });
780 }
781
782 fn register_embedded_singleton_input(
783 env: &mut Self::InstantiateEnv,
784 location_key: LocationKey,
785 ident: &syn::Ident,
786 element_type: &syn::Type,
787 ) {
788 env.singleton_inputs
789 .entry(location_key)
790 .unwrap()
791 .or_default()
792 .push((ident.clone(), element_type.clone()));
793 }
794
795 fn register_embedded_output(
796 env: &mut Self::InstantiateEnv,
797 location_key: LocationKey,
798 ident: &syn::Ident,
799 element_type: &syn::Type,
800 ) {
801 env.outputs
802 .entry(location_key)
803 .unwrap()
804 .or_default()
805 .push(EmbeddedStreamPort {
806 ident: ident.clone(),
807 element_type: element_type.clone(),
808 });
809 }
810}
811
812struct NetworkPortTokens {
816 fields: Vec<syn::Ident>,
817 field_docs: Vec<String>,
818 field_types: Vec<proc_macro2::TokenStream>,
822 generics: Vec<syn::Ident>,
823 items: Vec<proc_macro2::TokenStream>,
824 marker_defs: Vec<proc_macro2::TokenStream>,
825}
826
827impl NetworkPortTokens {
828 fn new(
829 ports: &[EmbeddedNetworkPort],
830 boundary: &BoundaryGuarantees,
831 fn_names: &SparseSecondaryMap<LocationKey, &str>,
832 generic_prefix: &str,
833 sending: bool,
834 item: impl Fn(&EmbeddedNetworkPort) -> proc_macro2::TokenStream,
835 ) -> Self {
836 let root = crate::staging_util::get_this_crate();
837 let items: Vec<proc_macro2::TokenStream> = ports.iter().map(&item).collect();
838 let fields: Vec<syn::Ident> = ports
840 .iter()
841 .map(|port| syn::Ident::new(&port.name, Span::call_site()))
842 .collect();
843 let mut marker_defs = Vec::with_capacity(ports.len());
844 let mut field_docs = Vec::with_capacity(ports.len());
845 for (port, marker) in ports.iter().zip(&fields) {
846 let name = &port.name;
847 let peer = fn_names[port.peer];
848 let channel = boundary.network(name);
849 let guarantees = if sending {
850 &channel.sent
851 } else {
852 &channel.received
853 };
854 let doc = format!(
855 "The `{name}` channel's end at this location; its other end is at `{peer}`.\n\n{}",
856 network_end_doc(port, guarantees, sending)
857 );
858 let marker_def = marker_def(&root, marker, &doc, guarantees);
859 let (transport, serialization) = (&port.transport, &port.serialization);
860 marker_defs.push(quote! {
861 #marker_def
862
863 impl #root::compile::embedded_runtime::EmbeddedNetworkChannel for #marker {
864 type Transport = #transport;
865 type Serialization = #serialization;
866 }
867 });
868 let (action, module) = if sending {
869 (format!("Sends on `{name}` to `{peer}`"), "outgoing")
870 } else {
871 (format!("Receives `{name}` from `{peer}`"), "incoming")
872 };
873 field_docs.push(format!("{action} (marker type: [`{module}::{marker}`])."));
874 }
875 let generics: Vec<syn::Ident> = (0..ports.len())
876 .map(|i| quote::format_ident!("{}{}", generic_prefix, i))
877 .collect();
878 let module = syn::Ident::new(
879 if sending { "outgoing" } else { "incoming" },
880 Span::call_site(),
881 );
882 let wrapper = if sending {
886 quote! { #root::compile::embedded_runtime::EmbeddedNetworkOutputChannel }
887 } else {
888 quote! { #root::compile::embedded_runtime::EmbeddedNetworkInputChannel }
889 };
890 let field_types = fields
891 .iter()
892 .zip(&generics)
893 .map(|(marker, generic)| quote! { #wrapper<#module::#marker, #generic> })
894 .collect();
895 NetworkPortTokens {
896 fields,
897 field_docs,
898 field_types,
899 generics,
900 items,
901 marker_defs,
902 }
903 }
904}
905
906fn marker_def(
910 root: &proc_macro2::TokenStream,
911 marker: &syn::Ident,
912 doc: &str,
913 guarantees: &EmbeddedStreamGuarantees,
914) -> proc_macro2::TokenStream {
915 let ordering = guarantees.order.type_tokens();
916 let retries = guarantees.retry.type_tokens();
917 quote! {
918 #[doc = #doc]
919 #[allow(non_camel_case_types, reason = "named after its field")]
920 pub struct #marker;
921
922 impl #root::compile::embedded_runtime::EmbeddedChannel for #marker {
923 type Ordering = #ordering;
924 type Retries = #retries;
925 }
926 }
927}
928
929fn promise_doc(guarantees: &EmbeddedStreamGuarantees, wrapper: &str) -> String {
931 format!(
932 "Promise its guarantees with `{wrapper}::new(.., Promises::new(){})`.",
933 promises_chain(guarantees)
934 )
935}
936
937fn promises_chain(guarantees: &EmbeddedStreamGuarantees) -> String {
939 use crate::compile::ir::{StreamOrder, StreamRetry};
940 [
941 (guarantees.order == StreamOrder::TotalOrder).then_some(".total_order(proof)"),
942 (guarantees.retry == StreamRetry::ExactlyOnce).then_some(".exactly_once(proof)"),
943 ]
944 .into_iter()
945 .flatten()
946 .collect()
947}
948
949fn acknowledge_doc(guarantees: &EmbeddedStreamGuarantees) -> String {
951 format!(
952 "Acknowledge what it may do with \
953 `EmbeddedOutputChannel::new(.., Acknowledgements::new(){})`.",
954 acknowledgements_chain(guarantees)
955 )
956}
957
958fn acknowledgements_chain(guarantees: &EmbeddedStreamGuarantees) -> String {
960 use crate::compile::ir::{StreamOrder, StreamRetry};
961 [
962 (guarantees.order == StreamOrder::NoOrder).then_some(".no_order()"),
963 (guarantees.retry == StreamRetry::AtLeastOnce).then_some(".at_least_once()"),
964 ]
965 .into_iter()
966 .flatten()
967 .collect()
968}
969
970fn network_end_doc(
972 port: &EmbeddedNetworkPort,
973 guarantees: &EmbeddedStreamGuarantees,
974 sending: bool,
975) -> String {
976 let config = &port.config;
977 if sending {
978 format!(
979 "Acknowledge the channel's configuration and what the stream sent on it may do with \
980 `EmbeddedNetworkOutputChannel::new(.., ChannelConfiguration::new({config}), \
981 Acknowledgements::new(){})`.",
982 acknowledgements_chain(guarantees)
983 )
984 } else {
985 format!(
986 "Acknowledge the channel's configuration and promise the guarantees of the stream it \
987 delivers with `EmbeddedNetworkInputChannel::new(.., ChannelConfiguration::new({config}), \
988 Promises::new(){})`.",
989 promises_chain(guarantees)
990 )
991 }
992}
993
994#[derive(Clone, Debug, Default)]
1005#[non_exhaustive]
1006pub struct EmbeddedCodegenOptions {
1007 pub require_flame_attribution: bool,
1014}
1015
1016impl EmbeddedCodegenOptions {
1017 pub fn with_flame_attribution() -> Self {
1019 Self {
1020 require_flame_attribution: true,
1021 }
1022 }
1023}
1024
1025impl super::deploy::DeployFlow<'_, EmbeddedDeploy> {
1026 pub fn generate_embedded(self, crate_name: &str) -> syn::File {
1060 self.generate_embedded_with_options(crate_name, &EmbeddedCodegenOptions::default())
1061 }
1062
1063 pub fn generate_embedded_with_options(
1065 mut self,
1066 crate_name: &str,
1067 codegen_options: &EmbeddedCodegenOptions,
1068 ) -> syn::File {
1069 let mut env = EmbeddedInstantiateEnv {
1070 boundary: BoundaryGuarantees::collect(&mut self.ir),
1071 ..Default::default()
1072 };
1073 let compiled = self.compile_internal(&mut env);
1074
1075 let root = crate::staging_util::get_this_crate();
1076 let orig_crate_name = quote::format_ident!("{}", crate_name.replace('-', "_"));
1077
1078 let mut items: Vec<syn::Item> = Vec::new();
1079
1080 let mut location_keys: Vec<_> = compiled.all_dfir().keys().collect();
1082 location_keys.sort();
1083
1084 let fn_names: SparseSecondaryMap<LocationKey, &str> = location_keys
1086 .iter()
1087 .map(|&k| {
1088 let name = self
1089 .processes
1090 .get(k)
1091 .map(|n| n.fn_name.as_str())
1092 .or_else(|| self.clusters.get(k).map(|n| n.fn_name.as_str()))
1093 .or_else(|| self.externals.get(k).map(|n| n.fn_name.as_str()))
1094 .expect("location key not found in any node map");
1095 (k, name)
1096 })
1097 .collect();
1098
1099 for location_key in location_keys {
1100 let graph = compiled.all_dfir()[location_key]
1101 .as_ref()
1102 .unwrap_or_else(|err| {
1103 panic!(
1104 "Failed to partition DFIR graph for location {location_key}: {}",
1105 err.diagnostic
1106 )
1107 });
1108
1109 let fn_name = fn_names[location_key];
1111 let fn_ident = syn::Ident::new(fn_name, Span::call_site());
1112
1113 let mut loc_inputs = env.inputs.get(location_key).cloned().unwrap_or_default();
1115 loc_inputs.sort_by(|a, b| a.ident.cmp(&b.ident));
1116
1117 let mut loc_outputs = env.outputs.get(location_key).cloned().unwrap_or_default();
1119 loc_outputs.sort_by(|a, b| a.ident.cmp(&b.ident));
1120
1121 let mut diagnostics = Diagnostics::new();
1122 let mut as_code_options = compiled
1125 .as_code_options
1126 .get(location_key)
1127 .cloned()
1128 .unwrap_or_default();
1129 as_code_options.exclude_type_guards = true;
1134 as_code_options.require_flame_attribution = codegen_options.require_flame_attribution;
1135 as_code_options.exclude_meta = true;
1139 let dfir_tokens = graph
1140 .as_code_with_options(
1141 "e! { __root_dfir_rs },
1142 &as_code_options,
1143 quote!(),
1144 &mut diagnostics,
1145 )
1146 .expect("DFIR inline code generation failed with diagnostics.");
1147
1148 let mut mod_items: Vec<proc_macro2::TokenStream> = Vec::new();
1150 let mut extra_fn_generics: Vec<proc_macro2::TokenStream> = Vec::new();
1151 let mut cluster_params: Vec<proc_macro2::TokenStream> = Vec::new();
1152 let mut output_params: Vec<proc_macro2::TokenStream> = Vec::new();
1153 let mut net_out_params: Vec<proc_macro2::TokenStream> = Vec::new();
1154 let mut net_in_params: Vec<proc_macro2::TokenStream> = Vec::new();
1155 let mut extra_destructure: Vec<proc_macro2::TokenStream> = Vec::new();
1156
1157 if self.clusters.contains_key(location_key)
1159 && self.clusters_reading_self_id.contains(&location_key)
1160 {
1161 cluster_params.push(quote! {
1162 __cluster_self_id: #root::location::member_id::TaglessMemberId
1163 });
1164 let self_id_ident = syn::Ident::new(
1166 &format!("__hydro_lang_cluster_self_id_{}", location_key),
1167 Span::call_site(),
1168 );
1169 extra_destructure.push(quote! {
1170 let #self_id_ident = __cluster_self_id;
1171 });
1172 }
1173
1174 if let Some(loc_memberships) = env.membership_streams.get(location_key) {
1176 let membership_struct_ident =
1177 syn::Ident::new("EmbeddedMembershipStreams", Span::call_site());
1178
1179 let mem_generic_idents: Vec<syn::Ident> = loc_memberships
1180 .iter()
1181 .enumerate()
1182 .map(|(i, _)| quote::format_ident!("__Mem{}", i))
1183 .collect();
1184
1185 let mem_field_names: Vec<syn::Ident> = loc_memberships
1186 .iter()
1187 .map(|k| {
1188 let cluster_fn_name = fn_names[*k];
1189 syn::Ident::new(cluster_fn_name, Span::call_site())
1190 })
1191 .collect();
1192
1193 let mem_item = quote! {
1194 (#root::location::member_id::TaglessMemberId, #root::location::MembershipEvent)
1195 };
1196 let mem_guarantees = EmbeddedStreamGuarantees {
1198 order: crate::compile::ir::StreamOrder::TotalOrder,
1199 retry: crate::compile::ir::StreamRetry::ExactlyOnce,
1200 };
1201 let mem_marker_defs = mem_field_names.iter().map(|field| {
1202 marker_def(
1203 &root,
1204 field,
1205 &format!(
1206 "The membership stream of the `{field}` cluster.\n\n{}",
1207 promise_doc(&mem_guarantees, "EmbeddedInputChannel")
1208 ),
1209 &mem_guarantees,
1210 )
1211 });
1212
1213 let struct_fields: Vec<proc_macro2::TokenStream> = mem_field_names
1214 .iter()
1215 .zip(mem_generic_idents.iter())
1216 .map(|(field, generic)| {
1217 let doc = format!(
1218 "Each member's `Joined` / `Left` events (marker type: [`membership::{field}`])."
1219 );
1220 quote! {
1221 #[doc = #doc]
1222 pub #field: #root::compile::embedded_runtime::EmbeddedInputChannel<membership::#field, #generic>
1223 }
1224 })
1225 .collect();
1226
1227 let struct_generics: Vec<proc_macro2::TokenStream> = mem_generic_idents
1228 .iter()
1229 .map(|generic| quote! { #generic: __root_dfir_rs::futures::Stream<Item = #mem_item> + Unpin })
1230 .collect();
1231
1232 for generic in &mem_generic_idents {
1233 extra_fn_generics.push(
1234 quote! { #generic: __root_dfir_rs::futures::Stream<Item = #mem_item> + Unpin + 'a },
1235 );
1236 }
1237
1238 cluster_params.push(quote! {
1239 __membership: #fn_ident::#membership_struct_ident<#(#mem_generic_idents),*>
1240 });
1241
1242 for (i, field) in mem_field_names.iter().enumerate() {
1243 let var_ident =
1244 syn::Ident::new(&format!("__membership_{}", i), Span::call_site());
1245 extra_destructure.push(quote! {
1246 let #var_ident = __membership.#field.into_inner();
1247 });
1248 }
1249
1250 mod_items.push(quote! {
1251 pub struct #membership_struct_ident<#(#struct_generics),*> {
1252 #(#struct_fields),*
1253 }
1254
1255 pub mod membership {
1258 use super::*;
1259 #(#mem_marker_defs)*
1260 }
1261 });
1262 }
1263
1264 let mut loc_singleton_inputs = env
1267 .singleton_inputs
1268 .get(location_key)
1269 .cloned()
1270 .unwrap_or_default();
1271 loc_singleton_inputs.sort_by(|a, b| a.0.cmp(&b.0));
1272
1273 let mut input_params: Vec<proc_macro2::TokenStream> = Vec::new();
1274 if !loc_inputs.is_empty() || !loc_singleton_inputs.is_empty() {
1275 let input_struct_ident = syn::Ident::new("EmbeddedInputs", Span::call_site());
1276
1277 let input_generic_idents: Vec<syn::Ident> = loc_inputs
1278 .iter()
1279 .enumerate()
1280 .map(|(i, _)| quote::format_ident!("__In{}", i))
1281 .collect();
1282
1283 let input_marker_defs: Vec<proc_macro2::TokenStream> = loc_inputs
1286 .iter()
1287 .map(|input| {
1288 let guarantees = env.boundary.input(location_key, &input.ident);
1289 marker_def(
1290 &root,
1291 &input.ident,
1292 &format!(
1293 "The `{}` stream input.\n\n{}",
1294 input.ident,
1295 promise_doc(&guarantees, "EmbeddedInputChannel")
1296 ),
1297 &guarantees,
1298 )
1299 })
1300 .collect();
1301
1302 let struct_fields: Vec<proc_macro2::TokenStream> = loc_inputs
1303 .iter()
1304 .zip(input_generic_idents.iter())
1305 .map(|(input, generic)| {
1306 let ident = &input.ident;
1307 let doc =
1308 format!("The `{ident}` stream input (marker type: [`inputs::{ident}`]).");
1309 quote! {
1310 #[doc = #doc]
1311 pub #ident: #root::compile::embedded_runtime::EmbeddedInputChannel<inputs::#ident, #generic>
1312 }
1313 })
1314 .chain(
1315 loc_singleton_inputs
1316 .iter()
1317 .map(|(ident, element_type)| quote! { pub #ident: #element_type }),
1318 )
1319 .collect();
1320
1321 let struct_generics: Vec<proc_macro2::TokenStream> = loc_inputs
1322 .iter()
1323 .zip(input_generic_idents.iter())
1324 .map(|(input, generic)| {
1325 let element_type = &input.element_type;
1326 quote! { #generic: __root_dfir_rs::futures::Stream<Item = #element_type> + Unpin }
1327 })
1328 .collect();
1329
1330 for (input, generic) in loc_inputs.iter().zip(input_generic_idents.iter()) {
1331 let element_type = &input.element_type;
1332 extra_fn_generics.push(
1333 quote! { #generic: __root_dfir_rs::futures::Stream<Item = #element_type> + Unpin + 'a },
1334 );
1335 }
1336
1337 input_params.push(quote! {
1338 __inputs: #fn_ident::#input_struct_ident<#(#input_generic_idents),*>
1339 });
1340
1341 for input in &loc_inputs {
1342 let ident = &input.ident;
1343 extra_destructure.push(quote! { let #ident = __inputs.#ident.into_inner(); });
1344 }
1345 for (ident, _) in &loc_singleton_inputs {
1346 extra_destructure.push(quote! { let #ident = __inputs.#ident; });
1347 }
1348
1349 let input_markers_mod = (!input_marker_defs.is_empty()).then(|| {
1350 quote! {
1351 pub mod inputs {
1354 use super::*;
1355 #(#input_marker_defs)*
1356 }
1357 }
1358 });
1359 mod_items.push(quote! {
1360 pub struct #input_struct_ident<#(#struct_generics),*> {
1361 #(#struct_fields),*
1362 }
1363
1364 #input_markers_mod
1365 });
1366 }
1367
1368 if !loc_outputs.is_empty() {
1371 let output_struct_ident = syn::Ident::new("EmbeddedOutputs", Span::call_site());
1372
1373 let mut struct_generics: Vec<proc_macro2::TokenStream> = Vec::new();
1374 let mut struct_fields: Vec<proc_macro2::TokenStream> = Vec::new();
1375 let mut generics: Vec<syn::Ident> = Vec::new();
1376 let mut marker_defs: Vec<proc_macro2::TokenStream> = Vec::new();
1377 for (i, output) in loc_outputs.iter().enumerate() {
1378 let ident = &output.ident;
1379 let element_type = &output.element_type;
1380 let generic = quote::format_ident!("__Out{}", i);
1381 let guarantees = env.boundary.output(location_key, ident);
1382 marker_defs.push(marker_def(
1383 &root,
1384 ident,
1385 &format!("The `{ident}` output.\n\n{}", acknowledge_doc(&guarantees)),
1386 &guarantees,
1387 ));
1388 let doc = format!("The `{ident}` output (marker type: [`outputs::{ident}`]).");
1389 let sink_bound = quote! { FnMut(#element_type) };
1390 struct_generics.push(quote! { #generic: #sink_bound });
1391 extra_fn_generics.push(quote! { #generic: #sink_bound + 'a });
1392 struct_fields.push(quote! {
1393 #[doc = #doc]
1394 pub #ident: #root::compile::embedded_runtime::EmbeddedOutputChannel<outputs::#ident, #generic>
1395 });
1396 generics.push(generic);
1397 extra_destructure
1398 .push(quote! { let mut #ident = __outputs.#ident.into_inner(); });
1399 }
1400
1401 output_params.push(quote! {
1402 __outputs: #fn_ident::#output_struct_ident<#(#generics),*>
1403 });
1404
1405 mod_items.push(quote! {
1406 pub struct #output_struct_ident<#(#struct_generics),*> {
1407 #(#struct_fields),*
1408 }
1409
1410 pub mod outputs {
1412 use super::*;
1413 #(#marker_defs)*
1414 }
1415 });
1416 }
1417
1418 if let Some(mut ports) = env.network_outputs.remove(location_key) {
1420 ports.sort_by(|a, b| a.name.cmp(&b.name));
1421 let channels = NetworkPortTokens::new(
1422 &ports,
1423 &env.boundary,
1424 &fn_names,
1425 "__NetOut",
1426 true,
1427 |port| {
1428 let payload = if let Some(ty) = &port.external_type {
1429 quote! { #ty }
1430 } else {
1431 quote! { #root::runtime_support::dfir_rs::bytes::Bytes }
1432 };
1433 if port.is_tagged {
1434 quote! { (#root::location::member_id::TaglessMemberId, #payload) }
1435 } else {
1436 payload
1437 }
1438 },
1439 );
1440 let NetworkPortTokens {
1441 fields,
1442 field_docs,
1443 field_types,
1444 generics,
1445 items,
1446 marker_defs,
1447 } = &channels;
1448
1449 for (generic, item) in generics.iter().zip(items) {
1450 extra_fn_generics.push(quote! { #generic: FnMut(#item) + 'a });
1451 }
1452
1453 net_out_params.push(quote! {
1454 __network_out: #fn_ident::EmbeddedNetworkOut<#(#generics),*>
1455 });
1456
1457 for (port, field) in ports.iter().zip(fields) {
1458 let var_ident =
1459 syn::Ident::new(&format!("__network_out_{}", port.name), Span::call_site());
1460 extra_destructure
1461 .push(quote! { let mut #var_ident = __network_out.#field.into_inner(); });
1462 }
1463
1464 mod_items.push(quote! {
1465 pub struct EmbeddedNetworkOut<#(#generics: FnMut(#items)),*> {
1466 #(#[doc = #field_docs] pub #fields: #field_types),*
1467 }
1468
1469 pub mod outgoing {
1472 use super::*;
1473 #(#marker_defs)*
1474 }
1475 });
1476 }
1477
1478 if let Some(mut ports) = env.network_inputs.remove(location_key) {
1480 ports.sort_by(|a, b| a.name.cmp(&b.name));
1481 let channels = NetworkPortTokens::new(
1482 &ports,
1483 &env.boundary,
1484 &fn_names,
1485 "__NetIn",
1486 false,
1487 |port| {
1488 match (&port.external_type, port.is_tagged) {
1491 (Some(ty), true) => {
1492 quote! { (#root::location::member_id::TaglessMemberId, #ty) }
1493 }
1494 (Some(ty), false) => quote! { #ty },
1495 (None, true) => {
1496 quote! { Result<(#root::location::member_id::TaglessMemberId, __root_dfir_rs::bytes::BytesMut), std::io::Error> }
1497 }
1498 (None, false) => {
1499 quote! { Result<__root_dfir_rs::bytes::BytesMut, std::io::Error> }
1500 }
1501 }
1502 },
1503 );
1504 let NetworkPortTokens {
1505 fields,
1506 field_docs,
1507 field_types,
1508 generics,
1509 items,
1510 marker_defs,
1511 } = &channels;
1512
1513 for (generic, item) in generics.iter().zip(items) {
1514 extra_fn_generics.push(
1515 quote! { #generic: __root_dfir_rs::futures::Stream<Item = #item> + Unpin + 'a },
1516 );
1517 }
1518
1519 net_in_params.push(quote! {
1520 __network_in: #fn_ident::EmbeddedNetworkIn<#(#generics),*>
1521 });
1522
1523 for (port, field) in ports.iter().zip(fields) {
1524 let var_ident =
1525 syn::Ident::new(&format!("__network_in_{}", port.name), Span::call_site());
1526 extra_destructure
1527 .push(quote! { let #var_ident = __network_in.#field.into_inner(); });
1528 }
1529
1530 mod_items.push(quote! {
1531 pub struct EmbeddedNetworkIn<#(#generics: __root_dfir_rs::futures::Stream<Item = #items> + Unpin),*> {
1532 #(#[doc = #field_docs] pub #fields: #field_types),*
1533 }
1534
1535 pub mod incoming {
1538 use super::*;
1539 #(#marker_defs)*
1540 }
1541 });
1542 }
1543
1544 let output_mod: syn::Item = syn::parse_quote! {
1546 pub mod #fn_ident {
1547 use super::*;
1548
1549 #(#mod_items)*
1550 }
1551 };
1552 items.push(output_mod);
1553
1554 let all_params: Vec<proc_macro2::TokenStream> = cluster_params
1556 .into_iter()
1557 .chain(input_params)
1558 .chain(output_params)
1559 .chain(net_in_params)
1560 .chain(net_out_params)
1561 .collect();
1562
1563 let dfir_metrics_ty = crate::compile::dfir_metrics_state_type(&as_code_options, &root);
1564 let ret_type: syn::Type = syn::parse_quote! { #root::runtime_support::dfir_rs::scheduled::context::Dfir<impl #root::runtime_support::dfir_rs::scheduled::context::TickClosure<Metrics = #dfir_metrics_ty> + 'a> };
1565
1566 let func = syn::parse_quote! {
1567 #[allow(unused, non_snake_case, clippy::suspicious_else_formatting)]
1568 pub fn #fn_ident<'a, #(#extra_fn_generics),*>(#(#all_params),*) -> #ret_type {
1569 #(#extra_destructure)*
1570 #dfir_tokens
1571 }
1572 };
1573
1574 items.push(func);
1575 }
1576
1577 syn::parse_quote! {
1578 use #orig_crate_name::__staged::__deps::*;
1579 use #root::prelude::*;
1580 use #root::runtime_support::dfir_rs as __root_dfir_rs;
1581 pub use #orig_crate_name::__staged;
1582
1583 #( #items )*
1584 }
1585 }
1586}