Files
threadx/scripts/check_clang.sh
T
Frédéric Desbiens 08eff8061c Made the Cortex-A12, A15 and A17 examples link with an LLVM toolchain (#596)
Those three sample scripts compiled crt0.S and reset.S but named neither on the
link line, and omitted -nostartfiles, so the toolchain was also free to link its
own startup. Under GNU that produced a working image; under an LLVM toolchain
picolibc's crt0 was pulled in and wanted __data_start, __data_source,
__data_size, __bss_size, __stack, __tls_base and __arm32_tls_tcb_offset, none of
which the linker script defines.

Defining picolibc's contract in the shared linker script was tried first and
abandoned: it reaches into ABI constants that cannot be verified here, and it is
unnecessary, because the in-tree crt0.S is already a complete startup for this
script. It sets up the stack and zeroes BSS between __bss_start__ and
__bss_end__, and .data carries no AT() so there is nothing to copy.

So the three scripts now pass -nostartfiles and name crt0.o and reset.o, which
is what the four sibling A profile cores already do and what these scripts were
evidently compiling those files for.

Both the old and the new GNU images contain the in-tree startup, __vectors from
reset.S and _mainCRTStartup from crt0.S, so the startup was already being used
through implicit startup file resolution rather than an explicit operand. How
that resolution happened without the objects being named is not accounted for
here, which is itself the argument for naming them: the same implicit behaviour
does not hold across toolchains, and that is why the LLVM link failed.

Add a readme to each of the three example directories. Their
tx_initialize_low_level.S is the generic ARMv7-A skeleton, 300 lines and
identical across all three, with no interrupt controller programming, no timer
and no vector table installation, where the A5, A7, A8 and A9 examples have all
three and ship the matching Versatile Express support files. These three
therefore demonstrate that the port builds; they will not receive a timer tick.
Nothing in the tree said so, which invites the assumption that they are
equivalent.

Verified with both toolchains for all three cores. GNU links at 41,276 bytes of
text, down from 41,768 because the unused toolchain startup is no longer
included, and Arm Toolchain for Embedded links at 37,982 where it previously
could not link at all. The resulting image is structurally sound: entry at
_start, __vectors at address zero, and a BSS range in RAM. It has not been
executed. scripts/check_clang.sh now links eleven of eleven example builds.

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

264 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.
EXAMPLES_EXPECTED_TO_FAIL="arm9 arm11 cortex_r4 cortex_r5"
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