Swift: Challenges and Opportunity for Language and Compiler Research [pdf]
Even though I haven&#x27;t used it much, I like Swift: it&#x27;s a big upgrade over Objective-C.<p>I do have some thoughts on the JIT&#x2F;AOT part of the presentation though, which seems the most interesting to me. After many years where the two camps were largely separate, what we&#x27;re seeing recently are more cases where JIT and AOT compilation get combined in new and interesting ways:<p>• Swift is AOT except when developers need fast response times, then it becomes JIT.<p>• Rust and Go are pure AOT always.<p>• Android went interpreted, JIT, AOT, back to JIT with AOT at night.<p>• Java 9 introduces mixed AOT&#x2F;JIT mode, in which you can pre-compile Java modules to native code ahead of time, but that native code still self-profiles and reports behaviour data into the runtime which can then schedule a JIT-compiled replacement for any given method using the new profiling data. It also introduces ahead of time cross-module optimisation (a &quot;static linker&quot;-like thing called jlink).<p>Obviously these are quite different approaches, but they&#x27;re working with similar(ish) languages on identical hardware. So which approach is going to win out, in the long run, if any?<p>It&#x27;s fair to say that LLVM is the most advanced compiler toolchain for C-like languages. The JVM is I&#x27;d argue the most advanced runtime for more dynamic languages like Java, JavaScript, Python, Ruby etc. But it seems to me that LLVM struggles with some optimisations that should be quite basic and important - the way I read the presentation is that it won&#x27;t do things like inline parts of the standard&#x2F;collections library into calling code because they reside in different modules, and inter-procedural optimisation is only done at the level of the module. Otherwise compilation times become too problematic. A profile-directed JIT compiler has no such problems and will happily optimise across module boundaries.<p>As languages evolve, they seem to take on more dynamic features. This is especially true with the incorporation of functional programming styles where you&#x27;re frequently passing functions to other functions and working with immutable data, which implies dynamic dispatch (when you can&#x27;t inline through the call chain) and lots of copying of short-lived data structures (what a generational GC eats for breakfast).<p>Another major trend is multi-core processors, which we still aren&#x27;t as good at exploiting as we should be. But more dynamic runtimes tend to find ways of using multiple cores even for single-threaded programs: if your program is inherently only able to use 1 or 2 cores at once, but you&#x27;re on an 8 core machine and you aren&#x27;t heat&#x2F;power constrained, then using the other cores for concurrent GC or JIT compilers is basically free. If these techniques can speed up the execution of your program threads then it&#x27;s a win, even though analysed holistically it might look like a loss. It is notable that multi-threading LLVM is apparently only a recent feature.<p>These trends lead me to believe that the JVM architects have the right general direction:<p>• Allow code to be AOT compiled but still optimise at runtime using multiple spare cores at once, to extract performance even in more modern, heavily OOP or FP-oriented code.<p>• Use a generational GC that is optimised for objects being mutated through copying and which doesn&#x27;t cause large quantities of cache-coherency traffic due to the use of atomics all over the place.<p>• Support inter-procedural ahead of time optimisations like dead code elimination and statically resolved reflection with an optional link phase.<p>• Rely on pure JIT when developing to keep the edit-compile-run loop tight.<p>• Use a high level code transport format like bytecode that minimises the exposed ABI, thus allowing you to tweak and optimise the ABI used at runtime without breaking the world.<p>Apple has gone in this direction with the app store compiling bitcode to binaries for you, but LLVM bitcode was never really designed for that use case. Still, it seems like LLVM will be heading further in this direction in the future, or at least would like to, judging from the following comment:<p><i>I dream about the day when we can speed up the edit&#x2F;compile&#x2F;run cycle by using a JIT compiler to get the process running, before all the code is even compiled. In addition to speeding the development cycle, this sort of approach could provide much higher execution performance for debug builds, by using the time after the process starts up to continuously optimize the hot code.</i><p><i>Unfortunately, getting here requires a lot of work and has some interesting open questions when it comes to dependence tracking for changes (how you invalidate previously compiled code). That said, this would be a really phenomenal research area for someone to tackle.</i><p>These problems were already tackled years ago by the HotSpot project, which is capable of tracking the dependencies between compiled methods and invalidating compiled code when assumptions used in their compilation become invalidated. As you can also do manual memory management and bypass the GC in languages that target the JVM, I wonder if Swift would benefit more than Chris Lattner imagines from a port that targets it (at least, once the JVM supports value types, which it&#x27;s getting experimental support for at the moment).