Home > Enterprise >  What is happening here in this divu with three operands?
What is happening here in this divu with three operands?

Time:10-08

I have the following line in a C code

i = low   (1664525*(unsigned)high   22695477*(unsigned)low) % (high-low 1);

The code in MIPS that I have for this line is as follows

lw      $3,40($fp)
li      $2,1638400              # 0x190000
ori     $2,$2,0x660d
mult    $3,$2
mflo    $2
lw      $4,36($fp)
li      $3,22675456       # 0x15a0000
ori     $3,$3,0x4e35
mult    $4,$3
mflo    $3
addu    $2,$2,$3
lw      $4,40($fp)
lw      $3,36($fp)
nop
subu    $3,$4,$3
addiu   $3,$3,1
divu    $0,$2,$3
bne     $3,$0,1f
nop
break   7
mfhi    $2
move    $3,$2
lw      $2,36($fp)
nop
addu    $2,$3,$2
sw      $2,8($fp)

Here, for reference, the value of "low" is stored at 36($fp), and the value of "high" is stored at 40($fp). I am able to understand how the code is working up until this line

divu $0, $2, $3

Here, I am a bit confused about what is happening as I have only seen divu commands with two operands. Additionally, I later see that they are obtaining the mod of the division(in this line)

mfhi    $2

But, shouldn't we be getting the quotient, using "mflo" instead? Can someone help me figure out how this part of the code is working?

CodePudding user response:

For ~current MIPS64:

According to official MIPS manuals you are looking at the assembly source for a release 6 MIPS64, which added a new instructions to deal with divide and modulo operations (DIV/MOD, DIVU/MODU, DDIV/DMOD, DDIVU/DMODU).

For example for DIVU (taken from the manual):

divu rd,rs,rt

DIVU: GPR[rd] <- sign_extend.32( divide.unsigned( GPR[rs], GPR[rt] )

DIVU performs an unsigned 32-bit integer division, and places the 32-bit quotient result in the destination register.

The 2-parameter couterparts of pre release 6 instructions now yield an reserved instruction exception

Now the interesting part:

According to the section on Availability and Compatibility from the manual "Some assemblers accept the pseudo-instruction syntax DIV rd,rs,rt and expand it to do DIV rs,rt;MFHI rd. Phrases such as “DIV with GPR output” and “DIV with HI/LO output” may be used when disambiguation is necessary."

The code you posted does use MFLO $2 to obtain the remainder after doing the division so I believe the assembler is using MIPS64 release 6 source to target a pre release 6. The MFHI $0 may be optimized out and now you can obtain the quotient by using MFLO $2

So I believe you may just see the instruction as divu $2, $3, with the pre release 6 HI/LO semantics


This was my original answer which I think make sense mostly for MIPS32 simulators:

divu r1, r2, r3 is a pseudoinstruction. It is expanded like this in Mars:

  bne r3, $0, skip
  nop
  break
skip:
  divu r2, r3
  mflo r1

so it basically checks whether the division can be done (skips the break otherwise) and then performs a divu and gets the quotient in r1

As you are using $0 as the target register it makes not much sense to use the pseudoinstruction. It will issue mflo $0.

You may probably just use divu $2, $3 if you dont mind $3 being 0

As noted in the comments, mfhi $2 is then used to get the remainder as expected.

CodePudding user response:

You're looking at GCC output, using syntax that GAS and clang will accept. Assembly source syntax is defined by the tool and can differ from what the vendor uses in their ISA manuals. The manuals nail down how the binary machine-code works, but different assemblers/disassemblers can vary the syntax if they choose.

divu $zero, src1, src2 is the GAS / clang syntax for the bare machine instruction, with implicit outputs in LO (quotient) and HI (remainder). In MARS/SPIM, divu srcr1, src2 is how you'd write that; MARS/SPIM and other MIPS simulators with built-in assemblers are more like classic MIPS assemblers, not the GNU toolchain. (TODO: test what MARS accepts, and check the machine code.)


With a first operand other than $zero, it's a destination for a pseudo-instruction. Clang expands it to trap on division by zero and mflo into the destination. (I don't have MIPS Binutils to test with.)

For example, (keeping in mind this is clang so it's targeting real commercial MIPS with a branch-delay slot, not MARS's default of a simplified MIPS with branch delay disabled.)

