Skip to content

Comment on x86 assembly doesn’t have to be scary (2018)

Comments

I sometimes wonder whether assembly code would even be considered "scary" today if IBM had picked the Motorola 68000 instead of the Intel 8088 for their PC.

The x86 instruction set was a cobbled-together mess from day one, while 68k assembly coding was pure joy because of its elegant and consistent instruction set.

It's "scary" on almost every architecture because there's no fault tolerance, recovery, exception handling, or reporting. If you make a mistake you get to reassemble the smashed plate of your memory image in order to work out what happened - assuming you can get at it at all. Some platforms will just reboot on you.

Hmm, I don't agree, stepping through assembly code in most (C/C++) debuggers works just as well as with high level languages, and process isolation in operating systems also works for assembly coding, so it's just as unlikely to crash the whole computer with assembly code as with high level languages (otherwise we'd have a massive problem if operating system security would just depend on banning low level programming).

Bare metal embedded coding is a different topic of course, but regular application development with assembly works just fine.

Inline assembly is certainly the least scary case and may be the easiest to get started with. But the original article targets the 16-bit DOS platform as a bootloader, where is "is" the operating system and you have no help.

In our codebase, we used inline asm twice; for 'pause' and 'rdtscp'. Even then we replaced that with intrinsics later.

'Regular applications' that use inline asm should really be raising eyebrows.

Sure, that's the case today, but in the 80's and early 90's it wasn't all that uncommon to write big applications completely in assembly code. With a proper macro assembler and IDE-like coding environment that wasn't as bad as it sounds today. The focus has just shifted away from assembly programming to high-level languages, and the tools moved along. At least reading assembly code is still important today though.

yep

https://en.wikipedia.org/wiki/RollerCoaster_Tycoon_(video_ga...

The game was developed in a small village near Dunblane over the course of two years.[2][5] Sawyer wrote 99% of the code for RollerCoaster Tycoon in x86 assembly language, with the remaining one percent written in C.[3]

Even into the late 90s 2D PC games were being written with large chunks of assembler. 3D APIs killed that. I'm sure there were huge slabs of machine code in console games until the early 2000s, especially systems that needed a lot of specialized code to fully exploit them, e.g. PlayStation 2.

68k assembly coding was pure joy because of its elegant and consistent instruction set

That's maybe a little spun. The separated address/data registers (there are two kinds of registers, and memory operations need to use one from each to combine to a final address) played hell with optimizer strategies for years.

In fact 68k compiler output was significantly sub-par pretty much throughout its lifetime. The "cobbled-together mess" had (post-386 anyway) a significantly more orthogonal instruction set and was just plain easier to optimize, even for humans.

The 68k was certainly great fun after 8bit systems, and the x86 was almost just more of the same. ARM32 is also nice, apart from some corner cases. I think everything has corner cases, apart from x86 where the whole thing is an ugly mess.

No, on the 68000 you can do memory addressing using none, one address register, two address registers, or one of each.

On the 68020 you get even more flexibility.

Yes. But what you cannot do is a load with the sum of any two GPRs. They need to be in the right partitions. That makes register assignment a huge pain for the optimizer, and historically hurt the architecture.

That's the kind of complexity that really hurts software. Compare vs. the commonly-cited x86 nonsense (the REP prefix, say), which complicates silicon implementations but generally makes software easier to write (c.f. decades of optimized inline memcpy implementations).

The point being the 68k was a dead end in a different direction. It was a "clean" archiecture from the perspective of a 1970's assembly programmer, but not a late 80's compiler writer.

The only thing you can't do is the sum of two data registers, and this only applies when calculating an effective address.

When you have 15 registers to work with that's not going to be more of a problem than only having 7 or 8 registers to work with.

I don't want to be too blunt, but have you looked at early 90's era 68k compiler output? It was crap. When Sun launched SPARC, like half the advantage of the platform was that the compiler was suddenly generating this amazingly clean code. The phantom spills and intra-GPR movs everyone was used to disappeared overnight.

As an owner of the original 1984 Mac, I bought a copy of the classic Lance Levanthal 68000 assembly book. I never got much into actually coding in it, but I remember how "this makes sense, mostly" the book felt.

Then I read some of the Intel stuff. OMG.

I agree. 68k was one of the most orthogonal architectures ever.

After the PDP-11 architecture, yes.

well except for the two sorts of registers

Three, starting with 68040.

And likely far more by now, had it become the PC instruction set architecture. The x86 instruction set was also simpler back in the late 70s, in the 8086 era when the Motorola 68000 was first released, than it is now.

Back then it only had the one register width (16-bit, e.g. "ax"), whereas now it has the 32-bit series (e.g. "eax") series and the 64-bit series (e.g. "rax"). It also now has SIMD, SSE/AVX, virtualization support, and other technologies. Back then it just had one operating mode (real mode), whereas now it has protected mode, long mode, system management mode, and a few other intermediate modes (e.g. "unreal mode").

So a lot of the complexity that x86 has now was introduced after that decision was made. It's definitely conceivable that the 68000 line would have developed similarly had it been chosen instead of x86 for the PC.

8086 let you address the upper and lower halves of the 16-bit registers as well, so don't trick yourself into thinking there was just one register width available in the sense that everything could only be treated as 16-bit words.

Many 8 bit processors had the same “feature”. For example, the Z80’s 8 bit registers were internally just halves of a 16 bit register.

totally agree. I learnt a bit of 6502/6809 in my early teens and in to college/ uni. I couldn't not be bothered with the x86 - a complete pita, although to be fair that may have been the not so great manuals I had at the time.

but I started in my first job dissassembling 68000, very easy to work with

Ideally - ideally IBM would have created and used their own microprocessor based on the System 370 architecture. But no - for them the PC was a glorified typewriter: even PS/2 (based on 80286) was mostly positioned for use merely as a "smart" terminal for mainframes. So, today the entire world is basically run on faster "typewriters" (sometimes enhanced to include "windows" - even on the server side).

ARM assembly code looks pretty nice. Mobile phones and Macs are using ARM, so there's lots of devices using it. Only Windows is using x86/x64.

>Only Windows is using x86/x64

Yeah, X64 is only used by windows, that's like ~80% of the desktop computing market, plus nearly every server and cloud instance out there, so not much at all. /s

Why do some users assume that the whole world revolves around Apple's iOS/M1 Mac ecosystem as if it exists in a vacuum?

Jesus, no wonder Microsoft can shove ads and do whatever they want with Windows.

Because well over 90% of shipped computing devices are ARM based.

Desktop computers are a declining and relatively small market. Your home, car and office are full of ARM devices. Potentially hundreds.

x86 lives in the data centre (Linux, not Windows, so contrary to grandparent post, but whatever) and in some desktop systems. Not the majority of systems.

Has nothing to do with an Apple fetish.

Why do some users assume that the whole world revolves around Apple...

Because for those users, it does, and Apple coddles and encourages that mindset.

nearly every server and cloud instance out there

Is this true? I thought Linux dominated the server space.

ARM assembly is easy to learn, because of its relatively small and orthogonal instruction set. However, load store architectures are annoying to program in, since you're constantly having to juggle memory. The lack of a convenient way to spill registers is also really really annoying! (you can only spill and load registers two at a time)

RISCV also has a nice assembly language.

Oh, absolutely:

    LUI  a0, 0x7FFFF
    ADDI a0, a0, 0xFFF
    
is superior to
    MOV eax, 0x7FFFFFFF
Except, of course, that RISC-V snippet doesn't actually load 0x7FFFFFFF into a0 because Reasons, it has to be
    LUI  a0, 0x80000
    ADDI a0, a0, 0xFFF
"But the assembler has the LI pseudoinstruction so it will properly do this calculation for you!". Right, so much for "nice assembly language": you need an actual smart macroassembler to write it.

Talkin' out of my ass here, but is this an artifact of the parameter having to fit in the same instruction word as the opcode ('cause RISC) and because the register is the size of a word (which is partially used by the opcode now), you can't actually load a whole register with an immediate in one go?

