No flags, just a 1 or a 0
Many CPUs keep a hidden flags register that a compare instruction sets and a branch reads. MIPS does not. It uses : the comparison result is a plain value, 1 or 0, in a plain register.
| instruction | compares | reading | second operand |
|---|---|---|---|
slt rd,rs,rt | rs < rt | signed | a register |
sltu rd,rs,rt | rs < rt | unsigned | a register |
slti rt,rs,imm | rs < imm | signed | a sign-extended 16-bit constant |
sltiu rt,rs,imm | rs < imm | unsigned | a sign-extended constant, then compared unsigned |
That is the whole family. There is no sgt, no sle, no sge. The next
sections show how those come for free.
Same bits, two answers
Whether a < b depends on how you read bit 31. The instruction chooses.
−1 versus 1, read both ways
How can the same two registers give two different answers?
-1 (signed) · 0xffffffff · 4294967295 if read unsigned
1 (signed) · 0x00000001
| instruction | reads the bits as | the question | result |
|---|---|---|---|
| slt | signed | -1 < 1? | 1 |
| sltu | unsigned | 4294967295 < 1? | 0 |
- bit 31 — the only bit whose meaning depends on signed vs unsigned
Click any input bit to flip it.
What to notice
Read signed, 0xFFFFFFFF is −1, and −1 < 1 is true: slt gives 1. Read
unsigned, the same bits are 4294967295, the largest value there is: sltu
gives 0. Bit 31 has a thick border; it is the only bit whose meaning changes.
Four compares on one pair
The runner puts −1 and 1 through all four instructions and prints each result.
It pauses once to ask you about sltu.
Four compares on one pair of values: −1 and 1
How can −1 < 1 be true and false at the same time?
program (address · word · instruction)
| PC → | 0xbfc00000 | 0x3c191000 | _start:lui $t9,0x1000 |
| 0xbfc00004 | 0x37390ff0 | ori $t9,$t9,0xff0 | |
| 0xbfc00008 | 0x2408ffff | li $t0,-1 | |
| 0xbfc0000c | 0x24090001 | li $t1,1 | |
| 0xbfc00010 | 0x0109502a | slt $t2,$t0,$t1 | |
| 0xbfc00014 | 0xaf2a0004 | sw $t2,4($t9) | |
| 0xbfc00018 | 0x24180020 | li $t8,32 | |
| 0xbfc0001c | 0xaf380000 | sw $t8,0($t9) | |
| 0xbfc00020 | 0x0109582b | sltu $t3,$t0,$t1 | |
| 0xbfc00024 | 0xaf2b0004 | sw $t3,4($t9) | |
| 0xbfc00028 | 0xaf380000 | sw $t8,0($t9) | |
| 0xbfc0002c | 0x290c0005 | slti $t4,$t0,5 | |
| 0xbfc00030 | 0xaf2c0004 | sw $t4,4($t9) | |
| 0xbfc00034 | 0xaf380000 | sw $t8,0($t9) | |
| 0xbfc00038 | 0x2d0dffff | sltiu $t5,$t0,-1 | |
| 0xbfc0003c | 0xaf2d0004 | sw $t5,4($t9) | |
| 0xbfc00040 | 0x240e0000 | li $t6,0 | |
| 0xbfc00044 | 0xaf2e0008 | sw $t6,8($t9) |
registers — read · written this tick
console (what the doors printed)
▮tick 0 of 18
Nothing has run yet. The PC (program counter) points at the first instruction.
$t0 is 0xFFFFFFFF: −1 read signed, 4294967295 read unsigned. slt says 1 (−1 < 1), sltu says 0 (4294967295 < 1 is false). slti agrees with slt. sltiu sign-extends its −1 to 0xFFFFFFFF and then compares unsigned: equal, so 0.
What to notice
slt prints 1, sltu prints 0, slti prints 1. sltiu prints 0 for a
different reason: its immediate −1 sign-extends to 0xFFFFFFFF, the same
value as $t0. Equal is not less, so the answer is 0.
Before reading on, decide:
$t0 = 0xFFFFFFFF. What does `sltiu $t5,$t0,-1` write, and why?
Greater than, and the other four
With only “less than”, every other comparison is a swap, a flip, or both:
| you want | write | why it works |
|---|---|---|
| a < b | slt rd,a,b | the instruction itself |
| a > b | slt rd,b,a | a > b is the same as b < a |
| a >= b | slt rd,a,b then xori rd,rd,1 | NOT (a < b); xori with 1 flips a 0/1 |
| a <= b | slt rd,b,a then xori rd,rd,1 | NOT (b < a) |
| a == 0 | sltiu rd,a,1 | unsigned, only 0 is below 1 |
| a != 0 | sltu rd,$zero,a | unsigned, 0 < a for every a except 0 |
Assemblers accept sgt, sge, sle, seq and sne as pseudo-instructions
and emit exactly these sequences.
How a compiler writes i < 5
Here is the for loop of hello.c, and what GCC emitted for it at -O0.
Click a C line to light up its MIPS lines.
for (i = 0; i < 5; i++) in hello.c and in the -O0 dump (i lives on the stack at 16(s8))
carolyne_ref/programs/hello.c; hello_o0.dump bfc001ac–bfc001f0
1for (i = 0;2 i < 5;3 i++) {4 print_int(i * i);5 print_char('\n');6}bfc001acsw zero,16(s8)bfc001b0b bfc001e4bfc001b4nopbfc001b8lw v0,16(s8)bfc001bcmul v0,v0,v0bfc001c0move a0,v0bfc001c4jal bfc000ec <print_int>bfc001c8nopbfc001ccli a0,10bfc001d0jal bfc000b0 <print_char>bfc001d4nopbfc001d8lw v0,16(s8)bfc001dcaddiu v0,v0,1bfc001e0sw v0,16(s8)bfc001e4lw v0,16(s8)bfc001e8slti v0,v0,5bfc001ecbnez v0,bfc001b8bfc001f0nopClick any highlighted line to see it light up in the other listing, and read what it does.
What to notice
i < 5 is two instructions: slti v0,v0,5 puts 1 or 0 in v0, and
bnez v0 branches on it. No flags, no compare-and-branch. Notice also that
the loop is entered through its test: b bfc001e4 jumps there first.
fib.c uses the same shape for n < 3: slti v0,v0,3 then beqz v0, at
fib_o0.dump bfc0028c. Once you know the pair, you can read every loop.
Go deeper: why MIPS refused a flags register, and what that means for Carolyne
A flags register is written by almost every arithmetic instruction and read by every conditional branch. That makes it a hidden dependency: instruction after instruction touches the same register, and a pipelined or out-of-order machine has to track it like any other. MIPS’s designers wanted no hidden state at all. A comparison result in an ordinary register is renamed, tracked and forwarded like every other value, with no special case.
Carolyne makes the point concrete. x86, which Carolyne also targets, needs a
one-register class for FLAGS, written as a second destination by most
instructions. MIPS needs nothing of the kind: slt is just an ALU instruction
with one destination. The engine that runs both does not care, which is the
whole thesis, but the MIPS description is shorter for it.
Can you now explain it?
A comparison is a value. slt writes 1 or 0, and a branch tests it. Signed or unsigned is the instruction's choice, and it only matters when bit 31 is set. Every other comparison is a swap, an xori, or a compare with 1 or $zero.
If you remember only three things
- No flags: the answer lives in a register. slt rd,rs,rt gives 1 or 0. chapter 5's beq/bne then compare that register with $zero.
- Bit 31 decides signed versus unsigned. 0xFFFFFFFF is −1 to slt and 4294967295 to sltu. The immediate forms sign-extend first, even sltiu.
- Six comparisons from one. Swap for greater-than, xori 1 for the negations, sltiu rd,a,1 for a == 0, sltu rd,$zero,a for a != 0.
Now try to explain this one yourself: what does sltu $t0,$zero,$t1
compute, for every possible value of $t1? Say why the unsigned form is the
right one here.
Go deeper: a model answer, once you have tried
It asks “is 0 < t0becomes 1 exactly whent0,t1gives 0 for every negative$t1`, because a
negative is less than 0 when read signed. The unsigned reading is what makes
“not zero” the same as “greater than zero”.
Check your understanding
0/5 answered1.How do you compute `a > b` (signed) into $t0, with a in $t1 and b in $t2?
2.$t0 holds 0x80000000 and $t1 holds 1. What do `slt $t2,$t0,$t1` and `sltu $t3,$t0,$t1` give?
3.What does GCC emit for `if (n < 3) …` with n in v0?
4.What does `slti $t0,$t1,-1` compare $t1 against?
5.`sltiu $t0,$t1,1` is a common idiom. What does it compute?