#!/usr/bin/env bash
#
# SPDX-License-Identifier: GPL-2.0-only

# Usage: [--debug] [path to xgcc/bin directory]

# Enable debug output
if [ "$1" = "--debug" ]; then
	shift
	set -x
fi

# GENERIC_COMPILER_PREFIX defaults to empty but can be used to override
# compiler search behavior
TMPFILE=""
XGCCPATH=$1

# payloads under payloads/external crossgcc path
if [ -d "$(pwd)/../../../../util/crossgcc/xgcc/bin/" ]
then
	XGCCPATH=${XGCCPATH:-"$(pwd)/../../../../util/crossgcc/xgcc/bin/"}
fi

# libpayload crossgcc path
if [ -d "$(pwd)/../../util/crossgcc/xgcc/bin/" ]
then
	XGCCPATH=${XGCCPATH:-"$(pwd)/../../util/crossgcc/xgcc/bin/"}
fi

# coreboot crossgcc path
if [ -d "$(pwd)/util/crossgcc/xgcc/bin/" ]
then
	XGCCPATH=${XGCCPATH:-"$(pwd)/util/crossgcc/xgcc/bin/"}
fi

die() {
	echo "ERROR: $*" >&2
	exit 1
}

clean_up() {
	if [ -n "$TMPFILE" ]; then
		rm -f "$TMPFILE" "$TMPFILE.c" "$TMPFILE.o"
	fi
}

trap clean_up EXIT


program_exists() {
	type "$1" >/dev/null 2>&1
}


if [ "$("${XGCCPATH}/iasl" 2>/dev/null | grep -c ACPI)" -gt 0 ]; then
	IASL=${XGCCPATH}/iasl
elif [ "$(iasl 2>/dev/null | grep -c ACPI)" -gt 0 ]; then
	IASL=iasl
fi

if program_exists "${XGCCPATH}/nasm" ; then
	NASM="${XGCCPATH}/nasm"
elif program_exists nasm; then
	NASM=nasm
fi

if program_exists "${XGCCPATH}/gcc"; then
	HOSTCC="${XGCCPATH}/gcc"
elif program_exists gcc; then
	HOSTCC=gcc
elif program_exists cc; then
	HOSTCC=cc
else
	die "no host compiler found"
fi

# Look for a C++ compiler (for kconfig's qconf), but don't fail if there is
# none, just set the compiler to false(1) which will break early enough if
# used while being less confusing than errors about "g not found" when
# "$HOSTCXX -g" evaluates to "-g" and make drops the leading dash.
if program_exists "${XGCCPATH}/g++"; then
	HOSTCXX="${XGCCPATH}/g++"
elif program_exists g++; then
	HOSTCXX=g++
elif program_exists c++; then
	HOSTCXX=c++
else
	HOSTCXX=false
fi

# try to find the core count using various methods
CORES="$(getconf _NPROCESSORS_ONLN 2>/dev/null)"
if [ -z "$CORES" ]; then
	NPROC=$(command -v nproc)
	if [ -n "$NPROC" ]; then
		CORES="$($NPROC)"
	fi
fi
if [ -z "$CORES" ]; then
	SYSCTL=$(command -v sysctl)
	if [ -n "$SYSCTL" ]; then
		CORES="$(${SYSCTL} -n hw.ncpu 2>/dev/null)"
	fi
fi
if [ -z "$CORES" ] && [ -f /proc/cpuinfo ]; then
	 CORES="$(grep 'processor' /proc/cpuinfo 2>/dev/null | wc -l)"
fi

cat <<EOF
# platform agnostic and host tools
XGCCPATH:=${XGCCPATH}
NASM:=${NASM}
IASL:=${IASL}
HOSTCC?=${HOSTCC}
HOSTCXX?=${HOSTCXX}
CPUS?=${CORES}

EOF

testcc() {
	local cc="$1"
	local cflags="$2"
	local tmp_c="$TMPFILE.c"
	local tmp_o="$TMPFILE.o"
	rm -f "$tmp_c" "$tmp_o"
	echo "void _start(void) {}" >"$tmp_c"
	"$cc" -nostdlib -Werror $cflags -c "$tmp_c" -o "$tmp_o" >/dev/null 2>&1
}

