Skip to content
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

Merged
merged 1 commit into from
Apr 16, 2024

Conversation

maribu
Copy link
Member

@maribu maribu commented Mar 27, 2024

Contribution description

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

Testing 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

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
@maribu maribu requested review from chrysn and benpicco March 27, 2024 10:29
@maribu maribu requested a review from miri64 as a code owner March 27, 2024 10:29
@github-actions github-actions bot added Area: tests Area: tests and testing framework Area: sys Area: System labels Mar 27, 2024
@benpicco benpicco added the CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR label Mar 27, 2024
Copy link
Member

@chrysn chrysn left a 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).

Comment on lines +980 to +984
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 */
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
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.

Copy link
Member Author

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.

Copy link
Member

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.

@riot-ci
Copy link

riot-ci commented Mar 27, 2024

Murdock results

✔️ PASSED

45c473d sys/vfs: use atomic_utils rather C11 atomics

Success Failures Total Runtime
10031 0 10031 10m:15s

Artifacts

@maikerlab
Copy link

Fixes my issue, where I couldn't USEMODULE += vfs in Rust application. Thanks!
Builds now for native and native64 target as well (I am using Ubuntu 22.04 on x86_64).

Ran unit tests for vfs:

  • cd tests/unittests
  • BOARD=native make tests-vfs term -> OK (40 tests)
  • BOARD=native64 make tests-vfs term -> OK (40 tests)

Also did some manual testing using tests/sys/vfs_default application:

  • genfile -o /nvm0/test: 2048 bytes written
  • vfs r /nvm0/test 2048 0: output matches content generated by genfile

Copy link
Member

@chrysn chrysn left a comment

Choose a reason for hiding this comment

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

  1. Fundamentals are good (we generally prefer our atomic wrappers for the reasons you outlined).
  2. There is barely any code design needed, and what is there is OK.
  3. I trust maikerlab's tests.
  4. No obvious code convention violations.
  5. Comments are the suitable documentation for the implementation detail, and describe precisely what may surprise a reader.

Thank you both, let's ship it.

@benpicco benpicco added this pull request to the merge queue Apr 15, 2024
@maribu maribu removed this pull request from the merge queue due to a manual request Apr 16, 2024
@maribu maribu added this pull request to the merge queue Apr 16, 2024
@kfessel
Copy link
Contributor

kfessel commented Apr 16, 2024

@maribu

https://doc.rust-lang.org/std/sync/atomic/
https://doc.rust-lang.org/nomicon/atomics.html

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 )?

@maribu
Copy link
Member Author

maribu commented Apr 16, 2024

Says that rust follows the the C/C++11 Model

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.

Merged via the queue into RIOT-OS:master with commit 48838f4 Apr 16, 2024
30 checks passed
@maribu maribu deleted the sys/vfs/atomic_utils branch April 23, 2024 08:54
@mguetschow mguetschow added this to the Release 2024.07 milestone Jul 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: sys Area: System Area: tests Area: tests and testing framework CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants