Skip to content

Conversation

benpicco
Copy link
Contributor

@benpicco benpicco commented Mar 1, 2023

Contribution description

The compiler is pretty smart about optimizing code when it knows that a condition can not happen.
When DEVELHELP is enabled it knows that the condition inside the assert() must be always true and can optimize the code accordingly, as otherwise a __NORETURN _assert_failure() function is called.

With DEVELHELP disabled the compiler no longer has this information because the assert(cond) is replaced by a (void)cond by the preprocessor.

Replace it by a if (!cond) { __builtin_unreachable(); } instead.

Testing procedure

Code size decrease shows the compiler can optimize the code with DEVELHELP=0:

master

   text	   data	    bss	    dec	    hex	filename
  96116	    228	  18300	 114644	  1bfd4	examples/gnrc_networking/bin/samr21-xpro/gnrc_networking.elf

this PR

   text	   data	    bss	    dec	    hex	filename
  95880	    228	  18300	 114408	  1bee8	examples/gnrc_networking/bin/samr21-xpro/gnrc_networking.elf

see also https://godbolt.org/z/KaajqWv5Y
and https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p1774r4.pdf

Issues/PRs references

@github-actions github-actions bot added Area: core Area: RIOT kernel. Handle PRs marked with this with care! Area: network Area: Networking Area: sys Area: System labels Mar 1, 2023
@benpicco benpicco added CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR CI: no fast fail don't abort PR build after first error and removed Area: network Area: Networking Area: core Area: RIOT kernel. Handle PRs marked with this with care! Area: sys Area: System labels Mar 1, 2023
@benpicco benpicco requested a review from maribu March 1, 2023 13:53
@benpicco benpicco added the Type: enhancement The issue suggests enhanceable parts / The PR enhances parts of the codebase / documentation label Mar 1, 2023
@github-actions github-actions bot added Area: build system Area: Build system Area: core Area: RIOT kernel. Handle PRs marked with this with care! Area: network Area: Networking Area: sys Area: System labels Mar 1, 2023
@riot-ci
Copy link

riot-ci commented Mar 1, 2023

Murdock results

✔️ PASSED

36a34e5 core/assert: mark failed condition unreachable with NDEBUG

Success Failures Total Runtime
6918 0 6919 16m:54s

Artifacts

@benpicco benpicco requested a review from gschorcht as a code owner March 1, 2023 15:20
@github-actions github-actions bot added Area: cpu Area: CPU/MCU ports Platform: ESP Platform: This PR/issue effects ESP-based platforms labels Mar 1, 2023
@benpicco benpicco force-pushed the assert-unreachable branch from 5633326 to 3ce4cec Compare March 1, 2023 15:34
@github-actions github-actions bot added the Area: drivers Area: Device drivers label Mar 1, 2023
@benpicco benpicco force-pushed the assert-unreachable branch from 175c819 to d633d82 Compare March 1, 2023 17:02
@benpicco
Copy link
Contributor Author

benpicco commented Mar 1, 2023

bors try

bors bot added a commit that referenced this pull request Mar 1, 2023
@github-actions github-actions bot added the Process: missing approvals Integration Process: PR needs more ACKS (handled by action) label Mar 2, 2023
@benpicco benpicco removed the Platform: ESP Platform: This PR/issue effects ESP-based platforms label Mar 2, 2023
Copy link
Contributor

@kfessel kfessel left a comment

Choose a reason for hiding this comment

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

Other than these new inlines 👍

since this change core it needs 2nd ack

@benpicco
Copy link
Contributor Author

benpicco commented Mar 2, 2023

Making the functions inline allows to git bisect seamlessly as the the code still compiles without b8e4965.

But I can drop it.

@github-actions github-actions bot added the Platform: ESP Platform: This PR/issue effects ESP-based platforms label Mar 3, 2023
@kfessel
Copy link
Contributor

kfessel commented Mar 3, 2023

maybe @miri64 or @kaspar030 wants to a (second) look

@maribu
Copy link
Member

maribu commented Mar 3, 2023

This is somewhat similar to #16375 (before the drop of assert_ng()). However, in addition to fix all the compilation issues and fixing the

