[prev in list] [next in list] [prev in thread] [next in thread]
List: llvm-commits
Subject: [PATCH] D112895: [CVP] Canonicalize signed relational comparisons of scalar integers to unsigned com
From: Roman Lebedev via Phabricator via llvm-commits <llvm-commits () lists ! llvm ! org>
Date: 2021-10-31 20:27:37
Message-ID: LYGL9LCVSdSY0sFnvuL89A () geopod-ismtpd-4-1
[Download RAW message or body]
lebedev.ri created this revision.
lebedev.ri added reviewers: reames, mkazantsev, nikic.
lebedev.ri added a project: LLVM.
Herald added a subscriber: hiraditya.
lebedev.ri requested review of this revision.
Now that the reasoning was added to ConstantRange in D90924 <https://review=
s.llvm.org/D90924>,
this replicates IndVars variant of this transform (D111836 <https://reviews=
.llvm.org/D111836>)
in a pass that uses value range reasoning for the transform.
Repository:
rG LLVM Github Monorepo
https://reviews.llvm.org/D112895
Files:
llvm/lib/Transforms/Scalar/CorrelatedValuePropagation.cpp
llvm/test/Transforms/CorrelatedValuePropagation/basic.ll
llvm/test/Transforms/CorrelatedValuePropagation/deopt.ll
llvm/test/Transforms/CorrelatedValuePropagation/minmaxabs.ll
llvm/test/Transforms/CorrelatedValuePropagation/overflow_predicate.ll
llvm/test/Transforms/CorrelatedValuePropagation/range.ll
llvm/test/Transforms/CorrelatedValuePropagation/sdiv.ll
llvm/test/Transforms/CorrelatedValuePropagation/srem.ll
llvm/test/Transforms/PhaseOrdering/X86/vector-reductions-logical.ll
["D112895.383679.patch" (D112895.383679.patch)]
Index: llvm/test/Transforms/PhaseOrdering/X86/vector-reductions-logical.ll
===================================================================
--- llvm/test/Transforms/PhaseOrdering/X86/vector-reductions-logical.ll
+++ llvm/test/Transforms/PhaseOrdering/X86/vector-reductions-logical.ll
@@ -583,13 +583,13 @@
; CHECK-NEXT: [[DOTNOT:%.*]] = icmp eq i4 [[TMP1]], 0
; CHECK-NEXT: br i1 [[DOTNOT]], label [[IF_END:%.*]], label [[RETURN:%.*]]
; CHECK: if.end:
-; CHECK-NEXT: [[TMP2:%.*]] = icmp sgt <4 x i32> [[T_FR]], <i32 255, i32 255, i32 \
255, i32 255> +; CHECK-NEXT: [[TMP2:%.*]] = icmp ugt <4 x i32> [[T_FR]], <i32 255, \
i32 255, i32 255, i32 255> ; CHECK-NEXT: [[TMP3:%.*]] = bitcast <4 x i1> [[TMP2]] \
to i4
-; CHECK-NEXT: [[DOTNOT7:%.*]] = icmp eq i4 [[TMP3]], 0
+; CHECK-NEXT: [[DOTNOT11:%.*]] = icmp eq i4 [[TMP3]], 0
; CHECK-NEXT: [[SHIFT:%.*]] = shufflevector <4 x i32> [[T_FR]], <4 x i32> poison, \
<4 x i32> <i32 1, i32 undef, i32 undef, i32 undef> ; CHECK-NEXT: [[TMP4:%.*]] = \
add nuw nsw <4 x i32> [[SHIFT]], [[T_FR]] ; CHECK-NEXT: [[ADD:%.*]] = \
extractelement <4 x i32> [[TMP4]], i32 0
-; CHECK-NEXT: [[SPEC_SELECT:%.*]] = select i1 [[DOTNOT7]], i32 [[ADD]], i32 0
+; CHECK-NEXT: [[SPEC_SELECT:%.*]] = select i1 [[DOTNOT11]], i32 [[ADD]], i32 0
; CHECK-NEXT: br label [[RETURN]]
; CHECK: return:
; CHECK-NEXT: [[RETVAL_0:%.*]] = phi i32 [ 0, [[ENTRY:%.*]] ], [ [[SPEC_SELECT]], \
[[IF_END]] ]
Index: llvm/test/Transforms/CorrelatedValuePropagation/srem.ll
===================================================================
--- llvm/test/Transforms/CorrelatedValuePropagation/srem.ll
+++ llvm/test/Transforms/CorrelatedValuePropagation/srem.ll
@@ -40,12 +40,12 @@
; CHECK-NEXT: [[CMP:%.*]] = icmp sgt i32 [[N:%.*]], 0
; CHECK-NEXT: br i1 [[CMP]], label [[LOOP:%.*]], label [[EXIT:%.*]]
; CHECK: loop:
-; CHECK-NEXT: [[A:%.*]] = phi i32 [ [[N]], [[ENTRY:%.*]] ], [ [[REM1:%.*]], \
[[LOOP]] ]
-; CHECK-NEXT: [[COND:%.*]] = icmp sgt i32 [[A]], 4
-; CHECK-NEXT: call void @llvm.assume(i1 [[COND]])
-; CHECK-NEXT: [[REM1]] = urem i32 [[A]], 17
-; CHECK-NEXT: [[LOOPCOND:%.*]] = icmp sgt i32 [[REM1]], 8
-; CHECK-NEXT: br i1 [[LOOPCOND]], label [[LOOP]], label [[EXIT]]
+; CHECK-NEXT: [[A:%.*]] = phi i32 [ [[N]], [[ENTRY:%.*]] ], [ [[REM2:%.*]], \
[[LOOP]] ] +; CHECK-NEXT: [[COND1:%.*]] = icmp ugt i32 [[A]], 4
+; CHECK-NEXT: call void @llvm.assume(i1 [[COND1]])
+; CHECK-NEXT: [[REM2]] = urem i32 [[A]], 17
+; CHECK-NEXT: [[LOOPCOND3:%.*]] = icmp ugt i32 [[REM2]], 8
+; CHECK-NEXT: br i1 [[LOOPCOND3]], label [[LOOP]], label [[EXIT]]
; CHECK: exit:
; CHECK-NEXT: ret void
;
Index: llvm/test/Transforms/CorrelatedValuePropagation/sdiv.ll
===================================================================
--- llvm/test/Transforms/CorrelatedValuePropagation/sdiv.ll
+++ llvm/test/Transforms/CorrelatedValuePropagation/sdiv.ll
@@ -127,12 +127,12 @@
; CHECK-NEXT: [[CMP:%.*]] = icmp sgt i32 [[N:%.*]], 0
; CHECK-NEXT: br i1 [[CMP]], label [[LOOP:%.*]], label [[EXIT:%.*]]
; CHECK: loop:
-; CHECK-NEXT: [[A:%.*]] = phi i32 [ [[N]], [[ENTRY:%.*]] ], [ [[DIV1:%.*]], \
[[LOOP]] ]
-; CHECK-NEXT: [[COND:%.*]] = icmp sgt i32 [[A]], 4
-; CHECK-NEXT: call void @llvm.assume(i1 [[COND]])
-; CHECK-NEXT: [[DIV1]] = udiv i32 [[A]], 6
-; CHECK-NEXT: [[LOOPCOND:%.*]] = icmp sgt i32 [[DIV1]], 8
-; CHECK-NEXT: br i1 [[LOOPCOND]], label [[LOOP]], label [[EXIT]]
+; CHECK-NEXT: [[A:%.*]] = phi i32 [ [[N]], [[ENTRY:%.*]] ], [ [[DIV2:%.*]], \
[[LOOP]] ] +; CHECK-NEXT: [[COND1:%.*]] = icmp ugt i32 [[A]], 4
+; CHECK-NEXT: call void @llvm.assume(i1 [[COND1]])
+; CHECK-NEXT: [[DIV2]] = udiv i32 [[A]], 6
+; CHECK-NEXT: [[LOOPCOND3:%.*]] = icmp ugt i32 [[DIV2]], 8
+; CHECK-NEXT: br i1 [[LOOPCOND3]], label [[LOOP]], label [[EXIT]]
; CHECK: exit:
; CHECK-NEXT: ret void
;
Index: llvm/test/Transforms/CorrelatedValuePropagation/range.ll
===================================================================
--- llvm/test/Transforms/CorrelatedValuePropagation/range.ll
+++ llvm/test/Transforms/CorrelatedValuePropagation/range.ll
@@ -64,8 +64,8 @@
; CHECK: if.then:
; CHECK-NEXT: ret i32 1
; CHECK: if.end:
-; CHECK-NEXT: [[CMP1:%.*]] = icmp slt i32 [[C]], 3
-; CHECK-NEXT: br i1 [[CMP1]], label [[IF_THEN2:%.*]], label [[IF_END8:%.*]]
+; CHECK-NEXT: [[CMP11:%.*]] = icmp ult i32 [[C]], 3
+; CHECK-NEXT: br i1 [[CMP11]], label [[IF_THEN2:%.*]], label [[IF_END8:%.*]]
; CHECK: if.then2:
; CHECK-NEXT: br i1 true, label [[IF_THEN4:%.*]], label [[IF_END6:%.*]]
; CHECK: if.end6:
Index: llvm/test/Transforms/CorrelatedValuePropagation/overflow_predicate.ll
===================================================================
--- llvm/test/Transforms/CorrelatedValuePropagation/overflow_predicate.ll
+++ llvm/test/Transforms/CorrelatedValuePropagation/overflow_predicate.ll
@@ -113,8 +113,8 @@
; CHECK-NEXT: [[OV:%.*]] = extractvalue { i8, i1 } [[VAL_OV]], 1
; CHECK-NEXT: br i1 [[OV]], label [[OVERFLOW:%.*]], label [[TRAP:%.*]]
; CHECK: overflow:
-; CHECK-NEXT: [[C1:%.*]] = icmp sgt i8 [[X]], 28
-; CHECK-NEXT: store i1 [[C1]], i1* [[PC:%.*]], align 1
+; CHECK-NEXT: [[C11:%.*]] = icmp ugt i8 [[X]], 28
+; CHECK-NEXT: store i1 [[C11]], i1* [[PC:%.*]], align 1
; CHECK-NEXT: ret i1 true
; CHECK: trap:
; CHECK-NEXT: call void @llvm.trap()
@@ -241,8 +241,8 @@
; CHECK-NEXT: [[OV:%.*]] = extractvalue { i8, i1 } [[VAL_OV]], 1
; CHECK-NEXT: br i1 [[OV]], label [[OVERFLOW:%.*]], label [[TRAP:%.*]]
; CHECK: overflow:
-; CHECK-NEXT: [[C1:%.*]] = icmp slt i8 [[X]], -29
-; CHECK-NEXT: store i1 [[C1]], i1* [[PC:%.*]], align 1
+; CHECK-NEXT: [[C11:%.*]] = icmp ult i8 [[X]], -29
+; CHECK-NEXT: store i1 [[C11]], i1* [[PC:%.*]], align 1
; CHECK-NEXT: ret i1 true
; CHECK: trap:
; CHECK-NEXT: call void @llvm.trap()
Index: llvm/test/Transforms/CorrelatedValuePropagation/minmaxabs.ll
===================================================================
--- llvm/test/Transforms/CorrelatedValuePropagation/minmaxabs.ll
+++ llvm/test/Transforms/CorrelatedValuePropagation/minmaxabs.ll
@@ -60,8 +60,8 @@
; CHECK-LABEL: @test_smax(
; CHECK-NEXT: [[M:%.*]] = call i32 @llvm.smax.i32(i32 [[X:%.*]], i32 10)
; CHECK-NEXT: call void @use(i1 true)
-; CHECK-NEXT: [[C2:%.*]] = icmp sgt i32 [[M]], 10
-; CHECK-NEXT: call void @use(i1 [[C2]])
+; CHECK-NEXT: [[C21:%.*]] = icmp ugt i32 [[M]], 10
+; CHECK-NEXT: call void @use(i1 [[C21]])
; CHECK-NEXT: ret void
;
%m = call i32 @llvm.smax.i32(i32 %x, i32 10)
@@ -110,8 +110,8 @@
; CHECK-LABEL: @test_abs3(
; CHECK-NEXT: [[A:%.*]] = call i32 @llvm.abs.i32(i32 [[X:%.*]], i1 true)
; CHECK-NEXT: call void @use(i1 true)
-; CHECK-NEXT: [[C2:%.*]] = icmp sgt i32 [[A]], 0
-; CHECK-NEXT: call void @use(i1 [[C2]])
+; CHECK-NEXT: [[C21:%.*]] = icmp ugt i32 [[A]], 0
+; CHECK-NEXT: call void @use(i1 [[C21]])
; CHECK-NEXT: ret void
;
%a = call i32 @llvm.abs.i32(i32 %x, i1 true)
Index: llvm/test/Transforms/CorrelatedValuePropagation/deopt.ll
===================================================================
--- llvm/test/Transforms/CorrelatedValuePropagation/deopt.ll
+++ llvm/test/Transforms/CorrelatedValuePropagation/deopt.ll
@@ -97,8 +97,8 @@
; CHECK-LABEL: @test3(
; CHECK-NEXT: [[SEL:%.*]] = select i1 [[C:%.*]], i64 0, i64 1
; CHECK-NEXT: [[SEL2:%.*]] = select i1 [[C2:%.*]], i64 [[SEL]], i64 2
-; CHECK-NEXT: [[CMP:%.*]] = icmp sgt i64 [[SEL2]], 1
-; CHECK-NEXT: br i1 [[CMP]], label [[TAKEN:%.*]], label [[UNTAKEN:%.*]]
+; CHECK-NEXT: [[CMP1:%.*]] = icmp ugt i64 [[SEL2]], 1
+; CHECK-NEXT: br i1 [[CMP1]], label [[TAKEN:%.*]], label [[UNTAKEN:%.*]]
; CHECK: taken:
; CHECK-NEXT: call void @use() [ "deopt"(i64 2) ]
; CHECK-NEXT: ret void
@@ -122,8 +122,8 @@
; CHECK-NEXT: [[SEL2:%.*]] = select i1 [[C2:%.*]], i64 0, i64 1
; CHECK-NEXT: [[ADD1:%.*]] = add nuw nsw i64 0, [[SEL]]
; CHECK-NEXT: [[ADD2:%.*]] = add nuw nsw i64 [[ADD1]], [[SEL2]]
-; CHECK-NEXT: [[CMP:%.*]] = icmp sgt i64 [[ADD2]], 1
-; CHECK-NEXT: br i1 [[CMP]], label [[TAKEN:%.*]], label [[UNTAKEN:%.*]]
+; CHECK-NEXT: [[CMP1:%.*]] = icmp ugt i64 [[ADD2]], 1
+; CHECK-NEXT: br i1 [[CMP1]], label [[TAKEN:%.*]], label [[UNTAKEN:%.*]]
; CHECK: taken:
; CHECK-NEXT: call void @use() [ "deopt"(i64 2) ]
; CHECK-NEXT: ret void
Index: llvm/test/Transforms/CorrelatedValuePropagation/basic.ll
===================================================================
--- llvm/test/Transforms/CorrelatedValuePropagation/basic.ll
+++ llvm/test/Transforms/CorrelatedValuePropagation/basic.ll
@@ -532,8 +532,8 @@
; CHECK-NEXT: [[CMP2:%.*]] = icmp ult i32 [[B:%.*]], 20
; CHECK-NEXT: br i1 [[CMP2]], label [[B_GUARD:%.*]], label [[OUT]]
; CHECK: b_guard:
-; CHECK-NEXT: [[SEL_CMP:%.*]] = icmp sle i32 [[A]], [[B]]
-; CHECK-NEXT: [[MIN:%.*]] = select i1 [[SEL_CMP]], i32 [[A]], i32 [[B]]
+; CHECK-NEXT: [[SEL_CMP1:%.*]] = icmp ule i32 [[A]], [[B]]
+; CHECK-NEXT: [[MIN:%.*]] = select i1 [[SEL_CMP1]], i32 [[A]], i32 [[B]]
; CHECK-NEXT: ret i1 false
; CHECK: out:
; CHECK-NEXT: ret i1 false
@@ -564,8 +564,8 @@
; CHECK-NEXT: [[CMP2:%.*]] = icmp sgt i32 [[B:%.*]], 20
; CHECK-NEXT: br i1 [[CMP2]], label [[B_GUARD:%.*]], label [[OUT]]
; CHECK: b_guard:
-; CHECK-NEXT: [[SEL_CMP:%.*]] = icmp sge i32 [[A]], [[B]]
-; CHECK-NEXT: [[MAX:%.*]] = select i1 [[SEL_CMP]], i32 [[A]], i32 [[B]]
+; CHECK-NEXT: [[SEL_CMP1:%.*]] = icmp uge i32 [[A]], [[B]]
+; CHECK-NEXT: [[MAX:%.*]] = select i1 [[SEL_CMP1]], i32 [[A]], i32 [[B]]
; CHECK-NEXT: ret i1 false
; CHECK: out:
; CHECK-NEXT: ret i1 false
@@ -737,9 +737,9 @@
; CHECK-NEXT: [[CMP:%.*]] = icmp sge i32 [[A:%.*]], 5
; CHECK-NEXT: br i1 [[CMP]], label [[A_GUARD:%.*]], label [[OUT:%.*]]
; CHECK: a_guard:
-; CHECK-NEXT: [[SEL_CMP:%.*]] = icmp sgt i32 [[A]], 5
+; CHECK-NEXT: [[SEL_CMP1:%.*]] = icmp ugt i32 [[A]], 5
; CHECK-NEXT: [[ADD:%.*]] = add nsw i32 [[A]], -1
-; CHECK-NEXT: [[SEL:%.*]] = select i1 [[SEL_CMP]], i32 [[ADD]], i32 5
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[SEL_CMP1]], i32 [[ADD]], i32 5
; CHECK-NEXT: ret i1 false
; CHECK: out:
; CHECK-NEXT: ret i1 false
@@ -764,9 +764,9 @@
; CHECK-NEXT: [[CMP:%.*]] = icmp sge i32 [[A:%.*]], 5
; CHECK-NEXT: br i1 [[CMP]], label [[A_GUARD:%.*]], label [[OUT:%.*]]
; CHECK: a_guard:
-; CHECK-NEXT: [[SEL_CMP:%.*]] = icmp sle i32 [[A]], 5
+; CHECK-NEXT: [[SEL_CMP1:%.*]] = icmp ule i32 [[A]], 5
; CHECK-NEXT: [[ADD:%.*]] = add nsw i32 [[A]], -1
-; CHECK-NEXT: [[SEL:%.*]] = select i1 [[SEL_CMP]], i32 5, i32 [[ADD]]
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[SEL_CMP1]], i32 5, i32 [[ADD]]
; CHECK-NEXT: ret i1 false
; CHECK: out:
; CHECK-NEXT: ret i1 false
@@ -933,11 +933,11 @@
; CHECK-NEXT: [[CMP:%.*]] = icmp slt i32 [[A]], 0
; CHECK-NEXT: [[ABS:%.*]] = select i1 [[CMP]], i32 [[SUB]], i32 [[A]]
; CHECK-NEXT: store i1 true, i1* [[P:%.*]], align 1
-; CHECK-NEXT: [[C2:%.*]] = icmp slt i32 [[ABS]], 19
-; CHECK-NEXT: store i1 [[C2]], i1* [[P]], align 1
+; CHECK-NEXT: [[C21:%.*]] = icmp ult i32 [[ABS]], 19
+; CHECK-NEXT: store i1 [[C21]], i1* [[P]], align 1
; CHECK-NEXT: store i1 true, i1* [[P]], align 1
-; CHECK-NEXT: [[C4:%.*]] = icmp sge i32 [[ABS]], 1
-; CHECK-NEXT: store i1 [[C4]], i1* [[P]], align 1
+; CHECK-NEXT: [[C42:%.*]] = icmp uge i32 [[ABS]], 1
+; CHECK-NEXT: store i1 [[C42]], i1* [[P]], align 1
; CHECK-NEXT: br label [[EXIT]]
; CHECK: exit:
; CHECK-NEXT: ret void
@@ -978,11 +978,11 @@
; CHECK-NEXT: [[CMP:%.*]] = icmp sge i32 [[A]], 0
; CHECK-NEXT: [[ABS:%.*]] = select i1 [[CMP]], i32 [[A]], i32 [[SUB]]
; CHECK-NEXT: store i1 true, i1* [[P:%.*]], align 1
-; CHECK-NEXT: [[C2:%.*]] = icmp slt i32 [[ABS]], 19
-; CHECK-NEXT: store i1 [[C2]], i1* [[P]], align 1
+; CHECK-NEXT: [[C21:%.*]] = icmp ult i32 [[ABS]], 19
+; CHECK-NEXT: store i1 [[C21]], i1* [[P]], align 1
; CHECK-NEXT: store i1 true, i1* [[P]], align 1
-; CHECK-NEXT: [[C4:%.*]] = icmp sge i32 [[ABS]], 1
-; CHECK-NEXT: store i1 [[C4]], i1* [[P]], align 1
+; CHECK-NEXT: [[C42:%.*]] = icmp uge i32 [[ABS]], 1
+; CHECK-NEXT: store i1 [[C42]], i1* [[P]], align 1
; CHECK-NEXT: br label [[EXIT]]
; CHECK: exit:
; CHECK-NEXT: ret void
Index: llvm/lib/Transforms/Scalar/CorrelatedValuePropagation.cpp
===================================================================
--- llvm/lib/Transforms/Scalar/CorrelatedValuePropagation.cpp
+++ llvm/lib/Transforms/Scalar/CorrelatedValuePropagation.cpp
@@ -67,6 +67,7 @@
STATISTIC(NumAShrs, "Number of ashr converted to lshr");
STATISTIC(NumSRems, "Number of srem converted to urem");
STATISTIC(NumSExt, "Number of sext converted to zext");
+STATISTIC(NumSICmps, "Number of signed icmp preds simplified to unsigned");
STATISTIC(NumAnd, "Number of ands removed");
STATISTIC(NumNW, "Number of no-wrap deductions");
STATISTIC(NumNSW, "Number of no-signed-wrap deductions");
@@ -295,11 +296,39 @@
return true;
}
+static bool processICmp(ICmpInst *Cmp, LazyValueInfo *LVI) {
+ // Only for signed relational comparisons of scalar integers.
+ if (Cmp->getType()->isVectorTy() ||
+ !Cmp->getOperand(0)->getType()->isIntegerTy())
+ return false;
+
+ if (!Cmp->isSigned())
+ return false;
+
+ ICmpInst::Predicate UnsignedPred =
+ ConstantRange::getEquivalentPredWithFlippedSignedness(
+ Cmp->getPredicate(), LVI->getConstantRange(Cmp->getOperand(0), Cmp),
+ LVI->getConstantRange(Cmp->getOperand(1), Cmp));
+
+ if (UnsignedPred == ICmpInst::Predicate::BAD_ICMP_PREDICATE)
+ return false;
+
+ ++NumSICmps;
+ Instruction *UnsignedCmp =
+ ICmpInst::Create(Instruction::ICmp, UnsignedPred, Cmp->getOperand(0),
+ Cmp->getOperand(1), Cmp->getName(), Cmp);
+ UnsignedCmp->setDebugLoc(Cmp->getDebugLoc());
+ Cmp->replaceAllUsesWith(UnsignedCmp);
+ Cmp->eraseFromParent();
+
+ return true;
+}
+
/// See if LazyValueInfo's ability to exploit edge conditions or range
/// information is sufficient to prove this comparison. Even for local
/// conditions, this can sometimes prove conditions instcombine can't by
/// exploiting range information.
-static bool processCmp(CmpInst *Cmp, LazyValueInfo *LVI) {
+static bool constantFoldNonLocalCmp(CmpInst *Cmp, LazyValueInfo *LVI) {
Value *Op0 = Cmp->getOperand(0);
auto *C = dyn_cast<Constant>(Cmp->getOperand(1));
if (!C)
@@ -318,6 +347,17 @@
return true;
}
+static bool processCmp(CmpInst *Cmp, LazyValueInfo *LVI) {
+ if (constantFoldNonLocalCmp(Cmp, LVI))
+ return true;
+
+ if (auto *ICmp = dyn_cast<ICmpInst>(Cmp))
+ if (processICmp(ICmp, LVI))
+ return true;
+
+ return false;
+}
+
/// Simplify a switch instruction by removing cases which can never fire. If the
/// uselessness of a case could be determined locally then constant propagation
/// would already have figured it out. Instead, walk the predecessors and
[Attachment #4 (text/plain)]
_______________________________________________
llvm-commits mailing list
llvm-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-commits
[prev in list] [next in list] [prev in thread] [next in thread]
Configure |
About |
News |
Add a list |
Sponsored by KoreLogic