#!/usr/bin/env bash
#
# This file is part of the coreboot project.
#
# Copyright (C) 2007-2010 coresystems GmbH
# Copyright (C) 2012 Google Inc
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#

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

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

TMPFILE=""
XGCCPATH=$1

# 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
}

# Create temporary file(s).
TMPFILE="$(mktemp /tmp/temp.XXXX 2>/dev/null || echo /tmp/temp.78gOIUGz)"
touch "$TMPFILE"
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 gcc; then
	HOSTCC=gcc
elif program_exists cc; then
	HOSTCC=cc
else
	die "no host compiler found"
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
IASL:=${IASL}
HOSTCC?=${HOSTCC}
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

	# Architecture matched.
	GCCPREFIX="$gccprefix"

	unset ASFLAGS LDFLAGS
	unset CFLAGS_GCC CFLAGS_CLANG

	if [ -n "$use_dash_twidth" ]; then
		ASFLAGS="--$twidth"
		CFLAGS_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"
			CFLAGS_GCC="$CFLAGS_GCC -Wl,-b,elf32-i386 -Wl,-melf_i386"
			CFLAGS_CLANG="$CFLAGS_GCC -Wl,-b,elf32-i386 -Wl,-melf_i386"
			;;
	esac

	return 0
}

detect_special_flags() {
	local architecture="$1"
	# GCC 4.6 is much more picky about unused variables.
	# Turn off it's warnings for now:
	testcc "$GCC"      "$CFLAGS_GCC -Wno-unused-but-set-variable " &&
		CFLAGS_GCC="$CFLAGS_GCC -Wno-unused-but-set-variable "

	# Check for an operational -m32/-m64
	testcc "$GCC"      "$CFLAGS_GCC -m$TWIDTH " &&
		CFLAGS_GCC="$CFLAGS_GCC -m$TWIDTH "

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

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

	case "$architecture" in
	x86)
		;;
	x64)
		;;
	arm64)
		testld "$GCC" "$CFLAGS_GCC" "${GCCPREFIX}ld${LINKER_SUFFIX}" \
		  "$LDFLAGS --fix-cortex-a53-843419" && \
		  LDFLAGS_ARM64_A53_ERRATUM_843419+=" --fix-cortex-a53-843419"
		;;
	mipsel)
		testcc "$GCC" "$CFLAGS_GCC -mno-abicalls -fno-pic" && \
		  CFLAGS_GCC+=" -mno-abicalls -fno-pic"

		# Enforce little endian mode.
		testcc "$GCC" "$CFLAGS_GCC -EL" && \
		  CFLAGS_GCC+=" -EL"
		;;
	esac
}

detect_compiler_runtime() {
	test -z "$CLANG" || \
	CC_RT_CLANG="$(${CLANG} ${CFLAGS_CLANG} -print-librt-file-name 2>/dev/null)"
	test -z "$GCC" || \
	CC_RT_GCC="$(${GCC} ${CFLAGS_GCC} -print-libgcc-file-name)"
}

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

# GCC
GCC_CC_${TARCH}:=${GCC}
GCC_CFLAGS_${TARCH}:=${CFLAGS_GCC}
GCC_COMPILER_RT_${TARCH}:=${CC_RT_GCC}
GCC_COMPILER_RT_FLAGS_${TARCH}:=${CC_RT_EXTRA_GCC}
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" ] || [ "${TARCH}" = "x86_32" ]; then
cat <<EOF

ifeq (\$(CONFIG_USE_MARCH_586),y)
	GCC_CFLAGS_${TARCH} += -march=i586
else
	GCC_CFLAGS_${TARCH} += -march=i686
endif
EOF
fi

cat <<EOF

# Clang
CLANG_CC_${TARCH}:=${CLANG}
CLANG_CFLAGS_${TARCH}:=${CFLAGS_CLANG}
CLANG_CFLAGS_${TARCH}+=-no-integrated-as -Qunused-arguments -m${TWIDTH}
# tone down clang compiler warnings
CLANG_CFLAGS_${TARCH}+=-Wno-unused-variable -Wno-unused-function -Wno-tautological-compare -Wno-shift-overflow
CLANG_COMPILER_RT_${TARCH}:=${CC_RT_CLANG}
CLANG_COMPILER_RT_FLAGS_${TARCH}:=${CC_RT_EXTRA_CLANG}

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

CPP_${TARCH}:=${GCCPREFIX}cpp
AS_${TARCH}:=${GCCPREFIX}as ${ASFLAGS}
LD_${TARCH}:=${GCCPREFIX}ld${LINKER_SUFFIX} ${LDFLAGS}
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
NM_${TARCH}:=${GCCPREFIX}nm
OBJCOPY_${TARCH}:=${GCCPREFIX}objcopy
OBJDUMP_${TARCH}:=${GCCPREFIX}objdump
READELF_${TARCH}:=${GCCPREFIX}readelf
STRIP_${TARCH}:=${GCCPREFIX}strip
AR_${TARCH}:=${GCCPREFIX}ar
GNATBIND_${TARCH}:=${GCCPREFIX}gnatbind
CROSS_COMPILE_${TARCH}:=${GCCPREFIX}


EOF
#The two blank lines above are intentional separators
}

# Architecture definitions
SUPPORTED_ARCHITECTURES="arm arm64 mipsel riscv x64 x86 power8"

# 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"
	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="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_mipsel() {
	TARCH="mips"
	TBFDARCHS="tradlittlemips littlemips"
	TCLIST="mipsel"
	TWIDTH="32"
	TSUPP="mips mipsel"
	TABI="elf"
	TENDIAN="EL"
}

arch_config_power8() {
	TARCH="power8"
	TBFDARCHS="powerpcle"
	TCLIST="powerpc64 powerpc64le"
	TWIDTH="64"
	TSUPP="power8"
	TABI="linux" # there is no generic ABI on ppc64
	CC_RT_EXTRA_GCC="-mcpu=power8 -mlittle-endian"
}

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
	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 ""; do
			program_exists "${gccprefix}as" || continue
			for endian in $TENDIAN ""; do
				testas "$gccprefix" "$TWIDTH" "$TBFDARCH" \
					"" "$endian" && break 3
				testas "$gccprefix" "$TWIDTH" "$TBFDARCH" \
					"TRUE" "$endian" && break 3
			done
		done
	done
	if [ "invalid" != "$GCCPREFIX" ]; then
		GCC="${GCCPREFIX}gcc"
	fi

	for clang_arch in $TCLIST invalid; do
		testcc "${XGCCPATH}clang" "-target ${clang_arch}-$TABI -c" && break
	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="${XGCCPATH}clang"
		CFLAGS_CLANG="-target ${clang_arch}-${TABI} $CFLAGS_CLANG -ccc-gcc-name ${GCC}"
	fi
}

# This loops over all supported architectures.
for architecture in $SUPPORTED_ARCHITECTURES; do
	test_architecture "$architecture"
	detect_special_flags "$architecture"
	detect_compiler_runtime "$architecture"
	report_arch_toolchain
done
echo XCOMPILE_COMPLETE:=1
