Speaking only for the native target: Yes, but no reasonable one that I’m aware of - There is a “way” to manipulate the entire IR, but it’s not officially supported or straightforward. Basically, you would have to resort to some of the hacks we do with the SKIE plugin.
Suppose you only need to manipulate the IR and not generate any new exposed code (that would be callable from Swift). In that case, you can create a regular compiler plugin with the same extension listener you would use to manipulate the IR of a single module. But instead of adding it to the compilation phase, you add it to the linking phase (both the linker and the compiler phase use the same code, just with different settings).
The point is that linker still calls this IR extension but with a module that represents the whole project. However, there is one main problem with this approach: caching - you can’t modify any code cached in previous runs (you can, but these changes would be overridden in later phases). So, you would have to turn it off, which can increase compilation time significantly. It’s possible to get around this, but you would have to get dangerously close to “it’s better to fork the compiler territory” 😄
But it’s way more complicated if you need to add new exposed code (for example, functions callable from Swift). At that point, the only practical solution is to fork SKIE (or the compiler).