Skip to content

Commit

Permalink
fn rav1d_log: Replace with safe write! calls to a fully safe `Rav…
Browse files Browse the repository at this point in the history
…1dLogger`.

The existing variadic `rav1d_log` calls are replaced with `write!` calls
to an `Option<Rav1dLogger>` field.
`enum Rav1dLogger`'s interface is `fn write_fmt`, which `write!` calls.
These are defined as part of `trait Rav1dLog`, similar to `trait {io,fmt}::Write`.
The difference is that no errors are returned (we don't really need them)
and crucially, they are `&self` methods as opposed to `&mut self` methods,
which keeps things much simpler for the borrow checker.

`enum Rav1dLogger` can be `Dav1d` or `Std{out,err}` for now.
The `Dav1d` variant is necessary to convert to/from the `Dav1dLogger`,
and `Stderr` is the default logger used (and `Stdout` is trivially similar).
There will presumably be other loggers desired,
but it's easier to leave those for later once we know their desired behaviors better.

The `Dav1d` variant is called by implementing `fmt::Write`,
which lets us just implement `fn write_str` and have `fn write_fmt` auto-implemented.
`fn write_str` is implemented correctly but with poor performance by printing byte by byte.
This is to avoid dealing with intermediary null characters
and having to allocate a new null-terminated `CString`.
This is for logging so perf isn't important,
and if better perf is desired, users can switch
to the Rust API, which has no such limitation.

Then we construct a bijection between `{D,R}av1dLogger`
to allow the bidirectional conversions needed in the `DAV1D_API`s.
This is done by storing the `Std{out,err}` variants as special `Dav1dLogger::callback` `fn`s.
During the conversion back, if the callback is one of those special `fn`s,
we can decode it back into the same `Rav1dLogger` it originally was.
With use of the `Dav1dLogger::cookie` field, this can be extended
to other `Rav1dLogger` variants in the future as well.
  • Loading branch information
kkysen committed Dec 10, 2023
1 parent 9740d25 commit 4b9cc3a
Show file tree
Hide file tree
Showing 12 changed files with 190 additions and 139 deletions.
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ name = "seek_stress"
[dependencies]
bitflags = "2.4.0"
cfg-if = "1.0.0"
cstr = "0.2.11"
libc = "0.2"
num_cpus = "1.0"
paste = "1.0.14"
Expand Down
36 changes: 3 additions & 33 deletions include/dav1d/dav1d.rs
Original file line number Diff line number Diff line change
@@ -1,45 +1,15 @@
use crate::include::dav1d::picture::Dav1dPicAllocator;
use crate::include::dav1d::picture::Rav1dPicAllocator;
use crate::src::internal::Rav1dContext;
pub use crate::src::log::Dav1dLogger;
use crate::src::log::Rav1dLogger;
use crate::src::r#ref::Rav1dRef;
use std::ffi::c_char;
use std::ffi::c_int;
use std::ffi::c_uint;
use std::ffi::c_void;

pub type Dav1dContext = Rav1dContext;
pub type Dav1dRef = Rav1dRef;

#[derive(Clone)]
#[repr(C)]
pub struct Dav1dLogger {
pub cookie: *mut c_void,
pub callback:
Option<unsafe extern "C" fn(*mut c_void, *const c_char, ::core::ffi::VaList) -> ()>,
}

#[derive(Clone)]
#[repr(C)]
pub(crate) struct Rav1dLogger {
pub cookie: *mut c_void,
pub callback:
Option<unsafe extern "C" fn(*mut c_void, *const c_char, ::core::ffi::VaList) -> ()>,
}

impl From<Dav1dLogger> for Rav1dLogger {
fn from(value: Dav1dLogger) -> Self {
let Dav1dLogger { cookie, callback } = value;
Self { cookie, callback }
}
}

impl From<Rav1dLogger> for Dav1dLogger {
fn from(value: Rav1dLogger) -> Self {
let Rav1dLogger { cookie, callback } = value;
Self { cookie, callback }
}
}

pub type Dav1dInloopFilterType = c_uint;
pub const DAV1D_INLOOPFILTER_ALL: Dav1dInloopFilterType = 7;
pub const DAV1D_INLOOPFILTER_RESTORATION: Dav1dInloopFilterType = 4;
Expand Down Expand Up @@ -103,7 +73,7 @@ pub(crate) struct Rav1dSettings {
pub all_layers: bool,
pub frame_size_limit: c_uint,
pub allocator: Rav1dPicAllocator,
pub logger: Rav1dLogger,
pub logger: Option<Rav1dLogger>,
pub strict_std_compliance: bool,
pub output_invisible_frames: bool,
pub inloop_filters: Rav1dInloopFilterType,
Expand Down
2 changes: 1 addition & 1 deletion lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ pub mod src {
mod lf_apply;
mod lf_mask;
pub mod lib;
mod log;
pub(crate) mod log;
mod loopfilter;
mod looprestoration;
mod lr_apply;
Expand Down
12 changes: 5 additions & 7 deletions src/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ use crate::src::lf_mask::rav1d_create_lf_mask_intra;
use crate::src::lf_mask::Av1Filter;
use crate::src::lf_mask::Av1Restoration;
use crate::src::lf_mask::Av1RestorationUnit;
use crate::src::log::rav1d_log;
use crate::src::log::Rav1dLog as _;
use crate::src::loopfilter::rav1d_loop_filter_dsp_init;
use crate::src::looprestoration::rav1d_loop_restoration_dsp_init;
use crate::src::mc::rav1d_mc_dsp_init;
Expand Down Expand Up @@ -232,7 +232,6 @@ use libc::ptrdiff_t;
use libc::uintptr_t;
use std::array;
use std::cmp;
use std::ffi::c_char;
use std::ffi::c_int;
use std::ffi::c_uint;
use std::ffi::c_void;
Expand Down Expand Up @@ -5071,11 +5070,10 @@ pub unsafe fn rav1d_submit_frame(c: &mut Rav1dContext) -> Rav1dResult {
dsp.fg = Rav1dFilmGrainDSPContext::new::<BitDepth16>();
}
_ => {
rav1d_log(
c,
b"Compiled without support for %d-bit decoding\n\0" as *const u8
as *const c_char,
8 + 2 * (*f.seq_hdr).hbd,
writeln!(
c.logger,
"Compiled without support for {}-bit decoding",
8 + 2 * (*f.seq_hdr).hbd
);
on_error(f, c, out_delayed);
return Err(ENOPROTOOPT);
Expand Down
4 changes: 2 additions & 2 deletions src/internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ use crate::include::dav1d::data::Rav1dData;
use crate::include::dav1d::dav1d::Rav1dDecodeFrameType;
use crate::include::dav1d::dav1d::Rav1dEventFlags;
use crate::include::dav1d::dav1d::Rav1dInloopFilterType;
use crate::include::dav1d::dav1d::Rav1dLogger;
use crate::include::dav1d::headers::Rav1dContentLightLevel;
use crate::include::dav1d::headers::Rav1dFrameHeader;
use crate::include::dav1d::headers::Rav1dITUTT35;
Expand Down Expand Up @@ -42,6 +41,7 @@ use crate::src::lf_mask::Av1Filter;
use crate::src::lf_mask::Av1FilterLUT;
use crate::src::lf_mask::Av1Restoration;
use crate::src::lf_mask::Av1RestorationUnit;
use crate::src::log::Rav1dLogger;
use crate::src::loopfilter::Rav1dLoopFilterDSPContext;
use crate::src::looprestoration::Rav1dLoopRestorationDSPContext;
use crate::src::mc::Rav1dMCDSPContext;
Expand Down Expand Up @@ -238,7 +238,7 @@ pub struct Rav1dContext {
pub(crate) event_flags: Rav1dEventFlags,
pub(crate) cached_error_props: Rav1dDataProps,
pub(crate) cached_error: Rav1dResult,
pub(crate) logger: Rav1dLogger,
pub(crate) logger: Option<Rav1dLogger>,
pub(crate) picture_pool: *mut Rav1dMemPool,
}

Expand Down
10 changes: 5 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ use crate::src::intra_edge::rav1d_init_mode_tree;
use crate::src::levels::Av1Block;
use crate::src::levels::BL_128X128;
use crate::src::levels::BL_64X64;
use crate::src::log::rav1d_log;
use crate::src::log::Rav1dLog as _;
use crate::src::mem::freep;
use crate::src::mem::rav1d_alloc_aligned;
use crate::src::mem::rav1d_free_aligned;
Expand Down Expand Up @@ -320,9 +320,9 @@ pub(crate) unsafe fn rav1d_open(c_out: &mut *mut Rav1dContext, s: &Rav1dSettings
{
(*c).frame_size_limit = (8192 * 8192) as c_uint;
if s.frame_size_limit != 0 {
rav1d_log(
c,
b"Frame size limit reduced from %u to %u.\n\0" as *const u8 as *const c_char,
writeln!(
(*c).logger,
"Frame size limit reduced from {} to {}.",
s.frame_size_limit,
(*c).frame_size_limit,
);
Expand Down Expand Up @@ -530,7 +530,7 @@ pub(crate) unsafe fn rav1d_parse_sequence_header(
let mut res;
let mut s = Rav1dSettings::default();
s.n_threads = 1 as c_int;
s.logger.callback = None;
s.logger = None;
let mut c: *mut Rav1dContext = 0 as *mut Rav1dContext;
res = rav1d_open(&mut c, &mut s);
if res.is_err() {
Expand Down
167 changes: 136 additions & 31 deletions src/log.rs
Original file line number Diff line number Diff line change
@@ -1,47 +1,152 @@
use crate::include::common::validate::validate_input;
use crate::include::dav1d::dav1d::Rav1dLogger;
use crate::src::internal::Rav1dContext;
use crate::stderr;
use cstr::cstr;
use std::ffi::c_char;
use std::ffi::c_int;
use std::ffi::c_uint;
use std::ffi::c_void;
use std::fmt;
use std::fmt::Write as _;
use std::io::stderr;
use std::io::stdout;
use std::io::Write as _;
use std::ptr;

extern "C" {
fn vfprintf(_: *mut libc::FILE, _: *const c_char, _: ::core::ffi::VaList) -> c_int;
pub type Dav1dLoggerCallback = unsafe extern "C" fn(
// The above `cookie` field.
cookie: *mut c_void,
// A `printf`-style format specifier.
fmt: *const c_char,
// `printf`-style variadic args.
args: ...
);

#[derive(Clone)]
#[repr(C)]
pub struct Dav1dLogger {
/// A cookie that's passed as the first argument to the callback below.
cookie: *mut c_void,
/// A `printf`-style function except for an extra first argument that will always be the above `cookie`.
callback: Option<Dav1dLoggerCallback>,
}

#[cold]
pub unsafe extern "C" fn rav1d_log_default_callback(
_cookie: *mut c_void,
format: *const c_char,
mut ap: ::core::ffi::VaList,
) {
vfprintf(stderr, format, ap.as_va_list());
impl Dav1dLogger {
/// # Safety
///
/// `callback`, if non-[`None`]/`NULL` must be safe to call when:
/// * the first argument is `cookie`
/// * the rest of the arguments would be safe to call `printf` with
pub const unsafe fn new(cookie: *mut c_void, callback: Option<Dav1dLoggerCallback>) -> Self {
Self { cookie, callback }
}
}

impl Default for Rav1dLogger {
impl Default for Dav1dLogger {
fn default() -> Self {
Self {
cookie: ptr::null_mut(),
callback: Some(rav1d_log_default_callback),
// Safety: `callback` is [`None`].
unsafe { Self::new(ptr::null_mut(), None) }
}
}

/// A [`Dav1dLogger`] from C that's not
/// just a [`Rav1dLogger`] converted to a [`Dav1dLogger`].
#[derive(Clone)]
pub(crate) struct OnlyDav1dLogger {
cookie: *mut c_void,
callback: Dav1dLoggerCallback,
}

impl fmt::Write for OnlyDav1dLogger {
fn write_str(&mut self, s: &str) -> fmt::Result {
// `s` doesn't have a terminating nul-byte,
// and it may have internal nul-bytes,
// so it's easiest just to print one byte at a time.
// This may be slow, but logging can be disabled if it's slow,
// or the Rust API can be used instead.
let fmt = cstr!("%c");
for &byte in s.as_bytes() {
// # Safety
//
// The first argument is `self.cookie`
// and the rest are safe to call `printf` with,
// as required by [`Self::new`].
unsafe {
(self.callback)(self.cookie, fmt.as_ptr(), byte as c_uint);
}
}
Ok(())
}
}

#[cold]
pub unsafe extern "C" fn rav1d_log(c: *mut Rav1dContext, format: *const c_char, args: ...) {
if validate_input!(!c.is_null()).is_err() {
return;
#[derive(Clone, Default)]
pub(crate) enum Rav1dLogger {
Dav1d(OnlyDav1dLogger),
Stdout,
#[default]
Stderr,
}

/// Any type implementing [`Rav1dLog`] can be used with [`write!`].
pub trait Rav1dLog {
fn write_fmt(&self, args: fmt::Arguments);
}

impl Rav1dLog for Rav1dLogger {
/// Logging doesn't have to be fast when it's on,
/// but we don't want it slow things down when it's off,
/// so ensure the logging code isn't inlined everywhere, bloating call sites.
#[inline(never)]
fn write_fmt(&self, args: fmt::Arguments) {
match self {
// The `dav1d.clone()` is because [`fmt::Write::write_fmt`] takes `&mut`
// even though we don't need it to.
// [`OnlyDav1dLogger`] is trivial to [`Clone`], though, so we can just do that.
Self::Dav1d(dav1d) => dav1d.clone().write_fmt(args).unwrap(),
Self::Stdout => stdout().write_fmt(args).unwrap(),
Self::Stderr => stderr().write_fmt(args).unwrap(),
}
}
if ((*c).logger.callback).is_none() {
return;
}

impl Rav1dLog for Option<Rav1dLogger> {
/// When a logger isn't set, we don't want to have to run any code here,
/// so force this to be inlined so a [`None`] can be seen.
#[inline(always)]
fn write_fmt(&self, args: fmt::Arguments) {
if let Some(logger) = self {
logger.write_fmt(args);
}
}
}

/// Used as a marker for [`Rav1dLogger::Stdout`]. Still a valid (i.e. safe) [`Dav1dLoggerCallback`], though.
unsafe extern "C" fn rav1d_logger_stdout(_cookie: *mut c_void, _fmt: *const c_char, ...) {}

/// Used as a marker for [`Rav1dLogger::Stderr`]. Still a valid (i.e. safe) [`Dav1dLoggerCallback`], though.
unsafe extern "C" fn rav1d_logger_stderr(_cookie: *mut c_void, _fmt: *const c_char, ...) {}

impl From<Dav1dLogger> for Option<Rav1dLogger> {
fn from(logger: Dav1dLogger) -> Self {
let Dav1dLogger { cookie, callback } = logger;
let callback = callback?;
Some(if callback == rav1d_logger_stdout {
Rav1dLogger::Stdout
} else if callback == rav1d_logger_stderr {
Rav1dLogger::Stderr
} else {
Rav1dLogger::Dav1d(OnlyDav1dLogger { cookie, callback })
})
}
}

impl From<Option<Rav1dLogger>> for Dav1dLogger {
fn from(logger: Option<Rav1dLogger>) -> Self {
let cookie = match &logger {
Some(Rav1dLogger::Dav1d(dav1d)) => dav1d.cookie,
_ => ptr::null_mut(),
};
let callback = logger.map(|logger| match logger {
Rav1dLogger::Dav1d(dav1d) => dav1d.callback,
Rav1dLogger::Stdout => rav1d_logger_stdout,
Rav1dLogger::Stderr => rav1d_logger_stderr,
});
Self { cookie, callback }
}
let mut ap: ::core::ffi::VaListImpl;
ap = args.clone();
((*c).logger.callback).expect("non-null function pointer")(
(*c).logger.cookie,
format,
ap.as_va_list(),
);
}
Loading

0 comments on commit 4b9cc3a

Please sign in to comment.