5 ms·
This is assembly. This instruction is doing an boolean "Exclusive Or" of a register named "AX" (think of a register like a hardware variable) with itself into i
by navaati 7y ago
This is assembly. This instruction is doing an boolean "Exclusive Or" of a register named "AX" (think of a register like a hardware variable) with itself into itself. This always leave the register containing 0, because 0 XOR 0 = 0 and 1 XOR 1 = 0.
And for some weird reason, doing that was faster than just doing MOV AX, 0 (which is literally "Move 0 into AX", so "AX = 0" in more familiar syntax).
Edit: Oh man, my fellow HN'ers, as I did, all jumped on the occasion to show off :)
- omginternets 7y agoAh! Wow, that is surprising. Why was it so much faster?
- jedimastert 7y agoThere's a bunch of overhead associated with reading a constant from the program and moving that to the register. Math operations, on the other hand, are very simple to do because it's baked right into the CPU, only requiring one instruction and a few cycles
- murderfs 7y agoNo, the reason is that `xor ax, ax` has a shorter encoding, because it only takes 3 bits to encode a register on 32-bit x86, and far more bits are required for a useful immediate move. It would be ridiculous to have a short encoding for `mov rN, #imm` where the immediate has to be in the range 0-7.
- tomtomtom777 7y ago> It would be ridiculous to have a short encoding for `mov rN, #imm` where the immediate has to be in the range 0-7. Such instruction didn't exist but I wouldn't call it ridiculous. It would actually be quite useful as setting a register to 3 is far more common than setting a register to 7911.
- murderfs 7y agoIf you're going to do something like that, you might as well steal from the register numbering bits instead. (many architectures have done this: Intel has lots of short-form encodings for "op {al, ax, eax, rax} #imm", and ARM has Thumb, which basically chops the register count in half for all instructions to shorten their encoding)
- vidarh 7y agoM68000 has a MOVEQ #imm, Dn Since its instructions are always multiples of 16bit though, the immediate value is 8 bit, so slightly more useful, but the most common use of it was certainly setting a register to 0
- epx 7y agoTrivia: MIPS and RISC-V have the r0 register equal to zero to optimize this case in a more elegant way.
- will4274 7y agoSo does x64, almost. More accurately, so do the underlying Intel and AMD micro-archs that host x64.
- pwg 7y agoIn the beginning (8086/8088) because it was a shorter instruction encoding than the literal "move a zero into AX" instruction, so it created smaller code and saved a memory read (even if that read was a prefetch memory read). Later Intel actually special cased it in the instruction decoding path for the later CPU's (starting somewhere around the PII/PIII era, but I don't remember the exact timeframe) so that it also does not consume an execution unit and it /effectively/ executes in zero cycles.
- dfox 7y agoThe reason why it is special cased is that on pipelined or OoO CPU, xor ax,ax would otherwise be significantly slower than straight mov as xor has dependency on its operand registers. On a similar note on many RISC architectures NOP is actually something like ADD r0, r0, r0 and that too is usually special cased in the hazard stall and result forwarding logic (althought usually the special cased part is “ignore hazards that involve r0”)
- aflag 7y agoAdding the same register to itself and then assigning the value to itself is not really a nop. Why is that instruction forbidden and converted to a nop instead?
- pwg 7y agoBecause in some RISC architectures, the first (R0) or last (R31 or whatever is largest register number) is also a 'special' register in that it is hardwired to zero, any reads return zero, and writes are simply discarded. In this instance, if the register always reads as zero, and can never be changed, then ADD R0,R0,R0 is, in effect, a NOP, so it gets special cased, and doing so avoids having to allocate an additional opcode explicitly to the "NOP" instruction. Anticipating the question of "why is Rx (0 or 31 or ??) hardwired to zero?", that one is because it is useful for: 1) obtaining a zero without having to perform a load from memory 2) creating additional addressing modes by reusing another existing addressing mode For #2, if the RISC arch. implements, say, base plus index addressing where two registers are added together to obtain a final address, using R0 as one of the inputs creates a direct addressing mode from the base plus index mode. So base plus index could be written as load R5, R6+R7. Substituting R0 (assuming it is the hardwired to zero register) for R6 (or R7) results in directly addressing from the value in the other register, converting an 'indexed' addressing mode into a 'direct' mode, without having to add a 'mode select bit' to the actual instruction. The result being that the chip only needed hardware for a single addressing mode, but the programmer has two addressing modes available for their use. If memory serves, the DEC Alpha made large use of tricks like this. The hardware only implemented a small handful of addressing modes (say 4-5) yet the full set of addressing modes exposed to the programmer was two or three times larger due to creative uses of "zero stuffing" into the actual hardware modes.