12 ms·
XOR'ing a register with itself is the idiom for zeroing it out. Why not sub?
- nopurpose 6mo agoIt amazes me how entertaining Raymond's writing on most mundane aspects of computing often is.
- lynndotpy 6mo agoFor as much flack Microsoft gets today, they have some of the best people writing about low-level computing. James Mickens writings managed to make me literally laugh-out-loud on these subjects. Chen described him best as "the funniest man in Microsoft Research" ( https://devblogs.microsoft.com/oldnewthing/20131224-00/?p=2283 https://devblogs.microsoft.com/oldnewthing/20131224-00/?p=22... )
- grebc 6mo ago[flagged]
- NewCzech 6mo agoThe obvious answer is that XOR is faster. To do a subtract, you have to propagate the carry bit from the least-significant bit to the most-significant bit. In XOR you don't have to do that because the output of every bit is independent of the other adjacent bits. Probably, there are ALU pipeline designs where you don't pay an explicit penalty. But not all, and so XOR is faster. Surely, someone as awesome as Raymond Chen knows that. The answer is so obvious and basic I must be missing something myself?
- mikequinlan 6mo agoAs TFA says, on x86 `sub eax, eax` encodes to the same number of bytes and executes in the same number of cycles.
- whizzter 6mo agoOn modern ones, x86 has quite a history and the idiom might carry on from an even older machine. Edit: Looked at comments, seems like x86 and the major 8bit cpu's had the same speed, pondering in this might be a remnant from the 4-bit ALU times.
- abainbridge 6mo ago> seems like x86 and the major 8bit cpu's had the same speed, pondering in this might be a remnant from the 4-bit ALU times. I think that era of CPUs used a single circuit capable of doing add, sub, xor etc. They'd have 8 of them and the signals propagate through them in a row. I think this page explains the situation on the 6502: https://c74project.com/card-b-alu-cu/ https://c74project.com/card-b-alu-cu/ And this one for the ARM 1: https://daveshacks.blogspot.com/2015/12/inside-alu-of-armv1-first-arm.html https://daveshacks.blogspot.com/2015/12/inside-alu-of-armv1-... But I'm a software engineer speculating about how hardware works. You might want to ask a hardware engineer instead.
- adrian_b 6mo agoNope. In any ALU the speed is determined by the slowest operation, so XOR is never faster. It does not matter which is the width of the ALU, all that matters is that an ALU does many kinds of operations, including XOR and subtraction, where the operation done by an ALU is selected by some control bits. I have explained in another comment that the only CPUs where XOR can be faster than subtraction are the so-called superpipelined CPUs. Superpipelined CPUs have been made only after 1990 and there were very few such CPUs. Even if in superpipelined CPUs it is possible for XOR to be faster than subtraction, it is very unlikely that this feature has been implemented in anyone of the few superpipelined CPU models that have ever been made, because it would not have been worthwhile. For general-purpose computers, there have never been "4-bit ALU times". The first monolithic general-purpose processor was Intel 8008 (i.e. the monolithic version of Datapoint 2200), with an 8-bit ISA. Intel claims that Intel 4004 was the first "microprocessor" (in order to move its priority earlier by one year), but that was not a processor for a general-purpose computer, but a calculator IC. Its only historical relevance for the history of personal computers is that the Intel team which designed 4004 gained a lot of experience with it and they established a logic design methodology with PMOS transistors, which they used for designing the Intel 8008 processor. Intel 4004, its successors and similar 4-bit processors introduced later by Rockwell, TI and others, were suitable only for calculators or for industrial controllers, never for general-purpose computers. The first computers with monolithic processors, a.k.a. microcomputers, used 8-bit processors, and then 16-bit processors, and so on. For cost reduction, it is possible for an 8-bit ISA to use a 4-bit ALU or even just a serial 1-bit ALU, but this is transparent for the programmer and for general-purpose computers there never were 4-bit instruction sets.
- arka2147483647 6mo ago> The answer is so obvious A tangent, but what is Obvious depends on what you know. Often experts don't explain the things they think are Obvious, but those things are only Obvious to them, because they are the expert. We should all kind, and explain also the Obvious things those who do not know.
- akie 6mo ago"The proof is left as an exercise for the reader" comes to mind
- themafia 6mo agoXOR and SUB have had identical cycle counts and latencies since the 8088. That's because you can "look ahead" when doing carries in binary. It's just a matter of how much floorspace on the chip you want to use. https://en.wikipedia.org/wiki/Carry-lookahead_adder https://en.wikipedia.org/wiki/Carry-lookahead_adder The only minor difference between the two on x86, really, is SUB sets OF and CF according to the result while XOR always clears them.
- asQuirreL 6mo agoA carry lookahead adder makes your circuit depth logarithmic in the width of the inputs vs linear for a ripple carry adder, but that is still asymptotically worse than XORs constant depth. (But this does not discount the fact that basically all CPUs treat them both as one cycle)
- bonzini 6mo agoOF/CF/AF are always cleared anyway by SUB r,r. So there's absolutely no difference.
- themafia 6mo agoThe point is OF/CF are sometimes dependent on the inputs for SUB. They never are for XOR.
- bonzini 6mo agoAh, you mean in terms of complexity of the calculation. Thanks for clarifying. In practice AF and CF can be computed from the carry out vector which is already available, and OF is a single XOR (of the two most significant bits of the carry out vector). The same circuitry works for XOR and SUB if the carry out vector of XOR is simply all zeroes.
- themafia 6mo agoIt also clears any dependence on the state of those flags. Which is probably not useful in practice.
- svnt 6mo agoHis point is that in x86 there is no performance difference but everyone except his colleague/friend uses xor, while sub actually leaves cleaner flags behind. So he suspects its some kind of social convention selected at random and then propagated via spurious arguments in support (or that it “looks cooler” as a bit of a term of art). It could also be as a result of most people working in assembly being aware of the properties of logic gates, so they carry the understanding that under the hood it might somehow be better.
- 3form 6mo agoI think an even more likely explanation would be that x86 assembly programmers often were, or learned from other-architecture assembly programmers. Maybe there's a place where it makes more sense and it can be so attributed. 6502 and 68k being first places I would look at.
- richrichardsson 6mo agoFor 68k depending on the size you're interested in then it mostly doesn't matter. .b and .w -> clr eor sub are all identical for .l moveq #0 is the winner
- bonzini 6mo ago6502 doesn't even have register-to-register ALU operations, there's no alternative to LDA #0. 8080/Z80 is probably where XOR A got a lead over SUB A, but they are also the same number of cycles.
- zahlman 6mo agoGP seems to think it strange that "x86" would actually not have a performance difference here. I think this might just be due to not realizing just how far back in CPU history this goes.
- wongarsu 6mo agoIn a clockless cpu design you'd indeed expect xor to be faster. But in a regular CPU with a clock you either waste a bit of xor performance by making xor and sub both take the same number of ticks, or you speed up the clock enough that the speed difference between xor and sub justifies sub being at least a full tick slower The former just seems way more practical
- phire 6mo agoI'm not actually aware of any CPUs that preform a XOR faster than a SUB. And more importantly, they have identical timings on the 8086, which is where this pattern comes from.
- deleted 6mo ago[deleted]
- FarmerPotato 6mo agoI'm studying 4-bit-slice processors from the 1970s. This is all tangent to the x86 discussion. Minicomputer processors! I have two bit-slice machines from TI based on the 74S481 (4-bit slice x 4). Just like with the 74181, all ALU operations go through the same path, there are just extra gates that make the difference between logical or arithmetic. For instance, for each bit in the slice, the carry path is masked out if logical, but used if arithmetic. * The XOR operation (logical) is accomplished with A+B but no bits carry. If carry is not masked, you get arithmetic ADD. * The ZERO or CLEAR operation is (A+A without carry). With carry, A+A is a shift-left. * The ONES operation forces all the carry chain to 1 (ignoring operand) (you can do a ONES+1 to get arithmetic 0, but why?) * In the simpler 74181 (4 years earlier) there are 16 operations with 48 logical/arithmetic outcomes. Pick 12 or so for your instruction set. There are some weirdos. The crazy thing here is that in the TM990/1481 implementation, the microinstruction clock is 15 MHz, and each has a field for number of micro-wait states. This is faster than the '481s max! Theoretically, if 66ns is sufficient to settle the ALU, a logical operation doesn't need a micro-wait-state. While arithmetic needs one, only because of carry-look-ahead. If I/O buses are activated, then micro-instructions account for setup/hold times. I could be wrong about the details, but that field is there! It's the only architecture I know of with short and long microinstructions! (The others are like a fixed 4-stage cycle: input valid, ALU valid, store)
- phire 6mo agoThanks, I suspected there might be something from the minicomputer era. I've only really looked at a single AM2900 implementation (and it was far from optimal). Guess I need to dig deeper at some point. > The ONES operation forces all the carry chain to 1 (ignoring operand) (you can do a ONES+1 to get arithmetic 0, but why?) Forcing all carries to 1 inverts the output. If I'm understanding the ALU correctly, (the datasheet doesn't show that part) it only implements OR and XOR. When combined with the ability to invert both inputs, AND can be implemented as !(!A OR !B), NAND is (!A OR !B) and so on. Or maybe the ALU implements NOR and XNOR, and all the carry logic is physically inverted from what the documentation says.
- feverzsj 6mo agoIt's like 0.5 cycles vs 0.9 cycles. So both are 1 cycle, considering synchronization.
- pishpash 6mo agoBut energy consumption could be different for this hypothetical 0.5 and 0.9.
- scheme271 6mo agoEnergy consumption wasn't really a concern when the idiom developed. I don't think people really cared about the energy consumption of instructions until well into the x86-64 era.
- allenrb 6mo agoNot sure why this is being downvoted, but it’s absolutely correct. For most of the history of computing, people were happy that it worked at all. Being concerned about energy efficiency is a recent byproduct of mobile devices and, even more recently, giant amounts of compute adding up to gigawatts.
- pishpash 6mo agoThis take is anachronistic. Thermal issues were evident by the late 1990's. Of course by that time not many were working in x86 assembly but embedded systems sure cared about power. People forget embedded predated mobile by a good 20 years.
- imtringued 6mo agoNintendo's original Game Boy lasted 40 hours on two AA batteries in 1989. You can't reach those numbers without engineering for energy efficiency.
- Tepix 6mo agoFrom TFA: > It encodes to the same number of bytes, executes in the same number of cycles.
- abainbridge 6mo agoThose aren't the only resources. I could imagine XOR takes less energy because using it might activate less circuitry than SUB.
- zahlman 6mo agoI'm not aware of any stories in the historical record of "real programmers" optimizing for power use, only for speed or code size.
- abainbridge 6mo agoFor a few years I worked in the team that wrote software for an embedded audio DSP. The power draw to do something was normally more important than the speed. Eg when decoding MP3 or SBC you probably had enough MIPS to keep up with the stream rate, so the main thing the customers cared about was battery life. Mostly the techniques to optimize for speed were the same as those for power. But I remember being told that add/sub used less power than multiply even though both were single cycle. And that for loops with fewer than 16 instructions used less power because there was a simple 16 instruction program memory cache that saved the energy required to fetch instructions from RAM or ROM. (The RAM and ROM access was generally single cycle too). Nowadays, I expect optimizations that minimize energy consumption are an important target for LLM hosts.
- toast0 6mo agoSibling posted a good example. But I know of (without details) things where you have to insert nops to keep peak power down, so the system doesn't brown out (in my experience, the 68hc11 won't take conditional branches if the power supply voltage dips too far; but I didn't work around that, I just made sure to use fresh batteries when my code started acting up). Especially during early boot. Apple got in a lot of trouble for reducing peak power without telling people, to avoid overloading dying batteries.
- jojobas 6mo agoThe non-obvious bit is why there isn't an even faster and shorter "mov <register>,0" instructions - the processors started short-circuiting xor <register>,<register> much later.
- deleted 6mo ago[deleted]
- drob518 6mo agoRight, like a “set reg to zero” instruction. One byte. Just encodes the operation and the reg to zero. I’m surprised we didn’t have it on those old processors. Maybe the thinking was that it was already there: xor reg,reg.
- bonzini 6mo agoOne byte instructions, with 8 registers as in the 8086, waste 8 opcodes which is 3% of the total. There are just five: "INC reg", "DEC reg", "PUSH reg", "POP reg", "XCHG AX, reg" (which is 7 wasted opcodes instead of 8, because "XCHG AX, AX" doubles as NOP). One-byte INC/DEC was dropped with x86-64, and PUSH/POP are almost obsolete in APX due to its addition of PUSH2/POP2, leaving only the least useful of the five in the most recent incantation of the instruction set.
- drob518 6mo agoI’m not sure I understand what you mean by “waste 8 opcodes.”
- bonzini 6mo agoThey occupy 8 of the possible 256 byte values. Together, those five cases used about 15% of the space. Though I was forgetting one important case: MOV r,imm also used one-byte opcodes with the register index embedded. And it came in byte and word variants, so it used a further 16 opcodes bytes for a total of 56 one byte opcodes with register encoding.
- virexene 6mo agoThe operation is slightly more complex yes, but has there ever been an x86 CPU where SUB or XOR takes more than a single CPU cycle?
- praptak 6mo agoI wonder if you could measure the difference in power consumption. I mean, not for zeroing because we know from the TFA that it's special-cased anyway. But maybe if you test on different registers?
- flohofwoe 6mo agoThat comment is not very useful without pointing to realworld CPUs where SUB is more expensive than XOR ;) E.g. on Z80 and 6502 both have the same cycle count.
- brigade 6mo agoCortex A8 vsub reads the second source register a cycle earlier than veor, so that can add one cycle latency Not scalar, but still sub vs xor. Though you’d use vmov immediate for zeroing anyway.
- GoblinSlayer 6mo agoHarvard Mark I? Not sure why people think programming started with Z80.
- flohofwoe 6mo agoMy WW2-era assembly is a bit rusty, but I don't think the Harvard Mark 1 had bitwise logical operations?
- bonzini 6mo agoThe article is about x86, and x86 assembly is mostly a superset of 8080 (which is why machine language numbers registers as AX/CX/DX/BX, matching roughly the function of A/BC/DE/HL on the 8080—in particular with respect to BX and HL being last).
- GoblinSlayer 6mo agoSo you say x86 wasn't made ex nihilo, but evolved from previous designs? When this evolution began? 8080 followed 8008, code for which was written in macro-11 https://en.wikipedia.org/wiki/PDP-11_architecture#Example_code https://en.wikipedia.org/wiki/PDP-11_architecture#Example_co...
- deleted 6mo ago[deleted]
- 6mo ago
- billpg 6mo agoI had a similar reaction when learning 8086 assembly and finding the correct way to do `if x==y` was a CMP instruction which performed a subtraction and set only the flags. (The book had a section with all the branch instructions to use for a variety of comparison operators.) I think I spent a few minutes experimenting with XOR to see if I could fashion a compare-two-values-and-branch macro that avoided any subtraction.
- rep_lodsb 6mo agoComparing for equality can use either SUB or XOR: it sets the zero flag if (and only if) the two values are equal. That's why JE/JNE (jump if equal/not equal) is an alias for JZ/JNZ (jump if zero/not zero). There's also the TEST instruction, which does a logical AND but without storing the result (like CMP does for SUB). This can be used to test specific bits. Testing a single register for zero can be done in several ways, in addition to CMP with 0: TEST AX,AX AND AX,AX OR AX,AX INC AX followed by DEC AX (or the other way around) The 8080/Z80 didn't have TEST, but the other three were all in common use. Particularly INC/DEC, since it worked with all registers instead of just the accumulator. Also any arithmetic operation sets those flags, so you may not even need an explicit test. MOV doesn't set flags however, at least on x86 -- it does on some other architectures.
- defmacr0 6mo agoI would be surprised if modern CPUs didn't decode "xor eax, eax" into a set of micro-ops that simply moves from an externally invisible dedicated 0 register. These days the x86 ISA is more of an API contract than an actual representation of what the hardware internals do.
- brigade 6mo agoZero micro ops to be precise, that’s handled entirely at the register rename stage with no data movement.
- defrost 6mo agoFrom TFA: The predominance of these idioms as a way to zero out a register led Intel to add special xor r, r-detection and sub r, r-detection in the instruction decoding front-end and rename the destination to an internal zero register, bypassing the execution of the instruction entirely. You can imagine that the instruction, in some sense, “takes zero cycles to execute”.
- rasz 6mo ago"rename the destination to an internal zero register" That would be quite late then, 1997 Pentium 2 for general population.
- bahmboo 6mo agoBecause he is explicitly talking about x86 - maybe you missed that.
- adrian_b 6mo agoXOR is faster when you do that alone in an FPGA or in an ASIC. When you do XOR together with many other operations in an ALU (arithmetic-logical unit), the speed is determined by the slowest operation, so the speed of any faster operation does not matter. This means that in almost all CPUs XOR and addition and subtraction have the same speed, despite the fact that XOR could be done faster. In a modern pipelined CPU, the clock frequency is normally chosen so that a 64-bit addition can be done in 1 clock cycle, when including all the overheads caused by registers, multiplexers and other circuitry outside the ALU stages. Operations more complex than 64-bit addition/subtraction have a latency greater than 1 clock cycle, even if one such operation can be initiated every clock cycle in one of the execution pipelines. The operations less complex than 64-bit addition/subtraction, like XOR, are still executed in 1 clock cycle, so they do not have any speed advantage. There have existed so-called superpipelined CPUs, where the clock frequency is increased, so that even addition/subtraction has a latency of 2 or more clock cycles. Only in superpipelined CPUs it would be possible to have a XOR instruction that is faster than subtraction, but I do not know if this has ever been implemented in a real superpipelined CPU, because it could complicate the execution pipeline for negligible performance improvements. Initially superpipelining was promoted by DEC as a supposedly better alternative to the superscalar processors promoted by IBM. However, later superpipelining was abandoned, because the superscalar approach provides better energy efficiency for the same performance. (I.e. even if for a few years it was thought that a Speed Demon beats a Brainiac, eventually it was proven that a Brainiac beats a Speed Demon, like shown in the Apple CPUs) While mainstream CPUs do not use superpipelining, there have been some relatively recent IBM POWER CPUs that were superpipelined, but for a different reason than originally proposed. Those POWER CPUs were intended for having good performance only in multi-threaded workloads when using SMT, and not in single-thread applications. So by running simultaneous threads on the same ALU the multi-cycle latency of addition/subtraction was masked. This technique allowed IBM a simpler implementation of a CPU intended to run at 5 GHz or more, by degrading only the single-thread performance, without affecting the SMT performance. Because this would not have provided any advantage when using SMT, I assume that in those POWER CPUs XOR was not made faster than subtraction, even if this would have theoretically been possible.
- imtringued 6mo ago
- bialpio 6mo agoFrom TFA: The predominance of these idioms as a way to zero out a register led Intel to add special xor r, r-detection and sub r, r-detection in the instruction decoding front-end and rename the destination to an internal zero register, bypassing the execution of the instruction entirely.
- TacticalCoder 6mo ago> The obvious answer is that XOR is faster. It used to be not only faster but also smaller. And back then this mattered. Say you had a computer running at 33 Mhz, you had 33 million cycles per second to do your stuff. A 60 Hz game? 33 million / 60 and suddenly you only have about 500 000 cycles per frame. 200 scanlines? Suddenly you're left with only 2500 cycles per scanline to do your stuff. And 2500 cycles really isn't that much. So every cycle counted back then. We'd use the official doc and see how many cycles each instruction would take. And we'd then verify by code that this was correct too. And memory mattered too. XOR was both faster and smaller (less bytes) then a MOV ..., 0. Full stop. And when those CPU first began having cache, the cache were really tiny at first: literally caching ridiculously low number of CPU instructions. We could actually count the size of the cache manually (for example by filling with a few NOP instructions then modifying them to, say, add one, and checking which result we got at the end). XOR, due to being smaller, allowed to put more instructions in the cache too. Now people may lament that it persisted way long after our x86 CPUs weren't even real x86 CPUs anymore and that is another topic. But there's a reason XOR was used and people should deal with it. We zero with XOR EAX,EAX and that's it.
- zahlman 6mo agoThe context was comparison to SUB EAX,EAX, not to a MOV.
- drob518 6mo agoYea, that’s what immediately went through my head, too. XOR is ALWAYS going to be single cycle because it’s bit-parallel.
- Sharlin 6mo agoAnd SUB is also always a single cycle on any practically useful architecture since the 70s. Theoretical archs where SUB might be slower than XOR don't matter.
- Someone 6mo ago> To do a subtract, you have to propagate the carry bit from the least-significant bit to the most-significant bit. Yes, but that need not scale linearly with the number of bits. https://en.wikipedia.org/wiki/Carry-lookahead_adder https://en.wikipedia.org/wiki/Carry-lookahead_adder: “A carry-lookahead adder (CLA) or fast adder is a type of electronics adder used in digital logic. A carry-lookahead adder […] can be contrasted with the simpler, but usually slower, ripple-carry adder (RCA), for which the carry bit is calculated alongside the sum bit, and each stage must wait until the previous carry bit has been calculated to begin calculating its own sum bit and carry bit. The carry-lookahead adder calculates one or more carry bits before the sum, which reduces the wait time to calculate the result of the larger-value bits of the adder. […] Already in the mid-1800s, Charles Babbage recognized the performance penalty imposed by the ripple-carry used in his difference engine, and subsequently designed mechanisms for anticipating carriage for his never-built analytical engine.[1][2] Konrad Zuse is thought to have implemented the first carry-lookahead adder in his 1930s binary mechanical computer, the Zuse Z1.” I think most, if not all, current ALUs implement such adders.
- dreamcompiler 6mo agoCarry lookahead is definitely faster than ripple carry but it's not free. It requires high-fan-in gates that take up a fair amount of silicon. That silicon saves time though, so as you say almost nobody uses ripple carry any more.
- Symmetry 6mo agoThere's a structure called a carry-bypass adder[1] that lets you add two numbers in O(√n) time for only O(n) gates. That or a similar structure is what modern CPUs use and they allow you two add two numbers in a single clock cycle which is all you care about from a software perspective. There are also tree adders which add in O(log(n)) time but use O(n^2) gates if you really need the speed, but AFAIK nobody actually does need to. [1]https://en.wikipedia.org/wiki/Carry-skip_adder https://en.wikipedia.org/wiki/Carry-skip_adder
- anematode 6mo agoMy favorite (admittedly not super useful) trick in this domain is that sbb eax, eax breaks the dependency on the previous value of eax (just like xor and sub) and only depends on the carry flag. arm64 is less obtuse and just gives you csetm (special case of csinv) for this purpose.
- not_a_bijection 6mo agoThat's even more useful because of x86's braindamanged "setcc", which only affects the lowest byte of the destination, AFAIR, and so always has to be combined with a zeroing idiom before the setcc or a zero extension after it in practice.
- defrost 6mo agoOnce an instruction has an edge, even if only extremely slight, that’s enough to tip the scales and rally everyone to that side. And this, interestingly, is why life on earth uses left-handed amino acids and right-handed sugars .. and why left handed sugar is perfect for diet sodas.
- praptak 6mo agoYou still need to explain why this case creates a positive feedback loop rather than a negative one. I mean left/right fuel intakes in cars and male/female ratios somehow tend to balance at 50/50.
- defrost 6mo agoWrt amino acids and sugars I personally don't have to explain as a good many others have already. eg: For one, Isaac Asimov in the 1970s wrote at length on this in his role as a non fiction science writer with a Chemistry Phd > male/female ratios somehow tend to balance at 50/50. This is different to the case of actual right handed dominance in humans and to L- Vs R- dominance in chirality ... ( Men and women aren't actual mirror images of each other ... )
- phenol 6mo agoproducts of an asymmetric reaction performed without enantiomeric control can selectively catalyse the formation of more products with the same handedness -- this is called autocatalysis. so the first full reaction might produce a left-handed product (by chance) but that left-handed product will then cause future products to be preferentially left-handed. see the [Soai reaction](https://en.wikipedia.org/wiki/Soai_reaction?wprov=sfla1 https://en.wikipedia.org/wiki/Soai_reaction?wprov=sfla1) for an example of this. as mentioned by others this is conjectural but it is a popular (if somewhat unfalsifiable) explanation for homochirality
- ben_w 6mo agoRegarding gender ratios: https://en.wikipedia.org/wiki/Fisher's_principle https://en.wikipedia.org/wiki/Fisher's_principle There's exceptions, but they tend to be colonial animals in the broadest sense e.g. how clownfish males are famously able to become female but each group has one breeding male and one breeding female at any given time*, or bees where the males (drones) are functionally flying sperm and there's only one fertile female in any given colony; or some reptiles which have a temperature-dependent sex determination that may have been 50/50 before we started causing rapid climate change but in many cases isn't now: https://en.wikipedia.org/wiki/Temperature-dependent_sex_determination https://en.wikipedia.org/wiki/Temperature-dependent_sex_dete... * Wolves, despite being where nomenclature of "alpha" comes from, are not this. The researcher who coined the term realised they made a mistake and what he thought of as the "alpha" pair were simply the parents of the others in that specific situation: https://davemech.org/wolf-news-and-information/ https://davemech.org/wolf-news-and-information/
- Sweepi 6mo ago"Bonus bonus chatter: The xor trick doesn’t work for Itanium because mathematical operations don’t reset the NaT bit. Fortunately, Itanium also has a dedicated zero register, so you don’t need this trick. You can just move zero into your desired destination." Will remember for the next time I write asm for Itanium!
- shawn_w 6mo agoQuite a few architectures have a dedicated 0 register.
- signa11 6mo agoindeed. riscv for instance. also, afaik, xor’ing is faster. i would assume that someone like mr. raymond would know…
- pif 6mo agoWhich part of "mathematical operations don’t reset the NaT bit" did you not understand?
- IshKebab 6mo ago> afaik, xor’ing is faster Even tiny tiny CPUs can do sub in one cycle, so I doubt that. On super-scalar CPUs xor and sub are normally issued to the same execution units so it wouldn't make a difference there either.
- tliltocatl 6mo agoOn superscalars running xor trick as is would be significantly slower because it implies a data dependency where there isn't one. But all OOO x86's optimize it away internally.
- IshKebab 6mo agoSub has the same false data dependency.
- tliltocatl 6mo agoIt might be because XOR is rarely (in terms of static count, dynamically it surely appears a lot in some hot loops) used for anything else, so it is easier to spot and identify as "special" if you are writing manual assembly.
- stingraycharles 6mo agoAnd helps with SMT Edit: this is apparently not the case, see @tliltocatl's comment down the thread
- tliltocatl 6mo agoWhat's SMT in this context?
- recursivecaveat 6mo agoSimultaneous Multi-Threading (hyper-threading as Intel calls it). I'm not a cpu guy, but I think the ALU used for subtraction would be a more valuable resource to leave available to the other thread than whatever implements a xor. Hence you prefer to use the xor for zeroing and conserve the ALU for other threads to use.
- tliltocatl 6mo agoI don't think that's how it works. - Normally ALU implements all "light" operations (i. e. add/sub/and/or/xor) in a single block, separating them would result in far more interconnect overhead. Often, CPUs have specialized adder-only units for address generation, but never a xor-specialized block. - All CPUs that implement hyper-threading also optimize a XOR EAX,EAX into MOV EAX,ZERO/SET FLAGS (where ZERO is an invisible zero register just like on Itanium and RISCs). This helps register renaming and eliminates a spurious dependency. - The XOR trick is about as old as 8086 if not older.
- Symmetry 6mo ago
- rasz 6mo agoLooking at some random 1989 Zenith 386SX bios written in assembly so purely programmer preferences: 8 'sub al, al', 14 'sub ah, ah', 3 'sub ax, ax' 26 'xor al, al', 43 'xor ah, ah', 3 'xor ax, ax' edit: checked a 2010 bios and not a single 'sub x, x'
- pishpash 6mo agoCould be used to express 1 bit of information in some non-obvious convention.
- adrian_b 6mo agoThe x86-64 ISA provides a lot of alternative encodings for the same instruction or for instructions that are equivalent. It has already been suggested to use these for steganography, i.e. for embedding a hidden message in a binary executable file, by encoding 1 or more bits in the choice of the instruction encoding among alternatives, for every instruction for which alternatives exist.
- ralferoo 6mo agoThe shareware assembler a86 used to use this to fingerprint its output so the author could check whether random programs to see if they were assembled using it without having paid the shareware fee.
- empiricus 6mo agoThe hw implementation of xor is simpler than sub, so it should consume slightly less energy. Wondering how much energy was saved in the whole world by using xor instead of sub.
- flohofwoe 6mo agoI doubt any of that is measurable, since all ALU operations are usually implemented with the same logic (e.g. see https://www.righto.com/2013/09/the-z-80-has-4-bit-alu-heres-how-it.html https://www.righto.com/2013/09/the-z-80-has-4-bit-alu-heres-...)
- croes 6mo agoI guess everything what was saved was burned by the first useless image created per AI
- Symmetry 6mo agoFor a 32 bit number you're looking at going from using 256 to ~1800 transistors in the operation itself. A modern core will have roughly 1,000,000,000 transistors. Some of those are for vector operations that aren't involved in a xor or sub, but most of them are for allowing the core to extract more parallelism from the instruction stream. It's really just a dust mote compared to the power reduction you could get by, e.g., targeting a 10 MHz lower clock rate.
- jhoechtl 6mo agoBack in the stone ages XOR ing was just 1 byte of opcode. Habbits stick. In effect XORing is no longer faster since a long time.
- dragontamer 6mo agoThe XOR trick is implemented as a (malloc from register file) on modern processors, implemented in the decoder and it won't even issue a uOp to the execution pipelines. Its basically free today. Of course, mov RAX, 0 is also free and does the same thing. But CPUs have limited decoder lengths per clock tick, so the more instructions you fit in a given size, the more parallel a modern CPU can potentially execute. So.... definitely still use XOR trick today. But really, let the compiler handle it. Its pretty good at keeping track of these things in practice. ----------- I'm not sure if "sub" is hard-coded to be recognized in the decoder as a zero'd out allocation from the register file. There's only certain instructions that have been guaranteed to do this by Intel/AMD.
- mmozeiko 6mo agosub is also recognized as zeroing idiom for register file. Intel documents these in "3.5.1.7 Clearing Registers and Dependency Breaking Idioms" from Optimization Reference Manual: https://www.intel.com/content/www/us/en/developer/articles/technical/intel64-and-ia32-architectures-optimization.html https://www.intel.com/content/www/us/en/developer/articles/t... Here's html version: https://zzqcn.github.io/perf/intel_opt_manual/3.html#clearing-registers-and-dependency-breaking-idioms https://zzqcn.github.io/perf/intel_opt_manual/3.html#clearin... AMD has similar list in "2.9.2 Idioms for Dependency removal" from "Software Optimization Guide for the AMD Zen5 Microarchitecture" document: https://docs.amd.com/v/u/en-US/58455_1.00 https://docs.amd.com/v/u/en-US/58455_1.00
- flohofwoe 6mo agoDepending on what's stone-age for you, a SUB with a register was also only one byte, and was the same cost as XOR, at least in the Intel/Zilog lineage all the way back to the 70s ;)
- NetMageSCW 6mo ago
- drfuchs 6mo agoRelatedly, there's a steganographic opportunity to hide info in machine code by using "XOR rax,rax" for a "zero" and "SUB rax,rax" for a "one" in your executable. Shouldn't be too hard to add a compiler feature to allow you to specify the string you want encoded into its output.
- gynvael 6mo agoThis sounds like a Paged Out article ;)
- defmacr0 6mo agohttps://www.cs.columbia.edu/~angelos/Papers/hydan.pdf https://www.cs.columbia.edu/~angelos/Papers/hydan.pdf Here's some more prior art
- not_a_bijection 6mo agoYou can do better. X86 has both "op [mem], reg" and "op reg, [mem]" variants of most instructions, where "[mem]" can be a register too. So you have two ways to encode "xor eax, eax", differing by which of the operands is in the "possible memory operand" slot, the source or the destination.
- mpeg 6mo agoThis one would be a fun challenge in a ctf, or maybe more appropriate for a puzzle hunt – most people would look at the dissassembly and not at the actual bytes and completely miss the binary encoding
- zzo38computer 6mo agoSome disassembly listings will also include the actual bytes (there are multiple reasons why you will want this).
- EvanAnderson 6mo agoThat could be a style metric, too. Time spent reversing MS-DOS viruses in my youth showed me assembler programmers very clearly have styles to their code. It's too weak for definitive attribution but it was interesting to see "rhymes" between, for example, the viruses written by The Dark Avenger.
- jdw64 6mo ago[dead]
- enduku 6mo agoI ran into this rabbithole while writing an x86-64 asm rewriter. xor was the default zeroing idiom.I onkly did sub reg,reg when I actually want its flags result. Otherwise the main rule is: do not touch either form unless flags liveness makes the rewrite obviously safe. Had about 40 such idioms for the passes.
- b1temy 6mo agoBack when I was in university, one of the units touching Assembly[0] required students to use subtraction to zero out the register instead of using the move instruction (which also worked), as it used fewer cycles. I looked it up afterwards and xor was also a valid instruction in that architecture to zero out a register, and used even fewer cycles than the subtraction method; but it was not listed in the subset of the assembly language instructions we were allowed to use for that unit. I suspect that it was deemed a bit off-topic, since you would need to explain what the mathematical XOR operation was (if you didn't already learn about it in other units), when the unit was about something else entirely- but everyone knows what subtraction is, and that subtracting a number by itself leads to zero. [0] Not x86, I do not recall the exact architecture.
- adrian_b 6mo agoIt should be noted that XOR is just (bitwise) subtraction modulo 2. There are many kinds of SUB instructions in the x86-64 ISA, which do subtraction modulo 2^64, modulo 2^32, modulo 2^16 or modulo 2^8. To produce a null result, any kind of subtraction can be used, and XOR is just a particular case of subtraction, it is not a different kind of operation. Unlike for bigger moduli, when operations are done modulo 2 addition and subtraction are the same, so XOR can be used for either addition modulo 2 or subtraction modulo 2.
- gblargg 6mo ago> XOR is just a particular case of subtraction, it is not a different kind of operation. It's different in that there's no carry propagation.
- adrian_b 6mo agoThat is not a property specific to XOR. Whenever you do addition/subtraction modulo some power of two, the carry does not propagate over the boundaries that correspond to the size of the modulus. For instance, you can make the 128-bit register XMM1 to be zero in one of the following ways: PXOR XMM1, XMM1 ; Subtraction modulo 2^1 PSUBB XMM1, XMM1 ; Subtraction modulo 2^8 PSUBW XMM1, XMM1 ; Subtraction modulo 2^16 PSUBD XMM1, XMM1 ; Subtraction modulo 2^32 PSUBQ XMM1, XMM1 ; Subtraction modulo 2^64 In all these 5 instructions, the carry propagates inside chunks corresponding to the size of the modulus and the carry does not propagate between chunks. For XOR, i.e. subtraction modulo 2^1, the size of a chunk is just 1 bit, so the propagation of the carry inside the chunk happens to do nothing. There are no special rules for XOR, its behavior is the same as for any other subtraction, any behavior that seems special is caused by the facts that the numbers 1 (size in bits of the integer residue) and 0 (number of carry propagations inside a number having the size of the residue) are somewhat more special numbers than the other cardinal numbers. When you do not do those 5 operations inside a single ALU, but with separate adders, the shorter is the number of bits over which the carry must propagate, the faster is the logic device. But when a single ALU does all 5, the speed of the ALU is a little slower than the slowest of those 5 (a little slower because there are additional control gates for selecting the desired operation). The other bitwise operations are also just particular cases of more general vector operations. Each of the 3 most important bitwise operations is the 1-bit limit of 2 operations which are distinct for numbers with sizes greater than 1 bit, but which are equivalent for 1-bit numbers. While XOR is just addition or subtraction of 1-bit numbers, AND is just minimum or multiplication of 1-bit numbers, and OR is just maximum of 1-bit numbers or the 1-bit version of the function that gives the probability for 1 of 2 events to happen (i.e. difference between sum and product).
- Suzuran 6mo agoOn some of IBM's smaller processors, such as channel controllers and the CSP used in the midrange line prior to the System/38, the xor instruction had a special feature when used with identical source and destination - It would inhibit parity and/or ECC error checking on the read cycle, which meant that xor could be used to clear a register or memory location that had been stored with bad parity without taking a machine check or processor check.
- rep_lodsb 6mo agoInteresting, since the general culture at IBM seems to have preferred SUB over XOR -- their earlier business-oriented machines didn't even have a XOR instruction, and even on later ones the use of SUB has persisted, including in the IBM PC and AT BIOS. (There was another, now deleted, comment somewhere in this thread that mentioned IBM's preference for SUB. Source of that statement was Claude, but it seems very likely to be correct. The BIOS code I've checked myself, lots of 'SUB AX,AX', no XOR)
- Suzuran 6mo agoYou may not be looking for the right thing. On the aforementioned CSP, the instruction that performed XOR was called "XR" and not "XOR". My source is firsthand knowledge; I was a CE and performed service calls on the System/34, System/36, 370, and 390. In any case, I am describing equipment built mostly in late 60s through the late 70s at IBM Rochester and Poughkeepsie. The IBM PC was developed by an entirely different team at IBM Boca Raton, and IBM didn't design its CPU.
- rep_lodsb 6mo agoI don't doubt that this specific processor special-cased XOR (regardless of how it was called in the assembly language)! Merely pointing out that where both operations were available, there seems to have been a preference to use SUB instead, with some continuity from early business-oriented mainframes, to the 360, to the PC.
- 6mo ago
- zahlman 6mo ago> but xor took a slightly lead due to some fluke, perhaps because it felt more “clever”. Absolutely. But I can also imagine that it feels more like something that should be more efficient, because it's "a bit hack" rather than arithmetic. After all, it avoids all the "data dependencies" (carries, never mind the ALU is clocked to allow time for that regardless)! I imagine that a similar feeling is behind XOR swap. > Once an instruction has an edge, even if only extremely slight, that’s enough to tip the scales and rally everyone to that side. Network effects are much older than social media, then....
- dreamcompiler 6mo agoI vaguely remember we used the XOR trick on processors other than Intel, so it may not be Intel-specific. In principle, sub requires 4 steps: 1. Move both operands to the ALU 2. Invert second operand (twos complement convert) 3. Add (which internally is just XOR plus carry propagate) 4. Move result to proper result register. This is absolutely not how modern processors do it in practice; there are many shortcuts, but at least with pure XOR you don't need twos complement conversion or carry propagation. Source: Wrote microcode at work a million years ago when designing a GPU.
- rep_lodsb 6mo agoThese two steps usually run in parallel though, with transistors to enable them depending on what operation should be performed.
- bonzini 6mo agoYou don't do twos complement negation for sub in an integer ALU. You do ones complement (A + ~B) and set the input carry to 1. The difference is that you don't need two carry propagations and therefore you can just add a fancy A + ~B function to the ALU. Floating point is different because what matters is same sign or different sign (for same sign you cannot have cancellation and the exponent will always be the same or one than the largest input's. So the FP mantissa tends to use sign magnitude representation.
- RiverCrochet 6mo agoXOR is a simple logic-gate operation. SUB would have to be an ALU operation. A one-bit adder (which is subtraction in reverse) makes signals pass through two gates. See https://en.wikipedia.org/wiki/Adder_(electronics) https://en.wikipedia.org/wiki/Adder_(electronics) You need the 2 gates for adding/subtracting because you care about carry. So if you're adding/subtracting 8 bits, 16 bits, or more, you're connecting multiples of these together, and that carry has to ripple through all the rest of the gates one-by-one. It can't be paralellized without extra circuitry, which increases your costs in other ways. Without the AND gate needed for carry, all the XORs can fire off at the same time. If you added the extra circuitry for a parallelizable add/subtract to make it as fast as XOR, your actual parallel XOR would consume less power.
- commandlinefan 6mo agoIt's still the same number of clock cycles, though, isn't it? You're using some extra circuitry during the SUB, but during the XOR, that circuitry is just sitting idle anyway, so it's still six of one/half a dozen of the other.
- deleted 6mo ago[deleted]
- deleted 6mo ago[deleted]
- RiverCrochet 6mo agoXOR can do everything in 1 cycle (which is hopefully far, far less than the clock). SUB-if done the simple way-has to take n cycles where n is the number of bits subtracted.
- jonathrg 6mo agoWhat do you mean by cycles? A ripple-carry adder needs to wait for the carry bits to ripple through yes, but there's no clock cycle involved.
- matja 6mo agoSUB has higher latency than XOR on some Intel CPUs: latency (L) and throughput (T) measurements from the InstLatx64 project (https://github.com/InstLatx64/InstLatx64 https://github.com/InstLatx64/InstLatx64) : | GenuineIntel | ArrowLake_08_LC | SUB r64, r64 | L: 0.26ns= 1.00c | T: 0.03ns= 0.135c | | GenuineIntel | ArrowLake_08_LC | XOR r64, r64 | L: 0.03ns= 0.13c | T: 0.03ns= 0.133c | | GenuineIntel | GoldmontPlus | SUB r64, r64 | L: 0.67ns= 1.0 c | T: 0.22ns= 0.33 c | | GenuineIntel | GoldmontPlus | XOR r64, r64 | L: 0.22ns= 0.3 c | T: 0.22ns= 0.33 c | | GenuineIntel | Denverton | SUB r64, r64 | L: 0.50ns= 1.0 c | T: 0.17ns= 0.33 c | | GenuineIntel | Denverton | XOR r64, r64 | L: 0.17ns= 0.3 c | T: 0.17ns= 0.33 c | I couldn't find any AMD chips where the same is true.
- deleted 6mo ago[deleted]
- Symmetry 6mo ago.03ns is a frequency of 33 GHz. The chip doesn't actually clock that fast. What I think you're seeing is the front end detecting the idiom and directing the renamer to zero that register and just remove that instruction from the stream hitting the execution resources.
- adrian_b 6mo agoSUB does not have higher latency than XOR on any Intel CPU, when those operations are really performed, e.g. when their operands are distinct registers. The weird values among those listed by you, i.e. those where the latency is less than 1 clock cycle, are when the operations have not been executed. There are various special cases that are detected and such operations are not executed in an ALU. For instance, when the operands of XOR/SUB are the same the operation is not done and a null result is produced. On certain CPUs, the cases when one operand is a small constant are also detected and that operation is done by special circuits at the register renamer stage, so such operations do not reach the schedulers for the execution units. To understand the meaning of the values, we must see the actual loop that has been used for measuring the latency. In reality, the latency measured between truly dependent instructions cannot be less than 1 clock cycle. If a latency-measuring loop provides a time that when divided by the number of instructions is less than 1, that is because some of those instructions have been skipped. So that XOR-latency measuring loop must have included XORs between identical operands, which were bypassed.
- butterisgood 6mo agoI recall thinking about these things quite a bit when reading Michael Abrash back in the 90s. How much of that advice applies to anything these days is questionable. Back then we used to squeeze as much as possible from every clock cycle. And cache misses weren’t great but the “front side bus” vs CPU clock difference wasn’t so insane either. RAM is “far away” now. So the stuff you optimize for has changed a bit. Always measure!
- hadnoclue 6mo ago[dead]
- NanoWar 6mo agoXORing just feels more like xxxxing out the register. SUB feels like a calculation or mistaken use of a register.
- billforsternz 6mo agoBack in the early 1980s I leveled up my self taught Z80 assembly skills by reading a book that attempted to disassemble and explain the Sinclair Spectrum ROM. I remember the very first ROM instruction was XOR A and this was already a revelation to me as I'd never considered doing anything other than LD A,0 to clear the accumulator.
- fortran77 6mo agoSUB may have side effects of setting flags that XOR may not depending on the CPU.
- lochnessduck 6mo agoI use the carry flag in a lot of z80 assembly for communicating a status of an operation. XOR doesn’t mess with the carry flag, I think it’s another point in favor of xor. (Though I don’t remember even considering using sub)
- Jare 6mo agoThis is the exact reason I remember from back in the 80's. Perform arithmetic, clear register, CF is still valid.
- kevin_thibedeau 6mo agoXOR is faster than SUB on bit slice processors. I'd posit that that created established idioms as micros came on the scene.
- aforwardslash 6mo agoAfaik xor reg,reg is optimized by the cpu as zero-out reg; sub reg, reg is quite more difficult to optimize this way; this seems to be quite important in modern cpus, where cisc is translated to micro-ops; in superscalar archs, this is probably optimized away instead of causing a stall.
- michaelcampbell 6mo ago> I don’t know why xor won the battle, but I suspect it was just a case of swarming. > In my hypothetical history, xor and sub started out with roughly similar popularity, but xor took a slightly lead due to some fluke, perhaps because it felt more “clever”. SO MUCH ink and "odd" code has been spilled over these 2 sentences over the past few decades...
- deleted 6mo ago[deleted]