commit aed06e272cc04f04a687906e11e9c1b8165d28df parent 994d0d3666007b7fe0e8e68732631f994f496c20 Author: Ryan Sepassi <rsepassi@gmail.com> Date: Mon, 4 May 2026 06:38:13 -0700 riscv64 and amd64 Diffstat:
27 files changed, 702 insertions(+), 138 deletions(-)
diff --git a/Makefile b/Makefile @@ -19,17 +19,20 @@ # make tcc-flat flatten upstream tcc.c into one TU # make tcc-boot2 cc.scm + P1pp pipeline → tcc-boot2 ELF # make tcc-gcc same flatten output, built with stock gcc -# (sanity check; ARCH=aarch64 only) +# (sanity check; ARCH in {aarch64, amd64}) # make tcc-tcc second-stage tcc: tcc-boot2 compiles # tcc.flat.c into a self-built tcc; the # tcc-cc / tcc-libc suites use this +# (ARCH in {aarch64, amd64, riscv64}) # make test every suite, every arch # make test SUITE=m1pp m1pp suite, every arch # make test SUITE=p1 ARCH=amd64 p1 suite, one arch # make test SUITE=scheme1 scheme1 .scm fixtures, every arch -# make test SUITE=tcc-cc tcc-boot2 compiles tests/cc, aarch64 -# make test SUITE=tcc-libc tcc-boot2 builds mes-libc and runs -# tests/cc-libc against it, aarch64 +# make test SUITE=tcc-cc tcc-tcc compiles + runs tests/cc +# (ARCH in {aarch64, amd64, riscv64}) +# make test SUITE=tcc-libc tcc-boot2 builds mes-libc, tcc-tcc +# compiles + links tests/cc-libc against it +# (ARCH in {aarch64, amd64, riscv64}) # make test SUITE=cc-ext vendored c-testsuite (broad coverage, # opt-in: not part of `make test`) # make image build the per-arch container image @@ -343,25 +346,23 @@ $(TCC_BOOT2_BINS): build/%/tcc-boot2/tcc-boot2: \ # musl's crt0 + libc. If tcc-gcc runs and tcc-boot2 doesn't, the bug # is downstream of the C source — i.e. in cc.scm or the P1 pipeline. # -# aarch64 only today; bring up other arches by adding a tcc-gcc/<arch>/ -# variant of start.S + sys_stubs.c. The cc.scm bug we're chasing is -# arch-agnostic, so one validation arch is enough. -TCC_GCC_ARCH := aarch64 -TCC_GCC_PLATFORM := $(PLATFORM_$(TCC_GCC_ARCH)) -TCC_GCC_BIN := build/$(TCC_GCC_ARCH)/tcc-gcc/tcc-gcc -TCC_GCC_IMAGE := build/$(TCC_GCC_ARCH)/.image-alpine-gcc -TCC_GCC_HARNESS := tcc-gcc/$(TCC_GCC_ARCH)/start.S tcc-gcc/$(TCC_GCC_ARCH)/sys_stubs.c +# aarch64 and amd64 are wired today; bring up another arch by adding +# a tcc-gcc/<arch>/ variant of start.S + sys_stubs.c. The cc.scm bug +# we're chasing is arch-agnostic, so one validation arch is enough. +TCC_GCC_BIN := build/$(ARCH)/tcc-gcc/tcc-gcc +TCC_GCC_IMAGE := build/$(ARCH)/.image-alpine-gcc +TCC_GCC_HARNESS := tcc-gcc/$(ARCH)/start.S tcc-gcc/$(ARCH)/sys_stubs.c tcc-gcc: $(TCC_GCC_BIN) -$(TCC_GCC_BIN): $(TCC_FLAT) build/$(TCC_GCC_ARCH)/vendor/mes-libc/libc.flat.c \ +$(TCC_GCC_BIN): $(TCC_FLAT) build/$(ARCH)/vendor/mes-libc/libc.flat.c \ $(TCC_GCC_HARNESS) scripts/build-tcc-gcc.sh $(TCC_GCC_IMAGE) mkdir -p $(@D) - podman run --rm --pull=never --platform $(TCC_GCC_PLATFORM) \ - -e ARCH=$(TCC_GCC_ARCH) \ - -v $(CURDIR):/work -w /work boot2-alpine-gcc:$(TCC_GCC_ARCH) \ + podman run --rm --pull=never --platform $(PLATFORM_$(ARCH)) \ + -e ARCH=$(ARCH) \ + -v $(CURDIR):/work -w /work boot2-alpine-gcc:$(ARCH) \ sh scripts/build-tcc-gcc.sh $@ $(TCC_FLAT) \ - build/$(TCC_GCC_ARCH)/vendor/mes-libc/libc.flat.c + build/$(ARCH)/vendor/mes-libc/libc.flat.c # --- tcc-cc test harness support ----------------------------------------- # @@ -371,18 +372,53 @@ $(TCC_GCC_BIN): $(TCC_FLAT) build/$(TCC_GCC_ARCH)/vendor/mes-libc/libc.flat.c \ # with each test fixture under the tcc-cc suite. HOST_CC ?= cc -TCC_CC_ARCH := aarch64 -TCC_CC_START := build/$(TCC_CC_ARCH)/tcc-cc/start.o -TCC_CC_MEM := build/$(TCC_CC_ARCH)/tcc-cc/mem.o -TCC_CC_TCC_INCLUDE := build/tcc/ARM64/tcc-0.9.26-1147-gee75a10c/include - -# tcc-libc suite supports: tcc-boot2 (built by cc.scm) compiles -# mes-libc into libc.o, then for each tests/cc-libc fixture, links -# fixture + start.o + sys_stubs.o + mem.o + libc.o into a runnable -# ELF. End-to-end exercise of "tcc as a real compiler" against the -# same libc the cc.scm + libc.P1pp pipeline uses. -TCC_LIBC_ARCH := aarch64 -TCC_LIBC_DIR := build/$(TCC_LIBC_ARCH)/tcc-libc + +# tcc-cc / tcc-libc / tcc-tcc share an arch with the building tcc-boot2 +# (the binary the suites drive). Three arches are wired today: aarch64, +# amd64, riscv64. ARCH selects which one. The cross-asm target string +# (or container) and the per-target tcc include path follow ARCH. +TCC_HARNESS_ARCHES := aarch64 amd64 riscv64 + +HOST_CC_TARGET_aarch64 := aarch64-linux-gnu +HOST_CC_TARGET_amd64 := x86_64-linux-gnu +HOST_CC_TARGET = $(HOST_CC_TARGET_$(ARCH)) + +# Cross-assembler for the per-arch .S harness inputs. aarch64 / amd64 +# go through host clang's `-target` (works out of the box on macOS). +# riscv64 routes through alpine-gcc:riscv64 because Apple's clang ships +# without a RISC-V backend; the routing is invisible at the call site — +# both forms consume (out, src) positionals. +ifeq ($(ARCH),riscv64) +TCC_ASM_DEPS := build/$(ARCH)/.image-alpine-gcc +TCC_ASM = $(call ALPINE_GCC,$(ARCH)) cc -c -o $(1) -x assembler $(2) +else +TCC_ASM_DEPS := +TCC_ASM = $(HOST_CC) -target $(HOST_CC_TARGET) -c -o $(1) -x assembler $(2) +endif + +TCC_CC_START := build/$(ARCH)/tcc-cc/start.o +TCC_CC_MEM := build/$(ARCH)/tcc-cc/mem.o +TCC_CC_TCC_INCLUDE = build/tcc/$(TCC_TARGET)/tcc-0.9.26-1147-gee75a10c/include +TCC_CC_TCC_LIBDIR = build/tcc/$(TCC_TARGET)/tcc-0.9.26-1147-gee75a10c/lib + +# x86_64-only: tcc emits calls to __va_start / __va_arg for variadic +# functions. Upstream tcc supplies these via lib/va_list.c → va_list.o +# (OBJ-x86_64 in lib/Makefile). The tcc-cc / tcc-libc suites link +# -nostdlib so we compile this object with tcc-boot2 and add it to +# every link. On other arches this variable is empty: aarch64 / riscv64 +# tcc lower va_arg without out-of-line helpers. +ifeq ($(ARCH),amd64) +TCC_CC_VA_LIST := build/$(ARCH)/tcc-cc/va_list.o +else +TCC_CC_VA_LIST := +endif + +# tcc-libc suite: tcc-boot2 (built by cc.scm) compiles mes-libc into +# libc.o, then for each tests/cc-libc fixture, links fixture + +# start.o + sys_stubs.o + mem.o + libc.o into a runnable ELF. +# End-to-end exercise of "tcc as a real compiler" against the same +# libc the cc.scm + libc.P1pp pipeline uses. +TCC_LIBC_DIR := build/$(ARCH)/tcc-libc TCC_LIBC_START := $(TCC_LIBC_DIR)/start.o TCC_LIBC_SYS_STUBS := $(TCC_LIBC_DIR)/sys_stubs.o TCC_LIBC_MEM := $(TCC_LIBC_DIR)/mem.o @@ -394,51 +430,63 @@ TCC_LIBC_LIBC := $(TCC_LIBC_DIR)/libc.o # fixtures through tcc-tcc, not tcc-boot2 — so a regression in # cc.scm's emitted code surfaces as a tcc-tcc misbehavior on a # fixture, and the test set spans tcc compiling itself. -TCC_TCC_ARCH := aarch64 -TCC_TCC_BIN := build/$(TCC_TCC_ARCH)/tcc-tcc/tcc-tcc +TCC_TCC_BIN := build/$(ARCH)/tcc-tcc/tcc-tcc -$(TCC_CC_START): tcc-cc/$(TCC_CC_ARCH)/start.S +$(TCC_CC_START): tcc-cc/$(ARCH)/start.S $(TCC_ASM_DEPS) mkdir -p $(@D) - $(HOST_CC) -target aarch64-linux-gnu -c -o $@ -x assembler $< + $(call TCC_ASM,$@,$<) # Tiny mem* runtime: tcc emits calls to memcpy/memmove/memset for # struct copies and bulk zero-init past its inline thresholds, but -# its ARM64 libtcc1 (lib-arm64.o) doesn't define them — upstream -# expects libc to. The tcc-cc suite links -nostdlib, so we compile -# this fallback with tcc-boot2 itself and link it alongside start.o. -# memcmp lives here too, as a plain compiler-builtin for fixtures -# that reach it via `extern int memcmp(...)`. +# its libtcc1 doesn't define them — upstream expects libc to. The +# tcc-cc suite links -nostdlib, so we compile this fallback with +# tcc-boot2 itself and link it alongside start.o. memcmp lives here +# too, as a plain compiler-builtin for fixtures that reach it via +# `extern int memcmp(...)`. $(TCC_CC_MEM): tcc-cc/mem.c \ - build/$(TCC_CC_ARCH)/tcc-boot2/tcc-boot2 \ - build/$(TCC_CC_ARCH)/.image + build/$(ARCH)/tcc-boot2/tcc-boot2 \ + build/$(ARCH)/.image mkdir -p $(@D) - $(call PODMAN,$(TCC_CC_ARCH)) \ - build/$(TCC_CC_ARCH)/tcc-boot2/tcc-boot2 \ + $(call PODMAN,$(ARCH)) \ + build/$(ARCH)/tcc-boot2/tcc-boot2 \ -nostdlib -I $(TCC_CC_TCC_INCLUDE) -c -o $@ $< +# x86_64 va_list runtime: defines __va_start / __va_arg, the intrinsics +# tcc's x86_64 codegen lowers `va_start` / `va_arg` to. The Makefile +# rule is gated on amd64 via TCC_CC_VA_LIST being non-empty above. +build/amd64/tcc-cc/va_list.o: \ + build/amd64/tcc-boot2/tcc-boot2 \ + build/amd64/.image $(TCC_FLAT) + mkdir -p $(@D) + $(call PODMAN,amd64) \ + build/amd64/tcc-boot2/tcc-boot2 \ + -nostdlib -I build/tcc/X86_64/tcc-0.9.26-1147-gee75a10c/include \ + -D TCC_TARGET_X86_64=1 \ + -c -o $@ build/tcc/X86_64/tcc-0.9.26-1147-gee75a10c/lib/va_list.c + # --- tcc-libc test harness inputs ---------------------------------------- # # start.o threads __libc_init in front of main and exits with main's # return value. sys_stubs.o implements the libp1pp-shaped sys_* -# wrappers via raw aarch64 svc; both are produced by the host -# cross-toolchain (no asm support in tcc-boot2's aarch64 codegen). -$(TCC_LIBC_START): tcc-libc/$(TCC_LIBC_ARCH)/start.S +# wrappers via raw syscalls; both are produced by the host +# cross-toolchain (no asm support in tcc-boot2's codegen). +$(TCC_LIBC_START): tcc-libc/$(ARCH)/start.S $(TCC_ASM_DEPS) mkdir -p $(@D) - $(HOST_CC) -target aarch64-linux-gnu -c -o $@ -x assembler $< + $(call TCC_ASM,$@,$<) -$(TCC_LIBC_SYS_STUBS): tcc-libc/$(TCC_LIBC_ARCH)/sys_stubs.S +$(TCC_LIBC_SYS_STUBS): tcc-libc/$(ARCH)/sys_stubs.S $(TCC_ASM_DEPS) mkdir -p $(@D) - $(HOST_CC) -target aarch64-linux-gnu -c -o $@ -x assembler $< + $(call TCC_ASM,$@,$<) # tcc-libc reuses tcc-cc/mem.c for the compiler-builtin mem* runtime, -# but rebuilds it under build/$(TCC_LIBC_ARCH)/tcc-libc/ to keep the -# suite's outputs cleanly separated from the tcc-cc tree. +# but rebuilds it under build/$(ARCH)/tcc-libc/ to keep the suite's +# outputs cleanly separated from the tcc-cc tree. $(TCC_LIBC_MEM): tcc-cc/mem.c \ - build/$(TCC_LIBC_ARCH)/tcc-boot2/tcc-boot2 \ - build/$(TCC_LIBC_ARCH)/.image + build/$(ARCH)/tcc-boot2/tcc-boot2 \ + build/$(ARCH)/.image mkdir -p $(@D) - $(call PODMAN,$(TCC_LIBC_ARCH)) \ - build/$(TCC_LIBC_ARCH)/tcc-boot2/tcc-boot2 \ + $(call PODMAN,$(ARCH)) \ + build/$(ARCH)/tcc-boot2/tcc-boot2 \ -nostdlib -I $(TCC_CC_TCC_INCLUDE) -c -o $@ $< # libc.o: tcc-boot2 compiles the same flatten output cc.scm consumes. @@ -446,13 +494,13 @@ $(TCC_LIBC_MEM): tcc-cc/mem.c \ # names onto tcc's native va_* macros (tcc has no notion of a # __builtin_va_list keyword); the shim is the only piece glueing # the host-preprocessed flatten to tcc-boot2's frontend. -$(TCC_LIBC_LIBC): build/$(TCC_LIBC_ARCH)/vendor/mes-libc/libc.flat.c \ +$(TCC_LIBC_LIBC): build/$(ARCH)/vendor/mes-libc/libc.flat.c \ tcc-libc/va_list_shim.h \ - build/$(TCC_LIBC_ARCH)/tcc-boot2/tcc-boot2 \ - build/$(TCC_LIBC_ARCH)/.image + build/$(ARCH)/tcc-boot2/tcc-boot2 \ + build/$(ARCH)/.image mkdir -p $(@D) - $(call PODMAN,$(TCC_LIBC_ARCH)) \ - build/$(TCC_LIBC_ARCH)/tcc-boot2/tcc-boot2 \ + $(call PODMAN,$(ARCH)) \ + build/$(ARCH)/tcc-boot2/tcc-boot2 \ -nostdlib -I $(TCC_CC_TCC_INCLUDE) \ -include tcc-libc/va_list_shim.h \ -c -o $@ $< @@ -460,19 +508,19 @@ $(TCC_LIBC_LIBC): build/$(TCC_LIBC_ARCH)/vendor/mes-libc/libc.flat.c \ # --- tcc-tcc: second-stage tcc ------------------------------------------- # # Build inputs come straight from the tcc-libc setup: same start / -# sys_stubs / mem / libc objects. lib-arm64.o (TFmode soft-float -# helpers) is materialized inside the build script alongside the link -# step, since nothing else needs it. +# sys_stubs / mem / libc objects. The aarch64 build also pulls in +# lib-arm64.o (TFmode soft-float helpers) — that's materialized +# inside the build script alongside the link step. tcc-tcc: $(TCC_TCC_BIN) $(TCC_TCC_BIN): scripts/boot-build-tcc-tcc.sh \ $(TCC_FLAT) tcc-libc/va_list_shim.h \ - build/$(TCC_TCC_ARCH)/tcc-boot2/tcc-boot2 \ + build/$(ARCH)/tcc-boot2/tcc-boot2 \ $(TCC_LIBC_START) $(TCC_LIBC_SYS_STUBS) \ $(TCC_LIBC_MEM) $(TCC_LIBC_LIBC) \ - build/$(TCC_TCC_ARCH)/.image + build/$(ARCH)/.image mkdir -p $(@D) - $(call PODMAN,$(TCC_TCC_ARCH)) \ + $(call PODMAN,$(ARCH)) \ sh scripts/boot-build-tcc-tcc.sh $@ # --- Native tools (opt-in dev-loop helpers) ------------------------------- @@ -560,12 +608,13 @@ TEST_CC_LIBC_DEPS := $(TEST_CC_DEPS) \ $(foreach a,$(TEST_ARCHES),build/$(a)/vendor/mes-libc/libc.P1pp) \ P1/entry-libc.P1pp P1/elf-end.P1pp -TEST_TCC_CC_DEPS := build/$(TCC_CC_ARCH)/.image \ - $(TCC_TCC_BIN) $(TCC_CC_START) $(TCC_CC_MEM) +TEST_TCC_CC_DEPS := build/$(ARCH)/.image \ + $(TCC_TCC_BIN) $(TCC_CC_START) $(TCC_CC_MEM) $(TCC_CC_VA_LIST) -TEST_TCC_LIBC_DEPS := build/$(TCC_LIBC_ARCH)/.image \ +TEST_TCC_LIBC_DEPS := build/$(ARCH)/.image \ $(TCC_TCC_BIN) \ - $(TCC_LIBC_START) $(TCC_LIBC_SYS_STUBS) $(TCC_LIBC_MEM) $(TCC_LIBC_LIBC) + $(TCC_LIBC_START) $(TCC_LIBC_SYS_STUBS) $(TCC_LIBC_MEM) $(TCC_LIBC_LIBC) \ + $(TCC_CC_VA_LIST) test: ifeq ($(SUITE),) @@ -600,17 +649,17 @@ else ifeq ($(SUITE),cc-ext) @$(MAKE) --no-print-directory $(TEST_CC_LIBC_DEPS) sh scripts/run-tests.sh --suite=cc-ext $(if $(ARCH_FILTER),--arch=$(ARCH_FILTER)) $(NAMES) else ifeq ($(SUITE),tcc-cc) - @if [ -n "$(ARCH_FILTER)" ] && [ "$(ARCH_FILTER)" != "$(TCC_CC_ARCH)" ]; then \ - echo "tcc-cc currently supports ARCH=$(TCC_CC_ARCH) only" >&2; exit 2; \ + @if [ -z "$(filter $(ARCH),$(TCC_HARNESS_ARCHES))" ]; then \ + echo "tcc-cc supports ARCH in {$(TCC_HARNESS_ARCHES)} only (got '$(ARCH)')" >&2; exit 2; \ fi - @$(MAKE) --no-print-directory ARCH=$(TCC_CC_ARCH) $(TEST_TCC_CC_DEPS) - sh scripts/run-tests.sh --suite=tcc-cc --arch=$(TCC_CC_ARCH) $(NAMES) + @$(MAKE) --no-print-directory ARCH=$(ARCH) $(TEST_TCC_CC_DEPS) + sh scripts/run-tests.sh --suite=tcc-cc --arch=$(ARCH) $(NAMES) else ifeq ($(SUITE),tcc-libc) - @if [ -n "$(ARCH_FILTER)" ] && [ "$(ARCH_FILTER)" != "$(TCC_LIBC_ARCH)" ]; then \ - echo "tcc-libc currently supports ARCH=$(TCC_LIBC_ARCH) only" >&2; exit 2; \ + @if [ -z "$(filter $(ARCH),$(TCC_HARNESS_ARCHES))" ]; then \ + echo "tcc-libc supports ARCH in {$(TCC_HARNESS_ARCHES)} only (got '$(ARCH)')" >&2; exit 2; \ fi - @$(MAKE) --no-print-directory ARCH=$(TCC_LIBC_ARCH) $(TEST_TCC_LIBC_DEPS) - sh scripts/run-tests.sh --suite=tcc-libc --arch=$(TCC_LIBC_ARCH) $(NAMES) + @$(MAKE) --no-print-directory ARCH=$(ARCH) $(TEST_TCC_LIBC_DEPS) + sh scripts/run-tests.sh --suite=tcc-libc --arch=$(ARCH) $(NAMES) else @echo "unknown SUITE='$(SUITE)' (m1pp | p1 | scheme1 | cc-util | cc-lex | cc-pp | cc-cg | cc | cc-libc | cc-ext | tcc-cc | tcc-libc)" >&2; exit 2 endif diff --git a/scripts/boot-build-tcc-tcc.sh b/scripts/boot-build-tcc-tcc.sh @@ -3,14 +3,26 @@ ## ## tcc-boot2 (the cc.scm-built tcc) compiles tcc.flat.c into a fresh ## tcc binary, linked against the same libc.o / mem.o / sys_stubs.o / -## start.o the tcc-libc suite uses, plus tcc's own lib-arm64.c -## soft-float TFmode helpers (libgcc-equivalent on aarch64). The result -## — tcc-tcc — is the "twice-compiled" tcc: stage 1 was cc.scm -## compiling tcc; stage 2 is tcc compiling tcc. Both are bit-distinct -## from each other but functionally equivalent; the tcc-cc / tcc-libc -## suites use tcc-tcc as their reference compiler. +## start.o the tcc-libc suite uses, plus per-target libtcc1 helpers: +## - aarch64 / riscv64: lib-arm64.c (soft-float TFmode helpers — +## __addtf3 / __extenddftf2 / …; libgcc-equivalent — same source +## on both arches, gated internally by __riscv to skip the +## __arm64_clear_cache wrapper; upstream tcc names the .o +## lib-arm64.o for both via RISCV64_O = lib-arm64.o in +## lib/Makefile). +## - amd64: va_list.c (defines __va_start / __va_arg, the +## intrinsics tcc's x86_64 codegen calls for variadic functions +## — see tcc/include/stdarg.h on x86_64 and OBJ-x86_64 in +## lib/Makefile). Long double on amd64 is x87 80-bit and tcc +## emits native FPU instructions, so no soft-float helper is +## needed. +## The result — tcc-tcc — is the +## "twice-compiled" tcc: stage 1 was cc.scm compiling tcc; stage 2 is +## tcc compiling tcc. Both are bit-distinct from each other but +## functionally equivalent; the tcc-cc / tcc-libc suites use tcc-tcc +## as their reference compiler. ## -## Env: ARCH=aarch64 (only arch wired today) +## Env: ARCH in {aarch64, amd64, riscv64} ## Usage: boot-build-tcc-tcc.sh <out> set -eu @@ -19,10 +31,16 @@ set -eu OUT=$1 +case "$ARCH" in + aarch64) TCC_TARGET=ARM64; LIB_TARGET_DEFINES="-D TCC_TARGET_ARM64=1 -D TCC_TARGET_ARM=1" ;; + amd64) TCC_TARGET=X86_64; LIB_TARGET_DEFINES= ;; + riscv64) TCC_TARGET=RISCV64; LIB_TARGET_DEFINES="-D TCC_TARGET_RISCV64=1" ;; + *) echo "boot-build-tcc-tcc.sh: unsupported ARCH '$ARCH'" >&2; exit 2 ;; +esac + TCC_BOOT2=build/$ARCH/tcc-boot2/tcc-boot2 -TCC_INC=build/tcc/ARM64/tcc-0.9.26-1147-gee75a10c/include -LIBARM64_C=build/tcc/ARM64/tcc-0.9.26-1147-gee75a10c/lib/lib-arm64.c -TCC_FLAT=build/tcc/ARM64/tcc.flat.c +TCC_INC=build/tcc/$TCC_TARGET/tcc-0.9.26-1147-gee75a10c/include +TCC_FLAT=build/tcc/$TCC_TARGET/tcc.flat.c LIBC_O=build/$ARCH/tcc-libc/libc.o MEM_O=build/$ARCH/tcc-libc/mem.o SYS_O=build/$ARCH/tcc-libc/sys_stubs.o @@ -32,14 +50,34 @@ WORK=$(dirname "$OUT") mkdir -p "$WORK" -# lib-arm64.o: TFmode soft-float helpers (__addtf3 / __extenddftf2 / …). -# tcc.flat.c references these for long double arithmetic; without them -# the final link fails with undefined symbols. -"$TCC_BOOT2" -nostdlib -I "$TCC_INC" \ - -D HAVE_CONFIG_H=1 -D TCC_TARGET_ARM64=1 -D TCC_TARGET_ARM=1 \ - -c -o "$WORK/lib-arm64.o" "$LIBARM64_C" +TCC_LIB_DIR=build/tcc/$TCC_TARGET/tcc-0.9.26-1147-gee75a10c/lib + +LIB_OBJS= +if [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "riscv64" ]; then + # lib-arm64.o: TFmode soft-float helpers (__addtf3 / __extenddftf2 / + # …). tcc.flat.c references these for long double arithmetic; + # without them the final link fails with undefined symbols. Upstream + # tcc reuses lib-arm64.c for riscv64 too (RISCV64_O = lib-arm64.o + # in lib/Makefile); the file gates the arm64-specific cache-flush + # wrapper on !__riscv. + # shellcheck disable=SC2086 # LIB_TARGET_DEFINES is intentionally word-split. + "$TCC_BOOT2" -nostdlib -I "$TCC_INC" \ + -D HAVE_CONFIG_H=1 $LIB_TARGET_DEFINES \ + -c -o "$WORK/lib-arm64.o" "$TCC_LIB_DIR/lib-arm64.c" + LIB_OBJS=$WORK/lib-arm64.o +elif [ "$ARCH" = "amd64" ]; then + # va_list.o: defines __va_start / __va_arg. tcc's x86_64 codegen + # lowers va_start / va_arg to direct calls into these intrinsics + # (see tcc/include/stdarg.h, lib/va_list.c). Without them the + # tcc-tcc link fails with undefined symbols. + "$TCC_BOOT2" -nostdlib -I "$TCC_INC" \ + -D TCC_TARGET_X86_64=1 \ + -c -o "$WORK/va_list.o" "$TCC_LIB_DIR/va_list.c" + LIB_OBJS=$WORK/va_list.o +fi # Compile + link tcc-tcc in one tcc-boot2 invocation. +# shellcheck disable=SC2086 # $LIB_OBJS is intentionally word-split (may be empty). "$TCC_BOOT2" -nostdlib -I "$TCC_INC" -include "$SHIM" \ - "$START_O" "$SYS_O" "$MEM_O" "$LIBC_O" "$WORK/lib-arm64.o" \ + "$START_O" "$SYS_O" "$MEM_O" "$LIBC_O" $LIB_OBJS \ "$TCC_FLAT" -o "$OUT" diff --git a/scripts/boot-run-tests.sh b/scripts/boot-run-tests.sh @@ -600,16 +600,28 @@ run_cc_ext_suite() { ## Makefile target tcc-tcc supplies the binary; start.o / mem.o come ## from the tcc-cc tree (cross-asm and tcc-boot2-built respectively). run_tcc_cc_suite() { - if [ "$ARCH" != "aarch64" ]; then - echo " FAIL [$ARCH] tcc-cc" - echo " tcc-cc currently supports ARCH=aarch64 only" >&2 - return - fi + case "$ARCH" in + aarch64) tcc_target=ARM64; tcc_banner='AArch64' ;; + amd64) tcc_target=X86_64; tcc_banner='x86_64' ;; + riscv64) tcc_target=RISCV64; tcc_banner='riscv64' ;; + *) + echo " FAIL [$ARCH] tcc-cc" + echo " tcc-cc supports ARCH in {aarch64, amd64, riscv64} only" >&2 + return + ;; + esac tcc=build/$ARCH/tcc-tcc/tcc-tcc start=build/$ARCH/tcc-cc/start.o mem=build/$ARCH/tcc-cc/mem.o - tcc_include=build/tcc/ARM64/tcc-0.9.26-1147-gee75a10c/include + tcc_include=build/tcc/$tcc_target/tcc-0.9.26-1147-gee75a10c/include + # x86_64 only: __va_start / __va_arg intrinsics for variadic + # functions. Other arches lower va_arg without out-of-line helpers. + if [ "$ARCH" = "amd64" ]; then + va_list=build/$ARCH/tcc-cc/va_list.o + else + va_list= + fi if [ ! -x "$tcc" ]; then echo " FAIL [$ARCH] tcc-cc" echo " missing $tcc -- run 'make test SUITE=tcc-cc ARCH=$ARCH'" >&2 @@ -625,9 +637,14 @@ run_tcc_cc_suite() { echo " missing $mem -- run 'make test SUITE=tcc-cc ARCH=$ARCH'" >&2 return fi - if ! "$tcc" -version 2>/dev/null | grep 'AArch64' >/dev/null; then + if [ -n "$va_list" ] && [ ! -e "$va_list" ]; then echo " FAIL [$ARCH] tcc-cc" - echo " $tcc is not an AArch64-targeted tcc; rebuild with TCC_TARGET=ARM64" >&2 + echo " missing $va_list -- run 'make test SUITE=tcc-cc ARCH=$ARCH'" >&2 + return + fi + if ! "$tcc" -version 2>/dev/null | grep "$tcc_banner" >/dev/null; then + echo " FAIL [$ARCH] tcc-cc" + echo " $tcc is not a $tcc_banner-targeted tcc; rebuild with TCC_TARGET=$tcc_target" >&2 return fi @@ -652,7 +669,9 @@ run_tcc_cc_suite() { mkdir -p "$(dirname "$elf")" "$workdir" tcc_log=$workdir/tcc.log - if ! "$tcc" -nostdlib -I "$tcc_include" "$start" "$mem" "$src" -o "$elf" \ + # shellcheck disable=SC2086 # $va_list is intentionally word-split (may be empty). + if ! "$tcc" -nostdlib -I "$tcc_include" \ + "$start" "$mem" $va_list "$src" -o "$elf" \ >"$tcc_log" 2>&1; then fail "$label" "tcc compile/link failed:" "$tcc_log" continue @@ -675,24 +694,36 @@ run_tcc_cc_suite() { ## twice-compiled tcc (cc.scm built tcc-boot2; tcc-boot2 built tcc-tcc). ## tcc-boot2 already compiled mes-libc into libc.o; for each tests/cc-libc ## fixture, tcc-tcc compiles + links the fixture against -## start.o aarch64 entry stub: __libc_init then main then exit -## sys_stubs.o Linux aarch64 svc-based sys_* implementations +## start.o per-arch entry stub: __libc_init then main then exit +## sys_stubs.o per-arch raw-syscall sys_* implementations ## mem.o mem* compiler-builtin runtime (memcpy/memmove/memset/memcmp) ## libc.o tcc-boot2-built mes-libc -## and runs the resulting ELF natively in the aarch64 container. +## and runs the resulting ELF natively in the per-arch container. run_tcc_libc_suite() { - if [ "$ARCH" != "aarch64" ]; then - echo " FAIL [$ARCH] tcc-libc" - echo " tcc-libc currently supports ARCH=aarch64 only" >&2 - return - fi + case "$ARCH" in + aarch64) tcc_target=ARM64; tcc_banner='AArch64' ;; + amd64) tcc_target=X86_64; tcc_banner='x86_64' ;; + riscv64) tcc_target=RISCV64; tcc_banner='riscv64' ;; + *) + echo " FAIL [$ARCH] tcc-libc" + echo " tcc-libc supports ARCH in {aarch64, amd64, riscv64} only" >&2 + return + ;; + esac tcc=build/$ARCH/tcc-tcc/tcc-tcc start=build/$ARCH/tcc-libc/start.o sys_stubs=build/$ARCH/tcc-libc/sys_stubs.o mem=build/$ARCH/tcc-libc/mem.o libc=build/$ARCH/tcc-libc/libc.o - tcc_include=build/tcc/ARM64/tcc-0.9.26-1147-gee75a10c/include + tcc_include=build/tcc/$tcc_target/tcc-0.9.26-1147-gee75a10c/include + # x86_64 only: __va_start / __va_arg intrinsics for variadic + # functions. mes-libc's printf family hits this directly. + if [ "$ARCH" = "amd64" ]; then + va_list=build/$ARCH/tcc-cc/va_list.o + else + va_list= + fi for f in "$tcc" "$start" "$sys_stubs" "$mem" "$libc"; do if [ ! -e "$f" ]; then echo " FAIL [$ARCH] tcc-libc" @@ -700,9 +731,14 @@ run_tcc_libc_suite() { return fi done - if ! "$tcc" -version 2>/dev/null | grep 'AArch64' >/dev/null; then + if [ -n "$va_list" ] && [ ! -e "$va_list" ]; then + echo " FAIL [$ARCH] tcc-libc" + echo " missing $va_list -- run 'make test SUITE=tcc-libc ARCH=$ARCH'" >&2 + return + fi + if ! "$tcc" -version 2>/dev/null | grep "$tcc_banner" >/dev/null; then echo " FAIL [$ARCH] tcc-libc" - echo " $tcc is not an AArch64-targeted tcc; rebuild with TCC_TARGET=ARM64" >&2 + echo " $tcc is not a $tcc_banner-targeted tcc; rebuild with TCC_TARGET=$tcc_target" >&2 return fi @@ -727,8 +763,9 @@ run_tcc_libc_suite() { mkdir -p "$(dirname "$elf")" "$workdir" tcc_log=$workdir/tcc.log + # shellcheck disable=SC2086 # $va_list is intentionally word-split (may be empty). if ! "$tcc" -nostdlib -I "$tcc_include" \ - "$start" "$sys_stubs" "$mem" "$libc" "$src" -o "$elf" \ + "$start" "$sys_stubs" "$mem" "$libc" $va_list "$src" -o "$elf" \ >"$tcc_log" 2>&1; then fail "$label" "tcc compile/link failed:" "$tcc_log" continue diff --git a/scripts/libc-flatten.sh b/scripts/libc-flatten.sh @@ -125,6 +125,24 @@ apply_simple_patch \ "$STAGE/mes/ntoab.c" \ "$PATCHES/ntoab-inline-defined.before" \ "$PATCHES/ntoab-inline-defined.after" +# stdio/{printf,sprintf,snprintf}.c carry a mes-mescc-specific +# `ap += (__FOO_VARARGS + ...)` block guarded by `__GNUC__ && __x86_64__`. +# That arithmetic is meaningful only inside mes's compiler; under stock +# gcc preprocessing for amd64 it expands to a reference to an undefined +# `__FOO_VARARGS` and breaks cc.scm. Strip the block — the va_start that +# follows handles varargs correctly under any standard C compiler. +apply_simple_patch \ + "$STAGE/stdio/printf.c" \ + "$PATCHES/printf-mes-varargs.before" \ + "$PATCHES/printf-mes-varargs.after" +apply_simple_patch \ + "$STAGE/stdio/sprintf.c" \ + "$PATCHES/sprintf-mes-varargs.before" \ + "$PATCHES/sprintf-mes-varargs.after" +apply_simple_patch \ + "$STAGE/stdio/snprintf.c" \ + "$PATCHES/snprintf-mes-varargs.before" \ + "$PATCHES/snprintf-mes-varargs.after" # --- (3) flatten via host preprocessor -------------------------------- HOST_CC=${HOST_CC:-cc} diff --git a/scripts/run-gcc-libc-flat-tcc.sh b/scripts/run-gcc-libc-flat-tcc.sh @@ -1,20 +1,44 @@ #!/bin/sh +## run-gcc-libc-flat-tcc.sh — tcc-gcc baseline runner. +## +## Builds the tcc.flat.c-built tcc-gcc against mes-libc's mem* sources +## into a runtime archive, then walks tests/cc/<name>.c through tcc-gcc +## linking against that archive. The control reference for the +## tcc-cc/tcc-libc suites — if a fixture passes here but fails through +## cc.scm + tcc-boot2 / tcc-tcc, the bug lives in our pipeline rather +## than in tcc-0.9.26 itself. +## +## Env: ARCH=aarch64 (default) | amd64 +## TCC=<path> overrides the per-arch tcc-gcc binary +## Usage: scripts/run-gcc-libc-flat-tcc.sh [<test-name>...] + set -eu ROOT=$(cd "$(dirname "$0")/.." && pwd) cd "$ROOT" -TCC=${TCC:-build/aarch64/tcc-gcc/tcc-gcc} -START=build/aarch64/tcc-cc/start.o -OUT_ROOT=build/aarch64/tests/gcc-libc-flat-tcc -WORK_ROOT=build/aarch64/.work/tests/gcc-libc-flat-tcc -TCC_SRC=build/tcc/ARM64/tcc-0.9.26-1147-gee75a10c +ARCH=${ARCH:-aarch64} + +case "$ARCH" in + aarch64) TCC_TARGET=ARM64; RUNTIME_TARGET_DEFINES="-D TCC_TARGET_ARM64=1 -D TCC_TARGET_ARM=1"; LIB_HELPER_SRC=lib/lib-arm64.c; LIB_HELPER_NAME=lib-arm64.o ;; + amd64) TCC_TARGET=X86_64; RUNTIME_TARGET_DEFINES="-D TCC_TARGET_X86_64=1"; LIB_HELPER_SRC=; LIB_HELPER_NAME= ;; + *) echo "$0: unsupported ARCH '$ARCH' (aarch64|amd64)" >&2; exit 2 ;; +esac + +TCC=${TCC:-build/$ARCH/tcc-gcc/tcc-gcc} +START=build/$ARCH/tcc-cc/start.o +OUT_ROOT=build/$ARCH/tests/gcc-libc-flat-tcc +WORK_ROOT=build/$ARCH/.work/tests/gcc-libc-flat-tcc +TCC_SRC=build/tcc/$TCC_TARGET/tcc-0.9.26-1147-gee75a10c MES_INC=vendor/mes-libc/include -MES_LINUX_INC=vendor/mes-libc/include/linux/riscv64 +case "$ARCH" in + aarch64) MES_LINUX_INC=vendor/mes-libc/include/linux/riscv64 ;; + amd64) MES_LINUX_INC=vendor/mes-libc/include/linux/x86_64 ;; +esac RUNTIME=$WORK_ROOT/runtime.a [ -x "$TCC" ] || { - echo "missing $TCC; build it with scripts/build-tcc-gcc.sh and build/tcc/ARM64/tcc.flat.c" >&2 + echo "missing $TCC; build it with scripts/build-tcc-gcc.sh and build/tcc/$TCC_TARGET/tcc.flat.c" >&2 exit 2 } [ -r "$START" ] || { echo "missing $START" >&2; exit 2; } @@ -27,14 +51,18 @@ build_runtime() { rm -rf "$WORK_ROOT/runtime-objs" mkdir -p "$WORK_ROOT/runtime-objs" + # shellcheck disable=SC2086 # RUNTIME_TARGET_DEFINES is intentionally word-split. "$TCC" -c -D HAVE_CONFIG_H=1 -D HAVE_LONG_LONG=1 -D HAVE_FLOAT=1 \ - -D TCC_TARGET_ARM64=1 -D TCC_TARGET_ARM=1 \ + $RUNTIME_TARGET_DEFINES \ -I "$TCC_SRC" -I "$TCC_SRC/include" \ -o "$WORK_ROOT/runtime-objs/libtcc1.o" "$TCC_SRC/lib/libtcc1.c" - "$TCC" -c -D HAVE_CONFIG_H=1 -D HAVE_LONG_LONG=1 -D HAVE_FLOAT=1 \ - -D TCC_TARGET_ARM64=1 -D TCC_TARGET_ARM=1 \ - -I "$TCC_SRC" -I "$TCC_SRC/include" \ - -o "$WORK_ROOT/runtime-objs/lib-arm64.o" "$TCC_SRC/lib/lib-arm64.c" + if [ -n "$LIB_HELPER_SRC" ]; then + # shellcheck disable=SC2086 # RUNTIME_TARGET_DEFINES is intentionally word-split. + "$TCC" -c -D HAVE_CONFIG_H=1 -D HAVE_LONG_LONG=1 -D HAVE_FLOAT=1 \ + $RUNTIME_TARGET_DEFINES \ + -I "$TCC_SRC" -I "$TCC_SRC/include" \ + -o "$WORK_ROOT/runtime-objs/$LIB_HELPER_NAME" "$TCC_SRC/$LIB_HELPER_SRC" + fi for src in string/memcpy.c string/memmove.c string/memset.c string/memcmp.c; do obj=$WORK_ROOT/runtime-objs/$(basename "$src" .c).o diff --git a/scripts/simple-patches/tcc-0.9.26/riscv-stdarg-fix.after b/scripts/simple-patches/tcc-0.9.26/riscv-stdarg-fix.after @@ -0,0 +1,10 @@ +#elif __riscv +/* Upstream order is reversed — `typedef __builtin_va_list va_list;` + * comes before `typedef char *__builtin_va_list;`, which makes the + * first typedef reference an undeclared name. Stock tcc papers over + * this with a built-in `__builtin_va_list` keyword; tcc-boot2's + * frontend has no such keyword (it treats it as a plain typedef + * tag), so the upstream order parse-fails. Swap the two so the base + * typedef is in scope by the time va_list claims it. */ +typedef char *__builtin_va_list; +typedef __builtin_va_list va_list; diff --git a/scripts/simple-patches/tcc-0.9.26/riscv-stdarg-fix.before b/scripts/simple-patches/tcc-0.9.26/riscv-stdarg-fix.before @@ -0,0 +1,3 @@ +#elif __riscv +typedef __builtin_va_list va_list; +typedef char *__builtin_va_list; diff --git a/scripts/simple-patches/tcc-0.9.26/va_list-no-abort.after b/scripts/simple-patches/tcc-0.9.26/va_list-no-abort.after @@ -0,0 +1,7 @@ +/* Avoid include files, they may not be available when cross compiling */ +extern void *memset(void *s, int c, __SIZE_TYPE__ n); +/* boot2: replace abort() with an inline spin so we don't pull libc + into the link. The default case below is unreachable anyway — + tcc's x86_64 codegen only emits the three documented arg_type + values. */ +static void abort(void) { for (;;) {} } diff --git a/scripts/simple-patches/tcc-0.9.26/va_list-no-abort.before b/scripts/simple-patches/tcc-0.9.26/va_list-no-abort.before @@ -0,0 +1,3 @@ +/* Avoid include files, they may not be available when cross compiling */ +extern void *memset(void *s, int c, __SIZE_TYPE__ n); +extern void abort(void); diff --git a/scripts/simple-patches/tcc-0.9.26/x86_64-static-plt32.after b/scripts/simple-patches/tcc-0.9.26/x86_64-static-plt32.after @@ -0,0 +1,20 @@ +#ifdef TCC_TARGET_X86_64 + /* boot2: tcc 0.9.26 only collapses PLT32→PC32 for hidden + or LOCAL symbols. Under BOOTSTRAP we force static_link=1 + and emit no .dynamic / no PT_INTERP, so no runtime + linker exists to fill in the GOT slot a PLT entry would + jump through. Extend the collapse to any symbol defined + in this object (st_shndx != SHN_UNDEF) so the call site + becomes a direct PC-relative branch — same effect a + static link gets on aarch64, where R_AARCH64_CALL26 is + always resolved direct. Without this, `call main` from + start.S resolves to a PLT entry whose GOT slot is never + populated → jmp 0 → SIGSEGV. */ + if ((type == R_X86_64_PLT32 || type == R_X86_64_PC32) && + (ELFW(ST_VISIBILITY)(sym->st_other) != STV_DEFAULT || + ELFW(ST_BIND)(sym->st_info) == STB_LOCAL || + sym->st_shndx != SHN_UNDEF)) { + rel->r_info = ELFW(R_INFO)(sym_index, R_X86_64_PC32); + continue; + } +#endif diff --git a/scripts/simple-patches/tcc-0.9.26/x86_64-static-plt32.before b/scripts/simple-patches/tcc-0.9.26/x86_64-static-plt32.before @@ -0,0 +1,8 @@ +#ifdef TCC_TARGET_X86_64 + if ((type == R_X86_64_PLT32 || type == R_X86_64_PC32) && + (ELFW(ST_VISIBILITY)(sym->st_other) != STV_DEFAULT || + ELFW(ST_BIND)(sym->st_info) == STB_LOCAL)) { + rel->r_info = ELFW(R_INFO)(sym_index, R_X86_64_PC32); + continue; + } +#endif diff --git a/scripts/stage1-flatten.sh b/scripts/stage1-flatten.sh @@ -142,6 +142,26 @@ apply_our_patch date-time-stub "$SRC/tccpp.c" apply_our_patch lex-char-unsigned "$SRC/tccpp.c" apply_our_patch elfinterp-stub "$SRC/tccelf.c" +# x86_64 static-link PLT32 collapse: under BOOTSTRAP we force +# static_link=1 with no .dynamic / no PT_INTERP, so the runtime linker +# never fills the PLT's GOT slots. Upstream tcc 0.9.26 only collapses +# PLT32→PC32 for hidden-visibility or LOCAL symbols, leaving global +# defined symbols going through unfilled PLT entries. The patch widens +# the condition to any symbol defined in this binary (st_shndx != +# SHN_UNDEF), which matches the aarch64 path's behavior. Harmless on +# other arches: the block is gated `#ifdef TCC_TARGET_X86_64`. +apply_our_patch x86_64-static-plt32 "$SRC/tccelf.c" + +# x86_64 va_list runtime: tcc's lib/va_list.c declares `extern void +# abort(void)` and calls it in an unreachable default branch of the +# arg-type switch. Under -nostdlib that abort() symbol is unresolved +# and the link fails. Replace with an inline spin — same effect, no +# libc dependency. Unconditional patch: lib/va_list.c is only +# compiled on amd64, but the .before block is gated by the file's +# `#if defined TCC_TARGET_X86_64` so other arches see the patch +# inert. +apply_our_patch va_list-no-abort "$SRC/lib/va_list.c" + # Const-expr short-circuit: gen_opic/gen_opif must respect nocode_wanted # so 1 || (1/0), 0 && (1/0), 1 ? 2 : 1/0 etc. don't abort with "division # by zero in constant" in their unevaluated arms (C11 §6.6¶3). @@ -154,6 +174,16 @@ apply_our_patch aarch64-stdarg-array "$SRC/include/stdarg.h" apply_our_patch arm64-va-pointer-operand "$SRC/arm64-gen.c" apply_our_patch arm64-va-arg-pointer "$SRC/arm64-gen.c" +# riscv64 stdarg.h order fix — the upstream `#elif __riscv` branch +# uses `__builtin_va_list` before it's typedef'd. Stock tcc treats +# `__builtin_va_list` as a built-in keyword and forgives the forward +# reference; tcc-boot2's frontend does not. Swap the two typedefs so +# the base `char *__builtin_va_list` is in scope before va_list claims +# it. Affects only the riscv branch — the patch is gated by the +# `#elif __riscv` line in the before-block, so it's a no-op when that +# branch is absent (other tcc trees). +apply_our_patch riscv-stdarg-fix "$SRC/include/stdarg.h" + # Empty config.h shims — pass1.kaem creates these via `catm <out>` (line 27-28). : > "$SRC/config.h" mkdir -p "$WORK/mes-overlay/mes" diff --git a/tcc-cc/amd64/start.S b/tcc-cc/amd64/start.S @@ -0,0 +1,8 @@ +.globl _start +_start: + movq (%rsp), %rdi + leaq 8(%rsp), %rsi + call main + movq %rax, %rdi + movq $60, %rax + syscall diff --git a/tcc-cc/riscv64/start.S b/tcc-cc/riscv64/start.S @@ -0,0 +1,11 @@ +/* Linux riscv64 entry stub for the tcc-cc suite — same shape as + * tcc-cc/aarch64/start.S: pull argc/argv off the kernel-supplied + * stack frame, call main, exit with main's return value. */ + + .globl _start +_start: + ld a0, 0(sp) /* argc */ + addi a1, sp, 8 /* argv */ + call main + li a7, 93 /* NR_exit */ + ecall diff --git a/tcc-gcc/amd64/start.S b/tcc-gcc/amd64/start.S @@ -0,0 +1,23 @@ +/* x86_64 _start: mirror P1/entry-libc.P1pp. + * + * On entry rsp points at [argc][argv0]…[NULL][envp0]…[NULL][auxv]. Convert + * to (rdi=argc, rsi=argv) and run __libc_init(argc, argv) → main(argc, argv) + * → exit(rc). If exit returns, spin. + */ + .text + .globl _start +_start: + movq (%rsp), %r12 // argc (callee-saved r12) + leaq 8(%rsp), %r13 // argv (callee-saved r13) + + movq %r12, %rdi + movq %r13, %rsi + call __libc_init + + movq %r12, %rdi + movq %r13, %rsi + call main + + movq %rax, %rdi + call exit +1: jmp 1b diff --git a/tcc-gcc/amd64/sys_stubs.c b/tcc-gcc/amd64/sys_stubs.c @@ -0,0 +1,67 @@ +/* x86_64 syscall stubs matching P1pp.P1pp's sys_* entry points. + * Same C ABI as P1pp's labelled entries — our libc.flat.c calls these + * with argument shapes copied straight from boot2-syscall.c. + * + * Linux x86_64 syscall ABI: nr in rax, args in rdi/rsi/rdx/r10/r8/r9, + * return in rax. The C ABI hands us the first four args in + * rdi/rsi/rdx/rcx; we move rcx → r10 before issuing `syscall` (the + * instruction clobbers rcx). + */ + +static inline long _syscall0(long nr) { + long ret; + __asm__ volatile ("syscall" : "=a"(ret) : "0"(nr) : "rcx", "r11", "memory"); + return ret; +} +static inline long _syscall1(long nr, long a) { + long ret; + __asm__ volatile ("syscall" + : "=a"(ret) + : "0"(nr), "D"(a) + : "rcx", "r11", "memory"); + return ret; +} +static inline long _syscall2(long nr, long a, long b) { + long ret; + __asm__ volatile ("syscall" + : "=a"(ret) + : "0"(nr), "D"(a), "S"(b) + : "rcx", "r11", "memory"); + return ret; +} +static inline long _syscall3(long nr, long a, long b, long c) { + long ret; + __asm__ volatile ("syscall" + : "=a"(ret) + : "0"(nr), "D"(a), "S"(b), "d"(c) + : "rcx", "r11", "memory"); + return ret; +} +static inline long _syscall4(long nr, long a, long b, long c, long d) { + long ret; + register long r10 __asm__("r10") = d; + __asm__ volatile ("syscall" + : "=a"(ret) + : "0"(nr), "D"(a), "S"(b), "d"(c), "r"(r10) + : "rcx", "r11", "memory"); + return ret; +} + +#define NR_read 0 +#define NR_write 1 +#define NR_close 3 +#define NR_lseek 8 +#define NR_brk 12 +#define NR_exit 60 +#define NR_openat 257 +#define NR_unlinkat 263 +#define AT_FDCWD (-100) + +long sys_read (long fd, long buf, long n) { return _syscall3(NR_read, fd, buf, n); } +long sys_write (long fd, long buf, long n) { return _syscall3(NR_write, fd, buf, n); } +long sys_close (long fd) { return _syscall1(NR_close, fd); } +long sys_open (long path, long flags, long mode) { return _syscall4(NR_openat, AT_FDCWD, path, flags, mode); } +long sys_lseek (long fd, long off, long whence) { return _syscall3(NR_lseek, fd, off, whence); } +long sys_brk (long addr) { return _syscall1(NR_brk, addr); } +long sys_unlink(long path) { return _syscall3(NR_unlinkat, AT_FDCWD, path, 0); } +long sys_exit (long code) { _syscall1(NR_exit, code); for(;;); } diff --git a/tcc-libc/amd64/start.S b/tcc-libc/amd64/start.S @@ -0,0 +1,19 @@ +/* tcc-libc entry stub — same role as P1/entry-libc.P1pp's p1_main: + * call __libc_init(argc, argv) so `environ` is set, then main(argc, + * argv), then exit with main's return value. Linux x86_64 brings + * argc at [rsp] and argv at rsp+8 on entry. */ + + .globl _start +_start: + movq (%rsp), %rdi /* argc */ + leaq 8(%rsp), %rsi /* argv */ + pushq %rsi /* save across __libc_init call */ + pushq %rdi /* keeps 16-byte stack alignment */ + call __libc_init + popq %rdi + popq %rsi + call main + /* main's return is in %rax — feed it to exit(2). */ + movq %rax, %rdi + movq $60, %rax /* NR_exit */ + syscall diff --git a/tcc-libc/amd64/sys_stubs.S b/tcc-libc/amd64/sys_stubs.S @@ -0,0 +1,69 @@ +/* Linux x86_64 syscall stubs matching the sys_* labels libp1pp + * provides (see P1/P1pp.P1pp). boot2-syscall.c declares them as + * extern long sys_<name>(...) and the mes-libc layers (read/write/ + * open/close/lseek/sbrk/unlink/_exit) call them. The cc.scm + libp1pp + * pipeline resolves these labels against P1pp.P1pp's wrappers; the + * tcc-libc suite links against this object instead since tcc-built + * binaries don't catm libp1pp. + * + * Linux x86_64 syscall ABI: nr in rax, args in rdi/rsi/rdx/r10/r8/r9 + * (note: r10, not rcx, for the 4th arg — the syscall instruction + * clobbers rcx); return in rax. + * + * sys_open/sys_unlink shuffle args because Linux's openat/unlinkat + * take an AT_FDCWD prefix that the libp1pp-compatible wrappers + * don't surface to callers. + */ + + .globl sys_read, sys_write, sys_close, sys_open + .globl sys_lseek, sys_brk, sys_unlink, sys_exit + +sys_read: + movq $0, %rax + syscall + ret + +sys_write: + movq $1, %rax + syscall + ret + +sys_close: + movq $3, %rax + syscall + ret + +sys_open: + /* (path, flags, mode) -> openat(AT_FDCWD, path, flags, mode) */ + movq %rdx, %r10 /* mode */ + movq %rsi, %rdx /* flags */ + movq %rdi, %rsi /* path */ + movq $-100, %rdi /* AT_FDCWD */ + movq $257, %rax /* NR_openat */ + syscall + ret + +sys_lseek: + movq $8, %rax + syscall + ret + +sys_brk: + movq $12, %rax + syscall + ret + +sys_unlink: + /* (path) -> unlinkat(AT_FDCWD, path, 0) */ + movq %rdi, %rsi /* path */ + movq $-100, %rdi /* AT_FDCWD */ + movq $0, %rdx /* flags */ + movq $263, %rax /* NR_unlinkat */ + syscall + ret + +sys_exit: + movq $60, %rax + syscall + /* unreachable */ +1: jmp 1b diff --git a/tcc-libc/riscv64/start.S b/tcc-libc/riscv64/start.S @@ -0,0 +1,20 @@ +/* tcc-libc entry stub — riscv64 sibling of tcc-libc/aarch64/start.S. + * Linux brings argc at [sp] and argv at sp+8 on entry. Call + * __libc_init(argc, argv) so `environ` is set, then main(argc, argv), + * then exit with main's return value. */ + + .globl _start +_start: + ld a0, 0(sp) /* argc */ + addi a1, sp, 8 /* argv */ + addi sp, sp, -16 /* save argc/argv across __libc_init */ + sd a0, 0(sp) + sd a1, 8(sp) + call __libc_init + ld a0, 0(sp) + ld a1, 8(sp) + addi sp, sp, 16 + call main + /* main's return is in a0 — feed it to exit(2). */ + li a7, 93 /* NR_exit */ + ecall diff --git a/tcc-libc/riscv64/sys_stubs.S b/tcc-libc/riscv64/sys_stubs.S @@ -0,0 +1,68 @@ +/* Linux riscv64 syscall stubs matching the sys_* labels libp1pp + * provides (see P1/P1pp.P1pp). boot2-syscall.c declares them as + * extern long sys_<name>(...) and the mes-libc layers (read/write/ + * open/close/lseek/sbrk/unlink/_exit) call them. The cc.scm + libp1pp + * pipeline resolves these labels against P1pp.P1pp's wrappers; the + * tcc-libc suite links against this object instead since tcc-built + * binaries don't catm libp1pp. + * + * Linux riscv64 syscall ABI: nr in a7, args in a0-a5, return in a0. + * Same generic-unistd numbering as aarch64. + * + * sys_open/sys_unlink shuffle args because Linux's openat/unlinkat + * take an AT_FDCWD prefix that the libp1pp-compatible wrappers + * don't surface to callers. + */ + + .globl sys_read, sys_write, sys_close, sys_open + .globl sys_lseek, sys_brk, sys_unlink, sys_exit + +sys_read: + li a7, 63 + ecall + ret + +sys_write: + li a7, 64 + ecall + ret + +sys_close: + li a7, 57 + ecall + ret + +sys_open: + /* (path, flags, mode) -> openat(AT_FDCWD, path, flags, mode) */ + mv a3, a2 /* mode */ + mv a2, a1 /* flags */ + mv a1, a0 /* path */ + li a0, -100 /* AT_FDCWD */ + li a7, 56 + ecall + ret + +sys_lseek: + li a7, 62 + ecall + ret + +sys_brk: + li a7, 214 + ecall + ret + +sys_unlink: + /* (path) -> unlinkat(AT_FDCWD, path, 0) */ + mv a1, a0 /* path */ + li a0, -100 /* AT_FDCWD */ + li a2, 0 /* flags */ + li a7, 35 + ecall + ret + +sys_exit: + li a7, 93 + ecall + /* unreachable */ +1: j 1b diff --git a/tcc-libc/va_list_shim.h b/tcc-libc/va_list_shim.h @@ -1,25 +1,32 @@ /* tcc-libc va_list shim — pre-included when tcc-boot2 compiles * libc.flat.c (or any other host-preprocessed TU under our boot2 * stdarg.h shim). The flatten step routes `va_list` through - * `__builtin_va_list`, but stock tcc's frontend does not recognize - * that token as a type — tcc's <stdarg.h> defines `va_list` as - * `__va_list_struct[1]`. Make `__builtin_va_list` an alias for the - * same array type so libc.flat.c's + * `__builtin_va_list`, but on aarch64 / amd64 stock tcc's frontend + * does not recognize that token as a type — tcc's <stdarg.h> defines + * `va_list` as `__va_list_struct[1]`. Make `__builtin_va_list` an + * alias for the same array type so libc.flat.c's * * typedef __builtin_va_list va_list; * * collapses to a (legal) duplicate typedef of the existing * tcc-stdlib `va_list`. The `__builtin_va_*` macros in the flatten - * are direct tcc intrinsics; they do not need a shim. */ + * are direct tcc intrinsics; the aliases just reach them by the + * gcc-conformant builtin spelling. + * + * On riscv64 tcc's <stdarg.h> already names everything by the + * gcc spelling — `va_list` is a typedef to `__builtin_va_list`, + * `__builtin_va_arg` / `__builtin_va_end` / `__builtin_va_copy` are + * macros, and `__builtin_va_start` is a frontend intrinsic — so the + * shim has nothing to add. The aliases below would in fact redefine + * the macros stdarg.h already declared, triggering an infinite-loop + * expansion of `__builtin_va_arg(ap, type)` through `va_arg(ap, + * type)` and back. Gate the aliases on !__riscv. */ #include <stdarg.h> -typedef va_list __builtin_va_list; -/* Likewise alias the __builtin_va_* call sites the flatten leaves - * behind onto tcc's macro-driven va_* implementations. tcc has its - * own native intrinsics underneath va_start / va_arg / va_end (see - * tcc/include/stdarg.h); these aliases just reach them by the - * gcc-conformant builtin spelling. */ +#ifndef __riscv +typedef va_list __builtin_va_list; #define __builtin_va_start(ap, last) va_start(ap, last) #define __builtin_va_end(ap) va_end(ap) #define __builtin_va_arg(ap, type) va_arg(ap, type) #define __builtin_va_copy(dst, src) va_copy(dst, src) +#endif diff --git a/vendor/mes-libc/patches/printf-mes-varargs.after b/vendor/mes-libc/patches/printf-mes-varargs.after @@ -0,0 +1,2 @@ +/* boot2: drop mes-mescc-specific x86_64 va_list pre-offset; va_start + below handles varargs correctly under any standard C compiler. */ diff --git a/vendor/mes-libc/patches/printf-mes-varargs.before b/vendor/mes-libc/patches/printf-mes-varargs.before @@ -0,0 +1,5 @@ +#if __GNUC__ && __x86_64__ && !SYSTEM_LIBC +#define __FUNCTION_ARGS 1 + ap += (__FOO_VARARGS + (__FUNCTION_ARGS << 1)) << 3; +#undef __FUNCTION_ARGS +#endif diff --git a/vendor/mes-libc/patches/snprintf-mes-varargs.after b/vendor/mes-libc/patches/snprintf-mes-varargs.after @@ -0,0 +1,2 @@ +/* boot2: drop mes-mescc-specific x86_64 va_list pre-offset; va_start + below handles varargs correctly under any standard C compiler. */ diff --git a/vendor/mes-libc/patches/snprintf-mes-varargs.before b/vendor/mes-libc/patches/snprintf-mes-varargs.before @@ -0,0 +1,5 @@ +#if __GNUC__ && __x86_64__ && !SYSTEM_LIBC +#define __FUNCTION_ARGS 3 + ap += (__FOO_VARARGS + (__FUNCTION_ARGS << 1)) << 3; +#undef __FUNCTION_ARGS +#endif diff --git a/vendor/mes-libc/patches/sprintf-mes-varargs.after b/vendor/mes-libc/patches/sprintf-mes-varargs.after @@ -0,0 +1,2 @@ +/* boot2: drop mes-mescc-specific x86_64 va_list pre-offset; va_start + below handles varargs correctly under any standard C compiler. */ diff --git a/vendor/mes-libc/patches/sprintf-mes-varargs.before b/vendor/mes-libc/patches/sprintf-mes-varargs.before @@ -0,0 +1,5 @@ +#if __GNUC__ && __x86_64__ && !SYSTEM_LIBC +#define __FUNCTION_ARGS 2 + ap += (__FOO_VARARGS + (__FUNCTION_ARGS << 1)) << 3; +#undef __FUNCTION_ARGS +#endif