#!/bin/sh
#
# Find the compiler fixed point and update the seed binary.
#
# Iterates self-compilation stages starting from the current seed until a fixed
# point is reached, then copies the result back to the seed.
#
# Usage: seed/update [--seed <path>] [--from-s0]
#
# Options:
#   --seed <path>            Use <path> as the seed binary instead of seed/radiance.rv64
#   --from-s0                Run the seed as a native binary (stage 0) instead of
#                            through the emulator
#
# Environment variables:
#   EMULATOR_MEMORY_SIZE     Emulator memory in KB (default: 385024)
#   EMULATOR_DATA_SIZE       Emulator data segment in KB (default: 348160)
#
set -eu

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"

cd "${ROOT_DIR}"

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

step() {
  printf "\n==> %s\n" "$*"
}

fail() {
  printf "!! %s\n" "$*" >&2
  exit 1
}

# ---------------------------------------------------------------------------
# Command line flags for Radiance compiler
# ---------------------------------------------------------------------------

STD_MODS="$(sed 's/^/-mod /' std.lib | tr '\n' ' ')"
OPTS="-pkg std ${STD_MODS} -pkg radiance -mod compiler/radiance.rad -entry radiance"

# ---------------------------------------------------------------------------
# Emulator settings
# ---------------------------------------------------------------------------

MEMORY_SIZE="${EMULATOR_MEMORY_SIZE:-385024}"
DATA_SIZE="${EMULATOR_DATA_SIZE:-348160}"
EMU="${RAD_EMULATOR:-emulator} -debug -no-validate \
    -memory-size=${MEMORY_SIZE} -data-size=${DATA_SIZE} \
    -count-instructions -run"

# ---------------------------------------------------------------------------
# Compile one self-hosted stage.
#   stage <input> <output>
# ---------------------------------------------------------------------------

stage() {
  time $EMU "$1" $OPTS -o "$2"
}

# ---------------------------------------------------------------------------
# Compile one stage using a native (s0) binary.
#   stageS0 <input> <output>
# ---------------------------------------------------------------------------

stageS0() {
  time "$1" compiler/radiance.rad $OPTS -o "$2"
}

# ---------------------------------------------------------------------------
# Compare two stage binaries. Returns 0 if identical, 1 if they differ.
# ---------------------------------------------------------------------------

compareStages() {
  if cmp -s "$1" "$2" \
  && cmp -s "$1.ro.data" "$2.ro.data" \
  && cmp -s "$1.rw.data" "$2.rw.data"; then
    printf " IDENTICAL\n  sha256: %s\n" "$(sha256sum "$2" | cut -d' ' -f1)"
    return 0
  else
    printf " DIFFERENT\n"
    return 1
  fi
}

# ---------------------------------------------------------------------------
# Copy the fixed-point binary to the seed.
# ---------------------------------------------------------------------------

updateSeed() {
  [ "$1"         != seed/radiance.rv64 ]         && cp "$1"         seed/radiance.rv64
  [ "$1.ro.data" != seed/radiance.rv64.ro.data ] && cp "$1.ro.data" seed/radiance.rv64.ro.data
  [ "$1.rw.data" != seed/radiance.rv64.rw.data ] && cp "$1.rw.data" seed/radiance.rv64.rw.data

  git rev-parse HEAD > seed/radiance.rv64.git

  printf "\n"
  printf "Fixed point reached at %s\n" "$2"
  printf "Seed updated (commit $(git rev-parse --short HEAD))\n"
}

# ---------------------------------------------------------------------------
# Parse arguments
# ---------------------------------------------------------------------------

SEED="seed/radiance.rv64"
FROM_S0=0

while [ $# -gt 0 ]; do
  case "$1" in
    --seed)
      [ $# -ge 2 ] || fail "--seed requires an argument"
      SEED="$(realpath "$2")"
      shift 2
      ;;
    --from-s0)
      FROM_S0=1
      shift
      ;;
    *)
      fail "unknown option: $1"
      ;;
  esac
done

command -v "${RAD_EMULATOR:-emulator}" >/dev/null 2>&1 \
  || fail "emulator not found: set RAD_EMULATOR or add 'emulator' to PATH"
[ -f "${SEED}" ] || fail "seed binary not found: ${SEED}"

# ---------------------------------------------------------------------------
# Stage 1: seed compiles source
# ---------------------------------------------------------------------------

S1=seed/radiance.rv64.s1
S2=seed/radiance.rv64.s2
S3=seed/radiance.rv64.s3
S4=seed/radiance.rv64.s4

step "Stage 1: S0 builds S1"
if [ "${FROM_S0}" -eq 1 ]; then
  stageS0 "${SEED}" "${S1}"
else
  stage "${SEED}" "${S1}"
fi

printf "\nComparing S0 and S1 ..."
if compareStages "${SEED}" "${S1}"; then
  updateSeed "${SEED}" "S0"
  exit 0
fi

# ---------------------------------------------------------------------------
# Stage 2: S1 compiles source
# ---------------------------------------------------------------------------

step "Stage 2: S1 builds S2"
stage "${S1}" "${S2}"

printf "\nComparing S1 and S2 ..."
if compareStages "${S1}" "${S2}"; then
  updateSeed "${S1}" "S1"
  exit 0
fi

# ---------------------------------------------------------------------------
# Stage 3: S2 compiles source
# ---------------------------------------------------------------------------

step "Stage 3: S2 builds S3"
stage "${S2}" "${S3}"

printf "\nComparing S2 and S3 ..."
if compareStages "${S2}" "${S3}"; then
  updateSeed "${S2}" "S2"
  exit 0
fi

fail "No fixed point reached after 3 stages"
