build: freestanding host-test compilation#1499
Draft
Mearman wants to merge 15 commits into
Draft
Conversation
df92abc to
b9ae242
Compare
This was referenced Jun 12, 2026
8254a69 to
56e2328
Compare
Adds a new "freestanding" build mode for clar unit tests. Tests built
with freestanding=True are compiled with -nostdinc against pblibc headers
and the compiler's own built-in include directory, with no host libc in
scope. This lets us prove that firmware libc implementations are correct
without mixing in host-libc behaviour at the compilation or link level.
Infrastructure added:
wscript — discovers COMPILER_BUILTINS_INCLUDE at configure time using
-print-resource-dir (clang) or -print-file-name=include (gcc).
waftools/pebble_test.py — adds _add_freestanding_clar_test() and a
freestanding=True parameter to clar() / add_clar_test(). The
freestanding path uses -nostdinc with -isystem ordering: pblibc first,
then compiler builtins, then the platform SDK/system include directory
as a last-resort fallback for #include_next chains (e.g. setjmp.h).
tools/clar/clar.c — guards host-only includes (assert.h, strings.h,
sys/types.h, sys/stat.h, fflush) with #ifndef CLAR_FREESTANDING.
Adds forward declarations for POSIX functions missing from pblibc
(strcasecmp) in the freestanding branch. Adds the freestanding
platform-specific block (#elif defined(CLAR_FREESTANDING)).
tools/clar/clar.py — adds a "freestanding" print_mode that selects the
clar_sandbox_freestanding / clar_io_freestanding / clar_categorize /
clar_mock / clar_print_freestanding module set.
tools/clar/clar_sandbox_freestanding.c — static stubs for clar_sandbox,
clar_unsandbox, fixture_path, and cl_fs_cleanup.
tools/clar/clar_io_freestanding.c — freestanding I/O module included
into clar_main.c. Routes printf/fprintf/vprintf/vfprintf/fflush
through freestanding_write() in shim.c. Declares strcasecmp and
defines FILE/stderr.
tools/clar/clar_print_freestanding.c — print backend implementing
clar_print_init/shutdown/error/ontest/onsuite/onabort.
tests/freestanding/shim.c — the single host-boundary translation unit
compiled WITHOUT -nostdinc. Provides freestanding_write (write(2)-
backed output), calloc (malloc+memset), realloc (size-header allocator
so the old size is known on grow), strdup, abort (_exit(1)),
strcasecmp, and qsort (insertion-sort stub).
tests/freestanding/wscript_build — builds shim.c as libfreestanding_shim
after stripping -nostdinc and -isystem flags from CFLAGS.
Signed-off-by: Joseph Mearman <joseph@mearman.co.uk>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three header updates needed when pblibc is used as the sole libc under -nostdinc on a non-ARM host: stdlib.h — add calloc, realloc, qsort, abort, and strdup declarations. malloc and free were already present; the new functions are provided by tests/freestanding/shim.c in freestanding builds and by the host libc in regular test builds. sys/cdefs.h — add compiler-attribute shims expected by platform SDK/libc headers that are reached through #include_next chains. macOS SDK headers (setjmp.h, assert.h) use __dead2, __cold, __disable_tail_calls; Linux glibc headers use __THROW, __THROWNL, __LEAF, __NTH, __NTHNL. All expand to standard GCC/Clang attributes or to nothing on other compilers. unistd.h — replace empty stub with #include <sys/types.h> so that ssize_t is available. clar_asserts.h includes <unistd.h> for ssize_t, and sys/types.h already defines it correctly for non-ARM hosts. Signed-off-by: Joseph Mearman <joseph@mearman.co.uk> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Switch test_floor from the standard host-test path (which linked against host libm) to freestanding=True, so it compiles under -nostdinc against pblibc headers only. This proves the freestanding infrastructure end-to-end: floor.c and its test now compile and pass with no host libc in scope, on both macOS (arm64, Apple Clang) and Linux (x86-64, LLVM 18 in Docker). Signed-off-by: Joseph Mearman <joseph@mearman.co.uk> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
HUGE_VAL is the positive-infinity double constant required by C99 <math.h>. It was absent from the pblibc header, which caused a compilation failure when building test_log.c under -nostdinc (the host libm's definition is not visible in that mode). HUGE_VAL evaluates to INFINITY on every IEEE-754 host, so the definition is correct for both the firmware target and the host freestanding test build. Signed-off-by: Joseph Mearman <joseph@mearman.co.uk> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both tests included <fenv.h> and called fesetround(FE_TONEAREST) in their __initialize hook. This is a host-libc header that does not exist under -nostdinc, so it blocks freestanding compilation. The call is a no-op: FE_TONEAREST (round-half-to-even) is the IEEE-754 power-on default on every host the project targets (x86-64 and arm64), and the build already passes -ffp-contract=off and -fexcess-precision=standard which remove the remaining FP-divergence sources. Removing it has no effect on test behaviour. Delete the #include <fenv.h>, the fesetround call, and the now-empty __initialize hooks from test_log.c and test_pow.c. Signed-off-by: Joseph Mearman <joseph@mearman.co.uk> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Flip the three remaining libc/math clar() calls to freestanding=True and drop test_libs=['m']: the product sources (round.c, log.c, pow.c, scalbn.c, sqrt.c) are pblibc implementations compiled from source, so the host libm is not needed. Under -nostdinc each test resolves: <math.h> — pblibc src/libc/include/math.h (HUGE_VAL now present) <stdint.h> — compiler built-in dir clar.h — generated dir pblibc_private.h — src/libc add_include Verified 4/4 pass natively (macOS/clang) and in the pebbleos-docker container (Linux/x86-64 amd64). Signed-off-by: Joseph Mearman <joseph@mearman.co.uk> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
pblibc_private.h undefines the ctype macros (isspace, isdigit, etc.) so that test code can be redirected to pblibc_* function names. Both strtol.c and vsprintf.c included <ctype.h> before <pblibc_private.h>, so the macros were undefined before the function bodies executed. Under hosted builds this was harmless because the host libc provides the underlying symbols after the #undef. Under -nostdinc freestanding builds there is no fallback: the call to isspace() / isdigit() fails to link. Fix: include <ctype.h> after <pblibc_private.h> so the pblibc macro definitions are always present when the function body runs, regardless of whether pblibc_private.h undefines and re-maps other symbols. Signed-off-by: Joseph Mearman <joseph@mearman.co.uk> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add freestanding=True to all string test entries: memcmp, memcpy, memset, memchr, atoi, strtol, strcat, strlen, strcpy, strcmp, strchr, strspn, and strstr. strtol and atoi also require src/libc/ctype_ptr.c because strtol.c calls isspace() whose macro expands to a reference to __ctype_data[]. test_ctype is intentionally left hosted: its test strategy compares pblibc's ctype against the host libc as the oracle. That comparison is only meaningful when the host ctype.h is available; under -nostdinc the pblibc ctype.h would serve as both sides of the comparison. Signed-off-by: Joseph Mearman <joseph@mearman.co.uk> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
vsprintf.c calls isdigit() via the pblibc ctype macro, which expands to a reference to __ctype_data[]. Add src/libc/ctype_ptr.c as a product source so the symbol is present at link time. Signed-off-by: Joseph Mearman <joseph@mearman.co.uk> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The pblibc_private.h rename block aliased memcpy -> pblibc_memcpy and friends so host unit tests would not collide with the host libc. Freestanding tests build under -nostdinc with no host libc in scope, so there is nothing to collide with and the shim is unnecessary there: gate the rename block on !defined(CLAR_FREESTANDING), which the freestanding group defines. For the tests to find their symbols once the shim no longer declares them, the pblibc headers must be complete: math.h was missing pow and scalbn (both implemented in src/libc/math), so add the declarations, and test_sprintf.c now includes <stdio.h> for snprintf rather than leaning on the shim. The hosted test group keeps the shim, which it still needs. Signed-off-by: Joseph Mearman <joseph@mearman.co.uk> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
<assert.h> from Xcode 26 SDK expands assert() using __unsafe_forge_null_terminated, a non-standard C extension that does not parse under -nostdinc. Gate the include on !CLAR_FREESTANDING and replace assert(0) with __builtin_trap() in that code path. Signed-off-by: Joseph Mearman <joseph@mearman.co.uk> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
<strings.h> from Xcode 26 SDK uses _LIBC_CSTR and _LIBC_SIZE pointer annotations that cause parse errors under -nostdinc. Gate the include and provide a static inline ffs() wrapper around __builtin_ffs() for the freestanding path. Signed-off-by: Joseph Mearman <joseph@mearman.co.uk> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…standing Set freestanding=True on five clar() entries and add the include paths each test needs (-I src/fw, src/libutil/includes, tests/stubs; plus src/libos/include for mbuf which needs os/mutex.h). Tests verified 5/5 passing natively (macOS clang) and in the PebbleOS Docker container (Linux). Signed-off-by: Joseph Mearman <joseph@mearman.co.uk> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… freestanding Freestanding-compile these service tests, adding the required include paths and marking them freestanding=True in the clar() declaration. Signed-off-by: Joseph Mearman <joseph@mearman.co.uk> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Joseph Mearman <joseph@mearman.co.uk>
56e2328 to
6f53bc1
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Host tests compile against the host libc, so they pick up different headers, different libm, different linker behaviour per platform. Freestanding compilation compiles tests with
-ffreestanding -nostdlibagainst pblibc instead, so the test environment matches what the firmware links against and the same test binary is built regardless of host OS.Freestanding compilation infrastructure:
freestanding=Trueonclar()swaps the host libc for pblibc headers and adds the right compilation flags.pblibc headers extended for freestanding builds (missing size_t, NULL, etc.), HUGE_VAL added to pblibc math.h.
Math tests migrated: test_floor, test_round, test_log, test_pow. test_log and test_pow no longer need fenv.h because the previous PR replaced the host-libm oracle with static reference tables.
String and printf include-order fix: ctype.h moved after pblibc_private.h in strtol and vsprintf (same pattern as floor.c).
String tests (memcpy, strlen, etc.) and test_sprintf migrated to freestanding.
Rename shim retired for freestanding builds: the
#define fn pblibc_fnshim in pblibc_private.h is no longer needed when compiling freestanding, since there's no host libc to rename away from.Stubs layer made freestanding-compatible (stubs_logging.h).
Util data-structure tests migrated: buffer, pstring, lru_cache, mbuf, dict.
Three service tests migrated: registry_endpoint, transcription, app_file.
Merge order: 2 (after #1497)
Depended on by: #1500