# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.


@depends(target.os)
def arm_option_defaults(os):
    if os == "Android":
        arch = "armv7-a"
        thumb = "yes"
        fpu = "neon"
        float_abi = "softfp"
    else:
        arch = thumb = fpu = float_abi = "toolchain-default"
    return namespace(
        arch=arch,
        thumb=thumb,
        fpu=fpu,
        float_abi=float_abi,
    )


# Note: '{...|}' in the help of all options with a non-constant default to
# make the lint happy. The first arm is always going to be used, because a
# default is always returned. The lint is fooled by this file being
# conditional. If it weren't conditional, the lint wouldn't ask for '{|}' to
# be there.
option(
    "--with-arch",
    nargs=1,
    default=arm_option_defaults.arch,
    help="{Use specific CPU features (-march=type). Resets thumb, fpu, "
    "float-abi, etc. defaults when set|}",
)


@depends("--with-arch")
def arch_option(value):
    if value:
        if value[0] != "toolchain-default":
            return ["-march={}".format(value[0])]
    return []


option(
    "--with-thumb",
    choices=("yes", "no", "toolchain-default"),
    default=arm_option_defaults.thumb,
    nargs="?",
    help="{Use Thumb instruction set (-mthumb)|}",
)


def normalize_arm_option(value):
    if value:
        if len(value):
            if value[0] == "yes":
                return True
            elif value[0] == "no":
                return False
            else:
                return value[0]
        return True
    return False


@depends("--with-thumb")
def thumb_option(value):
    value = normalize_arm_option(value)
    if value is True:
        return ["-mthumb"]
    if value is False:
        return ["-marm"]
    return []


option(
    "--with-thumb-interwork",
    choices=("yes", "no", "toolchain-default"),
    default="toolchain-default",
    nargs="?",
    help="Use Thumb/ARM instuctions interwork (-mthumb-interwork)",
)


@depends("--with-thumb-interwork")
def thumb_interwork_option(value):
    value = normalize_arm_option(value)
    if value is True:
        return ["-mthumb-interwork"]
    if value is False:
        return ["-mno-thumb-interwork"]
    return []


option(
    "--with-fpu",
    nargs=1,
    default=arm_option_defaults.fpu,
    help="{Use specific FPU type (-mfpu=type)|}",
)


@depends("--with-fpu")
def fpu_option(value):
    if value:
        if value[0] != "toolchain-default":
            return ["-mfpu={}".format(value[0])]
    return []


option(
    "--with-float-abi",
    nargs=1,
    default=arm_option_defaults.float_abi,
    help="{Use specific arm float ABI (-mfloat-abi=type)|}",
)


@depends("--with-float-abi")
def float_abi_option(value):
    if value:
        if value[0] != "toolchain-default":
            return ["-mfloat-abi={}".format(value[0])]
    return []


option(
    "--with-soft-float",
    choices=("yes", "no", "toolchain-default"),
    default="toolchain-default",
    nargs="?",
    help="Use soft float library (-msoft-float)",
)


@depends("--with-soft-float")
def soft_float_option(value):
    value = normalize_arm_option(value)
    if value is True:
        return ["-msoft-float"]
    if value is False:
        return ["-mno-soft-float"]
    return []


check_and_add_flag(
    "-mno-unaligned-access", when=depends(target.os)(lambda os: os == "Android")
)


# The set of flags that clang understands
@depends(
    arch_option,
    thumb_option,
    fpu_option,
    float_abi_option,
    soft_float_option,
)
def all_clang_arm_flags(arch, thumb, fpu, float_abi, soft_float):
    return arch + thumb + fpu + float_abi + soft_float


# All the flags the compiler understands. When the compiler is clang, this
# still includes unsupported flags, but we live it to configure to fail
# during a compiler check. These checks aren't available for clang as used
# by bindgen, so we keep the separate set of flags for clang for bindgen.
@depends(all_clang_arm_flags, thumb_interwork_option)
def all_arm_flags(flags, interwork):
    return flags + interwork


add_old_configure_assignment("_ARM_FLAGS", all_arm_flags)
add_old_configure_assignment("_THUMB_FLAGS", thumb_option)


