0

System info

  • zig version 0.12.0-dev.15+1c7798a3c
  • zig flags: -O ReleaseFast -target mips-linux-gnu

Background

New to MIPS, trying to figure out some simple examples!

I have the following zig code:

export fn mul_add(num: f32) f32 {
    const result = 2e-3 * num + 1;
    return result;
}

that generates the assembly:

$CPI0_0:
        .4byte  0x3b03126f
$CPI0_1:
        .4byte  0x3f800000
mul_add:
        lui     $1, %hi($CPI0_0)
        lwc1    $f0, %lo($CPI0_0)($1)
        lui     $1, %hi($CPI0_1)
        lwc1    $f1, %lo($CPI0_1)($1)
        mul.s   $f0, $f12, $f0           # <- here's the multiply!
        jr      $ra
        add.s   $f0, $f0, $f1            # <- here's the add!

If I change that zig code to use the builtin function @mulAdd:

export fn mul_add(num: f32) f32 {
    const result = @mulAdd(f32, 2e-3, num, 1);
    return result;
}

it will instead generate this assembly:

$CPI0_0:
        .4byte  0x3b03126f
mul_add:
        addiu   $sp, $sp, -24           # <- ...what is this add doing?
        sw      $ra, 20($sp)
        lui     $1, %hi($CPI0_0)
        lwc1    $f14, %lo($CPI0_0)($1)
        jal     fmaf                    # <- multiply-then-add instruction?
        lui     $6, 16256
        lw      $ra, 20($sp)
        jr      $ra
        addiu   $sp, $sp, 24            # <- ...is this add undoing the first add?

compiler explorer link

This version is 9 instructions total, compared to the 7 total instructions of the original version.

Questions

  • where's the multiply instruction in the @mulAdd version?
  • what's the gist of the second version's assembly? why does it need the additional two instructions compared to the first version?
  • [EDIT] it seems that jal is used to jump to the fmaf routine. But I don't see fmaf anywhere else in the assembly. Where is that defined? (e.g., is that a MIPS builtin routine?) Do docs exist for that function?
