[flang-commits] [flang] [flang] Added fir.create_box operation. (PR #210220)

Slava Zakharin via flang-commits flang-commits at lists.llvm.org
Fri Jul 17 09:05:07 PDT 2026


https://github.com/vzakhari updated https://github.com/llvm/llvm-project/pull/210220

>From 25170af486534eabb42b89d4d79c2446979b0d0c Mon Sep 17 00:00:00 2001
From: Slava Zakharin <szakharin at nvidia.com>
Date: Thu, 16 Jul 2026 17:31:36 -0700
Subject: [PATCH 1/2] [flang] Added fir.create_box operation.

This patch adds `fir.create_box` operation that allows
creating `!fir.box<>` values with arbitrary byte strides.
It can be used to recreate boxes describing non-contiguous
arrays from descriptor components (base_addr/lbs/extents/strides)
across offload regions.

This is just initial definition and code generation code.
The folders and passes producing this operation will be added
later.
---
 .../include/flang/Optimizer/Dialect/FIROps.td | 59 ++++++++++++++++++
 flang/lib/Optimizer/CodeGen/CodeGen.cpp       | 55 ++++++++++++++--
 flang/lib/Optimizer/Dialect/FIROps.cpp        | 62 +++++++++++++++++++
 flang/test/Fir/create-box-codegen.fir         | 39 ++++++++++++
 flang/test/Fir/fir-ops.fir                    | 21 +++++++
 flang/test/Fir/invalid.fir                    | 32 ++++++++++
 6 files changed, 262 insertions(+), 6 deletions(-)
 create mode 100644 flang/test/Fir/create-box-codegen.fir

diff --git a/flang/include/flang/Optimizer/Dialect/FIROps.td b/flang/include/flang/Optimizer/Dialect/FIROps.td
index 8ecdd5ec4ac1c..367f69cf43aa3 100644
--- a/flang/include/flang/Optimizer/Dialect/FIROps.td
+++ b/flang/include/flang/Optimizer/Dialect/FIROps.td
@@ -1019,6 +1019,65 @@ def fir_ReboxOp : fir_Op<"rebox", [NoMemoryEffect, AttrSizedOperandSegments,
   }];
 }
 
