#!/usr/bin/env bash
# buildGno.sh — compile a C or C++ source into a GNO/ME-loadable OMF
# shell command.
#
# Usage: bash demos/buildGno.sh <basename>
#   demos/<basename>.c   -> demos/<basename>.omf   (compiled with clang)
#   demos/<basename>.cpp -> demos/<basename>.omf   (compiled with clang++)
#
# Uses crt0Gno (GNO entry contract) + the GNO libc backend, then wraps
# the flat image in a relocatable ExpressLoad OMF via omfEmit — the
# GS/OS Loader (which GNO uses to launch commands) requires a real OMF,
# not a flat binary.  C++ programs additionally link libcxxabi.o
# (operator new/delete/__cxa_atexit/__cxa_guard_*/dynamic_cast/typeinfo)
# and libcxxabiSjlj.o (SJLJ exception runtime), plus libunwindStub.o
# (Itanium `_Unwind_*` surface routed onto SJLJ — Phase 5.1).  Link-time
# GC removes whichever portions aren't referenced, so the cost is zero
# for pure-C programs.

set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"

# --debug parallels demos/build.sh: emit `-g` IR, ask link816 for a
# DWARF sidecar, suffix outputs with `_dbg` so debug + release coexist.
# Phase 3.1 (debugger front-end).
DEBUG=0
ARGS=()
while [ $# -gt 0 ]; do
    case "$1" in
        --debug) DEBUG=1; shift;;
        *) ARGS+=("$1"); shift;;
    esac
done
set -- "${ARGS[@]+"${ARGS[@]}"}"

[ $# -ge 1 ] || { echo "usage: $0 [--debug] <basename>" >&2; exit 2; }
BASE="$1"

if [ -f "$SCRIPT_DIR/$BASE.cpp" ]; then
    SRC="$SCRIPT_DIR/$BASE.cpp"
    CC="$ROOT/tools/llvm-mos-build/bin/clang++"
    # Default is -fno-exceptions -fno-rtti (the supported subset for
    # third-party C++).  Probes that exercise the SJLJ exception runtime
    # (e.g. unwindStubProbe.cpp) opt in via GNO_CXX_EXCEPTIONS=1, which
    # switches to -fsjlj-exceptions and pulls in the libunwindStub.o
    # symbols.  Set GNO_CXX_RTTI=1 alongside if you need typeinfo
    # objects (not currently exercised by any in-tree probe).
    if [ "${GNO_CXX_EXCEPTIONS:-0}" = 1 ]; then
        LANG_FLAGS="-fsjlj-exceptions"
        if [ "${GNO_CXX_RTTI:-0}" != 1 ]; then
            LANG_FLAGS="$LANG_FLAGS -fno-rtti"
        fi
    else
        LANG_FLAGS="-fno-exceptions -fno-rtti"
    fi
elif [ -f "$SCRIPT_DIR/$BASE.c" ]; then
    SRC="$SCRIPT_DIR/$BASE.c"
    CC="$ROOT/tools/llvm-mos-build/bin/clang"
    LANG_FLAGS=""
else
    echo "no source: $SCRIPT_DIR/$BASE.{c,cpp}" >&2
    exit 2
fi

LINK="$ROOT/tools/link816"
OMF="$ROOT/tools/omfEmit"
RT="$ROOT/runtime"

if [ "$DEBUG" = 1 ]; then
    OUTBASE="${BASE}_dbg"
    DBGFLAGS="-g"
else
    OUTBASE="$BASE"
    DBGFLAGS=""
fi
OBJ="$SCRIPT_DIR/$OUTBASE.o"
BIN="$SCRIPT_DIR/$OUTBASE.bin"
MAP="$SCRIPT_DIR/$OUTBASE.map"
RELOC="$SCRIPT_DIR/$OUTBASE.reloc"
OUT="$SCRIPT_DIR/$OUTBASE.omf"
DWARF="$SCRIPT_DIR/$OUTBASE.dwarf"

echo "compile: $(basename "$SRC") -> $OUTBASE.o"
# `-I include/c++` makes <etl/...> headers findable when the source is .cpp.
# Harmless for .c — the directory just doesn't contain anything reachable
# from a C TU.
"$CC" --target=w65816 -I"$RT/include" -I"$RT/include/c++" $DBGFLAGS -O2 -ffunction-sections $LANG_FLAGS ${GNO_CFLAGS:-} -c "$SRC" -o "$OBJ"

echo "link: -> $OUTBASE.bin"
LINKER_ARGS=(-o "$BIN" --text-base 0x1000 --bss-base 0xA000 \
    --map "$MAP" --reloc-out "$RELOC")
if [ "$DEBUG" = 1 ]; then
    LINKER_ARGS+=(--debug-out "$DWARF")
fi
"$LINK" "${LINKER_ARGS[@]}" \
    "$RT/crt0Gno.o" "$OBJ" \
    "$RT/libcGno.o" "$RT/gnoKernel.o" "$RT/gnoGsos.o" \
    "$RT/libc.o" "$RT/snprintf.o" "$RT/extras.o" \
    "$RT/softFloat.o" "$RT/softDouble.o" \
    "$RT/math.o" \
    "$RT/iigsToolbox.o" "$RT/iigsToolboxExtra.o" \
    "$RT/libgcc.o" \
    "$RT/libcxxabi.o" "$RT/libcxxabiSjlj.o" "$RT/libunwindStub.o"

echo "OMF: -> $OUTBASE.omf"
# Declare a dedicated DP/Stack (OMF KIND=0x4012) segment.  Without it GNO
# falls back to a 4 KB default stack shared with DP and placed low in bank 0,
# which is too small / mis-placed for GS/OS file I/O: GNO's GS/OS interceptor
# (StackGSOS) carves its direct-page work area off the caller's S and the FST
# nests deep below it, corrupting the saved SP on certain stack-buffer layouts.
# A sized $12 segment makes the Loader allocate a larger bank-0 block (S set
# high in it), giving headroom and moving stack buffers off the collision.
# This is the idiomatic ORCA/GNO mechanism (== #pragma stacksize).
#
# Size ceiling for exec-into: KERNexecve loads the NEW program via GS/OS
# InitialLoad2 BEFORE freeing the outgoing image (kern/gno/sys.c:762 vs 912),
# so during an execve() BOTH the caller's and the callee's DP/Stack segments
# occupy bank 0 at once.  Bank 0 has ~48 KB usable for stacks, so a 16 KB
# default (== 32 KB transient across an exec) overflows and InitialLoad2 fails
# with a Memory Manager error that GNO maps to errno EIO -- exec-into never
# transfers control.  8 KB (16 KB transient) fits with margin and still gives
# ample GS/OS file-I/O headroom.  Verified: 8 KB and 4 KB exec cleanly, 16 KB
# does not (demos/gnoExecProbe -> gnoExeced under real GNO/MAME).
"$OMF" --input "$BIN" --map "$MAP" \
    --base 0x1000 --entry __start --output "$OUT" \
    --name "$(echo "$OUTBASE" | tr '[:lower:]' '[:upper:]' | cut -c1-8)" \
    --expressload --relocs "$RELOC" --stack-size "${GNO_STACK_SIZE:-0x2000}"

ls -la "$OUT"
if [ "$DEBUG" = 1 ]; then
    echo "debug sidecar: $DWARF"
    echo "map: $MAP"
fi
echo "done: $OUT  (run with: bash scripts/runInGno.sh $OUT --check 0x025000=C0DE)"
