Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WRONG code CFGOpt? InstCombine? #49032

Closed
JonPsson opened this issue Mar 22, 2021 · 7 comments
Closed

WRONG code CFGOpt? InstCombine? #49032

JonPsson opened this issue Mar 22, 2021 · 7 comments
Assignees
Labels
bugzilla Issues migrated from bugzilla

Comments

@JonPsson
Copy link
Contributor

Bugzilla Link 49688
Resolution FIXED
Resolved on May 10, 2021 18:51
Version trunk
OS Linux
Attachments reduced testcase
CC @aqjune,@LebedevRI,@JonPsson,@rotateright,@zsrkmyn
Fixed by commit(s) 5207cde

Extended Description

This program (wrong0.i):

static int *a;
int b, d;
int *c = &b;
int main() {
int e[3] = {2, 1, -2139220656};
a = &e[0];
d = (e[2] < 0) || (e[2] > (7 >> e[2]));
*c = d;
printf("%d\n", b);
}

I beleive the right-shift with a known negative value is undefined, but since the first expression is always true, the program as a whole is still well-defined, or?

Since e[2] is always less than 0, I would expect the second expression (after the '||') to never be evaluated. However, SimplifyCFGPass does not understand that the first expression is always true and so merges them so that they are always both executed. I wonder why this is allowed...?

%1 = load i32, i32* %arrayidx1, align 4, !tbaa !​6
%cmp = icmp slt i32 %1, 0
%shr = ashr i32 7, %1
%cmp4 = icmp sgt i32 %1, %shr
%2 = select i1 %cmp, i1 true, i1 %cmp4

Then InstCombine decides to remove the first check against less than zero:

%arrayidx1 = getelementptr inbounds [3 x i32], [3 x i32]* %e, i64 0, i64 2
%1 = load i32, i32* %arrayidx1, align 4, !tbaa !​6
%shr = lshr i32 7, %1
%2 = icmp ugt i32 %1, %shr
%lor.ext = zext i1 %2 to i32
store i32 %lor.ext, i32* @​d, align 4, !tbaa !​6

So it seems that the user has written a well-defined program, but the compiler has failed to realize this through the known constants.

clang -march=arch13 -O1 wrong0.i -o a.out -w; ./a.out
1
clang -march=arch13 -O2 wrong0.i -o a.out -w; ./a.out
0
clang -march=arch13 -O2 wrong0.i -o a.out -mllvm -enable-gvn-memdep=false -w; ./a.out
1
clang -march=arch13 -O2 wrong0.i -o a.out -mllvm -memssa-check-limit=0 -w; ./a.out
0

The store of '0' to @​d is then later produced by GVN:

*** IR Dump After MergedLoadStoreMotionPass ***
; Function Attrs: nofree nounwind
define dso_local signext i32 @​main() local_unnamed_addr #​0 {
entry:
%e = alloca [3 x i32], align 4
%0 = bitcast [3 x i32]* %e to i8*
call void @​llvm.lifetime.start.p0i8(i64 12, i8* nonnull %0) #​2
call void @​llvm.memcpy.p0i8.p0i8.i64(i8* noundef nonnull align 4 dereferenceable(12) %0, i8* noundef nonnull align 4 dereferenceable(12) bitcast ([3 x i32]* @​__const.main.e to i8*), i64 12, i1 false)
%arrayidx = getelementptr inbounds [3 x i32], [3 x i32]* %e, i64 0, i64 0
store i32* %arrayidx, i32** @​a, align 8, !tbaa !​2
%arrayidx1 = getelementptr inbounds [3 x i32], [3 x i32]* %e, i64 0, i64 2
%1 = load i32, i32* %arrayidx1, align 4, !tbaa !​6
%shr = lshr i32 7, %1
%2 = icmp ugt i32 %1, %shr
%lor.ext = zext i1 %2 to i32
store i32 %lor.ext, i32* @​d, align 4, !tbaa !​6
%3 = load i32*, i32** @​c, align 8, !tbaa !​2
store i32 %lor.ext, i32* %3, align 4, !tbaa !​6
%4 = load i32, i32* @​b, align 4, !tbaa !​6
%call = call signext i32 (i8*, ...) @​printf(i8* nonnull dereferenceable(1) getelementptr inbounds ([4 x i8], [4 x i8]* @.str, i64 0, i64 0), i32 signext %4)
call void @​llvm.lifetime.end.p0i8(i64 12, i8* nonnull %0) #​2
ret i32 0
}

*** IR Dump After GVN ***
; Function Attrs: nofree nounwind
define dso_local signext i32 @​main() local_unnamed_addr #​0 {
entry:
%e = alloca [3 x i32], align 4
%0 = bitcast [3 x i32]* %e to i8*
call void @​llvm.lifetime.start.p0i8(i64 12, i8* nonnull %0) #​2
call void @​llvm.memcpy.p0i8.p0i8.i64(i8* noundef nonnull align 4 dereferenceable(12) %0, i8* noundef nonnull align 4 dereferenceable(12) bitcast ([3 x i32]* @​__const.main.e to i8*), i64 12, i1 false)
%arrayidx = getelementptr inbounds [3 x i32], [3 x i32]* %e, i64 0, i64 0
store i32* %arrayidx, i32** @​a, align 8, !tbaa !​2
%arrayidx1 = getelementptr inbounds [3 x i32], [3 x i32]* %e, i64 0, i64 2
store i32 0, i32* @​d, align 4, !tbaa !​6
%1 = load i32*, i32** @​c, align 8, !tbaa !​2
store i32 0, i32* %1, align 4, !tbaa !​6
%2 = load i32, i32* @​b, align 4, !tbaa !​6
%call = call signext i32 (i8*, ...) @​printf(i8* nonnull dereferenceable(1) getelementptr inbounds ([4 x i8], [4 x i8]* @.str, i64 0, i64 0), i32 signext %2)
call void @​llvm.lifetime.end.p0i8(i64 12, i8* nonnull %0) #​2
ret i32 0
}

