[llvm-branch-commits] [flang] [flang] Add fir.assert and its LLVM code generation (PR #231245)

via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Mon Oct 12 10:53:31 PDT 2026


https://github.com/jeanPerier created https://github.com/llvm/llvm-project/pull/231245

Add the new fir.assert operation for which the design was introduced in https://github.com/llvm/llvm-project/pull/228502

Verify its message and diagnostic operands, and erase constant-true assertions. Keep attribute and optionality queries able to inspect guarded values without making rewriting operations transparent to the guard.
    
For the LLVM code generation, generate assertions to a weighted branch, a noreturn runtime call on failure, and guarded operands on success. Pack larger diagnostic payloads into an array on the failure path and preserve source locations and device symbol scope. Add dialect, code generation, effect, and variable-query tests.
    
This introduces the operation without emitting it from source lowering.

Assisted-by: AI

>From 49ca54d5bc55fde492a74a311c9dbe157a465760 Mon Sep 17 00:00:00 2001
From: Jean Perier <jperier at nvidia.com>
Date: Mon, 12 Oct 2026 09:58:43 -0700
Subject: [PATCH] [flang] Add fir.assert and its LLVM lowering

Introduce a non-speculatable assertion with typed guarded results and a
write effect on a non-addressable Fortran runtime resource. Verify its
message and diagnostic operands, and erase constant-true assertions.
Keep attribute and optionality queries able to inspect guarded values
without making rewriting operations transparent to the guard.

Lower assertions to a weighted branch, a noreturn runtime call on failure,
and guarded operands on success. Pack larger diagnostic payloads into an
array on the failure path and preserve source locations and device symbol
scope. Add dialect, code generation, effect, and variable-query tests.

This introduces the operation without emitting it from source lowering.
---
 .../flang/Optimizer/Dialect/FIRAttr.td        |  19 +++
 .../include/flang/Optimizer/Dialect/FIROps.h  |   7 +
 .../include/flang/Optimizer/Dialect/FIROps.td |  45 ++++++
 flang/lib/Optimizer/CodeGen/CodeGen.cpp       | 146 +++++++++++++++++-
 flang/lib/Optimizer/Dialect/FIROps.cpp        |  77 ++++++++-
 flang/test/Fir/assert-array-codegen.fir       |  75 +++++++++
 flang/test/Fir/assert-codegen.fir             |  68 ++++++++
 flang/test/Fir/assert-invalid.fir             |  71 +++++++++
 flang/test/Fir/assert-memory.fir              |  45 ++++++
 flang/test/Fir/assert-no-fold-through.fir     |  16 ++
 flang/test/Fir/assert.fir                     |  72 +++++++++
 .../Optimizer/FortranVariableTest.cpp         |  66 ++++++++
 12 files changed, 703 insertions(+), 4 deletions(-)
 create mode 100644 flang/test/Fir/assert-array-codegen.fir
 create mode 100644 flang/test/Fir/assert-codegen.fir
 create mode 100644 flang/test/Fir/assert-invalid.fir
 create mode 100644 flang/test/Fir/assert-memory.fir
 create mode 100644 flang/test/Fir/assert-no-fold-through.fir
 create mode 100644 flang/test/Fir/assert.fir

diff --git a/flang/include/flang/Optimizer/Dialect/FIRAttr.td b/flang/include/flang/Optimizer/Dialect/FIRAttr.td
index 88d7964768b5a..581c831c8985e 100644
--- a/flang/include/flang/Optimizer/Dialect/FIRAttr.td
+++ b/flang/include/flang/Optimizer/Dialect/FIRAttr.td
@@ -189,6 +189,25 @@ def fir_ReduceAttr : fir_Attr<"Reduce"> {
 // mlir::SideEffects::Resource for modelling operations which add debugging information
 def DebuggingResource : Resource<"::fir::DebuggingResource">;
 
+def FortranRuntimeResource : Resource<"::fir::FortranRuntimeResource">;
+
+def fir_RuntimeCheckKind : I32EnumAttr<"RuntimeCheckKind",
+    "Fortran runtime check category", [
+      I32EnumAttrCase<"status", 0>,
+      I32EnumAttrCase<"conformance", 1>,
+      I32EnumAttrCase<"argument", 2>,
+      I32EnumAttrCase<"bounds", 3>,
+      I32EnumAttrCase<"pointer", 4>,
+      I32EnumAttrCase<"doLoop", 5, "do">,
+      I32EnumAttrCase<"bits", 6>,
+      I32EnumAttrCase<"divide", 7>
+    ]> {
+  let genSpecializedAttr = 0;
+  let cppNamespace = "::fir";
+}
+def fir_RuntimeCheckKindAttr
+    : EnumAttr<FIROpsDialect, fir_RuntimeCheckKind, "runtime_check_kind">;
+
 def fir_LowerBoundModifierAttribute : I32EnumAttr<
     "LowerBoundModifierAttribute",
     "Describes how to modify lower bounds",
diff --git a/flang/include/flang/Optimizer/Dialect/FIROps.h b/flang/include/flang/Optimizer/Dialect/FIROps.h
index 9d771b1e60604..5ea6cb81587ba 100644
--- a/flang/include/flang/Optimizer/Dialect/FIROps.h
+++ b/flang/include/flang/Optimizer/Dialect/FIROps.h
@@ -54,6 +54,13 @@ struct DebuggingResource
   bool isAddressable() const override { return false; }
 };
 
+/// Runtime checks can terminate execution, but do not access user memory.
+struct FortranRuntimeResource
+    : public mlir::SideEffects::Resource::Base<FortranRuntimeResource> {
+  mlir::StringRef getName() const final { return "FortranRuntimeResource"; }
+  bool isAddressable() const override { return false; }
+};
+
 /// Model operations which read from/write to volatile memory
 struct VolatileMemoryResource
     : public mlir::SideEffects::Resource::Base<VolatileMemoryResource> {
diff --git a/flang/include/flang/Optimizer/Dialect/FIROps.td b/flang/include/flang/Optimizer/Dialect/FIROps.td
index 7fc12430d0663..9d9ffe4ee9b3b 100644
--- a/flang/include/flang/Optimizer/Dialect/FIROps.td
+++ b/flang/include/flang/Optimizer/Dialect/FIROps.td
@@ -81,6 +81,51 @@ def AnyRefOfConstantSizeAggregateType : TypeConstraint<
 // Memory SSA operations
 //===----------------------------------------------------------------------===//
 
+def fir_AssertOp : fir_Op<"assert", [
+    AttrSizedOperandSegments,
+    TypesMatchWith<"result types match guarded operand types", "guarded",
+                   "results", "$_self">,
+    MemoryEffects<[MemWrite<FortranRuntimeResource>]>]> {
+  let summary = "check a condition and guard values used by subsequent operations";
+  let description = [{
+    If `condition` is true, each result equals the corresponding `guarded`
+    operand. Otherwise, execution terminates through the Fortran runtime,
+    reporting `message` at the operation's source location. The message may
+    contain `%lld` directives, one for each integer or index operand
+    in `values`, and `%%` for a literal percent sign. Diagnostic values are
+    interpreted as signed integers and must fit in 64 bits.
+
+    Consumers of the results depend on the check. The operation is deliberately
+    not speculatable or a view: looking through it in a rewrite would discard
+    that dependence. Analyses may inspect the guarded operands. Its write to
+    a non-addressable runtime resource preserves checks without obstructing
+    memory optimizations.
+
+    Guard array bases and scalar operands used by the protected computation.
+    FIR users of shape and slice metadata may require direct constructor
+    operands; guard the underlying extents or indices before constructing that
+    metadata instead.
+  }];
+  let arguments = (ins I1:$condition, fir_RuntimeCheckKindAttr:$kind,
+                       StrAttr:$message,
+                       Variadic<AnySignlessIntegerOrIndex>:$values,
+                       Variadic<AnyType>:$guarded);
+  let results = (outs Variadic<AnyType>:$results);
+  let assemblyFormat = [{
+    $condition `,` enum($kind) `,` $message
+    (`values` `(` $values^ `:` type($values) `)`)?
+    (`guard` `(` $guarded^ `:` type($guarded) `)`)? attr-dict
+  }];
+  let builders = [OpBuilder<(ins "mlir::Value":$condition,
+      "fir::RuntimeCheckKind":$kind, "llvm::StringRef":$message,
+      "mlir::ValueRange":$values, "mlir::ValueRange":$guarded), [{
+    build($_builder, $_state, guarded.getTypes(), condition, kind, message,
+          values, guarded);
+  }]>];
+  let hasVerifier = 1;
+  let hasCanonicalizer = 1;
+}
+
 def fir_AllocaOp : fir_Op<"alloca", [
   AttrSizedOperandSegments,
   DeclareOpInterfaceMethods<PromotableAllocationOpInterface>
diff --git a/flang/lib/Optimizer/CodeGen/CodeGen.cpp b/flang/lib/Optimizer/CodeGen/CodeGen.cpp
index 3eec423b5669b..29a5ef06cc732 100644
--- a/flang/lib/Optimizer/CodeGen/CodeGen.cpp
+++ b/flang/lib/Optimizer/CodeGen/CodeGen.cpp
@@ -4415,6 +4415,150 @@ struct UnreachableOpConversion
   }
 };
 
+/// Prefer the original source of an inlined operation to its call site.
+static mlir::FileLineColLoc findAssertSourceLocation(mlir::Location loc) {
+  if (auto file = mlir::dyn_cast<mlir::FileLineColLoc>(loc))
+    return file;
+  if (auto call = mlir::dyn_cast<mlir::CallSiteLoc>(loc)) {
+    if (auto file = findAssertSourceLocation(call.getCallee()))
+      return file;
+    return findAssertSourceLocation(call.getCaller());
+  }
+  if (auto fused = mlir::dyn_cast<mlir::FusedLoc>(loc))
+    for (mlir::Location nested : fused.getLocations())
+      if (auto file = findAssertSourceLocation(nested))
+        return file;
+  if (auto named = mlir::dyn_cast<mlir::NameLoc>(loc))
+    return findAssertSourceLocation(named.getChildLoc());
+  return {};
+}
+
+struct AssertOpConversion : public fir::FIROpConversion<fir::AssertOp> {
+  using FIROpConversion::FIROpConversion;
+
+  llvm::LogicalResult
+  matchAndRewrite(fir::AssertOp op, OpAdaptor adaptor,
+                  mlir::ConversionPatternRewriter &rewriter) const override {
+    mlir::Location loc = op.getLoc();
+    // A device function's strings and runtime declaration must remain in its
+    // GPU module when host and device modules are subsequently separated.
+    mlir::Operation *scope = mlir::SymbolTable::getNearestSymbolTable(op);
+    auto ptrTy = getLlvmPtrType(op.getContext());
+    auto i64Ty = rewriter.getI64Type();
+    auto countTy = rewriter.getIntegerType(lowerTy().getPointerBitwidth(0));
+    bool useArray = adaptor.getValues().size() > 4;
+    llvm::SmallVector<mlir::Type> argumentTypes{ptrTy, ptrTy,
+                                                rewriter.getI32Type()};
+    if (useArray)
+      argumentTypes.append({countTy, ptrTy});
+    else
+      argumentTypes.append(4, i64Ty);
+    auto functionType = mlir::LLVM::LLVMFunctionType::get(
+        mlir::LLVM::LLVMVoidType::get(op.getContext()), argumentTypes);
+    llvm::StringRef runtimeName =
+        useArray ? "_FortranAReportFatalUserErrorValueArray"
+                 : "_FortranAReportFatalUserErrorValues";
+    mlir::Operation *declaration =
+        mlir::SymbolTable::lookupSymbolIn(scope, runtimeName);
+    if (declaration) {
+      if (auto function = mlir::dyn_cast<mlir::LLVM::LLVMFuncOp>(declaration)) {
+        if (function.getFunctionType() != functionType)
+          return op.emitOpError("incompatible runtime function declaration");
+        rewriter.modifyOpInPlace(function, [&] { function.setNoreturn(true); });
+      } else if (auto function =
+                     mlir::dyn_cast<mlir::func::FuncOp>(declaration)) {
+        if (function.getNumResults() != 0 ||
+            function.getNumArguments() != functionType.getNumParams())
+          return op.emitOpError("incompatible runtime function declaration");
+        for (auto [input, expected] : llvm::zip_equal(
+                 function.getArgumentTypes(), functionType.getParams()))
+          if (convertType(input) != expected)
+            return op.emitOpError("incompatible runtime function declaration");
+        rewriter.modifyOpInPlace(function, [&] {
+          function->setAttr("llvm.noreturn", rewriter.getUnitAttr());
+        });
+      } else {
+        return op.emitOpError("runtime function name conflicts with a symbol");
+      }
+    } else {
+      mlir::OpBuilder::InsertionGuard guard(rewriter);
+      rewriter.setInsertionPointToStart(&scope->getRegion(0).front());
+      auto function = mlir::LLVM::LLVMFuncOp::create(rewriter, loc, runtimeName,
+                                                     functionType);
+      function.setNoreturn(true);
+    }
+
+    mlir::Block *before = op->getBlock();
+    mlir::Block *continuation =
+        rewriter.splitBlock(before, mlir::Block::iterator(op));
+    mlir::Block *failure = rewriter.createBlock(before->getParent());
+
+    auto stringAddress = [&](llvm::StringRef text) -> mlir::Value {
+      std::string terminated = text.str() + '\0';
+      std::string name = fir::factory::uniqueCGIdent("assert", terminated);
+      if (!mlir::SymbolTable::lookupSymbolIn(scope, name)) {
+        mlir::OpBuilder::InsertionGuard guard(rewriter);
+        rewriter.setInsertionPointToStart(&scope->getRegion(0).front());
+        auto arrayTy = mlir::LLVM::LLVMArrayType::get(rewriter.getI8Type(),
+                                                      terminated.size());
+        mlir::LLVM::GlobalOp::create(rewriter, loc, arrayTy, /*constant=*/true,
+                                     mlir::LLVM::Linkage::Internal, name,
+                                     rewriter.getStringAttr(terminated));
+      }
+      return mlir::LLVM::AddressOfOp::create(rewriter, loc, ptrTy, name);
+    };
+
+    mlir::Value message = stringAddress(op.getMessage());
+    mlir::FileLineColLoc source = findAssertSourceLocation(loc);
+    mlir::Value filename =
+        source ? stringAddress(source.getFilename().getValue())
+               : mlir::LLVM::ZeroOp::create(rewriter, loc, ptrTy);
+    mlir::Value line =
+        genI32Constant(loc, rewriter, source ? source.getLine() : 0);
+    llvm::SmallVector<mlir::Value> arguments{message, filename, line};
+    if (useArray) {
+      // Materialize diagnostic storage only on the failure path. Keep the
+      // values in SSA form until here so they add no memory dependencies to
+      // the high-level assertion.
+      mlir::Value count = fir::genConstantIndex(loc, countTy, rewriter,
+                                                adaptor.getValues().size());
+      auto storagePtrTy =
+          getLlvmPtrType(op.getContext(), getAllocaAddressSpace(rewriter));
+      mlir::Value storage = mlir::LLVM::AllocaOp::create(
+          rewriter, loc, storagePtrTy, i64Ty, count);
+      for (auto [index, value] : llvm::enumerate(adaptor.getValues())) {
+        mlir::Value offset =
+            fir::genConstantIndex(loc, countTy, rewriter, index);
+        mlir::Value address = mlir::LLVM::GEPOp::create(
+            rewriter, loc, storagePtrTy, i64Ty, storage,
+            llvm::ArrayRef<mlir::LLVM::GEPArg>{offset});
+        mlir::LLVM::StoreOp::create(
+            rewriter, loc, integerCast(loc, rewriter, i64Ty, value), address);
+      }
+      if (storagePtrTy != ptrTy)
+        storage =
+            mlir::LLVM::AddrSpaceCastOp::create(rewriter, loc, ptrTy, storage);
+      arguments.append({count, storage});
+    } else {
+      for (mlir::Value value : adaptor.getValues())
+        arguments.push_back(integerCast(loc, rewriter, i64Ty, value));
+      while (arguments.size() < 7)
+        arguments.push_back(mlir::LLVM::ConstantOp::create(
+            rewriter, loc, i64Ty, rewriter.getI64IntegerAttr(0)));
+    }
+    mlir::LLVM::CallOp::create(rewriter, loc, functionType, runtimeName,
+                               arguments);
+    mlir::LLVM::UnreachableOp::create(rewriter, loc);
+
+    rewriter.setInsertionPointToEnd(before);
+    mlir::LLVM::CondBrOp::create(rewriter, loc, adaptor.getCondition(),
+                                 continuation, mlir::ValueRange{}, failure,
+                                 mlir::ValueRange{}, std::make_pair(2000u, 1u));
+    rewriter.replaceOp(op, adaptor.getGuarded());
+    return mlir::success();
+  }
+};
+
 /// `fir.is_present` -->
 /// ```
 ///  %0 = llvm.mlir.constant(0 : i64)
@@ -5193,7 +5337,7 @@ void fir::populateFIRToLLVMConversionPatterns(
       StoreOpConversion, StringLitOpConversion, SubcOpConversion,
       TypeDescOpConversion, TypeInfoOpConversion, UnboxCharOpConversion,
       UnboxProcOpConversion, UndefOpConversion, UnreachableOpConversion,
-      UseStmtOpConversion, ModuleDebugImportsOpConversion,
+      AssertOpConversion, UseStmtOpConversion, ModuleDebugImportsOpConversion,
       XArrayCoorOpConversion, XEmboxOpConversion, XReboxOpConversion,
       ZeroOpConversion>(converter, options);
 
diff --git a/flang/lib/Optimizer/Dialect/FIROps.cpp b/flang/lib/Optimizer/Dialect/FIROps.cpp
index 606d47631488b..4f3ab9984f99e 100644
--- a/flang/lib/Optimizer/Dialect/FIROps.cpp
+++ b/flang/lib/Optimizer/Dialect/FIROps.cpp
@@ -56,6 +56,59 @@ bool fir::useStrictVolatileVerification() {
   return clUseStrictVolatileVerification;
 }
 
+//===----------------------------------------------------------------------===//
+// AssertOp
+//===----------------------------------------------------------------------===//
+
+llvm::LogicalResult fir::AssertOp::verify() {
+  if (getGuarded().getTypes() != getResultTypes())
+    return emitOpError("result types must match guarded operand types");
+  for (mlir::Type type : getValues().getTypes())
+    if (auto integer = mlir::dyn_cast<mlir::IntegerType>(type))
+      if (integer.getWidth() > 64)
+        return emitOpError("diagnostic integers must be at most 64 bits wide");
+
+  llvm::StringRef message = getMessage();
+  if (message.contains('\0'))
+    return emitOpError("message must not contain an embedded null character");
+  unsigned directives = 0;
+  while (!message.empty()) {
+    size_t percent = message.find('%');
+    if (percent == llvm::StringRef::npos)
+      break;
+    message = message.drop_front(percent + 1);
+    if (message.consume_front("%"))
+      continue;
+    if (!message.consume_front("lld"))
+      return emitOpError("message supports only %lld and %% format directives");
+    ++directives;
+  }
+  if (directives != getValues().size())
+    return emitOpError(
+        "number of %lld directives must match diagnostic values");
+  return mlir::success();
+}
+
+namespace {
+struct RemoveTrueAssert : mlir::OpRewritePattern<fir::AssertOp> {
+  using OpRewritePattern::OpRewritePattern;
+
+  llvm::LogicalResult
+  matchAndRewrite(fir::AssertOp op,
+                  mlir::PatternRewriter &rewriter) const override {
+    if (!mlir::matchPattern(op.getCondition(), mlir::m_One()))
+      return mlir::failure();
+    rewriter.replaceOp(op, op.getGuarded());
+    return mlir::success();
+  }
+};
+} // namespace
+
+void fir::AssertOp::getCanonicalizationPatterns(
+    mlir::RewritePatternSet &patterns, mlir::MLIRContext *context) {
+  patterns.add<RemoveTrueAssert>(context);
+}
+
 static void propagateAttributes(mlir::Operation *fromOp,
                                 mlir::Operation *toOp) {
   if (!fromOp || !toOp)
@@ -247,6 +300,13 @@ bool fir::mayBeAbsentBox(mlir::Value val) {
     if (auto varIface = mlir::dyn_cast<fir::FortranVariableOpInterface>(defOp))
       return varIface.isOptional();
 
+    if (auto assertOp = mlir::dyn_cast<fir::AssertOp>(defOp)) {
+      val =
+          assertOp
+              .getGuarded()[mlir::cast<mlir::OpResult>(val).getResultNumber()];
+      continue;
+    }
+
     // Check for fir.embox and fir.rebox before checking for
     // FortranObjectViewOpInterface, which they support.
     // A box created by fir.embox/fir.rebox/fir.rebox_assumed_rank is only
@@ -6104,10 +6164,21 @@ valueCheckFirAttributes(mlir::Value value,
   };
   // If this is a fir.box that was loaded, the fir attributes will be on the
   // related fir.ref<fir.box> creation.
-  if (mlir::isa<fir::BoxType>(value.getType()))
-    if (auto definingOp = value.getDefiningOp())
-      if (auto loadOp = mlir::dyn_cast<fir::LoadOp>(definingOp))
+  // This is an attribute query only. Do not make rewriting helpers transparent
+  // to guards: replacing a guarded use with its origin loses the check.
+  while (true) {
+    if (auto assertOp = value.getDefiningOp<fir::AssertOp>()) {
+      value = assertOp.getGuarded()[mlir::cast<mlir::OpResult>(value)
+                                        .getResultNumber()];
+      continue;
+    }
+    if (mlir::isa<fir::BoxType>(value.getType()))
+      if (auto loadOp = value.getDefiningOp<fir::LoadOp>()) {
         value = loadOp.getMemref();
+        continue;
+      }
+    break;
+  }
   // If this is a function argument, look in the argument attributes.
   if (auto blockArg = mlir::dyn_cast<mlir::BlockArgument>(value)) {
     if (blockArg.getOwner() && blockArg.getOwner()->isEntryBlock())
diff --git a/flang/test/Fir/assert-array-codegen.fir b/flang/test/Fir/assert-array-codegen.fir
new file mode 100644
index 0000000000000..4824a77f23940
--- /dev/null
+++ b/flang/test/Fir/assert-array-codegen.fir
@@ -0,0 +1,75 @@
+// RUN: fir-opt --split-input-file --fir-to-llvm-ir %s | FileCheck %s
+// RUN: fir-opt --split-input-file --fir-to-llvm-ir="datalayout=e-p:64:64-p5:32:32-A5" %s | FileCheck %s --check-prefix=PRIVATE
+// RUN: fir-opt --split-input-file --fir-to-llvm-ir="target=i386-unknown-linux-gnu datalayout=e-p:32:32" %s | FileCheck %s --check-prefix=COUNT32
+
+// Five values select the array entry. Conversion, allocation and stores must
+// stay on the failure path, and the guarded result survives on success.
+func.func @array_values(%ok: i1, %a: i8, %b: i16, %c: i32, %d: index, %e: i64, %base: !fir.ref<i32>) -> !fir.ref<i32> {
+  %g = fir.assert %ok, bounds, "dimension %lld, position %lld, index %lld, bounds %lld:%lld (100%%)" values(%a, %b, %c, %d, %e : i8, i16, i32, index, i64) guard(%base : !fir.ref<i32>) loc("bounds.f90":17:2)
+  return %g : !fir.ref<i32>
+}
+// CHECK: llvm.func @_FortranAReportFatalUserErrorValueArray(!llvm.ptr, !llvm.ptr, i32, i64, !llvm.ptr) attributes {noreturn}
+// CHECK-LABEL: llvm.func @array_values(
+// CHECK-SAME: %[[OK:.*]]: i1, %[[A:.*]]: i8, %[[B:.*]]: i16, %[[C:.*]]: i32, %[[D:.*]]: i64, %[[E:.*]]: i64, %[[BASE:.*]]: !llvm.ptr)
+// CHECK-NEXT: llvm.cond_br %[[OK]] weights([2000, 1]), ^[[CONT:bb[0-9]+]], ^[[FAIL:bb[0-9]+]]
+// CHECK: ^[[CONT]]:
+// CHECK-NEXT: llvm.return %[[BASE]] : !llvm.ptr
+// CHECK: ^[[FAIL]]:
+// CHECK: %[[COUNT:.*]] = llvm.mlir.constant(5 : i64)
+// CHECK-NEXT: %[[STORAGE:.*]] = llvm.alloca %[[COUNT]] x i64 : (i64) -> !llvm.ptr
+// CHECK: %[[P0:.*]] = llvm.getelementptr %[[STORAGE]][%{{.*}}]
+// CHECK: %[[V0:.*]] = llvm.sext %[[A]] : i8 to i64
+// CHECK-NEXT: llvm.store %[[V0]], %[[P0]]
+// CHECK: %[[P1:.*]] = llvm.getelementptr %[[STORAGE]][%{{.*}}]
+// CHECK: %[[V1:.*]] = llvm.sext %[[B]] : i16 to i64
+// CHECK-NEXT: llvm.store %[[V1]], %[[P1]]
+// CHECK: %[[P2:.*]] = llvm.getelementptr %[[STORAGE]][%{{.*}}]
+// CHECK: %[[V2:.*]] = llvm.sext %[[C]] : i32 to i64
+// CHECK-NEXT: llvm.store %[[V2]], %[[P2]]
+// CHECK: %[[P3:.*]] = llvm.getelementptr %[[STORAGE]][%{{.*}}]
+// CHECK-NEXT: llvm.store %[[D]], %[[P3]]
+// CHECK: %[[P4:.*]] = llvm.getelementptr %[[STORAGE]][%{{.*}}]
+// CHECK-NEXT: llvm.store %[[E]], %[[P4]]
+// CHECK: llvm.call @_FortranAReportFatalUserErrorValueArray(%{{.*}}, %{{.*}}, %{{.*}}, %[[COUNT]], %[[STORAGE]])
+// CHECK-NEXT: llvm.unreachable
+// PRIVATE-LABEL: llvm.func @array_values
+// PRIVATE: %[[STORAGE:.*]] = llvm.alloca %{{.*}} x i64 : (i64) -> !llvm.ptr<5>
+// PRIVATE: %[[GENERIC:.*]] = llvm.addrspacecast %[[STORAGE]] : !llvm.ptr<5> to !llvm.ptr
+// PRIVATE-NEXT: llvm.call @_FortranAReportFatalUserErrorValueArray(%{{.*}}, %{{.*}}, %{{.*}}, %{{.*}}, %[[GENERIC]])
+// COUNT32: llvm.func @_FortranAReportFatalUserErrorValueArray(!llvm.ptr, !llvm.ptr, i32, i32, !llvm.ptr) attributes {noreturn}
+// COUNT32-LABEL: llvm.func @array_values
+// COUNT32: %[[COUNT:.*]] = llvm.mlir.constant(5 : i32)
+// COUNT32-NEXT: llvm.alloca %[[COUNT]] x i64 : (i32) -> !llvm.ptr
+// COUNT32: llvm.call @_FortranAReportFatalUserErrorValueArray
+
+// -----
+
+// A complete pair of rank-six shapes needs twelve values. The device runtime
+// declaration and diagnostic strings must stay inside the GPU module.
+gpu.module @device {
+  gpu.func @shapes(%ok: i1, %n: index) {
+    fir.assert %ok, conformance, "shapes (%lld,%lld,%lld,%lld,%lld,%lld) and (%lld,%lld,%lld,%lld,%lld,%lld)" values(%n, %n, %n, %n, %n, %n, %n, %n, %n, %n, %n, %n : index, index, index, index, index, index, index, index, index, index, index, index) loc("shapes.f90":23:1)
+    gpu.return
+  }
+}
+// CHECK-LABEL: gpu.module @device {
+// CHECK-DAG: llvm.mlir.global internal constant @{{.*}}("shapes.f90\00")
+// CHECK-DAG: llvm.func @_FortranAReportFatalUserErrorValueArray({{.*}}) attributes {noreturn}
+// CHECK: gpu.func @shapes
+// CHECK: llvm.cond_br
+// CHECK: %[[COUNT:.*]] = llvm.mlir.constant(12 : i64)
+// CHECK: %[[ARRAY:.*]] = llvm.alloca %[[COUNT]] x i64
+// CHECK-COUNT-12: llvm.store
+// CHECK: llvm.call @_FortranAReportFatalUserErrorValueArray(%{{.*}}, %{{.*}}, %{{.*}}, %[[COUNT]], %[[ARRAY]])
+// CHECK-NEXT: llvm.unreachable
+
+// -----
+
+func.func private @_FortranAReportFatalUserErrorValueArray(!fir.ref<i8>, !fir.ref<i8>, i32, index, !fir.ref<i64>)
+func.func @existing_array(%ok: i1, %n: i64) {
+  fir.assert %ok, bounds, "%lld %lld %lld %lld %lld" values(%n, %n, %n, %n, %n : i64, i64, i64, i64, i64)
+  return
+}
+// CHECK: llvm.func @_FortranAReportFatalUserErrorValueArray({{.*}}) attributes {noreturn{{.*}}}
+// CHECK-LABEL: llvm.func @existing_array
+// CHECK: llvm.call @_FortranAReportFatalUserErrorValueArray
diff --git a/flang/test/Fir/assert-codegen.fir b/flang/test/Fir/assert-codegen.fir
new file mode 100644
index 0000000000000..7bd2126a27632
--- /dev/null
+++ b/flang/test/Fir/assert-codegen.fir
@@ -0,0 +1,68 @@
+// RUN: fir-opt --split-input-file --fir-to-llvm-ir %s | FileCheck %s
+
+func.func @check(%ok: i1, %small: i32, %n: index, %array: !fir.ref<!fir.array<?xi32>>) -> !fir.ref<!fir.array<?xi32>> {
+  %g = fir.assert %ok, conformance, "extents %lld and %lld" values(%small, %n : i32, index) guard(%array : !fir.ref<!fir.array<?xi32>>) loc("source.f90":42:7)
+  return %g : !fir.ref<!fir.array<?xi32>>
+}
+// CHECK-DAG: llvm.mlir.global internal constant @{{.*}}("extents %lld and %lld\00")
+// CHECK-DAG: llvm.mlir.global internal constant @{{.*}}("source.f90\00")
+// CHECK-DAG: llvm.func @_FortranAReportFatalUserErrorValues(!llvm.ptr, !llvm.ptr, i32, i64, i64, i64, i64) attributes {noreturn}
+// CHECK-LABEL: llvm.func @check(
+// CHECK-SAME: %[[OK:.*]]: i1, %[[SMALL:.*]]: i32, %[[N:.*]]: i64, %[[ARRAY:.*]]: !llvm.ptr)
+// CHECK-NEXT: llvm.cond_br %[[OK]] weights([2000, 1]), ^[[CONT:bb[0-9]+]], ^[[FAIL:bb[0-9]+]]
+// CHECK: ^[[CONT]]:
+// CHECK-NEXT: llvm.return %[[ARRAY]] : !llvm.ptr
+// CHECK: ^[[FAIL]]:
+// CHECK: %[[MSG:.*]] = llvm.mlir.addressof
+// CHECK: %[[FILE:.*]] = llvm.mlir.addressof
+// CHECK: %[[LINE:.*]] = llvm.mlir.constant(42 : i32)
+// CHECK: %[[EXT:.*]] = llvm.sext %[[SMALL]] : i32 to i64
+// CHECK: %[[ZERO:.*]] = llvm.mlir.constant(0 : i64)
+// CHECK: llvm.call @_FortranAReportFatalUserErrorValues(%[[MSG]], %[[FILE]], %[[LINE]], %[[EXT]], %[[N]], %[[ZERO]], %{{.*}})
+// CHECK-NEXT: llvm.unreachable
+
+// -----
+
+// Existing declarations must also acquire noreturn.
+llvm.func @_FortranAReportFatalUserErrorValues(!llvm.ptr, !llvm.ptr, i32, i64, i64, i64, i64)
+func.func @existing_llvm(%ok: i1) {
+  fir.assert %ok, status, "allocation failed" loc(unknown)
+  return
+}
+// CHECK: llvm.func @_FortranAReportFatalUserErrorValues({{.*}}) attributes {noreturn}
+// CHECK-LABEL: llvm.func @existing_llvm
+// CHECK: llvm.cond_br
+// CHECK: %[[NULL:.*]] = llvm.mlir.zero : !llvm.ptr
+// CHECK: %[[LINE:.*]] = llvm.mlir.constant(0 : i32)
+// CHECK: llvm.call @_FortranAReportFatalUserErrorValues(%{{.*}}, %[[NULL]], %[[LINE]],
+// CHECK-NEXT: llvm.unreachable
+
+// -----
+
+func.func @existing_fir(%ok: i1) {
+  fir.assert %ok, status, "allocation failed" loc(fused["inlined"(callsite("inner.f90":9:2 at "outer.f90":80:1))])
+  return
+}
+func.func private @_FortranAReportFatalUserErrorValues(!fir.ref<i8>, !fir.ref<i8>, i32, i64, i64, i64, i64)
+// CHECK: llvm.mlir.global internal constant @{{.*}}("inner.f90\00")
+// CHECK-LABEL: llvm.func @existing_fir
+// CHECK: llvm.mlir.constant(9 : i32)
+// CHECK: llvm.call @_FortranAReportFatalUserErrorValues
+// CHECK: llvm.func @_FortranAReportFatalUserErrorValues({{.*}}) attributes {noreturn{{.*}}}
+
+// -----
+
+gpu.module @device {
+  gpu.func @check_device(%ok: i1, %n: i64) {
+    fir.assert %ok, bounds, "subscript %lld" values(%n : i64) loc("kernel.f90":11:3)
+    gpu.return
+  }
+}
+// CHECK-LABEL: gpu.module @device {
+// CHECK-DAG: llvm.mlir.global internal constant @{{.*}}("kernel.f90\00")
+// CHECK-DAG: llvm.mlir.global internal constant @{{.*}}("subscript %lld\00")
+// CHECK-DAG: llvm.func @_FortranAReportFatalUserErrorValues({{.*}}) attributes {noreturn}
+// CHECK: gpu.func @check_device
+// CHECK: llvm.cond_br
+// CHECK: llvm.call @_FortranAReportFatalUserErrorValues
+// CHECK: llvm.unreachable
diff --git a/flang/test/Fir/assert-invalid.fir b/flang/test/Fir/assert-invalid.fir
new file mode 100644
index 0000000000000..1003021e3733c
--- /dev/null
+++ b/flang/test/Fir/assert-invalid.fir
@@ -0,0 +1,71 @@
+// RUN: fir-opt --split-input-file --verify-diagnostics %s
+
+func.func @format(%ok: i1, %i: i32) {
+  // expected-error at +1 {{message supports only %lld and %% format directives}}
+  fir.assert %ok, argument, "%d" values(%i : i32)
+  return
+}
+
+// -----
+
+func.func @trailing_percent(%ok: i1) {
+  // expected-error at +1 {{message supports only %lld and %% format directives}}
+  fir.assert %ok, argument, "bad %"
+  return
+}
+
+// -----
+
+func.func @too_few_values(%ok: i1) {
+  // expected-error at +1 {{number of %lld directives must match diagnostic values}}
+  fir.assert %ok, argument, "%lld"
+  return
+}
+
+// -----
+
+func.func @too_many_values(%ok: i1, %i: i32) {
+  // expected-error at +1 {{number of %lld directives must match diagnostic values}}
+  fir.assert %ok, argument, "%%lld" values(%i : i32)
+  return
+}
+
+// -----
+
+func.func @wide_integer(%ok: i1, %i: i128) {
+  // expected-error at +1 {{diagnostic integers must be at most 64 bits wide}}
+  fir.assert %ok, argument, "%lld" values(%i : i128)
+  return
+}
+
+// -----
+
+func.func @null_character(%ok: i1) {
+  // expected-error at +1 {{message must not contain an embedded null character}}
+  fir.assert %ok, argument, "bad\00argument"
+  return
+}
+
+// -----
+
+func.func @wrong_result_type(%ok: i1, %i: i32) {
+  // expected-error at +1 {{failed to verify that result types match guarded operand types}}
+  %g = "fir.assert"(%ok, %i) <{kind = #fir.runtime_check_kind<argument>, message = "bad", operandSegmentSizes = array<i32: 1, 0, 1>}> : (i1, i32) -> i64
+  return
+}
+
+// -----
+
+func.func @wrong_result_count(%ok: i1, %i: i32) {
+  // expected-error at +1 {{failed to verify that result types match guarded operand types}}
+  "fir.assert"(%ok, %i) <{kind = #fir.runtime_check_kind<argument>, message = "bad", operandSegmentSizes = array<i32: 1, 0, 1>}> : (i1, i32) -> ()
+  return
+}
+
+// -----
+
+func.func @real_value(%ok: i1, %x: f32) {
+  // expected-error at +1 {{must be variadic of signless integer or index}}
+  fir.assert %ok, argument, "%lld" values(%x : f32)
+  return
+}
diff --git a/flang/test/Fir/assert-memory.fir b/flang/test/Fir/assert-memory.fir
new file mode 100644
index 0000000000000..d28d49485f43d
--- /dev/null
+++ b/flang/test/Fir/assert-memory.fir
@@ -0,0 +1,45 @@
+// RUN: fir-opt %s --cse --split-input-file | FileCheck %s --check-prefix=CSE
+// RUN: fir-opt %s --fir-memref-dataflow-opt --split-input-file | FileCheck %s --check-prefix=FORWARD
+
+// Checks touch no user memory, so they do not prevent load CSE.
+// CSE-LABEL: func.func @load_cse(
+// CSE: %[[V:.*]] = fir.load
+// CSE-NEXT: fir.assert
+// CSE-NOT: fir.load
+// CSE: arith.addi %[[V]], %[[V]]
+func.func @load_cse(%addr: !fir.ref<i32>, %ok: i1) -> i32 {
+  %a = fir.load %addr : !fir.ref<i32>
+  fir.assert %ok, argument, "invalid argument"
+  %b = fir.load %addr : !fir.ref<i32>
+  %sum = arith.addi %a, %b : i32
+  return %sum : i32
+}
+
+// -----
+
+// FORWARD-LABEL: func.func @store_forward(
+// FORWARD-SAME: %[[ADDR:.*]]: !fir.ref<i32>, %[[VALUE:.*]]: i32,
+// FORWARD: fir.store %[[VALUE]]
+// FORWARD-NEXT: fir.assert
+// FORWARD-NOT: fir.load
+// FORWARD: return %[[VALUE]]
+func.func @store_forward(%addr: !fir.ref<i32>, %value: i32, %ok: i1) -> i32 {
+  fir.store %value to %addr : !fir.ref<i32>
+  fir.assert %ok, argument, "invalid argument"
+  %load = fir.load %addr : !fir.ref<i32>
+  return %load : i32
+}
+
+// -----
+
+// CSE-LABEL: func.func @guarded_load(
+// CSE: fir.load
+// CSE: %[[G:.*]] = fir.assert
+// CSE-NEXT: %{{.*}} = fir.load %[[G]]
+func.func @guarded_load(%addr: !fir.ref<i32>, %ok: i1) -> i32 {
+  %a = fir.load %addr : !fir.ref<i32>
+  %guarded = fir.assert %ok, pointer, "invalid pointer" guard(%addr : !fir.ref<i32>)
+  %b = fir.load %guarded : !fir.ref<i32>
+  %sum = arith.addi %a, %b : i32
+  return %sum : i32
+}
diff --git a/flang/test/Fir/assert-no-fold-through.fir b/flang/test/Fir/assert-no-fold-through.fir
new file mode 100644
index 0000000000000..a783272d255a9
--- /dev/null
+++ b/flang/test/Fir/assert-no-fold-through.fir
@@ -0,0 +1,16 @@
+// RUN: fir-opt %s --canonicalize | FileCheck %s
+
+// Reboxing a guarded box address must not fold back to the original box.
+// CHECK-LABEL: func.func @guarded_box_address(
+// CHECK: %[[ADDR:.*]] = fir.box_addr
+// CHECK: %[[GUARDED:.*]] = fir.assert {{.*}} guard(%[[ADDR]]
+// CHECK: %[[BOX:.*]] = fir.embox %[[GUARDED]]
+// CHECK: return %[[BOX]]
+func.func @guarded_box_address(%box: !fir.box<!fir.array<10xi32>>, %ok: i1) -> !fir.box<!fir.array<10xi32>> {
+  %c10 = arith.constant 10 : index
+  %shape = fir.shape %c10 : (index) -> !fir.shape<1>
+  %addr = fir.box_addr %box : (!fir.box<!fir.array<10xi32>>) -> !fir.ref<!fir.array<10xi32>>
+  %g = fir.assert %ok, pointer, "invalid pointer" guard(%addr : !fir.ref<!fir.array<10xi32>>)
+  %result = fir.embox %g(%shape) : (!fir.ref<!fir.array<10xi32>>, !fir.shape<1>) -> !fir.box<!fir.array<10xi32>>
+  return %result : !fir.box<!fir.array<10xi32>>
+}
diff --git a/flang/test/Fir/assert.fir b/flang/test/Fir/assert.fir
new file mode 100644
index 0000000000000..e4c0bf7e65d90
--- /dev/null
+++ b/flang/test/Fir/assert.fir
@@ -0,0 +1,72 @@
+// RUN: fir-opt %s | fir-opt | FileCheck %s --check-prefix=ROUNDTRIP
+// RUN: fir-opt --canonicalize %s | FileCheck %s --check-prefix=FOLD
+
+func.func @asserts(%ok: i1, %n: index, %a: !fir.box<!fir.array<?xf32>>, %i: i32) -> (!fir.box<!fir.array<?xf32>>, i32) {
+  fir.assert %ok, status, "allocation failed"
+  %g:2 = fir.assert %ok, conformance, "extent %lld, expected %lld (100%%)" values(%n, %i : index, i32) guard(%a, %i : !fir.box<!fir.array<?xf32>>, i32)
+  fir.assert %ok, argument, "argument"
+  fir.assert %ok, bounds, "bounds"
+  fir.assert %ok, pointer, "pointer"
+  fir.assert %ok, do, "do"
+  fir.assert %ok, bits, "bits"
+  fir.assert %ok, divide, "divide"
+  return %g#0, %g#1 : !fir.box<!fir.array<?xf32>>, i32
+}
+// ROUNDTRIP-LABEL: func.func @asserts
+// ROUNDTRIP: fir.assert %{{.*}}, status, "allocation failed"
+// ROUNDTRIP: %[[G:.*]]:2 = fir.assert %{{.*}}, conformance, "extent %lld, expected %lld (100%%)" values(%{{.*}}, %{{.*}} : index, i32) guard(%{{.*}}, %{{.*}} : !fir.box<!fir.array<?xf32>>, i32)
+// ROUNDTRIP: fir.assert %{{.*}}, argument,
+// ROUNDTRIP: fir.assert %{{.*}}, bounds,
+// ROUNDTRIP: fir.assert %{{.*}}, pointer,
+// ROUNDTRIP: fir.assert %{{.*}}, do,
+// ROUNDTRIP: fir.assert %{{.*}}, bits,
+// ROUNDTRIP: fir.assert %{{.*}}, divide,
+// ROUNDTRIP: return %[[G]]#0, %[[G]]#1
+
+func.func @many_values(%ok: i1, %i: i32) {
+  fir.assert %ok, argument, "%lld %lld %lld %lld %lld" values(%i, %i, %i, %i, %i : i32, i32, i32, i32, i32)
+  return
+}
+// ROUNDTRIP-LABEL: func.func @many_values
+// ROUNDTRIP: fir.assert %{{.*}}, argument, "%lld %lld %lld %lld %lld" values(
+
+func.func @true_check(%x: i32) -> i32 {
+  %true = arith.constant true
+  fir.assert %true, status, "no results"
+  %g = fir.assert %true, argument, "guard" guard(%x : i32)
+  return %g : i32
+}
+// FOLD-LABEL: func.func @true_check(
+// FOLD-SAME: %[[X:.*]]: i32)
+// FOLD-NOT: fir.assert
+// FOLD: return %[[X]] : i32
+
+func.func @false_check(%x: i32) -> i32 {
+  %false = arith.constant false
+  fir.assert %false, status, "no results"
+  %g = fir.assert %false, argument, "guard" guard(%x : i32)
+  return %g : i32
+}
+// FOLD-LABEL: func.func @false_check
+// FOLD: fir.assert %{{.*}}, status, "no results"
+// FOLD: %[[G:.*]] = fir.assert %{{.*}}, argument, "guard"
+// FOLD: return %[[G]] : i32
+
+// Unused guards must not make a potentially failing assertion trivially dead.
+func.func @unused_results(%ok: i1, %x: i32) {
+  %g = fir.assert %ok, bounds, "unused" guard(%x : i32)
+  return
+}
+// FOLD-LABEL: func.func @unused_results
+// FOLD: fir.assert %{{.*}}, bounds, "unused" guard
+
+// This is not a view: folding a reboxed address would bypass the check.
+func.func @keep_guard(%ok: i1, %box: !fir.box<i32>) -> !fir.box<i32> {
+  %addr = fir.box_addr %box : (!fir.box<i32>) -> !fir.ref<i32>
+  %guard = fir.assert %ok, pointer, "null pointer" guard(%addr : !fir.ref<i32>)
+  %result = fir.embox %guard : (!fir.ref<i32>) -> !fir.box<i32>
+  return %result : !fir.box<i32>
+}
+// FOLD-LABEL: func.func @keep_guard
+// FOLD: %[[G:.*]] = fir.assert
+// FOLD: fir.embox %[[G]]
diff --git a/flang/unittests/Optimizer/FortranVariableTest.cpp b/flang/unittests/Optimizer/FortranVariableTest.cpp
index 7b363590cdea1..d2ed554735762 100644
--- a/flang/unittests/Optimizer/FortranVariableTest.cpp
+++ b/flang/unittests/Optimizer/FortranVariableTest.cpp
@@ -8,6 +8,7 @@
 
 #include "gtest/gtest.h"
 #include "flang/Optimizer/Dialect/FIROps.h"
+#include "flang/Optimizer/Dialect/FIROpsSupport.h"
 #include "flang/Optimizer/Support/InitFIR.h"
 
 struct FortranVariableTest : public testing::Test {
@@ -160,3 +161,68 @@ TEST_F(FortranVariableTest, CharacterArray) {
   EXPECT_EQ(fortranVariable.getBase().getType(), addr.getType());
   EXPECT_EQ(fortranVariable.getExplicitCharLen(), len);
 }
+
+TEST_F(FortranVariableTest, AttributesThroughAssert) {
+  mlir::Location loc = getLoc();
+  auto target = fir::AllocaOp::create(*builder, loc, builder->getI32Type());
+  target->setAttr(fir::getTargetAttrName(), builder->getUnitAttr());
+  auto plain = fir::AllocaOp::create(*builder, loc, builder->getI32Type());
+  auto condition = mlir::arith::ConstantOp::create(
+      *builder, loc, builder->getBoolAttr(false));
+  auto guard = fir::AssertOp::create(*builder, loc, condition,
+      fir::RuntimeCheckKind::conformance, "mismatch", mlir::ValueRange{},
+      mlir::ValueRange{plain, target});
+  auto nested = fir::AssertOp::create(*builder, loc, condition,
+      fir::RuntimeCheckKind::pointer, "invalid pointer", mlir::ValueRange{},
+      guard.getResults());
+  EXPECT_FALSE(
+      fir::valueHasFirAttribute(nested.getResult(0), fir::getTargetAttrName()));
+  EXPECT_TRUE(
+      fir::valueHasFirAttribute(nested.getResult(1), fir::getTargetAttrName()));
+  EXPECT_FALSE(fir::valueMayHaveFirAttributes(
+      nested.getResult(0), {fir::getTargetAttrName()}));
+  EXPECT_TRUE(fir::valueMayHaveFirAttributes(
+      nested.getResult(1), {fir::getTargetAttrName()}));
+}
+
+TEST_F(FortranVariableTest, BoxAttributesThroughAssertAndLoad) {
+  mlir::Location loc = getLoc();
+  auto boxTy = fir::BoxType::get(builder->getF32Type());
+  auto boxAddr = fir::AllocaOp::create(*builder, loc, boxTy);
+  boxAddr->setAttr(fir::getTargetAttrName(), builder->getUnitAttr());
+  auto condition = mlir::arith::ConstantOp::create(
+      *builder, loc, builder->getBoolAttr(false));
+  auto guardAddr = fir::AssertOp::create(*builder, loc, condition,
+      fir::RuntimeCheckKind::pointer, "invalid pointer", mlir::ValueRange{},
+      mlir::ValueRange{boxAddr});
+  auto box = fir::LoadOp::create(*builder, loc, guardAddr.getResult(0));
+  auto guardBox = fir::AssertOp::create(*builder, loc, condition,
+      fir::RuntimeCheckKind::conformance, "mismatch", mlir::ValueRange{},
+      mlir::ValueRange{box});
+  EXPECT_TRUE(fir::valueHasFirAttribute(
+      guardBox.getResult(0), fir::getTargetAttrName()));
+  EXPECT_FALSE(fir::mayBeAbsentBox(guardBox.getResult(0)));
+}
+
+TEST_F(FortranVariableTest, OptionalBoxThroughAssert) {
+  mlir::Location loc = getLoc();
+  auto boxTy = fir::BoxType::get(builder->getF32Type());
+  mlir::Value box = builder->getInsertionBlock()->addArgument(boxTy, loc);
+  auto condition = mlir::arith::ConstantOp::create(
+      *builder, loc, builder->getBoolAttr(false));
+  for (bool optional : {false, true}) {
+    auto flags = optional ? fir::FortranVariableFlagsEnum::optional
+                          : fir::FortranVariableFlagsEnum::None;
+    auto declare = fir::DeclareOp::create(*builder, loc, boxTy, box,
+        /*shape=*/mlir::Value{}, /*typeParams=*/mlir::ValueRange{},
+        /*dummy_scope=*/nullptr, /*storage=*/nullptr, /*storage_offset=*/0,
+        builder->getStringAttr("x"),
+        fir::FortranVariableFlagsAttr::get(&context, flags),
+        /*data_attr=*/cuf::DataAttributeAttr{},
+        /*dummy_arg_no=*/mlir::IntegerAttr{});
+    auto guard = fir::AssertOp::create(*builder, loc, condition,
+        fir::RuntimeCheckKind::conformance, "mismatch", mlir::ValueRange{},
+        mlir::ValueRange{declare});
+    EXPECT_EQ(fir::mayBeAbsentBox(guard.getResult(0)), optional);
+  }
+}



More information about the llvm-branch-commits mailing list