@depends(configure_cache, c_compiler, all_arm_flags)
@checking("ARM version support in compiler", lambda x: x.arm_arch)
@imports(_from="textwrap", _import="dedent")
def arm_target(configure_cache, compiler, all_arm_flags):
    # We're going to preprocess the following source to figure out some details
    # about the arm target options we have enabled.
    source = dedent(
        """\
        %ARM_ARCH __ARM_ARCH
        #if __thumb2__
        %THUMB2 yes
        #else
        %THUMB2 no
        #endif
        // Confusingly, the __SOFTFP__ preprocessor variable indicates the
        // "softfloat" ABI, not the "softfp" ABI.
        #if __SOFTFP__
        %FLOAT_ABI soft
        #elif __ARM_PCS_VFP
        %FLOAT_ABI hard
        #else
        %FLOAT_ABI softfp
        #endif
        // There is more subtlety to it than this preprocessor test, but MOZ_FPU doesn't
        // need to be too fine-grained.
        #if __ARM_NEON
        %FPU neon
        #elif __ARM_VFPV2__ || __ARM_FP == 12
        %FPU vfpv2
        #elif __ARM_VFPV3__
        %FPU vfpv3
        #elif __ARM_VFPV4__ || __ARM_FP == 14
        %FPU vfpv4
        #elif __ARM_FPV5__
        %FPU fp-armv8
        #endif
    """
    )
    result = try_invoke_compiler(
        configure_cache,
        [compiler.compiler] + compiler.flags,
        compiler.language,
        source,
        ["-E"] + all_arm_flags,
        wrapper=compiler.wrapper,
    )
    # Metadata emitted by preprocessors such as GCC with LANG=ja_JP.utf-8 may
    # have non-ASCII characters. Treat the output as bytearray.
    data = {"fpu": None}  # fpu may not get a value from the preprocessor.
    for line in result.splitlines():
        if line.startswith("%"):
            k, _, v = line.partition(" ")
            k = k.lstrip("%").lower()
            if k == "arm_arch":
                data[k] = int(v)
            else:
                data[k] = {
                    "yes": True,
                    "no": False,
                }.get(v, v)
            log.debug("%s = %s", k, data[k])

    return namespace(**data)


@depends(arm_target.arm_arch, when=depends(target.os)(lambda os: os == "Android"))
def armv7(arch):
    if arch < 7:
        die("Android/armv6 and earlier are not supported")


set_config("MOZ_THUMB2", True, when=arm_target.thumb2)
set_define("MOZ_THUMB2", True, when=arm_target.thumb2)


have_arm_simd = c_compiler.try_compile(
    body='asm("uqadd8 r1, r1, r2");', check_msg="for ARM SIMD support in compiler"
)

set_config("HAVE_ARM_SIMD", have_arm_simd)
set_define("HAVE_ARM_SIMD", have_arm_simd)

have_arm_neon = c_compiler.try_compile(
    body='asm(".fpu neon\\n vadd.i8 d0, d0, d0");',
    check_msg="for ARM NEON support in compiler",
)

set_config("HAVE_ARM_NEON", have_arm_neon)
set_define("HAVE_ARM_NEON", have_arm_neon)


# We don't need to build NEON support if we're targetting a non-NEON device.
# This matches media/webrtc/trunk/webrtc/build/common.gypi.
@depends(arm_target.arm_arch, when=have_arm_neon)
def build_arm_neon(arm_arch):
    return arm_arch >= 7


set_config("BUILD_ARM_NEON", True, when=build_arm_neon)
set_define("BUILD_ARM_NEON", True, when=build_arm_neon)


set_config("ARM_ARCH", depends(arm_target.arm_arch)(lambda x: str(x)))
set_config("MOZ_FPU", arm_target.fpu)


@depends(arm_target)
def neon_flags(arm_target):
    # Building with -mfpu=neon requires either the "softfp" or the
    # "hardfp" ABI. Depending on the compiler's default target, and the
    # CFLAGS, the default ABI might be neither, in which case it is the
    # "softfloat" ABI.
    # The "softfloat" ABI is binary-compatible with the "softfp" ABI, so
    # we can safely mix code built with both ABIs. So, if we detect
    # that compiling uses the "softfloat" ABI, force the use of the
    # "softfp" ABI instead.
    flags = ["-mfpu=neon"]
    if arm_target.float_abi == "soft":
        flags.append("-mfloat-abi=softfp")
    if arm_target.arm_arch < 7:
        # clang needs to be forced to at least armv7 for -mfpu=neon to do
        # something.
        flags.append("-march=armv7-a")
    return tuple(flags)


set_config("NEON_FLAGS", neon_flags)