#!/bin/bash
#
#  aompcc: Wrapper (driver) for the AOMP compiler. 
#          The goal of this wrapper is to have a simplified interface for 
#          compiling for openmp target offload.
#
#  Written by Greg Rodgers  Gregory.Rodgers@amd.com
PROGVERSION="5.5.3"
#
# Copyright(C) 2020 Advanced Micro Devices, Inc. All rights reserved.
# 
# AMD is granting you permission to use this software and documentation (if any) (collectively, the 
# Materials) pursuant to the terms and conditions of the Software License Agreement included with the 
# Materials.  If you do not have a copy of the Software License Agreement, contact your AMD 
# representative for a copy.
# 
# You agree that you will not reverse engineer or decompile the Materials, in whole or in part, except for 
# example code which is provided in source code form and as allowed by applicable law.
# 
# WARRANTY DISCLAIMER: THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY 
# KIND.  AMD DISCLAIMS ALL WARRANTIES, EXPRESS, IMPLIED, OR STATUTORY, INCLUDING BUT NOT 
# LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 
# PURPOSE, TITLE, NON-INFRINGEMENT, THAT THE SOFTWARE WILL RUN UNINTERRUPTED OR ERROR-
# FREE OR WARRANTIES ARISING FROM CUSTOM OF TRADE OR COURSE OF USAGE.  THE ENTIRE RISK 
# ASSOCIATED WITH THE USE OF THE SOFTWARE IS ASSUMED BY YOU.  Some jurisdictions do not 
# allow the exclusion of implied warranties, so the above exclusion may not apply to You. 
# 
# LIMITATION OF LIABILITY AND INDEMNIFICATION:  AMD AND ITS LICENSORS WILL NOT, 
# UNDER ANY CIRCUMSTANCES BE LIABLE TO YOU FOR ANY PUNITIVE, DIRECT, INCIDENTAL, 
# INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING FROM USE OF THE SOFTWARE OR THIS 
# AGREEMENT EVEN IF AMD AND ITS LICENSORS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH 
# DAMAGES.  In no event shall AMD's total liability to You for all damages, losses, and 
# causes of action (whether in contract, tort (including negligence) or otherwise) 
# exceed the amount of $100 USD.  You agree to defend, indemnify and hold harmless 
# AMD and its licensors, and any of their directors, officers, employees, affiliates or 
# agents from and against any and all loss, damage, liability and other expenses 
# (including reasonable attorneys' fees), resulting from Your use of the Software or 
# violation of the terms and conditions of this Agreement.  
# 
# U.S. GOVERNMENT RESTRICTED RIGHTS: The Materials are provided with "RESTRICTED RIGHTS." 
# Use, duplication, or disclosure by the Government is subject to the restrictions as set 
# forth in FAR 52.227-14 and DFAR252.227-7013, et seq., or its successor.  Use of the 
# Materials by the Government constitutes acknowledgement of AMD's proprietary rights in them.
# 
# EXPORT RESTRICTIONS: The Materials may be subject to export restrictions as stated in the 
# Software License Agreement.
# 

echo "Warning: aompcc is now deprecated."

# FIXME: Remove this after backend makes this default
TEMP_FIX_ARGS=""
function usage(){
/bin/cat 2>&1 <<"EOF" 

   aompcc: Wrapper (driver) for the AOMP compiler. 
           The goal of this wrapper is to have a simplified interface
           for compiling openmp target offload.
   Usage:  aompcc [ options ] input-files

   Options without values:
    -g        Generate debug information
    -version  Display version of aompcc then exit
    --version Display version of aompcc then exit
    -v        Verbose, just print commands
    -vv       Very verbose, pass -v to commands
    -n        Dryrun, do nothing, show commands that would execute
    -h        Print this help message
    -k        Keep temporary files
    -c        Compile to object code only

   Options with values:         
    -aomp      <path>           $AOMP or /opt/rocm/llvm
    -I         <include dir>    Provide one directory per -I option
    -O         <LLVM opt>       LLVM optimization level
    -o         <outfilename>    Default=a.out
    -t         <tdir>           Temporary directory or intermediate files
                                Default=/tmp/aompcc-tmp-$$
    --offload-arch  <cputype>  Default= value returned by mygpu utility

   Examples:
    aompcc my.c              /* Compiles with OpenMP to create a.out */

   Note: Instead of providing these command line options:
     -aomp, --offload-arch,
     you may set these environment variables, respectively:
     AOMP, AOMP_GPU

   Command line options take precedence over environment variables. 

   Copyright (c) 2020 ADVANCED MICRO DEVICES, INC.

EOF
   exit 0 
}

