Is there a way to use GCC to convert C to MIPS?
Asked Answered
S

7

23

I completed a C-to-MIPS conversion for a class, and I want to check it against the assembly. I have heard that there is a way of configuring GCC, so that it can convert C code to the MIPS architecture rather than the x86 architecture (my computer users an Core i5 processor) and prints the output.

Running the terminal in Ubuntu (which comes with GCC), what command do I use to configure GCC to convert to MIPS? Is there anything I need to install as well?

Note: I'm not looking for which compiler to use, or people saying "Well, you could cross-compile, but instead you should use this other thing that has no instructions on how to set up."

If you're going to post that, at least refer me to instructions. GCC came with Ubuntu. I don't have experience on how to install compilers and it's not easy finding online tutorials for anything other than GCC. Then there's the case of cross-compiling I need to know about as well.

Species answered 14/11, 2010 at 0:13 Comment(2)
You probably just want "compile" rather than "convert". The compiler always "converts" one language to another. If you run on x86 boxen, it generally converts your source language to a x86 executable suitable for your OS, but compilers are not limited to targeting their host OS and architecture...Rudnick
Yeah, that's kind of what I'm asking. Thanks for correcting my use of terminology, but can you answer the actual question?Species
G
36

GCC can produce assembly code for a large number of architectures, include MIPS. But what architecture a given GCC instance targets is decided when GCC itself is compiled. The precompiled binary you will find in an Ubuntu system knows about x86 (possibly both 32-bit and 64-bit modes) but not MIPS.

Compiling GCC with a target architecture distinct from the architecture on which GCC itself will be running is known as preparing a cross-compilation toolchain. This is doable but requires quite a bit of documentation-reading and patience; you usually need to first build a cross-assembler and cross-linker (GNU binutils), then build the cross-GCC itself.

I recommend using buildroot. This is a set of scripts and makefiles designed to help with the production of a complete cross-compilation toolchain and utilities. At the end of the day, you will get a complete OS and development tools for a target system. This includes the cross-compiler you are after.

Another quite different solution is to use QEMU. This is an emulator for various processors and systems, including MIPS systems. You can use it to run a virtual machine with a MIPS processor, and, within that machine, install an operating system for MIPS, e.g. Debian, a Linux distribution. This way, you get a native GCC (a GCC running on a MIPS system and producing code for MIPS).

The QEMU way might be a tad simpler; using cross-compilation requires some understanding of some hairy details. Either way, you will need about 1 GB of free disk space.

Grishilda answered 14/11, 2010 at 15:31 Comment(2)
if I could sir I would upvote you twice for this answer, cheersImplicate
If you just need the cross-compiler, install it from the Ubuntu repositories. See: https://mcmap.net/q/473836/-is-there-a-way-to-use-gcc-to-convert-c-to-mipsAdulthood
E
6

It's not a configuration thing. You need a version of GCC that cross-compiles to MIPS. This requires a special GCC build and is quite hairy to set up (building GCC is not for the faint of heart).

I'd recommend using LCC for this. it’s way easier to do cross-compilation with LCC than it is with GCC, and building LCC is a matter of seconds on current machines.

Edmea answered 14/11, 2010 at 0:18 Comment(4)
Thanks for the quick response, but it circumvents my question. You're telling me what I should do very generally (cross-compiling?), and then directing me to a different compiler. I'm asking in detail how to get that version of GCC and what commands to use, not if I should use GCC or a different compiler. Then there's the case of setting up this compiler, for which there isn't any sort of instructions on the LCC site.Species
I was assuming that your main interest was compiling C to MIPS assembly, not doing so with GCC. If you insist on using GCC for this, go ahead, but consider yourself warned - it's not worth it to compile just one program (particularly if you struggle with getting LCC to build). Some (relatively out-of-date, but I didn't find anything newer) instructions on build cross GCCs are here: gcc.gnu.org/wiki/Building_Cross_Toolchains_with_gccEdmea
My mistake. I think I misunderstood your answer. Yes my interest is converting C to MIPS. But if you're going to tell me to use a different compiler, can you refer me to somewhere that explains how to set it up and then how to cross-compile with it to MIPS? I can't find either of these things.Species
There's detailed set-up instructions in the LCC distribution in doc/install.html. What to do exactly depends on what host platform you're compiling on - Win32, Linux, something different? In any case, to get a MIPS compiler, you specify "mips/irix" as TARGET when building lcc.Edmea
O
6

