Skip to content
Contents

Comparing: slt and its family

Section 3.4 10 min read

  • set on less than
  • signed versus unsigned comparison
  • greater, less-or-equal, greater-or-equal by swapping and xori
  • the slti + bnez idiom

Checked against: MIPS32 Vol. II: SLT, SLTU, SLTI, SLTIU carolyne_ref/dumps/hello_o0.dump (bfc001ac–bfc001f0, the for loop) carolyne_ref/dumps/fib_o0.dump (bfc0028c) carolyne_ref/programs/hello.c

Before you start

  • §3.1: two's complement, the two readings of one bit pattern
  • §3.2: compare immediates are sign-extended

By the end, you can

  • Compute any of the six comparisons with slt, sltu and xori
  • Predict slt versus sltu on a value whose bit 31 is set
  • Read the compiler's slti + bnez as a C condition
  • Say why MIPS has no flags register

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.

instructioncomparesreadingsecond operand
slt rd,rs,rtrs < rtsigneda register
sltu rd,rs,rtrs < rtunsigneda register
slti rt,rs,immrs < immsigneda sign-extended 16-bit constant
sltiu rt,rs,immrs < immunsigneda 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?

bit 31bit 27bit 23bit 19bit 15bit 11bit 7bit 3
a (rs)

-1 (signed) · 0xffffffff · 4294967295 if read unsigned

b (rt)

1 (signed) · 0x00000001

instructionreads the bits asthe questionresult
sltsigned-1 < 1?1
sltuunsigned4294967295 < 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 →0xbfc000000x3c191000_start:lui $t9,0x1000
0xbfc000040x37390ff0ori $t9,$t9,0xff0
0xbfc000080x2408ffffli $t0,-1
0xbfc0000c0x24090001li $t1,1
0xbfc000100x0109502aslt $t2,$t0,$t1
0xbfc000140xaf2a0004sw $t2,4($t9)
0xbfc000180x24180020li $t8,32
0xbfc0001c0xaf380000sw $t8,0($t9)
0xbfc000200x0109582bsltu $t3,$t0,$t1
0xbfc000240xaf2b0004sw $t3,4($t9)
0xbfc000280xaf380000sw $t8,0($t9)
0xbfc0002c0x290c0005slti $t4,$t0,5
0xbfc000300xaf2c0004sw $t4,4($t9)
0xbfc000340xaf380000sw $t8,0($t9)
0xbfc000380x2d0dffffsltiu $t5,$t0,-1
0xbfc0003c0xaf2d0004sw $t5,4($t9)
0xbfc000400x240e0000li $t6,0
0xbfc000440xaf2e0008sw $t6,8($t9)

registers — read · written this tick

HI 0x00000000LO 0x00000000PC 0xbfc00000

console (what the doors printed)

▮
cycle 0/181 tick = 1 instruction (not a cycle)

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 wantwritewhy it works
a < bslt rd,a,bthe instruction itself
a > bslt rd,b,aa > b is the same as b < a
a >= bslt rd,a,b then xori rd,rd,1NOT (a < b); xori with 1 flips a 0/1
a <= bslt rd,b,a then xori rd,rd,1NOT (b < a)
a == 0sltiu rd,a,1unsigned, only 0 is below 1
a != 0sltu rd,$zero,aunsigned, 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

hello.c
1for (i = 0;
2 i < 5;
3 i++) {
4 print_int(i * i);
5 print_char('\n');
6}
MIPS (-O0)
bfc001acsw zero,16(s8)
bfc001b0b bfc001e4
bfc001b4nop
bfc001b8lw v0,16(s8)
bfc001bcmul v0,v0,v0
bfc001c0move a0,v0
bfc001c4jal bfc000ec <print_int>
bfc001c8nop
bfc001ccli a0,10
bfc001d0jal bfc000b0 <print_char>
bfc001d4nop
bfc001d8lw v0,16(s8)
bfc001dcaddiu v0,v0,1
bfc001e0sw v0,16(s8)
bfc001e4lw v0,16(s8)
bfc001e8slti v0,v0,5
bfc001ecbnez v0,bfc001b8
bfc001f0nop

Click 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

  1. 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.
  2. Bit 31 decides signed versus unsigned. 0xFFFFFFFF is −1 to slt and 4294967295 to sltu. The immediate forms sign-extend first, even sltiu.
  3. 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 < t1,unsigned?".Readunsigned,everyvalueexcept0isgreaterthan0.So‘t1, unsigned?". Read unsigned, every value except 0 is greater than 0. So `t0becomes 1 exactly whent1‘isnotzero,and0whenitis.Thatisthe‘snez‘idiom,"setifnotequaltozero".Thesignedformwouldfail:‘sltt1` is not zero, and 0 when it is. That is the `snez` idiom, "set if not equal to zero". The signed form would fail: `slt t0,zero,zero,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 answered
  1. 1.How do you compute `a > b` (signed) into $t0, with a in $t1 and b in $t2?

  2. 2.$t0 holds 0x80000000 and $t1 holds 1. What do `slt $t2,$t0,$t1` and `sltu $t3,$t0,$t1` give?

  3. 3.What does GCC emit for `if (n < 3) …` with n in v0?

  4. 4.What does `slti $t0,$t1,-1` compare $t1 against?

  5. 5.`sltiu $t0,$t1,1` is a common idiom. What does it compute?


Next: Chapter 4 — memory is a long row of bytes.

Where this comes back

One later section builds on this one.