Skip to content

Commit 4e7a52e

Browse files
committed
Rewrite fmax/fmin InstSimplify elementwise
Rewrite all the fmin/fmax(x, const) optimizations in InstSimplify to use elementwise checking instead of pattern-matching. This allows for improved splat-vector handling, as well as handling of mixed constant vectors e.g. <sNan, Inf, poison>.
1 parent a80f4f0 commit 4e7a52e

File tree

2 files changed

+296
-48
lines changed

2 files changed

+296
-48
lines changed

llvm/lib/Analysis/InstructionSimplify.cpp

Lines changed: 133 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -6723,7 +6723,7 @@ Value *llvm::simplifyBinaryIntrinsic(Intrinsic::ID IID, Type *ReturnType,
67236723
// inserting an llvm.canonicalize to transform input sNaNs into qNaNs,
67246724
// or may assume all NaN inputs are qNaNs.
67256725

6726-
// If the arguments are the same, this is a no-op.
6726+
// If the arguments are the same, this is a no-op (ignoring NaN quieting)
67276727
if (Op0 == Op1)
67286728
return Op0;
67296729

@@ -6735,54 +6735,139 @@ Value *llvm::simplifyBinaryIntrinsic(Intrinsic::ID IID, Type *ReturnType,
67356735
if (Q.isUndefValue(Op1))
67366736
return Op0;
67376737

6738-
bool PropagateNaN = IID == Intrinsic::minimum || IID == Intrinsic::maximum;
6739-
bool PropagateSNaN = IID == Intrinsic::minnum || IID == Intrinsic::maxnum;
6740-
bool IsMin = IID == Intrinsic::minimum || IID == Intrinsic::minnum ||
6741-
IID == Intrinsic::minimumnum;
6742-
6743-
// minnum(x, qnan) -> x
6744-
// maxnum(x, qnan) -> x
6745-
// minnum(x, snan) -> qnan
6746-
// maxnum(x, snan) -> qnan
6747-
// minimum(X, nan) -> qnan
6748-
// maximum(X, nan) -> qnan
6749-
if (PropagateSNaN && match(Op1, m_sNaN()))
6750-
return propagateNaN(cast<Constant>(Op1));
6751-
if (match(Op1, m_NaN())) {
6752-
if (PropagateNaN)
6753-
return propagateNaN(cast<Constant>(Op1));
6754-
// In cases like mixed <sNaN, qNaN> vectors, avoid the optimization to
6755-
// allow correct sNaN propagation where necessary.
6756-
else if (PropagateSNaN && !match(Op1, m_qNaN()))
6757-
break;
6758-
else
6759-
return Op0;
6760-
}
6738+
if (Constant *C = dyn_cast<Constant>(Op1)) {
6739+
bool PropagateNaN =
6740+
IID == Intrinsic::minimum || IID == Intrinsic::maximum;
6741+
bool PropagateSNaN = IID == Intrinsic::minnum || IID == Intrinsic::maxnum;
6742+
bool IsMin = IID == Intrinsic::minimum || IID == Intrinsic::minnum ||
6743+
IID == Intrinsic::minimumnum;
6744+
6745+
// Get the optimized value for a constant scalar input. The result may
6746+
// indicate either to use the non-const LHS value, or return a pointer
6747+
// to a new constant value to use instead of the input (after e.g.
6748+
// quieting NaNs). Returns empty optional value if it cannot be optimized.
6749+
typedef struct {
6750+
bool UseNonConstVal;
6751+
Constant *NewConstVal;
6752+
} OptResult;
6753+
auto GetOptResultFor = [PropagateNaN, PropagateSNaN, IsMin,
6754+
Call](Constant *C) -> std::optional<OptResult> {
6755+
auto UseNonConstVal = []() -> OptResult { return {true, nullptr}; };
6756+
auto UseConstVal = [](Constant *C) -> OptResult { return {false, C}; };
6757+
6758+
// min/max(opt, poison) -> poison
6759+
if (isa<UndefValue>(C))
6760+
return UseConstVal(C);
6761+
6762+
const ConstantFP *CFP = dyn_cast<ConstantFP>(C);
6763+
if (!CFP)
6764+
return {};
6765+
APFloat CAPF = CFP->getValueAPF();
6766+
6767+
// minnum(x, qnan) -> x
6768+
// maxnum(x, qnan) -> x
6769+
// minnum(x, snan) -> qnan
6770+
// maxnum(x, snan) -> qnan
6771+
// minimum(X, nan) -> qnan
6772+
// maximum(X, nan) -> qnan
6773+
// minimumnum(X, nan) -> x
6774+
// maximumnum(X, nan) -> x
6775+
if (CAPF.isNaN()) {
6776+
if (PropagateNaN || (PropagateSNaN && CAPF.isSignaling()))
6777+
return UseConstVal(ConstantFP::get(C->getType(), CAPF.makeQuiet()));
6778+
else
6779+
return UseNonConstVal();
6780+
}
67616781

6762-
// In the following folds, inf can be replaced with the largest finite
6763-
// float, if the ninf flag is set.
6764-
const APFloat *C;
6765-
if (match(Op1, m_APFloat(C)) &&
6766-
(C->isInfinity() || (Call && Call->hasNoInfs() && C->isLargest()))) {
6767-
// minnum(X, -inf) -> -inf (ignoring sNaN -> qNaN propagation)
6768-
// maxnum(X, +inf) -> +inf (ignoring sNaN -> qNaN propagation)
6769-
// minimum(X, -inf) -> -inf if nnan
6770-
// maximum(X, +inf) -> +inf if nnan
6771-
// minimumnum(X, -inf) -> -inf
6772-
// maximumnum(X, +inf) -> +inf
6773-
if (C->isNegative() == IsMin &&
6774-
(!PropagateNaN || (Call && Call->hasNoNaNs())))
6775-
return ConstantFP::get(ReturnType, *C);
6776-
6777-
// minnum(X, +inf) -> X if nnan
6778-
// maxnum(X, -inf) -> X if nnan
6779-
// minimum(X, +inf) -> X (ignoring quieting of sNaNs)
6780-
// maximum(X, -inf) -> X (ignoring quieting of sNaNs)
6781-
// maximumnum(X, -inf) -> X if nnan
6782-
// minimumnum(X, +inf) -> X if nnan
6783-
if (C->isNegative() != IsMin &&
6784-
(PropagateNaN || (Call && Call->hasNoNaNs())))
6785-
return Op0;
6782+
if (CAPF.isInfinity() ||
6783+
(Call && Call->hasNoInfs() && CAPF.isLargest())) {
6784+
// minnum(X, -inf) -> -inf (ignoring sNaN -> qNaN propagation)
6785+
// maxnum(X, +inf) -> +inf (ignoring sNaN -> qNaN propagation)
6786+
// minimum(X, -inf) -> -inf if nnan
6787+
// maximum(X, +inf) -> +inf if nnan
6788+
// minimumnum(X, -inf) -> -inf
6789+
// maximumnum(X, +inf) -> +inf
6790+
if (CAPF.isNegative() == IsMin &&
6791+
(!PropagateNaN || (Call && Call->hasNoNaNs())))
6792+
return UseConstVal(C);
6793+
6794+
// minnum(X, +inf) -> X if nnan
6795+
// maxnum(X, -inf) -> X if nnan
6796+
// minimum(X, +inf) -> X (ignoring quieting of sNaNs)
6797+
// maximum(X, -inf) -> X (ignoring quieting of sNaNs)
6798+
// maximumnum(X, -inf) -> X if nnan
6799+
// minimumnum(X, +inf) -> X if nnan
6800+
if (CAPF.isNegative() != IsMin &&
6801+
(PropagateNaN || (Call && Call->hasNoNaNs())))
6802+
return UseNonConstVal();
6803+
}
6804+
6805+
// Cannot optimize this element
6806+
return {};
6807+
};
6808+
6809+
if (VectorType *VTy = dyn_cast<VectorType>(C->getType())) {
6810+
// Handle splat vectors (including scalable vectors)
6811+
if (Constant *SplatVal = C->getSplatValue()) {
6812+
std::optional<OptResult> OptSplatVal = GetOptResultFor(SplatVal);
6813+
if (OptSplatVal.has_value()) {
6814+
if (OptSplatVal.value().UseNonConstVal)
6815+
return Op0;
6816+
assert(OptSplatVal.value().NewConstVal != nullptr);
6817+
return ConstantVector::getSplat(VTy->getElementCount(),
6818+
OptSplatVal.value().NewConstVal);
6819+
}
6820+
}
6821+
6822+
// Check elementwise whether we can optimize to either a constant value
6823+
// or return the LHS value. We cannot mix and match LHS + constant
6824+
// elements, as this would require inserting a new VectorShuffle
6825+
// instruction, which is not allowed in simplifyBinOp, so bail early if
6826+
// any element cannot be optimized, or if lhs vs const optimizations
6827+
// start to mismatch. However, we can turn undef/poison into the LHS
6828+
// value, so only bail if we need at least 1 non undef/poison RHS const.
6829+
bool CanOptimize = true;
6830+
bool AllConstValsAreUndef = true;
6831+
if (auto *FVty = dyn_cast<FixedVectorType>(VTy)) {
6832+
unsigned NumElts = FVty->getNumElements();
6833+
// Storage to build up the constant return value (possible altered
6834+
// from the input RHS value by quieting NaNs)
6835+
SmallVector<Constant *, 16> NewC(NumElts);
6836+
6837+
bool NeedsConstElement = false;
6838+
bool NeedsLHSElement = false;
6839+
for (unsigned i = 0; i != NumElts; ++i) {
6840+
Constant *EltC = C->getAggregateElement(i);
6841+
std::optional<OptResult> OptElemVal = GetOptResultFor(EltC);
6842+
if (!OptElemVal.has_value()) {
6843+
CanOptimize = false;
6844+
break;
6845+
}
6846+
if (OptElemVal.value().UseNonConstVal) {
6847+
NeedsLHSElement = true;
6848+
if (NeedsConstElement && !AllConstValsAreUndef)
6849+
break;
6850+
} else {
6851+
NeedsConstElement = true;
6852+
assert(OptElemVal.value().NewConstVal != nullptr);
6853+
NewC[i] = OptElemVal.value().NewConstVal;
6854+
AllConstValsAreUndef &= isa<UndefValue>(NewC[i]);
6855+
if (NeedsLHSElement && !AllConstValsAreUndef)
6856+
break;
6857+
}
6858+
}
6859+
6860+
if (CanOptimize && (!NeedsLHSElement || AllConstValsAreUndef))
6861+
return NeedsLHSElement ? Op0 : ConstantVector::get(NewC);
6862+
}
6863+
} else {
6864+
// Handle scalar inputs
6865+
std::optional<OptResult> OptScalarVal = GetOptResultFor(C);
6866+
if (OptScalarVal.has_value()) {
6867+
OptResult Res = OptScalarVal.value();
6868+
return Res.UseNonConstVal ? Op0 : Res.NewConstVal;
6869+
}
6870+
}
67866871
}
67876872

67886873
// Min/max of the same operation with common operand:

llvm/test/Transforms/InstSimplify/fminmax-folds.ll

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,20 @@ declare float @llvm.minnum.f32(float, float)
88
declare float @llvm.maxnum.f32(float, float)
99
declare float @llvm.minimum.f32(float, float)
1010
declare float @llvm.maximum.f32(float, float)
11+
declare float @llvm.minimumnum.f32(float, float)
12+
declare float @llvm.maximumnum.f32(float, float)
1113
declare <2 x float> @llvm.minnum.v2f32(<2 x float>, <2 x float>)
1214
declare <2 x float> @llvm.maxnum.v2f32(<2 x float>, <2 x float>)
1315
declare <2 x float> @llvm.minimum.v2f32(<2 x float>, <2 x float>)
1416
declare <2 x float> @llvm.maximum.v2f32(<2 x float>, <2 x float>)
17+
declare <2 x float> @llvm.minimumnum.v2f32(<2 x float>, <2 x float>)
18+
declare <2 x float> @llvm.maximumnum.v2f32(<2 x float>, <2 x float>)
19+
declare <3 x float> @llvm.minnum.v3f32(<3 x float>, <3 x float>)
20+
declare <3 x float> @llvm.maxnum.v3f32(<3 x float>, <3 x float>)
21+
declare <3 x float> @llvm.minimum.v3f32(<3 x float>, <3 x float>)
22+
declare <3 x float> @llvm.maximum.v3f32(<3 x float>, <3 x float>)
23+
declare <3 x float> @llvm.minimumnum.v3f32(<3 x float>, <3 x float>)
24+
declare <3 x float> @llvm.maximumnum.v3f32(<3 x float>, <3 x float>)
1525

1626
declare double @llvm.minnum.f64(double, double)
1727
declare double @llvm.maxnum.f64(double, double)
@@ -21,6 +31,10 @@ declare double @llvm.minimum.f64(double, double)
2131
declare double @llvm.maximum.f64(double, double)
2232
declare <2 x double> @llvm.minimum.v2f64(<2 x double>, <2 x double>)
2333
declare <2 x double> @llvm.maximum.v2f64(<2 x double>, <2 x double>)
34+
declare double @llvm.minimumnum.f64(double, double)
35+
declare double @llvm.maximumnum.f64(double, double)
36+
declare <2 x double> @llvm.minimumnum.v2f64(<2 x double>, <2 x double>)
37+
declare <2 x double> @llvm.maximumnum.v2f64(<2 x double>, <2 x double>)
2438

2539
define float @test_minnum_const_nan(float %x) {
2640
; CHECK-LABEL: @test_minnum_const_nan(
@@ -220,6 +234,155 @@ define <2 x float> @test_minimumnum_const_inf_vec(<2 x float> %x) {
220234
ret <2 x float> %r
221235
}
222236

237+
define <vscale x 2 x float> @test_minnum_const_snan_scalable_vec(<vscale x 2 x float> %x) {
238+
; CHECK-LABEL: @test_minnum_const_snan_scalable_vec(
239+
; CHECK-NEXT: ret <vscale x 2 x float> splat (float 0x7FFC000000000000)
240+
;
241+
%r = call <vscale x 2 x float> @llvm.minnum.nxv2f32(<vscale x 2 x float> %x, <vscale x 2 x float> splat (float 0x7ff4000000000000))
242+
ret <vscale x 2 x float> %r
243+
}
244+
245+
define <vscale x 2 x float> @test_maxnum_const_snan_scalable_vec(<vscale x 2 x float> %x) {
246+
; CHECK-LABEL: @test_maxnum_const_snan_scalable_vec(
247+
; CHECK-NEXT: ret <vscale x 2 x float> splat (float 0x7FFC000000000000)
248+
;
249+
%r = call <vscale x 2 x float> @llvm.maxnum.nxv2f32(<vscale x 2 x float> %x, <vscale x 2 x float> splat (float 0x7ff4000000000000))
250+
ret <vscale x 2 x float> %r
251+
}
252+
253+
define <vscale x 2 x float> @test_maximum_const_snan_scalable_vec(<vscale x 2 x float> %x) {
254+
; CHECK-LABEL: @test_maximum_const_snan_scalable_vec(
255+
; CHECK-NEXT: ret <vscale x 2 x float> splat (float 0x7FFC000000000000)
256+
;
257+
%r = call <vscale x 2 x float> @llvm.maximum.nxv2f32(<vscale x 2 x float> %x, <vscale x 2 x float> splat (float 0x7ff4000000000000))
258+
ret <vscale x 2 x float> %r
259+
}
260+
261+
define <vscale x 2 x float> @test_minimum_const_snan_scalable_vec(<vscale x 2 x float> %x) {
262+
; CHECK-LABEL: @test_minimum_const_snan_scalable_vec(
263+
; CHECK-NEXT: ret <vscale x 2 x float> splat (float 0x7FFC000000000000)
264+
;
265+
%r = call <vscale x 2 x float> @llvm.minimum.nxv2f32(<vscale x 2 x float> %x, <vscale x 2 x float> splat (float 0x7ff4000000000000))
266+
ret <vscale x 2 x float> %r
267+
}
268+
269+
define <vscale x 2 x float> @test_maximumnum_const_snan_scalable_vec(<vscale x 2 x float> %x) {
270+
; CHECK-LABEL: @test_maximumnum_const_snan_scalable_vec(
271+
; CHECK-NEXT: ret <vscale x 2 x float> [[R:%.*]]
272+
;
273+
%r = call <vscale x 2 x float> @llvm.maximumnum.nxv2f32(<vscale x 2 x float> %x, <vscale x 2 x float> splat (float 0x7ff4000000000000))
274+
ret <vscale x 2 x float> %r
275+
}
276+
277+
define <vscale x 2 x float> @test_minimumnum_const_snan_scalable_vec(<vscale x 2 x float> %x) {
278+
; CHECK-LABEL: @test_minimumnum_const_snan_scalable_vec(
279+
; CHECK-NEXT: ret <vscale x 2 x float> [[X:%.*]]
280+
;
281+
%r = call <vscale x 2 x float> @llvm.minimumnum.nxv2f32(<vscale x 2 x float> %x, <vscale x 2 x float> splat (float 0x7ff4000000000000))
282+
ret <vscale x 2 x float> %r
283+
}
284+
285+
define <2 x float> @test_minnum_const_inf_poison_vec_nnan(<2 x float> %x) {
286+
; CHECK-LABEL: @test_minnum_const_inf_poison_vec_nnan(
287+
; CHECK-NEXT: ret <2 x float> [[X:%.*]]
288+
;
289+
%r = call nnan <2 x float> @llvm.minnum.v2f32(<2 x float> %x, <2 x float> <float 0x7ff0000000000000, float poison>)
290+
ret <2 x float> %r
291+
}
292+
293+
define <2 x float> @test_maxnum_const_inf_poison_vec_nnan(<2 x float> %x) {
294+
; CHECK-LABEL: @test_maxnum_const_inf_poison_vec_nnan(
295+
; CHECK-NEXT: ret <2 x float> <float 0x7FF0000000000000, float poison>
296+
;
297+
%r = call nnan <2 x float> @llvm.maxnum.v2f32(<2 x float> %x, <2 x float> <float 0x7ff0000000000000, float poison>)
298+
ret <2 x float> %r
299+
}
300+
301+
define <2 x float> @test_maximum_const_inf_poison_vec_nnan(<2 x float> %x) {
302+
; CHECK-LABEL: @test_maximum_const_inf_poison_vec_nnan(
303+
; CHECK-NEXT: ret <2 x float> <float 0x7FF0000000000000, float poison>
304+
;
305+
%r = call nnan <2 x float> @llvm.maximum.v2f32(<2 x float> %x, <2 x float> <float 0x7ff0000000000000, float poison>)
306+
ret <2 x float> %r
307+
}
308+
309+
define <2 x float> @test_minimum_const_inf_poison_vec_nnan(<2 x float> %x) {
310+
; CHECK-LABEL: @test_minimum_const_inf_poison_vec_nnan(
311+
; CHECK-NEXT: ret <2 x float> [[X:%.*]]
312+
;
313+
%r = call nnan <2 x float> @llvm.minimum.v2f32(<2 x float> %x, <2 x float> <float 0x7ff0000000000000, float poison>)
314+
ret <2 x float> %r
315+
}
316+
317+
define <2 x float> @test_maximumnum_const_inf_poison_vec_nnan(<2 x float> %x) {
318+
; CHECK-LABEL: @test_maximumnum_const_inf_poison_vec_nnan(
319+
; CHECK-NEXT: ret <2 x float> <float 0x7FF0000000000000, float poison>
320+
;
321+
%r = call nnan <2 x float> @llvm.maximumnum.v2f32(<2 x float> %x, <2 x float> <float 0x7ff0000000000000, float poison>)
322+
ret <2 x float> %r
323+
}
324+
325+
define <2 x float> @test_minimumnum_const_inf_poison_vec_nnan(<2 x float> %x) {
326+
; CHECK-LABEL: @test_minimumnum_const_inf_poison_vec_nnan(
327+
; CHECK-NEXT: ret <2 x float> [[X:%.*]]
328+
;
329+
%r = call nnan <2 x float> @llvm.minimumnum.v2f32(<2 x float> %x, <2 x float> <float 0x7ff0000000000000, float poison>)
330+
ret <2 x float> %r
331+
}
332+
333+
define <3 x float> @test_minnum_const_inf_poison_snan_vec(<3 x float> %x) {
334+
; CHECK-LABEL: @test_minnum_const_inf_poison_snan_vec(
335+
; CHECK-NEXT: [[X:%.*]] = call <3 x float> @llvm.minnum.v3f32(<3 x float> [[X1:%.*]], <3 x float> <float 0x7FF0000000000000, float poison, float 0x7FF4000000000000>)
336+
; CHECK-NEXT: ret <3 x float> [[X]]
337+
;
338+
%r = call <3 x float> @llvm.minnum.v3f32(<3 x float> %x, <3 x float> <float 0x7ff0000000000000, float poison, float 0x7ff4000000000000>)
339+
ret <3 x float> %r
340+
}
341+
342+
define <3 x float> @test_maxnum_const_inf_poison_snan_vec(<3 x float> %x) {
343+
; CHECK-LABEL: @test_maxnum_const_inf_poison_snan_vec(
344+
; CHECK-NEXT: ret <3 x float> <float 0x7FF0000000000000, float poison, float 0x7FFC000000000000>
345+
;
346+
%r = call <3 x float> @llvm.maxnum.v3f32(<3 x float> %x, <3 x float> <float 0x7ff0000000000000, float poison, float 0x7ff4000000000000>)
347+
ret <3 x float> %r
348+
}
349+
350+
define <3 x float> @test_maximum_const_inf_poison_snan_vec(<3 x float> %x) {
351+
; CHECK-LABEL: @test_maximum_const_inf_poison_snan_vec(
352+
; CHECK-NEXT: [[R:%.*]] = call <3 x float> @llvm.maximum.v3f32(<3 x float> [[X:%.*]], <3 x float> <float 0x7FF0000000000000, float poison, float 0x7FF4000000000000>)
353+
; CHECK-NEXT: ret <3 x float> [[R]]
354+
;
355+
%r = call <3 x float> @llvm.maximum.v3f32(<3 x float> %x, <3 x float> <float 0x7ff0000000000000, float poison, float 0x7ff4000000000000>)
356+
ret <3 x float> %r
357+
}
358+
359+
define <3 x float> @test_minimum_const_inf_poison_snan_vec(<3 x float> %x) {
360+
; CHECK-LABEL: @test_minimum_const_inf_poison_snan_vec(
361+
; CHECK-NEXT: [[X:%.*]] = call <3 x float> @llvm.minimum.v3f32(<3 x float> [[X1:%.*]], <3 x float> <float 0x7FF0000000000000, float poison, float 0x7FF4000000000000>)
362+
; CHECK-NEXT: ret <3 x float> [[X]]
363+
;
364+
%r = call <3 x float> @llvm.minimum.v3f32(<3 x float> %x, <3 x float> <float 0x7ff0000000000000, float poison, float 0x7ff4000000000000>)
365+
ret <3 x float> %r
366+
}
367+
368+
define <3 x float> @test_maximumnum_const_inf_poison_snan_vec(<3 x float> %x) {
369+
; CHECK-LABEL: @test_maximumnum_const_inf_poison_snan_vec(
370+
; CHECK-NEXT: [[R:%.*]] = call <3 x float> @llvm.maximumnum.v3f32(<3 x float> [[X:%.*]], <3 x float> <float 0x7FF0000000000000, float poison, float 0x7FF4000000000000>)
371+
; CHECK-NEXT: ret <3 x float> [[R]]
372+
;
373+
%r = call <3 x float> @llvm.maximumnum.v3f32(<3 x float> %x, <3 x float> <float 0x7ff0000000000000, float poison, float 0x7ff4000000000000>)
374+
ret <3 x float> %r
375+
}
376+
377+
define <3 x float> @test_minimumnum_const_inf_poison_snan_vec(<3 x float> %x) {
378+
; CHECK-LABEL: @test_minimumnum_const_inf_poison_snan_vec(
379+
; CHECK-NEXT: [[X:%.*]] = call <3 x float> @llvm.minimumnum.v3f32(<3 x float> [[X1:%.*]], <3 x float> <float 0x7FF0000000000000, float poison, float 0x7FF4000000000000>)
380+
; CHECK-NEXT: ret <3 x float> [[X]]
381+
;
382+
%r = call <3 x float> @llvm.minimumnum.v3f32(<3 x float> %x, <3 x float> <float 0x7ff0000000000000, float poison, float 0x7ff4000000000000>)
383+
ret <3 x float> %r
384+
}
385+
223386
define float @test_minnum_const_neg_inf(float %x) {
224387
; CHECK-LABEL: @test_minnum_const_neg_inf(
225388
; CHECK-NEXT: ret float 0xFFF0000000000000

0 commit comments

Comments
 (0)