1
votes

I think I need to paste the full code although it looks long.

I write a simple code for test.

#include <stdio.h>

int funadd(int a, int b){
  int x = 0;

  x = a + b;

  return x;
}

int fun(int a, int b){
  int y = 17;
  int returnvalue = 0;

  returnvalue = funadd(a, b);
  returnvalue = returnvalue - y;

  return returnvalue;
}

int main(){

  int a = 32;
  int b = 24;

  int c = 0;

  c = fun(a, b);

  printf("%d\n", c);

  return c;

}

After assembly:

    .file   1 "testfuncall.c"
    .section .mdebug.abi32
    .previous
    .nan    legacy
    .module fp=xx
    .module nooddspreg
    .abicalls
    .text
    .align  2
    .globl  funadd
    .set    nomips16
    .set    nomicromips
    .ent    funadd
    .type   funadd, @function
funadd:
    .frame  $fp,24,$31      # vars= 8, regs= 1/0, args= 0, gp= 8
    .mask   0x40000000,-4
    .fmask  0x00000000,0
    .set    noreorder
    .set    nomacro
    addiu   $sp,$sp,-24
    sw  $fp,20($sp)
    move    $fp,$sp
    sw  $4,24($fp)
    sw  $5,28($fp)
    sw  $0,8($fp)
    lw  $3,24($fp)
    lw  $2,28($fp)
    addu    $2,$3,$2
    sw  $2,8($fp)
    lw  $2,8($fp)
    move    $sp,$fp
    lw  $fp,20($sp)
    addiu   $sp,$sp,24
    jr  $31
    nop

    .set    macro
    .set    reorder
    .end    funadd
    .size   funadd, .-funadd
    .align  2
    .globl  fun
    .set    nomips16
    .set    nomicromips
    .ent    fun
    .type   fun, @function
fun:
    .frame  $fp,40,$31      # vars= 8, regs= 2/0, args= 16, gp= 8
    .mask   0xc0000000,-4
    .fmask  0x00000000,0
    .set    noreorder
    .cpload $25
    .set    nomacro
    addiu   $sp,$sp,-40
    sw  $31,36($sp)
    sw  $fp,32($sp)
    move    $fp,$sp
    .cprestore  16
    sw  $4,40($fp)
    sw  $5,44($fp)
    li  $2,17           # 0x11
    sw  $2,24($fp)
    sw  $0,28($fp)
    lw  $5,44($fp)
    lw  $4,40($fp)
    lw  $2,%got(funadd)($28)
    move    $25,$2
    .reloc  1f,R_MIPS_JALR,funadd
1:  jalr    $25
    nop

    lw  $28,16($fp)
    sw  $2,28($fp)
    lw  $3,28($fp)
    lw  $2,24($fp)
    subu    $2,$3,$2
    sw  $2,28($fp)
    lw  $2,28($fp)
    move    $sp,$fp
    lw  $31,36($sp)
    lw  $fp,32($sp)
    addiu   $sp,$sp,40
    jr  $31
    nop

    .set    macro
    .set    reorder
    .end    fun
    .size   fun, .-fun
    .rdata
    .align  2
$LC0:
    .ascii  "%d\012\000"
    .text
    .align  2
    .globl  main
    .set    nomips16
    .set    nomicromips
    .ent    main
    .type   main, @function
main:
    .frame  $fp,48,$31      # vars= 16, regs= 2/0, args= 16, gp= 8
    .mask   0xc0000000,-4
    .fmask  0x00000000,0
    .set    noreorder
    .cpload $25
    .set    nomacro
    addiu   $sp,$sp,-48
    sw  $31,44($sp)
    sw  $fp,40($sp)
    move    $fp,$sp
    .cprestore  16
    li  $2,32           # 0x20
    sw  $2,24($fp)
    li  $2,24           # 0x18
    sw  $2,28($fp)
    sw  $0,32($fp)
    lw  $5,28($fp)
    lw  $4,24($fp)
    lw  $2,%got(fun)($28)
    move    $25,$2
    .reloc  1f,R_MIPS_JALR,fun
