# **********************************************************
# Copyright (c) 2010-2014 Google, Inc.    All rights reserved.
# Copyright (c) 2009-2010 VMware, Inc.    All rights reserved.
# **********************************************************

# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
#   this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
#   this list of conditions and the following disclaimer in the documentation
#   and/or other materials provided with the distribution.
#
# * Neither the name of VMware, Inc. nor the names of its contributors may be
#   used to endorse or promote products derived from this software without
#   specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
# DAMAGE.

###########################################################################
# Enhanced assembly support for CMake projects
#
# Features:
# + Cmake support for asm with Makefiles and Visual Studio.
# + Cross-platform asm using Intel syntax and C pre-processor
#   to feed to assembler.
# + Asm code in C/C++ files (separated via #ifdef ASM_CODE_ONLY) to avoid
#   needing standalone assembly files with 64-bit cl.
#
###################################################
# Usage:
#
# Setup:
# + Place both this file and cpp2asm_add_newlines.cmake in the same
#   directory (or, point at cpp2asm_add_newlines.cmake with the
#   variable ${cpp2asm_newline_script_path}).
# + Include this file in your CMakeLists.txt file.
# + For UNIX, the available assembler may not support Intel syntex.
#   In that case, CMAKE_ASM_SUPPORTS_INTEL_SYNTAX will be set to false.
#   The includer of this file can decide whether that's a fatal error.
#
# Example setup:
#   include(cpp2asm_support.cmake)
#   if (UNIX)
#     if (NOT CMAKE_ASM_SUPPORTS_INTEL_SYNTAX)
#       <either FATAL_ERROR or point CMAKE_ASM_COMPILER at a gas binary to use>
#     endif ()
#   endif (UNIX)
#
# Assembly file:
# + Write assembly using Intel syntax and the macros in the
#   cpp2asm_defines.h file included in this directory.
# + Before #include of cpp2asm_defines.h in your asm file, define
#   one of ASSEMBLE_WITH_{GAS,MASM,NASM}
# + cpp2asm_defines.h is not automatically added as a dependence.
#   If you are using it, pass in its path as part of the extra_deps option.
# + cpp2asm_defines.h's dir is not automatically added as an include dir.
#   If you are using it, pass in "-I;<path>" as part of the extra_defs option.
# + The global preprocessor defines are used for Makefile generators.
#   For Visual Studio generators, pass in the defines needed (as a list)
#   as part of the extra_defs option.  These extra_defs are NOT honored
#   for Makefile generators and should be added by the caller to the
#   target or global flags.
# + Have assembly files #include a configure.h or other header to obtain
#   their defines (this file does not support passing them in via cmdline
#   to the preprocessor or the assembler).
# + For pure assembly files, use add_asm_target(); for split C/C++ + asm files,
#   separate the two pieces via #ifdef ASM_CODE_ONLY and use
#   add_split_asm_target().
# + For VS generators, add the returned target as a dependence of the final
#   executable/library target to avoid races when building.
#
# Example:
#
#   set(asm_deps "/path/to/cpp2asm_defines.h" "/path/to/configure.h")
#   set(asm_defs -I "/path/to/cpp2asm_defines.h" -I "/path/to/configure.h")
#   add_asm_target(foo.asm foo_asm_src foo_asm_tgt "" "${asm_defs}" ${asm_deps})
#   add_library(bar bar.c ${foo_asm_src})
#   if ("${CMAKE_GENERATOR}" MATCHES "Visual Studio")
#     add_dependencies(bar ${foo_asm_tgt})
#   endif ("${CMAKE_GENERATOR}" MATCHES "Visual Studio")
#
###########################################################################

if ("${CMAKE_GENERATOR}" MATCHES "Visual Studio 10")
  # For i#801 workaround
  cmake_minimum_required(VERSION 2.8.8)
else ()
  if (APPLE)
    # We want ASM NASM support
    cmake_minimum_required(VERSION 2.8.3)
  else (APPLE)
    # Require 2.6.4 to avoid cmake bug #8639, unless this var is
    # properly set (which it is for DR b/c it has a workaround):
    if (NOT "${CMAKE_ASM_SOURCE_FILE_EXTENSIONS}" MATCHES "asm")
      cmake_minimum_required(VERSION 2.6.4)
    endif ()
  endif (APPLE)
