mirror of
https://github.com/eclipse-threadx/threadx.git
synced 2026-10-06 06:59:08 +08:00
scripts/check_clang.sh globbed ports_module/*/gnu/src, which does not exist --
the module ports keep their assembly in module_manager/src. The [ -d ] guard
skipped it in silence, so 116 assembly files across nine Arm module ports were
assembled by no check, with either compiler, in the script whose own comments
state three times that "a port that is simply absent from the count reads as
covered". Stage 1 goes from 724 of 724 to 840 of 840; the feature-macro stage
had the same gap and goes from 412 files to 469.
Correcting the path exposed five defects, and only one of them was a build
failure. The other four assembled cleanly and did the wrong thing, because GAS
runs the C preprocessor on .S and not on .s:
ports_smp/cortex_a7_smp/gnu/src/tx_thread_smp_unprotect.s, the only .s in a
directory of twenty-one .S, ignored all four of its own feature macros. It
wrote the caller's LR into the protection structure on every unprotect -- a
store guarded by TX_MPCORE_DEBUG_ENABLE -- sent an unconditional SEV, and
returned through both BX lr and MOV pc, lr. Its cortex_a5_smp and
cortex_a9_smp siblings are .S.
ports_module/cortex_m33/.../tx_thread_stack_build.s emitted both arms of an
#ifdef TX_SINGLE_MODE_SECURE, so the non-secure LR value overwrote the secure
one and the secure build got the wrong frame.
ports_module/cortex_m23/.../tx_thread_context_{save,restore}.S carried the
POP {r0, lr} that check_clang.sh's own comment describes as the reason the
feature-macro stage exists. The 16-bit Thumb POP takes r0-r7 and pc only.
The identical fix already sits in ports/cortex_m23/gnu/src; the module copy
never got it because nothing scanned it.
ports_module/cortex_m23/.../tx_thread_secure_stack_initialize.S used MOV
rather than MOVS for an 8-bit immediate, latent behind TX_SINGLE_MODE_SECURE.
Both siblings in the same directory already use MOVS.
ports_module/cortex_a7/gnu/module_manager/src is the one that failed to
assemble, on GCC 14.3 as well as on LLVM: #define SYS_MODE was never
expanded, so #SYS_MODE reached the assembler as an undefined symbol.
Twenty-nine .s files under gnu trees are renamed to .S. Every one of them is
already named .S by the build scripts that compile it, so this repairs those
scripts rather than churning them -- ports_module/cortex_a7's build_threadx.bat
names all eighteen with a capital S, and works today only on a case-insensitive
filesystem. Renaming rather than converting the #defines to GNU assignments is
what fixes the #ifdef blocks as well as the constants; the assignments would
have fixed two files and left twenty-seven silently ignoring their macros.
Files with no preprocessor directives are left as .s: they are not broken, and
check_ports.sh gains a check that keeps them that way. Only the gnu trees are
checked there -- the IAR, Arm Compiler 5 and Keil assemblers preprocess .s
themselves, and about three hundred files in this repository rely on that.
Verified with both toolchains on the same tree: 840 of 840 assembled by
ATfE 22.1.0 and by arm-gnu-toolchain 14.3.rel1, all five stages of
check_clang.sh green, and check_ports.sh green including the reproducibility
check. The new check was shown to fail by planting a copy of the file it was
written for.
No regression test accompanies this. The assembly it covers is executed by no
host test, and the check itself going from 724 files to 840 is the coverage
AGENTS.md asks for -- together with the new check_ports.sh section, which is
what stops the class recurring.
Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
293 lines
12 KiB
Bash
Executable File
293 lines
12 KiB
Bash
Executable File
#!/bin/bash
|
|
##############################################################################
|
|
# Copyright (C) 2026 Eclipse ThreadX contributors
|
|
#
|
|
# This program and the accompanying materials are made available under the
|
|
# terms of the MIT License which is available at
|
|
# https://opensource.org/licenses/MIT.
|
|
#
|
|
# AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5).
|
|
# The AI-generated portions may be considered public domain (CC0-1.0)
|
|
# and not subject to the project's licence. The human contributor has
|
|
# reviewed and verified that the code is correct.
|
|
#
|
|
# SPDX-License-Identifier: MIT and CC0-1.0
|
|
##############################################################################
|
|
|
|
# Consistency checks for the port trees. Run it before cutting a release, or
|
|
# any time the ports have been touched:
|
|
#
|
|
# scripts/check_ports.sh
|
|
#
|
|
# Options:
|
|
# --no-regen Skip the reproducibility check, which needs a clean tree.
|
|
# --quiet Print only failures and the summary.
|
|
#
|
|
# Exit status is 0 when every check passes and 1 otherwise, so the same
|
|
# command serves CI and the command line.
|
|
#
|
|
# Each check exists because a real defect reached the repository through it:
|
|
#
|
|
# 1. Reproducibility. The Cortex-M ports are generated by the copy scripts,
|
|
# but fixes were applied to the generated copies instead of the source for
|
|
# eight months. The next run of those scripts would have reverted them.
|
|
#
|
|
# 2. Preprocessor balance. A fix left ports/cortex_m85/iar/inc/tx_port.h with
|
|
# one more #endif than #if, so that header could not compile.
|
|
#
|
|
# 3. Code at file scope. A fix left a second, headerless copy of a function
|
|
# body in ports/cortex_m4/ac6/inc/tx_port.h, which placed statements
|
|
# outside any function. See issue 569.
|
|
#
|
|
# 4. Lowercase .s under a gnu tree. GAS preprocesses .S and not .s, so a
|
|
# .s file's #ifdef blocks are assembled whichever way the macro is set.
|
|
# Twenty-nine files were in that state, including one non-module SMP
|
|
# kernel port, and only one of them failed to assemble.
|
|
#
|
|
# The last section reports, without failing, on port families that have no copy
|
|
# script and so cannot be checked for reproducibility.
|
|
#
|
|
# Headers under example_build are skipped: those trees vendor third party SDK
|
|
# code, which is not ours to hold to these rules.
|
|
|
|
set -u
|
|
|
|
cd "$(dirname "$(realpath "$0")")/.."
|
|
|
|
regen=1
|
|
quiet=0
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
--no-regen) regen=0 ;;
|
|
--quiet) quiet=1 ;;
|
|
-h|--help) sed -n '17,30p' "$0"; exit 0 ;;
|
|
*) echo "Unknown option: $arg" >&2; exit 2 ;;
|
|
esac
|
|
done
|
|
|
|
failures=0
|
|
|
|
say() { [ "$quiet" -eq 1 ] || echo "$@"; }
|
|
fail() { echo " FAIL: $*"; failures=$((failures + 1)); }
|
|
|
|
# --------------------------------------------------------------------------
|
|
# 1. The generated ports must be reproducible from ports_arch.
|
|
# --------------------------------------------------------------------------
|
|
say ""
|
|
say "== Generated ports are reproducible from ports_arch =="
|
|
|
|
if [ "$regen" -eq 0 ]; then
|
|
say " skipped (--no-regen)"
|
|
elif ! command -v git >/dev/null 2>&1; then
|
|
say " skipped (git not available)"
|
|
elif [ -n "$(git status --porcelain -uno)" ]; then
|
|
fail "the working tree has uncommitted changes; commit or stash them, or pass --no-regen"
|
|
else
|
|
# A generator that fails or is missing must not leave the tree looking
|
|
# clean, which would be a false pass.
|
|
generator_failed=0
|
|
run_generator() {
|
|
if ! "$@" >/dev/null 2>&1; then
|
|
fail "generator failed: $*"
|
|
generator_failed=1
|
|
fi
|
|
}
|
|
|
|
run_generator ./scripts/copy_armv7_m.sh
|
|
run_generator ./scripts/copy_armv8_m.sh
|
|
run_generator ./scripts/copy_module_armv7_m.sh
|
|
run_generator env -C ports_arch/ARMv7-A ./update.sh --port-sets tx \
|
|
--copy-common-files --copy-port-files --copy-example --patch-files
|
|
run_generator env -C ports_arch/ARMv8-A ./update.sh --port-sets tx,tx_smp \
|
|
--copy-common-files --copy-port-files --copy-example --patch-files
|
|
|
|
drift="$(git status --porcelain -uno)"
|
|
if [ -n "$drift" ]; then
|
|
fail "running the copy scripts changed $(echo "$drift" | wc -l) file(s)."
|
|
echo " The ports below were edited directly instead of through ports_arch."
|
|
echo " Re-apply the change to ports_arch and re-run the copy scripts."
|
|
echo "$drift" | sed 's/^/ /'
|
|
git checkout -- . >/dev/null 2>&1
|
|
else
|
|
say " ok: the copy scripts change nothing"
|
|
fi
|
|
fi
|
|
|
|
# --------------------------------------------------------------------------
|
|
# 2. Preprocessor directives must balance in every port header.
|
|
# --------------------------------------------------------------------------
|
|
say ""
|
|
say "== Preprocessor directives balance =="
|
|
|
|
unbalanced=0
|
|
while IFS= read -r f; do
|
|
result="$(awk '
|
|
/^[ \t]*#[ \t]*(if|ifdef|ifndef)/ { depth++ }
|
|
/^[ \t]*#[ \t]*endif/ {
|
|
depth--
|
|
if (depth < 0 && first == 0) { first = NR }
|
|
}
|
|
END { printf "%d %d", depth, first }
|
|
' "$f")"
|
|
depth="${result% *}"
|
|
first="${result#* }"
|
|
if [ "$depth" -ne 0 ]; then
|
|
if [ "$first" -ne 0 ]; then
|
|
fail "$f: #endif without a matching #if at line $first (final depth $depth)"
|
|
else
|
|
fail "$f: $depth unterminated #if (final depth $depth)"
|
|
fi
|
|
unbalanced=$((unbalanced + 1))
|
|
fi
|
|
done < <(find ports ports_arch ports_module ports_smp -name "*.h" -type f \
|
|
! -path "*/example_build/*" 2>/dev/null | sort)
|
|
|
|
[ "$unbalanced" -eq 0 ] && say " ok: every port header balances"
|
|
|
|
# --------------------------------------------------------------------------
|
|
# 3. No statements outside a function body in a port header.
|
|
# --------------------------------------------------------------------------
|
|
# Tracks brace depth, ignoring preprocessor lines, comments and strings, and
|
|
# reports any statement that lands at depth zero. Declarations, typedefs,
|
|
# externs and macro definitions are expected there; assignments, calls and
|
|
# dereferences are not, and an orphaned function body shows up as exactly that.
|
|
say ""
|
|
say "== No code at file scope in port headers =="
|
|
|
|
orphans=0
|
|
while IFS= read -r f; do
|
|
result="$(awk '
|
|
# Skip preprocessor lines, including multi-line macro bodies, whose
|
|
# continuations are statements by design.
|
|
/^[ \t]*#/ { if (/\\[ \t]*$/) { in_macro = 1 }; next }
|
|
in_macro { if (!/\\[ \t]*$/) { in_macro = 0 }; next }
|
|
|
|
# Skip comments, both kinds, including continuation lines.
|
|
in_comment { if (/\*\//) { in_comment = 0 }; next }
|
|
/^[ \t]*\/\// { next }
|
|
/\/\*/ { if (!/\*\//) { in_comment = 1 }; next }
|
|
|
|
{
|
|
line = $0
|
|
gsub(/"[^"]*"/, "", line)
|
|
gsub(/\/\/.*$/, "", line)
|
|
stripped = line
|
|
gsub(/^[ \t]+|[ \t]+$/, "", stripped)
|
|
|
|
# A statement at file scope ends in a semicolon and either assigns,
|
|
# dereferences or opens a control structure. Declarations and
|
|
# prototypes also end in a semicolon but match none of these.
|
|
if (depth == 0 && stripped ~ /;[ \t]*$/ &&
|
|
(stripped ~ /^\*\(/ ||
|
|
stripped ~ /^[A-Za-z_][A-Za-z0-9_]*([ \t]*(\[[^]]*\]|->[ \t]*[A-Za-z_][A-Za-z0-9_]*|\.[A-Za-z_][A-Za-z0-9_]*))*[ \t]*=[^=]/ ||
|
|
stripped ~ /^(return|if|while|for|switch|do)[ \t(]/)) {
|
|
print NR ": " stripped
|
|
found++
|
|
}
|
|
|
|
n = gsub(/{/, "{", line); m = gsub(/}/, "}", line)
|
|
depth += n - m
|
|
if (depth < 0) { depth = 0 }
|
|
}
|
|
END { exit (found > 0 ? 1 : 0) }
|
|
' "$f")"
|
|
if [ -n "$result" ]; then
|
|
fail "$f: statement(s) outside any function body"
|
|
echo "$result" | sed 's/^/ /'
|
|
orphans=$((orphans + 1))
|
|
fi
|
|
done < <(find ports ports_arch ports_module ports_smp -name "*.h" -type f \
|
|
! -path "*/example_build/*" 2>/dev/null | sort)
|
|
|
|
[ "$orphans" -eq 0 ] && say " ok: no port header carries code at file scope"
|
|
|
|
# --------------------------------------------------------------------------
|
|
# 4. GNU assembly that uses the preprocessor must be named .S, not .s.
|
|
# --------------------------------------------------------------------------
|
|
# GAS runs the C preprocessor on .S and not on .s. In a .s file every line
|
|
# beginning with # is just a comment, so nothing fails and nothing is
|
|
# substituted: a #define constant reaches the assembler as an undefined
|
|
# symbol, an #ifdef block is assembled whatever the macro says, and an
|
|
# #if/#else pair emits *both* branches. The port silently ignores its own
|
|
# feature macros.
|
|
#
|
|
# Measured 26 Aug 2026, when a corrected glob in check_clang.sh first offered
|
|
# the module ports to a compiler. Twenty-nine files were affected, and the
|
|
# damage was not only cosmetic:
|
|
#
|
|
# ports_smp/cortex_a7_smp/gnu/src/tx_thread_smp_unprotect.s -- the only .s
|
|
# in a directory of twenty-one .S -- wrote the caller's LR into the
|
|
# protection structure on every unprotect, a store guarded by
|
|
# TX_MPCORE_DEBUG_ENABLE, and returned through both BX lr and MOV pc, lr.
|
|
#
|
|
# ports_module/cortex_m33/.../tx_thread_stack_build.s emitted both arms of
|
|
# an #ifdef TX_SINGLE_MODE_SECURE, so the second LR value overwrote the
|
|
# first and the secure build got the non-secure one.
|
|
#
|
|
# ports_module/cortex_a7/.../tx_thread_schedule.s did fail to assemble, on
|
|
# GCC 14.3 as well as on LLVM, because #define SYS_MODE was never expanded.
|
|
# That is the only one of the twenty-nine any compiler complained about.
|
|
#
|
|
# Only the gnu trees are checked. The IAR, Arm Compiler 5 and Keil assemblers
|
|
# preprocess .s themselves, so the same combination is correct there, and
|
|
# around three hundred files in this repository depend on it.
|
|
say ""
|
|
say "== GNU assembly using the preprocessor is named .S =="
|
|
|
|
lowercase=0
|
|
while IFS= read -r f; do
|
|
first="$(grep -nE '^[ \t]*#[ \t]*(define|include|if|ifdef|ifndef|else|elif|endif|undef)\b' \
|
|
"$f" | head -1)"
|
|
if [ -n "$first" ]; then
|
|
fail "$f: preprocessor directive in a .s file, which GAS does not preprocess"
|
|
echo " line $first"
|
|
echo " Rename the file to .S. Check the callers first: a build script"
|
|
echo " that already names it .S is the usual sign of how this happened."
|
|
lowercase=$((lowercase + 1))
|
|
fi
|
|
done < <(find ports ports_arch ports_module ports_smp -name "*.s" -type f \
|
|
-path "*/gnu/*" 2>/dev/null | sort)
|
|
|
|
[ "$lowercase" -eq 0 ] && say " ok: no .s file under a gnu tree uses the preprocessor"
|
|
|
|
# --------------------------------------------------------------------------
|
|
# 5. Report only: families with no copy script.
|
|
# --------------------------------------------------------------------------
|
|
# These are maintained by hand, so a fix applied to one toolchain can silently
|
|
# miss the others. Nothing here fails the run; it is a prompt to look.
|
|
say ""
|
|
say "== Families with no copy script (report only) =="
|
|
|
|
if [ "$quiet" -eq 0 ]; then
|
|
for family in ports/cortex_m0 ports/cortex_m0+ ports/cortex_m23; do
|
|
[ -d "$family" ] || continue
|
|
for probe in "dsb 0xF" "isb 0xF"; do
|
|
have=""; missing=""
|
|
for header in "$family"/*/inc/tx_port.h; do
|
|
[ -f "$header" ] || continue
|
|
grep -q "0xE000ED04" "$header" || continue
|
|
tool="$(basename "$(dirname "$(dirname "$header")")")"
|
|
if grep -q "$probe" "$header"; then
|
|
have="$have $tool"
|
|
else
|
|
missing="$missing $tool"
|
|
fi
|
|
done
|
|
if [ -n "$have" ] && [ -n "$missing" ]; then
|
|
echo " $family: \"$probe\" present in$have but absent in$missing"
|
|
fi
|
|
done
|
|
done
|
|
say " (nothing above means the toolchains within each family agree)"
|
|
fi
|
|
|
|
# --------------------------------------------------------------------------
|
|
say ""
|
|
if [ "$failures" -eq 0 ]; then
|
|
say "All port consistency checks passed."
|
|
exit 0
|
|
fi
|
|
|
|
echo "$failures port consistency check(s) failed."
|
|
exit 1
|