Hacker News new | ask | show | jobs
by phire 14 days ago
IMO, the "out-of-order performance/complexity" excuse is complete bullshit.

Out-of-order pipelines actually have a really elegant way of handling flags, they just store a copy of the flags register on every ROB entry. During renaming, instructions that consume flags just gain an extra implicit input pointing to the ROB that will contain the correct flags.

Such pipelines already need deal with so much serialised state. The flags are actually one of the easier things to deal with, they basically get support for free when they implement register renaming.

It's actually the "classic RISC" style in-order pipelines where flags are annoying to deal with. They pipelines don't need to do any register renaming, so they don't have a free solution to handle flags. And the RISC-V ISA is very much optimised for these simpler pipelines.

2 comments

Instructions that update some flags and not others have to be serialised in this design.
True, and I wouldn't be surprised if modern x86 implementations need to do something a bit smarter.

But if you are designing a new ISA for the modern era (the purported selling point of RISC-V), you just don't implement any such instructions. Only implement clean instructions that update all the flags or none. ARM and PowerPC already did this 35-40 years ago.

In ISAs where this problem exists, i.e. in x86-64 where the carry flag is updated or not updated regardless of what happens with the other flags, the flags register is split into 2 separate registers, which are renamed independently.

The 2 parts of the flags register are reunited only for the purpose of saving or restoring from the main memory. (Actually the x86-64 flags register has 3 independent parts, the carry flag, the other status flags, and a set of configuration flags, which are not modified by most instructions.)

> Out-of-order pipelines actually have a really elegant way of handling flags, they just store a copy of the flags register on every ROB entry.

And yet, flag writing instructions are usually half the throughput on regular ALU instructions, on modern wide ooo designs. [1, 2]

But I generally agree, there is a good way of handling them, it's hust unclear to me how expensive it is. It can't be that expensive, but apparently it is expensive enough, to not put a mask register write port on all integer execution units.

[1] https://dougallj.github.io/applecpu/firestorm-int.html (see how ADD is 6-issue and ADDS 3-issue)

[2] https://developer.arm.com/documentation/111027/4-0/ (again 8-issue ADD, but 4-issue ADDS)

I wonder if the issue is actually the cost of calculating the flags.

N and C are basically free (copy of bit 63, and it's carry out), V is an extra gate or two, but Z requires a full 64-bit wide NOR gate.

Or it might be about making the register file holding the flags smaller (only 3R3W, instead of 6R6W), along with simplifying the associated bypass network and routing (the first three ALUs are also the only units that can consume flags).

When I say ooo cpus get flag handling for "essentially free", I'm only actually talking about the complexity of tracking "implicit state", and that it can be done without extra latency. You still need to spend transistors to support renaming flags, and to actually calculate and storing the flags.

I can see an argument for an ISA that got rid of the N and Z flags, but kept C/V (potentially merged into a single flag). You can trivially reconstruct N/Z from the result register, but not C/V.

That seems like a weird thing to save on, for architectures that have expensive but rarely used things like CLZ on all ALUs.

But I hadn't considered the ISA design option of only carry flag, no seperate cmp and branch, before.

Huh, it does have CLZ on all ALUs (And CLZ can actually be used to reduce the cost of checking for all zeros).

So it must be something about limiting the complexity of the flags register file and/or its bypass network.

> But I hadn't considered the ISA design option of only carry flag, no seperate cmp and branch, before.

Well, RISC-V does have a decent point: Most of the time you simply don't need flags. The only area this approach falls apart is detecting overflow or add-with-carry.

So, why not just cover that flaw by adding a carry/overflow flag.

The counter-argument is that as soon has you "ruin the purity of the design" by adding one flag, you might as well add the complete set of all four. Then you can simply branch instruction encodings, and use the extra bits for something else (like seperate set-flags versions of all the ALU instructions)

> So, why not just cover that flaw by adding a carry/overflow flag.

Actually, why not just have a single generic 1-bit flag?

You could still have cmp + branch, but now you put the comparison type into the cmp. This gives you a larger branch range again. A flag setting add/sub could also simply set that flag on carry. cmov works, some form of ccmp also works.

Designing an ISA arround carry flags certainly gives you many goodies: cmov, adc, ccmp, larger branch range
Well, cmov is absolutely possible without flags. You see such "select" instructions all the time on GPUs. Doesn't even require an extra instruction, cmp + cmov is two instructions, seqz + select is two.

But it does require three input registers. One for a control value, plus the two options. And RISC-V has a thing about not requiring three input registers.... Yeah, I'm not a fan of RISC-V.

One of the other major advantages of flags is it allowed for a cheap third input to certain instructions (like cmov, adc, ccmp), without requiring a full 3R1W register file.

The fact that it's possible to design a viable ISA without flags is really neat, it was absolutely worth trying. But MIPS already proved it was possible, and showed all the downsides, it did not need to be tried a second time with RISC-V.