Explicit monomorphization for compilation time reduction

Why does it need to be a proc macro? It's trivially implementable via macro_rules! if I'm not missing anything:

macro_rules! mono {
    {
        $(
            $f:path;
        )*
    } => {
        $(
            const _: *const () = (&$f) as *const _ as _;
        )*
    };
}

(play)

2 Likes

I want to make it easier by allowing users writing

pub struct GenericStruct<T>(T);

#[mono(i32)]
impl<T> GenericStruct<T> {
    pub fn method1() {}

    #[mono(f32)]
    pub fn method2<U>(_: U) {}
}

and get


mono! {
    GenericStruct::<i32>::method1;
    GenericStruct::<i32>::method2::<f32>;
}

No, absolutely not. You are thinking of export template, which was deprecated and removed in C++11, with extern template introduced as a replacement. extern template is a perfectly acceptable tool for sharing template instantiations to improve compile times.

Ah yes, good old export template. The one thing that absolutely zero major compilers supported ever.

EDG had an experimental implementation of export template in their compiler. If I remember correctly, it mostly worked, but it turned out not to be better than putting all the template definitions in the headers, and that was what persuaded the committee to drop the feature from C++11.

I'm aware that at least one C++ compiler did, but AFAIK, none of the major ones ever did. Unless EDG counts as "major".

Before LLVM came along, only two complete implementations of a C++ front end existed: g++ and EDG. Literally every proprietary C++ compiler in the world licensed EDG's front end. So, yeah, for these purposes it counts as "major".

However, I don't recall their export template implementation ever being deployed in a production compiler, so you're right in that sense.

1 Like

By the way, the godbolt compiler now supports outputting macro expansion, pretty handy (not that this is a particularly complex macro): Compiler Explorer

I have some questions about how we should move this forward.

  1. So does this mean that between this funny looking instantiation [1] (or mono!) and -Zshare-generics that the viability of this shared/explicit monomorphization idea to reduce compile times can already be evaluated and used today?

[1]: pub const _: *const () = (&$f) as *const _ as _;

In issue #48779 linked above, michaelwoerister mentioned -15.9% for tokio-web-push in 2018. That's pretty nice. But that means this has been possible since 2018...

  1. Are there other examples of how this impacts compile time?
  2. Is it already widely used?
    • Is it still implemented in tokio-web-push?
  3. If not, why not?
    • E.g. Nobody connected the dots before? Lack of awareness that this is a feature? Ergonomics? Compiler flag defaults? It doesn't actually work as well as hoped? It's not actually helpful for some practical reason?

If it generally works but is not widely used, we can make positive improvements to existing libraries today before needing to make changes to the compiler. I think our first focus should be to deliver value to library authors and users now and evaluating the results of broader adoption, then consider compiler changes like new built-ins so in the future libraries that set the MSRV to a version with this as a built-in can switch to the new syntax.

1 Like

Yes.

It's not implemented in tokio-web-push. share-generics is a compiler feature. Library authors don't have to do anything. The mono macro is what we hope library authors would add to their code to better utilize share-generics between sibling crates.


I don't have answer to other questions. But I don't think compiler change is necessary. We can implement mono in a crate.

  • Is it still implemented in tokio-web-push?

share-generics is a compiler feature.

Nothing is stopping someone from using the instantiation directly. There is no hard dependency on a particular macro named mono, the macro is just a convenience. Library authors do have to have this code in their crate (at least after macro expansion) for each instance they believe monomorphization will help:

pub const _: *const () = (&$f) as *const _ as _;

Library authors have several choices, all of which may be reasonable in particular situations:

  • Write out the above code manually for each type. (It's a little weird looking but it's not that bad.)
  • Copy the macro above and paste it into their crate and use it. (Again, it's a tiny easy to understand macro.)
  • Wait for someone to publish the macro in a crate and take a dependency on the crate. (Taking a dependency is not free either.)

My point is that we can communicate this feature to libraries we think it could help right now, we don't even need to wait for a crate. (If an author wants to use the macro from a crate, that's fine too, but there's nothing forcing us to make the crate first.)

It doesn't have to be specifically that code (that's just an easy way to force the function to be monomorphized), they just need to (transitively) name/call that monomorphizaton of the function in some non-generic code.

1 Like

Just so, if the library just happens to use a particular monomorphization then it will be cached for any dependents and this forced instantiation trick is not strictly necessary. But if the goal is to be explicit (see title: Explicit monomorphization for compilation time reduction) then listing out monomorphization targets for the express purpose of caching them is probably still a good idea even if some of them are used incidentally in the library.

Maybe "targets x, y, z are pre-monomorphized" should be considered a true library feature and even be configurable as a crate feature.