Live data from Hacker News

Learning to Read X86 Assembly Language

patshaughnessy.net

161–170 of 238 posts

Re: Learning to Read X86 Assembly Language

#161

Earlier quoted context omitted.

The AT&T syntax for x86 thing is a huge mistake. For someone who grew up on normal processors (MC68000 and UltraSPARC) AT&T syntax is the best thing since sliced bread: it's perfectly logical to move something to somewhere, instead of "move to somewhere something".

I haven't done any 68K Asm and barely glanced at SPARC, but how does src, dst interact with noncommutative operations like subtraction and comparison? E.g. with x86 Intel syntax, cmp eax, 5 ; eax - 5 jg morethan5 ; eax > 5 ? then jump. sub eax, ecx ; eax = eax - ecx This is one of the most confusing things about AT&T x86 --- the comparisons and subtractions have their operands reversed, and you have to identify and m…

Exactly this. The instruction set is designed around Intel syntax. When you flip operands around because you prefer a different ordering, it messes up things like jg/ja/jl/jb/etc.

And it's all arbitrary anyway. Some people might prefer a [src, dest] ordering, but it's not inherently any more natural than [dest, src]. Look at variable assignments: "x = y" in almost any programming language will assign y to x.

Re: Learning to Read X86 Assembly Language

#162

And, of course, modern compilers will usually produce faster, more optimized code than you ever could, without making any mistakes. This assertion comes up over and over again in the last 30 years. Every time I've had it asserted to me, it always came from non-assembler programmers, who always wrote in a high level language. I have yet to see evidence of optimizing compilers generating code even remotely close in eff…

Why couldn't they though? Doesn't sound very hard to only generate the wordy prologues and epilogues when necessary (i.e. when you have to save any registers). Why they apparently don't do this is another question then.

Re: Learning to Read X86 Assembly Language

#163

Earlier quoted context omitted.

OK, so it's not a magical convention - it works it out bottom-up. First decide on registers for each parameter when you generate the code for the function, then based on that generate specific code for the instances where you call that function. Cool, thank you. Also that example, heh. I tried to go back gcc versions to see if there was a case where it didn't do TCO - nope. Also, I like how returning 0 is "xor eax, e…

> Also, I like how returning 0 is "xor eax, eax". Why is it so different with different optimisation levels? The default emits quite a bit of code, -O1 is `mov eax, 0`

I used to be a native asm programmer in Z80 and 680x0, and one reason for using XOR rather than MOV is to do with condition codes: the XOR operation will most likely update the condition codes (notably, the Zero flag), whereas MOV will probably not.

Often you would not want the flags updated when simply clearing a register - you're hardly likely to test the Zero flag having just set something to zero, because it's obvious, and more importantly you may want to set something to zero while preserving the flags from a previous operation.

But often you don't care about the flags so you can use the slightly shorter and/or faster XOR operation. It used to generally be shorter and faster because the MOV instruction had the initial step of an immediate load of the zero from memory.

And that's why it changes with different optimisation levels - the compiler knows when the flags need to be preserved, and if they don't it can get away with using XOR.

Re: Learning to Read X86 Assembly Language

#164
post #68

As AT&T syntax is still being used, at this point I'm willing to believe it's to purposely make x86 assembly hard and unpleasant to read and write. Perhaps so people will want to stay away from it, and in a way, to reduce the amount of code that is tied to the x86 platform. It spreads the thinking x86 assembly is terrible and ugly. Intel syntax is much cleaner, in particular, Intel Ideal (as opposed to MASM), and spe…

All of those are misleading because the FS segment override isn't specific to an operand. It applies to the whole instruction, which commonly has one place (a memory reference) for the override to take effect. You can have more than one override, but only the last one remains active. Normally you can have an override even if it isn't used. There are a few instructions with more than one memory access; the override only applies to one access and you don't get to choose which one.

Re: Learning to Read X86 Assembly Language

#165

Earlier quoted context omitted.

> Also, I like how returning 0 is "xor eax, eax". Why is it so different with different optimisation levels? The default emits quite a bit of code, -O1 is `mov eax, 0`

I used to be a native asm programmer in Z80 and 680x0, and one reason for using XOR rather than MOV is to do with condition codes: the XOR operation will most likely update the condition codes (notably, the Zero flag), whereas MOV will probably not. Often you would not want the flags updated when simply clearing a register - you're hardly likely to test the Zero flag having just set something to zero, because it's ob…

It's been a while since I programmed low level but I think on the 68k series they started to introduce cache and multi stage instruction pipelines. By alternating instructions working on different things you could get a decent performance gain. If every instruction had to wait for the result of the previous instruction to complete then it wouldn't be running at its best. With careful planning you could insert 'free' instructions but you would have to watch how flags were altered. We used to spend quite a bit of time optimising code to this level, eeking every bit of performance out of the hardware. Great fun.

Re: Learning to Read X86 Assembly Language

#166

Earlier quoted context omitted.

Suppose you're right and the registers are arbitrary. Then how would foreign function calls work? If you're compiling Rust code that calls into a C library, how does it know what registers to use? So the choice of registers cannot be arbitrary, unless the compiler knows the function is only used within an object file. The registers are predetermined by a convention unless you use the 'static' keyword to signal that t…

Then how would foreign function calls work? If you're compiling Rust code that calls into a C library, how does it know what registers to use? By using information kept with the function, or perhaps even encoded into the function name itself (as already happens when distinguishing between different calling conventions, or in the case of C++ name mangling)? Coming from an Asm background, where there basically is no on…

> By using information kept with the function

How would you do that with dynamically linked code, inspect functions you're calling at runtime before laying out your arguments?

> perhaps even encoded into the function name itself

That would mean name mangling in C and assembly.

> Coming from an Asm background, where there basically is no one "calling convention"

Right, because you can lay out memory however you want since you're at the assembly level. Higher-level code (C up) can't do that, so instead you've got standard calling conventions for inter-library call (inside a compilation unit, the compiler is free to use alternate calling conventions since it has complete control over both sides of the callsite, that's also how it can inline calls entirely).

> programmers would document which registers (almost always registers, rarely the stack --- and that can make for some great efficiency gains)

Some standard CC (though not the old CDECL) also use registers, so far as they can, depending on the arch. The SystemV AMD64 ABI uses 6 x86_64 registers for integer/pointer arguments and 8 SSE registers for FP arguments, with the rest on the stack.

Re: Learning to Read X86 Assembly Language

#167

And, of course, modern compilers will usually produce faster, more optimized code than you ever could, without making any mistakes. This assertion comes up over and over again in the last 30 years. Every time I've had it asserted to me, it always came from non-assembler programmers, who always wrote in a high level language. I have yet to see evidence of optimizing compilers generating code even remotely close in eff…

[deleted]

Re: Learning to Read X86 Assembly Language

#168

Earlier quoted context omitted.

I used to be a native asm programmer in Z80 and 680x0, and one reason for using XOR rather than MOV is to do with condition codes: the XOR operation will most likely update the condition codes (notably, the Zero flag), whereas MOV will probably not. Often you would not want the flags updated when simply clearing a register - you're hardly likely to test the Zero flag having just set something to zero, because it's ob…

It's been a while since I programmed low level but I think on the 68k series they started to introduce cache and multi stage instruction pipelines. By alternating instructions working on different things you could get a decent performance gain. If every instruction had to wait for the result of the previous instruction to complete then it wouldn't be running at its best. With careful planning you could insert 'free'…

Sure, things have moved on a lot since those days. I think in modern RISC architectures you can even specify whether the instruction should set the condition flags.

Re: Learning to Read X86 Assembly Language

#169

Earlier quoted context omitted.

It wasn't easy. Very asymmetric, not many registers, and tiny stack. The only things that were written directly in it were the operating system, and the virtual machines for higher level languages such as COBOL and MPL, because it was too hard to compile to. I worked on the virtual machines. After programming in it, I can assure you that programming in other assembly languages (including x86) is a breeze. I should pu…

Isn't the high level Burroughs assembly a compertly insane almost high level language? I looked into implementing it into my assembler and just ran in the other direction when I saw how much unnecessary co plexity there was in the language.

B90 was an 8 bit machine and was built at Cumbernauld in Scotland, where I worked. The B900 had similar architecture.

There was also a B1900 built in Liège in Belgium, which was a 24 bit machine whose instruction set was designed to run virtual machines (i.e. interpreters). Those systems had a reputation for being slow. I don't know much about them.

The Liège plant closed around 1982 and the Cumbernauld plant closed around 1985.

Burroughs mainframes (B5000 onwards to A series) may be the ones you're thinking of. These are justifiably praised for being ahead of their time. They were high level stack based machines with 48 bit words + 3 tag bits, and programmed directly in an Algol 60 variant, with additional instructions to enable COBOL to execute efficiently. There was no assembly language needed.

Re: Learning to Read X86 Assembly Language

#170

Also Matt Godbolt's gcc explorer is the the bee's knees for understanding assembly https://godbolt.org/ I think that playing around with it for 2 hours will teach you more than most classes on the topic. It really drives home why interactivity is such a bit deal in education. You should also try writing a script for counting instructions in binaries. It's pretty illuminating. Here are some sample statistics https://w…

Wow, thanks for posting this — it is a fantastic resource!

I love it even more for using Intel x86 assembly syntax, instead of the horrible AT&T syntax.

Post reply on HN