Skip to main content

Buffer

Struct Buffer 

Source
pub struct Buffer { /* private fields */ }
Expand description

An owned device allocation. Use Buffer::binding or Buffer::try_slice to borrow a binding; this API does not expose host pointers. A handle is bound to its device. Any stream on that device may use it; ordering conflicting access is the caller’s, with events.

Implementations§

Source§

impl Buffer

Source

pub fn bytes(&self) -> usize

The actual allocation size, including rounding of empty allocations.

Source

pub fn try_slice(&self, offset: usize, length: usize) -> Result<View<'_>>

Borrow a span, rejecting overflow and allocation overruns.

Examples found in repository?
examples/stream_bench.rs (line 70)
30fn main() -> Result<()> {
31    let mut stream = Stream::open()?;
32    let mut metrics = BTreeMap::new();
33    let samples = std::env::var("HRX_BENCH_SAMPLES")
34        .ok()
35        .and_then(|value| value.parse::<usize>().ok())
36        .filter(|&value| value > 0)
37        .unwrap_or(9);
38    let (ns, _) = measure(&mut stream, samples, 50_000, |stream| {
39        for _ in 0..50_000 {
40            let buffer = stream.scratch(1024 * 1024)?;
41            black_box(&buffer);
42            stream.recycle(buffer)?;
43        }
44        Ok(())
45    })?;
46    metrics.insert("scratch_reuse_ns", ns);
47    let (ns, _) = measure(&mut stream, samples, 2_000, |stream| {
48        for _ in 0..2_000 {
49            black_box(stream.allocate(1024 * 1024)?);
50        }
51        Ok(())
52    })?;
53    metrics.insert("allocate_drop_ns", ns);
54
55    let bytes = 256 * 1024 * 1024;
56    let chunk = vec![0x3cu8; 4 * 1024 * 1024];
57    let weights = stream.allocate(bytes)?;
58    let (_, ns) = measure(&mut stream, samples, 1, |stream| {
59        for offset in (0..bytes).step_by(chunk.len()) {
60            stream.upload(weights.binding().slice(offset, chunk.len())?, &chunk)?;
61        }
62        Ok(())
63    })?;
64    metrics.insert(
65        "queued_upload_gib_s",
66        bytes as f64 / (1024f64.powi(3) * ns * 1e-9),
67    );
68    let mut check = [0u8; 16];
69    stream.read_blocking(
70        weights.try_slice(bytes - check.len(), check.len())?,
71        &mut check,
72    )?;
73    assert_eq!(check, [0x3c; 16]);
74    drop(weights);
75
76    let compiler = hrx::loom::Compiler::for_stream(None, &stream)?;
77    let module = compiler.module(include_str!("../tests/kernels/euler.loom"));
78    let mut spec = hrx::loom::Specialization::new("krea2_euler");
79    spec.set_config("krea2.euler.grid_x", "1");
80    spec.set_config("krea2.euler.grid_y", "1");
81    let artifact = module.compile(&spec)?;
82    // This repository owns the source; use its exact dimensions and scalar layout.
83    let kernel = unsafe { stream.load_artifact(&artifact)? };
84    let sample = stream.allocate(512)?;
85    let velocity = stream.allocate(512)?;
86    let ones: Vec<u8> = (0..256).flat_map(|_| 0x3f80u16.to_le_bytes()).collect();
87    stream.upload_blocking(sample.binding(), &ones)?;
88    stream.upload_blocking(velocity.binding(), &ones)?;
89    let mut constants = Constants::new();
90    match kernel.info().constant_byte_length {
91        8 => constants.push(256u32)?,
92        12 => constants.push(256u64)?,
93        n => return Err(hrx::Error::Message(format!("unexpected constant size {n}"))),
94    }
95    constants.push(0f32)?;
96    let bindings = [sample.binding(), velocity.binding()];
97    let (enqueue, complete) = measure(&mut stream, samples, 2048, |stream| {
98        for _ in 0..2048 {
99            // The source accesses exactly the 256 bf16 elements in each buffer.
100            unsafe {
101                stream.dispatch(&kernel, [1; 3], [256, 1, 1], &constants, &bindings)?;
102            }
103        }
104        Ok(())
105    })?;
106    metrics.insert("dispatch_enqueue_ns", enqueue);
107    metrics.insert("dispatch_complete_ns", complete);
108
109    // Use disjoint allocations in both graphs. Even an idempotent store is a
110    // write: concurrent Euler dispatches cannot share their in/out buffer.
111    let mut graph_buffers = Vec::new();
112    for _ in 0..32 {
113        let sample = stream.allocate(512)?;
114        let velocity = stream.allocate(512)?;
115        stream.upload(sample.binding(), &ones)?;
116        stream.upload(velocity.binding(), &ones)?;
117        graph_buffers.push((sample, velocity));
118    }
119    stream.synchronize()?;
120    let mut builder = stream.graph()?;
121    let mut previous = None;
122    for (sample, velocity) in &graph_buffers {
123        let after = previous.as_slice();
124        previous = Some(unsafe {
125            builder.dispatch(
126                after,
127                &kernel,
128                [1; 3],
129                [256, 1, 1],
130                &constants,
131                &[sample.binding(), velocity.binding()],
132            )?
133        });
134    }
135    let chained = builder.finish()?;
136
137    // The same disjoint workloads, this time allowing concurrent scheduling.
138    let mut builder = stream.graph()?;
139    for (sample, velocity) in &graph_buffers {
140        unsafe {
141            builder.dispatch(
142                &[],
143                &kernel,
144                [1; 3],
145                [256, 1, 1],
146                &constants,
147                &[sample.binding(), velocity.binding()],
148            )?;
149        }
150    }
151    let independent = builder.finish()?;
152    let mut graphs = [chained, independent];
153    let mut enqueue = [Vec::new(), Vec::new()];
154    let mut complete = [Vec::new(), Vec::new()];
155    for round in 0..samples + 3 {
156        for arm in if round % 2 == 0 { [0, 1] } else { [1, 0] } {
157            stream.synchronize()?;
158            let start = Instant::now();
159            for _ in 0..256 {
160                stream.launch(&mut graphs[arm])?;
161            }
162            let submitted = start.elapsed();
163            stream.synchronize()?;
164            let elapsed = start.elapsed();
165            if round >= 3 {
166                enqueue[arm].push(submitted.as_secs_f64() * 1e9 / 256.0);
167                complete[arm].push(elapsed.as_secs_f64() * 1e9 / (256.0 * 32.0));
168            }
169        }
170    }
171    for values in enqueue.iter_mut().chain(complete.iter_mut()) {
172        values.sort_by(f64::total_cmp);
173    }
174    let ns = complete[0][samples / 2];
175    let ns_free = complete[1][samples / 2];
176    metrics.insert("graph_enqueue_ns_per_replay", enqueue[0][samples / 2]);
177    metrics.insert("graph_complete_ns_per_kernel", ns);
178    metrics.insert("graph_independent_ns_per_kernel", ns_free);
179    metrics.insert("graph_scheduling_difference_ns_per_kernel", ns - ns_free);
180    for (sample, _) in &graph_buffers {
181        let mut output = vec![0; 512];
182        stream.read_blocking(sample.binding(), &mut output)?;
183        assert_eq!(output, ones);
184    }
185    let mut output = vec![0; 512];
186    stream.read_blocking(sample.binding(), &mut output)?;
187    assert_eq!(output, ones);
188    println!("{}", serde_json::to_string(&metrics)?);
189    Ok(())
190}
Source

