# SPDX-FileCopyrightText: Copyright 2025 crueter
# SPDX-License-Identifier: GPL-3.0-or-later

## DetectArchitecture ##
#[[
Does exactly as it sounds. Detects common symbols defined for different architectures and
adds compile definitions thereof. Namely:
- arm64
- arm
- x86_64
- x86
- ia64
- mips64
- mips
- ppc64
- ppc
- riscv
- riscv64
- loongarch64
- wasm

Unsupported architectures:
- ARMv2-6
- m68k
- PIC

This file WILL NOT detect endian-ness for you.

This file is based off of Yuzu and Dynarmic.
]]

# multiarch builds are a special case and also very difficult
# this is what I have for now, but it's not ideal

# Do note that situations where multiple architectures are defined
# should NOT be too dependent on the architecture
# otherwise, you may end up with duplicate code
if (CMAKE_OSX_ARCHITECTURES)
    set(MULTIARCH_BUILD 1)
    set(ARCHITECTURE "${CMAKE_OSX_ARCHITECTURES}")

    # hope and pray the architecture names match
    foreach(ARCH IN ${CMAKE_OSX_ARCHITECTURES})
        set(ARCHITECTURE_${ARCH} 1 PARENT_SCOPE)
        add_definitions(-DARCHITECTURE_${ARCH}=1)
    endforeach()

    return()
endif()

include(CheckSymbolExists)
function(detect_architecture symbol arch)
    # The output variable needs to be unset between invocations otherwise
    # CMake's crazy scope rules will keep it defined
    unset(SYMBOL_EXISTS CACHE)

    if (NOT DEFINED ARCHITECTURE)
        set(CMAKE_REQUIRED_QUIET 1)
        check_symbol_exists("${symbol}" "" SYMBOL_EXISTS)
        unset(CMAKE_REQUIRED_QUIET)

        if (SYMBOL_EXISTS)
            set(ARCHITECTURE "${arch}" PARENT_SCOPE)
            set(ARCHITECTURE_${arch} 1 PARENT_SCOPE)
            add_definitions(-DARCHITECTURE_${arch}=1)
        endif()
    endif()
endfunction()

function(detect_architecture_symbols)
    if (DEFINED ARCHITECTURE)
        return()
    endif()

    set(oneValueArgs ARCH)
    set(multiValueArgs SYMBOLS)

    cmake_parse_arguments(ARGS "" "${oneValueArgs}" "${multiValueArgs}"
        "${ARGN}")

    set(arch "${ARGS_ARCH}")
    foreach(symbol ${ARGS_SYMBOLS})
        detect_architecture("${symbol}" "${arch}")

        if (ARCHITECTURE_${arch})
            message(DEBUG "[DetectArchitecture] Found architecture symbol ${symbol} for ${arch}")
            set(ARCHITECTURE "${arch}" PARENT_SCOPE)
            set(ARCHITECTURE_${arch} 1 PARENT_SCOPE)
            add_definitions(-DARCHITECTURE_${arch}=1)

            return()
        endif()
    endforeach()
endfunction()

function(DetectArchitecture)
    # arches here are put in a sane default order of importance
    # notably, amd64, arm64, and riscv (in order) are BY FAR the most common
    # mips is pretty popular in embedded
    # ppc64 is pretty popular in supercomputing
    # sparc is uh
    # ia64 exists
    # the rest exist, but are probably less popular than ia64

    detect_architecture_symbols(
        ARCH arm64
        SYMBOLS
            "__ARM64__"
            "__aarch64__"
            "_M_ARM64")

    detect_architecture_symbols(
        ARCH x86_64
        SYMBOLS
            "__x86_64"
            "__x86_64__"
            "__amd64"
            "_M_X64"
            "_M_AMD64")

    # riscv is interesting since it generally does not define a riscv64-specific symbol
    # We can, however, check for the rv32 zcf extension which is good enough of a heuristic on GCC
    detect_architecture_symbols(
        ARCH riscv
        SYMBOLS
            "__riscv_zcf")

    # if zcf doesn't exist we can safely assume it's riscv64
    detect_architecture_symbols(
        ARCH riscv64
        SYMBOLS
            "__riscv")

    detect_architecture_symbols(
        ARCH x86
        SYMBOLS
            "__i386"
            "__i386__"
            "_M_IX86")

    detect_architecture_symbols(
        ARCH arm
        SYMBOLS
            "__arm__"
            "__TARGET_ARCH_ARM"
            "_M_ARM")

    detect_architecture_symbols(
        ARCH ia64
        SYMBOLS
            "__ia64"
            "__ia64__"
            "_M_IA64")

    # mips is probably the least fun to detect due to microMIPS
    # Because microMIPS is such cancer I'm considering it out of scope for now
    detect_architecture_symbols(
        ARCH mips64
        SYMBOLS
            "__mips64")

    detect_architecture_symbols(
        ARCH mips
        SYMBOLS
            "__mips"
            "__mips__"
            "_M_MRX000")

    detect_architecture_symbols(
        ARCH ppc64
        SYMBOLS
            "__ppc64__"
            "__powerpc64__"
            "_ARCH_PPC64"
            "_M_PPC64")

    detect_architecture_symbols(
        ARCH ppc
        SYMBOLS
            "__ppc__"
            "__ppc"
            "__powerpc__"
            "_ARCH_COM"
            "_ARCH_PWR"
            "_ARCH_PPC"
            "_M_MPPC"
            "_M_PPC")

    detect_architecture_symbols(
        ARCH sparc64
        SYMBOLS
            "__sparc_v9__")

    detect_architecture_symbols(
        ARCH sparc
        SYMBOLS
            "__sparc__"
            "__sparc")

    # I don't actually know about loongarch32 since crossdev does not support it, only 64
    detect_architecture_symbols(
        ARCH loongarch64
        SYMBOLS
            "__loongarch__"
            "__loongarch64")

    detect_architecture_symbols(
        ARCH wasm
        SYMBOLS
            "__EMSCRIPTEN__")

    # "generic" target
    # If you have reached this point, you're on some as-of-yet unsupported architecture.
    # See the docs up above for known unsupported architectures
    # If you're not in the list... I think you know what you're doing.
    if (NOT DEFINED ARCHITECTURE)
        set(ARCHITECTURE "GENERIC")
        set(ARCHITECTURE_GENERIC 1)
        add_definitions(-DARCHITECTURE_GENERIC=1)
    endif()

    message(STATUS "[DetectArchitecture] Target architecture: ${ARCHITECTURE}")
    set(ARCHITECTURE "${ARCHITECTURE}" PARENT_SCOPE)
    set(ARCHITECTURE_${ARCHITECTURE} 1 PARENT_SCOPE)
endfunction()
