You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Incremental compilation results in linker error when method use is removed #59535
Not specific to thumbv7em-none-eabi. @jsgf told me #61917 happened when building for x86_64-unknown-linux-gnu. We don't yet have a reliable repro on x86 ("build this commit followed by this commit") and the project in #61917 is not using Cargo so minimizing from there may be more complicated.
At this point I think I've gotten it at small as I can with my limited knowledge of embedded rust.
Embedded Rust is pretty much regular Rust sans magic done by the operating system and libc in a regular application. The reduced 1.6k middle crate is just an abstraction over fixed bits at fixed memory addresses to talk to built-in peripherals (data busses, timers, I/O pins, radios...) generated from a chip manufacturer provided register description.
Having multiple impl R { }s on the same struct R in the same module sounds quite wrong, is that even allowed? I could imagine that this confuses the heck out of the name mangling.
Having multiple impl R { }s on the same struct R in the same module sounds quite wrong, is that even allowed? I could imagine that this confuses the heck out of the name mangling.
@pnkfelix Yes, I have learned that last weekend in Barcelona and makes sense to me. Should have updated my comment to not waste your time, sorry about that.
It's still kind of weird that Rust accepts multiple identical impls. That sounds like something which should possibly be at least a lint.
Current status: using -C save-temps and llvm-dis, I have been inspecting the contents of the distinct codegen units that rustc generates.
As a reminder, the undefined symbol is: _$LT$rubble..ble..Duration$u20$as$u20$core..fmt..Display$GT$::fmt::hcaf51ebe05a39b3b, (referenced by mks4f5lxe2i4sqc in /tmp/issue59535/out/rubble2/rubble.mks4f5lxe2i4sqc.rcgu.o). So searching for hcaf51ebe05a39b3b in the disassembled bitcode files (*.ll) seemed like a reasonable step to take.
From there, I discovered that rubble.3bvob4s3e3jl7a31.rcgu.no-opt.ll has a defintion for the aforementioned symbol:
but rubble.3bvob4s3e3jl7a31.rcgu.ll is missing that definition. So I think that means the definition is getting eliminated by llvm optimization passes.
One thing that stood out to me here: The function definition in the llvm bitcode says define internal. But this definition is referenced by another codegen unit. Surely that should not be internal ? (Maybe this is why the LLVM optimization passes are able to justify removing it?)
Still, seems odd that this only crops up during this combination of factors (namely, the fact that incremental compilation seems to be involved...)
Ah: The first clean run, that didn't have incremental build products, does not mark the function definition as internal; that definition is merely marked hidden
@pnkfelix Yes, I have learned that last weekend in Barcelona and makes sense to me. Should have updated my comment to not waste your time, sorry about that.
It's still kind of weird that Rust accepts multiple identical impls. That sounds like something which should possibly be at least a lint.
Well, multiple identical non-trivialimpl blocks is impossible: You get "duplicate definition" errors.
And multiple non-identical impl blocks is useful, e.g. for code-grouping. As mentioned in a very recent tweet:
rust documentation code layout idea: group functions together in separate impl blocks, even if the blocks have the same trait bounds or are in the same file
then apply doc comments to the impl blocks that rustdoc can show to create "sections"
So I think the only case here that one might want to lint for is trivial (i.e. empty) impl blocks. (But even there I would want to exercise care to not lint cases where all the items in the block were removed due to cfg attributes.)
More data, after adding some debug! instrumentation:
The module rustc_mir::monomorphize::partitioning has fn internalize_symbols, which tries to mark symbols internal that are only referenced by the current codegen unit.
The first (i.e. clean) run does not mark rubble[317d]::ble[0]::{{impl}}[0]::fmt[0] as internal, because it has these two accessors:
The second run with incremental build produces and an empty process_data_packet definition does mark rubble[317d]::ble[0]::{{impl}}[0]::fmt[0] as internal, while noting that it has this one accessor:
// Found an accessor from another CGU, so skip to the next
// item without marking this one as internal.
continue
}
}
(The debug print outs above do not include the codegen unit placements for the accessors, so I do not actually know for sure how much sense all of this makes. But it certainly makes sense that the aforementioned change to process_data_packet would cause it to be removed from the accessors list, so there is no smoking gun here yet...)
More data after adding instrumentation of the codegen units involved: The codegen unit that contains the unresolved reference is named mks4f5lxe2i4sqc. It has a single fn item (one of the fmt fns, specifically core::fmt[0]::{{impl}}[49]::fmt[0]<rubble::ble[0]::Duration[0]>), and that fn item accesses rubble[317d]::ble[0]::{{impl}}[1]::fmt[0].
Note that rubble[317d]::ble[0]::{{impl}}[1]::fmt[0] (which belongs to codegen unit 3bvob4s3e3jl7a31) has previously been found to be an accessor of rubble[317d]::ble[0]::{{impl}}[0]::fmt[0] (whcih belongs to the same codegen unit). The latter item is the item that being marked as Internal and subsequently has its definition optimized away by LLVM.
In short: it looks like we have a call chain of methods: A calls B calls C, A is in one code-gen unit, B and C are in another, and we make the decision to mark C internal (since it is only reachable from B, in the same codegen-unit), but independently, the compiler (presumably LLVM) has inlined the body of B into A, and so we now have a call from A to an internal method (C) in a distinct codegen-unit.
Is there some sort of inlining happening here that implies we need to do a deeper traversal of the accessor-chain before we commit to marking a reachable item as internal?
From looking at the pre- and post-optimized versions of rubble.mks4f5lxe2i4sqc.rcgu.ll, I am thinking that this seems like strong possibility.
Another thing to note: it seems to me like the inlining in question is happening for both the first (clean) build and also for the the second (reusing) build.
(Is it possible that the second build is reusing the object code for the mks4f5lxe2i4sqc codegen unit from the first build? Is that somehow relevant, or a red herring?)
In particular: Using the simplified A,B,C model I summarized above, I would have imagined that LLVM could not inline the body of B into A since it is making a call to the internal function C. But maybe the issue is that we are reusing the results of a previous compile where it was legal to inline B into A (because during that compile, C was not marked internal), and independently we are now deciding to recategorize C as internal.
Note however that I also would have thought that LLVM could not inline the body of B into A because they belong to distinct codegen units. So there are multiple points of personal confusion here for me to resolve in my theories above.
Further update: it seems like the inlining of B into A is happening during link-time optimization (specifically ThinLTO), which does seem like it would jibe with the rules for link-time optimization...
Okay, further investigation: rustc itself controls whether we can reuse the post-ThinLTO version of a module (or if we have to go back and reoptimize it).
And, thank goodness, we already had debug instrumentation in place to emit information about such reuse:
In other words: This lends credence to my theory that we are reusing an artifact from a previous incremental compile that should have been invalidated by the changes to what symbols were marked internal by rustc_mir::monomorphize::partitioning::internalize_symbols
The code that dictates whether a post-ThinLTO object file for a codegen unit can be reused is here:
lto.rs, when it decides which modules can be reused in PostLTO form, looks at the import-list and sees if they are all on the green modules list. But it is looking at the import list that it currently gets from LLVM. Should we be instead (or additionally) looking at the old import list, from the time that we did the LTO optimization that we are reusing? (Presumably we would just serialize the ThinLTOImports and re-load that, or add it to the dep-graph maybe.)
which reflects the whatever the current LTO imports are computed, which may differ from the set of imports that were used to drive the original LTO optimization that we are reusing. In particular, they may differ by being a subset of the original set of imports, which means we can overlook hidden dependencies.
Activity
dtolnay commentedon Jun 19, 2019
I minimized this to ~60 lines in jonas-schievink/rubble#62.
dtolnay commentedon Jun 19, 2019
#61917 looks like it reproduces this in a non-Cargo environment. Some observations from that issue:
@jsgf
pnkfelix commentedon Jun 27, 2019
pre-triage: P-high; leaving nominated in hopes it increases chance that we discuss it today
pnkfelix commentedon Jun 27, 2019
discussed at T-compiler meeting. Tentatively assigning to @Zoxc for initial investigation.
@Zoxc, if you do not expect to be able to do much investigation, let me know (and unassign yourself)
Zoxc commentedon Jun 27, 2019
Is this specific to the thumbv7em-none-eabi target?
dtolnay commentedon Jun 27, 2019
Not specific to thumbv7em-none-eabi. @jsgf told me #61917 happened when building for x86_64-unknown-linux-gnu. We don't yet have a reliable repro on x86 ("build this commit followed by this commit") and the project in #61917 is not using Cargo so minimizing from there may be more complicated.
pnkfelix commentedon Sep 12, 2019
triage: Reassigning from @Zoxc to self.
6 remaining items
therealprof commentedon Nov 8, 2019
Embedded Rust is pretty much regular Rust sans magic done by the operating system and libc in a regular application. The reduced 1.6k middle crate is just an abstraction over fixed bits at fixed memory addresses to talk to built-in peripherals (data busses, timers, I/O pins, radios...) generated from a chip manufacturer provided register description.
Having multiple
impl R { }s on the samestruct Rin the same module sounds quite wrong, is that even allowed? I could imagine that this confuses the heck out of the name mangling.pnkfelix commentedon Nov 12, 2019
Its absolutely allowed. See:
https://doc.rust-lang.org/stable/book/ch05-03-method-syntax.html#multiple-impl-blocks
therealprof commentedon Nov 12, 2019
@pnkfelix Yes, I have learned that last weekend in Barcelona and makes sense to me. Should have updated my comment to not waste your time, sorry about that.
It's still kind of weird that Rust accepts multiple identical
impls. That sounds like something which should possibly be at least a lint.pnkfelix commentedon Nov 12, 2019
Current status: using
-C save-tempsandllvm-dis, I have been inspecting the contents of the distinct codegen units thatrustcgenerates.As a reminder, the undefined symbol is:
_$LT$rubble..ble..Duration$u20$as$u20$core..fmt..Display$GT$::fmt::hcaf51ebe05a39b3b, (referenced bymks4f5lxe2i4sqcin/tmp/issue59535/out/rubble2/rubble.mks4f5lxe2i4sqc.rcgu.o). So searching forhcaf51ebe05a39b3bin the disassembled bitcode files (*.ll) seemed like a reasonable step to take.From there, I discovered that
rubble.3bvob4s3e3jl7a31.rcgu.no-opt.llhas a defintion for the aforementioned symbol:but
rubble.3bvob4s3e3jl7a31.rcgu.llis missing that definition. So I think that means the definition is getting eliminated by llvm optimization passes.One thing that stood out to me here: The function definition in the llvm bitcode says
define internal. But this definition is referenced by another codegen unit. Surely that should not beinternal? (Maybe this is why the LLVM optimization passes are able to justify removing it?)Still, seems odd that this only crops up during this combination of factors (namely, the fact that incremental compilation seems to be involved...)
internal; that definition is merely markedhiddenpnkfelix commentedon Nov 12, 2019
Well, multiple identical non-trivial
implblocks is impossible: You get "duplicate definition" errors.And multiple non-identical
implblocks is useful, e.g. for code-grouping. As mentioned in a very recent tweet:So I think the only case here that one might want to lint for is trivial (i.e. empty)
implblocks. (But even there I would want to exercise care to not lint cases where all the items in the block were removed due tocfgattributes.)pnkfelix commentedon Nov 12, 2019
More data, after adding some
debug!instrumentation:The module
rustc_mir::monomorphize::partitioninghasfn internalize_symbols, which tries to mark symbols internal that are only referenced by the current codegen unit.The first (i.e. clean) run does not mark
rubble[317d]::ble[0]::{{impl}}[0]::fmt[0]as internal, because it has these two accessors:Fn(Instance { def: Item(DefId(0:19 ~ rubble[317d]::ble[0]::{{impl}}[1]::fmt[0])), substs: [] }),Fn(Instance { def: Item(DefId(0:6 ~ rubble[317d]::ble[0]::link[0]::process_data_packet[0])), substs: [] })The second run with incremental build produces and an empty
process_data_packetdefinition does markrubble[317d]::ble[0]::{{impl}}[0]::fmt[0]as internal, while noting that it has this one accessor:Fn(Instance { def: Item(DefId(0:17 ~ rubble[317d]::ble[0]::{{impl}}[1]::fmt[0])), substs: [] })The logic of the relevant code is here:
rust/src/librustc_mir/monomorphize/partitioning.rs
Lines 648 to 661 in a19f934
(The debug print outs above do not include the codegen unit placements for the accessors, so I do not actually know for sure how much sense all of this makes. But it certainly makes sense that the aforementioned change to
process_data_packetwould cause it to be removed from theaccessorslist, so there is no smoking gun here yet...)pnkfelix commentedon Nov 12, 2019
More data after adding instrumentation of the codegen units involved: The codegen unit that contains the unresolved reference is named
mks4f5lxe2i4sqc. It has a single fn item (one of thefmtfns, specificallycore::fmt[0]::{{impl}}[49]::fmt[0]<rubble::ble[0]::Duration[0]>), and that fn item accessesrubble[317d]::ble[0]::{{impl}}[1]::fmt[0].Note that
rubble[317d]::ble[0]::{{impl}}[1]::fmt[0](which belongs to codegen unit3bvob4s3e3jl7a31) has previously been found to be an accessor ofrubble[317d]::ble[0]::{{impl}}[0]::fmt[0](whcih belongs to the same codegen unit). The latter item is the item that being marked as Internal and subsequently has its definition optimized away by LLVM.In short: it looks like we have a call chain of methods: A calls B calls C, A is in one code-gen unit, B and C are in another, and we make the decision to mark
Cinternal (since it is only reachable from B, in the same codegen-unit), but independently, the compiler (presumably LLVM) has inlined the body of B into A, and so we now have a call from A to an internal method (C) in a distinct codegen-unit.Is there some sort of inlining happening here that implies we need to do a deeper traversal of the accessor-chain before we commit to marking a reachable item as internal?
rubble.mks4f5lxe2i4sqc.rcgu.ll, I am thinking that this seems like strong possibility.mks4f5lxe2i4sqccodegen unit from the first build? Is that somehow relevant, or a red herring?)pnkfelix commentedon Nov 14, 2019
Okay, further investigation:
rustcitself controls whether we can reuse the post-ThinLTO version of a module (or if we have to go back and reoptimize it).And, thank goodness, we already had debug instrumentation in place to emit information about such reuse:
In other words: This lends credence to my theory that we are reusing an artifact from a previous incremental compile that should have been invalidated by the changes to what symbols were marked internal by
rustc_mir::monomorphize::partitioning::internalize_symbolsThe code that dictates whether a post-ThinLTO object file for a codegen unit can be reused is here:
rust/src/librustc_codegen_llvm/back/lto.rs
Lines 499 to 526 in 5e380b7
I'm going to read over this more carefully and see if I can figure out how to make it aware of the impact of
internalize_symbols....pnkfelix commentedon Nov 27, 2019
@michaelwoerister and I are continuing to investigate this. (zulip archive)
My most recent hypothesis as to what the bug is (transcribed here):
Specifically:
rust/src/librustc_codegen_llvm/back/lto.rs
Lines 473 to 475 in 0f6f66f
is relying on this:
rust/src/librustc_codegen_llvm/back/lto.rs
Lines 853 to 858 in 0f6f66f
which reflects the whatever the current LTO imports are computed, which may differ from the set of imports that were used to drive the original LTO optimization that we are reusing. In particular, they may differ by being a subset of the original set of imports, which means we can overlook hidden dependencies.
Rollup merge of rust-lang#67020 - pnkfelix:issue-59535-accumulate-pas…
Auto merge of #67020 - pnkfelix:issue-59535-accumulate-past-lto-impor…
rhit-sashiks commentedon Oct 26, 2024
I still see this issue
bjorn3 commentedon Oct 26, 2024
If you mean #45929 (comment), that has almost certainly a different root cause that what this issue is about.