1use std::collections::BTreeMap;
18
19pub mod path {
21 pub const AOT: u8 = 1 << 0;
23 pub const RUN: u8 = 1 << 1;
25 pub const VM: u8 = 1 << 2;
27 pub const JIT: u8 = 1 << 3;
29 pub const CODEGEN: u8 = 1 << 4;
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum MemClass {
36 Neutral,
38 TradesMemForSpeed,
40 SavesMem,
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
50pub enum OptCost {
51 Cheap = 0,
53 Medium = 1,
56 Heavy = 2,
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
68pub enum Tier {
69 T0 = 0,
71 T1 = 1,
73 T2 = 2,
75 T3 = 3,
77}
78
79impl Tier {
80 #[inline]
83 pub fn budget(self) -> Option<OptCost> {
84 match self {
85 Tier::T0 => None,
86 Tier::T1 => Some(OptCost::Cheap),
87 Tier::T2 => Some(OptCost::Medium),
88 Tier::T3 => Some(OptCost::Heavy),
89 }
90 }
91}
92
93#[inline]
103pub fn admits(cfg: &OptimizationConfig, tier: Tier, opt: Opt) -> bool {
104 matches!(tier.budget(), Some(b) if opt.cost() <= b) && cfg.is_on(opt)
105}
106
107pub const OPT_COUNT: usize = Opt::Supercompile as usize + 1;
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq)]
118pub enum TierMode {
119 Tiered,
121 Eager,
123 Baseline,
125}
126
127#[derive(Debug, Clone, Copy, PartialEq, Eq)]
132pub struct TierThresholds {
133 pub t1: u32,
134 pub t2: u32,
135 pub t3: u32,
136}
137
138impl Default for TierThresholds {
139 fn default() -> Self {
140 Self { t1: 8, t2: 32, t3: 100 }
141 }
142}
143
144impl TierThresholds {
145 #[inline]
147 pub fn tier_for(self, count: u32) -> Tier {
148 if count >= self.t3 {
149 Tier::T3
150 } else if count >= self.t2 {
151 Tier::T2
152 } else if count >= self.t1 {
153 Tier::T1
154 } else {
155 Tier::T0
156 }
157 }
158
159 pub fn from_env() -> Self {
162 Self::from_spec(
163 std::env::var("LOGOS_TIER_THRESHOLDS").ok().as_deref(),
164 std::env::var("LOGOS_TIER_T1").ok().as_deref(),
165 std::env::var("LOGOS_TIER_T2").ok().as_deref(),
166 std::env::var("LOGOS_TIER_T3").ok().as_deref(),
167 )
168 }
169
170 pub fn from_spec(
172 combined: Option<&str>,
173 t1: Option<&str>,
174 t2: Option<&str>,
175 t3: Option<&str>,
176 ) -> Self {
177 let mut th = Self::default();
178 if let Some(c) = combined {
179 let parts: Vec<u32> = c.split(',').filter_map(|s| s.trim().parse().ok()).collect();
180 if parts.len() == 3 {
181 th = Self { t1: parts[0], t2: parts[1], t3: parts[2] };
182 }
183 }
184 if let Some(v) = t1.and_then(|s| s.trim().parse().ok()) {
185 th.t1 = v;
186 }
187 if let Some(v) = t2.and_then(|s| s.trim().parse().ok()) {
188 th.t2 = v;
189 }
190 if let Some(v) = t3.and_then(|s| s.trim().parse().ok()) {
191 th.t3 = v;
192 }
193 th
194 }
195}
196
197#[derive(Debug, Clone, Copy, PartialEq, Eq)]
199pub enum Pin {
200 Never,
203 Eager,
205 At(Tier),
207}
208
209#[derive(Debug, Clone, Copy, PartialEq, Eq)]
212pub struct PinSet {
213 pins: [Option<Pin>; OPT_COUNT],
214}
215
216impl Default for PinSet {
217 fn default() -> Self {
218 Self::none()
219 }
220}
221
222impl PinSet {
223 pub const fn none() -> Self {
225 Self { pins: [None; OPT_COUNT] }
226 }
227
228 #[inline]
229 pub fn get(&self, opt: Opt) -> Option<Pin> {
230 self.pins[opt as usize]
231 }
232
233 #[inline]
234 pub fn set(&mut self, opt: Opt, pin: Pin) {
235 self.pins[opt as usize] = Some(pin);
236 }
237
238 pub fn overlay(&mut self, other: &PinSet) {
242 for (slot, pin) in self.pins.iter_mut().zip(other.pins.iter()) {
243 if let Some(p) = pin {
244 *slot = Some(*p);
245 }
246 }
247 }
248}
249
250#[derive(Debug, Clone, Copy, PartialEq, Eq)]
255pub struct HotswapConfig {
256 pub mode: TierMode,
257 pub thresholds: TierThresholds,
258 pub force_tier: Option<Tier>,
261 pub pins: PinSet,
262}
263
264impl Default for HotswapConfig {
265 fn default() -> Self {
266 Self {
269 mode: TierMode::Eager,
270 thresholds: TierThresholds::default(),
271 force_tier: None,
272 pins: PinSet::none(),
273 }
274 }
275}
276
277impl HotswapConfig {
278 #[inline]
281 pub fn effective_tier(&self, count: u32) -> Tier {
282 if let Some(t) = self.force_tier {
283 return t;
284 }
285 match self.mode {
286 TierMode::Baseline => Tier::T0,
287 TierMode::Eager => Tier::T3,
288 TierMode::Tiered => self.thresholds.tier_for(count),
289 }
290 }
291
292 #[inline]
294 pub fn pin(&self, opt: Opt) -> Option<Pin> {
295 self.pins.get(opt)
296 }
297
298 #[inline]
306 pub fn run_tier(&self) -> Tier {
307 if let Some(t) = self.force_tier {
308 return t;
309 }
310 match self.mode {
311 TierMode::Eager => Tier::T3,
312 TierMode::Tiered | TierMode::Baseline => Tier::T0,
313 }
314 }
315
316 pub fn from_env() -> Self {
320 Self::from_spec(
321 std::env::var("LOGOS_HOTSWAP").ok().as_deref(),
322 std::env::var("LOGOS_TIER_PROFILE").ok().as_deref(),
323 std::env::var("LOGOS_FORCE_TIER").ok().as_deref(),
324 TierThresholds::from_env(),
325 std::env::var("LOGOS_TIER_PIN").ok().as_deref(),
326 )
327 }
328
329 pub fn from_spec(
331 hotswap: Option<&str>,
332 profile: Option<&str>,
333 force_tier: Option<&str>,
334 thresholds: TierThresholds,
335 pin_spec: Option<&str>,
336 ) -> Self {
337 let mode = if hotswap == Some("off") {
343 TierMode::Eager
344 } else {
345 match profile {
346 Some("tiered") => TierMode::Tiered,
347 Some("baseline") => TierMode::Baseline,
348 _ => TierMode::Eager,
349 }
350 };
351 let force_tier = force_tier.and_then(parse_tier);
352 let mut pins = PinSet::none();
353 if let Some(spec) = pin_spec {
354 apply_pin_spec(&mut pins, spec);
355 }
356 HotswapConfig { mode, thresholds, force_tier, pins }
357 }
358}
359
360fn parse_tier(s: &str) -> Option<Tier> {
363 match s.trim().to_ascii_lowercase().as_str() {
364 "0" | "t0" | "baseline" => Some(Tier::T0),
365 "1" | "t1" | "warm" => Some(Tier::T1),
366 "2" | "t2" | "hot" => Some(Tier::T2),
367 "3" | "t3" | "veryhot" | "very_hot" => Some(Tier::T3),
368 _ => None,
369 }
370}
371
372pub fn pin_from_str(s: &str) -> Option<Pin> {
375 match s.trim().to_ascii_lowercase().as_str() {
376 "eager" => Some(Pin::Eager),
377 "never" => Some(Pin::Never),
378 other => parse_tier(other).map(Pin::At),
379 }
380}
381
382fn apply_pin_spec(pins: &mut PinSet, spec: &str) {
385 for tok in spec.split([',', ';']) {
386 let tok = tok.trim();
387 if tok.is_empty() {
388 continue;
389 }
390 if let Some((kw, val)) = tok.split_once(':') {
391 if let (Some(opt), Some(pin)) = (by_keyword(kw.trim()), pin_from_str(val)) {
392 pins.set(opt, pin);
393 }
394 }
395 }
396}
397
398#[inline]
402pub fn admits_pinned(cfg: &OptimizationConfig, hs: &HotswapConfig, tier: Tier, opt: Opt) -> bool {
403 if !cfg.is_on(opt) {
404 return false;
405 }
406 match hs.pin(opt) {
407 Some(Pin::Never) => false,
408 Some(Pin::Eager) => true,
409 Some(Pin::At(t)) => tier >= t,
410 None => matches!(tier.budget(), Some(b) if opt.cost() <= b),
411 }
412}
413
414#[derive(Debug, Clone, Copy, PartialEq, Eq)]
416pub enum Scope {
417 ProgramOnly,
419 Both,
421}
422
423#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize)]
428#[repr(u8)]
429pub enum Opt {
430 Memo = 0,
432 Tco = 1,
434 Peephole = 2,
436 Borrow = 3,
438 Specialize = 4,
440 Comptime = 5,
442 Inline = 6,
444 Unfold = 7,
446 Defunctionalize = 8,
448 LoopHoist = 9,
450 Unroll = 10,
452 LoopSplit = 11,
454 ClosedForm = 12,
456 Fuse = 13,
458 LoopCse = 14,
460 Cse = 15,
462 DeadCode = 16,
464 Scalarize = 17,
466 Affine = 18,
468 Interleave = 19,
470 Unbox = 20,
472 HoistBorrows = 21,
474 Narrow = 22,
476 NarrowVm = 23,
478 NarrowMap = 24,
480 DenseMap = 25,
482 ElemType = 26,
484 FastDiv = 27,
486 FloatStrength = 28,
488 Oracle = 29,
490 OracleHints = 30,
492 Unchecked = 31,
494 Simd = 32,
496 Cascade = 33,
498 IndexString = 34,
500 CapScale = 35,
502 Symmetry = 36,
504 Popcount = 37,
506 Saturate = 38,
508 Supercompile = 39,
510}
511
512impl Opt {
513 #[inline]
515 pub const fn bit(self) -> u64 {
516 1u64 << (self as u8)
517 }
518
519 pub fn meta(self) -> &'static OptMeta {
521 ®ISTRY[self as usize]
523 }
524
525 #[inline]
528 pub fn cost(self) -> OptCost {
529 self.meta().cost
530 }
531}
532
533#[derive(Debug, Clone, Copy)]
537pub struct OptMeta {
538 pub opt: Opt,
540 pub keyword: &'static str,
542 pub label: &'static str,
544 pub group: &'static str,
546 pub default_on: bool,
548 pub paths: u8,
550 pub emits_unsafe: bool,
552 pub mem_class: MemClass,
554 pub cost: OptCost,
558 pub requires: &'static [Opt],
561 pub conflicts: &'static [Opt],
564 pub preempts: &'static [Opt],
571 pub scope: Scope,
573}
574
575use path::{AOT, CODEGEN, JIT, RUN, VM};
576use MemClass::{Neutral, SavesMem, TradesMemForSpeed};
577use OptCost::{Cheap, Heavy, Medium};
578use Scope::{Both, ProgramOnly};
579
580pub static REGISTRY: &[OptMeta] = &[
583 OptMeta { opt: Opt::Memo, keyword: "memo", label: "Memoization", group: "Inlining & calls", default_on: true, paths: AOT | CODEGEN, emits_unsafe: false, mem_class: TradesMemForSpeed, cost: Cheap, requires: &[], conflicts: &[], preempts: &[], scope: Both },
584 OptMeta { opt: Opt::Tco, keyword: "tco", label: "Tail-call optimization", group: "Inlining & calls", default_on: true, paths: AOT | RUN | CODEGEN, emits_unsafe: false, mem_class: Neutral, cost: Cheap, requires: &[], conflicts: &[], preempts: &[Opt::Memo], scope: Both },
585 OptMeta { opt: Opt::Peephole, keyword: "peephole", label: "Peephole rewrites", group: "Peephole", default_on: true, paths: AOT | CODEGEN, emits_unsafe: true, mem_class: Neutral, cost: Cheap, requires: &[], conflicts: &[], preempts: &[], scope: Both },
586 OptMeta { opt: Opt::Borrow, keyword: "borrow", label: "Borrow inference", group: "Arrays & memory", default_on: true, paths: AOT | CODEGEN, emits_unsafe: false, mem_class: SavesMem, cost: Cheap, requires: &[], conflicts: &[], preempts: &[Opt::Tco], scope: Both },
590 OptMeta { opt: Opt::Specialize, keyword: "specialize", label: "Partial evaluation", group: "Inlining & calls", default_on: true, paths: AOT | RUN, emits_unsafe: false, mem_class: TradesMemForSpeed, cost: Medium, requires: &[], conflicts: &[], preempts: &[], scope: Both },
591 OptMeta { opt: Opt::Comptime, keyword: "comptime", label: "Compile-time evaluation (CTFE)", group: "Inlining & calls", default_on: true, paths: AOT | RUN, emits_unsafe: false, mem_class: Neutral, cost: Medium, requires: &[], conflicts: &[], preempts: &[Opt::Supercompile], scope: Both },
592 OptMeta { opt: Opt::Inline, keyword: "inline", label: "Function inlining", group: "Inlining & calls", default_on: true, paths: AOT | RUN, emits_unsafe: false, mem_class: TradesMemForSpeed, cost: Cheap, requires: &[], conflicts: &[], preempts: &[], scope: Both },
593 OptMeta { opt: Opt::Unfold, keyword: "unfold", label: "Recursion unrolling", group: "Inlining & calls", default_on: true, paths: AOT | RUN, emits_unsafe: false, mem_class: TradesMemForSpeed, cost: Heavy, requires: &[], conflicts: &[], preempts: &[], scope: Both },
594 OptMeta { opt: Opt::Defunctionalize, keyword: "defunctionalize", label: "Defunctionalization", group: "Inlining & calls", default_on: true, paths: AOT, emits_unsafe: false, mem_class: Neutral, cost: Cheap, requires: &[], conflicts: &[], preempts: &[], scope: Both },
595 OptMeta { opt: Opt::LoopHoist, keyword: "loophoist", label: "Loop-invariant code motion", group: "Loops", default_on: true, paths: AOT | RUN, emits_unsafe: false, mem_class: Neutral, cost: Medium, requires: &[], conflicts: &[], preempts: &[], scope: Both },
596 OptMeta { opt: Opt::Unroll, keyword: "unroll", label: "Loop unrolling", group: "Loops", default_on: true, paths: AOT | RUN, emits_unsafe: false, mem_class: TradesMemForSpeed, cost: Heavy, requires: &[], conflicts: &[], preempts: &[Opt::Interleave], scope: Both },
597 OptMeta { opt: Opt::LoopSplit, keyword: "loopsplit", label: "Loop index-set splitting", group: "Loops", default_on: true, paths: AOT, emits_unsafe: false, mem_class: Neutral, cost: Cheap, requires: &[], conflicts: &[], preempts: &[], scope: Both },
598 OptMeta { opt: Opt::ClosedForm, keyword: "closedform", label: "Closed-form loop recognition", group: "Loops", default_on: true, paths: AOT | RUN, emits_unsafe: false, mem_class: Neutral, cost: Medium, requires: &[], conflicts: &[], preempts: &[Opt::Peephole], scope: Both },
599 OptMeta { opt: Opt::Fuse, keyword: "fuse", label: "Deforestation (stream fusion)", group: "Loops", default_on: true, paths: AOT | RUN, emits_unsafe: false, mem_class: SavesMem, cost: Medium, requires: &[], conflicts: &[], preempts: &[], scope: Both },
600 OptMeta { opt: Opt::LoopCse, keyword: "loopcse", label: "Loop-carried CSE", group: "Loops", default_on: true, paths: AOT | RUN, emits_unsafe: false, mem_class: Neutral, cost: Medium, requires: &[], conflicts: &[], preempts: &[], scope: Both },
601 OptMeta { opt: Opt::Cse, keyword: "cse", label: "Common-subexpression elimination (GVN)", group: "Redundancy", default_on: true, paths: AOT | RUN, emits_unsafe: false, mem_class: Neutral, cost: Medium, requires: &[], conflicts: &[], preempts: &[], scope: Both },
602 OptMeta { opt: Opt::DeadCode, keyword: "deadcode", label: "Dead-code elimination", group: "Redundancy", default_on: true, paths: AOT | RUN, emits_unsafe: false, mem_class: SavesMem, cost: Cheap, requires: &[], conflicts: &[], preempts: &[], scope: Both },
603 OptMeta { opt: Opt::Scalarize, keyword: "scalarize", label: "Array scalarization (SROA)", group: "Arrays & memory", default_on: true, paths: AOT | RUN, emits_unsafe: false, mem_class: TradesMemForSpeed, cost: Heavy, requires: &[Opt::Cse], conflicts: &[], preempts: &[Opt::HoistBorrows, Opt::Interleave, Opt::Unbox], scope: Both },
604 OptMeta { opt: Opt::Affine, keyword: "affine", label: "Affine array → closed form", group: "Arrays & memory", default_on: true, paths: AOT | RUN, emits_unsafe: false, mem_class: SavesMem, cost: Medium, requires: &[Opt::Unbox], conflicts: &[], preempts: &[], scope: Both },
605 OptMeta { opt: Opt::Interleave, keyword: "interleave", label: "Array-of-structs interleaving", group: "Arrays & memory", default_on: true, paths: AOT | CODEGEN, emits_unsafe: false, mem_class: Neutral, cost: Heavy, requires: &[Opt::Scalarize], conflicts: &[], preempts: &[], scope: ProgramOnly },
610 OptMeta { opt: Opt::Unbox, keyword: "unbox", label: "De-Rc (Vec instead of Rc<RefCell>)", group: "Arrays & memory", default_on: true, paths: AOT | CODEGEN, emits_unsafe: false, mem_class: SavesMem, cost: Cheap, requires: &[], conflicts: &[], preempts: &[], scope: ProgramOnly },
611 OptMeta { opt: Opt::HoistBorrows, keyword: "hoistborrows", label: "Borrow hoisting", group: "Arrays & memory", default_on: true, paths: AOT | CODEGEN, emits_unsafe: false, mem_class: Neutral, cost: Cheap, requires: &[], conflicts: &[], preempts: &[], scope: ProgramOnly },
612 OptMeta { opt: Opt::Narrow, keyword: "narrow", label: "i32 sequence narrowing (codegen)", group: "Number representation", default_on: true, paths: AOT | CODEGEN, emits_unsafe: false, mem_class: SavesMem, cost: Cheap, requires: &[Opt::Unbox], conflicts: &[], preempts: &[], scope: ProgramOnly },
613 OptMeta { opt: Opt::NarrowVm, keyword: "narrowvm", label: "i32 sequence narrowing (VM)", group: "Number representation", default_on: true, paths: VM | RUN, emits_unsafe: false, mem_class: SavesMem, cost: Cheap, requires: &[], conflicts: &[], preempts: &[], scope: ProgramOnly },
614 OptMeta { opt: Opt::NarrowMap, keyword: "narrowmap", label: "i32 map-key narrowing", group: "Number representation", default_on: true, paths: AOT | CODEGEN, emits_unsafe: false, mem_class: SavesMem, cost: Cheap, requires: &[], conflicts: &[], preempts: &[], scope: ProgramOnly },
615 OptMeta { opt: Opt::DenseMap, keyword: "densemap", label: "Dense direct-addressed map", group: "Number representation", default_on: true, paths: AOT | CODEGEN, emits_unsafe: false, mem_class: TradesMemForSpeed, cost: Cheap, requires: &[], conflicts: &[], preempts: &[Opt::NarrowMap], scope: ProgramOnly },
616 OptMeta { opt: Opt::ElemType, keyword: "elemtype", label: "Element-type narrowing", group: "Number representation", default_on: true, paths: AOT | RUN, emits_unsafe: false, mem_class: SavesMem, cost: Medium, requires: &[Opt::Oracle], conflicts: &[], preempts: &[], scope: Both },
617 OptMeta { opt: Opt::FastDiv, keyword: "fastdiv", label: "Magic-reciprocal division", group: "Number representation", default_on: true, paths: AOT | VM | CODEGEN, emits_unsafe: false, mem_class: Neutral, cost: Cheap, requires: &[], conflicts: &[], preempts: &[], scope: Both },
618 OptMeta { opt: Opt::FloatStrength, keyword: "floatstrength", label: "Float induction strength reduction", group: "Number representation", default_on: true, paths: AOT | RUN, emits_unsafe: false, mem_class: Neutral, cost: Medium, requires: &[], conflicts: &[], preempts: &[], scope: Both },
619 OptMeta { opt: Opt::Oracle, keyword: "oracle", label: "Abstract interpretation (oracle facts)", group: "Bounds & checks", default_on: true, paths: AOT | RUN, emits_unsafe: false, mem_class: Neutral, cost: Medium, requires: &[], conflicts: &[], preempts: &[], scope: Both },
620 OptMeta { opt: Opt::OracleHints, keyword: "oraclehints", label: "Oracle bounds-check guards", group: "Bounds & checks", default_on: true, paths: AOT | CODEGEN, emits_unsafe: false, mem_class: Neutral, cost: Medium, requires: &[Opt::Oracle], conflicts: &[], preempts: &[], scope: Both },
621 OptMeta { opt: Opt::Unchecked, keyword: "unchecked", label: "Oracle-proven unchecked indexing", group: "Bounds & checks", default_on: true, paths: AOT | JIT | CODEGEN, emits_unsafe: true, mem_class: Neutral, cost: Medium, requires: &[Opt::Oracle], conflicts: &[], preempts: &[], scope: Both },
622 OptMeta { opt: Opt::Simd, keyword: "simd", label: "SIMD search kernels", group: "Strings & SIMD", default_on: true, paths: AOT | CODEGEN, emits_unsafe: true, mem_class: Neutral, cost: Cheap, requires: &[], conflicts: &[], preempts: &[], scope: ProgramOnly },
623 OptMeta { opt: Opt::Cascade, keyword: "cascade", label: "Cascade folding", group: "Strings & SIMD", default_on: true, paths: AOT | CODEGEN, emits_unsafe: false, mem_class: Neutral, cost: Cheap, requires: &[], conflicts: &[], preempts: &[], scope: ProgramOnly },
624 OptMeta { opt: Opt::IndexString, keyword: "indexstring", label: "Indexed string search", group: "Strings & SIMD", default_on: true, paths: AOT | CODEGEN, emits_unsafe: false, mem_class: Neutral, cost: Cheap, requires: &[], conflicts: &[], preempts: &[], scope: ProgramOnly },
625 OptMeta { opt: Opt::CapScale, keyword: "capscale", label: "Capacity-scaling buffer fill", group: "Strings & SIMD", default_on: true, paths: AOT | CODEGEN, emits_unsafe: false, mem_class: Neutral, cost: Cheap, requires: &[], conflicts: &[], preempts: &[], scope: ProgramOnly },
626 OptMeta { opt: Opt::Symmetry, keyword: "symmetry", label: "Symmetry breaking", group: "Search-space", default_on: true, paths: AOT, emits_unsafe: false, mem_class: Neutral, cost: Heavy, requires: &[Opt::Specialize], conflicts: &[], preempts: &[], scope: Both },
627 OptMeta { opt: Opt::Popcount, keyword: "popcount", label: "Popcount leaf collapse", group: "Search-space", default_on: true, paths: AOT, emits_unsafe: false, mem_class: Neutral, cost: Cheap, requires: &[], conflicts: &[], preempts: &[], scope: Both },
628 OptMeta { opt: Opt::Saturate, keyword: "saturate", label: "Equality saturation (e-graph)", group: "Search-space", default_on: true, paths: AOT, emits_unsafe: false, mem_class: Neutral, cost: Heavy, requires: &[], conflicts: &[], preempts: &[], scope: Both },
629 OptMeta { opt: Opt::Supercompile, keyword: "supercompile", label: "Supercompilation", group: "Search-space", default_on: true, paths: AOT, emits_unsafe: false, mem_class: TradesMemForSpeed, cost: Heavy, requires: &[], conflicts: &[], preempts: &[], scope: Both },
630];
631
632pub fn by_keyword(word: &str) -> Option<Opt> {
634 let w = word.to_ascii_lowercase();
635 REGISTRY.iter().find(|m| m.keyword == w).map(|m| m.opt)
636}
637
638pub fn decorate_source(src: &str, disabled_keywords: &[&str]) -> String {
644 if disabled_keywords.is_empty() {
645 return src.to_string();
646 }
647 let decorators: String = disabled_keywords
648 .iter()
649 .map(|kw| format!("## No {kw}\n"))
650 .collect();
651 match src.find("## Main") {
652 Some(idx) => format!("{}{}{}", &src[..idx], decorators, &src[idx..]),
653 None => format!("{src}{decorators}"),
654 }
655}
656
657pub fn decorate_tiers(src: &str, pins: &[(&str, &str)]) -> String {
663 if pins.is_empty() {
664 return src.to_string();
665 }
666 let decorators: String = pins
667 .iter()
668 .map(|(kw, val)| format!("## Tier {kw} {val}\n"))
669 .collect();
670 match src.find("## Main") {
671 Some(idx) => format!("{}{}{}", &src[..idx], decorators, &src[idx..]),
672 None => format!("{src}{decorators}"),
673 }
674}
675
676#[derive(Debug, Clone, Copy, PartialEq, Eq)]
678pub enum Reason {
679 DependencyOff(Opt),
681 ConflictWith(Opt),
683}
684
685#[derive(Debug, Clone, Default, PartialEq, Eq)]
687pub struct NormalizeReport {
688 pub auto_disabled: Vec<(Opt, Reason)>,
690}
691
692impl NormalizeReport {
693 pub fn is_empty(&self) -> bool {
695 self.auto_disabled.is_empty()
696 }
697}
698
699#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
704pub struct OptimizationConfig {
705 enabled: u64,
706}
707
708impl Default for OptimizationConfig {
709 fn default() -> Self {
710 Self::all_on()
711 }
712}
713
714impl OptimizationConfig {
715 pub fn all_on() -> Self {
717 let mut enabled = 0u64;
718 for m in REGISTRY {
719 if m.default_on {
720 enabled |= m.opt.bit();
721 }
722 }
723 Self { enabled }
724 }
725
726 pub const fn all_off() -> Self {
728 Self { enabled: 0 }
729 }
730
731 #[inline]
733 pub const fn is_on(&self, opt: Opt) -> bool {
734 self.enabled & opt.bit() != 0
735 }
736
737 #[inline]
739 pub fn set(&mut self, opt: Opt, on: bool) {
740 if on {
741 self.enabled |= opt.bit();
742 } else {
743 self.enabled &= !opt.bit();
744 }
745 }
746
747 pub fn disable(mut self, opt: Opt) -> Self {
749 self.set(opt, false);
750 self
751 }
752
753 pub fn enable(mut self, opt: Opt) -> Self {
755 self.set(opt, true);
756 self
757 }
758
759 pub fn enable_with_requires(&mut self, opt: Opt) {
766 let mut visited = 0u64;
767 let mut stack = vec![opt];
768 while let Some(o) = stack.pop() {
769 if visited & o.bit() != 0 {
770 continue;
771 }
772 visited |= o.bit();
773 self.set(o, true);
774 for &req in o.meta().requires {
775 stack.push(req);
776 }
777 }
778 }
779
780 pub const fn is_all_off(&self) -> bool {
782 self.enabled == 0
783 }
784
785 pub const fn bits(&self) -> u64 {
788 self.enabled
789 }
790
791 pub const fn merged(&self, func: &OptimizationConfig) -> OptimizationConfig {
794 OptimizationConfig { enabled: self.enabled & func.enabled }
795 }
796
797 pub fn disabled_keywords(&self) -> impl Iterator<Item = &'static str> + '_ {
799 REGISTRY
800 .iter()
801 .filter(move |m| !self.is_on(m.opt))
802 .map(|m| m.keyword)
803 }
804
805 pub fn normalize(&mut self) -> NormalizeReport {
812 let mut report = NormalizeReport::default();
813 if self.is_all_off() {
814 return report;
815 }
816
817 for m in REGISTRY {
819 if !self.is_on(m.opt) {
820 continue;
821 }
822 for &other in m.conflicts {
823 if (other as u8) < (m.opt as u8) && self.is_on(other) {
824 self.set(m.opt, false);
825 report.auto_disabled.push((m.opt, Reason::ConflictWith(other)));
826 break;
827 }
828 }
829 }
830
831 loop {
833 let mut changed = false;
834 for m in REGISTRY {
835 if !self.is_on(m.opt) {
836 continue;
837 }
838 for &req in m.requires {
839 if !self.is_on(req) {
840 self.set(m.opt, false);
841 report.auto_disabled.push((m.opt, Reason::DependencyOff(req)));
842 changed = true;
843 break;
844 }
845 }
846 }
847 if !changed {
848 break;
849 }
850 }
851
852 report
853 }
854
855 pub fn from_env() -> Self {
863 let opt = std::env::var("LOGOS_OPT").ok();
864 let no_opt = std::env::var("LOGOS_NO_OPTIMIZE").ok();
865 let profile = std::env::var("LOGOS_OPT_PROFILE").ok();
866 let off = std::env::var("LOGOS_OPT_OFF").ok();
867 let master_off = opt.as_deref() == Some("off") || no_opt.as_deref() == Some("1");
868 Self::from_spec(master_off, profile.as_deref(), off.as_deref())
869 }
870
871 pub fn from_spec(master_off: bool, profile: Option<&str>, off_list: Option<&str>) -> Self {
876 if master_off {
877 return Self::all_off();
878 }
879
880 let mut cfg = match profile {
881 Some("memory") => Profile::Memory.config(),
882 Some("safety") => Profile::Safety.config(),
883 _ => Profile::Speed.config(),
884 };
885
886 if let Some(list) = off_list {
887 for tok in list.split([',', ' ', ';']) {
888 let tok = tok.trim();
889 if tok.is_empty() {
890 continue;
891 }
892 if let Some(opt) = by_keyword(tok) {
893 cfg.set(opt, false);
894 }
895 }
896 }
897
898 cfg.normalize();
899 cfg
900 }
901
902 pub fn from_toggles(toggles: &BTreeMap<String, bool>) -> (Self, Vec<Opt>) {
905 let mut cfg = Self::all_on();
906 for m in REGISTRY {
907 if let Some(&on) = toggles.get(m.keyword) {
908 cfg.set(m.opt, on);
909 }
910 }
911 let before = cfg.enabled;
912 let report = cfg.normalize();
913 let _ = before;
914 let forced = report.auto_disabled.iter().map(|(opt, _)| *opt).collect();
915 (cfg, forced)
916 }
917}
918
919#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
926pub struct FiredOptimizations {
927 fired: u64,
928}
929
930impl FiredOptimizations {
931 pub const fn new() -> Self {
933 Self { fired: 0 }
934 }
935
936 pub const fn from_bits(fired: u64) -> Self {
938 Self { fired }
939 }
940
941 #[inline]
943 pub fn mark(&mut self, opt: Opt) {
944 self.fired |= opt.bit();
945 }
946
947 #[inline]
949 pub const fn fired(&self, opt: Opt) -> bool {
950 self.fired & opt.bit() != 0
951 }
952
953 pub const fn is_empty(&self) -> bool {
955 self.fired == 0
956 }
957
958 pub const fn bits(&self) -> u64 {
960 self.fired
961 }
962
963 pub fn merge(&mut self, other: &FiredOptimizations) {
965 self.fired |= other.fired;
966 }
967
968 pub fn opts(&self) -> Vec<Opt> {
970 REGISTRY
971 .iter()
972 .filter(|m| self.fired(m.opt))
973 .map(|m| m.opt)
974 .collect()
975 }
976
977 pub fn keywords(&self) -> Vec<&'static str> {
979 REGISTRY
980 .iter()
981 .filter(|m| self.fired(m.opt))
982 .map(|m| m.keyword)
983 .collect()
984 }
985}
986
987#[derive(Debug, Clone, Copy, PartialEq, Eq)]
989pub enum OptRole {
990 Fired,
992 Enabler,
996 Preempted,
1000}
1001
1002#[derive(Debug, Clone)]
1005pub struct OptNode {
1006 pub opt: Opt,
1008 pub depth: usize,
1011 pub role: OptRole,
1013 pub has_children: bool,
1015 pub requires: Vec<Opt>,
1017 pub preempts: Vec<Opt>,
1019 pub preempted_by: Vec<Opt>,
1021 pub depends_on: Vec<Opt>,
1025}
1026
1027pub fn relationship_tree(
1043 fired: &[Opt],
1044 preempted: &[(Opt, Opt)],
1045 dependencies: &[(Opt, Opt)],
1046) -> Vec<OptNode> {
1047 let n = REGISTRY.len();
1048 let idx = |o: Opt| o as usize;
1049
1050 let parents_of = |y: usize| -> Vec<usize> {
1053 let yopt = REGISTRY[y].opt;
1054 let mut ps: Vec<usize> = REGISTRY[y].requires.iter().map(|&r| idx(r)).collect();
1055 for &(dep, on) in dependencies {
1056 if dep == yopt {
1057 ps.push(idx(on));
1058 }
1059 }
1060 ps.sort_unstable();
1061 ps.dedup();
1062 ps
1063 };
1064
1065 let mut want = vec![false; n];
1068 for &o in fired {
1069 want[idx(o)] = true;
1070 }
1071 for &(_, loser) in preempted {
1072 want[idx(loser)] = true;
1073 }
1074 for &(dep, on) in dependencies {
1075 want[idx(dep)] = true;
1076 want[idx(on)] = true;
1077 }
1078 loop {
1079 let mut changed = false;
1080 for i in 0..n {
1081 if want[i] {
1082 for p in parents_of(i) {
1083 if !want[p] {
1084 want[p] = true;
1085 changed = true;
1086 }
1087 }
1088 }
1089 }
1090 if !changed {
1091 break;
1092 }
1093 }
1094
1095 let fired_set: Vec<bool> = {
1099 let mut v = vec![false; n];
1100 for &o in fired {
1101 v[idx(o)] = true;
1102 }
1103 v
1104 };
1105 let loser_set: Vec<bool> = {
1106 let mut v = vec![false; n];
1107 for &(_, l) in preempted {
1108 v[idx(l)] = true;
1109 }
1110 v
1111 };
1112 let role_of = |i: usize| {
1113 if fired_set[i] {
1114 OptRole::Fired
1115 } else if loser_set[i] {
1116 OptRole::Preempted
1117 } else {
1118 OptRole::Enabler
1119 }
1120 };
1121
1122 let parent_in_want = |i: usize| parents_of(i).iter().any(|&p| want[p]);
1126 let mut order: Vec<(usize, usize)> = Vec::new();
1127 let mut visited = vec![false; n];
1128 let roots: Vec<usize> = (0..n).filter(|&i| want[i] && !parent_in_want(i)).collect();
1129 let mut stack: Vec<(usize, usize)> = roots.iter().rev().map(|&i| (i, 0usize)).collect();
1130 while let Some((i, depth)) = stack.pop() {
1131 if visited[i] {
1132 continue;
1133 }
1134 visited[i] = true;
1135 order.push((i, depth));
1136 let kids: Vec<usize> = (0..n)
1137 .filter(|&j| want[j] && !visited[j] && parents_of(j).contains(&i))
1138 .collect();
1139 for &k in kids.iter().rev() {
1140 stack.push((k, depth + 1));
1141 }
1142 }
1143 for i in 0..n {
1146 if want[i] && !visited[i] {
1147 order.push((i, 0));
1148 }
1149 }
1150
1151 order
1153 .into_iter()
1154 .map(|(i, depth)| {
1155 let opt = REGISTRY[i].opt;
1156 let has_children = (0..n).any(|j| want[j] && parents_of(j).contains(&i));
1157 let preempted_by: Vec<Opt> = preempted
1158 .iter()
1159 .filter(|&&(_, l)| l == opt)
1160 .map(|&(w, _)| w)
1161 .collect();
1162 let depends_on: Vec<Opt> = dependencies
1163 .iter()
1164 .filter(|&&(d, _)| d == opt)
1165 .map(|&(_, x)| x)
1166 .collect();
1167 OptNode {
1168 opt,
1169 depth,
1170 role: role_of(i),
1171 has_children,
1172 requires: REGISTRY[i].requires.to_vec(),
1173 preempts: REGISTRY[i].preempts.to_vec(),
1174 preempted_by,
1175 depends_on,
1176 }
1177 })
1178 .collect()
1179}
1180
1181#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1183pub enum Profile {
1184 Speed,
1186 Memory,
1188 Safety,
1190}
1191
1192impl Profile {
1193 pub fn config(self) -> OptimizationConfig {
1195 let mut cfg = OptimizationConfig::all_on();
1196 match self {
1197 Profile::Speed => {}
1198 Profile::Memory => {
1199 for m in REGISTRY {
1200 if m.mem_class == MemClass::TradesMemForSpeed {
1201 cfg.set(m.opt, false);
1202 }
1203 }
1204 }
1205 Profile::Safety => {
1206 for m in REGISTRY {
1207 if m.emits_unsafe {
1208 cfg.set(m.opt, false);
1209 }
1210 }
1211 }
1212 }
1213 cfg.normalize();
1214 cfg
1215 }
1216}
1217
1218#[cfg(test)]
1219mod tests {
1220 use super::*;
1221 use std::collections::HashSet;
1222
1223 #[test]
1224 fn registry_has_one_row_per_opt_in_order() {
1225 for (i, m) in REGISTRY.iter().enumerate() {
1227 assert_eq!(m.opt as usize, i, "registry row {i} out of order: {:?}", m.opt);
1228 }
1229 }
1230
1231 #[test]
1232 fn keywords_are_unique_lowercase_single_tokens() {
1233 let mut seen = HashSet::new();
1234 for m in REGISTRY {
1235 assert!(seen.insert(m.keyword), "duplicate keyword {}", m.keyword);
1236 assert_eq!(m.keyword, m.keyword.to_ascii_lowercase(), "keyword not lowercase: {}", m.keyword);
1237 assert!(!m.keyword.is_empty(), "empty keyword");
1238 assert!(!m.keyword.contains(char::is_whitespace), "keyword has whitespace: {}", m.keyword);
1239 }
1240 }
1241
1242 #[test]
1243 fn requires_and_conflicts_reference_real_variants_no_cycle() {
1244 for m in REGISTRY {
1247 assert!(!m.requires.contains(&m.opt), "{:?} requires itself", m.opt);
1248 assert!(!m.conflicts.contains(&m.opt), "{:?} conflicts with itself", m.opt);
1249 for &r in m.requires {
1250 assert!(!r.meta().requires.contains(&m.opt), "requires cycle: {:?} <-> {:?}", m.opt, r);
1252 }
1253 }
1254 }
1255
1256 #[test]
1257 fn run_path_opt_costs_match_fixture() {
1258 use OptCost::{Cheap, Heavy, Medium};
1262 let expect = |opt: Opt, c: OptCost| assert_eq!(opt.cost(), c, "{opt:?} cost");
1263 for (opt, c) in [
1264 (Opt::Inline, Cheap),
1265 (Opt::Tco, Cheap),
1266 (Opt::DeadCode, Cheap),
1267 (Opt::Specialize, Medium), (Opt::Comptime, Medium),
1269 (Opt::LoopCse, Medium),
1270 (Opt::FloatStrength, Medium),
1271 (Opt::Cse, Medium),
1272 (Opt::Affine, Medium),
1273 (Opt::LoopHoist, Medium),
1274 (Opt::ClosedForm, Medium),
1275 (Opt::Fuse, Medium),
1276 (Opt::Oracle, Medium),
1277 (Opt::ElemType, Medium),
1278 (Opt::Unroll, Heavy),
1279 (Opt::Scalarize, Heavy),
1280 (Opt::Unfold, Heavy),
1281 ] {
1282 expect(opt, c);
1283 }
1284 }
1285
1286 #[test]
1287 fn requires_cost_monotone() {
1288 for m in REGISTRY {
1293 for &r in m.requires {
1294 assert!(
1295 r.cost() <= m.opt.cost(),
1296 "{:?} (cost {:?}) requires {:?} (cost {:?}) — violates cost monotonicity",
1297 m.opt,
1298 m.opt.cost(),
1299 r,
1300 r.cost()
1301 );
1302 }
1303 }
1304 }
1305
1306 #[test]
1307 fn tier_budget_and_admits_truth_table() {
1308 use OptCost::{Cheap, Heavy, Medium};
1309 assert_eq!(Tier::T0.budget(), None);
1310 assert_eq!(Tier::T1.budget(), Some(Cheap));
1311 assert_eq!(Tier::T2.budget(), Some(Medium));
1312 assert_eq!(Tier::T3.budget(), Some(Heavy));
1313
1314 let on = OptimizationConfig::all_on();
1315 assert!(!admits(&on, Tier::T0, Opt::Inline));
1317 assert!(admits(&on, Tier::T1, Opt::Inline)); assert!(!admits(&on, Tier::T1, Opt::Cse)); assert!(!admits(&on, Tier::T1, Opt::Unroll)); assert!(admits(&on, Tier::T2, Opt::Inline));
1323 assert!(admits(&on, Tier::T2, Opt::Cse));
1324 assert!(!admits(&on, Tier::T2, Opt::Unroll));
1325 assert!(admits(&on, Tier::T3, Opt::Inline));
1327 assert!(admits(&on, Tier::T3, Opt::Cse));
1328 assert!(admits(&on, Tier::T3, Opt::Unroll));
1329 let off = OptimizationConfig::all_on().disable(Opt::Inline);
1331 assert!(!admits(&off, Tier::T3, Opt::Inline));
1332 assert!(Tier::T0 < Tier::T1 && Tier::T1 < Tier::T2 && Tier::T2 < Tier::T3);
1334 }
1335
1336 #[test]
1337 fn opt_count_matches_registry() {
1338 assert_eq!(OPT_COUNT, REGISTRY.len());
1339 }
1340
1341 #[test]
1342 fn tier_thresholds_default_and_tier_for() {
1343 let t = TierThresholds::default();
1344 assert_eq!((t.t1, t.t2, t.t3), (8, 32, 100));
1345 assert_eq!(t.tier_for(0), Tier::T0);
1346 assert_eq!(t.tier_for(7), Tier::T0);
1347 assert_eq!(t.tier_for(8), Tier::T1);
1348 assert_eq!(t.tier_for(31), Tier::T1);
1349 assert_eq!(t.tier_for(32), Tier::T2);
1350 assert_eq!(t.tier_for(99), Tier::T2);
1351 assert_eq!(t.tier_for(100), Tier::T3);
1352 assert_eq!(t.tier_for(10_000), Tier::T3);
1353 }
1354
1355 #[test]
1356 fn hotswap_mode_effective_tier() {
1357 let th = TierThresholds::default();
1358 let mk = |mode, force_tier| HotswapConfig {
1359 mode,
1360 thresholds: th,
1361 force_tier,
1362 pins: PinSet::none(),
1363 };
1364 assert_eq!(mk(TierMode::Eager, None).effective_tier(0), Tier::T3);
1366 assert_eq!(mk(TierMode::Baseline, None).effective_tier(10_000), Tier::T0);
1368 let tiered = mk(TierMode::Tiered, None);
1370 assert_eq!(tiered.effective_tier(0), Tier::T0);
1371 assert_eq!(tiered.effective_tier(8), Tier::T1);
1372 assert_eq!(tiered.effective_tier(32), Tier::T2);
1373 assert_eq!(tiered.effective_tier(100), Tier::T3);
1374 let forced = mk(TierMode::Tiered, Some(Tier::T2));
1376 assert_eq!(forced.effective_tier(0), Tier::T2);
1377 assert_eq!(forced.effective_tier(10_000), Tier::T2);
1378 }
1379
1380 #[test]
1381 fn hotswap_from_spec_modes_and_force() {
1382 let th = TierThresholds::default();
1383 assert_eq!(
1385 HotswapConfig::from_spec(Some("off"), Some("baseline"), None, th, None).mode,
1386 TierMode::Eager
1387 );
1388 assert_eq!(HotswapConfig::from_spec(None, Some("eager"), None, th, None).mode, TierMode::Eager);
1390 assert_eq!(HotswapConfig::from_spec(None, Some("baseline"), None, th, None).mode, TierMode::Baseline);
1391 assert_eq!(HotswapConfig::from_spec(None, Some("tiered"), None, th, None).mode, TierMode::Tiered);
1392 assert_eq!(HotswapConfig::from_spec(None, None, None, th, None).mode, TierMode::Eager);
1394 assert_eq!(HotswapConfig::default().mode, TierMode::Eager);
1395 assert_eq!(HotswapConfig::from_spec(None, None, Some("2"), th, None).force_tier, Some(Tier::T2));
1397 assert_eq!(HotswapConfig::from_spec(None, None, Some("veryhot"), th, None).force_tier, Some(Tier::T3));
1398 assert_eq!(HotswapConfig::from_spec(None, None, Some("nonsense"), th, None).force_tier, None);
1399 }
1400
1401 #[test]
1402 fn run_tier_maps_mode_to_upfront_tier() {
1403 let th = TierThresholds::default();
1404 let mk = |mode, force_tier| HotswapConfig { mode, thresholds: th, force_tier, pins: PinSet::none() };
1405 assert_eq!(mk(TierMode::Eager, None).run_tier(), Tier::T3);
1407 assert_eq!(mk(TierMode::Tiered, None).run_tier(), Tier::T0);
1408 assert_eq!(mk(TierMode::Baseline, None).run_tier(), Tier::T0);
1409 assert_eq!(mk(TierMode::Tiered, Some(Tier::T2)).run_tier(), Tier::T2);
1411 assert_eq!(mk(TierMode::Eager, Some(Tier::T1)).run_tier(), Tier::T1);
1412 }
1413
1414 #[test]
1415 fn admits_pinned_overrides_cost_tier() {
1416 let on = OptimizationConfig::all_on();
1417 let mut pins = PinSet::none();
1418 pins.set(Opt::Unfold, Pin::Never);
1419 pins.set(Opt::Fuse, Pin::Eager);
1420 pins.set(Opt::Specialize, Pin::At(Tier::T2));
1421 let hs = HotswapConfig {
1422 mode: TierMode::Tiered,
1423 thresholds: TierThresholds::default(),
1424 force_tier: None,
1425 pins,
1426 };
1427 assert!(!admits_pinned(&on, &hs, Tier::T3, Opt::Unfold));
1429 assert!(admits_pinned(&on, &hs, Tier::T1, Opt::Fuse));
1431 assert!(!admits_pinned(&on, &hs, Tier::T1, Opt::Specialize));
1433 assert!(admits_pinned(&on, &hs, Tier::T2, Opt::Specialize));
1434 assert!(admits_pinned(&on, &hs, Tier::T1, Opt::Inline));
1436 assert!(!admits_pinned(&on, &hs, Tier::T1, Opt::Cse));
1437 let off = OptimizationConfig::all_on().disable(Opt::Fuse);
1439 assert!(!admits_pinned(&off, &hs, Tier::T3, Opt::Fuse));
1440 }
1441
1442 #[test]
1443 fn pin_spec_parses_keyword_value_pairs() {
1444 let mut pins = PinSet::none();
1445 apply_pin_spec(&mut pins, "specialize:eager, fuse:t1 ; unfold:never, bogus:t2, cse:nonsense");
1446 assert_eq!(pins.get(Opt::Specialize), Some(Pin::Eager));
1447 assert_eq!(pins.get(Opt::Fuse), Some(Pin::At(Tier::T1)));
1448 assert_eq!(pins.get(Opt::Unfold), Some(Pin::Never));
1449 assert_eq!(pins.get(Opt::Cse), None, "unparseable value is ignored");
1450 }
1451
1452 #[test]
1453 fn decorate_source_inserts_file_level_decorators() {
1454 let src = "## To f (x: Int) -> Int:\n Return x.\n\n## Main\nShow 1.\n";
1455 let d = decorate_source(src, &["scalarize", "unroll"]);
1456 assert!(d.contains("## No scalarize\n## No unroll\n## Main"), "got:\n{d}");
1457 assert!(d.starts_with("## To f"), "functions stay before the decorators");
1458 assert_eq!(decorate_source(src, &[]), src);
1460 let lib = "## To f (x: Int) -> Int:\n Return x.\n";
1462 assert!(decorate_source(lib, &["cse"]).ends_with("## No cse\n"));
1463 }
1464
1465 #[test]
1466 fn decorate_tiers_inserts_file_level_pins() {
1467 let src = "## To f (x: Int) -> Int:\n Return x.\n\n## Main\nShow 1.\n";
1468 let d = decorate_tiers(src, &[("specialize", "eager"), ("unfold", "never")]);
1469 assert!(
1470 d.contains("## Tier specialize eager\n## Tier unfold never\n## Main"),
1471 "got:\n{d}"
1472 );
1473 assert!(d.starts_with("## To f"), "functions stay before the decorators");
1474 assert_eq!(decorate_tiers(src, &[]), src, "empty list is a no-op");
1475 let lib = "## To f (x: Int) -> Int:\n Return x.\n";
1476 assert!(decorate_tiers(lib, &[("fuse", "t1")]).ends_with("## Tier fuse t1\n"));
1477 }
1478
1479 #[test]
1480 fn by_keyword_round_trips() {
1481 for m in REGISTRY {
1482 assert_eq!(by_keyword(m.keyword), Some(m.opt));
1483 assert_eq!(by_keyword(&m.keyword.to_uppercase()), Some(m.opt));
1484 }
1485 assert_eq!(by_keyword("nonsense"), None);
1486 }
1487
1488 #[test]
1489 fn all_on_enables_everything_all_off_nothing() {
1490 let on = OptimizationConfig::all_on();
1491 let off = OptimizationConfig::all_off();
1492 for m in REGISTRY {
1493 assert!(on.is_on(m.opt), "{:?} should be on", m.opt);
1494 assert!(!off.is_on(m.opt), "{:?} should be off", m.opt);
1495 }
1496 assert!(off.is_all_off());
1497 assert_eq!(OptimizationConfig::default(), on);
1498 }
1499
1500 #[test]
1501 fn serde_round_trips() {
1502 let cfg = OptimizationConfig::all_on().disable(Opt::Scalarize).disable(Opt::Unroll);
1503 let json = serde_json::to_string(&cfg).unwrap();
1504 let back: OptimizationConfig = serde_json::from_str(&json).unwrap();
1505 assert_eq!(cfg, back);
1506 }
1507
1508 #[test]
1509 fn normalize_disables_dependents_and_reports() {
1510 let mut cfg = OptimizationConfig::all_on().disable(Opt::Cse);
1512 let report = cfg.normalize();
1513 assert!(!cfg.is_on(Opt::Scalarize), "Scalarize must follow Cse off");
1514 assert!(report
1515 .auto_disabled
1516 .contains(&(Opt::Scalarize, Reason::DependencyOff(Opt::Cse))));
1517 }
1518
1519 #[test]
1520 fn normalize_oracle_closure() {
1521 let mut cfg = OptimizationConfig::all_on().disable(Opt::Oracle);
1523 cfg.normalize();
1524 assert!(!cfg.is_on(Opt::ElemType));
1525 assert!(!cfg.is_on(Opt::OracleHints));
1526 assert!(!cfg.is_on(Opt::Unchecked));
1527 }
1528
1529 #[test]
1530 fn default_config_is_stable_under_normalize() {
1531 let mut cfg = OptimizationConfig::all_on();
1537 let report = cfg.normalize();
1538 assert!(report.is_empty(), "default config must normalize to a no-op, got {:?}", report);
1539 assert!(cfg.is_on(Opt::Interleave));
1540 assert!(cfg.is_on(Opt::Unroll));
1541 assert!(cfg.is_on(Opt::Scalarize));
1542 assert_eq!(cfg, OptimizationConfig::all_on());
1543 }
1544
1545 #[test]
1546 fn safety_profile_drops_all_unsafe() {
1547 let cfg = Profile::Safety.config();
1548 for m in REGISTRY {
1549 if m.emits_unsafe {
1550 assert!(!cfg.is_on(m.opt), "Safety must disable unsafe-emitting {:?}", m.opt);
1551 }
1552 }
1553 }
1554
1555 #[test]
1556 fn memory_profile_drops_mem_hogs_keeps_savers() {
1557 let cfg = Profile::Memory.config();
1558 assert!(!cfg.is_on(Opt::Unroll));
1559 assert!(!cfg.is_on(Opt::Scalarize));
1560 assert!(cfg.is_on(Opt::Narrow) || !OptimizationConfig::all_on().is_on(Opt::Unbox));
1561 assert!(cfg.is_on(Opt::Unbox), "Memory keeps memory-saving Unbox");
1562 }
1563
1564 #[test]
1565 fn from_toggles_reports_forced_off() {
1566 let mut t = BTreeMap::new();
1567 t.insert("cse".to_string(), false);
1568 let (cfg, forced) = OptimizationConfig::from_toggles(&t);
1569 assert!(!cfg.is_on(Opt::Cse));
1570 assert!(!cfg.is_on(Opt::Scalarize));
1571 assert!(forced.contains(&Opt::Scalarize));
1572 }
1573
1574 #[test]
1575 fn from_spec_master_off_is_all_off() {
1576 assert!(OptimizationConfig::from_spec(true, None, None).is_all_off());
1577 assert!(OptimizationConfig::from_spec(true, Some("safety"), Some("cse")).is_all_off());
1578 }
1579
1580 #[test]
1581 fn from_spec_default_is_all_on() {
1582 assert_eq!(OptimizationConfig::from_spec(false, None, None), OptimizationConfig::all_on());
1583 }
1584
1585 #[test]
1586 fn from_spec_off_list_disables_listed_keywords() {
1587 let cfg = OptimizationConfig::from_spec(false, None, Some("scalarize, unroll;nonsense"));
1588 assert!(!cfg.is_on(Opt::Scalarize));
1589 assert!(!cfg.is_on(Opt::Unroll));
1590 assert!(cfg.is_on(Opt::Memo), "unlisted opts stay on; unknown tokens ignored");
1591 }
1592
1593 #[test]
1594 fn from_spec_off_list_normalizes_dependents() {
1595 let cfg = OptimizationConfig::from_spec(false, None, Some("oracle"));
1597 assert!(!cfg.is_on(Opt::Oracle));
1598 assert!(!cfg.is_on(Opt::Unchecked));
1599 assert!(!cfg.is_on(Opt::OracleHints));
1600 assert!(!cfg.is_on(Opt::ElemType));
1601 }
1602
1603 #[test]
1604 fn from_spec_profiles_drop_the_right_opts() {
1605 let safety = OptimizationConfig::from_spec(false, Some("safety"), None);
1606 for m in REGISTRY {
1607 if m.emits_unsafe {
1608 assert!(!safety.is_on(m.opt), "Safety must drop unsafe-emitting {:?}", m.opt);
1609 }
1610 }
1611 let memory = OptimizationConfig::from_spec(false, Some("memory"), None);
1612 assert!(!memory.is_on(Opt::Unroll));
1613 assert!(memory.is_on(Opt::Unbox), "Memory keeps memory-saving opts");
1614 }
1615
1616 #[test]
1619 fn disabling_any_requirement_cascades_to_dependents() {
1620 for m in REGISTRY {
1625 for &req in m.requires {
1626 let mut cfg = OptimizationConfig::all_on();
1627 cfg.set(req, false);
1628 cfg.normalize();
1629 assert!(
1630 !cfg.is_on(m.opt),
1631 "{:?} requires {:?}; disabling {:?} must cascade {:?} off",
1632 m.opt, req, req, m.opt
1633 );
1634 }
1635 }
1636 }
1637
1638 #[test]
1639 fn enable_with_requires_pulls_ancestors() {
1640 let mut cfg = OptimizationConfig::all_off();
1643 cfg.enable_with_requires(Opt::Interleave);
1644 assert!(cfg.is_on(Opt::Interleave));
1645 assert!(cfg.is_on(Opt::Scalarize), "Interleave needs Scalarize");
1646 assert!(cfg.is_on(Opt::Cse), "…which needs Cse");
1647 assert!(!cfg.is_on(Opt::Unroll), "unrelated opts stay off");
1648 assert!(!cfg.is_on(Opt::Oracle), "unrelated opts stay off");
1649 let report = cfg.normalize();
1650 assert!(report.is_empty(), "an enabled chain must be normalize-stable: {report:?}");
1651 }
1652
1653 fn role_of(tree: &[OptNode], opt: Opt) -> Option<OptRole> {
1656 tree.iter().find(|n| n.opt == opt).map(|n| n.role)
1657 }
1658 fn depth_of(tree: &[OptNode], opt: Opt) -> usize {
1659 tree.iter().find(|n| n.opt == opt).unwrap().depth
1660 }
1661 fn pos_of(tree: &[OptNode], opt: Opt) -> usize {
1662 tree.iter().position(|n| n.opt == opt).unwrap()
1663 }
1664
1665 #[test]
1666 fn relationship_tree_pulls_enabler_parents_for_orphan_children() {
1667 let fired = [Opt::Unchecked, Opt::OracleHints, Opt::ElemType];
1671 let tree = relationship_tree(&fired, &[], &[]);
1672 assert_eq!(role_of(&tree, Opt::Unchecked), Some(OptRole::Fired));
1673 assert_eq!(role_of(&tree, Opt::Oracle), Some(OptRole::Enabler));
1674 assert!(depth_of(&tree, Opt::Oracle) < depth_of(&tree, Opt::Unchecked));
1675 assert!(tree.iter().find(|n| n.opt == Opt::Oracle).unwrap().has_children);
1676 }
1677
1678 #[test]
1679 fn relationship_tree_surfaces_preempted_losers() {
1680 let fired = [Opt::DenseMap];
1684 let preempted = [(Opt::DenseMap, Opt::NarrowMap)];
1685 let tree = relationship_tree(&fired, &preempted, &[]);
1686 let node = tree.iter().find(|n| n.opt == Opt::NarrowMap).expect("narrowmap present");
1687 assert_eq!(node.role, OptRole::Preempted);
1688 assert!(node.preempted_by.contains(&Opt::DenseMap));
1689 assert_eq!(role_of(&tree, Opt::DenseMap), Some(OptRole::Fired));
1690 }
1691
1692 #[test]
1693 fn relationship_tree_nests_requires_chain_by_depth() {
1694 let fired = [Opt::Cse, Opt::Scalarize, Opt::Interleave];
1696 let tree = relationship_tree(&fired, &[], &[]);
1697 assert_eq!(depth_of(&tree, Opt::Cse), 0);
1698 assert_eq!(depth_of(&tree, Opt::Scalarize), 1);
1699 assert_eq!(depth_of(&tree, Opt::Interleave), 2);
1700 assert!(pos_of(&tree, Opt::Cse) < pos_of(&tree, Opt::Scalarize));
1701 assert!(pos_of(&tree, Opt::Scalarize) < pos_of(&tree, Opt::Interleave));
1702 }
1703
1704 #[test]
1705 fn relationship_tree_is_deterministic_and_only_in_play() {
1706 let fired = [Opt::DenseMap, Opt::Unchecked];
1709 let preempted = [(Opt::DenseMap, Opt::NarrowMap)];
1710 let a = relationship_tree(&fired, &preempted, &[]);
1711 let b = relationship_tree(&fired, &preempted, &[]);
1712 let keys = |t: &[OptNode]| {
1713 t.iter().map(|n| (n.opt, n.depth, n.role)).collect::<Vec<_>>()
1714 };
1715 assert_eq!(keys(&a), keys(&b), "derivation must be deterministic");
1716 let in_play: std::collections::BTreeSet<Opt> = a.iter().map(|n| n.opt).collect();
1717 let expected: std::collections::BTreeSet<Opt> =
1718 [Opt::DenseMap, Opt::NarrowMap, Opt::Unchecked, Opt::Oracle].into_iter().collect();
1719 assert_eq!(in_play, expected, "only in-play opts appear");
1720 }
1721
1722 #[test]
1723 fn relationship_tree_nests_by_per_program_dependencies() {
1724 let fired = [Opt::Scalarize, Opt::DeadCode, Opt::Cse];
1728 let deps = [(Opt::DeadCode, Opt::Scalarize)];
1729 let tree = relationship_tree(&fired, &[], &deps);
1730 assert!(depth_of(&tree, Opt::DeadCode) > depth_of(&tree, Opt::Scalarize));
1732 assert!(pos_of(&tree, Opt::Scalarize) < pos_of(&tree, Opt::DeadCode));
1733 let dc = tree.iter().find(|n| n.opt == Opt::DeadCode).unwrap();
1734 assert!(dc.depends_on.contains(&Opt::Scalarize));
1735 assert_eq!(dc.role, OptRole::Fired);
1736 assert!(tree.iter().find(|n| n.opt == Opt::Scalarize).unwrap().has_children);
1738 assert!(depth_of(&tree, Opt::Scalarize) > depth_of(&tree, Opt::Cse));
1740 }
1741}