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

Refactor C FFI variadics to more closely match their C counterparts, and add Clone implementation #59625

Merged
merged 1 commit into from
Jun 19, 2019

Conversation

ahomescu
Copy link
Contributor

@ahomescu ahomescu commented Apr 2, 2019

We had to make some changes to expose va_copy and va_end directly to users (mainly for C2Rust, but not exclusively):

  • redefine the Rust variadic structures to more closely correspond to C: VaList now matches va_list, and VaListImpl matches __va_list_tag
  • add Clone for VaListImpl
  • add explicit as_va_list() conversion function from VaListImpl to VaList
  • add deref coercion from VaList to VaListImpl
  • add support for the asmjs target

All these changes were needed for use cases like:

let mut ap2 = va_copy(ap);
vprintf(fmt, ap2);
va_end(&mut ap2);

@rust-highfive
Copy link
Collaborator

Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @sfackler (or someone else) soon.

If any changes to this PR are deemed necessary, please add them as extra commits. This ensures that the reviewer can see what has changed since they last reviewed the code. Due to the way GitHub handles out-of-date commits, this should also make it reasonably obvious what issues have or haven't been addressed. Large or tricky changes may require several passes of review and changes.

Please see the contribution instructions for more information.

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Apr 2, 2019
@ahomescu
Copy link
Contributor Author

ahomescu commented Apr 2, 2019

r? @joshtriplett

@joshtriplett
Copy link
Member

cc @dlrobertson for API review.

Also, why does this move the intrinsics to core::intrinsics? That was pretty extensively discussed in the RFC thread, and many people didn't care for the idea of intrinsics in core::intrinsics becoming stable at some point. (And these should become stable at some point.)

@joshtriplett
Copy link
Member

I also wonder why these can't remain (unsafe) methods on a VaList rather than separate intrinsics.

@ahomescu
Copy link
Contributor Author

ahomescu commented Apr 2, 2019

Also, why does this move the intrinsics to core::intrinsics? That was pretty extensively discussed in the RFC thread, and many people didn't care for the idea of intrinsics in core::intrinsics becoming stable at some point. (And these should become stable at some point.)

That was @dlrobertson's suggestion, after I made the intrinsics public. The original implementation left them in ffi.rs and just made the functions public, I can revert to that.

I also wonder why these can't remain (unsafe) methods on a VaList rather than separate intrinsics.

I guess they could, they would just call out to the intrinsics internally.

@ahomescu
Copy link
Contributor Author

ahomescu commented Apr 2, 2019

I think there is one argument in favor of leaving the lower-level functions as intrinsics: users should generally use the higher-level with_copy function, and only call the intrinsics as a last resort.

@dlrobertson
Copy link
Contributor

Also, why does this move the intrinsics to core::intrinsics? That was pretty extensively discussed in the RFC thread, and many people didn't care for the idea of intrinsics in core::intrinsics becoming stable at some point. (And these should become stable at some point.)

I was not aware of that. I suggested this as I was hoping to "solve" the copy problem by making a va_copy intrinsic public.

I also wonder why these can't remain (unsafe) methods on a VaList rather than separate intrinsics.

Maybe. Definitely worth some experiments.

@joshtriplett
Copy link
Member

