refactor(encode/linux): second fence off — CudaHw::new's pointer walk gets its proof
`linux/mod.rs`'s fifteen sites are the same kind as `video_vulkan.rs`'s, not the ash kind: nine raw pointer dereferences and six libav calls, all inside `CudaHw::new`, which had no `unsafe` block and therefore no proof of the one thing worth proving here — that the pointer chain it walks is live. The marker STAYS (`cu_ctx: *mut c_void` is a `CUcontext` the caller must supply valid). The body is now two blocks, one per phase, because there are two distinct arguments to make. Both turn on the same non-obvious fact: `av_hwdevice_ctx_alloc`/`av_hwframe_ctx_alloc` return null or a ref whose `data` libav has ALREADY initialized, and `AvBuffer::from_raw` rejects null — so the `?` leaves before any of the field stores below it can run. That is what makes the `(*dev_ctx)`/`(*fc)` writes in-bounds stores on live allocations rather than a hope, and it is exactly the reasoning that was missing. The device block also records the ordering constraint that was implicit: `cuda_ctx` must be stored BEFORE `av_hwdevice_ctx_init`, which reads it. Two files now need no exemption: 14 fenced -> 12. Both were removable for the same reason — their sites are pointer dereferences, where a proof carries an argument, unlike the ash backends where it could only restate the call. That is the criterion for which fence to attack next, not file size. Verified on .21: fmt + `clippy --workspace --all-targets -- -D warnings` + the feature-gated `-p pf-encode --features nvenc,vulkan-encode,pyrowave` step, all rc=0 with no allow in either file.
This commit is contained in:
@@ -8,12 +8,6 @@
|
|||||||
//! does *not* accept — we expand it to `rgb0` (one padding byte/pixel, no colour math).
|
//! does *not* accept — we expand it to `rgb0` (one padding byte/pixel, no colour math).
|
||||||
//! The encoder is opened *without* a global header so VPS/SPS/PPS are emitted in-band on
|
//! The encoder is opened *without* a global header so VPS/SPS/PPS are emitted in-band on
|
||||||
//! every IDR — the output is both a playable raw Annex-B stream and self-contained AUs.
|
//! every IDR — the output is both a playable raw Annex-B stream and self-contained AUs.
|
||||||
// UNSAFE-LINT EXEMPTION (rationale + exit criteria: `unsafe_op_in_unsafe_fn` in the workspace
|
|
||||||
// Cargo.toml). This body is raw libav (`ffmpeg-sys-next`) hwcontext calls almost line for line;
|
|
||||||
// narrowing it would add one `unsafe {}` plus one SAFETY comment per call that could only restate
|
|
||||||
// the signature. Clearing this file means DELETING the markers that carry no caller contract, not
|
|
||||||
// wrapping the calls — until then the lint is off HERE and enforced everywhere else.
|
|
||||||
#![allow(unsafe_op_in_unsafe_fn)]
|
|
||||||
// Every `unsafe` block in this file carries a `// SAFETY:` proof; enforce it (unsafe-proof program).
|
// Every `unsafe` block in this file carries a `// SAFETY:` proof; enforce it (unsafe-proof program).
|
||||||
#![deny(clippy::undocumented_unsafe_blocks)]
|
#![deny(clippy::undocumented_unsafe_blocks)]
|
||||||
|
|
||||||
@@ -84,30 +78,49 @@ impl CudaHw {
|
|||||||
unsafe fn new(cu_ctx: *mut std::ffi::c_void, sw_format: Pixel, w: u32, h: u32) -> Result<Self> {
|
unsafe fn new(cu_ctx: *mut std::ffi::c_void, sw_format: Pixel, w: u32, h: u32) -> Result<Self> {
|
||||||
// Each `?`/`bail!` below drops whatever has been built so far — `AvBuffer`'s `Drop` is the
|
// Each `?`/`bail!` below drops whatever has been built so far — `AvBuffer`'s `Drop` is the
|
||||||
// single unref path, so the failure branches carry no cleanup of their own.
|
// single unref path, so the failure branches carry no cleanup of their own.
|
||||||
let device_ref = AvBuffer::from_raw(ffi::av_hwdevice_ctx_alloc(
|
|
||||||
ffi::AVHWDeviceType::AV_HWDEVICE_TYPE_CUDA,
|
|
||||||
))
|
|
||||||
.context("av_hwdevice_ctx_alloc(CUDA) failed")?;
|
|
||||||
let dev_ctx = (*device_ref.as_ptr()).data as *mut ffi::AVHWDeviceContext;
|
|
||||||
let cu = (*dev_ctx).hwctx as *mut AVCUDADeviceContext;
|
|
||||||
(*cu).cuda_ctx = cu_ctx; // share the importer's context
|
|
||||||
let r = ffi::av_hwdevice_ctx_init(device_ref.as_ptr());
|
|
||||||
if r < 0 {
|
|
||||||
bail!("av_hwdevice_ctx_init failed ({r})");
|
|
||||||
}
|
|
||||||
|
|
||||||
let frames_ref = AvBuffer::from_raw(ffi::av_hwframe_ctx_alloc(device_ref.as_ptr()))
|
// SAFETY: `av_hwdevice_ctx_alloc` returns either null — which `AvBuffer::from_raw` rejects,
|
||||||
.context("av_hwframe_ctx_alloc failed")?;
|
// so the `?` returns before anything below runs — or a fresh ref whose `data` libav has
|
||||||
let fc = (*frames_ref.as_ptr()).data as *mut ffi::AVHWFramesContext;
|
// already initialized as an `AVHWDeviceContext`. For a CUDA device that context's `hwctx`
|
||||||
(*fc).format = ffi::AVPixelFormat::AV_PIX_FMT_CUDA;
|
// is an `AVCUDADeviceContext` (our repr(C) mirror of libav's layout), so writing
|
||||||
(*fc).sw_format = pixel_to_av(sw_format);
|
// `cuda_ctx` is an in-bounds field store on a live allocation, and `cu_ctx` is a valid
|
||||||
(*fc).width = w as c_int;
|
// `CUcontext` by this fn's contract. `av_hwdevice_ctx_init` then takes the same live ref;
|
||||||
(*fc).height = h as c_int;
|
// it must see `cuda_ctx` already set, which is why the store precedes it.
|
||||||
(*fc).initial_pool_size = 0; // we supply the device pointers
|
let device_ref = unsafe {
|
||||||
let r = ffi::av_hwframe_ctx_init(frames_ref.as_ptr());
|
let device_ref = AvBuffer::from_raw(ffi::av_hwdevice_ctx_alloc(
|
||||||
if r < 0 {
|
ffi::AVHWDeviceType::AV_HWDEVICE_TYPE_CUDA,
|
||||||
bail!("av_hwframe_ctx_init failed ({r})");
|
))
|
||||||
}
|
.context("av_hwdevice_ctx_alloc(CUDA) failed")?;
|
||||||
|
let dev_ctx = (*device_ref.as_ptr()).data as *mut ffi::AVHWDeviceContext;
|
||||||
|
let cu = (*dev_ctx).hwctx as *mut AVCUDADeviceContext;
|
||||||
|
(*cu).cuda_ctx = cu_ctx; // share the importer's context
|
||||||
|
let r = ffi::av_hwdevice_ctx_init(device_ref.as_ptr());
|
||||||
|
if r < 0 {
|
||||||
|
bail!("av_hwdevice_ctx_init failed ({r})");
|
||||||
|
}
|
||||||
|
device_ref
|
||||||
|
};
|
||||||
|
|
||||||
|
// SAFETY: the same shape one level up — `av_hwframe_ctx_alloc` is handed the live,
|
||||||
|
// now-initialized device ref and returns null (rejected by `from_raw`, so the `?` leaves
|
||||||
|
// before the writes) or a ref whose `data` is a live `AVHWFramesContext`. Every store below
|
||||||
|
// is an in-bounds field write on that allocation, all plain scalars, done before
|
||||||
|
// `av_hwframe_ctx_init` reads them.
|
||||||
|
let frames_ref = unsafe {
|
||||||
|
let frames_ref = AvBuffer::from_raw(ffi::av_hwframe_ctx_alloc(device_ref.as_ptr()))
|
||||||
|
.context("av_hwframe_ctx_alloc failed")?;
|
||||||
|
let fc = (*frames_ref.as_ptr()).data as *mut ffi::AVHWFramesContext;
|
||||||
|
(*fc).format = ffi::AVPixelFormat::AV_PIX_FMT_CUDA;
|
||||||
|
(*fc).sw_format = pixel_to_av(sw_format);
|
||||||
|
(*fc).width = w as c_int;
|
||||||
|
(*fc).height = h as c_int;
|
||||||
|
(*fc).initial_pool_size = 0; // we supply the device pointers
|
||||||
|
let r = ffi::av_hwframe_ctx_init(frames_ref.as_ptr());
|
||||||
|
if r < 0 {
|
||||||
|
bail!("av_hwframe_ctx_init failed ({r})");
|
||||||
|
}
|
||||||
|
frames_ref
|
||||||
|
};
|
||||||
Ok(CudaHw {
|
Ok(CudaHw {
|
||||||
frames_ref,
|
frames_ref,
|
||||||
device_ref,
|
device_ref,
|
||||||
|
|||||||
Reference in New Issue
Block a user