DEADRC=12

#  Utility Functions
function do_err(){
   if [ $NEWTMPDIR ] ; then 
      if [ $KEEPTDIR ] ; then 
         cp -rp $TMPDIR $OUTDIR
         [ $VERBOSE ] && echo "#Info:  Temp files copied to $OUTDIR/$TMPNAME"
      fi
      rm -rf $TMPDIR
   else 
      if [ $KEEPTDIR ] ; then 
         [ $VERBOSE ] && echo "#Info:  Temp files kept in $TMPDIR"
      fi 
   fi
   [ $VV ] && echo "#Info:  Done"
   exit $1
}

function version(){
   echo $PROGVERSION
   exit 0
}

function runcmd(){
   if [ $# -eq 3 ]; then
      THISCMD="$1 \"$2\" \"$3\""
      outfile="$2"
      infile="$3"
   else
      THISCMD="$1"
   fi
   if [ $DRYRUN ] ; then
      echo "$THISCMD"
   else 
      [ $VERBOSE ] && echo "$THISCMD"
      if [ $# -eq 3 ]; then
         # Quotes are necessary to safeguard against file paths with spaces.
         $1 "$outfile" "$infile"
         rc=$?
      else
         $THISCMD
         rc=$?
      fi
      if [ $rc != 0 ] ; then 
         echo "ERROR:  The following command failed with return code $rc."
         echo "        $THISCMD"
         do_err $rc
      fi
   fi
}

function getdname(){
   local __DIRN=`dirname "$1"`
   if [ "$__DIRN" = "." ] ; then 
      __DIRN=$PWD; 
   else
      if [ ${__DIRN:0:1} != "/" ] ; then 
         if [ ${__DIRN:0:2} == ".." ] ; then 
               __DIRN=`dirname $PWD`/${__DIRN:3}
         else
            if [ ${__DIRN:0:1} = "." ] ; then 
               __DIRN=$PWD/${__DIRN:2}
            else
               __DIRN=$PWD/$__DIRN
            fi
         fi
      fi
   fi
   echo $__DIRN
}

#  --------  The main code starts here -----
INCLUDES=""
PASSTHRUARGS=""
INPUTFILES=""
#  Argument processing
while [ $# -gt 0 ] ; do
   case "$1" in 
      -q)               QUIET=true;;
      --quiet)          QUIET=true;;
      -k) 		KEEPTDIR=true;; 
      -n) 		DRYRUN=true;; 
      -c)               GEN_OBJECT_ONLY=true;;
      -g)               GEN_DEBUG=true;;
      -noqp) 		NOQP=true;;
      -noshared) 	NOSHARED=true;;
      -I) 		INCLUDES="$INCLUDES -I $2"; shift ;; 
      -O) 		LLVMOPT=$2; shift ;; 
      -O3) 		LLVMOPT=3 ;; 
      -O2) 		LLVMOPT=2 ;; 
      -O1) 		LLVMOPT=1 ;; 
      -O0) 		LLVMOPT=0 ;; 
      -o) 		OUTFILE=$2; shift ;; 
      -t)		TMPDIR=$2; shift ;; 
      --offload-arch=*) AOMP_GPU=`echo $1 | sed "s/--offload-arch=//g"`;;
      --offload-arch)   AOMP_GPU=$2; shift ;;
      -aomp)            AOMP=$2; shift ;;
      -triple)          TARGET_TRIPLE=$2; shift ;;
      -cuda-path)       CUDA_PATH=$2; shift ;;
      -h) 	        usage ;;
      -help) 	        usage ;;
      --help) 	        usage ;;
      -version) 	version ;;
      --version) 	version ;;
      -v) 		VERBOSE=true;;
      -vv) 		VV=true;;
      --) 		shift ;;
      *)
        dash=${1:0:1}
	if [ $dash == "-" ] ; then
	   PASSTHRUARGS+=" $1"
        else
	   if [ "$INPUTFILES" == "" ]; then
	      INPUTFILES+="$1"
	   else
	      INPUTFILES+=",$1"
	   fi
        fi
   esac
   shift
