Search code examples
coptimizationmipsgnu-assembler

How do I force GAS to generate output identical to input?


I want to write code that has a public C interface, but is otherwise implemented entirely in MIPS, mainly as a learning exercise. However, I'm stuck fighting GAS because it seems to assume it knows better than I do.

To illustrate, let's say I want to implement the following in MIPS:

int bar(void)
{
    return (4 / 2);
}

As I mentioned above, I want to be able to call ASM routines from C, so we'll need a C code file, bar.c, as well as the MIPS assembly in bar.S.

bar.c:

extern int bar(void);

void _start()
{
    int foo = bar();
}

bar.S:

.global bar .text

bar:
    addi $2, $0, 4
    addi $3, $0, 2
    div $2, $3
    mflo $2
    jr $31
    addu $3, $0, $0

mipsel-none-elf-gcc bar.c bar.S -o bar.elf -ffreestanding -nostdinc -nostdlib successfully compiles this, but mipsel-none-elf-objdump -d bar.elf shows that GAS is messing with my bar() code:

00400050 <bar>:
  400050:       20020004        addi    v0,zero,4
  400054:       20030002        addi    v1,zero,2
  400058:       14600002        bnez    v1,400064 <bar+0x14>
  40005c:       0043001a        div     zero,v0,v1
  400060:       0007000d        break   0x7
  400064:       2401ffff        li      at,-1
  400068:       14610004        bne     v1,at,40007c <bar+0x2c>
  40006c:       3c018000        lui     at,0x8000
  400070:       14410002        bne     v0,at,40007c <bar+0x2c>
  400074:       00000000        nop
  400078:       0006000d        break   0x6
  40007c:       00001012        mflo    v0
  400080:       00001012        mflo    v0
  400084:       03e00008        jr      ra
  400088:       00000000        nop
  40008c:       00001821        move    v1,zero

I don't want the division checks, or delay slot fixups, or the assembler being "helpful" in any way here: I'm perfectly capable of handling those things myself if necessary. How do I tell GAS to just be a dumb assembler?


Solution

  • This one was solved in the comments. GAS accepts a 3-operand version of div where division checks are disabled if the first operand is $0. As for the delay slot, a .set noreorder directive prevents reordering of instructions (duh). Changing the assembly to:

    .set noreorder
    
    .global bar .text
    
    bar:
        addi $2, $0, 4
        addi $3, $0, 2
        div $0, $2, $3
        mflo $2
        jr $31
        addu $3, $0, $0
    

    produces the correct output:

    00400050 <bar>:
      400050:       20020004        addi    v0,zero,4
      400054:       20030002        addi    v1,zero,2
      400058:       0043001a        div     zero,v0,v1
      40005c:       00001012        mflo    v0
      400060:       03e00008        jr      ra
      400064:       00001821        move    v1,zero