Compare commits
15
Commits
c5a77c61fa
...
bcb4e259c3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bcb4e259c3
|
||
|
|
ce92aa2b6d
|
||
|
|
b2fbde0fc2
|
||
|
|
5eb3cbe302
|
||
|
|
06d2ec7c34
|
||
|
|
20f514d91c
|
||
|
|
858fc1762c
|
||
|
|
40a392f454
|
||
|
|
558cf8fd12
|
||
|
|
2480c63fbd
|
||
|
|
601e1fe960
|
||
|
|
e3017cfe56
|
||
|
|
b2d588885e
|
||
|
|
dec1017527
|
||
|
|
ce517ad0d6
|
+11
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
@@ -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
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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 */
|
||||||
@@ -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
@@ -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
@@ -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 */
|
||||||
@@ -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 */
|
||||||
@@ -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 */
|
||||||
@@ -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 */
|
||||||
@@ -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 */
|
||||||
@@ -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 */
|
||||||
@@ -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 */
|
||||||
@@ -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 */
|
||||||
@@ -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 */
|
||||||
@@ -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 */
|
||||||
@@ -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
@@ -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.
|
||||||
|
|||||||
@@ -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 */
|
||||||
@@ -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 */
|
||||||
@@ -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
@@ -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. */
|
||||||
|
|
||||||
|
|||||||
@@ -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 */
|
||||||
@@ -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 */
|
||||||
@@ -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 */
|
||||||
@@ -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 */
|
||||||
@@ -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 */
|
||||||
@@ -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 */
|
||||||
@@ -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 */
|
||||||
|
|||||||
@@ -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 */
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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) */
|
||||||
@@ -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
@@ -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 */
|
||||||
|
|||||||
@@ -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 */
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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)
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
@@ -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 */
|
||||||
@@ -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
@@ -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);
|
||||||
|
}
|
||||||
@@ -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) */
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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) */
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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) */
|
||||||
@@ -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++;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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) */
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
Executable
+95
@@ -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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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) */
|
||||||
Reference in New Issue
Block a user