Back

Why xor eax, eax?

576 points2 monthsxania.org
jgrahamc2 months ago

In my 6502 hacking days, the presence of an exclusive OR was a sure-fire indicator you’d either found the encryption part of the code, or some kind of sprite routine.

Yeah, sadly the 6502 didn't allow you to do EOR A; while the Z80 did allow XOR A. If I remember correctly XOR A was AF and LD A, 0 was 3E 01[1]. So saved a whole byte! And I think the XOR was 3 clock cycles fast than the LD. So less space taken up by the instruction and faster.

I have a very distinct memory in my first job (writing x86 assembly) of the CEO walking up behind my desk and pointing out that I'd done MOV AX, 0 when I could have done XOR AX, AX.

[1] 3E 00

wavemode2 months ago

> CEO walking up behind my desk and pointing out that I'd done MOV AX, 0 when I could have done XOR AX, AX

Now that's what I call micromanagement.

(sorry couldn't resist)

xigoi2 months ago

The real joke is that a CEO had actual technical knowledge instead of just being there for decoration.

jgrahamc2 months ago

He was right though. We were memory and cycle constrained and I'd wasted both!

mkornaukhov2 months ago

Similarly, the CEO couldn't resist the outstanding optimization of memory and execution speed!

65102 months ago

[flagged]

+3
jgrahamc2 months ago
eru2 months ago

CEO doesn't need to mean some big boss. If you have a three person startup, the CEO might just be your co-founding buddy.

crest2 months ago

I had to pad the code for alignment reasons. ;-)

ksherlock2 months ago

I mean, he IS the Chief EORfficer

stevefan19992 months ago

> In my 6502 hacking days, the presence of an exclusive OR was a sure-fire indicator you’d either found the encryption part of the code, or some kind of sprite routine.

Correct. Most ciphers of that era should be Feistel cipher in the likes of DES/3DES, or even RC4 uses XOR too. Later AES/Rijndael, CRC and ECC (Elliptic Curve Cryptography) also make heavy use of XOR but in finite field terms which is based on modular arithmetic over GF(2), that effectively reduces to XOR (while in theory should be mod 2).

OhMeadhbh2 months ago

I was going to say "but RC4 and AES were published well after the 6502's heyday," but NESes were completely rocking it in '87 (and I'm told 65XX cores were used as the basis for several hard drive controllers of the era.) Alas, the closest I ever came to encryption on a (less than 32-bit system) was lucifer on an IBM channel controller in the forever-ago and debugging RC5 on an 8085.

kjs32 months ago

I'm told 65XX cores were used as the basis for several hard drive controllers of the era

Western Design Center is still (apparently) making a profit at least in part licensing 6502 core IP for embedded stuff. There's probably a 6502 buried and unrecognized in all sorts of low-cost control applications laying around you.

RC5 on an 8085

Oof. Well played.

+3
PaulHoule2 months ago
ASalazarMX2 months ago

Reading cryptography was that advanced at that time, I'm even more surprised that the venerable Norton Utilities for MS-DOS required a password, that was simply XORed with some constant and embedded in the executables. If the reserved space was zeroes, it considered it a fresh install and demanded a new password.

If it had been properly encrypted my young cracker self would have had no opportunity.

stevefan19992 months ago

Self-correction: It is GF(2^8) and not GF(2), but GF(2^8) primitive operations (such as carryless multiplication) can be reduced into a bunch of table lookups and/or GF(2) operations, which is how to AES crypto accelerators are being done in hardware.

Sesse__2 months ago

Well, running in CTR mode is really common now, and that ends up XORing the generated keystream into the plaintext… (CTR mode is essentially converting block ciphers into stream ciphers, if you want to see it that way.)

vanderZwan2 months ago

Hah, we commented on the exact same paragraph within a minute of each other! My memory agrees with your memory, although I think that should be 3E 00. Let me look that up:

https://jnz.dk/z80/ld_r_n.html

https://jnz.dk/z80/xor_r.html

Yep, if I'm reading this right that's 3E 00, since the second byte is the immediate value.

One difference between XOR and LD is that LD A, 0 does not affect flags, which sometimes mattered.

sfink2 months ago

What is this "LD A, 0" syntax? Is it a z80 thing?

One of the random things burned into my memory for 6502 assembly is that LDA is $A9. I never separated the instruction from the register; it's not like they were general purpose. But that might be because I learned programming from the 2 books that came with my C64, a BASIC manual and a machine code reference manual, and that's how they did it.

I learned assembly programming by reading through the list of supported instructions. That, and typing in games from Compute's Gazette and manually disassembling the DATA instructions to understand how they worked. Oh, and the zero-page reference.

Good times.

jgrahamc2 months ago

What is this "LD A, 0" syntax? Is it a z80 thing?

On the 6502 you had three instructions LDA, LDX, LDY where the register name is essentially part of the instruction name. On the Z80 you had a lot of "load" instruction so you had LD and then many different operands: loading 8-bit registers, loading 16-bit, writing to memory, reading from memory, reading/writing from memory using a register as an index. So, made more sense on Z80 to have "LD" whereas LDA/LDX/LDY worked fine on 6502.

Narishma2 months ago

> One of the random things burned into my memory for 6502 assembly is that LDA is $A9. I never separated the instruction from the register; it's not like they were general purpose.

You had LDA and LDX and LDY as separate instructions while the Z80 assembler had a single LD instruction with different operands. It's the same thing really.

sfink2 months ago

Right, though the LD? and ST? instructions were kind of exceptions. You could only do arithmetic and stack and bitwise ops (and, or, eor, shift, rotate) with A, never X nor Y. Increment and decrement were X/Y only. You couldn't even add two registers together without stashing one in memory.

vanderZwan2 months ago

> What is this "LD A, 0" syntax? Is it a z80 thing?

Well, I never wrote any 6502 so I can't compare, but yes, you could load immediate values into any register except the flag register on the Z80. Was that not a thing on the 6502?

jgrahamc2 months ago

The 6502 instruction set was really limited but there were three registers: A, X, Y and there were immediate load instructions for each: LDA #0, LDX #0, LDY #0.

jgrahamc2 months ago

You're right. Of course, it's 3E 00. Not sure how I remembered 3E 01. My only excuse is that it was 40 years ago!

anonzzzies2 months ago

3E 00 : I was on MSX and never had an assembler when you so I only remember the Hex, never actually knew the instructions; I wrote programs/games by data 3E,00,CD,etc without comments saying LD A as I never knew those at the time.

unnah2 months ago

Umm... how did you manage to learn those hex codes? You just read a lot of machine code and it started to make sense?

jgrahamc2 months ago

I started out writing machine code without an assembler and so had to hand assemble a lot of stuff. After a while you end up just knowing the common codes and can write your program directly. This was also useful because it was possible to write or modify programs directly through an interface sometimes called a "front panel" where you could change individual bytes in memory.

