-
Notifications
You must be signed in to change notification settings - Fork 2k
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
sys/vfs: use atomic_utils rather C11 atomics #20513
Conversation
This has the following advantages: - faster and leaner when C11 atomics are not efficient (e.g. on LLVM this is almost always the case, as LLVM will only use efficient atomics if it doesn't has to bail out to library calls even for exotic things) - Even for GCC e.g. on the nucleo-f429zi this safes 72 B of .text for examples/filesystem despite runtime checks added for over- and underflow - less pain in the ass for C++ and rust users, as both C++ and c2rust are incompatible with C11 atomics - adds test for overflow of the open file counter for more robust operation - adds `assumes()` so that underflows are detected in non-production code
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.
On a high level, looks good to me. I'm uncertain whether assume is warranted -- AIU the difference between assert and assume is that with assume it's UB to be false, thus helping the compiler optimize away any dead branch -- but we don't have any code use the before
, so it could just as well be asserts all over. (No harm done, at worst it needs a bit more thinking on the part of the reader).
uint16_t before = atomic_fetch_add_u16(&next->open_files, 1); | ||
/* We cannot use assume() here, an overflow could occur in absence of | ||
* any bugs and should also be checked for in production code. We use | ||
* expect() here, which was actually written for unit tests but works | ||
* here as well */ |
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.
uint16_t before = atomic_fetch_add_u16(&next->open_files, 1); | |
/* We cannot use assume() here, an overflow could occur in absence of | |
* any bugs and should also be checked for in production code. We use | |
* expect() here, which was actually written for unit tests but works | |
* here as well */ | |
uint16_t before = atomic_fetch_add_u16(&next->open_files, 1); | |
/* We cannot use assume() here, an overflow could occur when the only | |
* bug present is a memory leak, and should also be checked for in | |
* production code. We use expect() here, which was actually written for | |
* unit tests but works here as well */ |
Changes after the first lines are just impact of (guessed) reflowing.
Yes, technically there can be an application where open files exceed 2^16 and still not leak memory, but the file handles alone would be 256KiB at the very least, so it's safe to assume that there is a leak.
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 think we should be careful about ruling out use cases that appear crazy ;)
Technically, even some 8 bit CPUs can use multiple MiB of RAM. (The ATXmega board's RAM can be extended with PSRAM and does have a way to address 2^24 Bytes or 16 MiB of memory.) And ESPs with some MiBs of PSRAM are quite common, e.g. those with a high megapixel camera module need lots of RAM to be able to take pictures.
So, while the use case of having ten thousands of files open appears pretty crazy, it is technically possible. And everything that is technically possible will eventually some professional lemming.
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.
Fair enough. I'd still be confused reading this as to how this can happen without a bug (such as a memory leak), but no biggie.
Fixes my issue, where I couldn't Ran unit tests for vfs:
Also did some manual testing using
|
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.
- Fundamentals are good (we generally prefer our atomic wrappers for the reasons you outlined).
- There is barely any code design needed, and what is there is OK.
- I trust maikerlab's tests.
- No obvious code convention violations.
- Comments are the suitable documentation for the implementation detail, and describe precisely what may surprise a reader.
Thank you both, let's ship it.
https://doc.rust-lang.org/std/sync/atomic/ Says that rust follows the the C/C++11 Model https://gcc.gnu.org/bugzilla/show_bug.cgi?id=60932 says that stdatomic.h is part of C++20 https://en.cppreference.com/w/cpp/thread says that the missing piece _Atomic() was added with c++20 but most other things are there since c++11 How is a RIOT specific header more compatible (does it relax the requirement (the access is not atomic) when Rust is interacting with it )? |
Yeah, but that is about the memory model, not about the memory layout of a given variable in RAM. The memory layout of atomics in C and C++ are different, which is why you cannot pass an atomic variable across language barriers. In fact, C11 atomics are not even consistant between C compilers. E.g. LLVM will use library calls to implement atomic accesses the moment a single type of operation cannot be implemented without the help of library calls, GCC assumes that it can happily mix between lock-free implementation and library calls on MCUs, forcing the library call implementation to got the IRQ disable route. (IMO the GCC choice is better, as disabling IRQs on MCUs is cheap and the only sensible implementation there anyway.) The atomic utils do not define any new fancy types. They instead provide helper functions to operate on regular variables in an atomic fashion. |
Contribution description
This has the following advantages:
assumes()
so that underflows are detected in non-production codeTesting procedure
No regressions in VFS operations.
Beware: I don't have the time to test this, so this is completely untested.
Issues/PRs references
rust compatibility issues where reported in matrix