blaise/scripts/build-rtl-objects.sh
Graeme Geldenhuys 773d773008 rtl-unification: move E2E test helpers off blaise_rtl.a (source-built RTL)
The E2E test helpers linked Blaise programs against the shipped blaise_rtl.a.
With the archive on its way out (RTL-unification Stage 3), migrate them to the
source-built RTL:

- cp.test.e2e.base gains LinkWithRTL: assembles the program object, builds the
  RTL from source via scripts/build-rtl-objects.sh (--exclude-defined-by drops
  the RTL units the whole-program assembly already inlined), and links the loose
  objects.  Its five cc-link sites and the ToolchainAvailable gate now use it /
  the RTL source instead of the archive.
- New cp.test.rtllink unit factors the same link helper for the standalone
  helpers (publishedrtti, proctypes_ofobject, attributes) that lower programs
  themselves rather than via the base class.
- cp.test.cli and cp.test.assembler drive an external blaise binary that
  source-builds its own RTL, so their gates now require the binary + RTL source,
  not the archive.
- cp.test.linker's TestArchive_ParsesRTL kept its archive-parser coverage
  (including the GNU long-name member) by building a throwaway .a from the
  source-built objects with ar — a test-time-only ar use, not a product one.

build-rtl-objects.sh resolves its RTL source relative to its own location (so it
works from any CWD — the fixpoint scripts run from the project root, the test
runner from compiler/), caches a per-object .syms list to keep repeated
--exclude-defined-by calls cheap, and skips rebuilding fresh objects.

Full suite green (3829 tests) on both QBE- and native-built runners; the
previously-skipped TInternalAsmE2ETests / TCLIContractTests and the archive
parser test now run.
2026-06-25 23:08:30 +01:00

123 lines
4.8 KiB
Bash
Executable file

#!/bin/bash
# Build the implicit RTL object files from source, mirroring the compiler's
# own TBackendDriver.EnsureRTLObjects. Used by the fixpoint / bootstrap
# scripts to link a self-hosted compiler binary WITHOUT the legacy
# blaise_rtl.a archive (RTL-unification Stage 3).
#
# Usage:
# scripts/build-rtl-objects.sh <blaise-binary> <out-dir> [options]
# Options:
# --with-startup include runtime.start (its bare _start)
# --exclude-defined-by FILE omit any RTL object that defines a symbol FILE
# already defines (FILE is the main program object)
#
# Builds each RTL unit into <out-dir>/<unit>.o and prints, one per line on
# stdout, the object paths to put on the link line. By default runtime.start
# (the bare _start entry) is OMITTED — a gcc/cc link line gets _start from libc
# and calls main. Pass --with-startup to include it (a -nostartfiles / native
# internal link that owns the entry point).
#
# --exclude-defined-by handles the self-host link: a whole-program --emit-ir
# dump INLINES the RTL units the compiler transitively uses (runtime.arc via
# `uses classes`, etc.), so the program object already defines their symbols.
# Linking our standalone copy too would double-define them; this option drops
# the redundant objects, mirroring the compiler driver's own dedup.
#
# The build goes through a --unit-cache so each unit references its RTL deps'
# globals externally instead of re-defining them (the archive's member
# selection used to hide those duplicate definitions; explicit .o files do not).
# RTL units carry inline asm, so the native backend + internal assembler is
# mandatory. Build order is leaf-first so each unit's deps are already cached.
set -e
BLAISE="$1"
OUTDIR="$2"
shift 2 || true
WITH_STARTUP=0
EXCLUDE_OBJ=""
while [ $# -gt 0 ]; do
case "$1" in
--with-startup) WITH_STARTUP=1; shift ;;
--exclude-defined-by) EXCLUDE_OBJ="$2"; shift 2 ;;
*) echo "build-rtl-objects: unknown option $1" >&2; exit 2 ;;
esac
done
if [ -z "$BLAISE" ] || [ -z "$OUTDIR" ]; then
echo "usage: build-rtl-objects.sh <blaise-binary> <out-dir> [--with-startup] [--exclude-defined-by FILE]" >&2
exit 2
fi
if [ ! -x "$BLAISE" ]; then
echo "build-rtl-objects: compiler binary not found: $BLAISE" >&2
exit 2
fi
# Resolve the RTL source relative to THIS script (scripts/ is a sibling of
# compiler/), so the script works regardless of the caller's CWD — the fixpoint
# scripts run from the project root, the test runner from compiler/.
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
SRC="$SCRIPT_DIR/../compiler/src/main/pascal"
if [ ! -f "$SRC/runtime.arc.pas" ]; then
# Fall back to a CWD-relative path (e.g. an unusual invocation layout).
SRC=compiler/src/main/pascal
fi
if [ ! -f "$SRC/runtime.arc.pas" ]; then
echo "build-rtl-objects: RTL source not found under $SRC" >&2
exit 2
fi
mkdir -p "$OUTDIR"
# Leaf-first link order. rtl.platform owns the shared globals (GPlatformLayout,
# GRtlPlatform) that layout.linux + posix reference externally, so it is built
# first and linked once.
RTL_UNITS=(
rtl.platform
runtime.start runtime.atomic runtime.setjmp runtime.utf8
runtime.mem runtime.str runtime.set runtime.arc
runtime.weak runtime.float runtime.thread runtime.exc
rtl.platform.layout.linux rtl.platform.posix
)
# Symbols the main program object already defines (for --exclude-defined-by).
EXCL_DEFS=""
if [ -n "$EXCLUDE_OBJ" ]; then
EXCL_DEFS=$(nm "$EXCLUDE_OBJ" 2>/dev/null | grep -E ' [TDBR] ' | awk '{print $3}' | sort -u)
fi
OBJS=()
for u in "${RTL_UNITS[@]}"; do
obj="$OUTDIR/$u.o"
src="$SRC/$u.pas"
# Rebuild only when the cached object is missing or older than its source, so
# a persistent $OUTDIR (e.g. reused across a test suite) builds the RTL once.
if [ ! -f "$obj" ] || [ "$src" -nt "$obj" ]; then
"$BLAISE" --backend native --assembler internal \
--source "$src" \
--unit-path "$SRC" --unit-cache "$OUTDIR" \
--output "$obj" >/dev/null 2>"$OUTDIR/$u.build.err" || {
echo "build-rtl-objects: failed to build $u" >&2
tail -5 "$OUTDIR/$u.build.err" >&2
exit 1
}
# Cache this object's defined symbols so repeated --exclude-defined-by calls
# (e.g. one per test in a suite) don't re-run nm on the stable RTL objects.
nm "$obj" 2>/dev/null | grep -E ' [TDBR] ' | awk '{print $3}' | sort -u \
> "$obj.syms"
fi
if [ "$u" = "runtime.start" ] && [ "$WITH_STARTUP" -eq 0 ]; then
continue
fi
# Drop this object if it re-defines any symbol the main program already owns
# (it was inlined into the program) — otherwise the loose .o collides.
if [ -n "$EXCL_DEFS" ] && [ -f "$obj.syms" ] && [ -s "$obj.syms" ]; then
if [ -n "$(comm -12 "$obj.syms" <(printf '%s\n' "$EXCL_DEFS"))" ]; then
continue
fi
fi
OBJS+=("$obj")
done
printf '%s\n' "${OBJS[@]}"