Skip to content

Commit 0c9a023

Browse files
[flang][fir] always use memcpy for fir.box (#113949)
@jeanPerier explained the importance of converting box loads and stores into `memcpy`s instead of aggregate loads and stores, and I'll do my best to explain it here. * [(godbolt link) Example comparing opt transformations on memcpys vs aggregate load/stores](https://godbolt.org/z/be7xM83cG) * LLVM can more effectively reason about memcpys compared to aggregate load/stores. * This came up when others were discussing array descriptors for assumed-rank arrays passed to `bind(c)` subroutines, with the implication that the array descriptors are known to have lower bounds of 1 and that they are not pointer/allocatable types. * [(godbolt link) Clang also uses memcpys so we should probably follow them, assuming the clang developers are generatign what they know Opt will handle more effectively.](https://godbolt.org/z/YT4x7387W) * This currently may not help much without the `nocapture` attribute being propagated to function calls, but [it looks like someone may do this soon (discourse link)](https://discourse.llvm.org/t/applying-the-nocapture-attribute-to-reference-passed-arguments-in-fortran-subroutines/81401/23) or I can do this in a follow-up patch. Note on test `flang/test/Fir/embox-char.fir`: it looks like the original test was auto-generated. I wasn't too sure which parts were especially important to test, so I regenerated the test. If we want the updated version to look more like the old version, I'll make those changes.
1 parent 4927725 commit 0c9a023

11 files changed

+188
-205
lines changed

Diff for: flang/lib/Optimizer/CodeGen/CodeGen.cpp

+20-39
Original file line numberDiff line numberDiff line change
@@ -2949,9 +2949,10 @@ struct LoadOpConversion : public fir::FIROpConversion<fir::LoadOp> {
29492949
llvm::LogicalResult
29502950
matchAndRewrite(fir::LoadOp load, OpAdaptor adaptor,
29512951
mlir::ConversionPatternRewriter &rewriter) const override {
2952+
29522953
mlir::Type llvmLoadTy = convertObjectType(load.getType());
29532954
if (auto boxTy = mlir::dyn_cast<fir::BaseBoxType>(load.getType())) {
2954-
// fir.box is a special case because it is considered as an ssa values in
2955+
// fir.box is a special case because it is considered an ssa value in
29552956
// fir, but it is lowered as a pointer to a descriptor. So
29562957
// fir.ref<fir.box> and fir.box end up being the same llvm types and
29572958
// loading a fir.ref<fir.box> is implemented as taking a snapshot of the
@@ -2960,30 +2961,17 @@ struct LoadOpConversion : public fir::FIROpConversion<fir::LoadOp> {
29602961
mlir::Location loc = load.getLoc();
29612962
auto newBoxStorage =
29622963
genAllocaAndAddrCastWithType(loc, llvmLoadTy, defaultAlign, rewriter);
2963-
// TODO: always generate llvm.memcpy, LLVM is better at optimizing it than
2964-
// aggregate loads + stores.
2965-
if (boxTy.isAssumedRank()) {
2966-
2967-
TypePair boxTypePair{boxTy, llvmLoadTy};
2968-
mlir::Value boxSize =
2969-
computeBoxSize(loc, boxTypePair, inputBoxStorage, rewriter);
2970-
auto memcpy = rewriter.create<mlir::LLVM::MemcpyOp>(
2971-
loc, newBoxStorage, inputBoxStorage, boxSize, /*isVolatile=*/false);
2972-
if (std::optional<mlir::ArrayAttr> optionalTag = load.getTbaa())
2973-
memcpy.setTBAATags(*optionalTag);
2974-
else
2975-
attachTBAATag(memcpy, boxTy, boxTy, nullptr);
2976-
} else {
2977-
auto boxValue = rewriter.create<mlir::LLVM::LoadOp>(loc, llvmLoadTy,
2978-
inputBoxStorage);
2979-
if (std::optional<mlir::ArrayAttr> optionalTag = load.getTbaa())
2980-
boxValue.setTBAATags(*optionalTag);
2981-
else
2982-
attachTBAATag(boxValue, boxTy, boxTy, nullptr);
2983-
auto storeOp =
2984-
rewriter.create<mlir::LLVM::StoreOp>(loc, boxValue, newBoxStorage);
2985-
attachTBAATag(storeOp, boxTy, boxTy, nullptr);
2986-
}
2964+
2965+
TypePair boxTypePair{boxTy, llvmLoadTy};
2966+
mlir::Value boxSize =
2967+
computeBoxSize(loc, boxTypePair, inputBoxStorage, rewriter);
2968+
auto memcpy = rewriter.create<mlir::LLVM::MemcpyOp>(
2969+
loc, newBoxStorage, inputBoxStorage, boxSize, /*isVolatile=*/false);
2970+
2971+
if (std::optional<mlir::ArrayAttr> optionalTag = load.getTbaa())
2972+
memcpy.setTBAATags(*optionalTag);
2973+
else
2974+
attachTBAATag(memcpy, boxTy, boxTy, nullptr);
29872975
rewriter.replaceOp(load, newBoxStorage);
29882976
} else {
29892977
auto loadOp = rewriter.create<mlir::LLVM::LoadOp>(
@@ -3227,20 +3215,13 @@ struct StoreOpConversion : public fir::FIROpConversion<fir::StoreOp> {
32273215
mlir::LLVM::AliasAnalysisOpInterface newOp;
32283216
if (auto boxTy = mlir::dyn_cast<fir::BaseBoxType>(storeTy)) {
32293217
mlir::Type llvmBoxTy = lowerTy().convertBoxTypeAsStruct(boxTy);
3230-
// fir.box value is actually in memory, load it first before storing it,
3231-
// or do a memcopy for assumed-rank descriptors.
3232-
if (boxTy.isAssumedRank()) {
3233-
TypePair boxTypePair{boxTy, llvmBoxTy};
3234-
mlir::Value boxSize =
3235-
computeBoxSize(loc, boxTypePair, llvmValue, rewriter);
3236-
newOp = rewriter.create<mlir::LLVM::MemcpyOp>(
3237-
loc, llvmMemref, llvmValue, boxSize, /*isVolatile=*/false);
3238-
} else {
3239-
auto val =
3240-
rewriter.create<mlir::LLVM::LoadOp>(loc, llvmBoxTy, llvmValue);
3241-
attachTBAATag(val, boxTy, boxTy, nullptr);
3242-
newOp = rewriter.create<mlir::LLVM::StoreOp>(loc, val, llvmMemref);
3243-
}
3218+
// Always use memcpy because LLVM is not as effective at optimizing
3219+
// aggregate loads/stores as it is optimizing memcpy.
3220+
TypePair boxTypePair{boxTy, llvmBoxTy};
3221+
mlir::Value boxSize =
3222+
computeBoxSize(loc, boxTypePair, llvmValue, rewriter);
3223+
newOp = rewriter.create<mlir::LLVM::MemcpyOp>(
3224+
loc, llvmMemref, llvmValue, boxSize, /*isVolatile=*/false);
32443225
} else {
32453226
newOp = rewriter.create<mlir::LLVM::StoreOp>(loc, llvmValue, llvmMemref);
32463227
}

Diff for: flang/test/Fir/box.fir

+13-6
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,14 @@ func.func @fa(%a : !fir.ref<!fir.array<100xf32>>) {
5656
// CHECK-LABEL: define void @b1(
5757
// CHECK-SAME: ptr %[[res:.*]], ptr %[[arg0:.*]], i64 %[[arg1:.*]])
5858
func.func @b1(%arg0 : !fir.ref<!fir.char<1,?>>, %arg1 : index) -> !fir.box<!fir.char<1,?>> {
59+
// CHECK: %[[alloca:.*]] = alloca { ptr, i64, i32, i8, i8, i8, i8 }
5960
// CHECK: %[[size:.*]] = mul i64 ptrtoint (ptr getelementptr (i8, ptr null, i32 1) to i64), %[[arg1]]
6061
// CHECK: insertvalue {{.*}} undef, i64 %[[size]], 1
6162
// CHECK: insertvalue {{.*}} i32 20240719, 2
6263
// CHECK: insertvalue {{.*}} ptr %[[arg0]], 0
6364
%x = fir.embox %arg0 typeparams %arg1 : (!fir.ref<!fir.char<1,?>>, index) -> !fir.box<!fir.char<1,?>>
64-
// CHECK: store {{.*}}, ptr %[[res]]
65+
// CHECK: store {{.*}}, ptr %[[alloca]]
66+
// CHECK: call void @llvm.memcpy.p0.p0.i32(ptr %[[res]], ptr %[[alloca]], i32 24, i1 false)
6567
return %x : !fir.box<!fir.char<1,?>>
6668
}
6769

@@ -71,11 +73,13 @@ func.func @b1(%arg0 : !fir.ref<!fir.char<1,?>>, %arg1 : index) -> !fir.box<!fir.
7173
// CHECK-SAME: ptr %[[arg0:.*]], i64 %[[arg1:.*]])
7274
func.func @b2(%arg0 : !fir.ref<!fir.array<?x!fir.char<1,5>>>, %arg1 : index) -> !fir.box<!fir.array<?x!fir.char<1,5>>> {
7375
%1 = fir.shape %arg1 : (index) -> !fir.shape<1>
76+
// CHECK: %[[alloca:.*]] = alloca { ptr, i64, i32, i8, i8, i8, i8, [1 x [3 x i64]] }
7477
// CHECK: insertvalue {{.*}} { ptr undef, i64 ptrtoint (ptr getelementptr ([5 x i8], ptr null, i32 1) to i64), i32 20240719, i8 1, i8 40, i8 0, i8 0, {{.*}} }, i64 %[[arg1]], 7, 0, 1
7578
// CHECK: insertvalue {{.*}} %{{.*}}, i64 ptrtoint (ptr getelementptr ([5 x i8], ptr null, i32 1) to i64), 7, 0, 2
7679
// CHECK: insertvalue {{.*}} ptr %[[arg0]], 0
7780
%2 = fir.embox %arg0(%1) : (!fir.ref<!fir.array<?x!fir.char<1,5>>>, !fir.shape<1>) -> !fir.box<!fir.array<?x!fir.char<1,5>>>
78-
// CHECK: store {{.*}}, ptr %[[res]]
81+
// CHECK: store {{.*}}, ptr %[[alloca]]
82+
// CHECK: call void @llvm.memcpy.p0.p0.i32(ptr %[[res]], ptr %[[alloca]], i32 48, i1 false)
7983
return %2 : !fir.box<!fir.array<?x!fir.char<1,5>>>
8084
}
8185

@@ -84,14 +88,16 @@ func.func @b2(%arg0 : !fir.ref<!fir.array<?x!fir.char<1,5>>>, %arg1 : index) ->
8488
// CHECK-SAME: ptr %[[res:.*]], ptr %[[arg0:.*]], i64 %[[arg1:.*]], i64 %[[arg2:.*]])
8589
func.func @b3(%arg0 : !fir.ref<!fir.array<?x!fir.char<1,?>>>, %arg1 : index, %arg2 : index) -> !fir.box<!fir.array<?x!fir.char<1,?>>> {
8690
%1 = fir.shape %arg2 : (index) -> !fir.shape<1>
91+
// CHECK: %[[alloca:.*]] = alloca { ptr, i64, i32, i8, i8, i8, i8, [1 x [3 x i64]] }
8792
// CHECK: %[[size:.*]] = mul i64 ptrtoint (ptr getelementptr (i8, ptr null, i32 1) to i64), %[[arg1]]
8893
// CHECK: insertvalue {{.*}} i64 %[[size]], 1
8994
// CHECK: insertvalue {{.*}} i32 20240719, 2
9095
// CHECK: insertvalue {{.*}} i64 %[[arg2]], 7, 0, 1
9196
// CHECK: insertvalue {{.*}} i64 %[[size]], 7, 0, 2
9297
// CHECK: insertvalue {{.*}} ptr %[[arg0]], 0
9398
%2 = fir.embox %arg0(%1) typeparams %arg1 : (!fir.ref<!fir.array<?x!fir.char<1,?>>>, !fir.shape<1>, index) -> !fir.box<!fir.array<?x!fir.char<1,?>>>
94-
// CHECK: store {{.*}}, ptr %[[res]]
99+
// CHECK: store {{.*}}, ptr %[[alloca]]
100+
// CHECK: call void @llvm.memcpy.p0.p0.i32(ptr %[[res]], ptr %[[alloca]], i32 48, i1 false)
95101
return %2 : !fir.box<!fir.array<?x!fir.char<1,?>>>
96102
}
97103

@@ -101,14 +107,16 @@ func.func @b3(%arg0 : !fir.ref<!fir.array<?x!fir.char<1,?>>>, %arg1 : index, %ar
101107
func.func @b4(%arg0 : !fir.ref<!fir.array<7x!fir.char<1,?>>>, %arg1 : index) -> !fir.box<!fir.array<7x!fir.char<1,?>>> {
102108
%c_7 = arith.constant 7 : index
103109
%1 = fir.shape %c_7 : (index) -> !fir.shape<1>
110+
// CHECK: %[[alloca:.*]] = alloca { ptr, i64, i32, i8, i8, i8, i8, [1 x [3 x i64]] }
104111
// CHECK: %[[size:.*]] = mul i64 ptrtoint (ptr getelementptr (i8, ptr null, i32 1) to i64), %[[arg1]]
105112
// CHECK: insertvalue {{.*}} i64 %[[size]], 1
106113
// CHECK: insertvalue {{.*}} i32 20240719, 2
107114
// CHECK: insertvalue {{.*}} i64 7, 7, 0, 1
108115
// CHECK: insertvalue {{.*}} i64 %[[size]], 7, 0, 2
109116
// CHECK: insertvalue {{.*}} ptr %[[arg0]], 0
110117
%x = fir.embox %arg0(%1) typeparams %arg1 : (!fir.ref<!fir.array<7x!fir.char<1,?>>>, !fir.shape<1>, index) -> !fir.box<!fir.array<7x!fir.char<1,?>>>
111-
// CHECK: store {{.*}}, ptr %[[res]]
118+
// CHECK: store {{.*}}, ptr %[[alloca]]
119+
// CHECK: call void @llvm.memcpy.p0.p0.i32(ptr %[[res]], ptr %[[alloca]], i32 48, i1 false)
112120
return %x : !fir.box<!fir.array<7x!fir.char<1,?>>>
113121
}
114122

@@ -117,8 +125,7 @@ func.func @b4(%arg0 : !fir.ref<!fir.array<7x!fir.char<1,?>>>, %arg1 : index) ->
117125
// CHECK-SAME: ptr %[[arg0:.*]], ptr %[[arg1:.*]])
118126
func.func @b5(%arg0 : !fir.ref<!fir.box<!fir.heap<!fir.array<?x?xf32>>>>, %arg1 : !fir.box<!fir.heap<!fir.array<?x?xf32>>>) {
119127
fir.store %arg1 to %arg0 : !fir.ref<!fir.box<!fir.heap<!fir.array<?x?xf32>>>>
120-
// CHECK: %[[boxLoad:.*]] = load { ptr, i64, i32, i8, i8, i8, i8, [2 x [3 x i64]] }, ptr %[[arg1]]
121-
// CHECK: store { ptr, i64, i32, i8, i8, i8, i8, [2 x [3 x i64]] } %[[boxLoad]], ptr %[[arg0]]
128+
// CHECK: call void @llvm.memcpy.p0.p0.i32(ptr %0, ptr %1, i32 72, i1 false)
122129
return
123130
}
124131

Diff for: flang/test/Fir/convert-to-llvm-openmp-and-fir.fir

+2-2
Original file line numberDiff line numberDiff line change
@@ -799,8 +799,8 @@ func.func @_QPs(%arg0: !fir.ref<complex<f32>> {fir.bindc_name = "x"}) {
799799
//CHECK: omp.parallel {
800800
//CHECK: %[[CONST_1:.*]] = llvm.mlir.constant(1 : i32) : i32
801801
//CHECK: %[[ALLOCA_1:.*]] = llvm.alloca %[[CONST_1:.*]] x !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8)> {alignment = 8 : i64} : (i32) -> !llvm.ptr
802-
//CHECK: %[[LOAD:.*]] = llvm.load %[[ALLOCA]] : !llvm.ptr -> !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8)>
803-
//CHECK: llvm.store %[[LOAD]], %[[ALLOCA_1]] : !llvm.struct<(ptr, i64, i32, i8, i8, i8, i8)>, !llvm.ptr
802+
//CHECK: %[[SIZE:.*]] = llvm.mlir.constant(24 : i32) : i32
803+
//CHECK: "llvm.intr.memcpy"(%[[ALLOCA_1]], %[[ALLOCA]], %[[SIZE]]) <{isVolatile = false}> : (!llvm.ptr, !llvm.ptr, i32) -> ()
804804
//CHECK: %[[GEP:.*]] = llvm.getelementptr %[[ALLOCA_1]][0, 0] : (!llvm.ptr) -> !llvm.ptr
805805
//CHECK: %[[LOAD_2:.*]] = llvm.load %[[GEP]] : !llvm.ptr -> !llvm.ptr
806806
//CHECK: omp.terminator

Diff for: flang/test/Fir/convert-to-llvm.fir

+15-13
Original file line numberDiff line numberDiff line change
@@ -862,8 +862,8 @@ func.func @test_store_box(%array : !fir.ref<!fir.box<!fir.array<?x?xf32>>>, %box
862862
// CHECK-LABEL: llvm.func @test_store_box
863863
// CHECK-SAME: (%[[arg0:.*]]: !llvm.ptr,
864864
// CHECK-SAME: %[[arg1:.*]]: !llvm.ptr) {
865-
// CHECK-NEXT: %[[box_to_store:.*]] = llvm.load %arg1 : !llvm.ptr -> !llvm.struct<(ptr, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, array<2 x array<3 x i{{.*}}>>)>
866-
// CHECK-NEXT: llvm.store %[[box_to_store]], %[[arg0]] : !llvm.struct<(ptr, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, array<2 x array<3 x i{{.*}}>>)>, !llvm.ptr
865+
// CHECK-NEXT: %[[size:.*]] = llvm.mlir.constant(72 : i32) : i32
866+
// CHECK-NEXT: "llvm.intr.memcpy"(%[[arg0]], %[[arg1]], %[[size]]) <{isVolatile = false}> : (!llvm.ptr, !llvm.ptr, i32) -> ()
867867
// CHECK-NEXT: llvm.return
868868
// CHECK-NEXT: }
869869

@@ -875,15 +875,17 @@ func.func @store_unlimited_polymorphic_box(%arg0 : !fir.class<none>, %arg1 : !fi
875875
fir.store %arg3 to %arg3r : !fir.ref<!fir.box<!fir.array<?xnone>>>
876876
return
877877
}
878-
// CHECK-LABEL: llvm.func @store_unlimited_polymorphic_box(
879-
// CHECK: %[[VAL_8:.*]] = llvm.load %{{.*}} : !llvm.ptr -> !llvm.struct<(ptr, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, ptr, array<1 x i{{.*}}>)>
880-
// CHECK: llvm.store %[[VAL_8]], %{{.*}} : !llvm.struct<(ptr, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, ptr, array<1 x i{{.*}}>)>, !llvm.ptr
881-
// CHECK: %[[VAL_9:.*]] = llvm.load %{{.*}} : !llvm.ptr -> !llvm.struct<(ptr, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, array<1 x array<3 x i{{.*}}>>, ptr, array<1 x i{{.*}}>)>
882-
// CHECK: llvm.store %[[VAL_9]], %{{.*}} : !llvm.struct<(ptr, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, array<1 x array<3 x i{{.*}}>>, ptr, array<1 x i{{.*}}>)>, !llvm.ptr
883-
// CHECK: %[[VAL_10:.*]] = llvm.load %{{.*}} : !llvm.ptr -> !llvm.struct<(ptr, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, ptr, array<1 x i{{.*}}>)>
884-
// CHECK: llvm.store %[[VAL_10]], %{{.*}} : !llvm.struct<(ptr, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, ptr, array<1 x i{{.*}}>)>, !llvm.ptr
885-
// CHECK: %[[VAL_11:.*]] = llvm.load %{{.*}}: !llvm.ptr
886-
// CHECK: llvm.store %[[VAL_11]], %{{.*}} : !llvm.struct<(ptr, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, array<1 x array<3 x i{{.*}}>>, ptr, array<1 x i{{.*}}>)>, !llvm.ptr
878+
// CHECK: llvm.func @store_unlimited_polymorphic_box(%[[VAL_0:.*]]: !llvm.ptr, %[[VAL_1:.*]]: !llvm.ptr, %[[VAL_2:.*]]: !llvm.ptr, %[[VAL_3:.*]]: !llvm.ptr, %[[VAL_4:.*]]: !llvm.ptr, %[[VAL_5:.*]]: !llvm.ptr, %[[VAL_6:.*]]: !llvm.ptr, %[[VAL_7:.*]]: !llvm.ptr) {
879+
// CHECK: %[[VAL_8:.*]] = llvm.mlir.constant(40 : i32) : i32
880+
// CHECK: "llvm.intr.memcpy"(%[[VAL_4]], %[[VAL_0]], %[[VAL_8]]) <{isVolatile = false}> : (!llvm.ptr, !llvm.ptr, i32) -> ()
881+
// CHECK: %[[VAL_9:.*]] = llvm.mlir.constant(64 : i32) : i32
882+
// CHECK: "llvm.intr.memcpy"(%[[VAL_5]], %[[VAL_1]], %[[VAL_9]]) <{isVolatile = false}> : (!llvm.ptr, !llvm.ptr, i32) -> ()
883+
// CHECK: %[[VAL_10:.*]] = llvm.mlir.constant(40 : i32) : i32
884+
// CHECK: "llvm.intr.memcpy"(%[[VAL_6]], %[[VAL_2]], %[[VAL_10]]) <{isVolatile = false}> : (!llvm.ptr, !llvm.ptr, i32) -> ()
885+
// CHECK: %[[VAL_11:.*]] = llvm.mlir.constant(64 : i32) : i32
886+
// CHECK: "llvm.intr.memcpy"(%[[VAL_7]], %[[VAL_3]], %[[VAL_11]]) <{isVolatile = false}> : (!llvm.ptr, !llvm.ptr, i32) -> ()
887+
// CHECK: llvm.return
888+
// CHECK: }
887889

888890

889891
// -----
@@ -935,8 +937,8 @@ func.func @test_load_box(%addr : !fir.ref<!fir.box<!fir.array<10xf32>>>) {
935937
// GENERIC-NEXT: %[[box_copy:.*]] = llvm.alloca %[[c1]] x !llvm.struct<([[DESC_TYPE:.*]])>
936938
// AMDGPU-NEXT: %[[alloca_box_copy:.*]] = llvm.alloca %[[c1]] x !llvm.struct<([[DESC_TYPE:.*]])>{{.*}} : (i32) -> !llvm.ptr<5>
937939
// AMDGPU-NEXT: %[[box_copy:.*]] = llvm.addrspacecast %[[alloca_box_copy]] : !llvm.ptr<5> to !llvm.ptr
938-
// CHECK-NEXT: %[[box_val:.*]] = llvm.load %[[arg0]] : !llvm.ptr -> !llvm.struct<([[DESC_TYPE]])>
939-
// CHECK-NEXT: llvm.store %[[box_val]], %[[box_copy]] : !llvm.struct<([[DESC_TYPE]])>, !llvm.ptr
940+
// CHECK-NEXT: %[[size:.*]] = llvm.mlir.constant(48 : i32) : i32
941+
// CHECK-NEXT: "llvm.intr.memcpy"(%[[box_copy]], %[[arg0]], %[[size]]) <{isVolatile = false}> : (!llvm.ptr, !llvm.ptr, i32) -> ()
940942
// CHECK-NEXT: llvm.call @takes_box(%[[box_copy]]) : (!llvm.ptr) -> ()
941943
// CHECK-NEXT: llvm.return
942944
// CHECK-NEXT: }

0 commit comments

Comments
 (0)