$ clang -Wall -target mips -march=mips32r5 -O2 -c mips-div.s
$ llvm-objdump -d mips-div.o
...
00000000 <quot>:              
# divu  $v0, $4, $5 in the asm source assembled to
       0: 14 a0 00 02   bnez    $5, 12 <quot 0xc>  # skip over the break 7 on $a1!=0
       4: 00 85 00 1b   divu    $zero, $4, $5      #  in the branch-delay slot of bnez
       8: 00 07 00 0d   break   7 <quot>
       c: 00 00 10 12   mflo    $2                 # $v0
 # end of pseudocode expansion for  divu  $v0, $4, $5
...

      34: 00 85 00 1b   divu    $zero, $4, $5     # same source syntax

Strangely, clang at least treats div $4, $5 as div $4, $4, $5, expanding it, instead of just assembling it to the bare 2-operand machine instruction. But clang supports this as a general pattern: addu $a0, $a1 assembles to addu $4, $4, $5 as if you'd written addu $a0, $a0, $a1. Or as if MIPS supported a 2-operand "destructive" form that updates the destination. So with the 2-operand syntax for divu, it seems this expansion takes precedence over the actual machine instruction.

Again, seems like a strange design choice for clang, or more likely for the GNU assembler many years ago. But given that fact, they did need a syntax that unambiguously indicates the bare machine instruction. They chose a $zero destination.

It is an R-type instruction, and the field in the machine instruction that would normally the destination is required to be zero for div and divu. So perhaps this makes text parsing / formatting easier in the assembler and disassembler.

The compiler knows how to lay out branches itself, and can schedule mflo where appropriate, so it uses the divu $zero, src, src version.


MIPS32/64 r6 has a 3-operand machine instruction, replacing the old

The first section of this answer is about MIPS before that, from classic MIPS I to MIPS32/64 r5.

  • In MIPS32r6 / MIPS64r6, divu is a 3-operand instruction with a different opcode than before. It writes the quotient to the first operand. (LO and HI don't exist, mflo/mfhi were removed in R6.)

    # clang -target mips -march=mips32r6 div-mips.s && llvm-objdump -d div-mips.o
    00 85 00 9b   divu    $zero, $4, $5        # $a0 / $a1 discarding the result
    
  • In earlier revisions of MIPS, divu is documented as a 2-operand instruction where the implicit destination is LO (quotient) and HI (remainder). This form was removed in Release 6.

    # clang -target mips -march=mips32r5
    00 85 00 1b   divu    $zero, $4, $5     # same in the asm source
    

MIPS32/64 r6 in 2014 reorganized some opcodes as well as removing some rarely used ones, and is not binary compatible in general.

Current versions of tools that you're using, like GCC, post-date that. But MARS and SPIM haven't been updated for it AFAIK, and most people wouldn't want them to be unless it was an optional feature. Typical university courses that use MIPS or a MIPS-like ISA for teaching are more like MIPS I or II from the mid to late 1980s.

But I suspect clang is just being compatible with GNU Binutils, and that it's been using the 3-operand $zero form to mean the bare instruction since long before 2014 when MIPS32/64 r6 was new. But I'd be curious if anyone has MIPS Binutils, especially an old version, that could test assembling / disassembling.

See also the MIPS64r6 ISA manual from mips.com. I'm looking at v6.06, page 194 from 2016 for the entry for the new form of divu. (I don't expect any further updates to MIPS manuals; the ISA is commercially dead-ended.)

Availability and Compatibility:
These instructions are introduced by and required as of Release 6.

Release 6 divide instructions have the same opcode mnemonic as the pre-Release 6 divide instructions (DIV, DIVU, DDIV, DDIVU). The instruction encodings are different, as are the instruction semantics: the Release 6 instruction produces only the quotient, whereas the pre-Release 6 instruction produces quotient and remainder in HI/LO registers respectively, and separate modulo instructions are required to obtain the remainder.

The assembly syntax distinguishes the Release 6 from the pre-Release 6 divide instructions. For example, Release 6 “DIV rd,rs,rt” specifies 3 register operands, versus pre-Release 6 “DIV rs,rt”, which has only two register arguments, with the HI/LO registers implied. Some assemblers accept the pseudo-instruction syntax “DIV rd,rs,rt” and expand it to do “DIV rs,rt;MFHI rd”. Phrases such as “DIV with GPR output” and “DIV with HI/LO output” may be used when disambiguation is necessary.

That documentation doesn't exactly match what GCC and clang do, but assembly language text source syntax is really the choice of the tool, with the vendor docs and ISA really only having full control over the rules for the machine code.

There's also a modu instruction on MIPS32/64 r6, which you can use instead


shouldn't we be getting the quotient, using "mflo" instead?

% (high-low 1) is a remainder operation, not a / operator where you'd want the quotient.

  • Related