testld() {
	local gcc="$1"
	local cflags="$2"
	local ld="$3"
	local ldflags="$4"
	local tmp_o="$TMPFILE.o"
	local tmp_elf="$TMPFILE.elf"
	rm -f "$tmp_elf"
	testcc "$gcc" "$cflags" &&
	$ld -nostdlib -static $ldflags -o "$tmp_elf" "$tmp_o" >/dev/null 2>&1
}

testas() {
	local gccprefix="$1"
	local twidth="$2"
	local arch="$3"
	local use_dash_twidth="$4"
	local endian="$5"
	local obj_file="$TMPFILE.o"
	local full_arch="elf$twidth-$arch"

	rm -f "$obj_file"
	[ -n "$use_dash_twidth" ] && use_dash_twidth="--$twidth"
	[ -n "$endian" ] && endian="-$endian"
	"${gccprefix}as" $use_dash_twidth $endian -o "$obj_file" "$TMPFILE" \
		2>/dev/null || return 1

	# Check output content type.
	local obj_type="$(LANG=C LC_ALL='' "${gccprefix}"objdump -p "$obj_file" 2>/dev/null)"
	local obj_arch="$(expr "$obj_type" : '.*format \(.[a-z0-9-]*\)')"
	[ "$obj_arch" = "$full_arch" ] || return 1

	unset ASFLAGS LDFLAGS
	unset FLAGS_GCC CFLAGS_GCC CFLAGS_CLANG

	if [ -n "$use_dash_twidth" ]; then
		ASFLAGS="--$twidth"
		FLAGS_GCC="-m$twidth"
		CFLAGS_CLANG="-m$twidth"
		LDFLAGS="-b $full_arch"

	fi

	# Special parameters only available in dash_twidth mode.
	[ -n "$use_dash_twidth" ] && case "$full_arch" in
		"elf32-i386" )
			LDFLAGS="$LDFLAGS -melf_i386"
			FLAGS_GCC="$FLAGS_GCC -Wl,-b,elf32-i386 -Wl,-melf_i386"
			CFLAGS_CLANG="$CFLAGS_CLANG -Wl,-b,elf32-i386 -Wl,-melf_i386"
			;;
	esac

	return 0
}

detect_special_flags() {
	local architecture="$1"
	# Check for an operational -m32/-m64
	testcc "$GCC"     "$FLAGS_GCC -m$TWIDTH " &&
		FLAGS_GCC="$FLAGS_GCC -m$TWIDTH "

	# Use bfd linker instead of gold if available:
	testcc "$GCC"     "$FLAGS_GCC -fuse-ld=bfd" &&
		FLAGS_GCC="$FLAGS_GCC -fuse-ld=bfd" && LINKER_SUFFIX='.bfd'

	testcc "$GCC"     "$FLAGS_GCC -fno-stack-protector" &&
		FLAGS_GCC="$FLAGS_GCC -fno-stack-protector"
	testcc "$GCC"     "$FLAGS_GCC -Wl,--build-id=none" &&
		FLAGS_GCC="$FLAGS_GCC -Wl,--build-id=none"

	testcc "$GCC"      "$CFLAGS_GCC -Wno-address-of-packed-member $FLAGS_GCC" &&
		CFLAGS_GCC="$CFLAGS_GCC -Wno-address-of-packed-member"
	testcc "$GCC"      "$CFLAGS_GCC --param=min-pagesize=1024 $FLAGS_GCC" &&
		CFLAGS_GCC="$CFLAGS_GCC --param=min-pagesize=1024"
	case "$architecture" in
	x86)
		;;
	x64)
		;;
	arm64)
		testld "$GCC" "$FLAGS_GCC" "${GCCPREFIX}ld${LINKER_SUFFIX}" \
		  "$LDFLAGS --fix-cortex-a53-843419" && \
		  LDFLAGS_ARM64_A53_ERRATUM_843419+=" --fix-cortex-a53-843419"
		;;
	riscv)
		testcc "$GCC" "$FLAGS_GCC -march=rv64iadc_zicsr_zifencei" &&
		  ARCH_SUFFIX="_zicsr_zifencei"
		;;
	esac
}