Back in 1985 I did some hand-coding like this because I didn't have access to an assembler: https://blog.jgc.org/2013/04/how-i-coded-in-1985.html and I typed the whole program in through the keypad.

stevekemp2 months ago

Same here. On/For the ZX Spectrum, looking up the hex-codes in the back of the orange book. At least it was spiral-bound to make it easier.

Later still I'd be patching binaries to ensure their serial-checks passed, on Intel.

af782 months ago

I had a similar experience of writing machine code for Z80-based computers (Amstrad CPC) in the 90's, as a teenager. I didn't have an assembler so I manually converted mnemonics to hex. I still remember a few opcodes: CD for CALL, C9 for RET, 01 for LD BC, 21 for LD HL... Needless to say, the process was tedious and error-prone. Calculating relative jumps was a pain. So was keeping track of offsets and addresses of variables and jump targets. I tended to insert nops to avoid having to recalculate everything in case I needed to modify some code... I can't say I miss these times.

I'm quite sure none of my friends knew any CPU opcode; however, people usually remembered a few phone numbers.

kragen2 months ago

The instruction sets were a lot simpler at the time. The 8080 instruction set listing is only a few pages, and some of that is instructions you rarely use like RRC and DAA. The operand fields are always in the same place. My own summary of the instruction set is at https://dercuano.github.io/notes/8080-opcode-map.html#addtoc....

senderista2 months ago

It wasn't unusual in the 80s to type in machine code listings to a PC; I remember doing this as an 8-year-old from magazines, but I didn't understand any of the stuff I was typing in.

anonzzzies2 months ago

Typing from mags, getting interested in how the magic works by learning to use a hex monitor and trying out things. I was a kid so time enough.

I didn't know you could do it differently for years after I started.

amirhirsch2 months ago

I implemented a PDP-11 in 2007-10 and I can still read PDP-11 Octal

favorited2 months ago

"Prefer `xor a` instead of `ld a, 0`" is basically the first optimization that you learn when doing SM83 assembly.

https://github.com/pret/pokecrystal/wiki/Optimizing-assembly...

mmphosis2 months ago

Try to keep the value 0 in the Y register.

  echo tya|asm|mondump -r|6502
                                A=AA X=00 Y=00 S=00 P=22 PC=0300  0
  0300- 98        TYA           A=00 X=00 Y=00 S=00 P=22 PC=0301  2
brucehoult2 months ago

That's 1 byte smaller than `LDA #0`, but not faster. And you don't have enough registers to waste them -- being able to do `STZ` and the `(zp)` addressing mode without having to keep 0 in Z or Y were small but soooo convenient things in the 65C02.

snvzz2 months ago

You might like the PC Engine, a game console based on the 65C02*.

*Actually a custom chip also containing some peripherals.

user39393822 months ago

I’m building a new 6502 machine

daeken2 months ago

Back in 2005 or 2006, I was working at a little startup with "DVD Jon" Johansen and we'd have Quake 3 tournaments to break up the monotony of reverse-engineering and juggling storage infrastructure. His name was always "xor eax,eax" and I always just had to laugh at the idea of getting zeroed out by someone with that name. (Which happened a lot -- I was good, but he was much better!)

VectorLock2 months ago

I was there but never got in on the Quake 3 fun; mp3t**

pansa22 months ago

> Unlike other partial register writes, when writing to an e register like eax, the architecture zeros the top 32 bits for free.

I’m familiar with 32-bit x86 assembly from writing it 10-20 years ago. So I was aware of the benefit of xor in general, but the above quote was new to me.

I don’t have any experience with 64-bit assembly - is there a guide anywhere that teaches 64-bit specifics like the above? Something like “x64 for those who know x86”?

sparkie2 months ago

It's not only xor that does this, but most 32-bit operations zero-extend the result of the 64-bit register. AMD did this for backward compatibility. so existing programs would mostly continue working, unlike Intel's earlier attempt at 64-bits which was an entirely new design.

The reason `xor eax,eax` is preferred to `xor rax,rax` is due to how the instructions are encoded - it saves one byte which in turn reduces instruction cache usage.

When using 64-bit operations, a REX prefix is required on the instruction (byte 0x40..0x4F), which serves two purposes - the MSB of the low nybble (W) being set (ie, REX prefixes 0x48..0x4f) indicates a 64-bit operation, and the low 3 bits of low nybble allow using registers r8-r15 by providing an extra bit for the ModRM register field and the base and index fields in the SIB byte, as only 3-bits (8-registers) are provided by x86.

A recent addition, APX, adds an additional 16 registers (r16-r31), which need 2 additional bits. There's a REX2 prefix for this (0xD5 ...), which is a two byte prefix to the instruction. REX2 replaces the REX prefix when accessing r16-r31, still contains the W bit, but it also includes an `M0` bit, which says which of the two main opcode maps to use, which replaces the 0x0F prefix, so it has no additional cost over the REX prefix when accessing the second opcode map.

cesarb2 months ago

> It's not only xor that does this, but most 32-bit operations zero-extend the result of the 64-bit register. AMD did this for backward compatibility.

It's not just that, zero-extending or sign-extending the result is also better for out-of-order implementations. If parts of the output register are preserved, the instruction needs an extra dependency on the original value.

ychen3062 months ago

This. It's for renaming.

nickelpro2 months ago

Except for `xchg eax, eax`, which was the canonical nop on x86. Because it was supposed to do nothing, having it zero out the top 32-bits of rax would be quite surprising. So it doesn't.

Instead you need to use the multi-byte, general purpose encoding of `xchg` for `xchg eax, eax` to get the expected behavior.

veltas2 months ago

Chapter 3 of volume 1, ctrl+f for "64-bit mode", has a lot of the essentials including e.g. the stuff about zeroing out the top half of the register.

https://www.intel.com/content/www/us/en/developer/articles/t...

huflungdung2 months ago

[dead]

matt_d2 months ago

See https://github.com/MattPD/cpplinks/blob/master/assembly.x86.... - mostly focused on x86-64 (and some of the talks/tutorials offer pretty good overview)

wildlogic2 months ago

I learned this trick writing shellcode - the shellcode has to be null byte (0x00) free, or it will terminate and not progress past the null byte, since it is the string terminator. of course, when you xor something with itself, the result is zero. the byte code generated by the instruction xor eax, eax doesn't contain null bytes, whereas mov eax, 0 does.

anhldbk2 months ago

Yes, it's one of my favorite trick also.

eb0la2 months ago

I remember a lot of code zeroing registrers, dating at least back from the IBM PC XT days (before the 80286).

If you decode the instruction, it makes sense to use XOR:

- mov ax, 0 - needs 4 bytes (66 b8 00 00) - xor ax,ax - needs 3 bytes (66 31 c0)

This extra byte in a machine with less than 1 Megabyte of memory did id matter.

