Change:
This is an extension of but to support modification of a private method in another crate. This is currently complicated by the fact that when we write metadata, we make one metadata node per item. So any changes to the item at all are confounded together. IOW, we can't tell the difference between adding an inherent impl or method to a struct and changing the types of its fields. This results in really poor re-use.
cc
As far as priorities, I would put this goal somewhat secondary -- it's very important, but we should focus first on and of course correctness goals. IOW we can have a widely used version that doesn't handle this yet, since most people work primarily at a leaf crate when building. But we should link this goal perhaps to some issues that pertain to specific impl changes for fixing it.
triage: P-medium
The private callees might have been inlined through the public method.
Good note. Is there any way to determine whether some private item was inlined? We must investigate this case.
Inlining should mostly be handled by splitting interfaces from bodies for Metadata dep-nodes, like we also plan to do for HIR dep-nodes. the public extern method is neither generic nor marked with then we should not need to care what got inlined into it. the public extern method is generic or marked with , we must track what goes into its body. If inlining happens at the MIR level, this should already be covered by the current tracking system. At LLVM level it's more difficult. I could imagine that we give the Rust compiler a heuristic that decides which functions are very unlikely to be inlined and then mark those functions with at the LLVM level --- and add dependency edges to all other callees because we can't guarantee that they haven't been inlined.
Just because a function (in the same LLVM module) is doesn't mean LLVM won't peek at its implementation. For example, consider : The LLVM IR for is (in debug and in release), but in release mode, gets optimized to . It also adds various attributes to that can only be justified by analyzing or inlining (e.g., ).
Wow! That's a pretty blatant violation of what the documentation says about the attribute. Thanks for the info! Oh well, we can still fall back to the brute force method of preventing inlining by moving those functions into their own compilation unit.
This optimisation happens because of global (cross-function) constant propagation and not inlining, so no violations here. The way I see it, this goal cannot be achieved without instrumenting LLVM appropriately. As an aside, LLVM has quite a few module-wide optimisations (inlining and global constprop being two examples), all of which would have to be instrumented somehow. Separate compilation units sound quite inefficient to me at the first consideration.
Separate compilation units might work with ThinLTO, if we get the granularity just right.
They are probably not ideal from a compile-time point of view. One consequence of instrumenting LLVM would be that we have to push some of the dependency tracking past the LLVM passes. Probably not that big a deal. A short-term solution would be to add dependency edges to everything that is transitively called within the same compilation unit (i.e. the worst case result that LLVM instrumentation data could yield).
Oh wait, all of this might be a red herring: We are never re-using LLVM IR from an external crate. And regenerating the IR for locally inlined instances will necessarily create dependency edges to the MIR in metadata, so we should be fine.
Right, this is one of the reasons we don't attempt to second guess LLVM's inlining, instead relying on just not giving it access to data, and assuming it uses whatever we give it, no matter what we say.... yes, I'm not sure how all of this is relevant. =) Certainly if the function is and we regenerate its IR, that is one that wasn't the case I was worried about, really. (And even then, we should be able to limit the effect of its propagation in many cases.)
I'd like to work on this - is there a good place that I should start?
This probably already works. So I suggest: if there's a test case for it already in . not, add one.
With the test , I think this can be closed now. :)
Yes, the test looks good. Thanks,