@JonPsson
Copy link
Contributor Author

assigned to @aqjune

@aqjune
Copy link
Contributor

aqjune commented Mar 23, 2021

A reduced input:

define i1 @&#8203;f(i32 %i1) {
entry:
  %cmp = icmp slt i32 %i1, 0
  %shr = ashr i32 7, %i1
  %cmp4 = icmp sgt i32 %i1, %shr
  %i2 = select i1 %cmp, i1 true, i1 %cmp4
  ret i1 %i2
}

It's again due to the old transformation in InstCombine 'select i1 %a, i1 true, i1 %b' => 'or i1 %a, %b'.
The former expression effectively stops propagation of the result of invalid shift whereas the latter one allows its propagation, starting reasoning between two icmps and simplifying the result.

The transformation should be disabled; I'm sending patches to minimize performance impact after disabling it (https://reviews.llvm.org/D93065).

@JonPsson
Copy link
Contributor Author

I think I saw this again:

define dso_local signext i32 @​f(i32 signext %g, i32 zeroext %h) local_unnamed_addr #​0 {
entry:
%cmp = icmp slt i32 %g, 0
%shr = ashr i32 7, %h
%cmp1 = icmp sgt i32 %g, %shr
%0 = select i1 %cmp, i1 true, i1 %cmp1
%lor.ext = zext i1 %0 to i32
ret i32 %lor.ext
}

=>

*** IR Dump After InstCombinePass ***
define dso_local signext i32 @​f(i32 signext %g, i32 zeroext %h) local_unnamed_addr #​0 {
entry:
%shr = lshr i32 7, %h
%0 = icmp ult i32 %shr, %g
%lor.ext = zext i1 %0 to i32
ret i32 %lor.ext
}

This doesn't look right - the check if g < 0 is simply removed for some reason.

opt -O2 fun.ll -march=z14

Input:
define i32 @​f(i32 signext %g, i32 zeroext %h) #​0 {
entry:
%g.addr = alloca i32, align 4
%h.addr = alloca i32, align 4
store i32 %g, i32* %g.addr, align 4
store i32 %h, i32* %h.addr, align 4
%0 = load i32, i32* %g.addr, align 4
%cmp = icmp slt i32 %0, 0
br i1 %cmp, label %lor.end, label %lor.rhs

lor.rhs: ; preds = %entry
%1 = load i32, i32* %g.addr, align 4
%2 = load i32, i32* %h.addr, align 4
%shr = ashr i32 7, %2
%cmp1 = icmp sgt i32 %1, %shr
br label %lor.end

lor.end: ; preds = %lor.rhs, %entry
%3 = phi i1 [ true, %entry ], [ %cmp1, %lor.rhs ]
%lor.ext = zext i1 %3 to i32
ret i32 %lor.ext
}

@LebedevRI
Copy link
Member

I think I saw this again:

define dso_local signext i32 @​f(i32 signext %g, i32 zeroext %h)
local_unnamed_addr #​0 {
entry:
%cmp = icmp slt i32 %g, 0
%shr = ashr i32 7, %h
%cmp1 = icmp sgt i32 %g, %shr
%0 = select i1 %cmp, i1 true, i1 %cmp1
%lor.ext = zext i1 %0 to i32
ret i32 %lor.ext
}

=>

*** IR Dump After InstCombinePass ***
define dso_local signext i32 @​f(i32 signext %g, i32 zeroext %h)
local_unnamed_addr #​0 {
entry:
%shr = lshr i32 7, %h
%0 = icmp ult i32 %shr, %g
%lor.ext = zext i1 %0 to i32
ret i32 %lor.ext
}

This doesn't look right - the check if g < 0 is simply removed for some
reason.

opt -O2 fun.ll -march=z14

Input:
define i32 @​f(i32 signext %g, i32 zeroext %h) #​0 {
entry:
%g.addr = alloca i32, align 4
%h.addr = alloca i32, align 4
store i32 %g, i32* %g.addr, align 4
store i32 %h, i32* %h.addr, align 4
%0 = load i32, i32* %g.addr, align 4
%cmp = icmp slt i32 %0, 0
br i1 %cmp, label %lor.end, label %lor.rhs

lor.rhs: ; preds = %entry

%1 = load i32, i32* %g.addr, align 4
%2 = load i32, i32* %h.addr, align 4
%shr = ashr i32 7, %2
%cmp1 = icmp sgt i32 %1, %shr
br label %lor.end

lor.end: ; preds = %lor.rhs, %entry

%3 = phi i1 [ true, %entry ], [ %cmp1, %lor.rhs ]
%lor.ext = zext i1 %3 to i32
ret i32 %lor.ext
}

Yep, https://alive2.llvm.org/ce/z/NGCDRB

@aqjune
Copy link
Contributor

aqjune commented Mar 31, 2021

I'll make a patch that conditionally disables select -> and/or transformation. I think now it's time to start ditching out the optimization.

@aqjune
Copy link
Contributor

aqjune commented Mar 31, 2021

@aqjune
Copy link
Contributor

aqjune commented Apr 9, 2021

@llvmbot llvmbot transferred this issue from llvm/llvm-bugzilla-archive Dec 11, 2021
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bugzilla Issues migrated from bugzilla
Projects
None yet
Development

No branches or pull requests

3 participants