done

fcount=0

# INPUTFILES is comma separated, temporarily set IFS to comma.
IFS=,
for __input_file in $INPUTFILES ; do
   fcount=$(( fcount + 1 ))
   if [ $fcount == 1 ] ; then
      FIRST_INPUT_FILE_NAME=$__input_file
   fi
   if [ ! -e "$__input_file" ] ; then
      echo "ERROR:  The file $__input_file does not exist."
      exit $DEADRC
   fi
done
IFS=" "
if [ -z "$FIRST_INPUT_FILE_NAME" ]  ; then
   echo "ERROR:  No File specified."
   exit $DEADRC
fi

cdir=$(getdname $0)
[ ! -L "$cdir/aompcc" ] || cdir=$(getdname `readlink -f "$cdir/aompcc"`)
HOW_CALLED=${0##*/}

AOMP=${AOMP:-/opt/rocm/llvm}
if [ ! -d $AOMP ] ; then
   echo "ERROR: AOMP not found at $AOMP"
   echo "       Please install AOMP or set environment variable AOMP"
   echo ""
   echo "Examples: export AOMP=/opt/rocm/llvm"
   echo "          export AOMP=/usr/lib/aomp"
   exit 1
fi

TARGET_TRIPLE=${TARGET_TRIPLE:-amdgcn-amd-amdhsa}

# Determine which gfx processor to use.
if [ ! $AOMP_GPU ] ; then 
   # Use the mygpu in pair with this script, not the pre-installed one.
   AOMP_GPU=`$cdir/mygpu`
   if [ "$AOMP_GPU" == "" ] || [ "$AOMP_GPU" == "unknown" ] ; then
      echo "Valid GPU not detected. Please specify --offload-arch or set AOMP_GPU."
      exit 1
   fi
fi

LLVMOPT=${LLVMOPT:-3}

if [ $VV ]  ; then 
   VERBOSE=true
fi

RUNDATE=`date`

# Parse FIRST_INPUT_FILE_NAME for filetype, directory, and filename
INPUT_FTYPE=${FIRST_INPUT_FILE_NAME##*\.}
INDIR=$(getdname "$FIRST_INPUT_FILE_NAME")
FILENAME=${FIRST_INPUT_FILE_NAME##*/}
# FNAME has the filetype extension removed, used for naming intermediate filenames
FNAME=${FILENAME%.*}

if [ -z $OUTFILE ] ; then 
#  Output file not specified so use input directory
   OUTDIR=$INDIR
   if [ $GEN_OBJECT_ONLY ] ; then
      OUTFILE=${FNAME}.o
   else
      OUTFILE="a.out"
   fi
else 
#  Use the specified OUTFILE
   OUTDIR=$(getdname "$OUTFILE")
   OUTFILE=${OUTFILE##*/}
fi 

sdir=$(getdname $0)
[ ! -L "$sdir/aompcc" ] || sdir=$(getdname `readlink "$sdir/aompcc"`)
ROCC_DIR=$sdir

TMPNAME="aompcc-tmp-$$"
TMPDIR=${TMPDIR:-/tmp/$TMPNAME}
if [ -d "$TMPDIR" ] ; then
   KEEPTDIR=true
else 
   if [ $DRYRUN ] ; then
      echo "mkdir -p $TMPDIR"
   else
      mkdir -p $TMPDIR
      NEWTMPDIR=true
   fi
fi

# Be sure not to delete the output directory
if [ "$TMPDIR" == "$OUTDIR" ] ; then
   KEEPTDIR=true
fi
if [ ! -d "$TMPDIR" ] && [ ! $DRYRUN ] ; then
   echo "ERROR:  Directory $TMPDIR does not exist or could not be created"
   exit $DEADRC
fi 
if [ ! -d "$OUTDIR" ] && [ ! $DRYRUN ]  ; then
   echo "ERROR:  The output directory $OUTDIR does not exist"
   exit $DEADRC
fi 

#  Print Header block
if [ $VERBOSE ] ; then 
   echo "#   "
   echo "#Info:  AOMP Path:	$AOMP/bin"
   echo "#Info:  How called:	$HOW_CALLED"
   echo "#Info:  Run date:	$RUNDATE"
   echo "#Info:  Input files:	$INPUTFILES"
   echo "#Info:  Code object:	$OUTDIR/$OUTFILE"
   [ $KEEPTDIR ] &&  echo "#Info:  Temp dir:	$TMPDIR"
   echo "#   "
fi 

UNAMEP=`uname -m`
HOST_TARGET="$UNAMEP-pc-linux-gnu"
if [ "$UNAMEP" == "ppc64le" ] ; then 
  HOST_TARGET="ppc64le-linux-gnu"
fi

#  Pick the compiler based on filetype 
COMPILER_BIN_DIR="$AOMP/bin"
clangversion=$($AOMP/bin/clang --version | grep -oP '(?<=clang version )[0-9.]+')
CLANG_INCLUDE_DIR="$AOMP"/lib/clang/"$clangversion"/include

if [ "$HOW_CALLED" == "hipcc" ] || [ "$INPUT_FTYPE" == "hip" ] ; then
   CLANG_CMD="clang++"
   AOMP_GPU_UCASE=${AOMP_GPU^^}
   CLANG_ARGS="-O$LLVMOPT  \
-lamdhip64 -fopenmp \
-std=c++11 -I$CLANG_INCLUDE_DIR \
-I$AOMP/hsa/include \
-D__HIP_VDI__ \
-fhip-new-launch-api \
-I$AOMP/include  \
-D__HIP_ARCH_${AOMP_GPU_UCASE}__=1 \
--cuda-gpu-arch=$AOMP_GPU "
   #  treat rest of c and cpp files as hip
   HOW_CALLED="hipcc"
elif [ "$INPUT_FTYPE" == "cu" ] ; then
   echo "WARNING: CUDA WITH NVCC IN aompcc IS UNTESTED "
   if [ ! -d $CUDA_PATH ] ; then 
      echo "WARNING:  No CUDA_PATH directory at $CUDA_PATH "
      exit $DEADRC 
   fi
   CLANG_CMD="nvcc"
   INCLUDES="-I $CUDA_PATH/include ${INCLUDES}"
   CLANG_ARGS="$CLANG_ARGS $INCLUDES"
   COMPILER_BIN_DIR="$CUDA_PATH/bin"

elif [ "$INPUT_FTYPE" == "cl" ] ; then 
   echo "WARNING: OPENCL IN aompcc IS UNTESTED "
   CLANG_CMD="clang-ocl"
   if [ $CL12 ] ; then
      CLANG_ARGS="$CLANG_ARGS -emit-llvm -target $TARGET_TRIPLE -x cl -D__AMD__=1 -D__$AOMP_GPU__=1  -D__OPENCL_VERSION__=120 -D__IMAGE_SUPPORT__=1 -O3 -m64 -cl-kernel-arg-info -cl-std=CL1.2 -mllvm -amdgpu-early-inline-all -Xclang -target-feature -Xclang -code-object-v3 -Xclang -cl-ext=+cl_khr_fp64,+cl_khr_global_int32_base_atomics,+cl_khr_global_int32_extended_atomics,+cl_khr_local_int32_base_atomics,+cl_khr_local_int32_extended_atomics,+cl_khr_int64_base_atomics,+cl_khr_int64_extended_atomics,+cl_khr_3d_image_writes,+cl_khr_byte_addressable_store,+cl_khr_gl_sharing,+cl_amd_media_ops,+cl_amd_media_ops2,+cl_khr_subgroups -include $AOMP/lib/clang/9.0.1/include/opencl-c.h $CLOPTS $LINKOPTS"
   else
      CLANG_ARGS="$CLANG_ARGS -x cl -Xclang -cl-std=CL2.0 -Xclang -code-object-v3 $CLOPTS $LINKOPTS $INCLUDES -include $AOMP/lib/clang/9.0.1/include/opencl-c.h -Dcl_clang_storage_class_specifiers -Dcl_khr_fp64 -target ${TARGET_TRIPLE}"
   fi

elif [ "$INPUT_FTYPE" == "cpp" ]  || [ "$INPUT_FTYPE" == "cxx" ] ; then
   # OpenMP c++
   CLANG_ARGS="$CLANG_ARGS -O$LLVMOPT -target $HOST_TARGET -fopenmp -fopenmp-targets=$TARGET_TRIPLE -Xopenmp-target=$TARGET_TRIPLE -march=$AOMP_GPU $TEMP_FIX_ARGS"
   CLANG_CMD="clang++"

elif [ "$INPUT_FTYPE" == "c" ] ; then 
   # OpenMP c
   CLANG_ARGS="$CLANG_ARGS -O$LLVMOPT -target $HOST_TARGET -fopenmp -fopenmp-targets=$TARGET_TRIPLE -Xopenmp-target=$TARGET_TRIPLE -march=$AOMP_GPU $TEMP_FIX_ARGS"
   CLANG_CMD="clang"

elif [ "$INPUT_FTYPE" == "f" ] ; then 
   # OpenMP FORTRAN
   CLANG_ARGS="$CLANG_ARGS -O$LLVMOPT -target $HOST_TARGET -fopenmp -fopenmp-targets=$TARGET_TRIPLE -Xopenmp-target=$TARGET_TRIPLE -march=$AOMP_GPU"
   CLANG_CMD="flang"
else
   echo "ERROR:  PRIMARY FILE TYPE $INPUT_FTYPE NOT SUPPORTED"
   exit $DEADRC
fi

if [ $GEN_DEBUG ]  ; then
   CLANG_ARGS=" -g $CLANG_ARGS"
fi
if [ $VV ]  ; then
   CLANG_ARGS=" -v $CLANG_ARGS"
fi
if [ $GEN_OBJECT_ONLY ]  ; then
   CLANG_ARGS=" -c $CLANG_ARGS"
fi

__INPUTS=""
IFS=,
for __input_file in $INPUTFILES; do
  _ftype=${__input_file##*\.}
  if [ "$_ftype" == "hip" ] ; then
     CLANG_ARGS+=" -x hip"
  elif [ "$HOW_CALLED" == "hipcc" ] && [ "$_ftype" == "cpp" ] ; then
     CLANG_ARGS+=" -x hip"
  elif [ "$HOW_CALLED" == "hipcc" ] && [ "$_ftype" == "hpp" ] ; then
     CLANG_ARGS+=" -x hip"
  elif [ "$HOW_CALLED" == "hipcc" ] && [ "$_ftype" == "c" ] ; then
     CLANG_ARGS+=" -x hip"
  elif [ "$HOW_CALLED" == "hipcc" ] && [ "$_ftype" == "h" ] ; then
     CLANG_ARGS+=" -x hip"
  fi
  __INPUTS+=" $__input_file"
done
IFS=" "

# Remove space at beginning of __INPUTS string if it exists.
__ADJUSTEDINPUTS=$(echo $__INPUTS | sed "s/^[[:space:]]//")

runcmd "$COMPILER_BIN_DIR/$CLANG_CMD $CLANG_ARGS $PASSTHRUARGS -o " "$OUTDIR/$OUTFILE" "$__ADJUSTEDINPUTS"

# cleanup
do_err 0
exit 0