For a one-time use for a small program or couple functions, you don't need to install anything locally.

Use Matt Godbolt's compiler explorer site, https://godbolt.org/, which has GCC and clang for various ISAs including MIPS and x86-64, and some other compilers.

Note that the compiler explorer by default filters directives so you can just see the instructions, leaving out stuff like alignment, sections, .globl, and so on. (For a function with no global / static data, this is actually fine, especially when you just want to use a compiler to make an example for you. The default section is .text anyway, if you don't use any directives.)


Most people that want MIPS asm for homework are using SPIM or MARS, usually without branch-delay slots. (Unlike real MIPS, so you need to tweak the compiler to not take advantage of the next instruction after a branch running unconditionally, even when it's taken.) For GCC, the option is -fno-delayed-branch - that will fill every delay slot with a NOP, so the code will still run on a real MIPS. You can just manually remove all the NOPs.

There may be other tweaks needed, like MARS may require you to use jr $31 instead of j $31, Tweak mips-gcc output to work with MARS without using a script. And of course I/O code will have to be implemented using MARS's toy system calls, not jal calls to standard library functions like printf or std::ostream::operator<<. You can usefully compile (and hand-tweak) asm for manipulating data, like multiplying integers or summing or reversing an array, though.

Unfortunately GCC doesn't have an option to use register names like $a0 instead of $r. For PowerPC there's -mregnames to use r1 instead of 1, but no similar option for MIPS to use "more symbolic" reg names.

int maybe_square(int num) {
    if (num>0)
        return num;
    return num * num;
}

On Godbolt with GCC 5.4 -xc -O3 -march=mips32r2 -Wall -fverbose-asm -fno-delayed-branch

-xc compiles as C, not C++, because I find that more convenient than flipping between the C and C++ languages in the dropdown and having the site erase my source code.

-fverbose-asm comments the asm with C variable names for the destination and sources. (In optimized code that's often an invented temporary, but not always.)

-O3 enables full optimization, because the default -O0 debug mode is a horrible mess for humans to read. Always use at least -Og if you want to look at the code by hand and see how it implements the source. How to remove "noise" from GCC/clang assembly output?. You might also use -fno-unroll-loops, and -fno-tree-vectorize if compiling for an ISA with SIMD instructions.

This uses mul instead of the classic MIPS mult + mflo, thanks to the -march= option to tell GCC we're compiling for a later MIPS ISA, not whatever the default baseline is. (Perhaps MIPS I aka R2000, -march=mips1)

See also the GCC manual's section on MIPS target options.

# gcc 5.4 -O3
square:
        blez    $4,$L5
        nop
        move    $2,$4    # D.1492, num         # retval = num
        j       $31                            # jr $ra  = return
        nop

$L5:
        mul     $2,$4,$4   # D.1492, num, num   # retval = num * num
        j       $31                             # jr $ra  = return
        nop

Or with clang, use -target mips to tell it to compile for MIPS. You can do this on your desktop; unlike GCC, clang is normally built with multiple back-ends enabled.

From the same Godbolt link, clang 10.1 -xc -O3 -target mips -Wall -fverbose-asm -fomit-frame-pointer. The default target is apparently MIPS32 or something like that for clang. Also, clang defaults to enabling frame pointers for MIPS, making the asm noisy.

Note that it chose to make branchless asm, doing if-conversion into a conditional-move to select between the original input and the mul result. Unfortunately clang doesn't support -fno-delayed-branch; maybe it has another name for the same option, or maybe there's no hope.

maybe_square:
        slti    $1, $4, 1
        addiu   $2, $zero, 1
        movn    $2, $4, $1            # conditional move based on $1
        jr      $ra
        mul     $2, $2, $4            # in the branch delay slot

In this case we can simply put the mul before the jr, but in other cases converting to no-branch-delay asm is not totally trivial. e.g. branch on a loop counter before decrementing it can't be undone by putting the decrement first; that would change the meaning.


Register names:

Compilers use register numbers, not bothering with names. For human use, you will often want to translate back. Many places online have MIPS register tables that show how $4..$7 are $a0..$a3, $8 .. $15 are $t0 .. $t7, etc. For example this one.

Outrush answered 13/8, 2020 at 1:37 Comment(0)
A
4

You should install a cross-compiler from the Ubuntu repositories. GCC MIPS C cross-compilers are available in the repositories. Pick according to your needs:

  • gcc-mips-linux-gnu - 32-bit big-endian.
  • gcc-mipsel-linux-gnu - 32-bit little-endian.
  • gcc-mips64-linux-gnuabi64 - 64-bit big-endian.
  • gcc-mips64el-linux-gnuabi64 - 64-bit little-endian.
  • etc.

(Note for users of Ubuntu 20.10 (Groovy Gorilla) or later, and Debian users: if you usually like to install your regular compilers using the build-essential package, you would be interested to know of the existence of crossbuild-essential-mips, crossbuild-essential-mipsel, crossbuild-essential-mips64el, etc.)

In the following examples, I will assume that you chose the 32-bit little-endian version (sudo apt-get install gcc-mipsel-linux-gnu). The commands for other MIPS versions are similar.

To deal with MIPS instead of the native architecture of your system, use the mipsel-linux-gnu-gcc command instead of gcc. For example, mipsel-linux-gnu-gcc -fverbose-asm -S myprog.c produces a file myprog.s containing MIPS assembly.

Another way to see the MIPS assembly: run mipsel-linux-gnu-gcc -g -c myprog.c to produce an object file myprog.o that contains debugging information. Then view the disassembly of the object file using mipsel-linux-gnu-objdump -d -S myprog.o. For example, if myprog.c is this:

#include <stdio.h>

int main()
{
    int a = 1;
    int b = 2;
    printf("The answer is: %d\n", a + b);
    return 0;
}

And if it is compiled using mipsel-linux-gnu-gcc -g -c myprog.c, then mipsel-linux-gnu-objdump -d -S myprog.o will show something like this:

myprog.o:     file format elf32-tradlittlemips


Disassembly of section .text:

00000000 <main>:
#include <stdio.h>

int main() {
   0:    27bdffd8     addiu sp,sp,-40
   4:    afbf0024     sw    ra,36(sp)
   8:    afbe0020     sw    s8,32(sp)
   c:    03a0f025     move  s8,sp
  10:    3c1c0000     lui   gp,0x0
  14:    279c0000     addiu gp,gp,0
  18:    afbc0010     sw    gp,16(sp)
    int a = 1;
  1c:    24020001     li    v0,1
  20:    afc20018     sw    v0,24(s8)
    int b = 2;
  24:    24020002     li    v0,2
  28:    afc2001c     sw    v0,28(s8)
    printf("The answer is: %d\n", a + b);
  2c:    8fc30018     lw    v1,24(s8)
  30:    8fc2001c     lw    v0,28(s8)
  34:    00621021     addu  v0,v1,v0
  38:    00402825     move  a1,v0
  3c:    3c020000     lui   v0,0x0
  40:    24440000     addiu a0,v0,0
  44:    8f820000     lw    v0,0(gp)
  48:    0040c825     move  t9,v0
  4c:    0320f809     jalr  t9
  50:    00000000     nop
  54:    8fdc0010     lw    gp,16(s8)
    return 0;
  58:    00001025     move  v0,zero
}
  5c:    03c0e825     move  sp,s8
  60:    8fbf0024     lw    ra,36(sp)
  64:    8fbe0020     lw    s8,32(sp)
  68:    27bd0028     addiu sp,sp,40
  6c:    03e00008     jr    ra
  70:    00000000     nop
    ...
Adulthood answered 21/9, 2019 at 13:47 Comment(0)
B
2

You would need to download the source to binutils and gcc-core and compile with something like ../configure --target=mips .... You may need to choose a specific MIPS target. Then you could use mips-gcc -S.

Beasley answered 14/11, 2010 at 0:17 Comment(2)
can you please explain a little more. I also need to convert from C to MIPSR3000.Mulder
@ShankhadeepMukerji Look at gcc.gnu.org/install . Under step 3 look at "target specification".Beasley
C
2

You can cross-compile the GCC so that it generates MIPS code instead of x86. That's a nice learning experience.

If you want quick results you can also get a prebuilt GCC with MIPS support. One is the CodeSourcery Lite Toolchain. It is free, comes for a lot of architectures (including MIPS) and they have ready to use binaries for Linux and Windows.

http://www.codesourcery.com/sgpp/lite/mips/portal/subscription?@template=lite

Cardew answered 14/11, 2010 at 5:54 Comment(1)
The link redirects (but it still works. To sourcery.sw.siemens.com). Now it is "Sourcery CodeBench Lite".Balsaminaceous
A
1

You should compile your own version of GCC which is able to cross-compile. Of course this isn’t easy, so you could look for a different approach... For example, this SDK.

Athey answered 14/11, 2010 at 0:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.