1<!--===- docs/IORuntimeInternals.md 2 3 Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 See https://llvm.org/LICENSE.txt for license information. 5 SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 7--> 8 9Fortran I/O Runtime Library Internal Design 10=========================================== 11 12This note is meant to be an overview of the design of the *implementation* 13of the f18 Fortran compiler's runtime support library for I/O statements. 14 15The *interface* to the I/O runtime support library is defined in the 16C++ header file `runtime/io-api.h`. 17This interface was designed to minimize the amount of complexity exposed 18to its clients, which are of course the sequences of calls generated by 19the compiler to implement each I/O statement. 20By keeping this interface as simple as possible, we hope that we have 21lowered the risk of future incompatible changes that would necessitate 22recompilation of Fortran codes in order to link with later versions of 23the runtime library. 24As one will see in `io-api.h`, the interface is also directly callable 25from C and C++ programs. 26 27The I/O facilities of the Fortran 2018 language are specified in the 28language standard in its clauses 12 (I/O statements) and 13 (`FORMAT`). 29It's a complicated collection of language features: 30 * Files can comprise *records* or *streams*. 31 * Records can be fixed-length or variable-length. 32 * Record files can be accessed sequentially or directly (random access). 33 * Files can be *formatted*, or *unformatted* raw bits. 34 * `CHARACTER` scalars and arrays can be used as if they were 35fixed-length formatted sequential record files. 36 * Formatted I/O can be under control of a `FORMAT` statement 37or `FMT=` specifier, *list-directed* with default formatting chosen 38by the runtime, or `NAMELIST`, in which a collection of variables 39can be given a name and passed as a group to the runtime library. 40 * Sequential records of a file can be partially processed by one 41or more *non-advancing* I/O statements and eventually completed by 42another. 43 * `FORMAT` strings can manipulate the position in the current 44record arbitrarily, causing re-reading or overwriting. 45 * Floating-point output formatting supports more rounding modes 46than the IEEE standard for floating-point arithmetic. 47 48The Fortran I/O runtime support library is written in C++17, and 49uses some C++17 standard library facilities, but it is intended 50to not have any link-time dependences on the C++ runtime support 51library or any LLVM libraries. 52This is important because there are at least two C++ runtime support 53libraries, and we don't want Fortran application builders to have to 54build multiple versions of their codes; neither do we want to require 55them to ship LLVM libraries along with their products. 56 57Consequently, dynamic memory allocation in the Fortran runtime 58uses only C's `malloc()` and `free()` functions, and the few 59C++ standard class templates that we instantiate in the library have been 60modified with optional template arguments that override their 61allocators and deallocators. 62 63Conversions between the many binary floating-point formats supported 64by f18 and their decimal representations are performed with the same 65template library of fast conversion algorithms used to interpret 66floating-point values in Fortran source programs and to emit them 67to module files. 68 69Overview of Classes 70=================== 71 72A suite of C++ classes and class templates are composed to construct 73the Fortran I/O runtime support library. 74They (mostly) reside in the C++ namespace `Fortran::runtime::io`. 75They are summarized here in a bottom-up order of dependence. 76 77The header and C++ implementation source file names of these 78classes are in the process of being vigorously rearranged and 79modified; use `grep` or an IDE to discover these classes in 80the source for now. (Sorry!) 81 82`Terminator` 83---------- 84A general facility for the entire library, `Terminator` latches a 85source program statement location in terms of an unowned pointer to 86its source file path name and line number and uses them to construct 87a fatal error message if needed. 88It is used for both user program errors and internal runtime library crashes. 89 90`IoErrorHandler` 91-------------- 92When I/O error conditions arise at runtime that the Fortran program 93might have the privilege to handle itself via `ERR=`, `END=`, or 94`EOR=` labels and/or by an `IOSTAT=` variable, this subclass of 95`Terminator` is used to either latch the error indication or to crash. 96It sorts out priorities in the case of multiple errors and determines 97the final `IOSTAT=` value at the end of an I/O statement. 98 99`MutableModes` 100------------ 101Fortran's formatted I/O statements are affected by a suite of 102modes that can be configured by `OPEN` statements, overridden by 103data transfer I/O statement control lists, and further overridden 104between data items with control edit descriptors in a `FORMAT` string. 105These modes are represented with a `MutableModes` instance, and these 106are instantiated and copied where one would expect them to be in 107order to properly isolate their modifications. 108The modes in force at the time each data item is processed constitute 109a member of each `DataEdit`. 110 111`DataEdit` 112-------- 113Represents a single data edit descriptor from a `FORMAT` statement 114or `FMT=` character value, with some hidden extensions to also 115support formatting of list-directed transfers. 116It holds an instance of `MutableModes`, and also has a repetition 117count for when an array appears as a data item in the *io-list*. 118For simplicity and efficiency, each data edit descriptor is 119encoded in the `DataEdit` as a simple capitalized character 120(or two) and some optional field widths. 121 122`FormatControl<>` 123--------------- 124This class template traverses a `FORMAT` statement's contents (or `FMT=` 125character value) to extract data edit descriptors like `E20.14` to 126serve each item in an I/O data transfer statement's *io-list*, 127making callbacks to an instance of its class template argument 128along the way to effect character literal output and record 129positioning. 130The Fortran language standard defines formatted I/O as if the `FORMAT` 131string were driving the traversal of the data items in the *io-list*, 132but our implementation reverses that perspective to allow a more 133convenient (for the compiler) I/O runtime support library API design 134in which each data item is presented to the library with a distinct 135type-dependent call. 136 137Clients of `FormatControl` instantiations call its `GetNextDataEdit()` 138member function to acquire the next data edit descriptor to be processed 139from the format, and `FinishOutput()` to flush out any remaining 140output strings or record positionings at the end of the *io-list*. 141 142The `DefaultFormatControlCallbacks` structure summarizes the API 143expected by `FormatControl` from its class template actual arguments. 144 145`OpenFile` 146-------- 147This class encapsulates all (I hope) the operating system interfaces 148used to interact with the host's filesystems for operations on 149external units. 150Asynchronous I/O interfaces are faked for now with synchronous 151operations and deferred results. 152 153`ConnectionState` 154--------------- 155An active connection to an external or internal unit maintains 156the common parts of its state in this subclass of `ConnectionAttributes`. 157The base class holds state that should not change during the 158lifetime of the connection, while the subclass maintains state 159that may change during I/O statement execution. 160 161`InternalDescriptorUnit` 162---------------------- 163When I/O is being performed from/to a Fortran `CHARACTER` array 164rather than an external file, this class manages the standard 165interoperable descriptor used to access its elements as records. 166It has the necessary interfaces to serve as an actual argument 167to the `FormatControl` class template. 168 169`FileFrame<>` 170----------- 171This CRTP class template isolates all of the complexity involved between 172an external unit's `OpenFile` and the buffering requirements 173imposed by the capabilities of Fortran `FORMAT` control edit 174descriptors that allow repositioning within the current record. 175Its interface enables its clients to define a "frame" (my term, 176not Fortran's) that is a contiguous range of bytes that are 177or may soon be in the file. 178This frame is defined as a file offset and a byte size. 179The `FileFrame` instance manages an internal circular buffer 180with two essential guarantees: 181 1821. The most recently requested frame is present in the buffer 183and contiguous in memory. 1841. Any extra data after the frame that may have been read from 185the external unit will be preserved, so that it's safe to 186read from a socket, pipe, or tape and not have to worry about 187repositioning and rereading. 188 189In end-of-file situations, it's possible that a request to read 190a frame may come up short. 191 192As a CRTP class template, `FileFrame` accesses the raw filesystem 193facilities it needs from `*this`. 194 195`ExternalFileUnit` 196---------------- 197This class mixes in `ConnectionState`, `OpenFile`, and 198`FileFrame<ExternalFileUnit>` to represent the state of an open 199(or soon to be opened) external file descriptor as a Fortran 200I/O unit. 201It has the contextual APIs required to serve as a template actual 202argument to `FormatControl`. 203And it contains a `std::variant<>` suitable for holding the 204state of the active I/O statement in progress on the unit 205(see below). 206 207`ExternalFileUnit` instances reside in a `Map` that is allocated 208as a static variable and indexed by Fortran unit number. 209Static member functions `LookUp()`, `LookUpOrCrash()`, and `LookUpOrCreate()` 210probe the map to convert Fortran `UNIT=` numbers from I/O statements 211into references to active units. 212 213`IoStatementBase` 214--------------- 215The subclasses of `IoStatementBase` each encapsulate and maintain 216the state of one active Fortran I/O statement across the several 217I/O runtime library API function calls it may comprise. 218The subclasses handle the distinctions between internal vs. external I/O, 219formatted vs. list-directed vs. unformatted I/O, input vs. output, 220and so on. 221 222`IoStatementBase` inherits default `FORMAT` processing callbacks and 223an `IoErrorHandler`. 224Each of the `IoStatementBase` classes that pertain to formatted I/O 225support the contextual callback interfaces needed by `FormatControl`, 226overriding the default callbacks of the base class, which crash if 227called inappropriately (e.g., if a `CLOSE` statement somehow 228passes a data item from an *io-list*). 229 230The lifetimes of these subclasses' instances each begin with a user 231program call to an I/O API routine with a name like `BeginExternalListOutput()` 232and persist until `EndIoStatement()` is called. 233 234To reduce dynamic memory allocation, *external* I/O statements allocate 235their per-statement state class instances in space reserved in the 236`ExternalFileUnit` instance. 237Internal I/O statements currently use dynamic allocation, but 238the I/O API supports a means whereby the code generated for the Fortran 239program may supply stack space to the I/O runtime support library 240for this purpose. 241 242`IoStatementState` 243---------------- 244F18's Fortran I/O runtime support library defines and implements an API 245that uses a sequence of function calls to implement each Fortran I/O 246statement. 247The state of each I/O statement in progress is maintained in some 248subclass of `IoStatementBase`, as noted above. 249The purpose of `IoStatementState` is to provide generic access 250to the specific state classes without recourse to C++ `virtual` 251functions or function pointers, language features that may not be 252available to us in some important execution environments. 253`IoStatementState` comprises a `std::variant<>` of wrapped references 254to the various possibilities, and uses `std::visit()` to 255access them as needed by the I/O API calls that process each specifier 256in the I/O *control-list* and each item in the *io-list*. 257 258Pointers to `IoStatementState` instances are the `Cookie` type returned 259in the I/O API for `Begin...` I/O statement calls, passed back for 260the *control-list* specifiers and *io-list* data items, and consumed 261by the `EndIoStatement()` call at the end of the statement. 262 263Storage for `IoStatementState` is reserved in `ExternalFileUnit` for 264external I/O units, and in the various final subclasses for internal 265I/O statement states otherwise. 266 267Since Fortran permits a `CLOSE` statement to reference a nonexistent 268unit, the library has to treat that (expected to be rare) situation 269as a weird variation of internal I/O since there's no `ExternalFileUnit` 270available to hold its `IoStatementBase` subclass or `IoStatementState`. 271 272A Narrative Overview Of `PRINT *, 'HELLO, WORLD'` 273================================================= 2741. When the compiled Fortran program begins execution at the `main()` 275entry point exported from its main program, it calls `ProgramStart()` 276with its arguments and environment. 2771. The generated code calls `BeginExternalListOutput()` to 278start the sequence of calls that implement the `PRINT` statement. 279Since the Fortran runtime I/O library has not yet been used in 280this process, its data structures are initialized on this 281first call, and Fortran I/O units 5 and 6 are connected with 282the stadard input and output file descriptors (respectively). 283The default unit code is converted to 6 and passed to 284`ExternalFileUnit::LookUpOrCrash()`, which returns a reference to 285unit 6's instance. 2861. We check that the unit was opened for formatted I/O. 2871. `ExternalFileUnit::BeginIoStatement<>()` is called to initialize 288an instance of `ExternalListIoStatementState<false>` in the unit, 289point to it with an `IoStatementState`, and return a reference to 290that object whose address will be the `Cookie` for this statement. 2911. The generated code calls `OutputAscii()` with that cookie and the 292address and length of the string. 2931. `OutputAscii()` confirms that the cookie corresponds to an output 294statement and determines that it's list-directed. 2951. `ListDirectedStatementState<false>::EmitLeadingSpaceOrAdvance()` 296emits the required initial space on the new current output record 297by calling `IoStatementState::GetConnectionState()` to locate 298the connection state, determining from the record position state 299that the space is necessary, and calling `IoStatementState::Emit()` 300to cough it out. That call is redirected to `ExternalFileUnit::Emit()`, 301which calls `FileFrame<ExternalFileUnit>::WriteFrame()` to extend 302the frame of the current record and then `memcpy()` to fill its 303first byte with the space. 3041. Back in `OutputAscii()`, the mutable modes and connection state 305of the `IoStatementState` are queried to see whether we're in an 306`WRITE(UNIT=,FMT=,DELIM=)` statement with a delimited specifier. 307If we were, the library would emit the appropriate quote marks, 308double up any instances of that character in the text, and split the 309text over multiple records if it's long. 3101. But we don't have a delimiter, so `OutputAscii()` just carves 311up the text into record-sized chunks and emits them. There's just 312one chunk for our short `CHARACTER` string value in this example. 313It's passed to `IoStatementState::Emit()`, which (as above) is 314redirected to `ExternalFileUnit::Emit()`, which interacts with the 315frame to extend the frame and `memcpy` data into the buffer. 3161. A flag is set in `ListDirectedStatementState<false>` to remember 317that the last item emitted in this list-directed output statement 318was an undelimited `CHARACTER` value, so that if the next item is 319also an undelimited `CHARACTER`, no interposing space will be emitted 320between them. 3211. `OutputAscii()` return `true` to its caller. 3221. The generated code calls `EndIoStatement()`, which is redirected to 323`ExternalIoStatementState<false>`'s override of that function. 324As this is not a non-advancing I/O statement, `ExternalFileUnit::AdvanceRecord()` 325is called to end the record. Since this is a sequential formatted 326file, a newline is emitted. 3271. If unit 6 is connected to a terminal, the buffer is flushed. 328`FileFrame<ExternalFileUnit>::Flush()` drives `ExternalFileUnit::Write()` 329to push out the data in maximal contiguous chunks, dealing with any 330short writes that might occur, and collecting I/O errors along the way. 331This statement has no `ERR=` label or `IOSTAT=` specifier, so errors 332arriving at `IoErrorHandler::SignalErrno()` will cause an immediate 333crash. 3341. `ExternalIoStatementBase::EndIoStatement()` is called. 335It gets the final `IOSTAT=` value from `IoStatementBase::EndIoStatement()`, 336tells the `ExternalFileUnit` that no I/O statement remains active, and 337returns the I/O status value back to the program. 3381. Eventually, the program calls `ProgramEndStatement()`, which 339calls `ExternalFileUnit::CloseAll()`, which flushes and closes all 340open files. If the standard output were not a terminal, the output 341would be written now with the same sequence of calls as above. 3421. `exit(EXIT_SUCCESS)`. 343