[flang-commits] [flang] [llvm] [flang-rt] Add fatal diagnostics with integer values (PR #231241)

via flang-commits flang-commits at lists.llvm.org
Tue Oct 13 04:11:40 PDT 2026


https://github.com/jeanPerier updated https://github.com/llvm/llvm-project/pull/231241

>From 188058ef47c872f2b3975bbfca6d054eca7de7ae Mon Sep 17 00:00:00 2001
From: Jean Perier <jperier at nvidia.com>
Date: Mon, 12 Oct 2026 09:58:31 -0700
Subject: [PATCH 1/2] [flang-rt] Add fatal diagnostics with integer values

Add noreturn runtime entries for messages with integer diagnostic values.
Use a fixed four-value signature for small messages and a count-and-array
signature for larger payloads, keeping both callable from device code.

The array entry accepts the restricted %lld/%% format, checks argument
counts and allocation sizes, and preserves the existing source location,
crash handler, and termination behavior. Document the runtime interfaces
in the inline-check design and cover formatting and failures in unit tests.
---
 flang-rt/lib/runtime/stop.cpp       | 72 +++++++++++++++++++++++++
 flang-rt/unittests/Runtime/Stop.cpp | 82 +++++++++++++++++++++++++++++
 flang/docs/InlineRuntimeChecks.md   | 26 +++++----
 flang/include/flang/Runtime/stop.h  | 17 ++++++
 4 files changed, 187 insertions(+), 10 deletions(-)

diff --git a/flang-rt/lib/runtime/stop.cpp b/flang-rt/lib/runtime/stop.cpp
index b4508e0b7d0bc..b4179c38bd275 100644
--- a/flang-rt/lib/runtime/stop.cpp
+++ b/flang-rt/lib/runtime/stop.cpp
@@ -11,11 +11,15 @@
 #include "flang-rt/runtime/environment.h"
 #include "flang-rt/runtime/file.h"
 #include "flang-rt/runtime/io-error.h"
+#include "flang-rt/runtime/memory.h"
 #include "flang-rt/runtime/terminator.h"
 #include "flang-rt/runtime/unit.h"
+#include "flang/Runtime/freestanding-tools.h"
 #include <cfenv>
 #include <cstdio>
 #include <cstdlib>
+#include <cstring>
+#include <limits>
 
 #ifdef HAVE_BACKTRACE
 #include BACKTRACE_HEADER
@@ -249,4 +253,72 @@ RT_OPTNONE_ATTR void FORTRAN_PROCEDURE_NAME(backtrace)() { PrintBacktrace(); }
     const char *message, const char *source, int line) {
   Fortran::runtime::Terminator{source, line}.Crash(message);
 }
+
+RT_OFFLOAD_API_GROUP_BEGIN
+[[noreturn]] RT_API_ATTRS void RTNAME(ReportFatalUserErrorValues)(
+    const char *message, const char *source, int line, long long v0,
+    long long v1, long long v2, long long v3) {
+  Fortran::runtime::Terminator{source, line}.Crash(message, v0, v1, v2, v3);
+}
+
+[[noreturn]] RT_API_ATTRS void RTNAME(ReportFatalUserErrorValueArray)(
+    const char *message, const char *source, int line, std::size_t count,
+    const long long *values) {
+  Fortran::runtime::Terminator terminator{source, line};
+  RUNTIME_CHECK(terminator, message && (count == 0 || values));
+
+  // Reserve enough space for every value, its sign, and the unchanged format.
+  // Allocation is confined to this fatal path, and no diagnostic is truncated.
+  constexpr std::size_t maxValueChars{
+      std::numeric_limits<unsigned long long>::digits10 + 2};
+  std::size_t messageLength{Fortran::runtime::strlen(message)};
+  if (messageLength == std::numeric_limits<std::size_t>::max() ||
+      count > (std::numeric_limits<std::size_t>::max() - messageLength - 1) /
+              maxValueChars) {
+    terminator.Crash("Fatal error message size overflow");
+  }
+  char *buffer{static_cast<char *>(Fortran::runtime::AllocateMemoryOrCrash(
+      terminator, messageLength + count * maxValueChars + 1))};
+  char *out{buffer};
+  std::size_t nextValue{0};
+  for (const char *p{message}; *p; ++p) {
+    if (*p != '%') {
+      *out++ = *p;
+    } else if (p[1] == '%') {
+      *out++ = '%';
+      ++p;
+    } else if (p[1] == 'l' && p[2] == 'l' && p[3] == 'd') {
+      if (nextValue == count) {
+        terminator.Crash("Fatal error message value count mismatch");
+      }
+      long long value{values[nextValue++]};
+      unsigned long long magnitude{static_cast<unsigned long long>(value)};
+      if (value < 0) {
+        *out++ = '-';
+        // Negate in unsigned arithmetic, including for the most negative value.
+        magnitude = -magnitude;
+      }
+      char digits[maxValueChars];
+      char *end{digits + sizeof digits}, *begin{end};
+      do {
+        *--begin = '0' + magnitude % 10;
+        magnitude /= 10;
+      } while (magnitude != 0);
+      std::size_t length{static_cast<std::size_t>(end - begin)};
+      Fortran::runtime::memcpy(out, begin, length);
+      out += length;
+      p += 3;
+    } else {
+      terminator.Crash("Invalid fatal error message format");
+    }
+  }
+  if (nextValue != count) {
+    terminator.Crash("Fatal error message value count mismatch");
+  }
+  *out = '\0';
+  // Keep both the standard diagnostic and the host crash-handler hook. The
+  // rendered percent signs are data, not additional format conversions.
+  terminator.Crash("%s", buffer);
+}
+RT_OFFLOAD_API_GROUP_END
 }
diff --git a/flang-rt/unittests/Runtime/Stop.cpp b/flang-rt/unittests/Runtime/Stop.cpp
index 5c30f6c301cd3..c74b44aa4d68f 100644
--- a/flang-rt/unittests/Runtime/Stop.cpp
+++ b/flang-rt/unittests/Runtime/Stop.cpp
@@ -13,9 +13,11 @@
 #include "flang/Runtime/stop.h"
 #include "CrashHandlerFixture.h"
 #include "flang-rt/runtime/environment.h"
+#include "flang-rt/runtime/terminator.h"
 #include <cstdlib>
 #include <cstring>
 #include <gtest/gtest.h>
+#include <limits>
 
 using namespace Fortran::runtime;
 
@@ -128,3 +130,83 @@ TEST_F(TestProgramEnd, CrashTest) {
       RTNAME(ReportFatalUserError)(crashMessage.c_str(), fileName.c_str(), 343),
       fullMessage.c_str());
 }