pub fn device_ptr(&self) -> Result<*mut c_void>

The device address backing this buffer.

Needed to hand the allocation to another driver – exporting it as a dma-buf for the NPU, say – rather than copying its contents out.

Examples found in repository?
examples/dmabuf_probe.rs (line 196)
72fn main() -> Result<(), String> {
73    let args: Vec<String> = std::env::args().skip(1).collect();
74    let (xclbin, mib) = match args.as_slice() {
75        [xclbin] => (xclbin.clone(), 4usize),
76        [xclbin, flag, value] if flag == "--mib" => (
77            xclbin.clone(),
78            value.parse().map_err(|_| "--mib expects an integer")?,
79        ),
80        _ => return Err("usage: dmabuf_probe <xclbin> [--mib N]".into()),
81    };
82    let bytes = mib * 1024 * 1024;
83    let hsa = Hsa::open()?;
84
85    unsafe {
86        let init: libloading::Symbol<unsafe extern "C" fn() -> Status> =
87            hsa.symbol(b"hsa_init\0")?;
88        let status = init();
89        if status != HSA_STATUS_SUCCESS {
90            return Err(format!("hsa_init returned {status}"));
91        }
92
93        // --- find a GPU agent -------------------------------------------------
94        let iterate_agents: libloading::Symbol<
95            unsafe extern "C" fn(
96                unsafe extern "C" fn(u64, *mut c_void) -> Status,
97                *mut c_void,
98            ) -> Status,
99        > = hsa.symbol(b"hsa_iterate_agents\0")?;
100        let agent_info: libloading::Symbol<unsafe extern "C" fn(u64, u32, *mut c_void) -> Status> =
101            hsa.symbol(b"hsa_agent_get_info\0")?;
102
103        // The callback needs the symbol; pass it through the user-data pointer.
104        struct AgentScan<'a> {
105            info: &'a dyn Fn(u64, u32, *mut c_void) -> Status,
106            found: Found,
107        }
108        unsafe extern "C" fn on_agent(agent: u64, data: *mut c_void) -> Status {
109            let scan = unsafe { &mut *data.cast::<AgentScan>() };
110            let mut kind: u32 = 0;
111            if (scan.info)(agent, HSA_AGENT_INFO_DEVICE, (&raw mut kind).cast())
112                == HSA_STATUS_SUCCESS
113                && kind == HSA_DEVICE_TYPE_GPU
114                && scan.found.agent == 0
115            {
116                scan.found.agent = agent;
117            }
118            HSA_STATUS_SUCCESS
119        }
120        let info_fn = |agent, key, out| agent_info(agent, key, out);
121        let mut scan = AgentScan {
122            info: &info_fn,
123            found: Found::default(),
124        };
125        iterate_agents(on_agent, (&raw mut scan).cast());
126        if scan.found.agent == 0 {
127            return Err("no GPU agent found".into());
128        }
129        println!("gpu agent: 0x{:x}", scan.found.agent);
130
131        // --- find an allocatable global pool ----------------------------------
132        let iterate_pools: libloading::Symbol<
133            unsafe extern "C" fn(
134                u64,
135                unsafe extern "C" fn(u64, *mut c_void) -> Status,
136                *mut c_void,
137            ) -> Status,
138        > = hsa.symbol(b"hsa_amd_agent_iterate_memory_pools\0")?;
139        let pool_info: libloading::Symbol<unsafe extern "C" fn(u64, u32, *mut c_void) -> Status> =
140            hsa.symbol(b"hsa_amd_memory_pool_get_info\0")?;
141
142        struct PoolScan<'a> {
143            info: &'a dyn Fn(u64, u32, *mut c_void) -> Status,
144            found: Found,
145        }
146        unsafe extern "C" fn on_pool(pool: u64, data: *mut c_void) -> Status {
147            let scan = unsafe { &mut *data.cast::<PoolScan>() };
148            if scan.found.pool != 0 {
149                return HSA_STATUS_SUCCESS;
150            }
151            let mut segment: u32 = u32::MAX;
152            let mut allowed: bool = false;
153            let mut size: usize = 0;
154            (scan.info)(pool, POOL_INFO_SEGMENT, (&raw mut segment).cast());
155            (scan.info)(pool, POOL_INFO_ALLOC_ALLOWED, (&raw mut allowed).cast());
156            (scan.info)(pool, POOL_INFO_SIZE, (&raw mut size).cast());
157            if segment == SEGMENT_GLOBAL && allowed && size > 0 {
158                scan.found.pool = pool;
159                scan.found.pool_bytes = size;
160            }
161            HSA_STATUS_SUCCESS
162        }
163        let pool_fn = |pool, key, out| pool_info(pool, key, out);
164        let mut pools = PoolScan {
165            info: &pool_fn,
166            found: Found::default(),
167        };
168        iterate_pools(scan.found.agent, on_pool, (&raw mut pools).cast());
169        if pools.found.pool == 0 {
170            return Err("no allocatable global memory pool on the GPU agent".into());
171        }
172        println!(
173            "gpu memory pool: 0x{:x} ({} MiB)",
174            pools.found.pool,
175            pools.found.pool_bytes / (1024 * 1024)
176        );
177
178        // --- allocate from it and export a dma-buf ----------------------------
179        let allocate: libloading::Symbol<
180            unsafe extern "C" fn(u64, usize, u32, *mut *mut c_void) -> Status,
181        > = hsa.symbol(b"hsa_amd_memory_pool_allocate\0")?;
182        let mut pointer: *mut c_void = std::ptr::null_mut();
183        let status = allocate(pools.found.pool, bytes, 0, &raw mut pointer);
184        if status != HSA_STATUS_SUCCESS {
185            return Err(format!("hsa_amd_memory_pool_allocate returned {status}"));
186        }
187        println!("pool allocation: {mib} MiB at {pointer:p}");
188
189        // The route that would make a shared buffer a first-class hrx buffer: allocate it
190        // through the GPU runtime, take its device pointer, and export that.
191        match hrx::Stream::open() {
192            Ok(mut stream) => match stream.allocate_shared(bytes) {
193                Ok(buffer) => {
194                    stream.fill(buffer.binding(), 0x11).ok();
195                    stream.synchronize().ok();
196                    match buffer.device_ptr() {
197                        Ok(hrx_pointer) => {
198                            println!("hrx shared allocation: ok, device ptr {hrx_pointer:p}");
199                            let mut fd: i32 = -1;
200                            let mut offset: u64 = 0;
201                            let export: libloading::Symbol<
202                                unsafe extern "C" fn(
203                                    *const c_void,
204                                    usize,
205                                    *mut i32,
206                                    *mut u64,
207                                ) -> Status,
208                            > = hsa.symbol(b"hsa_amd_portable_export_dmabuf\0")?;
209                            let status = export(hrx_pointer, bytes, &raw mut fd, &raw mut offset);
210                            if status != HSA_STATUS_SUCCESS {
211                                println!("  dma-buf export of an hrx buffer: NO (status {status})");
212                            } else {
213                                println!(
214                                    "  dma-buf export of an hrx buffer: YES (fd {fd}, offset {offset})"
215                                );
216                                match dvxrt::Context::new(0, &xclbin) {
217                                    Ok(context) => match context.import_dmabuf(fd, bytes) {
218                                        Ok(bo) => {
219                                            let mut seen = vec![0u8; 4096];
220                                            bo.read(&mut seen).ok();
221                                            let wrong = seen.iter().filter(|&&b| b != 0x11).count();
222                                            println!(
223                                                "  npu reads the hrx buffer: {}",
224                                                if wrong == 0 {
225                                                    "YES -- one buffer, both engines".into()
226                                                } else {
227                                                    format!("NO ({wrong}/4096 differ)")
228                                                }
229                                            );
230                                        }
231                                        Err(error) => println!("  npu import: NO ({error})"),
232                                    },
233                                    Err(error) => println!("  npu context: {error}"),
234                                }
235                            }
236                        }
237                        Err(error) => println!("hrx shared allocation device ptr: NO ({error})"),
238                    }
239                }
240                Err(error) => println!("hrx shared allocation: NO ({error})"),
241            },
242            Err(error) => println!("hrx stream unavailable: {error}"),
243        }
244
245        let export: libloading::Symbol<
246            unsafe extern "C" fn(*const c_void, usize, *mut i32, *mut u64) -> Status,
247        > = hsa.symbol(b"hsa_amd_portable_export_dmabuf\0")?;
248        let mut fd: i32 = -1;
249        let mut offset: u64 = 0;
250        let status = export(pointer, bytes, &raw mut fd, &raw mut offset);
251        if status != HSA_STATUS_SUCCESS {
252            println!("hsa dma-buf export of pool memory: NO (status {status})");
253            return Ok(());
254        }
255        println!("hsa dma-buf export of pool memory: YES (fd {fd}, offset {offset})");
256
257        // --- and hand that descriptor to the NPU ------------------------------
258        match dvxrt::Context::new(0, &xclbin) {
259            Ok(context) => {
260                let _group = context.group_id(ARG_A).map_err(|e| e.to_string())?;
261                match context.import_dmabuf(fd, bytes) {
262                    Ok(imported) => {
263                        println!("npu import of the GPU dma-buf: YES");
264                        // Import alone only proves the descriptor was accepted. Write with
265                        // the GPU and read through the NPU's BO to prove it is one buffer.
266                        let fill: libloading::Symbol<
267                            unsafe extern "C" fn(*mut c_void, u32, usize) -> Status,
268                        > = hsa.symbol(b"hsa_amd_memory_fill\0")?;
269                        let pattern = 0xa5a5_a5a5u32;
270                        let status = fill(pointer, pattern, bytes / 4);
271                        if status != HSA_STATUS_SUCCESS {
272                            println!("  gpu fill of pool memory failed (status {status})");
273                            return Ok(());
274                        }
275                        let mut seen = vec![0u8; 4096];
276                        match imported.read(&mut seen) {
277                            Ok(()) => {
278                                let wrong = seen.iter().filter(|&&b| b != 0xa5).count();
279                                if wrong == 0 {
280                                    println!(
281                                        "  gpu write -> npu read over dma-buf: YES -- zero-copy confirmed"
282                                    );
283                                } else {
284                                    println!(
285                                        "  gpu write -> npu read over dma-buf: NO ({wrong}/4096 bytes differ)"
286                                    );
287                                }
288                            }
289                            Err(error) => {
290                                println!("  read through the imported BO failed: {error}")
291                            }
292                        }
293                    }
294                    Err(error) => println!("npu import of the GPU dma-buf: NO ({error})"),
295                }
296            }
297            Err(error) => println!("npu context: UNAVAILABLE ({error})"),
298        }
299    }
300    Ok(())
301}
Source

