No one should use the AT&T syntax (2021)
outerproduct.net
outerproduct.net
1. It matches the manual. AMD and Intel’s manuals agree about operand order, and IMO there is no justification whatsoever for an assembly syntax to fail to match the documentation. This is especially true for 3-address instructions and for two-input instructions like CMP.
All these arguments about mathematical notation or linguistics are nonsense in my book. The correct order for the operands of ADD is what the corresponding manual says.
2. Intel address syntax is so much better than AT&T’s that it’s not even funny.
I have messed with code to save and restore AVX-512 registers, and this is about the only thing I like about AT&T syntax: there’s a uniform way to specify the instruction size that works when the instruction has no operands. I’m looking at you, XSAVE, XRSTOR, SYSRET, etc. The manual has no useful syntax to offer here.
I also don't understand why x86 syntax has to be mutilated when special syntax is supported for other architectures. It's somehow OK for ARM64 to have [x1, w9, sxtw #2] or [x3, #32]! and 68K to have (%a2,%d1.l8), but x86-64 can't have [%rcx + %rdx4].
Reading x86-64 disassembly in AT&T syntax is just painful.
I found them to be very regular and understandable.
So when I encountered intel chips and their assembly syntax, I disliked them.
So about that time I switched to higher level languages.
Are there still chips left have a nice orthogonal assembly language? I vaguely recall 68k being orthogonal too.
ADD A,B ;8 bit add, B to A
ADD HL,BC ;16 bit add, BC to HL
ADD B,A ;invalid! destination can only be A or HL
ADC A,B ;add with carry
SBC A,B ;subtract with carry
SUB B ;subtract (A is implicit, same for AND,OR,XOR)
LD A,(HL) ;load A from memory at HL
LD B,(HL) ;load B from HL
LD A,(DE) ;load A from DE
LD B,(DE) ;invalid! must be reg=A or mem=(HL)
LD A,(var) ;load A from variable
LD HL,(var) ;load HL from variable
LD DE,(var) ;extra opcode byte, 4 cycles slower
LD B,(var) ;invalid! must be A or register pair
JP (HL) ;load PC with contents of HL register, NOT MEMORY!
EX DE,HL ;exchange DE with HL (no other registers allowed)
EX HL,(SP) ;exchange HL with top of stack (no other reg,mem allowed)
EX AF,AF' ;wtf were they thinking?
EXX ;should be EX BCDEHL,BCDEHL' for consistency :)I would just add SUB A, B (etc.) and call it a day.
edit: looks like the Z800 was an improved/orthogonalized (and pipelined!) extension of the Z80 that maintained software compatibility. And it looks like the SUB A, B format is supported by eZ80 assemblers, though A is still the only valid 8-bit accumulator.
This doesn't make any more convincing an argument than the "linguistic" and "consistency" arguments he shot down earlier.
Why should putting the mutated argument first emphasize it any more than putting it second? If you order things the same way every time, people will implicitly know which one is the mutated argument because that's how the language is structured.
There really is no strong benefit to either ordering, other than that [src, dst] is closer to human language (which is a minor benefit at best and not worth fighting over).
Personally I think assembly should just have a little more syntax. If we wrote
dst = mv src
or dst <- add src
it would just be obvious.This really is where AT&T fell over: Their syntax evolution feels bolted on for the most part, and not well thought out. The result can be quite confusing in a lot of cases.
Remember that when the 8080 engineers left to found Zilog, they changed the MVI instruction to LD, as it had been intended.
(LD on 4004 and 8008, MOV on 8080, LD/ST and MOV on 8085, MOV on 8086)
Z80 is also highly unorthogonal (even more so than x86), but obscures this by using the same mnemonic with different operand combinations, not all of which are legal and some require prefix bytes.
"Compare A and B and do X if result is 'less than'" should never mean "do X if B is less than A".
Anyway, the most important consideration for me is that standard documentation uses Intel operand order. Intel's manuals are hard enough to read, I wouldn't want to additionally permute operands in my head.
My only point is that argument ordering is not something to worry about, and every argument I've heard one way or the other has been weak at best.
The argument above applies neatly to assembly instructions, which generally change one thing.
In English, but not Telugu.
I know the author is just making a joke here, and it is funny. Somehow it reminded me of some actual advice I've seen more than once on /r/thinkpad. I am paraphrasing from memory, but I'm sure I have the gist of it:
> When you get a brand new ThinkPad in a factory sealed box, the first thing you must do is completely disassemble it and inspect all the parts. When you reassemble it, repaste the CPU with a quality paste like Arctic MX-4. Now you will have a ThinkPad built to your standards, not Lenovo's.
Paraphrasing: "Upon executing HALT, the processor will stop, and nothing short of a reset will get it going again. Although threatening it with a hammer has been known to work on occasion, especially if it knows you've killed before."
Nice little animation.
To me, as a non-WEB developer, I think that would be hard to do on a WEB Page.
As for AT&T vs x86 assembly, it is a flame war I have not heard from in decades and a couple of arguments were hit upon in the article were part of that war. Nice hearing about an old classic war :)
I have not even looked at assembly in many decades, and back then I did the bare minimum with help from someone, so most of the article was over my head.
Forcing it as a high-contrast distraction upon people who just want to read an article seems like it will discourage some of them.
Discouraged people who nevertheless want to read the article, while using their pointing device to scroll, could add this to their uBlock Origin "My filters" tab:
||outerproduct.net/oneko.js$importanthttps://developer.mozilla.org/en-US/docs/Web/CSS/@media/pref...
I've had models assume different operand order in two different places in the same file.
x86 assembler really is the gift that keeps on giving. Sadly it doesn't given you anything nice.
> This is usually only brought up by people who have prior assembly programming experience, which makes me think we made the right choice.
I originally started with Intel syntax when learning assembly since it was just in my textbook. I sometimes ran into weird syntax issues I didn't yet understand.
For example, why "mov eax, [ebx + 2 * ecx + 4]" is allowed, but not "mov eax, [ebx + ecx + edx]" (1)? Or maybe "mov eax, [ebx + 3 * ecx]" (2)? Or maybe "mov eax, [2 * ebx + 4 * ecx]" (3)?
As long as it is a math expression it should work right? Why did the compiler keeps telling me the expression is invalid?
Later when I learned about AT&T syntax, everything started to make sense. The syntax ensures you cannot construct (1) and (3), and when trying to use (2) it explicitly tells you it expects 1, 2, 4, or 8 but got 3.
I started to use AT&T syntax since then.
In my opinion, there is one most confusing part about AT&T syntax, which is the condition based instructions. I guess the original author of the article did not do much programming with AT&T syntax so they did not notice.
cmpl %eax, %ebx
jae label
Now tell me if eax is bigger, should you jump to label?Instead, with Intel syntax it is pretty straight forward. The "jae ..." following "cmp ebx, eax" translates to "if (ebx >= eax) goto ..."
(BTW, the cat or whatever following your mouse pointer is super annoying)
Because it's translated into a single machine instruction!!! "mov eax,[[ebx]]" doesn't work either - not on x86 at least, historically there were architectures that had indirect memory references :)
It's still MUCH easier to read and write. How do you even remember the order in AT&T syntax? Shouldn't the scale factor maybe be given as a shift count since that's how the hardware works? By using familiar arithmetic expressions, it becomes perfectly clear what is meant, and anyone who actually writes assembly will quickly learn what forms are allowed. And if you are only reading the code, it doesn't matter.
Of course, AT&T syntax comes from UNIX, where the zeroth commandment is "Thou shalt have no other programming languages but C and shell scripts". People who have invested the effort to memorize something like 42 different levels of operator precedence, and fluently read and write symbol vomit like "(((void *)(int [])fn(foo,bar=baz==quux++))" are obviously desperate to make assembly look EVEN MORE complicated...
Actually we never learned the x86 binary representation of instructions in that assembly language class. The textbook also did not cover that.
All I wanted to say is: Intel syntax hides the fact that there are only 4 things in address calculation: displacement, base, index, scale. The compilation error is also hard to understand (at least for the compiler I used). It says something like "the expression is invalid" but you never know what went wrong.
AT&T syntax exposed the underlying requirement, and the compilation error is easy to understand.
Now I am okay with both AT&T and Intel, but when I was learning, I appreciated AT&T syntax more. Assembly is mandatory for CS major in that college, and AT&T syntax made my semester easier.
AT&T syntax forces you to learn this before writing or even reading a single line of code that references memory, instead of giving the illusion that maybe something like [eax+ebx+ecx] or [[eax]] was also allowed. I don't think that's very helpful. It also forces you to learn a very specific way of writing it that is completely unintuitive.
I'm somewhat sympathetic to the argument that assembly syntax should correspond to the underlying hardware, just not to such an extreme. For example, I prefer 8080 over Z80 for that reason (one mnemonic for each addressing mode).
Well, you already know this requirement, so it is not a problem for you. However for learners, who don't have even the remotest idea of machine instructions, all they get is some invalid expression error messages. Same message for all (1) (2) and (3) cases I listed above.
Maybe Intel syntax works better for experienced programmers. But at least for me, when I was a newbie to assembly, AT&T was better than Intel syntax. It made learning process easier.
> AT&T syntax forces you to learn this before writing or even reading a single line of code that references memory
I do believe that rules forced by language is a good thing. At least it helped me understand why (1) (2) and (3) didn't work
Many people believe Rust can help people write safer code. Why? Because of its rules. Incorrect ownership will be discovered by the borrow checker so a compilation error will force the programmer to correct it.
Maybe an experienced C++ programmer will be able to handle memory management correctly in C++, and loves the extra freedom that C++ brings. But Rust can be helpful for learners. It provides clear error message helping them understand why their code is wrong. While in C++? Segmentation fault.
(Needless to say, Rust also helps experienced programmers. Even experienced programmers write buggy C++ code.)
mov eax, [edi + 8*ebx + 3]
is written as mov 3(%edi,%ebx,8), %eaxNo, DWORD DWORD DWORD is obnoxious.
* The suffixes give you a lot of easy specificity about what the operands are, while they are unnecessary if you already know what the operands are.
* DWORD PTR [x + A + B * C] is wordy, but at least the math is clear, and you can write the arithmetic in any order you want (as well as using 3, 5, and 9 as multipliers). X(A, B, C) is more concise and has no operand order to think about, and not terribly hard to read once you learn.
* AT&T syntax from a compiler always uses the easy cases and never makes mistakes about placement of the "glyphs" ($, %, etc.). There is none of the example's "movq ($28), %rax" because having a symbol called "$28" is fundamentally dumb.
* Operand order in AT&T is really dumb, especially for 3-operand instructions, but if you're just reading, you really don't need to know operand order to understand what's going on.
For these reasons, I assume the OP was written by someone who I assume writes a lot of assembly by comparison.
I always found AT&T order much more intuitive to read. The "what am I doing" and then "where will it go" felt natural to me.
But I didn't do much assembler, so my opinion doesn't matter :)
I might be biased as an Intel-syntax-loving assembly-writing heathen.
The article also discussed this argument, and I find it weird, because higher level languages also put the operation on the right side.
Maybe I just prefer having the operation and the target close together? Having `abs(x) = y` put the absolute value of y into x, would be extremly weird to me in C++.
> I might be biased as an Intel-syntax-loving assembly-writing heathen.
I guess the world is on your side, and I just avoid assembly where possible :D (although, not really because of the syntax part, I can live with either syntax decision in the end after getting used to it)
Jokes about C++ aside, many DSPs with their own assembly languages have chosen to learn from C and higher level languages. The following style of syntax is not uncommon for DSPs (I am not using a specific one, but an abstract style), where writing assembly is expected:
R1 = R2 + R3;
R5 = R1 * R3;
R4 = POPCOUNT(R1);
R3 = [R0] <- loads data at pointer in R0 into R3
Curiously, RISC-V didn't do this despite having the option to write their own new assembler. I guess the old opcodes die hard, but I also assume it has to do with LLVM strongly suggesting [OPCODE] [ARGS] syntax for assembly.
I know, if it returned something it could write into, it would. .__. https://godbolt.org/z/7h6YMqbEW
> Jokes about C++ aside, many DSPs with their own assembly languages have chosen to learn from C and higher level languages. The following style of syntax is not uncommon for DSPs (I am not using a specific one, but an abstract style), where writing assembly is expected:
> R1 = R2 + R3 R5 = R1 * R3 R4 = POPCOUNT(R1) R3 = [R0] <- loads data at pointer in R0 into R3
That's really cool to know, my exposure to assembly is mostly limited to a little bit disassembly for all kinds of reasons (so mostly x86 reading) or 1 instance of inline assembly. Most of the other instances I worked with SSE intrinsics instead, so my knowledge is very limited.
This thread was extremly interesting to read.
The at&t syntax didn't appear in a vacuum. There are several architectures, e.g. popularly m68k, which use this order.
AT&T syntax also pre-dated Intel syntax by several years, and was based on the PDP-11 assembler which had the destination last. Many UNIX vendors later moved to 68000 which also had the destination last (possibly influenced by AT&T syntax, as all their 8-bit processors embedded the destination in the opcode). Other chips like SPARC, which was developed by a UNIX vendor for a UNIX-like machines followed suit, probably because all their engineers were already using AT&T syntax anyway.
As for the other arguments in the article, I think it's kind of fair to complain about the syntax, but also important to remember that as part of the GCC toolchain, the assembler was never really intended for humans to use, other than for short bits of glue code, but was instead really intended to just have enough functionality to assemble the output from cc1. When GCC was ported to Intel, it almost certainly made sense to keep AT&T syntax in gas because gcc developers were familiar with it, and the cc1 backend would probably have required extensive changes to output in a different order even though it wouldn't really benefit anyone because nobody was actually intended to read the intermediate .S file other than while debugging gcc itself. Obviously nowadays, cc1 directly outputs to .o and the -S flag is more for debug, and sometimes generates code that doesn't always exactly correspond to the generated code in the .o.
But anyway, despite all that defence for why AT&T syntax, I agree, it's ugly and I always hate writing code with it. I almost always use an assembler that uses the chip vendor's preferred syntax on whatever platform I'm using, mostly because it's then easier to refer to documentation.
NASM syntax:
mov rax,[Foo] ;load var into reg, QWORD is implicit
add dword [Bar],1234h ;add constant to DWORD var
movzx eax,byte [rsi] ;zero extend BYTE to DWORD regWow, that surprises me. Without knowing anything, I would think it were the other way around, because in Intel syntax, the address calculation is more explicit than the comma-and-parentheses form; the registers don't require a % sign every time, making it distracting to read the code; and the opcodes are easier to read out loud because they lack the suffixes.
For me, it was exposure to M68k that made Intel syntax seem absolutely awful. AT&T syntax is also awful, but at least the operand order is the way that I'm (still) more used to.
I don't know why 68000 is so popular here (maybe people on HN learned it in school?), but you're far more likely to be writing ARM assembly or these days RISC-V assembly than writing for an antiquated core like the 68000.
Believe it or not, outside the x86 world, Intel-style syntax has basically won.
I abandoned assembler when I switched to Linux and got my first PC because x86 assembler was so awful I couldn't bear working with it.
AT&T syntax made it slightly less atrocious through familiarity, but only slightly.
AT&T syntax is broken (2021) - https://news.ycombinator.com/item?id=33652023 - Nov 2022 (44 comments)
Why no one should use the AT&T syntax ever - https://news.ycombinator.com/item?id=27089329 - May 2021 (8 comments)
Also:
AT&T Syntax versus Intel Syntax (2001) - https://news.ycombinator.com/item?id=33585154 - Nov 2022 (105 comments)
What was the original reason for the design of AT&T assembly syntax? (2017) - https://news.ycombinator.com/item?id=26127368 - Feb 2021 (58 comments)
Others?
> mov eax, ebx ; (1) load one dword from the EBX register and store it in the EAX register
Note how the comment says something entirely different than the assembler mnemonics ("load" instead of "move", and the source and target register in the comment are in different order than in the mnemonics).
My simple rule of thumb when I worked both on Z80 and 68k in the early 90s:
- LOAD dst WITH src
- MOVE src INTO dst
Intel syntax would make a lot more sense if it would use the Z80-style LD instead of MOV.
Not that I'm an AT&T syntax fan, but this one detail always bothered me. But I guess I should get used to it because ARM also got it "wrong" ;)
They are not as arbritary as first glance as they might appear, related functions often cluster together creating families of opcodes. How many instructions in the family, what circuits and hardware components they use, and other optimization factors influence the arrangement of instructions as well which can determine the binary number the opcode uses.
Intel syntax's
sub rax, rbx
is equivalent to AT&T syntax's subq %rbx, %rax
...and it computes (rax - rbx), not (rbx - rax)!I suppose you could justify the AT&T syntax as meaning "subtract rbx from rax". But it seems incredibly counterintuitive to me.
Edit: As another comment pointed out, this issue also applies to comparisons and is even worse there. Intel's
cmp rax, rbx
jg foo
is equivalent to AT&T syntax's cmp %rbx, %rax
jg foo
and it means "compare rax to rbx; if it's 'g'reater, then 'j'ump to foo". There's no good way to write that in English where rbx comes first. At best you could say: "compare rbx to rax; if rax is greater"… $ mv new-name.txt old-name.txt
I defer to the blogger's superior UX expertise!A few years back, mpv changed to make named arguments require an equals rather than a whitespace. I've gotten used to it, but it seems like such a weird outlier.
Merge the right into the left. Sure, totally intuitive.
On the other hand for shell scripts, using from to makes it more consistent. With what? Pipelines read a from-file process it and then write. from to.
;; Lisp
(setf a (xor b c))
;; Kanren
;; Unification instead of mutation.
;; Therefore these are equivalent:
(== a (xor b c))
(== (xor b c) a)
# Contrived untested Raku example
$b ^^ $c ==> my $a;
% Amberjack
b xor c -> a
// Java
var a = b ^ c; my $b = 42;
my $c = False;
$b ^^ $c ==> my $a;
say $a; #[42]
^^ yepAlso, memcpy(dest,source,len) in C.
The modem will perform the local analog loopback test if &T1 is selected. The test can be run only when in an asynchronous operation in non-error-correction mode (normal). To terminate the test in progress, the escape sequence must be entered first (see Section 3.1.1). If S18 is non-zero, the test will terminate automatically after the time specified by S18 and the OK result code will be reported.
https://web.archive.org/web/20151028101531/http://www.zoomte...
AT vs AT&T phone company and childhood memories.
(Clearly this was wrong after clicking into the article)
This is a mov instruction, not a ld instruction.
There is no loading going on. The value in EBX is already loaded. Otherwise it wouldn't be in a register.
It's hard for me to take someone seriously who doesn't understand the difference between a load and a move but feels as if they have the right to an opinion about assembly.
https://cdrdv2-public.intel.com/819723/325462-sdm-vol-1-2abc... Volume 1, page 7-3 (page 181 of the PDF).
In the context of assembly, load/store refers to memory ⇆ register.
https://www.felixcloutier.com/x86/mov-2: "MOV — Move to/from Debug Registers ... At the opcode level, the reg field within the ModR/M byte specifies which of the debug registers is loaded or read. The two bits in the mod field are ignored. The r/m field specifies the general-purpose register loaded or read."
The question is whether "Intel also uses 'load' in the descriptions of register-to-register moves."
What Felix Cloutier says is not probative on this question.
What Intel says in Intel's manual posted on Intel's website is the definitive authority on what Intel says on the matter.
https://cdrdv2-public.intel.com/819723/325462-sdm-vol-1-2abc..., Vol. 2B page 4-42, (physical page 1263): "MOV—Move to/from Debug Registers ... At the opcode level, the reg field within the ModR/M byte specifies which of the debug registers is loaded or read. The two bits in the mod field are ignored. The r/m field specifies the general-purpose register loaded or read."
Also, the masm syntax is full of weird-o heuristics to account for the fact that you can omit the size of the operands, and I have been hit by that several times. Yes, I prefer writing in masm, but if I had seen at&t syntax first, I am sure I would be a fan of at&t syntax.
Why would anyone even write in an assembly language instead of a human friendly language that has a simple compiler to to goofy assembly?