In 386 processors it was also - mov eax,0 - needs 5 bytes (b8 00 00 00 00) - xor eax,eax - needs 2 bytes (31 c0)

Here Intel made the decision to use only 2 bytes. I bet this helps both the instruction decoder and (of course) saves more memory than the old 8086 instruction.

Sharlin2 months ago

As the author says, a couple of extra bytes still matter, perhaps more than 20ish years ago. There are vast amounts of RAM, sure, but it's glacially slow, and there's only a few tens of kBs of L1 instruction cache.

Never mind the fact that, as the author also mentions, the xor idiom takes essentially zero cycles to execute because nothing actually happens besides assigning a new pre-zeroed physical register to the logical register name early on in the pipeline, after which the instruction is retired.

umanwizard2 months ago

> nothing actually happens besides assigning a new pre-zeroed physical register to the logical register name early on in the pipeline, after which the instruction is retired.

This is slightly inaccurate -- instructions retire in order, so it doesn't necessarily retire immediately after it's decoded and the new zeroed register is assigned. It has to sit in the reorder buffer waiting until all the instructions ahead of it are retired as well.

Thus in workloads where reorder buffer size is a bottleneck, it could contribute to that. However I doubt this describes most workloads.

Sharlin2 months ago

Thanks, that makes sense.

cogman102 months ago

L1 instruction cache is backed by L2 and L3 caches.

For the AMD 9950, we are talking about 1280kb of L1 (per core). 16MB of L2 (per core) and 64MB of L3 (shared, 128 if you have the X3D version).

I won't say it doesn't matter, but it doesn't matter as much as it once did. CPU caches have gotten huge while the instructions remain the same size.

The more important part, at this point, is it's idiomatic. That means hardware designers are much more likely to put in specialty logic to make sure it's fast. It's a common enough operation to deserve it's own special cases. You can fit a lot of 8 byte instructions into 1280kb of memory. And as it turns out, it's pretty common for applications to spend a lot of their time in small chunks of instructions. The slow part of a lot of code will be that `for loop` with the 30 AVX instructions doing magic. That's why you'll often see compilers burn `NOP` instructions to align a loop. That's to avoid splitting a cache line.

Sharlin2 months ago

> For the AMD 9950, we are talking about 1280kb of L1 (per core). 16MB of L2 (per core)

Ryzen 9 CPUs have 1280kB of L1 in total. 80kB (48+32) per core, and the 9 series is the first in the entire history of Ryzens to have some other number than 64 (32+32) kilobytes of L1 per core. The 16MB L2 figure is also total. 1MB per core, same as the 7 series. AMD obviously touts the total, not per-core, amounts in their marketing materials because it looks more impressive.

monocasa2 months ago

Yeah, the reason for that is that it's expensive in PPA for the size of an L1 cache to exceed number of ways times page size. The jump to 48kB was also a jump to 12 way set associative.

As an aside, zen 1 did actually have a 64kB (and only 4 way!) L1I cache, but changed to the page size times way count restriction with zen 2, reducing the L1 size by half.

You can also see this on the apple side, where their giant 192kB caches L1I are 12 ways with a 16kB page size.

kbolino2 months ago

Also, rather importantly, the L1i (instruction) cache is still only 32 kB. The part that got bigger, the 48 kB of L1d (data) cache, does not count for this purpose.

gpderetta2 months ago

Instruction caches also prefetch very well, as long as branch prediction is good. Of course on a misprediction you might also suffer a cache miss in addition to the normal penalty.

vardump2 months ago

> - mov ax, 0 - needs 4 bytes (66 b8 00 00) - xor ax,ax - needs 3 bytes (66 31 c0)

You don't need operand size prefix 0x66 when running 16 bit code in Real Mode. So "mov ax, 0" is 3 bytes and "xor ax, ax" is just 2 bytes.

eb0la2 months ago

My fault: I just compiled the instruction with an assembler instead of looking up the actual instruction from documentation.

It makes much more sense: resetting ax, and bc (xor ax,ax ; xor bx,bx) will be 4 octets, DWORD aligned, and a bit faster to fetch by the x86 than the 3-octet version I wrote before.

Someone2 months ago

> If you decode the instruction, it makes sense to use XOR:

> - mov ax, 0 - needs 4 bytes (66 b8 00 00) - xor ax,ax - needs 3 bytes (66 31 c0)

Except, apparently, on the pentium Pro, according to this comment: https://randomascii.wordpress.com/2012/12/29/the-surprising-..., which says:

“But there was at least one out-of-order design that did not recognize xor reg, reg as a special case: the Pentium Pro. The Intel Optimization manuals for the Pentium Pro recommended “mov” to zero a register.”

qingcharles2 months ago

That's weird, I looked it up earlier and found the P6 (Pentium Pro) was the first to actually make the xor optimization into a zero clock operation.

https://fanael.github.io/archives/topic-microarchitecture-ar...

Someone2 months ago

A few paragraphs down from that:

“I assume that the ability to recognize that the exclusive-or zeroing idiom doesn't really depend on the previous value of a register, so that it can be dispatched immediately without waiting for the old value — thus breaking the dependency chain — met the same fate; the Pentium Pro shipped without it.

Some of the cut features were introduced in later models: segment register renaming, for example, was added back in the Pentium II. Maybe dependency-breaking zeroing XOR was added in later P6 models too? After all, it seems such a simple yet important thing, and indeed, I remember seeing people claim that's the case in some old forum posts and mailing list messages. On the other hand, some sources, such as Agner Fog's optimization manuals say that not only it was never present in any of the P6 processors, it was also missing in Pentium M.”

Anarch157a2 months ago

I don't know enough of the 8086 so I don't know if this works the same, but on the Z80 (which means it was probably true for the 8080 too), XOR A would also clear pretty much all bits on the flag register, meaning the flags would be in a known state before doing something that could affect them.

vanderZwan2 months ago

Which I guess is the same reason why modern Intel CPU pipelines can rely on it for pipelining.

RHSeeger2 months ago

> the IBM PC XT days (before the 80286)

Fun fact - the IBM PC XT also came in a 286 model (the XT 286).

eb0la2 months ago

You're right. I forgot that!

RHSeeger2 months ago

To be fair, I only remember because that was the 2nd computer I owned.

chasd002 months ago

> - mov ax, 0 - needs 4 bytes (66 b8 00 00) - xor ax,ax - needs 3 bytes (66 31 c0)

iirc doesn't word alignment matter? I have no idea if this is how the IBM PC XT was aligned but if you had 4 byte words then it doesn't matter if you save a byte with xor because you wouldn't be able to use it for anything else anyway. again, iirc.

Narishma2 months ago

No, the 8088 used in the PC has a 2 byte word size. More importantly, it only has an 8-bit data bus, so alignment didn't really matter because it fetched instructions one byte at a time.

fooker2 months ago

It's funny how machine code is a high level language nowadays, for this example the CPU recognizes the zeroing pattern and does something quite a bit different.

dheatov2 months ago

It's really impressive how powerful and efficient it has become. However, I find it so much more difficult to build mental model of it. I've been struggling with atomic and r/w barrier as there are sooo many ways the instructions could've been executed (or not executed!).

fooker2 months ago

It's a consequence of keeping our general purpose single threaded programming model the same for five decades.

It has it's merits, but the underlying hardware has changed.

Intel tried to push this responsibility to the compiler with Itanium but that failed catastrophicically, so we're back to the CPU pretending it's 1985.

Reubensson2 months ago

What do you mean that cpu does something different? Isnt cpu doing what is being asked, that being xor with consequence of zeroing when given two same values.

IsTom2 months ago

I think OP means that it has come a long way from the simple mental model of µops being a direct execution of operations and with all the register renamings and so on

dooglius2 months ago

FTA:

> And, having done that it removes the operation from the execution queue - that is the xor takes zero execution cycles!1 It’s essentially optimised out by the CPU

12_throw_away2 months ago

> with consequence of zeroing when given two same values

Right, it has the same consequence, but it doesn't actually perform the stated operation. ASM is just a now just a high level language that tells the computer to "please give me the same state that a PDP-11-like computer would give me upon executing these instructions."

horsawlarway2 months ago

No.

It's emulating the zero result when it recognizes this pattern, usually by playing clever tricks with virtual registers.

fooker2 months ago

Same consequence yes.

But it will not execute xor, nor will it actually zero out eax in most cases.

It'll do something similar to constant propagation with the information that whenever xor eax, eax occurs; all uses of eax go through a simpler execution path until eax is overwritten.

pclmulqdq2 months ago

In modern CPUs, a lot of these are recognized as zeroing idioms and they end up doing the same thing (often a register renaming trick). Using the shortest one makes sense. If you use a really weird zeroing pattern, you can also see it as a backend uop while many of these zeroing idioms are elided by the frontend on some cores.

timonoko2 months ago

I had Atari Portfolio, sold without software, except basic command.com. I could only copy raw ASCII bytes from keyboard to file. When it is in COM-file, it is directly executable. What is the simple program I made from ASCII-bytes to read bytes in hexadecimal. I thought that for a week, but finally solved it.

@grok: This is a classic and brilliant puzzle from the early days of DOS. The challenge is to write a self-displaying hex dumper (.COM file) using only the copy con command, which means every byte of the program must correspond to a standard, typeable ASCII character. This is a form of code-golfing that relies on a deep understanding of 8086 machine code.

The solution is a clever piece of self-modifying code. Since many essential 8086 opcodes (like MOV, INT, RET) don't have printable ASCII equivalents, the program must construct these instructions in memory and then execute them.

Here is a well-known 128-byte solution. I will first show you the ASCII characters you would type, and then explain how it works.

The Program to Type

You would have started by typing copy con hex.com and then carefully typed the following 128 characters (including spaces and case-sensitive characters) and finally pressed Ctrl+Z to save the file.

   1 j!>1!QhH-@0X,jHj.Ph1PhD0Ph 0@h, j, h.0@h, h 0@h.H-@,jH1X,h.H.@,H1j j X,h.H.@,H1j j X.H-@H-@,jHj.Ph1PhE0Ph@0@h, j, h.0@h, h
     0@h.
How It Works: A High-Level Explanation

This program is a marvel of 8086 trickery. Here is a simplified breakdown of what's happening:

etc.etc

timonoko2 months ago

My program was definitively shorter. I think I did not bother with real hexadecimals. Just used last four bytes of characters to make a full byte. Used it as a bootstrap program.

@grok: While your exact code is lost to time, it would have looked something like one of the ultra-small ASCII "dropper" programs that were once passed around. Here is a plausible 32-byte example of what the program you typed might have looked like.

  You would have run copy con nibbler.com, typed the following line, and hit Ctrl+Z:

  `j%1!PZYfX0f1Xf1f1AYf1E_j%1!PZ`

  This looks like nonsense, but to the 8088/8086 processor, it's a dense set of instructions that does the following:
etc etc.
timonoko2 months ago

97% of these millenials of HN do not understand the problem and its brilliant solution. That is why I was truly astonished @grok grokked it rightaway.

BTW. It is not beyond possibility that this nibbler or dropper was made by myself and published in Usenet by me myself in 1989. Who else would have such a problem.

It was a bankcrupt sale and the machine was sold as "inactivated".

rep_lodsb2 months ago

97% of AI enthusiasts will be astonished by complete garbage, and just blindly copy-paste it into forums, assuming that others will share their astonishment (and somehow consider them intelligent / interesting for it, despite expending almost zero effort themselves?).

Let's take a look at just the first few instructions here:

    ASCII HEX      ASM
    j%    6A 25    PUSH 25h        ; 80186+ only, won't work on Atari Portfolio!
    1!    31 21    XOR [BX+DI],SP  ; complete nonsense
    P     50       PUSH AX         ; AX sort-of-undefined at this point, depends on DOS version and command line arguments
    Z     5A       POP DX          ; copy AX into DX
    Y     59       POP CX          ; CX = 25h?
    fX    66 58    POP EAX         ; 80386+ only! also nothing on stack anymore!
    0f1   30 66 31 XOR [BP+31h],AH ; complete nonsense
    ...
This looks like randomly jumbled together from fragments of ASCII-only machine code intended for much newer x86 processors -- just look at how often `f` appears in those strings, which is the operand-size prefix, and meaningless on 16-bit chips. The memory addressing might make more sense in 32-bit mode too, where a different encoding is used (modr/m and SIB byte).

And this is how LLMs work. They can associate the tokens for e.g. `PZ` with the instructions `PUSH AX / POP DX`, and a certain kind of program in which those would be likely to appear, drawn somewhere from their massive training set.

Humans can easily learn to recognize these 'words' of ASCII text in machine code too, just by spending time looking at it. Another good one is the pair of `<ar` and `<zw` (usually next to each other, with an unprintable character between), present in most upcase()-like code from that era. So if you had asked Grok to accept both upper and lower case of hex input, I would bet that it would have inserted those sequences somewhere too.

But what LLMs CAN NOT do is plan ahead on how to use these program fragments to accomplish a specific task, or keep simple constraints in mind like "this must run on the 80C88 processor in the Atari Portfolio". They even have trouble with keeping track of registers, stack, or which mode the CPU is in.

