Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions scripts/embedded_bench.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#!/bin/bash
# Cross-compile the many_conditionals.emb.h header on embedded toolchains and
# report .text and per-function sizes for the generated Ok() methods.
#
# Usage: scripts/embedded_bench.sh <out-dir>
# Run from the repo root after regenerating goldens / building the header.
#
# Each target compiles a tiny TU that forces the relevant Ok() methods to be
# emitted, then reports `size` on the resulting object file plus a few key
# symbol sizes from `nm`. The script is idempotent: it leaves nothing behind
# inside the repo, only the requested out-dir.

set -euo pipefail

OUT="${1:-/tmp/embedded-bench}"
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
mkdir -p "$OUT"

# Regenerate the header from the current .emb file so we measure the live
# state of the generator.
python3 "$REPO/scripts/regenerate_goldens.py" >/dev/null

# Small TU that pulls Ok() into the object file. Without this every Ok() is
# a `weak` symbol the linker would dead-strip if unreachable.
DRIVER="$OUT/driver.cc"
cat > "$DRIVER" <<'CPP'
#include <cstdint>
#include "testdata/many_conditionals.emb.h"

// Sink for results so the compiler can't fold Ok() away.
volatile bool emboss_result_sink;

extern "C" void large_ok(const char *buf) {
auto v = emboss::test::MakeLargeConditionalsView(buf, 100);
emboss_result_sink = v.Ok();
}

extern "C" void disjunction_ok(const char *buf) {
auto v = emboss::test::MakeDisjunctionConditionalsView(buf, 16);
emboss_result_sink = v.Ok();
}
CPP

# Common flags. -Os for embedded space optimization, -ffunction-sections so
# each function ends up in its own .text.<symbol> section (makes
# nm/objdump --size-sort more meaningful), -fno-exceptions/-fno-rtti to
# match typical embedded builds.
EMBEDDED_FLAGS="-std=c++17 -Os -ffunction-sections -fdata-sections -fno-exceptions -fno-rtti"
INCLUDES="-I$REPO -I$REPO/testdata/golden_cpp"

# Headers reference '...emb.h' from testdata/, but the .emb.h files live in
# testdata/golden_cpp/. Drop a symlink so the include resolves.
mkdir -p "$OUT/include/testdata"
ln -sf "$REPO/testdata/golden_cpp/many_conditionals.emb.h" \
"$OUT/include/testdata/many_conditionals.emb.h"

report_size() {
local label="$1"
local nm_bin="$2"
local obj="$3"
echo "=== $label ==="
# `size` is part of binutils for the same target prefix, but the
# canonical `size` from host binutils understands ELF objects from any
# arch we'll see, so we use it unconditionally.
size "$obj"
echo "--- LargeConditionals::Ok() ---"
# nm output has the type-template parameter list itself containing
# `>` characters, so the regex uses `.*` (greedy) and anchors on
# `>::Ok() const$` at end of line to ensure we match the outermost
# `Ok()` method and not any nested inner view's `Ok()`.
"$nm_bin" --size-sort -S --demangle "$obj" 2>/dev/null \
| grep -E "GenericLargeConditionalsView<.*>::Ok\(\) const$" | tail -1 \
|| true
echo "--- DisjunctionConditionals::Ok() ---"
"$nm_bin" --size-sort -S --demangle "$obj" 2>/dev/null \
| grep -E "GenericDisjunctionConditionalsView<.*>::Ok\(\) const$" | tail -1 \
|| true
}

# --- ARM Cortex-M4 / Thumb-2 (STM32 family) ---
ARM_OBJ="$OUT/many_conditionals.thumb.o"
arm-none-eabi-g++ $EMBEDDED_FLAGS \
-mthumb -mcpu=cortex-m4 -mfloat-abi=soft \
$INCLUDES -I"$OUT/include" \
-c "$DRIVER" -o "$ARM_OBJ"
report_size "ARM Cortex-M4 (Thumb-2, -Os)" arm-none-eabi-nm "$ARM_OBJ"

# --- MicroBlaze ---
MB_PREFIX="/opt/microblaze/microblazebe--glibc--stable-2025.08-1/bin/microblaze-buildroot-linux-gnu"
MB_OBJ="$OUT/many_conditionals.microblaze.o"
"$MB_PREFIX-g++" $EMBEDDED_FLAGS \
$INCLUDES -I"$OUT/include" \
-c "$DRIVER" -o "$MB_OBJ"
report_size "MicroBlaze (big-endian, -Os)" "$MB_PREFIX-nm" "$MB_OBJ"

