From cded11d896f24e916b0387f2fce40c07d8019c0f Mon Sep 17 00:00:00 2001 From: Brandon Payton Date: Sat, 27 Jun 2026 17:07:26 -0400 Subject: [PATCH] Fix pthread stack alignment for SQLite sorter temps Align wasm pthread clone stacks to 16 bytes before kernel_clone so stack-based variadic calls made from pthreads read 64-bit arguments correctly. This fixes SQLite sorter temp-name corruption in sort/sort2 tests. Also install the Kandelo SQLite testrunner platform shim into the staged upstream testrunner so nested config jobs inherit it, including the browser demo path, and add a Sortix pthread snprintf regression for the SQLite temp-name format. --- apps/browser-demos/pages/sqlite-test/main.ts | 29 ++++++++ .../src/thread/wasm32posix/clone.c | 10 ++- scripts/run-sqlite-official-tests.sh | 33 +++++++++ .../pthread/pthread_stack_snprintf_varargs.c | 67 +++++++++++++++++++ 4 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 tests/sortix/os-test-local/basic/pthread/pthread_stack_snprintf_varargs.c diff --git a/apps/browser-demos/pages/sqlite-test/main.ts b/apps/browser-demos/pages/sqlite-test/main.ts index 960fdd2f1..43479273c 100644 --- a/apps/browser-demos/pages/sqlite-test/main.ts +++ b/apps/browser-demos/pages/sqlite-test/main.ts @@ -108,6 +108,34 @@ function createFs(): MemoryFileSystem { return fs; } +function installKandeloTestrunnerShim(fs: MemoryFileSystem): void { + const path = "/sqlite/test/testrunner.tcl"; + const decoder = new TextDecoder(); + const original = decoder.decode(readVfsFile(fs, path)); + if (original.includes("kandelo_testrunner_platform_shim")) return; + + const marker = 'exec tclsh "$0" "$@"\n'; + const markerEnd = original.indexOf(marker); + if (markerEnd < 0) { + throw new Error("Unable to locate SQLite testrunner.tcl shell preamble"); + } + + const insertAt = markerEnd + marker.length; + const shim = [ + "", + "# Kandelo platform shim: the wasm Tcl build reports a target OS name", + "# that SQLite testrunner.tcl does not classify. Keep the shim inside", + "# the staged upstream runner so nested config jobs inherit it too.", + "if {![info exists ::kandelo_testrunner_platform_shim]} {", + " set ::kandelo_testrunner_platform_shim 1", + " set ::tcl_platform(os) OpenBSD", + " set ::tcl_platform(platform) unix", + "}", + "", + ].join("\n"); + writeVfsFile(fs, path, original.slice(0, insertAt) + shim + original.slice(insertAt), 0o644); +} + function sqliteSyscallLogPtrWidth(): 4 | 8 | undefined { const value = import.meta.env.VITE_SQLITE_BROWSER_SYSCALL_LOG_PTR_WIDTH; if (value === "4") return 4; @@ -164,6 +192,7 @@ async function init() { }; const artifactTimer = window.setInterval(publishArtifactSnapshot, 5000); if (argv[1] === "kandelo-testrunner.tcl") { + installKandeloTestrunnerShim(fs); writeVfsFile(fs, "/sqlite/kandelo-testrunner.tcl", [ "set ::tcl_platform(os) OpenBSD", "set ::tcl_platform(platform) unix", diff --git a/libc/musl-overlay/src/thread/wasm32posix/clone.c b/libc/musl-overlay/src/thread/wasm32posix/clone.c index bd6c45f58..6e1475e48 100644 --- a/libc/musl-overlay/src/thread/wasm32posix/clone.c +++ b/libc/musl-overlay/src/thread/wasm32posix/clone.c @@ -27,9 +27,17 @@ int __clone(int (*fn)(void *), void *stack, int flags, void *arg, ...) int *ctid = __builtin_va_arg(ap, int *); __builtin_va_end(ap); + /* + * pthread_create places its start_args object on the child stack, but only + * realigns the resulting stack pointer to pointer width. Wasm codegen + * assumes a 16-byte-aligned __stack_pointer at function entry; starting a + * thread at a 4-byte residue corrupts stack-based varargs such as %llx%c. + */ + uintptr_t stack_ptr = (uintptr_t)stack & ~(uintptr_t)15; + return kernel_clone( (uint32_t)(uintptr_t)fn, - (uint32_t)(uintptr_t)stack, + (uint32_t)stack_ptr, (uint32_t)flags, (uint32_t)(uintptr_t)arg, (uint32_t)(uintptr_t)ptid, diff --git a/scripts/run-sqlite-official-tests.sh b/scripts/run-sqlite-official-tests.sh index a27544ad4..5aaf09a4e 100755 --- a/scripts/run-sqlite-official-tests.sh +++ b/scripts/run-sqlite-official-tests.sh @@ -248,6 +248,38 @@ write_sqlite_report() { cat "$report" } +install_kandelo_testrunner_shim() { + local target="$1" + local tmp="$target.kandelo-shim" + + if grep -q 'kandelo_testrunner_platform_shim' "$target"; then + return + fi + + awk ' + { + print + if (!done && $0 == "exec tclsh \"$0\" \"$@\"") { + print "" + print "# Kandelo platform shim: the wasm Tcl build reports a target OS name" + print "# that SQLite testrunner.tcl does not classify. Keep the shim inside" + print "# the staged upstream runner so nested config jobs inherit it too." + print "if {![info exists ::kandelo_testrunner_platform_shim]} {" + print " set ::kandelo_testrunner_platform_shim 1" + print " set ::tcl_platform(os) OpenBSD" + print " set ::tcl_platform(platform) unix" + print "}" + done = 1 + } + } + END { + if (!done) exit 42 + } + ' "$target" > "$tmp" + mv "$tmp" "$target" + chmod a+r "$target" +} + cleanup() { if [ "$KEEP_WORKDIR" = "1" ]; then echo "Keeping SQLite official workdir: $WORKDIR" @@ -267,6 +299,7 @@ chmod -R a+rX "$WORKDIR" chmod 0777 "$WORKDIR" mkdir -p "$WORKDIR/testdir" chmod 0777 "$WORKDIR/testdir" +install_kandelo_testrunner_shim "$WORKDIR/test/testrunner.tcl" cp "$TESTFIXTURE" "$WORKDIR/testfixture" cp "$TESTFIXTURE" "$WORKDIR/testfixture.wasm" cp "$SQLITE3" "$WORKDIR/sqlite3" diff --git a/tests/sortix/os-test-local/basic/pthread/pthread_stack_snprintf_varargs.c b/tests/sortix/os-test-local/basic/pthread/pthread_stack_snprintf_varargs.c new file mode 100644 index 000000000..fa4ea0516 --- /dev/null +++ b/tests/sortix/os-test-local/basic/pthread/pthread_stack_snprintf_varargs.c @@ -0,0 +1,67 @@ +/* Test pthread initial stack alignment for variadic calls. */ + +#include +#include +#include + +#include "../basic.h" + +struct test_case { + unsigned long long value; + const char* expected; +}; + +static const struct test_case test_cases[] = { + { 0x333c7d7900000000ULL, "/tmp/etilqs_333c7d7900000000" }, + { 0x17887ec000000000ULL, "/tmp/etilqs_17887ec000000000" }, + { 0x09f49ef200000000ULL, "/tmp/etilqs_9f49ef200000000" }, +}; + +static void check_format(const struct test_case* test) +{ + char buffer[80]; + memset(buffer, 0x5a, sizeof(buffer)); + + int ret = snprintf(buffer, sizeof(buffer), "%s/etilqs_%llx%c", + "/tmp", test->value, 0); + size_t expected_len = strlen(test->expected); + if ( ret != (int) expected_len + 1 ) + errx(1, "snprintf returned %d, expected %zu", ret, + expected_len + 1); + if ( strlen(buffer) != expected_len ) + errx(1, "snprintf wrote visible length %zu, expected %zu", + strlen(buffer), expected_len); + if ( strcmp(buffer, test->expected) != 0 ) + errx(1, "snprintf wrote '%s', expected '%s'", buffer, + test->expected); + if ( buffer[expected_len] != '\0' || buffer[expected_len + 1] != '\0' ) + errx(1, "snprintf did not write the %%c NUL and terminator"); +} + +static void* start(void* arg) +{ + check_format((const struct test_case*) arg); + return NULL; +} + +int main(void) +{ + for ( size_t i = 0; i < sizeof(test_cases) / sizeof(test_cases[0]); i++ ) + { + pthread_t thread; + int errnum = pthread_create(&thread, NULL, start, + (void*) &test_cases[i]); + if ( errnum ) + { + errno = errnum; + err(1, "pthread_create"); + } + errnum = pthread_join(thread, NULL); + if ( errnum ) + { + errno = errnum; + err(1, "pthread_join"); + } + } + return 0; +}