Thread local storage is a fine way to do this, and the cost (of a function call unless you use a reserved key) is either balanced by the savings or at least insignificant relative to the other advantages.
It's tragic that LLVM and Swift compiler internal interface design is dominated by figuring out how to pass context around and get back to it. Whenever I'm working on implementation, the majority of my time is spent on this. Refactoring code and making cross-cutting changes is at least 10x harder because we don't have idiomatic access to thread context. e.g. when I introduced SILFunctionConventions what should have been a mostly mechanical change taking only a few days ending up taking several weeks because I had to rewrite each of a few hundred unfamiliar routines doing their own ad-hoc SILFunction access. Likewise, any time we need to refactor pass logic as utilities, what should be a copy-paste of some code blocks ends up requiring rewriting the code and any referenced function and method declarations (e.g. as instance variables become function arguments and vice-versa).
Back to a compile time... there are plenty of opportunities to reduce the amount of redundant work being done by the compiler, and there is plenty of foolishness when it comes to choices of high level with data structures and algorithms. The real barrier to improving compile time is complexity that obscures understanding and discourages rewriting the code from the top-down where it's really needed. That's why I'm opposed to micro-optimizations that increase complexity.