(yes, late, but I can't let this stand here uncommented. https://xkcd.com/386 )

+1
timonoko2 months ago
deadcore2 months ago

Matt Godbolt also uploads to his self titled Youtube channel: https://www.youtube.com/watch?v=eLjZ48gqbyg

brucehoult2 months ago

He also runs a site with a bunch of different compilers and versions :p

mattgodbolt2 months ago

That's just some weird side hobby of his.

brucehoult2 months ago

Dude. You've become a verb.

vanderZwan2 months ago

Not sure why you got downvoted for pointing that out - it might be linked at the end of the article but people can still miss that.

deadcore2 months ago

*shrugs* the internet being the internet I suppose.

There was "See the video that accompanies this post." but NGL was just posting encase anyone didn’t have time to read or missed it.

omnicognate2 months ago

It happens to be the first instruction of the first snippet in the wonderful xchg rax,rax.

https://www.xorpd.net/pages/xchg_rax/snip_00.html

dooglius2 months ago

Not sure what I am looking at here is this just a bunch of different ways to zero registers?

omnicognate2 months ago

It's a collection of interesting assembly snippets ("gems and riddles" in the author's words) presented without commentary. People have posted annotated "solutions" online, but figuring out what the snippets do and why they are interesting is the fun of it.

It's also available as an inscrutable printed book on Amazon.

_jzlw2 months ago

That music when you click "int" is awesome. Reminds me of the good ol' days of keygens.

therein2 months ago

Keygen music will always have a special place in my heart. This is a good one.

I do wonder who was the first cracker that thought of including a keygen music that started the tradition.

I also miss how different groups competed with each other and boasted about theirs while dissing others in readmes.

Readme's would have .NFO suffix and that would try to load in some Windows tool but you had to open them in notepad. Good times.

Audiophilip2 months ago

It's a chiptune-style xm module, "Funky Stars" by Quazar: https://soundcloud.com/scene_music/funky-stars

fortran772 months ago

Back when I did IBM 370 BAL Assembly Language, we did the same thing to clear a register to zero.

  XR   15,15         XOR REGISTER 15 WITH REGISTER 15
vs

  L    15,=F'0'      LOAD REGISTER 15 WITH 0
This was alleged to be faster on the 370 because because XR operated entirely within the CPU registers, and L (Load) fetched data from memory (i.e.., the constant came from program memory).
charles_f2 months ago

> By using a slightly more obscure instruction, we save three bytes every time we need to set a register to zero

Meanwhile, most "apps" we get nowadays contain half of npmjs neatly bundled in electron. I miss the days when default was native and devs had constraints to how big their output could be.

Filligree2 months ago

JS is just easier and takes less code.

Which isn’t an excuse anymore. UI coding isn’t that hard; if someone can’t do it, well, Claude certainly can.

charles_f2 months ago

I'm fine with that, but keeping some consideration to optimization should still be something, even in environments when constraints are low. The problem is when no-one cares and includes 4 versions of jquery in their app so that they don't have to do const $=document.getElementById, everything grows to weigh 1Gb, use 1Gb of ram and 10% of your CPU, and your system is as sluggish nowadays (or even more) than it was 10y ago, with 10x the ram and processing power.

anticrymactic2 months ago

> so that they don't have to do const $=document.getElementById,

``` const window.$ = (q)=>document.querySelector(q); ``` Emulates the behavior much better. This is already set on modern version of browsers[1]

[1] https://firefox-source-docs.mozilla.org/devtools-user/web_co...

dapperhn2 months ago

It is set, but only in the developer console, not for JavaScript included with the website/app.

int_19h2 months ago

It's not even true that HTML/JS is easier than something like, say, WPF.

saagarjha2 months ago

Claude is pretty bad at coding UIs.

vanderZwan2 months ago

> In my 6502 hacking days, the presence of an exclusive OR was a sure-fire indicator you’d either found the encryption part of the code, or some kind of sprite routine.

Meanwhile, people like me who got started with a Z80 instead immediately knew why, since XOR A is the smallest and fastest way to clear the accumulator and flag register. Funny how that also shows how specific this is to a particular CPU lineage or its offshoots.

ethin2 months ago

> In this case, even though rax is needed to hold the full 64-bit long result, by writing to eax, we get a nice effect: Unlike other partial register writes, when writing to an e register like eax, the architecture zeros the top 32 bits for free. So xor eax, eax sets all 64 bits to zero.

I had no idea this happened. Talk about a fascinating bit of X86 trivia! Do other architectures do this too? I'd imagine so, but you never know.

monocasa2 months ago

A lot of the RISC architectures do something similar (sign extend rather than zero extend) when using 32 ops on a 64 bit processor. MIPS and PowerPC come to mind off of the top of my head. Being careful about that in the spec basically lets them treat 32-bit mode on a 64-bit processor as just 'mask off the top bits on any memory access'. Some of these processors will even let you use 64bit ops in 32bit mode, and really only just truncate memory addresses.

So the real question is why does x86 zero extend rather than sign extend in these cases, and the answer is probably that by zero extending, with an implementation that treats a 64bit architectural register as a pair 32bit renamed physical registers, you can statically set the architectural upper register back on the free pool by marking it as zero rather than the sign extended result of an op.

2019842 months ago

AArch64 also zeroes the upper 32 bits of the destination register when you use a 32 bit instruction.

flykespice2 months ago

I'm curious, why is that?

I know x86-64 zeroes the upper part of the register for backwards compability and improve instruction cache (no need for REX prefix), but AArch64 is unclear for me.

2019842 months ago

It's to break dependencies for register renaming. If you have an instruction like

  mov w5, w6 // move low 32 bits of register 6 into low 32 bits of register 5
This instruction only depends on the value of register 6. If instead it of zeroing the upper half it left it unchanged, then it would depend on w6 and also the previous value of register 5. That would constrain the renamer and consequently out-of-order execution.
umanwizard2 months ago

I don't know either, but why wouldn't backwards compatibility apply to aarch64? It too is based on a pre-existing 32-bit architecture.

Narishma2 months ago

I don't think it's backwards compatible the same way x86-64 is.

zeuxcg2 months ago

You really want to avoid a dependency on prior content of the destination register, to allow renaming and maximize out of order scheduling.

flustercan2 months ago

As a longtime developer currently perusing their first computer science degree, it makes me happy that I understood this article. Nearly makes all the trouble seem worth it.

grimgrin2 months ago

I'd like to learn about the earliest pronunciations of these instructions. Only because watching a video earlier, I heard "MOV" pronounced "MAUV" not "MOVE"

Not sure exactly how I could dig up pronunciations, except finding the oldest recordings

pwg2 months ago

> Only because watching a video earlier, I heard "MOV" pronounced "MAUV" not "MOVE"

Was it someone from an electronics background? Because MOV is also the acronym for Metal Oxide Varistor [1] from electronics and in the electronics world the acronym it is often pronounced "MAUV".

[1] https://en.wikipedia.org/wiki/Varistor

jmmv2 months ago

> It gets better though! Since this is a very common operation, x86 CPUs spot this “zeroing idiom” early in the pipeline and can specifically optimise around it: the out-of-order tracking systems knows that the value of “eax” (or whichever register is being zeroed) does not depend on the previous value of eax, so it can allocate a fresh, dependency-free zero register renamer slot.

