1use tenferro_ops::dim_expr::DimExpr;
2use tenferro_ops::shape_extent::ShapeExtent;
3use tenferro_tensor::DType;
4
5use super::EffectResourceError;
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
9#[non_exhaustive]
10pub enum SemanticProvenanceKind {
11 Builder,
13 Imported,
15 Derived,
17}
18
19#[derive(Clone)]
20pub(crate) struct SemanticProvenance {
21 kind: SemanticProvenanceKind,
22 label: Option<std::sync::Arc<str>>,
23}
24
25impl SemanticProvenance {
26 pub(crate) fn builder(label: Option<&str>) -> Self {
27 Self {
28 kind: SemanticProvenanceKind::Builder,
29 label: label.map(std::sync::Arc::from),
30 }
31 }
32
33 pub(crate) fn view(&self) -> SemanticProvenanceView<'_> {
34 SemanticProvenanceView {
35 kind: self.kind,
36 label: self.label.as_deref(),
37 }
38 }
39}
40
41#[derive(Clone, Copy)]
43pub struct SemanticProvenanceView<'a> {
44 kind: SemanticProvenanceKind,
45 label: Option<&'a str>,
46}
47
48impl<'a> SemanticProvenanceView<'a> {
49 pub const fn kind(self) -> SemanticProvenanceKind {
51 self.kind
52 }
53
54 pub const fn label(self) -> Option<&'a str> {
56 self.label
57 }
58}
59
60impl std::fmt::Debug for SemanticProvenanceView<'_> {
61 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62 formatter
63 .debug_struct("SemanticProvenanceView")
64 .field("kind", &self.kind)
65 .field("has_label", &self.label.is_some())
66 .finish()
67 }
68}
69
70#[derive(Clone, Debug, PartialEq, Eq, Hash)]
72pub struct ProgramValueMetadata {
73 dtype: DType,
74 shape: Box<[ShapeExtent<DimExpr>]>,
75 scalar_identity: Option<&'static str>,
76}
77
78impl ProgramValueMetadata {
79 pub fn new(dtype: DType, shape: impl IntoIterator<Item = DimExpr>) -> Self {
81 Self {
82 dtype,
83 shape: shape.into_iter().map(ShapeExtent::Exact).collect(),
84 scalar_identity: None,
85 }
86 }
87
88 pub fn from_extents(
90 dtype: DType,
91 shape: impl IntoIterator<Item = ShapeExtent<DimExpr>>,
92 ) -> Self {
93 Self {
94 dtype,
95 shape: shape.into_iter().collect(),
96 scalar_identity: None,
97 }
98 }
99
100 #[must_use]
121 pub fn with_scalar_identity(mut self, identity: &'static str) -> Self {
122 self.scalar_identity = Some(identity);
123 self
124 }
125
126 #[must_use]
128 pub const fn scalar_identity(&self) -> Option<&'static str> {
129 self.scalar_identity
130 }
131
132 pub const fn dtype(&self) -> DType {
134 self.dtype
135 }
136
137 pub fn shape(&self) -> &[ShapeExtent<DimExpr>] {
139 &self.shape
140 }
141
142 pub(crate) fn logical_retained_bytes(&self) -> Option<usize> {
143 checked_sum([
144 self.shape
145 .len()
146 .checked_mul(std::mem::size_of::<ShapeExtent<DimExpr>>())?,
147 checked_sum_options(self.shape.iter().map(shape_extent_logical_retained_bytes))?,
148 ])
149 }
150}
151
152#[derive(Clone, Debug, PartialEq, Eq, Hash)]
154pub struct ProgramInputSpec {
155 metadata: ProgramValueMetadata,
156}
157
158impl ProgramInputSpec {
159 pub fn new(dtype: DType, shape: impl IntoIterator<Item = DimExpr>) -> Self {
161 Self {
162 metadata: ProgramValueMetadata::new(dtype, shape),
163 }
164 }
165
166 #[must_use]
183 pub fn with_scalar_identity(mut self, identity: &'static str) -> Self {
184 self.metadata = self.metadata.with_scalar_identity(identity);
185 self
186 }
187
188 pub fn from_metadata(metadata: ProgramValueMetadata) -> Self {
190 Self { metadata }
191 }
192
193 pub const fn metadata(&self) -> &ProgramValueMetadata {
195 &self.metadata
196 }
197}
198
199#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
201#[non_exhaustive]
202pub enum ProgramShapeRelation {
203 Equal,
205 LessEqual,
207 GreaterEqual,
209}
210
211#[derive(Clone, Debug)]
213pub struct ShapeGuard {
214 relation: ProgramShapeRelation,
215 lhs: DimExpr,
216 rhs: DimExpr,
217 source_family: Option<&'static str>,
218}
219
220impl ShapeGuard {
221 pub fn new(relation: ProgramShapeRelation, lhs: DimExpr, rhs: DimExpr) -> Self {
223 Self {
224 relation,
225 lhs,
226 rhs,
227 source_family: None,
228 }
229 }
230
231 #[cfg(test)]
232 pub(crate) fn with_source_family(mut self, family: &'static str) -> Self {
233 self.source_family = Some(family);
234 self
235 }
236
237 pub const fn relation(&self) -> ProgramShapeRelation {
239 self.relation
240 }
241
242 pub const fn lhs(&self) -> &DimExpr {
244 &self.lhs
245 }
246
247 pub const fn rhs(&self) -> &DimExpr {
249 &self.rhs
250 }
251
252 pub const fn source_family(&self) -> Option<&'static str> {
257 self.source_family
258 }
259
260 pub(crate) fn logical_retained_bytes(&self) -> Option<usize> {
261 checked_sum([
262 dim_expr_logical_retained_bytes(&self.lhs)?,
263 dim_expr_logical_retained_bytes(&self.rhs)?,
264 ])
265 }
266}
267
268impl PartialEq for ShapeGuard {
269 fn eq(&self, other: &Self) -> bool {
270 self.relation == other.relation && self.lhs == other.lhs && self.rhs == other.rhs
271 }
272}
273
274impl Eq for ShapeGuard {}
275
276impl std::hash::Hash for ShapeGuard {
277 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
278 self.relation.hash(state);
279 self.lhs.hash(state);
280 self.rhs.hash(state);
281 }
282}
283
284fn shape_extent_logical_retained_bytes(extent: &ShapeExtent<DimExpr>) -> Option<usize> {
285 match extent {
286 ShapeExtent::Exact(expression) | ShapeExtent::UpperBound(expression) => {
287 dim_expr_logical_retained_bytes(expression)
288 }
289 ShapeExtent::Unknown => Some(0),
290 }
291}
292
293fn dim_expr_logical_retained_bytes(expression: &DimExpr) -> Option<usize> {
294 match expression {
295 DimExpr::Const(_) | DimExpr::InputDim { .. } => Some(0),
296 DimExpr::Add(left, right)
297 | DimExpr::Sub(left, right)
298 | DimExpr::Mul(left, right)
299 | DimExpr::FloorDiv(left, right)
300 | DimExpr::Min(left, right)
301 | DimExpr::Max(left, right) => checked_sum([
302 2usize.checked_mul(std::mem::size_of::<DimExpr>())?,
303 dim_expr_logical_retained_bytes(left)?,
304 dim_expr_logical_retained_bytes(right)?,
305 ]),
306 }
307}
308
309fn checked_sum(values: impl IntoIterator<Item = usize>) -> Option<usize> {
310 values
311 .into_iter()
312 .try_fold(0usize, |sum, value| sum.checked_add(value))
313}
314
315fn checked_sum_options(values: impl IntoIterator<Item = Option<usize>>) -> Option<usize> {
316 values
317 .into_iter()
318 .try_fold(0usize, |sum, value| sum.checked_add(value?))
319}
320
321#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
323pub struct EffectResource {
324 family: &'static str,
325 key: u64,
326}
327
328impl EffectResource {
329 pub fn new(family: &'static str, key: u64) -> Result<Self, EffectResourceError> {
336 let version = family.rsplit_once(".v").map(|(_, version)| version);
337 if family.is_empty()
338 || !version.is_some_and(|version| {
339 !version.is_empty() && version.bytes().all(|byte| byte.is_ascii_digit())
340 })
341 {
342 return Err(EffectResourceError::InvalidFamily);
343 }
344 Ok(Self { family, key })
345 }
346
347 pub const fn family(self) -> &'static str {
349 self.family
350 }
351
352 pub const fn key(self) -> u64 {
354 self.key
355 }
356}
357
358#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
360pub enum EffectAccess {
361 Read,
363 Write,
365}
366
367#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
369pub struct Effect {
370 resource: EffectResource,
371 access: EffectAccess,
372}
373
374impl Effect {
375 pub const fn new(resource: EffectResource, access: EffectAccess) -> Self {
377 Self { resource, access }
378 }
379
380 pub const fn resource(self) -> EffectResource {
382 self.resource
383 }
384
385 pub const fn access(self) -> EffectAccess {
387 self.access
388 }
389}
390
391#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
393pub enum AliasKind {
394 Fresh,
396 ViewOf,
398 MustAlias,
400 ExternalAlias,
402}
403
404#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
406pub struct Alias {
407 kind: AliasKind,
408 output: usize,
409 input: Option<usize>,
410 resource: Option<EffectResource>,
411}
412
413impl Alias {
414 pub const fn fresh(output: usize) -> Self {
416 Self {
417 kind: AliasKind::Fresh,
418 output,
419 input: None,
420 resource: None,
421 }
422 }
423
424 pub const fn view_of(output: usize, input: usize) -> Self {
426 Self {
427 kind: AliasKind::ViewOf,
428 output,
429 input: Some(input),
430 resource: None,
431 }
432 }
433
434 pub const fn must_alias(output: usize, input: usize) -> Self {
436 Self {
437 kind: AliasKind::MustAlias,
438 output,
439 input: Some(input),
440 resource: None,
441 }
442 }
443
444 pub const fn external(output: usize, resource: EffectResource) -> Self {
446 Self {
447 kind: AliasKind::ExternalAlias,
448 output,
449 input: None,
450 resource: Some(resource),
451 }
452 }
453
454 pub const fn kind(self) -> AliasKind {
456 self.kind
457 }
458
459 pub const fn output(self) -> usize {
461 self.output
462 }
463
464 pub const fn input(self) -> Option<usize> {
466 self.input
467 }
468
469 pub const fn resource(self) -> Option<EffectResource> {
471 self.resource
472 }
473}
474
475#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
477#[non_exhaustive]
478pub enum SemanticPlacementKind {
479 Any,
481 SameAsInput,
483}
484
485#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
487pub struct SemanticPlacementConstraint {
488 kind: SemanticPlacementKind,
489 input: Option<usize>,
490}
491
492impl SemanticPlacementConstraint {
493 pub const fn any() -> Self {
495 Self {
496 kind: SemanticPlacementKind::Any,
497 input: None,
498 }
499 }
500
501 pub const fn same_as_input(input: usize) -> Self {
503 Self {
504 kind: SemanticPlacementKind::SameAsInput,
505 input: Some(input),
506 }
507 }
508
509 pub const fn kind(self) -> SemanticPlacementKind {
511 self.kind
512 }
513
514 pub const fn input(self) -> Option<usize> {
516 self.input
517 }
518}