Written twice, computed once
gcc 2.9 runs common-subexpression elimination before it allocates registers. When the same expression appears more than once in a straight run of code, and nothing between the two spellings could have changed its inputs, the compiler evaluates it once and keeps the result in a register.
That matters for decomp because it removes information. A target that computes a value once and reads it twice is equally consistent with C that named the value in a local and C that just wrote the expression out again. Both spellings reach the optimizer as the same internal graph, so both produce the same bytes. You get to pick whichever reads better.
The elimination is not unconditional. A bl in between forces a reload, because the callee could have written anywhere. So does a store through the same pointer the value was loaded from. A store through a different pointer does not: gcc 2.9 treats two distinct pointers as distinct objects and keeps the cached value.
Here is spread, which uses a | b three times inside one ternary:
0 orr r0, r1
2 sub r1, r2, r0
4 cmp r0, r2
6 ble 10 ~>
8 sub r1, r0, r2
10 ~>mov r0, r1
12 bx lr
One orr. The result sits in r0 for the rest of the function, and all three appearances read it: the sub r1, r2, r0 that builds the else-value, the cmp r0, r2 that tests it, and the sub r1, r0, r2 on the then-side.
Now the same function with the expression pulled into a local first:
0 orr r0, r1
2 sub r1, r2, r0
4 cmp r0, r2
6 ble 10 ~>
8 sub r1, r0, r2
10 ~>mov r0, r1
12 bx lr
Byte for byte, the same function. The local vanished into a register the compiler was going to allocate anyway.
Your target does the same trick with a subscripted load feeding a multiply. The address arithmetic, the load and the mul all appear once; look at how many places downstream read that single result.
Your task
Write func_083b8230 to reproduce the target assembly.