While this is probably true ("probably" because I haven't checked it myself, but it makes sense), the CPU could do the exact same thing for "mov eax, 0", couldn't it? (Does it?)

adrian_b2 months ago

Most Intel/AMD CPUs do the same thing for a few alternative instructions, e.g. "sub rax, rax".

I do not think that anyone bothers to do this for a "mov eax, 0", because neither assembly programmers nor compilers use such an instruction. Either "xor reg,reg" or "sub reg,reg" have been the recommended instructions for clearing registers since 1978, i.e. since the launch of Intel 8086, because Intel 8086 lacked a "clear" instruction, like that of the competing CPUs from DEC or Motorola.

One should remember that what is improperly named "exclusive or" in computer jargon is actually simultaneously addition modulo 2 and subtraction modulo 2 (because these 2 operations are identical; the different methods of carry and borrow generation distinguish addition from subtraction only for moduli greater than 2).

The subtraction of a thing from itself is null, which is why clearing a register is done by subtracting it from itself, either with word subtraction or with bitwise modulo-2 subtraction, a.k.a. XOR.

(The true "exclusive or" operation is a logical operation distinct from the addition/subtraction modulo 2. These 2 distinct operations are equivalent only for 2 operands. For 3 or more operands they are different, but programmers still use incorrectly the term XOR when they mean the addition modulo 2 of 3 or more operands. The true "exclusive" or is the function that is true only when exactly one of its operands is true, unlike "inclusive" or, which is true when at least one of its operands is true. To these 2 logical "or" functions correspond the 2 logical quantifiers "There exists a unique ..." and "There exists a ...".)

lucozade2 months ago

> couldn't it? (Does it?)

It could of course. It can do pretty much any pattern matching it likes. But I doubt very much it would because that pattern is way less common.

As the article points out, the XOR saves 3 bytes of instructions for a really, really common pattern (to zero a register, particularly the return register).

So there's very good reason to perform the XOR preferentially and hence good reason to optimise that very common idiom.

Other approaches eg add a new "zero <reg>" instruction are basically worse as they're not backward compatible and don't really improve anything other than making the assembly a tiny bit more human readable.

electroly2 months ago

Sure, lots of longer instructions have this effect. "xor eax,eax" is interesting because it's short. That zero immediate in "mov eax,0" is bigger than the entire "xor eax,eax" instruction.

MobiusHorizons2 months ago

I believe it does in some newer CPUs. It takes extra silicon to recognize the pattern though, and compilers emit the xor because the instruction is smaller, so I doubt there is much speed up in real workloads.

pwg2 months ago

> the CPU could do the exact same thing for "mov eax, 0", couldn't it?

Yes, it could, but mov eax, 0 is still going to also be six bytes of instruction in cache, and fetched, and decoded, so optimizing on the shorter version is marginally better.

addaon2 months ago

Yes, "mov r, imm" also breaks dependencies -- but the immediate needs to be encoded, so the instruction is longer.

jakewil2 months ago

I'm building a gameboy emulator and when I was debugging the boot ROM I noticed there was the instruction `xor A` (which xor's a with itself). I was wondering why they chose such a weird way to set A to 0. Now it makes sense -- since the boot ROM is only 256 bytes, they really needed to conserve space! Thanks for this, looking forward to the rest of the series!

jabedude2 months ago

similarly IIRC, on (some generations of) x86 chips, NOP is sugar around `XCHG EAX, EAX` which is effectively a do-nothing operation

bitwize2 months ago

This is pretty much all x86 chips as far as I'm aware: opcode 0x90 which is equivalent to XCHG AX,AX.

The 8080 and Z80's NOP was at opcode 0. Which was neat because you could make a "NOP slide" simply by zeroing out memory.

kccqzy2 months ago

There are multiple variants of nop mainly because you sometimes need the nop instruction to take up a certain number of bytes for alignment purposes. You have the 1-byte nop, but there is also the 9-byte nop.

ternaryoperator2 months ago

The origin AFAIK stems from the mainframe days. When using BAL (the assembly language for the IBM/360 family and its descendants), xoring was faster than moving 0 to the variable. Many of the early devs who wrote assembly for PCs came from mainframe backgrounds and so the idiom was carried over.

kwertyoowiyop2 months ago

In this thread, we have found all the programmers born before 1975!

vanderZwan2 months ago

Hey, some of us are younger and happened to get into programming via making games on their TI-83 graphing calculator in Z80!

3oil32 months ago

What a great article! When the author mentionned "showing-off", that's what I thought at first, I mean, most of us have the "why not spend 2 hours trying to figure it out when you can read the manual for 2 minute" kind of mind-set, which is similar to the "why not make it really complex if we can make it simple". But no, it's actually a really smart idea!!

struc_so2 months ago

It’s not just about code size or cycle count anymore; modern OoO (Out-of-Order) processors treat this idiomatically. The renamer recognizes xor reg, reg as a dependency-breaking zeroing idiom immediately, which frees up the physical register allocation faster than a mov. It's fascinating how hardware optimization has effectively leaked into the instruction set definition over time.

Suzuran2 months ago

In some older IBM-built processors (channel controllers, the various iterations of the CSP), an xor of something against itself also had the effect of safely clearing a stored bad parity without triggering a parity check from reading the operand. You would see strategic clearing in this manner done by system software or firmware during error recovery or early initialization.

Dwedit2 months ago

Because "sub eax,eax" looks stupid. (and also clears the carry flag, unlike "xor eax, eax")

tom_2 months ago

xor clears the carry as well? In fact, looks like xor and sub affect the same set of flags!

xor:

> The OF and CF flags are cleared; the SF, ZF, and PF flags are set according to the result. The state of the AF flag is undefined.

sub:

> The OF, SF, ZF, AF, PF, and CF flags are set according to the result.

