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

tailcallelim introduces write to readonly byval parameter #64289

Closed
erikdesjardins opened this issue Aug 1, 2023 · 8 comments · Fixed by llvm/llvm-project-release-prs#556
Closed

Comments

@erikdesjardins
Copy link
Member

erikdesjardins commented Aug 1, 2023

With the following IR:

define void @foo(ptr readonly byval(i64) %x) {
start:
  %new_x = alloca i64, align 8
  store i64 0, ptr %new_x, align 8
  call void @foo(ptr %new_x)
  ret void
}

opt -passes="tailcallelim" results in (https://godbolt.org/z/9arGs5xhx):

define void @foo(ptr readonly byval(i64) %x) {
  %new_x1 = alloca i64, align 1
  %new_x = alloca i64, align 8
  br label %tailrecurse

tailrecurse:                                      ; preds = %tailrecurse, %start
  store i64 0, ptr %new_x, align 8
  call void @llvm.memcpy.p0.p0.i64(ptr align 1 %new_x1, ptr align 1 %new_x, i64 8, i1 false)
  call void @llvm.memcpy.p0.p0.i64(ptr align 1 %x, ptr align 1 %new_x1, i64 8, i1 false)
  br label %tailrecurse
}

declare void @llvm.memcpy.p0.p0.i64(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i64, i1 immarg)

%x is now written to, but it still has its readonly attribute.

LangRef seems to imply that this is illegal:

The copy is considered to belong to the caller not the callee (for example, readonly functions should not write to byval parameters).

However, Alive2 is fine with writes to readonly byval arguments (but does flag identical IR without byval): https://alive2.llvm.org/ce/z/T6yxU-


This leads to an end-to-end miscompile, where the following recursion (always terminating after one call):

define void @foo(ptr noalias byval(i64) %x) {
start:
  %new_x = alloca i64, align 8
  %x_val = load i64, ptr %x, align 8
  %is_zero = icmp eq i64 %x_val, 0
  br i1 %is_zero, label %end, label %recurse

recurse:
  store i64 0, ptr %new_x, align 8
  call void @foo(ptr %new_x)
  br label %end

end:
  ret void
}

is converted to an infinite loop with opt -O3 (https://godbolt.org/z/9zbn6hno9):

define void @foo(ptr noalias nocapture readonly byval(i64) %x) local_unnamed_addr #0 {
  %x_val = load i64, ptr %x, align 8
  %is_zero = icmp eq i64 %x_val, 0
  br i1 %is_zero, label %end, label %recurse

recurse:                                          ; preds = %start, %recurse
  br label %recurse

end:                                              ; preds = %start
  ret void
}

If LangRef is right, the bug is in tailcallelim. If Alive2 is right, the bug is in whatever other transforms assume that readonly is meaningful on byval arguments.

Upstream issue: rust-lang/rust#114312

@DianQK
Copy link
Member

DianQK commented Aug 1, 2023

If Alive2 is correct, it is possible that LICM caused this error.
https://llvm.godbolt.org/z/sxTrYYq6q.

@DianQK
Copy link
Member

DianQK commented Aug 1, 2023

If Alive2 is correct, it is possible that LICM caused this error. https://llvm.godbolt.org/z/sxTrYYq6q.

Bisected: 01859da.
Maybe we should remove noalias/readonly in tailcallelim?
cc @pcwalton @nikic

@nikic
Copy link
Contributor

nikic commented Aug 1, 2023

Yes, I think we should be dropping readonly in tailcallelim.

Generally, changing function attributes during a (non-IPO) transform is very fishy, in particular because the attribute might have been propagated out to callers and used there.

However, the situation with byval + readonly is quite special, as the note in LangRef indicates. From the perspective of a caller, byval is basically always "readonly", in that the memory passed to the argument will always be copied to the byval stack slot and never modified. The readonly attribute in this case only describes the internal memory effects of the function, not the effects visible to the caller. As such, I believe that dropping readonly during the transform would be safe in this particular case.

@DianQK
Copy link
Member

DianQK commented Aug 1, 2023

I try to submit a patch that removes readonly.
https://reviews.llvm.org/D156793

@DianQK DianQK added this to the LLVM 17.0.X Release milestone Aug 2, 2023
@DianQK DianQK closed this as completed in c3f227e Aug 8, 2023
@DianQK
Copy link
Member

DianQK commented Aug 8, 2023

Reopen and wait for the post CI to look ok then pick to 17.

@DianQK DianQK reopened this Aug 8, 2023
DianQK added a commit to DianQK/llvm-project that referenced this issue Aug 8, 2023
When eliminating a tail call, we modify the values of the arguments.
Therefore, if the byval parameter has a readonly attribute, we have to remove it. It is safe because,
from the perspective of a caller, the byval parameter is always treated as "readonly," even if the readonly attribute is removed.

Fixes llvm#64289.

Reviewed By: nikic

Differential Revision: https://reviews.llvm.org/D156793

(cherry picked from commit c3f227e)
@DianQK
Copy link
Member

DianQK commented Aug 9, 2023

/cherry-pick b77e556 c3f227e

@llvmbot
Copy link
Collaborator

llvmbot commented Aug 9, 2023

/branch llvm/llvm-project-release-prs/issue64289

@llvmbot
Copy link
Collaborator

llvmbot commented Aug 9, 2023

/pull-request llvm/llvm-project-release-prs#556

tru pushed a commit that referenced this issue Aug 9, 2023
When eliminating a tail call, we modify the values of the arguments.
Therefore, if the byval parameter has a readonly attribute, we have to remove it. It is safe because,
from the perspective of a caller, the byval parameter is always treated as "readonly," even if the readonly attribute is removed.

Fixes #64289.

Reviewed By: nikic

Differential Revision: https://reviews.llvm.org/D156793

(cherry picked from commit c3f227e)
cuviper pushed a commit to rust-lang/llvm-project that referenced this issue Aug 10, 2023
When eliminating a tail call, we modify the values of the arguments.
Therefore, if the byval parameter has a readonly attribute, we have to remove it. It is safe because,
from the perspective of a caller, the byval parameter is always treated as "readonly," even if the readonly attribute is removed.

Fixes llvm#64289.

Reviewed By: nikic

Differential Revision: https://reviews.llvm.org/D156793

(cherry picked from commit c3f227e)
@DianQK DianQK self-assigned this Sep 4, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment