1use super::{
2 invalid_plan, is_canonical_sha256, AttributeId, BTreeMap, BTreeSet, CapabilityId,
3 ContractVersion, Deserialize, NodeId, OperationId, ProgramValueId,
4 ProviderCompatibilityRejectReason, ProviderId, ProviderResourcePlan, ResolvedValueBinding,
5 ResolvedValueRole, ResourceId, SemanticValue, Serialize, StateId, TensorAccess, VNextError,
6};
7use crate::vnext::ProviderExecutionSemantics;
8
9pub const EXECUTION_PLAN_SCHEMA: PlanSchemaVersion = PlanSchemaVersion::new(8, 1);
10pub const MAX_EXECUTION_PLAN_WIRE_BYTES: usize = 16 * 1024 * 1024;
11pub const MAX_EXECUTION_PLAN_RESOURCE_ROWS: usize = 65_536;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
16pub struct PlanSchemaVersion {
17 pub major: u16,
18 pub minor: u16,
19}
20
21impl PlanSchemaVersion {
22 pub const fn new(major: u16, minor: u16) -> Self {
23 Self { major, minor }
24 }
25}
26
27#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
28#[serde(transparent)]
29pub struct PlanHash(String);
30
31impl PlanHash {
32 pub(super) fn new(value: String) -> Result<Self, VNextError> {
33 if !is_canonical_sha256(&value) {
34 return Err(invalid_plan("plan hash must be a lowercase SHA256"));
35 }
36 Ok(Self(value))
37 }
38
39 pub fn as_str(&self) -> &str {
40 &self.0
41 }
42}
43
44impl<'de> Deserialize<'de> for PlanHash {
45 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
46 where
47 D: serde::Deserializer<'de>,
48 {
49 Self::new(String::deserialize(deserializer)?).map_err(serde::de::Error::custom)
50 }
51}
52
53impl std::fmt::Display for PlanHash {
54 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 formatter.write_str(&self.0)
56 }
57}
58
59#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
60#[serde(rename_all = "snake_case")]
61pub enum PlanProviderRejectReason {
62 NotRegistered,
63 Incompatible(Vec<ProviderCompatibilityRejectReason>),
64 StorageIncompatible { resource_ids: Vec<ResourceId> },
65}
66
67#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
68pub struct RejectedProvider {
69 pub(super) provider_id: ProviderId,
70 pub(super) reasons: PlanProviderRejectReason,
71}
72
73impl RejectedProvider {
74 pub fn provider_id(&self) -> &ProviderId {
75 &self.provider_id
76 }
77
78 pub fn reasons(&self) -> &PlanProviderRejectReason {
79 &self.reasons
80 }
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
84#[serde(rename_all = "snake_case")]
85pub enum ProviderSelectionReason {
86 PreferredCompatible,
87 CanonicalCompatible,
88 FallbackFromPreferred,
89}
90
91#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
92pub struct ProviderSelection {
93 pub(super) requested_provider: Option<ProviderId>,
94 pub(super) selected_provider: ProviderId,
95 pub(super) selection_reason: ProviderSelectionReason,
96 pub(super) rejected_providers: Vec<RejectedProvider>,
97}
98
99impl ProviderSelection {
100 pub fn requested_provider(&self) -> Option<&ProviderId> {
101 self.requested_provider.as_ref()
102 }
103
104 pub fn selected_provider(&self) -> &ProviderId {
105 &self.selected_provider
106 }
107
108 pub const fn selection_reason(&self) -> ProviderSelectionReason {
109 self.selection_reason
110 }
111
112 pub fn rejected_providers(&self) -> &[RejectedProvider] {
113 &self.rejected_providers
114 }
115}
116
117#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
118#[serde(rename_all = "snake_case")]
119pub enum PlanExactAliasKind {
120 MayAlias,
121 MustAlias,
122}
123
124#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
128pub struct PlanExactAlias {
129 pub(super) output_value_id: ProgramValueId,
130 pub(super) output_ordinal: u32,
131 pub(super) input_value_id: ProgramValueId,
132 pub(super) input_ordinal: u32,
133 pub(super) kind: PlanExactAliasKind,
134}
135
136impl PlanExactAlias {
137 pub fn output_value_id(&self) -> &ProgramValueId {
138 &self.output_value_id
139 }
140
141 pub const fn output_ordinal(&self) -> u32 {
142 self.output_ordinal
143 }
144
145 pub fn input_value_id(&self) -> &ProgramValueId {
146 &self.input_value_id
147 }
148
149 pub const fn input_ordinal(&self) -> u32 {
150 self.input_ordinal
151 }
152
153 pub const fn kind(&self) -> PlanExactAliasKind {
154 self.kind
155 }
156}
157
158#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
161pub struct PlanStateEffect {
162 pub(super) state_id: StateId,
163 pub(super) state_value_id: ProgramValueId,
164 pub(super) lifetime: AllocationLifetime,
165 pub(super) access: TensorAccess,
166 pub(super) resource_ids: Vec<ResourceId>,
167}
168
169#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
172pub struct NodeTokenBindingProjection {
173 pub(super) value_id: ProgramValueId,
174 pub(super) role: ResolvedValueRole,
175 pub(super) ordinal: u32,
176 pub(super) axis: u32,
177 pub(super) rank: u32,
178 pub(super) canonical_extent: u64,
179}
180
181impl NodeTokenBindingProjection {
182 pub fn value_id(&self) -> &ProgramValueId {
183 &self.value_id
184 }
185
186 pub const fn role(&self) -> ResolvedValueRole {
187 self.role
188 }
189
190 pub const fn ordinal(&self) -> u32 {
191 self.ordinal
192 }
193
194 pub const fn axis(&self) -> u32 {
195 self.axis
196 }
197
198 pub const fn rank(&self) -> u32 {
199 self.rank
200 }
201
202 pub const fn canonical_extent(&self) -> u64 {
203 self.canonical_extent
204 }
205}
206
207#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
212#[serde(rename_all = "snake_case")]
213pub enum NodeWorkContract {
214 Fixed,
215 Tokens {
216 source: NodeTokenBindingProjection,
217 projections: Vec<NodeTokenBindingProjection>,
218 },
219}
220
221impl NodeWorkContract {
222 pub fn token_source(&self) -> Option<&NodeTokenBindingProjection> {
223 match self {
224 Self::Fixed => None,
225 Self::Tokens { source, .. } => Some(source),
226 }
227 }
228
229 pub fn token_projections(&self) -> &[NodeTokenBindingProjection] {
230 match self {
231 Self::Fixed => &[],
232 Self::Tokens { projections, .. } => projections,
233 }
234 }
235
236 pub fn token_projection(
237 &self,
238 role: ResolvedValueRole,
239 ordinal: u32,
240 ) -> Option<&NodeTokenBindingProjection> {
241 self.token_projections()
242 .iter()
243 .find(|projection| projection.role == role && projection.ordinal == ordinal)
244 }
245}
246
247impl PlanStateEffect {
248 pub fn state_id(&self) -> &StateId {
249 &self.state_id
250 }
251
252 pub fn state_value_id(&self) -> &ProgramValueId {
253 &self.state_value_id
254 }
255
256 pub const fn lifetime(&self) -> AllocationLifetime {
257 self.lifetime
258 }
259
260 pub const fn access(&self) -> TensorAccess {
261 self.access
262 }
263
264 pub fn resource_ids(&self) -> &[ResourceId] {
265 &self.resource_ids
266 }
267}
268
269#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
270pub struct PlanNode {
271 pub(super) id: NodeId,
272 pub(super) dependencies: Vec<NodeId>,
273 pub(super) operation_id: OperationId,
274 pub(super) operation_version: ContractVersion,
275 pub(super) operation_fingerprint: String,
276 pub(super) provider_implementation_fingerprint: String,
277 pub(super) provider_execution_semantics: ProviderExecutionSemantics,
278 pub(super) required_capabilities: BTreeSet<CapabilityId>,
279 pub(super) attributes: BTreeMap<AttributeId, SemanticValue>,
280 pub(super) work: NodeWorkContract,
281 pub(super) selection: ProviderSelection,
282 pub(super) provider_resources: ProviderResourcePlan,
283 pub(super) values: Vec<ResolvedValueBinding>,
284 pub(super) exact_aliases: Vec<PlanExactAlias>,
285 pub(super) state_effects: Vec<PlanStateEffect>,
286 pub(super) scratch_resource: Option<ResourceId>,
287 pub(super) binding_resource: Option<ResourceId>,
288 pub(super) persistent_resource: Option<ResourceId>,
289 pub(super) resources: Vec<ResourceId>,
290}
291
292impl PlanNode {
293 #[cfg(test)]
294 pub(crate) fn resource_test_node(id: NodeId) -> Self {
295 let provider_resources = ProviderResourcePlan {
296 provider_id: ProviderId::new("provider/resource-test").expect("valid provider id"),
297 estimator_id: "resource-test-estimator".to_owned(),
298 estimator_version: ContractVersion::new(1, 0),
299 estimator_implementation_fingerprint: "1".repeat(64),
300 estimator_input_fingerprint: "2".repeat(64),
301 estimate_fingerprint: "3".repeat(64),
302 value_alignment_bytes: 16,
303 scratch: None,
304 binding: None,
305 persistent: None,
306 };
307 let selected_provider = provider_resources.provider_id().clone();
308 Self {
309 id,
310 dependencies: Vec::new(),
311 operation_id: OperationId::new("operation/resource-test").expect("valid operation id"),
312 operation_version: ContractVersion::new(1, 0),
313 operation_fingerprint: "4".repeat(64),
314 provider_implementation_fingerprint: "5".repeat(64),
315 provider_execution_semantics: ProviderExecutionSemantics::bitwise_eager_and_replay(),
316 required_capabilities: BTreeSet::new(),
317 attributes: BTreeMap::new(),
318 work: super::NodeWorkContract::Fixed,
319 selection: super::ProviderSelection {
320 requested_provider: None,
321 selected_provider,
322 selection_reason: super::ProviderSelectionReason::CanonicalCompatible,
323 rejected_providers: Vec::new(),
324 },
325 provider_resources,
326 values: Vec::new(),
327 exact_aliases: Vec::new(),
328 state_effects: Vec::new(),
329 scratch_resource: None,
330 binding_resource: None,
331 persistent_resource: None,
332 resources: Vec::new(),
333 }
334 }
335
336 #[cfg(test)]
337 pub(crate) fn resource_test_node_with_binding(
338 id: NodeId,
339 binding_resource: ResourceId,
340 ) -> Self {
341 let mut node = Self::resource_test_node(id);
342 node.binding_resource = Some(binding_resource.clone());
343 node.resources = vec![binding_resource];
344 node
345 }
346
347 #[cfg(test)]
348 pub(crate) fn resource_test_node_with_state_effect(
349 id: NodeId,
350 state_id: StateId,
351 state_value_id: ProgramValueId,
352 lifetime: AllocationLifetime,
353 access: TensorAccess,
354 resource_ids: Vec<ResourceId>,
355 ) -> Self {
356 let mut node = Self::resource_test_node(id);
357 node.state_effects = vec![PlanStateEffect {
358 state_id,
359 state_value_id,
360 lifetime,
361 access,
362 resource_ids,
363 }];
364 node
365 }
366
367 pub fn id(&self) -> &NodeId {
368 &self.id
369 }
370
371 pub fn dependencies(&self) -> &[NodeId] {
372 &self.dependencies
373 }
374
375 pub fn operation_id(&self) -> &OperationId {
376 &self.operation_id
377 }
378
379 pub const fn operation_version(&self) -> ContractVersion {
380 self.operation_version
381 }
382
383 pub fn operation_fingerprint(&self) -> &str {
384 &self.operation_fingerprint
385 }
386
387 pub fn provider_implementation_fingerprint(&self) -> &str {
388 &self.provider_implementation_fingerprint
389 }
390
391 pub const fn provider_execution_semantics(&self) -> ProviderExecutionSemantics {
392 self.provider_execution_semantics
393 }
394
395 pub fn required_capabilities(&self) -> &BTreeSet<CapabilityId> {
396 &self.required_capabilities
397 }
398
399 pub fn attributes(&self) -> &BTreeMap<AttributeId, SemanticValue> {
400 &self.attributes
401 }
402
403 pub fn work(&self) -> &NodeWorkContract {
404 &self.work
405 }
406
407 pub fn selection(&self) -> &ProviderSelection {
408 &self.selection
409 }
410
411 pub fn provider_resources(&self) -> &ProviderResourcePlan {
412 &self.provider_resources
413 }
414
415 pub fn values(&self) -> &[ResolvedValueBinding] {
416 &self.values
417 }
418
419 pub fn exact_aliases(&self) -> &[PlanExactAlias] {
420 &self.exact_aliases
421 }
422
423 pub fn state_effects(&self) -> &[PlanStateEffect] {
424 &self.state_effects
425 }
426
427 pub fn scratch_resource(&self) -> Option<&ResourceId> {
428 self.scratch_resource.as_ref()
429 }
430
431 pub fn binding_resource(&self) -> Option<&ResourceId> {
432 self.binding_resource.as_ref()
433 }
434
435 pub fn persistent_resource(&self) -> Option<&ResourceId> {
436 self.persistent_resource.as_ref()
437 }
438
439 pub fn resources(&self) -> &[ResourceId] {
440 &self.resources
441 }
442}
443
444#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
445#[serde(rename_all = "snake_case")]
446pub enum AllocationLifetime {
447 Plan,
448 Request,
449 Sequence,
450 Step,
451 Invocation,
452}
453
454#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
455#[serde(rename_all = "snake_case")]
456pub enum AllocationKind {
457 Value,
458 Scratch { node_id: NodeId },
459 Binding { node_id: NodeId },
460 Persistent { node_id: NodeId },
461}