From 10246c8d26407505aeee7c542f3e82821dfbc988 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Tue, 1 Sep 2026 17:26:22 -0400 Subject: [PATCH 1/2] Compiled the module manager C sources, which no check had ever built #672 corrected this script's assembly glob and brought the module ports into the count, but only their assembly. Their C stayed outside every check: 27 files of portable module manager under common_modules, plus the per-port code under ports_module//gnu/module_manager/src. By this script's own standard -- a port simply absent from the count reads as covered -- 293 files across nine Arm module ports were compiled by nothing, with either compiler. Each module port ships its own tx_port.h and txm_module_port.h carrying the control-block extensions the dispatch code needs, so a port is compiled against its own headers rather than the base port's. Two details the ports themselves dictate: An SMP port's control blocks come from common_smp. Pairing cortex_a35_smp with the single-core headers hid _tx_thread_smp_protect and _tx_thread_smp_unprotect behind implicit declarations and lost tx_thread_smp_core_executing from TX_THREAD, so fourteen files reported errors for a port that builds correctly. The TrustZone ports carry cmse_nonsecure_entry, which needs -mcmse to be honoured rather than ignored. tx_thread_secure_stack.c also carries GCC's optimize attribute, which clang does not implement; that divergence is suppressed by name, for a file GCC builds cleanly. All nine ports compile: 293 of 293. Verified that the stage fails as intended by injecting a defect into a throwaway worktree -- a defect in common_modules is reported under every port, one in a port's own source only under that port. Assisted-by: Claude Code (Opus 5) --- scripts/check_clang.sh | 79 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 3 deletions(-) diff --git a/scripts/check_clang.sh b/scripts/check_clang.sh index f8ced1dd0..144a039f2 100755 --- a/scripts/check_clang.sh +++ b/scripts/check_clang.sh @@ -14,11 +14,12 @@ # SPDX-License-Identifier: MIT and CC0-1.0 ############################################################################## -# Builds the Arm ports with an LLVM based toolchain, in five stages: assemble +# Builds the Arm ports with an LLVM based toolchain, in six stages: assemble # every assembly source of every Arm gnu port, assemble again the parts guarded # by feature macros, compile the common C sources for one core per architecture -# profile, then link the example builds, both the script-driven ones and those -# driven by CMake. Only the linking stages need a target C library. +# profile, compile the module manager C sources once per Arm module port, then +# link the example builds, both the script-driven ones and those driven by +# CMake. Only the linking stages need a target C library. # # scripts/check_clang.sh # clang from PATH # scripts/check_clang.sh --clang /path/to/clang @@ -319,6 +320,78 @@ if [ "$asm_only" -eq 0 ]; then fi +# -------------------------------------------------------------------------- +if [ "$asm_only" -eq 0 ]; then + say "" + say "== Module manager C sources, one per Arm module port ==" + + # Correcting the assembly glob above brought the module ports into the count, + # but only their assembly. Their C stayed outside every check: 27 files of + # portable module manager under common_modules, plus the per-port code under + # ports_module//gnu/module_manager/src. Nothing compiled either one, so + # by this script's own standard they read as covered while being unbuilt. + # + # Each module port ships its own tx_port.h and txm_module_port.h, carrying the + # control-block extensions the dispatch code needs, so a port is compiled + # against its own headers rather than the base port's. + module_skipped="" + for dir in ports_module/*/gnu/module_manager/src; do + [ -d "$dir" ] || continue + core="$(echo "$dir" | cut -d/ -f2)" + spec="${PORT_TARGET[$core]:-}" + if [ -z "$spec" ]; then + module_skipped="$module_skipped $core" + continue + fi + + inc="ports_module/$core/gnu/inc" + if [ ! -f "$inc/tx_port.h" ] || [ ! -f "$inc/txm_module_port.h" ]; then + module_skipped="$module_skipped $core(headers)" + continue + fi + + # shellcheck disable=SC2086 + set -- $spec + target="$1"; cpu="$2"; shift 2; extra="$*" + + # An SMP port's control blocks come from common_smp; pairing it with the + # single-core headers hides _tx_thread_smp_protect behind an implicit + # declaration instead of compiling the port that is actually shipped. + case "$core" in + *_smp) kernel_inc="common_smp/inc" ;; + *) kernel_inc="common/inc" ;; + esac + + # The TrustZone ports carry cmse_nonsecure_entry, which needs -mcmse to be + # honoured rather than ignored. Those files also carry GCC's optimize + # attribute, which clang does not implement and warns about; that is a + # toolchain divergence in a file GCC builds cleanly, not a port defect. + port_extra="" + if [ -f "$inc/tx_secure_interface.h" ]; then + port_extra="-mcmse -Wno-unknown-attributes" + fi + + count=0; bad=0 + for src in common_modules/module_manager/src/*.c "$dir"/*.c; do + [ -f "$src" ] || continue + count=$((count + 1)) + output="$("$CC" --target="$target" -mcpu="$cpu" $extra $port_extra \ + -I"$inc" -I"$kernel_inc" -Icommon_modules/inc \ + -Icommon_modules/module_manager/inc -c "$src" -o /dev/null 2>&1)" + if [ -n "$output" ]; then + fail "$core: $src" + echo "$output" | grep "error:" | head -3 | sed 's/^/ /' + bad=$((bad + 1)); failures=$((failures + 1)) + fi + done + say " $core: $((count - bad)) of $count compiled" + done + if [ -n "$module_skipped" ]; then + say " no target mapping, skipped:$module_skipped" + fi +fi + + # -------------------------------------------------------------------------- if [ "$no_examples" -eq 0 ]; then say "" From e65e44856a3cd0ff17cabba5c4342253061c77e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Wed, 9 Sep 2026 11:53:49 -0400 Subject: [PATCH 2/2] Ran the module manager stage against the sources that reach it Three corrections to the new stage, all found by running it against dev rather than against the tree it was written on. The workflows did not trigger on common_modules. Both check_clang.yml and check_gcc.yml list ports_module but not common_modules, and the portable module manager under it is the larger half of what the stage compiles -- 28 of the 31 to 37 files each port builds, plus two include directories. A change there left the stage unrun, which is the same "absent from the count reads as covered" that the stage exists to close. Both lists gain common_modules, and they stay identical to each other as the comment in each asks. A deliberate deprecation notice read as a build failure. Since this PR was opened, txm_module_manager_absolute_load.c gained a #pragma message steering callers to the extended entry point. The stage treats any compiler output as a failure, so that one notice failed every port: nine failures on a tree where nothing is wrong. Pragma messages are now waived for the stage, because a notice to callers is not a defect in the file that carries it. That failure also printed nothing. Both C stages report by grepping the output for "error:", so a diagnostic that is not an error produced a bare FAIL line with no reason under it, and the only way to learn the reason was to reproduce the compile by hand. Both stages now fall back to showing what the compiler actually said. The TrustZone attribute waiver is narrowed to the one file that needs it. tx_thread_secure_stack.c carries GCC's optimize attribute, which clang does not implement; it is the only file among the 300-odd this stage compiles that does. Waiving the warning for the whole port would have swallowed a stray unknown attribute anywhere else in it. Verified with the same toolchain CI uses, ATfE 22.1.0: the full script passes, and every port compiles every file. cortex_a35 31/31 cortex_a35_smp 31/31 cortex_a7 34/34 cortex_m0+ 33/33 cortex_m23 37/37 cortex_m3 33/33 cortex_m33 37/37 cortex_m4 33/33 cortex_m7 33/33 The counts are each one higher than this PR first reported, because txm_module_manager_absolute_load_extended.c has landed since. The stage picked it up with no edit, which is what globbing the directories was for. Assisted-by: Claude Code (Opus 5) --- .github/workflows/clang_check.yml | 6 +++++ .github/workflows/gcc_check.yml | 12 +++++++++ scripts/check_clang.sh | 41 +++++++++++++++++++++++++------ 3 files changed, 52 insertions(+), 7 deletions(-) diff --git a/.github/workflows/clang_check.yml b/.github/workflows/clang_check.yml index 2c90c790b..53b60bfeb 100644 --- a/.github/workflows/clang_check.yml +++ b/.github/workflows/clang_check.yml @@ -20,6 +20,12 @@ on: - "CMakeLists.txt" - "cmake/**" - "common/**" + # common_modules/ holds the portable module manager that the module + # manager stage compiles once per Arm module port -- the larger half of + # what that stage builds. Without it a change there leaves the stage + # unrun, which is the same "absent from the count reads as covered" the + # stage exists to close. + - "common_modules/**" - "common_smp/**" - "ports/**" - "ports_arch/**" diff --git a/.github/workflows/gcc_check.yml b/.github/workflows/gcc_check.yml index 31e1ec346..eabbd0a7e 100644 --- a/.github/workflows/gcc_check.yml +++ b/.github/workflows/gcc_check.yml @@ -42,6 +42,12 @@ on: - "CMakeLists.txt" - "cmake/**" - "common/**" + # common_modules/ holds the portable module manager that the module + # manager stage compiles once per Arm module port -- the larger half of + # what that stage builds. Without it a change there leaves the stage + # unrun, which is the same "absent from the count reads as covered" the + # stage exists to close. + - "common_modules/**" - "common_smp/**" - "ports/**" - "ports_arch/**" @@ -67,6 +73,12 @@ on: - "CMakeLists.txt" - "cmake/**" - "common/**" + # common_modules/ holds the portable module manager that the module + # manager stage compiles once per Arm module port -- the larger half of + # what that stage builds. Without it a change there leaves the stage + # unrun, which is the same "absent from the count reads as covered" the + # stage exists to close. + - "common_modules/**" - "common_smp/**" - "ports/**" - "ports_arch/**" diff --git a/scripts/check_clang.sh b/scripts/check_clang.sh index 144a039f2..57c4d63af 100755 --- a/scripts/check_clang.sh +++ b/scripts/check_clang.sh @@ -311,7 +311,14 @@ if [ "$asm_only" -eq 0 ]; then -Iports/"$core"/gnu/inc -Icommon/inc -c "$src" -o /dev/null 2>&1)" if [ -n "$output" ]; then fail "$core: $src" - echo "$output" | grep "error:" | head -3 | sed 's/^/ /' + # Show the error lines when there are any, and otherwise + # whatever the compiler did say -- a FAIL with nothing under it + # sends the reader off to reproduce the command by hand. + if echo "$output" | grep -q "error:"; then + echo "$output" | grep "error:" | head -3 | sed 's/^/ /' + else + echo "$output" | head -3 | sed 's/^/ /' + fi bad=$((bad + 1)); failures=$((failures + 1)) fi done @@ -362,25 +369,45 @@ if [ "$asm_only" -eq 0 ]; then *) kernel_inc="common/inc" ;; esac - # The TrustZone ports carry cmse_nonsecure_entry, which needs -mcmse to be - # honoured rather than ignored. Those files also carry GCC's optimize - # attribute, which clang does not implement and warns about; that is a - # toolchain divergence in a file GCC builds cleanly, not a port defect. + # The TrustZone ports carry cmse_nonsecure_entry, which needs -mcmse to + # be honoured rather than ignored. port_extra="" if [ -f "$inc/tx_secure_interface.h" ]; then - port_extra="-mcmse -Wno-unknown-attributes" + port_extra="-mcmse" fi count=0; bad=0 for src in common_modules/module_manager/src/*.c "$dir"/*.c; do [ -f "$src" ] || continue count=$((count + 1)) + # tx_thread_secure_stack.c carries GCC's optimize attribute, which + # clang does not implement and warns about. That is a toolchain + # divergence in a file GCC builds cleanly, so it is waived for that + # file alone -- a stray unknown attribute anywhere else in the port + # must still be reported. + src_extra="" + case "$src" in + */tx_thread_secure_stack.c) src_extra="-Wno-unknown-attributes" ;; + esac + + # A #pragma message is a deliberate notice to callers, not a defect + # in the file that carries it. txm_module_manager_absolute_load.c + # deprecates itself in favour of the extended entry point, and this + # stage compiles it once per port. output="$("$CC" --target="$target" -mcpu="$cpu" $extra $port_extra \ + $src_extra "-Wno-#pragma-messages" \ -I"$inc" -I"$kernel_inc" -Icommon_modules/inc \ -Icommon_modules/module_manager/inc -c "$src" -o /dev/null 2>&1)" if [ -n "$output" ]; then fail "$core: $src" - echo "$output" | grep "error:" | head -3 | sed 's/^/ /' + # Show the error lines when there are any, and otherwise + # whatever the compiler did say -- a FAIL with nothing under it + # sends the reader off to reproduce the command by hand. + if echo "$output" | grep -q "error:"; then + echo "$output" | grep "error:" | head -3 | sed 's/^/ /' + else + echo "$output" | head -3 | sed 's/^/ /' + fi bad=$((bad + 1)); failures=$((failures + 1)) fi done