Discussion:
<ins> // Copyright 2014 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your // option. This file may not be copied, modified, or distributed // except according to those terms. // Test where we change the body of a private method in an impl. // We then test what sort of functions must be rebuilt as a result. // revisions:rpass1 rpass2 // compile-flags: -Z query-dep-graph #![feature(rustc_attrs)] #![feature(stmt_expr_attributes)] #![allow(dead_code)] #![rustc_partition_translated(module="struct_point-point", cfg="rpass2")] #![rustc_partition_reused(module="struct_point-fn_calls_methods_in_same_impl", cfg="rpass2")] #![rustc_partition_reused(module="struct_point-fn_calls_methods_in_another_impl", cfg="rpass2")] #![rustc_partition_reused(module="struct_point-fn_make_struct", cfg="rpass2")] #![rustc_partition_reused(module="struct_point-fn_read_field", cfg="rpass2")] #![rustc_partition_reused(module="struct_point-fn_write_field", cfg="rpass2")] mod point { pub struct Point { pub x: f32, pub y: f32, } fn distance_squared(this: &Point) -> f32 { #[cfg(rpass1)] return this.x + this.y; #[cfg(rpass2)] return this.x * this.x + this.y * this.y; } impl Point { pub fn distance_from_origin(&self) -> f32 { distance_squared(self).sqrt() } } impl Point { pub fn translate(&mut self, x: f32, y: f32) { self.x += x; self.y += y; } } } /// A fn item that calls (public) methods on `Point` from the same impl which changed mod fn_calls_methods_in_same_impl { use point::Point; #[rustc_clean(label="TypeckItemBody", cfg="rpass2")] pub fn check() { let x = Point { x: 2.0, y: 2.0 }; x.distance_from_origin(); } } /// A fn item that calls (public) methods on `Point` from another impl mod fn_calls_methods_in_another_impl { use point::Point; #[rustc_clean(label="TypeckItemBody", cfg="rpass2")] pub fn check() { let mut x = Point { x: 2.0, y: 2.0 }; x.translate(3.0, 3.0); } } /// A fn item that makes an instance of `Point` but does not invoke methods mod fn_make_struct { use point::Point; #[rustc_clean(label="TypeckItemBody", cfg="rpass2")] pub fn make_origin() -> Point { Point { x: 2.0, y: 2.0 } } } /// A fn item that reads fields from `Point` but does not invoke methods mod fn_read_field { use point::Point; #[rustc_clean(label="TypeckItemBody", cfg="rpass2")] pub fn get_x(p: Point) -> f32 { p.x } } /// A fn item that writes to a field of `Point` but does not invoke methods mod fn_write_field { use point::Point; #[rustc_clean(label="TypeckItemBody", cfg="rpass2")] pub fn inc_x(p: &mut Point) { p.x += 1.0; } } fn main() { } </ins>

<ins> // Copyright 2014 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your // option. This file may not be copied, modified, or distributed // except according to those terms. pub struct Point { pub x: f32, pub y: f32, } fn distance_squared(this: &Point) -> f32 { #[cfg(rpass1)] return this.x + this.y; #[cfg(rpass2)] return this.x * this.x + this.y * this.y; } impl Point { pub fn distance_from_origin(&self) -> f32 { distance_squared(self).sqrt() } } impl Point { pub fn translate(&mut self, x: f32, y: f32) { self.x += x; self.y += y; } } </ins>

<ins> // Copyright 2014 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your // option. This file may not be copied, modified, or distributed // except according to those terms. // Test where we change the body of a private method in an impl. // We then test what sort of functions must be rebuilt as a result. // revisions:rpass1 rpass2 // compile-flags: -Z query-dep-graph // aux-build:point.rs #![feature(rustc_attrs)] #![feature(stmt_expr_attributes)] #![allow(dead_code)] #![rustc_partition_reused(module="struct_point-fn_calls_methods_in_same_impl", cfg="rpass2")] #![rustc_partition_reused(module="struct_point-fn_calls_methods_in_another_impl", cfg="rpass2")] #![rustc_partition_reused(module="struct_point-fn_read_field", cfg="rpass2")] #![rustc_partition_reused(module="struct_point-fn_write_field", cfg="rpass2")] // FIXME(#37335) -- should be reused, but an errant Krate edge causes // it to get translated (at least I *think* this is that same problem) #![rustc_partition_translated(module="struct_point-fn_make_struct", cfg="rpass2")] extern crate point; /// A fn item that calls (public) methods on `Point` from the same impl which changed mod fn_calls_methods_in_same_impl { use point::Point; #[rustc_clean(label="TypeckItemBody", cfg="rpass2")] pub fn check() { let x = Point { x: 2.0, y: 2.0 }; x.distance_from_origin(); } } /// A fn item that calls (public) methods on `Point` from another impl mod fn_calls_methods_in_another_impl { use point::Point; #[rustc_clean(label="TypeckItemBody", cfg="rpass2")] pub fn check() { let mut x = Point { x: 2.0, y: 2.0 }; x.translate(3.0, 3.0); } } /// A fn item that makes an instance of `Point` but does not invoke methods mod fn_make_struct { use point::Point; #[rustc_clean(label="TypeckItemBody", cfg="rpass2")] pub fn make_origin() -> Point { Point { x: 2.0, y: 2.0 } } } /// A fn item that reads fields from `Point` but does not invoke methods mod fn_read_field { use point::Point; #[rustc_clean(label="TypeckItemBody", cfg="rpass2")] pub fn get_x(p: Point) -> f32 { p.x } } /// A fn item that writes to a field of `Point` but does not invoke methods mod fn_write_field { use point::Point; #[rustc_clean(label="TypeckItemBody", cfg="rpass2")] pub fn inc_x(p: &mut Point) { p.x += 1.0; } } fn main() { } </ins>

