forked from OSchip/llvm-project
[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:
commit
b98ad941a4
|
@ -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:
|
|
@ -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"),
|
||||||
|
]
|
||||||
|
|
|
@ -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
|
|
@ -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()
|
|
@ -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)
|
|
@ -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.
|
|
@ -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.
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
|
@ -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@")
|
|
@ -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...)
|
|
@ -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.
|
|
@ -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.
|
|
@ -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
|
|
@ -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.
|
|
@ -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
|
|
@ -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.
|
|
@ -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.
|
|
@ -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.)
|
|
@ -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 — 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.
|
|
@ -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)`.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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 → 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)
|
||||||
|
|
|
@ -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.
|
|
@ -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)
|
|
@ -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 directive’s 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 variable’s `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
|
@ -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).
|
|
@ -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.
|
|
@ -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.
|
|
@ -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)
|
||||||
|
```
|
|
@ -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.
|
|
@ -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.
|
|
@ -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`.
|
|
@ -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
|
|
@ -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)
|
|
@ -0,0 +1 @@
|
||||||
|
add_subdirectory(flang)
|
|
@ -0,0 +1,3 @@
|
||||||
|
if(LINK_WITH_FIR)
|
||||||
|
add_subdirectory(Optimizer)
|
||||||
|
endif()
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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
|
|
@ -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_
|
|
@ -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_
|
|
@ -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
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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_
|
|
@ -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_
|
|
@ -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
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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
|
|
@ -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_
|
|
@ -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 ¶m)
|
||||||
|
: base_{std::move(x)}, parameter_{param} {}
|
||||||
|
TypeParamInquiry(std::optional<NamedEntity> &&x, const Symbol ¶m)
|
||||||
|
: base_{std::move(x)}, parameter_{param} {}
|
||||||
|
|
||||||
|
const std::optional<NamedEntity> &base() const { return base_; }
|
||||||
|
std::optional<NamedEntity> &base() { return base_; }
|
||||||
|
const Symbol ¶meter() 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_
|
|
@ -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_ */
|
|
@ -0,0 +1,2 @@
|
||||||
|
BasedOnStyle: LLVM
|
||||||
|
AlwaysBreakTemplateDeclarations: Yes
|
|
@ -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 ⌖
|
||||||
|
};
|
||||||
|
|
||||||
|
/// 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_
|
|
@ -0,0 +1,2 @@
|
||||||
|
BasedOnStyle: LLVM
|
||||||
|
AlwaysBreakTemplateDeclarations: Yes
|
|
@ -0,0 +1 @@
|
||||||
|
add_subdirectory(Dialect)
|
|
@ -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)
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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_
|
|
@ -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
Loading…
Reference in New Issue