(I don't have an x64 system handy, but hopefully the reference manual can be trusted. I dimly remembered this, or something like it, tripping me up after coming from programming for the 6502.)

trollbridge2 months ago

This is a good thing since the pipeline now doesn’t have to track the state of the flags since they all got zero’d.

sfink2 months ago

Strangely, the only difference on the flags is that AF (auxiliary carry) is undefined for `xor eax, eax` but guaranteed to be zeroed for `sub eax, eax`. I don't know what that means in practice, though I'm guessing that at the very least the hardware would not treat it as a dependency on the previous value.

HackerThemAll2 months ago

If I remember correctly, sub used to be slower than xor on some ancient architectures.

Quitschquat2 months ago

At some point I could disassemble 8086 (16 bit x86/real mode) as a kid. Byte sequences like 31 C9 or 31 C0 were a sure way to know if a loop of some kind was being initialized. Even simple compilers at the time made the mov xx, 0 → xor xx, xx optimization.

kstrauser2 months ago

Why wasn't that a standard assembler macro, like ZEROAX or something? It seems to come up enough that it seems like there'd be a common shortcut for it.

(Not suggesting it should be. Maybe that's a terrible idea, but I don't know why.)

sfink2 months ago

I don't know, but one reason might be that with 8-bit opcodes you only have 256 instructions to play with, and many of those encode registers, so ZEROAX is burning a meaningful percentage of your total opcode space. And if you're not encoding it into a single byte, then it's pure waste: you already need XOR (and SUB), so you'd just be adding a redundant way of achieving the same thing. (Note that this argument doesn't completely hold up, since eg the 6502 had a fair number of undocumented opcodes largely because they didn't need all of them.)

Though technically you said "assembler macro", not opcode. For that, I suspect the argument is more psychological: we had such limited resources of all sorts back then that being parsimonious with everything was a required mindset. The mindset didn't just mean you made everything as short as possible, it also meant you reused everything you possibly could. So reusing XOR just felt more fitting and natural than carving out a separate assembler instruction name. (Also, there would be the question of what effect ZEROAX should have on the flags, which can be somewhat inferred when reusing an existing instruction.)

kstrauser2 months ago

I meant something defined in the assembler along the lines of

  .macro ZEROAX
    xor eax, eax
  .endm
where it was defined with a semantically meaningful name, but emitting the exact same opcodes as when writing it out. I mean, I guess taking that to the logical extreme, you'd end up with... C. I dunno, it just seemed like the sort of thing that would have caught on my convention.

I use to write lots of 6502 and 68k assembler, and 68k especially tended to look quite human-readable by the time devs ended up writing macros for everything. Perhaps that wasn't the same culture around x86 asm, which I admit I've done far, far less of.

sfink2 months ago

Right, that's what I was thinking in my 2nd paragraph. No real reason not to, I suspect it just conflicted with the mindset of cleverness that you had to have for other reasons. Macros for >1 instruction would be fine, macros for 1 instruction would be looked down on because you haven't joined the club by twisting your brain into knots.

> I use to write lots of 6502 and 68k assembler, and 68k especially tended to look quite human-readable by the time devs ended up writing macros for everything.

Yes. I only did nontrivial amounts of 6502 and x86, but from what I saw of 68k, it seemed like it started out cleaner-looking and more readable even before adding in macros. (Or for all I know, I was reading code using built-in macros.)

HackerThemAll2 months ago

> Interestingly, when zeroing the “extended” numbered registers (like r8), GCC still uses the d (double width, ie 32-bit) variant.

Of course. I might have some data stored in the higher dword of that register.

Tuna-Fish2 months ago

Clearing e8 also clears the upper half.

Partial register updates are kryptonite to OoO engines. For people used to low-level programming weak machines, it seems natural to just update part of a register, but the way every modern OoO CPU works that is literally not a possible operation. Registers are written to exactly once, and this operation also frees every subsequent instruction waiting for that register to be executed. Dirty registers don't get written to again, they are garbage collected and reset for next renaming.

The only way to implement partial register updates is to add 3-operand instructions, and have the old register state to be the third input. This is also more expensive than it sounds like, and on many modern CPUs you can execute only one 3-operand integer instruction per clock, vs 4+ 2-operand ones.

rfl8902 months ago

Which will still be zeroed.

bitwize2 months ago

Because mov eax, 0 requires fetching a constant and prolongs instruction fetching/execution. XOR A was a trick I learned back in the Z80 days.

OgsyedIE2 months ago

The page crashes after 3 seconds, 100% of the time, on the latest version of Android Chrome and works fine on Brave, fyi.

robmccoll2 months ago

This is not my experience on the latest version of Chrome Android (142.0.7444.171). It did not crash for me.

flohofwoe2 months ago

The actually surprising part to me is that such an important instruction uses a two byte encoding instead of one byte :)

kccqzy2 months ago

Even supporting just 8 registers that would take up 8/256=0.03125 of the instruction encoding space.

GuB-422 months ago

They could have made a version just for (E)AX. "general purpose" registers in x86 are not the same. AX is the accumulator, for arithmetic, BX is for indexing, CX is the loop counter and DX is for data and extending AX in divisions. You don't have to use them for that purpose, but you will have access more optimized instructions if you do. Out of these 4, AX is the most likely you would want to set to zero.

For loops, it is generally expected that you count down, with CX. The "LOOP" instruction is designed for this, so no special need to zero CX. SI and DI, the index registers may benefit from an optimized zeroing, for use with the "string" instructions.

Here I think Intel engineers didn't see the need and not having a special instruction to zero AX must simplify the decoder.

JuniperMesos2 months ago

> In this case, even though rax is needed to hold the full 64-bit long result, by writing to eax, we get a nice effect: Unlike other partial register writes, when writing to an e register like eax, the architecture zeros the top 32 bits for free. So xor eax, eax sets all 64 bits to zero.

Huh, news to me. Although the amount of x86-64 assembly programming I've personally done is extremely minimal. Frankly, this is exactly the sort of architecture-specific detail I'm happy to let an ASM-generating library know for me rather than know myself.

silverfrost2 months ago

Back on the Z80 'xor a' is the shortest sequence to zero A

BiraIgnacio2 months ago

Also cool this got at the top item on the HN front page

dintech2 months ago

My brain read this is "Why not ear wax?"

kragen2 months ago

    xor wax, wax    ; clear wax
    xor sax, sax    ; clear sax
    xor fax, fax    ; tru tru
sixthDot2 months ago

I've wrote a lot of `xor al,al` in my youth.

snvzz2 months ago

Because, unlike RISC-V, x86 has no x0 register.

crote2 months ago

And the other way around: RISC-V doesn't have a move instruction so that's done as "dst = src + 0", and it doesn't have a nop instruction so that's done as "x0 = x0 + 0". There's like a dozen of them.

It's quite interesting what neat tricks roll out once you've got a guaranteed zero register - it greatly reduces the number of distinct instructions you need for what is basically the same operation.

kruador2 months ago

ARM64 assembly has a MOV instruction, but for most of the ways it's used, it's an alias in the assembler to something else. For example, MOV between two registers actually generates ORR rd, rZR, rm, i.e. rd := (zero-register) OR rm. Or, a MOV with a small immediate is ORR rd, rZR, #imm.

If trying to set the stack pointer, or copy the stack pointer, instead the underlying instruction is ADD SP, Xn, #0 i.e. SP = Xn + 0. This is because the stack pointer and zero register are both encoded as register 31 (11111). Some instructions allow you to use the zero register, others the stack pointer. Presumably ORR uses the zero register and ADD the stack pointer.

NOP maps to HINT #0. There are 128 HINT values available; anything not implemented on this processor executes as a NOP.

There are other operations that are aliased like CMP Xm, Xn is really an alias for SUBS XZR, Xm, Xn: subtract Xn from Xm, store the result in the zero register [i.e. discard it], and set the flags. RISC-V doesn't have flags, of course. ARM Ltd clearly considered them still useful.

