Skip to main content

tenferro_runtime/program/
builder.rs

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
26/// Mutable validation boundary for one semantic program.
27pub 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    /// Construct an empty builder with a fresh opaque identity.
44    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    /// Attach a tensor default or large constant to one external input.
56    ///
57    /// # Errors
58    ///
59    /// Returns [`ProgramBuildError::ForeignValue`] for a token from another
60    /// builder, [`ProgramBuildError::BindingTargetNotInput`] for a computed
61    /// value, or [`ProgramBuildError::DuplicateBinding`] when the input already
62    /// has a binding.
63    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    /// Add one ordered external input.
96    ///
97    /// # Errors
98    ///
99    /// Returns [`ProgramBuildError::TooManyValues`] if the builder cannot
100    /// represent another value slot.
101    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    /// Validate that a value belongs to this builder.
111    ///
112    /// # Errors
113    ///
114    /// Returns [`ProgramBuildError::ForeignValue`] for a token from another
115    /// builder or one that does not name an existing value.
116    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    /// Borrow metadata for a builder-local value.
124    ///
125    /// # Errors
126    ///
127    /// Returns [`ProgramBuildError::ForeignValue`] for a foreign token.
128    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    /// Return the number of semantic operations added so far.
137    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    /// Import the dependency closure of ordered source roots atomically.
159    ///
160    /// Empty and duplicate roots are preserved. Tensor bindings remain
161    /// separate and are remapped only for imported source inputs.
162    ///
163    /// # Errors
164    ///
165    /// Returns [`ProgramBuildError::ForeignImportRoot`] for a root outside the
166    /// source program, [`ProgramBuildError::ForeignBindings`] for bindings
167    /// frozen with another program, [`ProgramBuildError::InvalidImport`] for
168    /// invalid source structure, or [`ProgramBuildError::TooManyValues`] when
169    /// the destination cannot represent the imported values. On error this
170    /// builder is unchanged.
171    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    /// Consume this builder and atomically freeze semantic structure and bindings.
186    ///
187    /// # Errors
188    ///
189    /// Returns [`ProgramFinishError::ForeignOutput`] for an output outside this
190    /// builder, [`ProgramFinishError::StructuralValidation`] for invalid SSA
191    /// structure, or [`ProgramFinishError::BindingFinalization`] when a tensor
192    /// binding does not match its input declaration.
193    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    /// Add one canonical core semantic operation.
245    ///
246    /// # Errors
247    ///
248    /// Returns a typed build error for foreign values, wrong arity, invalid
249    /// metadata, or an unrepresentable output count.
250    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    /// Add one extension semantic operation with explicit effects and aliases.
272    ///
273    /// # Examples
274    ///
275    /// ```
276    /// use std::any::Any;
277    /// use std::hash::Hasher;
278    /// use std::sync::Arc;
279    /// use tenferro_ops::dim_expr::DimExpr;
280    /// use tenferro_ops::ext_op::{
281    ///     ExtensionAliasDeclaration, ExtensionEffectDeclaration, ExtensionOp,
282    /// };
283    /// use tenferro_ops::{ExtensionShapeContext, SymDim};
284    /// use tenferro_runtime::program::{ProgramInputSpec, SemanticProgramBuilder};
285    /// use tenferro_tensor::DType;
286    ///
287    /// #[derive(Clone, Debug)]
288    /// struct Identity;
289    /// impl ExtensionOp for Identity {
290    ///     fn family_id(&self) -> &'static str { "example.identity.v1" }
291    ///     fn payload_hash(&self, hasher: &mut dyn Hasher) {
292    ///         hasher.write_u8(1);
293    ///     }
294    ///     fn payload_eq(&self, other: &dyn ExtensionOp) -> bool {
295    ///         other.as_any().is::<Self>()
296    ///     }
297    ///     fn clone_arc(&self) -> Arc<dyn ExtensionOp> {
298    ///         Arc::new(self.clone())
299    ///     }
300    ///     fn as_any(&self) -> &dyn Any { self }
301    ///     fn input_count(&self) -> usize { 1 }
302    ///     fn output_count(&self) -> usize { 1 }
303    ///     fn infer_output_meta(
304    ///         &self,
305    ///         context: &mut ExtensionShapeContext<'_>,
306    ///     ) -> tenferro_tensor::Result<Vec<(DType, Vec<SymDim>)>> {
307    ///         Ok(vec![(
308    ///             context.input_dtype(0)?,
309    ///             context.input_shape(0)?.to_vec(),
310    ///         )])
311    ///     }
312    ///     fn semantic_effects(&self) -> ExtensionEffectDeclaration<'_> {
313    ///         ExtensionEffectDeclaration::Declared(&[])
314    ///     }
315    ///     fn semantic_aliases(&self) -> ExtensionAliasDeclaration<'_> {
316    ///         ExtensionAliasDeclaration::AllFresh
317    ///     }
318    /// }
319    ///
320    /// let mut builder = SemanticProgramBuilder::new();
321    /// let input = builder.input(ProgramInputSpec::new(
322    ///     DType::F64,
323    ///     [DimExpr::Const(2)],
324    /// ))?;
325    /// let output = builder.add_extension(Arc::new(Identity), &[input])?[0];
326    /// let frozen = builder.finish(&[output])?;
327    /// assert_eq!(frozen.program.operations().count(), 1);
328    /// # Ok::<(), Box<dyn std::error::Error>>(())
329    /// ```
330    ///
331    /// # Errors
332    ///
333    /// Returns a typed build error when the payload leaves effects or aliases
334    /// undeclared, metadata inference fails, or any value/arity/alias is
335    /// invalid.
336    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    /// Return ordered input metadata after resolving bound input dimensions.
542    ///
543    /// Bound tensor shapes are process-local and intentionally live outside the
544    /// semantic identity, but AD/runtime caches that prepare shape-specialized
545    /// programs must distinguish those concrete shapes.
546    #[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    /// Return a clone of this frozen program with tensor bindings copied from
585    /// `source` onto this program's input prefix.
586    ///
587    /// This is intentionally narrow: semantic AD transforms import every source
588    /// primal input first, then append derivative seed inputs. Cached derivative
589    /// program structure can therefore be reused across source programs with the
590    /// same normalized semantics while still carrying the current source's
591    /// process-local tensor defaults.
592    #[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        // Pre-resolve concrete input shapes from tensor bindings for InputDim
740        // resolution in imported metadata.
741        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
1180/// Resolve [`DimExpr::InputDim`] references using concrete bound-input shapes.
1181///
1182/// When an input has a known tensor binding, its concrete shape replaces the
1183/// symbolic `InputDim { input_idx, axis }` reference. References to unbound
1184/// inputs are left unchanged.
1185fn 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}