Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@ cargo run
In about 30 seconds, you have a typed `source → task → sink` graph that prints its
first messages and records `logs/hello-copper.copper`. Start with
`copperconfig.ron`, `src/main.rs`, and `src/tasks.rs`; the generated `justfile`
also provides helpers for logs, CopperLists, graph rendering, and replay.
also provides helpers for logs, CopperLists, topology (`just dag`), the exact
generated process schedule (`just plan`), and replay.

## How Copper Fits Together

Expand Down
237 changes: 59 additions & 178 deletions core/cu29_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,16 @@ use cu29_runtime::config::{
};
use cu29_runtime::curuntime::{
CuExecutionLoop, CuExecutionStep, CuExecutionUnit, CuStepPhase, CuTaskType,
compute_runtime_plan, expand_anytime_steps, find_task_type_for_id,
find_task_type_for_id,
};
use cu29_runtime::planner::{DEFAULT_COPPERLIST_COUNT, PlanEntityKind, assemble_runtime_plan};
use cu29_traits::{CuError, CuResult};
use proc_macro2::{Ident, Span};

mod bundle_resources;
mod resources;
mod utils;

const DEFAULT_CLNB: usize = 2; // We can double buffer for now until we add the parallel copperlist execution support.

#[inline]
fn int2sliceindex(i: u32) -> syn::Index {
syn::Index::from(i as usize)
Expand Down Expand Up @@ -675,11 +674,10 @@ fn build_gen_cumsgs_support(
graph: &CuGraph,
mission_label: Option<&str>,
) -> CuResult<proc_macro2::TokenStream> {
let task_specs = CuTaskSpecSet::from_graph(graph)?;
let channel_usage = collect_bridge_channel_usage(graph);
let mut bridge_specs = build_bridge_specs(cuconfig, graph, &channel_usage);
let (culist_plan, exec_entities, plan_to_original) =
build_execution_plan(graph, &task_specs, &mut bridge_specs).map_err(|e| {
build_execution_plan(cuconfig, graph, &mut bridge_specs).map_err(|e| {
if let Some(mission) = mission_label {
CuError::from(format!(
"Could not compute copperlist plan for mission '{mission}': {e}"
Expand Down Expand Up @@ -1564,7 +1562,7 @@ pub fn copper_runtime(args: TokenStream, input: TokenStream) -> TokenStream {
.logging
.as_ref()
.and_then(|logging| logging.copperlist_count)
.unwrap_or(DEFAULT_CLNB);
.unwrap_or(DEFAULT_COPPERLIST_COUNT);
let copperlist_count_tokens = proc_macro2::Literal::usize_unsuffixed(copperlist_count);
let caller_root = utils::caller_crate_root();
let (git_commit, git_dirty) = detect_git_info(&caller_root);
Expand Down Expand Up @@ -1723,7 +1721,7 @@ pub fn copper_runtime(args: TokenStream, input: TokenStream) -> TokenStream {
let mut culist_bridge_specs =
build_bridge_specs(&copper_config, graph, &culist_channel_usage);
let (culist_plan, culist_exec_entities, culist_plan_to_original) =
match build_execution_plan(graph, &task_specs, &mut culist_bridge_specs) {
match build_execution_plan(&copper_config, graph, &mut culist_bridge_specs) {
Ok(plan) => plan,
Err(e) => return return_error(format!("Could not compute copperlist plan: {e}")),
};
Expand Down Expand Up @@ -6145,6 +6143,7 @@ fn task_output_payload_type(
})
}

#[cfg(test)]
fn synthesized_single_output_msg_name(
task_type: &Type,
task_kind: CuTaskType,
Expand Down Expand Up @@ -7921,187 +7920,71 @@ fn build_bridge_resource_mappings(
}

fn build_execution_plan(
config: &CuConfig,
graph: &CuGraph,
task_specs: &CuTaskSpecSet,
bridge_specs: &mut [BridgeSpec],
) -> CuResult<(
CuExecutionLoop,
Vec<ExecutionEntity>,
HashMap<NodeId, NodeId>,
)> {
let mut plan_graph = CuGraph::default();
let mut exec_entities = Vec::new();
let mut original_to_plan = HashMap::new();
let mut plan_to_original = HashMap::new();
let mut name_to_original = HashMap::new();
let mut channel_nodes = HashMap::new();

for (node_id, node) in graph.get_all_nodes() {
name_to_original.insert(node.get_id(), node_id);
if node.get_flavor() != Flavor::Task {
continue;
}
let plan_node_id = plan_graph.add_node(node.clone())?;
let task_index = task_specs.node_id_to_task_index[node_id as usize]
.expect("Task missing from specifications");
plan_to_original.insert(plan_node_id, node_id);
original_to_plan.insert(node_id, plan_node_id);
if plan_node_id as usize != exec_entities.len() {
panic!("Unexpected node ordering while mirroring tasks in plan graph");
}
exec_entities.push(ExecutionEntity {
kind: ExecutionEntityKind::Task { task_index },
});
}

for (node_id, node) in graph.get_all_nodes() {
if node.get_flavor() != Flavor::Task {
continue;
}
let Some(task_index) = task_specs.node_id_to_task_index[node_id as usize] else {
continue;
};
if !task_specs
.autogenerated_output_flags
.get(task_index)
.copied()
.unwrap_or(false)
{
continue;
}
let plan_node_id = *original_to_plan
.get(&node_id)
.unwrap_or_else(|| panic!("Task '{}' missing from mirrored plan graph", node.get_id()));
let task_kind = task_specs.cutypes[task_index];
let task_type: Type =
parse_str(task_specs.type_names[task_index].as_str()).unwrap_or_else(|err| {
panic!(
"Could not parse task type '{}': {err}",
task_specs.type_names[task_index]
)
});
let msg_type = synthesized_single_output_msg_name(
&task_type,
task_kind,
task_specs.anytime_configs[task_index].is_some(),
);
plan_graph
.get_node_mut(plan_node_id)
.unwrap_or_else(|| panic!("Plan node '{}' missing from mirrored graph", node.get_id()))
.add_nc_output(msg_type.as_str(), usize::MAX);
}

for (bridge_index, spec) in bridge_specs.iter_mut().enumerate() {
for (channel_index, channel_spec) in spec.rx_channels.iter_mut().enumerate() {
let mut node = Node::new(
format!("{}::rx::{}", spec.id, channel_spec.id).as_str(),
"__CuBridgeRxChannel",
);
node.set_flavor(Flavor::Bridge);
let plan_node_id = plan_graph.add_node(node)?;
if plan_node_id as usize != exec_entities.len() {
panic!("Unexpected node ordering while inserting bridge rx channel");
}
channel_spec.plan_node_id = Some(plan_node_id);
exec_entities.push(ExecutionEntity {
kind: ExecutionEntityKind::BridgeRx {
let assembled = assemble_runtime_plan(config, graph)?;
let mut exec_entities = Vec::with_capacity(assembled.entities.len());
for (plan_node_id, entity) in assembled.entities.iter().enumerate() {
let kind = match entity.kind {
PlanEntityKind::Task { task_index, .. } => ExecutionEntityKind::Task { task_index },
PlanEntityKind::BridgeRx {
bridge_config_index,
channel_config_index,
} => {
let bridge_index = bridge_specs
.iter()
.position(|bridge| bridge.config_index == bridge_config_index)
.expect("shared planner returned an unknown bridge");
let channel_index = bridge_specs[bridge_index]
.rx_channels
.iter()
.position(|channel| channel.config_index == channel_config_index)
.expect("shared planner returned an unknown bridge rx channel");
bridge_specs[bridge_index].rx_channels[channel_index].plan_node_id =
Some(plan_node_id as NodeId);
ExecutionEntityKind::BridgeRx {
bridge_index,
channel_index,
},
});
channel_nodes.insert(
BridgeChannelKey {
bridge_id: spec.id.clone(),
channel_id: channel_spec.id.clone(),
direction: BridgeChannelDirection::Rx,
},
plan_node_id,
);
}

for (channel_index, channel_spec) in spec.tx_channels.iter_mut().enumerate() {
let mut node = Node::new(
format!("{}::tx::{}", spec.id, channel_spec.id).as_str(),
"__CuBridgeTxChannel",
);
node.set_flavor(Flavor::Bridge);
let plan_node_id = plan_graph.add_node(node)?;
if plan_node_id as usize != exec_entities.len() {
panic!("Unexpected node ordering while inserting bridge tx channel");
}
channel_spec.plan_node_id = Some(plan_node_id);
exec_entities.push(ExecutionEntity {
kind: ExecutionEntityKind::BridgeTx {
}
}
PlanEntityKind::BridgeTx {
bridge_config_index,
channel_config_index,
} => {
let bridge_index = bridge_specs
.iter()
.position(|bridge| bridge.config_index == bridge_config_index)
.expect("shared planner returned an unknown bridge");
let channel_index = bridge_specs[bridge_index]
.tx_channels
.iter()
.position(|channel| channel.config_index == channel_config_index)
.expect("shared planner returned an unknown bridge tx channel");
bridge_specs[bridge_index].tx_channels[channel_index].plan_node_id =
Some(plan_node_id as NodeId);
ExecutionEntityKind::BridgeTx {
bridge_index,
channel_index,
},
});
channel_nodes.insert(
BridgeChannelKey {
bridge_id: spec.id.clone(),
channel_id: channel_spec.id.clone(),
direction: BridgeChannelDirection::Tx,
},
plan_node_id,
);
}
}

for cnx in graph.edges() {
let src_plan = if let Some(channel) = &cnx.src_channel {
let key = BridgeChannelKey {
bridge_id: cnx.src.clone(),
channel_id: channel.clone(),
direction: BridgeChannelDirection::Rx,
};
*channel_nodes
.get(&key)
.unwrap_or_else(|| panic!("Bridge source {:?} missing from plan graph", key))
} else {
let node_id = name_to_original
.get(&cnx.src)
.copied()
.unwrap_or_else(|| panic!("Unknown source node '{}'", cnx.src));
*original_to_plan
.get(&node_id)
.unwrap_or_else(|| panic!("Source node '{}' missing from plan", cnx.src))
};

let dst_plan = if let Some(channel) = &cnx.dst_channel {
let key = BridgeChannelKey {
bridge_id: cnx.dst.clone(),
channel_id: channel.clone(),
direction: BridgeChannelDirection::Tx,
};
*channel_nodes
.get(&key)
.unwrap_or_else(|| panic!("Bridge destination {:?} missing from plan graph", key))
} else {
let node_id = name_to_original
.get(&cnx.dst)
.copied()
.unwrap_or_else(|| panic!("Unknown destination node '{}'", cnx.dst));
*original_to_plan
.get(&node_id)
.unwrap_or_else(|| panic!("Destination node '{}' missing from plan", cnx.dst))
}
}
};

plan_graph
.connect_ext_with_order(
src_plan,
dst_plan,
&cnx.msg,
cnx.missions.clone(),
None,
None,
cnx.order,
)
.map_err(|e| CuError::from(e.to_string()))?;
exec_entities.push(ExecutionEntity { kind });
}

let mut runtime_plan = compute_runtime_plan(&plan_graph)?;
expand_anytime_steps(&mut runtime_plan)?;
Ok((runtime_plan, exec_entities, plan_to_original))
let plan_to_original = assembled
.plan_to_original
.iter()
.enumerate()
.filter_map(|(plan_node_id, original)| {
original.map(|original| (plan_node_id as NodeId, original))
})
.collect();
Ok((assembled.execution, exec_entities, plan_to_original))
}

fn collect_culist_metadata(
Expand Down Expand Up @@ -10368,12 +10251,10 @@ mod tests {
read_config("tests/config/multi_output_source_non_first_connected_valid.ron")
.expect("failed to read test config");
let graph = config.get_graph(None).expect("missing graph");
let task_specs = CuTaskSpecSet::from_graph(graph).expect("task specs");
let channel_usage = collect_bridge_channel_usage(graph);
let mut bridge_specs = build_bridge_specs(&config, graph, &channel_usage);
let (runtime_plan, exec_entities, plan_to_original) =
build_execution_plan(graph, &task_specs, &mut bridge_specs)
.expect("runtime plan failed");
build_execution_plan(&config, graph, &mut bridge_specs).expect("runtime plan failed");
let output_packs = extract_output_packs(&runtime_plan);
let task_names = collect_task_names(graph);
let (_, node_output_positions) = collect_culist_metadata(
Expand Down
5 changes: 5 additions & 0 deletions core/cu29_runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ name = "cu29-rendercfg"
path = "src/rendercfg.rs"
required-features = ["std"]

[[bin]]
name = "cu29-plan"
path = "src/plan.rs"
required-features = ["std"]

[dependencies]
cu29-clock = { workspace = true }
cu29-intern-strs = { workspace = true, optional = true }
Expand Down
2 changes: 2 additions & 0 deletions core/cu29_runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ pub mod parallel_queue;
#[cfg(all(feature = "std", feature = "parallel-rt"))]
pub mod parallel_rt;
pub mod payload;
#[doc(hidden)]
pub mod planner;
#[cfg(feature = "std")]
pub mod pool;
pub mod reflect;
Expand Down
Loading
Loading