kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit 7087a3c382b08472a57ec07f33498c2b300e41f8
parent ed5f4832cdf3eb4a5193b6a54bcde06f79ba578f
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Tue, 16 Jun 2026 09:48:49 -0700

scripts: ecosystem build-speed benchmark + ecosystem-speed target

Preserves uncommitted working-tree edits found on main (kit/clang/tcc
ecosystem build-phase timing harness) so the O1-PATTERNS integration merge
can proceed without discarding them.

Diffstat:
Mmk/test.mk | 8+++++++-
Ascripts/ecosystem_speed.sh | 742+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 749 insertions(+), 1 deletion(-)

diff --git a/mk/test.mk b/mk/test.mk @@ -323,13 +323,19 @@ test-extlink: bin # (network); the gate then runs fully offline. Known kit bugs are kept RED on # purpose (see test/ecosystem/known_bugs/); failing artifacts are preserved # under build/ecosystem-fail/. -.PHONY: test-ecosystem provision-ecosystem +.PHONY: test-ecosystem provision-ecosystem ecosystem-speed provision-ecosystem: @sh scripts/ecosystem.sh fetch all test-ecosystem: bin @KIT=$(abspath $(BIN)) sh test/ecosystem/run.sh +ecosystem-speed: + @if [ ! -x build/release/kit ]; then \ + $(MAKE) --no-print-directory RELEASE=1 bin; \ + fi + @KIT=$(abspath build/release/kit) AR=$(AR) bash scripts/ecosystem_speed.sh + # test-dist: one-command verification of the dist subsystem (content-addressed # store + signed packaging). Runs the CAS and pkg driver corpora, which cover # round-trip, integrity/tamper detection, trust store, and the read-side diff --git a/scripts/ecosystem_speed.sh b/scripts/ecosystem_speed.sh @@ -0,0 +1,742 @@ +#!/usr/bin/env bash +# Compare ecosystem build-phase speed across kit, clang, and tcc. +# +# Phases are measured separately: +# compile: compile library TUs plus the driver TU to objects. +# archive: archive the kit-compiled library objects with kit ar vs host ar. +# link: link precompiled driver object plus static library into an app. +# +# Correctness remains owned by test/ecosystem/run.sh; this script is a timing +# report over the same pinned project recipes. + +set -euo pipefail + +SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) +ROOT=$(cd "$SCRIPT_DIR/.." && pwd) +SELF="$ROOT/scripts/ecosystem_speed.sh" +ECO_DIR="$ROOT/test/ecosystem" +RECIPE_DIR="$ECO_DIR/recipes" +ECOSYS="$ROOT/scripts/ecosystem.sh" + +KIT="${KIT:-$ROOT/build/release/kit}" +CLANG="${CLANG:-clang}" +TCC="${TCC:-tcc}" +HOST_AR="${AR:-ar}" +RUNS="${ECO_SPEED_RUNS:-${RUNS:-5}}" +TIMEOUT_S="${ECO_SPEED_TIMEOUT:-${TIMEOUT:-20}}" +OPTS="${ECO_SPEED_OPTS:-O0 O1}" +OUT_ROOT="${ECO_SPEED_OUT:-$ROOT/build/ecosystem-speed}" + +die() { + printf 'ecosystem-speed: %s\n' "$*" >&2 + exit 2 +} + +usage() { + cat <<EOF +usage: scripts/ecosystem_speed.sh [project ...] + +env: + KIT kit binary (default: build/release/kit) + CLANG clang binary (default: clang) + TCC tcc binary (default: tcc; skipped if unavailable) + AR host ar binary (default: ar) + ECO_SPEED_RUNS best-of-N runs per row (default: 5) + ECO_SPEED_TIMEOUT phase timeout seconds per run (default: 20) + ECO_SPEED_OPTS opt levels, e.g. "O0 O1" (default: "O0 O1") + ECO_SPEED_PROJECTS project list if no argv is passed + ECO_SPEED_OUT output dir for logs/results (default: build/ecosystem-speed) +EOF +} + +recipe_reset() { + ECO_VERSION= + ECO_URL= + ECO_SHA256= + ECO_SRCDIR=. + ECO_INCLUDES="." + ECO_DEFINES= + ECO_LIBS= + ECO_DRIVER= + ECO_BUG= + eco_lib_srcs() { :; } + eco_run() { :; } +} + +load_recipe() { + local name=$1 + local recipe="$RECIPE_DIR/$name.recipe" + [ -f "$recipe" ] || die "unknown project '$name' (see: scripts/ecosystem.sh list)" + recipe_reset + # shellcheck disable=SC1090 + . "$recipe" +} + +srcdir_for() { + "$ECOSYS" srcdir "$1" 2>/dev/null || true +} + +driver_for() { + case "$ECO_DRIVER" in + use/*) printf '%s/%s\n' "$ECO_DIR" "$ECO_DRIVER" ;; + *) printf '%s/%s\n' "$ECO_SRC" "$ECO_DRIVER" ;; + esac +} + +host_sdk() { + case "$(uname -s)" in + Darwin) xcrun --sdk macosx --show-sdk-path 2>/dev/null || true ;; + *) printf '\n' ;; + esac +} + +append_sysroot_args() { + local compiler=$1 + local sdk=$2 + case "$(uname -s)" in + Darwin) + [ -n "$sdk" ] || return 0 + case "$compiler" in + kit) printf '%s\n' --sysroot "$sdk" ;; + clang) printf '%s\n' -isysroot "$sdk" ;; + tcc) printf '%s\n' -isysroot "$sdk" ;; + esac + ;; + esac +} + +quote_cmd() { + local first=1 + for arg in "$@"; do + if [ "$first" = 0 ]; then printf ' '; fi + first=0 + printf '%q' "$arg" + done + printf '\n' +} + +compiler_available() { + local compiler=$1 + case "$compiler" in + kit) [ -x "$KIT" ] ;; + clang) command -v "$CLANG" >/dev/null 2>&1 ;; + tcc) + if [ -x "$TCC" ]; then return 0; fi + command -v "$TCC" >/dev/null 2>&1 + ;; + *) return 1 ;; + esac +} + +archiver_available() { + local archiver=$1 + case "$archiver" in + kit) [ -x "$KIT" ] ;; + host) command -v "$HOST_AR" >/dev/null 2>&1 ;; + *) return 1 ;; + esac +} + +compile_one() { + local compiler=$1 + local opt=$2 + local project=$3 + local out_dir=$4 + local src_list=$5 + local sdk=$6 + + load_recipe "$project" + ECO_SRC="$(srcdir_for "$project")" + export ECO_SRC ECO_DIR + [ -n "$ECO_SRC" ] && [ -d "$ECO_SRC" ] || die "$project is not provisioned" + + mkdir -p "$out_dir/obj" + : > "$out_dir/build.err" + : > "$out_dir/cmds.sh" + : > "$out_dir/objects" + : > "$out_dir/lib_objs" + : > "$out_dir/driver_obj" + + local inc_args=() + local def_args=() + local sys_args=() + local inc def + for inc in $ECO_INCLUDES; do inc_args+=("-I$ECO_SRC/$inc"); done + for def in $ECO_DEFINES; do def_args+=("$def"); done + while IFS= read -r a; do sys_args+=("$a"); done \ + < <(append_sysroot_args "$compiler" "$sdk") + + local total=0 + total=$(awk 'NF {n++} END {print n + 0}' "$src_list") + [ "$total" -gt 0 ] || die "$project has no sources" + + local idx=0 + local src obj + while IFS= read -r src; do + [ -n "$src" ] || continue + idx=$((idx + 1)) + obj="$out_dir/obj/$idx.o" + + local cmd=() + case "$compiler" in + kit) + cmd=("$KIT" cc "-$opt" "${sys_args[@]}" "${def_args[@]}" + "${inc_args[@]}" -c "$src" -o "$obj") + ;; + clang) + cmd=("$CLANG" "-$opt" "${sys_args[@]}" "${def_args[@]}" + "${inc_args[@]}" -c "$src" -o "$obj") + ;; + tcc) + cmd=("$TCC" "-$opt" "${sys_args[@]}" "${def_args[@]}" + "${inc_args[@]}" -c "$src" -o "$obj") + ;; + *) die "unknown compiler '$compiler'" ;; + esac + + quote_cmd "${cmd[@]}" >> "$out_dir/cmds.sh" + if ! "${cmd[@]}" 2>>"$out_dir/build.err"; then + return 1 + fi + printf '%s\n' "$obj" >> "$out_dir/objects" + if [ "$idx" -lt "$total" ]; then + printf '%s\n' "$obj" >> "$out_dir/lib_objs" + else + printf '%s\n' "$obj" > "$out_dir/driver_obj" + fi + done < "$src_list" +} + +archive_one() { + local archiver=$1 + local out_dir=$2 + local obj_list=$3 + + mkdir -p "$out_dir" + : > "$out_dir/build.err" + : > "$out_dir/cmds.sh" + + local objs=() + local obj + while IFS= read -r obj; do + [ -n "$obj" ] || continue + objs+=("$obj") + done < "$obj_list" + [ "${#objs[@]}" -gt 0 ] || die "archive has no objects: $obj_list" + + local cmd=() + case "$archiver" in + kit) cmd=("$KIT" ar rcs "$out_dir/lib.a" "${objs[@]}") ;; + host) cmd=("$HOST_AR" rcs "$out_dir/lib.a" "${objs[@]}") ;; + *) die "unknown archiver '$archiver'" ;; + esac + quote_cmd "${cmd[@]}" >> "$out_dir/cmds.sh" + "${cmd[@]}" 2>>"$out_dir/build.err" +} + +link_one() { + local compiler=$1 + local opt=$2 + local project=$3 + local out_dir=$4 + local fixture_dir=$5 + local sdk=$6 + + load_recipe "$project" + ECO_SRC="$(srcdir_for "$project")" + export ECO_SRC ECO_DIR + [ -n "$ECO_SRC" ] && [ -d "$ECO_SRC" ] || die "$project is not provisioned" + + mkdir -p "$out_dir" + : > "$out_dir/build.err" + : > "$out_dir/cmds.sh" + + local driver_obj lib + driver_obj=$(cat "$fixture_dir/driver_obj") + lib="$fixture_dir/lib.a" + [ -f "$driver_obj" ] || die "missing driver object: $driver_obj" + [ -f "$lib" ] || die "missing archive: $lib" + + local sys_args=() + local lib_args=() + local a + while IFS= read -r a; do sys_args+=("$a"); done \ + < <(append_sysroot_args "$compiler" "$sdk") + for a in $ECO_LIBS; do lib_args+=("$a"); done + + local cmd=() + case "$compiler" in + kit) + cmd=("$KIT" cc "-$opt" "${sys_args[@]}" "$driver_obj" "$lib" + "${lib_args[@]}" -lc -o "$out_dir/app") + ;; + clang) + cmd=("$CLANG" "-$opt" "${sys_args[@]}" "$driver_obj" "$lib" + "${lib_args[@]}" -o "$out_dir/app") + ;; + tcc) + cmd=("$TCC" "-$opt" "${sys_args[@]}" "$driver_obj" "$lib" + "${lib_args[@]}" -o "$out_dir/app") + ;; + *) die "unknown compiler '$compiler'" ;; + esac + quote_cmd "${cmd[@]}" >> "$out_dir/cmds.sh" + "${cmd[@]}" 2>>"$out_dir/build.err" +} + +run_with_timeout() { + local timeout_s=$1 + shift + perl -MTime::HiRes=time,sleep -MPOSIX=':sys_wait_h,setpgid' -e ' + my ($timeout, @cmd) = @ARGV; + my $start = time; + my $pid = fork(); + die "fork failed\n" unless defined $pid; + if ($pid == 0) { + setpgid(0, 0); + exec @cmd; + print STDERR "exec failed: $!\n"; + exit 127; + } + setpgid($pid, $pid); + my $timed_out = 0; + my $status; + while (1) { + my $r = waitpid($pid, WNOHANG); + if ($r == $pid) { + $status = $?; + last; + } + if (time - $start >= $timeout) { + $timed_out = 1; + kill "TERM", -$pid; + my $until = time + 2.0; + while (time < $until) { + $r = waitpid($pid, WNOHANG); + if ($r == $pid) { + $status = $?; + last; + } + sleep 0.02; + } + if (!defined $status) { + kill "KILL", -$pid; + waitpid($pid, 0); + $status = $?; + } + last; + } + sleep 0.01; + } + my $elapsed = time - $start; + my $rc; + if ($timed_out) { + $rc = 124; + } elsif ($status & 127) { + $rc = 128 + ($status & 127); + } else { + $rc = ($status >> 8) & 255; + } + printf "elapsed %.6f\nrc %d\n", $elapsed, $rc; + exit $rc; + ' "$timeout_s" "$@" +} + +timed_phase() { + local phase=$1 + local tool=$2 + local run_dir=$3 + shift 3 + + mkdir -p "$run_dir" + local cmd=() + case "$phase" in + compile) + [ $# -eq 4 ] || die "compile phase expects opt project src_list sdk" + cmd=(bash "$SELF" --compile-one "$tool" "$1" "$2" "$run_dir" "$3" "$4") + ;; + archive) + [ $# -eq 1 ] || die "archive phase expects obj_list" + cmd=(bash "$SELF" --archive-one "$tool" "$run_dir" "$1") + ;; + link) + [ $# -eq 4 ] || die "link phase expects opt project fixture_dir sdk" + cmd=(bash "$SELF" --link-one "$tool" "$1" "$2" "$run_dir" "$3" "$4") + ;; + *) die "unknown phase '$phase'" ;; + esac + set +e + run_with_timeout "$TIMEOUT_S" "${cmd[@]}" \ + > "$run_dir/time.txt" 2> "$run_dir/time.err" + local rc=$? + set -e + + local elapsed + elapsed=$(awk '/^elapsed / {print $2}' "$run_dir/time.txt" | tail -1) + [ -n "$elapsed" ] || elapsed=0 + printf '%s,%s\n' "$rc" "$elapsed" +} + +record_best() { + local phase=$1 + local tool=$2 + local project=$3 + local opt=$4 + local row_dir=$5 + shift 5 + + local best="" + local best_rc=125 + local run + for run in $(seq 1 "$RUNS"); do + local run_dir="$row_dir/$phase-$tool-$run" + local got rc elapsed + got=$(timed_phase "$phase" "$tool" "$run_dir" "$@") + rc=${got%,*} + elapsed=${got#*,} + printf '%s,%s,%s,%s,%s,%s,%s\n' "$project" "$opt" "$phase" "$tool" \ + "$run" "$rc" "$elapsed" >> "$OUT_ROOT/runs.csv" + if [ "$rc" -eq 0 ]; then + local ms + ms=$(awk -v s="$elapsed" 'BEGIN { printf "%.3f", s * 1000.0 }') + if [ -z "$best" ] || awk -v a="$ms" -v b="$best" 'BEGIN { exit !(a < b) }'; then + best=$ms + best_rc=0 + fi + else + best_rc=$rc + fi + done + + if [ -n "$best" ]; then + printf '%s\n' "$best" + elif [ "$best_rc" -eq 124 ]; then + printf 'timeout\n' + else + printf 'fail\n' + fi +} + +best_compile_ms_for() { + local compiler=$1 + local project=$2 + local opt=$3 + local src_list=$4 + local sdk=$5 + local row_dir=$6 + + if ! compiler_available "$compiler"; then + printf 'missing\n' + return 0 + fi + record_best compile "$compiler" "$project" "$opt" "$row_dir" \ + "$opt" "$project" "$src_list" "$sdk" +} + +best_archive_ms_for() { + local archiver=$1 + local project=$2 + local opt=$3 + local fixture_dir=$4 + local row_dir=$5 + + if ! archiver_available "$archiver"; then + printf 'missing\n' + return 0 + fi + [ -f "$fixture_dir/lib_objs" ] || { printf 'fail\n'; return 0; } + record_best archive "$archiver" "$project" "$opt" "$row_dir" \ + "$fixture_dir/lib_objs" +} + +best_link_ms_for() { + local compiler=$1 + local project=$2 + local opt=$3 + local fixture_dir=$4 + local sdk=$5 + local row_dir=$6 + + if ! compiler_available "$compiler"; then + printf 'missing\n' + return 0 + fi + [ -f "$fixture_dir/lib.a" ] || { printf 'fail\n'; return 0; } + record_best link "$compiler" "$project" "$opt" "$row_dir" \ + "$opt" "$project" "$fixture_dir" "$sdk" +} + +make_fixture() { + local compiler=$1 + local project=$2 + local opt=$3 + local src_list=$4 + local sdk=$5 + local fixture_dir=$6 + + if ! compiler_available "$compiler"; then + return 1 + fi + rm -rf "$fixture_dir" + mkdir -p "$fixture_dir" + local got rc + got=$(timed_phase compile "$compiler" "$fixture_dir" \ + "$opt" "$project" "$src_list" "$sdk") + rc=${got%,*} + [ "$rc" -eq 0 ] || return 1 + + local archiver=host + [ "$compiler" = kit ] && archiver=kit + got=$(timed_phase archive "$archiver" "$fixture_dir/archive" \ + "$fixture_dir/lib_objs") + rc=${got%,*} + [ "$rc" -eq 0 ] || return 1 + mv "$fixture_dir/archive/lib.a" "$fixture_dir/lib.a" + return 0 +} + +prepare_project() { + local project=$1 + local out_dir=$2 + local src_list="$out_dir/srcs" + local lib_src_list="$out_dir/lib_srcs" + load_recipe "$project" + ECO_SRC="$(srcdir_for "$project")" + export ECO_SRC ECO_DIR + if [ -z "$ECO_SRC" ] || [ ! -d "$ECO_SRC" ]; then + return 1 + fi + mkdir -p "$out_dir" + eco_lib_srcs > "$lib_src_list" + cp "$lib_src_list" "$src_list" + driver_for >> "$src_list" + return 0 +} + +print_toolchain_phase() { + local results=$1 + local phase=$2 + local units_label=$3 + + awk -F, -v phase_name="$phase" -v units_label="$units_label" \ + -v opts_list="$OPTS" ' + function isnum(x) { return x ~ /^[0-9]+([.][0-9]+)?$/ } + function cell(x) { return isnum(x) ? sprintf("%8.1f", x) : sprintf("%8s", x) } + function speed(ref, kit) { + return (isnum(ref) && isnum(kit) && kit > 0) ? sprintf("%7.3fx", ref / kit) : sprintf("%7s", "-") + } + function add_geo(tag, ref, kit) { + if (isnum(ref) && isnum(kit) && kit > 0 && ref > 0) { + geo_sum[tag] += log(ref / kit) + geo_n[tag]++ + } + } + BEGIN { + nopts = split(opts_list, opts, /[ \t]+/) + printf "\n[%s]\n", phase_name + printf "%-10s %-3s %4s %8s %8s %7s %8s %7s\n", \ + "project", "opt", units_label, "kit_ms", "clang_ms", "k/clang", "tcc_ms", "k/tcc" + printf "%-10s %-3s %4s %8s %8s %7s %8s %7s\n", \ + "----------", "---", "----", "--------", "--------", "-------", "--------", "-------" + } + NR == 1 { next } + $1 == phase_name { + opt=$3; units=$4; kit=$5; clang=$6; tcc=$7 + printf "%-10s %-3s %4s %s %s %s %s %s\n", \ + $2, opt, units, cell(kit), cell(clang), speed(clang, kit), cell(tcc), speed(tcc, kit) + add_geo(opt ":clang", clang, kit) + add_geo(opt ":tcc", tcc, kit) + add_geo("all:clang", clang, kit) + add_geo("all:tcc", tcc, kit) + } + END { + printf "%-10s %-3s %4s %8s %8s %7s %8s %7s\n", \ + "----------", "---", "----", "--------", "--------", "-------", "--------", "-------" + for (i = 1; i <= nopts; ++i) { + opt = opts[i] + kc = opt ":clang" + kt = opt ":tcc" + cnt_clang = geo_n[kc] + 0 + cnt_tcc = geo_n[kt] + 0 + clang_geo = geo_n[kc] ? sprintf("%7.3fx", exp(geo_sum[kc] / geo_n[kc])) : sprintf("%7s", "-") + tcc_geo = geo_n[kt] ? sprintf("%7.3fx", exp(geo_sum[kt] / geo_n[kt])) : sprintf("%7s", "-") + printf "%-10s %-3s %4s %8s %8s %7s %8s %7s\n", \ + "geomean", opt, cnt_clang "/" cnt_tcc, "", "", clang_geo, "", tcc_geo + } + cnt_clang = geo_n["all:clang"] + 0 + cnt_tcc = geo_n["all:tcc"] + 0 + clang_geo = geo_n["all:clang"] ? sprintf("%7.3fx", exp(geo_sum["all:clang"] / geo_n["all:clang"])) : sprintf("%7s", "-") + tcc_geo = geo_n["all:tcc"] ? sprintf("%7.3fx", exp(geo_sum["all:tcc"] / geo_n["all:tcc"])) : sprintf("%7s", "-") + printf "%-10s %-3s %4s %8s %8s %7s %8s %7s\n", \ + "geomean", "all", cnt_clang "/" cnt_tcc, "", "", clang_geo, "", tcc_geo + } + ' "$results" +} + +print_archive_phase() { + local results=$1 + + awk -F, -v opts_list="$OPTS" ' + function isnum(x) { return x ~ /^[0-9]+([.][0-9]+)?$/ } + function cell(x) { return isnum(x) ? sprintf("%8.1f", x) : sprintf("%8s", x) } + function speed(ref, kit) { + return (isnum(ref) && isnum(kit) && kit > 0) ? sprintf("%7.3fx", ref / kit) : sprintf("%7s", "-") + } + function add_geo(tag, ref, kit) { + if (isnum(ref) && isnum(kit) && kit > 0 && ref > 0) { + geo_sum[tag] += log(ref / kit) + geo_n[tag]++ + } + } + BEGIN { + nopts = split(opts_list, opts, /[ \t]+/) + printf "\n[archive]\n" + printf "%-10s %-3s %4s %8s %8s %7s\n", \ + "project", "opt", "objs", "kit_ar", "host_ar", "k/host" + printf "%-10s %-3s %4s %8s %8s %7s\n", \ + "----------", "---", "----", "--------", "--------", "-------" + } + NR == 1 { next } + $1 == "archive" { + opt=$3; units=$4; kit=$5; host=$8 + printf "%-10s %-3s %4s %s %s %s\n", \ + $2, opt, units, cell(kit), cell(host), speed(host, kit) + add_geo(opt ":host", host, kit) + add_geo("all:host", host, kit) + } + END { + printf "%-10s %-3s %4s %8s %8s %7s\n", \ + "----------", "---", "----", "--------", "--------", "-------" + for (i = 1; i <= nopts; ++i) { + opt = opts[i] + key = opt ":host" + host_geo = geo_n[key] ? sprintf("%7.3fx", exp(geo_sum[key] / geo_n[key])) : sprintf("%7s", "-") + printf "%-10s %-3s %4s %8s %8s %7s\n", \ + "geomean", opt, geo_n[key], "", "", host_geo + } + host_geo = geo_n["all:host"] ? sprintf("%7.3fx", exp(geo_sum["all:host"] / geo_n["all:host"])) : sprintf("%7s", "-") + printf "%-10s %-3s %4s %8s %8s %7s\n", \ + "geomean", "all", geo_n["all:host"], "", "", host_geo + } + ' "$results" +} + +print_report() { + local results=$1 + printf 'ecosystem speed: best-of-%s, timeout=%ss\n' "$RUNS" "$TIMEOUT_S" + printf 'kit=%s\n' "$KIT" + printf 'clang=%s\n' "$CLANG" + if compiler_available tcc; then + printf 'tcc=%s\n' "$TCC" + else + printf 'tcc=%s (missing; rows skipped)\n' "$TCC" + fi + printf 'host_ar=%s\n' "$HOST_AR" + printf 'archive_input=kit-compiled library objects\n' + print_toolchain_phase "$results" compile srcs + print_archive_phase "$results" + print_toolchain_phase "$results" link apps + printf '\nlogs=%s\n' "$OUT_ROOT" +} + +main() { + if [ "${1:-}" = "--help" ] || [ "${1:-}" = "-h" ]; then + usage + exit 0 + fi + case "${1:-}" in + --compile-one) + shift + [ $# -eq 6 ] || die "--compile-one expects compiler opt project out_dir src_list sdk" + compile_one "$@" + exit $? + ;; + --archive-one) + shift + [ $# -eq 3 ] || die "--archive-one expects archiver out_dir obj_list" + archive_one "$@" + exit $? + ;; + --link-one) + shift + [ $# -eq 6 ] || die "--link-one expects compiler opt project out_dir fixture_dir sdk" + link_one "$@" + exit $? + ;; + esac + + [ -x "$KIT" ] || die "kit binary not found at $KIT (run: make RELEASE=1 bin)" + command -v "$CLANG" >/dev/null 2>&1 || die "clang not found: $CLANG" + command -v "$HOST_AR" >/dev/null 2>&1 || die "host ar not found: $HOST_AR" + + mkdir -p "$OUT_ROOT" + printf 'project,opt,phase,tool,run,rc,seconds\n' > "$OUT_ROOT/runs.csv" + local results="$OUT_ROOT/results.csv" + printf 'phase,project,opt,units,kit_ms,clang_ms,tcc_ms,host_ms\n' > "$results" + + local sdk + sdk="$(host_sdk)" + + local projects + if [ "$#" -gt 0 ]; then + projects="$*" + elif [ -n "${ECO_SPEED_PROJECTS:-}" ]; then + projects="$ECO_SPEED_PROJECTS" + else + projects="$("$ECOSYS" list)" + fi + + local any=0 + local project opt + for project in $projects; do + local project_dir="$OUT_ROOT/$project" + if ! prepare_project "$project" "$project_dir"; then + printf 'ecosystem-speed: skip %-10s not provisioned (run: make provision-ecosystem)\n' "$project" >&2 + continue + fi + any=1 + + local src_list="$project_dir/srcs" + local lib_src_list="$project_dir/lib_srcs" + local srcs lib_objs + srcs=$(awk 'NF {n++} END {print n + 0}' "$src_list") + lib_objs=$(awk 'NF {n++} END {print n + 0}' "$lib_src_list") + + for opt in $OPTS; do + local row_dir="$project_dir/$opt" + mkdir -p "$row_dir" + + local kit_compile clang_compile tcc_compile + kit_compile=$(best_compile_ms_for kit "$project" "$opt" "$src_list" "$sdk" "$row_dir") + clang_compile=$(best_compile_ms_for clang "$project" "$opt" "$src_list" "$sdk" "$row_dir") + tcc_compile=$(best_compile_ms_for tcc "$project" "$opt" "$src_list" "$sdk" "$row_dir") + printf 'compile,%s,%s,%s,%s,%s,%s,\n' "$project" "$opt" "$srcs" \ + "$kit_compile" "$clang_compile" "$tcc_compile" >> "$results" + + local kit_fixture="$row_dir/fixture-kit" + local clang_fixture="$row_dir/fixture-clang" + local tcc_fixture="$row_dir/fixture-tcc" + make_fixture kit "$project" "$opt" "$src_list" "$sdk" "$kit_fixture" || true + make_fixture clang "$project" "$opt" "$src_list" "$sdk" "$clang_fixture" || true + make_fixture tcc "$project" "$opt" "$src_list" "$sdk" "$tcc_fixture" || true + + local kit_ar host_ar + kit_ar=$(best_archive_ms_for kit "$project" "$opt" "$kit_fixture" "$row_dir") + host_ar=$(best_archive_ms_for host "$project" "$opt" "$kit_fixture" "$row_dir") + printf 'archive,%s,%s,%s,%s,,,%s\n' "$project" "$opt" "$lib_objs" \ + "$kit_ar" "$host_ar" >> "$results" + + local kit_link clang_link tcc_link + kit_link=$(best_link_ms_for kit "$project" "$opt" "$kit_fixture" "$sdk" "$row_dir") + clang_link=$(best_link_ms_for clang "$project" "$opt" "$clang_fixture" "$sdk" "$row_dir") + tcc_link=$(best_link_ms_for tcc "$project" "$opt" "$tcc_fixture" "$sdk" "$row_dir") + printf 'link,%s,%s,1,%s,%s,%s,\n' "$project" "$opt" \ + "$kit_link" "$clang_link" "$tcc_link" >> "$results" + done + done + + [ "$any" = 1 ] || die "no provisioned ecosystem projects found" + print_report "$results" +} + +main "$@"