[flang-commits] [flang] [llvm] Flang improve fidelity of unformatted I/O endianness with FORT_CONVET_UNIT (PR #223831)
David Parks via flang-commits
flang-commits at lists.llvm.org
Tue Sep 15 14:04:51 PDT 2026
https://github.com/d-parks created https://github.com/llvm/llvm-project/pull/223831
Add further fidelity to specifying desired endianness of unformatted I/O file on a per unit basis with the `FORT_CONVERT_UNIT` environment variable.
@vzakhari @klausler @sscalpone
To reviewers (friends) I'm not sure about:
1. The correct function prefixing for the FLANG runtime environment.
2. How to test/validate on Windows
3. How to test/validate with CUDA runtime support enabled
Thank you.
https://discourse.llvm.org/t/runtime-unformatted-i-o-conversion-between-big-and-little-endian-formats/91751
Documentation for new environment variable:
## `FORT_CONVERT_UNIT`
Determines the data conversion applied to specific unformatted I/O units.
```
FORT_CONVERT_UNIT= mode | mode ';' exception | exception ;
mode: 'NATIVE' | 'LITTLE_ENDIAN' | 'BIG_ENDIAN' | 'SWAP';
exception: mode ':' unit_list | unit_list ;
unit_list: unit_spec | unit_list ',' unit_spec ;
unit_spec: integer | integer '-' integer ;
integer: [0-9]+ ;
```
The endianness of unformatted files is determined in the following order:
1. The host processor's native endianness.
5. The setting of the `-fconvert=<mode>` flang compiler command-line option.
6. The global setting from the `FORT_CONVERT=mode` or `FORT_CONVERT_UNIT=mode`
environment variables. Contradictory mode settings between `FORT_CONVERT` and
`FORT_CONVERT_UNIT` result in `FORT_CONVERT_UNIT` taking priority.
7. The explicit setting of the `CONVERT=` specifier in the `OPEN` statement for
a particular unit.
8. The exception setting for an individual unit or range of units from the
`FORT_CONVERT_UNIT` environment variable.
### Examples
* If the `FORT_CONVERT`, `FORT_CONVERT_UNIT`, and `CONVERT=` specifier in the
`OPEN` statement are all missing, no data conversion is performed for
unformatted I/O; the host processor's native encoding is used.
* If the environment variable `FORT_CONVERT=BIG_ENDIAN` is set and no
`CONVERT=` specifier is present in the `OPEN` statement, input is assumed to
be big-endian, and output is emitted in big-endian format.
* On a little-endian host, if the environment variable `FORT_CONVERT=SWAP` is
set and the `CONVERT=LITTLE_ENDIAN` specifier is present in the `OPEN`
statement, the `OPEN` statement takes precedence: input is assumed to be
little-endian, and output is emitted in little-endian format.
* If unit 10 is opened on a little-endian host with the environment variable
`FORT_CONVERT=SWAP`, `FORT_CONVERT_UNIT=BIG_ENDIAN:10`, and an `OPEN`
statement for unit 10 with the `CONVERT=LITTLE_ENDIAN` specifier, the
`BIG_ENDIAN:10` exception from the `FORT_CONVERT_UNIT` environment variable
takes precedence: input is assumed to be big-endian, and output is emitted
in big-endian format.
### Notes
1. `<mode>` values specified with the runtime environment variables
`FORT_CONVERT` or `FORT_CONVERT_UNIT` are case-insensitive.
2. `<mode>` values specified with the flang command-line option
`-fconvert=<mode>` are case-sensitive and include:
`mode: 'native' | 'little-endian' | 'big-endian' | 'swap';`
3. `unit_spec` supports ranges separated by a hyphen. Ranges must denote
positive unit numbers, and the starting unit (LHS) must be less than or equal
to the ending unit (RHS).
4. Unit numbers and ranges can be specified multiple times with different
`modes`, with the last (rightmost) `exception` taking priority. For example:
`FORT_CONVERT_UNIT="LITTLE_ENDIAN:10,11,15-20;BIG_ENDIAN:19"`
The conversion for unit 19 will be `BIG_ENDIAN`.
5. If an `exception` is not prefixed with `mode:`, `mode` is assumed to be
`BIG_ENDIAN`. For example:
`FORT_CONVERT_UNIT="20-25;LITTLE_ENDIAN:26-27"`
Regardless of the host processor's endianness, units 20 through 25 will be
treated as big-endian for both input and output, while units 26 and 27 will be
treated as little-endian for both input and output.
>From 0a16a181b58c055e351ca4b5ed53c8b9822cdbff Mon Sep 17 00:00:00 2001
From: David Parks <djp at david-parks.com>
Date: Tue, 15 Sep 2026 14:35:55 -0600
Subject: [PATCH] Flang improve fidelity of unformatted I/O endianness with
FORT_CONVERT_UNIT
---
.../include/flang-rt/runtime/environment.h | 13 ++
flang-rt/lib/runtime/array.h | 11 +-
flang-rt/lib/runtime/environment.cpp | 161 +++++++++++++++++-
flang-rt/lib/runtime/external-unit.cpp | 18 ++
flang-rt/test/Runtime/fort_convert.f90 | 151 ++++++++++++++++
flang/docs/RuntimeEnvironment.md | 79 ++++++++-
6 files changed, 427 insertions(+), 6 deletions(-)
create mode 100644 flang-rt/test/Runtime/fort_convert.f90
diff --git a/flang-rt/include/flang-rt/runtime/environment.h b/flang-rt/include/flang-rt/runtime/environment.h
index 8ae441c12b3f8..bba75cc06f5b5 100644
--- a/flang-rt/include/flang-rt/runtime/environment.h
+++ b/flang-rt/include/flang-rt/runtime/environment.h
@@ -9,6 +9,7 @@
#ifndef FLANG_RT_RUNTIME_ENVIRONMENT_H_
#define FLANG_RT_RUNTIME_ENVIRONMENT_H_
+#include "../lib/runtime/array.h"
#include "flang/Common/optional.h"
#include "flang/Decimal/decimal.h"
#include "flang/Runtime/entry-names.h"
@@ -37,6 +38,14 @@ RT_API_ATTRS common::optional<Convert> GetConvertFromString(
struct ExecutionEnvironment {
+ // List of unit(s) from environment variable FORT_CONVERT_UNIT with specific
+ // conversion rules.
+ struct ConvertUnit {
+ Convert conversion;
+ std::int32_t startUnit;
+ std::int32_t endUnit;
+ };
+
typedef void (*ConfigEnvCallbackPtr)(
int, const char *[], const char *[], const EnvironmentDefaultList *);
@@ -58,6 +67,9 @@ struct ExecutionEnvironment {
std::int32_t UnsetEnv(
const char *name, std::size_t name_length, const Terminator &terminator);
+ bool ParseFortConvertUnit(const char *);
+ Convert UnitRtConvert(int);
+
int argc{0};
const char **argv{nullptr};
char **envp{nullptr};
@@ -66,6 +78,7 @@ struct ExecutionEnvironment {
enum decimal::FortranRounding defaultOutputRoundingMode{
decimal::FortranRounding::RoundNearest}; // RP(==PN)
Convert conversion{Convert::Unknown}; // FORT_CONVERT
+ DynamicArray<ConvertUnit> convertUnits; // FORT_CONVERT_UNIT
bool noStopMessage{false}; // NO_STOP_MESSAGE=1 inhibits "Fortran STOP"
// FLANG_TIMEF_IN_MILLISECONDS=1 sets TIMEF resolution to milliseconds.
// Default resolution is seconds.
diff --git a/flang-rt/lib/runtime/array.h b/flang-rt/lib/runtime/array.h
index 4b9bf4541ad7b..ab0d0e2eb3d26 100644
--- a/flang-rt/lib/runtime/array.h
+++ b/flang-rt/lib/runtime/array.h
@@ -15,11 +15,18 @@
namespace Fortran::runtime {
// A simple dynamic array that only supports appending to avoid std::vector.
template <typename T> struct DynamicArray {
- ~DynamicArray() {
+ ~DynamicArray() { clear(); }
+
+ // clear() - remove all elements from array and free allocated memory.
+ void clear() {
+ if (capacity_ == 0) {
+ return;
+ }
for (std::size_t i = 0; i < size_; ++i) {
data_[i].~T();
}
FreeMemory(data_);
+ size_ = capacity_ = 0;
}
void emplace_back(T &&value) {
@@ -47,6 +54,8 @@ template <typename T> struct DynamicArray {
T *begin() const { return data_; }
T *end() const { return data_ + size_; }
+ std::size_t size() const { return size_; }
+ bool empty() const { return 0 == size_; }
private:
T *data_ = nullptr;
diff --git a/flang-rt/lib/runtime/environment.cpp b/flang-rt/lib/runtime/environment.cpp
index ea98eb4634293..0663399a55f75 100644
--- a/flang-rt/lib/runtime/environment.cpp
+++ b/flang-rt/lib/runtime/environment.cpp
@@ -1,3 +1,4 @@
+#include <string_view>
//===-- lib/runtime/environment.cpp -----------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
@@ -6,8 +7,8 @@
//
//===----------------------------------------------------------------------===//
-#include "flang-rt/runtime/environment.h"
#include "environment-default-list.h"
+#include "flang-rt/runtime/environment.h"
#include "flang-rt/runtime/memory.h"
#include "flang-rt/runtime/tools.h"
#include <cstdio>
@@ -97,6 +98,157 @@ common::optional<Convert> GetConvertFromString(const char *x, std::size_t n) {
}
RT_OFFLOAD_API_GROUP_END
+bool ExecutionEnvironment::ParseFortConvertUnit(const char *cenvStr) {
+ bool success{true};
+ char *envStr{strdup(cenvStr)};
+
+ if (nullptr == envStr) {
+ Terminator{__FILE__, __LINE__}.Crash(
+ "FORT_CONVERT_UNIT: could not allocate memory");
+ }
+
+ char *exceptionSvptr{nullptr};
+ char *exceptionStr;
+ char *unitListStr;
+ bool firstException{true};
+ Convert gblConversion{conversion};
+
+ // Flang's FORT_CONVERT_UNIT syntax follows NVIDIA's FORT_CONVERT_UNIT and
+ // GNU's gfortran GFORTRAN_CONVERT_UNIT.
+ //
+ // FORT_CONVERT_UNIT: mode | mode ';' exception | exception ;
+ // mode: 'native' | 'swap' | 'big_endian' | 'little_endian' ;
+ // exception: mode ':' unit_list | unit_list ;
+ // unit_list: unit_spec | unit_list ',' unit_spec ;
+ // unit_spec: INTEGER | INTEGER '-' INTEGER ;
+
+ while (success) {
+#if _WIN32
+ exceptionStr =
+ strtok_s(exceptionSvptr ? nullptr : envStr, ";", &exceptionSvptr);
+#else
+ exceptionStr =
+ strtok_r(exceptionSvptr ? nullptr : envStr, ";", &exceptionSvptr);
+#endif
+ if (nullptr == exceptionStr) {
+ break;
+ }
+
+ // unitList is not yet correct, might be nullptr or pointing to ':'.
+ unitListStr = strchr(exceptionStr, ':');
+
+ if (firstException && (nullptr == unitListStr) &&
+ !isdigit(exceptionStr[0])) {
+ // if first pass extracting an exception, and exceptionStr does not
+ // contain a ':' this becomes the global setting.
+ // Akin to specifying FORT_CONVERT=<mode>.
+ if (auto convert{
+ GetConvertFromString(exceptionStr, std::strlen(exceptionStr))}) {
+ gblConversion = *convert;
+ } else {
+ success = false;
+ break;
+ }
+ firstException = false;
+ continue;
+ }
+
+ // mode ';' exception | exception
+ char *unitListSvptr{nullptr};
+ // For the case where <mode> is unspecified
+ Convert conversion{Convert::BigEndian};
+
+ // If unitListStr != nullptr extract mode from modeStr[:unitListStr-1].
+ if (nullptr != unitListStr) {
+ *unitListStr++ = '\0'; // Advance unitListStr
+ if (auto convert{
+ GetConvertFromString(exceptionStr, std::strlen(exceptionStr))}) {
+ conversion = *convert;
+ } else {
+ success = false;
+ break;
+ }
+ } else {
+ unitListStr = exceptionStr;
+ }
+
+ unitListSvptr = nullptr;
+
+ // Loop over unit list extracting individual or ranges of units, separated
+ // by commas.
+
+ while (success) {
+ char *units;
+ int lb, ub;
+ char remStr[2];
+ int nread;
+ int nexpected;
+
+ lb = ub = -1;
+#if _WIN32
+ units =
+ strtok_s(unitListSvptr ? nullptr : unitListStr, ";", &unitListSvptr);
+#else
+ units =
+ strtok_r(unitListSvptr ? nullptr : unitListStr, ",", &unitListSvptr);
+#endif
+ if (nullptr == units) {
+ break;
+ }
+
+ // single unit or range of units.
+ // If hyphen is detected in units, assume range
+ if (strchr(units, '-')) {
+ nexpected = 2;
+ nread = sscanf(units, "%u-%u%1s", &lb, &ub, remStr);
+ } else {
+ nexpected = 1;
+ nread = sscanf(units, "%u%1s", &lb, remStr);
+ ub = lb;
+ }
+ if (nread != nexpected || (lb < 0) || (ub < 0) || (lb > ub)) {
+ success = false;
+ break;
+ }
+
+ ConvertUnit cu;
+ cu.conversion = conversion;
+ cu.startUnit = lb;
+ cu.endUnit = ub;
+ convertUnits.emplace_back(std::move(cu));
+ }
+ }
+
+ free(envStr); // from strdup()
+
+ if (success) {
+ conversion = gblConversion;
+ } else {
+ // Failure(s)
+ convertUnits.clear();
+ }
+ return success;
+}
+
+// ExecutionEnvironment::UnitRtConvert
+// Scan linear array ExecutionEnvironment::convertUnits for unitNumber, and if
+// found, return user specified (runtime) I/O conversion for unformatted
+// files.
+
+Convert ExecutionEnvironment::UnitRtConvert(int unitNumber) {
+ Convert convertReturn{Convert::Unknown};
+ // convertUnits is a small array, but still iterate backwards.
+ // rbegin() and rend() are not available.
+ for (auto it = convertUnits.end(); it != convertUnits.begin();) {
+ --it;
+ if (unitNumber >= it->startUnit && unitNumber <= it->endUnit) {
+ convertReturn = it->conversion;
+ break;
+ }
+ }
+ return convertReturn;
+}
+
void ExecutionEnvironment::Configure(int ac, const char *av[],
const char *env[], const EnvironmentDefaultList *envDefaults) {
argc = ac;
@@ -146,6 +298,13 @@ void ExecutionEnvironment::Configure(int ac, const char *av[],
}
}
+ if (auto *x{std::getenv("FORT_CONVERT_UNIT")}) {
+ if (!ParseFortConvertUnit(x)) {
+ std::fprintf(stderr,
+ "Fortran runtime: FORT_CONVERT_UNIT=%s is invalid; ignored\n", x);
+ }
+ }
+
if (auto *x{std::getenv("FORT_TRUNCATE_STREAM")}) {
char *end;
auto n{std::strtol(x, &end, 10)};
diff --git a/flang-rt/lib/runtime/external-unit.cpp b/flang-rt/lib/runtime/external-unit.cpp
index b4eaf2e85fc9a..67a9b9c0f397d 100644
--- a/flang-rt/lib/runtime/external-unit.cpp
+++ b/flang-rt/lib/runtime/external-unit.cpp
@@ -11,6 +11,7 @@
//===----------------------------------------------------------------------===//
#include "unit-map.h"
+#include "flang-rt/runtime/environment.h"
#include "flang-rt/runtime/io-error.h"
#include "flang-rt/runtime/lock.h"
#include "flang-rt/runtime/tools.h"
@@ -112,9 +113,26 @@ bool ExternalFileUnit::OpenUnit(common::optional<OpenStatus> status,
common::optional<Action> action, Position position,
OwningPtr<char> &&newPath, std::size_t newPathLength, Convert convert,
IoErrorHandler &handler) {
+ Convert explicitRtConvert{Convert::Unknown};
+
+ // Increasing order of conversion specifiers (endianness)
+ // 1. CONVERT=<mode> specifier on OPEN statement.
+ // 2. Environment variable FORT_CONVERT iff CONVERT specifier is not present.
+ // 3. Even if CONVERT specifier is present, check to environment variable
+ // FORT_CONVERT_UNIT to see if unit number has an explicit setting.
+
if (convert == Convert::Unknown) {
convert = executionEnvironment.conversion;
}
+
+ explicitRtConvert = executionEnvironment.UnitRtConvert(unitNumber());
+ if (Convert::Unknown != explicitRtConvert) {
+ // Runtime has overridden previous defaults using environment variable
+ // FORT_CONVERT_UNIT.
+ convert = explicitRtConvert;
+ }
+
+ executionEnvironment.UnitRtConvert(unitNumber());
swapEndianness_ = convert == Convert::Swap ||
(convert == Convert::LittleEndian && !isHostLittleEndian) ||
(convert == Convert::BigEndian && isHostLittleEndian);
diff --git a/flang-rt/test/Runtime/fort_convert.f90 b/flang-rt/test/Runtime/fort_convert.f90
new file mode 100644
index 0000000000000..19fc173ec9bf0
--- /dev/null
+++ b/flang-rt/test/Runtime/fort_convert.f90
@@ -0,0 +1,151 @@
+! UNSUPPORTED: offload-cuda
+! UNSUPPORTED: system-windows
+
+! Verify endian conversion for unformatted stream I/O with and
+! without runtime environment variable FORT_CONVERT_UNIT specified.
+!
+! Default units:
+! OPEN(10, FORM="unformatted")
+! OPEN(11, FORM="unformatted", CONVERT="native")
+! OPEN(12, FORM="unformatted", CONVERT="big_endian")
+! OPEN(13, FORM="unformatted", CONVERT="little_endian")
+! OPEN(14, FORM="unformatted", CONVERT="swap")
+!
+! First test, FORT_CONVERT_UNIT not present.
+! Second test: FORT_CONVERT_UNIT="swap:10-11;little_endian:12;big_endian:13;native:14"
+
+! RUN: %flang %isysroot -L"%libdir" %s -o %t
+! RUN: %t | FileCheck %s
+! RUN: env FORT_CONVERT_UNIT="swap:10-11;little_endian:12;big_endian:13;native:14" %t | FileCheck %s
+
+! CHECK: PASS
+module testmod
+ implicit none
+ character(len=1), dimension(4) :: c4
+ integer(kind=4) :: i4 = 1
+ integer(kind=4), dimension(2) :: data10 = [10, int(z'01cafe23')]
+ integer(kind=4), dimension(2) :: data11 = [11, int(z'45beef67')]
+ integer(kind=4), dimension(2) :: data12 = [12, int(z'98faceab')]
+ integer(kind=4), dimension(2) :: data13 = [13, int(z'cdfeed01')]
+ integer(kind=4), dimension(2) :: data14 = [14, int(z'02deaf03')]
+
+ contains
+ logical function isLittleEndian()
+ c4 = transfer(i4, c4)
+ isLittleEndian = ichar(c4(1)) == 1
+ end function isLittleEndian
+
+ logical function isBigEndian()
+ isBigEndian = .not. isLittleEndian()
+ end function isBigEndian
+
+ logical function filecheck(unit, refdata, isNative) result(res)
+ integer(kind=4) :: unit
+ integer(kind=4), dimension(2) :: refdata
+ logical :: isNative
+
+ integer :: i
+ integer :: ios
+ integer(kind=4), dimension(4) :: filearr
+ integer(kind=4), dimension(4) :: workarr
+ character(len=1), dimension(4) :: charwork
+
+ res = .true.
+
+ read(unit, iostat=ios) filearr
+ if (ios /= 0) then
+ print*, 'ios=', ios
+ stop 1
+ end if
+
+ workarr(1) = int(z'00000008')
+ workarr(2:3) = refdata
+ workarr(4) = int(z'00000008')
+
+ ! If isNative == .true. file data should match in memory layout of refdata.
+ ! If isNative == .false., refdata (kind=4) has to have endianness switched.
+ if (.not. isNative) then
+ ! swap endianness of input reference data
+ do i = 1, 4
+ charwork = transfer(workarr(i), charwork)
+ charwork = charwork(size(charwork):1:-1)
+ workarr(i) = transfer(charwork, workarr(i))
+ end do
+ end if
+
+ res = .not. any(filearr /= workarr)
+
+ if (.not. res) then
+ write(*,'(4("0x", z8.8:x))') filearr
+ write(*,'(4("0x", z8.8:x))') workarr
+ endif
+
+ end function filecheck
+end module testmod
+program main
+ use testmod
+ implicit none
+ integer :: ios
+ integer :: i
+ logical :: FORT_CONVERT_UNIT_present
+
+ call get_environment_variable("FORT_CONVERT_UNIT", length=ios)
+ FORT_CONVERT_UNIT_present = ios /= 0
+
+ ! Some runtime debug statements.
+ ! print*, 'FORT_CONVERT_UNIT_present=', FORT_CONVERT_UNIT_present
+ ! if (isLittleEndian()) print *, 'Little Endian'
+
+ open(10, iostat=ios, form="unformatted", access="sequential", status="unknown")
+ if (ios /= 0) stop 10
+ open(11, iostat=ios, form="unformatted", access="sequential", status="unknown", convert="native")
+ if (ios /= 0) stop 11
+ open(12, iostat=ios, form="unformatted", access="sequential", status="unknown", convert="big_endian")
+ if (ios /= 0) stop 12
+ open(13, iostat=ios, form="unformatted", access="sequential", status="unknown", convert="little_endian")
+ if (ios /= 0) stop 13
+ open(14, iostat=ios, form="unformatted", access="sequential", status="unknown", convert="swap")
+ if (ios /= 0) stop 14
+
+ write(10, iostat=ios) data10
+ if (ios /= 0) stop 20
+ write(11, iostat=ios) data11
+ if (ios /= 0) stop 21
+ write(12, iostat=ios) data12
+ if (ios /= 0) stop 22
+ write(13, iostat=ios) data13
+ if (ios /= 0) stop 23
+ write(14, iostat=ios) data14
+ if (ios /= 0) stop 24
+
+ do i = 10, 14
+ close(i)
+ end do
+
+ open(20, iostat=ios, form="unformatted", access="stream", name="fort.10", status="old", convert="native")
+ if (ios /= 0) stop 30
+ open(21, iostat=ios, form="unformatted", access="stream", name="fort.11", status="old", convert="native")
+ if (ios /= 0) stop 31
+ open(22, iostat=ios, form="unformatted", access="stream", name="fort.12", status="old", convert="native")
+ if (ios /= 0) stop 32
+ open(23, iostat=ios, form="unformatted", access="stream", name="fort.13", status="old", convert="native")
+ if (ios /= 0) stop 33
+ open(24, iostat=ios, form="unformatted", access="stream", name="fort.14", status="old", convert="native")
+ if (ios /= 0) stop 34
+
+ if (FORT_CONVERT_UNIT_present) then
+ if (.not. filecheck(20, data10, .false.)) stop 40
+ if (.not. filecheck(21, data11, .false.)) stop 41
+ if (.not. filecheck(22, data12, isLittleEndian())) stop 42
+ if (.not. filecheck(23, data13, isBigEndian())) stop 43
+ if (.not. filecheck(24, data14, .true.)) stop 44
+ else
+ if (.not. filecheck(20, data10, .true.)) stop 45
+ if (.not. filecheck(21, data11, .true.)) stop 46
+ if (.not. filecheck(22, data12, isBigEndian())) stop 47
+ if (.not. filecheck(23, data13, isLittleEndian())) stop 48
+ if (.not. filecheck(24, data14, .false.)) stop 49
+ endif
+
+ print *,'PASS'
+end program main
diff --git a/flang/docs/RuntimeEnvironment.md b/flang/docs/RuntimeEnvironment.md
index 0414ea1e0d59c..6f6edac13ac3f 100644
--- a/flang/docs/RuntimeEnvironment.md
+++ b/flang/docs/RuntimeEnvironment.md
@@ -27,12 +27,83 @@ encoding on input and use UTF-8 encoding on formatted external output.
## `FORT_CONVERT`
-Determines data conversions applied to unformatted I/O.
+Determines the data conversions applied to all unformatted I/O units that do
+not have an explicit `CONVERT=` specifier in their `OPEN` statements.
+`FORT_CONVERT` is an alias for the `FORT_CONVERT_UNIT` environment
+variable without any `exception`s.
+`FORT_CONVERT=mode`
+* `mode: 'NATIVE' | 'LITTLE_ENDIAN' | 'BIG_ENDIAN' | 'SWAP';`
* `NATIVE`: no conversions (default)
-* `LITTLE_ENDIAN`: assume input is little-endian; emit little-endian output
-* `BIG_ENDIAN`: assume input is big-endian; emit big-endian output
-* `SWAP`: reverse endianness (always convert)
+* `LITTLE_ENDIAN`: assumes that input is little-endian; emit little-endian
+output
+* `BIG_ENDIAN`: assumes that input is big-endian; emit big-endian output
+* `SWAP`: reverses the endianness (always convert)
+
+## `FORT_CONVERT_UNIT`
+
+Determines the data conversion applied to specific unformatted I/O units.
+
+```
+FORT_CONVERT_UNIT= mode | mode ';' exception | exception ;
+mode: 'NATIVE' | 'LITTLE_ENDIAN' | 'BIG_ENDIAN' | 'SWAP';
+exception: mode ':' unit_list | unit_list ;
+unit_list: unit_spec | unit_list ',' unit_spec ;
+unit_spec: integer | integer '-' integer ;
+integer: [0-9]+ ;
+```
+
+The endianness of unformatted files is determined in the following order:
+1. The host processor's native endianness.
+2. The setting of the `-fconvert=<mode>` flang compiler command-line option.
+3. The global setting from the `FORT_CONVERT=mode` or `FORT_CONVERT_UNIT=mode`
+environment variables. Contradictory mode settings between `FORT_CONVERT` and
+`FORT_CONVERT_UNIT` result in `FORT_CONVERT_UNIT` taking priority.
+4. The explicit setting of the `CONVERT=` specifier in the `OPEN` statement for
+a particular unit.
+5. The exception setting for an individual unit or range of units from the
+`FORT_CONVERT_UNIT` environment variable.
+
+### Examples
+
+* If the `FORT_CONVERT`, `FORT_CONVERT_UNIT`, and `CONVERT=` specifier in the
+ `OPEN` statement are all missing, no data conversion is performed for
+ unformatted I/O; the host processor's native encoding is used.
+* If the environment variable `FORT_CONVERT=BIG_ENDIAN` is set and no
+ `CONVERT=` specifier is present in the `OPEN` statement, input is assumed to
+ be big-endian, and output is emitted in big-endian format.
+* On a little-endian host, if the environment variable `FORT_CONVERT=SWAP` is
+ set and the `CONVERT=LITTLE_ENDIAN` specifier is present in the `OPEN`
+ statement, the `OPEN` statement takes precedence: input is assumed to be
+ little-endian, and output is emitted in little-endian format.
+* If unit 10 is opened on a little-endian host with the environment variable
+ `FORT_CONVERT=SWAP`, `FORT_CONVERT_UNIT=BIG_ENDIAN:10`, and an `OPEN`
+ statement for unit 10 with the `CONVERT=LITTLE_ENDIAN` specifier, the
+ `BIG_ENDIAN:10` exception from the `FORT_CONVERT_UNIT` environment variable
+ takes precedence: input is assumed to be big-endian, and output is emitted
+ in big-endian format.
+
+### Notes
+
+1. `<mode>` values specified with the runtime environment variables
+ `FORT_CONVERT` or `FORT_CONVERT_UNIT` are case-insensitive.
+2. `<mode>` values specified with the flang command-line option
+ `-fconvert=<mode>` are case-sensitive and include:
+`mode: 'native' | 'little-endian' | 'big-endian' | 'swap';`
+3. `unit_spec` supports ranges separated by a hyphen. Ranges must denote
+positive unit numbers, and the starting unit (LHS) must be less than or equal
+to the ending unit (RHS).
+4. Unit numbers and ranges can be specified multiple times with different
+`modes`, with the last (rightmost) `exception` taking priority. For example:
+`FORT_CONVERT_UNIT="LITTLE_ENDIAN:10,11,15-20;BIG_ENDIAN:19"`
+The conversion for unit 19 will be `BIG_ENDIAN`.
+5. If an `exception` is not prefixed with `mode:`, `mode` is assumed to be
+`BIG_ENDIAN`. For example:
+`FORT_CONVERT_UNIT="20-25;LITTLE_ENDIAN:26-27"`
+Regardless of the host processor's endianness, units 20 through 25 will be
+treated as big-endian for both input and output, while units 26 and 27 will be
+treated as little-endian for both input and output.
+
## `FORT_CHECK_POINTER_DEALLOCATION`
More information about the flang-commits
mailing list