#!/bin/bash
# Copyright 2024 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2

EAPI=8
source tests-common.sh || exit

inherit zig-utils

# Set ZIG_TEST_COMPILATION env-var to "1" if you want to test binary
# compilation and running under QEMU. Assumes "zig" is present in PATH
# and qemu binfmt is enabled.
#
# If CPU is marked with ":no-run", it means program compiled for it
# successfully but crashed when running under QEMU.
if [[ "${ZIG_TEST_COMPILATION:-0}" -eq 1 ]]; then
	MY_WORKDIR="$(mktemp -d)"
	my_cleanup() {
		popd > /dev/null
		rm -rf "${MY_WORKDIR}"
	}
	trap 'my_cleanup' EXIT

	pushd "${MY_WORKDIR}" > /dev/null || die
	cat <<- _EOF_ > main.zig || die
		const std = @import("std");

		pub fn main() !void {
			const stdout = std.io.getStdOut();
			try stdout.writeAll("Hello, Gentoo!\n");
		}
	_EOF_

	zig_test_compile_and_run() {
		eindent
		einfo "${1}: ${2}"
		eoutdent

		if [[ "${2}" == native* ]]; then
			return 0
		fi

		if [[ "${1}" == arm* ]]; then
			# Bunch of unimplemented Zig
			# routines for more "bare-bone" arm.
			case "${2}" in
				# Some errors in inline assembly, likely too low baseline
				generic-soft_float | generic+soft_float) return 0;;
				# undefined symbol: __sync_fetch_and_add_1
				# compiler-rt not implemented in upstream for it yet:
				# https://github.com/ziglang/zig/issues/4959
				generic+v5te-soft_float) return 0;;
				*) ;;
			esac
		fi

		local ret=0

		local args=(
			-target "${1}"
			-mcpu "${2//:no-run/}"
			main.zig
		)
		if ! zig build-exe "${args[@]}" > /dev/null; then
			eerror "Failed to compile for: ${1}, ${2}"
			((++ret))
			return ${ret}
		fi

		# Can't run macOS binaries in Linux user-mode QEMU emulators
		if [[ "${1}" == *macos* || "${2}" == *":no-run" ]]; then
			return ${ret}
		fi

		if ! ./main > /dev/null; then
			eerror "Failed to run for: ${1}, ${2}"
			((++ret))
		fi

		return ${ret}
	}
else
	zig_test_compile_and_run() { :; }
fi

test-convert_c_env_to_zig_tuple() {
	local ret=0

	local -n map=${1}

	local c_tuple
	for key in "${!map[@]}"; do
		local expected="${map["${key}"]}"

		local c_tuple
		local c_flags
		if [[ "${key}" == *:* ]]; then
			c_tuple="${key%%:*}"
			c_flags="${key##*:}"
		else
			c_tuple="${key}"
			c_flags=""
		fi

		local actual="$(zig-utils_c_env_to_zig_target "${c_tuple}" "${c_flags}")"
		if [[ "${expected}" != "${actual}" ]]; then
			eerror "Translating ${c_tuple}: expected ${expected}, found ${actual}"
			((++ret))
			continue
		fi

		local zig_cpu="$(zig-utils_c_env_to_zig_cpu "${c_tuple}" "${c_flags}")"
		if ! zig_test_compile_and_run "${expected}" "${zig_cpu}"; then
			((++ret))
			continue
		fi
	done

	return ${ret}
}

test-convert_c_env_to_zig_cpu() {
	local ret=0

	local -n map=${1}
	local chost=${2}

	local c_flags
	for c_flags in "${!map[@]}"; do
		local expected="${map["${c_flags}"]}"
		local actual="$(zig-utils_c_env_to_zig_cpu "${chost}" "${c_flags}")"
		if [[ "${expected//:no-run/}" != "${actual}" ]]; then
			eerror "Translating ${c_flags}: expected ${expected//:no-run/}, found ${actual}"
			((++ret))
			continue
		fi

		local zig_target="$(zig-utils_c_env_to_zig_target "${chost}" "${c_flags}")"
		if ! zig_test_compile_and_run "${zig_target}" "${expected}"; then
			((++ret))
			continue
		fi
	done

	return ${ret}
}

