[PATCH] D75799: [JumpThreading] Don't create PHI nodes with "is.constant" values
Bill Wendling via Phabricator via llvm-commits
llvm-commits at lists.llvm.org
Mon Mar 9 14:36:56 PDT 2020
void added a comment.
In D75799#1913287 <https://reviews.llvm.org/D75799#1913287>, @joerg wrote:
> It still seems to be a perfectly reasonable optimisation under the semantics of `is.constant`. The code here seems to be an abuse of it though and makes assumptions that are not sensible.
The code is abusi^Wusing how `__builtin_constant_p()` works in gcc. It's honored even after inlining. But while that's icky, what's going on here is not quite that.
int val;
if (get_user(len, optlen))
return -EFAULT;
len = min_t(unsigned int, len, sizeof(int));
if (len < 0)
return -EINVAL;
switch (optname) {
...
case TCP_ZEROCOPY_RECEIVE: {
struct tcp_zerocopy_receive zc;
int err;
if (get_user(len, optlen))
return -EFAULT;
if (len < offsetofend(struct tcp_zerocopy_receive, length))
return -EINVAL;
if (len > sizeof(zc)) {
len = sizeof(zc);
if (put_user(len, optlen))
return -EFAULT;
}
if (copy_from_user(&zc, optval, len))
return -EFAULT;
lock_sock(sk);
err = tcp_zerocopy_receive(sk, &zc);
release_sock(sk);
if (len == sizeof(zc))
goto zerocopy_rcv_sk_err;
switch (len) {
case offsetofend(struct tcp_zerocopy_receive, err):
goto zerocopy_rcv_sk_err;
case offsetofend(struct tcp_zerocopy_receive, inq):
goto zerocopy_rcv_inq;
case offsetofend(struct tcp_zerocopy_receive, length):
default:
goto zerocopy_rcv_out;
}
zerocopy_rcv_sk_err:
if (!err)
zc.err = sock_error(sk);
zerocopy_rcv_inq:
zc.inq = tcp_inq_hint(sk);
zerocopy_rcv_out:
if (!err && copy_to_user(optval, &zc, len))
err = -EFAULT;
return err;
}
The `len` variable is assigned in several places here. In only one place does it get a constant value: the `len = sizeof(zc)` line. The `copy_from_user` and `copy_to_user` are inlined. They call a function (also inlined) to check the object size for overwrites:
_Bool check_copy_size(const void *addr, unsigned long bytes, _Bool is_source)
{
int sz = __builtin_object_size(addr, 0);
if (__builtin_expect(!!(sz >= 0 && sz < bytes), 0)) {
if (!__builtin_constant_p(bytes))
copy_overwrite(sz, bytes);
else if (is_source)
__bad_copy_from();
else
__bad_copy_to();
return false;
}
check_object_size(addr, bytes, is_source);
return true;
}
The `__builtin_constant_p(bytes)` needs to return `false`, otherwise we'll get a link error because the `__bad_copy_*` functions don't exist. (Note: `bytes` is `len` here.)
Now, because `len` doesn't change between the `copy_from_user` and `copy_to_user` calls, the check if it's constant is combined. Now jump threading comes along and essentially converts that code into one where the `if (len == sizeof(zc))` statement is split into two paths: one with `len` constant and the other with it non-constant. Those two combine into a PHI node (actually multiple PHI nodes) and that's what's used within the `copy_to_user` call. The `copy_from_user` call seems content to use the non-constant path (probably because of interaction between inlining and jump threading, because removing some of the calls between the two `copy_*_user` calls gets rid of the problem).
Threading through the `if (len == sizeof(zc))` statement would be fine if it weren't for the fact that programmers rely upon `is.constant` to be false when not all paths to it have constant values for the operand.
Repository:
rG LLVM Github Monorepo
CHANGES SINCE LAST ACTION
https://reviews.llvm.org/D75799/new/
https://reviews.llvm.org/D75799
More information about the llvm-commits
mailing list