void foo(int bar)
{
#NDEBUG
    (void)bar
#endif
    assert(bar == 42);

boilerplate this also improves the binaries.

I'm in favor of this. It does change the semantics of assert(), but code that does use assert() correctly will not be affect. It will only fix bugs such as

    assert(spi_acquire(...) == SPI_OK);

to still work with NDEBUG correctly. I'd argue that changes in behavior of buggy code are not that much of a concern, as we should just fix the bugs anyway.

We do no longer have a cppcheck pass that would prevent statements with side-effects in assert(). It would be nice to have something else that enforces side-effect free statements in assert(), so that our code would not start relying on our custom assert() semantics.

@maribu
Copy link
Member

maribu commented Mar 6, 2023

If you add a bit of documentation that explains how our assert() would deviate from the C standard and why we do so, I'd give a second ACK.

I think something such as

#define LOG_DEBUG(...) assert(printf(__VA_ARGS__) > 0)

would be actually legitimate, but insane. But I cannot come up with code that is sane and bug-free that would notice the deviation from the C standard here.

@benpicco
Copy link
Contributor Author

benpicco commented Mar 6, 2023

I extended the documentation in assert.h

@benpicco benpicco force-pushed the assert-unreachable branch from 86e58b0 to c9bd3df Compare March 6, 2023 14:14
@benpicco benpicco force-pushed the assert-unreachable branch from c9bd3df to 36a34e5 Compare March 6, 2023 15:08
@github-actions github-actions bot removed the Process: missing approvals Integration Process: PR needs more ACKS (handled by action) label Mar 6, 2023
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.

This version of assert() slightly deviates from the one described in the standard

That's a gross understatement. This change turns code that's well-defined in C (eg. int f(int i) { assert(i != 0); return 2 * i; } / f(0)) to never be undefined behavior into undefined behavior.

I haven't manage to read all comments yet in the short time since I've seen this, but I'd advocate we use a dedicated function (dunno, maybe assume()) instead of redefining something that's well known to C programmers.

@OlegHahm
Copy link
Member

OlegHahm commented Mar 6, 2023

Wouldn't this change also introduce additional if statements for productive code?

@chrysn
Copy link
Member

chrysn commented Mar 6, 2023

It might introduce additional statements; as long as the assert is simple, it typically won't (because the unreachable will just narrow down the legal values), but if the assert includes a call outside the current compilation unit, that call suddenly will be made.

@OlegHahm
Copy link
Member

OlegHahm commented Mar 6, 2023

In my perception assert() must not (under no circumstances) result in any code when NDEBUG is set.

@maribu
Copy link
Member

maribu commented Mar 6, 2023

In my perception assert() must not (under no circumstances) result in any code when NDEBUG is set.

Assumption about generated code and the C language don't mix well together.

The C compiler is allowed to do crazy things like taking the hash value of the source file as seed for a random number generator that is used for statistical analysis (e.g. which code path is taken more often 200 randomly chosen inputs) and optimize for that. Fixing a typo in a comment would then result in possibly better or worse machine code (in terms of code size, performance). Both versions would however be show the same observable behavior and, thus, would be correct.

@OlegHahm
Copy link
Member

OlegHahm commented Mar 6, 2023

In my perception assert() must not (under no circumstances) result in any code when NDEBUG is set.

Assumption about generated code and the C language don't mix well together.

Unless it's part of a specification. The POSIX doc says:
"Forcing a definition of the name NDEBUG, either from the compiler command line or with the preprocessor control statement #define NDEBUG ahead of the #include <assert.h> statement, shall stop assertions from being compiled into the program."

The Linux man page for instance states:
"If the macro NDEBUG is defined at the moment <assert.h> was last included, the macro assert() generates no code"

@kfessel
Copy link
Contributor

kfessel commented Jun 6, 2023

superseded by merged #19354

@kfessel kfessel closed this Jun 6, 2023
@benpicco benpicco deleted the assert-unreachable branch June 6, 2023 15:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: core Area: RIOT kernel. Handle PRs marked with this with care! Area: cpu Area: CPU/MCU ports Area: drivers Area: Device drivers Area: network Area: Networking Area: sys Area: System CI: no fast fail don't abort PR build after first error CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR Platform: ESP Platform: This PR/issue effects ESP-based platforms Process: needs >1 ACK Integration Process: This PR requires more than one ACK Type: enhancement The issue suggests enhanceable parts / The PR enhances parts of the codebase / documentation
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants