No, that is wrong. The issue is that the compiler does world splitting optimizations based on what is in the system image. ! only has methods that return Bool, so uninferred code will still specialize on the Bool output. If there are three types, like Bool, True, and False, then it will create code to union split on those three. If there are 4, like Bool, True, False, and Num (symbolic types), then that is over the limit and it will no longer do any splitting and will dynamically handle the boxed output. I think this is a major over optimization which is contributing to a lot of compilation bloat with little to no real world runtime performance gain, so I would say we should drop the heuristic to 1 and just add one more method to ! to Base and it will be fine.
But, with all of that, if the static types and dispatches were added to the Base system image then it would always default to the three branches and completely remove the invalidations. Maybe that’s not an elegant way to do it, but it makes clear what the issue is and what it’s a fix.
(I would also drop the other union splitting heuristic too, but that’s another matter…)
@oxinabox saw something similar with ChainRulesCore invalidations recently too. I think this heuristic is set in a way that favors recompilation way too much right now, especially since it only (slightly) helps performance of only uninferred code
In the past I’ve pointed out that symbols, floats, and integers are the only basic components that need to be static and then everything related can be built on them, so I’m not convinced there will be an endless list of new static types needed.
Now if there’s a better solution out there for handling this then we shouldn’t accept the PR but an endless list of related new static bit types shouldn’t be relevant.
But also, if you just push the main operators over the limit, then anyone can extend in a package without causing any recompilation. It’s all just because Base has a few types and overspecializes on it to the point where no other code can extend the operator without extensive expensive invalidations.
Another way to put it is, if ! is supposed to be an operator that can only return Bool, then that should be enforced in the adding of methods somehow. But currently you’re allowed to do it, the compiler has assumed you will never do it, and therefore you recompile everything. Turn it off or enforce it.
The types from Static.jl are intended to be used in such a way that if they are extracted from the field of some object and passed around the downstream user shouldn’t have to make any special effort to get things working. This means it intersects with other issues a lot. The invalidations are the thing that have pushed us into actively discussing the static types, but really aren’t the root issue a lot of the time when it comes to invalidations. For example, I recall a related issue with == that causes invalidations whenever defined for any new type.
So an unintended benefit of Static.jl has been the incidental discovery of more general problems in Julia just because something like StaticInt causes invalidations. Unfortunately, this also means that discussions tend to go in circles because you can move on from one problem to another for a while before everyone is on the same page concerning the unique benefit of the compiler having a value explicitly made static and passed around.
Who are “we”? Looking at these threads, I suspect there are very different niches where people may use static values.
Presumably, many usecases involve enduser code, or packages that are close to the enduser instead of deep dependencies. For me, the whole appeal of static values is that I decide which values should be static close to the top level, and they automatically propagate through lower-level code. Any function or struct with an <:Integer constraint immediately makes Static.jl unusable.