[flang-commits] [flang] [flang][runtime] Implement EX editing for input & output (PR #67208)

via flang-commits flang-commits at lists.llvm.org
Fri Sep 22 16:40:04 PDT 2023


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-flang-runtime

<details>
<summary>Changes</summary>

Support the EX edit descriptor for hexadecimal real formatted output and hexadecimal real input for all forms of formatted input.. (We're possibly the first Fortran compiler to support this feature for input editing; only one other can handle EX output editing.)

As true (not BOZ) hexadecimal floating-point constants are not supported in Fortran source code, only in formatted input, the implementation takes place in the I/O editing portion of the runtime, not as new conversions in the Decimal library.

---

Patch is 40.95 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/67208.diff


7 Files Affected:

- (modified) flang/include/flang/Common/real.h (+7) 
- (modified) flang/include/flang/Decimal/binary-floating-point.h (+59) 
- (modified) flang/include/flang/Decimal/decimal.h (-8) 
- (modified) flang/runtime/edit-input.cpp (+214-79) 
- (modified) flang/runtime/edit-output.cpp (+146-18) 
- (modified) flang/runtime/edit-output.h (+9-15) 
- (modified) flang/unittests/Runtime/NumericalFormatTest.cpp (+60-2) 


``````````diff
diff --git a/flang/include/flang/Common/real.h b/flang/include/flang/Common/real.h
index 036f665d3da6194..50aab7d89a597ef 100644
--- a/flang/include/flang/Common/real.h
+++ b/flang/include/flang/Common/real.h
@@ -63,6 +63,10 @@ static constexpr int MaxDecimalConversionDigits(int binaryPrecision) {
   }
 }
 
+static constexpr int MaxHexadecimalConversionDigits(int binaryPrecision) {
+  return binaryPrecision >= 0 ? (binaryPrecision + 3) / 4 : binaryPrecision;
+}
+
 static constexpr int RealKindForPrecision(int binaryPrecision) {
   switch (binaryPrecision) {
   case 8: // IEEE single (truncated): 1+8+7 with implicit bit
@@ -132,6 +136,9 @@ template <int BINARY_PRECISION> class RealDetails {
   static constexpr int maxDecimalConversionDigits{
       MaxDecimalConversionDigits(binaryPrecision)};
 
+  static constexpr int maxHexadecimalConversionDigits{
+      MaxHexadecimalConversionDigits(binaryPrecision)};
+
   static_assert(binaryPrecision > 0);
   static_assert(exponentBits > 1);
   static_assert(exponentBits <= 15);
diff --git a/flang/include/flang/Decimal/binary-floating-point.h b/flang/include/flang/Decimal/binary-floating-point.h
index 28346e71828fd4f..b9346a8585e2d24 100644
--- a/flang/include/flang/Decimal/binary-floating-point.h
+++ b/flang/include/flang/Decimal/binary-floating-point.h
@@ -21,10 +21,19 @@
 
 namespace Fortran::decimal {
 
+enum FortranRounding {
+  RoundNearest, /* RN and RP */
+  RoundUp, /* RU */
+  RoundDown, /* RD */
+  RoundToZero, /* RZ - no rounding */
+  RoundCompatible, /* RC: like RN, but ties go away from 0 */
+};
+
 template <int BINARY_PRECISION>
 class BinaryFloatingPointNumber : public common::RealDetails<BINARY_PRECISION> {
 public:
   using Details = common::RealDetails<BINARY_PRECISION>;
+  using Details::binaryPrecision;
   using Details::bits;
   using Details::decimalPrecision;
   using Details::decimalRange;
@@ -33,6 +42,7 @@ class BinaryFloatingPointNumber : public common::RealDetails<BINARY_PRECISION> {
   using Details::isImplicitMSB;
   using Details::maxDecimalConversionDigits;
   using Details::maxExponent;
+  using Details::maxHexadecimalConversionDigits;
   using Details::significandBits;
 
   using RawType = common::HostUnsignedIntType<bits>;
@@ -120,6 +130,55 @@ class BinaryFloatingPointNumber : public common::RealDetails<BINARY_PRECISION> {
     InsertExplicitMSB();
   }
 
+  static constexpr BinaryFloatingPointNumber Infinity(bool isNegative) {
+    RawType result{RawType{maxExponent} << significandBits};
+    if (isNegative) {
+      result |= RawType{1} << (bits - 1);
+    }
+    return BinaryFloatingPointNumber{result};
+  }
+
+  // Returns true when the result is exact
+  constexpr bool RoundToBits(int keepBits, enum FortranRounding mode) {
+    if (IsNaN() || IsInfinite() || keepBits >= binaryPrecision) {
+      return true;
+    }
+    int lostBits{binaryPrecision - keepBits};
+    RawType lostMask{static_cast<RawType>((RawType{1} << lostBits) - 1)};
+    if (RawType lost{static_cast<RawType>(raw_ & lostMask)}; lost != 0) {
+      bool increase{false};
+      switch (mode) {
+      case RoundNearest:
+        if (lost >> (lostBits - 1) != 0) { // >= tie
+          if ((lost & (lostMask >> 1)) != 0) {
+            increase = true; // > tie
+          } else {
+            increase = ((raw_ >> lostBits) & 1) != 0; // tie to even
+          }
+        }
+        break;
+      case RoundUp:
+        increase = !IsNegative();
+        break;
+      case RoundDown:
+        increase = IsNegative();
+        break;
+      case RoundToZero:
+        break;
+      case RoundCompatible:
+        increase = lost >> (lostBits - 1) != 0; // >= tie
+        break;
+      }
+      if (increase) {
+        raw_ |= lostMask;
+        Next();
+      }
+      return false; // inexact
+    } else {
+      return true; // exact
+    }
+  }
+
 private:
   constexpr void RemoveExplicitMSB() {
     if constexpr (!isImplicitMSB) {
diff --git a/flang/include/flang/Decimal/decimal.h b/flang/include/flang/Decimal/decimal.h
index b9ac6b71cd03a7f..a4e0ee7c8474607 100644
--- a/flang/include/flang/Decimal/decimal.h
+++ b/flang/include/flang/Decimal/decimal.h
@@ -43,14 +43,6 @@ struct ConversionToDecimalResult {
   enum ConversionResultFlags flags;
 };
 
-enum FortranRounding {
-  RoundNearest, /* RN and RP */
-  RoundUp, /* RU */
-  RoundDown, /* RD */
-  RoundToZero, /* RZ - no rounding */
-  RoundCompatible, /* RC: like RN, but ties go away from 0 */
-};
-
 /* 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
diff --git a/flang/runtime/edit-input.cpp b/flang/runtime/edit-input.cpp
index 1861c9f8499b0b0..85096021d5193e1 100644
--- a/flang/runtime/edit-input.cpp
+++ b/flang/runtime/edit-input.cpp
@@ -229,17 +229,22 @@ bool EditIntegerInput(
 
 // Parses a REAL input number from the input source as a normalized
 // fraction into a supplied buffer -- there's an optional '-', a
-// decimal point, and at least one digit.  The adjusted exponent value
-// is returned in a reference argument.  The returned value is the number
-// of characters that (should) have been written to the buffer -- this can
-// be larger than the buffer size and can indicate overflow.  Replaces
-// blanks with zeroes if appropriate.
-static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io,
-    const DataEdit &edit, int &exponent) {
+// (hexa)decimal point, and at least one digit.
+// Replaces blanks with zeroes where appropriate.
+struct ScannedRealInput {
+  // Number of characters that (should) have been written to the
+  // buffer -- this can be larger than the buffer size, which
+  // indicates buffer overflow.  Zero indicates an error.
+  int got{0};
+  int exponent{0}; // adjusted as necessary; binary if isHexadecimal
+  bool isHexadecimal{false}; // 0X...
+};
+static ScannedRealInput ScanRealInput(
+    char *buffer, int bufferSize, IoStatementState &io, const DataEdit &edit) {
   std::optional<int> remaining;
   std::optional<char32_t> next;
   int got{0};
-  std::optional<int> decimalPoint;
+  std::optional<int> decimalPoint; // misnamed for hexadecimal, sorry
   auto Put{[&](char ch) -> void {
     if (got < bufferSize) {
       buffer[got] = ch;
@@ -251,6 +256,7 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io,
     Put('-');
   }
   bool bzMode{(edit.modes.editingFlags & blankZero) != 0};
+  int exponent{0};
   if (!next || (!bzMode && *next == ' ')) {
     if (!edit.IsListDirected() && !io.GetConnectionState().IsAtEOF()) {
       // An empty/blank field means zero when not list-directed.
@@ -259,10 +265,11 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io,
       // required to pass FCVS.
       Put('0');
     }
-    return got;
+    return {got, exponent, false};
   }
   char32_t decimal{GetDecimalPoint(edit)};
   char32_t first{*next >= 'a' && *next <= 'z' ? *next + 'A' - 'a' : *next};
+  bool isHexadecimal{false};
   if (first == 'N' || first == 'I') {
     // NaN or infinity - convert to upper case
     // Subtle: a blank field of digits could be followed by 'E' or 'D',
@@ -283,7 +290,7 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io,
         if (depth == 0) {
           break;
         } else if (!next) {
-          return 0; // error
+          return {}; // error
         } else if (*next == '(') {
           ++depth;
         } else if (*next == ')') {
@@ -292,19 +299,32 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io,
         Put(*next);
       }
     }
-    exponent = 0;
   } else if (first == decimal || (first >= '0' && first <= '9') ||
       (bzMode && (first == ' ' || first == '\t')) || first == 'E' ||
       first == 'D' || first == 'Q') {
-    Put('.'); // input field is normalized to a fraction
+    if (first == '0') {
+      next = io.NextInField(remaining, edit);
+      if (next && (*next == 'x' || *next == 'X')) { // 0X...
+        isHexadecimal = true;
+        next = io.NextInField(remaining, edit);
+      } else {
+        Put('0');
+      }
+    }
+    // input field is normalized to a fraction
+    if (!isHexadecimal) {
+      Put('.');
+    }
     auto start{got};
     for (; next; next = io.NextInField(remaining, edit)) {
       char32_t ch{*next};
       if (ch == ' ' || ch == '\t') {
-        if (bzMode) {
+        if (isHexadecimal) {
+          return {}; // error
+        } else if (bzMode) {
           ch = '0'; // BZ mode - treat blank as if it were zero
         } else {
-          continue;
+          continue; // ignore blank in fixed field
         }
       }
       if (ch == '0' && got == start && !decimalPoint) {
@@ -314,6 +334,10 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io,
       } else if (ch == decimal && !decimalPoint) {
         // the decimal point is *not* copied to the buffer
         decimalPoint = got - start; // # of digits before the decimal point
+      } else if (isHexadecimal && ch >= 'A' && ch <= 'F') {
+        Put(ch);
+      } else if (isHexadecimal && ch >= 'a' && ch <= 'f') {
+        Put(ch - 'a' + 'A'); // normalize to capitals
       } else {
         break;
       }
@@ -328,17 +352,22 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io,
     auto nextBeforeExponent{next};
     auto startExponent{io.GetConnectionState().positionInRecord};
     bool hasGoodExponent{false};
-    if (next &&
-        (*next == 'e' || *next == 'E' || *next == 'd' || *next == 'D' ||
-            *next == 'q' || *next == 'Q')) {
-      // Optional exponent letter.  Blanks are allowed between the
-      // optional exponent letter and the exponent value.
-      io.SkipSpaces(remaining);
-      next = io.NextInField(remaining, edit);
+    if (next) {
+      if (isHexadecimal) {
+        if (*next == 'p' || *next == 'P') {
+          next = io.NextInField(remaining, edit);
+        } else {
+          // The binary exponent is not optional in the standard.
+          return {}; // error
+        }
+      } else if (*next == 'e' || *next == 'E' || *next == 'd' || *next == 'D' ||
+          *next == 'q' || *next == 'Q') {
+        // Optional exponent letter.  Blanks are allowed between the
+        // optional exponent letter and the exponent value.
+        io.SkipSpaces(remaining);
+        next = io.NextInField(remaining, edit);
+      }
     }
-    // The default exponent is -kP, but the scale factor doesn't affect
-    // an explicit exponent.
-    exponent = -edit.modes.scale;
     if (next &&
         (*next == '-' || *next == '+' || (*next >= '0' && *next <= '9') ||
             *next == ' ' || *next == '\t')) {
@@ -346,14 +375,16 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io,
       if (negExpo || *next == '+') {
         next = io.NextInField(remaining, edit);
       }
-      for (exponent = 0; next; next = io.NextInField(remaining, edit)) {
+      for (; next; next = io.NextInField(remaining, edit)) {
         if (*next >= '0' && *next <= '9') {
           hasGoodExponent = true;
           if (exponent < 10000) {
             exponent = 10 * exponent + *next - '0';
           }
         } else if (*next == ' ' || *next == '\t') {
-          if (bzMode) {
+          if (isHexadecimal) {
+            break;
+          } else if (bzMode) {
             hasGoodExponent = true;
             exponent = 10 * exponent;
           }
@@ -366,11 +397,21 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io,
       }
     }
     if (!hasGoodExponent) {
+      if (isHexadecimal) {
+        return {}; // error
+      }
       // There isn't a good exponent; do not consume it.
       next = nextBeforeExponent;
       io.HandleAbsolutePosition(startExponent);
-    }
-    if (decimalPoint) {
+      // The default exponent is -kP, but the scale factor doesn't affect
+      // an explicit exponent.
+      exponent = -edit.modes.scale;
+    }
+    // Adjust exponent by number of digits before the decimal point.
+    if (isHexadecimal) {
+      // Exponents for hexadecimal input are binary.
+      exponent += decimalPoint.value_or(got - start) * 4;
+    } else if (decimalPoint) {
       exponent += *decimalPoint;
     } else {
       // When no decimal point (or comma) appears in the value, the 'd'
@@ -379,10 +420,6 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io,
       // the assumed decimal point (13.7.2.3.2)
       exponent += got - start - edit.digits.value_or(0);
     }
-  } else {
-    // TODO: hex FP input
-    exponent = 0;
-    return 0;
   }
   // Consume the trailing ')' of a list-directed or NAMELIST complex
   // input value.
@@ -403,10 +440,10 @@ static int ScanRealInput(char *buffer, int bufferSize, IoStatementState &io,
       next = io.NextInField(remaining, edit);
     }
     if (next) {
-      return 0; // error: unused nonblank character in fixed-width field
+      return {}; // error: unused nonblank character in fixed-width field
     }
   }
-  return got;
+  return {got, exponent, isHexadecimal};
 }
 
 static void RaiseFPExceptions(decimal::ConversionResultFlags flags) {
@@ -433,7 +470,7 @@ static void RaiseFPExceptions(decimal::ConversionResultFlags flags) {
 // converter without modification, this fast path for real input
 // saves time by avoiding memory copies and reformatting of the exponent.
 template <int PRECISION>
-static bool TryFastPathRealInput(
+static bool TryFastPathRealDecimalInput(
     IoStatementState &io, const DataEdit &edit, void *n) {
   if (edit.modes.editingFlags & (blankZero | decimalComma)) {
     return false;
@@ -504,10 +541,101 @@ static bool TryFastPathRealInput(
   return true;
 }
 
+template <int binaryPrecision>
+decimal::ConversionToBinaryResult<binaryPrecision> ConvertHexadecimal(
+    const char *&p, enum decimal::FortranRounding rounding, int expo) {
+  using RealType = decimal::BinaryFloatingPointNumber<binaryPrecision>;
+  using RawType = typename RealType::RawType;
+  bool isNegative{*p == '-'};
+  constexpr RawType one{1};
+  RawType signBit{0};
+  if (isNegative) {
+    ++p;
+    signBit = one << (RealType::bits - 1);
+  }
+  RawType fraction{0};
+  // Adjust the incoming binary P+/- exponent to shift the radix point
+  // to below the LSB and add in the bias.
+  expo += binaryPrecision + 1 + RealType::exponentBias;
+  // Input the fraction.  Maintain two extra least-significant bits for
+  // rounding.
+  for (; *p; ++p) {
+    fraction <<= 4;
+    expo -= 4;
+    if (*p >= '0' && *p <= '9') {
+      fraction |= *p - '0';
+    } else if (*p >= 'A' && *p <= 'F') {
+      fraction |= *p - 'A' + 10; // data were normalized to capitals
+    } else {
+      break;
+    }
+    while (fraction >> (binaryPrecision + 2)) {
+      fraction = (fraction >> 1) | (fraction & 1);
+      ++expo;
+    }
+  }
+  int roundingBits{0};
+  if (fraction) {
+    // Boost biased expo if too small
+    while (expo < 1) {
+      fraction = (fraction >> 1) | (fraction & one);
+      ++expo;
+    }
+    // Normalize
+    while (expo > 1 && !(fraction >> (binaryPrecision + 2 - 1))) {
+      fraction <<= 1;
+      --expo;
+    }
+    // Rounding
+    roundingBits = static_cast<int>(fraction) & 3;
+    fraction >>= 2;
+    bool increase{false};
+    switch (rounding) {
+    case decimal::RoundNearest: // RN & RP
+      increase = roundingBits == 3 || (roundingBits == 2 && fraction & 1);
+      break;
+    case decimal::RoundUp: // RU
+      increase = !isNegative && roundingBits > 0;
+      break;
+    case decimal::RoundDown: // RD
+      increase = isNegative && roundingBits > 0;
+      break;
+    case decimal::RoundToZero: // RZ
+      break;
+    case decimal::RoundCompatible: // RC
+      increase = roundingBits >= 2;
+      break;
+    }
+    if (increase) {
+      ++fraction;
+      if (fraction >> binaryPrecision) {
+        fraction >>= 1;
+        ++expo;
+      }
+    }
+  }
+  // Package & return result
+  constexpr RawType significandMask{(one << RealType::significandBits) - 1};
+  if (!fraction) {
+    expo = 0;
+  } else if (expo == 1 && !(fraction >> (binaryPrecision - 1))) {
+    expo = 0; // subnormal
+  } else if (expo >= RealType::maxExponent) {
+    expo = RealType::maxExponent; // +/-Inf
+    fraction = 0;
+  } else {
+    fraction &= significandMask; // remove explicit normalization unless x87
+  }
+  return decimal::ConversionToBinaryResult<binaryPrecision>{
+      RealType{static_cast<RawType>(signBit |
+          static_cast<RawType>(expo) << RealType::significandBits | fraction)},
+      roundingBits ? decimal::Inexact : decimal::Exact};
+}
+
 template <int KIND>
 bool EditCommonRealInput(IoStatementState &io, const DataEdit &edit, void *n) {
   constexpr int binaryPrecision{common::PrecisionOfRealKind(KIND)};
-  if (TryFastPathRealInput<binaryPrecision>(io, edit, n)) {
+  if (TryFastPathRealDecimalInput<binaryPrecision>(io, edit, n)) {
     return CheckCompleteListDirectedField(io, edit);
   }
   // Fast path wasn't available or didn't work; go the more general route
@@ -515,8 +643,8 @@ bool EditCommonRealInput(IoStatementState &io, const DataEdit &edit, void *n) {
       common::MaxDecimalConversionDigits(binaryPrecision)};
   static constexpr int bufferSize{maxDigits + 18};
   char buffer[bufferSize];
-  int exponent{0};
-  int got{ScanRealInput(buffer, maxDigits + 2, io, edit, exponent)};
+  auto scanned{ScanRealInput(buffer, maxDigits + 2, io, edit)};
+  int got{scanned.got};
   if (got >= maxDigits + 2) {
     io.GetIoErrorHandler().Crash("EditCommonRealInput: buffer was too small");
     return false;
@@ -529,48 +657,55 @@ bool EditCommonRealInput(IoStatementState &io, const DataEdit &edit, void *n) {
         static_cast<int>(connection.currentRecordNumber));
     return false;
   }
-  bool hadExtra{got > maxDigits};
-  if (exponent != 0) {
-    buffer[got++] = 'e';
-    if (exponent < 0) {
-      buffer[got++] = '-';
-      exponent = -exponent;
-    }
-    if (exponent > 9999) {
-      exponent = 9999; // will convert to +/-Inf
-    }
-    if (exponent > 999) {
-      int dig{exponent / 1000};
-      buffer[got++] = '0' + dig;
-      int rest{exponent - 1000 * dig};
-      dig = rest / 100;
-      buffer[got++] = '0' + dig;
-      rest -= 100 * dig;
-      dig = rest / 10;
-      buffer[got++] = '0' + dig;
-      buffer[got++] = '0' + (rest - 10 * dig);
-    } else if (exponent > 99) {
-      int dig{exponent / 100};
-      buffer[got++] = '0' + dig;
-      int rest{exponent - 100 * dig};
-      dig = rest / 10;
-      buffer[got++] = '0' + dig;
-      buffer[got++] = '0' + (rest - 10 * dig);
-    } else if (exponent > 9) {
-      int dig{exponent / 10};
-      buffer[got++] = '0' + dig;
-      buffer[got++] = '0' + (exponent - 10 * dig);
-    } else {
-      buffer[got++] = '0' + exponent;
-    }
-  }
-  buffer[got] = '\0';
+  decimal::ConversionToBinaryResult<binaryPrecision> converted;
   const char *p{buffer};
-  decimal::ConversionToBinaryResult<binaryPrecision> converted{
-      decimal::ConvertToBinary<binaryPrecision>(p, edit.modes.round)};
-  if (hadExtra) {
-    converted.flags = static_cast<enum decimal::ConversionResultFlags>(
-        converted.flags | decimal::Inexact);
+  if (scanned.isHexadecimal) {
+    buffer[got] = '\0';
+    converted = ConvertHexadecimal<binaryPrecision>(
+        p, edit.modes.round, scanned.exponent);
+  } else {
+    bool hadExtra{got > maxDigits};
+    int exponent{scanned.exponent};
+    if (exponent != 0) {
+      buffer[got++] = 'e';
+      if (exponent < 0) {
+        buffer[got++] = '-';
+        exponent = -exponent;
+      }
+      if (exponent > 9999) {
+        exponent = 9999; // will convert to +/-Inf
+      }
+      if (exponent > 999) {
+        int dig{exponent / 1000};
+        buffer[got++] = '0' + dig;
+        int rest{exponent - 1000 * dig};
+        dig = rest / 100;
+        buffer[got++] = '0' + dig;
+        rest -= 100 * dig;
+        dig = rest / 10;
+        buffer[got++] = '0' + dig;
+        buffer[got++] = '0' + (rest - 10 * dig);
+      } else if (exponent > 99) {
+        int dig{exponent / 100};
+        buffer[got++] = '0' + dig;
+        int rest{exponent - 100 * dig};
+        dig = rest / 10;
+        buffer[got++] = '0' + dig;
+        buffer[got++] = '0' + (r...
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/67208


More information about the flang-commits mailing list