Skip to content

rust: CStr overhaul - #273

Merged
ojeda merged 3 commits into
Rust-for-Linux:rustfrom
nbdd0121:cstr_v2
May 26, 2021
Merged

rust: CStr overhaul#273
ojeda merged 3 commits into
Rust-for-Linux:rustfrom
nbdd0121:cstr_v2

Conversation

@nbdd0121

Copy link
Copy Markdown
Member

CStr is overhauled to make using it more similar to use a str.

This is split from #258, without procedural macros and CBoundedStr and does not depend on build_assert!.

Some additional improvements:

  • Added a BStr which is just [u8] but provide an extra semantical annotation;
  • Make CStr deref toBStr so many functions could be used;
  • Add AsRef<BStr> for CStr, combining with deref this allows CStr to be used when &BStr is exepcted;
  • Allow CStr to be sliced like a [u8];
  • Change c_string_bytes to CStr::from_char_ptr

r? @ojeda@wedsonaf

@nbdd0121
nbdd0121force-pushed the cstr_v2 branch 2 times, most recently from 029c83a to e27e37bCompareMay 17, 2021 19:42

@wedsonafwedsonaf left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this way more than the other one. Thanks!

Comment threadrust/kernel/str.rs Outdated

#[inline]
fn index(&self, index: ops::RangeFrom<usize>) -> &Self::Output {
assert!(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to return an empty string (pointer to null terminator) if index is greater than or equal to the length? This way we avoid an additional runtime panic while still returning a value that makes sense.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe in release mode that could be done, but in debug mode I think a failing assertion is the best option. It will avoid a potentially confusing result and if the start of the range is after the last byte of the string, that is very likely a bug.

@bjorn3bjorn3May 18, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By the way the index operation would panic anyway if the range starts 2 or more bytes after the null terminator. This panic only handles the additional case where the range starts exactly one byte after the null terminator. So for example this assertion catches CStr::from_bytes_with_nul(b"\0")[1..], but CStr::from_bytes_with_nul(b"\0")[2..] would panic even without this assertion.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current design is that CStr behaves just like a BStr (with the NUL hidden when dereferencing to BStr).

let c = CStr::from_bytes_with_nul(b"\0");let b = &*c;
b[1];// This will panic
b[1..];// This will panic
c[1];// This will panic
c[1..];// so this also should panic

Bounds check violation is one of the cases where I think panic is preferred to fixing the issue silently (as doing so in C would be UB).

Comment threadrust/kernel/str.rs Outdated
Comment threadrust/kernel/str.rs
Comment threadrust/kernel/str.rs Outdated
Comment threadrust/kernel/str.rs Outdated
Comment threadrust/kernel/str.rs
Comment threadrust/kernel/str.rs
@ksquirrel

This comment has been minimized.

@ksquirrel

This comment has been minimized.

Comment threadrust/kernel/str.rs
return Err(CStrConvertError::InteriorNul);
}
i += 1;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be

if bytes[..bytes.len() - 1].iter().any(|x| x == 0){returnErr(CStrConvertError::InteriorNul);}

which I would ensure that there are no excessive bounds checks for bytes[i].
(The compiler can probably elide these here but I would generally rather not rely on it.)

Alternatively it could also be

for x in&bytes[..bytes.len() - 1]{if x == 0{returnErr(CStrConvertError::InteriorNul);}}

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Slicing could not be used in const fn.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Originally this is written as bytes[..bytes.len() - 1].contains(&0), but following suggestion from @wedsonaf I changed it to a while loop as it currently is (for loops also couldn't be used in const fn).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I missed that this has to be const, sorry.
I guess we'll just have to hope/check that the bounds checks are properly elided. (Or provide separate const/runtime versions but that doesn't seem very nice)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Originally this is written as bytes[..bytes.len() - 1].contains(&0)

Yeah that would have been even better, it's quite unfortunate that const support in rust is still so immature.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked at the assembly and it seems that LLVM couldn't infer that i < len - 1 implies i < len. This boils down to the fact that len - 1 is not UB even when len == 0, so there isn't sufficient info to optimize out the boundary check. It's unfortunate that LLVM couldn't take advantage of the knowledge that len > 0.

However somehow i + 1 < len would allow LLVM to optimize away the bound check. Perhaps it's more common to have comparison against len so LLVM is better at doing it.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is indeed weird. I guess if i + 1 < len works we can just go with that and add a comment why it is written this way.

Alternatively we could of course also use get_unchecked to ensure there are no bounds checks but I don't know if that is actually worth the extra unsafetly here since it is technically possible without it.

@soruhsoruhMay 20, 2021

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also maybe use a NonZeroUsize for the len which I would hope LLVM can optimize properly.
We could probably even make this completely safe without losing perf by replacing the initial is_empty() check with a NonZeroUsize::new(bytes.len()) call.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think get_unchecked is worth it, and it is not const fn either. TBH I don't think this function'll be used in hot path; usually CStr is constructed either statically or from char* with from_char_ptr. So even bounds checks are left, it wouldn't be a big deal. It's too premature to optimize it. I would prefer simplicity to a few unnecessary bounds checks.

NonZeroUsize is usually for memory optimisations. LLVM already knows that length is not 0 after the is_empty check, it just simply couldn't use it to infer i < len, so replacing it with NonZeroUsize wouldn't help either.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that this is probably premature, I guess my usual use cases for CStr in regular Rust code may have made me overestimate the importance of this quite a bit.

@ksquirrel

This comment has been minimized.

@nbdd0121
nbdd0121force-pushed the cstr_v2 branch 2 times, most recently from a8132c3 to e318f62CompareMay 20, 2021 03:17
@ksquirrel

This comment has been minimized.

@ksquirrel

This comment has been minimized.

@ksquirrel

This comment has been minimized.

@nbdd0121

Copy link
Copy Markdown
MemberAuthor

Can we get this PR merged? I don't want to rebase each time another PR get merged 😂. I also had a few other PRs that are based on this one.

@wedsonaf

Copy link
Copy Markdown

Can we get this PR merged? I don't want to rebase each time another PR get merged 😂. I also had a few other PRs that are based on this one.

Taking one last look now.

@ojedaojeda left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple nits.

Comment threadrust/kernel/str.rs Outdated
Comment threadrust/kernel/str.rs Outdated

@wedsonafwedsonaf left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM after Miguel's comments are addressed.

Comment threadrust/kernel/str.rs
pub const fn from_bytes_with_nul_unwrap(bytes: &[u8]) -> &Self {
match Self::from_bytes_with_nul(bytes) {
Ok(v) => v,
Err(_) => panic!("string contains interior NUL"),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this turn into a compile-time error?

@nbdd0121nbdd0121May 26, 2021

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As the comment above the function said, this function is only called by c_str macro, which uses it to initialize a const, so this will only be trigger at compile-time. Once const_panic is stablised we can just do a match at c_str! and remove this function completely.

`CStr` is overhauled to make using it more similar to use a `str`.
Signed-off-by: Gary Guo <gary@garyguo.net>
Suggested-by: Wedson Almeida Filho <wedsonaf@google.com>
Signed-off-by: Gary Guo <gary@garyguo.net>
Signed-off-by: Gary Guo <gary@garyguo.net>
@ksquirrel

Copy link
Copy Markdown
Member

Review of 3a859fde34d5:

@ojeda
ojeda merged commit 7451cda into Rust-for-Linux:rustMay 26, 2021
@nbdd0121
nbdd0121 deleted the cstr_v2 branch May 26, 2021 02:25
@TheSven73

Copy link
Copy Markdown
Collaborator

Hey, this PR breaks an existing file drivers/char/hw_random/bcm2835_rng_rust.rs, that's not nice :(

Should I add that file to the CI defconfig so we can catch these things in the future?

TheSven73 pushed a commit to TheSven73/linux that referenced this pull request May 26, 2021
Fix build error from Rust-for-Linux#273 ("rust: CStr overhaul").
Signed-off-by: Sven Van Asbroeck <thesven73@gmail.com>
TheSven73 pushed a commit to TheSven73/linux that referenced this pull request May 26, 2021
Fix build error from Rust-for-Linux#273 ("rust: CStr overhaul").
Signed-off-by: Sven Van Asbroeck <thesven73@gmail.com>
@ojeda

Copy link
Copy Markdown
Member

Hey, this PR breaks an existing file drivers/char/hw_random/bcm2835_rng_rust.rs, that's not nice :(

Should I add that file to the CI defconfig so we can catch these things in the future?

Definitely!

TheSven73 pushed a commit to TheSven73/linux that referenced this pull request May 26, 2021
Fix build error from Rust-for-Linux#273 ("rust: CStr overhaul").
Signed-off-by: Sven Van Asbroeck <thesven73@gmail.com>
ojeda pushed a commit that referenced this pull request Aug 7, 2023
The function meson_clk_pll_enable() can be invoked under the enable_lock
spinlock from the clk core logic, which risks a kernel panic during the
usleep_range() call:
BUG: scheduling while atomic: kworker/u4:2/36/0x00000002
Modules linked in: g_ffs usb_f_fs libcomposite
CPU: 1 PID: 36 Comm: kworker/u4:2 Not tainted 6.4.0-rc5 #273
Workqueue: events_unbound async_run_entry_fn
Call trace:
dump_backtrace+0x9c/0x128
show_stack+0x20/0x38
dump_stack_lvl+0x48/0x60
dump_stack+0x18/0x28
__schedule_bug+0x58/0x78
__schedule+0x828/0xa88
schedule+0x64/0xd8
schedule_hrtimeout_range_clock+0xd0/0x208
schedule_hrtimeout_range+0x1c/0x30
usleep_range_state+0x6c/0xa8
meson_clk_pll_enable+0x1f4/0x310
clk_core_enable+0x78/0x200
clk_core_enable+0x58/0x200
clk_core_enable+0x58/0x200
clk_core_enable+0x58/0x200
clk_enable+0x34/0x60
So it is required to use the udelay() function instead of usleep_range()
for the atomic context safety.
Fixes: b6ec400 ("clk: meson: introduce new pll power-on sequence for A1 SoC family")
Reported-by: Jan Dakinevich <yvdakinevich@sberdevices.ru>
Signed-off-by: Dmitry Rokosov <ddrokosov@sberdevices.ru>
Link: https://lore.kernel.org/r/20230704215404.11533-1-ddrokosov@sberdevices.ru
Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@nbdd0121@ksquirrel@wedsonaf@TheSven73@ojeda@bjorn3@soruh