1use std::sync::Arc;
2
3use tenferro_ops::dim_expr::DimExpr;
4use tenferro_ops::ext_op::{
5 ExtensionAlias, ExtensionAliasDeclaration, ExtensionEffectAccess, ExtensionEffectDeclaration,
6 ExtensionOp,
7};
8use tenferro_ops::shape_extent::ShapeExtent;
9use tenferro_tensor::Tensor;
10
11use crate::checkpoint::RetainedValue;
12
13use super::bindings::PendingBinding;
14use super::identity::SemanticIdentity;
15use super::metadata::SemanticProvenance;
16use super::op::{SemanticOp, SemanticOperation};
17use super::value::ProgramBuilderNonce;
18use super::{
19 Alias, BindingKey, CoreSemanticOp, Effect, EffectAccess, EffectResource, FrozenProgram,
20 ImportedProgramValues, ProgramBindingError, ProgramBindings, ProgramBuildError,
21 ProgramFinishError, ProgramImport, ProgramInputSpec, ProgramShapeRelation,
22 ProgramStructuralError, ProgramValue, ProgramValueMetadata, SemanticPlacementConstraint,
23 SemanticProgram, ShapeGuard,
24};
25
26pub struct SemanticProgramBuilder {
28 owner: ProgramBuilderNonce,
29 inputs: Vec<ProgramValue>,
30 input_specs: Vec<ProgramInputSpec>,
31 values: Vec<ProgramValueMetadata>,
32 operations: Vec<SemanticOperation>,
33 bindings: Vec<PendingBinding>,
34}
35
36impl Default for SemanticProgramBuilder {
37 fn default() -> Self {
38 Self::new()
39 }
40}
41
42impl SemanticProgramBuilder {
43 pub fn new() -> Self {
45 Self {
46 owner: ProgramBuilderNonce::fresh(),
47 inputs: Vec::new(),
48 input_specs: Vec::new(),
49 values: Vec::new(),
50 operations: Vec::new(),
51 bindings: Vec::new(),
52 }
53 }
54
55 pub fn bind_input(
64 &mut self,
65 input: ProgramValue,
66 tensor: Tensor,
67 ) -> Result<BindingKey, ProgramBuildError> {
68 self.validate_value(input)?;
69 if !self.inputs.contains(&input) {
70 return Err(ProgramBuildError::BindingTargetNotInput);
71 }
72 if self.bindings.iter().any(|binding| binding.input == input) {
73 return Err(ProgramBuildError::DuplicateBinding);
74 }
75 self.bind_input_retained(input, Arc::new(RetainedValue::from_tensor(tensor)))
76 }
77
78 pub(crate) fn bind_input_retained(
79 &mut self,
80 input: ProgramValue,
81 tensor: Arc<RetainedValue>,
82 ) -> Result<BindingKey, ProgramBuildError> {
83 self.validate_value(input)?;
84 if !self.inputs.contains(&input) {
85 return Err(ProgramBuildError::BindingTargetNotInput);
86 }
87 if self.bindings.iter().any(|binding| binding.input == input) {
88 return Err(ProgramBuildError::DuplicateBinding);
89 }
90 let key = BindingKey::new(input.slot, self.owner);
91 self.bindings.push(PendingBinding { key, input, tensor });
92 Ok(key)
93 }
94
95 pub fn input(&mut self, spec: ProgramInputSpec) -> Result<ProgramValue, ProgramBuildError> {
102 let slot = self.next_value_slot()?;
103 let value = ProgramValue::new(slot, self.owner);
104 self.values.push(spec.metadata().clone());
105 self.inputs.push(value);
106 self.input_specs.push(spec);
107 Ok(value)
108 }
109
110 pub fn validate_value(&self, value: ProgramValue) -> Result<(), ProgramBuildError> {
117 if value.owner != self.owner || value.slot as usize >= self.values.len() {
118 return Err(ProgramBuildError::ForeignValue);
119 }
120 Ok(())
121 }
122
123 pub fn value_metadata(
129 &self,
130 value: ProgramValue,
131 ) -> Result<&ProgramValueMetadata, ProgramBuildError> {
132 self.validate_value(value)?;
133 Ok(&self.values[value.slot as usize])
134 }
135
136 pub fn operation_count(&self) -> usize {
138 self.operations.len()
139 }
140
141 pub(crate) fn add_shape_guards_to_output(
142 &mut self,
143 output: ProgramValue,
144 guards: impl IntoIterator<Item = ShapeGuard>,
145 ) -> Result<(), ProgramBuildError> {
146 self.validate_value(output)?;
147 let operation = self
148 .operations
149 .iter_mut()
150 .find(|operation| operation.outputs.contains(&output))
151 .ok_or(ProgramBuildError::GuardTargetNotOperationOutput)?;
152 let mut combined = operation.shape_guards.to_vec();
153 combined.extend(guards);
154 operation.shape_guards = combined.into_boxed_slice();
155 Ok(())
156 }
157
158 pub fn import(
172 &mut self,
173 request: ProgramImport<'_>,
174 ) -> Result<ImportedProgramValues, ProgramBuildError> {
175 let transaction = ImportTransaction::prepare(self, request)?;
176 let roots = transaction.roots.clone();
177 self.inputs.extend(transaction.inputs);
178 self.input_specs.extend(transaction.input_specs);
179 self.values.extend(transaction.values);
180 self.operations.extend(transaction.operations);
181 self.bindings.extend(transaction.bindings);
182 Ok(ImportedProgramValues::new(roots))
183 }
184
185 pub fn finish(self, outputs: &[ProgramValue]) -> Result<FrozenProgram, ProgramFinishError> {
194 if outputs
195 .iter()
196 .any(|output| output.owner != self.owner || output.slot as usize >= self.values.len())
197 {
198 return Err(ProgramFinishError::ForeignOutput);
199 }
200
201 validate_structure(
202 self.owner,
203 &self.inputs,
204 self.values.len(),
205 &self.operations,
206 )?;
207 validate_bindings(&self.inputs, &self.input_specs, &self.bindings)?;
208
209 let inputs = self.inputs.into_boxed_slice();
210 let outputs: Box<[ProgramValue]> = outputs.into();
211 let values = self.values.into_boxed_slice();
212 let operations = self.operations.into_boxed_slice();
213 let shape_guards: Box<[ShapeGuard]> = operations
214 .iter()
215 .flat_map(|operation| operation.shape_guards.iter().cloned())
216 .collect();
217 let identity =
218 SemanticIdentity::build(&inputs, &outputs, &values, &operations, &shape_guards);
219 let bindings = ProgramBindings::freeze(self.owner, self.bindings);
220 let program = SemanticProgram {
221 owner: self.owner,
222 inputs,
223 outputs,
224 values,
225 operations,
226 shape_guards,
227 identity,
228 };
229 Ok(FrozenProgram {
230 program: Arc::new(program),
231 bindings,
232 })
233 }
234
235 #[cfg(test)]
236 pub(crate) fn operation_views_for_test(
237 &self,
238 ) -> impl ExactSizeIterator<Item = super::SemanticOperationView<'_>> + '_ {
239 self.operations
240 .iter()
241 .map(super::SemanticOperationView::new)
242 }
243
244 pub fn add_op(
251 &mut self,
252 op: CoreSemanticOp,
253 inputs: &[ProgramValue],
254 ) -> Result<Box<[ProgramValue]>, ProgramBuildError> {
255 self.validate_inputs(inputs)?;
256 validate_arity(op.input_count(), inputs.len())?;
257 let output_count = op.output_count();
258 let metadata = self.infer_core_metadata(&op, inputs)?;
259 validate_output_count(output_count, metadata.len())?;
260 let aliases = (0..output_count).map(Alias::fresh).collect();
261 self.append_operation(
262 SemanticOp::Core(op),
263 inputs,
264 metadata,
265 Vec::new(),
266 aliases,
267 Vec::new(),
268 )
269 }
270
271 pub fn add_extension(
337 &mut self,
338 op: Arc<dyn ExtensionOp>,
339 inputs: &[ProgramValue],
340 ) -> Result<Box<[ProgramValue]>, ProgramBuildError> {
341 self.validate_inputs(inputs)?;
342 validate_arity(op.input_count(), inputs.len())?;
343 let effects = extension_effects(op.as_ref())?;
344 let aliases = extension_aliases(op.as_ref())?;
345 validate_aliases(&aliases, inputs.len(), op.output_count())?;
346 let (metadata, guards) = self.infer_extension_metadata(op.as_ref(), inputs)?;
347 validate_output_count(op.output_count(), metadata.len())?;
348 self.append_operation(
349 SemanticOp::Extension(op),
350 inputs,
351 metadata,
352 effects,
353 aliases,
354 guards,
355 )
356 }
357
358 fn next_value_slot(&self) -> Result<u32, ProgramBuildError> {
359 u32::try_from(self.values.len()).map_err(|_| ProgramBuildError::TooManyValues)
360 }
361
362 fn validate_inputs(&self, inputs: &[ProgramValue]) -> Result<(), ProgramBuildError> {
363 inputs
364 .iter()
365 .try_for_each(|&value| self.validate_value(value))
366 }
367
368 fn input_metadata(
369 &self,
370 inputs: &[ProgramValue],
371 ) -> Result<Vec<&ProgramValueMetadata>, ProgramBuildError> {
372 inputs
373 .iter()
374 .map(|&value| self.value_metadata(value))
375 .collect()
376 }
377
378 fn infer_core_metadata(
379 &self,
380 op: &CoreSemanticOp,
381 inputs: &[ProgramValue],
382 ) -> Result<Vec<ProgramValueMetadata>, ProgramBuildError> {
383 let input_metadata = self.input_metadata(inputs)?;
384 let precision = input_extent_precision(&input_metadata);
385 let input_dtypes: Vec<_> = input_metadata
386 .iter()
387 .map(|metadata| metadata.dtype())
388 .collect();
389 let input_shapes = inference_shapes(&input_metadata);
390 let input_shape_refs: Vec<_> = input_shapes.iter().map(Vec::as_slice).collect();
391 let standard = tenferro_ops::std_tensor_op::StdTensorOp::from(op);
392 let dtype = crate::shape_infer::infer_output_dtype(&standard, &input_dtypes)
393 .map_err(metadata_error)?;
394 if core_output_uses_local_shape_coordinates(op) {
395 let local_input_shapes: Vec<_> = input_metadata
396 .iter()
397 .enumerate()
398 .map(|(input_idx, metadata)| {
399 DimExpr::input_shape(input_idx, metadata.shape().len())
400 })
401 .collect();
402 let local_input_shape_refs: Vec<_> =
403 local_input_shapes.iter().map(Vec::as_slice).collect();
404 let output_extents =
405 crate::shape_infer::infer_output_extents(&standard, &local_input_shape_refs)
406 .map_err(metadata_error)?;
407 output_extents
408 .into_iter()
409 .map(|shape| {
410 resolve_inferred_extents(shape, precision, &input_shape_refs)
411 .map(|shape| ProgramValueMetadata::from_extents(dtype, shape))
412 })
413 .collect()
414 } else {
415 let output_extents =
416 crate::shape_infer::infer_output_extents(&standard, &input_shape_refs)
417 .map_err(metadata_error)?;
418 Ok(output_extents
419 .into_iter()
420 .map(|shape| {
421 ProgramValueMetadata::from_extents(
422 dtype,
423 conservatively_bound_extents(shape, precision),
424 )
425 })
426 .collect())
427 }
428 }
429
430 fn infer_extension_metadata(
431 &self,
432 op: &dyn ExtensionOp,
433 inputs: &[ProgramValue],
434 ) -> Result<(Vec<ProgramValueMetadata>, Vec<ShapeGuard>), ProgramBuildError> {
435 let input_metadata = self.input_metadata(inputs)?;
436 let precision = input_extent_precision(&input_metadata);
437 let input_dtypes: Vec<_> = input_metadata
438 .iter()
439 .map(|metadata| metadata.dtype())
440 .collect();
441 let input_shapes = inference_shapes(&input_metadata);
442 let input_shape_refs: Vec<_> = input_shapes.iter().map(Vec::as_slice).collect();
443 let inferred = crate::shape_infer::infer_extension_output_meta_with_constraints(
444 op,
445 &input_dtypes,
446 &input_shape_refs,
447 )
448 .map_err(metadata_error)?;
449 let metadata = inferred
450 .output_metas
451 .into_iter()
452 .map(|(dtype, shape)| {
453 ProgramValueMetadata::from_extents(
454 dtype,
455 conservatively_bound_extents(
456 shape.into_iter().map(ShapeExtent::Exact),
457 precision,
458 ),
459 )
460 })
461 .collect();
462 let guards = inferred
463 .constraints
464 .into_iter()
465 .map(|constraint| {
466 let relation = match constraint.relation {
467 tenferro_ops::ShapeRelation::Equal => ProgramShapeRelation::Equal,
468 };
469 ShapeGuard::new(relation, constraint.lhs, constraint.rhs)
470 })
471 .collect();
472 Ok((metadata, guards))
473 }
474
475 fn append_operation(
476 &mut self,
477 op: SemanticOp,
478 inputs: &[ProgramValue],
479 metadata: Vec<ProgramValueMetadata>,
480 effects: Vec<Effect>,
481 aliases: Vec<Alias>,
482 shape_guards: Vec<ShapeGuard>,
483 ) -> Result<Box<[ProgramValue]>, ProgramBuildError> {
484 let provenance = match &op {
485 SemanticOp::Core(_) => SemanticProvenance::builder(None),
486 SemanticOp::Extension(extension) => {
487 SemanticProvenance::builder(Some(extension.family_id()))
488 }
489 };
490 let start = self.values.len();
491 let end = start
492 .checked_add(metadata.len())
493 .ok_or(ProgramBuildError::TooManyValues)?;
494 if end > u32::MAX as usize {
495 return Err(ProgramBuildError::TooManyValues);
496 }
497 let outputs: Box<[_]> = (start..end)
498 .map(|slot| ProgramValue::new(slot as u32, self.owner))
499 .collect();
500 self.values.extend(metadata);
501 self.operations.push(SemanticOperation {
502 op,
503 inputs: inputs.into(),
504 outputs: outputs.clone(),
505 effects: effects.into(),
506 aliases: aliases.into(),
507 shape_guards: shape_guards.into(),
508 placement: SemanticPlacementConstraint::any(),
509 provenance,
510 });
511 Ok(outputs)
512 }
513}
514
515fn core_output_uses_local_shape_coordinates(op: &CoreSemanticOp) -> bool {
516 matches!(
517 op,
518 CoreSemanticOp::Reshape { .. }
519 | CoreSemanticOp::BroadcastInDim { .. }
520 | CoreSemanticOp::GatherDynamicSliceSizes { .. }
521 )
522}
523
524fn validate_arity(expected: usize, actual: usize) -> Result<(), ProgramBuildError> {
525 if expected == actual {
526 Ok(())
527 } else {
528 Err(ProgramBuildError::Arity { expected, actual })
529 }
530}
531
532fn validate_output_count(expected: usize, actual: usize) -> Result<(), ProgramBuildError> {
533 if expected == actual {
534 Ok(())
535 } else {
536 Err(ProgramBuildError::OutputMetadataCount { expected, actual })
537 }
538}
539
540impl FrozenProgram {
541 #[doc(hidden)]
547 pub fn input_metadata_with_bound_shapes(&self) -> Box<[ProgramValueMetadata]> {
548 let bound_input_shapes: Vec<Option<Vec<DimExpr>>> = self
549 .program
550 .inputs
551 .iter()
552 .map(|input| {
553 self.bindings.tensor_for_input(*input).map(|tensor| {
554 tensor
555 .shape()
556 .iter()
557 .map(|&size| DimExpr::Const(size))
558 .collect()
559 })
560 })
561 .collect();
562
563 self.program
564 .inputs
565 .iter()
566 .map(|input| {
567 let metadata = self.program.values[input.slot as usize].clone();
568 ProgramValueMetadata::from_extents(
569 metadata.dtype(),
570 metadata.shape().iter().map(|extent| match extent {
571 ShapeExtent::Exact(expr) => ShapeExtent::Exact(
572 resolve_dim_expr_from_input_shapes(expr, &bound_input_shapes),
573 ),
574 ShapeExtent::UpperBound(expr) => ShapeExtent::UpperBound(
575 resolve_dim_expr_from_input_shapes(expr, &bound_input_shapes),
576 ),
577 ShapeExtent::Unknown => ShapeExtent::Unknown,
578 }),
579 )
580 })
581 .collect()
582 }
583
584 #[doc(hidden)]
593 pub fn with_input_prefix_bindings_from(
594 &self,
595 source: &FrozenProgram,
596 ) -> Result<FrozenProgram, ProgramFinishError> {
597 if self.program.inputs.len() < source.program.inputs.len() {
598 return Err(ProgramFinishError::StructuralValidation {
599 source: ProgramStructuralError::InvalidValueReference,
600 });
601 }
602
603 let mut bindings = Vec::new();
604 for (source_input, destination_input) in
605 source.program.inputs.iter().zip(self.program.inputs.iter())
606 {
607 if let Some(tensor) = source.bindings.tensor_for_input(*source_input) {
608 bindings.push(PendingBinding {
609 key: BindingKey::new(destination_input.slot, self.program.owner),
610 input: *destination_input,
611 tensor,
612 });
613 }
614 }
615
616 let input_specs: Vec<_> = self
617 .program
618 .inputs
619 .iter()
620 .map(|input| {
621 ProgramInputSpec::from_metadata(self.program.values[input.slot as usize].clone())
622 })
623 .collect();
624 validate_bindings(&self.program.inputs, &input_specs, &bindings)?;
625
626 Ok(FrozenProgram {
627 program: Arc::clone(&self.program),
628 bindings: ProgramBindings::freeze(self.program.owner, bindings),
629 })
630 }
631}
632
633struct ImportTransaction {
634 inputs: Vec<ProgramValue>,
635 input_specs: Vec<ProgramInputSpec>,
636 values: Vec<ProgramValueMetadata>,
637 operations: Vec<SemanticOperation>,
638 bindings: Vec<PendingBinding>,
639 roots: Box<[ProgramValue]>,
640}
641
642impl ImportTransaction {
643 fn prepare(
644 destination: &SemanticProgramBuilder,
645 request: ProgramImport<'_>,
646 ) -> Result<Self, ProgramBuildError> {
647 let source = request.program;
648 if !request.bindings.belongs_to(source.owner) {
649 return Err(ProgramBuildError::ForeignBindings);
650 }
651 if request
652 .roots
653 .iter()
654 .any(|root| root.owner != source.owner || root.slot as usize >= source.values.len())
655 {
656 return Err(ProgramBuildError::ForeignImportRoot);
657 }
658
659 let mut producer = vec![None; source.values.len()];
660 for (operation_index, operation) in source.operations.iter().enumerate() {
661 for output in &operation.outputs {
662 producer[output.slot as usize] = Some(operation_index);
663 }
664 }
665
666 let mut needed_values = vec![false; source.values.len()];
667 let mut needed_operations = vec![false; source.operations.len()];
668 let mut pending: Vec<_> = request
669 .roots
670 .iter()
671 .map(|root| root.slot as usize)
672 .collect();
673 pending.extend(
674 request
675 .bindings
676 .bound_inputs()
677 .map(|input| input.slot as usize),
678 );
679 for (operation_index, operation) in source.operations.iter().enumerate() {
680 if !operation.effects.is_empty() {
681 needed_operations[operation_index] = true;
682 for output in &operation.outputs {
683 needed_values[output.slot as usize] = true;
684 }
685 pending.extend(operation.inputs.iter().map(|input| input.slot as usize));
686 }
687 }
688 while let Some(slot) = pending.pop() {
689 if needed_values[slot] {
690 continue;
691 }
692 needed_values[slot] = true;
693 if let Some(operation_index) = producer[slot] {
694 if !needed_operations[operation_index] {
695 needed_operations[operation_index] = true;
696 let operation = &source.operations[operation_index];
697 for output in &operation.outputs {
698 needed_values[output.slot as usize] = true;
699 }
700 pending.extend(operation.inputs.iter().map(|input| input.slot as usize));
701 }
702 }
703 }
704
705 let imported_input_count = source
706 .inputs
707 .iter()
708 .filter(|input| needed_values[input.slot as usize])
709 .count();
710 let imported_output_count: usize = source
711 .operations
712 .iter()
713 .zip(&needed_operations)
714 .filter(|(_, needed)| **needed)
715 .map(|(operation, _)| operation.outputs.len())
716 .sum();
717 let imported_value_count = imported_input_count
718 .checked_add(imported_output_count)
719 .ok_or(ProgramBuildError::TooManyValues)?;
720 let final_value_count = destination
721 .values
722 .len()
723 .checked_add(imported_value_count)
724 .ok_or(ProgramBuildError::TooManyValues)?;
725 if final_value_count > u32::MAX as usize {
726 return Err(ProgramBuildError::TooManyValues);
727 }
728
729 let mut transaction = Self {
730 inputs: Vec::with_capacity(imported_input_count),
731 input_specs: Vec::with_capacity(imported_input_count),
732 values: Vec::with_capacity(imported_value_count),
733 operations: Vec::with_capacity(
734 needed_operations.iter().filter(|needed| **needed).count(),
735 ),
736 bindings: Vec::new(),
737 roots: Box::new([]),
738 };
739 let bound_input_shapes: Vec<Option<Vec<DimExpr>>> = source
742 .inputs
743 .iter()
744 .map(|input| {
745 request.bindings.tensor_for_input(*input).map(|tensor| {
746 tensor
747 .shape()
748 .iter()
749 .map(|&size| DimExpr::Const(size))
750 .collect()
751 })
752 })
753 .collect();
754
755 let resolve_extent = |extent: &ShapeExtent<DimExpr>| -> ShapeExtent<DimExpr> {
756 match extent {
757 ShapeExtent::Exact(expr) => ShapeExtent::Exact(resolve_dim_expr_from_input_shapes(
758 expr,
759 &bound_input_shapes,
760 )),
761 ShapeExtent::UpperBound(expr) => ShapeExtent::UpperBound(
762 resolve_dim_expr_from_input_shapes(expr, &bound_input_shapes),
763 ),
764 ShapeExtent::Unknown => ShapeExtent::Unknown,
765 }
766 };
767
768 let mut remap = vec![None; source.values.len()];
769
770 for &input in &source.inputs {
771 if !needed_values[input.slot as usize] {
772 continue;
773 }
774 let metadata = source.values[input.slot as usize].clone();
775 let metadata = ProgramValueMetadata::from_extents(
776 metadata.dtype(),
777 metadata
778 .shape()
779 .iter()
780 .map(&resolve_extent)
781 .collect::<Vec<_>>(),
782 );
783 let imported = transaction.next_value(destination.values.len(), destination.owner)?;
784 transaction.inputs.push(imported);
785 transaction
786 .input_specs
787 .push(ProgramInputSpec::from_metadata(metadata.clone()));
788 transaction.values.push(metadata);
789 remap[input.slot as usize] = Some(imported);
790 if let Some(tensor) = request.bindings.tensor_for_input(input) {
791 transaction.bindings.push(PendingBinding {
792 key: BindingKey::new(imported.slot, destination.owner),
793 input: imported,
794 tensor,
795 });
796 }
797 }
798
799 for (operation, needed) in source.operations.iter().zip(needed_operations) {
800 if !needed {
801 continue;
802 }
803 let inputs: Box<[_]> = operation
804 .inputs
805 .iter()
806 .map(|input| {
807 remap[input.slot as usize].ok_or(ProgramBuildError::InvalidImport {
808 source: ProgramStructuralError::InvalidSsaOrder,
809 })
810 })
811 .collect::<Result<_, _>>()?;
812 let mut outputs = Vec::with_capacity(operation.outputs.len());
813 for output in &operation.outputs {
814 let imported =
815 transaction.next_value(destination.values.len(), destination.owner)?;
816 let meta = source.values[output.slot as usize].clone();
817 let resolved = ProgramValueMetadata::from_extents(
818 meta.dtype(),
819 meta.shape().iter().map(&resolve_extent).collect::<Vec<_>>(),
820 );
821 transaction.values.push(resolved);
822 remap[output.slot as usize] = Some(imported);
823 outputs.push(imported);
824 }
825 let op = match &operation.op {
826 SemanticOp::Core(op) => SemanticOp::Core(op.clone()),
827 SemanticOp::Extension(op) => SemanticOp::Extension(op.clone_arc()),
828 };
829 transaction.operations.push(SemanticOperation {
830 op,
831 inputs,
832 outputs: outputs.into(),
833 effects: operation.effects.clone(),
834 aliases: operation.aliases.clone(),
835 shape_guards: operation.shape_guards.clone(),
836 placement: operation.placement,
837 provenance: operation.provenance.clone(),
838 });
839 }
840
841 transaction.roots = request
842 .roots
843 .iter()
844 .map(|root| {
845 remap[root.slot as usize].ok_or(ProgramBuildError::InvalidImport {
846 source: ProgramStructuralError::InvalidValueReference,
847 })
848 })
849 .collect::<Result<_, _>>()?;
850 Ok(transaction)
851 }
852
853 fn next_value(
854 &self,
855 destination_value_count: usize,
856 owner: ProgramBuilderNonce,
857 ) -> Result<ProgramValue, ProgramBuildError> {
858 let slot = destination_value_count
859 .checked_add(self.values.len())
860 .ok_or(ProgramBuildError::TooManyValues)?;
861 let slot = u32::try_from(slot).map_err(|_| ProgramBuildError::TooManyValues)?;
862 Ok(ProgramValue::new(slot, owner))
863 }
864}
865
866#[derive(Clone, Copy)]
867enum InputExtentPrecision {
868 Exact,
869 Bounded,
870 Unknown,
871}
872
873fn input_extent_precision(metadata: &[&ProgramValueMetadata]) -> InputExtentPrecision {
874 let mut precision = InputExtentPrecision::Exact;
875 for extent in metadata.iter().flat_map(|metadata| metadata.shape()) {
876 match extent {
877 ShapeExtent::Unknown => return InputExtentPrecision::Unknown,
878 ShapeExtent::UpperBound(_) => precision = InputExtentPrecision::Bounded,
879 ShapeExtent::Exact(_) => {}
880 }
881 }
882 precision
883}
884
885fn conservatively_bound_extents(
886 extents: impl IntoIterator<Item = ShapeExtent<DimExpr>>,
887 precision: InputExtentPrecision,
888) -> impl Iterator<Item = ShapeExtent<DimExpr>> {
889 extents.into_iter().map(move |extent| match precision {
890 InputExtentPrecision::Exact => extent,
891 InputExtentPrecision::Bounded => match extent {
892 ShapeExtent::Exact(expression) | ShapeExtent::UpperBound(expression) => {
893 ShapeExtent::UpperBound(expression)
894 }
895 ShapeExtent::Unknown => ShapeExtent::Unknown,
896 },
897 InputExtentPrecision::Unknown => ShapeExtent::Unknown,
898 })
899}
900
901fn resolve_inferred_extents(
902 extents: impl IntoIterator<Item = ShapeExtent<DimExpr>>,
903 precision: InputExtentPrecision,
904 input_shapes: &[&[DimExpr]],
905) -> Result<Vec<ShapeExtent<DimExpr>>, ProgramBuildError> {
906 extents
907 .into_iter()
908 .map(|extent| {
909 if matches!(precision, InputExtentPrecision::Unknown) {
910 return Ok(ShapeExtent::Unknown);
911 }
912 let resolved = match extent {
913 ShapeExtent::Exact(expression) => ShapeExtent::Exact(
914 crate::shape_infer::resolve_dim_expr_from_shapes(&expression, input_shapes)
915 .map_err(metadata_error)?,
916 ),
917 ShapeExtent::UpperBound(expression) => ShapeExtent::UpperBound(
918 crate::shape_infer::resolve_dim_expr_from_shapes(&expression, input_shapes)
919 .map_err(metadata_error)?,
920 ),
921 ShapeExtent::Unknown => ShapeExtent::Unknown,
922 };
923 Ok(match precision {
924 InputExtentPrecision::Exact => resolved,
925 InputExtentPrecision::Bounded => match resolved {
926 ShapeExtent::Exact(expression) | ShapeExtent::UpperBound(expression) => {
927 ShapeExtent::UpperBound(expression)
928 }
929 ShapeExtent::Unknown => ShapeExtent::Unknown,
930 },
931 InputExtentPrecision::Unknown => unreachable!("handled above"),
932 })
933 })
934 .collect()
935}
936
937fn inference_shapes(metadata: &[&ProgramValueMetadata]) -> Vec<Vec<DimExpr>> {
938 metadata
939 .iter()
940 .enumerate()
941 .map(|(input_idx, metadata)| {
942 metadata
943 .shape()
944 .iter()
945 .enumerate()
946 .map(|(axis, extent)| match extent {
947 ShapeExtent::Exact(expression) | ShapeExtent::UpperBound(expression) => {
948 expression.clone()
949 }
950 ShapeExtent::Unknown => DimExpr::InputDim { input_idx, axis },
951 })
952 .collect()
953 })
954 .collect()
955}
956
957fn metadata_error(source: crate::Error) -> ProgramBuildError {
958 ProgramBuildError::Metadata {
959 source: Box::new(source),
960 }
961}
962
963fn extension_effects(op: &dyn ExtensionOp) -> Result<Vec<Effect>, ProgramBuildError> {
964 let family = op.family_id();
965 let effects = match op.semantic_effects() {
966 ExtensionEffectDeclaration::Undeclared => {
967 return Err(ProgramBuildError::UndeclaredExtensionEffects { family })
968 }
969 ExtensionEffectDeclaration::Declared(effects) => effects,
970 };
971 effects
972 .iter()
973 .map(|effect| {
974 let resource = EffectResource::new(effect.family, effect.key)
975 .map_err(|source| ProgramBuildError::InvalidEffectResource { family, source })?;
976 let access = match effect.access {
977 ExtensionEffectAccess::Read => EffectAccess::Read,
978 ExtensionEffectAccess::Write => EffectAccess::Write,
979 };
980 Ok(Effect::new(resource, access))
981 })
982 .collect()
983}
984
985fn extension_aliases(op: &dyn ExtensionOp) -> Result<Vec<Alias>, ProgramBuildError> {
986 let family = op.family_id();
987 match op.semantic_aliases() {
988 ExtensionAliasDeclaration::Undeclared => {
989 Err(ProgramBuildError::UndeclaredExtensionAliases { family })
990 }
991 ExtensionAliasDeclaration::AllFresh => {
992 Ok((0..op.output_count()).map(Alias::fresh).collect())
993 }
994 ExtensionAliasDeclaration::Declared(aliases) => aliases
995 .iter()
996 .map(|alias| match *alias {
997 ExtensionAlias::Fresh { output } => Ok(Alias::fresh(output)),
998 ExtensionAlias::ViewOf { output, input } => Ok(Alias::view_of(output, input)),
999 ExtensionAlias::MustAlias { output, input } => Ok(Alias::must_alias(output, input)),
1000 ExtensionAlias::ExternalAlias {
1001 output,
1002 family: resource_family,
1003 key,
1004 } => EffectResource::new(resource_family, key)
1005 .map(|resource| Alias::external(output, resource))
1006 .map_err(|source| ProgramBuildError::InvalidEffectResource { family, source }),
1007 })
1008 .collect(),
1009 }
1010}
1011
1012fn validate_aliases(
1013 aliases: &[Alias],
1014 input_count: usize,
1015 output_count: usize,
1016) -> Result<(), ProgramBuildError> {
1017 let mut seen = vec![false; output_count];
1018 for &alias in aliases {
1019 let output = alias.output();
1020 let input = alias.input();
1021 if output >= output_count || input.is_some_and(|input| input >= input_count) {
1022 return Err(ProgramBuildError::AliasOutOfBounds {
1023 output,
1024 output_count,
1025 input,
1026 input_count,
1027 });
1028 }
1029 if seen[output] {
1030 return Err(ProgramBuildError::AliasCoverage {
1031 expected: output_count,
1032 actual: seen.iter().filter(|&&present| present).count(),
1033 });
1034 }
1035 seen[output] = true;
1036 }
1037 let actual = seen.iter().filter(|&&present| present).count();
1038 if actual != output_count {
1039 return Err(ProgramBuildError::AliasCoverage {
1040 expected: output_count,
1041 actual,
1042 });
1043 }
1044 Ok(())
1045}
1046
1047fn validate_structure(
1048 owner: ProgramBuilderNonce,
1049 inputs: &[ProgramValue],
1050 value_count: usize,
1051 operations: &[SemanticOperation],
1052) -> Result<(), ProgramFinishError> {
1053 let mut covered = vec![false; value_count];
1054 for input in inputs {
1055 if input.owner != owner
1056 || input.slot as usize >= value_count
1057 || covered[input.slot as usize]
1058 {
1059 return Err(ProgramFinishError::StructuralValidation {
1060 source: ProgramStructuralError::InvalidValueReference,
1061 });
1062 }
1063 covered[input.slot as usize] = true;
1064 }
1065 let mut previous_output = None;
1066 for operation in operations {
1067 let Some(first_output) = operation.outputs.first() else {
1068 if operation.inputs.iter().any(|value| {
1069 value.owner != owner
1070 || value.slot as usize >= value_count
1071 || !covered[value.slot as usize]
1072 }) {
1073 return Err(ProgramFinishError::StructuralValidation {
1074 source: ProgramStructuralError::InvalidValueReference,
1075 });
1076 }
1077 continue;
1078 };
1079 let output_start = first_output.slot as usize;
1080 let valid_input = operation.inputs.iter().all(|value| {
1081 value.owner == owner
1082 && (value.slot as usize) < output_start
1083 && (value.slot as usize) < value_count
1084 && covered[value.slot as usize]
1085 });
1086 let valid_output = operation.outputs.iter().enumerate().all(|(offset, value)| {
1087 value.owner == owner
1088 && value.slot as usize == output_start + offset
1089 && (value.slot as usize) < value_count
1090 && !covered[value.slot as usize]
1091 });
1092 let ordered = previous_output.is_none_or(|previous| output_start > previous);
1093 if !valid_input || !valid_output || !ordered {
1094 let source = if operation
1095 .inputs
1096 .iter()
1097 .chain(operation.outputs.iter())
1098 .any(|value| value.owner != owner || value.slot as usize >= value_count)
1099 {
1100 ProgramStructuralError::InvalidValueReference
1101 } else {
1102 ProgramStructuralError::InvalidSsaOrder
1103 };
1104 return Err(ProgramFinishError::StructuralValidation { source });
1105 }
1106 for output in &operation.outputs {
1107 covered[output.slot as usize] = true;
1108 }
1109 previous_output = operation.outputs.last().map(|value| value.slot as usize);
1110 }
1111 if covered.iter().any(|covered| !covered) {
1112 return Err(ProgramFinishError::StructuralValidation {
1113 source: ProgramStructuralError::InvalidSsaOrder,
1114 });
1115 }
1116 Ok(())
1117}
1118
1119fn validate_bindings(
1120 inputs: &[ProgramValue],
1121 input_specs: &[ProgramInputSpec],
1122 bindings: &[PendingBinding],
1123) -> Result<(), ProgramFinishError> {
1124 for binding in bindings {
1125 let input_index = inputs
1126 .iter()
1127 .position(|input| *input == binding.input)
1128 .ok_or(ProgramFinishError::BindingFinalization {
1129 source: ProgramBindingError::InvalidTarget,
1130 })?;
1131 let spec = &input_specs[input_index];
1132 let metadata = spec.metadata();
1133 let actual_dtype = binding.tensor.dtype();
1134 if actual_dtype != metadata.dtype() {
1135 return Err(ProgramFinishError::BindingFinalization {
1136 source: ProgramBindingError::DTypeMismatch {
1137 expected: metadata.dtype(),
1138 actual: actual_dtype,
1139 },
1140 });
1141 }
1142 let actual_shape = binding.tensor.shape();
1143 if actual_shape.len() != metadata.shape().len() {
1144 return Err(ProgramFinishError::BindingFinalization {
1145 source: ProgramBindingError::RankMismatch {
1146 expected: metadata.shape().len(),
1147 actual: actual_shape.len(),
1148 },
1149 });
1150 }
1151 for (axis, (extent, &actual)) in
1152 metadata.shape().iter().zip(actual_shape.iter()).enumerate()
1153 {
1154 match extent {
1155 ShapeExtent::Exact(DimExpr::Const(expected)) if *expected != actual => {
1156 return Err(ProgramFinishError::BindingFinalization {
1157 source: ProgramBindingError::ExactExtentMismatch {
1158 axis,
1159 expected: *expected,
1160 actual,
1161 },
1162 });
1163 }
1164 ShapeExtent::UpperBound(DimExpr::Const(bound)) if actual > *bound => {
1165 return Err(ProgramFinishError::BindingFinalization {
1166 source: ProgramBindingError::UpperBoundExceeded {
1167 axis,
1168 bound: *bound,
1169 actual,
1170 },
1171 });
1172 }
1173 _ => {}
1174 }
1175 }
1176 }
1177 Ok(())
1178}
1179
1180fn resolve_dim_expr_from_input_shapes(
1186 expr: &DimExpr,
1187 bound_input_shapes: &[Option<Vec<DimExpr>>],
1188) -> DimExpr {
1189 match expr {
1190 DimExpr::Const(_) => expr.clone(),
1191 DimExpr::InputDim { input_idx, axis } => {
1192 if let Some(Some(shape)) = bound_input_shapes.get(*input_idx) {
1193 if let Some(dim) = shape.get(*axis) {
1194 return dim.clone();
1195 }
1196 }
1197 expr.clone()
1198 }
1199 DimExpr::Add(a, b) => DimExpr::add(
1200 resolve_dim_expr_from_input_shapes(a, bound_input_shapes),
1201 resolve_dim_expr_from_input_shapes(b, bound_input_shapes),
1202 ),
1203 DimExpr::Sub(a, b) => DimExpr::sub(
1204 resolve_dim_expr_from_input_shapes(a, bound_input_shapes),
1205 resolve_dim_expr_from_input_shapes(b, bound_input_shapes),
1206 ),
1207 DimExpr::Mul(a, b) => DimExpr::mul(
1208 resolve_dim_expr_from_input_shapes(a, bound_input_shapes),
1209 resolve_dim_expr_from_input_shapes(b, bound_input_shapes),
1210 ),
1211 DimExpr::FloorDiv(a, b) => DimExpr::floor_div(
1212 resolve_dim_expr_from_input_shapes(a, bound_input_shapes),
1213 resolve_dim_expr_from_input_shapes(b, bound_input_shapes),
1214 ),
1215 DimExpr::Min(a, b) => DimExpr::min(
1216 resolve_dim_expr_from_input_shapes(a, bound_input_shapes),
1217 resolve_dim_expr_from_input_shapes(b, bound_input_shapes),
1218 ),
1219 DimExpr::Max(a, b) => DimExpr::max(
1220 resolve_dim_expr_from_input_shapes(a, bound_input_shapes),
1221 resolve_dim_expr_from_input_shapes(b, bound_input_shapes),
1222 ),
1223 }
1224}