CrepeGoat
  • 2,315
  • 20
  • 24
  • 1
    The `jal fmaf` calls to a helper function to perform the **f**used **m**ultiply-**a**dd **f**loat. This is more accurate than separate multiply and add. [See documentation](https://ziglang.org/download/0.5.0/release-notes.html#mulAdd): "Performs (a * b) + c, except only rounds once, and is thus more accurate." – Raymond Chen Aug 08 '23 at 03:45
  • 2
    On a platform that has fma as a built-in instruction such as modern x64, maybe [the result](https://godbolt.org/z/4MhEP8sWY) is more in line with what you expected – harold Aug 08 '23 at 07:39
  • As an alternative, you might do the arithmetic in 64-bit float before converting to 32 bit. – Erik Eidt Aug 08 '23 at 19:55
  • @RaymondChen I'm familiar with the zig docs, I'm more interested in MIPS docs. Specifically, what's a "helper function" in the context of assembly code? is there documentation on all the helper functions available in MIPS? – CrepeGoat Aug 16 '23 at 16:13
  • @ErikEidt *why* might I "do the arithmetic in 64-bit float before converting to 32 bit"? what's the advantage/difference of doing that? – CrepeGoat Aug 16 '23 at 16:15
  • Some people will use `@mulAdd` for extra precision in rounding over doing separate 32-bit multiply, followed by 32-bit add. Some hardware can do fused multiply-add directly so we imagine that Zig will use that hardware capability for both extra precision and extra performance (one floating point op instead of two). But MIPS doesn't offer fused multiply-add in hardware, and so it uses a helper function to obtain that extra rounding precision at the cost of performance – Erik Eidt Aug 16 '23 at 16:34
  • 1
    So, another approach to obtaining that extra rounding precision is to perform the individual operations not in 32-bit float but in 64-bit float, so that before rounding down to 32-bit it will have extra precision. This approach will avoid the use of the helper function, so could potentially improve performance over calling the helper function. – Erik Eidt Aug 16 '23 at 16:36
  • "What's a 'helper function' in the context of assembly code?" In general, a "helper function" is a function that helps you do something. In this case, it's a function that the zig compiler writers taught the zig compiler to use when somebody asked it to do a `@mulAdd` on MIPS. – Raymond Chen Aug 16 '23 at 17:43
  • @ErikEidt ahhh I understand, basically `@mulAdd` gives you extra accuracy, but using float64's would also give you extra accuracy. That's true! However I'm not necessarily trying to get more accuracy in my calculations, I'm really just trying to understand the assembly output for this routine. – CrepeGoat Aug 17 '23 at 04:07
  • @RaymondChen I appreciate that you're trying to help me understand, but "a helper function is a function that helps you do something" is a tautology, and doesn't provide any further information. I also didn't get much from the rest of your prior comment. I'm going to edit my question with more information, hopefully that will clarify what I'm looking for. – CrepeGoat Aug 17 '23 at 04:10
  • 1
    Sorry. It's a tautology because it's just the definition. It's a function that helps. In this case, it helps the MIPS code generator perform a `@mulAdd` operation. If you don't like that name, you could just call it "a function": "It's a function that performs a fused multiply-add float." – Raymond Chen Aug 17 '23 at 04:20
  • okay, that more succinct form makes sense. thank you for your knowledge and patience :) – CrepeGoat Aug 17 '23 at 04:29

1 Answers1

1

The second version calls fmaf; This is just a C99 function not a builtin. The additional adds you're seeing are for moving the stack pointer $sp to pass arguments to fmaf (and to reset it again after the call) – I'm not that familiar with MIPS specifically but apparently the calling convention here is to pass some arguments via register and some via the stack, you'd have to read the docs for the exact details.

Cubic
  • 14,902
  • 5
  • 47
  • 92
  • so the assembly code is calling a C99 function? how does that work? is it just another assembly routine that the compiler provides? – CrepeGoat Aug 16 '23 at 02:29
  • "how does that work?" – I'm not sure what you mean. It works like you see in the code, it writes some arguments in the register, pushes some on the stack (according to the calling convention) and then just jumps to the `fmaf` label using the `jal` instruction. – Cubic Aug 16 '23 at 08:50
  • No, I mean how is assembly code calling a routine that’s exclusive to a higher level language like C99? I thought every natively-compiled language gets converted into pure assembly. – CrepeGoat Aug 16 '23 at 15:11
  • also, where is the `fmaf` routine stored? is it some DLL that C99 provides? from the assembly it doesn't look like it's stored anywhere in my program. – CrepeGoat Aug 16 '23 at 16:11
  • alsoalso, what other "external" routines are available via `jal`? is there a bit of documentation for MIPS that lists them all, or talks about this in any detail? (I haven't seen any so far.) – CrepeGoat Aug 16 '23 at 16:12
  • @CrepeGoat The `fmaf` function is not part of the MIPS architecture. It's just a function in a library. (In this case, the C runtime library.) – Raymond Chen Aug 17 '23 at 04:22
  • okay... `fmaf` is not a part of MIPS, it's a part of the C runtime library. is the C runtime library like a dynamically-linked library? is it something that the OS provides? – CrepeGoat Aug 17 '23 at 04:32
  • @CrepeGoat again, `jal` is just a jump+restore return address instruction. It's like `CALL` in some other assembly languages. I don't know where in your assembly you're looking, but apparently your assembler does know about that label so it's available somehow. It _might_ be dynamically linked, but it doesn't _have_ to be. – Cubic Aug 20 '23 at 12:56
  • 1
    @CrepeGoat it's from compiler-rt. A special library of "runtime" functions that get included into code. clang includes an implementation; so does zig. – daurnimator Aug 22 '23 at 05:31