detect_compiler_runtime() {
	test -z "$GCC" || \
	CC_RT_GCC="$(${GCC} ${CFLAGS_GCC} ${FLAGS_GCC} -print-libgcc-file-name)"
	if [ ${CLANG_RUNTIME} = "libgcc" ]; then
		CC_RT_CLANG=${CC_RT_GCC}
	else
		test -z "$CLANG" || \
		  CC_RT_CLANG="$(${CLANG} ${CFLAGS_CLANG} -print-libgcc-file-name 2>/dev/null)"
	fi
}

report_arch_toolchain() {
	cat <<EOF
# elf${TWIDTH}-${TBFDARCH} toolchain (${GCC})
ARCH_SUPPORTED+=${TARCH}
SUBARCH_SUPPORTED+=${TSUPP-${TARCH}}
ARCH_SUFFIX_${TARCH}:=${ARCH_SUFFIX}

# GCC
GCC_CC_${TARCH}:=${GCC}
GCC_CFLAGS_${TARCH}:=${CFLAGS_GCC} ${FLAGS_GCC}
# Generally available for GCC's cc1:
GCC_CFLAGS_${TARCH}+=-fno-delete-null-pointer-checks -Wlogical-op
GCC_ADAFLAGS_${TARCH}:=${FLAGS_GCC}
GCC_COMPILER_RT_${TARCH}:=${CC_RT_GCC}
GCC_COMPILER_RT_FLAGS_${TARCH}:=${CC_RT_EXTRA_GCC}
EOF
if [ "${TARCH}" = "x86_64" ]; then
cat <<EOF
	GCC_CFLAGS_${TARCH} += -malign-data=abi
EOF
fi
cat <<EOF

# Clang
CLANG_CC_${TARCH}:=${CLANG}
CLANG_CFLAGS_${TARCH}:=${CFLAGS_CLANG}
CLANG_CFLAGS_${TARCH}+=-Qunused-arguments -m${TWIDTH}
# tone down clang compiler warnings
CLANG_CFLAGS_${TARCH}+=-Wno-unused-variable -Wno-unused-function -Wno-tautological-compare
CLANG_CFLAGS_${TARCH}+=-Wno-shift-overflow -Wno-address-of-packed-member -Wno-initializer-overrides
CLANG_CFLAGS_${TARCH}+=-fbracket-depth=2048
CLANG_COMPILER_RT_${TARCH}:=${CC_RT_CLANG}
CLANG_COMPILER_RT_FLAGS_${TARCH}:=${CC_RT_EXTRA_CLANG}

# GCC/Clang Common
ifeq (\$(CONFIG_COMPILER_GCC)\$(CONFIG_LP_COMPILER_GCC),y)
	CC_${TARCH}:=\$(GCC_CC_${TARCH})
	CFLAGS_${TARCH}:=\$(GCC_CFLAGS_${TARCH})
	COMPILER_RT_${TARCH}:=\$(GCC_COMPILER_RT_${TARCH})
	COMPILER_RT_FLAGS_${TARCH}:=\$(GCC_COMPILER_RT_FLAGS_${TARCH})
else
	CC_${TARCH}:=\$(CLANG_CC_${TARCH})
	CFLAGS_${TARCH}:=\$(CLANG_CFLAGS_${TARCH})
	COMPILER_RT_${TARCH}:=\$(CLANG_COMPILER_RT_${TARCH})
	COMPILER_RT_FLAGS_${TARCH}:=\$(CLANG_COMPILER_RT_FLAGS_${TARCH})
endif
EOF

# Generally the x86 should build for i686 -- no sse/mmx
# instructions since SMM modules are compiled using these
# flags. Note that this doesn't prevent a project using
# xcompile to explicitly specify -mmsse, etc flags.
# The Quark processor doesn't support the instructions
# introduced with the Pentium 6 architecture, so allow it
# to use i586 instead.
if [ "${TARCH}" = "x86_64" ]; then
cat <<EOF
	CFLAGS_${TARCH} += -march=nocona
EOF
fi

if [ "${TARCH}" = "x86_32" ]; then
cat <<EOF

CFLAGS_${TARCH} += -march=i686
EOF
fi

cat <<EOF

CPP_${TARCH}:=${GCCPREFIX}cpp
AS_${TARCH}:=${GCCPREFIX}as ${ASFLAGS}
LD_${TARCH}:=${GCCPREFIX}ld${LINKER_SUFFIX} ${LDFLAGS}
GCOV_${TARCH}:=${GCCPREFIX}gcov
EOF

	if [ "${TARCH}" = "arm64" ] && \
			[ -n "${LDFLAGS_ARM64_A53_ERRATUM_843419}" ]; then
		cat <<EOF

ifeq (\$(CONFIG_ARM64_A53_ERRATUM_843419),y)
	LD_${TARCH}+=${LDFLAGS_ARM64_A53_ERRATUM_843419}
endif

EOF
	fi # if [ "${TARCH}" = "arm64" ]...

	cat <<EOF
ifeq (\$(CONFIG_COMPILER_GCC)\$(CONFIG_LP_COMPILER_GCC),y)
	NM_${TARCH}:=${GCCPREFIX}gcc-nm
	AR_${TARCH}:=${GCCPREFIX}gcc-ar
else
	NM_${TARCH}:=${GCCPREFIX}nm
	AR_${TARCH}:=${GCCPREFIX}ar
endif
OBJCOPY_${TARCH}:=${GCCPREFIX}objcopy
OBJDUMP_${TARCH}:=${GCCPREFIX}objdump
READELF_${TARCH}:=${GCCPREFIX}readelf
STRIP_${TARCH}:=${GCCPREFIX}strip
GNATBIND_${TARCH}:=${GCCPREFIX}gnatbind
CROSS_COMPILE_${TARCH}:=${GCCPREFIX}


EOF
#The two blank lines above are intentional separators
}