There are other oddities, things like 'rotate right' is encoded as 'extract register from pair of registers', but it specifies the same source register twice.

Disassemblers do their best to hide this from you. ARM list a 'preferred decoding' for any instruction that has aliases, to map back to a more meaningful alias wherever possible.

Findecanor2 months ago

There is a `c.mv` instruction in the compressed set, which most RISC-V processors implement.

That, and `add rd, rs, x0` could (like the zeroing idiom on x86), run entirely in the decoding and register-renaming stages of a processor.

RISC-V does actually have quite a few idioms. Some idioms are multi-instruction sequences ("macro ops") that could get folded into single micro-ops ("macro-op fusion"/"instruction fusion"): for example `lui` followed by `addi` for loading a 32-bit constant, and left shift followed by right shift for extracting a bitfield.

pwg2 months ago

The DEC Alpha chip was the same. It also had a hardwired zero register (although IIRC the zero register was r31 instead of r0) and about half the addressing modes and a whole bunch of "assembly instructions" were created by interesting uses of that zero register.

dist1ll2 months ago

Another one is "jalr x0, imm(x0)", which turns an indirect branch into a direct jump to address "imm" in a single instruction w/o clobbering a register. Pretty neat.

Findecanor2 months ago

x86 has no architectural zero register, but a x86 CPU could have a microarchitectural zero register.

And when the instruction decoder in such a CPU with register renaming sees `xor eax, eax`, it just makes `eax` point to the zero register for instructions after it. It does not have to put any instruction into the pipeline, and it takes effectively 0 cycles. That is what makes the "zeroing idiom" so powerful.

jabl2 months ago

From your past posting history, I presume that you're implying this makes RISC-V better?

Do we have any data showing that having a dedicated zero register is better than a short and canonical instruction for zeroing an arbitrary register?

phire2 months ago

The zero register helps RISC-V (and MIPS before it) really cut down on the number of instructions, and hardware complexity.

You don't need a mov instruction, you just OR with $zero. You don't need a load immediate instruction you just ADDI/ORI with $zero. You don't need a Neg instruction, you just SUB with $zero. All your Compare-And-Branch instructions get a compare with $zero variant for free.

I refuse to say this "zero register" approach is better, it is part of a wide design with many interacting features. But once you have 31 registers, it's quite cheap to allocate one register to be zero, and may actually save encoding space elsewhere. (And encoding space is always an issue with fixed width instructions).

AArch64 takes the concept further, they have a register that is sometimes acts as the zero register (when used in ALU instructions) and other times is the stack pointer (when used in memory instructions and a few special stack instructions).

phkahler2 months ago

>> The zero register helps RISC-V (and MIPS before it) really cut down on the number of instructions, and hardware complexity.

Which if funny because IMHO RISC-V instruction encoding is garbage. It was all optimized around the idea of fixed length 32-bit instructions. This leads to weird sized immediates (12 bits?) and 2 instructions to load a 32 bit constant. No support for 64 bit immediates. Then they decided to have "compressed" instructions that are 16 bits, so it's somewhat variable length anyway.

IMHO once all the vector, AI and graphics instructions are nailed down they should make RISC-VI where it's almost the same but re-encoding the instructions. Have sensible 16-bit ones, 32-bit, and use immediate constants after the opcodes. It seems like there is a lot they could do to clean it up - obviously not as much as x86 ;-)

kruador2 months ago

ARM64 also has fixed length 32-bit instructions. Yes, immediates are normally small and it's not particularly orthogonal as to how many bits are available.

The largest MOV available is 16 bits, but those 16 bits can be shifted by 0, 16, 32 or 48 bits, so the worst case for a 64-bit immediate is 4 instructions. Or the compiler can decide to put the data in a PC-relative pool and use ADR or ADRP to calculate the address.

ADD immediate is 12 bits but can optionally apply a 12-bit left-shift to that immediate, so for immediates up to 24 bits it can be done in two instructions.

ARM64 decoding is also pretty complex, far less orthogonal than ARM32. Then again, ARM32 was designed to be decodable on a chip with 25,000 transistors, not where you can spend thousands of transistors to decode a single instruction.

+1
zozbot2342 months ago
+1
adgjlsfhk12 months ago
wongarsu2 months ago

MIPS for example also has one, along with a similar number of registers (~32). So it's not like RISC-V took a radical new position here, they were able to look back at what worked and what didn't, and decided that for their target a zero register was the right tradeoff. It's certainly the more "elegant" solution. A zero register is useful as input or output register for all kinds of operations, not just for zeroing

kevin_thibedeau2 months ago

It's a definite liability on a machine with only 8 general purpose registers. Losing 12% of the register space for a constant would be a waste of hardware.

menaerus2 months ago

8 registers? Ever heard of register renaming?

+1
Polizeiposaune2 months ago
account422 months ago

That's irrelevant, the zero register would be taking a slot in the limited register addressing bits in instructions, not replace a physical register on the chip.

kevin_thibedeau2 months ago

8086 doesn't have that.

gruez2 months ago

It's basically the eternal debate of RISC vs CISC (x86). RISC proponents claim RISC is better because it's simpler to decode. CISC proponents retort that CISC means code can be more compact, which helps with cache hits.

bluGill2 months ago

In the real world there is no CISC or RISC anymore. RISC is always extended to some new feature and suddenly becomes more complex. Meanwhile CISC is just a decoder over a RISC processor. Either way you get the best of both worlds: simple hardware (the RISC internals and CSIC instructions that do what you need.

Don't get too carried away in the above, x86 is still a lot more complex than ARM or RISC-V. However the complexity is only a tiny part of a CPU and so it doesn't matter.

snvzz2 months ago

You seem to be confusing ISA and microarchitecture.

Modern ISAs try really hard to be independent from microarchitecture.

snvzz2 months ago

>CISC proponents retort that CISC means code can be more compact,

RISC-V has the most compact code on 64bit, with margin to boot.

On 32bit, it used to be behind Thumb2, but it's the best as of the bit manipulation and extra compressed extensions circa 2021.

dooglius2 months ago

I think one could just pick a convention where a particular GP register is zeroed at program startup and just make your own zero register that way, getting all the benefits at very small cost. The microarchitecture AIUI has a dedicated zero register so any processor-level optimizations would still apply.

pklausler2 months ago

That’s what was done on the CDC 6600 with two handy values, B0 (0) and B1 (1).

gpderetta2 months ago

x86 doesn't need a zero register as it can encode constants in the instruction itself.

rhaps0dy2 months ago

No RSS? I want to subscribe :'(

sph2 months ago

“Who cares about RSS, no one uses it any more”

There’s dozens of us! By the way, totally unaffiliated, but I have used fetchrss for those websites that have no feed.

sylware2 months ago

Remnant of RISC attempt without a zero register.

sylware2 months ago

Come on... that was a joke... this karma system...

tony-john122 months ago

[dead]