4 ms·
That's not how registers work. Register names are not registers. Unless you are in the deepest of embedded, all operations that write to a register allocate a n
by Tuna-Fish 2y ago
That's not how registers work. Register names are not registers. Unless you are in the deepest of embedded, all operations that write to a register allocate a new one, and do not write to their input register.
edit: OoO primer, assuming x86_64 but everything else works the same way too:
Your cpu has hundreds of physical registers. There are 16 register names managed in the frontend. Whenever the frontend sees an instruction that writes to RAX, it allocates a fresh register that does not contain a value, sets it as pending, writes that register number under RAX in the register alias table (RAT), and sends the instruction forward. Once the instruction is in the scheduler, it waits until all it's inputs are ready, and then issues to an execution unit. Once it gets executed, it writes it's value to the register allocated for it and sets it as ready.
If you have an instruction that uses the same register name as input and output, those are guaranteed not to be the same register. Used registers get garbage collected once no name or pending instruction refers to them anymore.
- phkahler 2y agoThank you, that was a very good explanation of "register renaming". The way you described it "renaming" is really more like allocation and is pretty simple - not done on an as-needed basis, just done automatically for every instruction making it SSA (single static assignment).
- Tuna-Fish 2y agoYep. And if you are using clang (or something else that does SSA register allocation), you basically first build the SSA form, compile it down to a fixed register set, and then the CPU reconstructs the SSA form. Another thing that's often misunderstood is branch prediction -- branch prediction is not looking at branches and picking which way to go. Branch prediction runs easily at least 3 cycles before the CPU can see the instructions, so it operates just on memory addresses, and tries to guess the next address to fetch. Generally, before it's built any statistics, it just guesses "next address". When there are jumps, it builds history out of those and makes decision based on that. On various architectures, there used to be hinted branches where the predictor could be tweaked to always first attempt to follow a taken branch, and fall back to prediction if that fails a few times. The hints are all no-ops today, simply because there is no way for the predictor to make use of instruction contents because it doesn't see those until it's work is already done.
- LeifCarrotson 2y agoAs someone deep in an embedded cave, that's illuminating. You have a completely different meaning abstracted on top of the word "register". Over here, in AVR and MSP430 and Cortex-M0 land, there are about 16 registers, instructions take at most 2 registers, and write to the destination (often also a source argument, ADD R1 R2 does R1 := R1 + R2). There is no 'frontend', no renaming, no aliasing, no garbage collector; what you see is what you get. I assume that sometime long ago, the words had the original meaning, but they gradually added abstractions that were still mostly indiscernible at each step of the journey until they had completely diverged.
- mystified5016 2y agoYeah I'm deep into AVR, never got into the fancy architectures like x86 and this sounded bananas to me. In my world a register is a register is hardware. Hell, by sidestepping GCC's register allocation one can substantially increase performance if you arrange your data into registers that your ALU or other peripheral likes best. Prime example, RAM access can be twice as fast if you load your target address into the X/Y/Z register with LD instead of letting GCC emit the LDS instruction. You'd do something like store a pointer to a very important struct that you want to access many times very quickly. I honestly have no clue if a similar concept exists on modern x86. I'd assume that you are explicitly meant to not care about things at this level and it's all handled behind the scenes. It's crazy how much the x86 CPU does for you, but I find it much more fun to tweak things close to the bare metal.
- gpderetta 2y agoIt is not really my speciality, but there are equivalent things on OoO Land. Although CPUs are very good at running reasonable code efficiency, peak FP performance is still the real of hand written ASM or at the very least copious use of intrinsics. It can also be very microarchitecture specific. Because FP code often need significant unrolling, the number of architectural registers needed to store partial results can be a bottleneck, especially if the compiler doesn't do a perfect job.
- dzaima 2y ago
- Symmetry 2y agoJust as importantly, most values in critical dependencies happens via the bypass network, forwarding from one execution port directly to the next without ever making the round trip to the registers.