# Copyright 2018 The Hafnium Authors.
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file or at
# https://opensource.org/licenses/BSD-3-Clause.

declare_args() {
  # Set by arch toolchain. Prefix for binutils tools.
  tool_prefix = ""

  # Enable link time optimizations
  use_lto = true
}

# Template for embedded toolchains; there is no support for C++ or libraries.
# Instead, use source_set to group source together.
template("embedded_cc_toolchain") {
  toolchain(target_name) {
    assert(defined(invoker.cc), "\"cc\" must be defined for ${target_name}.")
    assert(defined(invoker.ld), "\"ld\" must be defined for ${target_name}.")

    # Collect extra flags from the toolchain.
    extra_defines = ""
    extra_cflags = "-ffunction-sections -fdata-sections"
    if (use_lto) {
      extra_cflags += " -flto"
    }
    extra_ldflags = "-pie --gc-sections"

    if (defined(invoker.extra_defines)) {
      extra_defines += " ${invoker.extra_defines}"
    }
    if (defined(invoker.extra_cflags)) {
      extra_cflags += " ${invoker.extra_cflags}"
    }
    if (defined(invoker.extra_ldflags)) {
      extra_ldflags += " ${invoker.extra_ldflags}"
    }

    # Define the tools.
    tool("cc") {
      depfile = "{{output}}.d"
      command = "${invoker.cc} -MMD -MF $depfile ${extra_defines} {{defines}} {{include_dirs}} ${extra_cflags} {{cflags}} {{cflags_c}} -c {{source}} -o {{output}}"
      depsformat = "gcc"
      description = "CC {{output}}"
      outputs =
          [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" ]
    }

    tool("asm") {
      depfile = "{{output}}.d"
      command = "${invoker.cc} -MMD -MF $depfile ${extra_defines} {{defines}} {{include_dirs}} ${extra_cflags} {{asmflags}} -c {{source}} -o {{output}}"
      depsformat = "gcc"
      description = "ASM {{output}}"
      outputs =
          [ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" ]
    }

    tool("link") {
      outfile = "{{output_dir}}/{{target_output_name}}{{output_extension}}"
      rspfile = "$outfile.rsp"
      command = "${invoker.ld} ${extra_ldflags} {{ldflags}} -o $outfile --start-group @$rspfile --end-group"
      description = "LINK $outfile"
      default_output_dir = "{{root_out_dir}}"
      rspfile_content = "{{inputs}}"
      outputs = [ outfile ]
    }

    tool("alink") {
      outfile = "{{target_out_dir}}/{{target_output_name}}.a"
      command = "rm -f $outfile && ${invoker.ar} -rc $outfile {{inputs}}"
      description = "ALINK $outfile"
      outputs = [ outfile ]
    }

    tool("stamp") {
      command = "touch {{output}}"
      description = "STAMP {{output}}"
    }

    tool("copy") {
      command = "cp -af {{source}} {{output}}"
      description = "COPY {{source}} {{output}}"
    }

    toolchain_args = {
      forward_variables_from(invoker.toolchain_args, "*")
    }
  }
}

# Specialize for clang.
template("embedded_clang_toolchain") {
  assert(defined(invoker.target),
         "\"target\" must be defined for ${target_name}.")
  assert(defined(invoker.extra_defines),
         "\"extra_defines\" must be defined for ${target_name}")
  assert(defined(invoker.extra_cflags),
         "\"extra_cflags\" must be defined for ${target_name}")
  assert(defined(invoker.extra_ldflags),
         "\"extra_ldflags\" must be defined for ${target_name}")

  embedded_cc_toolchain(target_name) {
    cc = "clang"
    ld = "ld.lld"
    ar = "llvm-ar"

    forward_variables_from(invoker,
                           [
                             "extra_defines",
                             "extra_cflags",
                             "extra_ldflags",
                           ])

    extra_cflags += " -target ${invoker.target} -fcolor-diagnostics -nostdinc" +
                    " -isystem ${toolchain_lib}/include"
    extra_ldflags += " -O2 --icf=all --fatal-warnings --color-diagnostics"

    toolchain_args = {
      if (defined(invoker.toolchain_args)) {
        forward_variables_from(invoker.toolchain_args, "*")
      }
    }
  }
}

# Specialize for mixed toolchain with clang and bfd linker.
template("embedded_clang_bfd_toolchain") {
  assert(defined(invoker.target),
         "\"target\" must be defined for ${target_name}.")
  assert(defined(invoker.tool_prefix),
         "\"tool_prefix\" must be defined for ${target_name}.")
  assert(defined(invoker.extra_defines),
         "\"extra_defines\" must be defined for ${target_name}")
  assert(defined(invoker.extra_cflags),
         "\"extra_cflags\" must be defined for ${target_name}")
  assert(defined(invoker.extra_ldflags),
         "\"extra_ldflags\" must be defined for ${target_name}")

  embedded_cc_toolchain(target_name) {
    cc = "clang"
    ld = "${invoker.tool_prefix}ld.bfd"
    ar = "llvm-ar"

    forward_variables_from(invoker,
                           [
                             "extra_defines",
                             "extra_cflags",
                             "extra_ldflags",
                           ])
    extra_cflags += " -target ${invoker.target} -fcolor-diagnostics"
    extra_ldflags += " --fatal-warnings"
    if (use_lto) {
      extra_ldflags += " -O2 --icf=all"
    }

    toolchain_args = {
      if (defined(invoker.toolchain_args)) {
        forward_variables_from(invoker.toolchain_args, "*")
      }
    }
  }
}

# Expand to clang variants.
template("embedded_platform_toolchain") {
  assert(defined(invoker.arch), "\"arch\" must be defined for ${target_name}.")
  assert(defined(invoker.target),
         "\"target\" must be defined for ${target_name}.")
  assert(defined(invoker.tool_prefix),
         "\"tool_prefix\" must be defined for ${target_name}.")
  assert(defined(invoker.origin_address),
         "\"origin_address\" must be defined for ${target_name}.")
  assert(defined(invoker.heap_pages),
         "\"heap_pages\" must be defined for ${target_name}.")
  assert(defined(invoker.max_cpus),
         "\"max_cpus\" must be defined for ${target_name}.")
  assert(defined(invoker.max_vms),
         "\"max_vms\" must be defined for ${target_name}.")
  assert(defined(invoker.platform_name),
         "\"platform_name\" must be defined for ${target_name}.")

  extra_defines = ""
  extra_cflags = "-fno-builtin -ffreestanding -fpic"
  extra_ldflags = "--defsym=ORIGIN_ADDRESS=${invoker.origin_address}"
  if (defined(invoker.extra_defines)) {
    extra_defines += " ${invoker.extra_defines}"
  }
  if (defined(invoker.extra_cflags)) {
    extra_cflags += " ${invoker.extra_cflags}"
  }
  if (defined(invoker.extra_ldflags)) {
    extra_ldflags += " ${invoker.extra_ldflags}"
  }
  toolchain_args = {
    use_platform = true
    plat_name = invoker.platform_name
    plat_arch = invoker.arch
    plat_heap_pages = invoker.heap_pages
    plat_max_cpus = invoker.max_cpus
    plat_max_vms = invoker.max_vms
    if (defined(invoker.toolchain_args)) {
      forward_variables_from(invoker.toolchain_args, "*")
    }
  }

  embedded_clang_toolchain("${target_name}_clang") {
    target = invoker.target
  }

  embedded_clang_bfd_toolchain("${target_name}_clang_bfd") {
    target = invoker.target
    tool_prefix = invoker.tool_prefix
  }
}

# Specialize for different architectures.

template("aarch64_common_toolchain") {
  assert(defined(invoker.cpu), "\"cpu\" must be defined for ${target_name}.")
  assert(defined(invoker.target),
         "\"target\" must be defined for ${target_name}")
  assert(defined(invoker.tool_prefix),
         "\"tool_prefix\" must be defined for ${target_name}")
  assert(defined(invoker.origin_address),
         "\"origin_address\" must be defined for ${target_name}.")
  assert(defined(invoker.console),
         "\"console\" must be defined for ${target_name}.")
  assert(defined(invoker.heap_pages),
         "\"heap_pages\" must be defined for ${target_name}.")
  assert(defined(invoker.max_cpus),
         "\"max_cpus\" must be defined for ${target_name}.")
  assert(defined(invoker.max_vms),
         "\"max_vms\" must be defined for ${target_name}.")
  assert(defined(invoker.gic_version),
         "\"gic_version\" must be defined for ${target_name}.")
  if (invoker.gic_version < 2 || invoker.gic_version >= 5) {
    assert(false, "Unsupported gic_version ${invoker.gic_version}")
  }
  if (invoker.gic_version == 3 || invoker.gic_version == 4) {
    assert(defined(invoker.gicd_base_address),
           "\"gicd_base_address\" must be defined for ${target_name}.")
    assert(defined(invoker.gicr_base_address),
           "\"gicr_base_address\" must be defined for ${target_name}.")
    assert(defined(invoker.gicr_frames),
           "\"gicr_frames\" must be defined for ${target_name}.")
  }

  assert(defined(invoker.platform_name),
         "\"platform_name\" must be defined for ${target_name}.")

  embedded_platform_toolchain(target_name) {
    forward_variables_from(invoker,
                           [
                             "origin_address",
                             "heap_pages",
                             "max_cpus",
                             "max_vms",
                             "platform_name",
                             "extra_ldflags",
                           ])
    arch = "aarch64"
    target = invoker.target
    tool_prefix = invoker.tool_prefix
    extra_cflags = "-mcpu=${invoker.cpu} -mstrict-align"
    if (defined(invoker.extra_cflags)) {
      extra_cflags += " ${invoker.extra_cflags}"
    }

    extra_defines = ""
    if (defined(invoker.extra_defines)) {
      extra_defines += " ${invoker.extra_defines}"
    }

    if (invoker.gic_version > 0) {
      extra_defines += " -DGIC_VERSION=${invoker.gic_version}"
    }
    if (invoker.gic_version == 3 || invoker.gic_version == 4) {
      extra_defines += " -DGICD_BASE=${invoker.gicd_base_address} -DGICR_BASE=${invoker.gicr_base_address} -DGICR_FRAMES=${invoker.gicr_frames}"
    }
    if (defined(invoker.gic_enable_espi)) {
      assert(invoker.gic_enable_espi == 1 || invoker.gic_enable_espi == 0,
             "Illegal value specified for \"gic_enable_espi\"")

      extra_defines += " -DGIC_EXT_INTID=${invoker.gic_enable_espi}"
    }
    if (defined(invoker.branch_protection)) {
      assert(invoker.branch_protection == "none" ||
                 invoker.branch_protection == "standard" ||
                 invoker.branch_protection == "pac-ret" ||
                 invoker.branch_protection == "pac-ret+leaf" ||
                 invoker.branch_protection == "bti",
             "Unsupported branch protect feature specified")

      if (invoker.branch_protection != "none") {
        extra_cflags += " -mbranch-protection=${invoker.branch_protection}"
        extra_defines += " -DBRANCH_PROTECTION=1"
      }
    }

    toolchain_args = {
      plat_boot_flow = invoker.boot_flow
      plat_console = invoker.console
      plat_iommu = invoker.iommu
      plat_memory_protect = invoker.memory_protect
      if (defined(invoker.stdout)) {
        stdout = invoker.stdout
      }
      if (defined(invoker.max_image_size)) {
        plat_max_image_size = invoker.max_image_size
      }
      forward_variables_from(invoker.toolchain_args, "*")
    }

    extra_cflags += " -march=armv8.5-a"

    if (defined(toolchain_args.enable_mte)) {
      assert(
          toolchain_args.enable_mte == "1" || toolchain_args.enable_mte == "0",
          "Illegal value specified for \"enable_mte\"")

      extra_defines += " -DENABLE_MTE=${toolchain_args.enable_mte}"
      if (toolchain_args.enable_mte == "1") {
        extra_cflags += "+memtag"
        extra_cflags += " -fsanitize=memtag-stack"
      }
    }
  }
}

template("aarch64_toolchain") {
  aarch64_common_toolchain("${target_name}") {
    forward_variables_from(invoker, "*")
    target = "aarch64-none-elf"
    tool_prefix = "aarch64-none-elf-"
    platform_name = target_name
  }
}

template("aarch64_toolchains") {
  aarch64_toolchain("${target_name}") {
    forward_variables_from(invoker,
                           [
                             "origin_address",
                             "boot_flow",
                             "console",
                             "iommu",
                             "memory_protect",
                             "gic_version",
                             "gic_enable_espi",
                             "gicd_base_address",
                             "gicr_base_address",
                             "gicr_frames",
                             "heap_pages",
                             "max_cpus",
                             "max_image_size",
                             "max_vms",
                             "toolchain_args",
                             "branch_protection",
                           ])
    cpu = "${invoker.cpu}+nofp"

    # Add a macro so files can tell whether they are not being built for a VM.
    extra_defines = " -DVM_TOOLCHAIN=0"
    extra_cflags = " -mgeneral-regs-only"
  }

  # Toolchain for building test VMs which run under Hafnium.
  aarch64_toolchain("${target_name}_vm") {
    forward_variables_from(invoker,
                           [
                             "origin_address",
                             "gic_version",
                             "gic_enable_espi",
                             "gicd_base_address",
                             "gicr_base_address",
                             "gicr_frames",
                             "max_cpus",
                             "toolchain_args",
                             "console",
                             "branch_protection",
                           ])
    cpu = "${invoker.cpu}+fp"
    boot_flow = "//src/arch/fake:boot_flow"
    iommu = "//src/iommu:absent"
    memory_protect = "//src/memory_protect:absent"
    stdout = "//src/arch/aarch64/hftest:stdout"

    # Nonsense values because they are required but shouldn't be used.
    heap_pages = 0
    max_vms = 0

    # Add a macro so files can tell whether they are being built for a VM.
    extra_defines = " -DVM_TOOLCHAIN=1"
  }
}
