Skip to main content

ferrum_kernels/backend/reference/
runtime.rs

1use std::collections::BTreeSet;
2use std::error::Error;
3use std::fmt;
4use std::ops::Range;
5use std::sync::atomic::{AtomicU64, Ordering};
6use std::sync::{Arc, Mutex, MutexGuard};
7
8use ferrum_interfaces::vnext::{
9    BufferDescriptor, CapabilityId, CopyRegion, DefinitelyNotSubmitted, DeviceAllocationPermit,
10    DeviceBatchingForm, DeviceBufferRetention, DeviceClass, DeviceCommandBatch,
11    DeviceCommandLogicalWork, DeviceCommandPhase, DeviceComputePathRequirement, DeviceDescriptor,
12    DeviceErrorReport, DeviceExecutionPath, DeviceNativeOperationId, DeviceNativeWorkAttribution,
13    DeviceRuntime, DeviceSubmissionAttribution, DeviceTerminal, DeviceTerminalReceipt,
14    DeviceTimingMode, ElementType, FenceIndeterminate, FenceQuery, HostTransferLayout, StreamState,
15    VNextError, DENSE_LINEAR_F16_CAPABILITY_ID, DEVICE_COPY_NATIVE_OPERATION_ID,
16    DEVICE_ZERO_NATIVE_OPERATION_ID, HOST_UPLOAD_NATIVE_OPERATION_ID,
17};
18use half::f16;
19
20static NEXT_RUNTIME_INSTANCE: AtomicU64 = AtomicU64::new(1);
21static NEXT_STREAM_INSTANCE: AtomicU64 = AtomicU64::new(1);
22static NEXT_FENCE_INSTANCE: AtomicU64 = AtomicU64::new(1);
23
24pub(crate) struct ReferenceDeviceRuntimeConfig {
25    pub(crate) descriptor: DeviceDescriptor,
26}
27
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct ReferenceDeviceRuntimeError {
30    message: String,
31}
32
33impl ReferenceDeviceRuntimeError {
34    pub(crate) fn contract(message: impl Into<String>) -> Self {
35        Self {
36            message: message.into(),
37        }
38    }
39}
40
41impl fmt::Display for ReferenceDeviceRuntimeError {
42    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
43        formatter.write_str(&self.message)
44    }
45}
46
47impl Error for ReferenceDeviceRuntimeError {}
48
49struct ReferenceAllocation {
50    bytes: Mutex<Box<[u8]>>,
51    logical_offset_bytes: usize,
52    live_allocations: Arc<AtomicU64>,
53}
54
55impl ReferenceAllocation {
56    fn lock(&self) -> MutexGuard<'_, Box<[u8]>> {
57        self.bytes
58            .lock()
59            .unwrap_or_else(|poisoned| poisoned.into_inner())
60    }
61}
62
63impl Drop for ReferenceAllocation {
64    fn drop(&mut self) {
65        let prior = self.live_allocations.fetch_sub(1, Ordering::Relaxed);
66        debug_assert!(prior > 0, "reference allocation accounting underflow");
67    }
68}
69
70/// One core-authorized reference allocation.
71pub struct ReferenceDeviceBuffer {
72    descriptor: BufferDescriptor,
73    runtime_instance: u64,
74    allocation: Arc<ReferenceAllocation>,
75}
76
77impl fmt::Debug for ReferenceDeviceBuffer {
78    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
79        formatter
80            .debug_struct("ReferenceDeviceBuffer")
81            .field("descriptor", &self.descriptor)
82            .field("runtime_instance", &self.runtime_instance)
83            .finish_non_exhaustive()
84    }
85}
86
87impl ReferenceDeviceBuffer {
88    fn region(
89        &self,
90        range: Range<u64>,
91    ) -> Result<ReferenceBufferRegion, ReferenceDeviceRuntimeError> {
92        self.region_with_retention(range, None)
93    }
94
95    pub(crate) fn retained_region(
96        &self,
97        range: Range<u64>,
98        retention: DeviceBufferRetention,
99    ) -> Result<ReferenceBufferRegion, ReferenceDeviceRuntimeError> {
100        self.region_with_retention(range, Some(retention))
101    }
102
103    fn region_with_retention(
104        &self,
105        range: Range<u64>,
106        retention: Option<DeviceBufferRetention>,
107    ) -> Result<ReferenceBufferRegion, ReferenceDeviceRuntimeError> {
108        if range.start >= range.end || range.end > self.descriptor.size_bytes {
109            return Err(ReferenceDeviceRuntimeError::contract(
110                "reference buffer region is empty or exceeds its admitted allocation",
111            ));
112        }
113        let offset_bytes = usize::try_from(range.start).map_err(|_| {
114            ReferenceDeviceRuntimeError::contract("reference buffer offset exceeds usize")
115        })?;
116        let offset_bytes = self
117            .allocation
118            .logical_offset_bytes
119            .checked_add(offset_bytes)
120            .ok_or_else(|| {
121                ReferenceDeviceRuntimeError::contract(
122                    "reference buffer physical offset overflows usize",
123                )
124            })?;
125        let length_bytes = usize::try_from(range.end - range.start).map_err(|_| {
126            ReferenceDeviceRuntimeError::contract("reference buffer length exceeds usize")
127        })?;
128        Ok(ReferenceBufferRegion {
129            allocation: Arc::clone(&self.allocation),
130            runtime_instance: self.runtime_instance,
131            offset_bytes,
132            length_bytes,
133            element_type: self.descriptor.element_type,
134            _retention: retention,
135        })
136    }
137}
138
139#[derive(Clone)]
140pub(crate) struct ReferenceBufferRegion {
141    allocation: Arc<ReferenceAllocation>,
142    runtime_instance: u64,
143    offset_bytes: usize,
144    length_bytes: usize,
145    element_type: ElementType,
146    _retention: Option<DeviceBufferRetention>,
147}
148
149impl ReferenceBufferRegion {
150    pub(crate) const fn length_bytes(&self) -> usize {
151        self.length_bytes
152    }
153
154    pub(crate) const fn element_type(&self) -> ElementType {
155        self.element_type
156    }
157
158    pub(crate) fn same_physical_region(&self, other: &Self) -> bool {
159        Arc::ptr_eq(&self.allocation, &other.allocation)
160            && self.offset_bytes == other.offset_bytes
161            && self.length_bytes == other.length_bytes
162            && self.element_type == other.element_type
163    }
164
165    fn validate_runtime(&self, runtime_instance: u64) -> Result<(), ReferenceDeviceRuntimeError> {
166        if self.runtime_instance != runtime_instance {
167            return Err(ReferenceDeviceRuntimeError::contract(
168                "reference command contains a buffer from another runtime",
169            ));
170        }
171        Ok(())
172    }
173
174    fn read(&self) -> Vec<u8> {
175        let bytes = self.allocation.lock();
176        bytes[self.offset_bytes..self.offset_bytes + self.length_bytes].to_vec()
177    }
178
179    fn write(&self, source: &[u8]) {
180        assert_eq!(source.len(), self.length_bytes);
181        let mut bytes = self.allocation.lock();
182        bytes[self.offset_bytes..self.offset_bytes + self.length_bytes].copy_from_slice(source);
183    }
184
185    fn zero(&self) {
186        let mut bytes = self.allocation.lock();
187        bytes[self.offset_bytes..self.offset_bytes + self.length_bytes].fill(0);
188    }
189}
190
191pub(crate) struct ReferenceDenseLinearLaunch {
192    pub(crate) input: ReferenceBufferRegion,
193    pub(crate) weight: ReferenceBufferRegion,
194    pub(crate) output: ReferenceBufferRegion,
195    pub(crate) rows: usize,
196    pub(crate) in_features: usize,
197    pub(crate) out_features: usize,
198}
199
200enum ReferenceCommandKind {
201    Copy {
202        source: ReferenceBufferRegion,
203        destination: ReferenceBufferRegion,
204    },
205    Upload {
206        source: Box<[u8]>,
207        destination: ReferenceBufferRegion,
208    },
209    Zero {
210        destination: ReferenceBufferRegion,
211    },
212    DenseLinear {
213        launches: Box<[ReferenceDenseLinearLaunch]>,
214    },
215}
216
217/// An owned, fully validated command for the synchronous reference runtime.
218pub struct ReferenceDeviceCommand {
219    operation: &'static str,
220    batching_form: DeviceBatchingForm,
221    participant_start: u32,
222    participant_count: u32,
223    token_count: u64,
224    compute_dispatch_count: u64,
225    transfer_command_count: u64,
226    kind: ReferenceCommandKind,
227}
228
229impl fmt::Debug for ReferenceDeviceCommand {
230    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
231        let kind = match self.kind {
232            ReferenceCommandKind::Copy { .. } => "copy",
233            ReferenceCommandKind::Upload { .. } => "upload",
234            ReferenceCommandKind::Zero { .. } => "zero",
235            ReferenceCommandKind::DenseLinear { .. } => "dense_linear",
236        };
237        formatter
238            .debug_struct("ReferenceDeviceCommand")
239            .field("kind", &kind)
240            .field("operation", &self.operation)
241            .field("batching_form", &self.batching_form)
242            .field("participant_start", &self.participant_start)
243            .field("participant_count", &self.participant_count)
244            .field("token_count", &self.token_count)
245            .finish()
246    }
247}
248
249impl ReferenceDeviceCommand {
250    pub(crate) fn dense_linear(
251        launches: Vec<ReferenceDenseLinearLaunch>,
252        batching_form: DeviceBatchingForm,
253        participant_count: u32,
254        token_count: u64,
255    ) -> Result<Self, ReferenceDeviceRuntimeError> {
256        if launches.is_empty() || participant_count == 0 || token_count == 0 {
257            return Err(ReferenceDeviceRuntimeError::contract(
258                "reference dense-linear attribution has no participants or work",
259            ));
260        }
261        let compute_dispatch_count = u64::try_from(launches.len()).map_err(|_| {
262            ReferenceDeviceRuntimeError::contract(
263                "reference dense-linear dispatch count exceeds u64",
264            )
265        })?;
266        Ok(Self {
267            operation: "vnext_dense_linear",
268            batching_form,
269            participant_start: 0,
270            participant_count,
271            token_count,
272            compute_dispatch_count,
273            transfer_command_count: 0,
274            kind: ReferenceCommandKind::DenseLinear {
275                launches: launches.into_boxed_slice(),
276            },
277        })
278    }
279
280    fn transfer(operation: &'static str, kind: ReferenceCommandKind) -> Self {
281        Self {
282            operation,
283            batching_form: DeviceBatchingForm::Scalar,
284            participant_start: 0,
285            participant_count: 0,
286            token_count: 0,
287            compute_dispatch_count: 0,
288            transfer_command_count: 1,
289            kind,
290        }
291    }
292
293    fn bind_core_logical_work(
294        mut self,
295        logical_work: DeviceCommandLogicalWork,
296    ) -> Result<Self, ReferenceDeviceRuntimeError> {
297        if self.participant_count != 0 || self.token_count != 0 {
298            return Err(ReferenceDeviceRuntimeError::contract(
299                "reference core logical work cannot replace provider command attribution",
300            ));
301        }
302        self.batching_form = logical_work.batching_form();
303        self.participant_start = logical_work.participant_start();
304        self.participant_count = logical_work.participant_count();
305        self.token_count = logical_work.token_count();
306        Ok(self)
307    }
308
309    fn attribution(
310        &self,
311        command_index: u32,
312        node_index: Option<u32>,
313        phase: DeviceCommandPhase,
314    ) -> Result<DeviceNativeWorkAttribution, ReferenceDeviceRuntimeError> {
315        let native_op_id = DeviceNativeOperationId::new(self.operation).ok_or_else(|| {
316            ReferenceDeviceRuntimeError::contract(
317                "reference command attribution has a non-portable native operation identity",
318            )
319        })?;
320        DeviceNativeWorkAttribution::with_participant_range(
321            command_index,
322            node_index,
323            phase,
324            native_op_id,
325            DeviceExecutionPath::Eager,
326            self.batching_form,
327            self.participant_start,
328            self.participant_count,
329            self.token_count,
330            self.compute_dispatch_count,
331            self.transfer_command_count,
332            None,
333        )
334        .ok_or_else(|| {
335            ReferenceDeviceRuntimeError::contract(
336                "reference command attribution has invalid native work metadata",
337            )
338        })
339    }
340
341    fn validate_runtime(&self, runtime_instance: u64) -> Result<(), ReferenceDeviceRuntimeError> {
342        match &self.kind {
343            ReferenceCommandKind::Copy {
344                source,
345                destination,
346            } => {
347                source.validate_runtime(runtime_instance)?;
348                destination.validate_runtime(runtime_instance)
349            }
350            ReferenceCommandKind::Upload { destination, .. }
351            | ReferenceCommandKind::Zero { destination } => {
352                destination.validate_runtime(runtime_instance)
353            }
354            ReferenceCommandKind::DenseLinear { launches } => {
355                for launch in launches {
356                    launch.input.validate_runtime(runtime_instance)?;
357                    launch.weight.validate_runtime(runtime_instance)?;
358                    launch.output.validate_runtime(runtime_instance)?;
359                }
360                Ok(())
361            }
362        }
363    }
364
365    fn execute(&self, counters: &ReferenceRuntimeCounters) {
366        match &self.kind {
367            ReferenceCommandKind::Copy {
368                source,
369                destination,
370            } => destination.write(&source.read()),
371            ReferenceCommandKind::Upload {
372                source,
373                destination,
374            } => {
375                destination.write(source);
376                counters.uploaded_bytes.fetch_add(
377                    u64::try_from(source.len()).expect("reference upload length fits u64"),
378                    Ordering::Relaxed,
379                );
380            }
381            ReferenceCommandKind::Zero { destination } => destination.zero(),
382            ReferenceCommandKind::DenseLinear { launches } => {
383                for launch in launches {
384                    execute_dense_linear(launch);
385                    counters
386                        .dense_linear_launches
387                        .fetch_add(1, Ordering::Relaxed);
388                }
389            }
390        }
391    }
392}
393
394fn execute_dense_linear(launch: &ReferenceDenseLinearLaunch) {
395    let input = launch.input.read();
396    let weight = launch.weight.read();
397    let mut output = vec![0_u8; launch.output.length_bytes()];
398    for row in 0..launch.rows {
399        for out_feature in 0..launch.out_features {
400            let mut sum = 0.0_f32;
401            for in_feature in 0..launch.in_features {
402                let input_index = row * launch.in_features + in_feature;
403                let weight_index = out_feature * launch.in_features + in_feature;
404                sum += read_f16(&input, input_index) * read_f16(&weight, weight_index);
405            }
406            let output_index = row * launch.out_features + out_feature;
407            let bytes = f16::from_f32(sum).to_bits().to_le_bytes();
408            output[output_index * 2..output_index * 2 + 2].copy_from_slice(&bytes);
409        }
410    }
411    launch.output.write(&output);
412}
413
414fn read_f16(bytes: &[u8], index: usize) -> f32 {
415    let offset = index * 2;
416    f16::from_bits(u16::from_le_bytes([bytes[offset], bytes[offset + 1]])).to_f32()
417}
418
419fn aligned_storage(
420    size_bytes: usize,
421    alignment_bytes: usize,
422) -> Result<(Box<[u8]>, usize), ReferenceDeviceRuntimeError> {
423    if size_bytes == 0 || alignment_bytes == 0 || !alignment_bytes.is_power_of_two() {
424        return Err(ReferenceDeviceRuntimeError::contract(
425            "reference allocation size or alignment is invalid",
426        ));
427    }
428    let storage_bytes = size_bytes.checked_add(alignment_bytes - 1).ok_or_else(|| {
429        ReferenceDeviceRuntimeError::contract("reference aligned allocation size overflows")
430    })?;
431    let bytes = vec![0_u8; storage_bytes].into_boxed_slice();
432    let base = bytes.as_ptr() as usize;
433    let aligned = base
434        .checked_add(alignment_bytes - 1)
435        .map(|address| address & !(alignment_bytes - 1))
436        .ok_or_else(|| {
437            ReferenceDeviceRuntimeError::contract("reference aligned address overflows")
438        })?;
439    let logical_offset_bytes = aligned.checked_sub(base).ok_or_else(|| {
440        ReferenceDeviceRuntimeError::contract("reference aligned address precedes its allocation")
441    })?;
442    if logical_offset_bytes
443        .checked_add(size_bytes)
444        .is_none_or(|end| end > bytes.len())
445    {
446        return Err(ReferenceDeviceRuntimeError::contract(
447            "reference aligned logical range exceeds its allocation",
448        ));
449    }
450    Ok((bytes, logical_offset_bytes))
451}
452
453pub struct ReferenceDeviceStream {
454    runtime_instance: u64,
455    stream_instance: u64,
456    state: StreamState,
457}
458
459impl fmt::Debug for ReferenceDeviceStream {
460    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
461        formatter
462            .debug_struct("ReferenceDeviceStream")
463            .field("runtime_instance", &self.runtime_instance)
464            .field("stream_instance", &self.stream_instance)
465            .field("state", &self.state)
466            .finish()
467    }
468}
469
470pub struct ReferenceDeviceFence {
471    runtime_instance: u64,
472    fence_instance: u64,
473    attribution: Option<DeviceSubmissionAttribution>,
474}
475
476impl fmt::Debug for ReferenceDeviceFence {
477    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
478        formatter
479            .debug_struct("ReferenceDeviceFence")
480            .field("runtime_instance", &self.runtime_instance)
481            .field("fence_instance", &self.fence_instance)
482            .finish()
483    }
484}
485
486#[derive(Default)]
487struct ReferenceRuntimeCounters {
488    allocations: AtomicU64,
489    live_allocations: Arc<AtomicU64>,
490    submissions: AtomicU64,
491    commands: AtomicU64,
492    uploaded_bytes: AtomicU64,
493    dense_linear_launches: AtomicU64,
494    readback_bytes: AtomicU64,
495}
496
497#[derive(Debug, Clone, Copy, PartialEq, Eq)]
498pub struct ReferenceDeviceRuntimeSnapshot {
499    pub allocations: u64,
500    pub live_allocations: u64,
501    pub submissions: u64,
502    pub commands: u64,
503    pub uploaded_bytes: u64,
504    pub dense_linear_launches: u64,
505    pub readback_bytes: u64,
506}
507
508/// Synchronous, in-memory vNext runtime used for bounded numerical reference
509/// execution. It is never selected implicitly by a product backend.
510pub struct ReferenceDeviceRuntime {
511    descriptor: DeviceDescriptor,
512    runtime_instance: u64,
513    counters: ReferenceRuntimeCounters,
514}
515
516impl ReferenceDeviceRuntime {
517    pub(crate) fn new(
518        config: ReferenceDeviceRuntimeConfig,
519    ) -> Result<Self, ReferenceDeviceRuntimeError> {
520        config
521            .descriptor
522            .validate()
523            .map_err(|error| ReferenceDeviceRuntimeError::contract(error.to_string()))?;
524        let supported_capabilities =
525            BTreeSet::from([CapabilityId::new(DENSE_LINEAR_F16_CAPABILITY_ID)
526                .map_err(|error| ReferenceDeviceRuntimeError::contract(error.to_string()))?]);
527        if config.descriptor.class != DeviceClass::Reference
528            || config.descriptor.capabilities != supported_capabilities
529        {
530            return Err(ReferenceDeviceRuntimeError::contract(
531                "reference runtime descriptor overclaims its fixed device class or capabilities",
532            ));
533        }
534        let runtime_instance = NEXT_RUNTIME_INSTANCE
535            .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |value| {
536                value.checked_add(1)
537            })
538            .map_err(|_| {
539                ReferenceDeviceRuntimeError::contract("reference runtime identity exhausted")
540            })?;
541        Ok(Self {
542            descriptor: config.descriptor,
543            runtime_instance,
544            counters: ReferenceRuntimeCounters::default(),
545        })
546    }
547
548    pub fn snapshot(&self) -> ReferenceDeviceRuntimeSnapshot {
549        ReferenceDeviceRuntimeSnapshot {
550            allocations: self.counters.allocations.load(Ordering::Relaxed),
551            live_allocations: self.counters.live_allocations.load(Ordering::Relaxed),
552            submissions: self.counters.submissions.load(Ordering::Relaxed),
553            commands: self.counters.commands.load(Ordering::Relaxed),
554            uploaded_bytes: self.counters.uploaded_bytes.load(Ordering::Relaxed),
555            dense_linear_launches: self.counters.dense_linear_launches.load(Ordering::Relaxed),
556            readback_bytes: self.counters.readback_bytes.load(Ordering::Relaxed),
557        }
558    }
559}
560
561impl DeviceRuntime for ReferenceDeviceRuntime {
562    type Buffer = ReferenceDeviceBuffer;
563    type Stream = ReferenceDeviceStream;
564    type Command = ReferenceDeviceCommand;
565    type Fence = ReferenceDeviceFence;
566    type Error = ReferenceDeviceRuntimeError;
567
568    fn descriptor(&self) -> &DeviceDescriptor {
569        &self.descriptor
570    }
571
572    fn attention_execution_policy(&self) -> ferrum_types::AttentionExecutionPolicy {
573        ferrum_types::AttentionExecutionPolicy::Portable
574    }
575
576    fn allocate(&self, permit: DeviceAllocationPermit<'_>) -> Result<Self::Buffer, Self::Error> {
577        let request = permit.into_request();
578        let size = usize::try_from(request.size_bytes()).map_err(|_| {
579            ReferenceDeviceRuntimeError::contract("reference allocation exceeds usize")
580        })?;
581        let alignment = usize::try_from(request.alignment_bytes()).map_err(|_| {
582            ReferenceDeviceRuntimeError::contract("reference alignment exceeds usize")
583        })?;
584        let (bytes, logical_offset_bytes) = aligned_storage(size, alignment)?;
585        let descriptor = BufferDescriptor {
586            resource_id: request.resource_id().clone(),
587            size_bytes: request.size_bytes(),
588            alignment_bytes: request.alignment_bytes(),
589            usage: request.usage(),
590            element_type: request.element_type(),
591        };
592        self.counters.allocations.fetch_add(1, Ordering::Relaxed);
593        self.counters
594            .live_allocations
595            .fetch_add(1, Ordering::Relaxed);
596        Ok(ReferenceDeviceBuffer {
597            descriptor,
598            runtime_instance: self.runtime_instance,
599            allocation: Arc::new(ReferenceAllocation {
600                bytes: Mutex::new(bytes),
601                logical_offset_bytes,
602                live_allocations: Arc::clone(&self.counters.live_allocations),
603            }),
604        })
605    }
606
607    fn buffer_descriptor(&self, buffer: &Self::Buffer) -> BufferDescriptor {
608        buffer.descriptor.clone()
609    }
610
611    fn create_stream(&self) -> Result<Self::Stream, Self::Error> {
612        let stream_instance = NEXT_STREAM_INSTANCE
613            .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |value| {
614                value.checked_add(1)
615            })
616            .map_err(|_| {
617                ReferenceDeviceRuntimeError::contract("reference stream identity exhausted")
618            })?;
619        Ok(ReferenceDeviceStream {
620            runtime_instance: self.runtime_instance,
621            stream_instance,
622            state: StreamState::Ready,
623        })
624    }
625
626    fn stream_state(&self, stream: &Self::Stream) -> StreamState {
627        if stream.runtime_instance == self.runtime_instance {
628            stream.state
629        } else {
630            StreamState::Failed
631        }
632    }
633
634    fn encode_copy(
635        &self,
636        source: &Self::Buffer,
637        destination: &Self::Buffer,
638        region: CopyRegion,
639    ) -> Result<Self::Command, Self::Error> {
640        region
641            .validate_bounds(&source.descriptor, &destination.descriptor)
642            .map_err(|error| ReferenceDeviceRuntimeError::contract(error.to_string()))?;
643        if source.descriptor.element_type != destination.descriptor.element_type {
644            return Err(ReferenceDeviceRuntimeError::contract(
645                "reference copy requires matching source and destination element types",
646            ));
647        }
648        let source = source.region(
649            region.source_offset_bytes()..region.source_offset_bytes() + region.length_bytes(),
650        )?;
651        let destination = destination.region(
652            region.destination_offset_bytes()
653                ..region.destination_offset_bytes() + region.length_bytes(),
654        )?;
655        Ok(ReferenceDeviceCommand::transfer(
656            DEVICE_COPY_NATIVE_OPERATION_ID.as_str(),
657            ReferenceCommandKind::Copy {
658                source,
659                destination,
660            },
661        ))
662    }
663
664    fn encode_upload(
665        &self,
666        source: &[u8],
667        source_layout: HostTransferLayout,
668        destination: &Self::Buffer,
669        destination_offset_bytes: u64,
670    ) -> Result<Self::Command, Self::Error> {
671        source_layout
672            .validate_bytes(source.len())
673            .map_err(|error| ReferenceDeviceRuntimeError::contract(error.to_string()))?;
674        if source_layout.element_type() != destination.descriptor.element_type {
675            return Err(ReferenceDeviceRuntimeError::contract(
676                "reference upload layout differs from destination element type",
677            ));
678        }
679        let length = source_layout
680            .byte_len()
681            .map_err(|error| ReferenceDeviceRuntimeError::contract(error.to_string()))?;
682        let end = destination_offset_bytes
683            .checked_add(length)
684            .ok_or_else(|| {
685                ReferenceDeviceRuntimeError::contract("reference upload range overflows")
686            })?;
687        let destination = destination.region(destination_offset_bytes..end)?;
688        Ok(ReferenceDeviceCommand::transfer(
689            HOST_UPLOAD_NATIVE_OPERATION_ID.as_str(),
690            ReferenceCommandKind::Upload {
691                source: source.to_vec().into_boxed_slice(),
692                destination,
693            },
694        ))
695    }
696
697    fn encode_zero(
698        &self,
699        destination: &Self::Buffer,
700        destination_offset_bytes: u64,
701        length_bytes: u64,
702    ) -> Result<Self::Command, Self::Error> {
703        let end = destination_offset_bytes
704            .checked_add(length_bytes)
705            .ok_or_else(|| {
706                ReferenceDeviceRuntimeError::contract("reference zero range overflows")
707            })?;
708        let destination = destination.region(destination_offset_bytes..end)?;
709        Ok(ReferenceDeviceCommand::transfer(
710            DEVICE_ZERO_NATIVE_OPERATION_ID.as_str(),
711            ReferenceCommandKind::Zero { destination },
712        ))
713    }
714
715    fn submit(
716        &self,
717        stream: &mut Self::Stream,
718        commands: DeviceCommandBatch<Self::Command>,
719    ) -> Result<Self::Fence, DefinitelyNotSubmitted<Self::Error>> {
720        if stream.runtime_instance != self.runtime_instance {
721            return Err(DefinitelyNotSubmitted::new(
722                ReferenceDeviceRuntimeError::contract(
723                    "reference stream belongs to another runtime",
724                ),
725            ));
726        }
727        if commands.is_empty() {
728            return Err(DefinitelyNotSubmitted::new(
729                ReferenceDeviceRuntimeError::contract(
730                    "reference runtime cannot submit an empty batch",
731                ),
732            ));
733        }
734        validate_submission_requirements(
735            commands.timing_mode(),
736            commands.compute_path_requirement(),
737            commands.reusable_execution_capture().is_some(),
738        )
739        .map_err(DefinitelyNotSubmitted::new)?;
740        let attribution_required = commands
741            .attribution_requirement()
742            .logical_execution_path_required();
743        let entries = commands
744            .into_entries()
745            .into_iter()
746            .map(|entry| {
747                let (phase, node_index, logical_work, command) = entry.into_parts();
748                let command = match logical_work {
749                    Some(logical_work) => command.bind_core_logical_work(logical_work)?,
750                    None => command,
751                };
752                Ok((phase, node_index, command))
753            })
754            .collect::<Result<Vec<_>, ReferenceDeviceRuntimeError>>()
755            .map_err(DefinitelyNotSubmitted::new)?;
756        for (_, _, command) in &entries {
757            command
758                .validate_runtime(self.runtime_instance)
759                .map_err(DefinitelyNotSubmitted::new)?;
760        }
761        let attribution = if attribution_required {
762            let rows = entries
763                .iter()
764                .enumerate()
765                .map(|(command_index, (phase, node_index, command))| {
766                    let command_index = u32::try_from(command_index).map_err(|_| {
767                        ReferenceDeviceRuntimeError::contract("reference command index exceeds u32")
768                    })?;
769                    command.attribution(command_index, *node_index, *phase)
770                })
771                .collect::<Result<Vec<_>, _>>()
772                .map_err(DefinitelyNotSubmitted::new)?;
773            Some(DeviceSubmissionAttribution::new(rows).ok_or_else(|| {
774                DefinitelyNotSubmitted::new(ReferenceDeviceRuntimeError::contract(
775                    "reference submission attribution is empty or unordered",
776                ))
777            })?)
778        } else {
779            None
780        };
781        for (_, _, command) in &entries {
782            command.execute(&self.counters);
783        }
784        self.counters.submissions.fetch_add(1, Ordering::Relaxed);
785        self.counters.commands.fetch_add(
786            u64::try_from(entries.len()).expect("reference command count fits u64"),
787            Ordering::Relaxed,
788        );
789        stream.state = StreamState::Submitted;
790        let fence_instance = NEXT_FENCE_INSTANCE
791            .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |value| {
792                value.checked_add(1)
793            })
794            .expect("reference fence identity space exhausted");
795        Ok(ReferenceDeviceFence {
796            runtime_instance: self.runtime_instance,
797            fence_instance,
798            attribution,
799        })
800    }
801
802    fn submission_attribution(&self, fence: &Self::Fence) -> Option<DeviceSubmissionAttribution> {
803        (fence.runtime_instance == self.runtime_instance)
804            .then(|| fence.attribution.clone())
805            .flatten()
806    }
807
808    fn query_fence(&self, fence: &Self::Fence) -> FenceQuery<Self::Error> {
809        if fence.runtime_instance != self.runtime_instance {
810            return FenceQuery::Indeterminate(ReferenceDeviceRuntimeError::contract(
811                "reference fence belongs to another runtime",
812            ));
813        }
814        FenceQuery::Terminal(DeviceTerminalReceipt::unprofiled(DeviceTerminal::Succeeded))
815    }
816
817    fn wait_fence(
818        &self,
819        fence: &Self::Fence,
820    ) -> Result<DeviceTerminalReceipt<Self::Error>, FenceIndeterminate<Self::Error>> {
821        if fence.runtime_instance != self.runtime_instance {
822            return Err(FenceIndeterminate::new(
823                ReferenceDeviceRuntimeError::contract("reference fence belongs to another runtime"),
824            ));
825        }
826        Ok(DeviceTerminalReceipt::unprofiled(DeviceTerminal::Succeeded))
827    }
828
829    fn synchronize(&self, stream: &mut Self::Stream) -> Result<(), Self::Error> {
830        if stream.runtime_instance != self.runtime_instance {
831            return Err(ReferenceDeviceRuntimeError::contract(
832                "reference stream belongs to another runtime",
833            ));
834        }
835        stream.state = StreamState::Ready;
836        Ok(())
837    }
838
839    fn readback(
840        &self,
841        stream: &mut Self::Stream,
842        source: &Self::Buffer,
843        region: CopyRegion,
844        output_layout: HostTransferLayout,
845    ) -> Result<Vec<u8>, Self::Error> {
846        if stream.runtime_instance != self.runtime_instance
847            || source.runtime_instance != self.runtime_instance
848        {
849            return Err(ReferenceDeviceRuntimeError::contract(
850                "reference readback belongs to another runtime",
851            ));
852        }
853        if output_layout.element_type() != source.descriptor.element_type {
854            return Err(ReferenceDeviceRuntimeError::contract(
855                "reference readback layout differs from source element type",
856            ));
857        }
858        let output_bytes = output_layout
859            .byte_len()
860            .map_err(|error| ReferenceDeviceRuntimeError::contract(error.to_string()))?;
861        let length = region.length_bytes();
862        let source_end = region
863            .source_offset_bytes()
864            .checked_add(length)
865            .filter(|end| *end <= source.descriptor.size_bytes)
866            .ok_or_else(|| {
867                ReferenceDeviceRuntimeError::contract(
868                    "reference readback source exceeds its admitted allocation",
869                )
870            })?;
871        let output_end = region
872            .destination_offset_bytes()
873            .checked_add(length)
874            .filter(|end| *end <= output_bytes)
875            .ok_or_else(|| {
876                ReferenceDeviceRuntimeError::contract(
877                    "reference readback destination exceeds its host layout",
878                )
879            })?;
880        self.synchronize(stream)?;
881        let source_bytes = source
882            .region(region.source_offset_bytes()..source_end)?
883            .read();
884        let output_len = usize::try_from(output_bytes).map_err(|_| {
885            ReferenceDeviceRuntimeError::contract("reference readback output exceeds usize")
886        })?;
887        let output_start = usize::try_from(region.destination_offset_bytes()).map_err(|_| {
888            ReferenceDeviceRuntimeError::contract("reference readback offset exceeds usize")
889        })?;
890        let output_end = usize::try_from(output_end).map_err(|_| {
891            ReferenceDeviceRuntimeError::contract("reference readback end exceeds usize")
892        })?;
893        if source_bytes.len() != output_end - output_start {
894            return Err(ReferenceDeviceRuntimeError::contract(
895                "reference readback source and destination lengths differ",
896            ));
897        }
898        let mut output = vec![0_u8; output_len];
899        output[output_start..output_end].copy_from_slice(&source_bytes);
900        self.counters
901            .readback_bytes
902            .fetch_add(length, Ordering::Relaxed);
903        Ok(output)
904    }
905
906    fn describe_error(&self, error: &Self::Error) -> Result<DeviceErrorReport, VNextError> {
907        DeviceErrorReport::new("reference_runtime", error.to_string(), false)
908    }
909}
910
911fn validate_submission_requirements(
912    timing_mode: DeviceTimingMode,
913    compute_path: DeviceComputePathRequirement,
914    has_reusable_capture: bool,
915) -> Result<(), ReferenceDeviceRuntimeError> {
916    if timing_mode != DeviceTimingMode::Off {
917        return Err(ReferenceDeviceRuntimeError::contract(
918            "reference runtime does not provide device timing evidence",
919        ));
920    }
921    if matches!(
922        compute_path,
923        DeviceComputePathRequirement::ReplayedOnly
924            | DeviceComputePathRequirement::ReplayedWithDeclaredEagerBoundaries
925    ) {
926        return Err(ReferenceDeviceRuntimeError::contract(
927            "reference runtime cannot satisfy a replay-required compute submission",
928        ));
929    }
930    if has_reusable_capture {
931        return Err(ReferenceDeviceRuntimeError::contract(
932            "reference runtime cannot consume reusable execution capture metadata",
933        ));
934    }
935    Ok(())
936}
937
938#[cfg(test)]
939mod tests {
940    use super::*;
941    use crate::backend::reference::composition::reference_vnext_runtime_config;
942    use ferrum_interfaces::vnext::{BufferUsage, DeviceId, ResourceId};
943
944    fn config() -> ReferenceDeviceRuntimeConfig {
945        reference_vnext_runtime_config(
946            DeviceId::new("device.reference.runtime-test").expect("valid device id"),
947        )
948        .expect("valid reference config")
949    }
950
951    fn buffer(
952        runtime: &ReferenceDeviceRuntime,
953        resource_id: &str,
954        contents: &[u8],
955        alignment_bytes: usize,
956        element_type: ElementType,
957    ) -> ReferenceDeviceBuffer {
958        let (bytes, logical_offset_bytes) =
959            aligned_storage(contents.len(), alignment_bytes).expect("aligned test storage");
960        runtime
961            .counters
962            .live_allocations
963            .fetch_add(1, Ordering::Relaxed);
964        let buffer = ReferenceDeviceBuffer {
965            descriptor: BufferDescriptor {
966                resource_id: ResourceId::new(resource_id).expect("valid test resource id"),
967                size_bytes: contents.len() as u64,
968                alignment_bytes: alignment_bytes as u64,
969                usage: BufferUsage::Transfer,
970                element_type,
971            },
972            runtime_instance: runtime.runtime_instance,
973            allocation: Arc::new(ReferenceAllocation {
974                bytes: Mutex::new(bytes),
975                logical_offset_bytes,
976                live_allocations: Arc::clone(&runtime.counters.live_allocations),
977            }),
978        };
979        buffer
980            .region(0..contents.len() as u64)
981            .expect("test buffer region")
982            .write(contents);
983        buffer
984    }
985
986    #[test]
987    fn aligned_reference_storage_fulfills_descriptor_contract() {
988        for alignment in [1, 2, 16, 64, 4096] {
989            let (bytes, offset) = aligned_storage(257, alignment).expect("aligned storage");
990            assert_eq!((bytes.as_ptr() as usize + offset) % alignment, 0);
991            assert!(offset + 257 <= bytes.len());
992        }
993    }
994
995    #[test]
996    fn descriptor_cannot_overclaim_device_class_or_capabilities() {
997        ReferenceDeviceRuntime::new(config()).expect("fixed reference descriptor must be valid");
998
999        let mut wrong_class = config();
1000        wrong_class.descriptor.class = DeviceClass::Accelerator;
1001        assert!(ReferenceDeviceRuntime::new(wrong_class).is_err());
1002
1003        let mut extra_capability = config();
1004        extra_capability.descriptor.capabilities.insert(
1005            CapabilityId::new("capability.reference.unimplemented")
1006                .expect("valid synthetic capability id"),
1007        );
1008        assert!(ReferenceDeviceRuntime::new(extra_capability).is_err());
1009    }
1010
1011    #[test]
1012    fn submission_requirements_fail_closed_before_reference_execution() {
1013        assert!(validate_submission_requirements(
1014            DeviceTimingMode::Off,
1015            DeviceComputePathRequirement::Adaptive,
1016            false,
1017        )
1018        .is_ok());
1019        assert!(validate_submission_requirements(
1020            DeviceTimingMode::Off,
1021            DeviceComputePathRequirement::EagerOnly,
1022            false,
1023        )
1024        .is_ok());
1025        assert!(validate_submission_requirements(
1026            DeviceTimingMode::Off,
1027            DeviceComputePathRequirement::ReplayedOnly,
1028            false,
1029        )
1030        .is_err());
1031        assert!(validate_submission_requirements(
1032            DeviceTimingMode::Off,
1033            DeviceComputePathRequirement::ReplayedWithDeclaredEagerBoundaries,
1034            false,
1035        )
1036        .is_err());
1037        assert!(validate_submission_requirements(
1038            DeviceTimingMode::Completion,
1039            DeviceComputePathRequirement::Adaptive,
1040            false,
1041        )
1042        .is_err());
1043        assert!(validate_submission_requirements(
1044            DeviceTimingMode::Off,
1045            DeviceComputePathRequirement::Adaptive,
1046            true,
1047        )
1048        .is_err());
1049    }
1050
1051    #[test]
1052    fn foreign_stream_state_fails_closed() {
1053        let first = ReferenceDeviceRuntime::new(config()).expect("first runtime");
1054        let second = ReferenceDeviceRuntime::new(config()).expect("second runtime");
1055        let stream = first.create_stream().expect("first stream");
1056        assert_eq!(first.stream_state(&stream), StreamState::Ready);
1057        assert_eq!(second.stream_state(&stream), StreamState::Failed);
1058    }
1059
1060    #[test]
1061    fn readback_honors_host_offset_and_element_type() {
1062        let runtime = ReferenceDeviceRuntime::new(config()).expect("reference runtime");
1063        let source = buffer(
1064            &runtime,
1065            "resource.reference.readback",
1066            &[10, 11, 12, 13],
1067            16,
1068            ElementType::U8,
1069        );
1070        let mut stream = runtime.create_stream().expect("reference stream");
1071        let region = CopyRegion::new(1, 2, 2).expect("valid readback region");
1072        let output = runtime
1073            .readback(
1074                &mut stream,
1075                &source,
1076                region,
1077                HostTransferLayout::new(ElementType::U8, 6).expect("valid host layout"),
1078            )
1079            .expect("offset readback");
1080        assert_eq!(output, [0, 0, 11, 12, 0, 0]);
1081        assert!(runtime
1082            .readback(
1083                &mut stream,
1084                &source,
1085                region,
1086                HostTransferLayout::new(ElementType::F16, 3).expect("valid mismatched layout"),
1087            )
1088            .is_err());
1089    }
1090}