Skip to content

[types] Take the C library's types from the system's own headers - #1348

Merged
gmh5225 merged 1 commit into
devfrom
claude/libc-system-headers
Oct 18, 2026
Merged

gmh5225 merged 1 commit into
devfrom
claude/libc-system-headers

Conversation

@gmh5225

@gmh5225 gmh5225 commented Oct 18, 2026

Copy link
Copy Markdown
Contributor

Summary

C and C++ headers rendered for a Linux program defined the C library's own types again. They collided with the system headers wherever the compiler had them. Examples: C++'s struct __fsid_t against glibc's typedef of an anonymous record, and a 32-bit __off_t that is long in glibc but int32_t in the portable spelling. On Windows and macOS, the same types collided with what those compilers' own headers declare.

A program joined to glibc's, bionic's or musl's library (HeaderLibrarySystems.def) now gets a header that takes those types from the system's own headers where the compiler has that C library. Everywhere else, Windows and macOS among them, it defines them itself.

Which types are the C library's. Any type whose name the joined library has, including the program's own DWARF definition of it (struct stat).

On the system. A probe ahead of every include defines _GNU_SOURCE, as the libraries were read, and then NEVERD_LIBC_FROM_SYSTEM, when all of these hold:

  • the compiler targets the library's platform and architecture;
  • its C library is the library's (__GLIBC__, __BIONIC__, or neither for musl).

The probe fails clearly when glibc's or bionic's headers were read earlier without _GNU_SOURCE. NEVERD_OWN_LIBC_TYPES asks for the header's own definitions instead.

There, the header includes only the headers its C library types come from:

  • generate_linux_types.py follows the preprocessor's include stack and writes #pragma neverd_header("stdio.h") before each declaration, naming the unit header that brought it in;
  • the C parser keeps that on the type (LocalType::Header);
  • all 12 Linux, Android and musl libraries are regenerated. Normalized, they differ from dev's only by these pragmas, the typedef forms below, NEVERD_GNU_STRUCT and the member macros' guards.

The library's prototypes are declared only where the system's headers do not declare them.

Elsewhere. The definitions stand, but the compiler's own headers may declare some of their names otherwise (PreludeNames.def):

  • Renamed for the header's own definitions: reserved names (glibc's bits/types.h, macOS's __mbstate_t), C's <stddef.h> and <stdint.h> names, and what the Windows C runtimes' <corecrt.h> declares (time_t, wint_t). The header uses #define X neverd_X, restored with push_macro/pop_macro, so the program's layouts hold and the names stay the compiler's afterwards. The C parser reads these renames as the system's own names.
  • Undefined meanwhile: member names that a C library's <sys/cdefs.h> defines as macros (__unused on macOS and bionic).
  • Checks: the C library types' size and offset checks follow both branches. Where the system declares the types, they are checked only as C, because glibc's C++ declares __SOCKADDR_ARG otherwise.

Layouts that hold on other compilers.

  • Typedef-named records: a record C names only through a typedef (typedef struct { ... } X, or typedef struct X X) is declared typedef struct X X; and named X, as the system's headers name it. LocalType::TypedefNamed comes from the C parser and the DWARF import.
  • i386 records: a record holding eight-byte scalars, which i386 aligns to four, takes #pragma pack(push, 4). That changes nothing on i386 and keeps the layout where compilers align them to eight. The parser reads a pack of the ABI's own limit that changes no layout as no pack.
  • GNU bit-fields: records with them take NEVERD_GNU_STRUCT, which is MinGW's gcc_struct.
  • Arrays of unknown records: a typedef of an array of a record whose members are unknown (ARM glibc's jmp_buf) is written as a comment, as are the prototypes that take it.
  • Python: __int128 gets an aligned class, as long double has. Before, a record holding one was too small on ARM64.

Interfaces.

  • C API: "systemLibrary" (by default the library the program joins; false for none) and "headerPragmas".
  • CLI: neverd types --system-library, --header-pragmas.
  • fetch_render_toolchains.py also fetches zig (macOS's and musl's headers), libnativehelper and system/logging at the Android libraries' commits, and musl 1.2.5 (SHA-256 matching Debian's copy), which the generator reads.
  • check_type_renders.py --system-library compiles every spelling on the library's own system, and its portable C on every other system of its pointer width.