1:  jalr    $25
    nop

    lw  $28,16($fp)
    sw  $2,32($fp)
    lw  $5,32($fp)
    lw  $2,%got($LC0)($28)
    addiu   $4,$2,%lo($LC0)
    lw  $2,%call16(printf)($28)
    move    $25,$2
    .reloc  1f,R_MIPS_JALR,printf
1:  jalr    $25
    nop

    lw  $28,16($fp)
    lw  $2,32($fp)
    move    $sp,$fp
    lw  $31,44($sp)
    lw  $fp,40($sp)
    addiu   $sp,$sp,48
    jr  $31
    nop

    .set    macro
    .set    reorder
    .end    main
    .size   main, .-main
    .ident  "GCC: (Debian 6.3.0-18+deb9u1) 6.3.0 20170516"

I realize after each function call, there is a lw $28,16($fp) instruction. But I don't see any code that would have stored a value there first in either the caller or callee.

I can read MIPS assembly. I know that lw is load word, and how $fp and $sp are frame pointer and stack pointer.

I just can't understand how it makes sense to load anything from 16($fp); it seems there is an uninitialized space.

I know $28 is $gp, and can see it being used as a GOT pointer to load function addresses before calls, but it seems nothing initializes that register either before its used in functions.

Does the MIPS calling convention require $28 to already be pointing at the GOT on function entry?

2
There's a sw $5,28($fp) earlier in funadd, before the reload of that register arg from the same address it was spilled to. You seem to have removed the stores from fun and main so of course the code looks like it's reading uninitialized memory. Unless the source is doing that, that's unlikely. In fun and main, I think $28 is a GOT pointer. - Peter Cordes
@PeterCordes I don't get what you said. Actually, fun and main doesn't store anyting to the 16($fp), but load 16($fp) to $28. Stackoverflow says I paste too much code so I remove some code, let me try to paste the full code. - 50u1w4y
Can you link the source on godbolt.org? Compile with -fverbose-asm to make GCC annotate each instruction with the C name of the operands. - Peter Cordes
@PeterCordes Hey, thank you. But I have pasted the full code successfully. The answer below seems not what I want. - 50u1w4y
A load into $2 does not affect $28. Those are different registers. As you know, $2 is $v0 (return val in the standard calling convention), while $28 = $gp, "Global Area Pointer (base of global data segment)". So yeah, it's clear GCC is using it as a GOT pointer, but agreed I don't see where it's initialized. Maybe in PIC code, it's already part of the calling convention? That is strange; it's getting reloaded after calls but I don't see where it's initially stored. 16($fp) is different memory in each func. - Peter Cordes

2 Answers

2
votes
lw $28,16($fp)

lw is a "load word" instruction -- it loads a word (4 bytes or 32 bits) from memory into a register. $28 is the destination register (also sometimes referred to a $gp), and 16($fp) is the address to load from -- 16 bytes into the frame ($fp is the frame pointer register, and 16 is added to it to get the address to load from).

The "frame" is generally used to hold local variables of the function --when a function starts, it allocates a frame on the stack by subtracting a constant from $sp, then stores the caller's $fp value somewhere in there and copies $sp to $fp so it points at this newly allocated frame. It then reads and writes local data into/from the frame with load(l) and store(s) instructions.

If you had compiled with optimization, GCC would keep locals in registers when possible instead of wasting huge numbers of instructions storing/reloading them to the stack. And would access stack memory relative to the stack pointer, instead of spending an instruction to set up $fp as a traditional frame pointer. Un-optimized code doesn't look anything like what a human would write by hand, but optimized code sometimes does.

1
votes

I think I know why.

I have ignored the .cprestore 16 automatically because I regard it as useless. But it turns out to emit actual instructions, not just metadata, related to the $gp register.

.cprestore 16 will expand to sw $gp,16(sp). It is used together with .cpload $25 and other necessary code. Specifically, you can read the link https://www.linux-mips.org/wiki/PIC_code