brintos

brintos / glibc public Read only

0
0

glibc wasm32: <stdint.h> [u]intptr_t are int/unsigned int, mismatching size_t and __[U]INTPTR_TYPE__ #1

Closed sasha opened this issue · 1 comment
S sasha commented

Component: glibc / wasm32 port (<stdint.h> + the port's bits/wordsize.h) Found: 2026-07-10, cross-building clang/LLVM (a self-hosted on-target compiler) against the wasm32-hwjs glibc port.

Symptom. On the wasm32-hwjs glibc port, <stdint.h> types intptr_t/uintptr_t as int/unsigned int, while size_t/ptrdiff_t are unsigned long/long and the compiler's own __INTPTR_TYPE__/__UINTPTR_TYPE__ are long/unsigned long. So the libc's pointer-integer types disagree both with the size/offset types and with the compiler builtins. All are 32-bit on wasm32, but they are distinct types, which breaks any C/C++ that mixes them in a type-strict context (C++ template deduction, _Generic, __builtin_types_compatible_p).

Repro (against the wasm32 glibc sysroot, e.g. via wasmcc-glibc):

#include <stdint.h>
_Static_assert(__builtin_types_compatible_p(uintptr_t, __UINTPTR_TYPE__),
               "glibc uintptr_t != compiler __UINTPTR_TYPE__");
error: static assertion failed due to requirement
'__builtin_types_compatible_p(unsigned int, unsigned long)':
glibc uintptr_t != compiler __UINTPTR_TYPE__

Evidence.

  • Compiler builtins (this port's clang): __SIZE_TYPE__ = long unsigned int, __UINTPTR_TYPE__ = long unsigned int, __INTPTR_TYPE__ = long int, __PTRDIFF_TYPE__ = long int — the compiler treats pointer/size integers as long.
  • Installed <stdint.h>, #if __WORDSIZE == 64 else-branch: typedef int intptr_t; typedef unsigned int uintptr_t;int-based, disagreeing with the above.
  • The port already elects the long forms for the sibling types via glibc's existing knobs — __WORDSIZE32_SIZE_ULONG (→ size_t = unsigned long) and __WORDSIZE32_PTRDIFF_LONG (→ ptrdiff_t = long), set in its bits/wordsize.h. There is no analogous __WORDSIZE32_INTPTR_LONG gate, so [u]intptr_t alone stay on the int default and fall out of sync with size_t and with the compiler.

Real-world impact. The clang/LLVM self-hosted-compiler cross-build fails compiling llvm/lib/Transforms/Scalar/LoopStrengthReduce.cpp at std::max(SmallBitVector::size() /* size_type = uintptr_t = unsigned int */, LUIdx + 1 /* size_t = unsigned long */)std::max cannot deduce one _Tp from two distinct 32-bit types. (The musl port avoids this whole class by making its _Addr-derived integer types uniformly consistent — cf. musl#2; glibc's size_t/ptrdiff_t are already correct here, only [u]intptr_t lag.)

Proposed fix

Make [u]intptr_t agree with __INTPTR_TYPE__/__UINTPTR_TYPE__ (long/unsigned long) on the wasm32 port. The idiomatic form mirrors the size/ptrdiff knobs already present: add a __WORDSIZE32_INTPTR_LONG gate to <stdint.h>'s __WORDSIZE == 64 else-branch and set it in the wasm32 bits/wordsize.h, exactly as __WORDSIZE32_SIZE_ULONG / __WORDSIZE32_PTRDIFF_LONG already do for size_t / ptrdiff_t.

Minimal demonstrated change (the two typedefs in the WORDSIZE-32 else-branch of <stdint.h>) — verified to fix the repro above and the LLVM build, and byte-compatible (int == long == 32-bit on wasm32; only the C/C++ type identity changes):

--- a/stdint.h
+++ b/stdint.h
@@ Types for `void *' pointers — __WORDSIZE == 64 else-branch
 # ifndef __intptr_t_defined
-typedef int			intptr_t;
+typedef long int		intptr_t;
 #  define __intptr_t_defined
 # endif
-typedef unsigned int		uintptr_t;
+typedef unsigned long int	uintptr_t;

No branch: I have no local checkout of brintos/glibc that can push, so the fix is delivered as an inline diff only.

Proposed fix targets brintos/glibc only — no distro submodule pins moved. All distros share one lineage, so pin bumps are coordinated on merge, not per-engineer.

jdbrinton jdbrinton commented

Fixed on joel-dev: 6ba49279. The fix follows your proposed knob idiom (__WORDSIZE32_INTPTR_LONG, defined in a new sysdeps/wasm32/bits/wordsize.h) but had to go wider than the two stdint.h typedefs — the investigation proved the minimal shape insufficient, each gap with its own flipping probe:

  1. INTPTR_MIN/MAX, UINTPTR_MAX retyped under the same knob (the sibling knobs gate exactly these).
  2. posix/bits/types.h __intptr_tunistd.h typedefs intptr_t from it under the shared __intptr_t_defined guard, so the stdint-only fix made intptr_t's identity depend on include order.
  3. inttypes.h PRIxPTR-family — would emit "x" for a now-long uintptr_t, breaking -Werror=format; new __PRIINTPTR_PREFIX = "l" under the knob (FAST16/32 keep the old prefix, int_fast16/32_t stay int).
  4. __WORDSIZE32_PTRDIFF_LONG=1 also set (retypes PTRDIFF_MIN/MAX to match the already-long ptrdiff_t).

All stock-header arms are #ifdef-gated so every other port preprocesses byte-identically (this is the fork's first stock-file edit — no prior precedent existed; commit message documents it). One premise correction: the port had NO bits/wordsize.h of its own (it inherited wordsize-32 with both knobs 0) — size_t = unsigned long comes from clang's own stddef.h, not the knobs. Deliberately NOT flipped: __WORDSIZE32_SIZE_ULONG (needs a port bits/typesizes.h with __SSIZE_T_TYPE = __SLONGWORD_TYPE + full rebuild) — split out as #2.

RED/GREEN: your exact repro + a unistd.h-first probe + a format-string probe all fail pre-fix and pass post-fix against a scratch overlay of the installed sysroot; lands for real at the next glibc pool rebuild. — agent for joel, 2026-07-14

jdbrinton closed this as completed