Compare commits

..
15 Commits
Author SHA1 Message Date
huntedbytheirs bcb4e259c3 build: wire strings.h sources and tests 2026-09-03 20:57:18 -04:00
huntedbytheirs ce92aa2b6d feat(strings): strcasecmp/ffs/bcopy/bzero 2026-09-03 20:54:23 -04:00
huntedbytheirs b2fbde0fc2 build: wire malloc/string/ctype sources and tests 2026-09-03 20:50:20 -04:00
huntedbytheirs 5eb3cbe302 feat(malloc): first-fit allocator with mmap fallback 2026-09-03 20:44:08 -04:00
huntedbytheirs 06d2ec7c34 feat(string): complete string.h + strdup/memccpy 2026-09-03 20:05:49 -04:00
huntedbytheirs 20f514d91c feat(ctype): classification and case-mapping tables 2026-09-03 20:03:07 -04:00
huntedbytheirs 858fc1762c fix(bench): separate vlibc-under-test TU from host harness TUs 2026-09-03 19:46:20 -04:00
huntedbytheirs 40a392f454 build: wire new src dirs, crt objects, and make check into Autotools 2026-09-03 19:36:32 -04:00
huntedbytheirs 558cf8fd12 feat(start): _start, __libc_start_main, exit/atexit/environ 2026-09-03 19:01:51 -04:00
huntedbytheirs 2480c63fbd fix(syscall): pin r10/r8 for 4- and 5-argument syscalls 2026-09-03 18:32:44 -04:00
huntedbytheirs 601e1fe960 fix(setjmp): restore caller rbx in sigsetjmp mask path 2026-09-03 18:29:02 -04:00
huntedbytheirs e3017cfe56 test(errno): make test_strerror self-contained under -Iinclude 2026-09-03 18:26:48 -04:00
huntedbytheirs b2d588885e feat(setjmp): x86_64 setjmp/longjmp/sigsetjmp 2026-09-03 17:39:20 -04:00
huntedbytheirs dec1017527 feat(headers): stddef/stdint/stdbool/limits/float/assert + features wiring 2026-09-03 17:34:16 -04:00
huntedbytheirs ce517ad0d6 feat(errno): public errno.h with strerror/strerror_r 2026-09-03 17:20:24 -04:00
83 changed files with 11273 additions and 182 deletions
+11
View File
@@ -107,3 +107,14 @@ Makefile.in
/benchmarks/bench_vlibc /benchmarks/bench_vlibc
/benchmarks/bench_string /benchmarks/bench_string
# make check outputs: static test binaries + parallel-test logs (todo 6).
/test_*
/test-suite.log
/tests/startup_modes.sh.log
/tests/startup_modes.sh.trs
# make dist outputs.
/vlibc-*.tar.gz
/vlibc-*/
+284 -6
View File
@@ -2,23 +2,141 @@
SUBDIRS = benchmarks SUBDIRS = benchmarks
AM_CPPFLAGS = -I$(top_srcdir)/include # Build-dir include FIRST: configure generates include/vlibc/features.h
# (from the .in template) into the BUILD tree, and a VPATH build must find
# the generated header before the source tree's checked-in headers.
AM_CPPFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/include
AM_CFLAGS = @VLIBC_CFLAGS@ AM_CFLAGS = @VLIBC_CFLAGS@
# ---- Library ------------------------------------------------------------- # ---- Library -------------------------------------------------------------
vlibc_include_HEADERS = include/vlibc.h include/stddef.h include/string.h
# Authoritative source-dir inventory (todo 6). Every directory a later plan
# todo creates is enumerated here ONCE, so no area can be forgotten; each is
# appended to libvlibc_la_SOURCES WHEN ITS TODO LANDS — the list grows
# incrementally and never references a directory that does not exist yet:
#
# W1 (wired now): src/internal, src/start, src/errno,
# src/setjmp/x86_64, src/string, crt/ (crt1.o)
# todo 7 src/malloc todo 39-42 src/math todo 50 src/multibyte
# todo 11-14 src/stdlib todo 43 src/complex todo 53-54 src/regex
# todo 15-18 src/stdio todo 9,51 src/ctype todo 57-58 src/network
# todo 19,38 src/unistd todo 55 src/search todo 59 src/mq
# todo 20,23,56 src/process todo 66 src/crypt todo 60 src/aio
# todo 21 src/fcntl todo 44-48 src/thread todo 61 src/ipc
# todo 22,34 src/stat todo 49,52 src/locale todo 62-63 src/ldso
# todo 24 src/dirent todo 42 src/fenv/x86_64
# todo 25 src/select
# todo 26-27 src/time
# todo 28 src/signal
# todo 29 src/termios
# todo 30 src/uio
# todo 31 src/resource
# todo 32,36,54,61,67 src/misc
# todo 33 src/mman
# todo 35 src/sys
# todo 37 src/passwd
# todo 37 src/group
#
# NOTE: crt/x86_64/crt1.s is NOT a library source — it is assembled into the
# standalone crt1.o below (the sole crt owner; todo 64 reuses, never rebuilds).
# Level-independent L1 core (wired today + the pre-plan string slice).
VLIBC_CORE_SRCS = \
src/vlibc.c \
src/internal/assert_fail.c \
src/internal/errno.c \
src/internal/syscall_ret.c \
src/start/libc_start_main.c \
src/start/exit.c \
src/start/environ.c \
src/start/tls.c \
src/errno/strerror.c \
src/setjmp/x86_64/setjmp.s \
src/setjmp/x86_64/longjmp.s \
src/setjmp/x86_64/sigsetjmp.s \
src/setjmp/x86_64/siglongjmp.s \
src/string/strlen.c \
src/string/strcmp.c \
src/string/memcpy.c \
src/string/memmove.c \
src/string/memset.c \
src/string/strcat.c \
src/string/strchr.c \
src/string/strcoll.c \
src/string/strcpy.c \
src/string/strdup.c \
src/string/memchr.c \
src/string/memcmp.c \
src/string/strncmp.c \
src/string/strncpy.c \
src/string/strnlen.c \
src/string/strsignal.c \
src/string/strspn.c \
src/string/strstr.c \
src/string/strtok.c \
src/malloc/malloc.c \
src/ctype/ctype.c
# Profile-gated sources, EXISTING gates preserved (strlcpy/strlcat stay L2
# BSD, strcasestr stays L3 GNU — NOT promoted). Task 8/9 additions: stpcpy.c
# (stpcpy/stpncpy) and memccpy.c are WHOLE-FILE L2-gated (#if VLIBC_LEVEL_GE(2)
# around the entire body), and isascii.c/toascii.c hold only L2 symbols
# (the header gates their declarations) — all four must be omitted from a
# level-1 profile, so they join the GE_2 conditional list. Task 10's
# strings_impl.c is likewise whole-file L2-gated (strcasecmp/strncasecmp/ffs/
# ffsl/ffsll/bcmp/bcopy/bzero/index/rindex — nothing is POSIX.1-2008 base), so
# it also joins the GE_2 conditional list. Files with L1
# bodies plus small L2 sections (strcoll.c, strdup.c) stay in VLIBC_CORE_SRCS
# and self-gate at level 1.
VLIBC_LEVEL2_SRCS = src/string/strlcpy.c src/string/strlcat.c \
src/string/stpcpy.c src/string/memccpy.c \
src/ctype/isascii.c src/ctype/toascii.c \
src/string/strings_impl.c
VLIBC_LEVEL3_SRCS = src/string/strcasestr.c
vlibc_lib_LTLIBRARIES = libvlibc.la vlibc_lib_LTLIBRARIES = libvlibc.la
libvlibc_la_SOURCES = src/vlibc.c src/string/strlen.c src/string/strcmp.c \ libvlibc_la_SOURCES = $(VLIBC_CORE_SRCS)
src/string/memcpy.c src/string/memmove.c src/string/memset.c
if PROFILE_GE_2 if PROFILE_GE_2
libvlibc_la_SOURCES += src/string/strlcpy.c src/string/strlcat.c libvlibc_la_SOURCES += $(VLIBC_LEVEL2_SRCS)
endif endif
if PROFILE_GE_3 if PROFILE_GE_3
libvlibc_la_SOURCES += src/string/strcasestr.c libvlibc_la_SOURCES += $(VLIBC_LEVEL3_SRCS)
endif endif
libvlibc_la_LDFLAGS = -version-info 0:0:0 -no-undefined libvlibc_la_LDFLAGS = -version-info 0:0:0 -no-undefined
# ---- Public headers ------------------------------------------------------
# The whole public header inventory (the 3 scaffold headers plus todo 5's
# skeleton); the profile-gated generated features.h is installed separately
# below.
vlibc_include_HEADERS = \
include/vlibc.h \
include/stddef.h \
include/string.h \
include/strings.h \
include/errno.h \
include/setjmp.h \
include/assert.h \
include/ctype.h \
include/cpio.h \
include/float.h \
include/iso646.h \
include/limits.h \
include/stdalign.h \
include/stdarg.h \
include/stdatomic.h \
include/stdbit.h \
include/stdbool.h \
include/stdckdint.h \
include/stdint.h \
include/stdlib.h \
include/stdnoreturn.h \
include/tar.h \
include/tgmath.h \
include/threads.h \
include/uchar.h \
include/sys/types.h
# Install location depends on the install method (see configure.ac). # Install location depends on the install method (see configure.ac).
if INSTALL_OVERWRITE if INSTALL_OVERWRITE
vlibc_includedir = $(includedir) vlibc_includedir = $(includedir)
@@ -38,6 +156,17 @@ nodist_vlibc_features_HEADERS = include/vlibc/features.h
# ---- Compiler drivers ---------------------------------------------------- # ---- Compiler drivers ----------------------------------------------------
bin_SCRIPTS = tools/vlibc-gcc tools/vlibc-clang bin_SCRIPTS = tools/vlibc-gcc tools/vlibc-clang
# ---- crt objects ---------------------------------------------------------
# crt1.o (from crt/x86_64/crt1.s) is built here and ONLY here: todo 64
# reuses these objects for the shared build and must NOT rebuild them.
# Built by `make all` via all-local so a static vlibc link always has its
# entry object available next to the archive.
crt1.o: crt/x86_64/crt1.s
$(AM_V_GEN)$(CC) $(AM_CPPFLAGS) $(CPPFLAGS) -c -o $@ $<
all-local: crt1.o
# ---- Targets ------------------------------------------------------------- # ---- Targets -------------------------------------------------------------
# layouts/C.md requires `make debug`, `make release`, `make bench` and # layouts/C.md requires `make debug`, `make release`, `make bench` and
# `make clean`; build outputs land in bin/{release,debug}. # `make clean`; build outputs land in bin/{release,debug}.
@@ -50,6 +179,7 @@ debug:
$(MKDIR_P) bin/debug $(MKDIR_P) bin/debug
-cp -P .libs/libvlibc.so* bin/debug/ -cp -P .libs/libvlibc.so* bin/debug/
-cp .libs/libvlibc.a bin/debug/ -cp .libs/libvlibc.a bin/debug/
-cp crt1.o bin/debug/
release: release:
$(MAKE) clean $(MAKE) clean
@@ -57,6 +187,7 @@ release:
$(MKDIR_P) bin/release $(MKDIR_P) bin/release
-cp -P .libs/libvlibc.so* bin/release/ -cp -P .libs/libvlibc.so* bin/release/
-cp .libs/libvlibc.a bin/release/ -cp .libs/libvlibc.a bin/release/
-cp crt1.o bin/release/
bench: all bench: all
$(MAKE) -C benchmarks bench $(MAKE) -C benchmarks bench
@@ -68,3 +199,150 @@ compile-commands: clean
bear --append -- $(MAKE) -C benchmarks bench_vlibc bear --append -- $(MAKE) -C benchmarks bench_vlibc
.PHONY: debug release bench compile-commands .PHONY: debug release bench compile-commands
# ---- make check ----------------------------------------------------------
#
# Every test binary is a STATIC VLIBC EXECUTABLE: linked with -nostdlib
# -static -no-pie against crt1.o + a dedicated level-2 test archive. No
# glibc symbol can resolve, so each test genuinely proves self-hosting.
#
# The test archive (libvlibc-check.a) is compiled from the FULL source list
# (all levels included) at VLIBC_LEVEL=2 — the complete L1+L2 surface —
# INDEPENDENT of the configured profile. Rationale: some tests exercise
# L2-gated functions (test_strerror calls strerror_r, future tests call
# bcopy etc.), which a level-1 configured archive does not provide; the
# shipped libvlibc.a keeps the profile gating configured at build time.
# Object files are plain `ar` members compiled directly with $(CC) — no
# libtool (libtool's build-dir libvlibc.a is a wrapper script, and only
# .libs/libvlibc.a is linkable, which is a level-gated profile archive).
#
# Mechanics (extend here for future tests):
# - Add the binary to check_PROGRAMS + TESTS with
# `<bin>_SOURCES = tests/test_x.c`,
# `<bin>_CFLAGS = $(VLIBC_TEST_CFLAGS)` (renames the object to
# `<bin>-<src>.o` automatically),
# `<bin>_LINK = $(VLIBC_TEST_LINK)` and
# `<bin>_LDADD = $(VLIBC_TEST_LDADD)` (static vlibc link; the crt
# object + archive must be in LDADD so they follow the test object).
# - Multi-mode tests run through tests/startup_modes.sh (see that file).
VLIBC_TEST_LEVEL = 2
VLIBC_CHECK_SRCS = $(VLIBC_CORE_SRCS) $(VLIBC_LEVEL2_SRCS) \
$(VLIBC_LEVEL3_SRCS)
VLIBC_CHECK_OBJECTS_FROM_C = $(VLIBC_CHECK_SRCS:.c=.check.o)
VLIBC_CHECK_OBJECTS = $(VLIBC_CHECK_OBJECTS_FROM_C:.s=.check.o)
VLIBC_CHECK_CFLAGS = @VLIBC_CFLAGS@ -DVLIBC_LEVEL=$(VLIBC_TEST_LEVEL) -fno-pic
# The archive itself (plain ar: members compiled at the test level).
libvlibc-check.a: $(VLIBC_CHECK_OBJECTS)
$(AR) $(ARFLAGS) $@ $^
# Library sources compiled at the test level. Deliberately no
# -DHAVE_CONFIG_H and no $(DEFS): config.h pins the PROFILE level, which
# would override -DVLIBC_LEVEL (last definition wins), so the check objects
# must get their level from <vlibc/features.h> alone.
%.check.o: %.c
$(AM_V_CC)$(CC) $(AM_CPPFLAGS) $(CPPFLAGS) $(VLIBC_CHECK_CFLAGS) -c -o $@ $<
%.check.o: %.s
$(AM_V_CCAS)$(CC) $(AM_CPPFLAGS) $(CPPFLAGS) -c -o $@ $<
# Static vlibc link for every check program. automake links
# "$(<bin>_LINK) $(<bin>_OBJECTS) $(<bin>_LDADD) $(LIBS)", so the entry
# object, the level-2 archive and libgcc go in LDADD — they MUST follow the
# test object or the archive would see no undefined symbols and stay
# unextracted. -nostdlib means -lgcc is not auto-added, so it is spelled
# out (permitted libgcc helpers).
VLIBC_TEST_LINK = $(CC) -nostdlib -static -no-pie -o $@
VLIBC_TEST_LDADD = crt1.o libvlibc-check.a -lgcc
# Test TUs: no stack protector (the library is protector-free at this stage),
# non-PIC, level overridden to the test level so L2 declarations are visible.
VLIBC_TEST_CFLAGS = -DVLIBC_LEVEL=$(VLIBC_TEST_LEVEL) -fno-stack-protector \
-fno-pic -fno-pie
check_PROGRAMS = test_syscall test_strerror test_setjmp test_headers \
test_startup test_malloc test_string test_ctype test_strings
test_syscall_SOURCES = tests/syscall_test.c
test_syscall_CFLAGS = $(VLIBC_TEST_CFLAGS)
test_syscall_DEPENDENCIES = crt1.o libvlibc-check.a
test_syscall_LINK = $(VLIBC_TEST_LINK)
test_syscall_LDADD = $(VLIBC_TEST_LDADD)
test_strerror_SOURCES = tests/test_strerror.c
test_strerror_CFLAGS = $(VLIBC_TEST_CFLAGS)
test_strerror_DEPENDENCIES = crt1.o libvlibc-check.a
test_strerror_LINK = $(VLIBC_TEST_LINK)
test_strerror_LDADD = $(VLIBC_TEST_LDADD)
test_setjmp_SOURCES = tests/test_setjmp.c
test_setjmp_CFLAGS = $(VLIBC_TEST_CFLAGS)
test_setjmp_DEPENDENCIES = crt1.o libvlibc-check.a
test_setjmp_LINK = $(VLIBC_TEST_LINK)
test_setjmp_LDADD = $(VLIBC_TEST_LDADD)
test_headers_SOURCES = tests/test_headers.c
test_headers_CFLAGS = $(VLIBC_TEST_CFLAGS)
test_headers_DEPENDENCIES = crt1.o libvlibc-check.a
test_headers_LINK = $(VLIBC_TEST_LINK)
test_headers_LDADD = $(VLIBC_TEST_LDADD)
test_startup_SOURCES = tests/test_startup.c
test_startup_CFLAGS = $(VLIBC_TEST_CFLAGS)
test_startup_DEPENDENCIES = crt1.o libvlibc-check.a
test_startup_LINK = $(VLIBC_TEST_LINK)
test_startup_LDADD = $(VLIBC_TEST_LDADD)
test_malloc_SOURCES = tests/test_malloc.c
test_malloc_CFLAGS = $(VLIBC_TEST_CFLAGS)
test_malloc_DEPENDENCIES = crt1.o libvlibc-check.a
test_malloc_LINK = $(VLIBC_TEST_LINK)
test_malloc_LDADD = $(VLIBC_TEST_LDADD)
test_string_SOURCES = tests/test_string.c
test_string_CFLAGS = $(VLIBC_TEST_CFLAGS)
test_string_DEPENDENCIES = crt1.o libvlibc-check.a
test_string_LINK = $(VLIBC_TEST_LINK)
test_string_LDADD = $(VLIBC_TEST_LDADD)
test_ctype_SOURCES = tests/test_ctype.c
test_ctype_CFLAGS = $(VLIBC_TEST_CFLAGS)
test_ctype_DEPENDENCIES = crt1.o libvlibc-check.a
test_ctype_LINK = $(VLIBC_TEST_LINK)
test_ctype_LDADD = $(VLIBC_TEST_LDADD)
test_strings_SOURCES = tests/test_strings.c
test_strings_CFLAGS = $(VLIBC_TEST_CFLAGS)
test_strings_DEPENDENCIES = crt1.o libvlibc-check.a
test_strings_LINK = $(VLIBC_TEST_LINK)
test_strings_LDADD = $(VLIBC_TEST_LDADD)
# test_startup's default mode exits 42 by design, so it is NOT a bare TESTS
# entry; tests/startup_modes.sh runs its full mode matrix and the -f failure
# scenarios of the other binaries. test_malloc/test_string/test_ctype/
# test_strings default to the happy mode (exit 0 on all-pass) and are bare
# TESTS entries; their -f/-p modes stay runnable by hand.
TESTS = test_syscall test_strerror test_setjmp test_headers \
test_malloc test_string test_ctype test_strings \
tests/startup_modes.sh
CLEANFILES = crt1.o libvlibc-check.a $(VLIBC_CHECK_OBJECTS)
# Distributed but not installed / not otherwise picked up by automake.
# (TESTS scripts are not auto-distributed, so the mode runner is explicit.)
EXTRA_DIST = \
crt/x86_64/crt1.s \
include/vlibc/internal/test.h \
tests/conformance/symbols.list \
tests/startup_modes.sh \
arch/x86_64/syscall_arch.h \
src/internal/atomic.h \
src/internal/errno.h \
src/internal/libc.h \
src/internal/malloc.h \
src/internal/syscall.h \
src/internal/types.h \
src/start/start.h \
src/start/tcb.h
+5 -2
View File
@@ -75,10 +75,11 @@ static inline long __attribute__((always_inline))
__syscall4(long n, long a, long b, long c, long d) __syscall4(long n, long a, long b, long c, long d)
{ {
unsigned long ret; unsigned long ret;
register long r10 __asm__("r10") = d;
__asm__ volatile("syscall" __asm__ volatile("syscall"
: "=a"(ret) : "=a"(ret)
: "a"(n), "D"(a), "S"(b), "d"(c), "r"(d) : "a"(n), "D"(a), "S"(b), "d"(c), "r"(r10)
: "rcx", "r11", "memory"); : "rcx", "r11", "memory");
return (long)ret; return (long)ret;
} }
@@ -87,10 +88,12 @@ static inline long __attribute__((always_inline))
__syscall5(long n, long a, long b, long c, long d, long e) __syscall5(long n, long a, long b, long c, long d, long e)
{ {
unsigned long ret; unsigned long ret;
register long r10 __asm__("r10") = d;
register long r8 __asm__("r8") = e;
__asm__ volatile("syscall" __asm__ volatile("syscall"
: "=a"(ret) : "=a"(ret)
: "a"(n), "D"(a), "S"(b), "d"(c), "r"(d), "r"(e) : "a"(n), "D"(a), "S"(b), "d"(c), "r"(r10), "r"(r8)
: "rcx", "r11", "memory"); : "rcx", "r11", "memory");
return (long)ret; return (long)ret;
} }
+26 -7
View File
@@ -1,6 +1,7 @@
# vlibc — benchmark harnesses (musts/BENCHMARKING.md). # vlibc — benchmark harnesses (musts/BENCHMARKING.md).
AM_CPPFLAGS = -I$(top_srcdir)/include # Build-dir include FIRST (generated features.h lives there in VPATH builds).
AM_CPPFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/include
AM_CFLAGS = @VLIBC_CFLAGS@ AM_CFLAGS = @VLIBC_CFLAGS@
# Built only on demand (via `make bench`), so `make all` does not need the # Built only on demand (via `make bench`), so `make all` does not need the
@@ -9,18 +10,30 @@ EXTRA_PROGRAMS = bench_vlibc bench_string
bench_vlibc_SOURCES = bench_vlibc.c bench_vlibc_SOURCES = bench_vlibc.c
bench_string_SOURCES = bench_string.c bench_string_SOURCES = bench_string.c
# bench_string is a system-headers-only translation unit (it times whichever # Both harness TUs are system-headers-only translation units (they time
# libc it is linked against), so it must NOT inherit AM_CPPFLAGS: -I include # whichever libc they are linked against), so they must NOT inherit
# would pull vlibc's self-contained <stddef.h>/<string.h> into the same TU as # AM_CPPFLAGS: -I include would pull vlibc's self-contained
# the system <stdio.h>/<time.h> and double-define size_t/NULL/offsetof. # <stdarg.h>/<stddef.h>/<string.h> into the same TU as the system
# bench_vlibc keeps the inherited -I include. # <stdio.h>/<time.h> and double-define size_t/NULL/offsetof (or hide the
# compiler's internal <stdarg.h> behind vlibc's). The vlibc function under
# test is reached through the adapter TU bench_vlibc_under.c, which is
# compiled with vlibc's headers and exposes the call to the harness through
# its own declaration (see that file).
bench_vlibc_CPPFLAGS =
bench_string_CPPFLAGS = bench_string_CPPFLAGS =
# Adapter TU for bench_vlibc: vlibc's headers only, so it must NOT inherit
# the (empty) per-target CPPFLAGS above; it gets the include path back
# explicitly.
bench_vlibc_under.o: bench_vlibc_under.c
$(AM_V_CC)$(CC) -I$(top_srcdir)/include $(CPPFLAGS) $(AM_CFLAGS) \
$(CFLAGS) -c -o $@ $<
# By default benchmarks link against vlibc itself. --with-libc=glibc links the # By default benchmarks link against vlibc itself. --with-libc=glibc links the
# same harness against the system glibc; --with-libc=musl builds it with # same harness against the system glibc; --with-libc=musl builds it with
# musl-gcc for the musl reference. # musl-gcc for the musl reference.
if BENCH_LINK_VLIBC if BENCH_LINK_VLIBC
bench_vlibc_LDADD = ../libvlibc.la bench_vlibc_LDADD = bench_vlibc_under.o ../libvlibc.la
bench_string_LDADD = ../libvlibc.la bench_string_LDADD = ../libvlibc.la
else else
bench_vlibc_LDADD = bench_vlibc_LDADD =
@@ -37,6 +50,12 @@ endif
# binary from the previous configuration and the wrong libc would be measured. # binary from the previous configuration and the wrong libc would be measured.
EXTRA_bench_string_DEPENDENCIES = $(top_builddir)/config.status EXTRA_bench_string_DEPENDENCIES = $(top_builddir)/config.status
EXTRA_bench_vlibc_DEPENDENCIES = $(top_builddir)/config.status EXTRA_bench_vlibc_DEPENDENCIES = $(top_builddir)/config.status
bench_vlibc_DEPENDENCIES = bench_vlibc_under.o
# The adapter TU is built by a hand-rolled rule (per-TU include paths), so it
# is not picked up by automake's automatic distribution/cleaning.
EXTRA_DIST = bench_vlibc_under.c
CLEANFILES = bench_vlibc_under.o
# bench_vlibc.c calls vlibc_version(), which exists only in vlibc — under # bench_vlibc.c calls vlibc_version(), which exists only in vlibc — under
# --with-libc=glibc/musl it cannot link, so it is built and run only in the # --with-libc=glibc/musl it cannot link, so it is built and run only in the
+9
View File
@@ -149,5 +149,14 @@ main(void)
printf("%llu\n", sink); printf("%llu\n", sink);
} }
/*
* Explicit flush: this harness is a host program, but under
* --with-libc=vlibc its DT_NEEDED order puts libvlibc.so before
* libc.so.6, so the exit() that runs at process end is vlibc's — which
* does not flush stdio yet (that hook lands with the stdio todo).
* Without the flush the timed results above are lost.
*/
fflush(stdout);
return 0; return 0;
} }
+24 -3
View File
@@ -5,16 +5,29 @@
* This stub times vlibc_version() and is the skeleton that per-component * This stub times vlibc_version() and is the skeleton that per-component
* benchmarks build on. Reconfigure with --with-libc=musl or --with-libc=glibc * benchmarks build on. Reconfigure with --with-libc=musl or --with-libc=glibc
* to link the same harness against a reference libc for comparison. * to link the same harness against a reference libc for comparison.
*
* This TU is deliberately a HOST-headers-only translation unit: it includes
* no vlibc header, because vlibc's self-contained
* <stdarg.h>/<stddef.h>/<limits.h>/<float.h> shadow GCC's internal headers
* and mixing them with the system <stdio.h>/<time.h> hard-errors. The
* function under test is reached through the adapter TU
* bench_vlibc_under.c, which is compiled with vlibc's headers and exposes
* the call through its own declaration here.
*/ */
#ifdef HAVE_CONFIG_H #ifdef HAVE_CONFIG_H
#include <config.h> #include <config.h>
#endif #endif
#include <vlibc.h>
#include <stdio.h> #include <stdio.h>
#include <time.h> #include <time.h>
/* Adapter entry (bench_vlibc_under.c); declared here rather than including
* <vlibc.h>, which this host-header TU must not do. The adapter also drops
* vlibc_version()'s __attribute__((const)), so the timed loop really
* executes the call. */
const char *
bench_vlibc_version(void);
#define ITERATIONS 100000000ULL #define ITERATIONS 100000000ULL
int int
@@ -34,7 +47,7 @@ main(void)
for (unsigned long long i = 0; i < ITERATIONS; i++) for (unsigned long long i = 0; i < ITERATIONS; i++)
{ {
version = vlibc_version(); version = bench_vlibc_version();
} }
if (clock_gettime(CLOCK_MONOTONIC, &end) != 0) if (clock_gettime(CLOCK_MONOTONIC, &end) != 0)
@@ -49,5 +62,13 @@ main(void)
printf("vlibc_version() x %llu: %.3f s (%.2f ns/call), version=%s\n", ITERATIONS, seconds, printf("vlibc_version() x %llu: %.3f s (%.2f ns/call), version=%s\n", ITERATIONS, seconds,
seconds * 1000000000.0 / ITERATIONS, (const char *)version); seconds * 1000000000.0 / ITERATIONS, (const char *)version);
/*
* Explicit flush: this harness is a host program, but its DT_NEEDED order
* puts libvlibc.so before libc.so.6, so the exit() that runs at process
* end is vlibc's — which does not flush stdio yet (that hook lands with
* the stdio todo). Without the flush the buffered result above is lost.
*/
fflush(stdout);
return 0; return 0;
} }
+29
View File
@@ -0,0 +1,29 @@
/*
* vlibc-under-test adapter for the bench_vlibc harness (todo 6 bench fix).
*
* This translation unit is compiled with vlibc's OWN headers (-I ../include)
* and is the ONLY TU in the benchmark that may include a vlibc header. The
* harness TU (bench_vlibc.c) must never include one: vlibc's self-contained
* <stdarg.h>/<stddef.h>/<limits.h>/<float.h> shadow GCC's internal headers,
* so any TU that mixes a vlibc header with the host <stdio.h>/<time.h>
* fails to compile (glibc's <stdio.h> needs __gnuc_va_list, which only the
* compiler's internal <stdarg.h> defines). The adapter isolates the
* vlibc-facing call here and exposes it to the host-header harness through
* its own declaration.
*
* Passing through the adapter also drops vlibc_version()'s
* __attribute__((const)) at the harness call site: the harness sees a plain
* external function, so the timed loop genuinely executes the call instead
* of being hoisted out by the optimizer.
*/
#include <vlibc.h>
const char *
bench_vlibc_version(void);
const char *
bench_vlibc_version(void)
{
return vlibc_version();
}
Vendored
+160 -1
View File
@@ -686,6 +686,11 @@ PROFILE_GE_2_TRUE
vlibc_level vlibc_level
vlibc_profile vlibc_profile
VLIBC_CFLAGS VLIBC_CFLAGS
am__fastdepCCAS_FALSE
am__fastdepCCAS_TRUE
CCASDEPMODE
CCASFLAGS
CCAS
am__fastdepCC_FALSE am__fastdepCC_FALSE
am__fastdepCC_TRUE am__fastdepCC_TRUE
CCDEPMODE CCDEPMODE
@@ -806,6 +811,8 @@ CFLAGS
LDFLAGS LDFLAGS
LIBS LIBS
CPPFLAGS CPPFLAGS
CCAS
CCASFLAGS
LT_SYS_LIBRARY_PATH' LT_SYS_LIBRARY_PATH'
@@ -1482,6 +1489,8 @@ Some influential environment variables:
LIBS libraries to pass to the linker, e.g. -l<library> LIBS libraries to pass to the linker, e.g. -l<library>
CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I<include dir> if CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I<include dir> if
you have headers in a nonstandard directory <include dir> you have headers in a nonstandard directory <include dir>
CCAS assembler compiler command (defaults to CC)
CCASFLAGS assembler compiler flags (defaults to CFLAGS)
LT_SYS_LIBRARY_PATH LT_SYS_LIBRARY_PATH
User-defined run-time library search path. User-defined run-time library search path.
@@ -4931,6 +4940,143 @@ else
fi fi
# AM_PROG_AS: library sources include raw x86_64 assembly (the setjmp family
# in src/setjmp/x86_64/*.s), which automake compiles through CCAS/CCASFLAGS.
# By default we simply use the C compiler to build assembly code.
test "${CCAS+set}" = set || CCAS=$CC
test "${CCASFLAGS+set}" = set || CCASFLAGS=$CFLAGS
depcc="$CCAS" am_compiler_list=
{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking dependency style of $depcc" >&5
printf %s "checking dependency style of $depcc... " >&6; }
if test ${am_cv_CCAS_dependencies_compiler_type+y}
then :
printf %s "(cached) " >&6
else case e in #(
e) if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then
# We make a subdir and do the tests there. Otherwise we can end up
# making bogus files that we don't know about and never remove. For
# instance it was reported that on HP-UX the gcc test will end up
# making a dummy file named 'D' -- because '-MD' means "put the output
# in D".
rm -rf conftest.dir
mkdir conftest.dir
# Copy depcomp to subdir because otherwise we won't find it if we're
# using a relative directory.
cp "$am_depcomp" conftest.dir
cd conftest.dir
# We will build objects and dependencies in a subdirectory because
# it helps to detect inapplicable dependency modes. For instance
# both Tru64's cc and ICC support -MD to output dependencies as a
# side effect of compilation, but ICC will put the dependencies in
# the current directory while Tru64 will put them in the object
# directory.
mkdir sub
am_cv_CCAS_dependencies_compiler_type=none
if test "$am_compiler_list" = ""; then
am_compiler_list=`sed -n 's/^#*\([a-zA-Z0-9]*\))$/\1/p' < ./depcomp`
fi
am__universal=false
for depmode in $am_compiler_list; do
# Setup a source with many dependencies, because some compilers
# like to wrap large dependency lists on column 80 (with \), and
# we should not choose a depcomp mode which is confused by this.
#
# We need to recreate these files for each test, as the compiler may
# overwrite some of them when testing with obscure command lines.
# This happens at least with the AIX C compiler.
: > sub/conftest.c
for i in 1 2 3 4 5 6; do
echo '#include "conftst'$i'.h"' >> sub/conftest.c
# Using ": > sub/conftst$i.h" creates only sub/conftst1.h with
# Solaris 10 /bin/sh.
echo '/* dummy */' > sub/conftst$i.h
done
echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf
# We check with '-c' and '-o' for the sake of the "dashmstdout"
# mode. It turns out that the SunPro C++ compiler does not properly
# handle '-M -o', and we need to detect this. Also, some Intel
# versions had trouble with output in subdirs.
am__obj=sub/conftest.${OBJEXT-o}
am__minus_obj="-o $am__obj"
case $depmode in
gcc)
# This depmode causes a compiler race in universal mode.
test "$am__universal" = false || continue
;;
nosideeffect)
# After this tag, mechanisms are not by side-effect, so they'll
# only be used when explicitly requested.
if test "x$enable_dependency_tracking" = xyes; then
continue
else
break
fi
;;
msvc7 | msvc7msys | msvisualcpp | msvcmsys)
# This compiler won't grok '-c -o', but also, the minuso test has
# not run yet. These depmodes are late enough in the game, and
# so weak that their functioning should not be impacted.
am__obj=conftest.${OBJEXT-o}
am__minus_obj=
;;
none) break ;;
esac
if depmode=$depmode \
source=sub/conftest.c object=$am__obj \
depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \
$SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \
>/dev/null 2>conftest.err &&
grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 &&
grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 &&
grep $am__obj sub/conftest.Po > /dev/null 2>&1 &&
${MAKE-make} -s -f confmf > /dev/null 2>&1; then
# icc doesn't choke on unknown options, it will just issue warnings
# or remarks (even with -Werror). So we grep stderr for any message
# that says an option was ignored or not supported.
# When given -MP, icc 7.0 and 7.1 complain thus:
# icc: Command line warning: ignoring option '-M'; no argument required
# The diagnosis changed in icc 8.0:
# icc: Command line remark: option '-MP' not supported
if (grep 'ignoring option' conftest.err ||
grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else
am_cv_CCAS_dependencies_compiler_type=$depmode
break
fi
fi
done
cd ..
rm -rf conftest.dir
else
am_cv_CCAS_dependencies_compiler_type=none
fi
;;
esac
fi
{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: result: $am_cv_CCAS_dependencies_compiler_type" >&5
printf '%s\n' "$am_cv_CCAS_dependencies_compiler_type" >&6; }
CCASDEPMODE=depmode=$am_cv_CCAS_dependencies_compiler_type
if
test "x$enable_dependency_tracking" != xno \
&& test "$am_cv_CCAS_dependencies_compiler_type" = gcc3; then
am__fastdepCCAS_TRUE=
am__fastdepCCAS_FALSE='#'
else
am__fastdepCCAS_TRUE='#'
am__fastdepCCAS_FALSE=
fi
{ printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether the compiler is GCC" >&5 { printf '%s\n' "$as_me:${as_lineno-$LINENO}: checking whether the compiler is GCC" >&5
printf %s "checking whether the compiler is GCC... " >&6; } printf %s "checking whether the compiler is GCC... " >&6; }
@@ -5186,7 +5332,16 @@ fi
rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
CFLAGS=$vlibc_save_CFLAGS CFLAGS=$vlibc_save_CFLAGS
VLIBC_CFLAGS="-std=$vlibc_cstd -Wall -Wextra" # -fno-stack-protector: distro GCC builds enable -fstack-protector-strong by
# default, which would make library objects reference __stack_chk_fail — a
# symbol vlibc does not provide yet. Every make-check test binary and every
# -nostdlib -static consumer links against the plain archive, and one
# undefined __stack_chk_fail in it breaks the whole self-hosting link. A
# bootstrap libc must build its own objects without the protector (the stack
# protector runtime lands with the hardening work, after which this flag is
# revisited). Tests compile with the same flag (tests/Makefile.am wiring in
# Makefile.am).
VLIBC_CFLAGS="-std=$vlibc_cstd -Wall -Wextra -fno-stack-protector"
# ---- Compatibility profile ---------------------------------------------- # ---- Compatibility profile ----------------------------------------------
@@ -14607,6 +14762,10 @@ if test -z "${am__fastdepCC_TRUE}" && test -z "${am__fastdepCC_FALSE}"; then
as_fn_error $? "conditional \"am__fastdepCC\" was never defined. as_fn_error $? "conditional \"am__fastdepCC\" was never defined.
Usually this means the macro was only invoked conditionally." "$LINENO" 5 Usually this means the macro was only invoked conditionally." "$LINENO" 5
fi fi
if test -z "${am__fastdepCCAS_TRUE}" && test -z "${am__fastdepCCAS_FALSE}"; then
as_fn_error $? "conditional \"am__fastdepCCAS\" was never defined.
Usually this means the macro was only invoked conditionally." "$LINENO" 5
fi
if test -z "${PROFILE_GE_2_TRUE}" && test -z "${PROFILE_GE_2_FALSE}"; then if test -z "${PROFILE_GE_2_TRUE}" && test -z "${PROFILE_GE_2_FALSE}"; then
as_fn_error $? "conditional \"PROFILE_GE_2\" was never defined. as_fn_error $? "conditional \"PROFILE_GE_2\" was never defined.
Usually this means the macro was only invoked conditionally." "$LINENO" 5 Usually this means the macro was only invoked conditionally." "$LINENO" 5
+13 -1
View File
@@ -26,6 +26,9 @@ AM_INIT_AUTOMAKE([foreign subdir-objects])
# (including clang), so it cannot be used to enforce the single-compiler rule. # (including clang), so it cannot be used to enforce the single-compiler rule.
# Verify the compiler is genuinely GCC, not a compatible one. # Verify the compiler is genuinely GCC, not a compatible one.
AC_PROG_CC AC_PROG_CC
# AM_PROG_AS: library sources include raw x86_64 assembly (the setjmp family
# in src/setjmp/x86_64/*.s), which automake compiles through CCAS/CCASFLAGS.
AM_PROG_AS
AC_MSG_CHECKING([whether the compiler is GCC]) AC_MSG_CHECKING([whether the compiler is GCC])
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([], [ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([], [
#if !defined(__GNUC__) || defined(__clang__) #if !defined(__GNUC__) || defined(__clang__)
@@ -58,7 +61,16 @@ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([], [])],
AC_MSG_ERROR([$CC does not support -std=$vlibc_cstd])]) AC_MSG_ERROR([$CC does not support -std=$vlibc_cstd])])
CFLAGS=$vlibc_save_CFLAGS CFLAGS=$vlibc_save_CFLAGS
AC_SUBST([VLIBC_CFLAGS], ["-std=$vlibc_cstd -Wall -Wextra"]) # -fno-stack-protector: distro GCC builds enable -fstack-protector-strong by
# default, which would make library objects reference __stack_chk_fail — a
# symbol vlibc does not provide yet. Every make-check test binary and every
# -nostdlib -static consumer links against the plain archive, and one
# undefined __stack_chk_fail in it breaks the whole self-hosting link. A
# bootstrap libc must build its own objects without the protector (the stack
# protector runtime lands with the hardening work, after which this flag is
# revisited). Tests compile with the same flag (tests/Makefile.am wiring in
# Makefile.am).
AC_SUBST([VLIBC_CFLAGS], ["-std=$vlibc_cstd -Wall -Wextra -fno-stack-protector"])
# ---- Compatibility profile ---------------------------------------------- # ---- Compatibility profile ----------------------------------------------
# Exactly one of the five profiles is active. They are mutually exclusive; # Exactly one of the five profiles is active. They are mutually exclusive;
+34
View File
@@ -0,0 +1,34 @@
/*
* vlibc — x86_64 _start (todo 3).
*
* The kernel enters the program here with the initial stack holding, in
* order from %rsp: argc, argv[0..argc-1], a NULL argv terminator, the
* environment strings, a NULL envp terminator, and the auxiliary vector.
* The kernel leaves %rsp 16-byte aligned at entry.
*
* _start forwards (main, argc, argv, envp) to __libc_start_main, matching
* vlibc's startup signature (src/start/start.h). The frame pointer is
* zeroed first so the outermost frame terminates backtraces, and %rsp is
* 16-byte aligned at the call site per the SysV AMD64 ABI. __libc_start_main
* never returns (it ends in exit), so the hlt loop below is unreachable; it
* exists only to satisfy the assembler and to fault loudly (hlt in user
* mode raises #GP) if a broken build ever falls through.
*/
.text
.global _start
.type _start, @function
_start:
xor %ebp, %ebp # outermost frame: zero frame pointer
mov %rsp, %r8 # keep the raw initial stack for envp
pop %rsi # rsi = argc
mov %rsp, %rdx # rdx = argv
lea 16(%r8, %rsi, 8), %rcx # rcx = envp = initial rsp + 8 + 8*(argc+1)
lea main(%rip), %rdi # rdi = &main
and $-16, %rsp # ABI: rsp % 16 == 0 at the call site
call __libc_start_main # _Noreturn: routes main's result into exit
1: hlt
jmp 1b
.size _start, . - _start
.section .note.GNU-stack, "", @progbits
+45
View File
@@ -0,0 +1,45 @@
#ifndef VLIBC_ASSERT_H
#define VLIBC_ASSERT_H
/*
* vlibc — <assert.h>.
*
* The assert diagnostic macro. When NDEBUG is defined the macro expands to
* ((void)0) and the expression is not evaluated; otherwise a false result
* calls the library-internal __vlibc_assert_fail(), which writes the
* diagnostic to fd 2 through the raw syscall layer and terminates the
* process with __builtin_trap(). The helper deliberately does not depend on
* abort(), exit() or stdio: those land in later todos, and an assertion
* failure must work even when the runtime is half-initialized.
*
* static_assert is a C23 keyword; for C17 and older this header maps it onto
* _Static_assert, and _Static_assert itself remains usable in every mode.
*
* This header is ISO C core and is present in every profile.
*/
#include <vlibc/features.h>
/* NDEBUG: strip the check entirely, matching the standard. */
#ifdef NDEBUG
#define assert(expr) ((void)0)
#else
/*
* Terminate after writing the failing assertion to fd 2; never returns.
* Implemented in src/internal/assert_fail.c. The name sits in the
* implementation-reserved namespace because it is libc plumbing, not
* public API.
*/
__attribute__((noreturn)) void
__vlibc_assert_fail(const char *expr, const char *file,
int line, // NOLINT(bugprone-reserved-identifier)
const char *func);
#define assert(expr) ((expr) ? (void)0 : __vlibc_assert_fail(#expr, __FILE__, __LINE__, __func__))
#endif
#if !defined(__cplusplus) && (!defined(__STDC_VERSION__) || __STDC_VERSION__ < 202311L)
#define static_assert _Static_assert
#endif
#endif /* VLIBC_ASSERT_H */
+48
View File
@@ -0,0 +1,48 @@
#ifndef VLIBC_CPIO_H
#define VLIBC_CPIO_H
/*
* vlibc — <cpio.h>.
*
* Constants for cpio archive formats (POSIX). Constants only: the file and
* mode bits used in "newc"/odc archive headers, and the octal magic string
* of the odc format. The newc magic ("070701") is not standard and is
* deliberately omitted.
*/
#include <vlibc/features.h>
#if VLIBC_HAS_HEADER_CPIO_H
/* Octal magic of the odc ("old binary") archive format. */
#define MAGIC "070707"
/* Mode bits (permissions) of an archived entry. */
#define C_IRUSR 0000400
#define C_IWUSR 0000200
#define C_IXUSR 0000100
#define C_IRGRP 0000040
#define C_IWGRP 0000020
#define C_IXGRP 0000010
#define C_IROTH 0000004
#define C_IWOTH 0000002
#define C_IXOTH 0000001
/* Set-id and sticky bits of an archived entry. */
#define C_ISUID 0004000
#define C_ISGID 0002000
#define C_ISVTX 0001000
/* File type bits of an archived entry. */
#define C_ISDIR 0040000
#define C_ISFIFO 0010000
#define C_ISREG 0100000
#define C_ISBLK 0060000
#define C_ISCHR 0020000
#define C_ISCTG 0110000
#define C_ISLNK 0120000
#define C_ISSOCK 0140000
#endif /* VLIBC_HAS_HEADER_CPIO_H */
#endif /* VLIBC_CPIO_H */
+164
View File
@@ -0,0 +1,164 @@
#ifndef VLIBC_CTYPE_H
#define VLIBC_CTYPE_H
/*
* vlibc — <ctype.h>.
*
* Character classification and case mapping. Every function takes an int
* that represents an unsigned char value or EOF (-1). The implementation
* always casts the argument to unsigned char before any table lookup, so a
* negative char value can never index out of bounds; EOF classifies false
* in every is* function, and tolower/toupper pass it (and every non-letter)
* through unchanged. All results are for the "C" locale.
*
* Levels are cumulative (see include/vlibc/features.h):
* Level 1 (onlyposix): ISO C — the classification and case-mapping
* functions.
* Level 2 (muslmimic): XSI — isascii, toascii.
*
* This header includes <vlibc/features.h> itself, so the gates below always
* see the configured VLIBC_LEVEL even when the caller included no vlibc
* header first. The classification functions return exactly 1 when the
* character is in the class and 0 otherwise, never any other nonzero value.
*/
#include <vlibc/features.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Level 1: ISO C (always present). */
/*
* Nonzero when c is alphanumeric: a letter or a decimal digit.
* pure: reads the classification table, no side effects.
*/
__attribute__((pure)) int
isalnum(int c);
/*
* Nonzero when c is a letter.
* pure: reads the classification table, no side effects.
*/
__attribute__((pure)) int
isalpha(int c);
/*
* Nonzero when c is blank: space or horizontal tab.
* pure: reads the classification table, no side effects.
*/
__attribute__((pure)) int
isblank(int c);
/*
* Nonzero when c is a control character (0x00..0x1f or 0x7f).
* pure: reads the classification table, no side effects.
*/
__attribute__((pure)) int
iscntrl(int c);
/*
* Nonzero when c is a decimal digit.
* const: bit arithmetic on the argument alone (also matches GCC's const
* builtin declaration for isdigit).
*/
__attribute__((const)) int
isdigit(int c);
/*
* Nonzero when c has a visible representation (printable, except space).
* pure: reads the classification table, no side effects.
*/
__attribute__((pure)) int
isgraph(int c);
/*
* Nonzero when c is a lowercase letter.
* pure: reads the classification table, no side effects.
*/
__attribute__((pure)) int
islower(int c);
/*
* Nonzero when c is printable (0x20..0x7e).
* pure: the implementation is bit arithmetic on the argument alone, but
* GCC's isprint builtin is declared pure, and a const redeclaration would
* conflict with it.
*/
__attribute__((pure)) int
isprint(int c);
/*
* Nonzero when c is a punctuation character: printable and not alphanumeric
* and not space.
* pure: reads the classification table, no side effects.
*/
__attribute__((pure)) int
ispunct(int c);
/*
* Nonzero when c is white space (space, form feed, newline, carriage
* return, horizontal or vertical tab).
* pure: reads the classification table, no side effects.
*/
__attribute__((pure)) int
isspace(int c);
/*
* Nonzero when c is an uppercase letter.
* pure: reads the classification table, no side effects.
*/
__attribute__((pure)) int
isupper(int c);
/*
* Nonzero when c is a hexadecimal digit (0-9, a-f, A-F).
* const: bit arithmetic on the argument alone (also matches GCC's const
* builtin declaration for isxdigit).
*/
__attribute__((const)) int
isxdigit(int c);
/*
* Return the lowercase counterpart of c when c is an uppercase letter
* ('A'..'Z' in the "C" locale); otherwise return c unchanged, including
* EOF.
* pure: reads the classification table, no side effects.
*/
__attribute__((pure)) int
tolower(int c);
/*
* Return the uppercase counterpart of c when c is a lowercase letter
* ('a'..'z' in the "C" locale); otherwise return c unchanged, including
* EOF.
* pure: reads the classification table, no side effects.
*/
__attribute__((pure)) int
toupper(int c);
#if VLIBC_LEVEL_GE(2)
/* Level 2 (muslmimic): XSI. */
/*
* Nonzero when c is a 7-bit ASCII value (0..127). EOF and negative values
* are outside the range.
* const: depends only on its argument.
*/
__attribute__((const)) int
isascii(int c);
/*
* Clear every bit above the low 7 of c (c & 0x7f).
* const: depends only on its argument.
*/
__attribute__((const)) int
toascii(int c);
#endif /* VLIBC_LEVEL_GE(2) */
#ifdef __cplusplus
}
#endif
#endif /* VLIBC_CTYPE_H */
+229
View File
@@ -0,0 +1,229 @@
#ifndef VLIBC_ERRNO_H
#define VLIBC_ERRNO_H
/*
* vlibc — <errno.h>.
*
* errno is per-thread state. It lives in a slot of the thread control block
* (TCB), addressed relative to the x86_64 FS thread pointer; there is no
* process-global errno object and no compiler-managed TLS (`__thread`) here.
* The errno macro below expands to a dereference of __errno_location(), the
* implementation-reserved accessor that returns the address of the calling
* thread's errno slot. The offset of that slot inside the TCB is the ABI
* constant VLIBC_TCB_ERRNO_OFF, owned by the internal TCB/ABI headers (see
* src/internal/errno.h) — this public header is the single canonical home
* for the errno macro, the __errno_location() declaration, and the E*
* constant table, so internal code includes it instead of keeping a copy.
*
* Levels are cumulative (see include/vlibc/features.h):
* Level 1 (onlyposix): POSIX base — strerror.
* Level 2 (muslmimic): XSI — strerror_r (the int-returning flavor; the
* GNU char*-returning variant is level 3+ and is
* deliberately not provided).
*
* This header includes <vlibc/features.h> itself, so the gates below always
* see the configured VLIBC_LEVEL even when the caller included no vlibc
* header first, and <stddef.h> for size_t.
*/
#include <vlibc/features.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/*
* Return the address of the calling thread's errno slot.
*
* Requires the thread pointer (FS) to be initialized to a TCB whose first
* word is the TCB's own address — the startup todo owns that bootstrap and
* sets it before any code that can touch errno runs. This function itself
* performs no setup and no other TCB access.
*
* The name sits in the implementation-reserved namespace deliberately: it is
* this libc's private errno accessor, not public API.
*/
int *
__errno_location(void); // NOLINT(bugprone-reserved-identifier)
/* The conventional errno lvalue; resolves to the caller's TCB slot. */
#define errno (*__errno_location())
/*
* Error numbers: the Linux errno ABI (asm-generic/errno-base.h plus
* asm-generic/errno.h, which is the table x86_64 uses), transcribed as
* kernel-ABI facts. These values are fixed and shared with the kernel.
* Values 41 and 58 do not exist in the ABI (EWOULDBLOCK and EDEADLOCK are
* aliases of EAGAIN and EDEADLK); EHWPOISON is the last defined value.
*/
#define EPERM 1 /* Operation not permitted */
#define ENOENT 2 /* No such file or directory */
#define ESRCH 3 /* No such process */
#define EINTR 4 /* Interrupted system call */
#define EIO 5 /* I/O error */
#define ENXIO 6 /* No such device or address */
#define E2BIG 7 /* Argument list too long */
#define ENOEXEC 8 /* Exec format error */
#define EBADF 9 /* Bad file descriptor */
#define ECHILD 10 /* No child processes */
#define EAGAIN 11 /* Try again */
#define ENOMEM 12 /* Out of memory */
#define EACCES 13 /* Permission denied */
#define EFAULT 14 /* Bad address */
#define ENOTBLK 15 /* Block device required */
#define EBUSY 16 /* Device or resource busy */
#define EEXIST 17 /* File exists */
#define EXDEV 18 /* Cross-device link */
#define ENODEV 19 /* No such device */
#define ENOTDIR 20 /* Not a directory */
#define EISDIR 21 /* Is a directory */
#define EINVAL 22 /* Invalid argument */
#define ENFILE 23 /* File table overflow */
#define EMFILE 24 /* Too many open files */
#define ENOTTY 25 /* Not a typewriter */
#define ETXTBSY 26 /* Text file busy */
#define EFBIG 27 /* File too large */
#define ENOSPC 28 /* No space left on device */
#define ESPIPE 29 /* Illegal seek */
#define EROFS 30 /* Read-only file system */
#define EMLINK 31 /* Too many links */
#define EPIPE 32 /* Broken pipe */
#define EDOM 33 /* Math argument out of domain of func */
#define ERANGE 34 /* Math result not representable */
#define EDEADLK 35 /* Resource deadlock would occur */
#define ENAMETOOLONG 36 /* File name too long */
#define ENOLCK 37 /* No record locks available */
#define ENOSYS 38 /* Invalid system call number */
#define ENOTEMPTY 39 /* Directory not empty */
#define ELOOP 40 /* Too many symbolic links encountered */
#define EWOULDBLOCK EAGAIN /* Operation would block */
#define ENOMSG 42 /* No message of desired type */
#define EIDRM 43 /* Identifier removed */
#define ECHRNG 44 /* Channel number out of range */
#define EL2NSYNC 45 /* Level 2 not synchronized */
#define EL3HLT 46 /* Level 3 halted */
#define EL3RST 47 /* Level 3 reset */
#define ELNRNG 48 /* Link number out of range */
#define EUNATCH 49 /* Protocol driver not attached */
#define ENOCSI 50 /* No CSI structure available */
#define EL2HLT 51 /* Level 2 halted */
#define EBADE 52 /* Invalid exchange */
#define EBADR 53 /* Invalid request descriptor */
#define EXFULL 54 /* Exchange full */
#define ENOANO 55 /* No anode */
#define EBADRQC 56 /* Invalid request code */
#define EBADSLT 57 /* Invalid slot */
#define EDEADLOCK EDEADLK /* File locking deadlock error */
#define EBFONT 59 /* Bad font file format */
#define ENOSTR 60 /* Device not a stream */
#define ENODATA 61 /* No data available */
#define ETIME 62 /* Timer expired */
#define ENOSR 63 /* Out of streams resources */
#define ENONET 64 /* Machine is not on the network */
#define ENOPKG 65 /* Package not installed */
#define EREMOTE 66 /* Object is remote */
#define ENOLINK 67 /* Link has been severed */
#define EADV 68 /* Advertise error */
#define ESRMNT 69 /* Srmount error */
#define ECOMM 70 /* Communication error on send */
#define EPROTO 71 /* Protocol error */
#define EMULTIHOP 72 /* Multihop attempted */
#define EDOTDOT 73 /* RFS specific error */
#define EBADMSG 74 /* Not a data message */
#define EOVERFLOW 75 /* Value too large for defined data type */
#define ENOTUNIQ 76 /* Name not unique on network */
#define EBADFD 77 /* File descriptor in bad state */
#define EREMCHG 78 /* Remote address changed */
#define ELIBACC 79 /* Can not access a needed shared library */
#define ELIBBAD 80 /* Accessing a corrupted shared library */
#define ELIBSCN 81 /* .lib section in a.out corrupted */
#define ELIBMAX 82 /* Attempting to link in too many shared libraries */
#define ELIBEXEC 83 /* Cannot exec a shared library directly */
#define EILSEQ 84 /* Illegal byte sequence */
#define ERESTART 85 /* Interrupted system call should be restarted */
#define ESTRPIPE 86 /* Streams pipe error */
#define EUSERS 87 /* Too many users */
#define ENOTSOCK 88 /* Socket operation on non-socket */
#define EDESTADDRREQ 89 /* Destination address required */
#define EMSGSIZE 90 /* Message too long */
#define EPROTOTYPE 91 /* Protocol wrong type for socket */
#define ENOPROTOOPT 92 /* Protocol not available */
#define EPROTONOSUPPORT 93 /* Protocol not supported */
#define ESOCKTNOSUPPORT 94 /* Socket type not supported */
#define EOPNOTSUPP 95 /* Operation not supported on transport endpoint */
#define EPFNOSUPPORT 96 /* Protocol family not supported */
#define EAFNOSUPPORT 97 /* Address family not supported by protocol */
#define EADDRINUSE 98 /* Address already in use */
#define EADDRNOTAVAIL 99 /* Cannot assign requested address */
#define ENETDOWN 100 /* Network is down */
#define ENETUNREACH 101 /* Network is unreachable */
#define ENETRESET 102 /* Network dropped connection because of reset */
#define ECONNABORTED 103 /* Software caused connection abort */
#define ECONNRESET 104 /* Connection reset by peer */
#define ENOBUFS 105 /* No buffer space available */
#define EISCONN 106 /* Transport endpoint is already connected */
#define ENOTCONN 107 /* Transport endpoint is not connected */
#define ESHUTDOWN 108 /* Cannot send after transport endpoint shutdown */
#define ETOOMANYREFS 109 /* Too many references: cannot splice */
#define ETIMEDOUT 110 /* Connection timed out */
#define ECONNREFUSED 111 /* Connection refused */
#define EHOSTDOWN 112 /* Host is down */
#define EHOSTUNREACH 113 /* No route to host */
#define EALREADY 114 /* Operation already in progress */
#define EINPROGRESS 115 /* Operation now in progress */
#define ESTALE 116 /* Stale file handle */
#define EUCLEAN 117 /* Structure needs cleaning */
#define ENOTNAM 118 /* Not a XENIX named type file */
#define ENAVAIL 119 /* No XENIX semaphores available */
#define EISNAM 120 /* Is a named type file */
#define EREMOTEIO 121 /* Remote I/O error */
#define EDQUOT 122 /* Quota exceeded */
#define ENOMEDIUM 123 /* No medium found */
#define EMEDIUMTYPE 124 /* Wrong medium type */
#define ECANCELED 125 /* Operation canceled */
#define ENOKEY 126 /* Required key not available */
#define EKEYEXPIRED 127 /* Key has expired */
#define EKEYREVOKED 128 /* Key has been revoked */
#define EKEYREJECTED 129 /* Key was rejected by service */
#define EOWNERDEAD 130 /* Owner died */
#define ENOTRECOVERABLE 131 /* State not recoverable */
#define ERFKILL 132 /* Operation not possible due to RF-kill */
#define EHWPOISON 133 /* Memory page has hardware error */
/*
* Return a pointer to the message string for errnum. The string for a known
* value points to immutable static storage and is safe to keep across calls;
* an unknown value yields "Unknown error <errnum>", formatted into shared
* static storage whose contents a later call may overwrite (unknown errno
* values are a rare diagnostic path; see strerror.c). Never returns NULL.
*
* No intent attribute: the unknown-value path writes shared storage.
*/
char *
strerror(int errnum);
#if VLIBC_LEVEL_GE(2)
/* Level 2 (muslmimic): XSI. */
/*
* XSI strerror_r: copy the message for errnum into buf, truncated to buflen
* and always NUL-terminated when buflen > 0, and return 0 — including for
* unknown errnum values, whose text is "Unknown error <errnum>". When buflen
* is 0 nothing is written and 0 is returned; when buf is NULL and buflen > 0,
* EINVAL is returned. The GNU char*-returning variant is deliberately not
* provided.
*
* No intent attribute: it writes memory.
*/
int
strerror_r(int errnum, char *buf, size_t buflen);
#endif /* VLIBC_LEVEL_GE(2) */
#ifdef __cplusplus
}
#endif
#endif /* VLIBC_ERRNO_H */
+79
View File
@@ -0,0 +1,79 @@
#ifndef VLIBC_FLOAT_H
#define VLIBC_FLOAT_H
/*
* vlibc — <float.h>.
*
* Characteristics of floating-point types (C23). Every value is derived from
* compiler predefined macros (__FLT_MANT_DIG__, __DBL_MAX__, ...), so this
* header is fully self-contained. The values reflect the x86_64 SSE2
* environment: FLT_RADIX 2, IEC 60559 semantics for float, double and long
* double (the 80-bit extended type).
*
* FLT_ROUNDS is 1 (round to nearest): vlibc does not implement the floating
* environment yet, so the default rounding mode is always active. The fenv
* todo (#42) is expected to replace this with a live read of the x87/MXCSR
* control word.
*
* This header is ISO C core and is present in every profile.
*/
#include <vlibc/features.h>
/* Base of all floating-point types. */
#define FLT_RADIX __FLT_RADIX__
/* Current rounding mode (see the header comment). */
#define FLT_ROUNDS 1
/* Evaluation method (x86_64: all operations use the type's own range). */
#define FLT_EVAL_METHOD __FLT_EVAL_METHOD__
/* IEC 60559 conformance of each type (C23). */
#define FLT_IS_IEC_60559 __FLT_IS_IEC_60559__
#define DBL_IS_IEC_60559 __DBL_IS_IEC_60559__
#define LDBL_IS_IEC_60559 __LDBL_IS_IEC_60559__
/* float. */
#define FLT_MANT_DIG __FLT_MANT_DIG__
#define FLT_DECIMAL_DIG __FLT_DECIMAL_DIG__
#define FLT_DIG __FLT_DIG__
#define FLT_MIN_EXP __FLT_MIN_EXP__
#define FLT_MIN_10_EXP __FLT_MIN_10_EXP__
#define FLT_MAX_EXP __FLT_MAX_EXP__
#define FLT_MAX_10_EXP __FLT_MAX_10_EXP__
#define FLT_MAX __FLT_MAX__
#define FLT_NORM_MAX __FLT_NORM_MAX__
#define FLT_EPSILON __FLT_EPSILON__
#define FLT_MIN __FLT_MIN__
#define FLT_TRUE_MIN __FLT_DENORM_MIN__
/* double. */
#define DBL_MANT_DIG __DBL_MANT_DIG__
#define DBL_DECIMAL_DIG __DBL_DECIMAL_DIG__
#define DBL_DIG __DBL_DIG__
#define DBL_MIN_EXP __DBL_MIN_EXP__
#define DBL_MIN_10_EXP __DBL_MIN_10_EXP__
#define DBL_MAX_EXP __DBL_MAX_EXP__
#define DBL_MAX_10_EXP __DBL_MAX_10_EXP__
#define DBL_MAX __DBL_MAX__
#define DBL_NORM_MAX __DBL_NORM_MAX__
#define DBL_EPSILON __DBL_EPSILON__
#define DBL_MIN __DBL_MIN__
#define DBL_TRUE_MIN __DBL_DENORM_MIN__
/* long double (x86_64: 80-bit extended). */
#define LDBL_MANT_DIG __LDBL_MANT_DIG__
#define LDBL_DECIMAL_DIG __LDBL_DECIMAL_DIG__
#define LDBL_DIG __LDBL_DIG__
#define LDBL_MIN_EXP __LDBL_MIN_EXP__
#define LDBL_MIN_10_EXP __LDBL_MIN_10_EXP__
#define LDBL_MAX_EXP __LDBL_MAX_EXP__
#define LDBL_MAX_10_EXP __LDBL_MAX_10_EXP__
#define LDBL_MAX __LDBL_MAX__
#define LDBL_NORM_MAX __LDBL_NORM_MAX__
#define LDBL_EPSILON __LDBL_EPSILON__
#define LDBL_MIN __LDBL_MIN__
#define LDBL_TRUE_MIN __LDBL_DENORM_MIN__
#endif /* VLIBC_FLOAT_H */
+32
View File
@@ -0,0 +1,32 @@
#ifndef VLIBC_ISO646_H
#define VLIBC_ISO646_H
/*
* vlibc — <iso646.h>.
*
* Alternative spellings for the bitwise and logical operators. C23 removed
* them from the language as alternative tokens, so this header provides them
* as macros; in C17 the spellings are lexed as tokens and the macros never
* fire, which makes the definitions harmless in both modes. In C++ they are
* part of the language, so no macros are defined there.
*
* This header is ISO C core and is present in every profile.
*/
#include <vlibc/features.h>
#ifndef __cplusplus
#define and &&
#define and_eq &=
#define bitand &
#define bitor |
#define compl ~
#define not !
#define not_eq !=
#define or ||
#define or_eq |=
#define xor ^
#define xor_eq ^=
#endif
#endif /* VLIBC_ISO646_H */
+70
View File
@@ -0,0 +1,70 @@
#ifndef VLIBC_LIMITS_H
#define VLIBC_LIMITS_H
/*
* vlibc — <limits.h>.
*
* Sizes of integer types (C23). Every value is derived from compiler
* predefined macros (__CHAR_BIT__, __SCHAR_MAX__, __SIZEOF_INT__, ...), so
* this header is fully self-contained. char is signed on x86_64; the
* __CHAR_UNSIGNED__ branch keeps the header correct if that ever changes.
*
* This header is ISO C core and is present in every profile.
*/
#include <vlibc/features.h>
/* Number of bits in the smallest addressable object. */
#define CHAR_BIT __CHAR_BIT__
/* Maximum number of bytes in a multibyte character (UTF-8 over 32-bit
* wchar_t needs at most 4). */
#define MB_LEN_MAX 4
/* Limits of the plain char types. */
#define SCHAR_MIN (-__SCHAR_MAX__ - 1)
#define SCHAR_MAX __SCHAR_MAX__
#define UCHAR_MAX (__SCHAR_MAX__ * 2 + 1)
#if defined(__CHAR_UNSIGNED__)
#define CHAR_MIN 0
#define CHAR_MAX UCHAR_MAX
#else
#define CHAR_MIN SCHAR_MIN
#define CHAR_MAX SCHAR_MAX
#endif
/* Limits of the short types. */
#define SHRT_MIN (-__SHRT_MAX__ - 1)
#define SHRT_MAX __SHRT_MAX__
#define USHRT_MAX (__SHRT_MAX__ * 2 + 1)
/* Limits of the int types. */
#define INT_MIN (-__INT_MAX__ - 1)
#define INT_MAX __INT_MAX__
#define UINT_MAX (__INT_MAX__ * 2U + 1U)
/* Limits of the long types (x86_64 LP64: 64-bit). */
#define LONG_MIN (-__LONG_MAX__ - 1L)
#define LONG_MAX __LONG_MAX__
#define ULONG_MAX (__LONG_MAX__ * 2UL + 1UL)
/* Limits of the long long types. */
#define LLONG_MIN (-__LONG_LONG_MAX__ - 1LL)
#define LLONG_MAX __LONG_LONG_MAX__
#define ULLONG_MAX (__LONG_LONG_MAX__ * 2ULL + 1ULL)
/* Width of each integer type, in bits (C23). */
#define BOOL_WIDTH 1
#define CHAR_WIDTH __CHAR_BIT__
#define SCHAR_WIDTH __CHAR_BIT__
#define UCHAR_WIDTH __CHAR_BIT__
#define SHRT_WIDTH (__SIZEOF_SHORT__ * __CHAR_BIT__)
#define USHRT_WIDTH (__SIZEOF_SHORT__ * __CHAR_BIT__)
#define INT_WIDTH (__SIZEOF_INT__ * __CHAR_BIT__)
#define UINT_WIDTH (__SIZEOF_INT__ * __CHAR_BIT__)
#define LONG_WIDTH (__SIZEOF_LONG__ * __CHAR_BIT__)
#define ULONG_WIDTH (__SIZEOF_LONG__ * __CHAR_BIT__)
#define LLONG_WIDTH (__SIZEOF_LONG_LONG__ * __CHAR_BIT__)
#define ULLONG_WIDTH (__SIZEOF_LONG_LONG__ * __CHAR_BIT__)
#endif /* VLIBC_LIMITS_H */
+117
View File
@@ -0,0 +1,117 @@
#ifndef VLIBC_SETJMP_H
#define VLIBC_SETJMP_H
/*
* vlibc — <setjmp.h>.
*
* Non-local jumps. setjmp(env) saves the calling environment and returns 0;
* longjmp(env, val) makes that setjmp return val, with 0 coerced to 1 so the
* two returns are distinguishable. sigsetjmp/siglongjmp additionally save and
* restore the signal mask when the buffer's saved-mask flag is set.
*
* jmp_buf is an array type (C23 7.13.1 requires it); both types below are
* arrays of unsigned long. The word layout is ABI: the x86_64 assembly in
* src/setjmp/x86_64/{setjmp,longjmp,sigsetjmp,siglongjmp}.s reads and writes
* exactly these slots. This comment is the single authoritative map — keep it
* and those files in sync:
*
* [0] rbx callee-saved general registers (System V AMD64 ABI)
* [1] rbp
* [2] r12
* [3] r13
* [4] r14
* [5] r15
* [6] rsp the caller's stack pointer (just past the return address)
* [7] rip the return address, where longjmp resumes execution
* [8] mxcsr SSE control/status register (stmxcsr/ldmxcsr)
* [9] x87 cw x87 control word (fnstcw/fldcw)
*
* sigjmp_buf extends the base layout with two more words:
*
* [10] saved-mask flag (1 when the signal mask was saved)
* [11] signal mask (the x86_64 Linux sigset_t: a single 64-bit word)
*
* Because the base layout occupies words 0..9 of both types, the plain
* setjmp/longjmp assembly works verbatim on sigjmp_buf storage as well.
*/
#include <vlibc/features.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Word counts and per-slot indices (see the authoritative map above). */
#define VLIBC_JMP_BUF_WORDS 10
#define VLIBC_JMP_RBX 0
#define VLIBC_JMP_RBP 1
#define VLIBC_JMP_R12 2
#define VLIBC_JMP_R13 3
#define VLIBC_JMP_R14 4
#define VLIBC_JMP_R15 5
#define VLIBC_JMP_RSP 6
#define VLIBC_JMP_RIP 7
#define VLIBC_JMP_MXCSR 8
#define VLIBC_JMP_X87CW 9
#define VLIBC_SIGJMP_BUF_WORDS 12
#define VLIBC_SIGJMP_FLAG 10
#define VLIBC_SIGJMP_MASK 11
typedef unsigned long jmp_buf[VLIBC_JMP_BUF_WORDS];
typedef unsigned long sigjmp_buf[VLIBC_SIGJMP_BUF_WORDS];
/*
* Save the calling environment and return 0; after longjmp(env, val) the
* matching setjmp invocation returns val. A macro (C23 7.13.1: usable without
* a prototype in scope); it expands to _setjmp(env).
*/
#define setjmp(env) _setjmp(env)
/*
* Like setjmp, plus the current signal mask is saved into env when savemask is
* nonzero (siglongjmp then restores it). Macro expanding to
* __sigsetjmp(env, savemask).
*/
#define sigsetjmp(env, savemask) __sigsetjmp(env, savemask)
/*
* The save behind the setjmp macro; identical semantics (POSIX _setjmp).
*/
int
_setjmp(jmp_buf env); // NOLINT(bugprone-reserved-identifier)
/*
* Restore the environment saved by _setjmp/setjmp and make it return val, with
* 0 coerced to 1. Does not return.
*/
__attribute__((noreturn)) void
_longjmp(jmp_buf env, int val); // NOLINT(bugprone-reserved-identifier)
/*
* ISO C longjmp: same as _longjmp.
*/
__attribute__((noreturn)) void
longjmp(jmp_buf env, int val);
/*
* The save behind the sigsetjmp macro. Implementation-reserved name, so the
* public API keeps only the macro; declared because the macro expansion must
* have a prototype in scope (C23 removed implicit declarations).
*/
int
__sigsetjmp(sigjmp_buf env, int savemask); // NOLINT(bugprone-reserved-identifier)
/*
* Restore the environment saved by sigsetjmp and make it return val, with 0
* coerced to 1; when the buffer's saved-mask flag is set, the signal mask is
* restored first. Does not return.
*/
__attribute__((noreturn)) void
siglongjmp(sigjmp_buf env, int val);
#ifdef __cplusplus
}
#endif
#endif /* VLIBC_SETJMP_H */
+27
View File
@@ -0,0 +1,27 @@
#ifndef VLIBC_STDALIGN_H
#define VLIBC_STDALIGN_H
/*
* vlibc — <stdalign.h>.
*
* In C23 alignas and alignof are keywords, so this header defines only the
* feature-test macros __alignas_is_defined and __alignof_is_defined. In C17
* (and older) it additionally maps them onto the _Alignas/_Alignof keywords.
* In C++ the two names are keywords as well.
*
* This header is ISO C core and is present in every profile.
*/
#include <vlibc/features.h>
#ifndef __cplusplus
#if !defined(__STDC_VERSION__) || __STDC_VERSION__ < 202311L
#define alignas _Alignas
#define alignof _Alignof
#endif
#endif
#define __alignas_is_defined 1
#define __alignof_is_defined 1
#endif /* VLIBC_STDALIGN_H */
+24
View File
@@ -0,0 +1,24 @@
#ifndef VLIBC_STDARG_H
#define VLIBC_STDARG_H
/*
* vlibc — <stdarg.h>.
*
* Variable argument lists. The entire header defers to the compiler's
* builtin support: __builtin_va_list names the ABI's va_list layout and the
* __builtin_va_* entry points manipulate it. Nothing here depends on a
* system header.
*
* This header is ISO C core and is present in every profile.
*/
#include <vlibc/features.h>
typedef __builtin_va_list va_list;
#define va_start(ap, last) __builtin_va_start((ap), (last))
#define va_end(ap) __builtin_va_end((ap))
#define va_arg(ap, type) __builtin_va_arg((ap), type)
#define va_copy(dst, src) __builtin_va_copy((dst), (src))
#endif /* VLIBC_STDARG_H */
+186
View File
@@ -0,0 +1,186 @@
#ifndef VLIBC_STDATOMIC_H
#define VLIBC_STDATOMIC_H
/*
* vlibc — <stdatomic.h>.
*
* Atomics (C23). All operations defer to the GCC __atomic_* builtins, the
* same idiom src/internal/atomic.h uses: on x86_64 every standard atomic
* type here is lock-free in hardware, so the builtins inline to plain
* instructions and never call into libatomic. The header is fully
* self-contained (no system stdatomic.h) and compiles as C17-style code too,
* since _Atomic remains a keyword.
*
* The atomic_* operations are function-like macros over the polymorphic
* builtins (C23 allows any atomic operation to be implemented as a macro),
* which keeps them type-generic across every atomic_* type without a
* _Generic dispatch table.
*
* This header is ISO C core and is present in every profile.
*/
#include <vlibc/features.h>
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Memory ordering. The enum values are the GCC __ATOMIC_* constants. */
typedef enum
{
memory_order_relaxed = __ATOMIC_RELAXED,
memory_order_consume = __ATOMIC_CONSUME,
memory_order_acquire = __ATOMIC_ACQUIRE,
memory_order_release = __ATOMIC_RELEASE,
memory_order_acq_rel = __ATOMIC_ACQ_REL,
memory_order_seq_cst = __ATOMIC_SEQ_CST
} memory_order;
/* Lock-free guarantees for each type (0: never, 1: sometimes, 2: always). */
#define ATOMIC_BOOL_LOCK_FREE __GCC_ATOMIC_BOOL_LOCK_FREE
#define ATOMIC_CHAR_LOCK_FREE __GCC_ATOMIC_CHAR_LOCK_FREE
#define ATOMIC_CHAR16_T_LOCK_FREE __GCC_ATOMIC_CHAR16_T_LOCK_FREE
#define ATOMIC_CHAR32_T_LOCK_FREE __GCC_ATOMIC_CHAR32_T_LOCK_FREE
#define ATOMIC_WCHAR_T_LOCK_FREE __GCC_ATOMIC_WCHAR_T_LOCK_FREE
#define ATOMIC_SHORT_LOCK_FREE __GCC_ATOMIC_SHORT_LOCK_FREE
#define ATOMIC_INT_LOCK_FREE __GCC_ATOMIC_INT_LOCK_FREE
#define ATOMIC_LONG_LOCK_FREE __GCC_ATOMIC_LONG_LOCK_FREE
#define ATOMIC_LLONG_LOCK_FREE __GCC_ATOMIC_LLONG_LOCK_FREE
#define ATOMIC_POINTER_LOCK_FREE __GCC_ATOMIC_POINTER_LOCK_FREE
/* Atomic scalar types. */
typedef _Atomic _Bool atomic_bool;
typedef _Atomic char atomic_char;
typedef _Atomic signed char atomic_schar;
typedef _Atomic unsigned char atomic_uchar;
typedef _Atomic short atomic_short;
typedef _Atomic unsigned short atomic_ushort;
typedef _Atomic int atomic_int;
typedef _Atomic unsigned int atomic_uint;
typedef _Atomic long atomic_long;
typedef _Atomic unsigned long atomic_ulong;
typedef _Atomic long long atomic_llong;
typedef _Atomic unsigned long long atomic_ullong;
typedef _Atomic __CHAR16_TYPE__ atomic_char16_t;
typedef _Atomic __CHAR32_TYPE__ atomic_char32_t;
typedef _Atomic __WCHAR_TYPE__ atomic_wchar_t;
typedef _Atomic int_least8_t atomic_int_least8_t;
typedef _Atomic uint_least8_t atomic_uint_least8_t;
typedef _Atomic int_least16_t atomic_int_least16_t;
typedef _Atomic uint_least16_t atomic_uint_least16_t;
typedef _Atomic int_least32_t atomic_int_least32_t;
typedef _Atomic uint_least32_t atomic_uint_least32_t;
typedef _Atomic int_least64_t atomic_int_least64_t;
typedef _Atomic uint_least64_t atomic_uint_least64_t;
typedef _Atomic int_fast8_t atomic_int_fast8_t;
typedef _Atomic uint_fast8_t atomic_uint_fast8_t;
typedef _Atomic int_fast16_t atomic_int_fast16_t;
typedef _Atomic uint_fast16_t atomic_uint_fast16_t;
typedef _Atomic int_fast32_t atomic_int_fast32_t;
typedef _Atomic uint_fast32_t atomic_uint_fast32_t;
typedef _Atomic int_fast64_t atomic_int_fast64_t;
typedef _Atomic uint_fast64_t atomic_uint_fast64_t;
typedef _Atomic intptr_t atomic_intptr_t;
typedef _Atomic uintptr_t atomic_uintptr_t;
typedef _Atomic size_t atomic_size_t;
typedef _Atomic ptrdiff_t atomic_ptrdiff_t;
typedef _Atomic intmax_t atomic_intmax_t;
typedef _Atomic uintmax_t atomic_uintmax_t;
/* The only type the standard guarantees to be lock-free. */
typedef _Atomic unsigned int atomic_flag;
/* Initializer for an atomic_flag object. */
#define ATOMIC_FLAG_INIT 0
/*
* The operations below are macros because the __atomic_* builtins are
* polymorphic: they accept any 1/2/4/8-byte scalar or pointer type, so a
* single macro serves every atomic_* type. The (object) argument is the
* address of the atomic object, matching the standard's function signatures.
*/
/* Initialize the atomic object to value (a relaxed store). */
#define atomic_init(object, value) __atomic_store_n((object), (value), __ATOMIC_RELAXED)
/* Load the current value. */
#define atomic_load(object) __atomic_load_n((object), __ATOMIC_SEQ_CST)
#define atomic_load_explicit(object, order) __atomic_load_n((object), (order))
/* Store value. */
#define atomic_store(object, value) __atomic_store_n((object), (value), __ATOMIC_SEQ_CST)
#define atomic_store_explicit(object, value, order) __atomic_store_n((object), (value), (order))
/* Exchange object's value with desired; returns the previous value. */
#define atomic_exchange(object, desired) __atomic_exchange_n((object), (desired), __ATOMIC_SEQ_CST)
#define atomic_exchange_explicit(object, desired, order) \
__atomic_exchange_n((object), (desired), (order))
/*
* Compare-and-swap: store desired into object iff *object equals *expected.
* On failure *expected is updated with the actual value and the macro
* returns false. The first memorder governs the read-modify-write on
* success, the second the load on failure.
*/
#define atomic_compare_exchange_strong(object, expected, desired) \
__atomic_compare_exchange_n((object), (expected), (desired), 0, __ATOMIC_SEQ_CST, \
__ATOMIC_SEQ_CST)
#define atomic_compare_exchange_strong_explicit(object, expected, desired, success, failure) \
__atomic_compare_exchange_n((object), (expected), (desired), 0, (success), (failure))
#define atomic_compare_exchange_weak(object, expected, desired) \
__atomic_compare_exchange_n((object), (expected), (desired), 1, __ATOMIC_SEQ_CST, \
__ATOMIC_SEQ_CST)
#define atomic_compare_exchange_weak_explicit(object, expected, desired, success, failure) \
__atomic_compare_exchange_n((object), (expected), (desired), 1, (success), (failure))
/* Fetch and add; returns the previous value. */
#define atomic_fetch_add(object, operand) __atomic_fetch_add((object), (operand), __ATOMIC_SEQ_CST)
#define atomic_fetch_add_explicit(object, operand, order) \
__atomic_fetch_add((object), (operand), (order))
/* Fetch and subtract; returns the previous value. */
#define atomic_fetch_sub(object, operand) __atomic_fetch_sub((object), (operand), __ATOMIC_SEQ_CST)
#define atomic_fetch_sub_explicit(object, operand, order) \
__atomic_fetch_sub((object), (operand), (order))
/* Fetch and bitwise-or; returns the previous value. */
#define atomic_fetch_or(object, operand) __atomic_fetch_or((object), (operand), __ATOMIC_SEQ_CST)
#define atomic_fetch_or_explicit(object, operand, order) \
__atomic_fetch_or((object), (operand), (order))
/* Fetch and bitwise-xor; returns the previous value. */
#define atomic_fetch_xor(object, operand) __atomic_fetch_xor((object), (operand), __ATOMIC_SEQ_CST)
#define atomic_fetch_xor_explicit(object, operand, order) \
__atomic_fetch_xor((object), (operand), (order))
/* Fetch and bitwise-and; returns the previous value. */
#define atomic_fetch_and(object, operand) __atomic_fetch_and((object), (operand), __ATOMIC_SEQ_CST)
#define atomic_fetch_and_explicit(object, operand, order) \
__atomic_fetch_and((object), (operand), (order))
/* atomic_flag: test-and-set (returns the previous flag value). */
#define atomic_flag_test_and_set(object) __atomic_test_and_set((object), __ATOMIC_SEQ_CST)
#define atomic_flag_test_and_set_explicit(object, order) __atomic_test_and_set((object), (order))
/* atomic_flag: clear. */
#define atomic_flag_clear(object) __atomic_clear((object), __ATOMIC_SEQ_CST)
#define atomic_flag_clear_explicit(object, order) __atomic_clear((object), (order))
/* Non-zero iff *object is always lock-free. */
#define atomic_is_lock_free(object) __atomic_is_lock_free(sizeof *(object), (object))
/* Fences. */
#define atomic_thread_fence(order) __atomic_thread_fence((order))
#define atomic_signal_fence(order) __atomic_signal_fence((order))
/* Carry a dependency through a load without a synchronization edge. */
#define kill_dependency(y) (y)
#ifdef __cplusplus
}
#endif
#endif /* VLIBC_STDATOMIC_H */
+496
View File
@@ -0,0 +1,496 @@
#ifndef VLIBC_STDBIT_H
#define VLIBC_STDBIT_H
/*
* vlibc — <stdbit.h>.
*
* Bit and byte manipulation (C23 7.18). Every per-width operation is a
* static inline function over the GCC counting builtins
* (__builtin_clz/clzl/clzll, __builtin_ctz/ctzl/ctzll,
* __builtin_popcount/popcountl/popcountll), so no call escapes into the
* library. The type-generic names (stdc_leading_zeros, ...) dispatch through
* _Generic to the per-width functions; signed arguments convert to the
* unsigned type of the same rank, which preserves the bit pattern exactly as
* C23 specifies. Widths match x86_64: int is 32-bit, long is 64-bit.
*
* This header is ISO C core and is present in every profile.
*/
#include <vlibc/features.h>
#include <limits.h>
/* stdc_leading_zeros: number of 0 bits before the first 1 bit, or the width. */
static inline unsigned int
stdc_leading_zeros_uc(unsigned char x)
{
return x == 0 ? 8u : (unsigned int)(__builtin_clz(x) - 24);
}
static inline unsigned int
stdc_leading_zeros_us(unsigned short x)
{
return x == 0 ? 16u : (unsigned int)(__builtin_clz(x) - 16);
}
static inline unsigned int
stdc_leading_zeros_ui(unsigned int x)
{
return x == 0 ? 32u : (unsigned int)__builtin_clz(x);
}
static inline unsigned int
stdc_leading_zeros_ul(unsigned long x)
{
return x == 0 ? 64u : (unsigned int)__builtin_clzl(x);
}
static inline unsigned int
stdc_leading_zeros_ull(unsigned long long x)
{
return x == 0 ? 64u : (unsigned int)__builtin_clzll(x);
}
/* stdc_leading_ones: number of 1 bits before the first 0 bit, or the width. */
static inline unsigned int
stdc_leading_ones_uc(unsigned char x)
{
return x == UCHAR_MAX ? 8u : (unsigned int)(__builtin_clz((unsigned char)~x) - 24);
}
static inline unsigned int
stdc_leading_ones_us(unsigned short x)
{
return x == USHRT_MAX ? 16u : (unsigned int)(__builtin_clz((unsigned short)~x) - 16);
}
static inline unsigned int
stdc_leading_ones_ui(unsigned int x)
{
return x == UINT_MAX ? 32u : (unsigned int)__builtin_clz(~x);
}
static inline unsigned int
stdc_leading_ones_ul(unsigned long x)
{
return x == ULONG_MAX ? 64u : (unsigned int)__builtin_clzl(~x);
}
static inline unsigned int
stdc_leading_ones_ull(unsigned long long x)
{
return x == ULLONG_MAX ? 64u : (unsigned int)__builtin_clzll(~x);
}
/* stdc_trailing_zeros: number of 0 bits after the last 1 bit, or the width. */
static inline unsigned int
stdc_trailing_zeros_uc(unsigned char x)
{
return x == 0 ? 8u : (unsigned int)__builtin_ctz(x);
}
static inline unsigned int
stdc_trailing_zeros_us(unsigned short x)
{
return x == 0 ? 16u : (unsigned int)__builtin_ctz(x);
}
static inline unsigned int
stdc_trailing_zeros_ui(unsigned int x)
{
return x == 0 ? 32u : (unsigned int)__builtin_ctz(x);
}
static inline unsigned int
stdc_trailing_zeros_ul(unsigned long x)
{
return x == 0 ? 64u : (unsigned int)__builtin_ctzl(x);
}
static inline unsigned int
stdc_trailing_zeros_ull(unsigned long long x)
{
return x == 0 ? 64u : (unsigned int)__builtin_ctzll(x);
}
/* stdc_trailing_ones: number of 1 bits after the last 0 bit, or the width. */
static inline unsigned int
stdc_trailing_ones_uc(unsigned char x)
{
return x == UCHAR_MAX ? 8u : (unsigned int)__builtin_ctz((unsigned char)~x);
}
static inline unsigned int
stdc_trailing_ones_us(unsigned short x)
{
return x == USHRT_MAX ? 16u : (unsigned int)__builtin_ctz((unsigned short)~x);
}
static inline unsigned int
stdc_trailing_ones_ui(unsigned int x)
{
return x == UINT_MAX ? 32u : (unsigned int)__builtin_ctz(~x);
}
static inline unsigned int
stdc_trailing_ones_ul(unsigned long x)
{
return x == ULONG_MAX ? 64u : (unsigned int)__builtin_ctzl(~x);
}
static inline unsigned int
stdc_trailing_ones_ull(unsigned long long x)
{
return x == ULLONG_MAX ? 64u : (unsigned int)__builtin_ctzll(~x);
}
/*
* stdc_first_*: index (from the most significant bit) of the first zero/one
* bit, or the width when no such bit exists. By construction, the index of
* the first zero from the top equals the count of leading ones, and so on.
*/
static inline unsigned int
stdc_first_leading_zero_uc(unsigned char x)
{
return stdc_leading_ones_uc(x);
}
static inline unsigned int
stdc_first_leading_zero_us(unsigned short x)
{
return stdc_leading_ones_us(x);
}
static inline unsigned int
stdc_first_leading_zero_ui(unsigned int x)
{
return stdc_leading_ones_ui(x);
}
static inline unsigned int
stdc_first_leading_zero_ul(unsigned long x)
{
return stdc_leading_ones_ul(x);
}
static inline unsigned int
stdc_first_leading_zero_ull(unsigned long long x)
{
return stdc_leading_ones_ull(x);
}
static inline unsigned int
stdc_first_leading_one_uc(unsigned char x)
{
return stdc_leading_zeros_uc(x);
}
static inline unsigned int
stdc_first_leading_one_us(unsigned short x)
{
return stdc_leading_zeros_us(x);
}
static inline unsigned int
stdc_first_leading_one_ui(unsigned int x)
{
return stdc_leading_zeros_ui(x);
}
static inline unsigned int
stdc_first_leading_one_ul(unsigned long x)
{
return stdc_leading_zeros_ul(x);
}
static inline unsigned int
stdc_first_leading_one_ull(unsigned long long x)
{
return stdc_leading_zeros_ull(x);
}
static inline unsigned int
stdc_first_trailing_zero_uc(unsigned char x)
{
return stdc_trailing_ones_uc(x);
}
static inline unsigned int
stdc_first_trailing_zero_us(unsigned short x)
{
return stdc_trailing_ones_us(x);
}
static inline unsigned int
stdc_first_trailing_zero_ui(unsigned int x)
{
return stdc_trailing_ones_ui(x);
}
static inline unsigned int
stdc_first_trailing_zero_ul(unsigned long x)
{
return stdc_trailing_ones_ul(x);
}
static inline unsigned int
stdc_first_trailing_zero_ull(unsigned long long x)
{
return stdc_trailing_ones_ull(x);
}
static inline unsigned int
stdc_first_trailing_one_uc(unsigned char x)
{
return stdc_trailing_zeros_uc(x);
}
static inline unsigned int
stdc_first_trailing_one_us(unsigned short x)
{
return stdc_trailing_zeros_us(x);
}
static inline unsigned int
stdc_first_trailing_one_ui(unsigned int x)
{
return stdc_trailing_zeros_ui(x);
}
static inline unsigned int
stdc_first_trailing_one_ul(unsigned long x)
{
return stdc_trailing_zeros_ul(x);
}
static inline unsigned int
stdc_first_trailing_one_ull(unsigned long long x)
{
return stdc_trailing_zeros_ull(x);
}
/* stdc_count_ones: number of 1 bits. */
static inline unsigned int
stdc_count_ones_uc(unsigned char x)
{
return (unsigned int)__builtin_popcount(x);
}
static inline unsigned int
stdc_count_ones_us(unsigned short x)
{
return (unsigned int)__builtin_popcount(x);
}
static inline unsigned int
stdc_count_ones_ui(unsigned int x)
{
return (unsigned int)__builtin_popcount(x);
}
static inline unsigned int
stdc_count_ones_ul(unsigned long x)
{
return (unsigned int)__builtin_popcountl(x);
}
static inline unsigned int
stdc_count_ones_ull(unsigned long long x)
{
return (unsigned int)__builtin_popcountll(x);
}
/* stdc_count_zeros: number of 0 bits. */
static inline unsigned int
stdc_count_zeros_uc(unsigned char x)
{
return 8u - stdc_count_ones_uc(x);
}
static inline unsigned int
stdc_count_zeros_us(unsigned short x)
{
return 16u - stdc_count_ones_us(x);
}
static inline unsigned int
stdc_count_zeros_ui(unsigned int x)
{
return 32u - stdc_count_ones_ui(x);
}
static inline unsigned int
stdc_count_zeros_ul(unsigned long x)
{
return 64u - stdc_count_ones_ul(x);
}
static inline unsigned int
stdc_count_zeros_ull(unsigned long long x)
{
return 64u - stdc_count_ones_ull(x);
}
/* stdc_has_single_bit: true iff exactly one bit is set. */
static inline _Bool
stdc_has_single_bit_uc(unsigned char x)
{
return x != 0 && (x & (x - 1U)) == 0;
}
static inline _Bool
stdc_has_single_bit_us(unsigned short x)
{
return x != 0 && (x & (x - 1U)) == 0;
}
static inline _Bool
stdc_has_single_bit_ui(unsigned int x)
{
return x != 0 && (x & (x - 1U)) == 0;
}
static inline _Bool
stdc_has_single_bit_ul(unsigned long x)
{
return x != 0 && (x & (x - 1UL)) == 0;
}
static inline _Bool
stdc_has_single_bit_ull(unsigned long long x)
{
return x != 0 && (x & (x - 1ULL)) == 0;
}
/* stdc_bit_width: bits needed to represent x (0 for 0, 1 for 1). */
static inline unsigned int
stdc_bit_width_uc(unsigned char x)
{
return x == 0 ? 0u : (unsigned int)(32 - __builtin_clz(x));
}
static inline unsigned int
stdc_bit_width_us(unsigned short x)
{
return x == 0 ? 0u : (unsigned int)(32 - __builtin_clz(x));
}
static inline unsigned int
stdc_bit_width_ui(unsigned int x)
{
return x == 0 ? 0u : (unsigned int)(32 - __builtin_clz(x));
}
static inline unsigned int
stdc_bit_width_ul(unsigned long x)
{
return x == 0 ? 0u : (unsigned int)(64 - __builtin_clzl(x));
}
static inline unsigned int
stdc_bit_width_ull(unsigned long long x)
{
return x == 0 ? 0u : (unsigned int)(64 - __builtin_clzll(x));
}
/* stdc_bit_floor: largest power of 2 not greater than x (0 for 0). */
static inline unsigned char
stdc_bit_floor_uc(unsigned char x)
{
return (unsigned char)(x == 0 ? 0 : (1u << (32 - __builtin_clz(x) - 1)));
}
static inline unsigned short
stdc_bit_floor_us(unsigned short x)
{
return (unsigned short)(x == 0 ? 0 : (1u << (32 - __builtin_clz(x) - 1)));
}
static inline unsigned int
stdc_bit_floor_ui(unsigned int x)
{
return x == 0 ? 0u : (1u << (32 - __builtin_clz(x) - 1));
}
static inline unsigned long
stdc_bit_floor_ul(unsigned long x)
{
return x == 0 ? 0ul : (1ul << (64 - __builtin_clzl(x) - 1));
}
static inline unsigned long long
stdc_bit_floor_ull(unsigned long long x)
{
return x == 0 ? 0ull : (1ull << (64 - __builtin_clzll(x) - 1));
}
/*
* stdc_bit_ceil: smallest power of 2 not less than x. The result is
* undefined when it is not representable in the type of x, per C23.
*/
static inline unsigned char
stdc_bit_ceil_uc(unsigned char x)
{
return (unsigned char)(x <= 1 ? 1 : (1u << (32 - __builtin_clz(x - 1))));
}
static inline unsigned short
stdc_bit_ceil_us(unsigned short x)
{
return (unsigned short)(x <= 1 ? 1 : (1u << (32 - __builtin_clz(x - 1))));
}
static inline unsigned int
stdc_bit_ceil_ui(unsigned int x)
{
return x <= 1 ? 1u : (1u << (32 - __builtin_clz(x - 1)));
}
static inline unsigned long
stdc_bit_ceil_ul(unsigned long x)
{
return x <= 1 ? 1ul : (1ul << (64 - __builtin_clzl(x - 1)));
}
static inline unsigned long long
stdc_bit_ceil_ull(unsigned long long x)
{
return x <= 1 ? 1ull : (1ull << (64 - __builtin_clzll(x - 1)));
}
/*
* Type-generic dispatch: map the (lvalue-converted) argument type to the
* per-width function. Signed types route to the unsigned function of the
* same rank, which preserves the bit pattern as C23 requires.
*/
#define VLIBC_STDBIT_DISPATCH(fn, x) \
_Generic((x), \
_Bool: fn##_uc, \
char: fn##_uc, \
signed char: fn##_uc, \
unsigned char: fn##_uc, \
short: fn##_us, \
unsigned short: fn##_us, \
int: fn##_ui, \
unsigned int: fn##_ui, \
long: fn##_ul, \
unsigned long: fn##_ul, \
long long: fn##_ull, \
unsigned long long: fn##_ull)(x)
#define stdc_leading_zeros(x) VLIBC_STDBIT_DISPATCH(stdc_leading_zeros, x)
#define stdc_leading_ones(x) VLIBC_STDBIT_DISPATCH(stdc_leading_ones, x)
#define stdc_trailing_zeros(x) VLIBC_STDBIT_DISPATCH(stdc_trailing_zeros, x)
#define stdc_trailing_ones(x) VLIBC_STDBIT_DISPATCH(stdc_trailing_ones, x)
#define stdc_first_leading_zero(x) VLIBC_STDBIT_DISPATCH(stdc_first_leading_zero, x)
#define stdc_first_leading_one(x) VLIBC_STDBIT_DISPATCH(stdc_first_leading_one, x)
#define stdc_first_trailing_zero(x) VLIBC_STDBIT_DISPATCH(stdc_first_trailing_zero, x)
#define stdc_first_trailing_one(x) VLIBC_STDBIT_DISPATCH(stdc_first_trailing_one, x)
#define stdc_count_zeros(x) VLIBC_STDBIT_DISPATCH(stdc_count_zeros, x)
#define stdc_count_ones(x) VLIBC_STDBIT_DISPATCH(stdc_count_ones, x)
#define stdc_has_single_bit(x) VLIBC_STDBIT_DISPATCH(stdc_has_single_bit, x)
#define stdc_bit_width(x) VLIBC_STDBIT_DISPATCH(stdc_bit_width, x)
#define stdc_bit_floor(x) VLIBC_STDBIT_DISPATCH(stdc_bit_floor, x)
#define stdc_bit_ceil(x) VLIBC_STDBIT_DISPATCH(stdc_bit_ceil, x)
#endif /* VLIBC_STDBIT_H */
+27
View File
@@ -0,0 +1,27 @@
#ifndef VLIBC_STDBOOL_H
#define VLIBC_STDBOOL_H
/*
* vlibc — <stdbool.h>.
*
* In C23 bool, true and false are keywords, so this header defines only the
* guard macro __bool_true_false_are_defined. In C17 (and older) it defines
* the _Bool-based spellings the standard requires. In C++ the three names are
* keywords as well, so only the guard macro is defined there too.
*
* This header is ISO C core and is present in every profile.
*/
#include <vlibc/features.h>
#ifndef __cplusplus
#if !defined(__STDC_VERSION__) || __STDC_VERSION__ < 202311L
#define bool _Bool
#define true 1
#define false 0
#endif
#endif
#define __bool_true_false_are_defined 1
#endif /* VLIBC_STDBOOL_H */
+23
View File
@@ -0,0 +1,23 @@
#ifndef VLIBC_STDCKDINT_H
#define VLIBC_STDCKDINT_H
/*
* vlibc — <stdckdint.h>.
*
* Checked integer arithmetic (C23). Each macro computes the operation and
* stores the result through *result; it returns true when the mathematical
* result is not representable in the result type, in which case *result is
* left unmodified. The implementation defers to the GCC
* __builtin_*_overflow family, which is type-generic across every integer
* type, matching the standard's semantics.
*
* This header is ISO C core and is present in every profile.
*/
#include <vlibc/features.h>
#define ckd_add(result, a, b) __builtin_add_overflow((a), (b), (result))
#define ckd_sub(result, a, b) __builtin_sub_overflow((a), (b), (result))
#define ckd_mul(result, a, b) __builtin_mul_overflow((a), (b), (result))
#endif /* VLIBC_STDCKDINT_H */
+11 -2
View File
@@ -20,11 +20,15 @@ typedef __SIZE_TYPE__ size_t;
/* Signed integer type of the difference of two pointers. */ /* Signed integer type of the difference of two pointers. */
typedef __PTRDIFF_TYPE__ ptrdiff_t; typedef __PTRDIFF_TYPE__ ptrdiff_t;
/* Wide character type. */ /* Wide character type; a C++ keyword there, so no redefinition. */
#ifndef __cplusplus
typedef __WCHAR_TYPE__ wchar_t; typedef __WCHAR_TYPE__ wchar_t;
#endif
/* Type of the null pointer constant nullptr (C23). */ /* Type of the null pointer constant nullptr (C23); a C++ keyword there. */
#if !defined(__cplusplus) && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
typedef typeof(nullptr) nullptr_t; typedef typeof(nullptr) nullptr_t;
#endif
/* Null pointer constant. */ /* Null pointer constant. */
#define NULL ((void *)0) #define NULL ((void *)0)
@@ -32,6 +36,11 @@ typedef typeof(nullptr) nullptr_t;
/* Offset in bytes of a member from the start of its enclosing object. */ /* Offset in bytes of a member from the start of its enclosing object. */
#define offsetof(type, m) __builtin_offsetof(type, m) #define offsetof(type, m) __builtin_offsetof(type, m)
/* Mark the following point of the program as unreachable (C23). */
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
#define unreachable() __builtin_unreachable()
#endif
/* /*
* An object type whose alignment is as great as that of any supported * An object type whose alignment is as great as that of any supported
* fundamental type; suitable as the storage type for aligned allocation. * fundamental type; suitable as the storage type for aligned allocation.
+157
View File
@@ -0,0 +1,157 @@
#ifndef VLIBC_STDINT_H
#define VLIBC_STDINT_H
/*
* vlibc — <stdint.h>.
*
* Fixed-width integer types and their limit and constant macros (C23). Every
* definition is derived from compiler predefined macros (__INT8_TYPE__,
* __INT64_MAX__, __INT64_C, ...), so this header is fully self-contained and
* never borrows from a system header. Widths follow the x86_64 LP64 model.
*
* The compatibility profile is exposed by include/vlibc/features.h; this
* header is ISO C core and is present in every profile.
*/
#include <vlibc/features.h>
/* Exact-width types. */
typedef __INT8_TYPE__ int8_t;
typedef __INT16_TYPE__ int16_t;
typedef __INT32_TYPE__ int32_t;
typedef __INT64_TYPE__ int64_t;
typedef __UINT8_TYPE__ uint8_t;
typedef __UINT16_TYPE__ uint16_t;
typedef __UINT32_TYPE__ uint32_t;
typedef __UINT64_TYPE__ uint64_t;
/* Types with at least the given width (least-width). */
typedef __INT_LEAST8_TYPE__ int_least8_t;
typedef __INT_LEAST16_TYPE__ int_least16_t;
typedef __INT_LEAST32_TYPE__ int_least32_t;
typedef __INT_LEAST64_TYPE__ int_least64_t;
typedef __UINT_LEAST8_TYPE__ uint_least8_t;
typedef __UINT_LEAST16_TYPE__ uint_least16_t;
typedef __UINT_LEAST32_TYPE__ uint_least32_t;
typedef __UINT_LEAST64_TYPE__ uint_least64_t;
/* Fastest types with at least the given width. */
typedef __INT_FAST8_TYPE__ int_fast8_t;
typedef __INT_FAST16_TYPE__ int_fast16_t;
typedef __INT_FAST32_TYPE__ int_fast32_t;
typedef __INT_FAST64_TYPE__ int_fast64_t;
typedef __UINT_FAST8_TYPE__ uint_fast8_t;
typedef __UINT_FAST16_TYPE__ uint_fast16_t;
typedef __UINT_FAST32_TYPE__ uint_fast32_t;
typedef __UINT_FAST64_TYPE__ uint_fast64_t;
/* Types wide enough for pointers. */
typedef __INTPTR_TYPE__ intptr_t;
typedef __UINTPTR_TYPE__ uintptr_t;
/* Types of maximum width. */
typedef __INTMAX_TYPE__ intmax_t;
typedef __UINTMAX_TYPE__ uintmax_t;
/* Limits of the exact-width types. */
#define INT8_MIN (-__INT8_MAX__ - 1)
#define INT8_MAX __INT8_MAX__
#define UINT8_MAX __UINT8_MAX__
#define INT16_MIN (-__INT16_MAX__ - 1)
#define INT16_MAX __INT16_MAX__
#define UINT16_MAX __UINT16_MAX__
#define INT32_MIN (-__INT32_MAX__ - 1)
#define INT32_MAX __INT32_MAX__
#define UINT32_MAX __UINT32_MAX__
#define INT64_MIN (-__INT64_MAX__ - 1)
#define INT64_MAX __INT64_MAX__
#define UINT64_MAX __UINT64_MAX__
/* Limits of the least-width types. */
#define INT_LEAST8_MIN (-__INT_LEAST8_MAX__ - 1)
#define INT_LEAST8_MAX __INT_LEAST8_MAX__
#define UINT_LEAST8_MAX __UINT_LEAST8_MAX__
#define INT_LEAST16_MIN (-__INT_LEAST16_MAX__ - 1)
#define INT_LEAST16_MAX __INT_LEAST16_MAX__
#define UINT_LEAST16_MAX __UINT_LEAST16_MAX__
#define INT_LEAST32_MIN (-__INT_LEAST32_MAX__ - 1)
#define INT_LEAST32_MAX __INT_LEAST32_MAX__
#define UINT_LEAST32_MAX __UINT_LEAST32_MAX__
#define INT_LEAST64_MIN (-__INT_LEAST64_MAX__ - 1)
#define INT_LEAST64_MAX __INT_LEAST64_MAX__
#define UINT_LEAST64_MAX __UINT_LEAST64_MAX__
/* Limits of the fastest types. */
#define INT_FAST8_MIN (-__INT_FAST8_MAX__ - 1)
#define INT_FAST8_MAX __INT_FAST8_MAX__
#define UINT_FAST8_MAX __UINT_FAST8_MAX__
#define INT_FAST16_MIN (-__INT_FAST16_MAX__ - 1)
#define INT_FAST16_MAX __INT_FAST16_MAX__
#define UINT_FAST16_MAX __UINT_FAST16_MAX__
#define INT_FAST32_MIN (-__INT_FAST32_MAX__ - 1)
#define INT_FAST32_MAX __INT_FAST32_MAX__
#define UINT_FAST32_MAX __UINT_FAST32_MAX__
#define INT_FAST64_MIN (-__INT_FAST64_MAX__ - 1)
#define INT_FAST64_MAX __INT_FAST64_MAX__
#define UINT_FAST64_MAX __UINT_FAST64_MAX__
/* Limits of the pointer-width and maximum-width types. */
#define INTPTR_MIN (-__INTPTR_MAX__ - 1)
#define INTPTR_MAX __INTPTR_MAX__
#define UINTPTR_MAX __UINTPTR_MAX__
#define INTMAX_MIN (-__INTMAX_MAX__ - 1)
#define INTMAX_MAX __INTMAX_MAX__
#define UINTMAX_MAX __UINTMAX_MAX__
/* Limits of other standard integer types (x86_64: wchar_t is signed int). */
#define WCHAR_MIN __WCHAR_MIN__
#define WCHAR_MAX __WCHAR_MAX__
/* Macros for integer constants of the given type. */
#define INT8_C(c) __INT8_C(c)
#define INT16_C(c) __INT16_C(c)
#define INT32_C(c) __INT32_C(c)
#define INT64_C(c) __INT64_C(c)
#define UINT8_C(c) __UINT8_C(c)
#define UINT16_C(c) __UINT16_C(c)
#define UINT32_C(c) __UINT32_C(c)
#define UINT64_C(c) __UINT64_C(c)
#define INTMAX_C(c) __INTMAX_C(c)
#define UINTMAX_C(c) __UINTMAX_C(c)
/* Width of each integer type, in bits (C23). */
#define INT8_WIDTH 8
#define INT16_WIDTH 16
#define INT32_WIDTH 32
#define INT64_WIDTH 64
#define UINT8_WIDTH 8
#define UINT16_WIDTH 16
#define UINT32_WIDTH 32
#define UINT64_WIDTH 64
#define INT_LEAST8_WIDTH __INT_LEAST8_WIDTH__
#define INT_LEAST16_WIDTH __INT_LEAST16_WIDTH__
#define INT_LEAST32_WIDTH __INT_LEAST32_WIDTH__
#define INT_LEAST64_WIDTH __INT_LEAST64_WIDTH__
#define UINT_LEAST8_WIDTH __UINT_LEAST8_WIDTH__
#define UINT_LEAST16_WIDTH __UINT_LEAST16_WIDTH__
#define UINT_LEAST32_WIDTH __UINT_LEAST32_WIDTH__
#define UINT_LEAST64_WIDTH __UINT_LEAST64_WIDTH__
#define INT_FAST8_WIDTH __INT_FAST8_WIDTH__
#define INT_FAST16_WIDTH __INT_FAST16_WIDTH__
#define INT_FAST32_WIDTH __INT_FAST32_WIDTH__
#define INT_FAST64_WIDTH __INT_FAST64_WIDTH__
#define UINT_FAST8_WIDTH __UINT_FAST8_WIDTH__
#define UINT_FAST16_WIDTH __UINT_FAST16_WIDTH__
#define UINT_FAST32_WIDTH __UINT_FAST32_WIDTH__
#define UINT_FAST64_WIDTH __UINT_FAST64_WIDTH__
#define INTPTR_WIDTH __INTPTR_WIDTH__
#define UINTPTR_WIDTH __UINTPTR_WIDTH__
#define INTMAX_WIDTH __INTMAX_WIDTH__
#define UINTMAX_WIDTH __UINTMAX_WIDTH__
#define PTRDIFF_WIDTH __PTRDIFF_WIDTH__
#define SIG_ATOMIC_WIDTH __SIG_ATOMIC_WIDTH__
#define SIZE_WIDTH __SIZE_WIDTH__
#define WCHAR_WIDTH __WCHAR_WIDTH__
#define WINT_WIDTH __WINT_WIDTH__
#endif /* VLIBC_STDINT_H */
+112
View File
@@ -0,0 +1,112 @@
#ifndef VLIBC_STDLIB_H
#define VLIBC_STDLIB_H
/*
* vlibc — <stdlib.h>.
*
* This header is the shared home for the stdlib declarations; it currently
* holds only the memory-management family (todo 7). Later todos extend it
* in place: todo 11 adds the numeric conversions (atoi/strtol/strtod/...),
* todo 12 the pseudo-random and search/divide functions
* (rand/srand/qsort/bsearch/abs/div/...), and todo 13 the environment and
* multibyte helpers (getenv/setenv/mblen/mbtowc/...).
*
* Memory management functions, gated by the active compatibility profile
* (see include/vlibc/features.h). Levels are cumulative:
*
* Level 1 (onlyposix): ISO C core + POSIX base — malloc, free, calloc,
* realloc, aligned_alloc, posix_memalign.
* Level 2 (muslmimic): malloc_usable_size (BSD/musl).
*
* This header includes <vlibc/features.h> itself, so the gates below always
* see the configured VLIBC_LEVEL even when the caller included no vlibc
* header first, and <stddef.h> for size_t.
*/
#include <vlibc/features.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Level 1: memory management (always present). */
/*
* Allocate size bytes, 16-byte aligned. The memory is uninitialized.
* malloc(0) returns a unique minimum-size block (never NULL on success).
* NULL + errno ENOMEM on failure.
* malloc: the result does not alias any other pointer and has size bytes.
*/
__attribute__((malloc, alloc_size(1))) void *
malloc(size_t size);
/*
* Release the block at ptr, which must be NULL or a value returned by an
* earlier allocation in this family. free(NULL) is a no-op.
*/
void
free(void *ptr);
/*
* Allocate an array of nmemb elements of size bytes each, all bits zero.
* The product is overflow-checked: on overflow NULL + errno ENOMEM.
* calloc with a zero product returns a unique zeroed minimum-size block.
* malloc: the result does not alias any other pointer and has nmemb*size
* bytes.
*/
__attribute__((malloc, alloc_size(1, 2))) void *
calloc(size_t nmemb, size_t size);
/*
* Resize the block at ptr to size bytes, preserving the first min(old,
* size) bytes. realloc(NULL, size) behaves as malloc(size); realloc(ptr, 0)
* frees ptr and returns NULL. The old block is freed on success and left
* untouched on failure (NULL + errno ENOMEM).
* alloc_size(2): the result has size bytes.
*/
__attribute__((alloc_size(2))) void *
realloc(void *ptr, size_t size);
/*
* Allocate size bytes aligned to alignment. alignment must be a power of
* two that is a multiple of sizeof(void *), and size must be a multiple of
* alignment; a violation fails with NULL + errno EINVAL (a non-power-of-two
* alignment is undefined behavior in C23, so only well-formed arguments
* reach the allocator). size 0 returns NULL. The result is released with
* free.
* malloc + alloc_align(1): the result does not alias any other pointer and
* is aligned to alignment.
*/
__attribute__((malloc, alloc_size(2), alloc_align(1))) void *
aligned_alloc(size_t alignment, size_t size);
/*
* Allocate size bytes at address alignment and store the result in
* *memptr. alignment must be a power of two and a multiple of
* sizeof(void *). Returns 0 on success, EINVAL for a bad alignment, ENOMEM
* on allocation failure. Never sets errno itself, and never modifies
* *memptr on failure. size 0 returns a unique minimum-size block.
*/
__attribute__((access(write_only, 1))) int
posix_memalign(void **memptr, size_t alignment, size_t size);
#if VLIBC_LEVEL_GE(2)
/* Level 2 (muslmimic): BSD/musl extensions. */
/*
* Return the number of bytes actually available in the block at ptr,
* including any internal padding; at least as large as the requested size.
* ptr may be any block returned by the allocator family; NULL returns 0.
* pure: reads memory, no side effects.
*/
__attribute__((pure)) size_t
malloc_usable_size(void *ptr);
#endif /* VLIBC_LEVEL_GE(2) */
#ifdef __cplusplus
}
#endif
#endif /* VLIBC_STDLIB_H */
+23
View File
@@ -0,0 +1,23 @@
#ifndef VLIBC_STDNORETURN_H
#define VLIBC_STDNORETURN_H
/*
* vlibc — <stdnoreturn.h>.
*
* In C23 noreturn is a keyword, so this header has nothing left to define.
* In C17 (and older) it maps noreturn onto the _Noreturn function specifier.
* In C++ [[noreturn]] is an attribute, not a macro, so nothing is defined
* there either.
*
* This header is ISO C core and is present in every profile.
*/
#include <vlibc/features.h>
#ifndef __cplusplus
#if !defined(__STDC_VERSION__) || __STDC_VERSION__ < 202311L
#define noreturn _Noreturn
#endif
#endif
#endif /* VLIBC_STDNORETURN_H */
+226 -2
View File
@@ -7,8 +7,14 @@
* String and memory functions, gated by the active compatibility profile * String and memory functions, gated by the active compatibility profile
* (see include/vlibc/features.h). Levels are cumulative: * (see include/vlibc/features.h). Levels are cumulative:
* *
* Level 1 (onlyposix): ISO C core — memcpy, memmove, memset, strlen, strcmp. * Level 1 (onlyposix): ISO C core + POSIX.1-2008 base — memcpy, memmove,
* Level 2 (muslmimic): BSD extensions — strlcpy, strlcat. * memset, memchr, memcmp, strlen, strnlen, strcmp,
* strncmp, strcpy, strncpy, strcat, strncat, strchr,
* strrchr, strspn, strcspn, strpbrk, strstr, strtok,
* strtok_r, strcoll, strxfrm, strdup, strndup,
* strsignal.
* Level 2 (muslmimic): BSD + XSI extensions — strlcpy, strlcat, stpcpy,
* stpncpy, memccpy, strcoll_l, strxfrm_l.
* Level 3 (muslext): GNU extensions — strcasestr. * Level 3 (muslext): GNU extensions — strcasestr.
* *
* This header includes <vlibc/features.h> itself, so the gates below always * This header includes <vlibc/features.h> itself, so the gates below always
@@ -33,6 +39,15 @@ extern "C" {
__attribute__((pure)) size_t __attribute__((pure)) size_t
strlen(const char *s); strlen(const char *s);
/*
* Return the length of s, excluding the terminating NUL, examining at most
* maxlen bytes; the result is at most maxlen even when s is not a proper
* NUL-terminated string.
* pure: reads memory, no side effects.
*/
__attribute__((pure)) size_t
strnlen(const char *s, size_t maxlen);
/* /*
* Compare the strings lhs and rhs; return negative, zero, or positive when * Compare the strings lhs and rhs; return negative, zero, or positive when
* lhs is less than, equal to, or greater than rhs. * lhs is less than, equal to, or greater than rhs.
@@ -41,6 +56,24 @@ strlen(const char *s);
__attribute__((pure)) int __attribute__((pure)) int
strcmp(const char *lhs, const char *rhs); strcmp(const char *lhs, const char *rhs);
/*
* Compare at most n bytes of lhs and rhs, stopping early at the first
* difference or the first NUL; return negative, zero, or positive when lhs
* is less than, equal to, or greater than rhs.
* pure: reads memory, no side effects.
*/
__attribute__((pure)) int
strncmp(const char *lhs, const char *rhs, size_t n);
/*
* Compare s1 and s2 under the active locale's collating sequence; return
* negative, zero, or positive. Only the "C" locale exists so far (locales
* are owned by a later todo), where collation is identical to strcmp.
* pure: reads memory, no side effects.
*/
__attribute__((pure)) int
strcoll(const char *s1, const char *s2);
/* /*
* Copy n bytes from src to dst. The regions must not overlap (restrict). * Copy n bytes from src to dst. The regions must not overlap (restrict).
* No intent attribute: it writes memory. * No intent attribute: it writes memory.
@@ -63,6 +96,157 @@ memmove(void *dst, const void *src, size_t n);
void * void *
memset(void *dst, int c, size_t n); memset(void *dst, int c, size_t n);
/*
* Return a pointer to the first occurrence of c (converted to unsigned
* char) among the first n bytes of s, or NULL when absent.
* pure: reads memory, no side effects.
*/
__attribute__((pure)) void *
memchr(const void *s, int c, size_t n);
/*
* Compare the first n bytes of lhs and rhs as unsigned char; return
* negative, zero, or positive when lhs is less than, equal to, or greater
* than rhs. Unlike strcmp, a NUL byte does not end the comparison.
* pure: reads memory, no side effects.
*/
__attribute__((pure)) int
memcmp(const void *lhs, const void *rhs, size_t n);
/*
* Copy src to dst, including the terminating NUL; return dst. The strings
* must not overlap (restrict).
* No intent attribute: it writes memory.
*/
char *
strcpy(char *restrict dst, const char *restrict src);
/*
* Copy at most n bytes from src to dst. When src is shorter than n the
* remainder of dst is NUL-padded; when src is n bytes or longer the result
* is not NUL-terminated. Return dst.
* No intent attribute: it writes memory.
*/
char *
strncpy(char *restrict dst, const char *restrict src, size_t n);
/*
* Append src (including its NUL) to the end of dst; return dst. The strings
* must not overlap (restrict).
* No intent attribute: it writes memory.
*/
char *
strcat(char *restrict dst, const char *restrict src);
/*
* Append at most n bytes of src to dst and always NUL-terminate; return
* dst.
* No intent attribute: it writes memory.
*/
char *
strncat(char *restrict dst, const char *restrict src, size_t n);
/*
* Return a pointer to the first occurrence of c (converted to char) in s,
* or NULL when absent. The terminating NUL is part of the string, so
* strchr(s, '\0') returns a pointer to it.
* pure: reads memory, no side effects.
*/
__attribute__((pure)) char *
strchr(const char *s, int c);
/*
* Return a pointer to the last occurrence of c (converted to char) in s, or
* NULL when absent. The terminating NUL is part of the string, so
* strrchr(s, '\0') returns a pointer to it.
* pure: reads memory, no side effects.
*/
__attribute__((pure)) char *
strrchr(const char *s, int c);
/*
* Return the length of the initial span of s consisting entirely of bytes
* that occur in accept.
* pure: reads memory, no side effects.
*/
__attribute__((pure)) size_t
strspn(const char *s, const char *accept);
/*
* Return the length of the initial span of s consisting entirely of bytes
* that do NOT occur in reject.
* pure: reads memory, no side effects.
*/
__attribute__((pure)) size_t
strcspn(const char *s, const char *reject);
/*
* Return a pointer to the first byte in s that also occurs in accept, or
* NULL when none occurs.
* pure: reads memory, no side effects.
*/
__attribute__((pure)) char *
strpbrk(const char *s, const char *accept);
/*
* Return a pointer to the first occurrence of needle in haystack, or NULL
* when absent. An empty needle matches haystack itself.
* pure: reads memory, no side effects.
*/
__attribute__((pure)) char *
strstr(const char *haystack, const char *needle);
/*
* Split s into tokens delimited by any byte from sep. On the first call s
* names the string; subsequent calls with NULL continue the same string.
* Leading and consecutive delimiters produce no empty tokens, and the
* delimiter bytes in s are overwritten with NUL. Returns NULL when no token
* remains. strtok keeps its state in private static storage and is not
* thread-safe; strtok_r keeps it in *state and is.
* No intent attribute: strtok mutates private state, strtok_r writes
* through its parameters.
*/
char *
strtok(char *restrict s, const char *restrict sep);
char *
strtok_r(char *restrict s, const char *restrict sep, char **restrict state);
/*
* Transform src under the active locale's collating sequence so that strcmp
* on transformed strings orders them as strcoll would, storing at most n
* bytes of the result in dst (always NUL-terminated when n > 0; nothing is
* written when n == 0). Return the length of the full transformed string,
* excluding the NUL. In the "C" locale the transformation is the identity
* and the return is strlen(src).
* No intent attribute: it writes memory.
*/
size_t
strxfrm(char *restrict dst, const char *restrict src, size_t n);
/*
* Return a heap copy of s (malloc-allocated; release with free), or NULL
* with errno ENOMEM on allocation failure. strndup copies at most n bytes
* and NUL-terminates, so strndup(s, 0) returns the empty string.
* malloc: returns fresh unaliased storage the caller owns.
*/
__attribute__((malloc)) char *
strdup(const char *s);
__attribute__((malloc)) char *
strndup(const char *s, size_t n);
/*
* Return a pointer to a static string describing the signal sig, or a
* formatted "Unknown signal <sig>" string for unrecognized numbers. Never
* returns NULL; distinct known signals yield distinct strings. The text for
* signal 0 is unspecified by POSIX and deliberately not pinned.
*
* No intent attribute: the unknown-signal path writes shared storage.
*/
char *
strsignal(int sig);
#if VLIBC_LEVEL >= 2 #if VLIBC_LEVEL >= 2
/* Level 2 (muslmimic): BSD extensions. */ /* Level 2 (muslmimic): BSD extensions. */
@@ -83,6 +267,46 @@ size_t
strlcat(char *dst, const char *src, size_t size); strlcat(char *dst, const char *src, size_t size);
#endif /* VLIBC_LEVEL >= 2 */ #endif /* VLIBC_LEVEL >= 2 */
#if VLIBC_LEVEL_GE(2)
/* Level 2 (muslmimic): XSI extensions. */
/*
* strcpy/strncpy variants returning a pointer to the terminating NUL of
* dst: stpcpy to the NUL of the copied string, stpncpy to the first NUL
* written (or to dst + n when no NUL was written).
* No intent attribute: they write memory.
*/
char *
stpcpy(char *restrict dst, const char *restrict src);
char *
stpncpy(char *restrict dst, const char *restrict src, size_t n);
/*
* Copy at most n bytes from src to dst, stopping after the first byte equal
* to c (converted to unsigned char); return a pointer to the byte after c
* in dst when found, NULL otherwise. The regions must not overlap
* (restrict).
* No intent attribute: it writes memory.
*/
void *
memccpy(void *restrict dst, const void *restrict src, int c, size_t n);
/*
* Locale-parameterized XSI variants of strcoll/strxfrm. No locale machinery
* exists yet, so the locale argument is accepted and ignored and behavior
* is the "C" locale behavior of the base functions. The locale parameter is
* typed void * for now: locale_t will be an ABI-identical pointer typedef
* defined by <locale.h>, whose todo updates these signatures to the real
* type.
*/
__attribute__((pure)) int
strcoll_l(const char *s1, const char *s2, void *locale);
size_t
strxfrm_l(char *restrict dst, const char *restrict src, size_t n, void *locale);
#endif /* VLIBC_LEVEL_GE(2) */
#if VLIBC_LEVEL >= 3 #if VLIBC_LEVEL >= 3
/* Level 3 (muslext): GNU extensions. */ /* Level 3 (muslext): GNU extensions. */
+133
View File
@@ -0,0 +1,133 @@
#ifndef VLIBC_STRINGS_H
#define VLIBC_STRINGS_H
/*
* vlibc — <strings.h>.
*
* BSD/XSI legacy string functions, gated by the active compatibility
* profile (see include/vlibc/features.h). This ENTIRE header is a legacy
* extension: nothing it declares is POSIX.1-2008 base, so at level 1
* (onlyposix) it is empty. POSIX mandates that <strings.h> (BSD legacy)
* and <string.h> (ISO C) remain separate headers; the two never share a
* declaration, so including both can never conflict.
*
* strcasecmp / strncasecmp — XSI case-insensitive comparisons (ASCII
* 'A'..'Z'/'a'..'z' fold only, byte-wise);
* ffs / ffsl / ffsll — XSI find-first-set-bit, 1-based;
* bcmp / bcopy / bzero — BSD legacy byte operations (bcopy takes
* (src, dst) — the arguments are REVERSED
* relative to memcpy/memmove);
* index / rindex — BSD legacy names for strchr / strrchr.
*
* This header includes <vlibc/features.h> itself, so the gate below always
* sees the configured VLIBC_LEVEL even when the caller included no vlibc
* header first, and <stddef.h> for size_t.
*/
#include <vlibc/features.h>
#if VLIBC_LEVEL_GE(2)
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/*
* Compare s1 and s2 byte-wise, ignoring case: each byte is read as
* unsigned char and an ASCII 'A'..'Z' is folded to 'a'..'z' before the
* comparison; bytes at or above 0x80 pass through unmodified. Return
* negative, zero, or positive when s1 is less than, equal to, or greater
* than s2.
* pure: reads memory, no side effects.
*/
__attribute__((pure)) int
strcasecmp(const char *s1, const char *s2);
/*
* Compare at most n bytes of s1 and s2 as strcasecmp does, stopping early
* at the first difference or the first NUL; return negative, zero, or
* positive like strcasecmp. The NUL terminator of either string ends the
* comparison even when n is larger.
* pure: reads memory, no side effects.
*/
__attribute__((pure)) int
strncasecmp(const char *s1, const char *s2, size_t n);
/*
* Return the 1-based index of the least significant set bit of i, or 0
* when i has no set bit: ffs(0) == 0, ffs(1) == 1, ffs(8) == 4,
* ffs(INT_MIN) == 32.
* const: the result depends only on i.
*/
__attribute__((const)) int
ffs(int i);
/*
* Same as ffs for long: ffsl(0) == 0, ffsl(1L << 40) == 41,
* ffsl(LONG_MIN) == 64 (long is 64-bit on x86_64).
* const: the result depends only on i.
*/
__attribute__((const)) int
ffsl(long i);
/*
* Same as ffs for long long: ffsll(0) == 0, ffsll(LLONG_MIN) == 64.
* const: the result depends only on i.
*/
__attribute__((const)) int
ffsll(long long i);
/*
* Compare the first n bytes of s1 and s2 as unsigned char, like memcmp;
* unlike strcmp a NUL byte does not end the comparison. Return 0 when the
* n bytes are equal, nonzero otherwise.
* pure: reads memory, no side effects.
*/
__attribute__((pure)) int
bcmp(const void *s1, const void *s2, size_t n);
/*
* Copy n bytes from src to dst. The regions may overlap and the copy
* behaves like memmove. NOTE the argument order: source first, destination
* second — the reverse of memcpy/memmove.
* No intent attribute: it writes memory.
*/
void
bcopy(const void *src, void *dst, size_t n);
/*
* Fill n bytes at s with zero.
* No intent attribute: it writes memory.
*/
void
bzero(void *s, size_t n);
/*
* Return a pointer to the first occurrence of c (converted to unsigned
* char) in s, or NULL when absent. The terminating NUL is part of the
* string, so index(s, '\0') returns a pointer to it. Legacy name for
* strchr.
* pure: reads memory, no side effects.
*/
__attribute__((pure)) char *
index(const char *s, int c);
/*
* Return a pointer to the last occurrence of c (converted to unsigned
* char) in s, or NULL when absent. The terminating NUL is part of the
* string, so rindex(s, '\0') returns a pointer to it. Legacy name for
* strrchr.
* pure: reads memory, no side effects.
*/
__attribute__((pure)) char *
rindex(const char *s, int c);
#ifdef __cplusplus
}
#endif
#endif /* VLIBC_LEVEL_GE(2) */
#endif /* VLIBC_STRINGS_H */
+77
View File
@@ -0,0 +1,77 @@
#ifndef VLIBC_SYS_TYPES_H
#define VLIBC_SYS_TYPES_H
/*
* vlibc — <sys/types.h>.
*
* The POSIX base scalar types. This public header is authoritative for
* public consumers; src/internal/types.h mirrors the same x86_64 LP64
* choices for internal use (both agree today — see the learnings note).
* Widths follow the Linux x86_64 ABI, not the ISO minimums: long is 64-bit.
*/
#include <vlibc/features.h>
#include <stddef.h>
#if VLIBC_HAS_HEADER_SYS_TYPES_H
/* Signed type for byte counts and read()/write() results (x86_64: long). */
typedef __PTRDIFF_TYPE__ ssize_t;
/* Calendar time in seconds. */
typedef long time_t;
/* Clock ticks (times(2)). */
typedef long clock_t;
/* Clock id (clock_gettime(2)). */
typedef int clockid_t;
/* Timer id (timer_create(2)). */
typedef void *timer_t;
/* Process ID. */
typedef int pid_t;
/* User and group IDs. */
typedef unsigned int uid_t;
typedef unsigned int gid_t;
/* General identifier. */
typedef unsigned int id_t;
/* System V IPC key. */
typedef int key_t;
/* File offset. */
typedef long off_t;
/* File mode bits (and permissions). */
typedef unsigned int mode_t;
/* Device and inode numbers. */
typedef unsigned long long dev_t;
typedef unsigned long long ino_t;
/* Hard link count. */
typedef unsigned long long nlink_t;
/* Block counts and sizes. */
typedef long blkcnt_t;
typedef long blksize_t;
/* File-system block and file counts (statvfs). */
typedef unsigned long fsblkcnt_t;
typedef unsigned long fsfilcnt_t;
/* Microsecond intervals (timeval). */
typedef long suseconds_t;
typedef unsigned int useconds_t;
/* Socket address lengths. */
typedef unsigned int socklen_t;
#endif /* VLIBC_HAS_HEADER_SYS_TYPES_H */
#endif /* VLIBC_SYS_TYPES_H */
+51
View File
@@ -0,0 +1,51 @@
#ifndef VLIBC_TAR_H
#define VLIBC_TAR_H
/*
* vlibc — <tar.h>.
*
* Constants for the ustar tar archive format (POSIX). Constants only: the
* header magic and version strings, the typeflag characters that identify
* entry kinds, and the mode bits stored in each header.
*/
#include <vlibc/features.h>
#if VLIBC_HAS_HEADER_TAR_H
/* Header magic (6 bytes) and version (2 bytes). */
#define TMAGIC "ustar"
#define TMAGLEN 6
#define TVERSION "00"
#define TVERSLEN 2
/* Typeflag characters naming the entry kind. */
#define REGTYPE '0'
#define AREGTYPE '\0'
#define LNKTYPE '1'
#define SYMTYPE '2'
#define CHRTYPE '3'
#define BLKTYPE '4'
#define DIRTYPE '5'
#define FIFOTYPE '6'
#define CONTTYPE '7'
/* Set-id and sticky bits. */
#define TSUID 04000
#define TSGID 02000
#define TSVTX 01000
/* Mode bits (permissions): user, group, other. */
#define TUREAD 00400
#define TUWRITE 00200
#define TUEXEC 00100
#define TGREAD 00040
#define TGWRITE 00020
#define TGEXEC 00010
#define TOREAD 00004
#define TOWRITE 00002
#define TOEXEC 00001
#endif /* VLIBC_HAS_HEADER_TAR_H */
#endif /* VLIBC_TAR_H */
+18
View File
@@ -0,0 +1,18 @@
#ifndef VLIBC_TGMATH_H
#define VLIBC_TGMATH_H
/*
* vlibc — <tgmath.h>.
*
* Type-generic math. STUB: the type-generic dispatch over <math.h> is
* IMPLEMENTED by the first math todo (#39), not here. This stub exists so
* that including <tgmath.h> alongside the other headers compiles cleanly
* today; it deliberately defines no type-generic macros until #39 lands
* the <math.h> function inventory it dispatches to.
*
* This header is ISO C core and is present in every profile.
*/
#include <vlibc/features.h>
#endif /* VLIBC_TGMATH_H */
+190
View File
@@ -0,0 +1,190 @@
#ifndef VLIBC_THREADS_H
#define VLIBC_THREADS_H
/*
* vlibc — <threads.h>.
*
* C11 threads. STUB: the header lands here (level-1 gate) but every
* function is implemented by the threads todo (#45), which wraps them as
* thin aliases over the pthread layer. Until then, user code compiles
* against these declarations and linking resolves once #45 lands.
*
* The types are placeholder shapes for now; #45 owns their final form (the
* pthread-backed layout) and may refine them in place.
*/
#include <vlibc/features.h>
#if VLIBC_HAS_HEADER_THREADS_H
#ifdef __cplusplus
extern "C" {
#endif
/* Forward-declared: the real definition lives in <time.h> (later todo). */
struct timespec;
/* Thread identifier (x86_64: matches pthread_t's unsigned long). */
typedef unsigned long thrd_t;
/* Start routine of a thread. */
typedef int (*thrd_start_t)(void *);
/* Destructor run when a thread exits. */
typedef void (*tss_dtor_t)(void *);
/* Mutex. */
typedef struct
{
unsigned long opaque;
} mtx_t;
/* Condition variable. */
typedef struct
{
unsigned long opaque;
} cnd_t;
/* Thread-specific storage key. */
typedef struct
{
unsigned long opaque;
} tss_t;
/* call_once state. */
typedef struct
{
unsigned long opaque;
} once_flag;
/* Initializer for a once_flag object. */
#define ONCE_FLAG_INIT {0}
/* Upper bound on tss_dtor_t invocations per key at thread exit. */
#define TSS_DTOR_ITERATIONS 4
/* Mutex kinds for mtx_init. */
enum
{
mtx_plain = 0,
mtx_recursive = 1,
mtx_timed = 2
};
/* Result codes shared by the thrd_* and mtx_* functions. */
enum
{
thrd_success = 0,
thrd_busy = 1,
thrd_error = 2,
thrd_nomem = 3,
thrd_timedout = 4
};
/* Create a thread running func(arg); stores its id through thr. */
int
thrd_create(thrd_t *thr, thrd_start_t func, void *arg);
/* Non-zero iff lhs and rhs name the same thread. */
int
thrd_equal(thrd_t lhs, thrd_t rhs);
/* Identifier of the calling thread. */
thrd_t
thrd_current(void);
/* Sleep for the interval in *duration; *remaining gets the unslept part. */
int
thrd_sleep(const struct timespec *duration, struct timespec *remaining);
/* Yield the processor to other runnable threads. */
void
thrd_yield(void);
/* Terminate the calling thread, reporting res. */
_Noreturn void
thrd_exit(int res);
/* Detach thr so its resources are released on exit. */
int
thrd_detach(thrd_t thr);
/* Wait for thr to exit; stores its result through *res when non-NULL. */
int
thrd_join(thrd_t thr, int *res);
/* Initialize *mtx with the given kind. */
int
mtx_init(mtx_t *mtx, int type);
/* Lock *mtx, blocking if needed. */
int
mtx_lock(mtx_t *mtx);
/* Lock *mtx, blocking at most until *ts. */
int
mtx_timedlock(mtx_t *restrict mtx, const struct timespec *restrict ts);
/* Lock *mtx unless another thread holds it (thrd_busy). */
int
mtx_trylock(mtx_t *mtx);
/* Unlock *mtx. */
int
mtx_unlock(mtx_t *mtx);
/* Release *mtx's resources. */
void
mtx_destroy(mtx_t *mtx);
/* Run func exactly once per once_flag object. */
void
call_once(once_flag *flag, void (*func)(void));
/* Initialize *cnd. */
int
cnd_init(cnd_t *cnd);
/* Wake one thread waiting on *cnd. */
int
cnd_signal(cnd_t *cnd);
/* Wake all threads waiting on *cnd. */
int
cnd_broadcast(cnd_t *cnd);
/* Wait on *cnd; *mtx must be locked by the caller. */
int
cnd_wait(cnd_t *cnd, mtx_t *mtx);
/* Wait on *cnd at most until *ts. */
int
cnd_timedwait(cnd_t *restrict cnd, mtx_t *restrict mtx, const struct timespec *restrict ts);
/* Release *cnd's resources. */
void
cnd_destroy(cnd_t *cnd);
/* Create a thread-specific key *key; dtor may be NULL. */
int
tss_create(tss_t *key, tss_dtor_t dtor);
/* Value stored for *key in the calling thread (NULL when unset). */
void *
tss_get(tss_t key);
/* Store val under *key for the calling thread. */
int
tss_set(tss_t key, void *val);
/* Release *key; the key may not be used afterwards. */
void
tss_delete(tss_t key);
#ifdef __cplusplus
}
#endif
#endif /* VLIBC_HAS_HEADER_THREADS_H */
#endif /* VLIBC_THREADS_H */
+64
View File
@@ -0,0 +1,64 @@
#ifndef VLIBC_UCHAR_H
#define VLIBC_UCHAR_H
/*
* vlibc — <uchar.h>.
*
* Unicode character types and the multibyte conversion declarations (C11,
* gated at level 1 per the plan's header inventory). In C23 char16_t and
* char32_t are keywords, so the typedefs below only fire in C17 (and
* older); the C17 types are the compiler's __CHAR16_TYPE__/__CHAR32_TYPE__,
* which is what the C23 keywords name as well.
*
* The mbrtoc16/mbrtoc32/c16rtomb/c32rtomb functions are DECLARED here but
* implemented by the multibyte todo (#50): user code compiles against this
* header today and linking resolves once #50 lands.
*/
#include <vlibc/features.h>
#include <stddef.h>
#if VLIBC_HAS_HEADER_UCHAR_H
/*
* char16_t/char32_t remain typedefs in C23 (unlike bool/alignas/... they were
* NOT promoted to keywords), so they are defined in every C mode from the
* compiler's __CHAR16_TYPE__/__CHAR32_TYPE__; in C++ they are keywords and
* no typedef fires.
*/
#ifndef __cplusplus
typedef __CHAR16_TYPE__ char16_t;
typedef __CHAR32_TYPE__ char32_t;
#endif
/*
* Conversion state for multibyte conversions. Two opaque words; the real
* layout is owned by the multibyte todo (#50), which may extend this
* definition when it lands (see the learnings note).
*/
typedef struct
{
unsigned int state[2];
} mbstate_t;
/*
* Multibyte <-> UTF-16/UTF-32 conversions (implemented in #50).
*
* mbrtoc16: convert at most n bytes of the multibyte string s to a char16_t
* stored through pc16, consuming the corresponding number of bytes.
* c16rtomb: convert char16_t c16 to at most MB_CUR_MAX bytes at s.
* The char32_t forms are the same for UTF-32.
*/
size_t
mbrtoc16(char16_t *restrict pc16, const char *restrict s, size_t n, mbstate_t *restrict ps);
size_t
c16rtomb(char *restrict s, char16_t c16, mbstate_t *restrict ps);
size_t
mbrtoc32(char32_t *restrict pc32, const char *restrict s, size_t n, mbstate_t *restrict ps);
size_t
c32rtomb(char *restrict s, char32_t c32, mbstate_t *restrict ps);
#endif /* VLIBC_HAS_HEADER_UCHAR_H */
#endif /* VLIBC_UCHAR_H */
+17
View File
@@ -57,4 +57,21 @@
#define VLIBC_HAS_VLIBC 1 #define VLIBC_HAS_VLIBC 1
#endif #endif
/*
* Per-header availability gates, derived from VLIBC_LEVEL. The ISO C core
* headers (<stddef.h>, <stdint.h>, <stdbool.h>, <stdalign.h>, <iso646.h>,
* <limits.h>, <float.h>, <assert.h>, <stdarg.h>, <stdnoreturn.h>,
* <stdatomic.h>, <stdbit.h>, <stdckdint.h>, <tgmath.h>) are present in every
* profile and carry no gate. The POSIX-facing headers below exist only in
* profiles that provide POSIX (level >= 1, i.e. all profiles today); each
* header includes this file and gates its declarations on its own macro.
*/
#if VLIBC_LEVEL >= 1
#define VLIBC_HAS_HEADER_SYS_TYPES_H 1
#define VLIBC_HAS_HEADER_UCHAR_H 1
#define VLIBC_HAS_HEADER_CPIO_H 1
#define VLIBC_HAS_HEADER_TAR_H 1
#define VLIBC_HAS_HEADER_THREADS_H 1
#endif
#endif /* VLIBC_FEATURES_H */ #endif /* VLIBC_FEATURES_H */
+266
View File
@@ -0,0 +1,266 @@
#ifndef VLIBC_TEST_H
#define VLIBC_TEST_H
/*
* vlibc — shared test contract (todo 6).
*
* Every future per-function test (test_*.c) includes this header and gets:
*
* - TEST_ASSERT_EQ(actual, expected) integer equality, compared as
* signed 64-bit; on failure prints
* both values in hex
* - TEST_ASSERT_TRUE(cond) boolean check
* - TEST_ASSERT_STREQ(actual, expected) NUL-terminated string equality;
* NULL-safe (NULL equals only NULL)
* - TEST_ASSERT_NULL(p) pointer is NULL
* - TEST_MAIN(tests) a main() that runs the array of
* struct vlibc_test entries, prints
* one "RUN <name>: PASS|FAIL" line
* per test and a PASS/FAIL summary,
* and returns 0 when everything
* passed and 1 otherwise
*
* Contract:
* - Dependency-light: needs only vlibc's own <stddef.h> and the internal
* raw-syscall layer (src/internal/syscall.h, via a path-relative
* include). It deliberately includes NO host header and does NOT use
* vlibc stdio (which does not exist yet). All output goes through raw
* SYS_write to fd 1 (progress/summary) and fd 2 (assertion failures).
* - Compile tests with -Iinclude -std=c23 -fno-stack-protector
* -fno-pic -fno-pie (the make check harness does this; see Makefile.am)
* and link statically against the vlibc test archive.
* - A test function returns 0 on success, nonzero on failure; TEST_MAIN
* aggregates both the return values and any assertion failures.
* - The failure counter is per translation unit (one static int); keep
* each test binary a single TU.
* - Tests that need argc/argv (mode flags like -f) write their own
* main() and may still use the TEST_ASSERT_* macros.
* - One test function per checked behavior, named for the output.
* - All macro arguments are evaluated exactly once.
* - Do NOT include host headers in a TU that includes this file: the
* -Iinclude path shadows GCC's internal headers (see tests/test_strerror.c).
*/
#include <stddef.h>
#include "../../../src/internal/syscall.h"
/* One registered test: a name for the runner output and its run function. */
struct vlibc_test
{
const char *name;
int (*run)(void);
};
/* Assertion failures recorded in this translation unit. */
static int vlibc_test_failures;
/* ---- Raw output helpers (no stdio: raw SYS_write only) ---- */
static inline long
vlibc_test_strlen(const char *s)
{
long n = 0;
while (s[n] != '\0')
{
n++;
}
return n;
}
static inline void
vlibc_test_say(int fd, const char *s)
{
(void)__syscall3(SYS_write, fd, (long)s, vlibc_test_strlen(s));
}
/*
* The helpers below all take an (fd, value) or (value, name, file, line)
* argument shape whose order is a fixed diagnostic convention; the
* easily-swappable-parameters warning does not apply.
*/
// NOLINTBEGIN(bugprone-easily-swappable-parameters)
static inline void
vlibc_test_say_dec(int fd, unsigned long v)
{
char buf[24];
int i = (int)sizeof(buf);
buf[--i] = '\0';
do
{
buf[--i] = (char)('0' + (v % 10));
v /= 10;
} while (v != 0);
(void)__syscall3(SYS_write, fd, (long)(buf + i), (long)(sizeof(buf) - 1 - i));
}
static inline void
vlibc_test_say_hex(int fd, unsigned long long v)
{
char buf[18];
int i = (int)sizeof(buf);
buf[--i] = '\0';
do
{
unsigned int d = (unsigned int)(v & 0xf);
buf[--i] = (char)(d < 10 ? '0' + d : 'a' + d - 10);
v >>= 4;
} while (v != 0);
(void)__syscall3(SYS_write, fd, (long)(buf + i), (long)(sizeof(buf) - 1 - i));
}
/* ---- Assertion machinery (implementation side of the macros) ---- */
static inline void
vlibc_test_fail_header(const char *file, int line)
{
vlibc_test_failures++;
vlibc_test_say(2, "ASSERT FAIL: ");
vlibc_test_say(2, file);
vlibc_test_say(2, ":");
vlibc_test_say_dec(2, (unsigned long)line);
vlibc_test_say(2, ": ");
}
static inline void // NOLINT(bugprone-easily-swappable-parameters)
vlibc_test_check(int ok, const char *what, const char *file, int line)
{
if (ok)
{
return;
}
vlibc_test_fail_header(file, line);
vlibc_test_say(2, what);
vlibc_test_say(2, " is false\n");
}
static inline void // NOLINT(bugprone-easily-swappable-parameters)
vlibc_test_check_eq(long long actual, long long expected, const char *actual_s,
const char *expected_s, const char *file, int line)
{
if (actual == expected)
{
return;
}
vlibc_test_fail_header(file, line);
vlibc_test_say(2, "TEST_ASSERT_EQ(");
vlibc_test_say(2, actual_s);
vlibc_test_say(2, ", ");
vlibc_test_say(2, expected_s);
vlibc_test_say(2, "): got 0x");
vlibc_test_say_hex(2, (unsigned long long)actual);
vlibc_test_say(2, ", want 0x");
vlibc_test_say_hex(2, (unsigned long long)expected);
vlibc_test_say(2, "\n");
}
static inline int
vlibc_test_str_eq(const char *a, const char *b)
{
if (a == b)
{
return 1;
}
if (a == NULL || b == NULL)
{
return 0;
}
while (*a != '\0' && *a == *b)
{
a++;
b++;
}
return *a == *b;
}
static inline void // NOLINT(bugprone-easily-swappable-parameters)
vlibc_test_check_streq(const char *actual, const char *expected,
const char *actual_s, const char *expected_s,
const char *file, int line)
{
if (vlibc_test_str_eq(actual, expected))
{
return;
}
vlibc_test_fail_header(file, line);
vlibc_test_say(2, "TEST_ASSERT_STREQ(");
vlibc_test_say(2, actual_s);
vlibc_test_say(2, ", ");
vlibc_test_say(2, expected_s);
vlibc_test_say(2, "): got \"");
vlibc_test_say(2, actual == NULL ? "(null)" : actual);
vlibc_test_say(2, "\", want \"");
vlibc_test_say(2, expected == NULL ? "(null)" : expected);
vlibc_test_say(2, "\"\n");
}
// NOLINTEND(bugprone-easily-swappable-parameters)
/* ---- Public assertion macros ---- */
#define TEST_ASSERT_TRUE(cond) \
do \
{ \
vlibc_test_check((cond) != 0, #cond, __FILE__, __LINE__); \
} while (0)
#define TEST_ASSERT_EQ(actual, expected) \
do \
{ \
vlibc_test_check_eq((long long)(actual), (long long)(expected), \
#actual, #expected, __FILE__, __LINE__); \
} while (0)
#define TEST_ASSERT_NULL(p) \
do \
{ \
vlibc_test_check((p) == NULL, #p " == NULL", __FILE__, __LINE__); \
} while (0)
#define TEST_ASSERT_STREQ(actual, expected) \
do \
{ \
vlibc_test_check_streq((actual), (expected), #actual, #expected, \
__FILE__, __LINE__); \
} while (0)
/* ---- Runner ---- */
#define TEST_MAIN(tests) \
int \
main(void) \
{ \
const size_t vlibc_test_count = sizeof(tests) / sizeof((tests)[0]); \
size_t vlibc_test_i; \
size_t vlibc_test_passed = 0; \
for (vlibc_test_i = 0; vlibc_test_i < vlibc_test_count; vlibc_test_i++) \
{ \
int vlibc_test_before = vlibc_test_failures; \
vlibc_test_say(1, "RUN "); \
vlibc_test_say(1, (tests)[vlibc_test_i].name); \
vlibc_test_say(1, ": "); \
if ((tests)[vlibc_test_i].run() == 0 && \
vlibc_test_failures == vlibc_test_before) \
{ \
vlibc_test_say(1, "PASS\n"); \
vlibc_test_passed++; \
} \
else \
{ \
vlibc_test_say(1, "FAIL\n"); \
} \
} \
vlibc_test_say(1, "SUMMARY: "); \
vlibc_test_say_dec(1, (unsigned long)vlibc_test_passed); \
vlibc_test_say(1, "/"); \
vlibc_test_say_dec(1, (unsigned long)vlibc_test_count); \
vlibc_test_say(1, " passed, "); \
vlibc_test_say_dec(1, (unsigned long)vlibc_test_failures); \
vlibc_test_say(1, " assertion failure(s)\n"); \
return vlibc_test_passed == vlibc_test_count ? 0 : 1; \
}
#endif /* VLIBC_TEST_H */
+172
View File
@@ -0,0 +1,172 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <ctype.h>
/*
* The "C" locale character-class table: one entry per unsigned char value,
* holding the OR of the class bits below. The table is laid out as an
* explicit 8-entries-per-row grid so each byte can be audited against the
* ASCII ranges the standard specifies. The 0x80..0xff half is deliberately
* not written: a static initializer zero-fills the elements it does not
* mention, so every high byte — entry 255 included, which is where EOF
* lands after the unsigned char cast — classifies false. Because every
* lookup casts the argument to unsigned char first:
*
* - a negative char value can never index out of bounds;
* - EOF classifies false in every table-driven is* function without a
* special case;
* - tolower/toupper pass EOF (and every non-letter) through unchanged,
* returning the argument as given.
*
* isdigit, isxdigit and isprint are not table-driven: they are single
* unsigned comparisons on the argument (see below), which makes them
* genuinely const-capable — but only isdigit/isxdigit are declared const:
* GCC predeclares isdigit/isxdigit as const builtins and isprint as a pure
* builtin, and the header's attribute must match each builtin's declaration
* or the weaker one is rejected with a warning.
*/
#define CTYPE_UPPER 0x01U
#define CTYPE_LOWER 0x02U
#define CTYPE_DIGIT 0x04U
#define CTYPE_CNTRL 0x08U
#define CTYPE_PUNCT 0x10U
#define CTYPE_SPACE 0x20U
#define CTYPE_BLANK 0x80U
/* Combined masks, mirroring the standard's definitions. */
#define CTYPE_ALPHA (CTYPE_UPPER | CTYPE_LOWER)
#define CTYPE_ALNUM (CTYPE_ALPHA | CTYPE_DIGIT)
#define CTYPE_GRAPH (CTYPE_ALNUM | CTYPE_PUNCT)
static const unsigned char ctype_class[256] = {
/* 0 1 2 3 4 5 6 7 */
/* 0x00 */ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
/* 0x08 */ 0x08, 0xa8, 0x28, 0x28, 0x28, 0x28, 0x08, 0x08,
/* 0x10 */ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
/* 0x18 */ 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
/* 0x20 */ 0xa0, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
/* 0x28 */ 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
/* 0x30 */ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
/* 0x38 */ 0x04, 0x04, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
/* 0x40 */ 0x10, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
/* 0x48 */ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
/* 0x50 */ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
/* 0x58 */ 0x01, 0x01, 0x01, 0x10, 0x10, 0x10, 0x10, 0x10,
/* 0x60 */ 0x10, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
/* 0x68 */ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
/* 0x70 */ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
/* 0x78 */ 0x02, 0x02, 0x02, 0x10, 0x10, 0x10, 0x10, 0x08,
/* 0x80..0xff stay zero by the static initializer's default. */
};
int
isalnum(int c)
{
return (ctype_class[(unsigned char)c] & CTYPE_ALNUM) != 0;
}
int
isalpha(int c)
{
return (ctype_class[(unsigned char)c] & CTYPE_ALPHA) != 0;
}
int
isblank(int c)
{
return (ctype_class[(unsigned char)c] & CTYPE_BLANK) != 0;
}
int
iscntrl(int c)
{
return (ctype_class[(unsigned char)c] & CTYPE_CNTRL) != 0;
}
/*
* Pure arithmetic (no table): the unsigned cast turns every value below '0'
* into a huge number, so exactly 0..9 pass the range check.
*/
int
isdigit(int c)
{
return (unsigned int)c - '0' < 10U;
}
int
isgraph(int c)
{
return (ctype_class[(unsigned char)c] & CTYPE_GRAPH) != 0;
}
int
islower(int c)
{
return (ctype_class[(unsigned char)c] & CTYPE_LOWER) != 0;
}
/*
* Pure arithmetic (no table): the printable range 0x20..0x7e.
*/
int
isprint(int c)
{
return (unsigned int)c - 0x20 < 0x5fU;
}
int
ispunct(int c)
{
return (ctype_class[(unsigned char)c] & CTYPE_PUNCT) != 0;
}
int
isspace(int c)
{
return (ctype_class[(unsigned char)c] & CTYPE_SPACE) != 0;
}
int
isupper(int c)
{
return (ctype_class[(unsigned char)c] & CTYPE_UPPER) != 0;
}
/*
* Pure arithmetic (no table): c | 0x20 folds 'A'..'F' to 'a'..'f'; the
* unsigned cast first keeps every negative value out of the range check.
*/
int
isxdigit(int c)
{
return (unsigned int)c - '0' < 10U || (unsigned int)(c | 0x20) - 'a' < 6U;
}
/*
* Map an uppercase letter to lowercase and pass everything else — non-letters
* and EOF alike — through unchanged. The class bit is authoritative, so the
* unsigned char cast already keeps EOF and negative values out of the
* mapping branch.
*/
int
tolower(int c)
{
if ((ctype_class[(unsigned char)c] & CTYPE_UPPER) != 0)
{
return c + ('a' - 'A');
}
return c;
}
int
toupper(int c)
{
if ((ctype_class[(unsigned char)c] & CTYPE_LOWER) != 0)
{
return c - ('a' - 'A');
}
return c;
}
+15
View File
@@ -0,0 +1,15 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <ctype.h>
/*
* True when c is a 7-bit ASCII value. EOF and every other negative value are
* outside the range, so they classify false without a cast.
*/
int
isascii(int c)
{
return c >= 0 && c < 128;
}
+15
View File
@@ -0,0 +1,15 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <ctype.h>
/*
* Clear every bit above the low 7 of c. Pure bit arithmetic: defined for
* every int value, EOF included (toascii(-1) is 0x7f).
*/
int
toascii(int c)
{
return c & 0x7f;
}
+296
View File
@@ -0,0 +1,296 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <errno.h>
/*
* vlibc — strerror / strerror_r (todo 2).
*
* strerror maps an errno value to an informative, descriptive message. The
* messages are this libc's own standard English texts (they need not match
* glibc verbatim). Known values resolve to immutable static string constants;
* unknown values resolve to "Unknown error <N>" with <N> formatted in place.
*
* Thread-safety of the unknown-value path: the formatted string lives in a
* single static buffer, so two threads formatting *unknown* errno values
* concurrently can observe each other's text. This tradeoff is deliberate at
* this stage: unknown errno values are a rare diagnostic path, the library
* has no per-thread storage beyond the errno TCB slot yet, and strerror must
* not depend on malloc (or any other subsystem). The pointer returned for
* known values is unaffected. Once the real TCB layout lands (startup todo),
* this can migrate to a per-thread buffer without changing the interface.
*
* The unknown-value formatter is hand-rolled: strerror is libc base and must
* not pull in stdio/string before those exist.
*/
/*
* Message table: one entry per defined E* value, indexed directly by the
* errno number. Designated initializers keep name/value pairs provably in
* sync with <errno.h>; entries for the ABI gaps (41, 58 — values the kernel
* never produces) stay NULL and are treated as unknown. EWOULDBLOCK and
* EDEADLOCK are aliases of EAGAIN and EDEADLK, so they share those entries.
* The array auto-sizes to EHWPOISON + 1 (134) entries.
*/
static const char *const errmsg[] = {
[0] = "Success",
[EPERM] = "Operation not permitted",
[ENOENT] = "No such file or directory",
[ESRCH] = "No such process",
[EINTR] = "Interrupted system call",
[EIO] = "Input/output error",
[ENXIO] = "No such device or address",
[E2BIG] = "Argument list too long",
[ENOEXEC] = "Exec format error",
[EBADF] = "Bad file descriptor",
[ECHILD] = "No child processes",
[EAGAIN] = "Resource temporarily unavailable",
[ENOMEM] = "Cannot allocate memory",
[EACCES] = "Permission denied",
[EFAULT] = "Bad address",
[ENOTBLK] = "Block device required",
[EBUSY] = "Device or resource busy",
[EEXIST] = "File exists",
[EXDEV] = "Invalid cross-device link",
[ENODEV] = "No such device",
[ENOTDIR] = "Not a directory",
[EISDIR] = "Is a directory",
[EINVAL] = "Invalid argument",
[ENFILE] = "Too many open files in system",
[EMFILE] = "Too many open files",
[ENOTTY] = "Inappropriate ioctl for device",
[ETXTBSY] = "Text file busy",
[EFBIG] = "File too large",
[ENOSPC] = "No space left on device",
[ESPIPE] = "Illegal seek",
[EROFS] = "Read-only file system",
[EMLINK] = "Too many links",
[EPIPE] = "Broken pipe",
[EDOM] = "Numerical argument out of domain",
[ERANGE] = "Numerical result out of range",
[EDEADLK] = "Resource deadlock would occur",
[ENAMETOOLONG] = "File name too long",
[ENOLCK] = "No locks available",
[ENOSYS] = "Function not implemented",
[ENOTEMPTY] = "Directory not empty",
[ELOOP] = "Too many levels of symbolic links",
[ENOMSG] = "No message of desired type",
[EIDRM] = "Identifier removed",
[ECHRNG] = "Channel number out of range",
[EL2NSYNC] = "Level 2 not synchronized",
[EL3HLT] = "Level 3 halted",
[EL3RST] = "Level 3 reset",
[ELNRNG] = "Link number out of range",
[EUNATCH] = "Protocol driver not attached",
[ENOCSI] = "No CSI structure available",
[EL2HLT] = "Level 2 halted",
[EBADE] = "Invalid exchange",
[EBADR] = "Invalid request descriptor",
[EXFULL] = "Exchange full",
[ENOANO] = "No anode",
[EBADRQC] = "Invalid request code",
[EBADSLT] = "Invalid slot",
[EBFONT] = "Bad font file format",
[ENOSTR] = "Device not a stream",
[ENODATA] = "No data available",
[ETIME] = "Timer expired",
[ENOSR] = "Out of streams resources",
[ENONET] = "Machine is not on the network",
[ENOPKG] = "Package not installed",
[EREMOTE] = "Object is remote",
[ENOLINK] = "Link has been severed",
[EADV] = "Advertise error",
[ESRMNT] = "Srmount error",
[ECOMM] = "Communication error on send",
[EPROTO] = "Protocol error",
[EMULTIHOP] = "Multihop attempted",
[EDOTDOT] = "RFS specific error",
[EBADMSG] = "Bad message",
[EOVERFLOW] = "Value too large for defined data type",
[ENOTUNIQ] = "Name not unique on network",
[EBADFD] = "File descriptor in bad state",
[EREMCHG] = "Remote address changed",
[ELIBACC] = "Can not access a needed shared library",
[ELIBBAD] = "Accessing a corrupted shared library",
[ELIBSCN] = ".lib section in a.out corrupted",
[ELIBMAX] = "Attempting to link in too many shared libraries",
[ELIBEXEC] = "Cannot exec a shared library directly",
[EILSEQ] = "Invalid or incomplete multibyte or wide character",
[ERESTART] = "Interrupted system call should be restarted",
[ESTRPIPE] = "Streams pipe error",
[EUSERS] = "Too many users",
[ENOTSOCK] = "Socket operation on non-socket",
[EDESTADDRREQ] = "Destination address required",
[EMSGSIZE] = "Message too long",
[EPROTOTYPE] = "Protocol wrong type for socket",
[ENOPROTOOPT] = "Protocol not available",
[EPROTONOSUPPORT] = "Protocol not supported",
[ESOCKTNOSUPPORT] = "Socket type not supported",
[EOPNOTSUPP] = "Operation not supported",
[EPFNOSUPPORT] = "Protocol family not supported",
[EAFNOSUPPORT] = "Address family not supported by protocol",
[EADDRINUSE] = "Address already in use",
[EADDRNOTAVAIL] = "Cannot assign requested address",
[ENETDOWN] = "Network is down",
[ENETUNREACH] = "Network is unreachable",
[ENETRESET] = "Network dropped connection on reset",
[ECONNABORTED] = "Software caused connection abort",
[ECONNRESET] = "Connection reset by peer",
[ENOBUFS] = "No buffer space available",
[EISCONN] = "Transport endpoint is already connected",
[ENOTCONN] = "Transport endpoint is not connected",
[ESHUTDOWN] = "Cannot send after transport endpoint shutdown",
[ETOOMANYREFS] = "Too many references: cannot splice",
[ETIMEDOUT] = "Connection timed out",
[ECONNREFUSED] = "Connection refused",
[EHOSTDOWN] = "Host is down",
[EHOSTUNREACH] = "No route to host",
[EALREADY] = "Operation already in progress",
[EINPROGRESS] = "Operation now in progress",
[ESTALE] = "Stale file handle",
[EUCLEAN] = "Structure needs cleaning",
[ENOTNAM] = "Not a XENIX named type file",
[ENAVAIL] = "No XENIX semaphores available",
[EISNAM] = "Is a named type file",
[EREMOTEIO] = "Remote I/O error",
[EDQUOT] = "Disk quota exceeded",
[ENOMEDIUM] = "No medium found",
[EMEDIUMTYPE] = "Wrong medium type",
[ECANCELED] = "Operation canceled",
[ENOKEY] = "Required key not available",
[EKEYEXPIRED] = "Key has expired",
[EKEYREVOKED] = "Key has been revoked",
[EKEYREJECTED] = "Key was rejected by service",
[EOWNERDEAD] = "Owner died",
[ENOTRECOVERABLE] = "State not recoverable",
[ERFKILL] = "Operation not possible due to RF-kill",
[EHWPOISON] = "Memory page has hardware error",
};
/* The table spans 0..EHWPOISON exactly: the ABI gaps stay NULL entries. */
_Static_assert(sizeof errmsg / sizeof errmsg[0] == EHWPOISON + 1,
"strerror table must cover every E* value 0..EHWPOISON");
/* Shared storage for the unknown-value path (see the file-top comment). */
static char errbuf[32];
/*
* Write "Unknown error <errnum>" into dst, truncating to cap bytes and always
* NUL-terminating when cap > 0. Hand-rolled so this file needs no stdio.
*/
static void
format_unknown(int errnum, char *dst, size_t cap)
{
static const char prefix[] = "Unknown error ";
char digits[12]; /* enough for "-2147483648" */
size_t ndigits;
size_t i;
unsigned long mag;
if (cap == 0)
{
return;
}
/* Absolute value as unsigned long; INT_MIN negates safely in long. */
mag = (unsigned long)(errnum < 0 ? -(long)errnum : errnum);
/* Digits, least significant first. */
ndigits = 0;
do
{
digits[ndigits] = (char)('0' + (int)(mag % 10));
ndigits++;
mag /= 10;
} while (mag != 0);
i = 0;
while (i + 1 < cap && prefix[i] != '\0')
{
dst[i] = prefix[i];
i++;
}
if (i + 1 < cap && errnum < 0)
{
dst[i] = '-';
i++;
}
while (i + 1 < cap && ndigits > 0)
{
ndigits--;
dst[i] = digits[ndigits];
i++;
}
dst[i] = '\0';
}
/*
* Return the message for errnum, or NULL when it is unknown (out of table
* range or an ABI gap).
*/
static const char *
lookup(int errnum)
{
if (errnum >= 0 && errnum < (int)(sizeof errmsg / sizeof errmsg[0]))
{
return errmsg[errnum];
}
return NULL;
}
char *
strerror(int errnum)
{
const char *msg;
msg = lookup(errnum);
if (msg == NULL)
{
format_unknown(errnum, errbuf, sizeof errbuf);
msg = errbuf;
}
return (char *)msg;
}
#if VLIBC_LEVEL_GE(2)
/*
* XSI strerror_r: copy the message for errnum into buf (truncated to buflen,
* always NUL-terminated when buflen > 0) and return 0. Unknown errnum values
* produce "Unknown error <errnum>" with the same success return; buf == NULL
* with buflen > 0 yields EINVAL; buflen == 0 writes nothing and returns 0.
* The GNU char*-returning semantics are deliberately not implemented.
*/
int
strerror_r(int errnum, char *buf, size_t buflen)
{
const char *msg;
char unknown[32];
size_t i;
if (buflen == 0)
{
return 0;
}
if (buf == NULL)
{
return EINVAL;
}
msg = lookup(errnum);
if (msg == NULL)
{
format_unknown(errnum, unknown, sizeof unknown);
msg = unknown;
}
i = 0;
while (i + 1 < buflen && msg[i] != '\0')
{
buf[i] = msg[i];
i++;
}
buf[i] = '\0';
return 0;
}
#endif /* VLIBC_LEVEL_GE(2) */
+80
View File
@@ -0,0 +1,80 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "syscall.h"
/*
* The failing-assertion sink behind <assert.h>.
*
* Writes the standard-shaped diagnostic to fd 2 through the raw syscall
* layer, then traps. No abort(), exit() or stdio: those land in later todos
* and an assertion failure must be able to fire from a half-initialized
* runtime. __builtin_trap() terminates with SIGILL, which the assert QA
* harness expects (the standard only requires abnormal termination).
*
* Not declared hidden, unlike the other src/internal helpers: <assert.h>
* declares this symbol to consumers, so it is part of the public ABI surface
* and must resolve across the shared-library boundary.
*/
/* Convert v to decimal digits at p; returns the next free position. */
static char *
append_dec(char *p, int v)
{
char tmp[12];
int i = 0;
if (v == 0)
{
*p++ = '0';
return p;
}
while (v > 0)
{
tmp[i++] = (char)('0' + v % 10);
v /= 10;
}
while (i > 0)
{
*p++ = tmp[--i];
}
return p;
}
/* Append the string s at p; returns the next free position. */
static char *
append_str(char *p, const char *s)
{
while (*s != '\0')
{
*p++ = *s++;
}
return p;
}
/*
* The __vlibc_assert_fail name sits in the implementation-reserved namespace
* by design (it is the libc's private assert plumbing), so the
* reserved-identifier check is waived.
*/
__attribute__((noreturn)) void
__vlibc_assert_fail(const char *expr, const char *file, int line,
const char *func) // NOLINT(bugprone-reserved-identifier)
{
char msg[384];
char *p = msg;
p = append_str(p, "Assertion failed: ");
p = append_str(p, expr);
p = append_str(p, " (file ");
p = append_str(p, file);
p = append_str(p, ": line ");
p = append_dec(p, line);
p = append_str(p, ", func ");
p = append_str(p, func);
p = append_str(p, ")\n");
(void)__syscall3(SYS_write, 2, (long)msg, (long)(p - msg));
__builtin_trap();
}
+10 -158
View File
@@ -8,6 +8,14 @@
* (TCB), addressed relative to the x86_64 FS thread pointer; there is no * (TCB), addressed relative to the x86_64 FS thread pointer; there is no
* process-global errno object and no compiler-managed TLS (`__thread`) here. * process-global errno object and no compiler-managed TLS (`__thread`) here.
* *
* The public <errno.h> is the single canonical home for the errno macro, the
* __errno_location() declaration, and the E* constant table (1..133, with
* EAGAIN/EWOULDBLOCK and EDEADLK/EDEADLOCK aliases and no values 41/58).
* This header includes it instead of keeping a second copy, so the public
* and internal views of errno can never drift apart. What stays internal-
* only here is the errno TCB-slot offset, an ABI constant the public header
* must not expose.
*
* The offset of the errno slot inside the TCB is an ABI constant defined * The offset of the errno slot inside the TCB is an ABI constant defined
* below and consumed by the authoritative TCB layout (see the startup todo, * below and consumed by the authoritative TCB layout (see the startup todo,
* which owns the TCB/DTV layout). No other layer may re-derive errno's * which owns the TCB/DTV layout). No other layer may re-derive errno's
@@ -23,163 +31,7 @@
*/ */
#define VLIBC_TCB_ERRNO_OFF 8 #define VLIBC_TCB_ERRNO_OFF 8
/* /* errno, __errno_location(), and the E* table live in the public header. */
* Return the address of the calling thread's errno slot. #include <errno.h>
*
* Requires the thread pointer (FS) to be initialized to a TCB whose first
* word is the TCB's own address — the startup todo owns that bootstrap and
* sets it before any code that can touch errno runs. This function itself
* performs no setup and no other TCB access.
*
* The name sits in the implementation-reserved namespace deliberately: it is
* this libc's private errno accessor, not public API.
*/
int *
__errno_location(void); // NOLINT(bugprone-reserved-identifier)
/* The conventional errno lvalue; resolves to the caller's TCB slot. */
#define errno (*__errno_location())
/*
* Error numbers: the Linux errno ABI (asm-generic/errno-base.h plus
* asm-generic/errno.h, which is the table x86_64 uses), transcribed as
* kernel-ABI facts. These values are fixed and shared with the kernel. The
* public <errno.h> is owned by a later todo; this internal copy is what
* internal code and tests compile against.
*/
#define EPERM 1 /* Operation not permitted */
#define ENOENT 2 /* No such file or directory */
#define ESRCH 3 /* No such process */
#define EINTR 4 /* Interrupted system call */
#define EIO 5 /* I/O error */
#define ENXIO 6 /* No such device or address */
#define E2BIG 7 /* Argument list too long */
#define ENOEXEC 8 /* Exec format error */
#define EBADF 9 /* Bad file descriptor */
#define ECHILD 10 /* No child processes */
#define EAGAIN 11 /* Try again */
#define ENOMEM 12 /* Out of memory */
#define EACCES 13 /* Permission denied */
#define EFAULT 14 /* Bad address */
#define ENOTBLK 15 /* Block device required */
#define EBUSY 16 /* Device or resource busy */
#define EEXIST 17 /* File exists */
#define EXDEV 18 /* Cross-device link */
#define ENODEV 19 /* No such device */
#define ENOTDIR 20 /* Not a directory */
#define EISDIR 21 /* Is a directory */
#define EINVAL 22 /* Invalid argument */
#define ENFILE 23 /* File table overflow */
#define EMFILE 24 /* Too many open files */
#define ENOTTY 25 /* Not a typewriter */
#define ETXTBSY 26 /* Text file busy */
#define EFBIG 27 /* File too large */
#define ENOSPC 28 /* No space left on device */
#define ESPIPE 29 /* Illegal seek */
#define EROFS 30 /* Read-only file system */
#define EMLINK 31 /* Too many links */
#define EPIPE 32 /* Broken pipe */
#define EDOM 33 /* Math argument out of domain of func */
#define ERANGE 34 /* Math result not representable */
#define EDEADLK 35 /* Resource deadlock would occur */
#define ENAMETOOLONG 36 /* File name too long */
#define ENOLCK 37 /* No record locks available */
#define ENOSYS 38 /* Invalid system call number */
#define ENOTEMPTY 39 /* Directory not empty */
#define ELOOP 40 /* Too many symbolic links encountered */
#define EWOULDBLOCK EAGAIN /* Operation would block */
#define ENOMSG 42 /* No message of desired type */
#define EIDRM 43 /* Identifier removed */
#define ECHRNG 44 /* Channel number out of range */
#define EL2NSYNC 45 /* Level 2 not synchronized */
#define EL3HLT 46 /* Level 3 halted */
#define EL3RST 47 /* Level 3 reset */
#define ELNRNG 48 /* Link number out of range */
#define EUNATCH 49 /* Protocol driver not attached */
#define ENOCSI 50 /* No CSI structure available */
#define EL2HLT 51 /* Level 2 halted */
#define EBADE 52 /* Invalid exchange */
#define EBADR 53 /* Invalid request descriptor */
#define EXFULL 54 /* Exchange full */
#define ENOANO 55 /* No anode */
#define EBADRQC 56 /* Invalid request code */
#define EBADSLT 57 /* Invalid slot */
#define EDEADLOCK EDEADLK /* File locking deadlock error */
#define EBFONT 59 /* Bad font file format */
#define ENOSTR 60 /* Device not a stream */
#define ENODATA 61 /* No data available */
#define ETIME 62 /* Timer expired */
#define ENOSR 63 /* Out of streams resources */
#define ENONET 64 /* Machine is not on the network */
#define ENOPKG 65 /* Package not installed */
#define EREMOTE 66 /* Object is remote */
#define ENOLINK 67 /* Link has been severed */
#define EADV 68 /* Advertise error */
#define ESRMNT 69 /* Srmount error */
#define ECOMM 70 /* Communication error on send */
#define EPROTO 71 /* Protocol error */
#define EMULTIHOP 72 /* Multihop attempted */
#define EDOTDOT 73 /* RFS specific error */
#define EBADMSG 74 /* Not a data message */
#define EOVERFLOW 75 /* Value too large for defined data type */
#define ENOTUNIQ 76 /* Name not unique on network */
#define EBADFD 77 /* File descriptor in bad state */
#define EREMCHG 78 /* Remote address changed */
#define ELIBACC 79 /* Can not access a needed shared library */
#define ELIBBAD 80 /* Accessing a corrupted shared library */
#define ELIBSCN 81 /* .lib section in a.out corrupted */
#define ELIBMAX 82 /* Attempting to link in too many shared libraries */
#define ELIBEXEC 83 /* Cannot exec a shared library directly */
#define EILSEQ 84 /* Illegal byte sequence */
#define ERESTART 85 /* Interrupted system call should be restarted */
#define ESTRPIPE 86 /* Streams pipe error */
#define EUSERS 87 /* Too many users */
#define ENOTSOCK 88 /* Socket operation on non-socket */
#define EDESTADDRREQ 89 /* Destination address required */
#define EMSGSIZE 90 /* Message too long */
#define EPROTOTYPE 91 /* Protocol wrong type for socket */
#define ENOPROTOOPT 92 /* Protocol not available */
#define EPROTONOSUPPORT 93 /* Protocol not supported */
#define ESOCKTNOSUPPORT 94 /* Socket type not supported */
#define EOPNOTSUPP 95 /* Operation not supported on transport endpoint */
#define EPFNOSUPPORT 96 /* Protocol family not supported */
#define EAFNOSUPPORT 97 /* Address family not supported by protocol */
#define EADDRINUSE 98 /* Address already in use */
#define EADDRNOTAVAIL 99 /* Cannot assign requested address */
#define ENETDOWN 100 /* Network is down */
#define ENETUNREACH 101 /* Network is unreachable */
#define ENETRESET 102 /* Network dropped connection because of reset */
#define ECONNABORTED 103 /* Software caused connection abort */
#define ECONNRESET 104 /* Connection reset by peer */
#define ENOBUFS 105 /* No buffer space available */
#define EISCONN 106 /* Transport endpoint is already connected */
#define ENOTCONN 107 /* Transport endpoint is not connected */
#define ESHUTDOWN 108 /* Cannot send after transport endpoint shutdown */
#define ETOOMANYREFS 109 /* Too many references: cannot splice */
#define ETIMEDOUT 110 /* Connection timed out */
#define ECONNREFUSED 111 /* Connection refused */
#define EHOSTDOWN 112 /* Host is down */
#define EHOSTUNREACH 113 /* No route to host */
#define EALREADY 114 /* Operation already in progress */
#define EINPROGRESS 115 /* Operation now in progress */
#define ESTALE 116 /* Stale file handle */
#define EUCLEAN 117 /* Structure needs cleaning */
#define ENOTNAM 118 /* Not a XENIX named type file */
#define ENAVAIL 119 /* No XENIX semaphores available */
#define EISNAM 120 /* Is a named type file */
#define EREMOTEIO 121 /* Remote I/O error */
#define EDQUOT 122 /* Quota exceeded */
#define ENOMEDIUM 123 /* No medium found */
#define EMEDIUMTYPE 124 /* Wrong medium type */
#define ECANCELED 125 /* Operation canceled */
#define ENOKEY 126 /* Required key not available */
#define EKEYEXPIRED 127 /* Key has expired */
#define EKEYREVOKED 128 /* Key has been revoked */
#define EKEYREJECTED 129 /* Key was rejected by service */
#define EOWNERDEAD 130 /* Owner died */
#define ENOTRECOVERABLE 131 /* State not recoverable */
#define ERFKILL 132 /* Operation not possible due to RF-kill */
#define EHWPOISON 133 /* Memory page has hardware error */
#endif /* VLIBC_INTERNAL_ERRNO_H */ #endif /* VLIBC_INTERNAL_ERRNO_H */
+30
View File
@@ -0,0 +1,30 @@
#ifndef VLIBC_INTERNAL_MALLOC_H
#define VLIBC_INTERNAL_MALLOC_H
/*
* vlibc — internal allocator seam (todo 8).
*
* The allocator todo (#7) owns the real implementation and provides these
* entry points; library code that must allocate (strdup, strndup, and later
* stdio/regex/...) calls them instead of the public malloc/free so that the
* allocator remains the single allocation implementation. The public
* malloc/free/calloc/realloc of todo 7 must be backed by the same allocator,
* so a block returned by __libc_malloc can be released with public free.
*
* - __libc_malloc(n): allocate n bytes, 16-byte aligned; NULL + errno ENOMEM
* on failure. May return a unique pointer even when n == 0.
* - __libc_free(p): release a block returned by __libc_malloc; NULL is a
* no-op.
*/
#include <stddef.h>
#include "libc.h"
hidden void *
__libc_malloc(size_t n); // NOLINT(bugprone-reserved-identifier)
hidden void
__libc_free(void *p); // NOLINT(bugprone-reserved-identifier)
#endif /* VLIBC_INTERNAL_MALLOC_H */
+951
View File
@@ -0,0 +1,951 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stddef.h>
#include <stdint.h>
#include <errno.h>
#include <vlibc/features.h>
#include "../internal/malloc.h"
#include "../internal/syscall.h"
/*
* vlibc — the heap allocator (todo 7).
*
* A first-fit allocator with segregated size bins, a brk-grown heap, and an
* mmap path for large blocks. Layout reference: musl's single-file malloc;
* this is a clean-room implementation with vlibc's own chunk format.
*
* Chunk layout (every chunk address is 16-aligned; every chunk size is a
* multiple of VLIBC_CHUNK_ALIGN = 32):
*
* struct chunk { size_t psize, csize; } (16-byte header)
* user pointer = chunk + 16
*
* csize low bits (masked with ~3 when reading sizes):
* bit 0 VLIBC_CHUNK_INUSE — chunk is allocated.
* bit 1 VLIBC_CHUNK_ALIGNED — chunk is the raw block of an
* aligned_alloc-style allocation.
* psize low bits (masked with ~7 when reading sizes):
* bit 0 — the previous chunk is in use (1) or free (0).
* bit 2 VLIBC_CHUNK_MMAP — this chunk lives in its own mmap, psize
* holds the mapping length.
*
* Free chunks keep their doubly-linked-list pointers (next/prev) in the
* first 16 bytes of the user area, which is why the minimum chunk is
* 16 (header) + 16 (pointers) = 32 bytes. There is never more than one
* adjacent pair of free chunks: free() coalesces immediately, and the
* top chunk (the free block at the end of the heap) is kept out of the
* bins.
*
* Allocation: requests whose normalized chunk size reaches
* VLIBC_MMAP_THRESHOLD (128 KiB) get a private SYS_mmap mapping (freed
* with SYS_munmap). Everything else searches the bins first-fit (bins 0-3
* hold the exact sizes 32/64/96/128, bins 4-12 hold log-size ranges up to
* just below the mmap threshold), then splits the top chunk, growing the
* heap with SYS_brk as needed. If brk fails, the request falls back to a
* single-chunk mmap.
*
* The brk heap never shrinks below its start address and releases whole
* trailing pages back to the kernel when the top chunk holds more than a
* page beyond the minimum chunk. sbrk() is deliberately not provided (see
* the plan: it conflicts with this allocator).
*
* Aligned allocations (alignment > 16): the raw block is a normal chunk
* marked VLIBC_CHUNK_ALIGNED; the returned pointer sits somewhere inside
* the block with a two-word descriptor directly before it:
*
* [q-16] = alignment (a power of two, so bit 0 is clear)
* [q-8] = raw pointer (the start of the block's user area)
*
* free() tells the two cases apart by the C_INUSE bit of the word at p-16:
* an allocated chunk always has it set, while a descriptor's alignment word
* always has it clear. realloc() on an aligned pointer preserves the
* alignment (via the descriptor) and malloc_usable_size() reports the raw
* block size minus the alignment offset.
*
* malloc(0) returns a unique minimum-size chunk. realloc(p, 0) frees p and
* returns NULL. calloc overflows and aligned_alloc/posix_memalign argument
* violations fail with NULL + errno (ENOMEM/EINVAL); posix_memalign returns
* the error number instead of setting errno and never modifies *memptr on
* failure. Over-requested brk chunks zero themselves when they come from a
* fresh page of the heap, which makes calloc cheap in the common case.
*
* Single-threaded for now: the pthread todo will add locking around the
* global allocator state.
*/
#define VLIBC_CHUNK_OVERHEAD 16 /* psize + csize */
#define VLIBC_CHUNK_ALIGN 32 /* chunk-size granularity */
#define VLIBC_CHUNK_MIN 32 /* smallest chunk: header + 2 pointers */
#define VLIBC_CHUNK_INUSE 1UL /* csize bit 0 */
#define VLIBC_CHUNK_ALIGNED 2UL /* csize bit 1 */
#define VLIBC_CHUNK_MMAP 4UL /* psize bit 2 (private mmap block) */
#define VLIBC_CSIZE_MASK (~(size_t)3)
#define VLIBC_PSIZE_MASK (~(size_t)7)
/* Requests whose normalized chunk size reaches this go straight to mmap. */
#define VLIBC_MMAP_THRESHOLD ((size_t)(128 * 1024))
/* 4 exact-size fast bins (32/64/96/128) + 9 log-range bins. */
#define VLIBC_BIN_COUNT 13
#define VLIBC_BIN_FAST 4
/* Largest user request that cannot overflow the chunk-size normalization. */
#define VLIBC_CHUNK_MAX_USER ((size_t)-1 - VLIBC_CHUNK_ALIGN - VLIBC_CHUNK_OVERHEAD - 1)
/* Minimum heap growth in bytes, rounded up to a page by the extender. */
#define VLIBC_HEAP_GROW ((size_t)32768)
struct vlibc_chunk
{
size_t psize;
size_t csize;
struct vlibc_chunk *next;
struct vlibc_chunk *prev;
};
static struct vlibc_chunk *vlibc_bins[VLIBC_BIN_COUNT];
static struct vlibc_chunk *vlibc_top; /* free block at the end of the heap */
static uintptr_t vlibc_heap_start; /* 0 = heap not yet queried */
static uintptr_t vlibc_heap_end; /* current brk */
static size_t vlibc_live; /* allocated chunks on the heap */
static size_t vlibc_live_mmap; /* allocated mmap chunks */
static void *
vlibc_aligned_core(size_t align, size_t size);
/* Round a user request up to a chunk size (multiple of VLIBC_CHUNK_ALIGN). */
static size_t
vlibc_norm(size_t n)
{
size_t sz =
(n + VLIBC_CHUNK_OVERHEAD + VLIBC_CHUNK_ALIGN - 1) & ~(size_t)(VLIBC_CHUNK_ALIGN - 1);
return sz < VLIBC_CHUNK_MIN ? VLIBC_CHUNK_MIN : sz;
}
/* Index of the bin a free chunk of the given (flag-free) size belongs to. */
static int
vlibc_bin_of(size_t sz)
{
if (sz <= (size_t)(VLIBC_BIN_FAST * VLIBC_CHUNK_ALIGN))
{
return (int)(sz / VLIBC_CHUNK_ALIGN) - 1;
}
return (int)(VLIBC_BIN_FAST + 63 - (unsigned)__builtin_clzll(sz / VLIBC_CHUNK_ALIGN) - 3);
}
static void
vlibc_bin_insert(struct vlibc_chunk *c)
{
int i = vlibc_bin_of(c->csize & VLIBC_CSIZE_MASK);
c->prev = NULL;
c->next = vlibc_bins[i];
if (vlibc_bins[i] != NULL)
{
vlibc_bins[i]->prev = c;
}
vlibc_bins[i] = c;
}
static void
vlibc_bin_remove(struct vlibc_chunk *c)
{
int i = vlibc_bin_of(c->csize & VLIBC_CSIZE_MASK);
if (c->prev != NULL)
{
c->prev->next = c->next;
}
else
{
vlibc_bins[i] = c->next;
}
if (c->next != NULL)
{
c->next->prev = c->prev;
}
c->next = NULL;
c->prev = NULL;
}
/* Record c (size sz, flag-free) as allocated and update the next header. */
static void
vlibc_mark_alloc(struct vlibc_chunk *c, size_t sz)
{
struct vlibc_chunk *next = (struct vlibc_chunk *)((char *)c + sz);
c->csize = sz | VLIBC_CHUNK_INUSE;
if ((uintptr_t)next < vlibc_heap_end && next != vlibc_top)
{
next->psize = c->csize;
}
}
/*
* Query the brk once and remember the heap window. Returns 0 on success;
* -1 leaves the window uninitialized so callers fall back to mmap.
*/
static int
vlibc_heap_init(void)
{
long r;
if (vlibc_heap_start != 0)
{
return 0;
}
r = __syscall1(SYS_brk, 0);
if (r < 0)
{
return -1;
}
vlibc_heap_start = ((uintptr_t)r + 15) & ~(uintptr_t)15;
vlibc_heap_end = vlibc_heap_start;
return 0;
}
/*
* Grow the heap by at least need bytes. On success the top chunk covers the
* new space (created if the heap was fully consumed); on failure the state
* is unchanged and the caller falls back to a private mmap.
*/
static int
vlibc_heap_extend(size_t need)
{
size_t want = need > VLIBC_HEAP_GROW ? need : VLIBC_HEAP_GROW;
uintptr_t new_end;
long r;
want = (want + 4095) & ~(size_t)4095;
new_end = vlibc_heap_end + want;
r = __syscall1(SYS_brk, (long)new_end);
if ((uintptr_t)r != new_end)
{
return -1;
}
if (vlibc_top != NULL)
{
vlibc_top->csize += want;
}
else
{
vlibc_top = (struct vlibc_chunk *)vlibc_heap_end;
vlibc_top->psize = 0 | VLIBC_CHUNK_INUSE; /* prev size unknown */
vlibc_top->csize = want;
}
vlibc_heap_end = new_end;
return 0;
}
/*
* Give whole trailing pages of the top chunk back to the kernel. Always
* keeps at least a minimum chunk so the heap window never closes.
*/
static void
vlibc_heap_trim(void)
{
size_t sz = vlibc_top->csize;
size_t release = (sz - VLIBC_CHUNK_MIN) & ~(size_t)4095;
uintptr_t new_end;
long r;
if (release < 4096)
{
return;
}
new_end = (uintptr_t)vlibc_top + sz - release;
r = __syscall1(SYS_brk, (long)new_end);
if ((uintptr_t)r == new_end)
{
vlibc_top->csize = sz - release;
vlibc_heap_end = new_end;
}
}
/* Private mmap block of normalized size nn. NULL on failure. */
static void *
vlibc_mmap_chunk(size_t nn)
{
size_t len;
long r;
struct vlibc_chunk *c;
if (nn > (size_t)-1 - VLIBC_CHUNK_OVERHEAD - 4095)
{
return NULL; /* the rounded mapping length would overflow */
}
len = (nn + VLIBC_CHUNK_OVERHEAD + 4095) & ~(size_t)4095;
r = __syscall6(SYS_mmap, 0, (long)len, 3, 0x22, -1, 0);
if ((uintptr_t)r > (uintptr_t)-4096)
{
return NULL; /* kernel returned -errno */
}
c = (struct vlibc_chunk *)r;
c->psize = len | VLIBC_CHUNK_MMAP;
c->csize = nn | VLIBC_CHUNK_INUSE;
vlibc_live_mmap++;
return (char *)c + VLIBC_CHUNK_OVERHEAD;
}
/* First free chunk with size >= nn anywhere in bins [bin_of(nn), end). */
static struct vlibc_chunk *
vlibc_bin_find(size_t nn)
{
int i;
for (i = vlibc_bin_of(nn); i < VLIBC_BIN_COUNT; i++)
{
struct vlibc_chunk *c;
for (c = vlibc_bins[i]; c != NULL; c = c->next)
{
if ((c->csize & VLIBC_CSIZE_MASK) >= nn)
{
return c;
}
}
}
return NULL;
}
/*
* Split free chunk c (current size s) into an nn-byte allocation and a free
* remainder. Requires s >= nn + MIN. The chunk after the original extent
* gets its psize pointed at the remainder: without that update a later free
* there would walk back over the stale (too large) size and land on the
* wrong chunk. When the split chunk reaches the top chunk, the remainder is
* absorbed into the top instead of entering a bin — a free chunk directly
* before top would violate the coalescing invariant and leave the top's
* predecessor size stale.
*/
static void
vlibc_split(struct vlibc_chunk *c, size_t s, size_t nn)
{
struct vlibc_chunk *rem = (struct vlibc_chunk *)((char *)c + nn);
struct vlibc_chunk *after = (struct vlibc_chunk *)((char *)c + s);
rem->psize = nn; /* prev still free at this instant */
rem->csize = s - nn;
c->csize = nn;
if (after == vlibc_top)
{
rem->csize += vlibc_top->csize;
rem->psize = nn | VLIBC_CHUNK_INUSE; /* c becomes allocated below */
vlibc_top = rem;
}
else if (after == (struct vlibc_chunk *)vlibc_heap_end)
{
rem->psize = nn | VLIBC_CHUNK_INUSE;
vlibc_top = rem;
}
else
{
if ((uintptr_t)after < vlibc_heap_end)
{
after->psize = s - nn;
}
vlibc_bin_insert(rem);
}
}
/*
* Carve nn bytes out of the top chunk and return the user pointer. When the
* remainder is at least a minimum chunk it becomes the new top; otherwise
* the whole top is consumed and the next extension starts a fresh one.
*/
static void *
vlibc_carve_top(size_t nn)
{
struct vlibc_chunk *c = vlibc_top;
size_t s = c->csize;
if (s >= nn + VLIBC_CHUNK_MIN)
{
struct vlibc_chunk *rem = (struct vlibc_chunk *)((char *)c + nn);
rem->psize = nn | VLIBC_CHUNK_INUSE;
rem->csize = s - nn;
c->csize = nn | VLIBC_CHUNK_INUSE;
vlibc_top = rem;
}
else
{
c->csize = s | VLIBC_CHUNK_INUSE;
vlibc_top = NULL;
}
vlibc_live++;
return (char *)c + VLIBC_CHUNK_OVERHEAD;
}
/*
* The allocator core (the internal seam src/internal/malloc.h documents).
* Allocates n bytes, 16-byte aligned; NULL + errno ENOMEM on failure. May
* return a unique pointer even when n == 0.
*/
hidden void *
__libc_malloc(size_t n) // NOLINT(bugprone-reserved-identifier)
{
size_t nn;
struct vlibc_chunk *c;
if (n > VLIBC_CHUNK_MAX_USER)
{
errno = ENOMEM;
return NULL;
}
nn = vlibc_norm(n);
if (nn >= VLIBC_MMAP_THRESHOLD)
{
void *p = vlibc_mmap_chunk(nn);
if (p == NULL)
{
errno = ENOMEM;
}
return p;
}
c = vlibc_bin_find(nn);
if (c != NULL)
{
size_t s = c->csize & VLIBC_CSIZE_MASK;
vlibc_bin_remove(c);
if (s >= nn + VLIBC_CHUNK_MIN)
{
vlibc_split(c, s, nn);
vlibc_mark_alloc(c, nn);
}
else
{
vlibc_mark_alloc(c, s);
}
vlibc_live++;
return (char *)c + VLIBC_CHUNK_OVERHEAD;
}
if (vlibc_heap_init() == 0 && vlibc_heap_extend(nn) == 0)
{
return vlibc_carve_top(nn);
}
{
void *p = vlibc_mmap_chunk(nn);
if (p == NULL)
{
errno = ENOMEM;
}
return p;
}
}
/*
* Release a block returned by __libc_malloc; NULL is a no-op. Coalesces
* with adjacent free chunks and the top chunk, then trims trailing pages.
*/
hidden void
__libc_free(void *p) // NOLINT(bugprone-reserved-identifier)
{
struct vlibc_chunk *c;
size_t sz;
if (p == NULL)
{
return;
}
c = (struct vlibc_chunk *)((char *)p - VLIBC_CHUNK_OVERHEAD);
if ((c->csize & VLIBC_CHUNK_INUSE) == 0)
{
/* Aligned allocation: the descriptor sits right before p. */
void *raw = *(void **)((char *)p - sizeof(void *));
p = raw;
c = (struct vlibc_chunk *)((char *)raw - VLIBC_CHUNK_OVERHEAD);
}
if ((c->psize & VLIBC_CHUNK_MMAP) != 0)
{
(void)__syscall2(SYS_munmap, (long)c, (long)(c->psize & VLIBC_PSIZE_MASK));
vlibc_live_mmap--;
return;
}
vlibc_live--;
sz = c->csize & VLIBC_CSIZE_MASK;
c->csize = sz; /* clear INUSE and ALIGNED */
/* Coalesce with the following chunk, top chunk first. */
{
struct vlibc_chunk *next = (struct vlibc_chunk *)((char *)c + sz);
if (next == vlibc_top)
{
c->csize += vlibc_top->csize;
vlibc_top = c;
}
else if ((uintptr_t)next < vlibc_heap_end && (next->csize & VLIBC_CHUNK_INUSE) == 0)
{
struct vlibc_chunk *after;
vlibc_bin_remove(next);
c->csize += next->csize;
after = (struct vlibc_chunk *)((char *)c + c->csize);
if ((uintptr_t)after < vlibc_heap_end)
{
after->psize = c->csize;
}
}
}
/* Coalesce with the preceding chunk when it is free. */
if ((c->psize & 1) == 0)
{
struct vlibc_chunk *prev =
(struct vlibc_chunk *)((char *)c - (c->psize & VLIBC_PSIZE_MASK));
struct vlibc_chunk *after;
vlibc_bin_remove(prev);
prev->csize += c->csize;
if (c == vlibc_top)
{
vlibc_top = prev;
}
c = prev;
after = (struct vlibc_chunk *)((char *)c + c->csize);
if ((uintptr_t)after < vlibc_heap_end)
{
after->psize = c->csize;
}
}
/*
* A free block that ends exactly at the heap end is — or joins — the
* top chunk. This must also run when the top was fully consumed
* (vlibc_top == NULL): a freed trailing chunk that merely entered a bin
* would leave the allocator without a top, and a later extension would
* then grow over the bin's space while stale top state lets trim shrink
* the brk across live chunks.
*/
{
struct vlibc_chunk *end = (struct vlibc_chunk *)((char *)c + (c->csize & VLIBC_CSIZE_MASK));
if (end == vlibc_top)
{
c->csize += vlibc_top->csize;
vlibc_top = c;
}
else if (end == (struct vlibc_chunk *)vlibc_heap_end)
{
vlibc_top = c;
}
}
if (c == vlibc_top)
{
vlibc_heap_trim();
}
else
{
vlibc_bin_insert(c);
}
}
/*
* The public malloc: thin forward to the internal seam so that every
* allocation path in the library shares one implementation.
*/
void *
malloc(size_t n)
{
return __libc_malloc(n);
}
/* The public free: thin forward to the internal seam. */
void
free(void *p)
{
__libc_free(p);
}
/*
* Allocate zeroed memory for nmemb elements of size bytes. The product is
* overflow-checked; NULL + errno ENOMEM on failure. calloc(n, 0) and
* calloc(0, n) return a unique zeroed minimum-size chunk like malloc(0).
*/
void *
calloc(size_t nmemb, size_t size)
{
size_t total;
void *p;
struct vlibc_chunk *c;
size_t usable;
size_t i;
if (size != 0 && nmemb > (size_t)-1 / size)
{
errno = ENOMEM;
return NULL;
}
total = nmemb * size;
p = malloc(total);
if (p == NULL)
{
return NULL;
}
c = (struct vlibc_chunk *)((char *)p - VLIBC_CHUNK_OVERHEAD);
if ((c->psize & VLIBC_CHUNK_MMAP) != 0)
{
return p; /* a fresh mmap is already zero */
}
usable = (c->csize & VLIBC_CSIZE_MASK) - VLIBC_CHUNK_OVERHEAD;
for (i = 0; i < usable; i++)
{
((unsigned char *)p)[i] = 0;
}
return p;
}
/*
* Resize the block at p to n bytes. realloc(NULL, n) is malloc(n);
* realloc(p, 0) frees p and returns NULL. Growth first tries to merge the
* following free chunk (or the top chunk); otherwise the block is copied to
* a fresh allocation and freed. On failure the original block is untouched
* and errno is ENOMEM.
*/
void *
realloc(void *p, size_t n)
{
struct vlibc_chunk *c;
size_t sz;
size_t nn;
void *np;
size_t old;
size_t copy;
size_t i;
if (p == NULL)
{
return malloc(n);
}
if (n == 0)
{
free(p);
return NULL;
}
if (n > VLIBC_CHUNK_MAX_USER)
{
errno = ENOMEM;
return NULL;
}
nn = vlibc_norm(n);
c = (struct vlibc_chunk *)((char *)p - VLIBC_CHUNK_OVERHEAD);
if ((c->csize & VLIBC_CHUNK_INUSE) == 0)
{
/* Aligned allocation: re-allocate with the original alignment. */
size_t align = *(size_t *)((char *)p - 2 * sizeof(size_t));
void *raw = *(void **)((char *)p - sizeof(void *));
struct vlibc_chunk *rc = (struct vlibc_chunk *)((char *)raw - VLIBC_CHUNK_OVERHEAD);
old =
(rc->csize & VLIBC_CSIZE_MASK) - VLIBC_CHUNK_OVERHEAD - ((uintptr_t)p - (uintptr_t)raw);
if (n % align != 0)
{
errno = EINVAL;
return NULL;
}
np = vlibc_aligned_core(align, n);
if (np == NULL)
{
return NULL;
}
copy = old < n ? old : n;
for (i = 0; i < copy; i++)
{
((unsigned char *)np)[i] = ((unsigned char *)p)[i];
}
free(p);
return np;
}
if ((c->psize & VLIBC_CHUNK_MMAP) != 0)
{
old = (c->csize & VLIBC_CSIZE_MASK) - VLIBC_CHUNK_OVERHEAD;
np = malloc(n);
if (np == NULL)
{
return NULL;
}
copy = old < n ? old : n;
for (i = 0; i < copy; i++)
{
((unsigned char *)np)[i] = ((unsigned char *)p)[i];
}
free(p);
return np;
}
sz = c->csize & VLIBC_CSIZE_MASK;
if (nn <= sz)
{
if (sz >= nn + VLIBC_CHUNK_MIN)
{
/* Shrink in place, releasing the tail as a free chunk. */
vlibc_split(c, sz, nn);
vlibc_mark_alloc(c, nn);
}
return p;
}
/* Try to grow into the following free chunk (or the top chunk). */
{
struct vlibc_chunk *next = (struct vlibc_chunk *)((char *)c + sz);
if (next == vlibc_top)
{
size_t total = sz + vlibc_top->csize;
if (total >= nn)
{
if (total >= nn + VLIBC_CHUNK_MIN)
{
struct vlibc_chunk *rem = (struct vlibc_chunk *)((char *)c + nn);
rem->psize = nn | VLIBC_CHUNK_INUSE;
rem->csize = total - nn;
c->csize = nn | VLIBC_CHUNK_INUSE;
vlibc_top = rem;
}
else
{
c->csize = total | VLIBC_CHUNK_INUSE;
vlibc_top = NULL;
}
return p;
}
}
else if ((uintptr_t)next < vlibc_heap_end && (next->csize & VLIBC_CHUNK_INUSE) == 0)
{
size_t total = sz + (next->csize & VLIBC_CSIZE_MASK);
if (total >= nn)
{
vlibc_bin_remove(next);
if (total >= nn + VLIBC_CHUNK_MIN)
{
struct vlibc_chunk *rem = (struct vlibc_chunk *)((char *)c + nn);
struct vlibc_chunk *after;
rem->psize = nn | VLIBC_CHUNK_INUSE;
rem->csize = total - nn;
c->csize = nn | VLIBC_CHUNK_INUSE;
after = (struct vlibc_chunk *)((char *)rem + rem->csize);
if (after == vlibc_top)
{
rem->csize += vlibc_top->csize;
vlibc_top = rem;
}
else if (after == (struct vlibc_chunk *)vlibc_heap_end)
{
vlibc_top = rem;
}
else
{
vlibc_bin_insert(rem);
if ((uintptr_t)after < vlibc_heap_end)
{
after->psize = rem->csize;
}
}
}
else
{
struct vlibc_chunk *after = (struct vlibc_chunk *)((char *)c + total);
c->csize = total | VLIBC_CHUNK_INUSE;
if ((uintptr_t)after < vlibc_heap_end)
{
after->psize = c->csize;
}
}
return p;
}
/* Keep next linked; fall through to alloc-copy-free. */
}
}
/* Allocate fresh, copy the smaller of the two payloads, free the old. */
old = sz - VLIBC_CHUNK_OVERHEAD;
np = malloc(n);
if (np == NULL)
{
return NULL;
}
copy = old < n ? old : n;
for (i = 0; i < copy; i++)
{
((unsigned char *)np)[i] = ((unsigned char *)p)[i];
}
free(p);
return np;
}
/*
* Core of the aligned family: allocate size bytes aligned to align (a
* power of two greater than 16). The returned pointer has a two-word
* descriptor directly before it ([q-16] = align, [q-8] = raw pointer).
*/
static void *
vlibc_aligned_core(size_t align, size_t size)
{
void *raw;
void *q;
struct vlibc_chunk *c;
raw = malloc(size + align + VLIBC_CHUNK_ALIGN);
if (raw == NULL)
{
return NULL;
}
q = (void *)(((uintptr_t)raw + VLIBC_CHUNK_ALIGN + align - 1) & -(uintptr_t)align);
*(size_t *)((char *)q - 2 * sizeof(size_t)) = align;
*(void **)((char *)q - sizeof(void *)) = raw;
c = (struct vlibc_chunk *)((char *)raw - VLIBC_CHUNK_OVERHEAD);
c->csize |= VLIBC_CHUNK_ALIGNED;
return q;
}
/*
* Allocate size bytes aligned to alignment. alignment must be a power of
* two that is a multiple of sizeof(void *), and size must be a multiple of
* alignment; a violation fails with NULL + errno EINVAL (a non-power-of-two
* alignment is undefined behavior in C23, so only well-formed arguments
* reach the allocator). size 0 returns NULL. The returned pointer is a
* valid malloc block and is released with free.
*/
void *
aligned_alloc(size_t alignment, size_t size)
{
if ((alignment & (alignment - 1)) != 0 || alignment % sizeof(void *) != 0 ||
size % alignment != 0)
{
errno = EINVAL;
return NULL;
}
if (size == 0)
{
return NULL;
}
if (alignment <= VLIBC_CHUNK_OVERHEAD)
{
return malloc(size);
}
return vlibc_aligned_core(alignment, size);
}
/*
* Allocate size bytes at address alignment and store the result in
* *memptr. alignment must be a power of two and a multiple of
* sizeof(void *). Returns 0 on success, EINVAL for a bad alignment, ENOMEM
* on allocation failure; never sets errno itself and never modifies
* *memptr on failure. size 0 returns a unique minimum-size block.
*/
int
posix_memalign(void **memptr, size_t alignment, size_t size)
{
void *p;
if (memptr == NULL || (alignment & (alignment - 1)) != 0 || alignment % sizeof(void *) != 0)
{
return EINVAL;
}
p = vlibc_aligned_core(alignment, size == 0 ? 1 : size);
if (p == NULL)
{
return ENOMEM;
}
*memptr = p;
return 0;
}
#if VLIBC_LEVEL_GE(2)
/*
* Return the number of bytes actually available in the block at p,
* including any internal padding. p may be any block returned by the
* allocator family. NULL returns 0.
*/
size_t
malloc_usable_size(void *p)
{
struct vlibc_chunk *c;
if (p == NULL)
{
return 0;
}
c = (struct vlibc_chunk *)((char *)p - VLIBC_CHUNK_OVERHEAD);
if ((c->csize & VLIBC_CHUNK_INUSE) == 0)
{
/* Aligned allocation: report the raw block minus the offset. */
void *raw = *(void **)((char *)p - sizeof(void *));
struct vlibc_chunk *rc = (struct vlibc_chunk *)((char *)raw - VLIBC_CHUNK_OVERHEAD);
if ((rc->psize & VLIBC_CHUNK_MMAP) != 0)
{
return (rc->psize & VLIBC_PSIZE_MASK) - VLIBC_CHUNK_OVERHEAD -
((uintptr_t)p - (uintptr_t)raw);
}
return (rc->csize & VLIBC_CSIZE_MASK) - VLIBC_CHUNK_OVERHEAD -
((uintptr_t)p - (uintptr_t)raw);
}
if ((c->psize & VLIBC_CHUNK_MMAP) != 0)
{
return (c->psize & VLIBC_PSIZE_MASK) - VLIBC_CHUNK_OVERHEAD;
}
return (c->csize & VLIBC_CSIZE_MASK) - VLIBC_CHUNK_OVERHEAD;
}
#endif /* VLIBC_LEVEL_GE(2) */
/*
* Internal consistency hook used by the test suite: walk the whole heap
* chunk-by-chunk (an independent account of every live allocation), sum
* the in-use chunks, and compare against the allocator's own counters.
* Returns the total number of live blocks, or (size_t)-1 when the heap
* walk disagrees with the counters (corruption or an accounting bug).
*/
hidden size_t
__vlibc_malloc_check(void) // NOLINT(bugprone-reserved-identifier)
{
uintptr_t cur;
size_t live = 0;
if (vlibc_heap_start == 0)
{
return vlibc_live + vlibc_live_mmap == 0 ? 0 : (size_t)-1;
}
cur = vlibc_heap_start;
while (cur + VLIBC_CHUNK_MIN <= vlibc_heap_end)
{
struct vlibc_chunk *c = (struct vlibc_chunk *)cur;
size_t sz = c->csize & VLIBC_CSIZE_MASK;
if (sz < VLIBC_CHUNK_MIN || sz % VLIBC_CHUNK_ALIGN != 0 || cur + sz > vlibc_heap_end)
{
return (size_t)-1;
}
if ((c->csize & VLIBC_CHUNK_INUSE) != 0)
{
live++;
}
cur += sz;
}
if (cur != vlibc_heap_end || live != vlibc_live)
{
return (size_t)-1;
}
return live + vlibc_live_mmap;
}
+48
View File
@@ -0,0 +1,48 @@
/*
* vlibc — x86_64 longjmp (todo 4).
*
* longjmp(env, val) / _longjmp(env, val): restore the environment saved by
* setjmp and make that setjmp return val — with 0 coerced to 1 (POSIX:
* longjmp(env, 0) must return 1 so it is distinguishable from the initial
* setjmp return of 0). Never returns.
*
* Restores the FP control state (MXCSR and x87 control word), the
* callee-saved registers, then jumps to the saved rip with the saved rsp,
* exactly as if setjmp had returned val.
*
* Registers: env arrives in rdi and is moved to r12, a callee-saved scratch
* register that is itself restored from the buffer, so it can serve as the
* base for every load. val rides in rax, which is never restored from the
* buffer, and is therefore the value setjmp observes. The saved rsp and rip
* travel through rdx and rcx (both caller-saved, safe to clobber) until the
* final `mov %rdx,%rsp; jmp *%rcx`.
*/
.global longjmp
.global _longjmp
.type longjmp,@function
.type _longjmp,@function
longjmp:
_longjmp:
mov %rsi,%rax /* val; coerce 0 to 1 below */
test %rax,%rax
jnz 1f
inc %rax
1:
mov %rdi,%r12 /* env base (r12 is restored from the buffer later) */
ldmxcsr 64(%r12)
fldcw 72(%r12)
mov 0(%r12),%rbx
mov 8(%r12),%rbp
mov 32(%r12),%r14
mov 40(%r12),%r15
mov 48(%r12),%rdx /* saved rsp */
mov 56(%r12),%rcx /* saved rip */
mov 24(%r12),%r13
mov 16(%r12),%r12 /* last read of env */
mov %rdx,%rsp
jmp *%rcx
.size longjmp,.-longjmp
.size _longjmp,.-_longjmp
.section .note.GNU-stack,"",@progbits
+49
View File
@@ -0,0 +1,49 @@
/*
* vlibc — x86_64 setjmp (todo 4).
*
* setjmp(env) / _setjmp(env): save the calling environment into env and
* return 0. Both entry points share one body; the setjmp macro in
* include/setjmp.h expands to _setjmp, while the plain symbol is kept so
* address-takers and #undef-users still link.
*
* jmp_buf slot map (the authoritative layout; see include/setjmp.h):
*
* [0] rbx [1] rbp [2] r12 [3] r13 [4] r14
* [5] r15 [6] rsp [7] rip [8] mxcsr [9] x87 control word
*
* The saved rsp is the caller's stack pointer (rsp + 8, past the return
* address pushed by the call); the saved rip is that return address, so
* longjmp can resume exactly as if setjmp had returned. The FP control
* state (MXCSR and the x87 control word) is saved as well, so a longjmp out
* of code that changed rounding/trap state restores it — this must be
* assembly: C cannot access the register file.
*
* Only the caller's callee-saved registers are preserved by this function
* itself (rbx is saved into the buffer before any use); caller-saved
* registers are untouched, and the return value 0 is delivered in eax.
*/
.global setjmp
.global _setjmp
.type setjmp,@function
.type _setjmp,@function
setjmp:
_setjmp:
mov %rbx,0(%rdi)
mov %rbp,8(%rdi)
mov %r12,16(%rdi)
mov %r13,24(%rdi)
mov %r14,32(%rdi)
mov %r15,40(%rdi)
lea 8(%rsp),%rdx
mov %rdx,48(%rdi)
mov (%rsp),%rax
mov %rax,56(%rdi)
stmxcsr 64(%rdi)
fnstcw 72(%rdi)
xor %eax,%eax
ret
.size setjmp,.-setjmp
.size _setjmp,.-_setjmp
.section .note.GNU-stack,"",@progbits
+55
View File
@@ -0,0 +1,55 @@
/*
* vlibc — x86_64 siglongjmp (todo 4).
*
* siglongjmp(env, val): like longjmp, but when the buffer's saved-mask flag
* (word 10, see include/setjmp.h) is set, first restore the signal mask
* stored at word 11 with rt_sigprocmask(SIG_SETMASK). Never returns.
*
* The syscall is made inline here: siglongjmp is naked assembly that must
* not call into C while the register set is half-restored. The kernel ABI
* facts hardcoded below mirror src/internal/syscall.h:
*
* SYS_rt_sigprocmask = 14, SIG_SETMASK = 2, sigsetsize = 8
* (x86_64 Linux sigset_t is a single 64-bit word)
*
* Register discipline matches longjmp.s: env moves to r12 (callee-saved
* scratch, restored from the buffer last), val rides in rax — stashed in
* r13 across the syscall, which clobbers rax — and the saved rsp/rip travel
* through rdx/rcx until the final `mov %rdx,%rsp; jmp *%rcx`.
*/
.global siglongjmp
.type siglongjmp,@function
siglongjmp:
mov %rsi,%rax /* val; coerce 0 to 1 below */
test %rax,%rax
jnz 1f
inc %rax
1:
mov %rdi,%r12 /* env base (r12 is restored from the buffer later) */
cmpl $0,80(%r12) /* saved-mask flag */
jz 2f
mov %rax,%r13 /* val must survive the syscall */
mov $14,%eax /* SYS_rt_sigprocmask */
mov $2,%edi /* SIG_SETMASK */
lea 88(%r12),%rsi /* set = &env[11] */
xor %edx,%edx /* oldset = NULL */
mov $8,%r10d /* sigsetsize (x86_64 sigset_t = one word) */
syscall
mov %r13,%rax /* val back in rax */
2:
ldmxcsr 64(%r12)
fldcw 72(%r12)
mov 0(%r12),%rbx
mov 8(%r12),%rbp
mov 32(%r12),%r14
mov 40(%r12),%r15
mov 48(%r12),%rdx /* saved rsp */
mov 56(%r12),%rcx /* saved rip */
mov 24(%r12),%r13
mov 16(%r12),%r12 /* last read of env */
mov %rdx,%rsp
jmp *%rcx
.size siglongjmp,.-siglongjmp
.section .note.GNU-stack,"",@progbits
+75
View File
@@ -0,0 +1,75 @@
/*
* vlibc — x86_64 sigsetjmp (todo 4).
*
* __sigsetjmp(env, savemask) / sigsetjmp(env, savemask): like setjmp, plus
* the current signal mask is saved into env when savemask is nonzero. Both
* entry points share one body; the sigsetjmp macro in include/setjmp.h
* expands to __sigsetjmp, while the plain symbol is kept so address-takers
* and #undef-users still link.
*
* Slot map (authoritative; see include/setjmp.h): a sigjmp_buf holds the
* base jmp_buf layout at words 0..9 (same offsets as plain setjmp, so the
* shared longjmp restore logic works on either storage), plus:
*
* [10] saved-mask flag (1 when the signal mask was saved)
* [11] signal mask (the x86_64 Linux sigset_t: a single 64-bit word)
*
* The saved-mask flag is cleared before anything else, so a buffer saved
* with savemask == 0 — or jumped to by plain longjmp — never presents a
* stale flag to siglongjmp. When savemask is nonzero, the mask is queried
* with a raw rt_sigprocmask syscall made inline right here: this function
* must stay pure assembly, because the saved rip/rsp resume DIRECTLY in the
* caller (an intermediate C frame would have its return-address slot above
* the saved stack pointer, where intervening calls would clobber it before
* the longjmp). The kernel ABI facts below mirror src/internal/syscall.h:
*
* SYS_rt_sigprocmask = 14, SIG_BLOCK = 0 (NULL newset queries),
* sigsetsize = 8 (x86_64 sigset_t is one 64-bit word)
*
* The mask slot is written through &env[11]; on success the flag is set and
* 0 is returned in eax, exactly like plain setjmp.
*
* rbx carries the env base across the syscall (rdi/rsi/rdx/r10 hold the
* syscall arguments, rax the number, rcx/r11 are clobbered by the kernel).
* rbx is callee-saved: its original value was stored in env[0] above, so it
* is reloaded from there BEFORE the ret, leaving every callee-saved register
* intact on the ordinary return path as the SysV AMD64 ABI requires.
*/
.global __sigsetjmp
.global sigsetjmp
.type __sigsetjmp,@function
.type sigsetjmp,@function
__sigsetjmp:
sigsetjmp:
movq $0,80(%rdi) /* clear the saved-mask flag */
mov %rbx,0(%rdi)
mov %rbp,8(%rdi)
mov %r12,16(%rdi)
mov %r13,24(%rdi)
mov %r14,32(%rdi)
mov %r15,40(%rdi)
lea 8(%rsp),%rdx
mov %rdx,48(%rdi)
mov (%rsp),%rax
mov %rax,56(%rdi)
stmxcsr 64(%rdi)
fnstcw 72(%rdi)
test %esi,%esi
jz 1f
mov %rdi,%rbx /* env base (scratch; caller rbx is in env[0]) */
mov $14,%eax /* SYS_rt_sigprocmask */
xor %edi,%edi /* how = SIG_BLOCK(0) */
xor %esi,%esi /* set = NULL (query only) */
lea 88(%rbx),%rdx /* oldset = &env[11] */
mov $8,%r10d /* sigsetsize (x86_64 sigset_t = one word) */
syscall
movq $1,80(%rbx) /* mask saved: set the flag */
mov 0(%rbx),%rbx /* restore the caller's rbx before returning */
1:
xor %eax,%eax
ret
.size __sigsetjmp,.-__sigsetjmp
.size sigsetjmp,.-sigsetjmp
.section .note.GNU-stack,"",@progbits
+33
View File
@@ -0,0 +1,33 @@
#include "../internal/libc.h"
#include "start.h"
/*
* vlibc — environ (todo 3).
*
* POSIX exposes the process environment as the global `environ`: a pointer
* to the array of "name=value" strings, terminated by a NULL pointer. The
* kernel places the initial environment on the stack at exec time; crt1.s
* forwards the envp pointer to __libc_start_main, which stores it here
* before main runs, so consumers see a fully initialized environment from
* the first line of main.
*
* environ is the single storage object. __environ is an internal hidden
* alias of that same storage: library code (getenv/setenv, todo 13) goes
* through the internal name so the public symbol is never referenced from
* inside the library, and setenv mutating environ is visible through
* __environ and vice versa. A plain exported mutable global is exactly what
* POSIX requires (setenv replaces the array pointer).
*
* exit() deliberately does not touch environ: the strings and pointer array
* live on the kernel-provided initial stack, not the heap — there is
* nothing to free.
*/
char **environ = 0;
/*
* Internal alias of environ: same storage, hidden visibility. The alias is
* what makes the two spellings indistinguishable to setenv/getenv.
*/
hidden extern __typeof(environ) __environ
__attribute__((__alias__("environ"))); // NOLINT(bugprone-reserved-identifier)
+110
View File
@@ -0,0 +1,110 @@
#include "../internal/syscall.h"
#include "start.h"
/*
* vlibc — exit/_Exit/_exit/quick_exit/atexit/at_quick_exit (todo 3).
*
* Handler storage is a FIXED pre-allocated array (VLIBC_EXIT_HANDLERS_MAX
* entries): exit must work when malloc cannot, and the allocator is a later
* todo — no allocation happens here, ever. The atexit list and the
* at_quick_exit list are STRICTLY SEPARATE per POSIX: exit() runs only the
* atexit list, quick_exit() only the at_quick_exit list, never both.
*
* Order: both lists run in reverse registration order (LIFO) — the handler
* registered last runs first (C11 7.22.4.4, POSIX exit()).
*
* Registration once termination has begun is undefined behavior per POSIX.
* Rather than corrupt the list or crash, atexit/at_quick_exit gracefully
* refuse: the terminating flag makes them return -1. Registering a 33rd
* handler (list full) also returns -1, the POSIX failure convention. A
* handler calling exit() again is likewise UB; the count-based loops below
* simply keep running the remaining handlers — deterministic, no crash.
*
* Termination always goes through SYS_exit_group, so every thread of the
* process dies — the correct semantics once threading lands (#44).
*
* exit() is required to flush all open streams and remove tmpfile()
* creations before terminating. No streams exist yet: the stdio flush hook
* belongs to the stdio todo (#28) and slots in at the marker below.
*/
#define VLIBC_EXIT_HANDLERS_MAX 32
struct exit_handler_list
{
void (*fns[VLIBC_EXIT_HANDLERS_MAX])(void);
int count;
};
static struct exit_handler_list atexit_list;
static struct exit_handler_list quick_list;
static int terminating;
int
atexit(void (*func)(void))
{
if (func == 0 || terminating || atexit_list.count >= VLIBC_EXIT_HANDLERS_MAX)
{
return -1;
}
atexit_list.fns[atexit_list.count] = func;
atexit_list.count++;
return 0;
}
int
at_quick_exit(void (*func)(void))
{
if (func == 0 || terminating || quick_list.count >= VLIBC_EXIT_HANDLERS_MAX)
{
return -1;
}
quick_list.fns[quick_list.count] = func;
quick_list.count++;
return 0;
}
void
exit(int status)
{
terminating = 1;
while (atexit_list.count > 0)
{
atexit_list.count--;
atexit_list.fns[atexit_list.count]();
}
/* stdio hook (#28): flush + close all streams, remove tmpfile files. */
_exit(status);
}
void
quick_exit(int status)
{
terminating = 1;
while (quick_list.count > 0)
{
quick_list.count--;
quick_list.fns[quick_list.count]();
}
_exit(status);
}
void
_exit(int status)
{
__syscall1(SYS_exit_group, (long)status);
/*
* exit_group never returns for a valid status; the loop is defensive
* (and satisfies the noreturn contract without unreachable-code UB).
*/
for (;;)
{
__syscall1(SYS_exit, (long)status);
}
}
void
_Exit(int status)
{
_exit(status);
}
+29
View File
@@ -0,0 +1,29 @@
#include "start.h"
/*
* vlibc — __libc_start_main (todo 3).
*
* Called by crt1.s's _start with (main, argc, argv, envp) after the kernel
* has placed argc/argv/envp/auxv on the initial stack. In order:
*
* 1. install environ from the kernel-provided envp — before main runs, so
* consumers see the environment from the first line of main;
* 2. bootstrap main-thread TLS (static case; init_main_tls skips itself
* when the FS thread pointer is already set, the dynamic-loader #62
* case);
* 3. call main(argc, argv);
* 4. route main's return value into exit().
*
* Never returns. The signature is vlibc's own; crt1.s and the future
* dynamic loader (#62) must match it.
*/
void
__libc_start_main(
int (*main_fn)(int, char **, char **), int argc, char **argv,
char **envp) // NOLINT(bugprone-reserved-identifier,bugprone-easily-swappable-parameters)
{
environ = envp;
init_main_tls(envp);
exit(main_fn(argc, argv, envp));
}
+67
View File
@@ -0,0 +1,67 @@
#ifndef VLIBC_INTERNAL_START_H
#define VLIBC_INTERNAL_START_H
/*
* vlibc — program startup / termination surface (todo 3).
*
* The canonical prototypes of the startup ABI. These names are mandated by
* the C standard and POSIX, not invented by vlibc: exit/_Exit/atexit/
* at_quick_exit belong to <stdlib.h> and _exit to <unistd.h>, environ is
* the POSIX process environment global, and __libc_start_main is the CRT
* ABI entry point that crt1.s calls. Once <stdlib.h>/<unistd.h> land (later
* todos) their declarations move there; internal code keeps including this
* header until then, and consumers declare the names themselves.
*
* The standard mandates several names in the implementation- or
* standard-reserved namespace (_Exit, _exit, at_quick_exit,
* __libc_start_main, __environ); the NOLINT waiver below covers exactly
* those mandated names, nothing else.
*/
#include "../internal/libc.h"
// NOLINTBEGIN(bugprone-reserved-identifier,bugprone-easily-swappable-parameters)
/* Termination (src/start/exit.c). Never return. */
void
exit(int status) __attribute__((__noreturn__));
void
_Exit(int status) __attribute__((__noreturn__));
void
_exit(int status) __attribute__((__noreturn__));
void
quick_exit(int status) __attribute__((__noreturn__));
/* Handler registration (src/start/exit.c). 0 on success, -1 on failure. */
int
atexit(void (*func)(void));
int
at_quick_exit(void (*func)(void));
/*
* CRT ABI entry point (src/start/libc_start_main.c), called by crt1.s with
* (main, argc, argv, envp). Never returns: it routes main's result into
* exit(). The future dynamic loader (#62) must use the same signature.
*/
void
__libc_start_main(int (*main_fn)(int, char **, char **), int argc, char **argv, char **envp)
__attribute__((__noreturn__));
/* The POSIX process environment (src/start/environ.c). */
extern char **environ;
/* Internal alias of the same storage, hidden visibility (src/start/environ.c). */
extern char **__environ;
// NOLINTEND(bugprone-reserved-identifier,bugprone-easily-swappable-parameters)
/*
* Main-thread TLS bootstrap (src/start/tls.c): parse the auxv for the
* program headers, find PT_TLS, allocate the static TLS block + TCB, copy
* the image, and install the FS thread pointer via arch_prctl(ARCH_SET_FS).
* Skips itself when FS is already set (the dynamic-loader case, #62).
*/
hidden void
init_main_tls(char **envp);
#endif /* VLIBC_INTERNAL_START_H */
+87
View File
@@ -0,0 +1,87 @@
#ifndef VLIBC_INTERNAL_START_TCB_H
#define VLIBC_INTERNAL_START_TCB_H
/*
* vlibc — AUTHORITATIVE x86_64 TCB / thread-pointer layout (todo 3).
*
* This header defines the thread control block (TCB) layout ONCE, for the
* whole library. Later layers reference these constants and NEVER redefine
* them:
*
* #44 threading substrate: clones this layout for every new thread and
* initializes the fenv slot per thread.
* #45 sizes new-thread TLS from the static TLS block arithmetic below.
* #62 ld.so: performs the dynamic-case equivalent of this bootstrap (the
* FS-already-set guard in tls.c skips this code in that case).
* #42 fenv: reads/writes the reserved fenv slot through the fixed
* offsets below; the slot is per-thread lazy storage for the x87
* control word and MXCSR.
*
* ABI model — ELF TLS variant II on x86_64:
*
* The thread pointer (TP) is delivered by the %fs segment and points
* DIRECTLY at the TCB (this is what "variant II" means; variant I, used by
* e.g. 32-bit x86, points at the end of the TLS data instead). Static TLS
* data live at NEGATIVE offsets from TP (%fs:N with N < 0, the LE model);
* the TCB fields live at positive offsets. Slot 0 is required to hold the
* TCB's own address because __builtin_thread_pointer() reads %fs:0 — that
* is the entire mechanism by which C code recovers TP from a register.
*
* TCB field map (offsets are byte offsets from TP, i.e. from %fs:0):
*
* TP + 0 VLIBC_TCB_SELF_OFF TCB self pointer (TP itself).
* TP + 8 VLIBC_TCB_ERRNO_OFF per-thread errno slot: 8 bytes, errno
* occupies the low 4. This offset is OWNED
* by src/internal/errno.h (task 1 fixed it
* as an ABI constant) and is consumed
* here, never re-derived.
* TP + 16 VLIBC_TCB_FENV_OFF per-thread fenv reservation, 8 bytes:
* +16 VLIBC_TCB_FENV_X87CW_OFF uint16 x87 control word,
* reset default 0x037F (double
* precision, round-to-nearest)
* +18 (pad, reserved)
* +20 VLIBC_TCB_FENV_MXCSR_OFF uint32 MXCSR, reset default
* 0x1F80 (all exceptions masked)
* TP + 24 end of the fixed layout; VLIBC_TCB_SIZE = 24.
*
* The layout grows FORWARD from TP if later layers need more TCB fields;
* the fixed slots above are never renumbered. A DTV pointer is deliberately
* NOT reserved: the static TLS model needs no DTV (every access is
* %fs-relative with a link-time offset; there is no __tls_get_addr). Should
* #62's dynamic TLS ever require a DTV, add it at TP + VLIBC_TCB_SIZE —
* glibc's variant-II convention keeps the DTV at a negative offset relative
* to a separately allocated block; vlibc deliberately keeps the simpler
* grow-upward model documented here.
*
* Static TLS placement contract (tls.c implements this; the linker bakes
* its TPOFF arithmetic against the same formula, verified empirically with
* GNU ld):
*
* - tp = base + round_up(p_memsz, p_align), with base page-aligned.
* - the PT_TLS image is copied to [base, base + p_filesz), the BSS tail
* [base + p_filesz, base + p_memsz) is zeroed, and the TCB occupies
* [tp, tp + VLIBC_TCB_SIZE).
* - TP alignment equals p_align EXACTLY (never bumped to 16): the linker
* computes tpoff = var_offset - round_up(p_memsz, p_align), so any
* other rounding breaks every %fs offset when p_memsz is not a
* multiple of the runtime's alignment. When the program has no PT_TLS
* at all, p_align is taken as 16 and the block is TCB-only.
*
* TCB fields only need 8-byte alignment, and the linker guarantees
* p_align >= the alignment of every TLS object, so the exact-p_align
* rule is always sufficient.
*/
#include "../internal/errno.h" /* VLIBC_TCB_ERRNO_OFF */
#define VLIBC_TCB_SELF_OFF 0
/* VLIBC_TCB_ERRNO_OFF (8) is consumed from src/internal/errno.h. */
#define VLIBC_TCB_FENV_OFF 16
#define VLIBC_TCB_FENV_X87CW_OFF (VLIBC_TCB_FENV_OFF + 0)
#define VLIBC_TCB_FENV_MXCSR_OFF (VLIBC_TCB_FENV_OFF + 4)
#define VLIBC_TCB_SIZE 24
#endif /* VLIBC_INTERNAL_START_TCB_H */
+293
View File
@@ -0,0 +1,293 @@
#include <stdint.h>
#include <string.h>
#include "../internal/syscall.h"
#include "start.h"
#include "tcb.h"
/*
* vlibc — main-thread TLS bootstrap (todo 3).
*
* Makes the TCB-slot errno (and any other TCB state) work from the very
* first libc call in a STATICALLY linked program. The dynamic case is
* explicitly out of scope here: when the FS thread pointer is already set
* (the dynamic loader #62 installed it before transferring control), this
* function detects that and returns without touching anything.
*
* Mechanism:
* - The kernel puts the auxiliary vector on the initial stack right after
* envp's NULL terminator; AT_PHDR/AT_PHENT/AT_PHNUM locate the ELF
* program headers, which are searched for PT_TLS.
* - One anonymous private mmap holds the whole static TLS area: the TLS
* image at the base, the TCB at the top. The placement formula (tp =
* base + round_up(p_memsz, p_align)) is the exact arithmetic the linker
* baked into every %fs-relative offset; see tcb.h for the contract.
* - The image is copied from memory at p_vaddr: for a static non-PIE
* executable GNU ld lays PT_TLS out inside the RW PT_LOAD, so the
* template is already mapped (verified empirically). When no PT_LOAD
* covers the segment — a layout this project does not produce — the
* file fallback reads the segment from /proc/self/exe via pread.
* - The TCB's self pointer, errno slot, and fenv defaults are written,
* then arch_prctl(ARCH_SET_FS) installs TP. From that instruction on,
* __builtin_thread_pointer() (which reads %fs:0) returns TP and errno
* works.
*
* No errno is touched before arch_prctl: errno lives in the TCB, which does
* not exist until this function creates it — a pre-TLS errno read would
* dereference %fs:0 with the kernel's post-exec FS base of 0 and fault on
* the unmapped NULL page. Failures therefore terminate hard (exit 127 via
* exit_group) instead of reporting through errno.
*/
/* arch_prctl codes (kernel UAPI asm/prctl.h). */
#define ARCH_SET_FS 0x1002
#define ARCH_GET_FS 0x1003
/* auxv a_type values (kernel UAPI asm/auxvec.h). AT_RANDOM is deliberately
* not consumed: nothing in this todo needs a per-process random seed. */
#define AT_NULL 0
#define AT_PHDR 3
#define AT_PHENT 4
#define AT_PHNUM 5
/* ELF program header p_type values (kernel UAPI linux/elf.h). */
#define PT_LOAD 1
#define PT_TLS 7
/* mmap/openat/pread constants (kernel UAPI). */
#define PROT_READ 0x1
#define PROT_WRITE 0x2
#define MAP_PRIVATE 0x2
#define MAP_ANONYMOUS 0x20
#define AT_FDCWD (-100)
#define O_RDONLY 0
#define O_CLOEXEC 0x80000
/* ELF64 program header, exactly as laid out in the file/memory image. */
struct elf64_phdr
{
uint32_t p_type;
uint32_t p_flags;
uint64_t p_offset;
uint64_t p_vaddr;
uint64_t p_paddr;
uint64_t p_filesz;
uint64_t p_memsz;
uint64_t p_align;
};
/*
* Round v up to a multiple of a. a must be a power of two, which ELF
* p_align of a PT_TLS segment always is.
*/
static size_t
round_up(size_t v, size_t a)
{
return (v + a - 1) & ~(a - 1);
}
/*
* Hard-stop for unrecoverable bootstrap failures (mmap ENOMEM, a TLS image
* that is neither mapped nor readable). exit 127 — errno is unavailable
* here, because the TCB that would hold it does not exist yet.
*/
static _Noreturn void
tls_fail(void)
{
__syscall1(SYS_exit_group, 127);
__builtin_unreachable();
}
/*
* The helpers below take structurally similar out-parameters (auxv and ELF
* layout values), so the easily-swappable-parameters check is waived for
* this section: these are kernel/ELF ABI shapes, not caller-facing APIs.
*/
// NOLINTBEGIN(bugprone-easily-swappable-parameters)
/*
* Walk the auxiliary vector on the initial stack: envp points at the first
* environment string, the auxv follows envp's NULL terminator as pairs of
* (type, value) longs, ending at AT_NULL. The kernel wrote these on the
* stack at exec time, so the ORIGINAL envp (never the possibly-mutated
* environ global) must be used.
*/
static void
find_auxv(char **envp, uintptr_t *phdr, uintptr_t *phent, uintptr_t *phnum)
{
while (*envp != 0)
{
envp++;
}
envp++; /* step over the NULL terminator */
for (uintptr_t *aux = (uintptr_t *)envp; aux[0] != AT_NULL; aux += 2)
{
if (aux[0] == AT_PHDR)
{
*phdr = aux[1];
}
else if (aux[0] == AT_PHENT)
{
*phent = aux[1];
}
else if (aux[0] == AT_PHNUM)
{
*phnum = aux[1];
}
}
}
/*
* Search the program headers for PT_TLS and check whether the image is
* already mapped (some PT_LOAD covers [p_vaddr, p_vaddr + p_filesz)).
* Returns 1 when PT_TLS exists, filling the out parameters.
*/
static int
find_tls(uintptr_t phdr, uintptr_t phent, uintptr_t phnum, uint64_t *offset, uint64_t *vaddr,
size_t *filesz, size_t *memsz, size_t *align, int *mapped)
{
int found = 0;
uint64_t lo = 0;
uint64_t hi = 0;
for (uintptr_t i = 0; i < phnum; i++)
{
const struct elf64_phdr *p = (const struct elf64_phdr *)(phdr + i * phent);
if (p->p_type == PT_TLS)
{
*offset = p->p_offset;
*vaddr = p->p_vaddr;
*filesz = (size_t)p->p_filesz;
*memsz = (size_t)p->p_memsz;
*align = (size_t)p->p_align;
lo = p->p_vaddr;
hi = p->p_vaddr + p->p_filesz;
found = 1;
}
}
*mapped = 0;
if (found)
{
for (uintptr_t i = 0; i < phnum; i++)
{
const struct elf64_phdr *p = (const struct elf64_phdr *)(phdr + i * phent);
if (p->p_type == PT_LOAD && p->p_vaddr <= lo && hi <= p->p_vaddr + p->p_memsz)
{
*mapped = 1;
break;
}
}
}
return found;
}
/*
* Copy the initial TLS image (the .tdata template; .tbss is zeroed by the
* caller). Primary path: memcpy from the mapped image. Fallback: pread the
* segment out of /proc/self/exe — immune to chdir, works even for deleted
* executables, no host libc involved.
*/
static void
copy_tls_image(char *dst, uint64_t offset, uint64_t vaddr, size_t filesz, int mapped)
{
if (mapped)
{
memcpy(dst, (const void *)vaddr, filesz);
return;
}
long fd = __syscall3(SYS_openat, AT_FDCWD, (long)"/proc/self/exe", O_RDONLY | O_CLOEXEC);
if (fd < 0)
{
tls_fail();
}
size_t done = 0;
while (done < filesz)
{
long r = __syscall4(SYS_pread64, fd, (long)(dst + done), (long)(filesz - done),
(long)(offset + done));
if (r <= 0)
{
tls_fail();
}
done += (size_t)r;
}
__syscall1(SYS_close, fd);
}
// NOLINTEND(bugprone-easily-swappable-parameters)
void
init_main_tls(char **envp)
{
uintptr_t fs_base = 0;
uintptr_t phdr = 0, phent = 0, phnum = 0;
uint64_t offset = 0, vaddr = 0;
size_t filesz = 0, memsz = 0, align = 16;
int mapped = 0;
size_t block, total;
uintptr_t base, tp;
char *image;
/*
* Static-case guard. arch_prctl(ARCH_GET_FS) is used instead of
* __builtin_thread_pointer() because the latter dereferences %fs:0,
* and with the kernel's post-exec FS base of 0 that read faults on the
* unmapped NULL page. A non-zero FS base means the dynamic loader (#62)
* already installed a TCB: leave it alone.
*/
__syscall2(SYS_arch_prctl, ARCH_GET_FS, (long)&fs_base);
if (fs_base != 0)
{
return;
}
find_auxv(envp, &phdr, &phent, &phnum);
if (phdr != 0 && phent != 0 &&
!find_tls(phdr, phent, phnum, &offset, &vaddr, &filesz, &memsz, &align, &mapped))
{
/* No TLS in this program: TCB-only block, arbitrary alignment. */
filesz = 0;
memsz = 0;
align = 16;
}
/*
* Single allocation: TLS image at the base, TCB at tp. tp uses exactly
* p_align (never bumped): the linker bakes tpoff against
* round_up(p_memsz, p_align) and any other rounding breaks the
* %fs-relative offsets (see tcb.h).
*/
block = round_up(memsz, align);
total = block + VLIBC_TCB_SIZE;
base = (uintptr_t)__syscall6(SYS_mmap, 0, (long)total, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if ((long)base < 0 && (long)base > -4096)
{
/* ENOMEM and friends: no errno possible yet (the TCB IS errno). */
tls_fail();
}
tp = base + block;
image = (char *)base;
if (filesz > 0)
{
copy_tls_image(image, offset, vaddr, filesz, mapped);
}
if (memsz > filesz)
{
memset(image + filesz, 0, memsz - filesz);
}
/* Build the TCB at TP (authoritative layout in tcb.h). */
((uintptr_t *)tp)[VLIBC_TCB_SELF_OFF / sizeof(uintptr_t)] = tp;
*(int *)(tp + VLIBC_TCB_ERRNO_OFF) = 0;
*(uint16_t *)(tp + VLIBC_TCB_FENV_X87CW_OFF) = 0x037F; /* x87 reset default */
*(uint32_t *)(tp + VLIBC_TCB_FENV_MXCSR_OFF) = 0x1F80; /* MXCSR reset default */
/* From here on the TCB (and errno) is live. Install the thread pointer. */
__syscall2(SYS_arch_prctl, ARCH_SET_FS, (long)tp);
}
+40
View File
@@ -0,0 +1,40 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
#if VLIBC_LEVEL_GE(2)
/*
* XSI memccpy (todo 8): copy at most n bytes from src to dst, stopping
* after the first byte equal to c (converted to unsigned char). Return a
* pointer to the byte after c in dst when found, NULL otherwise. The
* regions must not overlap (restrict).
*
* See strcpy.c: the copy loop is a recognizable pattern, so disable
* -ftree-loop-distribute-patterns here too.
*/
__attribute__((optimize("no-tree-loop-distribute-patterns"))) void *
memccpy(void *restrict dst, const void *restrict src, int c, size_t n) // NOLINT(bugprone-*)
{
unsigned char *d = dst;
const unsigned char *s = src;
const unsigned char uc = (unsigned char)c;
while (n != 0)
{
*d = *s;
if (*d == uc)
{
return d + 1;
}
d++;
s++;
n--;
}
return NULL;
}
#endif /* VLIBC_LEVEL_GE(2) */
+34
View File
@@ -0,0 +1,34 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
/*
* Return a pointer to the first occurrence of c (converted to unsigned
* char) among the first n bytes of s, or NULL when absent. Bytes are
* examined as unsigned char so negative c values search for the matching
* 0x80..0xff byte.
*
* See strcpy.c: the scan loop is a pattern GCC's
* -ftree-loop-distribute-patterns can recognize (memchr), so disable that
* transformation to avoid self-recursion.
*/
__attribute__((optimize("no-tree-loop-distribute-patterns"))) void *
memchr(const void *s, int c, size_t n) // NOLINT(bugprone-easily-swappable-parameters)
{
const unsigned char *p = s;
const unsigned char uc = (unsigned char)c;
while (n != 0)
{
if (*p == uc)
{
return (void *)p;
}
p++;
n--;
}
return NULL;
}
+34
View File
@@ -0,0 +1,34 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
/*
* Compare the first n bytes of lhs and rhs as unsigned char; return
* negative, zero, or positive when lhs is less than, equal to, or greater
* than rhs. Unlike strcmp the comparison never stops at a NUL byte.
*
* See strcpy.c: the compare loop is a pattern GCC's
* -ftree-loop-distribute-patterns can recognize (memcmp), so disable that
* transformation to avoid self-recursion.
*/
__attribute__((optimize("no-tree-loop-distribute-patterns"))) int
memcmp(const void *lhs, const void *rhs, size_t n) // NOLINT(bugprone-easily-swappable-parameters)
{
const unsigned char *l = lhs;
const unsigned char *r = rhs;
while (n != 0)
{
if (*l != *r)
{
return *l < *r ? -1 : 1;
}
l++;
r++;
n--;
}
return 0;
}
+69
View File
@@ -0,0 +1,69 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
#if VLIBC_LEVEL_GE(2)
/*
* XSI stpcpy / stpncpy (todo 8): strcpy / strncpy variants that return a
* pointer to the terminating NUL of dst instead of dst itself.
*
* See strcpy.c: the copy loops are recognizable patterns, so disable
* -ftree-loop-distribute-patterns here too.
*/
/*
* Copy src to dst including the terminating NUL; return a pointer to the
* NUL written into dst.
*/
__attribute__((optimize("no-tree-loop-distribute-patterns"))) char *
stpcpy(char *restrict dst, const char *restrict src) // NOLINT(bugprone-*)
{
char *d = dst;
for (;;)
{
*d = *src;
if (*d == '\0')
{
return d;
}
d++;
src++;
}
}
/*
* Copy at most n bytes from src to dst, NUL-padding when src is shorter;
* return a pointer to the first NUL written into dst, or to dst + n when no
* NUL was written.
*/
__attribute__((optimize("no-tree-loop-distribute-patterns"))) char *
stpncpy(char *restrict dst, const char *restrict src, size_t n) // NOLINT(bugprone-*)
{
char *d = dst;
char *end;
/* Copy until src ends or n bytes have been copied. */
while (n != 0 && *src != '\0')
{
*d++ = *src++;
n--;
}
/* The first NUL (or one past the last byte) sits right here. */
end = d;
/* NUL-pad the remainder. */
while (n != 0)
{
*d++ = '\0';
n--;
}
return end;
}
#endif /* VLIBC_LEVEL_GE(2) */
+65
View File
@@ -0,0 +1,65 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
/*
* Append src (including its terminating NUL) to the end of dst; return dst.
* The strings must not overlap (restrict).
*
* See strcpy.c: the copy loop is a recognizable pattern, so disable
* -ftree-loop-distribute-patterns here too.
*/
__attribute__((optimize("no-tree-loop-distribute-patterns"))) char *
strcat(char *restrict dst, const char *restrict src) // NOLINT(bugprone-*)
{
char *d = dst;
/* Find the terminating NUL of dst. */
while (*d != '\0')
{
d++;
}
/* Append src including its NUL. */
for (;;)
{
*d++ = *src;
if (*src == '\0')
{
return dst;
}
src++;
}
}
/*
* Append at most n bytes of src to dst and always NUL-terminate; return dst.
*
* See strcpy.c: the copy loop is a recognizable pattern, so disable
* -ftree-loop-distribute-patterns here too.
*/
__attribute__((optimize("no-tree-loop-distribute-patterns"))) char *
strncat(char *restrict dst, const char *restrict src, size_t n) // NOLINT(bugprone-*)
{
char *d = dst;
/* Find the terminating NUL of dst. */
while (*d != '\0')
{
d++;
}
/* Append at most n bytes of src, stopping early at its NUL. */
while (n != 0 && *src != '\0')
{
*d++ = *src++;
n--;
}
/* strncat always NUL-terminates. */
*d = '\0';
return dst;
}
+52
View File
@@ -0,0 +1,52 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
/*
* Return a pointer to the first occurrence of c (converted to char) in s,
* or NULL when absent. The terminating NUL is part of the string, so
* strchr(s, '\0') returns a pointer to it.
*/
char *
strchr(const char *s, int c) // NOLINT(bugprone-easily-swappable-parameters)
{
const char cc = (char)c;
for (;; s++)
{
if (*s == cc)
{
return (char *)s;
}
if (*s == '\0')
{
return NULL;
}
}
}
/*
* Return a pointer to the last occurrence of c (converted to char) in s, or
* NULL when absent. The terminating NUL is part of the string, so
* strrchr(s, '\0') returns a pointer to it.
*/
char *
strrchr(const char *s, int c) // NOLINT(bugprone-easily-swappable-parameters)
{
const char cc = (char)c;
const char *last = NULL;
for (;; s++)
{
if (*s == cc)
{
last = s;
}
if (*s == '\0')
{
return (char *)last;
}
}
}
+79
View File
@@ -0,0 +1,79 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
/*
* C-locale collation (todo 49 owns real locales). In the "C" locale the
* collating sequence is the machine collating sequence — byte order — so
* strcoll degenerates to strcmp, and the strxfrm transformation is the
* identity: the transformed form of a string is the string itself.
*/
/*
* Compare s1 and s2 under the active locale's collating sequence and return
* negative, zero, or positive. "C" locale: identical to strcmp.
*/
int
strcoll(const char *s1, const char *s2) // NOLINT(bugprone-easily-swappable-parameters)
{
return strcmp(s1, s2);
}
/*
* Transform src so that strcmp on transformed strings orders them as
* strcoll would, storing at most n bytes of the result in dst (always
* NUL-terminated when n > 0; nothing is written when n == 0). Return the
* length of the full transformed string excluding the NUL — in the "C"
* locale, strlen(src).
*/
size_t
strxfrm(char *restrict dst, const char *restrict src,
size_t n) // NOLINT(bugprone-easily-swappable-parameters)
{
size_t len = strlen(src);
size_t copy;
size_t i;
if (n == 0)
{
return len;
}
copy = len < n ? len : n - 1;
for (i = 0; i < copy; i++)
{
dst[i] = src[i];
}
dst[copy] = '\0';
return len;
}
#if VLIBC_LEVEL_GE(2)
/*
* XSI locale-parameterized variants. No locale machinery exists yet (todo
* 49), so the locale argument is accepted and ignored and the behavior is
* the "C" locale behavior of the base functions. The locale parameter is
* typed void * for now: locale_t will be an ABI-identical pointer typedef
* defined by <locale.h>, and todo 49 updates these signatures to the real
* type.
*/
int
strcoll_l(const char *s1, const char *s2,
void *locale) // NOLINT(bugprone-easily-swappable-parameters)
{
(void)locale;
return strcoll(s1, s2);
}
size_t
strxfrm_l(char *restrict dst, const char *restrict src, size_t n,
void *locale) // NOLINT(bugprone-easily-swappable-parameters)
{
(void)locale;
return strxfrm(dst, src, n);
}
#endif /* VLIBC_LEVEL_GE(2) */
+31
View File
@@ -0,0 +1,31 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
/*
* Copy src to dst including the terminating NUL; return dst. The strings
* must not overlap (restrict).
*
* The copy loop below is the canonical strcpy idiom; GCC's
* -ftree-loop-distribute-patterns (on by default at -O2 and -O3) can rewrite
* such loops into calls to the recognized string builtins — potentially this
* very function — causing infinite self-recursion. Disable that one
* transformation for this function only.
*/
__attribute__((optimize("no-tree-loop-distribute-patterns"))) char *
strcpy(char *restrict dst, const char *restrict src) // NOLINT(bugprone-*)
{
char *d = dst;
for (;;)
{
*d++ = *src;
if (*src == '\0')
{
return dst;
}
src++;
}
}
+69
View File
@@ -0,0 +1,69 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
#include <errno.h>
#include "../internal/malloc.h"
/*
* strdup / strndup (todo 8).
*
* Both allocate through the internal allocator seam __libc_malloc (see
* src/internal/malloc.h; todo 7 provides the real allocator), so the copies
* are released with the public free. On allocation failure errno is set to
* ENOMEM and NULL is returned.
*/
/*
* Return a heap copy of s, or NULL with errno ENOMEM on allocation failure.
*/
char *
strdup(const char *s)
{
size_t len = strlen(s) + 1;
char *p = __libc_malloc(len);
size_t i;
if (p == NULL)
{
errno = ENOMEM;
return NULL;
}
for (i = 0; i < len; i++)
{
p[i] = s[i];
}
return p;
}
/*
* Return a heap copy of the first n bytes of s, NUL-terminated. When s is
* shorter than n the copy is the whole string; strndup(s, 0) returns the
* empty string. NULL with errno ENOMEM on allocation failure.
*/
char *
strndup(const char *s, size_t n)
{
size_t len = strnlen(s, n);
char *p = __libc_malloc(len + 1);
size_t i;
if (p == NULL)
{
errno = ENOMEM;
return NULL;
}
for (i = 0; i < len; i++)
{
p[i] = s[i];
}
p[len] = '\0';
return p;
}
+202
View File
@@ -0,0 +1,202 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
#include <strings.h>
#if VLIBC_LEVEL_GE(2)
/*
* vlibc — <strings.h> legacy functions (todo 10).
*
* The whole file self-gates at VLIBC_LEVEL >= 2 (the build wiring pass may
* compile it unconditionally): at level 1 this translation unit produces
* nothing, because every function here is XSI/BSD legacy and none is
* POSIX.1-2008 base.
*
* bcopy/bzero/bcmp delegate to the project's own memmove/memset/memcmp
* (satisfied from the archive at link time) — bcopy MUST NOT be a bare
* memcpy alias, because its contract includes overlapping regions.
* index/rindex are written as direct byte scans (own code, not strchr
* wrappers) so their documented unsigned char conversion is explicit.
*/
/*
* Fold an ASCII uppercase letter to lowercase; every other byte passes
* through unchanged. Kept local and locale-free: the BSD comparisons must
* never depend on ctype tables or the active locale.
*/
static inline unsigned char
fold_ascii_byte(unsigned char c)
{
if (c >= 'A' && c <= 'Z')
{
return (unsigned char)(c + ('a' - 'A'));
}
return c;
}
/*
* Compare s1 and s2 byte-wise ignoring case. Equal-folded runs are skipped
* with a raw-byte fast path; the first differing byte pair is compared
* folded, so "A" and "a" order as equal and the returned sign is that of
* the folded difference. The scan stops at the first NUL in either string.
*/
int
strcasecmp(const char *s1, const char *s2)
{
const unsigned char *l = (const unsigned char *)s1;
const unsigned char *r = (const unsigned char *)s2;
for (; *l == *r; l++, r++)
{
if (*l == '\0')
{
return 0;
}
}
return (int)fold_ascii_byte(*l) - (int)fold_ascii_byte(*r);
}
/*
* Compare at most n bytes of s1 and s2 as strcasecmp does, stopping early
* at the first folded difference or the first NUL. When the comparison
* ends at a NUL, the shorter (equal-prefix) string sorts first.
*/
int
strncasecmp(const char *s1, const char *s2, size_t n)
{
const unsigned char *l = (const unsigned char *)s1;
const unsigned char *r = (const unsigned char *)s2;
while (n != 0 && *l != '\0' && *r != '\0')
{
const unsigned char fl = fold_ascii_byte(*l);
const unsigned char fr = fold_ascii_byte(*r);
if (fl != fr)
{
return (int)fl - (int)fr;
}
l++;
r++;
n--;
}
/* n bytes compared: equal so far. */
if (n == 0)
{
return 0;
}
/* A NUL ended the scan: the one that is NUL here is the shorter. */
return (int)*l - (int)*r;
}
/*
* Return the 1-based index of the least significant set bit of i, or 0
* when i is 0. The zero case is guarded because __builtin_ctz is
* undefined for a zero argument.
*/
int
ffs(int i)
{
return i == 0 ? 0 : __builtin_ctz((unsigned int)i) + 1;
}
int
ffsl(long i)
{
return i == 0 ? 0 : __builtin_ctzl((unsigned long)i) + 1;
}
int
ffsll(long long i)
{
return i == 0 ? 0 : __builtin_ctzll((unsigned long long)i) + 1;
}
/*
* Copy n bytes from src to dst with memmove semantics: the regions may
* overlap and the copy behaves as if through a temporary. The argument
* order (src, dst) is the reverse of memmove's — kept as such because it
* is the BSD contract.
*/
void
bcopy(const void *src, void *dst, size_t n)
{
(void)memmove(dst, src, n);
}
/*
* Fill n bytes at s with zero.
*/
void
bzero(void *s, size_t n)
{
(void)memset(s, 0, n);
}
/*
* Compare the first n bytes of s1 and s2 as unsigned char, like memcmp;
* return 0 when equal, nonzero otherwise. A NUL byte does not end the
* comparison.
*/
int
bcmp(const void *s1, const void *s2, size_t n)
{
return memcmp(s1, s2, n);
}
/*
* Return a pointer to the first occurrence of c (converted to unsigned
* char) in s, or NULL when absent. The terminating NUL is part of the
* string, so index(s, '\0') returns a pointer to it. Legacy name for
* strchr.
*/
char *
index(const char *s, int c)
{
const unsigned char cc = (unsigned char)c;
for (;; s++)
{
if ((unsigned char)*s == cc)
{
return (char *)s;
}
if (*s == '\0')
{
return NULL;
}
}
}
/*
* Return a pointer to the last occurrence of c (converted to unsigned
* char) in s, or NULL when absent. The terminating NUL is part of the
* string, so rindex(s, '\0') returns a pointer to it. Legacy name for
* strrchr.
*/
char *
rindex(const char *s, int c)
{
const unsigned char cc = (unsigned char)c;
const char *last = NULL;
for (;; s++)
{
if ((unsigned char)*s == cc)
{
last = s;
}
if (*s == '\0')
{
return (char *)last;
}
}
}
#endif /* VLIBC_LEVEL_GE(2) */
+36
View File
@@ -0,0 +1,36 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
/*
* Compare at most n bytes of lhs and rhs as unsigned char, stopping early
* at the first difference or the first NUL of either string. Return
* negative, zero, or positive when lhs is less than, equal to, or greater
* than rhs.
*
* See strcpy.c: the scan/compare loop is a pattern GCC's
* -ftree-loop-distribute-patterns can recognize (strcmp/strncmp), so disable
* that transformation to avoid self-recursion.
*/
__attribute__((optimize("no-tree-loop-distribute-patterns"))) int
strncmp(const char *lhs, const char *rhs, size_t n) // NOLINT(bugprone-easily-swappable-parameters)
{
const unsigned char *l = (const unsigned char *)lhs;
const unsigned char *r = (const unsigned char *)rhs;
while (n != 0 && *l != '\0' && *l == *r)
{
l++;
r++;
n--;
}
if (n == 0 || *l == *r)
{
return 0;
}
return *l < *r ? -1 : 1;
}
+35
View File
@@ -0,0 +1,35 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
/*
* Copy at most n bytes from src to dst. When src is shorter than n, the
* remainder of dst is filled with NULs; when src is n bytes or longer, no
* NUL is written at all. Return dst.
*
* See strcpy.c: the copy loop is a recognizable pattern, so disable
* -ftree-loop-distribute-patterns here too.
*/
__attribute__((optimize("no-tree-loop-distribute-patterns"))) char *
strncpy(char *restrict dst, const char *restrict src, size_t n) // NOLINT(bugprone-*)
{
char *d = dst;
/* Copy until src ends or n bytes have been copied. */
while (n != 0 && *src != '\0')
{
*d++ = *src++;
n--;
}
/* NUL-pad the remainder (this also terminates a short src). */
while (n != 0)
{
*d++ = '\0';
n--;
}
return dst;
}
+28
View File
@@ -0,0 +1,28 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
/*
* Return the length of s, excluding the terminating NUL, examining at most
* maxlen bytes: the result is min(strlen(s), maxlen) when s is a proper
* string. Unlike strlen, the scan provably stops at maxlen bytes, which is
* why strndup and friends rely on it for untrusted buffers.
*
* See strcpy.c: the NUL scan is exactly the pattern GCC's
* -ftree-loop-distribute-patterns recognizes as strnlen/strlen, so disable
* that transformation to avoid self-recursion.
*/
__attribute__((optimize("no-tree-loop-distribute-patterns"))) size_t
strnlen(const char *s, size_t maxlen)
{
size_t n = 0;
while (n < maxlen && s[n] != '\0')
{
n++;
}
return n;
}
+136
View File
@@ -0,0 +1,136 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
/*
* vlibc — strsignal (todo 8).
*
* Maps a signal number to its description string. The table uses the
* x86_64 Linux asm-generic signal numbers (SIGHUP=1 .. SIGSYS=31), which
* are kernel-ABI facts; <signal.h> (todo 28) will become the canonical home
* for the signal-number names, and this table should then reference those
* names instead of the literal numbers.
*
* Known signals return immutable static string literals. Unrecognized
* numbers — including 0, whose text POSIX deliberately leaves unspecified —
* format as "Unknown signal <N>" into one shared static buffer. That is the
* same single-buffer tradeoff as strerror (see src/errno/strerror.c): the
* unknown path is rare diagnostics, there is no per-thread storage beyond
* the errno TCB slot yet, and strsignal must not depend on malloc. Migrate
* to a per-thread buffer once the TCB supports it.
*/
/*
* One entry per x86_64 asm-generic signal 1..31. The texts are standard
* English descriptions of what each signal means; the wording is vlibc's
* own (POSIX fixes the meanings, not the strings).
*/
static const char *const sigmsg[] = {
[1] = "Hangup",
[2] = "Interrupt",
[3] = "Quit",
[4] = "Illegal instruction",
[5] = "Trace/breakpoint trap",
[6] = "Aborted",
[7] = "Bus error",
[8] = "Floating point exception",
[9] = "Killed",
[10] = "User defined signal 1",
[11] = "Segmentation fault",
[12] = "User defined signal 2",
[13] = "Broken pipe",
[14] = "Alarm clock",
[15] = "Terminated",
[16] = "Stack fault",
[17] = "Child exited",
[18] = "Continued",
[19] = "Stopped (signal)",
[20] = "Stopped",
[21] = "Stopped (tty input)",
[22] = "Stopped (tty output)",
[23] = "Urgent I/O condition",
[24] = "CPU time limit exceeded",
[25] = "File size limit exceeded",
[26] = "Virtual timer expired",
[27] = "Profiling timer expired",
[28] = "Window changed",
[29] = "I/O possible",
[30] = "Power failure",
[31] = "Bad system call",
};
/* The table spans exactly 1..31 (index 0 stays NULL: signal 0 is unknown). */
_Static_assert(sizeof sigmsg / sizeof sigmsg[0] == 32,
"strsignal table must cover signal numbers 1..31");
/* Shared storage for the unknown-signal path (see the file-top comment). */
static char sigbuf[32];
/*
* Write "Unknown signal <sig>" into dst, truncating to cap bytes and always
* NUL-terminating when cap > 0. Hand-rolled so this file needs no stdio.
*/
static void
format_unknown(int sig, char *dst, size_t cap) // NOLINT(bugprone-easily-swappable-parameters)
{
static const char prefix[] = "Unknown signal ";
char digits[12]; /* enough for "-2147483648" */
size_t ndigits;
size_t i;
unsigned long mag;
if (cap == 0)
{
return;
}
/* Absolute value as unsigned long; INT_MIN negates safely in long. */
mag = (unsigned long)(sig < 0 ? -(long)sig : sig);
/* Digits, least significant first. */
ndigits = 0;
do
{
digits[ndigits] = (char)('0' + (int)(mag % 10));
ndigits++;
mag /= 10;
} while (mag != 0);
i = 0;
while (i + 1 < cap && prefix[i] != '\0')
{
dst[i] = prefix[i];
i++;
}
if (i + 1 < cap && sig < 0)
{
dst[i] = '-';
i++;
}
while (i + 1 < cap && ndigits > 0)
{
ndigits--;
dst[i] = digits[ndigits];
i++;
}
dst[i] = '\0';
}
/*
* Return a pointer to a static string describing the signal sig. Never
* returns NULL. Distinct known signals yield distinct strings; signal 0 and
* out-of-range numbers yield "Unknown signal <sig>".
*/
char *
strsignal(int sig)
{
if (sig > 0 && sig < (int)(sizeof sigmsg / sizeof sigmsg[0]))
{
return (char *)sigmsg[sig];
}
format_unknown(sig, sigbuf, sizeof sigbuf);
return sigbuf;
}
+71
View File
@@ -0,0 +1,71 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
/*
* True when c appears anywhere in set (including as the NUL of set — but set
* is a string, so scanning stops at that NUL, as intended).
*/
static int
str_in_set(const char *set, char c) // NOLINT(bugprone-easily-swappable-parameters)
{
for (; *set != '\0'; set++)
{
if (*set == c)
{
return 1;
}
}
return 0;
}
/*
* Return the length of the initial span of s consisting entirely of bytes
* that occur in accept.
*/
size_t
strspn(const char *s, const char *accept) // NOLINT(bugprone-easily-swappable-parameters)
{
size_t n = 0;
while (s[n] != '\0' && str_in_set(accept, s[n]))
{
n++;
}
return n;
}
/*
* Return the length of the initial span of s consisting entirely of bytes
* that do NOT occur in reject.
*/
size_t
strcspn(const char *s, const char *reject) // NOLINT(bugprone-easily-swappable-parameters)
{
size_t n = 0;
while (s[n] != '\0' && !str_in_set(reject, s[n]))
{
n++;
}
return n;
}
/*
* Return a pointer to the first byte in s that also occurs in accept, or
* NULL when none occurs.
*/
char *
strpbrk(const char *s, const char *accept) // NOLINT(bugprone-easily-swappable-parameters)
{
for (; *s != '\0'; s++)
{
if (str_in_set(accept, *s))
{
return (char *)s;
}
}
return NULL;
}
+43
View File
@@ -0,0 +1,43 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
/*
* Return a pointer to the first occurrence of needle in haystack, or NULL
* when absent. An empty needle matches haystack itself.
*
* Naive two-pointer scan: for each position in haystack, compare characters
* until the needle is exhausted (match) or a mismatch occurs (advance). The
* inner loop stops at the NUL terminator of either string, so the comparison
* never reads past the end of haystack when needle is longer than the
* remaining tail.
*/
char *
strstr(const char *haystack, const char *needle) // NOLINT(bugprone-easily-swappable-parameters)
{
if (*needle == '\0')
{
return (char *)haystack;
}
for (; *haystack != '\0'; haystack++)
{
const char *h = haystack;
const char *n = needle;
while (*n != '\0' && *h == *n)
{
h++;
n++;
}
if (*n == '\0')
{
return (char *)haystack;
}
}
return NULL;
}
+73
View File
@@ -0,0 +1,73 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
/*
* Shared continuation pointer for the non-reentrant strtok. Plain static
* storage: POSIX requires strtok itself to be neither thread-safe nor
* reentrant; callers needing either use strtok_r.
*/
static char *strtok_next;
/*
* Split s into tokens delimited by any byte from sep. On the first call s
* names the string; on later calls with s == NULL the search continues in
* the same string. Leading and consecutive delimiters produce no empty
* tokens, and the delimiter bytes in s are overwritten with NUL. Returns
* NULL when no token remains.
*/
char *
strtok(char *restrict s, const char *restrict sep) // NOLINT(bugprone-easily-swappable-parameters)
{
return strtok_r(s, sep, &strtok_next);
}
/*
* Reentrant strtok: the continuation pointer lives in *state instead of
* private static storage. When a call finds no token (including the first
* call on an empty or all-delimiter string), *state is set to NULL, so a
* later strtok_r(NULL, sep, state) call returns NULL without further reads.
*/
char *
strtok_r(char *restrict s, const char *restrict sep, char **restrict state)
{
char *tok;
if (s == NULL)
{
s = *state;
if (s == NULL)
{
return NULL;
}
}
/* Skip leading delimiters (this also skips the empty tokens that
consecutive delimiters would otherwise produce). */
s += strspn(s, sep);
if (*s == '\0')
{
*state = NULL;
return NULL;
}
tok = s;
/* Find the end of the token. */
s += strcspn(s, sep);
if (*s != '\0')
{
/* Replace the delimiter with NUL and continue after it. */
*s++ = '\0';
}
else
{
/* The token ran to the end of the string. */
s = NULL;
}
*state = s;
return tok;
}
File diff suppressed because it is too large Load Diff
+95
View File
@@ -0,0 +1,95 @@
#!/bin/sh
# vlibc — make check mode matrix (todo 6).
#
# Runs every scenario that a single "run the binary once" TESTS entry cannot
# cover: the -f failure modes of the four syscall/errno/setjmp/headers tests
# and the full test_startup mode matrix. Every scenario pins its exact exit
# status and (where load-bearing) its exact stdout; see tests/test_startup.c
# and the individual tests for the mode contracts.
#
# The binaries are the static vlibc executables built by make check next to
# the top-level Makefile. make check executes this script with the build
# directory root as the working directory (in-tree and VPATH builds alike),
# so the binaries resolve as plain ./<name> paths relative to the CWD, NOT
# relative to this script's location.
fail=0
# run_rc <binary> <want_rc> <description> [args...]
# Runs the binary with the given args and checks the exit status only (the
# -f modes print diagnostics, whose text is not part of the contract).
run_rc()
{
bin=$1
want_rc=$2
desc=$3
shift 3
"$bin" "$@" >/dev/null 2>&1
rc=$?
if [ "$rc" -ne "$want_rc" ]; then
echo "FAIL: $desc: rc=$rc want=$want_rc"
fail=1
else
echo "ok: $desc"
fi
}
# run <binary> <want_rc> <want_stdout> <description> [args...]
# Like run_rc but also compares the exact stdout (stderr discarded). An
# empty want_stdout matches an empty stdout.
run()
{
bin=$1
want_rc=$2
want_out=$3
desc=$4
shift 4
out=$("$bin" "$@" 2>/dev/null)
rc=$?
if [ "$rc" -ne "$want_rc" ]; then
echo "FAIL: $desc: rc=$rc want=$want_rc"
fail=1
elif [ "$out" != "$want_out" ]; then
echo "FAIL: $desc: stdout [$out] want [$want_out]"
fail=1
else
echo "ok: $desc"
fi
}
bindir=.
# -f failure scenarios of the four arg-mode tests (exit status is the
# contract; the diagnostics on stdout are not pinned).
run_rc "./test_syscall" 0 "syscall -f: ENOENT via fake TCB slot" -f
run_rc "./test_strerror" 0 "strerror -f: unknown errno formats" -f
run_rc "./test_setjmp" 0 "setjmp -f: longjmp(env,0) coerced to 1" -f
run_rc "./test_headers" 132 "headers -f: assert(0) traps (SIGILL)" -f
# test_startup mode matrix (each mode pins its own exit status; the default
# mode exits 42 on success, which is why it is not a bare TESTS entry).
run "./test_startup" 42 "C
B
A" "startup default: atexit LIFO CBA, status 42"
run "./test_startup" 5 "Q" "startup -q: quick_exit runs only at_quick_exit" -q
run "./test_startup" 7 "" "startup -z: _exit runs no handlers" -z
run "./test_startup" 0 "" "startup -t: main-thread TLS + errno TCB slot" -t
run "./test_startup" 0 "" "startup -x: handler-list limits" -x
# -e needs VLIBC_TEST_VAR=hello in environ (checked by the test itself).
out=$(VLIBC_TEST_VAR=hello "./test_startup" -e extraarg 2>/dev/null)
rc=$?
if [ "$rc" -ne 0 ]; then
echo "FAIL: startup -e: argv/env plumbing rc=$rc want=0"
fail=1
elif [ "$out" != "argc=3
argv0=./test_startup
argv1=-e
argv2=extraarg" ]; then
echo "FAIL: startup -e: unexpected stdout [$out]"
fail=1
else
echo "ok: startup -e: argv/env plumbing"
fi
exit $fail
+60
View File
@@ -14,6 +14,9 @@
* then point exactly at fake + VLIBC_TCB_ERRNO_OFF, a failing syscall * then point exactly at fake + VLIBC_TCB_ERRNO_OFF, a failing syscall
* must land its errno in that slot, and the real thread pointer is * must land its errno in that slot, and the real thread pointer is
* restored afterwards. * restored afterwards.
* 4. register pinning: a 4-arg (SYS_rt_sigprocmask) and a 5-arg
* (SYS_ppoll) syscall whose later arguments are load-bearing must see
* them in r10/r8, where the x86_64 kernel ABI places args 4 and 5.
* *
* All diagnostics go through raw SYS_write (no printf): between installing * All diagnostics go through raw SYS_write (no printf): between installing
* and restoring the fake thread pointer the test must not call any libc * and restoring the fake thread pointer the test must not call any libc
@@ -32,6 +35,11 @@
#define TEST_O_RDONLY 0 #define TEST_O_RDONLY 0
#define TEST_O_CLOEXEC 0x80000 #define TEST_O_CLOEXEC 0x80000
/* Constants the public <signal.h>/<poll.h> will own; test-local copies. */
#define TEST_SIG_BLOCK 0
#define TEST_SIG_SETMASK 2
#define TEST_SIGUSR1 10
/* arch_prctl codes (kernel UAPI). */ /* arch_prctl codes (kernel UAPI). */
#define TEST_ARCH_SET_FS 0x1002 #define TEST_ARCH_SET_FS 0x1002
#define TEST_ARCH_GET_FS 0x1003 #define TEST_ARCH_GET_FS 0x1003
@@ -101,6 +109,54 @@ restore_real_tcb(unsigned long real_fs)
return syscall_ret(__syscall2(SYS_arch_prctl, TEST_ARCH_SET_FS, (long)real_fs)) != 0; return syscall_ret(__syscall2(SYS_arch_prctl, TEST_ARCH_SET_FS, (long)real_fs)) != 0;
} }
struct test_timespec
{
long tv_sec;
long tv_nsec;
};
/*
* 4-arg regression probe: block SIGUSR1, read the mask back through a NULL
* set, restore. The 4th argument (sigsetsize) must reach the kernel in r10;
* placed anywhere else it fails with -EINVAL.
*/
static int
probe_rt_sigprocmask(void)
{
unsigned long mask = 1UL << (TEST_SIGUSR1 - 1);
unsigned long oldmask = 0;
unsigned long readback = 0;
if (__syscall4(SYS_rt_sigprocmask, TEST_SIG_BLOCK, (long)&mask, (long)&oldmask, 8) != 0)
{
return 0;
}
if (__syscall4(SYS_rt_sigprocmask, TEST_SIG_BLOCK, 0, (long)&readback, 8) != 0)
{
return 0;
}
if (__syscall4(SYS_rt_sigprocmask, TEST_SIG_SETMASK, (long)&oldmask, 0, 8) != 0)
{
return 0;
}
return (readback & (1UL << (TEST_SIGUSR1 - 1))) != 0;
}
/*
* 5-arg regression probe: ppoll with no fds and a zero timeout must return
* 0. The 4th argument (sigmask) must reach r10 and the 5th (sigsetsize) r8;
* misplaced, the kernel reads a pointer value where the set size belongs and
* answers -EINVAL.
*/
static int
probe_ppoll(void)
{
struct test_timespec ts = {0, 0};
unsigned long mask = 0;
return __syscall5(SYS_ppoll, 0, 0, (long)&ts, (long)&mask, 8) == 0;
}
/* /*
* Failure scenario (-f): SYS_openat on a nonexistent path must return -1 * Failure scenario (-f): SYS_openat on a nonexistent path must return -1
* and leave errno == ENOENT in the TCB slot. Prints the observed result to * and leave errno == ENOENT in the TCB slot. Prints the observed result to
@@ -173,6 +229,10 @@ main(int argc, char **argv)
check(*(int *)((char *)fake_tcb + VLIBC_TCB_ERRNO_OFF) == ENOENT, check(*(int *)((char *)fake_tcb + VLIBC_TCB_ERRNO_OFF) == ENOENT,
"fake TCB errno slot retains ENOENT after restore"); "fake TCB errno slot retains ENOENT after restore");
/* 4. Register pinning for 4- and 5-argument syscalls (r10/r8). */
check(probe_rt_sigprocmask(), "rt_sigprocmask 4-arg r10 pinning");
check(probe_ppoll(), "ppoll 5-arg r10/r8 pinning");
/* /*
* VLIBC_TCB_ERRNO_OFF addresses slot 1 of whichever TCB the FS thread * VLIBC_TCB_ERRNO_OFF addresses slot 1 of whichever TCB the FS thread
* pointer selects; under the host libc that slot is its private TLS * pointer selects; under the host libc that slot is its private TLS
+325
View File
@@ -0,0 +1,325 @@
/*
* vlibc — ctype.h classification + case mapping test (todo 9).
*
* Coverage:
*
* 1. classification sweep: every is* function, for all 256 unsigned char
* values AND EOF, must agree with the "C" locale ASCII ranges the
* standard specifies — the golden model below derives its expectation
* straight from those ranges, independent of the implementation's
* table;
* 2. case-mapping sweep: tolower/toupper over all 256 values + EOF (map
* A-Z/a-z, pass every non-letter through unchanged, EOF -> EOF);
* 3. negative-char sweep: every negative signed-char value passed as int
* must classify false in all is* functions, case-map to itself, and
* never fault — proving the unsigned char cast keeps negative values
* from indexing out of bounds (isalpha((char)0xe9) == 0 included);
* 4. spot checks from the todo acceptance list;
* 5. -f: the negative QA — isprint('\n') == 0, isalpha(EOF) == 0,
* tolower(EOF)/toupper(EOF) == EOF (a nonzero isprint('\n') is the
* defect).
*
* No host headers: <ctype.h> is vlibc's own (-Iinclude shadows the system
* one), and every diagnostic goes through the raw SYS_write helpers from
* <vlibc/internal/test.h>. Compiled with -DVLIBC_LEVEL=2 so both the L1
* functions and the L2 isascii/toascii gate are exercised (mirrors
* libvlibc-check.a, which builds the level-2 surface).
*
* Not part of the library proper; compiled manually for this todo (the
* tests/ + make check wiring is owned by a later todo).
*/
#include <ctype.h>
#include <vlibc/internal/test.h>
/* EOF as the standard defines it; ctype.h deliberately does not provide it. */
#define TEST_EOF (-1)
static int failures;
/* ---- Golden model: the "C" locale classes, as the standard defines them ---- */
enum
{
CLS_ALNUM,
CLS_ALPHA,
CLS_BLANK,
CLS_CNTRL,
CLS_DIGIT,
CLS_GRAPH,
CLS_LOWER,
CLS_PRINT,
CLS_PUNCT,
CLS_SPACE,
CLS_UPPER,
CLS_XDIGIT
};
typedef int (*ctype_pred)(int);
/* The real functions under test, indexed by the CLS_* enum above. */
static const ctype_pred cls_preds[] = {isalnum, isalpha, isblank, iscntrl, isdigit, isgraph,
islower, isprint, ispunct, isspace, isupper, isxdigit};
static const char *const cls_names[] = {"isalnum", "isalpha", "isblank", "iscntrl",
"isdigit", "isgraph", "islower", "isprint",
"ispunct", "isspace", "isupper", "isxdigit"};
static int
expect_class(int c, int cls) // NOLINT(bugprone-easily-swappable-parameters)
{
if (c < 0 || c > 127)
{
return 0;
}
// NOLINTBEGIN(bugprone-switch-missing-default-case)
switch (cls)
{
case CLS_UPPER:
return c >= 'A' && c <= 'Z';
case CLS_LOWER:
return c >= 'a' && c <= 'z';
case CLS_DIGIT:
return c >= '0' && c <= '9';
case CLS_ALPHA:
return expect_class(c, CLS_UPPER) || expect_class(c, CLS_LOWER);
case CLS_ALNUM:
return expect_class(c, CLS_ALPHA) || expect_class(c, CLS_DIGIT);
case CLS_XDIGIT:
return expect_class(c, CLS_DIGIT) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
case CLS_CNTRL:
return (c >= 0x00 && c <= 0x1f) || c == 0x7f;
case CLS_BLANK:
return c == ' ' || c == '\t';
case CLS_SPACE:
return c == ' ' || (c >= '\t' && c <= '\r');
case CLS_PUNCT:
return (c >= '!' && c <= '/') || (c >= ':' && c <= '@') || (c >= '[' && c <= '`') ||
(c >= '{' && c <= '~');
case CLS_GRAPH:
return expect_class(c, CLS_ALNUM) || expect_class(c, CLS_PUNCT);
case CLS_PRINT:
return expect_class(c, CLS_GRAPH) || c == ' ';
}
// NOLINTEND(bugprone-switch-missing-default-case)
return 0;
}
static int
expect_tolower(int c)
{
return c >= 'A' && c <= 'Z' ? c + ('a' - 'A') : c;
}
static int
expect_toupper(int c)
{
return c >= 'a' && c <= 'z' ? c - ('a' - 'A') : c;
}
/* ---- Sweeps ---- */
/* 1. All 256 byte values + EOF against the golden model, per class. */
static void
classification_sweep(void)
{
size_t k;
for (k = 0; k < sizeof(cls_preds) / sizeof(cls_preds[0]); k++)
{
int c;
for (c = 0; c < 256; c++)
{
if ((cls_preds[k](c) != 0) == (expect_class(c, (int)k) != 0))
{
continue;
}
vlibc_test_say(2, "mismatch: ");
vlibc_test_say(2, cls_names[k]);
vlibc_test_say(2, "(0x");
vlibc_test_say_hex(2, (unsigned long long)c);
vlibc_test_say(2, ")\n");
failures++;
break;
}
if (cls_preds[k](TEST_EOF) != 0)
{
vlibc_test_say(2, "mismatch: ");
vlibc_test_say(2, cls_names[k]);
vlibc_test_say(2, "(EOF)\n");
failures++;
}
}
}
/* 2. Case mapping over all 256 byte values + EOF. */
static void
case_mapping_sweep(void)
{
int c;
for (c = 0; c < 256; c++)
{
if (tolower(c) != expect_tolower(c))
{
vlibc_test_say(2, "tolower mismatch at 0x");
vlibc_test_say_hex(2, (unsigned long long)c);
vlibc_test_say(2, "\n");
failures++;
}
if (toupper(c) != expect_toupper(c))
{
vlibc_test_say(2, "toupper mismatch at 0x");
vlibc_test_say_hex(2, (unsigned long long)c);
vlibc_test_say(2, "\n");
failures++;
}
}
if (tolower(TEST_EOF) != TEST_EOF || toupper(TEST_EOF) != TEST_EOF)
{
vlibc_test_say(2, "case mapping of EOF did not return EOF unchanged\n");
failures++;
}
}
/* 3. Every negative signed-char value: classify false, case-map to itself. */
static void
negative_char_sweep(void)
{
signed char sc;
size_t k;
for (sc = (signed char)-128; (int)sc < 0; sc++)
{
int c = (int)sc;
for (k = 0; k < sizeof(cls_preds) / sizeof(cls_preds[0]); k++)
{
if (cls_preds[k](c) != 0)
{
vlibc_test_say(2, "negative value classified nonzero by ");
vlibc_test_say(2, cls_names[k]);
vlibc_test_say(2, "\n");
failures++;
}
}
if (tolower(c) != c || toupper(c) != c)
{
vlibc_test_say(2, "negative value case-mapped instead of passing through\n");
failures++;
}
#if VLIBC_LEVEL_GE(2)
if (isascii(c) != 0)
{
vlibc_test_say(2, "negative value classified ASCII by isascii\n");
failures++;
}
if (toascii(c) != (c & 0x7f))
{
vlibc_test_say(2, "toascii mismatch on a negative value\n");
failures++;
}
#endif /* VLIBC_LEVEL_GE(2) */
}
}
/* 4. Acceptance-list spot checks. */
static void
spot_checks(void)
{
TEST_ASSERT_EQ(tolower('A'), 'a');
TEST_ASSERT_EQ(toupper('a'), 'A');
TEST_ASSERT_EQ(tolower('a'), 'a'); /* lowercase identity */
TEST_ASSERT_EQ(toupper('Z'), 'Z'); /* uppercase identity */
TEST_ASSERT_EQ(tolower('Z'), 'z');
TEST_ASSERT_EQ(toupper('z'), 'Z');
TEST_ASSERT_EQ(tolower('0'), '0'); /* digit identity */
TEST_ASSERT_TRUE(isupper('A'));
TEST_ASSERT_TRUE(islower('a'));
TEST_ASSERT_TRUE(isalpha(TEST_EOF) == 0);
TEST_ASSERT_TRUE(isprint('\n') == 0); /* negative QA: no false positive */
TEST_ASSERT_TRUE(isdigit('5'));
TEST_ASSERT_TRUE(!isalpha('5'));
TEST_ASSERT_TRUE(isspace(' '));
TEST_ASSERT_TRUE(isspace('\n'));
TEST_ASSERT_TRUE(ispunct('!'));
TEST_ASSERT_TRUE(!ispunct('A'));
TEST_ASSERT_TRUE(isgraph('~'));
TEST_ASSERT_TRUE(!isgraph(' '));
TEST_ASSERT_TRUE(isprint(' '));
TEST_ASSERT_TRUE(isblank('\t'));
TEST_ASSERT_TRUE(isblank(' '));
TEST_ASSERT_TRUE(!isblank('x'));
TEST_ASSERT_TRUE(iscntrl(0x7f));
TEST_ASSERT_TRUE(!iscntrl(' '));
TEST_ASSERT_TRUE(isxdigit('f'));
TEST_ASSERT_TRUE(isxdigit('F'));
TEST_ASSERT_TRUE(!isxdigit('g'));
TEST_ASSERT_TRUE(!isalnum('@'));
TEST_ASSERT_TRUE(isalnum('A'));
TEST_ASSERT_TRUE(isalpha((char)0xe9) == 0); /* negative char: (char)0xe9 == -23 */
#if VLIBC_LEVEL_GE(2)
TEST_ASSERT_TRUE(!isascii(0x80));
TEST_ASSERT_TRUE(isascii(0x00));
TEST_ASSERT_TRUE(isascii(0x7f));
TEST_ASSERT_TRUE(!isascii(TEST_EOF));
TEST_ASSERT_EQ(toascii('A'), 'A'); /* c & 0x7f, not a predicate */
TEST_ASSERT_EQ(toascii(0x80), 0);
TEST_ASSERT_EQ(toascii(TEST_EOF), 0x7f);
#endif /* VLIBC_LEVEL_GE(2) */
}
/*
* Failure scenario (-f): the negative QA from the todo. Exits 0 only when
* each defect is absent.
*/
static int
failure_scenario(void)
{
if (isprint('\n') != 0)
{
vlibc_test_say(1, "isprint('\\n') != 0 (false positive)\n");
return 1;
}
if (isalpha(TEST_EOF) != 0)
{
vlibc_test_say(1, "isalpha(EOF) != 0\n");
return 1;
}
if (tolower(TEST_EOF) != TEST_EOF || toupper(TEST_EOF) != TEST_EOF)
{
vlibc_test_say(1, "tolower/toupper(EOF) did not return EOF unchanged\n");
return 1;
}
vlibc_test_say(1, "negative QA: isprint('\\n') == 0, isalpha(EOF) == 0, "
"tolower/toupper(EOF) == EOF\n");
return 0;
}
int
main(int argc, char **argv)
{
if (argc == 2 && argv[1][0] == '-' && argv[1][1] == 'f')
{
return failure_scenario();
}
classification_sweep();
case_mapping_sweep();
negative_char_sweep();
spot_checks();
if (failures > 0 || vlibc_test_failures > 0)
{
vlibc_test_say(2, "FAILED (");
// NOLINTNEXTLINE(bugprone-misplaced-widening-cast)
vlibc_test_say_dec(2, (unsigned long)(failures + vlibc_test_failures));
vlibc_test_say(2, " check(s))\n");
return 1;
}
vlibc_test_say(1, "all ctype tests passed\n");
return 0;
}
+331
View File
@@ -0,0 +1,331 @@
/*
* vlibc — public header skeleton test (todo 5).
*
* Exercises the full public header inventory in one translation unit:
*
* 1. compile-time: every new header plus the scaffold headers is included
* TWICE, so the include guards are proven to tolerate re-inclusion in
* any order; static_asserts pin the type widths and the standard macro
* values;
* 2. runtime: a happy-path main() checks the type sizes, the C23 keywords
* the headers provide (nullptr, alignas/alignof, iso646 spellings),
* stdatomic, stdbit, stdckdint and stdarg behavior end to end;
* 3. failure mode (-f): assert(0) must write the diagnostic to fd 2 and
* terminate with SIGILL — __vlibc_assert_fail() ends in
* __builtin_trap(), so the harness expects exit status 132 (128 + 4).
* When built with -DNDEBUG the -f run prints that assert was swallowed
* and exits 0 instead.
*
* All output goes through the raw SYS_write layer, like tests/syscall_test.c:
* no host <stdio.h> is included, so the TU exercises only vlibc's own
* headers through -Iinclude and stays fully self-contained.
*
* Not part of the library proper; compiled manually for this todo (the
* tests/ + make check wiring is owned by a later todo).
*/
#include <assert.h>
#include <cpio.h>
#include <float.h>
#include <iso646.h>
#include <limits.h>
#include <stdalign.h>
#include <stdarg.h>
#include <stdatomic.h>
#include <stdbit.h>
#include <stdbool.h>
#include <stdckdint.h>
#include <stddef.h>
#include <stdint.h>
#include <stdnoreturn.h>
#include <string.h>
#include <sys/types.h>
#include <tar.h>
#include <tgmath.h>
#include <threads.h>
#include <uchar.h>
#include <vlibc.h>
/* Second round: the guards must make every re-inclusion a no-op. */
#include <assert.h>
#include <cpio.h>
#include <float.h>
#include <iso646.h>
#include <limits.h>
#include <stdalign.h>
#include <stdarg.h>
#include <stdatomic.h>
#include <stdbit.h>
#include <stdbool.h>
#include <stdckdint.h>
#include <stddef.h>
#include <stdint.h>
#include <stdnoreturn.h>
#include <string.h>
#include <sys/types.h>
#include <tar.h>
#include <tgmath.h>
#include <threads.h>
#include <uchar.h>
#include <vlibc.h>
#include "../src/internal/syscall.h"
/* ---- Compile-time gates (features.h wiring) ---- */
#if !VLIBC_HAS_POSIX
#error "VLIBC_HAS_POSIX must be 1 at level 1"
#endif
#if !defined(VLIBC_HAS_HEADER_SYS_TYPES_H) || !defined(VLIBC_HAS_HEADER_UCHAR_H) || \
!defined(VLIBC_HAS_HEADER_CPIO_H) || !defined(VLIBC_HAS_HEADER_TAR_H) || \
!defined(VLIBC_HAS_HEADER_THREADS_H)
#error "level-1 header gates missing from features.h"
#endif
/* ---- Compile-time checks: stdint.h / limits.h / float.h ---- */
static_assert(sizeof(int8_t) == 1 && sizeof(uint8_t) == 1, "8-bit exact types");
static_assert(sizeof(int16_t) == 2 && sizeof(uint16_t) == 2, "16-bit exact types");
static_assert(sizeof(int32_t) == 4 && sizeof(uint32_t) == 4, "32-bit exact types");
static_assert(sizeof(int64_t) == 8 && sizeof(uint64_t) == 8, "64-bit exact types");
static_assert(sizeof(intptr_t) == sizeof(void *) && sizeof(uintptr_t) == sizeof(void *),
"pointer-width types");
static_assert(sizeof(intmax_t) == 8 && sizeof(uintmax_t) == 8, "max-width types");
static_assert(INT64_WIDTH == 64 && UINT64_WIDTH == 64, "exact widths");
static_assert(SIZE_WIDTH == 64 && PTRDIFF_WIDTH == 64, "pointer-class widths");
static_assert(WCHAR_WIDTH == 32, "wchar_t is 32-bit int on x86_64");
static_assert(INT32_MAX == 2147483647 && INT32_MIN == (-2147483647 - 1), "int32 limits");
static_assert(UINT32_MAX == 4294967295U, "uint32 limit");
static_assert(INT64_MAX == INT64_C(0x7fffffffffffffff), "INT64_C produces long");
static_assert(UINT64_MAX == UINT64_C(0xffffffffffffffff), "UINT64_C produces unsigned long");
static_assert(CHAR_BIT == 8 && SCHAR_MIN == -128 && UCHAR_MAX == 255, "char limits");
static_assert(SHRT_MAX == 32767 && USHRT_MAX == 65535, "short limits");
static_assert(INT_MAX == 2147483647 && UINT_MAX == 4294967295U, "int limits");
static_assert(LONG_MAX == 0x7fffffffffffffffL && ULONG_MAX == 0xffffffffffffffffUL,
"long limits (LP64)");
static_assert(LLONG_MAX == 0x7fffffffffffffffLL && ULLONG_MAX == 0xffffffffffffffffULL,
"long long limits");
static_assert(FLT_RADIX == 2, "binary floating point");
static_assert(FLT_MANT_DIG == 24 && DBL_MANT_DIG == 53, "IEC 60559 mantissa widths");
static_assert(FLT_ROUNDS == 1, "default rounding mode");
static_assert(FLT_IS_IEC_60559 == 1 && DBL_IS_IEC_60559 == 1, "IEC 60559 types");
/* ---- Compile-time checks: keyword / feature-test plumbing ---- */
#ifndef __bool_true_false_are_defined
#error "stdbool.h must define __bool_true_false_are_defined"
#endif
#ifndef __alignas_is_defined
#error "stdalign.h must define __alignas_is_defined"
#endif
#ifndef __alignof_is_defined
#error "stdalign.h must define __alignof_is_defined"
#endif
_Static_assert(sizeof(int) == 4, "_Static_assert stays usable alongside static_assert");
/* ---- Runtime harness ---- */
static int failures;
/* Write a NUL-terminated string to fd via the raw syscall layer. */
static void
say(int fd, const char *s)
{
size_t n = 0;
while (s[n] != '\0')
{
n++;
}
(void)__syscall3(SYS_write, fd, (long)s, (long)n);
}
static void
check(int cond, const char *what)
{
if (!cond)
{
say(2, "FAIL: ");
say(2, what);
say(2, "\n");
failures++;
}
}
/* Sum n int arguments via <stdarg.h>. */
static int
sum(int n, ...)
{
va_list ap;
int total = 0;
int i;
va_start(ap, n);
for (i = 0; i < n; i++)
{
total += va_arg(ap, int);
}
va_end(ap);
return total;
}
int
main(int argc, char **argv)
{
if (argc == 2 && argv[1][0] == '-' && argv[1][1] == 'f')
{
#ifdef NDEBUG
say(1, "NDEBUG: assert(0) swallowed\n");
return 0;
#else
say(1, "about to assert(0): expect SIGILL\n");
assert(0);
return 1; /* unreachable: __vlibc_assert_fail traps */
#endif
}
/* stddef.h: the C23 pieces. */
{
nullptr_t np = nullptr;
struct of_test
{
char c;
int i;
};
check(sizeof(size_t) == 8 && sizeof(ptrdiff_t) == 8, "size_t/ptrdiff_t widths");
check(sizeof(wchar_t) == 4, "wchar_t width");
check(sizeof(nullptr_t) == sizeof(void *), "nullptr_t size");
check(np == nullptr, "nullptr constant round-trip");
check(offsetof(struct of_test, i) == 4, "offsetof");
check(NULL == 0, "NULL");
}
/* stdalign.h / C23 keywords. */
{
alignas(16) int aligned_obj;
check(alignof(double) >= 8, "alignof");
check(((uintptr_t)&aligned_obj & 15) == 0, "alignas(16) honored");
}
/* iso646.h: alternative spellings are macros in C23. */
{
int alt = (1 and 1) or 0;
check(alt == 1, "iso646 and/or");
check(not(alt == 0), "iso646 not");
}
/* stdbit.h: type-generic dispatch and per-width spellings. */
check(stdc_leading_zeros(1u) == 31, "stdc_leading_zeros");
check(stdc_leading_zeros_uc(0x80u) == 0, "stdc_leading_zeros_uc");
check(stdc_leading_ones(0xffffffffu) == 32, "stdc_leading_ones full");
check(stdc_trailing_zeros(0x100u) == 8, "stdc_trailing_zeros");
check(stdc_trailing_ones(7u) == 3, "stdc_trailing_ones");
check(stdc_count_ones(0xffu) == 8, "stdc_count_ones");
check(stdc_count_zeros(0xffu) == 24, "stdc_count_zeros");
check(stdc_has_single_bit(0x40u) == 1, "stdc_has_single_bit true");
check(stdc_has_single_bit(0x41u) == 0, "stdc_has_single_bit false");
check(stdc_bit_width(0u) == 0, "stdc_bit_width(0)");
check(stdc_bit_width(0x40u) == 7, "stdc_bit_width");
check(stdc_bit_floor(0x33u) == 0x20u, "stdc_bit_floor");
check(stdc_bit_ceil(0x33u) == 0x40u, "stdc_bit_ceil");
check(stdc_first_leading_one((unsigned char)0x80) == 0, "stdc_first_leading_one");
check(stdc_first_leading_zero(0xfffffffeu) == 31, "stdc_first_leading_zero");
check(stdc_first_trailing_one(0x80u) == 7, "stdc_first_trailing_one");
check(stdc_first_trailing_zero(1u) == 1, "stdc_first_trailing_zero");
/* stdckdint.h: overflow detection and result preservation. */
{
unsigned int ures = 0;
int sres = 0;
check(ckd_add(&ures, 0xffffffffu, 1u) == 1, "ckd_add detects overflow");
check(ures == 0, "ckd_add leaves result unmodified on overflow");
check(ckd_add(&ures, 1u, 2u) == 0 && ures == 3u, "ckd_add computes");
check(ckd_mul(&sres, 100000, 100000) == 1, "ckd_mul detects overflow");
check(ckd_sub(&ures, 5u, 7u) == 1, "ckd_sub detects underflow");
}
/* stdatomic.h: the macro operations over GCC builtins. */
{
atomic_int counter = 7;
atomic_flag flag = ATOMIC_FLAG_INIT;
int expected;
check(atomic_load(&counter) == 7, "atomic_load");
atomic_store(&counter, 5);
check(atomic_load(&counter) == 5, "atomic_store");
check(atomic_fetch_add(&counter, 2) == 5 && atomic_load(&counter) == 7, "atomic_fetch_add");
check(atomic_exchange(&counter, 1) == 7, "atomic_exchange");
expected = 1;
check(atomic_compare_exchange_strong(&counter, &expected, 9) == 1 &&
atomic_load(&counter) == 9,
"compare_exchange success");
expected = 3;
check(atomic_compare_exchange_strong(&counter, &expected, 0) == 0 && expected == 9,
"compare_exchange failure updates expected");
check(atomic_flag_test_and_set(&flag) == 0, "atomic_flag initially clear");
check(atomic_flag_test_and_set(&flag) == 1, "atomic_flag now set");
atomic_flag_clear(&flag);
check(atomic_flag_test_and_set(&flag) == 0, "atomic_flag cleared");
check(atomic_is_lock_free(&counter) != 0, "atomic_int lock-free");
atomic_thread_fence(memory_order_seq_cst);
atomic_signal_fence(memory_order_relaxed);
}
/* stdarg.h: variadic round-trip. */
check(sum(3, 1, 2, 3) == 6, "stdarg sum");
check(sum(0) == 0, "stdarg empty call");
/* assert.h happy path: a true assertion is a no-op. */
assert(1);
assert(sizeof(int) == 4);
check(1, "assert(true) no-op");
/* uchar.h: C23 keyword types and the declared-only conversions. */
check(sizeof(char16_t) == 2, "char16_t width");
check(sizeof(char32_t) == 4, "char32_t width");
{
mbstate_t mbs;
mbs.state[0] = 0;
mbs.state[1] = 0;
check(mbs.state[0] == 0 && mbs.state[1] == 0, "mbstate_t usable");
}
/* sys/types.h: the POSIX scalar set (x86_64 LP64). */
static_assert(sizeof(ssize_t) == 8 && sizeof(off_t) == 8, "signed 64-bit types");
static_assert(sizeof(time_t) == 8 && sizeof(clock_t) == 8, "time types");
static_assert(sizeof(pid_t) == 4 && sizeof(uid_t) == 4 && sizeof(gid_t) == 4, "id types");
static_assert(sizeof(mode_t) == 4 && sizeof(id_t) == 4 && sizeof(key_t) == 4,
"mode/id/key types");
static_assert(sizeof(dev_t) == 8 && sizeof(ino_t) == 8 && sizeof(nlink_t) == 8,
"file identity types");
static_assert(sizeof(blkcnt_t) == 8 && sizeof(blksize_t) == 8, "block types");
static_assert(sizeof(suseconds_t) == 8 && sizeof(useconds_t) == 4, "usec types");
/* cpio.h / tar.h: the archive constants. */
check(C_IRUSR == 0400 && C_IRGRP == 0040 && C_IROTH == 0004, "cpio permission bits");
check(C_ISREG == 0100000 && C_ISDIR == 0040000 && C_ISLNK == 0120000, "cpio type bits");
check(TMAGIC[0] == 'u' && TMAGIC[1] == 's' && TMAGLEN == 6, "tar magic");
check(TVERSION[0] == '0' && TVERSLEN == 2, "tar version");
check(REGTYPE == '0' && AREGTYPE == '\0' && LNKTYPE == '1' && DIRTYPE == '5', "tar typeflags");
check(TSUID == 04000 && TUREAD == 00400 && TGREAD == 00040 && TOREAD == 00004, "tar mode bits");
/* threads.h stub: the types exist; the functions stay unimplemented. */
{
once_flag of = ONCE_FLAG_INIT;
thrd_t t = 0;
check(of.opaque == 0 && t == 0, "threads.h stub types");
check(TSS_DTOR_ITERATIONS == 4, "TSS_DTOR_ITERATIONS");
}
if (failures > 0)
{
say(2, "FAILED\n");
}
return failures == 0 ? 0 : 1;
}
+449
View File
@@ -0,0 +1,449 @@
/*
* vlibc — malloc/free/calloc/realloc/aligned_alloc/posix_memalign test
* (todo 7).
*
* Exercises the heap allocator end to end:
*
* 1. malloc(1) / malloc(1KB) / malloc(1MB) / malloc(1GB) — the 1GB case
* takes the mmap path — all non-NULL, 16-byte aligned, distinct,
* writable; freed again.
* 2. malloc(0) returns a unique usable minimum-size block; two calls
* yield distinct pointers.
* 3. calloc(100, 100) returns 10000 zero bytes; a zero-product calloc
* returns a unique zeroed block.
* 4. realloc grows (contents preserved), shrinks in place, realloc(p, 0)
* frees and returns NULL, realloc(NULL, n) behaves as malloc(n).
* 5. aligned_alloc(64, 4096) and aligned_alloc(256, 8192) return
* correctly aligned usable blocks; free works on them.
* 6. posix_memalign(&p, 16, 100) returns 0 with p 16-aligned; the
* negative case posix_memalign(&p, 3, 8) returns EINVAL and leaves p
* unmodified.
* 7. malloc_usable_size reports at least the requested size (level 2).
* 8. A 10k random alloc/free churn (sizes up to 4 KiB plus occasional
* 256 KiB mmap blocks) ends with the allocator's own walk
* (__vlibc_malloc_check) reporting zero live chunks — the no-leak
* proof, no valgrind involved.
*
* The failure scenarios (allocation overflow, calloc overflow) live in the
* `-f` mode: those paths set errno inside the library, and under the host
* libc the TCB slot our errno macro addresses is glibc's private TLS state
* (writing it corrupts the host; see tests/syscall_test.c). The -f mode
* therefore exits through a raw SYS_exit_group without ever touching the
* host libc's atexit/cleanup machinery, and the default mode never invokes
* those paths at all. The default mode's negative case is posix_memalign,
* whose EINVAL is a RETURN VALUE, never an errno write.
*
* All diagnostics go through raw SYS_write (no stdio): under -Iinclude the
* vlibc public headers shadow GCC's internal ones, so a host <stdio.h>
* would not compile. The stdlib.h below is vlibc's own new header.
*
* Not part of the library proper; compiled manually for this todo (the
* tests/ + make check wiring is owned by a later todo).
*/
#include <stddef.h>
#include <stdint.h>
#include <errno.h>
#include "../include/stdlib.h"
#include "../src/internal/syscall.h"
/*
* Internal no-leak hook from src/malloc/malloc.c (hidden, so it never
* leaves the archive as a dynamic symbol). Returns the number of live
* blocks, or (size_t)-1 when the heap walk disagrees with the counters.
*/
__attribute__((visibility("hidden"))) size_t
__vlibc_malloc_check(void); // NOLINT(bugprone-reserved-identifier)
static int failures;
/* Write a NUL-terminated string to fd via the raw syscall layer. */
static void
say(int fd, const char *s)
{
long n = 0;
while (s[n] != '\0')
{
n++;
}
__syscall3(SYS_write, fd, (long)s, n);
}
/* Write v in decimal to fd. */
static void
say_dec(int fd, unsigned long v) // NOLINT(bugprone-easily-swappable-parameters)
{
char buf[24];
int i = (int)sizeof(buf);
buf[--i] = '\0';
do
{
buf[--i] = (char)('0' + (v % 10));
v /= 10;
} while (v != 0);
__syscall3(SYS_write, fd, (long)(buf + i), (long)(sizeof(buf) - 1 - i));
}
static void
check(int cond, const char *what)
{
if (cond)
{
say(1, "PASS: ");
say(1, what);
say(1, "\n");
}
else
{
say(2, "FAIL: ");
say(2, what);
say(2, "\n");
failures++;
}
}
/* xorshift32; deterministic, allocation-independent. */
static unsigned
rng_next(unsigned *state)
{
unsigned x = *state;
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
*state = x;
return x;
}
/*
* Allocation-family calls through noipa proxies so the compiler never sees
* constant arguments (a literal 0 product or a folded overflow would trip
* the alloc_size diagnostics — or, worse, let GCC treat the call as
* alloc_size-undefined and elide it, assuming a non-NULL result). noipa is
* required: plain noinline is defeated by same-TU interprocedural
* constant propagation.
*/
static __attribute__((noipa)) void *
malloc_proxy(size_t size)
{
return malloc(size);
}
static __attribute__((noipa)) void *
calloc_proxy(size_t nmemb, size_t size)
{
return calloc(nmemb, size);
}
static __attribute__((noipa)) void *
realloc_proxy(void *ptr, size_t size)
{
return realloc(ptr, size);
}
/* 1. Plain allocations across four orders of magnitude, then free. */
static void
basic_alloc_free(void)
{
static const size_t sizes[] = {1, 1024, 1048576, 1073741824};
void *p[4];
unsigned i;
for (i = 0; i < 4; i++)
{
p[i] = malloc(sizes[i]);
check(p[i] != NULL, "malloc returns non-NULL");
check(((uintptr_t)p[i] & 15) == 0, "malloc result is 16-byte aligned");
if (p[i] != NULL)
{
((unsigned char *)p[i])[0] = 0x5a;
((unsigned char *)p[i])[sizes[i] - 1] = 0xa5;
}
}
check(p[0] != p[1] && p[1] != p[2] && p[2] != p[3], "distinct blocks for distinct sizes");
for (i = 0; i < 4; i++)
{
free(p[i]);
}
check(__vlibc_malloc_check() == 0, "no live chunks after freeing all four");
}
/* 2. malloc(0) semantics: unique, usable, minimum-size. */
static void
zero_size_malloc(void)
{
void *a = malloc(0);
void *b = malloc(0);
check(a != NULL && b != NULL, "malloc(0) returns non-NULL");
check(a != b, "two malloc(0) calls return distinct pointers");
if (a != NULL)
{
((unsigned char *)a)[0] = 0x11;
}
free(a);
free(b);
}
/* 3. calloc zeroing, including a zero product. */
static void
calloc_zeroing(void)
{
unsigned char *p = calloc(100, 100);
size_t i;
int all_zero = 1;
check(p != NULL, "calloc(100, 100) returns non-NULL");
if (p != NULL)
{
for (i = 0; i < 10000; i++)
{
if (p[i] != 0)
{
all_zero = 0;
}
}
}
check(all_zero, "calloc(100, 100) is 10000 zero bytes");
free(p);
p = calloc_proxy(0, 1);
check(p != NULL, "calloc(0, 1) returns a unique zeroed block");
free(p);
}
/* 4. realloc grow / shrink / free-on-zero / NULL-as-malloc. */
static void
realloc_paths(void)
{
unsigned char *p = malloc(100);
unsigned char *q;
size_t i;
int preserved = 1;
check(p != NULL, "realloc setup: malloc(100) non-NULL");
for (i = 0; i < 100; i++)
{
p[i] = (unsigned char)i;
}
q = realloc(p, 4096);
check(q != NULL, "realloc(p, 4096) grows and returns non-NULL");
for (i = 0; i < 100; i++)
{
if (q[i] != (unsigned char)i)
{
preserved = 0;
}
}
check(preserved, "realloc grow preserves all 100 bytes");
q[4095] = 0xee;
p = realloc(q, 100);
check(p != NULL, "realloc(q, 100) shrinks and returns non-NULL");
preserved = 1;
for (i = 0; i < 100; i++)
{
if (p[i] != (unsigned char)i)
{
preserved = 0;
}
}
check(preserved, "realloc shrink preserves all 100 bytes");
q = realloc(p, 0);
check(q == NULL, "realloc(p, 0) returns NULL");
q = realloc(NULL, 64);
check(q != NULL, "realloc(NULL, 64) behaves as malloc");
free(q);
}
/* 5. aligned_alloc alignment guarantees. */
static void
aligned_alloc_paths(void)
{
void *p = aligned_alloc(64, 4096);
void *q = aligned_alloc(256, 8192);
check(p != NULL, "aligned_alloc(64, 4096) returns non-NULL");
check(((uintptr_t)p & 63) == 0, "aligned_alloc(64, 4096) is 64-byte aligned");
check(q != NULL, "aligned_alloc(256, 8192) returns non-NULL");
check(((uintptr_t)q & 255) == 0, "aligned_alloc(256, 8192) is 256-byte aligned");
if (p != NULL)
{
((unsigned char *)p)[4095] = 0x33;
}
free(p);
free(q);
check(__vlibc_malloc_check() == 0, "no live chunks after freeing aligned blocks");
}
/* 6. posix_memalign happy + EINVAL negative. */
static void
posix_memalign_paths(void)
{
void *p = (void *)0x1; /* a recognizable sentinel */
void *before = p;
int rc = posix_memalign(&p, 16, 100);
check(rc == 0, "posix_memalign(&p, 16, 100) returns 0");
check(((uintptr_t)p & 15) == 0, "posix_memalign(16) result is 16-byte aligned");
free(p);
p = (void *)0x1;
rc = posix_memalign(&p, 3, 8);
check(rc == EINVAL, "posix_memalign(&p, 3, 8) returns EINVAL");
check(p == before, "posix_memalign failure leaves *memptr unmodified");
}
#if VLIBC_LEVEL_GE(2)
/* 7. malloc_usable_size reports at least the request. */
static void
usable_size_paths(void)
{
void *p = malloc(100);
if (p != NULL)
{
check(malloc_usable_size(p) >= 100, "malloc_usable_size(p) >= requested 100");
}
else
{
check(0, "usable-size setup: malloc(100) non-NULL");
}
free(p);
check(malloc_usable_size(NULL) == 0, "malloc_usable_size(NULL) == 0");
}
#endif /* VLIBC_LEVEL_GE(2) */
/* 8. 10k random alloc/free churn ending with zero live chunks. */
static void
churn_test(void)
{
enum
{
SLOTS = 1024
};
void *slot[SLOTS];
unsigned rng = 0x9e3779b9U;
unsigned i;
for (i = 0; i < SLOTS; i++)
{
slot[i] = NULL;
}
for (i = 0; i < 10000; i++)
{
unsigned idx = rng_next(&rng) % SLOTS;
size_t sz;
if (slot[idx] != NULL)
{
free(slot[idx]);
slot[idx] = NULL;
continue;
}
sz = rng_next(&rng) % 4096;
if ((rng_next(&rng) & 31) == 0)
{
sz = 262144; /* occasional 256 KiB mmap block */
}
slot[idx] = malloc(sz == 0 ? 1 : sz);
check(slot[idx] != NULL, "churn: malloc returns non-NULL");
if (slot[idx] != NULL)
{
((unsigned char *)slot[idx])[0] = (unsigned char)sz;
}
}
for (i = 0; i < SLOTS; i++)
{
free(slot[i]);
slot[i] = NULL;
}
check(__vlibc_malloc_check() == 0, "churn ends with zero live chunks");
}
/* Failure scenarios (-f): allocation overflow returns NULL + errno ENOMEM. */
static int
failure_scenarios(void)
{
void *p;
/*
* SIZE_MAX - 10 overflows the internal chunk-size normalization
* deterministically (no syscall, no overcommit dependency — SIZE_MAX/2
* is a legitimate lazy 8 EiB mapping under Linux overcommit and can
* legitimately succeed). The noipa proxy keeps GCC from folding the
* constant and assuming the alloc_size-undefined call returns non-NULL.
*/
p = malloc_proxy((size_t)-1 - 10);
if (p != NULL)
{
say(2, "FAIL: malloc(SIZE_MAX-10) returned non-NULL\n");
failures++;
}
else
{
say(1, "PASS: malloc(SIZE_MAX-10) -> NULL (overflow)\n");
}
p = calloc_proxy(((size_t)-1) / 2, 2);
if (p != NULL)
{
say(2, "FAIL: calloc(SIZE_MAX/2, 2) returned non-NULL\n");
failures++;
}
else
{
say(1, "PASS: calloc(SIZE_MAX/2, 2) -> NULL (overflow)\n");
}
p = realloc_proxy(NULL, (size_t)-1 - 10);
if (p != NULL)
{
say(2, "FAIL: realloc(NULL, SIZE_MAX-10) returned non-NULL\n");
failures++;
}
else
{
say(1, "PASS: realloc(NULL, SIZE_MAX-10) -> NULL (overflow)\n");
}
return failures > 0 ? 1 : 0;
}
int
main(int argc, char **argv)
{
int rc;
if (argc == 2 && argv[1][0] == '-' && argv[1][1] == 'f')
{
/*
* The failure scenarios write errno inside the library; under the
* host libc that slot is glibc's private TLS state, so leave via
* the raw syscall without running host cleanup.
*/
rc = failure_scenarios();
__syscall1(SYS_exit_group, rc);
return rc; /* not reached */
}
basic_alloc_free();
zero_size_malloc();
calloc_zeroing();
realloc_paths();
aligned_alloc_paths();
posix_memalign_paths();
#if VLIBC_LEVEL_GE(2)
usable_size_paths();
#endif
churn_test();
if (failures > 0)
{
say(2, "FAILED (");
say_dec(2, (unsigned long)failures);
say(2, " check(s))\n");
return 1;
}
say(1, "all malloc tests passed\n");
return 0;
}
+372
View File
@@ -0,0 +1,372 @@
/*
* vlibc — setjmp/longjmp + sigsetjmp/siglongjmp test (todo 4).
*
* Exercises the non-local jump implementation end to end:
*
* 1. setjmp returns 0 on the initial call and 42 after longjmp(env, 42);
* 2. longjmp(env, 0) makes setjmp return 1, not 0 (POSIX coercion);
* 3. a volatile local survives a jump across a real call boundary;
* 4. FP control state (MXCSR and the x87 control word) round-trips:
* changed at the jump site, must come back as saved;
* 5. sigsetjmp(env, 1) captures a blocked SIGUSR1 mask that siglongjmp
* restores; sigsetjmp(env2, 0) must NOT restore it (flag logic);
* 6. _setjmp/_longjmp basic round-trip.
*
* The signal mask is manipulated with raw SYS_rt_sigprocmask (no signal.h:
* that header belongs to a later todo), and all diagnostics go through raw
* SYS_write (no stdio: this test must not depend on host libc headers, which
* the -Iinclude search path could otherwise shadow). With `-f`, only the
* POSIX coercion failure scenario runs: longjmp(env, 0) must return 1 — a 0
* return is the defect.
*
* Not part of the library proper; compiled manually for this todo (the
* tests/ + make check wiring is owned by a later todo).
*/
#include <setjmp.h>
#include "../src/internal/syscall.h"
/* x86_64 signal number (kernel UAPI <asm/signal.h>): SIGUSR1 = 10. */
#define TEST_SIGUSR1 10
/* rt_sigprocmask how codes (kernel UAPI). */
#define TEST_SIG_BLOCK 0
#define TEST_SIG_UNBLOCK 1
#define TEST_SIG_SETMASK 2
/* x86_64 Linux sigset_t is a single 64-bit word. */
#define TEST_SIGSETSIZE 8
static int failures;
/*
* rt_sigprocmask with the kernel argument registers pinned by hand. The
* internal __syscall4() wrapper hands its 4th argument to the compiler as a
* generic "r" operand, which the compiler may place in r8 — but the x86_64
* syscall ABI delivers the 4th argument in r10, so the kernel then sees
* garbage in sigsetsize and rejects the call. Pin r10 here (the same
* technique src/internal/syscall.h uses for __syscall6); this test is
* self-contained and must be deterministic regardless of register
* allocation.
*/
static long
test_rt_sigprocmask(long how, const unsigned long *set,
unsigned long *oldset) // NOLINT(readability-non-const-parameter)
{
register long rdx __asm__("rdx") = (long)oldset;
register long rsi __asm__("rsi") = (long)set;
register long rdi __asm__("rdi") = how;
register long r10 __asm__("r10") = TEST_SIGSETSIZE;
register long rax __asm__("rax") = SYS_rt_sigprocmask;
__asm__ volatile("syscall"
: "+a"(rax)
: "r"(rdi), "r"(rsi), "r"(rdx), "r"(r10)
: "rcx", "r11", "memory");
return rax;
}
/* Write a NUL-terminated string to fd via the raw syscall layer. */
static void
say(int fd, const char *s)
{
long n = 0;
while (s[n] != '\0')
{
n++;
}
__syscall3(SYS_write, fd, (long)s, n);
}
/* Write v in decimal to fd. */
static void
say_dec(int fd, unsigned long v) // NOLINT(bugprone-easily-swappable-parameters)
{
char buf[24];
int i = (int)sizeof(buf);
buf[--i] = '\0';
do
{
buf[--i] = (char)('0' + (v % 10));
v /= 10;
} while (v != 0);
__syscall3(SYS_write, fd, (long)(buf + i), (long)(sizeof(buf) - 1 - i));
}
static void
check(int cond, const char *what)
{
if (cond)
{
say(1, "PASS: ");
say(1, what);
say(1, "\n");
}
else
{
say(2, "FAIL: ");
say(2, what);
say(2, "\n");
failures++;
}
}
/* Raw FP control-state access (the public <fenv.h> owns this later). */
static unsigned
read_mxcsr(void)
{
unsigned x;
__asm__ volatile("stmxcsr %0" : "=m"(x));
return x;
}
static void
write_mxcsr(unsigned x)
{
__asm__ volatile("ldmxcsr %0" : : "m"(x));
}
static unsigned short
read_x87_cw(void)
{
unsigned short c;
__asm__ volatile("fnstcw %0" : "=m"(c));
return c;
}
static void
write_x87_cw(unsigned short c)
{
__asm__ volatile("fldcw %0" : : "m"(c));
}
/* Query the current signal mask via raw rt_sigprocmask (SIG_BLOCK + NULL). */
static unsigned long
query_mask(void)
{
unsigned long old = 0;
test_rt_sigprocmask(TEST_SIG_BLOCK, 0, &old);
return old;
}
/*
* Must stay a real function: the volatile-local test needs the longjmp to
* cross an actual call boundary, not an inlined body.
*/
static __attribute__((noinline)) void
jump_out(jmp_buf env, int val)
{
longjmp(env, val);
}
/* 1. Initial return 0, then longjmp(env, 42) -> 42. */
static void
basic_roundtrip(void)
{
jmp_buf env;
int r = setjmp(env);
if (r == 0)
{
longjmp(env, 42);
check(0, "longjmp returned to its caller (noreturn violated)");
return;
}
check(r == 42, "longjmp(env,42) -> setjmp returned 42");
}
/* 2. longjmp(env, 0) -> setjmp returns 1, never 0. */
static void
coercion_test(void)
{
jmp_buf env;
int r = setjmp(env);
if (r == 0)
{
longjmp(env, 0);
check(0, "longjmp returned to its caller (noreturn violated)");
return;
}
check(r == 1, "longjmp(env,0) -> setjmp returned 1, not 0 (POSIX coercion)");
}
/* 3. Volatile locals survive a jump across a call boundary. */
static void
volatile_local_test(void)
{
jmp_buf env;
volatile int local = 1;
int r = setjmp(env);
if (r == 0)
{
local = 99;
jump_out(env, 5);
check(0, "longjmp returned to its caller (noreturn violated)");
return;
}
check(r == 5 && local == 99, "volatile local survives longjmp across a call boundary");
}
/* 4. FP control state round-trips through setjmp/longjmp. */
static void
fp_state_test(void)
{
jmp_buf env;
unsigned mx = read_mxcsr();
unsigned short cw = read_x87_cw();
int r = setjmp(env);
if (r == 0)
{
/* Toggle the MXCSR rounding-control bits and the x87 PC+RC bits. */
write_mxcsr(mx ^ 0x6000U);
write_x87_cw((unsigned short)(cw ^ 0x0f00U));
longjmp(env, 3);
check(0, "longjmp returned to its caller (noreturn violated)");
return;
}
check(r == 3, "FP-state test: longjmp(env,3) -> setjmp returned 3");
check(read_mxcsr() == mx, "mxcsr restored by longjmp");
check(read_x87_cw() == cw, "x87 control word restored by longjmp");
}
/* 5. sigsetjmp/siglongjmp round-trip a blocked mask; savemask=0 skips it. */
static void
sigmask_test(void)
{
sigjmp_buf env;
sigjmp_buf env2;
unsigned long block = 1UL << (TEST_SIGUSR1 - 1);
unsigned long orig = query_mask();
int r;
/* Block SIGUSR1 (raw rt_sigprocmask), then save the masked state. */
test_rt_sigprocmask(TEST_SIG_BLOCK, &block, 0);
r = sigsetjmp(env, 1);
if (r == 0)
{
/* Drop the block; siglongjmp must restore it. */
test_rt_sigprocmask(TEST_SIG_UNBLOCK, &block, 0);
siglongjmp(env, 7);
check(0, "siglongjmp returned to its caller (noreturn violated)");
return;
}
check(r == 7, "siglongjmp(env,7) -> sigsetjmp returned 7");
check((query_mask() & block) != 0, "siglongjmp restored the blocked SIGUSR1 mask");
/* Negative: savemask == 0 -> the mask must NOT be restored. */
r = sigsetjmp(env2, 0);
if (r == 0)
{
test_rt_sigprocmask(TEST_SIG_UNBLOCK, &block, 0);
siglongjmp(env2, 8);
check(0, "siglongjmp returned to its caller (noreturn violated)");
return;
}
check(r == 8, "siglongjmp(env2,8) -> sigsetjmp returned 8");
check((query_mask() & block) == 0, "sigsetjmp(env2,0): mask NOT restored (flag logic)");
/* Leave the process mask as it was found. */
test_rt_sigprocmask(TEST_SIG_SETMASK, &orig, 0);
}
/* 6. _setjmp/_longjmp basic round-trip. */
static void
underscore_forms_test(void)
{
jmp_buf env;
int r = _setjmp(env);
if (r == 0)
{
_longjmp(env, 9);
check(0, "_longjmp returned to its caller (noreturn violated)");
return;
}
check(r == 9, "_longjmp(env,9) -> _setjmp returned 9");
}
/*
* 7. The initial sigsetjmp(env, 1) return must preserve the caller's rbx.
* A canary is planted in rbx via inline assembly (declared clobbered, so
* the compiler holds nothing else there), sigsetjmp returns normally, and
* rbx is read back raw: the SysV AMD64 ABI requires the function to leave
* every callee-saved register intact on the ordinary return path, and the
* compiler assumes exactly that when allocating registers around the call,
* so nothing reloads rbx between the plant and the read.
*/
static __attribute__((noinline)) void
rbx_callee_saved_test(void)
{
sigjmp_buf env;
unsigned long after = 0;
int r;
__asm__ volatile("mov $0x12345678, %%rbx" ::: "rbx");
r = sigsetjmp(env, 1);
__asm__ volatile("mov %%rbx, %0" : "=r"(after));
check(r == 0, "sigsetjmp(env,1) initial return is 0 (rbx canary scenario)");
check(after == 0x12345678UL, "caller rbx preserved across sigsetjmp(env,1) initial return");
}
/*
* Failure scenario (-f): longjmp(env, 0) must make setjmp return 1. Exits 0
* only when the coercion behaved exactly as POSIX specifies.
*/
static int
failure_scenario(void)
{
jmp_buf env;
int r = setjmp(env);
if (r == 0)
{
longjmp(env, 0);
return 1; /* longjmp returned: noreturn violated */
}
if (r == 1)
{
say(1, "longjmp(env,0) -> setjmp returned 1 (a 0 return would be the defect)\n");
return 0;
}
say(1, "longjmp(env,0) -> setjmp returned ");
say_dec(1, (unsigned long)r);
say(1, ", want 1\n");
return 1;
}
int
main(int argc, char **argv)
{
if (argc == 2 && argv[1][0] == '-' && argv[1][1] == 'f')
{
return failure_scenario();
}
basic_roundtrip();
coercion_test();
volatile_local_test();
fp_state_test();
sigmask_test();
underscore_forms_test();
rbx_callee_saved_test();
if (failures > 0)
{
say(2, "FAILED (");
say_dec(2, (unsigned long)failures);
say(2, " check(s))\n");
return 1;
}
say(1, "all setjmp tests passed\n");
return 0;
}
+330
View File
@@ -0,0 +1,330 @@
/*
* vlibc — startup test (todo 3).
*
* Exercises the static program-startup path end to end. This test is
* COMPILED INTO A STATIC VLIBC BINARY (harness commands in
* .omo/evidence/task-3-full-posix.log): glibc is never linked, so every
* exit()/atexit()/environ reference resolves to vlibc's own symbols via
* crt1.s and the src/start objects. Running it under the host libc would
* silently test glibc, not vlibc — the static link is the test.
*
* Modes (argv[1]):
* (none) atexit LIFO: register A, B, C on the atexit list, Q on the
* at_quick_exit list, then return 42 from main. exit() must run
* C, B, A in that order (reverse registration), must NOT run Q,
* and the process must exit with status 42.
* -q quick_exit(5): the at_quick_exit handler runs, the atexit
* handler must NOT. Status must be 5.
* -z _exit(7): neither handler list runs at all. Status 7.
* -e argv/env plumbing: prints argc and argv, checks
* VLIBC_TEST_VAR=hello in environ. Status 0 on match.
* -t main-thread TLS + errno: a file-scope __thread int set in main
* must read back through a separate function (exercises the
* PT_TLS copy path), and errno must round-trip through the TCB
* slot (proves the bootstrap ran before the first libc call).
* -x handler-list limits: 32 registrations fit, the 33rd returns
* -1, and atexit() during exit is gracefully refused (-1).
*
* No host headers: -Iinclude shadows GCC's internal headers (see
* tests/test_strerror.c). <stddef.h>/<errno.h> below are vlibc's own.
* All diagnostics go through raw SYS_write.
*/
#include <errno.h>
#include <stddef.h>
#include "../src/internal/syscall.h"
/*
* Manual declarations of the startup surface under test: <stdlib.h> and
* <unistd.h> are later todos; consumers today declare exactly these names,
* matching src/start/start.h.
*/
[[noreturn]] void
exit(int status);
[[noreturn]] void
_Exit(int status);
[[noreturn]] void
_exit(int status);
[[noreturn]] void
quick_exit(int status);
int
atexit(void (*func)(void));
int
at_quick_exit(void (*func)(void));
extern char **environ;
static int failures;
/* Write a NUL-terminated string to fd via the raw syscall layer. */
static void
say(int fd, const char *s)
{
long n = 0;
while (s[n] != '\0')
{
n++;
}
__syscall3(SYS_write, fd, (long)s, n);
}
/* Write v in decimal to fd. */
static void
say_dec(int fd, unsigned long v) // NOLINT(bugprone-easily-swappable-parameters)
{
char buf[24];
int i = (int)sizeof(buf);
buf[--i] = '\0';
do
{
buf[--i] = (char)('0' + (v % 10));
v /= 10;
} while (v != 0);
__syscall3(SYS_write, fd, (long)(buf + i), (long)(sizeof(buf) - 1 - i));
}
static void
check(int cond, const char *what)
{
if (!cond)
{
say(2, "FAIL: ");
say(2, what);
say(2, "\n");
failures++;
}
}
/* True when s is exactly lit. Hand-rolled: this test keeps deps minimal. */
static int
str_is(const char *s, const char *lit)
{
int i = 0;
while (lit[i] != '\0' && s[i] == lit[i])
{
i++;
}
return lit[i] == '\0' && s[i] == '\0';
}
/* True when environ holds an entry spelling "name=want". */
static int // NOLINT(bugprone-easily-swappable-parameters)
env_matches(const char *name, const char *want)
{
for (char **e = environ; e != 0 && *e != 0; e++)
{
const char *s = *e;
int i = 0;
while (name[i] != '\0' && s[i] == name[i])
{
i++;
}
if (name[i] == '\0' && s[i] == '=')
{
int j = 0;
while (want[j] != '\0' && s[i + 1 + j] == want[j])
{
j++;
}
if (want[j] == '\0' && s[i + 1 + j] == '\0')
{
return 1;
}
}
}
return 0;
}
/* Exit handlers. Each prints one distinguishing marker line. */
static void
hA(void)
{
say(1, "A\n");
}
static void
hB(void)
{
say(1, "B\n");
}
static void
hC(void)
{
say(1, "C\n");
}
static void
hQ(void)
{
say(1, "Q\n");
}
static void
hNoop(void)
{}
/* Registered as one of the 32 in -x mode: registration during exit must fail. */
static void
hRegisterDuringExit(void)
{
if (atexit(hA) == 0)
{
say(2, "FAIL: atexit() during exit accepted (want graceful -1)\n");
failures++;
}
}
/*
* File-scope __thread objects: live in PT_TLS, every access is %fs-relative
* with a link-time offset. tls_preinit tests the .tdata image copy (nonzero
* initial value), tls_counter tests the .tbss zeroing plus the write/read
* round trip. The reads go through noinline helpers on opaque pointers:
* a direct read of a static TLS object whose initializer the compiler can
* see gets constant-folded, which would delete the variable (and with it
* the PT_TLS image) and silently untest the copy path. The address-of and
* the helper-force a real TLS relocation and a real memory load.
*/
static __thread int tls_preinit = 42;
static __thread int tls_counter;
static int __attribute__((noinline))
tls_deref(int *p)
{
/* Opaque barrier: prevents interprocedural folding of *p to 42, which
* would delete the TLS image (see the block comment above). */
__asm__ volatile("" : "+r"(p));
return *p;
}
static int __attribute__((noinline))
tls_readback(void)
{
return tls_counter;
}
/*
* Scenario 1: atexit LIFO. exit() must run the LAST registered handler
* first and skip the at_quick_exit list entirely.
*/
static int
lifo_scenario(void)
{
atexit(hA);
atexit(hB);
atexit(hC);
at_quick_exit(hQ); /* must NOT run: no "Q" on stdout */
return 42; /* routed into exit(42) by __libc_start_main */
}
/* Scenario 2: quick_exit runs ONLY at_quick_exit handlers. */
static int
quick_scenario(void)
{
atexit(hA); /* must NOT run: no "A" on stdout */
at_quick_exit(hQ);
quick_exit(5);
return 1; /* unreachable */
}
/* Scenario 3: _exit runs NO handlers at all. */
static int
raw_exit_scenario(void)
{
atexit(hA);
at_quick_exit(hQ);
_exit(7);
return 1; /* unreachable */
}
/* Scenario 4: argc/argv/environ plumbing from the kernel stack. */
static int
env_scenario(int argc, char **argv)
{
if (argc != 3)
{
say(2, "FAIL: argc != 3 (harness must pass -e plus one argument)\n");
return 1;
}
say(1, "argc=");
say_dec(1, (unsigned long)argc);
say(1, "\nargv0=");
say(1, argv[0]);
say(1, "\nargv1=");
say(1, argv[1]);
say(1, "\nargv2=");
say(1, argv[2]);
say(1, "\n");
check(str_is(argv[1], "-e"), "argv[1] is the -e mode flag");
check(str_is(argv[2], "extraarg"), "argv[2] == \"extraarg\"");
check(env_matches("VLIBC_TEST_VAR", "hello"), "environ contains VLIBC_TEST_VAR=hello");
return failures == 0 ? 0 : 1;
}
/* Scenario 5: main-thread TLS image + TCB errno slot. */
static int
tls_scenario(void)
{
check(tls_deref(&tls_preinit) == 42, "initialized TLS image copied (.tdata value 42)");
check(tls_counter == 0, "TLS BSS tail zeroed (.tbss value 0)");
tls_counter = 777;
check(tls_readback() == 777, "__thread round trip across a function call");
errno = 123;
check(errno == 123, "errno round trip through the TCB slot");
return failures == 0 ? 0 : 1;
}
/* Scenario 6: fixed handler-list limits and the exiting guard. */
static int
limit_scenario(void)
{
for (int i = 0; i < 31; i++)
{
if (atexit(hNoop) != 0)
{
say(2, "FAIL: atexit slot accounting broke before the list was full\n");
return 1;
}
}
if (atexit(hRegisterDuringExit) != 0)
{
say(2, "FAIL: 32nd atexit registration rejected (list should just fit)\n");
return 1;
}
check(atexit(hA) == -1, "33rd atexit() returns -1 (fixed list full)");
return failures == 0 ? 0 : 1;
}
int
main(int argc, char **argv)
{
if (argc >= 2)
{
if (argv[1][0] == '-' && argv[1][1] == 'q')
{
return quick_scenario();
}
if (argv[1][0] == '-' && argv[1][1] == 'z')
{
return raw_exit_scenario();
}
if (argv[1][0] == '-' && argv[1][1] == 'e')
{
return env_scenario(argc, argv);
}
if (argv[1][0] == '-' && argv[1][1] == 't')
{
return tls_scenario();
}
if (argv[1][0] == '-' && argv[1][1] == 'x')
{
return limit_scenario();
}
}
return lifo_scenario();
}
+169
View File
@@ -0,0 +1,169 @@
/*
* vlibc — strerror/strerror_r test (todo 2).
*
* Exercises the public <errno.h> end to end:
*
* 1. strerror(0) is "Success"; known values return the exact message and
* are non-NULL and distinct; aliases (EWOULDBLOCK/EDEADLOCK) resolve to
* their canonical entries; the last table value (EHWPOISON) is covered.
* 2. Unknown errno values never return NULL and never crash: 9999 and the
* ABI-gap value 41 format as "Unknown error <N>", negatives too.
* 3. strerror_r (XSI): returns 0 and fills buf with the right text; an
* unknown value still returns 0 with "Unknown error <N>"; truncation
* into a tiny buf stays NUL-terminated; buflen 0 writes nothing; a NULL
* buf with buflen > 0 returns EINVAL.
*
* The test deliberately never touches errno itself: under the host libc the
* TCB slot our errno macro addresses is glibc's private TLS state, and
* writing it would corrupt the host (see tests/syscall_test.c). All coverage
* here is through the errnum parameters.
*
* All diagnostics go through raw SYS_write (no stdio): under -Iinclude the
* vlibc public headers shadow GCC's internal ones, so mixing in a host
* <stdio.h> breaks compilation (glibc's stdio.h pulls in our minimal
* <stdarg.h>, which lacks __gnuc_va_list). <string.h> below is vlibc's own
* header, which is safe.
*
* Not part of the library proper; compiled manually for this todo (the
* tests/ + make check wiring is owned by a later todo).
*/
#include <errno.h>
#include <string.h>
#include "../src/internal/syscall.h"
static int failures;
/* Write a NUL-terminated string to fd via the raw syscall layer. */
static void
say(int fd, const char *s)
{
long n = 0;
while (s[n] != '\0')
{
n++;
}
__syscall3(SYS_write, fd, (long)s, n);
}
static void
check(int cond, const char *what)
{
if (!cond)
{
say(2, "FAIL: ");
say(2, what);
say(2, "\n");
failures++;
}
}
/*
* Failure scenario (-f): an unknown errno must yield "Unknown error 9999",
* non-NULL, without crashing. Prints the observed string; exits 0 only when
* the failure behaved exactly as specified.
*/
static int
failure_scenario(void)
{
const char *s = strerror(9999);
if (s == NULL)
{
say(1, "strerror(9999)=NULL\n");
return 1;
}
say(1, "strerror(9999)=");
say(1, s);
say(1, "\n");
return strcmp(s, "Unknown error 9999") == 0 ? 0 : 1;
}
int
main(int argc, char **argv)
{
const char *a;
const char *b;
char buf[64];
char tiny[8];
char untouched[4];
int r;
if (argc == 2 && argv[1][0] == '-' && argv[1][1] == 'f')
{
return failure_scenario();
}
/* 1. Known values: exact text, non-NULL, distinct. */
check(strcmp(strerror(0), "Success") == 0, "strerror(0) == \"Success\"");
check(strcmp(strerror(2), "No such file or directory") == 0,
"strerror(2) == \"No such file or directory\"");
check(strcmp(strerror(EINVAL), "Invalid argument") == 0,
"strerror(EINVAL) == \"Invalid argument\"");
check(strcmp(strerror(EPERM), "Operation not permitted") == 0,
"strerror(EPERM) == \"Operation not permitted\"");
a = strerror(EINVAL);
b = strerror(EPERM);
check(a != NULL && b != NULL, "known values non-NULL");
check(a != b, "known values distinct");
/* Aliases resolve to their canonical entries. */
check(strerror(EWOULDBLOCK) == strerror(EAGAIN), "EWOULDBLOCK == EAGAIN");
check(strerror(EDEADLOCK) == strerror(EDEADLK), "EDEADLOCK == EDEADLK");
/* Table ends: last defined value and ABI gap values. */
check(strerror(EHWPOISON) != NULL, "EHWPOISON (last value) non-NULL");
check(strcmp(strerror(41), "Unknown error 41") == 0, "ABI gap 41 unknown");
check(strcmp(strerror(58), "Unknown error 58") == 0, "ABI gap 58 unknown");
/* 2. Unknown values: never NULL, never crash, formatted. */
a = strerror(9999);
check(a != NULL, "strerror(9999) non-NULL");
check(strcmp(a, "Unknown error 9999") == 0, "strerror(9999) text");
a = strerror(-7);
check(a != NULL, "strerror(-7) non-NULL");
check(strcmp(a, "Unknown error -7") == 0, "strerror(-7) text");
/* 3. strerror_r: XSI int return, exact fill. */
r = strerror_r(EDOM, buf, sizeof buf);
check(r == 0, "strerror_r(EDOM) returns 0");
check(strcmp(buf, "Numerical argument out of domain") == 0, "strerror_r(EDOM) fills buf");
/* Unknown errno still succeeds and formats into buf (XSI). */
r = strerror_r(9999, buf, sizeof buf);
check(r == 0, "strerror_r(9999) returns 0 (XSI)");
check(strcmp(buf, "Unknown error 9999") == 0, "strerror_r(9999) text");
/* Truncation into a tiny buf stays NUL-terminated. */
r = strerror_r(ENAMETOOLONG, tiny, sizeof tiny);
check(r == 0, "strerror_r(ENAMETOOLONG, tiny) returns 0");
check(tiny[sizeof tiny - 1] == '\0', "tiny buf NUL-terminated");
check(strcmp(tiny, "File na") == 0, "tiny buf truncated prefix");
/* buflen 0: nothing written, still success. */
untouched[0] = 'X';
untouched[1] = 'X';
untouched[2] = 'X';
untouched[3] = '\0';
r = strerror_r(EDOM, untouched, 0);
check(r == 0, "strerror_r(EDOM, buf, 0) returns 0");
check(untouched[0] == 'X', "buflen 0 writes nothing");
/* NULL buf with buflen > 0: EINVAL. */
r = strerror_r(EDOM, NULL, 16);
check(r == EINVAL, "strerror_r(NULL buf) returns EINVAL");
if (failures > 0)
{
say(2, "FAILED\n");
}
else
{
say(1, "strerror ok\n");
}
return failures == 0 ? 0 : 1;
}
+709
View File
@@ -0,0 +1,709 @@
/*
* vlibc — string.h completion test (todo 8).
*
* Covers the new string search/memory functions end to end:
*
* 1. copy family: strcpy/strncpy (NUL padding), strcat/strncat (always
* NUL-terminated), L2 stpcpy/stpncpy (return the NUL position);
* 2. search family: strchr/strrchr (including the NUL as a match),
* strspn/strcspn/strpbrk, strstr (empty/longer/overlapping needles);
* 3. tokenizer: strtok/strtok_r — empty source yields NULL, consecutive
* delimiters produce NO empty tokens, an all-delimiter string yields
* NULL on the first call, and the delimiter string is never modified;
* 4. collation: strcoll == strcmp and strxfrm (identity transform,
* returns strlen, truncates to n-1 bytes + NUL) — "C" locale only;
* L2 strcoll_l/strxfrm_l ignore the locale argument;
* 5. memory: memchr (incl. negative c), memcmp ordering, L2 memccpy;
* 6. misc: strnlen (stops at maxlen), strncmp (stops at NUL), strdup/
* strndup round-trips, strsignal (distinct non-null strings; unknown
* numbers format as "Unknown signal <N>"; signal 0 is asserted
* distinct + non-null only, per POSIX).
*
* The expected values were captured against the host glibc as the
* ground-truth oracle for the corpus (see the task-8 evidence log); the
* assertions below encode those golden results inline. Two strxfrm cases
* intentionally assert VLIBc's pinned contract (n-1 bytes + NUL, return
* strlen) rather than glibc's: POSIX leaves the dst contents UNSPECIFIED
* when the return value is >= n.
*
* strdup/strndup allocate through the internal __libc_malloc seam
* (src/internal/malloc.h); under make check the real allocator (todo 7)
* provides it. For the STANDALONE verification of this todo the test links
* a throwaway /tmp allocator stub (never committed) — see the evidence log.
*
* With `-p`, a representative sample of results is printed to stdout for
* eyeball diffing; the assertions always run.
*
* No host libc headers are included (the -Iinclude path would shadow GCC's
* internal headers); all output goes through raw SYS_write via
* <vlibc/internal/test.h>. Not yet wired into make check (a later todo).
*
* Standalone build:
* gcc -Iinclude -DVLIBC_LEVEL=2 -std=c23 -Wall -Wextra -pedantic \
* -o /tmp/t8 tests/test_string.c \
* src/string/{strcpy,strncpy,strcat,strchr,strspn,strstr,strtok,strcoll,strdup,strnlen,strncmp,memchr,memcmp,strsignal,stpcpy,memccpy}.c \
* src/string/{strlen,strcmp}.c /tmp/allocstub.c \
* src/internal/errno.c src/internal/syscall_ret.c
*/
#include <string.h>
#include <vlibc/internal/test.h>
#include "../src/internal/malloc.h"
/* ---- shared helpers ---- */
/*
* Collect up to cap tokens from a fresh copy of s using strtok; the copy is
* consumed (delimiters become NUL), matching POSIX strtok semantics.
*/
static int
tok_collect(char *s, const char *sep, char *out[], int cap)
{
char *t;
int n = 0;
t = strtok(s, sep);
while (t != NULL && n < cap)
{
out[n++] = t;
t = strtok(NULL, sep);
}
return n;
}
/* Same, but through strtok_r with an explicit state. */
static int
tokr_collect(char *s, const char *sep, char *out[], int cap)
{
char *t;
char *state = NULL;
int n = 0;
t = strtok_r(s, sep, &state);
while (t != NULL && n < cap)
{
out[n++] = t;
t = strtok_r(NULL, sep, &state);
}
return n;
}
/*
* GCC's -Wstringop-truncation/-Wstringop-overread fire when the source
* length is statically visible at a bounded-copy call site. The truncation
* and oversized-n corpus cases below are exactly what those diagnostics
* guard against — intentionally — so they go through noipa wrappers
* (noipa, not just noinline: GCC's constprop clones ignore noinline at
* -O2 and re-expose the call sites).
*/
static __attribute__((noipa)) char *
strncpy_opaque(char *dst, const char *src, size_t n)
{
return strncpy(dst, src, n);
}
static __attribute__((noipa)) char *
strncat_opaque(char *dst, const char *src, size_t n)
{
return strncat(dst, src, n);
}
static __attribute__((noipa)) char *
strndup_opaque(const char *s, size_t n)
{
return strndup(s, n);
}
/* ---- test functions (one per checked behavior) ---- */
static int
test_strcpy(void)
{
char buf[16];
TEST_ASSERT_TRUE(strcpy(buf, "hello") == buf);
TEST_ASSERT_STREQ(buf, "hello");
TEST_ASSERT_TRUE(strcpy(buf, "") == buf);
TEST_ASSERT_STREQ(buf, "");
return 0;
}
static int
test_strncpy(void)
{
char buf[8];
/* Shorter src: NUL-padded to n. */
memset(buf, 0x41, sizeof buf);
TEST_ASSERT_TRUE(strncpy(buf, "hi", 5) == buf);
TEST_ASSERT_STREQ(buf, "hi");
TEST_ASSERT_EQ(buf[2], '\0');
TEST_ASSERT_EQ(buf[3], '\0');
TEST_ASSERT_EQ(buf[4], '\0');
TEST_ASSERT_EQ(buf[5], 'A'); /* untouched past n */
/* Src n bytes or longer: no NUL written within n. */
memset(buf, 0x41, sizeof buf);
TEST_ASSERT_TRUE(strncpy_opaque(buf, "hello", 3) == buf);
TEST_ASSERT_EQ(buf[0], 'h');
TEST_ASSERT_EQ(buf[1], 'e');
TEST_ASSERT_EQ(buf[2], 'l');
TEST_ASSERT_EQ(buf[3], 'A'); /* not NUL-terminated */
/* n == 0: nothing written. */
memset(buf, 0x41, sizeof buf);
strncpy_opaque(buf, "hello", 0);
TEST_ASSERT_EQ(buf[0], 'A');
return 0;
}
static int
test_strcat(void)
{
char buf[16];
strcpy(buf, "hello");
TEST_ASSERT_TRUE(strcat(buf, " world") == buf);
TEST_ASSERT_STREQ(buf, "hello world");
strcpy(buf, "");
strcat(buf, "x");
TEST_ASSERT_STREQ(buf, "x");
return 0;
}
static int
test_strncat(void)
{
char buf[16];
strcpy(buf, "abc");
TEST_ASSERT_TRUE(strncat_opaque(buf, "defgh", 3) == buf);
TEST_ASSERT_STREQ(buf, "abcdef");
strcpy(buf, "abc");
strncat(buf, "defgh", 10);
TEST_ASSERT_STREQ(buf, "abcdefgh");
strcpy(buf, "abc");
strncat_opaque(buf, "xyz", 0);
TEST_ASSERT_STREQ(buf, "abc");
return 0;
}
static int
test_strchr(void)
{
const char *s = "hello";
TEST_ASSERT_EQ(strchr(s, 'l') - s, 2);
TEST_ASSERT_EQ(strchr(s, 'h') - s, 0);
TEST_ASSERT_NULL(strchr(s, 'z'));
TEST_ASSERT_EQ(strchr(s, '\0') - s, 5);
return 0;
}
static int
test_strrchr(void)
{
const char *s = "hello";
TEST_ASSERT_EQ(strrchr(s, 'l') - s, 3);
TEST_ASSERT_EQ(strrchr(s, 'h') - s, 0);
TEST_ASSERT_NULL(strrchr(s, 'z'));
TEST_ASSERT_EQ(strrchr(s, '\0') - s, 5);
return 0;
}
static int
test_strspn(void)
{
TEST_ASSERT_EQ(strspn("abcba", "abc"), 5);
TEST_ASSERT_EQ(strspn("abcba", "ab"), 2);
TEST_ASSERT_EQ(strspn("abcba", "z"), 0);
TEST_ASSERT_EQ(strspn("", "ab"), 0);
return 0;
}
static int
test_strcspn(void)
{
TEST_ASSERT_EQ(strcspn("abcba", "b"), 1);
TEST_ASSERT_EQ(strcspn("abcba", "z"), 5);
TEST_ASSERT_EQ(strcspn("abcba", "a"), 0);
TEST_ASSERT_EQ(strcspn("", "ab"), 0);
return 0;
}
static int
test_strpbrk(void)
{
const char *s = "hello";
TEST_ASSERT_EQ(strpbrk(s, "aeiou") - s, 1);
TEST_ASSERT_EQ(strpbrk(s, "h") - s, 0);
TEST_ASSERT_NULL(strpbrk(s, "xyz"));
TEST_ASSERT_NULL(strpbrk("", "x"));
return 0;
}
static int
test_strstr(void)
{
const char *h = "hello world";
TEST_ASSERT_EQ(strstr(h, "world") - h, 6);
TEST_ASSERT_EQ(strstr(h, "hello") - h, 0);
TEST_ASSERT_EQ(strstr(h, "o") - h, 4);
TEST_ASSERT_NULL(strstr(h, "helloo"));
TEST_ASSERT_NULL(strstr("hi", "hello")); /* needle longer than haystack */
TEST_ASSERT_TRUE(strstr(h, "") == h); /* empty needle matches haystack */
TEST_ASSERT_EQ(strstr("aaaa", "aaa") - "aaaa", 0); /* overlapping */
TEST_ASSERT_EQ(strstr("banana", "nan") - "banana", 2);
TEST_ASSERT_EQ(strstr("abc", "bc") - "abc", 1);
return 0;
}
static int
test_strtok(void)
{
char buf[32];
char *out[8];
int n;
/* Empty source: first call yields NULL (no tokens). */
strcpy(buf, "");
TEST_ASSERT_NULL(strtok(buf, " "));
/* Consecutive delimiters produce NO empty tokens. */
strcpy(buf, "a,,b");
n = tok_collect(buf, ",", out, 8);
TEST_ASSERT_EQ(n, 2);
TEST_ASSERT_STREQ(out[0], "a");
TEST_ASSERT_STREQ(out[1], "b");
/* All delimiters: first call yields NULL. */
strcpy(buf, ",,,");
TEST_ASSERT_NULL(strtok(buf, ","));
/* Leading + trailing delimiters are skipped. */
strcpy(buf, "xxhelloxx");
n = tok_collect(buf, "x", out, 8);
TEST_ASSERT_EQ(n, 1);
TEST_ASSERT_STREQ(out[0], "hello");
/* No delimiter present: the whole string is one token. */
strcpy(buf, "hello");
n = tok_collect(buf, " ", out, 8);
TEST_ASSERT_EQ(n, 1);
TEST_ASSERT_STREQ(out[0], "hello");
/* Multiple whitespace runs collapse to the same tokens. */
strcpy(buf, "a b c");
n = tok_collect(buf, " ", out, 8);
TEST_ASSERT_EQ(n, 3);
TEST_ASSERT_STREQ(out[0], "a");
TEST_ASSERT_STREQ(out[1], "b");
TEST_ASSERT_STREQ(out[2], "c");
/* The delimiter string is never modified. */
{
static char sep[] = ",;";
char *sep_before = sep;
strcpy(buf, "a,b;c");
(void)tok_collect(buf, sep, out, 8);
TEST_ASSERT_TRUE(sep == sep_before);
TEST_ASSERT_EQ(sep[0], ',');
TEST_ASSERT_EQ(sep[1], ';');
}
return 0;
}
static int
test_strtok_r(void)
{
char buf[32];
char *out[8];
int n;
strcpy(buf, "a,,b");
n = tokr_collect(buf, ",", out, 8);
TEST_ASSERT_EQ(n, 2);
TEST_ASSERT_STREQ(out[0], "a");
TEST_ASSERT_STREQ(out[1], "b");
strcpy(buf, "");
TEST_ASSERT_NULL(strtok_r(buf, " ", &(char *){NULL}));
/* Two interleaved scans keep independent state. */
{
char s1[16];
char s2[16];
char *state1 = NULL;
char *state2 = NULL;
char *t1;
char *t2;
strcpy(s1, "one two");
strcpy(s2, "red green blue");
t1 = strtok_r(s1, " ", &state1);
t2 = strtok_r(s2, " ", &state2);
TEST_ASSERT_STREQ(t1, "one");
TEST_ASSERT_STREQ(t2, "red");
t1 = strtok_r(NULL, " ", &state1);
TEST_ASSERT_STREQ(t1, "two");
t2 = strtok_r(NULL, " ", &state2);
TEST_ASSERT_STREQ(t2, "green");
t1 = strtok_r(NULL, " ", &state1);
TEST_ASSERT_NULL(t1);
t2 = strtok_r(NULL, " ", &state2);
TEST_ASSERT_STREQ(t2, "blue");
}
return 0;
}
static int
test_strcoll(void)
{
TEST_ASSERT_EQ(strcoll("apple", "banana"), strcmp("apple", "banana"));
TEST_ASSERT_EQ(strcoll("apple", "banana") < 0, 1);
TEST_ASSERT_EQ(strcoll("b", "a") > 0, 1);
TEST_ASSERT_EQ(strcoll("same", "same"), 0);
TEST_ASSERT_EQ(strcoll("A", "a"), strcmp("A", "a"));
TEST_ASSERT_EQ(strcoll("", ""), 0);
return 0;
}
static int
test_strxfrm(void)
{
char xf[16];
/* Identity transform, returns strlen(src). */
TEST_ASSERT_EQ(strxfrm(xf, "hello", sizeof xf), 5);
TEST_ASSERT_STREQ(xf, "hello");
/* Truncation: at most n-1 bytes + NUL, return still 5. */
TEST_ASSERT_EQ(strxfrm(xf, "hello", 4), 5);
TEST_ASSERT_STREQ(xf, "hel");
/* n == 0: nothing written, return still 5. */
memset(xf, 0x41, sizeof xf);
TEST_ASSERT_EQ(strxfrm(xf, "hello", 0), 5);
TEST_ASSERT_EQ(xf[0], 'A');
/* n == 1: only the NUL. */
TEST_ASSERT_EQ(strxfrm(xf, "hello", 1), 5);
TEST_ASSERT_EQ(xf[0], '\0');
TEST_ASSERT_EQ(strxfrm(xf, "", sizeof xf), 0);
TEST_ASSERT_STREQ(xf, "");
return 0;
}
static int
test_strdup(void)
{
char src[] = "hello";
char *p = strdup(src);
TEST_ASSERT_TRUE(p != NULL);
TEST_ASSERT_STREQ(p, "hello");
TEST_ASSERT_TRUE(p != src); /* a real copy, not the source array */
__libc_free(p);
p = strdup("");
TEST_ASSERT_TRUE(p != NULL);
TEST_ASSERT_STREQ(p, "");
__libc_free(p);
return 0;
}
static int
test_strndup(void)
{
char *p = strndup("abcdef", 3);
TEST_ASSERT_TRUE(p != NULL);
TEST_ASSERT_STREQ(p, "abc");
__libc_free(p);
p = strndup_opaque("ab", 10); /* n beyond the NUL: whole string */
TEST_ASSERT_TRUE(p != NULL);
TEST_ASSERT_STREQ(p, "ab");
__libc_free(p);
p = strndup("xyz", 0); /* n == 0: empty string */
TEST_ASSERT_TRUE(p != NULL);
TEST_ASSERT_STREQ(p, "");
__libc_free(p);
p = strndup("", 0);
TEST_ASSERT_TRUE(p != NULL);
TEST_ASSERT_STREQ(p, "");
__libc_free(p);
return 0;
}
static int
test_strnlen(void)
{
TEST_ASSERT_EQ(strnlen("abc", 2), 2);
TEST_ASSERT_EQ(strnlen("abc", 10), 3);
TEST_ASSERT_EQ(strnlen("abc", 0), 0);
TEST_ASSERT_EQ(strnlen("", 5), 0);
return 0;
}
static int
test_strncmp(void)
{
TEST_ASSERT_EQ(strncmp("abc", "abc", 3), 0);
TEST_ASSERT_EQ(strncmp("ab", "abc", 2), 0);
TEST_ASSERT_EQ(strncmp("ab", "ac", 1), 0);
TEST_ASSERT_EQ(strncmp("abc", "abd", 3) < 0, 1);
TEST_ASSERT_EQ(strncmp("abd", "abc", 3) > 0, 1);
TEST_ASSERT_EQ(strncmp("x", "y", 0), 0); /* n == 0 */
/* Stops at the first NUL even when n is larger. */
TEST_ASSERT_EQ(strncmp("a\0b", "a\0c", 5), 0);
TEST_ASSERT_EQ(strncmp("a\0b", "a", 5), 0);
return 0;
}
static int
test_memchr(void)
{
const char *s = "hello";
unsigned char high[] = {0x41, 0xff, 0x00};
TEST_ASSERT_EQ((char *)memchr(s, 'l', 5) - s, 2);
TEST_ASSERT_NULL(memchr(s, 'z', 5));
TEST_ASSERT_NULL(memchr(s, 'h', 0)); /* n == 0 */
TEST_ASSERT_EQ((char *)memchr(s, '\0', 6) - s, 5);
/* Negative c searches for the matching high byte. */
TEST_ASSERT_EQ((char *)memchr(high, 0xff, 3) - (char *)high, 1);
return 0;
}
static int
test_memcmp(void)
{
unsigned char hi[] = {0xff, 0x00};
unsigned char lo[] = {0x7f, 0x00};
TEST_ASSERT_EQ(memcmp("abc", "abc", 3), 0);
TEST_ASSERT_EQ(memcmp("abc", "abd", 3) < 0, 1);
TEST_ASSERT_EQ(memcmp("abd", "abc", 3) > 0, 1);
TEST_ASSERT_EQ(memcmp("x", "y", 0), 0); /* n == 0 */
/* Bytes compare as unsigned char: 0xff > 0x7f. */
TEST_ASSERT_EQ(memcmp(hi, lo, 1) > 0, 1);
TEST_ASSERT_EQ(memcmp(lo, hi, 1) < 0, 1);
/* Does not stop at NUL bytes. */
TEST_ASSERT_EQ(memcmp("a\0b", "a\0c", 3) < 0, 1);
return 0;
}
static int
test_strsignal(void)
{
/* Known signals: pinned texts (vlibc's own table, x86_64 numbers). */
TEST_ASSERT_STREQ(strsignal(2), "Interrupt");
TEST_ASSERT_STREQ(strsignal(11), "Segmentation fault");
TEST_ASSERT_STREQ(strsignal(1), "Hangup");
TEST_ASSERT_STREQ(strsignal(9), "Killed");
TEST_ASSERT_STREQ(strsignal(31), "Bad system call");
/* Distinct strings for distinct known signals. */
TEST_ASSERT_TRUE(strsignal(2) != strsignal(11));
TEST_ASSERT_TRUE(strsignal(1) != strsignal(2));
/* Signal 0: non-null and distinct, text deliberately not pinned. */
TEST_ASSERT_TRUE(strsignal(0) != NULL);
TEST_ASSERT_TRUE(strsignal(0) != strsignal(1));
TEST_ASSERT_TRUE(strsignal(0) != strsignal(2));
/* Unknown numbers: formatted, non-null, distinct. */
TEST_ASSERT_STREQ(strsignal(999), "Unknown signal 999");
TEST_ASSERT_STREQ(strsignal(32), "Unknown signal 32");
TEST_ASSERT_TRUE(strsignal(-1) != NULL);
TEST_ASSERT_STREQ(strsignal(-1), "Unknown signal -1");
return 0;
}
static int
test_stpcpy_stpncpy(void)
{
char buf[8];
char *end;
end = stpcpy(buf, "abc");
TEST_ASSERT_EQ(end - buf, 3);
TEST_ASSERT_STREQ(buf, "abc");
TEST_ASSERT_EQ(*end, '\0');
end = stpcpy(buf, "");
TEST_ASSERT_EQ(end - buf, 0);
TEST_ASSERT_EQ(buf[0], '\0');
/* Short src: NUL-padded; return points at the first NUL. */
end = stpncpy(buf, "abc", 8);
TEST_ASSERT_EQ(end - buf, 3);
TEST_ASSERT_STREQ(buf, "abc");
/* Src >= n: no NUL written; return is dst + n. */
memset(buf, 'X', sizeof buf);
end = stpncpy(buf, "abcdefgh", 4);
TEST_ASSERT_EQ(end - buf, 4);
TEST_ASSERT_EQ(buf[0], 'a');
TEST_ASSERT_EQ(buf[3], 'd');
TEST_ASSERT_EQ(buf[4], 'X');
/* n == 0: return dst, nothing written. */
memset(buf, 'X', sizeof buf);
end = stpncpy(buf, "abc", 0);
TEST_ASSERT_EQ(end - buf, 0);
TEST_ASSERT_EQ(buf[0], 'X');
return 0;
}
static int
test_memccpy(void)
{
unsigned char buf[16];
void *p;
p = memccpy(buf, "hello world", ' ', 20);
TEST_ASSERT_EQ((char *)p - (char *)buf, 6); /* one past the ' ' */
TEST_ASSERT_EQ(buf[5], ' ');
/* c never appears within n: NULL, full copy made. */
p = memccpy(buf, "hello", 'z', 20);
TEST_ASSERT_NULL(p);
TEST_ASSERT_EQ(buf[4], 'o');
/* c exists but beyond n: NULL. */
p = memccpy(buf, "hello", 'o', 3);
TEST_ASSERT_NULL(p);
/* n == 0: NULL, nothing written. */
memset(buf, 'X', sizeof buf);
p = memccpy(buf, "hello", 'h', 0);
TEST_ASSERT_NULL(p);
TEST_ASSERT_EQ(buf[0], 'X');
return 0;
}
static int
test_strcoll_l(void)
{
char xf[16];
/* The locale argument is ignored ("C" locale behavior). */
TEST_ASSERT_EQ(strcoll_l("a", "b", NULL), strcoll("a", "b"));
TEST_ASSERT_EQ(strcoll_l("a", "b", NULL) < 0, 1);
TEST_ASSERT_EQ(strcoll_l("x", "x", NULL), 0);
TEST_ASSERT_EQ(strxfrm_l(xf, "hi", sizeof xf, NULL), strxfrm(xf, "hi", sizeof xf));
TEST_ASSERT_STREQ(xf, "hi");
return 0;
}
/* ---- register + runner ---- */
static const struct vlibc_test tests[] = {
{"strcpy", test_strcpy}, {"strncpy", test_strncpy},
{"strcat", test_strcat}, {"strncat", test_strncat},
{"strchr", test_strchr}, {"strrchr", test_strrchr},
{"strspn", test_strspn}, {"strcspn", test_strcspn},
{"strpbrk", test_strpbrk}, {"strstr", test_strstr},
{"strtok", test_strtok}, {"strtok_r", test_strtok_r},
{"strcoll", test_strcoll}, {"strxfrm", test_strxfrm},
{"strdup", test_strdup}, {"strndup", test_strndup},
{"strnlen", test_strnlen}, {"strncmp", test_strncmp},
{"memchr", test_memchr}, {"memcmp", test_memcmp},
{"strsignal", test_strsignal}, {"stpcpy/stpncpy", test_stpcpy_stpncpy},
{"memccpy", test_memccpy}, {"strcoll_l/strxfrm_l", test_strcoll_l},
};
/* -p mode: print a representative sample for eyeball diffing. */
static void
print_sample(void)
{
char buf[32];
char *out[8];
int n;
char *t;
vlibc_test_say(1, "strtok(a,,b, ,)= ");
strcpy(buf, "a,,b");
n = tok_collect(buf, ",", out, 8);
for (int i = 0; i < n; i++)
{
vlibc_test_say(1, "[");
vlibc_test_say(1, out[i]);
vlibc_test_say(1, "]");
}
vlibc_test_say(1, "\nstrxfrm(hello,n=4)=len ");
vlibc_test_say_dec(1, (unsigned long)strxfrm(buf, "hello", 4));
vlibc_test_say(1, " dst=\"");
vlibc_test_say(1, buf);
vlibc_test_say(1, "\"\nstrsignal(2)=\"");
vlibc_test_say(1, strsignal(2));
vlibc_test_say(1, "\" strsignal(0)=\"");
vlibc_test_say(1, strsignal(0));
vlibc_test_say(1, "\" strsignal(999)=\"");
vlibc_test_say(1, strsignal(999));
vlibc_test_say(1, "\"\n");
t = strdup("dup-ok");
vlibc_test_say(1, "strdup(dup-ok)=\"");
vlibc_test_say(1, t);
vlibc_test_say(1, "\"\n");
__libc_free(t);
}
/*
* Own main (not TEST_MAIN): supports the -p print mode. Everything else
* follows the TEST_MAIN contract — same output shape, 0 on all-pass.
*/
int
main(int argc, char **argv)
{
const size_t count = sizeof tests / sizeof tests[0];
size_t passed = 0;
size_t i;
if (argc > 1 && strcmp(argv[1], "-p") == 0)
{
print_sample();
}
for (i = 0; i < count; i++)
{
int before = vlibc_test_failures;
vlibc_test_say(1, "RUN ");
vlibc_test_say(1, tests[i].name);
vlibc_test_say(1, ": ");
if (tests[i].run() == 0 && vlibc_test_failures == before)
{
vlibc_test_say(1, "PASS\n");
passed++;
}
else
{
vlibc_test_say(1, "FAIL\n");
}
}
vlibc_test_say(1, "SUMMARY: ");
vlibc_test_say_dec(1, (unsigned long)passed);
vlibc_test_say(1, "/");
vlibc_test_say_dec(1, (unsigned long)count);
vlibc_test_say(1, " passed, ");
vlibc_test_say_dec(1, (unsigned long)vlibc_test_failures);
vlibc_test_say(1, " assertion failure(s)\n");
return passed == count ? 0 : 1;
}
+297
View File
@@ -0,0 +1,297 @@
/*
* vlibc — strings.h legacy functions test (todo 10).
*
* Covers the whole <strings.h> surface end to end:
*
* 1. strcasecmp: ASCII case-insensitive compare ("AbC" vs "aBc" == 0),
* ordering sign, empty strings, and high bytes (>= 0x80) compared
* unmodified — no locale folding;
* 2. strncasecmp: the n bound (differences beyond n are invisible),
* n == 0, and NUL-terminated early stop;
* 3. ffs/ffsl/ffsll: ffs(0) == 0, ffs(8) == 4, ffs(INT_MIN) == 32,
* ffsl(LONG_MIN) == 64, ffsll(LLONG_MIN) == 64, plus low-bit spots;
* 4. bcopy: (src, dst) argument order (reversed vs memcpy), and the
* discriminating overlap case dst == src + 1 — a forward-only copy
* would smear the source byte and produce "aaaaaa" instead of
* "aabcde";
* 5. bzero: zeroes the first n bytes and nothing beyond; n == 0 is a
* no-op;
* 6. bcmp: equal/differ, does not stop at NUL, unsigned byte order;
* 7. index/rindex: first/last occurrence, the NUL counts as a match,
* c converts through unsigned char, NULL when absent.
*
* With `-f`, only the plan's failure scenario runs: index("abc", 'z') and
* rindex("abc", 'z') must BOTH be NULL (character absent); a non-NULL
* result is the defect. Exits 0 when both are NULL as expected.
*
* No host libc headers are included (the -Iinclude path would shadow
* GCC's internal headers); all output goes through raw SYS_write via
* <vlibc/internal/test.h>. Not yet wired into make check (the build
* wiring todo owns that).
*
* The whole body mirrors the header's gate: every <strings.h> function is
* level 2, so at level 1 this TU compiles to a no-op runner (the test_ctype
* precedent). Compile the real test with -DVLIBC_LEVEL=2.
*
* Standalone build:
* gcc -Iinclude -DVLIBC_LEVEL=2 -std=c23 -Wall -Wextra -pedantic \
* -o /tmp/t10 tests/test_strings.c src/string/strings_impl.c \
* src/string/{memmove,memset,memcmp}.c
*/
#include <strings.h>
#include <limits.h>
#include <vlibc/internal/test.h>
#if VLIBC_LEVEL_GE(2)
/* ---- test functions (one per checked behavior) ---- */
static int
test_strcasecmp(void)
{
static const unsigned char hi[] = {0xe1, 0x00};
static const unsigned char lo[] = {0x61, 0x00};
TEST_ASSERT_EQ(strcasecmp("AbC", "aBc"), 0);
TEST_ASSERT_EQ(strcasecmp("", ""), 0);
TEST_ASSERT_EQ(strcasecmp("cafe", "CAFE"), 0);
TEST_ASSERT_TRUE(strcasecmp("abc", "abd") < 0);
TEST_ASSERT_TRUE(strcasecmp("abd", "abc") > 0);
TEST_ASSERT_TRUE(strcasecmp("", "a") < 0);
TEST_ASSERT_TRUE(strcasecmp("a", "") > 0);
TEST_ASSERT_TRUE(strcasecmp("Z", "a") > 0); /* folds 'Z' to 'z' */
TEST_ASSERT_TRUE(strcasecmp("A", "z") < 0);
/* Bytes >= 0x80 pass through unmodified and sort above ASCII. */
TEST_ASSERT_EQ(strcasecmp((const char *)hi, (const char *)hi), 0);
TEST_ASSERT_TRUE(strcasecmp((const char *)hi, (const char *)lo) > 0);
return 0;
}
static int
test_strncasecmp(void)
{
TEST_ASSERT_EQ(strncasecmp("AbCd", "aBcD", 4), 0);
TEST_ASSERT_EQ(strncasecmp("abcX", "abcY", 3), 0); /* diff beyond n */
TEST_ASSERT_TRUE(strncasecmp("abcX", "abcY", 4) < 0);
TEST_ASSERT_EQ(strncasecmp("x", "y", 0), 0); /* n == 0 */
TEST_ASSERT_EQ(strncasecmp("", "", 5), 0);
TEST_ASSERT_EQ(strncasecmp("a\0b", "a\0c", 3), 0); /* stops at NUL */
TEST_ASSERT_TRUE(strncasecmp("abc", "abcd", 4) < 0); /* shorter lhs */
TEST_ASSERT_TRUE(strncasecmp("abcd", "abc", 4) > 0);
TEST_ASSERT_EQ(strncasecmp("hello", "HELLO world", 5), 0);
return 0;
}
static int
test_ffs(void)
{
TEST_ASSERT_EQ(ffs(0), 0);
TEST_ASSERT_EQ(ffs(1), 1);
TEST_ASSERT_EQ(ffs(2), 2);
TEST_ASSERT_EQ(ffs(8), 4);
TEST_ASSERT_EQ(ffs(0x8000), 16);
TEST_ASSERT_EQ(ffs(-1), 1); /* all bits set: lowest bit is bit 0 */
TEST_ASSERT_EQ(ffs(INT_MIN), 32);
TEST_ASSERT_EQ(ffsl(0), 0);
TEST_ASSERT_EQ(ffsl(1L << 40), 41);
TEST_ASSERT_EQ(ffsl(LONG_MIN), 64); /* long is 64-bit on x86_64 */
TEST_ASSERT_EQ(ffsll(0), 0);
TEST_ASSERT_EQ(ffsll(0x100000000LL), 33);
TEST_ASSERT_EQ(ffsll(LLONG_MIN), 64);
return 0;
}
static int
test_bcopy(void)
{
char buf[16] = "abcdef";
char buf2[16] = "abcdef";
/* Non-overlapping copy; NOTE the (src, dst) argument order. */
bcopy(buf, buf2, 7);
TEST_ASSERT_STREQ(buf2, "abcdef");
/* Overlap, dst == src + 1: only a backward (memmove-style) copy can
* produce this; a forward copy smears src[0] over the whole range. */
bcopy(buf, buf + 1, 5);
TEST_ASSERT_STREQ(buf, "aabcde");
/* Overlap, dst inside src at +2: both directions agree, sanity only. */
{
char buf3[16] = "abcdef";
bcopy(buf3, buf3 + 2, 4);
TEST_ASSERT_STREQ(buf3, "ababcd");
}
/* Overlap, dst before src (backward region): forward copy is correct. */
{
char buf4[16] = "abcdef";
bcopy(buf4 + 2, buf4, 4);
TEST_ASSERT_STREQ(buf4, "cdefef");
}
return 0;
}
static int
test_bzero(void)
{
char buf[8];
int i;
for (i = 0; i < 8; i++)
{
buf[i] = 'A';
}
bzero(buf, 4);
TEST_ASSERT_EQ(buf[0], '\0');
TEST_ASSERT_EQ(buf[3], '\0');
TEST_ASSERT_EQ(buf[4], 'A'); /* untouched past n */
bzero(buf, 0); /* n == 0: nothing */
TEST_ASSERT_EQ(buf[0], '\0');
TEST_ASSERT_EQ(buf[4], 'A');
return 0;
}
static int
test_bcmp(void)
{
static const unsigned char hi[] = {0xff, 0x00};
static const unsigned char lo[] = {0x7f, 0x00};
TEST_ASSERT_EQ(bcmp("abc", "abc", 3), 0);
TEST_ASSERT_TRUE(bcmp("abc", "abd", 3) != 0);
TEST_ASSERT_EQ(bcmp("abc", "abd", 2), 0);
TEST_ASSERT_EQ(bcmp("", "", 0), 0);
/* Bytes compare as unsigned char; a NUL does not end the comparison. */
TEST_ASSERT_TRUE(bcmp(hi, lo, 1) != 0);
TEST_ASSERT_EQ(bcmp(hi, hi, 2), 0);
TEST_ASSERT_EQ(bcmp("a\0b", "a\0c", 3) != 0, 1);
return 0;
}
static int
test_index(void)
{
char s[] = "hello";
TEST_ASSERT_EQ(index(s, 'l') - s, 2);
TEST_ASSERT_EQ(index(s, 'h') - s, 0);
TEST_ASSERT_EQ(index(s, '\0') - s, 5); /* the NUL counts */
TEST_ASSERT_NULL(index(s, 'z'));
/* c converts through unsigned char: 0x100 is byte 0, the NUL. */
TEST_ASSERT_EQ(index(s, 0x100) - s, 5);
return 0;
}
static int
test_rindex(void)
{
char s[] = "hello";
TEST_ASSERT_EQ(rindex(s, 'l') - s, 3);
TEST_ASSERT_EQ(rindex(s, 'h') - s, 0);
TEST_ASSERT_EQ(rindex(s, '\0') - s, 5); /* the NUL counts */
TEST_ASSERT_NULL(rindex(s, 'z'));
return 0;
}
/* ---- failure mode: the plan's absent-character scenario ---- */
static int
test_absent_char(void)
{
TEST_ASSERT_NULL(index("abc", 'z'));
TEST_ASSERT_NULL(rindex("abc", 'z'));
return 0;
}
/* ---- register + runner ---- */
static const struct vlibc_test tests[] = {
{"strcasecmp", test_strcasecmp},
{"strncasecmp", test_strncasecmp},
{"ffs", test_ffs},
{"bcopy", test_bcopy},
{"bzero", test_bzero},
{"bcmp", test_bcmp},
{"index", test_index},
{"rindex", test_rindex},
};
/*
* Own main (not TEST_MAIN): supports the -f failure mode. Everything else
* follows the TEST_MAIN contract — same output shape, 0 on all-pass.
*/
int
main(int argc, char **argv)
{
const size_t count = sizeof tests / sizeof tests[0];
size_t passed = 0;
size_t i;
if (argc > 1 && argv[1][0] == '-' && argv[1][1] == 'f' && argv[1][2] == '\0')
{
int before = vlibc_test_failures;
vlibc_test_say(1, "RUN absent-char: ");
if (test_absent_char() == 0 && vlibc_test_failures == before)
{
vlibc_test_say(1, "PASS\n");
vlibc_test_say(1, "SUMMARY: 1/1 passed, 0 assertion failure(s)\n");
return 0;
}
vlibc_test_say(1, "FAIL\n");
return 1;
}
for (i = 0; i < count; i++)
{
int before = vlibc_test_failures;
vlibc_test_say(1, "RUN ");
vlibc_test_say(1, tests[i].name);
vlibc_test_say(1, ": ");
if (tests[i].run() == 0 && vlibc_test_failures == before)
{
vlibc_test_say(1, "PASS\n");
passed++;
}
else
{
vlibc_test_say(1, "FAIL\n");
}
}
vlibc_test_say(1, "SUMMARY: ");
vlibc_test_say_dec(1, (unsigned long)passed);
vlibc_test_say(1, "/");
vlibc_test_say_dec(1, (unsigned long)count);
vlibc_test_say(1, " passed, ");
vlibc_test_say_dec(1, (unsigned long)vlibc_test_failures);
vlibc_test_say(1, " assertion failure(s)\n");
return passed == count ? 0 : 1;
}
#else /* !VLIBC_LEVEL_GE(2) */
/*
* Level 1: every <strings.h> function is gated at level 2, so there is
* nothing to run. Keep the TU compilable at any configured profile.
*/
int
main(void)
{
vlibc_test_say(1, "SKIP: <strings.h> is level 2, not available here\n");
return 0;
}
#endif /* VLIBC_LEVEL_GE(2) */