After my experience with swift and the bone headed decisions made in lower level design items of the language to make it more 'ergonomic' at the expense of compile speed and debug-ability, I worry about chris latner making the same mistakes again.
Swift literally took 10x more time to compile than equivalent objective-c code for not much benefit when it first came out, along with a debugger that to this day is significantly slower, unreliable and is still buggier than the objective-c one. It also does not scale linearly in compile time speed when you throw more cores to it, unlike c, c++ & objective-c, and linking is pretty much solved now with the mold linker.
There are / were huge chokepoints in the compiling chain that caused many of these regressions, that when I learned the minor benefits they brought made me do a facepalm. So many literal expensive megayears of life wasted for so little benefit.
%80 of the benefits of swift that were cheap to implement could've been ported over something equivalent to objective-c for little to no compile time penalties with the new ergonomic language syntax, such as strong nullables and enum ADT types. To this day, it's you still have to codegen mocks. It's frustrating.
I would wait and see what benefits that mojo will actually bring, and I hope that chris and the team there has learned from their mistakes with swift, and chose compile speed over little features that many could live without.
I also hope they use this as an opportunity to solve pythons horrible package management issue and copy ideas from rust's cargo liberally.
I have literally the same experience about slow and overly complex type systems and too much sugar as you're pointing out. I've learned a lot from it, and the conclusion is "don't do it again". You can see a specific comment about this at the end of this section:
https://docs.modular.com/mojo/notebooks/HelloMojo.html#overl...
```
Mojo doesn’t support overloading solely on result type, and doesn’t use result type or contextual type information for type inference, keeping things simple, fast, and predictable. Mojo will never produce an “expression too complex” error, because its type-checker is simple and fast by definition.
```
It's also interesting that Rust et al made similar (but also different) mistakes and have compile time issues scaling. Mojo has a ton of core compiler improvements as well addressing the "LLVM is slow" sorts of issues that "zero abstraction" languages have when expecting LLVM to do all the work for them.
Threading the needle between "making the same damn mistake over and over" and "second system syndrome" is hard. I really really hope Mojo can do it! And for my favorite language Python too, that's a nice little bonus.
> Mojo has a ton of core compiler improvements as well addressing the "LLVM is slow" sorts of issues that "zero abstraction" languages have when expecting LLVM to do all the work for them.
This sounds super interesting. Is there a good write up about this, or about the specifics of what specific type system features tend to make languages like Rust/Swift slow? Is it constraint-solving stuff? Exhaustive pattern matching?
We are a bit overwhelmed at the moment, but sure, this is something we'd eventually give an llvm devmtg talk about or something. Broadly speaking, there are three kinds of things that matter:
1) Type system. If you go with a constraint based hindly milner (https://en.wikipedia.org/wiki/Hindley%E2%80%93Milner_type_sy...) type system and then add things like overloading etc, you quickly get into exponential behavior. Swift has been fighting this for years now to tamp down the worst cases. This isn't good; don't do that.
2) Mid-level compilation model. "Zero cost abstraction" languages including C++ (but also more recent ones like Swift and Rust and now Mojo) rely in massive inlining and abstraction elimination to get "zero cost" behavior. LLVM can do inlining and simplification and all the other stuff, but it is a very low level of abstraction - generating a TON of IR and then asking LLVM to delete it for you is very slow, and shows up in the profile as "llvm is slow" because you're creating all the llvm ir nodes ("LLVM") and then asking llvm optimization passes to delete them for you. It is far better to not generate it in the first place.
3) Code generation: Reasonable codegen is O(N^2) and worse in core algorithms like register allocation and scheduling. This is unavoidable if you want high quality of results (though of course, llvm is also far from perfect). LLVM is also very old and single threaded. If you don't do anything to address these issues, you'll be bottlenecked in this phase.
These all have good solutions, but you have to architect your compiler in anticipation of them. Mojo isn't just a syntax or PL step forward, it is a massive step forward in compiler architecture.
Chris, you are so generous for explaining this stuff, thank you.
Quick question about #2 that I may not be picking up on, but how is it possible to avoid generating the "TON of IR" in the first place? (I'd live with a link to further reading if you're unable to go into details). Thanks!
[Not him of course] — My understanding is that instead of generating a ton of (LLVM) IR, you generate a little (MLIR) IR, because MLIR lets you define operations at higher levels of abstraction, suited to particular tasks. For instance, if you're doing loop optimizations, instead of crawling through a sea of compare and branch and arithmetic operations, you'd just use a higher-level ‘for’ operation¹. Only after you've done everything you can at the high level do you move down to a more concrete representation, so you hope to end up with both less LLVM IR and less work to do on it.
At this point (and after seeing the same thing working with Deno), I would love if the Swift team created some opt-in swift-2.0 mode that would get rid of that bits of syntax sugar that make Swift slow to compile.
I'd rewrite some parts of my apps in a few days and never look back.
With every new langauge that comes out, sure it brings a few interesting features, but it always takes 5+ years for IDE support, a good debugger, and compile speed (if ever) to arrive.
Good debugging tooling and compiler speed probably outweighs all the other benefits in the long run. Rust is a good example. JavaScript transpiling too. And once it arrives, people will probably be onto the next new language that barely has IDE syntax highlighting ready.
> pythons horrible package management
It really is terrible. The import system is so convoluted, but I guess it's quite old.
Python really needs a good monorepo-friendly package manager like Node's pnpm.
Even though the ESM transition is a clusterfuck, the new module system is actually quite nice and straightforward if you don't need to touch older code.
Go does very well here (maybe less so on the debugger, but the simplicity of the language makes me lean on the debugger a lot less than in more complex languages).
Perhaps other system programming languages can learn from D language where the DMD, D's reference compiler, compile itself and the entire standard library in less than 5 seconds, with parallelism turned off [1].
[1] Ask HN: Why do you use Rust, when D is available?
That depends on the sourcebase and compiler. Anyone who used Metrowerks C++ back in the 90’s can attest that C++ compilation doesn’t have to be slower than C. Swift has baked compilation slowness into the type system, and it will never be as fast to compile as C/C++ on the same hardware. The compiled code might be faster, though.
Don’t C/C++ scale linearly with more cores because you just compile different compilands on the cores? Wouldn’t that be the same for Swift? Note, I’ve never used Swift.
Nope it doesn't work that way for swift, stuff has to repeat on multiple cores per file within a single module, leading to being less compute efficient the more cores you add, unless you force everything into a single thread with WMO mode, but then you have manually manage your build graph very carefully to stay compute efficient. It could've changed since I've last looked, but there are still things like that are lurking around.
But the same also applies to C++. A boatload of my compile time is spent on expanding, parsing the same header files again and again in different compilation units. This can be solved by precompiled headers, yeah. But then again a boatload of the compile time is spent on instantiating the same templates in different compilation units again and again and again.
It's just that the fact that the language is cursed from the beginning gives an illusion of "linear scaling".
Swift has this weird thing where a two files next to each other can use the symbols from their neighbors without any import. I find this... disturbing.
It does not in swift's case. You have to set the namespace/module/library manually with the set of files and they never really embraced public submodules that might've reduced this.
Swift literally took 10x more time to compile than equivalent objective-c code for not much benefit when it first came out, along with a debugger that to this day is significantly slower, unreliable and is still buggier than the objective-c one. It also does not scale linearly in compile time speed when you throw more cores to it, unlike c, c++ & objective-c, and linking is pretty much solved now with the mold linker.
There are / were huge chokepoints in the compiling chain that caused many of these regressions, that when I learned the minor benefits they brought made me do a facepalm. So many literal expensive megayears of life wasted for so little benefit.
%80 of the benefits of swift that were cheap to implement could've been ported over something equivalent to objective-c for little to no compile time penalties with the new ergonomic language syntax, such as strong nullables and enum ADT types. To this day, it's you still have to codegen mocks. It's frustrating.
I would wait and see what benefits that mojo will actually bring, and I hope that chris and the team there has learned from their mistakes with swift, and chose compile speed over little features that many could live without.
I also hope they use this as an opportunity to solve pythons horrible package management issue and copy ideas from rust's cargo liberally.