4 ms·
> extends the number of addressable GPRs to 32 from 16 I have always been curious as to why the number of GPRs were limited for so long on X86 given that the i
by soulbadguy 3y ago
> extends the number of addressable GPRs to 32 from 16
I have always been curious as to why the number of GPRs were limited for so long on X86 given that the instruction set is already variable length, and the CPU have typically a very large number of internal arch-register that could be cheaply addressed.
Having looked at the pain of developing a good register allocate in LLVM, and how critical memory access can me in hot/tight loops i would have loved to have even more register something closer to 64 or 128, and let the cpu manage the spilling internally.
- jcranmer 3y ago> I have always been curious as to why the number of GPRs were limited for so long on X86 given that the instruction set is already variable length, and the CPU have typically a very large number of internal arch-register that could be cheaply addressed. Because it's hard-ish to wrench in the encoding bits. x86 registers are addressed via the ModR/M byte, which gives you 4 addressing modes × 2 register operands (if you have 8 registers). Note that non-register-register addressing modes include a second SIB byte which means you have up to three registers encoded in an instruction. x86-64 extended the number to 16 by adding a prefix that has 4 bits, which encoded a 32-bit/64-bit selector and 1 bit for each of the three possible registers (R, X, B). Note that to keep the prefix to only a 1-byte length, they had to reclaim 16 possible opcodes, whose space is pretty limited. Encoding large register numbers gets pretty chonky in instruction space (3 register ids of 5 bits each is 15 bits of your instruction for operands, and that's before you start considering possible opcodes). It also increases the size of context switches (especially thread contexts), since you have to spill all of those registers even if they're not filled with useful data. 8 registers is definitely too few; it's not clear to me if the extra working set size afforded by 32 registers is worth it over the fatter instructions.
- duped 3y agoThey're not really limited since the instruction decoder is actually doing a bunch of work to assign to physical registers. The 16 GPRs are an abstraction, iirc. Even though it's only one extra bit to index 16 registers vs 8, it takes an entire extra byte in the encoding (you have to add the REX prefix with the R, X, and B bits set appropriately for the MSB of the registers you're modifying in the MODR/M byte). And you only get 4 bits to use out of that byte because the leading four are a way to disambiguate it (also iirc, this stuff is terribly documented). And paying an extra byte per GRP instruction is actually kind of expensive. If you look at your compiler output you'll see a bunch of Exx instructions even in 64 bit long mode when optimizing for instruction decode/size. It turns out a variable length encoding is really hard to extend without getting weird with it.
- peterfirefly 3y ago> And you only get 4 bits to use out of that byte because the leading four are a way to disambiguate it (also iirc, this stuff is terribly documented). No, it has been extremely well documented since 2000 (three years before AMD had the first 64-bit chip ready).
- gpderetta 3y agoWell, originally there where 7 (well, 6 really as frame pointers were pretty much required) general purpose registers. AMD64 bumped that to 14-15. That's a huge change with clear performance benefits. going to 32 is already into diminishing results for most use cases and has non-trivial encoding, function calling and context-switching implications.
- peterfirefly 3y agoShared libs on the 386 stole a register more, because IP-relative addressing wasn't a thing until AMD64 (except for jmp/call). Shared libs need to refer to their own data + they may need to form pointers to code within them. (Shared libraries need to have a pointer to their code and data. This is done by creating a small table of entry code in front of the "real" code that sets a register to point to the library code/data memory before jumping to the real code. This code will look different for each process that has loaded the shared library so it gets its own little memory block. The rest of the code/data will be the same in all processes so it can be shared between them -- unless/until some of the data is written to, of course.) So code in shared libs really only had 5 registers unless the frame pointer was fomitted. That's why it was necessary to tell the compiler whether to build normal code or shared library code. The AMD64 not only had more registers, it also has an RIP-relative addressing mode so it doesn't need to steal that register for shared libraries.
- variadix 3y agoAdding more registers makes context switching take longer and require more memory. It’s a trade off.
- kccqzy 3y agoThese registers are caller-saved, so no need to save them for system calls. Thread switching might be performance neutral if the OS uses the xsave / xrstor instructions.