Skip to content

build: allow application code to opt out of Zephyr stdint convention#104292

Open
npitre wants to merge 1 commit intozephyrproject-rtos:mainfrom
npitre:stdint
Open

build: allow application code to opt out of Zephyr stdint convention#104292
npitre wants to merge 1 commit intozephyrproject-rtos:mainfrom
npitre:stdint

Conversation

@npitre
Copy link

@npitre npitre commented Feb 20, 2026

Summary

  • Introduce CONFIG_OMIT_ZEPHYR_STDINT_FOR_APPS to let application code use the toolchain's native integer type definitions while keeping Zephyr's own kernel, drivers, and subsystem code under the enforced zephyr_stdint.h convention.
  • The mechanism pre-defines the zephyr_stdint.h include guard for the app build target, using the same approach as CMSIS-DSP (commit 8d95c2c).
  • This is ABI-safe because Zephyr is C-only (no symbol mangling) and the affected types share the same size and calling convention.

Motivation

Zephyr's zephyr_stdint.h remaps compiler-provided type macros (e.g. __INT32_TYPE__ from long int to int) to ensure consistent printf format specifiers across architectures. This is valuable for Zephyr's internal code but creates conflicts for application code that includes compiler-provided headers designed around native type mappings, such as those implementing language extensions and intrinsics.

The conflict manifests through C11 _Generic associations becoming ambiguous when the remapped types collapse distinct type entries into duplicates, or through pointer type mismatches in intrinsic function signatures.

Ref: #46032

Zephyr enforces a particular mapping of C99 fixed-width integer types
through zephyr_stdint.h, force-included via -imacros on all translation
units when CONFIG_ENFORCE_ZEPHYR_STDINT=y (the default). This remaps
the compiler's built-in type macros so that int32_t is always "int"
and int64_t is always "long long", ensuring that printf format
specifiers like %d and %lld work consistently without warnings across
ILP32 and LP64 architectures.