endif ()

##################################################
# Helper files

if (NOT cpp2asm_newline_script_path)
  # By default we expect cpp2asm_add_newlines.cmake in same dir as this file.
  get_filename_component(my_dir "${CMAKE_CURRENT_LIST_FILE}" PATH)
  set(cpp2asm_newline_script_path "${my_dir}/cpp2asm_add_newlines.cmake")
endif ()
if (NOT EXISTS "${cpp2asm_newline_script_path}")
  message(FATAL_ERROR "Cannot find \"${cpp2asm_newline_script_path}\". "
    "Please set the cpp2asm_newline_script_path variable.")
endif ()

##################################################
# Preprocessor location and flags

if (UNIX)
  # "gcc -E" on a non-.c-extension file gives message:
  #   "linker input file unused because linking not done"
  # and doesn't produce any output, so we must use cpp for our .asm files.
  # we assume it's in the same dir.
  get_filename_component(compiler_path ${CMAKE_C_COMPILER} PATH)
  find_program(CMAKE_CPP cpp HINTS "${compiler_path}" DOC "path to C preprocessor")
  if (NOT CMAKE_CPP)
    message(FATAL_ERROR "cpp is required to build")
  endif (NOT CMAKE_CPP)
  mark_as_advanced(CMAKE_CPP)

  set(CPP_KEEP_COMMENTS -C)
  set(CPP_NO_LINENUM -P)
  set(CPP_KEEP_WHITESPACE -traditional-cpp)
  set(CMAKE_CPP_FLAGS "")
else (UNIX)
  set(CMAKE_CPP ${CMAKE_C_COMPILER})

  set(CPP_KEEP_COMMENTS /C)
  set(CPP_NO_LINENUM /EP)
  set(CPP_KEEP_WHITESPACE "")
  set(CMAKE_CPP_FLAGS "/nologo")
endif (UNIX)

##################################################
# Assembler location and flags

if (NOT "${CMAKE_GENERATOR}" MATCHES "Visual Studio")
  if (UNIX)
    # i#646: cmake 2.8.5 requires ASM-ATT to find as: but we don't want to change
    # all our vars to have -ATT suffix so we set the search list instead.
    # This must be done prior to enable_language(ASM).
    set(CMAKE_ASM_COMPILER_INIT gas as)
  endif (UNIX)
  # CMake does not support assembly with VS generators
  # (http://public.kitware.com/Bug/view.php?id=11536)
  # so we have to add our own custom commands and targets
  if (APPLE)
    # NASM support was added in 2.8.3.  It clears ASM_DIALECT for us.
    enable_language(ASM_NASM)
  else (APPLE)
    enable_language(ASM)
  endif (APPLE)
endif ()

if (APPLE)
  # XXX: we may be able to avoid some of this given CMake 2.8.3's NASM support.
  find_program(NASM nasm DOC "path to nasm assembler")
  if (NOT NASM)
    message(FATAL_ERROR "nasm assembler not found: required to build")
  endif (NOT NASM)
  message(STATUS "Found nasm: ${NASM}")
  execute_process(COMMAND ${NASM} -hf OUTPUT_VARIABLE nasm_result ERROR_QUIET)
  if (X64)
    # x64 support added between 0.98.40 and 2.10.07
    if (NOT nasm_result MATCHES macho64)
      message(FATAL_ERROR "nasm is too old: no 64-bit support")
    endif ()
    set(ASM_FLAGS "${ASM_FLAGS} -fmacho64")
  else (X64)
    if (nasm_result MATCHES macho32)
      set(ASM_FLAGS "${ASM_FLAGS} -fmacho32")
    else ()
      set(ASM_FLAGS "${ASM_FLAGS} -fmacho")
    endif ()
  endif (X64)
  if (DEBUG)
    set(ASM_FLAGS "${ASM_FLAGS} -g")
  endif (DEBUG)