+
+TEST_F(TestProgramEnd, CrashValuesTest) {
+  EXPECT_DEATH(
+      RTNAME(ReportFatalUserErrorValues)("sizes %lld, %lld, %lld, %lld %%",
+          "check.f90", 42, -17, 4294967296LL, 0, 9),
+      "check.f90\\(42\\): sizes -17, 4294967296, 0, 9 %");
+}
+
+TEST_F(TestProgramEnd, CrashValuesWithoutValuesTest) {
+  EXPECT_DEATH(RTNAME(ReportFatalUserErrorValues)(
+                   "bad argument: 100%%", "check.f90", 7, 0, 0, 0, 0),
+      "check.f90\\(7\\): bad argument: 100%");
+}
+
+TEST_F(TestProgramEnd, CrashValueArrayTest) {
+  const long long values[]{std::numeric_limits<long long>::min(),
+      std::numeric_limits<long long>::max(), 0, -1, 4294967296LL, 17};
+  EXPECT_DEATH(
+      RTNAME(ReportFatalUserErrorValueArray)(
+          "%lld %lld %lld %lld %lld %%%lld %%lld", "check.f90", 43, 6, values),
+      "check.f90\\(43\\): -9223372036854775808 9223372036854775807 0 -1 "
+      "4294967296 %17 %lld");
+}
+
+TEST_F(TestProgramEnd, CrashValueArrayWithoutValuesTest) {
+  EXPECT_DEATH(RTNAME(ReportFatalUserErrorValueArray)(
+                   "bad argument: 100%%", "check.f90", 7, 0, nullptr),
+      "check.f90\\(7\\): bad argument: 100%");
+  EXPECT_DEATH(
+      RTNAME(ReportFatalUserErrorValueArray)("", "check.f90", 7, 0, nullptr),
+      "check.f90\\(7\\): ");
+}
+
+TEST_F(TestProgramEnd, CrashValueArrayInvalidFormatTest) {
+  for (const char *message : {"%", "%l", "%ll", "%d", "%n", "%1$lld"}) {
+    EXPECT_DEATH(RTNAME(ReportFatalUserErrorValueArray)(
+                     message, "check.f90", 7, 0, nullptr),
+        "Invalid fatal error message format");
+  }
+}
+
+TEST_F(TestProgramEnd, CrashValueArrayCountMismatchTest) {
+  const long long values[]{1, 2};
+  EXPECT_DEATH(RTNAME(ReportFatalUserErrorValueArray)(
+                   "%lld %lld", "check.f90", 7, 1, values),
+      "Fatal error message value count mismatch");
+  EXPECT_DEATH(
+      RTNAME(ReportFatalUserErrorValueArray)("%lld", "check.f90", 7, 2, values),
+      "Fatal error message value count mismatch");
+  EXPECT_DEATH(RTNAME(ReportFatalUserErrorValueArray)(
+                   "%lld", "check.f90", 7, 0, nullptr),
+      "Fatal error message value count mismatch");
+  EXPECT_DEATH(RTNAME(ReportFatalUserErrorValueArray)(
+                   "%lld", "check.f90", 7, 1, nullptr),
+      "RUNTIME_CHECK");
+}
+
+TEST_F(TestProgramEnd, CrashValueArraySizeOverflowTest) {
+  const long long value{0};
+  EXPECT_DEATH(RTNAME(ReportFatalUserErrorValueArray)("%lld", "check.f90", 7,
+                   std::numeric_limits<std::size_t>::max(), &value),
+      "Fatal error message size overflow");
+}
+
+TEST_F(TestProgramEnd, CrashValueArrayStandardDiagnosticTest) {
+  // The test crash handler truncates messages to 1000 characters. Exercise the
+  // actual runtime diagnostic, including its header and a much longer message.
+  std::string message(4096, 'x');
+  message += " %lld %lld %lld %lld %lld %%";
+  const long long values[]{-1, 0, 1, 2, 3};
+  EXPECT_DEATH(
+      {
+        Terminator::RegisterCrashHandler(nullptr);
+        RTNAME(ReportFatalUserErrorValueArray)(
+            message.c_str(), "check.f90", 99, 5, values);
+      },
+      ("fatal Fortran runtime error\\(check.f90:99\\): " +
+          std::string(4096, 'x') + " -1 0 1 2 3 %")
+          .c_str());
+}
diff --git a/flang/docs/InlineRuntimeChecks.md b/flang/docs/InlineRuntimeChecks.md
index 7a7e2892036cf..fa24ebc0f16ea 100644
--- a/flang/docs/InlineRuntimeChecks.md
+++ b/flang/docs/InlineRuntimeChecks.md
@@ -255,12 +255,17 @@ llvm.cond_br %ok weights([2000, 1]), ^cont, ^fail
 
 - **Runtime entry.** `_FortranAReportFatalUserError(msg, source, line)`
   (`flang/include/flang/Runtime/stop.h`) is `[[noreturn]]` and device-callable
-  (`RT_API_ATTRS`). It takes no values. Proposed addition:
-  `ReportFatalUserErrorValues(msg, source, line, v0..v3)` with a fixed
-  number of `long long` arguments. That keeps the ABI simple on devices, and
-  messages use `%lld` only (CUDA's device `printf` only documents the `h`,
-  `l`, and `ll` length modifiers). The message is passed to `printf`, so a
-  literal `%` in a message must be escaped.
+  (`RT_API_ATTRS`). It takes no values. Assertions with up to four values use
+  `ReportFatalUserErrorValues(msg, source, line, v0..v3)` with fixed
+  `long long` arguments, padding unused arguments with zeros. Assertions with
+  more values use `ReportFatalUserErrorValueArray(msg, source, line, count,
+  values)`, where `count` is a `size_t` and `values` points to an array of
+  `long long`. Code generation packs that array on the failure path; the
+  assertion's diagnostic operands remain SSA values until then. Both entries
+  have fixed signatures to keep the ABI simple on devices. Messages use only
+  `%lld` and `%%` (for a literal percent sign). The array entry formats these
+  directives before passing the completed message to the existing runtime
+  termination machinery.
 - **Declaration.** The runtime function declaration must carry `noreturn`,
   so LLVM can use the checked condition on the continuation path.
 - **Source location.** Take it from the assert's own location. Resolve
