HN2new | past | comments | ask | show | jobs | submitlogin

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.

-Chris Lattner @ Modular



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


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.

¹ e.g. https://mlir.llvm.org/docs/Dialects/Affine/#affinefor-mliraf...


Ah makes sense, they can do more with an intermediate IR (MLIR) and not make llvm churn through a ton of unnecessary nodes. Thanks!


And developing a ton of IR leads to large binary sizes, but that is something you also see in C++ too with template specialization.


Do you see overloading as must-have?


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.


Hope it works out!




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: