omena_transform_passes/runtime/
planner.rs1use omena_transform_cst::{
8 TRANSFORM_PASS_CATALOG_LEN, TransformDagEdgeV0, TransformLayer, TransformPassContractV0,
9 TransformPassKind, all_transform_pass_kinds, default_transform_dag_edges,
10 default_transform_pass_contracts,
11};
12
13use crate::{
14 TransformPassExecutionStatus, TransformPassPlanV0, TransformPassRegistryEntryV0,
15 TransformPassesBoundarySummaryV0,
16};
17
18pub fn summarize_omena_transform_passes_boundary() -> TransformPassesBoundarySummaryV0 {
19 let registry_entries = default_transform_pass_contracts()
20 .into_iter()
21 .map(registry_entry_for_contract)
22 .collect::<Vec<_>>();
23 let pass_count = registry_entries.len();
24 let semantic_aware_pass_count = registry_entries
25 .iter()
26 .filter(|entry| entry.contract.layer == TransformLayer::SemanticAware)
27 .count();
28 let cascade_aware_pass_count = registry_entries
29 .iter()
30 .filter(|entry| entry.contract.reads_cascade_model)
31 .count();
32
33 TransformPassesBoundarySummaryV0 {
34 schema_version: "0",
35 product: "omena-transform-passes.boundary",
36 registry_entries,
37 dag_edges: default_transform_dag_edges(),
38 pass_count,
39 full_catalog_registered: pass_count == TRANSFORM_PASS_CATALOG_LEN,
40 semantic_aware_pass_count,
41 cascade_aware_pass_count,
42 planner_enforces_dag_edges: true,
43 execution_runtime_ready: true,
44 incremental_execution_runtime_ready: true,
45 implemented_mutation_pass_ids: implemented_mutation_pass_ids(),
46 next_surfaces: Vec::new(),
47 }
48}
49
50pub fn plan_transform_passes(requested: &[TransformPassKind]) -> TransformPassPlanV0 {
51 let requested_pass_ids = requested.iter().map(|pass| pass.id()).collect::<Vec<_>>();
52 let ordered_passes = order_passes_by_dag(requested);
53 let ordered_pass_ids = ordered_passes
54 .iter()
55 .map(|pass| pass.id())
56 .collect::<Vec<_>>();
57 let dag_edges = default_transform_dag_edges();
58 let satisfied_dag_edge_count = dag_edges
59 .iter()
60 .filter(|edge| {
61 edge_applies(edge, &ordered_pass_ids) && edge_is_satisfied(edge, &ordered_pass_ids)
62 })
63 .count();
64 let violated_dag_edge_count = dag_edges
65 .iter()
66 .filter(|edge| {
67 edge_applies(edge, &ordered_pass_ids) && !edge_is_satisfied(edge, &ordered_pass_ids)
68 })
69 .count();
70
71 TransformPassPlanV0 {
72 schema_version: "0",
73 product: "omena-transform-passes.plan",
74 requested_pass_ids,
75 ordered_pass_ids,
76 satisfied_dag_edge_count,
77 violated_dag_edge_count,
78 all_requested_registered: requested.iter().all(pass_is_registered),
79 }
80}
81
82#[cfg(feature = "lawvere-trace")]
83pub fn plan_transform_passes_parallel_lawvere_layers(
84 requested: &[TransformPassKind],
85) -> omena_lawvere::TransformPassParallelPlanV0 {
86 omena_lawvere::plan_transform_pass_parallel_layers_v0(requested)
87}
88
89pub fn implemented_mutation_pass_ids() -> Vec<&'static str> {
90 vec![
91 TransformPassKind::WhitespaceStrip.id(),
92 TransformPassKind::CommentStrip.id(),
93 TransformPassKind::NumberCompression.id(),
94 TransformPassKind::UnitNormalization.id(),
95 TransformPassKind::ColorCompression.id(),
96 TransformPassKind::UrlQuoteStrip.id(),
97 TransformPassKind::StringQuoteNormalize.id(),
98 TransformPassKind::SelectorIsWhereCompression.id(),
99 TransformPassKind::ShorthandCombining.id(),
100 TransformPassKind::RuleDeduplication.id(),
101 TransformPassKind::RuleMerging.id(),
102 TransformPassKind::SelectorMerging.id(),
103 TransformPassKind::EmptyRuleRemoval.id(),
104 TransformPassKind::VendorPrefixing.id(),
105 TransformPassKind::LightDarkLowering.id(),
106 TransformPassKind::ColorMixLowering.id(),
107 TransformPassKind::OklchOklabLowering.id(),
108 TransformPassKind::ColorFunctionLowering.id(),
109 TransformPassKind::LogicalToPhysical.id(),
110 TransformPassKind::NestingUnwrap.id(),
111 TransformPassKind::ScopeFlatten.id(),
112 TransformPassKind::LayerFlatten.id(),
113 TransformPassKind::SupportsStaticEval.id(),
114 TransformPassKind::MediaStaticEval.id(),
115 TransformPassKind::DeadMediaBranchRemoval.id(),
116 TransformPassKind::DeadSupportsBranchRemoval.id(),
117 TransformPassKind::ImportInline.id(),
118 TransformPassKind::ScssModuleEvaluate.id(),
119 TransformPassKind::LessModuleEvaluate.id(),
120 TransformPassKind::ValueResolution.id(),
121 TransformPassKind::StaticVarSubstitution.id(),
122 TransformPassKind::ResolveCssModulesComposes.id(),
123 TransformPassKind::HashCssModuleClassNames.id(),
124 TransformPassKind::TreeShakeClass.id(),
125 TransformPassKind::TreeShakeKeyframes.id(),
126 TransformPassKind::TreeShakeValue.id(),
127 TransformPassKind::TreeShakeCustomProperty.id(),
128 TransformPassKind::DesignTokenRouting.id(),
129 TransformPassKind::CalcReduction.id(),
130 TransformPassKind::PrintCss.id(),
131 ]
132}
133
134fn registry_entry_for_contract(contract: TransformPassContractV0) -> TransformPassRegistryEntryV0 {
135 TransformPassRegistryEntryV0 {
136 module_family: module_family_for_pass(contract.kind),
137 query_family: query_family_for_pass(contract.kind),
138 execution_status: TransformPassExecutionStatus::RegistryAndPlannerReady,
139 contract,
140 }
141}
142
143fn module_family_for_pass(kind: TransformPassKind) -> &'static str {
144 match kind.ordinal() {
145 1..=7 => "commodity-token",
146 8 | 25 => "egg-backed",
147 9..=13 => "cascade-proven-structural",
148 14..=24 => "target-lowering",
149 26..=28 => "module-bundle",
150 29..=32 => "css-modules-resolution",
151 33..=39 => "semantic-reachability",
152 40 => "emission",
153 _ => "unknown",
154 }
155}
156
157fn query_family_for_pass(kind: TransformPassKind) -> &'static str {
158 match kind.layer() {
159 TransformLayer::SemanticAware => "semantic-aware-transform-query",
160 TransformLayer::Commodity => "commodity-transform-query",
161 TransformLayer::Emission => "emission-transform-query",
162 TransformLayer::SemanticReadOnly => "semantic-read-only-query",
163 }
164}
165
166fn order_passes_by_dag(requested: &[TransformPassKind]) -> Vec<TransformPassKind> {
167 let mut remaining = dedupe_requested_passes(requested);
168 remaining.sort_by_key(|kind| (execution_rank(*kind), kind.ordinal()));
169
170 let mut ordered = Vec::with_capacity(remaining.len());
171 while !remaining.is_empty() {
172 let next_index = remaining
173 .iter()
174 .position(|candidate| !has_incoming_edge_from_remaining(*candidate, &remaining))
175 .unwrap_or_default();
176 ordered.push(remaining.remove(next_index));
177 }
178
179 ordered
180}
181
182fn dedupe_requested_passes(requested: &[TransformPassKind]) -> Vec<TransformPassKind> {
183 let mut unique = Vec::new();
184 for pass in requested {
185 if !unique.contains(pass) {
186 unique.push(*pass);
187 }
188 }
189 unique
190}
191
192fn has_incoming_edge_from_remaining(
193 candidate: TransformPassKind,
194 remaining: &[TransformPassKind],
195) -> bool {
196 default_transform_dag_edges().iter().any(|edge| {
197 edge.to == candidate.id()
198 && remaining
199 .iter()
200 .any(|other| other.id() == edge.from && *other != candidate)
201 })
202}
203
204fn edge_applies(edge: &TransformDagEdgeV0, ordered_pass_ids: &[&'static str]) -> bool {
205 ordered_pass_ids.contains(&edge.from) && ordered_pass_ids.contains(&edge.to)
206}
207
208fn edge_is_satisfied(edge: &TransformDagEdgeV0, ordered_pass_ids: &[&'static str]) -> bool {
209 let from = position_of_pass_id(edge.from, ordered_pass_ids);
210 let to = position_of_pass_id(edge.to, ordered_pass_ids);
211 match (from, to) {
212 (Some(from), Some(to)) => from < to,
213 _ => false,
214 }
215}
216
217fn position_of_pass_id(pass_id: &'static str, ordered_pass_ids: &[&'static str]) -> Option<usize> {
218 ordered_pass_ids
219 .iter()
220 .position(|ordered_pass_id| *ordered_pass_id == pass_id)
221}
222
223fn pass_is_registered(pass: &TransformPassKind) -> bool {
224 default_transform_pass_contracts()
225 .iter()
226 .any(|contract| contract.kind == *pass)
227}
228
229pub(crate) fn transform_pass_kind_from_id(pass_id: &str) -> Option<TransformPassKind> {
230 all_transform_pass_kinds()
231 .into_iter()
232 .find(|kind| kind.id() == pass_id)
233}
234
235fn execution_rank(kind: TransformPassKind) -> u8 {
236 match kind.ordinal() {
237 26..=28 => 10,
238 29..=39 => 20,
239 14..=24 => 30,
240 8..=13 | 25 => 40,
241 1..=7 => 50,
242 40 => 60,
243 _ => 70,
244 }
245}