-
Notifications
You must be signed in to change notification settings - Fork 275
Binary size optimization experiment #569
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,321 @@ | ||
use self::sealed::{LogArgs, LogKVs, LogLevel, LogTarget}; | ||
use crate::{Level, LevelFilter, Metadata, Record}; | ||
use std::cmp::Ordering; | ||
pub use std::convert::identity; | ||
use std::fmt::Arguments; | ||
use std::option::Option; | ||
pub use std::primitive::str; | ||
pub use std::{file, format_args, line, module_path, stringify}; | ||
|
||
#[cfg(feature = "kv_unstable")] | ||
pub type LogKvValue<'a> = dyn crate::kv::value::ToValue + 'a; | ||
|
||
#[cfg(not(feature = "kv_unstable"))] | ||
pub type LogKvValue<'a> = str; | ||
|
||
mod sealed { | ||
use crate::Level; | ||
use std::fmt::Arguments; | ||
|
||
pub trait LogLevel { | ||
fn into_log_level(self) -> Level; | ||
} | ||
|
||
pub trait LogArgs { | ||
fn with(self, f: impl FnOnce(Arguments)); | ||
} | ||
|
||
pub trait LogTarget { | ||
fn with(self, module_path: &'static str, f: impl FnOnce(&str)); | ||
} | ||
|
||
pub trait LogKVs { | ||
fn with(self, f: impl FnOnce(&[(&str, &super::LogKvValue)])); | ||
} | ||
} | ||
|
||
// `LogLevel`. | ||
|
||
impl LogLevel for Level { | ||
#[inline] | ||
fn into_log_level(self) -> Level { | ||
self | ||
} | ||
} | ||
|
||
macro_rules! define_static_levels { | ||
($($ty:ident => $lvl:ident,)*) => { | ||
$( | ||
#[derive(Debug)] | ||
pub struct $ty; | ||
|
||
impl LogLevel for $ty { | ||
#[inline] | ||
fn into_log_level(self) -> Level { | ||
Level::$lvl | ||
} | ||
} | ||
|
||
impl PartialEq<LevelFilter> for $ty { | ||
#[inline] | ||
fn eq(&self, other: &LevelFilter) -> bool { | ||
Level::$lvl.eq(other) | ||
} | ||
} | ||
|
||
impl PartialOrd<LevelFilter> for $ty { | ||
#[inline] | ||
fn partial_cmp(&self, other: &LevelFilter) -> Option<Ordering> { | ||
Level::$lvl.partial_cmp(other) | ||
} | ||
|
||
#[inline] | ||
fn lt(&self, other: &LevelFilter) -> bool { | ||
Level::$lvl.lt(other) | ||
} | ||
|
||
#[inline] | ||
fn le(&self, other: &LevelFilter) -> bool { | ||
Level::$lvl.le(other) | ||
} | ||
|
||
#[inline] | ||
fn gt(&self, other: &LevelFilter) -> bool { | ||
Level::$lvl.gt(other) | ||
} | ||
|
||
#[inline] | ||
fn ge(&self, other: &LevelFilter) -> bool { | ||
Level::$lvl.ge(other) | ||
} | ||
} | ||
)* | ||
}; | ||
} | ||
|
||
define_static_levels![ | ||
StaticLevelError => Error, | ||
StaticLevelWarn => Warn, | ||
StaticLevelInfo => Info, | ||
StaticLevelDebug => Debug, | ||
StaticLevelTrace => Trace, | ||
]; | ||
|
||
// `LogArgs`. | ||
|
||
impl LogArgs for &str { | ||
#[inline] | ||
fn with(self, f: impl FnOnce(Arguments)) { | ||
f(format_args!("{self}")) | ||
} | ||
} | ||
|
||
impl LogArgs for Arguments<'_> { | ||
fn with(self, f: impl FnOnce(Arguments)) { | ||
f(self) | ||
} | ||
} | ||
|
||
// `LogTarget`. | ||
|
||
impl LogTarget for &str { | ||
#[inline] | ||
fn with(self, _module_path: &'static str, f: impl FnOnce(&str)) { | ||
f(self) | ||
} | ||
} | ||
|
||
#[derive(Debug)] | ||
pub struct TargetIsModulePath; | ||
|
||
impl LogTarget for TargetIsModulePath { | ||
#[inline] | ||
fn with(self, module_path: &'static str, f: impl FnOnce(&str)) { | ||
f(module_path) | ||
} | ||
} | ||
|
||
// `LogKVs`. | ||
|
||
impl LogKVs for &[(&str, &LogKvValue<'_>)] { | ||
#[inline] | ||
fn with(self, f: impl FnOnce(&[(&str, &LogKvValue)])) { | ||
f(self) | ||
} | ||
} | ||
|
||
#[derive(Debug)] | ||
pub struct EmptyKVs; | ||
|
||
impl LogKVs for EmptyKVs { | ||
#[inline] | ||
fn with(self, f: impl FnOnce(&[(&str, &LogKvValue)])) { | ||
f(&[]) | ||
} | ||
} | ||
|
||
// Log functions. | ||
|
||
fn log_0( | ||
&(module_path, file): &'static (&'static str, &'static str), | ||
line: u32, | ||
level: Level, | ||
args: Arguments, | ||
target: &str, | ||
kvs: &[(&str, &LogKvValue)], | ||
) { | ||
#[cfg(not(feature = "kv_unstable"))] | ||
if !kvs.is_empty() { | ||
panic!( | ||
"key-value support is experimental and must be enabled using the `kv_unstable` feature" | ||
); | ||
} | ||
|
||
let mut builder = Record::builder(); | ||
|
||
builder | ||
.args(args) | ||
.level(level) | ||
.target(target) | ||
.module_path_static(Some(module_path)) | ||
.file_static(Some(file)) | ||
.line(Some(line)); | ||
|
||
#[cfg(feature = "kv_unstable")] | ||
builder.key_values(&kvs); | ||
|
||
crate::logger().log(&builder.build()); | ||
} | ||
|
||
fn log_1<K>( | ||
module_path_and_file: &'static (&'static str, &'static str), | ||
line: u32, | ||
level: Level, | ||
args: Arguments, | ||
target: &str, | ||
kvs: K, | ||
) where | ||
K: LogKVs, | ||
{ | ||
kvs.with(|kvs| log_0(module_path_and_file, line, level, args, target, kvs)); | ||
} | ||
|
||
fn log_2<T, K>( | ||
module_path_and_file: &'static (&'static str, &'static str), | ||
line: u32, | ||
level: Level, | ||
args: Arguments, | ||
target: T, | ||
kvs: K, | ||
) where | ||
T: LogTarget, | ||
K: LogKVs, | ||
{ | ||
target.with(module_path_and_file.0, |target| { | ||
log_1(module_path_and_file, line, level, args, target, kvs) | ||
}); | ||
} | ||
|
||
pub fn log_3<A, T, K>( | ||
module_path_and_file: &'static (&'static str, &'static str), | ||
line: u32, | ||
level: Level, | ||
args: A, | ||
target: T, | ||
kvs: K, | ||
) where | ||
A: LogArgs, | ||
T: LogTarget, | ||
K: LogKVs, | ||
{ | ||
args.with(|args| log_2(module_path_and_file, line, level, args, target, kvs)) | ||
} | ||
|
||
pub fn log<L, A, T, K>( | ||
module_path_and_file: &'static (&'static str, &'static str), | ||
line: u32, | ||
level: L, | ||
args: A, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you could try passing I still think losing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have added a const function to check whether the format argument is a string literal as a not perfect replacement for As for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I was actually referring to using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not sure I understand. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Implementator of Now that you decided to pass arg as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see your point. Maybe this can be optimized by There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Ideally yes, but that will require the args to be I guess we can ask @m-ou-se for this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A simpler approach could be |
||
target: T, | ||
kvs: K, | ||
) where | ||
L: LogLevel, | ||
A: LogArgs, | ||
T: LogTarget, | ||
K: LogKVs, | ||
{ | ||
log_3( | ||
module_path_and_file, | ||
line, | ||
level.into_log_level(), | ||
args, | ||
target, | ||
kvs, | ||
) | ||
} | ||
|
||
pub fn enabled(level: Level, target: &str) -> bool { | ||
crate::logger().enabled(&Metadata::builder().level(level).target(target).build()) | ||
} | ||
|
||
pub const fn is_literal(s: &str) -> bool { | ||
let s = s.as_bytes(); | ||
let n = s.len(); | ||
let mut i = 0; | ||
|
||
while i < n { | ||
if matches!(s[i], b'{' | b'}') { | ||
return false; | ||
} | ||
|
||
i += 1; | ||
} | ||
|
||
true | ||
} | ||
|
||
pub fn unused() -> usize { | ||
fn for_all_kvs<L, A, T>() -> usize | ||
where | ||
L: LogLevel, | ||
A: LogArgs, | ||
T: LogTarget, | ||
{ | ||
type K0 = &'static [(&'static str, &'static LogKvValue<'static>)]; | ||
type K1 = EmptyKVs; | ||
|
||
log::<L, A, T, K0> as usize ^ log::<L, A, T, K1> as usize | ||
} | ||
|
||
fn for_all_targets<L, A>() -> usize | ||
where | ||
L: LogLevel, | ||
A: LogArgs, | ||
{ | ||
type T0 = &'static str; | ||
type T1 = TargetIsModulePath; | ||
|
||
for_all_kvs::<L, A, T0>() ^ for_all_kvs::<L, A, T1>() | ||
} | ||
|
||
fn for_all_arguments<L>() -> usize | ||
where | ||
L: LogLevel, | ||
{ | ||
type A0 = &'static str; | ||
type A1 = Arguments<'static>; | ||
|
||
for_all_targets::<L, A0>() ^ for_all_targets::<L, A1>() | ||
} | ||
|
||
[ | ||
for_all_arguments::<Level>(), | ||
for_all_arguments::<StaticLevelError>(), | ||
for_all_arguments::<StaticLevelWarn>(), | ||
for_all_arguments::<StaticLevelInfo>(), | ||
for_all_arguments::<StaticLevelDebug>(), | ||
for_all_arguments::<StaticLevelTrace>(), | ||
] | ||
.iter() | ||
.fold(0, |x, y| x ^ y) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why every
Log*
trait has to accept a closure here?Can't they just return the value directly?
Returning the closure would only add more overhead and generate more code.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For
LogArgs
, I can’t return aArguments
value from a&str
value:So I have to use a callback function.
For other ones, since they need to be converted into references, the trait method may need to take a
&self
argument, and I don’t feel like taking references from&str
or a zero sized type, since the will get a&&str
type and&ZST
type. But I am not very insistent on this.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah for
LogArgs
you have to use a callback due to limitation ofArguments<'_>
, although I wonderI don't think there's much issue with this since it will most likely be inlined.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, now I have removed callback functions from
LogTarget
andLogKVs
.