Skip to main content

ferrum_interfaces/vnext/operation/
buffer_view.rs

1use std::ops::Range;
2
3use super::super::{
4    AllocationLifetime, BatchWorkShape, BufferDescriptor, BufferUsage, DeviceBufferRetention,
5    DeviceRuntime, DynamicResourceDemand, DynamicResourceShape, LeasedBufferView,
6    LogicalBackingBufferView, LogicalBackingSegmentBinding, NodeWorkContract, ResourceId,
7    ResourceTransactionIdentity, VNextError,
8};
9use super::foundation::invalid_operation;
10use super::{DynamicStorageView, ResolvedStorageComponent, ResolvedValueBinding};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub(super) enum ValueBindingPhysicalCoverage {
14    CanonicalComponent,
15    RuntimeTokenView,
16}
17
18pub(super) fn validate_value_binding_physical_coverage(
19    work: &NodeWorkContract,
20    binding: &ResolvedValueBinding,
21    component: &ResolvedStorageComponent,
22    descriptor: &BufferDescriptor,
23    dynamic_demand: Option<&DynamicResourceDemand>,
24    value_alignment_bytes: u64,
25) -> Result<ValueBindingPhysicalCoverage, VNextError> {
26    let required_end = component
27        .offset_bytes()
28        .checked_add(component.length_bytes())
29        .ok_or_else(|| invalid_operation("bound component range overflows u64"))?;
30    if descriptor.usage != binding.usage()
31        || descriptor.element_type != component.element_type()
32        || descriptor.alignment_bytes < value_alignment_bytes
33        || descriptor.alignment_bytes % value_alignment_bytes != 0
34        || component.offset_bytes() % value_alignment_bytes != 0
35    {
36        return Err(invalid_operation(format!(
37            "resource `{}` differs from its value binding",
38            component.resource_id()
39        )));
40    }
41
42    let Some(projection) = work.token_projection(binding.role(), binding.ordinal()) else {
43        if required_end > descriptor.size_bytes {
44            return Err(invalid_operation(format!(
45                "resource `{}` differs from its value binding",
46                component.resource_id()
47            )));
48        }
49        return Ok(ValueBindingPhysicalCoverage::CanonicalComponent);
50    };
51
52    let axis = usize::try_from(projection.axis())
53        .map_err(|_| invalid_operation("token projection axis exceeds usize"))?;
54    let canonical_extent = projection.canonical_extent();
55    let bytes_per_token = component
56        .length_bytes()
57        .checked_div(canonical_extent)
58        .filter(|bytes| *bytes > 0)
59        .ok_or_else(|| invalid_operation("token projection has zero canonical extent"))?;
60    let demand_matches = matches!(
61        dynamic_demand,
62        Some(DynamicResourceDemand::Tokens {
63            bytes_per_token: planned_bytes_per_token,
64            ..
65        }) if *planned_bytes_per_token == bytes_per_token
66    );
67    if binding.usage() != BufferUsage::Activations
68        || component.offset_bytes() != 0
69        || component.length_bytes() % canonical_extent != 0
70        || usize::try_from(projection.rank()).ok() != Some(binding.tensor().dimensions().len())
71        || binding.tensor().dimensions().get(axis) != Some(&canonical_extent)
72        || !demand_matches
73        || descriptor.size_bytes < bytes_per_token
74    {
75        return Err(invalid_operation(format!(
76            "resource `{}` differs from its token-projected value binding",
77            component.resource_id()
78        )));
79    }
80
81    Ok(ValueBindingPhysicalCoverage::RuntimeTokenView)
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85enum StepParticipantRangeCoordinates {
86    SourceToken,
87    ParticipantLocal,
88}
89
90/// Maps source-token coordinates used by product input uploads into shared
91/// Step backing. Fixed participant values already use local coordinates.
92pub(crate) fn translate_step_participant_upload_range(
93    demand: &DynamicResourceDemand,
94    work_shape: &BatchWorkShape,
95    participant_index: usize,
96    semantic_range: Range<u64>,
97) -> Result<Range<u64>, VNextError> {
98    translate_step_participant_range(
99        demand,
100        work_shape,
101        participant_index,
102        semantic_range,
103        StepParticipantRangeCoordinates::SourceToken,
104    )
105}
106
107/// Maps participant-local completion coordinates into shared Step backing.
108pub(crate) fn translate_step_participant_readback_range(
109    demand: &DynamicResourceDemand,
110    work_shape: &BatchWorkShape,
111    participant_index: usize,
112    semantic_range: Range<u64>,
113) -> Result<Range<u64>, VNextError> {
114    translate_step_participant_range(
115        demand,
116        work_shape,
117        participant_index,
118        semantic_range,
119        StepParticipantRangeCoordinates::ParticipantLocal,
120    )
121}
122
123/// Converts one provider-visible packed token range back into the
124/// participant-local coordinates required by completion readback requests.
125/// The inverse is explicit so a packed range cannot be projected twice.
126pub(crate) fn packed_step_token_range_to_participant_local_readback(
127    demand: &DynamicResourceDemand,
128    work_shape: &BatchWorkShape,
129    participant_index: usize,
130    packed_range: Range<u64>,
131) -> Result<Range<u64>, VNextError> {
132    if packed_range.start >= packed_range.end {
133        return Err(invalid_operation(
134            "packed participant token readback range is empty",
135        ));
136    }
137    let token_range = work_shape
138        .participant_token_ranges()
139        .get(participant_index)
140        .ok_or_else(|| invalid_operation("packed participant token readback is out of range"))?;
141    let DynamicResourceDemand::Tokens {
142        bytes_per_token,
143        maximum_tokens,
144    } = demand
145    else {
146        return Err(invalid_operation(
147            "packed participant token readback requires token demand",
148        ));
149    };
150    if work_shape.immediate_tokens() > *maximum_tokens {
151        return Err(invalid_operation(
152            "packed participant token readback exceeds its planned ceiling",
153        ));
154    }
155    let participant_start = token_range
156        .immediate_token_range()
157        .start
158        .checked_mul(*bytes_per_token)
159        .ok_or_else(|| invalid_operation("packed participant token offset overflows u64"))?;
160    let participant_end = token_range
161        .immediate_token_range()
162        .end
163        .checked_mul(*bytes_per_token)
164        .ok_or_else(|| invalid_operation("packed participant token range overflows u64"))?;
165    if packed_range.start < participant_start || packed_range.end > participant_end {
166        return Err(invalid_operation(
167            "packed participant token readback is outside its immediate span",
168        ));
169    }
170    Ok(packed_range.start - participant_start..packed_range.end - participant_start)
171}
172
173fn translate_step_participant_range(
174    demand: &DynamicResourceDemand,
175    work_shape: &BatchWorkShape,
176    participant_index: usize,
177    semantic_range: Range<u64>,
178    coordinates: StepParticipantRangeCoordinates,
179) -> Result<Range<u64>, VNextError> {
180    if semantic_range.start >= semantic_range.end {
181        return Err(invalid_operation(
182            "participant resource projection has an empty semantic range",
183        ));
184    }
185    if participant_index >= work_shape.participant_token_ranges().len() {
186        return Err(invalid_operation(
187            "participant resource projection is out of range",
188        ));
189    }
190    match demand {
191        DynamicResourceDemand::ActualSequences {
192            bytes_per_sequence,
193            maximum_sequences,
194        } => {
195            if work_shape.immediate_sequences() > *maximum_sequences
196                || semantic_range.end > *bytes_per_sequence
197            {
198                return Err(invalid_operation(
199                    "participant fixed resource projection exceeds its planned stride",
200                ));
201            }
202            let base = bytes_per_sequence
203                .checked_mul(u64::try_from(participant_index).map_err(|_| {
204                    invalid_operation("participant resource projection exceeds u64")
205                })?)
206                .ok_or_else(|| {
207                    invalid_operation("participant fixed resource projection overflows u64")
208                })?;
209            let translated_start = base
210                .checked_add(semantic_range.start)
211                .ok_or_else(|| invalid_operation("participant fixed range overflows u64"))?;
212            let translated_end = base
213                .checked_add(semantic_range.end)
214                .ok_or_else(|| invalid_operation("participant fixed range overflows u64"))?;
215            Ok(translated_start..translated_end)
216        }
217        DynamicResourceDemand::Tokens {
218            bytes_per_token,
219            maximum_tokens,
220        } => {
221            if work_shape.immediate_tokens() > *maximum_tokens {
222                return Err(invalid_operation(
223                    "participant token resource projection exceeds its planned ceiling",
224                ));
225            }
226            let token_range = &work_shape.participant_token_ranges()[participant_index];
227            let source = token_range.source_token_range();
228            let packed = token_range.immediate_token_range();
229            let source_start = source
230                .start
231                .checked_mul(*bytes_per_token)
232                .ok_or_else(|| invalid_operation("source token byte offset overflows u64"))?;
233            let source_end = source
234                .end
235                .checked_mul(*bytes_per_token)
236                .ok_or_else(|| invalid_operation("source token byte range overflows u64"))?;
237            let packed_start = packed
238                .start
239                .checked_mul(*bytes_per_token)
240                .ok_or_else(|| invalid_operation("packed token byte offset overflows u64"))?;
241            let relative_range = match coordinates {
242                StepParticipantRangeCoordinates::SourceToken => {
243                    if semantic_range.start < source_start || semantic_range.end > source_end {
244                        return Err(invalid_operation(
245                            "participant token resource projection is outside its source span",
246                        ));
247                    }
248                    semantic_range.start - source_start..semantic_range.end - source_start
249                }
250                StepParticipantRangeCoordinates::ParticipantLocal => {
251                    let span_bytes = source_end - source_start;
252                    if semantic_range.end > span_bytes {
253                        return Err(invalid_operation(
254                            "participant token readback exceeds its immediate span",
255                        ));
256                    }
257                    semantic_range
258                }
259            };
260            let translated_start = packed_start
261                .checked_add(relative_range.start)
262                .ok_or_else(|| invalid_operation("packed token projection overflows u64"))?;
263            let translated_end = translated_start
264                .checked_add(relative_range.end - relative_range.start)
265                .ok_or_else(|| invalid_operation("packed token range overflows u64"))?;
266            Ok(translated_start..translated_end)
267        }
268        // Fixed/page-shaped internal activations are batch-wide resources. The
269        // planner gives product-fixed I/O an ActualSequences demand, so only
270        // that typed demand is participant-local here.
271        _ => Ok(semantic_range),
272    }
273}
274
275#[derive(Debug, Clone, Copy, PartialEq, Eq)]
276pub enum OperationBufferStorageKind {
277    StaticContiguous,
278    DynamicContiguous,
279    DynamicPaged,
280}
281
282enum OperationBufferSource<'a, B> {
283    Static {
284        view: LeasedBufferView<'a, B>,
285        retention: DeviceBufferRetention,
286    },
287    Backing(LogicalBackingBufferView<'a, B>),
288}
289
290#[derive(Debug, Clone, Copy, PartialEq, Eq)]
291enum OperationBufferCoverage {
292    Exact,
293    /// The operation sees an exact logical prefix while resource authority
294    /// retains wider physical capacity for a frontier or reusable bucket.
295    BackingWindow {
296        offset_bytes: u64,
297    },
298}
299
300enum OperationRegionSource<'a, B> {
301    Contiguous {
302        buffer: &'a B,
303        physical_base_offset_bytes: u64,
304        retention: DeviceBufferRetention,
305    },
306    Paged {
307        bindings: &'a [LogicalBackingSegmentBinding<B>],
308        logical_origin_bytes: u64,
309    },
310}
311
312/// A checked logical range translated to physical device-buffer regions.
313/// Dynamic buffers never expose an arena buffer without its physical offsets.
314pub struct OperationBufferRegions<'a, B> {
315    storage_kind: OperationBufferStorageKind,
316    logical_offset_bytes: u64,
317    logical_length_bytes: u64,
318    source: OperationRegionSource<'a, B>,
319}
320
321impl<'a, B> OperationBufferRegions<'a, B> {
322    pub const fn storage_kind(&self) -> OperationBufferStorageKind {
323        self.storage_kind
324    }
325
326    pub const fn logical_offset_bytes(&self) -> u64 {
327        self.logical_offset_bytes
328    }
329
330    pub const fn logical_length_bytes(&self) -> u64 {
331        self.logical_length_bytes
332    }
333
334    pub fn iter(&self) -> OperationBufferRegionIter<'a, B> {
335        let logical_end_bytes = self
336            .logical_offset_bytes
337            .checked_add(self.logical_length_bytes)
338            .expect("validated operation logical range does not overflow");
339        match &self.source {
340            OperationRegionSource::Contiguous {
341                buffer,
342                physical_base_offset_bytes,
343                retention,
344            } => OperationBufferRegionIter {
345                state: OperationBufferRegionIterState::Contiguous(Some(OperationPhysicalRegion {
346                    buffer: *buffer,
347                    logical_offset_bytes: self.logical_offset_bytes,
348                    physical_offset_bytes: physical_base_offset_bytes
349                        .checked_add(self.logical_offset_bytes)
350                        .expect("validated contiguous physical range does not overflow"),
351                    length_bytes: self.logical_length_bytes,
352                    retention: retention.clone(),
353                })),
354            },
355            OperationRegionSource::Paged {
356                bindings,
357                logical_origin_bytes,
358            } => OperationBufferRegionIter {
359                state: OperationBufferRegionIterState::Paged {
360                    bindings: *bindings,
361                    logical_origin_bytes: *logical_origin_bytes,
362                    requested_start_bytes: logical_origin_bytes
363                        .checked_add(self.logical_offset_bytes)
364                        .expect("validated paged window start does not overflow"),
365                    requested_end_bytes: logical_origin_bytes
366                        .checked_add(logical_end_bytes)
367                        .expect("validated paged window end does not overflow"),
368                    next_segment: 0,
369                    next_segment_logical_offset_bytes: 0,
370                },
371            },
372        }
373    }
374}
375
376/// One indivisible physical region. The buffer reference is intentionally
377/// returned only together with the physical byte range.
378pub struct OperationPhysicalRegion<'a, B> {
379    buffer: &'a B,
380    logical_offset_bytes: u64,
381    physical_offset_bytes: u64,
382    length_bytes: u64,
383    retention: DeviceBufferRetention,
384}
385
386impl<'a, B> OperationPhysicalRegion<'a, B> {
387    pub const fn logical_offset_bytes(&self) -> u64 {
388        self.logical_offset_bytes
389    }
390
391    pub const fn length_bytes(&self) -> u64 {
392        self.length_bytes
393    }
394
395    pub fn buffer_and_physical_range(
396        &self,
397    ) -> (&'a B, std::ops::Range<u64>, DeviceBufferRetention) {
398        (
399            self.buffer,
400            self.physical_offset_bytes
401                ..self
402                    .physical_offset_bytes
403                    .checked_add(self.length_bytes)
404                    .expect("validated physical region does not overflow"),
405            self.retention.clone(),
406        )
407    }
408}
409
410pub struct OperationBufferRegionIter<'a, B> {
411    state: OperationBufferRegionIterState<'a, B>,
412}
413
414enum OperationBufferRegionIterState<'a, B> {
415    Contiguous(Option<OperationPhysicalRegion<'a, B>>),
416    Paged {
417        bindings: &'a [LogicalBackingSegmentBinding<B>],
418        logical_origin_bytes: u64,
419        requested_start_bytes: u64,
420        requested_end_bytes: u64,
421        next_segment: usize,
422        next_segment_logical_offset_bytes: u64,
423    },
424}
425
426impl<'a, B> Iterator for OperationBufferRegionIter<'a, B> {
427    type Item = OperationPhysicalRegion<'a, B>;
428
429    fn next(&mut self) -> Option<Self::Item> {
430        match &mut self.state {
431            OperationBufferRegionIterState::Contiguous(region) => region.take(),
432            OperationBufferRegionIterState::Paged {
433                bindings,
434                logical_origin_bytes,
435                requested_start_bytes,
436                requested_end_bytes,
437                next_segment,
438                next_segment_logical_offset_bytes,
439            } => {
440                while let Some(binding) = bindings.get(*next_segment) {
441                    *next_segment += 1;
442                    let (segment_logical_end, region) = translate_paged_segment(
443                        binding.buffer(),
444                        binding.retention(),
445                        binding.segment().offset_bytes(),
446                        binding.segment().length_bytes(),
447                        *next_segment_logical_offset_bytes,
448                        *requested_start_bytes,
449                        *requested_end_bytes,
450                    );
451                    *next_segment_logical_offset_bytes = segment_logical_end;
452                    if let Some(mut region) = region {
453                        region.logical_offset_bytes = region
454                            .logical_offset_bytes
455                            .checked_sub(*logical_origin_bytes)
456                            .expect("paged region is inside its validated backing window");
457                        return Some(region);
458                    }
459                }
460                None
461            }
462        }
463    }
464}
465
466fn translate_paged_segment<'a, B>(
467    buffer: &'a B,
468    retention: DeviceBufferRetention,
469    physical_offset_bytes: u64,
470    length_bytes: u64,
471    logical_start_bytes: u64,
472    requested_start_bytes: u64,
473    requested_end_bytes: u64,
474) -> (u64, Option<OperationPhysicalRegion<'a, B>>) {
475    let logical_end_bytes = logical_start_bytes
476        .checked_add(length_bytes)
477        .expect("validated backing segments do not overflow");
478    let translated_start = logical_start_bytes.max(requested_start_bytes);
479    let translated_end = logical_end_bytes.min(requested_end_bytes);
480    let region = (translated_start < translated_end).then(|| OperationPhysicalRegion {
481        buffer,
482        logical_offset_bytes: translated_start,
483        physical_offset_bytes: physical_offset_bytes
484            .checked_add(translated_start - logical_start_bytes)
485            .expect("validated paged physical range does not overflow"),
486        length_bytes: translated_end - translated_start,
487        retention,
488    });
489    (logical_end_bytes, region)
490}
491
492const fn operation_storage_kind(view: DynamicStorageView) -> OperationBufferStorageKind {
493    match view {
494        DynamicStorageView::Contiguous => OperationBufferStorageKind::DynamicContiguous,
495        DynamicStorageView::PagedRegions { .. } => OperationBufferStorageKind::DynamicPaged,
496    }
497}
498
499fn validate_dynamic_binding_layout(
500    storage_kind: OperationBufferStorageKind,
501    logical_size_bytes: u64,
502    mut binding_lengths: impl ExactSizeIterator<Item = u64>,
503    coverage: OperationBufferCoverage,
504) -> Result<(), VNextError> {
505    let binding_count = binding_lengths.len();
506    if storage_kind == OperationBufferStorageKind::StaticContiguous {
507        return Err(invalid_operation(
508            "dynamic backing cannot claim static storage kind",
509        ));
510    }
511    if binding_count == 0 {
512        return Err(invalid_operation(
513            "dynamic backing has no physical segment binding",
514        ));
515    }
516    if storage_kind == OperationBufferStorageKind::DynamicContiguous && binding_count != 1 {
517        return Err(invalid_operation(
518            "contiguous dynamic storage requires one physical segment binding",
519        ));
520    }
521    let covered = binding_lengths.try_fold(0_u64, |total, length_bytes| {
522        total
523            .checked_add(length_bytes)
524            .ok_or_else(|| invalid_operation("backing segment coverage overflows u64"))
525    })?;
526    let window_offset = match coverage {
527        OperationBufferCoverage::Exact => 0,
528        OperationBufferCoverage::BackingWindow { offset_bytes } => offset_bytes,
529    };
530    let required_end = window_offset
531        .checked_add(logical_size_bytes)
532        .ok_or_else(|| invalid_operation("operation backing window overflows u64"))?;
533    if covered < required_end
534        || (coverage == OperationBufferCoverage::Exact && covered != logical_size_bytes)
535    {
536        return Err(invalid_operation(
537            "dynamic backing segments do not cover the operation's exact logical view",
538        ));
539    }
540    Ok(())
541}
542
543pub(super) fn sequence_execution_shape(
544    committed: DynamicResourceShape,
545    source_end_tokens: u64,
546) -> Result<DynamicResourceShape, VNextError> {
547    if committed.sequences() != 1
548        || source_end_tokens == 0
549        || source_end_tokens > committed.tokens()
550    {
551        return Err(invalid_operation(
552            "sequence operation frontier is empty or exceeds committed backing",
553        ));
554    }
555    Ok(DynamicResourceShape::from_validated(
556        1,
557        source_end_tokens,
558        committed.pages(),
559    ))
560}
561
562pub struct OperationBufferView<'a, B> {
563    descriptor: BufferDescriptor,
564    source: OperationBufferSource<'a, B>,
565    coverage: OperationBufferCoverage,
566    allocation_lifetime: AllocationLifetime,
567    packed_batch_coordinates: bool,
568}
569
570impl<'a, B> OperationBufferView<'a, B> {
571    pub(super) fn from_static(
572        view: LeasedBufferView<'a, B>,
573        retention: DeviceBufferRetention,
574    ) -> Self {
575        Self {
576            descriptor: view.committed_descriptor().clone(),
577            source: OperationBufferSource::Static { view, retention },
578            coverage: OperationBufferCoverage::Exact,
579            allocation_lifetime: AllocationLifetime::Plan,
580            packed_batch_coordinates: false,
581        }
582    }
583
584    pub(super) fn from_backing_exact(
585        descriptor: BufferDescriptor,
586        backing: LogicalBackingBufferView<'a, B>,
587        allocation_lifetime: AllocationLifetime,
588    ) -> Self {
589        Self::from_backing(
590            descriptor,
591            backing,
592            OperationBufferCoverage::Exact,
593            allocation_lifetime,
594        )
595    }
596
597    pub(super) fn from_backing_prefix(
598        descriptor: BufferDescriptor,
599        backing: LogicalBackingBufferView<'a, B>,
600        allocation_lifetime: AllocationLifetime,
601    ) -> Self {
602        Self::from_backing(
603            descriptor,
604            backing,
605            OperationBufferCoverage::BackingWindow { offset_bytes: 0 },
606            allocation_lifetime,
607        )
608    }
609
610    pub(super) fn from_backing_window(
611        descriptor: BufferDescriptor,
612        backing: LogicalBackingBufferView<'a, B>,
613        offset_bytes: u64,
614        allocation_lifetime: AllocationLifetime,
615    ) -> Self {
616        Self::from_backing(
617            descriptor,
618            backing,
619            OperationBufferCoverage::BackingWindow { offset_bytes },
620            allocation_lifetime,
621        )
622    }
623
624    fn from_backing(
625        descriptor: BufferDescriptor,
626        backing: LogicalBackingBufferView<'a, B>,
627        coverage: OperationBufferCoverage,
628        allocation_lifetime: AllocationLifetime,
629    ) -> Self {
630        Self {
631            descriptor,
632            source: OperationBufferSource::Backing(backing),
633            coverage,
634            allocation_lifetime,
635            packed_batch_coordinates: false,
636        }
637    }
638
639    pub(super) fn with_packed_batch_coordinates(mut self, packed: bool) -> Self {
640        self.packed_batch_coordinates = packed;
641        self
642    }
643
644    pub(super) fn validate_runtime<R>(
645        &self,
646        runtime: &R,
647        expected_static_identity: Option<&ResourceTransactionIdentity>,
648    ) -> Result<(), VNextError>
649    where
650        R: DeviceRuntime<Buffer = B>,
651    {
652        match &self.source {
653            OperationBufferSource::Static { view, .. } => {
654                let actual = runtime.buffer_descriptor(view.buffer());
655                if Some(view.identity()) != expected_static_identity
656                    || &actual != view.committed_descriptor()
657                    || view.generation() == 0
658                {
659                    return Err(invalid_operation(format!(
660                        "runtime descriptor differs from committed static resource `{}`",
661                        self.resource_id()
662                    )));
663                }
664            }
665            OperationBufferSource::Backing(backing_view) => {
666                let bindings = backing_view.segment_bindings();
667                if bindings.is_empty()
668                    || bindings.len() != backing_view.committed_evidence_segments().count()
669                    || bindings
670                        .iter()
671                        .zip(backing_view.committed_evidence_segments())
672                        .any(|(binding, evidence)| {
673                            let actual = runtime.buffer_descriptor(binding.buffer());
674                            binding.segment() != evidence
675                                || binding.chunk() != evidence.chunk()
676                                || &actual != binding.descriptor()
677                                || binding
678                                    .segment()
679                                    .offset_bytes()
680                                    .checked_add(binding.segment().length_bytes())
681                                    .is_none_or(|end| end > binding.descriptor().size_bytes)
682                        })
683                {
684                    return Err(invalid_operation(format!(
685                        "runtime descriptor differs from a committed backing chunk for `{}`",
686                        self.resource_id()
687                    )));
688                }
689            }
690        }
691        Ok(())
692    }
693
694    pub fn resource_id(&self) -> &ResourceId {
695        &self.descriptor.resource_id
696    }
697
698    pub fn descriptor(&self) -> &BufferDescriptor {
699        &self.descriptor
700    }
701
702    pub const fn allocation_lifetime(&self) -> AllocationLifetime {
703        self.allocation_lifetime
704    }
705
706    pub const fn uses_packed_batch_coordinates(&self) -> bool {
707        self.packed_batch_coordinates
708    }
709
710    pub fn storage_kind(&self) -> OperationBufferStorageKind {
711        match &self.source {
712            OperationBufferSource::Static { .. } => OperationBufferStorageKind::StaticContiguous,
713            OperationBufferSource::Backing(view) => {
714                operation_storage_kind(view.storage_profile().view())
715            }
716        }
717    }
718
719    pub fn translate(
720        &self,
721        logical_offset_bytes: u64,
722        logical_length_bytes: u64,
723    ) -> Result<OperationBufferRegions<'_, B>, VNextError> {
724        let logical_end_bytes = logical_offset_bytes
725            .checked_add(logical_length_bytes)
726            .ok_or_else(|| invalid_operation("operation logical buffer range overflows u64"))?;
727        if logical_length_bytes == 0 || logical_end_bytes > self.descriptor.size_bytes {
728            return Err(invalid_operation(
729                "operation logical buffer range is empty or outside its resource",
730            ));
731        }
732        match &self.source {
733            OperationBufferSource::Static { view, retention } => Ok(OperationBufferRegions {
734                storage_kind: OperationBufferStorageKind::StaticContiguous,
735                logical_offset_bytes,
736                logical_length_bytes,
737                source: OperationRegionSource::Contiguous {
738                    buffer: view.buffer(),
739                    physical_base_offset_bytes: 0,
740                    retention: retention.clone(),
741                },
742            }),
743            OperationBufferSource::Backing(view) => {
744                let bindings = view.segment_bindings();
745                if bindings.len() != view.committed_evidence_segments().count()
746                    || bindings.iter().zip(view.committed_evidence_segments()).any(
747                        |(binding, segment)| {
748                            binding.segment() != segment || binding.chunk() != segment.chunk()
749                        },
750                    )
751                {
752                    return Err(invalid_operation(
753                        "dynamic backing bindings differ from committed segment evidence",
754                    ));
755                }
756                let storage_kind = operation_storage_kind(view.storage_profile().view());
757                let backing_window_offset = match self.coverage {
758                    OperationBufferCoverage::Exact => 0,
759                    OperationBufferCoverage::BackingWindow { offset_bytes } => offset_bytes,
760                };
761                validate_dynamic_binding_layout(
762                    storage_kind,
763                    self.descriptor.size_bytes,
764                    bindings
765                        .iter()
766                        .map(|binding| binding.segment().length_bytes()),
767                    self.coverage,
768                )?;
769                let source = match storage_kind {
770                    OperationBufferStorageKind::DynamicContiguous => {
771                        let binding = &bindings[0];
772                        OperationRegionSource::Contiguous {
773                            buffer: binding.buffer(),
774                            physical_base_offset_bytes: binding
775                                .segment()
776                                .offset_bytes()
777                                .checked_add(backing_window_offset)
778                                .ok_or_else(|| {
779                                    invalid_operation(
780                                        "participant backing physical offset overflows u64",
781                                    )
782                                })?,
783                            retention: binding.retention(),
784                        }
785                    }
786                    OperationBufferStorageKind::DynamicPaged => OperationRegionSource::Paged {
787                        bindings,
788                        logical_origin_bytes: backing_window_offset,
789                    },
790                    OperationBufferStorageKind::StaticContiguous => unreachable!(
791                        "dynamic storage kind was validated before region construction"
792                    ),
793                };
794                Ok(OperationBufferRegions {
795                    storage_kind,
796                    logical_offset_bytes,
797                    logical_length_bytes,
798                    source,
799                })
800            }
801        }
802    }
803}
804
805#[cfg(test)]
806mod operation_buffer_region_tests {
807    use super::{
808        packed_step_token_range_to_participant_local_readback, sequence_execution_shape,
809        translate_paged_segment, translate_step_participant_readback_range,
810        translate_step_participant_upload_range, validate_dynamic_binding_layout,
811        validate_value_binding_physical_coverage, OperationBufferCoverage, OperationBufferRegions,
812        OperationBufferStorageKind, OperationRegionSource, ValueBindingPhysicalCoverage,
813    };
814    use crate::vnext::{
815        AliasPolicy, BatchWorkShape, BufferDescriptor, BufferUsage, DeviceBufferRetention,
816        DynamicResourceDemand, DynamicResourceShape, ElementType, NodeWorkContract, ProgramValueId,
817        ResolvedTensorLayout, ResolvedTensorSpec, ResolvedValueBinding, ResolvedValueRole,
818        ResolvedValueStorage, ResourceId, TensorAccess, TokenSpanWork,
819    };
820    use std::sync::atomic::Ordering;
821    use std::sync::Arc;
822
823    #[test]
824    fn token_projection_validates_runtime_view_instead_of_canonical_extent() {
825        let resource_id = ResourceId::new("resource.activation.token-ids").unwrap();
826        let binding = ResolvedValueBinding::new(
827            ProgramValueId::new("value.input.token-ids").unwrap(),
828            ResolvedValueRole::Input,
829            0,
830            ResolvedTensorSpec::new(
831                vec![128],
832                ElementType::U32,
833                ResolvedTensorLayout::Contiguous,
834            )
835            .unwrap(),
836            TensorAccess::Read,
837            AliasPolicy::NoAlias,
838            BufferUsage::Activations,
839            None,
840            ResolvedValueStorage::single(resource_id.clone(), 0, 512, ElementType::U32).unwrap(),
841        )
842        .unwrap();
843        let work: NodeWorkContract = serde_json::from_value(serde_json::json!({
844            "tokens": {
845                "source": {
846                    "value_id": "value.input.token-ids",
847                    "role": "input",
848                    "ordinal": 0,
849                    "axis": 0,
850                    "rank": 1,
851                    "canonical_extent": 128
852                },
853                "projections": [{
854                    "value_id": "value.input.token-ids",
855                    "role": "input",
856                    "ordinal": 0,
857                    "axis": 0,
858                    "rank": 1,
859                    "canonical_extent": 128
860                }]
861            }
862        }))
863        .unwrap();
864        let descriptor = BufferDescriptor {
865            resource_id,
866            size_bytes: 160,
867            alignment_bytes: 16,
868            usage: BufferUsage::Activations,
869            element_type: ElementType::U32,
870        };
871        let component = &binding.storage().components()[0];
872        let demand = DynamicResourceDemand::tokens(4, 128).unwrap();
873
874        assert_eq!(
875            validate_value_binding_physical_coverage(
876                &work,
877                &binding,
878                component,
879                &descriptor,
880                Some(&demand),
881                16,
882            )
883            .unwrap(),
884            ValueBindingPhysicalCoverage::RuntimeTokenView
885        );
886        assert!(validate_value_binding_physical_coverage(
887            &NodeWorkContract::Fixed,
888            &binding,
889            component,
890            &descriptor,
891            Some(&demand),
892            16,
893        )
894        .is_err());
895        assert!(validate_value_binding_physical_coverage(
896            &work,
897            &binding,
898            component,
899            &descriptor,
900            Some(&DynamicResourceDemand::tokens(8, 128).unwrap()),
901            16,
902        )
903        .is_err());
904    }
905
906    #[test]
907    fn paged_translation_uses_each_chunks_exact_buffer() {
908        struct MockBinding<'a> {
909            buffer: &'a u8,
910            physical_offset_bytes: u64,
911            length_bytes: u64,
912        }
913
914        let first_buffer = 7_u8;
915        let second_buffer = 11_u8;
916        let bindings = [
917            MockBinding {
918                buffer: &first_buffer,
919                physical_offset_bytes: 64,
920                length_bytes: 8,
921            },
922            MockBinding {
923                buffer: &second_buffer,
924                physical_offset_bytes: 200,
925                length_bytes: 12,
926            },
927        ];
928        let mut next_logical_offset = 0;
929        let translated = bindings
930            .iter()
931            .filter_map(|binding| {
932                let (logical_end, region) = translate_paged_segment(
933                    binding.buffer,
934                    DeviceBufferRetention::plan(Arc::new(())),
935                    binding.physical_offset_bytes,
936                    binding.length_bytes,
937                    next_logical_offset,
938                    6,
939                    16,
940                );
941                next_logical_offset = logical_end;
942                region
943            })
944            .collect::<Vec<_>>();
945
946        assert_eq!(translated.len(), 2);
947        let (first, first_physical, _first_retention) = translated[0].buffer_and_physical_range();
948        assert!(std::ptr::eq(first, &first_buffer));
949        assert_eq!(translated[0].logical_offset_bytes(), 6);
950        assert_eq!(first_physical, 70..72);
951        let (second, second_physical, _second_retention) =
952            translated[1].buffer_and_physical_range();
953        assert!(std::ptr::eq(second, &second_buffer));
954        assert_eq!(translated[1].logical_offset_bytes(), 8);
955        assert_eq!(second_physical, 200..208);
956    }
957
958    #[test]
959    fn contiguous_layout_rejects_cross_chunk_bindings() {
960        let first_buffer = 7_u8;
961        let second_buffer = 11_u8;
962        let bindings = [(&first_buffer, 8_u64), (&second_buffer, 12_u64)];
963
964        let error = validate_dynamic_binding_layout(
965            OperationBufferStorageKind::DynamicContiguous,
966            20,
967            bindings.iter().map(|(_, length_bytes)| *length_bytes),
968            OperationBufferCoverage::Exact,
969        )
970        .unwrap_err();
971
972        assert!(error
973            .to_string()
974            .contains("contiguous dynamic storage requires one physical segment binding"));
975    }
976
977    #[test]
978    fn operation_prefix_view_retains_wider_backing_coverage() {
979        validate_dynamic_binding_layout(
980            OperationBufferStorageKind::DynamicPaged,
981            64,
982            [64_u64, 64].into_iter(),
983            OperationBufferCoverage::BackingWindow { offset_bytes: 0 },
984        )
985        .unwrap();
986        validate_dynamic_binding_layout(
987            OperationBufferStorageKind::DynamicContiguous,
988            64,
989            [128_u64].into_iter(),
990            OperationBufferCoverage::BackingWindow { offset_bytes: 0 },
991        )
992        .unwrap();
993
994        assert!(validate_dynamic_binding_layout(
995            OperationBufferStorageKind::DynamicPaged,
996            64,
997            [64_u64, 64].into_iter(),
998            OperationBufferCoverage::Exact,
999        )
1000        .is_err());
1001        assert!(validate_dynamic_binding_layout(
1002            OperationBufferStorageKind::DynamicPaged,
1003            128,
1004            [64_u64].into_iter(),
1005            OperationBufferCoverage::BackingWindow { offset_bytes: 0 },
1006        )
1007        .is_err());
1008    }
1009
1010    #[test]
1011    fn participant_step_projection_keeps_upload_and_readback_coordinate_spaces_distinct() {
1012        let first_tokens = vec![1_u32; 20];
1013        let second_tokens = vec![2_u32; 9];
1014        let work = BatchWorkShape::test_only(vec![
1015            TokenSpanWork::from_token_ids(&first_tokens, 17..18).unwrap(),
1016            TokenSpanWork::from_token_ids(&second_tokens, 7..9).unwrap(),
1017        ])
1018        .unwrap();
1019        let tokens = DynamicResourceDemand::tokens(4, 32).unwrap();
1020
1021        assert_eq!(
1022            translate_step_participant_upload_range(&tokens, &work, 0, 68..72).unwrap(),
1023            0..4
1024        );
1025        assert_eq!(
1026            translate_step_participant_upload_range(&tokens, &work, 1, 28..36).unwrap(),
1027            4..12
1028        );
1029        assert_eq!(
1030            translate_step_participant_readback_range(&tokens, &work, 1, 0..8).unwrap(),
1031            4..12
1032        );
1033        assert_eq!(
1034            packed_step_token_range_to_participant_local_readback(&tokens, &work, 1, 4..12)
1035                .unwrap(),
1036            0..8
1037        );
1038        assert!(translate_step_participant_readback_range(&tokens, &work, 1, 28..36).is_err());
1039        assert!(
1040            packed_step_token_range_to_participant_local_readback(&tokens, &work, 1, 28..36)
1041                .is_err()
1042        );
1043    }
1044
1045    #[test]
1046    fn fixed_participant_step_projection_uses_disjoint_aligned_strides() {
1047        let tokens = [1_u32];
1048        let work = BatchWorkShape::test_only(vec![
1049            TokenSpanWork::from_token_ids(&tokens, 0..1).unwrap(),
1050            TokenSpanWork::from_token_ids(&tokens, 0..1).unwrap(),
1051        ])
1052        .unwrap();
1053        let fixed = DynamicResourceDemand::actual_sequences(16, 4).unwrap();
1054
1055        assert_eq!(
1056            translate_step_participant_upload_range(&fixed, &work, 0, 0..4).unwrap(),
1057            0..4
1058        );
1059        assert_eq!(
1060            translate_step_participant_upload_range(&fixed, &work, 1, 0..4).unwrap(),
1061            16..20
1062        );
1063        assert_eq!(
1064            translate_step_participant_readback_range(&fixed, &work, 1, 0..4).unwrap(),
1065            16..20
1066        );
1067        assert!(translate_step_participant_upload_range(&fixed, &work, 1, 0..17).is_err());
1068        assert_eq!(
1069            translate_step_participant_upload_range(
1070                &DynamicResourceDemand::fixed(16).unwrap(),
1071                &work,
1072                1,
1073                0..4,
1074            )
1075            .unwrap(),
1076            0..4
1077        );
1078    }
1079
1080    #[test]
1081    fn sequence_execution_shape_uses_the_executed_source_frontier() {
1082        let committed = DynamicResourceShape::from_validated(1, 8, 3);
1083        let projected = sequence_execution_shape(committed, 4).unwrap();
1084
1085        assert_eq!(projected.sequences(), 1);
1086        assert_eq!(projected.tokens(), 4);
1087        assert_eq!(projected.pages(), 3);
1088        assert!(sequence_execution_shape(committed, 0).is_err());
1089        assert!(sequence_execution_shape(committed, 9).is_err());
1090        assert!(
1091            sequence_execution_shape(DynamicResourceShape::from_validated(2, 8, 3), 4).is_err()
1092        );
1093    }
1094
1095    #[test]
1096    fn contiguous_translation_applies_physical_base_offset() {
1097        let buffer = 9_u8;
1098        let regions = OperationBufferRegions {
1099            storage_kind: OperationBufferStorageKind::DynamicContiguous,
1100            logical_offset_bytes: 16,
1101            logical_length_bytes: 32,
1102            source: OperationRegionSource::Contiguous {
1103                buffer: &buffer,
1104                physical_base_offset_bytes: 4096,
1105                retention: DeviceBufferRetention::plan(Arc::new(())),
1106            },
1107        };
1108
1109        let translated = regions.iter().collect::<Vec<_>>();
1110        assert_eq!(translated.len(), 1);
1111        let (actual, physical, _retention) = translated[0].buffer_and_physical_range();
1112        assert_eq!(*actual, buffer);
1113        assert_eq!(translated[0].logical_offset_bytes(), 16);
1114        assert_eq!(physical, 4112..4144);
1115    }
1116
1117    #[test]
1118    fn physical_region_retains_opaque_owner_after_translation_source_drops() {
1119        struct DropOwner(Arc<std::sync::atomic::AtomicBool>);
1120
1121        impl Drop for DropOwner {
1122            fn drop(&mut self) {
1123                self.0.store(true, Ordering::Release);
1124            }
1125        }
1126
1127        let buffer = 9_u8;
1128        let dropped = Arc::new(std::sync::atomic::AtomicBool::new(false));
1129        let owner = Arc::new(DropOwner(Arc::clone(&dropped)));
1130        let regions = OperationBufferRegions {
1131            storage_kind: OperationBufferStorageKind::DynamicContiguous,
1132            logical_offset_bytes: 0,
1133            logical_length_bytes: 8,
1134            source: OperationRegionSource::Contiguous {
1135                buffer: &buffer,
1136                physical_base_offset_bytes: 64,
1137                retention: DeviceBufferRetention::plan(Arc::clone(&owner)),
1138            },
1139        };
1140        drop(owner);
1141
1142        let translated = regions.iter().collect::<Vec<_>>();
1143        drop(regions);
1144        assert!(!dropped.load(Ordering::Acquire));
1145        let (_, physical, retention) = translated[0].buffer_and_physical_range();
1146        assert_eq!(physical, 64..72);
1147        drop(translated);
1148        assert!(!dropped.load(Ordering::Acquire));
1149
1150        drop(retention);
1151        assert!(dropped.load(Ordering::Acquire));
1152    }
1153}