Skip to content

Instantly share code, notes, and snippets.

@StanHash
Created October 19, 2023 14:02
Show Gist options
  • Save StanHash/ee741eb7da92a429d86f66158f8df614 to your computer and use it in GitHub Desktop.
Save StanHash/ee741eb7da92a429d86f66158f8df614 to your computer and use it in GitHub Desktop.
Recommended GCC flags for compiling C objects for lyn/GBA ROM hacking

Recommended GCC flags for compiling C objects for lyn/GBA ROM hacking

arm-none-eabi-gcc -c INPUT -o OUTPUT -O2 -Wall -Wextra -Wno-unused -ffreestanding \
    -mthumb -mthumb-interwork -mcpu=arm7tdmi -mabi=apcs-gnu -mfloat-abi=softfp -mlong-calls

Add -g if your build pipeline allows for debug info to be propagated (lyn and EA don't).

Add any -I PATH you need (header include paths).

Hide this in your build script/Makefile.

Read the GCC docs for more.

Breakdown

gcc -c INPUT -o OUTPUT

This invokes gcc (the C compiler) and tells it to compile into an object (relocatable) file. If one omits -c, gcc will (attempt to) produce an executable instead. INPUT is the input .c file. OUTPUT is the output .o file.

you can use -S instead of -c to produce an asm file instead of an object file.

-O2

This is the optimization level. -O2 is a good baseline. -O3 is probably fine as well.

I used to suggest -Os, but it caused problems as it leads gcc to emit calls to libgcc functions that we don't have by default (it does so to support packed switch jump tables that are presumably only emitted with -Os). Don't use -Os.

-O0 should never be used. If for some reason your code compiles/works with -O0 but not -O2 there's a problem with your code and you should fix that instead.

If your build pipeline allows debug information to be propagated, consider perhaps -Og as well.

You can use the optimize(...) function attribute to override this (see GCC docs), if you need to.

-Wall -Wextra -Wno-unused

The enabled warnings. -Wall enables most basic warnings, -Wextra enables some extras. (see GCC docs for details)

-Wno-unused disables warnings that deal with unused varaibles, parameters, static functions, etc. I like to disable them as I usually end up having many things temporarily unused during dev and don't want actually important warnings to be lost among instances of these. This one is more of a suggestion than a recommendation.

-ffreestanding

This tells gcc to assume we are in a freestanding environment (as opposed to a hosted environment). It basically means there's no standard library. Note that despite that, gcc still emits calls to memcpy, memset and friends.

-mthumb

Default to compiling functions in thumb mode.

You can use the target(arm) function attribute to override this for individual functions, if you need to.

-mthumb-interwork

Enables ARM/Thumb interworking, which basically makes the compiler emit bxs at the end of functions instead of pop {pc}. This is not necessary most of the time, but doesn't hurt to have on.

-mcpu=arm7tdmi

Identifies the target CPU. This both tells the compiler which instructions are available and may also enable (or disable) some CPU-specific optimizations.

-mcpu implies both an adequate -march option and a -mtune option, which are thus not necessary to pass explicitly.

-mabi=apcs-gnu

Use the old (GNU-flavored) APCS ABI instead of the newer AAPCS. For our purposes, both ABIs are mostly compatible (to my knowledge). However, AAPCS requires the stack pointer to be aligned to 8 byte boundaries which results in unnecessary extra pushes/pops in functions.

Small performance deficits are usually not a big deal imo, but in this case there's no real reason not to have this on.

-mfloat-abi=softfp

Use software floating point. This is only necessary if your compiler was not configured to have this the default. For example: this is required if you use Compiler Explorer to test codegen using GCC targetting Linux ARM. It doesn't hurt to have.

-mlong-calls

Emit calls to outside functions using a function pointer (absolute address) rather than a bl instruction (relative offset). This is necessary if you are going to put code far away from other code (most common "free space" ranges are far enough away for this to matter).

Using long calls does lead to some overhead in function calls. On the other hand, if you do need a long call, having the compiler handle it before the linker leads to better codegen overall.

You can use the the no_long_calls pragma or the short_call function attribute on declarations to override this (see GCC docs), if you need to.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment