From e4a265a9914ea9ccb947436b277c31362141c90d Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Thu, 28 May 2026 01:50:09 +0300 Subject: [PATCH] tcc/bionic: fix unresolved __builtin_bswap16/__builtin_nanf on Termux (fix #27207) (#27277) --- .github/workflows/termux_ci.yml | 74 ++++++++++++++++++++++++++ thirdparty/mbedtls/library/alignment.h | 4 +- thirdparty/mbedtls/mbedtls.patch | 4 +- vlib/v/builder/cc.v | 37 ++++++++++++- vlib/v/gen/c/cgen.v | 22 ++++++++ 5 files changed, 135 insertions(+), 6 deletions(-) diff --git a/.github/workflows/termux_ci.yml b/.github/workflows/termux_ci.yml index a2e3933cb..8ff03ba25 100644 --- a/.github/workflows/termux_ci.yml +++ b/.github/workflows/termux_ci.yml @@ -53,3 +53,77 @@ jobs: v run examples/primes.v v -e "import os; dump( os.user_os() )" ' + + termux-build-arm64-tcc: + # Diagnostic job for vlang/v#27207: reproduces the Termux/AArch64 tcc + # bootstrap failure on a real arm64 runner so we can collect logs without + # local hardware. Allowed to fail until the upstream tcc AArch64 bug is + # resolved. + runs-on: ubuntu-24.04-arm + continue-on-error: true + timeout-minutes: 45 + steps: + - uses: actions/checkout@v6 + - name: Build in Termux (arm64, default tcc) + run: | + set -o xtrace + uname -a + docker run --rm --mount type=bind,source=/home/runner/work/v/v,destination=/src -w /src termux/termux-docker:latest bash -c ' + set -o xtrace + uname -a + cp -r /src ~/vproject; cd ~/vproject + export TERMUX_VERSION=0.118.3 + bash .github/workflows/retry.sh pkg update -y + bash .github/workflows/retry.sh pkg install -y clang libexecinfo libgc libgc-static make git gdb file + git log -n4 + ulimit -c unlimited || true + # Explicit `-cc tcc` is required: on Linux arm64 the Makefile + # otherwise injects `-cc "$(CC)"` (see GNUmakefile:149-155) and + # the bundled tcc path would never run — defeating the purpose + # of this diagnostic job for #27207. Capture every invocation + # via -showcc and the full build output to build.log. + set +e + VFLAGS="-cc tcc -showcc -stats" make 2>&1 | tee build.log + rc=${PIPESTATUS[0]} + set -e + echo "make exit code: $rc" + # Always copy the log back to the bind-mounted dir so the runner + # step that uploads the artifact can find it. + cp build.log /src/build.log || true + if [ "$rc" -ne 0 ] || [ ! -x ./v ]; then + echo "===== bootstrap failed; collecting diagnostics =====" + echo "--- tcc binary ---" + ls -la thirdparty/tcc/ 2>/dev/null || true + for t in thirdparty/tcc/tcc.exe thirdparty/tcc/tcc; do + if [ -x "$t" ]; then "$t" -v || true; fi + done + echo "--- last 120 lines of build.log ---" + tail -n 120 build.log || true + echo "--- v1/v2/v file info ---" + file v1 v2 v 2>/dev/null || true + echo "--- last tcc invocations from build.log ---" + grep -E "tcc(\.exe)? " build.log | tail -n10 || true + echo "--- core files (if any) ---" + ls -la core* 2>/dev/null || true + for c in core core.*; do + if [ -f "$c" ] && [ -x ./v2 ]; then + echo "--- gdb backtrace from $c against v2 ---" + gdb --batch --ex bt --ex "info registers" ./v2 "$c" || true + fi + done + # If make exited 0 but ./v is missing, force a nonzero exit so + # the diagnostic job does not go green without a usable compiler. + if [ "$rc" -eq 0 ]; then rc=1; fi + exit "$rc" + fi + echo "===== bootstrap succeeded =====" + ./v -version + ./v run examples/hello_world.v + ' + - name: Upload build log + if: always() + uses: actions/upload-artifact@v4 + with: + name: termux-arm64-build-log + path: build.log + if-no-files-found: ignore diff --git a/thirdparty/mbedtls/library/alignment.h b/thirdparty/mbedtls/library/alignment.h index 5de60439c..5547fd9b2 100644 --- a/thirdparty/mbedtls/library/alignment.h +++ b/thirdparty/mbedtls/library/alignment.h @@ -280,7 +280,7 @@ static inline void mbedtls_put_unaligned_uint64(void *p, uint64_t x) /* * Detect GCC built-in byteswap routines */ -#if defined(__GNUC__) && !(defined(__TINYC__) && defined(__FreeBSD__)) +#if defined(__GNUC__) && !defined(__TINYC__) #if MBEDTLS_GCC_VERSION >= 40800 #define MBEDTLS_BSWAP16 __builtin_bswap16 #endif @@ -293,7 +293,7 @@ static inline void mbedtls_put_unaligned_uint64(void *p, uint64_t x) /* * Detect Clang built-in byteswap routines */ -#if defined(__clang__) && defined(__has_builtin) && !(defined(__TINYC__) && defined(__FreeBSD__)) +#if defined(__clang__) && defined(__has_builtin) && !defined(__TINYC__) #if __has_builtin(__builtin_bswap16) && !defined(MBEDTLS_BSWAP16) #define MBEDTLS_BSWAP16 __builtin_bswap16 #endif /* __has_builtin(__builtin_bswap16) */ diff --git a/thirdparty/mbedtls/mbedtls.patch b/thirdparty/mbedtls/mbedtls.patch index 0f35b79fc..ddd2506ea 100644 --- a/thirdparty/mbedtls/mbedtls.patch +++ b/thirdparty/mbedtls/mbedtls.patch @@ -49,7 +49,7 @@ diff -ur mbedtls.orig/library/alignment.h mbedtls/library/alignment.h * Detect GCC built-in byteswap routines */ -#if defined(__GNUC__) -+#if defined(__GNUC__) && !(defined(__TINYC__) && defined(__FreeBSD__)) ++#if defined(__GNUC__) && !defined(__TINYC__) #if MBEDTLS_GCC_VERSION >= 40800 #define MBEDTLS_BSWAP16 __builtin_bswap16 #endif @@ -58,7 +58,7 @@ diff -ur mbedtls.orig/library/alignment.h mbedtls/library/alignment.h * Detect Clang built-in byteswap routines */ -#if defined(__clang__) && defined(__has_builtin) -+#if defined(__clang__) && defined(__has_builtin) && !(defined(__TINYC__) && defined(__FreeBSD__)) ++#if defined(__clang__) && defined(__has_builtin) && !defined(__TINYC__) #if __has_builtin(__builtin_bswap16) && !defined(MBEDTLS_BSWAP16) #define MBEDTLS_BSWAP16 __builtin_bswap16 #endif /* __has_builtin(__builtin_bswap16) */ diff --git a/vlib/v/builder/cc.v b/vlib/v/builder/cc.v index c4c990f1b..adc8f412b 100644 --- a/vlib/v/builder/cc.v +++ b/vlib/v/builder/cc.v @@ -2284,6 +2284,30 @@ fn (v &Builder) should_compile_bundled_thirdparty_object_from_source(obj_path st return v.ccoptions.cc == .tcc && v.pref.os == .macos } +// latest_thirdparty_header_mtime returns the most recent mtime among `.h` and +// `.hpp` files in the directory of `source_file`. It is used as an extra cache +// key for third-party object compilation, so that header-only patches (for +// example to mbedtls/library/alignment.h) reliably invalidate stale `.o` +// files that were produced before the header change. +fn latest_thirdparty_header_mtime(source_file string) i64 { + if source_file == '' { + return 0 + } + dir := os.dir(source_file) + entries := os.ls(dir) or { return 0 } + mut latest := i64(0) + for f in entries { + if !(f.ends_with('.h') || f.ends_with('.hpp')) { + continue + } + m := os.file_last_mod_unix(os.join_path(dir, f)) + if m > latest { + latest = m + } + } + return latest +} + fn (mut v Builder) build_thirdparty_obj_file(mod string, path string, moduleflags []cflag.CFlag) { trace_thirdparty_obj_files := 'trace_thirdparty_obj_files' in v.pref.compile_defines obj_path := os.real_path(path) @@ -2331,10 +2355,19 @@ fn (mut v Builder) build_thirdparty_obj_file(mod string, path string, moduleflag '${os.quoted_path(obj_path)} not found, building it in ${os.quoted_path(opath)} ...' } if os.exists(opath) { + opath_mtime := os.file_last_mod_unix(opath) + src_mtime := os.file_last_mod_unix(source_file) + // Header-only edits in the source directory (e.g. mbedtls's + // alignment.h) leave every sibling `.c` untouched, so a pure + // `opath_mtime < src_mtime` test would silently reuse stale objects + // that still reference the old headers. Treat the newest sibling + // header as part of the cache key as well. + hdr_mtime := latest_thirdparty_header_mtime(source_file) + deps_mtime := if hdr_mtime > src_mtime { hdr_mtime } else { src_mtime } if compile_bundled_source && !cached_object_was_built_from_source { rebuild_reason_message = '${os.quoted_path(opath)} was copied from a bundled object, rebuilding it from ${os.quoted_path(source_file)} ...' - } else if os.file_last_mod_unix(opath) < os.file_last_mod_unix(source_file) { - rebuild_reason_message = '${os.quoted_path(opath)} is older than ${os.quoted_path(source_file)}, rebuilding ...' + } else if opath_mtime < deps_mtime { + rebuild_reason_message = '${os.quoted_path(opath)} is older than ${os.quoted_path(source_file)} or its sibling headers, rebuilding ...' } else { return } diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 4d05da8b6..ccfe3328b 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -1192,6 +1192,28 @@ pub fn (mut g Gen) init() { g.preincludes.writeln(tcc_undef_has_include) g.cheaders.writeln(tcc_undef_has_include) g.includes.writeln(tcc_undef_has_include) + // Android/Termux bionic expands NAN/INFINITY/HUGE_VAL* via + // __builtin_nanf/__builtin_inf*/__builtin_huge_val*. The AArch64 tcc + // port does not lower these intrinsics, so binaries fail at runtime + // with unresolved __builtin_nanf etc. symbols (vlang/v#27207). + // Emit the macro shim into preincludes/cheaders/includes so it is + // in scope before *any* bionic header is reached, including those + // pulled in via `#preinclude `. + tcc_bionic_math_shim := ' +#if defined(__TINYC__) && defined(__BIONIC__) + #define __builtin_nanf(ignored_string) (0.0F / 0.0F) + #define __builtin_nan(ignored_string) (0.0 / 0.0) + #define __builtin_nanl(ignored_string) (0.0L / 0.0L) + #define __builtin_inff() (1.0F / 0.0F) + #define __builtin_inf() (1.0 / 0.0) + #define __builtin_infl() (1.0L / 0.0L) + #define __builtin_huge_valf() (1.0F / 0.0F) + #define __builtin_huge_val() (1.0 / 0.0) + #define __builtin_huge_vall() (1.0L / 0.0L) +#endif' + g.preincludes.writeln(tcc_bionic_math_shim) + g.cheaders.writeln(tcc_bionic_math_shim) + g.includes.writeln(tcc_bionic_math_shim) if g.pref.os == .freebsd { g.cheaders.writeln('#include ') g.cheaders.writeln('#include ') -- 2.39.5