1use super::{
2 Access, BufferView, Completion, Engine, KernelContract, Runtime, RuntimeOwner,
3 completion::Signal,
4};
5use crate::{Error, Result};
6use std::sync::{
7 Arc, Mutex,
8 atomic::{AtomicBool, Ordering},
9};
10
11#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, serde::Serialize)]
13pub enum GpuLane {
14 Upload,
16 #[default]
18 Compute,
19 Download,
21}
22
23#[derive(Clone)]
25pub struct GpuKernel {
26 pub(super) raw: crate::gpu::Kernel,
27 pub(super) contract: KernelContract,
28 pub(super) grid: [u32; 3],
29 pub(super) block: [u32; 3],
30 pub(super) runtime: Arc<RuntimeOwner>,
31}
32impl GpuKernel {
33 pub fn sanitizer_reports(&self) -> Result<crate::fabric::SanitizerReports> {
37 self.raw.sanitizer_reports()
38 }
39 pub fn contract(&self) -> &KernelContract {
41 &self.contract
42 }
43}
44#[derive(Clone, Copy, Debug, PartialEq, Eq)]
46pub struct Node(pub(crate) usize);
47#[derive(Clone)]
48pub(super) struct Use {
49 pub view: BufferView,
50 pub access: Access,
51}
52impl Use {
53 #[cfg(test)]
54 pub fn conflicts(&self, other: &Self) -> bool {
55 self.view.conflicts(&other.view) && (self.access.writes() || other.access.writes())
56 }
57}
58enum Description {
59 Fill(BufferView, u8),
60 Copy(BufferView, BufferView),
61 Gpu(GpuKernel, Vec<BufferView>),
62 Scoped(Arc<Mutex<ScopedGpu>>, Vec<BufferView>),
63 #[cfg(feature = "npu")]
64 Npu(crate::npu::NpuKernel, Vec<BufferView>),
65}
66type ScopedGpu = Box<dyn FnMut(&[crate::gpu::View<'_>]) -> Result<()> + Send>;
67struct Entry {
68 operation: Description,
69 uses: Vec<Use>,
70 lane: GpuLane,
71}
72pub struct Graph {
74 runtime: Runtime,
75 entries: Vec<Entry>,
76 fragment_scratch: Vec<(bool, BufferView)>,
79}
80impl Graph {
81 pub(crate) fn validate_runtime(&self, runtime: &Runtime) -> Result<()> {
82 if !self.runtime.same_domain(runtime) {
83 return Err(Error::Message("graph belongs to another runtime".into()));
84 }
85 Ok(())
86 }
87 pub unsafe fn gpu_scoped(
100 &mut self,
101 bindings: &[super::GpuAccess],
102 callback: impl FnMut(&[crate::gpu::View<'_>]) -> Result<()> + Send + 'static,
103 ) -> Result<Node> {
104 for binding in bindings {
105 binding.view.gpu()?;
106 }
107 self.push(
108 Description::Scoped(
109 Arc::new(Mutex::new(Box::new(callback))),
110 bindings.iter().map(|b| b.view.clone()).collect(),
111 ),
112 bindings
113 .iter()
114 .map(|b| Use {
115 view: b.view.clone(),
116 access: b.access,
117 })
118 .collect(),
119 )
120 }
121 pub(super) fn new(runtime: Runtime) -> Self {
122 Self {
123 runtime,
124 entries: Vec::new(),
125 fragment_scratch: Vec::new(),
126 }
127 }
128
129 pub(crate) fn fragment_scratch(
130 &mut self,
131 requests: &[(usize, bool)],
132 ) -> Result<Vec<BufferView>> {
133 let mut used = Vec::with_capacity(requests.len());
134 let mut result = Vec::with_capacity(requests.len());
135 for &(bytes, host_visible) in requests {
136 let best = self
137 .fragment_scratch
138 .iter()
139 .enumerate()
140 .filter(|(index, (host, view))| {
141 !used.contains(index) && *host == host_visible && view.len() >= bytes
142 })
143 .min_by_key(|(_, (_, view))| view.len())
144 .map(|(index, _)| index);
145 let index = if let Some(index) = best {
146 index
147 } else {
148 let placement = if host_visible {
149 super::MemoryPlacement::HostVisible
150 } else {
151 super::MemoryPlacement::GpuLocal
152 };
153 let view = self.runtime.allocate(bytes, placement)?.view();
154 self.fragment_scratch.push((host_visible, view));
155 self.fragment_scratch.len() - 1
156 };
157 used.push(index);
158 result.push(self.fragment_scratch[index].1.slice(0..bytes)?);
159 }
160 Ok(result)
161 }
162 fn push(&mut self, operation: Description, uses: Vec<Use>) -> Result<Node> {
163 for binding in &uses {
164 if !Arc::ptr_eq(&binding.view.buffer.storage.runtime, &self.runtime.inner) {
165 return Err(Error::Message("buffer belongs to another runtime".into()));
166 }
167 }
168 let node = Node(self.entries.len());
169 self.entries.push(Entry {
170 operation,
171 uses,
172 lane: GpuLane::Compute,
173 });
174 Ok(node)
175 }
176 pub fn fill(&mut self, destination: BufferView, value: u8) -> Result<Node> {
178 destination.gpu()?;
179 self.push(
180 Description::Fill(destination.clone(), value),
181 vec![Use {
182 view: destination,
183 access: Access::Write,
184 }],
185 )
186 }
187 pub fn copy(&mut self, destination: BufferView, source: BufferView) -> Result<Node> {
189 let lane = match (
190 source.buffer.storage.pointer.is_null(),
191 destination.buffer.storage.pointer.is_null(),
192 ) {
193 (false, true) => GpuLane::Upload,
194 (true, false) => GpuLane::Download,
195 _ => GpuLane::Compute,
196 };
197 self.copy_on(destination, source, lane)
198 }
199
200 pub fn copy_on(
202 &mut self,
203 destination: BufferView,
204 source: BufferView,
205 lane: GpuLane,
206 ) -> Result<Node> {
207 destination.gpu()?;
208 source.gpu()?;
209 if destination.len() != source.len() || destination.overlaps(&source) {
210 return Err(Error::Message(
211 "copy needs equal, nonoverlapping ranges".into(),
212 ));
213 }
214 let node = self.push(
215 Description::Copy(destination.clone(), source.clone()),
216 vec![
217 Use {
218 view: destination,
219 access: Access::Write,
220 },
221 Use {
222 view: source,
223 access: Access::Read,
224 },
225 ],
226 )?;
227 self.entries[node.0].lane = lane;
228 Ok(node)
229 }
230 pub fn gpu(&mut self, kernel: &GpuKernel, bindings: &[BufferView]) -> Result<Node> {
234 kernel.contract.check(bindings)?;
235 unsafe { self.gpu_aliasing(kernel, bindings) }
237 }
238
239 pub unsafe fn gpu_aliasing(
246 &mut self,
247 kernel: &GpuKernel,
248 bindings: &[BufferView],
249 ) -> Result<Node> {
250 if !Arc::ptr_eq(&kernel.runtime, &self.runtime.inner) {
251 return Err(Error::Message("kernel belongs to another runtime".into()));
252 }
253 kernel.contract.check_extents(bindings)?;
254 for (binding, contract) in bindings.iter().zip(&kernel.contract.bindings) {
255 let address = binding.gpu()?.owner().allocation_address()?;
256 if !(address + binding.offset() as u64).is_multiple_of(contract.alignment as u64) {
257 return Err(Error::Message(
258 "GPU allocation does not satisfy kernel alignment".into(),
259 ));
260 }
261 }
262 let uses = bindings
263 .iter()
264 .zip(&kernel.contract.bindings)
265 .map(|(view, c)| Use {
266 view: view.clone(),
267 access: c.access,
268 })
269 .collect();
270 self.push(Description::Gpu(kernel.clone(), bindings.to_vec()), uses)
271 }
272 #[cfg(feature = "npu")]
276 pub fn npu(&mut self, kernel: &crate::npu::NpuKernel, bindings: &[BufferView]) -> Result<Node> {
277 kernel.contract().check(bindings)?;
278 for (binding, contract) in bindings.iter().zip(&kernel.contract().bindings) {
279 let native = binding
280 .buffer
281 .storage
282 .native
283 .as_ref()
284 .ok_or_else(|| Error::Unsupported("binding has no native storage".into()))?;
285 let address = native.device_address(&kernel.inner.device)?;
286 if !(address + binding.offset() as u64).is_multiple_of(contract.alignment as u64) {
287 return Err(Error::Message(
288 "NPU allocation does not satisfy kernel alignment".into(),
289 ));
290 }
291 }
292 let uses = bindings
293 .iter()
294 .zip(&kernel.contract().bindings)
295 .map(|(view, c)| Use {
296 view: view.clone(),
297 access: c.access,
298 })
299 .collect();
300 self.push(Description::Npu(kernel.clone(), bindings.to_vec()), uses)
301 }
302 pub fn prepare(self) -> Result<ExecutableGraph> {
304 if self.entries.is_empty() {
305 return Err(Error::Message("cannot prepare an empty graph".into()));
306 }
307 let mut operations: Vec<Operation> = Vec::new();
308 let mut index = 0;
309 while index < self.entries.len() {
310 if let Description::Scoped(callback, bindings) = &self.entries[index].operation {
311 operations.push(Operation {
312 lane: GpuLane::Compute,
313 copy_bytes: 0,
314 backend: Backend::Scoped(callback.clone(), bindings.clone()),
315 uses: self.entries[index].uses.clone(),
316 dependencies: Vec::new(),
317 });
318 index += 1;
319 continue;
320 }
321 #[cfg(feature = "npu")]
322 if let Description::Npu(kernel, bindings) = &self.entries[index].operation {
323 let arguments = bindings
324 .iter()
325 .map(|view| -> Result<_> {
326 Ok(crate::fabric::XdnaBinding {
327 buffer: view.buffer.storage.native.as_ref().ok_or_else(|| {
328 Error::Unsupported("missing native XDNA backing".into())
329 })?,
330 offset: view.offset(),
331 length: view.len(),
332 })
333 })
334 .collect::<Result<Vec<_>>>()?;
335 let run = unsafe {
338 kernel.inner.device.prepare_xdna(
339 &kernel.inner.artifact,
340 kernel.inner.columns,
341 &arguments,
342 )
343 }?;
344 operations.push(Operation {
345 lane: GpuLane::Compute,
346 copy_bytes: 0,
347 backend: Backend::Npu(Mutex::new(run), kernel.clone()),
348 uses: self.entries[index].uses.clone(),
349 dependencies: Vec::new(),
350 });
351 index += 1;
352 continue;
353 }
354 if let (
355 super::ComputeEngine::Aql {
356 maximum_private_bytes,
357 },
358 Description::Gpu(kernel, bindings),
359 ) = (
360 self.runtime.options.compute_engine,
361 &self.entries[index].operation,
362 ) {
363 let lane = self.entries[index].lane;
364 let queue =
365 crate::cached_init(&self.runtime.inner.aql_queues[lane as usize], || {
366 self.runtime
367 .inner
368 .device()?
369 .native()
370 .aql_queue_budgeted(maximum_private_bytes, self.runtime.memory_budget())
371 })?;
372 let views = bindings
373 .iter()
374 .map(BufferView::gpu)
375 .collect::<Result<Vec<_>>>()?;
376 let constants = crate::gpu::Constants::from_bytes(&kernel.contract.constants)?;
377 let command = unsafe {
378 kernel
379 .raw
380 .prepare_aql(&queue, kernel.grid, kernel.block, &constants, &views)
381 }?;
382 operations.push(Operation {
383 lane,
384 copy_bytes: 0,
385 backend: Backend::Aql(command),
386 uses: self.entries[index].uses.clone(),
387 dependencies: Vec::new(),
388 });
389 index += 1;
390 continue;
391 }
392 let start = index;
393 let lane = self.entries[start].lane;
394 while index < self.entries.len() {
395 if matches!(self.entries[index].operation, Description::Scoped(..)) {
396 break;
397 }
398 if matches!(
399 self.runtime.options.compute_engine,
400 super::ComputeEngine::Aql { .. }
401 ) && matches!(self.entries[index].operation, Description::Gpu(..))
402 {
403 break;
404 }
405 if self.entries[index].lane != lane {
406 break;
407 }
408 #[cfg(feature = "npu")]
409 if matches!(self.entries[index].operation, Description::Npu(..)) {
410 break;
411 }
412 index += 1;
413 }
414 if self.entries[start..index]
417 .iter()
418 .all(|e| matches!(e.operation, Description::Copy(..)))
419 {
420 if self.runtime.options.copy_engine == super::CopyEngine::Sdma {
421 let queue =
422 crate::cached_init(&self.runtime.inner.sdma_queues[lane as usize], || {
423 self.runtime.inner.device()?.native().sdma_queue()
424 })?;
425 let commands = self.entries[start..index]
426 .iter()
427 .map(|entry| {
428 let Description::Copy(dst, src) = &entry.operation else {
429 unreachable!()
430 };
431 let dst = dst.gpu()?;
432 let src = src.gpu()?;
433 queue.prepare_copy_reserved(
434 &dst.owner().native,
435 dst.offset(),
436 &src.owner().native,
437 src.offset(),
438 dst.len(),
439 self.runtime.memory_budget(),
440 )
441 })
442 .collect::<Result<Vec<_>>>()?;
443 operations.push(Operation {
444 lane,
445 copy_bytes: self.entries[start..index]
446 .iter()
447 .map(|entry| match &entry.operation {
448 Description::Copy(dst, _) => dst.len(),
449 _ => unreachable!(),
450 })
451 .sum(),
452 backend: Backend::Sdma { commands },
453 uses: self.entries[start..index]
454 .iter()
455 .flat_map(|entry| entry.uses.iter().cloned())
456 .collect(),
457 dependencies: Vec::new(),
458 });
459 continue;
460 }
461 let stream =
462 crate::cached_init(&self.runtime.inner.copy_streams[lane as usize], || {
463 let stream = self.runtime.inner.device()?.stream()?;
464 self.runtime
465 .inner
466 .core
467 .counters
468 .copy_streams_created
469 .fetch_add(1, Ordering::Relaxed);
470 Ok(Arc::new(Mutex::new(CopyStream {
471 stream,
472 healthy: true,
473 })))
474 })?;
475 let copies: Vec<_> = self.entries[start..index]
476 .iter()
477 .map(|entry| {
478 let Description::Copy(dst, src) = &entry.operation else {
479 unreachable!()
480 };
481 (dst.clone(), src.clone())
482 })
483 .collect();
484 operations.push(Operation {
485 lane,
486 copy_bytes: copies.iter().map(|(dst, _)| dst.len()).sum(),
487 backend: Backend::Copies(stream, copies),
488 uses: self.entries[start..index]
489 .iter()
490 .flat_map(|e| e.uses.iter().cloned())
491 .collect(),
492 dependencies: Vec::new(),
493 });
494 continue;
495 }
496 let stream = self.runtime.inner.device()?.stream()?;
497 let mut graph = stream.graph()?;
498 let mut nodes = Vec::new();
499 let mut uses = Vec::new();
500 let mut frontier = super::dependencies::Frontier::default();
501 for (i, entry) in self.entries[start..index].iter().enumerate() {
502 let dependencies = frontier
503 .dependencies(i, &entry.uses)
504 .into_iter()
505 .map(|j| nodes[j])
506 .collect::<Vec<_>>();
507 let node = match &entry.operation {
508 Description::Fill(dst, value) => {
509 graph.fill(&dependencies, dst.gpu()?, *value)?
510 }
511 Description::Copy(dst, src) => {
512 graph.copy(&dependencies, dst.gpu()?, src.gpu()?)?
513 }
514 Description::Gpu(kernel, bindings) => {
515 let views = bindings
516 .iter()
517 .map(BufferView::gpu)
518 .collect::<Result<Vec<_>>>()?;
519 let constants =
520 crate::gpu::Constants::from_bytes(&kernel.contract.constants)?;
521 unsafe {
522 graph.dispatch(
523 &dependencies,
524 &kernel.raw,
525 kernel.grid,
526 kernel.block,
527 &constants,
528 &views,
529 )
530 }?
531 }
532 Description::Scoped(..) => unreachable!(),
533 #[cfg(feature = "npu")]
534 Description::Npu(..) => unreachable!(),
535 };
536 nodes.push(node);
537 uses.extend(entry.uses.iter().cloned());
538 }
539 let executable = graph.finish()?;
540 self.runtime
541 .inner
542 .core
543 .counters
544 .native_graphs_prepared
545 .fetch_add(1, Ordering::Relaxed);
546 operations.push(Operation {
547 lane,
548 copy_bytes: self.entries[start..index]
549 .iter()
550 .filter_map(|entry| match &entry.operation {
551 Description::Copy(dst, _) => Some(dst.len()),
552 _ => None,
553 })
554 .sum(),
555 backend: Backend::Gpu(Box::new(Mutex::new((stream, executable)))),
556 uses: super::dependencies::summarize(uses),
557 dependencies: Vec::new(),
558 });
559 }
560 let mut frontier = super::dependencies::Frontier::default();
561 for (index, operation) in operations.iter_mut().enumerate() {
562 operation.uses = super::dependencies::summarize(operation.uses.drain(..));
563 operation.dependencies = frontier.dependencies(index, &operation.uses);
564 }
565 let uses =
566 super::dependencies::summarize(operations.iter().flat_map(|o| o.uses.iter().cloned()));
567 let slots = (0..self.runtime.options.graph_slots)
568 .map(|_| Slot {
569 nodes: vec![NodeState::Pending; operations.len()],
570 occupied: false,
571 failure: None,
572 })
573 .collect();
574 Ok(ExecutableGraph {
575 inner: Arc::new(Prepared {
576 quarantined: AtomicBool::new(false),
577 signals: (0..self.runtime.options.graph_slots)
578 .map(|_| Signal::new())
579 .collect(),
580 runtime: self.runtime.inner,
581 parallel: operations
582 .iter()
583 .enumerate()
584 .skip(1)
585 .any(|(index, op)| !op.dependencies.contains(&(index - 1))),
586 operations,
587 uses,
588 slots: Mutex::new(slots),
589 }),
590 })
591 }
592}
593pub(super) struct CopyStream {
594 stream: crate::gpu::Stream,
595 healthy: bool,
596}
597pub(super) enum Backend {
598 Aql(crate::fabric::PreparedAql),
599 Sdma {
600 commands: Vec<crate::fabric::PreparedGpu>,
601 },
602 Copies(Arc<Mutex<CopyStream>>, Vec<(BufferView, BufferView)>),
603 Gpu(Box<Mutex<(crate::gpu::Stream, crate::gpu::GraphExec)>>),
604 Scoped(Arc<Mutex<ScopedGpu>>, Vec<BufferView>),
605 #[cfg(feature = "npu")]
606 Npu(Mutex<crate::fabric::XdnaProgram>, crate::npu::NpuKernel),
607 #[cfg(test)]
608 Mock(Engine, Arc<dyn Fn() -> Result<()> + Send + Sync>),
609}
610pub(super) struct Operation {
611 pub lane: GpuLane,
612 pub copy_bytes: usize,
613 pub backend: Backend,
614 pub uses: Vec<Use>,
615 pub dependencies: Vec<usize>,
616}
617impl Operation {
618 pub fn queue(&self) -> usize {
619 match self.engine() {
620 Engine::Gpu => self.lane as usize,
621 Engine::Npu => 3,
622 Engine::Host => 4,
623 }
624 }
625 pub fn engine(&self) -> Engine {
626 match &self.backend {
627 Backend::Aql(_)
628 | Backend::Gpu(_)
629 | Backend::Scoped(..)
630 | Backend::Copies(..)
631 | Backend::Sdma { .. } => Engine::Gpu,
632 #[cfg(feature = "npu")]
633 Backend::Npu(..) => Engine::Npu,
634 #[cfg(test)]
635 Backend::Mock(engine, _) => *engine,
636 }
637 }
638 pub fn run(&self) -> Result<()> {
639 for access in &self.uses {
640 if let Some(message) = &access
641 .view
642 .buffer
643 .storage
644 .host
645 .lock()
646 .unwrap_or_else(|e| e.into_inner())
647 .poison
648 {
649 return Err(Error::DeviceLost(message.clone()));
650 }
651 access.view.buffer.storage.make_visible(self.engine())?;
652 }
653 match &self.backend {
654 Backend::Aql(command) => {
655 unsafe { command.dispatch() }?.wait()?;
656 }
657 Backend::Sdma { commands, .. } => {
658 let mut last = None;
659 let submitted: Result<()> = (|| {
660 for command in commands {
661 let _uses = command.retain_parts()?;
662 for part in command.parts() {
663 last = Some(unsafe { part.dispatch_part_wait() }?);
664 }
665 }
666 Ok(())
667 })();
668 if let Some(done) = last {
670 done.wait()?;
671 }
672 submitted?;
673 }
674 Backend::Copies(stream, copies) => {
675 let mut stream = stream.lock().unwrap_or_else(|e| e.into_inner());
676 if !stream.healthy {
677 return Err(Error::DeviceLost("copy stream is quarantined".into()));
678 }
679 let result = (|| {
680 for (dst, src) in copies {
681 stream.stream.copy(dst.gpu()?, src.gpu()?)?;
682 }
683 stream.stream.synchronize()
684 })();
685 if result.is_err() {
686 stream.healthy = false;
687 }
688 result?;
689 }
690 Backend::Gpu(pair) => {
691 let mut pair = pair.lock().unwrap_or_else(|e| e.into_inner());
692 let (stream, graph) = &mut *pair;
693 stream.launch(graph)?;
694 stream.synchronize()?;
695 }
696 Backend::Scoped(callback, bindings) => {
697 let views = bindings
698 .iter()
699 .map(BufferView::gpu)
700 .collect::<Result<Vec<_>>>()?;
701 callback.lock().unwrap_or_else(|e| e.into_inner())(&views)?;
702 }
703 #[cfg(feature = "npu")]
704 Backend::Npu(run, _kernel) => {
705 unsafe { run.lock().unwrap_or_else(|e| e.into_inner()).dispatch() }?.wait()?
706 }
707 #[cfg(test)]
708 Backend::Mock(_, action) => action()?,
709 }
710 if self.copy_bytes != 0
711 && let Some(access) = self.uses.first()
712 {
713 access
714 .view
715 .buffer
716 .storage
717 .runtime
718 .core
719 .counters
720 .lane_copy_bytes[self.lane as usize]
721 .fetch_add(self.copy_bytes as u64, std::sync::atomic::Ordering::Relaxed);
722 access
723 .view
724 .buffer
725 .storage
726 .runtime
727 .core
728 .counters
729 .copied_bytes
730 .fetch_add(self.copy_bytes as u64, std::sync::atomic::Ordering::Relaxed);
731 }
732 for access in &self.uses {
733 if access.access.writes() {
734 access
735 .view
736 .buffer
737 .storage
738 .visibility
739 .lock()
740 .unwrap_or_else(|e| e.into_inner())
741 .wrote(self.engine());
742 }
743 }
744 Ok(())
745 }
746}
747#[derive(Clone, Copy, PartialEq, Eq)]
748pub(super) enum NodeState {
749 Pending,
750 Running,
751 Done,
752}
753pub(super) struct Slot {
754 pub nodes: Vec<NodeState>,
755 pub occupied: bool,
756 pub failure: Option<Arc<Error>>,
757}
758pub(super) struct Prepared {
759 pub quarantined: AtomicBool,
760 pub runtime: Arc<RuntimeOwner>,
761 pub parallel: bool,
762 pub operations: Vec<Operation>,
763 pub uses: Vec<Use>,
764 pub slots: Mutex<Vec<Slot>>,
765 pub signals: Vec<Arc<Signal>>,
766}
767#[derive(Clone)]
769pub struct ExecutableGraph {
770 pub(super) inner: Arc<Prepared>,
771}
772impl ExecutableGraph {
773 pub(crate) fn validate_runtime(&self, runtime: &Runtime) -> Result<()> {
774 if !Arc::ptr_eq(&self.inner.runtime, &runtime.inner) {
775 return Err(Error::Message("graph belongs to another runtime".into()));
776 }
777 Ok(())
778 }
779
780 pub(crate) fn usable(&self) -> Result<()> {
781 if self.inner.quarantined.load(Ordering::Acquire) {
782 return Err(Error::DeviceLost(
783 "graph quarantined after native failure".into(),
784 ));
785 }
786 for usage in &self.inner.uses {
787 if let Some(message) = &usage
788 .view
789 .buffer
790 .storage
791 .host
792 .lock()
793 .unwrap_or_else(|e| e.into_inner())
794 .poison
795 {
796 return Err(Error::DeviceLost(message.clone()));
797 }
798 }
799 Ok(())
800 }
801 pub fn submit(&self) -> Result<Completion> {
804 self.submit_after(&[])
805 }
806
807 pub fn submit_after(&self, dependencies: &[Completion]) -> Result<Completion> {
810 self.usable()?;
811 let core = &self.inner.runtime.core;
812 for dependency in dependencies {
813 if let Some(owner) = dependency.core.upgrade() {
814 if !Arc::ptr_eq(core, &owner) {
815 return Err(Error::Message(
816 "completion belongs to another runtime".into(),
817 ));
818 }
819 } else if !dependency.is_complete() {
820 return Err(Error::Message("orphaned incomplete producer".into()));
821 }
822 }
823 let mut scheduler = core.state.lock().unwrap_or_else(|e| e.into_inner());
824 if scheduler.occupied() >= scheduler.capacity {
825 return Err(Error::Busy("runtime submission capacity exhausted".into()));
826 }
827 for access in &self.inner.uses {
828 access.view.buffer.storage.host_conflict(access.access)?;
829 }
830 let mut slots = self.inner.slots.lock().unwrap_or_else(|e| e.into_inner());
831 let (slot_index, slot) = slots
832 .iter_mut()
833 .enumerate()
834 .find(|(index, slot)| {
835 let signal = &self.inner.signals[*index];
836 !slot.occupied
837 && signal.observers.load(Ordering::Acquire) == 0
838 && signal.state.lock().unwrap_or_else(|e| e.into_inner()).done
839 })
840 .ok_or_else(|| {
841 Error::Busy("prepared run slots are occupied; drop completed observers or reserve more slots".into())
842 })?;
843 self.inner.signals[slot_index].reset();
844 slot.nodes.fill(NodeState::Pending);
845 slot.failure = None;
846 slot.occupied = true;
847 let completion =
848 Completion::new(self.inner.signals[slot_index].clone(), Arc::downgrade(core));
849 core.counters
850 .submissions
851 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
852 scheduler.enqueue(self.inner.clone(), slot_index, dependencies.to_vec());
853 drop(slots);
854 drop(scheduler);
855 core.changed.notify_one();
856 Ok(completion)
857 }
858 pub fn regions(&self) -> usize {
860 self.inner.operations.len()
861 }
862}