Files
threadx/scripts/check_clang.sh
T
Frédéric Desbiens a8aebc5206 Completed the Cortex-M0 barriers and made its example build with both toolchains (#595)
Two unrelated Cortex-M0 gaps, both left over from earlier work.

The memory barriers from #523 reached the Cortex-M0 gnu port but not its ac6 or
iar siblings, in either the inline system return in tx_port.h or the assembly
routine. Both take the same GNU or IAR code path, and iar already carried the
entry barrier, so the missing pieces were the entry pair for ac6 and the barrier
after restoring the interrupt posture for both. All three tools now match.

The Cortex-M0 example could not link with any toolchain. cortexm0_crt0.S
references 23 linker script symbols and the script defined only 12 of them, so
__text_start__, __text_end__, __text_load_start__, the rodata and fast section
symbols, and the ctors and dtors load addresses were all unresolved. The
Cortex-M4 script defines all 23, including a .fast section with no content whose
symbols exist so that the startup copy is a no-op, and its comment says as much.
The Cortex-M0 script is brought to that same shape.

That left the example failing under LLVM only, on instructions that ARMv6-M can
encode just one way. The file declared .code 16 but no syntax mode, so GNU as
used the legacy divided syntax in which a plain add or sub sets the flags
implicitly, while LLVM implements unified syntax only and rejected the
non-flag-setting spelling. Declaring .syntax unified and writing movs, adds and
subs makes both assemblers agree, and the encodings GNU produces are byte
identical before and after, verified by disassembling both objects.

The Cortex-M0 example now links with GNU at 22,520 bytes of text and with Arm
Toolchain for Embedded at 22,866, so it comes off the list of examples not
expected to link and scripts/check_clang.sh now links eight of eight.

Assisted-by: Claude Code (Opus 5) <noreply@anthropic.com>
2026-08-10 17:34:13 -04:00

270 lines
10 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
##############################################################################
# Builds the Arm ports with an LLVM based toolchain: every assembly source of
# every Arm gnu port, and the common C sources for one core per architecture
# profile. Compilation only, no linking, so no target C library is required
# profile, then links the example builds that have a script driver.
#
# scripts/check_clang.sh # clang from PATH
# scripts/check_clang.sh --clang /path/to/clang
# CLANG=/path/to/clang scripts/check_clang.sh
#
# Options:
# --clang <path> Compiler to use; defaults to $CLANG then to clang.
# --asm-only Skip the C sources and the example builds.
# --no-examples Skip the example builds.
# --quiet Print only failures and the summary.
#
# Exit status is 0 when everything builds and 1 otherwise.
#
# Why this exists: the gnu ports are only ever built with GNU tooling, and GNU
# as accepts several non-canonical forms that LLVM's assembler rejects. Those
# forms accumulated unnoticed. This check also covers Arm Toolchain for
# Embedded, which is LLVM based and is the successor to Arm Compiler 6, so the
# ac6 code paths are exercised here as well.
#
# Arm Toolchain for Embedded releases:
# https://github.com/arm/arm-toolchain/releases
set -u
cd "$(dirname "$(realpath "$0")")/.."
CC="${CLANG:-clang}"
asm_only=0
no_examples=0
quiet=0
while [ "$#" -gt 0 ]; do
case "$1" in
--clang) [ "$#" -ge 2 ] || { echo "Error: --clang needs a path" >&2; exit 2; }; CC="$2"; shift 2 ;;
--asm-only) asm_only=1; no_examples=1; shift ;;
--no-examples) no_examples=1; shift ;;
--quiet) quiet=1; shift ;;
-h|--help) sed -n '17,33p' "$0"; exit 0 ;;
*) echo "Error: unknown option '$1'" >&2; exit 2 ;;
esac
done
say() { [ "$quiet" -eq 1 ] || echo "$@"; }
fail() { echo " FAIL: $*"; }
if ! command -v "$CC" >/dev/null 2>&1 && [ ! -x "$CC" ]; then
echo "Error: compiler '$CC' not found."
echo "Pass --clang <path>, set CLANG, or install Arm Toolchain for Embedded:"
echo " https://github.com/arm/arm-toolchain/releases"
exit 1
fi
# Resolve to an absolute path. The example stage runs the build scripts from
# inside their own directories, so a relative compiler path would stop
# resolving there.
if [ -e "$CC" ]; then
CC="$(realpath "$CC")"
else
CC="$(command -v "$CC")"
fi
say ""
say "Using: $CC"
say " $("$CC" --version | head -1)"
# Each port directory is mapped explicitly to a target triple and CPU. Do not
# replace this with prefix matching: cortex_a5* also matches cortex_a53 and
# cortex_a55, which are AArch64, and assembling those as ARM32 produces a flood
# of misleading errors.
declare -A PORT_TARGET=(
[cortex_m0]="arm-none-eabi cortex-m0 -mthumb"
[cortex_m0+]="arm-none-eabi cortex-m0plus -mthumb"
[cortex_m3]="arm-none-eabi cortex-m3 -mthumb"
[cortex_m4]="arm-none-eabi cortex-m4 -mthumb"
[cortex_m7]="arm-none-eabi cortex-m7 -mthumb"
[cortex_m23]="arm-none-eabi cortex-m23 -mthumb"
[cortex_m33]="arm-none-eabi cortex-m33 -mthumb"
[cortex_m55]="arm-none-eabi cortex-m55 -mthumb -mfloat-abi=hard"
[cortex_m85]="arm-none-eabi cortex-m85 -mthumb -mfloat-abi=hard"
[cortex_a5]="arm-none-eabi cortex-a5"
[cortex_a7]="arm-none-eabi cortex-a7"
[cortex_a8]="arm-none-eabi cortex-a8"
[cortex_a9]="arm-none-eabi cortex-a9"
[cortex_a12]="arm-none-eabi cortex-a12"
[cortex_a15]="arm-none-eabi cortex-a15"
[cortex_a17]="arm-none-eabi cortex-a17"
[cortex_a5_smp]="arm-none-eabi cortex-a5"
[cortex_a7_smp]="arm-none-eabi cortex-a7"
[cortex_a9_smp]="arm-none-eabi cortex-a9"
[cortex_r4]="arm-none-eabi cortex-r4"
[cortex_r5]="arm-none-eabi cortex-r5"
[cortex_r52]="arm-none-eabi cortex-r52"
[cortex_a34]="aarch64-none-elf cortex-a34"
[cortex_a35]="aarch64-none-elf cortex-a35"
[cortex_a53]="aarch64-none-elf cortex-a53"
[cortex_a55]="aarch64-none-elf cortex-a55"
[cortex_a57]="aarch64-none-elf cortex-a57"
[cortex_a65]="aarch64-none-elf cortex-a65"
[cortex_a65ae]="aarch64-none-elf cortex-a65ae"
[cortex_a72]="aarch64-none-elf cortex-a72"
[cortex_a73]="aarch64-none-elf cortex-a73"
[cortex_a75]="aarch64-none-elf cortex-a75"
[cortex_a76]="aarch64-none-elf cortex-a76"
[cortex_a76ae]="aarch64-none-elf cortex-a76ae"
[cortex_a77]="aarch64-none-elf cortex-a77"
[cortex_a34_smp]="aarch64-none-elf cortex-a34"
[cortex_a35_smp]="aarch64-none-elf cortex-a35"
[cortex_a53_smp]="aarch64-none-elf cortex-a53"
[cortex_a55_smp]="aarch64-none-elf cortex-a55"
[cortex_a57_smp]="aarch64-none-elf cortex-a57"
[cortex_a65_smp]="aarch64-none-elf cortex-a65"
[cortex_a65ae_smp]="aarch64-none-elf cortex-a65ae"
[cortex_a72_smp]="aarch64-none-elf cortex-a72"
[cortex_a73_smp]="aarch64-none-elf cortex-a73"
[cortex_a75_smp]="aarch64-none-elf cortex-a75"
[cortex_a76_smp]="aarch64-none-elf cortex-a76"
[cortex_a76ae_smp]="aarch64-none-elf cortex-a76ae"
[cortex_a77_smp]="aarch64-none-elf cortex-a77"
[cortex_a78_smp]="aarch64-none-elf cortex-a78"
)
# One core per architecture profile for the C sources. Compiling all of them
# for every core would multiply the run time without adding coverage, since the
# port headers differ by profile rather than by core.
C_CORES="cortex_m0 cortex_m4 cortex_m23 cortex_m33 cortex_m55 cortex_a7 cortex_a53 cortex_r5"
# Example builds that are not expected to link, with the reason. Listed
# explicitly rather than silently skipped, so the gaps stay visible.
#
# These fail with the GNU toolchain too, so they are not LLVM problems:
# arm9 arm11 need newlib multilib variants for those CPUs, which are
# cortex_r4 cortex_r5 not present in every GNU toolchain packaging.
#
# This one is specific to LLVM:
# cortex_a12 a15 a17 their link line omits -nostartfiles, so the toolchain's
# own crt0 is linked, and it needs picolibc's
# __data_start, __data_source, __data_size and __bss_size,
# which the example's linker script does not define.
EXAMPLES_EXPECTED_TO_FAIL="arm9 arm11 cortex_r4 cortex_r5 cortex_a12 cortex_a15 cortex_a17"
failures=0
skipped=""
# --------------------------------------------------------------------------
say ""
say "== Assembly sources of every Arm gnu port =="
total=0
for dir in ports/*/gnu/src ports_smp/*/gnu/src ports_module/*/gnu/src; do
[ -d "$dir" ] || continue
core="$(echo "$dir" | cut -d/ -f2)"
spec="${PORT_TARGET[$core]:-}"
if [ -z "$spec" ]; then
skipped="$skipped $core"
continue
fi
# shellcheck disable=SC2086
set -- $spec
target="$1"; cpu="$2"; shift 2; extra="$*"
for src in "$dir"/*.S "$dir"/*.s; do
[ -f "$src" ] || continue
total=$((total + 1))
output="$("$CC" --target="$target" -mcpu="$cpu" $extra -c "$src" -o /dev/null 2>&1)"
if [ -n "$output" ]; then
fail "$src"
echo "$output" | sed 's/^/ /'
failures=$((failures + 1))
fi
done
done
say " $((total - failures)) of $total assembled"
if [ -n "$skipped" ]; then
say " not Arm, skipped:$(echo $skipped | tr ' ' '\n' | sort -u | tr '\n' ' ')"
fi
# --------------------------------------------------------------------------
if [ "$asm_only" -eq 0 ]; then
say ""
say "== Common C sources, one core per architecture profile =="
for core in $C_CORES; do
spec="${PORT_TARGET[$core]:-}"
[ -n "$spec" ] || continue
# shellcheck disable=SC2086
set -- $spec
target="$1"; cpu="$2"; shift 2; extra="$*"
count=0; bad=0
for src in common/src/*.c; do
count=$((count + 1))
output="$("$CC" --target="$target" -mcpu="$cpu" $extra \
-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/^/ /'
bad=$((bad + 1)); failures=$((failures + 1))
fi
done
say " $core: $((count - bad)) of $count compiled"
done
fi
# --------------------------------------------------------------------------
if [ "$no_examples" -eq 0 ]; then
say ""
say "== Example builds, linked with lld =="
example_ok=0
example_total=0
example_known=""
for dir in ports/*/gnu/example_build; do
[ -f "$dir/build_threadx.sh" ] && [ -f "$dir/build_threadx_sample.sh" ] || continue
core="$(echo "$dir" | cut -d/ -f2)"
case " $EXAMPLES_EXPECTED_TO_FAIL " in
*" $core "*) example_known="$example_known $core"; continue ;;
esac
example_total=$((example_total + 1))
rm -f "$dir"/*.o "$dir"/*.a "$dir"/*.out "$dir"/*.map 2>/dev/null || true
log="$(cd "$dir" && TOOLCHAIN=atfe ATFE_CLANG="$CC" ./build_threadx.sh 2>&1 && \
TOOLCHAIN=atfe ATFE_CLANG="$CC" ./build_threadx_sample.sh 2>&1)" || true
if [ -f "$dir/sample_threadx.out" ]; then
example_ok=$((example_ok + 1))
else
fail "$core: example build produced no image"
# Not filtered on "error": a missing tool reports "command not
# found" or "Permission denied", and filtering hid exactly that.
echo "$log" | tail -6 | sed 's/^/ /'
failures=$((failures + 1))
fi
rm -f "$dir"/*.o "$dir"/*.a "$dir"/*.out "$dir"/*.map 2>/dev/null || true
done
say " $example_ok of $example_total example builds linked"
if [ -n "$example_known" ]; then
say " known not to link, see the list at the top of this script:$example_known"
fi
fi
# --------------------------------------------------------------------------
say ""
if [ "$failures" -eq 0 ]; then
say "All LLVM toolchain checks passed."
exit 0
fi
echo "$failures LLVM toolchain check(s) failed."
exit 1