joshtriplett commented Apr 2, 2019 via email

};
let impl_ty = match inner_ty.sty {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use ty_adt_def here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I considered it myself, but I thought the code winds up about the same (match against Some instead of ty::Adt). I'll change it in the next commit.

// and store it in the the spoofed `VaList`.
let layout = self.layout_of(inner_ty);
let backing = PlaceRef::alloca(self, layout, "");
let src = self.load(args[0].immediate(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks flipped. In the prior version we load for non-pointer variants and don't load for pointer variants.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

impl_ty being a ty::Adt means we're in a struct variant; for pointer variants, impl_ty is a ty::Foreign. Both inner_ty and impl_ty are retrieved from va_list(), so impl_ty should always match the definition of VaListImpl.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In other words, ty::Adt for VaListImpl => struct variant => we want a load. Otherwise, ty::Foreign => pointer variant => no load. Did I get this wrong?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah right, this was flipped in the previous version, but that was due to the old signature of va_copy.

@ahomescu
Copy link
Contributor Author

ahomescu commented Apr 2, 2019

I'll make some changes and see how far I get, but there might be technical reasons why we can't use methods: the va_copy intrinsic internally builds a LLVM alloca which lives on the stack of va_copy's caller. If we add our own wrapper method, it would hijack that alloca; I wonder if making the wrapper #[inline(always)] fixes this?

@ahomescu
Copy link
Contributor Author

ahomescu commented Apr 2, 2019

I added this function to VaListMethods:

#[inline(always)]
unsafe fn raw_copy(&self) -> Self {
    va_copy(self)
}

and it gets inlined correctly in the optimized test file (c-variadic-opt.rs) but not in the debug one (c-variadic.rs). The bitcode I'm getting is:

; <&mut core::ffi::VaListInner as core::ffi::VaListMethods>::raw_copy
; Function Attrs: alwaysinline nonlazybind uwtable
define internal align 8 dereferenceable(24) %"core::ffi::VaListInner"* @"_ZN79_$LT$$RF$mut$u20$core..ffi..VaListInner$u20$as$u20$core..ffi..VaListMethods$GT$8raw_copy17h78377ab98854a549E"(%"core::ffi::VaListInner"** noalias readonly align 8 dereferenceable(8) %self) unnamed_addr #1 {
start:
  %0 = alloca %"core::ffi::VaListInner", align 8
  %tmp_ret = alloca %"core::ffi::VaListInner"*, align 8
  %1 = bitcast %"core::ffi::VaListInner"** %tmp_ret to i8*
  call void @llvm.lifetime.start.p0i8(i64 8, i8* %1)
  %2 = load %"core::ffi::VaListInner"*, %"core::ffi::VaListInner"** %self, align 8
  store %"core::ffi::VaListInner"* %0, %"core::ffi::VaListInner"** %tmp_ret, align 8
  %3 = bitcast %"core::ffi::VaListInner"* %0 to i8*
  %4 = bitcast %"core::ffi::VaListInner"* %2 to i8*
  call void @llvm.va_copy(i8* %3, i8* %4)
  %5 = load %"core::ffi::VaListInner"*, %"core::ffi::VaListInner"** %tmp_ret, align 8, !nonnull !3
  %6 = bitcast %"core::ffi::VaListInner"** %tmp_ret to i8*
  call void @llvm.lifetime.end.p0i8(i64 8, i8* %6)
  br label %bb1

bb1:                                              ; preds = %start
  ret %"core::ffi::VaListInner"* %5
}

The alloca assigned to %0 should be in the caller, not in this function.

let intrinsic = self.cx().get_intrinsic(&("llvm.va_copy"));
self.call(intrinsic, &[llresult, va_list], None);
if impl_ty.ty_adt_def().is_some() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I switched to ty_adt_def both here and above.

@ahomescu
Copy link
Contributor Author

ahomescu commented Apr 2, 2019

The inlining works as expected for optimized builds, like in c-variadic-opt.rs. I'm not sure what options we have to move that alloca up one level from raw_copy into its caller.

@ahomescu
Copy link
Contributor Author

ahomescu commented Apr 3, 2019

@joshtriplett Moving the intrinsics back into ffi.rs from intrinsics.rs is simple, I can do that. I can also switch them back to private (users who really need them can redeclare them manually), but it would be more convenient if they stayed public.

I also wonder why these can't remain (unsafe) methods on a VaList rather than separate intrinsics.

This is getting problematic for technical reasons, see above. Any thoughts on how to solve the alloca issue?

@ahomescu
Copy link
Contributor Author

ahomescu commented Apr 9, 2019

The main blocker for this is to figure out if we can provide method wrappers like raw_copy (or with another name like unsafe_copy suggested by @joshtriplett), instead of exposing the intrinsics directly.
For that to work, we need a way to force-inline this method (even in debug mode):

#[inline(always)]
unsafe fn raw_copy(&self) -> Self {
    va_copy(self)
}

cc @rust-lang/compiler

@joshtriplett
Copy link
Member

Or we need some other way to ensure that the alloca inside va_copy happens in the caller.

Could someone from @rust-lang/compiler suggest a way to do that?

@nagisa
Copy link
Member

nagisa commented Apr 9, 2019

#[inline(always)] will inline even in "debug" mode.

@ahomescu
Copy link
Contributor Author

Sounds like something else is going on with the inlining, I'll investigate.

@ahomescu
Copy link
Contributor Author

According to https://github.com/rust-lang/rust/blob/master/src/librustc_mir/transform/inline.rs#L45, the Inline pass only runs for mir-opt-level >= 2. I set that to 2 manually in my test, and the inlining happened. I found at https://github.com/rust-lang/rust/blob/master/src/librustc/session/config.rs#L1320 that the default value is 1.

I could add a comment to unsafe_copy saying "this function requires -Z mir-opt-level=2 or higher", but I'm not sure that's the cleanest solution.

Separately, I discovered that, for some reason, the Playground always performs the inlining.

@nagisa
Copy link
Member

nagisa commented Apr 10, 2019

The inline and inline(always) are also handled by LLVM and LLVM is configured to run the always_inline pass whenever the crate contains at least one instance of #[inline] attribute. There is also code somewhere that sets -O1 whenever inlining has been required, but I’m unable to find it at the moment.

The relevant code is here. Playground shows the output of --emit=llvm-ir,asm which is emitted after the optimisations by LLVM are done.

@ahomescu
Copy link
Contributor Author

ahomescu commented Apr 10, 2019

I think I found the culprit: the test file had -C no-prepopulate-passes in the header. The unsafe_copy inlining test needs to go in its own separate file, and that seems to fix it.

@joshtriplett
Copy link
Member

@ahomescu Awesome!

}

pub unsafe extern "C" fn va_copy_variadic(ap: VaList) {
let mut ap2 = ap.unsafe_copy();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this test that checks that unsafe_copy gets inlined.

@ahomescu
Copy link
Contributor Author

All tests pass on x86_64 now (including the new ones).

@ahomescu
Copy link
Contributor Author

@nagisa The current status is: the inlining always happens, unless both -C no-prepopulate-passes has been passed and mir-opt-level is less than 2. It would be nice to emit an error or at least a warning (discussed this with @joshtriplett on Discord) by checking if either:

  1. unsafe_copy exists at all in LLVM IR after LlvmCodegenBackend::optimize() returns, or
  2. unsafe_copy is called, and the condition no_prepopulate_passes && mir_opt_level < 2 is true.

Any thoughts on how and where to add this?

@eddyb
Copy link
Member

eddyb commented Apr 11, 2019

I disagree with exposing the "raw copy", and think C2Rust should be performing the rewriting necessary (AFAIK it should always be possible EDIT: that is, when not UB).

Last time I was shown an example of non-scoped va_copy, it was a misuse of C loops, and Rust's loop makes the correctly scoped solution readily available.

@bors
Copy link
Contributor

bors commented Jun 18, 2019

📌 Commit b9ea653 has been approved by eddyb

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jun 18, 2019
@bors
Copy link
Contributor

bors commented Jun 18, 2019

⌛ Testing commit b9ea653 with merge d3619b53f40f03030deb93c690f87ee3157569d2...

@bors
Copy link
Contributor

bors commented Jun 18, 2019

💔 Test failed - checks-travis

@rust-highfive
Copy link
Collaborator

Your PR failed on Travis (raw log). Through arcane magic we have determined that the following fragments from the build log may contain information about the problem.

Click to expand the log.
$ sudo -E apt-get -yq --no-install-suggests --no-install-recommends $(travis_apt_get_options) install gdb
Reading package lists...
Building dependency tree...
Reading state information...
Some packages could not be installed. This may mean that you have
requested an impossible situation or if you are using the unstable
distribution that some required packages have not yet been created
or been moved out of Incoming.
The following information may help to resolve the situation:
The following packages have unmet dependencies:
 gdb : Depends: libbabeltrace-ctf1 (>= 1.2.1) but it is not installable
       Depends: libbabeltrace1 (>= 1.2.1) but it is not installable
E: Unable to correct problems, you have held broken packages.
travis_fold:start:apt-get.diagnostics
apt-get install failed
apt-get install failed
$ cat ${TRAVIS_HOME}/apt-get-update.log
Get:1 http://apt.postgresql.org/pub/repos/apt xenial-pgdg InRelease [51.5 kB]
Get:2 http://security.ubuntu.com/ubuntu xenial-security InRelease [109 kB]
Get:3 http://apt.postgresql.org/pub/repos/apt xenial-pgdg/main amd64 Packages [205 kB]
Get:4 http://apt.postgresql.org/pub/repos/apt xenial-pgdg/main i386 Packages [205 kB]
Get:6 http://security.ubuntu.com/ubuntu xenial-security/restricted Sources [2,243 B]
Get:7 http://security.ubuntu.com/ubuntu xenial-security/universe Sources [130 kB]
Get:8 http://security.ubuntu.com/ubuntu xenial-security/multiverse Sources [3,517 B]
Get:9 http://security.ubuntu.com/ubuntu xenial-security/main amd64 Packages [857 kB]
Get:9 http://security.ubuntu.com/ubuntu xenial-security/main amd64 Packages [857 kB]
Get:10 http://security.ubuntu.com/ubuntu xenial-security/main i386 Packages [704 kB]
Get:11 http://security.ubuntu.com/ubuntu xenial-security/main Translation-en [382 kB]
Get:12 http://security.ubuntu.com/ubuntu xenial-security/restricted amd64 Packages [12.7 kB]
Get:13 http://security.ubuntu.com/ubuntu xenial-security/restricted i386 Packages [12.7 kB]
Get:14 http://security.ubuntu.com/ubuntu xenial-security/restricted Translation-en [2,204 B]
Get:16 http://security.ubuntu.com/ubuntu xenial-security/universe i386 Packages [482 kB]
Get:17 http://security.ubuntu.com/ubuntu xenial-security/universe Translation-en [240 kB]
Get:18 http://security.ubuntu.com/ubuntu xenial-security/multiverse amd64 Packages [6,121 B]
Get:19 http://security.ubuntu.com/ubuntu xenial-security/multiverse i386 Packages [6,297 B]
Get:19 http://security.ubuntu.com/ubuntu xenial-security/multiverse i386 Packages [6,297 B]
Get:20 http://security.ubuntu.com/ubuntu xenial-security/multiverse Translation-en [2,699 B]
Err:21 http://us-east-1.ec2.archive.ubuntu.com/ubuntu xenial InRelease
  Could not connect to apt.cache.travis-ci.com:80 (34.96.81.152), connection timed out
Err:22 http://us-east-1.ec2.archive.ubuntu.com/ubuntu xenial-updates InRelease
  Unable to connect to apt.cache.travis-ci.com:http:
Err:23 http://us-east-1.ec2.archive.ubuntu.com/ubuntu xenial-backports InRelease
  Unable to connect to apt.cache.travis-ci.com:http:
Reading package lists...
Reading package lists...
W: Failed to fetch http://us-east-1.ec2.archive.ubuntu.com/ubuntu/dists/xenial/InRelease  Could not connect to apt.cache.travis-ci.com:80 (34.96.81.152), connection timed out
W: Failed to fetch http://us-east-1.ec2.archive.ubuntu.com/ubuntu/dists/xenial-updates/InRelease  Unable to connect to apt.cache.travis-ci.com:http:
W: Failed to fetch http://us-east-1.ec2.archive.ubuntu.com/ubuntu/dists/xenial-backports/InRelease  Unable to connect to apt.cache.travis-ci.com:http:
travis_fold:end:apt-get.diagnostics
travis_fold:end:apt-get.diagnostics
The command "sudo -E apt-get -yq --no-install-suggests --no-install-recommends $(travis_apt_get_options) install gdb" failed and exited with 100 during .
Your build has been stopped.

I'm a bot! I can only do what humans tell me to, so if this was not helpful or you have suggestions for improvements, please ping or otherwise contact @TimNN. (Feature Requests)

@bors bors added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. labels Jun 18, 2019
@ljedrz
Copy link
Contributor

ljedrz commented Jun 18, 2019

The error looks spurious.

@bors retry

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jun 18, 2019
@bors
Copy link
Contributor

bors commented Jun 18, 2019

⌛ Testing commit b9ea653 with merge 605ea9d...

bors added a commit that referenced this pull request Jun 18, 2019
Refactor C FFI variadics to more closely match their C counterparts, and add Clone implementation

We had to make some changes to expose `va_copy` and `va_end` directly to users (mainly for C2Rust, but not exclusively):
- redefine the Rust variadic structures to more closely correspond to C: `VaList` now matches `va_list`, and `VaListImpl` matches `__va_list_tag`
- add `Clone` for `VaListImpl`
- add explicit `as_va_list()` conversion function from `VaListImpl` to `VaList`
- add deref coercion from `VaList` to `VaListImpl`
- add support for the `asmjs` target

All these changes were needed for use cases like:
```Rust
let mut ap2 = va_copy(ap);
vprintf(fmt, ap2);
va_end(&mut ap2);
```
@bors
Copy link
Contributor

bors commented Jun 19, 2019

☀️ Test successful - checks-travis, status-appveyor
Approved by: eddyb
Pushing 605ea9d to master...

@bors bors added the merged-by-bors This PR was explicitly merged by bors. label Jun 19, 2019
@bors bors merged commit b9ea653 into rust-lang:master Jun 19, 2019
@rust-highfive
Copy link
Collaborator

📣 Toolstate changed by #59625!

Tested on commit 605ea9d.
Direct link to PR: #59625

🎉 rls on windows: test-fail → test-pass (cc @Xanewok, @rust-lang/infra).

rust-highfive added a commit to rust-lang-nursery/rust-toolstate that referenced this pull request Jun 19, 2019
Tested on commit rust-lang/rust@605ea9d.
Direct link to PR: <rust-lang/rust#59625>

🎉 rls on windows: test-fail → test-pass (cc @Xanewok, @rust-lang/infra).
@ahomescu
Copy link
Contributor Author

Thanks everyone!

@ahomescu ahomescu deleted the copy_variadics_typealias branch June 19, 2019 20:03
@glaubitz
Copy link
Contributor

I think this change broke Rust again on sparc64 although I'm not sure yet as I haven't done any bisecting:

glaubitz@kyoto:~/rust$ ./x.py build
Updating only changed submodules
Submodules updated in 0.21 seconds
    Finished dev [unoptimized] target(s) in 5.16s
Building stage0 std artifacts (sparc64-unknown-linux-gnu -> sparc64-unknown-linux-gnu)
   Compiling core v0.0.0 (/home/glaubitz/rust/src/libcore)
error: internal compiler error: src/librustc_typeck/check/intrinsic.rs:363: va_list structure is invalid

thread 'rustc' panicked at 'Box<Any>', src/librustc_errors/lib.rs:643:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
error: aborting due to previous error


note: the compiler unexpectedly panicked. this is a bug.

note: we would appreciate a bug report: https://github.com/rust-lang/rust/blob/master/CONTRIBUTING.md#bug-reports

note: rustc 1.37.0-dev running on sparc64-unknown-linux-gnu

note: compiler flags: -Z external-macro-backtrace -Z unstable-options -Z force-unstable-if-unmarked -C opt-level=2 -C debuginfo=0 -C prefer-dynamic -C debug-assertions=n -C link-args=-Wl,-rpath,$ORIGIN/../lib --crate-type lib

note: some of the compiler flags provided by cargo are hidden

error: Could not compile `core`.

To learn more, run the command again with --verbose.
command did not execute successfully: "/home/glaubitz/stage2/bin/cargo" "build" "--target" "sparc64-unknown-linux-gnu" "-j" "32" "--release" "--features" "panic-unwind backtrace compiler-builtins-c" "--manifest-path" "/home/glaubitz/rust/src/libstd/Cargo.toml" "--message-format" "json"
expected success, got: exit code: 101
failed to run: /home/glaubitz/rust/build/bootstrap/debug/bootstrap build
Build completed unsuccessfully in 0:02:10
glaubitz@kyoto:~/rust$

Do we need to add sparc64 to the arch exclusion lists above (and s390x etc)?

CC @jrtc27 @psumbera @karcherm

@jrtc27
Copy link

jrtc27 commented Jun 22, 2019

This PR removed the va_list structure is invalid assertion message.

@glaubitz
Copy link
Contributor

@jrtc27 Okay, you're right. I forgot to perform a proper git pull on the machine where I cross-compiled Rust for sparc64. So, false alarm.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
F-c_variadic `#![feature(c_variadic)]` merged-by-bors This PR was explicitly merged by bors. S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion.
Projects
None yet
Development

Successfully merging this pull request may close these issues.