Skip to main content

vyre_megakernel/
lib.rs

1//! Backend-neutral compilation from validated typed graphs to immutable artifacts.
2//!
3//! # Ownership
4//!
5//! This crate owns the whole-program compile seam:
6//! - input: a validated typed [`ProgramGraph`], immutable [`ExternalFacts`], and
7//!   explicit [`SearchBudget`]
8//! - output: one versioned immutable [`Artifact`] plus optional [`TargetPayload`]
9//!   values in an [`ArtifactEnvelope`]
10//!
11//! Device admission, materialization, submission, queues, residency, and recovery
12//! are consumers of this compiler product and do not alter artifact identity.
13
14#![forbid(unsafe_code)]
15
16mod artifact;
17mod candidate;
18/// Open, reproducible whole-program candidate cost model.
19pub mod cost;
20mod envelope;
21mod facts;
22/// Stable semantic legality decisions for whole-program fusion.
23pub mod legality;
24mod normalize;
25mod search;
26mod select;
27/// Target compiler facets over compiler-selected modules and canonical ABI.
28pub mod target;
29
30pub use envelope::{
31    ArtifactEnvelope, TargetEntryPoint, TargetPayload, TargetPayloadFormat, TargetProfile,
32    TargetResourceAccess, TargetResourceBinding, TargetResourceMemory,
33    ARTIFACT_ENVELOPE_SCHEMA_VERSION, TARGET_PAYLOAD_SCHEMA_VERSION,
34};
35pub use target::{
36    attach_target, compile_selected_modules, EmittedTargetModule, SelectedLowering,
37    TargetCompileError, TargetCompiler, TargetModuleBundle, TargetModuleImage,
38    TARGET_MODULE_BUNDLE_SCHEMA_VERSION,
39};
40
41use std::collections::{BTreeMap, BTreeSet};
42
43use serde::{Deserialize, Serialize};
44use thiserror::Error;
45pub use vyre_foundation::diagnostics::Diagnostic;
46use vyre_foundation::diagnostics::{DiagnosticStage, OpLocation, RetryClass};
47use vyre_foundation::ir::{
48    BufferAccess, DataType, GraphValueId, ProgramGraph, ShapeDim, ValueLifetime,
49};
50use vyre_foundation::validate::{validate_with_options, BackendCapabilities, ValidationOptions};
51
52/// Current canonical artifact schema.
53pub const ARTIFACT_SCHEMA_VERSION: u16 = 4;
54const ARTIFACT_MAGIC: &[u8; 4] = b"VMK0";
55const ARTIFACT_HEADER_BYTES: usize = 10;
56const ARTIFACT_DIGEST_BYTES: usize = 32;
57const ARTIFACT_DIGEST_DOMAIN: &[u8] = b"vyre-megakernel-artifact-v4\0";
58const SOURCE_DIGEST_DOMAIN: &[u8] = b"vyre-megakernel-source-v2\0";
59const REQUEST_DIGEST_DOMAIN: &[u8] = b"vyre-megakernel-request-v2\0";
60const COMPILER_IR_CAPABILITIES: BackendCapabilities = BackendCapabilities {
61    supports_subgroup_ops: true,
62    supports_indirect_dispatch: true,
63    supports_specialization_constants: true,
64    supports_distributed_collectives: true,
65    has_mul_high: true,
66    has_dual_issue_fp32_int32: true,
67    has_tensor_core_int: true,
68    has_native_f16: true,
69    has_warp_shuffle: true,
70    has_shared_memory: true,
71    has_transcendental_polynomial_emit: true,
72    max_native_int_width: u32::MAX,
73};
74
75/// Stable 256-bit content identity.
76#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
77pub struct Digest(pub [u8; 32]);
78
79impl Digest {
80    /// Return the digest bytes.
81    #[must_use]
82    pub const fn as_bytes(&self) -> &[u8; 32] {
83        &self.0
84    }
85}
86
87/// Canonical node identity inside an artifact.
88#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
89pub struct ArtifactNodeId(pub u32);
90
91/// Canonical value identity inside an artifact.
92#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
93pub struct ArtifactValueId(pub u32);
94
95/// Canonical fusion-group identity inside an artifact.
96#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
97pub struct FusionGroupId(pub u32);
98
99/// Dependency endpoint with an explicit identity domain.
100#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
101#[serde(rename_all = "snake_case")]
102pub enum DependencyEndpoint {
103    /// An executable graph node.
104    Node(ArtifactNodeId),
105    /// A typed graph value materialized at a boundary.
106    Value(ArtifactValueId),
107}
108
109/// Semantic reason that one artifact record depends on another.
110#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
111#[serde(rename_all = "snake_case")]
112pub enum DependencyKind {
113    /// A produced value is consumed by another node.
114    Data,
115    /// A retained value is replaced by a type-preserving successor.
116    Retained,
117    /// A value must exist beyond its producing fusion group.
118    Materialization,
119}
120
121/// One canonical typed dependency edge.
122#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
123pub struct DependencyEdge {
124    /// Edge source.
125    pub from: DependencyEndpoint,
126    /// Edge destination.
127    pub to: DependencyEndpoint,
128    /// Stable semantic edge kind.
129    pub kind: DependencyKind,
130    /// Connected value for data, retained, and materialization edges.
131    pub value: Option<ArtifactValueId>,
132}
133
134/// Explicit bounds for one whole-program schedule search.
135#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
136pub struct SearchBudget {
137    /// Maximum legal candidates examined by the open cost model.
138    pub max_candidates: u32,
139    /// Maximum abstract CPU work units consumed by analysis and search.
140    pub max_cpu_work: u64,
141    /// Maximum target compilations used for finalist evaluation.
142    pub max_target_compilations: u32,
143    /// Maximum on-device measurements used for finalist evaluation.
144    pub max_measurements: u32,
145    /// Maximum elapsed search time in nanoseconds.
146    pub max_elapsed_ns: u64,
147}
148
149impl SearchBudget {
150    /// Construct an explicit bounded search budget.
151    #[must_use]
152    pub const fn new(
153        max_candidates: u32,
154        max_cpu_work: u64,
155        max_target_compilations: u32,
156        max_measurements: u32,
157        max_elapsed_ns: u64,
158    ) -> Self {
159        Self {
160            max_candidates,
161            max_cpu_work,
162            max_target_compilations,
163            max_measurements,
164            max_elapsed_ns,
165        }
166    }
167}
168/// Exact bounded work performed while selecting a whole-program plan.
169#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
170pub struct SearchWork {
171    /// Legal candidates scored by the open cost model.
172    pub candidates_explored: u32,
173    /// Abstract deterministic CPU work units consumed.
174    pub cpu_work: u64,
175    /// Target compilations performed for finalist evaluation.
176    pub target_compilations: u32,
177    /// On-device measurements performed for finalist evaluation.
178    pub measurements: u32,
179    /// Deterministic elapsed-budget units charged by the search.
180    pub elapsed_ns: u64,
181}
182
183/// Stable external semantic facts not encoded by graph topology.
184#[derive(Clone, Debug, PartialEq, Eq)]
185pub struct ExternalFacts {
186    /// Digest of validated semantic configuration outside the graph.
187    pub configuration_digest: Digest,
188    /// Exact value for every symbolic graph dimension.
189    pub symbolic_bindings: BTreeMap<String, u64>,
190    /// Verified content identity for every constant graph value.
191    pub constant_identities: BTreeMap<GraphValueId, Digest>,
192}
193
194impl ExternalFacts {
195    /// Construct external facts with no constant identities.
196    #[must_use]
197    pub fn new(configuration_digest: Digest, symbolic_bindings: BTreeMap<String, u64>) -> Self {
198        Self {
199            configuration_digest,
200            symbolic_bindings,
201            constant_identities: BTreeMap::new(),
202        }
203    }
204}
205
206/// Unvalidated whole-program compilation request.
207pub struct CompileRequest {
208    graph: ProgramGraph,
209    facts: ExternalFacts,
210    search_budget: SearchBudget,
211    max_artifact_bytes: u64,
212}
213
214impl CompileRequest {
215    /// Construct a request. Call [`Self::validate`] before compilation.
216    #[must_use]
217    pub const fn new(
218        graph: ProgramGraph,
219        facts: ExternalFacts,
220        search_budget: SearchBudget,
221        max_artifact_bytes: u64,
222    ) -> Self {
223        Self {
224            graph,
225            facts,
226            search_budget,
227            max_artifact_bytes,
228        }
229    }
230
231    /// Validate topology, programs, external facts, and resource bounds.
232    pub fn validate(self) -> Result<ValidatedCompileRequest, CompileError> {
233        if self.max_artifact_bytes == 0 {
234            return Err(failure(
235                CompilerFailureKind::ArtifactLimit,
236                "request.max_artifact_bytes",
237                "artifact byte limit must be greater than zero",
238                "supply a positive bounded artifact byte limit",
239            ));
240        }
241        if self.search_budget.max_candidates == 0
242            || self.search_budget.max_cpu_work == 0
243            || self.search_budget.max_elapsed_ns == 0
244        {
245            return Err(failure(
246                CompilerFailureKind::InvalidSearchBudget,
247                "request.search_budget",
248                "candidate, CPU-work, and elapsed-work bounds must be positive",
249                "supply explicit positive bounds for every mandatory search dimension",
250            ));
251        }
252        self.graph.analyze().map_err(|error| {
253            failure(
254                CompilerFailureKind::InvalidProgram,
255                "request.graph",
256                error.to_string(),
257                "supply a structurally valid acyclic ProgramGraph",
258            )
259        })?;
260        for node in self.graph.nodes() {
261            let report = validate_with_options(
262                &node.program,
263                ValidationOptions::universal().with_backend_capabilities(COMPILER_IR_CAPABILITIES),
264            );
265            if let Some(issue) = report.errors.into_iter().next() {
266                let path = format!("request.graph.nodes[{}].program", node.id.0);
267                let mut diagnostic = issue.diagnostic();
268                if let Some(location) = diagnostic.location.as_mut() {
269                    location.path = Some(path);
270                    location.graph_node = Some(node.id.0);
271                }
272                return Err(CompileError { diagnostic });
273            }
274        }
275        validate_bindings(&self.graph, &self.facts.symbolic_bindings)?;
276        validate_constant_identities(&self.graph, &self.facts.constant_identities)?;
277        Ok(ValidatedCompileRequest {
278            graph: self.graph,
279            facts: self.facts,
280            search_budget: self.search_budget,
281            max_artifact_bytes: self.max_artifact_bytes,
282        })
283    }
284}
285
286/// A graph and complete immutable facts that passed request validation.
287pub struct ValidatedCompileRequest {
288    graph: ProgramGraph,
289    facts: ExternalFacts,
290    search_budget: SearchBudget,
291    max_artifact_bytes: u64,
292}
293
294impl ValidatedCompileRequest {
295    /// Borrow the validated source graph.
296    #[must_use]
297    pub const fn graph(&self) -> &ProgramGraph {
298        &self.graph
299    }
300
301    /// Borrow validated external semantic facts.
302    #[must_use]
303    pub const fn facts(&self) -> &ExternalFacts {
304        &self.facts
305    }
306
307    /// Return the explicit bounded-search policy.
308    #[must_use]
309    pub const fn search_budget(&self) -> SearchBudget {
310        self.search_budget
311    }
312
313    /// Return the maximum accepted artifact byte length.
314    #[must_use]
315    pub const fn max_artifact_bytes(&self) -> u64 {
316        self.max_artifact_bytes
317    }
318}
319
320/// Compiler-internal failure classification projected into the shared diagnostic protocol.
321#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
322pub(crate) enum CompilerFailureKind {
323    /// A program failed structural validation.
324    InvalidProgram,
325    /// A symbolic extent had no exact binding.
326    MissingSymbol,
327    /// A binding was supplied for no graph symbol.
328    UnknownSymbol,
329    /// An ordering constraint made the dependency graph cyclic.
330    DependencyCycle,
331    /// Checked size arithmetic overflowed.
332    ResourceOverflow,
333    /// A value representation has no static packed size.
334    UnsizedResource,
335    /// The canonical artifact exceeded the caller's bound.
336    ArtifactLimit,
337    /// Artifact framing or canonical body data was malformed.
338    MalformedArtifact,
339    /// Artifact schema is not supported by this compiler version.
340    VersionSkew,
341    /// Artifact content identity did not match its body.
342    DigestMismatch,
343    /// Target payload framing or metadata was malformed.
344    MalformedTargetPayload,
345    /// Target payload schema or format version is incompatible.
346    TargetPayloadVersionSkew,
347    /// Target payload content identity did not match its metadata and bytes.
348    TargetPayloadDigestMismatch,
349    /// Target payload metadata names a different neutral artifact record.
350    TargetPayloadAssociationMismatch,
351    /// No attached target payload satisfies the required format identity.
352    IncompatibleTargetPayload,
353    /// Mandatory schedule-search bounds are zero or otherwise invalid.
354    InvalidSearchBudget,
355    /// A constant graph value has no verified content identity.
356    MissingConstantIdentity,
357    /// A constant identity was supplied for a non-constant graph value.
358    UnknownConstantIdentity,
359}
360
361impl CompilerFailureKind {
362    /// Stable ASCII code for logs and serialized evidence.
363    #[must_use]
364    const fn as_str(self) -> &'static str {
365        match self {
366            Self::InvalidProgram => "MKC001_INVALID_PROGRAM",
367            Self::MissingSymbol => "MKC002_MISSING_SYMBOL",
368            Self::UnknownSymbol => "MKC003_UNKNOWN_SYMBOL",
369            Self::DependencyCycle => "MKC010_DEPENDENCY_CYCLE",
370            Self::ResourceOverflow => "MKC011_RESOURCE_OVERFLOW",
371            Self::UnsizedResource => "MKC012_UNSIZED_RESOURCE",
372            Self::ArtifactLimit => "MKC013_ARTIFACT_LIMIT",
373            Self::MalformedArtifact => "MKC014_MALFORMED_ARTIFACT",
374            Self::VersionSkew => "MKC015_VERSION_SKEW",
375            Self::DigestMismatch => "MKC016_DIGEST_MISMATCH",
376            Self::MalformedTargetPayload => "MKC017_MALFORMED_TARGET_PAYLOAD",
377            Self::TargetPayloadVersionSkew => "MKC018_TARGET_PAYLOAD_VERSION_SKEW",
378            Self::TargetPayloadDigestMismatch => "MKC019_TARGET_PAYLOAD_DIGEST_MISMATCH",
379            Self::TargetPayloadAssociationMismatch => "MKC020_TARGET_PAYLOAD_ASSOCIATION_MISMATCH",
380            Self::IncompatibleTargetPayload => "MKC021_INCOMPATIBLE_TARGET_PAYLOAD",
381            Self::InvalidSearchBudget => "MKC022_INVALID_SEARCH_BUDGET",
382            Self::MissingConstantIdentity => "MKC023_MISSING_CONSTANT_IDENTITY",
383            Self::UnknownConstantIdentity => "MKC024_UNKNOWN_CONSTANT_IDENTITY",
384        }
385    }
386}
387
388const fn diagnostic_stage(code: CompilerFailureKind) -> DiagnosticStage {
389    match code {
390        CompilerFailureKind::InvalidProgram
391        | CompilerFailureKind::MissingSymbol
392        | CompilerFailureKind::UnknownSymbol
393        | CompilerFailureKind::InvalidSearchBudget
394        | CompilerFailureKind::MissingConstantIdentity
395        | CompilerFailureKind::UnknownConstantIdentity => DiagnosticStage::Validate,
396        CompilerFailureKind::DependencyCycle => DiagnosticStage::Plan,
397        CompilerFailureKind::ResourceOverflow | CompilerFailureKind::UnsizedResource => {
398            DiagnosticStage::Lower
399        }
400        CompilerFailureKind::ArtifactLimit => DiagnosticStage::Emit,
401        CompilerFailureKind::MalformedArtifact
402        | CompilerFailureKind::VersionSkew
403        | CompilerFailureKind::DigestMismatch
404        | CompilerFailureKind::MalformedTargetPayload
405        | CompilerFailureKind::TargetPayloadVersionSkew
406        | CompilerFailureKind::TargetPayloadDigestMismatch
407        | CompilerFailureKind::TargetPayloadAssociationMismatch
408        | CompilerFailureKind::IncompatibleTargetPayload => DiagnosticStage::Admit,
409    }
410}
411
412const fn diagnostic_retry(code: CompilerFailureKind) -> RetryClass {
413    match code {
414        CompilerFailureKind::VersionSkew
415        | CompilerFailureKind::TargetPayloadVersionSkew
416        | CompilerFailureKind::IncompatibleTargetPayload => RetryClass::RecompileSource,
417        _ => RetryClass::Never,
418    }
419}
420
421/// Compilation or artifact-validation failure.
422#[derive(Clone, Debug, PartialEq, Eq, Error)]
423#[error("{diagnostic}")]
424pub struct CompileError {
425    /// Structured stable diagnostic.
426    pub diagnostic: Diagnostic,
427}
428
429/// Canonical executable-node payload.
430#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
431pub struct NodeRecord {
432    /// Graph node identity preserved from [`ProgramGraph`].
433    pub id: ArtifactNodeId,
434    /// Stable diagnostic name; graph ID assignment never depends on lexical order.
435    pub name: String,
436    /// Canonical versioned program wire bytes.
437    pub program: Vec<u8>,
438}
439
440/// Neutral per-node logical geometry.
441#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
442pub struct GeometryRecord {
443    /// Node whose program declares the geometry.
444    pub node: ArtifactNodeId,
445    /// Program-declared workgroup dimensions.
446    pub workgroup_size: [u32; 3],
447}
448
449/// Graph value lifetime represented in the artifact schema.
450#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
451#[serde(rename_all = "snake_case")]
452pub enum ResourceLifetime {
453    /// Immutable constant input.
454    Constant,
455    /// Temporary value for one submission.
456    Invocation,
457    /// Mutable value retained across submissions.
458    Retained,
459    /// Caller-visible graph output.
460    Output,
461}
462
463/// Canonical resource and liveness fact for one typed graph value.
464#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
465pub struct ResourceRecord {
466    /// Canonical value identity.
467    pub value: ArtifactValueId,
468    /// Stable graph value name.
469    pub name: String,
470    /// Resolved logical element count.
471    pub element_count: u64,
472    /// Canonical packed byte count.
473    pub byte_count: u64,
474    /// Semantic lifetime class.
475    pub lifetime: ResourceLifetime,
476    /// First barrier stage needing the value.
477    pub first_stage: u32,
478    /// Last barrier stage needing the value.
479    pub last_stage: u32,
480}
481
482/// Aggregate checked resource envelope.
483#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
484pub struct ResourceEnvelope {
485    /// Sum of all canonical value byte counts.
486    pub total_bytes: u64,
487    /// Maximum bytes simultaneously live in any artifact stage.
488    pub peak_live_bytes: u64,
489}
490
491/// Canonical neutral resource access.
492#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
493#[serde(rename_all = "snake_case")]
494pub enum AbiAccess {
495    /// Read-only resource.
496    ReadOnly,
497    /// Write-only resource.
498    WriteOnly,
499    /// Read-write resource.
500    ReadWrite,
501    /// Uniform read-only resource.
502    Uniform,
503}
504
505/// One canonical resource slot in the whole-program ABI.
506#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
507pub struct ResourceAbiRecord {
508    /// Dense canonical slot.
509    pub slot: u32,
510    /// Typed graph value occupying this slot.
511    pub value: ArtifactValueId,
512    /// Element representation.
513    pub dtype: DataType,
514    /// Required access.
515    pub access: AbiAccess,
516}
517
518/// One canonical executable entry in the whole-program ABI.
519#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
520pub struct EntryAbiRecord {
521    /// Typed graph node implemented by this entry.
522    pub node: ArtifactNodeId,
523    /// Input value identities in Program buffer order.
524    pub inputs: Vec<ArtifactValueId>,
525    /// Output value identities in Program buffer order.
526    pub outputs: Vec<ArtifactValueId>,
527}
528
529/// Canonical resource and entry ABI projected to every target payload.
530#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
531pub struct ArtifactAbi {
532    /// Dense resource slots.
533    pub resources: Vec<ResourceAbiRecord>,
534    /// Executable entries.
535    pub entries: Vec<EntryAbiRecord>,
536}
537
538/// Canonical compiler-selected fusion group.
539#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
540pub struct FusionRecord {
541    /// Stable group identity.
542    pub id: FusionGroupId,
543    /// Typed group members.
544    pub members: Vec<ArtifactNodeId>,
545    /// Dependency stage selected for this group.
546    pub stage: u32,
547    /// Compiler-derived semantic-legality identities used to form the group.
548    pub legality: Vec<Digest>,
549}
550/// Stable evidence that one proposed fusion was pruned before selection.
551#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
552pub struct FusionRejection {
553    /// Proposed producer.
554    pub from: ArtifactNodeId,
555    /// Proposed consumer.
556    pub to: ArtifactNodeId,
557    /// Connecting value.
558    pub value: ArtifactValueId,
559    /// Stable semantic rejection reason.
560    pub reason: legality::FusionRejectionReason,
561}
562
563/// Dependency-completion boundary between canonical stages.
564#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
565pub struct BarrierRecord {
566    /// Stage that must complete.
567    pub before_stage: u32,
568    /// Stage admitted after completion.
569    pub after_stage: u32,
570    /// Sorted dependency-edge indices requiring the boundary.
571    pub dependencies: Vec<u32>,
572}
573
574/// Reason a typed value crosses a fusion-group boundary.
575#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
576#[serde(rename_all = "snake_case")]
577pub enum MaterializationReason {
578    /// Value is consumed by a different fusion group.
579    CrossGroupUse,
580    /// Value is observable after artifact completion.
581    Output,
582    /// Value is retained for a later submission.
583    Retained,
584}
585
586/// Canonical value materialization fact.
587#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
588pub struct MaterializationRecord {
589    /// Materialized value.
590    pub value: ArtifactValueId,
591    /// Producing fusion group.
592    pub producer: FusionGroupId,
593    /// Earliest stage at which the value exists.
594    pub stage: u32,
595    /// Stable semantic reason.
596    pub reason: MaterializationReason,
597}
598
599/// Immutable compiler-selected whole-program plan.
600#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
601pub struct SelectedPlan {
602    /// Selected fusion groups.
603    pub fusion: Vec<FusionRecord>,
604    /// Required dependency-completion boundaries.
605    pub barriers: Vec<BarrierRecord>,
606    /// Required cross-stage value materializations.
607    pub materializations: Vec<MaterializationRecord>,
608    /// Number of legal candidates examined.
609    pub candidates_explored: u32,
610    /// Search bounds under which this plan was selected.
611    pub search_budget: SearchBudget,
612    /// Exact work charged against the bounded search.
613    pub search_work: SearchWork,
614    /// Open-model cost of the selected plan.
615    pub selection_cost: cost::CostBreakdown,
616    /// Illegal producer-consumer fusions pruned with stable reasons.
617    pub pruned_fusions: Vec<FusionRejection>,
618}
619
620/// Deterministic identities establishing how an artifact was produced.
621#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
622pub struct Provenance {
623    /// Canonical source-graph content identity.
624    pub source_graph: Digest,
625    /// Canonical validated-request identity.
626    pub request: Digest,
627    /// Compiler crate version.
628    pub compiler_version: String,
629}
630
631#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
632#[serde(deny_unknown_fields)]
633struct ArtifactPayload {
634    schema_version: u16,
635    nodes: Vec<NodeRecord>,
636    dependencies: Vec<DependencyEdge>,
637    selected_plan: SelectedPlan,
638    abi: ArtifactAbi,
639    resources: Vec<ResourceRecord>,
640    resource_envelope: ResourceEnvelope,
641    geometry: Vec<GeometryRecord>,
642    provenance: Provenance,
643}
644
645/// Versioned immutable canonical whole-program compiler result.
646#[derive(Clone, Debug, PartialEq, Eq)]
647pub struct Artifact {
648    payload: ArtifactPayload,
649    digest: Digest,
650}
651
652impl Artifact {
653    /// Artifact schema version.
654    #[must_use]
655    pub const fn schema_version(&self) -> u16 {
656        self.payload.schema_version
657    }
658
659    /// Canonical node records.
660    #[must_use]
661    pub fn nodes(&self) -> &[NodeRecord] {
662        &self.payload.nodes
663    }
664
665    /// Canonical typed dependency edges.
666    #[must_use]
667    pub fn dependencies(&self) -> &[DependencyEdge] {
668        &self.payload.dependencies
669    }
670
671    /// Canonical fusion groups.
672    #[must_use]
673    pub fn fusion(&self) -> &[FusionRecord] {
674        &self.payload.selected_plan.fusion
675    }
676
677    /// Canonical barrier boundaries.
678    #[must_use]
679    pub fn barriers(&self) -> &[BarrierRecord] {
680        &self.payload.selected_plan.barriers
681    }
682
683    /// Canonical resource records.
684    #[must_use]
685    pub fn resources(&self) -> &[ResourceRecord] {
686        &self.payload.resources
687    }
688
689    /// Checked aggregate resource envelope.
690    #[must_use]
691    pub const fn resource_envelope(&self) -> ResourceEnvelope {
692        self.payload.resource_envelope
693    }
694
695    /// Program-declared neutral geometry records.
696    #[must_use]
697    pub fn geometry(&self) -> &[GeometryRecord] {
698        &self.payload.geometry
699    }
700
701    /// Canonical materialization records.
702    #[must_use]
703    pub fn materializations(&self) -> &[MaterializationRecord] {
704        &self.payload.selected_plan.materializations
705    }
706
707    /// Compiler-selected plan with recorded search bounds.
708    #[must_use]
709    pub const fn selected_plan(&self) -> &SelectedPlan {
710        &self.payload.selected_plan
711    }
712
713    /// Canonical neutral resource and entry ABI.
714    #[must_use]
715    pub const fn abi(&self) -> &ArtifactAbi {
716        &self.payload.abi
717    }
718
719    /// Deterministic artifact provenance.
720    #[must_use]
721    pub const fn provenance(&self) -> &Provenance {
722        &self.payload.provenance
723    }
724
725    /// Canonical artifact content identity.
726    #[must_use]
727    pub const fn digest(&self) -> Digest {
728        self.digest
729    }
730
731    /// Encode canonical versioned bytes with an authenticated body.
732    pub fn to_bytes(&self) -> Result<Vec<u8>, CompileError> {
733        encode_payload(&self.payload)
734    }
735
736    /// Decode, authenticate, and reject non-canonical or incompatible bytes.
737    pub fn from_bytes(bytes: &[u8]) -> Result<Self, CompileError> {
738        if bytes.len() < ARTIFACT_HEADER_BYTES + ARTIFACT_DIGEST_BYTES {
739            return Err(failure(
740                CompilerFailureKind::MalformedArtifact,
741                "artifact.header",
742                "artifact is shorter than its fixed framing",
743                "supply complete VMK0 bytes",
744            ));
745        }
746        if &bytes[..4] != ARTIFACT_MAGIC {
747            return Err(failure(
748                CompilerFailureKind::MalformedArtifact,
749                "artifact.magic",
750                "artifact magic is not VMK0",
751                "supply canonical megakernel artifact bytes",
752            ));
753        }
754        let version = u16::from_le_bytes([bytes[4], bytes[5]]);
755        if version != ARTIFACT_SCHEMA_VERSION {
756            return Err(failure(
757                CompilerFailureKind::VersionSkew,
758                "artifact.schema_version",
759                format!("schema {version} is unsupported; expected {ARTIFACT_SCHEMA_VERSION}"),
760                "recompile the source graph with this compiler version",
761            ));
762        }
763        let body_len = u32::from_le_bytes(bytes[6..10].try_into().expect("fixed slice")) as usize;
764        let expected_len = ARTIFACT_HEADER_BYTES
765            .checked_add(body_len)
766            .and_then(|len| len.checked_add(ARTIFACT_DIGEST_BYTES))
767            .ok_or_else(|| {
768                failure(
769                    CompilerFailureKind::MalformedArtifact,
770                    "artifact.body_length",
771                    "framed body length overflowed addressable memory",
772                    "supply bounded canonical artifact bytes",
773                )
774            })?;
775        if bytes.len() != expected_len {
776            return Err(failure(
777                CompilerFailureKind::MalformedArtifact,
778                "artifact.body_length",
779                format!(
780                    "framing declares {expected_len} bytes but received {}",
781                    bytes.len()
782                ),
783                "supply exactly one complete canonical artifact",
784            ));
785        }
786        let body = &bytes[ARTIFACT_HEADER_BYTES..ARTIFACT_HEADER_BYTES + body_len];
787        let expected_digest = artifact_digest(version, body);
788        let encoded_digest: [u8; 32] = bytes[ARTIFACT_HEADER_BYTES + body_len..]
789            .try_into()
790            .expect("validated digest length");
791        if expected_digest.0 != encoded_digest {
792            return Err(failure(
793                CompilerFailureKind::DigestMismatch,
794                "artifact.digest",
795                "artifact body does not match its content identity",
796                "discard the corrupted artifact and recompile",
797            ));
798        }
799        let payload: ArtifactPayload = serde_json::from_slice(body).map_err(|error| {
800            failure(
801                CompilerFailureKind::MalformedArtifact,
802                "artifact.body",
803                error.to_string(),
804                "supply a canonical body emitted by this crate",
805            )
806        })?;
807        if payload.schema_version != version {
808            return Err(failure(
809                CompilerFailureKind::VersionSkew,
810                "artifact.body.schema_version",
811                "body schema disagrees with framing schema",
812                "recompile instead of rewriting artifact framing",
813            ));
814        }
815        let canonical = serde_json::to_vec(&payload).map_err(serialization_failure)?;
816        if canonical != body {
817            return Err(failure(
818                CompilerFailureKind::MalformedArtifact,
819                "artifact.body",
820                "artifact body is valid JSON but not canonical JSON",
821                "use the canonical bytes emitted by Artifact::to_bytes",
822            ));
823        }
824        Ok(Self {
825            payload,
826            digest: expected_digest,
827        })
828    }
829}
830
831/// Compile one validated typed graph into a canonical backend-neutral artifact.
832pub fn compile(request: &ValidatedCompileRequest) -> Result<Artifact, CompileError> {
833    let canonical_wire = request.graph.to_wire().map_err(|error| {
834        failure(
835            CompilerFailureKind::InvalidProgram,
836            "request.graph",
837            error.to_string(),
838            "supply a graph representable by the canonical foundation wire format",
839        )
840    })?;
841    let source_graph = domain_digest(SOURCE_DIGEST_DOMAIN, &canonical_wire);
842
843    let nodes = request
844        .graph
845        .nodes()
846        .iter()
847        .map(|node| {
848            let program = node.program.canonical_wire_bytes().map_err(|error| {
849                failure(
850                    CompilerFailureKind::InvalidProgram,
851                    format!("request.graph.nodes[{}].program", node.id.0),
852                    error.to_string(),
853                    "supply canonical-wire-compatible typed IR",
854                )
855            })?;
856            Ok(NodeRecord {
857                id: ArtifactNodeId(node.id.0),
858                name: node.name.clone(),
859                program,
860            })
861        })
862        .collect::<Result<Vec<_>, CompileError>>()?;
863    let geometry = request
864        .graph
865        .nodes()
866        .iter()
867        .map(|node| GeometryRecord {
868            node: ArtifactNodeId(node.id.0),
869            workgroup_size: node.program.workgroup_size,
870        })
871        .collect::<Vec<_>>();
872
873    let normalized = normalize::normalize(&request.graph)?;
874    let dependencies = normalized.dependencies;
875    let artifact::ArtifactPlan {
876        node_groups,
877        stages,
878        selected_plan,
879    } = artifact::plan(&request.graph, &dependencies, request.search_budget)?;
880    let (resources, resource_envelope) = build_resources(
881        &request.graph,
882        &request.facts.symbolic_bindings,
883        &node_groups,
884        &stages,
885    )?;
886    let abi = build_abi(&request.graph)?;
887    let request_bytes =
888        serde_json::to_vec(&RequestIdentity::from(request)).map_err(serialization_failure)?;
889    let provenance = Provenance {
890        source_graph,
891        request: domain_digest(REQUEST_DIGEST_DOMAIN, &request_bytes),
892        compiler_version: env!("CARGO_PKG_VERSION").to_string(),
893    };
894    let payload = ArtifactPayload {
895        schema_version: ARTIFACT_SCHEMA_VERSION,
896        nodes,
897        dependencies,
898        selected_plan,
899        abi,
900        resources,
901        resource_envelope,
902        geometry,
903        provenance,
904    };
905    let bytes = encode_payload(&payload)?;
906    let byte_len = u64::try_from(bytes.len())
907        .map_err(|_| overflow("artifact", "artifact length exceeds u64"))?;
908    if byte_len > request.max_artifact_bytes {
909        return Err(failure(
910            CompilerFailureKind::ArtifactLimit,
911            "artifact",
912            format!(
913                "canonical artifact is {byte_len} bytes; limit is {}",
914                request.max_artifact_bytes
915            ),
916            "raise the explicit artifact bound or reduce the source graph",
917        ));
918    }
919    let digest: [u8; 32] = bytes[bytes.len() - ARTIFACT_DIGEST_BYTES..]
920        .try_into()
921        .expect("encoded digest length");
922    Ok(Artifact {
923        payload,
924        digest: Digest(digest),
925    })
926}
927
928#[derive(Serialize)]
929struct RequestIdentity<'a> {
930    configuration_digest: Digest,
931    symbolic_bindings: &'a BTreeMap<String, u64>,
932    constant_identities: Vec<(u32, Digest)>,
933    search_budget: SearchBudget,
934}
935
936impl<'a> From<&'a ValidatedCompileRequest> for RequestIdentity<'a> {
937    fn from(request: &'a ValidatedCompileRequest) -> Self {
938        Self {
939            configuration_digest: request.facts.configuration_digest,
940            symbolic_bindings: &request.facts.symbolic_bindings,
941            constant_identities: request
942                .facts
943                .constant_identities
944                .iter()
945                .map(|(id, digest)| (id.0, *digest))
946                .collect(),
947            search_budget: request.search_budget,
948        }
949    }
950}
951
952fn validate_bindings(
953    graph: &ProgramGraph,
954    bindings: &BTreeMap<String, u64>,
955) -> Result<(), CompileError> {
956    let symbols: BTreeSet<&str> = graph
957        .values()
958        .iter()
959        .flat_map(|value| &value.contract.shape)
960        .filter_map(|dim| match dim {
961            ShapeDim::Known(_) => None,
962            ShapeDim::Symbol(symbol) => Some(symbol.as_str()),
963        })
964        .collect();
965    if let Some(symbol) = symbols
966        .iter()
967        .find(|symbol| !bindings.contains_key(**symbol))
968    {
969        return Err(failure(
970            CompilerFailureKind::MissingSymbol,
971            format!("request.facts.symbolic_bindings.{symbol}"),
972            "graph symbol has no exact extent",
973            "bind every symbolic graph dimension before compilation",
974        ));
975    }
976    if let Some(symbol) = bindings
977        .keys()
978        .find(|symbol| !symbols.contains(symbol.as_str()))
979    {
980        return Err(failure(
981            CompilerFailureKind::UnknownSymbol,
982            format!("request.facts.symbolic_bindings.{symbol}"),
983            "binding does not occur in the graph",
984            "remove stale bindings or use the graph's exact symbol name",
985        ));
986    }
987    Ok(())
988}
989
990fn validate_constant_identities(
991    graph: &ProgramGraph,
992    identities: &BTreeMap<GraphValueId, Digest>,
993) -> Result<(), CompileError> {
994    let constants = graph
995        .values()
996        .iter()
997        .filter(|value| value.contract.lifetime == ValueLifetime::Constant)
998        .map(|value| value.id)
999        .collect::<BTreeSet<_>>();
1000    if let Some(id) = constants.iter().find(|id| !identities.contains_key(*id)) {
1001        return Err(failure(
1002            CompilerFailureKind::MissingConstantIdentity,
1003            format!("request.facts.constant_identities.{}", id.0),
1004            "constant graph value has no verified content identity",
1005            "supply one digest keyed by the constant GraphValueId",
1006        ));
1007    }
1008    if let Some(id) = identities.keys().find(|id| !constants.contains(id)) {
1009        return Err(failure(
1010            CompilerFailureKind::UnknownConstantIdentity,
1011            format!("request.facts.constant_identities.{}", id.0),
1012            "constant identity names a non-constant or missing graph value",
1013            "remove stale identities and key constant content by GraphValueId",
1014        ));
1015    }
1016    Ok(())
1017}
1018
1019fn build_abi(graph: &ProgramGraph) -> Result<ArtifactAbi, CompileError> {
1020    let resources = graph
1021        .values()
1022        .iter()
1023        .map(|value| {
1024            let access = match value.contract.access.clone() {
1025                BufferAccess::ReadOnly => AbiAccess::ReadOnly,
1026                BufferAccess::WriteOnly => AbiAccess::WriteOnly,
1027                BufferAccess::ReadWrite => AbiAccess::ReadWrite,
1028                BufferAccess::Uniform => AbiAccess::Uniform,
1029                unsupported => {
1030                    return Err(failure(
1031                        CompilerFailureKind::InvalidProgram,
1032                        format!("request.graph.values[{}].contract.access", value.id.0),
1033                        format!("access {unsupported:?} has no artifact ABI representation"),
1034                        "lower workgroup/private resources inside the node Program",
1035                    ))
1036                }
1037            };
1038            Ok(ResourceAbiRecord {
1039                slot: value.id.0,
1040                value: ArtifactValueId(value.id.0),
1041                dtype: value.contract.dtype.clone(),
1042                access,
1043            })
1044        })
1045        .collect::<Result<Vec<_>, CompileError>>()?;
1046    let entries = graph
1047        .nodes()
1048        .iter()
1049        .map(|node| EntryAbiRecord {
1050            node: ArtifactNodeId(node.id.0),
1051            inputs: node
1052                .inputs
1053                .iter()
1054                .map(|input| ArtifactValueId(input.value.0))
1055                .collect(),
1056            outputs: node
1057                .outputs
1058                .iter()
1059                .map(|output| ArtifactValueId(output.0))
1060                .collect(),
1061        })
1062        .collect();
1063    Ok(ArtifactAbi { resources, entries })
1064}
1065
1066fn ensure_node_dag(
1067    count: usize,
1068    dependencies: &[DependencyEdge],
1069    code: CompilerFailureKind,
1070) -> Result<(), CompileError> {
1071    let groups: Vec<_> = (0..count).map(|id| FusionGroupId(id as u32)).collect();
1072    ensure_group_dag(count, dependencies, &groups, code)
1073}
1074
1075fn ensure_group_dag(
1076    count: usize,
1077    dependencies: &[DependencyEdge],
1078    node_groups: &[FusionGroupId],
1079    code: CompilerFailureKind,
1080) -> Result<(), CompileError> {
1081    group_stages_inner(count, dependencies, node_groups)
1082        .map(|_| ())
1083        .map_err(|_| {
1084            failure(
1085                code,
1086                "artifact.dependencies",
1087                "dependency graph contains a cycle",
1088                "remove the cyclic semantic dependency",
1089            )
1090        })
1091}
1092
1093fn group_stages(
1094    count: usize,
1095    dependencies: &[DependencyEdge],
1096    node_groups: &[FusionGroupId],
1097) -> Result<Vec<u32>, CompileError> {
1098    group_stages_inner(count, dependencies, node_groups).map_err(|_| {
1099        failure(
1100            CompilerFailureKind::DependencyCycle,
1101            "artifact.dependencies",
1102            "selected-plan dependency graph contains a cycle",
1103            "fix compiler legality before plan selection",
1104        )
1105    })
1106}
1107
1108fn group_stages_inner(
1109    count: usize,
1110    dependencies: &[DependencyEdge],
1111    node_groups: &[FusionGroupId],
1112) -> Result<Vec<u32>, ()> {
1113    let mut outgoing = vec![BTreeSet::<usize>::new(); count];
1114    let mut indegree = vec![0usize; count];
1115    for edge in dependencies {
1116        let (DependencyEndpoint::Node(from), DependencyEndpoint::Node(to)) = (edge.from, edge.to)
1117        else {
1118            continue;
1119        };
1120        let from = node_groups[from.0 as usize].0 as usize;
1121        let to = node_groups[to.0 as usize].0 as usize;
1122        if from != to && outgoing[from].insert(to) {
1123            indegree[to] += 1;
1124        }
1125    }
1126    let mut ready: BTreeSet<usize> = indegree
1127        .iter()
1128        .enumerate()
1129        .filter_map(|(index, degree)| (*degree == 0).then_some(index))
1130        .collect();
1131    let mut stage = vec![0u32; count];
1132    let mut visited = 0usize;
1133    while let Some(next) = ready.pop_first() {
1134        visited += 1;
1135        for successor in outgoing[next].iter().copied() {
1136            stage[successor] = stage[successor].max(stage[next].checked_add(1).ok_or(())?);
1137            indegree[successor] -= 1;
1138            if indegree[successor] == 0 {
1139                ready.insert(successor);
1140            }
1141        }
1142    }
1143    (visited == count).then_some(stage).ok_or(())
1144}
1145
1146fn build_barriers(
1147    dependencies: &[DependencyEdge],
1148    node_groups: &[FusionGroupId],
1149    stages: &[u32],
1150) -> Result<Vec<BarrierRecord>, CompileError> {
1151    let max_stage = stages.iter().copied().max().unwrap_or(0);
1152    let mut barriers = Vec::new();
1153    for after_stage in 1..=max_stage {
1154        let mut edge_ids = Vec::new();
1155        for (index, edge) in dependencies.iter().enumerate() {
1156            let (DependencyEndpoint::Node(from), DependencyEndpoint::Node(to)) =
1157                (edge.from, edge.to)
1158            else {
1159                continue;
1160            };
1161            let from_stage = stages[node_groups[from.0 as usize].0 as usize];
1162            let to_stage = stages[node_groups[to.0 as usize].0 as usize];
1163            if from_stage < after_stage && to_stage == after_stage {
1164                edge_ids.push(
1165                    u32::try_from(index).map_err(|_| {
1166                        overflow("artifact.dependencies", "edge identity exceeds u32")
1167                    })?,
1168                );
1169            }
1170        }
1171        barriers.push(BarrierRecord {
1172            before_stage: after_stage - 1,
1173            after_stage,
1174            dependencies: edge_ids,
1175        });
1176    }
1177    Ok(barriers)
1178}
1179
1180fn build_materializations(
1181    graph: &ProgramGraph,
1182    node_groups: &[FusionGroupId],
1183    stages: &[u32],
1184) -> Vec<MaterializationRecord> {
1185    let mut records = Vec::new();
1186    for value in graph.values() {
1187        let Some(producer) = value.producer else {
1188            continue;
1189        };
1190        let producer_node = ArtifactNodeId(producer.0);
1191        let producer_group = node_groups[producer_node.0 as usize];
1192        let producer_stage = stages[producer_group.0 as usize];
1193        let cross_group = value.consumers.iter().any(|consumer| {
1194            let consumer_node = ArtifactNodeId(consumer.0);
1195            node_groups[consumer_node.0 as usize] != producer_group
1196        });
1197        let reason = match value.contract.lifetime {
1198            ValueLifetime::Output => Some(MaterializationReason::Output),
1199            ValueLifetime::Retained => Some(MaterializationReason::Retained),
1200            _ if cross_group => Some(MaterializationReason::CrossGroupUse),
1201            _ => None,
1202        };
1203        if let Some(reason) = reason {
1204            records.push(MaterializationRecord {
1205                value: ArtifactValueId(value.id.0),
1206                producer: producer_group,
1207                stage: producer_stage,
1208                reason,
1209            });
1210        }
1211    }
1212    records.sort_by_key(|record| (record.value, record.reason as u8));
1213    records
1214}
1215
1216fn build_resources(
1217    graph: &ProgramGraph,
1218    bindings: &BTreeMap<String, u64>,
1219    node_groups: &[FusionGroupId],
1220    stages: &[u32],
1221) -> Result<(Vec<ResourceRecord>, ResourceEnvelope), CompileError> {
1222    let final_stage = stages.iter().copied().max().unwrap_or(0);
1223    let mut resources = Vec::with_capacity(graph.values().len());
1224    for value in graph.values() {
1225        let mut element_count = 1u64;
1226        for dim in &value.contract.shape {
1227            let extent = match dim {
1228                ShapeDim::Known(extent) => *extent,
1229                ShapeDim::Symbol(symbol) => bindings[symbol],
1230            };
1231            element_count = element_count.checked_mul(extent).ok_or_else(|| {
1232                overflow(
1233                    format!("graph.values[{}].shape", value.name),
1234                    "shape element count exceeds u64",
1235                )
1236            })?;
1237        }
1238        let host_count = usize::try_from(element_count).map_err(|_| {
1239            overflow(
1240                format!("graph.values[{}].shape", value.name),
1241                "shape element count exceeds addressable packed-size input",
1242            )
1243        })?;
1244        let byte_count = value
1245            .contract
1246            .dtype
1247            .packed_size_bytes(host_count)
1248            .map_err(|message| overflow(format!("graph.values[{}].dtype", value.name), message))?
1249            .ok_or_else(|| {
1250                failure(
1251                    CompilerFailureKind::UnsizedResource,
1252                    format!("graph.values[{}].dtype", value.name),
1253                    "value representation has no fixed packed byte size",
1254                    "resolve the representation to a fixed-width typed value before compilation",
1255                )
1256            })?;
1257        let byte_count = u64::try_from(byte_count).map_err(|_| {
1258            overflow(
1259                format!("graph.values[{}]", value.name),
1260                "packed byte count exceeds u64",
1261            )
1262        })?;
1263        let producer_stage = value.producer.map_or(0, |producer| {
1264            stages[node_groups[producer.0 as usize].0 as usize]
1265        });
1266        let mut last_stage = value
1267            .consumers
1268            .iter()
1269            .map(|consumer| stages[node_groups[consumer.0 as usize].0 as usize])
1270            .max()
1271            .unwrap_or(producer_stage);
1272        if matches!(
1273            value.contract.lifetime,
1274            ValueLifetime::Output | ValueLifetime::Retained
1275        ) {
1276            last_stage = last_stage.max(final_stage);
1277        }
1278        resources.push(ResourceRecord {
1279            value: ArtifactValueId(value.id.0),
1280            name: value.name.clone(),
1281            element_count,
1282            byte_count,
1283            lifetime: match value.contract.lifetime {
1284                ValueLifetime::Constant => ResourceLifetime::Constant,
1285                ValueLifetime::Invocation => ResourceLifetime::Invocation,
1286                ValueLifetime::Retained => ResourceLifetime::Retained,
1287                ValueLifetime::Output => ResourceLifetime::Output,
1288            },
1289            first_stage: producer_stage,
1290            last_stage,
1291        });
1292    }
1293    resources.sort_by_key(|resource| resource.value);
1294    let total_bytes = resources.iter().try_fold(0u64, |total, resource| {
1295        total.checked_add(resource.byte_count).ok_or_else(|| {
1296            overflow(
1297                "artifact.resource_envelope.total_bytes",
1298                "resource sum exceeds u64",
1299            )
1300        })
1301    })?;
1302    let mut peak_live_bytes = 0u64;
1303    for stage in 0..=final_stage {
1304        let live = resources
1305            .iter()
1306            .filter(|resource| resource.first_stage <= stage && stage <= resource.last_stage)
1307            .try_fold(0u64, |total, resource| {
1308                total.checked_add(resource.byte_count).ok_or_else(|| {
1309                    overflow(
1310                        "artifact.resource_envelope.peak_live_bytes",
1311                        "live resource sum exceeds u64",
1312                    )
1313                })
1314            })?;
1315        peak_live_bytes = peak_live_bytes.max(live);
1316    }
1317    Ok((
1318        resources,
1319        ResourceEnvelope {
1320            total_bytes,
1321            peak_live_bytes,
1322        },
1323    ))
1324}
1325
1326fn encode_payload(payload: &ArtifactPayload) -> Result<Vec<u8>, CompileError> {
1327    let body = serde_json::to_vec(payload).map_err(serialization_failure)?;
1328    let body_len = u32::try_from(body.len()).map_err(|_| {
1329        overflow(
1330            "artifact.body",
1331            "canonical body exceeds the u32 framing limit",
1332        )
1333    })?;
1334    let digest = artifact_digest(payload.schema_version, &body);
1335    let capacity = ARTIFACT_HEADER_BYTES
1336        .checked_add(body.len())
1337        .and_then(|len| len.checked_add(ARTIFACT_DIGEST_BYTES))
1338        .ok_or_else(|| overflow("artifact", "encoded artifact length overflowed usize"))?;
1339    let mut bytes = Vec::with_capacity(capacity);
1340    bytes.extend_from_slice(ARTIFACT_MAGIC);
1341    bytes.extend_from_slice(&payload.schema_version.to_le_bytes());
1342    bytes.extend_from_slice(&body_len.to_le_bytes());
1343    bytes.extend_from_slice(&body);
1344    bytes.extend_from_slice(&digest.0);
1345    Ok(bytes)
1346}
1347
1348fn artifact_digest(version: u16, body: &[u8]) -> Digest {
1349    let mut hasher = blake3::Hasher::new();
1350    hasher.update(ARTIFACT_DIGEST_DOMAIN);
1351    hasher.update(&version.to_le_bytes());
1352    hasher.update(&(body.len() as u64).to_le_bytes());
1353    hasher.update(body);
1354    Digest(*hasher.finalize().as_bytes())
1355}
1356
1357fn domain_digest(domain: &[u8], bytes: &[u8]) -> Digest {
1358    let mut hasher = blake3::Hasher::new();
1359    hasher.update(domain);
1360    hasher.update(&(bytes.len() as u64).to_le_bytes());
1361    hasher.update(bytes);
1362    Digest(*hasher.finalize().as_bytes())
1363}
1364
1365fn serialization_failure(error: serde_json::Error) -> CompileError {
1366    failure(
1367        CompilerFailureKind::MalformedArtifact,
1368        "artifact.body",
1369        error.to_string(),
1370        "use values representable by the canonical artifact schema",
1371    )
1372}
1373
1374fn overflow(path: impl Into<String>, message: impl Into<String>) -> CompileError {
1375    failure(
1376        CompilerFailureKind::ResourceOverflow,
1377        path,
1378        message,
1379        "reduce resolved extents or split the graph before compilation",
1380    )
1381}
1382
1383fn failure(
1384    code: CompilerFailureKind,
1385    path: impl Into<String>,
1386    message: impl Into<String>,
1387    fix: impl Into<String>,
1388) -> CompileError {
1389    let stage = diagnostic_stage(code);
1390    let retry = diagnostic_retry(code);
1391    CompileError {
1392        diagnostic: Diagnostic::error(code.as_str(), message.into())
1393            .with_stage(stage)
1394            .with_location(OpLocation::op("vyre-megakernel").with_path(path))
1395            .with_fix(fix.into())
1396            .with_retry(retry),
1397    }
1398}