elseif (UNIX)
  set(ASM_FLAGS "-mmnemonic=intel -msyntax=intel -mnaked-reg --noexecstack")
  if (X64)
    set(ASM_FLAGS "${ASM_FLAGS} --64")
  else (X64)
    # putting --32 last so we fail on -mmnemonic=intel on older as, not --32
    set(ASM_FLAGS "${ASM_FLAGS} --32")
  endif (X64)
  if (DEBUG)
    set(ASM_FLAGS "${ASM_FLAGS} -g")
  endif (DEBUG)
else ()
  if (X64)
    find_program(CMAKE_ASM_COMPILER ml64.exe HINTS "${cl_path}" DOC "path to assembler")
  else (X64)
    find_program(CMAKE_ASM_COMPILER ml.exe HINTS "${cl_path}" DOC "path to assembler")
  endif (X64)
  if (NOT CMAKE_ASM_COMPILER)
    message(FATAL_ERROR "assembler not found: required to build")
  endif (NOT CMAKE_ASM_COMPILER)
  message(STATUS "Found assembler: ${CMAKE_ASM_COMPILER}")
  if (NOT DEFINED GENERATE_PDBS OR GENERATE_PDBS)
    set(ASM_DBG "/Zi /Zd")
  else ()
    set(ASM_DBG "")
  endif ()
  set(ASM_FLAGS "/nologo ${ASM_DBG}")
endif ()
string(REPLACE " " ";" ASM_FLAGS_LIST "${ASM_FLAGS}")

if (APPLE)
  # XXX: xcode assembler uses Intel mnemonics but opposite src,dst order!
  # We rely on having nasm installed as a workaround.
  set(CMAKE_ASM_SUPPORTS_INTEL_SYNTAX ON)
endif (APPLE)
if (UNIX AND NOT APPLE)
  # We require gas >= 2.18.50 for --32, --64, and the new -msyntax=intel, etc.
  execute_process(COMMAND
    ${CMAKE_ASM_COMPILER} --help
    RESULT_VARIABLE asm_result
    ERROR_VARIABLE asm_error
    OUTPUT_VARIABLE asm_out)
  if (asm_result OR asm_error)
    message(FATAL_ERROR "*** ${CMAKE_ASM_COMPILER} failed: ***\n${asm_error}")
  endif (asm_result OR asm_error)
  # turn the flags into a vector
  string(REGEX REPLACE " " ";" flags_needed "${ASM_FLAGS}")
  # we want "-mmnemonic=intel" to match "-mmnemonic=[att|intel]"
  string(REGEX REPLACE "=" ".*" flags_needed "${flags_needed}")
  set(flag_present 1)
  foreach (flag ${flags_needed})
    if (flag_present)
      string(REGEX MATCH "${flag}" flag_present "${asm_out}")
      if (NOT flag_present)
        message("${CMAKE_ASM_COMPILER} missing flag \"${flag}\"")
      endif (NOT flag_present)
    endif (flag_present)
  endforeach (flag)
  # let caller decide whether a fatal error or not
  if (NOT flag_present)
    set(CMAKE_ASM_SUPPORTS_INTEL_SYNTAX OFF)
  else (NOT flag_present)
    set(CMAKE_ASM_SUPPORTS_INTEL_SYNTAX ON)
  endif (NOT flag_present)
endif (UNIX AND NOT APPLE)

##################################################
# Assembler build rule for Makefile generators

if (APPLE)
  # Despite the docs, -o does not work: cpp prints to stdout.
  set(CMAKE_ASM_NASM_COMPILE_OBJECT
    "${CMAKE_CPP} ${CMAKE_CPP_FLAGS} <FLAGS> <DEFINES> -E <SOURCE> > <OBJECT>.s"
    "<CMAKE_COMMAND> -Dfile=<OBJECT>.s -P \"${cpp2asm_newline_script_path}\""
    "<NASM> ${ASM_FLAGS} -o <OBJECT> <OBJECT>.s"
    )
