forked from OSchip/llvm-project
494 lines
16 KiB
Bash
Executable File
494 lines
16 KiB
Bash
Executable File
#! /usr/bin/env bash
|
|
#===-- tools/f18/flang-to-external-fc.sh --------------------------*- sh -*-===#
|
|
#
|
|
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
# See https://llvm.org/LICENSE.txt for license information.
|
|
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
#
|
|
#===------------------------------------------------------------------------===#
|
|
# A wrapper script for Flang's compiler driver that was developed for testing and
|
|
# experimenting. You should be able to use it as a regular compiler driver. It
|
|
# will:
|
|
# * run Flang's compiler driver to unparse the input source files
|
|
# * use the external compiler (defined via FLANG_FC environment variable) to
|
|
# compile the unparsed source files
|
|
#
|
|
# Tested with Bash 4.4. This script will exit immediately if you use an
|
|
# older version of Bash.
|
|
#===------------------------------------------------------------------------===#
|
|
set -euo pipefail
|
|
|
|
# Global variables to make the parsing of input arguments a bit easier
|
|
INPUT_FILES=()
|
|
OPTIONS=()
|
|
OUTPUT_FILE=""
|
|
MODULE_DIR=""
|
|
INTRINSICS_MOD_DIR=""
|
|
COMPILE_ONLY="False"
|
|
PREPROCESS_ONLY="False"
|
|
PRINT_VERSION="False"
|
|
|
|
# === check_bash_version ======================================================
|
|
#
|
|
# Checks the Bash version that's used to run this script. Exits immediately
|
|
# with a non-zero return code if it's lower than 4.4. Otherwise returns 0
|
|
# (success).
|
|
# =============================================================================
|
|
check_bash_version() {
|
|
message="Error: Your Bash is too old. Please use Bash >= 4.4"
|
|
# Major version
|
|
[[ "${BASH_VERSINFO[0]:-0}" -lt 4 ]] && echo $message && exit 1
|
|
|
|
# Minor version
|
|
if [[ "${BASH_VERSINFO[0]}" == 4 ]]; then
|
|
[[ "${BASH_VERSINFO[1]:-0}" -lt 4 ]] && echo $message && exit 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
# === parse_args ==============================================================
|
|
#
|
|
# Parse the input arguments passed to this script. Sets the global variables
|
|
# declared at the top.
|
|
#
|
|
# INPUTS:
|
|
# $1 - all input arguments
|
|
# OUTPUTS:
|
|
# Saved in the global variables for this script
|
|
# =============================================================================
|
|
parse_args()
|
|
{
|
|
while [ "${1:-}" != "" ]; do
|
|
# CASE 1: Compiler option
|
|
if [[ "${1:0:1}" == "-" ]] ; then
|
|
# Output file - extract it into a global variable
|
|
if [[ "$1" == "-o" ]] ; then
|
|
shift
|
|
OUTPUT_FILE="$1"
|
|
shift
|
|
continue
|
|
fi
|
|
|
|
# Module directory - extract it into a global variable
|
|
if [[ "$1" == "-module-dir" ]]; then
|
|
shift
|
|
MODULE_DIR="$1"
|
|
shift
|
|
continue
|
|
fi
|
|
|
|
# Intrinsics module dir - extract it into a global var
|
|
if [[ "$1" == "-intrinsics-module-directory" ]]; then shift
|
|
INTRINSICS_MOD_DIR=$1
|
|
shift
|
|
continue
|
|
fi
|
|
|
|
# Module suffix cannot be modified - this script defines it before
|
|
# calling the driver.
|
|
if [[ "$1" == "-module-suffix" ]]; then
|
|
echo "ERROR: \'-module-suffix\' is not available when using the \'flang\' script"
|
|
exit 1
|
|
fi
|
|
|
|
# Special treatment for `J <dir>` and `-I <dir>`. We translate these
|
|
# into `J<dir>` and `-I<dir>` respectively.
|
|
if [[ "$1" == "-J" ]] || [[ "$1" == "-I" ]]; then
|
|
opt=$1
|
|
shift
|
|
OPTIONS+=("$opt$1")
|
|
shift
|
|
continue
|
|
fi
|
|
|
|
# This is a regular option - just add it to the list.
|
|
OPTIONS+=($1)
|
|
if [[ $1 == "-c" ]]; then
|
|
COMPILE_ONLY="True"
|
|
fi
|
|
|
|
if [[ $1 == "-E" ]]; then
|
|
PREPROCESS_ONLY="True"
|
|
fi
|
|
|
|
if [[ $1 == "-v" || $1 == "--version" ]]; then
|
|
PRINT_VERSION="True"
|
|
fi
|
|
|
|
shift
|
|
continue
|
|
|
|
# CASE 2: A regular file (either source or a library file)
|
|
elif [[ -f "$1" ]]; then
|
|
INPUT_FILES+=($1)
|
|
shift
|
|
continue
|
|
|
|
else
|
|
# CASE 3: Unsupported
|
|
echo "ERROR: unrecognised option format: \`$1\`. Perhaps non-existent file?"
|
|
exit 1
|
|
fi
|
|
done
|
|
}
|
|
|
|
# === categorise_files ========================================================
|
|
#
|
|
# Categorises input files into:
|
|
# * Fortran source files (to be compiled)
|
|
# * library files (to be linked into the final executable)
|
|
#
|
|
# INPUTS:
|
|
# $1 - all input files to be categorised (array, name reference)
|
|
# OUTPUTS:
|
|
# $2 - Fortran source files extracted from $1 (array, name reference)
|
|
# $3 - other source files extracted from $1 (array, name reference)
|
|
# $4 - object files extracted from $1 (array, name reference)
|
|
# $5 - lib files extracted from $1 (array, name reference)
|
|
# =============================================================================
|
|
categorise_files()
|
|
{
|
|
local -n -r all_files=$1
|
|
local -n fortran_sources=$2
|
|
local -n other_sources=$3
|
|
local -n objects=$4
|
|
local -n libs=$5
|
|
|
|
for current_file in "${all_files[@]}"; do
|
|
file_ext=${current_file##*.}
|
|
if [[ $file_ext == "f" ]] || [[ $file_ext == "f90" ]] ||
|
|
[[ $file_ext == "f" ]] || [[ $file_ext == "F" ]] || [[ $file_ext == "ff" ]] ||
|
|
[[ $file_ext == "f90" ]] || [[ $file_ext == "F90" ]] || [[ $file_ext == "ff90" ]] ||
|
|
[[ $file_ext == "f95" ]] || [[ $file_ext == "F95" ]] || [[ $file_ext == "ff95" ]] ||
|
|
[[ $file_ext == "cuf" ]] || [[ $file_ext == "CUF" ]] || [[ $file_ext == "f18" ]] ||
|
|
[[ $file_ext == "F18" ]] || [[ $file_ext == "ff18" ]]; then
|
|
fortran_sources+=($current_file)
|
|
elif [[ $file_ext == "a" ]] || [[ $file_ext == "so" ]]; then
|
|
libs+=($current_file)
|
|
elif [[ $file_ext == "o" ]]; then
|
|
objects+=($current_file)
|
|
else
|
|
other_sources+=($current_file)
|
|
fi
|
|
done
|
|
}
|
|
|
|
# === categorise_opts ==========================================================
|
|
#
|
|
# Categorises compiler options into options for:
|
|
# * the Flang driver (either new or the "throwaway" driver)
|
|
# * the external Fortran driver that will generate the code
|
|
# Most options accepted by Flang will be claimed by it. The only exceptions are
|
|
# `-I` and `-J`.
|
|
#
|
|
# INPUTS:
|
|
# $1 - all compiler options (array, name reference)
|
|
# OUTPUTS:
|
|
# $2 - compiler options for the Flang driver (array, name reference)
|
|
# $3 - compiler options for the external driver (array, name reference)
|
|
# =============================================================================
|
|
categorise_opts()
|
|
{
|
|
local -n all_opts=$1
|
|
local -n flang_opts=$2
|
|
local -n fc_opts=$3
|
|
|
|
for opt in "${all_opts[@]}"; do
|
|
# These options are claimed by Flang, but should've been dealt with in parse_args.
|
|
if [[ $opt == "-module-dir" ]] ||
|
|
[[ $opt == "-o" ]] ||
|
|
[[ $opt == "-fintrinsic-modules-path" ]] ; then
|
|
echo "ERROR: $opt should've been fully processed by \`parse_args\`"
|
|
exit 1
|
|
fi
|
|
|
|
if
|
|
# The options claimed by Flang. This list needs to be compatible with
|
|
# what's supported by Flang's compiler driver (i.e. `flang-new`).
|
|
[[ $opt == "-cpp" ]] ||
|
|
[[ $opt =~ ^-D.* ]] ||
|
|
[[ $opt == "-E" ]] ||
|
|
[[ $opt == "-falternative-parameter-statement" ]] ||
|
|
[[ $opt == "-fbackslash" ]] ||
|
|
[[ $opt == "-fcolor-diagnostics" ]] ||
|
|
[[ $opt == "-fdefault-double-8" ]] ||
|
|
[[ $opt == "-fdefault-integer-8" ]] ||
|
|
[[ $opt == "-fdefault-real-8" ]] ||
|
|
[[ $opt == "-ffixed-form" ]] ||
|
|
[[ $opt =~ ^-ffixed-line-length=.* ]] ||
|
|
[[ $opt == "-ffree-form" ]] ||
|
|
[[ $opt == "-fimplicit-none" ]] ||
|
|
[[ $opt =~ ^-finput-charset=.* ]] ||
|
|
[[ $opt == "-flarge-sizes" ]] ||
|
|
[[ $opt == "-flogical-abbreviations" ]] ||
|
|
[[ $opt == "-fno-color-diagnostics" ]] ||
|
|
[[ $opt == "-fxor-operator" ]] ||
|
|
[[ $opt == "-help" ]] ||
|
|
[[ $opt == "-nocpp" ]] ||
|
|
[[ $opt == "-pedantic" ]] ||
|
|
[[ $opt =~ ^-std=.* ]] ||
|
|
[[ $opt =~ ^-U.* ]] ||
|
|
[[ $opt == "-Werror" ]]; then
|
|
flang_opts+=($opt)
|
|
elif
|
|
# We translate the following into equivalents understood by `flang-new`
|
|
[[ $opt == "-Mfixed" ]] || [[ $opt == "-Mfree" ]]; then
|
|
case $opt in
|
|
-Mfixed)
|
|
flang_opts+=("-ffixed-form")
|
|
;;
|
|
|
|
-Mfree)
|
|
flang_opts+=("-ffree-form")
|
|
;;
|
|
|
|
*)
|
|
echo "ERROR: $opt has no equivalent in 'flang-new'"
|
|
exit 1
|
|
;;
|
|
esac
|
|
elif
|
|
# Options that are needed for both Flang and the external driver.
|
|
[[ $opt =~ -I.* ]] ||
|
|
[[ $opt =~ -J.* ]] ||
|
|
[[ $opt == "-fopenmp" ]] ||
|
|
[[ $opt == "-fopenacc" ]]; then
|
|
flang_opts+=($opt)
|
|
fc_opts+=($opt)
|
|
else
|
|
# All other options are claimed for the external driver.
|
|
fc_opts+=($opt)
|
|
fi
|
|
done
|
|
}
|
|
|
|
# === get_external_fc_name ====================================================
|
|
#
|
|
# Returns the name of external Fortran compiler based on values of
|
|
# environment variables.
|
|
# =============================================================================
|
|
get_external_fc_name() {
|
|
if [[ -v FLANG_FC ]]; then
|
|
echo ${FLANG_FC}
|
|
elif [[ -v F18_FC ]]; then
|
|
# We support F18_FC for backwards compatibility.
|
|
echo ${F18_FC}
|
|
else
|
|
echo gfortran
|
|
fi
|
|
}
|
|
|
|
# === preprocess ==============================================================
|
|
#
|
|
# Runs the preprocessing. Fortran files are preprocessed using Flang. Other
|
|
# files are preprocessed using the external Fortran compiler.
|
|
#
|
|
# INPUTS:
|
|
# $1 - Fortran source files (array, name reference)
|
|
# $2 - other source files (array, name reference)
|
|
# $3 - compiler flags (array, name reference)
|
|
# =============================================================================
|
|
preprocess() {
|
|
local -n fortran_srcs=$1
|
|
local -n other_srcs=$2
|
|
local -n opts=$3
|
|
|
|
local ext_fc="$(get_external_fc_name)"
|
|
|
|
local -r wd=$(cd "$(dirname "$0")/.." && pwd)
|
|
|
|
# Use the provided output file name.
|
|
if [[ ! -z ${OUTPUT_FILE:+x} ]]; then
|
|
output_definition="-o $OUTPUT_FILE"
|
|
fi
|
|
|
|
# Preprocess fortran sources using Flang
|
|
for idx in "${!fortran_srcs[@]}"; do
|
|
if ! "$wd/bin/flang-new" -E "${opts[@]}" "${fortran_srcs[$idx]}" ${output_definition:+$output_definition}
|
|
then status=$?
|
|
echo flang: in "$PWD", flang-new failed with exit status $status: "$wd/bin/flang-new" "${opts[@]}" "$@" >&2
|
|
exit $status
|
|
fi
|
|
done
|
|
|
|
# Preprocess other sources using Flang
|
|
for idx in "${!other_srcs[@]}"; do
|
|
if ! $ext_fc -E "${opts[@]}" "${other_srcs[$idx]}" ${output_definition:+$output_definition}
|
|
then status=$?
|
|
echo flang: in "$PWD", flang-new failed with exit status $status: "$wd/bin/flang-new" "${opts[@]}" "$@" >&2
|
|
exit $status
|
|
fi
|
|
done
|
|
}
|
|
|
|
# === get_relocatable_name ======================================================
|
|
# This method generates the name of the output file for the compilation phase
|
|
# (triggered with `-c`). If the user of this script is only interested in
|
|
# compilation (`flang -c`), use $OUTPUT_FILE provided that it was defined.
|
|
# Otherwise, use the usual heuristics:
|
|
# * file.f --> file.o
|
|
# * file.c --> file.o
|
|
#
|
|
# INPUTS:
|
|
# $1 - input source file for which to generate the output name
|
|
# =============================================================================
|
|
get_relocatable_name() {
|
|
local -r src_file=$1
|
|
|
|
if [[ $COMPILE_ONLY == "True" ]] && [[ ! -z ${OUTPUT_FILE:+x} ]]; then
|
|
out_file="$OUTPUT_FILE"
|
|
else
|
|
current_ext=${src_file##*.}
|
|
new_ext="o"
|
|
|
|
out_file=$(basename "${src_file}" "$current_ext")${new_ext}
|
|
fi
|
|
|
|
echo "$out_file"
|
|
}
|
|
|
|
# === main ====================================================================
|
|
# Main entry point for this script
|
|
# =============================================================================
|
|
main() {
|
|
check_bash_version
|
|
parse_args "$@"
|
|
|
|
if [[ $PRINT_VERSION == "True" ]]; then
|
|
echo "flang version @FLANG_VERSION@"
|
|
exit 0
|
|
fi
|
|
|
|
# Source, object and library files provided by the user
|
|
local fortran_source_files=()
|
|
local other_source_files=()
|
|
local object_files=()
|
|
local lib_files=()
|
|
categorise_files INPUT_FILES fortran_source_files other_source_files object_files lib_files
|
|
|
|
if [[ $PREPROCESS_ONLY == "True" ]]; then
|
|
preprocess fortran_source_files other_source_files OPTIONS
|
|
exit 0
|
|
fi
|
|
|
|
# Options for the Flang driver.
|
|
# NOTE: We need `-fc1` to make sure that the frontend driver rather than
|
|
# compiler driver is used. We also need to make sure that that's the first
|
|
# flag that the driver will see (otherwise it assumes compiler/toolchain
|
|
# driver mode).
|
|
local flang_options=("-fc1")
|
|
# Options for the external Fortran Compiler
|
|
local ext_fc_options=()
|
|
categorise_opts OPTIONS flang_options ext_fc_options
|
|
|
|
local -r wd=$(cd "$(dirname "$0")/.." && pwd)
|
|
|
|
# uuidgen is common but not installed by default on some distros
|
|
if ! command -v uuidgen &> /dev/null
|
|
then
|
|
echo "uuidgen is required for generating unparsed file names."
|
|
exit 1
|
|
fi
|
|
|
|
# STEP 1: Unparse
|
|
# Base-name for the unparsed files. These are just temporary files that are
|
|
# first generated and then deleted by this script.
|
|
# NOTE: We need to make sure that the base-name is unique to every
|
|
# invocation. Otherwise we can't use this script in parallel.
|
|
local -r unique_id=$(uuidgen | cut -b25-36)
|
|
local -r unparsed_file_base="flang_unparsed_file_$unique_id"
|
|
|
|
flang_options+=("-module-suffix")
|
|
flang_options+=(".f18.mod")
|
|
flang_options+=("-fdebug-unparse")
|
|
flang_options+=("-fno-analyzed-objects-for-unparse")
|
|
|
|
[[ ! -z ${MODULE_DIR} ]] && flang_options+=("-module-dir ${MODULE_DIR}")
|
|
[[ ! -z ${INTRINSICS_MOD_DIR} ]] && flang_options+=("-intrinsics-module-directory ${INTRINSICS_MOD_DIR}")
|
|
for idx in "${!fortran_source_files[@]}"; do
|
|
set +e
|
|
"$wd/bin/flang-new" "${flang_options[@]}" "${fortran_source_files[$idx]}" -o "${unparsed_file_base}_${idx}.f90"
|
|
ret_status=$?
|
|
set -e
|
|
if [[ $ret_status != 0 ]]; then
|
|
echo flang: in "$PWD", flang-new failed with exit status "$ret_status": "$wd/bin/flang-new" "${flang_options[@]}" "$@" >&2
|
|
exit "$ret_status"
|
|
fi
|
|
done
|
|
|
|
# STEP 2: Compile Fortran Source Files
|
|
local ext_fc="$(get_external_fc_name)"
|
|
# Temporary object files generated by this script. To be deleted at the end.
|
|
local temp_object_files=()
|
|
for idx in "${!fortran_source_files[@]}"; do
|
|
# We always have to specify the output name with `-o <out_obj_file>`. This
|
|
# is because we are using the unparsed rather than the original source file
|
|
# below. As a result, we cannot rely on the compiler-generated output name.
|
|
out_obj_file=$(get_relocatable_name "${fortran_source_files[$idx]}")
|
|
|
|
set +e
|
|
$ext_fc "-c" "${ext_fc_options[@]}" "${unparsed_file_base}_${idx}.f90" "-o" "${out_obj_file}"
|
|
ret_status=$?
|
|
set -e
|
|
if [[ $ret_status != 0 ]]; then
|
|
echo flang: in "$PWD", "$ext_fc" failed with exit status "$ret_status": "$ext_fc" "${ext_fc_options[@]}" "$@" >&2
|
|
exit "$ret_status"
|
|
fi
|
|
temp_object_files+=(${out_obj_file})
|
|
done
|
|
|
|
# Delete the unparsed files
|
|
for idx in "${!fortran_source_files[@]}"; do
|
|
rm "${unparsed_file_base}_${idx}.f90"
|
|
done
|
|
|
|
# STEP 3: Compile Other Source Files
|
|
for idx in "${!other_source_files[@]}"; do
|
|
# We always specify the output name with `-o <out_obj_file>`. The user
|
|
# might have used `-o`, but we never add it to $OPTIONS (or
|
|
# $ext_fc_options). Hence we need to use `get_relocatable_name`.
|
|
out_obj_file=$(get_relocatable_name "${other_source_files[$idx]}")
|
|
|
|
set +e
|
|
$ext_fc "-c" "${ext_fc_options[@]}" "${other_source_files[${idx}]}" "-o" "${out_obj_file}"
|
|
ret_status=$?
|
|
set -e
|
|
if [[ $ret_status != 0 ]]; then
|
|
echo flang: in "$PWD", "$ext_fc" failed with exit status "$ret_status": "$ext_fc" "${ext_fc_options[@]}" "$@" >&2
|
|
exit "$ret_status"
|
|
fi
|
|
temp_object_files+=(${out_obj_file})
|
|
done
|
|
|
|
# STEP 4: Link
|
|
if [[ $COMPILE_ONLY == "True" ]]; then
|
|
exit 0;
|
|
fi
|
|
|
|
if [[ ${#temp_object_files[@]} -ge 1 ]] || [[ ${#object_files[@]} -ge 1 ]] ; then
|
|
# If $OUTPUT_FILE was specified, use it for the output name.
|
|
if [[ ! -z ${OUTPUT_FILE:+x} ]]; then
|
|
output_definition="-o $OUTPUT_FILE"
|
|
else
|
|
output_definition=""
|
|
fi
|
|
|
|
set +e
|
|
$ext_fc "${ext_fc_options[@]}" "${object_files[@]}" "${temp_object_files[@]}" "${lib_files[@]}" ${output_definition:+$output_definition}
|
|
ret_status=$?
|
|
set -e
|
|
if [[ $ret_status != 0 ]]; then
|
|
echo flang: in "$PWD", "$ext_fc" failed with exit status "$ret_status": "$ext_fc" "${ext_fc_options[@]}" "$@" >&2
|
|
exit "$ret_status"
|
|
fi
|
|
fi
|
|
|
|
# Delete intermediate object files
|
|
for idx in "${!fortran_source_files[@]}"; do
|
|
rm "${temp_object_files[$idx]}"
|
|
done
|
|
}
|
|
|
|
main "${@}"
|