Skip to main content

hrx/fabric/
memory.rs

1use super::*;
2use std::sync::Mutex;
3
4/// Additional requirements for fresh system backing. Unsupported constraints
5/// fail allocation; placement never silently falls back to another NUMA node.
6#[derive(Clone, Copy, Debug, Default)]
7pub struct AllocationOptions {
8    /// Prepare native source handles for remapping by every device consumer.
9    pub mapping_source: bool,
10    /// Prepare replaceable write-back CPU aliases.
11    pub host_mapping_source: bool,
12    /// Require physical host pages on this OS NUMA node.
13    pub host_numa_node: Option<u32>,
14    /// Require coherent GPU attachments (does not imply NPU coherence).
15    pub coherent_gpu: bool,
16    /// Minimum power-of-two alignment, or zero for provider policy.
17    pub minimum_alignment: u64,
18    /// Additional whole-backing flags, such as reservation capability requirements.
19    /// Constants are in [`native`]; per-device flags belong in the fields above.
20    pub required_backing_flags: u64,
21}
22impl AllocationOptions {
23    fn required_flags(self) -> u64 {
24        AMDF_MEMORY_FLAG_HOST_VISIBLE as u64
25            | self.required_backing_flags
26            | if self.host_mapping_source {
27                AMDF_MEMORY_FLAG_HOST_MAPPING_SOURCE as u64
28            } else {
29                0
30            }
31    }
32    fn placement(self) -> amdf_memory_placement_t {
33        self.host_numa_node
34            .map_or_else(Default::default, |node| amdf_memory_placement_t {
35                kind: AMDF_MEMORY_PLACEMENT_KIND_HOST_NUMA,
36                node,
37            })
38    }
39}
40
41/// Shared backing with explicit device access and checked host transfers.
42#[derive(Clone)]
43pub struct Buffer(pub(super) Arc<Memory>);
44pub(super) struct Memory {
45    pub(super) instance: Arc<Instance>,
46    pub(super) devices: Vec<Device>,
47    pub(super) raw: *mut amdf_memory_t,
48    pub(super) mapping: *mut amdf_host_mapping_t,
49    pub(super) pointer: *mut u8,
50    bytes: usize,
51    needs_cache: Arc<std::sync::atomic::AtomicBool>,
52    pub(super) source: Option<Buffer>,
53    pub(super) reservation: Option<Arc<crate::residency::MemoryReservation>>,
54    host_storage: Option<Arc<memmap2::MmapMut>>,
55    // Submission leases and host access serialize through this lock. A lease
56    // lasts through terminal completion, not just command publication.
57    pub(super) uses: Arc<Mutex<usize>>,
58}
59// The immutable mapping is accessed only under uses; commands must acquire a
60// use lease before touching backing. Native metadata queries are thread-safe.
61unsafe impl Send for Memory {}
62unsafe impl Sync for Memory {}
63impl Drop for Memory {
64    fn drop(&mut self) {
65        let api = self.instance.api.core;
66        unsafe {
67            let mapping_released =
68                self.mapping.is_null() || api.host_mapping_destroy.unwrap()(self.mapping) == 0;
69            if !mapping_released || api.memory_destroy.unwrap()(self.raw) != 0 {
70                // Native detach failed: preserve the required owner lifetime.
71                std::mem::forget(self.instance.clone());
72                std::mem::forget(self.devices.clone());
73                std::mem::forget(self.source.take());
74                std::mem::forget(self.reservation.take());
75                std::mem::forget(self.host_storage.take());
76            }
77        }
78    }
79}
80impl Buffer {
81    pub(super) fn owns_registered_pages(&self) -> bool {
82        self.0.host_storage.is_some()
83    }
84
85    pub(crate) fn identity(&self) -> usize {
86        Arc::as_ptr(&self.0) as usize
87    }
88    pub(crate) fn same_backing(&self, other: &Self) -> bool {
89        Arc::ptr_eq(&self.0, &other.0)
90    }
91    pub(crate) fn exclusively_owned(&self) -> bool {
92        Arc::strong_count(&self.0) == 1
93    }
94    pub(crate) fn zero(&self) -> Result<()> {
95        let uses = self
96            .0
97            .uses
98            .lock()
99            .map_err(|_| Error::DeviceLost("memory ownership poisoned".into()))?;
100        if *uses != 0 {
101            return Err(Error::Busy("memory is executing".into()));
102        }
103        unsafe {
104            ptr::write_bytes(self.host_pointer(), 0, self.len());
105            self.cache_control(true, 0, self.len())
106        }
107    }
108
109    pub(super) fn retain_use(&self) -> Result<DeviceUse> {
110        let mut uses = self
111            .0
112            .uses
113            .lock()
114            .map_err(|_| Error::DeviceLost("memory ownership poisoned".into()))?;
115        *uses = uses
116            .checked_add(1)
117            .ok_or_else(|| Error::Busy("memory lease limit".into()))?;
118        Ok(DeviceUse(self.clone()))
119    }
120    // Only privately owned fence buffers use this path while a device writes.
121    pub(super) fn fence_value(&self) -> Result<u32> {
122        if self.len() < 4 {
123            return Err(Error::Message("completion storage is too small".into()));
124        }
125        unsafe {
126            // Fence allocations have only a HOST_COHERENT GPU consumer. GPU
127            // release packets and the acquire load establish visibility.
128            if self
129                .0
130                .needs_cache
131                .load(std::sync::atomic::Ordering::Acquire)
132            {
133                return Err(Error::Message(
134                    "completion fence must be GPU-coherent only".into(),
135                ));
136            }
137            Ok(self
138                .0
139                .pointer
140                .cast::<std::sync::atomic::AtomicU32>()
141                .as_ref()
142                .unwrap()
143                .load(std::sync::atomic::Ordering::Acquire))
144        }
145    }
146    pub(crate) fn host_pointer(&self) -> *mut u8 {
147        self.0.pointer
148    }
149    /// Publish or acquire a mapped range under an external scheduling lease.
150    ///
151    /// # Safety
152    /// The caller must exclude conflicting host and device access until this
153    /// cache operation completes, and retain this allocation through that use.
154    pub unsafe fn cache_control(&self, flush: bool, offset: usize, length: usize) -> Result<()> {
155        self.range(offset, length)?;
156        // Owned imports retain the original mapping as the maintenance site
157        // for these exact physical bytes, including its cache-policy contract.
158        if let Some(source) = &self.0.source {
159            return unsafe { source.cache_control(flush, offset, length) };
160        }
161        if !self
162            .0
163            .needs_cache
164            .load(std::sync::atomic::Ordering::Acquire)
165        {
166            std::sync::atomic::fence(std::sync::atomic::Ordering::SeqCst);
167            return Ok(());
168        }
169        check("explicit mapped visibility", unsafe {
170            entry!(self.0.instance.api.core, host_mapping_cache_control)(
171                self.0.mapping,
172                if flush {
173                    AMDF_HOST_CACHE_OPERATION_FLUSH
174                } else {
175                    AMDF_HOST_CACHE_OPERATION_INVALIDATE
176                },
177                offset as u64,
178                length as u64,
179            )
180        })
181    }
182    /// Logical byte length; allocation rounding grants no additional access.
183    pub fn len(&self) -> usize {
184        self.0.bytes
185    }
186    /// Allocations are always nonempty.
187    pub fn is_empty(&self) -> bool {
188        self.len() == 0
189    }
190    fn range(&self, offset: usize, length: usize) -> Result<()> {
191        if offset
192            .checked_add(length)
193            .is_none_or(|end| end > self.len())
194        {
195            return Err(Error::Message("buffer range is out of bounds".into()));
196        }
197        Ok(())
198    }
199    /// Copy and publish host bytes. In-flight device use returns Busy.
200    pub fn write(&self, offset: usize, bytes: &[u8]) -> Result<()> {
201        self.range(offset, bytes.len())?;
202        let uses = self
203            .0
204            .uses
205            .lock()
206            .map_err(|_| Error::DeviceLost("memory ownership poisoned".into()))?;
207        if *uses != 0 {
208            return Err(Error::Busy("buffer is in use by a device".into()));
209        }
210        unsafe {
211            // A partial host write must preserve bytes in the same cache line
212            // that the GPU may have changed since the last host read.
213            self.cache_control(false, offset, bytes.len())?;
214            ptr::copy_nonoverlapping(bytes.as_ptr(), self.0.pointer.add(offset), bytes.len());
215            self.cache_control(true, offset, bytes.len())
216        }
217    }
218    /// Acquire and copy completed device results. In-flight use returns Busy.
219    pub fn read(&self, offset: usize, bytes: &mut [u8]) -> Result<()> {
220        self.range(offset, bytes.len())?;
221        let uses = self
222            .0
223            .uses
224            .lock()
225            .map_err(|_| Error::DeviceLost("memory ownership poisoned".into()))?;
226        if *uses != 0 {
227            return Err(Error::Busy("buffer is in use by a device".into()));
228        }
229        unsafe {
230            self.cache_control(false, offset, bytes.len())?;
231            ptr::copy_nonoverlapping(self.0.pointer.add(offset), bytes.as_mut_ptr(), bytes.len());
232        }
233        Ok(())
234    }
235    // Private runtime state needs one indivisible host transaction against
236    // submission admission; a separate read/write pair would race new uses.
237    pub(super) fn with_host_bytes<T>(
238        &self,
239        action: impl FnOnce(&mut [u8]) -> Result<T>,
240    ) -> Result<T> {
241        let uses = self
242            .0
243            .uses
244            .lock()
245            .map_err(|_| Error::DeviceLost("memory ownership poisoned".into()))?;
246        if *uses != 0 {
247            return Err(Error::Busy("buffer is in use by a device".into()));
248        }
249        unsafe {
250            self.cache_control(false, 0, self.len())?;
251            let result = action(std::slice::from_raw_parts_mut(self.0.pointer, self.len()));
252            self.cache_control(true, 0, self.len())?;
253            result
254        }
255    }
256    /// Address in an explicitly admitted device's primary data interface.
257    /// The address is valid only while this buffer and device remain alive.
258    pub fn device_address(&self, device: &Device) -> Result<u64> {
259        let index = self
260            .0
261            .devices
262            .iter()
263            .position(|d| Arc::ptr_eq(&d.0, &device.0));
264        let Some(index) = index else {
265            return self.0.source.as_ref().map_or_else(
266                || Err(Error::Message("device has no access to this buffer".into())),
267                |source| source.device_address(device),
268            );
269        };
270        let kind = match device.endpoint().engine() {
271            Engine::Gpu => AMDF_MEMORY_ADDRESS_GPU,
272            Engine::Xdna => AMDF_MEMORY_ADDRESS_XDNA_DMA,
273        };
274        let mut address = 0;
275        check("memory_query_address", unsafe {
276            entry!(self.0.instance.api.core, memory_query_address)(
277                self.0.raw,
278                index as u32,
279                kind,
280                &mut address,
281            )
282        })?;
283        Ok(address)
284    }
285}
286
287pub(super) struct DeviceUse(Buffer);
288impl Drop for DeviceUse {
289    fn drop(&mut self) {
290        let mut uses = self.0.0.uses.lock().unwrap_or_else(|e| e.into_inner());
291        *uses -= 1;
292    }
293}
294
295#[derive(Default)]
296struct MemoryOwners {
297    coherent: bool,
298    profile: Option<(*mut amdf_memory_scope_t, u32)>,
299    source: Option<Buffer>,
300    reservation: Option<Arc<crate::residency::MemoryReservation>>,
301    host_storage: Option<Arc<memmap2::MmapMut>>,
302    options: AllocationOptions,
303}
304struct ExternalMemory {
305    api: Arc<Api>,
306    value: amdf_external_memory_t,
307}
308impl Drop for ExternalMemory {
309    fn drop(&mut self) {
310        unsafe {
311            self.api.core.external_memory_release.unwrap()(&mut self.value);
312        }
313    }
314}
315impl Fabric {
316    /// Allocate initialized system memory with explicit remapping/placement
317    /// requirements. Device access ordinals follow the order in `devices`.
318    pub fn allocate_with_options(
319        &self,
320        bytes: usize,
321        devices: &[Device],
322        options: AllocationOptions,
323        budget: Option<&crate::residency::MemoryBudget>,
324    ) -> Result<Buffer> {
325        self.create_memory(
326            bytes,
327            devices,
328            AMDF_MEMORY_ACCESS_READ | AMDF_MEMORY_ACCESS_WRITE,
329            options.minimum_alignment,
330            None,
331            MemoryOwners {
332                coherent: options.coherent_gpu,
333                reservation: budget.map(|b| b.reserve(bytes).map(Arc::new)).transpose()?,
334                options,
335                ..Default::default()
336            },
337            None,
338        )
339    }
340    /// Allocate page-rounded anonymous host storage and register device access.
341    /// The mapping and optional budget charge remain owned by every buffer alias,
342    /// pending device use, and native-detach quarantine. Useful for kernel I/O
343    /// registration requiring ordinary host pages rather than device allocations.
344    /// Linux KFD requires [`NativeLifetime::Process`] for this construction role.
345    pub fn allocate_registered(
346        &self,
347        bytes: usize,
348        devices: &[Device],
349        budget: Option<&crate::residency::MemoryBudget>,
350    ) -> Result<Buffer> {
351        let page = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
352        if page <= 0 || bytes == 0 {
353            return Err(Error::Message("invalid registered allocation size".into()));
354        }
355        let page = page as usize;
356        let bytes = bytes
357            .checked_add(page - 1)
358            .map(|v| v / page * page)
359            .ok_or_else(|| Error::Message("registered allocation overflow".into()))?;
360        let reservation = budget.map(|b| b.reserve(bytes).map(Arc::new)).transpose()?;
361        let mut mapping = memmap2::MmapOptions::new().len(bytes).map_anon()?;
362        let pointer = mapping.as_mut_ptr();
363        self.create_memory(
364            bytes,
365            devices,
366            AMDF_MEMORY_ACCESS_READ | AMDF_MEMORY_ACCESS_WRITE,
367            page as u64,
368            Some(pointer.cast()),
369            MemoryOwners {
370                coherent: true,
371                reservation,
372                host_storage: Some(Arc::new(mapping)),
373                ..Default::default()
374            },
375            None,
376        )
377    }
378    /// Allocate GPU-coherent host storage for direct host access and polling.
379    /// Host and device accesses must still be ordered by completion.
380    pub fn allocate_shared(&self, bytes: usize, devices: &[Device]) -> Result<Buffer> {
381        self.create_memory(
382            bytes,
383            devices,
384            AMDF_MEMORY_ACCESS_READ | AMDF_MEMORY_ACCESS_WRITE,
385            64,
386            None,
387            MemoryOwners {
388                coherent: true,
389                ..Default::default()
390            },
391            None,
392        )
393    }
394    /// Allocate host-visible system backing with access for every listed device.
395    /// Devices must belong to this fabric; no implicit device activation occurs.
396    pub fn allocate(&self, bytes: usize, devices: &[Device]) -> Result<Buffer> {
397        self.allocate_access(
398            bytes,
399            devices,
400            AMDF_MEMORY_ACCESS_READ | AMDF_MEMORY_ACCESS_WRITE,
401            64,
402        )
403    }
404    /// Allocate and publish a nonempty host byte slice in one initialization pass.
405    /// The returned storage owns a copy; the input may be released immediately.
406    pub fn allocate_from(&self, data: &[u8], devices: &[Device]) -> Result<Buffer> {
407        self.create_memory(
408            data.len(),
409            devices,
410            AMDF_MEMORY_ACCESS_READ | AMDF_MEMORY_ACCESS_WRITE,
411            64,
412            None,
413            MemoryOwners::default(),
414            Some(data),
415        )
416    }
417    /// Allocate backing under a shared residency ceiling. Clones and in-flight
418    /// native owners retain the charge until safe destruction or quarantine.
419    pub fn allocate_budgeted(
420        &self,
421        bytes: usize,
422        devices: &[Device],
423        budget: &crate::residency::MemoryBudget,
424    ) -> Result<Buffer> {
425        let reservation = Arc::new(budget.reserve(bytes)?);
426        self.create_memory(
427            bytes,
428            devices,
429            AMDF_MEMORY_ACCESS_READ | AMDF_MEMORY_ACCESS_WRITE,
430            64,
431            None,
432            MemoryOwners {
433                coherent: false,
434                profile: None,
435                source: None,
436                host_storage: None,
437                reservation: Some(reservation),
438                ..Default::default()
439            },
440            None,
441        )
442    }
443    pub(super) fn allocate_access(
444        &self,
445        bytes: usize,
446        devices: &[Device],
447        access: u32,
448        alignment: u64,
449    ) -> Result<Buffer> {
450        self.create_memory(
451            bytes,
452            devices,
453            access,
454            alignment,
455            None,
456            MemoryOwners::default(),
457            None,
458        )
459    }
460    pub(super) fn allocate_owned(
461        &self,
462        bytes: usize,
463        device: &Device,
464        access: u32,
465        alignment: u64,
466        coherent: bool,
467        budget: Option<&crate::residency::MemoryBudget>,
468    ) -> Result<Buffer> {
469        let reservation = budget.map(|b| b.reserve(bytes).map(Arc::new)).transpose()?;
470        self.create_memory(
471            bytes,
472            std::slice::from_ref(device),
473            access,
474            alignment,
475            None,
476            MemoryOwners {
477                coherent,
478                profile: None,
479                source: None,
480                reservation,
481                host_storage: None,
482                ..Default::default()
483            },
484            None,
485        )
486    }
487    #[cfg(feature = "npu")]
488    pub(crate) fn share_owned(&self, source: &Buffer, devices: &[Device]) -> Result<Buffer> {
489        let additional: Vec<_> = devices
490            .iter()
491            .filter(|device| source.device_address(device).is_err())
492            .cloned()
493            .collect();
494        if additional.is_empty() {
495            return Ok(source.clone());
496        }
497        self.create_memory(
498            source.len(),
499            &additional,
500            AMDF_MEMORY_ACCESS_READ | AMDF_MEMORY_ACCESS_WRITE,
501            4096,
502            None,
503            MemoryOwners {
504                coherent: false,
505                profile: None,
506                host_storage: None,
507                source: Some(source.clone()),
508                reservation: None,
509                ..Default::default()
510            },
511            None,
512        )
513    }
514
515    /// Register caller-owned host pages with explicit device access.
516    ///
517    /// # Safety
518    /// The aligned host range must stay allocated and stable until this buffer,
519    /// its clones, and every accepted device use have been destroyed. Host access
520    /// must be ordered against all device use, including native aliases.
521    pub unsafe fn register_host(
522        &self,
523        pointer: *mut std::ffi::c_void,
524        bytes: usize,
525        devices: &[Device],
526    ) -> Result<Buffer> {
527        if pointer.is_null() {
528            return Err(Error::Message("host registration pointer is null".into()));
529        }
530        self.create_memory(
531            bytes,
532            devices,
533            AMDF_MEMORY_ACCESS_READ | AMDF_MEMORY_ACCESS_WRITE,
534            4096,
535            Some(pointer),
536            MemoryOwners {
537                coherent: true,
538                ..Default::default()
539            },
540            None,
541        )
542    }
543    #[allow(clippy::too_many_arguments)]
544    fn create_memory(
545        &self,
546        bytes: usize,
547        devices: &[Device],
548        access: u32,
549        alignment: u64,
550        host: Option<*mut std::ffi::c_void>,
551        owners: MemoryOwners,
552        initial_data: Option<&[u8]>,
553    ) -> Result<Buffer> {
554        if bytes == 0 {
555            return Err(Error::Message("allocation must be nonempty".into()));
556        }
557        if initial_data
558            .is_some_and(|data| data.len() != bytes || host.is_some() || owners.source.is_some())
559        {
560            return Err(Error::Message(
561                "initial data must cover exactly one new allocation".into(),
562            ));
563        }
564        for (index, device) in devices.iter().enumerate() {
565            if !Arc::ptr_eq(&self.0, &device.0.endpoint.0.instance)
566                || devices[..index]
567                    .iter()
568                    .any(|other| Arc::ptr_eq(&other.0, &device.0))
569            {
570                return Err(Error::Message(
571                    "allocation devices must be unique and belong to this fabric".into(),
572                ));
573            }
574        }
575        let api = self.0.api.core;
576        let _ = entry!(api, memory_destroy);
577        let _ = entry!(api, host_mapping_destroy);
578        let _ = entry!(api, host_mapping_cache_control);
579        let mut external = if let Some(source) = &owners.source {
580            let _ = entry!(api, external_memory_release);
581            let mut external = ExternalMemory {
582                api: self.0.api.clone(),
583                value: Default::default(),
584            };
585            let options = amdf_memory_export_info_t {
586                type_: AMDF_STRUCTURE_TYPE_MEMORY_EXPORT_INFO,
587                structure_size: size_of::<amdf_memory_export_info_t>() as u32,
588                external_memory_type: AMDF_EXTERNAL_MEMORY_TYPE_DMA_BUF_FD,
589                byte_length: bytes as u64,
590                ..Default::default()
591            };
592            check("memory_export", unsafe {
593                entry!(api, memory_export)(source.0.raw, &options, &mut external.value)
594            })?;
595            Some(external)
596        } else {
597            None
598        };
599        let accesses: Vec<_> = devices
600            .iter()
601            .map(|device| amdf_memory_device_access_t {
602                device: device.0.raw,
603                requirements: amdf_memory_access_requirements_t {
604                    access,
605                    flags: AMDF_MEMORY_FLAG_DEVICE_ADDRESS as u64
606                        | if owners.options.mapping_source {
607                            AMDF_MEMORY_FLAG_MAPPING_SOURCE as u64
608                        } else {
609                            0
610                        }
611                        | if owners.coherent && device.endpoint().engine() == Engine::Gpu {
612                            AMDF_MEMORY_FLAG_HOST_COHERENT as u64
613                        } else {
614                            0
615                        },
616                    address_kinds: 1
617                        << match device.endpoint().engine() {
618                            Engine::Gpu => AMDF_MEMORY_ADDRESS_GPU,
619                            Engine::Xdna => AMDF_MEMORY_ADDRESS_XDNA_DMA,
620                        },
621                    ..Default::default()
622                },
623            })
624            .collect();
625        let mut count = 0;
626        let status = unsafe {
627            entry!(api, instance_enumerate_memory_scopes)(
628                self.0.raw,
629                0,
630                ptr::null_mut(),
631                &mut count,
632            )
633        };
634        if status != AMDF_STATUS_CODE_BUFFER_TOO_SMALL as u64 {
635            check("enumerate memory scopes", status)?;
636        }
637        let mut scopes = vec![ptr::null_mut(); count as usize];
638        check("enumerate memory scopes", unsafe {
639            entry!(api, instance_enumerate_memory_scopes)(
640                self.0.raw,
641                count,
642                scopes.as_mut_ptr(),
643                &mut count,
644            )
645        })?;
646        for scope in scopes {
647            let mut scope_info = amdf_memory_scope_info_t {
648                type_: AMDF_STRUCTURE_TYPE_MEMORY_SCOPE_INFO,
649                structure_size: size_of::<amdf_memory_scope_info_t>() as u32,
650                ..Default::default()
651            };
652            check("memory_scope_query_info", unsafe {
653                entry!(api, memory_scope_query_info)(scope, &mut scope_info)
654            })?;
655            if scope_info.kind != AMDF_MEMORY_SCOPE_KIND_SYSTEM {
656                continue;
657            }
658            for ordinal in 0..scope_info.memory_profile_count {
659                if owners
660                    .profile
661                    .is_some_and(|selected| selected != (scope, ordinal))
662                {
663                    continue;
664                }
665                let mut profile = amdf_memory_profile_t {
666                    type_: AMDF_STRUCTURE_TYPE_MEMORY_PROFILE,
667                    structure_size: size_of::<amdf_memory_profile_t>() as u32,
668                    ..Default::default()
669                };
670                let mut capabilities = vec![
671                    amdf_memory_access_capabilities_t {
672                        type_: AMDF_STRUCTURE_TYPE_MEMORY_ACCESS_CAPABILITIES,
673                        structure_size: size_of::<amdf_memory_access_capabilities_t>() as u32,
674                        ..Default::default()
675                    };
676                    devices.len()
677                ];
678                let status = unsafe {
679                    entry!(api, memory_scope_query_device_profile)(
680                        scope,
681                        ordinal,
682                        accesses.len() as u32,
683                        accesses.as_ptr(),
684                        &mut profile,
685                        capabilities.as_mut_ptr(),
686                    )
687                };
688                if status == AMDF_STATUS_CODE_UNSUPPORTED as u64 {
689                    continue;
690                }
691                check("memory_scope_query_device_profile", status)?;
692                let role = if external.is_some() {
693                    AMDF_MEMORY_PROFILE_ROLE_IMPORT
694                } else if host.is_some() {
695                    AMDF_MEMORY_PROFILE_ROLE_REGISTER
696                } else {
697                    AMDF_MEMORY_PROFILE_ROLE_CREATE
698                };
699                let construction = if external.is_some() {
700                    &profile.import
701                } else if host.is_some() {
702                    &profile.registration
703                } else {
704                    &profile.allocation
705                };
706                if profile.roles & (role | AMDF_MEMORY_PROFILE_ROLE_HOST_MAP) as u64
707                    != (role | AMDF_MEMORY_PROFILE_ROLE_HOST_MAP) as u64
708                    || profile.supported_flags & owners.options.required_flags()
709                        != owners.options.required_flags()
710                    || (owners.options.host_numa_node.is_some()
711                        && construction.placement_kinds
712                            & (1 << AMDF_MEMORY_PLACEMENT_KIND_HOST_NUMA)
713                            == 0)
714                    || construction.maximum_byte_length < bytes as u64
715                {
716                    continue;
717                }
718                let options = amdf_memory_create_info_t {
719                    type_: AMDF_STRUCTURE_TYPE_MEMORY_CREATE_INFO,
720                    structure_size: size_of::<amdf_memory_create_info_t>() as u32,
721                    memory_profile_ordinal: ordinal,
722                    access_count: accesses.len() as u32,
723                    required_flags: owners.options.required_flags(),
724                    placement: owners.options.placement(),
725                    byte_length: bytes as u64,
726                    minimum_alignment: alignment,
727                    accesses: accesses.as_ptr(),
728                    registered_host_pointer: host.unwrap_or(ptr::null_mut()),
729                    registered_host_cacheability: if host.is_some() {
730                        AMDF_HOST_CACHEABILITY_WRITE_BACK
731                    } else {
732                        0
733                    },
734                    ..Default::default()
735                };
736                let mut raw = ptr::null_mut();
737                if let Some(external) = &mut external {
738                    let options = amdf_memory_import_info_t {
739                        type_: AMDF_STRUCTURE_TYPE_MEMORY_IMPORT_INFO,
740                        structure_size: size_of::<amdf_memory_import_info_t>() as u32,
741                        memory_profile_ordinal: ordinal,
742                        access_count: accesses.len() as u32,
743                        required_flags: AMDF_MEMORY_FLAG_HOST_VISIBLE as u64,
744                        minimum_alignment: alignment,
745                        accesses: accesses.as_ptr(),
746                        ..Default::default()
747                    };
748                    check("memory_import", unsafe {
749                        entry!(api, memory_import)(scope, &options, &mut external.value, &mut raw)
750                    })?;
751                } else {
752                    check("memory_create", unsafe {
753                        entry!(api, memory_create)(scope, &options, &mut raw)
754                    })?;
755                }
756                if raw.is_null() {
757                    return Err(missing("created memory"));
758                }
759                let needs_cache = owners.source.as_ref().map_or_else(
760                    || Arc::new(std::sync::atomic::AtomicBool::new(false)),
761                    |source| source.0.needs_cache.clone(),
762                );
763                if !owners.coherent
764                    || devices.is_empty()
765                    || devices
766                        .iter()
767                        .any(|device| device.endpoint().engine() != Engine::Gpu)
768                {
769                    needs_cache.store(true, std::sync::atomic::Ordering::Release);
770                }
771                let mut memory = Memory {
772                    instance: self.0.clone(),
773                    devices: devices.to_vec(),
774                    raw,
775                    mapping: ptr::null_mut(),
776                    pointer: ptr::null_mut(),
777                    bytes,
778                    needs_cache,
779                    source: owners.source.clone(),
780                    reservation: owners.reservation.clone(),
781                    host_storage: owners.host_storage.clone(),
782                    uses: owners
783                        .source
784                        .as_ref()
785                        .map_or_else(|| Arc::new(Mutex::new(0)), |source| source.0.uses.clone()),
786                };
787                let options = amdf_memory_map_info_t {
788                    type_: AMDF_STRUCTURE_TYPE_MEMORY_MAP_INFO,
789                    structure_size: size_of::<amdf_memory_map_info_t>() as u32,
790                    byte_length: bytes as u64,
791                    flags: AMDF_MEMORY_MAP_FLAG_READ | AMDF_MEMORY_MAP_FLAG_WRITE,
792                    ..Default::default()
793                };
794                check("memory_map", unsafe {
795                    entry!(api, memory_map)(raw, &options, &mut memory.mapping)
796                })?;
797                let mut info = amdf_host_mapping_info_t {
798                    type_: AMDF_STRUCTURE_TYPE_HOST_MAPPING_INFO,
799                    structure_size: size_of::<amdf_host_mapping_info_t>() as u32,
800                    ..Default::default()
801                };
802                check("host_mapping_query_info", unsafe {
803                    entry!(api, host_mapping_query_info)(memory.mapping, &mut info)
804                })?;
805                if info.pointer.is_null() || info.byte_length < bytes as u64 {
806                    return Err(missing("complete host mapping"));
807                }
808                memory.pointer = info.pointer.cast();
809                unsafe {
810                    if host.is_none() && owners.source.is_none() {
811                        if let Some(data) = initial_data {
812                            // Fresh private backing has no device-written cache
813                            // lines to acquire. Initialize all bytes before the
814                            // single publication below or exposing a safe Buffer.
815                            ptr::copy_nonoverlapping(data.as_ptr(), memory.pointer, bytes);
816                        } else {
817                            ptr::write_bytes(memory.pointer, 0, bytes);
818                        }
819                    }
820                }
821                let buffer = Buffer(Arc::new(memory));
822                unsafe {
823                    buffer.cache_control(true, 0, bytes)?;
824                }
825                return Ok(buffer);
826            }
827        }
828        Err(Error::Unsupported(
829            "no system memory profile admits all requested consumers".into(),
830        ))
831    }
832}
833
834#[cfg(all(test, feature = "npu"))]
835mod alias_tests {
836    use super::*;
837    #[test]
838    #[ignore = "requires native compiler, gfx1151 and NPU5"]
839    fn imported_aliases_share_host_leases_and_cache_requirements() -> Result<()> {
840        let gpu = Device::open(Engine::Gpu, 0)?;
841        let npu = Device::open(Engine::Xdna, 0)?;
842        let fabric = gpu.fabric();
843        let source = fabric.allocate(4096, std::slice::from_ref(&gpu))?;
844        let alias = fabric.share_owned(&source, &[gpu, npu.clone()])?;
845        assert!(
846            source
847                .0
848                .needs_cache
849                .load(std::sync::atomic::Ordering::Acquire)
850        );
851        let output = fabric.allocate(4096, std::slice::from_ref(&npu))?;
852        let artifact = crate::loom::Compiler::for_target(None, npu.target())?
853            .module(include_str!("../../tests/kernels/copy.xdna.loom"))
854            .compile(&crate::loom::Specialization::new("copy").with_config("copy.packets", "1"))?;
855        let program = unsafe {
856            npu.prepare_xdna(
857                &artifact,
858                1,
859                &[
860                    XdnaBinding {
861                        buffer: &alias,
862                        offset: 0,
863                        length: 4096,
864                    },
865                    XdnaBinding {
866                        buffer: &output,
867                        offset: 0,
868                        length: 4096,
869                    },
870                ],
871            )
872        }?;
873        for value in [0x53, 0x27, 0xb8] {
874            source.write(0, &[value; 4096])?;
875            let done = unsafe { program.dispatch() }?;
876            assert!(matches!(source.write(0, &[0]), Err(Error::Busy(_))));
877            assert!(matches!(alias.read(0, &mut [0]), Err(Error::Busy(_))));
878            done.wait()?;
879            let mut actual = [0; 4096];
880            output.read(0, &mut actual)?;
881            assert_eq!(actual, [value; 4096]);
882        }
883        drop(source);
884        alias.write(0, &[0x4b; 4096])?;
885        unsafe { program.dispatch() }?.wait()?;
886        let mut actual = [0; 4096];
887        output.read(0, &mut actual)?;
888        assert_eq!(actual, [0x4b; 4096]);
889        Ok(())
890    }
891}
892
893/// One host-visible allocation profile and its complete device access set.
894/// Queries allocate no backing; allocation uses this exact profile and flags.
895/// Registration and external imports have separate native contracts and are not
896/// admitted by this allocation-only plan.
897#[derive(Clone)]
898pub struct AllocationProfile {
899    fabric: Fabric,
900    devices: Vec<Device>,
901    scope: *mut amdf_memory_scope_t,
902    ordinal: u32,
903    coherent: bool,
904    limits: amdf_memory_construction_capabilities_t,
905}
906// Native scopes and immutable profile facts live with the retained instance.
907unsafe impl Send for AllocationProfile {}
908unsafe impl Sync for AllocationProfile {}
909impl Fabric {
910    /// Enumerate allocation profiles for a complete, unique set of live devices.
911    /// `coherent_gpu` requires coherent GPU attachments; it does not infer NPU
912    /// coherence or promise system atomic support.
913    pub fn allocation_profiles(
914        &self,
915        devices: &[Device],
916        coherent_gpu: bool,
917    ) -> Result<Vec<AllocationProfile>> {
918        for (i, device) in devices.iter().enumerate() {
919            if !Arc::ptr_eq(&self.0, &device.0.endpoint.0.instance)
920                || devices[..i]
921                    .iter()
922                    .any(|other| Arc::ptr_eq(&other.0, &device.0))
923            {
924                return Err(Error::Message(
925                    "allocation devices must be unique and belong to this fabric".into(),
926                ));
927            }
928        }
929        let api = self.0.api.core;
930        let accesses = profile_accesses(devices, coherent_gpu);
931        let mut count = 0;
932        let status = unsafe {
933            entry!(api, instance_enumerate_memory_scopes)(
934                self.0.raw,
935                0,
936                ptr::null_mut(),
937                &mut count,
938            )
939        };
940        if status != AMDF_STATUS_CODE_BUFFER_TOO_SMALL as u64 {
941            check("enumerate memory scopes", status)?;
942        }
943        let mut scopes = vec![ptr::null_mut(); count as usize];
944        check("enumerate memory scopes", unsafe {
945            entry!(api, instance_enumerate_memory_scopes)(
946                self.0.raw,
947                count,
948                scopes.as_mut_ptr(),
949                &mut count,
950            )
951        })?;
952        scopes.truncate(count as usize);
953        let mut result = Vec::new();
954        for scope in scopes {
955            let mut info = amdf_memory_scope_info_t {
956                type_: AMDF_STRUCTURE_TYPE_MEMORY_SCOPE_INFO,
957                structure_size: size_of::<amdf_memory_scope_info_t>() as u32,
958                ..Default::default()
959            };
960            check("memory_scope_query_info", unsafe {
961                entry!(api, memory_scope_query_info)(scope, &mut info)
962            })?;
963            if info.kind != AMDF_MEMORY_SCOPE_KIND_SYSTEM {
964                continue;
965            }
966            for ordinal in 0..info.memory_profile_count {
967                let mut profile = amdf_memory_profile_t {
968                    type_: AMDF_STRUCTURE_TYPE_MEMORY_PROFILE,
969                    structure_size: size_of::<amdf_memory_profile_t>() as u32,
970                    ..Default::default()
971                };
972                let mut capabilities = vec![
973                    amdf_memory_access_capabilities_t {
974                        type_: AMDF_STRUCTURE_TYPE_MEMORY_ACCESS_CAPABILITIES,
975                        structure_size: size_of::<amdf_memory_access_capabilities_t>() as u32,
976                        ..Default::default()
977                    };
978                    devices.len()
979                ];
980                let status = unsafe {
981                    entry!(api, memory_scope_query_device_profile)(
982                        scope,
983                        ordinal,
984                        accesses.len() as u32,
985                        accesses.as_ptr(),
986                        &mut profile,
987                        capabilities.as_mut_ptr(),
988                    )
989                };
990                if status == AMDF_STATUS_CODE_UNSUPPORTED as u64 {
991                    continue;
992                }
993                check("memory_scope_query_device_profile", status)?;
994                let roles =
995                    (AMDF_MEMORY_PROFILE_ROLE_CREATE | AMDF_MEMORY_PROFILE_ROLE_HOST_MAP) as u64;
996                if profile.roles & roles != roles
997                    || profile.supported_flags & AMDF_MEMORY_FLAG_HOST_VISIBLE as u64 == 0
998                {
999                    continue;
1000                }
1001                result.push(AllocationProfile {
1002                    fabric: self.clone(),
1003                    devices: devices.to_vec(),
1004                    scope,
1005                    ordinal,
1006                    coherent: coherent_gpu,
1007                    limits: profile.allocation,
1008                });
1009            }
1010        }
1011        Ok(result)
1012    }
1013}
1014fn profile_accesses(devices: &[Device], coherent: bool) -> Vec<amdf_memory_device_access_t> {
1015    devices
1016        .iter()
1017        .map(|device| amdf_memory_device_access_t {
1018            device: device.0.raw,
1019            requirements: amdf_memory_access_requirements_t {
1020                access: AMDF_MEMORY_ACCESS_READ | AMDF_MEMORY_ACCESS_WRITE,
1021                flags: AMDF_MEMORY_FLAG_DEVICE_ADDRESS as u64
1022                    | if coherent && device.endpoint().engine() == Engine::Gpu {
1023                        AMDF_MEMORY_FLAG_HOST_COHERENT as u64
1024                    } else {
1025                        0
1026                    },
1027                address_kinds: 1
1028                    << match device.endpoint().engine() {
1029                        Engine::Gpu => AMDF_MEMORY_ADDRESS_GPU,
1030                        Engine::Xdna => AMDF_MEMORY_ADDRESS_XDNA_DMA,
1031                    },
1032                ..Default::default()
1033            },
1034        })
1035        .collect()
1036}
1037impl AllocationProfile {
1038    /// Whether this allocating profile accepts an explicit host NUMA constraint.
1039    /// Node availability and native policy are checked during allocation.
1040    pub fn supports_host_numa(&self) -> bool {
1041        self.limits.placement_kinds & (1 << AMDF_MEMORY_PLACEMENT_KIND_HOST_NUMA) != 0
1042    }
1043    /// Largest logical allocation admitted by this profile.
1044    pub fn maximum_bytes(&self) -> u64 {
1045        self.limits.maximum_byte_length
1046    }
1047    /// Required logical allocation length granularity.
1048    pub fn byte_granularity(&self) -> u64 {
1049        self.limits.byte_length_granularity
1050    }
1051    /// Allocate under precisely the queried construction contract.
1052    pub fn allocate(
1053        &self,
1054        bytes: usize,
1055        budget: Option<&crate::residency::MemoryBudget>,
1056    ) -> Result<Buffer> {
1057        if bytes == 0
1058            || bytes as u64 > self.maximum_bytes()
1059            || !((bytes as u64).is_multiple_of(self.byte_granularity().max(1)))
1060        {
1061            return Err(Error::Message(
1062                "allocation does not satisfy the selected profile's size limits".into(),
1063            ));
1064        }
1065        self.fabric.create_memory(
1066            bytes,
1067            &self.devices,
1068            AMDF_MEMORY_ACCESS_READ | AMDF_MEMORY_ACCESS_WRITE,
1069            self.limits.minimum_alignment,
1070            None,
1071            MemoryOwners {
1072                coherent: self.coherent,
1073                profile: Some((self.scope, self.ordinal)),
1074                source: None,
1075                host_storage: None,
1076                reservation: budget.map(|b| b.reserve(bytes).map(Arc::new)).transpose()?,
1077                ..Default::default()
1078            },
1079            None,
1080        )
1081    }
1082    /// Qualify a directional visibility relation before allocating any backing.
1083    /// Returned facts retain this profile and all devices. They describe queue
1084    /// sites, not cache instructions executed within a shader.
1085    pub fn visibility(
1086        &self,
1087        producer: MemorySite<'_>,
1088        consumer: MemorySite<'_>,
1089    ) -> Result<ProspectiveVisibility> {
1090        let site = |site: MemorySite<'_>| -> Result<amdf_memory_profile_site_t> {
1091            let mut result = amdf_memory_profile_site_t::default();
1092            match site {
1093                MemorySite::Host => {
1094                    result.kind = AMDF_MEMORY_SITE_KIND_HOST;
1095                    result.value.host_access =
1096                        AMDF_MEMORY_MAP_FLAG_READ | AMDF_MEMORY_MAP_FLAG_WRITE;
1097                }
1098                MemorySite::Device(device, family) => {
1099                    let ordinal = self
1100                        .devices
1101                        .iter()
1102                        .position(|d| Arc::ptr_eq(&d.0, &device.0))
1103                        .ok_or_else(|| {
1104                            Error::Message(
1105                                "visibility device is absent from allocation profile".into(),
1106                            )
1107                        })?;
1108                    result.kind = AMDF_MEMORY_SITE_KIND_DEVICE;
1109                    result.value.device = amdf_memory_profile_site_t__bindgen_ty_1__bindgen_ty_1 {
1110                        access_ordinal: ordinal as u32,
1111                        queue_family_ordinal: family,
1112                    };
1113                }
1114            }
1115            Ok(result)
1116        };
1117        let accesses = profile_accesses(&self.devices, self.coherent);
1118        let query = amdf_memory_profile_pair_query_t {
1119            type_: AMDF_STRUCTURE_TYPE_MEMORY_PROFILE_PAIR_QUERY,
1120            structure_size: size_of::<amdf_memory_profile_pair_query_t>() as u32,
1121            memory_profile_ordinal: self.ordinal,
1122            access_count: accesses.len() as u32,
1123            accesses: accesses.as_ptr(),
1124            required_flags: AMDF_MEMORY_FLAG_HOST_VISIBLE as u64,
1125            producer: site(producer)?,
1126            consumer: site(consumer)?,
1127            ..Default::default()
1128        };
1129        let mut info = amdf_memory_pair_info_t {
1130            type_: AMDF_STRUCTURE_TYPE_MEMORY_PAIR_INFO,
1131            structure_size: size_of::<amdf_memory_pair_info_t>() as u32,
1132            ..Default::default()
1133        };
1134        check("memory_scope_query_pair_info", unsafe {
1135            entry!(self.fabric.0.api.core, memory_scope_query_pair_info)(
1136                self.scope, &query, &mut info,
1137            )
1138        })?;
1139        Ok(ProspectiveVisibility {
1140            _profile: self.clone(),
1141            facts: visibility::VisibilityFacts::from_native(info)?,
1142        })
1143    }
1144}
1145/// Prospective facts retaining their allocation profile and complete device set.
1146pub struct ProspectiveVisibility {
1147    _profile: AllocationProfile,
1148    facts: visibility::VisibilityFacts,
1149}
1150impl std::ops::Deref for ProspectiveVisibility {
1151    type Target = visibility::VisibilityFacts;
1152    fn deref(&self) -> &Self::Target {
1153        &self.facts
1154    }
1155}