elseif (UNIX)
  # we used to have ".ifdef FOO" and to not have it turn into ".ifdef 1" we'd say
  # "-DFOO=FOO", but we now use exclusively preprocessor defines, which is good
  # since our defines are mostly in configure.h where we can't as easily tweak them
  # (update: I do have top-level defines gathered up in ${defines}).
  # so, we don't bother transforming -DFOO into -DFOO=FOO, nor with setting
  # up the --defsym args.
  set(CMAKE_ASM_COMPILE_OBJECT
    "${CMAKE_CPP} ${CMAKE_CPP_FLAGS} <FLAGS> <DEFINES> -E <SOURCE> -o <OBJECT>.s"
    "<CMAKE_COMMAND> -Dfile=<OBJECT>.s -P \"${cpp2asm_newline_script_path}\""
    # not using <FLAGS> b/c of cmake bug #8107 where -Ddynamorio_EXPORTS
    # is passed in: we don't need the include dirs b/c of the cpp step.
    # update: Brad fixed bug #8107: moved -Ddynamorio_EXPORTS from <FLAGS> to <DEFINES>
    # in CMake/Source/cmMakefileTargetGenerator.cxx:1.115 (will be in 2.6.4).
    #
    # we also aren't passing any <DEFINES> since for one thing
    # there's no way to transform to --defsym: luckily we don't need them
    # since using cpp now (see above).
    # FIXME: I tried setting CMAKE_ASM_DEFINE_FLAG to "--defsym " (not clear
    # how to get =1 in there :should verify it's needed) but <DEFINES>
    # comes up empty for me.
    "<CMAKE_ASM_COMPILER> ${ASM_FLAGS} -o <OBJECT> <OBJECT>.s"
    )
else ()
  # Even if we didn't preprocess we'd need our own rule since cmake doesn't
  # support ml.
  set(CMAKE_ASM_COMPILE_OBJECT
    # There's no way to specify a non-default name with /P: writes to
    # cwd/sourcebase.i.  Could copy with "cmake -E copy" but no way to
    # run get_filename_component on a tag var.  So going with
    # redirection operator which should work in all supported shells.
    #
    # ml can't handle line number markers so using ${CPP_NO_LINENUM}.
    "<CMAKE_C_COMPILER> ${CMAKE_CPP_FLAGS} <FLAGS> <DEFINES> -E ${CPP_NO_LINENUM} <SOURCE> > <OBJECT>.s"
    # cmake does add quotes in custom commands, etc. but not in this rule so we add
    # them to handle paths with spaces:
    "<CMAKE_COMMAND> -Dfile=<OBJECT>.s -P \"${cpp2asm_newline_script_path}\""
    "<CMAKE_ASM_COMPILER> ${ASM_FLAGS} /c /Fo<OBJECT> <OBJECT>.s"
    )
endif ()

##################################################
# Assembler build commands for Visual Studio generators and
# for assembly code contained in C source files for all generators