<ins> // Copyright 2014 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your // option. This file may not be copied, modified, or distributed // except according to those terms. pub struct Point { pub x: f32, pub y: f32, } impl Point { fn distance_squared(&self) -> f32 { #[cfg(rpass1)] return self.x + self.y; #[cfg(rpass2)] return self.x * self.x + self.y * self.y; } pub fn distance_from_origin(&self) -> f32 { self.distance_squared().sqrt() } } impl Point { pub fn translate(&mut self, x: f32, y: f32) { self.x += x; self.y += y; } } </ins>

<ins> // Copyright 2014 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your // option. This file may not be copied, modified, or distributed // except according to those terms. // Test where we change the body of a private method in an impl. // We then test what sort of functions must be rebuilt as a result. // revisions:rpass1 rpass2 // compile-flags: -Z query-dep-graph // aux-build:point.rs #![feature(rustc_attrs)] #![feature(stmt_expr_attributes)] #![allow(dead_code)] // FIXME(#37333) -- the following modules *should* be reused but are not #![rustc_partition_translated(module="struct_point-fn_calls_methods_in_same_impl", cfg="rpass2")] #![rustc_partition_translated(module="struct_point-fn_calls_methods_in_another_impl", cfg="rpass2")] #![rustc_partition_translated(module="struct_point-fn_make_struct", cfg="rpass2")] #![rustc_partition_translated(module="struct_point-fn_read_field", cfg="rpass2")] #![rustc_partition_translated(module="struct_point-fn_write_field", cfg="rpass2")] extern crate point; /// A fn item that calls (public) methods on `Point` from the same impl which changed mod fn_calls_methods_in_same_impl { use point::Point; // FIXME(#37333) -- we should not need to typeck this again #[rustc_dirty(label="TypeckItemBody", cfg="rpass2")] pub fn check() { let x = Point { x: 2.0, y: 2.0 }; x.distance_from_origin(); } } /// A fn item that calls (public) methods on `Point` from another impl mod fn_calls_methods_in_another_impl { use point::Point; // FIXME(#37333) -- we should not need to typeck this again #[rustc_dirty(label="TypeckItemBody", cfg="rpass2")] pub fn check() { let mut x = Point { x: 2.0, y: 2.0 }; x.translate(3.0, 3.0); } } /// A fn item that makes an instance of `Point` but does not invoke methods mod fn_make_struct { use point::Point; // FIXME(#37333) -- we should not need to typeck this again #[rustc_dirty(label="TypeckItemBody", cfg="rpass2")] pub fn make_origin() -> Point { Point { x: 2.0, y: 2.0 } } } /// A fn item that reads fields from `Point` but does not invoke methods mod fn_read_field { use point::Point; // FIXME(#37333) -- we should not need to typeck this again #[rustc_dirty(label="TypeckItemBody", cfg="rpass2")] pub fn get_x(p: Point) -> f32 { p.x } } /// A fn item that writes to a field of `Point` but does not invoke methods mod fn_write_field { use point::Point; // FIXME(#37333) -- we should not need to typeck this again #[rustc_dirty(label="TypeckItemBody", cfg="rpass2")] pub fn inc_x(p: &mut Point) { p.x += 1.0; } } fn main() { } </ins>