mirror of
https://github.com/eclipse-threadx/threadx.git
synced 2026-10-06 06:59:08 +08:00
Fixes #551 `_tx_thread_system_return_inline()` in the Cortex-M `tx_port.h` headers spells its barriers `dsb 0xF` and `isb 0xF`. A bare hexadecimal operand is a GNU assembler extension, and IAR rejects it with `operand syntax error`, so the header cannot be included at all. The block is guarded for GCC, armclang and IAR together, so every IAR user of an affected port hits it -- four independent reports on Cortex-M33 and M7 with EWARM 9.50 and 9.70. Both operands become `sy`, the Arm UAL name for exactly what `0xF` encodes. The generated instruction is unchanged. Applied to the two `ports_arch` masters and all 32 copies under `ports`, covering M0, M23, M3, M33, M4, M52, M55, M7 and M85 across ac5, ac6, gnu, iar and keil, plus the `scripts/check_ports.sh` probes that matched the old spelling. `check_ports.sh` passes, the copy scripts still reproduce every generated port byte for byte, and `arm-none-eabi-gcc -O2` compiles a caller for every patched header, emitting `dsb sy` and `isb sy`. Three headers that need toolchain intrinsics GCC does not ship fail identically on `dev`. Assisted-by: Copilot (Opus 5) <noreply@github.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 sy" "isb sy"; 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
|