Skip to main content

vyre_megakernel/
target.rs

1use serde::{Deserialize, Serialize};
2use std::collections::{HashMap, HashSet};
3use thiserror::Error;
4use vyre_foundation::{execution_plan::fusion::merge_programs_shared, ir::Program};
5use vyre_lower::{KernelDescriptor, MemoryClass};
6
7use crate::{
8    Artifact, ArtifactAbi, ArtifactEnvelope, ArtifactNodeId, CompileError, FusionGroupId,
9    FusionRecord, ResourceLifetime, TargetEntryPoint, TargetPayload, TargetPayloadFormat,
10    TargetProfile, TargetResourceAccess, TargetResourceBinding, TargetResourceMemory,
11};
12
13/// One compiler-selected group decoded into verified semantic modules.
14#[derive(Clone, Debug, PartialEq, Eq)]
15pub struct SelectedModule {
16    /// Stable selected group identity.
17    pub group: FusionGroupId,
18    /// Dependency stage selected by the whole-program planner.
19    pub stage: u32,
20    /// Typed graph node identities in deterministic emission order.
21    pub nodes: Vec<ArtifactNodeId>,
22    /// Canonical Programs corresponding one-for-one with `nodes`.
23    pub programs: Vec<Program>,
24}
25
26/// One compiler-selected group after canonical semantic optimization and
27/// verified representation lowering.
28#[derive(Clone, Debug)]
29pub struct SelectedLowering {
30    /// Exact neutral artifact identity.
31    pub artifact: crate::Digest,
32    /// Stable selected group identity.
33    pub group: FusionGroupId,
34    /// Dependency stage selected by the whole-program planner.
35    pub stage: u32,
36    /// Typed graph node identities in deterministic emission order.
37    pub nodes: Vec<ArtifactNodeId>,
38    /// Verified backend-neutral descriptor consumed by concrete emitters.
39    pub descriptor: KernelDescriptor,
40    /// Canonical ABI slice for this selected group.
41    pub abi: ArtifactAbi,
42    /// Canonical descriptor-to-artifact resource association.
43    pub canonical_bindings: Vec<TargetResourceBinding>,
44    /// Authoritative logical invocation span before target grid projection.
45    pub logical_element_count: u32,
46    program: Program,
47}
48
49/// Canonical target-module bundle schema carried inside one target payload.
50pub const TARGET_MODULE_BUNDLE_SCHEMA_VERSION: u16 = 2;
51
52/// One generated target module corresponding to one selected fusion group.
53#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
54pub struct TargetModuleImage {
55    /// Stable selected fusion group.
56    pub group: FusionGroupId,
57    /// Dependency stage of this module.
58    pub stage: u32,
59    /// Exact selected node identities in deterministic order.
60    pub nodes: Vec<ArtifactNodeId>,
61    /// Canonical optimized Program wire consumed without semantic re-lowering.
62    pub program: Vec<u8>,
63    /// Verified lowering product consumed by materializers without re-lowering.
64    pub descriptor: KernelDescriptor,
65    /// Target entry-point name.
66    pub entry_point: String,
67    /// Immutable target-native module bytes.
68    pub bytes: Vec<u8>,
69}
70
71/// Canonical ordered target modules for one neutral artifact.
72#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
73pub struct TargetModuleBundle {
74    /// Bundle schema.
75    pub schema_version: u16,
76    /// Modules ordered by dependency stage and fusion-group identity.
77    pub modules: Vec<TargetModuleImage>,
78}
79
80impl TargetModuleBundle {
81    /// Construct and canonically order target modules.
82    #[must_use]
83    pub fn new(mut modules: Vec<TargetModuleImage>) -> Self {
84        modules.sort_by_key(|module| (module.stage, module.group));
85        Self {
86            schema_version: TARGET_MODULE_BUNDLE_SCHEMA_VERSION,
87            modules,
88        }
89    }
90
91    /// Encode canonical target-module bytes.
92    pub fn to_bytes(&self) -> Result<Vec<u8>, TargetCompileError> {
93        let body = serde_json::to_vec(self)
94            .map_err(|error| TargetCompileError::ModuleBundle(error.to_string()))?;
95        let digest = blake3::hash(&body);
96        let mut bytes = Vec::with_capacity(32 + body.len());
97        bytes.extend_from_slice(digest.as_bytes());
98        bytes.extend_from_slice(&body);
99        Ok(bytes)
100    }
101
102    /// Decode and validate canonical target-module bytes.
103    pub fn from_bytes(bytes: &[u8]) -> Result<Self, TargetCompileError> {
104        let (expected, body) = bytes.split_at_checked(32).ok_or_else(|| {
105            TargetCompileError::ModuleBundle("target module bundle is truncated".to_string())
106        })?;
107        let actual = blake3::hash(body);
108        if actual.as_bytes() != expected {
109            return Err(TargetCompileError::ModuleBundle(
110                "target module bundle digest mismatch".to_string(),
111            ));
112        }
113        let bundle: Self = serde_json::from_slice(body)
114            .map_err(|error| TargetCompileError::ModuleBundle(error.to_string()))?;
115        for module in &bundle.modules {
116            if module.nodes.is_empty() {
117                return Err(TargetCompileError::ModuleBundle(format!(
118                    "fusion group {} has no selected nodes",
119                    module.group.0
120                )));
121            }
122            Program::from_wire(&module.program).map_err(|error| {
123                TargetCompileError::ModuleBundle(format!(
124                    "fusion group {} selected Program is malformed: {error}",
125                    module.group.0
126                ))
127            })?;
128            vyre_lower::verify_descriptor(&module.descriptor).map_err(|error| {
129                TargetCompileError::ModuleBundle(format!(
130                    "fusion group {} descriptor is invalid: {error:?}",
131                    module.group.0
132                ))
133            })?;
134        }
135        if bundle.schema_version != TARGET_MODULE_BUNDLE_SCHEMA_VERSION {
136            return Err(TargetCompileError::ModuleBundle(format!(
137                "schema {} is unsupported; expected {}",
138                bundle.schema_version, TARGET_MODULE_BUNDLE_SCHEMA_VERSION
139            )));
140        }
141        if bundle.modules.windows(2).any(|modules| {
142            (modules[0].stage, modules[0].group) >= (modules[1].stage, modules[1].group)
143        }) {
144            return Err(TargetCompileError::ModuleBundle(
145                "module bundle is not in canonical stage/group order".to_string(),
146            ));
147        }
148        let canonical = bundle.to_bytes()?;
149        if canonical != bytes {
150            return Err(TargetCompileError::ModuleBundle(
151                "module bundle is not in canonical stage/group order".to_string(),
152            ));
153        }
154        Ok(bundle)
155    }
156}
157
158/// Failure produced by a registered target compiler facet.
159#[derive(Debug, Error)]
160#[non_exhaustive]
161pub enum TargetCompileError {
162    /// The neutral artifact could not be decoded into selected modules.
163    #[error("target compiler rejected the neutral artifact: {0}")]
164    InvalidArtifact(String),
165    /// The target cannot represent one selected module or ABI contract.
166    #[error("target capability rejected the selected plan: {0}")]
167    Unsupported(String),
168    /// Verified target lowering or emission failed.
169    #[error("target emission failed: {0}")]
170    Emission(String),
171    /// Canonical target-module bundle encoding or decoding failed.
172    #[error("target module bundle failed: {0}")]
173    ModuleBundle(String),
174    /// The emitted payload violated the canonical payload contract.
175    #[error("target payload construction failed: {0}")]
176    Payload(#[from] CompileError),
177}
178
179/// Pure compiler facet from a selected neutral artifact to immutable target bytes.
180pub trait TargetCompiler: Send + Sync {
181    /// Exact target payload format produced by this facet.
182    fn format(&self) -> &TargetPayloadFormat;
183
184    /// Immutable capability profile used by this pure compiler.
185    fn profile(&self) -> &TargetProfile;
186
187    /// Compile every selected module and project the canonical artifact ABI.
188    fn compile(&self, artifact: &Artifact) -> Result<TargetPayload, TargetCompileError>;
189}
190/// Compile and attach one target payload to its exact neutral artifact.
191///
192/// This is the only orchestration boundary from a pure target compiler facet to
193/// an authenticated deployable envelope. It does not acquire a device or
194/// materialize native handles.
195pub fn attach_target(
196    artifact: Artifact,
197    compiler: &dyn TargetCompiler,
198) -> Result<ArtifactEnvelope, TargetCompileError> {
199    let payload = compiler.compile(&artifact)?;
200    let mut envelope = ArtifactEnvelope::new(artifact);
201    envelope.attach_target_payload(payload)?;
202    Ok(envelope)
203}
204
205/// Decode compiler-selected modules from one authenticated neutral artifact.
206///
207/// Target compilers use the verified [`compile_selected_modules`] boundary
208/// rather than reconstructing graph order or reading raw frontend Programs.
209pub(crate) fn selected_modules(
210    artifact: &Artifact,
211) -> Result<Vec<SelectedModule>, TargetCompileError> {
212    artifact
213        .fusion()
214        .iter()
215        .map(|group| decode_group(artifact, group))
216        .collect()
217}
218
219/// Form one generated semantic Program for a compiler-selected fusion group.
220///
221/// Programs in a graph composition use shared buffer names for connected
222/// values. Shared fusion preserves those dataflow names, alpha-renames local
223/// collisions, inserts required intra-kernel barriers, and rejects unsafe
224/// geometry or aliasing.
225fn fuse_selected_module(module: &SelectedModule) -> Result<Program, TargetCompileError> {
226    merge_programs_shared(&module.programs).map_err(|error| {
227        TargetCompileError::Unsupported(format!(
228            "fusion group {} cannot form one target module: {error}",
229            module.group.0
230        ))
231    })
232}
233
234/// Target-native bytes and the exact emitted entry metadata.
235#[derive(Clone, Debug, PartialEq, Eq)]
236pub struct EmittedTargetModule {
237    /// Entry point exported by the target-native module.
238    pub entry_point: String,
239    /// Exact target grid dimensions.
240    pub grid_size: [u32; 3],
241    /// Entry-local dynamic shared byte requirement.
242    pub dynamic_shared_bytes: u32,
243    /// Exact target workgroup dimensions.
244    pub workgroup_size: [u32; 3],
245    /// Exact target resource projection.
246    pub resource_bindings: Vec<TargetResourceBinding>,
247    /// Immutable target-native module bytes.
248    pub bytes: Vec<u8>,
249}
250
251/// Compile all selected groups through one verified lowering boundary and
252/// package canonical target bytes.
253pub fn compile_selected_modules(
254    artifact: &Artifact,
255    format: TargetPayloadFormat,
256    profile: TargetProfile,
257    mut emit: impl FnMut(
258        &SelectedLowering,
259        &TargetProfile,
260    ) -> Result<EmittedTargetModule, TargetCompileError>,
261) -> Result<TargetPayload, TargetCompileError> {
262    let modules = selected_modules(artifact)?;
263    let mut images = Vec::with_capacity(modules.len());
264    let mut entries = Vec::with_capacity(modules.len());
265    for module in modules {
266        let program = fuse_selected_module(&module)?;
267        let lowered = vyre_lower::lower_verified(&program).map_err(|error| {
268            TargetCompileError::Emission(format!(
269                "verified lowering failed for fusion group {}: {error}",
270                module.group.0
271            ))
272        })?;
273        let bindings = selected_resource_bindings(artifact, &module, &lowered.descriptor)?;
274        let abi = selected_abi(artifact, &module);
275        let logical_element_count =
276            selected_logical_element_count(artifact, &module, &lowered.program);
277        let selected = SelectedLowering {
278            artifact: artifact.digest(),
279            group: module.group,
280            stage: module.stage,
281            nodes: module.nodes,
282            descriptor: lowered.descriptor,
283            abi,
284            canonical_bindings: bindings,
285            logical_element_count,
286            program: lowered.program,
287        };
288        let emitted = emit(&selected, &profile)?;
289        let node = *selected.nodes.first().ok_or_else(|| {
290            TargetCompileError::InvalidArtifact(format!(
291                "fusion group {} has no member node",
292                selected.group.0
293            ))
294        })?;
295        let entry_point = emitted.entry_point;
296        entries.push(TargetEntryPoint {
297            name: entry_point.clone(),
298            node,
299            workgroup_size: emitted.workgroup_size,
300            grid_size: emitted.grid_size,
301            dynamic_shared_bytes: emitted.dynamic_shared_bytes,
302            resource_bindings: emitted.resource_bindings,
303        });
304        let program = selected.program.to_wire().map_err(|error| {
305            TargetCompileError::ModuleBundle(format!(
306                "fusion group {} selected Program encoding failed: {error}",
307                selected.group.0
308            ))
309        })?;
310        images.push(TargetModuleImage {
311            group: selected.group,
312            stage: selected.stage,
313            nodes: selected.nodes.clone(),
314            program,
315            descriptor: selected.descriptor.clone(),
316            entry_point,
317            bytes: emitted.bytes,
318        });
319    }
320    let bytes = TargetModuleBundle::new(images).to_bytes()?;
321    TargetPayload::new(artifact, format, profile, entries, bytes).map_err(Into::into)
322}
323
324/// Projects verified descriptor bindings onto the selected artifact resources.
325fn selected_resource_bindings(
326    artifact: &Artifact,
327    module: &SelectedModule,
328    descriptor: &KernelDescriptor,
329) -> Result<Vec<TargetResourceBinding>, TargetCompileError> {
330    let canonical_by_name = module
331        .nodes
332        .iter()
333        .filter_map(|node| {
334            artifact
335                .abi()
336                .entries
337                .iter()
338                .find(|entry| entry.node == *node)
339        })
340        .flat_map(|entry| entry.inputs.iter().chain(entry.outputs.iter()).copied())
341        .filter_map(|value| {
342            artifact
343                .resources()
344                .iter()
345                .find(|resource| resource.value == value)
346                .map(|resource| (resource.name.as_str(), value))
347        })
348        .collect::<HashMap<_, _>>();
349    let constant_values = artifact
350        .resources()
351        .iter()
352        .filter(|resource| resource.lifetime == ResourceLifetime::Constant)
353        .map(|resource| resource.value)
354        .collect::<HashSet<_>>();
355    descriptor
356        .bindings
357        .slots
358        .iter()
359        .filter(|slot| {
360            !matches!(
361                slot.memory_class,
362                MemoryClass::Shared | MemoryClass::Scratch
363            ) && slot.name != vyre_lower::TRAP_SIDECAR_NAME
364        })
365        .map(|slot| {
366            let resource = canonical_by_name
367                .get(slot.name.as_str())
368                .copied()
369                .ok_or_else(|| {
370                    TargetCompileError::InvalidArtifact(format!(
371                    "fusion group {} descriptor binding `{}` has no canonical artifact resource",
372                    module.group.0, slot.name
373                ))
374                })?;
375            Ok(TargetResourceBinding {
376                resource,
377                group: if matches!(slot.memory_class, MemoryClass::Uniform) {
378                    1
379                } else {
380                    0
381                },
382                slot: slot.slot,
383                memory: if matches!(
384                    slot.memory_class,
385                    MemoryClass::Constant | MemoryClass::Uniform
386                ) || constant_values.contains(&resource)
387                {
388                    TargetResourceMemory::Constant
389                } else {
390                    TargetResourceMemory::Global
391                },
392                access: match slot.visibility {
393                    vyre_lower::BindingVisibility::ReadOnly => TargetResourceAccess::ReadOnly,
394                    vyre_lower::BindingVisibility::WriteOnly => TargetResourceAccess::WriteOnly,
395                    vyre_lower::BindingVisibility::ReadWrite => TargetResourceAccess::ReadWrite,
396                },
397            })
398        })
399        .collect()
400}
401
402fn selected_logical_element_count(
403    artifact: &Artifact,
404    module: &SelectedModule,
405    program: &Program,
406) -> u32 {
407    let nodes = module.nodes.iter().copied().collect::<HashSet<_>>();
408    let values = artifact
409        .abi()
410        .entries
411        .iter()
412        .filter(|entry| nodes.contains(&entry.node))
413        .flat_map(|entry| entry.inputs.iter().chain(&entry.outputs))
414        .copied()
415        .collect::<HashSet<_>>();
416    let full_span = program.stats().atomic_op_count > 0
417        || vyre_foundation::program_caps::scan(program).subgroup_ops;
418    let selected = artifact
419        .resources()
420        .iter()
421        .filter(|resource| values.contains(&resource.value));
422    let count = if full_span {
423        selected.map(|resource| resource.element_count).max()
424    } else {
425        selected
426            .filter(|resource| {
427                artifact
428                    .abi()
429                    .resources
430                    .iter()
431                    .find(|abi| abi.value == resource.value)
432                    .is_some_and(|abi| {
433                        matches!(
434                            abi.access,
435                            crate::AbiAccess::WriteOnly | crate::AbiAccess::ReadWrite
436                        )
437                    })
438            })
439            .map(|resource| resource.element_count)
440            .max()
441            .or_else(|| {
442                artifact
443                    .resources()
444                    .iter()
445                    .filter(|resource| values.contains(&resource.value))
446                    .map(|resource| resource.element_count)
447                    .max()
448            })
449    }
450    .unwrap_or(1)
451    .max(1);
452    u32::try_from(count).unwrap_or(u32::MAX)
453}
454
455fn selected_abi(artifact: &Artifact, module: &SelectedModule) -> ArtifactAbi {
456    let nodes = module.nodes.iter().copied().collect::<HashSet<_>>();
457    let entries = artifact
458        .abi()
459        .entries
460        .iter()
461        .filter(|entry| nodes.contains(&entry.node))
462        .cloned()
463        .collect::<Vec<_>>();
464    let values = entries
465        .iter()
466        .flat_map(|entry| entry.inputs.iter().chain(&entry.outputs))
467        .copied()
468        .collect::<HashSet<_>>();
469    ArtifactAbi {
470        resources: artifact
471            .abi()
472            .resources
473            .iter()
474            .filter(|resource| values.contains(&resource.value))
475            .cloned()
476            .collect(),
477        entries,
478    }
479}
480
481fn decode_group(
482    artifact: &Artifact,
483    group: &FusionRecord,
484) -> Result<SelectedModule, TargetCompileError> {
485    let mut nodes = group.members.clone();
486    nodes.sort();
487    let programs = nodes
488        .iter()
489        .map(|node| {
490            let record = artifact
491                .nodes()
492                .iter()
493                .find(|record| record.id == *node)
494                .ok_or_else(|| {
495                    TargetCompileError::InvalidArtifact(format!(
496                        "fusion group {} references missing node {}",
497                        group.id.0, node.0
498                    ))
499                })?;
500            Program::from_wire(&record.program).map_err(|error| {
501                TargetCompileError::InvalidArtifact(format!(
502                    "node {} canonical Program failed to decode: {error}",
503                    node.0
504                ))
505            })
506        })
507        .collect::<Result<Vec<_>, _>>()?;
508    Ok(SelectedModule {
509        group: group.id,
510        stage: group.stage,
511        nodes,
512        programs,
513    })
514}