# Architecture definitions
SUPPORTED_ARCHITECTURES="arm arm64 riscv x64 x86 ppc64"

# TARCH: local name for the architecture
# (used as CC_${TARCH} in the build system)
# TBFDARCHS: architecture name in binutils (eg elf32-${TBFDARCH})
# TCLIST: first part of the compiler triplet (eg i386 in i386-elf)
# TWIDTH: numerical argument for cpu mode: gcc -m${TWIDTH}
# TSUPP: supported subarchs (for -mcpu=...)
# TABI: typically elf, eabi or linux

arch_config_arm() {
	TARCH="arm"
	TBFDARCHS="littlearm"
	TCLIST="armv7-a armv7a arm"
	TWIDTH="32"
	TSUPP="arm armv4 armv7 armv7_m armv7_r"
	TABI="eabi"
}

arch_config_arm64() {
	TARCH="arm64"
	TBFDARCHS="littleaarch64"
	TCLIST="aarch64"
	TWIDTH="64"
	TSUPP="arm64 armv8_64"
	TABI="elf"
}

arch_config_riscv() {
	TARCH="riscv"
	TBFDARCHS="littleriscv"
	TCLIST="riscv64 riscv"
	TWIDTH="64"
	TABI="elf"
}

arch_config_x64() {
	TARCH="x86_64"
	TBFDARCHS="x86-64"
	TCLIST="x86_64"
	TWIDTH="64"
	TABI="elf"
}

arch_config_x86() {
	TARCH="x86_32"
	TBFDARCHS="i386"
	TCLIST="i386 x86_64"
	TWIDTH="32"
	TABI="elf"
	CC_RT_EXTRA_GCC="--wrap __divdi3 --wrap __udivdi3 --wrap __moddi3 --wrap __umoddi3"
}

arch_config_ppc64() {
	TARCH="ppc64"
	TBFDARCHS="powerpc"
	TCLIST="powerpc64"
	TWIDTH="64"
	TSUPP="ppc64"
	TABI="linux-gnu" # there is no generic ABI on ppc64
	CC_RT_EXTRA_GCC="-mcpu=power8 -mbig-endian"
}

