Skip to content

Commit 2abd6d6

Browse files
authored
[LV] Vectorize conditional scalar assignments (#158088)
Based on Michael Maitland's previous work: #121222 This PR uses the existing recurrences code instead of introducing a new pass just for CSA autovec. I've also made recipes that are more generic.
1 parent d2afc3e commit 2abd6d6

23 files changed

+3514
-315
lines changed

llvm/include/llvm/Analysis/IVDescriptors.h

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ enum class RecurKind {
7070
FindLastIVUMax, ///< FindLast reduction with select(cmp(),x,y) where one of
7171
///< (x,y) is increasing loop induction, and both x and y
7272
///< are integer type, producing a UMax reduction.
73+
FindLast, ///< FindLast reduction with select(cmp(),x,y) where x and y
74+
///< are an integer type, one is the current recurrence value,
75+
///< and the other is an arbitrary value.
7376
// clang-format on
7477
// TODO: Any_of and FindLast reduction need not be restricted to integer type
7578
// only.
@@ -180,13 +183,11 @@ class RecurrenceDescriptor {
180183
/// Returns a struct describing whether the instruction is either a
181184
/// Select(ICmp(A, B), X, Y), or
182185
/// Select(FCmp(A, B), X, Y)
183-
/// where one of (X, Y) is an increasing (FindLast) or decreasing (FindFirst)
184-
/// loop induction variable, and the other is a PHI value.
185-
// TODO: Support non-monotonic variable. FindLast does not need be restricted
186-
// to increasing loop induction variables.
187-
LLVM_ABI static InstDesc isFindIVPattern(RecurKind Kind, Loop *TheLoop,
188-
PHINode *OrigPhi, Instruction *I,
189-
ScalarEvolution &SE);
186+
/// where one of (X, Y) is an increasing (FindLastIV) or decreasing
187+
/// (FindFirstIV) loop induction variable, or an arbitrary integer value
188+
/// (FindLast), and the other is a PHI value.
189+
LLVM_ABI static InstDesc isFindPattern(Loop *TheLoop, PHINode *OrigPhi,
190+
Instruction *I, ScalarEvolution &SE);
190191

191192
/// Returns a struct describing if the instruction is a
192193
/// Select(FCmp(X, Y), (Z = X op PHINode), PHINode) instruction pattern.
@@ -310,6 +311,17 @@ class RecurrenceDescriptor {
310311
isFindLastIVRecurrenceKind(Kind);
311312
}
312313

314+
/// Returns true if the recurrence kind is of the form
315+
/// select(cmp(),x,y) where one of (x,y) is an arbitrary value and the
316+
/// other is a recurrence.
317+
static bool isFindLastRecurrenceKind(RecurKind Kind) {
318+
return Kind == RecurKind::FindLast;
319+
}
320+
321+
static bool isFindRecurrenceKind(RecurKind Kind) {
322+
return isFindLastRecurrenceKind(Kind) || isFindIVRecurrenceKind(Kind);
323+
}
324+
313325
/// Returns the type of the recurrence. This type can be narrower than the
314326
/// actual type of the Phi if the recurrence has been type-promoted.
315327
Type *getRecurrenceType() const { return RecurrenceType; }

llvm/lib/Analysis/IVDescriptors.cpp

Lines changed: 50 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ bool RecurrenceDescriptor::isIntegerRecurrenceKind(RecurKind Kind) {
5858
case RecurKind::FindFirstIVUMin:
5959
case RecurKind::FindLastIVSMax:
6060
case RecurKind::FindLastIVUMax:
61+
// TODO: Make type-agnostic.
62+
case RecurKind::FindLast:
6163
return true;
6264
}
6365
return false;
@@ -721,9 +723,15 @@ RecurrenceDescriptor::isAnyOfPattern(Loop *Loop, PHINode *OrigPhi,
721723
// if (src[i] > 3)
722724
// r = i;
723725
// }
726+
// or like this:
727+
// int r = 0;
728+
// for (int i = 0; i < n; i++) {
729+
// if (src[i] > 3)
730+
// r = <loop-varying value>;
731+
// }
724732
// The reduction value (r) is derived from either the values of an induction
725-
// variable (i) sequence, or from the start value (0). The LLVM IR generated for
726-
// such loops would be as follows:
733+
// variable (i) sequence, an arbitrary loop-varying value, or from the start
734+
// value (0). The LLVM IR generated for such loops would be as follows:
727735
// for.body:
728736
// %r = phi i32 [ %spec.select, %for.body ], [ 0, %entry ]
729737
// %i = phi i32 [ %inc, %for.body ], [ 0, %entry ]
@@ -732,23 +740,27 @@ RecurrenceDescriptor::isAnyOfPattern(Loop *Loop, PHINode *OrigPhi,
732740
// %spec.select = select i1 %cmp, i32 %i, i32 %r
733741
// %inc = add nsw i32 %i, 1
734742
// ...
735-
// Since 'i' is an induction variable, the reduction value after the loop will
736-
// be the maximum (increasing induction) or minimum (decreasing induction) value
737-
// of 'i' that the condition (src[i] > 3) is satisfied, or the start value (0 in
738-
// the example above). When the start value of the induction variable 'i' is
739-
// greater than the minimum (increasing induction) or maximum (decreasing
740-
// induction) value of the data type, we can use the minimum (increasing
741-
// induction) or maximum (decreasing induction) value of the data type as a
742-
// sentinel value to replace the start value. This allows us to perform a single
743-
// reduction max (increasing induction) or min (decreasing induction) operation
744-
// to obtain the final reduction result.
743+
// When searching for an induction variable (i), the reduction value after the
744+
// loop will be the maximum (increasing induction) or minimum (decreasing
745+
// induction) value of 'i' that the condition (src[i] > 3) is satisfied, or the
746+
// start value (0 in the example above). When the start value of the induction
747+
// variable 'i' is greater than the minimum (increasing induction) or maximum
748+
// (decreasing induction) value of the data type, we can use the minimum
749+
// (increasing induction) or maximum (decreasing induction) value of the data
750+
// type as a sentinel value to replace the start value. This allows us to
751+
// perform a single reduction max (increasing induction) or min (decreasing
752+
// induction) operation to obtain the final reduction result.
745753
// TODO: It is possible to solve the case where the start value is the minimum
746754
// value of the data type or a non-constant value by using mask and multiple
747755
// reduction operations.
756+
//
757+
// When searching for an arbitrary loop-varying value, the reduction value will
758+
// either be the initial value (0) if the condition was never met, or the value
759+
// of the loop-varying value in the most recent loop iteration where the
760+
// condition was met.
748761
RecurrenceDescriptor::InstDesc
749-
RecurrenceDescriptor::isFindIVPattern(RecurKind Kind, Loop *TheLoop,
750-
PHINode *OrigPhi, Instruction *I,
751-
ScalarEvolution &SE) {
762+
RecurrenceDescriptor::isFindPattern(Loop *TheLoop, PHINode *OrigPhi,
763+
Instruction *I, ScalarEvolution &SE) {
752764
// TODO: Support the vectorization of FindLastIV when the reduction phi is
753765
// used by more than one select instruction. This vectorization is only
754766
// performed when the SCEV of each increasing induction variable used by the
@@ -757,8 +769,10 @@ RecurrenceDescriptor::isFindIVPattern(RecurKind Kind, Loop *TheLoop,
757769
return InstDesc(false, I);
758770

759771
// We are looking for selects of the form:
760-
// select(cmp(), phi, loop_induction) or
761-
// select(cmp(), loop_induction, phi)
772+
// select(cmp(), phi, value) or
773+
// select(cmp(), value, phi)
774+
// where 'value' must be a loop induction variable
775+
// (for FindFirstIV/FindLastIV) or an arbitrary value (for FindLast).
762776
// TODO: Match selects with multi-use cmp conditions.
763777
Value *NonRdxPhi = nullptr;
764778
if (!match(I, m_CombineOr(m_Select(m_OneUse(m_Cmp()), m_Value(NonRdxPhi),
@@ -769,7 +783,7 @@ RecurrenceDescriptor::isFindIVPattern(RecurKind Kind, Loop *TheLoop,
769783

770784
// Returns either FindFirstIV/FindLastIV, if such a pattern is found, or
771785
// std::nullopt.
772-
auto GetRecurKind = [&](Value *V) -> std::optional<RecurKind> {
786+
auto GetFindFirstLastIVRecurKind = [&](Value *V) -> std::optional<RecurKind> {
773787
Type *Ty = V->getType();
774788
if (!SE.isSCEVable(Ty))
775789
return std::nullopt;
@@ -780,8 +794,9 @@ RecurrenceDescriptor::isFindIVPattern(RecurKind Kind, Loop *TheLoop,
780794
m_SpecificLoop(TheLoop))))
781795
return std::nullopt;
782796

783-
if ((isFindFirstIVRecurrenceKind(Kind) && !SE.isKnownNegative(Step)) ||
784-
(isFindLastIVRecurrenceKind(Kind) && !SE.isKnownPositive(Step)))
797+
// We must have a known positive or negative step for FindIV
798+
const bool PositiveStep = SE.isKnownPositive(Step);
799+
if (!SE.isKnownNonZero(Step))
785800
return std::nullopt;
786801

787802
// Check if the minimum (FindLast) or maximum (FindFirst) value of the
@@ -797,7 +812,7 @@ RecurrenceDescriptor::isFindIVPattern(RecurKind Kind, Loop *TheLoop,
797812
IsSigned ? SE.getSignedRange(AR) : SE.getUnsignedRange(AR);
798813
unsigned NumBits = Ty->getIntegerBitWidth();
799814
APInt Sentinel;
800-
if (isFindLastIVRecurrenceKind(Kind)) {
815+
if (PositiveStep) {
801816
Sentinel = IsSigned ? APInt::getSignedMinValue(NumBits)
802817
: APInt::getMinValue(NumBits);
803818
} else {
@@ -806,26 +821,22 @@ RecurrenceDescriptor::isFindIVPattern(RecurKind Kind, Loop *TheLoop,
806821
}
807822
ConstantRange ValidRange = ConstantRange(Sentinel).inverse();
808823

809-
LLVM_DEBUG(dbgs() << "LV: "
810-
<< (isFindLastIVRecurrenceKind(Kind) ? "FindLastIV"
811-
: "FindFirstIV")
812-
<< " valid range is " << ValidRange
813-
<< ", and the range of " << *AR << " is " << IVRange
814-
<< "\n");
824+
LLVM_DEBUG(
825+
dbgs() << "LV: " << (PositiveStep ? "FindLastIV" : "FindFirstIV")
826+
<< " valid range is " << ValidRange << ", and the range of "
827+
<< *AR << " is " << IVRange << "\n");
815828

816829
// Ensure the induction variable does not wrap around by verifying that
817830
// its range is fully contained within the valid range.
818831
return ValidRange.contains(IVRange);
819832
};
820-
if (isFindLastIVRecurrenceKind(Kind)) {
833+
if (PositiveStep) {
821834
if (CheckRange(true))
822835
return RecurKind::FindLastIVSMax;
823836
if (CheckRange(false))
824837
return RecurKind::FindLastIVUMax;
825838
return std::nullopt;
826839
}
827-
assert(isFindFirstIVRecurrenceKind(Kind) &&
828-
"Kind must either be a FindLastIV or FindFirstIV");
829840

830841
if (CheckRange(true))
831842
return RecurKind::FindFirstIVSMin;
@@ -834,10 +845,11 @@ RecurrenceDescriptor::isFindIVPattern(RecurKind Kind, Loop *TheLoop,
834845
return std::nullopt;
835846
};
836847

837-
if (auto RK = GetRecurKind(NonRdxPhi))
848+
if (auto RK = GetFindFirstLastIVRecurKind(NonRdxPhi))
838849
return InstDesc(I, *RK);
839850

840-
return InstDesc(false, I);
851+
// If the recurrence is not specific to an IV, return a generic FindLast.
852+
return InstDesc(I, RecurKind::FindLast);
841853
}
842854

843855
RecurrenceDescriptor::InstDesc
@@ -971,8 +983,8 @@ RecurrenceDescriptor::InstDesc RecurrenceDescriptor::isRecurrenceInstr(
971983
Kind == RecurKind::Add || Kind == RecurKind::Mul ||
972984
Kind == RecurKind::Sub || Kind == RecurKind::AddChainWithSubs)
973985
return isConditionalRdxPattern(I);
974-
if (isFindIVRecurrenceKind(Kind) && SE)
975-
return isFindIVPattern(Kind, L, OrigPhi, I, *SE);
986+
if (isFindRecurrenceKind(Kind) && SE)
987+
return isFindPattern(L, OrigPhi, I, *SE);
976988
[[fallthrough]];
977989
case Instruction::FCmp:
978990
case Instruction::ICmp:
@@ -1112,14 +1124,9 @@ bool RecurrenceDescriptor::isReductionPHI(PHINode *Phi, Loop *TheLoop,
11121124
<< "\n");
11131125
return true;
11141126
}
1115-
if (AddReductionVar(Phi, RecurKind::FindLastIVSMax, TheLoop, FMF, RedDes, DB,
1116-
AC, DT, SE)) {
1117-
LLVM_DEBUG(dbgs() << "Found a FindLastIV reduction PHI." << *Phi << "\n");
1118-
return true;
1119-
}
1120-
if (AddReductionVar(Phi, RecurKind::FindFirstIVSMin, TheLoop, FMF, RedDes, DB,
1121-
AC, DT, SE)) {
1122-
LLVM_DEBUG(dbgs() << "Found a FindFirstIV reduction PHI." << *Phi << "\n");
1127+
if (AddReductionVar(Phi, RecurKind::FindLast, TheLoop, FMF, RedDes, DB, AC,
1128+
DT, SE)) {
1129+
LLVM_DEBUG(dbgs() << "Found a Find reduction PHI." << *Phi << "\n");
11231130
return true;
11241131
}
11251132
if (AddReductionVar(Phi, RecurKind::FMul, TheLoop, FMF, RedDes, DB, AC, DT,
@@ -1169,7 +1176,6 @@ bool RecurrenceDescriptor::isReductionPHI(PHINode *Phi, Loop *TheLoop,
11691176
<< "\n");
11701177
return true;
11711178
}
1172-
11731179
// Not a reduction of known type.
11741180
return false;
11751181
}
@@ -1294,6 +1300,7 @@ unsigned RecurrenceDescriptor::getOpcode(RecurKind Kind) {
12941300
case RecurKind::FMaximumNum:
12951301
case RecurKind::FMinimumNum:
12961302
return Instruction::FCmp;
1303+
case RecurKind::FindLast:
12971304
case RecurKind::AnyOf:
12981305
case RecurKind::FindFirstIVSMin:
12991306
case RecurKind::FindFirstIVUMin:

llvm/lib/Target/AArch64/AArch64TargetTransformInfo.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5544,6 +5544,7 @@ bool AArch64TTIImpl::isLegalToVectorizeReduction(
55445544
case RecurKind::FMax:
55455545
case RecurKind::FMulAdd:
55465546
case RecurKind::AnyOf:
5547+
case RecurKind::FindLast:
55475548
return true;
55485549
default:
55495550
return false;

llvm/lib/Transforms/Utils/LoopUnroll.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1258,9 +1258,10 @@ llvm::canParallelizeReductionWhenUnrolling(PHINode &Phi, Loop *L,
12581258
return std::nullopt;
12591259
RecurKind RK = RdxDesc.getRecurrenceKind();
12601260
// Skip unsupported reductions.
1261-
// TODO: Handle additional reductions, including min-max reductions.
1261+
// TODO: Handle additional reductions, including FP and min-max
1262+
// reductions.
12621263
if (RecurrenceDescriptor::isAnyOfRecurrenceKind(RK) ||
1263-
RecurrenceDescriptor::isFindIVRecurrenceKind(RK) ||
1264+
RecurrenceDescriptor::isFindRecurrenceKind(RK) ||
12641265
RecurrenceDescriptor::isMinMaxRecurrenceKind(RK))
12651266
return std::nullopt;
12661267

llvm/lib/Transforms/Utils/LoopUtils.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1480,7 +1480,7 @@ Value *llvm::createSimpleReduction(IRBuilderBase &Builder, Value *Src,
14801480
Value *llvm::createSimpleReduction(IRBuilderBase &Builder, Value *Src,
14811481
RecurKind Kind, Value *Mask, Value *EVL) {
14821482
assert(!RecurrenceDescriptor::isAnyOfRecurrenceKind(Kind) &&
1483-
!RecurrenceDescriptor::isFindIVRecurrenceKind(Kind) &&
1483+
!RecurrenceDescriptor::isFindRecurrenceKind(Kind) &&
14841484
"AnyOf and FindIV reductions are not supported.");
14851485
Intrinsic::ID Id = getReductionIntrinsicID(Kind);
14861486
auto VPID = VPIntrinsic::getForIntrinsic(Id);

llvm/lib/Transforms/Vectorize/LoopVectorize.cpp

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4343,11 +4343,15 @@ bool LoopVectorizationPlanner::isCandidateForEpilogueVectorization(
43434343
ElementCount VF) const {
43444344
// Cross iteration phis such as fixed-order recurrences and FMaxNum/FMinNum
43454345
// reductions need special handling and are currently unsupported.
4346+
// FindLast reductions also require special handling for the synthesized
4347+
// mask PHI.
43464348
if (any_of(OrigLoop->getHeader()->phis(), [&](PHINode &Phi) {
43474349
if (!Legal->isReductionVariable(&Phi))
43484350
return Legal->isFixedOrderRecurrence(&Phi);
4349-
return RecurrenceDescriptor::isFPMinMaxNumRecurrenceKind(
4350-
Legal->getRecurrenceDescriptor(&Phi).getRecurrenceKind());
4351+
RecurKind Kind =
4352+
Legal->getRecurrenceDescriptor(&Phi).getRecurrenceKind();
4353+
return RecurrenceDescriptor::isFindLastRecurrenceKind(Kind) ||
4354+
RecurrenceDescriptor::isFPMinMaxNumRecurrenceKind(Kind);
43514355
}))
43524356
return false;
43534357

@@ -4653,6 +4657,14 @@ LoopVectorizationPlanner::selectInterleaveCount(VPlan &Plan, ElementCount VF,
46534657
any_of(Plan.getVectorLoopRegion()->getEntryBasicBlock()->phis(),
46544658
IsaPred<VPReductionPHIRecipe>);
46554659

4660+
// FIXME: implement interleaving for FindLast transform correctly.
4661+
if (any_of(make_second_range(Legal->getReductionVars()),
4662+
[](const RecurrenceDescriptor &RdxDesc) {
4663+
return RecurrenceDescriptor::isFindLastRecurrenceKind(
4664+
RdxDesc.getRecurrenceKind());
4665+
}))
4666+
return 1;
4667+
46564668
// If we did not calculate the cost for VF (because the user selected the VF)
46574669
// then we calculate the cost of VF here.
46584670
if (LoopCost == 0) {
@@ -8585,6 +8597,11 @@ VPlanPtr LoopVectorizationPlanner::tryToBuildVPlanWithVPRecipes(
85858597
*Plan))
85868598
return nullptr;
85878599

8600+
// Create whole-vector selects for find-last recurrences.
8601+
if (!VPlanTransforms::runPass(VPlanTransforms::handleFindLastReductions,
8602+
*Plan))
8603+
return nullptr;
8604+
85888605
// Transform recipes to abstract recipes if it is legal and beneficial and
85898606
// clamp the range for better cost estimation.
85908607
// TODO: Enable following transform when the EVL-version of extended-reduction
@@ -8856,7 +8873,8 @@ void LoopVectorizationPlanner::addReductionResultComputation(
88568873
RecurKind RK = RdxDesc.getRecurrenceKind();
88578874
if ((!RecurrenceDescriptor::isAnyOfRecurrenceKind(RK) &&
88588875
!RecurrenceDescriptor::isFindIVRecurrenceKind(RK) &&
8859-
!RecurrenceDescriptor::isMinMaxRecurrenceKind(RK))) {
8876+
!RecurrenceDescriptor::isMinMaxRecurrenceKind(RK) &&
8877+
!RecurrenceDescriptor::isFindLastRecurrenceKind(RK))) {
88608878
VPBuilder PHBuilder(Plan->getVectorPreheader());
88618879
VPValue *Iden = Plan->getOrAddLiveIn(
88628880
getRecurrenceIdentity(RK, PhiTy, RdxDesc.getFastMathFlags()));
@@ -9998,6 +10016,18 @@ bool LoopVectorizePass::processLoop(Loop *L) {
999810016
// Override IC if user provided an interleave count.
999910017
IC = UserIC > 0 ? UserIC : IC;
1000010018

10019+
// FIXME: Enable interleaving for FindLast reductions.
10020+
if (any_of(LVL.getReductionVars().values(), [](auto &RdxDesc) {
10021+
return RecurrenceDescriptor::isFindLastRecurrenceKind(
10022+
RdxDesc.getRecurrenceKind());
10023+
})) {
10024+
LLVM_DEBUG(dbgs() << "LV: Not interleaving due to FindLast reduction.\n");
10025+
IntDiagMsg = {"FindLastPreventsScalarInterleaving",
10026+
"Unable to interleave due to FindLast reduction."};
10027+
InterleaveLoop = false;
10028+
IC = 1;
10029+
}
10030+
1000110031
// Emit diagnostic messages, if any.
1000210032
const char *VAPassName = Hints.vectorizeAnalysisPassName();
1000310033
if (!VectorizeLoop && !InterleaveLoop) {

llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26007,6 +26007,7 @@ class HorizontalReduction {
2600726007
case RecurKind::FindFirstIVUMin:
2600826008
case RecurKind::FindLastIVSMax:
2600926009
case RecurKind::FindLastIVUMax:
26010+
case RecurKind::FindLast:
2601026011
case RecurKind::FMaxNum:
2601126012
case RecurKind::FMinNum:
2601226013
case RecurKind::FMaximumNum:
@@ -26148,6 +26149,7 @@ class HorizontalReduction {
2614826149
case RecurKind::FindFirstIVUMin:
2614926150
case RecurKind::FindLastIVSMax:
2615026151
case RecurKind::FindLastIVUMax:
26152+
case RecurKind::FindLast:
2615126153
case RecurKind::FMaxNum:
2615226154
case RecurKind::FMinNum:
2615326155
case RecurKind::FMaximumNum:
@@ -26254,6 +26256,7 @@ class HorizontalReduction {
2625426256
case RecurKind::FindFirstIVUMin:
2625526257
case RecurKind::FindLastIVSMax:
2625626258
case RecurKind::FindLastIVUMax:
26259+
case RecurKind::FindLast:
2625726260
case RecurKind::FMaxNum:
2625826261
case RecurKind::FMinNum:
2625926262
case RecurKind::FMaximumNum:

llvm/lib/Transforms/Vectorize/VPlan.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1189,6 +1189,11 @@ class LLVM_ABI_FOR_TEST VPInstruction : public VPRecipeWithIRFlags,
11891189
/// Explicit user for the resume phi of the canonical induction in the main
11901190
/// VPlan, used by the epilogue vector loop.
11911191
ResumeForEpilogue,
1192+
/// Extracts the lane from the first operand corresponding to the last
1193+
/// active (non-zero) lane in the mask (second operand), or if no lanes
1194+
/// were active in the mask, returns the default value (third operand).
1195+
ExtractLastActive,
1196+
11921197
/// Returns the value for vscale.
11931198
VScale,
11941199
OpsEnd = VScale,
@@ -2378,6 +2383,10 @@ class LLVM_ABI_FOR_TEST VPWidenPHIRecipe : public VPSingleDefRecipe,
23782383
/// Generate the phi/select nodes.
23792384
void execute(VPTransformState &State) override;
23802385

2386+
/// Return the cost of this VPWidenPHIRecipe.
2387+
InstructionCost computeCost(ElementCount VF,
2388+
VPCostContext &Ctx) const override;
2389+
23812390
protected:
23822391
#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
23832392
/// Print the recipe.

0 commit comments

Comments
 (0)