@@ -778,10 +783,11 @@ elimination do not. The code generation pattern must work on unoptimized IR.
 
 ## 9. Open questions
 
-1. Message formatting: a fixed number of `long long` values, or a variadic
-   runtime entry? Inline messages keep the runtime's text as closely as
-   possible, and only differ where the inline check is stricter (per-dimension
-   conformance, section 5.1).
+1. Diagnostic detail: report only the failing dimension, or include complete
+   shapes and subscript tuples? The fixed and count-and-array entries support
+   both. Inline messages keep the runtime's text as closely as possible, and
+   only differ where the inline check is stricter (per-dimension conformance,
+   section 5.1).
 2. When a default-on kind (`conformance`, `argument`) is disabled by an
    option, should its checks be emitted as assumptions (`llvm.intr.assume` at
    code generation) instead of not at all? An assumption gives more
diff --git a/flang/include/flang/Runtime/stop.h b/flang/include/flang/Runtime/stop.h
index 710d75494c875..f310872778285 100644
--- a/flang/include/flang/Runtime/stop.h
+++ b/flang/include/flang/Runtime/stop.h
@@ -44,6 +44,23 @@ void FORTRAN_PROCEDURE_NAME(backtrace)(NO_ARGUMENTS);
 NORETURN RT_API_ATTRS void RTNAME(ReportFatalUserError)(
     const char *message, const char *source, int line);
 
+// The NUL-terminated message contains only %lld conversions and %% escapes.
+// A fixed argument list also permits calls from device code; unused values
+// are ignored.
+RT_OFFLOAD_API_GROUP_BEGIN
+NORETURN RT_API_ATTRS void RTNAME(ReportFatalUserErrorValues)(
+    const char *message, const char *source, int line, long long v0,
+    long long v1, long long v2, long long v3);
+
+// The same NUL-terminated restricted format with an arbitrary number of values.
+// The number of %lld conversions must equal count; values may be null when
+// count is zero. This fallback allocates its formatted message only on the
+// fatal path.
+NORETURN RT_API_ATTRS void RTNAME(ReportFatalUserErrorValueArray)(
+    const char *message, const char *source, int line, size_t count,
+    const long long *values);
+RT_OFFLOAD_API_GROUP_END
+
 FORTRAN_EXTERN_C_END
 
 #endif // FORTRAN_RUNTIME_STOP_H_

>From 9e47009cec87a333616a053b0a9624864a21af94 Mon Sep 17 00:00:00 2001
From: Jean Perier <jperier at nvidia.com>
Date: Tue, 13 Oct 2026 01:53:49 -0700
Subject: [PATCH 2/2] [flang-rt] Preserve fatal diagnostics when allocation
 fails

Validate the array-entry format and value count before printing error message.

Print the validated message in bounded pieces through the existing
termination helpers, avoiding allocation of a formatted message and the
custom decimal conversion. Concurrent failures may interleave their output.

Document the fixed-entry caller contract and mark all three fatal-error
entries for device compilation. Test large mismatched counts,
including long text and integer limits.

Check the real runtime diagnostic in the array-entry tests and remove the
allocation-failure regression and its static-runtime lit feature.
---
 flang-rt/lib/runtime/stop.cpp       | 87 ++++++++++++-----------------
 flang-rt/unittests/Runtime/Stop.cpp | 29 +++++-----
 flang/docs/InlineRuntimeChecks.md   |  7 ++-
 flang/include/flang/Runtime/stop.h  | 15 ++---
 4 files changed, 62 insertions(+), 76 deletions(-)

