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
70pub 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
217pub 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
508pub 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}