It's the statement form, uses Intel syntax, and the compiler keeps track of which registers are modified.
amluto 1 days ago [-]
I have very mixed opinions about the custom syntax. IMO the correct asm syntax, with very few exceptions, is the one in the manual. This is why Intel syntax is right and AT&T syntax is wrong: the ISA comes from Intel, the docs are from Intel and AMD, and those docs use Intel syntax.
So I was kind of hoping that the custom syntax would at least result in a very, very strong checker, at least as good as Fil-C’s. Maybe with an escape hatch to say something like “I know it looks like I clobbered xyz, but I promise I really didn’t.
Sadly, the CPUID example in the article apparently compiles, but IMO it shouldn’t have: CPUID takes two inputs, in EAX and ECX, and the example forgot to bind ECX as an input. One might argue that CPUID takes even more inputs if you’re on a VM and doing something special, but ECX is really quite unambiguous.
applfanboysbgon 1 minutes ago [-]
The syntax in the manual is embarrassingly outdated. Like, it's actually a disgrace and shameful for our profession that assembly languages and tooling are stuck in the previous century. There is absolutely zero logical reason we should be constrained to such primitiveness.
CBLT 1 hours ago [-]
> the CPUID example in the article [...] forgot to bind ECX as an input.
I'm not really familiar with this stuff, but the example uses what it calls a "pin" (which in their docs is a type of "binding") on ECX before calling CPUID.
AshamedCaptain 5 hours ago [-]
> AT&T bakes the width into the mnemonic (movb, movw, movl, movq [...] Intel’s syntax is to prefix the memory operand with byte, word, dword, or qword, but Odin’s just uses the Odin type system directly.
In GAS you can skip the width suffix from the mnemonic, and in most Intel assemblers you can skip the memory type operators like byte. They happily guess it from the operands. The problem is that on x86 (but also other ISAs, even if to a lower extent) the different operand sizes have a lot of side effects, which is why everyone just makes the operand size explicit, up to the point that apparently the author/LLM believes that it is mandatory to specify them.
This kind of defeats the headline of the article...
Tomorrow you need to pass a 128 bit int into two registers and your fancy syntax then also becomes a messy bunch of hacks. This is why everyone's inline assembly syntax looks like that, because they want to cover the weird cases (gcc's one is almost like an history book). You're normally using inline assembly for when you have some ridiculous corner case, if not, then what you ought to use is more akin to intrinsics...
Also it forgets Watcom C, which does have a complete but messy syntax for inline assembly (which combines nicely with its ability to specify really weird calling conventions).
WalterBright 9 minutes ago [-]
Zortech had a complete inline assembler in the 80's. It's now in the D compiler!
winocm 3 hours ago [-]
Oh man, #pragma aux.
sxzygz 3 hours ago [-]
This article is really about the inline assembly syntax developed for the author's programming language Odin (and definitely nothing about TALs, typed assembly languages). There are a lot of interesting ideas here.
One of my criticisms, however, is simply pointing to how similar mainstream general purpose CPU architectures have become; they are all C machines. This radically simplifies the complexity on the compiler front where, it seems, the author is targeting amd64 and aarch64. Extending the compiler to rv64 will probably be straightforward.
I don't know anything about Odin, or its compiler implementation, but I imagine the language adheres to a view of the machine that matches the C machine model. Imagine a more esoteric language, the compiler would probably need an intermediate language matching the C machine model and in which the inline assembly would have to have survive some idempotent lowering to the intermediate representation before being further lowered to the object code. These details are what I am really curious about and probably the most intellectually stimulating.
The most interesting possibility is if the Odin compiler is itself written wholly in Odin. If this were the case, it would really show the power of the inline assembly syntax. As far as I am aware no optimizing compiler has really pushed this angle whilst targeting multiple instruction architectures. If I recall correctly, even the Plan9 C compiler moved some basic optimization to their genericized assembler, and I've not kept up with it as it's evolved into the current Go compiler.
Very interesting work as I have often though about inline assembly syntax in a high-level language. Keep it up gingerbill.
genxy 1 days ago [-]
An avenuge of research worth being sniped on is Typed Assembly Language
TALs are not what I am referring to here. I am arguing that assembly is already typed and does not need extra annotation to be typed.
TALs are also solving an entirely different problem.
genxy 1 days ago [-]
The technique is good, and compilers that interact with assembly should do this, but as you outline, they basically just shove blobs of text around and hope for the best.
I didn't say you were referring to TALs. Yours is a syntax level check, not type checking of the program in the normative sense. It might be more accurate refer to your technique as an "instruction signature", rather than a type.
I would argue that that are complementary and not entirely different.
I thought it would be interesting for folks.
questionableans 1 days ago [-]
But a language being “typed” doesn’t tell us anything useful. Untyped languages are typed too: they’re uni-typed (every expression is an expression).
I think you do your analysis a disservice by focusing on “is assembly language typed?” as the top line question. The more interesting question you examine is what do the type constraints in inline asm offer, and how do they interact with the host language’s type system?
gingerBill 1 days ago [-]
I know that "untyped" means a single-type, but assembly operands have multiple different kinds of types (as I state in the article). What makes it really interesting is what you can know about each instruction and what it does (what operands it excepts, what it clobbers, what side-effects its has, etc).
And from that huge table of type information, this can be used to give good error messages and suggestions to the user because the compiler actually knows all of this. The type constraints here allow for a lot more than information that normal assemblers just don't give.
questionableans 1 days ago [-]
Yes, and your second paragraph above is the interesting part that I would want the reader to focus on, starting from the title.
magicalhippo 1 days ago [-]
> But because of its time period, the built-in assembler only ever understood up to 80286 instructions, so the day you wanted a 386 and its 32-bit registers you were sent off to an external assembler anyway.
Or you just prefixed the instructions with "db $66", et voila your instructions were 32bit. I wrote a lot of inline 32bit assembly that way in TP 6.0 and 7.0.
Krssst 1 days ago [-]
Sorry, somewhat of a tangent but regarding:
> The %0 and %1 are positional references into a list you have to count by hand.
You can name your operands in gcc inline assembly.
Which is already infinitely more readable and requires no parochial sigils nor the arcane clobbering syntax.
Jblx2 5 hours ago [-]
Can you get an assemble-time or run-time type-error with assembly? Might be a fine article otherwise without the click-bait headline.
measurablefunc 5 hours ago [-]
> However, every instruction has a set of valid forms. Each form dictates the kind of each operand (register, memory, immediate, label), the class of each register (general-purpose, vector, mask), the width of each operand, the range each immediate may take, and what the instruction clobbers (flags, memory, particular registers). In x86, a mulps wants a 128-bit vector register; a crc32 in one of its forms wants a 32-bit destination and an 8-bit memory source; div reads and writes rdx and rax whether ask to it do or not.
The instructions have bit-width, arity/source/target requirements so technically there are types whereas an abstract virtual machine that only operates on some fixed set of integer registers is mostly untyped (modulo number of registers).
childintime 24 hours ago [-]
I don't care much about the typed part, I care much more that this is a good take on what an assembler should be, far ahead of the GCC monstrosity, that serves just one purpose well: it screams "don't use me". This feature could make Odin the language of choice for some types of projects, for it seems to remove so much friction.
IshKebab 4 hours ago [-]
Everyone is not wrong, they just don't mean the straw man that you are taking down. The fact that there are integer, float, vector registers etc. does not invalidate the point that people mean when they say "assembly is untyped".
fithisux 22 hours ago [-]
True. It takes some time to grasp but gingerbill is right.
https://github.com/dlang/dmd/blob/master/druntime/src/core/i...
It's the statement form, uses Intel syntax, and the compiler keeps track of which registers are modified.
So I was kind of hoping that the custom syntax would at least result in a very, very strong checker, at least as good as Fil-C’s. Maybe with an escape hatch to say something like “I know it looks like I clobbered xyz, but I promise I really didn’t.
Sadly, the CPUID example in the article apparently compiles, but IMO it shouldn’t have: CPUID takes two inputs, in EAX and ECX, and the example forgot to bind ECX as an input. One might argue that CPUID takes even more inputs if you’re on a VM and doing something special, but ECX is really quite unambiguous.
I'm not really familiar with this stuff, but the example uses what it calls a "pin" (which in their docs is a type of "binding") on ECX before calling CPUID.
In GAS you can skip the width suffix from the mnemonic, and in most Intel assemblers you can skip the memory type operators like byte. They happily guess it from the operands. The problem is that on x86 (but also other ISAs, even if to a lower extent) the different operand sizes have a lot of side effects, which is why everyone just makes the operand size explicit, up to the point that apparently the author/LLM believes that it is mandatory to specify them.
This kind of defeats the headline of the article...
Tomorrow you need to pass a 128 bit int into two registers and your fancy syntax then also becomes a messy bunch of hacks. This is why everyone's inline assembly syntax looks like that, because they want to cover the weird cases (gcc's one is almost like an history book). You're normally using inline assembly for when you have some ridiculous corner case, if not, then what you ought to use is more akin to intrinsics...
Also it forgets Watcom C, which does have a complete but messy syntax for inline assembly (which combines nicely with its ability to specify really weird calling conventions).
One of my criticisms, however, is simply pointing to how similar mainstream general purpose CPU architectures have become; they are all C machines. This radically simplifies the complexity on the compiler front where, it seems, the author is targeting amd64 and aarch64. Extending the compiler to rv64 will probably be straightforward.
I don't know anything about Odin, or its compiler implementation, but I imagine the language adheres to a view of the machine that matches the C machine model. Imagine a more esoteric language, the compiler would probably need an intermediate language matching the C machine model and in which the inline assembly would have to have survive some idempotent lowering to the intermediate representation before being further lowered to the object code. These details are what I am really curious about and probably the most intellectually stimulating.
The most interesting possibility is if the Odin compiler is itself written wholly in Odin. If this were the case, it would really show the power of the inline assembly syntax. As far as I am aware no optimizing compiler has really pushed this angle whilst targeting multiple instruction architectures. If I recall correctly, even the Plan9 C compiler moved some basic optimization to their genericized assembler, and I've not kept up with it as it's evolved into the current Go compiler.
Very interesting work as I have often though about inline assembly syntax in a high-level language. Keep it up gingerbill.
https://en.wikipedia.org/wiki/Typed_assembly_language
https://www.cs.cornell.edu/talc/overview.html
TALs are also solving an entirely different problem.
I didn't say you were referring to TALs. Yours is a syntax level check, not type checking of the program in the normative sense. It might be more accurate refer to your technique as an "instruction signature", rather than a type.
I would argue that that are complementary and not entirely different.
I thought it would be interesting for folks.
I think you do your analysis a disservice by focusing on “is assembly language typed?” as the top line question. The more interesting question you examine is what do the type constraints in inline asm offer, and how do they interact with the host language’s type system?
And from that huge table of type information, this can be used to give good error messages and suggestions to the user because the compiler actually knows all of this. The type constraints here allow for a lot more than information that normal assemblers just don't give.
Or you just prefixed the instructions with "db $66", et voila your instructions were 32bit. I wrote a lot of inline 32bit assembly that way in TP 6.0 and 7.0.
> The %0 and %1 are positional references into a list you have to count by hand.
You can name your operands in gcc inline assembly.
https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html#Output-...
Look for "asmSymbolicName".
On a phone so not checking if it builds, but something like `asm("add %[my_out], %[my_in], #3":[my_out]"=r"(outvar):[my_in]"r"(invar):);`.
add_three :: asm(my_in: u64) -> (my_out: u64) { add my_out, my_in, 3 }
out_var = add_three(in_var)
Which is already infinitely more readable and requires no parochial sigils nor the arcane clobbering syntax.
The instructions have bit-width, arity/source/target requirements so technically there are types whereas an abstract virtual machine that only operates on some fixed set of integer registers is mostly untyped (modulo number of registers).