Absolutely, and different RISCs coped with it in their own ways. ARM has 12-bit immediates which it treats as having a 8-bit and a 4-bit parts: the 8-bit part is extended into 32 bits and then rotated right twice the number in the 4-bit part. MIPS has 16-bit immediates and the 32-bit load is generally done by LUI then ORI (since MIPS zero-extends the immediates unlike RISC-V which sign-extends those). And RISC-V has 12- (for lower part of the word) and 20-bit (for the upper part) immediates.

The funny part is, there is now the "C" extension to RISC which introduces 16-bit instructions which are allowed to freely mix with 32-bit instructions — so now those 32-bit instructions can be 16-bit aligned and even be split between two physical memory pages which kinda kills the whole "but at least fixed-length encoding prevents Spectre-like exploits" argument.

"But the assembler has the LI pseudoinstruction so it will properly do this calculation for you!". Right, so much for "nice assembly language": you need an actual smart macroassembler to write it.

In x86 the mnemonic "MOV" can be translated into instructions with a different opcode according to the addressing mode, immediate value size, or target register. Most of the x86 instructions have a similar issue, while RISC-V macroassembler are pretty simple. Therefore, the x86 assembler must actually contain much more intelligence than the RISC-V assembler to make it look "simple" and "nice".

Well, sure, it's awkward but also consistent. So once you've learned the rules/pattern for this kind of thing, you can read/write code without having to look up a wack of different instructions and modes and register sets, which x86 is notorious for.

Assembly isn't supposed to be convenient and expressive to write. for that we have high level languages. But some consistency makes for less error prone and easier analysis.

x86 is also consistent, just in a different way. There are basic moves and arithmetics, basic branching, and basic stack-related stuff. Next, there are extensions: string-manipulating extension (STOS/LODS/etc.), multiplication/division extension (MUL/DIV with their idiosyncrastic use of DX:AX), floating-point extension, control-registers extensions (tons of those), vectorized extensions, etc. Inside any set of instructions, things are pretty consistent. It's just that the Intel's Software Developer's Manual is not structured this way, it lumps all of those instructions together.

But RISC-V specification is explicitly structured around describing several basic cores and the extensions to those so it looks like it's all very unified and consistent: and indeed, it mostly is since it was developed mostly in one continuous effort with consistency in mind. Bute there are still some inconsistencies between how things are done in different extensions as well, for example, the "C" extension uses zero-extended immediates in half of its instructions unlike the rest of ISA and the other half of this very extension because of pragmatics: nobody would like to have negative offsets in those shortened instructions, so those are unsigned.

To be clear, that happens simply because ADDI sign-extends its immediate argument. This saves the need for separate immediate opcodes, and is reasonably consistent with the use of sign-extension elsewhere in the ISA.

AboutSource Built by g1lg1l

Hackerly is an independent reader for Hacker News, built on the public HN API. Not affiliated with Y Combinator.