Files
Frédéric Desbiens 990faf670d Enabled execution profiling for Cortex-R5 (#766)
The Cortex-R5 assembly guarded its execution-profile hooks with only the legacy
TX_ENABLE_EXECUTION_CHANGE_NOTIFY symbol. The documented
TX_EXECUTION_PROFILE_ENABLE configuration initialized profiling without recording
thread or interrupt transitions.

I made all AC5, AC6, GNU, Green Hills, and IAR hooks accept both symbols. I also
extended the port consistency and GNU/LLVM feature checks to cover the current
configuration.

All 849 base assembly files and all 219 TX_EXECUTION_PROFILE_ENABLE files passed
with GCC 14.2.1 and clang 22.1.0. A CMake/Ninja Cortex-R5 profile build emitted
all seven expected hook relocations. Proprietary toolchains were not run.

Assisted-by: Codex (GPT-5) <noreply@openai.com>
2026-09-28 12:45:09 -04:00

316 lines
13 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. Cortex-R5 execution-profile guards accept both configuration names.
# --------------------------------------------------------------------------
# Profiling storage uses TX_EXECUTION_PROFILE_ENABLE, while
# TX_ENABLE_EXECUTION_CHANGE_NOTIFY remains a supported compatibility symbol.
# Every Cortex-R5 profiling hook must recognize both symbols.
say ""
say "== Cortex-R5 execution-profile guards accept both symbols =="
profile_guards_missing=0
while IFS=: read -r file line text; do
case "$text" in
*TX_EXECUTION_PROFILE_ENABLE*) ;;
*)
fail "$file:$line accepts the legacy execution-profile symbol only"
profile_guards_missing=$((profile_guards_missing + 1))
;;
esac
done < <(grep -Rn "TX_ENABLE_EXECUTION_CHANGE_NOTIFY" ports/cortex_r5/*/src 2>/dev/null)
[ "$profile_guards_missing" -eq 0 ] && \
say " ok: every Cortex-R5 profiling hook accepts both symbols"
# --------------------------------------------------------------------------
# 6. 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