pub fn binding(&self) -> View<'_>

Borrow the whole allocation as a device binding.

Examples found in repository?
examples/graph_prepare.rs (line 21)
4fn main() -> hrx::Result<()> {
5    let runtime = Runtime::new()?;
6    let buffer = runtime.allocate(4096, MemoryPlacement::GpuLocal)?;
7    let stream = Stream::open()?;
8    let raw = stream.allocate(4096)?;
9    for count in [128, 512, 1024, 2048] {
10        let begin = Instant::now();
11        let mut graph = runtime.graph();
12        for _ in 0..count {
13            graph.fill(buffer.view(), 0)?;
14        }
15        let graph = graph.prepare()?;
16        let inferred = begin.elapsed();
17        let begin = Instant::now();
18        let mut native = stream.graph()?;
19        let mut last = None;
20        for _ in 0..count {
21            last = Some(native.fill(last.as_slice(), raw.binding(), 0)?);
22        }
23        let _native = native.finish()?;
24        println!(
25            "nodes={count} inferred_us={} explicit_us={}",
26            inferred.as_micros(),
27            begin.elapsed().as_micros()
28        );
29        graph.submit()?.wait()?;
30    }
31    Ok(())
32}
More examples
Hide additional examples
examples/import_probe.rs (line 46)
16fn main() -> Result<()> {
17    let mib: usize = match std::env::args().collect::<Vec<_>>().as_slice() {
18        [_] => 16,
19        [_, flag, value] if flag == "--mib" => value.parse().expect("--mib expects an integer"),
20        _ => {
21            eprintln!("usage: import_probe [--mib N]");
22            std::process::exit(2);
23        }
24    };
25    let bytes = mib * 1024 * 1024;
26    let layout = Layout::from_size_align(bytes, PAGE).expect("page-aligned layout");
27
28    // Owned for the whole scope below; the imported buffer never frees it.
29    let host = unsafe { alloc_zeroed(layout) };
30    assert!(!host.is_null(), "host allocation failed");
31    let result = (|| -> Result<()> {
32        let mut stream = Stream::open()?;
33        let buffer = match unsafe { stream.import_host(host.cast(), bytes) } {
34            Ok(buffer) => buffer,
35            Err(error) => {
36                println!("import: UNSUPPORTED ({error})");
37                return Ok(());
38            }
39        };
40        println!("import: ok ({mib} MiB at {host:p})");
41
42        // Prove the host pages start clear, so a match afterwards can only be the GPU.
43        let before = unsafe { std::slice::from_raw_parts(host, bytes) };
44        assert!(before.iter().all(|&b| b == 0), "host memory was not zeroed");
45
46        stream.fill(buffer.binding(), PATTERN)?;
47        stream.synchronize()?;
48
49        // Read the ORIGINAL host pointer -- no download, no staging buffer.
50        let after = unsafe { std::slice::from_raw_parts(host, bytes) };
51        let wrong = after.iter().filter(|&&b| b != PATTERN).count();
52        if wrong == 0 {
53            println!("gpu fill visible on host pointer: YES (zero-copy confirmed)");
54        } else {
55            println!("gpu fill visible on host pointer: NO ({wrong}/{bytes} bytes unwritten)");
56        }
57
58        // A device-side read of the same buffer must also see host writes.
59        let mirror = stream.allocate(bytes)?;
60        unsafe { std::slice::from_raw_parts_mut(host, bytes) }.fill(0x3c);
61        stream.copy(mirror.binding(), buffer.binding())?;
62        let mut check = [0u8; 64];
63        stream.read_blocking(mirror.binding().slice(0, check.len())?, &mut check)?;
64        let coherent = check.iter().all(|&b| b == 0x3c);
65        println!(
66            "host write visible to device: {}",
67            if coherent { "YES" } else { "NO" }
68        );
69        Ok(())
70    })();
71    unsafe { dealloc(host, layout) };
72    result
73}
examples/gpu_load.rs (line 101)
88fn main() -> Result<()> {
89    let options = match parse() {
90        Ok(options) => options,
91        Err(message) => {
92            eprintln!("gpu_load: {message}");
93            std::process::exit(2);
94        }
95    };
96    let size = options.mib * 1024 * 1024;
97    let mut stream = Stream::open()?;
98    let source = stream.allocate(size)?;
99    let destination = stream.allocate(size)?;
100    // Touch both allocations so the steady-state loop never pays first-use faults.
101    stream.fill(source.binding(), 0x3c)?;
102    stream.fill(destination.binding(), 0)?;
103    stream.synchronize()?;
104
105    let run_batch = |stream: &mut Stream| -> Result<()> {
106        for _ in 0..options.batch {
107            match options.mode {
108                Mode::Copy => stream.copy(destination.binding(), source.binding())?,
109                Mode::Fill => stream.fill(destination.binding(), 0x5a)?,
110            }
111        }
112        stream.synchronize()
113    };
114
115    // Warm up outside the measured window: the first batch pays queue and paging costs.
116    run_batch(&mut stream)?;
117
118    let window = Duration::from_secs_f64(options.seconds);
119    let start = Instant::now();
120    let mut iterations = 0usize;
121    // Per-batch rates, so the harness can see whether contention was steady or bursty.
122    let mut batch_rates = Vec::new();
123    while start.elapsed() < window {
124        let batch_start = Instant::now();
125        run_batch(&mut stream)?;
126        let seconds = batch_start.elapsed().as_secs_f64();
127        iterations += options.batch;
128        let bytes = options.mode.traffic(size) * options.batch;
129        batch_rates.push(bytes as f64 / seconds / 1e9);
130    }
131    let elapsed = start.elapsed().as_secs_f64();
132    let total = options.mode.traffic(size) as f64 * iterations as f64;
133    let mean = total / elapsed / 1e9;
134    batch_rates.sort_by(f64::total_cmp);
135    let median = batch_rates[batch_rates.len() / 2];
136    let low = batch_rates[batch_rates.len() / 20];
137
138    println!(
139        concat!(
140            r#"{{"device":"gpu","mode":"{}","buffer_mib":{},"batch":{},"#,
141            r#""seconds":{:.4},"iterations":{},"gb_s_mean":{:.3},"#,
142            r#""gb_s_median":{:.3},"gb_s_p5":{:.3}}}"#
143        ),
144        options.mode.name(),
145        options.mib,
146        options.batch,
147        elapsed,
148        iterations,
149        mean,
150        median,
151        low
152    );
153    eprintln!(
154        "gpu_load: {} {:.1} GB/s mean over {:.1}s ({} iterations)",
155        options.mode.name(),
156        mean,
157        elapsed,
158        iterations
159    );
160    Ok(())
161}
examples/unified_probe.rs (line 136)
81fn main() -> Result<()> {
82    let args: Vec<String> = std::env::args().skip(1).collect();
83    let (xclbin, mib) = match args.as_slice() {
84        [xclbin] => (xclbin.clone(), 4usize),
85        [xclbin, flag, value] if flag == "--mib" => (
86            xclbin.clone(),
87            value.parse().expect("--mib expects an integer"),
88        ),
89        _ => {
90            eprintln!("usage: unified_probe <xclbin> [--mib N]");
91            std::process::exit(2);
92        }
93    };
94    let bytes = mib * 1024 * 1024;
95    let layout = Layout::from_size_align(bytes, PAGE).expect("page-aligned layout");
96    let host = unsafe { alloc_zeroed(layout) };
97    assert!(!host.is_null(), "host allocation failed");
98
99    let result = (|| -> Result<()> {
100        println!("host allocation: {mib} MiB at {host:p}");
101
102        let context = match unsafe { dvxrt::Context::new(0, &xclbin) } {
103            Ok(context) => context,
104            Err(error) => {
105                println!("npu context: UNAVAILABLE ({error})");
106                return Ok(());
107            }
108        };
109        let group = context.group_id(ARG_A).expect("group id for A");
110
111        // 1. Can amdxdna pin pages we allocated? Reported either way -- if it can, either
112        //    runtime may own the allocation; if it cannot, only the NPU's allocator may.
113        match unsafe { context.import_bo(host.cast(), bytes, dvxrt::BoKind::HostOnly, group) } {
114            Ok(_) => println!("npu userptr import (host-owned pages): YES"),
115            Err(error) => println!("npu userptr import (host-owned pages): NO ({error})"),
116        }
117
118        // 2. The direction that matters: let the NPU's allocator own the pages -- it is the
119        //    more constrained of the two -- and import its host mapping into the GPU runtime.
120        let bo = context
121            .alloc_bo(bytes, dvxrt::BoKind::HostOnly, group)
122            .expect("allocate an NPU BO");
123        let shared = bo.map().expect("map the NPU BO");
124        println!("npu allocation mapped at {shared:p}");
125        assert_eq!(shared as usize % PAGE, 0, "NPU mapping is not page-aligned");
126
127        let mut stream = Stream::open()?;
128        let imported = unsafe { stream.import_host(shared.cast(), bytes) };
129        match &imported {
130            Ok(_) => println!("gpu import of the NPU mapping: YES"),
131            Err(error) => println!("gpu import of the NPU mapping: NO ({error})"),
132        }
133
134        // 3. If the shared mapping took, check that a GPU write lands in the NPU's BO.
135        if let Ok(buffer) = &imported {
136            stream.fill(buffer.binding(), GPU_PATTERN)?;
137            stream.synchronize()?;
138            bo.sync(false, bytes).expect("invalidate for device reads");
139            let mut seen = vec![0u8; 4096];
140            bo.read(&mut seen).expect("read through the NPU BO");
141            let wrong = seen.iter().filter(|&&b| b != GPU_PATTERN).count();
142            println!(
143                "gpu write -> npu read: {}",
144                if wrong == 0 {
145                    "YES".into()
146                } else {
147                    format!("NO ({wrong} bytes differ)")
148                }
149            );
150        }
151
152        // 3b. The dma-buf route: the GPU allocates, HSA exports the allocation as a
153        //     dma-buf, and XRT imports that descriptor. Neither runtime copies, and unlike
154        //     the host-pointer routes above neither driver has to pin the other's pages.
155        // The GPU-side import above HSA-locked these pages, so they are exportable.
156        let locked = unsafe { stream.import_host(host.cast(), bytes) };
157        let export = match &locked {
158            Ok(buffer) => {
159                stream.fill(buffer.binding(), GPU_PATTERN)?;
160                stream.synchronize()?;
161                export_dmabuf(host.cast(), bytes)
162            }
163            Err(error) => Err(format!("host pages are not GPU-resident: {error}")),
164        };
165        match export {
166            Ok((fd, size)) => {
167                println!("hsa dma-buf export of a GPU buffer: YES (fd {fd})");
168                match unsafe { context.import_dmabuf(fd, size) } {
169                    Ok(imported) => {
170                        println!("npu import of the GPU dma-buf: YES");
171                        let mut seen = vec![0u8; 4096];
172                        match imported.read(&mut seen) {
173                            Ok(()) => {
174                                let wrong = seen.iter().filter(|&&b| b != GPU_PATTERN).count();
175                                println!(
176                                    "  gpu write -> npu read over dma-buf: {}",
177                                    if wrong == 0 {
178                                        "YES".into()
179                                    } else {
180                                        format!("NO ({wrong} bytes differ)")
181                                    }
182                                );
183                            }
184                            Err(error) => {
185                                println!("  read through the imported BO failed: {error}")
186                            }
187                        }
188                    }
189                    Err(error) => println!("npu import of the GPU dma-buf: NO ({error})"),
190                }
191            }
192            Err(error) => println!("hsa dma-buf export of a GPU buffer: NO ({error})"),
193        }
194
195        // 4. And the reverse: the NPU's BO writes, the GPU reads.
196        if let Ok(buffer) = &imported {
197            bo.write(&vec![NPU_PATTERN; bytes])
198                .expect("write through the NPU BO");
199            let mirror = stream.allocate(bytes)?;
200            stream.copy(mirror.binding(), buffer.binding())?;
201            let mut check = [0u8; 64];
202            stream.read_blocking(mirror.binding().slice(0, check.len())?, &mut check)?;
203            let back = check.iter().all(|&b| b == NPU_PATTERN);
204            println!("npu write -> gpu read: {}", if back { "YES" } else { "NO" });
205        }
206        Ok(())
207    })();
208    unsafe { dealloc(host, layout) };
209    result
210}
examples/stream_bench.rs (line 60)
30fn main() -> Result<()> {
31    let mut stream = Stream::open()?;
32    let mut metrics = BTreeMap::new();
33    let samples = std::env::var("HRX_BENCH_SAMPLES")
34        .ok()
35        .and_then(|value| value.parse::<usize>().ok())
36        .filter(|&value| value > 0)
37        .unwrap_or(9);
38    let (ns, _) = measure(&mut stream, samples, 50_000, |stream| {
39        for _ in 0..50_000 {
40            let buffer = stream.scratch(1024 * 1024)?;
41            black_box(&buffer);
42            stream.recycle(buffer)?;
43        }
44        Ok(())
45    })?;
46    metrics.insert("scratch_reuse_ns", ns);
47    let (ns, _) = measure(&mut stream, samples, 2_000, |stream| {
48        for _ in 0..2_000 {
49            black_box(stream.allocate(1024 * 1024)?);
50        }
51        Ok(())
52    })?;
53    metrics.insert("allocate_drop_ns", ns);
54
55    let bytes = 256 * 1024 * 1024;
56    let chunk = vec![0x3cu8; 4 * 1024 * 1024];
57    let weights = stream.allocate(bytes)?;
58    let (_, ns) = measure(&mut stream, samples, 1, |stream| {
59        for offset in (0..bytes).step_by(chunk.len()) {
60            stream.upload(weights.binding().slice(offset, chunk.len())?, &chunk)?;
61        }
62        Ok(())
63    })?;
64    metrics.insert(
65        "queued_upload_gib_s",
66        bytes as f64 / (1024f64.powi(3) * ns * 1e-9),
67    );
68    let mut check = [0u8; 16];
69    stream.read_blocking(
70        weights.try_slice(bytes - check.len(), check.len())?,
71        &mut check,
72    )?;
73    assert_eq!(check, [0x3c; 16]);
74    drop(weights);
75
76    let compiler = hrx::loom::Compiler::for_stream(None, &stream)?;
77    let module = compiler.module(include_str!("../tests/kernels/euler.loom"));
78    let mut spec = hrx::loom::Specialization::new("krea2_euler");
79    spec.set_config("krea2.euler.grid_x", "1");
80    spec.set_config("krea2.euler.grid_y", "1");
81    let artifact = module.compile(&spec)?;
82    // This repository owns the source; use its exact dimensions and scalar layout.
83    let kernel = unsafe { stream.load_artifact(&artifact)? };
84    let sample = stream.allocate(512)?;
85    let velocity = stream.allocate(512)?;
86    let ones: Vec<u8> = (0..256).flat_map(|_| 0x3f80u16.to_le_bytes()).collect();
87    stream.upload_blocking(sample.binding(), &ones)?;
88    stream.upload_blocking(velocity.binding(), &ones)?;
89    let mut constants = Constants::new();
90    match kernel.info().constant_byte_length {
91        8 => constants.push(256u32)?,
92        12 => constants.push(256u64)?,
93        n => return Err(hrx::Error::Message(format!("unexpected constant size {n}"))),
94    }
95    constants.push(0f32)?;
96    let bindings = [sample.binding(), velocity.binding()];
97    let (enqueue, complete) = measure(&mut stream, samples, 2048, |stream| {
98        for _ in 0..2048 {
99            // The source accesses exactly the 256 bf16 elements in each buffer.
100            unsafe {
101                stream.dispatch(&kernel, [1; 3], [256, 1, 1], &constants, &bindings)?;
102            }
103        }
104        Ok(())
105    })?;
106    metrics.insert("dispatch_enqueue_ns", enqueue);
107    metrics.insert("dispatch_complete_ns", complete);
108
109    // Use disjoint allocations in both graphs. Even an idempotent store is a
110    // write: concurrent Euler dispatches cannot share their in/out buffer.
111    let mut graph_buffers = Vec::new();
112    for _ in 0..32 {
113        let sample = stream.allocate(512)?;
114        let velocity = stream.allocate(512)?;
115        stream.upload(sample.binding(), &ones)?;
116        stream.upload(velocity.binding(), &ones)?;
117        graph_buffers.push((sample, velocity));
118    }
119    stream.synchronize()?;
120    let mut builder = stream.graph()?;
121    let mut previous = None;
122    for (sample, velocity) in &graph_buffers {
123        let after = previous.as_slice();
124        previous = Some(unsafe {
125            builder.dispatch(
126                after,
127                &kernel,
128                [1; 3],
129                [256, 1, 1],
130                &constants,
131                &[sample.binding(), velocity.binding()],
132            )?
133        });
134    }
135    let chained = builder.finish()?;
136
137    // The same disjoint workloads, this time allowing concurrent scheduling.
138    let mut builder = stream.graph()?;
139    for (sample, velocity) in &graph_buffers {
140        unsafe {
141            builder.dispatch(
142                &[],
143                &kernel,
144                [1; 3],
145                [256, 1, 1],
146                &constants,
147                &[sample.binding(), velocity.binding()],
148            )?;
149        }
150    }
151    let independent = builder.finish()?;
152    let mut graphs = [chained, independent];
153    let mut enqueue = [Vec::new(), Vec::new()];
154    let mut complete = [Vec::new(), Vec::new()];
155    for round in 0..samples + 3 {
156        for arm in if round % 2 == 0 { [0, 1] } else { [1, 0] } {
157            stream.synchronize()?;
158            let start = Instant::now();
159            for _ in 0..256 {
160                stream.launch(&mut graphs[arm])?;
161            }
162            let submitted = start.elapsed();
163            stream.synchronize()?;
164            let elapsed = start.elapsed();
165            if round >= 3 {
166                enqueue[arm].push(submitted.as_secs_f64() * 1e9 / 256.0);
167                complete[arm].push(elapsed.as_secs_f64() * 1e9 / (256.0 * 32.0));
168            }
169        }
170    }
171    for values in enqueue.iter_mut().chain(complete.iter_mut()) {
172        values.sort_by(f64::total_cmp);
173    }
174    let ns = complete[0][samples / 2];
175    let ns_free = complete[1][samples / 2];
176    metrics.insert("graph_enqueue_ns_per_replay", enqueue[0][samples / 2]);
177    metrics.insert("graph_complete_ns_per_kernel", ns);
178    metrics.insert("graph_independent_ns_per_kernel", ns_free);
179    metrics.insert("graph_scheduling_difference_ns_per_kernel", ns - ns_free);
180    for (sample, _) in &graph_buffers {
181        let mut output = vec![0; 512];
182        stream.read_blocking(sample.binding(), &mut output)?;
183        assert_eq!(output, ones);
184    }
185    let mut output = vec![0; 512];
186    stream.read_blocking(sample.binding(), &mut output)?;
187    assert_eq!(output, ones);
188    println!("{}", serde_json::to_string(&metrics)?);
189    Ok(())
190}
examples/dmabuf_probe.rs (line 194)
72fn main() -> Result<(), String> {
73    let args: Vec<String> = std::env::args().skip(1).collect();
74    let (xclbin, mib) = match args.as_slice() {
75        [xclbin] => (xclbin.clone(), 4usize),
76        [xclbin, flag, value] if flag == "--mib" => (
77            xclbin.clone(),
78            value.parse().map_err(|_| "--mib expects an integer")?,
79        ),
80        _ => return Err("usage: dmabuf_probe <xclbin> [--mib N]".into()),
81    };
82    let bytes = mib * 1024 * 1024;
83    let hsa = Hsa::open()?;
84
85    unsafe {
86        let init: libloading::Symbol<unsafe extern "C" fn() -> Status> =
87            hsa.symbol(b"hsa_init\0")?;
88        let status = init();
89        if status != HSA_STATUS_SUCCESS {
90            return Err(format!("hsa_init returned {status}"));
91        }
92
93        // --- find a GPU agent -------------------------------------------------
94        let iterate_agents: libloading::Symbol<
95            unsafe extern "C" fn(
96                unsafe extern "C" fn(u64, *mut c_void) -> Status,
97                *mut c_void,
98            ) -> Status,
99        > = hsa.symbol(b"hsa_iterate_agents\0")?;
100        let agent_info: libloading::Symbol<unsafe extern "C" fn(u64, u32, *mut c_void) -> Status> =
101            hsa.symbol(b"hsa_agent_get_info\0")?;
102
103        // The callback needs the symbol; pass it through the user-data pointer.
104        struct AgentScan<'a> {
105            info: &'a dyn Fn(u64, u32, *mut c_void) -> Status,
106            found: Found,
107        }
108        unsafe extern "C" fn on_agent(agent: u64, data: *mut c_void) -> Status {
109            let scan = unsafe { &mut *data.cast::<AgentScan>() };
110            let mut kind: u32 = 0;
111            if (scan.info)(agent, HSA_AGENT_INFO_DEVICE, (&raw mut kind).cast())
112                == HSA_STATUS_SUCCESS
113                && kind == HSA_DEVICE_TYPE_GPU
114                && scan.found.agent == 0
115            {
116                scan.found.agent = agent;
117            }
118            HSA_STATUS_SUCCESS
119        }
120        let info_fn = |agent, key, out| agent_info(agent, key, out);
121        let mut scan = AgentScan {
122            info: &info_fn,
123            found: Found::default(),
124        };
125        iterate_agents(on_agent, (&raw mut scan).cast());
126        if scan.found.agent == 0 {
127            return Err("no GPU agent found".into());
128        }
129        println!("gpu agent: 0x{:x}", scan.found.agent);
130
131        // --- find an allocatable global pool ----------------------------------
132        let iterate_pools: libloading::Symbol<
133            unsafe extern "C" fn(
134                u64,
135                unsafe extern "C" fn(u64, *mut c_void) -> Status,
136                *mut c_void,
137            ) -> Status,
138        > = hsa.symbol(b"hsa_amd_agent_iterate_memory_pools\0")?;
139        let pool_info: libloading::Symbol<unsafe extern "C" fn(u64, u32, *mut c_void) -> Status> =
140            hsa.symbol(b"hsa_amd_memory_pool_get_info\0")?;
141
142        struct PoolScan<'a> {
143            info: &'a dyn Fn(u64, u32, *mut c_void) -> Status,
144            found: Found,
145        }
146        unsafe extern "C" fn on_pool(pool: u64, data: *mut c_void) -> Status {
147            let scan = unsafe { &mut *data.cast::<PoolScan>() };
148            if scan.found.pool != 0 {
149                return HSA_STATUS_SUCCESS;
150            }
151            let mut segment: u32 = u32::MAX;
152            let mut allowed: bool = false;
153            let mut size: usize = 0;
154            (scan.info)(pool, POOL_INFO_SEGMENT, (&raw mut segment).cast());
155            (scan.info)(pool, POOL_INFO_ALLOC_ALLOWED, (&raw mut allowed).cast());
156            (scan.info)(pool, POOL_INFO_SIZE, (&raw mut size).cast());
157            if segment == SEGMENT_GLOBAL && allowed && size > 0 {
158                scan.found.pool = pool;
159                scan.found.pool_bytes = size;
160            }
161            HSA_STATUS_SUCCESS
162        }
163        let pool_fn = |pool, key, out| pool_info(pool, key, out);
164        let mut pools = PoolScan {
165            info: &pool_fn,
166            found: Found::default(),
167        };
168        iterate_pools(scan.found.agent, on_pool, (&raw mut pools).cast());
169        if pools.found.pool == 0 {
170            return Err("no allocatable global memory pool on the GPU agent".into());
171        }
172        println!(
173            "gpu memory pool: 0x{:x} ({} MiB)",
174            pools.found.pool,
175            pools.found.pool_bytes / (1024 * 1024)
176        );
177
178        // --- allocate from it and export a dma-buf ----------------------------
179        let allocate: libloading::Symbol<
180            unsafe extern "C" fn(u64, usize, u32, *mut *mut c_void) -> Status,
181        > = hsa.symbol(b"hsa_amd_memory_pool_allocate\0")?;
182        let mut pointer: *mut c_void = std::ptr::null_mut();
183        let status = allocate(pools.found.pool, bytes, 0, &raw mut pointer);
184        if status != HSA_STATUS_SUCCESS {
185            return Err(format!("hsa_amd_memory_pool_allocate returned {status}"));
186        }
187        println!("pool allocation: {mib} MiB at {pointer:p}");
188
189        // The route that would make a shared buffer a first-class hrx buffer: allocate it
190        // through the GPU runtime, take its device pointer, and export that.
191        match hrx::Stream::open() {
192            Ok(mut stream) => match stream.allocate_shared(bytes) {
193                Ok(buffer) => {
194                    stream.fill(buffer.binding(), 0x11).ok();
195                    stream.synchronize().ok();
196                    match buffer.device_ptr() {
197                        Ok(hrx_pointer) => {
198                            println!("hrx shared allocation: ok, device ptr {hrx_pointer:p}");
199                            let mut fd: i32 = -1;
200                            let mut offset: u64 = 0;
201                            let export: libloading::Symbol<
202                                unsafe extern "C" fn(
203                                    *const c_void,
204                                    usize,
205                                    *mut i32,
206                                    *mut u64,
207                                ) -> Status,
208                            > = hsa.symbol(b"hsa_amd_portable_export_dmabuf\0")?;
209                            let status = export(hrx_pointer, bytes, &raw mut fd, &raw mut offset);
210                            if status != HSA_STATUS_SUCCESS {
211                                println!("  dma-buf export of an hrx buffer: NO (status {status})");
212                            } else {
213                                println!(
214                                    "  dma-buf export of an hrx buffer: YES (fd {fd}, offset {offset})"
215                                );
216                                match dvxrt::Context::new(0, &xclbin) {
217                                    Ok(context) => match context.import_dmabuf(fd, bytes) {
218                                        Ok(bo) => {
219                                            let mut seen = vec![0u8; 4096];
220                                            bo.read(&mut seen).ok();
221                                            let wrong = seen.iter().filter(|&&b| b != 0x11).count();
222                                            println!(
223                                                "  npu reads the hrx buffer: {}",
224                                                if wrong == 0 {
225                                                    "YES -- one buffer, both engines".into()
226                                                } else {
227                                                    format!("NO ({wrong}/4096 differ)")
228                                                }
229                                            );
230                                        }
231                                        Err(error) => println!("  npu import: NO ({error})"),
232                                    },
233                                    Err(error) => println!("  npu context: {error}"),
234                                }
235                            }
236                        }
237                        Err(error) => println!("hrx shared allocation device ptr: NO ({error})"),
238                    }
239                }
240                Err(error) => println!("hrx shared allocation: NO ({error})"),
241            },
242            Err(error) => println!("hrx stream unavailable: {error}"),
243        }
244
245        let export: libloading::Symbol<
246            unsafe extern "C" fn(*const c_void, usize, *mut i32, *mut u64) -> Status,
247        > = hsa.symbol(b"hsa_amd_portable_export_dmabuf\0")?;
248        let mut fd: i32 = -1;
249        let mut offset: u64 = 0;
250        let status = export(pointer, bytes, &raw mut fd, &raw mut offset);
251        if status != HSA_STATUS_SUCCESS {
252            println!("hsa dma-buf export of pool memory: NO (status {status})");
253            return Ok(());
254        }
255        println!("hsa dma-buf export of pool memory: YES (fd {fd}, offset {offset})");
256
257        // --- and hand that descriptor to the NPU ------------------------------
258        match dvxrt::Context::new(0, &xclbin) {
259            Ok(context) => {
260                let _group = context.group_id(ARG_A).map_err(|e| e.to_string())?;
261                match context.import_dmabuf(fd, bytes) {
262                    Ok(imported) => {
263                        println!("npu import of the GPU dma-buf: YES");
264                        // Import alone only proves the descriptor was accepted. Write with
265                        // the GPU and read through the NPU's BO to prove it is one buffer.
266                        let fill: libloading::Symbol<
267                            unsafe extern "C" fn(*mut c_void, u32, usize) -> Status,
268                        > = hsa.symbol(b"hsa_amd_memory_fill\0")?;
269                        let pattern = 0xa5a5_a5a5u32;
270                        let status = fill(pointer, pattern, bytes / 4);
271                        if status != HSA_STATUS_SUCCESS {
272                            println!("  gpu fill of pool memory failed (status {status})");
273                            return Ok(());
274                        }
275                        let mut seen = vec![0u8; 4096];
276                        match imported.read(&mut seen) {
277                            Ok(()) => {
278                                let wrong = seen.iter().filter(|&&b| b != 0xa5).count();
279                                if wrong == 0 {
280                                    println!(
281                                        "  gpu write -> npu read over dma-buf: YES -- zero-copy confirmed"
282                                    );
283                                } else {
284                                    println!(
285                                        "  gpu write -> npu read over dma-buf: NO ({wrong}/4096 bytes differ)"
286                                    );
287                                }
288                            }
289                            Err(error) => {
290                                println!("  read through the imported BO failed: {error}")
291                            }
292                        }
293                    }
294                    Err(error) => println!("npu import of the GPU dma-buf: NO ({error})"),
295                }
296            }
297            Err(error) => println!("npu context: UNAVAILABLE ({error})"),
298        }
299    }
300    Ok(())
301}
Source

