[flang] Merge flang-compiler/f18

This is the initial merge of flang-compiler, which is done in this way
principally to preserve the history and git-blame, without generating a large
number of commits on the first-parent history of LLVM.

If you don't care about the flang history during a bisect remember that you can
supply paths to git-bisect, e.g. `git bisect start clang llvm`.

The history of f18 was rewritten to:

* Put the code under /flang/.
* Linearize the history.
* Rewrite commit messages so that issue and PR numbers point to the old repository.

Credit to Peter Waller for writing the flatten and merge script.

Updates: flang-compiler/f18#876 (submission into llvm-project)
Mailing-list: http://lists.llvm.org/pipermail/llvm-dev/2020-January/137989.html ([llvm-dev] Flang landing in the monorepo - next Monday!)
Mailing-list: http://lists.llvm.org/pipermail/llvm-dev/2019-December/137661.html ([llvm-dev] Flang landing in the monorepo)

Co-authored-by: Peter Waller <peter.waller@arm.com>
This commit is contained in:
David Truby 2020-04-09 16:11:58 +01:00
commit b98ad941a4
822 changed files with 134445 additions and 0 deletions

21
flang/.clang-format Normal file
View File

@ -0,0 +1,21 @@
---
# See: https://clang.llvm.org/docs/ClangFormatStyleOptions.html
BasedOnStyle: LLVM
AlignAfterOpenBracket: DontAlign
AlignEscapedNewlines: DontAlign
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignOperands: false
AlignTrailingComments: false
IncludeCategories:
- Regex: '^<'
Priority: 4
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
Priority: 3
- Regex: '^"(flang|\.\.)/'
Priority: 2
- Regex: '.*'
Priority: 1
...
# vim:set filetype=yaml:

60
flang/.drone.star Normal file
View File

@ -0,0 +1,60 @@
def clang(arch):
return {
"kind": "pipeline",
"name": "%s-clang" % arch,
"steps": [
{
"name": "test",
"image": "ubuntu",
"commands": [
"apt-get update && apt-get install -y clang-8 cmake ninja-build lld-8 llvm-8-dev libc++-8-dev libc++abi-8-dev libz-dev git",
"git clone --depth=1 -b f18 https://github.com/flang-compiler/f18-llvm-project.git llvm-project",
"mkdir llvm-project/build && cd llvm-project/build",
'env CC=clang-8 CXX=clang++-8 CXXFLAGS="-stdlib=libc++" LDFLAGS="-fuse-ld=lld" cmake -GNinja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../install -DLLVM_TARGETS_TO_BUILD=host -DLLVM_INSTALL_UTILS=On -DLLVM_ENABLE_PROJECTS="mlir" ../llvm',
"ninja install",
"cd ../..",
"mkdir build && cd build",
'env CC=clang-8 CXX=clang++-8 CXXFLAGS="-UNDEBUG -stdlib=libc++" LDFLAGS="-fuse-ld=lld" cmake -GNinja -DCMAKE_BUILD_TYPE=Release .. -DLLVM_DIR=/drone/src/llvm-project/install/lib/cmake/llvm -DMLIR_DIR=/drone/src/llvm-project/install/lib/cmake/mlir -DLLVM_EXTERNAL_LIT=/drone/src/llvm-project/build/bin/llvm-lit',
"ninja -j8",
"ctest --output-on-failure -j24",
"ninja check-all",
],
},
],
}
def gcc(arch):
return {
"kind": "pipeline",
"name": "%s-gcc" % arch,
"steps": [
{
"name": "test",
"image": "gcc",
"commands": [
"apt-get update && apt-get install -y cmake ninja-build llvm-dev libz-dev git",
"git clone --depth=1 -b f18 https://github.com/flang-compiler/f18-llvm-project.git llvm-project",
"mkdir llvm-project/build && cd llvm-project/build",
'env CC=gcc CXX=g++ LDFLAGS="-fuse-ld=gold" cmake -GNinja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../install -DLLVM_TARGETS_TO_BUILD=host -DLLVM_INSTALL_UTILS=On -DLLVM_ENABLE_PROJECTS="mlir" ../llvm',
"ninja install",
"cd ../..",
"mkdir build && cd build",
'env CC=gcc CXX=g++ CXXFLAGS="-UNDEBUG" LDFLAGS="-fuse-ld=gold" cmake -GNinja -DCMAKE_BUILD_TYPE=Release .. -DLLVM_DIR=/drone/src/llvm-project/install/lib/cmake/llvm -DMLIR_DIR=/drone/src/llvm-project/install/lib/cmake/mlir -DLLVM_EXTERNAL_LIT=/drone/src/llvm-project/build/bin/llvm-lit',
"ninja -j8",
"ctest --output-on-failure -j24",
"ninja check-all",
],
},
],
}
def main(ctx):
return [
clang("amd64"),
clang("arm64"),
gcc("amd64"),
gcc("arm64"),
]

21
flang/.gitignore vendored Normal file
View File

@ -0,0 +1,21 @@
Debug
Release
MinSizeRel
build
root
tags
TAGS
*.o
.nfs*
*.sw?
*~
*#
CMakeCache.txt
*/CMakeFiles/*
*/*/CMakeFiles/*
*/Makefile
*/*/Makefile
cmake_install.cmake
formatted
.DS_Store
.vs_code

375
flang/CMakeLists.txt Normal file
View File

@ -0,0 +1,375 @@
cmake_minimum_required(VERSION 3.9.0)
# RPATH settings on macOS do not affect INSTALL_NAME.
if (POLICY CMP0068)
cmake_policy(SET CMP0068 NEW)
set(CMAKE_BUILD_WITH_INSTALL_NAME_DIR ON)
endif()
# Include file check macros honor CMAKE_REQUIRED_LIBRARIES.
if(POLICY CMP0075)
cmake_policy(SET CMP0075 NEW)
endif()
# option() honors normal variables.
if (POLICY CMP0077)
cmake_policy(SET CMP0077 NEW)
endif()
option(LINK_WITH_FIR "Link driver with FIR and LLVM" ON)
# Flang requires C++17.
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
set(CMAKE_CXX_EXTENSIONS OFF)
set(FLANG_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
if (CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR AND NOT MSVC_IDE)
message(FATAL_ERROR "In-source builds are not allowed. \
Please create a directory and run cmake from there,\
passing the path to this source directory as the last argument.\
This process created the file `CMakeCache.txt' and the directory\
`CMakeFiles'. Please delete them.")
endif()
# Add Flang-centric modules to cmake path.
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules")
include(AddFlang)
# Check for a standalone build and configure as appropriate from
# there.
if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
message("Building Flang as a standalone project.")
project(Flang)
set(FLANG_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR})
if (NOT MSVC_IDE)
set(LLVM_ENABLE_ASSERTIONS ${ENABLE_ASSERTIONS}
CACHE BOOL "Enable assertions")
# Assertions follow llvm's configuration.
mark_as_advanced(LLVM_ENABLE_ASSERTIONS)
endif()
# We need a pre-built/installed version of LLVM.
find_package(LLVM REQUIRED HINTS "${LLVM_CMAKE_PATH}")
list(APPEND CMAKE_MODULE_PATH ${LLVM_DIR})
# If LLVM links to zlib we need the imported targets so we can too.
if(LLVM_ENABLE_ZLIB)
find_package(ZLIB REQUIRED)
endif()
include(CMakeParseArguments)
include(AddLLVM)
include(HandleLLVMOptions)
include(VersionFromVCS)
if(LINK_WITH_FIR)
include(TableGen)
find_package(MLIR REQUIRED CONFIG)
# Use SYSTEM for the same reasons as for LLVM includes
include_directories(SYSTEM ${MLIR_INCLUDE_DIRS})
list(APPEND CMAKE_MODULE_PATH ${MLIR_DIR})
include(AddMLIR)
find_program(MLIR_TABLEGEN_EXE "mlir-tblgen" ${LLVM_TOOLS_BINARY_DIR}
NO_DEFAULT_PATH)
endif()
option(LLVM_ENABLE_WARNINGS "Enable compiler warnings." ON)
option(LLVM_INSTALL_TOOLCHAIN_ONLY
"Only include toolchain files in the 'install' target." OFF)
option(LLVM_FORCE_USE_OLD_HOST_TOOLCHAIN
"Set to ON to force using an old, unsupported host toolchain." OFF)
# Add LLVM include files as if they were SYSTEM because there are complex unused
# parameter issues that may or may not appear depending on the environments and
# compilers (ifdefs are involved). This allows warnings from LLVM headers to be
# ignored while keeping -Wunused-parameter a fatal error inside f18 code base.
# This may have to be fine-tuned if flang headers are consider part of this
# LLVM_INCLUDE_DIRS when merging in the monorepo (Warning from flang headers
# should not be suppressed).
include_directories(SYSTEM ${LLVM_INCLUDE_DIRS})
add_definitions(${LLVM_DEFINITIONS})
# LLVM's cmake configuration files currently sneak in a c++11 flag.
# We look for it here and remove it from Flang's compile flags to
# avoid some mixed compilation flangs (e.g. -std=c++11 ... -std=c++17).
if (DEFINED LLVM_CXX_STD)
message("LLVM configuration set a C++ standard: ${LLVM_CXX_STD}")
if (NOT LLVM_CXX_STD EQUAL "c++17")
message("Flang: Overriding LLVM's 'cxx_std' setting...")
message(" removing '-std=${LLVM_CXX_STD}'")
message(" CMAKE_CXX_FLAGS='${CMAKE_CXX_FLAGS}'")
string(REPLACE " -std=${LLVM_CXX_STD}" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
message(" [NEW] CMAKE_CXX_FLAGS='${CMAKE_CXX_FLAGS}'")
endif()
endif()
link_directories("${LLVM_LIBRARY_DIR}")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY
${CMAKE_BINARY_DIR}/lib${LLVM_LIBDIR_SUFFIX})
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY
${CMAKE_BINARY_DIR}/lib${LLVM_LIBDIR_SUFFIX})
set(BACKEND_PACKAGE_STRING "LLVM ${LLVM_PACKAGE_VERSION}")
set(LLVM_EXTERNAL_LIT "${LLVM_TOOLS_BINARY_DIR}/llvm-lit" CACHE STRING "Command used to spawn lit")
option(FLANG_INCLUDE_TESTS
"Generate build targets for the Flang unit tests."
ON)
add_custom_target(check-all DEPENDS check-flang)
else()
option(FLANG_INCLUDE_TESTS
"Generate build targets for the Flang unit tests."
${LLVM_INCLUDE_TESTS})
set(FLANG_BINARY_DIR ${CMAKE_BINARY_DIR}/tools/flang)
set(BACKEND_PACKAGE_STRING "${PACKAGE_STRING}")
if (LINK_WITH_FIR)
set(MLIR_MAIN_SRC_DIR ${LLVM_MAIN_SRC_DIR}/../mlir/include ) # --src-root
set(MLIR_INCLUDE_DIR ${LLVM_MAIN_SRC_DIR}/../mlir/include ) # --includedir
set(MLIR_TABLEGEN_OUTPUT_DIR ${CMAKE_BINARY_DIR}/tools/mlir/include)
set(MLIR_TABLEGEN_EXE $<TARGET_FILE:mlir-tblgen>)
include_directories(SYSTEM ${MLIR_INCLUDE_DIR})
include_directories(SYSTEM ${MLIR_TABLEGEN_OUTPUT_DIR})
endif()
endif()
if(LINK_WITH_FIR)
# tco tool and FIR lib output directories
set(LLVM_RUNTIME_OUTPUT_INTDIR ${CMAKE_BINARY_DIR}/bin)
set(LLVM_LIBRARY_OUTPUT_INTDIR ${CMAKE_BINARY_DIR}/lib)
# Always build tco tool
set(LLVM_BUILD_TOOLS ON)
message(STATUS "Linking driver with FIR and LLVM")
llvm_map_components_to_libnames(LLVM_COMMON_LIBS support)
message(STATUS "LLVM libraries: ${LLVM_COMMON_LIBS}")
endif()
# Add Flang-centric modules to cmake path.
include_directories(BEFORE
${FLANG_BINARY_DIR}/include
${FLANG_SOURCE_DIR}/include)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules")
if (NOT DEFAULT_SYSROOT)
set(DEFAULT_SYSROOT "" CACHE PATH
"The <path> to use for the system root for all compiler invocations (--sysroot=<path>).")
endif()
if (NOT ENABLE_LINKER_BUILD_ID)
set(ENABLE_LINKER_BUILD_ID OFF CACHE BOOL "pass --build-id to ld")
endif()
set(FLANG_DEFAULT_LINKER "" CACHE STRING
"Default linker to use (linker name or absolute path, empty for platform default)")
set(FLANG_DEFAULT_RTLIB "" CACHE STRING
"Default Fortran runtime library to use (\"libFortranRuntime\"), leave empty for platform default.")
if (NOT(FLANG_DEFAULT_RTLIB STREQUAL ""))
message(WARNING "Resetting Flang's default runtime library to use platform default.")
set(FLANG_DEFAULT_RTLIB "" CACHE STRING
"Default runtime library to use (empty for platform default)" FORCE)
endif()
set(PACKAGE_VERSION "${LLVM_PACKAGE_VERSION}")
# Override LLVM versioning for now...
set(FLANG_VERSION_MAJOR "0")
set(FLANG_VERSION_MINOR "1")
set(FLANG_VERSION_PATCHLEVEL "0")
if (NOT DEFINED FLANG_VERSION_MAJOR)
set(FLANG_VERSION_MAJOR ${LLVM_VERSION_MAJOR})
endif()
if (NOT DEFINED FLANG_VERSION_MINOR)
set(FLANG_VERSION_MINOR ${LLVM_VERSION_MINOR})
endif()
if (NOT DEFINED FLANG_VERSION_PATCHLEVEL)
set(FLANG_VERSION_PATCHLEVEL ${LLVM_VERSION_PATCH})
endif()
# Unlike PACKAGE_VERSION, FLANG_VERSION does not include LLVM_VERSION_SUFFIX.
set(FLANG_VERSION "${FLANG_VERSION_MAJOR}.${FLANG_VERSION_MINOR}.${FLANG_VERSION_PATCHLEVEL}")
message(STATUS "Flang version: ${FLANG_VERSION}")
# Flang executable version information
set(FLANG_EXECUTABLE_VERSION
"${FLANG_VERSION_MAJOR}" CACHE STRING
"Major version number to appended to the flang executable name.")
set(LIBFLANG_LIBRARY_VERSION
"${FLANG_VERSION_MAJOR}" CACHE STRING
"Major version number to appended to the libflang library.")
mark_as_advanced(FLANG_EXECUTABLE_VERSION LIBFLANG_LIBRARY_VERSION)
set(FLANG_VENDOR ${PACKAGE_VENDOR} CACHE STRING
"Vendor-specific Flang version information.")
set(FLANG_VENDOR_UTI "org.llvm.flang" CACHE STRING
"Vendor-specific uti.")
if (FLANG_VENDOR)
add_definitions(-DFLANG_VENDOR="${FLANG_VENDOR} ")
endif()
set(FLANG_REPOSITORY_STRING "" CACHE STRING
"Vendor-specific text for showing the repository the source is taken from.")
if (FLANG_REPOSITORY_STRING)
add_definitions(-DFLANG_REPOSITORY_STRING="${FLANG_REPOSITORY_STRING}")
endif()
# Configure Flang's Version.inc file.
configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/include/flang/Version.inc.in
${CMAKE_CURRENT_BINARY_DIR}/include/flang/Version.inc)
# Configure Flang's version info header file.
configure_file(
${FLANG_SOURCE_DIR}/include/flang/Config/config.h.cmake
${FLANG_BINARY_DIR}/include/flang/Config/config.h)
# Add global F18 flags.
set(CMAKE_CXX_FLAGS "-fno-rtti -fno-exceptions -pedantic -Wall -Wextra -Werror -Wcast-qual -Wimplicit-fallthrough -Wdelete-non-virtual-dtor ${CMAKE_CXX_FLAGS}")
# Builtin check_cxx_compiler_flag doesn't seem to work correctly
macro(check_compiler_flag flag resultVar)
unset(${resultVar} CACHE)
check_cxx_compiler_flag("${flag}" ${resultVar})
endmacro()
check_compiler_flag("-Werror -Wno-deprecated-copy" CXX_SUPPORTS_NO_DEPRECATED_COPY_FLAG)
if (CXX_SUPPORTS_NO_DEPRECATED_COPY_FLAG)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated-copy")
endif()
check_compiler_flag("-Wstring-conversion" CXX_SUPPORTS_NO_STRING_CONVERSION_FLAG)
if (CXX_SUPPORTS_NO_STRING_CONVERSION_FLAG)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-string-conversion")
endif()
# Add appropriate flags for GCC
if (LLVM_COMPILER_IS_GCC_COMPATIBLE)
if (NOT "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-strict-aliasing -fno-semantic-interposition")
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-command-line-argument -Wstring-conversion \
-Wcovered-switch-default")
endif() # Clang.
check_cxx_compiler_flag("-Werror -Wnested-anon-types" CXX_SUPPORTS_NO_NESTED_ANON_TYPES_FLAG)
if (CXX_SUPPORTS_NO_NESTED_ANON_TYPES_FLAG)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-nested-anon-types")
endif()
# Add to or adjust build type flags.
#
# TODO: This needs some extra thought. CMake's default for release builds
# is -O3, which can cause build failures on certain platforms (and compilers)
# with the current code base -- some templated functions are inlined and don't
# become available at link time when using -O3 (with Clang under MacOS/darwin).
# If we reset CMake's default flags we also clobber any user provided settings;
# make it difficult to customize a build in this regard... The setup below
# has this side effect but enables successful builds across multiple platforms
# in release mode...
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUGF18")
set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL} -DCHECK=\"(void)\"") # do we need -O2 here?
set(CMAKE_CXX_FLAGS_RELEASE "-O2")
# Building shared libraries is bad for performance with GCC by default
# due to the need to preserve the right to override external entry points
if (BUILD_SHARED_LIBS AND NOT (CMAKE_CXX_COMPILER_ID MATCHES "Clang"))
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fno-semantic-interposition")
endif()
endif()
list(REMOVE_DUPLICATES CMAKE_CXX_FLAGS)
# Determine HOST_LINK_VERSION on Darwin.
set(HOST_LINK_VERSION)
if (APPLE)
set(LD_V_OUTPUT)
execute_process(
COMMAND sh -c "${CMAKE_LINKER} -v 2>&1 | head -1"
RESULT_VARIABLE HAD_ERROR
OUTPUT_VARIABLE LD_V_OUTPUT)
if (NOT HAD_ERROR)
if ("${LD_V_OUTPUT}" MATCHES ".*ld64-([0-9.]+).*")
string(REGEX REPLACE ".*ld64-([0-9.]+).*" "\\1" HOST_LINK_VERSION ${LD_V_OUTPUT})
elseif ("${LD_V_OUTPUT}" MATCHES "[^0-9]*([0-9.]+).*")
string(REGEX REPLACE "[^0-9]*([0-9.]+).*" "\\1" HOST_LINK_VERSION ${LD_V_OUTPUT})
endif()
else()
message(FATAL_ERROR "${CMAKE_LINKER} failed with status ${HAD_ERROR}")
endif()
endif()
include(CMakeParseArguments)
include(AddFlang)
add_subdirectory(include)
add_subdirectory(lib)
add_subdirectory(cmake/modules)
option(FLANG_BUILD_TOOLS
"Build the Flang tools. If OFF, just generate build targets." ON)
if (FLANG_BUILD_TOOLS)
add_subdirectory(tools)
endif()
add_subdirectory(runtime)
if (FLANG_INCLUDE_TESTS)
enable_testing()
add_subdirectory(test)
add_subdirectory(unittests)
endif()
# TODO: Add doxygen support.
#option(FLANG_INCLUDE_DOCS "Generate build targets for the Flang docs."
# ${LLVM_INCLUDE_DOCS})
#if (FLANG_INCLUDE_DOCS)
# add_subdirectory(documentation)
#endif()
# Custom target to install Flang libraries.
add_custom_target(flang-libraries)
set_target_properties(flang-libraries PROPERTIES FOLDER "Misc")
if (NOT LLVM_ENABLE_IDE)
add_llvm_install_targets(install-flang-libraries
DEPENDS flang-libraries
COMPONENT flang-libraries)
endif()
get_property(FLANG_LIBS GLOBAL PROPERTY FLANG_LIBS)
if (FLANG_LIBS)
list(REMOVE_DUPLICATES FLANG_LIBS)
foreach(lib ${FLANG_LIBS})
add_dependencies(flang-libraries ${lib})
if (NOT LLVM_ENABLE_IDE)
add_dependencies(install-flang-libraries install-${lib})
endif()
endforeach()
endif()
if (NOT LLVM_INSTALL_TOOLCHAIN_ONLY)
install(DIRECTORY include/flang
DESTINATION include
COMPONENT flang-headers
FILES_MATCHING
PATTERN "*.def"
PATTERN "*.h"
PATTERN "*.inc"
PATTERN "*.td"
PATTERN "config.h" EXCLUDE
PATTERN ".git" EXCLUDE
PATTERN "CMakeFiles" EXCLUDE)
endif()

18
flang/CODE_OWNERS.TXT Normal file
View File

@ -0,0 +1,18 @@
This file is a list of the people responsible for ensuring that patches for a
particular part of Flang are reviewed, either by themself or by someone else.
They are also the gatekeepers for their part of Flang, with the final word on
what goes in or not.
The list is sorted by surname and formatted to allow easy grepping and
beautification by scripts. The fields are: name (N), email (E), web-address
(W), PGP key ID and fingerprint (P), description (D), snail-mail address
(S) and (I) IRC handle. Each entry should contain at least the (N), (E) and
(D) fields.
N: Steve Scalpone
E: sscalpone@nvidia.com
D: Anything not covered by others
N: Eric Schweitz
E: eschweitz@nvidia.com
D: FIR (lib/Optimizer), Fortran lowering (lib/Lower)

234
flang/LICENSE.txt Normal file
View File

@ -0,0 +1,234 @@
==============================================================================
The LLVM Project is under the Apache License v2.0 with LLVM Exceptions:
==============================================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
---- LLVM Exceptions to the Apache 2.0 License ----
As an exception, if, as a result of your compiling your source code, portions
of this Software are embedded into an Object form of such source code, you
may redistribute such embedded portions in such Object form without complying
with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
In addition, if you combine or link compiled forms of this Software with
software that is licensed under the GPLv2 ("Combined Software") and if a
court of competent jurisdiction determines that the patent provision (Section
3), the indemnity provision (Section 9) or other Section of the License
conflicts with the conditions of the GPLv2, you may retroactively and
prospectively choose to deem waived or otherwise exclude such Section(s) of
the License, but only in their entirety and only with respect to the Combined
Software.
==============================================================================
Software from third parties included in the LLVM Project:
==============================================================================
The LLVM Project contains third party software which is under different license
terms. All such code will be identified clearly using at least one of two
mechanisms:
1) It will be in a separate directory tree with its own `LICENSE.txt` or
`LICENSE` file at the top containing the specific license and restrictions
which apply to that software, or
2) It will contain specific license and restriction terms at the top of every
file.

227
flang/README.md Normal file
View File

@ -0,0 +1,227 @@
<!--===- README.md
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
-->
# F18
F18 is a ground-up implementation of a Fortran front end written in modern C++.
F18, when combined with LLVM, is intended to replace the Flang compiler.
Flang is a Fortran compiler targeting LLVM.
Visit the [Flang wiki](https://github.com/flang-compiler/flang/wiki)
for more information about Flang.
## Getting Started
Read more about f18 in the [documentation directory](documentation).
Start with the [compiler overview](documentation/Overview.md).
To better understand Fortran as a language
and the specific grammar accepted by f18,
read [Fortran For C Programmers](documentation/FortranForCProgrammers.md)
and
f18's specifications of the [Fortran grammar](documentation/f2018-grammar.txt)
and
the [OpenMP grammar](documentation/OpenMP-4.5-grammar.txt).
Treatment of language extensions is covered
in [this document](documentation/Extensions.md).
To understand the compilers handling of intrinsics,
see the [discussion of intrinsics](documentation/Intrinsics.md).
To understand how an f18 program communicates with libraries at runtime,
see the discussion of [runtime descriptors](documentation/RuntimeDescriptor.md).
If you're interested in contributing to the compiler,
read the [style guide](documentation/C++style.md)
and
also review [how f18 uses modern C++ features](documentation/C++17.md).
## Building F18
### Get the Source Code
```
cd where/you/want/the/source
git clone https://github.com/flang-compiler/f18.git
```
### Supported C++ compilers
F18 is written in C++17.
The code has been compiled and tested with
GCC versions 7.2.0, 7.3.0, 8.1.0, and 8.2.0.
The code has been compiled and tested with
clang version 7.0 and 8.0
using either GNU's libstdc++ or LLVM's libc++.
### LLVM dependency
F18 uses components from LLVM.
The instructions to build LLVM can be found at
https://llvm.org/docs/GettingStarted.html.
We highly recommend using the same compiler to compile both llvm and f18.
The f18 CMakeList.txt file uses
the variable `LLVM_DIR` to find the installed LLVM components
and
the variable `MLIR_DIR` to find the installed MLIR components.
To get the correct LLVM and MLIR libraries included in your f18 build,
define LLVM_DIR and MLIR_DIR on the cmake command line.
```
LLVM=<LLVM_BUILD_DIR>/lib/cmake/llvm \
MLIR=<LLVM_BUILD_DIR>/lib/cmake/mlir \
cmake -DLLVM_DIR=$LLVM -DMLIR_DIR=$MLIR ...
```
where `LLVM_BUILD_DIR` is
the top-level directory where LLVM was built.
### LLVM dependency when building f18 with Fortran IR
If you do not want to build Fortran IR, add `-DLINK_WITH_FIR=Off` to f18 cmake
command and ignore the rest of this section.
If you intend to build f18 with Fortran IR (`-DLINK_WITH_FIR` On by default),
you must:
- build LLVM with the same compiler and options as the one you are using
to build F18.
- pass `-DCMAKE_CXX_STANDARD=17 -DLLVM_ENABLE_PROJECTS="mlir"`
to LLVM cmake command.
- install LLVM somewhere with `make install` in order to get the required
AddMLIR cmake file (it is not generated in LLVM build directory).
Installing LLVM from packages is most likely not an option as it will not include
MLIR and not be built following C++17 standard.
MLIR is under active development and the most recent development version
may be incompatible. A branch named `f18` is available inside LLVM fork in
https://github.com/flang-compiler/f18-llvm-project. It contains a version of LLVM
that is known be compatible to build f18 with FIR.
The fastest way to get set up is to do:
```
cd where/you/want/to/build/llvm
git clone --depth=1 -b f18 https://github.com/flang-compiler/f18-llvm-project.git
mkdir build
mkdir install
cd build
cmake ../f18-llvm-project/llvm -DCMAKE_BUILD_TYPE=Release \
-DLLVM_ENABLE_PROJECTS=mlir -DCMAKE_CXX_STANDARD=17 \
-DLLVM_INSTALL_UTILS=On \
-DCMAKE_INSTALL_PREFIX=../install
make
make install
```
Then, `-DLLVM_DIR` would have to be set to
`<where/you/want/to/build/llvm>/install/lib/cmake/llvm`
and, `-DMLIR_DIR` would have to be set to
`<where/you/want/to/build/llvm>/install/lib/cmake/mlir`
in f18 cmake command.
To run lit tests,
`-DLLVM_EXTERNAL_LIT=<where/you/want/to/build/llvm>/build/bin/llvm-lit` must be
added to f18 cmake command. This is because `llvm-lit` is not part of
LLVM installation.
Note that when using some advanced options from f18 cmake file it may be
necessary to reproduce their effects in LLVM cmake command.
### Building f18 with GCC
By default,
cmake will search for g++ on your PATH.
The g++ version must be one of the supported versions
in order to build f18.
Or,
cmake will use the variable CXX to find the C++ compiler.
CXX should include the full path to the compiler
or a name that will be found on your PATH,
e.g. g++-8.3, assuming g++-8.3 is on your PATH.
```
export CXX=g++-8.3
```
or
```
CXX=/opt/gcc-8.3/bin/g++-8.3 cmake ...
```
### Building f18 with clang
To build f18 with clang,
cmake needs to know how to find clang++
and the GCC library and tools that were used to build clang++.
CXX should include the full path to clang++
or clang++ should be found on your PATH.
```
export CXX=clang++
```
### Installation Directory
To specify a custom install location,
add
`-DCMAKE_INSTALL_PREFIX=<INSTALL_PREFIX>`
to the cmake command
where `<INSTALL_PREFIX>`
is the path where f18 should be installed.
### Build Types
To create a debug build,
add
`-DCMAKE_BUILD_TYPE=Debug`
to the cmake command.
Debug builds execute slowly.
To create a release build,
add
`-DCMAKE_BUILD_TYPE=Release`
to the cmake command.
Release builds execute quickly.
### Build F18
```
cd ~/f18/build
cmake -DLLVM_DIR=$LLVM -DMLIR_DIR=$MLIR ~/f18/src
make
```
### How to Run the Regression Tests
To run all tests:
```
cd ~/f18/build
cmake -DLLVM_DIR=$LLVM -DMLIR_DIR=$MLIR ~/f18/src
make test check-all
```
To run individual regression tests llvm-lit needs to know the lit
configuration for f18. The parameters in charge of this are:
flang_site_config and flang_config. And they can be set as shown bellow:
```
<path-to-llvm-lit>/llvm-lit \
--param flang_site_config=<path-to-f18-build>/test-lit/lit.site.cfg.py \
--param flang_config=<path-to-f18-build>/test-lit/lit.cfg.py \
<path-to-fortran-test>
```
# How to Generate FIR Documentation
If f18 was built with `-DLINK_WITH_FIR=On` (`On` by default), it is possible to
generate FIR language documentation by running `make flang-doc`. This will
create `docs/Dialect/FIRLangRef.md` in f18 build directory.

View File

@ -0,0 +1,141 @@
macro(set_flang_windows_version_resource_properties name)
if (DEFINED windows_resource_file)
set_windows_version_resource_properties(${name} ${windows_resource_file}
VERSION_MAJOR ${FLANG_VERSION_MAJOR}
VERSION_MINOR ${FLANG_VERSION_MINOR}
VERSION_PATCHLEVEL ${FLANG_VERSION_PATCHLEVEL}
VERSION_STRING "${FLANG_VERSION} (${BACKEND_PACKAGE_STRING})"
PRODUCT_NAME "flang")
endif()
endmacro()
macro(add_flang_subdirectory name)
add_llvm_subdirectory(FLANG TOOL ${name})
endmacro()
macro(add_flang_library name)
cmake_parse_arguments(ARG
"SHARED"
""
"ADDITIONAL_HEADERS"
${ARGN})
set(srcs)
if (MSVC_IDE OR XCODE)
# Add public headers
file(RELATIVE_PATH lib_path
${FLANG_SOURCE_DIR}/lib/
${CMAKE_CURRENT_SOURCE_DIR})
if(NOT lib_path MATCHES "^[.][.]")
file( GLOB_RECURSE headers
${FLANG_SOURCE_DIR}/include/flang/${lib_path}/*.h
${FLANG_SOURCE_DIR}/include/flang/${lib_path}/*.def)
set_source_files_properties(${headers} PROPERTIES HEADER_FILE_ONLY ON)
if (headers)
set(srcs ${headers})
endif()
endif()
endif(MSVC_IDE OR XCODE)
if (srcs OR ARG_ADDITIONAL_HEADERS)
set(srcs
ADDITIONAL_HEADERS
${srcs}
${ARG_ADDITIONAL_HEADERS}) # It may contain unparsed unknown args.
endif()
if (ARG_SHARED)
set(LIBTYPE SHARED)
else()
# llvm_add_library ignores BUILD_SHARED_LIBS if STATIC is explicitly set,
# so we need to handle it here.
if (BUILD_SHARED_LIBS)
set(LIBTYPE SHARED OBJECT)
else()
set(LIBTYPE STATIC OBJECT)
endif()
set_property(GLOBAL APPEND PROPERTY FLANG_STATIC_LIBS ${name})
endif()
llvm_add_library(${name} ${LIBTYPE} ${ARG_UNPARSED_ARGUMENTS} ${srcs})
if (TARGET ${name})
target_link_libraries(${name} INTERFACE ${LLVM_COMMON_LIBS})
if (NOT LLVM_INSTALL_TOOLCHAIN_ONLY OR ${name} STREQUAL "libflang")
set(export_to_flangtargets)
if (${name} IN_LIST LLVM_DISTRIBUTION_COMPONENTS OR
"flang-libraries" IN_LIST LLVM_DISTRIBUTION_COMPONENTS OR
NOT LLVM_DISTRIBUTION_COMPONENTS)
set(export_to_flangtargets EXPORT FlangTargets)
set_property(GLOBAL PROPERTY FLANG_HAS_EXPORTS True)
endif()
install(TARGETS ${name}
COMPONENT ${name}
${export_to_flangtargets}
LIBRARY DESTINATION lib${LLVM_LIBDIR_SUFFIX}
ARCHIVE DESTINATION lib${LLVM_LIBDIR_SUFFIX}
RUNTIME DESTINATION bin)
if (NOT LLVM_ENABLE_IDE)
add_llvm_install_targets(install-${name}
DEPENDS ${name}
COMPONENT ${name})
endif()
set_property(GLOBAL APPEND PROPERTY FLANG_LIBS ${name})
endif()
set_property(GLOBAL APPEND PROPERTY FLANG_EXPORTS ${name})
else()
# Add empty "phony" target
add_custom_target(${name})
endif()
set_target_properties(${name} PROPERTIES FOLDER "Flang libraries")
set_flang_windows_version_resource_properties(${name})
endmacro(add_flang_library)
macro(add_flang_executable name)
add_llvm_executable(${name} ${ARGN})
set_target_properties(${name} PROPERTIES FOLDER "Flang executables")
set_flang_windows_version_resource_properties(${name})
endmacro(add_flang_executable)
macro(add_flang_tool name)
if (NOT FLANG_BUILD_TOOLS)
set(EXCLUDE_FROM_ALL ON)
endif()
add_flang_executable(${name} ${ARGN})
add_dependencies(${name} flang-resource-headers)
if (FLANG_BUILD_TOOLS)
set(export_to_flangtargets)
if (${name} IN_LIST LLVM_DISTRIBUTION_COMPONENTS OR
NOT LLVM_DISTRIBUTION_COMPONENTS)
set(export_to_flangtargets EXPORT FlangTargets)
set_property(GLOBAL PROPERTY FLANG_HAS_EXPORTS True)
endif()
install(TARGETS ${name}
${export_to_flangtargets}
RUNTIME DESTINATION bin
COMPONENT ${name})
if(NOT LLVM_ENABLE_IDE)
add_llvm_install_targets(install-${name}
DEPENDS ${name}
COMPONENT ${name})
endif()
set_property(GLOBAL APPEND PROPERTY FLANG_EXPORTS ${name})
endif()
endmacro()
macro(add_flang_symlink name dest)
add_llvm_tool_symlink(${name} ${dest} ALWAYS_GENERATE)
# Always generate install targets
llvm_install_symlink(${name} ${dest} ALWAYS_GENERATE)
endmacro()

View File

@ -0,0 +1,74 @@
# Generate a list of CMake library targets so that other CMake projects can
# link against them. LLVM calls its version of this file LLVMExports.cmake, but
# the usual CMake convention seems to be ${Project}Targets.cmake.
set(FLANG_INSTALL_PACKAGE_DIR lib${LLVM_LIBDIR_SUFFIX}/cmake/flang)
set(flang_cmake_builddir "${CMAKE_BINARY_DIR}/${FLANG_INSTALL_PACKAGE_DIR}")
# Keep this in sync with llvm/cmake/CMakeLists.txt!
set(LLVM_INSTALL_PACKAGE_DIR lib${LLVM_LIBDIR_SUFFIX}/cmake/llvm)
set(llvm_cmake_builddir "${LLVM_BINARY_DIR}/${LLVM_INSTALL_PACKAGE_DIR}")
get_property(FLANG_EXPORTS GLOBAL PROPERTY FLANG_EXPORTS)
export(TARGETS ${FLANG_EXPORTS} FILE ${flang_cmake_builddir}/FlangTargets.cmake)
# Generate FlangConfig.cmake for the build tree.
set(FLANG_CONFIG_CMAKE_DIR "${flang_cmake_builddir}")
set(FLANG_CONFIG_LLVM_CMAKE_DIR "${llvm_cmake_builddir}")
set(FLANG_CONFIG_EXPORTS_FILE "${flang_cmake_builddir}/FlangTargets.cmake")
set(FLANG_CONFIG_INCLUDE_DIRS
"${FLANG_SOURCE_DIR}/include"
"${FLANG_BINARY_DIR}/include"
)
configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/FlangConfig.cmake.in
${flang_cmake_builddir}/FlangConfig.cmake
@ONLY)
set(FLANG_CONFIG_CMAKE_DIR)
set(FLANG_CONFIG_LLVM_CMAKE_DIR)
set(FLANG_CONFIG_EXPORTS_FILE)
# Generate FlangConfig.cmake for the install tree.
set(FLANG_CONFIG_CODE "
# Compute the installation prefix from this LLVMConfig.cmake file location.
get_filename_component(FLANG_INSTALL_PREFIX \"\${CMAKE_CURRENT_LIST_FILE}\" PATH)")
# Construct the proper number of get_filename_component(... PATH)
# calls to compute the installation prefix.
string(REGEX REPLACE "/" ";" _count "${FLANG_INSTALL_PACKAGE_DIR}")
foreach(p ${_count})
set(FLANG_CONFIG_CODE "${FLANG_CONFIG_CODE}
get_filename_component(FLANG_INSTALL_PREFIX \"\${FLANG_INSTALL_PREFIX}\" PATH)")
endforeach(p)
set(FLANG_CONFIG_CMAKE_DIR "\${FLANG_INSTALL_PREFIX}/${FLANG_INSTALL_PACKAGE_DIR}")
set(FLANG_CONFIG_LLVM_CMAKE_DIR "\${FLANG_INSTALL_PREFIX}/${LLVM_INSTALL_PACKAGE_DIR}")
set(FLANG_CONFIG_EXPORTS_FILE "\${FLANG_CMAKE_DIR}/FlangTargets.cmake")
set(FLANG_CONFIG_INCLUDE_DIRS "\${FLANG_INSTALL_PREFIX}/include")
configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/FlangConfig.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/FlangConfig.cmake
@ONLY)
set(FLANG_CONFIG_CODE)
set(FLANG_CONFIG_CMAKE_DIR)
set(FLANG_CONFIG_EXPORTS_FILE)
if (NOT LLVM_INSTALL_TOOLCHAIN_ONLY)
get_property(flang_has_exports GLOBAL PROPERTY FLANG_HAS_EXPORTS)
if(flang_has_exports)
install(EXPORT FlangTargets DESTINATION ${FLANG_INSTALL_PACKAGE_DIR}
COMPONENT flang-cmake-exports)
endif()
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/FlangConfig.cmake
DESTINATION ${FLANG_INSTALL_PACKAGE_DIR}
COMPONENT flang-cmake-exports)
if(NOT LLVM_ENABLE_IDE)
# Add a dummy target so this can be used with LLVM_DISTRIBUTION_COMPONENTS
add_custom_target(flang-cmake-exports)
add_llvm_install_targets(install-flang-cmake-exports
COMPONENT flang-cmake-exports)
endif()
endif()

View File

@ -0,0 +1,13 @@
# This file allows users to call find_package(Flang) and pick up our targets.
@FLANG_CONFIG_CODE@
find_package(LLVM REQUIRED CONFIG
HINTS "@FLANG_CONFIG_LLVM_CMAKE_DIR@")
set(FLANG_EXPORTED_TARGETS "@FLANG_EXPORTS@")
set(FLANG_CMAKE_DIR "FLANG_CONFIG_CMAKE_DIR@")
set(FLANG_INCLUDE_DIRS "@FLANG_CONFIG_INCLUDE_DIRS@")
# Provide all our library targets to users.
include("@FLANG_CONFIG_EXPORTS_FILE@")

View File

@ -0,0 +1,209 @@
<!--===- documentation/ArrayComposition.md
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
-->
This note attempts to describe the motivation for and design of an
implementation of Fortran 90 (and later) array expression evaluation that
minimizes the use of dynamically allocated temporary storage for
the results of calls to transformational intrinsic functions, and
making them more amenable to acceleration.
The transformational intrinsic functions of Fortran of interest to
us here include:
* Reductions to scalars (`SUM(X)`, also `ALL`, `ANY`, `COUNT`,
`DOT_PRODUCT`,
`IALL`, `IANY`, `IPARITY`, `MAXVAL`, `MINVAL`, `PARITY`, `PRODUCT`)
* Axial reductions (`SUM(X,DIM=)`, &c.)
* Location reductions to indices (`MAXLOC`, `MINLOC`, `FINDLOC`)
* Axial location reductions (`MAXLOC(DIM=`, &c.)
* `TRANSPOSE(M)` matrix transposition
* `RESHAPE` without `ORDER=`
* `RESHAPE` with `ORDER=`
* `CSHIFT` and `EOSHIFT` with scalar `SHIFT=`
* `CSHIFT` and `EOSHIFT` with array-valued `SHIFT=`
* `PACK` and `UNPACK`
* `MATMUL`
* `SPREAD`
Other Fortran intrinsic functions are technically transformational (e.g.,
`COMMAND_ARGUMENT_COUNT`) but not of interest for this note.
The generic `REDUCE` is also not considered here.
Arrays as functions
===================
A whole array can be viewed as a function that maps its indices to the values
of its elements.
Specifically, it is a map from a tuple of integers to its element type.
The rank of the array is the number of elements in that tuple,
and the shape of the array delimits the domain of the map.
`REAL :: A(N,M)` can be seen as a function mapping ordered pairs of integers
`(J,K)` with `1<=J<=N` and `1<=J<=M` to real values.
Array expressions as functions
==============================
The same perspective can be taken of an array expression comprising
intrinsic operators and elemental functions.
Fortran doesn't allow one to apply subscripts directly to an expression,
but expressions have rank and shape, and one can view array expressions
as functions over index tuples by applying those indices to the arrays
and subexpressions in the expression.
Consider `B = A + 1.0` (assuming `REAL :: A(N,M), B(N,M)`).
The right-hand side of that assignment could be evaluated into a
temporary array `T` and then subscripted as it is copied into `B`.
```
REAL, ALLOCATABLE :: T(:,:)
ALLOCATE(T(N,M))
DO CONCURRENT(J=1:N,K=1:M)
T(J,K)=A(J,K) + 1.0
END DO
DO CONCURRENT(J=1:N,K=1:M)
B(J,K)=T(J,K)
END DO
DEALLOCATE(T)
```
But we can avoid the allocation, population, and deallocation of
the temporary by treating the right-hand side expression as if it
were a statement function `F(J,K)=A(J,K)+1.0` and evaluating
```
DO CONCURRENT(J=1:N,K=1:M)
A(J,K)=F(J,K)
END DO
```
In general, when a Fortran array assignment to a non-allocatable array
does not include the left-hand
side variable as an operand of the right-hand side expression, and any
function calls on the right-hand side are elemental or scalar-valued,
we can avoid the use of a temporary.
Transformational intrinsic functions as function composition
============================================================
Many of the transformational intrinsic functions listed above
can, when their array arguments are viewed as functions over their
index tuples, be seen as compositions of those functions with
functions of the "incoming" indices -- yielding a function for
an entire right-hand side of an array assignment statement.
For example, the application of `TRANSPOSE(A + 1.0)` to the index
tuple `(J,K)` becomes `A(K,J) + 1.0`.
Partial (axial) reductions can be similarly composed.
The application of `SUM(A,DIM=2)` to the index `J` is the
complete reduction `SUM(A(J,:))`.
More completely:
* Reductions to scalars (`SUM(X)` without `DIM=`) become
runtime calls; the result needs no dynamic allocation,
being a scalar.
* Axial reductions (`SUM(X,DIM=d)`) applied to indices `(J,K)`
become scalar values like `SUM(X(J,K,:))` if `d=3`.
* Location reductions to indices (`MAXLOC(X)` without `DIM=`)
do not require dynamic allocation, since their results are
either scalar or small vectors of length `RANK(X)`.
* Axial location reductions (`MAXLOC(X,DIM=)`, &c.)
are handled like other axial reductions like `SUM(DIM=)`.
* `TRANSPOSE(M)` exchanges the two components of the index tuple.
* `RESHAPE(A,SHAPE=s)` without `ORDER=` must precompute the shape
vector `S`, and then use it to linearize indices into offsets
in the storage order of `A` (whose shape must also be captured).
These conversions can involve division and/or modulus, which
can be optimized into a fixed-point multiplication using the
usual technique.
* `RESHAPE` with `ORDER=` is similar, but must permute the
components of the index tuple; it generalizes `TRANSPOSE`.
* `CSHIFT` applies addition and modulus.
* `EOSHIFT` applies addition and a conditional move (`MERGE`).
* `PACK` and `UNPACK` are likely to require a runtime call.
* `MATMUL(A,B)` can become `DOT_PRODUCT(A(J,:),B(:,K))`, but
might benefit from calling a highly optimized runtime
routine.
* `SPREAD(A,DIM=d,NCOPIES=n)` for compile-time `d` simply
applies `A` to a reduced index tuple.
Determination of rank and shape
===============================
An important part of evaluating array expressions without the use of
temporary storage is determining the shape of the result prior to,
or without, evaluating the elements of the result.
The shapes of array objects, results of elemental intrinsic functions,
and results of intrinsic operations are obvious.
But it is possible to determine the shapes of the results of many
transformational intrinsic function calls as well.
* `SHAPE(SUM(X,DIM=d))` is `SHAPE(X)` with one element removed:
`PACK(SHAPE(X),[(j,j=1,RANK(X))]/=d)` in general.
(The `DIM=` argument is commonly a compile-time constant.)
* `SHAPE(MAXLOC(X))` is `[RANK(X)]`.
* `SHAPE(MAXLOC(X,DIM=d))` is `SHAPE(X)` with one element removed.
* `SHAPE(TRANSPOSE(M))` is a reversal of `SHAPE(M)`.
* `SHAPE(RESHAPE(..., SHAPE=S))` is `S`.
* `SHAPE(CSHIFT(X))` is `SHAPE(X)`; same with `EOSHIFT`.
* `SHAPE(PACK(A,VECTOR=V))` is `SHAPE(V)`
* `SHAPE(PACK(A,MASK=m))` with non-scalar `m` and without `VECTOR=` is `[COUNT(m)]`.
* `RANK(PACK(...))` is always 1.
* `SHAPE(UNPACK(MASK=M))` is `SHAPE(M)`.
* `SHAPE(MATMUL(A,B))` drops one value from `SHAPE(A)` and another from `SHAPE(B)`.
* `SHAPE(SHAPE(X))` is `[RANK(X)]`.
* `SHAPE(SPREAD(A,DIM=d,NCOPIES=n))` is `SHAPE(A)` with `n` inserted at
dimension `d`.
This is useful because expression evaluations that *do* require temporaries
to hold their results (due to the context in which the evaluation occurs)
can be implemented with a separation of the allocation
of the temporary array and the population of the array.
The code that evaluates the expression, or that implements a transformational
intrinsic in the runtime library, can be designed with an API that includes
a pointer to the destination array as an argument.
Statements like `ALLOCATE(A,SOURCE=expression)` should thus be capable
of evaluating their array expressions directly into the newly-allocated
storage for the allocatable array.
The implementation would generate code to calculate the shape, use it
to allocate the memory and populate the descriptor, and then drive a
loop nest around the expression to populate the array.
In cases where the analyzed shape is known at compile time, we should
be able to have the opportunity to avoid heap allocation in favor of
stack storage, if the scope of the variable is local.
Automatic reallocation of allocatables
======================================
Fortran 2003 introduced the ability to assign non-conforming array expressions
to ALLOCATABLE arrays with the implied semantics of reallocation to the
new shape.
The implementation of this feature also becomes more straightforward if
our implementation of array expressions has decoupled calculation of shapes
from the evaluation of the elements of the result.
Rewriting rules
===============
Let `{...}` denote an ordered tuple of 1-based indices, e.g. `{j,k}`, into
the result of an array expression or subexpression.
* Array constructors always yield vectors; higher-rank arrays that appear as
constituents are flattened; so `[X] => RESHAPE(X,SHAPE=[SIZE(X)})`.
* Array constructors with multiple constituents are concatenations of
their constituents; so `[X,Y]{j} => MERGE(Y{j-SIZE(X)},X{j},J>SIZE(X))`.
* Array constructors with implied DO loops are difficult when nested
triangularly.
* Whole array references can have lower bounds other than 1, so
`A => A(LBOUND(A,1):UBOUND(A,1),...)`.
* Array sections simply apply indices: `A(i:...:n){j} => A(i1+n*(j-1))`.
* Vector-valued subscripts apply indices to the subscript: `A(N(:)){j} => A(N(:){j})`.
* Scalar operands ignore indices: `X{j,k} => X`.
Further, they are evaluated at most once.
* Elemental operators and functions apply indices to their arguments:
`(A(:,:) + B(:,:)){j,k}` => A(:,:){j,k} + B(:,:){j,k}`.
* `TRANSPOSE(X){j,k} => X{k,j}`.
* `SPREAD(X,DIM=2,...){j,k} => X{j}`; i.e., the contents are replicated.
If X is sufficiently expensive to compute elementally, it might be evaluated
into a temporary.
(more...)

View File

@ -0,0 +1,149 @@
<!--===- documentation/C++17.md
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
-->
## C++14/17 features used in f18
The C++ dialect used in this project constitutes a subset of the
standard C++ programming language and library features.
We want our dialect to be compatible with the LLVM C++ language
subset that will be in use at the time that we integrate with that
project.
We also want to maximize portability, future-proofing,
compile-time error checking, and use of best practices.
To that end, we have a C++ style guide (q.v.) that lays
out the details of how our C++ code should look and gives
guidance about feature usage.
We have chosen to use some features of the recent C++17
language standard in f18.
The most important of these are:
* sum types (discriminated unions) in the form of `std::variant`
* `using` template parameter packs
* generic lambdas with `auto` argument types
* product types in the form of `std::tuple`
* `std::optional`
(`std::tuple` is actually a C++11 feature, but I include it
in this list because it's not particularly well known.)
### Sum types
First, some background information to explain the need for sum types
in f18.
Fortran is notoriously problematic to lex and parse, as tokenization
depends on the state of the partial parse;
the language has no reserved words in the sense that C++ does.
Fortran parsers implemented with distinct lexing and parsing phases
(generated by hand or with tools) need to implement them as
coroutines with complicated state, and experience has shown that
it's hard to get them right and harder to extend them as the language
evolves.
Alternatively, with the use of backtracking, one can parse Fortran with
a unified lexer/parser.
We have chosen to do so because it is simpler and should reduce
both initial bugs and long-term maintenance.
Specifically, f18's parser uses the technique of recursive descent with
backtracking.
It is constructed as the incremental composition of pure parsing functions
that each, when given a context (location in the input stream plus some state),
either _succeeds_ or _fails_ to recognize some piece of Fortran.
On success, they return a new state and some semantic value, and this is
usually an instance of a C++ `struct` type that encodes the semantic
content of a production in the Fortran grammar.
This technique allows us to specify both the Fortran grammar and the
representation of successfully parsed programs with C++ code
whose functions and data structures correspond closely to the productions
of Fortran.
The specification of Fortran uses a form of BNF with alternatives,
optional elements, sequences, and lists. Each of these constructs
in the Fortran grammar maps directly in the f18 parser to both
the means of combining other parsers as alternatives, &c., and to
the declarations of the parse tree data structures that represent
the results of successful parses.
Move semantics are used in the parsing functions to acquire and
combine the results of sub-parses into the result of a larger
parse.
To represent nodes in the Fortran parse tree, we need a means of
handling sum types for productions that have multiple alternatives.
The bounded polymorphism supplied by the C++17 `std::variant` fits
those needs exactly.
For example, production R502 in Fortran defines the top-level
program unit of Fortran as being a function, subroutine, module, &c.
The `struct ProgramUnit` in the f18 parse tree header file
represents each program unit with a member that is a `std::variant`
over the six possibilities.
Similarly, the parser for that type in the f18 grammar has six alternatives,
each of which constructs an instance of `ProgramUnit` upon the result of
parsing a `Module`, `FunctionSubprogram`, and so on.
Code that performs semantic analysis on the result of a successful
parse is typically implemented with overloaded functions.
A function instantiated on `ProgramUnit` will use `std::visit` to
identify the right alternative and perform the right actions.
The call to `std::visit` must pass a visitor that can handle all
of the possibilities, and f18 will fail to build if one is missing.
Were we unable to use `std::variant` directly, we would likely
have chosen to implement a local `SumType` replacement; in the
absence of C++17's abilities of `using` a template parameter pack
and allowing `auto` arguments in anonymous lambda functions,
it would be less convenient to use.
The other options for polymorphism in C++ at the level of C++11
would be to:
* loosen up compile-time type safety and use a unified parse tree node
representation with an enumeration type for an operator and generic
subtree pointers, or
* define the sum types for the parse tree as abstract base classes from
which each particular alternative would derive, and then use virtual
functions (or the forbidden `dynamic_cast`) to identify alternatives
during analysis
### Product types
Many productions in the Fortran grammar describe a sequence of various
sub-parses.
For example, R504 defines the things that may appear in the "specification
part" of a subprogram in the order in which they are allowed: `USE`
statements, then `IMPORT` statements, and so on.
The parse tree node that represents such a thing needs to incorporate
the representations of those parses, of course.
It turns out to be convenient to allow these data members to be anonymous
components of a `std::tuple` product type.
This type facilitates the automation of code that walks over all of the
members in a type-safe fashion and avoids the need to invent and remember
needless member names -- the components of a `std::tuple` instance can
be identified and accessed in terms of their types, and those tend to be
distinct.
So we use `std::tuple` for such things.
It has also been handy for template metaprogramming that needs to work
with lists of types.
### `std::optional`
This simple little type is used wherever a value might or might not be
present.
It is especially useful for function results and
rvalue reference arguments.
It corresponds directly to the optional elements in the productions
of the Fortran grammar.
It is also used as a wrapper around a parse tree node type to define the
results of the various parsing functions, where presence of a value
signifies a successful recognition and absence denotes a failed parse.
It is used in data structures in place of nullable pointers to
avoid indirection as well as the possible confusion over whether a pointer
is allowed to be null.

View File

@ -0,0 +1,334 @@
<!--===- documentation/C++style.md
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
-->
## In brief:
* Use *clang-format*
from llvm 7
on all C++ source and header files before
every merge to master. All code layout should be determined
by means of clang-format.
* Where a clear precedent exists in the project, follow it.
* Otherwise, where [LLVM's C++ style guide](https://llvm.org/docs/CodingStandards.html#style-issues)
is clear on usage, follow it.
* Otherwise, where a good public C++ style guide is relevant and clear,
follow it. [Google's](https://google.github.io/styleguide/cppguide.html)
is pretty good and comes with lots of justifications for its rules.
* Reasonable exceptions to these guidelines can be made.
* Be aware of some workarounds for known issues in older C++ compilers that should
still be able to compile f18. They are listed at the end of this document.
## In particular:
Use serial commas in comments, error messages, and documentation
unless they introduce ambiguity.
### Error messages
1. Messages should be a single sentence with few exceptions.
1. Fortran keywords should appear in upper case.
1. Names from the program appear in single quotes.
1. Messages should start with a capital letter.
1. Messages should not end with a period.
### Files
1. File names should use dashes, not underscores. C++ sources have the
extension ".cpp", not ".C" or ".cc" or ".cxx". Don't create needless
source directory hierarchies.
1. Header files should be idempotent. Use the usual technique:
```
#ifndef FORTRAN_header_H_
#define FORTRAN_header_H_
// code
#endif // FORTRAN_header_H_
```
1. `#include` every header defining an entity that your project header or source
file actually uses directly. (Exception: when foo.cpp starts, as it should,
with `#include "foo.h"`, and foo.h includes bar.h in order to define the
interface to the module foo, you don't have to redundantly `#include "bar.h"`
in foo.cpp.)
1. In the source file "foo.cpp", put its corresponding `#include "foo.h"`
first in the sequence of inclusions.
Then `#include` other project headers in alphabetic order; then C++ standard
headers, also alphabetically; then C and system headers.
1. Don't use `#include <iostream>`. If you need it for temporary debugging,
remove the inclusion before committing.
### Naming
1. C++ names that correspond to well-known interfaces from the STL, LLVM,
and Fortran standard
can and should look like their models when the reader can safely assume that
they mean the same thing -- e.g., `clear()` and `size()` member functions
in a class that implements an STL-ish container.
Fortran intrinsic function names are conventionally in ALL CAPS.
1. Non-public data members should be named with leading miniscule (lower-case)
letters, internal camelCase capitalization, and a trailing underscore,
e.g. `DoubleEntryBookkeepingSystem myLedger_;`. POD structures with
only public data members shouldn't use trailing underscores, since they
don't have class functions from which data members need to be distinguishable.
1. Accessor member functions are named with the non-public data member's name,
less the trailing underscore. Mutator member functions are named `set_...`
and should return `*this`. Don't define accessors or mutators needlessly.
1. Other class functions should be named with leading capital letters,
CamelCase, and no underscores, and, like all functions, should be based
on imperative verbs, e.g. `HaltAndCatchFire()`.
1. It is fine to use short names for local variables with limited scopes,
especially when you can declare them directly in a `for()`/`while()`/`if()`
condition. Otherwise, prefer complete English words to abbreviations
when creating names.
### Commentary
1. Use `//` for all comments except for short `/*notes*/` within expressions.
1. When `//` follows code on a line, precede it with two spaces.
1. Comments should matter. Assume that the reader knows current C++ at least as
well as you do and avoid distracting her by calling out usage of new
features in comments.
### Layout
Always run `clang-format` on your changes before committing code. LLVM
has a `git-clang-format` script to facilitate running clang-format only
on the lines that have changed.
Here's what you can expect to see `clang-format` do:
1. Indent with two spaces.
1. Don't indent public:, protected:, and private:
accessibility labels.
1. Never use more than 80 characters per source line.
1. Don't use tabs.
1. Don't indent the bodies of namespaces, even when nested.
1. Function result types go on the same line as the function and argument
names.
Don't try to make columns of variable names or comments
align vertically -- they are maintenance problems.
Always wrap the bodies of `if()`, `else`, `while()`, `for()`, `do`, &c.
with braces, even when the body is a single statement or empty. The
opening `{` goes on
the end of the line, not on the next line. Functions also put the opening
`{` after the formal arguments or new-style result type, not on the next
line. Use `{}` for empty inline constructors and destructors in classes.
If any branch of an `if`/`else if`/`else` cascade ends with a return statement,
they all should, with the understanding that the cases are all unexceptional.
When testing for an error case that should cause an early return, do so with
an `if` that doesn't have a following `else`.
Don't waste space on the screen with needless blank lines or elaborate block
commentary (lines of dashes, boxes of asterisks, &c.). Write code so as to be
easily read and understood with a minimum of scrolling.
Avoid using assignments in controlling expressions of `if()` &c., even with
the idiom of wrapping them with extra parentheses.
In multi-element initializer lists (especially `common::visitors{...}`),
including a comma after the last element often causes `clang-format` to do
a better jobs of formatting.
### C++ language
Use *C++17*, unless some compiler to which we must be portable lacks a feature
you are considering.
However:
1. Never throw or catch exceptions.
1. Never use run-time type information or `dynamic_cast<>`.
1. Never declare static data that executes a constructor.
(This is why `#include <iostream>` is contraindicated.)
1. Use `{braced initializers}` in all circumstances where they work, including
default data member initialization. They inhibit implicit truncation.
Don't use `= expr` initialization just to effect implicit truncation;
prefer an explicit `static_cast<>`.
With C++17, braced initializers work fine with `auto` too.
Sometimes, however, there are better alternatives to empty braces;
e.g., prefer `return std::nullopt;` to `return {};` to make it more clear
that the function's result type is a `std::optional<>`.
1. Avoid unsigned types apart from `size_t`, which must be used with care.
When `int` just obviously works, just use `int`. When you need something
bigger than `int`, use `std::int64_t` rather than `long` or `long long`.
1. Use namespaces to avoid conflicts with client code. Use one top-level
`Fortran` project namespace. Don't introduce needless nested namespaces within the
project when names don't conflict or better solutions exist. Never use
`using namespace ...;` outside test code; never use `using namespace std;`
anywhere. Access STL entities with names like `std::unique_ptr<>`,
without a leading `::`.
1. Prefer `static` functions over functions in anonymous namespaces in source files.
1. Use `auto` judiciously. When the type of a local variable is known,
monomorphic, and easy to type, be explicit rather than using `auto`.
Don't use `auto` functions unless the type of the result of an outlined member
function definition can be more clear due to its use of types declared in the
class.
1. Use move semantics and smart pointers to make dynamic memory ownership
clear. Consider reworking any code that uses `malloc()` or a (non-placement)
`operator new`.
See the section on Pointers below for some suggested options.
1. When defining argument types, use values when object semantics are
not required and the value is small and copyable without allocation
(e.g., `int`);
use `const` or rvalue references for larger values (e.g., `std::string`);
use `const` references to rather than pointers to immutable objects;
and use non-`const` references for mutable objects, including "output" arguments
when they can't be function results.
Put such output arguments last (_pace_ the standard C library conventions for `memcpy()` & al.).
1. Prefer `typename` to `class` in template argument declarations.
1. Prefer `enum class` to plain `enum` wherever `enum class` will work.
We have an `ENUM_CLASS` macro that helps capture the names of constants.
1. Use `constexpr` and `const` generously.
1. When a `switch()` statement's labels do not cover all possible case values
explicitly, it should contain either a `default:;` at its end or a
`default:` label that obviously crashes; we have a `CRASH_NO_CASE` macro
for such situations.
1. On the other hand, when a `switch()` statement really does cover all of
the values of an `enum class`, please insert a call to the `SWITCH_COVERS_ALL_CASES`
macro at the top of the block. This macro does the right thing for G++ and
clang to ensure that no warning is emitted when the cases are indeed all covered.
1. When using `std::optional` values, avoid unprotected access to their content.
This is usually by means of `x.has_value()` guarding execution of `*x`.
This is implicit when they are function results assigned to local variables
in `if`/`while` predicates.
When no presence test is obviously protecting a `*x` reference to the
contents, and it is assumed that the contents are present, validate that
assumption by using `x.value()` instead.
1. We use `c_str()` rather than `data()` when converting a `std::string`
to a `const char *` when the result is expected to be NUL-terminated.
1. Avoid explicit comparisions of pointers to `nullptr` and tests of
presence of `optional<>` values with `.has_value()` in the predicate
expressions of control flow statements, but prefer them to implicit
conversions to `bool` when initializing `bool` variables and arguments,
and to the use of the idiom `!!`.
#### Classes
1. Define POD structures with `struct`.
1. Don't use `this->` in (non-static) member functions, unless forced to
do so in a template member function.
1. Define accessor and mutator member functions (implicitly) inline in the
class, after constructors and assignments. Don't needlessly define
(implicit) inline member functions in classes unless they really solve a
performance problem.
1. Try to make class definitions in headers concise specifications of
interfaces, at least to the extent that C++ allows.
1. When copy constructors and copy assignment are not necessary,
and move constructors/assignment is present, don't declare them and they
will be implicitly deleted. When neither copy nor move constructors
or assignments should exist for a class, explicitly `=delete` all of them.
1. Make single-argument constructors (other than copy and move constructors)
'explicit' unless you really want to define an implicit conversion.
#### Pointers
There are many -- perhaps too many -- means of indirect addressing
data in this project.
Some of these are standard C++ language and library features,
while others are local inventions in `lib/Common`:
* Bare pointers (`Foo *p`): these are obviously nullable, non-owning,
undefined when uninitialized, shallowly copyable, reassignable, and often
not the right abstraction to use in this project.
But they can be the right choice to represent an optional
non-owning reference, as in a function result.
Use the `DEREF()` macro to convert a pointer to a reference that isn't
already protected by an explicit test for null.
* References (`Foo &r`, `const Foo &r`): non-nullable, not owning,
shallowly copyable, and not reassignable.
References are great for invisible indirection to objects whose lifetimes are
broader than that of the reference.
Take care when initializing a reference with another reference to ensure
that a copy is not made because only one of the references is `const`;
this is a pernicious C++ language pitfall!
* Rvalue references (`Foo &&r`): These are non-nullable references
*with* ownership, and they are ubiquitously used for formal arguments
wherever appropriate.
* `std::reference_wrapper<>`: non-nullable, not owning, shallowly
copyable, and (unlike bare references) reassignable, so suitable for
use in STL containers and for data members in classes that need to be
copyable or assignable.
* `common::Reference<>`: like `std::reference_wrapper<>`, but also supports
move semantics, member access, and comparison for equality; suitable for use in
`std::variant<>`.
* `std::unique_ptr<>`: A nullable pointer with ownership, null by default,
not copyable, reassignable.
F18 has a helpful `Deleter<>` class template that makes `unique_ptr<>`
easier to use with forward-referenced data types.
* `std::shared_ptr<>`: A nullable pointer with shared ownership via reference
counting, null by default, shallowly copyable, reassignable, and slow.
* `Indirection<>`: A non-nullable pointer with ownership and
optional deep copy semantics; reassignable.
Often better than a reference (due to ownership) or `std::unique_ptr<>`
(due to non-nullability and copyability).
Can be wrapped in `std::optional<>` when nullability is required.
Usable with forward-referenced data types with some use of `extern template`
in headers and explicit template instantiation in source files.
* `CountedReference<>`: A nullable pointer with shared ownership via
reference counting, null by default, shallowly copyable, reassignable.
Safe to use *only* when the data are private to just one
thread of execution.
Used sparingly in place of `std::shared_ptr<>` only when the overhead
of that standard feature is prohibitive.
A feature matrix:
| indirection | nullable | default null | owning | reassignable | copyable | undefined type ok? |
| ----------- | -------- | ------------ | ------ | ------------ | -------- | ------------------ |
| `*p` | yes | no | no | yes | shallowly | yes |
| `&r` | no | n/a | no | no | shallowly | yes |
| `&&r` | no | n/a | yes | no | shallowly | yes |
| `reference_wrapper<>` | no | n/a | no | yes | shallowly | yes |
| `Reference<>` | no | n/a | no | yes | shallowly | yes |
| `unique_ptr<>` | yes | yes | yes | yes | no | yes, with work |
| `shared_ptr<>` | yes | yes | yes | yes | shallowly | no |
| `Indirection<>` | no | n/a | yes | yes | optionally deeply | yes, with work |
| `CountedReference<>` | yes | yes | yes | yes | shallowly | no |
### Overall design preferences
Don't use dynamic solutions to solve problems that can be solved at
build time; don't solve build time problems by writing programs that
produce source code when macros and templates suffice; don't write macros
when templates suffice. Templates are statically typed, checked by the
compiler, and are (or should be) visible to debuggers.
### Exceptions to these guidelines
Reasonable exceptions will be allowed; these guidelines cannot anticipate
all situations.
For example, names that come from other sources might be more clear if
their original spellings are preserved rather than mangled to conform
needlessly to the conventions here, as Google's C++ style guide does
in a way that leads to weirdly capitalized abbreviations in names
like `Http`.
Consistency is one of many aspects in the pursuit of clarity,
but not an end in itself.
## C++ compiler bug workarounds
Below is a list of workarounds for C++ compiler bugs met with f18 that, even
if the bugs are fixed in latest C++ compiler versions, need to be applied so
that all desired tool-chains can compile f18.
### Explicitly move noncopyable local variable into optional results
The following code is legal C++ but fails to compile with the
default Ubuntu 18.04 g++ compiler (7.4.0-1ubuntu1~18.0.4.1):
```
class CantBeCopied {
public:
CantBeCopied(const CantBeCopied&) = delete;
CantBeCopied(CantBeCopied&&) = default;
CantBeCopied() {}
};
std::optional<CantBeCopied> fooNOK() {
CantBeCopied result;
return result; // Legal C++, but does not compile with Ubuntu 18.04 default g++
}
std::optional<CantBeCopied> fooOK() {
CantBeCopied result;
return {std::move(result)}; // Compiles OK everywhere
}
```
The underlying bug is actually not specific to `std::optional` but this is the most common
case in f18 where the issue may occur. The actual bug can be reproduced with any class `B`
that has a perfect forwarding constructor taking `CantBeCopied` as argument:
`template<typename CantBeCopied> B(CantBeCopied&& x) x_{std::forward<CantBeCopied>(x)} {}`.
In such scenarios, Ubuntu 18.04 g++ fails to instantiate the move constructor
and to construct the returned value as it should, instead it complains about a
missing copy constructor.
Local result variables do not need to and should not be explicitly moved into optionals
if they have a copy constructor.

View File

@ -0,0 +1,679 @@
<!--===- documentation/Calls.md
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
-->
## Procedure reference implementation protocol
Fortran function and subroutine references are complicated.
This document attempts to collect the requirements imposed by the 2018
standard (and legacy extensions) on programs and implementations, work
through the implications of the various features, and propose both a
runtime model and a compiler design.
All section, requirement, and constraint numbers herein pertain to
the Fortran 2018 standard.
This note does not consider calls to intrinsic procedures, statement
functions, or calls to internal runtime support library routines.
## Quick review of terminology
* A *dummy argument* is a function or subroutine parameter.
It is *associated* with an *effective argument* at each call
to the procedure.
* The *shape* of an array is a vector containing its extent (size)
on each dimension; the *rank* of an array is the number of its
dimensions (i.e., the shape of its shape).
The absolute values of the lower and upper bounds of the dimensions
of an array are not part of its shape, just their difference (plus 1).
* An *explicit-shape* array has all of its bounds specified; lower
bounds default to 1. These can be passed by with a single address
and their contents are contiguous.
* An *assumed-size* array is an explicit-shape array with `*` as its
final dimension, which is the most-significant one in Fortran
and whose value does not affect indexed address calculations.
* A *deferred-shape* array (`DIMENSION::A(:)`) is a `POINTER` or `ALLOCATABLE`.
`POINTER` target data might not be contiguous.
* An *assumed-shape* (not size!) array (`DIMENSION::A(:)`) is a dummy argument
that is neither `POINTER` nor `ALLOCATABLE`; its lower bounds can be set
by the procedure that receives them (defaulting to 1), and its
upper bounds are functions of the lower bounds and the extents of
dimensions in the *shape* of the effective argument.
* An *assumed-length* `CHARACTER(*)` dummy argument
takes its length from the effective argument.
* An *assumed-length* `CHARACTER(*)` *result* of an external function (C721)
has its length determined by its eventual declaration in a calling scope.
* An *assumed-rank* `DIMENSION::A(..)` dummy argument array has an unknown
number of dimensions.
* A *polymorphic* `CLASS(t)` dummy argument, `ALLOCATABLE`, or `POINTER`
has a specific derived type or some extension of that type.
An *unlimited polymorphic* `CLASS(*)` object can have any
intrinsic or derived type.
* *Interoperable* `BIND(C)` procedures are written in C or callable from C.
## Interfaces
Referenced procedures may or may not have declared interfaces
available to their call sites.
Procedures with some post-Fortran '77 features *require* an
explicit interface to be called (15.4.2.2) or even passed (4.3.4(5)):
* use of argument keywords in a call
* procedures that are `ELEMENTAL` or `BIND(C)`
* procedures that are required to be `PURE` due to the context of the call
(specification expression, `DO CONCURRENT`, `FORALL`)
* dummy arguments with these attributes: `ALLOCATABLE`, `POINTER`,
`VALUE`, `TARGET`, `OPTIONAL`, `ASYNCHRONOUS`, `VOLATILE`,
and, as a consequence of limitations on its use, `CONTIGUOUS`;
`INTENT()`, however, does *not* require an explicit interface
* dummy arguments that are coarrays
* dummy arguments that are assumed-shape or assumed-rank arrays
* dummy arguments with parameterized derived types
* dummy arguments that are polymorphic
* function result that is an array
* function result that is `ALLOCATABLE` or `POINTER`
* `CHARACTER` function result whose length is neither constant
nor assumed
* derived type function result with `LEN` type parameter value that is
not constant
(note that result derived type parameters cannot be assumed (C795))
Module procedures, internal procedures, procedure pointers,
type-bound procedures, and recursive references by a procedure to itself
always have explicit interfaces.
(Consequently, they cannot be assumed-length `CHARACTER(*)` functions;
conveniently, assumed-length `CHARACTER(*)` functions are prohibited from
recursion (15.6.2.1(3))).
Other uses of procedures besides calls may also require explicit interfaces,
such as procedure pointer assignment, type-bound procedure bindings, &c.
Note that non-parameterized monomorphic derived type arguments do
*not* by themselves require the use of an explicit interface.
However, dummy arguments with any derived type parameters *do*
require an explicit interface, even if they are all `KIND` type
parameters.
15.5.2.9(2) explicitly allows an assumed-length `CHARACTER(*)` function
to be passed as an actual argument to an explicit-length dummy;
this has implications for calls to character-valued dummy functions
and function pointers.
(In the scopes that reference `CHARACTER` functions, they must have
visible definitions with explicit result lengths.)
### Implicit interfaces
In the absence of any characteristic or context that *requires* an
explicit interface (see above), an external function or subroutine (R503)
or `ENTRY` (R1541) can be called directly or indirectly via its implicit interface.
Each of the arguments can be passed as a simple address, including
dummy procedures.
Procedures that *can* be called via an implicit interface can
undergo more thorough checking
by semantics when an explicit interface for them exists, but they must be
compiled as if all calls to them were through the implicit interface.
This note will mention special handling for procedures that are exposed
to the possibility of being called with an implicit interface as *F77ish* procedures
below; this is of course not standard terminology.
Internal and module subprograms that are ever passed as arguments &/or
assigned as targets of procedure pointers may be F77ish.
Every F77ish procedure can and must be distiguished at compilation time.
Such procedures should respect the external naming conventions (when external)
and any legacy ABI used for Fortran '77 programs on the target architecture,
so that portable libraries can be compiled
and used by distinct implementations (and their versions)
of Fortran.
Note that F77ish functions still have known result types, possibly by means
of implicit typing of their names.
They can also be `CHARACTER(*)` assumed-length character functions.
In other words: these F77sh procedures that do not require the use of an explicit
interface and that can possibly be referenced, directly or indirectly,
with implicit interfaces are limited to argument lists that comprise
only the addresses of effective arguments and the length of a `CHARACTER` function result
(when there is one), and they can return only scalar values with constant
type parameter values.
None of their arguments or results need be (or can be) implemented
with descriptors,
and any internal procedures passed to them as arguments must be
simple addresses of non-internal subprograms or trampolines for
internal procedures.
Note that the `INTENT` attribute does not, by itself,
require the use of explicit interface; neither does the use of a dummy
procedure (implicit or explicit in their interfaces).
So the analyis of calls to F77ish procedures must allow for the
invisible use of `INTENT(OUT)`.
## Protocol overview
Here is a summary script of all of the actions that may need to be taken
by the calling procedure and its referenced procedure to effect
the call, entry, exit, and return steps of the procedure reference
protocol.
The order of these steps is not particularly strict, and we have
some design alternatives that are explored further below.
### Before the call:
1. Compute &/or copy into temporary storage the values of
some effective argument expressions and designators (see below).
1. Create and populate descriptors for arguments that use them
(see below).
1. Possibly allocate function result storage,
when its size can be known by all callers; function results that are
neither `POINTER` nor `ALLOCATABLE` must have explicit shapes (C816).
1. Create and populate a descriptor for the function result, if it
needs one (deferred-shape/-length `POINTER`, any `ALLOCATABLE`,
derived type with non-constant length parameters, &c.).
1. Capture the values of host-escaping local objects in memory;
package them into single address (for calls to internal procedures &
for calls that pass internal procedures as arguments).
1. Resolve the target procedure's polymorphic binding, if any.
1. Marshal effective argument addresses (or values for `%VAL()` and some
discretionary `VALUE` arguments) into registers.
1. Marshal `CHARACTER` argument lengths in additional value arguments for
`CHARACTER` effective arguments not passed via descriptors.
These lengths must be 64-bit integers.
1. Marshal an extra argument for the length of a `CHARACTER` function
result if the function is F77ish.
1. Marshal an extra argument for the function result's descriptor,
if it needs one.
1. Set the "host instance" (static link) register when calling an internal
procedure from its host or another internal procedure, a procedure pointer,
or dummy procedure (when it has a descriptor).
1. Jump.
### On entry:
1. For subprograms with alternate `ENTRY` points: shuffle `ENTRY` dummy arguments
set a compiler-generated variable to identify the alternate entry point,
and jump to the common entry point for common processing and a `switch()`
to the statement after the `ENTRY`.
1. Capture `CHARACTER` argument &/or assumed-length result length values.
1. Complete `VALUE` copying if this step will not always be done
by the caller (as I think it should be).
1. Finalize &/or re-initialize `INTENT(OUT)` non-pointer
effective arguments (see below).
1. For interoperable procedures called from C: compact discontiguous
dummy argument values when necessary (`CONTIGUOUS` &/or
explicit-shape/assumed-size arrays of assumed-length `CHARACTER(*)`).
1. Optionally compact assumed-shape arguments for contiguity on one
or more leading dimensions to improve SIMD vectorization, if not
`TARGET` and not already sufficiently contiguous.
(PGI does this in the caller, whether the callee needs it or not.)
1. Complete allocation of function result storage, if that has
not been done by the caller.
1. Initialize components of derived type local variables,
including the function result.
Execute the callee, populating the function result or selecting
the subroutine's alternate return.
### On exit:
1. Clean up local scope (finalization, deallocation)
1. Deallocate `VALUE` argument temporaries.
(But don't finalize them; see 7.5.6.3(3)).
1. Replace any assumed-shape argument data that were compacted on
entry for contiguity when the data were possibly
modified across the call (never when `INTENT(IN)` or `VALUE`).
1. Identify alternate `RETURN` to caller.
1. Marshal results.
1. Jump
### On return to the caller:
1. Save the result registers, if any.
1. Copy effective argument array designator data that was copied into
a temporary back into its original storage (see below).
1. Complete deallocation of effective argument temporaries (not `VALUE`).
1. Reload definable host-escaping local objects from memory, if they
were saved to memory by the host before the call.
1. `GO TO` alternate return, if any.
1. Use the function result in an expression.
1. Eventually, finalize &/or deallocate the function result.
(I've omitted some obvious steps, like preserving/restoring callee-saved
registers on entry/exit, dealing with caller-saved registers before/after
calls, and architecture-dependent ABI requirements.)
## The messy details
### Copying effective argument values into temporary storage
There are several conditions that require the compiler to generate
code that allocates and populates temporary storage for an actual
argument.
First, effective arguments that are expressions, not designators, obviously
need to be computed and captured into memory in order to be passed
by reference.
This includes parenthesized designators like `(X)`, which are
expressions in Fortran, as an important special case.
(This case also technically includes unparenthesized constants,
but those are better implemented by passing addresses in read-only
memory.)
The dummy argument cannot be known to have `INTENT(OUT)` or
`INTENT(IN OUT)`.
Small scalar or elemental `VALUE` arguments may be passed in registers,
as should arguments wrapped in the legacy VMS `%VAL()` notation.
Multiple elemental `VALUE` arguments might be packed into SIMD registers.
Effective arguments that are designators, not expressions, must also
be copied into temporaries in the following situations.
1. Coindexed objects need to be copied into the local image.
This can get very involved if they contain `ALLOCATABLE`
components, which also need to be copied, along with their
`ALLOCATABLE` components, and may be best implemented with a runtime
library routine working off a description of the type.
1. Effective arguments associated with dummies with the `VALUE`
attribute need to be copied; this can be done on either
side of the call, but there are optimization opportunities
available when the caller's side bears the responsibility.
1. In non-elemental calls, the values of array sections with
vector-valued subscripts need to be gathered into temporaries.
These effective arguments are not definable, and they are not allowed to
be associated with non-`VALUE` dummy arguments with the attributes
`INTENT(OUT)`, `INTENT(IN OUT)`, `ASYNCHRONOUS`, or `VOLATILE`
(15.5.2.4(21)); `INTENT()` can't always be checked.
1. Non-simply-contiguous (9.5.4) arrays being passed to non-`POINTER`
dummy arguments that must be contiguous (due to a `CONTIGUOUS`
attribute, or not being assumed-shape or assumed-rank; this
is always the case for F77ish procedures).
This should be a runtime decision, so that effective arguments
that turn out to be contiguous can be passed cheaply.
This rule does not apply to coarray dummies, whose effective arguments
are required to be simply contiguous when this rule would otherwise
force the use of a temporary (15.5.2.8); neither does it apply
to `ASYNCHRONOUS` and `VOLATILE` effective arguments, which are
disallowed when copies would be necessary (C1538 - C1540).
*Only temporaries created by this contiguity requirement are
candidates for being copied back to the original variable after
the call* (see below).
Fortran requires (18.3.6(5)) that calls to interoperable procedures
with dummy argument arrays with contiguity requirements
handle the compaction of discontiguous data *in the Fortran callee*,
at least when called from C.
And discontiguous data must be compacted on the *caller's* side
when passed from Fortran to C (18.3.6(6)).
We could perform all argument compaction (discretionary or
required) in the callee, but there are many cases where the
compiler knows that the effective argument data are contiguous
when compiling the caller (a temporary is needed for other reasons,
or the effective argument is simply contiguous) and a run-time test for
discontiguity in the callee can be avoided by using a caller-compaction
convention when we have the freedom to choose.
While we are unlikely to want to _needlessly_ use a temporary for
an effective argument that does not require one for any of these
reasons above, we are specifically disallowed from doing so
by the standard in cases where pointers to the original target
data are required to be valid across the call (15.5.2.4(9-10)).
In particular, compaction of assumed-shape arrays for discretionary
contiguity on the leading dimension to ease SIMD vectorization
cannot be done safely for `TARGET` dummies without `VALUE`.
Effective arguments associated with known `INTENT(OUT)` dummies that
require allocation of a temporary -- and this can only be for reasons of
contiguity -- don't have to populate it, but they do have to perform
minimal initialization of any `ALLOCATABLE` components so that
the runtime doesn't crash when the callee finalizes and deallocates
them.
`ALLOCATABLE` coarrays are prohibited from being affected by `INTENT(OUT)`
(see C846).
Note that calls to implicit interfaces must conservatively allow
for the use of `INTENT(OUT)` by the callee.
Except for `VALUE` and known `INTENT(IN)` dummy arguments, the original
contents of local designators that have been compacted into temporaries
could optionally have their `ALLOCATABLE` components invalidated
across the call as an aid to debugging.
Except for `VALUE` and known `INTENT(IN)` dummy arguments, the contents of
the temporary storage will be copied back into the effective argument
designator after control returns from the procedure, and it may be necessary
to preserve addresses (or the values of subscripts and cosubscripts
needed to recalculate them) of the effective argument designator, or its
elements, in additional temporary storage if they can't be safely or
quickly recomputed after the call.
### `INTENT(OUT)` preparation
Effective arguments that are associated with `INTENT(OUT)`
dummy arguments are required to be definable.
This cannot always be checked, as the use of `INTENT(OUT)`
does not by itself mandate the use of an explicit interface.
`INTENT(OUT)` arguments are finalized (as if) on entry to the called
procedure. In particular, in calls to elemental procedures,
the elements of an array are finalized by a scalar or elemental
`FINAL` procedure (7.5.6.3(7)).
Derived type components that are `ALLOCATABLE` are finalized
and deallocated; they are prohibited from being coarrays.
Components with initializers are (re)initialized.
The preparation of effective arguments for `INTENT(OUT)` could be
done on either side of the call. If the preparation is
done by the caller, there is an optimization opportunity
in situations where unmodified incoming `INTENT(OUT)` dummy
arguments whose types lack `FINAL` procedures are being passed
onward as outgoing `INTENT(OUT)` arguments.
### Arguments and function results requiring descriptors
Dummy arguments are represented with the addresses of new descriptors
when they have any of the following characteristics:
1. assumed-shape array (`DIMENSION::A(:)`)
1. assumed-rank array (`DIMENSION::A(..)`)
1. parameterized derived type with assumed `LEN` parameters
1. polymorphic (`CLASS(T)`, `CLASS(*)`)
1. assumed-type (`TYPE(*)`)
1. coarray dummy argument
1. `INTENT(IN) POINTER` argument (15.5.2.7, C.10.4)
`ALLOCATABLE` and other `POINTER` arguments can be passed by simple
address.
Non-F77ish procedures use descriptors to represent two further
kinds of dummy arguments:
1. assumed-length `CHARACTER(*)`
1. dummy procedures
F77ish procedures use other means to convey character length and host instance
links (respectively) for these arguments.
Function results are described by the caller & callee in
a caller-supplied descriptor when they have any of the following
characteristics, some which necessitate an explicit interface:
1. deferred-shape array (so `ALLOCATABLE` or `POINTER`)
1. derived type with any non-constant `LEN` parameter
(C795 prohibit assumed lengths)
1. procedure pointer result (when the interface must be explicit)
Storage for a function call's result is allocated by the caller when
possible: the result is neither `ALLOCATABLE` nor `POINTER`,
the shape is scalar or explicit, and the type has `LEN` parameters
that are constant expressions.
In other words, the result doesn't require the use of a descriptor
but can't be returned in registers.
This allows a function result to be written directly into a local
variable or temporary when it is safe to treat the variable as if
it were an additional `INTENT(OUT)` argument.
(Storage for `CHARACTER` results, assumed or explicit, is always
allocated by the caller, and the length is always passed so that
an assumed-length external function will work when eventually
called from a scope that declares the length that it will use
(15.5.2.9 (2)).)
Note that the lower bounds of the dimensions of non-`POINTER`
non-`ALLOCATABLE` dummy argument arrays are determined by the
callee, not the caller.
(A Fortran pitfall: declaring `A(0:9)`, passing it to a dummy
array `D(:)`, and assuming that `LBOUND(D,1)` will be zero
in the callee.)
If the declaration of an assumed-shape dummy argument array
contains an explicit lower bound expression (R819), its value
needs to be computed by the callee;
it may be captured and saved in the incoming descriptor
as long as we assume that argument descriptors can be modified
by callees.
Callers should fill in all of the fields of outgoing
non-`POINTER` non-`ALLOCATABLE` argument
descriptors with the assumption that the callee will use 1 for
lower bound values, and callees can rely on them being 1 if
not modified.
### Copying temporary storage back into argument designators
Except for `VALUE` and known `INTENT(IN)` dummy arguments and array sections
with vector-valued subscripts (15.5.2.4(21)), temporary storage into
which effective argument data were compacted for contiguity before the call
must be redistributed back to its original storage by the caller after
the return.
In conjunction with saved cosubscript values, a standard descriptor
would suffice to represent a pointer to the original storage into which the
temporary data should be redistributed;
the descriptor need not be fully populated with type information.
Note that coindexed objects with `ALLOCATABLE` ultimate components
are required to be associated only with dummy arguments with the
`VALUE` &/or `INTENT(IN)` attributes (15.6.2.4(6)), so there is no
requirement that the local image somehow reallocate remote storage
when copying the data back.
### Polymorphic bindings
Calls to the type-bound procedures of monomorphic types are
resolved at compilation time, as are calls to `NON_OVERRIDABLE`
type-bound procedures.
The resolution of calls to overridable type-bound procedures of
polymorphic types must be completed at execution (generic resolution
of type-bound procedure bindings from effective argument types, kinds,
and ranks is always a compilation-time task (15.5.6, C.10.6)).
Each derived type that declares or inherits any overridable
type-bound procedure bindings must correspond to a static constant
table of code addresses (or, more likely, a static constant type
description containing or pointing to such a table, along with
information used by the runtime support library for initialization,
copying, finalization, and I/O of type instances). Each overridable
type-bound procedure in the type corresponds to an index into this table.
### Host instance linkage
Calls to dummy procedures and procedure pointers that resolve to
internal procedures need to pass an additional "host instance" argument that
addresses a block of storage in the stack frame of the their
host subprogram that was active at the time they were passed as an
effective argument or associated with a procedure pointer.
This is similar to a static link in implementations of programming
languages with nested subprograms, although Fortran only allows
one level of nesting.
The 64-bit x86 and little-endian OpenPower ABIs reserve registers
for this purpose (`%r10` & `R11`); 64-bit ARM has a reserved register
that can be used (`x18`).
The host subprogram objects that are visible to any of their internal
subprograms need to be resident in memory across any calls to them
(direct or not). Any host subprogram object that might be defined
during a call to an internal subprogram needs to be reloaded after
a call or reside permanently in memory.
A simple conservative analysis of the internal subprograms can
identify all of these escaping objects and their definable subset.
The address of the host subprogram storage used to hold the escaping
objects needs to be saved alongside the code address(es) that
represent a procedure pointer.
It also needs to be conveyed alongside the text address for a
dummy procedure.
For F77ish procedures, we cannot use a "procedure pointer descriptor"
to pass a procedure argument -- they expect to receive a single
address argument.
We will need to package the host instance link in a trampoline
that loads its address into the designated register.
GNU Fortran and Intel Fortran construct trampolines by writing
a sequence of machine instructions to a block of storage in the
host's stack frame, which requires the stack to be executable,
which seems inadvisable for security reasons;
XLF manages trampolines in its runtime support library, which adds some overhead
to their construction and a reclamation obligation;
NAG Fortran manages a static fixed-sized stack of trampolines
per call site, imposing a hidden limit on recursion and foregoing
reentrancy;
PGI passes host instance links in descriptors in additional arguments
that are not always successfully forwarded across implicit interfaces,
sometimes leading to crashes when they turn out to be needed.
F18 will manage a pool of trampolines in its runtime support library
that can be used to pass internal procedures as effective arguments
to F77ish procedures, so that
a bare code address can serve to represent the effective argument.
But targets that can only be called with an explicit interface
have the option of using a "fat pointer" (or additional argument)
to represent a dummy procedure closure so as
to avoid the overhead of constructing and reclaiming a trampoline.
Procedure descriptors can also support multiple code addresses.
### Naming
External subroutines and functions (R503) and `ENTRY` points (R1541)
with `BIND(C)` (R808) have linker-visible names that are either explicitly
specified in the program or determined by straightforward rules.
The names of other F77ish external procedures should respect the conventions
of the target architecture for legacy Fortran '77 programs; this is typically
something like `foo_`.
In other cases, however, we have fewer constraints on external naming,
as well as some additional requirements and goals.
Module procedures need to be distinguished by the name of their module
and (when they have one) the submodule where their interface was
defined.
Note that submodule names are distinct in their modules, not hierarchical,
so at most two levels of qualification are needed.
Pure `ELEMENTAL` functions (15.8) must use distinct names for any alternate
entry points used for packed SIMD arguments of various widths if we support
calls to these functions in SIMD parallel contexts.
There are already conventions for these names in `libpgmath`.
The names of non-F77ish external procedures
should be distinguished as such so that incorrect attempts to call or pass
them with an implicit interface will fail to resolve at link time.
Fortran 2018 explicitly enables us to do this with a correction to Fortran
2003 in 4.3.4(5).
Last, there must be reasonably permanent naming conventions used
by the F18 runtime library for those unrestricted specific intrinsic
functions (table 16.2 in 16.8) and extensions that can be passed as
arguments.
In these cases where external naming is at the discretion
of the implementation, we should use names that are not in the C language
user namespace, begin with something that identifies
the current incompatible version of F18, the module, the submodule, and
elemental SIMD width, and are followed by the external name.
The parts of the external name can be separated by some character that
is acceptable for use in LLVM IR and assembly language but not in user
Fortran or C code, or by switching case
(so long as there's a way to cope with extension names that don't begin
with letters).
In particular, the period (`.`) seems safe to use as a separator character,
so a `Fa.` prefix can serve to isolate these discretionary names from
other uses and to identify the earliest link-compatible version.
For examples: `Fa.mod.foo`, `Fa.mod.submod.foo`, and (for an external
subprogram that requires an explicit interface) `Fa.foo`.
When the ABI changes in the future in an incompatible way, the
initial prefix becomes `Fb.`, `Fc.`, &c.
## Summary of checks to be enforced in semantics analysis
8.5.10 `INTENT` attributes
* (C846) An `INTENT(OUT)` argument shall not be associated with an
object that is or has an allocatable coarray.
* (C847) An `INTENT(OUT)` argument shall not have `LOCK_TYPE` or `EVENT_TYPE`.
8.5.18 `VALUE` attribute
* (C863) The argument cannot be assumed-size, a coarray, or have a coarray
ultimate component.
* (C864) The argument cannot be `ALLOCATABLE`, `POINTER`, `INTENT(OUT)`,
`INTENT(IN OUT)`, or `VOLATILE`.
* (C865) If the procedure is `BIND(C)`, the argument cannot be `OPTIONAL`.
15.5.1 procedure references:
* (C1533) can't pass non-intrinsic `ELEMENTAL` as argument
* (C1536) alternate return labels must be in the inclusive scope
* (C1537) coindexed argument cannot have a `POINTER` ultimate component
15.5.2.4 requirements for non-`POINTER` non-`ALLOCATABLE` dummies:
* (2) dummy must be monomorphic for coindexed polymorphic actual
* (2) dummy must be polymorphic for assumed-size polymorphic actual
* (2) dummy cannot be `TYPE(*)` if effective is PDT or has TBPs or `FINAL`
* (4) character length of effective cannot be less than dummy
* (6) coindexed effective with `ALLOCATABLE` ultimate component requires
`INTENT(IN)` &/or `VALUE` dummy
* (13) a coindexed scalar effective requires a scalar dummy
* (14) a non-conindexed scalar effective usually requires a scalar dummy,
but there are some exceptions that allow elements of storage sequences
to be passed and treated like explicit-shape or assumed-size arrays
(see 15.5.2.11)
* (16) array rank agreement
* (20) `INTENT(OUT)` & `INTENT(IN OUT)` dummies require definable actuals
* (21) array sections with vector subscripts can't be passed to definable dummies
(`INTENT(OUT)`, `INTENT(IN OUT)`, `ASYNCHRONOUS`, `VOLATILE`)
* (22) `VOLATILE` attributes must match when dummy has a coarray ultimate component
* (C1538 - C1540) checks for `ASYNCHRONOUS` and `VOLATILE`
15.5.2.5 requirements for `ALLOCATABLE` & `POINTER` arguments when both
the dummy and effective arguments have the same attributes:
* (2) both or neither can be polymorphic
* (2) both are unlimited polymorphic or both have the same declared type
* (3) rank compatibility
* (4) effective argument must have deferred the same type parameters as the dummy
15.5.2.6 `ALLOCATABLE` dummy arguments:
* (2) effective must be `ALLOCATABLE`
* (3) corank must match
* (4) coindexed effective requires `INTENT(IN)` dummy
* (7) `INTENT(OUT)` & `INTENT(IN OUT)` dummies require definable actuals
15.5.2.7 `POINTER` dummy arguments:
* (C1541) `CONTIGUOUS` dummy requires simply contiguous actual
* (C1542) effective argument cannot be coindexed unless procedure is intrinsic
* (2) effective argument must be `POINTER` unless dummy is `INTENT(IN)` and
effective could be the right-hand side of a pointer assignment statement
15.5.2.8 corray dummy arguments:
* (1) effective argument must be coarray
* (1) `VOLATILE` attributes must match
* (2) explicitly or implicitly contiguous dummy array requires a simply contiguous actual
15.5.2.9 dummy procedures:
* (1) explicit dummy procedure interface must have same characteristics as actual
* (5) dummy procedure `POINTER` requirements on effective arguments
15.6.2.1 procedure definitions:
* `NON_RECURSIVE` procedures cannot recurse.
* Assumed-length `CHARACTER(*)` functions cannot be declared as `RECURSIVE`, array-valued,
`POINTER`, `ELEMENTAL`, or `PURE' (C723), and cannot be called recursively (15.6.2.1(3)).
* (C823) A function result cannot be a coarray or contain a coarray ultimate component.
`PURE` requirements (15.7): C1583 - C1599.
These also apply to `ELEMENTAL` procedures that are not `IMPURE`.
`ELEMENTAL` requirements (15.8.1): C15100-C15103,
and C1533 (can't pass as effective argument unless intrinsic)
For interoperable procedures and interfaces (18.3.6):
* C1552 - C1559
* function result is scalar and of interoperable type (C1553, 18.3.1-3)
* `VALUE` arguments are scalar and of interoperable type
* `POINTER` dummies cannot be `CONTIGUOUS` (18.3.6 paragraph 2(5))
* assumed-type dummies cannot be `ALLOCATABLE`, `POINTER`, assumed-shape, or assumed-rank (18.3.6 paragraph 2 (5))
* `CHARACTER` dummies that are `ALLOCATABLE` or `POINTER` must be deferred-length
## Further topics to document
* Alternate return specifiers
* `%VAL()`, `%REF()`, and `%DESCR()` legacy VMS interoperability extensions
* Unrestricted specific intrinsic functions as effective arguments
* SIMD variants of `ELEMENTAL` procedures (& unrestricted specific intrinsics)
* Elemental subroutine calls with array arguments

View File

@ -0,0 +1,147 @@
<!--===- documentation/Character.md
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
-->
## Implementation of `CHARACTER` types in f18
### Kinds and Character Sets
The f18 compiler and runtime support three kinds of the intrinsic
`CHARACTER` type of Fortran 2018.
The default (`CHARACTER(KIND=1)`) holds 8-bit character codes;
`CHARACTER(KIND=2)` holds 16-bit character codes;
and `CHARACTER(KIND=4)` holds 32-bit character codes.
We assume that code values 0 through 127 correspond to
the 7-bit ASCII character set (ISO-646) in every kind of `CHARACTER`.
This is a valid assumption for Unicode (UCS == ISO/IEC-10646),
ISO-8859, and many legacy character sets and interchange formats.
`CHARACTER` data in memory and unformatted files are not in an
interchange representation (like UTF-8, Shift-JIS, EUC-JP, or a JIS X).
Each character's code in memory occupies a 1-, 2-, or 4- byte
word and substrings can be indexed with simple arithmetic.
In formatted I/O, however, `CHARACTER` data may be assumed to use
the UTF-8 variable-length encoding when it is selected with
`OPEN(ENCODING='UTF-8')`.
`CHARACTER(KIND=1)` literal constants in Fortran source files,
Hollerith constants, and formatted I/O with `ENCODING='DEFAULT'`
are not translated.
For the purposes of non-default-kind `CHARACTER` constants in Fortran
source files, formatted I/O with `ENCODING='UTF-8'` or non-default-kind
`CHARACTER` value, and conversions between kinds of `CHARACTER`,
by default:
* `CHARACTER(KIND=1)` is assumed to be ISO-8859-1 (Latin-1),
* `CHARACTER(KIND=2)` is assumed to be UCS-2 (16-bit Unicode), and
* `CHARACTER(KIND=4)` is assumed to be UCS-4 (full Unicode in a 32-bit word).
In particular, conversions between kinds are assumed to be
simple zero-extensions or truncation, not table look-ups.
We might want to support one or more environment variables to change these
assumptions, especially for `KIND=1` users of ISO-8859 character sets
besides Latin-1.
### Lengths
Allocatable `CHARACTER` objects in Fortran may defer the specification
of their lengths until the time of their allocation or whole (non-substring)
assignment.
Non-allocatable objects (and non-deferred-length allocatables) have
lengths that are fixed or assumed from an actual argument, or,
in the case of assumed-length `CHARACTER` functions, their local
declaration in the calling scope.
The elements of `CHARACTER` arrays have the same length.
Assignments to targets that are not deferred-length allocatables will
truncate or pad the assigned value to the length of the left-hand side
of the assignment.
Lengths and offsets that are used by or exposed to Fortran programs via
declarations, substring bounds, and the `LEN()` intrinsic function are always
represented in units of characters, not bytes.
In generated code, assumed-length arguments, the runtime support library,
and in the `elem_len` field of the interoperable descriptor `cdesc_t`,
lengths are always in units of bytes.
The distinction matters only for kinds other than the default.
Fortran substrings are rather like subscript triplets into a hidden
"zero" dimension of a scalar `CHARACTER` value, but they cannot have
strides.
### Concatenation
Fortran has one `CHARACTER`-valued intrinsic operator, `//`, which
concatenates its operands (10.1.5.3).
The operands must have the same kind type parameter.
One or both of the operands may be arrays; if both are arrays, their
shapes must be identical.
The effective length of the result is the sum of the lengths of the
operands.
Parentheses may be ignored, so any `CHARACTER`-valued expression
may be "flattened" into a single sequence of concatenations.
The result of `//` may be used
* as an operand to another concatenation,
* as an operand of a `CHARACTER` relation,
* as an actual argument,
* as the right-hand side of an assignment,
* as the `SOURCE=` or `MOLD=` of an `ALLOCATE` statemnt,
* as the selector or case-expr of an `ASSOCIATE` or `SELECT` construct,
* as a component of a structure or array constructor,
* as the value of a named constant or initializer,
* as the `NAME=` of a `BIND(C)` attribute,
* as the stop-code of a `STOP` statement,
* as the value of a specifier of an I/O statement,
* or as the value of a statement function.
The f18 compiler has a general (but slow) means of implementing concatenation
and a specialized (fast) option to optimize the most common case.
#### General concatenation
In the most general case, the f18 compiler's generated code and
runtime support library represent the result as a deferred-length allocatable
`CHARACTER` temporary scalar or array variable that is initialized
as a zero-length array by `AllocatableInitCharacter()`
and then progressively augmented in place by the values of each of the
operands of the concatenation sequence in turn with calls to
`CharacterConcatenate()`.
Conformability errors are fatal -- Fortran has no means by which a program
may recover from them.
The result is then used as any other deferred-length allocatable
array or scalar would be, and finally deallocated like any other
allocatable.
The runtime routine `CharacterAssign()` takes care of
truncating, padding, or replicating the value(s) assigned to the left-hand
side, as well as reallocating an nonconforming or deferred-length allocatable
left-hand side. It takes the descriptors of the left- and right-hand sides of
a `CHARACTER` assignemnt as its arguments.
When the left-hand side of a `CHARACTER` assignment is a deferred-length
allocatable and the right-hand side is a temporary, use of the runtime's
`MoveAlloc()` subroutine instead can save an allocation and a copy.
#### Optimized concatenation
Scalar `CHARACTER(KIND=1)` expressions evaluated as the right-hand sides of
assignments to independent substrings or whole variables that are not
deferred-length allocatables can be optimized into a sequence of
calls to the runtime support library that do not allocate temporary
memory.
The routine `CharacterAppend()` copies data from the right-hand side value
to the remaining space, if any, in the left-hand side object, and returns
the new offset of the reduced remaining space.
It is essentially `memcpy(lhs + offset, rhs, min(lhsLength - offset, rhsLength))`.
It does nothing when `offset > lhsLength`.
`void CharacterPad()`adds any necessary trailing blank characters.

View File

@ -0,0 +1,161 @@
<!--===- documentation/ControlFlowGraph.md
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
-->
## Concept
After a Fortran subprogram has been parsed, its names resolved, and all its
semantic constraints successfully checked, the parse tree of its
executable part is translated into another abstract representation,
namely the _control flow graph_ described in this note.
This second representation of the subprogram's executable part is
suitable for analysis and incremental modification as the subprogram
is readied for code generation.
Many high-level Fortran features are implemented by rewriting portions
of a subprogram's control flow graph in place.
### Control Flow Graph
A _control flow graph_ is a collection of simple (_i.e.,_ "non-extended")
basic _blocks_ that comprise straight-line sequences of _actions_ with a
single entry point and a single exit point, and a collection of
directed flow _edges_ (or _arcs_) denoting all possible transitions of
control flow that may take place during execution from the end of
one basic block to the beginning of another (or itself).
A block that has multiple distinct successors in the flow of control
must end with an action that selects its successor.
The sequence of actions that constitutes a basic block may
include references to user and library procedures.
Subprogram calls with implicit control flow afterwards, namely
alternate returns and `END=`/`ERR=` labels on input/output,
will be lowered in translation to a representation that materializes
that control flow into something similar to a computed `GO TO` or
C language `switch` statement.
For convenience in optimization and to simplify the implementation of
data flow confluence functions, we may choose to maintain the
property that each flow arc is the sole outbound arc emanating from
its originating block, the sole inbound arc arriving at its destination,
or both.
Empty blocks would inserted to "split" arcs when necessary to maintain this
invariant property.
Fortran subprograms (other than internal subprograms) can have multiple
entry points by using the obsolescent `ENTRY` statement.
We will implement such subprograms by constructing a union
of their dummy argument lists and using it as part of the definition
of a new subroutine or function that can be called by each of
the entry points, which are then all converted into wrapper routines that
pass a selector value as an additional argument to drive a `switch` on entry
to the new subprogram.
This transformation ensures that every subprogram's control
flow graph has a well-defined `START` node.
Statement labels can be used in Fortran on any statement, but only
the labels that decorate legal destinations of `GO TO` statements
need to be implemented in the control flow graph.
Specifically, non-executable statements like `DATA`, `NAMELIST`, and
`FORMAT` statements will be extracted into data initialization
records before or during the construction of the control flow
graph, and will survive only as synonyms for `CONTINUE`.
Nests of multiple labeled `DO` loops that terminate on the same
label will be have that label rewritten so that `GO TO` within
the loop nest will arrive at the copy that most closely nests
the context.
The Fortran standard does not require us to do this, but XLF
(at least) works this way.
### Expressions and Statements (Operations and Actions)
Expressions are trees, not DAGs, of intrinsic operations,
resolved function references, constant literals, and
data designators.
Expression nodes are represented in the compiler in a type-safe manner.
There is a distinct class or class template for every category of
intrinsic type, templatized over its supported kind type parameter values.
Operands are storage-owning indirections to other instances
of `Expression`, instances of constant values, and to representations
of data and function references.
These indirections are not nullable apart from the situation in which
the operands of an expression are being removed for use elsewhere before
the expression is destructed.
The ranks and the extents of the shapes of the results of expressions
are explicit for constant arrays and recoverable by analysis otherwise.
Parenthesized subexpressions are scrupulously preserved in accordance with
the Fortran standard.
The expression tree is meant to be a representation that is
as equally well suited for use in the symbol table (e.g., for
a bound of an explicit shape array) as it is for an action
in a basic block of the control flow graph (e.g., the right
hand side of an assignment statement).
Each basic block comprises a linear sequence of _actions_.
These are represented as a doubly-linked list so that insertion
and deletion can be done in constant time.
Only the last action in a basic block can represent a change
to the flow of control.
### Scope Transitions
Some of the various scopes of the symbol table are visible in the control flow
graph as `SCOPE ENTRY` and `SCOPE EXIT` actions.
`SCOPE ENTRY` actions are unique for their corresponding scopes,
while `SCOPE EXIT` actions need not be so.
It must be the case that
any flow of control within the subprogram will enter only scopes that are
not yet active, and exit only the most recently entered scope that has not
yet been deactivated; i.e., when modeled by a push-down stack that is
pushed by each traversal of a `SCOPE ENTRY` action,
the entries of the stack are always distinct, only the scope at
the top of the stack is ever popped by `SCOPE EXIT`, and the stack is empty
when the subprogram terminates.
Further, any references to resolved symbols must be to symbols whose scopes
are active.
The `DEALLOCATE` actions and calls to `FINAL` procedures implied by scoped
lifetimes will be explicit in the sequence of actions in the control flow
graph.
Parallel regions might be partially represented by scopes, or by explicit
operations similar to the scope entry and exit operations.
### Data Flow Representation
The subprogram text will be in static single assignment form by the time the
subprogram arrives at the bridge to the LLVM IR builder.
Merge points are actions at the heads of basic blocks whose operands
are definition points; definition points are actions at the ends of
basic blocks whose operands are expression trees (which may refer to
merge points).
### Rewriting Transformations
#### I/O
#### Dynamic allocation
#### Array constructors
#### Derived type initialization, deallocation, and finalization
The machinery behind the complicated semantics of Fortran's derived types
and `ALLOCATABLE` objects will be implemented in large part by the run time
support library.
#### Actual argument temporaries
#### Array assignments, `WHERE`, and `FORALL`
Array operations have shape.
`WHERE` masks have shape.
Their effects on array operations are by means of explicit `MASK` operands that
are part of array assignment operations.
#### Intrinsic function and subroutine calls

View File

@ -0,0 +1,14 @@
<!--===- documentation/Directives.md
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
-->
Compiler directives supported by F18
====================================
* `!dir$ fixed` and `!dir$ free` select Fortran source forms. Their effect
persists to the end of the current source file.
* `!dir$ ignore_tkr (tkr) var-list` omits checks on type, kind, and/or rank.

View File

@ -0,0 +1,190 @@
<!--===- documentation/Extensions.md
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
-->
As a general principle, this compiler will accept by default and
without complaint many legacy features, extensions to the standard
language, and features that have been deleted from the standard,
so long as the recognition of those features would not cause a
standard-conforming program to be rejected or misinterpreted.
Other non-standard features, which do conflict with the current
standard specification of the Fortran programming language, are
accepted if enabled by command-line options.
Intentional violations of the standard
======================================
* Scalar `INTEGER` actual argument expressions (not variables!)
are converted to the kinds of scalar `INTEGER` dummy arguments
when the interface is explicit and the kinds differ.
This conversion allows the results of the intrinsics like
`SIZE` that (as mentioned below) may return non-default
`INTEGER` results by default to be passed. A warning is
emitted when truncation is possible.
* We are not strict on the contents of `BLOCK DATA` subprograms
so long as they contain no executable code, no internal subprograms,
and allocate no storage outside a named `COMMON` block. (C1415)
Extensions, deletions, and legacy features supported by default
===============================================================
* Tabs in source
* `<>` as synonym for `.NE.` and `/=`
* `$` and `@` as legal characters in names
* Initialization in type declaration statements using `/values/`
* Kind specification with `*`, e.g. `REAL*4`
* `DOUBLE COMPLEX`
* Signed complex literal constants
* DEC `STRUCTURE`, `RECORD`, `UNION`, and `MAP`
* Structure field access with `.field`
* `BYTE` as synonym for `INTEGER(KIND=1)`
* Quad precision REAL literals with `Q`
* `X` prefix/suffix as synonym for `Z` on hexadecimal literals
* `B`, `O`, `Z`, and `X` accepted as suffixes as well as prefixes
* Triplets allowed in array constructors
* Old-style `PARAMETER pi=3.14` statement without parentheses
* `%LOC`, `%VAL`, and `%REF`
* Leading comma allowed before I/O item list
* Empty parentheses allowed in `PROGRAM P()`
* Missing parentheses allowed in `FUNCTION F`
* Cray based `POINTER(p,x)` and `LOC()` intrinsic (with `%LOC()` as
an alias)
* Arithmetic `IF`. (Which branch should NaN take? Fall through?)
* `ASSIGN` statement, assigned `GO TO`, and assigned format
* `PAUSE` statement
* Hollerith literals and edit descriptors
* `NAMELIST` allowed in the execution part
* Omitted colons on type declaration statements with attributes
* COMPLEX constructor expression, e.g. `(x+y,z)`
* `+` and `-` before all primary expressions, e.g. `x*-y`
* `.NOT. .NOT.` accepted
* `NAME=` as synonym for `FILE=`
* Data edit descriptors without width or other details
* `D` lines in fixed form as comments or debug code
* `CONVERT=` on the OPEN and INQUIRE statements
* `DISPOSE=` on the OPEN and INQUIRE statements
* Leading semicolons are ignored before any statement that
could have a label
* The character `&` in column 1 in fixed form source is a variant form
of continuation line.
* Character literals as elements of an array constructor without an explicit
type specifier need not have the same length; the longest literal determines
the length parameter of the implicit type, not the first.
* Outside a character literal, a comment after a continuation marker (&)
need not begin with a comment marker (!).
* Classic C-style /*comments*/ are skipped, so multi-language header
files are easier to write and use.
* $ and \ edit descriptors are supported in FORMAT to suppress newline
output on user prompts.
* REAL and DOUBLE PRECISION variable and bounds in DO loops
* Integer literals without explicit kind specifiers that are out of range
for the default kind of INTEGER are assumed to have the least larger kind
that can hold them, if one exists.
* BOZ literals can be used as INTEGER values in contexts where the type is
unambiguous: the right hand sides of assigments and initializations
of INTEGER entities, and as actual arguments to a few intrinsic functions
(ACHAR, BTEST, CHAR). But they cannot be used if the type would not
be known (e.g., `IAND(X'1',X'2')`).
* BOZ literals can also be used as REAL values in some contexts where the
type is unambiguous, such as initializations of REAL parameters.
* EQUIVALENCE of numeric and character sequences (a ubiquitous extension)
* Values for whole anonymous parent components in structure constructors
(e.g., `EXTENDEDTYPE(PARENTTYPE(1,2,3))` rather than `EXTENDEDTYPE(1,2,3)`
or `EXTENDEDTYPE(PARENTTYPE=PARENTTYPE(1,2,3))`).
* Some intrinsic functions are specified in the standard as requiring the
same type and kind for their arguments (viz., ATAN with two arguments,
ATAN2, DIM, HYPOT, MAX, MIN, MOD, and MODULO);
we allow distinct types to be used, promoting
the arguments as if they were operands to an intrinsic `+` operator,
and defining the result type accordingly.
* DOUBLE COMPLEX intrinsics DREAL, DCMPLX, DCONJG, and DIMAG.
* INT_PTR_KIND intrinsic returns the kind of c_intptr_t.
* Restricted specific conversion intrinsics FLOAT, SNGL, IDINT, IFIX, DREAL,
and DCMPLX accept arguments of any kind instead of only the default kind or
double precision kind. Their result kinds remain as specified.
* Specific intrinsics AMAX0, AMAX1, AMIN0, AMIN1, DMAX1, DMIN1, MAX0, MAX1,
MIN0, and MIN1 accept more argument types than specified. They are replaced by
the related generics followed by conversions to the specified result types.
* When a scalar CHARACTER actual argument of the same kind is known to
have a length shorter than the associated dummy argument, it is extended
on the right with blanks, similar to assignment.
* When a dummy argument is `POINTER` or `ALLOCATABLE` and is `INTENT(IN)`, we
relax enforcement of some requirements on actual arguments that must otherwise
hold true for definable arguments.
* Assignment of `LOGICAL` to `INTEGER` and vice versa (but not other types) is
allowed. The values are normalized.
* An effectively empty source file (no program unit) is accepted and
produces an empty relocatable output file.
* A `RETURN` statement may appear in a main program.
Extensions supported when enabled by options
--------------------------------------------
* C-style backslash escape sequences in quoted CHARACTER literals
(but not Hollerith) [-fbackslash]
* Logical abbreviations `.T.`, `.F.`, `.N.`, `.A.`, `.O.`, and `.X.`
[-flogical-abbreviations]
* `.XOR.` as a synonym for `.NEQV.` [-fxor-operator]
* The default `INTEGER` type is required by the standard to occupy
the same amount of storage as the default `REAL` type. Default
`REAL` is of course 32-bit IEEE-754 floating-point today. This legacy
rule imposes an artificially small constraint in some cases
where Fortran mandates that something have the default `INTEGER`
type: specifically, the results of references to the intrinsic functions
`SIZE`, `LBOUND`, `UBOUND`, `SHAPE`, and the location reductions
`FINDLOC`, `MAXLOC`, and `MINLOC` in the absence of an explicit
`KIND=` actual argument. We return `INTEGER(KIND=8)` by default in
these cases when the `-flarge-sizes` option is enabled.
Extensions and legacy features deliberately not supported
---------------------------------------------------------
* `.LG.` as synonym for `.NE.`
* `REDIMENSION`
* Allocatable `COMMON`
* Expressions in formats
* `ACCEPT` as synonym for `READ *`
* `TYPE` as synonym for `PRINT`
* `ARRAY` as synonym for `DIMENSION`
* `VIRTUAL` as synonym for `DIMENSION`
* `ENCODE` and `DECODE` as synonyms for internal I/O
* `IMPLICIT AUTOMATIC`, `IMPLICIT STATIC`
* Default exponent of zero, e.g. `3.14159E`
* Characters in defined operators that are neither letters nor digits
* `B` suffix on unquoted octal constants
* `Z` prefix on unquoted hexadecimal constants (dangerous)
* `T` and `F` as abbreviations for `.TRUE.` and `.FALSE.` in DATA (PGI/XLF)
* Use of host FORMAT labels in internal subprograms (PGI-only feature)
* ALLOCATE(TYPE(derived)::...) as variant of correct ALLOCATE(derived::...) (PGI only)
* Defining an explicit interface for a subprogram within itself (PGI only)
* USE association of a procedure interface within that same procedure's definition
* NULL() as a structure constructor expression for an ALLOCATABLE component (PGI).
* Conversion of LOGICAL to INTEGER in expressions.
* IF (integer expression) THEN ... END IF (PGI/Intel)
* Comparsion of LOGICAL with ==/.EQ. rather than .EQV. (also .NEQV.) (PGI/Intel)
* Procedure pointers in COMMON blocks (PGI/Intel)
* Underindexing multi-dimensional arrays (e.g., A(1) rather than A(1,1)) (PGI only)
* Legacy PGI `NCHARACTER` type and `NC` Kanji character literals
* Using non-integer expressions for array bounds (e.g., REAL A(3.14159)) (PGI/Intel)
* Mixing INTEGER types as operands to bit intrinsics (e.g., IAND); only two
compilers support it, and they disagree on sign extension.
* Module & program names that conflict with an object inside the unit (PGI only).
* When the same name is brought into scope via USE association from
multiple modules, the name must refer to a generic interface; PGI
allows a name to be a procedure from one module and a generic interface
from another.
* Type parameter declarations must come first in a derived type definition;
some compilers allow them to follow `PRIVATE`, or be intermixed with the
component declarations.
* Wrong argument types in calls to specific intrinsics that have different names than the
related generics. Some accepted exceptions are listed above in the allowed extensions.
PGI, Intel, and XLF support this in ways that are not numerically equivalent.
PGI converts the arguments while Intel and XLF replace the specific by the related generic.
Preprocessing behavior
======================
* The preprocessor is always run, whatever the filename extension may be.
* We respect Fortran comments in macro actual arguments (like GNU, Intel, NAG;
unlike PGI and XLF) on the principle that macro calls should be treated
like function references. Fortran's line continuation methods also work.

View File

@ -0,0 +1,365 @@
<!--===- documentation/FortranForCProgrammers.md
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
-->
Fortran For C Programmers
=========================
This note is limited to essential information about Fortran so that
a C or C++ programmer can get started more quickly with the language,
at least as a reader, and avoid some common pitfalls when starting
to write or modify Fortran code.
Please see other sources to learn about Fortran's rich history,
current applications, and modern best practices in new code.
Know This At Least
------------------
* There have been many implementations of Fortran, often from competing
vendors, and the standard language has been defined by U.S. and
international standards organizations. The various editions of
the standard are known as the '66, '77, '90, '95, 2003, 2008, and
(now) 2018 standards.
* Forward compatibility is important. Fortran has outlasted many
generations of computer systems hardware and software. Standard
compliance notwithstanding, Fortran programmers generally expect that
code that has compiled successfully in the past will continue to
compile and work indefinitely. The standards sometimes designate
features as being deprecated, obsolescent, or even deleted, but that
can be read only as discouraging their use in new code -- they'll
probably always work in any serious implementation.
* Fortran has two source forms, which are typically distinguished by
filename suffixes. `foo.f` is old-style "fixed-form" source, and
`foo.f90` is new-style "free-form" source. All language features
are available in both source forms. Neither form has reserved words
in the sense that C does. Spaces are not required between tokens
in fixed form, and case is not significant in either form.
* Variable declarations are optional by default. Variables whose
names begin with the letters `I` through `N` are implicitly
`INTEGER`, and others are implicitly `REAL`. These implicit typing
rules can be changed in the source.
* Fortran uses parentheses in both array references and function calls.
All arrays must be declared as such; other names followed by parenthesized
expressions are assumed to be function calls.
* Fortran has a _lot_ of built-in "intrinsic" functions. They are always
available without a need to declare or import them. Their names reflect
the implicit typing rules, so you will encounter names that have been
modified so that they have the right type (e.g., `AIMAG` has a leading `A`
so that it's `REAL` rather than `INTEGER`).
* The modern language has means for declaring types, data, and subprogram
interfaces in compiled "modules", as well as legacy mechanisms for
sharing data and interconnecting subprograms.
A Rosetta Stone
---------------
Fortran's language standard and other documentation uses some terminology
in particular ways that might be unfamiliar.
| Fortran | English |
| ------- | ------- |
| Association | Making a name refer to something else |
| Assumed | Some attribute of an argument or interface that is not known until a call is made |
| Companion processor | A C compiler |
| Component | Class member |
| Deferred | Some attribute of a variable that is not known until an allocation or assignment |
| Derived type | C++ class |
| Dummy argument | C++ reference argument |
| Final procedure | C++ destructor |
| Generic | Overloaded function, resolved by actual arguments |
| Host procedure | The subprogram that contains a nested one |
| Implied DO | There's a loop inside a statement |
| Interface | Prototype |
| Internal I/O | `sscanf` and `snprintf` |
| Intrinsic | Built-in type or function |
| Polymorphic | Dynamically typed |
| Processor | Fortran compiler |
| Rank | Number of dimensions that an array has |
| `SAVE` attribute | Statically allocated |
| Type-bound procedure | Kind of a C++ member function but not really |
| Unformatted | Raw binary |
Data Types
----------
There are five built-in ("intrinsic") types: `INTEGER`, `REAL`, `COMPLEX`,
`LOGICAL`, and `CHARACTER`.
They are parameterized with "kind" values, which should be treated as
non-portable integer codes, although in practice today these are the
byte sizes of the data.
(For `COMPLEX`, the kind type parameter value is the byte size of one of the
two `REAL` components, or half of the total size.)
The legacy `DOUBLE PRECISION` intrinsic type is an alias for a kind of `REAL`
that should be bigger than the default `REAL`.
`COMPLEX` is a simple structure that comprises two `REAL` components.
`CHARACTER` data also have length, which may or may not be known at compilation
time.
`CHARACTER` variables are fixed-length strings and they get padded out
with space characters when not completely assigned.
User-defined ("derived") data types can be synthesized from the intrinsic
types and from previously-defined user types, much like a C `struct`.
Derived types can be parameterized with integer values that either have
to be constant at compilation time ("kind" parameters) or deferred to
execution ("len" parameters).
Derived types can inherit ("extend") from at most one other derived type.
They can have user-defined destructors (`FINAL` procedures).
They can specify default initial values for their components.
With some work, one can also specify a general constructor function,
since Fortran allows a generic interface to have the same name as that
of a derived type.
Last, there are "typeless" binary constants that can be used in a few
situations, like static data initialization or immediate conversion,
where type is not necessary.
Arrays
------
Arrays are not types in Fortran.
Being an array is a property of an object or function, not of a type.
Unlike C, one cannot have an array of arrays or an array of pointers,
although can can have an array of a derived type that has arrays or
pointers as components.
Arrays are multidimensional, and the number of dimensions is called
the _rank_ of the array.
In storage, arrays are stored such that the last subscript has the
largest stride in memory, e.g. A(1,1) is followed by A(2,1), not A(1,2).
And yes, the default lower bound on each dimension is 1, not 0.
Expressions can manipulate arrays as multidimensional values, and
the compiler will create the necessary loops.
Allocatables
------------
Modern Fortran programs use `ALLOCATABLE` data extensively.
Such variables and derived type components are allocated dynamically.
They are automatically deallocated when they go out of scope, much
like C++'s `std::vector<>` class template instances are.
The array bounds, derived type `LEN` parameters, and even the
type of an allocatable can all be deferred to run time.
(If you really want to learn all about modern Fortran, I suggest
that you study everything that can be done with `ALLOCATABLE` data,
and follow up all the references that are made in the documentation
from the description of `ALLOCATABLE` to other topics; it's a feature
that interacts with much of the rest of the language.)
I/O
---
Fortran's input/output features are built into the syntax of the language,
rather than being defined by library interfaces as in C and C++.
There are means for raw binary I/O and for "formatted" transfers to
character representations.
There are means for random-access I/O using fixed-size records as well as for
sequential I/O.
One can scan data from or format data into `CHARACTER` variables via
"internal" formatted I/O.
I/O from and to files uses a scheme of integer "unit" numbers that is
similar to the open file descriptors of UNIX; i.e., one opens a file
and assigns it a unit number, then uses that unit number in subsequent
`READ` and `WRITE` statements.
Formatted I/O relies on format specifications to map values to fields of
characters, similar to the format strings used with C's `printf` family
of standard library functions.
These format specifications can appear in `FORMAT` statements and
be referenced by their labels, in character literals directly in I/O
statements, or in character variables.
One can also use compiler-generated formatting in "list-directed" I/O,
in which the compiler derives reasonable default formats based on
data types.
Subprograms
-----------
Fortran has both `FUNCTION` and `SUBROUTINE` subprograms.
They share the same name space, but functions cannot be called as
subroutines or vice versa.
Subroutines are called with the `CALL` statement, while functions are
invoked with function references in expressions.
There is one level of subprogram nesting.
A function, subroutine, or main program can have functions and subroutines
nested within it, but these "internal" procedures cannot themselves have
their own internal procedures.
As is the case with C++ lambda expressions, internal procedures can
reference names from their host subprograms.
Modules
-------
Modern Fortran has good support for separate compilation and namespace
management.
The *module* is the basic unit of compilation, although independent
subprograms still exist, of course, as well as the main program.
Modules define types, constants, interfaces, and nested
subprograms.
Objects from a module are made available for use in other compilation
units via the `USE` statement, which has options for limiting the objects
that are made available as well as for renaming them.
All references to objects in modules are done with direct names or
aliases that have been added to the local scope, as Fortran has no means
of qualifying references with module names.
Arguments
---------
Functions and subroutines have "dummy" arguments that are dynamically
associated with actual arguments during calls.
Essentially, all argument passing in Fortran is by reference, not value.
One may restrict access to argument data by declaring that dummy
arguments have `INTENT(IN)`, but that corresponds to the use of
a `const` reference in C++ and does not imply that the data are
copied; use `VALUE` for that.
When it is not possible to pass a reference to an object, or a sparse
regular array section of an object, as an actual argument, Fortran
compilers must allocate temporary space to hold the actual argument
across the call.
This is always guaranteed to happen when an actual argument is enclosed
in parentheses.
The compiler is free to assume that any aliasing between dummy arguments
and other data is safe.
In other words, if some object can be written to under one name, it's
never going to be read or written using some other name in that same
scope.
```
SUBROUTINE FOO(X,Y,Z)
X = 3.14159
Y = 2.1828
Z = 2 * X ! CAN BE FOLDED AT COMPILE TIME
END
```
This is the opposite of the assumptions under which a C or C++ compiler must
labor when trying to optimize code with pointers.
Overloading
-----------
Fortran supports a form of overloading via its interface feature.
By default, an interface is a means for specifying prototypes for a
set of subroutines and functions.
But when an interface is named, that name becomes a *generic* name
for its specific subprograms, and calls via the generic name are
mapped at compile time to one of the specific subprograms based
on the types, kinds, and ranks of the actual arguments.
A similar feature can be used for generic type-bound procedures.
This feature can be used to overload the built-in operators and some
I/O statements, too.
Polymorphism
------------
Fortran code can be written to accept data of some derived type or
any extension thereof using `CLASS`, deferring the actual type to
execution, rather than the usual `TYPE` syntax.
This is somewhat similar to the use of `virtual` functions in c++.
Fortran's `SELECT TYPE` construct is used to distinguish between
possible specific types dynamically, when necessary. It's a
little like C++17's `std::visit()` on a discriminated union.
Pointers
--------
Pointers are objects in Fortran, not data types.
Pointers can point to data, arrays, and subprograms.
A pointer can only point to data that has the `TARGET` attribute.
Outside of the pointer assignment statement (`P=>X`) and some intrinsic
functions and cases with pointer dummy arguments, pointers are implicitly
dereferenced, and the use of their name is a reference to the data to which
they point instead.
Unlike C, a pointer cannot point to a pointer *per se*, nor can they be
used to implement a level of indirection to the management structure of
an allocatable.
If you assign to a Fortran pointer to make it point at another pointer,
you are making the pointer point to the data (if any) to which the other
pointer points.
Similarly, if you assign to a Fortran pointer to make it point to an allocatable,
you are making the pointer point to the current content of the allocatable,
not to the metadata that manages the allocatable.
Unlike allocatables, pointers do not deallocate their data when they go
out of scope.
A legacy feature, "Cray pointers", implements dynamic base addressing of
one variable using an address stored in another.
Preprocessing
-------------
There is no standard preprocessing feature, but every real Fortran implementation
has some support for passing Fortran source code through a variant of
the standard C source preprocessor.
Since Fortran is very different from C at the lexical level (e.g., line
continuations, Hollerith literals, no reserved words, fixed form), using
a stock modern C preprocessor on Fortran source can be difficult.
Preprocessing behavior varies across implementations and one should not depend on
much portability.
Preprocessing is typically requested by the use of a capitalized filename
suffix (e.g., "foo.F90") or a compiler command line option.
(Since the F18 compiler always runs its built-in preprocessing stage,
no special option or filename suffix is required.)
"Object Oriented" Programming
-----------------------------
Fortran doesn't have member functions (or subroutines) in the sense
that C++ does, in which a function has immediate access to the members
of a specific instance of a derived type.
But Fortran does have an analog to C++'s `this` via *type-bound
procedures*.
This is a means of binding a particular subprogram name to a derived
type, possibly with aliasing, in such a way that the subprogram can
be called as if it were a component of the type (e.g., `X%F(Y)`)
and receive the object to the left of the `%` as an additional actual argument,
exactly as if the call had been written `F(X,Y)`.
The object is passed as the first argument by default, but that can be
changed; indeed, the same specific subprogram can be used for multiple
type-bound procedures by choosing different dummy arguments to serve as
the passed object.
The equivalent of a `static` member function is also available by saying
that no argument is to be associated with the object via `NOPASS`.
There's a lot more that can be said about type-bound procedures (e.g., how they
support overloading) but this should be enough to get you started with
the most common usage.
Pitfalls
--------
Variable initializers, e.g. `INTEGER :: J=123`, are _static_ initializers!
They imply that the variable is stored in static storage, not on the stack,
and the initialized value lasts only until the variable is assigned.
One must use an assignment statement to implement a dynamic initializer
that will apply to every fresh instance of the variable.
Be especially careful when using initializers in the newish `BLOCK` construct,
which perpetuates the interpretation as static data.
(Derived type component initializers, however, do work as expected.)
If you see an assignment to an array that's never been declared as such,
it's probably a definition of a *statement function*, which is like
a parameterized macro definition, e.g. `A(X)=SQRT(X)**3`.
In the original Fortran language, this was the only means for user
function definitions.
Today, of course, one should use an external or internal function instead.
Fortran expressions don't bind exactly like C's do.
Watch out for exponentiation with `**`, which of course C lacks; it
binds more tightly than negation does (e.g., `-2**2` is -4),
and it binds to the right, unlike what any other Fortran and most
C operators do; e.g., `2**2**3` is 256, not 64.
Logical values must be compared with special logical equivalence
relations (`.EQV.` and `.NEQV.`) rather than the usual equality
operators.
A Fortran compiler is allowed to short-circuit expression evaluation,
but not required to do so.
If one needs to protect a use of an `OPTIONAL` argument or possibly
disassociated pointer, use an `IF` statement, not a logical `.AND.`
operation.
In fact, Fortran can remove function calls from expressions if their
values are not required to determine the value of the expression's
result; e.g., if there is a `PRINT` statement in function `F`, it
may or may not be executed by the assignment statement `X=0*F()`.
(Well, it probably will be, in practice, but compilers always reserve
the right to optimize better.)

View File

@ -0,0 +1,204 @@
<!--===- documentation/FortranIR.md
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
-->
# Design: Fortran IR
## Introduction
After semantic analysis is complete and it has been determined that the compiler has a legal Fortran program as input, the parse tree will be lowered to an intermediate representation for the purposes of high-level analysis and optimization. In this document, that intermediate representation will be called Fortran IR or FIR. The pass that converts from the parse tree and other data structures of the front-end to FIR will be called the "Burnside bridge".
FIR will be an explicit, operational, and strongly-typed representation, which shall encapsulate control-flow as graphs.
## Requirements
### White Paper: [Control Flow Graph](ControlFlowGraph.md)<sup>1</sup>
This is a list of requirements extracted from that document, which will be referred to as CFG-WP.
1. Control flow to be explicit (e.g. ERR= specifiers)
2. May need critical edge splitting
3. Lowering of procedures with ENTRY statements is specified
4. Procedures will have a start node
5. Non-executable statements will be ignored
6. Labeled DO loop execution with GOTO specified
7. Operations and actions (statements) are defined
8. The last statement in a basic block can represent a change in control flow
9. Scope transitions to be made explicit (special actions)
10. The IR will be in SSA form
### Explicit Control Flow
In Fortran, there are a number of statements that result in control flow to statements other than the one immediately subsequent. These can be sorted these into two categories: structured and unstructured.
#### Structured Control Flow
Fortran has executable constructs that imply three basic control flow forms. The first form is a structured loop (DO construct)<sup>2</sup>. The second form is a structured cascade of conditional branches (IF construct, IF statement,<sup>3</sup> WHERE construct). The third form is a structured multiway branch (SELECT CASE, SELECT RANK, and SELECT TYPE constructs). The FORALL construct, while it implies a semantic model of interleaved iterations, can be modeled as a special single-entry single-exit region in FIR perhaps with backstage marker statements.<sup>4</sup>
The CYCLE and EXIT statements interact with the above structured executable constructs by providing structured transfers of control.<sup>5</sup> CYCLE (possibly named) is only valid in DO constructs and creates an alternate backedge in the enclosing loop. EXIT transfers control out of the enclosing (possibly named) construct, which need not be a DO construct.
#### Unstructured Control Flow
Fortran also has mechanisms of transferring control between a statement and another statement with a corresponding label. The origin of these edges can be GOTO statements, computed GOTO statements, assigned GOTO statements, arithmetic IF statements, alt-return specifications, and END/EOR/ERR I/O specifiers. These statements are "unstructured" in the sense that the target of the control-flow has fewer constraints and the labelled statements must be linked to their origins.
Another category of unstructured control flow are statements that terminate execution. These include RETURN, FAIL IMAGE, STOP and ERROR STOP statements. The PAUSE statement can be modeled as a call to the runtime.
### Operations
The compiler's to be determined optimization passes will inform us as to the exact composition of FIR at the operations level. This details here will necessarily change, so please read them with a grain of salt.
The plan (see CFG-WP) is that statements (actions) will be a veneer model of Fortran syntactical executable constructs. Fortran statements will correspond one to one with actions. Actions will be composed of and own objects of Fortran::evaluate::GenericExprWrapper. Values of type GenericExprWrapper will have Fortran types. This implies that actions will not be in an explicit data flow representation and have optional type information.<sup>6</sup> Initially, values will bind to symbols in a context and have an implicit use-def relation. An action statement may entail a "big step" operation with many side-effects. No semantics has been defined at this time. Actions may reference other non-executable statements from the parse tree in some to be determined manner.
From the CFG-WP, it is stated that the FIR will ultimately be in an SSA form. It is clear that a later pass can rewrite the values/expressions and construct a factored use-def version of the expressions. This may/should also involve expanding "big step" actions to a series of instructions and introducing typing information for all instructions. Again, the exact "lowered representation" will be informed from the requirements of the optimization passes and is presently to be determined.
### Other
Overall project goals include becoming part of the LLVM ecosystem as well as using LLVM as a backend.
Critical edge splitting can be constructed on-demand and as needed.
Lowering of procedures with ENTRY statements is specified. The plan is to lower procedures with ENTRY statements as specified in the CFG-WP.
In FIR, a procedure will have a method that returns the start node.
When lowering to FIR statements, non-executable statements will be discarded.
Labeled DO loops are converted to non-labeled DO loops in the semantics processing.
The last statement in a basic block can represent a change in control flow. LLVM-IR and SIL<sup>7</sup> require that basic blocks end with a terminator. FIR will also have terminators.
The CFG-WP states that scope transitions are to be made explicit. We will cover this more below.
LLVM does not require the FIR to be in SSA form. LLVM's mem-to-reg pass does the conversion into SSA form. FIR can support SSA for optimization passes on-demand with its own mem-to-reg and reg-to-mem type passes.
Data objects with process lifetime will be captured indirectly by a reference to the (global) symbol table.
## Exploration
### Construction
Our aim to construct a CFG where all control-flow is explicitly modeled by relations. A basic block will be a sequence of statements for which if the first statement is executed then all other statements in the basic block will also be executed, in order.<sup>8</sup> A CFG is therefore this set of basic blocks and the control-flow relations between those blocks.
#### Alternative: direct approach
The CFG can be directly constructed by traversing the parse tree, threading contextual state, and building basic blocks along with control-flow relationships.
* Pro: Straightforward implementation when control-flow is well-structured as the contextual state parallels the syntax of the language closely.
* Con: The contextual state needed can become large and difficult to manage in the presence of unstructured control-flow. For example, not every labeled statement in Fortran may be a control-flow destination.
* Con: The contextual state must deal with the recursive nature of the parse tree.
* Con: Complexity. Since structured constructs cohabitate with unstructured constructs, the context needs to carry information about all combinations until the basic blocks and relations are fully elaborated.
#### Alternative: linearized approach (decomposing the problem)
Instead of constructing the CFG directly from a parse tree traversal, an intermediate form can be constructed to explicitly capture the executable statements, which ones give rise to control-flow graph edge sources, and which are control-flow graph edge targets. This linearized form flattens the tree structure of the parse tree. The linearized form does not require recursive visitation of nested constructs and can be used to directly identify the entries and exits of basic blocks.
While each control-flow source statement is explicit in the traversal, it can be the case that not all of the targets have been traversed yet (references to forward basic blocks), and those basic blocks will not yet have been created. These relations can be captured at the time the source is traversed, added to a to do list, and then completed when all the basic blocks for the procedure have been created. Specifically, at the point when we create a terminator all information is known to create the FIR terminator, however all basic blocks that may be referenced may not have been created. Those are resolved in one final "clean up" pass over a list of closures.
* Con: An extra representation must be defined and constructed.
* Pro: This representation reifies all the information that is referred to as contextual state in the direct approach.
* Pro: Constructing the linearized form can be done with a simple traversal of the parse tree.
* Pro: Once composed the linearized form can be traversed and a CFG directly constructed. This greatly reduces bookkeeping of contextual state.
### Details
#### Grappling with Control Flow
Above, various Fortran executable constructs were discussed with respect to how they (may) give rise to control flow. These Fortran statements are mapped to a small number of FIR statements: ReturnStmt, BranchStmt, SwitchStmt, IndirectBrStmt, and UnreachableStmt.
_ReturnStmt_: execution leaves the enclosing Procedure. A ReturnStmt can return an optional value. This would appear for RETURN statements or at END SUBROUTINE.
_BranchStmt_: execution of the current basic block ends. If the branch is unconditional then control transfers to exactly one successor basic block. If the branch is conditional then control transfers to exactly one of two successor blocks depending on the true/false value of the condition. All successors must be in the current Procedure. Unconditional branches would appear for GOTO statements. Conditional branches would appear for IF constructs, IF statements, etc.
_SwitchStmt_: Exactly one of multiple successors is selected based on the control expression. Successors are pairs of case expressions and basic blocks. If the control expression compares to the case expression and returns true, then that control transfers to that block. There may be one special block, the default block, that is selected if none of the case expressions compares true. This would appear for SELECT CASE, SELECT TYPE, SELECT RANK, COMPUTED GOTO, WRITE with exceptional condition label specificers, alternate return specifiers, etc.
_IndirectBrStmt_: A variable is loaded with the address of a basic block in the containing Procedure. Control is transferred to the contents of this variable. An IndirectBrStmt also requires a complete list of potential basic blocks that may be loaded into the variable. This would appear for ASSIGNED GOTO.
Supporting ASSIGNED GOTO offers a little extra challenge as the ASSIGN GOTO statement's list of target labels is optional. If that list is not present, then the procedure must be analyzed to find ASSIGN statements. The implementation proactively looks for ASSIGN statements and keeps a dictionary mapping an assigned Symbol to its set of targets. When constructing the CFG, ASSIGNED GOTOs can be processed as to potential targets either from the list provided in the ASSIGNED GOTO or from the analysis pass.
Alternatively, ASSIGNED GOTO could be implemented as a _SwitchStmt_ that tests on a compiler-defined value and fully elaborates all potential target basic blocks.
_UnreachableStmt_: If control reaches an unreachable statement, then an error has occurred. Calls to library routines that do not return should be followed by an UnreachableStmt. An example would be the STOP statement.
#### Scope
In the CFG-WP, scopes are meant to be captured by a pair of backstage statements for entering and exiting a particular scope. In structured code, these pairs would not be problematic; however, control flow in Fortran is ad hoc, particularly in legacy Fortran. In short, Fortran does not have a clean sense of structure with respect to scope.
To separate concerns, FIR will construct the ad hoc CFG and impose bounding boxes over regions of that graph to demarcate and superimpose scope structures on that CFG. Any GOTO-like statements that are side-entries and side-exits to the region will be explicit.
Once the basic blocks are constructed, CFG edges defined, and the CFG is simplified, a simple pass that analyzes the region bounding boxes can decorate the basic blocks with the SCOPE ENTER and SCOPE EXIT statements and flatten/remove the region structure. It will then be the burden of any optimization passes to guarantee legal orderings of SCOPE ENTER and SCOPE EXIT pairs.
* Pro: Separation of concerns allows for simpler, easier to maintain code
* Pro: Simplification of the CFG can be done without worrying about SCOPE markers
* Pro: Allows a precise superimposing of all Fortran constructs with scoping considerations over an otherwise ad hoc CFG.
* Con: Adds "an extra layer" to FIR as compared to SIL. However, that can be mitigated/made inconsequential by a pass that flattens the Region tree and inserts the backstage SCOPE marker statements.
#### Structure
_Program_: A program instance is the top-level object that contains the representation of all the code being compiled, the compilation unit. It contains a list of procedures and a reference to the global symbol table.
_Procedure_: This is a named Fortran procedure (subroutine or function). It contains a (hierarchical) list of regions. It also owns the master list of all basic blocks for the procedure.
_Region_: A region is owned by a procedure or by another region. A region owns a reference to a scope in the symbol table tree. The list of delineated basic blocks can also be requested from a region.
_Basic block_: A basic block is owned by a procedure. A basic block owns a list of statements. The last statement in the list must be a terminator, and no other statement in the list can be a terminator. A basic block owns a list of its predecessors, which are also basic blocks. (Precisely, it is this level of FIR that is the CFG.)
_Statement_: An executable Fortran construct that owns/refers to expressions, symbols, scopes, etc. produced by the front-end.
_Terminator_: A statement that orchestrates control-flow. Terminator statements may reference other basic blocks and can be accessed by their parent basic block to discover successor blocks, if any.
#### Support
Since there is some state that needs to be maintained and forwarded as the FIR is constructed, a FIRBuilder can be used for convenience. The FIRBuilder constructs statements and updates the CFG accordingly.
To support visualization, there is a support class to dump the FIR to a dotty graph.
### Data Structures
FIR is intentionally similar to SIL from the statement level up to the level of a program.
#### Alternative: LLVM
Program, procedure, region, and basic block all leverage code from LLVM, in much the same way as SIL. These data structures have significant investment and engineering behind their use in compilers, and it makes sense to leverage that work.
* Pro: Uses LLVM data structures, pervasive in compiler projects such as LLVM, SIL, etc.
* Pro: Get used to seeing and using LLVM, as f18 aims to be an LLVM project
* Con: Uses LLVM data structures, which the project has been avoiding
#### Alternative: C++ Standard Template Library
Clearly, the STL can be used to maintain lists, etc.
* Pro: Keeps the number of libraries minimal
* Con: The STL is general purpose and not necessarily tuned to support compiler construction
#### Alternative: Boost, Library XYZ, etc.
* Con: Don't see a strong motivation at present for adding another library.
Statements are a bit of a transition point. Instead of the LLVM-IR approach of strictly using subtype polymorphism (for hash consing, etc.), FIR statements are a hybrid between ad hoc/subtype polymorphism and parametric polymorphism. This gives us a middle ground of genericity through superclassing and the strong and exact type-safety of algebraic data types &mdash; effectively providing type casing and type classing.
The operations (expressions) owned/referenced by a statement, variable references, etc. will be data structures from the Fortran::evaluate, Fortran::semantics, etc. namespaces.
<hr>
<sup>1</sup> CFG paper. https://bit.ly/2q9IRaQ
<sup>2</sup> All labeled DO sequences will have been translated to DO constructs by semantic analysis.
<sup>3</sup> IF statements are handled like IF constructs with no ELSE alternatives.
<sup>4</sup> In a subsequent discussion, we may want to lower FORALL constructs to semantically distinct loops or even another canonical representation.
<sup>5</sup> These statements are only valid in structured constructs and the branches are well-defined by that executable construct.
<sup>6</sup> Unlike SIL and LLVM-IR.
<sup>7</sup> SIL is the Swift (high-level) intermediate language. https://bit.ly/2RHW0DQ
<sup>8</sup> Single-threaded semantics.

View File

@ -0,0 +1,342 @@
<!--===- documentation/IORuntimeInternals.md
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
-->
Fortran I/O Runtime Library Internal Design
===========================================
This note is meant to be an overview of the design of the *implementation*
of the f18 Fortran compiler's runtime support library for I/O statements.
The *interface* to the I/O runtime support library is defined in the
C++ header file `runtime/io-api.h`.
This interface was designed to minimize the amount of complexity exposed
to its clients, which are of course the sequences of calls generated by
the compiler to implement each I/O statement.
By keeping this interface as simple as possible, we hope that we have
lowered the risk of future incompatible changes that would necessitate
recompilation of Fortran codes in order to link with later versions of
the runtime library.
As one will see in `io-api.h`, the interface is also directly callable
from C and C++ programs.
The I/O facilities of the Fortran 2018 language are specified in the
language standard in its clauses 12 (I/O statements) and 13 (`FORMAT`).
It's a complicated collection of language features:
* Files can comprise *records* or *streams*.
* Records can be fixed-length or variable-length.
* Record files can be accessed sequentially or directly (random access).
* Files can be *formatted*, or *unformatted* raw bits.
* `CHARACTER` scalars and arrays can be used as if they were
fixed-length formatted sequential record files.
* Formatted I/O can be under control of a `FORMAT` statement
or `FMT=` specifier, *list-directed* with default formatting chosen
by the runtime, or `NAMELIST`, in which a collection of variables
can be given a name and passed as a group to the runtime library.
* Sequential records of a file can be partially processed by one
or more *non-advancing* I/O statements and eventually completed by
another.
* `FORMAT` strings can manipulate the position in the current
record arbitrarily, causing re-reading or overwriting.
* Floating-point output formatting supports more rounding modes
than the IEEE standard for floating-point arithmetic.
The Fortran I/O runtime support library is written in C++17, and
uses some C++17 standard library facilities, but it is intended
to not have any link-time dependences on the C++ runtime support
library or any LLVM libraries.
This is important because there are at least two C++ runtime support
libraries, and we don't want Fortran application builders to have to
build multiple versions of their codes; neither do we want to require
them to ship LLVM libraries along with their products.
Consequently, dynamic memory allocation in the Fortran runtime
uses only C's `malloc()` and `free()` functions, and the few
C++ standard class templates that we instantiate in the library have been
modified with optional template arguments that override their
allocators and deallocators.
Conversions between the many binary floating-point formats supported
by f18 and their decimal representations are performed with the same
template library of fast conversion algorithms used to interpret
floating-point values in Fortran source programs and to emit them
to module files.
Overview of Classes
===================
A suite of C++ classes and class templates are composed to construct
the Fortran I/O runtime support library.
They (mostly) reside in the C++ namespace `Fortran::runtime::io`.
They are summarized here in a bottom-up order of dependence.
The header and C++ implementation source file names of these
classes are in the process of being vigorously rearranged and
modified; use `grep` or an IDE to discover these classes in
the source for now. (Sorry!)
`Terminator`
----------
A general facility for the entire library, `Terminator` latches a
source program statement location in terms of an unowned pointer to
its source file path name and line number and uses them to construct
a fatal error message if needed.
It is used for both user program errors and internal runtime library crashes.
`IoErrorHandler`
--------------
When I/O error conditions arise at runtime that the Fortran program
might have the privilege to handle itself via `ERR=`, `END=`, or
`EOR=` labels and/or by an `IOSTAT=` variable, this subclass of
`Terminator` is used to either latch the error indication or to crash.
It sorts out priorities in the case of multiple errors and determines
the final `IOSTAT=` value at the end of an I/O statement.
`MutableModes`
------------
Fortran's formatted I/O statements are affected by a suite of
modes that can be configured by `OPEN` statements, overridden by
data transfer I/O statement control lists, and further overridden
between data items with control edit descriptors in a `FORMAT` string.
These modes are represented with a `MutableModes` instance, and these
are instantiated and copied where one would expect them to be in
order to properly isolate their modifications.
The modes in force at the time each data item is processed constitute
a member of each `DataEdit`.
`DataEdit`
--------
Represents a single data edit descriptor from a `FORMAT` statement
or `FMT=` character value, with some hidden extensions to also
support formatting of list-directed transfers.
It holds an instance of `MutableModes`, and also has a repetition
count for when an array appears as a data item in the *io-list*.
For simplicity and efficiency, each data edit descriptor is
encoded in the `DataEdit` as a simple capitalized character
(or two) and some optional field widths.
`FormatControl<>`
---------------
This class template traverses a `FORMAT` statement's contents (or `FMT=`
character value) to extract data edit descriptors like `E20.14` to
serve each item in an I/O data transfer statement's *io-list*,
making callbacks to an instance of its class template argument
along the way to effect character literal output and record
positioning.
The Fortran language standard defines formatted I/O as if the `FORMAT`
string were driving the traversal of the data items in the *io-list*,
but our implementation reverses that perspective to allow a more
convenient (for the compiler) I/O runtime support library API design
in which each data item is presented to the library with a distinct
type-dependent call.
Clients of `FormatControl` instantiations call its `GetNextDataEdit()`
member function to acquire the next data edit descriptor to be processed
from the format, and `FinishOutput()` to flush out any remaining
output strings or record positionings at the end of the *io-list*.
The `DefaultFormatControlCallbacks` structure summarizes the API
expected by `FormatControl` from its class template actual arguments.
`OpenFile`
--------
This class encapsulates all (I hope) the operating system interfaces
used to interact with the host's filesystems for operations on
external units.
Asynchronous I/O interfaces are faked for now with synchronous
operations and deferred results.
`ConnectionState`
---------------
An active connection to an external or internal unit maintains
the common parts of its state in this subclass of `ConnectionAttributes`.
The base class holds state that should not change during the
lifetime of the connection, while the subclass maintains state
that may change during I/O statement execution.
`InternalDescriptorUnit`
----------------------
When I/O is being performed from/to a Fortran `CHARACTER` array
rather than an external file, this class manages the standard
interoperable descriptor used to access its elements as records.
It has the necessary interfaces to serve as an actual argument
to the `FormatControl` class template.
`FileFrame<>`
-----------
This CRTP class template isolates all of the complexity involved between
an external unit's `OpenFile` and the buffering requirements
imposed by the capabilities of Fortran `FORMAT` control edit
descriptors that allow repositioning within the current record.
Its interface enables its clients to define a "frame" (my term,
not Fortran's) that is a contiguous range of bytes that are
or may soon be in the file.
This frame is defined as a file offset and a byte size.
The `FileFrame` instance manages an internal circular buffer
with two essential guarantees:
1. The most recently requested frame is present in the buffer
and contiguous in memory.
1. Any extra data after the frame that may have been read from
the external unit will be preserved, so that it's safe to
read from a socket, pipe, or tape and not have to worry about
repositioning and rereading.
In end-of-file situations, it's possible that a request to read
a frame may come up short.
As a CRTP class template, `FileFrame` accesses the raw filesystem
facilities it needs from `*this`.
`ExternalFileUnit`
----------------
This class mixes in `ConnectionState`, `OpenFile`, and
`FileFrame<ExternalFileUnit>` to represent the state of an open
(or soon to be opened) external file descriptor as a Fortran
I/O unit.
It has the contextual APIs required to serve as a template actual
argument to `FormatControl`.
And it contains a `std::variant<>` suitable for holding the
state of the active I/O statement in progress on the unit
(see below).
`ExternalFileUnit` instances reside in a `Map` that is allocated
as a static variable and indexed by Fortran unit number.
Static member functions `LookUp()`, `LookUpOrCrash()`, and `LookUpOrCreate()`
probe the map to convert Fortran `UNIT=` numbers from I/O statements
into references to active units.
`IoStatementBase`
---------------
The subclasses of `IoStatementBase` each encapsulate and maintain
the state of one active Fortran I/O statement across the several
I/O runtime library API function calls it may comprise.
The subclasses handle the distinctions between internal vs. external I/O,
formatted vs. list-directed vs. unformatted I/O, input vs. output,
and so on.
`IoStatementBase` inherits default `FORMAT` processing callbacks and
an `IoErrorHandler`.
Each of the `IoStatementBase` classes that pertain to formatted I/O
support the contextual callback interfaces needed by `FormatControl`,
overriding the default callbacks of the base class, which crash if
called inappropriately (e.g., if a `CLOSE` statement somehow
passes a data item from an *io-list*).
The lifetimes of these subclasses' instances each begin with a user
program call to an I/O API routine with a name like `BeginExternalListOutput()`
and persist until `EndIoStatement()` is called.
To reduce dynamic memory allocation, *external* I/O statements allocate
their per-statement state class instances in space reserved in the
`ExternalFileUnit` instance.
Internal I/O statements currently use dynamic allocation, but
the I/O API supports a means whereby the code generated for the Fortran
program may supply stack space to the I/O runtime support library
for this purpose.
`IoStatementState`
----------------
F18's Fortran I/O runtime support library defines and implements an API
that uses a sequence of function calls to implement each Fortran I/O
statement.
The state of each I/O statement in progress is maintained in some
subclass of `IoStatementBase`, as noted above.
The purpose of `IoStatementState` is to provide generic access
to the specific state classes without recourse to C++ `virtual`
functions or function pointers, language features that may not be
available to us in some important execution environments.
`IoStatementState` comprises a `std::variant<>` of wrapped references
to the various possibilities, and uses `std::visit()` to
access them as needed by the I/O API calls that process each specifier
in the I/O *control-list* and each item in the *io-list*.
Pointers to `IoStatementState` instances are the `Cookie` type returned
in the I/O API for `Begin...` I/O statement calls, passed back for
the *control-list* specifiers and *io-list* data items, and consumed
by the `EndIoStatement()` call at the end of the statement.
Storage for `IoStatementState` is reserved in `ExternalFileUnit` for
external I/O units, and in the various final subclasses for internal
I/O statement states otherwise.
Since Fortran permits a `CLOSE` statement to reference a nonexistent
unit, the library has to treat that (expected to be rare) situation
as a weird variation of internal I/O since there's no `ExternalFileUnit`
available to hold its `IoStatementBase` subclass or `IoStatementState`.
A Narrative Overview Of `PRINT *, 'HELLO, WORLD'`
=================================================
1. When the compiled Fortran program begins execution at the `main()`
entry point exported from its main program, it calls `ProgramStart()`
with its arguments and environment.
1. The generated code calls `BeginExternalListOutput()` to
start the sequence of calls that implement the `PRINT` statement.
Since the Fortran runtime I/O library has not yet been used in
this process, its data structures are initialized on this
first call, and Fortran I/O units 5 and 6 are connected with
the stadard input and output file descriptors (respectively).
The default unit code is converted to 6 and passed to
`ExternalFileUnit::LookUpOrCrash()`, which returns a reference to
unit 6's instance.
1. We check that the unit was opened for formatted I/O.
1. `ExternalFileUnit::BeginIoStatement<>()` is called to initialize
an instance of `ExternalListIoStatementState<false>` in the unit,
point to it with an `IoStatementState`, and return a reference to
that object whose address will be the `Cookie` for this statement.
1. The generated code calls `OutputAscii()` with that cookie and the
address and length of the string.
1. `OutputAscii()` confirms that the cookie corresponds to an output
statement and determines that it's list-directed.
1. `ListDirectedStatementState<false>::EmitLeadingSpaceOrAdvance()`
emits the required initial space on the new current output record
by calling `IoStatementState::GetConnectionState()` to locate
the connection state, determining from the record position state
that the space is necessary, and calling `IoStatementState::Emit()`
to cough it out. That call is redirected to `ExternalFileUnit::Emit()`,
which calls `FileFrame<ExternalFileUnit>::WriteFrame()` to extend
the frame of the current record and then `memcpy()` to fill its
first byte with the space.
1. Back in `OutputAscii()`, the mutable modes and connection state
of the `IoStatementState` are queried to see whether we're in an
`WRITE(UNIT=,FMT=,DELIM=)` statement with a delimited specifier.
If we were, the library would emit the appropriate quote marks,
double up any instances of that character in the text, and split the
text over multiple records if it's long.
1. But we don't have a delimiter, so `OutputAscii()` just carves
up the text into record-sized chunks and emits them. There's just
one chunk for our short `CHARACTER` string value in this example.
It's passed to `IoStatementState::Emit()`, which (as above) is
redirected to `ExternalFileUnit::Emit()`, which interacts with the
frame to extend the frame and `memcpy` data into the buffer.
1. A flag is set in `ListDirectedStatementState<false>` to remember
that the last item emitted in this list-directed output statement
was an undelimited `CHARACTER` value, so that if the next item is
also an undelimited `CHARACTER`, no interposing space will be emitted
between them.
1. `OutputAscii()` return `true` to its caller.
1. The generated code calls `EndIoStatement()`, which is redirected to
`ExternalIoStatementState<false>`'s override of that function.
As this is not a non-advancing I/O statement, `ExternalFileUnit::AdvanceRecord()`
is called to end the record. Since this is a sequential formatted
file, a newline is emitted.
1. If unit 6 is connected to a terminal, the buffer is flushed.
`FileFrame<ExternalFileUnit>::Flush()` drives `ExternalFileUnit::Write()`
to push out the data in maximal contiguous chunks, dealing with any
short writes that might occur, and collecting I/O errors along the way.
This statement has no `ERR=` label or `IOSTAT=` specifier, so errors
arriving at `IoErrorHandler::SignalErrno()` will cause an immediate
crash.
1. `ExternalIoStatementBase::EndIoStatement()` is called.
It gets the final `IOSTAT=` value from `IoStatementBase::EndIoStatement()`,
tells the `ExternalFileUnit` that no I/O statement remains active, and
returns the I/O status value back to the program.
1. Eventually, the program calls `ProgramEndStatement()`, which
calls `ExternalFileUnit::CloseAll()`, which flushes and closes all
open files. If the standard output were not a terminal, the output
would be written now with the same sequence of calls as above.
1. `exit(EXIT_SUCCESS)`.

View File

@ -0,0 +1,832 @@
<!--===- documentation/ImplementingASemanticCheck.md
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
-->
# Introduction
I recently added a semantic check to the f18 compiler front end. This document
describes my thought process and the resulting implementation.
For more information about the compiler, start with the
[compiler overview](Overview.md).
# Problem definition
In the 2018 Fortran standard, section 11.1.7.4.3, paragraph 2, states that:
```
Except for the incrementation of the DO variable that occurs in step (3), the DO variable
shall neither be redefined nor become undefined while the DO construct is active.
```
One of the ways that DO variables might be redefined is if they are passed to
functions with dummy arguments whose `INTENT` is `INTENT(OUT)` or
`INTENT(INOUT)`. I implemented this semantic check. Specifically, I changed
the compiler to emit an error message if an active DO variable was passed to a
dummy argument of a FUNCTION with INTENT(OUT). Similarly, I had the compiler
emit a warning if an active DO variable was passed to a dummy argument with
INTENT(INOUT). Previously, I had implemented similar checks for SUBROUTINE
calls.
# Creating a test
My first step was to create a test case to cause the problem. I called it testfun.f90 and used it to check the behavior of other Fortran compilers. Here's the initial version:
```fortran
subroutine s()
Integer :: ivar, jvar
do ivar = 1, 10
jvar = intentOutFunc(ivar) ! Error since ivar is a DO variable
end do
contains
function intentOutFunc(dummyArg)
integer, intent(out) :: dummyArg
integer :: intentOutFunc
dummyArg = 216
end function intentOutFunc
end subroutine s
```
I verified that other Fortran compilers produced an error message at the point
of the call to `intentOutFunc()`:
```fortran
jvar = intentOutFunc(ivar) ! Error since ivar is a DO variable
```
I also used this program to produce a parse tree for the program using the command:
```bash
f18 -fdebug-dump-parse-tree -fparse-only testfun.f90
```
Here's the relevant fragment of the parse tree produced by the compiler:
```
| | ExecutionPartConstruct -> ExecutableConstruct -> DoConstruct
| | | NonLabelDoStmt
| | | | LoopControl -> LoopBounds
| | | | | Scalar -> Name = 'ivar'
| | | | | Scalar -> Expr = '1_4'
| | | | | | LiteralConstant -> IntLiteralConstant = '1'
| | | | | Scalar -> Expr = '10_4'
| | | | | | LiteralConstant -> IntLiteralConstant = '10'
| | | Block
| | | | ExecutionPartConstruct -> ExecutableConstruct -> ActionStmt -> AssignmentStmt = 'jvar=intentoutfunc(ivar)'
| | | | | Variable -> Designator -> DataRef -> Name = 'jvar'
| | | | | Expr = 'intentoutfunc(ivar)'
| | | | | | FunctionReference -> Call
| | | | | | | ProcedureDesignator -> Name = 'intentoutfunc'
| | | | | | | ActualArgSpec
| | | | | | | | ActualArg -> Expr = 'ivar'
| | | | | | | | | Designator -> DataRef -> Name = 'ivar'
| | | EndDoStmt ->
```
Note that this fragment of the tree only shows four `parser::Expr` nodes,
but the full parse tree also contained a fifth `parser::Expr` node for the
constant 216 in the statement:
```fortran
dummyArg = 216
```
# Analysis and implementation planning
I then considered what I needed to do. I needed to detect situations where an
active DO variable was passed to a dummy argument with `INTENT(OUT)` or
`INTENT(INOUT)`. Once I detected such a situation, I needed to produce a
message that highlighted the erroneous source code.
## Deciding where to add the code to the compiler
This new semantic check would depend on several types of information -- the
parse tree, source code location information, symbols, and expressions. Thus I
needed to put my new code in a place in the compiler after the parse tree had
been created, name resolution had already happened, and expression semantic
checking had already taken place.
Most semantic checks for statements are implemented by walking the parse tree
and performing analysis on the nodes they visit. My plan was to use this
method. The infrastructure for walking the parse tree for statement semantic
checking is implemented in the files `lib/Semantics/semantics.cpp`.
Here's a fragment of the declaration of the framework's parse tree visitor from
`lib/Semantics/semantics.cpp`:
```C++
// A parse tree visitor that calls Enter/Leave functions from each checker
// class C supplied as template parameters. Enter is called before the node's
// children are visited, Leave is called after. No two checkers may have the
// same Enter or Leave function. Each checker must be constructible from
// SemanticsContext and have BaseChecker as a virtual base class.
template<typename... C> class SemanticsVisitor : public virtual C... {
public:
using C::Enter...;
using C::Leave...;
using BaseChecker::Enter;
using BaseChecker::Leave;
SemanticsVisitor(SemanticsContext &context)
: C{context}..., context_{context} {}
...
```
Since FUNCTION calls are a kind of expression, I was planning to base my
implementation on the contents of `parser::Expr` nodes. I would need to define
either an `Enter()` or `Leave()` function whose parameter was a `parser::Expr`
node. Here's the declaration I put into `lib/Semantics/check-do.h`:
```C++
void Leave(const parser::Expr &);
```
The `Enter()` functions get called at the time the node is first visited --
that is, before its children. The `Leave()` function gets called after the
children are visited. For my check the visitation order didn't matter, so I
arbitrarily chose to implement the `Leave()` function to visit the parse tree
node.
Since my semantic check was focused on DO CONCURRENT statements, I added it to
the file `lib/Semantics/check-do.cpp` where most of the semantic checking for
DO statements already lived.
## Taking advantage of prior work
When implementing a similar check for SUBROUTINE calls, I created a utility
functions in `lib/Semantics/semantics.cpp` to emit messages if
a symbol corresponding to an active DO variable was being potentially modified:
```C++
void WarnDoVarRedefine(const parser::CharBlock &location, const Symbol &var);
void CheckDoVarRedefine(const parser::CharBlock &location, const Symbol &var);
```
The first function is intended for dummy arguments of `INTENT(INOUT)` and
the second for `INTENT(OUT)`.
Thus I needed three pieces of
information --
1. the source location of the erroneous text,
2. the `INTENT` of the associated dummy argument, and
3. the relevant symbol passed as the actual argument.
The first and third are needed since they're required to call the utility
functions. The second is needed to determine whether to call them.
## Finding the source location
The source code location information that I'd need for the error message must
come from the parse tree. I looked in the file
`include/flang/Parser/parse-tree.h` and determined that a `struct Expr`
contained source location information since it had the field `CharBlock
source`. Thus, if I visited a `parser::Expr` node, I could get the source
location information for the associated expression.
## Determining the `INTENT`
I knew that I could find the `INTENT` of the dummy argument associated with the
actual argument from the function called `dummyIntent()` in the class
`evaluate::ActualArgument` in the file `include/flang/Evaluate/call.h`. So
if I could find an `evaluate::ActualArgument` in an expression, I could
determine the `INTENT` of the associated dummy argument. I knew that it was
valid to call `dummyIntent()` because the data on which `dummyIntent()`
depends is established during semantic processing for expressions, and the
semantic processing for expressions happens before semantic checking for DO
constructs.
In my prior work on checking the INTENT of arguments for SUBROUTINE calls,
the parse tree held a node for the call (a `parser::CallStmt`) that contained
an `evaluate::ProcedureRef` node.
```C++
struct CallStmt {
WRAPPER_CLASS_BOILERPLATE(CallStmt, Call);
mutable std::unique_ptr<evaluate::ProcedureRef,
common::Deleter<evaluate::ProcedureRef>>
typedCall; // filled by semantics
};
```
The `evaluate::ProcedureRef` contains a list of `evaluate::ActualArgument`
nodes. I could then find the INTENT of a dummy argument from the
`evaluate::ActualArgument` node.
For a FUNCTION call, though, there is no similar way to get from a parse tree
node to an `evaluate::ProcedureRef` node. But I knew that there was an
existing framework used in DO construct semantic checking that traversed an
`evaluate::Expr` node collecting `semantics::Symbol` nodes. I guessed that I'd
be able to use a similar framework to traverse an `evaluate::Expr` node to
find all of the `evaluate::ActualArgument` nodes.
Note that the compiler has multiple types called `Expr`. One is in the
`parser` namespace. `parser::Expr` is defined in the file
`include/flang/Parser/parse-tree.h`. It represents a parsed expression that
maps directly to the source code and has fields that specify any operators in
the expression, the operands, and the source position of the expression.
Additionally, in the namespace `evaluate`, there are `evaluate::Expr<T>`
template classes defined in the file `include/flang/Evaluate/expression.h`.
These are parameterized over the various types of Fortran and constitute a
suite of strongly-typed representations of valid Fortran expressions of type
`T` that have been fully elaborated with conversion operations and subjected to
constant folding. After an expression has undergone semantic analysis, the
field `typedExpr` in the `parser::Expr` node is filled in with a pointer that
owns an instance of `evaluate::Expr<SomeType>`, the most general representation
of an analyzed expression.
All of the declarations associated with both FUNCTION and SUBROUTINE calls are
in `include/flang/Evaluate/call.h`. An `evaluate::FunctionRef` inherits from
an `evaluate::ProcedureRef` which contains the list of
`evaluate::ActualArgument` nodes. But the relationship between an
`evaluate::FunctionRef` node and its associated arguments is not relevant. I
only needed to find the `evaluate::ActualArgument` nodes in an expression.
They hold all of the information I needed.
So my plan was to start with the `parser::Expr` node and extract its
associated `evaluate::Expr` field. I would then traverse the
`evaluate::Expr` tree collecting all of the `evaluate::ActualArgument`
nodes. I would look at each of these nodes to determine the `INTENT` of
the associated dummy argument.
This combination of the traversal framework and `dummyIntent()` would give
me the `INTENT` of all of the dummy arguments in a FUNCTION call. Thus, I
would have the second piece of information I needed.
## Determining if the actual argument is a variable
I also guessed that I could determine if the `evaluate::ActualArgument`
consisted of a variable.
Once I had a symbol for the variable, I could call one of the functions:
```C++
void WarnDoVarRedefine(const parser::CharBlock &, const Symbol &);
void CheckDoVarRedefine(const parser::CharBlock &, const Symbol &);
```
to emit the messages.
If my plans worked out, this would give me the three pieces of information I
needed -- the source location of the erroneous text, the `INTENT` of the dummy
argument, and a symbol that I could use to determine whether the actual
argument was an active DO variable.
# Implementation
## Adding a parse tree visitor
I started my implementation by adding a visitor for `parser::Expr` nodes.
Since this analysis is part of DO construct checking, I did this in
`lib/Semantics/check-do.cpp`. I added a print statement to the visitor to
verify that my new code was actually getting executed.
In `lib/Semantics/check-do.h`, I added the declaration for the visitor:
```C++
void Leave(const parser::Expr &);
```
In `lib/Semantics/check-do.cpp`, I added an (almost empty) implementation:
```C++
void DoChecker::Leave(const parser::Expr &) {
std::cout << "In Leave for parser::Expr\n";
}
```
I then built the compiler with these changes and ran it on my test program.
This time, I made sure to invoke semantic checking. Here's the command I used:
```bash
f18 -fdebug-resolve-names -fdebug-dump-parse-tree -funparse-with-symbols testfun.f90
```
This produced the output:
```
In Leave for parser::Expr
In Leave for parser::Expr
In Leave for parser::Expr
In Leave for parser::Expr
In Leave for parser::Expr
```
This made sense since the parse tree contained five `parser::Expr` nodes.
So far, so good. Note that a `parse::Expr` node has a field with the
source position of the associated expression (`CharBlock source`). So I
now had one of the three pieces of information needed to detect and report
errors.
## Collecting the actual arguments
To get the `INTENT` of the dummy arguments and the `semantics::Symbol` associated with the
actual argument, I needed to find all of the actual arguments embedded in an
expression that contained a FUNCTION call. So my next step was to write the
framework to walk the `evaluate::Expr` to gather all of the
`evaluate::ActualArgument` nodes. The code that I planned to model it on
was the existing infrastructure that collected all of the `semantics::Symbol` nodes from an
`evaluate::Expr`. I found this implementation in
`lib/Evaluate/tools.cpp`:
```C++
struct CollectSymbolsHelper
: public SetTraverse<CollectSymbolsHelper, semantics::SymbolSet> {
using Base = SetTraverse<CollectSymbolsHelper, semantics::SymbolSet>;
CollectSymbolsHelper() : Base{*this} {}
using Base::operator();
semantics::SymbolSet operator()(const Symbol &symbol) const {
return {symbol};
}
};
template<typename A> semantics::SymbolSet CollectSymbols(const A &x) {
return CollectSymbolsHelper{}(x);
}
```
Note that the `CollectSymbols()` function returns a `semantics::Symbolset`,
which is declared in `include/flang/Semantics/symbol.h`:
```C++
using SymbolSet = std::set<SymbolRef>;
```
This infrastructure yields a collection based on `std::set<>`. Using an
`std::set<>` means that if the same object is inserted twice, the
collection only gets one copy. This was the behavior that I wanted.
Here's a sample invocation of `CollectSymbols()` that I found:
```C++
if (const auto *expr{GetExpr(parsedExpr)}) {
for (const Symbol &symbol : evaluate::CollectSymbols(*expr)) {
```
I noted that a `SymbolSet` did not actually contain an
`std::set<Symbol>`. This wasn't surprising since we don't want to put the
full `semantics::Symbol` objects into the set. Ideally, we would be able to create an
`std::set<Symbol &>` (a set of C++ references to symbols). But C++ doesn't
support sets that contain references. This limitation is part of the rationale
for the f18 implementation of type `common::Reference`, which is defined in
`include/flang/Common/reference.h`.
`SymbolRef`, the specialization of the template `common::Reference` for
`semantics::Symbol`, is declared in the file
`include/flang/Semantics/symbol.h`:
```C++
using SymbolRef = common::Reference<const Symbol>;
```
So to implement something that would collect `evaluate::ActualArgument`
nodes from an `evaluate::Expr`, I first defined the required types
`ActualArgumentRef` and `ActualArgumentSet`. Since these are being
used exclusively for DO construct semantic checking (currently), I put their
definitions into `lib/Semantics/check-do.cpp`:
```C++
namespace Fortran::evaluate {
using ActualArgumentRef = common::Reference<const ActualArgument>;
}
using ActualArgumentSet = std::set<evaluate::ActualArgumentRef>;
```
Since `ActualArgument` is in the namespace `evaluate`, I put the
definition for `ActualArgumentRef` in that namespace, too.
I then modeled the code to create an `ActualArgumentSet` after the code to
collect a `SymbolSet` and put it into `lib/Semantics/check-do.cpp`:
```C++
struct CollectActualArgumentsHelper
: public evaluate::SetTraverse<CollectActualArgumentsHelper,
ActualArgumentSet> {
using Base = SetTraverse<CollectActualArgumentsHelper, ActualArgumentSet>;
CollectActualArgumentsHelper() : Base{*this} {}
using Base::operator();
ActualArgumentSet operator()(const evaluate::ActualArgument &arg) const {
return ActualArgumentSet{arg};
}
};
template<typename A> ActualArgumentSet CollectActualArguments(const A &x) {
return CollectActualArgumentsHelper{}(x);
}
template ActualArgumentSet CollectActualArguments(const SomeExpr &);
```
Unfortunately, when I tried to build this code, I got an error message saying
`std::set` requires the `<` operator to be defined for its contents.
To fix this, I added a definition for `<`. I didn't care how `<` was
defined, so I just used the address of the object:
```C++
inline bool operator<(ActualArgumentRef x, ActualArgumentRef y) {
return &*x < &*y;
}
```
I was surprised when this did not make the error message saying that I needed
the `<` operator go away. Eventually, I figured out that the definition of
the `<` operator needed to be in the `evaluate` namespace. Once I put
it there, everything compiled successfully. Here's the code that worked:
```C++
namespace Fortran::evaluate {
using ActualArgumentRef = common::Reference<const ActualArgument>;
inline bool operator<(ActualArgumentRef x, ActualArgumentRef y) {
return &*x < &*y;
}
}
```
I then modified my visitor for the parser::Expr to invoke my new collection
framework. To verify that it was actually doing something, I printed out the
number of `evaluate::ActualArgument` nodes that it collected. Note the
call to `GetExpr()` in the invocation of `CollectActualArguments()`. I
modeled this on similar code that collected a `SymbolSet` described above:
```C++
void DoChecker::Leave(const parser::Expr &parsedExpr) {
std::cout << "In Leave for parser::Expr\n";
ActualArgumentSet argSet{CollectActualArguments(GetExpr(parsedExpr))};
std::cout << "Number of arguments: " << argSet.size() << "\n";
}
```
I compiled and tested this code on my little test program. Here's the output that I got:
```
In Leave for parser::Expr
Number of arguments: 0
In Leave for parser::Expr
Number of arguments: 0
In Leave for parser::Expr
Number of arguments: 0
In Leave for parser::Expr
Number of arguments: 1
In Leave for parser::Expr
Number of arguments: 0
```
So most of the `parser::Expr`nodes contained no actual arguments, but the
fourth expression in the parse tree walk contained a single argument. This may
seem wrong since the third `parser::Expr` node in the file contains the
`FunctionReference` node along with the arguments that we're gathering.
But since the tree walk function is being called upon leaving a
`parser::Expr` node, the function visits the `parser::Expr` node
associated with the `parser::ActualArg` node before it visits the
`parser::Expr` node associated with the `parser::FunctionReference`
node.
So far, so good.
## Finding the `INTENT` of the dummy argument
I now wanted to find the `INTENT` of the dummy argument associated with the
arguments in the set. As mentioned earlier, the type
`evaluate::ActualArgument` has a member function called `dummyIntent()`
that gives this value. So I augmented my code to print out the `INTENT`:
```C++
void DoChecker::Leave(const parser::Expr &parsedExpr) {
std::cout << "In Leave for parser::Expr\n";
ActualArgumentSet argSet{CollectActualArguments(GetExpr(parsedExpr))};
std::cout << "Number of arguments: " << argSet.size() << "\n";
for (const evaluate::ActualArgumentRef &argRef : argSet) {
common::Intent intent{argRef->dummyIntent()};
switch (intent) {
case common::Intent::In: std::cout << "INTENT(IN)\n"; break;
case common::Intent::Out: std::cout << "INTENT(OUT)\n"; break;
case common::Intent::InOut: std::cout << "INTENT(INOUT)\n"; break;
default: std::cout << "default INTENT\n";
}
}
}
```
I then rebuilt my compiler and ran it on my test case. This produced the following output:
```
In Leave for parser::Expr
Number of arguments: 0
In Leave for parser::Expr
Number of arguments: 0
In Leave for parser::Expr
Number of arguments: 0
In Leave for parser::Expr
Number of arguments: 1
INTENT(OUT)
In Leave for parser::Expr
Number of arguments: 0
```
I then modified my test case to convince myself that I was getting the correct
`INTENT` for `IN`, `INOUT`, and default cases.
So far, so good.
## Finding the symbols for arguments that are variables
The third and last piece of information I needed was to determine if a variable
was being passed as an actual argument. In such cases, I wanted to get the
symbol table node (`semantics::Symbol`) for the variable. My starting point was the
`evaluate::ActualArgument` node.
I was unsure of how to do this, so I browsed through existing code to look for
how it treated `evaluate::ActualArgument` objects. Since most of the code that deals with the `evaluate` namespace is in the lib/Evaluate directory, I looked there. I ran `grep` on all of the `.cpp` files looking for
uses of `ActualArgument`. One of the first hits I got was in `lib/Evaluate/call.cpp` in the definition of `ActualArgument::GetType()`:
```C++
std::optional<DynamicType> ActualArgument::GetType() const {
if (const Expr<SomeType> *expr{UnwrapExpr()}) {
return expr->GetType();
} else if (std::holds_alternative<AssumedType>(u_)) {
return DynamicType::AssumedType();
} else {
return std::nullopt;
}
}
```
I noted the call to `UnwrapExpr()` that yielded a value of
`Expr<SomeType>`. So I guessed that I could use this member function to
get an `evaluate::Expr<SomeType>` on which I could perform further analysis.
I also knew that the header file `include/flang/Evaluate/tools.h` held many
utility functions for dealing with `evaluate::Expr` objects. I was hoping to
find something that would determine if an `evaluate::Expr` was a variable. So
I searched for `IsVariable` and got a hit immediately.
```C++
template<typename A> bool IsVariable(const A &x) {
if (auto known{IsVariableHelper{}(x)}) {
return *known;
} else {
return false;
}
}
```
But I actually needed more than just the knowledge that an `evaluate::Expr` was
a variable. I needed the `semantics::Symbol` associated with the variable. So
I searched in `include/flang/Evaluate/tools.h` for functions that returned a
`semantics::Symbol`. I found the following:
```C++
// If an expression is simply a whole symbol data designator,
// extract and return that symbol, else null.
template<typename A> const Symbol *UnwrapWholeSymbolDataRef(const A &x) {
if (auto dataRef{ExtractDataRef(x)}) {
if (const SymbolRef * p{std::get_if<SymbolRef>(&dataRef->u)}) {
return &p->get();
}
}
return nullptr;
}
```
This was exactly what I wanted. DO variables must be whole symbols. So I
could try to extract a whole `semantics::Symbol` from the `evaluate::Expr` in my
`evaluate::ActualArgument`. If this extraction resulted in a `semantics::Symbol`
that wasn't a `nullptr`, I could then conclude if it was a variable that I
could pass to existing functions that would determine if it was an active DO
variable.
I then modified the compiler to perform the analysis that I'd guessed would
work:
```C++
void DoChecker::Leave(const parser::Expr &parsedExpr) {
std::cout << "In Leave for parser::Expr\n";
ActualArgumentSet argSet{CollectActualArguments(GetExpr(parsedExpr))};
std::cout << "Number of arguments: " << argSet.size() << "\n";
for (const evaluate::ActualArgumentRef &argRef : argSet) {
if (const SomeExpr * argExpr{argRef->UnwrapExpr()}) {
std::cout << "Got an unwrapped Expr\n";
if (const Symbol * var{evaluate::UnwrapWholeSymbolDataRef(*argExpr)}) {
std::cout << "Found a whole variable: " << *var << "\n";
}
}
common::Intent intent{argRef->dummyIntent()};
switch (intent) {
case common::Intent::In: std::cout << "INTENT(IN)\n"; break;
case common::Intent::Out: std::cout << "INTENT(OUT)\n"; break;
case common::Intent::InOut: std::cout << "INTENT(INOUT)\n"; break;
default: std::cout << "default INTENT\n";
}
}
}
```
Note the line that prints out the symbol table entry for the variable:
```C++
std::cout << "Found a whole variable: " << *var << "\n";
```
The compiler defines the "<<" operator for `semantics::Symbol`, which is handy
for analyzing the compiler's behavior.
Here's the result of running the modified compiler on my Fortran test case:
```
In Leave for parser::Expr
Number of arguments: 0
In Leave for parser::Expr
Number of arguments: 0
In Leave for parser::Expr
Number of arguments: 0
In Leave for parser::Expr
Number of arguments: 1
Got an unwrapped Expr
Found a whole variable: ivar: ObjectEntity type: INTEGER(4)
INTENT(OUT)
In Leave for parser::Expr
Number of arguments: 0
```
Sweet.
## Emitting the messages
At this point, using the source location information from the original
`parser::Expr`, I had enough information to plug into the exiting
interfaces for emitting messages for active DO variables. I modified the
compiler code accordingly:
```C++
void DoChecker::Leave(const parser::Expr &parsedExpr) {
std::cout << "In Leave for parser::Expr\n";
ActualArgumentSet argSet{CollectActualArguments(GetExpr(parsedExpr))};
std::cout << "Number of arguments: " << argSet.size() << "\n";
for (const evaluate::ActualArgumentRef &argRef : argSet) {
if (const SomeExpr * argExpr{argRef->UnwrapExpr()}) {
std::cout << "Got an unwrapped Expr\n";
if (const Symbol * var{evaluate::UnwrapWholeSymbolDataRef(*argExpr)}) {
std::cout << "Found a whole variable: " << *var << "\n";
common::Intent intent{argRef->dummyIntent()};
switch (intent) {
case common::Intent::In: std::cout << "INTENT(IN)\n"; break;
case common::Intent::Out:
std::cout << "INTENT(OUT)\n";
context_.CheckDoVarRedefine(parsedExpr.source, *var);
break;
case common::Intent::InOut:
std::cout << "INTENT(INOUT)\n";
context_.WarnDoVarRedefine(parsedExpr.source, *var);
break;
default: std::cout << "default INTENT\n";
}
}
}
}
}
```
I then ran this code on my test case, and miraculously, got the following
output:
```
In Leave for parser::Expr
Number of arguments: 0
In Leave for parser::Expr
Number of arguments: 0
In Leave for parser::Expr
Number of arguments: 0
In Leave for parser::Expr
Number of arguments: 1
Got an unwrapped Expr
Found a whole variable: ivar: ObjectEntity type: INTEGER(4)
INTENT(OUT)
In Leave for parser::Expr
Number of arguments: 0
testfun.f90:6:12: error: Cannot redefine DO variable 'ivar'
jvar = intentOutFunc(ivar)
^^^^^^^^^^^^^^^^^^^
testfun.f90:5:6: Enclosing DO construct
do ivar = 1, 10
^^^^
```
Even sweeter.
# Improving the test case
At this point, my implementation seemed to be working. But I was concerned
about the limitations of my test case. So I augmented it to include arguments
other than `INTENT(OUT)` and more complex expressions. Luckily, my
augmented test did not reveal any new problems.
Here's the test I ended up with:
```Fortran
subroutine s()
Integer :: ivar, jvar
! This one is OK
do ivar = 1, 10
jvar = intentInFunc(ivar)
end do
! Error for passing a DO variable to an INTENT(OUT) dummy
do ivar = 1, 10
jvar = intentOutFunc(ivar)
end do
! Error for passing a DO variable to an INTENT(OUT) dummy, more complex
! expression
do ivar = 1, 10
jvar = 83 + intentInFunc(intentOutFunc(ivar))
end do
! Warning for passing a DO variable to an INTENT(INOUT) dummy
do ivar = 1, 10
jvar = intentInOutFunc(ivar)
end do
contains
function intentInFunc(dummyArg)
integer, intent(in) :: dummyArg
integer :: intentInFunc
intentInFunc = 343
end function intentInFunc
function intentOutFunc(dummyArg)
integer, intent(out) :: dummyArg
integer :: intentOutFunc
dummyArg = 216
intentOutFunc = 343
end function intentOutFunc
function intentInOutFunc(dummyArg)
integer, intent(inout) :: dummyArg
integer :: intentInOutFunc
dummyArg = 216
intentInOutFunc = 343
end function intentInOutFunc
end subroutine s
```
# Submitting the pull request
At this point, my implementation seemed functionally complete, so I stripped out all of the debug statements, ran `clang-format` on it and reviewed it
to make sure that the names were clear. Here's what I ended up with:
```C++
void DoChecker::Leave(const parser::Expr &parsedExpr) {
ActualArgumentSet argSet{CollectActualArguments(GetExpr(parsedExpr))};
for (const evaluate::ActualArgumentRef &argRef : argSet) {
if (const SomeExpr * argExpr{argRef->UnwrapExpr()}) {
if (const Symbol * var{evaluate::UnwrapWholeSymbolDataRef(*argExpr)}) {
common::Intent intent{argRef->dummyIntent()};
switch (intent) {
case common::Intent::Out:
context_.CheckDoVarRedefine(parsedExpr.source, *var);
break;
case common::Intent::InOut:
context_.WarnDoVarRedefine(parsedExpr.source, *var);
break;
default:; // INTENT(IN) or default intent
}
}
}
}
}
```
I then created a pull request to get review comments.
# Responding to pull request comments
I got feedback suggesting that I use an `if` statement rather than a
`case` statement. Another comment reminded me that I should look at the
code I'd previously writted to do a similar check for SUBROUTINE calls to see
if there was an opportunity to share code. This examination resulted in
converting my existing code to the following pair of functions:
```C++
static void CheckIfArgIsDoVar(const evaluate::ActualArgument &arg,
const parser::CharBlock location, SemanticsContext &context) {
common::Intent intent{arg.dummyIntent()};
if (intent == common::Intent::Out || intent == common::Intent::InOut) {
if (const SomeExpr * argExpr{arg.UnwrapExpr()}) {
if (const Symbol * var{evaluate::UnwrapWholeSymbolDataRef(*argExpr)}) {
if (intent == common::Intent::Out) {
context.CheckDoVarRedefine(location, *var);
} else {
context.WarnDoVarRedefine(location, *var); // INTENT(INOUT)
}
}
}
}
}
void DoChecker::Leave(const parser::Expr &parsedExpr) {
if (const SomeExpr * expr{GetExpr(parsedExpr)}) {
ActualArgumentSet argSet{CollectActualArguments(*expr)};
for (const evaluate::ActualArgumentRef &argRef : argSet) {
CheckIfArgIsDoVar(*argRef, parsedExpr.source, context_);
}
}
}
```
The function `CheckIfArgIsDoVar()` was shared with the checks for DO
variables being passed to SUBROUTINE calls.
At this point, my pull request was approved, and I merged it and deleted the
associated branch.

View File

@ -0,0 +1,791 @@
<!--===- documentation/Intrinsics.md
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 categorization of standard (2018) and extended Fortran intrinsic procedures
This note attempts to group the intrinsic procedures of Fortran into categories
of functions or subroutines with similar interfaces as an aid to
comprehension beyond that which might be gained from the standard's
alphabetical list.
A brief status of intrinsic procedure support in f18 is also given at the end.
Few procedures are actually described here apart from their interfaces; see the
Fortran 2018 standard (section 16) for the complete story.
Intrinsic modules are not covered here.
## General rules
1. The value of any intrinsic function's `KIND` actual argument, if present,
must be a scalar constant integer expression, of any kind, whose value
resolves to some supported kind of the function's result type.
If optional and absent, the kind of the function's result is
either the default kind of that category or to the kind of an argument
(e.g., as in `AINT`).
1. Procedures are summarized with a non-Fortran syntax for brevity.
Wherever a function has a short definition, it appears after an
equal sign as if it were a statement function. Any functions referenced
in these short summaries are intrinsic.
1. Unless stated otherwise, an actual argument may have any supported kind
of a particular intrinsic type. Sometimes a pattern variable
can appear in a description (e.g., `REAL(k)`) when the kind of an
actual argument's type must match the kind of another argument, or
determines the kind type parameter of the function result.
1. When an intrinsic type name appears without a kind (e.g., `REAL`),
it refers to the default kind of that type. Sometimes the word
`default` will appear for clarity.
1. The names of the dummy arguments actually matter because they can
be used as keywords for actual arguments.
1. All standard intrinsic functions are pure, even when not elemental.
1. Assumed-rank arguments may not appear as actual arguments unless
expressly permitted.
1. When an argument is described with a default value, e.g. `KIND=KIND(0)`,
it is an optional argument. Optional arguments without defaults,
e.g. `DIM` on many transformationals, are wrapped in `[]` brackets
as in the Fortran standard. When an intrinsic has optional arguments
with and without default values, the arguments with default values
may appear within the brackets to preserve the order of arguments
(e.g., `COUNT`).
# Elemental intrinsic functions
Pure elemental semantics apply to these functions, to wit: when one or more of
the actual arguments are arrays, the arguments must be conformable, and
the result is also an array.
Scalar arguments are expanded when the arguments are not all scalars.
## Elemental intrinsic functions that may have unrestricted specific procedures
When an elemental intrinsic function is documented here as having an
_unrestricted specific name_, that name may be passed as an actual
argument, used as the target of a procedure pointer, appear in
a generic interface, and be otherwise used as if it were an external
procedure.
An `INTRINSIC` statement or attribute may have to be applied to an
unrestricted specific name to enable such usage.
When a name is being used as a specific procedure for any purpose other
than that of a called function, the specific instance of the function
that accepts and returns values of the default kinds of the intrinsic
types is used.
A Fortran `INTERFACE` could be written to define each of
these unrestricted specific intrinsic function names.
Calls to dummy arguments and procedure pointers that correspond to these
specific names must pass only scalar actual argument values.
No other intrinsic function name can be passed as an actual argument,
used as a pointer target, appear in a generic interface, or be otherwise
used except as the name of a called function.
Some of these _restricted specific intrinsic functions_, e.g. `FLOAT`,
provide a means for invoking a corresponding generic (`REAL` in the case of `FLOAT`)
with forced argument and result kinds.
Others, viz. `CHAR`, `ICHAR`, `INT`, `REAL`, and the lexical comparisons like `LGE`,
have the same name as their generic functions, and it is not clear what purpose
is accomplished by the standard by defining them as specific functions.
### Trigonometric elemental intrinsic functions, generic and (mostly) specific
All of these functions can be used as unrestricted specific names.
```
ACOS(REAL(k) X) -> REAL(k)
ASIN(REAL(k) X) -> REAL(k)
ATAN(REAL(k) X) -> REAL(k)
ATAN(REAL(k) Y, REAL(k) X) -> REAL(k) = ATAN2(Y, X)
ATAN2(REAL(k) Y, REAL(k) X) -> REAL(k)
COS(REAL(k) X) -> REAL(k)
COSH(REAL(k) X) -> REAL(k)
SIN(REAL(k) X) -> REAL(k)
SINH(REAL(k) X) -> REAL(k)
TAN(REAL(k) X) -> REAL(k)
TANH(REAL(k) X) -> REAL(k)
```
These `COMPLEX` versions of some of those functions, and the
inverse hyperbolic functions, cannot be used as specific names.
```
ACOS(COMPLEX(k) X) -> COMPLEX(k)
ASIN(COMPLEX(k) X) -> COMPLEX(k)
ATAN(COMPLEX(k) X) -> COMPLEX(k)
ACOSH(REAL(k) X) -> REAL(k)
ACOSH(COMPLEX(k) X) -> COMPLEX(k)
ASINH(REAL(k) X) -> REAL(k)
ASINH(COMPLEX(k) X) -> COMPLEX(k)
ATANH(REAL(k) X) -> REAL(k)
ATANH(COMPLEX(k) X) -> COMPLEX(k)
COS(COMPLEX(k) X) -> COMPLEX(k)
COSH(COMPLEX(k) X) -> COMPLEX(k)
SIN(COMPLEX(k) X) -> COMPLEX(k)
SINH(COMPLEX(k) X) -> COMPLEX(k)
TAN(COMPLEX(k) X) -> COMPLEX(k)
TANH(COMPLEX(k) X) -> COMPLEX(k)
```
### Non-trigonometric elemental intrinsic functions, generic and specific
These functions *can* be used as unrestricted specific names.
```
ABS(REAL(k) A) -> REAL(k) = SIGN(A, 0.0)
AIMAG(COMPLEX(k) Z) -> REAL(k) = Z%IM
AINT(REAL(k) A, KIND=k) -> REAL(KIND)
ANINT(REAL(k) A, KIND=k) -> REAL(KIND)
CONJG(COMPLEX(k) Z) -> COMPLEX(k) = CMPLX(Z%RE, -Z%IM)
DIM(REAL(k) X, REAL(k) Y) -> REAL(k) = X-MIN(X,Y)
DPROD(default REAL X, default REAL Y) -> DOUBLE PRECISION = DBLE(X)*DBLE(Y)
EXP(REAL(k) X) -> REAL(k)
INDEX(CHARACTER(k) STRING, CHARACTER(k) SUBSTRING, LOGICAL(any) BACK=.FALSE., KIND=KIND(0)) -> INTEGER(KIND)
LEN(CHARACTER(k,n) STRING, KIND=KIND(0)) -> INTEGER(KIND) = n
LOG(REAL(k) X) -> REAL(k)
LOG10(REAL(k) X) -> REAL(k)
MOD(INTEGER(k) A, INTEGER(k) P) -> INTEGER(k) = A-P*INT(A/P)
NINT(REAL(k) A, KIND=KIND(0)) -> INTEGER(KIND)
SIGN(REAL(k) A, REAL(k) B) -> REAL(k)
SQRT(REAL(k) X) -> REAL(k) = X ** 0.5
```
These variants, however *cannot* be used as specific names without recourse to an alias
from the following section:
```
ABS(INTEGER(k) A) -> INTEGER(k) = SIGN(A, 0)
ABS(COMPLEX(k) A) -> REAL(k) = HYPOT(A%RE, A%IM)
DIM(INTEGER(k) X, INTEGER(k) Y) -> INTEGER(k) = X-MIN(X,Y)
EXP(COMPLEX(k) X) -> COMPLEX(k)
LOG(COMPLEX(k) X) -> COMPLEX(k)
MOD(REAL(k) A, REAL(k) P) -> REAL(k) = A-P*INT(A/P)
SIGN(INTEGER(k) A, INTEGER(k) B) -> INTEGER(k)
SQRT(COMPLEX(k) X) -> COMPLEX(k)
```
### Unrestricted specific aliases for some elemental intrinsic functions with distinct names
```
ALOG(REAL X) -> REAL = LOG(X)
ALOG10(REAL X) -> REAL = LOG10(X)
AMOD(REAL A, REAL P) -> REAL = MOD(A, P)
CABS(COMPLEX A) = ABS(A)
CCOS(COMPLEX X) = COS(X)
CEXP(COMPLEX A) -> COMPLEX = EXP(A)
CLOG(COMPLEX X) -> COMPLEX = LOG(X)
CSIN(COMPLEX X) -> COMPLEX = SIN(X)
CSQRT(COMPLEX X) -> COMPLEX = SQRT(X)
CTAN(COMPLEX X) -> COMPLEX = TAN(X)
DABS(DOUBLE PRECISION A) -> DOUBLE PRECISION = ABS(A)
DACOS(DOUBLE PRECISION X) -> DOUBLE PRECISION = ACOS(X)
DASIN(DOUBLE PRECISION X) -> DOUBLE PRECISION = ASIN(X)
DATAN(DOUBLE PRECISION X) -> DOUBLE PRECISION = ATAN(X)
DATAN2(DOUBLE PRECISION Y, DOUBLE PRECISION X) -> DOUBLE PRECISION = ATAN2(Y, X)
DCOS(DOUBLE PRECISION X) -> DOUBLE PRECISION = COS(X)
DCOSH(DOUBLE PRECISION X) -> DOUBLE PRECISION = COSH(X)
DDIM(DOUBLE PRECISION X, DOUBLE PRECISION Y) -> DOUBLE PRECISION = X-MIN(X,Y)
DEXP(DOUBLE PRECISION X) -> DOUBLE PRECISION = EXP(X)
DINT(DOUBLE PRECISION A) -> DOUBLE PRECISION = AINT(A)
DLOG(DOUBLE PRECISION X) -> DOUBLE PRECISION = LOG(X)
DLOG10(DOUBLE PRECISION X) -> DOUBLE PRECISION = LOG10(X)
DMOD(DOUBLE PRECISION A, DOUBLE PRECISION P) -> DOUBLE PRECISION = MOD(A, P)
DNINT(DOUBLE PRECISION A) -> DOUBLE PRECISION = ANINT(A)
DSIGN(DOUBLE PRECISION A, DOUBLE PRECISION B) -> DOUBLE PRECISION = SIGN(A, B)
DSIN(DOUBLE PRECISION X) -> DOUBLE PRECISION = SIN(X)
DSINH(DOUBLE PRECISION X) -> DOUBLE PRECISION = SINH(X)
DSQRT(DOUBLE PRECISION X) -> DOUBLE PRECISION = SQRT(X)
DTAN(DOUBLE PRECISION X) -> DOUBLE PRECISION = TAN(X)
DTANH(DOUBLE PRECISION X) -> DOUBLE PRECISION = TANH(X)
IABS(INTEGER A) -> INTEGER = ABS(A)
IDIM(INTEGER X, INTEGER Y) -> INTEGER = X-MIN(X,Y)
IDNINT(DOUBLE PRECISION A) -> INTEGER = NINT(A)
ISIGN(INTEGER A, INTEGER B) -> INTEGER = SIGN(A, B)
```
## Generic elemental intrinsic functions without specific names
(No procedures after this point can be passed as actual arguments, used as
pointer targets, or appear as specific procedures in generic interfaces.)
### Elemental conversions
```
ACHAR(INTEGER(k) I, KIND=KIND('')) -> CHARACTER(KIND,LEN=1)
CEILING(REAL() A, KIND=KIND(0)) -> INTEGER(KIND)
CHAR(INTEGER(any) I, KIND=KIND('')) -> CHARACTER(KIND,LEN=1)
CMPLX(COMPLEX(k) X, KIND=KIND(0.0D0)) -> COMPLEX(KIND)
CMPLX(INTEGER or REAL or BOZ X, INTEGER or REAL or BOZ Y=0, KIND=KIND((0,0))) -> COMPLEX(KIND)
DBLE(INTEGER or REAL or COMPLEX or BOZ A) = REAL(A, KIND=KIND(0.0D0))
EXPONENT(REAL(any) X) -> default INTEGER
FLOOR(REAL(any) A, KIND=KIND(0)) -> INTEGER(KIND)
IACHAR(CHARACTER(KIND=k,LEN=1) C, KIND=KIND(0)) -> INTEGER(KIND)
ICHAR(CHARACTER(KIND=k,LEN=1) C, KIND=KIND(0)) -> INTEGER(KIND)
INT(INTEGER or REAL or COMPLEX or BOZ A, KIND=KIND(0)) -> INTEGER(KIND)
LOGICAL(LOGICAL(any) L, KIND=KIND(.TRUE.)) -> LOGICAL(KIND)
REAL(INTEGER or REAL or COMPLEX or BOZ A, KIND=KIND(0.0)) -> REAL(KIND)
```
### Other generic elemental intrinsic functions without specific names
N.B. `BESSEL_JN(N1, N2, X)` and `BESSEL_YN(N1, N2, X)` are categorized
below with the _transformational_ intrinsic functions.
```
BESSEL_J0(REAL(k) X) -> REAL(k)
BESSEL_J1(REAL(k) X) -> REAL(k)
BESSEL_JN(INTEGER(n) N, REAL(k) X) -> REAL(k)
BESSEL_Y0(REAL(k) X) -> REAL(k)
BESSEL_Y1(REAL(k) X) -> REAL(k)
BESSEL_YN(INTEGER(n) N, REAL(k) X) -> REAL(k)
ERF(REAL(k) X) -> REAL(k)
ERFC(REAL(k) X) -> REAL(k)
ERFC_SCALED(REAL(k) X) -> REAL(k)
FRACTION(REAL(k) X) -> REAL(k)
GAMMA(REAL(k) X) -> REAL(k)
HYPOT(REAL(k) X, REAL(k) Y) -> REAL(k) = SQRT(X*X+Y*Y) without spurious overflow
IMAGE_STATUS(INTEGER(any) IMAGE [, scalar TEAM_TYPE TEAM ]) -> default INTEGER
IS_IOSTAT_END(INTEGER(any) I) -> default LOGICAL
IS_IOSTAT_EOR(INTEGER(any) I) -> default LOGICAL
LOG_GAMMA(REAL(k) X) -> REAL(k)
MAX(INTEGER(k) ...) -> INTEGER(k)
MAX(REAL(k) ...) -> REAL(k)
MAX(CHARACTER(KIND=k) ...) -> CHARACTER(KIND=k,LEN=MAX(LEN(...)))
MERGE(any type TSOURCE, same type FSOURCE, LOGICAL(any) MASK) -> type of FSOURCE
MIN(INTEGER(k) ...) -> INTEGER(k)
MIN(REAL(k) ...) -> REAL(k)
MIN(CHARACTER(KIND=k) ...) -> CHARACTER(KIND=k,LEN=MAX(LEN(...)))
MODULO(INTEGER(k) A, INTEGER(k) P) -> INTEGER(k); P*result >= 0
MODULO(REAL(k) A, REAL(k) P) -> REAL(k) = A - P*FLOOR(A/P)
NEAREST(REAL(k) X, REAL(any) S) -> REAL(k)
OUT_OF_RANGE(INTEGER(any) X, scalar INTEGER or REAL(k) MOLD) -> default LOGICAL
OUT_OF_RANGE(REAL(any) X, scalar REAL(k) MOLD) -> default LOGICAL
OUT_OF_RANGE(REAL(any) X, scalar INTEGER(any) MOLD, scalar LOGICAL(any) ROUND=.FALSE.) -> default LOGICAL
RRSPACING(REAL(k) X) -> REAL(k)
SCALE(REAL(k) X, INTEGER(any) I) -> REAL(k)
SET_EXPONENT(REAL(k) X, INTEGER(any) I) -> REAL(k)
SPACING(REAL(k) X) -> REAL(k)
```
### Restricted specific aliases for elemental conversions &/or extrema with default intrinsic types
```
AMAX0(INTEGER ...) = REAL(MAX(...))
AMAX1(REAL ...) = MAX(...)
AMIN0(INTEGER...) = REAL(MIN(...))
AMIN1(REAL ...) = MIN(...)
DMAX1(DOUBLE PRECISION ...) = MAX(...)
DMIN1(DOUBLE PRECISION ...) = MIN(...)
FLOAT(INTEGER I) = REAL(I)
IDINT(DOUBLE PRECISION A) = INT(A)
IFIX(REAL A) = INT(A)
MAX0(INTEGER ...) = MAX(...)
MAX1(REAL ...) = INT(MAX(...))
MIN0(INTEGER ...) = MIN(...)
MIN1(REAL ...) = INT(MIN(...))
SNGL(DOUBLE PRECISION A) = REAL(A)
```
### Generic elemental bit manipulation intrinsic functions
Many of these accept a typeless "BOZ" literal as an actual argument.
It is interpreted as having the kind of intrinsic `INTEGER` type
as another argument, as if the typeless were implicitly wrapped
in a call to `INT()`.
When multiple arguments can be either `INTEGER` values or typeless
constants, it is forbidden for *all* of them to be typeless
constants if the result of the function is `INTEGER`
(i.e., only `BGE`, `BGT`, `BLE`, and `BLT` can have multiple
typeless arguments).
```
BGE(INTEGER(n1) or BOZ I, INTEGER(n2) or BOZ J) -> default LOGICAL
BGT(INTEGER(n1) or BOZ I, INTEGER(n2) or BOZ J) -> default LOGICAL
BLE(INTEGER(n1) or BOZ I, INTEGER(n2) or BOZ J) -> default LOGICAL
BLT(INTEGER(n1) or BOZ I, INTEGER(n2) or BOZ J) -> default LOGICAL
BTEST(INTEGER(n1) I, INTEGER(n2) POS) -> default LOGICAL
DSHIFTL(INTEGER(k) I, INTEGER(k) or BOZ J, INTEGER(any) SHIFT) -> INTEGER(k)
DSHIFTL(BOZ I, INTEGER(k), INTEGER(any) SHIFT) -> INTEGER(k)
DSHIFTR(INTEGER(k) I, INTEGER(k) or BOZ J, INTEGER(any) SHIFT) -> INTEGER(k)
DSHIFTR(BOZ I, INTEGER(k), INTEGER(any) SHIFT) -> INTEGER(k)
IAND(INTEGER(k) I, INTEGER(k) or BOZ J) -> INTEGER(k)
IAND(BOZ I, INTEGER(k) J) -> INTEGER(k)
IBCLR(INTEGER(k) I, INTEGER(any) POS) -> INTEGER(k)
IBITS(INTEGER(k) I, INTEGER(n1) POS, INTEGER(n2) LEN) -> INTEGER(k)
IBSET(INTEGER(k) I, INTEGER(any) POS) -> INTEGER(k)
IEOR(INTEGER(k) I, INTEGER(k) or BOZ J) -> INTEGER(k)
IEOR(BOZ I, INTEGER(k) J) -> INTEGER(k)
IOR(INTEGER(k) I, INTEGER(k) or BOZ J) -> INTEGER(k)
IOR(BOZ I, INTEGER(k) J) -> INTEGER(k)
ISHFT(INTEGER(k) I, INTEGER(any) SHIFT) -> INTEGER(k)
ISHFTC(INTEGER(k) I, INTEGER(n1) SHIFT, INTEGER(n2) SIZE=BIT_SIZE(I)) -> INTEGER(k)
LEADZ(INTEGER(any) I) -> default INTEGER
MASKL(INTEGER(any) I, KIND=KIND(0)) -> INTEGER(KIND)
MASKR(INTEGER(any) I, KIND=KIND(0)) -> INTEGER(KIND)
MERGE_BITS(INTEGER(k) I, INTEGER(k) or BOZ J, INTEGER(k) or BOZ MASK) = IOR(IAND(I,MASK),IAND(J,NOT(MASK)))
MERGE_BITS(BOZ I, INTEGER(k) J, INTEGER(k) or BOZ MASK) = IOR(IAND(I,MASK),IAND(J,NOT(MASK)))
NOT(INTEGER(k) I) -> INTEGER(k)
POPCNT(INTEGER(any) I) -> default INTEGER
POPPAR(INTEGER(any) I) -> default INTEGER = IAND(POPCNT(I), Z'1')
SHIFTA(INTEGER(k) I, INTEGER(any) SHIFT) -> INTEGER(k)
SHIFTL(INTEGER(k) I, INTEGER(any) SHIFT) -> INTEGER(k)
SHIFTR(INTEGER(k) I, INTEGER(any) SHIFT) -> INTEGER(k)
TRAILZ(INTEGER(any) I) -> default INTEGER
```
### Character elemental intrinsic functions
See also `INDEX` and `LEN` above among the elemental intrinsic functions with
unrestricted specific names.
```
ADJUSTL(CHARACTER(k,LEN=n) STRING) -> CHARACTER(k,LEN=n)
ADJUSTR(CHARACTER(k,LEN=n) STRING) -> CHARACTER(k,LEN=n)
LEN_TRIM(CHARACTER(k,n) STRING, KIND=KIND(0)) -> INTEGER(KIND) = n
LGE(CHARACTER(k,n1) STRING_A, CHARACTER(k,n2) STRING_B) -> default LOGICAL
LGT(CHARACTER(k,n1) STRING_A, CHARACTER(k,n2) STRING_B) -> default LOGICAL
LLE(CHARACTER(k,n1) STRING_A, CHARACTER(k,n2) STRING_B) -> default LOGICAL
LLT(CHARACTER(k,n1) STRING_A, CHARACTER(k,n2) STRING_B) -> default LOGICAL
SCAN(CHARACTER(k,n) STRING, CHARACTER(k,m) SET, LOGICAL(any) BACK=.FALSE., KIND=KIND(0)) -> INTEGER(KIND)
VERIFY(CHARACTER(k,n) STRING, CHARACTER(k,m) SET, LOGICAL(any) BACK=.FALSE., KIND=KIND(0)) -> INTEGER(KIND)
```
`SCAN` returns the index of the first (or last, if `BACK=.TRUE.`) character in `STRING`
that is present in `SET`, or zero if none is.
`VERIFY` is essentially the opposite: it returns the index of the first (or last) character
in `STRING` that is *not* present in `SET`, or zero if all are.
# Transformational intrinsic functions
This category comprises a large collection of intrinsic functions that
are collected together because they somehow transform their arguments
in a way that prevents them from being elemental.
All of them are pure, however.
Some general rules apply to the transformational intrinsic functions:
1. `DIM` arguments are optional; if present, the actual argument must be
a scalar integer of any kind.
1. When an optional `DIM` argument is absent, or an `ARRAY` or `MASK`
argument is a vector, the result of the function is scalar; otherwise,
the result is an array of the same shape as the `ARRAY` or `MASK`
argument with the dimension `DIM` removed from the shape.
1. When a function takes an optional `MASK` argument, it must be conformable
with its `ARRAY` argument if it is present, and the mask can be any kind
of `LOGICAL`. It can be scalar.
1. The type `numeric` here can be any kind of `INTEGER`, `REAL`, or `COMPLEX`.
1. The type `relational` here can be any kind of `INTEGER`, `REAL`, or `CHARACTER`.
1. The type `any` here denotes any intrinsic or derived type.
1. The notation `(..)` denotes an array of any rank (but not an assumed-rank array).
## Logical reduction transformational intrinsic functions
```
ALL(LOGICAL(k) MASK(..) [, DIM ]) -> LOGICAL(k)
ANY(LOGICAL(k) MASK(..) [, DIM ]) -> LOGICAL(k)
COUNT(LOGICAL(any) MASK(..) [, DIM, KIND=KIND(0) ]) -> INTEGER(KIND)
PARITY(LOGICAL(k) MASK(..) [, DIM ]) -> LOGICAL(k)
```
## Numeric reduction transformational intrinsic functions
```
IALL(INTEGER(k) ARRAY(..) [, DIM, MASK ]) -> INTEGER(k)
IANY(INTEGER(k) ARRAY(..) [, DIM, MASK ]) -> INTEGER(k)
IPARITY(INTEGER(k) ARRAY(..) [, DIM, MASK ]) -> INTEGER(k)
NORM2(REAL(k) X(..) [, DIM ]) -> REAL(k)
PRODUCT(numeric ARRAY(..) [, DIM, MASK ]) -> numeric
SUM(numeric ARRAY(..) [, DIM, MASK ]) -> numeric
```
`NORM2` generalizes `HYPOT` by computing `SQRT(SUM(X*X))` while avoiding spurious overflows.
## Extrema reduction transformational intrinsic functions
```
MAXVAL(relational(k) ARRAY(..) [, DIM, MASK ]) -> relational(k)
MINVAL(relational(k) ARRAY(..) [, DIM, MASK ]) -> relational(k)
```
### Locational transformational intrinsic functions
When the optional `DIM` argument is absent, the result is an `INTEGER(KIND)`
vector whose length is the rank of `ARRAY`.
When the optional `DIM` argument is present, the result is an `INTEGER(KIND)`
array of rank `RANK(ARRAY)-1` and shape equal to that of `ARRAY` with
the dimension `DIM` removed.
The optional `BACK` argument is a scalar LOGICAL value of any kind.
When present and `.TRUE.`, it causes the function to return the index
of the *last* occurence of the target or extreme value.
For `FINDLOC`, `ARRAY` may have any of the five intrinsic types, and `VALUE`
must a scalar value of a type for which `ARRAY==VALUE` or `ARRAY .EQV. VALUE`
is an acceptable expression.
```
FINDLOC(intrinsic ARRAY(..), scalar VALUE [, DIM, MASK, KIND=KIND(0), BACK=.FALSE. ])
MAXLOC(relational ARRAY(..) [, DIM, MASK, KIND=KIND(0), BACK=.FALSE. ])
MINLOC(relational ARRAY(..) [, DIM, MASK, KIND=KIND(0), BACK=.FALSE. ])
```
## Data rearrangement transformational intrinsic functions
The optional `DIM` argument to these functions must be a scalar integer of
any kind, and it takes a default value of 1 when absent.
```
CSHIFT(any ARRAY(..), INTEGER(any) SHIFT(..) [, DIM ]) -> same type/kind/shape as ARRAY
```
Either `SHIFT` is scalar or `RANK(SHIFT) == RANK(ARRAY) - 1` and `SHAPE(SHIFT)` is that of `SHAPE(ARRAY)` with element `DIM` removed.
```
EOSHIFT(any ARRAY(..), INTEGER(any) SHIFT(..) [, BOUNDARY, DIM ]) -> same type/kind/shape as ARRAY
```
* `SHIFT` is scalar or `RANK(SHIFT) == RANK(ARRAY) - 1` and `SHAPE(SHIFT)` is that of `SHAPE(ARRAY)` with element `DIM` removed.
* If `BOUNDARY` is present, it must have the same type and parameters as `ARRAY`.
* If `BOUNDARY` is absent, `ARRAY` must be of an intrinsic type, and the default `BOUNDARY` is the obvious `0`, `' '`, or `.FALSE.` value of `KIND(ARRAY)`.
* If `BOUNDARY` is present, either it is scalar, or `RANK(BOUNDARY) == RANK(ARRAY) - 1` and `SHAPE(BOUNDARY)` is that of `SHAPE(ARRAY)` with element `DIM`
removed.
```
PACK(any ARRAY(..), LOGICAL(any) MASK(..)) -> vector of same type and kind as ARRAY
```
* `MASK` is conformable with `ARRAY` and may be scalar.
* The length of the result vector is `COUNT(MASK)` if `MASK` is an array, else `SIZE(ARRAY)` if `MASK` is `.TRUE.`, else zero.
```
PACK(any ARRAY(..), LOGICAL(any) MASK(..), any VECTOR(n)) -> vector of same type, kind, and size as VECTOR
```
* `MASK` is conformable with `ARRAY` and may be scalar.
* `VECTOR` has the same type and kind as `ARRAY`.
* `VECTOR` must not be smaller than result of `PACK` with no `VECTOR` argument.
* The leading elements of `VECTOR` are replaced with elements from `ARRAY` as
if `PACK` had been invoked without `VECTOR`.
```
RESHAPE(any SOURCE(..), INTEGER(k) SHAPE(n) [, PAD(..), INTEGER(k2) ORDER(n) ]) -> SOURCE array with shape SHAPE
```
* If `ORDER` is present, it is a vector of the same size as `SHAPE`, and
contains a permutation.
* The element(s) of `PAD` are used to fill out the result once `SOURCE`
has been consumed.
```
SPREAD(any SOURCE, DIM, scalar INTEGER(any) NCOPIES) -> same type as SOURCE, rank=RANK(SOURCE)+1
TRANSFER(any SOURCE, any MOLD) -> scalar if MOLD is scalar, else vector; same type and kind as MOLD
TRANSFER(any SOURCE, any MOLD, scalar INTEGER(any) SIZE) -> vector(SIZE) of type and kind of MOLD
TRANSPOSE(any MATRIX(n,m)) -> matrix(m,n) of same type and kind as MATRIX
```
The shape of the result of `SPREAD` is the same as that of `SOURCE`, with `NCOPIES` inserted
at position `DIM`.
```
UNPACK(any VECTOR(n), LOGICAL(any) MASK(..), FIELD) -> type and kind of VECTOR, shape of MASK
```
`FIELD` has same type and kind as `VECTOR` and is conformable with `MASK`.
## Other transformational intrinsic functions
```
BESSEL_JN(INTEGER(n1) N1, INTEGER(n2) N2, REAL(k) X) -> REAL(k) vector (MAX(N2-N1+1,0))
BESSEL_YN(INTEGER(n1) N1, INTEGER(n2) N2, REAL(k) X) -> REAL(k) vector (MAX(N2-N1+1,0))
COMMAND_ARGUMENT_COUNT() -> scalar default INTEGER
DOT_PRODUCT(LOGICAL(k) VECTOR_A(n), LOGICAL(k) VECTOR_B(n)) -> LOGICAL(k) = ANY(VECTOR_A .AND. VECTOR_B)
DOT_PRODUCT(COMPLEX(any) VECTOR_A(n), numeric VECTOR_B(n)) = SUM(CONJG(VECTOR_A) * VECTOR_B)
DOT_PRODUCT(INTEGER(any) or REAL(any) VECTOR_A(n), numeric VECTOR_B(n)) = SUM(VECTOR_A * VECTOR_B)
MATMUL(numeric ARRAY_A(j), numeric ARRAY_B(j,k)) -> numeric vector(k)
MATMUL(numeric ARRAY_A(j,k), numeric ARRAY_B(k)) -> numeric vector(j)
MATMUL(numeric ARRAY_A(j,k), numeric ARRAY_B(k,m)) -> numeric matrix(j,m)
MATMUL(LOGICAL(n1) ARRAY_A(j), LOGICAL(n2) ARRAY_B(j,k)) -> LOGICAL vector(k)
MATMUL(LOGICAL(n1) ARRAY_A(j,k), LOGICAL(n2) ARRAY_B(k)) -> LOGICAL vector(j)
MATMUL(LOGICAL(n1) ARRAY_A(j,k), LOGICAL(n2) ARRAY_B(k,m)) -> LOGICAL matrix(j,m)
NULL([POINTER/ALLOCATABLE MOLD]) -> POINTER
REDUCE(any ARRAY(..), function OPERATION [, DIM, LOGICAL(any) MASK(..), IDENTITY, LOGICAL ORDERED=.FALSE. ])
REPEAT(CHARACTER(k,n) STRING, INTEGER(any) NCOPIES) -> CHARACTER(k,n*NCOPIES)
SELECTED_CHAR_KIND('DEFAULT' or 'ASCII' or 'ISO_10646' or ...) -> scalar default INTEGER
SELECTED_INT_KIND(scalar INTEGER(any) R) -> scalar default INTEGER
SELECTED_REAL_KIND([scalar INTEGER(any) P, scalar INTEGER(any) R, scalar INTEGER(any) RADIX]) -> scalar default INTEGER
SHAPE(SOURCE, KIND=KIND(0)) -> INTEGER(KIND)(RANK(SOURCE))
TRIM(CHARACTER(k,n) STRING) -> CHARACTER(k)
```
The type and kind of the result of a numeric `MATMUL` is the same as would result from
a multiplication of an element of ARRAY_A and an element of ARRAY_B.
The kind of the `LOGICAL` result of a `LOGICAL` `MATMUL` is the same as would result
from an intrinsic `.AND.` operation between an element of `ARRAY_A` and an element
of `ARRAY_B`.
Note that `DOT_PRODUCT` with a `COMPLEX` first argument operates on its complex conjugate,
but that `MATMUL` with a `COMPLEX` argument does not.
The `MOLD` argument to `NULL` may be omitted only in a context where the type of the pointer is known,
such as an initializer or pointer assignment statement.
At least one argument must be present in a call to `SELECTED_REAL_KIND`.
An assumed-rank array may be passed to `SHAPE`, and if it is associated with an assumed-size array,
the last element of the result will be -1.
## Coarray transformational intrinsic functions
```
FAILED_IMAGES([scalar TEAM_TYPE TEAM, KIND=KIND(0)]) -> INTEGER(KIND) vector
GET_TEAM([scalar INTEGER(?) LEVEL]) -> scalar TEAM_TYPE
IMAGE_INDEX(COARRAY, INTEGER(any) SUB(n) [, scalar TEAM_TYPE TEAM ]) -> scalar default INTEGER
IMAGE_INDEX(COARRAY, INTEGER(any) SUB(n), scalar INTEGER(any) TEAM_NUMBER) -> scalar default INTEGER
NUM_IMAGES([scalar TEAM_TYPE TEAM]) -> scalar default INTEGER
NUM_IMAGES(scalar INTEGER(any) TEAM_NUMBER) -> scalar default INTEGER
STOPPED_IMAGES([scalar TEAM_TYPE TEAM, KIND=KIND(0)]) -> INTEGER(KIND) vector
TEAM_NUMBER([scalar TEAM_TYPE TEAM]) -> scalar default INTEGER
THIS_IMAGE([COARRAY, DIM, scalar TEAM_TYPE TEAM]) -> default INTEGER
```
The result of `THIS_IMAGE` is a scalar if `DIM` is present or if `COARRAY` is absent,
and a vector whose length is the corank of `COARRAY` otherwise.
# Inquiry intrinsic functions
These are neither elemental nor transformational; all are pure.
## Type inquiry intrinsic functions
All of these functions return constants.
The value of the argument is not used, and may well be undefined.
```
BIT_SIZE(INTEGER(k) I(..)) -> INTEGER(k)
DIGITS(INTEGER or REAL X(..)) -> scalar default INTEGER
EPSILON(REAL(k) X(..)) -> scalar REAL(k)
HUGE(INTEGER(k) X(..)) -> scalar INTEGER(k)
HUGE(REAL(k) X(..)) -> scalar of REAL(k)
KIND(intrinsic X(..)) -> scalar default INTEGER
MAXEXPONENT(REAL(k) X(..)) -> scalar default INTEGER
MINEXPONENT(REAL(k) X(..)) -> scalar default INTEGER
NEW_LINE(CHARACTER(k,n) A(..)) -> scalar CHARACTER(k,1) = CHAR(10)
PRECISION(REAL(k) or COMPLEX(k) X(..)) -> scalar default INTEGER
RADIX(INTEGER(k) or REAL(k) X(..)) -> scalar default INTEGER, always 2
RANGE(INTEGER(k) or REAL(k) or COMPLEX(k) X(..)) -> scalar default INTEGER
TINY(REAL(k) X(..)) -> scalar REAL(k)
```
## Bound and size inquiry intrinsic functions
The results are scalar when `DIM` is present, and a vector of length=(co)rank(`(CO)ARRAY`)
when `DIM` is absent.
```
LBOUND(any ARRAY(..) [, DIM, KIND=KIND(0) ]) -> INTEGER(KIND)
LCOBOUND(any COARRAY [, DIM, KIND=KIND(0) ]) -> INTEGER(KIND)
SIZE(any ARRAY(..) [, DIM, KIND=KIND(0) ]) -> INTEGER(KIND)
UBOUND(any ARRAY(..) [, DIM, KIND=KIND(0) ]) -> INTEGER(KIND)
UCOBOUND(any COARRAY [, DIM, KIND=KIND(0) ]) -> INTEGER(KIND)
```
Assumed-rank arrays may be used with `LBOUND`, `SIZE`, and `UBOUND`.
## Object characteristic inquiry intrinsic functions
```
ALLOCATED(any type ALLOCATABLE ARRAY) -> scalar default LOGICAL
ALLOCATED(any type ALLOCATABLE SCALAR) -> scalar default LOGICAL
ASSOCIATED(any type POINTER POINTER [, same type TARGET]) -> scalar default LOGICAL
COSHAPE(COARRAY, KIND=KIND(0)) -> INTEGER(KIND) vector of length corank(COARRAY)
EXTENDS_TYPE_OF(A, MOLD) -> default LOGICAL
IS_CONTIGUOUS(any data ARRAY(..)) -> scalar default LOGICAL
PRESENT(OPTIONAL A) -> scalar default LOGICAL
RANK(any data A) -> scalar default INTEGER = 0 if A is scalar, SIZE(SHAPE(A)) if A is an array, rank if assumed-rank
SAME_TYPE_AS(A, B) -> scalar default LOGICAL
STORAGE_SIZE(any data A, KIND=KIND(0)) -> INTEGER(KIND)
```
The arguments to `EXTENDS_TYPE_OF` must be of extensible derived types or be unlimited polymorphic.
An assumed-rank array may be used with `IS_CONTIGUOUS` and `RANK`.
# Intrinsic subroutines
(*TODO*: complete these descriptions)
## One elemental intrinsic subroutine
```
INTERFACE
SUBROUTINE MVBITS(FROM, FROMPOS, LEN, TO, TOPOS)
INTEGER(k1) :: FROM, TO
INTENT(IN) :: FROM
INTENT(INOUT) :: TO
INTEGER(k2), INTENT(IN) :: FROMPOS
INTEGER(k3), INTENT(IN) :: LEN
INTEGER(k4), INTENT(IN) :: TOPOS
END SUBROUTINE
END INTERFACE
```
## Non-elemental intrinsic subroutines
```
CALL CPU_TIME(REAL INTENT(OUT) TIME)
```
The kind of `TIME` is not specified in the standard.
```
CALL DATE_AND_TIME([DATE, TIME, ZONE, VALUES])
```
* All arguments are `OPTIONAL` and `INTENT(OUT)`.
* `DATE`, `TIME`, and `ZONE` are scalar default `CHARACTER`.
* `VALUES` is a vector of at least 8 elements of `INTEGER(KIND >= 2)`.
```
CALL EVENT_QUERY(EVENT, COUNT [, STAT])
CALL EXECUTE_COMMAND_LINE(COMMAND [, WAIT, EXITSTAT, CMDSTAT, CMDMSG ])
CALL GET_COMMAND([COMMAND, LENGTH, STATUS, ERRMSG ])
CALL GET_COMMAND_ARGUMENT(NUMBER [, VALUE, LENGTH, STATUS, ERRMSG ])
CALL GET_ENVIRONMENT_VARIABLE(NAME [, VALUE, LENGTH, STATUS, TRIM_NAME, ERRMSG ])
CALL MOVE_ALLOC(ALLOCATABLE INTENT(INOUT) FROM, ALLOCATABLE INTENT(OUT) TO [, STAT, ERRMSG ])
CALL RANDOM_INIT(LOGICAL(k1) INTENT(IN) REPEATABLE, LOGICAL(k2) INTENT(IN) IMAGE_DISTINCT)
CALL RANDOM_NUMBER(REAL(k) INTENT(OUT) HARVEST(..))
CALL RANDOM_SEED([SIZE, PUT, GET])
CALL SYSTEM_CLOCK([COUNT, COUNT_RATE, COUNT_MAX])
```
## Atomic intrinsic subroutines
```
CALL ATOMIC_ADD(ATOM, VALUE [, STAT=])
CALL ATOMIC_AND(ATOM, VALUE [, STAT=])
CALL ATOMIC_CAS(ATOM, OLD, COMPARE, NEW [, STAT=])
CALL ATOMIC_DEFINE(ATOM, VALUE [, STAT=])
CALL ATOMIC_FETCH_ADD(ATOM, VALUE, OLD [, STAT=])
CALL ATOMIC_FETCH_AND(ATOM, VALUE, OLD [, STAT=])
CALL ATOMIC_FETCH_OR(ATOM, VALUE, OLD [, STAT=])
CALL ATOMIC_FETCH_XOR(ATOM, VALUE, OLD [, STAT=])
CALL ATOMIC_OR(ATOM, VALUE [, STAT=])
CALL ATOMIC_REF(VALUE, ATOM [, STAT=])
CALL ATOMIC_XOR(ATOM, VALUE [, STAT=])
```
## Collective intrinsic subroutines
```
CALL CO_BROADCAST
CALL CO_MAX
CALL CO_MIN
CALL CO_REDUCE
CALL CO_SUM
```
# Non-standard intrinsics
## PGI
```
AND, OR, XOR
LSHIFT, RSHIFT, SHIFT
ZEXT, IZEXT
COSD, SIND, TAND, ACOSD, ASIND, ATAND, ATAN2D
COMPL
DCMPLX
EQV, NEQV
INT8
JINT, JNINT, KNINT
LOC
```
## Intel
```
DCMPLX(X,Y), QCMPLX(X,Y)
DREAL(DOUBLE COMPLEX A) -> DOUBLE PRECISION
DFLOAT, DREAL
QEXT, QFLOAT, QREAL
DNUM, INUM, JNUM, KNUM, QNUM, RNUM - scan value from string
ZEXT
RAN, RANF
ILEN(I) = BIT_SIZE(I)
SIZEOF
MCLOCK, SECNDS
COTAN(X) = 1.0/TAN(X)
COSD, SIND, TAND, ACOSD, ASIND, ATAND, ATAN2D, COTAND - degrees
AND, OR, XOR
LSHIFT, RSHIFT
IBCHNG, ISHA, ISHC, ISHL, IXOR
IARG, IARGC, NARGS, NUMARG
BADDRESS, IADDR
CACHESIZE, EOF, FP_CLASS, INT_PTR_KIND, ISNAN, LOC
MALLOC
```
# Intrinsic Procedure Support in f18
This section gives an overview of the support inside f18 libraries for the
intrinsic procedures listed above.
It may be outdated, refer to f18 code base for the actual support status.
## Semantic Analysis
F18 semantic expression analysis phase detects intrinsic procedure references,
validates the argument types and deduces the return types.
This phase currently supports all the intrinsic procedures listed above but the ones in the table below.
| Intrinsic Category | Intrinsic Procedures Lacking Support |
| --- | --- |
| Coarray intrinsic functions | LCOBOUND, UCOBOUND, FAILED_IMAGES, GET_TEAM, IMAGE_INDEX, NUM_IMAGES, STOPPED_IMAGES, TEAM_NUMBER, THIS_IMAGE, COSHAPE |
| Object characteristic inquiry functions | ALLOCATED, ASSOCIATED, EXTENDS_TYPE_OF, IS_CONTIGUOUS, PRESENT, RANK, SAME_TYPE, STORAGE_SIZE |
| Type inquiry intrinsic functions | BIT_SIZE, DIGITS, EPSILON, HUGE, KIND, MAXEXPONENT, MINEXPONENT, NEW_LINE, PRECISION, RADIX, RANGE, TINY|
| Non-standard intrinsic functions | AND, OR, XOR, LSHIFT, RSHIFT, SHIFT, ZEXT, IZEXT, COSD, SIND, TAND, ACOSD, ASIND, ATAND, ATAN2D, COMPL, DCMPLX, EQV, NEQV, INT8, JINT, JNINT, KNINT, LOC, QCMPLX, DREAL, DFLOAT, QEXT, QFLOAT, QREAL, DNUM, NUM, JNUM, KNUM, QNUM, RNUM, RAN, RANF, ILEN, SIZEOF, MCLOCK, SECNDS, COTAN, IBCHNG, ISHA, ISHC, ISHL, IXOR, IARG, IARGC, NARGS, NUMARG, BADDRESS, IADDR, CACHESIZE, EOF, FP_CLASS, INT_PTR_KIND, ISNAN, MALLOC |
| Intrinsic subroutines |MVBITS (elemental), CPU_TIME, DATE_AND_TIME, EVENT_QUERY, EXECUTE_COMMAND_LINE, GET_COMMAND, GET_COMMAND_ARGUMENT, GET_ENVIRONMENT_VARIABLE, MOVE_ALLOC, RANDOM_INIT, RANDOM_NUMBER, RANDOM_SEED, SYSTEM_CLOCK |
| Atomic intrinsic subroutines | ATOMIC_ADD &al. |
| Collective intrinsic subroutines | CO_BROADCAST &al. |
## Intrinsic Function Folding
Fortran Constant Expressions can contain references to a certain number of
intrinsic functions (see Fortran 2018 standard section 10.1.12 for more details).
Constant Expressions may be used to define kind arguments. Therefore, the semantic
expression analysis phase must be able to fold references to intrinsic functions
listed in section 10.1.12.
F18 intrinsic function folding is either performed by implementations directly
operating on f18 scalar types or by using host runtime functions and
host hardware types. F18 supports folding elemental intrinsic functions over
arrays when an implementation is provided for the scalars (regardless of whether
it is using host hardware types or not).
The status of intrinsic function folding support is given in the sub-sections below.
### Intrinsic Functions with Host Independent Folding Support
Implementations using f18 scalar types enables folding intrinsic functions
on any host and with any possible type kind supported by f18. The intrinsic functions
listed below are folded using host independent implementations.
| Return Type | Intrinsic Functions with Host Independent Folding Support|
| --- | --- |
| INTEGER| ABS(INTEGER(k)), DIM(INTEGER(k), INTEGER(k)), DSHIFTL, DSHIFTR, IAND, IBCLR, IBSET, IEOR, INT, IOR, ISHFT, KIND, LEN, LEADZ, MASKL, MASKR, MERGE_BITS, POPCNT, POPPAR, SHIFTA, SHIFTL, SHIFTR, TRAILZ |
| REAL | ABS(REAL(k)), ABS(COMPLEX(k)), AIMAG, AINT, DPROD, REAL |
| COMPLEX | CMPLX, CONJG |
| LOGICAL | BGE, BGT, BLE, BLT |
### Intrinsic Functions with Host Dependent Folding Support
Implementations using the host runtime may not be available for all supported
f18 types depending on the host hardware types and the libraries available on the host.
The actual support on a host depends on what the host hardware types are.
The list below gives the functions that are folded using host runtime and the related C/C++ types.
F18 automatically detects if these types match an f18 scalar type. If so,
folding of the intrinsic functions will be possible for the related f18 scalar type,
otherwise an error message will be produced by f18 when attempting to fold related intrinsic functions.
| C/C++ Host Type | Intrinsic Functions with Host Standard C++ Library Based Folding Support |
| --- | --- |
| float, double and long double | ACOS, ACOSH, ASINH, ATAN, ATAN2, ATANH, COS, COSH, ERF, ERFC, EXP, GAMMA, HYPOT, LOG, LOG10, LOG_GAMMA, MOD, SIN, SQRT, SINH, SQRT, TAN, TANH |
| std::complex for float, double and long double| ACOS, ACOSH, ASIN, ASINH, ATAN, ATANH, COS, COSH, EXP, LOG, SIN, SINH, SQRT, TAN, TANH |
On top of the default usage of C++ standard library functions for folding described
in the table above, it is possible to compile f18 evaluate library with
[libpgmath](https://github.com/flang-compiler/flang/tree/master/runtime/libpgmath)
so that it can be used for folding. To do so, one must have a compiled version
of the libpgmath library available on the host and add
`-DLIBPGMATH_DIR=<path to the compiled shared libpgmath library>` to the f18 cmake command.
Libpgmath comes with real and complex functions that replace C++ standard library
float and double functions to fold all the intrinsic functions listed in the table above.
It has no long double versions. If the host long double matches an f18 scalar type,
C++ standard library functions will still be used for folding expressions with this scalar type.
Libpgmath adds the possibility to fold the following functions for f18 real scalar
types related to host float and double types.
| C/C++ Host Type | Additional Intrinsic Function Folding Support with Libpgmath (Optional) |
| --- | --- |
|float and double| BESSEL_J0, BESSEL_J1, BESSEL_JN (elemental only), BESSEL_Y0, BESSEL_Y1, BESSEL_Yn (elemental only), ERFC_SCALED |
Libpgmath comes in three variants (precise, relaxed and fast). So far, only the
precise version is used for intrinsic function folding in f18. It guarantees the greatest numerical precision.
### Intrinsic Functions with Missing Folding Support
The following intrinsic functions are allowed in constant expressions but f18
is not yet able to fold them. Note that there might be constraints on the arguments
so that these intrinsics can be used in constant expressions (see section 10.1.12 of Fortran 2018 standard).
ALL, ACHAR, ADJUSTL, ADJUSTR, ANINT, ANY, BESSEL_JN (transformational only),
BESSEL_YN (transformational only), BTEST, CEILING, CHAR, COUNT, CSHIFT, DOT_PRODUCT,
DIM (REAL only), DOT_PRODUCT, EOSHIFT, FINDLOC, FLOOR, FRACTION, HUGE, IACHAR, IALL,
IANY, IPARITY, IBITS, ICHAR, IMAGE_STATUS, INDEX, ISHFTC, IS_IOSTAT_END,
IS_IOSTAT_EOR, LBOUND, LEN_TRIM, LGE, LGT, LLE, LLT, LOGICAL, MATMUL, MAX, MAXLOC,
MAXVAL, MERGE, MIN, MINLOC, MINVAL, MOD (INTEGER only), MODULO, NEAREST, NINT,
NORM2, NOT, OUT_OF_RANGE, PACK, PARITY, PRODUCT, REPEAT, REDUCE, RESHAPE,
RRSPACING, SCAN, SCALE, SELECTED_CHAR_KIND, SELECTED_INT_KIND, SELECTED_REAL_KIND,
SET_EXPONENT, SHAPE, SIGN, SIZE, SPACING, SPREAD, SUM, TINY, TRANSFER, TRANSPOSE,
TRIM, UBOUND, UNPACK, VERIFY.
Coarray, non standard, IEEE and ISO_C_BINDINGS intrinsic functions that can be
used in constant expressions have currently no folding support at all.

View File

@ -0,0 +1,288 @@
<!--===- documentation/LabelResolution.md
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
-->
# Semantics: Resolving Labels and Construct Names
## Overview
After the Fortran input file(s) has been parsed into a syntax tree, the compiler must check that the program checks semantically. Target labels must be checked and violations of legal semantics should be reported to the user.
This is the detailed design document on how these labels will be semantically checked. Legal semantics may result in rewrite operations on the syntax tree. Semantics violations will be reported as errors to the user.
## Requirements
- Input: a parse tree that decomposes the Fortran program unit
- Output:
* **Success** returns true
(Additionally, the parse tree may be rewritten on success to capture the nested DO loop structure explicitly from any _label-do-stmt_ type loops.)
* **Failure** returns false, instantiates (a container of) error message(s) to indicate the problem(s)
### Label generalities (6.2.5)
Enforcement of the general label constraints. There are three sorts of label usage. Labels can serve
1. as a _label-do-stmt_ block range marker
1. as branching (control flow) targets
1. as specification annotations (`FORMAT` statements) for data transfer statements (I/O constructs)
Labels are related to the standard definition of inclusive scope. For example, control-flow arcs are not allowed to originate from one inclusive scope and target statements outside of that inclusive scope.
Inclusive scope is defined as a tree structure of nested scoping constructs. A statement, _s_, is said to be *in* the same inclusive scope as another statement, _t_, if and only if _s_ and _t_ are in the same scope or _t_ is in one of the enclosing scopes of _s_, otherwise _s_ is *not in* the same inclusive scope as _t_. (Inclusive scope is unidirectional and is always from innermost scopes to outermost scopes.)
#### Semantic Checks
- labels range from 1 to 99999, inclusive (6.2.5 note 2)
* handled automatically by the parser, but add a range check
- labels must be pairwise distinct within their program unit scope (6.2.5 para 2)
* if redundant labels appear &rarr; error redundant labels
* the total number of unique statement labels may have a limit
### Labels Used for `DO` Loop Ranging
#### _label-do-stmt_ (R1121)
A _label-do-stmt_ is a control construct that results in the iterative execution of a number of statements. A _label-do-stmt_ has a (possibly shared, _nonblock-do-construct_) _label_ that will be called the loop target label. The statements to be executed will be the range from the _label-do-stmt_ to the statement identified by the loop target label, inclusive. This range of statements will be called the loop's body and logically forms a _do-block_.
A _label-do-stmt_ is quite similar to a _block-do-construct_ in semantics, but the parse tree is different in that the parser does not impose a _do-block_ structure on the loop body.
In F18, the nonblock `DO` construct has been removed. For legacy support (through F08), we will need to handle nonblock `DO` constructs. In F18, the following legacy code is an error.
```fortran
DO 100 I = 1, 100
DO 100 J = 1, 100
...
100 CONTINUE
```
##### Semantic Checks
- the loop body target label must exist in the scope (F18:C1133; F08:C815, C817, C819)
* if the label does not appear, error of missing label
- the loop body target label must be, lexically, after the _label-do-stmt_ (R1119)
* if the label appears lexically preceding the `DO`, error of malformed `DO`
- control cannot transfer into the body from outside the _do-block_
* Exceptions (errors demoted to warnings)
- some implementations relax enforcement of this and allow `GOTO`s from the loop body to "extended ranges" and back again (PGI & gfortan appear to allow, NAG & Intel do not.)
- should some form of "extended ranges" for _do-constructs_ be supported, it should still be limited and not include parallel loops such as `DO CONCURRENT` or loops annotated with OpenACC or OpenMP directives.
* `GOTO`s into the `DO`s inclusive scope, error/warn of invalid transfer of control
- requires that the loop terminating statement for a _label-do-stmt_ be either an `END DO` or a `CONTINUE`
* Exception
- earlier standards allowed other statements to be terminators
Semantics for F08 and earlier that support sharing the loop terminating statement in a _nonblock-do-construct_ between multiple loops
- some statements cannot be _do-term-action-stmt_ (F08:C816)
* a _do-term-action-stmt_ is an _action-stmt_ but does not include _arithmetic-if-stmt_, _continue-stmt_, _cycle-stmt_, _end-function-stmt_, _end-mp-subprogram-stmt_, _end-program-stmt_, _end-subroutine-stmt_, _error-stop-stmt_, _exit-stmt_, _goto-stmt_, _return-stmt_, or _stop-stmt_
- if the term action statement is forbidden, error invalid statement in `DO` loop term position
- some statements cannot be _do-term-shared-stmt_ (F08:C818)
* this is the case as in our above example where two different nested loops share the same terminating statement (`100 continue`)
* a _do-term-shared-stmt_ is an _action-stmt_ with all the same exclusions as a _do-term-action-stmt_ except a _continue-stmt_ **is** allowed
- if the term shared action statement is forbidden, error invalid statement in term position
If the `DO` loop is a `DO CONCURRENT` construct, there are additional constraints (11.1.7.5).
- a _return-stmt_ is not allowed (C1136)
- image control statements are not allowed (C1137)
- branches must be from a statement and to a statement that both reside within the `DO CONCURRENT` (C1138)
- impure procedures shall not be called (C1139)
- deallocation of polymorphic objects is not allowed (C1140)
- references to `IEEE_GET_FLAG`, `IEEE_SET_HALTING_MODE`, and `IEEE_GET_HALTING_MODE` cannot appear in the body of a `DO CONCURRENT` (C1141)
- the use of the `ADVANCE=` specifier by an I/O statement in the body of a `DO CONCURRENT` is not allowed (11.1.7.5, para 5)
### Labels Used in Branching
#### _goto-stmt_ (11.2.2, R1157)
A `GOTO` statement is a simple, direct transfer of control from the `GOTO` to the labelled statement.
##### Semantic Checks
- the labelled statement that is the target of a `GOTO` (11.2.1 constraints)
- must refer to a label that is in inclusive scope of the computed `GOTO` statement (C1169)
* if a label does not exist, error nonexistent label
* if a label is out of scope, error out of inclusive scope
- the branch target statement must be valid
* if the statement is not allowed as a branch target, error not a valid branch target
- the labelled statement must be a branch target statement
* a branch target statement is any of _action-stmt_, _associate-stmt_, _end-associate-stmt_, _if-then-stmt_, _end-if-stmt_, _select-case-stmt_, _end-select-stmt_, _select-rank-stmt_, _end-select-rank-stmt_, _select-type-stmt_, _end-select-type-stmt_, _do-stmt_, _end-do-stmt_, _block-stmt_, _end-block-stmt_, _critical-stmt_, _end-critical-stmt_, _forall-construct-stmt_, _forall-stmt_, _where-construct-stmt_, _end-function-stmt_, _end-mp-subprogram-stmt_, _end-program-stmt_, or _end-subroutine-stmt_. (11.2.1)
* Some deleted features that were _action-stmt_ in older standards include _arithmetic-if-stmt_, _assign-stmt_, _assigned-goto-stmt_, and _pause-stmt_. For legacy mode support, these statements should be considered _action-stmt_.
#### _computed-goto-stmt_ (11.2.3, R1158)
The computed `GOTO` statement is analogous to a `switch` statement in C++.
```fortran
GOTO ( label-list ) [,] scalar-int-expr
```
##### Semantics Checks
- each label in _label-list_ (11.2.1 constraints, same as `GOTO`)
- must refer to a label that is in inclusive scope of the computed `GOTO` statement (C1170)
* if a label does not exist, error nonexistent label
* if a label is out of scope, error out of inclusive scope
- the branch target statement must be valid
* if the statement is not allowed as a branch target, error not a valid branch target
- the _scalar-int-expr_ needs to have `INTEGER` type
* check the type of the expression (type checking done elsewhere)
#### R853 _arithmetic-if-stmt_ (F08:8.2.4)
This control-flow construct is deleted in F18.
```fortran
IF (scalar-numeric-expr) label1,label2,label3
```
The arithmetic if statement is like a three-way branch operator. If the scalar numeric expression is less than zero goto _label-1_, else if the variable is equal to zero goto _label-2_, else if the variable is greater than zero goto _label-3_.
##### Semantics Checks
- the labels in the _arithmetic-if-stmt_ triple must all be present in the inclusive scope (F08:C848)
* if a label does not exist, error nonexistent label
* if a label is out of scope, error out of inclusive scope
- the _scalar-numeric-expr_ must not be `COMPLEX` (F08:C849)
* check the type of the expression (type checking done elsewhere)
#### _alt-return-spec_ (15.5.1, R1525)
These are a Fortran control-flow construct for combining a return from a subroutine with a branch to a labelled statement in the calling routine all in one operation. A typical implementation is for the subroutine to return a hidden integer, which is used as a key in the calling code to then, possibly, branch to a labelled statement in inclusive scope.
The labels are passed by the calling routine. We want to check those labels at the call-site, that is instances of _alt-return-spec_.
##### Semantics Checks
- each _alt-return-spec_ (11.2.1 constraints, same as `GOTO`)
- must refer to a label that is in inclusive scope of the `CALL` statement
* if a label does not exist, error nonexistent label
* if a label is out of scope, error out of inclusive scope
- the branch target statement must be valid
* if the statement is not allowed as a branch target, error not a valid branch target
#### **END**, **EOR**, **ERR** specifiers (12.11)
These specifiers can appear in I/O statements and can transfer control to specific labelled statements under exceptional conditions like end-of-file, end-of-record, and other error conditions. (The PGI compiler adds code to test the results from the runtime routines to determine if these branches should take place.)
##### Semantics Checks
- each END, EOR, and ERR specifier (11.2.1 constraints, same as `GOTO`)
- must refer to a label that is in inclusive scope of the I/O statement
* if a label does not exist, error nonexistent label
* if a label is out of scope, error out of inclusive scope
- the branch target statement must be valid
* if the statement is not allowed as a branch target, error not a valid branch target
#### _assigned-goto-stmt_ and _assign-stmt_ (F90:8.2.4)
Deleted feature since Fortran 95.
The _assigned-goto-stmt_ and _assign-stmt_ were _action-stmt_ in the Fortran 90 standard. They are included here for completeness. This pair of obsolete statements can (will) be enabled as part of the compiler's legacy Fortran support.
The _assign-stmt_ stores a _label_ in an integer variable. The _assigned-goto-stmt_ will then transfer control to the _label_ stored in that integer variable.
```fortran
ASSIGN 10 TO i
...
GOTO i (10,20,30)
```
##### Semantic Checks
- an _assigned-goto-stmt_ cannot be a _do-term-action-stmt_ (F90:R829)
- an _assigned-goto-stmt_ cannot be a _do-term-shared-stmt_ (F90:R833)
- constraints from (F90:R839)
- each _label_ in an optional _label-list_ must be the statement label of a branch target statement that appears in the same scoping unit as the _assigned-goto-stmt_
- _scalar-int-variable_ (`i` in the example above) must be named and of type default integer
- an integer variable that has been assigned a label may only be referenced in an _assigned-goto_ or as a format specifier in an I/O statement
- when an I/O statement with a _format-specifier_ that is an integer variable is executed or when an _assigned-goto_ is executed, the variable must have been assigned a _label_
- an integer variable can only be assigned a label via the `ASSIGN` statement
- the label assigned to the variable must be in the same scoping unit as the _assigned-goto_ that branches to the _label_ value
- if the parameterized list of labels is present, the label value assigned to the integer variable must appear in that _label-list_
- a distinct _label_ can appear more than once in the _label-list_
Some interpretation is needed as the terms of the older standard are different.
A "scoping unit" is defined as
- a derived-type definition
- a procedure interface body, excluding derived-types and interfaces contained within it
- a program unit or subprogram, excluding derived-types, interfaces, and subprograms contained within it
This is a more lax definition of scope than inclusive scope.
A _named variable_ distinguishes a variable such as, `i`, from an element of an array, `a(i)`, for example.
### Labels used in I/O
#### Data transfer statements
In data transfer (I/O) statements (e.g., `READ`), the user can specify a `FMT=` specifier that can take a label as its argument. (R1215)
##### Semantic Checks
- if the `FMT=` specifier has a label as its argument (C1230)
- the label must correspond to a `FORMAT` statement
* if the statement is not a `FORMAT`, error statement must be a `FORMAT`
- the labelled `FORMAT` statement must be in the same inclusive scope as the originating data transfer statement (also in 2008)
* if the label statement does not exist, error label does not exist
* if the label statement is not in scope, error label is not in inclusive scope
- Exceptions (errors demoted to warnings)
- PGI extension: referenced `FORMAT` statements may appear in a host procedure
- Possible relaxation: the scope of the referenced `FORMAT` statement may be ignored, allowing a `FORMAT` to be referenced from any scope in the compilation.
### Construct Name generalities
Various Fortran constructs can have names. These include
- the `WHERE` construct (10.2.3)
- the `FORALL` construct (10.2.4)
- the `ASSOCIATE` construct (11.1.3)
- the `BLOCK` construct (11.1.4)
- the `CHANGE TEAM` construct (11.1.5)
- the `CRITICAL` construct (11.1.6)
- the `DO` construct (11.1.7)
- the `IF` construct (11.1.8)
- the `SELECT CASE` construct (11.1.9)
- the `SELECT RANK` construct (11.1.10)
- the `SELECT TYPE` construct (11.1.11)
#### Semantics Checks
A construct name is a name formed under 6.2.2. A name is an identifier. Identifiers are parsed by the parser.
- the maximum length of a name is 63 characters (C601)
Names must either not be given for the construct or used throughout when specified.
- if a construct is given a name, the construct's `END` statement must also specify the same name (`WHERE` C1033, `FORALL` C1035, ...)
- `WHERE` has additional `ELSEWHERE` clauses
- `IF` has additional `ELSE IF` and `ELSE` clauses
- `SELECT CASE` has additional `CASE` clauses
- `SELECT RANK` has additional `RANK` clauses
- `SELECT TYPE` has additional _type-guard-stmt_
These additional statements must meet the same constraint as the `END` of the construct. Names must match, if present, or there must be no names for any of the clauses.
### `CYCLE` statement (11.1.7.4.4)
The `CYCLE` statement takes an optional _do-construct-name_.
#### Semantics Checks
- if the `CYCLE` has a _construct-name_, then the `CYCLE` statement must appear within that named _do-construct_ (C1134)
- if the `CYCLE` does not have a _do-construct-name_, the `CYCLE` statement must appear within a _do-construct_ (C1134)
### `EXIT` statement (11.1.12)
The `EXIT` statement takes an optional _construct-name_.
#### Semantics Checks
- if the `EXIT` has a _construct-name_, then the `EXIT` statement must appear within that named construct (C1166)
- if the `EXIT` does not have a _construct-name_, the `EXIT` statement must appear within a _do-construct_ (C1166)
- an _exit-stmt_ must not appear in a `DO CONCURRENT` if the `EXIT` belongs to the `DO CONCURRENT` or an outer construct enclosing the `DO CONCURRENT` (C1167)
- an _exit-stmt_ must not appear in a `CHANGE TEAM` (`CRITICAL`) if the `EXIT` belongs to an outer construct enclosing the `CHANGE TEAM` (`CRITICAL`) (C1168)

View File

@ -0,0 +1,158 @@
<!--===- documentation/ModFiles.md
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
-->
# Module Files
Module files hold information from a module that is necessary to compile
program units that depend on the module.
## Name
Module files must be searchable by module name. They are typically named
`<modulename>.mod`. The advantage of using `.mod` is that it is consistent with
other compilers so users will know what they are. Also, makefiles and scripts
often use `rm *.mod` to clean up.
The disadvantage of using the same name as other compilers is that it is not
clear which compiler created a `.mod` file and files from multiple compilers
cannot be in the same directory. This could be solved by adding something
between the module name and extension, e.g. `<modulename>-f18.mod`.
## Format
Module files will be Fortran source.
Declarations of all visible entities will be included, along with private
entities that they depend on.
Entity declarations that span multiple statements will be collapsed into
a single *type-declaration-statement*.
Executable statements will be omitted.
### Header
There will be a header containing extra information that cannot be expressed
in Fortran. This will take the form of a comment or directive
at the beginning of the file.
If it's a comment, the module file reader would have to strip it out and
perform *ad hoc* parsing on it. If it's a directive the compiler could
parse it like other directives as part of the grammar.
Processing the header before parsing might result in better error messages
when the `.mod` file is invalid.
Regardless of whether the header is a comment or directive we can use the
same string to introduce it: `!mod$`.
Information in the header:
- Magic string to confirm it is an f18 `.mod` file
- Version information: to indicate the version of the file format, in case it changes,
and the version of the compiler that wrote the file, for diagnostics.
- Checksum of the body of the current file
- Modules we depend on and the checksum of their module file when the current
module file is created
- The source file that produced the `.mod` file? This could be used in error messages.
### Body
The body will consist of minimal Fortran source for the required declarations.
The order will match the order they first appeared in the source.
Some normalization will take place:
- extraneous spaces will be removed
- implicit types will be made explicit
- attributes will be written in a consistent order
- entity declarations will be combined into a single declaration
- function return types specified in a *prefix-spec* will be replaced by
an entity declaration
- etc.
#### Symbols included
All public symbols from the module need to be included.
In addition, some private symbols are needed:
- private types that appear in the public API
- private components of non-private derived types
- private parameters used in non-private declarations (initial values, kind parameters)
- others?
It might be possible to anonymize private names if users don't want them exposed
in the `.mod` file. (Currently they are readable in PGI `.mod` files.)
#### USE association
A module that contains `USE` statements needs them represented in the
`.mod` file.
Each use-associated symbol will be written as a separate *use-only* statement,
possibly with renaming.
Alternatives:
- Emit a single `USE` for each module, listing all of the symbols that were
use-associated in the *only-list*.
- Detect when all of the symbols from a module are imported (either by a *use-stmt*
without an *only-list* or because all of the public symbols of the module
have been listed in *only-list*s). In that case collapse them into a single *use-stmt*.
- Emit the *use-stmt*s that appeared in the original source.
## Reading and writing module files
### Options
The compiler will have command-line options to specify where to search
for module files and where to write them. By default it will be the current
directory for both.
For PGI, `-I` specifies directories to search for include files and module
files. `-module` specifics a directory to write module files in as well as to
search for them. gfortran is similar except it uses `-J` instead of `-module`.
The search order for module files is:
1. The `-module` directory (Note: for gfortran the `-J` directory is not searched).
2. The current directory
3. The `-I` directories in the order they appear on the command line
### Writing module files
When writing a module file, if the existing one matches what would be written,
the timestamp is not updated.
Module files will be written after semantics, i.e. after the compiler has
determined the module is valid Fortran.<br>
**NOTE:** PGI does create `.mod` files sometimes even when the module has a
compilation error.
Question: If the compiler can get far enough to determine it is compiling a module
but then encounters an error, should it delete the existing `.mod` file?
PGI does not, gfortran does.
### Reading module files
When the compiler finds a `.mod` file it needs to read, it firsts checks the first
line and verifies it is a valid module file. It can also verify checksums of
modules it depends on and report if they are out of date.
If the header is valid, the module file will be run through the parser and name
resolution to recreate the symbols from the module. Once the symbol table is
populated the parse tree can be discarded.
When processing `.mod` files we know they are valid Fortran with these properties:
1. The input (without the header) is already in the "cooked input" format.
2. No preprocessing is necessary.
3. No errors can occur.
## Error messages referring to modules
With this design, diagnostics can refer to names in modules and can emit a
normalized declaration of an entity but not point to its location in the
source.
If the header includes the source file it came from, that could be included in
a diagnostic but we still wouldn't have line numbers.
To provide line numbers and character positions or source lines as the user
wrote them we would have to save some amount of provenance information in the
module file as well.

View File

@ -0,0 +1,464 @@
#===-- documentation/OpenMP-4.5-grammar.txt --------------------------------===#
#
# 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
#
#===------------------------------------------------------------------------===#
# OpenMP 4.5 Specifications
2 omp-directive -> sentinel directive-name [clause[ [,] clause]...]
2.1.1 sentinel -> !$omp | c$omp | *$omp
2.1.2 sentinel -> !$omp
# directive-name
2.5 parallel -> PARALLEL [parallel-clause[ [,] parallel-clause]...]
parallel-clause -> if-clause |
num-threads-clause |
default-clause |
private-clause |
firstprivate-clause |
shared-clause |
copyin-clause |
reduction-clause |
proc-bind-clause
2.5 end-parallel -> END PARALLEL
2.7.1 do -> DO [do-clause[ [,] do-clause]...]
do-clause -> private-clause |
firstprivate-clause |
lastprivate-clause |
linear-clause |
reduction-clause |
schedule-clause |
collapse-clause |
ordered-clause
2.7.1 end-do -> END DO [nowait-clause]
2.7.2 sections -> SECTIONS [sections-clause[ [,] sections-clause]...]
sections-clause -> private-clause |
firstprivate-clause |
lastprivate-clause |
reduction-clause
2.7.2 section -> SECTION
2.7.2 end-sections -> END SECTIONS [nowait-clause]
2.7.3 single -> SINGLE [single-clause[ [,] single-clause]...]
single-clause -> private-clause |
firstprivate-clause
2.7.3 end-single -> END SINGLE [end-single-clause[ [,] end-single-clause]...]
end-single-clause -> copyprivate-clause |
nowait-clause
2.7.4 workshare -> WORKSHARE
2.7.4 end-workshare -> END WORKSHARE [nowait-clause]
2.8.1 simd -> SIMD [simd-clause[ [,] simd-clause]...]
simd-clause -> safelen-clause |
simdlen-clause |
linear-clause |
aligned-clause |
private-clause |
lastprivate-clause |
reduction-clause |
collapse-clause
2.8.1 end-simd -> END SIMD
2.8.2 declare-simd -> DECLARE SIMD [(proc-name)] [declare-simd-clause[ [,] declare-simd-clause]...]
declare-simd-clause -> simdlen-clause |
linear-clause |
aligned-clause |
uniform-clause |
inbranch-clause |
notinbranch-clause
2.8.3 do-simd -> DO SIMD [do-simd-clause[ [,] do-simd-clause]...]
do-simd-clause -> do-clause |
simd-clause
2.8.3 end-do-simd -> END DO SIMD [nowait-clause]
2.9.1 task -> TASK [task-clause[ [,] task-clause]...]
task-clause -> if-clause |
final-clause |
untied-clause |
default-clause |
mergeable-clause |
private-clause |
firstprivate-clause |
shared-clause |
depend-clause |
priority-clause
2.9.1 end-task -> END TASK
2.9.2 taskloop -> TASKLOOP [taskloop-clause[ [,] taskloop-clause]...]
taskloop-clause -> if-clause |
shared-clause |
private-clause |
firstprivate-clause |
lastprivate-clause |
default-clause |
grainsize-clause |
num-tasks-clause |
collapse-clause |
final-clause |
priority-clause |
untied-clause |
mergeable-clause |
nogroup-clause
2.9.2 end-taskloop -> END TASKLOOP
2.9.3 taskloop-simd -> TASKLOOP SIMD [taskloop-simd-clause[ [,] taskloop-simd-clause]...]
taskloop-simd-clause -> taskloop-clause |
simd-clause
2.9.3 end-taskloop-simd -> END TASKLOOP SIMD
2.9.4 taskyield -> TASKYIELD
2.10.1 target-data -> TARGET DATA target-data-clause[ [ [,] target-data-clause]...]
target-data-clause -> if-clause |
device-clause |
map-clause |
use-device-ptr-clause
2.10.1 end-target-data -> END TARGET DATA
2.10.2 target-enter-data -> TARGET ENTER DATA [ target-enter-data-clause[ [,] target-enter-data-clause]...]
target-enter-data-clause -> if-clause |
device-clause |
map-clause |
depend-clause |
nowait-clause
2.10.3 target-exit-data -> TARGET EXIT DATA [ target-exit-data-clause[ [,] target-exit-data-clause]...]
target-exit-data-clause -> if-clause |
device-clause |
map-clause |
depend-clause |
nowait-clause
2.10.4 target -> TARGET [target-clause[ [,] target-clause]...]
target-clause -> if-clause |
device-clause |
private-clause |
firstprivate-clause |
map-clause |
is-device-ptr-clause |
defaultmap-clause |
nowait-clause |
depend-clause
2.10.4 end-target -> END TARGET
2.10.5 target-update -> TARGET UPDATE target-update-clause[ [ [,] target-update-clause]...]
target-update-clause -> motion-clause |
if-clause |
device-clause |
nowait-clause |
depend-clause
motion-clause -> to-clause |
from-clause
2.10.6 declare-target -> DECLARE TARGET (extended-list) |
DECLARE TARGET [declare-target-clause[ [,] declare-target-clause]...]
declare-target-clause -> to-clause |
link-clause
2.10.7 teams -> TEAMS [teams-clause[ [,] teams-clause]...]
teams-clause -> num-teams-clause |
thread-limit-clause |
default-clause |
private-clause |
firstprivate-clause |
shared-clause |
reduction-clause
2.10.7 end-teams -> END TEAMS
2.10.8 distribute -> DISTRIBUTE [distribute-clause[ [,] distribute-clause]...]
distribute-clause -> private-clause |
firstprivate-clause |
lastprivate-clause |
collapse-clause |
dist-schedule-clause
2.10.8 end-distribute -> END DISTRIBUTE
2.10.9 distribute-simd -> DISTRIBUTE SIMD [distribute-simd-clause[ [,] distribute-simd-clause]...]
distribute-simd-clause -> distribute-clause |
simd-clause
2.10.9 end-distribute-simd -> END DISTRIBUTE SIMD
2.10.10 distribute-parellel-do ->
DISTRIBUTE PARALLEL DO [distribute-parallel-do-clause[ [,] distribute-parallel-do-clause]...]
distribute-parallel-do-clause -> distribute-clause |
parallel-do-clause
2.10.10 end-distribute-parellel-do -> END DISTRIBUTE PARALLEL DO
2.10.11 distribute-parallel-do-simd ->
DISTRIBUTE PARALLEL DO SIMD [distribute-parallel-do-simd-clause[ [,] distribute-parallel-do-simd-clause]...]
distribute-parallel-do-simd-clause -> distribute-clause |
parallel-do-simd-clause
2.10.11 end-distribute-parallel-do-simd -> END DISTRIBUTE PARALLEL DO SIMD
2.11.1 parallel-do -> PARALLEL DO [parallel-do-clause[ [,] parallel-do-clause]...]
parallel-do-clause -> parallel-clause |
do-clause
2.11.1 end-parallel-do -> END PARALLEL DO
2.11.2 parallel-sections -> PARALLEL SECTIONS [parallel-sections-clause[ [,] parallel-sections-clause]...]
parallel-sections-clause -> parallel-clause |
sections-clause
2.11.2 end-parallel-sections -> END PARALLEL SECTIONS
2.11.3 parallel-workshare -> PARALLEL WORKSHARE [parallel-workshare-clause[ [,] parallel-workshare-clause]...]
parallel-workshare-clause -> parallel-clause
2.11.3 end-parallel-workshare -> END PARALLEL WORKSHARE
2.11.4 parallel-do-simd -> PARALLEL DO SIMD [parallel-do-simd-clause[ [,] parallel-do-simd-clause]...]
parallel-do-simd-clause -> parallel-clause |
do-simd-clause
2.11.4 end-parallel-do-simd -> END PARALLEL DO SIMD
2.11.5 target-parallel -> TARGET PARALLEL [target-parallel-clause[ [,] target-parallel-clause]...]
target-parallel-clause -> target-clause |
parallel-clause
2.11.5 end-target-parallel -> END TARGET PARALLEL
2.11.6 target-parallel-do -> TARGET PARALLEL DO [target-parallel-do-clause[ [,] target-parallel-do-clause]...]
target-parallel-do-clause -> target-clause |
parallel-do-clause
2.11.6 end-target-parallel-do -> END TARGET PARALLEL DO
2.11.7 target-parallel-do-simd ->
TARGET PARALLEL DO SIMD [target-parallel-do-simd-clause[ [,] target-parallel-do-simd-clause]...]
target-parallel-do-simd-clause -> target-clause |
parallel-do-simd-clause
2.11.7 end-target-parallel-do-simd -> END TARGET PARALLEL DO SIMD
2.11.8 target-simd -> TARGET SIMD [target-simd-clause[ [,] target-simd-clause]...]
target-simd-clause -> target-clause |
simd-clause
2.11.8 end-target-simd -> END TARGET SIMD
2.11.9 target-teams -> TARGET TEAMS [target-teams-clause[ [,] target-teams-clause]...]
target-teams-clause -> target-clause |
teams-clause
2.11.9 end-target-teams -> END TARGET TEAMS
2.11.10 teams-distribute -> TEAMS DISTRIBUTE [teams-distribute-clause[ [,] teams-distribute-clause]...]
teams-distribute-clause -> teams-clause |
distribute-clause
2.11.10 end-teams-distribute -> END TEAMS DISTRIBUTE
2.11.11 teams-distribute-simd ->
TEAMS DISTRIBUTE SIMD [teams-distribute-simd-clause[ [,] teams-distribute-simd-clause]...]
teams-distribute-simd-clause -> teams-clause |
distribute-simd-clause
2.11.11 end-teams-distribute-simd -> END TEAMS DISTRIBUTE SIMD
2.11.12 target-teams-distribute ->
TARGET TEAMS DISTRIBUTE [target-teams-distribute-clause[ [,] target-teams-distribute-clause]...]
target-teams-distribute-clause -> target-clause |
teams-distribute-clause
2.11.12 end-target-teams-distribute -> END TARGET TEAMS DISTRIBUTE
2.11.13 target-teams-distribute-simd ->
TARGET TEAMS DISTRIBUTE SIMD [target-teams-distribute-simd-clause[ [,] target-teams-distribute-simd-clause]...]
target-teams-distribute-simd-clause -> target-clause |
teams-distribute-simd-clause
2.11.13 end-target-teams-distribute-simd -> END TARGET TEAMS DISTRIBUTE SIMD
2.11.14 teams-distribute-parallel-do ->
TEAMS DISTRIBUTE PARALLEL DO [teams-distribute-parallel-do-clause[ [,] teams-distribute-parallel-do-clause]...]
teams-distribute-parallel-do-clause -> teams-clause |
distribute-parallel-do-clause
2.11.14 end-teams-distribute-parallel-do -> END TEAMS DISTRIBUTE PARALLEL DO
2.11.15 target-teams-distribute-parallel-do ->
TARGET TEAMS DISTRIBUTE PARALLEL DO [target-teams-distribute-parallel-do-clause[ [,] target-teams-distribute-parallel-do-clause]...]
target-teams-distribute-parallel-do-clause -> target-clause |
teams-distribute-parallel-do-clause
2.11.15 end-target-teams-distribute-parallel-do -> END TARGET TEAMS DISTRIBUTE PARALLEL DO
2.11.16 teams-distribute-parallel-do-simd ->
TEAMS DISTRIBUTE PARALLEL DO SIMD [teams-distribute-parallel-do-simd-clause[ [,] teams-distribute-parallel-do-simd-clause]...]
teams-distribute-parallel-do-simd-clause -> teams-clause |
distribute-parallel-do-simd-clause
2.11.16 end-teams-distribute-parallel-do-simd -> END TEAMS DISTRIBUTE PARALLEL DO SIMD
2.11.17 target-teams-distribute-parallel-do-simd ->
TARGET TEAMS DISTRIBUTE PARALLEL DO SIMD [target-teams-distribute-parallel-do-simd-clause[ [,] target-teams-distribute-parallel-do-simd-clause]...]
target-teams-distribute-parallel-do-simd-clause -> target-clause |
teams-distribute-parallel-do-simd-clause
2.11.17 end-target-teams-distribute-parallel-do-simd -> END TARGET TEAMS DISTRIBUTE PARALLEL DO SIMD
2.13.1 master -> MASTER
2.13.1 end-master -> END MASTER
2.13.2 critical -> CRITICAL [(name) [HINT(hint-expr)]]
2.13.2 end-critical -> END CRITICAL [(name)]
2.13.3 barrier -> BARRIER
2.13.4 taskwait -> TASKWAIT
2.13.5 taskgroup -> TASKGROUP
2.13.5 end-taskgroup -> END TASKGROUP
2.13.6 atomic -> ATOMIC [seq_cst[,]] atomic-clause [[,]seq_cst] |
ATOMIC [seq_cst]
atomic-clause -> READ | WRITE | UPDATE | CAPTURE
2.13.7 flush -> FLUSH [(variable-name-list)]
2.13.8 ordered -> ORDERED ordered-construct-clause [[[,] ordered-construct-clause]...]
ordered-construct-clause -> depend-clause
2.13.8 end-ordered -> END ORDERED
2.14.1 cancel -> CANCEL construct-type-clause [ [,] if-clause]
construct-type-clause -> PARALLEL |
SECTIONS |
DO |
TASKGROUP
2.14.2 cancellation-point -> CANCELLATION POINT construct-type-clause
2.15.2 threadprivate -> THREADPRIVATE (variable-name-list)
2.16 declare-reduction -> DECLARE REDUCTION (reduction-identifier : type-list : combiner) [initializer-clause]
# Clauses
2.5 proc-bind-clause -> PROC_BIND (MASTER | CLOSE | SPREAD)
2.5 num-threads-clause -> NUM_THREADS (scalar-int-expr)
2.7.1 schedule-clause -> SCHEDULE ([sched-modifier] [, sched-modifier]:]
kind[, chunk_size])
2.7.1 kind -> STATIC | DYNAMIC | GUIDED | AUTO | RUNTIME
2.7.1 sched-modifier -> MONOTONIC | NONMONOTONIC | SIMD
2.7.1 chunk_size -> scalar-int-expr
2.7.1 collapse-clause -> COLLAPSE (scalar-constant)
2.7.1 ordered-clause -> ORDERED [(scalar-constant)]
2.7.1 nowait-clause -> NOWAIT
2.8.1 aligned-clause -> ALIGNED (variable-name-list[ : scalar-constant])
2.8.1 safelen-clause -> SAFELEN (scalar-constant)
2.8.1 simdlen-clause -> SIMDLEN (scalar-contant)
2.8.2 uniform-clause -> UNIFORM (dummy-arg-name-list)
2.8.2 inbranch-clause -> INBRANCH
2.8.2 notinbranch-clause -> NOTINBRANCH
2.13.9 depend-clause -> DEPEND (((IN | OUT | INOUT) : variable-name-list) |
SOURCE |
SINK : vec)
vec -> iterator [+/- scalar-int-expr],..., iterator[...]
2.9.2 num-tasks-clause -> NUM_TASKS (scalar-int-expr)
2.9.2 grainsize-clause -> GRAINSIZE (scalar-int-expr)
2.9.2 nogroup-clause -> NOGROUP
2.9.2 untied-clause -> UNTIED
2.9.2 priority-clause -> PRIORITY (scalar-int-expr)
2.9.2 mergeable-clause -> MERGEABLE
2.9.2 final-clause -> FINAL (scalar-int-expr)
2.10.1 use-device-ptr-clause -> USE_DEVICE_PTR (variable-name-list)
2.10.1 device-clause -> DEVICE (scalar-integer-expr)
2.10.4 is-device-ptr-clause -> IS_DEVICE_PTR (variable-name-list)
2.10.5 to-clause -> TO (variable-name-list)
2.10.5 from-clause -> FROM (variable-name-list)
2.10.6 link-clause -> LINK (variable-name-list)
2.10.7 num-teams-clause -> NUM_TEAMS (scalar-integer-expr)
2.10.7 thread-limit-clause -> THREAD_LIMIT (scalar-integer-expr)
2.10.8 dist-schedule-clause -> DIST_SCHEDULE (STATIC [ , chunk_size])
2.12 if-clause -> IF ([ directive-name-modifier :] scalar-logical-expr)
2.15.3.1 default-clause -> DEFAULT (PRIVATE | FIRSTPRIVATE | SHARED | NONE)
2.15.3.2 shared-clause -> SHARED (variable-name-list)
2.15.3.3 private-clause -> PRIVATE (variable-name-list)
2.15.3.4 firstprivate-clause -> FIRSTPRIVATE (variable-name-list)
2.15.3.5 lastprivate-clause -> LASTPRIVATE (variable-name-list)
2.15.3.6 reduction-clause -> REDUCTION (reduction-identifier: variable-name-list)
reduction-identifier -> + | - | * |
.AND. | .OR. | .EQV. | .NEQV. |
MAX | MIN | IAND | IOR | IEOR
2.15.3.7 linear-clause -> LINEAR (linear-list[ : linear-step])
linear-list -> list | modifier(list)
modifier -> REF | VAL | UVAL
2.15.4.1 copyin-clause -> COPYIN (variable-name-list)
2.15.4.2 copyprivate-clause -> COPYPRIVATE (variable-name-list)
2.15.5.1 map -> MAP ([ [ALWAYS[,]] map-type : ] variable-name-list)
map-type -> TO | FROM | TOFROM |
ALLOC | RELEASE | DELETE
2.15.5.2 defaultmap -> DEFAULTMAP (TOFROM:SCALAR)

View File

@ -0,0 +1,670 @@
<!--===- documentation/OpenMP-semantics.md
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
-->
# OpenMP Semantic Analysis
## OpenMP for F18
1. Define and document the parse tree representation for
* Directives (listed below)
* Clauses (listed below)
* Documentation
1. All the directives and clauses need source provenance for messages
1. Define and document how an OpenMP directive in the parse tree
will be represented as the parent of the statement(s)
to which the directive applies.
The parser itself will not be able to construct this representation;
there will be subsequent passes that do so
just like for example _do-stmt_ and _do-construct_.
1. Define and document the symbol table extensions
1. Define and document the module file extensions
### Directives
OpenMP divides directives into three categories as follows.
The directives that are in the same categories share some characteristics.
#### Declarative directives
An OpenMP directive may only be placed in a declarative context.
A declarative directive results in one or more declarations only;
it is not associated with the immediate execution of any user code.
List of existing ones:
* declare simd
* declare target
* threadprivate
* declare reduction
There is a parser node for each of these directives and
the parser node saves information associated with the directive,
for example,
the name of the procedure-name in the `declare simd` directive.
Each parse tree node keeps source provenance,
one for the directive name itself and
one for the entire directive starting from the directive name.
A top-level class, `OpenMPDeclarativeConstruct`,
holds all four of the node types as discriminated unions
along with the source provenance for the entire directive
starting from `!$OMP`.
In `parser-tree.h`,
`OpenMPDeclarativeConstruct` is part
of the `SpecificationConstruct` and `SpecificationPart`
in F18 because
a declarative directive can only be placed in the specification part
of a Fortran program.
All the `Names` or `Designators` associated
with the declarative directive will be resolved in later phases.
#### Executable directives
An OpenMP directive that is **not** declarative.
That is, it may only be placed in an executable context.
It contains stand-alone directives and constructs
that are associated with code blocks.
The stand-alone directive is described in the next section.
The constructs associated with code blocks listed below
share a similar structure:
_Begin Directive_, _Clause List_, _Code Block_, _End Directive_.
The _End Directive_ is optional for constructs
like Loop-associated constructs.
* Block-associated constructs (`OpenMPBlockConstruct`)
* Loop-associated constructs (`OpenMPLoopConstruct`)
* Atomic construct (`OpenMPAtomicConstruct`)
* Sections Construct (`OpenMPSectionsConstruct`,
contains Sections/Parallel Sections constructs)
* Critical Construct (`OpenMPCriticalConstruct`)
A top-level class, `OpenMPConstruct`,
includes stand-alone directive and constructs
listed above as discriminated unions.
In the `parse-tree.h`, `OpenMPConstruct` is an element
of the `ExecutableConstruct`.
All the `Names` or `Designators` associated
with the executable directive will be resolved in Semantic Analysis.
When the backtracking parser can not identify the associated code blocks,
the parse tree will be rewritten later in the Semantics Analysis.
#### Stand-alone Directives
An OpenMP executable directive that has no associated user code
except for that which appears in clauses in the directive.
List of existing ones:
* taskyield
* barrier
* taskwait
* target enter data
* target exit data
* target update
* ordered
* flush
* cancel
* cancellation point
A higher-level class is created for each category
which contains directives listed above that share a similar structure:
* OpenMPSimpleStandaloneConstruct
(taskyield, barrier, taskwait,
target enter/exit data, target update, ordered)
* OpenMPFlushConstruct
* OpenMPCancelConstruct
* OpenMPCancellationPointConstruct
A top-level class, `OpenMPStandaloneConstruct`,
holds all four of the node types as discriminated unions
along with the source provenance for the entire directive.
Also, each parser node for the stand-alone directive saves
the source provenance for the directive name itself.
### Clauses
Each clause represented as a distinct class in `parse-tree.h`.
A top-level class, `OmpClause`,
includes all the clauses as discriminated unions.
The parser node for `OmpClause` saves the source provenance
for the entire clause.
All the `Names` or `Designators` associated
with the clauses will be resolved in Semantic Analysis.
Note that the backtracking parser will not validate
that the list of clauses associated
with a directive is valid other than to make sure they are well-formed.
In particular,
the parser does not check that
the association between directive and clauses is correct
nor check that the values in the directives or clauses are correct.
These checks are deferred to later phases of semantics to simplify the parser.
## Symbol Table Extensions for OpenMP
Name resolution can be impacted by the OpenMP code.
In addition to the regular steps to do the name resolution,
new scopes and symbols may need to be created
when encountering certain OpenMP constructs.
This section describes the extensions
for OpenMP during Symbol Table construction.
OpenMP uses the fork-join model of parallel execution and
all OpenMP threads have access to
a _shared_ memory place to store and retrieve variables
but each thread can also have access to
its _threadprivate_ memory that must not be accessed by other threads.
For the directives and clauses that can control the data environments,
compiler needs to determine two kinds of _access_
to variables used in the directives associated structured block:
**shared** and **private**.
Each variable referenced in the structured block
has an original variable immediately outside of the OpenMP constructs.
Reference to a shared variable in the structured block
becomes a reference to the original variable.
However, each private variable referenced in the structured block,
a new version of the original variable (of the same type and size)
will be created in the threadprivate memory.
There are exceptions that directives/clauses
need to create a new `Symbol` without creating a new `Scope`,
but in general,
when encountering each of the data environment controlling directives
(discussed in the following sections),
a new `Scope` will be created.
For each private variable referenced in the structured block,
a new `Symbol` is created out of the original variable
and the new `Symbol` is associated
with original variables `Symbol` via `HostAssocDetails`.
A new set of OpenMP specific flags are added
into `Flag` class in `symbol.h` to indicate the types of
associations,
data-sharing attributes,
and data-mapping attributes
in the OpenMP data environments.
### New Symbol without new Scope
OpenMP directives that require new `Symbol` to be created
but not new `Scope` are listed in the following table
in terms of the Symbol Table extensions for OpenMP:
<table>
<tr>
<td rowspan="2" colspan="2" >Directives/Clauses
</td>
<td rowspan="2" >Create New
<p>
Symbol
<p>
w/
</td>
<td colspan="2" >Add Flag
</td>
</tr>
<tr>
<td>on Symbol of
</td>
<td>Flag
</td>
</tr>
<tr>
<td rowspan="4" >Declarative Directives
</td>
<td>declare simd [(proc-name)]
</td>
<td>-
</td>
<td>The name of the enclosing function, subroutine, or interface body
to which it applies, or proc-name
</td>
<td>OmpDeclareSimd
</td>
</tr>
<tr>
<td>declare target
</td>
<td>-
</td>
<td>The name of the enclosing function, subroutine, or interface body
to which it applies
</td>
<td>OmpDeclareTarget
</td>
</tr>
<tr>
<td>threadprivate(list)
</td>
<td>-
</td>
<td>named variables and named common blocks
</td>
<td>OmpThreadPrivate
</td>
</tr>
<tr>
<td>declare reduction
</td>
<td>*
</td>
<td>reduction-identifier
</td>
<td>OmpDeclareReduction
</td>
</tr>
<tr>
<td>Stand-alone directives
</td>
<td>flush
</td>
<td>-
</td>
<td>variable, array section or common block name
</td>
<td>OmpFlushed
</td>
</tr>
<tr>
<td colspan="2" >critical [(name)]
</td>
<td>-
</td>
<td>name (user-defined identifier)
</td>
<td>OmpCriticalLock
</td>
</tr>
<tr>
<td colspan="2" >if ([ directive-name-modifier :] scalar-logical-expr)
</td>
<td>-
</td>
<td>directive-name-modifier
</td>
<td>OmpIfSpecified
</td>
</tr>
</table>
- No Action
* Discussed in “Module File Extensions for OpenMP” section
### New Symbol with new Scope
For the following OpenMP regions:
* `target` regions
* `teams` regions
* `parallel` regions
* `simd` regions
* task generating regions (created by `task` or `taskloop` constructs)
* worksharing regions
(created by `do`, `sections`, `single`, or `workshare` constructs)
A new `Scope` will be created
when encountering the above OpenMP constructs
to ensure the correct data environment during the Code Generation.
To determine whether a variable referenced in these regions
needs the creation of a new `Symbol`,
all the data-sharing attribute rules
described in OpenMP Spec [2.15.1] apply during the Name Resolution.
The available data-sharing attributes are:
**_shared_**,
**_private_**,
**_linear_**,
**_firstprivate_**,
and **_lastprivate_**.
The attribute is represented as `Flag` in the `Symbol` object.
More details are listed in the following table:
<table>
<tr>
<td rowspan="2" >Attribute
</td>
<td rowspan="2" >Create New Symbol
</td>
<td colspan="2" >Add Flag
</td>
</tr>
<tr>
<td>on Symbol of
</td>
<td>Flag
</td>
</tr>
<tr>
<td>shared
</td>
<td>No
</td>
<td>Original variable
</td>
<td>OmpShared
</td>
</tr>
<tr>
<td>private
</td>
<td>Yes
</td>
<td>New Symbol
</td>
<td>OmpPrivate
</td>
</tr>
<tr>
<td>linear
</td>
<td>Yes
</td>
<td>New Symbol
</td>
<td>OmpLinear
</td>
</tr>
<tr>
<td>firstprivate
</td>
<td>Yes
</td>
<td>New Symbol
</td>
<td>OmpFirstPrivate
</td>
</tr>
<tr>
<td>lastprivate
</td>
<td>Yes
</td>
<td>New Symbol
</td>
<td>OmpLastPrivate
</td>
</tr>
</table>
To determine the right data-sharing attribute,
OpenMP defines that the data-sharing attributes
of variables that are referenced in a construct can be
_predetermined_, _explicitly determined_, or _implicitly determined_.
#### Predetermined data-sharing attributes
* Assumed-size arrays are **shared**
* The loop iteration variable(s)
in the associated _do-loop(s)_ of a
_do_,
_parallel do_,
_taskloop_,
or _distributeconstruct_
is (are) **private**
* A loop iteration variable
for a sequential loop in a _parallel_ or task generating construct
is **private** in the innermost such construct that encloses the loop
* Implied-do indices and _forall_ indices are **private**
* The loop iteration variable in the associated _do-loop_
of a _simd_ construct with just one associated _do-loop_
is **linear** with a linear-step
that is the increment of the associated _do-loop_
* The loop iteration variables in the associated _do-loop(s)_ of a _simd_
construct with multiple associated _do-loop(s)_ are **lastprivate**
#### Explicitly determined data-sharing attributes
Variables with _explicitly determined_ data-sharing attributes are:
* Variables are referenced in a given construct
* Variables are listed in a data-sharing attribute clause on the construct.
The data-sharing attribute clauses are:
* _default_ clause
(discussed in “Implicitly determined data-sharing attributes”)
* _shared_ clause
* _private_ clause
* _linear_ clause
* _firstprivate_ clause
* _lastprivate_ clause
* _reduction_ clause
(new `Symbol` created with the flag `OmpReduction` set)
Note that variables with _predetermined_ data-sharing attributes
may not be listed (with exceptions) in data-sharing attribute clauses.
#### Implicitly determined data-sharing attributes
Variables with implicitly determined data-sharing attributes are:
* Variables are referenced in a given construct
* Variables do not have _predetermined_ data-sharing attributes
* Variables are not listed in a data-sharing attribute clause
on the construct.
Rules for variables with _implicitly determined_ data-sharing attributes:
* In a _parallel_ construct, if no _default_ clause is present,
these variables are **shared**
* In a task generating construct,
if no _default_ clause is present,
a variable for which the data-sharing attribute
is not determined by the rules above
and that in the enclosing context is determined
to be shared by all implicit tasks
bound to the current team is **shared**
* In a _target_ construct,
variables that are not mapped after applying data-mapping attribute rules
(discussed later) are **firstprivate**
* In an orphaned task generating construct,
if no _default_ clause is present, dummy arguments are **firstprivate**
* In a task generating construct, if no _default_ clause is present,
a variable for which the data-sharing attribute is not determined
by the rules above is **firstprivate**
* For constructs other than task generating constructs or _target_ constructs,
if no _default_ clause is present,
these variables reference the variables with the same names
that exist in the enclosing context
* In a _parallel_, _teams_, or task generating construct,
the data-sharing attributes of these variables are determined
by the _default_ clause, if present:
* _default(shared)_
clause causes all variables referenced in the construct
that have _implicitly determined_ data-sharing attributes
to be **shared**
* _default(private)_
clause causes all variables referenced in the construct
that have _implicitly determined_ data-sharing attributes
to be **private**
* _default(firstprivate)_
clause causes all variables referenced in the construct
that have _implicitly determined_ data-sharing attributes
to be **firstprivate**
* _default(none)_
clause requires that each variable
that is referenced in the construct,
and that does not have a _predetermined_ data-sharing attribute,
must have its data-sharing attribute _explicitly determined_
by being listed in a data-sharing attribute clause
### Data-mapping Attribute
When encountering the _target data_ and _target_ directives,
the data-mapping attributes of any variable referenced in a target region
will be determined and represented as `Flag` in the `Symbol` object
of the variable.
No `Symbol` or `Scope` will be created.
The basic steps to determine the data-mapping attribute are:
1. If _map_ clause is present,
the data-mapping attribute is determined by the _map-type_
on the clause and its corresponding `Flag` are listed below:
<table>
<tr>
<td>
data-mapping attribute
</td>
<td>Flag
</td>
</tr>
<tr>
<td>to
</td>
<td>OmpMapTo
</td>
</tr>
<tr>
<td>from
</td>
<td>OmpMapFrom
</td>
</tr>
<tr>
<td>tofrom
(default if map-type is not present)
</td>
<td>OmpMapTo & OmpMapFrom
</td>
</tr>
<tr>
<td>alloc
</td>
<td>OmpMapAlloc
</td>
</tr>
<tr>
<td>release
</td>
<td>OmpMapRelease
</td>
</tr>
<tr>
<td>delete
</td>
<td>OmpMapDelete
</td>
</tr>
</table>
2. Otherwise, the following data-mapping rules apply
for variables referenced in a _target_ construct
that are _not_ declared in the construct and
do not appear in data-sharing attribute or map clauses:
* If a variable appears in a _to_ or _link_ clause
on a _declare target_ directive then it is treated
as if it had appeared in a _map_ clause with a _map-type_ of **tofrom**
3. Otherwise, the following implicit data-mapping attribute rules apply:
* If a _defaultmap(tofrom:scalar)_ clause is _not_ present
then a scalar variable is not mapped,
but instead has an implicit data-sharing attribute of **firstprivate**
* If a _defaultmap(tofrom:scalar)_ clause is present
then a scalar variable is treated as if it had appeared
in a map clause with a map-type of **tofrom**
* If a variable is not a scalar
then it is treated as if it had appeared in a map clause
with a _map-type_ of **tofrom**
After the completion of the Name Resolution phase,
all the data-sharing or data-mapping attributes marked for the `Symbols`
may be used later in the Semantics Analysis and in the Code Generation.
## Module File Extensions for OpenMP
After the successful compilation of modules and submodules
that may contain the following Declarative Directives,
the entire directive starting from `!$OMP` needs to be written out
into `.mod` files in their corresponding Specification Part:
* _declare simd_ or _declare target_
In the “New Symbol without new Scope” section,
we described that when encountering these two declarative directives,
new `Flag` will be applied to the Symbol of the name of
the enclosing function, subroutine, or interface body to
which it applies, or proc-name.
This `Flag` should be part of the API information
for the given subroutine or function
* _declare reduction_
The _reduction-identifier_ in this directive
can be use-associated or host-associated.
However, it will not act like other Symbols
because user may have a reduction name
that is the same as a Fortran entity name in the same scope.
Therefore a specific data structure needs to be created
to save the _reduction-identifier_ information
in the Scope and this directive needs to be written into `.mod` files
## Phases of OpenMP Analysis
1. Create the parse tree for OpenMP
1. Add types for directives and clauses
1. Add type(s) that will be used for directives
2. Add type(s) that will be used for clauses
3. Add other types, e.g. wrappers or other containers
4. Use std::variant to encapsulate meaningful types
2. Implemented in the parser for OpenMP (openmp-grammar.h)
2. Create canonical nesting
1. Restructure parse tree to reflect the association
of directives and stmts
1. Associate `OpenMPLoopConstruct`
with `DoConstruct` and `OpenMPEndLoopDirective`
1. Investigate, and perhaps reuse,
the algorithm used to restructure do-loops
2. Add a pass near the code that restructures do-loops;
but do not extend the code that handles do-loop for OpenMP;
keep this code separate.
3. Report errors that prevent restructuring
(e.g. loop directive not followed by loop)
We should abort in case of errors
because there is no point to perform further checks
if it is not a legal OpenMP construct
3. Validate the structured-block
1. Structured-block is a block of executable statements
1. Single entry and single exit
1. Access to the structured block must not be the result of a branch
1. The point of exit cannot be a branch out of the structured block
4. Check that directive and clause combinations are legal
1. Begin and End directive should match
1. Simply check that the clauses are allowed by the directives
1. Write as a separate pass for simplicity and correctness of the parse tree
5. Write parse tree tests
1. At this point, the parse tree should be perfectly formed
1. Write tests that check for correct form and provenance information
1. Write tests for errors that can occur during the restructuring
6. Scope, symbol tables, and name resolution
1. Update the existing code to handle names and scopes introduced by OpenMP
1. Write tests to make sure names are properly implemented
7. Check semantics that is specific to each directive
1. Validate the directive and its clauses
1. Some clause checks require the result of name resolution,
i.e. “A list item may appear in a _linear_ or _firstprivate_ clause
but not both.”
1. TBD:
Validate the nested statement for legality in the scope of the directive
1. Check the nesting of regions [OpenMP 4.5 spec 2.17]
8. Module file utilities
1. Write necessary OpenMP declarative directives to `.mod` files
2. Update the existing code
to read available OpenMP directives from the `.mod` files

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,103 @@
<!--===- documentation/Overview.md
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
-->
# Overview of Compiler Phases
Each phase produces either correct output or fatal errors.
## Prescan and Preprocess
See: [Preprocessing.md](Preprocessing.md).
**Input:** Fortran source and header files, command line macro definitions,
set of enabled compiler directives (to be treated as directives rather than
comments).
**Output:**
- A "cooked" character stream: the entire program as a contiguous stream of
normalized Fortran source.
Extraneous whitespace and comments are removed (except comments that are
compiler directives that are not disabled) and case is normalized.
- Provenance information mapping each character back to the source it came from.
This is used in subsequent phases to issue errors messages that refer to source locations.
**Entry point:** `parser::Parsing::Prescan`
**Command:** `f18 -E src.f90` dumps the cooked character stream
## Parse
**Input:** Cooked character stream.
**Output:** A parse tree representing a syntactically correct program,
rooted at a `parser::Program`.
See: [Parsing.md](Parsing.md) and [ParserCombinators.md](ParserCombinators.md).
**Entry point:** `parser::Parsing::Parse`
**Command:**
- `f18 -fdebug-dump-parse-tree -fparse-only src.f90` dumps the parse tree
- `f18 -funparse src.f90` converts the parse tree to normalized Fortran
## Validate Labels and Canonicalize Do Statements
**Input:** Parse tree.
**Output:** The parse tree with label constraints and construct names checked,
and each `LabelDoStmt` converted to a `NonLabelDoStmt`.
See: [LabelResolution.md](LabelResolution.md).
**Entry points:** `semantics::ValidateLabels`, `parser::CanonicalizeDo`
## Resolve Names
**Input:** Parse tree (without `LabelDoStmt`) and `.mod` files from compilation
of USEd modules.
**Output:**
- Tree of scopes populated with symbols and types
- Parse tree with some refinements:
- each `parser::Name::symbol` field points to one of the symbols
- each `parser::TypeSpec::declTypeSpec` field points to one of the types
- array element references that were parsed as function references or
statement functions are corrected
**Entry points:** `semantics::ResolveNames`, `semantics::RewriteParseTree`
**Command:** `f18 -fdebug-dump-symbols -fparse-only src.f90` dumps the
tree of scopes and symbols in each scope
## Check DO CONCURRENT Constraints
**Input:** Parse tree with names resolved.
**Output:** Parse tree with semantically correct DO CONCURRENT loops.
## Write Module Files
**Input:** Parse tree with names resolved.
**Output:** For each module and submodule, a `.mod` file containing a minimal
Fortran representation suitable for compiling program units that depend on it.
See [ModFiles.md](ModFiles.md).
## Analyze Expressions and Assignments
**Input:** Parse tree with names resolved.
**Output:** Parse tree with `parser::Expr::typedExpr` filled in and semantic
checks performed on all expressions and assignment statements.
**Entry points**: `semantics::AnalyzeExpressions`, `semantics::AnalyzeAssignments`
## Produce the Intermediate Representation
**Input:** Parse tree with names and labels resolved.
**Output:** An intermediate representation of the executable program.
See [FortranIR.md](FortranIR.md).

View File

@ -0,0 +1,164 @@
<!--===- documentation/ParserCombinators.md
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
-->
## Concept
The Fortran language recognizer here can be classified as an LL recursive
descent parser. It is composed from a *parser combinator* library that
defines a few fundamental parsers and a few ways to compose them into more
powerful parsers.
For our purposes here, a *parser* is any object that attempts to recognize
an instance of some syntax from an input stream. It may succeed or fail.
On success, it may return some semantic value to its caller.
In C++ terms, a parser is any instance of a class that
1. has a `constexpr` default constructor,
1. defines a type named `resultType`, and
1. provides a function (`const` member or `static`) that accepts a reference to a
`ParseState` as its argument and returns a `std::optional<resultType>` as a
result, with the presence or absence of a value in the `std::optional<>`
signifying success or failure, respectively.
```
std::optional<resultType> Parse(ParseState &) const;
```
The `resultType` of a parser is typically the class type of some particular
node type in the parse tree.
`ParseState` is a class that encapsulates a position in the source stream,
collects messages, and holds a few state flags that determine tokenization
(e.g., are we in a character literal?). Instances of `ParseState` are
independent and complete -- they are cheap to duplicate whenever necessary to
implement backtracking.
The `constexpr` default constructor of a parser is important. The functions
(below) that operate on instances of parsers are themselves all `constexpr`.
This use of compile-time expressions allows the entirety of a recursive
descent parser for a language to be constructed at compilation time through
the use of templates.
### Fundamental Predefined Parsers
These objects and functions are (or return) the fundamental parsers:
* `ok` is a trivial parser that always succeeds without advancing.
* `pure(x)` returns a trivial parser that always succeeds without advancing,
returning some value `x`.
* `fail<T>(msg)` denotes a trivial parser that always fails, emitting the
given message as a side effect. The template parameter is the type of
the value that the parser never returns.
* `cut` is a trivial parser that always fails silently.
* `nextCh` consumes the next character and returns its location,
and fails at EOF.
* `"xyz"_ch` succeeds if the next character consumed matches any of those
in the string and returns its location. Be advised that the source
will have been normalized to lower case (miniscule) letters outside
character and Hollerith literals and edit descriptors before parsing.
### Combinators
These functions and operators combine existing parsers to generate new parsers.
They are `constexpr`, so they should be viewed as type-safe macros.
* `!p` succeeds if p fails, and fails if p succeeds.
* `p >> q` fails if p does, otherwise running q and returning its value when
it succeeds.
* `p / q` fails if p does, otherwise running q and returning p's value
if q succeeds.
* `p || q` succeeds if p does, otherwise running q. The two parsers must
have the same type, and the value returned by the first succeeding parser
is the value of the combination.
* `first(p1, p2, ...)` returns the value of the first parser that succeeds.
All of the parsers in the list must return the same type.
It is essentially the same as `p1 || p2 || ...` but has a slightly
faster implementation and may be easier to format in your code.
* `lookAhead(p)` succeeds if p does, but doesn't modify any state.
* `attempt(p)` succeeds if p does, safely preserving state on failure.
* `many(p)` recognizes a greedy sequence of zero or more nonempty successes
of p, and returns `std::list<>` of their values. It always succeeds.
* `some(p)` recognized a greedy sequence of one or more successes of p.
It fails if p immediately fails.
* `skipMany(p)` is the same as `many(p)`, but it discards the results.
* `maybe(p)` tries to match p, returning an `std::optional<T>` value.
It always succeeds.
* `defaulted(p)` matches p, and when p fails it returns a
default-constructed instance of p's resultType. It always succeeds.
* `nonemptySeparated(p, q)` repeatedly matches "p q p q p q ... p",
returning a `std::list<>` of only the values of the p's. It fails if
p immediately fails.
* `extension(p)` parses p if strict standard compliance is disabled,
or with a warning if nonstandard usage warnings are enabled.
* `deprecated(p)` parses p if strict standard compliance is disabled,
with a warning if deprecated usage warnings are enabled.
* `inContext(msg, p)` runs p within an error message context; any
message that `p` generates will be tagged with `msg` as its
context. Contexts may nest.
* `withMessage(msg, p)` succeeds if `p` does, and if it does not,
it discards the messages from `p` and fails with the specified message.
* `recovery(p, q)` is equivalent to `p || q`, except that error messages
generated from the first parser are retained, and a flag is set in
the ParseState to remember that error recovery was necessary.
* `localRecovery(msg, p, q)` is equivalent to `recovery(withMessage(msg, p), defaulted(cut >> p) >> q)`. It is useful for targeted error recovery situations
within statements.
Note that
```
a >> b >> c / d / e
```
matches a sequence of five parsers, but returns only the result that was
obtained by matching `c`.
### Applicatives
The following *applicative* combinators combine parsers and modify or
collect the values that they return.
* `construct<T>(p1, p2, ...)` matches zero or more parsers in succession,
collecting their results and then passing them with move semantics to a
constructor for the type T if they all succeed.
If there is a single parser as the argument and it returns no usable
value but only success or failure (_e.g.,_ `"IF"_tok`), the default
nullary constructor of the type `T` is called.
* `sourced(p)` matches p, and fills in its `source` data member with the
locations of the cooked character stream that it consumed
* `applyFunction(f, p1, p2, ...)` matches one or more parsers in succession,
collecting their results and passing them as rvalue reference arguments to
some function, returning its result.
* `applyLambda([](&&x){}, p1, p2, ...)` is the same thing, but for lambdas
and other function objects.
* `applyMem(mf, p1, p2, ...)` is the same thing, but invokes a member
function of the result of the first parser for updates in place.
### Token Parsers
Last, we have these basic parsers on which the actual grammar of the Fortran
is built. All of the following parsers consume characters acquired from
`nextCh`.
* `space` always succeeds after consuming any spaces
* `spaceCheck` always succeeds after consuming any spaces, and can emit
a warning if there was no space in free form code before a character
that could continue a name or keyword
* `digit` matches one cooked decimal digit (0-9)
* `letter` matches one cooked letter (A-Z)
* `"..."_tok` match the content of the string, skipping spaces before and
after. Internal spaces are optional matches. The `_tok` suffix is
optional when the parser appears before the combinator `>>` or after
the combinator `/`.
* `"..."_sptok` is a string match in which the spaces are required in
free form source.
* `"..."_id` is a string match for a complete identifier (not a prefix of
a longer identifier or keyword).
* `parenthesized(p)` is shorthand for `"(" >> p / ")"`.
* `bracketed(p)` is shorthand for `"[" >> p / "]"`.
* `nonEmptyList(p)` matches a comma-separated list of one or more
instances of p.
* `nonEmptyList(errorMessage, p)` is equivalent to
`withMessage(errorMessage, nonemptyList(p))`, which allows one to supply
a meaningful error message in the event of an empty list.
* `optionalList(p)` is the same thing, but can be empty, and always succeeds.
### Debugging Parser
Last, a string literal `"..."_debug` denotes a parser that emits the string to
`llvm::errs` and succeeds. It is useful for tracing while debugging a parser but should
obviously not be committed for production code.

View File

@ -0,0 +1,213 @@
<!--===- documentation/Parsing.md
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
-->
The F18 Parser
==============
This program source code implements a parser for the Fortran programming
language.
The draft ISO standard for Fortran 2018 dated July 2017 was used as the
primary definition of the language. The parser also accepts many features
from previous versions of the standard that are no longer part of the Fortran
2018 language.
It also accepts many features that have never been part of any version
of the standard Fortran language but have been supported by previous
implementations and are known or suspected to remain in use. As a
general principle, we want to recognize and implement any such feature
so long as it does not conflict with requirements of the current standard
for Fortran.
The parser is implemented in standard ISO C++ and requires the 2017
edition of the language and library. The parser constitutes a reentrant
library with no mutable or constructed static data. Best modern C++
programming practices are observed to ensure that the ownership of
dynamic memory is clear, that value rather than object semantics are
defined for the data structures, that most functions are free from
invisible side effects, and that the strictest available type checking
is enforced by the C++ compiler when the Fortran parser is built.
Class inheritance is rare and dynamic polymorphism is avoided in favor
of modern discriminated unions. To the furthest reasonable extent, the
parser has been implemented in a declarative fashion that corresponds
closely to the text of the Fortran language standard.
The several major modules of the Fortran parser are composed into a
top-level Parsing class, by means of which one may drive the parsing of a
source file and receive its parse tree and error messages. The interfaces
of the Parsing class correspond to the two major passes of the parser,
which are described below.
Prescanning and Preprocessing
-----------------------------
The first pass is performed by an instance of the Prescanner class,
with help from an instance of Preprocessor.
The prescanner generates the "cooked character stream", implemented
by a CookedSource class instance, in which:
* line ends have been normalized to single ASCII LF characters (UNIX newlines)
* all `INCLUDE` files have been expanded
* all continued Fortran source lines have been unified
* all comments and insignificant spaces have been removed
* fixed form right margins have been clipped
* extra blank card columns have been inserted into character literals
and Hollerith constants
* preprocessing directives have been implemented
* preprocessing macro invocations have been expanded
* legacy `D` lines in fixed form source have been omitted or included
* except for the payload in character literals, Hollerith constants,
and character and Hollerith edit descriptors, all letters have been
normalized to lower case
* all original non-ASCII characters in Hollerith constants have been
decoded and re-encoded into UTF-8
Lines in the cooked character stream can be of arbitrary length.
The purpose of the cooked character stream is to enable the implementation
of a parser whose sole concern is the recognition of the Fortran language
from productions that closely correspond to the grammar that is presented
in the Fortran standard, without having to deal with the complexity of
all of the source-level concerns in the preceding list.
The implementation of the preprocessor interacts with the prescanner by
means of _token sequences_. These are partitionings of input lines into
contiguous virtual blocks of characters, and are the only place in this
Fortran compiler in which we have reified a tokenization of the program
source; the parser proper does not have a tokenizer. The prescanner
builds these token sequences out of source lines and supplies them
to the preprocessor, which interprets directives and expands macro
invocations. The token sequences returned by the preprocessor are then
marshaled to constitute the cooked character stream that is the output of
the prescanner.
The preprocessor and prescanner can both instantiate new temporary
instances of the Prescanner class to locate, open, and process any
include files.
The tight interaction and mutual design of the prescanner and preprocessor
enable a principled implementation of preprocessing for the Fortran
language that implements a reasonable facsimile of the C language
preprocessor that is fully aware of Fortran's source forms, line
continuation mechanisms, case insensitivity, token syntax, &c.
The preprocessor always runs. There's no good reason for it not to.
The content of the cooked character stream is available and useful
for debugging, being as it is a simple value forwarded from the first major
pass of the compiler to the second.
Source Provenance
-----------------
The prescanner constructs a chronicle of every file that is read by the
parser, viz. the original source file and all others that it directly
or indirectly includes. One copy of the content of each of these files
is mapped or read into the address space of the parser. Memory mapping
is used initially, but files with DOS line breaks or a missing terminal
newline are immediately normalized in a buffer when necessary.
The virtual input stream, which marshals every appearance of every file
and every expansion of every macro invocation, is not materialized as
an actual stream of bytes. There is, however, a mapping from each byte
position in this virtual input stream back to whence it came (maintained
by an instance of the AllSources class). Offsets into this virtual input
stream constitute values of the Provenance class. Provenance values,
and contiguous ranges thereof, are used to describe and delimit source
positions for messaging.
Further, every byte in the cooked character stream supplied by the
prescanner to the parser can be inexpensively mapped to its provenance.
Simple `const char *` pointers to characters in the cooked character
stream, or to contiguous ranges thereof, are used as source position
indicators within the parser and in the parse tree.
Messages
--------
Message texts, and snprintf-like formatting strings for constructing
messages, are instantiated in the various components of the parser with
C++ user defined character literals tagged with `_err_en_US` and `_en_US`
(signifying fatality and language, with the default being the dialect of
English used in the United States) so that they may be easily identified
for localization. As described above, messages are associated with
source code positions by means of provenance values.
The Parse Tree
--------------
Each of the ca. 450 numbered requirement productions in the standard
Fortran language grammar, as well as the productions implied by legacy
extensions and preserved obsolescent features, maps to a distinct class
in the parse tree so as to maximize the efficacy of static type checking
by the C++ compiler.
A transcription of the Fortran grammar appears with production requirement
numbers in the commentary before these class definitions, so that one
may easily refer to the standard (or to the parse tree definitions while
reading that document).
Three paradigms collectively implement most of the parse tree classes:
* *wrappers*, in which a single data member `v` has been encapsulated
in a new type
* *tuples* (or product types), in which several values of arbitrary
types have been encapsulated in a single data member `t` whose type
is an instance of `std::tuple<>`
* *discriminated unions* (or sum types), in which one value whose type is
a dynamic selection from a set of distinct types is saved in a data
member `u` whose type is an instance of `std::variant<>`
The use of these patterns is a design convenience, and exceptions to them
are not uncommon wherever it made better sense to write custom definitions.
Parse tree entities should be viewed as values, not objects; their
addresses should not be abused for purposes of identification. They are
assembled with C++ move semantics during parse tree construction.
Their default and copy constructors are deliberately deleted in their
declarations.
The std::list<> data type is used in the parse tree to reliably store pointers
to other relevant entries in the tree. Since the tree lists are moved and
spliced at certain points std::list<> provides the necessary guarantee of the
stability of pointers into these lists.
There is a general purpose library by means of which parse trees may
be traversed.
Parsing
-------
This compiler attempts to recognize the entire cooked character stream
(see above) as a Fortran program. It records the reductions made during
a successful recognition as a parse tree value. The recognized grammar
is that of a whole source file, not just of its possible statements,
so the parser has no global state that tracks the subprogram hierarchy
or the structure of their nested block constructs. The parser performs
no semantic analysis along the way, deferring all of that work to the
next pass of the compiler.
The resulting parse tree therefore necessarily contains ambiguous parses
that cannot be resolved without recourse to a symbol table. Most notably,
leading assignments to array elements can be misrecognized as statement
function definitions, and array element references can be misrecognized
as function calls. The semantic analysis phase of the compiler performs
local rewrites of the parse tree once it can be disambiguated by symbols
and types.
Formally speaking, this parser is based on recursive descent with
localized backtracking (specifically, it will not backtrack into a
successful reduction to try its other alternatives). It is not generated
as a table or code from a specification of the Fortran grammar; rather, it
_is_ the grammar, as declaratively respecified in C++ constant expressions
using a small collection of basic token recognition objects and a library
of "parser combinator" template functions that compose them to form more
complicated recognizers and their correspondences to the construction
of parse tree values.
Unparsing
---------
Parse trees can be converted back into free form Fortran source code.
This formatter is not really a classical "pretty printer", but is
more of a data structure dump whose output is suitable for compilation
by another compiler. It is also used for testing the parser, since a
reparse of an unparsed parse tree should be an identity function apart from
source provenance.

View File

@ -0,0 +1,223 @@
<!--===- documentation/Preprocessing.md
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
-->
Fortran Preprocessing
=====================
Behavior common to (nearly) all compilers:
------------------------------------------
* Macro and argument names are sensitive to case.
* Fixed form right margin clipping after column 72 (or 132)
has precedence over macro name recognition, and also over
recognition of function-like parentheses and arguments.
* Fixed form right margin clipping does not apply to directive lines.
* Macro names are not recognized as such when spaces are inserted
into their invocations in fixed form.
This includes spaces at the ends of lines that have been clipped
at column 72 (or whatever).
* Text is rescanned after expansion of macros and arguments.
* Macros are not expanded within quoted character literals or
quoted FORMAT edit descriptors.
* Macro expansion occurs before any effective token pasting via fixed form
space removal.
* C-like line continuations with backslash-newline are allowed in
directives, including the definitions of macro bodies.
* `/* Old style C comments */` are ignored in directives and
removed from the bodies of macro definitions.
* `// New style C comments` are not removed, since Fortran has OPERATOR(//).
* C-like line continuations with backslash-newline can appear in
old-style C comments in directives.
* After `#define FALSE TRUE`, `.FALSE.` is replaced by `.TRUE.`;
i.e., tokenization does not hide the names of operators or logical constants.
* `#define KWM c` allows the use of `KWM` in column 1 as a fixed form comment
line indicator.
* A `#define` directive intermixed with continuation lines can't
define a macro that's invoked earlier in the same continued statement.
Behavior that is not consistent over all extant compilers but which
probably should be uncontroversial:
-----------------------------------
* Invoked macro names can straddle a Fortran line continuation.
* ... unless implicit fixed form card padding intervenes; i.e.,
in fixed form, a continued macro name has to be split at column
72 (or 132).
* Comment lines may appear with continuations in a split macro names.
* Function-like macro invocations can straddle a Fortran fixed form line
continuation between the name and the left parenthesis, and comment and
directive lines can be there too.
* Function-like macro invocations can straddle a Fortran fixed form line
continuation between the parentheses, and comment lines can be there too.
* Macros are not expanded within Hollerith constants or Hollerith
FORMAT edit descriptors.
* Token pasting with `##` works in function-like macros.
* Argument stringization with `#` works in function-like macros.
* Directives can be capitalized (e.g., `#DEFINE`) in fixed form.
* Fixed form clipping after column 72 or 132 is done before macro expansion,
not after.
* C-like line continuation with backslash-newline can appear in the name of
a keyword-like macro definition.
* If `#` is in column 6 in fixed form, it's a continuation marker, not a
directive indicator.
* `#define KWM !` allows KWM to signal a comment.
Judgement calls, where precedents are unclear:
----------------------------------------------
* Expressions in `#if` and `#elif` should support both Fortran and C
operators; e.g., `#if 2 .LT. 3` should work.
* If a function-like macro does not close its parentheses, line
continuation should be assumed.
* ... However, the leading parenthesis has to be on the same line as
the name of the function-like macro, or on a continuation line thereof.
* If macros expand to text containing `&`, it doesn't work as a free form
line continuation marker.
* `#define c 1` does not allow a `c` in column 1 to be used as a label
in fixed form, rather than as a comment line indicator.
* IBM claims to be ISO C compliant and therefore recognizes trigraph sequences.
* Fortran comments in macro actual arguments should be respected, on
the principle that a macro call should work like a function reference.
* If a `#define` or `#undef` directive appears among continuation
lines, it may or may not affect text in the continued statement that
appeared before the directive.
Behavior that few compilers properly support (or none), but should:
-------------------------------------------------------------------
* A macro invocation can straddle free form continuation lines in all of their
forms, with continuation allowed in the name, before the arguments, and
within the arguments.
* Directives can be capitalized in free form, too.
* `__VA_ARGS__` and `__VA_OPT__` work in variadic function-like macros.
In short, a Fortran preprocessor should work as if:
---------------------------------------------------
1. Fixed form lines are padded up to column 72 (or 132) and clipped thereafter.
2. Fortran comments are removed.
3. C-style line continuations are processed in preprocessing directives.
4. C old-style comments are removed from directives.
5. Fortran line continuations are processed (outside preprocessing directives).
Line continuation rules depend on source form.
Comment lines that are enabled compiler directives have their line
continuations processed.
Conditional compilation preprocessing directives (e.g., `#if`) may be
appear among continuation lines, and have their usual effects upon them.
6. Other preprocessing directives are processed and macros expanded.
Along the way, Fortran `INCLUDE` lines and preprocessor `#include` directives
are expanded, and all these steps applied recursively to the introduced text.
7. Any Fortran comments created by macro replacement are removed.
Steps 5 and 6 are interleaved with respect to the preprocessing state.
Conditional compilation preprocessing directives always reflect only the macro
definition state produced by the active `#define` and `#undef` preprocessing directives
that precede them.
If the source form is changed by means of a compiler directive (i.e.,
`!DIR$ FIXED` or `FREE`) in an included source file, its effects cease
at the end of that file.
Last, if the preprocessor is not integrated into the Fortran compiler,
new Fortran continuation line markers should be introduced into the final
text.
OpenMP-style directives that look like comments are not addressed by
this scheme but are obvious extensions.
Appendix
========
`N` in the table below means "not supported"; this doesn't
mean a bug, it just means that a particular behavior was
not observed.
`E` signifies "error reported".
The abbreviation `KWM` stands for "keyword macro" and `FLM` means
"function-like macro".
The first block of tests (`pp0*.F`) are all fixed-form source files;
the second block (`pp1*.F90`) are free-form source files.
```
f18
| pgfortran
| | ifort
| | | gfortran
| | | | xlf
| | | | | nagfor
| | | | | |
. . . . . . pp001.F keyword macros
. . . . . . pp002.F #undef
. . . . . . pp003.F function-like macros
. . . . . . pp004.F KWMs case-sensitive
. N . N N . pp005.F KWM split across continuation, implicit padding
. N . N N . pp006.F ditto, but with intervening *comment line
N N N N N N pp007.F KWM split across continuation, clipped after column 72
. . . . . . pp008.F KWM with spaces in name at invocation NOT replaced
. N . N N . pp009.F FLM call split across continuation, implicit padding
. N . N N . pp010.F ditto, but with intervening *comment line
N N N N N N pp011.F FLM call name split across continuation, clipped
. N . N N . pp012.F FLM call name split across continuation
. E . N N . pp013.F FLM call split between name and (
. N . N N . pp014.F FLM call split between name and (, with intervening *comment
. E . N N . pp015.F FLM call split between name and (, clipped
. E . N N . pp016.F FLM call split between name and ( and in argument
. . . . . . pp017.F KLM rescan
. . . . . . pp018.F KLM rescan with #undef (so rescan is after expansion)
. . . . . . pp019.F FLM rescan
. . . . . . pp020.F FLM expansion of argument
. . . . . . pp021.F KWM NOT expanded in 'literal'
. . . . . . pp022.F KWM NOT expanded in "literal"
. . E E . E pp023.F KWM NOT expanded in 9HHOLLERITH literal
. . . E . . pp024.F KWM NOT expanded in Hollerith in FORMAT
. . . . . . pp025.F KWM expansion is before token pasting due to fixed-form space removal
. . . E . E pp026.F ## token pasting works in FLM
E . . E E . pp027.F #DEFINE works in fixed form
. N . N N . pp028.F fixed-form clipping done before KWM expansion on source line
. . . . . . pp029.F \ newline allowed in #define
. . . . . . pp030.F /* C comment */ erased from #define
E E E E E E pp031.F // C++ comment NOT erased from #define
. . . . . . pp032.F /* C comment */ \ newline erased from #define
. . . . . . pp033.F /* C comment \ newline */ erased from #define
. . . . . N pp034.F \ newline allowed in name on KWM definition
. E . E E . pp035.F #if 2 .LT. 3 works
. . . . . . pp036.F #define FALSE TRUE ... .FALSE. -> .TRUE.
N N N N N N pp037.F fixed-form clipping NOT applied to #define
. . E . E E pp038.F FLM call with closing ')' on next line (not a continuation)
E . E . E E pp039.F FLM call with '(' on next line (not a continuation)
. . . . . . pp040.F #define KWM c, then KWM works as comment line initiator
E . E . . E pp041.F use KWM expansion as continuation indicators
N N N . . N pp042.F #define c 1, then use c as label in fixed-form
. . . . N . pp043.F #define with # in column 6 is a continuation line in fixed-form
E . . . . . pp044.F #define directive amid continuations
. . . . . . pp101.F90 keyword macros
. . . . . . pp102.F90 #undef
. . . . . . pp103.F90 function-like macros
. . . . . . pp104.F90 KWMs case-sensitive
. N N N N N pp105.F90 KWM call name split across continuation, with leading &
. N N N N N pp106.F90 ditto, with & ! comment
N N E E N . pp107.F90 KWM call name split across continuation, no leading &, with & ! comment
N N E E N . pp108.F90 ditto, but without & ! comment
. N N N N N pp109.F90 FLM call name split with leading &
. N N N N N pp110.F90 ditto, with & ! comment
N N E E N . pp111.F90 FLM call name split across continuation, no leading &, with & ! comment
N N E E N . pp112.F90 ditto, but without & ! comment
. N N N N E pp113.F90 FLM call split across continuation between name and (, leading &
. N N N N E pp114.F90 ditto, with & ! comment, leading &
N N N N N . pp115.F90 ditto, with & ! comment, no leading &
N N N N N . pp116.F90 FLM call split between name and (, no leading &
. . . . . . pp117.F90 KWM rescan
. . . . . . pp118.F90 KWM rescan with #undef, proving rescan after expansion
. . . . . . pp119.F90 FLM rescan
. . . . . . pp120.F90 FLM expansion of argument
. . . . . . pp121.F90 KWM NOT expanded in 'literal'
. . . . . . pp122.F90 KWM NOT expanded in "literal"
. . E E . E pp123.F90 KWM NOT expanded in Hollerith literal
. . E E . E pp124.F90 KWM NOT expanded in Hollerith in FORMAT
E . . E E . pp125.F90 #DEFINE works in free form
. . . . . . pp126.F90 \ newline works in #define
N . E . E E pp127.F90 FLM call with closing ')' on next line (not a continuation)
E . E . E E pp128.F90 FLM call with '(' on next line (not a continuation)
. . N . . N pp129.F90 #define KWM !, then KWM works as comment line initiator
E . E . . E pp130.F90 #define KWM &, use for continuation w/o pasting (ifort and nag seem to continue #define)
```

View File

@ -0,0 +1,47 @@
<!--===- documentation/PullRequestChecklist.md
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
-->
# Pull request checklist
Please review the following items before submitting a pull request. This list
can also be used when reviewing pull requests.
* Verify that new files have a license with correct file name.
* Run `git diff` on all modified files to look for spurious changes such as
`#include <iostream>`.
* If you added code that causes the compiler to emit a new error message, make
sure that you also added a test that causes that error message to appear
and verifies its correctness.
* Annotate the code and tests with appropriate references to constraint and
requirement numbers from the Fortran standard. Do not include the text of
the constraint or requirement, just its number.
* Alphabetize arbitrary lists of names.
* Check dereferences of pointers and optionals where necessary.
* Ensure that the scopes of all functions and variables are as local as
possible.
* Try to make all functions fit on a screen (40 lines).
* Build and test with both GNU and clang compilers.
* When submitting an update to a pull request, review previous pull request
comments and make sure that you've actually made all of the changes that
were requested.
## Follow the style guide
The following items are taken from the [C++ style guide](C++style.md). But
even though I've read the style guide, they regularly trip me up.
* Run clang-format using the git-clang-format script from LLVM HEAD.
* Make sure that all source lines have 80 or fewer characters. Note that
clang-format will do this for most code. But you may need to break up long
strings.
* Review declarations for proper use of `constexpr` and `const`.
* Follow the C++ [naming guidelines](C++style.md#naming).
* Ensure that the names evoke their purpose and are consistent with existing code.
* Used braced initializers.
* Review pointer and reference types to make sure that you're using them
appropriately. Note that the [C++ style guide](C++style.md) contains a
section that describes all of the pointer types along with their
characteristics.
* Declare non-member functions ```static``` when possible. Prefer
```static``` functions over functions in anonymous namespaces.

View File

@ -0,0 +1,436 @@
<!--===- documentation/RuntimeDescriptor.md
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
-->
## Concept
The properties that characterize data values and objects in Fortran
programs must sometimes be materialized when the program runs.
Some properties are known during compilation and constant during
execution, yet must be reified anyway for execution in order to
drive the interfaces of a language support library or the mandated
interfaces of interoperable (i.e., C) procedure calls.
Note that many Fortran intrinsic subprograms have interfaces
that are more flexible and generic than actual Fortran subprograms
can be, so properties that must be known during compilation and
are constant during execution may still need to be materialized
for calls to the library, even if only by modifying names to
distinguish types or their kind specializations.
Other properties are deferred to execution, and need to be represented
to serve the needs of compiled code and the run time support library.
Previous implementations of Fortran have typically defined a small
sheaf of _descriptor_ data structures for this purpose, and attached
these descriptors as additional hidden arguments, type components,
and local variables so as to convey dynamic characteristics between
subprograms and between user code and the run-time support library.
### References
References are to the 12-2017 draft of the Fortran 2018 standard
(N2146).
Section 15.4.2.2 can be interpreted as a decent list of things that
might need descriptors or other hidden state passed across a
subprogram call, since such features (apart from assumed-length
`CHARACTER` function results) trigger a requirement for the
subprogram to have an explicit interface visible to their callers.
Section 15.5.2 has good laundry lists of situations that can arise
across subprogram call boundaries.
## A survey of dynamic characteristics
### Length of assumed-length `CHARACTER` function results (B.3.6)
```
CHARACTER*8 :: FOO
PRINT *, FOO('abcdefghijklmnopqrstuvwxyz')
...
CHARACTER*(*) FUNCTION FOO(STR)
CHARACTER*26 STR
FOO=STR
END
```
prints `abcdefgh` because the length parameter of the character type
of the result of `FOO` is passed across the call -- even in the absence
of an explicit interface!
### Assumed length type parameters (7.2)
Dummy arguments and associate names for `SELECT TYPE` can have assumed length
type parameters, which are denoted by asterisks (not colons).
Their values come from actual arguments or the associated expression (resp.).
### Explicit-shape arrays (8.5.8.2)
The expressions used for lower and upper bounds must be captured and remain
invariant over the scope of an array, even if they contain references to
variables that are later modified.
Explicit-shape arrays can be dummy arguments, "adjustable" local variables,
and components of derived type (using specification expressions in terms
of constants and KIND type parameters).
### Leading dimensions of assumed-size arrays (8.5.8.5)
```
SUBROUTINE BAR(A)
REAL A(2,3,*)
END
```
The total size and final dimension's extent do not constitute dynamic
properties.
The called subprogram has no means to extract the extent of the
last (major) dimension, and may not depend upon it implicitly by using
the array in any context that demands a known shape.
The values of the expressions used as the bounds of the dimensions
that appear prior to
the last dimension are, however, effectively captured on entry to the
subprogram, and remain invariant even if the variables that appear in
those expressions have their values modified later.
This is similar to the requirements for an explicit-shape array.
### Some function results
1. Deferred-shape
2. Deferred length type parameter values
3. Stride information for `POINTER` results
Note that while function result variables can have the `ALLOCATABLE`
attribute, the function itself and the value returned to the caller
do not possess the attribute.
### Assumed-shape arrays
The extents of the dimensions of assumed-shape dummy argument arrays
are conveyed from those of the actual effective arguments.
The bounds, however, are not. The called subprogram can define the
lower bound to be a value other than 1, but that is a local effect
only.
### Deferred-shape arrays
The extents and bounds of `POINTER` and `ALLOCATABLE` arrays are
established by pointer assignments and `ALLOCATE` statements.
Note that dummy arguments and function results that are `POINTER`
or `ALLOCATABLE` can be deferred-shape, not assumed-shape -- one cannot
supply a lower bound expression as a local effect.
### Strides
Some arrays can have discontiguous (or negative) strides.
These include assumed-shape dummy arguments and deferred-shape
`POINTER` variables, components, and function results.
Fortran disallows some conceivable cases that might otherwise
require implied strides, such as passing an array of an extended
derived type as an actual argument that corresponds to a
nonpolymorphic dummy array of a base type, or the similar
case of pointer assignment to a base of an extended derived type.
Other arrays, including `ALLOCATABLE`, can be assured to
be contiguous, and do not necessarily need to manage or
convey dynamic stride information.
`CONTIGUOUS` dummy arguments and `POINTER` arrays need not
record stride information either.
(The standard notes that a `CONTIGUOUS POINTER` occupies a
number of storage units that is distinct from that required
to hold a non-`CONTIGUOUS` pointer.)
Note that Fortran distinguishes the `CONTIGUOUS` attribute from
the concept of being known or required to be _simply contiguous_ (9.5.4),
which includes `CONTIGUOUS` entities as well as many others, and
the concept of actually _being_ contiguous (8.5.7) during execution.
I believe that the property of being simply contiguous implies
that an entity is known at compilation time to not require the
use or maintenance of hidden stride values.
### Derived type component initializers
Fortran allows components of derived types to be declared with
initial values that are to be assigned to the components when an
instance of the derived type is created.
These include `ALLOCATABLE` components, which are always initialized
to a deallocated state.
These can be implemented with constructor subroutines, inline
stores or block copies from static initializer blocks, or a sequence
of sparse offset/size/value component initializers to be emplaced
by the run-time library.
N.B. Fortran allows kind type parameters to appear in component
initialization constant expressions, but not length type parameters,
so the initialization values are constants.
N.B. Initialization is not assignment, and cannot be implemented
with assignments to uninitialized derived type instances from
static constant initializers.
### Polymorphic `CLASS()`, `CLASS(*)`, and `TYPE(*)`
Type identification for `SELECT TYPE`.
Default initializers (see above).
Offset locations of `ALLOCATABLE` and polymorphic components.
Presence of `FINAL` procedures.
Mappings to overridable type-bound specific procedures.
### Deferred length type parameters
Derived types with length type parameters, and `CHARACTER`, may be used
with the values of those parameters deferred to execution.
Their actual values must be maintained as characteristics of the dynamic
type that is associated with a value or object
.
A single copy of the deferred length type parameters suffices for
all of the elements of an array of that parameterized derived type.
### Components whose types and/or shape depends on length type parameters
Non-pointer, non-allocatable components whose types or shapes are expressed
in terms of length type parameters will probably have to be implemented as
if they had deferred type and/or shape and were `ALLOCATABLE`.
The derived type instance constructor must allocate them and possibly
initialize them; the instance destructor must deallocate them.
### Assumed rank arrays
Rank is almost always known at compilation time and would be redundant
in most circumstances if also managed dynamically.
`DIMENSION(..)` dummy arguments (8.5.8.7), however, are a recent feature
with which the rank of a whole array is dynamic outside the cases of
a `SELECT RANK` construct.
The lower bounds of the dimensions of assumed rank arrays
are always 1.
### Cached invariant subexpressions for addressing
Implementations of Fortran have often maintained precalculated integer
values to accelerate subscript computations.
For example, given `REAL*8 :: A(2:4,3:5)`, the data reference `A(I,J)`
resolves to something like `&A + 8*((I-2)+3*(J-3))`, and this can be
effectively reassociated to `&A - 88 + 8*I + 24*J`
or `&A - 88 + 8*(I + 3*J)`.
When the offset term and coefficients are not compile-time constants,
they are at least invariant and can be precomputed.
In the cases of dummy argument arrays, `POINTER`, and `ALLOCATABLE`,
these addressing invariants could be managed alongside other dynamic
information like deferred extents and lower bounds to avoid their
recalculation.
It's not clear that it's worth the trouble to do so, since the
expressions are invariant and cheap.
### Coarray state (8.5.6)
A _coarray_ is an `ALLOCATABLE` variable or component, or statically
allocated variable (`SAVE` attribute explicit or implied), or dummy
argument whose ultimate effective argument is one of such things.
Each image in a team maintains its portion of each coarray and can
access those portions of the coarray that are maintained by other images
in the team.
Allocations and deallocations are synchronization events at which
the several images can exchange whatever information is needed by
the underlying intercommunication interface to access the data
of their peers.
(Strictly speaking, an implementation could synchronize
images at allocations and deallocations with simple barriers, and defer
the communication of remote access information until it is needed for a
given coarray on a given image, so long as it could be acquired in a
"one-sided" fashion.)
### Presence of `OPTIONAL` dummy arguments
Typically indicated with null argument addresses.
Note that `POINTER` and `ALLOCATABLE` objects can be passed to
non-`POINTER` non-`ALLOCATABLE` dummy arguments, and their
association or allocation status (resp.) determines the presence
of the dummy argument.
### Stronger contiguity enforcement or indication
Some implementations of Fortran guarantee that dummy argument arrays
are, or have been made to be, contiguous on one or more dimensions
when the language does not require them to be so (8.5.7 p2).
Others pass a flag to identify contiguous arrays (or could pass the
number of contiguous leading dimensions, although I know of no such
implementation) so that optimizing transformations that depend on
contiguity can be made conditional with multiple-version code generation
and selected during execution.
In the absence of a contiguity guarantee or flag, the called side
would have to determine contiguity dynamically, if it cares,
by calculating addresses of elements in the array whose subscripts
differ by exactly 1 on exactly 1 dimension of interest, and checking
whether that difference exactly matches the byte size of the type times
the product of the extents of any prior dimensions.
### Host instances for dummy procedures and procedure pointers
A static link or other means of accessing the imported state of the
host procedure must be available when an internal procedure is
used as an actual argument or as a pointer assignment target.
### Alternate returns
Subroutines (only) with alternate return arguments need a
means, such as the otherwise unused function return value, by which
to distinguish and identify the use of an alternate `RETURN` statement.
The protocol can be a simple nonzero integer that drives a switch
in the caller, or the caller can pass multiple return addresses as
arguments for the callee to substitute on the stack for the original
return address in the event of an alternate `RETURN`.
## Implementation options
### A note on array descriptions
Some arrays require dynamic management of distinct combinations of
values per dimension.
One can extract the extent on a dimension from its bounds, or extract
the upper bound from the extent and the lower bound. Having distinct
extent and upper bound would be redundant.
Contiguous arrays can assume a stride of 1 on each dimension.
Assumed-shape and assumed-size dummy argument arrays need not convey
lower bounds.
So there are examples of dimensions with
* extent only (== upper bound): `CONTIGUOUS` assumed-shape, explict shape and multidimensional assumed-size with constant lower bound
* lower bound and either extent or upper bound: `ALLOCATABLE`, `CONTIGUOUS` `POINTER`, general explicit-shape and multidimensional assumed-size
* extent (== upper bound) and stride: general (non-`CONTIGUOUS`) assumed-shape
* lower bound, stride, and either extent or upper bound: general (non-`CONTIGUOUS`) `POINTER`, assumed-rank
and these cases could be accompanied by precomputed invariant
addressing subexpressions to accelerate indexing calculations.
### Interoperability requirements
Fortran 2018 requires that a Fortran implementation supply a header file
`ISO_Fortran_binding.h` for use in C and C++ programs that defines and
implements an interface to Fortran objects from the _interoperable_
subset of Fortran objects and their types suitable for use when those
objects are passed to C functions.
This interface mandates a fat descriptor that is passed by address,
containing (at least)
* a data base address
* explicit rank and type
* flags to distinguish `POINTER` and `ALLOCATABLE`
* elemental byte size, and
* (per-dimension) lower bound, extent, and byte stride
The requirements on the interoperability API do not mandate any
support for features like derived type component initialization,
automatic deallocation of `ALLOCATABLE` components, finalization,
derived type parameters, data contiguity flags, &c.
But neither does the Standard preclude inclusion of additional
interfaces to describe and support such things.
Given a desire to fully support the Fortran 2018 language, we need
to either support the interoperability requirements as a distinct
specialization of the procedure call protocol, or use the
`ISO_Fortran_binding.h` header file requirements as a subset basis for a
complete implementation that adds representations for all the
missing capabilities, which would be isolated and named so as
to prevent user C code from relying upon them.
### Design space
There is a range of possible options for representing the
properties of values and objects during the execution of Fortran
programs.
At one extreme, the amount of dynamic information is minimized,
and is packaged in custom data structures or additional arguments
for each situation to convey only the values that are unknown at
compilation time and actually needed at execution time.
At the other extreme, data values and objects are described completely,
including even the values of properties are known at compilation time.
This is not as silly as it sounds -- e.g., Fortran array descriptors
have historically materialized the number of dimensions they cover, even
though rank will be (nearly) always be a known constant during compilation.
When data are packaged, their containers can be self-describing to
some degree.
Description records can have tag values or strings.
Their fields can have presence flags or identifying tags, and fields
need not have fixed offsets or ordering.
This flexibility can increase binary compatibility across revisions
of the run-time support library, and is convenient for debugging
that library.
However, it is not free.
Further, the requirements of the representation of dynamic
properties of values and objects depend on the execution model:
specifically, are the complicated semantics of intrinsic assignment,
deallocation, and finalization of allocatables implemented entirely
in the support library, in generated code for non-recursive cases,
or by means of a combination of the two approaches?
Consider how to implement the following:
```
TYPE :: LIST
REAL :: HEAD
TYPE(LIST), ALLOCATABLE :: REST
END TYPE LIST
TYPE(LIST), ALLOCATABLE :: A, B
...
A = B
```
Fortran requires that `A`'s arbitrary-length linked list be deleted and
replaced with a "deep copy" of `B`'s.
So either a complicated pair of loops must be generated by the compiler,
or a sophisticated run time support library needs to be driven with
an expressive representation of type information.
## Proposal
We need to write `ISO_Fortran_binding.h` in any event.
It is a header that is published for use in user C code for interoperation
with compiled Fortran and the Fortran run time support library.
There is a sole descriptor structure defined in `ISO_Fortran_binding.h`.
It is suitable for characterizing scalars and array sections of intrinsic
types.
It is essentially a "fat" data pointer that encapsulates a raw data pointer,
a type code, rank, elemental byte size, and per-dimension bounds and stride.
Please note that the mandated interoperable descriptor includes the data
pointer.
This design in the Standard precludes the use of static descriptors that
could be associated with dynamic base addresses.
The F18 runtime cannot use just the mandated interoperable
`struct CFI_cdesc_t` argument descriptor structure as its
all-purpose data descriptor.
It has no information about derived type components, overridable
type-bound procedure bindings, type parameters, &c.
However, we could extend the standard interoperable argument descriptor.
The `struct CFI_cdesc_t` structure is not of fixed size, but we
can efficiently locate the first address after an instance of the
standard descriptor and attach our own data record there to
hold what we need.
There's at least one unused padding byte in the standard argument
descriptor that can be used to hold a flag indicating the presence
of the addenda.
The definitions of our additional run time data structures must
appear in a header file that is distinct from `ISO_Fortran_binding.h`,
and they should never be used by user applications.
This expanded descriptor structure can serve, at least initially for
simplicity, as the sole representation of `POINTER` variables and
components, `ALLOCATABLE` variables and components, and derived type
instances, including length parameter values.
An immediate concern with this concept is the amount of space and
initialization time that would be wasted when derived type components
needing a descriptor would have to be accompanied by an instance
of the general descriptor.
(In the linked list example close above, what could be done with a
single pointer for the `REST` component would become at least
a four-word dynamic structure.)
This concern is amplified when derived type instances
are allocated as arrays, since the overhead is per-element.
We can reduce this wastage in two ways.
First, when the content of the component's descriptor is constant
at compilation apart from its base address, a static descriptor
can be placed in read-only storage and attached to the description
of the derived type's components.
Second, we could eventually optimize the storage requirements by
omitting all static fields from the dynamic descriptor, and
expand the compressed dynamic descriptor during execution when
needed.

View File

@ -0,0 +1,156 @@
<!--===- documentation/Semantics.md
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
-->
# Semantic Analysis
The semantic analysis pass determines if a syntactically correct Fortran
program is is legal by enforcing the constraints of the language.
The input is a parse tree with a `Program` node at the root;
and a "cooked" character stream, a contiguous stream of characters
containing a normalized form of the Fortran source.
The semantic analysis pass takes a parse tree for a syntactically
correct Fortran program and determines whether it is legal by enforcing
the constraints of the language.
If the program is not legal, the results of the semantic pass will be a list of
errors associated with the program.
If the program is legal, the semantic pass will produce a (possibly modified)
parse tree for the semantically correct program with each name mapped to a symbol
and each expression fully analyzed.
All user errors are detected either prior to or during semantic analysis.
After it completes successfully the program should compile with no error messages.
There may still be warnings or informational messages.
## Phases of Semantic Analysis
1. [Validate labels](#validate-labels) -
Check all constraints on labels and branches
2. [Rewrite DO loops](#rewrite-do-loops) -
Convert all occurrences of `LabelDoStmt` to `DoConstruct`.
3. [Name resolution](#name-resolution) -
Analyze names and declarations, build a tree of Scopes containing Symbols,
and fill in the `Name::symbol` data member in the parse tree
4. [Rewrite parse tree](#rewrite-parse-tree) -
Fix incorrect parses based on symbol information
5. [Expression analysis](#expression-analysis) -
Analyze all expressions in the parse tree and fill in `Expr::typedExpr` and
`Variable::typedExpr` with analyzed expressions; fix incorrect parses
based on the result of this analysis
6. [Statement semantics](#statement-semantics) -
Perform remaining semantic checks on the execution parts of subprograms
7. [Write module files](#write-module-files) -
If no errors have occurred, write out `.mod` files for modules and submodules
If phase 1 or phase 2 encounter an error on any of the program units,
compilation terminates. Otherwise, phases 3-6 are all performed even if
errors occur.
Module files are written (phase 7) only if there are no errors.
### Validate labels
Perform semantic checks related to labels and branches:
- check that any labels that are referenced are defined and in scope
- check branches into loop bodies
- check that labeled `DO` loops are properly nested
- check labels in data transfer statements
### Rewrite DO loops
This phase normalizes the parse tree by removing all unstructured `DO` loops
and replacing them with `DO` constructs.
### Name resolution
The name resolution phase walks the parse tree and constructs the symbol table.
The symbol table consists of a tree of `Scope` objects rooted at the global scope.
The global scope is owned by the `SemanticsContext` object.
It contains a `Scope` for each program unit in the compilation.
Each `Scope` in the scope tree contains child scopes representing other scopes
lexically nested in it.
Each `Scope` also contains a map of `CharBlock` to `Symbol` representing names
declared in that scope. (All names in the symbol table are represented as
`CharBlock` objects, i.e. as substrings of the cooked character stream.)
All `Symbol` objects are owned by the symbol table data structures.
They should be accessed as `Symbol *` or `Symbol &` outside of the symbol
table classes as they can't be created, copied, or moved.
The `Symbol` class has functions and data common across all symbols, and a
`details` field that contains more information specific to that type of symbol.
Many symbols also have types, represented by `DeclTypeSpec`.
Types are also owned by scopes.
Name resolution happens on the parse tree in this order:
1. Process the specification of a program unit:
1. Create a new scope for the unit
2. Create a symbol for each contained subprogram containing just the name
3. Process the opening statement of the unit (`ModuleStmt`, `FunctionStmt`, etc.)
4. Process the specification part of the unit
2. Apply the same process recursively to nested subprograms
3. Process the execution part of the program unit
4. Process the execution parts of nested subprograms recursively
After the completion of this phase, every `Name` corresponds to a `Symbol`
unless an error occurred.
### Rewrite parse tree
The parser cannot build a completely correct parse tree without symbol information.
This phase corrects mis-parses based on symbols:
- Array element assignments may be parsed as statement functions: `a(i) = ...`
- Namelist group names without `NML=` may be parsed as format expressions
- A file unit number expression may be parsed as a character variable
This phase also produces an internal error if it finds a `Name` that does not
have its `symbol` data member filled in. This error is suppressed if other
errors have occurred because in that case a `Name` corresponding to an erroneous
symbol may not be resolved.
### Expression analysis
Expressions that occur in the specification part are analyzed during name
resolution, for example, initial values, array bounds, type parameters.
Any remaining expressions are analyzed in this phase.
For each `Variable` and top-level `Expr` (i.e. one that is not nested below
another `Expr` in the parse tree) the analyzed form of the expression is saved
in the `typedExpr` data member. After this phase has completed, the analyzed
expression can be accessed using `semantics::GetExpr()`.
This phase also corrects mis-parses based on the result of expression analysis:
- An expression like `a(b)` is parsed as a function reference but may need
to be rewritten to an array element reference (if `a` is an object entity)
or to a structure constructor (if `a` is a derive type)
- An expression like `a(b:c)` is parsed as an array section but may need to be
rewritten as a substring if `a` is an object with type CHARACTER
### Statement semantics
Multiple independent checkers driven by the `SemanticsVisitor` framework
perform the remaining semantic checks.
By this phase, all names and expressions that can be successfully resolved
have been. But there may be names without symbols or expressions without
analyzed form if errors occurred earlier.
### Write module files
Separate compilation information is written out on successful compilation
of modules and submodules. These are used as input to name resolution
in program units that `USE` the modules.
Module files are stripped down Fortran source for the module.
Parts that aren't needed to compile dependent program units (e.g. action statements)
are omitted.
The module file for module `m` is named `m.mod` and the module file for
submodule `s` of module `m` is named `m-s.mod`.

View File

@ -0,0 +1,801 @@
#===-- documentation/f2018-grammar.txt -------------------------------------===#
#
# 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
#
#===------------------------------------------------------------------------===#
R0001 digit -> 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
R0002 letter ->
A | B | C | D | E | F | G | H | I | J | K | L | M |
N | O | P | Q | R | S | T | U | V | W | X | Y | Z
R0003 rep-char
R401 xzy-list -> xzy [, xzy]...
R402 xzy-name -> name
R403 scalar-xyz -> xyz
R501 program -> program-unit [program-unit]...
R502 program-unit ->
main-program | external-subprogram | module | submodule | block-data
R503 external-subprogram -> function-subprogram | subroutine-subprogram
R504 specification-part ->
[use-stmt]... [import-stmt]... [implicit-part]
[declaration-construct]...
R505 implicit-part -> [implicit-part-stmt]... implicit-stmt
R506 implicit-part-stmt ->
implicit-stmt | parameter-stmt | format-stmt | entry-stmt
R507 declaration-construct ->
specification-construct | data-stmt | format-stmt | entry-stmt |
stmt-function-stmt
R508 specification-construct ->
derived-type-def | enum-def | generic-stmt | interface-block |
parameter-stmt | procedure-declaration-stmt |
other-specification-stmt | type-declaration-stmt
R509 execution-part -> executable-construct [execution-part-construct]...
R510 execution-part-construct ->
executable-construct | format-stmt | entry-stmt | data-stmt
R511 internal-subprogram-part -> contains-stmt [internal-subprogram]...
R512 internal-subprogram -> function-subprogram | subroutine-subprogram
R513 other-specification-stmt ->
access-stmt | allocatable-stmt | asynchronous-stmt | bind-stmt |
codimension-stmt | contiguous-stmt | dimension-stmt | external-stmt |
intent-stmt | intrinsic-stmt | namelist-stmt | optional-stmt |
pointer-stmt | protected-stmt | save-stmt | target-stmt |
volatile-stmt | value-stmt | common-stmt | equivalence-stmt
R514 executable-construct ->
action-stmt | associate-construct | block-construct | case-construct |
change-team-construct | critical-construct | do-construct |
if-construct | select-rank-construct | select-type-construct |
where-construct | forall-construct
R515 action-stmt ->
allocate-stmt | assignment-stmt | backspace-stmt | call-stmt |
close-stmt | continue-stmt | cycle-stmt | deallocate-stmt |
endfile-stmt | error-stop-stmt | event-post-stmt | event-wait-stmt |
exit-stmt | fail-image-stmt | flush-stmt | form-team-stmt |
goto-stmt | if-stmt | inquire-stmt | lock-stmt | nullify-stmt |
open-stmt | pointer-assignment-stmt | print-stmt | read-stmt |
return-stmt | rewind-stmt | stop-stmt | sync-all-stmt |
sync-images-stmt | sync-memory-stmt | sync-team-stmt | unlock-stmt |
wait-stmt | where-stmt | write-stmt | computed-goto-stmt | forall-stmt
R516 keyword -> name
R601 alphanumeric-character -> letter | digit | underscore @ | $
R602 underscore -> _
R603 name -> letter [alphanumeric-character]...
R604 constant -> literal-constant | named-constant
R605 literal-constant ->
int-literal-constant | real-literal-constant |
complex-literal-constant | logical-literal-constant |
char-literal-constant | boz-literal-constant
R606 named-constant -> name
R607 int-constant -> constant
R608 intrinsic-operator ->
power-op | mult-op | add-op | concat-op | rel-op |
not-op | and-op | or-op | equiv-op
R609 defined-operator ->
defined-unary-op | defined-binary-op | extended-intrinsic-op
R610 extended-intrinsic-op -> intrinsic-operator
R611 label -> digit [digit]...
R620 delimiter -> ( | ) | / | [ | ] | (/ | /)
R701 type-param-value -> scalar-int-expr | * | :
R702 type-spec -> intrinsic-type-spec | derived-type-spec
R703 declaration-type-spec ->
intrinsic-type-spec | TYPE ( intrinsic-type-spec ) |
TYPE ( derived-type-spec ) | CLASS ( derived-type-spec ) |
CLASS ( * ) | TYPE ( * )
R704 intrinsic-type-spec ->
integer-type-spec | REAL [kind-selector] | DOUBLE PRECISION |
COMPLEX [kind-selector] | CHARACTER [char-selector] |
LOGICAL [kind-selector] @ DOUBLE COMPLEX
R705 integer-type-spec -> INTEGER [kind-selector]
R706 kind-selector ->
( [KIND =] scalar-int-constant-expr ) @ * scalar-int-constant-expr
R707 signed-int-literal-constant -> [sign] int-literal-constant
R708 int-literal-constant -> digit-string [_ kind-param]
R709 kind-param -> digit-string | scalar-int-constant-name
R710 signed-digit-string -> [sign] digit-string
R711 digit-string -> digit [digit]...
R712 sign -> + | -
R713 signed-real-literal-constant -> [sign] real-literal-constant
R714 real-literal-constant ->
significand [exponent-letter exponent] [_ kind-param] |
digit-string exponent-letter exponent [_ kind-param]
R715 significand -> digit-string . [digit-string] | . digit-string
R716 exponent-letter -> E | D @ | Q
R717 exponent -> signed-digit-string
R718 complex-literal-constant -> ( real-part , imag-part )
R719 real-part ->
signed-int-literal-constant | signed-real-literal-constant |
named-constant
R720 imag-part ->
signed-int-literal-constant | signed-real-literal-constant |
named-constant
R721 char-selector ->
length-selector |
( LEN = type-param-value , KIND = scalar-int-constant-expr ) |
( type-param-value , [KIND =] scalar-int-constant-expr ) |
( KIND = scalar-int-constant-expr [, LEN = type-param-value] )
R722 length-selector -> ( [LEN =] type-param-value ) | * char-length [,]
R723 char-length -> ( type-param-value ) | digit-string
R724 char-literal-constant ->
[kind-param _] ' [rep-char]... ' | [kind-param _] " [rep-char]... "
R725 logical-literal-constant ->
.TRUE. [_ kind-param] | .FALSE. [_ kind-param] @ | .T. | .F.
R726 derived-type-def ->
derived-type-stmt [type-param-def-stmt]... [private-or-sequence]...
[component-part] [type-bound-procedure-part] end-type-stmt
R727 derived-type-stmt ->
TYPE [[, type-attr-spec-list] ::] type-name [( type-param-name-list )]
R728 type-attr-spec ->
ABSTRACT | access-spec | BIND(C) | EXTENDS ( parent-type-name )
R729 private-or-sequence -> private-components-stmt | sequence-stmt
R730 end-type-stmt -> END TYPE [type-name]
R731 sequence-stmt -> SEQUENCE
R732 type-param-def-stmt ->
integer-type-spec , type-param-attr-spec :: type-param-decl-list
R733 type-param-decl -> type-param-name [= scalar-int-constant-expr]
R734 type-param-attr-spec -> KIND | LEN
R735 component-part -> [component-def-stmt]...
R736 component-def-stmt -> data-component-def-stmt | proc-component-def-stmt
R737 data-component-def-stmt ->
declaration-type-spec [[, component-attr-spec-list] ::]
component-decl-list
R738 component-attr-spec ->
access-spec | ALLOCATABLE |
CODIMENSION lbracket coarray-spec rbracket | CONTIGUOUS |
DIMENSION ( component-array-spec ) | POINTER
R739 component-decl ->
component-name [( component-array-spec )]
[lbracket coarray-spec rbracket] [* char-length]
[component-initialization]
R740 component-array-spec ->
explicit-shape-spec-list | deferred-shape-spec-list
R741 proc-component-def-stmt ->
PROCEDURE ( [proc-interface] ) , proc-component-attr-spec-list ::
proc-decl-list
R742 proc-component-attr-spec ->
access-spec | NOPASS | PASS [(arg-name)] | POINTER
R743 component-initialization ->
= constant-expr | => null-init | => initial-data-target
R744 initial-data-target -> designator
R745 private-components-stmt -> PRIVATE
R746 type-bound-procedure-part ->
contains-stmt [binding-private-stmt] [type-bound-proc-binding]...
R747 binding-private-stmt -> PRIVATE
R748 type-bound-proc-binding ->
type-bound-procedure-stmt | type-bound-generic-stmt |
final-procedure-stmt
R749 type-bound-procedure-stmt ->
PROCEDURE [[, bind-attr-list] ::] type-bound-proc-decl-list |
PROCEDURE ( interface-name ) , bind-attr-list :: binding-name-list
R750 type-bound-proc-decl -> binding-name [=> procedure-name]
R751 type-bound-generic-stmt ->
GENERIC [, access-spec] :: generic-spec => binding-name-list
R752 bind-attr ->
access-spec | DEFERRED | NON_OVERRIDABLE | NOPASS | PASS [(arg-name)]
R753 final-procedure-stmt -> FINAL [::] final-subroutine-name-list
R754 derived-type-spec -> type-name [(type-param-spec-list)]
R755 type-param-spec -> [keyword =] type-param-value
R756 structure-constructor -> derived-type-spec ( [component-spec-list] )
R757 component-spec -> [keyword =] component-data-source
R758 component-data-source -> expr | data-target | proc-target
R759 enum-def ->
enum-def-stmt enumerator-def-stmt [enumerator-def-stmt]... end-enum-stmt
R760 enum-def-stmt -> ENUM, BIND(C)
R761 enumerator-def-stmt -> ENUMERATOR [::] enumerator-list
R762 enumerator -> named-constant [= scalar-int-constant-expr]
R763 end-enum-stmt -> END ENUM
R764 boz-literal-constant -> binary-constant | octal-constant | hex-constant
R765 binary-constant -> B ' digit [digit]... ' | B " digit [digit]... "
R766 octal-constant -> O ' digit [digit]... ' | O " digit [digit]... "
R767 hex-constant ->
Z ' hex-digit [hex-digit]... ' | Z " hex-digit [hex-digit]... "
R768 hex-digit -> digit | A | B | C | D | E | F
R769 array-constructor -> (/ ac-spec /) | lbracket ac-spec rbracket
R770 ac-spec -> type-spec :: | [type-spec ::] ac-value-list
R771 lbracket -> [
R772 rbracket -> ]
R773 ac-value -> expr | ac-implied-do
R774 ac-implied-do -> ( ac-value-list , ac-implied-do-control )
R775 ac-implied-do-control ->
[integer-type-spec ::] ac-do-variable = scalar-int-expr ,
scalar-int-expr [, scalar-int-expr]
R776 ac-do-variable -> do-variable
R801 type-declaration-stmt ->
declaration-type-spec [[, attr-spec]... ::] entity-decl-list
R802 attr-spec ->
access-spec | ALLOCATABLE | ASYNCHRONOUS |
CODIMENSION lbracket coarray-spec rbracket | CONTIGUOUS |
DIMENSION ( array-spec ) | EXTERNAL | INTENT ( intent-spec ) |
INTRINSIC | language-binding-spec | OPTIONAL | PARAMETER |
POINTER | PROTECTED | SAVE | TARGET | VALUE | VOLATILE
R803 entity-decl ->
object-name [( array-spec )] [lbracket coarray-spec rbracket]
[* char-length] [initialization] |
function-name [* char-length]
R804 object-name -> name
R805 initialization -> = constant-expr | => null-init | => initial-data-target
R806 null-init -> function-reference
R807 access-spec -> PUBLIC | PRIVATE
R808 language-binding-spec ->
BIND ( C [, NAME = scalar-default-char-constant-expr] )
R809 coarray-spec -> deferred-coshape-spec-list | explicit-coshape-spec
R810 deferred-coshape-spec -> :
R811 explicit-coshape-spec ->
[[lower-cobound :] upper-cobound ,]... [lower-cobound :] *
R812 lower-cobound -> specification-expr
R813 upper-cobound -> specification-expr
R814 dimension-spec -> DIMENSION ( array-spec )
R815 array-spec ->
explicit-shape-spec-list | assumed-shape-spec-list |
deferred-shape-spec-list | assumed-size-spec | implied-shape-spec |
implied-shape-or-assumed-size-spec | assumed-rank-spec
R816 explicit-shape-spec -> [lower-bound :] upper-bound
R817 lower-bound -> specification-expr
R818 upper-bound -> specification-expr
R819 assumed-shape-spec -> [lower-bound] :
R820 deferred-shape-spec -> :
R821 assumed-implied-spec -> [lower-bound :] *
R822 assumed-size-spec -> explicit-shape-spec-list , assumed-implied-spec
R823 implied-shape-or-assumed-size-spec -> assumed-implied-spec
R824 implied-shape-spec -> assumed-implied-spec , assumed-implied-spec-list
R825 assumed-rank-spec -> ..
R826 intent-spec -> IN | OUT | INOUT
R827 access-stmt -> access-spec [[::] access-id-list]
R828 access-id -> access-name | generic-spec
R829 allocatable-stmt -> ALLOCATABLE [::] allocatable-decl-list
R830 allocatable-decl ->
object-name [( array-spec )] [lbracket coarray-spec rbracket]
R831 asynchronous-stmt -> ASYNCHRONOUS [::] object-name-list
R832 bind-stmt -> language-binding-spec [::] bind-entity-list
R833 bind-entity -> entity-name | / common-block-name /
R834 codimension-stmt -> CODIMENSION [::] codimension-decl-list
R835 codimension-decl -> coarray-name lbracket coarray-spec rbracket
R836 contiguous-stmt -> CONTIGUOUS [::] object-name-list
R837 data-stmt -> DATA data-stmt-set [[,] data-stmt-set]...
R838 data-stmt-set -> data-stmt-object-list / data-stmt-value-list /
R839 data-stmt-object -> variable | data-implied-do
R840 data-implied-do ->
( data-i-do-object-list , [integer-type-spec ::]
data-i-do-variable = scalar-int-constant-expr ,
scalar-int-constant-expr [, scalar-int-constant-expr] )
R841 data-i-do-object ->
array-element | scalar-structure-component | data-implied-do
R842 data-i-do-variable -> do-variable
R843 data-stmt-value -> [data-stmt-repeat *] data-stmt-constant
R844 data-stmt-repeat -> scalar-int-constant | scalar-int-constant-subobject
R845 data-stmt-constant ->
scalar-constant | scalar-constant-subobject |
signed-int-literal-constant | signed-real-literal-constant |
null-init | initial-data-target | structure-constructor
R846 int-constant-subobject -> constant-subobject
R847 constant-subobject -> designator
R848 dimension-stmt ->
DIMENSION [::] array-name ( array-spec )
[, array-name ( array-spec )]...
R849 intent-stmt -> INTENT ( intent-spec ) [::] dummy-arg-name-list
R850 optional-stmt -> OPTIONAL [::] dummy-arg-name-list
R851 parameter-stmt -> PARAMETER ( named-constant-def-list )
R852 named-constant-def -> named-constant = constant-expr
R853 pointer-stmt -> POINTER [::] pointer-decl-list
R854 pointer-decl ->
object-name [( deferred-shape-spec-list )] | proc-entity-name
R855 protected-stmt -> PROTECTED [::] entity-name-list
R856 save-stmt -> SAVE [[::] saved-entity-list]
R857 saved-entity -> object-name | proc-pointer-name | / common-block-name /
R858 proc-pointer-name -> name
R859 target-stmt -> TARGET [::] target-decl-list
R860 target-decl ->
object-name [( array-spec )] [lbracket coarray-spec rbracket]
R861 value-stmt -> VALUE [::] dummy-arg-name-list
R862 volatile-stmt -> VOLATILE [::] object-name-list
R863 implicit-stmt ->
IMPLICIT implicit-spec-list |
IMPLICIT NONE [( [implicit-name-spec-list] )]
R864 implicit-spec -> declaration-type-spec ( letter-spec-list )
R865 letter-spec -> letter [- letter]
R866 implicit-name-spec -> EXTERNAL | TYPE
R867 import-stmt ->
IMPORT [[::] import-name-list] | IMPORT , ONLY : import-name-list |
IMPORT , NONE | IMPORT , ALL
R868 namelist-stmt ->
NAMELIST / namelist-group-name / namelist-group-object-list
[[,] / namelist-group-name / namelist-group-object-list]...
R869 namelist-group-object -> variable-name
R870 equivalence-stmt -> EQUIVALENCE equivalence-set-list
R871 equivalence-set -> ( equivalence-object , equivalence-object-list )
R872 equivalence-object -> variable-name | array-element | substring
R873 common-stmt ->
COMMON [/ [common-block-name] /] common-block-object-list
[[,] / [common-block-name] / common-block-object-list]...
R874 common-block-object -> variable-name [( array-spec )]
R901 designator ->
object-name | array-element | array-section |
coindexed-named-object | complex-part-designator |
structure-component | substring
R902 variable -> designator | function-reference
R903 variable-name -> name
R904 logical-variable -> variable
R905 char-variable -> variable
R906 default-char-variable -> variable
R907 int-variable -> variable
R908 substring -> parent-string ( substring-range )
R909 parent-string ->
scalar-variable-name | array-element | coindexed-named-object |
scalar-structure-component | scalar-char-literal-constant |
scalar-named-constant
R910 substring-range -> [scalar-int-expr] : [scalar-int-expr]
R911 data-ref -> part-ref [% part-ref]...
R912 part-ref -> part-name [( section-subscript-list )] [image-selector]
R913 structure-component -> data-ref
R914 coindexed-named-object -> data-ref
R915 complex-part-designator -> designator % RE | designator % IM
R916 type-param-inquiry -> designator % type-param-name
R917 array-element -> data-ref
R918 array-section ->
data-ref [( substring-range )] | complex-part-designator
R919 subscript -> scalar-int-expr
R920 section-subscript -> subscript | subscript-triplet | vector-subscript
R921 subscript-triplet -> [subscript] : [subscript] [: stride]
R922 stride -> scalar-int-expr
R923 vector-subscript -> int-expr
R924 image-selector ->
lbracket cosubscript-list [, image-selector-spec-list] rbracket
R925 cosubscript -> scalar-int-expr
R926 image-selector-spec ->
STAT = stat-variable | TEAM = team-value |
TEAM_NUMBER = scalar-int-expr
R927 allocate-stmt ->
ALLOCATE ( [type-spec ::] allocation-list [, alloc-opt-list] )
R928 alloc-opt ->
ERRMSG = errmsg-variable | MOLD = source-expr |
SOURCE = source-expr | STAT = stat-variable
R929 stat-variable -> scalar-int-variable
R930 errmsg-variable -> scalar-default-char-variable
R931 source-expr -> expr
R932 allocation ->
allocate-object [( allocate-shape-spec-list )]
[lbracket allocate-coarray-spec rbracket]
R933 allocate-object -> variable-name | structure-component
R934 allocate-shape-spec -> [lower-bound-expr :] upper-bound-expr
R935 lower-bound-expr -> scalar-int-expr
R936 upper-bound-expr -> scalar-int-expr
R937 allocate-coarray-spec ->
[allocate-coshape-spec-list ,] [lower-bound-expr :] *
R938 allocate-coshape-spec -> [lower-bound-expr :] upper-bound-expr
R939 nullify-stmt -> NULLIFY ( pointer-object-list )
R940 pointer-object -> variable-name | structure-component | proc-pointer-name
R941 deallocate-stmt ->
DEALLOCATE ( allocate-object-list [, dealloc-opt-list] )
R942 dealloc-opt -> STAT = stat-variable | ERRMSG = errmsg-variable
R1001 primary ->
literal-constant | designator | array-constructor |
structure-constructor | function-reference | type-param-inquiry |
type-param-name | ( expr )
R1002 level-1-expr -> [defined-unary-op] primary
R1003 defined-unary-op -> . letter [letter]... .
R1004 mult-operand -> level-1-expr [power-op mult-operand]
R1005 add-operand -> [add-operand mult-op] mult-operand
R1006 level-2-expr -> [[level-2-expr] add-op] add-operand
R1007 power-op -> **
R1008 mult-op -> * | /
R1009 add-op -> + | -
R1010 level-3-expr -> [level-3-expr concat-op] level-2-expr
R1011 concat-op -> //
R1012 level-4-expr -> [level-3-expr rel-op] level-3-expr
R1013 rel-op ->
.EQ. | .NE. | .LT. | .LE. | .GT. | .GE. |
== | /= | < | <= | > | >= @ | <>
R1014 and-operand -> [not-op] level-4-expr
R1015 or-operand -> [or-operand and-op] and-operand
R1016 equiv-operand -> [equiv-operand or-op] or-operand
R1017 level-5-expr -> [level-5-expr equiv-op] equiv-operand
R1018 not-op -> .NOT.
R1019 and-op -> .AND.
R1020 or-op -> .OR.
R1021 equiv-op -> .EQV. | .NEQV.
R1022 expr -> [expr defined-binary-op] level-5-expr
R1023 defined-binary-op -> . letter [letter]... .
R1024 logical-expr -> expr
R1025 default-char-expr -> expr
R1026 int-expr -> expr
R1027 numeric-expr -> expr
R1028 specification-expr -> scalar-int-expr
R1029 constant-expr -> expr
R1030 default-char-constant-expr -> default-char-expr
R1031 int-constant-expr -> int-expr
R1032 assignment-stmt -> variable = expr
R1033 pointer-assignment-stmt ->
data-pointer-object [( bounds-spec-list )] => data-target |
data-pointer-object ( bounds-remapping-list ) => data-target |
proc-pointer-object => proc-target
R1034 data-pointer-object ->
variable-name | scalar-variable % data-pointer-component-name
R1035 bounds-spec -> lower-bound-expr :
R1036 bounds-remapping -> lower-bound-expr : upper-bound-expr
R1037 data-target -> expr
R1038 proc-pointer-object -> proc-pointer-name | proc-component-ref
R1039 proc-component-ref -> scalar-variable % procedure-component-name
R1040 proc-target -> expr | procedure-name | proc-component-ref
R1041 where-stmt -> WHERE ( mask-expr ) where-assignment-stmt
R1042 where-construct ->
where-construct-stmt [where-body-construct]...
[masked-elsewhere-stmt [where-body-construct]...]...
[elsewhere-stmt [where-body-construct]...] end-where-stmt
R1043 where-construct-stmt -> [where-construct-name :] WHERE ( mask-expr )
R1044 where-body-construct ->
where-assignment-stmt | where-stmt | where-construct
R1045 where-assignment-stmt -> assignment-stmt
R1046 mask-expr -> logical-expr
R1047 masked-elsewhere-stmt -> ELSEWHERE ( mask-expr ) [where-construct-name]
R1048 elsewhere-stmt -> ELSEWHERE [where-construct-name]
R1049 end-where-stmt -> END WHERE [where-construct-name]
R1050 forall-construct ->
forall-construct-stmt [forall-body-construct]... end-forall-stmt
R1051 forall-construct-stmt ->
[forall-construct-name :] FORALL concurrent-header
R1052 forall-body-construct ->
forall-assignment-stmt | where-stmt | where-construct |
forall-construct | forall-stmt
R1053 forall-assignment-stmt -> assignment-stmt | pointer-assignment-stmt
R1054 end-forall-stmt -> END FORALL [forall-construct-name]
R1055 forall-stmt -> FORALL concurrent-header forall-assignment-stmt
R1101 block -> [execution-part-construct]...
R1102 associate-construct -> associate-stmt block end-associate-stmt
R1103 associate-stmt ->
[associate-construct-name :] ASSOCIATE ( association-list )
R1104 association -> associate-name => selector
R1105 selector -> expr | variable
R1106 end-associate-stmt -> END ASSOCIATE [associate-construct-name]
R1107 block-construct ->
block-stmt [block-specification-part] block end-block-stmt
R1108 block-stmt -> [block-construct-name :] BLOCK
R1109 block-specification-part ->
[use-stmt]... [import-stmt]...
[[declaration-construct]... specification-construct]
R1110 end-block-stmt -> END BLOCK [block-construct-name]
R1111 change-team-construct -> change-team-stmt block end-change-team-stmt
R1112 change-team-stmt ->
[team-construct-name :] CHANGE TEAM ( team-value
[, coarray-association-list] [, sync-stat-list] )
R1113 coarray-association -> codimension-decl => selector
R1114 end-change-team-stmt ->
END TEAM [( [sync-stat-list] )] [team-construct-name]
R1115 team-value -> scalar-expr
R1116 critical-construct -> critical-stmt block end-critical-stmt
R1117 critical-stmt ->
[critical-construct-name :] CRITICAL [( [sync-stat-list] )]
R1118 end-critical-stmt -> END CRITICAL [critical-construct-name]
R1119 do-construct -> do-stmt block end-do
R1120 do-stmt -> nonlabel-do-stmt | label-do-stmt
R1121 label-do-stmt -> [do-construct-name :] DO label [loop-control]
R1122 nonlabel-do-stmt -> [do-construct-name :] DO [loop-control]
R1123 loop-control ->
[,] do-variable = scalar-int-expr , scalar-int-expr
[, scalar-int-expr] |
[,] WHILE ( scalar-logical-expr ) |
[,] CONCURRENT concurrent-header concurrent-locality
R1124 do-variable -> scalar-int-variable-name
R1125 concurrent-header ->
( [integer-type-spec ::] concurrent-control-list [, scalar-mask-expr] )
R1126 concurrent-control ->
index-name = concurrent-limit : concurrent-limit [: concurrent-step]
R1127 concurrent-limit -> scalar-int-expr
R1128 concurrent-step -> scalar-int-expr
R1129 concurrent-locality -> [locality-spec]...
R1130 locality-spec ->
LOCAL ( variable-name-list ) | LOCAL_INIT ( variable-name-list ) |
SHARED ( variable-name-list ) | DEFAULT ( NONE )
R1131 end-do -> end-do-stmt | continue-stmt
R1132 end-do-stmt -> END DO [do-construct-name]
R1133 cycle-stmt -> CYCLE [do-construct-name]
R1134 if-construct ->
if-then-stmt block [else-if-stmt block]... [else-stmt block]
end-if-stmt
R1135 if-then-stmt -> [if-construct-name :] IF ( scalar-logical-expr ) THEN
R1136 else-if-stmt -> ELSE IF ( scalar-logical-expr ) THEN [if-construct-name]
R1137 else-stmt -> ELSE [if-construct-name]
R1138 end-if-stmt -> END IF [if-construct-name]
R1139 if-stmt -> IF ( scalar-logical-expr ) action-stmt
R1140 case-construct -> select-case-stmt [case-stmt block]... end-select-stmt
R1141 select-case-stmt -> [case-construct-name :] SELECT CASE ( case-expr )
R1142 case-stmt -> CASE case-selector [case-construct-name]
R1143 end-select-stmt -> END SELECT [case-construct-name]
R1144 case-expr -> scalar-expr
R1145 case-selector -> ( case-value-range-list ) | DEFAULT
R1146 case-value-range ->
case-value | case-value : | : case-value | case-value : case-value
R1147 case-value -> scalar-constant-expr
R1148 select-rank-construct ->
select-rank-stmt [select-rank-case-stmt block]... end-select-rank-stmt
R1149 select-rank-stmt ->
[select-construct-name :] SELECT RANK ( [associate-name =>] selector )
R1150 select-rank-case-stmt ->
RANK ( scalar-int-constant-expr ) [select-construct-name] |
RANK ( * ) [select-construct-name] |
RANK DEFAULT [select-construct-name]
R1151 end-select-rank-stmt -> END SELECT [select-construct-name]
R1152 select-type-construct ->
select-type-stmt [type-guard-stmt block]... end-select-type-stmt
R1153 select-type-stmt ->
[select-construct-name :] SELECT TYPE ( [associate-name =>] selector )
R1154 type-guard-stmt ->
TYPE IS ( type-spec ) [select-construct-name] |
CLASS IS ( derived-type-spec ) [select-construct-name] |
CLASS DEFAULT [select-construct-name]
R1155 end-select-type-stmt -> END SELECT [select-construct-name]
R1156 exit-stmt -> EXIT [construct-name]
R1157 goto-stmt -> GO TO label
R1158 computed-goto-stmt -> GO TO ( label-list ) [,] scalar-int-expr
R1159 continue-stmt -> CONTINUE
R1160 stop-stmt -> STOP [stop-code] [, QUIET = scalar-logical-expr]
R1161 error-stop-stmt -> ERROR STOP [stop-code] [, QUIET = scalar-logical-expr]
R1162 stop-code -> scalar-default-char-expr | scalar-int-expr
R1163 fail-image-stmt -> FAIL IMAGE
R1164 sync-all-stmt -> SYNC ALL [( [sync-stat-list] )]
R1165 sync-stat -> STAT = stat-variable | ERRMSG = errmsg-variable
R1166 sync-images-stmt -> SYNC IMAGES ( image-set [, sync-stat-list] )
R1167 image-set -> int-expr | *
R1168 sync-memory-stmt -> SYNC MEMORY [( [sync-stat-list] )]
R1169 sync-team-stmt -> SYNC TEAM ( team-value [, sync-stat-list] )
R1170 event-post-stmt -> EVENT POST ( event-variable [, sync-stat-list] )
R1171 event-variable -> scalar-variable
R1172 event-wait-stmt -> EVENT WAIT ( event-variable [, event-wait-spec-list] )
R1173 event-wait-spec -> until-spec | sync-stat
R1174 until-spec -> UNTIL_COUNT = scalar-int-expr
R1175 form-team-stmt ->
FORM TEAM ( team-number , team-variable [, form-team-spec-list] )
R1176 team-number -> scalar-int-expr
R1177 team-variable -> scalar-variable
R1178 form-team-spec -> NEW_INDEX = scalar-int-expr | sync-stat
R1179 lock-stmt -> LOCK ( lock-variable [, lock-stat-list] )
R1180 lock-stat -> ACQUIRED_LOCK = scalar-logical-variable | sync-stat
R1181 unlock-stmt -> UNLOCK ( lock-variable [, sync-stat-list] )
R1182 lock-variable -> scalar-variable
R1201 io-unit -> file-unit-number | * | internal-file-variable
R1202 file-unit-number -> scalar-int-expr
R1203 internal-file-variable -> char-variable
R1204 open-stmt -> OPEN ( connect-spec-list )
R1205 connect-spec ->
[UNIT =] file-unit-number | ACCESS = scalar-default-char-expr |
ACTION = scalar-default-char-expr |
ASYNCHRONOUS = scalar-default-char-expr |
BLANK = scalar-default-char-expr |
DECIMAL = scalar-default-char-expr | DELIM = scalar-default-char-expr |
ENCODING = scalar-default-char-expr | ERR = label |
FILE = file-name-expr | FORM = scalar-default-char-expr |
IOMSG = iomsg-variable | IOSTAT = scalar-int-variable |
NEWUNIT = scalar-int-variable | PAD = scalar-default-char-expr |
POSITION = scalar-default-char-expr | RECL = scalar-int-expr |
ROUND = scalar-default-char-expr | SIGN = scalar-default-char-expr |
STATUS = scalar-default-char-expr
@ | CONVERT = scalar-default-char-expr
| DISPOSE = scalar-default-char-expr
R1206 file-name-expr -> scalar-default-char-expr
R1207 iomsg-variable -> scalar-default-char-variable
R1208 close-stmt -> CLOSE ( close-spec-list )
R1209 close-spec ->
[UNIT =] file-unit-number | IOSTAT = scalar-int-variable |
IOMSG = iomsg-variable | ERR = label |
STATUS = scalar-default-char-expr
R1210 read-stmt ->
READ ( io-control-spec-list ) [input-item-list] |
READ format [, input-item-list]
R1211 write-stmt -> WRITE ( io-control-spec-list ) [output-item-list]
R1212 print-stmt -> PRINT format [, output-item-list]
R1213 io-control-spec ->
[UNIT =] io-unit | [FMT =] format | [NML =] namelist-group-name |
ADVANCE = scalar-default-char-expr |
ASYNCHRONOUS = scalar-default-char-constant-expr |
BLANK = scalar-default-char-expr | DECIMAL = scalar-default-char-expr |
DELIM = scalar-default-char-expr | END = label | EOR = label |
ERR = label | ID = id-variable | IOMSG = iomsg-variable |
IOSTAT = scalar-int-variable | PAD = scalar-default-char-expr |
POS = scalar-int-expr | REC = scalar-int-expr |
ROUND = scalar-default-char-expr | SIGN = scalar-default-char-expr |
SIZE = scalar-int-variable
R1214 id-variable -> scalar-int-variable
R1215 format -> default-char-expr | label | *
R1216 input-item -> variable | io-implied-do
R1217 output-item -> expr | io-implied-do
R1218 io-implied-do -> ( io-implied-do-object-list , io-implied-do-control )
R1219 io-implied-do-object -> input-item | output-item
R1220 io-implied-do-control ->
do-variable = scalar-int-expr , scalar-int-expr [, scalar-int-expr]
R1221 dtv-type-spec -> TYPE ( derived-type-spec ) | CLASS ( derived-type-spec )
R1222 wait-stmt -> WAIT ( wait-spec-list )
R1223 wait-spec ->
[UNIT =] file-unit-number | END = label | EOR = label | ERR = label |
ID = scalar-int-expr | IOMSG = iomsg-variable |
IOSTAT = scalar-int-variable
R1224 backspace-stmt ->
BACKSPACE file-unit-number | BACKSPACE ( position-spec-list )
R1225 endfile-stmt -> ENDFILE file-unit-number | ENDFILE ( position-spec-list )
R1226 rewind-stmt -> REWIND file-unit-number | REWIND ( position-spec-list )
R1227 position-spec ->
[UNIT =] file-unit-number | IOMSG = iomsg-variable |
IOSTAT = scalar-int-variable | ERR = label
R1228 flush-stmt -> FLUSH file-unit-number | FLUSH ( flush-spec-list )
R1229 flush-spec ->
[UNIT =] file-unit-number | IOSTAT = scalar-int-variable |
IOMSG = iomsg-variable | ERR = label
R1230 inquire-stmt ->
INQUIRE ( inquire-spec-list ) |
INQUIRE ( IOLENGTH = scalar-int-variable ) output-item-list
R1231 inquire-spec ->
[UNIT =] file-unit-number | FILE = file-name-expr |
ACCESS = scalar-default-char-variable |
ACTION = scalar-default-char-variable |
ASYNCHRONOUS = scalar-default-char-variable |
BLANK = scalar-default-char-variable |
DECIMAL = scalar-default-char-variable |
DELIM = scalar-default-char-variable |
ENCODING = scalar-default-char-variable |
ERR = label | EXIST = scalar-logical-variable |
FORM = scalar-default-char-variable |
FORMATTED = scalar-default-char-variable | ID = scalar-int-expr |
IOMSG = iomsg-variable | IOSTAT = scalar-int-variable |
NAME = scalar-default-char-variable |
NAMED = scalar-logical-variable | NEXTREC = scalar-int-variable |
NUMBER = scalar-int-variable | OPENED = scalar-logical-variable |
PAD = scalar-default-char-variable |
PENDING = scalar-logical-variable | POS = scalar-int-variable |
POSITION = scalar-default-char-variable |
READ = scalar-default-char-variable |
READWRITE = scalar-default-char-variable |
RECL = scalar-int-variable | ROUND = scalar-default-char-variable |
SEQUENTIAL = scalar-default-char-variable |
SIGN = scalar-default-char-variable | SIZE = scalar-int-variable |
STREAM = scalar-default-char-variable |
STATUS = scalar-default-char-variable |
WRITE = scalar-default-char-variable
@ | CONVERT = scalar-default-char-expr
| DISPOSE = scalar-default-char-expr
R1301 format-stmt -> FORMAT format-specification
R1302 format-specification ->
( [format-items] ) | ( [format-items ,] unlimited-format-item )
R1303 format-items -> format-item [[,] format-item]...
R1304 format-item ->
[r] data-edit-desc | control-edit-desc | char-string-edit-desc | [r] ( format-items )
R1305 unlimited-format-item -> * ( format-items )
R1306 r -> digit-string
R1307 data-edit-desc ->
I w [. m] | B w [. m] | O w [. m] | Z w [. m] | F w . d |
E w . d [E e] | EN w . d [E e] | ES w . d [E e] | EX w . d [E e] |
G w [. d [E e]] | L w | A [w] | D w . d |
DT [char-literal-constant] [( v-list )]
R1308 w -> digit-string
R1309 m -> digit-string
R1310 d -> digit-string
R1311 e -> digit-string
R1312 v -> [sign] digit-string
R1313 control-edit-desc ->
position-edit-desc | [r] / | : | sign-edit-desc | k P |
blank-interp-edit-desc | round-edit-desc | decimal-edit-desc |
@ $ | \
R1314 k -> [sign] digit-string
R1315 position-edit-desc -> T n | TL n | TR n | n X
R1316 n -> digit-string
R1317 sign-edit-desc -> SS | SP | S
R1318 blank-interp-edit-desc -> BN | BZ
R1319 round-edit-desc -> RU | RD | RZ | RN | RC | RP
R1320 decimal-edit-desc -> DC | DP
R1321 char-string-edit-desc -> char-literal-constant
R1401 main-program ->
[program-stmt] [specification-part] [execution-part]
[internal-subprogram-part] end-program-stmt
R1402 program-stmt -> PROGRAM program-name
R1403 end-program-stmt -> END [PROGRAM [program-name]]
R1404 module ->
module-stmt [specification-part] [module-subprogram-part]
end-module-stmt
R1405 module-stmt -> MODULE module-name
R1406 end-module-stmt -> END [MODULE [module-name]]
R1407 module-subprogram-part -> contains-stmt [module-subprogram]...
R1408 module-subprogram ->
function-subprogram | subroutine-subprogram |
separate-module-subprogram
R1409 use-stmt ->
USE [[, module-nature] ::] module-name [, rename-list] |
USE [[, module-nature] ::] module-name , ONLY : [only-list]
R1410 module-nature -> INTRINSIC | NON_INTRINSIC
R1411 rename ->
local-name => use-name |
OPERATOR ( local-defined-operator ) =>
OPERATOR ( use-defined-operator )
R1412 only -> generic-spec | only-use-name | rename
R1413 only-use-name -> use-name
R1414 local-defined-operator -> defined-unary-op | defined-binary-op
R1415 use-defined-operator -> defined-unary-op | defined-binary-op
R1416 submodule ->
submodule-stmt [specification-part] [module-subprogram-part]
end-submodule-stmt
R1417 submodule-stmt -> SUBMODULE ( parent-identifier ) submodule-name
R1418 parent-identifier -> ancestor-module-name [: parent-submodule-name]
R1419 end-submodule-stmt -> END [SUBMODULE [submodule-name]]
R1420 block-data -> block-data-stmt [specification-part] end-block-data-stmt
R1421 block-data-stmt -> BLOCK DATA [block-data-name]
R1422 end-block-data-stmt -> END [BLOCK DATA [block-data-name]]
R1501 interface-block ->
interface-stmt [interface-specification]... end-interface-stmt
R1502 interface-specification -> interface-body | procedure-stmt
R1503 interface-stmt -> INTERFACE [generic-spec] | ABSTRACT INTERFACE
R1504 end-interface-stmt -> END INTERFACE [generic-spec]
R1505 interface-body ->
function-stmt [specification-part] end-function-stmt |
subroutine-stmt [specification-part] end-subroutine-stmt
R1506 procedure-stmt -> [MODULE] PROCEDURE [::] specific-procedure-list
R1507 specific-procedure -> procedure-name
R1508 generic-spec ->
generic-name | OPERATOR ( defined-operator ) |
ASSIGNMENT ( = ) | defined-io-generic-spec
R1509 defined-io-generic-spec ->
READ ( FORMATTED ) | READ ( UNFORMATTED ) |
WRITE ( FORMATTED ) | WRITE ( UNFORMATTED )
R1510 generic-stmt ->
GENERIC [, access-spec] :: generic-spec => specific-procedure-list
R1511 external-stmt -> EXTERNAL [::] external-name-list
R1512 procedure-declaration-stmt ->
PROCEDURE ( [proc-interface] ) [[, proc-attr-spec]... ::]
proc-decl-list
R1513 proc-interface -> interface-name | declaration-type-spec
R1514 proc-attr-spec ->
access-spec | proc-language-binding-spec | INTENT ( intent-spec ) |
OPTIONAL | POINTER | PROTECTED | SAVE
R1515 proc-decl -> procedure-entity-name [=> proc-pointer-init]
R1516 interface-name -> name
R1517 proc-pointer-init -> null-init | initial-proc-target
R1518 initial-proc-target -> procedure-name
R1519 intrinsic-stmt -> INTRINSIC [::] intrinsic-procedure-name-list
R1520 function-reference -> procedure-designator ( [actual-arg-spec-list] )
R1521 call-stmt -> CALL procedure-designator [( [actual-arg-spec-list] )]
R1522 procedure-designator ->
procedure-name | proc-component-ref | data-ref % binding-name
R1523 actual-arg-spec -> [keyword =] actual-arg
R1524 actual-arg ->
expr | variable | procedure-name | proc-component-ref | alt-return-spec
R1525 alt-return-spec -> * label
R1526 prefix -> prefix-spec [prefix-spec]...
R1527 prefix-spec ->
declaration-type-spec | ELEMENTAL | IMPURE | MODULE | NON_RECURSIVE |
PURE | RECURSIVE
R1528 proc-language-binding-spec -> language-binding-spec
R1529 function-subprogram ->
function-stmt [specification-part] [execution-part]
[internal-subprogram-part] end-function-stmt
R1530 function-stmt ->
[prefix] FUNCTION function-name ( [dummy-arg-name-list] ) [suffix]
R1531 dummy-arg-name -> name
R1532 suffix ->
proc-language-binding-spec [RESULT ( result-name )] |
RESULT ( result-name ) [proc-language-binding-spec]
R1533 end-function-stmt -> END [FUNCTION [function-name]]
R1534 subroutine-subprogram ->
subroutine-stmt [specification-part] [execution-part]
[internal-subprogram-part] end-subroutine-stmt
R1535 subroutine-stmt ->
[prefix] SUBROUTINE subroutine-name
[( [dummy-arg-list] ) [proc-language-binding-spec]]
R1536 dummy-arg -> dummy-arg-name | *
R1537 end-subroutine-stmt -> END [SUBROUTINE [subroutine-name]]
R1538 separate-module-subprogram ->
mp-subprogram-stmt [specification-part] [execution-part]
[internal-subprogram-part] end-mp-subprogram-stmt
R1539 mp-subprogram-stmt -> MODULE PROCEDURE procedure-name
R1540 end-mp-subprogram-stmt -> END [PROCEDURE [procedure-name]]
R1541 entry-stmt -> ENTRY entry-name [( [dummy-arg-list] ) [suffix]]
R1542 return-stmt -> RETURN [scalar-int-expr]
R1543 contains-stmt -> CONTAINS
R1544 stmt-function-stmt ->
function-name ( [dummy-arg-name-list] ) = scalar-expr

View File

@ -0,0 +1,38 @@
;;===-- documentation/flang-c-style.el ------------------------------------===;;
;;
;; 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
;;
;;===----------------------------------------------------------------------===;;
;; Define a cc-mode style for editing C++ codes in Flang.
;;
;; Inspired from LLVM style in
;; https://github.com/llvm-mirror/llvm/blob/master/utils/emacs/emacs.el
;;
(c-add-style "flang"
'("gnu"
(fill-column . 80)
(c++-indent-level . 2)
(c-basic-offset . 2)
(indent-tabs-mode . nil)
(c-offsets-alist .
((arglist-intro . ++)
(innamespace . 0)
(member-init-intro . ++)
))
))
;;
;; Use the following to make it the default.
;;
(defun flang-c-mode-hook ()
(c-set-style "flang")
)
(add-hook 'c-mode-hook 'flang-c-mode-hook)
(add-hook 'c++-mode-hook 'flang-c-mode-hook)

View File

@ -0,0 +1 @@
add_subdirectory(flang)

View File

@ -0,0 +1,3 @@
if(LINK_WITH_FIR)
add_subdirectory(Optimizer)
endif()

View File

@ -0,0 +1,65 @@
//===-- include/flang/Common/Fortran-features.h -----------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_COMMON_FORTRAN_FEATURES_H_
#define FORTRAN_COMMON_FORTRAN_FEATURES_H_
#include "flang/Common/Fortran.h"
#include "flang/Common/enum-set.h"
#include "flang/Common/idioms.h"
namespace Fortran::common {
ENUM_CLASS(LanguageFeature, BackslashEscapes, OldDebugLines,
FixedFormContinuationWithColumn1Ampersand, LogicalAbbreviations,
XOROperator, PunctuationInNames, OptionalFreeFormSpace, BOZExtensions,
EmptyStatement, AlternativeNE, ExecutionPartNamelist, DECStructures,
DoubleComplex, Byte, StarKind, QuadPrecision, SlashInitialization,
TripletInArrayConstructor, MissingColons, SignedComplexLiteral,
OldStyleParameter, ComplexConstructor, PercentLOC, SignedPrimary, FileName,
Convert, Dispose, IOListLeadingComma, AbbreviatedEditDescriptor,
ProgramParentheses, PercentRefAndVal, OmitFunctionDummies, CrayPointer,
Hollerith, ArithmeticIF, Assign, AssignedGOTO, Pause, OpenMP,
CruftAfterAmpersand, ClassicCComments, AdditionalFormats, BigIntLiterals,
RealDoControls, EquivalenceNumericWithCharacter, AdditionalIntrinsics,
AnonymousParents, OldLabelDoEndStatements, LogicalIntegerAssignment,
EmptySourceFile, ProgramReturn)
using LanguageFeatures = EnumSet<LanguageFeature, LanguageFeature_enumSize>;
class LanguageFeatureControl {
public:
LanguageFeatureControl() {
// These features must be explicitly enabled by command line options.
disable_.set(LanguageFeature::OldDebugLines);
disable_.set(LanguageFeature::OpenMP);
// These features, if enabled, conflict with valid standard usage,
// so there are disabled here by default.
disable_.set(LanguageFeature::BackslashEscapes);
disable_.set(LanguageFeature::LogicalAbbreviations);
disable_.set(LanguageFeature::XOROperator);
}
LanguageFeatureControl(const LanguageFeatureControl &) = default;
void Enable(LanguageFeature f, bool yes = true) { disable_.set(f, !yes); }
void EnableWarning(LanguageFeature f, bool yes = true) { warn_.set(f, yes); }
void WarnOnAllNonstandard(bool yes = true) { warnAll_ = yes; }
bool IsEnabled(LanguageFeature f) const { return !disable_.test(f); }
bool ShouldWarn(LanguageFeature f) const {
return (warnAll_ && f != LanguageFeature::OpenMP) || warn_.test(f);
}
// Return all spellings of operators names, depending on features enabled
std::vector<const char *> GetNames(LogicalOperator) const;
std::vector<const char *> GetNames(RelationalOperator) const;
private:
LanguageFeatures disable_;
LanguageFeatures warn_;
bool warnAll_{false};
};
} // namespace Fortran::common
#endif // FORTRAN_COMMON_FORTRAN_FEATURES_H_

View File

@ -0,0 +1,72 @@
//===-- include/flang/Common/Fortran.h --------------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_COMMON_FORTRAN_H_
#define FORTRAN_COMMON_FORTRAN_H_
// Fortran language concepts that are used in many phases are defined
// once here to avoid redundancy and needless translation.
#include "idioms.h"
#include <cinttypes>
#include <vector>
namespace Fortran::common {
// Fortran has five kinds of intrinsic data types, plus the derived types.
ENUM_CLASS(TypeCategory, Integer, Real, Complex, Character, Logical, Derived)
constexpr bool IsNumericTypeCategory(TypeCategory category) {
return category == TypeCategory::Integer || category == TypeCategory::Real ||
category == TypeCategory::Complex;
}
// Kinds of IMPORT statements. Default means IMPORT or IMPORT :: names.
ENUM_CLASS(ImportKind, Default, Only, None, All)
// The attribute on a type parameter can be KIND or LEN.
ENUM_CLASS(TypeParamAttr, Kind, Len)
ENUM_CLASS(NumericOperator, Power, Multiply, Divide, Add, Subtract)
const char *AsFortran(NumericOperator);
ENUM_CLASS(LogicalOperator, And, Or, Eqv, Neqv, Not)
const char *AsFortran(LogicalOperator);
ENUM_CLASS(RelationalOperator, LT, LE, EQ, NE, GE, GT)
const char *AsFortran(RelationalOperator);
ENUM_CLASS(Intent, Default, In, Out, InOut)
ENUM_CLASS(IoStmtKind, None, Backspace, Close, Endfile, Flush, Inquire, Open,
Print, Read, Rewind, Wait, Write)
// Union of specifiers for all I/O statements.
ENUM_CLASS(IoSpecKind, Access, Action, Advance, Asynchronous, Blank, Decimal,
Delim, Direct, Encoding, End, Eor, Err, Exist, File, Fmt, Form, Formatted,
Id, Iomsg, Iostat, Name, Named, Newunit, Nextrec, Nml, Number, Opened, Pad,
Pending, Pos, Position, Read, Readwrite, Rec, Recl, Round, Sequential, Sign,
Size, Status, Stream, Unformatted, Unit, Write,
Convert, // nonstandard
Dispose, // nonstandard
)
// Floating-point rounding modes; these are packed into a byte to save
// room in the runtime's format processing context structure.
enum class RoundingMode : std::uint8_t {
TiesToEven, // ROUND=NEAREST, RN - default IEEE rounding
ToZero, // ROUND=ZERO, RZ - truncation
Down, // ROUND=DOWN, RD
Up, // ROUND=UP, RU
TiesAwayFromZero, // ROUND=COMPATIBLE, RC - ties round away from zero
};
// Fortran arrays may have up to 15 dimensions (See Fortran 2018 section 5.4.6).
static constexpr int maxRank{15};
} // namespace Fortran::common
#endif // FORTRAN_COMMON_FORTRAN_H_

View File

@ -0,0 +1,87 @@
//===-- include/flang/Common/bit-population-count.h -------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_COMMON_BIT_POPULATION_COUNT_H_
#define FORTRAN_COMMON_BIT_POPULATION_COUNT_H_
// Fast and portable functions that implement Fortran's POPCNT and POPPAR
// intrinsic functions. POPCNT returns the number of bits that are set (1)
// in its argument. POPPAR is a parity function that returns true
// when POPCNT is odd.
#include <cinttypes>
namespace Fortran::common {
inline constexpr int BitPopulationCount(std::uint64_t x) {
// In each of the 32 2-bit fields, count the bits that were present.
// This leaves a value [0..2] in each of these 2-bit fields.
x = (x & 0x5555555555555555) + ((x >> 1) & 0x5555555555555555);
// Combine into 16 4-bit fields, each holding [0..4]
x = (x & 0x3333333333333333) + ((x >> 2) & 0x3333333333333333);
// Now 8 8-bit fields, each with [0..8] in their lower 4 bits.
x = (x & 0x0f0f0f0f0f0f0f0f) + ((x >> 4) & 0x0f0f0f0f0f0f0f0f);
// Now 4 16-bit fields, each with [0..16] in their lower 5 bits.
x = (x & 0x001f001f001f001f) + ((x >> 8) & 0x001f001f001f001f);
// Now 2 32-bit fields, each with [0..32] in their lower 6 bits.
x = (x & 0x0000003f0000003f) + ((x >> 16) & 0x0000003f0000003f);
// Last step: 1 64-bit field, with [0..64]
return (x & 0x7f) + (x >> 32);
}
inline constexpr int BitPopulationCount(std::uint32_t x) {
// In each of the 16 2-bit fields, count the bits that were present.
// This leaves a value [0..2] in each of these 2-bit fields.
x = (x & 0x55555555) + ((x >> 1) & 0x55555555);
// Combine into 8 4-bit fields, each holding [0..4]
x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
// Now 4 8-bit fields, each with [0..8] in their lower 4 bits.
x = (x & 0x0f0f0f0f) + ((x >> 4) & 0x0f0f0f0f);
// Now 2 16-bit fields, each with [0..16] in their lower 5 bits.
x = (x & 0x001f001f) + ((x >> 8) & 0x001f001f);
// Last step: 1 32-bit field, with [0..32]
return (x & 0x3f) + (x >> 16);
}
inline constexpr int BitPopulationCount(std::uint16_t x) {
// In each of the 8 2-bit fields, count the bits that were present.
// This leaves a value [0..2] in each of these 2-bit fields.
x = (x & 0x5555) + ((x >> 1) & 0x5555);
// Combine into 4 4-bit fields, each holding [0..4]
x = (x & 0x3333) + ((x >> 2) & 0x3333);
// Now 2 8-bit fields, each with [0..8] in their lower 4 bits.
x = (x & 0x0f0f) + ((x >> 4) & 0x0f0f);
// Last step: 1 16-bit field, with [0..16]
return (x & 0x1f) + (x >> 8);
}
inline constexpr int BitPopulationCount(std::uint8_t x) {
// In each of the 4 2-bit fields, count the bits that were present.
// This leaves a value [0..2] in each of these 2-bit fields.
x = (x & 0x55) + ((x >> 1) & 0x55);
// Combine into 2 4-bit fields, each holding [0..4]
x = (x & 0x33) + ((x >> 2) & 0x33);
// Last step: 1 8-bit field, with [0..8]
return (x & 0xf) + (x >> 4);
}
template <typename UINT> inline constexpr bool Parity(UINT x) {
return BitPopulationCount(x) & 1;
}
// "Parity is for farmers." -- Seymour R. Cray
template <typename UINT> inline constexpr int TrailingZeroBitCount(UINT x) {
if ((x & 1) != 0) {
return 0; // fast path for odd values
} else {
return BitPopulationCount(static_cast<UINT>(x ^ (x - 1))) - !!x;
}
}
} // namespace Fortran::common
#endif // FORTRAN_COMMON_BIT_POPULATION_COUNT_H_

View File

@ -0,0 +1,147 @@
//===-- include/flang/Common/constexpr-bitset.h -----------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_COMMON_CONSTEXPR_BITSET_H_
#define FORTRAN_COMMON_CONSTEXPR_BITSET_H_
// Implements a replacement for std::bitset<> that is suitable for use
// in constexpr expressions. Limited to elements in [0..63].
#include "bit-population-count.h"
#include <cstddef>
#include <cstdint>
#include <initializer_list>
#include <optional>
#include <type_traits>
namespace Fortran::common {
template <int BITS> class BitSet {
static_assert(BITS > 0 && BITS <= 64);
static constexpr bool partialWord{BITS != 32 && BITS != 64};
using Word = std::conditional_t<(BITS > 32), std::uint64_t, std::uint32_t>;
static constexpr Word allBits{
partialWord ? (static_cast<Word>(1) << BITS) - 1 : ~static_cast<Word>(0)};
constexpr BitSet(Word b) : bits_{b} {}
public:
constexpr BitSet() {}
constexpr BitSet(const std::initializer_list<int> &xs) {
for (auto x : xs) {
set(x);
}
}
constexpr BitSet(const BitSet &) = default;
constexpr BitSet(BitSet &&) = default;
constexpr BitSet &operator=(const BitSet &) = default;
constexpr BitSet &operator=(BitSet &&) = default;
constexpr BitSet &operator&=(const BitSet &that) {
bits_ &= that.bits_;
return *this;
}
constexpr BitSet &operator&=(BitSet &&that) {
bits_ &= that.bits_;
return *this;
}
constexpr BitSet &operator^=(const BitSet &that) {
bits_ ^= that.bits_;
return *this;
}
constexpr BitSet &operator^=(BitSet &&that) {
bits_ ^= that.bits_;
return *this;
}
constexpr BitSet &operator|=(const BitSet &that) {
bits_ |= that.bits_;
return *this;
}
constexpr BitSet &operator|=(BitSet &&that) {
bits_ |= that.bits_;
return *this;
}
constexpr BitSet operator~() const { return ~bits_; }
constexpr BitSet operator&(const BitSet &that) const {
return bits_ & that.bits_;
}
constexpr BitSet operator&(BitSet &&that) const { return bits_ & that.bits_; }
constexpr BitSet operator^(const BitSet &that) const {
return bits_ ^ that.bits_;
}
constexpr BitSet operator^(BitSet &&that) const { return bits_ & that.bits_; }
constexpr BitSet operator|(const BitSet &that) const {
return bits_ | that.bits_;
}
constexpr BitSet operator|(BitSet &&that) const { return bits_ | that.bits_; }
constexpr bool operator==(const BitSet &that) const {
return bits_ == that.bits_;
}
constexpr bool operator==(BitSet &&that) const { return bits_ == that.bits_; }
constexpr bool operator!=(const BitSet &that) const {
return bits_ != that.bits_;
}
constexpr bool operator!=(BitSet &&that) const { return bits_ != that.bits_; }
static constexpr std::size_t size() { return BITS; }
constexpr bool test(std::size_t x) const {
return x < BITS && ((bits_ >> x) & 1) != 0;
}
constexpr bool all() const { return bits_ == allBits; }
constexpr bool any() const { return bits_ != 0; }
constexpr bool none() const { return bits_ == 0; }
constexpr std::size_t count() const { return BitPopulationCount(bits_); }
constexpr BitSet &set() {
bits_ = allBits;
return *this;
}
constexpr BitSet set(std::size_t x, bool value = true) {
if (!value) {
return reset(x);
} else {
bits_ |= static_cast<Word>(1) << x;
return *this;
}
}
constexpr BitSet &reset() {
bits_ = 0;
return *this;
}
constexpr BitSet &reset(std::size_t x) {
bits_ &= ~(static_cast<Word>(1) << x);
return *this;
}
constexpr BitSet &flip() {
bits_ ^= allBits;
return *this;
}
constexpr BitSet &flip(std::size_t x) {
bits_ ^= static_cast<Word>(1) << x;
return *this;
}
constexpr std::optional<std::size_t> LeastElement() const {
if (bits_ == 0) {
return std::nullopt;
} else {
return {TrailingZeroBitCount(bits_)};
}
}
Word bits() const { return bits_; }
private:
Word bits_{0};
};
} // namespace Fortran::common
#endif // FORTRAN_COMMON_CONSTEXPR_BITSET_H_

View File

@ -0,0 +1,61 @@
//===-- include/flang/Common/default-kinds.h --------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_COMMON_DEFAULT_KINDS_H_
#define FORTRAN_COMMON_DEFAULT_KINDS_H_
#include "flang/Common/Fortran.h"
#include <cstdint>
namespace Fortran::common {
// All address calculations in generated code are 64-bit safe.
// Compile-time folding of bounds, subscripts, and lengths
// consequently uses 64-bit signed integers. The name reflects
// this usage as a subscript into a constant array.
using ConstantSubscript = std::int64_t;
// Represent the default values of the kind parameters of the
// various intrinsic types. Most of these can be configured by
// means of the compiler command line.
class IntrinsicTypeDefaultKinds {
public:
IntrinsicTypeDefaultKinds();
int subscriptIntegerKind() const { return subscriptIntegerKind_; }
int sizeIntegerKind() const { return sizeIntegerKind_; }
int doublePrecisionKind() const { return doublePrecisionKind_; }
int quadPrecisionKind() const { return quadPrecisionKind_; }
IntrinsicTypeDefaultKinds &set_defaultIntegerKind(int);
IntrinsicTypeDefaultKinds &set_subscriptIntegerKind(int);
IntrinsicTypeDefaultKinds &set_sizeIntegerKind(int);
IntrinsicTypeDefaultKinds &set_defaultRealKind(int);
IntrinsicTypeDefaultKinds &set_doublePrecisionKind(int);
IntrinsicTypeDefaultKinds &set_quadPrecisionKind(int);
IntrinsicTypeDefaultKinds &set_defaultCharacterKind(int);
IntrinsicTypeDefaultKinds &set_defaultLogicalKind(int);
int GetDefaultKind(TypeCategory) const;
private:
// Default REAL just simply has to be IEEE-754 single precision today.
// It occupies one numeric storage unit by definition. The default INTEGER
// and default LOGICAL intrinsic types also have to occupy one numeric
// storage unit, so their kinds are also forced. Default COMPLEX must always
// comprise two default REAL components.
int defaultIntegerKind_{4};
int subscriptIntegerKind_{8};
int sizeIntegerKind_{4}; // SIZE(), UBOUND(), &c. default KIND=
int defaultRealKind_{defaultIntegerKind_};
int doublePrecisionKind_{2 * defaultRealKind_};
int quadPrecisionKind_{2 * doublePrecisionKind_};
int defaultCharacterKind_{1};
int defaultLogicalKind_{defaultIntegerKind_};
};
} // namespace Fortran::common
#endif // FORTRAN_COMMON_DEFAULT_KINDS_H_

View File

@ -0,0 +1,224 @@
//===-- include/flang/Common/enum-set.h -------------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_COMMON_ENUM_SET_H_
#define FORTRAN_COMMON_ENUM_SET_H_
// Implements a set of enums as a std::bitset<>. APIs from bitset<> and set<>
// can be used on these sets, whichever might be more clear to the user.
// This class template facilitates the use of the more type-safe C++ "enum
// class" feature without loss of convenience.
#include "constexpr-bitset.h"
#include "idioms.h"
#include "llvm/Support/raw_ostream.h"
#include <bitset>
#include <cstddef>
#include <initializer_list>
#include <optional>
#include <string>
#include <type_traits>
namespace Fortran::common {
template <typename ENUM, std::size_t BITS> class EnumSet {
static_assert(BITS > 0);
public:
// When the bitset fits in a word, use a custom local bitset class that is
// more amenable to constexpr evaluation than the current std::bitset<>.
using bitsetType =
std::conditional_t<(BITS <= 64), common::BitSet<BITS>, std::bitset<BITS>>;
using enumerationType = ENUM;
constexpr EnumSet() {}
constexpr EnumSet(const std::initializer_list<enumerationType> &enums) {
for (auto x : enums) {
set(x);
}
}
constexpr EnumSet(const EnumSet &) = default;
constexpr EnumSet(EnumSet &&) = default;
constexpr EnumSet &operator=(const EnumSet &) = default;
constexpr EnumSet &operator=(EnumSet &&) = default;
const bitsetType &bitset() const { return bitset_; }
constexpr EnumSet &operator&=(const EnumSet &that) {
bitset_ &= that.bitset_;
return *this;
}
constexpr EnumSet &operator&=(EnumSet &&that) {
bitset_ &= that.bitset_;
return *this;
}
constexpr EnumSet &operator|=(const EnumSet &that) {
bitset_ |= that.bitset_;
return *this;
}
constexpr EnumSet &operator|=(EnumSet &&that) {
bitset_ |= that.bitset_;
return *this;
}
constexpr EnumSet &operator^=(const EnumSet &that) {
bitset_ ^= that.bitset_;
return *this;
}
constexpr EnumSet &operator^=(EnumSet &&that) {
bitset_ ^= that.bitset_;
return *this;
}
constexpr EnumSet operator~() const {
EnumSet result;
result.bitset_ = ~bitset_;
return result;
}
constexpr EnumSet operator&(const EnumSet &that) const {
EnumSet result{*this};
result.bitset_ &= that.bitset_;
return result;
}
constexpr EnumSet operator&(EnumSet &&that) const {
EnumSet result{*this};
result.bitset_ &= that.bitset_;
return result;
}
constexpr EnumSet operator|(const EnumSet &that) const {
EnumSet result{*this};
result.bitset_ |= that.bitset_;
return result;
}
constexpr EnumSet operator|(EnumSet &&that) const {
EnumSet result{*this};
result.bitset_ |= that.bitset_;
return result;
}
constexpr EnumSet operator^(const EnumSet &that) const {
EnumSet result{*this};
result.bitset_ ^= that.bitset_;
return result;
}
constexpr EnumSet operator^(EnumSet &&that) const {
EnumSet result{*this};
result.bitset_ ^= that.bitset_;
return result;
}
constexpr bool operator==(const EnumSet &that) const {
return bitset_ == that.bitset_;
}
constexpr bool operator==(EnumSet &&that) const {
return bitset_ == that.bitset_;
}
constexpr bool operator!=(const EnumSet &that) const {
return bitset_ != that.bitset_;
}
constexpr bool operator!=(EnumSet &&that) const {
return bitset_ != that.bitset_;
}
// N.B. std::bitset<> has size() for max_size(), but that's not the same
// thing as std::set<>::size(), which is an element count.
static constexpr std::size_t max_size() { return BITS; }
constexpr bool test(enumerationType x) const {
return bitset_.test(static_cast<std::size_t>(x));
}
constexpr bool all() const { return bitset_.all(); }
constexpr bool any() const { return bitset_.any(); }
constexpr bool none() const { return bitset_.none(); }
// N.B. std::bitset<> has count() as an element count, while
// std::set<>::count(x) returns 0 or 1 to indicate presence.
constexpr std::size_t count() const { return bitset_.count(); }
constexpr std::size_t count(enumerationType x) const {
return test(x) ? 1 : 0;
}
constexpr EnumSet &set() {
bitset_.set();
return *this;
}
constexpr EnumSet &set(enumerationType x, bool value = true) {
bitset_.set(static_cast<std::size_t>(x), value);
return *this;
}
constexpr EnumSet &reset() {
bitset_.reset();
return *this;
}
constexpr EnumSet &reset(enumerationType x) {
bitset_.reset(static_cast<std::size_t>(x));
return *this;
}
constexpr EnumSet &flip() {
bitset_.flip();
return *this;
}
constexpr EnumSet &flip(enumerationType x) {
bitset_.flip(static_cast<std::size_t>(x));
return *this;
}
constexpr bool empty() const { return none(); }
void clear() { reset(); }
void insert(enumerationType x) { set(x); }
void insert(enumerationType &&x) { set(x); }
void emplace(enumerationType &&x) { set(x); }
void erase(enumerationType x) { reset(x); }
void erase(enumerationType &&x) { reset(x); }
constexpr std::optional<enumerationType> LeastElement() const {
if (empty()) {
return std::nullopt;
} else if constexpr (std::is_same_v<bitsetType, common::BitSet<BITS>>) {
return {static_cast<enumerationType>(bitset_.LeastElement().value())};
} else {
// std::bitset: just iterate
for (std::size_t j{0}; j < BITS; ++j) {
auto enumerator{static_cast<enumerationType>(j)};
if (bitset_.test(enumerator)) {
return {enumerator};
}
}
die("EnumSet::LeastElement(): no bit found in non-empty std::bitset");
}
}
template <typename FUNC> void IterateOverMembers(const FUNC &f) const {
EnumSet copy{*this};
while (auto least{copy.LeastElement()}) {
f(*least);
copy.erase(*least);
}
}
llvm::raw_ostream &Dump(
llvm::raw_ostream &o, std::string EnumToString(enumerationType)) const {
char sep{'{'};
IterateOverMembers([&](auto e) {
o << sep << EnumToString(e);
sep = ',';
});
return o << (sep == '{' ? "{}" : "}");
}
private:
bitsetType bitset_{};
};
} // namespace Fortran::common
template <typename ENUM, std::size_t values>
struct std::hash<Fortran::common::EnumSet<ENUM, values>> {
std::size_t operator()(
const Fortran::common::EnumSet<ENUM, values> &x) const {
return std::hash(x.bitset());
}
};
#endif // FORTRAN_COMMON_ENUM_SET_H_

View File

@ -0,0 +1,845 @@
//===-- include/flang/Common/format.h ---------------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_COMMON_FORMAT_H_
#define FORTRAN_COMMON_FORMAT_H_
#include "enum-set.h"
#include "flang/Common/Fortran.h"
#include <cstring>
// Define a FormatValidator class template to validate a format expression
// of a given CHAR type. To enable use in runtime library code as well as
// compiler code, the implementation does its own parsing without recourse
// to compiler parser machinery, and avoids features that require C++ runtime
// library support. A format expression is a pointer to a fixed size
// character string, with an explicit length. Class function Check analyzes
// the expression for syntax and semantic errors and warnings. When an error
// or warning is found, a caller-supplied reporter function is called, which
// may request early termination of validation analysis when some threshold
// number of errors have been reported. If the context is a READ, WRITE,
// or PRINT statement, rather than a FORMAT statement, statement-specific
// checks are also done.
namespace Fortran::common {
struct FormatMessage {
const char *text; // message text; may have one %s argument
const char *arg; // optional %s argument value
int offset; // offset to message marker
int length; // length of message marker
bool isError; // vs. warning
};
// This declaration is logically private to class FormatValidator.
// It is placed here to work around a clang compilation problem.
ENUM_CLASS(TokenKind, None, A, B, BN, BZ, D, DC, DP, DT, E, EN, ES, EX, F, G, I,
L, O, P, RC, RD, RN, RP, RU, RZ, S, SP, SS, T, TL, TR, X, Z, Colon, Slash,
Backslash, // nonstandard: inhibit newline on output
Dollar, // nonstandard: inhibit newline on output on terminals
Star, LParen, RParen, Comma, Point, Sign,
UnsignedInteger, // value in integerValue_
String) // char-literal-constant or Hollerith constant
template <typename CHAR = char> class FormatValidator {
public:
using Reporter = std::function<bool(const FormatMessage &)>;
FormatValidator(const CHAR *format, size_t length, Reporter reporter,
IoStmtKind stmt = IoStmtKind::None)
: format_{format}, end_{format + length}, reporter_{reporter},
stmt_{stmt}, cursor_{format - 1} {
CHECK(format);
}
bool Check();
int maxNesting() const { return maxNesting_; }
private:
common::EnumSet<TokenKind, TokenKind_enumSize> itemsWithLeadingInts_{
TokenKind::A, TokenKind::B, TokenKind::D, TokenKind::DT, TokenKind::E,
TokenKind::EN, TokenKind::ES, TokenKind::EX, TokenKind::F, TokenKind::G,
TokenKind::I, TokenKind::L, TokenKind::O, TokenKind::P, TokenKind::X,
TokenKind::Z, TokenKind::Slash, TokenKind::LParen};
struct Token {
Token &set_kind(TokenKind kind) {
kind_ = kind;
return *this;
}
Token &set_offset(int offset) {
offset_ = offset;
return *this;
}
Token &set_length(int length) {
length_ = length;
return *this;
}
TokenKind kind() const { return kind_; }
int offset() const { return offset_; }
int length() const { return length_; }
bool IsSet() { return kind_ != TokenKind::None; }
private:
TokenKind kind_{TokenKind::None};
int offset_{0};
int length_{1};
};
void ReportWarning(const char *text) { ReportWarning(text, token_); }
void ReportWarning(
const char *text, Token &token, const char *arg = nullptr) {
FormatMessage msg{
text, arg ? arg : argString_, token.offset(), token.length(), false};
reporterExit_ |= reporter_(msg);
}
void ReportError(const char *text) { ReportError(text, token_); }
void ReportError(const char *text, Token &token, const char *arg = nullptr) {
if (suppressMessageCascade_) {
return;
}
formatHasErrors_ = true;
suppressMessageCascade_ = true;
FormatMessage msg{
text, arg ? arg : argString_, token.offset(), token.length(), true};
reporterExit_ |= reporter_(msg);
}
void SetLength() { SetLength(token_); }
void SetLength(Token &token) {
token.set_length(cursor_ - format_ - token.offset() + (cursor_ < end_));
}
CHAR NextChar();
CHAR LookAheadChar();
void Advance(TokenKind);
void NextToken();
void check_r(bool allowed = true);
bool check_w();
void check_m();
bool check_d();
void check_e();
const CHAR *const format_; // format text
const CHAR *const end_; // one-past-last of format_ text
Reporter reporter_;
IoStmtKind stmt_;
const CHAR *cursor_{}; // current location in format_
const CHAR *laCursor_{}; // lookahead cursor
Token token_{}; // current token
int64_t integerValue_{-1}; // value of UnsignedInteger token
Token knrToken_{}; // k, n, or r UnsignedInteger token
int64_t knrValue_{-1}; // -1 ==> not present
int64_t wValue_{-1};
bool previousTokenWasInt_{false};
char argString_[3]{}; // 1-2 character msg arg; usually edit descriptor name
bool formatHasErrors_{false};
bool unterminatedFormatError_{false};
bool suppressMessageCascade_{false};
bool reporterExit_{false};
int maxNesting_{0}; // max level of nested parentheses
};
template <typename CHAR> CHAR FormatValidator<CHAR>::NextChar() {
for (++cursor_; cursor_ < end_; ++cursor_) {
if (*cursor_ != ' ') {
return toupper(*cursor_);
}
}
cursor_ = end_; // don't allow cursor_ > end_
return ' ';
}
template <typename CHAR> CHAR FormatValidator<CHAR>::LookAheadChar() {
for (laCursor_ = cursor_ + 1; laCursor_ < end_; ++laCursor_) {
if (*laCursor_ != ' ') {
return toupper(*laCursor_);
}
}
laCursor_ = end_; // don't allow laCursor_ > end_
return ' ';
}
// After a call to LookAheadChar, set token kind and advance cursor to laCursor.
template <typename CHAR> void FormatValidator<CHAR>::Advance(TokenKind tk) {
cursor_ = laCursor_;
token_.set_kind(tk);
}
template <typename CHAR> void FormatValidator<CHAR>::NextToken() {
// At entry, cursor_ points before the start of the next token.
// At exit, cursor_ points to last CHAR of token_.
previousTokenWasInt_ = token_.kind() == TokenKind::UnsignedInteger;
CHAR c{NextChar()};
token_.set_kind(TokenKind::None);
token_.set_offset(cursor_ - format_);
token_.set_length(1);
if (c == '_' && integerValue_ >= 0) { // C1305, C1309, C1310, C1312, C1313
ReportError("Kind parameter '_' character in format expression");
}
integerValue_ = -1;
switch (c) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9': {
int64_t lastValue;
const CHAR *lastCursor;
integerValue_ = 0;
bool overflow{false};
do {
lastValue = integerValue_;
lastCursor = cursor_;
integerValue_ = 10 * integerValue_ + c - '0';
if (lastValue > integerValue_) {
overflow = true;
}
c = NextChar();
} while (c >= '0' && c <= '9');
cursor_ = lastCursor;
token_.set_kind(TokenKind::UnsignedInteger);
if (overflow) {
SetLength();
ReportError("Integer overflow in format expression");
break;
}
if (LookAheadChar() != 'H') {
break;
}
// Hollerith constant
if (laCursor_ + integerValue_ < end_) {
token_.set_kind(TokenKind::String);
cursor_ = laCursor_ + integerValue_;
} else {
token_.set_kind(TokenKind::None);
cursor_ = end_;
}
SetLength();
if (stmt_ == IoStmtKind::Read) { // 13.3.2p6
ReportError("'H' edit descriptor in READ format expression");
} else if (token_.kind() == TokenKind::None) {
ReportError("Unterminated 'H' edit descriptor");
} else {
ReportWarning("Legacy 'H' edit descriptor");
}
break;
}
case 'A':
token_.set_kind(TokenKind::A);
break;
case 'B':
switch (LookAheadChar()) {
case 'N':
Advance(TokenKind::BN);
break;
case 'Z':
Advance(TokenKind::BZ);
break;
default:
token_.set_kind(TokenKind::B);
break;
}
break;
case 'D':
switch (LookAheadChar()) {
case 'C':
Advance(TokenKind::DC);
break;
case 'P':
Advance(TokenKind::DP);
break;
case 'T':
Advance(TokenKind::DT);
break;
default:
token_.set_kind(TokenKind::D);
break;
}
break;
case 'E':
switch (LookAheadChar()) {
case 'N':
Advance(TokenKind::EN);
break;
case 'S':
Advance(TokenKind::ES);
break;
case 'X':
Advance(TokenKind::EX);
break;
default:
token_.set_kind(TokenKind::E);
break;
}
break;
case 'F':
token_.set_kind(TokenKind::F);
break;
case 'G':
token_.set_kind(TokenKind::G);
break;
case 'I':
token_.set_kind(TokenKind::I);
break;
case 'L':
token_.set_kind(TokenKind::L);
break;
case 'O':
token_.set_kind(TokenKind::O);
break;
case 'P':
token_.set_kind(TokenKind::P);
break;
case 'R':
switch (LookAheadChar()) {
case 'C':
Advance(TokenKind::RC);
break;
case 'D':
Advance(TokenKind::RD);
break;
case 'N':
Advance(TokenKind::RN);
break;
case 'P':
Advance(TokenKind::RP);
break;
case 'U':
Advance(TokenKind::RU);
break;
case 'Z':
Advance(TokenKind::RZ);
break;
default:
token_.set_kind(TokenKind::None);
break;
}
break;
case 'S':
switch (LookAheadChar()) {
case 'P':
Advance(TokenKind::SP);
break;
case 'S':
Advance(TokenKind::SS);
break;
default:
token_.set_kind(TokenKind::S);
break;
}
break;
case 'T':
switch (LookAheadChar()) {
case 'L':
Advance(TokenKind::TL);
break;
case 'R':
Advance(TokenKind::TR);
break;
default:
token_.set_kind(TokenKind::T);
break;
}
break;
case 'X':
token_.set_kind(TokenKind::X);
break;
case 'Z':
token_.set_kind(TokenKind::Z);
break;
case '-':
case '+':
token_.set_kind(TokenKind::Sign);
break;
case '/':
token_.set_kind(TokenKind::Slash);
break;
case '(':
token_.set_kind(TokenKind::LParen);
break;
case ')':
token_.set_kind(TokenKind::RParen);
break;
case '.':
token_.set_kind(TokenKind::Point);
break;
case ':':
token_.set_kind(TokenKind::Colon);
break;
case '\\':
token_.set_kind(TokenKind::Backslash);
break;
case '$':
token_.set_kind(TokenKind::Dollar);
break;
case '*':
token_.set_kind(LookAheadChar() == '(' ? TokenKind::Star : TokenKind::None);
break;
case ',': {
token_.set_kind(TokenKind::Comma);
CHAR laChar = LookAheadChar();
if (laChar == ',') {
Advance(TokenKind::Comma);
token_.set_offset(cursor_ - format_);
ReportError("Unexpected ',' in format expression");
} else if (laChar == ')') {
ReportError("Unexpected ',' before ')' in format expression");
}
break;
}
case '\'':
case '"':
for (++cursor_; cursor_ < end_; ++cursor_) {
if (*cursor_ == c) {
if (auto nc{cursor_ + 1}; nc < end_ && *nc != c) {
token_.set_kind(TokenKind::String);
break;
}
++cursor_;
}
}
SetLength();
if (stmt_ == IoStmtKind::Read) { // 13.3.2p6
ReportError("String edit descriptor in READ format expression");
} else if (token_.kind() != TokenKind::String) {
ReportError("Unterminated string");
}
break;
default:
if (cursor_ >= end_ && !unterminatedFormatError_) {
suppressMessageCascade_ = false;
ReportError("Unterminated format expression");
unterminatedFormatError_ = true;
}
token_.set_kind(TokenKind::None);
break;
}
SetLength();
}
template <typename CHAR> void FormatValidator<CHAR>::check_r(bool allowed) {
if (!allowed && knrValue_ >= 0) {
ReportError("Repeat specifier before '%s' edit descriptor", knrToken_);
} else if (knrValue_ == 0) {
ReportError("'%s' edit descriptor repeat specifier must be positive",
knrToken_); // C1304
}
}
// Return the predicate "w value is present" to control further processing.
template <typename CHAR> bool FormatValidator<CHAR>::check_w() {
if (token_.kind() == TokenKind::UnsignedInteger) {
wValue_ = integerValue_;
if (wValue_ == 0 &&
(*argString_ == 'A' || *argString_ == 'L' ||
stmt_ == IoStmtKind::Read)) { // C1306, 13.7.2.1p6
ReportError("'%s' edit descriptor 'w' value must be positive");
}
NextToken();
return true;
}
if (*argString_ != 'A') {
ReportWarning("Expected '%s' edit descriptor 'w' value"); // C1306
}
return false;
}
template <typename CHAR> void FormatValidator<CHAR>::check_m() {
if (token_.kind() != TokenKind::Point) {
return;
}
NextToken();
if (token_.kind() != TokenKind::UnsignedInteger) {
ReportError("Expected '%s' edit descriptor 'm' value after '.'");
return;
}
if ((stmt_ == IoStmtKind::Print || stmt_ == IoStmtKind::Write) &&
wValue_ > 0 && integerValue_ > wValue_) { // 13.7.2.2p5, 13.7.2.4p6
ReportError("'%s' edit descriptor 'm' value is greater than 'w' value");
}
NextToken();
}
// Return the predicate "d value is present" to control further processing.
template <typename CHAR> bool FormatValidator<CHAR>::check_d() {
if (token_.kind() != TokenKind::Point) {
ReportError("Expected '%s' edit descriptor '.d' value");
return false;
}
NextToken();
if (token_.kind() != TokenKind::UnsignedInteger) {
ReportError("Expected '%s' edit descriptor 'd' value after '.'");
return false;
}
NextToken();
return true;
}
template <typename CHAR> void FormatValidator<CHAR>::check_e() {
if (token_.kind() != TokenKind::E) {
return;
}
NextToken();
if (token_.kind() != TokenKind::UnsignedInteger) {
ReportError("Expected '%s' edit descriptor 'e' value after 'E'");
return;
}
NextToken();
}
template <typename CHAR> bool FormatValidator<CHAR>::Check() {
if (!*format_) {
ReportError("Empty format expression");
return formatHasErrors_;
}
NextToken();
if (token_.kind() != TokenKind::LParen) {
ReportError("Format expression must have an initial '('");
return formatHasErrors_;
}
NextToken();
int nestLevel{0}; // Outer level ()s are at level 0.
Token starToken{}; // unlimited format token
bool hasDataEditDesc{false};
// Subject to error recovery exceptions, a loop iteration processes one
// edit descriptor or does list management. The loop terminates when
// - a level-0 right paren is processed (format may be valid)
// - the end of an incomplete format is reached
// - the error reporter requests termination (error threshold reached)
while (!reporterExit_) {
Token signToken{};
knrValue_ = -1; // -1 ==> not present
wValue_ = -1;
bool commaRequired{true};
if (token_.kind() == TokenKind::Sign) {
signToken = token_;
NextToken();
}
if (token_.kind() == TokenKind::UnsignedInteger) {
knrToken_ = token_;
knrValue_ = integerValue_;
NextToken();
}
if (signToken.IsSet() && (knrValue_ < 0 || token_.kind() != TokenKind::P)) {
argString_[0] = format_[signToken.offset()];
argString_[1] = 0;
ReportError("Unexpected '%s' in format expression", signToken);
}
// Default message argument.
// Alphabetic edit descriptor names are one or two characters in length.
argString_[0] = toupper(format_[token_.offset()]);
argString_[1] = token_.length() > 1 ? toupper(*cursor_) : 0;
// Process one format edit descriptor or do format list management.
switch (token_.kind()) {
case TokenKind::A:
// R1307 data-edit-desc -> A [w]
hasDataEditDesc = true;
check_r();
NextToken();
check_w();
break;
case TokenKind::B:
case TokenKind::I:
case TokenKind::O:
case TokenKind::Z:
// R1307 data-edit-desc -> B w [. m] | I w [. m] | O w [. m] | Z w [. m]
hasDataEditDesc = true;
check_r();
NextToken();
if (check_w()) {
check_m();
}
break;
case TokenKind::D:
case TokenKind::F:
// R1307 data-edit-desc -> D w . d | F w . d
hasDataEditDesc = true;
check_r();
NextToken();
if (check_w()) {
check_d();
}
break;
case TokenKind::E:
case TokenKind::EN:
case TokenKind::ES:
case TokenKind::EX:
// R1307 data-edit-desc ->
// E w . d [E e] | EN w . d [E e] | ES w . d [E e] | EX w . d [E e]
hasDataEditDesc = true;
check_r();
NextToken();
if (check_w() && check_d()) {
check_e();
}
break;
case TokenKind::G:
// R1307 data-edit-desc -> G w [. d [E e]]
hasDataEditDesc = true;
check_r();
NextToken();
if (check_w()) {
if (wValue_ > 0) {
if (check_d()) { // C1307
check_e();
}
} else if (token_.kind() == TokenKind::Point && check_d() &&
token_.kind() == TokenKind::E) {
ReportError("Unexpected 'e' in 'G0' edit descriptor"); // C1308
NextToken();
if (token_.kind() == TokenKind::UnsignedInteger) {
NextToken();
}
}
}
break;
case TokenKind::L:
// R1307 data-edit-desc -> L w
hasDataEditDesc = true;
check_r();
NextToken();
check_w();
break;
case TokenKind::DT:
// R1307 data-edit-desc -> DT [char-literal-constant] [( v-list )]
hasDataEditDesc = true;
check_r();
NextToken();
if (token_.kind() == TokenKind::String) {
NextToken();
}
if (token_.kind() == TokenKind::LParen) {
do {
NextToken();
if (token_.kind() == TokenKind::Sign) {
NextToken();
}
if (token_.kind() != TokenKind::UnsignedInteger) {
ReportError(
"Expected integer constant in 'DT' edit descriptor v-list");
break;
}
NextToken();
} while (token_.kind() == TokenKind::Comma);
if (token_.kind() != TokenKind::RParen) {
ReportError("Expected ',' or ')' in 'DT' edit descriptor v-list");
while (cursor_ < end_ && token_.kind() != TokenKind::RParen) {
NextToken();
}
}
NextToken();
}
break;
case TokenKind::String:
// R1304 data-edit-desc -> char-string-edit-desc
if (knrValue_ >= 0) {
ReportError("Repeat specifier before character string edit descriptor",
knrToken_);
}
NextToken();
break;
case TokenKind::BN:
case TokenKind::BZ:
case TokenKind::DC:
case TokenKind::DP:
case TokenKind::RC:
case TokenKind::RD:
case TokenKind::RN:
case TokenKind::RP:
case TokenKind::RU:
case TokenKind::RZ:
case TokenKind::S:
case TokenKind::SP:
case TokenKind::SS:
// R1317 sign-edit-desc -> SS | SP | S
// R1318 blank-interp-edit-desc -> BN | BZ
// R1319 round-edit-desc -> RU | RD | RZ | RN | RC | RP
// R1320 decimal-edit-desc -> DC | DP
check_r(false);
NextToken();
break;
case TokenKind::P: {
// R1313 control-edit-desc -> k P
if (knrValue_ < 0) {
ReportError("'P' edit descriptor must have a scale factor");
}
// Diagnosing C1302 may require multiple token lookahead.
// Save current cursor position to enable backup.
const CHAR *saveCursor{cursor_};
NextToken();
if (token_.kind() == TokenKind::UnsignedInteger) {
NextToken();
}
switch (token_.kind()) {
case TokenKind::D:
case TokenKind::E:
case TokenKind::EN:
case TokenKind::ES:
case TokenKind::EX:
case TokenKind::F:
case TokenKind::G:
commaRequired = false;
break;
default:;
}
cursor_ = saveCursor;
NextToken();
break;
}
case TokenKind::T:
case TokenKind::TL:
case TokenKind::TR:
// R1315 position-edit-desc -> T n | TL n | TR n
check_r(false);
NextToken();
if (integerValue_ <= 0) { // C1311
ReportError("'%s' edit descriptor must have a positive position value");
}
NextToken();
break;
case TokenKind::X:
// R1315 position-edit-desc -> n X
if (knrValue_ == 0) { // C1311
ReportError("'X' edit descriptor must have a positive position value",
knrToken_);
} else if (knrValue_ < 0) {
ReportWarning(
"'X' edit descriptor must have a positive position value");
}
NextToken();
break;
case TokenKind::Colon:
// R1313 control-edit-desc -> :
check_r(false);
commaRequired = false;
NextToken();
break;
case TokenKind::Slash:
// R1313 control-edit-desc -> [r] /
commaRequired = false;
NextToken();
break;
case TokenKind::Backslash:
check_r(false);
ReportWarning("Non-standard '\\' edit descriptor");
NextToken();
break;
case TokenKind::Dollar:
check_r(false);
ReportWarning("Non-standard '$' edit descriptor");
NextToken();
break;
case TokenKind::Star:
// NextToken assigns a token kind of Star only if * is followed by (.
// So the next token is guaranteed to be LParen.
if (nestLevel > 0) {
ReportError("Nested unlimited format item list");
}
starToken = token_;
if (knrValue_ >= 0) {
ReportError(
"Repeat specifier before unlimited format item list", knrToken_);
}
hasDataEditDesc = false;
NextToken();
[[fallthrough]];
case TokenKind::LParen:
if (knrValue_ == 0) {
ReportError("List repeat specifier must be positive", knrToken_);
}
if (++nestLevel > maxNesting_) {
maxNesting_ = nestLevel;
}
break;
case TokenKind::RParen:
if (knrValue_ >= 0) {
ReportError("Unexpected integer constant", knrToken_);
}
do {
if (nestLevel == 0) {
// Any characters after level-0 ) are ignored.
return formatHasErrors_; // normal exit (may have messages)
}
if (nestLevel == 1 && starToken.IsSet() && !hasDataEditDesc) {
SetLength(starToken);
ReportError( // C1303
"Unlimited format item list must contain a data edit descriptor",
starToken);
}
--nestLevel;
NextToken();
} while (token_.kind() == TokenKind::RParen);
if (nestLevel == 0 && starToken.IsSet()) {
ReportError("Character in format after unlimited format item list");
}
break;
case TokenKind::Comma:
if (knrValue_ >= 0) {
ReportError("Unexpected integer constant", knrToken_);
}
if (suppressMessageCascade_ || reporterExit_) {
break;
}
[[fallthrough]];
default:
ReportError("Unexpected '%s' in format expression");
NextToken();
}
// Process comma separator and exit an incomplete format.
switch (token_.kind()) {
case TokenKind::Colon: // Comma not required; token not yet processed.
case TokenKind::Slash: // Comma not required; token not yet processed.
case TokenKind::RParen: // Comma not allowed; token not yet processed.
suppressMessageCascade_ = false;
break;
case TokenKind::LParen: // Comma not allowed; token already processed.
case TokenKind::Comma: // Normal comma case; move past token.
suppressMessageCascade_ = false;
NextToken();
break;
case TokenKind::Sign: // Error; main switch has a better message.
case TokenKind::None: // Error; token not yet processed.
if (cursor_ >= end_) {
return formatHasErrors_; // incomplete format error exit
}
break;
default:
// Possible first token of the next format item; token not yet processed.
if (commaRequired) {
const char *s{"Expected ',' or ')' in format expression"}; // C1302
if (previousTokenWasInt_ && itemsWithLeadingInts_.test(token_.kind())) {
ReportError(s);
} else {
ReportWarning(s);
}
}
}
}
return formatHasErrors_; // error reporter (message threshold) exit
}
} // namespace Fortran::common
#endif // FORTRAN_COMMON_FORMAT_H_

View File

@ -0,0 +1,166 @@
//===-- include/flang/Common/idioms.h ---------------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_COMMON_IDIOMS_H_
#define FORTRAN_COMMON_IDIOMS_H_
// Defines anything that might ever be useful in more than one source file
// or that is too weird or too specific to the host C++ compiler to be
// exposed elsewhere.
#ifndef __cplusplus
#error this is a C++ program
#endif
#if __cplusplus < 201703L
#error this is a C++17 program
#endif
#if !__clang__ && defined __GNUC__ && __GNUC__ < 7
#error g++ >= 7.2 is required
#endif
#include <functional>
#include <list>
#include <optional>
#include <string>
#include <tuple>
#include <type_traits>
#include <variant>
#if __GNUC__ == 7
// Avoid a deduction bug in GNU 7.x headers by forcing the answer.
namespace std {
template <typename A>
struct is_trivially_copy_constructible<list<A>> : false_type {};
template <typename A>
struct is_trivially_copy_constructible<optional<list<A>>> : false_type {};
} // namespace std
#endif
// enable "this is a std::string"s with the 's' suffix
using namespace std::literals::string_literals;
namespace Fortran::common {
// Helper templates for combining a list of lambdas into an anonymous
// struct for use with std::visit() on a std::variant<> sum type.
// E.g.: std::visit(visitors{
// [&](const firstType &x) { ... },
// [&](const secondType &x) { ... },
// ...
// [&](const auto &catchAll) { ... }}, variantObject);
template <typename... LAMBDAS> struct visitors : LAMBDAS... {
using LAMBDAS::operator()...;
};
template <typename... LAMBDAS> visitors(LAMBDAS... x) -> visitors<LAMBDAS...>;
// Calls std::fprintf(stderr, ...), then abort().
[[noreturn]] void die(const char *, ...);
#define DIE(x) Fortran::common::die(x " at " __FILE__ "(%d)", __LINE__)
// For switch statement default: labels.
#define CRASH_NO_CASE DIE("no case")
// clang-format off
// For switch statements whose cases have return statements for
// all possibilities. Clang emits warnings if the default: is
// present, gcc emits warnings if it is absent.
#if __clang__
#define SWITCH_COVERS_ALL_CASES
#else
#define SWITCH_COVERS_ALL_CASES default: CRASH_NO_CASE;
#endif
// clang-format on
// For cheap assertions that should be applied in production.
// To disable, compile with '-DCHECK=(void)'
#ifndef CHECK
#define CHECK(x) ((x) || (DIE("CHECK(" #x ") failed"), false))
#endif
// User-defined type traits that default to false:
// Invoke CLASS_TRAIT(traitName) to define a trait, then put
// using traitName = std::true_type; (or false_type)
// into the appropriate class definitions. You can then use
// typename std::enable_if_t<traitName<...>, ...>
// in template specialization definitions.
#define CLASS_TRAIT(T) \
namespace class_trait_ns_##T { \
template <typename A> std::true_type test(typename A::T *); \
template <typename A> std::false_type test(...); \
template <typename A> \
constexpr bool has_trait{decltype(test<A>(nullptr))::value}; \
template <typename A> constexpr bool trait_value() { \
if constexpr (has_trait<A>) { \
using U = typename A::T; \
return U::value; \
} else { \
return false; \
} \
} \
} \
template <typename A> constexpr bool T{class_trait_ns_##T::trait_value<A>()};
#if !defined ATTRIBUTE_UNUSED && (__clang__ || __GNUC__)
#define ATTRIBUTE_UNUSED __attribute__((unused))
#endif
// Define enum class NAME with the given enumerators, a static
// function EnumToString() that maps enumerators to std::string,
// and a constant NAME_enumSize that captures the number of items
// in the enum class.
std::string EnumIndexToString(int index, const char *names);
template <typename A> struct ListItemCount {
constexpr ListItemCount(std::initializer_list<A> list) : value{list.size()} {}
const std::size_t value;
};
#define ENUM_CLASS(NAME, ...) \
enum class NAME { __VA_ARGS__ }; \
ATTRIBUTE_UNUSED static constexpr std::size_t NAME##_enumSize{[] { \
enum { __VA_ARGS__ }; \
return Fortran::common::ListItemCount{__VA_ARGS__}.value; \
}()}; \
ATTRIBUTE_UNUSED static inline std::string EnumToString(NAME e) { \
return Fortran::common::EnumIndexToString( \
static_cast<int>(e), #__VA_ARGS__); \
}
// Check that a pointer is non-null and dereference it
#define DEREF(p) Fortran::common::Deref(p, __FILE__, __LINE__)
template <typename T> constexpr T &Deref(T *p, const char *file, int line) {
if (!p) {
Fortran::common::die("nullptr dereference at %s(%d)", file, line);
}
return *p;
}
// Given a const reference to a value, return a copy of the value.
template <typename A> A Clone(const A &x) { return x; }
// C++ does a weird and dangerous thing when deducing template type parameters
// from function arguments: lvalue references are allowed to match rvalue
// reference arguments. Template function declarations like
// template<typename A> int foo(A &&);
// need to be protected against this C++ language feature when functions
// may modify such arguments. Use these type functions to invoke SFINAE
// on a result type via
// template<typename A> common::IfNoLvalue<int, A> foo(A &&);
// or, for constructors,
// template<typename A, typename = common::NoLvalue<A>> int foo(A &&);
// This works with parameter packs too.
template <typename A, typename... B>
using IfNoLvalue = std::enable_if_t<(... && !std::is_lvalue_reference_v<B>), A>;
template <typename... RVREF> using NoLvalue = IfNoLvalue<void, RVREF...>;
} // namespace Fortran::common
#endif // FORTRAN_COMMON_IDIOMS_H_

View File

@ -0,0 +1,141 @@
//===-- include/flang/Common/indirection.h ----------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_COMMON_INDIRECTION_H_
#define FORTRAN_COMMON_INDIRECTION_H_
// Define a smart pointer class template that is rather like
// non-nullable std::unique_ptr<>. Indirection<> is, like a C++ reference
// type, restricted to be non-null when constructed or assigned.
// Indirection<> optionally supports copy construction and copy assignment.
//
// To use Indirection<> with forward-referenced types, add
// extern template class Fortran::common::Indirection<FORWARD_TYPE>;
// outside any namespace in a header before use, and
// template class Fortran::common::Indirection<FORWARD_TYPE>;
// in one C++ source file later where a definition of the type is visible.
#include "idioms.h"
#include <memory>
#include <type_traits>
#include <utility>
namespace Fortran::common {
// The default case does not support (deep) copy construction or assignment.
template <typename A, bool COPY = false> class Indirection {
public:
using element_type = A;
Indirection() = delete;
Indirection(A *&&p) : p_{p} {
CHECK(p_ && "assigning null pointer to Indirection");
p = nullptr;
}
Indirection(A &&x) : p_{new A(std::move(x))} {}
Indirection(Indirection &&that) : p_{that.p_} {
CHECK(p_ && "move construction of Indirection from null Indirection");
that.p_ = nullptr;
}
~Indirection() {
delete p_;
p_ = nullptr;
}
Indirection &operator=(Indirection &&that) {
CHECK(that.p_ && "move assignment of null Indirection to Indirection");
auto tmp{p_};
p_ = that.p_;
that.p_ = tmp;
return *this;
}
A &value() { return *p_; }
const A &value() const { return *p_; }
bool operator==(const A &that) const { return *p_ == that; }
bool operator==(const Indirection &that) const { return *p_ == *that.p_; }
template <typename... ARGS>
static common::IfNoLvalue<Indirection, ARGS...> Make(ARGS &&... args) {
return {new A(std::move(args)...)};
}
private:
A *p_{nullptr};
};
// Variant with copy construction and assignment
template <typename A> class Indirection<A, true> {
public:
using element_type = A;
Indirection() = delete;
Indirection(A *&&p) : p_{p} {
CHECK(p_ && "assigning null pointer to Indirection");
p = nullptr;
}
Indirection(const A &x) : p_{new A(x)} {}
Indirection(A &&x) : p_{new A(std::move(x))} {}
Indirection(const Indirection &that) {
CHECK(that.p_ && "copy construction of Indirection from null Indirection");
p_ = new A(*that.p_);
}
Indirection(Indirection &&that) : p_{that.p_} {
CHECK(p_ && "move construction of Indirection from null Indirection");
that.p_ = nullptr;
}
~Indirection() {
delete p_;
p_ = nullptr;
}
Indirection &operator=(const Indirection &that) {
CHECK(that.p_ && "copy assignment of Indirection from null Indirection");
*p_ = *that.p_;
return *this;
}
Indirection &operator=(Indirection &&that) {
CHECK(that.p_ && "move assignment of null Indirection to Indirection");
auto tmp{p_};
p_ = that.p_;
that.p_ = tmp;
return *this;
}
A &value() { return *p_; }
const A &value() const { return *p_; }
bool operator==(const A &that) const { return *p_ == that; }
bool operator==(const Indirection &that) const { return *p_ == *that.p_; }
template <typename... ARGS>
static common::IfNoLvalue<Indirection, ARGS...> Make(ARGS &&... args) {
return {new A(std::move(args)...)};
}
private:
A *p_{nullptr};
};
template <typename A> using CopyableIndirection = Indirection<A, true>;
// For use with std::unique_ptr<> when declaring owning pointers to
// forward-referenced types, here's a minimal custom deleter that avoids
// some of the drama with std::default_delete<>. Invoke DEFINE_DELETER()
// later in exactly one C++ source file where a complete definition of the
// type is visible. Be advised, std::unique_ptr<> does not have copy
// semantics; if you need ownership, copy semantics, and nullability,
// std::optional<CopyableIndirection<>> works.
template <typename A> class Deleter {
public:
void operator()(A *) const;
};
} // namespace Fortran::common
#define DEFINE_DELETER(A) \
template <> void Fortran::common::Deleter<A>::operator()(A *p) const { \
delete p; \
}
#endif // FORTRAN_COMMON_INDIRECTION_H_

View File

@ -0,0 +1,115 @@
//===-- include/flang/Common/interval.h -------------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_COMMON_INTERVAL_H_
#define FORTRAN_COMMON_INTERVAL_H_
// Defines a generalized template class Interval<A> to represent
// the half-open interval [x .. x+n).
#include "idioms.h"
#include <algorithm>
#include <cstddef>
#include <utility>
namespace Fortran::common {
template <typename A> class Interval {
public:
using type = A;
constexpr Interval() {}
constexpr Interval(const A &s, std::size_t n = 1) : start_{s}, size_{n} {}
constexpr Interval(A &&s, std::size_t n = 1)
: start_{std::move(s)}, size_{n} {}
constexpr Interval(const Interval &) = default;
constexpr Interval(Interval &&) = default;
constexpr Interval &operator=(const Interval &) = default;
constexpr Interval &operator=(Interval &&) = default;
constexpr bool operator==(const Interval &that) const {
return start_ == that.start_ && size_ == that.size_;
}
constexpr bool operator!=(const Interval &that) const {
return !(*this == that);
}
constexpr const A &start() const { return start_; }
constexpr std::size_t size() const { return size_; }
constexpr bool empty() const { return size_ == 0; }
constexpr bool Contains(const A &x) const {
return start_ <= x && x < start_ + size_;
}
constexpr bool Contains(const Interval &that) const {
return Contains(that.start_) && Contains(that.start_ + (that.size_ - 1));
}
constexpr bool IsDisjointWith(const Interval &that) const {
return that.NextAfter() <= start_ || NextAfter() <= that.start_;
}
constexpr bool ImmediatelyPrecedes(const Interval &that) const {
return NextAfter() == that.start_;
}
void Annex(const Interval &that) {
size_ = (that.start_ + that.size_) - start_;
}
bool AnnexIfPredecessor(const Interval &that) {
if (ImmediatelyPrecedes(that)) {
size_ += that.size_;
return true;
}
return false;
}
void ExtendToCover(const Interval &that) {
if (size_ == 0) {
*this = that;
} else if (that.size_ != 0) {
const auto end{std::max(NextAfter(), that.NextAfter())};
start_ = std::min(start_, that.start_);
size_ = end - start_;
}
}
std::size_t MemberOffset(const A &x) const {
CHECK(Contains(x));
return x - start_;
}
A OffsetMember(std::size_t n) const {
CHECK(n < size_);
return start_ + n;
}
constexpr A Last() const { return start_ + (size_ - 1); }
constexpr A NextAfter() const { return start_ + size_; }
constexpr Interval Prefix(std::size_t n) const {
return {start_, std::min(size_, n)};
}
Interval Suffix(std::size_t n) const {
CHECK(n <= size_);
return {start_ + n, size_ - n};
}
constexpr Interval Intersection(const Interval &that) const {
if (that.NextAfter() <= start_) {
return {};
} else if (that.start_ <= start_) {
auto skip{start_ - that.start_};
return {start_, std::min(size_, that.size_ - skip)};
} else if (NextAfter() <= that.start_) {
return {};
} else {
auto skip{that.start_ - start_};
return {that.start_, std::min(that.size_, size_ - skip)};
}
}
private:
A start_;
std::size_t size_{0};
};
} // namespace Fortran::common
#endif // FORTRAN_COMMON_INTERVAL_H_

View File

@ -0,0 +1,96 @@
//===-- include/flang/Common/leading-zero-bit-count.h -----------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_COMMON_LEADING_ZERO_BIT_COUNT_H_
#define FORTRAN_COMMON_LEADING_ZERO_BIT_COUNT_H_
// A fast and portable function that implements Fortran's LEADZ intrinsic
// function, which counts the number of leading (most significant) zero bit
// positions in an integer value. (If the most significant bit is set, the
// leading zero count is zero; if no bit is set, the leading zero count is the
// word size in bits; otherwise, it's the largest left shift count that
// doesn't reduce the number of bits in the word that are set.)
#include <cinttypes>
namespace Fortran::common {
namespace {
// The following magic constant is a binary deBruijn sequence.
// It has the remarkable property that if one extends it
// (virtually) on the right with 5 more zero bits, then all
// of the 64 contiguous framed blocks of six bits in the
// extended 69-bit sequence are distinct. Consequently,
// if one shifts it left by any shift count [0..63] with
// truncation and extracts the uppermost six bit field
// of the shifted value, each shift count maps to a distinct
// field value. That means that we can map those 64 field
// values back to the shift counts that produce them,
// and (the point) this means that we can shift this value
// by an unknown bit count in [0..63] and then figure out
// what that count must have been.
// 0 7 e d d 5 e 5 9 a 4 e 2 8 c 2
// 0000011111101101110101011110010110011010010011100010100011000010
static constexpr std::uint64_t deBruijn{0x07edd5e59a4e28c2};
static constexpr std::uint8_t mapping[64]{63, 0, 58, 1, 59, 47, 53, 2, 60, 39,
48, 27, 54, 33, 42, 3, 61, 51, 37, 40, 49, 18, 28, 20, 55, 30, 34, 11, 43,
14, 22, 4, 62, 57, 46, 52, 38, 26, 32, 41, 50, 36, 17, 19, 29, 10, 13, 21,
56, 45, 25, 31, 35, 16, 9, 12, 44, 24, 15, 8, 23, 7, 6, 5};
} // namespace
inline constexpr int LeadingZeroBitCount(std::uint64_t x) {
if (x == 0) {
return 64;
} else {
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
x |= x >> 8;
x |= x >> 16;
x |= x >> 32;
// All of the bits below the uppermost set bit are now also set.
x -= x >> 1; // All of the bits below the uppermost are now clear.
// x now has exactly one bit set, so it is a power of two, so
// multiplication by x is equivalent to a left shift by its
// base-2 logarithm. We calculate that unknown base-2 logarithm
// by shifting the deBruijn sequence and mapping the framed value.
int base2Log{mapping[(x * deBruijn) >> 58]};
return 63 - base2Log; // convert to leading zero count
}
}
inline constexpr int LeadingZeroBitCount(std::uint32_t x) {
return LeadingZeroBitCount(static_cast<std::uint64_t>(x)) - 32;
}
inline constexpr int LeadingZeroBitCount(std::uint16_t x) {
return LeadingZeroBitCount(static_cast<std::uint64_t>(x)) - 48;
}
namespace {
static constexpr std::uint8_t eightBitLeadingZeroBitCount[256]{8, 7, 6, 6, 5, 5,
5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
}
inline constexpr int LeadingZeroBitCount(std::uint8_t x) {
return eightBitLeadingZeroBitCount[x];
}
template <typename A> inline constexpr int BitsNeededFor(A x) {
return 8 * sizeof x - LeadingZeroBitCount(x);
}
} // namespace Fortran::common
#endif // FORTRAN_COMMON_LEADING_ZERO_BIT_COUNT_H_

View File

@ -0,0 +1,102 @@
//===-- include/flang/Common/real.h -----------------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_COMMON_REAL_H_
#define FORTRAN_COMMON_REAL_H_
// Characteristics of IEEE-754 & related binary floating-point numbers.
// The various representations are distinguished by their binary precisions
// (number of explicit significand bits and any implicit MSB in the fraction).
#include <cinttypes>
namespace Fortran::common {
// Total representation size in bits for each type
static constexpr int BitsForBinaryPrecision(int binaryPrecision) {
switch (binaryPrecision) {
case 8:
return 16; // IEEE single (truncated): 1+8+7
case 11:
return 16; // IEEE half precision: 1+5+10
case 24:
return 32; // IEEE single precision: 1+8+23
case 53:
return 64; // IEEE double precision: 1+11+52
case 64:
return 80; // x87 extended precision: 1+15+64
case 106:
return 128; // "double-double": 2*(1+11+52)
case 113:
return 128; // IEEE quad precision: 1+15+112
default:
return -1;
}
}
// Number of significant decimal digits in the fraction of the
// exact conversion of the least nonzero (subnormal) value
// in each type; i.e., a 128-bit quad value can be formatted
// exactly with FORMAT(E0.22981).
static constexpr int MaxDecimalConversionDigits(int binaryPrecision) {
switch (binaryPrecision) {
case 8:
return 93;
case 11:
return 17;
case 24:
return 105;
case 53:
return 751;
case 64:
return 11495;
case 106:
return 2 * 751;
case 113:
return 11530;
default:
return -1;
}
}
template <int BINARY_PRECISION> class RealDetails {
private:
// Converts bit widths to whole decimal digits
static constexpr int LogBaseTwoToLogBaseTen(int logb2) {
constexpr std::int64_t LogBaseTenOfTwoTimesTenToThe12th{301029995664};
constexpr std::int64_t TenToThe12th{1000000000000};
std::int64_t logb10{
(logb2 * LogBaseTenOfTwoTimesTenToThe12th) / TenToThe12th};
return static_cast<int>(logb10);
}
public:
static constexpr int binaryPrecision{BINARY_PRECISION};
static constexpr int bits{BitsForBinaryPrecision(binaryPrecision)};
static constexpr bool isImplicitMSB{binaryPrecision != 64 /*x87*/};
static constexpr int significandBits{binaryPrecision - isImplicitMSB};
static constexpr int exponentBits{bits - significandBits - 1 /*sign*/};
static constexpr int maxExponent{(1 << exponentBits) - 1};
static constexpr int exponentBias{maxExponent / 2};
static constexpr int decimalPrecision{
LogBaseTwoToLogBaseTen(binaryPrecision - 1)};
static constexpr int decimalRange{LogBaseTwoToLogBaseTen(exponentBias - 1)};
// Number of significant decimal digits in the fraction of the
// exact conversion of the least nonzero subnormal.
static constexpr int maxDecimalConversionDigits{
MaxDecimalConversionDigits(binaryPrecision)};
static_assert(binaryPrecision > 0);
static_assert(exponentBits > 1);
static_assert(exponentBits <= 15);
};
} // namespace Fortran::common
#endif // FORTRAN_COMMON_REAL_H_

View File

@ -0,0 +1,76 @@
//===-- include/flang/Common/reference-counted.h ----------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_COMMON_REFERENCE_COUNTED_H_
#define FORTRAN_COMMON_REFERENCE_COUNTED_H_
// A class template of smart pointers to objects with their own
// reference counting object lifetimes that's lighter weight
// than std::shared_ptr<>. Not thread-safe.
namespace Fortran::common {
// A base class for reference-counted objects. Must be public.
template <typename A> class ReferenceCounted {
public:
ReferenceCounted() {}
void TakeReference() { ++references_; }
void DropReference() {
if (--references_ == 0) {
delete static_cast<A *>(this);
}
}
private:
int references_{0};
};
// A reference to a reference-counted object.
template <typename A> class CountedReference {
public:
using type = A;
CountedReference() {}
CountedReference(type *m) : p_{m} { Take(); }
CountedReference(const CountedReference &c) : p_{c.p_} { Take(); }
CountedReference(CountedReference &&c) : p_{c.p_} { c.p_ = nullptr; }
CountedReference &operator=(const CountedReference &c) {
c.Take();
Drop();
p_ = c.p_;
return *this;
}
CountedReference &operator=(CountedReference &&c) {
A *p{c.p_};
c.p_ = nullptr;
Drop();
p_ = p;
return *this;
}
~CountedReference() { Drop(); }
operator bool() const { return p_ != nullptr; }
type *get() const { return p_; }
type &operator*() const { return *p_; }
type *operator->() const { return p_; }
private:
void Take() const {
if (p_) {
p_->TakeReference();
}
}
void Drop() {
if (p_) {
p_->DropReference();
p_ = nullptr;
}
}
type *p_{nullptr};
};
} // namespace Fortran::common
#endif // FORTRAN_COMMON_REFERENCE_COUNTED_H_

View File

@ -0,0 +1,63 @@
//===-- include/flang/Common/reference.h ------------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
// Implements a better std::reference_wrapper<> template class with
// move semantics, equality testing, and member access.
// Use Reference<A> in place of a real A& reference when assignability is
// required; safer than a bare pointer because it's guaranteed to not be null.
#ifndef FORTRAN_COMMON_REFERENCE_H_
#define FORTRAN_COMMON_REFERENCE_H_
#include <type_traits>
namespace Fortran::common {
template <typename A> class Reference {
public:
using type = A;
Reference(type &x) : p_{&x} {}
Reference(const Reference &that) : p_{that.p_} {}
Reference(Reference &&that) : p_{that.p_} {}
Reference &operator=(const Reference &that) {
p_ = that.p_;
return *this;
}
Reference &operator=(Reference &&that) {
p_ = that.p_;
return *this;
}
// Implicit conversions to references are supported only for
// const-qualified types in order to avoid any pernicious
// creation of a temporary copy in cases like:
// Reference<type> ref;
// const Type &x{ref}; // creates ref to temp copy!
operator std::conditional_t<std::is_const_v<type>, type &, void>()
const noexcept {
if constexpr (std::is_const_v<type>) {
return *p_;
}
}
type &get() const noexcept { return *p_; }
type *operator->() const { return p_; }
type &operator*() const { return *p_; }
bool operator==(std::add_const_t<A> &that) const {
return p_ == &that || *p_ == that;
}
bool operator!=(std::add_const_t<A> &that) const { return !(*this == that); }
bool operator==(const Reference &that) const {
return p_ == that.p_ || *this == *that.p_;
}
bool operator!=(const Reference &that) const { return !(*this == that); }
private:
type *p_; // never null
};
template <typename A> Reference(A &) -> Reference<A>;
} // namespace Fortran::common
#endif

View File

@ -0,0 +1,46 @@
//===-- include/flang/Common/restorer.h -------------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
// Utility: before overwriting a variable, capture its value and
// ensure that it will be restored when the Restorer goes out of scope.
//
// int x{3};
// {
// auto save{common::ScopedSet(x, 4)};
// // x is now 4
// }
// // x is back to 3
#ifndef FORTRAN_COMMON_RESTORER_H_
#define FORTRAN_COMMON_RESTORER_H_
#include "idioms.h"
namespace Fortran::common {
template <typename A> class Restorer {
public:
explicit Restorer(A &p) : p_{p}, original_{std::move(p)} {}
~Restorer() { p_ = std::move(original_); }
private:
A &p_;
A original_;
};
template <typename A, typename B>
common::IfNoLvalue<Restorer<A>, B> ScopedSet(A &to, B &&from) {
Restorer<A> result{to};
to = std::move(from);
return result;
}
template <typename A, typename B>
common::IfNoLvalue<Restorer<A>, B> ScopedSet(A &to, const B &from) {
Restorer<A> result{to};
to = from;
return result;
}
} // namespace Fortran::common
#endif // FORTRAN_COMMON_RESTORER_H_

View File

@ -0,0 +1,323 @@
//===-- include/flang/Common/template.h -------------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_COMMON_TEMPLATE_H_
#define FORTRAN_COMMON_TEMPLATE_H_
#include "flang/Common/idioms.h"
#include <functional>
#include <optional>
#include <tuple>
#include <type_traits>
#include <variant>
#include <vector>
// Utility templates for metaprogramming and for composing the
// std::optional<>, std::tuple<>, and std::variant<> containers.
namespace Fortran::common {
// SearchTypeList<PREDICATE, TYPES...> scans a list of types. The zero-based
// index of the first type T in the list for which PREDICATE<T>::value() is
// true is returned, or -1 if the predicate is false for every type in the list.
// This is a compile-time operation; see SearchTypes below for a run-time form.
template <int N, template <typename> class PREDICATE, typename TUPLE>
struct SearchTypeListHelper {
static constexpr int value() {
if constexpr (N >= std::tuple_size_v<TUPLE>) {
return -1;
} else if constexpr (PREDICATE<std::tuple_element_t<N, TUPLE>>::value()) {
return N;
} else {
return SearchTypeListHelper<N + 1, PREDICATE, TUPLE>::value();
}
}
};
template <template <typename> class PREDICATE, typename... TYPES>
constexpr int SearchTypeList{
SearchTypeListHelper<0, PREDICATE, std::tuple<TYPES...>>::value()};
// TypeIndex<A, TYPES...> scans a list of types for simple type equality.
// The zero-based index of A in the list is returned, or -1 if A is not present.
template <typename A> struct MatchType {
template <typename B> struct Match {
static constexpr bool value() {
return std::is_same_v<std::decay_t<A>, std::decay_t<B>>;
}
};
};
template <typename A, typename... TYPES>
constexpr int TypeIndex{SearchTypeList<MatchType<A>::template Match, TYPES...>};
// IsTypeInList<A, TYPES...> is a simple presence predicate.
template <typename A, typename... TYPES>
constexpr bool IsTypeInList{TypeIndex<A, TYPES...> >= 0};
// OverMembers extracts the list of types that constitute the alternatives
// of a std::variant or elements of a std::tuple and passes that list as
// parameter types to a given variadic template.
template <template <typename...> class, typename> struct OverMembersHelper;
template <template <typename...> class T, typename... Ts>
struct OverMembersHelper<T, std::variant<Ts...>> {
using type = T<Ts...>;
};
template <template <typename...> class T, typename... Ts>
struct OverMembersHelper<T, std::tuple<Ts...>> {
using type = T<Ts...>;
};
template <template <typename...> class T, typename TUPLEorVARIANT>
using OverMembers =
typename OverMembersHelper<T, std::decay_t<TUPLEorVARIANT>>::type;
// SearchMembers<PREDICATE> scans the types that constitute the alternatives
// of a std::variant instantiation or elements of a std::tuple.
// The zero-based index of the first type T among the alternatives for which
// PREDICATE<T>::value() is true is returned, or -1 when the predicate is false
// for every type in the set.
template <template <typename> class PREDICATE> struct SearchMembersHelper {
template <typename... Ts> struct Scanner {
static constexpr int value() { return SearchTypeList<PREDICATE, Ts...>; }
};
};
template <template <typename> class PREDICATE, typename TUPLEorVARIANT>
constexpr int SearchMembers{
OverMembers<SearchMembersHelper<PREDICATE>::template Scanner,
TUPLEorVARIANT>::value()};
template <typename A, typename TUPLEorVARIANT>
constexpr bool HasMember{
SearchMembers<MatchType<A>::template Match, TUPLEorVARIANT> >= 0};
// std::optional<std::optional<A>> -> std::optional<A>
template <typename A>
std::optional<A> JoinOptional(std::optional<std::optional<A>> &&x) {
if (x) {
return std::move(*x);
}
return std::nullopt;
}
// Convert an std::optional to an ordinary pointer
template <typename A> const A *GetPtrFromOptional(const std::optional<A> &x) {
if (x) {
return &*x;
} else {
return nullptr;
}
}
// Copy a value from one variant type to another. The types allowed in the
// source variant must all be allowed in the destination variant type.
template <typename TOV, typename FROMV> TOV CopyVariant(const FROMV &u) {
return std::visit([](const auto &x) -> TOV { return {x}; }, u);
}
// Move a value from one variant type to another. The types allowed in the
// source variant must all be allowed in the destination variant type.
template <typename TOV, typename FROMV>
common::IfNoLvalue<TOV, FROMV> MoveVariant(FROMV &&u) {
return std::visit(
[](auto &&x) -> TOV { return {std::move(x)}; }, std::move(u));
}
// CombineTuples takes a list of std::tuple<> template instantiation types
// and constructs a new std::tuple type that concatenates all of their member
// types. E.g.,
// CombineTuples<std::tuple<char, int>, std::tuple<float, double>>
// is std::tuple<char, int, float, double>.
template <typename... TUPLES> struct CombineTuplesHelper {
static decltype(auto) f(TUPLES *... a) {
return std::tuple_cat(std::move(*a)...);
}
using type = decltype(f(static_cast<TUPLES *>(nullptr)...));
};
template <typename... TUPLES>
using CombineTuples = typename CombineTuplesHelper<TUPLES...>::type;
// CombineVariants takes a list of std::variant<> instantiations and constructs
// a new instantiation that holds all of their alternatives, which must be
// pairwise distinct.
template <typename> struct VariantToTupleHelper;
template <typename... Ts> struct VariantToTupleHelper<std::variant<Ts...>> {
using type = std::tuple<Ts...>;
};
template <typename VARIANT>
using VariantToTuple = typename VariantToTupleHelper<VARIANT>::type;
template <typename A, typename... REST> struct AreTypesDistinctHelper {
static constexpr bool value() {
if constexpr (sizeof...(REST) > 0) {
// extra () for clang-format
return ((... && !std::is_same_v<A, REST>)) &&
AreTypesDistinctHelper<REST...>::value();
}
return true;
}
};
template <typename... Ts>
constexpr bool AreTypesDistinct{AreTypesDistinctHelper<Ts...>::value()};
template <typename A, typename... Ts> struct AreSameTypeHelper {
using type = A;
static constexpr bool value() {
if constexpr (sizeof...(Ts) == 0) {
return true;
} else {
using Rest = AreSameTypeHelper<Ts...>;
return std::is_same_v<type, typename Rest::type> && Rest::value();
}
}
};
template <typename... Ts>
constexpr bool AreSameType{AreSameTypeHelper<Ts...>::value()};
template <typename> struct TupleToVariantHelper;
template <typename... Ts> struct TupleToVariantHelper<std::tuple<Ts...>> {
static_assert(AreTypesDistinct<Ts...>,
"TupleToVariant: types are not pairwise distinct");
using type = std::variant<Ts...>;
};
template <typename TUPLE>
using TupleToVariant = typename TupleToVariantHelper<TUPLE>::type;
template <typename... VARIANTS> struct CombineVariantsHelper {
using type = TupleToVariant<CombineTuples<VariantToTuple<VARIANTS>...>>;
};
template <typename... VARIANTS>
using CombineVariants = typename CombineVariantsHelper<VARIANTS...>::type;
// SquashVariantOfVariants: given a std::variant whose alternatives are
// all std::variant instantiations, form a new union over their alternatives.
template <typename VARIANT>
using SquashVariantOfVariants = OverMembers<CombineVariants, VARIANT>;
// Given a type function, MapTemplate applies it to each of the types
// in a tuple or variant, and collect the results in a given variadic
// template (typically a std::variant).
template <template <typename> class, template <typename...> class, typename...>
struct MapTemplateHelper;
template <template <typename> class F, template <typename...> class PACKAGE,
typename... Ts>
struct MapTemplateHelper<F, PACKAGE, std::tuple<Ts...>> {
using type = PACKAGE<F<Ts>...>;
};
template <template <typename> class F, template <typename...> class PACKAGE,
typename... Ts>
struct MapTemplateHelper<F, PACKAGE, std::variant<Ts...>> {
using type = PACKAGE<F<Ts>...>;
};
template <template <typename> class F, typename TUPLEorVARIANT,
template <typename...> class PACKAGE = std::variant>
using MapTemplate =
typename MapTemplateHelper<F, PACKAGE, TUPLEorVARIANT>::type;
// std::tuple<std::optional<>...> -> std::optional<std::tuple<...>>
// i.e., inverts a tuple of optional values into an optional tuple that has
// a value only if all of the original elements were present.
template <typename... A, std::size_t... J>
std::optional<std::tuple<A...>> AllElementsPresentHelper(
std::tuple<std::optional<A>...> &&t, std::index_sequence<J...>) {
bool present[]{std::get<J>(t).has_value()...};
for (std::size_t j{0}; j < sizeof...(J); ++j) {
if (!present[j]) {
return std::nullopt;
}
}
return {std::make_tuple(*std::get<J>(t)...)};
}
template <typename... A>
std::optional<std::tuple<A...>> AllElementsPresent(
std::tuple<std::optional<A>...> &&t) {
return AllElementsPresentHelper(
std::move(t), std::index_sequence_for<A...>{});
}
// std::vector<std::optional<A>> -> std::optional<std::vector<A>>
// i.e., inverts a vector of optional values into an optional vector that
// will have a value only when all of the original elements are present.
template <typename A>
std::optional<std::vector<A>> AllElementsPresent(
std::vector<std::optional<A>> &&v) {
for (const auto &maybeA : v) {
if (!maybeA) {
return std::nullopt;
}
}
std::vector<A> result;
for (auto &&maybeA : std::move(v)) {
result.emplace_back(std::move(*maybeA));
}
return result;
}
// (std::optional<>...) -> std::optional<std::tuple<...>>
// i.e., given some number of optional values, return a optional tuple of
// those values that is present only of all of the values were so.
template <typename... A>
std::optional<std::tuple<A...>> AllPresent(std::optional<A> &&... x) {
return AllElementsPresent(std::make_tuple(std::move(x)...));
}
// (f(A...) -> R) -> std::optional<A>... -> std::optional<R>
// Apply a function to optional arguments if all are present.
// N.B. If the function returns std::optional, MapOptional will return
// std::optional<std::optional<...>> and you will probably want to
// run it through JoinOptional to "squash" it.
template <typename R, typename... A>
std::optional<R> MapOptional(
std::function<R(A &&...)> &&f, std::optional<A> &&... x) {
if (auto args{AllPresent(std::move(x)...)}) {
return std::make_optional(std::apply(std::move(f), std::move(*args)));
}
return std::nullopt;
}
template <typename R, typename... A>
std::optional<R> MapOptional(R (*f)(A &&...), std::optional<A> &&... x) {
return MapOptional(std::function<R(A && ...)>{f}, std::move(x)...);
}
// Given a VISITOR class of the general form
// struct VISITOR {
// using Result = ...;
// using Types = std::tuple<...>;
// template<typename T> Result Test() { ... }
// };
// SearchTypes will traverse the element types in the tuple in order
// and invoke VISITOR::Test<T>() on each until it returns a value that
// casts to true. If no invocation of Test succeeds, SearchTypes will
// return a default value.
template <std::size_t J, typename VISITOR>
common::IfNoLvalue<typename VISITOR::Result, VISITOR> SearchTypesHelper(
VISITOR &&visitor, typename VISITOR::Result &&defaultResult) {
using Tuple = typename VISITOR::Types;
if constexpr (J < std::tuple_size_v<Tuple>) {
if (auto result{visitor.template Test<std::tuple_element_t<J, Tuple>>()}) {
return result;
}
return SearchTypesHelper<J + 1, VISITOR>(
std::move(visitor), std::move(defaultResult));
} else {
return std::move(defaultResult);
}
}
template <typename VISITOR>
common::IfNoLvalue<typename VISITOR::Result, VISITOR> SearchTypes(
VISITOR &&visitor,
typename VISITOR::Result defaultResult = typename VISITOR::Result{}) {
return SearchTypesHelper<0, VISITOR>(
std::move(visitor), std::move(defaultResult));
}
} // namespace Fortran::common
#endif // FORTRAN_COMMON_TEMPLATE_H_

View File

@ -0,0 +1,274 @@
//===-- include/flang/Common/uint128.h --------------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
// Portable 128-bit unsigned integer arithmetic for use in impoverished
// C++ implementations lacking __uint128_t.
#ifndef FORTRAN_COMMON_UINT128_H_
#define FORTRAN_COMMON_UINT128_H_
#ifndef AVOID_NATIVE_UINT128_T
#define AVOID_NATIVE_UINT128_T 1 // always use this code for now for testing
#endif
#include "leading-zero-bit-count.h"
#include <cstdint>
#include <type_traits>
namespace Fortran::common {
class UnsignedInt128 {
public:
constexpr UnsignedInt128() {}
// This means of definition provides some portability for
// "size_t" operands.
constexpr UnsignedInt128(unsigned n) : low_{n} {}
constexpr UnsignedInt128(unsigned long n) : low_{n} {}
constexpr UnsignedInt128(unsigned long long n) : low_{n} {}
constexpr UnsignedInt128(int n)
: low_{static_cast<std::uint64_t>(n)}, high_{-static_cast<std::uint64_t>(
n < 0)} {}
constexpr UnsignedInt128(long n)
: low_{static_cast<std::uint64_t>(n)}, high_{-static_cast<std::uint64_t>(
n < 0)} {}
constexpr UnsignedInt128(long long n)
: low_{static_cast<std::uint64_t>(n)}, high_{-static_cast<std::uint64_t>(
n < 0)} {}
constexpr UnsignedInt128(const UnsignedInt128 &) = default;
constexpr UnsignedInt128(UnsignedInt128 &&) = default;
constexpr UnsignedInt128 &operator=(const UnsignedInt128 &) = default;
constexpr UnsignedInt128 &operator=(UnsignedInt128 &&) = default;
constexpr UnsignedInt128 operator+() const { return *this; }
constexpr UnsignedInt128 operator~() const { return {~high_, ~low_}; }
constexpr UnsignedInt128 operator-() const { return ~*this + 1; }
constexpr bool operator!() const { return !low_ && !high_; }
constexpr explicit operator bool() const { return low_ || high_; }
constexpr explicit operator std::uint64_t() const { return low_; }
constexpr explicit operator int() const { return static_cast<int>(low_); }
constexpr std::uint64_t high() const { return high_; }
constexpr std::uint64_t low() const { return low_; }
constexpr UnsignedInt128 operator++(/*prefix*/) {
*this += 1;
return *this;
}
constexpr UnsignedInt128 operator++(int /*postfix*/) {
UnsignedInt128 result{*this};
*this += 1;
return result;
}
constexpr UnsignedInt128 operator--(/*prefix*/) {
*this -= 1;
return *this;
}
constexpr UnsignedInt128 operator--(int /*postfix*/) {
UnsignedInt128 result{*this};
*this -= 1;
return result;
}
constexpr UnsignedInt128 operator&(UnsignedInt128 that) const {
return {high_ & that.high_, low_ & that.low_};
}
constexpr UnsignedInt128 operator|(UnsignedInt128 that) const {
return {high_ | that.high_, low_ | that.low_};
}
constexpr UnsignedInt128 operator^(UnsignedInt128 that) const {
return {high_ ^ that.high_, low_ ^ that.low_};
}
constexpr UnsignedInt128 operator<<(UnsignedInt128 that) const {
if (that >= 128) {
return {};
} else if (that == 0) {
return *this;
} else {
std::uint64_t n{that.low_};
if (n >= 64) {
return {low_ << (n - 64), 0};
} else {
return {(high_ << n) | (low_ >> (64 - n)), low_ << n};
}
}
}
constexpr UnsignedInt128 operator>>(UnsignedInt128 that) const {
if (that >= 128) {
return {};
} else if (that == 0) {
return *this;
} else {
std::uint64_t n{that.low_};
if (n >= 64) {
return {0, high_ >> (n - 64)};
} else {
return {high_ >> n, (high_ << (64 - n)) | (low_ >> n)};
}
}
}
constexpr UnsignedInt128 operator+(UnsignedInt128 that) const {
std::uint64_t lower{(low_ & ~topBit) + (that.low_ & ~topBit)};
bool carry{((lower >> 63) + (low_ >> 63) + (that.low_ >> 63)) > 1};
return {high_ + that.high_ + carry, low_ + that.low_};
}
constexpr UnsignedInt128 operator-(UnsignedInt128 that) const {
return *this + -that;
}
constexpr UnsignedInt128 operator*(UnsignedInt128 that) const {
std::uint64_t mask32{0xffffffff};
if (high_ == 0 && that.high_ == 0) {
std::uint64_t x0{low_ & mask32}, x1{low_ >> 32};
std::uint64_t y0{that.low_ & mask32}, y1{that.low_ >> 32};
UnsignedInt128 x0y0{x0 * y0}, x0y1{x0 * y1};
UnsignedInt128 x1y0{x1 * y0}, x1y1{x1 * y1};
return x0y0 + ((x0y1 + x1y0) << 32) + (x1y1 << 64);
} else {
std::uint64_t x0{low_ & mask32}, x1{low_ >> 32}, x2{high_ & mask32},
x3{high_ >> 32};
std::uint64_t y0{that.low_ & mask32}, y1{that.low_ >> 32},
y2{that.high_ & mask32}, y3{that.high_ >> 32};
UnsignedInt128 x0y0{x0 * y0}, x0y1{x0 * y1}, x0y2{x0 * y2}, x0y3{x0 * y3};
UnsignedInt128 x1y0{x1 * y0}, x1y1{x1 * y1}, x1y2{x1 * y2};
UnsignedInt128 x2y0{x2 * y0}, x2y1{x2 * y1};
UnsignedInt128 x3y0{x3 * y0};
return x0y0 + ((x0y1 + x1y0) << 32) + ((x0y2 + x1y1 + x2y0) << 64) +
((x0y3 + x1y2 + x2y1 + x3y0) << 96);
}
}
constexpr UnsignedInt128 operator/(UnsignedInt128 that) const {
int j{LeadingZeroes()};
UnsignedInt128 bits{*this};
bits <<= j;
UnsignedInt128 numerator{};
UnsignedInt128 quotient{};
for (; j < 128; ++j) {
numerator <<= 1;
if (bits.high_ & topBit) {
numerator.low_ |= 1;
}
bits <<= 1;
quotient <<= 1;
if (numerator >= that) {
++quotient;
numerator -= that;
}
}
return quotient;
}
constexpr UnsignedInt128 operator%(UnsignedInt128 that) const {
int j{LeadingZeroes()};
UnsignedInt128 bits{*this};
bits <<= j;
UnsignedInt128 remainder{};
for (; j < 128; ++j) {
remainder <<= 1;
if (bits.high_ & topBit) {
remainder.low_ |= 1;
}
bits <<= 1;
if (remainder >= that) {
remainder -= that;
}
}
return remainder;
}
constexpr bool operator<(UnsignedInt128 that) const {
return high_ < that.high_ || (high_ == that.high_ && low_ < that.low_);
}
constexpr bool operator<=(UnsignedInt128 that) const {
return !(*this > that);
}
constexpr bool operator==(UnsignedInt128 that) const {
return low_ == that.low_ && high_ == that.high_;
}
constexpr bool operator!=(UnsignedInt128 that) const {
return !(*this == that);
}
constexpr bool operator>=(UnsignedInt128 that) const { return that <= *this; }
constexpr bool operator>(UnsignedInt128 that) const { return that < *this; }
constexpr UnsignedInt128 &operator&=(const UnsignedInt128 &that) {
*this = *this & that;
return *this;
}
constexpr UnsignedInt128 &operator|=(const UnsignedInt128 &that) {
*this = *this | that;
return *this;
}
constexpr UnsignedInt128 &operator^=(const UnsignedInt128 &that) {
*this = *this ^ that;
return *this;
}
constexpr UnsignedInt128 &operator<<=(const UnsignedInt128 &that) {
*this = *this << that;
return *this;
}
constexpr UnsignedInt128 &operator>>=(const UnsignedInt128 &that) {
*this = *this >> that;
return *this;
}
constexpr UnsignedInt128 &operator+=(const UnsignedInt128 &that) {
*this = *this + that;
return *this;
}
constexpr UnsignedInt128 &operator-=(const UnsignedInt128 &that) {
*this = *this - that;
return *this;
}
constexpr UnsignedInt128 &operator*=(const UnsignedInt128 &that) {
*this = *this * that;
return *this;
}
constexpr UnsignedInt128 &operator/=(const UnsignedInt128 &that) {
*this = *this / that;
return *this;
}
constexpr UnsignedInt128 &operator%=(const UnsignedInt128 &that) {
*this = *this % that;
return *this;
}
private:
constexpr UnsignedInt128(std::uint64_t hi, std::uint64_t lo)
: low_{lo}, high_{hi} {}
constexpr int LeadingZeroes() const {
if (high_ == 0) {
return 64 + LeadingZeroBitCount(low_);
} else {
return LeadingZeroBitCount(high_);
}
}
static constexpr std::uint64_t topBit{std::uint64_t{1} << 63};
std::uint64_t low_{0}, high_{0};
};
#if AVOID_NATIVE_UINT128_T
using uint128_t = UnsignedInt128;
#elif (defined __GNUC__ || defined __clang__) && defined __SIZEOF_INT128__
using uint128_t = __uint128_t;
#else
using uint128_t = UnsignedInt128;
#endif
template <int BITS> struct HostUnsignedIntTypeHelper {
using type = std::conditional_t<(BITS <= 8), std::uint8_t,
std::conditional_t<(BITS <= 16), std::uint16_t,
std::conditional_t<(BITS <= 32), std::uint32_t,
std::conditional_t<(BITS <= 64), std::uint64_t, uint128_t>>>>;
};
template <int BITS>
using HostUnsignedIntType = typename HostUnsignedIntTypeHelper<BITS>::type;
} // namespace Fortran::common
#endif

View File

@ -0,0 +1,77 @@
//===-- include/flang/Common/unsigned-const-division.h ----------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_COMMON_UNSIGNED_CONST_DIVISION_H_
#define FORTRAN_COMMON_UNSIGNED_CONST_DIVISION_H_
// Work around unoptimized implementations of unsigned integer division
// by constant values in some compilers (looking at YOU, clang 7!) by
// explicitly implementing integer division by constant divisors as
// multiplication by a fixed-point reciprocal and a right shift.
#include "bit-population-count.h"
#include "leading-zero-bit-count.h"
#include "uint128.h"
#include <cinttypes>
#include <type_traits>
namespace Fortran::common {
template <typename UINT> class FixedPointReciprocal {
public:
using type = UINT;
private:
static_assert(std::is_unsigned_v<type>);
static const int bits{static_cast<int>(8 * sizeof(type))};
static_assert(bits <= 64);
using Big = HostUnsignedIntType<bits * 2>;
public:
static constexpr FixedPointReciprocal For(type n) {
if (n == 0) {
return {0, 0};
} else if ((n & (n - 1)) == 0) { // n is a power of two
return {TrailingZeroBitCount(n), 1};
} else {
int shift{bits - 1 + BitsNeededFor(n)};
return {shift, static_cast<type>(((Big{1} << shift) + n - 1) / n)};
}
}
constexpr type Divide(type n) const {
return static_cast<type>((static_cast<Big>(reciprocal_) * n) >> shift_);
}
private:
constexpr FixedPointReciprocal(int s, type r) : shift_{s}, reciprocal_{r} {}
int shift_;
type reciprocal_;
};
static_assert(FixedPointReciprocal<std::uint32_t>::For(5).Divide(2000000000u) ==
400000000u);
static_assert(FixedPointReciprocal<std::uint64_t>::For(10).Divide(
10000000000000000u) == 1000000000000000u);
template <typename UINT, std::uint64_t DENOM>
inline constexpr UINT DivideUnsignedBy(UINT n) {
if constexpr (std::is_same_v<UINT, uint128_t>) {
return n / static_cast<UINT>(DENOM);
} else {
// G++ can recognize that the reciprocal is a compile-time
// constant when For() is called inline, but clang requires
// a constexpr variable definition to force compile-time
// evaluation of the reciprocal.
constexpr auto recip{FixedPointReciprocal<UINT>::For(DENOM)};
return recip.Divide(n);
}
}
} // namespace Fortran::common
#endif

View File

@ -0,0 +1,157 @@
//===-- include/flang/Common/unwrap.h ---------------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_COMMON_UNWRAP_H_
#define FORTRAN_COMMON_UNWRAP_H_
#include "indirection.h"
#include "reference-counted.h"
#include "reference.h"
#include <memory>
#include <optional>
#include <type_traits>
#include <variant>
// Given a nest of variants, optionals, &/or pointers, Unwrap<>() isolates
// a packaged value of a specific type if it is present and returns a pointer
// thereto; otherwise, it returns a null pointer. It's analogous to
// std::get_if<>() but it accepts a reference argument and is recursive.
// The target type parameter cannot be omitted.
//
// Be advised: If the target type parameter is not const-qualified, but the
// isolated value is const-qualified, the result of Unwrap<> will be a
// pointer to a const-qualified value.
//
// Further: const-qualified alternatives in instances of non-const-qualified
// variants will not be returned from Unwrap if the target type is not
// const-qualified.
//
// UnwrapCopy<>() is a variation of Unwrap<>() that returns an optional copy
// of the value if one is present with the desired type.
namespace Fortran::common {
// Utility: Produces "const A" if B is const and A is not already so.
template <typename A, typename B>
using Constify = std::conditional_t<std::is_const_v<B> && !std::is_const_v<A>,
std::add_const_t<A>, A>;
// Unwrap's mutually-recursive template functions are packaged in a struct
// to avoid a need for prototypes.
struct UnwrapperHelper {
// Base case
template <typename A, typename B>
static auto Unwrap(B &x) -> Constify<A, B> * {
if constexpr (std::is_same_v<std::decay_t<A>, std::decay_t<B>>) {
return &x;
} else {
return nullptr;
}
}
// Implementations of specializations
template <typename A, typename B>
static auto Unwrap(B *p) -> Constify<A, B> * {
if (p) {
return Unwrap<A>(*p);
} else {
return nullptr;
}
}
template <typename A, typename B>
static auto Unwrap(const std::unique_ptr<B> &p) -> Constify<A, B> * {
if (p.get()) {
return Unwrap<A>(*p);
} else {
return nullptr;
}
}
template <typename A, typename B>
static auto Unwrap(const std::shared_ptr<B> &p) -> Constify<A, B> * {
if (p.get()) {
return Unwrap<A>(*p);
} else {
return nullptr;
}
}
template <typename A, typename B>
static auto Unwrap(std::optional<B> &x) -> Constify<A, B> * {
if (x) {
return Unwrap<A>(*x);
} else {
return nullptr;
}
}
template <typename A, typename B>
static auto Unwrap(const std::optional<B> &x) -> Constify<A, B> * {
if (x) {
return Unwrap<A>(*x);
} else {
return nullptr;
}
}
template <typename A, typename... Bs>
static A *Unwrap(std::variant<Bs...> &u) {
return std::visit(
[](auto &x) -> A * {
using Ty = std::decay_t<decltype(Unwrap<A>(x))>;
if constexpr (!std::is_const_v<std::remove_pointer_t<Ty>> ||
std::is_const_v<A>) {
return Unwrap<A>(x);
}
return nullptr;
},
u);
}
template <typename A, typename... Bs>
static auto Unwrap(const std::variant<Bs...> &u) -> std::add_const_t<A> * {
return std::visit(
[](const auto &x) -> std::add_const_t<A> * { return Unwrap<A>(x); }, u);
}
template <typename A, typename B>
static auto Unwrap(const Reference<B> &ref) -> Constify<A, B> * {
return Unwrap<A>(*ref);
}
template <typename A, typename B, bool COPY>
static auto Unwrap(const Indirection<B, COPY> &p) -> Constify<A, B> * {
return Unwrap<A>(*p);
}
template <typename A, typename B>
static auto Unwrap(const CountedReference<B> &p) -> Constify<A, B> * {
if (p.get()) {
return Unwrap<A>(*p);
} else {
return nullptr;
}
}
};
template <typename A, typename B> auto Unwrap(B &x) -> Constify<A, B> * {
return UnwrapperHelper::Unwrap<A>(x);
}
// Returns a copy of a wrapped value, if present, otherwise a vacant optional.
template <typename A, typename B> std::optional<A> UnwrapCopy(const B &x) {
if (const A * p{Unwrap<A>(x)}) {
return std::make_optional<A>(*p);
} else {
return std::nullopt;
}
}
} // namespace Fortran::common
#endif // FORTRAN_COMMON_UNWRAP_H_

View File

@ -0,0 +1,21 @@
#===-- include/flang/Config/config.h.cmake ---------------------------------===#
#
# 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
#
#===------------------------------------------------------------------------===#
/* This generated file is for internal use. Do not include it from headers. */
#ifdef FLANG_CONFIG_H
#error config.h can only be included once
#else
#define FLANG_CONFIG_H
#define FLANG_VERSION "${FLANG_VERSION}"
#endif

View File

@ -0,0 +1,96 @@
//===-- include/flang/Decimal/binary-floating-point.h -----------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_DECIMAL_BINARY_FLOATING_POINT_H_
#define FORTRAN_DECIMAL_BINARY_FLOATING_POINT_H_
// Access and manipulate the fields of an IEEE-754 binary
// floating-point value via a generalized template.
#include "flang/Common/real.h"
#include "flang/Common/uint128.h"
#include <cinttypes>
#include <climits>
#include <cstring>
#include <type_traits>
namespace Fortran::decimal {
template <int BINARY_PRECISION>
struct BinaryFloatingPointNumber
: public common::RealDetails<BINARY_PRECISION> {
using Details = common::RealDetails<BINARY_PRECISION>;
using Details::bits;
using Details::decimalPrecision;
using Details::decimalRange;
using Details::exponentBias;
using Details::exponentBits;
using Details::isImplicitMSB;
using Details::maxDecimalConversionDigits;
using Details::maxExponent;
using Details::significandBits;
using RawType = common::HostUnsignedIntType<bits>;
static_assert(CHAR_BIT * sizeof(RawType) >= bits);
static constexpr RawType significandMask{(RawType{1} << significandBits) - 1};
constexpr BinaryFloatingPointNumber() {} // zero
constexpr BinaryFloatingPointNumber(
const BinaryFloatingPointNumber &that) = default;
constexpr BinaryFloatingPointNumber(
BinaryFloatingPointNumber &&that) = default;
constexpr BinaryFloatingPointNumber &operator=(
const BinaryFloatingPointNumber &that) = default;
constexpr BinaryFloatingPointNumber &operator=(
BinaryFloatingPointNumber &&that) = default;
template <typename A> explicit constexpr BinaryFloatingPointNumber(A x) {
static_assert(sizeof raw <= sizeof x);
std::memcpy(reinterpret_cast<void *>(&raw),
reinterpret_cast<const void *>(&x), sizeof raw);
}
constexpr int BiasedExponent() const {
return static_cast<int>(
(raw >> significandBits) & ((1 << exponentBits) - 1));
}
constexpr int UnbiasedExponent() const {
int biased{BiasedExponent()};
return biased - exponentBias + (biased == 0);
}
constexpr RawType Significand() const { return raw & significandMask; }
constexpr RawType Fraction() const {
RawType sig{Significand()};
if (isImplicitMSB && BiasedExponent() > 0) {
sig |= RawType{1} << significandBits;
}
return sig;
}
constexpr bool IsZero() const {
return (raw & ((RawType{1} << (bits - 1)) - 1)) == 0;
}
constexpr bool IsNaN() const {
return BiasedExponent() == maxExponent && Significand() != 0;
}
constexpr bool IsInfinite() const {
return BiasedExponent() == maxExponent && Significand() == 0;
}
constexpr bool IsMaximalFiniteMagnitude() const {
return BiasedExponent() == maxExponent - 1 &&
Significand() == significandMask;
}
constexpr bool IsNegative() const { return ((raw >> (bits - 1)) & 1) != 0; }
constexpr void Negate() { raw ^= RawType{1} << (bits - 1); }
RawType raw{0};
};
} // namespace Fortran::decimal
#endif

View File

@ -0,0 +1,151 @@
/*===-- include/flang/Decimal/decimal.h ---------------------------*- C++ -*-===
*
* 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
*
* ===-----------------------------------------------------------------------===
*/
/* C and C++ API for binary-to/from-decimal conversion package. */
#ifndef FORTRAN_DECIMAL_DECIMAL_H_
#define FORTRAN_DECIMAL_DECIMAL_H_
#include <stddef.h>
#ifdef __cplusplus
// Binary-to-decimal conversions (formatting) produce a sequence of decimal
// digit characters in a NUL-terminated user-supplied buffer that constitute
// a decimal fraction (or zero), accompanied by a decimal exponent that
// you'll get to adjust and format yourself. There can be a leading sign
// character.
// Negative zero is "-0". The result can also be "NaN", "Inf", "+Inf",
// or "-Inf".
// If the conversion can't fit in the user-supplied buffer, a null pointer
// is returned.
#include "binary-floating-point.h"
namespace Fortran::decimal {
#endif /* C++ */
enum ConversionResultFlags {
Exact = 0,
Overflow = 1,
Inexact = 2,
Invalid = 4,
};
struct ConversionToDecimalResult {
const char *str; /* may not be original buffer pointer; null if overflow */
size_t length; /* does not include NUL terminator */
int decimalExponent; /* assuming decimal point to the left of first digit */
enum ConversionResultFlags flags;
};
enum FortranRounding {
RoundNearest, /* RN */
RoundUp, /* RU */
RoundDown, /* RD */
RoundToZero, /* RZ - no rounding */
RoundCompatible, /* RC: like RN, but ties go away from 0 */
RoundDefault, /* RP: maps to one of the above */
};
/* The "minimize" flag causes the fewest number of output digits
* to be emitted such that reading them back into the same binary
* floating-point format with RoundNearest will return the same
* value.
*/
enum DecimalConversionFlags {
Minimize = 1, /* Minimize # of digits */
AlwaysSign = 2, /* emit leading '+' if not negative */
};
/*
* When allocating decimal conversion output buffers, use the maximum
* number of significant decimal digits in the representation of the
* least nonzero value, and add this extra space for a sign, a NUL, and
* some extra due to the library working internally in base 10**16
* and computing its output size in multiples of 16.
*/
#define EXTRA_DECIMAL_CONVERSION_SPACE (1 + 1 + 16 - 1)
#ifdef __cplusplus
template <int PREC>
ConversionToDecimalResult ConvertToDecimal(char *, size_t,
DecimalConversionFlags, int digits, enum FortranRounding rounding,
BinaryFloatingPointNumber<PREC> x);
extern template ConversionToDecimalResult ConvertToDecimal<8>(char *, size_t,
enum DecimalConversionFlags, int, enum FortranRounding,
BinaryFloatingPointNumber<8>);
extern template ConversionToDecimalResult ConvertToDecimal<11>(char *, size_t,
enum DecimalConversionFlags, int, enum FortranRounding,
BinaryFloatingPointNumber<11>);
extern template ConversionToDecimalResult ConvertToDecimal<24>(char *, size_t,
enum DecimalConversionFlags, int, enum FortranRounding,
BinaryFloatingPointNumber<24>);
extern template ConversionToDecimalResult ConvertToDecimal<53>(char *, size_t,
enum DecimalConversionFlags, int, enum FortranRounding,
BinaryFloatingPointNumber<53>);
extern template ConversionToDecimalResult ConvertToDecimal<64>(char *, size_t,
enum DecimalConversionFlags, int, enum FortranRounding,
BinaryFloatingPointNumber<64>);
extern template ConversionToDecimalResult ConvertToDecimal<113>(char *, size_t,
enum DecimalConversionFlags, int, enum FortranRounding,
BinaryFloatingPointNumber<113>);
template <int PREC> struct ConversionToBinaryResult {
BinaryFloatingPointNumber<PREC> binary;
enum ConversionResultFlags flags { Exact };
};
template <int PREC>
ConversionToBinaryResult<PREC> ConvertToBinary(
const char *&, enum FortranRounding = RoundNearest);
extern template ConversionToBinaryResult<8> ConvertToBinary<8>(
const char *&, enum FortranRounding = RoundNearest);
extern template ConversionToBinaryResult<11> ConvertToBinary<11>(
const char *&, enum FortranRounding = RoundNearest);
extern template ConversionToBinaryResult<24> ConvertToBinary<24>(
const char *&, enum FortranRounding = RoundNearest);
extern template ConversionToBinaryResult<53> ConvertToBinary<53>(
const char *&, enum FortranRounding = RoundNearest);
extern template ConversionToBinaryResult<64> ConvertToBinary<64>(
const char *&, enum FortranRounding = RoundNearest);
extern template ConversionToBinaryResult<113> ConvertToBinary<113>(
const char *&, enum FortranRounding = RoundNearest);
} // namespace Fortran::decimal
extern "C" {
#define NS(x) Fortran::decimal::x
#else /* C++ */
#define NS(x) x
#endif /* C++ */
struct NS(ConversionToDecimalResult)
ConvertFloatToDecimal(char *, size_t, enum NS(DecimalConversionFlags),
int digits, enum NS(FortranRounding), float);
struct NS(ConversionToDecimalResult)
ConvertDoubleToDecimal(char *, size_t, enum NS(DecimalConversionFlags),
int digits, enum NS(FortranRounding), double);
#if __x86_64__ && !defined(_MSC_VER)
struct NS(ConversionToDecimalResult)
ConvertLongDoubleToDecimal(char *, size_t, enum NS(DecimalConversionFlags),
int digits, enum NS(FortranRounding), long double);
#endif
enum NS(ConversionResultFlags)
ConvertDecimalToFloat(const char **, float *, enum NS(FortranRounding));
enum NS(ConversionResultFlags)
ConvertDecimalToDouble(const char **, double *, enum NS(FortranRounding));
#if __x86_64__ && !defined(_MSC_VER)
enum NS(ConversionResultFlags) ConvertDecimalToLongDouble(
const char **, long double *, enum NS(FortranRounding));
#endif
#undef NS
#ifdef __cplusplus
} // extern "C"
#endif
#endif

View File

@ -0,0 +1,227 @@
//===-- include/flang/Evaluate/call.h ---------------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_EVALUATE_CALL_H_
#define FORTRAN_EVALUATE_CALL_H_
#include "common.h"
#include "constant.h"
#include "formatting.h"
#include "type.h"
#include "flang/Common/indirection.h"
#include "flang/Common/reference.h"
#include "flang/Parser/char-block.h"
#include "flang/Semantics/attr.h"
#include <optional>
#include <vector>
namespace llvm {
class raw_ostream;
}
namespace Fortran::semantics {
class Symbol;
}
// Mutually referential data structures are represented here with forward
// declarations of hitherto undefined class types and a level of indirection.
namespace Fortran::evaluate {
class Component;
class IntrinsicProcTable;
} // namespace Fortran::evaluate
namespace Fortran::evaluate::characteristics {
struct DummyArgument;
struct Procedure;
} // namespace Fortran::evaluate::characteristics
extern template class Fortran::common::Indirection<Fortran::evaluate::Component,
true>;
extern template class Fortran::common::Indirection<
Fortran::evaluate::characteristics::Procedure, true>;
namespace Fortran::evaluate {
using semantics::Symbol;
using SymbolRef = common::Reference<const Symbol>;
class ActualArgument {
public:
// Dummy arguments that are TYPE(*) can be forwarded as actual arguments.
// Since that's the only thing one may do with them in Fortran, they're
// represented in expressions as a special case of an actual argument.
class AssumedType {
public:
explicit AssumedType(const Symbol &);
DEFAULT_CONSTRUCTORS_AND_ASSIGNMENTS(AssumedType)
const Symbol &symbol() const { return symbol_; }
int Rank() const;
bool operator==(const AssumedType &that) const {
return &*symbol_ == &*that.symbol_;
}
llvm::raw_ostream &AsFortran(llvm::raw_ostream &) const;
private:
SymbolRef symbol_;
};
DECLARE_CONSTRUCTORS_AND_ASSIGNMENTS(ActualArgument)
explicit ActualArgument(Expr<SomeType> &&);
explicit ActualArgument(common::CopyableIndirection<Expr<SomeType>> &&);
explicit ActualArgument(AssumedType);
~ActualArgument();
ActualArgument &operator=(Expr<SomeType> &&);
Expr<SomeType> *UnwrapExpr() {
if (auto *p{
std::get_if<common::CopyableIndirection<Expr<SomeType>>>(&u_)}) {
return &p->value();
} else {
return nullptr;
}
}
const Expr<SomeType> *UnwrapExpr() const {
if (const auto *p{
std::get_if<common::CopyableIndirection<Expr<SomeType>>>(&u_)}) {
return &p->value();
} else {
return nullptr;
}
}
const Symbol *GetAssumedTypeDummy() const {
if (const AssumedType * aType{std::get_if<AssumedType>(&u_)}) {
return &aType->symbol();
} else {
return nullptr;
}
}
std::optional<DynamicType> GetType() const;
int Rank() const;
bool operator==(const ActualArgument &) const;
llvm::raw_ostream &AsFortran(llvm::raw_ostream &) const;
std::optional<parser::CharBlock> keyword() const { return keyword_; }
void set_keyword(parser::CharBlock x) { keyword_ = x; }
bool isAlternateReturn() const { return isAlternateReturn_; }
void set_isAlternateReturn() { isAlternateReturn_ = true; }
bool isPassedObject() const { return isPassedObject_; }
void set_isPassedObject(bool yes = true) { isPassedObject_ = yes; }
bool Matches(const characteristics::DummyArgument &) const;
common::Intent dummyIntent() const { return dummyIntent_; }
ActualArgument &set_dummyIntent(common::Intent intent) {
dummyIntent_ = intent;
return *this;
}
// Wrap this argument in parentheses
void Parenthesize();
// TODO: Mark legacy %VAL and %REF arguments
private:
// Subtlety: There is a distinction that must be maintained here between an
// actual argument expression that is a variable and one that is not,
// e.g. between X and (X). The parser attempts to parse each argument
// first as a variable, then as an expression, and the distinction appears
// in the parse tree.
std::variant<common::CopyableIndirection<Expr<SomeType>>, AssumedType> u_;
std::optional<parser::CharBlock> keyword_;
bool isAlternateReturn_{false}; // whether expr is a "*label" number
bool isPassedObject_{false};
common::Intent dummyIntent_{common::Intent::Default};
};
using ActualArguments = std::vector<std::optional<ActualArgument>>;
// Intrinsics are identified by their names and the characteristics
// of their arguments, at least for now.
using IntrinsicProcedure = std::string;
struct SpecificIntrinsic {
SpecificIntrinsic(IntrinsicProcedure, characteristics::Procedure &&);
DECLARE_CONSTRUCTORS_AND_ASSIGNMENTS(SpecificIntrinsic)
~SpecificIntrinsic();
bool operator==(const SpecificIntrinsic &) const;
llvm::raw_ostream &AsFortran(llvm::raw_ostream &) const;
IntrinsicProcedure name;
bool isRestrictedSpecific{false}; // if true, can only call it, not pass it
common::CopyableIndirection<characteristics::Procedure> characteristics;
};
struct ProcedureDesignator {
EVALUATE_UNION_CLASS_BOILERPLATE(ProcedureDesignator)
explicit ProcedureDesignator(SpecificIntrinsic &&i) : u{std::move(i)} {}
explicit ProcedureDesignator(const Symbol &n) : u{n} {}
explicit ProcedureDesignator(Component &&);
// Exactly one of these will return a non-null pointer.
const SpecificIntrinsic *GetSpecificIntrinsic() const;
const Symbol *GetSymbol() const; // symbol or component symbol
// For references to NOPASS components and bindings only.
// References to PASS components and bindings are represented
// with the symbol below and the base object DataRef in the
// passed-object ActualArgument.
// Always null when the procedure is intrinsic.
const Component *GetComponent() const;
const Symbol *GetInterfaceSymbol() const;
std::string GetName() const;
std::optional<DynamicType> GetType() const;
int Rank() const;
bool IsElemental() const;
std::optional<Expr<SubscriptInteger>> LEN() const;
llvm::raw_ostream &AsFortran(llvm::raw_ostream &) const;
std::variant<SpecificIntrinsic, SymbolRef,
common::CopyableIndirection<Component>>
u;
};
class ProcedureRef {
public:
CLASS_BOILERPLATE(ProcedureRef)
ProcedureRef(ProcedureDesignator &&p, ActualArguments &&a)
: proc_{std::move(p)}, arguments_(std::move(a)) {}
~ProcedureRef();
ProcedureDesignator &proc() { return proc_; }
const ProcedureDesignator &proc() const { return proc_; }
ActualArguments &arguments() { return arguments_; }
const ActualArguments &arguments() const { return arguments_; }
std::optional<Expr<SubscriptInteger>> LEN() const;
int Rank() const;
bool IsElemental() const { return proc_.IsElemental(); }
bool operator==(const ProcedureRef &) const;
llvm::raw_ostream &AsFortran(llvm::raw_ostream &) const;
protected:
ProcedureDesignator proc_;
ActualArguments arguments_;
};
template <typename A> class FunctionRef : public ProcedureRef {
public:
using Result = A;
CLASS_BOILERPLATE(FunctionRef)
explicit FunctionRef(ProcedureRef &&pr) : ProcedureRef{std::move(pr)} {}
FunctionRef(ProcedureDesignator &&p, ActualArguments &&a)
: ProcedureRef{std::move(p), std::move(a)} {}
std::optional<DynamicType> GetType() const { return proc_.GetType(); }
std::optional<Constant<Result>> Fold(FoldingContext &); // for intrinsics
};
FOR_EACH_SPECIFIC_TYPE(extern template class FunctionRef, )
} // namespace Fortran::evaluate
#endif // FORTRAN_EVALUATE_CALL_H_

View File

@ -0,0 +1,305 @@
//===-- include/flang/Evaluate/characteristics.h ----------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
// Defines data structures to represent "characteristics" of Fortran
// procedures and other entities as they are specified in section 15.3
// of Fortran 2018.
#ifndef FORTRAN_EVALUATE_CHARACTERISTICS_H_
#define FORTRAN_EVALUATE_CHARACTERISTICS_H_
#include "common.h"
#include "expression.h"
#include "shape.h"
#include "type.h"
#include "flang/Common/Fortran.h"
#include "flang/Common/enum-set.h"
#include "flang/Common/idioms.h"
#include "flang/Common/indirection.h"
#include "flang/Parser/char-block.h"
#include "flang/Semantics/symbol.h"
#include <optional>
#include <string>
#include <variant>
#include <vector>
namespace llvm {
class raw_ostream;
}
namespace Fortran::evaluate {
class IntrinsicProcTable;
}
namespace Fortran::evaluate::characteristics {
struct Procedure;
}
extern template class Fortran::common::Indirection<
Fortran::evaluate::characteristics::Procedure, true>;
namespace Fortran::evaluate::characteristics {
using common::CopyableIndirection;
// Are these procedures distinguishable for a generic name?
bool Distinguishable(const Procedure &, const Procedure &);
// Are these procedures distinguishable for a generic operator or assignment?
bool DistinguishableOpOrAssign(const Procedure &, const Procedure &);
// Shapes of function results and dummy arguments have to have
// the same rank, the same deferred dimensions, and the same
// values for explicit dimensions when constant.
bool ShapesAreCompatible(const Shape &, const Shape &);
class TypeAndShape {
public:
ENUM_CLASS(
Attr, AssumedRank, AssumedShape, AssumedSize, DeferredShape, Coarray)
using Attrs = common::EnumSet<Attr, Attr_enumSize>;
explicit TypeAndShape(DynamicType t) : type_{t} { AcquireLEN(); }
TypeAndShape(DynamicType t, int rank) : type_{t}, shape_(rank) {
AcquireLEN();
}
TypeAndShape(DynamicType t, Shape &&s) : type_{t}, shape_{std::move(s)} {
AcquireLEN();
}
TypeAndShape(DynamicType t, std::optional<Shape> &&s) : type_{t} {
if (s) {
shape_ = std::move(*s);
}
AcquireLEN();
}
DEFAULT_CONSTRUCTORS_AND_ASSIGNMENTS(TypeAndShape)
bool operator==(const TypeAndShape &) const;
bool operator!=(const TypeAndShape &that) const { return !(*this == that); }
static std::optional<TypeAndShape> Characterize(
const semantics::Symbol &, FoldingContext &);
static std::optional<TypeAndShape> Characterize(
const semantics::ObjectEntityDetails &);
static std::optional<TypeAndShape> Characterize(
const semantics::AssocEntityDetails &, FoldingContext &);
static std::optional<TypeAndShape> Characterize(
const semantics::ProcEntityDetails &);
static std::optional<TypeAndShape> Characterize(
const semantics::ProcInterface &);
static std::optional<TypeAndShape> Characterize(
const semantics::DeclTypeSpec &);
template <typename A>
static std::optional<TypeAndShape> Characterize(
const A &x, FoldingContext &context) {
if (const auto *symbol{UnwrapWholeSymbolDataRef(x)}) {
if (auto result{Characterize(*symbol, context)}) {
return result;
}
}
if (auto type{x.GetType()}) {
if (auto shape{GetShape(context, x)}) {
TypeAndShape result{*type, std::move(*shape)};
if (type->category() == TypeCategory::Character) {
if (const auto *chExpr{UnwrapExpr<Expr<SomeCharacter>>(x)}) {
if (auto length{chExpr->LEN()}) {
result.set_LEN(Expr<SomeInteger>{std::move(*length)});
}
}
}
return result;
}
}
return std::nullopt;
}
DynamicType type() const { return type_; }
TypeAndShape &set_type(DynamicType t) {
type_ = t;
return *this;
}
const std::optional<Expr<SomeInteger>> &LEN() const { return LEN_; }
TypeAndShape &set_LEN(Expr<SomeInteger> &&len) {
LEN_ = std::move(len);
return *this;
}
const Shape &shape() const { return shape_; }
const Attrs &attrs() const { return attrs_; }
int corank() const { return corank_; }
int Rank() const { return GetRank(shape_); }
bool IsCompatibleWith(parser::ContextualMessages &, const TypeAndShape &that,
const char *thisIs = "POINTER", const char *thatIs = "TARGET",
bool isElemental = false) const;
llvm::raw_ostream &Dump(llvm::raw_ostream &) const;
private:
void AcquireShape(const semantics::ObjectEntityDetails &);
void AcquireLEN();
protected:
DynamicType type_;
std::optional<Expr<SomeInteger>> LEN_;
Shape shape_;
Attrs attrs_;
int corank_{0};
};
// 15.3.2.2
struct DummyDataObject {
ENUM_CLASS(Attr, Optional, Allocatable, Asynchronous, Contiguous, Value,
Volatile, Pointer, Target)
using Attrs = common::EnumSet<Attr, Attr_enumSize>;
DEFAULT_CONSTRUCTORS_AND_ASSIGNMENTS(DummyDataObject)
explicit DummyDataObject(const TypeAndShape &t) : type{t} {}
explicit DummyDataObject(TypeAndShape &&t) : type{std::move(t)} {}
explicit DummyDataObject(DynamicType t) : type{t} {}
bool operator==(const DummyDataObject &) const;
bool operator!=(const DummyDataObject &that) const {
return !(*this == that);
}
static std::optional<DummyDataObject> Characterize(const semantics::Symbol &);
bool CanBePassedViaImplicitInterface() const;
llvm::raw_ostream &Dump(llvm::raw_ostream &) const;
TypeAndShape type;
std::vector<Expr<SubscriptInteger>> coshape;
common::Intent intent{common::Intent::Default};
Attrs attrs;
};
// 15.3.2.3
struct DummyProcedure {
ENUM_CLASS(Attr, Pointer, Optional)
using Attrs = common::EnumSet<Attr, Attr_enumSize>;
DECLARE_CONSTRUCTORS_AND_ASSIGNMENTS(DummyProcedure)
explicit DummyProcedure(Procedure &&);
bool operator==(const DummyProcedure &) const;
bool operator!=(const DummyProcedure &that) const { return !(*this == that); }
static std::optional<DummyProcedure> Characterize(
const semantics::Symbol &, const IntrinsicProcTable &);
llvm::raw_ostream &Dump(llvm::raw_ostream &) const;
CopyableIndirection<Procedure> procedure;
common::Intent intent{common::Intent::Default};
Attrs attrs;
};
// 15.3.2.4
struct AlternateReturn {
bool operator==(const AlternateReturn &) const { return true; }
bool operator!=(const AlternateReturn &) const { return false; }
llvm::raw_ostream &Dump(llvm::raw_ostream &) const;
};
// 15.3.2.1
struct DummyArgument {
DECLARE_CONSTRUCTORS_AND_ASSIGNMENTS(DummyArgument)
DummyArgument(std::string &&name, DummyDataObject &&x)
: name{std::move(name)}, u{std::move(x)} {}
DummyArgument(std::string &&name, DummyProcedure &&x)
: name{std::move(name)}, u{std::move(x)} {}
explicit DummyArgument(AlternateReturn &&x) : u{std::move(x)} {}
~DummyArgument();
bool operator==(const DummyArgument &) const;
bool operator!=(const DummyArgument &that) const { return !(*this == that); }
static std::optional<DummyArgument> Characterize(
const semantics::Symbol &, const IntrinsicProcTable &);
static std::optional<DummyArgument> FromActual(
std::string &&, const Expr<SomeType> &, FoldingContext &);
bool IsOptional() const;
void SetOptional(bool = true);
bool CanBePassedViaImplicitInterface() const;
llvm::raw_ostream &Dump(llvm::raw_ostream &) const;
// name and pass are not characteristics and so does not participate in
// operator== but are needed to determine if procedures are distinguishable
std::string name;
bool pass{false}; // is this the PASS argument of its procedure
std::variant<DummyDataObject, DummyProcedure, AlternateReturn> u;
};
using DummyArguments = std::vector<DummyArgument>;
// 15.3.3
struct FunctionResult {
ENUM_CLASS(Attr, Allocatable, Pointer, Contiguous)
using Attrs = common::EnumSet<Attr, Attr_enumSize>;
DECLARE_CONSTRUCTORS_AND_ASSIGNMENTS(FunctionResult)
explicit FunctionResult(DynamicType);
explicit FunctionResult(TypeAndShape &&);
explicit FunctionResult(Procedure &&);
~FunctionResult();
bool operator==(const FunctionResult &) const;
bool operator!=(const FunctionResult &that) const { return !(*this == that); }
static std::optional<FunctionResult> Characterize(
const Symbol &, const IntrinsicProcTable &);
bool IsAssumedLengthCharacter() const;
const Procedure *IsProcedurePointer() const {
if (const auto *pp{std::get_if<CopyableIndirection<Procedure>>(&u)}) {
return &pp->value();
} else {
return nullptr;
}
}
const TypeAndShape *GetTypeAndShape() const {
return std::get_if<TypeAndShape>(&u);
}
void SetType(DynamicType t) { std::get<TypeAndShape>(u).set_type(t); }
bool CanBeReturnedViaImplicitInterface() const;
llvm::raw_ostream &Dump(llvm::raw_ostream &) const;
Attrs attrs;
std::variant<TypeAndShape, CopyableIndirection<Procedure>> u;
};
// 15.3.1
struct Procedure {
ENUM_CLASS(
Attr, Pure, Elemental, BindC, ImplicitInterface, NullPointer, Subroutine)
using Attrs = common::EnumSet<Attr, Attr_enumSize>;
Procedure(FunctionResult &&, DummyArguments &&, Attrs);
Procedure(DummyArguments &&, Attrs); // for subroutines and NULL()
DECLARE_CONSTRUCTORS_AND_ASSIGNMENTS(Procedure)
~Procedure();
bool operator==(const Procedure &) const;
bool operator!=(const Procedure &that) const { return !(*this == that); }
// Characterizes the procedure represented by a symbol, which may be an
// "unrestricted specific intrinsic function".
static std::optional<Procedure> Characterize(
const semantics::Symbol &, const IntrinsicProcTable &);
static std::optional<Procedure> Characterize(
const ProcedureDesignator &, const IntrinsicProcTable &);
static std::optional<Procedure> Characterize(
const ProcedureRef &, const IntrinsicProcTable &);
// At most one of these will return true.
// For "EXTERNAL P" with no calls to P, both will be false.
bool IsFunction() const { return functionResult.has_value(); }
bool IsSubroutine() const { return attrs.test(Attr::Subroutine); }
bool IsPure() const { return attrs.test(Attr::Pure); }
bool IsElemental() const { return attrs.test(Attr::Elemental); }
bool IsBindC() const { return attrs.test(Attr::BindC); }
bool HasExplicitInterface() const {
return !attrs.test(Attr::ImplicitInterface);
}
int FindPassIndex(std::optional<parser::CharBlock>) const;
bool CanBeCalledViaImplicitInterface() const;
bool CanOverride(const Procedure &, std::optional<int> passIndex) const;
llvm::raw_ostream &Dump(llvm::raw_ostream &) const;
std::optional<FunctionResult> functionResult;
DummyArguments dummyArguments;
Attrs attrs;
private:
Procedure() {}
};
} // namespace Fortran::evaluate::characteristics
#endif // FORTRAN_EVALUATE_CHARACTERISTICS_H_

View File

@ -0,0 +1,69 @@
//===-- include/flang/Evaluate/check-expression.h ---------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
// Static expression checking
#ifndef FORTRAN_EVALUATE_CHECK_EXPRESSION_H_
#define FORTRAN_EVALUATE_CHECK_EXPRESSION_H_
#include "expression.h"
#include "type.h"
#include <optional>
namespace Fortran::parser {
class ContextualMessages;
}
namespace Fortran::semantics {
class Scope;
}
namespace Fortran::evaluate {
class IntrinsicProcTable;
// Predicate: true when an expression is a constant expression (in the
// strict sense of the Fortran standard); it may not (yet) be a hard
// constant value.
template <typename A> bool IsConstantExpr(const A &);
extern template bool IsConstantExpr(const Expr<SomeType> &);
extern template bool IsConstantExpr(const Expr<SomeInteger> &);
extern template bool IsConstantExpr(const Expr<SubscriptInteger> &);
// Checks whether an expression is an object designator with
// constant addressing and no vector-valued subscript.
bool IsInitialDataTarget(const Expr<SomeType> &, parser::ContextualMessages &);
// Check whether an expression is a specification expression
// (10.1.11(2), C1010). Constant expressions are always valid
// specification expressions.
template <typename A>
void CheckSpecificationExpr(
const A &, parser::ContextualMessages &, const semantics::Scope &);
extern template void CheckSpecificationExpr(const Expr<SomeType> &x,
parser::ContextualMessages &, const semantics::Scope &);
extern template void CheckSpecificationExpr(const Expr<SomeInteger> &x,
parser::ContextualMessages &, const semantics::Scope &);
extern template void CheckSpecificationExpr(const Expr<SubscriptInteger> &x,
parser::ContextualMessages &, const semantics::Scope &);
extern template void CheckSpecificationExpr(
const std::optional<Expr<SomeType>> &x, parser::ContextualMessages &,
const semantics::Scope &);
extern template void CheckSpecificationExpr(
const std::optional<Expr<SomeInteger>> &x, parser::ContextualMessages &,
const semantics::Scope &);
extern template void CheckSpecificationExpr(
const std::optional<Expr<SubscriptInteger>> &x,
parser::ContextualMessages &, const semantics::Scope &);
// Simple contiguity (9.5.4)
template <typename A>
bool IsSimplyContiguous(const A &, const IntrinsicProcTable &);
extern template bool IsSimplyContiguous(
const Expr<SomeType> &, const IntrinsicProcTable &);
} // namespace Fortran::evaluate
#endif

View File

@ -0,0 +1,272 @@
//===-- include/flang/Evaluate/common.h -------------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_EVALUATE_COMMON_H_
#define FORTRAN_EVALUATE_COMMON_H_
#include "intrinsics-library.h"
#include "flang/Common/Fortran.h"
#include "flang/Common/default-kinds.h"
#include "flang/Common/enum-set.h"
#include "flang/Common/idioms.h"
#include "flang/Common/indirection.h"
#include "flang/Common/restorer.h"
#include "flang/Parser/char-block.h"
#include "flang/Parser/message.h"
#include <cinttypes>
#include <map>
namespace Fortran::semantics {
class DerivedTypeSpec;
}
namespace Fortran::evaluate {
class IntrinsicProcTable;
using common::ConstantSubscript;
using common::RelationalOperator;
// Integers are always ordered; reals may not be.
ENUM_CLASS(Ordering, Less, Equal, Greater)
ENUM_CLASS(Relation, Less, Equal, Greater, Unordered)
template <typename A>
static constexpr Ordering Compare(const A &x, const A &y) {
if (x < y) {
return Ordering::Less;
} else if (x > y) {
return Ordering::Greater;
} else {
return Ordering::Equal;
}
}
static constexpr Ordering Reverse(Ordering ordering) {
if (ordering == Ordering::Less) {
return Ordering::Greater;
} else if (ordering == Ordering::Greater) {
return Ordering::Less;
} else {
return Ordering::Equal;
}
}
static constexpr Relation RelationFromOrdering(Ordering ordering) {
if (ordering == Ordering::Less) {
return Relation::Less;
} else if (ordering == Ordering::Greater) {
return Relation::Greater;
} else {
return Relation::Equal;
}
}
static constexpr Relation Reverse(Relation relation) {
if (relation == Relation::Less) {
return Relation::Greater;
} else if (relation == Relation::Greater) {
return Relation::Less;
} else {
return relation;
}
}
static constexpr bool Satisfies(RelationalOperator op, Ordering order) {
switch (order) {
case Ordering::Less:
return op == RelationalOperator::LT || op == RelationalOperator::LE ||
op == RelationalOperator::NE;
case Ordering::Equal:
return op == RelationalOperator::LE || op == RelationalOperator::EQ ||
op == RelationalOperator::GE;
case Ordering::Greater:
return op == RelationalOperator::NE || op == RelationalOperator::GE ||
op == RelationalOperator::GT;
}
return false; // silence g++ warning
}
static constexpr bool Satisfies(RelationalOperator op, Relation relation) {
switch (relation) {
case Relation::Less:
return Satisfies(op, Ordering::Less);
case Relation::Equal:
return Satisfies(op, Ordering::Equal);
case Relation::Greater:
return Satisfies(op, Ordering::Greater);
case Relation::Unordered:
return false;
}
return false; // silence g++ warning
}
ENUM_CLASS(
RealFlag, Overflow, DivideByZero, InvalidArgument, Underflow, Inexact)
using RealFlags = common::EnumSet<RealFlag, RealFlag_enumSize>;
template <typename A> struct ValueWithRealFlags {
A AccumulateFlags(RealFlags &f) {
f |= flags;
return value;
}
A value;
RealFlags flags{};
};
struct Rounding {
common::RoundingMode mode{common::RoundingMode::TiesToEven};
// When set, emulate status flag behavior peculiar to x86
// (viz., fail to set the Underflow flag when an inexact product of a
// multiplication is rounded up to a normal number from a subnormal
// in some rounding modes)
#if __x86_64__
bool x86CompatibleBehavior{true};
#else
bool x86CompatibleBehavior{false};
#endif
};
static constexpr Rounding defaultRounding;
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
constexpr bool isHostLittleEndian{false};
#elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
constexpr bool isHostLittleEndian{true};
#else
#error host endianness is not known
#endif
// HostUnsignedInt<BITS> finds the smallest native unsigned integer type
// whose size is >= BITS.
template <bool LE8, bool LE16, bool LE32, bool LE64> struct SmallestUInt {};
template <> struct SmallestUInt<true, true, true, true> {
using type = std::uint8_t;
};
template <> struct SmallestUInt<false, true, true, true> {
using type = std::uint16_t;
};
template <> struct SmallestUInt<false, false, true, true> {
using type = std::uint32_t;
};
template <> struct SmallestUInt<false, false, false, true> {
using type = std::uint64_t;
};
template <int BITS>
using HostUnsignedInt =
typename SmallestUInt<BITS <= 8, BITS <= 16, BITS <= 32, BITS <= 64>::type;
// Many classes in this library follow a common paradigm.
// - There is no default constructor (Class() {}), usually to prevent the
// need for std::monostate as a default constituent in a std::variant<>.
// - There are full copy and move semantics for construction and assignment.
// - Discriminated unions have a std::variant<> member "u" and support
// explicit copy and move constructors as well as comparison for equality.
#define DECLARE_CONSTRUCTORS_AND_ASSIGNMENTS(t) \
t(const t &); \
t(t &&); \
t &operator=(const t &); \
t &operator=(t &&);
#define DEFAULT_CONSTRUCTORS_AND_ASSIGNMENTS(t) \
t(const t &) = default; \
t(t &&) = default; \
t &operator=(const t &) = default; \
t &operator=(t &&) = default;
#define DEFINE_DEFAULT_CONSTRUCTORS_AND_ASSIGNMENTS(t) \
t::t(const t &) = default; \
t::t(t &&) = default; \
t &t::operator=(const t &) = default; \
t &t::operator=(t &&) = default;
#define CONSTEXPR_CONSTRUCTORS_AND_ASSIGNMENTS(t) \
constexpr t(const t &) = default; \
constexpr t(t &&) = default; \
constexpr t &operator=(const t &) = default; \
constexpr t &operator=(t &&) = default;
#define CLASS_BOILERPLATE(t) \
t() = delete; \
DEFAULT_CONSTRUCTORS_AND_ASSIGNMENTS(t)
#define UNION_CONSTRUCTORS(t) \
template <typename _A> explicit t(const _A &x) : u{x} {} \
template <typename _A, typename = common::NoLvalue<_A>> \
explicit t(_A &&x) : u(std::move(x)) {}
#define EVALUATE_UNION_CLASS_BOILERPLATE(t) \
CLASS_BOILERPLATE(t) \
UNION_CONSTRUCTORS(t) \
bool operator==(const t &) const;
// Forward definition of Expr<> so that it can be indirectly used in its own
// definition
template <typename A> class Expr;
class FoldingContext {
public:
FoldingContext(
const common::IntrinsicTypeDefaultKinds &d, const IntrinsicProcTable &t)
: defaults_{d}, intrinsics_{t} {}
FoldingContext(const parser::ContextualMessages &m,
const common::IntrinsicTypeDefaultKinds &d, const IntrinsicProcTable &t,
Rounding round = defaultRounding, bool flush = false)
: messages_{m}, defaults_{d}, intrinsics_{t}, rounding_{round},
flushSubnormalsToZero_{flush} {}
FoldingContext(const FoldingContext &that)
: messages_{that.messages_}, defaults_{that.defaults_},
intrinsics_{that.intrinsics_}, rounding_{that.rounding_},
flushSubnormalsToZero_{that.flushSubnormalsToZero_},
pdtInstance_{that.pdtInstance_}, impliedDos_{that.impliedDos_} {}
FoldingContext(
const FoldingContext &that, const parser::ContextualMessages &m)
: messages_{m}, defaults_{that.defaults_},
intrinsics_{that.intrinsics_}, rounding_{that.rounding_},
flushSubnormalsToZero_{that.flushSubnormalsToZero_},
pdtInstance_{that.pdtInstance_}, impliedDos_{that.impliedDos_} {}
parser::ContextualMessages &messages() { return messages_; }
const parser::ContextualMessages &messages() const { return messages_; }
const common::IntrinsicTypeDefaultKinds &defaults() const {
return defaults_;
}
Rounding rounding() const { return rounding_; }
bool flushSubnormalsToZero() const { return flushSubnormalsToZero_; }
bool bigEndian() const { return bigEndian_; }
const semantics::DerivedTypeSpec *pdtInstance() const { return pdtInstance_; }
const HostIntrinsicProceduresLibrary &hostIntrinsicsLibrary() const {
return hostIntrinsicsLibrary_;
}
const evaluate::IntrinsicProcTable &intrinsics() const { return intrinsics_; }
ConstantSubscript &StartImpliedDo(parser::CharBlock, ConstantSubscript = 1);
std::optional<ConstantSubscript> GetImpliedDo(parser::CharBlock) const;
void EndImpliedDo(parser::CharBlock);
std::map<parser::CharBlock, ConstantSubscript> &impliedDos() {
return impliedDos_;
}
common::Restorer<const semantics::DerivedTypeSpec *> WithPDTInstance(
const semantics::DerivedTypeSpec &spec) {
return common::ScopedSet(pdtInstance_, &spec);
}
private:
parser::ContextualMessages messages_;
const common::IntrinsicTypeDefaultKinds &defaults_;
const IntrinsicProcTable &intrinsics_;
Rounding rounding_{defaultRounding};
bool flushSubnormalsToZero_{false};
bool bigEndian_{false};
const semantics::DerivedTypeSpec *pdtInstance_{nullptr};
std::map<parser::CharBlock, ConstantSubscript> impliedDos_;
HostIntrinsicProceduresLibrary hostIntrinsicsLibrary_;
};
void RealFlagWarnings(FoldingContext &, const RealFlags &, const char *op);
} // namespace Fortran::evaluate
#endif // FORTRAN_EVALUATE_COMMON_H_

View File

@ -0,0 +1,105 @@
//===-- include/flang/Evaluate/complex.h ------------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_EVALUATE_COMPLEX_H_
#define FORTRAN_EVALUATE_COMPLEX_H_
#include "formatting.h"
#include "real.h"
#include <string>
namespace llvm {
class raw_ostream;
}
namespace Fortran::evaluate::value {
template <typename REAL_TYPE> class Complex {
public:
using Part = REAL_TYPE;
static constexpr int bits{2 * Part::bits};
constexpr Complex() {} // (+0.0, +0.0)
constexpr Complex(const Complex &) = default;
constexpr Complex(const Part &r, const Part &i) : re_{r}, im_{i} {}
explicit constexpr Complex(const Part &r) : re_{r} {}
constexpr Complex &operator=(const Complex &) = default;
constexpr Complex &operator=(Complex &&) = default;
constexpr bool operator==(const Complex &that) const {
return re_ == that.re_ && im_ == that.im_;
}
constexpr const Part &REAL() const { return re_; }
constexpr const Part &AIMAG() const { return im_; }
constexpr Complex CONJG() const { return {re_, im_.Negate()}; }
constexpr Complex Negate() const { return {re_.Negate(), im_.Negate()}; }
constexpr bool Equals(const Complex &that) const {
return re_.Compare(that.re_) == Relation::Equal &&
im_.Compare(that.im_) == Relation::Equal;
}
constexpr bool IsZero() const { return re_.IsZero() || im_.IsZero(); }
constexpr bool IsInfinite() const {
return re_.IsInfinite() || im_.IsInfinite();
}
constexpr bool IsNotANumber() const {
return re_.IsNotANumber() || im_.IsNotANumber();
}
constexpr bool IsSignalingNaN() const {
return re_.IsSignalingNaN() || im_.IsSignalingNaN();
}
template <typename INT>
static ValueWithRealFlags<Complex> FromInteger(
const INT &n, Rounding rounding = defaultRounding) {
ValueWithRealFlags<Complex> result;
result.value.re_ =
Part::FromInteger(n, rounding).AccumulateFlags(result.flags);
return result;
}
ValueWithRealFlags<Complex> Add(
const Complex &, Rounding rounding = defaultRounding) const;
ValueWithRealFlags<Complex> Subtract(
const Complex &, Rounding rounding = defaultRounding) const;
ValueWithRealFlags<Complex> Multiply(
const Complex &, Rounding rounding = defaultRounding) const;
ValueWithRealFlags<Complex> Divide(
const Complex &, Rounding rounding = defaultRounding) const;
constexpr Complex FlushSubnormalToZero() const {
return {re_.FlushSubnormalToZero(), im_.FlushSubnormalToZero()};
}
static constexpr Complex NotANumber() {
return {Part::NotANumber(), Part::NotANumber()};
}
std::string DumpHexadecimal() const;
llvm::raw_ostream &AsFortran(llvm::raw_ostream &, int kind) const;
// TODO: (C)ABS once Real::HYPOT is done
// TODO: unit testing
private:
Part re_, im_;
};
extern template class Complex<Real<Integer<16>, 11>>;
extern template class Complex<Real<Integer<16>, 8>>;
extern template class Complex<Real<Integer<32>, 24>>;
extern template class Complex<Real<Integer<64>, 53>>;
extern template class Complex<Real<Integer<80>, 64>>;
extern template class Complex<Real<Integer<128>, 113>>;
} // namespace Fortran::evaluate::value
#endif // FORTRAN_EVALUATE_COMPLEX_H_

View File

@ -0,0 +1,235 @@
//===-- include/flang/Evaluate/constant.h -----------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_EVALUATE_CONSTANT_H_
#define FORTRAN_EVALUATE_CONSTANT_H_
#include "formatting.h"
#include "type.h"
#include "flang/Common/default-kinds.h"
#include "flang/Common/reference.h"
#include <map>
#include <vector>
namespace llvm {
class raw_ostream;
}
namespace Fortran::semantics {
class Symbol;
}
namespace Fortran::evaluate {
using semantics::Symbol;
using SymbolRef = common::Reference<const Symbol>;
// Wraps a constant value in a class templated by its resolved type.
// This Constant<> template class should be instantiated only for
// concrete intrinsic types and SomeDerived. There is no instance
// Constant<Expr<SomeType>> since there is no way to constrain each
// element of its array to hold the same type. To represent a generic
// constants, use a generic expression like Expr<SomeInteger> &
// Expr<SomeType>) to wrap the appropriate instantiation of Constant<>.
template <typename> class Constant;
// When describing shapes of constants or specifying 1-based subscript
// values as indices into constants, use a vector of integers.
using ConstantSubscripts = std::vector<ConstantSubscript>;
inline int GetRank(const ConstantSubscripts &s) {
return static_cast<int>(s.size());
}
std::size_t TotalElementCount(const ConstantSubscripts &);
// Validate dimension re-ordering like ORDER in RESHAPE.
// On success, return a vector that can be used as dimOrder in
// ConstantBound::IncrementSubscripts.
std::optional<std::vector<int>> ValidateDimensionOrder(
int rank, const std::vector<int> &order);
bool IsValidShape(const ConstantSubscripts &);
class ConstantBounds {
public:
ConstantBounds() = default;
explicit ConstantBounds(const ConstantSubscripts &shape);
explicit ConstantBounds(ConstantSubscripts &&shape);
~ConstantBounds();
const ConstantSubscripts &shape() const { return shape_; }
const ConstantSubscripts &lbounds() const { return lbounds_; }
void set_lbounds(ConstantSubscripts &&);
int Rank() const { return GetRank(shape_); }
Constant<SubscriptInteger> SHAPE() const;
// If no optional dimension order argument is passed, increments a vector of
// subscripts in Fortran array order (first dimension varying most quickly).
// Otherwise, increments the vector of subscripts according to the given
// dimension order (dimension dimOrder[0] varying most quickly. Dimensions
// indexing is zero based here.) Returns false when last element was visited.
bool IncrementSubscripts(
ConstantSubscripts &, const std::vector<int> *dimOrder = nullptr) const;
protected:
ConstantSubscript SubscriptsToOffset(const ConstantSubscripts &) const;
private:
ConstantSubscripts shape_;
ConstantSubscripts lbounds_;
};
// Constant<> is specialized for Character kinds and SomeDerived.
// The non-Character intrinsic types, and SomeDerived, share enough
// common behavior that they use this common base class.
template <typename RESULT, typename ELEMENT = Scalar<RESULT>>
class ConstantBase : public ConstantBounds {
static_assert(RESULT::category != TypeCategory::Character);
public:
using Result = RESULT;
using Element = ELEMENT;
template <typename A>
ConstantBase(const A &x, Result res = Result{}) : result_{res}, values_{x} {}
template <typename A, typename = common::NoLvalue<A>>
ConstantBase(A &&x, Result res = Result{})
: result_{res}, values_{std::move(x)} {}
ConstantBase(
std::vector<Element> &&, ConstantSubscripts &&, Result = Result{});
DEFAULT_CONSTRUCTORS_AND_ASSIGNMENTS(ConstantBase)
~ConstantBase();
bool operator==(const ConstantBase &) const;
bool empty() const { return values_.empty(); }
std::size_t size() const { return values_.size(); }
const std::vector<Element> &values() const { return values_; }
constexpr Result result() const { return result_; }
constexpr DynamicType GetType() const { return result_.GetType(); }
llvm::raw_ostream &AsFortran(llvm::raw_ostream &) const;
protected:
std::vector<Element> Reshape(const ConstantSubscripts &) const;
std::size_t CopyFrom(const ConstantBase &source, std::size_t count,
ConstantSubscripts &resultSubscripts, const std::vector<int> *dimOrder);
Result result_;
std::vector<Element> values_;
};
template <typename T> class Constant : public ConstantBase<T> {
public:
using Result = T;
using Base = ConstantBase<T>;
using Element = Scalar<T>;
using Base::Base;
CLASS_BOILERPLATE(Constant)
std::optional<Scalar<T>> GetScalarValue() const {
if (ConstantBounds::Rank() == 0) {
return Base::values_.at(0);
} else {
return std::nullopt;
}
}
// Apply subscripts.
Element At(const ConstantSubscripts &) const;
Constant Reshape(ConstantSubscripts &&) const;
std::size_t CopyFrom(const Constant &source, std::size_t count,
ConstantSubscripts &resultSubscripts, const std::vector<int> *dimOrder);
};
template <int KIND>
class Constant<Type<TypeCategory::Character, KIND>> : public ConstantBounds {
public:
using Result = Type<TypeCategory::Character, KIND>;
using Element = Scalar<Result>;
CLASS_BOILERPLATE(Constant)
explicit Constant(const Scalar<Result> &);
explicit Constant(Scalar<Result> &&);
Constant(ConstantSubscript, std::vector<Element> &&, ConstantSubscripts &&);
~Constant();
bool operator==(const Constant &that) const {
return shape() == that.shape() && values_ == that.values_;
}
bool empty() const;
std::size_t size() const;
ConstantSubscript LEN() const { return length_; }
std::optional<Scalar<Result>> GetScalarValue() const {
if (Rank() == 0) {
return values_;
} else {
return std::nullopt;
}
}
// Apply subscripts
Scalar<Result> At(const ConstantSubscripts &) const;
Constant Reshape(ConstantSubscripts &&) const;
llvm::raw_ostream &AsFortran(llvm::raw_ostream &) const;
static constexpr DynamicType GetType() {
return {TypeCategory::Character, KIND};
}
std::size_t CopyFrom(const Constant &source, std::size_t count,
ConstantSubscripts &resultSubscripts, const std::vector<int> *dimOrder);
private:
Scalar<Result> values_; // one contiguous string
ConstantSubscript length_;
ConstantSubscripts shape_;
ConstantSubscripts lbounds_;
};
class StructureConstructor;
using StructureConstructorValues =
std::map<SymbolRef, common::CopyableIndirection<Expr<SomeType>>>;
template <>
class Constant<SomeDerived>
: public ConstantBase<SomeDerived, StructureConstructorValues> {
public:
using Result = SomeDerived;
using Element = StructureConstructorValues;
using Base = ConstantBase<SomeDerived, StructureConstructorValues>;
Constant(const StructureConstructor &);
Constant(StructureConstructor &&);
Constant(const semantics::DerivedTypeSpec &,
std::vector<StructureConstructorValues> &&, ConstantSubscripts &&);
Constant(const semantics::DerivedTypeSpec &,
std::vector<StructureConstructor> &&, ConstantSubscripts &&);
CLASS_BOILERPLATE(Constant)
std::optional<StructureConstructor> GetScalarValue() const;
StructureConstructor At(const ConstantSubscripts &) const;
Constant Reshape(ConstantSubscripts &&) const;
std::size_t CopyFrom(const Constant &source, std::size_t count,
ConstantSubscripts &resultSubscripts, const std::vector<int> *dimOrder);
};
FOR_EACH_LENGTHLESS_INTRINSIC_KIND(extern template class ConstantBase, )
extern template class ConstantBase<SomeDerived, StructureConstructorValues>;
FOR_EACH_INTRINSIC_KIND(extern template class Constant, )
#define INSTANTIATE_CONSTANT_TEMPLATES \
FOR_EACH_LENGTHLESS_INTRINSIC_KIND(template class ConstantBase, ) \
template class ConstantBase<SomeDerived, StructureConstructorValues>; \
FOR_EACH_INTRINSIC_KIND(template class Constant, )
} // namespace Fortran::evaluate
#endif // FORTRAN_EVALUATE_CONSTANT_H_

View File

@ -0,0 +1,872 @@
//===-- include/flang/Evaluate/expression.h ---------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_EVALUATE_EXPRESSION_H_
#define FORTRAN_EVALUATE_EXPRESSION_H_
// Represent Fortran expressions in a type-safe manner.
// Expressions are the sole owners of their constituents; i.e., there is no
// context-independent hash table or sharing of common subexpressions, and
// thus these are trees, not DAGs. Both deep copy and move semantics are
// supported for expression construction. Expressions may be compared
// for equality.
#include "common.h"
#include "constant.h"
#include "formatting.h"
#include "type.h"
#include "variable.h"
#include "flang/Common/Fortran.h"
#include "flang/Common/idioms.h"
#include "flang/Common/indirection.h"
#include "flang/Common/template.h"
#include "flang/Parser/char-block.h"
#include <algorithm>
#include <list>
#include <tuple>
#include <type_traits>
#include <variant>
namespace llvm {
class raw_ostream;
}
namespace Fortran::evaluate {
using common::LogicalOperator;
using common::RelationalOperator;
// Expressions are represented by specializations of the class template Expr.
// Each of these specializations wraps a single data member "u" that
// is a std::variant<> discriminated union over all of the representational
// types for the constants, variables, operations, and other entities that
// can be valid expressions in that context:
// - Expr<Type<CATEGORY, KIND>> represents an expression whose result is of a
// specific intrinsic type category and kind, e.g. Type<TypeCategory::Real, 4>
// - Expr<SomeDerived> wraps data and procedure references that result in an
// instance of a derived type (or CLASS(*) unlimited polymorphic)
// - Expr<SomeKind<CATEGORY>> is a union of Expr<Type<CATEGORY, K>> for each
// kind type parameter value K in that intrinsic type category. It represents
// an expression with known category and any kind.
// - Expr<SomeType> is a union of Expr<SomeKind<CATEGORY>> over the five
// intrinsic type categories of Fortran. It represents any valid expression.
//
// Everything that can appear in, or as, a valid Fortran expression must be
// represented with an instance of some class containing a Result typedef that
// maps to some instantiation of Type<CATEGORY, KIND>, SomeKind<CATEGORY>,
// or SomeType. (Exception: BOZ literal constants in generic Expr<SomeType>.)
template <typename A> using ResultType = typename std::decay_t<A>::Result;
// Common Expr<> behaviors: every Expr<T> derives from ExpressionBase<T>.
template <typename RESULT> class ExpressionBase {
public:
using Result = RESULT;
private:
using Derived = Expr<Result>;
#if defined(__APPLE__) && defined(__GNUC__)
Derived &derived();
const Derived &derived() const;
#else
Derived &derived() { return *static_cast<Derived *>(this); }
const Derived &derived() const { return *static_cast<const Derived *>(this); }
#endif
public:
template <typename A> Derived &operator=(const A &x) {
Derived &d{derived()};
d.u = x;
return d;
}
template <typename A> common::IfNoLvalue<Derived &, A> operator=(A &&x) {
Derived &d{derived()};
d.u = std::move(x);
return d;
}
std::optional<DynamicType> GetType() const;
int Rank() const;
std::string AsFortran() const;
llvm::raw_ostream &AsFortran(llvm::raw_ostream &) const;
static Derived Rewrite(FoldingContext &, Derived &&);
};
// Operations always have specific Fortran result types (i.e., with known
// intrinsic type category and kind parameter value). The classes that
// represent the operations all inherit from this Operation<> base class
// template. Note that Operation has as its first type parameter (DERIVED) a
// "curiously reoccurring template pattern (CRTP)" reference to the specific
// operation class being derived from Operation; e.g., Add is defined with
// struct Add : public Operation<Add, ...>. Uses of instances of Operation<>,
// including its own member functions, can access each specific class derived
// from it via its derived() member function with compile-time type safety.
template <typename DERIVED, typename RESULT, typename... OPERANDS>
class Operation {
// The extra final member is a dummy that allows a safe unused reference
// to element 1 to arise indirectly in the definition of "right()" below
// when the operation has but a single operand.
using OperandTypes = std::tuple<OPERANDS..., std::monostate>;
public:
using Derived = DERIVED;
using Result = RESULT;
static_assert(IsSpecificIntrinsicType<Result>);
static constexpr std::size_t operands{sizeof...(OPERANDS)};
template <int J> using Operand = std::tuple_element_t<J, OperandTypes>;
// Unary operations wrap a single Expr with a CopyableIndirection.
// Binary operations wrap a tuple of CopyableIndirections to Exprs.
private:
using Container = std::conditional_t<operands == 1,
common::CopyableIndirection<Expr<Operand<0>>>,
std::tuple<common::CopyableIndirection<Expr<OPERANDS>>...>>;
public:
CLASS_BOILERPLATE(Operation)
explicit Operation(const Expr<OPERANDS> &... x) : operand_{x...} {}
explicit Operation(Expr<OPERANDS> &&... x) : operand_{std::move(x)...} {}
Derived &derived() { return *static_cast<Derived *>(this); }
const Derived &derived() const { return *static_cast<const Derived *>(this); }
// References to operand expressions from member functions of derived
// classes for specific operators can be made by index, e.g. operand<0>(),
// which must be spelled like "this->template operand<0>()" when
// inherited in a derived class template. There are convenience aliases
// left() and right() that are not templates.
template <int J> Expr<Operand<J>> &operand() {
if constexpr (operands == 1) {
static_assert(J == 0);
return operand_.value();
} else {
return std::get<J>(operand_).value();
}
}
template <int J> const Expr<Operand<J>> &operand() const {
if constexpr (operands == 1) {
static_assert(J == 0);
return operand_.value();
} else {
return std::get<J>(operand_).value();
}
}
Expr<Operand<0>> &left() { return operand<0>(); }
const Expr<Operand<0>> &left() const { return operand<0>(); }
std::conditional_t<(operands > 1), Expr<Operand<1>> &, void> right() {
if constexpr (operands > 1) {
return operand<1>();
}
}
std::conditional_t<(operands > 1), const Expr<Operand<1>> &, void>
right() const {
if constexpr (operands > 1) {
return operand<1>();
}
}
static constexpr std::optional<DynamicType> GetType() {
return Result::GetType();
}
int Rank() const {
int rank{left().Rank()};
if constexpr (operands > 1) {
return std::max(rank, right().Rank());
} else {
return rank;
}
}
bool operator==(const Operation &that) const {
return operand_ == that.operand_;
}
llvm::raw_ostream &AsFortran(llvm::raw_ostream &) const;
private:
Container operand_;
};
// Unary operations
// Conversions to specific types from expressions of known category and
// dynamic kind.
template <typename TO, TypeCategory FROMCAT = TO::category>
struct Convert : public Operation<Convert<TO, FROMCAT>, TO, SomeKind<FROMCAT>> {
// Fortran doesn't have conversions between kinds of CHARACTER apart from
// assignments, and in those the data must be convertible to/from 7-bit ASCII.
// Conversions between kinds of COMPLEX are represented piecewise.
static_assert(((TO::category == TypeCategory::Integer ||
TO::category == TypeCategory::Real) &&
(FROMCAT == TypeCategory::Integer ||
FROMCAT == TypeCategory::Real)) ||
(TO::category == TypeCategory::Character &&
FROMCAT == TypeCategory::Character) ||
(TO::category == TypeCategory::Logical &&
FROMCAT == TypeCategory::Logical));
using Result = TO;
using Operand = SomeKind<FROMCAT>;
using Base = Operation<Convert, Result, Operand>;
using Base::Base;
llvm::raw_ostream &AsFortran(llvm::raw_ostream &) const;
};
template <typename A>
struct Parentheses : public Operation<Parentheses<A>, A, A> {
using Result = A;
using Operand = A;
using Base = Operation<Parentheses, A, A>;
using Base::Base;
};
template <typename A> struct Negate : public Operation<Negate<A>, A, A> {
using Result = A;
using Operand = A;
using Base = Operation<Negate, A, A>;
using Base::Base;
};
template <int KIND>
struct ComplexComponent
: public Operation<ComplexComponent<KIND>, Type<TypeCategory::Real, KIND>,
Type<TypeCategory::Complex, KIND>> {
using Result = Type<TypeCategory::Real, KIND>;
using Operand = Type<TypeCategory::Complex, KIND>;
using Base = Operation<ComplexComponent, Result, Operand>;
CLASS_BOILERPLATE(ComplexComponent)
ComplexComponent(bool isImaginary, const Expr<Operand> &x)
: Base{x}, isImaginaryPart{isImaginary} {}
ComplexComponent(bool isImaginary, Expr<Operand> &&x)
: Base{std::move(x)}, isImaginaryPart{isImaginary} {}
bool isImaginaryPart{true};
};
template <int KIND>
struct Not : public Operation<Not<KIND>, Type<TypeCategory::Logical, KIND>,
Type<TypeCategory::Logical, KIND>> {
using Result = Type<TypeCategory::Logical, KIND>;
using Operand = Result;
using Base = Operation<Not, Result, Operand>;
using Base::Base;
};
// Character lengths are determined by context in Fortran and do not
// have explicit syntax for changing them. Expressions represent
// changes of length (e.g., for assignments and structure constructors)
// with this operation.
template <int KIND>
struct SetLength
: public Operation<SetLength<KIND>, Type<TypeCategory::Character, KIND>,
Type<TypeCategory::Character, KIND>, SubscriptInteger> {
using Result = Type<TypeCategory::Character, KIND>;
using CharacterOperand = Result;
using LengthOperand = SubscriptInteger;
using Base = Operation<SetLength, Result, CharacterOperand, LengthOperand>;
using Base::Base;
};
// Binary operations
template <typename A> struct Add : public Operation<Add<A>, A, A, A> {
using Result = A;
using Operand = A;
using Base = Operation<Add, A, A, A>;
using Base::Base;
};
template <typename A> struct Subtract : public Operation<Subtract<A>, A, A, A> {
using Result = A;
using Operand = A;
using Base = Operation<Subtract, A, A, A>;
using Base::Base;
};
template <typename A> struct Multiply : public Operation<Multiply<A>, A, A, A> {
using Result = A;
using Operand = A;
using Base = Operation<Multiply, A, A, A>;
using Base::Base;
};
template <typename A> struct Divide : public Operation<Divide<A>, A, A, A> {
using Result = A;
using Operand = A;
using Base = Operation<Divide, A, A, A>;
using Base::Base;
};
template <typename A> struct Power : public Operation<Power<A>, A, A, A> {
using Result = A;
using Operand = A;
using Base = Operation<Power, A, A, A>;
using Base::Base;
};
template <typename A>
struct RealToIntPower : public Operation<RealToIntPower<A>, A, A, SomeInteger> {
using Base = Operation<RealToIntPower, A, A, SomeInteger>;
using Result = A;
using BaseOperand = A;
using ExponentOperand = SomeInteger;
using Base::Base;
};
template <typename A> struct Extremum : public Operation<Extremum<A>, A, A, A> {
using Result = A;
using Operand = A;
using Base = Operation<Extremum, A, A, A>;
CLASS_BOILERPLATE(Extremum)
Extremum(Ordering ord, const Expr<Operand> &x, const Expr<Operand> &y)
: Base{x, y}, ordering{ord} {}
Extremum(Ordering ord, Expr<Operand> &&x, Expr<Operand> &&y)
: Base{std::move(x), std::move(y)}, ordering{ord} {}
Ordering ordering{Ordering::Greater};
};
template <int KIND>
struct ComplexConstructor
: public Operation<ComplexConstructor<KIND>,
Type<TypeCategory::Complex, KIND>, Type<TypeCategory::Real, KIND>,
Type<TypeCategory::Real, KIND>> {
using Result = Type<TypeCategory::Complex, KIND>;
using Operand = Type<TypeCategory::Real, KIND>;
using Base = Operation<ComplexConstructor, Result, Operand, Operand>;
using Base::Base;
};
template <int KIND>
struct Concat
: public Operation<Concat<KIND>, Type<TypeCategory::Character, KIND>,
Type<TypeCategory::Character, KIND>,
Type<TypeCategory::Character, KIND>> {
using Result = Type<TypeCategory::Character, KIND>;
using Operand = Result;
using Base = Operation<Concat, Result, Operand, Operand>;
using Base::Base;
};
template <int KIND>
struct LogicalOperation
: public Operation<LogicalOperation<KIND>,
Type<TypeCategory::Logical, KIND>, Type<TypeCategory::Logical, KIND>,
Type<TypeCategory::Logical, KIND>> {
using Result = Type<TypeCategory::Logical, KIND>;
using Operand = Result;
using Base = Operation<LogicalOperation, Result, Operand, Operand>;
CLASS_BOILERPLATE(LogicalOperation)
LogicalOperation(
LogicalOperator opr, const Expr<Operand> &x, const Expr<Operand> &y)
: Base{x, y}, logicalOperator{opr} {}
LogicalOperation(LogicalOperator opr, Expr<Operand> &&x, Expr<Operand> &&y)
: Base{std::move(x), std::move(y)}, logicalOperator{opr} {}
LogicalOperator logicalOperator;
};
// Array constructors
template <typename RESULT> class ArrayConstructorValues;
struct ImpliedDoIndex {
using Result = SubscriptInteger;
bool operator==(const ImpliedDoIndex &) const;
static constexpr int Rank() { return 0; }
parser::CharBlock name; // nested implied DOs must use distinct names
};
template <typename RESULT> class ImpliedDo {
public:
using Result = RESULT;
using Index = ResultType<ImpliedDoIndex>;
ImpliedDo(parser::CharBlock name, Expr<Index> &&lower, Expr<Index> &&upper,
Expr<Index> &&stride, ArrayConstructorValues<Result> &&values)
: name_{name}, lower_{std::move(lower)}, upper_{std::move(upper)},
stride_{std::move(stride)}, values_{std::move(values)} {}
DEFAULT_CONSTRUCTORS_AND_ASSIGNMENTS(ImpliedDo)
bool operator==(const ImpliedDo &) const;
parser::CharBlock name() const { return name_; }
Expr<Index> &lower() { return lower_.value(); }
const Expr<Index> &lower() const { return lower_.value(); }
Expr<Index> &upper() { return upper_.value(); }
const Expr<Index> &upper() const { return upper_.value(); }
Expr<Index> &stride() { return stride_.value(); }
const Expr<Index> &stride() const { return stride_.value(); }
ArrayConstructorValues<Result> &values() { return values_.value(); }
const ArrayConstructorValues<Result> &values() const {
return values_.value();
}
private:
parser::CharBlock name_;
common::CopyableIndirection<Expr<Index>> lower_, upper_, stride_;
common::CopyableIndirection<ArrayConstructorValues<Result>> values_;
};
template <typename RESULT> struct ArrayConstructorValue {
using Result = RESULT;
EVALUATE_UNION_CLASS_BOILERPLATE(ArrayConstructorValue)
std::variant<Expr<Result>, ImpliedDo<Result>> u;
};
template <typename RESULT> class ArrayConstructorValues {
public:
using Result = RESULT;
using Values = std::vector<ArrayConstructorValue<Result>>;
DEFAULT_CONSTRUCTORS_AND_ASSIGNMENTS(ArrayConstructorValues)
ArrayConstructorValues() {}
bool operator==(const ArrayConstructorValues &) const;
static constexpr int Rank() { return 1; }
template <typename A> common::NoLvalue<A> Push(A &&x) {
values_.emplace_back(std::move(x));
}
typename Values::iterator begin() { return values_.begin(); }
typename Values::const_iterator begin() const { return values_.begin(); }
typename Values::iterator end() { return values_.end(); }
typename Values::const_iterator end() const { return values_.end(); }
protected:
Values values_;
};
// Note that there are specializations of ArrayConstructor for character
// and derived types, since they must carry additional type information,
// but that an empty ArrayConstructor can be constructed for any type
// given an expression from which such type information may be gleaned.
template <typename RESULT>
class ArrayConstructor : public ArrayConstructorValues<RESULT> {
public:
using Result = RESULT;
using Base = ArrayConstructorValues<Result>;
DEFAULT_CONSTRUCTORS_AND_ASSIGNMENTS(ArrayConstructor)
explicit ArrayConstructor(Base &&values) : Base{std::move(values)} {}
template <typename T> explicit ArrayConstructor(const Expr<T> &) {}
static constexpr Result result() { return Result{}; }
static constexpr DynamicType GetType() { return Result::GetType(); }
llvm::raw_ostream &AsFortran(llvm::raw_ostream &) const;
};
template <int KIND>
class ArrayConstructor<Type<TypeCategory::Character, KIND>>
: public ArrayConstructorValues<Type<TypeCategory::Character, KIND>> {
public:
using Result = Type<TypeCategory::Character, KIND>;
using Base = ArrayConstructorValues<Result>;
CLASS_BOILERPLATE(ArrayConstructor)
ArrayConstructor(Expr<SubscriptInteger> &&len, Base &&v)
: Base{std::move(v)}, length_{std::move(len)} {}
template <typename A>
explicit ArrayConstructor(const A &prototype)
: length_{prototype.LEN().value()} {}
bool operator==(const ArrayConstructor &) const;
static constexpr Result result() { return Result{}; }
static constexpr DynamicType GetType() { return Result::GetType(); }
llvm::raw_ostream &AsFortran(llvm::raw_ostream &) const;
const Expr<SubscriptInteger> &LEN() const { return length_.value(); }
private:
common::CopyableIndirection<Expr<SubscriptInteger>> length_;
};
template <>
class ArrayConstructor<SomeDerived>
: public ArrayConstructorValues<SomeDerived> {
public:
using Result = SomeDerived;
using Base = ArrayConstructorValues<Result>;
CLASS_BOILERPLATE(ArrayConstructor)
ArrayConstructor(const semantics::DerivedTypeSpec &spec, Base &&v)
: Base{std::move(v)}, result_{spec} {}
template <typename A>
explicit ArrayConstructor(const A &prototype)
: result_{prototype.GetType().value().GetDerivedTypeSpec()} {}
bool operator==(const ArrayConstructor &) const;
constexpr Result result() const { return result_; }
constexpr DynamicType GetType() const { return result_.GetType(); }
llvm::raw_ostream &AsFortran(llvm::raw_ostream &) const;
private:
Result result_;
};
// Expression representations for each type category.
template <int KIND>
class Expr<Type<TypeCategory::Integer, KIND>>
: public ExpressionBase<Type<TypeCategory::Integer, KIND>> {
public:
using Result = Type<TypeCategory::Integer, KIND>;
EVALUATE_UNION_CLASS_BOILERPLATE(Expr)
private:
using Conversions = std::tuple<Convert<Result, TypeCategory::Integer>,
Convert<Result, TypeCategory::Real>>;
using Operations = std::tuple<Parentheses<Result>, Negate<Result>,
Add<Result>, Subtract<Result>, Multiply<Result>, Divide<Result>,
Power<Result>, Extremum<Result>>;
using Indices = std::conditional_t<KIND == ImpliedDoIndex::Result::kind,
std::tuple<ImpliedDoIndex>, std::tuple<>>;
using DescriptorInquiries =
std::conditional_t<KIND == DescriptorInquiry::Result::kind,
std::tuple<DescriptorInquiry>, std::tuple<>>;
using Others = std::tuple<Constant<Result>, ArrayConstructor<Result>,
TypeParamInquiry<KIND>, Designator<Result>, FunctionRef<Result>>;
public:
common::TupleToVariant<common::CombineTuples<Operations, Conversions, Indices,
DescriptorInquiries, Others>>
u;
};
template <int KIND>
class Expr<Type<TypeCategory::Real, KIND>>
: public ExpressionBase<Type<TypeCategory::Real, KIND>> {
public:
using Result = Type<TypeCategory::Real, KIND>;
EVALUATE_UNION_CLASS_BOILERPLATE(Expr)
explicit Expr(const Scalar<Result> &x) : u{Constant<Result>{x}} {}
private:
// N.B. Real->Complex and Complex->Real conversions are done with CMPLX
// and part access operations (resp.). Conversions between kinds of
// Complex are done via decomposition to Real and reconstruction.
using Conversions = std::variant<Convert<Result, TypeCategory::Integer>,
Convert<Result, TypeCategory::Real>>;
using Operations = std::variant<ComplexComponent<KIND>, Parentheses<Result>,
Negate<Result>, Add<Result>, Subtract<Result>, Multiply<Result>,
Divide<Result>, Power<Result>, RealToIntPower<Result>, Extremum<Result>>;
using Others = std::variant<Constant<Result>, ArrayConstructor<Result>,
Designator<Result>, FunctionRef<Result>>;
public:
common::CombineVariants<Operations, Conversions, Others> u;
};
template <int KIND>
class Expr<Type<TypeCategory::Complex, KIND>>
: public ExpressionBase<Type<TypeCategory::Complex, KIND>> {
public:
using Result = Type<TypeCategory::Complex, KIND>;
EVALUATE_UNION_CLASS_BOILERPLATE(Expr)
explicit Expr(const Scalar<Result> &x) : u{Constant<Result>{x}} {}
// Note that many COMPLEX operations are represented as REAL operations
// over their components (viz., conversions, negation, add, and subtract).
using Operations =
std::variant<Parentheses<Result>, Multiply<Result>, Divide<Result>,
Power<Result>, RealToIntPower<Result>, ComplexConstructor<KIND>>;
using Others = std::variant<Constant<Result>, ArrayConstructor<Result>,
Designator<Result>, FunctionRef<Result>>;
public:
common::CombineVariants<Operations, Others> u;
};
FOR_EACH_INTEGER_KIND(extern template class Expr, )
FOR_EACH_REAL_KIND(extern template class Expr, )
FOR_EACH_COMPLEX_KIND(extern template class Expr, )
template <int KIND>
class Expr<Type<TypeCategory::Character, KIND>>
: public ExpressionBase<Type<TypeCategory::Character, KIND>> {
public:
using Result = Type<TypeCategory::Character, KIND>;
EVALUATE_UNION_CLASS_BOILERPLATE(Expr)
explicit Expr(const Scalar<Result> &x) : u{Constant<Result>{x}} {}
explicit Expr(Scalar<Result> &&x) : u{Constant<Result>{std::move(x)}} {}
std::optional<Expr<SubscriptInteger>> LEN() const;
std::variant<Constant<Result>, ArrayConstructor<Result>, Designator<Result>,
FunctionRef<Result>, Parentheses<Result>, Convert<Result>, Concat<KIND>,
Extremum<Result>, SetLength<KIND>>
u;
};
FOR_EACH_CHARACTER_KIND(extern template class Expr, )
// The Relational class template is a helper for constructing logical
// expressions with polymorphism over the cross product of the possible
// categories and kinds of comparable operands.
// Fortran defines a numeric relation with distinct types or kinds as
// first undergoing the same operand conversions that occur with the intrinsic
// addition operator. Character relations must have the same kind.
// There are no relations between LOGICAL values.
template <typename T>
struct Relational : public Operation<Relational<T>, LogicalResult, T, T> {
using Result = LogicalResult;
using Base = Operation<Relational, LogicalResult, T, T>;
using Operand = typename Base::template Operand<0>;
static_assert(Operand::category == TypeCategory::Integer ||
Operand::category == TypeCategory::Real ||
Operand::category == TypeCategory::Character);
CLASS_BOILERPLATE(Relational)
Relational(
RelationalOperator r, const Expr<Operand> &a, const Expr<Operand> &b)
: Base{a, b}, opr{r} {}
Relational(RelationalOperator r, Expr<Operand> &&a, Expr<Operand> &&b)
: Base{std::move(a), std::move(b)}, opr{r} {}
RelationalOperator opr;
};
template <> class Relational<SomeType> {
// COMPLEX data are compared piecewise.
using DirectlyComparableTypes =
common::CombineTuples<IntegerTypes, RealTypes, CharacterTypes>;
public:
using Result = LogicalResult;
EVALUATE_UNION_CLASS_BOILERPLATE(Relational)
static constexpr DynamicType GetType() { return Result::GetType(); }
int Rank() const {
return std::visit([](const auto &x) { return x.Rank(); }, u);
}
llvm::raw_ostream &AsFortran(llvm::raw_ostream &o) const;
common::MapTemplate<Relational, DirectlyComparableTypes> u;
};
FOR_EACH_INTEGER_KIND(extern template struct Relational, )
FOR_EACH_REAL_KIND(extern template struct Relational, )
FOR_EACH_CHARACTER_KIND(extern template struct Relational, )
extern template struct Relational<SomeType>;
// Logical expressions of a kind bigger than LogicalResult
// do not include Relational<> operations as possibilities,
// since the results of Relationals are always LogicalResult
// (kind=1).
template <int KIND>
class Expr<Type<TypeCategory::Logical, KIND>>
: public ExpressionBase<Type<TypeCategory::Logical, KIND>> {
public:
using Result = Type<TypeCategory::Logical, KIND>;
EVALUATE_UNION_CLASS_BOILERPLATE(Expr)
explicit Expr(const Scalar<Result> &x) : u{Constant<Result>{x}} {}
explicit Expr(bool x) : u{Constant<Result>{x}} {}
private:
using Operations = std::tuple<Convert<Result>, Parentheses<Result>, Not<KIND>,
LogicalOperation<KIND>>;
using Relations = std::conditional_t<KIND == LogicalResult::kind,
std::tuple<Relational<SomeType>>, std::tuple<>>;
using Others = std::tuple<Constant<Result>, ArrayConstructor<Result>,
Designator<Result>, FunctionRef<Result>>;
public:
common::TupleToVariant<common::CombineTuples<Operations, Relations, Others>>
u;
};
FOR_EACH_LOGICAL_KIND(extern template class Expr, )
// StructureConstructor pairs a StructureConstructorValues instance
// (a map associating symbols with expressions) with a derived type
// specification. There are two other similar classes:
// - ArrayConstructor<SomeDerived> comprises a derived type spec &
// zero or more instances of Expr<SomeDerived>; it has rank 1
// but not (in the most general case) a known shape.
// - Constant<SomeDerived> comprises a derived type spec, zero or more
// homogeneous instances of StructureConstructorValues whose type
// parameters and component expressions are all constant, and a
// known shape (possibly scalar).
// StructureConstructor represents a scalar value of derived type that
// is not necessarily a constant. It is used only as an Expr<SomeDerived>
// alternative and as the type Scalar<SomeDerived> (with an assumption
// of constant component value expressions).
class StructureConstructor {
public:
using Result = SomeDerived;
explicit StructureConstructor(const semantics::DerivedTypeSpec &spec)
: result_{spec} {}
StructureConstructor(
const semantics::DerivedTypeSpec &, const StructureConstructorValues &);
StructureConstructor(
const semantics::DerivedTypeSpec &, StructureConstructorValues &&);
CLASS_BOILERPLATE(StructureConstructor)
constexpr Result result() const { return result_; }
const semantics::DerivedTypeSpec &derivedTypeSpec() const {
return result_.derivedTypeSpec();
}
StructureConstructorValues &values() { return values_; }
const StructureConstructorValues &values() const { return values_; }
bool operator==(const StructureConstructor &) const;
StructureConstructorValues::iterator begin() { return values_.begin(); }
StructureConstructorValues::const_iterator begin() const {
return values_.begin();
}
StructureConstructorValues::iterator end() { return values_.end(); }
StructureConstructorValues::const_iterator end() const {
return values_.end();
}
const Expr<SomeType> *Find(const Symbol &) const; // can return null
StructureConstructor &Add(const semantics::Symbol &, Expr<SomeType> &&);
int Rank() const { return 0; }
DynamicType GetType() const;
llvm::raw_ostream &AsFortran(llvm::raw_ostream &) const;
private:
Result result_;
StructureConstructorValues values_;
};
// An expression whose result has a derived type.
template <> class Expr<SomeDerived> : public ExpressionBase<SomeDerived> {
public:
using Result = SomeDerived;
EVALUATE_UNION_CLASS_BOILERPLATE(Expr)
std::variant<Constant<Result>, ArrayConstructor<Result>, StructureConstructor,
Designator<Result>, FunctionRef<Result>>
u;
};
// A polymorphic expression of known intrinsic type category, but dynamic
// kind, represented as a discriminated union over Expr<Type<CAT, K>>
// for each supported kind K in the category.
template <TypeCategory CAT>
class Expr<SomeKind<CAT>> : public ExpressionBase<SomeKind<CAT>> {
public:
using Result = SomeKind<CAT>;
EVALUATE_UNION_CLASS_BOILERPLATE(Expr)
int GetKind() const;
common::MapTemplate<Expr, CategoryTypes<CAT>> u;
};
template <> class Expr<SomeCharacter> : public ExpressionBase<SomeCharacter> {
public:
using Result = SomeCharacter;
EVALUATE_UNION_CLASS_BOILERPLATE(Expr)
int GetKind() const;
std::optional<Expr<SubscriptInteger>> LEN() const;
common::MapTemplate<Expr, CategoryTypes<TypeCategory::Character>> u;
};
// A variant comprising the Expr<> instantiations over SomeDerived and
// SomeKind<CATEGORY>.
using CategoryExpression = common::MapTemplate<Expr, SomeCategory>;
// BOZ literal "typeless" constants must be wide enough to hold a numeric
// value of any supported kind of INTEGER or REAL. They must also be
// distinguishable from other integer constants, since they are permitted
// to be used in only a few situations.
using BOZLiteralConstant = typename LargestReal::Scalar::Word;
// Null pointers without MOLD= arguments are typed by context.
struct NullPointer {
constexpr bool operator==(const NullPointer &) const { return true; }
constexpr int Rank() const { return 0; }
};
// Procedure pointer targets are treated as if they were typeless.
// They are either procedure designators or values returned from
// function references.
using TypelessExpression = std::variant<BOZLiteralConstant, NullPointer,
ProcedureDesignator, ProcedureRef>;
// A completely generic expression, polymorphic across all of the intrinsic type
// categories and each of their kinds.
template <> class Expr<SomeType> : public ExpressionBase<SomeType> {
public:
using Result = SomeType;
EVALUATE_UNION_CLASS_BOILERPLATE(Expr)
// Owning references to these generic expressions can appear in other
// compiler data structures (viz., the parse tree and symbol table), so
// its destructor is externalized to reduce redundant default instances.
~Expr();
template <TypeCategory CAT, int KIND>
explicit Expr(const Expr<Type<CAT, KIND>> &x) : u{Expr<SomeKind<CAT>>{x}} {}
template <TypeCategory CAT, int KIND>
explicit Expr(Expr<Type<CAT, KIND>> &&x)
: u{Expr<SomeKind<CAT>>{std::move(x)}} {}
template <TypeCategory CAT, int KIND>
Expr &operator=(const Expr<Type<CAT, KIND>> &x) {
u = Expr<SomeKind<CAT>>{x};
return *this;
}
template <TypeCategory CAT, int KIND>
Expr &operator=(Expr<Type<CAT, KIND>> &&x) {
u = Expr<SomeKind<CAT>>{std::move(x)};
return *this;
}
public:
common::CombineVariants<TypelessExpression, CategoryExpression> u;
};
// An assignment is either intrinsic, user-defined (with a ProcedureRef to
// specify the procedure to call), or pointer assignment (with possibly empty
// BoundsSpec or non-empty BoundsRemapping). In all cases there are Exprs
// representing the LHS and RHS of the assignment.
class Assignment {
public:
Assignment(Expr<SomeType> &&lhs, Expr<SomeType> &&rhs)
: lhs(std::move(lhs)), rhs(std::move(rhs)) {}
struct Intrinsic {};
using BoundsSpec = std::vector<Expr<SubscriptInteger>>;
using BoundsRemapping =
std::vector<std::pair<Expr<SubscriptInteger>, Expr<SubscriptInteger>>>;
llvm::raw_ostream &AsFortran(llvm::raw_ostream &) const;
Expr<SomeType> lhs;
Expr<SomeType> rhs;
std::variant<Intrinsic, ProcedureRef, BoundsSpec, BoundsRemapping> u;
};
// This wrapper class is used, by means of a forward reference with
// an owning pointer, to cache analyzed expressions in parse tree nodes.
struct GenericExprWrapper {
GenericExprWrapper() {}
explicit GenericExprWrapper(std::optional<Expr<SomeType>> &&x)
: v{std::move(x)} {}
~GenericExprWrapper();
std::optional<Expr<SomeType>> v; // vacant if error
};
// Like GenericExprWrapper but for analyzed assignments
struct GenericAssignmentWrapper {
GenericAssignmentWrapper() {}
explicit GenericAssignmentWrapper(Assignment &&x) : v{std::move(x)} {}
~GenericAssignmentWrapper();
std::optional<Assignment> v; // vacant if error
};
FOR_EACH_CATEGORY_TYPE(extern template class Expr, )
FOR_EACH_TYPE_AND_KIND(extern template class ExpressionBase, )
FOR_EACH_INTRINSIC_KIND(extern template class ArrayConstructorValues, )
FOR_EACH_INTRINSIC_KIND(extern template class ArrayConstructor, )
// Template instantiations to resolve these "extern template" declarations.
#define INSTANTIATE_EXPRESSION_TEMPLATES \
FOR_EACH_INTRINSIC_KIND(template class Expr, ) \
FOR_EACH_CATEGORY_TYPE(template class Expr, ) \
FOR_EACH_INTEGER_KIND(template struct Relational, ) \
FOR_EACH_REAL_KIND(template struct Relational, ) \
FOR_EACH_CHARACTER_KIND(template struct Relational, ) \
template struct Relational<SomeType>; \
FOR_EACH_TYPE_AND_KIND(template class ExpressionBase, ) \
FOR_EACH_INTRINSIC_KIND(template class ArrayConstructorValues, ) \
FOR_EACH_INTRINSIC_KIND(template class ArrayConstructor, )
} // namespace Fortran::evaluate
#endif // FORTRAN_EVALUATE_EXPRESSION_H_

View File

@ -0,0 +1,100 @@
//===-- include/flang/Evaluate/fold.h ---------------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_EVALUATE_FOLD_H_
#define FORTRAN_EVALUATE_FOLD_H_
// Implements expression tree rewriting, particularly constant expression
// evaluation.
#include "common.h"
#include "constant.h"
#include "expression.h"
#include "tools.h"
#include "type.h"
#include <variant>
namespace Fortran::evaluate {
using namespace Fortran::parser::literals;
// Fold() rewrites an expression and returns it. When the rewritten expression
// is a constant, UnwrapConstantValue() and GetScalarConstantValue() below will
// be able to extract it.
// Note the rvalue reference argument: the rewrites are performed in place
// for efficiency.
template <typename T> Expr<T> Fold(FoldingContext &context, Expr<T> &&expr) {
return Expr<T>::Rewrite(context, std::move(expr));
}
template <typename T>
std::optional<Expr<T>> Fold(
FoldingContext &context, std::optional<Expr<T>> &&expr) {
if (expr) {
return Fold(context, std::move(*expr));
} else {
return std::nullopt;
}
}
// UnwrapConstantValue() isolates the known constant value of
// an expression, if it has one. It returns a pointer, which is
// const-qualified when the expression is so. The value can be
// parenthesized.
template <typename T, typename EXPR>
auto UnwrapConstantValue(EXPR &expr) -> common::Constify<Constant<T>, EXPR> * {
if (auto *c{UnwrapExpr<Constant<T>>(expr)}) {
return c;
} else {
if constexpr (!std::is_same_v<T, SomeDerived>) {
if (auto *parens{UnwrapExpr<Parentheses<T>>(expr)}) {
return UnwrapConstantValue<T>(parens->left());
}
}
return nullptr;
}
}
// GetScalarConstantValue() extracts the known scalar constant value of
// an expression, if it has one. The value can be parenthesized.
template <typename T, typename EXPR>
auto GetScalarConstantValue(const EXPR &expr) -> std::optional<Scalar<T>> {
if (const Constant<T> *constant{UnwrapConstantValue<T>(expr)}) {
return constant->GetScalarValue();
} else {
return std::nullopt;
}
}
// When an expression is a constant integer, ToInt64() extracts its value.
// Ensure that the expression has been folded beforehand when folding might
// be required.
template <int KIND>
std::optional<std::int64_t> ToInt64(
const Expr<Type<TypeCategory::Integer, KIND>> &expr) {
if (auto scalar{
GetScalarConstantValue<Type<TypeCategory::Integer, KIND>>(expr)}) {
return scalar->ToInt64();
} else {
return std::nullopt;
}
}
std::optional<std::int64_t> ToInt64(const Expr<SomeInteger> &);
std::optional<std::int64_t> ToInt64(const Expr<SomeType> &);
template <typename A>
std::optional<std::int64_t> ToInt64(const std::optional<A> &x) {
if (x) {
return ToInt64(*x);
} else {
return std::nullopt;
}
}
} // namespace Fortran::evaluate
#endif // FORTRAN_EVALUATE_FOLD_H_

View File

@ -0,0 +1,58 @@
//===-- include/flang/Evaluate/formatting.h ---------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_EVALUATE_FORMATTING_H_
#define FORTRAN_EVALUATE_FORMATTING_H_
// It is inconvenient in C++ to have llvm::raw_ostream::operator<<() as a direct
// friend function of a class template with many instantiations, so the
// various representational class templates in lib/Evaluate format themselves
// via AsFortran(llvm::raw_ostream &) member functions, which the operator<<()
// overload below will call. Others have AsFortran() member functions that
// return strings.
//
// This header is meant to be included by the headers that define the several
// representational class templates that need it, not by external clients.
#include "flang/Common/indirection.h"
#include "llvm/Support/raw_ostream.h"
#include <optional>
#include <type_traits>
namespace Fortran::evaluate {
template <typename A>
auto operator<<(llvm::raw_ostream &o, const A &x) -> decltype(x.AsFortran(o)) {
return x.AsFortran(o);
}
template <typename A>
auto operator<<(llvm::raw_ostream &o, const A &x)
-> decltype(o << x.AsFortran()) {
return o << x.AsFortran();
}
template <typename A, bool COPYABLE>
auto operator<<(
llvm::raw_ostream &o, const Fortran::common::Indirection<A, COPYABLE> &x)
-> decltype(o << x.value()) {
return o << x.value();
}
template <typename A>
auto operator<<(llvm::raw_ostream &o, const std::optional<A> &x)
-> decltype(o << *x) {
if (x) {
o << *x;
} else {
o << "(nullopt)";
}
return o;
}
} // namespace Fortran::evaluate
#endif // FORTRAN_EVALUATE_FORMATTING_H_

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,108 @@
//===-- include/flang/Evaluate/intrinsics-library.h -------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_EVALUATE_INTRINSICS_LIBRARY_H_
#define FORTRAN_EVALUATE_INTRINSICS_LIBRARY_H_
// Defines structures to be used in F18 for folding intrinsic function with host
// runtime libraries. To avoid unnecessary header circular dependencies, the
// actual implementation of the templatized member function are defined in
// intrinsics-library-templates.h The header at hand is meant to be included by
// files that need to define intrinsic runtime data structure but that do not
// use them directly. To actually use the runtime data structures,
// intrinsics-library-templates.h must be included.
#include <functional>
#include <map>
#include <optional>
#include <string>
#include <vector>
namespace Fortran::evaluate {
class FoldingContext;
using TypeCode = unsigned char;
template <typename TR, typename... TA> using FuncPointer = TR (*)(TA...);
// This specific type signature prevents GCC complaining about function casts.
using GenericFunctionPointer = void (*)(void);
enum class PassBy { Ref, Val };
template <typename TA, PassBy Pass = PassBy::Ref> struct ArgumentInfo {
using Type = TA;
static constexpr PassBy pass{Pass};
};
template <typename TR, typename... ArgInfo> struct Signature {
// Note valid template argument are of form
//<TR, ArgumentInfo<TA, PassBy>...> where TA and TR belong to RuntimeTypes.
// RuntimeTypes is a type union defined in intrinsics-library-templates.h to
// avoid circular dependencies. Argument of type void cannot be passed by
// value. So far TR cannot be a pointer.
const std::string name;
};
struct IntrinsicProcedureRuntimeDescription {
const std::string name;
const TypeCode returnType;
const std::vector<TypeCode> argumentsType;
const std::vector<PassBy> argumentsPassedBy;
const bool isElemental;
const GenericFunctionPointer callable;
// Construct from description using host independent types (RuntimeTypes)
template <typename TR, typename... ArgInfo>
IntrinsicProcedureRuntimeDescription(
const Signature<TR, ArgInfo...> &signature, bool isElemental = false);
};
// HostRuntimeIntrinsicProcedure allows host runtime function to be called for
// constant folding.
struct HostRuntimeIntrinsicProcedure : IntrinsicProcedureRuntimeDescription {
// Construct from runtime pointer with host types (float, double....)
template <typename HostTR, typename... HostTA>
HostRuntimeIntrinsicProcedure(const std::string &name,
FuncPointer<HostTR, HostTA...> func, bool isElemental = false);
HostRuntimeIntrinsicProcedure(
const IntrinsicProcedureRuntimeDescription &rteProc,
GenericFunctionPointer handle)
: IntrinsicProcedureRuntimeDescription{rteProc}, handle{handle} {}
GenericFunctionPointer handle;
};
// Defines a wrapper type that indirects calls to host runtime functions.
// Valid ConstantContainer are Scalar (only for elementals) and Constant.
template <template <typename> typename ConstantContainer, typename TR,
typename... TA>
using HostProcedureWrapper = std::function<ConstantContainer<TR>(
FoldingContext &, ConstantContainer<TA>...)>;
// HostIntrinsicProceduresLibrary is a data structure that holds
// HostRuntimeIntrinsicProcedure elements. It is meant for constant folding.
// When queried for an intrinsic procedure, it can return a callable object that
// implements this intrinsic if a host runtime function pointer for this
// intrinsic was added to its data structure.
class HostIntrinsicProceduresLibrary {
public:
HostIntrinsicProceduresLibrary();
void AddProcedure(HostRuntimeIntrinsicProcedure &&sym) {
const std::string name{sym.name};
procedures_.insert(std::make_pair(name, std::move(sym)));
}
bool HasEquivalentProcedure(
const IntrinsicProcedureRuntimeDescription &sym) const;
template <template <typename> typename ConstantContainer, typename TR,
typename... TA>
std::optional<HostProcedureWrapper<ConstantContainer, TR, TA...>>
GetHostProcedureWrapper(const std::string &name) const;
private:
std::multimap<std::string, const HostRuntimeIntrinsicProcedure> procedures_;
};
} // namespace Fortran::evaluate
#endif // FORTRAN_EVALUATE_INTRINSICS_LIBRARY_H_

View File

@ -0,0 +1,88 @@
//===-- include/flang/Evaluate/intrinsics.h ---------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_EVALUATE_INTRINSICS_H_
#define FORTRAN_EVALUATE_INTRINSICS_H_
#include "call.h"
#include "characteristics.h"
#include "type.h"
#include "flang/Common/default-kinds.h"
#include "flang/Parser/char-block.h"
#include "flang/Parser/message.h"
#include <optional>
#include <string>
namespace llvm {
class raw_ostream;
}
namespace Fortran::evaluate {
class FoldingContext;
// Utility for checking for missing, excess, and duplicated arguments,
// and rearranging the actual arguments into dummy argument order.
bool CheckAndRearrangeArguments(ActualArguments &, parser::ContextualMessages &,
const char *const dummyKeywords[] /* null terminated */,
std::size_t trailingOptionals = 0);
struct CallCharacteristics {
std::string name;
bool isSubroutineCall{false};
};
struct SpecificCall {
SpecificCall(SpecificIntrinsic &&si, ActualArguments &&as)
: specificIntrinsic{std::move(si)}, arguments{std::move(as)} {}
SpecificIntrinsic specificIntrinsic;
ActualArguments arguments;
};
struct SpecificIntrinsicFunctionInterface : public characteristics::Procedure {
SpecificIntrinsicFunctionInterface(
characteristics::Procedure &&p, std::string n, bool isRestrictedSpecific)
: characteristics::Procedure{std::move(p)}, genericName{n},
isRestrictedSpecific{isRestrictedSpecific} {}
std::string genericName;
bool isRestrictedSpecific;
// N.B. If there are multiple arguments, they all have the same type.
// All argument and result types are intrinsic types with default kinds.
};
class IntrinsicProcTable {
private:
class Implementation;
public:
~IntrinsicProcTable();
static IntrinsicProcTable Configure(
const common::IntrinsicTypeDefaultKinds &);
// Check whether a name should be allowed to appear on an INTRINSIC
// statement.
bool IsIntrinsic(const std::string &) const;
// Probe the intrinsics for a match against a specific call.
// On success, the actual arguments are transferred to the result
// in dummy argument order; on failure, the actual arguments remain
// untouched.
std::optional<SpecificCall> Probe(
const CallCharacteristics &, ActualArguments &, FoldingContext &) const;
// Probe the intrinsics with the name of a potential specific intrinsic.
std::optional<SpecificIntrinsicFunctionInterface> IsSpecificIntrinsicFunction(
const std::string &) const;
llvm::raw_ostream &Dump(llvm::raw_ostream &) const;
private:
Implementation *impl_{nullptr}; // owning pointer
};
} // namespace Fortran::evaluate
#endif // FORTRAN_EVALUATE_INTRINSICS_H_

View File

@ -0,0 +1,103 @@
//===-- include/flang/Evaluate/logical.h ------------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_EVALUATE_LOGICAL_H_
#define FORTRAN_EVALUATE_LOGICAL_H_
#include "integer.h"
#include <cinttypes>
namespace Fortran::evaluate::value {
template <int BITS, bool IS_LIKE_C = true> class Logical {
public:
static constexpr int bits{BITS};
// Module ISO_C_BINDING kind C_BOOL is LOGICAL(KIND=1) and must have
// C's bit representation (.TRUE. -> 1, .FALSE. -> 0).
static constexpr bool IsLikeC{BITS <= 8 || IS_LIKE_C};
constexpr Logical() {} // .FALSE.
template <int B, bool C>
constexpr Logical(Logical<B, C> x) : word_{Represent(x.IsTrue())} {}
constexpr Logical(bool truth) : word_{Represent(truth)} {}
template <int B, bool C> constexpr Logical &operator=(Logical<B, C> x) {
word_ = Represent(x.IsTrue());
}
// Fortran actually has only .EQV. & .NEQV. relational operations
// for LOGICAL, but this template class supports more so that
// it can be used with the STL for sorting and as a key type for
// std::set<> & std::map<>.
template <int B, bool C>
constexpr bool operator<(const Logical<B, C> &that) const {
return !IsTrue() && that.IsTrue();
}
template <int B, bool C>
constexpr bool operator<=(const Logical<B, C> &) const {
return !IsTrue();
}
template <int B, bool C>
constexpr bool operator==(const Logical<B, C> &that) const {
return IsTrue() == that.IsTrue();
}
template <int B, bool C>
constexpr bool operator!=(const Logical<B, C> &that) const {
return IsTrue() != that.IsTrue();
}
template <int B, bool C>
constexpr bool operator>=(const Logical<B, C> &) const {
return IsTrue();
}
template <int B, bool C>
constexpr bool operator>(const Logical<B, C> &that) const {
return IsTrue() && !that.IsTrue();
}
constexpr bool IsTrue() const {
if constexpr (IsLikeC) {
return !word_.IsZero();
} else {
return word_.BTEST(0);
}
}
constexpr Logical NOT() const { return {word_.IEOR(canonicalTrue)}; }
constexpr Logical AND(const Logical &that) const {
return {word_.IAND(that.word_)};
}
constexpr Logical OR(const Logical &that) const {
return {word_.IOR(that.word_)};
}
constexpr Logical EQV(const Logical &that) const { return NEQV(that).NOT(); }
constexpr Logical NEQV(const Logical &that) const {
return {word_.IEOR(that.word_)};
}
private:
using Word = Integer<bits>;
static constexpr Word canonicalTrue{IsLikeC ? -std::uint64_t{1} : 1};
static constexpr Word canonicalFalse{0};
static constexpr Word Represent(bool x) {
return x ? canonicalTrue : canonicalFalse;
}
constexpr Logical(const Word &w) : word_{w} {}
Word word_;
};
extern template class Logical<8>;
extern template class Logical<16>;
extern template class Logical<32>;
extern template class Logical<64>;
} // namespace Fortran::evaluate::value
#endif // FORTRAN_EVALUATE_LOGICAL_H_

View File

@ -0,0 +1,376 @@
//===-- include/flang/Evaluate/real.h ---------------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_EVALUATE_REAL_H_
#define FORTRAN_EVALUATE_REAL_H_
#include "formatting.h"
#include "integer.h"
#include "rounding-bits.h"
#include "flang/Common/real.h"
#include "flang/Evaluate/common.h"
#include <cinttypes>
#include <limits>
#include <string>
// Some environments, viz. clang on Darwin, allow the macro HUGE
// to leak out of <math.h> even when it is never directly included.
#undef HUGE
namespace llvm {
class raw_ostream;
}
namespace Fortran::evaluate::value {
// LOG10(2.)*1E12
static constexpr std::int64_t ScaledLogBaseTenOfTwo{301029995664};
// Models IEEE binary floating-point numbers (IEEE 754-2008,
// ISO/IEC/IEEE 60559.2011). The first argument to this
// class template must be (or look like) an instance of Integer<>;
// the second specifies the number of effective bits (binary precision)
// in the fraction.
template <typename WORD, int PREC>
class Real : public common::RealDetails<PREC> {
public:
using Word = WORD;
static constexpr int binaryPrecision{PREC};
using Details = common::RealDetails<PREC>;
using Details::exponentBias;
using Details::exponentBits;
using Details::isImplicitMSB;
using Details::maxExponent;
using Details::significandBits;
static constexpr int bits{Word::bits};
static_assert(bits >= Details::bits);
using Fraction = Integer<binaryPrecision>; // all bits made explicit
template <typename W, int P> friend class Real;
constexpr Real() {} // +0.0
constexpr Real(const Real &) = default;
constexpr Real(const Word &bits) : word_{bits} {}
constexpr Real &operator=(const Real &) = default;
constexpr Real &operator=(Real &&) = default;
constexpr bool operator==(const Real &that) const {
return word_ == that.word_;
}
// TODO: DIM, MAX, MIN, DPROD, FRACTION,
// INT/NINT, NEAREST, OUT_OF_RANGE,
// RRSPACING/SPACING, SCALE, SET_EXPONENT
constexpr bool IsSignBitSet() const { return word_.BTEST(bits - 1); }
constexpr bool IsNegative() const {
return !IsNotANumber() && IsSignBitSet();
}
constexpr bool IsNotANumber() const {
return Exponent() == maxExponent && !GetSignificand().IsZero();
}
constexpr bool IsQuietNaN() const {
return Exponent() == maxExponent &&
GetSignificand().BTEST(significandBits - 1);
}
constexpr bool IsSignalingNaN() const {
return IsNotANumber() && !GetSignificand().BTEST(significandBits - 1);
}
constexpr bool IsInfinite() const {
return Exponent() == maxExponent && GetSignificand().IsZero();
}
constexpr bool IsZero() const {
return Exponent() == 0 && GetSignificand().IsZero();
}
constexpr bool IsSubnormal() const {
return Exponent() == 0 && !GetSignificand().IsZero();
}
constexpr Real ABS() const { // non-arithmetic, no flags returned
return {word_.IBCLR(bits - 1)};
}
constexpr Real SetSign(bool toNegative) const { // non-arithmetic
if (toNegative) {
return {word_.IBSET(bits - 1)};
} else {
return ABS();
}
}
constexpr Real SIGN(const Real &x) const { return SetSign(x.IsSignBitSet()); }
constexpr Real Negate() const { return {word_.IEOR(word_.MASKL(1))}; }
Relation Compare(const Real &) const;
ValueWithRealFlags<Real> Add(
const Real &, Rounding rounding = defaultRounding) const;
ValueWithRealFlags<Real> Subtract(
const Real &y, Rounding rounding = defaultRounding) const {
return Add(y.Negate(), rounding);
}
ValueWithRealFlags<Real> Multiply(
const Real &, Rounding rounding = defaultRounding) const;
ValueWithRealFlags<Real> Divide(
const Real &, Rounding rounding = defaultRounding) const;
// SQRT(x**2 + y**2) but computed so as to avoid spurious overflow
// TODO: needed for CABS
ValueWithRealFlags<Real> HYPOT(
const Real &, Rounding rounding = defaultRounding) const;
template <typename INT> constexpr INT EXPONENT() const {
if (Exponent() == maxExponent) {
return INT::HUGE();
} else {
return {UnbiasedExponent()};
}
}
static constexpr Real EPSILON() {
Real epsilon;
epsilon.Normalize(
false, exponentBias - binaryPrecision, Fraction::MASKL(1));
return epsilon;
}
static constexpr Real HUGE() {
Real huge;
huge.Normalize(false, maxExponent - 1, Fraction::MASKR(binaryPrecision));
return huge;
}
static constexpr Real TINY() {
Real tiny;
tiny.Normalize(false, 1, Fraction::MASKL(1)); // minimum *normal* number
return tiny;
}
static constexpr int DIGITS{binaryPrecision};
static constexpr int PRECISION{Details::decimalPrecision};
static constexpr int RANGE{Details::decimalRange};
static constexpr int MAXEXPONENT{maxExponent - 1 - exponentBias};
static constexpr int MINEXPONENT{1 - exponentBias};
constexpr Real FlushSubnormalToZero() const {
if (IsSubnormal()) {
return Real{};
}
return *this;
}
// TODO: Configurable NotANumber representations
static constexpr Real NotANumber() {
return {Word{maxExponent}
.SHIFTL(significandBits)
.IBSET(significandBits - 1)
.IBSET(significandBits - 2)};
}
static constexpr Real Infinity(bool negative) {
Word infinity{maxExponent};
infinity = infinity.SHIFTL(significandBits);
if (negative) {
infinity = infinity.IBSET(infinity.bits - 1);
}
return {infinity};
}
template <typename INT>
static ValueWithRealFlags<Real> FromInteger(
const INT &n, Rounding rounding = defaultRounding) {
bool isNegative{n.IsNegative()};
INT absN{n};
if (isNegative) {
absN = n.Negate().value; // overflow is safe to ignore
}
int leadz{absN.LEADZ()};
if (leadz >= absN.bits) {
return {}; // all bits zero -> +0.0
}
ValueWithRealFlags<Real> result;
int exponent{exponentBias + absN.bits - leadz - 1};
int bitsNeeded{absN.bits - (leadz + isImplicitMSB)};
int bitsLost{bitsNeeded - significandBits};
if (bitsLost <= 0) {
Fraction fraction{Fraction::ConvertUnsigned(absN).value};
result.flags |= result.value.Normalize(
isNegative, exponent, fraction.SHIFTL(-bitsLost));
} else {
Fraction fraction{Fraction::ConvertUnsigned(absN.SHIFTR(bitsLost)).value};
result.flags |= result.value.Normalize(isNegative, exponent, fraction);
RoundingBits roundingBits{absN, bitsLost};
result.flags |= result.value.Round(rounding, roundingBits);
}
return result;
}
// Conversion to integer in the same real format (AINT(), ANINT())
ValueWithRealFlags<Real> ToWholeNumber(
common::RoundingMode = common::RoundingMode::ToZero) const;
// Conversion to an integer (INT(), NINT(), FLOOR(), CEILING())
template <typename INT>
constexpr ValueWithRealFlags<INT> ToInteger(
common::RoundingMode mode = common::RoundingMode::ToZero) const {
ValueWithRealFlags<INT> result;
if (IsNotANumber()) {
result.flags.set(RealFlag::InvalidArgument);
result.value = result.value.HUGE();
return result;
}
ValueWithRealFlags<Real> intPart{ToWholeNumber(mode)};
int exponent{intPart.value.Exponent()};
result.flags.set(
RealFlag::Overflow, exponent >= exponentBias + result.value.bits);
result.flags |= intPart.flags;
int shift{
exponent - exponentBias - binaryPrecision + 1}; // positive -> left
result.value =
result.value.ConvertUnsigned(intPart.value.GetFraction().SHIFTR(-shift))
.value.SHIFTL(shift);
if (IsSignBitSet()) {
auto negated{result.value.Negate()};
result.value = negated.value;
if (negated.overflow) {
result.flags.set(RealFlag::Overflow);
}
}
if (result.flags.test(RealFlag::Overflow)) {
result.value =
IsSignBitSet() ? result.value.MASKL(1) : result.value.HUGE();
}
return result;
}
template <typename A>
static ValueWithRealFlags<Real> Convert(
const A &x, Rounding rounding = defaultRounding) {
bool isNegative{x.IsNegative()};
A absX{x};
if (isNegative) {
absX = x.Negate();
}
ValueWithRealFlags<Real> result;
int exponent{exponentBias + x.UnbiasedExponent()};
int bitsLost{A::binaryPrecision - binaryPrecision};
if (exponent < 1) {
bitsLost += 1 - exponent;
exponent = 1;
}
typename A::Fraction xFraction{x.GetFraction()};
if (bitsLost <= 0) {
Fraction fraction{
Fraction::ConvertUnsigned(xFraction).value.SHIFTL(-bitsLost)};
result.flags |= result.value.Normalize(isNegative, exponent, fraction);
} else {
Fraction fraction{
Fraction::ConvertUnsigned(xFraction.SHIFTR(bitsLost)).value};
result.flags |= result.value.Normalize(isNegative, exponent, fraction);
RoundingBits roundingBits{xFraction, bitsLost};
result.flags |= result.value.Round(rounding, roundingBits);
}
return result;
}
constexpr Word RawBits() const { return word_; }
// Extracts "raw" biased exponent field.
constexpr int Exponent() const {
return word_.IBITS(significandBits, exponentBits).ToUInt64();
}
// Extracts the fraction; any implied bit is made explicit.
constexpr Fraction GetFraction() const {
Fraction result{Fraction::ConvertUnsigned(word_).value};
if constexpr (!isImplicitMSB) {
return result;
} else {
int exponent{Exponent()};
if (exponent > 0 && exponent < maxExponent) {
return result.IBSET(significandBits);
} else {
return result.IBCLR(significandBits);
}
}
}
// Extracts unbiased exponent value.
// Corrects the exponent value of a subnormal number.
constexpr int UnbiasedExponent() const {
int exponent{Exponent() - exponentBias};
if (IsSubnormal()) {
++exponent;
}
return exponent;
}
static ValueWithRealFlags<Real> Read(
const char *&, Rounding rounding = defaultRounding);
std::string DumpHexadecimal() const;
// Emits a character representation for an equivalent Fortran constant
// or parenthesized constant expression that produces this value.
llvm::raw_ostream &AsFortran(
llvm::raw_ostream &, int kind, bool minimal = false) const;
private:
using Significand = Integer<significandBits>; // no implicit bit
constexpr Significand GetSignificand() const {
return Significand::ConvertUnsigned(word_).value;
}
constexpr int CombineExponents(const Real &y, bool forDivide) const {
int exponent = Exponent(), yExponent = y.Exponent();
// A zero exponent field value has the same weight as 1.
exponent += !exponent;
yExponent += !yExponent;
if (forDivide) {
exponent += exponentBias - yExponent;
} else {
exponent += yExponent - exponentBias + 1;
}
return exponent;
}
static constexpr bool NextQuotientBit(
Fraction &top, bool &msb, const Fraction &divisor) {
bool greaterOrEqual{msb || top.CompareUnsigned(divisor) != Ordering::Less};
if (greaterOrEqual) {
top = top.SubtractSigned(divisor).value;
}
auto doubled{top.AddUnsigned(top)};
top = doubled.value;
msb = doubled.carry;
return greaterOrEqual;
}
// Normalizes and marshals the fields of a floating-point number in place.
// The value is a number, and a zero fraction means a zero value (i.e.,
// a maximal exponent and zero fraction doesn't signify infinity, although
// this member function will detect overflow and encode infinities).
RealFlags Normalize(bool negative, int exponent, const Fraction &fraction,
Rounding rounding = defaultRounding,
RoundingBits *roundingBits = nullptr);
// Rounds a result, if necessary, in place.
RealFlags Round(Rounding, const RoundingBits &, bool multiply = false);
static void NormalizeAndRound(ValueWithRealFlags<Real> &result,
bool isNegative, int exponent, const Fraction &, Rounding, RoundingBits,
bool multiply = false);
Word word_{}; // an Integer<>
};
extern template class Real<Integer<16>, 11>; // IEEE half format
extern template class Real<Integer<16>, 8>; // the "other" half format
extern template class Real<Integer<32>, 24>; // IEEE single
extern template class Real<Integer<64>, 53>; // IEEE double
extern template class Real<Integer<80>, 64>; // 80387 extended precision
extern template class Real<Integer<128>, 113>; // IEEE quad
// N.B. No "double-double" support.
} // namespace Fortran::evaluate::value
#endif // FORTRAN_EVALUATE_REAL_H_

View File

@ -0,0 +1,105 @@
//===-- include/flang/Evaluate/rounding-bits.h ------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_EVALUATE_ROUNDING_BITS_H_
#define FORTRAN_EVALUATE_ROUNDING_BITS_H_
// A helper class used by Real<> to determine rounding of rational results
// to floating-point values. Bits lost from intermediate computations by
// being shifted rightward are accumulated in instances of this class.
namespace Fortran::evaluate::value {
class RoundingBits {
public:
constexpr RoundingBits(
bool guard = false, bool round = false, bool sticky = false)
: guard_{guard}, round_{round}, sticky_{sticky} {}
template <typename FRACTION>
constexpr RoundingBits(const FRACTION &fraction, int rshift) {
if (rshift > 0 && rshift < fraction.bits + 1) {
guard_ = fraction.BTEST(rshift - 1);
}
if (rshift > 1 && rshift < fraction.bits + 2) {
round_ = fraction.BTEST(rshift - 2);
}
if (rshift > 2) {
if (rshift >= fraction.bits + 2) {
sticky_ = !fraction.IsZero();
} else {
auto mask{fraction.MASKR(rshift - 2)};
sticky_ = !fraction.IAND(mask).IsZero();
}
}
}
constexpr bool guard() const { return guard_; }
constexpr bool round() const { return round_; }
constexpr bool sticky() const { return sticky_; }
constexpr bool empty() const { return !(guard_ | round_ | sticky_); }
constexpr bool Negate() {
bool carry{!sticky_};
if (carry) {
carry = !round_;
} else {
round_ = !round_;
}
if (carry) {
carry = !guard_;
} else {
guard_ = !guard_;
}
return carry;
}
constexpr bool ShiftLeft() {
bool oldGuard{guard_};
guard_ = round_;
round_ = sticky_;
return oldGuard;
}
constexpr void ShiftRight(bool newGuard) {
sticky_ |= round_;
round_ = guard_;
guard_ = newGuard;
}
// Determines whether a value should be rounded by increasing its
// fraction, given a rounding mode and a summary of the lost bits.
constexpr bool MustRound(
Rounding rounding, bool isNegative, bool isOdd) const {
bool round{false}; // to dodge bogus g++ warning about missing return
switch (rounding.mode) {
case common::RoundingMode::TiesToEven:
round = guard_ && (round_ | sticky_ | isOdd);
break;
case common::RoundingMode::ToZero:
break;
case common::RoundingMode::Down:
round = isNegative && !empty();
break;
case common::RoundingMode::Up:
round = !isNegative && !empty();
break;
case common::RoundingMode::TiesAwayFromZero:
round = guard_;
break;
}
return round;
}
private:
bool guard_{false}; // 0.5 * ulp (unit in lowest place)
bool round_{false}; // 0.25 * ulp
bool sticky_{false}; // true if any lesser-valued bit would be set
};
} // namespace Fortran::evaluate::value
#endif // FORTRAN_EVALUATE_ROUNDING_BITS_H_

View File

@ -0,0 +1,192 @@
//===-- include/flang/Evaluate/shape.h --------------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
// GetShape() analyzes an expression and determines its shape, if possible,
// representing the result as a vector of scalar integer expressions.
#ifndef FORTRAN_EVALUATE_SHAPE_H_
#define FORTRAN_EVALUATE_SHAPE_H_
#include "expression.h"
#include "traverse.h"
#include "variable.h"
#include "flang/Common/indirection.h"
#include "flang/Evaluate/tools.h"
#include "flang/Evaluate/type.h"
#include <optional>
#include <variant>
namespace Fortran::parser {
class ContextualMessages;
}
namespace Fortran::evaluate {
class FoldingContext;
using ExtentType = SubscriptInteger;
using ExtentExpr = Expr<ExtentType>;
using MaybeExtentExpr = std::optional<ExtentExpr>;
using Shape = std::vector<MaybeExtentExpr>;
bool IsImpliedShape(const Symbol &);
bool IsExplicitShape(const Symbol &);
// Conversions between various representations of shapes.
Shape AsShape(const Constant<ExtentType> &);
std::optional<Shape> AsShape(FoldingContext &, ExtentExpr &&);
std::optional<ExtentExpr> AsExtentArrayExpr(const Shape &);
std::optional<Constant<ExtentType>> AsConstantShape(
FoldingContext &, const Shape &);
Constant<ExtentType> AsConstantShape(const ConstantSubscripts &);
ConstantSubscripts AsConstantExtents(const Constant<ExtentType> &);
std::optional<ConstantSubscripts> AsConstantExtents(
FoldingContext &, const Shape &);
inline int GetRank(const Shape &s) { return static_cast<int>(s.size()); }
template <typename A>
std::optional<Shape> GetShape(FoldingContext &, const A &);
// The dimension argument to these inquiries is zero-based,
// unlike the DIM= arguments to many intrinsics.
ExtentExpr GetLowerBound(FoldingContext &, const NamedEntity &, int dimension);
MaybeExtentExpr GetUpperBound(
FoldingContext &, const NamedEntity &, int dimension);
MaybeExtentExpr ComputeUpperBound(
FoldingContext &, ExtentExpr &&lower, MaybeExtentExpr &&extent);
Shape GetLowerBounds(FoldingContext &, const NamedEntity &);
Shape GetUpperBounds(FoldingContext &, const NamedEntity &);
MaybeExtentExpr GetExtent(FoldingContext &, const NamedEntity &, int dimension);
MaybeExtentExpr GetExtent(
FoldingContext &, const Subscript &, const NamedEntity &, int dimension);
// Compute an element count for a triplet or trip count for a DO.
ExtentExpr CountTrips(FoldingContext &, ExtentExpr &&lower, ExtentExpr &&upper,
ExtentExpr &&stride);
ExtentExpr CountTrips(FoldingContext &, const ExtentExpr &lower,
const ExtentExpr &upper, const ExtentExpr &stride);
MaybeExtentExpr CountTrips(FoldingContext &, MaybeExtentExpr &&lower,
MaybeExtentExpr &&upper, MaybeExtentExpr &&stride);
// Computes SIZE() == PRODUCT(shape)
MaybeExtentExpr GetSize(Shape &&);
// Utility predicate: does an expression reference any implied DO index?
bool ContainsAnyImpliedDoIndex(const ExtentExpr &);
class GetShapeHelper
: public AnyTraverse<GetShapeHelper, std::optional<Shape>> {
public:
using Result = std::optional<Shape>;
using Base = AnyTraverse<GetShapeHelper, Result>;
using Base::operator();
explicit GetShapeHelper(FoldingContext &c) : Base{*this}, context_{c} {}
Result operator()(const ImpliedDoIndex &) const { return Scalar(); }
Result operator()(const DescriptorInquiry &) const { return Scalar(); }
template <int KIND> Result operator()(const TypeParamInquiry<KIND> &) const {
return Scalar();
}
Result operator()(const BOZLiteralConstant &) const { return Scalar(); }
Result operator()(const StaticDataObject::Pointer &) const {
return Scalar();
}
Result operator()(const StructureConstructor &) const { return Scalar(); }
template <typename T> Result operator()(const Constant<T> &c) const {
return AsShape(c.SHAPE());
}
Result operator()(const Symbol &) const;
Result operator()(const Component &) const;
Result operator()(const ArrayRef &) const;
Result operator()(const CoarrayRef &) const;
Result operator()(const Substring &) const;
Result operator()(const ProcedureRef &) const;
template <typename T>
Result operator()(const ArrayConstructor<T> &aconst) const {
return Shape{GetArrayConstructorExtent(aconst)};
}
template <typename D, typename R, typename LO, typename RO>
Result operator()(const Operation<D, R, LO, RO> &operation) const {
if (operation.right().Rank() > 0) {
return (*this)(operation.right());
} else {
return (*this)(operation.left());
}
}
private:
static Result Scalar() { return Shape{}; }
template <typename T>
MaybeExtentExpr GetArrayConstructorValueExtent(
const ArrayConstructorValue<T> &value) const {
return std::visit(
common::visitors{
[&](const Expr<T> &x) -> MaybeExtentExpr {
if (std::optional<Shape> xShape{GetShape(context_, x)}) {
// Array values in array constructors get linearized.
return GetSize(std::move(*xShape));
} else {
return std::nullopt;
}
},
[&](const ImpliedDo<T> &ido) -> MaybeExtentExpr {
// Don't be heroic and try to figure out triangular implied DO
// nests.
if (!ContainsAnyImpliedDoIndex(ido.lower()) &&
!ContainsAnyImpliedDoIndex(ido.upper()) &&
!ContainsAnyImpliedDoIndex(ido.stride())) {
if (auto nValues{GetArrayConstructorExtent(ido.values())}) {
return std::move(*nValues) *
CountTrips(
context_, ido.lower(), ido.upper(), ido.stride());
}
}
return std::nullopt;
},
},
value.u);
}
template <typename T>
MaybeExtentExpr GetArrayConstructorExtent(
const ArrayConstructorValues<T> &values) const {
ExtentExpr result{0};
for (const auto &value : values) {
if (MaybeExtentExpr n{GetArrayConstructorValueExtent(value)}) {
result = std::move(result) + std::move(*n);
} else {
return std::nullopt;
}
}
return result;
}
FoldingContext &context_;
};
template <typename A>
std::optional<Shape> GetShape(FoldingContext &context, const A &x) {
return GetShapeHelper{context}(x);
}
// Compilation-time shape conformance checking, when corresponding extents
// are known.
bool CheckConformance(parser::ContextualMessages &, const Shape &left,
const Shape &right, const char *leftIs = "left operand",
const char *rightIs = "right operand");
} // namespace Fortran::evaluate
#endif // FORTRAN_EVALUATE_SHAPE_H_

View File

@ -0,0 +1,82 @@
//===-- include/flang/Evaluate/static-data.h --------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_EVALUATE_STATIC_DATA_H_
#define FORTRAN_EVALUATE_STATIC_DATA_H_
// Represents constant static data objects
#include "formatting.h"
#include "type.h"
#include "flang/Common/idioms.h"
#include <cinttypes>
#include <memory>
#include <optional>
#include <string>
#include <vector>
namespace llvm {
class raw_ostream;
}
namespace Fortran::evaluate {
class StaticDataObject {
public:
using Pointer = std::shared_ptr<StaticDataObject>;
StaticDataObject(const StaticDataObject &) = delete;
StaticDataObject(StaticDataObject &&) = delete;
StaticDataObject &operator=(const StaticDataObject &) = delete;
StaticDataObject &operator=(StaticDataObject &&) = delete;
static Pointer Create() { return Pointer{new StaticDataObject}; }
const std::string &name() const { return name_; }
StaticDataObject &set_name(std::string n) {
name_ = n;
return *this;
}
int alignment() const { return alignment_; }
StaticDataObject &set_alignment(int a) {
CHECK(a >= 0);
alignment_ = a;
return *this;
}
int itemBytes() const { return itemBytes_; }
StaticDataObject &set_itemBytes(int b) {
CHECK(b >= 1);
itemBytes_ = b;
return *this;
}
const std::vector<std::uint8_t> &data() const { return data_; }
std::vector<std::uint8_t> &data() { return data_; }
StaticDataObject &Push(const std::string &);
StaticDataObject &Push(const std::u16string &);
StaticDataObject &Push(const std::u32string &);
std::optional<std::string> AsString() const;
std::optional<std::u16string> AsU16String() const;
std::optional<std::u32string> AsU32String() const;
llvm::raw_ostream &AsFortran(llvm::raw_ostream &) const;
static bool bigEndian;
private:
StaticDataObject() {}
std::string name_;
int alignment_{1};
int itemBytes_{1};
std::vector<std::uint8_t> data_;
};
} // namespace Fortran::evaluate
#endif // FORTRAN_EVALUATE_STATIC_DATA_H_

View File

@ -0,0 +1,868 @@
//===-- include/flang/Evaluate/tools.h --------------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_EVALUATE_TOOLS_H_
#define FORTRAN_EVALUATE_TOOLS_H_
#include "traverse.h"
#include "flang/Common/idioms.h"
#include "flang/Common/template.h"
#include "flang/Common/unwrap.h"
#include "flang/Evaluate/constant.h"
#include "flang/Evaluate/expression.h"
#include "flang/Parser/message.h"
#include "flang/Semantics/attr.h"
#include "flang/Semantics/symbol.h"
#include <array>
#include <optional>
#include <set>
#include <type_traits>
#include <utility>
namespace Fortran::evaluate {
// Some expression predicates and extractors.
// When an Expr holds something that is a Variable (i.e., a Designator
// or pointer-valued FunctionRef), return a copy of its contents in
// a Variable.
template <typename A>
std::optional<Variable<A>> AsVariable(const Expr<A> &expr) {
using Variant = decltype(Variable<A>::u);
return std::visit(
[](const auto &x) -> std::optional<Variable<A>> {
if constexpr (common::HasMember<std::decay_t<decltype(x)>, Variant>) {
return Variable<A>{x};
}
return std::nullopt;
},
expr.u);
}
template <typename A>
std::optional<Variable<A>> AsVariable(const std::optional<Expr<A>> &expr) {
if (expr) {
return AsVariable(*expr);
} else {
return std::nullopt;
}
}
// Predicate: true when an expression is a variable reference, not an
// operation. Be advised: a call to a function that returns an object
// pointer is a "variable" in Fortran (it can be the left-hand side of
// an assignment).
struct IsVariableHelper
: public AnyTraverse<IsVariableHelper, std::optional<bool>> {
using Result = std::optional<bool>; // effectively tri-state
using Base = AnyTraverse<IsVariableHelper, Result>;
IsVariableHelper() : Base{*this} {}
using Base::operator();
Result operator()(const StaticDataObject &) const { return false; }
Result operator()(const Symbol &) const;
Result operator()(const Component &) const;
Result operator()(const ArrayRef &) const;
Result operator()(const Substring &) const;
Result operator()(const CoarrayRef &) const { return true; }
Result operator()(const ComplexPart &) const { return true; }
Result operator()(const ProcedureDesignator &) const;
template <typename T> Result operator()(const Expr<T> &x) const {
if constexpr (common::HasMember<T, AllIntrinsicTypes> ||
std::is_same_v<T, SomeDerived>) {
// Expression with a specific type
if (std::holds_alternative<Designator<T>>(x.u) ||
std::holds_alternative<FunctionRef<T>>(x.u)) {
if (auto known{(*this)(x.u)}) {
return known;
}
}
return false;
} else {
return (*this)(x.u);
}
}
};
template <typename A> bool IsVariable(const A &x) {
if (auto known{IsVariableHelper{}(x)}) {
return *known;
} else {
return false;
}
}
// Predicate: true when an expression is assumed-rank
bool IsAssumedRank(const Symbol &);
bool IsAssumedRank(const ActualArgument &);
template <typename A> bool IsAssumedRank(const A &) { return false; }
template <typename A> bool IsAssumedRank(const Designator<A> &designator) {
if (const auto *symbol{std::get_if<SymbolRef>(&designator.u)}) {
return IsAssumedRank(symbol->get());
} else {
return false;
}
}
template <typename T> bool IsAssumedRank(const Expr<T> &expr) {
return std::visit([](const auto &x) { return IsAssumedRank(x); }, expr.u);
}
template <typename A> bool IsAssumedRank(const std::optional<A> &x) {
return x && IsAssumedRank(*x);
}
// Generalizing packagers: these take operations and expressions of more
// specific types and wrap them in Expr<> containers of more abstract types.
template <typename A> common::IfNoLvalue<Expr<ResultType<A>>, A> AsExpr(A &&x) {
return Expr<ResultType<A>>{std::move(x)};
}
template <typename T> Expr<T> AsExpr(Expr<T> &&x) {
static_assert(IsSpecificIntrinsicType<T>);
return std::move(x);
}
template <TypeCategory CATEGORY>
Expr<SomeKind<CATEGORY>> AsCategoryExpr(Expr<SomeKind<CATEGORY>> &&x) {
return std::move(x);
}
template <typename A>
common::IfNoLvalue<Expr<SomeType>, A> AsGenericExpr(A &&x) {
if constexpr (common::HasMember<A, TypelessExpression>) {
return Expr<SomeType>{std::move(x)};
} else {
return Expr<SomeType>{AsCategoryExpr(std::move(x))};
}
}
template <typename A>
common::IfNoLvalue<Expr<SomeKind<ResultType<A>::category>>, A> AsCategoryExpr(
A &&x) {
return Expr<SomeKind<ResultType<A>::category>>{AsExpr(std::move(x))};
}
inline Expr<SomeType> AsGenericExpr(Expr<SomeType> &&x) { return std::move(x); }
Expr<SomeType> Parenthesize(Expr<SomeType> &&);
Expr<SomeReal> GetComplexPart(
const Expr<SomeComplex> &, bool isImaginary = false);
template <int KIND>
Expr<SomeComplex> MakeComplex(Expr<Type<TypeCategory::Real, KIND>> &&re,
Expr<Type<TypeCategory::Real, KIND>> &&im) {
return AsCategoryExpr(ComplexConstructor<KIND>{std::move(re), std::move(im)});
}
template <typename A> constexpr bool IsNumericCategoryExpr() {
if constexpr (common::HasMember<A, TypelessExpression>) {
return false;
} else {
return common::HasMember<ResultType<A>, NumericCategoryTypes>;
}
}
// Specializing extractor. If an Expr wraps some type of object, perhaps
// in several layers, return a pointer to it; otherwise null. Also works
// with expressions contained in ActualArgument.
template <typename A, typename B>
auto UnwrapExpr(B &x) -> common::Constify<A, B> * {
using Ty = std::decay_t<B>;
if constexpr (std::is_same_v<A, Ty>) {
return &x;
} else if constexpr (std::is_same_v<Ty, ActualArgument>) {
if (auto *expr{x.UnwrapExpr()}) {
return UnwrapExpr<A>(*expr);
}
} else if constexpr (std::is_same_v<Ty, Expr<SomeType>>) {
return std::visit([](auto &x) { return UnwrapExpr<A>(x); }, x.u);
} else if constexpr (!common::HasMember<A, TypelessExpression>) {
if constexpr (std::is_same_v<Ty, Expr<ResultType<A>>> ||
std::is_same_v<Ty, Expr<SomeKind<ResultType<A>::category>>>) {
return std::visit([](auto &x) { return UnwrapExpr<A>(x); }, x.u);
}
}
return nullptr;
}
template <typename A, typename B>
const A *UnwrapExpr(const std::optional<B> &x) {
if (x) {
return UnwrapExpr<A>(*x);
} else {
return nullptr;
}
}
template <typename A, typename B> A *UnwrapExpr(std::optional<B> &x) {
if (x) {
return UnwrapExpr<A>(*x);
} else {
return nullptr;
}
}
// If an expression simply wraps a DataRef, extract and return it.
// The Boolean argument controls the handling of Substring
// references: when true (not default), it extracts the base DataRef
// of a substring, if it has one.
template <typename A>
common::IfNoLvalue<std::optional<DataRef>, A> ExtractDataRef(
const A &, bool intoSubstring) {
return std::nullopt; // default base case
}
template <typename T>
std::optional<DataRef> ExtractDataRef(
const Designator<T> &d, bool intoSubstring = false) {
return std::visit(
[=](const auto &x) -> std::optional<DataRef> {
if constexpr (common::HasMember<decltype(x), decltype(DataRef::u)>) {
return DataRef{x};
}
if constexpr (std::is_same_v<std::decay_t<decltype(x)>, Substring>) {
if (intoSubstring) {
return ExtractSubstringBase(x);
}
}
return std::nullopt; // w/o "else" to dodge bogus g++ 8.1 warning
},
d.u);
}
template <typename T>
std::optional<DataRef> ExtractDataRef(
const Expr<T> &expr, bool intoSubstring = false) {
return std::visit(
[=](const auto &x) { return ExtractDataRef(x, intoSubstring); }, expr.u);
}
template <typename A>
std::optional<DataRef> ExtractDataRef(
const std::optional<A> &x, bool intoSubstring = false) {
if (x) {
return ExtractDataRef(*x, intoSubstring);
} else {
return std::nullopt;
}
}
std::optional<DataRef> ExtractSubstringBase(const Substring &);
// Predicate: is an expression is an array element reference?
template <typename T>
bool IsArrayElement(const Expr<T> &expr, bool intoSubstring = false) {
if (auto dataRef{ExtractDataRef(expr, intoSubstring)}) {
const DataRef *ref{&*dataRef};
while (const Component * component{std::get_if<Component>(&ref->u)}) {
ref = &component->base();
}
return std::holds_alternative<ArrayRef>(ref->u);
} else {
return false;
}
}
template <typename A>
std::optional<NamedEntity> ExtractNamedEntity(const A &x) {
if (auto dataRef{ExtractDataRef(x, true)}) {
return std::visit(
common::visitors{
[](SymbolRef &&symbol) -> std::optional<NamedEntity> {
return NamedEntity{symbol};
},
[](Component &&component) -> std::optional<NamedEntity> {
return NamedEntity{std::move(component)};
},
[](CoarrayRef &&co) -> std::optional<NamedEntity> {
return co.GetBase();
},
[](auto &&) { return std::optional<NamedEntity>{}; },
},
std::move(dataRef->u));
} else {
return std::nullopt;
}
}
struct ExtractCoindexedObjectHelper {
template <typename A> std::optional<CoarrayRef> operator()(const A &) const {
return std::nullopt;
}
std::optional<CoarrayRef> operator()(const CoarrayRef &x) const { return x; }
template <typename A>
std::optional<CoarrayRef> operator()(const Expr<A> &expr) const {
return std::visit(*this, expr.u);
}
std::optional<CoarrayRef> operator()(const DataRef &dataRef) const {
return std::visit(*this, dataRef.u);
}
std::optional<CoarrayRef> operator()(const NamedEntity &named) const {
if (const Component * component{named.UnwrapComponent()}) {
return (*this)(*component);
} else {
return std::nullopt;
}
}
std::optional<CoarrayRef> operator()(const ProcedureDesignator &des) const {
if (const auto *component{
std::get_if<common::CopyableIndirection<Component>>(&des.u)}) {
return (*this)(component->value());
} else {
return std::nullopt;
}
}
std::optional<CoarrayRef> operator()(const Component &component) const {
return (*this)(component.base());
}
std::optional<CoarrayRef> operator()(const ArrayRef &arrayRef) const {
return (*this)(arrayRef.base());
}
};
template <typename A> std::optional<CoarrayRef> ExtractCoarrayRef(const A &x) {
if (auto dataRef{ExtractDataRef(x, true)}) {
return ExtractCoindexedObjectHelper{}(*dataRef);
} else {
return ExtractCoindexedObjectHelper{}(x);
}
}
// If an expression is simply a whole symbol data designator,
// extract and return that symbol, else null.
template <typename A> const Symbol *UnwrapWholeSymbolDataRef(const A &x) {
if (auto dataRef{ExtractDataRef(x)}) {
if (const SymbolRef * p{std::get_if<SymbolRef>(&dataRef->u)}) {
return &p->get();
}
}
return nullptr;
}
// GetFirstSymbol(A%B%C[I]%D) -> A
template <typename A> const Symbol *GetFirstSymbol(const A &x) {
if (auto dataRef{ExtractDataRef(x, true)}) {
return &dataRef->GetFirstSymbol();
} else {
return nullptr;
}
}
// Creation of conversion expressions can be done to either a known
// specific intrinsic type with ConvertToType<T>(x) or by converting
// one arbitrary expression to the type of another with ConvertTo(to, from).
template <typename TO, TypeCategory FROMCAT>
Expr<TO> ConvertToType(Expr<SomeKind<FROMCAT>> &&x) {
static_assert(IsSpecificIntrinsicType<TO>);
if constexpr (FROMCAT != TO::category) {
if constexpr (TO::category == TypeCategory::Complex) {
using Part = typename TO::Part;
Scalar<Part> zero;
return Expr<TO>{ComplexConstructor<TO::kind>{
ConvertToType<Part>(std::move(x)), Expr<Part>{Constant<Part>{zero}}}};
} else if constexpr (FROMCAT == TypeCategory::Complex) {
// Extract and convert the real component of a complex value
return std::visit(
[&](auto &&z) {
using ZType = ResultType<decltype(z)>;
using Part = typename ZType::Part;
return ConvertToType<TO, TypeCategory::Real>(Expr<SomeReal>{
Expr<Part>{ComplexComponent<Part::kind>{false, std::move(z)}}});
},
std::move(x.u));
} else {
return Expr<TO>{Convert<TO, FROMCAT>{std::move(x)}};
}
} else {
// Same type category
if (auto *already{std::get_if<Expr<TO>>(&x.u)}) {
return std::move(*already);
}
if constexpr (TO::category == TypeCategory::Complex) {
// Extract, convert, and recombine the components.
return Expr<TO>{std::visit(
[](auto &z) {
using FromType = ResultType<decltype(z)>;
using FromPart = typename FromType::Part;
using FromGeneric = SomeKind<TypeCategory::Real>;
using ToPart = typename TO::Part;
Convert<ToPart, TypeCategory::Real> re{Expr<FromGeneric>{
Expr<FromPart>{ComplexComponent<FromType::kind>{false, z}}}};
Convert<ToPart, TypeCategory::Real> im{Expr<FromGeneric>{
Expr<FromPart>{ComplexComponent<FromType::kind>{true, z}}}};
return ComplexConstructor<TO::kind>{
AsExpr(std::move(re)), AsExpr(std::move(im))};
},
x.u)};
} else {
return Expr<TO>{Convert<TO, TO::category>{std::move(x)}};
}
}
}
template <typename TO, TypeCategory FROMCAT, int FROMKIND>
Expr<TO> ConvertToType(Expr<Type<FROMCAT, FROMKIND>> &&x) {
return ConvertToType<TO, FROMCAT>(Expr<SomeKind<FROMCAT>>{std::move(x)});
}
template <typename TO> Expr<TO> ConvertToType(BOZLiteralConstant &&x) {
static_assert(IsSpecificIntrinsicType<TO>);
if constexpr (TO::category == TypeCategory::Integer) {
return Expr<TO>{
Constant<TO>{Scalar<TO>::ConvertUnsigned(std::move(x)).value}};
} else {
static_assert(TO::category == TypeCategory::Real);
using Word = typename Scalar<TO>::Word;
return Expr<TO>{
Constant<TO>{Scalar<TO>{Word::ConvertUnsigned(std::move(x)).value}}};
}
}
// Conversions to dynamic types
std::optional<Expr<SomeType>> ConvertToType(
const DynamicType &, Expr<SomeType> &&);
std::optional<Expr<SomeType>> ConvertToType(
const DynamicType &, std::optional<Expr<SomeType>> &&);
std::optional<Expr<SomeType>> ConvertToType(const Symbol &, Expr<SomeType> &&);
std::optional<Expr<SomeType>> ConvertToType(
const Symbol &, std::optional<Expr<SomeType>> &&);
// Conversions to the type of another expression
template <TypeCategory TC, int TK, typename FROM>
common::IfNoLvalue<Expr<Type<TC, TK>>, FROM> ConvertTo(
const Expr<Type<TC, TK>> &, FROM &&x) {
return ConvertToType<Type<TC, TK>>(std::move(x));
}
template <TypeCategory TC, typename FROM>
common::IfNoLvalue<Expr<SomeKind<TC>>, FROM> ConvertTo(
const Expr<SomeKind<TC>> &to, FROM &&from) {
return std::visit(
[&](const auto &toKindExpr) {
using KindExpr = std::decay_t<decltype(toKindExpr)>;
return AsCategoryExpr(
ConvertToType<ResultType<KindExpr>>(std::move(from)));
},
to.u);
}
template <typename FROM>
common::IfNoLvalue<Expr<SomeType>, FROM> ConvertTo(
const Expr<SomeType> &to, FROM &&from) {
return std::visit(
[&](const auto &toCatExpr) {
return AsGenericExpr(ConvertTo(toCatExpr, std::move(from)));
},
to.u);
}
// Convert an expression of some known category to a dynamically chosen
// kind of some category (usually but not necessarily distinct).
template <TypeCategory TOCAT, typename VALUE> struct ConvertToKindHelper {
using Result = std::optional<Expr<SomeKind<TOCAT>>>;
using Types = CategoryTypes<TOCAT>;
ConvertToKindHelper(int k, VALUE &&x) : kind{k}, value{std::move(x)} {}
template <typename T> Result Test() {
if (kind == T::kind) {
return std::make_optional(
AsCategoryExpr(ConvertToType<T>(std::move(value))));
}
return std::nullopt;
}
int kind;
VALUE value;
};
template <TypeCategory TOCAT, typename VALUE>
common::IfNoLvalue<Expr<SomeKind<TOCAT>>, VALUE> ConvertToKind(
int kind, VALUE &&x) {
return common::SearchTypes(
ConvertToKindHelper<TOCAT, VALUE>{kind, std::move(x)})
.value();
}
// Given a type category CAT, SameKindExprs<CAT, N> is a variant that
// holds an arrays of expressions of the same supported kind in that
// category.
template <typename A, int N = 2> using SameExprs = std::array<Expr<A>, N>;
template <int N = 2> struct SameKindExprsHelper {
template <typename A> using SameExprs = std::array<Expr<A>, N>;
};
template <TypeCategory CAT, int N = 2>
using SameKindExprs =
common::MapTemplate<SameKindExprsHelper<N>::template SameExprs,
CategoryTypes<CAT>>;
// Given references to two expressions of arbitrary kind in the same type
// category, convert one to the kind of the other when it has the smaller kind,
// then return them in a type-safe package.
template <TypeCategory CAT>
SameKindExprs<CAT, 2> AsSameKindExprs(
Expr<SomeKind<CAT>> &&x, Expr<SomeKind<CAT>> &&y) {
return std::visit(
[&](auto &&kx, auto &&ky) -> SameKindExprs<CAT, 2> {
using XTy = ResultType<decltype(kx)>;
using YTy = ResultType<decltype(ky)>;
if constexpr (std::is_same_v<XTy, YTy>) {
return {SameExprs<XTy>{std::move(kx), std::move(ky)}};
} else if constexpr (XTy::kind < YTy::kind) {
return {SameExprs<YTy>{ConvertTo(ky, std::move(kx)), std::move(ky)}};
} else {
return {SameExprs<XTy>{std::move(kx), ConvertTo(kx, std::move(ky))}};
}
#if !__clang__ && 100 * __GNUC__ + __GNUC_MINOR__ == 801
// Silence a bogus warning about a missing return with G++ 8.1.0.
// Doesn't execute, but must be correctly typed.
CHECK(!"can't happen");
return {SameExprs<XTy>{std::move(kx), std::move(kx)}};
#endif
},
std::move(x.u), std::move(y.u));
}
// Ensure that both operands of an intrinsic REAL operation (or CMPLX()
// constructor) are INTEGER or REAL, then convert them as necessary to the
// same kind of REAL.
using ConvertRealOperandsResult =
std::optional<SameKindExprs<TypeCategory::Real, 2>>;
ConvertRealOperandsResult ConvertRealOperands(parser::ContextualMessages &,
Expr<SomeType> &&, Expr<SomeType> &&, int defaultRealKind);
// Per F'2018 R718, if both components are INTEGER, they are both converted
// to default REAL and the result is default COMPLEX. Otherwise, the
// kind of the result is the kind of most precise REAL component, and the other
// component is converted if necessary to its type.
std::optional<Expr<SomeComplex>> ConstructComplex(parser::ContextualMessages &,
Expr<SomeType> &&, Expr<SomeType> &&, int defaultRealKind);
std::optional<Expr<SomeComplex>> ConstructComplex(parser::ContextualMessages &,
std::optional<Expr<SomeType>> &&, std::optional<Expr<SomeType>> &&,
int defaultRealKind);
template <typename A> Expr<TypeOf<A>> ScalarConstantToExpr(const A &x) {
using Ty = TypeOf<A>;
static_assert(
std::is_same_v<Scalar<Ty>, std::decay_t<A>>, "TypeOf<> is broken");
return Expr<TypeOf<A>>{Constant<Ty>{x}};
}
// Combine two expressions of the same specific numeric type with an operation
// to produce a new expression. Implements piecewise addition and subtraction
// for COMPLEX.
template <template <typename> class OPR, typename SPECIFIC>
Expr<SPECIFIC> Combine(Expr<SPECIFIC> &&x, Expr<SPECIFIC> &&y) {
static_assert(IsSpecificIntrinsicType<SPECIFIC>);
if constexpr (SPECIFIC::category == TypeCategory::Complex &&
(std::is_same_v<OPR<LargestReal>, Add<LargestReal>> ||
std::is_same_v<OPR<LargestReal>, Subtract<LargestReal>>)) {
static constexpr int kind{SPECIFIC::kind};
using Part = Type<TypeCategory::Real, kind>;
return AsExpr(ComplexConstructor<kind>{
AsExpr(OPR<Part>{AsExpr(ComplexComponent<kind>{false, x}),
AsExpr(ComplexComponent<kind>{false, y})}),
AsExpr(OPR<Part>{AsExpr(ComplexComponent<kind>{true, x}),
AsExpr(ComplexComponent<kind>{true, y})})});
} else {
return AsExpr(OPR<SPECIFIC>{std::move(x), std::move(y)});
}
}
// Given two expressions of arbitrary kind in the same intrinsic type
// category, convert one of them if necessary to the larger kind of the
// other, then combine the resulting homogenized operands with a given
// operation, returning a new expression in the same type category.
template <template <typename> class OPR, TypeCategory CAT>
Expr<SomeKind<CAT>> PromoteAndCombine(
Expr<SomeKind<CAT>> &&x, Expr<SomeKind<CAT>> &&y) {
return std::visit(
[](auto &&xy) {
using Ty = ResultType<decltype(xy[0])>;
return AsCategoryExpr(
Combine<OPR, Ty>(std::move(xy[0]), std::move(xy[1])));
},
AsSameKindExprs(std::move(x), std::move(y)));
}
// Given two expressions of arbitrary type, try to combine them with a
// binary numeric operation (e.g., Add), possibly with data type conversion of
// one of the operands to the type of the other. Handles special cases with
// typeless literal operands and with REAL/COMPLEX exponentiation to INTEGER
// powers.
template <template <typename> class OPR>
std::optional<Expr<SomeType>> NumericOperation(parser::ContextualMessages &,
Expr<SomeType> &&, Expr<SomeType> &&, int defaultRealKind);
extern template std::optional<Expr<SomeType>> NumericOperation<Power>(
parser::ContextualMessages &, Expr<SomeType> &&, Expr<SomeType> &&,
int defaultRealKind);
extern template std::optional<Expr<SomeType>> NumericOperation<Multiply>(
parser::ContextualMessages &, Expr<SomeType> &&, Expr<SomeType> &&,
int defaultRealKind);
extern template std::optional<Expr<SomeType>> NumericOperation<Divide>(
parser::ContextualMessages &, Expr<SomeType> &&, Expr<SomeType> &&,
int defaultRealKind);
extern template std::optional<Expr<SomeType>> NumericOperation<Add>(
parser::ContextualMessages &, Expr<SomeType> &&, Expr<SomeType> &&,
int defaultRealKind);
extern template std::optional<Expr<SomeType>> NumericOperation<Subtract>(
parser::ContextualMessages &, Expr<SomeType> &&, Expr<SomeType> &&,
int defaultRealKind);
std::optional<Expr<SomeType>> Negation(
parser::ContextualMessages &, Expr<SomeType> &&);
// Given two expressions of arbitrary type, try to combine them with a
// relational operator (e.g., .LT.), possibly with data type conversion.
std::optional<Expr<LogicalResult>> Relate(parser::ContextualMessages &,
RelationalOperator, Expr<SomeType> &&, Expr<SomeType> &&);
template <int K>
Expr<Type<TypeCategory::Logical, K>> LogicalNegation(
Expr<Type<TypeCategory::Logical, K>> &&x) {
return AsExpr(Not<K>{std::move(x)});
}
Expr<SomeLogical> LogicalNegation(Expr<SomeLogical> &&);
template <int K>
Expr<Type<TypeCategory::Logical, K>> BinaryLogicalOperation(LogicalOperator opr,
Expr<Type<TypeCategory::Logical, K>> &&x,
Expr<Type<TypeCategory::Logical, K>> &&y) {
return AsExpr(LogicalOperation<K>{opr, std::move(x), std::move(y)});
}
Expr<SomeLogical> BinaryLogicalOperation(
LogicalOperator, Expr<SomeLogical> &&, Expr<SomeLogical> &&);
// Convenience functions and operator overloadings for expression construction.
// These interfaces are defined only for those situations that can never
// emit any message. Use the more general templates (above) in other
// situations.
template <TypeCategory C, int K>
Expr<Type<C, K>> operator-(Expr<Type<C, K>> &&x) {
return AsExpr(Negate<Type<C, K>>{std::move(x)});
}
template <int K>
Expr<Type<TypeCategory::Complex, K>> operator-(
Expr<Type<TypeCategory::Complex, K>> &&x) {
using Part = Type<TypeCategory::Real, K>;
return AsExpr(ComplexConstructor<K>{
AsExpr(Negate<Part>{AsExpr(ComplexComponent<K>{false, x})}),
AsExpr(Negate<Part>{AsExpr(ComplexComponent<K>{true, x})})});
}
template <TypeCategory C, int K>
Expr<Type<C, K>> operator+(Expr<Type<C, K>> &&x, Expr<Type<C, K>> &&y) {
return AsExpr(Combine<Add, Type<C, K>>(std::move(x), std::move(y)));
}
template <TypeCategory C, int K>
Expr<Type<C, K>> operator-(Expr<Type<C, K>> &&x, Expr<Type<C, K>> &&y) {
return AsExpr(Combine<Subtract, Type<C, K>>(std::move(x), std::move(y)));
}
template <TypeCategory C, int K>
Expr<Type<C, K>> operator*(Expr<Type<C, K>> &&x, Expr<Type<C, K>> &&y) {
return AsExpr(Combine<Multiply, Type<C, K>>(std::move(x), std::move(y)));
}
template <TypeCategory C, int K>
Expr<Type<C, K>> operator/(Expr<Type<C, K>> &&x, Expr<Type<C, K>> &&y) {
return AsExpr(Combine<Divide, Type<C, K>>(std::move(x), std::move(y)));
}
template <TypeCategory C> Expr<SomeKind<C>> operator-(Expr<SomeKind<C>> &&x) {
return std::visit(
[](auto &xk) { return Expr<SomeKind<C>>{-std::move(xk)}; }, x.u);
}
template <TypeCategory CAT>
Expr<SomeKind<CAT>> operator+(
Expr<SomeKind<CAT>> &&x, Expr<SomeKind<CAT>> &&y) {
return PromoteAndCombine<Add, CAT>(std::move(x), std::move(y));
}
template <TypeCategory CAT>
Expr<SomeKind<CAT>> operator-(
Expr<SomeKind<CAT>> &&x, Expr<SomeKind<CAT>> &&y) {
return PromoteAndCombine<Subtract, CAT>(std::move(x), std::move(y));
}
template <TypeCategory CAT>
Expr<SomeKind<CAT>> operator*(
Expr<SomeKind<CAT>> &&x, Expr<SomeKind<CAT>> &&y) {
return PromoteAndCombine<Multiply, CAT>(std::move(x), std::move(y));
}
template <TypeCategory CAT>
Expr<SomeKind<CAT>> operator/(
Expr<SomeKind<CAT>> &&x, Expr<SomeKind<CAT>> &&y) {
return PromoteAndCombine<Divide, CAT>(std::move(x), std::move(y));
}
// A utility for use with common::SearchTypes to create generic expressions
// when an intrinsic type category for (say) a variable is known
// but the kind parameter value is not.
template <TypeCategory CAT, template <typename> class TEMPLATE, typename VALUE>
struct TypeKindVisitor {
using Result = std::optional<Expr<SomeType>>;
using Types = CategoryTypes<CAT>;
TypeKindVisitor(int k, VALUE &&x) : kind{k}, value{std::move(x)} {}
TypeKindVisitor(int k, const VALUE &x) : kind{k}, value{x} {}
template <typename T> Result Test() {
if (kind == T::kind) {
return AsGenericExpr(TEMPLATE<T>{std::move(value)});
}
return std::nullopt;
}
int kind;
VALUE value;
};
// GetLastSymbol() returns the rightmost symbol in an object or procedure
// designator (which has perhaps been wrapped in an Expr<>), or a null pointer
// when none is found.
struct GetLastSymbolHelper
: public AnyTraverse<GetLastSymbolHelper, std::optional<const Symbol *>> {
using Result = std::optional<const Symbol *>;
using Base = AnyTraverse<GetLastSymbolHelper, Result>;
GetLastSymbolHelper() : Base{*this} {}
using Base::operator();
Result operator()(const Symbol &x) const { return &x; }
Result operator()(const Component &x) const { return &x.GetLastSymbol(); }
Result operator()(const NamedEntity &x) const { return &x.GetLastSymbol(); }
Result operator()(const ProcedureDesignator &x) const {
return x.GetSymbol();
}
template <typename T> Result operator()(const Expr<T> &x) const {
if constexpr (common::HasMember<T, AllIntrinsicTypes> ||
std::is_same_v<T, SomeDerived>) {
if (const auto *designator{std::get_if<Designator<T>>(&x.u)}) {
if (auto known{(*this)(*designator)}) {
return known;
}
}
return nullptr;
} else {
return (*this)(x.u);
}
}
};
template <typename A> const Symbol *GetLastSymbol(const A &x) {
if (auto known{GetLastSymbolHelper{}(x)}) {
return *known;
} else {
return nullptr;
}
}
// Convenience: If GetLastSymbol() succeeds on the argument, return its
// set of attributes, otherwise the empty set.
template <typename A> semantics::Attrs GetAttrs(const A &x) {
if (const Symbol * symbol{GetLastSymbol(x)}) {
return symbol->attrs();
} else {
return {};
}
}
// GetBaseObject()
template <typename A> std::optional<BaseObject> GetBaseObject(const A &) {
return std::nullopt;
}
template <typename T>
std::optional<BaseObject> GetBaseObject(const Designator<T> &x) {
return x.GetBaseObject();
}
template <typename T>
std::optional<BaseObject> GetBaseObject(const Expr<T> &x) {
return std::visit([](const auto &y) { return GetBaseObject(y); }, x.u);
}
template <typename A>
std::optional<BaseObject> GetBaseObject(const std::optional<A> &x) {
if (x) {
return GetBaseObject(*x);
} else {
return std::nullopt;
}
}
// Predicate: IsAllocatableOrPointer()
template <typename A> bool IsAllocatableOrPointer(const A &x) {
return GetAttrs(x).HasAny(
semantics::Attrs{semantics::Attr::POINTER, semantics::Attr::ALLOCATABLE});
}
// Procedure and pointer detection predicates
bool IsProcedure(const Expr<SomeType> &);
bool IsProcedurePointer(const Expr<SomeType> &);
bool IsNullPointer(const Expr<SomeType> &);
// Extracts the chain of symbols from a designator, which has perhaps been
// wrapped in an Expr<>, removing all of the (co)subscripts. The
// base object will be the first symbol in the result vector.
struct GetSymbolVectorHelper
: public Traverse<GetSymbolVectorHelper, SymbolVector> {
using Result = SymbolVector;
using Base = Traverse<GetSymbolVectorHelper, Result>;
using Base::operator();
GetSymbolVectorHelper() : Base{*this} {}
Result Default() { return {}; }
Result Combine(Result &&a, Result &&b) {
a.insert(a.end(), b.begin(), b.end());
return std::move(a);
}
Result operator()(const Symbol &) const;
Result operator()(const Component &) const;
Result operator()(const ArrayRef &) const;
Result operator()(const CoarrayRef &) const;
};
template <typename A> SymbolVector GetSymbolVector(const A &x) {
return GetSymbolVectorHelper{}(x);
}
// GetLastTarget() returns the rightmost symbol in an object designator's
// SymbolVector that has the POINTER or TARGET attribute, or a null pointer
// when none is found.
const Symbol *GetLastTarget(const SymbolVector &);
// Resolves any whole ASSOCIATE(B=>A) associations, then returns GetUltimate()
const Symbol &ResolveAssociations(const Symbol &);
// Collects all of the Symbols in an expression
template <typename A> semantics::SymbolSet CollectSymbols(const A &);
extern template semantics::SymbolSet CollectSymbols(const Expr<SomeType> &);
extern template semantics::SymbolSet CollectSymbols(const Expr<SomeInteger> &);
extern template semantics::SymbolSet CollectSymbols(
const Expr<SubscriptInteger> &);
// Predicate: does a variable contain a vector-valued subscript (not a triplet)?
bool HasVectorSubscript(const Expr<SomeType> &);
// Utilities for attaching the location of the declaration of a symbol
// of interest to a message, if both pointers are non-null. Handles
// the case of USE association gracefully.
parser::Message *AttachDeclaration(parser::Message &, const Symbol &);
parser::Message *AttachDeclaration(parser::Message *, const Symbol &);
template <typename MESSAGES, typename... A>
parser::Message *SayWithDeclaration(
MESSAGES &messages, const Symbol &symbol, A &&... x) {
return AttachDeclaration(messages.Say(std::forward<A>(x)...), symbol);
}
// Check for references to impure procedures; returns the name
// of one to complain about, if any exist.
std::optional<std::string> FindImpureCall(
const IntrinsicProcTable &, const Expr<SomeType> &);
std::optional<std::string> FindImpureCall(
const IntrinsicProcTable &, const ProcedureRef &);
} // namespace Fortran::evaluate
#endif // FORTRAN_EVALUATE_TOOLS_H_

View File

@ -0,0 +1,304 @@
//===-- include/flang/Evaluate/traverse.h -----------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_EVALUATE_TRAVERSE_H_
#define FORTRAN_EVALUATE_TRAVERSE_H_
// A utility for scanning all of the constituent objects in an Expr<>
// expression representation using a collection of mutually recursive
// functions to compose a function object.
//
// The class template Traverse<> below implements a function object that
// can handle every type that can appear in or around an Expr<>.
// Each of its overloads for operator() should be viewed as a *default*
// handler; some of these must be overridden by the client to accomplish
// its particular task.
//
// The client (Visitor) of Traverse<Visitor,Result> must define:
// - a member function "Result Default();"
// - a member function "Result Combine(Result &&, Result &&)"
// - overrides for "Result operator()"
//
// Boilerplate classes also appear below to ease construction of visitors.
// See CheckSpecificationExpr() in check-expression.cpp for an example client.
//
// How this works:
// - The operator() overloads in Traverse<> invoke the visitor's Default() for
// expression leaf nodes. They invoke the visitor's operator() for the
// subtrees of interior nodes, and the visitor's Combine() to merge their
// results together.
// - Overloads of operator() in each visitor handle the cases of interest.
#include "expression.h"
#include "flang/Semantics/symbol.h"
#include "flang/Semantics/type.h"
#include <set>
#include <type_traits>
namespace Fortran::evaluate {
template <typename Visitor, typename Result> class Traverse {
public:
explicit Traverse(Visitor &v) : visitor_{v} {}
// Packaging
template <typename A, bool C>
Result operator()(const common::Indirection<A, C> &x) const {
return visitor_(x.value());
}
template <typename A> Result operator()(SymbolRef x) const {
return visitor_(*x);
}
template <typename A> Result operator()(const std::unique_ptr<A> &x) const {
return visitor_(x.get());
}
template <typename A> Result operator()(const std::shared_ptr<A> &x) const {
return visitor_(x.get());
}
template <typename A> Result operator()(const A *x) const {
if (x) {
return visitor_(*x);
} else {
return visitor_.Default();
}
}
template <typename A> Result operator()(const std::optional<A> &x) const {
if (x) {
return visitor_(*x);
} else {
return visitor_.Default();
}
}
template <typename... A>
Result operator()(const std::variant<A...> &u) const {
return std::visit(visitor_, u);
}
template <typename A> Result operator()(const std::vector<A> &x) const {
return CombineContents(x);
}
// Leaves
Result operator()(const BOZLiteralConstant &) const {
return visitor_.Default();
}
Result operator()(const NullPointer &) const { return visitor_.Default(); }
template <typename T> Result operator()(const Constant<T> &x) const {
if constexpr (T::category == TypeCategory::Derived) {
std::optional<Result> result;
for (const StructureConstructorValues &map : x.values()) {
for (const auto &pair : map) {
auto value{visitor_(pair.second.value())};
result = result
? visitor_.Combine(std::move(*result), std::move(value))
: std::move(value);
}
}
return result ? *result : visitor_.Default();
} else {
return visitor_.Default();
}
}
Result operator()(const Symbol &) const { return visitor_.Default(); }
Result operator()(const StaticDataObject &) const {
return visitor_.Default();
}
Result operator()(const ImpliedDoIndex &) const { return visitor_.Default(); }
// Variables
Result operator()(const BaseObject &x) const { return visitor_(x.u); }
Result operator()(const Component &x) const {
return Combine(x.base(), x.GetLastSymbol());
}
Result operator()(const NamedEntity &x) const {
if (const Component * component{x.UnwrapComponent()}) {
return visitor_(*component);
} else {
return visitor_(x.GetFirstSymbol());
}
}
template <int KIND> Result operator()(const TypeParamInquiry<KIND> &x) const {
return visitor_(x.base());
}
Result operator()(const Triplet &x) const {
return Combine(x.lower(), x.upper(), x.stride());
}
Result operator()(const Subscript &x) const { return visitor_(x.u); }
Result operator()(const ArrayRef &x) const {
return Combine(x.base(), x.subscript());
}
Result operator()(const CoarrayRef &x) const {
return Combine(
x.base(), x.subscript(), x.cosubscript(), x.stat(), x.team());
}
Result operator()(const DataRef &x) const { return visitor_(x.u); }
Result operator()(const Substring &x) const {
return Combine(x.parent(), x.lower(), x.upper());
}
Result operator()(const ComplexPart &x) const {
return visitor_(x.complex());
}
template <typename T> Result operator()(const Designator<T> &x) const {
return visitor_(x.u);
}
template <typename T> Result operator()(const Variable<T> &x) const {
return visitor_(x.u);
}
Result operator()(const DescriptorInquiry &x) const {
return visitor_(x.base());
}
// Calls
Result operator()(const SpecificIntrinsic &) const {
return visitor_.Default();
}
Result operator()(const ProcedureDesignator &x) const {
if (const Component * component{x.GetComponent()}) {
return visitor_(*component);
} else if (const Symbol * symbol{x.GetSymbol()}) {
return visitor_(*symbol);
} else {
return visitor_(DEREF(x.GetSpecificIntrinsic()));
}
}
Result operator()(const ActualArgument &x) const {
if (const auto *symbol{x.GetAssumedTypeDummy()}) {
return visitor_(*symbol);
} else {
return visitor_(x.UnwrapExpr());
}
}
Result operator()(const ProcedureRef &x) const {
return Combine(x.proc(), x.arguments());
}
template <typename T> Result operator()(const FunctionRef<T> &x) const {
return visitor_(static_cast<const ProcedureRef &>(x));
}
// Other primaries
template <typename T>
Result operator()(const ArrayConstructorValue<T> &x) const {
return visitor_(x.u);
}
template <typename T>
Result operator()(const ArrayConstructorValues<T> &x) const {
return CombineContents(x);
}
template <typename T> Result operator()(const ImpliedDo<T> &x) const {
return Combine(x.lower(), x.upper(), x.stride(), x.values());
}
Result operator()(const semantics::ParamValue &x) const {
return visitor_(x.GetExplicit());
}
Result operator()(
const semantics::DerivedTypeSpec::ParameterMapType::value_type &x) const {
return visitor_(x.second);
}
Result operator()(const semantics::DerivedTypeSpec &x) const {
return CombineContents(x.parameters());
}
Result operator()(const StructureConstructorValues::value_type &x) const {
return visitor_(x.second);
}
Result operator()(const StructureConstructor &x) const {
return visitor_.Combine(visitor_(x.derivedTypeSpec()), CombineContents(x));
}
// Operations and wrappers
template <typename D, typename R, typename O>
Result operator()(const Operation<D, R, O> &op) const {
return visitor_(op.left());
}
template <typename D, typename R, typename LO, typename RO>
Result operator()(const Operation<D, R, LO, RO> &op) const {
return Combine(op.left(), op.right());
}
Result operator()(const Relational<SomeType> &x) const {
return visitor_(x.u);
}
template <typename T> Result operator()(const Expr<T> &x) const {
return visitor_(x.u);
}
private:
template <typename ITER> Result CombineRange(ITER iter, ITER end) const {
if (iter == end) {
return visitor_.Default();
} else {
Result result{visitor_(*iter++)};
for (; iter != end; ++iter) {
result = visitor_.Combine(std::move(result), visitor_(*iter));
}
return result;
}
}
template <typename A> Result CombineContents(const A &x) const {
return CombineRange(x.begin(), x.end());
}
template <typename A, typename... Bs>
Result Combine(const A &x, const Bs &... ys) const {
if constexpr (sizeof...(Bs) == 0) {
return visitor_(x);
} else {
return visitor_.Combine(visitor_(x), Combine(ys...));
}
}
Visitor &visitor_;
};
// For validity checks across an expression: if any operator() result is
// false, so is the overall result.
template <typename Visitor, bool DefaultValue,
typename Base = Traverse<Visitor, bool>>
struct AllTraverse : public Base {
AllTraverse(Visitor &v) : Base{v} {}
using Base::operator();
static bool Default() { return DefaultValue; }
static bool Combine(bool x, bool y) { return x && y; }
};
// For searches over an expression: the first operator() result that
// is truthful is the final result. Works for Booleans, pointers,
// and std::optional<>.
template <typename Visitor, typename Result = bool,
typename Base = Traverse<Visitor, Result>>
struct AnyTraverse : public Base {
AnyTraverse(Visitor &v) : Base{v} {}
using Base::operator();
static Result Default() { return {}; }
static Result Combine(Result &&x, Result &&y) {
if (x) {
return std::move(x);
} else {
return std::move(y);
}
}
};
template <typename Visitor, typename Set,
typename Base = Traverse<Visitor, Set>>
struct SetTraverse : public Base {
SetTraverse(Visitor &v) : Base{v} {}
using Base::operator();
static Set Default() { return {}; }
static Set Combine(Set &&x, Set &&y) {
#if defined __GNUC__ && !defined __APPLE__ && !(CLANG_LIBRARIES)
x.merge(y);
#else
// std::set::merge() not available (yet)
for (auto &value : y) {
x.insert(std::move(value));
}
#endif
return std::move(x);
}
};
} // namespace Fortran::evaluate
#endif

View File

@ -0,0 +1,517 @@
//===-- include/flang/Evaluate/type.h ---------------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_EVALUATE_TYPE_H_
#define FORTRAN_EVALUATE_TYPE_H_
// These definitions map Fortran's intrinsic types, characterized by byte
// sizes encoded in KIND type parameter values, to their value representation
// types in the evaluation library, which are parameterized in terms of
// total bit width and real precision. Instances of the Type class template
// are suitable for use as template parameters to instantiate other class
// templates, like expressions, over the supported types and kinds.
#include "common.h"
#include "complex.h"
#include "formatting.h"
#include "integer.h"
#include "logical.h"
#include "real.h"
#include "flang/Common/Fortran.h"
#include "flang/Common/idioms.h"
#include "flang/Common/template.h"
#include <cinttypes>
#include <optional>
#include <string>
#include <type_traits>
#include <variant>
namespace Fortran::semantics {
class DeclTypeSpec;
class DerivedTypeSpec;
class ParamValue;
class Symbol;
bool IsDescriptor(const Symbol &);
} // namespace Fortran::semantics
namespace Fortran::evaluate {
using common::TypeCategory;
// Specific intrinsic types are represented by specializations of
// this class template Type<CATEGORY, KIND>.
template <TypeCategory CATEGORY, int KIND = 0> class Type;
using SubscriptInteger = Type<TypeCategory::Integer, 8>;
using CInteger = Type<TypeCategory::Integer, 4>;
using LogicalResult = Type<TypeCategory::Logical, 4>;
using LargestReal = Type<TypeCategory::Real, 16>;
// A predicate that is true when a kind value is a kind that could possibly
// be supported for an intrinsic type category on some target instruction
// set architecture.
// TODO: specialize for the actual target architecture
static constexpr bool IsValidKindOfIntrinsicType(
TypeCategory category, std::int64_t kind) {
switch (category) {
case TypeCategory::Integer:
return kind == 1 || kind == 2 || kind == 4 || kind == 8 || kind == 16;
case TypeCategory::Real:
case TypeCategory::Complex:
return kind == 2 || kind == 3 || kind == 4 || kind == 8 || kind == 10 ||
kind == 16;
case TypeCategory::Character:
return kind == 1 || kind == 2 || kind == 4;
case TypeCategory::Logical:
return kind == 1 || kind == 2 || kind == 4 || kind == 8;
default:
return false;
}
}
// DynamicType is meant to be suitable for use as the result type for
// GetType() functions and member functions; consequently, it must be
// capable of being used in a constexpr context. So it does *not*
// directly hold anything requiring a destructor, such as an arbitrary
// CHARACTER length type parameter expression. Those must be derived
// via LEN() member functions, packaged elsewhere (e.g. as in
// ArrayConstructor), or copied from a parameter spec in the symbol table
// if one is supplied.
class DynamicType {
public:
constexpr DynamicType(TypeCategory cat, int k) : category_{cat}, kind_{k} {
CHECK(IsValidKindOfIntrinsicType(category_, kind_));
}
constexpr DynamicType(int k, const semantics::ParamValue &pv)
: category_{TypeCategory::Character}, kind_{k}, charLength_{&pv} {
CHECK(IsValidKindOfIntrinsicType(category_, kind_));
}
explicit constexpr DynamicType(
const semantics::DerivedTypeSpec &dt, bool poly = false)
: category_{TypeCategory::Derived}, derived_{&dt} {
if (poly) {
kind_ = ClassKind;
}
}
CONSTEXPR_CONSTRUCTORS_AND_ASSIGNMENTS(DynamicType)
// A rare use case used for representing the characteristics of an
// intrinsic function like REAL() that accepts a typeless BOZ literal
// argument, which is something that real user Fortran can't do.
static constexpr DynamicType TypelessIntrinsicArgument() {
DynamicType result;
result.category_ = TypeCategory::Integer;
result.kind_ = TypelessKind;
return result;
}
static constexpr DynamicType UnlimitedPolymorphic() {
DynamicType result;
result.category_ = TypeCategory::Derived;
result.kind_ = ClassKind;
result.derived_ = nullptr;
return result; // CLASS(*)
}
static constexpr DynamicType AssumedType() {
DynamicType result;
result.category_ = TypeCategory::Derived;
result.kind_ = AssumedTypeKind;
result.derived_ = nullptr;
return result; // TYPE(*)
}
// Comparison is deep -- type parameters are compared independently.
bool operator==(const DynamicType &) const;
bool operator!=(const DynamicType &that) const { return !(*this == that); }
constexpr TypeCategory category() const { return category_; }
constexpr int kind() const {
CHECK(kind_ > 0);
return kind_;
}
constexpr const semantics::ParamValue *charLength() const {
return charLength_;
}
std::optional<common::ConstantSubscript> GetCharLength() const;
std::string AsFortran() const;
std::string AsFortran(std::string &&charLenExpr) const;
DynamicType ResultTypeForMultiply(const DynamicType &) const;
bool IsAssumedLengthCharacter() const;
bool IsUnknownLengthCharacter() const;
bool IsTypelessIntrinsicArgument() const;
constexpr bool IsAssumedType() const { // TYPE(*)
return kind_ == AssumedTypeKind;
}
constexpr bool IsPolymorphic() const { // TYPE(*) or CLASS()
return kind_ == ClassKind || IsAssumedType();
}
constexpr bool IsUnlimitedPolymorphic() const { // TYPE(*) or CLASS(*)
return IsPolymorphic() && !derived_;
}
constexpr const semantics::DerivedTypeSpec &GetDerivedTypeSpec() const {
return DEREF(derived_);
}
bool RequiresDescriptor() const;
bool HasDeferredTypeParameter() const;
// 7.3.2.3 & 15.5.2.4 type compatibility.
// x.IsTypeCompatibleWith(y) is true if "x => y" or passing actual y to
// dummy argument x would be valid. Be advised, this is not a reflexive
// relation.
bool IsTypeCompatibleWith(const DynamicType &) const;
// Type compatible and kind type parameters match
bool IsTkCompatibleWith(const DynamicType &) const;
// Result will be missing when a symbol is absent or
// has an erroneous type, e.g., REAL(KIND=666).
static std::optional<DynamicType> From(const semantics::DeclTypeSpec &);
static std::optional<DynamicType> From(const semantics::Symbol &);
template <typename A> static std::optional<DynamicType> From(const A &x) {
return x.GetType();
}
template <typename A> static std::optional<DynamicType> From(const A *p) {
if (!p) {
return std::nullopt;
} else {
return From(*p);
}
}
template <typename A>
static std::optional<DynamicType> From(const std::optional<A> &x) {
if (x) {
return From(*x);
} else {
return std::nullopt;
}
}
private:
// Special kind codes are used to distinguish the following Fortran types.
enum SpecialKind {
TypelessKind = -1, // BOZ actual argument to intrinsic function
ClassKind = -2, // CLASS(T) or CLASS(*)
AssumedTypeKind = -3, // TYPE(*)
};
constexpr DynamicType() {}
TypeCategory category_{TypeCategory::Derived}; // overridable default
int kind_{0};
const semantics::ParamValue *charLength_{nullptr};
const semantics::DerivedTypeSpec *derived_{nullptr}; // TYPE(T), CLASS(T)
};
// Return the DerivedTypeSpec of a DynamicType if it has one.
const semantics::DerivedTypeSpec *GetDerivedTypeSpec(const DynamicType &);
const semantics::DerivedTypeSpec *GetDerivedTypeSpec(
const std::optional<DynamicType> &);
std::string DerivedTypeSpecAsFortran(const semantics::DerivedTypeSpec &);
template <TypeCategory CATEGORY, int KIND = 0> struct TypeBase {
static constexpr TypeCategory category{CATEGORY};
static constexpr int kind{KIND};
constexpr bool operator==(const TypeBase &) const { return true; }
static constexpr DynamicType GetType() { return {category, kind}; }
static std::string AsFortran() { return GetType().AsFortran(); }
};
template <int KIND>
class Type<TypeCategory::Integer, KIND>
: public TypeBase<TypeCategory::Integer, KIND> {
public:
using Scalar = value::Integer<8 * KIND>;
};
// REAL(KIND=2) is IEEE half-precision (16 bits)
template <>
class Type<TypeCategory::Real, 2> : public TypeBase<TypeCategory::Real, 2> {
public:
using Scalar =
value::Real<typename Type<TypeCategory::Integer, 2>::Scalar, 11>;
};
// REAL(KIND=3) identifies the "other" half-precision format, which is
// basically REAL(4) without its least-order 16 fraction bits.
template <>
class Type<TypeCategory::Real, 3> : public TypeBase<TypeCategory::Real, 3> {
public:
using Scalar =
value::Real<typename Type<TypeCategory::Integer, 2>::Scalar, 8>;
};
// REAL(KIND=4) is IEEE-754 single precision (32 bits)
template <>
class Type<TypeCategory::Real, 4> : public TypeBase<TypeCategory::Real, 4> {
public:
using Scalar =
value::Real<typename Type<TypeCategory::Integer, 4>::Scalar, 24>;
};
// REAL(KIND=8) is IEEE double precision (64 bits)
template <>
class Type<TypeCategory::Real, 8> : public TypeBase<TypeCategory::Real, 8> {
public:
using Scalar =
value::Real<typename Type<TypeCategory::Integer, 8>::Scalar, 53>;
};
// REAL(KIND=10) is x87 FPU extended precision (80 bits, all explicit)
template <>
class Type<TypeCategory::Real, 10> : public TypeBase<TypeCategory::Real, 10> {
public:
using Scalar = value::Real<value::Integer<80>, 64>;
};
// REAL(KIND=16) is IEEE quad precision (128 bits)
template <>
class Type<TypeCategory::Real, 16> : public TypeBase<TypeCategory::Real, 16> {
public:
using Scalar = value::Real<value::Integer<128>, 113>;
};
// The KIND type parameter on COMPLEX is the kind of each of its components.
template <int KIND>
class Type<TypeCategory::Complex, KIND>
: public TypeBase<TypeCategory::Complex, KIND> {
public:
using Part = Type<TypeCategory::Real, KIND>;
using Scalar = value::Complex<typename Part::Scalar>;
};
template <>
class Type<TypeCategory::Character, 1>
: public TypeBase<TypeCategory::Character, 1> {
public:
using Scalar = std::string;
};
template <>
class Type<TypeCategory::Character, 2>
: public TypeBase<TypeCategory::Character, 2> {
public:
using Scalar = std::u16string;
};
template <>
class Type<TypeCategory::Character, 4>
: public TypeBase<TypeCategory::Character, 4> {
public:
using Scalar = std::u32string;
};
template <int KIND>
class Type<TypeCategory::Logical, KIND>
: public TypeBase<TypeCategory::Logical, KIND> {
public:
using Scalar = value::Logical<8 * KIND>;
};
// Type functions
// Given a specific type, find the type of the same kind in another category.
template <TypeCategory CATEGORY, typename T>
using SameKind = Type<CATEGORY, std::decay_t<T>::kind>;
// Many expressions, including subscripts, CHARACTER lengths, array bounds,
// and effective type parameter values, are of a maximal kind of INTEGER.
using IndirectSubscriptIntegerExpr =
common::CopyableIndirection<Expr<SubscriptInteger>>;
// For each intrinsic type category CAT, CategoryTypes<CAT> is an instantiation
// of std::tuple<Type<CAT, K>> that comprises every kind value K in that
// category that could possibly be supported on any target.
template <TypeCategory CATEGORY, int KIND>
using CategoryKindTuple =
std::conditional_t<IsValidKindOfIntrinsicType(CATEGORY, KIND),
std::tuple<Type<CATEGORY, KIND>>, std::tuple<>>;
template <TypeCategory CATEGORY, int... KINDS>
using CategoryTypesHelper =
common::CombineTuples<CategoryKindTuple<CATEGORY, KINDS>...>;
template <TypeCategory CATEGORY>
using CategoryTypes = CategoryTypesHelper<CATEGORY, 1, 2, 3, 4, 8, 10, 16, 32>;
using IntegerTypes = CategoryTypes<TypeCategory::Integer>;
using RealTypes = CategoryTypes<TypeCategory::Real>;
using ComplexTypes = CategoryTypes<TypeCategory::Complex>;
using CharacterTypes = CategoryTypes<TypeCategory::Character>;
using LogicalTypes = CategoryTypes<TypeCategory::Logical>;
using FloatingTypes = common::CombineTuples<RealTypes, ComplexTypes>;
using NumericTypes = common::CombineTuples<IntegerTypes, FloatingTypes>;
using RelationalTypes = common::CombineTuples<NumericTypes, CharacterTypes>;
using AllIntrinsicTypes = common::CombineTuples<RelationalTypes, LogicalTypes>;
using LengthlessIntrinsicTypes =
common::CombineTuples<NumericTypes, LogicalTypes>;
// Predicates: does a type represent a specific intrinsic type?
template <typename T>
constexpr bool IsSpecificIntrinsicType{common::HasMember<T, AllIntrinsicTypes>};
// Predicate: is a type an intrinsic type that is completely characterized
// by its category and kind parameter value, or might it have a derived type
// &/or a length type parameter?
template <typename T>
constexpr bool IsLengthlessIntrinsicType{
common::HasMember<T, LengthlessIntrinsicTypes>};
// Represents a type of any supported kind within a particular category.
template <TypeCategory CATEGORY> struct SomeKind {
static constexpr TypeCategory category{CATEGORY};
constexpr bool operator==(const SomeKind &) const { return true; }
};
using NumericCategoryTypes = std::tuple<SomeKind<TypeCategory::Integer>,
SomeKind<TypeCategory::Real>, SomeKind<TypeCategory::Complex>>;
using AllIntrinsicCategoryTypes = std::tuple<SomeKind<TypeCategory::Integer>,
SomeKind<TypeCategory::Real>, SomeKind<TypeCategory::Complex>,
SomeKind<TypeCategory::Character>, SomeKind<TypeCategory::Logical>>;
// Represents a completely generic type (or, for Expr<SomeType>, a typeless
// value like a BOZ literal or NULL() pointer).
struct SomeType {};
class StructureConstructor;
// Represents any derived type, polymorphic or not, as well as CLASS(*).
template <> class SomeKind<TypeCategory::Derived> {
public:
static constexpr TypeCategory category{TypeCategory::Derived};
using Scalar = StructureConstructor;
constexpr SomeKind() {} // CLASS(*)
constexpr explicit SomeKind(const semantics::DerivedTypeSpec &dts)
: derivedTypeSpec_{&dts} {}
constexpr explicit SomeKind(const DynamicType &dt)
: SomeKind(dt.GetDerivedTypeSpec()) {}
CONSTEXPR_CONSTRUCTORS_AND_ASSIGNMENTS(SomeKind)
bool IsUnlimitedPolymorphic() const { return !derivedTypeSpec_; }
constexpr DynamicType GetType() const {
if (!derivedTypeSpec_) {
return DynamicType::UnlimitedPolymorphic();
} else {
return DynamicType{*derivedTypeSpec_};
}
}
const semantics::DerivedTypeSpec &derivedTypeSpec() const {
CHECK(derivedTypeSpec_);
return *derivedTypeSpec_;
}
bool operator==(const SomeKind &) const;
std::string AsFortran() const;
private:
const semantics::DerivedTypeSpec *derivedTypeSpec_{nullptr};
};
using SomeInteger = SomeKind<TypeCategory::Integer>;
using SomeReal = SomeKind<TypeCategory::Real>;
using SomeComplex = SomeKind<TypeCategory::Complex>;
using SomeCharacter = SomeKind<TypeCategory::Character>;
using SomeLogical = SomeKind<TypeCategory::Logical>;
using SomeDerived = SomeKind<TypeCategory::Derived>;
using SomeCategory = std::tuple<SomeInteger, SomeReal, SomeComplex,
SomeCharacter, SomeLogical, SomeDerived>;
using AllTypes =
common::CombineTuples<AllIntrinsicTypes, std::tuple<SomeDerived>>;
template <typename T> using Scalar = typename std::decay_t<T>::Scalar;
// When Scalar<T> is S, then TypeOf<S> is T.
// TypeOf is implemented by scanning all supported types for a match
// with Type<T>::Scalar.
template <typename CONST> struct TypeOfHelper {
template <typename T> struct Predicate {
static constexpr bool value() {
return std::is_same_v<std::decay_t<CONST>,
std::decay_t<typename T::Scalar>>;
}
};
static constexpr int index{
common::SearchMembers<Predicate, AllIntrinsicTypes>};
using type = std::conditional_t<index >= 0,
std::tuple_element_t<index, AllIntrinsicTypes>, void>;
};
template <typename CONST> using TypeOf = typename TypeOfHelper<CONST>::type;
int SelectedCharKind(const std::string &, int defaultKind);
int SelectedIntKind(std::int64_t precision = 0);
int SelectedRealKind(
std::int64_t precision = 0, std::int64_t range = 0, std::int64_t radix = 2);
// Utilities
bool IsKindTypeParameter(const semantics::Symbol &);
// For generating "[extern] template class", &c. boilerplate
#define EXPAND_FOR_EACH_INTEGER_KIND(M, P, S) \
M(P, S, 1) M(P, S, 2) M(P, S, 4) M(P, S, 8) M(P, S, 16)
#define EXPAND_FOR_EACH_REAL_KIND(M, P, S) \
M(P, S, 2) M(P, S, 3) M(P, S, 4) M(P, S, 8) M(P, S, 10) M(P, S, 16)
#define EXPAND_FOR_EACH_COMPLEX_KIND(M, P, S) EXPAND_FOR_EACH_REAL_KIND(M, P, S)
#define EXPAND_FOR_EACH_CHARACTER_KIND(M, P, S) M(P, S, 1) M(P, S, 2) M(P, S, 4)
#define EXPAND_FOR_EACH_LOGICAL_KIND(M, P, S) \
M(P, S, 1) M(P, S, 2) M(P, S, 4) M(P, S, 8)
#define TEMPLATE_INSTANTIATION(P, S, ARG) P<ARG> S;
#define FOR_EACH_INTEGER_KIND_HELP(PREFIX, SUFFIX, K) \
PREFIX<Type<TypeCategory::Integer, K>> SUFFIX;
#define FOR_EACH_REAL_KIND_HELP(PREFIX, SUFFIX, K) \
PREFIX<Type<TypeCategory::Real, K>> SUFFIX;
#define FOR_EACH_COMPLEX_KIND_HELP(PREFIX, SUFFIX, K) \
PREFIX<Type<TypeCategory::Complex, K>> SUFFIX;
#define FOR_EACH_CHARACTER_KIND_HELP(PREFIX, SUFFIX, K) \
PREFIX<Type<TypeCategory::Character, K>> SUFFIX;
#define FOR_EACH_LOGICAL_KIND_HELP(PREFIX, SUFFIX, K) \
PREFIX<Type<TypeCategory::Logical, K>> SUFFIX;
#define FOR_EACH_INTEGER_KIND(PREFIX, SUFFIX) \
EXPAND_FOR_EACH_INTEGER_KIND(FOR_EACH_INTEGER_KIND_HELP, PREFIX, SUFFIX)
#define FOR_EACH_REAL_KIND(PREFIX, SUFFIX) \
EXPAND_FOR_EACH_REAL_KIND(FOR_EACH_REAL_KIND_HELP, PREFIX, SUFFIX)
#define FOR_EACH_COMPLEX_KIND(PREFIX, SUFFIX) \
EXPAND_FOR_EACH_COMPLEX_KIND(FOR_EACH_COMPLEX_KIND_HELP, PREFIX, SUFFIX)
#define FOR_EACH_CHARACTER_KIND(PREFIX, SUFFIX) \
EXPAND_FOR_EACH_CHARACTER_KIND(FOR_EACH_CHARACTER_KIND_HELP, PREFIX, SUFFIX)
#define FOR_EACH_LOGICAL_KIND(PREFIX, SUFFIX) \
EXPAND_FOR_EACH_LOGICAL_KIND(FOR_EACH_LOGICAL_KIND_HELP, PREFIX, SUFFIX)
#define FOR_EACH_LENGTHLESS_INTRINSIC_KIND(PREFIX, SUFFIX) \
FOR_EACH_INTEGER_KIND(PREFIX, SUFFIX) \
FOR_EACH_REAL_KIND(PREFIX, SUFFIX) \
FOR_EACH_COMPLEX_KIND(PREFIX, SUFFIX) \
FOR_EACH_LOGICAL_KIND(PREFIX, SUFFIX)
#define FOR_EACH_INTRINSIC_KIND(PREFIX, SUFFIX) \
FOR_EACH_LENGTHLESS_INTRINSIC_KIND(PREFIX, SUFFIX) \
FOR_EACH_CHARACTER_KIND(PREFIX, SUFFIX)
#define FOR_EACH_SPECIFIC_TYPE(PREFIX, SUFFIX) \
FOR_EACH_INTRINSIC_KIND(PREFIX, SUFFIX) \
PREFIX<SomeDerived> SUFFIX;
#define FOR_EACH_CATEGORY_TYPE(PREFIX, SUFFIX) \
PREFIX<SomeInteger> SUFFIX; \
PREFIX<SomeReal> SUFFIX; \
PREFIX<SomeComplex> SUFFIX; \
PREFIX<SomeCharacter> SUFFIX; \
PREFIX<SomeLogical> SUFFIX; \
PREFIX<SomeDerived> SUFFIX; \
PREFIX<SomeType> SUFFIX;
#define FOR_EACH_TYPE_AND_KIND(PREFIX, SUFFIX) \
FOR_EACH_INTRINSIC_KIND(PREFIX, SUFFIX) \
FOR_EACH_CATEGORY_TYPE(PREFIX, SUFFIX)
} // namespace Fortran::evaluate
#endif // FORTRAN_EVALUATE_TYPE_H_

View File

@ -0,0 +1,447 @@
//===-- include/flang/Evaluate/variable.h -----------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_EVALUATE_VARIABLE_H_
#define FORTRAN_EVALUATE_VARIABLE_H_
// Defines data structures to represent data access and function calls
// for use in expressions and assignment statements. Both copy and move
// semantics are supported. The representation adheres closely to the
// Fortran 2018 language standard (q.v.) and uses strong typing to ensure
// that only admissable combinations can be constructed.
#include "call.h"
#include "common.h"
#include "formatting.h"
#include "static-data.h"
#include "type.h"
#include "flang/Common/idioms.h"
#include "flang/Common/reference.h"
#include "flang/Common/template.h"
#include "flang/Parser/char-block.h"
#include <optional>
#include <variant>
#include <vector>
namespace llvm {
class raw_ostream;
}
namespace Fortran::semantics {
class Symbol;
}
namespace Fortran::evaluate {
using semantics::Symbol;
using SymbolRef = common::Reference<const Symbol>;
using SymbolVector = std::vector<SymbolRef>;
// Forward declarations
struct DataRef;
template <typename T> struct Variable;
// Reference a base object in memory. This can be a Fortran symbol,
// static data (e.g., CHARACTER literal), or compiler-created temporary.
struct BaseObject {
EVALUATE_UNION_CLASS_BOILERPLATE(BaseObject)
int Rank() const;
std::optional<Expr<SubscriptInteger>> LEN() const;
llvm::raw_ostream &AsFortran(llvm::raw_ostream &) const;
const Symbol *symbol() const {
if (const auto *result{std::get_if<SymbolRef>(&u)}) {
return &result->get();
} else {
return nullptr;
}
}
std::variant<SymbolRef, StaticDataObject::Pointer> u;
};
// R913 structure-component & C920: Defined to be a multi-part
// data-ref whose last part has no subscripts (or image-selector, although
// that isn't explicit in the document). Pointer and allocatable components
// are not explicitly indirected in this representation.
// Complex components (%RE, %IM) are isolated below in ComplexPart.
// (Type parameter inquiries look like component references but are distinct
// constructs and not represented by this class.)
class Component {
public:
CLASS_BOILERPLATE(Component)
Component(const DataRef &b, const Symbol &c) : base_{b}, symbol_{c} {}
Component(DataRef &&b, const Symbol &c) : base_{std::move(b)}, symbol_{c} {}
Component(common::CopyableIndirection<DataRef> &&b, const Symbol &c)
: base_{std::move(b)}, symbol_{c} {}
const DataRef &base() const { return base_.value(); }
DataRef &base() { return base_.value(); }
int Rank() const;
const Symbol &GetFirstSymbol() const;
const Symbol &GetLastSymbol() const { return symbol_; }
std::optional<Expr<SubscriptInteger>> LEN() const;
bool operator==(const Component &) const;
llvm::raw_ostream &AsFortran(llvm::raw_ostream &) const;
private:
common::CopyableIndirection<DataRef> base_;
SymbolRef symbol_;
};
// A NamedEntity is either a whole Symbol or a component in an instance
// of a derived type. It may be a descriptor.
// TODO: this is basically a symbol with an optional DataRef base;
// could be used to replace Component.
class NamedEntity {
public:
CLASS_BOILERPLATE(NamedEntity)
explicit NamedEntity(const Symbol &symbol) : u_{symbol} {}
explicit NamedEntity(Component &&c) : u_{std::move(c)} {}
bool IsSymbol() const { return std::holds_alternative<SymbolRef>(u_); }
const Symbol &GetFirstSymbol() const;
const Symbol &GetLastSymbol() const;
const Component &GetComponent() const { return std::get<Component>(u_); }
Component &GetComponent() { return std::get<Component>(u_); }
const Component *UnwrapComponent() const; // null if just a Symbol
Component *UnwrapComponent();
int Rank() const;
std::optional<Expr<SubscriptInteger>> LEN() const;
bool operator==(const NamedEntity &) const;
llvm::raw_ostream &AsFortran(llvm::raw_ostream &) const;
private:
std::variant<SymbolRef, Component> u_;
};
// R916 type-param-inquiry
// N.B. x%LEN for CHARACTER is rewritten in semantics to LEN(x), which is
// then handled via LEN() member functions in the various classes;
// it becomes a DescriptorInquiry with Field::Len for assumed-length
// CHARACTER objects.
// x%KIND for intrinsic types is similarly rewritten in semantics to
// KIND(x), which is then folded to a constant value.
// "Bare" type parameter references within a derived type definition do
// not have base objects.
template <int KIND> class TypeParamInquiry {
public:
using Result = Type<TypeCategory::Integer, KIND>;
CLASS_BOILERPLATE(TypeParamInquiry)
TypeParamInquiry(NamedEntity &&x, const Symbol &param)
: base_{std::move(x)}, parameter_{param} {}
TypeParamInquiry(std::optional<NamedEntity> &&x, const Symbol &param)
: base_{std::move(x)}, parameter_{param} {}
const std::optional<NamedEntity> &base() const { return base_; }
std::optional<NamedEntity> &base() { return base_; }
const Symbol &parameter() const { return parameter_; }
static constexpr int Rank() { return 0; } // always scalar
bool operator==(const TypeParamInquiry &) const;
llvm::raw_ostream &AsFortran(llvm::raw_ostream &) const;
private:
std::optional<NamedEntity> base_;
SymbolRef parameter_;
};
EXPAND_FOR_EACH_INTEGER_KIND(
TEMPLATE_INSTANTIATION, extern template class TypeParamInquiry, )
// R921 subscript-triplet
class Triplet {
public:
Triplet();
DEFAULT_CONSTRUCTORS_AND_ASSIGNMENTS(Triplet)
Triplet(std::optional<Expr<SubscriptInteger>> &&,
std::optional<Expr<SubscriptInteger>> &&,
std::optional<Expr<SubscriptInteger>> &&);
std::optional<Expr<SubscriptInteger>> lower() const;
Triplet &set_lower(Expr<SubscriptInteger> &&);
std::optional<Expr<SubscriptInteger>> upper() const;
Triplet &set_upper(Expr<SubscriptInteger> &&);
Expr<SubscriptInteger> stride() const; // N.B. result is not optional<>
Triplet &set_stride(Expr<SubscriptInteger> &&);
bool operator==(const Triplet &) const;
bool IsStrideOne() const;
llvm::raw_ostream &AsFortran(llvm::raw_ostream &) const;
private:
std::optional<IndirectSubscriptIntegerExpr> lower_, upper_;
IndirectSubscriptIntegerExpr stride_;
};
// R919 subscript when rank 0, R923 vector-subscript when rank 1
struct Subscript {
EVALUATE_UNION_CLASS_BOILERPLATE(Subscript)
explicit Subscript(Expr<SubscriptInteger> &&s)
: u{IndirectSubscriptIntegerExpr::Make(std::move(s))} {}
int Rank() const;
llvm::raw_ostream &AsFortran(llvm::raw_ostream &) const;
std::variant<IndirectSubscriptIntegerExpr, Triplet> u;
};
// R917 array-element, R918 array-section; however, the case of an
// array-section that is a complex-part-designator is represented here
// as a ComplexPart instead. C919 & C925 require that at most one set of
// subscripts have rank greater than 0, but that is not explicit in
// these types.
class ArrayRef {
public:
CLASS_BOILERPLATE(ArrayRef)
ArrayRef(const Symbol &symbol, std::vector<Subscript> &&ss)
: base_{symbol}, subscript_(std::move(ss)) {}
ArrayRef(Component &&c, std::vector<Subscript> &&ss)
: base_{std::move(c)}, subscript_(std::move(ss)) {}
ArrayRef(NamedEntity &&base, std::vector<Subscript> &&ss)
: base_{std::move(base)}, subscript_(std::move(ss)) {}
NamedEntity &base() { return base_; }
const NamedEntity &base() const { return base_; }
std::vector<Subscript> &subscript() { return subscript_; }
const std::vector<Subscript> &subscript() const { return subscript_; }
int size() const { return static_cast<int>(subscript_.size()); }
Subscript &at(int n) { return subscript_.at(n); }
const Subscript &at(int n) const { return subscript_.at(n); }
template <typename A> common::IfNoLvalue<Subscript &, A> emplace_back(A &&x) {
return subscript_.emplace_back(std::move(x));
}
int Rank() const;
const Symbol &GetFirstSymbol() const;
const Symbol &GetLastSymbol() const;
std::optional<Expr<SubscriptInteger>> LEN() const;
bool operator==(const ArrayRef &) const;
llvm::raw_ostream &AsFortran(llvm::raw_ostream &) const;
private:
NamedEntity base_;
std::vector<Subscript> subscript_;
};
// R914 coindexed-named-object
// R924 image-selector, R926 image-selector-spec.
// C824 severely limits the usage of derived types with coarray ultimate
// components: they can't be pointers, allocatables, arrays, coarrays, or
// function results. They can be components of other derived types.
// Although the F'2018 Standard never prohibits multiple image-selectors
// per se in the same data-ref or designator, nor the presence of an
// image-selector after a part-ref with rank, the constraints on the
// derived types that would have be involved make it impossible to declare
// an object that could be referenced in these ways (esp. C748 & C825).
// C930 precludes having both TEAM= and TEAM_NUMBER=.
// TODO C931 prohibits the use of a coindexed object as a stat-variable.
class CoarrayRef {
public:
CLASS_BOILERPLATE(CoarrayRef)
CoarrayRef(SymbolVector &&, std::vector<Subscript> &&,
std::vector<Expr<SubscriptInteger>> &&);
const SymbolVector &base() const { return base_; }
SymbolVector &base() { return base_; }
const std::vector<Subscript> &subscript() const { return subscript_; }
std::vector<Subscript> &subscript() { return subscript_; }
const std::vector<Expr<SubscriptInteger>> &cosubscript() const {
return cosubscript_;
}
std::vector<Expr<SubscriptInteger>> &cosubscript() { return cosubscript_; }
// These integral expressions for STAT= and TEAM= must be variables
// (i.e., Designator or pointer-valued FunctionRef).
std::optional<Expr<SomeInteger>> stat() const;
CoarrayRef &set_stat(Expr<SomeInteger> &&);
std::optional<Expr<SomeInteger>> team() const;
bool teamIsTeamNumber() const { return teamIsTeamNumber_; }
CoarrayRef &set_team(Expr<SomeInteger> &&, bool isTeamNumber = false);
int Rank() const;
const Symbol &GetFirstSymbol() const;
const Symbol &GetLastSymbol() const;
NamedEntity GetBase() const;
std::optional<Expr<SubscriptInteger>> LEN() const;
bool operator==(const CoarrayRef &) const;
llvm::raw_ostream &AsFortran(llvm::raw_ostream &) const;
private:
SymbolVector base_;
std::vector<Subscript> subscript_;
std::vector<Expr<SubscriptInteger>> cosubscript_;
std::optional<common::CopyableIndirection<Expr<SomeInteger>>> stat_, team_;
bool teamIsTeamNumber_{false}; // false: TEAM=, true: TEAM_NUMBER=
};
// R911 data-ref is defined syntactically as a series of part-refs, which
// would be far too expressive if the constraints were ignored. Here, the
// possible outcomes are spelled out. Note that a data-ref cannot include
// a terminal substring range or complex component designator; use
// R901 designator for that.
struct DataRef {
EVALUATE_UNION_CLASS_BOILERPLATE(DataRef)
int Rank() const;
const Symbol &GetFirstSymbol() const;
const Symbol &GetLastSymbol() const;
std::optional<Expr<SubscriptInteger>> LEN() const;
llvm::raw_ostream &AsFortran(llvm::raw_ostream &) const;
std::variant<SymbolRef, Component, ArrayRef, CoarrayRef> u;
};
// R908 substring, R909 parent-string, R910 substring-range.
// The base object of a substring can be a literal.
// In the F2018 standard, substrings of array sections are parsed as
// variants of sections instead.
class Substring {
using Parent = std::variant<DataRef, StaticDataObject::Pointer>;
public:
CLASS_BOILERPLATE(Substring)
Substring(DataRef &&parent, std::optional<Expr<SubscriptInteger>> &&lower,
std::optional<Expr<SubscriptInteger>> &&upper)
: parent_{std::move(parent)} {
SetBounds(lower, upper);
}
Substring(StaticDataObject::Pointer &&parent,
std::optional<Expr<SubscriptInteger>> &&lower,
std::optional<Expr<SubscriptInteger>> &&upper)
: parent_{std::move(parent)} {
SetBounds(lower, upper);
}
Expr<SubscriptInteger> lower() const;
Substring &set_lower(Expr<SubscriptInteger> &&);
std::optional<Expr<SubscriptInteger>> upper() const;
Substring &set_upper(Expr<SubscriptInteger> &&);
const Parent &parent() const { return parent_; }
Parent &parent() { return parent_; }
int Rank() const;
template <typename A> const A *GetParentIf() const {
return std::get_if<A>(&parent_);
}
BaseObject GetBaseObject() const;
const Symbol *GetLastSymbol() const;
std::optional<Expr<SubscriptInteger>> LEN() const;
bool operator==(const Substring &) const;
llvm::raw_ostream &AsFortran(llvm::raw_ostream &) const;
std::optional<Expr<SomeCharacter>> Fold(FoldingContext &);
private:
void SetBounds(std::optional<Expr<SubscriptInteger>> &,
std::optional<Expr<SubscriptInteger>> &);
Parent parent_;
std::optional<IndirectSubscriptIntegerExpr> lower_, upper_;
};
// R915 complex-part-designator
// In the F2018 standard, complex parts of array sections are parsed as
// variants of sections instead.
class ComplexPart {
public:
ENUM_CLASS(Part, RE, IM)
CLASS_BOILERPLATE(ComplexPart)
ComplexPart(DataRef &&z, Part p) : complex_{std::move(z)}, part_{p} {}
const DataRef &complex() const { return complex_; }
Part part() const { return part_; }
int Rank() const;
const Symbol &GetFirstSymbol() const { return complex_.GetFirstSymbol(); }
const Symbol &GetLastSymbol() const { return complex_.GetLastSymbol(); }
bool operator==(const ComplexPart &) const;
llvm::raw_ostream &AsFortran(llvm::raw_ostream &) const;
private:
DataRef complex_;
Part part_;
};
// R901 designator is the most general data reference object, apart from
// calls to pointer-valued functions. Its variant holds everything that
// a DataRef can, and possibly also a substring reference or a
// complex component (%RE/%IM) reference.
template <typename T> class Designator {
using DataRefs = std::decay_t<decltype(DataRef::u)>;
using MaybeSubstring =
std::conditional_t<T::category == TypeCategory::Character,
std::variant<Substring>, std::variant<>>;
using MaybeComplexPart = std::conditional_t<T::category == TypeCategory::Real,
std::variant<ComplexPart>, std::variant<>>;
using Variant =
common::CombineVariants<DataRefs, MaybeSubstring, MaybeComplexPart>;
public:
using Result = T;
static_assert(
IsSpecificIntrinsicType<Result> || std::is_same_v<Result, SomeDerived>);
EVALUATE_UNION_CLASS_BOILERPLATE(Designator)
Designator(const DataRef &that) : u{common::CopyVariant<Variant>(that.u)} {}
Designator(DataRef &&that)
: u{common::MoveVariant<Variant>(std::move(that.u))} {}
std::optional<DynamicType> GetType() const;
int Rank() const;
BaseObject GetBaseObject() const;
const Symbol *GetLastSymbol() const;
std::optional<Expr<SubscriptInteger>> LEN() const;
llvm::raw_ostream &AsFortran(llvm::raw_ostream &o) const;
Variant u;
};
FOR_EACH_CHARACTER_KIND(extern template class Designator, )
template <typename T> struct Variable {
using Result = T;
static_assert(IsSpecificIntrinsicType<Result> ||
std::is_same_v<Result, SomeKind<TypeCategory::Derived>>);
EVALUATE_UNION_CLASS_BOILERPLATE(Variable)
std::optional<DynamicType> GetType() const {
return std::visit([](const auto &x) { return x.GetType(); }, u);
}
int Rank() const {
return std::visit([](const auto &x) { return x.Rank(); }, u);
}
llvm::raw_ostream &AsFortran(llvm::raw_ostream &o) const {
std::visit([&](const auto &x) { x.AsFortran(o); }, u);
return o;
}
std::variant<Designator<Result>, FunctionRef<Result>> u;
};
class DescriptorInquiry {
public:
using Result = SubscriptInteger;
ENUM_CLASS(Field, LowerBound, Extent, Stride, Rank, Len)
CLASS_BOILERPLATE(DescriptorInquiry)
DescriptorInquiry(const NamedEntity &, Field, int = 0);
DescriptorInquiry(NamedEntity &&, Field, int = 0);
NamedEntity &base() { return base_; }
const NamedEntity &base() const { return base_; }
Field field() const { return field_; }
int dimension() const { return dimension_; }
static constexpr int Rank() { return 0; } // always scalar
bool operator==(const DescriptorInquiry &) const;
llvm::raw_ostream &AsFortran(llvm::raw_ostream &) const;
private:
NamedEntity base_;
Field field_;
int dimension_{0}; // zero-based
};
#define INSTANTIATE_VARIABLE_TEMPLATES \
EXPAND_FOR_EACH_INTEGER_KIND( \
TEMPLATE_INSTANTIATION, template class TypeParamInquiry, ) \
FOR_EACH_SPECIFIC_TYPE(template class Designator, )
} // namespace Fortran::evaluate
#endif // FORTRAN_EVALUATE_VARIABLE_H_

View File

@ -0,0 +1,183 @@
/*===-- include/flang/ISO_Fortran_binding.h -----------------------*- C++ -*-===
*
* 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
*
* ===-----------------------------------------------------------------------===
*/
#ifndef CFI_ISO_FORTRAN_BINDING_H_
#define CFI_ISO_FORTRAN_BINDING_H_
#include <stddef.h>
/* Standard interface to Fortran from C and C++.
* These interfaces are named in section 18.5 of the Fortran 2018
* standard, with most of the actual details being left to the
* implementation.
*/
#ifdef __cplusplus
namespace Fortran {
namespace ISO {
inline namespace Fortran_2018 {
#endif
/* 18.5.4 */
#define CFI_VERSION 20180515
#define CFI_MAX_RANK 15
typedef unsigned char CFI_rank_t;
/* This type is probably larger than a default Fortran INTEGER
* and should be used for all array indexing and loop bound calculations.
*/
typedef ptrdiff_t CFI_index_t;
typedef unsigned char CFI_attribute_t;
#define CFI_attribute_pointer 1
#define CFI_attribute_allocatable 2
#define CFI_attribute_other 0 /* neither pointer nor allocatable */
typedef signed char CFI_type_t;
/* These codes are required to be macros (i.e., #ifdef will work).
* They are not required to be distinct, but neither are they required
* to have had their synonyms combined.
* Extension: 128-bit integers are anticipated
*/
#define CFI_type_signed_char 1
#define CFI_type_short 2
#define CFI_type_int 3
#define CFI_type_long 4
#define CFI_type_long_long 5
#define CFI_type_size_t 6
#define CFI_type_int8_t 7
#define CFI_type_int16_t 8
#define CFI_type_int32_t 9
#define CFI_type_int64_t 10
#define CFI_type_int128_t 11
#define CFI_type_int_least8_t 12
#define CFI_type_int_least16_t 13
#define CFI_type_int_least32_t 14
#define CFI_type_int_least64_t 15
#define CFI_type_int_least128_t 16
#define CFI_type_int_fast8_t 17
#define CFI_type_int_fast16_t 18
#define CFI_type_int_fast32_t 19
#define CFI_type_int_fast64_t 20
#define CFI_type_int_fast128_t 21
#define CFI_type_intmax_t 22
#define CFI_type_intptr_t 23
#define CFI_type_ptrdiff_t 24
#define CFI_type_float 25
#define CFI_type_double 26
#define CFI_type_long_double 27
#define CFI_type_float_Complex 28
#define CFI_type_double_Complex 29
#define CFI_type_long_double_Complex 30
#define CFI_type_Bool 31
#define CFI_type_char 32
#define CFI_type_cptr 33
#define CFI_type_struct 34
#define CFI_type_other (-1) // must be negative
/* Error code macros */
#define CFI_SUCCESS 0 /* must be zero */
#define CFI_ERROR_BASE_ADDR_NULL 1
#define CFI_ERROR_BASE_ADDR_NOT_NULL 2
#define CFI_INVALID_ELEM_LEN 3
#define CFI_INVALID_RANK 4
#define CFI_INVALID_TYPE 5
#define CFI_INVALID_ATTRIBUTE 6
#define CFI_INVALID_EXTENT 7
#define CFI_INVALID_DESCRIPTOR 8
#define CFI_ERROR_MEM_ALLOCATION 9
#define CFI_ERROR_OUT_OF_BOUNDS 10
/* 18.5.2 per-dimension information */
typedef struct CFI_dim_t {
CFI_index_t lower_bound;
CFI_index_t extent; /* == -1 for assumed size */
CFI_index_t sm; /* memory stride in bytes */
} CFI_dim_t;
#ifdef __cplusplus
namespace cfi_internal {
// C++ does not support flexible array.
// The below structure emulates a flexible array. This structure does not take
// care of getting the memory storage. Note that it already contains one element
// because a struct cannot be empty.
template <typename T> struct FlexibleArray : T {
T &operator[](int index) { return *(this + index); }
const T &operator[](int index) const { return *(this + index); }
operator T *() { return this; }
operator const T *() const { return this; }
};
} // namespace cfi_internal
#endif
/* 18.5.3 generic data descriptor */
typedef struct CFI_cdesc_t {
/* These three members must appear first, in exactly this order. */
void *base_addr;
size_t elem_len; /* element size in bytes */
int version; /* == CFI_VERSION */
CFI_rank_t rank; /* [0 .. CFI_MAX_RANK] */
CFI_type_t type;
CFI_attribute_t attribute;
unsigned char f18Addendum;
#ifdef __cplusplus
cfi_internal::FlexibleArray<CFI_dim_t> dim;
#else
CFI_dim_t dim[]; /* must appear last */
#endif
} CFI_cdesc_t;
/* 18.5.4 */
#ifdef __cplusplus
// The struct below take care of getting the memory storage for C++ CFI_cdesc_t
// that contain an emulated flexible array.
namespace cfi_internal {
template <int r> struct CdescStorage : public CFI_cdesc_t {
static_assert((r > 1 && r <= CFI_MAX_RANK), "CFI_INVALID_RANK");
CFI_dim_t dim[r - 1];
};
template <> struct CdescStorage<1> : public CFI_cdesc_t {};
template <> struct CdescStorage<0> : public CFI_cdesc_t {};
} // namespace cfi_internal
#define CFI_CDESC_T(rank) cfi_internal::CdescStorage<rank>
#else
#define CFI_CDESC_T(rank) \
struct { \
CFI_cdesc_t cdesc; /* must be first */ \
CFI_dim_t dim[rank]; \
}
#endif
/* 18.5.5 procedural interfaces*/
#ifdef __cplusplus
extern "C" {
#endif
void *CFI_address(const CFI_cdesc_t *, const CFI_index_t subscripts[]);
int CFI_allocate(CFI_cdesc_t *, const CFI_index_t lower_bounds[],
const CFI_index_t upper_bounds[], size_t elem_len);
int CFI_deallocate(CFI_cdesc_t *);
int CFI_establish(CFI_cdesc_t *, void *base_addr, CFI_attribute_t, CFI_type_t,
size_t elem_len, CFI_rank_t, const CFI_index_t extents[]);
int CFI_is_contiguous(const CFI_cdesc_t *);
int CFI_section(CFI_cdesc_t *, const CFI_cdesc_t *source,
const CFI_index_t lower_bounds[], const CFI_index_t upper_bounds[],
const CFI_index_t strides[]);
int CFI_select_part(CFI_cdesc_t *, const CFI_cdesc_t *source,
size_t displacement, size_t elem_len);
int CFI_setpointer(
CFI_cdesc_t *, const CFI_cdesc_t *source, const CFI_index_t lower_bounds[]);
#ifdef __cplusplus
} // extern "C"
} // inline namespace Fortran_2018
}
}
#endif
#endif /* CFI_ISO_FORTRAN_BINDING_H_ */

View File

@ -0,0 +1,2 @@
BasedOnStyle: LLVM
AlwaysBreakTemplateDeclarations: Yes

View File

@ -0,0 +1,397 @@
//===-- include/flang/Lower/PFTBuilder.h ------------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_LOWER_PFT_BUILDER_H_
#define FORTRAN_LOWER_PFT_BUILDER_H_
#include "flang/Common/template.h"
#include "flang/Parser/parse-tree.h"
#include <memory>
/// Build a light-weight tree over the parse-tree to help with lowering to FIR.
/// It is named Pre-FIR Tree (PFT) to underline it has no other usage than
/// helping lowering to FIR.
/// The PFT will capture pointers back into the parse tree, so the parse tree
/// data structure may <em>not</em> be changed between the construction of the
/// PFT and all of its uses.
///
/// The PFT captures a structured view of the program. The program is a list of
/// units. Function like units will contain lists of evaluations. Evaluations
/// are either statements or constructs, where a construct contains a list of
/// evaluations. The resulting PFT structure can then be used to create FIR.
namespace llvm {
class raw_ostream;
}
namespace Fortran::lower {
namespace pft {
struct Evaluation;
struct Program;
struct ModuleLikeUnit;
struct FunctionLikeUnit;
// TODO: A collection of Evaluations can obviously be any of the container
// types; leaving this as a std::list _for now_ because we reserve the right to
// insert PFT nodes in any order in O(1) time.
using EvaluationCollection = std::list<Evaluation>;
struct ParentType {
template <typename A>
ParentType(A &parent) : p{&parent} {}
const std::variant<Program *, ModuleLikeUnit *, FunctionLikeUnit *,
Evaluation *>
p;
};
/// Flags to describe the impact of parse-trees nodes on the program
/// control flow. These annotations to parse-tree nodes are later used to
/// build the control flow graph when lowering to FIR.
enum class CFGAnnotation {
None, // Node does not impact control flow.
Goto, // Node acts like a goto on the control flow.
CondGoto, // Node acts like a conditional goto on the control flow.
IndGoto, // Node acts like an indirect goto on the control flow.
IoSwitch, // Node is an IO statement with ERR, END, or EOR specifier.
Switch, // Node acts like a switch on the control flow.
Iterative, // Node creates iterations in the control flow.
FirStructuredOp, // Node is a structured loop.
Return, // Node triggers a return from the current procedure.
Terminate // Node terminates the program.
};
/// Compiler-generated jump
///
/// This is used to convert implicit control-flow edges to explicit form in the
/// decorated PFT
struct CGJump {
CGJump(Evaluation &to) : target{to} {}
Evaluation &target;
};
/// Classify the parse-tree nodes from ExecutablePartConstruct
using ActionStmts = std::tuple<
parser::AllocateStmt, parser::AssignmentStmt, parser::BackspaceStmt,
parser::CallStmt, parser::CloseStmt, parser::ContinueStmt,
parser::CycleStmt, parser::DeallocateStmt, parser::EndfileStmt,
parser::EventPostStmt, parser::EventWaitStmt, parser::ExitStmt,
parser::FailImageStmt, parser::FlushStmt, parser::FormTeamStmt,
parser::GotoStmt, parser::IfStmt, parser::InquireStmt, parser::LockStmt,
parser::NullifyStmt, parser::OpenStmt, parser::PointerAssignmentStmt,
parser::PrintStmt, parser::ReadStmt, parser::ReturnStmt, parser::RewindStmt,
parser::StopStmt, parser::SyncAllStmt, parser::SyncImagesStmt,
parser::SyncMemoryStmt, parser::SyncTeamStmt, parser::UnlockStmt,
parser::WaitStmt, parser::WhereStmt, parser::WriteStmt,
parser::ComputedGotoStmt, parser::ForallStmt, parser::ArithmeticIfStmt,
parser::AssignStmt, parser::AssignedGotoStmt, parser::PauseStmt>;
using OtherStmts = std::tuple<parser::FormatStmt, parser::EntryStmt,
parser::DataStmt, parser::NamelistStmt>;
using Constructs =
std::tuple<parser::AssociateConstruct, parser::BlockConstruct,
parser::CaseConstruct, parser::ChangeTeamConstruct,
parser::CriticalConstruct, parser::DoConstruct,
parser::IfConstruct, parser::SelectRankConstruct,
parser::SelectTypeConstruct, parser::WhereConstruct,
parser::ForallConstruct, parser::CompilerDirective,
parser::OpenMPConstruct, parser::OmpEndLoopDirective>;
using ConstructStmts = std::tuple<
parser::AssociateStmt, parser::EndAssociateStmt, parser::BlockStmt,
parser::EndBlockStmt, parser::SelectCaseStmt, parser::CaseStmt,
parser::EndSelectStmt, parser::ChangeTeamStmt, parser::EndChangeTeamStmt,
parser::CriticalStmt, parser::EndCriticalStmt, parser::NonLabelDoStmt,
parser::EndDoStmt, parser::IfThenStmt, parser::ElseIfStmt, parser::ElseStmt,
parser::EndIfStmt, parser::SelectRankStmt, parser::SelectRankCaseStmt,
parser::SelectTypeStmt, parser::TypeGuardStmt, parser::WhereConstructStmt,
parser::MaskedElsewhereStmt, parser::ElsewhereStmt, parser::EndWhereStmt,
parser::ForallConstructStmt, parser::EndForallStmt>;
template <typename A>
constexpr static bool isActionStmt{common::HasMember<A, ActionStmts>};
template <typename A>
constexpr static bool isConstruct{common::HasMember<A, Constructs>};
template <typename A>
constexpr static bool isConstructStmt{common::HasMember<A, ConstructStmts>};
template <typename A>
constexpr static bool isOtherStmt{common::HasMember<A, OtherStmts>};
template <typename A>
constexpr static bool isGenerated{std::is_same_v<A, CGJump>};
template <typename A>
constexpr static bool isFunctionLike{common::HasMember<
A, std::tuple<parser::MainProgram, parser::FunctionSubprogram,
parser::SubroutineSubprogram,
parser::SeparateModuleSubprogram>>};
/// Function-like units can contains lists of evaluations. These can be
/// (simple) statements or constructs, where a construct contains its own
/// evaluations.
struct Evaluation {
using EvalTuple = common::CombineTuples<ActionStmts, OtherStmts, Constructs,
ConstructStmts>;
/// Hide non-nullable pointers to the parse-tree node.
template <typename A>
using MakeRefType = const A *const;
using EvalVariant =
common::CombineVariants<common::MapTemplate<MakeRefType, EvalTuple>,
std::variant<CGJump>>;
template <typename A>
constexpr auto visit(A visitor) const {
return std::visit(common::visitors{
[&](const auto *p) { return visitor(*p); },
[&](auto &r) { return visitor(r); },
},
u);
}
template <typename A>
constexpr const A *getIf() const {
if constexpr (!std::is_same_v<A, CGJump>) {
if (auto *ptr{std::get_if<MakeRefType<A>>(&u)}) {
return *ptr;
}
} else {
return std::get_if<CGJump>(&u);
}
return nullptr;
}
template <typename A>
constexpr bool isA() const {
if constexpr (!std::is_same_v<A, CGJump>) {
return std::holds_alternative<MakeRefType<A>>(u);
}
return std::holds_alternative<CGJump>(u);
}
Evaluation() = delete;
Evaluation(const Evaluation &) = delete;
Evaluation(Evaluation &&) = default;
/// General ctor
template <typename A>
Evaluation(const A &a, const ParentType &p, const parser::CharBlock &pos,
const std::optional<parser::Label> &lab)
: u{&a}, parent{p}, pos{pos}, lab{lab} {}
/// Compiler-generated jump
Evaluation(const CGJump &jump, const ParentType &p)
: u{jump}, parent{p}, cfg{CFGAnnotation::Goto} {}
/// Construct ctor
template <typename A>
Evaluation(const A &a, const ParentType &parent) : u{&a}, parent{parent} {
static_assert(pft::isConstruct<A>, "must be a construct");
}
constexpr bool isActionOrGenerated() const {
return visit(common::visitors{
[](auto &r) {
using T = std::decay_t<decltype(r)>;
return isActionStmt<T> || isGenerated<T>;
},
});
}
constexpr bool isStmt() const {
return visit(common::visitors{
[](auto &r) {
using T = std::decay_t<decltype(r)>;
static constexpr bool isStmt{isActionStmt<T> || isOtherStmt<T> ||
isConstructStmt<T>};
static_assert(!(isStmt && pft::isConstruct<T>),
"statement classification is inconsistent");
return isStmt;
},
});
}
constexpr bool isConstruct() const { return !isStmt(); }
/// Set the type of originating control flow type for this evaluation.
void setCFG(CFGAnnotation a, Evaluation *cstr) {
cfg = a;
setBranches(cstr);
}
/// Is this evaluation a control-flow origin? (The PFT must be annotated)
bool isControlOrigin() const { return cfg != CFGAnnotation::None; }
/// Is this evaluation a control-flow target? (The PFT must be annotated)
bool isControlTarget() const { return isTarget; }
/// Set the containsBranches flag iff this evaluation (a construct) contains
/// control flow
void setBranches() { containsBranches = true; }
EvaluationCollection *getConstructEvals() {
auto *evals{subs.get()};
if (isStmt() && !evals) {
return nullptr;
}
if (isConstruct() && evals) {
return evals;
}
llvm_unreachable("evaluation subs is inconsistent");
return nullptr;
}
/// Set that the construct `cstr` (if not a nullptr) has branches.
static void setBranches(Evaluation *cstr) {
if (cstr)
cstr->setBranches();
}
EvalVariant u;
ParentType parent;
parser::CharBlock pos;
std::optional<parser::Label> lab;
std::unique_ptr<EvaluationCollection> subs; // construct sub-statements
CFGAnnotation cfg{CFGAnnotation::None};
bool isTarget{false}; // this evaluation is a control target
bool containsBranches{false}; // construct contains branches
};
/// A program is a list of program units.
/// These units can be function like, module like, or block data
struct ProgramUnit {
template <typename A>
ProgramUnit(const A &ptr, const ParentType &parent)
: p{&ptr}, parent{parent} {}
ProgramUnit(ProgramUnit &&) = default;
ProgramUnit(const ProgramUnit &) = delete;
const std::variant<
const parser::MainProgram *, const parser::FunctionSubprogram *,
const parser::SubroutineSubprogram *, const parser::Module *,
const parser::Submodule *, const parser::SeparateModuleSubprogram *,
const parser::BlockData *>
p;
ParentType parent;
};
/// Function-like units have similar structure. They all can contain executable
/// statements as well as other function-like units (internal procedures and
/// function statements).
struct FunctionLikeUnit : public ProgramUnit {
// wrapper statements for function-like syntactic structures
using FunctionStatement =
std::variant<const parser::Statement<parser::ProgramStmt> *,
const parser::Statement<parser::EndProgramStmt> *,
const parser::Statement<parser::FunctionStmt> *,
const parser::Statement<parser::EndFunctionStmt> *,
const parser::Statement<parser::SubroutineStmt> *,
const parser::Statement<parser::EndSubroutineStmt> *,
const parser::Statement<parser::MpSubprogramStmt> *,
const parser::Statement<parser::EndMpSubprogramStmt> *>;
FunctionLikeUnit(const parser::MainProgram &f, const ParentType &parent);
FunctionLikeUnit(const parser::FunctionSubprogram &f,
const ParentType &parent);
FunctionLikeUnit(const parser::SubroutineSubprogram &f,
const ParentType &parent);
FunctionLikeUnit(const parser::SeparateModuleSubprogram &f,
const ParentType &parent);
FunctionLikeUnit(FunctionLikeUnit &&) = default;
FunctionLikeUnit(const FunctionLikeUnit &) = delete;
bool isMainProgram() {
return std::holds_alternative<
const parser::Statement<parser::EndProgramStmt> *>(endStmt);
}
const parser::FunctionStmt *getFunction() {
return getA<parser::FunctionStmt>();
}
const parser::SubroutineStmt *getSubroutine() {
return getA<parser::SubroutineStmt>();
}
const parser::MpSubprogramStmt *getMPSubp() {
return getA<parser::MpSubprogramStmt>();
}
/// Anonymous programs do not have a begin statement
std::optional<FunctionStatement> beginStmt;
FunctionStatement endStmt;
EvaluationCollection evals; // statements
std::list<FunctionLikeUnit> funcs; // internal procedures
private:
template <typename A>
const A *getA() {
if (beginStmt) {
if (auto p =
std::get_if<const parser::Statement<A> *>(&beginStmt.value()))
return &(*p)->statement;
}
return nullptr;
}
};
/// Module-like units have similar structure. They all can contain a list of
/// function-like units.
struct ModuleLikeUnit : public ProgramUnit {
// wrapper statements for module-like syntactic structures
using ModuleStatement =
std::variant<const parser::Statement<parser::ModuleStmt> *,
const parser::Statement<parser::EndModuleStmt> *,
const parser::Statement<parser::SubmoduleStmt> *,
const parser::Statement<parser::EndSubmoduleStmt> *>;
ModuleLikeUnit(const parser::Module &m, const ParentType &parent);
ModuleLikeUnit(const parser::Submodule &m, const ParentType &parent);
~ModuleLikeUnit() = default;
ModuleLikeUnit(ModuleLikeUnit &&) = default;
ModuleLikeUnit(const ModuleLikeUnit &) = delete;
ModuleStatement beginStmt;
ModuleStatement endStmt;
std::list<FunctionLikeUnit> funcs;
};
struct BlockDataUnit : public ProgramUnit {
BlockDataUnit(const parser::BlockData &bd, const ParentType &parent);
BlockDataUnit(BlockDataUnit &&) = default;
BlockDataUnit(const BlockDataUnit &) = delete;
};
/// A Program is the top-level PFT
struct Program {
using Units = std::variant<FunctionLikeUnit, ModuleLikeUnit, BlockDataUnit>;
Program() = default;
Program(Program &&) = default;
Program(const Program &) = delete;
std::list<Units> &getUnits() { return units; }
private:
std::list<Units> units;
};
} // namespace pft
/// Create an PFT from the parse tree
std::unique_ptr<pft::Program> createPFT(const parser::Program &root);
/// Decorate the PFT with control flow annotations
///
/// The PFT must be decorated with control-flow annotations to prepare it for
/// use in generating a CFG-like structure.
void annotateControl(pft::Program &);
void dumpPFT(llvm::raw_ostream &o, pft::Program &);
} // namespace Fortran::lower
#endif // FORTRAN_LOWER_PFT_BUILDER_H_

View File

@ -0,0 +1,2 @@
BasedOnStyle: LLVM
AlwaysBreakTemplateDeclarations: Yes

View File

@ -0,0 +1 @@
add_subdirectory(Dialect)

View File

@ -0,0 +1,22 @@
# This replicates part of the add_mlir_dialect cmake function from MLIR that
# cannot be used her because it expects to be run inside MLIR directory which
# is not the case for FIR.
set(LLVM_TARGET_DEFINITIONS FIROps.td)
mlir_tablegen(FIROps.h.inc -gen-op-decls)
mlir_tablegen(FIROps.cpp.inc -gen-op-defs)
add_public_tablegen_target(FIROpsIncGen)
add_custom_target(flang-doc)
set(dialect_doc_filename "FIRLangRef")
set(LLVM_TARGET_DEFINITIONS FIROps.td)
tablegen(MLIR ${dialect_doc_filename}.md -gen-op-doc "-I${MLIR_MAIN_SRC_DIR}" "-I${MLIR_INCLUDE_DIR}")
set(GEN_DOC_FILE ${FLANG_BINARY_DIR}/docs/Dialect/${dialect_doc_filename}.md)
add_custom_command(
OUTPUT ${GEN_DOC_FILE}
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_BINARY_DIR}/${dialect_doc_filename}.md
${GEN_DOC_FILE}
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${dialect_doc_filename}.md)
add_custom_target(${dialect_doc_filename}DocGen DEPENDS ${GEN_DOC_FILE})
add_dependencies(flang-doc ${dialect_doc_filename}DocGen)

View File

@ -0,0 +1,166 @@
//===-- Optimizer/Dialect/FIRAttr.h -- FIR attributes -----------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef OPTIMIZER_DIALECT_FIRATTR_H
#define OPTIMIZER_DIALECT_FIRATTR_H
#include "mlir/IR/Attributes.h"
namespace mlir {
class DialectAsmParser;
class DialectAsmPrinter;
} // namespace mlir
namespace fir {
class FIROpsDialect;
namespace detail {
struct RealAttributeStorage;
struct TypeAttributeStorage;
} // namespace detail
enum AttributeKind {
FIR_ATTR = mlir::Attribute::FIRST_FIR_ATTR,
FIR_EXACTTYPE, // instance_of, precise type relation
FIR_SUBCLASS, // subsumed_by, is-a (subclass) relation
FIR_POINT,
FIR_CLOSEDCLOSED_INTERVAL,
FIR_OPENCLOSED_INTERVAL,
FIR_CLOSEDOPEN_INTERVAL,
FIR_REAL_ATTR,
};
class ExactTypeAttr
: public mlir::Attribute::AttrBase<ExactTypeAttr, mlir::Attribute,
detail::TypeAttributeStorage> {
public:
using Base::Base;
using ValueType = mlir::Type;
static constexpr llvm::StringRef getAttrName() { return "instance"; }
static ExactTypeAttr get(mlir::Type value);
mlir::Type getType() const;
static constexpr bool kindof(unsigned kind) { return kind == getId(); }
static constexpr unsigned getId() { return AttributeKind::FIR_EXACTTYPE; }
};
class SubclassAttr
: public mlir::Attribute::AttrBase<SubclassAttr, mlir::Attribute,
detail::TypeAttributeStorage> {
public:
using Base::Base;
using ValueType = mlir::Type;
static constexpr llvm::StringRef getAttrName() { return "subsumed"; }
static SubclassAttr get(mlir::Type value);
mlir::Type getType() const;
static constexpr bool kindof(unsigned kind) { return kind == getId(); }
static constexpr unsigned getId() { return AttributeKind::FIR_SUBCLASS; }
};
// Attributes for building SELECT CASE multiway branches
/// A closed interval (including the bound values) is an interval with both an
/// upper and lower bound as given as ssa-values.
/// A case selector of `CASE (n:m)` corresponds to any value from `n` to `m` and
/// is encoded as `#fir.interval, %n, %m`.
class ClosedIntervalAttr
: public mlir::Attribute::AttrBase<ClosedIntervalAttr> {
public:
using Base::Base;
static constexpr llvm::StringRef getAttrName() { return "interval"; }
static ClosedIntervalAttr get(mlir::MLIRContext *ctxt);
static constexpr bool kindof(unsigned kind) { return kind == getId(); }
static constexpr unsigned getId() {
return AttributeKind::FIR_CLOSEDCLOSED_INTERVAL;
}
};
/// An upper bound is an open interval (including the bound value) as given as
/// an ssa-value.
/// A case selector of `CASE (:m)` corresponds to any value up to and including
/// `m` and is encoded as `#fir.upper, %m`.
class UpperBoundAttr : public mlir::Attribute::AttrBase<UpperBoundAttr> {
public:
using Base::Base;
static constexpr llvm::StringRef getAttrName() { return "upper"; }
static UpperBoundAttr get(mlir::MLIRContext *ctxt);
static constexpr bool kindof(unsigned kind) { return kind == getId(); }
static constexpr unsigned getId() {
return AttributeKind::FIR_OPENCLOSED_INTERVAL;
}
};
/// A lower bound is an open interval (including the bound value) as given as
/// an ssa-value.
/// A case selector of `CASE (n:)` corresponds to any value down to and
/// including `n` and is encoded as `#fir.lower, %n`.
class LowerBoundAttr : public mlir::Attribute::AttrBase<LowerBoundAttr> {
public:
using Base::Base;
static constexpr llvm::StringRef getAttrName() { return "lower"; }
static LowerBoundAttr get(mlir::MLIRContext *ctxt);
static constexpr bool kindof(unsigned kind) { return kind == getId(); }
static constexpr unsigned getId() {
return AttributeKind::FIR_CLOSEDOPEN_INTERVAL;
}
};
/// A pointer interval is a closed interval as given as an ssa-value. The
/// interval contains exactly one value.
/// A case selector of `CASE (p)` corresponds to exactly the value `p` and is
/// encoded as `#fir.point, %p`.
class PointIntervalAttr : public mlir::Attribute::AttrBase<PointIntervalAttr> {
public:
using Base::Base;
static constexpr llvm::StringRef getAttrName() { return "point"; }
static PointIntervalAttr get(mlir::MLIRContext *ctxt);
static constexpr bool kindof(unsigned kind) { return kind == getId(); }
static constexpr unsigned getId() { return AttributeKind::FIR_POINT; }
};
/// A real attribute is used to workaround MLIR's default parsing of a real
/// constant.
/// `#fir.real<10, 3.14>` is used to introduce a real constant of value `3.14`
/// with a kind of `10`.
class RealAttr
: public mlir::Attribute::AttrBase<RealAttr, mlir::Attribute,
detail::RealAttributeStorage> {
public:
using Base::Base;
using ValueType = std::pair<int, llvm::APFloat>;
static constexpr llvm::StringRef getAttrName() { return "real"; }
static RealAttr get(mlir::MLIRContext *ctxt, const ValueType &key);
int getFKind() const;
llvm::APFloat getValue() const;
static constexpr bool kindof(unsigned kind) { return kind == getId(); }
static constexpr unsigned getId() { return AttributeKind::FIR_REAL_ATTR; }
};
mlir::Attribute parseFirAttribute(FIROpsDialect *dialect,
mlir::DialectAsmParser &parser,
mlir::Type type);
void printFirAttribute(FIROpsDialect *dialect, mlir::Attribute attr,
mlir::DialectAsmPrinter &p);
} // namespace fir
#endif // OPTIMIZER_DIALECT_FIRATTR_H

View File

@ -0,0 +1,92 @@
//===-- Optimizer/Dialect/FIRDialect.h -- FIR dialect -----------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef OPTIMIZER_DIALECT_FIRDIALECT_H
#define OPTIMIZER_DIALECT_FIRDIALECT_H
#include "mlir/IR/Dialect.h"
#include "mlir/InitAllDialects.h"
#include "mlir/InitAllPasses.h"
namespace llvm {
class raw_ostream;
class StringRef;
} // namespace llvm
namespace mlir {
class Attribute;
class DialectAsmParser;
class DialectAsmPrinter;
class Location;
class MLIRContext;
class Type;
} // namespace mlir
namespace fir {
/// FIR dialect
class FIROpsDialect final : public mlir::Dialect {
public:
explicit FIROpsDialect(mlir::MLIRContext *ctx);
virtual ~FIROpsDialect();
static llvm::StringRef getDialectNamespace() { return "fir"; }
mlir::Type parseType(mlir::DialectAsmParser &parser) const override;
void printType(mlir::Type ty, mlir::DialectAsmPrinter &p) const override;
mlir::Attribute parseAttribute(mlir::DialectAsmParser &parser,
mlir::Type type) const override;
void printAttribute(mlir::Attribute attr,
mlir::DialectAsmPrinter &p) const override;
};
/// Register the dialect with MLIR
inline void registerFIR() {
// we want to register exactly once
[[maybe_unused]] static bool init_once = [] {
mlir::registerDialect<mlir::AffineDialect>();
mlir::registerDialect<mlir::LLVM::LLVMDialect>();
mlir::registerDialect<mlir::loop::LoopOpsDialect>();
mlir::registerDialect<mlir::StandardOpsDialect>();
mlir::registerDialect<mlir::vector::VectorDialect>();
mlir::registerDialect<FIROpsDialect>();
return true;
}();
}
/// Register the standard passes we use. This comes from registerAllPasses(),
/// but is a smaller set since we aren't using many of the passes found there.
inline void registerGeneralPasses() {
mlir::createCanonicalizerPass();
mlir::createCSEPass();
mlir::createSuperVectorizePass({});
mlir::createLoopUnrollPass();
mlir::createLoopUnrollAndJamPass();
mlir::createSimplifyAffineStructuresPass();
mlir::createLoopFusionPass();
mlir::createLoopInvariantCodeMotionPass();
mlir::createAffineLoopInvariantCodeMotionPass();
mlir::createPipelineDataTransferPass();
mlir::createLowerAffinePass();
mlir::createLoopTilingPass(0);
mlir::createLoopCoalescingPass();
mlir::createAffineDataCopyGenerationPass(0, 0);
mlir::createMemRefDataFlowOptPass();
mlir::createStripDebugInfoPass();
mlir::createPrintOpStatsPass();
mlir::createInlinerPass();
mlir::createSymbolDCEPass();
mlir::createLocationSnapshotPass({});
}
inline void registerFIRPasses() { registerGeneralPasses(); }
} // namespace fir
#endif // OPTIMIZER_DIALECT_FIRDIALECT_H

View File

@ -0,0 +1,47 @@
//===-- Optimizer/Dialect/FIROps.h - FIR operations -------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef OPTIMIZER_DIALECT_FIROPS_H
#define OPTIMIZER_DIALECT_FIROPS_H
#include "mlir/Dialect/StandardOps/IR/Ops.h"
using namespace mlir;
namespace fir {
class FirEndOp;
class LoopOp;
class RealAttr;
void buildCmpFOp(mlir::Builder *builder, mlir::OperationState &result,
mlir::CmpFPredicate predicate, mlir::Value lhs,
mlir::Value rhs);
void buildCmpCOp(mlir::Builder *builder, mlir::OperationState &result,
mlir::CmpFPredicate predicate, mlir::Value lhs,
mlir::Value rhs);
unsigned getCaseArgumentOffset(llvm::ArrayRef<mlir::Attribute> cases,
unsigned dest);
LoopOp getForInductionVarOwner(mlir::Value val);
bool isReferenceLike(mlir::Type type);
mlir::ParseResult isValidCaseAttr(mlir::Attribute attr);
mlir::ParseResult parseCmpfOp(mlir::OpAsmParser &parser,
mlir::OperationState &result);
mlir::ParseResult parseCmpcOp(mlir::OpAsmParser &parser,
mlir::OperationState &result);
mlir::ParseResult parseSelector(mlir::OpAsmParser &parser,
mlir::OperationState &result,
mlir::OpAsmParser::OperandType &selector,
mlir::Type &type);
#define GET_OP_CLASSES
#include "flang/Optimizer/Dialect/FIROps.h.inc"
} // namespace fir
#endif // OPTIMIZER_DIALECT_FIROPS_H

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,63 @@
//===-- Optimizer/Dialect/FIROpsSupport.h -- FIR op support -----*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef OPTIMIZER_DIALECT_FIROPSSUPPORT_H
#define OPTIMIZER_DIALECT_FIROPSSUPPORT_H
#include "flang/Optimizer/Dialect/FIROps.h"
#include "mlir/Dialect/StandardOps/IR/Ops.h"
namespace fir {
/// return true iff the Operation is a non-volatile LoadOp
inline bool nonVolatileLoad(mlir::Operation *op) {
if (auto load = dyn_cast<fir::LoadOp>(op))
return !load.getAttr("volatile");
return false;
}
/// return true iff the Operation is a call
inline bool isaCall(mlir::Operation *op) {
return isa<fir::CallOp>(op) || isa<fir::DispatchOp>(op) ||
isa<mlir::CallOp>(op) || isa<mlir::CallIndirectOp>(op);
}
/// return true iff the Operation is a fir::CallOp, fir::DispatchOp,
/// mlir::CallOp, or mlir::CallIndirectOp and not pure
/// NB: this is not the same as `!pureCall(op)`
inline bool impureCall(mlir::Operation *op) {
// Should we also auto-detect that the called function is pure if its
// arguments are not references? For now, rely on a "pure" attribute.
return op && isaCall(op) && !op->getAttr("pure");
}
/// return true iff the Operation is a fir::CallOp, fir::DispatchOp,
/// mlir::CallOp, or mlir::CallIndirectOp and is also pure.
/// NB: this is not the same as `!impureCall(op)`
inline bool pureCall(mlir::Operation *op) {
// Should we also auto-detect that the called function is pure if its
// arguments are not references? For now, rely on a "pure" attribute.
return op && isaCall(op) && op->getAttr("pure");
}
/// Get or create a FuncOp in a module.
///
/// If `module` already contains FuncOp `name`, it is returned. Otherwise, a new
/// FuncOp is created, and that new FuncOp is returned.
mlir::FuncOp createFuncOp(mlir::Location loc, mlir::ModuleOp module,
llvm::StringRef name, mlir::FunctionType type,
llvm::ArrayRef<mlir::NamedAttribute> attrs = {});
/// Get or create a GlobalOp in a module.
fir::GlobalOp createGlobalOp(mlir::Location loc, mlir::ModuleOp module,
llvm::StringRef name, mlir::Type type,
llvm::ArrayRef<mlir::NamedAttribute> attrs = {});
} // namespace fir
#endif // OPTIMIZER_DIALECT_FIROPSSUPPORT_H

View File

@ -0,0 +1,399 @@
//===-- Optimizer/Dialect/FIRType.h -- FIR types ----------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef OPTIMIZER_DIALECT_FIRTYPE_H
#define OPTIMIZER_DIALECT_FIRTYPE_H
#include "mlir/IR/Attributes.h"
#include "mlir/IR/Types.h"
#include "llvm/ADT/SmallVector.h"
namespace llvm {
class raw_ostream;
class StringRef;
template <typename>
class ArrayRef;
class hash_code;
} // namespace llvm
namespace mlir {
class DialectAsmParser;
class DialectAsmPrinter;
} // namespace mlir
namespace fir {
class FIROpsDialect;
using KindTy = int;
namespace detail {
struct BoxTypeStorage;
struct BoxCharTypeStorage;
struct BoxProcTypeStorage;
struct CharacterTypeStorage;
struct CplxTypeStorage;
struct DimsTypeStorage;
struct FieldTypeStorage;
struct HeapTypeStorage;
struct IntTypeStorage;
struct LenTypeStorage;
struct LogicalTypeStorage;
struct PointerTypeStorage;
struct RealTypeStorage;
struct RecordTypeStorage;
struct ReferenceTypeStorage;
struct SequenceTypeStorage;
struct TypeDescTypeStorage;
} // namespace detail
/// Integral identifier for all the types comprising the FIR type system
enum TypeKind {
// The enum starts at the range reserved for this dialect.
FIR_TYPE = mlir::Type::FIRST_FIR_TYPE,
FIR_BOX, // (static) descriptor
FIR_BOXCHAR, // CHARACTER pointer and length
FIR_BOXPROC, // procedure with host association
FIR_CHARACTER, // intrinsic type
FIR_COMPLEX, // intrinsic type
FIR_DERIVED, // derived
FIR_DIMS,
FIR_FIELD,
FIR_HEAP,
FIR_INT, // intrinsic type
FIR_LEN,
FIR_LOGICAL, // intrinsic type
FIR_POINTER, // POINTER attr
FIR_REAL, // intrinsic type
FIR_REFERENCE,
FIR_SEQUENCE, // DIMENSION attr
FIR_TYPEDESC,
};
// These isa_ routines follow the precedent of llvm::isa_or_null<>
/// Is `t` any of the FIR dialect types?
bool isa_fir_type(mlir::Type t);
/// Is `t` any of the Standard dialect types?
bool isa_std_type(mlir::Type t);
/// Is `t` any of the FIR dialect or Standard dialect types?
bool isa_fir_or_std_type(mlir::Type t);
/// Is `t` a FIR dialect type that implies a memory (de)reference?
bool isa_ref_type(mlir::Type t);
/// Is `t` a FIR dialect aggregate type?
bool isa_aggregate(mlir::Type t);
/// Extract the `Type` pointed to from a FIR memory reference type. If `t` is
/// not a memory reference type, then returns a null `Type`.
mlir::Type dyn_cast_ptrEleTy(mlir::Type t);
/// Boilerplate mixin template
template <typename A, unsigned Id>
struct IntrinsicTypeMixin {
static constexpr bool kindof(unsigned kind) { return kind == getId(); }
static constexpr unsigned getId() { return Id; }
};
// Intrinsic types
/// Model of the Fortran CHARACTER intrinsic type, including the KIND type
/// parameter. The model does not include a LEN type parameter. A CharacterType
/// is thus the type of a single character value.
class CharacterType
: public mlir::Type::TypeBase<CharacterType, mlir::Type,
detail::CharacterTypeStorage>,
public IntrinsicTypeMixin<CharacterType, TypeKind::FIR_CHARACTER> {
public:
using Base::Base;
static CharacterType get(mlir::MLIRContext *ctxt, KindTy kind);
KindTy getFKind() const;
};
/// Model of a Fortran COMPLEX intrinsic type, including the KIND type
/// parameter. COMPLEX is a floating point type with a real and imaginary
/// member.
class CplxType : public mlir::Type::TypeBase<CplxType, mlir::Type,
detail::CplxTypeStorage>,
public IntrinsicTypeMixin<CplxType, TypeKind::FIR_COMPLEX> {
public:
using Base::Base;
static CplxType get(mlir::MLIRContext *ctxt, KindTy kind);
KindTy getFKind() const;
};
/// Model of a Fortran INTEGER intrinsic type, including the KIND type
/// parameter.
class IntType
: public mlir::Type::TypeBase<IntType, mlir::Type, detail::IntTypeStorage>,
public IntrinsicTypeMixin<IntType, TypeKind::FIR_INT> {
public:
using Base::Base;
static IntType get(mlir::MLIRContext *ctxt, KindTy kind);
KindTy getFKind() const;
};
/// Model of a Fortran LOGICAL intrinsic type, including the KIND type
/// parameter.
class LogicalType
: public mlir::Type::TypeBase<LogicalType, mlir::Type,
detail::LogicalTypeStorage>,
public IntrinsicTypeMixin<LogicalType, TypeKind::FIR_LOGICAL> {
public:
using Base::Base;
static LogicalType get(mlir::MLIRContext *ctxt, KindTy kind);
KindTy getFKind() const;
};
/// Model of a Fortran REAL (and DOUBLE PRECISION) intrinsic type, including the
/// KIND type parameter.
class RealType : public mlir::Type::TypeBase<RealType, mlir::Type,
detail::RealTypeStorage>,
public IntrinsicTypeMixin<RealType, TypeKind::FIR_REAL> {
public:
using Base::Base;
static RealType get(mlir::MLIRContext *ctxt, KindTy kind);
KindTy getFKind() const;
};
// FIR support types
/// The type of a Fortran descriptor. Descriptors are tuples of information that
/// describe an entity being passed from a calling context. This information
/// might include (but is not limited to) whether the entity is an array, its
/// size, or what type it has.
class BoxType
: public mlir::Type::TypeBase<BoxType, mlir::Type, detail::BoxTypeStorage> {
public:
using Base::Base;
static BoxType get(mlir::Type eleTy, mlir::AffineMapAttr map = {});
static bool kindof(unsigned kind) { return kind == TypeKind::FIR_BOX; }
mlir::Type getEleTy() const;
mlir::AffineMapAttr getLayoutMap() const;
static mlir::LogicalResult
verifyConstructionInvariants(mlir::Location, mlir::Type eleTy,
mlir::AffineMapAttr map);
};
/// The type of a pair that describes a CHARACTER variable. Specifically, a
/// CHARACTER consists of a reference to a buffer (the string value) and a LEN
/// type parameter (the runtime length of the buffer).
class BoxCharType : public mlir::Type::TypeBase<BoxCharType, mlir::Type,
detail::BoxCharTypeStorage> {
public:
using Base::Base;
static BoxCharType get(mlir::MLIRContext *ctxt, KindTy kind);
static bool kindof(unsigned kind) { return kind == TypeKind::FIR_BOXCHAR; }
CharacterType getEleTy() const;
};
/// The type of a pair that describes a PROCEDURE reference. Pointers to
/// internal procedures must carry an additional reference to the host's
/// variables that are referenced.
class BoxProcType : public mlir::Type::TypeBase<BoxProcType, mlir::Type,
detail::BoxProcTypeStorage> {
public:
using Base::Base;
static BoxProcType get(mlir::Type eleTy);
static bool kindof(unsigned kind) { return kind == TypeKind::FIR_BOXPROC; }
mlir::Type getEleTy() const;
static mlir::LogicalResult verifyConstructionInvariants(mlir::Location,
mlir::Type eleTy);
};
/// The type of a runtime vector that describes triples of array dimension
/// information. A triple consists of a lower bound, upper bound, and
/// stride. Each dimension of an array entity may have an associated triple that
/// maps how elements of the array are accessed.
class DimsType : public mlir::Type::TypeBase<DimsType, mlir::Type,
detail::DimsTypeStorage> {
public:
using Base::Base;
static DimsType get(mlir::MLIRContext *ctx, unsigned rank);
static bool kindof(unsigned kind) { return kind == TypeKind::FIR_DIMS; }
/// returns -1 if the rank is unknown
int getRank() const;
};
/// The type of a field name. Implementations may defer the layout of a Fortran
/// derived type until runtime. This implies that the runtime must be able to
/// determine the offset of fields within the entity.
class FieldType : public mlir::Type::TypeBase<FieldType, mlir::Type,
detail::FieldTypeStorage> {
public:
using Base::Base;
static FieldType get(mlir::MLIRContext *ctxt);
static bool kindof(unsigned kind) { return kind == TypeKind::FIR_FIELD; }
};
/// The type of a heap pointer. Fortran entities with the ALLOCATABLE attribute
/// may be allocated on the heap at runtime. These pointers are explicitly
/// distinguished to disallow the composition of multiple levels of
/// indirection. For example, an ALLOCATABLE POINTER is invalid.
class HeapType : public mlir::Type::TypeBase<HeapType, mlir::Type,
detail::HeapTypeStorage> {
public:
using Base::Base;
static HeapType get(mlir::Type elementType);
static bool kindof(unsigned kind) { return kind == TypeKind::FIR_HEAP; }
mlir::Type getEleTy() const;
static mlir::LogicalResult verifyConstructionInvariants(mlir::Location,
mlir::Type eleTy);
};
/// The type of a LEN parameter name. Implementations may defer the layout of a
/// Fortran derived type until runtime. This implies that the runtime must be
/// able to determine the offset of LEN type parameters related to an entity.
class LenType
: public mlir::Type::TypeBase<LenType, mlir::Type, detail::LenTypeStorage> {
public:
using Base::Base;
static LenType get(mlir::MLIRContext *ctxt);
static bool kindof(unsigned kind) { return kind == TypeKind::FIR_LEN; }
};
/// The type of entities with the POINTER attribute. These pointers are
/// explicitly distinguished to disallow the composition of multiple levels of
/// indirection. For example, an ALLOCATABLE POINTER is invalid.
class PointerType : public mlir::Type::TypeBase<PointerType, mlir::Type,
detail::PointerTypeStorage> {
public:
using Base::Base;
static PointerType get(mlir::Type elementType);
static bool kindof(unsigned kind) { return kind == TypeKind::FIR_POINTER; }
mlir::Type getEleTy() const;
static mlir::LogicalResult verifyConstructionInvariants(mlir::Location,
mlir::Type eleTy);
};
/// The type of a reference to an entity in memory.
class ReferenceType
: public mlir::Type::TypeBase<ReferenceType, mlir::Type,
detail::ReferenceTypeStorage> {
public:
using Base::Base;
static ReferenceType get(mlir::Type elementType);
static bool kindof(unsigned kind) { return kind == TypeKind::FIR_REFERENCE; }
mlir::Type getEleTy() const;
static mlir::LogicalResult verifyConstructionInvariants(mlir::Location,
mlir::Type eleTy);
};
/// A sequence type is a multi-dimensional array of values. The sequence type
/// may have an unknown number of dimensions or the extent of dimensions may be
/// unknown. A sequence type models a Fortran array entity, giving it a type in
/// FIR. A sequence type is assumed to be stored in a column-major order, which
/// differs from LLVM IR and other dialects of MLIR.
class SequenceType : public mlir::Type::TypeBase<SequenceType, mlir::Type,
detail::SequenceTypeStorage> {
public:
using Base::Base;
using Extent = int64_t;
using Shape = llvm::SmallVector<Extent, 8>;
/// Return a sequence type with the specified shape and element type
static SequenceType get(const Shape &shape, mlir::Type elementType,
mlir::AffineMapAttr map = {});
/// The element type of this sequence
mlir::Type getEleTy() const;
/// The shape of the sequence. If the sequence has an unknown shape, the shape
/// returned will be empty.
Shape getShape() const;
mlir::AffineMapAttr getLayoutMap() const;
/// The number of dimensions of the sequence
unsigned getDimension() const { return getShape().size(); }
/// The value `-1` represents an unknown extent for a dimension
static constexpr Extent getUnknownExtent() { return -1; }
static bool kindof(unsigned kind) { return kind == TypeKind::FIR_SEQUENCE; }
static mlir::LogicalResult
verifyConstructionInvariants(mlir::Location loc, const Shape &shape,
mlir::Type eleTy, mlir::AffineMapAttr map);
};
bool operator==(const SequenceType::Shape &, const SequenceType::Shape &);
llvm::hash_code hash_value(const SequenceType::Extent &);
llvm::hash_code hash_value(const SequenceType::Shape &);
/// The type of a type descriptor object. The runtime may generate type
/// descriptor objects to determine the type of an entity at runtime, etc.
class TypeDescType : public mlir::Type::TypeBase<TypeDescType, mlir::Type,
detail::TypeDescTypeStorage> {
public:
using Base::Base;
static TypeDescType get(mlir::Type ofType);
static constexpr bool kindof(unsigned kind) {
return kind == TypeKind::FIR_TYPEDESC;
}
mlir::Type getOfTy() const;
static mlir::LogicalResult verifyConstructionInvariants(mlir::Location,
mlir::Type ofType);
};
// Derived types
/// Model of Fortran's derived type, TYPE. The name of the TYPE includes any
/// KIND type parameters. The record includes runtime slots for LEN type
/// parameters and for data components.
class RecordType : public mlir::Type::TypeBase<RecordType, mlir::Type,
detail::RecordTypeStorage> {
public:
using Base::Base;
using TypePair = std::pair<std::string, mlir::Type>;
using TypeList = std::vector<TypePair>;
llvm::StringRef getName();
TypeList getTypeList();
TypeList getLenParamList();
mlir::Type getType(llvm::StringRef ident);
mlir::Type getType(unsigned index) {
assert(index < getNumFields());
return getTypeList()[index].second;
}
unsigned getNumFields() { return getTypeList().size(); }
unsigned getNumLenParams() { return getLenParamList().size(); }
static RecordType get(mlir::MLIRContext *ctxt, llvm::StringRef name);
void finalize(llvm::ArrayRef<TypePair> lenPList,
llvm::ArrayRef<TypePair> typeList);
static constexpr bool kindof(unsigned kind) { return kind == getId(); }
static constexpr unsigned getId() { return TypeKind::FIR_DERIVED; }
detail::RecordTypeStorage const *uniqueKey() const;
static mlir::LogicalResult verifyConstructionInvariants(mlir::Location,
llvm::StringRef name);
};
mlir::Type parseFirType(FIROpsDialect *, mlir::DialectAsmParser &parser);
void printFirType(FIROpsDialect *, mlir::Type ty, mlir::DialectAsmPrinter &p);
} // namespace fir
#endif // OPTIMIZER_DIALECT_FIRTYPE_H

View File

@ -0,0 +1,90 @@
//===-- Optimizer/Support/KindMapping.h -------------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef OPTIMIZER_SUPPORT_KINDMAPPING_H
#define OPTIMIZER_SUPPORT_KINDMAPPING_H
#include "mlir/IR/OpDefinition.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/IR/Type.h"
namespace llvm {
template <typename>
class Optional;
struct fltSemantics;
} // namespace llvm
namespace mlir {
class MLIRContext;
} // namespace mlir
namespace fir {
/// The kind mapping is an encoded string that informs FIR how the Fortran KIND
/// values from the front-end should be converted to LLVM IR types. This
/// encoding allows the mapping from front-end KIND values to backend LLVM IR
/// types to be customized by the front-end.
///
/// The provided string uses the following syntax.
///
/// intrinsic-key `:` kind-value (`,` intrinsic-key `:` kind-value)*
///
/// intrinsic-key is a single character for the intrinsic type.
/// 'i' : INTEGER (size in bits)
/// 'l' : LOGICAL (size in bits)
/// 'a' : CHARACTER (size in bits)
/// 'r' : REAL (encoding value)
/// 'c' : COMPLEX (encoding value)
///
/// kind-value is either an unsigned integer (for 'i', 'l', and 'a') or one of
/// 'Half', 'Float', 'Double', 'X86_FP80', or 'FP128' (for 'r' and 'c').
///
/// If LLVM adds support for new floating-point types, the final list should be
/// extended.
class KindMapping {
public:
using KindTy = unsigned;
using Bitsize = unsigned;
using LLVMTypeID = llvm::Type::TypeID;
using MatchResult = mlir::ParseResult;
explicit KindMapping(mlir::MLIRContext *context);
explicit KindMapping(mlir::MLIRContext *context, llvm::StringRef map);
/// Get the size in bits of !fir.char<kind>
Bitsize getCharacterBitsize(KindTy kind);
/// Get the size in bits of !fir.int<kind>
Bitsize getIntegerBitsize(KindTy kind);
/// Get the size in bits of !fir.logical<kind>
Bitsize getLogicalBitsize(KindTy kind);
/// Get the LLVM Type::TypeID of !fir.real<kind>
LLVMTypeID getRealTypeID(KindTy kind);
/// Get the LLVM Type::TypeID of !fir.complex<kind>
LLVMTypeID getComplexTypeID(KindTy kind);
mlir::MLIRContext *getContext() const { return context; }
/// Get the float semantics of !fir.real<kind>
const llvm::fltSemantics &getFloatSemantics(KindTy kind);
private:
MatchResult badMapString(const llvm::Twine &ptr);
MatchResult parse(llvm::StringRef kindMap);
mlir::MLIRContext *context;
llvm::DenseMap<std::pair<char, KindTy>, Bitsize> intMap;
llvm::DenseMap<std::pair<char, KindTy>, LLVMTypeID> floatMap;
};
} // namespace fir
#endif // OPTIMIZER_SUPPORT_KINDMAPPING_H

View File

@ -0,0 +1,155 @@
//===-- include/flang/Parser/char-block.h -----------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_PARSER_CHAR_BLOCK_H_
#define FORTRAN_PARSER_CHAR_BLOCK_H_
// Describes a contiguous block of characters; does not own their storage.
#include "flang/Common/interval.h"
#include <algorithm>
#include <cstddef>
#include <cstring>
#include <iosfwd>
#include <string>
#include <utility>
namespace llvm {
class raw_ostream;
}
namespace Fortran::parser {
class CharBlock {
public:
constexpr CharBlock() {}
constexpr CharBlock(const char *x, std::size_t n = 1) : interval_{x, n} {}
constexpr CharBlock(const char *b, const char *ep1)
: interval_{b, static_cast<std::size_t>(ep1 - b)} {}
CharBlock(const std::string &s) : interval_{s.data(), s.size()} {}
constexpr CharBlock(const CharBlock &) = default;
constexpr CharBlock(CharBlock &&) = default;
constexpr CharBlock &operator=(const CharBlock &) = default;
constexpr CharBlock &operator=(CharBlock &&) = default;
constexpr bool empty() const { return interval_.empty(); }
constexpr std::size_t size() const { return interval_.size(); }
constexpr const char *begin() const { return interval_.start(); }
constexpr const char *end() const {
return interval_.start() + interval_.size();
}
constexpr const char &operator[](std::size_t j) const {
return interval_.start()[j];
}
bool Contains(const CharBlock &that) const {
return interval_.Contains(that.interval_);
}
void ExtendToCover(const CharBlock &that) {
interval_.ExtendToCover(that.interval_);
}
char FirstNonBlank() const {
for (char ch : *this) {
if (ch != ' ' && ch != '\t') {
return ch;
}
}
return ' '; // non no-blank character
}
bool IsBlank() const { return FirstNonBlank() == ' '; }
std::string ToString() const {
return std::string{interval_.start(), interval_.size()};
}
// Convert to string, stopping early at any embedded '\0'.
std::string NULTerminatedToString() const {
return std::string{interval_.start(),
/*not in std::*/ strnlen(interval_.start(), interval_.size())};
}
bool operator<(const CharBlock &that) const { return Compare(that) < 0; }
bool operator<=(const CharBlock &that) const { return Compare(that) <= 0; }
bool operator==(const CharBlock &that) const { return Compare(that) == 0; }
bool operator!=(const CharBlock &that) const { return Compare(that) != 0; }
bool operator>=(const CharBlock &that) const { return Compare(that) >= 0; }
bool operator>(const CharBlock &that) const { return Compare(that) > 0; }
bool operator<(const char *that) const { return Compare(that) < 0; }
bool operator<=(const char *that) const { return Compare(that) <= 0; }
bool operator==(const char *that) const { return Compare(that) == 0; }
bool operator!=(const char *that) const { return Compare(that) != 0; }
bool operator>=(const char *that) const { return Compare(that) >= 0; }
bool operator>(const char *that) const { return Compare(that) > 0; }
friend bool operator<(const char *, const CharBlock &);
friend bool operator<=(const char *, const CharBlock &);
friend bool operator==(const char *, const CharBlock &);
friend bool operator!=(const char *, const CharBlock &);
friend bool operator>=(const char *, const CharBlock &);
friend bool operator>(const char *, const CharBlock &);
private:
int Compare(const CharBlock &that) const {
std::size_t bytes{std::min(size(), that.size())};
int cmp{std::memcmp(static_cast<const void *>(begin()),
static_cast<const void *>(that.begin()), bytes)};
if (cmp != 0) {
return cmp;
}
return size() < that.size() ? -1 : size() > that.size();
}
int Compare(const char *that) const {
std::size_t bytes{size()};
if (int cmp{std::strncmp(begin(), that, bytes)}) {
return cmp;
}
return that[bytes] == '\0' ? 0 : -1;
}
common::Interval<const char *> interval_{nullptr, 0};
};
inline bool operator<(const char *left, const CharBlock &right) {
return right > left;
}
inline bool operator<=(const char *left, const CharBlock &right) {
return right >= left;
}
inline bool operator==(const char *left, const CharBlock &right) {
return right == left;
}
inline bool operator!=(const char *left, const CharBlock &right) {
return right != left;
}
inline bool operator>=(const char *left, const CharBlock &right) {
return right <= left;
}
inline bool operator>(const char *left, const CharBlock &right) {
return right < left;
}
llvm::raw_ostream &operator<<(llvm::raw_ostream &os, const CharBlock &x);
} // namespace Fortran::parser
// Specializations to enable std::unordered_map<CharBlock, ...> &c.
template <> struct std::hash<Fortran::parser::CharBlock> {
std::size_t operator()(const Fortran::parser::CharBlock &x) const {
std::size_t hash{0}, bytes{x.size()};
for (std::size_t j{0}; j < bytes; ++j) {
hash = (hash * 31) ^ x[j];
}
return hash;
}
};
#endif // FORTRAN_PARSER_CHAR_BLOCK_H_

View File

@ -0,0 +1,77 @@
//===-- include/flang/Parser/char-buffer.h ----------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_PARSER_CHAR_BUFFER_H_
#define FORTRAN_PARSER_CHAR_BUFFER_H_
// Defines a simple expandable buffer suitable for efficiently accumulating
// a stream of bytes.
#include <cstddef>
#include <forward_list>
#include <string>
#include <utility>
#include <vector>
namespace Fortran::parser {
class CharBuffer {
public:
CharBuffer() {}
CharBuffer(CharBuffer &&that)
: blocks_(std::move(that.blocks_)), last_{that.last_},
bytes_{that.bytes_}, lastBlockEmpty_{that.lastBlockEmpty_} {
that.clear();
}
CharBuffer &operator=(CharBuffer &&that) {
blocks_ = std::move(that.blocks_);
last_ = that.last_;
bytes_ = that.bytes_;
lastBlockEmpty_ = that.lastBlockEmpty_;
that.clear();
return *this;
}
bool empty() const { return bytes_ == 0; }
std::size_t bytes() const { return bytes_; }
void clear() {
blocks_.clear();
last_ = blocks_.end();
bytes_ = 0;
lastBlockEmpty_ = false;
}
char *FreeSpace(std::size_t &);
void Claim(std::size_t);
// The return value is the byte offset of the new data,
// i.e. the value of size() before the call.
std::size_t Put(const char *data, std::size_t n);
std::size_t Put(const std::string &);
std::size_t Put(char x) { return Put(&x, 1); }
std::string Marshal() const;
// Removes carriage returns ('\r') and ensures a final line feed ('\n').
std::string MarshalNormalized() const;
private:
struct Block {
static constexpr std::size_t capacity{1 << 20};
char data[capacity];
};
int LastBlockOffset() const { return bytes_ % Block::capacity; }
std::forward_list<Block> blocks_;
std::forward_list<Block>::iterator last_{blocks_.end()};
std::size_t bytes_{0};
bool lastBlockEmpty_{false};
};
} // namespace Fortran::parser
#endif // FORTRAN_PARSER_CHAR_BUFFER_H_

View File

@ -0,0 +1,79 @@
//===-- include/flang/Parser/char-set.h -------------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_PARSER_CHAR_SET_H_
#define FORTRAN_PARSER_CHAR_SET_H_
// Sets of distinct characters that are valid in Fortran programs outside
// character literals are encoded as 64-bit integers by mapping them to a 6-bit
// character set encoding in which the case of letters is lost (even if
// mixed case input reached the parser, which it does not). These sets
// need to be suitable for constexprs, so std::bitset<> was not eligible.
#include <cinttypes>
#include <string>
namespace Fortran::parser {
struct SetOfChars {
constexpr SetOfChars() {}
constexpr SetOfChars(char c) {
// This is basically the old DECSIX encoding, which maps the
// 7-bit ASCII codes [32..95] to [0..63]. Only '#', '&', '?', '\', and '^'
// in that range are unused in Fortran after preprocessing outside
// character literals. We repurpose '^' and '?' for newline and unknown
// characters (resp.), leaving the others alone in case this code might
// be useful in preprocssing.
if (c == '\n') {
// map newline to '^'
c = '^';
} else if (c < 32 || c >= 127) {
// map other control characters, DEL, and 8-bit characters to '?'
c = '?';
} else if (c >= 96) {
// map lower-case letters to upper-case
c -= 32;
}
// range is now [32..95]; reduce to [0..63] and use as a shift count
bits_ = static_cast<std::uint64_t>(1) << (c - 32);
}
constexpr SetOfChars(const char str[], std::size_t n) {
for (std::size_t j{0}; j < n; ++j) {
bits_ |= SetOfChars{str[j]}.bits_;
}
}
constexpr SetOfChars(const SetOfChars &) = default;
constexpr SetOfChars(SetOfChars &&) = default;
constexpr SetOfChars &operator=(const SetOfChars &) = default;
constexpr SetOfChars &operator=(SetOfChars &&) = default;
constexpr bool empty() const { return bits_ == 0; }
constexpr bool Has(SetOfChars that) const {
return (that.bits_ & ~bits_) == 0;
}
constexpr SetOfChars Union(SetOfChars that) const {
return SetOfChars{bits_ | that.bits_};
}
constexpr SetOfChars Intersection(SetOfChars that) const {
return SetOfChars{bits_ & that.bits_};
}
constexpr SetOfChars Difference(SetOfChars that) const {
return SetOfChars{bits_ & ~that.bits_};
}
std::string ToString() const;
private:
constexpr SetOfChars(std::uint64_t b) : bits_{b} {}
std::uint64_t bits_{0};
};
} // namespace Fortran::parser
#endif // FORTRAN_PARSER_CHAR_SET_H_

View File

@ -0,0 +1,257 @@
//===-- include/flang/Parser/characters.h -----------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_PARSER_CHARACTERS_H_
#define FORTRAN_PARSER_CHARACTERS_H_
// Define some character classification predicates and
// conversions here to avoid dependences upon <cctype> and
// also to accomodate Fortran tokenization.
#include <cstddef>
#include <optional>
#include <string>
namespace Fortran::parser {
extern bool useHexadecimalEscapeSequences;
// We can easily support Fortran program source in any character
// set whose first 128 code points correspond to ASCII codes 0-127 (ISO/IEC646).
// The specific encodings that we can handle include:
// LATIN_1: ISO 8859-1 Latin-1
// UTF_8: Multi-byte encoding of Unicode (ISO/IEC 10646)
enum class Encoding { LATIN_1, UTF_8 };
inline constexpr bool IsUpperCaseLetter(char ch) {
return ch >= 'A' && ch <= 'Z';
}
inline constexpr bool IsLowerCaseLetter(char ch) {
return ch >= 'a' && ch <= 'z';
}
inline constexpr bool IsLetter(char ch) {
return IsUpperCaseLetter(ch) || IsLowerCaseLetter(ch);
}
inline constexpr bool IsDecimalDigit(char ch) { return ch >= '0' && ch <= '9'; }
inline constexpr bool IsHexadecimalDigit(char ch) {
return (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F') ||
(ch >= 'a' && ch <= 'f');
}
inline constexpr bool IsOctalDigit(char ch) { return ch >= '0' && ch <= '7'; }
inline constexpr bool IsLegalIdentifierStart(char ch) {
return IsLetter(ch) || ch == '_' || ch == '@' || ch == '$';
}
inline constexpr bool IsLegalInIdentifier(char ch) {
return IsLegalIdentifierStart(ch) || IsDecimalDigit(ch);
}
inline constexpr char ToLowerCaseLetter(char ch) {
return IsUpperCaseLetter(ch) ? ch - 'A' + 'a' : ch;
}
inline constexpr char ToLowerCaseLetter(char &&ch) {
return IsUpperCaseLetter(ch) ? ch - 'A' + 'a' : ch;
}
inline std::string ToLowerCaseLetters(const std::string &str) {
std::string lowered{str};
for (char &ch : lowered) {
ch = ToLowerCaseLetter(ch);
}
return lowered;
}
inline constexpr char ToUpperCaseLetter(char ch) {
return IsLowerCaseLetter(ch) ? ch - 'a' + 'A' : ch;
}
inline constexpr char ToUpperCaseLetter(char &&ch) {
return IsLowerCaseLetter(ch) ? ch - 'a' + 'A' : ch;
}
inline std::string ToUpperCaseLetters(const std::string &str) {
std::string raised{str};
for (char &ch : raised) {
ch = ToUpperCaseLetter(ch);
}
return raised;
}
inline constexpr bool IsSameApartFromCase(char x, char y) {
return ToLowerCaseLetter(x) == ToLowerCaseLetter(y);
}
inline constexpr char DecimalDigitValue(char ch) { return ch - '0'; }
inline constexpr char HexadecimalDigitValue(char ch) {
return IsUpperCaseLetter(ch)
? ch - 'A' + 10
: IsLowerCaseLetter(ch) ? ch - 'a' + 10 : DecimalDigitValue(ch);
}
inline constexpr std::optional<char> BackslashEscapeValue(char ch) {
switch (ch) {
case 'a':
return std::nullopt; // '\a'; PGF90 doesn't know \a
case 'b':
return '\b';
case 'f':
return '\f';
case 'n':
return '\n';
case 'r':
return '\r';
case 't':
return '\t';
case 'v':
return '\v';
case '"':
case '\'':
case '\\':
return ch;
default:
return std::nullopt;
}
}
inline constexpr std::optional<char> BackslashEscapeChar(char ch) {
switch (ch) {
case '\a':
return std::nullopt; // 'a'; PGF90 doesn't know \a
case '\b':
return 'b';
case '\f':
return 'f';
case '\n':
return 'n';
case '\r':
return 'r';
case '\t':
return 't';
case '\v':
return 'v';
case '"':
case '\'':
case '\\':
return ch;
default:
return std::nullopt;
}
}
struct EncodedCharacter {
static constexpr int maxEncodingBytes{6};
char buffer[maxEncodingBytes];
int bytes{0};
};
template <Encoding ENCODING> EncodedCharacter EncodeCharacter(char32_t ucs);
template <> EncodedCharacter EncodeCharacter<Encoding::LATIN_1>(char32_t);
template <> EncodedCharacter EncodeCharacter<Encoding::UTF_8>(char32_t);
EncodedCharacter EncodeCharacter(Encoding, char32_t ucs);
template <Encoding ENCODING, typename STRING>
std::string EncodeString(const STRING &);
extern template std::string EncodeString<Encoding::LATIN_1, std::string>(
const std::string &);
extern template std::string EncodeString<Encoding::UTF_8, std::u32string>(
const std::u32string &);
// EmitQuotedChar drives callbacks "emit" and "insert" to output the
// bytes of an encoding for a codepoint.
template <typename NORMAL, typename INSERTED>
void EmitQuotedChar(char32_t ch, const NORMAL &emit, const INSERTED &insert,
bool backslashEscapes = true, Encoding encoding = Encoding::UTF_8) {
auto emitOneByte{[&](std::uint8_t ch) {
if (backslashEscapes && (ch < ' ' || ch >= 0x7f || ch == '\\')) {
if (std::optional<char> escape{BackslashEscapeChar(ch)}) {
insert('\\');
emit(*escape);
} else if (useHexadecimalEscapeSequences) {
insert('\\');
insert('x');
int top{ch >> 4}, bottom{ch & 0xf};
insert(top > 9 ? 'a' + top - 10 : '0' + top);
insert(bottom > 9 ? 'a' + bottom - 10 : '0' + bottom);
} else {
// octal escape sequence; always emit 3 digits to avoid ambiguity
insert('\\');
insert('0' + (ch >> 6));
insert('0' + ((ch >> 3) & 7));
insert('0' + (ch & 7));
}
} else if (ch == '\n') { // always escape newlines
insert('\\');
insert('n');
} else {
emit(ch);
}
}};
if (ch <= 0x7f) {
emitOneByte(ch);
} else {
EncodedCharacter encoded{EncodeCharacter(encoding, ch)};
for (int j{0}; j < encoded.bytes; ++j) {
emitOneByte(encoded.buffer[j]);
}
}
}
std::string QuoteCharacterLiteral(const std::string &,
bool backslashEscapes = true, Encoding = Encoding::LATIN_1);
std::string QuoteCharacterLiteral(const std::u16string &,
bool backslashEscapes = true, Encoding = Encoding::UTF_8);
std::string QuoteCharacterLiteral(const std::u32string &,
bool backslashEscapes = true, Encoding = Encoding::UTF_8);
int UTF_8CharacterBytes(const char *);
struct DecodedCharacter {
char32_t codepoint{0};
int bytes{0}; // signifying failure
};
template <Encoding ENCODING>
DecodedCharacter DecodeRawCharacter(const char *, std::size_t);
template <>
DecodedCharacter DecodeRawCharacter<Encoding::LATIN_1>(
const char *, std::size_t);
template <>
DecodedCharacter DecodeRawCharacter<Encoding::UTF_8>(const char *, std::size_t);
// DecodeCharacter optionally handles backslash escape sequences, too.
template <Encoding ENCODING>
DecodedCharacter DecodeCharacter(
const char *, std::size_t, bool backslashEscapes);
extern template DecodedCharacter DecodeCharacter<Encoding::LATIN_1>(
const char *, std::size_t, bool);
extern template DecodedCharacter DecodeCharacter<Encoding::UTF_8>(
const char *, std::size_t, bool);
DecodedCharacter DecodeCharacter(
Encoding, const char *, std::size_t, bool backslashEscapes);
template <typename RESULT, Encoding ENCODING>
RESULT DecodeString(const std::string &, bool backslashEscapes);
extern template std::string DecodeString<std::string, Encoding::LATIN_1>(
const std::string &, bool);
extern template std::u16string DecodeString<std::u16string, Encoding::UTF_8>(
const std::string &, bool);
extern template std::u32string DecodeString<std::u32string, Encoding::UTF_8>(
const std::string &, bool);
} // namespace Fortran::parser
#endif // FORTRAN_PARSER_CHARACTERS_H_

View File

@ -0,0 +1,848 @@
//===-- include/flang/Parser/dump-parse-tree.h ------------------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_PARSER_DUMP_PARSE_TREE_H_
#define FORTRAN_PARSER_DUMP_PARSE_TREE_H_
#include "format-specification.h"
#include "parse-tree-visitor.h"
#include "parse-tree.h"
#include "unparse.h"
#include "flang/Common/idioms.h"
#include "flang/Common/indirection.h"
#include "llvm/Support/raw_ostream.h"
#include <string>
#include <type_traits>
namespace Fortran::parser {
// When SHOW_ALL_SOURCE_MEMBERS is defined, HasSource<T>::value is true if T has
// a member named source
template <typename T, typename = int> struct HasSource : std::false_type {};
#ifdef SHOW_ALL_SOURCE_MEMBERS
template <typename T>
struct HasSource<T, decltype((void)T::source, 0)> : std::true_type {};
#endif
//
// Dump the Parse Tree hierarchy of any node 'x' of the parse tree.
//
class ParseTreeDumper {
public:
explicit ParseTreeDumper(llvm::raw_ostream &out,
const AnalyzedObjectsAsFortran *asFortran = nullptr)
: out_(out), asFortran_{asFortran} {}
static constexpr const char *GetNodeName(const char *) { return "char *"; }
#define NODE_NAME(T, N) \
static constexpr const char *GetNodeName(const T &) { return N; }
#define NODE_ENUM(T, E) \
static std::string GetNodeName(const T::E &x) { \
return #E " = "s + T::EnumToString(x); \
}
#define NODE(T1, T2) NODE_NAME(T1::T2, #T2)
NODE_NAME(bool, "bool")
NODE_NAME(int, "int")
NODE(std, string)
NODE(std, int64_t)
NODE(std, uint64_t)
NODE(format, ControlEditDesc)
NODE(format::ControlEditDesc, Kind)
NODE(format, DerivedTypeDataEditDesc)
NODE(format, FormatItem)
NODE(format, FormatSpecification)
NODE(format, IntrinsicTypeDataEditDesc)
NODE(format::IntrinsicTypeDataEditDesc, Kind)
NODE(parser, Abstract)
NODE(parser, AcImpliedDo)
NODE(parser, AcImpliedDoControl)
NODE(parser, AcValue)
NODE(parser, AccessStmt)
NODE(parser, AccessId)
NODE(parser, AccessSpec)
NODE_ENUM(AccessSpec, Kind)
NODE(parser, AcSpec)
NODE(parser, ActionStmt)
NODE(parser, ActualArg)
NODE(ActualArg, PercentRef)
NODE(ActualArg, PercentVal)
NODE(parser, ActualArgSpec)
NODE(AcValue, Triplet)
NODE(parser, AllocOpt)
NODE(AllocOpt, Mold)
NODE(AllocOpt, Source)
NODE(parser, Allocatable)
NODE(parser, AllocatableStmt)
NODE(parser, AllocateCoarraySpec)
NODE(parser, AllocateObject)
NODE(parser, AllocateShapeSpec)
NODE(parser, AllocateStmt)
NODE(parser, Allocation)
NODE(parser, AltReturnSpec)
NODE(parser, ArithmeticIfStmt)
NODE(parser, ArrayConstructor)
NODE(parser, ArrayElement)
NODE(parser, ArraySpec)
NODE(parser, AssignStmt)
NODE(parser, AssignedGotoStmt)
NODE(parser, AssignmentStmt)
NODE(parser, AssociateConstruct)
NODE(parser, AssociateStmt)
NODE(parser, Association)
NODE(parser, AssumedImpliedSpec)
NODE(parser, AssumedRankSpec)
NODE(parser, AssumedShapeSpec)
NODE(parser, AssumedSizeSpec)
NODE(parser, Asynchronous)
NODE(parser, AsynchronousStmt)
NODE(parser, AttrSpec)
NODE(parser, BOZLiteralConstant)
NODE(parser, BackspaceStmt)
NODE(parser, BasedPointer)
NODE(parser, BasedPointerStmt)
NODE(parser, BindAttr)
NODE(BindAttr, Deferred)
NODE(BindAttr, Non_Overridable)
NODE(parser, BindEntity)
NODE_ENUM(BindEntity, Kind)
NODE(parser, BindStmt)
NODE(parser, Block)
NODE(parser, BlockConstruct)
NODE(parser, BlockData)
NODE(parser, BlockDataStmt)
NODE(parser, BlockSpecificationPart)
NODE(parser, BlockStmt)
NODE(parser, BoundsRemapping)
NODE(parser, BoundsSpec)
NODE(parser, Call)
NODE(parser, CallStmt)
NODE(parser, CaseConstruct)
NODE(CaseConstruct, Case)
NODE(parser, CaseSelector)
NODE(parser, CaseStmt)
NODE(parser, CaseValueRange)
NODE(CaseValueRange, Range)
NODE(parser, ChangeTeamConstruct)
NODE(parser, ChangeTeamStmt)
NODE(parser, CharLength)
NODE(parser, CharLiteralConstant)
NODE(parser, CharLiteralConstantSubstring)
NODE(parser, CharSelector)
NODE(CharSelector, LengthAndKind)
NODE(parser, CloseStmt)
NODE(CloseStmt, CloseSpec)
NODE(parser, CoarrayAssociation)
NODE(parser, CoarraySpec)
NODE(parser, CodimensionDecl)
NODE(parser, CodimensionStmt)
NODE(parser, CoindexedNamedObject)
NODE(parser, CommonBlockObject)
NODE(parser, CommonStmt)
NODE(CommonStmt, Block)
NODE(parser, CompilerDirective)
NODE(CompilerDirective, IgnoreTKR)
NODE(parser, ComplexLiteralConstant)
NODE(parser, ComplexPart)
NODE(parser, ComponentArraySpec)
NODE(parser, ComponentAttrSpec)
NODE(parser, ComponentDataSource)
NODE(parser, ComponentDecl)
NODE(parser, ComponentDefStmt)
NODE(parser, ComponentSpec)
NODE(parser, ComputedGotoStmt)
NODE(parser, ConcurrentControl)
NODE(parser, ConcurrentHeader)
NODE(parser, ConnectSpec)
NODE(ConnectSpec, CharExpr)
NODE_ENUM(ConnectSpec::CharExpr, Kind)
NODE(ConnectSpec, Newunit)
NODE(ConnectSpec, Recl)
NODE(parser, ConstantValue)
NODE(parser, ContainsStmt)
NODE(parser, Contiguous)
NODE(parser, ContiguousStmt)
NODE(parser, ContinueStmt)
NODE(parser, CriticalConstruct)
NODE(parser, CriticalStmt)
NODE(parser, CycleStmt)
NODE(parser, DataComponentDefStmt)
NODE(parser, DataIDoObject)
NODE(parser, DataImpliedDo)
NODE(parser, DataRef)
NODE(parser, DataStmt)
NODE(parser, DataStmtConstant)
NODE(parser, DataStmtObject)
NODE(parser, DataStmtRepeat)
NODE(parser, DataStmtSet)
NODE(parser, DataStmtValue)
NODE(parser, DeallocateStmt)
NODE(parser, DeclarationConstruct)
NODE(parser, DeclarationTypeSpec)
NODE(DeclarationTypeSpec, Class)
NODE(DeclarationTypeSpec, ClassStar)
NODE(DeclarationTypeSpec, Record)
NODE(DeclarationTypeSpec, Type)
NODE(DeclarationTypeSpec, TypeStar)
NODE(parser, Default)
NODE(parser, DeferredCoshapeSpecList)
NODE(parser, DeferredShapeSpecList)
NODE(parser, DefinedOpName)
NODE(parser, DefinedOperator)
NODE_ENUM(DefinedOperator, IntrinsicOperator)
NODE(parser, DerivedTypeDef)
NODE(parser, DerivedTypeSpec)
NODE(parser, DerivedTypeStmt)
NODE(parser, Designator)
NODE(parser, DimensionStmt)
NODE(DimensionStmt, Declaration)
NODE(parser, DoConstruct)
NODE(parser, DummyArg)
NODE(parser, ElseIfStmt)
NODE(parser, ElseStmt)
NODE(parser, ElsewhereStmt)
NODE(parser, EndAssociateStmt)
NODE(parser, EndBlockDataStmt)
NODE(parser, EndBlockStmt)
NODE(parser, EndChangeTeamStmt)
NODE(parser, EndCriticalStmt)
NODE(parser, EndDoStmt)
NODE(parser, EndEnumStmt)
NODE(parser, EndForallStmt)
NODE(parser, EndFunctionStmt)
NODE(parser, EndIfStmt)
NODE(parser, EndInterfaceStmt)
NODE(parser, EndLabel)
NODE(parser, EndModuleStmt)
NODE(parser, EndMpSubprogramStmt)
NODE(parser, EndProgramStmt)
NODE(parser, EndSelectStmt)
NODE(parser, EndSubmoduleStmt)
NODE(parser, EndSubroutineStmt)
NODE(parser, EndTypeStmt)
NODE(parser, EndWhereStmt)
NODE(parser, EndfileStmt)
NODE(parser, EntityDecl)
NODE(parser, EntryStmt)
NODE(parser, EnumDef)
NODE(parser, EnumDefStmt)
NODE(parser, Enumerator)
NODE(parser, EnumeratorDefStmt)
NODE(parser, EorLabel)
NODE(parser, EquivalenceObject)
NODE(parser, EquivalenceStmt)
NODE(parser, ErrLabel)
NODE(parser, ErrorRecovery)
NODE(parser, EventPostStmt)
NODE(parser, EventWaitStmt)
NODE(EventWaitStmt, EventWaitSpec)
NODE(parser, ExecutableConstruct)
NODE(parser, ExecutionPart)
NODE(parser, ExecutionPartConstruct)
NODE(parser, ExitStmt)
NODE(parser, ExplicitCoshapeSpec)
NODE(parser, ExplicitShapeSpec)
NODE(parser, Expr)
NODE(Expr, Parentheses)
NODE(Expr, UnaryPlus)
NODE(Expr, Negate)
NODE(Expr, NOT)
NODE(Expr, PercentLoc)
NODE(Expr, DefinedUnary)
NODE(Expr, Power)
NODE(Expr, Multiply)
NODE(Expr, Divide)
NODE(Expr, Add)
NODE(Expr, Subtract)
NODE(Expr, Concat)
NODE(Expr, LT)
NODE(Expr, LE)
NODE(Expr, EQ)
NODE(Expr, NE)
NODE(Expr, GE)
NODE(Expr, GT)
NODE(Expr, AND)
NODE(Expr, OR)
NODE(Expr, EQV)
NODE(Expr, NEQV)
NODE(Expr, DefinedBinary)
NODE(Expr, ComplexConstructor)
NODE(parser, External)
NODE(parser, ExternalStmt)
NODE(parser, FailImageStmt)
NODE(parser, FileUnitNumber)
NODE(parser, FinalProcedureStmt)
NODE(parser, FlushStmt)
NODE(parser, ForallAssignmentStmt)
NODE(parser, ForallBodyConstruct)
NODE(parser, ForallConstruct)
NODE(parser, ForallConstructStmt)
NODE(parser, ForallStmt)
NODE(parser, FormTeamStmt)
NODE(FormTeamStmt, FormTeamSpec)
NODE(parser, Format)
NODE(parser, FormatStmt)
NODE(parser, FunctionReference)
NODE(parser, FunctionStmt)
NODE(parser, FunctionSubprogram)
NODE(parser, GenericSpec)
NODE(GenericSpec, Assignment)
NODE(GenericSpec, ReadFormatted)
NODE(GenericSpec, ReadUnformatted)
NODE(GenericSpec, WriteFormatted)
NODE(GenericSpec, WriteUnformatted)
NODE(parser, GenericStmt)
NODE(parser, GotoStmt)
NODE(parser, HollerithLiteralConstant)
NODE(parser, IdExpr)
NODE(parser, IdVariable)
NODE(parser, IfConstruct)
NODE(IfConstruct, ElseBlock)
NODE(IfConstruct, ElseIfBlock)
NODE(parser, IfStmt)
NODE(parser, IfThenStmt)
NODE(parser, TeamValue)
NODE(parser, ImageSelector)
NODE(parser, ImageSelectorSpec)
NODE(ImageSelectorSpec, Stat)
NODE(ImageSelectorSpec, Team_Number)
NODE(parser, ImplicitPart)
NODE(parser, ImplicitPartStmt)
NODE(parser, ImplicitSpec)
NODE(parser, ImplicitStmt)
NODE_ENUM(ImplicitStmt, ImplicitNoneNameSpec)
NODE(parser, ImpliedShapeSpec)
NODE(parser, ImportStmt)
NODE(parser, Initialization)
NODE(parser, InputImpliedDo)
NODE(parser, InputItem)
NODE(parser, InquireSpec)
NODE(InquireSpec, CharVar)
NODE_ENUM(InquireSpec::CharVar, Kind)
NODE(InquireSpec, IntVar)
NODE_ENUM(InquireSpec::IntVar, Kind)
NODE(InquireSpec, LogVar)
NODE_ENUM(InquireSpec::LogVar, Kind)
NODE(parser, InquireStmt)
NODE(InquireStmt, Iolength)
NODE(parser, IntegerTypeSpec)
NODE(parser, IntentSpec)
NODE_ENUM(IntentSpec, Intent)
NODE(parser, IntentStmt)
NODE(parser, InterfaceBlock)
NODE(parser, InterfaceBody)
NODE(InterfaceBody, Function)
NODE(InterfaceBody, Subroutine)
NODE(parser, InterfaceSpecification)
NODE(parser, InterfaceStmt)
NODE(parser, InternalSubprogram)
NODE(parser, InternalSubprogramPart)
NODE(parser, Intrinsic)
NODE(parser, IntrinsicStmt)
NODE(parser, IntrinsicTypeSpec)
NODE(IntrinsicTypeSpec, Character)
NODE(IntrinsicTypeSpec, Complex)
NODE(IntrinsicTypeSpec, DoubleComplex)
NODE(IntrinsicTypeSpec, DoublePrecision)
NODE(IntrinsicTypeSpec, Logical)
NODE(IntrinsicTypeSpec, Real)
NODE(parser, IoControlSpec)
NODE(IoControlSpec, Asynchronous)
NODE(IoControlSpec, CharExpr)
NODE_ENUM(IoControlSpec::CharExpr, Kind)
NODE(IoControlSpec, Pos)
NODE(IoControlSpec, Rec)
NODE(IoControlSpec, Size)
NODE(parser, IoUnit)
NODE(parser, Keyword)
NODE(parser, KindParam)
NODE(parser, KindSelector)
NODE(KindSelector, StarSize)
NODE(parser, LabelDoStmt)
NODE(parser, LanguageBindingSpec)
NODE(parser, LengthSelector)
NODE(parser, LetterSpec)
NODE(parser, LiteralConstant)
NODE(parser, IntLiteralConstant)
NODE(parser, LocalitySpec)
NODE(LocalitySpec, DefaultNone)
NODE(LocalitySpec, Local)
NODE(LocalitySpec, LocalInit)
NODE(LocalitySpec, Shared)
NODE(parser, LockStmt)
NODE(LockStmt, LockStat)
NODE(parser, LogicalLiteralConstant)
NODE_NAME(LoopControl::Bounds, "LoopBounds")
NODE_NAME(AcImpliedDoControl::Bounds, "LoopBounds")
NODE_NAME(DataImpliedDo::Bounds, "LoopBounds")
NODE(parser, LoopControl)
NODE(LoopControl, Concurrent)
NODE(parser, MainProgram)
NODE(parser, Map)
NODE(Map, EndMapStmt)
NODE(Map, MapStmt)
NODE(parser, MaskedElsewhereStmt)
NODE(parser, Module)
NODE(parser, ModuleStmt)
NODE(parser, ModuleSubprogram)
NODE(parser, ModuleSubprogramPart)
NODE(parser, MpSubprogramStmt)
NODE(parser, MsgVariable)
NODE(parser, Name)
NODE(parser, NamedConstant)
NODE(parser, NamedConstantDef)
NODE(parser, NamelistStmt)
NODE(NamelistStmt, Group)
NODE(parser, NonLabelDoStmt)
NODE(parser, NoPass)
NODE(parser, NullifyStmt)
NODE(parser, NullInit)
NODE(parser, ObjectDecl)
NODE(parser, OldParameterStmt)
NODE(parser, OmpAlignedClause)
NODE(parser, OmpAtomic)
NODE(parser, OmpAtomicCapture)
NODE(OmpAtomicCapture, Stmt1)
NODE(OmpAtomicCapture, Stmt2)
NODE(parser, OmpAtomicRead)
NODE(parser, OmpAtomicUpdate)
NODE(parser, OmpAtomicWrite)
NODE(parser, OmpBeginBlockDirective)
NODE(parser, OmpBeginLoopDirective)
NODE(parser, OmpBeginSectionsDirective)
NODE(parser, OmpBlockDirective)
NODE_ENUM(OmpBlockDirective, Directive)
NODE(parser, OmpCancelType)
NODE_ENUM(OmpCancelType, Type)
NODE(parser, OmpClause)
NODE(parser, OmpClauseList)
NODE(OmpClause, Collapse)
NODE(OmpClause, Copyin)
NODE(OmpClause, Copyprivate)
NODE(OmpClause, Device)
NODE(OmpClause, DistSchedule)
NODE(OmpClause, Final)
NODE(OmpClause, Firstprivate)
NODE(OmpClause, From)
NODE(OmpClause, Grainsize)
NODE(OmpClause, Inbranch)
NODE(OmpClause, Lastprivate)
NODE(OmpClause, Mergeable)
NODE(OmpClause, Nogroup)
NODE(OmpClause, Notinbranch)
NODE(OmpClause, Threads)
NODE(OmpClause, Simd)
NODE(OmpClause, NumTasks)
NODE(OmpClause, NumTeams)
NODE(OmpClause, NumThreads)
NODE(OmpClause, Ordered)
NODE(OmpClause, Priority)
NODE(OmpClause, Private)
NODE(OmpClause, Safelen)
NODE(OmpClause, Shared)
NODE(OmpClause, Simdlen)
NODE(OmpClause, ThreadLimit)
NODE(OmpClause, To)
NODE(OmpClause, Link)
NODE(OmpClause, Uniform)
NODE(OmpClause, Untied)
NODE(OmpClause, UseDevicePtr)
NODE(OmpClause, IsDevicePtr)
NODE(parser, OmpCriticalDirective)
NODE(OmpCriticalDirective, Hint)
NODE(parser, OmpDeclareTargetSpecifier)
NODE(parser, OmpDeclareTargetWithClause)
NODE(parser, OmpDeclareTargetWithList)
NODE(parser, OmpDefaultClause)
NODE_ENUM(OmpDefaultClause, Type)
NODE(parser, OmpDefaultmapClause)
NODE_ENUM(OmpDefaultmapClause, ImplicitBehavior)
NODE_ENUM(OmpDefaultmapClause, VariableCategory)
NODE(parser, OmpDependClause)
NODE(OmpDependClause, InOut)
NODE(OmpDependClause, Sink)
NODE(OmpDependClause, Source)
NODE(parser, OmpDependenceType)
NODE_ENUM(OmpDependenceType, Type)
NODE(parser, OmpDependSinkVec)
NODE(parser, OmpDependSinkVecLength)
NODE(parser, OmpEndAtomic)
NODE(parser, OmpEndBlockDirective)
NODE(parser, OmpEndCriticalDirective)
NODE(parser, OmpEndLoopDirective)
NODE(parser, OmpEndSectionsDirective)
NODE(parser, OmpIfClause)
NODE_ENUM(OmpIfClause, DirectiveNameModifier)
NODE(parser, OmpLinearClause)
NODE(OmpLinearClause, WithModifier)
NODE(OmpLinearClause, WithoutModifier)
NODE(parser, OmpLinearModifier)
NODE_ENUM(OmpLinearModifier, Type)
NODE(parser, OmpLoopDirective)
NODE_ENUM(OmpLoopDirective, Directive)
NODE(parser, OmpMapClause)
NODE(parser, OmpMapType)
NODE(OmpMapType, Always)
NODE_ENUM(OmpMapType, Type)
NODE(parser, OmpMemoryClause)
NODE_ENUM(OmpMemoryClause, MemoryOrder)
NODE(parser, OmpMemoryClauseList)
NODE(parser, OmpMemoryClausePostList)
NODE(parser, OmpNowait)
NODE(parser, OmpObject)
NODE(parser, OmpObjectList)
NODE(parser, OmpProcBindClause)
NODE_ENUM(OmpProcBindClause, Type)
NODE(parser, OmpReductionClause)
NODE(parser, OmpReductionCombiner)
NODE(OmpReductionCombiner, FunctionCombiner)
NODE(parser, OmpReductionInitializerClause)
NODE(parser, OmpReductionOperator)
NODE(parser, OmpScheduleClause)
NODE_ENUM(OmpScheduleClause, ScheduleType)
NODE(parser, OmpScheduleModifier)
NODE(OmpScheduleModifier, Modifier1)
NODE(OmpScheduleModifier, Modifier2)
NODE(parser, OmpScheduleModifierType)
NODE_ENUM(OmpScheduleModifierType, ModType)
NODE(parser, OmpSectionBlocks)
NODE(parser, OmpSectionsDirective)
NODE_ENUM(OmpSectionsDirective, Directive)
NODE(parser, OmpSimpleStandaloneDirective)
NODE_ENUM(OmpSimpleStandaloneDirective, Directive)
NODE(parser, Only)
NODE(parser, OpenMPAtomicConstruct)
NODE(parser, OpenMPBlockConstruct)
NODE(parser, OpenMPCancelConstruct)
NODE(OpenMPCancelConstruct, If)
NODE(parser, OpenMPCancellationPointConstruct)
NODE(parser, OpenMPConstruct)
NODE(parser, OpenMPCriticalConstruct)
NODE(parser, OpenMPDeclarativeConstruct)
NODE(parser, OpenMPDeclareReductionConstruct)
NODE(parser, OpenMPDeclareSimdConstruct)
NODE(parser, OpenMPDeclareTargetConstruct)
NODE(parser, OpenMPFlushConstruct)
NODE(parser, OpenMPLoopConstruct)
NODE(parser, OpenMPSimpleStandaloneConstruct)
NODE(parser, OpenMPStandaloneConstruct)
NODE(parser, OpenMPSectionsConstruct)
NODE(parser, OpenMPThreadprivate)
NODE(parser, OpenStmt)
NODE(parser, Optional)
NODE(parser, OptionalStmt)
NODE(parser, OtherSpecificationStmt)
NODE(parser, OutputImpliedDo)
NODE(parser, OutputItem)
NODE(parser, Parameter)
NODE(parser, ParameterStmt)
NODE(parser, ParentIdentifier)
NODE(parser, Pass)
NODE(parser, PauseStmt)
NODE(parser, Pointer)
NODE(parser, PointerAssignmentStmt)
NODE(PointerAssignmentStmt, Bounds)
NODE(parser, PointerDecl)
NODE(parser, PointerObject)
NODE(parser, PointerStmt)
NODE(parser, PositionOrFlushSpec)
NODE(parser, PrefixSpec)
NODE(PrefixSpec, Elemental)
NODE(PrefixSpec, Impure)
NODE(PrefixSpec, Module)
NODE(PrefixSpec, Non_Recursive)
NODE(PrefixSpec, Pure)
NODE(PrefixSpec, Recursive)
NODE(parser, PrintStmt)
NODE(parser, PrivateStmt)
NODE(parser, PrivateOrSequence)
NODE(parser, ProcAttrSpec)
NODE(parser, ProcComponentAttrSpec)
NODE(parser, ProcComponentDefStmt)
NODE(parser, ProcComponentRef)
NODE(parser, ProcDecl)
NODE(parser, ProcInterface)
NODE(parser, ProcPointerInit)
NODE(parser, ProcedureDeclarationStmt)
NODE(parser, ProcedureDesignator)
NODE(parser, ProcedureStmt)
NODE_ENUM(ProcedureStmt, Kind)
NODE(parser, Program)
NODE(parser, ProgramStmt)
NODE(parser, ProgramUnit)
NODE(parser, Protected)
NODE(parser, ProtectedStmt)
NODE(parser, ReadStmt)
NODE(parser, RealLiteralConstant)
NODE(RealLiteralConstant, Real)
NODE(parser, Rename)
NODE(Rename, Names)
NODE(Rename, Operators)
NODE(parser, ReturnStmt)
NODE(parser, RewindStmt)
NODE(parser, Save)
NODE(parser, SaveStmt)
NODE(parser, SavedEntity)
NODE_ENUM(SavedEntity, Kind)
NODE(parser, SectionSubscript)
NODE(parser, SelectCaseStmt)
NODE(parser, SelectRankCaseStmt)
NODE(SelectRankCaseStmt, Rank)
NODE(parser, SelectRankConstruct)
NODE(SelectRankConstruct, RankCase)
NODE(parser, SelectRankStmt)
NODE(parser, SelectTypeConstruct)
NODE(SelectTypeConstruct, TypeCase)
NODE(parser, SelectTypeStmt)
NODE(parser, Selector)
NODE(parser, SeparateModuleSubprogram)
NODE(parser, SequenceStmt)
NODE(parser, Sign)
NODE(parser, SignedComplexLiteralConstant)
NODE(parser, SignedIntLiteralConstant)
NODE(parser, SignedRealLiteralConstant)
NODE(parser, SpecificationConstruct)
NODE(parser, SpecificationExpr)
NODE(parser, SpecificationPart)
NODE(parser, Star)
NODE(parser, StatOrErrmsg)
NODE(parser, StatVariable)
NODE(parser, StatusExpr)
NODE(parser, StmtFunctionStmt)
NODE(parser, StopCode)
NODE(parser, StopStmt)
NODE_ENUM(StopStmt, Kind)
NODE(parser, StructureComponent)
NODE(parser, StructureConstructor)
NODE(parser, StructureDef)
NODE(StructureDef, EndStructureStmt)
NODE(parser, StructureField)
NODE(parser, StructureStmt)
NODE(parser, Submodule)
NODE(parser, SubmoduleStmt)
NODE(parser, SubroutineStmt)
NODE(parser, SubroutineSubprogram)
NODE(parser, SubscriptTriplet)
NODE(parser, Substring)
NODE(parser, SubstringRange)
NODE(parser, Suffix)
NODE(parser, SyncAllStmt)
NODE(parser, SyncImagesStmt)
NODE(SyncImagesStmt, ImageSet)
NODE(parser, SyncMemoryStmt)
NODE(parser, SyncTeamStmt)
NODE(parser, Target)
NODE(parser, TargetStmt)
NODE(parser, TypeAttrSpec)
NODE(TypeAttrSpec, BindC)
NODE(TypeAttrSpec, Extends)
NODE(parser, TypeBoundGenericStmt)
NODE(parser, TypeBoundProcBinding)
NODE(parser, TypeBoundProcDecl)
NODE(parser, TypeBoundProcedurePart)
NODE(parser, TypeBoundProcedureStmt)
NODE(TypeBoundProcedureStmt, WithInterface)
NODE(TypeBoundProcedureStmt, WithoutInterface)
NODE(parser, TypeDeclarationStmt)
NODE(parser, TypeGuardStmt)
NODE(TypeGuardStmt, Guard)
NODE(parser, TypeParamDecl)
NODE(parser, TypeParamDefStmt)
NODE(common, TypeParamAttr)
NODE(parser, TypeParamSpec)
NODE(parser, TypeParamValue)
NODE(TypeParamValue, Deferred)
NODE(parser, TypeSpec)
NODE(parser, Union)
NODE(Union, EndUnionStmt)
NODE(Union, UnionStmt)
NODE(parser, UnlockStmt)
NODE(parser, UseStmt)
NODE_ENUM(UseStmt, ModuleNature)
NODE(parser, Value)
NODE(parser, ValueStmt)
NODE(parser, Variable)
NODE(parser, Verbatim)
NODE(parser, Volatile)
NODE(parser, VolatileStmt)
NODE(parser, WaitSpec)
NODE(parser, WaitStmt)
NODE(parser, WhereBodyConstruct)
NODE(parser, WhereConstruct)
NODE(WhereConstruct, Elsewhere)
NODE(WhereConstruct, MaskedElsewhere)
NODE(parser, WhereConstructStmt)
NODE(parser, WhereStmt)
NODE(parser, WriteStmt)
#undef NODE
#undef NODE_NAME
template <typename T> bool Pre(const T &x) {
std::string fortran{AsFortran<T>(x)};
if (fortran.empty() && (UnionTrait<T> || WrapperTrait<T>)) {
Prefix(GetNodeName(x));
} else {
IndentEmptyLine();
out_ << GetNodeName(x);
if (!fortran.empty()) {
out_ << " = '" << fortran << '\'';
}
EndLine();
++indent_;
}
return true;
}
template <typename T> void Post(const T &x) {
if (AsFortran<T>(x).empty() && (UnionTrait<T> || WrapperTrait<T>)) {
EndLineIfNonempty();
} else {
--indent_;
}
}
// A few types we want to ignore
bool Pre(const CharBlock &) { return true; }
void Post(const CharBlock &) {}
template <typename T> bool Pre(const Statement<T> &) { return true; }
template <typename T> void Post(const Statement<T> &) {}
template <typename T> bool Pre(const UnlabeledStatement<T> &) { return true; }
template <typename T> void Post(const UnlabeledStatement<T> &) {}
template <typename T> bool Pre(const common::Indirection<T> &) {
return true;
}
template <typename T> void Post(const common::Indirection<T> &) {}
template <typename A> bool Pre(const Scalar<A> &) {
Prefix("Scalar");
return true;
}
template <typename A> void Post(const Scalar<A> &) { EndLineIfNonempty(); }
template <typename A> bool Pre(const Constant<A> &) {
Prefix("Constant");
return true;
}
template <typename A> void Post(const Constant<A> &) { EndLineIfNonempty(); }
template <typename A> bool Pre(const Integer<A> &) {
Prefix("Integer");
return true;
}
template <typename A> void Post(const Integer<A> &) { EndLineIfNonempty(); }
template <typename A> bool Pre(const Logical<A> &) {
Prefix("Logical");
return true;
}
template <typename A> void Post(const Logical<A> &) { EndLineIfNonempty(); }
template <typename A> bool Pre(const DefaultChar<A> &) {
Prefix("DefaultChar");
return true;
}
template <typename A> void Post(const DefaultChar<A> &) {
EndLineIfNonempty();
}
template <typename... A> bool Pre(const std::tuple<A...> &) { return true; }
template <typename... A> void Post(const std::tuple<A...> &) {}
template <typename... A> bool Pre(const std::variant<A...> &) { return true; }
template <typename... A> void Post(const std::variant<A...> &) {}
protected:
// Return a Fortran representation of this node to include in the dump
template <typename T> std::string AsFortran(const T &x) {
std::string buf;
llvm::raw_string_ostream ss{buf};
if constexpr (std::is_same_v<T, Expr>) {
if (asFortran_ && x.typedExpr) {
asFortran_->expr(ss, *x.typedExpr);
}
} else if constexpr (std::is_same_v<T, AssignmentStmt> ||
std::is_same_v<T, PointerAssignmentStmt>) {
if (asFortran_ && x.typedAssignment) {
asFortran_->assignment(ss, *x.typedAssignment);
}
} else if constexpr (std::is_same_v<T, CallStmt>) {
if (asFortran_ && x.typedCall) {
asFortran_->call(ss, *x.typedCall);
}
} else if constexpr (std::is_same_v<T, IntLiteralConstant> ||
std::is_same_v<T, SignedIntLiteralConstant>) {
ss << std::get<CharBlock>(x.t);
} else if constexpr (std::is_same_v<T, RealLiteralConstant::Real>) {
ss << x.source;
} else if constexpr (std::is_same_v<T, std::string> ||
std::is_same_v<T, std::int64_t> || std::is_same_v<T, std::uint64_t>) {
ss << x;
}
if (ss.tell()) {
return ss.str();
}
if constexpr (std::is_same_v<T, Name> || HasSource<T>::value) {
return x.source.ToString();
} else if constexpr (std::is_same_v<T, std::string>) {
return x;
} else {
return "";
}
}
void IndentEmptyLine() {
if (emptyline_ && indent_ > 0) {
for (int i{0}; i < indent_; ++i) {
out_ << "| ";
}
emptyline_ = false;
}
}
void Prefix(const char *str) {
IndentEmptyLine();
out_ << str << " -> ";
emptyline_ = false;
}
void Prefix(const std::string &str) {
IndentEmptyLine();
out_ << str << " -> ";
emptyline_ = false;
}
void EndLine() {
out_ << '\n';
emptyline_ = true;
}
void EndLineIfNonempty() {
if (!emptyline_) {
EndLine();
}
}
private:
int indent_{0};
llvm::raw_ostream &out_;
const AnalyzedObjectsAsFortran *const asFortran_;
bool emptyline_{false};
};
template <typename T>
void DumpTree(llvm::raw_ostream &out, const T &x,
const AnalyzedObjectsAsFortran *asFortran = nullptr) {
ParseTreeDumper dumper{out, asFortran};
Walk(x, dumper);
}
} // namespace Fortran::parser
#endif // FORTRAN_PARSER_DUMP_PARSE_TREE_H_

Some files were not shown because too many files have changed in this diff Show More