I sincerely believe that the explicit distinction between inline assembly and “higher-level” code in Solidity was a mistake. A mistake which I see being suggested for solidity core in some of the example snippets on the blog. This is why I wanted to make my case for why and what I think you should be aiming for instead.
The Problems
Segregating language features between two scopes creates limitations on both sides that make it unnecessarily annoying to code and/or requires the language itself to duplicate concepts to make it ergonomic e.g.:
- The ability to define, export, import and invoke functions
- Control flow constructs (if-else-if conditional chains, switch, for-loops, while loops, do-while, etc.)
- The ability to create abstractions over data via structs, custom types and associated methods (via
using) - Access to low-level unsafe memory manipulation
- Static typing
- Access to non-uint256 constants
Furthermore because of these differences crossing the boundary becomes annoying as you have to think and deal with the differing semantics (all variables are u256’s vs. typed).
Why It’s Not Necessary
The main thing you can’t do in high-level solidity for which you need inline assembly for is:
- low-level memory control
- switch control flow statement
- direct access to certain EVM opcodes/functionality (e.g. raw
tstore,tload,byteopcode,log0-log4, etc.) - General optimization
All of this functionality can and would greatly benefit from being exposed as builtins directly accessible from the high-level language. This is what other mainstream system’s programming languages like C, Rust or Zig do. Low-level close to the metal primitives are primarily exposed and accessed via high-level wrappers.
For optimizations most of the things developers typically do are things the high-level language already provides but does sub-optimally:
- abi encoding
- abi decoding
- packed bytes handling
The optimizations that users typically do should just be incorporated in the compiler as these usually follow a pretty regular pattern. For others, adding unsafe casts and allowing you to do arithmetic and/or logical operations directly on booleans (e.g. non-branching bitwise or) covers another big portion of common optimizations.
You could even borrow the unsafe design concept from Rust: a simple function coloring system that has the developer specifically designate boundaries when core compiler invariants must be manually upheld and/or just as a general convention of tricky lower-level code.
Why Other Languages Have Inline Assembly
“If other mainstream languages have inline assembly why shouldn’t Solidity?”
My claim is that Solidity’s inline assembly isn’t comparable. Inline assembly in other languages let you largely bypass the compiler and directly handwrite the instructions that will be included final binary. This comes at a large DX tradeoff but let’s you really control everything when you need to. Solidity’s inline assembly doesn’t give you this power. It gives you lower-level access but not as far as to let you:
- write your own control flow with jumps
- manage the stack
- select exactly which arithmetic/logic instructions the final compiler will produce
- include instruction bytes the compiler doesn’t yet recognize
However this doesn’t mean that Solidity couldn’t or shouldn’t have “real” inline assembly, just that I believe the current approach is suboptimal and should either be ditched or reworked in a fresh compiler.
Conclusion
Inline assembly in its current form provides a sub-optimal developer experience and bloats the compiler by duplicating concepts. Solidity Core should take advantage of its clean slate mandate to either deprecate inline assembly entirely, lifting lower-level functionality into builtins or introduce true inline assembly.