tbegin '"C tuple to Zig tuple"'
declare -A c_to_zig_map=(
	# Just remove "vendor" field
	[aarch64-unknown-linux-gnu]=aarch64-linux-gnu
	[arm-unknown-linux-musleabi]=arm-linux-musleabi
	[x86_64-pc-linux-gnu]=x86_64-linux-gnu
	[loongarch64-unknown-linux-gnu]=loongarch64-linux-gnu
	[powerpc64le-unknown-linux-gnu]=powerpc64le-linux-gnu

	# ARM big-endian
	[armeb-unknown-linux-gnueabi]=armeb-linux-gnueabi

	# https://bugs.gentoo.org/924920
	[armv7a-unknown-linux-gnueabihf]=arm-linux-gnueabihf

	# ARM to Thumb
	[arm-unknown-linux-musleabi:"-march=armv7e-m"]=thumb-linux-musleabi

	# ARM families
	[armv6j-unknown-linux-gnueabihf]=arm-linux-gnueabihf
	[armv6j-linux-gnueabihf]=arm-linux-gnueabihf
	[armv7a-softfp-linux-gnueabi]=arm-linux-gnueabi
	[armv7a-linux-gnueabi]=arm-linux-gnueabi

	# X86 (32-bit) families
	[i486-pc-linux-gnu]=x86-linux-gnu
	[i686-linux-gnu]=x86-linux-gnu

	# MacOS
	[x86_64-apple-darwin15]=x86_64-macos-none
	[arm64-apple-darwin24]=aarch64-macos-none
)
test-convert_c_env_to_zig_tuple c_to_zig_map
tend ${?}

tbegin '"CFLAGS to Zig CPU for ARM"'
CHOST="armv7a-unknown-linux-musleabihf"
c_to_zig_map=(
	[" "]="generic-soft_float"
	["-mcpu=native"]="native-soft_float"
	["-mcpu=cortex-a9"]="cortex_a9-soft_float"

	["-mcpu=cortex-a9 -march=iwmmxt2"]="cortex_a9+iwmmxt2-soft_float"

	["-march=armv7e-m"]="generic+v7em-soft_float"
	["-march=armv5te"]="generic+v5te-soft_float"
	["-march=armv6j -mfpu=vfp"]="generic+v6j+vfp2-soft_float"
	["-march=armv7-a -mfpu=vfpv3-d16"]="generic+v7a+vfp3d16-soft_float"
	["-march=armv7-a -mfpu=crypto-neon-fp-armv8"]="generic+v7a+crypto+neon+fp_armv8-soft_float"

	# https://bugs.gentoo.org/924920
	["-march=armv7-a -mfpu=vfpv3-d16 -mfloat-abi=hard"]=generic+v7a+vfp3d16-soft_float
)
test-convert_c_env_to_zig_cpu c_to_zig_map "${CHOST}"
tend ${?}

tbegin '"CFLAGS to Zig CPU for AARCH64"'
CHOST="aarch64-unknown-linux-gnu"
c_to_zig_map=(
	[" "]="generic"
	["-mcpu=native"]="native"
	["-mcpu=cortex-a78"]="cortex_a78"

	["-march=armv8.3-a"]="generic+v8_3a"
	["-mcpu=cortex-a78 -march=armv8.3-a"]="cortex_a78+v8_3a"
)
test-convert_c_env_to_zig_cpu c_to_zig_map "${CHOST}"
tend ${?}

tbegin '"CFLAGS to Zig CPU for X86"'
CHOST="i686-pc-linux-gnu"
c_to_zig_map=(
	[" "]="i686"
	["-march=native"]="native"

	["-march=i486"]="i486"
	["-march=i586"]="i586"
	["-march=i686"]="i686"
	["-O2 -pipe -march=pentium-m"]="pentium_m"
)
test-convert_c_env_to_zig_cpu c_to_zig_map "${CHOST}"
tend ${?}