+def fir_CreateBoxOp : fir_Op<"create_box", [NoMemoryEffect,
+                                            AttrSizedOperandSegments,
+                                            fir_FortranObjectViewOpInterface]> {
+  let summary = "create a box from a base pointer and explicit byte strides";
+
+  let description = [{
+    Create an array descriptor from a base data pointer plus fully explicit
+    per-dimension lower bound, extent, and **byte** stride. Unlike `fir.embox`,
+    which derives the per-dimension stride from the element size (so it can only
+    describe strides that are integer multiples of the element size), the
+    strides given here are written into the descriptor verbatim. This makes it
+    possible to describe non-contiguous data whose byte stride is not a multiple
+    of the element size (for example a derived-type component array).
+
+    The element length and CFI type code are taken from the result box element
+    type, so the base pointer only supplies the address; it does not constrain
+    the stride.
+
+    ```
+      %box = fir.create_box %base lbs(%lb0) extents(%ext0) strides(%sm0)
+          : (!fir.heap<!fir.array<?xcomplex<f32>>>, index, index, index)
+              -> !fir.box<!fir.heap<!fir.array<?xcomplex<f32>>>>
+    ```
+
+    The `lbounds`, `extents`, and `strides` groups must each have exactly one
+    value per array dimension. The strides are expressed in bytes. The result
+    must be a non-polymorphic `!fir.box` of a statically-sized element type.
+
+    The result box element type must preserve the memref's storage kind, so
+    that the descriptor CFI attribute field is set correctly and the result
+    matches a box loaded from the corresponding `!fir.ref<!fir.box<...>>`:
+    `!fir.heap<...>` produces `!fir.box<!fir.heap<...>>` (allocatable),
+    `!fir.ptr<...>` produces `!fir.box<!fir.ptr<...>>` (pointer), and a plain
+    `!fir.ref<...>` produces `!fir.box<...>`.
+  }];
+
+  let arguments = (ins AnyReferenceLike:$memref,
+      Variadic<AnyIntegerType>:$lbounds,
+      Variadic<AnyIntegerType>:$extents,
+      Variadic<AnyIntegerType>:$strides);
+
+  let results = (outs fir_BoxType);
+
+  let assemblyFormat = [{
+    $memref `lbs` `(` $lbounds `)` `extents` `(` $extents `)`
+      `strides` `(` $strides `)` attr-dict `:`
+      functional-type(operands, results)
+  }];
+
+  let hasVerifier = 1;
+
+  let extraClassDeclaration = [{
+    unsigned getRank();
+    // FortranObjectViewOpInterface methods:
+    mlir::Value getViewSource(mlir::OpResult) { return getMemref(); }
+    std::optional<std::int64_t> getViewOffset(mlir::OpResult);
+  }];
+}
+
 def fir_ReboxAssumedRankOp : fir_Op<"rebox_assumed_rank",
   [DeclareOpInterfaceMethods<MemoryEffectsOpInterface>]> {
   let summary = "create an assumed-rank box given another assumed-rank box";
diff --git a/flang/lib/Optimizer/CodeGen/CodeGen.cpp b/flang/lib/Optimizer/CodeGen/CodeGen.cpp
index 8679307d40d9c..a1b6caf25cf03 100644
--- a/flang/lib/Optimizer/CodeGen/CodeGen.cpp
+++ b/flang/lib/Optimizer/CodeGen/CodeGen.cpp
@@ -1980,7 +1980,7 @@ struct EmboxCommonConversion : public fir::FIROpConversion<OP> {
     bool useInputType = fir::isPolymorphicType(boxTy) &&
                         !fir::isUnlimitedPolymorphicType(inputType);
     llvm::SmallVector<mlir::Value> typeparams = lenParams;
-    if constexpr (!std::is_same_v<BOX, fir::EmboxOp>) {
+    if constexpr (std::is_same_v<BOX, fir::cg::XEmboxOp>) {
       if (!box.getSubstr().empty() && fir::hasDynamicSize(boxTy.getEleTy()))
         typeparams.push_back(substrParams[1]);
     }
@@ -2323,6 +2323,49 @@ static bool isDeviceAllocation(mlir::Value val, mlir::Value adaptorVal) {
   return false;
 }
 
+/// Create a box from a base pointer and explicit per-dimension lower bound,
+/// extent, and byte stride. Unlike fir.embox, the strides are written into the
+/// descriptor verbatim (no scaling by the element size), while elem_len/type
+/// are taken from the result box element type.
+struct CreateBoxOpConversion : public EmboxCommonConversion<fir::CreateBoxOp> {
+  using EmboxCommonConversion::EmboxCommonConversion;
+
+  llvm::LogicalResult
+  matchAndRewrite(fir::CreateBoxOp createBox, OpAdaptor adaptor,
+                  mlir::ConversionPatternRewriter &rewriter) const override {
+    mlir::Location loc = createBox.getLoc();
+    const unsigned rank = createBox.getRank();
+    auto [boxTy, dest, eleSize] = consDescriptorPrefix(
+        createBox, fir::unwrapRefType(createBox.getMemref().getType()),
+        rewriter, rank, /*substrParams=*/mlir::ValueRange{},
+        /*lenParams=*/mlir::ValueRange{});
+
+    auto i64Ty = mlir::IntegerType::get(createBox.getContext(), 64);
+    mlir::ValueRange lbounds = adaptor.getLbounds();
+    mlir::ValueRange extents = adaptor.getExtents();
+    mlir::ValueRange strides = adaptor.getStrides();
+    for (unsigned d = 0; d < rank; ++d) {
+      mlir::Value lb = integerCast(loc, rewriter, i64Ty, lbounds[d]);
+      mlir::Value extent = integerCast(loc, rewriter, i64Ty, extents[d]);
+      // The stride is a byte stride and is stored as-is; this is the whole
+      // point of the op (fir.embox would scale it by the element size).
+      mlir::Value stride = integerCast(loc, rewriter, i64Ty, strides[d]);
+      dest = insertLowerBound(rewriter, loc, dest, d, lb);
+      dest = insertExtent(rewriter, loc, dest, d, extent);
+      dest = insertStride(rewriter, loc, dest, d, stride);
+    }
+    dest = insertBaseAddress(rewriter, loc, dest, adaptor.getMemref());
+
+    bool needsDeviceAlloc =
+        isDeviceAllocation(createBox.getMemref(), adaptor.getMemref()) ||
+        isUsedByGPULaunchFunc(createBox);
+    mlir::Value result = placeInMemoryIfNotGlobalInit(rewriter, loc, boxTy,
+                                                      dest, needsDeviceAlloc);
+    rewriter.replaceOp(createBox, result);
+    return mlir::success();
+  }
+};
+
 /// Create a generic box on a memory reference.
 struct XEmboxOpConversion : public EmboxCommonConversion<fir::cg::XEmboxOp> {
   using EmboxCommonConversion::EmboxCommonConversion;
@@ -5122,11 +5165,11 @@ void fir::populateFIRToLLVMConversionPatterns(
       DeclareValueOpConversion,
       DoConcurrentSpecifierOpConversion<fir::LocalitySpecifierOp>,
       DoConcurrentSpecifierOpConversion<fir::DeclareReductionOp>,
-      DivcOpConversion, EmboxOpConversion, EmboxCharOpConversion,
-      EmboxProcOpConversion, EqvOpConversion, ExtractValueOpConversion,
-      FakeUseOpConversion, FieldIndexOpConversion, FirEndOpConversion,
-      FreeMemOpConversion, GlobalLenOpConversion, GlobalOpConversion,
-      InsertOnRangeOpConversion, IsPresentOpConversion,
+      CreateBoxOpConversion, DivcOpConversion, EmboxOpConversion,
+      EmboxCharOpConversion, EmboxProcOpConversion, EqvOpConversion,
+      ExtractValueOpConversion, FakeUseOpConversion, FieldIndexOpConversion,
+      FirEndOpConversion, FreeMemOpConversion, GlobalLenOpConversion,
+      GlobalOpConversion, InsertOnRangeOpConversion, IsPresentOpConversion,
       LenParamIndexOpConversion, LoadOpConversion, LogicalAndOpConversion,
       LogicalOrOpConversion, MulcOpConversion, NegcOpConversion,
       NeqvOpConversion, NoReassocOpConversion, PrefetchOpConversion,
diff --git a/flang/lib/Optimizer/Dialect/FIROps.cpp b/flang/lib/Optimizer/Dialect/FIROps.cpp
index 34221654cb53a..24c0b065e3e03 100644
--- a/flang/lib/Optimizer/Dialect/FIROps.cpp
+++ b/flang/lib/Optimizer/Dialect/FIROps.cpp
@@ -2821,6 +2821,68 @@ mlir::Speculation::Speculatability fir::EmboxOp::getSpeculatability() {
              : mlir::Speculation::Speculatable;
 }
 
+//===----------------------------------------------------------------------===//
+// CreateBoxOp
+//===----------------------------------------------------------------------===//
+
+unsigned fir::CreateBoxOp::getRank() {
+  auto boxTy = mlir::cast<fir::BaseBoxType>(getResult().getType());
+  if (auto seqTy = mlir::dyn_cast<fir::SequenceType>(
+          fir::unwrapRefType(boxTy.getEleTy())))
+    return seqTy.getDimension();
+  return 0;
+}
+
+llvm::LogicalResult fir::CreateBoxOp::verify() {
+  auto memEleTy = fir::dyn_cast_ptrEleTy(getMemref().getType());
+  auto seqTy = mlir::dyn_cast_or_null<fir::SequenceType>(memEleTy);
+  if (!seqTy)
+    return emitOpError("memref must be a reference to an array");
+
+  auto boxTy = mlir::cast<fir::BaseBoxType>(getResult().getType());
+  auto boxSeqTy =
+      mlir::dyn_cast<fir::SequenceType>(fir::unwrapRefType(boxTy.getEleTy()));
+  if (!boxSeqTy)
+    return emitOpError("result box must describe an array");
+
+  // The result box must preserve the memref's storage kind so that the
+  // descriptor's CFI attribute field is set correctly and the value matches a
+  // box loaded from the corresponding !fir.ref<!fir.box<...>>:
+  //   !fir.heap<...> -> !fir.box<!fir.heap<...>> (allocatable)
+  //   !fir.ptr<...>  -> !fir.box<!fir.ptr<...>>  (pointer)
+  //   !fir.ref<...>  -> !fir.box<...>            (plain)
+  mlir::Type memrefTy = getMemref().getType();
+  mlir::Type boxEleTy = boxTy.getEleTy();
+  if (mlir::isa<fir::HeapType>(memrefTy) !=
+          mlir::isa<fir::HeapType>(boxEleTy) ||
+      mlir::isa<fir::PointerType>(memrefTy) !=
+          mlir::isa<fir::PointerType>(boxEleTy))
+    return emitOpError(
+        "result box element storage kind must match the memref storage kind");
+
+  unsigned rank = boxSeqTy.getDimension();
+  if (seqTy.getDimension() != rank)
+    return emitOpError("memref rank does not match result box rank");
+  if (getLbounds().size() != rank || getExtents().size() != rank ||
+      getStrides().size() != rank)
+    return emitOpError("expected ")
+           << rank << " values for each of lbounds, extents, and strides";
+
+  mlir::Type eleTy = boxSeqTy.getEleTy();
+  if (fir::isPolymorphicType(boxTy))
+    return emitOpError("result box element type must not be polymorphic");
+  if (fir::characterWithDynamicLen(eleTy) ||
+      fir::isRecordWithTypeParameters(eleTy))
+    return emitOpError("result box element type must be statically sized");
+  return mlir::success();
+}
+
+std::optional<std::int64_t> fir::CreateBoxOp::getViewOffset(mlir::OpResult) {
+  // The base pointer is the view source and the descriptor addresses relative
+  // to it, so the view offset onto the source is zero.
+  return 0;
+}
+
 //===----------------------------------------------------------------------===//
 // EmboxCharOp
 //===----------------------------------------------------------------------===//
diff --git a/flang/test/Fir/create-box-codegen.fir b/flang/test/Fir/create-box-codegen.fir
new file mode 100644
index 0000000000000..16d69f45d3ea1
--- /dev/null
+++ b/flang/test/Fir/create-box-codegen.fir
@@ -0,0 +1,39 @@
+// Test fir.create_box code generation.
+// RUN: fir-opt --fir-to-llvm-ir="target=x86_64-unknown-linux-gnu" %s | FileCheck %s
+
+// The byte stride operand is written into the descriptor verbatim: unlike
+// fir.embox, there is no scaling by the element size (no llvm.mul), so a
+// non-element-multiple stride is representable. elem_len/type still come from
+// the result box element type.
+
+func.func @create_box_strided(%base: !fir.heap<!fir.array<?xcomplex<f32>>>, %lb: index, %ext: index, %sm: index) {
+  %0 = fir.create_box %base lbs(%lb) extents(%ext) strides(%sm) : (!fir.heap<!fir.array<?xcomplex<f32>>>, index, index, index) -> !fir.box<!fir.heap<!fir.array<?xcomplex<f32>>>>
+  return
+}
+// The stride operand (%[[SM]]) is inserted at dims[0].sm ([7, 0, 2]) verbatim,
+// i.e. the value bound to the block argument, not a product of it and the
+// element size. Combined with CHECK-NOT: llvm.mul this proves no scaling.
+// CHECK-LABEL: llvm.func @create_box_strided(
+// CHECK-SAME:      %[[BASE:[^:]+]]: !llvm.ptr, %[[LB:[^:]+]]: i64, %[[EXT:[^:]+]]: i64, %[[SM:[^:]+]]: i64
+// CHECK-NOT:       llvm.mul
+// CHECK:           llvm.insertvalue %[[LB]], %{{.*}}[7, 0, 0] :
+// CHECK:           llvm.insertvalue %[[EXT]], %{{.*}}[7, 0, 1] :
+// CHECK:           llvm.insertvalue %[[SM]], %{{.*}}[7, 0, 2] :
+// CHECK:           llvm.insertvalue %[[BASE]], %{{.*}}[0] :
+
+// -----
+
+func.func @create_box_strided_2d(%base: !fir.ref<!fir.array<?x?xf32>>, %lb0: index, %ext0: index, %sm0: index, %lb1: index, %ext1: index, %sm1: index) {
+  %0 = fir.create_box %base lbs(%lb0, %lb1) extents(%ext0, %ext1) strides(%sm0, %sm1) : (!fir.ref<!fir.array<?x?xf32>>, index, index, index, index, index, index) -> !fir.box<!fir.array<?x?xf32>>
+  return
+}
+// CHECK-LABEL: llvm.func @create_box_strided_2d(
+// CHECK-SAME:      %[[BASE:[^:]+]]: !llvm.ptr, %[[LB0:[^:]+]]: i64, %[[EXT0:[^:]+]]: i64, %[[SM0:[^:]+]]: i64, %[[LB1:[^:]+]]: i64, %[[EXT1:[^:]+]]: i64, %[[SM1:[^:]+]]: i64
+// CHECK-NOT:       llvm.mul
+// CHECK:           llvm.insertvalue %[[LB0]], %{{.*}}[7, 0, 0] :
+// CHECK:           llvm.insertvalue %[[EXT0]], %{{.*}}[7, 0, 1] :
+// CHECK:           llvm.insertvalue %[[SM0]], %{{.*}}[7, 0, 2] :
+// CHECK:           llvm.insertvalue %[[LB1]], %{{.*}}[7, 1, 0] :
+// CHECK:           llvm.insertvalue %[[EXT1]], %{{.*}}[7, 1, 1] :
+// CHECK:           llvm.insertvalue %[[SM1]], %{{.*}}[7, 1, 2] :
+// CHECK:           llvm.insertvalue %[[BASE]], %{{.*}}[0] :
diff --git a/flang/test/Fir/fir-ops.fir b/flang/test/Fir/fir-ops.fir
index 417cdbcc5d9f5..9aebabff59e53 100644
--- a/flang/test/Fir/fir-ops.fir
+++ b/flang/test/Fir/fir-ops.fir
@@ -1139,6 +1139,27 @@ func.func @test_logical_ops(%l1: !fir.logical<4>, %l2: !fir.logical<4>, %i1: i32
   return
 }
 
+// CHECK-LABEL: @test_create_box(
+func.func @test_create_box(%arg0: !fir.heap<!fir.array<?xcomplex<f32>>>, %lb: index, %ext: index, %sm: index) {
+  // CHECK: fir.create_box %{{.*}} lbs(%{{.*}}) extents(%{{.*}}) strides(%{{.*}}) : (!fir.heap<!fir.array<?xcomplex<f32>>>, index, index, index) -> !fir.box<!fir.heap<!fir.array<?xcomplex<f32>>>>
+  %0 = fir.create_box %arg0 lbs(%lb) extents(%ext) strides(%sm) : (!fir.heap<!fir.array<?xcomplex<f32>>>, index, index, index) -> !fir.box<!fir.heap<!fir.array<?xcomplex<f32>>>>
+  return
+}
+
+// CHECK-LABEL: @test_create_box_pointer(
+func.func @test_create_box_pointer(%arg0: !fir.ptr<!fir.array<?xf32>>, %lb: index, %ext: index, %sm: index) {
+  // CHECK: fir.create_box %{{.*}} lbs(%{{.*}}) extents(%{{.*}}) strides(%{{.*}}) : (!fir.ptr<!fir.array<?xf32>>, index, index, index) -> !fir.box<!fir.ptr<!fir.array<?xf32>>>
+  %0 = fir.create_box %arg0 lbs(%lb) extents(%ext) strides(%sm) : (!fir.ptr<!fir.array<?xf32>>, index, index, index) -> !fir.box<!fir.ptr<!fir.array<?xf32>>>
+  return
+}
+
+// CHECK-LABEL: @test_create_box_2d(
+func.func @test_create_box_2d(%arg0: !fir.ref<!fir.array<?x?xf32>>, %lb0: index, %ext0: index, %sm0: index, %lb1: index, %ext1: index, %sm1: index) {
+  // CHECK: fir.create_box %{{.*}} lbs(%{{.*}}, %{{.*}}) extents(%{{.*}}, %{{.*}}) strides(%{{.*}}, %{{.*}}) : (!fir.ref<!fir.array<?x?xf32>>, index, index, index, index, index, index) -> !fir.box<!fir.array<?x?xf32>>
+  %0 = fir.create_box %arg0 lbs(%lb0, %lb1) extents(%ext0, %ext1) strides(%sm0, %sm1) : (!fir.ref<!fir.array<?x?xf32>>, index, index, index, index, index, index) -> !fir.box<!fir.array<?x?xf32>>
+  return
+}
+
 // CHECK-LABEL: fir.module_debug_imports "debug_mod"
 // CHECK-NEXT: fir.use_stmt "used_mod"
 // CHECK-NEXT: }
diff --git a/flang/test/Fir/invalid.fir b/flang/test/Fir/invalid.fir
index 2b63da1b8dd28..df1f1ae1549d8 100644
--- a/flang/test/Fir/invalid.fir
+++ b/flang/test/Fir/invalid.fir
@@ -372,6 +372,38 @@ func.func @embox_tdesc(%arg0: !fir.class<!fir.array<10x!fir.type<derived{a:i32,b
 
 // -----
 
+func.func @create_box_rank_mismatch(%arg0: !fir.ref<!fir.array<?xf32>>, %lb0: index, %ext0: index, %sm0: index, %lb1: index, %ext1: index, %sm1: index) {
+  // expected-error at +1 {{'fir.create_box' op memref rank does not match result box rank}}
+  %0 = fir.create_box %arg0 lbs(%lb0, %lb1) extents(%ext0, %ext1) strides(%sm0, %sm1) : (!fir.ref<!fir.array<?xf32>>, index, index, index, index, index, index) -> !fir.box<!fir.array<?x?xf32>>
+  return
+}
+
+// -----
+
+func.func @create_box_operand_count(%arg0: !fir.ref<!fir.array<?x?xf32>>, %lb: index, %ext: index, %sm: index) {
+  // expected-error at +1 {{'fir.create_box' op expected 2 values for each of lbounds, extents, and strides}}
+  %0 = fir.create_box %arg0 lbs(%lb) extents(%ext, %ext) strides(%sm, %sm) : (!fir.ref<!fir.array<?x?xf32>>, index, index, index, index, index) -> !fir.box<!fir.array<?x?xf32>>
+  return
+}
+
+// -----
+
+func.func @create_box_dynamic_char(%arg0: !fir.ref<!fir.array<?x!fir.char<1,?>>>, %lb: index, %ext: index, %sm: index) {
+  // expected-error at +1 {{'fir.create_box' op result box element type must be statically sized}}
+  %0 = fir.create_box %arg0 lbs(%lb) extents(%ext) strides(%sm) : (!fir.ref<!fir.array<?x!fir.char<1,?>>>, index, index, index) -> !fir.box<!fir.array<?x!fir.char<1,?>>>
+  return
+}
+
+// -----
+
+func.func @create_box_storage_kind(%arg0: !fir.heap<!fir.array<?xf32>>, %lb: index, %ext: index, %sm: index) {
+  // expected-error at +1 {{'fir.create_box' op result box element storage kind must match the memref storage kind}}
+  %0 = fir.create_box %arg0 lbs(%lb) extents(%ext) strides(%sm) : (!fir.heap<!fir.array<?xf32>>, index, index, index) -> !fir.box<!fir.array<?xf32>>
+  return
+}
+
+// -----
+
 %lo = arith.constant 1 : index
 %c1 = arith.constant 1 : index
 %up = arith.constant 10 : index

>From 89645ccdcf2b093db326e79230a3c99c7c0f1483 Mon Sep 17 00:00:00 2001
From: Slava Zakharin <szakharin at nvidia.com>
Date: Fri, 17 Jul 2026 08:48:03 -0700
Subject: [PATCH 2/2] Made it Pure + volatility verification.

---
 flang/include/flang/Optimizer/Dialect/FIROps.td | 2 +-
 flang/lib/Optimizer/Dialect/FIROps.cpp          | 5 +++++
 flang/test/Fir/invalid.fir                      | 8 ++++++++
 3 files changed, 14 insertions(+), 1 deletion(-)

diff --git a/flang/include/flang/Optimizer/Dialect/FIROps.td b/flang/include/flang/Optimizer/Dialect/FIROps.td
index 367f69cf43aa3..df7fdb27586d9 100644
--- a/flang/include/flang/Optimizer/Dialect/FIROps.td
+++ b/flang/include/flang/Optimizer/Dialect/FIROps.td
@@ -1019,7 +1019,7 @@ def fir_ReboxOp : fir_Op<"rebox", [NoMemoryEffect, AttrSizedOperandSegments,
   }];
 }
 
-def fir_CreateBoxOp : fir_Op<"create_box", [NoMemoryEffect,
+def fir_CreateBoxOp : fir_Op<"create_box", [Pure,
                                             AttrSizedOperandSegments,
                                             fir_FortranObjectViewOpInterface]> {
   let summary = "create a box from a base pointer and explicit byte strides";
diff --git a/flang/lib/Optimizer/Dialect/FIROps.cpp b/flang/lib/Optimizer/Dialect/FIROps.cpp
index 24c0b065e3e03..38c662fb23a99 100644
--- a/flang/lib/Optimizer/Dialect/FIROps.cpp
+++ b/flang/lib/Optimizer/Dialect/FIROps.cpp
@@ -2874,6 +2874,11 @@ llvm::LogicalResult fir::CreateBoxOp::verify() {
   if (fir::characterWithDynamicLen(eleTy) ||
       fir::isRecordWithTypeParameters(eleTy))
     return emitOpError("result box element type must be statically sized");
+  if (failed(verifyEmboxOpVolatilityInvariants(getMemref().getType(),
+                                               getResult().getType())))
+    return emitOpError(
+               "cannot convert between volatile and non-volatile types:")
+           << " " << getMemref().getType() << " " << getResult().getType();
   return mlir::success();
 }
 
diff --git a/flang/test/Fir/invalid.fir b/flang/test/Fir/invalid.fir
index df1f1ae1549d8..92280768e7154 100644
--- a/flang/test/Fir/invalid.fir
+++ b/flang/test/Fir/invalid.fir
@@ -404,6 +404,14 @@ func.func @create_box_storage_kind(%arg0: !fir.heap<!fir.array<?xf32>>, %lb: ind
 
 // -----
 
+func.func @create_box_volatile_mismatch(%arg0: !fir.ref<!fir.array<?xf32>, volatile>, %lb: index, %ext: index, %sm: index) {
+  // expected-error at +1 {{'fir.create_box' op cannot convert between volatile and non-volatile types}}
+  %0 = fir.create_box %arg0 lbs(%lb) extents(%ext) strides(%sm) : (!fir.ref<!fir.array<?xf32>, volatile>, index, index, index) -> !fir.box<!fir.array<?xf32>>
+  return
+}
+
+// -----
+
 %lo = arith.constant 1 : index
 %c1 = arith.constant 1 : index
 %up = arith.constant 10 : index



More information about the flang-commits mailing list