Everyone Says Assembly Is Untyped–Everyone Is Wrong

adamrezich1 pts0 comments

Everyone Says Assembly Is Untyped—Everyone Is Wrong - gingerBill

-->

TL;DR: Odin’s inline assembler is the best in the world of any language now.

Not string-based like GCC/Clang/Rust. Templates are real, typed, compiler-checked code.

Assembly isn’t untyped: each instruction is a polyadic typed algebra.

One syntax across all ISAs, Intel order, using Odin’s own tokens (coherency over consistency).

Named asm(params) -> (results) [bindings]: ties, pins, scratch, clobbers explicit — no %0/=r soup.

Multiple return values for free (assembly is polyadic: e.g. rdtsc, cpuid, div).

Real semantic diagnostics via core:rexcode encoding tables, not just typo fixes.

Restricted hygienic macros that replace many intrinsics; built in ~7 days.

I have been asked why Odin even bothers having its own custom inline assembler at all. Isn’t inline assembly a solved problem? You take a string, you hand it to the assembler, and you let it sort out the rest. Everyone from GCC to Clang to Rust

Rust’s inline assembly is a little more sophisticated because of the macro system, but not that much more. does more or less this. The wheel has been invented, right?

This is precisely the design I did not want, and precisely the design that most languages have settled for. My goal from the beginning was an inline assembler that actually integrates with the rest of the language rather than feeling bolted on the side. And I honestly believe that what Odin has ended up with is the best inline assembly system in any language right now. I don’t say that lightly, and by the end of this article I hope you’ll at least understand why I believe that to be true.

The String-Based Nonsense<br>Let’s start with the thing I was reacting against. Here is what a trivial “add one” looks like in GCC-style extended asm using x86 AT&T/GAS syntax:

int dst;<br>asm ("movl %1, %0\n\t"<br>"addl $1, %0"<br>: "=r" (dst) // outputs<br>: "r" (src) // inputs<br>: /* clobbers */);

Look at this and ask yourself: what does the compiler (as opposed to the assembler) understand here? The answer is “almost nothing”. The body is a string. "=r" and "r" are explicit constraint strings, another little stringly-typed DSL glued to the side of the real DSL. The %0 and %1 are positional references into a list you have to count by hand. And if you get any of it wrong, the error you get back is not from the compiler that knows your types and semantics; it is from the assembler, much later on, pointing at generated text that was not written by you.

This is the sort of thing that happens when a feature is designed as an escape-hatch first rather than as a part of the language. Nobody seems to have sat down and asked “what would inline assembly look like if it respected the type system, the calling conventions, the constant system, and other things (like multiple-return-value semantics) of the host language?”. Rather, they asked “how do I bodge some assembly into this function with the least amount of compiler work?”, and a string was the answer.

These kinds of inline assemblers ignore all of the aspects of the host language, and just bodge it in. I didn’t; I designed one from scratch.

A Brief History of Bolting It On<br>Strings are not the only way this has been done, and it is worth looking at what previous languages/compilers have done, because some of these approaches are a heck of a lot better than what GCC/Clang did, and unfortunately this development has stopped in compiler space.

MSVC<br>Microsoft’s C compilers had a genuinely different approach. MSVC’s __asm was statement-based, not string-based. You wrote a block of real instructions, and (this is the good part) you referenced your C variables and labels directly by name, and the compiler resolved them for you:

int add_one(int x) {<br>__asm {<br>mov eax, x // 'x' is the C parameter, resolved by the compiler<br>inc eax<br>} // value left in eax is the return value, by convention

No constraint strings. No %0. No counting operands. Compared to the GCC contraption this is honestly pleasant to read, and for a long time it was how an enormous amount of Windows systems code got written. So why did it disappear?

Firstly, it was x86-only . When Microsoft moved to x64 (and later ARM64) they did not port it. The official guidance became “use compiler intrinsics, or write a separate .asm file and run it through MASM”. One of the stated constraints for the x64 compiler was to have no inline assembler at all. A whole approach was thrown away at the ISA boundary rather than generalized across it.

Secondly, even where it existed, the compiler did not really understand the block. It resolved your symbol names, but it carried no explicit clobber information; the optimizer largely treated the region as an opaque fence to be conservative around. It knew what x was. It did not give any feedback to the user as to what the instructions did.

Turbo Pascal<br>If you go back further, you’ll find Turbo Pascal, which I have an obvious fondness for, as I do for Pascals in...

compiler assembly inline assembler language string

Related Articles