Skip to content

Commit

Permalink
[clang] Generate nuw GEPs for struct member accesses
Browse files Browse the repository at this point in the history
Generate nuw GEPs for struct member accesses, as these are always
constant positive offsets derived from the index of the struct member.

Regression tests are updated using update_cc_test_checks.py where
possible, and by find + replace where not.
  • Loading branch information
hazzlim committed Jul 18, 2024
1 parent 13a8f8d commit 0daf16a
Show file tree
Hide file tree
Showing 456 changed files with 18,259 additions and 18,242 deletions.
8 changes: 4 additions & 4 deletions clang/lib/CodeGen/CGBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -210,10 +210,10 @@ class CGBuilderTy : public CGBuilderBaseTy {
/// Given
/// %addr = {T1, T2...}* ...
/// produce
/// %name = getelementptr inbounds %addr, i32 0, i32 index
/// %name = getelementptr inbounds nuw %addr, i32 0, i32 index
///
/// This API assumes that drilling into a struct like this is always an
/// inbounds operation.
/// inbounds and nuw operation.
using CGBuilderBaseTy::CreateStructGEP;
Address CreateStructGEP(Address Addr, unsigned Index,
const llvm::Twine &Name = "") {
Expand All @@ -222,8 +222,8 @@ class CGBuilderTy : public CGBuilderBaseTy {
const llvm::StructLayout *Layout = DL.getStructLayout(ElTy);
auto Offset = CharUnits::fromQuantity(Layout->getElementOffset(Index));

return Address(CreateStructGEP(Addr.getElementType(), Addr.getBasePointer(),
Index, Name),
return Address(CreateNUWStructGEP(Addr.getElementType(),
Addr.getBasePointer(), Index, Name),
ElTy->getElementType(Index),
Addr.getAlignment().alignmentAtOffset(Offset),
Addr.isKnownNonNull());
Expand Down
26 changes: 13 additions & 13 deletions clang/test/C/C2y/n3254.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ struct S {
// CHECK-NEXT: [[ARRAYDECAY:%.*]] = getelementptr inbounds [12 x i8], ptr [[BUFFER]], i64 0, i64 0
// CHECK-NEXT: store ptr [[ARRAYDECAY]], ptr [[S_PTR]], align 8
// CHECK-NEXT: [[TMP0:%.*]] = load ptr, ptr [[S_PTR]], align 8
// CHECK-NEXT: [[X:%.*]] = getelementptr inbounds [[STRUCT_S:%.*]], ptr [[TMP0]], i32 0, i32 0
// CHECK-NEXT: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_S:%.*]], ptr [[TMP0]], i32 0, i32 0
// CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[X]], align 4
// CHECK-NEXT: ret i32 [[TMP1]]
//
Expand All @@ -42,10 +42,10 @@ int foo() {
// CHECK-NEXT: [[ENTRY:.*:]]
// CHECK-NEXT: [[BUFFER:%.*]] = alloca [12 x i8], align 4
// CHECK-NEXT: [[ARRAYDECAY:%.*]] = getelementptr inbounds [12 x i8], ptr [[BUFFER]], i64 0, i64 0
// CHECK-NEXT: [[C:%.*]] = getelementptr inbounds [[STRUCT_S:%.*]], ptr [[ARRAYDECAY]], i32 0, i32 1
// CHECK-NEXT: [[C:%.*]] = getelementptr inbounds nuw [[STRUCT_S:%.*]], ptr [[ARRAYDECAY]], i32 0, i32 1
// CHECK-NEXT: store i8 97, ptr [[C]], align 4
// CHECK-NEXT: [[ARRAYDECAY1:%.*]] = getelementptr inbounds [12 x i8], ptr [[BUFFER]], i64 0, i64 0
// CHECK-NEXT: [[C2:%.*]] = getelementptr inbounds [[STRUCT_S]], ptr [[ARRAYDECAY1]], i32 0, i32 1
// CHECK-NEXT: [[C2:%.*]] = getelementptr inbounds nuw [[STRUCT_S]], ptr [[ARRAYDECAY1]], i32 0, i32 1
// CHECK-NEXT: [[TMP0:%.*]] = load i8, ptr [[C2]], align 4
// CHECK-NEXT: ret i8 [[TMP0]]
//
Expand All @@ -60,10 +60,10 @@ char bar() {
// CHECK-NEXT: [[ENTRY:.*:]]
// CHECK-NEXT: [[BUFFER:%.*]] = alloca [12 x i8], align 4
// CHECK-NEXT: [[ARRAYDECAY:%.*]] = getelementptr inbounds [12 x i8], ptr [[BUFFER]], i64 0, i64 0
// CHECK-NEXT: [[F:%.*]] = getelementptr inbounds [[STRUCT_S:%.*]], ptr [[ARRAYDECAY]], i32 0, i32 2
// CHECK-NEXT: [[F:%.*]] = getelementptr inbounds nuw [[STRUCT_S:%.*]], ptr [[ARRAYDECAY]], i32 0, i32 2
// CHECK-NEXT: store float 3.000000e+00, ptr [[F]], align 4
// CHECK-NEXT: [[ARRAYDECAY1:%.*]] = getelementptr inbounds [12 x i8], ptr [[BUFFER]], i64 0, i64 0
// CHECK-NEXT: [[F2:%.*]] = getelementptr inbounds [[STRUCT_S]], ptr [[ARRAYDECAY1]], i32 0, i32 2
// CHECK-NEXT: [[F2:%.*]] = getelementptr inbounds nuw [[STRUCT_S]], ptr [[ARRAYDECAY1]], i32 0, i32 2
// CHECK-NEXT: [[TMP0:%.*]] = load float, ptr [[F2]], align 4
// CHECK-NEXT: ret float [[TMP0]]
//
Expand All @@ -83,11 +83,11 @@ struct T {
// CHECK-NEXT: [[T:%.*]] = alloca [[STRUCT_T:%.*]], align 4
// CHECK-NEXT: [[S_PTR:%.*]] = alloca ptr, align 8
// CHECK-NEXT: call void @llvm.memset.p0.i64(ptr align 4 [[T]], i8 0, i64 12, i1 false)
// CHECK-NEXT: [[BUFFER:%.*]] = getelementptr inbounds [[STRUCT_T]], ptr [[T]], i32 0, i32 0
// CHECK-NEXT: [[BUFFER:%.*]] = getelementptr inbounds nuw [[STRUCT_T]], ptr [[T]], i32 0, i32 0
// CHECK-NEXT: [[ARRAYDECAY:%.*]] = getelementptr inbounds [12 x i8], ptr [[BUFFER]], i64 0, i64 0
// CHECK-NEXT: store ptr [[ARRAYDECAY]], ptr [[S_PTR]], align 8
// CHECK-NEXT: [[TMP0:%.*]] = load ptr, ptr [[S_PTR]], align 8
// CHECK-NEXT: [[C:%.*]] = getelementptr inbounds [[STRUCT_S:%.*]], ptr [[TMP0]], i32 0, i32 1
// CHECK-NEXT: [[C:%.*]] = getelementptr inbounds nuw [[STRUCT_S:%.*]], ptr [[TMP0]], i32 0, i32 1
// CHECK-NEXT: [[TMP1:%.*]] = load i8, ptr [[C]], align 4
// CHECK-NEXT: ret i8 [[TMP1]]
//
Expand All @@ -107,11 +107,11 @@ char quux() {
// CHECK-NEXT: [[ARRAYDECAY:%.*]] = getelementptr inbounds [12 x i8], ptr [[BUFFER]], i64 0, i64 0
// CHECK-NEXT: store ptr [[ARRAYDECAY]], ptr [[T_PTR]], align 8
// CHECK-NEXT: [[TMP0:%.*]] = load ptr, ptr [[T_PTR]], align 8
// CHECK-NEXT: [[BUFFER1:%.*]] = getelementptr inbounds [[STRUCT_T:%.*]], ptr [[TMP0]], i32 0, i32 0
// CHECK-NEXT: [[BUFFER1:%.*]] = getelementptr inbounds nuw [[STRUCT_T:%.*]], ptr [[TMP0]], i32 0, i32 0
// CHECK-NEXT: [[ARRAYDECAY2:%.*]] = getelementptr inbounds [12 x i8], ptr [[BUFFER1]], i64 0, i64 0
// CHECK-NEXT: store ptr [[ARRAYDECAY2]], ptr [[S_PTR]], align 8
// CHECK-NEXT: [[TMP1:%.*]] = load ptr, ptr [[S_PTR]], align 8
// CHECK-NEXT: [[F:%.*]] = getelementptr inbounds [[STRUCT_S:%.*]], ptr [[TMP1]], i32 0, i32 2
// CHECK-NEXT: [[F:%.*]] = getelementptr inbounds nuw [[STRUCT_S:%.*]], ptr [[TMP1]], i32 0, i32 2
// CHECK-NEXT: [[TMP2:%.*]] = load float, ptr [[F]], align 4
// CHECK-NEXT: ret float [[TMP2]]
//
Expand All @@ -128,16 +128,16 @@ float quibble() {
// CHECK-NEXT: [[BUFFER:%.*]] = alloca [12 x i8], align 4
// CHECK-NEXT: [[S_PTR:%.*]] = alloca ptr, align 8
// CHECK-NEXT: [[ARRAYDECAY:%.*]] = getelementptr inbounds [12 x i8], ptr [[BUFFER]], i64 0, i64 0
// CHECK-NEXT: [[BUFFER1:%.*]] = getelementptr inbounds [[STRUCT_T:%.*]], ptr [[ARRAYDECAY]], i32 0, i32 0
// CHECK-NEXT: [[BUFFER1:%.*]] = getelementptr inbounds nuw [[STRUCT_T:%.*]], ptr [[ARRAYDECAY]], i32 0, i32 0
// CHECK-NEXT: [[ARRAYDECAY2:%.*]] = getelementptr inbounds [12 x i8], ptr [[BUFFER1]], i64 0, i64 0
// CHECK-NEXT: [[X:%.*]] = getelementptr inbounds [[STRUCT_S:%.*]], ptr [[ARRAYDECAY2]], i32 0, i32 0
// CHECK-NEXT: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_S:%.*]], ptr [[ARRAYDECAY2]], i32 0, i32 0
// CHECK-NEXT: store i32 12, ptr [[X]], align 4
// CHECK-NEXT: [[ARRAYDECAY3:%.*]] = getelementptr inbounds [12 x i8], ptr [[BUFFER]], i64 0, i64 0
// CHECK-NEXT: [[BUFFER4:%.*]] = getelementptr inbounds [[STRUCT_T]], ptr [[ARRAYDECAY3]], i32 0, i32 0
// CHECK-NEXT: [[BUFFER4:%.*]] = getelementptr inbounds nuw [[STRUCT_T]], ptr [[ARRAYDECAY3]], i32 0, i32 0
// CHECK-NEXT: [[ARRAYDECAY5:%.*]] = getelementptr inbounds [12 x i8], ptr [[BUFFER4]], i64 0, i64 0
// CHECK-NEXT: store ptr [[ARRAYDECAY5]], ptr [[S_PTR]], align 8
// CHECK-NEXT: [[TMP0:%.*]] = load ptr, ptr [[S_PTR]], align 8
// CHECK-NEXT: [[X6:%.*]] = getelementptr inbounds [[STRUCT_S]], ptr [[TMP0]], i32 0, i32 0
// CHECK-NEXT: [[X6:%.*]] = getelementptr inbounds nuw [[STRUCT_S]], ptr [[TMP0]], i32 0, i32 0
// CHECK-NEXT: [[TMP1:%.*]] = load i32, ptr [[X6]], align 4
// CHECK-NEXT: ret i32 [[TMP1]]
//
Expand Down
32 changes: 16 additions & 16 deletions clang/test/C/C2y/n3259.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,52 +12,52 @@ void test() {
// CHECK: store float 0
_Complex float f = __builtin_complex(1.0f, 0.0f);

// CHECK: %[[F_REALP1:.+]] = getelementptr inbounds { float, float }, ptr %[[F]], i32 0, i32 0
// CHECK: %[[F_REALP1:.+]] = getelementptr inbounds nuw { float, float }, ptr %[[F]], i32 0, i32 0
// CHECK-NEXT: %[[F_REAL:.+]] = load float, ptr %[[F_REALP1]]
// CHECK-NEXT: %[[F_IMAGP2:.+]] = getelementptr inbounds { float, float }, ptr %[[F]], i32 0, i32 1
// CHECK-NEXT: %[[F_IMAGP2:.+]] = getelementptr inbounds nuw { float, float }, ptr %[[F]], i32 0, i32 1
// CHECK-NEXT: %[[F_IMAG:.+]] = load float, ptr %[[F_IMAGP2]]
// CHECK-NEXT: %[[INC:.+]] = fadd float %[[F_REAL]], 1.000000e+00
// CHECK-NEXT: %[[F_REALP3:.+]] = getelementptr inbounds { float, float }, ptr %[[F]], i32 0, i32 0
// CHECK-NEXT: %[[F_IMAGP4:.+]] = getelementptr inbounds { float, float }, ptr %[[F]], i32 0, i32 1
// CHECK-NEXT: %[[F_REALP3:.+]] = getelementptr inbounds nuw { float, float }, ptr %[[F]], i32 0, i32 0
// CHECK-NEXT: %[[F_IMAGP4:.+]] = getelementptr inbounds nuw { float, float }, ptr %[[F]], i32 0, i32 1
// CHECK-NEXT: store float %[[INC]], ptr %[[F_REALP3]]
// CHECK-NEXT: store float %[[F_IMAG]], ptr %[[F_IMAGP4]]
f++; /* expected-warning {{'++' on an object of complex type is a C2y extension}}
pre-c2y-warning {{'++' on an object of complex type is incompatible with C standards before C2y}}
*/

// CHECK: %[[F_REALP5:.+]] = getelementptr inbounds { float, float }, ptr %[[F]], i32 0, i32 0
// CHECK: %[[F_REALP5:.+]] = getelementptr inbounds nuw { float, float }, ptr %[[F]], i32 0, i32 0
// CHECK-NEXT: %[[F_REAL6:.+]] = load float, ptr %[[F_REALP5]]
// CHECK-NEXT: %[[F_IMAGP7:.+]] = getelementptr inbounds { float, float }, ptr %[[F]], i32 0, i32 1
// CHECK-NEXT: %[[F_IMAGP7:.+]] = getelementptr inbounds nuw { float, float }, ptr %[[F]], i32 0, i32 1
// CHECK-NEXT: %[[F_IMAG8:.+]] = load float, ptr %[[F_IMAGP7]]
// CHECK-NEXT: %[[INC9:.+]] = fadd float %[[F_REAL6]], 1.000000e+00
// CHECK-NEXT: %[[F_REALP10:.+]] = getelementptr inbounds { float, float }, ptr %[[F]], i32 0, i32 0
// CHECK-NEXT: %[[F_IMAGP11:.+]] = getelementptr inbounds { float, float }, ptr %[[F]], i32 0, i32 1
// CHECK-NEXT: %[[F_REALP10:.+]] = getelementptr inbounds nuw { float, float }, ptr %[[F]], i32 0, i32 0
// CHECK-NEXT: %[[F_IMAGP11:.+]] = getelementptr inbounds nuw { float, float }, ptr %[[F]], i32 0, i32 1
// CHECK-NEXT: store float %[[INC9]], ptr %[[F_REALP10]]
// CHECK-NEXT: store float %[[F_IMAG8]], ptr %[[F_IMAGP11]]
++f; /* expected-warning {{'++' on an object of complex type is a C2y extension}}
pre-c2y-warning {{'++' on an object of complex type is incompatible with C standards before C2y}}
*/

// CHECK: %[[F_REALP12:.+]] = getelementptr inbounds { float, float }, ptr %[[F]], i32 0, i32 0
// CHECK: %[[F_REALP12:.+]] = getelementptr inbounds nuw { float, float }, ptr %[[F]], i32 0, i32 0
// CHECK-NEXT: %[[F_REAL13:.+]] = load float, ptr %[[F_REALP12]]
// CHECK-NEXT: %[[F_IMAGP14:.+]] = getelementptr inbounds { float, float }, ptr %[[F]], i32 0, i32 1
// CHECK-NEXT: %[[F_IMAGP14:.+]] = getelementptr inbounds nuw { float, float }, ptr %[[F]], i32 0, i32 1
// CHECK-NEXT: %[[F_IMAG15:.+]] = load float, ptr %[[F_IMAGP14]]
// CHECK-NEXT: %[[DEC:.+]] = fadd float %[[F_REAL13]], -1.000000e+00
// CHECK-NEXT: %[[F_REALP16:.+]] = getelementptr inbounds { float, float }, ptr %[[F]], i32 0, i32 0
// CHECK-NEXT: %[[F_IMAGP17:.+]] = getelementptr inbounds { float, float }, ptr %[[F]], i32 0, i32 1
// CHECK-NEXT: %[[F_REALP16:.+]] = getelementptr inbounds nuw { float, float }, ptr %[[F]], i32 0, i32 0
// CHECK-NEXT: %[[F_IMAGP17:.+]] = getelementptr inbounds nuw { float, float }, ptr %[[F]], i32 0, i32 1
// CHECK-NEXT: store float %[[DEC]], ptr %[[F_REALP16]]
// CHECK-NEXT: store float %[[F_IMAG15]], ptr %[[F_IMAGP17]]
f--; /* expected-warning {{'--' on an object of complex type is a C2y extension}}
pre-c2y-warning {{'--' on an object of complex type is incompatible with C standards before C2y}}
*/

// CHECK: %[[F_REALP18:.+]] = getelementptr inbounds { float, float }, ptr %[[F]], i32 0, i32 0
// CHECK: %[[F_REALP18:.+]] = getelementptr inbounds nuw { float, float }, ptr %[[F]], i32 0, i32 0
// CHECK-NEXT: %[[F_REAL19:.+]] = load float, ptr %[[F_REALP18]]
// CHECK-NEXT: %[[F_IMAGP20:.+]] = getelementptr inbounds { float, float }, ptr %[[F]], i32 0, i32 1
// CHECK-NEXT: %[[F_IMAGP20:.+]] = getelementptr inbounds nuw { float, float }, ptr %[[F]], i32 0, i32 1
// CHECK-NEXT: %[[F_IMAG21:.+]] = load float, ptr %[[F_IMAGP20]]
// CHECK-NEXT: %[[DEC22:.+]] = fadd float %[[F_REAL19]], -1.000000e+00
// CHECK-NEXT: %[[F_REALP23:.+]] = getelementptr inbounds { float, float }, ptr %[[F]], i32 0, i32 0
// CHECK-NEXT: %[[F_IMAGP24:.+]] = getelementptr inbounds { float, float }, ptr %[[F]], i32 0, i32 1
// CHECK-NEXT: %[[F_REALP23:.+]] = getelementptr inbounds nuw { float, float }, ptr %[[F]], i32 0, i32 0
// CHECK-NEXT: %[[F_IMAGP24:.+]] = getelementptr inbounds nuw { float, float }, ptr %[[F]], i32 0, i32 1
// CHECK-NEXT: store float %[[DEC22]], ptr %[[F_REALP23]]
// CHECK-NEXT: store float %[[F_IMAG21]], ptr %[[F_IMAGP24]]
--f; /* expected-warning {{'--' on an object of complex type is a C2y extension}}
Expand Down
12 changes: 6 additions & 6 deletions clang/test/CodeGen/PowerPC/powerpc-c99complex.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ _Complex float foo1(_Complex float x) {
// CHECK: ret { float, float }

// PPC32LNX-LABEL: define{{.*}} void @foo1(ptr dead_on_unwind noalias writable sret({ float, float }) align 4 %agg.result, ptr noundef byval({ float, float }) align 4 %x) #0 {
// PPC32LNX: [[RETREAL:%.*]] = getelementptr inbounds { float, float }, ptr %agg.result, i32 0, i32 0
// PPC32LNX-NEXT: [[RETIMAG:%.*]] = getelementptr inbounds { float, float }, ptr %agg.result, i32 0, i32 1
// PPC32LNX: [[RETREAL:%.*]] = getelementptr inbounds nuw { float, float }, ptr %agg.result, i32 0, i32 0
// PPC32LNX-NEXT: [[RETIMAG:%.*]] = getelementptr inbounds nuw { float, float }, ptr %agg.result, i32 0, i32 1
// PPC32LNX-NEXT: store float %{{.*}}, ptr [[RETREAL]], align 4
// PPC32LNX-NEXT: store float %{{.*}}, ptr [[RETIMAG]], align 4
}
Expand All @@ -22,8 +22,8 @@ _Complex double foo2(_Complex double x) {
// CHECK: ret { double, double }

// PPC32LNX-LABEL: define{{.*}} void @foo2(ptr dead_on_unwind noalias writable sret({ double, double }) align 8 %agg.result, ptr noundef byval({ double, double }) align 8 %x) #0 {
// PPC32LNX: [[RETREAL:%.*]] = getelementptr inbounds { double, double }, ptr %agg.result, i32 0, i32 0
// PPC32LNX-NEXT: [[RETIMAG:%.*]] = getelementptr inbounds { double, double }, ptr %agg.result, i32 0, i32 1
// PPC32LNX: [[RETREAL:%.*]] = getelementptr inbounds nuw { double, double }, ptr %agg.result, i32 0, i32 0
// PPC32LNX-NEXT: [[RETIMAG:%.*]] = getelementptr inbounds nuw { double, double }, ptr %agg.result, i32 0, i32 1
// PPC32LNX-NEXT: store double %{{.*}}, ptr [[RETREAL]], align 8
// PPC32LNX-NEXT: store double %{{.*}}, ptr [[RETIMAG]], align 8
}
Expand All @@ -37,8 +37,8 @@ _Complex long double foo3(_Complex long double x) {
// CHECK-LDBL128: ret { ppc_fp128, ppc_fp128 }

// PPC32LNX-LABEL: define{{.*}} void @foo3(ptr dead_on_unwind noalias writable sret({ ppc_fp128, ppc_fp128 }) align 16 %agg.result, ptr noundef byval({ ppc_fp128, ppc_fp128 }) align 16 %x) #0 {
// PPC32LNX: [[RETREAL:%.*]] = getelementptr inbounds { ppc_fp128, ppc_fp128 }, ptr %agg.result, i32 0, i32 0
// PPC32LNX-NEXT: [[RETIMAG:%.*]] = getelementptr inbounds { ppc_fp128, ppc_fp128 }, ptr %agg.result, i32 0, i32 1
// PPC32LNX: [[RETREAL:%.*]] = getelementptr inbounds nuw { ppc_fp128, ppc_fp128 }, ptr %agg.result, i32 0, i32 0
// PPC32LNX-NEXT: [[RETIMAG:%.*]] = getelementptr inbounds nuw { ppc_fp128, ppc_fp128 }, ptr %agg.result, i32 0, i32 1
// PPC32LNX-NEXT: store ppc_fp128 %{{.*}}, ptr [[RETREAL]], align 16
// PPC32LNX-NEXT: store ppc_fp128 %{{.*}}, ptr [[RETIMAG]], align 16
}
2 changes: 1 addition & 1 deletion clang/test/CodeGen/PowerPC/ppc64le-varargs-f128.c
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ void long_double(int n, ...) {
// IEEE: %[[V0:[0-9a-zA-Z_.]+]] = getelementptr inbounds i8, ptr %[[ALIGN]], i64 16
// IEEE: store ptr %[[V0]], ptr %[[AP]], align 8
// IEEE: call void @llvm.memcpy.p0.p0.i64(ptr align 16 %[[TMP:[0-9a-zA-Z_.]+]], ptr align 16 %[[ALIGN]], i64 16, i1 false)
// IEEE: %[[COERCE:[0-9a-zA-Z_.]+]] = getelementptr inbounds %struct.ldbl128_s, ptr %[[TMP]], i32 0, i32 0
// IEEE: %[[COERCE:[0-9a-zA-Z_.]+]] = getelementptr inbounds nuw %struct.ldbl128_s, ptr %[[TMP]], i32 0, i32 0
// IEEE: %[[V4:[0-9a-zA-Z_.]+]] = load fp128, ptr %[[COERCE]], align 16
// IEEE: call void @foo_ls(fp128 inreg %[[V4]])
// IEEE: call void @llvm.va_end.p0(ptr %[[AP]])
Expand Down
Loading

0 comments on commit 0daf16a

Please sign in to comment.