diff --git a/flang-rt/lib/runtime/stop.cpp b/flang-rt/lib/runtime/stop.cpp
index b4179c38bd275..561ea41642aaf 100644
--- a/flang-rt/lib/runtime/stop.cpp
+++ b/flang-rt/lib/runtime/stop.cpp
@@ -11,15 +11,11 @@
 #include "flang-rt/runtime/environment.h"
 #include "flang-rt/runtime/file.h"
 #include "flang-rt/runtime/io-error.h"
-#include "flang-rt/runtime/memory.h"
 #include "flang-rt/runtime/terminator.h"
 #include "flang-rt/runtime/unit.h"
-#include "flang/Runtime/freestanding-tools.h"
 #include <cfenv>
 #include <cstdio>
 #include <cstdlib>
-#include <cstring>
-#include <limits>
 
 #ifdef HAVE_BACKTRACE
 #include BACKTRACE_HEADER
@@ -249,12 +245,12 @@ static RT_NOINLINE_ATTR void PrintBacktrace() {
 
 RT_OPTNONE_ATTR void FORTRAN_PROCEDURE_NAME(backtrace)() { PrintBacktrace(); }
 
+RT_EXT_API_GROUP_BEGIN
 [[noreturn]] RT_API_ATTRS void RTNAME(ReportFatalUserError)(
     const char *message, const char *source, int line) {
   Fortran::runtime::Terminator{source, line}.Crash(message);
 }
 
-RT_OFFLOAD_API_GROUP_BEGIN
 [[noreturn]] RT_API_ATTRS void RTNAME(ReportFatalUserErrorValues)(
     const char *message, const char *source, int line, long long v0,
     long long v1, long long v2, long long v3) {
@@ -267,58 +263,47 @@ RT_OFFLOAD_API_GROUP_BEGIN
   Fortran::runtime::Terminator terminator{source, line};
   RUNTIME_CHECK(terminator, message && (count == 0 || values));
 
-  // Reserve enough space for every value, its sign, and the unchanged format.
-  // Allocation is confined to this fatal path, and no diagnostic is truncated.
-  constexpr std::size_t maxValueChars{
-      std::numeric_limits<unsigned long long>::digits10 + 2};
-  std::size_t messageLength{Fortran::runtime::strlen(message)};
-  if (messageLength == std::numeric_limits<std::size_t>::max() ||
-      count > (std::numeric_limits<std::size_t>::max() - messageLength - 1) /
-              maxValueChars) {
-    terminator.Crash("Fatal error message size overflow");
+  // Validate the format and count before printing the diagnostic or reading
+  // values.
+  std::size_t conversions{0};
+  for (const char *p{message}; *p; ++p) {
+    if (*p == '%') {
+      if (p[1] == '%') {
+        ++p;
+      } else if (p[1] == 'l' && p[2] == 'l' && p[3] == 'd') {
+        ++conversions;
+        p += 3;
+      } else {
+        terminator.Crash("Invalid fatal error message format");
+      }
+    }
   }
-  char *buffer{static_cast<char *>(Fortran::runtime::AllocateMemoryOrCrash(
-      terminator, messageLength + count * maxValueChars + 1))};
-  char *out{buffer};
+  if (conversions != count) {
+    terminator.Crash("Fatal error message value count mismatch");
+  }
+
+  // Print in pieces so reporting the error does not require allocating a
+  // formatted message. Concurrent failures may interleave their output.
+  terminator.CrashHeader();
   std::size_t nextValue{0};
-  for (const char *p{message}; *p; ++p) {
+  for (const char *p{message}; *p;) {
     if (*p != '%') {
-      *out++ = *p;
-    } else if (p[1] == '%') {
-      *out++ = '%';
-      ++p;
-    } else if (p[1] == 'l' && p[2] == 'l' && p[3] == 'd') {
-      if (nextValue == count) {
-        terminator.Crash("Fatal error message value count mismatch");
-      }
-      long long value{values[nextValue++]};
-      unsigned long long magnitude{static_cast<unsigned long long>(value)};
-      if (value < 0) {
-        *out++ = '-';
-        // Negate in unsigned arithmetic, including for the most negative value.
-        magnitude = -magnitude;
+      char chunk[256];
+      std::size_t length{0};
+      while (*p && *p != '%' && length < sizeof chunk - 1) {
+        chunk[length++] = *p++;
       }
-      char digits[maxValueChars];
-      char *end{digits + sizeof digits}, *begin{end};
-      do {
-        *--begin = '0' + magnitude % 10;
-        magnitude /= 10;
-      } while (magnitude != 0);
-      std::size_t length{static_cast<std::size_t>(end - begin)};
-      Fortran::runtime::memcpy(out, begin, length);
-      out += length;
-      p += 3;
+      chunk[length] = '\0';
+      terminator.PrintCrashArgs("%s", chunk);
+    } else if (p[1] == '%') {
+      terminator.PrintCrashArgs("%%");
+      p += 2;
     } else {
-      terminator.Crash("Invalid fatal error message format");
+      terminator.PrintCrashArgs("%lld", values[nextValue++]);
+      p += 4;
     }
   }
-  if (nextValue != count) {
-    terminator.Crash("Fatal error message value count mismatch");
-  }
-  *out = '\0';
-  // Keep both the standard diagnostic and the host crash-handler hook. The
-  // rendered percent signs are data, not additional format conversions.
-  terminator.Crash("%s", buffer);
+  terminator.CrashFooter();
 }
-RT_OFFLOAD_API_GROUP_END
+RT_EXT_API_GROUP_END
 }
diff --git a/flang-rt/unittests/Runtime/Stop.cpp b/flang-rt/unittests/Runtime/Stop.cpp
index c74b44aa4d68f..0facf6c8345a6 100644
--- a/flang-rt/unittests/Runtime/Stop.cpp
+++ b/flang-rt/unittests/Runtime/Stop.cpp
@@ -150,17 +150,18 @@ TEST_F(TestProgramEnd, CrashValueArrayTest) {
   EXPECT_DEATH(
       RTNAME(ReportFatalUserErrorValueArray)(
           "%lld %lld %lld %lld %lld %%%lld %%lld", "check.f90", 43, 6, values),
-      "check.f90\\(43\\): -9223372036854775808 9223372036854775807 0 -1 "
+      "fatal Fortran runtime error\\(check.f90:43\\): "
+      "-9223372036854775808 9223372036854775807 0 -1 "
       "4294967296 %17 %lld");
 }
 
 TEST_F(TestProgramEnd, CrashValueArrayWithoutValuesTest) {
   EXPECT_DEATH(RTNAME(ReportFatalUserErrorValueArray)(
                    "bad argument: 100%%", "check.f90", 7, 0, nullptr),
-      "check.f90\\(7\\): bad argument: 100%");
+      "fatal Fortran runtime error\\(check.f90:7\\): bad argument: 100%");
   EXPECT_DEATH(
       RTNAME(ReportFatalUserErrorValueArray)("", "check.f90", 7, 0, nullptr),
-      "check.f90\\(7\\): ");
+      "fatal Fortran runtime error\\(check.f90:7\\): ");
 }
 
 TEST_F(TestProgramEnd, CrashValueArrayInvalidFormatTest) {
@@ -187,25 +188,23 @@ TEST_F(TestProgramEnd, CrashValueArrayCountMismatchTest) {
       "RUNTIME_CHECK");
 }
 
-TEST_F(TestProgramEnd, CrashValueArraySizeOverflowTest) {
+TEST_F(TestProgramEnd, CrashValueArrayLargeCountMismatchTest) {
   const long long value{0};
-  EXPECT_DEATH(RTNAME(ReportFatalUserErrorValueArray)("%lld", "check.f90", 7,
-                   std::numeric_limits<std::size_t>::max(), &value),
-      "Fatal error message size overflow");
+  for (std::size_t count : {std::numeric_limits<std::size_t>::max(),
+           std::numeric_limits<std::size_t>::max() / 64}) {
+    EXPECT_DEATH(RTNAME(ReportFatalUserErrorValueArray)(
+                     "%lld", "check.f90", 7, count, &value),
+        "Fatal error message value count mismatch");
+  }
 }
 
 TEST_F(TestProgramEnd, CrashValueArrayStandardDiagnosticTest) {
-  // The test crash handler truncates messages to 1000 characters. Exercise the
-  // actual runtime diagnostic, including its header and a much longer message.
+  // A long literal must span several chunks without truncation.
   std::string message(4096, 'x');
   message += " %lld %lld %lld %lld %lld %%";
   const long long values[]{-1, 0, 1, 2, 3};
-  EXPECT_DEATH(
-      {
-        Terminator::RegisterCrashHandler(nullptr);
-        RTNAME(ReportFatalUserErrorValueArray)(
-            message.c_str(), "check.f90", 99, 5, values);
-      },
+  EXPECT_DEATH(RTNAME(ReportFatalUserErrorValueArray)(
+                   message.c_str(), "check.f90", 99, 5, values),
       ("fatal Fortran runtime error\\(check.f90:99\\): " +
           std::string(4096, 'x') + " -1 0 1 2 3 %")
           .c_str());
diff --git a/flang/docs/InlineRuntimeChecks.md b/flang/docs/InlineRuntimeChecks.md
index fa24ebc0f16ea..1d1d77bf924f4 100644
--- a/flang/docs/InlineRuntimeChecks.md
+++ b/flang/docs/InlineRuntimeChecks.md
@@ -263,9 +263,10 @@ llvm.cond_br %ok weights([2000, 1]), ^cont, ^fail
   `long long`. Code generation packs that array on the failure path; the
   assertion's diagnostic operands remain SSA values until then. Both entries
   have fixed signatures to keep the ABI simple on devices. Messages use only
-  `%lld` and `%%` (for a literal percent sign). The array entry formats these
-  directives before passing the completed message to the existing runtime
-  termination machinery.
+  `%lld` and `%%` (for a literal percent sign). The array entry prints the
+  diagnostic in pieces through the existing runtime termination machinery,
+  without allocating a formatted message. Concurrent failures may interleave
+  their output.
 - **Declaration.** The runtime function declaration must carry `noreturn`,
   so LLVM can use the checked condition on the continuation path.
 - **Source location.** Take it from the assert's own location. Resolve
diff --git a/flang/include/flang/Runtime/stop.h b/flang/include/flang/Runtime/stop.h
index f310872778285..495dad06c4266 100644
--- a/flang/include/flang/Runtime/stop.h
+++ b/flang/include/flang/Runtime/stop.h
@@ -41,25 +41,26 @@ void FORTRAN_PROCEDURE_NAME(backtrace)(NO_ARGUMENTS);
 
 // Crash with an error message when the program dynamically violates a Fortran
 // constraint.
+RT_EXT_API_GROUP_BEGIN
 NORETURN RT_API_ATTRS void RTNAME(ReportFatalUserError)(
     const char *message, const char *source, int line);
 
-// The NUL-terminated message contains only %lld conversions and %% escapes.
-// A fixed argument list also permits calls from device code; unused values
-// are ignored.
-RT_OFFLOAD_API_GROUP_BEGIN
+// The caller must provide a NUL-terminated message containing at most four
+// %lld conversions, with any other percent signs escaped as %%. This contract
+// is not checked. A fixed argument list also permits calls from device code;
+// unused values are ignored.
 NORETURN RT_API_ATTRS void RTNAME(ReportFatalUserErrorValues)(
     const char *message, const char *source, int line, long long v0,
     long long v1, long long v2, long long v3);
 
 // The same NUL-terminated restricted format with an arbitrary number of values.
 // The number of %lld conversions must equal count; values may be null when
-// count is zero. This fallback allocates its formatted message only on the
-// fatal path.
+// count is zero. The diagnostic is printed in pieces without allocating a
+// formatted message, so concurrent failures may interleave their output.
 NORETURN RT_API_ATTRS void RTNAME(ReportFatalUserErrorValueArray)(
     const char *message, const char *source, int line, size_t count,
     const long long *values);
-RT_OFFLOAD_API_GROUP_END
+RT_EXT_API_GROUP_END
 
 FORTRAN_EXTERN_C_END
 



More information about the flang-commits mailing list