Verification

check_type_renders.py --system-library on all 12 libraries. Each library's own system compiles every spelling:

  • glibc x64 and x86: the host's headers;
  • glibc ARM64 and ARM: the fetched sysroots;
  • Android: bionic with the NDK's headers;
  • musl: zig.

Its portable C compiles, defining the C library's types, on:

  • MinGW x64 and x86, with mingw-w64's headers;
  • MSVC x64 and x86, with clang's own headers;
  • macOS arm64 and x86-64, with zig;
  • musl, glibc and bionic.
Libraries Result
Android x64, x86, ARM64, ARM 0 failures
musl ARM64, ARM 0 failures
glibc x64, x86, ARM64, ARM; musl x64, x86 3 failures each, all MSVC: GNU bit-fields (_IO_FILE, fenv_t)

A glibc program's header, compiled for every target. linux_libc_calls_x64 and linux_libc_calls_x86, with checks and prototypes, compile with 0 errors on:

  • the host's glibc, as C, C as C++, and C++;
  • the host's glibc with NEVERD_OWN_LIBC_TYPES;
  • glibc of the other architecture;
  • MinGW;
  • macOS arm64 and x86-64;
  • musl and bionic.

MSVC fails only the two bit-field records. In C, the prototypes there warn where they redeclare routines the compiler knows otherwise, such as vprintf with its va_list.

Tests.

  • New: SessionCAPITest.TheCLibrarysTypesComeFromTheSystemsHeaders (DWARF objects dwarf_libc.{x64,x86}.o, built as dwarf_libc.c says), TypeRender.TheCLibrarysTypesComeFromItsHeadersOnItsSystem, TypeRender.RecordsATypedefNamesGoByThatName, TypeRender.I386RecordsLayOutAsI386OnEveryCompiler, TypeRender.ChecksLeaveTheFillersOut, TypeParse.HeaderPragmasNameTheSystemHeaderOfTheTypesAfterThem, TypeParse.AHeadersOwnRenamesLeaveTheSystemsNames, TypeParse.APackOfTheABIsOwnLimitIsNoPacking.
  • SessionCAPITest.ThirtyTwoBitLinuxObjectsLayRecordsOutAsGCCDoes now expects i386's pack.
  • NeverDTypesTests (137), NeverDSessionCAPITests (177), NeverDDebugInfoTests (140) and NeverDLocalTypesDebugContextTests (6) pass, with the kernel inputs.
  • Regenerating the 12 libraries reproduces them.
  • The style check passes.

Limitations

  • MSVC and GNU bit-fields: Microsoft's compilers cannot lay out GNU's bit-fields where they differ, so glibc's _IO_FILE and fenv_t and musl's fenv_t take another layout there, which the checks report.
  • Not covered by this change: the Windows libraries' C runtime types (tagLC_ID under MinGW) and macOS's libSystem; the next change does those.

C and C++ headers rendered for a Linux program defined the C library's
types again, which collided with the system's headers wherever the
compiler had them, and with what Windows' and macOS' compilers' own headers
declare.  A program joined to glibc's, bionic's or musl's library
(HeaderLibrarySystems.def) now takes those types from that system's
headers where the compiler has its C library: a probe defines
_GNU_SOURCE, as the libraries were read, and NEVERD_LIBC_FROM_SYSTEM, and
the header includes the headers its C library types come from, which the
regenerated libraries name before each type (#pragma neverd_header).
Everywhere else it defines them itself, renaming those the compiler's own
headers may declare otherwise for its definitions (PreludeNames.def) and
undefining member names a C library defines as macros meanwhile.

So that its layouts hold on other compilers: a record a typedef names is
declared and named by the typedef; an i386 record holding eight-byte
scalars takes pack(4), which the parser reads back as none; GNU's
bit-fields take gcc_struct under MinGW; an array of an unknown record is
said in a comment.  Python's __int128 takes C's alignment.  The C API and
CLI take the system library, the checker compiles each library on its own
system and on the others, and the fetch script fetches zig and the
generator's sources.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants