#!/bin/sh
# set -x
#
#------------------------------------
# Part of HPCToolkit (hpctoolkit.org)
#------------------------------------
#
# Copyright (c) 2002-2013, Rice University.
# 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 Rice University (RICE) 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 RICE 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 RICE 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.
#
# $HeadURL: http://hpctoolkit.googlecode.com/svn/trunk/src/tool/hpcrun/scripts/hpclink.in $
# $Id: hpclink.in 4171 2013-05-15 21:17:33Z mwkrentel $
#
# hpclink -- link application with libhpcrun, libmonitor, PAPI and
# Xed2 statically by editing the compile line.
#
# Usage: hpclink [options] compiler file ...
#
#     -h, --help
#     --memleak
#     -u, --undefined  <symbol>
#     -v, --verbose
#     -V, --version
#
#  where <symbol> is a symbol name passed to the linker (may be used
#  multiple times).
#
#------------------------------------------------------------
# Values from configure
#------------------------------------------------------------

VERSION='5.3.2-r4197'

# If this script can't find its own install prefix, or if the script
# is moved elsewhere, then set HPCTOOLKIT here.
# HPCTOOLKIT='/usr/local/hpctoolkit/openmpi-1.6.3/5.3.2'

# Relative paths are relative to HPCTOOLKIT.
ext_libs_dir='lib/hpctoolkit/ext-libs'
hpcfnbounds_dir='libexec/hpctoolkit'
libhpcrun_dir='lib/hpctoolkit'
libmonitor_dir='lib/hpctoolkit/ext-libs'
xed2_dir='lib/hpctoolkit/ext-libs'
libunwind_dir='lib/hpctoolkit/ext-libs'
libunwind_hpclink_libs=' -lunwind -lunwind-generic'

# Absolute path or empty.
papi_dir='/usr/lib64'
papi_extra_libs=' -lpfm'

upc_libs=''

hpctk_wrap_names='_Exit _exit exit main dlclose dlopen execl execle execlp execv execve execvp fork system vfork pthread_create pthread_exit pthread_sigmask sigtimedwait sigwait sigwaitinfo sigaction signal sigprocmask MPI_Init MPI_Init_thread MPI_Finalize MPI_Comm_rank mpi_init mpi_init_thread mpi_finalize mpi_comm_rank mpi_init_ mpi_init_thread_ mpi_finalize_ mpi_comm_rank_ mpi_init__ mpi_init_thread__ mpi_finalize__ mpi_comm_rank__   '

extra_wrap_names=
extra_hpc_files=

# Compiler and flags for array of fnbounds addresses.
CC='gcc -std=gnu99 '

hello="_hpc_hello_$$"
nm_addrs="_hpc_nm_addrs_$$"
cmd_out="_hpc_output_$$"

# Space-separated list of symbol names to force undefined.
undef_names=

# Space-separated list of libs (without -l) not to repeat on command line.
no_repeat_list='hugetlbfs'

#------------------------------------------------------------

cleanup()
{
    rm -f _hpc_*$$*
}

die()
{
    echo "$0: error: $*" 1>&2
    cleanup
    exit 1
}

mesg()
{
    echo "hpclink: $*"
}

#------------------------------------------------------------
# Usage Message
#------------------------------------------------------------

usage()
{
    cat <<EOF
Usage: hpclink [options] <link-command>

hpclink links HPCToolkit's performance measurement library into a
statically linked application.  (hpcrun's method for injecting its library
into a dynamically linked application will not work with a statically
linked applications.)

To link with hpclink, supply your application's normal link line as
<link-command>, which typically has the following form:
  <compiler> [link-options] <object-files> <libraries>

To control HPCToolkit's performance measurement library during an
application's execution, use the following environment variables:
  HPCRUN_EVENT_LIST=<event1>[@<period1>];...;<eventN>[@<periodN>]
                             : Sampling event list; hpcrun -e/--event
  HPCRUN_TRACE=1             : Enable tracing; hpcrun -t/--trace
  HPCRUN_PROCESS_FRACTION=<f>: Measure only a fraction <f> of the execution's
                               processes; hpcrun -f/-fp/--process-fraction
  HPCRUN_OUT_PATH=<outpath>  : Set output directory; hpcrun -o/--output

Options: Informational
  -v, --verbose        Verbose. Displays the original and modified command
                       lines.
  -V, --version        Print version information.
  -h, --help           Print help.

Options: Linking
  -dw, --double-wrap   Double quote the linker wrap options (-Wl,-Wl instead
                       of -Wl).  Used to work around problems with the 
                       Berkeley UPC compiler script (rarely needed).

  -fe <dir>, --front-end <dir>
                       In a cross-compile link, use the current install tree
                       for the back-end libraries and use <dir> for the install
                       directory (prefix) of the front-end tools (hpcfnbounds).

  --io                 Include the IO bytes read and written library.

  --ga                 Include GA (Global Arrays) wrappers.

  --memleak            Include HPCToolkit's memory leak detection libraries.

  --plugin name        Add the libraries and wrapped symbols specified by
                       the plugin 'name' in the plugins directory.

  -u <symbol>, --undefined <symbol>
                       Pass <symbol> to the linker as an undefined symbol.
                       This is to force the linker to pull in a reference
                       for that symbol.  May be used multiple times.
EOF
    exit 0
}

#------------------------------------------------------------
# Find path to this script
#------------------------------------------------------------

hpc_path_to_root=..
#
# Set and export HPCTOOLKIT to the root dir of the install (prefix).
# If HPCTOOLKIT already names a valid directory, then use that, else
# try to find the root directory from $0.
#
hpc_path_to_root="${hpc_path_to_root:-..}"
valid_hpctoolkit_dir()
{
    test -d "$1" && test -d "$1/lib/hpctoolkit"
}

# Warn if HPCTOOLKIT is set improperly, set to abs path if ok.
if test "x$HPCTOOLKIT" != x ; then
    if valid_hpctoolkit_dir "$HPCTOOLKIT" ; then
	HPCTOOLKIT=`( cd "$HPCTOOLKIT" 2>/dev/null && pwd )`
    else
	echo "Warning: invalid HPCTOOLKIT directory: $HPCTOOLKIT" 1>&2
    fi
fi

# Try dirname of $0, dereference symlink if needed.
if valid_hpctoolkit_dir "$HPCTOOLKIT" ; then :
else
    if test -L "$0" ; then
	hpc_file=`readlink "$0"`
    else
	hpc_file="$0"
    fi
    hpc_dir=`dirname "$hpc_file"`
    HPCTOOLKIT=`( cd "${hpc_dir}/${hpc_path_to_root}" 2>/dev/null && pwd )`
fi

if valid_hpctoolkit_dir "$HPCTOOLKIT" ; then :
else
    cat <<EOF >&2

$0: Unable to find HPCTOOLKIT root directory.
Please set HPCTOOLKIT to the install prefix, either in this script,
or in your environment, and try again.
EOF
    exit 1
fi

export HPCTOOLKIT

# Relative paths are relative to HPCTOOLKIT.
case "$ext_libs_dir" in
    /* ) ;;
    * )  ext_libs_dir="${HPCTOOLKIT}/${ext_libs_dir}" ;;
esac
case "$libhpcrun_dir" in
    /* ) ;;
    * )  libhpcrun_dir="${HPCTOOLKIT}/${libhpcrun_dir}" ;;
esac
case "$libmonitor_dir" in
    /* ) ;;
    * )  libmonitor_dir="${HPCTOOLKIT}/${libmonitor_dir}" ;;
esac
case "$xed2_dir" in
    /* ) ;;
    * )  xed2_dir="${HPCTOOLKIT}/${xed2_dir}" ;;
esac
case "$libunwind_dir" in
    /* ) ;;
    * )  libunwind_dir="${HPCTOOLKIT}/${libunwind_dir}" ;;
esac

LD_LIBRARY_PATH="${ext_libs_dir}:${LD_LIBRARY_PATH}"
export LD_LIBRARY_PATH

#------------------------------------------------------------
# Step 1 -- Parse linker options, our options first.
#------------------------------------------------------------

double_wrap=no
front_end_dir=no
my_plugin_list=
verbose=no
verb=:

while test "x$1" != x
do
    case "$1" in
	-h | --help )
	    usage
	    ;;
	-dw | --double-wrap )
	    double_wrap=yes
	    shift
	    ;;
	-fe | -front-end | --front-end )
	    test "x$2" != x || die "missing argument for --front-end"
	    front_end_dir="$2"
	    shift ; shift
	    ;;
	-io | --io )
	    io_wrap="${libhpcrun_dir}/libhpcrun_io_wrap.a"
	    test -f "$io_wrap" || die "unable to find: $io_wrap"
	    extra_hpc_files="$extra_hpc_files $io_wrap"
	    extra_wrap_names="$extra_wrap_names read write fread fwrite"
	    undef_names="$undef_names fwrite"
	    shift
	    ;;
	-ga | --ga )
	    ga_wrap="${libhpcrun_dir}/libhpcrun_ga_wrap.a"
	    test -f "$ga_wrap" || die "unable to find: $ga_wrap"
	    extra_hpc_files="$extra_hpc_files $ga_wrap"
	    extra_wrap_names="$extra_wrap_names \
                pnga_create \
                pnga_brdcst pnga_gop pnga_sync pnga_zero \
                pnga_get pnga_put pnga_acc \
                pnga_nbget pnga_nbput pnga_nbacc"
                # pnga_nbwait
	    undef_names="$undef_names pnga_create"
	    shift
	    ;;
	-memleak | --memleak )
	    memleak_wrap="${libhpcrun_dir}/libhpcrun_memleak_wrap.a"
	    test -f "$memleak_wrap" || die "unable to find: $memleak_wrap"
	    extra_hpc_files="$extra_hpc_files $memleak_wrap"
	    extra_wrap_names="$extra_wrap_names posix_memalign memalign valloc"
	    extra_wrap_names="$extra_wrap_names calloc free malloc realloc"
	    undef_names="$undef_names malloc"
	    shift
	    ;;
	-plugin | --plugin )
	    test "x$2" != x || die "missing argument for --plugin"
	    my_plugin_list="${my_plugin_list} $2"
	    shift ; shift
	    ;;
	-u | --undefined )
	    test "x$2" != x || die "missing argument for -u"
	    undef_names="${undef_names} $2"
	    shift ; shift
	    ;;
	-v | --verbose )
	    verbose=yes
	    verb=mesg
	    shift
	    ;;
	-V | --version )
	    echo "hpclink: A member of HPCToolkit, version $VERSION"
	    exit 0
	    ;;
	-- )
	    shift
	    break
	    ;;
	-* )
	    die "unknown option: $1"
	    ;;
	* )
	    break
	    ;;
    esac
done

#
# In a cross-compile link, we use fnbounds from the front-end tree and
# hpcrun libs, externals libs, PAPI and CC from the back-end tree.
# Note: since fnbounds is a program with a launch script, we need to
# reset HPCTOOLKIT to the front-end root before running fnbounds.
#
if test "$front_end_dir" != no ; then
    HPCTOOLKIT=`cd "$front_end_dir" && pwd`
    if test ! -d "$HPCTOOLKIT" ; then
        die "bad front-end directory: $front_end_dir"
    fi
fi

hpcfnbounds_dir="${HPCTOOLKIT}/${hpcfnbounds_dir}"
hpcfnbounds="${hpcfnbounds_dir}/hpcfnbounds"
test -x "$hpcfnbounds" || die "missing hpcfnbounds: $hpcfnbounds"

#
# Add plugin files: hpclink --plugin name
# Plugin name and hpclink_files can be absolute path or relative to
# the plugins directory.
#
extra_plugin_files=
for plugin in $my_plugin_list
do
    $verb "plugin: $plugin"
    case "$plugin" in
	/* ) conf_file="$plugin" ;;
	* )  conf_file="${libhpcrun_dir}/plugins/${plugin}" ;;
    esac
    my_plugin_dir=`dirname "$conf_file"`
    test -f "$conf_file" || die "missing plugin config file: $conf_file"
    file "$conf_file" | grep text >/dev/null 2>&1
    test $? -eq 0 || die "bad plugin config file: $conf_file"
    hpclink_files=
    hpclink_wrap_names=
    hpclink_undefined_names=
    . "$conf_file"
    for file in $hpclink_files ; do
	case "$file" in
	    /* ) abs_file="$file" ;;
	    * )  abs_file="${my_plugin_dir}/${file}" ;;
	esac
	test -f "$abs_file" || die "missing plugin file: $abs_file"
	$verb "plugin file: $abs_file"
	extra_plugin_files="$extra_plugin_files $abs_file"
    done
    $verb "plugin wrap names: $hpclink_wrap_names"
    $verb "plugin undefined names: $hpclink_undefined_names"
    extra_wrap_names="$extra_wrap_names $hpclink_wrap_names"
    undef_names="$undef_names $hpclink_undefined_names"
done

#
# Must have a compiler command and at least one argument.
#
test "x$2" != x || usage
command="$1"
shift

#
# Read the command line for: -l<lib> and -o <file> arguments.
# It's important not to change the command line here.
#
appl_libs=
appl_out=a.out
prev_arg=no
for arg in "$@"
do
    if test "x$prev_arg" = x-o ; then
	appl_out="$arg"
    else
	case "$arg" in
	    -l?* )
		copy=yes
		for lib in $no_repeat_list ; do
		    if test "x$arg" = "x-l$lib" ; then
			copy=no
			break
		    fi
		done
		if test "$copy" = yes ; then
		    appl_libs="$appl_libs $arg"
		fi
		;;
	esac
    fi
    prev_arg="$arg"
done

#------------------------------------------------------------
# Step 2 -- Build the new compile line.
#------------------------------------------------------------

# New compile line:
# undef-args, wrap-args, $@, libhpcrun.o, nm-addrs, libhpcrun_wrap.a,
# libmonitor, plugin libraries, PAPI, UPC, Xed2, -lpthread, -ldl,
# repeat-application-libs.

if test "$verbose" = yes ; then
    echo "original command line: $command $@"
    echo
fi

# Work around a problem with the Berkeley UPC compiler script.
if test "$double_wrap" = yes ; then
    linker_prefix=-Wl,-Wl
else
    linker_prefix=-Wl
fi

undef_args=
for name in $undef_names
do
    undef_args="${undef_args} ${linker_prefix},--undefined=${name}"
done

wrap_args=
for name in $hpctk_wrap_names $extra_wrap_names
do
    wrap_args="${wrap_args} ${linker_prefix},--wrap=${name}"
done

libhpcrun="${libhpcrun_dir}/libhpcrun.o"
test -f "$libhpcrun" || die "no such file: $libhpcrun"
set -- $undef_args $wrap_args $libhpcrun "$@"
set -- "$@" "${nm_addrs}.o"

libhpcrun_wrap="${libhpcrun_dir}/libhpcrun_wrap.a"
test -f "$libhpcrun_wrap" || die "no such file: $libhpcrun_wrap"
set -- "$@" "$libhpcrun_wrap" $extra_hpc_files

libmonitor="${libmonitor_dir}/libmonitor_wrap.a"
test -f "$libmonitor" || die "no such file: $libmonitor"
set -- "$@" "$libmonitor" $extra_plugin_files

if test -d "$papi_dir" ; then
    set -- "$@" "-L${papi_dir}" -lpapi $papi_extra_libs
fi

if test -d "$xed2_dir" ; then
    set -- "$@" "-L${xed2_dir}" -lxed
fi

if test -d "$libunwind_dir" ; then
    set -- "$@" "-L$libunwind_dir" $libunwind_hpclink_libs
fi

set -- "$@" $upc_libs

# FIXME: shouldn't always need pthread, and shoudn't ever need dl.
set -- "$@" -ldl -lrt -lpthread
set -- "$@" $appl_libs

#------------------------------------------------------------
# Step 3 -- Link with dummy nm file.
#------------------------------------------------------------

rm -f "${hello}.c" "$hello"
cat <<EOF > "${hello}.c"
int main(int argc, char **argv)
{
    return (0);
}
EOF
$CC -o "$hello" "${hello}.c"

rm -f "${nm_addrs}.c" "${nm_addrs}.o"
$hpcfnbounds -c "$hello" > "${nm_addrs}.c" || die "hpcfnbounds failed on $hello"
$CC -c -o "${nm_addrs}.o" "${nm_addrs}.c"

#
# Some compiler scripts add extra function calls to the end of the
# link line that fail to trigger --wrap.  So, if the first compile
# fails with undefined references to __wrap_foo, then add those names
# to the undef list and try again.  Do the same for an undefined
# reference to 'main' (gfortran does some bad things).
#
$command "$@" >"$cmd_out" 2>&1
if test $? -ne 0 || test ! -f "$appl_out"
then
    exec <"$cmd_out"
    while read line
    do
        if echo "$line" | grep -E -i -e 'undef.*__wrap_' >/dev/null 2>&1
	then
	    name=`expr "$line" : '.*\(__wrap_[A-Za-z0-9_]*\)'`
	    set -- "${linker_prefix},--undefined=${name}" "$@"
	elif echo "$line" | grep -E -i -e 'undef.*main' >/dev/null 2>&1
	then
	    set -- "${linker_prefix},--undefined=main" "$@"
	fi
    done
    $command "$@" || die "compile #1 failed: $command $@"
    test -f "$appl_out" || die "compile #1 failed: $command $@"
fi

# Require the output to be statically linked.
if file "$appl_out" | grep -i static >/dev/null ; then :
else
    mv -f "$appl_out" "${appl_out}.failed"
    die "program not statically linked (maybe forgot -static): $appl_out"
fi

#------------------------------------------------------------
# Step 4 -- Link with real nm file.
#------------------------------------------------------------

if test "$verbose" = yes ; then
    echo "new command line: $command $@"
    echo
fi

rm -f "${nm_addrs}.c" "${nm_addrs}.o"
$hpcfnbounds -c "$appl_out" > "${nm_addrs}.c" || die "hpcfnbounds failed on $appl_out"
$CC -c -o "${nm_addrs}.o" "${nm_addrs}.c"
$command "$@" || die "compile #2 failed: $command $@"
test -f "$appl_out" || die "compile #2 failed: $command $@"

cleanup
exit 0
