#!/bin/sh # check-arch-neutral.sh - Verify the kernel's architecture boundary. # # 1. Static rules: outside kernel/src/Arch/, kernel code may not contain # inline assembly, assembly sources, architecture #ifdefs, or includes of # a specific port's headers (it must use the interfaces in Arch/*.hpp). # 2. If clang is installed, compile the whole kernel for a second # architecture (default aarch64) and report any symbol the generic code # needs that the port does not define. Links too when ld.lld is found. # # Usage: ./scripts/check-arch-neutral.sh [arch] # # Copyright (c) 2026 Daniel Hammer set -e ROOT="$(cd "$(dirname "$0")/.." && pwd)" SRC="$ROOT/kernel/src" ARCH="${1:-aarch64}" status=0 cd "$SRC" # Files allowed to name an architecture: the Limine adapter reads the # per-architecture MP structures limine.h defines, and Io/IoPort.hpp is an # interface header that dispatches to the port like Arch/*.hpp do. ALLOW='^(limine\.h|Boot/Protocols/LimineProtocol\.cpp|Io/IoPort\.hpp)$' generic=$(find . -type f \( -name '*.c' -o -name '*.cpp' -o -name '*.h' -o -name '*.hpp' \) \ ! -path './Arch/*' | sed 's|^\./||' | LC_ALL=C sort) report() { echo "check-arch-neutral: $1" >&2 echo "$2" | sed 's/^/ /' >&2 status=1 } hits=$(grep -nE '(^|[^_[:alnum:]])(asm|__asm__)[[:space:]]*(volatile|\()' $generic || true) [ -n "$hits" ] && report "inline assembly outside src/Arch/:" "$hits" hits=$(find . -type f \( -name '*.asm' -o -name '*.S' -o -name '*.s' \) ! -path './Arch/*') [ -n "$hits" ] && report "assembly sources outside src/Arch/:" "$hits" hits=$(echo "$generic" | grep -vE "$ALLOW" | xargs grep -nE \ '__(x86_64|i386|aarch64|arm|riscv|loongarch[0-9]*)__|MONTAUK_ARCH_(X86|AARCH|RISCV|LOONG)[A-Z0-9_]*' || true) [ -n "$hits" ] && report "architecture conditionals outside src/Arch/:" "$hits" hits=$(echo "$generic" | grep -vE "$ALLOW" | xargs grep -nE \ '#[[:space:]]*include[[:space:]]*[<"]Arch/[^/>"]+/' || true) [ -n "$hits" ] && report "includes of a port's private headers:" "$hits" if [ $status -eq 0 ]; then echo "check-arch-neutral: static rules OK" fi # ---- Cross-compile check ---- if ! command -v clang >/dev/null 2>&1; then echo "check-arch-neutral: clang not found, skipping the $ARCH build" exit $status fi if [ ! -d "Arch/$ARCH" ]; then echo "check-arch-neutral: no src/Arch/$ARCH port" >&2 exit 1 fi echo "check-arch-neutral: compiling the kernel for $ARCH" make -C "$ROOT/kernel" ARCH="$ARCH" objects -j"$(nproc 2>/dev/null || echo 4)" \ >/dev/null 2>"$ROOT/kernel/check-arch-$ARCH.log" || { echo "check-arch-neutral: $ARCH compile failed (kernel/check-arch-$ARCH.log):" >&2 grep -E 'error' "$ROOT/kernel/check-arch-$ARCH.log" | head -20 >&2 exit 1 } rm -f "$ROOT/kernel/check-arch-$ARCH.log" NM="$(clang -print-prog-name=llvm-nm)" command -v "$NM" >/dev/null 2>&1 || NM=nm CXXFILT="$(clang -print-prog-name=llvm-cxxfilt)" command -v "$CXXFILT" >/dev/null 2>&1 || CXXFILT=cat cd "$ROOT/kernel" objs=$(find "obj-$ARCH" -name '*.o') tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT "$NM" --defined-only -j $objs "cc-runtime-$ARCH/cc-runtime.a" 2>/dev/null | LC_ALL=C sort -u > "$tmp/def" "$NM" --undefined-only -j $objs | LC_ALL=C sort -u > "$tmp/undef" # Provided by the linker script. printf '%s\n' KernelStartSymbol KernelEndSymbol __init_array __init_array_end >> "$tmp/def" LC_ALL=C sort -u -o "$tmp/def" "$tmp/def" missing=$(LC_ALL=C comm -23 "$tmp/undef" "$tmp/def" | "$CXXFILT") if [ -n "$missing" ]; then report "symbols the $ARCH port must still provide:" "$missing" exit 1 fi echo "check-arch-neutral: $ARCH objects compile and every symbol resolves" if command -v "${KERNEL_LD:-ld.lld}" >/dev/null 2>&1; then make ARCH="$ARCH" >/dev/null echo "check-arch-neutral: linked kernel/bin-$ARCH/kernel" fi exit $status