From "life before main" to "common life in main"

"Linker shenanigans." I'm not the best person to explain it, @dtolnay would be the one who understands how the linkme implementation works the best.

But the short version, as I understand it, is:

With linker shenanigans:

  • In creating a distributed slice "registry" called NAME, we set up three linker sections, the exact name and method of which are platform dependent, but we'll call __linkme_NAME, __linkme_start_NAME, and __linkme_end_NAME.
  • We ask the linker to lay these sections out such that __linkme_start_NAME is directly followed by __linkme_NAME which is then directly followed by __linkme_end_NAME.
  • Each item put in the distributed slice is (of known, verified type and) placed as a static in the __linkme_NAME section.
  • Again through platform/linker-specific tricks, we define statics that reside in the __linkme_start_NAME and __linkme_end_NAME sections.
  • We at runtime use those two statics to create our slice; we effectively have a "first before the start" and "first past the end" pointer from which to derive our linker-assembled slice.

This is almost certainly actually UB in a strict sense, as the Rust Abstract Machine doesn't have a concept of what we're doing here. In practice, this is closer to platform-defined behavior.

With compiler support, it would work much the same way, except that because the compiler itself knows about it, it wouldn't require platform linker support, just rustc linker support. At a high level,

  • The registry crate defines a distributed slice as a static.
  • Any static which is placed in the distributed slice is marked specially in the rlib as being part of the distributed slice.
  • When rustc is invoked to link together the rlibs into an executable, it first finds all of the statics marked as part of the distributed slice, and orchestrates the platform-specific operations to put them in a continuous statically allocated slice. This may be linker directives on some platforms (e.g. the ones linkme already supports), or it may be in directly reässembling the individual static sections into one static section (and references from the children back to the parent) before handing it off to the platform linker.
  • Notably, the Abstract Machine is now dealing with an actual slice of linktime determined size, rather than seeing you accessing outside of these statics you've defined, so it's no longer strictly speaking UB, and there's no danger of future optimizations breaking the behavior.

All of this of course only works with static linking.

7 Likes