#!/usr/bin/perl -w


# Need perl > 5.10 to use logic-defined or
use 5.006; use v5.10.1;
use File::Basename;

# HIP compiler driver
# Will call NVCC or HCC (depending on target) and pass the appropriate include and library options for
# the target compiler and HIP infrastructure.

# Will pass-through options to the target compiler.  The tools calling HIPCC must ensure the compiler
# options are appropriate for the target compiler.

# Environment variable HIP_PLATFORM control compilation path:
# HIP_PLATFORM='nvcc' or HIP_PLATFORM='hcc'.
# If HIP_PLATFORM is not set hipcc will attempt auto-detect based on if nvcc is found.
#
# Other environment variable controls:
# HIP_PATH  : Path to HIP directory, default is one dir level above location of this script
# CUDA_PATH : Path to CUDA SDK (default /usr/local/cuda). Used on NVIDIA platforms only.
# HCC_HOME  : Path to HCC SDK (default /opt/rocm/hcc).  Used on AMD platforms only.
# HSA_PATH  : Path to HSA dir (default /opt/rocm/hsa).  Used on AMD platforms only.

if(scalar @ARGV == 0){
    print "No Arguments passed, exiting ...\n";
    exit(-1);
}

#---
# Function to parse config file
sub parse_config_file {
    my ($file, $config) = @_;
    if (open (CONFIG, "$file")) {
        while (<CONFIG>) {
            my $config_line=$_;
            chop ($config_line);
            $config_line =~ s/^\s*//;
            $config_line =~ s/\s*$//;
            if (($config_line !~ /^#/) && ($config_line ne "")) {
                my ($name, $value) = split (/=/, $config_line);
                $$config{$name} = $value;
            }
        }
        close(CONFIG);
    }
}

$verbose = $ENV{'HIPCC_VERBOSE'} // 0;
# Verbose: 0x1=commands, 0x2=paths, 0x4=hippc args

$HIP_PATH=$ENV{'HIP_PATH'} // dirname (dirname $0); # use parent directory of hipcc

#---
# Read .hipInfo
my %hipConfig = ();
parse_config_file("$HIP_PATH/lib/.hipInfo", \%hipConfig);

#---
#HIP_PLATFORM controls whether to use NVCC or HCC for compilation:
$HIP_PLATFORM= `$HIP_PATH/bin/hipconfig --platform` // "hcc";
$HIP_VERSION= `$HIP_PATH/bin/hipconfig --version`;
($HIP_VERSION_MAJOR, $HIP_VERSION_MINOR, $HIP_VERSION_PATCH) = split(/\./, $HIP_VERSION);

if ($verbose & 0x2) {
    print ("HIP_PATH=$HIP_PATH\n");
    print ("HIP_PLATFORM=$HIP_PLATFORM\n");
}

# set if user explicitly requests -stdlib=libc++. (else we default to libstdc++ for better interop with g++):
$setStdLib = 0;  # TODO - set to 0

$target_gfx701 = 0;
$target_gfx801 = 0;
$target_gfx802 = 0;
$target_gfx803 = 0;
$target_gfx900 = 0;

if ($HIP_PLATFORM eq "hcc") {
    $HSA_PATH=$ENV{'HSA_PATH'} // "/opt/rocm/hsa";

    $HCC_HOME=$ENV{'HCC_HOME'} // $hipConfig{'HCC_HOME'} // "/opt/rocm/hcc";

    $HCC_VERSION=`${HCC_HOME}/bin/hcc --version`;
    $HCC_VERSION=~/.*based on HCC ([^ ]+).*/;
    $HCC_VERSION=$1;
    $HCC_VERSION_MAJOR=$HCC_VERSION;
    $HCC_VERSION_MAJOR=~s/\..*//;

    $ROCM_PATH=$ENV{'ROCM_PATH'} // "/opt/rocm";

    $HIP_ATP_MARKER=$ENV{'HIP_ATP_MARKER'} // 1;
    $marker_path = "$ROCM_PATH/profiler/CXLActivityLogger";

    $ROCM_TARGET=$ENV{'ROCM_TARGET'} // "gfx803";

    # HCC* may be used to compile src/hip_hcc.o (and also feed the HIPCXXFLAGS below)
    $HCC = "$HCC_HOME/bin/hcc";
    $HCCFLAGS = "-hc  -D__HIPCC__ -I$HCC_HOME/include ";

    $HIPCC=$HCC;
    $HIPCXXFLAGS = $HCCFLAGS;

    $HIPLDFLAGS = `${HCC_HOME}/bin/hcc-config --ldflags`;


    #### GCC system includes workaround ####
    $HCC_WA_FLAGS = " ";
    if ($HCC_VERSION_MAJOR eq 1) {
        my $GCC_CUR_VER = `gcc -dumpversion`;
        my $GPP_CUR_VER = `g++ -dumpversion`;
        $GCC_CUR_VER =~ s/\R//g;
        $GPP_CUR_VER =~ s/\R//g;

        my @GPP_VER_FIELDS = split('\.', $GPP_CUR_VER);

        # Only include the libstdc++ headers and libraries flags explicitly if the g++ is older than version 5.
        # That's because HCC already uses libstdc++ by default if a newer g++/libstdc++ is available
        if (${GCC_CUR_VER} eq ${GPP_CUR_VER} and $GPP_VER_FIELDS[0] < 5) {
            $HCC_WA_FLAGS .= " -stdlib=libstdc++  -I/usr/include/x86_64-linux-gnu -I/usr/include/x86_64-linux-gnu/c++/${GCC_CUR_VER} -I/usr/include/c++/${GCC_CUR_VER} ";
            # Add C++ libs for GCC.
            $HIPLDFLAGS .= " -lstdc++";
        }
    }

    # Force -stdlib=libc++ on UB14.04
    $HOST_OSNAME= `cat /etc/os-release | grep "^ID\=" | cut -d= -f2 | tr -d '\n'`;
    $HOST_OSVER= `cat /etc/os-release | grep "^VERSION_ID\=" | cut -d= -f2 | tr -d '\n'`;
    if ($HOST_OSNAME eq "ubuntu" and $HOST_OSVER eq "\"14.04\"") {
        $HIPCXXFLAGS .= " -stdlib=libc++";
        $setStdLib = 1;
    }

    $HIPCXXFLAGS .= " -I$HIP_PATH/include/hip/hcc_detail/cuda";
    $HIPCXXFLAGS .= " -I$HSA_PATH/include";
    $HIPCXXFLAGS .= " -Wno-deprecated-register";

    $HIPLDFLAGS .= " -lsupc++";
    $HIPLDFLAGS .= " -L$HSA_PATH/lib -L$ROCM_PATH/lib -lhsa-runtime64 -lhc_am -lhsakmt ";
#    $HIPLDFLAGS .= " -L$HCC_HOME/compiler/lib -lLLVMAMDGPUDesc -lLLVMAMDGPUUtils -lLLVMMC -lLLVMCore -lLLVMSupport ";

    # Add trace marker library:
    # TODO - once we cleanly separate the HIP API headers from HIP library headers this logic should move to CMakebuild option - apps do not need to see the marker library.
    if ($HIP_ATP_MARKER) {
        $marker_inc_path = "$marker_path/include";
        if (-e $marker_inc_path) {
            $HIPCXXFLAGS .= " -I$marker_inc_path";
        }
    }

    $marker_lib_path = "$marker_path/bin/x86_64";
    if (-e $marker_lib_path) {
        $HIPLDFLAGS .= " -L$marker_lib_path -lCXLActivityLogger -Wl,--rpath=$marker_lib_path";
    }

    $HIPLDFLAGS .= " -lm";

    if ($verbose & 0x2) {
        print ("HSA_PATH=$HSA_PATH\n");
        print ("HCC_HOME=$HCC_HOME\n");
    }

} elsif ($HIP_PLATFORM eq "nvcc") {
    if ($verbose & 0x2) {
        print ("CUDA_PATH=$CUDA_PATH\n");
    }
    $CUDA_PATH=$ENV{'CUDA_PATH'} // '/usr/local/cuda';

    $HIPCC="$CUDA_PATH/bin/nvcc";
    $HIPCXXFLAGS .= " -I$CUDA_PATH/include";

    $HIPLDFLAGS = "-lcuda -lcudart -L$CUDA_PATH/lib64";
} else {
    printf ("error: unknown HIP_PLATFORM = '$HIP_PLATFORM'");
    exit (-1);
}

# Add paths to common HIP includes:
$HIPCXXFLAGS .= " -I$HIP_PATH/include -DHIP_VERSION_MAJOR=$HIP_VERSION_MAJOR -DHIP_VERSION_MINOR=$HIP_VERSION_MINOR -DHIP_VERSION_PATCH=$HIP_VERSION_PATCH" ;

my $compileOnly = 0;
my $needCXXFLAGS = 0;  # need to add CXX flags to compile step
my $needLDFLAGS = 1;   # need to add LDFLAGS to compile step.
my $hasC = 0;          # options contain a c-style file (NVCC must force recognition as GPU file)
my $hasCU = 0;         # options contain a cu-style file (HCC must force recognition as GPU file)
my $needHipHcc = ($HIP_PLATFORM eq 'hcc');      # set if we need to link hip_hcc.o from src tree. (some builds, ie cmake, provide their own)
my $printHipVersion = 0;    # print HIP version
my $runCmd = 1;
my $buildDeps = 0;
my $linkType = 1;
my $setLinkType = 0;

my @options = ();
my @inputs  = ();

if ($verbose & 0x4) {
    print "hipcc-args: ", join (" ", @ARGV), "\n";
}

# Handle code object generation
my $ISACMD="";
if($HIP_PLATFORM eq "hcc"){
    $ISACMD .= "set ROCM_PATH=$ROCM_PATH && set ROCM_TARGET=$ROCM_TARGET && $HIP_PATH/bin/hccgenco.sh ";
    if($ARGV[0] eq "--genco"){
        foreach $isaarg (@ARGV[1..$#ARGV]){
            $ISACMD .= " ";
            $ISACMD .= $isaarg;
        }
        if ($verbose & 0x1) {
            print "hipcc-cmd: ", $ISACMD, "\n";
        }
        system($ISACMD) and die();
        exit(0);
    }
}

if(($HIP_PLATFORM eq "hcc")){
    $EXPORT_LL=" ";
    $ENV{HCC_EXTRA_LIBRARIES}="$HIP_PATH/lib/hip_hc.ll\n";
    $ENV{HIP_HC_IR_FILE}="";
}

if($HIP_PLATFORM eq "nvcc"){
    $ISACMD .= "$HIP_PATH/bin/hipcc -ptx ";
    if($ARGV[0] eq "--genco"){
        foreach $isaarg (@ARGV[1..$#ARGV]){
            $ISACMD .= " ";
            $ISACMD .= $isaarg;
        }
        if ($verbose & 0x1) {
            print "hipcc-cmd: ", $ISACMD, "\n";
        }
        system($ISACMD) and die();
        exit(0);
    }
}

my $toolArgs = "";  # arguments to pass to the hcc or nvcc tool

foreach $arg (@ARGV)
{
    $trimarg = $arg;
    $trimarg =~ s/^\s+|\s+$//g;  # Remive whitespace
    my $swallowArg = 0;
    if ($arg eq '-c') {
        $compileOnly = 1;
        $needCXXFLAGS = 1;
        $needLDFLAGS  = 0;
    }
    if ($arg eq '-o') {
        $needLDFLAGS = 1;
    }

    if(($trimarg eq '-stdlib=libc++') and ($setStdLib eq 0))
    {
        $HIPCXXFLAGS .= " -stdlib=libc++";
        $setStdLib = 1;
    }
    if($arg eq '--amdgpu-target=gfx701')
    {
        $target_gfx701 = 1;
    }
    if($arg eq '--amdgpu-target=gfx801')
    {
        $target_gfx801 = 1;
    }
    if($arg eq '--amdgpu-target=gfx802')
    {
        $target_gfx802 = 1;
    }
    if($arg eq '--amdgpu-target=gfx803')
    {
        $target_gfx803 = 1;
    }
    if($arg eq '--amdgpu-target=gfx900')
    {
        $target_gfx900 = 1;
    }

    if(($trimarg eq '-stdlib=libstdc++') and ($setStdLib eq 0))
    {
        $HIPCXXFLAGS .= $HCC_WA_FLAGS;
        $setStdLib = 1;
    }
    if($trimarg eq '--version') {
        $printHipVersion = 1;
    }
    if($trimarg eq '--short-version') {
        $printHipVersion = 1;
        $runCmd = 0;
    }
    if($trimarg eq '-M') {
        $compileOnly = 1;
        $buildDeps = 1;
    }
    if($trimarg eq '-use_fast_math') {
        $HIPCXXFLAGS .= " -DHIP_FAST_MATH ";
    }
    if(($trimarg eq '-use-staticlib') and ($setLinkType eq 0))
    {
        $linkType = 0;
        $setLinkType = 1;
    }
    if(($trimarg eq '-use-sharedlib') and ($setLinkType eq 0))
    {
        $linkType = 1;
        $setLinkType = 1;
    }

    if ($arg =~ m/^-/) {
        # options start with -

        # Process HIPCC options here:
        if ($arg =~ m/^--hipcc/) {
            $swallowArg = 1;
            #if $arg eq "--hipcc_profile") {  # Example argument here, hipcc
            #
            #}
        } else {
            push (@options, $arg);
        }
        #print "O: <$arg>\n";
    } else  {
        # input files and libraries
        if (($arg =~ /\.cpp$/) or ($arg =~ /\.c$/) or ($arg =~ /\.cc$/) ) {
            $hasC = 1;
            $needCXXFLAGS = 1;
        }
        if (($arg =~ /\.cu$/) or ($arg =~ /\.cuh$/)) {
            $hasCU = 1;
            $needCXXFLAGS = 1;
        }

        push (@inputs, $arg);
        #print "I: <$arg>\n";
    }
    $toolArgs .= " $arg" unless $swallowArg;
}
if(defined $ENV{HCC_AMDGPU_TARGET})
{
    foreach my $target (split(/,/, $ENV{HCC_AMDGPU_TARGET}))
    {
        if($target eq 'gfx701')
        {
            $target_gfx701 = 1;
        }
        if($target eq 'gfx801')
        {
            $target_gfx801 = 1;
        }
        if($target eq 'gfx802')
        {
            $target_gfx802 = 1;
        }
        if($target eq 'gfx803')
        {
            $target_gfx803 = 1;
        }
        if($target eq 'gfx900')
        {
            $target_gfx900 = 1;
        }
    }
}
if ($target_gfx701 eq 0 and $target_gfx801 eq 0 and $target_gfx802 eq 0 and $target_gfx803 eq 0 and $target_gfx900 eq 0)
{
    $target_gfx803 = 1;
}

if($HIP_PLATFORM eq "hcc"){

    $ENV{HCC_EXTRA_LIBRARIES}="$HIP_PATH/lib/hip_hc.ll\n";

    # Handle ROCm target platform
    if ($target_gfx701 eq 1) {
        $HIPLDFLAGS .= " --amdgpu-target=gfx701";
        $HIPCXXFLAGS .= " -D__HIP_ARCH_GFX701__=1 ";
    }
    if ($target_gfx801 eq 1) {
        $HIPLDFLAGS .= " --amdgpu-target=gfx801";
        $HIPCXXFLAGS .= " -D__HIP_ARCH_GFX801__=1 ";
    }
    if ($target_gfx802 eq 1) {
        $HIPLDFLAGS .= " --amdgpu-target=gfx802";
        $HIPCXXFLAGS .= " -D__HIP_ARCH_GFX802__=1 ";
    }
    if ($target_gfx803 eq 1) {
        $HIPLDFLAGS .= " --amdgpu-target=gfx803";
        $HIPCXXFLAGS .= " -D__HIP_ARCH_GFX803__=1 ";
        $ENV{HCC_EXTRA_LIBRARIES_GFX803}="$HIP_PATH/lib/hip_hc_gfx803.ll\n";
    }
    if ($target_gfx900 eq 1) {
        $HIPLDFLAGS .= " --amdgpu-target=gfx900";
        $HIPCXXFLAGS .= " -D__HIP_ARCH_GFX900__=1 ";
    }
}

if ($hasC and $HIP_PLATFORM eq 'nvcc') {
    $HIPCXXFLAGS .= " -x cu";
}
if ($hasCU and $HIP_PLATFORM eq 'hcc') {
    $HIPCXXFLAGS .= " -x c++";
}
if ($buildDeps and $HIP_PLATFORM eq 'nvcc') {
    $HIPCXXFLAGS .= " -M -D__CUDACC__";
}

if ($setStdLib eq 0 and $HIP_PLATFORM eq 'hcc')
{
    $HIPCXXFLAGS .= $HCC_WA_FLAGS;
}

if ($needHipHcc) {
    if ($linkType eq 0) {
        substr($HIPLDFLAGS,0,0) = "  $HIP_PATH/lib/libhip_hcc_static.a $HIP_PATH/lib/libhip_device.a " ;
    } else {
        substr($HIPLDFLAGS,0,0) = "  -Wl,--rpath=$HIP_PATH/lib $HIP_PATH/lib/libhip_hcc.so $HIP_PATH/lib/libhip_device.a ";
    }
}

# hipcc currrently requires separate compilation of source files, ie it is not possible to pass
# CPP files combined with .O files
# Reason is that NVCC uses the file extension to determine whether to compile in CUDA mode or
# pass-through CPP mode.



my $CMD="$HIPCC";
if ($needCXXFLAGS) {
    $CMD .= " $HIPCXXFLAGS";
}
if ($needLDFLAGS and not $compileOnly) {
    $CMD .= " $HIPLDFLAGS";
}
$CMD .= " $toolArgs";

if ($verbose & 0x1) {
    print "hipcc-cmd: ", $CMD, "\n";
}

if ($printHipVersion) {
    if ($runCmd) {
        print "HIP version: "
    }
    print $HIP_VERSION, "\n";
}
if ($runCmd) {
    if ($HIP_PLATFORM eq "hcc" and exists($hipConfig{'HCC_VERSION'}) and $HCC_VERSION ne $hipConfig{'HCC_VERSION'}) {
        print ("HIP ($HIP_PATH) was built using hcc $hipConfig{'HCC_VERSION'}, but you are using $HCC_HOME/hcc with version $HCC_VERSION from hipcc. Please rebuild HIP including cmake or update HCC_HOME variable.\n") ;
        die unless $ENV{'HIP_IGNORE_HCC_VERSION'};
    }
    system ("$CMD") and die ();
}

# vim: ts=4:sw=4:expandtab:smartindent
