[flang-commits] [flang] d63bd84 - [flang] Added fir.create_box operation. (#210220)
via flang-commits
flang-commits at lists.llvm.org
Fri Jul 17 09:58:46 PDT 2026
Author: Slava Zakharin
Date: 2026-07-17T09:58:41-07:00
New Revision: d63bd842939f22fad4e29660d66b99a9bf94e4b4
URL: https://github.com/llvm/llvm-project/commit/d63bd842939f22fad4e29660d66b99a9bf94e4b4
DIFF: https://github.com/llvm/llvm-project/commit/d63bd842939f22fad4e29660d66b99a9bf94e4b4.diff
LOG: [flang] Added fir.create_box operation. (#210220)
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.
Added:
flang/test/Fir/create-box-codegen.fir
Modified:
flang/include/flang/Optimizer/Dialect/FIROps.td
flang/lib/Optimizer/CodeGen/CodeGen.cpp
flang/lib/Optimizer/Dialect/FIROps.cpp
flang/test/Fir/fir-ops.fir
flang/test/Fir/invalid.fir
Removed:
################################################################################
diff --git a/flang/include/flang/Optimizer/Dialect/FIROps.td b/flang/include/flang/Optimizer/Dialect/FIROps.td
index 8ecdd5ec4ac1c..df7fdb27586d9 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", [Pure,
+ 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..38c662fb23a99 100644
--- a/flang/lib/Optimizer/Dialect/FIROps.cpp
+++ b/flang/lib/Optimizer/Dialect/FIROps.cpp
@@ -2821,6 +2821,73 @@ 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");
+ if (failed(verifyEmboxOpVolatilityInvariants(getMemref().getType(),
+ getResult().getType())))
+ return emitOpError(
+ "cannot convert between volatile and non-volatile types:")
+ << " " << getMemref().getType() << " " << getResult().getType();
+ 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..92280768e7154 100644
--- a/flang/test/Fir/invalid.fir
+++ b/flang/test/Fir/invalid.fir
@@ -372,6 +372,46 @@ 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
+}
+
+// -----
+
+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