# --- Host x86-64 reference, same flags, for comparison ---
HOST_OBJ="$OUT/many_conditionals.x86_64.o"
g++ $EMBEDDED_FLAGS \
$INCLUDES -I"$OUT/include" \
-c "$DRIVER" -o "$HOST_OBJ"
report_size "Host x86-64 (-Os)" nm "$HOST_OBJ"
116 changes: 116 additions & 0 deletions scripts/embedded_compare.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#!/bin/bash
# Run scripts/embedded_bench.sh twice — once on master, once on the current
# branch — and print a side-by-side comparison.
#
# Usage: scripts/embedded_compare.sh [base-ref]
# base-ref defaults to master.
#
# Must be run from a clean working tree. Restores the originally-checked-out
# branch at the end.

set -euo pipefail

REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$REPO"

BASE="${1:-master}"
BRANCH_REF="$(git symbolic-ref --short HEAD)"

# Refresh the index's stat cache; without this, a prior write to a
# tracked file (e.g. running the regen script earlier) leaves a stale
# diff-index even though the contents are identical to HEAD.
git update-index --refresh >/dev/null 2>&1 || true
if ! git diff-index --quiet HEAD; then
echo "Working tree has uncommitted changes; commit/stash first." >&2
exit 1
fi

OUT_BRANCH="$(mktemp -d)"
OUT_BASE="$(mktemp -d)"

echo "Running on $BRANCH_REF..."
scripts/embedded_bench.sh "$OUT_BRANCH" > "$OUT_BRANCH/log.txt" 2>&1

echo "Running on $BASE..."
restore_branch() {
# Discard any goldens the regen script may have rewritten on the way
# back to the original branch.
git checkout -- testdata/golden_cpp/ 2>/dev/null || true
git checkout -- testdata/many_conditionals.emb 2>/dev/null || true
rm -f testdata/imported_genfiles.emb
git checkout "$BRANCH_REF" >/dev/null 2>&1 || true
}
trap restore_branch EXIT

git checkout "$BASE" >/dev/null
# Pull the test schema/benchmark/scripts forward so the comparison
# measures the generator output, not the test surface area.
git checkout "$BRANCH_REF" -- \
testdata/many_conditionals.emb \
compiler/back_end/cpp/testcode/many_conditionals_benchmark.cc \
scripts/embedded_bench.sh \
scripts/regenerate_goldens.py >/dev/null
bash scripts/embedded_bench.sh "$OUT_BASE" > "$OUT_BASE/log.txt" 2>&1

# regen rewrote the goldens against master's generator; throw those
# changes away so the trap can switch branches cleanly.
git checkout -- testdata/golden_cpp/ testdata/many_conditionals.emb 2>/dev/null || true
rm -f testdata/imported_genfiles.emb

# --- Format report ---
echo
echo "============================================================"
echo " $BASE → $BRANCH_REF"
echo "============================================================"
python3 - "$OUT_BASE/log.txt" "$OUT_BRANCH/log.txt" <<'PY'
import re, sys

base_path, branch_path = sys.argv[1], sys.argv[2]

def parse(path):
with open(path) as f:
text = f.read()
out = {}
current_target = None
for line in text.splitlines():
m = re.match(r"=== (.+) ===", line)
if m:
current_target = m.group(1)
out[current_target] = {}
continue
m = re.match(r"--- (.+) ---", line)
if m:
label = m.group(1)
out[current_target]["_pending_label"] = label
continue
# Size line like " 18962 0 1 18963 4a13 /path"
m = re.match(r"\s*(\d+)\s+\d+\s+\d+\s+\d+\s+[0-9a-f]+\s+\S+", line)
if m and "_pending_label" not in out.get(current_target, {}):
out[current_target]["TU .text"] = int(m.group(1))
continue
# nm size line: "address size W symbol"
m = re.match(r"[0-9a-f]+\s+([0-9a-f]+)\s+\w\s", line)
if m and "_pending_label" in out[current_target]:
label = out[current_target].pop("_pending_label")
out[current_target][label] = int(m.group(1), 16)
return out

base = parse(base_path)
branch = parse(branch_path)

targets = list(base.keys())
metrics = ["TU .text", "LargeConditionals::Ok()", "DisjunctionConditionals::Ok()"]

w_target = max(len(t) for t in targets)
print(f"{'Target':<{w_target}} {'Metric':<32} {'Base':>10} {'Branch':>10} {'Delta':>10} {'%':>7}")
print("-" * (w_target + 75))
for tgt in targets:
for m in metrics:
b = base[tgt].get(m)
n = branch[tgt].get(m)
if b is None or n is None:
continue
delta = n - b
pct = (delta / b * 100) if b else 0
print(f"{tgt:<{w_target}} {m:<32} {b:>10} {n:>10} {delta:>+10} {pct:>+6.1f}%")
PY
Loading