Skip to content

Mark by-value parameters that are passed on the stack as nocapture #12494

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

Closed
wants to merge 1 commit into from

Conversation

dotdash
Copy link
Contributor

@dotdash dotdash commented Feb 23, 2014

The by-value argument is a copy that is only valid for the duration of
the function call, therefore keeping any pointer to it that outlives the
call is illegal.

The by-value argument is a copy that is only valid for the duration of
the function call, therefore keeping any pointer to it that outlives the
call is illegal.
@brson
Copy link
Contributor

brson commented Feb 24, 2014

@dotdash thanks for the explanation. do you have an example where this leads to TCO?

@dotdash
Copy link
Contributor Author

dotdash commented Feb 24, 2014

Generic example:

#[inline(never)]
fn huge_fn(x: &str, d: int) {
  println!("{}: {}", x, d);
}

fn rec(d: int) {
  huge_fn("Test", d);
  rec(d+1);
}

pub fn main() {
  rec(0);
}

Since &str is not an actual pointer but a struct and doesn't fit into a single register, it is passed as a by-value argument on the stack. Without the nocapture attribute, TCO can't happen.

In libstd, TCO is made possible for char::decompose_canonical and char::decompose_compatible (but nothing else).

In can check larger libs like libsyntax and librustc later today.

@dotdash
Copy link
Contributor Author

dotdash commented Feb 24, 2014

Further checking tells that there may be more TCO changes in libstd than just those two. I had looked for TAILCALL in the assembly diff, but that's only for recursive tail calls I think.

A different example that doesn't involve recursion is this:

#[crate_type="lib"];

#[inline(never)]
pub fn func(foo: Option<int>) {
    println!("{:?}", foo);
}

#[inline(never)]
pub fn func2(foo: int) {
    println!("{:?}", foo);
}

pub fn bar(foo_opt: Option<int>) {
    func(foo_opt);
    match foo_opt {
        Some(foo) => func2(foo),
        _ => {}
    }
}

With the change, the call to func2 gets lowered to a jmp.

I also found this change in the IR for librustc:

Old:

  %283 = bitcast [4 x i64]* %282 to i8*
  %284 = load i8* %283, align 8, !range !1
  switch i8 %284, label %match_else42 [
  ;...
  %285 = bitcast [4 x i64]* %282 to { i8, %"struct.syntax::ast::DefId[#9]", i8, { i64, void (i8*)*, i8*, i8*, %"struct.syntax::ast::Method[#9]" }* }*
  ;...
  %impl_did.sroa.0.0.idx = getelementptr inbounds { i8, %"struct.syntax::ast::DefId[#9]", i8, { i64, void (i8*)*, i8*, i8*, %"struct.syntax::ast::Method[#9]" }* }* %285, i64 0, i32 1, i32 0
  %impl_did.sroa.0.0.copyload = load i32* %impl_did.sroa.0.0.idx, align 4

New:

  %283 = getelementptr inbounds [4 x i64]* %282, i64 0, i64 0
  %284 = load i64* %283, align 8
  %285 = trunc i64 %284 to i8
  %286 = lshr i64 %284, 32
  %287 = trunc i64 %286 to i32
  switch i8 %285, label %match_else42 [

LLVM could deduce that the value doesn't change between the two loads and combine them to a single load instead (+ shifting). Unfortunately, I don't have a simple rust example that exposes this.

@brson
Copy link
Contributor

brson commented Feb 24, 2014

@dotdash Thanks for the details.

@nikomatsakis Can you review?

@bors bors closed this in 4243cad Feb 25, 2014
@dotdash dotdash deleted the nocapture_byval branch February 6, 2018 08:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants