inauguration 0.2.0

.in language and general compiler CLI (Core IR, hybrid SIL, staging, plugins)
Documentation
//! Hybrid compile wave orchestration (inlined for single-crate publish).
//! Source of truth: `compiler/rust-driver/crates/pipeline`.

use crate::hybrid_core::{ChangeEvent, TaskKind};
use crate::hybrid_scheduler::{BuildScheduler, SchedulerError};
use crate::hybrid_sil::{extract_call_graph, parse_textual_sil, remove_debug_insts};
use serde_json::Value;
use std::time::Instant;
use thiserror::Error;

#[derive(Debug, Error)]
pub enum PipelineError {
    #[error(transparent)]
    Scheduler(#[from] SchedulerError),
    #[error("invalid frontend artifact json line: {0}")]
    InvalidFrontendArtifact(#[from] serde_json::Error),
}

#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FrontendArtifactSummary {
    pub structs: usize,
    pub functions: usize,
    pub diagnostics: usize,
    pub success: bool,
    pub core_ir_decls: usize,
    pub parser_id: Option<String>,
}

#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParsedFrontendArtifact {
    pub summary: FrontendArtifactSummary,
    pub textual_sil: Option<String>,
}

fn parser_id_from_value(value: &Value) -> Option<String> {
    ["parser", "frontend"]
        .into_iter()
        .find_map(|key| value.get(key).and_then(Value::as_str).map(str::to_string))
        .or_else(|| {
            value
                .pointer("/pipeline/parser")
                .and_then(Value::as_str)
                .map(str::to_string)
        })
}

fn textual_sil_from_value(value: &Value) -> Option<String> {
    let s = value
        .get("textual_sil")
        .or_else(|| value.pointer("/pipeline/textual_sil"))
        .and_then(Value::as_str)?;
    let trimmed = s.trim();
    if trimmed.is_empty() {
        None
    } else {
        Some(trimmed.to_string())
    }
}

fn icore_or_core_ir_decls(value: &Value) -> Option<&Vec<Value>> {
    value
        .pointer("/core_ir/decls")
        .and_then(Value::as_array)
        .or_else(|| {
            if value.get("icoreVersion").is_some() {
                value.get("decls").and_then(Value::as_array)
            } else {
                None
            }
        })
}

fn symbol_counts_swift_style(value: &Value) -> Option<(usize, usize)> {
    let symbols = value.get("symbols")?;
    if !symbols.is_object() {
        return None;
    }
    let structs = value
        .pointer("/symbols/structs")
        .and_then(Value::as_array)
        .map_or(0, Vec::len);
    let functions = value
        .pointer("/symbols/functions")
        .and_then(Value::as_array)
        .map_or(0, Vec::len);
    Some((structs, functions))
}

fn decl_kind_counts(decls: &[Value]) -> (usize, usize, usize) {
    let mut structs = 0usize;
    let mut functions = 0usize;
    for d in decls {
        match d.get("kind").and_then(Value::as_str) {
            Some("struct") => structs += 1,
            Some("function") => functions += 1,
            _ => {}
        }
    }
    let total = decls.len();
    (structs, functions, total)
}

#[allow(dead_code)]
pub fn parse_frontend_artifact(json: &str) -> Result<ParsedFrontendArtifact, PipelineError> {
    let value: Value = serde_json::from_str(json)?;

    let diagnostics = value
        .get("diagnostics")
        .and_then(Value::as_array)
        .map_or(0, Vec::len);
    let success = value
        .get("success")
        .and_then(Value::as_bool)
        .unwrap_or(false);
    let parser_id = parser_id_from_value(&value);
    let textual_sil = textual_sil_from_value(&value);

    let (structs, functions, core_ir_decls) =
        if let Some((s, f)) = symbol_counts_swift_style(&value) {
            let core_ir_decls = icore_or_core_ir_decls(&value).map_or(0, Vec::len);
            (s, f, core_ir_decls)
        } else if let Some(decls) = icore_or_core_ir_decls(&value) {
            let (s, f, total) = decl_kind_counts(decls);
            (s, f, total)
        } else {
            (0, 0, 0)
        };

    Ok(ParsedFrontendArtifact {
        summary: FrontendArtifactSummary {
            structs,
            functions,
            diagnostics,
            success,
            core_ir_decls,
            parser_id,
        },
        textual_sil,
    })
}

#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct StageTimings {
    pub ast_refresh_us: u64,
    pub swift_frontend_us: u64,
    pub sil_analysis_us: u64,
    pub wave_us: u64,
    pub pipeline_us: u64,
}

#[allow(dead_code)]
pub fn summarize_frontend_artifact(json: &str) -> Result<FrontendArtifactSummary, PipelineError> {
    Ok(parse_frontend_artifact(json)?.summary)
}

#[allow(dead_code)]
pub async fn run_wave_with_timings_from_frontend(
    scheduler: &BuildScheduler,
    event: &ChangeEvent,
    frontend_json: Option<&str>,
    sil_fallback: &str,
) -> Result<(usize, StageTimings), PipelineError> {
    let sil_source = match frontend_json {
        Some(raw) => parse_frontend_artifact(raw)?
            .textual_sil
            .unwrap_or_else(|| sil_fallback.to_string()),
        None => sil_fallback.to_string(),
    };
    run_wave_with_timings(scheduler, event, &sil_source).await
}

#[allow(dead_code)]
pub async fn run_wave(
    scheduler: &BuildScheduler,
    event: &ChangeEvent,
    sil_source: &str,
) -> Result<usize, PipelineError> {
    Ok(run_wave_with_timings(scheduler, event, sil_source).await?.0)
}

pub async fn run_wave_with_timings(
    scheduler: &BuildScheduler,
    event: &ChangeEvent,
    sil_source: &str,
) -> Result<(usize, StageTimings), PipelineError> {
    let wave_start = Instant::now();
    scheduler.enqueue_wave(event).await;
    let mut processed = 0usize;
    let mut timings = StageTimings::default();
    while let Ok(task) = scheduler.next_task().await {
        if scheduler.is_cancelled(&task.cancel_token) {
            continue;
        }
        let stage_start = Instant::now();
        match task.task_kind {
            TaskKind::AstRefresh | TaskKind::SwiftFrontend => {
                processed += 1;
                let elapsed = stage_start.elapsed().as_micros() as u64;
                match task.task_kind {
                    TaskKind::AstRefresh => timings.ast_refresh_us += elapsed,
                    TaskKind::SwiftFrontend => timings.swift_frontend_us += elapsed,
                    TaskKind::SilAnalysis => {}
                }
            }
            TaskKind::SilAnalysis => {
                let artifact = parse_textual_sil(sil_source);
                let _optimized = remove_debug_insts(&artifact);
                let _report = extract_call_graph(&artifact);
                processed += 1;
                timings.sil_analysis_us += stage_start.elapsed().as_micros() as u64;
            }
        }
    }
    timings.wave_us = wave_start.elapsed().as_micros() as u64;
    timings.pipeline_us = timings.wave_us;
    Ok((processed, timings))
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::hybrid_sil::{extract_call_graph, parse_textual_sil};

    #[tokio::test]
    async fn runs_three_task_wave() {
        let scheduler = BuildScheduler::default();
        let (count, timings) = run_wave_with_timings(
            &scheduler,
            &ChangeEvent {
                path: "App.swift".to_string(),
                module_id: "App".to_string(),
                hash: "abc".to_string(),
                timestamp_ms: 7,
            },
            "sil @main\nentry:\n%0 = integer_literal $Builtin.Int64, 1",
        )
        .await
        .expect("pipeline runs");
        assert_eq!(count, 3);
        assert!(timings.wave_us <= 50_000_000);
        assert_eq!(timings.pipeline_us, timings.wave_us);
    }

    #[test]
    fn summarizes_frontend_artifact_json() {
        let summary = summarize_frontend_artifact(
            r#"{
  "format_version": 1,
  "module": "App",
  "source_path": "App.swift",
  "symbols": {
    "structs": [{ "name": "User" }],
    "functions": [{ "name": "main" }, { "name": "helper" }]
  },
  "typed_decls": [],
  "diagnostics": [],
  "success": true
}"#,
        )
        .expect("artifact parses");
        assert_eq!(summary.structs, 1);
        assert_eq!(summary.functions, 2);
        assert_eq!(summary.diagnostics, 0);
        assert!(summary.success);
        assert_eq!(summary.core_ir_decls, 0);
        assert_eq!(summary.parser_id, None);
    }

    #[test]
    fn parses_icore_bundle_with_embedded_sil() {
        let parsed = parse_frontend_artifact(
            r#"{
  "icoreVersion": 1,
  "parser": "icore",
  "decls": [
    { "kind": "struct", "name": "S", "fields": [] },
    { "kind": "function", "name": "main", "params": [], "return": "Void", "body": [] }
  ],
  "textual_sil": "sil @main\nbb0:\n%0 = integer_literal $Builtin.Int64, 0\n",
  "success": true
}"#,
        )
        .expect("icore artifact parses");
        assert_eq!(parsed.summary.core_ir_decls, 2);
        assert_eq!(parsed.summary.structs, 1);
        assert_eq!(parsed.summary.functions, 1);
        assert_eq!(parsed.summary.parser_id.as_deref(), Some("icore"));
        let sil = parsed.textual_sil.expect("embedded sil");
        assert!(sil.contains("sil @main"));
        assert!(sil.contains("bb0:"));
    }

    #[test]
    fn textual_sil_nested_under_pipeline() {
        let parsed = parse_frontend_artifact(
            r#"{
  "success": true,
  "pipeline": { "textual_sil": "sil @main\nbb0:\n%0 = function_ref @foo\n", "parser": "bundle" }
}"#,
        )
        .expect("bundle parses");
        assert_eq!(parsed.summary.parser_id.as_deref(), Some("bundle"));
        assert!(parsed.textual_sil.unwrap().contains("function_ref @foo"));
    }

    #[test]
    fn embedded_sil_matches_in_cli_core_ir_shape() {
        let artifact = r#"{"textual_sil":"sil @main\nbb0:\n%0 = function_ref @from_artifact\n"}"#;
        let sil = parse_frontend_artifact(artifact)
            .expect("parse")
            .textual_sil
            .expect("sil");
        let report = extract_call_graph(&parse_textual_sil(&sil));
        assert!(
            report
                .call_edges
                .iter()
                .any(|(_, callee)| callee == "from_artifact")
        );
    }

    #[tokio::test]
    async fn wave_from_frontend_runs_three_tasks() {
        let scheduler = BuildScheduler::default();
        let artifact = r#"{"success":true,"textual_sil":"sil @main\nbb0:\n%0 = integer_literal $Builtin.Int64, 1\n"}"#;
        let (count, timings) = run_wave_with_timings_from_frontend(
            &scheduler,
            &ChangeEvent {
                path: "x.in".into(),
                module_id: "M".into(),
                hash: "h".into(),
                timestamp_ms: 0,
            },
            Some(artifact),
            "",
        )
        .await
        .expect("wave");
        assert_eq!(count, 3);
        assert!(timings.wave_us <= 50_000_000);
        assert_eq!(timings.pipeline_us, timings.wave_us);
    }
}