# Adds rules to pre-process and assemble the assembly file in ${source}.
# ${extra_defs} should be a list.  It is passed to the pre-processor.
# ${extra_deps} should be a list.  It is listed as dependencies of the build rule.
# The name of the generated output will be stored in the output_out variable.
# This will be an .obj file for VS or an .asm file for Makefiles, but in either case
# it should be listed as a source of final target executable/library
# If ${extra_suffix} is non-empty, it will be inserted into the output file
# and target name before the extension.
# For VS generators, the caller should add ${tgt_out} to the final target's
# dependencies.
function (add_asm_target source output_out tgt_out extra_suffix extra_defs extra_deps)
  if ("${CMAKE_GENERATOR}" MATCHES "Visual Studio")
    get_filename_component(srcbase "${source}" NAME)

    set(s_file "${CMAKE_CURRENT_BINARY_DIR}/${srcbase}${extra_suffix}.s")
    set_source_files_properties("${s_file}" PROPERTIES GENERATED true)
    set(output "${CMAKE_CURRENT_BINARY_DIR}/${srcbase}${extra_suffix}.obj")

    # XXX i#801: a VS2010 bug results in custom commands having all their sources
    # rebuilt which results in racy collision for asm deps (for DR tests, tools_asm).
    # Workaround is to indirect through a dummy .stamp file to avoid
    # chaining sources to custom commands, although this will only
    # include the .obj file in the target link with cmake 2.8.8+.
    # (Alternative is to not list as a source and add to link flags.)
    set(output_stamp "${output}.stamp")
    set_property(SOURCE "${output}" PROPERTY GENERATED true)
    set(output_tgt generate_${srcbase}${extra_suffix})

    add_custom_command(OUTPUT "${output_stamp}"
      DEPENDS
        "${source}"
        "${cpp2asm_newline_script_path}"
        ${extra_deps}
      COMMAND ${CMAKE_COMMAND}
        ARGS -E touch "${output_stamp}"
      COMMAND "${CMAKE_C_COMPILER}" ARGS ${CMAKE_CPP_FLAGS}
        /I "${PROJECT_BINARY_DIR}" ${extra_defs}
        -E ${CPP_NO_LINENUM} "${source}"
        ">" "${s_file}"
      COMMAND "${CMAKE_COMMAND}" ARGS
        -D file=${s_file}
        -P "${cpp2asm_newline_script_path}"
      # We assume we don't need ${defines} => list on cmdline b/c assembly
      # files include configure.h and cpp is where most defines are wanted
      COMMAND "${CMAKE_ASM_COMPILER}" ARGS ${ASM_FLAGS_LIST} /c
        /Fo "${output}"
        "${s_file}"
      WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
      VERBATIM # recommended: p260
      )

    # for correct parallel builds we need a target to avoid
    # duplicate and racy VS per-project rules
    add_custom_target(${output_tgt} DEPENDS "${output_stamp}")
    set(${output_out} "${output}" PARENT_SCOPE)
    set(${tgt_out} ${output_tgt} PARENT_SCOPE)
  else ("${CMAKE_GENERATOR}" MATCHES "Visual Studio")
    if (extra_deps)
      # XXX: how get cmake to analyze #includes of my asm files?
      set_source_files_properties(${source} PROPERTIES OBJECT_DEPENDS
        "${cpp2asm_newline_script_path};${extra_deps}")
    endif (extra_deps)
    # We can't set the extra_defs as source file property b/c caller might
    # use same source for two different targets
    set(${output_out} "${source}" PARENT_SCOPE)
    set(${tgt_out} "" PARENT_SCOPE)
  endif ("${CMAKE_GENERATOR}" MATCHES "Visual Studio")
endfunction (add_asm_target)

# Adds rules to pre-process and assemble the assembly portion of the C/C++
# file ${source}, which must be separated via #ifdef ASM_CODE_ONLY.
# ${extra_defs} are passed to the pre-processor.
# ${extra_deps} are listed as dependencies of the build rule.
# The name of the generated output will be stored in the output_out variable.
# This will be an .obj file for VS or an .asm file for Makefiles, but in either case
# it should be listed as a source of final target executable/library
# If ${extra_suffix} is non-empty, it will be inserted into the output file
# and target name before the extension.
# For VS generators, the caller should add ${tgt_out} to the final target's
# dependencies.
function (add_split_asm_target source output_out tgt_out
    extra_suffix extra_defs extra_deps)
  get_filename_component(srcbase ${source} NAME)
  set(asm_file "${CMAKE_CURRENT_BINARY_DIR}/${srcbase}${extra_suffix}.asm")

  # Be sure to make extra_defs a list!
  set(extra_defs -DASM_CODE_ONLY ${extra_defs})
  add_asm_target("${asm_file}" asm_output asm_target ""
    "${extra_defs}" "${extra_deps}")

  if ("${CMAKE_GENERATOR}" MATCHES "Visual Studio")
    add_custom_command(TARGET ${asm_target} PRE_BUILD
      COMMAND ${CMAKE_COMMAND}
        ARGS -E copy "${source}" "${asm_file}")
  else ("${CMAKE_GENERATOR}" MATCHES "Visual Studio")
    set_source_files_properties(${asm_file} PROPERTIES
      GENERATED ON COMPILE_FLAGS "-DASM_CODE_ONLY")
    add_custom_command(
      OUTPUT ${asm_file}
      DEPENDS ${source}
      COMMAND ${CMAKE_COMMAND}
      ARGS -E copy "${source}" "${asm_file}"
      VERBATIM # recommended: p260
      )
  endif ("${CMAKE_GENERATOR}" MATCHES "Visual Studio")

  set(${output_out} "${asm_output}" PARENT_SCOPE)
  set(${tgt_out} ${asm_target} PARENT_SCOPE)
endfunction(add_split_asm_target)
