Monomorphization pre-lifetime-erasure? (question about specialization)

I finally read shipping specialization and the corresponding thread, but I still have what feels like an obvious question that I don't think was answered.

Why doesn't monomorphization itself occur prior to lifetime erasure? Or, could some kind of "partial monomorphization" occur earlier?

The blog post and thread both discussed having the typecheck phase "pass information" to the translation phase, but that presupposes that the information is otherwise "lost". Boats pointed out that there is no phase with "complete type information", but of course there could be if monomorphization occurred prior to lifetime information being discarded. I didn't see any responses to this idea in the thread, though.

The one obvious problem I can think of with monomorphizing earlier is that it inflates code size, but I don't know how much this would actually impact performance (especially memory usage) or whether it's the only problem with the idea.

One form of "partial monomorphization" that could be useful for closing the specialization soundness hole, I think, would be lifetime-only monomorphization. I realize that this would be completely different from the current method of monomorphization (since the entire problem is that monomorphization currently doesn't even have lifetime information), and I assume it would have quite a few complications (particularly around when to treat lifetimes as equivalent), but before going too deep in my own rabbit hole of hypotheticals, does anyone know of any existing discussions or design work around such a feature?

Right now lifetimes cannot affect code generation beyond failing to compile (modulo a few edge cases iirc). That's the main reason why specializing in lifetimes is opposed.

This would break the ability to have lifetime-generic methods on trait objects, as well as HRTB trait objects.

Remember that every method that takes a & is generic on a lifetime. So this would mean, for example, that every call even to something like [T]::len would need to emit a new copy of the function, just in case something was specializing on that particular lifetime. (And how big is that lifetime, anyway? Would v.len() + v.len() need two, because they're different borrows with different lifetimes?)

And if that needed to be monomorphized, that would also mean no separate compilation of anything that takes a reference either. Today, because lifetimes are erased, you can compile a non-inline non-type/const-generic function once in the crate that defined it.

So even if LLVM deduped things and the size didn't increase, this would be an enormous hit to compile times.

(Now, maybe it could just be "a different one for 'static", which isn't nearly as bad, but still has a bunch of separate compilation awkwardness -- at best.)

1 Like