6: Branches, pt. 2

(0 comments)

Let's continue REing the branch instructions.

0xe0   2897    2330     branch if predicate
0xe1   1036     913     branch ?
0xe2   1707    1188     branch if not predicate
0xe3    120      89     ??? [XXX]
0xe4   2020     695     call if predicate
0xe6     92      84     call if not predicate
0xe8    694     552     ret
0xea    248       1     abra
0xef  23932   13521     bnop
0xf0    161     329     ??? [XXX]
0xff    918     642     exit [intr] imm16

The next thing to look at is clearly opcode e1. Let's look for some usage examples.

00000360: 6cce400f  C  add $a25 $a25 0x1
00000360: 6a06405f     mov $sr96 $a25
00000360: 65d00003     mov $a26 0x3
00000360: e1ffffc0     bra1 0x35f [unknown: 000001c0]
00000361: e1000fa0     bra1 0x368 [unknown: 000001a0]
00000361: 6bcc00af     mov $a25 $x48
00000361: 6bcc80af     mov $a25 $x50
00000361: e1000fa0     bra1 0x368 [unknown: 000001a0]
00000362: ef0001ff     bnop
00000362: e1000da0     bra1 0x368 [unknown: 000001a0]
00000362: 6bcd00af     mov $a25 $x52
00000362: 6bcd80af     mov $a25 $x54
00000363: e1000ba0     bra1 0x368 [unknown: 000001a0]
00000363: ef0001ff     bnop
00000363: e1000ba0     bra1 0x368 [unknown: 000001a0]
00000363: 6bce00af     mov $a25 $x56
00000364: 6bce80af     mov $a25 $x58
00000364: e10009a0     bra1 0x368 [unknown: 000001a0]
00000364: ef0001ff     bnop
00000364: e10009a0     bra1 0x368 [unknown: 000001a0]
00000365: 6bcf00af     mov $a25 $x60
00000365: 6bcf80af     mov $a25 $x62
00000365: e10007a0     bra1 0x368 [unknown: 000001a0]
00000365: ef0001ff     bnop
00000366: fff90000   B exit intr 0
00000366: ef0001ff     bnop
00000366: ef0001ff     bnop
00000366: ef0001ff     bnop
00000367: ef0001ff     bnop
00000367: 4fffffff     anop
00000367: bf000007     vnop
00000367: ef0001ff     bnop
00000368: 7e967f17   B shr $a18 $a25 -0x1e
00000368: 63d6bfff     xor $a26 $a26 -0x1
00000368: e8ffffff     ret
00000368: 42ce7447     and $a25 $a25 $a26

Hm. Lots of branches, few other instructions. Looks like a switch() statement that reads from the right $x register. Note how the delay slots are used. Since the branch instructions are mostly identical, they must use some sort of counter that's incremented/decremented as a side effect of every branch. It's very likely that this counter is $sr96.

Digging around in other pieces confirms this - however, it seems that the counter can be any of $sr96-$sr99, and it's selected by bits 0-1 and bits 3-4 (both set to the same value) of the opcode. In addition, the first branch in series has bits 5-8 set to 1110, while subsequent ones have 1101. Note that, if 5-8 selects a $c bitfield as usual, 1110 corresponds to the always-0 bit, while 1101 corresponds to the unknown flag that's always been set by branches so far. Seems we'll soon figure out its purpose.

So, let's run an 0xe10009c0 branch with $sr96 set to 0xdead.

Branch not taken, $sr96 is now set to 0xdeac. Bit 13 of $c0 is not set.

How about the same instruction with $sr96 set to 0? Branch not taken, $sr96 set to 0, bit 13 of $c0 set.

0xe10009e0 with $sr96 set to 0xdead: branch taken, $sr96 is 0xdeac, $c0 bit 13 is not set.

0xe10009e0 with $sr96 set to 1: branch taken, $sr96 is 0, $c0 bit 13 is set.

I rechecked the e0 opcode just in case - it has no effect on $sr96, and $c0 bit 13 is always set, regardless of $sr96 value.

So it seems that e1 does two independent operations: it executes the normal e0 conditional branch, then it decrements $sr96-$sr99 if non-0, then sets bit 13 $c0 to 1 if it is now 0, and to 0 otherwise. I suppose that explains the sequence, more or less...

Let's just look which bitfield determines the $sr96-$sr99 register used.

Apparently, like for $c, bits 0-1 determine the output $sr to be used, bits 3-4 determine the input. Bit 2 doesn't disable the $sr write, however.

We also see that e3 is used a bit - there's a good chance it could be just e1 with a negated predicate. Again, a test confirms that.

If the instruction set is really that orthogonal, e5 and e7 could be calls with $sr96+ decrease. Another test, another confirmation.

How about return? There's a tiny chance e9 could be a ret with $sr96 manipulation...

Nope, not this time.

Looking around the code for e3 usage also shows it's used as a loop, with the $sr being a "remaining iterations" counter. We'll call $sr96-$sr99 the loop registers for that reason: $l0-$l3.

Time for f0 opcode. We've seen it used in the "clear all registers" sequence already, and it seems to be used 4 times to zero out 4 registers. Seems obvious. Let's try 0xf007dead.

0000f580: 0000dead 00000000 00000000 00000000

Yeah. And let's try 0xf018dead:

0000f580: 00000000 00000000 00000000 0000dead

And 0xf0180000:

0000f680: 00008000 00008000 00008000 0000a000

Interesting one. Seems the $cX/$lX registers are sort of tied to each other, with bit 13 of $cX being 1 iff $lX is 0. However, it's a loose coupling - we've already seen that a direct mov from $a can mess that up. This is probably what the "pointless" always-false first branch is for, and why the loop/switch counter has 1 added to it before the sequence.

0xe0   2897    2330     branch if predicate
0xe1   1036     913     branch if predicate + loop
0xe2   1707    1188     branch if not predicate
0xe3    120      89     branch if not predicate + loop
0xe4   2020     695     call if predicate
0xe5      -       -     call if predicate + loop
0xe6     92      84     call if not predicate
0xe7      -       -     call if not predicate + loop
0xe8    694     552     ret
0xea    248       1     abra
0xef  23932   13521     bnop
0xf0    161     329     mov $l imm16
0xff    918     642     exit [intr] imm16

Elapsed time: 2h

Currently unrated

Comments

There are currently no comments

New Comment

required

required (not published)

optional

required