This convention, introduced by commit f32330b ("stdint.h:
streamline type definitions"), remains desirable for Zephyr's own
internals: it provides a coherent type system across all supported
architectures, enables reliable printf format validation at compile
time, and avoids the syntactic burden of PRIxN macros throughout
the kernel and subsystem code.

However, application code has different concerns. Some toolchains
(notably arm-none-eabi-gcc) natively define int32_t as "long int" and
uint32_t as "unsigned long int". Compiler-provided headers such as
those implementing language extensions and intrinsics are designed
around these native type mappings. When Zephyr's override is in
effect, it can create type conflicts with such headers, for example
through C11 _Generic associations that become ambiguous when the
remapped types collapse distinct type entries into duplicates.

This has been observed with CMSIS-DSP which already works around
the issue by locally defining the zephyr_stdint.h include guard
to bypass the override (commit 8d95c2c ("modules/cmsis-dsp:
Don't use Zephyr stdint.h")). That per-library approach does not
scale well for application code that may pull in various compiler-
provided headers.

Introduce CONFIG_OMIT_ZEPHYR_STDINT_FOR_APPS to provide a proper
separation of concerns: Zephyr's own code continues to benefit from
the enforced type convention, while application code is free to use
the toolchain's native type definitions and whatever compiler-provided
headers it needs without conflict.

The mechanism is the same approach used by CMSIS-DSP: pre-defining
the include guard macro (ZEPHYR_INCLUDE_TOOLCHAIN_STDINT_H_) so that
the -imacros header becomes a no-op for the app target.

This is safe at the linker level because Zephyr is C-only code where
symbol names carry no type encoding. The affected types (e.g. "int"
vs "long" on ILP32, or "long" vs "long long" for 64-bit quantities)
are guaranteed to share the same size and ABI calling convention on
all architectures that Zephyr supports, so object code compiled under
either convention links and interoperates correctly.

Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
Copy link
Contributor

@nordicjm nordicjm left a comment

Choose a reason for hiding this comment

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

I don't see any reason for this since out-of-tree applications can just do this in their CMake file, and nothing in the zephyr tree should make use of this

@npitre
Copy link
Author

npitre commented Feb 20, 2026

I beg to differ. The fact that issue #46032 has been open for over 3 years without resolution, and that people are still running into this problem today (most recently ARM's own engineers struggling with ACLE intrinsics), suggests that "just do it in your CMake file" is not an adequate answer.

For an application developer hitting a cryptic _Generic conflict or a pointer type mismatch from a compiler-provided header, the path from build error to "I need to pre-define ZEPHYR_INCLUDE_TOOLCHAIN_STDINT_H_ on the app target" is non-obvious. It requires understanding Zephyr's -imacros injection mechanism, the include guard convention, and the right CMake scope — none of which are documented for end users. And documenting it wouldn't really help: a page explaining Zephyr's internal type override plumbing and how to undo it from CMake is rather arid knowledge that application developers shouldn't have to acquire when a simple Kconfig knob can solve their problem.

The most frequent criticism leveled at Zephyr is its steep learning curve, especially around the build system. New users are already expected to absorb Kconfig, devicetree, west, and CMake simultaneously. Telling them to also reverse-engineer internal build plumbing to work around a type conflict they didn't cause is exactly the kind of thing that drives people away. We should be lowering barriers, not defending them.

A Kconfig option is discoverable through menuconfig, has help text explaining what it does and why it's safe, and can be referenced in documentation and issue trackers. That's the difference between "technically possible" and "practically usable."

Making things easier for application developers is not a reason to reject a feature — it's the point of one.

@nordicjm
Copy link
Contributor

nordicjm commented Feb 23, 2026

I beg to differ. The fact that issue #46032 has been open for over 3 years without resolution, and that people are still running into this problem today (most recently ARM's own engineers struggling with ACLE intrinsics), suggests that "just do it in your CMake file" is not an adequate answer.

Then that is an issue for you toolchain/company, it is not a zephyr problem

A Kconfig option is discoverable through menuconfig, has help text explaining what it does and why it's safe, and can be referenced in documentation and issue trackers. That's the difference between "technically possible" and "practically usable."

Sure, then you can add it through a module you provide for your company which allows adding it with a Kconfig, but that is not a module needed in zephyr. Nothing posted above has changed my mind on that. And there's nothing wrong with having generic options in a custom module, you can do whatever you want downstream in custom modules

Copy link
Contributor

@tejlmand tejlmand left a comment

Choose a reason for hiding this comment

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

Thanks for opening this topic.

I'm not convinced that a Kconfig setting on just the app lib is the right path in this case, though I do understand and acknowledge the problem faced.

I beg to differ. The fact that issue #46032 has been open for over 3 years without resolution, and that people are still running into this problem today (most recently ARM's own engineers struggling with ACLE intrinsics), suggests that "just do it in your CMake file" is not an adequate answer.

Thanks for opening the discussion in #46032 again.
It's definitely an important one.

For an application developer hitting a cryptic Generic conflict or a pointer type mismatch from a compiler-provided header, the path from build error to "I need to pre-define ZEPHYR_INCLUDE_TOOLCHAIN_STDINT_H on the app target" is non-obvious.

Agree, we need something better / cleaner.

And documenting it wouldn't really help: a page explaining Zephyr's internal type override plumbing and how to undo it from CMake is rather arid knowledge that application developers shouldn't have to acquire when a simple Kconfig knob can solve their problem.

Kconfig sure has it's benefits in the help text rendered into documentation and menuconfig.

The problem with Kconfig in this case is that it's localized to app lib, but the problem is more general. Any Zephyr module developed downstream, for example integrating existing (legacy?) code included into Zephyr build through the use of Zephyr modules and using zephyr_library() will suffer this issue if the code relies on the toolchain's native int types.

I'm curious, in your use-case, is it required that the app lib is a Zephyr library ?
Libraries created using add_library() will not have have zephyr_stdint.h applied.
But of course also none of the other things inherited through zephyr_interface, including whole-archive.

Copy link
Contributor

@tejlmand tejlmand left a comment

Choose a reason for hiding this comment

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: Build System area: Kconfig area: Tests Issues related to a particular existing or missing test

4 participants