pub fn slice(&self, offset: usize, length: usize) -> View<'_>

Borrow a span within the allocation.

§Panics

Panics if offset arithmetic overflows or the span exceeds the allocation. Use Buffer::try_slice to return an error instead.

Trait Implementations§

Source§

impl Debug for Buffer

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Drop for Buffer

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

fn pin_drop(self: Pin<&mut Self>)

🔬This is a nightly-only experimental API. (pin_ergonomics)
Execute the destructor for this type, but different to Drop::drop, it requires self to be pinned. Read more
Source§

impl Send for Buffer

Source§

impl Sync for Buffer

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> DropFlavorWrapper<T> for T

Source§

type Flavor = MayDrop

The DropFlavor that wraps T into Self
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, W> HasTypeWitness<W> for T
where W: MakeTypeWitness<Arg = T>, T: ?Sized,

Source§

const WITNESS: W = W::MAKE

A constant of the type witness
Source§

impl<T> Identity for T
where T: ?Sized,

Source§

const TYPE_EQ: TypeEq<T, <T as Identity>::Type> = TypeEq::NEW

Proof that Self is the same type as Self::Type, provides methods for casting between Self and Self::Type.
Source§

type Type = T

The same type as Self, used to emulate type equality bounds (T == U) with associated type equality constraints (T: Identity<Type = U>).
Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<E> ResultError for E
where E: Send + Debug + Sync,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more