#!/bin/sh
# tms-clang -- M1 IR pipeline: clang IR, scale GEPs, optimize, normalize GEPs.
#
# Usage: tms-clang [-O0|-O2] [-I dir] -o OUT.ll IN.c
#
# -O2 (the default) scales, optimizes, then normalizes; -O0 skips the optimizer.
# The output is normalized IR for llc -march=tms34010; this wrapper does not
# invoke llc itself.
set -eu

ROOT=$(cd "$(dirname "$0")/.." && pwd)
TMS_CLANG=${TMS_CLANG:-$ROOT/build/llvm-build/bin/clang}
TMS_OPT=${TMS_OPT:-$ROOT/build/llvm-build/bin/opt}
TMS_BITADDR=${TMS_BITADDR:-$ROOT/tools/tms-bitaddr/tms-bitaddr}

OPTIMIZE=1
INCLUDES=
OUT=
IN=

usage() {
    echo "usage: $0 [-O0|-O2] [-I dir] -o OUT.ll IN.c" >&2
    exit 2
}

while [ $# -gt 0 ]; do
    case "$1" in
    -O0) OPTIMIZE=0; shift ;;
    -O2) OPTIMIZE=1; shift ;;
    -I) [ $# -ge 2 ] || usage; INCLUDES="$INCLUDES -I $2"; shift 2 ;;
    -I*) INCLUDES="$INCLUDES $1"; shift ;;
    -o) [ $# -ge 2 ] || usage; OUT=$2; shift 2 ;;
    -*) usage ;;
    *) [ -z "$IN" ] || usage; IN=$1; shift ;;
    esac
done

[ -n "$IN" ] || usage
[ -n "$OUT" ] || usage

TMP=$(mktemp -d "${TMPDIR:-/tmp}/tms-clang.XXXXXX")
trap 'rm -rf "$TMP"' EXIT HUP INT TERM

# shellcheck disable=SC2086
"$TMS_CLANG" --target=tms34010-unknown-none -O0 -Xclang -disable-O0-optnone \
    -S -emit-llvm -ffreestanding -fno-builtin -nostdinc $INCLUDES \
    -o "$TMP/step1.ll" "$IN"

# A `static` variadic definition is local, so DeadArgumentElimination removes
# its `...` when nothing calls llvm.va_start and rewrites every call site to
# drop the extra arguments.  The M4 contiguous-stack walk in the body still
# reads those slots, so -O2 silently miscompiles it.  Reject the shape before
# opt, but leave a definition that genuinely calls llvm.va_start alone (DAE
# keeps its varargs, and va_start itself is a separate M6 feature).  -O0 (which
# M4's varargs gate uses) and external definitions such as sprintf are
# unaffected.
if [ "$OPTIMIZE" -eq 1 ]; then
    STATIC_VARARG=$(awk '
        /^define / {
            in_def = 1
            sig = $0
            va_start = 0
            next
        }
        in_def && /llvm\.va_start/ { va_start = 1 }
        in_def && /^}/ {
            if (!va_start &&
                sig ~ /[^A-Za-z0-9_](internal|private)[^A-Za-z0-9_]/ &&
                sig ~ /\.\.\./) {
                print sig
                exit
            }
            in_def = 0
        }
    ' "$TMP/step1.ll")
    if [ -n "$STATIC_VARARG" ]; then
        NAME=$(printf '%s\n' "$STATIC_VARARG" |
            sed -n 's/.*@\([A-Za-z0-9_.$-]*\)(.*/\1/p')
        echo "tms-clang: error: static variadic definition '${NAME:-?}' loses its" \
            "'...' arguments at -O2 (dead argument elimination); remove 'static'" \
            "or compile with -O0" >&2
        exit 1
    fi
fi

"$TMS_BITADDR" --scale "$TMP/step1.ll" "$TMP/scaled.ll" 2>/dev/null

if [ "$OPTIMIZE" -eq 1 ]; then
    # This target has no i64 and no multiply-high.  IndVarSimplify's exit-value
    # replacement (which rewrites a loop's final value into a closed-form i33
    # multiply, e.g. n*(n+1)/2 for sumto) is therefore an unsupported shape.
    # -replexitval is an option of IndVarSimplify; disabling only that rewrite
    # keeps the loop, which is what ordinary -O2 code needs.  This is a driver
    # policy, not a target TTI policy: opt runs without a target machine here.
    # It stays until i64/M6 removes the workaround; llc diagnoses the raw i33
    # shape reached by an opt run that omits this flag (m5-policy.ll).
    "$TMS_OPT" -passes='default<O2>' -replexitval=never \
        -S "$TMP/scaled.ll" -o "$TMP/opt.ll"
    NORMALIZE_IN="$TMP/opt.ll"
else
    NORMALIZE_IN="$TMP/scaled.ll"
fi

mkdir -p "$(dirname "$OUT")"
"$TMS_BITADDR" --normalize "$NORMALIZE_IN" "$OUT" 2>/dev/null
