1#! /usr/bin/env bash 2#===-- tools/f18/flang.sh -----------------------------------------*- sh -*-===# 3# 4# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 5# See https://llvm.org/LICENSE.txt for license information. 6# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 7# 8#===------------------------------------------------------------------------===# 9# A wrapper script for Flang's compiler driver that was developed for testing and 10# experimenting. You should be able to use it as a regular compiler driver. It 11# will: 12# * run Flang's compiler driver to unparse the input source files 13# * use the external compiler (defined via F18_FC environment variable) to 14# compile the unparsed source files 15#===------------------------------------------------------------------------===# 16set -euo pipefail 17 18# Global variables to make the parsing of input arguments a bit easier 19INPUT_FILES=() 20OPTIONS=() 21OUTPUT_FILE="" 22MODULE_DIR="" 23INTRINSICS_MOD_DIR="" 24COMPILE_ONLY="False" 25PREPROCESS_ONLY="False" 26PRINT_VERSION="False" 27 28# === parse_args ============================================================== 29# 30# Parse the input arguments passed to this script. Sets the global variables 31# declared at the top. 32# 33# INPUTS: 34# $1 - all input arguments 35# OUTPUTS: 36# Saved in the global variables for this script 37# ============================================================================= 38parse_args() 39{ 40 while [ "${1:-}" != "" ]; do 41 # CASE 1: Compiler option 42 if [[ "${1:0:1}" == "-" ]] ; then 43 # Output file - extract it into a global variable 44 if [[ "$1" == "-o" ]] ; then 45 shift 46 OUTPUT_FILE="$1" 47 shift 48 continue 49 fi 50 51 # Module directory - extract it into a global variable 52 if [[ "$1" == "-module-dir" ]]; then 53 shift 54 MODULE_DIR="$1" 55 shift 56 continue 57 fi 58 59 # Intrinsics module dir - extract it into a global var 60 if [[ "$1" == "-intrinsics-module-directory" ]]; then shift 61 INTRINSICS_MOD_DIR=$1 62 shift 63 continue 64 fi 65 66 # Module suffix cannot be modified - this script defines it before 67 # calling the driver. 68 if [[ "$1" == "-module-suffix" ]]; then 69 echo "ERROR: \'-module-suffix\' is not available when using the \'flang\' script" 70 exit 1 71 fi 72 73 # Special treatment for `J <dir>` and `-I <dir>`. We translate these 74 # into `J<dir>` and `-I<dir>` respectively. 75 if [[ "$1" == "-J" ]] || [[ "$1" == "-I" ]]; then 76 opt=$1 77 shift 78 OPTIONS+=("$opt$1") 79 shift 80 continue 81 fi 82 83 # This is a regular option - just add it to the list. 84 OPTIONS+=($1) 85 if [[ $1 == "-c" ]]; then 86 COMPILE_ONLY="True" 87 fi 88 89 if [[ $1 == "-E" ]]; then 90 PREPROCESS_ONLY="True" 91 fi 92 93 if [[ $1 == "-v" || $1 == "--version" ]]; then 94 PRINT_VERSION="True" 95 fi 96 97 shift 98 continue 99 100 # CASE 2: A regular file (either source or a library file) 101 elif [[ -f "$1" ]]; then 102 INPUT_FILES+=($1) 103 shift 104 continue 105 106 else 107 # CASE 3: Unsupported 108 echo "ERROR: unrecognised option format: \`$1\`. Perhaps non-existent file?" 109 exit 1 110 fi 111 done 112} 113 114# === categorise_files ======================================================== 115# 116# Categorises input files into: 117# * Fortran source files (to be compiled) 118# * library files (to be linked into the final executable) 119# 120# INPUTS: 121# $1 - all input files to be categorised (array, name reference) 122# OUTPUTS: 123# $2 - Fortran source files extracted from $1 (array, name reference) 124# $3 - other source files extracted from $1 (array, name reference) 125# $4 - object files extracted from $1 (array, name reference) 126# $4 - lib files extracted from $1 (array, name reference) 127# ============================================================================= 128categorise_files() 129{ 130 local -n -r all_files=$1 131 local -n fortran_sources=$2 132 local -n other_sources=$3 133 local -n libs=$4 134 135 for current_file in "${all_files[@]}"; do 136 file_ext=${current_file##*.} 137 if [[ $file_ext == "f" ]] || [[ $file_ext == "f90" ]] || 138 [[ $file_ext == "f" ]] || [[ $file_ext == "F" ]] || [[ $file_ext == "ff" ]] || 139 [[ $file_ext == "f90" ]] || [[ $file_ext == "F90" ]] || [[ $file_ext == "ff90" ]] || 140 [[ $file_ext == "f95" ]] || [[ $file_ext == "F95" ]] || [[ $file_ext == "ff95" ]] || 141 [[ $file_ext == "cuf" ]] || [[ $file_ext == "CUF" ]] || [[ $file_ext == "f18" ]] || 142 [[ $file_ext == "F18" ]] || [[ $file_ext == "ff18" ]]; then 143 fortran_sources+=($current_file) 144 elif [[ $file_ext == "a" ]] || [[ $file_ext == "so" ]]; then 145 libs+=($current_file) 146 elif [[ $file_ext == "o" ]]; then 147 object_files+=($current_file) 148 else 149 other_sources+=($current_file) 150 fi 151 done 152} 153 154# === categorise_opts ========================================================== 155# 156# Categorises compiler options into options for: 157# * the Flang driver (either new or the "throwaway" driver) 158# * the external Fortran driver that will generate the code 159# Most options accepted by Flang will be claimed by it. The only exceptions are 160# `-I` and `-J`. 161# 162# INPUTS: 163# $1 - all compiler options (array, name reference) 164# OUTPUTS: 165# $2 - compiler options for the Flang driver (array, name reference) 166# $3 - compiler options for the external driver (array, name reference) 167# ============================================================================= 168categorise_opts() 169{ 170 local -n all_opts=$1 171 local -n flang_opts=$2 172 local -n fc_opts=$3 173 174 for opt in "${all_opts[@]}"; do 175 # These options are claimed by Flang, but should've been dealt with in parse_args. 176 if [[ $opt == "-module-dir" ]] || 177 [[ $opt == "-o" ]] || 178 [[ $opt == "-fintrinsic-modules-path" ]] ; then 179 echo "ERROR: $opt should've been fully processed by \`parse_args\`" 180 exit 1 181 fi 182 183 if 184 # The options claimed by Flang. This list needs to be compatible with 185 # what's supported by Flang's compiler driver (i.e. `flang-new` and f18). 186 [[ $opt == "-cpp" ]] || 187 [[ $opt =~ ^-D.* ]] || 188 [[ $opt == "-E" ]] || 189 [[ $opt == "-falternative-parameter-statement" ]] || 190 [[ $opt == "-fbackslash" ]] || 191 [[ $opt == "-fcolor-diagnostics" ]] || 192 [[ $opt == "-fdefault-double-8" ]] || 193 [[ $opt == "-fdefault-integer-8" ]] || 194 [[ $opt == "-fdefault-real-8" ]] || 195 [[ $opt == "-ffixed-form" ]] || 196 [[ $opt =~ ^-ffixed-line-length=.* ]] || 197 [[ $opt == "-ffree-form" ]] || 198 [[ $opt == "-fimplicit-none" ]] || 199 [[ $opt =~ ^-finput-charset=.* ]] || 200 [[ $opt == "-flarge-sizes" ]] || 201 [[ $opt == "-flogical-abbreviations" ]] || 202 [[ $opt == "-fno-color-diagnostics" ]] || 203 [[ $opt == "-fopenacc" ]] || 204 [[ $opt == "-fopenmp" ]] || 205 [[ $opt == "-fxor-operator" ]] || 206 [[ $opt == "-help" ]] || 207 [[ $opt == "-nocpp" ]] || 208 [[ $opt == "-pedantic" ]] || 209 [[ $opt =~ ^-std=.* ]] || 210 [[ $opt =~ ^-U.* ]] || 211 [[ $opt == "-Werror" ]]; then 212 flang_opts+=($opt) 213 elif 214 # These options are not supported by `flang-new`. There is also no point 215 # in forwarding them to the host compiler as the output from 216 # `-fdebug-unparse` will always be in free form. 217 [[ $opt == "-Mfixed" ]] || [[ $opt == "-Mfree" ]]; then 218 : 219 elif [[ $opt =~ -I.* ]] || [[ $opt =~ -J.* ]]; then 220 # Options that are needed for both Flang and the external driver. 221 flang_opts+=($opt) 222 fc_opts+=($opt) 223 else 224 # All other options are claimed for the external driver. 225 fc_opts+=($opt) 226 fi 227 done 228} 229 230# === preprocess ============================================================== 231# 232# Runs the preprocessing. Fortran files are preprocessed using Flang. Other 233# files are preprocessed using the external Fortran compiler. 234# 235# INPUTS: 236# $1 - Fortran source files (array, name reference) 237# $2 - other source files (array, name reference) 238# $3 - compiler flags (array, name reference) 239# ============================================================================= 240preprocess() { 241 local -n fortran_srcs=$1 242 local -n other_srcs=$2 243 local -n opts=$3 244 245 local -r ext_fc="${F18_FC:-gfortran}" 246 local -r wd=$(cd "$(dirname "$0")/.." && pwd) 247 248 # Use the provided output file name. 249 if [[ ! -z ${OUTPUT_FILE:+x} ]]; then 250 output_definition="-o $OUTPUT_FILE" 251 fi 252 253 # Preprocess fortran sources using Flang 254 for idx in "${!fortran_srcs[@]}"; do 255 if ! "$wd/bin/@FLANG_DEFAULT_DRIVER@" -E "${opts[@]}" "${fortran_srcs[$idx]}" ${output_definition:+$output_definition} 256 then status=$? 257 echo flang: in "$PWD", @FLANG_DEFAULT_DRIVER@ failed with exit status $status: "$wd/bin/@FLANG_DEFAULT_DRIVER@" "${opts[@]}" "$@" >&2 258 exit $status 259 fi 260 done 261 262 # Preprocess other sources using Flang 263 for idx in "${!other_srcs[@]}"; do 264 if ! $ext_fc -E "${opts[@]}" "${other_srcs[$idx]}" ${output_definition:+$output_definition} 265 then status=$? 266 echo flang: in "$PWD", flang-new failed with exit status $status: "$wd/bin/flang-new" "${opts[@]}" "$@" >&2 267 exit $status 268 fi 269 done 270} 271 272# === get_relocatable_name ====================================================== 273# This method generates the name of the output file for the compilation phase 274# (triggered with `-c`). If the user of this script is only interested in 275# compilation (`flang -c`), use $OUTPUT_FILE provided that it was defined. 276# Otherwise, use the usual heuristics: 277# * file.f --> file.o 278# * file.c --> file.o 279# 280# INPUTS: 281# $1 - input source file for which to generate the output name 282# ============================================================================= 283get_relocatable_name() { 284 local -r src_file=$1 285 286 if [[ $COMPILE_ONLY == "True" ]] && [[ ! -z ${OUTPUT_FILE:+x} ]]; then 287 out_file="$OUTPUT_FILE" 288 else 289 current_ext=${src_file##*.} 290 new_ext="o" 291 292 out_file=$(basename "${src_file}" "$current_ext")${new_ext} 293 fi 294 295 echo "$out_file" 296} 297 298# === main ==================================================================== 299# Main entry point for this script 300# ============================================================================= 301main() { 302 parse_args "$@" 303 304 if [[ $PRINT_VERSION == "True" ]]; then 305 echo "flang version @FLANG_VERSION@" 306 exit 0 307 fi 308 309 local fortran_source_files=() 310 local other_source_files=() 311 local object_files=() 312 local lib_files=() 313 categorise_files INPUT_FILES fortran_source_files other_source_files object_files lib_files 314 315 if [[ $PREPROCESS_ONLY == "True" ]]; then 316 preprocess fortran_source_files other_source_files OPTIONS 317 exit 0 318 fi 319 320 # Options for the Flang driver. 321 # NOTE: We need `-fc1` to make sure that the frontend driver rather than 322 # compiler driver is used. We also need to make sure that that's the first 323 # flag that the driver will see (otherwise it assumes compiler/toolchain 324 # driver mode).`f18` will just ignore this flag when uparsing, so it's fine 325 # to add it here unconditionally. 326 local flang_options=("-fc1") 327 # Options for the external Fortran Compiler 328 local ext_fc_options=() 329 categorise_opts OPTIONS flang_options ext_fc_options 330 331 local -r wd=$(cd "$(dirname "$0")/.." && pwd) 332 333 # STEP 1: Unparse 334 # Base-name for the unparsed files. These are just temporary files that are 335 # first generated and then deleted by this script. 336 # NOTE: We need to make sure that the base-name is unique to every 337 # invocation. Otherwise we can't use this script in parallel. 338 local -r unique_id=$(uuidgen | cut -b25-36) 339 local -r unparsed_file_base="flang_unparsed_file_$unique_id" 340 341 flang_options+=("-module-suffix") 342 flang_options+=(".f18.mod") 343 flang_options+=("-fdebug-unparse") 344 flang_options+=("-fno-analyzed-objects-for-unparse") 345 346 [[ ! -z ${MODULE_DIR} ]] && flang_options+=("-module-dir ${MODULE_DIR}") 347 [[ ! -z ${INTRINSICS_MOD_DIR} ]] && flang_options+=("-intrinsics-module-directory ${INTRINSICS_MOD_DIR}") 348 for idx in "${!fortran_source_files[@]}"; do 349 set +e 350 "$wd/bin/@FLANG_DEFAULT_DRIVER@" "${flang_options[@]}" "${fortran_source_files[$idx]}" -o "${unparsed_file_base}_${idx}.f90" 351 ret_status=$? 352 set -e 353 if [[ $ret_status != 0 ]]; then 354 echo flang: in "$PWD", @FLANG_DEFAULT_DRIVER@ failed with exit status "$ret_status": "$wd/bin/@FLANG_DEFAULT_DRIVER@" "${flang_options[@]}" "$@" >&2 355 exit "$ret_status" 356 fi 357 done 358 359 # STEP 2: Compile Fortran Source Files 360 readonly ext_fc="${F18_FC:-gfortran}" 361 for idx in "${!fortran_source_files[@]}"; do 362 # We always have to specify the output name with `-o <out_obj_file>`. This 363 # is because we are using the unparsed rather than the original source file 364 # below. As a result, we cannot rely on the compiler-generated output name. 365 out_obj_file=$(get_relocatable_name "${fortran_source_files[$idx]}") 366 367 set +e 368 $ext_fc "-c" "${ext_fc_options[@]}" "${unparsed_file_base}_${idx}.f90" "-o" "${out_obj_file}" 369 ret_status=$? 370 set -e 371 if [[ $ret_status != 0 ]]; then 372 echo flang: in "$PWD", "$ext_fc" failed with exit status "$ret_status": "$ext_fc" "${ext_fc_options[@]}" "$@" >&2 373 exit "$ret_status" 374 fi 375 object_files+=(${out_obj_file}) 376 done 377 378 # Delete the unparsed files 379 for idx in "${!fortran_source_files[@]}"; do 380 rm "${unparsed_file_base}_${idx}.f90" 381 done 382 383 # STEP 3: Compile Other Source Files 384 for idx in "${!other_source_files[@]}"; do 385 # We always specify the output name with `-o <out_obj_file>`. The user 386 # might have used `-o`, but we never add it to $OPTIONS (or 387 # $ext_fc_options). Hence we need to use `get_relocatable_name`. 388 out_obj_file=$(get_relocatable_name "${other_source_files[$idx]}") 389 390 set +e 391 $ext_fc "-c" "${ext_fc_options[@]}" "${other_source_files[${idx}]}" "-o" "${out_obj_file}" 392 ret_status=$? 393 set -e 394 if [[ $ret_status != 0 ]]; then 395 echo flang: in "$PWD", "$ext_fc" failed with exit status "$ret_status": "$ext_fc" "${ext_fc_options[@]}" "$@" >&2 396 exit "$ret_status" 397 fi 398 object_files+=(${out_obj_file}) 399 done 400 401 # STEP 4: Link 402 if [[ $COMPILE_ONLY == "True" ]]; then 403 exit 0; 404 fi 405 406 if [[ ${#object_files[@]} -ge 1 ]]; then 407 # If $OUTPUT_FILE was specified, use it for the output name. 408 if [[ ! -z ${OUTPUT_FILE:+x} ]]; then 409 output_definition="-o $OUTPUT_FILE" 410 else 411 output_definition="" 412 fi 413 414 set +e 415 $ext_fc "${ext_fc_options[@]}" "${object_files[@]}" "${lib_files[@]}" ${output_definition:+$output_definition} 416 ret_status=$? 417 set -e 418 if [[ $ret_status != 0 ]]; then 419 echo flang: in "$PWD", "$ext_fc" failed with exit status "$ret_status": "$ext_fc" "${ext_fc_options[@]}" "$@" >&2 420 exit "$ret_status" 421 fi 422 fi 423 424 # Delete intermediate object files 425 for idx in "${!fortran_source_files[@]}"; do 426 rm "${object_files[$idx]}" 427 done 428} 429 430main "${@}" 431