Having looked at a fair amount of AT&T syntax assembly code over the years, the sheer nonsensicalness of the AT&T syntax cannot be understated.
You seriously want to argue that disp(base,index,scale) is clearer than [disp+base+index*scale]?
Is there any excuse, other than sheer laziness on the part of the parser writer, to require EVERY NUMERIC CONSTANT to be prefixed with a # and EVERY REGISTER to be prefixed with the percent sign?
So I can confidently say that the purpose of AT&T syntax was to make assembly programmers want to gouge their eyes out.
Advantages of having a common set of conventions might not be apparent if you look at them through the lens of a single ISA (x86 in your example); however, if you consider that porting UNIX and a C compiler to a new hardware architecture was everyone's favourite pastime back in the day, having the $ and % conventions greatly helped with reducing the cognitive load and shielding the engineer from quirks and idiosyncrasies of new assembly languages / ISA's.
Registers can have vastly different names across hardware architectures, e.g. they could be named r0, g0, gr1, f2, Motorola 68HC16 has registers named X and Y, or simply numbered register names have been used as well (if my memory serves me well, the IBM System 360 ISA was the most egregious example where LD 1,1 meant load a constant of «1» into the register number 1, albeit I can't remember in which the order). Then there are widly varying processor specific control register names, e.g. i860 had «psr», «epsr», «db», «dirbase», «fir», «fsr», «kr», ki», «t» and «merge» control registers in the ISA, whereas the PDP-11's own processor status word (PSW) was mapped to the 177776 address in the hardware and PSW was a widely used (but not mandated) MACRO-11 preprocessor alias that had to be manually defined in every assembly file.
Also, the register window SPARC architecture utilises names gN for global registers and lN for local registers, and with C compilers habitually emitting jump label names that start with a «L», without the help of extra semantic cues, it would not be immediately obvious whether ld l0, l0 meant «load the address designated the label named l0 into a register named l0», or «store the contents of the register named l0 into an address the label l0 refers to» (I am stretching the SPARC example a bit here to highlight my point as it has a load/store architecture). Therefore seeing a $ was an immediate «heads-up, a constant is about to follow» and, likewise, seeing a % indicated a upcoming register name. It certainly has helped me quite a bit with squickly settling into a new architecture by skimming over assembly outputs of C compilers and assembly parts of UNIX kernels.
AT&T conventions were rather widely applied across for some time, but it appears that they are less frequently followed nowadays.
“You seriously want to argue that disp(base,index,scale) is clearer than [disp+base+indexscale]?”*
Did anybody ever argue that?I always thought the AT&T code was indicative of a meta-assembler (https://www.encyclopedia.com/computing/dictionaries-thesauru...: “meta-assembler A program that accepts the syntactic and semantic description of an assembly language, and generates an assembler for that language. Compare compiler-compiler.”)
Such a meta-assembler would not know that this instruction does a few adds and a multiplication. It just would know how to pack a few values of varying bit width into a few bytes to output.
And yes, a meta-assembler could add syntactic sugar to support that, but they date from a time where kilobytes were expensive, and such ‘fluff’ deemed not worth it.
That also may be why it uses prefixes for register names. It avoids lots of relatively costly lookups in a symbol table. It also may make it easier/feasible to write a one-pass assembler.
For the registers the % prefix avoids needing to add some prefix to every symbol. Otherwise you couldn't have symbols with the same names as registers.
Not sure what you mean with every numeric constant prefixed with #. That's not the case on x86 AT&T assembly. Immediates require $, but that's the same as the Intel syntax.
Any assembly language needs to distinguish between immediate values and addresses.
So you need a prefix on one of them, unless you use different mnemonics for the same operation, depending on whether it uses an immediate value or a memory operand.
Doubling the number of operation mnemonics is clearly worse than having a single prefix.
The prefix should be required for the less frequent case, and that is normally the use of immediate values.
The Intel syntax as seen in the Intel architecture manuals is somewhat simplified, because it does not contain symbols and expressions.
The complete assembly language syntax, for example the one implemented by the Microsoft MASM, needs frequently to distinguish the immediate values.
Because MASM followed the Intel manuals, it did not use a prefix on immediate values but instead it used ugly and verbose prefixes on the memory operands, e.g. "WORD PTR ".
In order to avoid excessive verbosity, MASM omitted the memory operand prefix in many cases where immediate values could not be used, which is possible only because the Intel instruction set is much less orthogonal than almost any other instruction set.
Always using a prefix to distinguish immediate values from addresses is preferable to the inconsistent MASM method, because it can be tricky to remember which instructions can or cannot have both immediate operands and memory operands, so that the prefix is mandatory. Moreover, later ISA extensions could also change the available addressing modes for an operation, possibly mandating the use of the prefix.
In conclusion, the MASM way of distinguishing the immediate values, by using "WORD PTR ", "BYTE PTR " and other similar prefixes on many but not all of the memory operands is certainly worse than always using the prefix "$" on immediate values.
WORD PTR etc. are size prefixes that would be needed even with some $-prefix for immediate values. I don't see the connection. Immediates and memory operands are already distinguished by [].
The equivalent to WORD PTR etc. in AT&T syntax is the size-suffixes on instructions (addl, addw etc.).
There are a lot of registers. 150+ named ones, including rarely used ones like SPL -- the low 8 bits of the stack pointer. This restriction would break existing assembly code whenever Intel added a new one, which happens regularly.
I'm honestly ok with prefixing registers with a consistent symbol, and discerning for eg. immediates and literal addresses in a uniform way is pretty useful in a lot of instruction sets. I think those are ok.
On the other hand, though, there are the ways that it diverges semantically from the platform's norm: Indexed-mode, like you said, adding suffixes (for word size) to opcodes, the always popular reversal of src,dst arguments. That's where it bothers me.
When you put it all together though it just gets a lot more frustrating. On the plus side at least you always know what you're looking at.
> While Intel always used a dst, src operand ordering for its assembler, there was no particularly compelling reason to adapt this convention and the UNIX assembler was written to use the src, dst operand ordering known from the PDP11.
Tangential but I like to see this as sort of two ways of thinking where the operands are in both cases ordered so that what you “have” is closer to “you”.
So in Intel syntax it’s like you are thinking from the CPU perspective and what you “have” are places that you want to put data into. So dst, src.
In AT&T syntax it’s like you are thinking from the perspective of a program, and what you “have” is data that you want to put into places. So src, dst.
I’m not suggesting that this is the reason mind you. Just saying how I like to think about it.
If that’s true, most programming languages work from the CPU perspective. I think that suggests the syntax may have come from consistency with “A = B” in programming languages (similar to the initially weird looking argument order of strcpy and friends in C), or, maybe, programming languages followed what assemblers did, or the two are similar because of some other common reason.
What I don’t like about Intel’s syntax is that the normal phrase is ‘move A to B’, but ‘mov A, B’ moves what’s in B to A.
(The reason I don’t like it may be because the first CPU I programmed was a 6502, which had separate load and store instructions. Unfortunately, that doesn’t work well with memory-to-memory (which the 6502 doesn’t have) or register to-register (for which the 6502 used a T for ‘transfer’, as in TXA for ‘copy X register into Accumulator) instructions)
Mathematical formulas, especially definitions, are often written like E = m * c^2 or pV = n k * T or F = m * a. Ie the simpler stuff often goes to the left.
So that tradition might have been where most programming languages picked the convention up from?
We're similar that way. I think of Intel as saying if you want to retrieve something, you need an accessible place to put it first.
So MOV is literally "move into this register I can access, this value that I can't access until it in a register."
For example, a wordy version of how I think through MOV AX,5 could be: "move into AX, the value 5" where AX is an accessible container for the value 5.
I did assembler coding for more than a decade, so I'm pretty nostalgic about it. Nothing I do really calls for it anymore these days.
I've always understood it as coming from mathematics: if you're used to writing "let X = <complex expression>" in papers, then the high-level syntax "var X = complex_expression();" or low-level syntax "mov dstX, src" are all directly relatable.
I have no info, speculating: could it be to reuse the existing programmer's knowledge and code corpus when porting Unix from PDP-11 to x86 machines?
This is assuming that AT&T valued similarity with existing Unix assembly codebases more than it valued similarity with existing external x86 assembly codebases.
My speculation: they took their PDP-11 assembler and modified it to make an x86 assembler. Using a PDP-11-inspired syntax, instead of Intel syntax, reduced the number of code changes required.
My guess is someone long ago decided to "be different" for whatever capricious reason, since I don't think the syntaxes for any other architectures are different from what's in the official manuals.
That article is from 2007, so I don't think we're going to get that promised update any time soon. I really wish they would have gotten around to it, because toward the end they say:
"Another GAS's problem is that it still keeps very old standards, not all of which are the best choice."
I'd love to see some examples of those poor choice standards compared in similar fashion as the article's earlier examples. That would be pretty interesting. They might also be updated since then. Can't know without examples.
Their argument about jumps was a little bit brilliant. If your syntax (like AT&T's "gas") is:
operation [source],[destination]
then your jumps should have something before an included comma, to put the destination in the right field:
jmp $,destination ; $ alone in assembler means "here"
That would be crazy of course, since the source is implicit, and it would suggest you can somehow jump from other places. But not doing it makes the language inconsistent. Intel's destination-first syntax gets around that by lopping off the end instead of something from the middle. This is the same for other opcodes like INC and DEC (increment and decrement) where the source is implied by the destination where the answer is stored. Somehow that never occurred to me, and now I can't not notice it.
PUSH and POP kinda mess this up for either format though, since PUSH points to a source, and POP points to a destination. So for consistency, one of them is always backward. It's still less typing.
Also I'd (lightly) rebut the claim that Intel's assembler needs 2 steps to make a binary, and MASM doesn't. The Microsoft DOS compilers have a command line switch to invoke the linker for you. Intel's assembler doesn't, like the article says, but if you compile with the make command instead, you accomplish the same thing.
Also, Intel's compiler is actually 'as' and not 'gas.' GNU utils used to name it gas, but I only ever see it referred to as the original 'as' these days, and even then, it's linked (the file system type of link... now it's getting confusing!) to a binary with the target architecture in the name. But no 'gas'. This might have happened after the article was written, though.
For the jmp instruction, both source and destination are the rip register. That other thing is an immediate value. Really jmp is just mov or add, with the rip register. Like so:
It was never legal to jump that way, though. And it wouldn't make sense if it was allowed by the cpu for your jump source to be somewhere you haven't been yet. So the $ "here" makes more sense, at least to me. Literally "from here, to there" versus "from there, to there."
Here's a similar "illegal" re-envisioning of a call:
push rip
mov rip, _interrupt ; a weird illegal jump
...
_interrupt:
...
pop rip ; similarly illegal way to RET
The workings are the same, but the cpu won't let you write it that way of course. The source and destination are still clear and intact because you start by pushing the source onto the stack before giving the destination, so it's at least good for demonstrating that bit of near-consistency in the Intel syntax.
Part of the reason these rip shenanigans aren't allowed is because you'd have a hell of a time jumping or calling to a different SEG (segment), as INT always does, and the jumps can do if you need them to, because you'd have to push change the index pointer and the segment at the same time somehow. So you'd be limited to .COM (single segment memory images) file format with no interrupts if that's how you wanted to go about doing them. Simply "editing" rip, if you could, would stick you to a single memory segment.
At the moment this question is closed as "opinion based". Which is ridiculous. There really needs to be a better scheme for how questions are closed. It's way too easy for questions to be closed for spurious reasons and the current method of reopening them is too hard.
The question is asking for historical information, not an opinion.
I generally love Stack Overflow, and have been active on it for ages, but there are a huge number of mods who see it as their life mission to close as many questions as possible. See also: Wikipedia deletionists.
I feel like Wikipedia is different because it actively harms the platform when people put up non-notable content, since it reduces the signal to noise and turns the platform into a way to launder legitimacy/importance. For example your local dentist should probably not have a wikipedia article, and you don't want there to be an arms race of every local dentist trying to get a wikipedia article so that they seem important.
The other issue is that many novice contributors write with an improper voice and don't cite their sources - which again lowers the quality of the platform.
Stack Overflow is different, as long as a question is on-topic and not a very close repeat I think there is no great reason to close it.
I guess it's one of the easiest things you can do as a mod, so if you want to do a lot of mod things... to the extent SO gives you "points" for it, it might even be engineered (on purpose?)
Correction, there are no points given for voting to close. Points are only for positive votes on questions and answers (and also, up to a certain number, for edits that are approved by other users).
That means that if spammers spam enough questions for the mods to shut down, the mods' accounts will be frozen and spammers will be able to spam unchallenged.
Also, that requires fine-tuning of the "too much".
One more subtle thing that annoys me is that there is a cultural difference between the engineering (including software) .SEs and the science ones (particularly mathematics) - the engineering ones aim to be a central repository of problems and solutions, whereas the mathematics ones will often respond to a question with another question. This isn't that bad for a student reading the responses to his or her question, but it goes against the ethos of SE in general.
I agree. The question was interesting and I wouldn't have minded if it was allowed to remain long enough to get some answers.
The best answer given is still flawed (eg: his mov example is backward, so he's actually describing movl from gas). So a continuation of the conversation would have been worthwhile.
> At the moment this question is closed as "opinion based". Which is ridiculous.
The -6 answer [1] also displays one of SO's other ridiculous habits: kinda ignorantly wander in to unhelpfully say the question is wrong and hope for sweet e-points.
If you do that, drop the ‘mov’. Before you know it you have a rudimentary programming language with a bunch of special variables, one for each register, and the ability to introduce other names for memory regions.
The intent of assembly is to avoid this kind of complexity.
However, there's nothing wrong with having the assembly language mnemonics/operators be symbolic.
`R1 = R1 + R2` is an acceptable way to represent your architecture's add instruction. You can't support arbitrary expressions of registers (but assemble-time constant expressions are usually ok).
> even when that requires two instructions.
In fact, some assemblers do provide assembler-mapped instructions or other similar features like this. The inverse is also possible: assembler optimizations to collapse multiple independent instructions into a single one.
> You can't support arbitrary expressions of registers
> In fact, some assemblers do provide assembler-mapped instructions or other similar features like this.
So, which is it :-) ? I think you know both are true, but for those who don’t:
The original intent of assembly was to make it easier to encode instructions. Those assemblers provided a 1:1 mapping between assembly instructions and CPU instructions.
(There may even have been assemblers that map assembly instructions 1:1 on machine words, but I think declaration of multi word memory regions was a very early addition. It’s so much more natural to write “give me 10 zero-initialized bytes here” than to write ten statements “give me a zero-initialized byte here”
So, that’s your first statement.
Macro assemblers (aka high-level assemblers) give you the second one. They let you write macros that, for example, add multi-word addition loops to your (now virtual) instruction set.
“High-level assemblers typically provide instructions that directly assemble one-to-one into low-level machine code as in any assembler, plus control statements such as IF, WHILE, REPEAT...UNTIL, and FOR, macros, and other enhancements. This allows the use of high-level control statement abstractions wherever maximal speed or minimal space is not essential; low-level statements that assemble directly to machine code can be used to produce the fastest or shortest code. The end result is assembly source code that is far more readable than standard assembly code while preserving the efficiency inherent with using assembly language.
High-level assemblers generally provide information-hiding facilities and the ability to call functions and procedures using a high-level-like syntax (i.e., the assembler automatically produces code to push parameters on the stack rather than the programmer having to manually write the code to do this).
High-level assemblers also provide data abstractions normally found in high-level languages. Examples include structures, unions, classes, and sets. Some high-level assemblers (e.g., TASM and HLA) support object-oriented programming.”
You seriously want to argue that disp(base,index,scale) is clearer than [disp+base+index*scale]?
Is there any excuse, other than sheer laziness on the part of the parser writer, to require EVERY NUMERIC CONSTANT to be prefixed with a # and EVERY REGISTER to be prefixed with the percent sign?
So I can confidently say that the purpose of AT&T syntax was to make assembly programmers want to gouge their eyes out.