Having designed microcoded pipelined processors from gates on up in uni, that's true.
integer cmp x, y often reduces to microcode that effectively runs:
- 3A: sub x, y, dummy_result (att syntax) <-- most likely^
- 2A: sub copy_of_x, y (intel syntax)
- 1A: push x, push y (or y, x), sub, update flags, pop
^ Depends heavily on the microarchitecture, because there's likely all sorts of extra features and ops that can be set in the same microinstruction cycle to implement the macroinstructions.
In signed 2's-complement (mostly everything), zero flag just checks that all result bits to be zero and the less than flag is just the result's MSB (negative result if 1).
Signed and unsigned have their under/overflow issues. Being able to detect that and handle it in code, rather than crashing or silently producing undefined results, can be important, such as safely re-callocing memory (multiply overflow). (There are other issues like pointer/slice index over/underflow too, and these are related problems.)
The issues are usually around whether branch-free code is absolutely necessary or whether predicting branches wrong would incur disastrous pipeline stall/s (greatly depends on the specific use-case, especially inner loops... don't prematurely overoptimize).
I almost feel there needs to be something in-between IR languages like LLVM IR and general-purpose C, that's not assembly but still general-purpose, functional/imperative, static analysis and able to expose math and branching state/options/differences cleanly across a variety platforms without a fugly/verbose syntax.
To make up a pseudoreligion (if it's doesn't look like your preferred Turing-complete language, sorry) at random:
var x, y int32
# ... assign x and y somehow here
begin
x = x +! y # or x +=! y
rescue IntegerOverflowError
# ... int32.max + int32.max => (0xFFFFFFFF + 0xFFFFFFFF) & 0xFFFFFFFF => 0xFFFFFFFE (-2, int32)
rescue IntegerUnderflowError
# ... int32.min + int32.min => (0x7FFFFFFF + 0x7FFFFFFF) & 0xFFFFFFFF => 0xFFFFFFFE (-2, int32)
end
var s, t uint64
# ... assign s and t somehow here
begin
s *=! t
rescue IntegerOverflowError
# ... uint64.max * uint64.max => ... => 1 (1, uint64)
end