Remove the STL and the C runtime from base - #14
Merged
Merged
Conversation
Survey of base turned up STL use in three shapes. This clears the two
that were incidental and leaves only what the language itself mandates.
C library headers: base spells its C calls bare (memcpy, snprintf), but
39 files reached them through <cstring>/<cstdio>/<cstdint>/... which only
guarantee the std:: overloads -- the bare name compiling at all was a
libstdc++ courtesy. Switched to <string.h>/<stdio.h>/... and rewrote the
remaining std::memcpy/fprintf/strcmp/getenv/... call sites to ::.
Dead includes: <vector> in command_line.h, <limits>/<set>/<string> in
file_util.h, <memory> in scoped_file.h. None were used.
Real dependencies, replaced with base's own:
- <type_traits> in option.h -> base/meta/traits.h, which gains
is_floating_point, is_arithmetic,
is_convertible and ConvertibleTo
- <bit> in bucket_allocator.h -> base::CountLeftZero, new alongside
PopCount in builtins_bit.h
- <cstddef> in compiler.h -> decltype(sizeof(0)), which is
size_t by definition and needs no
header at all
- <thread>/<chrono> in thread_pool -> base::SleepForMicroseconds, new in
thread.h with nanosleep and Sleep
backings, alongside Thread::Join
and YieldCurrentThread
- <thread> in the allocator test -> raw pthread/CreateThread, so the
standalone target keeps linking
allocator sources and nothing else
What remains is core language, not library: <new> for placement new,
<initializer_list> for brace-init constructors, and std::nothrow_t in the
operator new overrides, whose signature the ABI fixes. Both headers are
freestanding. The <format> shims stay opt-out under BASE_NO_STD_FORMAT,
and atomic.h/mutex.h keep their MSVC std:: fallbacks for now.
888 base_unittests and 45 base_memory_unittests pass.
mutex.h auto-selected <mutex>/<shared_mutex> on MSVC, but the native
Windows path it was falling back from is pure SRWLOCK and is already what
clang-cl builds use -- MSVC was taking the STL route for no reason the
code could point at. Removed the auto-enable; BASE_USE_STD_MUTEX survives
as a hand-set escape hatch. That takes the last non-mandated STL header
out of base's default build on every platform.
Tests for what the previous commit added, none of which had any:
- containers/builtins_bit_test.cc for CountLeftZero / CountRightZero,
per bit position, at zero, and at each type width
- meta/traits_test.cc for is_floating_point, is_arithmetic,
is_convertible and ConvertibleTo, plus the older traits option.h
dispatches on, which nothing covered either
- threading/thread_test.cc, which was one #if 0 block and now exercises
Thread::Join, SleepForMicroseconds and YieldCurrentThread
Writing them caught a real defect: CountLeftZero took its width from
MinMax<T>::digits(), which follows std::numeric_limits and excludes the
sign bit, so a signed argument would have been off by one. The width now
comes from sizeof and a static_assert rejects signed types, matching what
std::countl_zero accepts. A test pins the distinction.
907 base_unittests (up from 888) and 45 base_memory_unittests pass.
atomic.h auto-selected std::atomic whenever MSVC's own frontend compiled
it, because the native path is built on __atomic_* builtins and MSVC has
none. That was base's last STL dependency in a default build.
base::Atomic and base::Atomic<T*> are now one class over a primitive layer
with two backends: the __atomic_* builtins as before, and _Interlocked*
intrinsics for MSVC. The builtin path keeps its semantics exactly -- the
28 atomic tests, 907 unit tests and 45 memory tests cover the refactor.
Notes on the MSVC backend:
- Values are bit-cast to the signed integer of their width. Only 1, 2, 4
and 8 byte types are supported and a static_assert says so; the builtin
path's libatomic fallback for wider T has no counterpart here.
- Read-modify-write always uses the plain, full-barrier intrinsic. That
is stronger than a relaxed request asks for, never weaker, and on
x86/x64 it is what the weaker orders compile to anyway.
- Load and store are the only operations whose emitted code depends on
the order, so they are the only ones that branch on it. ARM64 gets a
real __dmb; x86/x64 gets a compiler barrier, which is all TSO needs.
- 32-bit x86 lacks the 64-bit read-modify-write intrinsics, so those are
synthesised from cmpxchg8b.
- The widths are named Int32/Int64 rather than spelled long/__int64,
because long is 64-bit on the host the test below runs on and hard
coding it would make every 4-byte atomic touch 8 bytes.
There is no MSVC here, so the backend is verified by running it: two new
targets compile it against atomic_msvc_intrin_shim.h, which stands the
intrinsics on the builtins, once normally and once with _M_IX86 asserted
to reach the compare-exchange loops. Both cover every width, pointers,
bool, a non-integer payload, and four threads contending on a counter and
a CAS. They pin the logic -- dispatch, argument order, bit-casting, the
negation behind fetch_sub, pointer byte scaling -- not the emitted code or
barrier placement, which only a Windows build can check.
Verified: gcc 16 Debug and clang Release, ctest 4/4 both. ThreadSanitizer
over the atomic, mutex and thread-pool tests reports no data races (the
thread-leak warnings are pre-existing: Thread had no Join until now).
The script flagged every std:: token equally, so the mandated ones -- placement new, brace-init's std::initializer_list, std::nothrow_t in the operator new replacements -- sat in the output run after run next to things that were actually removable. A report that is mostly false positives is a report nobody reads. It now splits the two, hides the mandated group behind --all, and counts only removable use in the percentage. It also understands includes, so a stray <vector> is a finding even where no std:: name follows it, and it skips the C headers under their C spelling while still flagging <cxxx>. Current output: 14 removable uses, 0.05%, all of them inside the BASE_USE_STD_ATOMIC and BASE_USE_STD_MUTEX branches that nothing enables. readme.md now states the policy and why each remaining category stays.
New base/memory/mem_ops.h holds MemCopy/MemMove/MemSet/MemZero/ MemCompare/MemEqual. These four are not ordinary library functions -- a compiler emits memcpy and memset calls on its own for struct assignment and zero-init, so the symbols are part of the freestanding contract. The header is not, though: GCC and Clang reach them as __builtin_, and MSVC inlines them once declared with #pragma intrinsic, so neither path needs <string.h>. 19 files converted. The str* calls went to base's own, which already existed and in two cases were simply not being used: CountStringLength for strlen, and Strcmp for strcmp. Added FindChar/FindLastChar for strchr/strrchr. <string.h> is now gone from 25 files. string_compare.h needed fixing before anything could use it. All three Strcmp overloads returned mem_size, which is u64 -- an unsigned return cannot express "sorts first", so every negative result wrapped to a huge positive one and `Strcmp(a, b) < 0` was false whatever the strings were. The limited overload also read two uninitialized locals when the limit started at zero. It had no callers and no test, which is how both survived. It now returns int, splits the limited form out as Strncmp, compares through the new base::make_unsigned_t so the answer does not depend on whether plain char is signed, adds StrEqual for the common case, and is constexpr throughout. string_compare_test.cc and char_algorithms_find_test.cc cover all of it, including the two defects above. 921 unit tests pass, ctest 4/4.
Every ToString overload was its own snprintf call, which pulled stdio into anything that turned a number into a string. base already formats integers itself in FormatInteger, so the six integer overloads now route through FormatTo and reach no CRT at all; the two float ones go through FormatTo as well and keep their historical precision, though FormatFloat still delegates to snprintf underneath for now. to_string.h had no test. The new one checks every overload against snprintf directly, so the move is provably output-identical rather than believed to be, and sweeps 19 magnitudes in both signs plus the type limits -- including LLONG_MIN, which breaks a naive negate-then-format.
FormatFloat built a printf spec and called snprintf, which is what kept stdio and its locale and float machinery linked into anything that formatted a double. base now converts floats itself. The conversion is exact rather than approximate. Every finite double is exactly representable in decimal because 2^-n is, and for m * 2^e with e < 0 the identity m / 2^-e == m * 5^-e / 10^-e turns the whole thing into one big-integer multiply: the digits of m * 5^-e, with -e of them after the point. So the digits are computed exactly and rounded once, at the requested position, half-to-even -- which is what printf does. The big integer is fixed-capacity, sized for the widest case (the smallest subnormal needs 2548 bits), and uses 32-bit limbs so no multiply or divide in it needs a 128-bit type MSVC would spell differently. float_format_test.cc checks it against printf rather than against my reading of the standard: same value, same spec, byte for byte. Beyond the hand-picked cases it sweeps every binary exponent from -1074 to 1023, plus random bit patterns and near-tie decimals whose count BASE_FLOAT_FUZZ_ITERATIONS raises. The deep run for this commit was 15 million comparisons, all matching. The sweep found the one bug that survived writing it: a three-digit exponent printed e+330 for e+300, because the middle digit divided by 100 instead of 10. Wiring it in also fixed a pre-existing defect. The old path formatted into a 64-byte buffer and clamped to it, so %f of anything near DBL_MAX -- over 300 characters before the point -- was silently truncated. It now spills to the heap, matching how FormatWideString already handles the same problem. Zero-padding now also keeps the sign ahead of the zeros. ctest 4/4; 40 format and to_string tests pass.
New strings/number_parse.{h,cc} with ParseInteger, ParseUnsigned and
ParseFloat, following the C functions closely enough to be drop-in for
base's callers -- leading space, sign, 0x and leading-0 radix detection,
saturation on overflow, an end pointer -- while setting no errno and
honouring no locale.
ParseFloat is correctly rounded, not approximate. The digits accumulate
exactly into the big integer the float formatter already uses (now lifted
into strings/decimal_bignum.h, shared by both directions), and the binary
result is rounded once, half-to-even. For a negative decimal exponent the
numerator is scaled up first and then divided down by tens, with every
remainder folded into a sticky bit, which is all the discarded part can
contribute to the rounding decision.
Checked against strtoll/strtoull/strtod on value and on how far each
consumed, over hand-picked cases plus sweeps that round-trip random
doubles through %.17g and generate random decimal text;
BASE_NUMBER_FUZZ_ITERATIONS raises the count. The deep run was 9 million
comparisons, all matching.
The sweeps found two bugs, both in the subnormal range and both worth
naming because neither shows up in ordinary values:
- Double rounding. Rounding to 53 bits and then shifting into the
subnormal range rounds twice and lands a ulp off. Rounding now happens
once, at the precision the result can actually hold.
- Carrying the wrong way. A subnormal's exponent is pinned at 2^-1074
and only its significand grows, so the shift-right-and-bump that a
normal number needs on carry divided the value by two instead. The
two regimes are now written out separately.
option.h no longer includes <stdlib.h> for parsing; only getenv remains.
New base/standard_streams.{h,cc}: WriteStandardError, WriteStandardOutput
and TerminateAbnormally, over write(2) and WriteFile. Buffering was the
wrong behaviour for diagnostics anyway -- a fatal check that writes its
reason into a FILE* buffer and then traps loses the message, which is the
one thing that had to survive. check.cc and logging.cc now format with
base::FormatTo into a stack buffer and issue a single write, so a
concurrent logger can interleave between lines but not inside one.
abort() is replaced by TerminateAbnormally, which traps first (so a
debugger stops at the fault rather than inside a runtime teardown path)
and then takes _exit or TerminateProcess, skipping atexit handlers and
stream flushes. scoped_generic.h was calling abort() without including
<stdlib.h> at all, compiling only because something else happened to pull
it in.
The two /proc readers went to open/read through a new ReadSmallFile, which
reads to EOF rather than sizing first, because the files under /proc report
a size of zero. Their sscanf and atoi went to base::ParseInteger.
getenv is gone: environment_variables_posix.cc walks `environ` directly,
which is the same array getenv scans. Writes still go through
setenv/unsetenv, which own the storage they allocate. option.h and
GetTempDir now go through GetEnvironmentVariable.
strerror is replaced by base::ErrnoName, which returns the symbolic name
rather than strerror's prose: the prose is translated, so the same failure
reads differently per locale, while the name is greppable and matches what
the manual pages and the calling code are written in.
The last snprintf, in cuid2.cc, went to FormatTo.
What stays, and why: malloc/realloc/free in allocator/default_crt_alloc.h,
which is the CRT allocator router and exists to call them; free() in
debugging.cc for the buffers __cxa_demangle and backtrace_symbols allocate,
which their contracts require; and the Windows entry-point shim, whose
whole job is CRT integration.
gcc Debug and clang Release, ctest 4/4 both.
Removed <string.h>, <stdio.h> and <stdlib.h> from seven files where the
call that needed them is gone. Three survived the earlier sweep because
they carry a trailing comment -- "// for memcmp" -- that an anchored
pattern did not match, and the comments were themselves stale.
find_stl.py is now find_runtime_deps.py and reports the C runtime on the
same terms as the STL, splitting removable from mandated. Two bugs in it
were worth fixing first, both of which made it report success it had not
earned:
- The include pattern was [a-z_]+, which cannot match the '.' in
<stdio.h>, so it had never once seen a C header. Verified the fix by
injecting an include and watching it get caught, rather than trusting
a clean run on a tree I had just cleaned.
- Headers with any other extension fell through to the STL branch, so
the Windows shim's <exe_common.inl> was reported as removable STL.
It draws the line at the runtime rather than the operating system: write,
open, mmap and pthread_* are the kernel interface and are not reported;
stdio, printf, malloc and locale are a library base does without.
Current state: zero removable CRT use, and the 0.05% of removable STL is
entirely the two BASE_USE_STD_* branches nothing enables.
readme.md documents the policy and carries a table of what to use instead.
The Linux build never compiles the _win.cc files, so everything this branch
added for Windows shipped unchecked. Auditing it turned up five defects,
three of which would have failed the build outright.
Windows:
- mem_ops.h declared memcpy and friends in terms of size_t, which
base/arch.h deliberately never defines because it includes nothing. Now
decltype(sizeof(0)), and with __cdecl so the redeclaration matches the
CRT's.
- number_parse.cc used __builtin_memcpy, and standard_streams_win.cc
__builtin_unreachable. MSVC has neither. The first goes through
base::MemCopy; the second through a new BASE_UNREACHABLE in compiler.h,
which is __assume(0) there.
- standard_streams_win.cc called GetStdHandle, WriteFile and
GetCurrentProcess, none of which base/win/minwin.h declares, and used
STD_ERROR_HANDLE, which it does not define. Added all of them in
minwin.h's existing style, plus the LPCVOID, LPDWORD and LPOVERLAPPED
typedefs they need, spelled as the SDK spells them so a translation
unit that also pulls in the real <windows.h> sees no conflict.
macOS:
- environment_variables_posix.cc read `environ` directly. Apple does not
export that symbol to a shared library, so a dylib links and then finds
nothing. Uses _NSGetEnviron() there, as environ(7) documents.
- system_error_posix.cc named EBADFD, which is Linux-only. Every errno
name outside C89 and core POSIX is now behind an #ifdef, so one missing
on a platform drops out of the table instead of failing the build.
Also pre-existing, and the reason to look: char_algorithms.h calls
__builtin_strlen unguarded, which MSVC does not have. That is older than
this branch and means base has not been building with MSVC proper, only
clang-cl. Guarded so MSVC takes the hand-rolled loop already sitting below
it. Worth knowing, because it means the _Interlocked* atomic backend
targets a configuration that may never have built.
BASE_UNREACHABLE first went in under #if defined(CONFIG_DEBUG), so the
Debug build passed and Release would not have. Both configurations are
checked now.
Verified by compiling, not by reading: stub SDK headers let GCC parse both
Windows sources and every _MSC_VER branch this branch touched, and run the
MSVC bit-scan and string paths. The macOS branches compile with a stubbed
crt_externs.h and with EBADFD undefined. gcc Debug and clang Release,
ctest 4/4 on both.
Windows CI reported the errors my offline stub headers could not. Most of
them were mine; two were already there.
Mine:
- atomic.h called ::MemoryBarrier() for the seq_cst fence. That macro
comes from <windows.h>, which this header will not drag in. It expands
to mfence anyway, so the instruction goes in directly, with __dmb on
ARM64.
- atomic_msvc_intrin_shim.h was being compiled by MSVC, where it cannot
work: it stands the MSVC intrinsics on the __atomic_* builtins, which
MSVC does not have. It also declared the 32-bit interlocked family on
int while Int32 is long there, so every call mismatched. The backend
test now takes the shim only off MSVC and the real <intrin.h> on it,
which makes that test a genuine check of the shipping code rather than
a stand-in. The shim #errors if MSVC reaches it, so the cause is one
line instead of eighty.
- memory_unittests_main.cc and the backend test spawn threads through the
platform API, but base/win/minwin.h declares no CreateThread,
WaitForSingleObject or CloseHandle, and no INFINITE or WAIT_OBJECT_0.
Added, in minwin.h's existing style.
- The backend test included <pthread.h> unconditionally. Its contention
workers are now file-scope functions with each platform's exact entry
signature, rather than lambdas, so nothing depends on a captureless
lambda converting to a __stdcall pointer.
- The _M_IX86 variant target is no longer built on MSVC, where that macro
is the compiler's own and contradicts _M_X64. The path it reaches is
architecture-independent C++, so one host covers it.
Already broken before this branch, and the reason Windows CI was red on
devel5: thread_win.cc defines GetThreadPriority and GetNativeThreadPriority
with a top-level const the declarations in thread.h do not have. GCC
ignores that on a return type; MSVC calls it a different type and rejects
the redefinition. Dropped the const.
gcc Debug and clang Release, ctest 4/4 on both.
Defining _mm_mfence in atomic_msvc_intrin_shim.h is an error on clang, which has it as a builtin. gcc accepted it, so the Debug build passed and only the clang Release build caught it. On x86 the shim now takes <emmintrin.h> for the real one and keeps the builtin fallback elsewhere.
Two more from Windows CI, which is now down to one error from around two hundred. float_format_test.cc built its infinity as 1.0 / 0.0. MSVC rejects that as a constant divide by zero rather than folding it to infinity, so both values now come from their bit patterns, which is exact and needs no constant folding at all. The STD_*_HANDLE, INFINITE and WAIT_OBJECT_0 macros minwin.h gained are now #ifndef-guarded, as INVALID_HANDLE_VALUE already was. thread_win.cc includes both <Windows.h> and minwin.h, and the SDK spells WAIT_OBJECT_0 as STATUS_WAIT_0 + 0, so a bare redefinition would have warned there. gcc Debug and clang Release, ctest 4/4 on both.
Windows now compiles, and the run got far enough to report test results. NumberParse.FloatSpecials compared NaN bit for bit, but a NaN's payload is unspecified and the two libraries choose differently: glibc hands back a bare quiet NaN for "nan" where the Microsoft CRT sets every payload bit. Both are correct. The test now compares only the sign, which strtod does carry through from "-nan", and keeps the exact bit comparison for everything finite.
The string overload of ReadValue declared a buffer of kMaxStringLength + 1 wchar_t and then told the API it was only kMaxStringLength long. The spare slot the comment reserved was never actually offered. That lost a value of exactly kMaxStringLength characters. RegSetValueExW appends the terminator a REG_SZ was stored without, so reading such a value back needs one slot more than the characters, and asking for less returned ERROR_MORE_DATA. ReadsAValueThatExactlyFillsTheBuffer covers precisely that case and has been failing since it was written; nobody saw it because the Windows build did not compile, so the suite never ran there. The clamp below is what keeps the read in bounds, not the size passed in: |written| is capped at kMaxStringLength before anything indexes the array, so the terminator this function writes lands on the spare slot at worst. Offering the full array changes what the API may fill, not what this function may touch. Predates this branch and is unrelated to the STL and CRT work. Fixed here because it is the last thing between this PR and a green Windows CI, which has been red on devel5 for some time.
The house rule bans the em dash, and "--" between words is the same thing in ASCII. 25 comments this branch added used it. They now use the comma, colon or full stop the sentence wanted, rather than a unicode en dash, which has no business in a C++ source file. The five "--" bullets in the atomic backend test's printf output are now single dashes. The unicode dashes still in alloc_bench.cc, bucket_allocator.h, function.h and base_string.h predate this branch and are left alone.
This was referenced Sep 16, 2026
Force67
added a commit
that referenced
this pull request
Sep 16, 2026
SetFromString stored the caller's pointer, so a string option was only as alive as whatever buffer it was parsed out of. InitOptionsFromEnv reads each variable into a local StringU8 and hands over its c_str(), which dies at the end of the loop iteration: every const char* option set from the environment has pointed at freed memory since base stopped calling getenv (#14). It does not read as a crash. rx takes its capture path from RX_UI_SHOT=<path>, and with the dangling value the engine wrote a 51 KB png to a filename made of whatever the freed bytes happened to spell. Whoever calls SetFromString next gets a different kind of wrong. The other two callers show the shape of the trap. option_file.cc knew, and interns every value into a pool that outlives the options (InternValue). recreation's platform-profile loader did not, and passes the c_str() of a std::string that goes out of scope one line later. Three call sites, two of them holding it wrong, is an API telling you where the ownership belongs: with the option. ParseFromString now copies into a buffer the option keeps, so a value survives its source, Reset() drops it again, and callers can pass anything they have at hand. Only const char* options carry the buffer -- detail::OwnedText is empty for every other T -- so an Option<int> is the size it always was. base/option_test.cc covers what nothing covered: parsing per type, a value outliving the buffer it came from, the same through the environment, Reset, and a rejected parse leaving the old value in place. Three of the six fail without this change.
Force67
added a commit
that referenced
this pull request
Sep 16, 2026
base/filesystem/file.h names uint8_t, uint32_t and int64_t across its signatures and includes nothing that declares them. They arrived transitively until base stopped pulling <cstdint> in for itself (#14), so whether the build survives now depends on the standard library: gcc 15 still lands them through another include, gcc 13 does not, and the library stops dead at base/filesystem/file.h:134:26: error: 'uint32_t' has not been declared base/filesystem/file.h:192:48: error: 'uint8_t' was not declared in this scope with the Span<uint8_t> signatures under it collapsing into 40 more diagnostics. A consumer on ubuntu 24.04 (gcc 13.3) cannot build devel5. The fix is not to include <stdint.h>. base has these types already -- u8, u32, i64, mem_size out of base/arch.h, which includes nothing itself by design -- and 54 of the 56 headers in base that name a fixed-width type use them. file.h was one of the two that did not, so it now says what its neighbours say, and the platform definitions in posix/file_posix.cc and win/file_win.cc follow the declarations. Casts and locals inside those files keep the C spellings, which is right: that code is talking to POSIX and Win32. base/hashing/crc.h is the other one, and it has the same gap without having failed yet: uint32_t reaches it only through whichever of <nmmintrin.h>, <intrin.h> or <arm_acle.h> its #if chain picks, and a target matching no branch gets none of them. That leaves base with no C fixed-width types in any public signature, and no new include to carry. Library and tests build clean on gcc 13.4 and gcc 15.2.
Force67
added a commit
that referenced
this pull request
Sep 16, 2026
base/filesystem/file.h names uint8_t, uint32_t and int64_t across its signatures and includes nothing that declares them. They arrived transitively until base stopped pulling <cstdint> in for itself (#14), so whether the build survives now depends on the standard library: gcc 15 still lands them through another include, gcc 13 does not, and the library stops dead at base/filesystem/file.h:134:26: error: 'uint32_t' has not been declared base/filesystem/file.h:192:48: error: 'uint8_t' was not declared in this scope with the Span<uint8_t> signatures under it collapsing into 40 more diagnostics. A consumer on ubuntu 24.04 (gcc 13.3) cannot build devel5. The fix is not to include <stdint.h>. base has these types already -- u8, u32, i64, mem_size out of base/arch.h, which includes nothing itself by design -- and 54 of the 56 headers in base that name a fixed-width type use them. file.h was one of the two that did not, so it now says what its neighbours say, and the platform definitions in posix/file_posix.cc and win/file_win.cc follow the declarations. Casts and locals inside those files keep the C spellings, which is right: that code is talking to POSIX and Win32. base/hashing/crc.h is the other one, and it has the same gap without having failed yet: uint32_t reaches it only through whichever of <nmmintrin.h>, <intrin.h> or <arm_acle.h> its #if chain picks, and a target matching no branch gets none of them. That leaves base with no C fixed-width types in any public signature, and no new include to carry. Library and tests build clean on gcc 13.4 and gcc 15.2.
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.
base said "no STL where possible" and then reached the standard library in 30 production files, and libc in another 25. This clears both out of every default build.
find_stl.pybecomesfind_runtime_deps.pyand reports the C runtime on the same terms as the STL. It now finds no removable CRT, and 0.05% removable STL, all of it inside theBASE_USE_STD_ATOMICandBASE_USE_STD_MUTEXbranches that nothing enables.STL
<type_traits>base/meta/traits.h, plusis_floating_point,is_arithmetic,is_convertible<bit>base::CountLeftZero, next to the existingPopCount<cstddef>decltype(sizeof(0)), which needs no header<thread>,<chrono>base::SleepForMicroseconds,Thread::Join,YieldCurrentThread<mutex>,<shared_mutex><atomic>on MSVC_Interlocked*backendWhat stays is core language, not library.
<new>backs placement new,<initializer_list>backs brace-init constructors, and the ABI fixes theoperator newsignatures that needstd::nothrow_t. Thestd::formattershims stay opt-out underBASE_NO_STD_FORMAT.C runtime
The line sits at the runtime, not the operating system.
write,open,mmapandpthread_*stay.<cstring>,<cstdio>and friendsmemcpy,memset,memmove,memcmpbase/memory/mem_ops.hstrlen,strchr,strcmpsnprintfbase::FormatTostrtoll,strtodbase/strings/number_parse.hfprintf(stderr, ...),abortbase/standard_streams.hgetenvenvironwalkstrerrorbase::ErrnoNamefopen+fgetsover/procbase::ReadSmallFile<cstring>and<cstdio>were the worst of it. They promise only thestd::overloads, so the barememcpyevery call site wrote compiled by a standard library's courtesy rather than by guarantee.Some CRT use stays on purpose.
allocator/default_crt_alloc.his the CRT allocator router and exists to callmalloc. Thefree()indebugging.ccreleases what__cxa_demangleandbacktrace_symbolshand back, andsetenvowns the storage it allocates. The Windows entry-point shim integrates with the CRT for a living.Float conversion
Formatting and parsing are exact. For
m * 2^ewithe < 0, the identitym / 2^-e == m * 5^-e / 10^-eturns the conversion into one big-integer multiply, so the digits come out exactly and get rounded once, half-to-even. The bignum uses 32-bit limbs, so nothing in it needs a 128-bit type MSVC spells differently.Both directions run against printf and strtod byte for byte. The sweeps cover every binary exponent from -1074 to 1023, random bit patterns, random decimal text, and near-tie values. The deep run was 24 million comparisons.
BASE_FLOAT_FUZZ_ITERATIONSandBASE_NUMBER_FUZZ_ITERATIONSraise the counts.That testing caught four bugs before they shipped:
e+330fore+300CountLeftZerotaking its width fromMinMax<T>::digits(), which drops the sign bitDefects fixed on the way through
base::Strcmpreturnedmem_size, which isu64. An unsigned return cannot express "sorts first", soStrcmp(a, b) < 0was false whatever the strings were. The limited overload also read two uninitialised locals at limit zero. No callers and no test, which is how both survived.FormatFloatformatted into a 64-byte buffer and clamped to it, so%fnearDBL_MAXwas silently truncated.scoped_generic.hcalledabort()without including<stdlib.h>. It compiled because something else pulled it in.find_stl.pymatched includes with[a-z_]+, which cannot match the.instdio.h. It had never once seen a C header. I injected an include and watched it get caught, instead of trusting a clean run on a tree I had just cleaned.MSVC
CI has an MSVC, so the
_Interlocked*backend is compiled and run by thecompiler it was written for. The backend test takes the real
<intrin.h>there,and the shim only off Windows. On MSVC it therefore checks the code that ships,
not a stand-in. A second target asserts
_M_IX86on a non-MSVC host to reachthe compare-exchange loops, which no other build does.
Windows CI was already failing on
devel5before this branch, on aconstthatGCC ignores on a return type and MSVC rejects as a different type. That is fixed
here too, so this turns Windows green rather than merely not-red.
Getting there took several rounds against real MSVC. It caught what stub headers
on Linux could not:
MemoryBarriercomes from<windows.h>, not<intrin.h>minwin.hdeclared none ofCreateThread,WaitForSingleObject,CloseHandle,GetStdHandleorWriteFile1.0 / 0.0as a constant divide by zeroThe tests found one more once it got that far: a NaN's payload differs between
libraries, so comparing parse results bit for bit was wrong to demand they
match.
Verification
gcc 16 Debug and clang Release, ctest 4/4 on both. 966 unit tests, up from 888. ThreadSanitizer over the atomic, mutex and thread-pool tests reports no data races. The thread-leak warnings it does report predate this work, because
Threadhad noJoinuntil now.