2

I'm curious as to why we are not allowed to use registers as offsets in MIPS. I know that you can't use registers as offsets like this: lw $t3, $t1($t4); I'm just curious as to why that is the case.

Is it a hardware restriction? Or simply just part of the ISA?


PS: if you're looking for what to do instead, see Load Word in MIPS, using register instead of immediate offset from another register or look at compiler output for a C function like int foo(int *arr, int idx){ return arr[idx]; } - https://godbolt.org/z/PhxG57ox1

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
KnowingQuasar
  • 23
  • 1
  • 3
  • 1
    That's the way they designed it. You'll have to ask the designers why they decided against it. – Raymond Chen Oct 22 '17 at 20:57
  • I think later MIPS *does* have indexed loads (as well as PC-relative). http://www.tkt.cs.tut.fi/kurssit/3200/S05/Luennot/Lec_notes05/Tkt_kpl5a.pdf. Probably supporting PC-relative meant they needed a 2-register adder in the AGU, so might as well also allow 2 arbitrary registers instead of just PC + register. [First-gen MIPS I had only *one* addressing mode: base + displacement](https://en.wikipedia.org/wiki/MIPS_architecture#Loads_and_stores) (which could be zero, of course). So obviously that makes decoding and address-generation simpler. – Peter Cordes Oct 22 '17 at 21:23
  • Wikipedia says [MIPS IV added indexed addressing modes](https://en.wikipedia.org/wiki/MIPS_architecture#MIPS_IV). Hmm, but they only mention it for FP loads/stores. [A current MIPS instruction-set quick ref](http://cdn2.imgtec.com/documentation/MD00565-2B-MIPS32-QRC-01.01.pdf) only lists the `off(Rs)` modes for integer loads/stores, including unaligned and LL/SC. – Peter Cordes Oct 22 '17 at 21:28
  • [`lwpc` is new in MIPS release 6](http://cdn2.imgtec.com/documentation/MIPS_Architecture_MIPS32_InstructionSet_%20AFP_P_MD00086_06.05.pdf), and uses the usual `sign_extend( offset << 2 )` encoding, but with `PC` instead of a GPR. That same doc does document the indexed FP loads (like `lwxc1`, which uses `GPR[base] + GPR[index]`). So yes, the MIPS ISA still doesn't include indexed integer loads. You have to do the address math yourself. At least there's a shift-and-add instruction (`lsa`) for scaled-index address generation (e.g. for `int[]`). – Peter Cordes Oct 22 '17 at 21:50
  • 1
    I'd guess the motivation was part of the whole RISC philosophy. MIPS was one of the very first RISC designs. – Peter Cordes Oct 22 '17 at 21:54
  • Also related: [Indexed addressing mode and implied addressing mode](https://stackoverflow.com/q/47868145) - another ISA-design question – Peter Cordes Dec 12 '22 at 18:46

1 Answers1

3

I'm curious as to why we are not allowed to use registers as offsets in MIPS.

I'm not sure if you mean "why does MIPS assembly not permit you to write it this form" or "why does the underlying ISA not offer this form".

If it's the former, then the answer is that the base ISA doesn't have any machine instructions that offers that functionality, and apparently the designers didn't decide to offer any pseudo-instruction that would implement that behind the scenes.2

If you're asking why the ISA doesn't offer it in the first place, it's just a design choice. By offering fewer or simpler addressing modes, you get the following advantages:

  • Less room is needed to encode a more limited set of possibilities, so you save encoding space for more opcodes, shorter instructions, etc.
  • The hardware can be simpler, or faster. For example, allowing two registers in address calculation may result in:
  • The need for an additional read port in the register file1.
  • Additional connections between the register file and the AGU to get both registers values there.
  • The need to do a full width (32 or 64 bit) addition rather than a simpler address-side + 16 bit-addition for the offset.
  • The need to have a three-input ALU if you want to still want to support immediate offsets with the 2-register addresses (and they are less useful if you don't).
  • Additional complexity in instruction decoding and address-generation since you may need to support two quite different paths for address generation.

Of course, all of those trade-offs may very well pay off in some contexts that could make good use of 2-reg addressing with smaller or faster code, but the original design which was heavily inspired by the RISC philosophy didn't include it. As Peter points out in the comments, new addressing modes have been subsequently added for some cases, although apparently not a general 2-reg addressing mode for load or store.

Is it a hardware restriction? Or simply just part of the ISA?

There's a bit of a false dichotomy there. Certainly it's not a hardware restriction in the sense that hardware could certainly support this, even when MIPS was designed. It sort of seems to imply that some existing hardware had that restriction and so the MIPS ISA somehow inherited it. I would suspect it was much the other way around: the ISA was defined this way, based on analysis of how likely hardware would be implemented, and then it became a hardware simplification since MIPS hardware doesn't need to support anything outside of what's in the MIPS ISA.


1 E.g., to support store instructions which would need to read from 3 registers.

2 It's certainly worth asking whether such a pseudo-instruction is a good idea or not: it would probably expand to an add of the two registers to a temporary register and then a lw with the result. There is always a danger that this hides "too much" work. Since this partly glosses over the difference between a true load that maps 1:1 to a hardware load, and the version that is doing extra arithmetic behind the covers, it is easy to imagine it might lead to sup-optimal decisions.

Take the classic example of linearly accessing two arrays of equal element size in a loop. With 2-reg addressing, it is natural to write this loop as two 2-reg accesses (each with a different base register and a common offset register). The only "overhead" for the offset maintenance is the single offset increment. This hides the fact that internally there are two hidden adds required to support the addressing mode: it would have simply been better to increment each base directly and not use the offset. Furthermore, once the overhead is clear, you can see that unrolling the loop and using immediate offsets can further reduce the overhead.

BeeOnRope
  • 60,350
  • 16
  • 207
  • 386
  • MIPS assembly has more pseudo-instructions than most, and [the usual register-names convention](http://www.cs.uwm.edu/classes/cs315/Bacon/Lecture/HTML/ch05s03.html) has a register used as a temporary by pseudo-instructions (`$1` aka `$at`) in hand-written asm. It wouldn't have surprised me at all if there was a load-indexed pseudo-instruction that did address-math into a temporary and then used `lw`. – Peter Cordes Oct 23 '17 at 00:51
  • @PeterCordes so there are pseudo-instructions that expand to more than 1 underlying machine instruction? – BeeOnRope Oct 23 '17 at 00:53
  • The register read port argument seems the most likely. I had been thinking of AGU complexity and decode (MIPS literally only has one addressing mode for integer loads/stores: GP register + `imm16`). And BTW, the FP indexed loads/stores don't support an immediate, only 2 registers. That's fine and only slightly less useful for a lot of use-cases like walking through an array. – Peter Cordes Oct 23 '17 at 00:54
  • Yeah, the most obvious ones being `la` and `li` for 32-bit immediates, that expand to `lui` / `ori` (or `addui`). That's pretty typical for most RISC architectures, but MIPS goes beyond that. https://www.cs.umd.edu/class/sum2003/cmsc311/Notes/Mips/pseudo.html says that `lw $rt, big($rs)` (with an offset > 16 bits) is supported, using a temporary register. Their sequence seems unlikely, since you can do it in 3 instructions using `lower( big )` as the offset for the final `lw`, but yes, MIPS assembly is crazy like that. – Peter Cordes Oct 23 '17 at 00:58
  • [Wikibooks gives another example](https://en.wikibooks.org/wiki/MIPS_Assembly/Pseudoinstructions): `blt $8, $9, label` becomes `slt $1, $8, $9` / `bne $1, $0, label`. (Where `$1` is the `$at` "assembler temporary" register, and `$0` is the architectural zero register.) – Peter Cordes Oct 23 '17 at 00:59
  • Well it certainly doens't have to be any particular argument that is the "right" one: most likely many or all contribute. For example, the encoding space and decoding complexity arguments are pretty much guaranteed to be true. Now it might not gain you much if say you never end up using all your encoding space, or Moore's law reduces the silicon cost of a more complex decoder to close to zero, but they certainly contribute. The register port issue could be the biggest blocker since it's kind of a cross-cutting issue that could have a cost out of proportion to the benefit. – BeeOnRope Oct 23 '17 at 00:59
  • @PeterCordes - I see, thanks. So the `1:1` argument isn't really correct as written. Is it true that instructions are at least true instructions (1:1) or pseudo-instructions? Are there pseudo-instructions that expand to a variable number of machine instructions? Then you could say that since some `lw` forms map directly to an instruction, you cannot also have forms that map to multiple instructions (but it leaves open the question of why the didn't offer a pseudo-instruction like `lw2` or whatever that offers this). – BeeOnRope Oct 23 '17 at 01:02
  • 1
    `li` with small constants is only one instructions, same for `lw` with small offsets. `lw` is the regular load-word machine instruction. I think some MIPS assemblers have a no-pseudo mode which you could use if you want to make sure you didn't unintentionally use multiple instructions. – Peter Cordes Oct 23 '17 at 01:04
  • I almost left out the register port argument though: since it doesn't actually require two register ports for reads at all (since the destination is a write), so "load" with 2 regs by itself actually makes quite a bit of sense in a classic RISC 3-arg architecture. It's only store that messes things up. Certainly you could imagine offering a 2-reg load, but only a 1-reg store, if such non-orthogonality wasn't anathema to the core principles of RISC. – BeeOnRope Oct 23 '17 at 01:05
  • MIPS was a *very* early RISC design. Apparently Stanford MIPS and Berkeley RISC were the two pioneering projects, before they knew how reduced was too reduced. Back then on such a limited transistor budget, having only one addressing mode is certainly valuable for decode. There are some SO questions about the internal control signals inside a MIPS CPU, so some computer architecture courses actually teach that because it's that simple. (And then ask questions like "what would you need to change to support feature X?", e.g. indexed loads or stores I guess.) Good point about only stores. – Peter Cordes Oct 23 '17 at 01:07
  • 2
    But anyway, I was thinking about the register-read port argument as a good explanation for why MIPS *still* doesn't have indexed integer load/store. There's plenty of coding space left, and MIPS Release 6 re-assigned some opcodes to make room for new instructions (e.g. branches without a branch-delay slot)! So it's not at all like x86 as far as coding-space and backwards-compat Flipping through the instruction-set reference manual (searching for `index`) was interesting. (And ports explains why it has indexed FP load/store, because that's the other register file.) – Peter Cordes Oct 23 '17 at 01:09
  • Historical effects might also explain MIPS pseudo-instructions. Since RISC was so new, early assemblers maybe tried to hide the RISC-ness. (Total guess). – Peter Cordes Oct 23 '17 at 01:10
  • 1
    @PeterCordes - yes, good point about today's reasons. A lot of the other costs reduce with time, but the cost of adding an extra read-port is still potentially prohibitive, since it can be a large part of the area of simple chips, it is inherently part of most of the important critical paths and can limit the cycle time, and can have effects on things like the renamer (if a chip is fancy enough to use one). I added a way-too-long footnote about whether a pseudo-instruction would have been a good idea. – BeeOnRope Oct 23 '17 at 01:24
  • 1
    ... the logic of which isn't really consistent with some existing pseudo-instructions like `div` and `rem` which definitely hide stuff that might waste effort (e.g., if you needed both of those you are much better writing it yourself and saving a `div`). Actually `div` answers a question I had above: they apparently did have pseudo-instructions with the same mnemonic that were either "pseudo" or "real" depending on, for example, the number of args. Interesting. Too sneaky for my liking though. – BeeOnRope Oct 23 '17 at 01:30
  • 2
    I'm not a fan of MIPS pseudo-instructions. If you have an architectural register "intended" for use as a hidden temporary, you may have too many registers... (Although honestly it's an interesting design to have a couple architectural registers that the kernel is allowed to asynchronously clobber for fast interrupts handlers, including TLB-miss handlers. It means that interrupt handling can be architecturally simpler; IDK how it works, but maybe it puts the old PC in one of those registers.) – Peter Cordes Oct 23 '17 at 01:37
  • Apparently microcontrollers from Microchip with DSP-extensions did add indexed loads like `LWX` but not corresponding indexed stores: [How to load and store word from/to address that index is in a register, MIPS](https://stackoverflow.com/a/70676439). Embedded designers apparently cared more about practicality than RISC philosophy / orthogonality. – Peter Cordes Jan 12 '22 at 13:14