#!/usr/bin/env ksh

set -eu

die() {
  printf '%s\n' "$2" >&2
  exit "$1"
}

if [ -z "${1:-}" ]; then
  die 1 "$(basename "$0"): usage: $0 <aarch64-sysroot>"
else
  case "$1" in
    /*) sysroot_tarball="$1" ;;
    *) sysroot_tarball="$PWD/$1" ;;
  esac
fi

ghc_version=9.10.1

# NOTE: I think this is only needed for (xz) tarball-based builds, but
# I'm not sure; wrap it in a condition for $FETCH_TARBALL_SOURCETREE if
# you want. libffi archive mentioned below is a gzip archive, libgmp is
# not vendored in an archive, but it must be somewhere in the tree. You
# can download the GHC source as a gzip archive, but I didn't, and I'm
# trying to keep everything as close as I can to how I did it, so that
# means installing xz, and if we do that, we might as well fetch the xz
# source tarball instead of the gzip one.
doas pkg_add xz # source tarball extractor
if [ "${FETCH_TARBALL_SOURCES:-}" != 1 ]; then
  doas pkg_add git
fi
doas pkg_add autoconf%2.71 automake%1.16 # for Git-based builds
# NOTE: the built-in compiler toolchain seems to only be able to target
# the same arch as it runs on itself.
doas pkg_add llvm%13 ghc # C & Haskell compilers (build dependencies)
# NOTE: libiconv may not be needed, it's installed in the arm64 sysroot.
# NOTE: gmp may not be needed, I had to use --with-intree-gmp.
doas pkg_add libiconv gmp alex happy # runtime dependencies
# NOTE: I think gmake is needed for using the in-tree libffi. Python is
# needed for `./boot`, and I'm not sure what else.
doas pkg_add python%3 gmake # misc. build dependencies
# NOTE: the build system calls tar without `-z` to extract libffi, which
# fails on OpenBSD tar, as it fails to recognise it's compressed, so
# while in principle it's just needed if making a bindist, in practice
# it's just needed.
doas pkg_add gtar # needed if making a bindist
doas pkg_add cabal-install bash # for Hadrian

if [ "${FETCH_TARBALL_SOURCES:-}" = 1 ]; then
  ghc_tarball="ghc-$ghc_version-src.tar.xz"

  curl -O "https://downloads.haskell.org/~ghc/$ghc_version/$ghc_tarball"

  if [ 'bf386a302d4ee054791ffd51748900f15d71760fd199157922d120cc1f89e2f7' != "$(sha256 -q "$ghc_tarball")" ]; then
    die 2 "$(basename "$0"): source tarball integrity check failed"
  fi

  xzcat "$ghc_tarball" | tar xf -
  rm "$ghc_tarball"
  cd "ghc-$ghc_version"
else
  # Clone the GHC repo w/ submodules, & checkout the ghc-9.10.1-release
  # tag, which will put us in a detached HEAD state…
  git clone \
    --single-branch \
    --branch ghc-9.10.1-release \
    --recurse-submodules \
    https://gitlab.haskell.org/ghc/ghc.git
  cd ghc
  # …then create a new branch from that tag. Not strictly necessary, but
  # nice for hacking around :)
  git checkout -b ghc-9.10.1-openbsd

  if [ '6d779c0fab30c39475aef50d39064ed67ce839d7' != "$(git log -1 --format=%H)" ]; then
    die 2 "$(basename "$0"): git repo integrity check failed"
  fi
fi

patch -p1 <<'PATCH'
diff --git a/hadrian/src/Hadrian/Haskell/Cabal/Parse.hs b/hadrian/src/Hadrian/Haskell/Cabal/Parse.hs
index 178bf86518..b7a5979122 100644
--- a/hadrian/src/Hadrian/Haskell/Cabal/Parse.hs
+++ b/hadrian/src/Hadrian/Haskell/Cabal/Parse.hs
@@ -208,7 +208,7 @@ copyPackage context@Context {..} = do
 
 shakeVerbosityToCabalFlag :: Verbosity -> String
 shakeVerbosityToCabalFlag = \case
-    Diagnostic -> "-v2"
+    Diagnostic -> "-v3"
     Verbose -> "-v1"
     -- Normal levels should not produce output to stdout
     Silent -> "-v0"
diff --git a/hadrian/src/Settings/Builders/Configure.hs b/hadrian/src/Settings/Builders/Configure.hs
index 49f4976729..910b64e22d 100644
--- a/hadrian/src/Settings/Builders/Configure.hs
+++ b/hadrian/src/Settings/Builders/Configure.hs
@@ -17,7 +17,8 @@ configureBuilderArgs = do
                 pure $ [ "--enable-shared=no"
                      , "--with-pic=yes"
                      , "--host=" ++ targetPlatform    -- GMP's host is our target
-                     , "--build=" ++ buildPlatform ]
+                     , "--build=" ++ buildPlatform
+                     , "LDFLAGS=--target=" ++ targetPlatform ]
                      -- Disable GMP's alloca usage on wasm32, it may
                      -- cause stack overflow (#22602) due to the
                      -- rather small 64KB default stack size. See
@@ -37,4 +38,5 @@ configureBuilderArgs = do
                             ++ (if wayUnit Dynamic way
                                     then "yes"
                                     else "no")
-                     , "--host=" ++ targetPlatform ] ]
+                     , "--host=" ++ targetPlatform
+                     , "LDFLAGS=--target=" ++ targetPlatform ] ]
diff --git a/llvm-targets b/llvm-targets
index 213a2ea4d6..c2bbf71024 100644
--- a/llvm-targets
+++ b/llvm-targets
@@ -59,5 +59,6 @@
 ,("armv7-unknown-freebsd-gnueabihf", ("e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64", "generic", "+strict-align"))
 ,("aarch64-unknown-netbsd", ("e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128", "generic", "+neon"))
 ,("x86_64-unknown-openbsd", ("e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128", "x86-64", "+retpoline-indirect-calls +retpoline-indirect-branches"))
+,("aarch64-unknown-openbsd", ("e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128", "generic", "+neon +strict-align"))
 ,("arm-unknown-nto-qnx-eabi", ("e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64", "arm7tdmi", "+strict-align"))
 ]
diff --git a/m4/fp_check_timer_create.m4 b/m4/fp_check_timer_create.m4
index 957772ba45..7abe23b027 100644
--- a/m4/fp_check_timer_create.m4
+++ b/m4/fp_check_timer_create.m4
@@ -11,7 +11,15 @@ then
   then
     # We can't test timer_create when we're cross-compiling, so we
     # optimistiaclly assume that it actually works properly.
-    AC_DEFINE([USE_TIMER_CREATE], 1,  [Define to 1 if we can use timer_create(CLOCK_REALTIME,...)])
+
+    # This assumption is false on OpenBSD, so we make an exception. It
+    # implements an empty `struct sigevent, and a `timer_create` which
+    # simply returns -1 (on all architectures) w/ an ENOSYS ("Function
+    # not implemented").
+    if test "$HostOS" != "openbsd"
+    then
+      AC_DEFINE([USE_TIMER_CREATE], 1,  [Define to 1 if we can use timer_create(CLOCK_REALTIME,...)])
+    fi
   else
   AC_CACHE_CHECK([for a working timer_create(CLOCK_REALTIME)],
     [fptools_cv_timer_create_works],
diff --git a/utils/llvm-targets/gen-data-layout.sh b/utils/llvm-targets/gen-data-layout.sh
index e5ead7ddeb..985b155667 100755
--- a/utils/llvm-targets/gen-data-layout.sh
+++ b/utils/llvm-targets/gen-data-layout.sh
@@ -124,6 +124,7 @@ TARGETS=(
     #########################
 
     "x86_64-unknown-openbsd"
+    "aarch64-unknown-openbsd"
 
     #########################
     # Other
PATCH

# Usually only needed for Git-based builds, but we need this since we
# patch the files which generate the configure scripts, and so have to
# run the ./boot script which source tarball-based builds don't usually
# have to do.
export AUTOCONF_VERSION=2.71
export AUTOMAKE_VERSION=1.16

export PATH="/usr/local/llvm13/bin:$PATH"
# NOTE: I'm not sure why this is needed; is it even? I don't recall if I
# tested without it, I just added it when I added llvm13 to the $PATH.
export LD_LIBRARY_PATH=/usr/local/llvm13/lib
export SYSROOT="$PWD/aarch64-sysroot"

mkdir aarch64-sysroot
tar xzf "$sysroot_tarball" -C aarch64-sysroot

# LLVM 13 from ports supports cross-compilation and the configure script
# does recognise it, but not the bintools prefixed with `llvm-`, and it
# falls back on the built-in single-target only Clang, so we explicitly
# specify the ports versions here.
export \
  AR=/usr/local/bin/llvm-ar-13 \
  RANLIB=/usr/local/bin/llvm-ranlib-13 \
  LD=/usr/local/bin/ld.lld-13 \
  OBJDUMP=/usr/local/bin/llvm-objdump-13 \
  NM=/usr/local/bin/llvm-nm-13 \
  LLVMAS=/usr/local/bin/llvm-as-13

# Checks for boot packages, and generates configure scripts
./boot

# $CONF_{GCC_LINKER,CC}_OPTS_STAGE{1,2} seem to be passed to the linker
# & compiler, but not $CONF_LD_LINKER_OPTS_STAGE{1,2}; reading configure
# indicates that GCC_LINKER vs. LD refers to the distinction between the
# linker called as `ld` vs. called by calling the compiler binary with a
# flag, not LLVM vs. GCC's linker.
OPENBSD_CC_OPTS='-Wl,--no-execute-only -Qunused-arguments -Wl,-z,nobtcfi'

# NOTE: $SYSROOT must be temporarily disabled, because we only want the
# cc shim to set --sysroot later on in the process. Come to think of it,
# I guess it should only be necessary for the stage 2 compiler,
#
# NOTE: probably $PWD/aarch64-sysroot can be replaced with $SYSROOT, as
# the shell should expand it before calling env, but I was paranoid when
# I initially ran this, and I'm giving everything as close to how I ran
# it as I can to allow others to reproduce my progress.
env \
  SYSROOT= \
  CC="$PWD/bin/aarch64-unknown-openbsd-cc" \
  CONF_CC_OPTS_STAGE1="--target=aarch64-unknown-openbsd --sysroot=$PWD/aarch64-sysroot" \
  CONF_CC_OPTS_STAGE2="$OPENBSD_CC_OPTS --target=aarch64-unknown-openbsd --sysroot=$PWD/aarch64-sysroot" \
  CONF_CPP_OPTS_STAGE1="-isysroot=$PWD/aarch64-sysroot" \
  CONF_CPP_OPTS_STAGE2="-isysroot=$PWD/aarch64-sysroot" \
  CONF_GCC_LINKER_OPTS_STAGE0="$OPENBSD_CC_OPTS" \
  CONF_GCC_LINKER_OPTS_STAGE1="$OPENBSD_CC_OPTS --target=aarch64-unknown-openbsd --sysroot=$PWD/aarch64-sysroot" \
  CONF_GCC_LINKER_OPTS_STAGE2="$OPENBSD_CC_OPTS --target=aarch64-unknown-openbsd --sysroot=$PWD/aarch64-sysroot" \
  ./configure \
  --target=aarch64-unknown-openbsd \
  --with-iconv-libraries="$PWD"/aarch64-sysroot/usr/local/lib \
  --with-iconv-includes="$PWD"/aarch64-sysroot/usr/local/include \
  --with-intree-gmp

cabal update

# Stage 0 in the following command line arguments refers to that which
# is built by stage 0, meaning stage 1. The second instance of stage 0
# in the preceding sentence refers to the GHC installed on the system,
# in this case from ports.
#
# The empty --configure-option=LDFLAGS= is necessary IIRC to counteract
# the issue of --host, in an earlier LDFLAGS=, being set to the --target
# passed to configure.
#
# The --target= in the stage 1 build doesn't, at first glance, appear to
# be necessary, as it seems to already get passed by the build system by
# default when you look at the logs, but it reliably fixed an issue.
# Both flags in the stage 1 build arguments were 100% necessary; I can't
# say that definitively about all the arguments for the stage 2 build.
#
# Lacking $LANG breaks something quite late in the build, stage 2 IIRC
# (which, again, doesn't make much sense for it to only break on stage
# 2, but whatever; try stage 1 & 2 separately with and without it if you
# like).
#
# Rerun the exact same command with stage1:exe:ghc-bin instead of
# stage2: to get the _build/ghc-stage1 script, as _build/ghc-stage2 only
# depends on the stage 1 binary deeper in the _build directory. The
# scripts simply call the original built binaries with some arguments to
# point to the built package databases, again IIRC.
#
# NOTE: I went overboard on the --ghc-option and -opt{c,a,l,P}; I won't
# even attempt to ask which are necessary and which aren't.
#
# NOTE: I think when I created the shim, I was not rebuilding stage 1
# each time, so it was meant to be temporary to see if I could get stage
# 2 to build by passing --sysroot via $SYSROOT. It doesn't make sense to
# me that it doesn't break the stage 1 build, but I guess it doesn't.
env \
  LANG=en_US.UTF-8 \
  MAKE=gmake \
  hadrian/build \
  --docs=none \
  --flavour=quickest \
  "stage0.*.cabal.configure.opts += \
    --configure-option=LDFLAGS= \
    --configure-option=--target=aarch64-unknown-openbsd" \
  "stage1.*.cabal.configure.opts += \
    --configure-option=CPPFLAGS=--sysroot=aarch64-sysroot \
    --ghc-option=-optc--target=aarch64-unknown-openbsd \
    --ghc-option=-optl--host=aarch64-unknown-openbsd \
    --ghc-option=-optl--target=aarch64-unknown-openbsd \
    --ghc-option=-opta--target=aarch64-unknown-openbsd" \
  "stage1.*.ghc.hs.opts += \
    -optc--target=aarch64-unknown-openbsd \
    -opta--target=aarch64-unknown-openbsd \
    -optl--host=aarch64-unknown-openbsd \
    -optl--target=aarch64-unknown-openbsd \
    -optl--sysroot=$SYSROOT" \
  "stage1.*.ghc.c.opts += \
    -optc--target=aarch64-unknown-openbsd \
    -optl--host=aarch64-unknown-openbsd \
    -optl--target=aarch64-unknown-openbsd \
    -opta--target=aarch64-unknown-openbsd \
    -optl--sysroot=$SYSROOT \
    -optP--sysroot=$SYSROOT" \
  "stage1.*.ghc.link.opts += \
    -optc--target=aarch64-unknown-openbsd \
    -optl--host=aarch64-unknown-openbsd \
    -optl--target=aarch64-unknown-openbsd \
    -opta--target=aarch64-unknown-openbsd \
    -optl--sysroot=$SYSROOT" \
  stage2:exe:ghc-bin
