Skip to content

Commit 4fcbdb2

Browse files
authored
[CIR] Upstream local initialization for VectorType (#138107)
This change adds local initialization for VectorType Issue #136487
1 parent 55e3910 commit 4fcbdb2

File tree

11 files changed

+253
-5
lines changed

11 files changed

+253
-5
lines changed

clang/include/clang/CIR/Dialect/IR/CIROps.td

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1952,4 +1952,28 @@ def TrapOp : CIR_Op<"trap", [Terminator]> {
19521952
let assemblyFormat = "attr-dict";
19531953
}
19541954

1955+
//===----------------------------------------------------------------------===//
1956+
// VecCreate
1957+
//===----------------------------------------------------------------------===//
1958+
1959+
def VecCreateOp : CIR_Op<"vec.create", [Pure]> {
1960+
1961+
let summary = "Create a vector value";
1962+
let description = [{
1963+
The `cir.vec.create` operation creates a vector value with the given element
1964+
values. The number of element arguments must match the number of elements
1965+
in the vector type.
1966+
}];
1967+
1968+
let arguments = (ins Variadic<CIR_AnyType>:$elements);
1969+
let results = (outs CIR_VectorType:$result);
1970+
1971+
let assemblyFormat = [{
1972+
`(` ($elements^ `:` type($elements))? `)` `:` qualified(type($result))
1973+
attr-dict
1974+
}];
1975+
1976+
let hasVerifier = 1;
1977+
}
1978+
19551979
#endif // CLANG_CIR_DIALECT_IR_CIROPS_TD

clang/lib/CIR/CodeGen/CIRGenExpr.cpp

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -258,9 +258,20 @@ void CIRGenFunction::emitStoreOfScalar(mlir::Value value, Address addr,
258258
bool isInit, bool isNontemporal) {
259259
assert(!cir::MissingFeatures::opLoadStoreThreadLocal());
260260

261-
if (ty->getAs<clang::VectorType>()) {
262-
cgm.errorNYI(addr.getPointer().getLoc(), "emitStoreOfScalar vector type");
263-
return;
261+
if (const auto *clangVecTy = ty->getAs<clang::VectorType>()) {
262+
// Boolean vectors use `iN` as storage type.
263+
if (clangVecTy->isExtVectorBoolType())
264+
cgm.errorNYI(addr.getPointer().getLoc(),
265+
"emitStoreOfScalar ExtVectorBoolType");
266+
267+
// Handle vectors of size 3 like size 4 for better performance.
268+
const mlir::Type elementType = addr.getElementType();
269+
const auto vecTy = cast<cir::VectorType>(elementType);
270+
271+
// TODO(CIR): Use `ABIInfo::getOptimalVectorMemoryType` once it upstreamed
272+
if (vecTy.getSize() == 3 && !getLangOpts().PreserveVec3Type)
273+
cgm.errorNYI(addr.getPointer().getLoc(),
274+
"emitStoreOfScalar Vec3 & PreserveVec3Type disabled");
264275
}
265276

266277
value = emitToMemory(value, ty);

clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,8 @@ class ScalarExprEmitter : public StmtVisitor<ScalarExprEmitter, mlir::Value> {
170170

171171
mlir::Value VisitMemberExpr(MemberExpr *e);
172172

173+
mlir::Value VisitInitListExpr(InitListExpr *e);
174+
173175
mlir::Value VisitExplicitCastExpr(ExplicitCastExpr *e) {
174176
return VisitCastExpr(e);
175177
}
@@ -1674,6 +1676,43 @@ mlir::Value ScalarExprEmitter::VisitMemberExpr(MemberExpr *e) {
16741676
return emitLoadOfLValue(e);
16751677
}
16761678

1679+
mlir::Value ScalarExprEmitter::VisitInitListExpr(InitListExpr *e) {
1680+
const unsigned numInitElements = e->getNumInits();
1681+
1682+
if (e->hadArrayRangeDesignator()) {
1683+
cgf.cgm.errorNYI(e->getSourceRange(), "ArrayRangeDesignator");
1684+
return {};
1685+
}
1686+
1687+
if (numInitElements == 0) {
1688+
cgf.cgm.errorNYI(e->getSourceRange(), "InitListExpr with 0 init elements");
1689+
return {};
1690+
}
1691+
1692+
if (e->getType()->isVectorType()) {
1693+
const auto vectorType =
1694+
mlir::cast<cir::VectorType>(cgf.convertType(e->getType()));
1695+
1696+
SmallVector<mlir::Value, 16> elements;
1697+
for (Expr *init : e->inits()) {
1698+
elements.push_back(Visit(init));
1699+
}
1700+
1701+
// Zero-initialize any remaining values.
1702+
if (numInitElements < vectorType.getSize()) {
1703+
const mlir::Value zeroValue = cgf.getBuilder().getNullValue(
1704+
vectorType.getElementType(), cgf.getLoc(e->getSourceRange()));
1705+
std::fill_n(std::back_inserter(elements),
1706+
vectorType.getSize() - numInitElements, zeroValue);
1707+
}
1708+
1709+
return cgf.getBuilder().create<cir::VecCreateOp>(
1710+
cgf.getLoc(e->getSourceRange()), vectorType, elements);
1711+
}
1712+
1713+
return Visit(e->getInit(0));
1714+
}
1715+
16771716
mlir::Value CIRGenFunction::emitScalarConversion(mlir::Value src,
16781717
QualType srcTy, QualType dstTy,
16791718
SourceLocation loc) {

clang/lib/CIR/Dialect/IR/CIRDialect.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1338,6 +1338,33 @@ LogicalResult cir::GetMemberOp::verify() {
13381338
return mlir::success();
13391339
}
13401340

1341+
//===----------------------------------------------------------------------===//
1342+
// VecCreateOp
1343+
//===----------------------------------------------------------------------===//
1344+
1345+
LogicalResult cir::VecCreateOp::verify() {
1346+
// Verify that the number of arguments matches the number of elements in the
1347+
// vector, and that the type of all the arguments matches the type of the
1348+
// elements in the vector.
1349+
const VectorType vecTy = getResult().getType();
1350+
if (getElements().size() != vecTy.getSize()) {
1351+
return emitOpError() << "operand count of " << getElements().size()
1352+
<< " doesn't match vector type " << vecTy
1353+
<< " element count of " << vecTy.getSize();
1354+
}
1355+
1356+
const mlir::Type elementType = vecTy.getElementType();
1357+
for (const mlir::Value element : getElements()) {
1358+
if (element.getType() != elementType) {
1359+
return emitOpError() << "operand type " << element.getType()
1360+
<< " doesn't match vector element type "
1361+
<< elementType;
1362+
}
1363+
}
1364+
1365+
return success();
1366+
}
1367+
13411368
//===----------------------------------------------------------------------===//
13421369
// TableGen'd op method definitions
13431370
//===----------------------------------------------------------------------===//

clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1599,7 +1599,8 @@ void ConvertCIRToLLVMPass::runOnOperation() {
15991599
CIRToLLVMStackSaveOpLowering,
16001600
CIRToLLVMStackRestoreOpLowering,
16011601
CIRToLLVMTrapOpLowering,
1602-
CIRToLLVMUnaryOpLowering
1602+
CIRToLLVMUnaryOpLowering,
1603+
CIRToLLVMVecCreateOpLowering
16031604
// clang-format on
16041605
>(converter, patterns.getContext());
16051606

@@ -1685,6 +1686,29 @@ mlir::LogicalResult CIRToLLVMStackRestoreOpLowering::matchAndRewrite(
16851686
return mlir::success();
16861687
}
16871688

1689+
mlir::LogicalResult CIRToLLVMVecCreateOpLowering::matchAndRewrite(
1690+
cir::VecCreateOp op, OpAdaptor adaptor,
1691+
mlir::ConversionPatternRewriter &rewriter) const {
1692+
// Start with an 'undef' value for the vector. Then 'insertelement' for
1693+
// each of the vector elements.
1694+
const auto vecTy = mlir::cast<cir::VectorType>(op.getType());
1695+
const mlir::Type llvmTy = typeConverter->convertType(vecTy);
1696+
const mlir::Location loc = op.getLoc();
1697+
mlir::Value result = rewriter.create<mlir::LLVM::PoisonOp>(loc, llvmTy);
1698+
assert(vecTy.getSize() == op.getElements().size() &&
1699+
"cir.vec.create op count doesn't match vector type elements count");
1700+
1701+
for (uint64_t i = 0; i < vecTy.getSize(); ++i) {
1702+
const mlir::Value indexValue =
1703+
rewriter.create<mlir::LLVM::ConstantOp>(loc, rewriter.getI64Type(), i);
1704+
result = rewriter.create<mlir::LLVM::InsertElementOp>(
1705+
loc, result, adaptor.getElements()[i], indexValue);
1706+
}
1707+
1708+
rewriter.replaceOp(op, result);
1709+
return mlir::success();
1710+
}
1711+
16881712
std::unique_ptr<mlir::Pass> createConvertCIRToLLVMPass() {
16891713
return std::make_unique<ConvertCIRToLLVMPass>();
16901714
}

clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,16 @@ class CIRToLLVMStackRestoreOpLowering
293293
mlir::ConversionPatternRewriter &rewriter) const override;
294294
};
295295

296+
class CIRToLLVMVecCreateOpLowering
297+
: public mlir::OpConversionPattern<cir::VecCreateOp> {
298+
public:
299+
using mlir::OpConversionPattern<cir::VecCreateOp>::OpConversionPattern;
300+
301+
mlir::LogicalResult
302+
matchAndRewrite(cir::VecCreateOp op, OpAdaptor,
303+
mlir::ConversionPatternRewriter &) const override;
304+
};
305+
296306
} // namespace direct
297307
} // namespace cir
298308

clang/test/CIR/CodeGen/vector-ext.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,27 +48,56 @@ vi4 vec_e = { 1, 2, 3, 4 };
4848

4949
// OGCG: @[[VEC_E:.*]] = global <4 x i32> <i32 1, i32 2, i32 3, i32 4>
5050

51+
int x = 5;
52+
5153
void foo() {
5254
vi4 a;
5355
vi3 b;
5456
vi2 c;
5557
vd2 d;
58+
59+
vi4 e = { 1, 2, 3, 4 };
60+
61+
vi4 f = { x, 5, 6, x + 1 };
62+
63+
vi4 g = { 5 };
5664
}
5765

5866
// CIR: %[[VEC_A:.*]] = cir.alloca !cir.vector<4 x !s32i>, !cir.ptr<!cir.vector<4 x !s32i>>, ["a"]
5967
// CIR: %[[VEC_B:.*]] = cir.alloca !cir.vector<3 x !s32i>, !cir.ptr<!cir.vector<3 x !s32i>>, ["b"]
6068
// CIR: %[[VEC_C:.*]] = cir.alloca !cir.vector<2 x !s32i>, !cir.ptr<!cir.vector<2 x !s32i>>, ["c"]
6169
// CIR: %[[VEC_D:.*]] = cir.alloca !cir.vector<2 x !cir.double>, !cir.ptr<!cir.vector<2 x !cir.double>>, ["d"]
70+
// CIR: %[[VEC_E:.*]] = cir.alloca !cir.vector<4 x !s32i>, !cir.ptr<!cir.vector<4 x !s32i>>, ["e", init]
71+
// CIR: %[[VEC_F:.*]] = cir.alloca !cir.vector<4 x !s32i>, !cir.ptr<!cir.vector<4 x !s32i>>, ["f", init]
72+
// CIR: %[[VEC_G:.*]] = cir.alloca !cir.vector<4 x !s32i>, !cir.ptr<!cir.vector<4 x !s32i>>, ["g", init]
73+
// CIR: %[[VEC_E_VAL:.*]] = cir.vec.create({{.*}}, {{.*}}, {{.*}}, {{.*}} : !s32i, !s32i, !s32i, !s32i) : !cir.vector<4 x !s32i>
74+
// CIR: cir.store %[[VEC_E_VAL]], %[[VEC_E]] : !cir.vector<4 x !s32i>, !cir.ptr<!cir.vector<4 x !s32i>>
75+
// CIR: %[[VEC_F_VAL:.*]] = cir.vec.create({{.*}}, {{.*}}, {{.*}}, {{.*}} : !s32i, !s32i, !s32i, !s32i) : !cir.vector<4 x !s32i>
76+
// CIR: cir.store %[[VEC_F_VAL]], %[[VEC_F]] : !cir.vector<4 x !s32i>, !cir.ptr<!cir.vector<4 x !s32i>>
77+
// CIR: %[[VEC_G_VAL:.*]] = cir.vec.create({{.*}}, {{.*}}, {{.*}}, {{.*}} : !s32i, !s32i, !s32i, !s32i) : !cir.vector<4 x !s32i>
78+
// CIR: cir.store %[[VEC_G_VAL]], %[[VEC_G]] : !cir.vector<4 x !s32i>, !cir.ptr<!cir.vector<4 x !s32i>>
6279

6380
// LLVM: %[[VEC_A:.*]] = alloca <4 x i32>, i64 1, align 16
6481
// LLVM: %[[VEC_B:.*]] = alloca <3 x i32>, i64 1, align 16
6582
// LLVM: %[[VEC_C:.*]] = alloca <2 x i32>, i64 1, align 8
6683
// LLVM: %[[VEC_D:.*]] = alloca <2 x double>, i64 1, align 16
84+
// LLVM: %[[VEC_E:.*]] = alloca <4 x i32>, i64 1, align 16
85+
// LLVM: %[[VEC_F:.*]] = alloca <4 x i32>, i64 1, align 16
86+
// LLVM: %[[VEC_G:.*]] = alloca <4 x i32>, i64 1, align 16
87+
// LLVM: store <4 x i32> <i32 1, i32 2, i32 3, i32 4>, ptr %[[VEC_E]], align 16
88+
// LLVM: store <4 x i32> {{.*}}, ptr %[[VEC_F]], align 16
89+
// LLVM: store <4 x i32> <i32 5, i32 0, i32 0, i32 0>, ptr %[[VEC_G]], align 16
6790

6891
// OGCG: %[[VEC_A:.*]] = alloca <4 x i32>, align 16
6992
// OGCG: %[[VEC_B:.*]] = alloca <3 x i32>, align 16
7093
// OGCG: %[[VEC_C:.*]] = alloca <2 x i32>, align 8
7194
// OGCG: %[[VEC_D:.*]] = alloca <2 x double>, align 16
95+
// OGCG: %[[VEC_E:.*]] = alloca <4 x i32>, align 16
96+
// OGCG: %[[VEC_F:.*]] = alloca <4 x i32>, align 16
97+
// OGCG: %[[VEC_G:.*]] = alloca <4 x i32>, align 16
98+
// OGCG: store <4 x i32> <i32 1, i32 2, i32 3, i32 4>, ptr %[[VEC_E]], align 16
99+
// OGCG: store <4 x i32> {{.*}}, ptr %[[VEC_F]], align 16
100+
// OGCG: store <4 x i32> <i32 5, i32 0, i32 0, i32 0>, ptr %[[VEC_G]], align 16
72101

73102
void foo2(vi4 p) {}
74103

clang/test/CIR/CodeGen/vector.cpp

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,23 +39,52 @@ vi4 d = { 1, 2, 3, 4 };
3939

4040
// OGCG: @[[VEC_D:.*]] = global <4 x i32> <i32 1, i32 2, i32 3, i32 4>
4141

42-
void vec_int_test() {
42+
int x = 5;
43+
44+
void foo() {
4345
vi4 a;
4446
vd2 b;
4547
vll2 c;
48+
49+
vi4 d = { 1, 2, 3, 4 };
50+
51+
vi4 e = { x, 5, 6, x + 1 };
52+
53+
vi4 f = { 5 };
4654
}
4755

4856
// CIR: %[[VEC_A:.*]] = cir.alloca !cir.vector<4 x !s32i>, !cir.ptr<!cir.vector<4 x !s32i>>, ["a"]
4957
// CIR: %[[VEC_B:.*]] = cir.alloca !cir.vector<2 x !cir.double>, !cir.ptr<!cir.vector<2 x !cir.double>>, ["b"]
5058
// CIR: %[[VEC_C:.*]] = cir.alloca !cir.vector<2 x !s64i>, !cir.ptr<!cir.vector<2 x !s64i>>, ["c"]
59+
// CIR: %[[VEC_D:.*]] = cir.alloca !cir.vector<4 x !s32i>, !cir.ptr<!cir.vector<4 x !s32i>>, ["d", init]
60+
// CIR: %[[VEC_E:.*]] = cir.alloca !cir.vector<4 x !s32i>, !cir.ptr<!cir.vector<4 x !s32i>>, ["e", init]
61+
// CIR: %[[VEC_F:.*]] = cir.alloca !cir.vector<4 x !s32i>, !cir.ptr<!cir.vector<4 x !s32i>>, ["f", init]
62+
// CIR: %[[VEC_D_VAL:.*]] = cir.vec.create({{.*}}, {{.*}}, {{.*}}, {{.*}} : !s32i, !s32i, !s32i, !s32i) : !cir.vector<4 x !s32i>
63+
// CIR: cir.store %[[VEC_D_VAL]], %[[VEC_D]] : !cir.vector<4 x !s32i>, !cir.ptr<!cir.vector<4 x !s32i>>
64+
// CIR: %[[VEC_E_VAL:.*]] = cir.vec.create({{.*}}, {{.*}}, {{.*}}, {{.*}} : !s32i, !s32i, !s32i, !s32i) : !cir.vector<4 x !s32i>
65+
// CIR: cir.store %[[VEC_E_VAL]], %[[VEC_E]] : !cir.vector<4 x !s32i>, !cir.ptr<!cir.vector<4 x !s32i>>
66+
// CIR: %[[VEC_F_VAL:.*]] = cir.vec.create({{.*}}, {{.*}}, {{.*}}, {{.*}} : !s32i, !s32i, !s32i, !s32i) : !cir.vector<4 x !s32i>
67+
// CIR: cir.store %[[VEC_F_VAL]], %[[VEC_F]] : !cir.vector<4 x !s32i>, !cir.ptr<!cir.vector<4 x !s32i>>
5168

5269
// LLVM: %[[VEC_A:.*]] = alloca <4 x i32>, i64 1, align 16
5370
// LLVM: %[[VEC_B:.*]] = alloca <2 x double>, i64 1, align 16
5471
// LLVM: %[[VEC_C:.*]] = alloca <2 x i64>, i64 1, align 16
72+
// LLVM: %[[VEC_D:.*]] = alloca <4 x i32>, i64 1, align 16
73+
// LLVM: %[[VEC_E:.*]] = alloca <4 x i32>, i64 1, align 16
74+
// LLVM: %[[VEC_F:.*]] = alloca <4 x i32>, i64 1, align 16
75+
// LLVM: store <4 x i32> <i32 1, i32 2, i32 3, i32 4>, ptr %[[VEC_D]], align 16
76+
// LLVM: store <4 x i32> {{.*}}, ptr %[[VEC_E]], align 16
77+
// LLVM: store <4 x i32> <i32 5, i32 0, i32 0, i32 0>, ptr %[[VEC_F]], align 16
5578

5679
// OGCG: %[[VEC_A:.*]] = alloca <4 x i32>, align 16
5780
// OGCG: %[[VEC_B:.*]] = alloca <2 x double>, align 16
5881
// OGCG: %[[VEC_C:.*]] = alloca <2 x i64>, align 16
82+
// OGCG: %[[VEC_D:.*]] = alloca <4 x i32>, align 16
83+
// OGCG: %[[VEC_E:.*]] = alloca <4 x i32>, align 16
84+
// OGCG: %[[VEC_F:.*]] = alloca <4 x i32>, align 16
85+
// OGCG: store <4 x i32> <i32 1, i32 2, i32 3, i32 4>, ptr %[[VEC_D]], align 16
86+
// OGCG: store <4 x i32> {{.*}}, ptr %[[VEC_E]], align 16
87+
// OGCG: store <4 x i32> <i32 5, i32 0, i32 0, i32 0>, ptr %[[VEC_F]], align 16
5988

6089
void foo2(vi4 p) {}
6190

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// RUN: cir-opt %s -verify-diagnostics -split-input-file
2+
3+
!s32i = !cir.int<s, 32>
4+
5+
module {
6+
cir.func @foo() {
7+
%1 = cir.const #cir.int<1> : !s32i
8+
%2 = cir.const #cir.int<2> : !s32i
9+
%3 = cir.const #cir.int<3> : !s32i
10+
%4 = cir.const #cir.int<4> : !s32i
11+
12+
// expected-error @below {{operand count of 4 doesn't match vector type '!cir.vector<8 x !cir.int<s, 32>>' element count of 8}}
13+
%5 = cir.vec.create(%1, %2, %3, %4 : !s32i, !s32i, !s32i, !s32i) : !cir.vector<8 x !s32i>
14+
cir.return
15+
}
16+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// RUN: cir-opt %s -verify-diagnostics -split-input-file
2+
3+
!s32i = !cir.int<s, 32>
4+
!s64i = !cir.int<s, 64>
5+
6+
module {
7+
cir.func @foo() {
8+
%1 = cir.const #cir.int<1> : !s32i
9+
%2 = cir.const #cir.int<2> : !s32i
10+
%3 = cir.const #cir.int<3> : !s32i
11+
%4 = cir.const #cir.int<4> : !s64i
12+
13+
// expected-error @below {{operand type '!cir.int<s, 64>' doesn't match vector element type '!cir.int<s, 32>'}}
14+
%5 = cir.vec.create(%1, %2, %3, %4 : !s32i, !s32i, !s32i, !s64i) : !cir.vector<4 x !s32i>
15+
cir.return
16+
}
17+
}

clang/test/CIR/IR/vector.cir

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,26 @@ cir.func @vec_double_test() {
4343
// CHECK: cir.return
4444
// CHECK: }
4545

46+
cir.func @local_vector_create_test() {
47+
%0 = cir.alloca !cir.vector<4 x !s32i>, !cir.ptr<!cir.vector<4 x !s32i>>, ["a", init]
48+
%1 = cir.const #cir.int<1> : !s32i
49+
%2 = cir.const #cir.int<2> : !s32i
50+
%3 = cir.const #cir.int<3> : !s32i
51+
%4 = cir.const #cir.int<4> : !s32i
52+
%5 = cir.vec.create(%1, %2, %3, %4 : !s32i, !s32i, !s32i, !s32i) : !cir.vector<4 x !s32i>
53+
cir.store %5, %0 : !cir.vector<4 x !s32i>, !cir.ptr<!cir.vector<4 x !s32i>>
54+
cir.return
55+
}
56+
57+
// CHECK: cir.func @local_vector_create_test() {
58+
// CHECK: %0 = cir.alloca !cir.vector<4 x !s32i>, !cir.ptr<!cir.vector<4 x !s32i>>, ["a", init]
59+
// CHECK: %1 = cir.const #cir.int<1> : !s32i
60+
// CHECK: %2 = cir.const #cir.int<2> : !s32i
61+
// CHECK: %3 = cir.const #cir.int<3> : !s32i
62+
// CHECK: %4 = cir.const #cir.int<4> : !s32i
63+
// CHECK: %5 = cir.vec.create(%1, %2, %3, %4 : !s32i, !s32i, !s32i, !s32i) : !cir.vector<4 x !s32i>
64+
// CHECK: cir.store %5, %0 : !cir.vector<4 x !s32i>, !cir.ptr<!cir.vector<4 x !s32i>>
65+
// CHECK: cir.return
66+
// CHECK: }
67+
4668
}

0 commit comments

Comments
 (0)