tbegin '"CFLAGS to Zig CPU for X86-64"'
CHOST="x86_64-pc-linux-gnu"
c_to_zig_map=(
	[" "]="x86_64"
	["-march=native"]="native"

	["-march=x86-64-v2"]="x86_64_v2"
	["-march=x86-64"]="x86_64"
	["-march=znver2"]="znver2"
)
test-convert_c_env_to_zig_cpu c_to_zig_map "${CHOST}"
tend ${?}

tbegin '"CFLAGS to Zig CPU for RISCV32"'
CHOST="riscv32-unknown-linux-gnu"
c_to_zig_map=(
	[" "]="generic_rv32"
	["-mcpu=native"]="native"
	["-mcpu=sifive-e31"]="sifive_e31"

	["-mabi=ilp32d -march=rv32imafdc"]="generic_rv32+i+m+a+f+d+c+d"
	["-mcpu=native -mabi=ilp32 -march=rv32imac"]="native+i+m+a+c"
)
test-convert_c_env_to_zig_cpu c_to_zig_map "${CHOST}"
tend ${?}

tbegin '"CFLAGS to Zig CPU for RISCV64"'
CHOST="riscv64-unknown-linux-gnu"
c_to_zig_map=(
	[" "]="generic_rv64"
	["-mcpu=native"]="native"
	["-mcpu=sifive-u74"]="sifive_u74"

	["-mabi=lp64 -march=rv64imac"]="generic_rv64+i+m+a+c"
	["-mabi=lp64d -march=rv64gc"]="generic_rv64+i+m+a+f+d+zicsr+zifencei+c+d"
	["-march=rv64gcv"]="generic_rv64+i+m+a+f+d+zicsr+zifencei+c+v"
	["-march=rv64imafdc -mcpu=sifive-u74"]="sifive_u74+i+m+a+f+d+c"
	["-mcpu=native -mabi=lp64 -march=rv64imac"]="native+i+m+a+c"
)
test-convert_c_env_to_zig_cpu c_to_zig_map "${CHOST}"
tend ${?}

tbegin '"CFLAGS to Zig CPU for LoongArch64"'
CHOST="loongarch64-unknown-linux-gnu"
c_to_zig_map=(
	[" "]="generic_la64"
	["-march=native"]="native"
	["-march=la664"]="la664"

	["-march=loongarch64 -mabi=lp64d"]="loongarch64+d"
	["-mabi=lp64f"]="generic_la64+f"
)
test-convert_c_env_to_zig_cpu c_to_zig_map "${CHOST}"
tend ${?}

tbegin '"CFLAGS to Zig CPU for PowerPC"'
CHOST="powerpc-unknown-linux-gnu"
c_to_zig_map=(
	[" "]="ppc"
	["-mcpu=native"]="native"

	["-mcpu=G5"]="g5"

	# qemu: uncaught target signal 4 (Illegal instruction) - core dumped
	["-mcpu=power7"]="pwr7:no-run"
	["-mcpu=power8"]="pwr8:no-run"

	["-mcpu=powerpc"]="ppc"
	["-mcpu=powerpcle"]="ppc"
)
test-convert_c_env_to_zig_cpu c_to_zig_map "${CHOST}"
tend ${?}

tbegin '"CFLAGS to Zig CPU for PowerPC64"'
CHOST="powerpc64-unknown-linux-gnu"
c_to_zig_map=(
	[" "]="ppc64"
	["-mcpu=native"]="native"

	# qemu: uncaught target signal 4 (Illegal instruction) - core dumped
	["-mcpu=power10"]="pwr10:no-run"

	["-mcpu=power9"]="pwr9"
	["-mcpu=powerpc64"]="ppc64"
	["-mcpu=powerpc64le"]="ppc64le"
)
test-convert_c_env_to_zig_cpu c_to_zig_map "${CHOST}"
tend ${?}

texit