Say Hello to X64 Assembly Part 3
0xax.blogspot.com
0xax.blogspot.com
There's also an extra "add rdx, 0x0" in int_to_str, a puzzling multiplication by 1 in print, and a confusion between the standard input (0) and output (1) fds.
I had to port a code generation module from Intel syntax to AT&T. What a pain!
Gas is so limited compared with what PC macro assemblers are capable of.
I started coding x86 Assembly back when MS-DOS 3.3 was modern in PC world, so never got to love AT&T syntax.
Specially after my porting experiment.
I needed to change a compiler backend to depend only on binutils (gas & ld).
So the existing NASM code had to ported.
NASM macros couldn't be mapped to what Gas offers and I also had some issues getting my head around how to write the addressing modes.
I was using a common trick for code generation.
Generate bytecodes that are actually Assembler macros.
A simple way to get static binaries, even if the performance is not optimal.
In the end I gave up and made the backend generate the respective Assembly code directly.
But, as a side note, all PC assemblers are crap. They could do so much more. I don't know why I'm expected to handle register allocation, or think about inter-unit dependencies that cause stalls.
On the x86, starting with the 486 and Pentium it wasn't possible any longer to fit all variations on the head.
At least I can't.
The porting I mentioned above was to make a compiler depend only on binutils.
Other than that, my last 100% Assembly application was around 1995.
http://madscientistlabs.blogspot.ca/2013/07/gas-problems.htm...
Has Gas understanding of Intel syntax meanwhile improved? It had a few bugs when I did this.
NB the above is a personal view & I'm not a programmer by profession...so if I missed something - no offence intended.
That said, I am able to better overcome technical challenges when they arise due to my curiosity and passion for the details.
But the group of programmers who need to work in assembly is not very large, I think. Most of them are probably writing compiler backends or working in embedded systems.
But learning assembly teaches you a lot about how the processor works, so it might be time well spent even if you never actually write assembly.
(NB I don't really know assembly, I have written like 20 instructions of inline assembly (and 3 inline binary instructions - what a rush that was!), so I am not an expert on the matter, but I can see the appeal. ;-)
* building compilers, especially JIT
* debugging binaries
* the lowest level of kernels
* understanding security exploits like overflows, format string, JIT spraying
* hardcore performanceA while ago, I was doing some exploratory work — basically trying out various approaches to a problem. There was a particular piece of code that was quite expensive to execute and that was executed a lot. The net result was that a single calculation of the required matrix took about two months.
It took more than a month of hard, full-time work to fit the problem into fixed-point registers and the SIMD model used by Intel SSE. Over two weeks from that month were spent on debugging, even though I went through multiple versions in C, each one closer to the target assembly. But the resulting speedup was well worth it: about 600x.
Now think about it. 600x. We went from "a single try/iteration takes two months" to "we can try six variations overnight".
So yes, assembly matters, for many reasons of which I only outlined one.
As a side note, people tend to focus on big-O algorithm performance, neglecting the constant. But the constant matters in practice, more than most people think.
You list one specific example where ASM provided a tangible speed benefit in a research oriented processing heavy env.. Indeed. Now take a good look at my original comment:
>>Sure I can understand some in-line ASM for hardcore speed-critical code
I could just go on and on about how software is getting worse day by day. Even John Carmack recently tweeted about this - how smartphones with massive processing power and small resolutions can't display graphics decently as compared to very old machines. Anyway, thanks for letting me vent out my frustration which has been building up for quite a few days now.
I can't agree more. Many programmers are traditionally taught that optimisation is some sort of "last moment" effort for when thing are seriously bloated and slow and that programmer time is more expensive than machine time, and it seems all this does is breed a lazy, selfish attitude; yes, it's very likely not worth expending an hour or even 10 minutes to optimise a throwaway script that's used by yourself and isn't a bottleneck in your process, but when your software is used by hundreds, thousands, or even millions or more users worldwide, who are often paying users, it doesn't seem nice at all to be making them all wait or be forced to buy newer hardware just so you can "save" a bit of work that you should be doing anyway.
It's like http://xkcd.com/1205/ , multiplied by the size of the userbase.
E.g. your start-up time of Win 8.1 compared to Win 2000 on the two quoted machines probably will not change more than +/- a few percent if you'd change from completely-unoptimized-compiled C, to hyper-unrolled-handtuned-SSE3-AVX-assembly.
Also the quoted few-second delay of MS-Office popping up the "Cell Format" dialog probably has more to do with the software asking (I'm making that up) the OS for a list of 1000 fonts and their properties, and less with MS Excel being written in optimized assembly, C, VBS or python, for that matter.
In my experience, some programmers like to discuss endlessly about the merits of replacing "i4" with "i<<2", or other stupid micro-optimizations[#], and are often completely oblivious about the "high-level" design, such as using proper data structures or how to schedule their work efficiently on multiple cores. And, for the "high level" issues, scripting languages might even help coding out larger systems quickly, so that more proper optimization effort can be spend on the actual computing intensive or resource hungry task.
[#] THIS IS JUST A STUPID EXAMPLE! DON'T DISCUSS i<<2 <-> i4 HERE, PLEASE!
It is certainly not hard to beat a compiler on speed or size (often both), and I believe that the only ones who can't are the ones who learnt Asm the stupid way that compilers generate code and not how the machine really works. E.g. it's commonly taught that x86 has 6 general-purpose registers (reserving eBP/eSP), but in reality eBP-based stack frames and the use of the stack is nothing more than a compiler-generated artificial construct. Even eSP can be used for something else if you really need one more register[1]!
Compilers follow the rules of their source language and impose strict, often unnecessary conventions on their output. Asm follows the rules of the machine, which are far richer and more expressive than the abstracted simplicity of any HLL. That being said, they have improved significantly over the years - the days when compilers would push/pop every register on entry/exit to a function regardless of whether it was used (or its caller needed the value preserved), or when the start of every function could be identified by a distinctive 55 89 E5 (push bp; mov bp, sp) in the binary are fortunately mostly history.
However, the advances in tech have outpaced the advantages of ASM. Yes your code is 10x more efficient than mine, but I can throw 1000x as much processing power at this via cloud & a scripting languages.
Yes you've seen more "compiler-generated code" than I have but realistically...who is going to win this p!ssing contest? You writing ASM code or me scaling a scripting language across clouds? I bet I can deploy cloud processing power faster than you can
MOV AX,CS
MOV DS,AX
Also - I'm not in the cloud game - the above is a simple example/play the devils advocate.
Note that it's exceedingly rare that one should be writing an entire program in assembly. But knowing assembly allows you, as you bump up against performance constraints, to inspect what code is being generated and understand what's slowing things down, which can help guide you in adjusting the source so it will be faster, applying a better set of optimization options, hand-tweaking the generated assembly, or ultimately rewriting sections while having a reference implementation.
I'll concede that you'll need ASM for core speed critical applications like stock exchanges...then again I conceded that point before we started debating...
>Note that it's exceedingly rare that one should be writing an entire program in assembly.
My mention of in-line ASM should have made that obvious that I'm not arguing for pure ASM.
>applying a better set of optimization options, hand-tweaking
You hand tweak your ASM, I deploy additional cloud clusters. We'll see which scales better (pro-tip: optimization is notoriously prone to diminishing returns).
My mention of in-line ASM should have made that obvious that I'm not arguing for pure ASM."
That wasn't me arguing with you, it was an attempt to clarify my position on the matter in light of what else I said.
"You hand tweak your ASM, I deploy additional cloud clusters. We'll see which scales better"
That depends tremendously on, again, the context - including where one already stands on the various curves and on how parallel the problem is.
"(pro-tip: optimization is notoriously prone to diminishing returns)."
As is parallelism: http://en.wikipedia.org/wiki/Amdahl%27s_law
This kind of stuff is why I stopped teaching and mentoring. It's like trying to convince creationists with facts.
Also, it's wide knowledge programmers spend the vast majority of time debugging. The other day I wasted an hour because a popular high level language doesn't warn users when comparing apples to oranges. I've spent on ocassion a lot of time chasing bugs in cloud services that make debugging close to impossible, too. There's no free lunch.
The article has a great example of this in the int_to_str code. While it's far from optimal code (e.g. the "mov rbx, 10" should really be outside of the loop, the "add rdx, 0x0" is useless, etc.), it's using the stack in a way that a compiler would probably never do, and something that likely isn't possible to express at all in a HLL. The C version would involve at least allocating an array with some pointers into it to act as a stack.
It's in these approaches - fully exploiting the features of the machine - where human-written Asm really shines. In some ways, the compiler "has its hands tied" when it comes to generating code, since it is limited to generating code that represents ideas in the corresponding HLL; it can make use of extensive knowledge of microarchitecture to schedule instructions/uops, sophisticated algorithms for register allocation and instruction selection, and generate code that is faster on a particular model of CPU than if it did not, but this falls short of the human who realises that neither a separately allocated array, nor such instructions to manipulate it, are actually needed.
The human can apply even more extreme optimisation making use of microarchitectural details if she so chooses; but the fact is that even without this, the code is probably already better than what a compiler can do. In practice, for general-purpose-code (i.e. excluding additional features) most models of x86 are all quite similar since the P6, and Intel has been doing a great job at making existing code faster with each new generation, so there is little need to go to that extreme; the optimal sequence of instructions for one model is often the same or close to optimal for the next one. (The odd ones out are the P4, and possibly some of the early Atoms.)
Another item that appeared on HN a while ago illustrates this "think outside the HLL/compiler" idea too: http://davidad.github.io/blog/2014/02/25/overkilling-the-8-q... ( https://news.ycombinator.com/item?id=7301481 )
And it's a good thing such people exist, because they build all of the foundational technology that you take for granted.
Lots of multithreading, memory allocations, deallocations, etc. If there is one thing C++ developers know better than the language itself, it's debuggers and memory dumps. We used tools like windbg and gdb almost as much as the IDE.
Often, crash dumps from our release binaries didn't have enough inbuilt information to find the cause of the crash. We would then have to do things like walking the stack manually. For doing that effectively, it was necessary to understand x86 internals to figure out what the disassembled code is doing. This is probably the likeliest way most native application developers run into assembly language. Always helps to know it well.
I spent several years disassembling a game (C&C Red Alert 2), understanding the code, fixing the shitty parts and glitches left in it, and extending it. It was fascinating.
¹These examples are certainly far from trivial, I know, but that is half the fun.
brew install nasmThere are some hello-world examples for 32bit and 64bit with nasm on osx here: