Skip to main content

runmat_execution/executable/
manifest.rs

1use std::collections::BTreeSet;
2
3use serde::{Deserialize, Serialize};
4
5use super::{
6    component::validate_component_descriptors, ExecutableComponentDescriptor,
7    ExecutableComponentRevisions, ExecutableIdentity, ExecutableOptionalSection,
8    ExecutableSectionSupport,
9};
10use crate::{ContractError, Digest};
11use runmat_types::{
12    CapabilityRequirement, CapabilitySet, InteropManifest, ParallelManifest, RegionContract,
13};
14
15pub const EXECUTABLE_UNIT_SCHEMA_VERSION: u16 = crate::schema::EXECUTABLE_UNIT_SCHEMA_V3;
16
17#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
18#[serde(deny_unknown_fields)]
19pub struct ExecutableUnitManifest {
20    pub schema_version: u16,
21    pub identity: ExecutableIdentity,
22    pub revisions: ExecutableComponentRevisions,
23    pub components: Vec<ExecutableComponentDescriptor>,
24    pub capabilities: CapabilitySet,
25    pub regions: Vec<RegionContract>,
26    pub interop: InteropManifest,
27    pub parallel: ParallelManifest,
28    pub optional_sections: Vec<ExecutableOptionalSection>,
29}
30
31impl ExecutableUnitManifest {
32    pub fn validate(&self) -> Result<(), ContractError> {
33        if self.schema_version != EXECUTABLE_UNIT_SCHEMA_VERSION {
34            return Err(ContractError::UnsupportedSchema {
35                actual: self.schema_version,
36                supported: EXECUTABLE_UNIT_SCHEMA_VERSION,
37            });
38        }
39        self.identity.validate()?;
40        self.revisions.validate()?;
41        validate_component_descriptors(&self.components, &self.revisions)?;
42        if self.identity.program.catalog_fingerprint() != &self.revisions.catalog_fingerprint {
43            return Err(ContractError::invalid(
44                "executable.revisions.catalog_fingerprint",
45                "must match the immutable program revision",
46            ));
47        }
48        if self.regions.windows(2).any(|pair| pair[0].id >= pair[1].id) {
49            return Err(ContractError::invalid(
50                "executable.regions",
51                "regions must be sorted and unique by identity",
52            ));
53        }
54        for region in &self.regions {
55            region.validate().map_err(|error| {
56                ContractError::invalid(
57                    "executable.regions",
58                    format!("{}: {}", error.path, error.message),
59                )
60            })?;
61        }
62        self.interop.validate().map_err(|error| {
63            ContractError::invalid(
64                "executable.interop",
65                format!("{}: {}", error.path, error.message),
66            )
67        })?;
68        self.parallel.validate().map_err(|error| {
69            ContractError::invalid(
70                "executable.parallel",
71                format!("{}: {}", error.path, error.message),
72            )
73        })?;
74        let region_ids = self
75            .regions
76            .iter()
77            .map(|region| region.id)
78            .collect::<BTreeSet<_>>();
79        if self
80            .parallel
81            .parfor_regions
82            .iter()
83            .map(|region| region.id.0)
84            .chain(self.parallel.spmd_regions.iter().map(|region| region.id.0))
85            .any(|region| !region_ids.contains(&region))
86        {
87            return Err(ContractError::invalid(
88                "executable.parallel.regions",
89                "every parallel construct must name a declared region contract",
90            ));
91        }
92        if !self.interop.foreign_types.is_empty()
93            && !self
94                .capabilities
95                .0
96                .contains(&CapabilityRequirement::ForeignRuntime)
97        {
98            return Err(ContractError::invalid(
99                "executable.capabilities",
100                "foreign requirements need the foreign-runtime capability",
101            ));
102        }
103        if (!self.parallel.parfor_regions.is_empty() || !self.parallel.spmd_regions.is_empty())
104            && !self
105                .capabilities
106                .0
107                .contains(&CapabilityRequirement::ParallelRuntime)
108        {
109            return Err(ContractError::invalid(
110                "executable.capabilities",
111                "parallel constructs need the parallel-runtime capability",
112            ));
113        }
114        if (!self.parallel.distributed_values.is_empty() || !self.parallel.collectives.is_empty())
115            && !self
116                .capabilities
117                .0
118                .contains(&CapabilityRequirement::DistributedRuntime)
119        {
120            return Err(ContractError::invalid(
121                "executable.capabilities",
122                "distributed constructs need the distributed-runtime capability",
123            ));
124        }
125        if self
126            .optional_sections
127            .windows(2)
128            .any(|pair| pair[0].name >= pair[1].name)
129        {
130            return Err(ContractError::invalid(
131                "executable.optional_sections",
132                "sections must be sorted and unique by name",
133            ));
134        }
135        for section in &self.optional_sections {
136            section.validate()?;
137        }
138        Ok(())
139    }
140
141    pub fn validate_for(&self, support: &ExecutableSectionSupport) -> Result<(), ContractError> {
142        self.validate()?;
143        for section in &self.optional_sections {
144            support.validate_section(section)?;
145        }
146        Ok(())
147    }
148
149    pub fn canonical_bytes(&self) -> Result<Vec<u8>, ContractError> {
150        self.validate()?;
151        serde_json::to_vec(self)
152            .map_err(|error| ContractError::invalid("executable.manifest", error.to_string()))
153    }
154
155    pub fn from_canonical_bytes(bytes: &[u8]) -> Result<Self, ContractError> {
156        let manifest = serde_json::from_slice::<Self>(bytes)
157            .map_err(|error| ContractError::invalid("executable.manifest", error.to_string()))?;
158        manifest.validate()?;
159        if manifest.canonical_bytes()? != bytes {
160            return Err(ContractError::invalid(
161                "executable.manifest",
162                "encoding is valid JSON but not canonical RunMat JSON",
163            ));
164        }
165        Ok(manifest)
166    }
167
168    pub fn cache_key(&self) -> Result<Digest, ContractError> {
169        self.canonical_bytes().map(Digest::sha256)
170    }
171}