Ask HN: What are some examples of beautiful x86 assembly code?
I've been bumping into a lot of x86 assembly lately and, unrelated, recently read about the stellar reputation of SQLite's code base.
This got me thinking: what are some examples of high-quality and/or beautiful x86 assembly? In fact, what about for other processor families as well?
92 comments
[ 2.9 ms ] story [ 168 ms ] threadThere was some discussion of it here a while ago: https://news.ycombinator.com/item?id=942684 (though unfortunately the site is now dead)
...
and yes, I consider it beautiful x86 code :)
https://github.com/kaneton/appendix-bios
OT: This brings back memories of tinkering with the MS-DOS boot process. Back then, the BIOS would read the MBR and copy its contents to 0x7C00 and start execution from there. So you could assemble your own code (using no less than MS-DOS debug) and plonk it into the MBR. I remember doing things like fooling the boot loader into thinking there's less ram than there actually was (639kB instead of 640kB) and using the unaccounted 1kB for placing your own code that could be triggered by a captured interrupt... Fun times!
strlen():
or take strcpy(): versus the C version: It translates to the optimal machine code verbatim. It does not require any smarts of the compiler, at the expense of understanding on the side of the programmer. This is made possible by orthogonality of the instruction set, which in less well thought out designs was hacked around with special instructions (like LDIR on Z80). The price for that is you have to make compiler optimize these situations.Page 133 http://www.atarimania.com/documents/Asm_Lang_Prog_68K_Family...
Although I guess I'd implement strlen more like this (not sure if it works, been a long time since I last wrote anything for 68k): Just two instructions in the inner loop. But not sure whether address registers supported addq etc.[0]: It runs slower for short strings, though. Not sure where the break even point is.
But that gave me a new idea: copy pointer to d0 and complement the difference instead of addq, saving a1 register (+stack operations) and a move-instruction:
Equivalent C-code (http://cpp.sh/2oecd): Again, untested, but I think it probably works. I guess this is also faster for all string lengths >0 as well.This idea might not work on PDP-11 anymore, depending on how binary arithmetic is implemented.
http://www.jagregory.com/abrash-black-book/#thats-nicebut-it...
[1] https://www.amazon.com/dp/1502958082
[2] https://www.xorpd.net/pages/xchg_rax/snip_00.html
It's been a long time since I've done any assembly, and that was MIPS, but I don't see how there's any sort of exit condition or anything. I'm guessing there's more to xadd than `x + y = z`?
It’s sometimes difficult to think about the assembly code in situ when you start to think about the operating system doing a ton of context switching and paging etc. in the background, which can distract your thought process from what’s right in front of you (as well as the operating system’s software interrupts / system calls on top of the basic ISA, which is another abstraction!)
Older systems had the currently running program as the entire context of the system at that point in time - in a similar way to embedded programming, which is imho a much easier realm to learn assembly in once you’ve got a bit of basic electronics under your belt!
Even in those "old" systems of single address spaces and no protection, you're constantly getting timer interrupts, interrupts for I/O, etc., which your application might not have installed its own handler for.
I suspect we’re both making a similar point in a roundabout way - the operating system is both another layer of abstraction on top of the Instruction Set, while also making the programming process for that chipset somewhat easier (providing software interrupts etc. at the expense of bare metal understanding).
My argument is loosely that modern (x64) assembly is not so much targeting hardware as it is programming into a software abstraction (the operating system).
On your second question about different loops. First, you can loop through any register via (dec <reg>; jnz <label>), thus having nested loops. Second, all valuable registers are pushed onto the stack and popped before/after a call or an interrupt, so their contents don’t change. Some registers have to be saved by the caller and some by the callee, depending on a calling convention and an actual usage. The stack is always available (rsp points to its top), so you can offload temporary values and concentrate on current evaluation, loading values on demand later.
[0] https://stackoverflow.com/a/30131285
https://www.udemy.com/x86-asm-foundations/
EDIT: Looks like it is. https://www.xorpd.net/pages/x86_adventures.html
The exercises are open source and can be found here: https://github.com/xorpd/asm_prog_ex I think that more experienced developers can take the instruction set and bash through the exercises.
It’s ineresting that when tools are new, like ASM in 1978, they give high leverage to the first to use them. Microsoft was able to leverage a small amount of code into a world changing platform. Now it would be nearly impossible to do the same with a team the same size.
But in 2018, the nascent state of ML tools looks similar to the nascent state of programming tools in 1978. And indeed we are seeing entire companies built around relatively basic AI in the scheme of things. As first movers these companies have the same kind of leverage with respect to AI that Microsoft did to Software in the 1980s.
Perhaps in 2058 someone will share a link to a Tensorflow script and we will all marvel at its terseness and apparent simplicity.
What code are you looking at? I see "Microsoft BASIC for 6502 Original Source Code" which runs for 6955 lines. By my reasoning that is more than a hundred pages.
https://github.com/leni536/fast_hilbert_curve
I'm pretty proud of it. It calculates the coordinates of the nth point on the hilbert curve. No loops, no branches. It uses the pext, pdep and popcount intrinsics creatively.
disassembly of Pokémon Red/Blue
https://github.com/pret/pokered
TONC GBA Programming Principles
https://www.coranac.com/tonc/text/toc.htm
[1]: https://en.wikipedia.org/wiki/Zilog_Z80#Datapoint_2200_and_I...
https://realboyemulator.wordpress.com/2013/01/02/the-nintend...
https://gist.githubusercontent.com/jwieder/7e7e643cc71c81f63...
Note the '.section .rodata' directive which actually places the quads pointers, seemingly interleaved with code, in a read-only data section.
Note also the dec/test/jle instructions implementing the while loop occur before the last of the eight copy operations, and interleaved with the next-to-last copy operation.
___edit: formatting, sigh
[0] v 7.3.0 64bit; gcc -S -Os -masm=intel
https://en.wikipedia.org/wiki/Duff%27s_device
It only specifies which section that part of "code" goes into, the linker pools it all up in a binary image and fills in the address for the tags when linking as directed by the linker script[0].
[0]: https://sourceware.org/binutils/docs/ld/Simple-Example.html
This guy was a genius (those were the times you didn't do it for profit, it was a game). Google for zmist...
https://gist.github.com/jibsen/8afc36995aadb896b649
Lots of fun stuff there, like a scrolling Matrix screen saver in 8 bytes (!!) that jumps into the middle of an instruction: http://www.sizecoding.org/wiki/M8trix_8b
https://github.com/linker3000/Historic-code-PC-Pascal-and-AS...
https://github.com/xoreaxeaxeax/movfuscator
It also fully avoids all branches.
https://github.com/kirschju/demovfuscator