[flang-commits] [flang] [llvm] [mlir] [flang][OpenMP] Implement the error directive (PR #206175)
Caroline Newcombe via flang-commits
flang-commits at lists.llvm.org
Tue Jun 30 07:34:24 PDT 2026
https://github.com/cenewcombe updated https://github.com/llvm/llvm-project/pull/206175
>From daa792d683d7298ab0f95e2045a9fe754adc34c5 Mon Sep 17 00:00:00 2001
From: Caroline Newcombe <caroline.newcombe at hpe.com>
Date: Fri, 26 Jun 2026 13:57:47 -0500
Subject: [PATCH 1/2] [flang][OpenMP] Implement the error directive
---
flang/lib/Lower/OpenMP/OpenMP.cpp | 107 +++++++++++++++++-
flang/lib/Semantics/check-omp-structure.cpp | 50 ++++++++
flang/lib/Semantics/check-omp-structure.h | 3 +
flang/test/Lower/OpenMP/Todo/error.f90 | 7 --
flang/test/Lower/OpenMP/error.f90 | 47 ++++++++
flang/test/Parser/OpenMP/error-unparse.f90 | 13 +--
flang/test/Semantics/OpenMP/error.f90 | 43 +++++++
.../llvm/Frontend/OpenMP/OMPIRBuilder.h | 12 ++
llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp | 19 ++++
.../mlir/Dialect/OpenMP/OpenMPEnums.td | 20 ++++
mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td | 31 +++++
mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp | 12 ++
.../OpenMP/OpenMPToLLVMIRTranslation.cpp | 15 +++
mlir/test/Dialect/OpenMP/invalid.mlir | 7 ++
mlir/test/Dialect/OpenMP/ops.mlir | 25 ++++
mlir/test/Target/LLVMIR/openmp-llvm.mlir | 24 ++++
16 files changed, 417 insertions(+), 18 deletions(-)
delete mode 100644 flang/test/Lower/OpenMP/Todo/error.f90
create mode 100644 flang/test/Lower/OpenMP/error.f90
diff --git a/flang/lib/Lower/OpenMP/OpenMP.cpp b/flang/lib/Lower/OpenMP/OpenMP.cpp
index cd06bd5d33ca3..4fa9a3e163acb 100644
--- a/flang/lib/Lower/OpenMP/OpenMP.cpp
+++ b/flang/lib/Lower/OpenMP/OpenMP.cpp
@@ -19,6 +19,7 @@
#include "Utils.h"
#include "flang/Common/idioms.h"
#include "flang/Evaluate/expression.h"
+#include "flang/Evaluate/fold.h"
#include "flang/Evaluate/tools.h"
#include "flang/Evaluate/type.h"
#include "flang/Lower/Bridge.h"
@@ -34,6 +35,7 @@
#include "flang/Lower/Support/ReductionProcessor.h"
#include "flang/Lower/SymbolMap.h"
#include "flang/Optimizer/Builder/BoxValue.h"
+#include "flang/Optimizer/Builder/Character.h"
#include "flang/Optimizer/Builder/FIRBuilder.h"
#include "flang/Optimizer/Builder/Todo.h"
#include "flang/Optimizer/Dialect/FIRType.h"
@@ -5646,6 +5648,105 @@ static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
queue.begin(), critName);
}
+// Copy the character value `str` into fresh stack memory with an appended NUL
+// and return a pointer to its first character. Fortran characters are not
+// NUL-terminated, so the terminator is needed for C runtime entry points (such
+// as `__kmpc_error`) that expect a NUL-terminated string.
+static mlir::Value genNullTerminatedString(fir::FirOpBuilder &builder,
+ mlir::Location loc,
+ const fir::ExtendedValue &str) {
+ fir::factory::CharacterExprHelper helper(builder, loc);
+ const mlir::Value addr = fir::getBase(str);
+ const mlir::Value len = fir::getLen(str);
+ const auto charTy =
+ mlir::cast<fir::CharacterType>(fir::unwrapRefType(addr.getType()));
+ mlir::MLIRContext *ctx = builder.getContext();
+ const mlir::Type idxTy = builder.getIndexType();
+ const mlir::Value idxLen = builder.createConvert(loc, idxTy, len);
+ const mlir::Value one = builder.createIntegerConstant(loc, idxTy, 1);
+ const mlir::Value lenPlusOne =
+ mlir::arith::AddIOp::create(builder, loc, idxLen, one);
+
+ // Allocate `len + 1` characters and copy the message into them, leaving room
+ // for the terminating NUL.
+ const fir::CharBoxValue temp = helper.createCharacterTemp(charTy, lenPlusOne);
+ helper.createCopy(temp, fir::CharBoxValue{addr, len}, len);
+
+ // Address the buffer as an array of single characters so the terminating NUL
+ // can be stored at index `len`.
+ const auto singleTy = fir::CharacterType::get(
+ ctx, charTy.getFKind(), fir::CharacterType::singleton());
+ const mlir::Type singleRefTy = builder.getRefType(singleTy);
+ const mlir::Type seqRefTy = builder.getRefType(fir::SequenceType::get(
+ {fir::SequenceType::getUnknownExtent()}, singleTy));
+ const mlir::Value seq =
+ builder.createConvert(loc, seqRefTy, temp.getBuffer());
+ const mlir::Value nulAddr =
+ fir::CoordinateOp::create(builder, loc, singleRefTy, seq, idxLen);
+ const mlir::Value zero =
+ builder.createIntegerConstant(loc, builder.getI8Type(), 0);
+ const mlir::Value nul =
+ helper.createSingletonFromCode(zero, charTy.getFKind());
+ fir::StoreOp::create(builder, loc, nul, nulAddr);
+
+ // Pass a pointer to the first character of the buffer.
+ return builder.createConvert(loc, singleRefTy, temp.getBuffer());
+}
+
+// Lower an `!$omp error` directive. The `at(compilation)` form is handled
+// entirely in semantics, so only the `at(execution)` form reaches lowering,
+// where it becomes an `omp.error` operation.
+static void genErrorDirective(lower::AbstractConverter &converter,
+ semantics::SemanticsContext &semaCtx,
+ const parser::OmpErrorDirective &errDir) {
+ auto atKind = parser::OmpAtClause::ActionTime::Compilation;
+ auto sevKind = parser::OmpSeverityClause::SevLevel::Fatal;
+ std::optional<std::string> message;
+ MaybeExpr messageExpr;
+ for (const parser::OmpClause &clause : errDir.v.Clauses().v) {
+ if (const auto *at = std::get_if<parser::OmpClause::At>(&clause.u)) {
+ atKind = at->v.v;
+ } else if (const auto *sev =
+ std::get_if<parser::OmpClause::Severity>(&clause.u)) {
+ sevKind = sev->v.v;
+ } else if (const auto *msg =
+ std::get_if<parser::OmpClause::Message>(&clause.u)) {
+ if (auto expr = semantics::omp::GetEvaluateExpr(msg->v.v)) {
+ if (auto val = evaluate::GetScalarConstantValue<evaluate::Ascii>(*expr))
+ message = *val;
+ else
+ messageExpr = expr;
+ }
+ }
+ }
+
+ if (atKind != parser::OmpAtClause::ActionTime::Execution ||
+ semaCtx.langOptions().OpenMPSimd)
+ return;
+
+ fir::FirOpBuilder &builder = converter.getFirOpBuilder();
+ const mlir::Location loc = converter.getCurrentLocation();
+ const mlir::omp::ClauseSeverity sev =
+ sevKind == parser::OmpSeverityClause::SevLevel::Warning
+ ? mlir::omp::ClauseSeverity::warning
+ : mlir::omp::ClauseSeverity::fatal;
+
+ // A compile-time-constant message is stored directly on the operation as an
+ // attribute. A non-constant message is lowered to a null-terminated string in
+ // memory and passed as the `message_expr` operand.
+ mlir::StringAttr msgAttr;
+ mlir::Value msgExprVal;
+ if (message) {
+ msgAttr = builder.getStringAttr(*message);
+ } else if (messageExpr) {
+ lower::StatementContext stmtCtx;
+ fir::ExtendedValue str = converter.genExprAddr(loc, *messageExpr, stmtCtx);
+ msgExprVal = genNullTerminatedString(builder, loc, str);
+ }
+
+ mlir::omp::ErrorOp::create(builder, loc, sev, msgAttr, msgExprVal);
+}
+
static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
semantics::SemanticsContext &semaCtx,
lower::pft::Evaluation &eval,
@@ -5654,10 +5755,8 @@ static void genOMP(lower::AbstractConverter &converter, lower::SymMap &symTable,
[&](const parser::OmpNothingDirective &) {
// nothing-directive is a no-op (OpenMP 5.2 [8.4])
},
- [&](const parser::OmpErrorDirective &) {
- if (!semaCtx.langOptions().OpenMPSimd)
- TODO(converter.getCurrentLocation(),
- "OmpErrorDirective");
+ [&](const parser::OmpErrorDirective &errDir) {
+ genErrorDirective(converter, semaCtx, errDir);
},
},
dir.u);
diff --git a/flang/lib/Semantics/check-omp-structure.cpp b/flang/lib/Semantics/check-omp-structure.cpp
index faced12ae92ac..a6747f4f51d7e 100644
--- a/flang/lib/Semantics/check-omp-structure.cpp
+++ b/flang/lib/Semantics/check-omp-structure.cpp
@@ -2626,6 +2626,38 @@ void OmpStructureChecker::Enter(const parser::OpenMPDispatchConstruct &x) {
}
}
+void OmpStructureChecker::Enter(const parser::OmpErrorDirective &x) {
+ // Defaults per spec: AT(compilation), SEVERITY(fatal)
+ auto atKind{parser::OmpAtClause::ActionTime::Compilation};
+ auto sevKind{parser::OmpSeverityClause::SevLevel::Fatal};
+ std::optional<std::string> message;
+
+ for (const parser::OmpClause &clause : x.v.Clauses().v) {
+ if (const auto *at{std::get_if<parser::OmpClause::At>(&clause.u)}) {
+ atKind = at->v.v;
+ } else if (const auto *sev{
+ std::get_if<parser::OmpClause::Severity>(&clause.u)}) {
+ sevKind = sev->v.v;
+ } else if (const auto *msg{
+ std::get_if<parser::OmpClause::Message>(&clause.u)}) {
+ if (auto expr{GetEvaluateExpr(msg->v.v)}) {
+ if (auto val{
+ evaluate::GetScalarConstantValue<evaluate::Ascii>(*expr)}) {
+ message = *val;
+ }
+ }
+ }
+ }
+
+ if (atKind == parser::OmpAtClause::ActionTime::Compilation) {
+ if (sevKind == parser::OmpSeverityClause::SevLevel::Warning) {
+ context_.Say(x.v.source, "%s"_warn_en_US, message.value_or("WARNING"));
+ } else {
+ context_.Say(x.v.source, "%s"_err_en_US, message.value_or("ERROR"));
+ }
+ }
+}
+
void OmpStructureChecker::Enter(const parser::OmpClause::At &x) {
if (GetDirectiveNest(DeclarativeNest) > 0) {
if (x.v.v == parser::OmpAtClause::ActionTime::Execution) {
@@ -2635,6 +2667,24 @@ void OmpStructureChecker::Enter(const parser::OmpClause::At &x) {
}
}
+void OmpStructureChecker::Enter(const parser::OmpClause::Message &x) {
+ if (const auto expr{GetEvaluateExpr(x.v.v)}) {
+ const std::optional<evaluate::DynamicType> type{expr->GetType()};
+ if (!type || type->category() != evaluate::TypeCategory::Character) {
+ context_.Say(GetContext().clauseSource,
+ "The MESSAGE clause expression must be of type CHARACTER"_err_en_US);
+ } else if (expr->Rank() != 0) {
+ context_.Say(GetContext().clauseSource,
+ "The MESSAGE clause expression must be scalar"_err_en_US);
+ } else if (type->kind() !=
+ context_.defaultKinds().GetDefaultKind(
+ evaluate::TypeCategory::Character)) {
+ context_.Say(GetContext().clauseSource,
+ "The MESSAGE clause expression must be of default character kind"_err_en_US);
+ }
+ }
+}
+
void OmpStructureChecker::Enter(const parser::OpenMPAllocatorsConstruct &x) {
const parser::OmpDirectiveSpecification &beginSpec{x.BeginDir()};
const parser::OmpDirectiveName &dirName{beginSpec.DirName()};
diff --git a/flang/lib/Semantics/check-omp-structure.h b/flang/lib/Semantics/check-omp-structure.h
index 011b29d370ba3..b961ab51c6639 100644
--- a/flang/lib/Semantics/check-omp-structure.h
+++ b/flang/lib/Semantics/check-omp-structure.h
@@ -138,6 +138,8 @@ class OmpStructureChecker : public OmpStructureCheckerBase {
void Enter(const parser::OmpDirectiveSpecification &);
void Leave(const parser::OmpDirectiveSpecification &);
+ void Enter(const parser::OmpErrorDirective &);
+
void Enter(const parser::OmpMetadirectiveDirective &);
void Leave(const parser::OmpMetadirectiveDirective &);
@@ -187,6 +189,7 @@ class OmpStructureChecker : public OmpStructureCheckerBase {
void Enter(const parser::OmpClause::Linear &x);
void Enter(const parser::OmpClause::Looprange &x);
void Enter(const parser::OmpClause::Map &x);
+ void Enter(const parser::OmpClause::Message &x);
void Enter(const parser::OmpClause::NumTeams &x);
void Enter(const parser::OmpClause::NumThreads &x);
void Enter(const parser::OmpClause::OmpxBare &x);
diff --git a/flang/test/Lower/OpenMP/Todo/error.f90 b/flang/test/Lower/OpenMP/Todo/error.f90
deleted file mode 100644
index c8338278a4267..0000000000000
--- a/flang/test/Lower/OpenMP/Todo/error.f90
+++ /dev/null
@@ -1,7 +0,0 @@
-! RUN: %not_todo_cmd %flang_fc1 -emit-fir -fopenmp -fopenmp-version=51 -o - %s 2>&1 | FileCheck %s
-
-! CHECK: not yet implemented: OmpErrorDirective
-program p
- integer, allocatable :: x
- !$omp error at(compilation) severity(warning) message("an error")
-end program p
diff --git a/flang/test/Lower/OpenMP/error.f90 b/flang/test/Lower/OpenMP/error.f90
new file mode 100644
index 0000000000000..fbca3ec4f45e2
--- /dev/null
+++ b/flang/test/Lower/OpenMP/error.f90
@@ -0,0 +1,47 @@
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fopenmp-version=51 -o - %s 2>&1 | FileCheck %s
+
+! Lowering of `!$omp error at(execution)` to the `omp.error` operation. The
+! `at(compilation)` form is handled entirely in semantics and produces no IR.
+! Severity is `warning` or `fatal` (default `fatal`); the message is an optional
+! constant string that is omitted when absent. A non-constant message is lowered
+! to a null-terminated runtime string and passed as an operand.
+
+! CHECK-LABEL: func.func @_QQmain()
+program p
+ ! CHECK: omp.error severity(warning) message("a warning")
+ !$omp error at(execution) severity(warning) message("a warning")
+
+ ! CHECK: omp.error severity(fatal) message("fatal error")
+ !$omp error at(execution) severity(fatal) message("fatal error")
+
+ ! No MESSAGE clause; severity defaults to `fatal`.
+ ! CHECK: omp.error severity(fatal)
+ !$omp error at(execution)
+
+ ! An explicit empty MESSAGE string is lowered to an empty constant `message`
+ ! attribute (distinct from an absent clause) at the HLFIR level.
+ ! CHECK: omp.error severity(warning) message("")
+ !$omp error at(execution) severity(warning) message("")
+end program p
+
+! A non-constant MESSAGE is only known at run time, so it is lowered to a
+! null-terminated copy in memory and passed as the `message_expr` operand
+! instead of as a constant attribute.
+! CHECK-LABEL: func.func @_QPf_runtime_message
+subroutine f_runtime_message
+ character(len=16) :: msg
+ msg = "runtime warning"
+ ! CHECK: omp.error severity(warning) message_expr(%{{.*}} : !fir.ref<!fir.char<1>>)
+ !$omp error at(execution) severity(warning) message(msg)
+end subroutine f_runtime_message
+
+! The `at(compilation)` form (and the implicit default, which is also
+! compilation) is resolved entirely in semantics and must not lower to an
+! `omp.error` operation. `severity(warning)` keeps these as non-fatal
+! diagnostics so compilation still succeeds.
+! CHECK-LABEL: func.func @_QPf_compilation
+subroutine f_compilation
+ ! CHECK-NOT: omp.error
+ !$omp error severity(warning) message("implicit compilation")
+ !$omp error at(compilation) severity(warning) message("explicit compilation")
+end subroutine f_compilation
diff --git a/flang/test/Parser/OpenMP/error-unparse.f90 b/flang/test/Parser/OpenMP/error-unparse.f90
index 0902d109fd233..255da1111cbf3 100644
--- a/flang/test/Parser/OpenMP/error-unparse.f90
+++ b/flang/test/Parser/OpenMP/error-unparse.f90
@@ -1,5 +1,11 @@
! RUN: %flang_fc1 -fopenmp-version=51 -fopenmp -fdebug-unparse %s 2>&1 | FileCheck %s
! RUN: %flang_fc1 -fopenmp-version=51 -fopenmp -fdebug-dump-parse-tree %s 2>&1 | FileCheck %s --check-prefix="PARSE-TREE"
+
+! The AT(COMPILATION) form emits its diagnostic during semantics, so SEVERITY(FATAL)
+! would halt compilation before any unparse/parse-tree output is produced. Only
+! SEVERITY(WARNING) is used with AT(COMPILATION) here; SEVERITY(FATAL) is exercised
+! with AT(EXECUTION), which is not diagnosed until lowering. The compilation-time
+! diagnostics themselves are covered by Semantics/OpenMP/error.f90.
program main
character(*), parameter :: message = "This is an error"
!CHECK: !$OMP ERROR AT(COMPILATION) SEVERITY(WARNING) MESSAGE("some message here")
@@ -10,13 +16,6 @@ program main
!PARSE-TREE: LiteralConstant -> CharLiteralConstant
!PARSE-TREE: string = 'some message here'
!$omp error at(compilation) severity(warning) message("some message here")
- !CHECK: !$OMP ERROR AT(COMPILATION) SEVERITY(FATAL) MESSAGE("This is an error")
- !PARSE-TREE: ExecutionPartConstruct -> ExecutableConstruct -> OpenMPConstruct -> OmpUtilityDirective -> OmpErrorDirective
- !PARSE-TREE: OmpClauseList -> OmpClause -> At -> OmpAtClause -> ActionTime = Compilation
- !PARSE-TREE: OmpClause -> Severity -> OmpSeverityClause -> SevLevel = Fatal
- !PARSE-TREE: OmpClause -> Message -> OmpMessageClause -> Expr = '"This is an error"'
- !PARSE-TREE: Designator -> DataRef -> Name = 'message'
- !$omp error at(compilation) severity(fatal) message(message)
!CHECK: !$OMP ERROR AT(EXECUTION) SEVERITY(FATAL) MESSAGE("This is an error")
!PARSE-TREE: ExecutionPartConstruct -> ExecutableConstruct -> OpenMPConstruct -> OmpUtilityDirective -> OmpErrorDirective
!PARSE-TREE: OmpClauseList -> OmpClause -> At -> OmpAtClause -> ActionTime = Execution
diff --git a/flang/test/Semantics/OpenMP/error.f90 b/flang/test/Semantics/OpenMP/error.f90
index 067417a8cda3b..97eaa48d9899e 100644
--- a/flang/test/Semantics/OpenMP/error.f90
+++ b/flang/test/Semantics/OpenMP/error.f90
@@ -6,3 +6,46 @@ subroutine f00(x)
integer :: x
end
+subroutine f01
+!ERROR: an error
+ !$omp error at(compilation) severity(fatal) message("an error")
+end
+
+subroutine f02
+!WARNING: a warning
+ !$omp error at(compilation) severity(warning) message("a warning")
+end
+
+subroutine f03
+!ERROR: ERROR
+ !$omp error
+end
+
+subroutine f04
+!ERROR: ERROR
+ !$omp error at(compilation) severity(fatal)
+end
+
+subroutine f05
+!WARNING: WARNING
+ !$omp error at(compilation) severity(warning)
+end
+
+subroutine f06(n)
+ integer :: n
+!ERROR: The MESSAGE clause expression must be of type CHARACTER
+ !$omp error at(execution) severity(warning) message(n)
+end
+
+subroutine f07(c)
+ character(len=8) :: c(4)
+!ERROR: The MESSAGE clause expression must be scalar
+ !$omp error at(execution) severity(warning) message(c)
+end
+
+subroutine f08
+ character(kind=4, len=8) :: c
+!ERROR: The MESSAGE clause expression must be of default character kind
+ !$omp error at(execution) severity(warning) message(c)
+end
+
diff --git a/llvm/include/llvm/Frontend/OpenMP/OMPIRBuilder.h b/llvm/include/llvm/Frontend/OpenMP/OMPIRBuilder.h
index 965ac358c259e..a698124b93dc2 100644
--- a/llvm/include/llvm/Frontend/OpenMP/OMPIRBuilder.h
+++ b/llvm/include/llvm/Frontend/OpenMP/OMPIRBuilder.h
@@ -1497,6 +1497,18 @@ class OpenMPIRBuilder {
/// \param Loc The location where the flush directive was encountered
LLVM_ABI void createFlush(const LocationDescription &Loc);
+ /// Generate a call to the runtime to emit the diagnostic of an OpenMP
+ /// `error` directive with `at(execution)`.
+ ///
+ /// \param Loc The location where the error directive was encountered; it is
+ /// used to build the `ident_t` passed to the runtime.
+ /// \param IsFatal Selects `severity(fatal)` (true) or `severity(warning)`
+ /// (false).
+ /// \param Message The message string (an `i8*`) to display, or null when no
+ /// `message` clause is present.
+ LLVM_ABI void createError(const LocationDescription &Loc, bool IsFatal,
+ Value *Message);
+
/// Generator for '#omp taskwait'
///
/// \param Loc The location where the taskwait directive was encountered.
diff --git a/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp b/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
index f8c1999fe1b89..3b1f29b2e461e 100644
--- a/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
+++ b/llvm/lib/Frontend/OpenMP/OMPIRBuilder.cpp
@@ -2134,6 +2134,25 @@ void OpenMPIRBuilder::createFlush(const LocationDescription &Loc) {
emitFlush(Loc);
}
+void OpenMPIRBuilder::createError(const LocationDescription &Loc, bool IsFatal,
+ Value *Message) {
+ if (!updateToLocation(Loc))
+ return;
+
+ // Build call void __kmpc_error(ident_t *loc, int severity,
+ // const char *message)
+ uint32_t SrcLocStrSize;
+ Constant *SrcLocStr = getOrCreateSrcLocStr(Loc, SrcLocStrSize);
+ Value *Ident = getOrCreateIdent(SrcLocStr, SrcLocStrSize);
+ // Severity: 1 = warning, 2 = fatal.
+ Value *Severity = ConstantInt::get(Int32, IsFatal ? 2 : 1);
+ Value *MessageArg = Message ? Message : ConstantPointerNull::get(Int8Ptr);
+ Value *Args[] = {Ident, Severity, MessageArg};
+
+ createRuntimeFunctionCall(getOrCreateRuntimeFunctionPtr(OMPRTL___kmpc_error),
+ Args);
+}
+
void OpenMPIRBuilder::emitTaskwaitImpl(const LocationDescription &Loc) {
// Build call kmp_int32 __kmpc_omp_taskwait(ident_t *loc, kmp_int32
// global_tid);
diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPEnums.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPEnums.td
index deda86b26db61..85209637b89ae 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPEnums.td
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPEnums.td
@@ -77,6 +77,26 @@ def ClauseDependAttr : OpenMP_EnumAttr<ClauseDepend, "clause_depend"> {
let assemblyFormat = "`(` $value `)`";
}
+//===----------------------------------------------------------------------===//
+// severity enum.
+//===----------------------------------------------------------------------===//
+
+// The values match the `severity` argument expected by the `__kmpc_error`
+// runtime entry point (1 = warning, 2 = fatal).
+def ClauseSeverityWarning : I32EnumAttrCase<"warning", 1>;
+def ClauseSeverityFatal : I32EnumAttrCase<"fatal", 2>;
+
+def ClauseSeverity : OpenMP_I32EnumAttr<
+ "ClauseSeverity",
+ "severity clause", [
+ ClauseSeverityWarning,
+ ClauseSeverityFatal
+ ]>;
+
+def ClauseSeverityAttr : OpenMP_EnumAttr<ClauseSeverity, "clause_severity"> {
+ let assemblyFormat = "`(` $value `)`";
+}
+
//===----------------------------------------------------------------------===//
// clause_requires enum.
//===----------------------------------------------------------------------===//
diff --git a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
index 5b4ce7cc214bf..ed672b2b3c286 100644
--- a/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
+++ b/mlir/include/mlir/Dialect/OpenMP/OpenMPOps.td
@@ -1730,6 +1730,37 @@ def BarrierOp : OpenMP_Op<"barrier"> {
let assemblyFormat = "attr-dict";
}
+//===----------------------------------------------------------------------===//
+// [5.2] 10.4 error directive
+//===----------------------------------------------------------------------===//
+
+def ErrorOp : OpenMP_Op<"error"> {
+ let summary = "error directive";
+ let description = [{
+ The error directive instructs the compiler or runtime to display a message
+ and, when the severity is `fatal`, to terminate execution. The
+ `at(compilation)` form is handled entirely by the frontend; this operation
+ represents only the `at(execution)` form, which lowers to a call to the
+ `__kmpc_error` runtime entry point.
+
+ The `severity` attribute selects `warning` or `fatal` behavior. The message
+ to display is optional and may be provided as a compile-time-constant
+ string in the `message` attribute, or a pointer to a null-terminated string.
+ }];
+
+ let arguments = (ins ClauseSeverityAttr:$severity,
+ OptionalAttr<StrAttr>:$message,
+ Optional<OpenMP_PointerLikeType>:$message_expr);
+
+ let assemblyFormat = [{
+ `severity` `(` custom<ClauseAttr>($severity) `)`
+ ( `message` `(` $message^ `)` )?
+ ( `message_expr` `(` $message_expr^ `:` type($message_expr) `)` )? attr-dict
+ }];
+
+ let hasVerifier = 1;
+}
+
//===----------------------------------------------------------------------===//
// [5.1] 2.19.9 ordered Construct
//===----------------------------------------------------------------------===//
diff --git a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
index d29d221dfed14..83ae4052dd3e7 100644
--- a/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
+++ b/mlir/lib/Dialect/OpenMP/IR/OpenMPDialect.cpp
@@ -4452,6 +4452,18 @@ LogicalResult CriticalOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
return success();
}
+//===----------------------------------------------------------------------===//
+// Spec 5.1: Error directive (2.5.4)
+//===----------------------------------------------------------------------===//
+
+LogicalResult ErrorOp::verify() {
+ if (getMessage() && getMessageExpr())
+ return emitOpError() << "the message must be provided either as a constant "
+ "`message` attribute or as a `message_expr` "
+ "operand, but not both";
+ return success();
+}
+
//===----------------------------------------------------------------------===//
// Ordered construct
//===----------------------------------------------------------------------===//
diff --git a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
index d860c408e0fd2..345ce620a4b37 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/OpenMP/OpenMPToLLVMIRTranslation.cpp
@@ -9433,6 +9433,21 @@ LogicalResult OpenMPDialectLLVMIRTranslationInterface::convertOperation(
ompBuilder->createFlush(builder.saveIP());
return success();
})
+ .Case([&](omp::ErrorOp op) {
+ if (failed(checkImplementationStatus(*op)))
+ return failure();
+
+ llvm::Value *message = nullptr;
+ if (mlir::Value messageExpr = op.getMessageExpr())
+ message = moduleTranslation.lookupValue(messageExpr);
+ else if (std::optional<StringRef> msg = op.getMessage();
+ msg && !msg->empty())
+ message = builder.CreateGlobalString(*msg);
+ ompBuilder->createError(
+ llvm::OpenMPIRBuilder::LocationDescription(builder),
+ op.getSeverity() == omp::ClauseSeverity::fatal, message);
+ return success();
+ })
.Case([&](omp::ParallelOp op) {
return convertOmpParallel(op, builder, moduleTranslation);
})
diff --git a/mlir/test/Dialect/OpenMP/invalid.mlir b/mlir/test/Dialect/OpenMP/invalid.mlir
index 7f04d73b0ddbd..a4216ed16be35 100644
--- a/mlir/test/Dialect/OpenMP/invalid.mlir
+++ b/mlir/test/Dialect/OpenMP/invalid.mlir
@@ -4791,3 +4791,10 @@ func.func @free_shared_mem_invalid_alignment2(%n: i32, %ptr : !llvm.ptr) -> () {
omp.free_shared_mem [%n x i64 : (i32) align(3)] %ptr : !llvm.ptr
return
}
+
+// -----
+func.func @omp_error_message_and_message_expr(%msg : !llvm.ptr) -> () {
+ // expected-error @below {{the message must be provided either as a constant `message` attribute or as a `message_expr` operand, but not both}}
+ omp.error severity(warning) message("a warning") message_expr(%msg : !llvm.ptr)
+ return
+}
diff --git a/mlir/test/Dialect/OpenMP/ops.mlir b/mlir/test/Dialect/OpenMP/ops.mlir
index 7621dd87eb152..28306a68a9c1a 100644
--- a/mlir/test/Dialect/OpenMP/ops.mlir
+++ b/mlir/test/Dialect/OpenMP/ops.mlir
@@ -60,6 +60,31 @@ func.func @omp_flush(%arg0 : memref<i32>) -> () {
return
}
+// CHECK-LABEL: func @omp_error
+func.func @omp_error(%msg : !llvm.ptr) -> () {
+ // Warning severity with a message.
+ // CHECK: omp.error severity(warning) message("a warning")
+ omp.error severity(warning) message("a warning")
+
+ // Fatal severity with a message.
+ // CHECK: omp.error severity(fatal) message("fatal error")
+ omp.error severity(fatal) message("fatal error")
+
+ // Fatal severity without a message.
+ // CHECK: omp.error severity(fatal)
+ omp.error severity(fatal)
+
+ // Warning severity without a message.
+ // CHECK: omp.error severity(warning)
+ omp.error severity(warning)
+
+ // Warning severity with a runtime message operand.
+ // CHECK: omp.error severity(warning) message_expr(%{{.*}} : !llvm.ptr)
+ omp.error severity(warning) message_expr(%msg : !llvm.ptr)
+
+ return
+}
+
func.func @omp_terminator() -> () {
// CHECK: omp.terminator
omp.terminator
diff --git a/mlir/test/Target/LLVMIR/openmp-llvm.mlir b/mlir/test/Target/LLVMIR/openmp-llvm.mlir
index 1890eaa6a3f0b..3f200f40ea47a 100644
--- a/mlir/test/Target/LLVMIR/openmp-llvm.mlir
+++ b/mlir/test/Target/LLVMIR/openmp-llvm.mlir
@@ -41,6 +41,30 @@ llvm.func @test_flush_construct(%arg0: !llvm.ptr) {
llvm.return
}
+// CHECK-LABEL: define void @test_error_construct
+llvm.func @test_error_construct(%msg : !llvm.ptr) {
+ // Severity is 1 for `warning` and 2 for `fatal`; the message is a pointer to
+ // a constant string, or a null pointer when absent.
+ // CHECK: call void @__kmpc_error(ptr @{{[0-9]+}}, i32 1, ptr @{{.*}})
+ omp.error severity(warning) message("a warning")
+
+ // CHECK: call void @__kmpc_error(ptr @{{[0-9]+}}, i32 2, ptr @{{.*}})
+ omp.error severity(fatal) message("fatal error")
+
+ // A runtime message is passed through as the supplied pointer operand.
+ // CHECK: call void @__kmpc_error(ptr @{{[0-9]+}}, i32 1, ptr %{{.*}})
+ omp.error severity(warning) message_expr(%msg : !llvm.ptr)
+
+ // CHECK: call void @__kmpc_error(ptr @{{[0-9]+}}, i32 2, ptr null)
+ omp.error severity(fatal)
+
+ // CHECK: call void @__kmpc_error(ptr @{{[0-9]+}}, i32 1, ptr null)
+ omp.error severity(warning)
+
+ // CHECK-NEXT: ret void
+ llvm.return
+}
+
// CHECK-LABEL: define void @test_omp_parallel_1()
llvm.func @test_omp_parallel_1() -> () {
// CHECK: call void{{.*}}@__kmpc_fork_call{{.*}}@[[OMP_OUTLINED_FN_1:.*]])
>From 5d8fec7c25496add11bb21d62a555bab06f53f66 Mon Sep 17 00:00:00 2001
From: Caroline Newcombe <caroline.newcombe at hpe.com>
Date: Tue, 30 Jun 2026 09:33:59 -0500
Subject: [PATCH 2/2] [flang][OpenMP] Use no-sema unparse/parse-tree dump in
ERROR directive test to cover compilation-time fatal cases
---
flang/test/Parser/OpenMP/error-unparse.f90 | 39 ++++++++++++++--------
1 file changed, 25 insertions(+), 14 deletions(-)
diff --git a/flang/test/Parser/OpenMP/error-unparse.f90 b/flang/test/Parser/OpenMP/error-unparse.f90
index 255da1111cbf3..5689de8374297 100644
--- a/flang/test/Parser/OpenMP/error-unparse.f90
+++ b/flang/test/Parser/OpenMP/error-unparse.f90
@@ -1,26 +1,37 @@
-! RUN: %flang_fc1 -fopenmp-version=51 -fopenmp -fdebug-unparse %s 2>&1 | FileCheck %s
-! RUN: %flang_fc1 -fopenmp-version=51 -fopenmp -fdebug-dump-parse-tree %s 2>&1 | FileCheck %s --check-prefix="PARSE-TREE"
+! RUN: %flang_fc1 -fopenmp-version=51 -fopenmp -fdebug-unparse-no-sema %s 2>&1 | FileCheck %s
+! RUN: %flang_fc1 -fopenmp-version=51 -fopenmp -fdebug-dump-parse-tree-no-sema %s 2>&1 | FileCheck %s --check-prefix="PARSE-TREE"
-! The AT(COMPILATION) form emits its diagnostic during semantics, so SEVERITY(FATAL)
-! would halt compilation before any unparse/parse-tree output is produced. Only
-! SEVERITY(WARNING) is used with AT(COMPILATION) here; SEVERITY(FATAL) is exercised
-! with AT(EXECUTION), which is not diagnosed until lowering. The compilation-time
-! diagnostics themselves are covered by Semantics/OpenMP/error.f90.
+! Compilation-time diagnostics are covered by Semantics/OpenMP/error.f90.
program main
character(*), parameter :: message = "This is an error"
!CHECK: !$OMP ERROR AT(COMPILATION) SEVERITY(WARNING) MESSAGE("some message here")
- !PARSE-TREE: ExecutionPartConstruct -> ExecutableConstruct -> OpenMPConstruct -> OmpUtilityDirective -> OmpErrorDirective
+ !PARSE-TREE: DeclarationConstruct -> SpecificationConstruct -> OpenMPDeclarativeConstruct -> OmpUtilityDirective -> OmpErrorDirective -> OmpDirectiveSpecification
+ !PARSE-TREE: OmpDirectiveName -> llvm::omp::Directive = error
!PARSE-TREE: OmpClauseList -> OmpClause -> At -> OmpAtClause -> ActionTime = Compilation
!PARSE-TREE: OmpClause -> Severity -> OmpSeverityClause -> SevLevel = Warning
- !PARSE-TREE: OmpClause -> Message -> OmpMessageClause -> Expr = '"some message here"'
- !PARSE-TREE: LiteralConstant -> CharLiteralConstant
+ !PARSE-TREE: OmpClause -> Message -> OmpMessageClause -> Expr -> LiteralConstant -> CharLiteralConstant
!PARSE-TREE: string = 'some message here'
!$omp error at(compilation) severity(warning) message("some message here")
- !CHECK: !$OMP ERROR AT(EXECUTION) SEVERITY(FATAL) MESSAGE("This is an error")
- !PARSE-TREE: ExecutionPartConstruct -> ExecutableConstruct -> OpenMPConstruct -> OmpUtilityDirective -> OmpErrorDirective
+ !CHECK: !$OMP ERROR AT(COMPILATION) SEVERITY(FATAL) MESSAGE(message)
+ !PARSE-TREE: DeclarationConstruct -> SpecificationConstruct -> OpenMPDeclarativeConstruct -> OmpUtilityDirective -> OmpErrorDirective -> OmpDirectiveSpecification
+ !PARSE-TREE: OmpDirectiveName -> llvm::omp::Directive = error
+ !PARSE-TREE: OmpClauseList -> OmpClause -> At -> OmpAtClause -> ActionTime = Compilation
+ !PARSE-TREE: OmpClause -> Severity -> OmpSeverityClause -> SevLevel = Fatal
+ !PARSE-TREE: OmpClause -> Message -> OmpMessageClause -> Expr -> Designator -> DataRef -> Name = 'message'
+ !$omp error at(compilation) severity(fatal) message(message)
+ !CHECK: !$OMP ERROR AT(EXECUTION) SEVERITY(FATAL) MESSAGE(message)
+ !PARSE-TREE: DeclarationConstruct -> SpecificationConstruct -> OpenMPDeclarativeConstruct -> OmpUtilityDirective -> OmpErrorDirective -> OmpDirectiveSpecification
+ !PARSE-TREE: OmpDirectiveName -> llvm::omp::Directive = error
!PARSE-TREE: OmpClauseList -> OmpClause -> At -> OmpAtClause -> ActionTime = Execution
!PARSE-TREE: OmpClause -> Severity -> OmpSeverityClause -> SevLevel = Fatal
- !PARSE-TREE: OmpClause -> Message -> OmpMessageClause -> Expr = '"This is an error"'
- !PARSE-TREE: Designator -> DataRef -> Name = 'message'
+ !PARSE-TREE: OmpClause -> Message -> OmpMessageClause -> Expr -> Designator -> DataRef -> Name = 'message'
!$omp error at(EXECUTION) severity(fatal) message(message)
+ !CHECK: !$OMP ERROR AT(EXECUTION) SEVERITY(WARNING) MESSAGE("a warning")
+ !PARSE-TREE: DeclarationConstruct -> SpecificationConstruct -> OpenMPDeclarativeConstruct -> OmpUtilityDirective -> OmpErrorDirective -> OmpDirectiveSpecification
+ !PARSE-TREE: OmpDirectiveName -> llvm::omp::Directive = error
+ !PARSE-TREE: OmpClauseList -> OmpClause -> At -> OmpAtClause -> ActionTime = Execution
+ !PARSE-TREE: OmpClause -> Severity -> OmpSeverityClause -> SevLevel = Warning
+ !PARSE-TREE: OmpClause -> Message -> OmpMessageClause -> Expr -> LiteralConstant -> CharLiteralConstant
+ !PARSE-TREE: string = 'a warning'
+ !$omp error at(execution) severity(warning) message("a warning")
end program main
More information about the flang-commits
mailing list