-
Notifications
You must be signed in to change notification settings - Fork 89
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
Add no_std support #226
base: master
Are you sure you want to change the base?
Add no_std support #226
Conversation
f32::ceil isn't available in no_std
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.
Opinion: This affects too much code, too visibly. Infecting and changing all public bounds is just one uncomfortable change that most users won't be positively affected by. The structure of this implementation forces us to do all-or-nothing, however I don't think this ready for merging. I'd like at least a significantly better test coverage of individual (and somewhat awkward) floating point workarounds, and that they not be present in the usual configuratio. Some more detailed concers with specific implementation choices are listed below.
Overall, I would much rather we have crate that addresses those concerns separately. One that can be shared across multiple of image-rs
crates for no_std compat (png
, tiff
would be good starts) and provides Seek
maybe as well. With focussed test coverage, orthogonality of design. There are plenty but none I'm aware of have been too convincing regarding the SemVer / compatibility problems briefly mentioned below.
#[cfg(feature = "std")] | ||
impl<T: Read> JpegRead for T { |
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.
This doesn't work perfectly with features. If you include the crate with no-default-features, then this impl does not exist and consequently, anyone can legally define a type and impls like so:
struct SomeReader;
impl std::io::Read for SomeReader { … }
impl JpegRead for SomeReader { … }
However, then another unrelated dependency may include jpeg-decoder
with feature = std
and suddenly those two impls collide as the first implies this impl block, which conflicts with the second. This isn't necessarily a deal breaker, it's not unsound, but it's rather odd and unpredictable.
*self = remaining; | ||
Ok(()) | ||
} else { | ||
panic!(); |
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.
This isn't very complete yet, right? And that's probably why I'm a bit wary introducing a new trait just for jpeg-decoder
and then have it replicate mostly internals from what's usually in std
.
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.
And now that it existed once, my desire for much more extensive test coverage of all the impls has grown quite a lot.
#[cfg(feature = "std")] | ||
/// An I/O error occurred while decoding the image. | ||
Io(IoError), | ||
|
||
#[cfg(feature = "std")] | ||
/// An internal error occurred while decoding the image. | ||
Internal(Box<dyn StdError + Send + Sync + 'static>), //TODO: not used, can be removed with the next version bump |
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.
Having extensively worked with no_std
-but-std-compatible crates in networking, variants guarded behind cfg
flags are a major pita. This seems like the biggest risk to reject the PR.
If the enum itself is not non_exhaustive
then consumers intending to match on these variants will having to work quite awkwardly around the presence and absence of certain variants/branches. Especially since it depends on the feature of a dependency, which they may not want to publicly expose as a dependency/toggle themselves. This works just good enough if the program is small and jpeg-decoder
is a direct dependency but doesn't scale well. (Again, consider the feature may be activated by an unrelated crate).
This forces downstream crates, if they intend to be compatible with both, to run check
coverage of both with and without the feature combinations. And in the end you get this where it's a jungle of things that may or may not work with each other.
let size = size as u32 * idct_size as u32; | ||
let dim = size / 8 + u32::from(size % 8 != 0); | ||
dim as u16 | ||
} |
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 this change, you can probably PR this on its own. I like it better than the floating point code in any case as there aren't any subtle accuracy concerns.
@@ -113,6 +112,12 @@ pub struct IccChunk { | |||
pub data: Vec<u8>, | |||
} | |||
|
|||
fn calc_output_size(size: u16, idct_size: usize) -> u16 { | |||
let size = size as u32 * idct_size as u32; |
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.
Can we make sure the types are such that u32::from
can be used in both cases, to statically ensure this can't overflow?
@@ -164,6 +164,13 @@ impl Upsample for UpsamplerH2V1 { | |||
} | |||
} | |||
|
|||
// no_std version to calculate fractional part of positive numbers | |||
fn fract(v: f32) -> f32 { | |||
debug_assert!(v >= 0.0 && v <= (1 << core::f32::MANTISSA_DIGITS - 1) as f32); |
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.
If we are doing this for positive numbers only, then why isn't it using f32::floor()
? How does the conversion to usize
work but not the fract()
method? I'm just super confused. This needs better justification/eval of alternatives before affecting std
code as well.
@@ -176,7 +183,7 @@ impl Upsample for UpsamplerH1V2 { | |||
let row_near = row as f32 / 2.0; | |||
// If row_near's fractional is 0.0 we want row_far to be the previous row and if it's 0.5 we | |||
// want it to be the next row. | |||
let row_far = (row_near + row_near.fract() * 3.0 - 0.25).min((input_height - 1) as f32); | |||
let row_far = (row_near + fract(row_near) * 3.0 - 0.25).min((input_height - 1) as f32); |
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.
Can we not just use integer fixed-point math here?
#[cfg(feature = "std")] | ||
mod std_tests { |
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.
I wouldn't mind if the whole module were moved to a separate file here.
This is my attempt to make this crate usable on no_std.
I've splitted this changes into several commits to make it easier to review:
JpegRead
as an replacement for std::io::Read. Onstd
JpegRead
is implemented for everystd::io:::Read
which should allow users to use every type implementingRead
, too.use
relate changes that Move most uses of the std crate to core and alloc #196 didn't coverstd
feature and use it as a marker for std/no_std.This should fix #199