| #! /usr/bin/env bash |
| #===-- tools/f18/flang.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 |
| #===------------------------------------------------------------------------===# |
| 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" |
| |
| # === 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) |
| # $4 - 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 libs=$4 |
| |
| 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 |
| object_files+=($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 |
| } |
| |
| # === 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="" |
| if [[ -v $FLANG_FC ]]; then |
| ext_fc="${FLANG_FC}" |
| elif [[ -v $F18_FC ]]; then |
| # We support F18_FC for backwards compatibility. |
| ext_fc="${F18_FC}" |
| else |
| ext_fc=gfortran |
| fi |
| |
| |
| local ext_fc="${FLANG_FC:-gfortran}" |
| 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() { |
| parse_args "$@" |
| |
| if [[ $PRINT_VERSION == "True" ]]; then |
| echo "flang version @FLANG_VERSION@" |
| exit 0 |
| fi |
| |
| 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 |
| readonly ext_fc="${F18_FC:-gfortran}" |
| 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 |
| 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 |
| object_files+=(${out_obj_file}) |
| done |
| |
| # STEP 4: Link |
| if [[ $COMPILE_ONLY == "True" ]]; then |
| exit 0; |
| fi |
| |
| if [[ ${#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[@]}" "${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 "${object_files[$idx]}" |
| done |
| } |
| |
| main "${@}" |