Repository navigation
Coroutine: Assembly entry on every CPU, tested with QEMU. v1.9.3 - #42
Merged
Merged
Conversation
…malloc. StacksStartAtDifferentOffsets checks that st_randomize_stacks puts the frame of the same function at a different page offset in each new coroutine. With MALLOC_STACK, as SRS builds ST, malloc already chooses a different start within the page for each stack on Linux and Windows, so the page offset no longer shows ST's own offset, and the test skips. utest/Makefile passes -DMALLOC_STACK from the EXTRA_CFLAGS of the library build to the utest, so the utest knows how libst.a was built. --------- Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…s with qemu-user. Add auto/qemu.sh <cpu> [utest|tools|tools-malloc|all] and its Docker image, auto/qemu/Dockerfile, built from ubuntu:24.04 with the cross g++ of x86_64, aarch64, i386, arm, riscv64, loongarch64, mips, mipsel, mips64 and mips64el, and qemu-user. The script builds the image when missing, runs itself in it with the checkout mounted, cross-builds the library, the utest and the tools in place in LINUX_<cpu>_qemu_DBG, and runs them with qemu-<cpu> -L <sysroot>, or natively for the container's own CPU. It prints one RESULT line per run. The Makefile takes CC, CXX, AR, LD, RANLIB and TARGETDIR from the environment when set, with the same defaults as before. auto/tools.sh runs each tool through the launcher in ST_TOOL_RUN, empty by default. The exception tool detects the Darwin CPU with $(CXX) instead of g++. --------- Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
qemu-x86_64 8.2.2 crashes with "QEMU internal SIGSEGV" in pthread_getattr_np on the main thread, also in a program without ST, so PrimordialStackTest.DescribesTheMainThreadStack and the lifecycle tool cannot run under it. auto/qemu.sh now keeps a per-CPU gtest filter and a list of tools to leave out, with the reason, applied only when a CPU runs under qemu-user. Each skip prints "SKIP <cpu> <run> <what>: <why>" before its RESULT line. ST failures are never listed there, so they stay visible. --------- Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Add auto/darwin.sh <cpu> [utest|tools|tools-malloc|all] for arm64 and x86_64. It exports CPU_ARCHS, builds the library, the utest and the tools in place in DARWIN_<cpu>_DBG, and runs every binary with arch -<cpu>, so x86_64 runs under Rosetta 2 and a binary built for the wrong CPU fails to start. It prints one RESULT line per run, as auto/qemu.sh does. On macOS, the Makefile and every tool Makefile take CPU_ARCHS from the environment for -arch, and detect the host CPU only when it is not set. The utest Makefile adds -arch $(CPU_ARCHS) on macOS when it is set; the default builds are unchanged. --------- Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The stacks and randomize cases of tools/thread ran their 16 KB case in a function with a 64 KB frame, on the default 128 KB stack. On macOS x86_64, ___chkstk_darwin probes a large frame once more about twice its size below SP, so that frame faulted with SIGBUS before the case ran. The default-stack cases now use a function with a 16 KB buffer, and the 64 KB frame stays for the 256 KB custom stack. ST's default stack is unchanged. --------- Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… of the thread entry. md.h gets MD_GET_PC and MD_GET_FP next to MD_GET_SP on every OS and CPU: the jmpbuf slots of the address _st_md_cxt_restore jumps to and of the frame pointer. A new white-box utest checks them against a real save on every CPU except Windows: the saved SP is in the caller's frame, the PC in its code, and the frame pointer is its frame address. Where MD_INIT_THREAD_ENTRY is defined, today only on Windows x64, a second test checks a new thread before it runs: its saved PC is _st_md_thread_start, its SP has the ABI alignment, with a null return address on x86, and its frame pointer is null. The Windows x64 entry now also clears rbp, so the thread does not start with its creator's frame pointer. The utest reads _st_thread_t, so it now gets MD_ASAN from EXTRA_CFLAGS too, which changes that layout, and the test checks the layout before it reads the context. --------- Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Linux x86_64 gets _st_md_thread_start in md_linux.S and MD_INIT_THREAD_ENTRY in md.h, like Windows x64. st_thread_create still saves the context as a template, then the macro moves the saved SP to a null return address at 8 mod 16, as at a function entry, sets the saved PC to the entry, and clears the saved rbp. The first restore of the thread enters the entry on the new stack, which aligns rsp to 16 and calls _st_thread_main, instead of returning a second time from the save in st_thread_create. The entry marks the return address undefined in its CFI, so DWARF unwinds such as glibc backtrace() and C++ exceptions end there, and starts with rbp at 0, which ends frame-pointer walks. The utest of the thread entry now runs on Linux x86_64. --------- Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Linux aarch64 gets _st_md_thread_start in md_linux2.S and MD_INIT_THREAD_ENTRY in md.h. st_thread_create still saves the context as a template, then the macro aligns the saved SP to 16, sets the saved link register, which the restore branches to, to the entry, and clears the saved x29. The first restore of the thread enters the entry on the new stack, which calls _st_thread_main, instead of returning a second time from the save in st_thread_create. The entry marks x30 undefined in its CFI, so DWARF unwinds such as glibc backtrace() and C++ exceptions end there, and starts with x29 and x30 at 0, which ends frame-pointer walks. The utest of the thread entry now runs on Linux aarch64. --------- Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
macOS arm64 gets _st_md_thread_start in md_darwin.S and MD_INIT_THREAD_ENTRY in md.h. st_thread_create still saves the context as a template, then the macro aligns the saved SP to 16, sets the saved link register, which the restore returns to, to the entry, and clears the saved x29. The first restore of the thread enters the entry on the new stack, which calls _st_thread_main, instead of returning a second time from the save in st_thread_create. The entry starts with x29 and x30 at 0, which ends the frame-pointer walk of macOS backtrace(), and marks x30 undefined in its CFI, so DWARF unwinds end there too. The utest of the thread entry now runs on macOS arm64. --------- Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
macOS x86_64 gets _st_md_thread_start in md_darwin.S and MD_INIT_THREAD_ENTRY in md.h, like Linux x86_64. st_thread_create still saves the context as a template, then the macro moves the saved SP to a null return address at 8 mod 16, as at a function entry, sets the saved PC to the entry, and clears the saved rbp. The first restore of the thread enters the entry on the new stack, which aligns rsp to 16 and calls _st_thread_main, instead of returning a second time from the save in st_thread_create. The entry starts with rbp at 0, which ends the frame-pointer walk of macOS backtrace(), and marks the return address undefined in its CFI, so DWARF unwinds end there too. The utest of the thread entry now runs on macOS x86_64. --------- Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Linux i386 gets _st_md_thread_start in md_linux.S and MD_INIT_THREAD_ENTRY in md.h, like Linux x86_64. st_thread_create still saves the context as a template, then the macro moves the saved SP to a null return address at 12 mod 16, as at a function entry, sets the saved PC to the entry, and clears the saved ebp. The first restore of the thread enters the entry on the new stack, which loads the GOT address into ebx for the PLT call, aligns esp to 16 and calls _st_thread_main, instead of returning a second time from the save in st_thread_create. The entry marks the return address undefined in its CFI, so DWARF unwinds such as glibc backtrace() and C++ exceptions end there, and starts with ebp at 0, which ends frame-pointer walks. The utest of the thread entry now runs on Linux i386. --------- Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Linux arm gets _st_md_thread_start in md_linux2.S and MD_INIT_THREAD_ENTRY in md.h. st_thread_create still saves the context as a template, then the macro aligns the saved SP to 8, sets the saved link register, which the restore branches to with bx, to the entry, and clears the saved fp. The first restore of the thread enters the entry on the new stack, which calls _st_thread_main, instead of returning a second time from the save in st_thread_create. The entry is ARM code and the C code is usually Thumb: bx picks the mode from the address of the entry, and the linker turns its bl into blx. glibc backtrace() and C++ exceptions unwind with ARM EHABI on arm. The entry pushes two null words, which keep the 8-byte alignment, and its EHABI entry says they are the saved fp and lr, so the walk lists the entry as the last frame and stops at its null return address. Its CFI marks the return address undefined for DWARF unwinders such as gdb, and fp, r7 and lr start at 0. GCC leaves unwind tables out of C code on arm, so backtrace() found no frame at all in a coroutine. The Linux library and the backtrace tool now build with -funwind-tables, which x86_64 and aarch64 have by default, so the walk goes through the frames of ST up to the entry. The utest of the thread entry now runs on Linux arm, and the backtrace tool passes there. --------- Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Linux riscv64 gets _st_md_thread_start in md_linux2.S and MD_INIT_THREAD_ENTRY in md.h. st_thread_create still saves the context as a template, then the macro aligns the saved SP to 16, sets the saved return address ra, which the restore jumps to, to the entry, and clears the saved frame pointer s0. The first restore of the thread enters the entry on the new stack, which calls _st_thread_main, instead of returning a second time from the save in st_thread_create. The entry's CFI marks the return address undefined, so glibc backtrace() and C++ exceptions stop at the entry, and s0 and ra start at 0 for frame-pointer walks. The utest of the thread entry now runs on Linux riscv64, and the backtrace tool ends at the entry there. --------- Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Linux loongarch64 gets _st_md_thread_start in md_linux2.S and MD_INIT_THREAD_ENTRY in md.h. st_thread_create still saves the context as a template, then the macro aligns the saved SP to 16, sets the saved return address ra, which the restore jumps to, to the entry, and clears the saved frame pointer fp. The first restore of the thread enters the entry on the new stack, which calls _st_thread_main, instead of returning a second time from the save in st_thread_create. The entry's CFI marks the return address undefined, so glibc backtrace() and C++ exceptions stop at the entry, and fp and ra start at 0 for frame-pointer walks. The utest of the thread entry now runs on Linux loongarch64, and the backtrace tool ends at the entry there. --------- Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Linux mips64 and mips64el (n64) get _st_md_thread_start in md_linux2.S and MD_INIT_THREAD_ENTRY in md.h. st_thread_create still saves the context as a template, then the macro aligns the saved SP to 16, sets the saved return address ra, which the restore jumps to, to the entry, and clears the saved frame pointer fp. The first restore of the thread enters the entry on the new stack, which calls _st_thread_main, instead of returning a second time from the save in st_thread_create. The restore jumps by ra and leaves t9 and gp to the creator, so the entry finds its own address with bal, computes its own gp from it, and calls _st_thread_main through t9, as PIC code expects. It uses explicit GOT relocations, because the file is assembled without -fPIC, which would turn the .cpsetup and dla macros into absolute addresses, but also goes into the shared library. The entry's CFI marks the return address undefined, so glibc backtrace() and C++ exceptions stop at the entry, and fp and ra start at 0 for frame-pointer walks. The utest of the thread entry now runs on Linux mips64 and mips64el, and the backtrace tool ends at the entry there instead of walking on into the creator's frames. --------- Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Linux mips and mipsel (o32) get _st_md_thread_start in md_linux2.S and MD_INIT_THREAD_ENTRY in md.h. st_thread_create still saves the context as a template, then the macro aligns the saved SP to 8, as o32 wants, sets the saved return address ra, which the restore jumps to, to the entry, and clears the saved frame pointer fp. The first restore of the thread enters the entry on the new stack, which calls _st_thread_main, instead of returning a second time from the save. The restore jumps by ra and leaves t9 and gp to the creator, so the entry finds its own address with bal, computes its own gp from it with _gp_disp, and calls _st_thread_main through t9, as PIC code expects, after reserving the 16-byte argument save area that o32 callees may use. It uses explicit relocations, because the file is assembled without -fPIC, which would turn .cpload into an absolute address, but also goes into the shared library. The entry's CFI marks the return address undefined, so glibc backtrace() and C++ exceptions stop at the entry, and fp and ra start at 0 for frame-pointer walks. The utest of the thread entry now runs on Linux mips and mipsel, and the backtrace tool ends at the entry there instead of walking on into the creator's frames. The JB_* defines of both MIPS branches now name the slots the code uses: ra 1, gp 2, fp 11. --------- Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The riscv64 context switch in md_linux2.S saved only the integer registers s0-s11, but the ABI also makes fs0-fs11 callee-saved. A coroutine that kept doubles in them across a switch got the values another coroutine left behind, so the utest of live values across switches failed under qemu-riscv64. _st_md_cxt_save and _st_md_cxt_restore now store and load fs0-fs11 in twelve new slots after the existing ones, so the SP, ra and s0 slots that the thread entry patches do not move. They use fsd/fld when the CPU has the D extension, fsw/flw with only F, and nothing with soft float. The riscv64 jmpbuf in md.h grows from 22 to 26 slots to hold them; the other CPUs keep their size. --------- Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The MIPS context switch in md_linux2.S saved only the integer registers, but both ABIs also make some FP registers callee-saved: the even $f20-$f30 on o32, and $f24-$f31 on n64. A coroutine that kept doubles in them across a switch got the values another coroutine left behind, so the utest of live values across switches failed under qemu on mips, mipsel, mips64 and mips64el. _st_md_cxt_save and _st_md_cxt_restore now store and load them with sdc1/ldc1 in new slots after the existing ones, so the SP, ra, gp and fp slots that the thread entry patches do not move. Soft float builds save none. On o32 the even registers as doubles are right for every FP mode: with -mfp32 each covers an even/odd pair, and with -mfpxx (the Ubuntu default) or -mfp64 only the even ones are callee-saved. The o32 jmpbuf in md.h grows from 22 to 24 slots and is 8-byte aligned, as sdc1 requires. n64 needs 20 slots and keeps the shared 22. The SIZE comment now lists both. --------- Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
tools/porting printed _MIPSEL whenever __mips__ was defined, but only little-endian MIPS defines it, so the tool did not compile on mips and mips64. It now prints _MIPSEL and _MIPSEB each under its own #ifdef, so every MIPS byte order builds and shows its endianness. --------- Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The loongarch64 _st_md_cxt_save and _st_md_cxt_restore kept only the integer registers, so a coroutine's doubles in $f24-$f31 changed across a context switch. They now save and restore $f24-$f31 in jmpbuf slots 12-19, after the existing slots, with fst.d/fld.d on a 64-bit FPU, fst.s/fld.s on a 32-bit one, and nothing with soft float. 20 slots fit the existing jmpbuf. --------- Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The arm _st_md_cxt_save stores 10 core registers and, with VFP, d8-d15: 104 bytes, but the jmpbuf was 88 bytes. The jmpbuf is the last field of the thread, and the keys follow it on the stack, so every save overwrote keys 0-3, and st_thread_getspecific read back NULL after a switch. The arm jmpbuf now has 38 slots, which also hold wr10-wr15 with iWMMXt, and md.h fails to compile on arm if the jmpbuf is smaller than what the save stores. A new utest checks on every CPU that the save writes nothing past the jmpbuf. --------- Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…gwin64. Every platform but Cygwin64 now has an assembly entry, so st_thread_create no longer lets a new coroutine return a second time from _st_md_cxt_save. The save only fills the jmpbuf as a template, and MD_INIT_THREAD_ENTRY starts the coroutine in the entry on its new stack. Cygwin64, which has no entry, keeps the old start under #ifndef MD_INIT_THREAD_ENTRY. The TODO and the utest comments about the old start are updated. --------- Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
tools/backtrace now also checks the outermost frame of a backtrace inside a coroutine: it must be the return address in _st_md_thread_start, after its call to _st_thread_main, compared by address with the entry, so no frame of the creator, such as st_thread_create or main, follows it. It runs wherever ST has the entry, every platform but Cygwin64, and not with the builtin return addresses, which hold only the caller. --------- Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ows. An MSVC debug link is incremental by default, so the address of _st_md_thread_start in C is a jmp rel32 thunk, not the entry, and the backtrace stop check compared the outermost frame with the thunk. The tool now follows the thunk to the entry. --------- Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
….9.3 A new doc, docs/coroutine_entry.md, describes the assembly entry that starts every new thread on Linux, macOS and Windows x64, with a table of the SP alignment, the nulled registers and the unwind marks per CPU, and the save-then-patch start as the old one that only Cygwin64 still uses. docs/win64_coroutine.md stays about Windows x64 and links to it, and so do sched.c and the README. The README tells how to run the utest and the tools on every Linux CPU with QEMU in Docker, and on macOS x86_64 under Rosetta 2. Bump ST to v1.9.3. --------- Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ebuilds it. auto/qemu.sh tagged its image st-qemu:ubuntu24.04 and built it only when that tag was missing, so an edit to auto/qemu/Dockerfile was silently ignored. The tag is now st-qemu:<hash>, a short git hash-object of the path and content of every file in auto/qemu/, the build context, as they are on disk. A changed file builds a new image, and a reverted one reuses the old tag without a build. The image stays local; nothing is pushed to a registry. After a build, the script removes the older st-qemu images, about 3 GB each, except one that a container still uses, which docker rmi refuses. The README names the hash tag. --------- Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
qemu-x86_64 8.2.2 crashes in pthread_getattr_np on the main thread, so auto/qemu.sh skipped the primordial stack test and the lifecycle tool on x86_64. On a Docker host of another CPU, such as Apple Silicon, x86_64 now runs in st-qemu:<hash>-amd64, the amd64 stage of the same Dockerfile with only make and g++, started with --platform linux/amd64 from the amd64 ubuntu:24.04. Docker emulates the whole container, the native g++ builds x86_64, and every test and tool runs there, so the skips are gone. The hash tag covers both stages. A new run, asan, builds the utest and the tools with ASAN and MALLOC_STACK in LINUX_<cpu>_asan_DBG, for a CPU that runs natively in its container: aarch64, and x86_64 in the amd64 image. Every other CPU prints a SKIP line for it. It is part of all. The README and the script header describe both. --------- Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The QEMU image had no debugger, and the only way into it was a run of auto/qemu.sh, so a failure on another CPU could not be looked at by hand. The cross image now has gdb-multiarch, in a layer of its own so the cross compilers stay cached; the change gives the image a new hash tag. auto/qemu.sh <cpu> shell opens a bash in the image with the CPU's toolchain, TARGETDIR and ST_TOOL_RUN, plus ST_QEMU_USER and ST_QEMU_SYSROOT, or runs a command given after it. The header and the README show how to start a program under the gdb stub of qemu-<cpu> -g <port> and attach gdb-multiarch to it. The x86_64 shell uses the cross image and qemu-x86_64, because gdb cannot trace a program in the container Docker emulates for x86_64 on a host of another CPU. --------- Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… once. Two CPUs started at once in one checkout clashed: both pointed the shared obj link at their own build folder, and both built the tools in tools/<name>/, so one CPU linked or ran the other's library or tool binary. When the environment or the command line sets TARGETDIR, as auto/qemu.sh and auto/darwin.sh do: * The root Makefile writes nothing outside that folder: no obj link and no st.pc. * utest/Makefile builds the gtest objects and st_utest there, from its libst.a. * Every tool Makefile includes the new tools/tool.mk, which takes the library from that folder and builds the tool in its tools/<name>; auto/tools.sh runs it there. * auto/qemu.sh and auto/darwin.sh run st_utest from that folder. Without TARGETDIR nothing changes: obj, st.pc, obj/st_utest, tools/<name>/<name>, and the MSVC build. --------- Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… summary. Testing every Linux CPU took ten calls of auto/qemu.sh, one after another, about four and a half minutes. ./auto/qemu.sh all [utest|tools|tools-malloc|asan|all] now: * Builds the cross and amd64 images once, before any CPU starts. * Runs each CPU in its own container and build folder, at most ST_QEMU_JOBS at once, 5 by default, which finished as soon as 10, because x86_64 in the emulated amd64 container takes the longest. * Writes each CPU's output to /tmp/st-qemu-all/<cpu>.log, kept until the next run. * Prints one row per CPU, PASS or FAIL with the seconds of each run, and the wall time, then the end of the log of each CPU that failed, and exits 1 when any CPU failed. One CPU per call works as before. --------- Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The QEMU image ran every CPU with qemu-user 8.2.2, the version of Ubuntu 24.04. The Dockerfile now has a stage from Ubuntu 26.04, which installs qemu-user 10.2.1. Its binaries are statically linked, so the cross image copies only the ten it uses, and keeps the cross compilers of Ubuntu 24.04. Every CPU passes with it, and the eight CPUs that run under qemu-user take about as long as before. --------- Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…scv64 passes. IoOpenTest.FifoWriterWaitsForSlowReader failed on Linux riscv64 under qemu-user, in about 1 of 4 full utest runs, and in every run after FifoReaderSeesEofBeforeAnyWriter or FifoReaderWaitsForCommandThenSeesEof. A timeout counts from the scheduler's clock, which only moves when the scheduler runs. The test started the consumer coroutine, then built the 1 MB stream before the write. On an emulated riscv64, building it takes about 90 ms, so the clock was 90 to 97 ms old when st_write began to wait, and its 100 ms timeout expired after one 10 ms wait, with only one pipe buffer, 64 KB, written. The test now builds the stream first, then calls st_usleep(0) so the scheduler updates its clock, then starts the consumer. On riscv64 it passes 30 of 30 alone, 30 of 30 with the IoOpenTest suite, and 20 of 20 full runs; it passes on every Linux CPU, macOS arm64, and macOS x86_64. --------- Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## srs #42 +/- ##
=======================================
Coverage 96.01% 96.01%
=======================================
Files 8 8
Lines 1129 1129
Branches 287 286 -1
=======================================
Hits 1084 1084
Partials 45 45 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…indows udp passes. ST counts a timeout from its cached clock, which can be older than the st_utime() the tool reads as the start, so a timeout could end before the tool's start plus the timeout, as udp did on Windows CI. Yielding refreshes the cached clock after the start, as tools/poll already does. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The new actions-test-qemu job runs the utest, the tools, the tools with MALLOC_STACK, and ASAN where native, on all 10 Linux CPUs. One job, not one per CPU, so the image of the cross compilers is built once; the summary of auto/qemu.sh shows each CPU's result. actions-test-final now requires it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…plit in two jobs. The QEMU job was compile-bound: about 740 s of mostly compiling on a 4-core runner. Now: - ST_QEMU_CPUS picks the CPUs of auto/qemu.sh all. CI runs the eight CPUs no native job runs; x86_64 and aarch64 are in the utest jobs, and linux-x64 gains the ASAN utest that only the QEMU x86_64 run had. - Every compile in the QEMU images goes through ccache, with the cache on the host in ST_QEMU_CCACHE, ~/.cache/st-qemu-ccache by default. all prints the ccache stats. CI keeps the cache with actions/cache. - Two jobs of four CPUs, actions-test-qemu-misc and actions-test-qemu-mips, each on its own runner. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
winlinvip
added a commit
to ossrs/srs
that referenced
this pull request
Oct 10, 2026
Update the AI skills for the State Threads coroutine entry and the QEMU tests of [state-threads#42](ossrs/state-threads#42). No SRS media server or proxy code changes; only skills, and the version and changelog. ## State Threads code map and docs router ST now starts every new coroutine in the `_st_md_thread_start` assembly entry on every platform but Cygwin64, and tests 10 Linux CPUs with QEMU. The skills that route to ST say so: - `internal-codemap-for-srs` `references/state-threads.md`: - `md.h` has `MD_INIT_THREAD_ENTRY`, and each `md_*.S` has the entry. Cygwin64 has none and keeps the old start. - Lists `st_utest_entry.cpp`, and notes that `tools/backtrace` stops at the entry. - Tests another CPU with `auto/qemu.sh <cpu>` (QEMU user mode in Docker) or `auto/darwin.sh <cpu>` (macOS, x86_64 under Rosetta 2). - `internal-docs-for-srs`: routes to the new `docs/coroutine_entry.md` for the entry on every CPU, keeps `docs/win64_coroutine.md` for Windows x64, and notes the README section on QEMU and Rosetta. ## Autopilot - `srs-autopilot` decides open choices itself: it picks the best option, logs it as a decision, and goes on. It stops only for a dangerous operation or a severe, unexpected situation. - Each workflow (plan, track, run, review) moves to its own file in `references/`, so a session loads only the one it uses. ## Docs content - `srs-develop` `develop-code.md`: updating the English docs bundled in `skills/internal-docs-for-srs/` is done with the develop workflow. Translation, the website framework and releases stay with the private website skill. --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR starts every new coroutine in an assembly entry,
_st_md_thread_start, on every Linux and macOS CPU, the way Windows x64 already does since v1.9.2. It also adds a QEMU toolchain that builds and tests ST for 10 Linux CPUs on one machine.Coroutine entry
Before,
st_thread_createsaved the context, and a new coroutine started by returning a second time from that save, on a new stack. That is undefined behavior: the compiler may still use the creator's frame or registers after the save, the coroutine inherits the creator's callee-saved registers, and backtraces and unwinders have no clean bottom frame. It worked only because the code after the save is trivial.st_thread_createstill saves the context, but only as a template.MD_INIT_THREAD_ENTRYinmd.hthen sets the saved SP, PC and frame pointer, so the first switch to the coroutine enters_st_md_thread_starton its own stack, which calls_st_thread_main.md_linux.S,md_linux2.Sandmd_darwin.Sfor Linux x86_64, i386, aarch64, arm, riscv64, loongarch64, mips, mipsel, mips64 and mips64el, and macOS arm64 and x86_64. Each one aligns the stack as the ABI expects, nulls the frame pointer and return address, and marks the return address undefined in the CFI (and EHABI on arm), so a backtrace ends at the entry.#ifndef MD_INIT_THREAD_ENTRY. It is not tested.Context save fixes found by QEMU
fs0–fs11, MIPS the callee-saved FP registers (o32 and n64), and loongarch64$f24–$f31. Before, a coroutine switch lost them. Soft-float builds save none.md.hnow fails to compile if a jmpbuf is too small, and a utest checks on every CPU that the save writes nothing past it.JB_*defines are fixed, and theportingtool builds on big-endian MIPS.threadtool uses a 16 KB frame on the default stack, so it passes on macOS x86_64, where___chkstk_darwinfailed on the larger one.Tests
utest/st_utest_entry.cppchecks the saved PC and frame pointer accessors (MD_GET_PC,MD_GET_FP) on every CPU, and that a new coroutine starts in the entry.tools/backtracechecks that the outermost frame of a backtrace in a coroutine is inside_st_md_thread_start, on every platform but Cygwin64. On Windows it follows the MSVC incremental-link thunk.tools/backtracebuild with-funwind-tables, so the backtrace works on arm and MIPS.QEMU and Rosetta toolchain
auto/qemu.sh <cpu> [run]cross-builds ST withg++-<cpu>in oneubuntu:24.04container and runs the utest and tools with qemu-user 10.2. x86_64 runs natively in an amd64 container instead. Runs areutest,tools,tools-malloc,asan(x86_64 and aarch64) andall.auto/qemu.sh allruns every CPU in its own container,ST_QEMU_JOBSat once (5 by default), and prints a per-CPU table. All 10 CPUs pass in about 90 s.auto/qemu/, so a Dockerfile change builds a new image. After a build, the script removes the olderst-qemutags.auto/qemu.sh <cpu> shellopens a shell in the image, withgdb-multiarchto debug a CPU through qemu-user's gdb stub.TARGETDIRbuilds every output in its own folder, so several CPUs can build in one checkout at once. Default builds are unchanged.auto/darwin.sh arm64|x86_64builds and runs ST on macOS, x86_64 under Rosetta 2.