# Right now, the clang reference toolchain is not building compiler-rt builtins
# for any of the cross compile architectures. Hence we use libgcc for now,
# because that is available and lets us proceed with getting coreboot clang
# ready. Toggle CLANG_RUNTIME if you want to experiment with compiler-rt.

CLANG_RUNTIME="libgcc"
# CLANG_RUNTIME="compiler-rt"

test_architecture() {
	local architecture=$1
	local endian gccprefix search

	GCCPREFIX="invalid"
	unset TABI TARCH TBFDARCH TCLIST TENDIAN TSUPP TWIDTH
	unset CC_RT_EXTRA_GCC CC_RT_EXTRA_CLANG
	unset GCC CLANG
	unset ARCH_SUFFIX
	if type "arch_config_$architecture" > /dev/null; then
		"arch_config_$architecture"
	else
		die "no architecture definition for $architecture"
	fi

	# To override toolchain, define CROSS_COMPILE_$arch or CROSS_COMPILE as
	# environment variable.
	# Ex: CROSS_COMPILE_arm="armv7a-cros-linux-gnueabi-"
	#     CROSS_COMPILE_x86="i686-pc-linux-gnu-"
	search="$(eval echo "\$CROSS_COMPILE_$architecture" 2>/dev/null)"
	search="$search $CROSS_COMPILE"
	for toolchain in $TCLIST; do
		search="$search $XGCCPATH$toolchain-$TABI-"
		search="$search $toolchain-$TABI-"
		search="$search $toolchain-linux-gnu-"
		search="$search $toolchain-linux-"
		search="$search $toolchain-"
		search="$search $toolchain-linux-gnueabi-"
	done
	echo "###########################################################################"
	echo "# $architecture"
	echo "# TARCH_SEARCH=$search"

	# Search toolchain by checking assembler capability.
	for TBFDARCH in $TBFDARCHS; do
		for gccprefix in $search "$GENERIC_COMPILER_PREFIX"; do
			program_exists "${gccprefix}as" || continue
			for endian in $TENDIAN ""; do
				{ testas "$gccprefix" "$TWIDTH" "$TBFDARCH" \
					"" "$endian" ||
				testas "$gccprefix" "$TWIDTH" "$TBFDARCH" \
					"TRUE" "$endian" ; } && \
				testcc "${gccprefix}gcc" "$CFLAGS_GCC" "$FLAGS_GCC" && \
				GCCPREFIX="$gccprefix" && \
				break 3
			done
		done
	done
	if [ "invalid" != "$GCCPREFIX" ]; then
		GCC="${GCCPREFIX}gcc"
	fi

	for clang_arch in $TCLIST invalid; do
		for clang_prefix in $search $XGCCPATH "$GENERIC_COMPILER_PREFIX"; do
			testcc "${clang_prefix}clang" "-target ${clang_arch}-$TABI -c" && break 2
		done
	done

	if [ "invalid" != "$clang_arch" ]; then
		# FIXME: this may break in a clang && !gcc configuration,
		# but that's more of a clang limitation. Let's be optimistic
		# that this will change in the future.
		CLANG="${clang_prefix}clang"
		CFLAGS_CLANG="-target ${clang_arch}-${TABI} $CFLAGS_CLANG"
	fi
}

OUT="$(mktemp /tmp/temp.XXXXXX 2>/dev/null || echo /tmp/temp.coreboot.$RANDOM)"
rm -f $OUT

for architecture in $SUPPORTED_ARCHITECTURES; do
	(
	TMPFILE="$(mktemp /tmp/temp.XXXXXX 2>/dev/null || echo /tmp/temp.coreboot.$RANDOM)"
	touch $TMPFILE
	test_architecture "$architecture"
	detect_special_flags "$architecture"
	detect_compiler_runtime "$architecture"
	report_arch_toolchain
	clean_up
	) > $OUT.$architecture &
done
wait

for architecture in $SUPPORTED_ARCHITECTURES; do
	cat $OUT.$architecture
	rm -f $OUT.$architecture
done
echo XCOMPILE_COMPLETE:=1
