The surprising subtleties of zeroing a register (2012)
randomascii.wordpress.com
randomascii.wordpress.com
> An x86-64 CPU has sixteen integer registers, but 100-200 physical integer registers. Every time an instruction writes to, say, RAX the renamer chooses an available physical register and does the write to it, recording the fact that RAX is now physical-register #137. This allows the breaking of dependency chains, thus allowing execution parallelism.
I'm curious why they have so many more physical registers than... logical? registers. I have a couple of guesses:
* Physical registers are physically cheaper to add than logical registers.
* Adding logical registers breaks backwards compatibility, or at best means you get no speedup on things written (/compiled) for fewer logical registers. Adding physical registers lets you improve performance without recompiling.
* Adding logical registers increases complexity for people writing assembly and/or compilers. Adding physical registers moves that complexity to people designing CPUs.
Are some of these correct? Other reasons I'm missing?
A compiler's IR is a directed graph, where nodes are instructions and tagged by an assigned register. It would be pleasant to assign each node a distinct register, but then machine instructions would be unacceptably large. So the compiler's register allocator compresses the graph, by finding nodes that do not interfere and assigning them the same register.
The CPU's register renamer then reinflates this graph, by inspecting the dataflow between instructions. If two instructions share a register tag, but the second instruction has no dependence on the first, then they may be assigned different physical registers.
`xor eax, eax` has no dependence on any instruction, so it can be specially recognized as allocating a new physical register. In this way of thinking, `xor eax, eax` doesn't zero anything, but is like malloc: it produces a fresh place to read/write, that doesn't alias anything else.
And, regardless of how many logical registers you have you need to have more physical registers. These are needed for out-of-order (OOO) execution and speculative execution. An OOO super-scalar speculative CPU can have hundreds of instructions in flight and these instructions all need physical registers to work on. If you don't have excess physical registers you can't do OOO or speculative execution.
What register renaming allows is to increase the performance of both new and existing programs, which is no mean feat. It allows the CPU scheduler to search for more out-of-order parallelism rather than relying on the compiler to find in-order parallelism.
This binary compatibility doesn't seem very important now, don't break userspace excepted, but it was then. Compatibility made IBM and Intel hundreds of billions of dollars.
Register renaming enabled out-of-order execution. The 90s were a competition between in-order compiler based scheduling and out-of-order CPUs. The in-order proponents said that out-of-order was too power hungry, too complex and wouldn't scale. Well, it did. Even the Itanium which was the great in-order hope, its last microarchitecture, Poulson, had out-of-order execution.
Ultimately, out-of-order won the war but in-order survives for low power low complexity designs; the A53 is in-order. Skylake Server has 180 physical registers with an out-of-order search window of 224.
https://www.primeline-solutions.com/media/wysiwyg/news-press...
Additionally, compiler tech wasn't anywhere near where it was today, and high perf code was written in ASM. Therefore existing code would have to be rewritten to use more registers, but the 360/91 ran existing code just fine (which was very important for the 360/91's main customers).
That's where the incompatibility comes from -- x86-64 required an entirely new prefix to merely double the GPRs; adding a few hundred more would require some very substantial changes to the opcode map and all decoders already out there.
Seriously, Intel took a long view towards this. x87 was a wart on the side of mole and still its unholy marriage with MMX (they shared a register set) allowed existing programs to run while creating a compatibility barrier to competitors. Competitors had to be compatible and bug compatible. The guy tasked with doing this at Transmeta almost had a nervous breakdown, not from compatibility (easy) but from bug compatibility.
IBM 360 programs still run on the Z architecture.
https://news.ycombinator.com/item?id=17767925
https://news.ycombinator.com/item?id=23205225
Besides, register renaming seems to be working splendidly at the uarch level. Why complicate the architectural model when the gains are already present?
Just wanted to throw out there that it was AMD that came up with x86-64's ISA rather than Intel. Intel was still pushing Itanium hard at the time.
Register renaming allows instructions to be executed out-of-order [1] which allows for more instruction throughput.
This goes back to 1967 and to the IBM 360/91 with its 4 floating point registers. That's not many registers but Moore's law was making more transistors available. The problem was how to use these transistors to get more throughput from existing programs without changing the ISA and (potentially) breaking compatibility.
The solution was Tomasulo's algorithm [2] which allowed (few) architectural registers to be renamed to (many) physical registers.
original renamed reordered
mov RAX, 1 mov PHYS1, 1 mov PHYS1, 1; mov RAX, [RCX]
add RBX, RAX add RBX, PHYS1 add RBX, PHYS1
mov RAX, [RCX] mov RAX, [RCX]
The first and third instructions can be executed at the same time on independent functional units. The third is out-of-order with respect to the second.[1] https://inst.eecs.berkeley.edu/~cs152/sp20/lectures/L10-Comp...
The OS has to store and load all registers whenever it decides to switch which thread is processing. 100 more logical registers means 100 more locations the OS has to keep track of.
This is part of the reason why new SIMD instruction sets need OS support before you can start using them.
This, plus adding logical registers increases instruction size and therefore decreases the number of instructions that can be fetched with a given memory bandwidth.
(I don't know if any manufacturer actually does that last thing, however.)
> * Adding logical registers increases complexity for people writing assembly and/or compilers. Adding physical registers moves that complexity to people designing CPUs.
These two points are the same thing: compatibility and compatibility is what Intel and AMD have lived on from day one with x86. It's why we still live with this really weird instruction set with all of its historical oddities. Certain features of real mode weren't removed until long into the 64-bit era. Adding things isn't any better: If you wanted to add more add more registers, you'd have to change instruction encoding and the instruction space is finite (actually limited to 15 bytes.) That would be rather disruptive.
Basically Intel is saying if you had 200 GPRs, you couldn't do better at using the free ones than the CPU scheduler/decoder.
> Adding *architecturally visible* registers increases complexity for people writing assembly and/or compilers.
More registers just makes your code less likely to have to shuffle stuff to and back from RAM - which is where stuff will go if you don't have registers.
It's always faster for a CPU to access registers within itself than have to talk over a bus to a memory. Even when RAM was the same speed as CPUs (8-bit era) you would still save a cycle or two.
1) More bits to encode register numbers in instructions. Doubling the number of logical registers costs another two or three bits depending on how many registers are referenced in an instruction
2) Logical registers have to be saved on context switches
3) Logical registers have to be saved around function calls. Either the caller or the callee has to save (or not use) registers, and most functions are both callers and callees. That is, if you are not a leaf-node function then every register you use you have to first save to the stack, or else assume that the functions you call will trash it. Thus, more registers have diminishing returns.
4) No matter how many logical registers you have you _always_ want to have an order of magnitude more physical registers, because otherwise you can't implement OOO or speculative execution.
Point #4 is probably the most critical because I think what people are really asking is why are there more physical than logical registers, and OOO/speculative execution is the answer.
Rename logic from BOOM, a RISC-V core written in a DSL embedded in Scala:
https://github.com/riscv-boom/riscv-boom/blob/1ef2bc6f6c98e5...
From RSD, a core designed for FPGAs written in SystemVerilog:
https://github.com/rsd-devel/rsd/blob/master/Processor/Src/R...
And then there's Alibaba's recently open-sourced XuanTie C910, which contains this Verilog… which is completely unreadable. Seems like it was produced by some kind of code generator that they didn't open-source?
https://github.com/T-head-Semi/openc910/blob/d4a3b947ec9bb8f...
The CPU has limited memory bandwidth; the larger instruction size, the more bytes needs to be loaded from memory to execute the instruction. Same with cache size - the more space an instruction takes, the lower the amount of instructions that is cached. Lastly, there's the complexity & latency of the instruction decoder. This possible performance loss is averted by keeping instructions short and instruction set "dense".
Any instruction that refers to a register needs certain amount of bits in the operand portion to indicate which specific register(s) is to be used [1][2][3]. As example, in case of 8-register x86 the operand generally uses 3 bits just to indicate which register to use. In case of 16 register x86_64, it takes 4 bits. If we wanted to use all 200 physical register, that would require whole 8 bits reserved in the instruction just to indicate the register to use. Certain instructions - data transfer, algebra & bitwise operations, comparisons, etc. - naturally use two or more registers, so multiply that accordingly.
Since using this many registers gives only diminishing return in terms of performance (and also requires very heavy lifting on compiler's part[4]), the trade-off selected is that the compiler uses architecture-defined small number of registers, and the processor at runtime is able to speed up some code using the spare registers for instruction-level execution parallelism.
[Edit]
There's one more common circumstance where large number of registers is undesirable: a change of execution context (thread switch; process switch; interrupt). Typically all architecturally-visible registers are saved to memory on a change of context and new set is loaded for the new context. The more registers there are, the more work is to be done. Since the hardware registers are managed directly by CPU and serve as more of cache than directly accessed register, they don't need to be stored to memory.
[1] Aside of certain specialized instructions that implicitly use a particular register; for example in x86 many instructions implicitly use the FLAGS register; DIV/IDIV integer division implicitly uses AX and DX registers.
[2] Aside of certain instruction prefixes that influence which register is used; for example in x86 that would be segment register overrides.
[3] Aside of certain architectures where registers were organized in a "file" and available only through a "window" - i.e., an implicit context, implicit register addressing base; instruction operands referred to registers relative to the current window, and the whole window could be shifted by specialized instructions. Typically shifted on function enter/leave and similar. This was more-or-less the whole "hardware registers" being exposed at architecture level, however in a somewhat constrained / instruction-dense way.
[4] Arranging which registers to use, which to spill to memory etc. is non-trivial work for compiler, and the complexity grows super-linearly with the number of registers.
the implementation defines physical registers
any implementation is permissible as long as it conforms to the ISA
The most active previous discussion: https://news.ycombinator.com/item?id=19262249
The Surprising Subtleties of Zeroing a Register (2013) - https://news.ycombinator.com/item?id=19262249 - Feb 2019 (22 comments)
The Surprising Subtleties of Zeroing a Register (2012) - https://news.ycombinator.com/item?id=11057679 - Feb 2016 (3 comments)
The Surprising Subtleties of Zeroing a Register - https://news.ycombinator.com/item?id=6312266 - Sept 2013 (1 comment)
For SIMD-based FP, you can use VZEROUPPER and VZEROALL if you want to clear all {X,Y,Z}MM state. For a single register, I believe PXOR is still the common idiom, mirroring `XOR EAX, EAX`.
Should read: for x87, unless you are doing this for fun, shoot yourself in the face and instead use SSE because no one in their right mind should be writing x87 code in 2021!
Please help this architectural misfeature die, which should have happened decades ago.
My information is pretty old, but IIRC x87 still has specialized instructions for sin/cos/tan that are sometimes more performant than their equivalent implementations in SSE. x87 instructions are also very small, so trig-heavy workloads where I$ is a measurable performance component might unfortunately still be a good fit for x87.
For future CPUs, it is expected that the performance gap will increase.
The x87 trigonometric functions require typically between 100 and 200 clock cycles. During that time a recent CPU can execute 200 to 500 instructions, enough to compute many values of a trigonometric function (using a polynomial approximation). When SIMD instructions can be used, several tens of values of a function could be computed during a single x87 instruction.
If a trigonometric function is encoded as a single instruction, then it must launch a microprogram, to execute the many required steps.
A microprogram cannot execute faster than when the same execution steps would have been encoded as separate instructions, the only advantage of encoding a trigonometric function in a single instruction would be to reduce the program size. Most programs contain few trigonometric functions, so the reduction in program size is not worthwhile.
While a microprogrammed trigonometric function could be as fast as the equivalent sequence of instructions, in reality it is usually much slower.
The reason is that the modern CPUs are optimized for the most frequent instructions and they dedicate a minimum of resources for the seldom used microprogrammed instructions, so these hit various limitations that do not exist for the simple instructions. The microprogrammed instructions usually have some phases whose execution cannot be overlapped in time with other instructions, which leads to lower performance.
They absolutely are not. If you accept the same, crappy precision, you can do an estimate in just a few cycles instead of the 60+ that the instructions take.
If anything, AMD improved its handling on recent Zens.
Take a look at Agner manuals.
It is plausible it will be dropped at some point but we are not there yet.
[1] newer atoms, although a
That being said, it looks like my information is a little out of date, and they are renamed fully now and share a PRF with the AVX-512 K registers.
Since PPro there isn't really a register stack anymore. FXCH is not a "real" instruction any more, it has 0 latency and uses no execution resources, and it is resolved at the renaming stage and aliases the implicit stack positions to actual fp registers.
> The solution to this problem is register renaming. The FXCH instruction does not in reality swap the contents of two registers; it only swaps their names. Instructions that push or pop the register stack also work by renaming. Floating point register renaming has been highly optimized on the Pentiums so that a register may be renamed while in use. Register renaming never causes stalls - it is even possible to rename a register more than once in the same clock cycle, as for example when FLD or FCOMPP is paired with FXCH.
So, like I stated originally, the presence of FXCH and x87 stack doesn't necessarily imply the the same sorts of physical register allocation dependency breaking concerns involved with "how do I zero a register properly". They do now though, as it looks like there's true renaming going on now.
That is, even though not a lot of x87/MMX code is executed these days (and even less is written) I would be surprised if the OOO/speculative-execution of the processor gets halted whenever these instructions are encountered.
Note that x87 instructions still get used in most 32-bit programs because the x87 registers are the defined way for functions to return floating-point results.
Which is to say, citation needed.
The implications aren't that bad. Only instructions using the outputs of x87/MMX instructions would have to stall. And especially if you're only using them to shift values during function returns, then that wouldn't have to have a huge impact on performance.
Thanks for keeping me honest.
> 2: mov ebx, eax
> 3: xor eax, eax
> 4: add eax, ecx
> Ideally we would like our awesome out-of-order processor to execute instructions 1 and 3 in parallel. There is a literal data dependency between them, but a sufficiently advanced processor could detect that this dependency is artificial.
This doesn't seem right... instruction 2 does need to run after instruction 1 and before instruction 3.
Here's another variant:
> 1: add eax, 1 > 2: mov ebx, eax > 3: mov eax, 42 > 4: add eax, ecx
Here instruction 3 writes the constant 42 to the register. This might make it clearer why instruction #3 can run in parallel with or before instruction #1.
Note that the results must be _retired_ in order, but execution can happen out of order.