EmbCCdocs Repository

Invoking EmbCC#

This page is the reference for the embcc command line: how a command is put together, and every option in detail, grouped by what it is for. The option reference lists all of them in one table, printed by the compiler from its own option table; this page says what each one does.

EmbCC's own spellings are used throughout. Where GCC spells an option differently, GCC's spelling is given under the heading, and works too: a build written for GCC builds unchanged (Migrating from GCC). GCC flags that EmbCC has no name for are gathered in GCC flags EmbCC accepts.

Each option is one of three kinds, and its entry says which:

  • Honoured. The option does what its entry says.
  • Accepted. The option is taken without error because EmbCC already behaves the way it asks, or because doing nothing is harmless. Each such entry says which.
  • Refused. The option names a promise about the generated code that EmbCC cannot keep (position independence, stack protection, profiling, link-time optimization, and so on). The driver stops with an error that names the option, so a build never receives an object that silently lacks what it asked for.

An option that is none of these stops the driver with embcc: error: unknown argument 'ARG', followed by the usage line.

Synopsis#

embcc [OPTION...] [FILE] [OBJECT|ARCHIVE|-lLIB...]
                                           compile and link (x86-64 ELF; ARM,
                                           RISC-V, MIPS and AVR firmware given
                                           its memory map)
embcc -c [OPTION...] FILE [-o OBJECT]      compile or assemble to an object
embcc -S [OPTION...] FILE [-o FILE.s]      write assembly
embcc -E [OPTION...] FILE                  preprocess to standard output
embcc inspect STAGE FILE [OPTION...]       print one stage of the pipeline
embcc why DECISION [SUBJECT] FILE [OPTION...]
                                           explain an optimizer decision
embcc --version | --help[=gcc] | --help-warnings | --print-target
embcc --dump-predef | --print-search-dirs | --explain=ID
embcc --reference=options|warnings|targets|man
embcc --emit-empty-object FILE

EmbCC compiles any number of source files in one command, and links them with any number of objects, archives and -l libraries (see Several source files). A command line with no input file and none of the query options ends with embcc: error: no input file. Running embcc with no arguments prints the usage line and exits with status 1.

Options may appear in any order, before or after the input file, with the exceptions noted under --lang and --emit-empty-object. An option that takes a value accepts it after an = (--cpu=cortex-m4) or as the next argument (--cpu cortex-m4). POSIX's letters take theirs attached or next, as POSIX says (-o FILE, -DNAME).

What to build#

Input files#

The driver decides what to do with the input file from its suffix, unless -x says otherwise.

Suffix Treated as
.c C source
.cc .cpp .cxx .C .c++ .cp .CPP .ii C++ source (lowered to C internally; see C++)
.s GNU-syntax assembly, assembled for the selected target without preprocessing
.S GNU-syntax assembly, preprocessed first, with __ASSEMBLER__ defined
.asm NASM/Intel-syntax x86-64 assembly (the embas assembler)
.ir EmbIR text; meaningful only to embcc inspect ir
.o .obj .a an object or an archive, handed to the link as it is

Several source files#

embcc --opt=speed main.c uart.c timer.c -lm -o prog      # compile all three, link
embcc --opt=speed -j4 -c *.c                             # main.o uart.o timer.o ...

Each source is compiled by a run of embcc of its own, with the same options, as GCC's driver runs its compiler proper. Without -c, -S or -E the objects are temporaries beside the output (OUT.embcc-tmp-N.o), linked in command-line order among the other inputs and then removed; with -c or -S each source gets its own NAME.o or NAME.s in the working directory, and -o is refused, since it names one file; with -E they are printed one after the other. If any source fails, nothing is linked and the temporaries are removed. --syntax-only and --fix check each one. embcc inspect, --why, --emit-c and --emit-interfaces take one source.

-j N (or -jN) compiles N sources at once; -j alone, one per processor. The default is one at a time, so the diagnostics come out in command-line order. The image does not depend on -j.

This needs a host that can run a program. EmbCC built with PROCESS=none -- as it is for EmbLinkOS, which has no fork/exec -- compiles one source per command, and refuses a second with embcc: error: more than one source file ('a.c' and 'b.c'), and this host cannot run a compiler for each (EmbCC was built with PROCESS=none): compile each with -c and link the objects.

Anything else, including .i and .h, is not an input file to EmbCC. Without -x such an argument is refused as embcc: error: unknown argument 'NAME'. EmbCC does not read source from standard input; - is not accepted as a file name.

What happens to the input depends on the mode options:

Mode Input Result Default output
none C or C++ compiled and linked (x86-64 ELF, and ARM, RISC-V, MIPS and AVR firmware with a memory map) a.out
none .s .S assembled and linked, as above a.out
none only objects, archives and -l linked a.out
-c C or C++ relocatable object the input's file name with its suffix replaced by .o
-S C or C++ assembly text the input's file name with its suffix replaced by .s
-E C, C++ or .S preprocessed text standard output
--syntax-only C or C++ nothing; diagnostics only none
-c .s .S .asm object see Assembling and linking
any .asm object (NASM syntax is never linked by the driver) see Assembling and linking

As with GCC, a default output is written in the current directory, not next to the input: embcc -c src/foo.c writes foo.o, and embcc -S src/foo.c writes foo.s.

A C++ input is compiled for every target but AVR: with exceptions on x86-64 and AArch64, and with --no-exceptions on the embedded targets (see C++ targets). For AVR, every mode but -E, -M, --deps-only and --syntax-only stops with embcc: error: C++ is not yet supported for avr: the C++ lowering assumes a 32-bit int and 4-byte pointers ....

When several mode options are given, -E takes precedence over the others, then --syntax-only, then -S, then -c.

-c#

Compile the input to a relocatable object and do not link. The object format follows the target: ELF for the bare-metal, EmbLinkOS and Linux targets, Mach-O for the -apple-darwin targets and COFF for x86_64-windows-gnu (see Targets).

-S#

Write the code the backend generated as GNU assembler text instead of an object. Supported on every target. Without -o the output is NAME.s in the current directory; -o - writes it to standard output. -S implies -c.

Each instruction is written as its encoded bytes in a .byte directive, with every relocation attached explicitly through .reloc, so that assembling the file reproduces the object -c writes. On x86-64 each line also carries the disassembly as a comment; the other targets show the bytes only. The file is a record of what was compiled, not a starting point for hand editing.

add:
	.byte	0x03,0xfe	# add    %esi,%edi
	.byte	0x48,0x89,0xf8	# mov    %rdi,%rax
	.byte	0xc3	# ret
	.size	add, .-add

File-scope asm blocks and naked functions are written the same way on Cortex-M, RISC-V and AVR: their bytes, their .globl and .weak labels typed as the object types them, and a .reloc for every field they relocate. A label the object does not export is written as a local label of its block, .Lasm<N>.<name>. On Cortex-M a relocated data word is written .long sym+N instead, because an ARM assembler keeps a relocation's addend in the word, where .reloc does not put it. EmbCC reads the file back (embcc -c FILE.s), and so does llvm-mc.

-E#

Preprocess the input and write the result to standard output, or to FILE with -o FILE, as GCC does. -E applies to C, C++ and .S files. For a .asm file it is refused with embcc: error: -E does not apply to assembly.

Combined with --deps-only or -M, -E prints only the dependency rule (see --deps-only).

With -dM, -E prints every macro defined at the end of the file instead of the text. That covers the predefined macros, those from -D, and the file's own, one #define each, as GCC prints them. GCC promises no order, and neither does EmbCC.

Standard input. The input - is read from standard input, so the usual way to ask a compiler for its macros works: echo | embcc --target=thumbv7em-none-eabi -E -dM -. As with GCC, standard input is C under -E, and otherwise needs -x to say what it is: -E or -x required when input is from standard input.

-o FILE#

Write the output to FILE. The argument is the next word on the command line; -oFILE is not accepted. If -o is the last argument the driver stops with embcc: -o needs a FILE. When -o appears more than once, the last one wins.

-o - means standard output for the text-producing modes (-E, -S, --emit-interfaces, --emit-c). An object cannot be written to standard output, and -c -o - is refused:

embcc: error: `-o -` writes to stdout, which -E, -S and --emit-interfaces support but an object file does not; name a file

-j N, -jN, -j#

With several source files, compile N of them at once; -j alone, one per processor. The default is one at a time. The objects and the image are the same whatever N is; only the order the diagnostics come out in can differ. See Several source files.

--lang=LANGUAGE#

GCC: -x LANGUAGE.

Treat the input as LANGUAGE regardless of its suffix. The language may follow an = (--lang=c++) or be the next argument (--lang c++); GCC's -x takes it attached (-xc++) or next (-x c++).

LANGUAGE Meaning
c, cpp-output C
c++, c++-cpp-output C++
none go back to deciding by suffix
assembler GNU assembly, as a .s file is
assembler-with-cpp GNU assembly, preprocessed first, as a .S file is

The two assembly languages are what a CubeMX-generated Makefile passes: it assembles its startup_*.s with gcc -x assembler-with-cpp, so the lowercase .s is preprocessed. An object or an archive given in the same command (.o, .a, .obj) is still linked, not assembled.

Any other value is refused with embcc: error: unknown language 'LANGUAGE' for -x (c, c++, assembler or assembler-with-cpp).

Unlike GCC's -x, --lang applies to the one input file wherever it appears. The exception is an input whose suffix EmbCC does not recognize (prog.txt, prog.i): such a file is only taken as the input when --lang=c or --lang=c++ comes before it on the command line.

--syntax-only#

GCC: -fsyntax-only.

Run the preprocessor, parser and semantic analysis, report diagnostics, and write nothing. Implies -c. The exit status is 1 if any error was reported.

--keep-temps, --keep-temps=cwd, --keep-temps=obj#

GCC: -save-temps, -save-temps=cwd, -save-temps=obj.

Honoured. Keep the preprocessed source (.i, or .ii for C++) and the assembly (.s) of each C or C++ source, named as GCC 11 and later name them:

Command Files kept
embcc --keep-temps -c x.c -o build/x.o build/x.i, build/x.s
embcc --keep-temps -c x.c x.i, x.s
embcc --keep-temps -S x.c -o build/y.s build/y.i (the .s is the output)
embcc --keep-temps x.c y.c -o fw.elf fw-x.i, fw-x.s, fw-y.i, fw-y.s

--keep-temps=obj is the same as --keep-temps. --keep-temps=cwd puts the files in the current directory, named as above without the directory. GCC's -dumpbase NAME names them NAME.i and NAME.s. EmbCC passes nothing between its stages through files, so each is produced by running the compiler again on the same source with -E and with -S; the .s is the assembly of the object the command writes. A host that cannot run a program (EmbCC built with PROCESS=none) refuses the option. Nothing is kept for assembly inputs.

--emit-c#

For a C++ input, write the C that EmbCC lowers the C++ unit to, instead of compiling it. The output goes to -o FILE or to standard output. For a C input the driver stops with embcc: error: --emit-c lowers C++; 'FILE' is C.

--emit-interfaces#

Write the unit's interface record instead of an object: one line per entity the unit provides or uses, each with a stable name (a USR) and a hash of what dependents can observe of it. The output goes to -o FILE or to standard output; --emit-interfaces implies -c. The record is the input to the embidx cross-unit index.

; EmbCC interfaces v1
; t.c
file     21c568a73b4612ce t.c
provides c:@F@add                                 5336311b8eae8c73

A weak definition's provides line ends in weak (see embidx).

--emit-empty-object FILE#

Write a valid relocatable ELF object with an empty .text section to FILE. This option must be the first argument and FILE the only other one; anything else is refused (embcc: --emit-empty-object needs a FILE, or unknown argument when other options are present). The object is for the configured default target, since --target= cannot be combined with it. A default target whose object format is not ELF or COFF is refused with embcc: error: no object writer for Mach-O yet, which is what 'TRIPLE' needs.

Exit status#

embcc exits with status 0 when the requested output was produced and no error was reported, and 1 otherwise. A warning promoted by --warnings-are-errors or --error=NAME counts as an error, and the compile then leaves no output file (see --warnings-are-errors). With --fix, the status is 0 when at least one fix was applied.

The language#

--std=STANDARD#

GCC: -std=STANDARD.

Name the language standard. For C, each standard is compiled as it is:

--std= value Standard __STDC_VERSION__
c89, c90, iso9899:1990, gnu89, gnu90 C89/C90 not defined
iso9899:199409 C94: C89 with its first amendment 199409L
c99, c9x, iso9899:1999, gnu99, gnu9x C99 199901L
c11, c1x, iso9899:2011, gnu11, gnu1x C11 201112L
c17, c18, iso9899:2017, iso9899:2018, gnu17, gnu18 C17 201710L
c23, c2x, iso9899:2024, gnu23, gnu2x C23 202311L
c2y, gnu2y C2y, the standard after C23, as drafted 202500L

The default is gnu17. A cNN name is the standard alone and defines __STRICT_ANSI__; a gnuNN name adds GNU's extensions (GNU extensions). What the standard changes:

  • Keywords. Before C23, bool, true, false, nullptr, static_assert, alignas, alignof, thread_local, typeof_unqual and constexpr are ordinary names under a cNN standard. asm is a keyword only under gnuNN, and typeof under gnuNN or from C23. In strict C89, inline and restrict are names too.
  • EmbCC's GNU dialects before C23 also have C23's keywords, auto type inference, digit separators (1'000) and u8'c', as EmbCC always has; GCC's have them only from gnu23. --pedantic takes them away.
  • C89. Strict c89 has no // comments. In both c89 and gnu89 a for loop cannot declare its variable, a declaration with no type is an int (static x;, auto w = 4;, f() { ... }), and a call to a function nothing declares declares it extern int f();.
  • Before C23, int f() declares a function with no prototype: its calls are not checked against it and their arguments are promoted. From C23 it means int f(void). Old-style (K&R) definitions are compiled under every standard.
  • Trigraphs (??= for #, and the other eight) are read under a strict standard before C23, c89 to c17, as GCC reads them.

C2y's _Countof and 0o17 octal constants, and C23's binary constants, are compiled under every standard, as GCC compiles them; --pedantic reports them before the standard that has them.

Every case of tests/std/c-corpus.txt is compiled under each standard, as it is and with --pedantic-errors, and accepted or refused as GCC 16 decides (tools/std-check.py). Where EmbCC decides otherwise on purpose, the case is listed with the reason in tests/std/c-expect.txt.differs.

For C++, the accepted names are c++NN and gnu++NN, where NN is one of 98, 03, 11, 0x, 14, 1y, 17, 1z, 20, 2a, 23, 2b, 26 and 2c. These set the standard macros that libraries choose code by: __cplusplus (199711L, 201103L, 201402L, 201703L, 202002L, 202302L, or 202400L for C++26), the feature-test macros, and __STRICT_ANSI__ for the c++NN spellings. And they decide the program's language, as GCC's do: the keywords (C++11's constexpr and nullptr are names in C++98, C++20's concept and requires before C++20), the tokens (digit separators from C++14, u8'c' from C++17, <=> from C++20, u8"" of type const char8_t[] from C++20), and the constructs each standard introduced: in C++98 auto is a storage class and >> does not close two template argument lists, deduced return types and generic lambdas are C++14's, template<auto> and class template argument deduction C++17's, using enum C++20's. An older standard refuses a newer construct by name:

embcc: f.cc:3:1: error: class template argument deduction is C++17's, and the standard here is C++14

A system header is read with all of EmbCC's C++ whatever --std= says: EmbCC's own C++ library is C++20 throughout. The cases are checked against GCC 16 in tests/std/cxx-corpus.txt.

The default for C++ is equivalent to --std=gnu++20.

One C standard changes code generation: c89, c90, iso9899:1990, gnu89 and gnu90 select GNU89 inline semantics, as -fgnu89-inline does.

--char=signed, --char=unsigned#

GCC: -fsigned-char, -funsigned-char.

Make plain char signed or unsigned, overriding the target's default (signed on x86-64, macOS on AArch64, and AVR; unsigned on the other AArch64 targets, ARM and RISC-V; see Targets). Honoured: the predefined macro __CHAR_UNSIGNED__ is defined or removed to match. The last of the two on the command line wins.

--wchar=short, --wchar=int#

GCC: -fshort-wchar, -fno-short-wchar.

--wchar=short makes wchar_t an unsigned short on every target, as GCC's and Clang's option does. UEFI code and ARM code written for UTF-16 strings use it. What changes:

  • The type. wchar_t is unsigned short, in C and C++, and an L"" literal has 16-bit elements. A code point above U+FFFF is a surrogate pair, as in a u"" literal.
  • The macros, as Clang has them: __WCHAR_TYPE__ is unsigned short, __WCHAR_MAX__ 65535, __WCHAR_MIN__ 0, __WCHAR_WIDTH__ 16, __SIZEOF_WCHAR_T__ 2, __WCHAR_UNSIGNED__ 1, and on ARM __ARM_SIZEOF_WCHAR_T 2. So <wchar.h>'s WCHAR_MAX is 65535.
  • ARM objects record a 2-byte wchar_t in their build attributes (Tag_ABI_PCS_wchar_t).

lib/libc is built with the target's own wchar_t, so its wcs* functions do not work on 16-bit strings. This is the same as GCC with a newlib built without the option. The last of the two options on the command line wins.

--freestanding#

GCC: -ffreestanding.

Accepted. EmbCC makes no hosted assumptions to turn off: it treats no library function name as a builtin. __STDC_HOSTED__ stays defined as 1 on every target, with or without this option.

--no-builtins, --no-builtin=NAME#

GCC: -fno-builtin, -fno-builtin-NAME.

Accepted. EmbCC recognizes no library function name (memcpy, printf) as a builtin; only the __builtin_ spellings are builtins. A call to memcpy is a call, so the per-name form asks for nothing more.

--wrap-overflow#

GCC: -fwrapv.

Accepted. Signed integer overflow wraps in two's complement on every target, and no optimization assumes it cannot happen. -fno-strict-overflow says the same and is accepted too. -fno-wrapv, -ftrapv and -fstrict-overflow are not accepted.

--strict-aliasing, --no-strict-aliasing#

GCC: -fstrict-aliasing, -fno-strict-aliasing.

Accepted. EmbCC's alias analysis is not type-based, so code is compiled as --no-strict-aliasing would compile it in either case.

--common, --no-common#

GCC: -fcommon, -fno-common.

--no-common is the default: a tentative definition (int x; at file scope, with no initializer) is a definition in .bss, and two units that both have one do not link (embld: multiple definition of 'x').

--common is honoured for C on the ELF targets. A tentative definition is emitted as a COMMON symbol (SHN_COMMON, its value the alignment), as GCC emits it, and embld merges every unit's COMMON symbols of one name into one object with the largest size and the strictest alignment, or drops them for a real definition elsewhere. A static, thread-local or weak tentative definition, or one with a section attribute, stays a definition, as in GCC. A linker script must place *(COMMON) (in .bss, where the startup code zeroes it); embld says so if it does not. -S writes .comm NAME,SIZE,ALIGN. C++ has no tentative definitions, so the option changes nothing there. For a Mach-O or COFF target it is refused: embcc: error: --common is not supported for x86_64-apple-darwin: EmbCC writes COMMON symbols into ELF objects only, and this target's are Mach-O.

--enums=short, --enums=int#

GCC: -fshort-enums, -fno-short-enums.

--enums=short makes every enumeration the smallest integer type that holds all its values: unsigned char, signed char, unsigned short, short, or the type the values need, unsigned when no value is negative. That is how arm-none-eabi-gcc lays enumerations out by default, and how __attribute__((packed)) lays out one enumeration. It changes the layout of every structure that holds an enumeration, so it is an ABI choice: a Cortex-M object says which in Tag_ABI_enum_size (1, against 2 for int-sized ones), embld refuses to link the two kinds together, and ACLE's __ARM_SIZEOF_MINIMAL_ENUM is 1. C only: for a C++ input it is refused (supported for C, not C++). --enums=int is the default: an enumeration is int-sized unless its values need a wider type.

--enums=neutral#

Also accepted: -fenum-size-neutral (EmbCC's earlier spelling).

EmbCC's own. The object makes no claim about the size of an enumeration (Tag_ABI_enum_size 0), so it links with objects built either way. For a library whose interface passes no enumeration type: EmbCC's libc and compiler runtime are built with it.

--float-constants=single, --float-constants=double#

GCC: -fsingle-precision-constant, -fno-single-precision-constant.

Honoured, for C. A floating constant without a suffix has type float and the value it has as a float, as with GCC's option: sizeof(1.0) is 4, x * 0.1 with float x is a float multiply, and double d = 0.1; holds 0.1f widened. A constant beyond the range of float becomes an infinity, and one too small becomes zero, with a warning (floating constant exceeds the range of 'float'). L-suffixed constants are unchanged. For a C++ input it is refused (supported for C, not C++), because the C++ front end types its own constants. --float-constants=double is the default.

--char8#

GCC: -fchar8_t.

C++ only. Define __cpp_char8_t when the standard selected by --std= is earlier than C++20. In C++20 mode char8_t is a keyword already.

--exceptions, --no-exceptions#

GCC: -fexceptions, -fno-exceptions.

C++ exceptions are on by default. --no-exceptions turns them off as g++'s flag does. --exceptions turns them on and also requests unwind tables, which makes it useful for C code that C++ exceptions must unwind through (see unwind tables).

--rtti, --no-rtti#

GCC: -frtti, -fno-rtti.

Run-time type information is on by default for C++. --no-rtti turns it off.

--access-control, --no-access-control#

GCC: -faccess-control, -fno-access-control.

--no-access-control stops enforcing private and protected in C++, as GCC's option does. -faccess-control restores the default.

Language options that are not accepted#

-fms-extensions, -fno-asm and -fvisibility=... are not accepted (unknown argument).

Headers and macros#

The preprocessor itself is described in C language and Extensions.

-D NAME, -D NAME=VALUE#

Define a macro, as #define NAME 1 or #define NAME VALUE. The argument may be attached (-DNAME) or the next word (-D NAME). With no argument the driver stops with embcc: -D needs a macro name.

-U NAME#

Remove a predefined or earlier -D definition of NAME. -D and -U are applied in command-line order: -DFOO -UFOO leaves FOO undefined, and -UFOO -DFOO leaves it defined.

--include=FILE#

GCC: -include FILE.

Read FILE as if #include "FILE" were the first line of the source file. FILE is looked for in the working directory first, as GCC does, then on the include path. Several are read in the order given. A file that cannot be found is refused: cannot find "FILE", which --include names to read before the source.

-I DIR#

Add DIR to the list of directories searched for headers. The argument may be attached (-Iinclude) or the next word. With no argument the driver stops with embcc: -I needs a directory.

--system-include=DIR#

GCC: -isystem DIR.

Add DIR as a system header directory: searched in command-line order with the -I directories, but its headers do not produce warnings (unless --warn-in-system-headers) and are left out of the rules --deps and --deps-only write.

--no-std-include#

GCC: -nostdinc.

Do not search EmbCC's C library, C++ library and freestanding header directories. See the note below on the one directory this does not remove.

Search order#

For #include "file" the directory of the including file is searched first. Then, for both forms of #include, the directories are searched in this order:

  1. every -I and --system-include directory, in command-line order;
  2. the directory include beside the path the compiler was invoked as: for /opt/embcc/bin/embcc that is /opt/embcc/bin/include; for a bare embcc found through PATH it is ./include in the current working directory;
  3. unless --no-std-include is given, EmbCC's own directories, all treated as system directories: the C++ library headers, the C library headers, and the freestanding headers (<stddef.h>, <stdarg.h>, <stdint.h>, <limits.h>, <float.h> and the rest).

Directory 2 is searched as a system directory and is added even with --no-std-include. In a build tree it is EmbCC's freestanding header directory, so <stddef.h> and <stdint.h> remain reachable under --no-std-include there. When embcc is run through PATH from a directory that has an include subdirectory, headers there are found as system headers.

Project headers therefore take precedence over EmbCC's: a project that ships its own <stdio.h> in an -I directory gets its own.

The search list holds at most 512 directories -- a vendor SDK's build gives a hundred or so, one per component (the Pico SDK's does). EmbCC's four are appended only while there is room, so with more than 508 -I/--system-include options some of them are left out without a message, and a 513th -I is refused with embcc: too many -I directories (embcc: too many include directories for --system-include).

How the compiler finds its own files#

EmbCC locates its headers and per-target libraries relative to its own executable, so a build tree and an installation both work without -I:

  1. If the environment variable EMBCC_PREFIX is set and $EMBCC_PREFIX/lib/embcc/VERSION/include/stdio.h exists, that installation is used. If the variable is set and the file does not exist, the driver prints embcc: EMBCC_PREFIX=DIR has no lib/embcc/VERSION/include and goes on to the next step.
  2. If the directory containing the executable has lib/libc/include/stdio.h, it is a build tree, and its lib/ and build/ directories are used.
  3. Otherwise, if BINDIR/../lib/embcc/VERSION/include/stdio.h exists, that installation is used.

VERSION is the string --version prints (0.0.1-beta). The executable's own path is read from the operating system on macOS and Linux; on other hosts none of the steps after the first can apply, and every header needs an explicit -I. --print-search-dirs shows the result. The installed layout is described in Getting started.

Preprocessor options that are not accepted#

-imacros, -iquote, -idirafter, -iprefix, -dM, -dD, -C, -CC, -P, -H, -MG, -trigraphs and -undef are not accepted either. Use --dump-predef to list predefined macros.

The target#

Targets covers each family in depth, and the target list names every target.

--target=TARGET#

Generate code for TRIPLE. One embcc binary contains every backend, so any accepted triple can be named on any compiler. The triple selects the backend, the data model and ABI, the predefined macros, the object format, and the library directory used for linking.

A triple EmbCC does not know is refused, with the full list:

embcc: error: unknown target 'bogus'
embcc: the targets it emits for are:
embcc:   x86_64-elf
...

The accepted triples, with the canonical spelling first. The canonical name is what --print-target, --version and diagnostics print.

Canonical Also accepted Architecture Environment Object
x86_64-elf x86_64, x86_64-none-elf x86-64 bare metal ELF64
aarch64-elf aarch64, arm64, aarch64-none-elf AArch64 bare metal ELF64
thumbv7m-none-eabi thumbv7m, armv7m-none-eabi, arm-none-eabi ARMv7-M (Cortex-M3) bare metal ELF32
thumbv7em-none-eabi thumbv7em, armv7em-none-eabi ARMv7E-M (Cortex-M4/M7), soft float bare metal ELF32
thumbv7em-none-eabihf ARMv7E-M with FPv4-SP-D16 (FPv5-D16 with --cpu=cortex-m7), hard float bare metal ELF32
thumbv8m.main-none-eabi thumbv8m.main, thumbv8m-none-eabi, armv8m.main-none-eabi ARMv8-M Mainline (Cortex-M33), soft float bare metal ELF32
thumbv8m.main-none-eabihf ARMv8-M Mainline with FPv5-SP-D16, hard float bare metal ELF32
riscv32-unknown-elf riscv32-none-elf, riscv32, riscv32-elf, rv32 RV32 bare metal ELF32
riscv64-unknown-elf riscv64-none-elf, riscv64, riscv64-elf, rv64 RV64 bare metal ELF64
avr avr-none-elf, avr-elf, avr-unknown-none AVR (ATmega328P) bare metal ELF32
mipsel-none-elf mipsel-unknown-elf, mipsel-elf, mipsel MIPS32r2, little-endian, o32 soft float bare metal ELF32
x86_64-emblink x86-64 EmbLinkOS ELF64
aarch64-emblink AArch64 EmbLinkOS ELF64
x86_64-linux-gnu x86_64-linux x86-64 Linux, static ELF64
aarch64-linux-gnu aarch64-linux AArch64 Linux, static ELF64
x86_64-apple-darwin x86_64-darwin x86-64 macOS Mach-O
aarch64-apple-darwin arm64-apple-darwin, aarch64-darwin AArch64 macOS Mach-O
x86_64-windows-gnu x86_64-w64-mingw32 x86-64 Windows COFF

The ARM sub-architecture also follows from --cpu= and the float ABI, so --target=thumbv7m-none-eabi --cpu=cortex-m4 reports itself as thumbv7em-none-eabi. Every compile for x86_64-windows-gnu prints the --warn=windows-abi warning, because the Windows calling convention is not yet complete. What each target supports is in Targets.

The default target#

With no --target=, the target is chosen in this order:

  1. the environment variable EMBCC_DEFAULT_TARGET, if set and not empty;
  2. the DEFAULT_TARGET the compiler was built with (make DEFAULT_TARGET=riscv32-unknown-elf; see Getting started);
  3. x86_64-elf, or in a compiler built without x86 (make TARGETS=; see Choosing the targets) the first family it has: thumbv7m-none-eabi for TARGETS=arm.

--target= on the command line overrides both. A default that is not an accepted triple stops every invocation, naming where it came from:

embcc: error: the default target 'bogus' is not one EmbCC knows
embcc: it came from EMBCC_DEFAULT_TARGET in the environment
embcc: the targets it emits for are:
...

embcc --help and embcc --version print the default this compiler uses, and embcc --print-target prints the target in effect.

Options for every target#

--endian=little, --endian=big#

GCC: -mlittle-endian, -mbig-endian.

A target has one byte order, which its triple names, so either option is accepted when it says what the target is and refused when it does not: embcc: error: --endian=big is not supported for x86_64-elf, which is little-endian. The big-endian targets are PowerPC, SPARC, ColdFire, and MIPS under the mips-none-elf and mips64-none-elf triples; the others are little-endian (Target list).

x86-64 options#

--no-sse, --general-regs-only#

GCC: -mno-sse, -mno-sse2, -mgeneral-regs-only.

Generate no SSE instructions, for code that runs before the kernel has enabled SSE. Floating-point arithmetic needs SSE on x86-64, so a unit that uses it is refused: floating point needs SSE, which --no-sse forbids. On AArch64, --general-regs-only (and --no-sse) similarly keeps code out of the floating-point registers, and floating point is refused with floating point used under --general-regs-only (in 'f'). On the other targets these options are accepted and have no effect.

--no-red-zone#

GCC: -mno-red-zone; also -mno-mmx and -mno-80387.

Accepted on every target and without effect: EmbCC never uses MMX or the x87 register stack for ordinary code, and never uses the red zone.

--code-model=MODEL#

Accepted on every target, with any MODEL, and without effect. On x86-64 the code model EmbCC uses already suits a kernel linked in the upper 2 GiB of the address space.

ARM options#

These options apply to the Thumb targets. On any other target each one is refused: --cpu=cortex-m4 is an ARM option, and the target is x86_64-elf (--abi= is the RISC-V option of that name on RISC-V, below).

--isa=thumb#

Accepted. Thumb is the only instruction set a Cortex-M has.

--isa=arm#

Refused: --isa=arm is not supported: a Cortex-M has no ARM instruction set, only Thumb.

--cpu=CPU#

Select the processor. cortex-m0, cortex-m0plus and cortex-m1 select ARMv6-M (Thumb-1) code on any ARM triple. cortex-m4 and cortex-m7 select ARMv7E-M (with the DSP extension), cortex-m3 ARMv7-M, and cortex-m33 ARMv8-M Mainline with the DSP extension, on any Thumb triple, as clang takes them: the part has one architecture, whatever the triple said. The DSP extension defines __ARM_FEATURE_DSP and lets inline asm and .s files use its instructions (Inline assembly).

cortex-m23 selects ARMv8-M Baseline on any ARM triple, as thumbv8m.base-none-eabi does (Targets).

--isa=ARCH, --tune=CPU#

GCC: -march=ARCH, -mtune=CPU.

--isa= selects the level as --cpu= does:

  • armv6-m and armv6s-m give ARMv6-M;
  • armv7-m gives ARMv7-M, and armv7e-m ARMv7E-M;
  • armv8-m.base gives ARMv8-M Baseline, and armv8-m.main ARMv8-M Mainline.

GCC's extensions are accepted after +:

  • +fp names the architecture's single-precision unit: FPv4-SP-D16, or FPv5-SP-D16 on ARMv8-M;
  • +fp.dp names FPv5-D16, and +nofp none;
  • +dsp and +nodsp apply on armv8-m.main, which without +dsp (as the bare thumbv8m.main-none-eabi triple) has no DSP extension.

The unit is used when --fpu= is not given or is auto, and --float-abi= decides whether it is used at all, as with GCC. Any other architecture or extension is refused by name.

--tune= chooses a core to schedule for, which EmbCC does not do. Any Cortex-M core is accepted and changes nothing; anything else is refused. On armv7a-none-eabi, --isa=armv7-a and any --tune=cortex-a* are accepted.

Any other CPU is refused: --cpu=cortex-m55 is not a part EmbCC knows: it emits ARMv6-M (cortex-m0, m0plus, m1), ARMv8-M Baseline (cortex-m23), ARMv7-M and ARMv7E-M (cortex-m3, m4, m7) and ARMv8-M Mainline (cortex-m33).

--cmse#

Compile for the Secure state of an ARMv8-M part (TrustZone-M, ACLE's CMSE): cmse_nonsecure_entry and cmse_nonsecure_call take effect, and __ARM_FEATURE_CMSE is 3. Only on thumbv8m.main-none-eabi and thumbv8m.base-none-eabi, and only with soft float. See Targets.

--fpu=FPU#

Name the floating-point unit. EmbCC generates code for three units: the single-precision fpv4-sp-d16 (Cortex-M4F, ARMv7E-M) and fpv5-sp-d16 (Cortex-M33, ARMv8-M Mainline), and the double-precision fpv5-d16 (Cortex-M7, ARMv7E-M). none, soft and auto mean no FPU. The unit must match the architecture, on ARMv7 the architecture must be ARMv7E-M, and fpv5-d16 is refused with a --cpu= other than cortex-m7. An ARMv6-M part has no FPU, so every unit, and --float-abi=softfp or hard, is refused there:

--fpu=fpv4-sp-d16 is an ARMv7E-M unit, and the part is ARMv7-M (a Cortex-M3 has no FPU); add --cpu=cortex-m4
--fpu=fpv5-d16 is the Cortex-M7's double-precision unit, and --cpu=cortex-m4 does not have it; the Cortex-M4F's is --fpu=fpv4-sp-d16
--fpu=fpv5-d16 is not supported on thumbv8m.main-none-eabi: EmbCC emits VFP for the Cortex-M33's unit (--fpu=fpv5-sp-d16) and nothing else: ...
--fpu=fpv5-d16 on thumbv6m-none-eabi: an ARMv6-M core (Cortex-M0, M0+, M1) has no FPU, so floating point is soft and travels in the core registers

With a single-precision unit, float arithmetic is VFP instructions and double arithmetic is runtime calls. With fpv5-d16 both are VFP instructions: double arithmetic, comparisons, fabs, negation, __builtin_sqrt and the conversions to and from float and 32-bit integers are .f64 instructions, and only the conversions between a floating type and a 64-bit integer are still calls. __ARM_FP is 0x4 for the single-precision units and 0xc for fpv5-d16.

--fpu= alone generates no FPU instructions; --float-abi= decides that, as in GCC.

--float-abi=ABI#

ABI Effect
soft No FPU instructions; floating point is done by runtime calls. The default, except for the -eabihf triples.
softfp FPU instructions; floating-point arguments and results passed in core registers. Links with soft objects.
hard FPU instructions; floating-point arguments and results passed in s0-s15/d0-d7 (AAPCS-VFP). Does not link with soft objects.

softfp and hard need an FPU (--float-abi=hard needs an FPU to use: add --fpu=fpv4-sp-d16 (Cortex-M4F), --fpu=fpv5-d16 (Cortex-M7) or --fpu=fpv5-sp-d16 (Cortex-M33)). Any other value is refused: --float-abi=ABI is not an ARM float ABI: it is one of soft, softfp and hard.

--fpu= and --float-abi= are resolved together after the whole command line is read, so their order does not matter. An -eabihf triple implies the part's FPU and --float-abi=hard -- fpv4-sp-d16 for thumbv7em-none-eabihf, or fpv5-d16 with --cpu=cortex-m7 -- and an explicit option overrides it. The choice is recorded in the object's .ARM.attributes, in the predefined macros (__ARM_FP, __ARM_PCS_VFP, __SOFTFP__), and in the triple --print-target prints.

--abi=ABI#

aapcs and aapcs-linux are accepted: EmbCC emits the AAPCS (the AAPCS-VFP variant under --float-abi=hard), and aapcs-linux differs from it only in making every enumeration int-sized, which EmbCC's are. The other values (apcs-gnu, atpcs, iwmmxt) are refused: --abi=apcs-gnu is not supported: EmbCC emits the AAPCS ....

-mthumb-interwork, -mno-thumb-interwork#

Accepted. Interworking is between ARM and Thumb code, and a Cortex-M runs only Thumb: every call and return is already one a bx or blx would make.

--unaligned-access, --no-unaligned-access#

--unaligned-access is accepted: it is the ARMv7-M and ARMv8-M default, and what EmbCC's code relies on. --no-unaligned-access is refused, because EmbCC cannot keep it: a packed structure's int member is one ldr at its odd address, and a structure aligned below 4 (packed, or struct { char c[5]; }) is copied and passed by value a word at a time from wherever it lies.

--no-unaligned-access is not supported: EmbCC's ARMv7-M code uses word and halfword loads and stores at unaligned addresses (packed struct members; copies and by-value passing of structs aligned below 4), which the architecture allows and this flag forbids

-mslow-flash-data#

Accepted. It asks the compiler to keep constants out of literal pools in flash, and EmbCC's compiled Thumb code has none: it builds constants and addresses with movw/movt. (An assembly file's ldr rd, =VALUE still gets a pool entry where it needs one.)

RISC-V options#

--isa=ISA#

The instruction set, as GCC and clang spell it: rv32 or rv64 (the target's width), the base i -- or g, which is imafd with Zicsr and Zifencei -- then single-letter extensions in any order, then _zicsr and _zifencei. EmbCC emits I, M, A, F, D and C:

  • M is required (the code multiplies and divides with mul and div);
  • A gives the atomics as lr/sc loops and AMOs. Without it, as on an ESP32-C3 (--isa=rv32imc), every atomic exchange, compare-exchange and fetch-and-op is a call to libatomic's sized routines (__atomic_fetch_add_4 and its kin), as GCC and clang make it, and an aligned load or store stays one instruction. The program links the runtime and C library built without A, from <triple>/noa (whose routines mask interrupts: see Embedded), and the A extension's macros are left out (__riscv_atomic, __GCC_HAVE_SYNC_COMPARE_AND_SWAP_N, with every lock-free macro 1);
  • F lets float arithmetic, comparisons, square roots and conversions run on the FPU, and D the same for double; D needs F;
  • without C nothing is compressed and EF_RISCV_RVC is clear.

Anything else is refused by name: --isa=rv32imafcv: the 'v' extension is not supported, --isa=rv32imadc: the D extension needs F. The default is rv32imac and rv64imac. The predefined macros follow (__riscv_f, __riscv_d, __riscv_flen, __riscv_fdiv, __riscv_fsqrt, __riscv_zicsr, __riscv_zcf, __riscv_zcd, and the compressed ones only with C), as clang defines them for the same flags.

--abi=ABI#

Where floating point travels across a call: ilp32/lp64 in the integer registers (__riscv_float_abi_soft), ilp32f/lp64f a float in fa0-fa7 (__riscv_float_abi_single), ilp32d/lp64d a double too (__riscv_float_abi_double) -- with the psABI's flattening of small structs into floating-point registers. Without --abi= it follows --isa= as clang's does: D gives the double ABI, F alone the single one. A float ABI wider than the --isa= has FPU is refused (--abi=ilp32d passes doubles in floating-point registers, and --isa=rv32imafc has no D extension), as are ilp32e and the other width's ABIs. Objects of two float ABIs do not link (embld refuses them), and the driver links a hard-float ABI's librt.a and libc.a from <triple>/<abi> (riscv32-unknown-elf/ilp32f), which make rt-embedded libc-embedded builds.

MIPS options#

These options apply to mipsel-none-elf; on any other target they are unknown arguments (and --cpu= is an ARM option). EmbCC emits one MIPS configuration -- MIPS32 Release 2, little-endian, o32, soft float, no abicalls, no small data -- so each option is accepted when it asks for that and refused by name when it asks for anything else.

--cpu=CPU, --isa=CPU#

Accepted for a MIPS32 Release 2 core: mips32r2, m4k, m14k, m14kc, 24kc, 24kf, 24kec, 24kef, 34kc and 74kc. The code is the same for every one. Any other value, Release 6 and the pre-Release 2 ISAs included, is refused: --cpu=mips32r6 is not a MIPS32 Release 2 core: EmbCC emits MIPS32r2 (mips32r2, m4k, m14k, m14kc, 24kc, 24kf, 24kec, 24kef, 34kc, 74kc).

--abi=32 and the MIPS defaults#

GCC: -mabi=32, -msoft-float, -EL, -mno-abicalls, -G0.

Accepted; each names what EmbCC does anyway. Their opposites are refused:

--abi=n32 is not supported: EmbCC emits the o32 ABI (--abi=32) only
-mhard-float is not supported: EmbCC emits soft-float o32, which passes floating point in the integer registers
-EB is not supported: the MIPS target is little-endian (mipsel) only
-mabicalls is not supported: EmbCC's MIPS code takes addresses absolutely (lui/addiu) and keeps no $gp; it is -mno-abicalls code
-G8 is not supported: EmbCC puts no data in .sdata and addresses nothing through $gp (-G0)

Machine options that are not accepted#

-masm=, -m32 and -m64 are unknown arguments. --mcu= is refused on every target but AVR, and --cpu=, --isa=, --tune= and --abi= on a target whose section here and in Targets does not list them: embcc: error: --isa=x86-64 is not an option for x86_64-elf. The architecture, ABI and part are selected by the target triple (and on ARM by --cpu=, --fpu= and --float-abi=). The RISC-V targets generate the C (compressed) extension and the integer multiply/divide instructions; the AVR target generates code for the avr5 architecture of the ATmega328P, and --mcu= names the part (atmega328p, atmega328, atmega168p or atmega168). See Targets.

Optimization#

What each level and pass does is described in Optimization. This section lists the options.

--opt=none, -O0#

No optimization. This is the default. The optimizer does not run, so the per-pass options have no effect at --opt=none. Every source variable stays in its stack slot; on ARM Cortex-M and RISC-V the temporaries of expressions get registers (see Optimization).

--opt=basic, -O1, -O#

Optimize without making the code larger, as GCC's and Clang's -O1 do. Every backend allocates registers, with tail calls where its conditions allow them, and the optimizer runs the passes that only remove work: the local passes, cfg-clean, mem2reg, sroa, sccp, dse, load-cse, licm, if-convert and div-magic, and inlines a static function with a single caller and an always_inline function. Static functions that are unreachable after optimization are not emitted.

--opt=speed, -O2#

Everything in --opt=basic, plus every other pass in the pass table (the vectorizer on x86-64 only): full inlining, gcse, pre, unroll, switch-thread, tail-recursion and idiom.

--opt=max, -O3#

The same as --opt=speed. The output is identical.

--opt=size, -Os, --opt=min-size, -Oz#

Optimize for size: --opt=speed with the vectorize, unroll and switch-thread passes off, a smaller inlining budget, and size-directed choices in IR generation and in the backends. --opt=min-size is the same as --opt=size.

--opt=debug, -Og#

Optimize for debugging: --opt=basic, with every source variable kept in its stack slot, so a debugger reads each one where the debug information says it is. See Debugging.

--pass=NAME, --no-pass=NAME#

Also accepted: -fNAME, -fno-NAME (EmbCC's earlier spelling).

Turn one optimizer pass on or off, overriding what the -O level chose. A pass turned on this way also runs at --opt=basic, which lets one pass be tried on its own. The pass names are:

Pass Default at What it does
mem2reg --opt=basic promotes local variables to SSA values
gcse --opt=speed dominator-scoped global common-subexpression elimination
load-cse --opt=basic global redundant-load elimination
sccp --opt=basic resolves constant branches and drops dead blocks
licm --opt=basic loop-invariant code motion, loop rotation and strength reduction
vectorize --opt=speed, x86-64 only, not --opt=size lane-wise loops
inline --opt=basic (a single caller or always_inline only), --opt=speed inlines small callees into their callers
dse --opt=basic removes a store a later store overwrites
div-magic --opt=basic divides by a constant without a divide instruction
if-convert --opt=basic replaces a two-way branch with a select
cfg-clean --opt=basic threads jumps and removes unreachable code
tail-recursion --opt=speed turns a self tail call into a loop
idiom --opt=speed turns a copy or clear loop into memcpy/memory clear
sroa --opt=basic splits a private aggregate into scalars
unroll --opt=speed, not --opt=size unrolls counted loops
pre --opt=speed partial-redundancy elimination
switch-thread --opt=speed, not --opt=size jumps from a known state straight to its switch case
licm-mem --opt=basic moves loads out of loops and keeps a loop's variable in a register

A --pass=, --no-pass=, -f or -fno- option that is not a pass name and not listed elsewhere on this page is an unknown argument. GCC's pass options are accepted only where this page lists them (below); -ftree-vectorize, for example, is not.

--inline=declared, --inline=auto#

GCC: -fno-inline-functions, -finline-functions.

--inline=declared is honoured: the inliner takes only functions declared inline in some declaration, or marked always_inline, at every level. A function its author did not mark stays a call (and a symbol in the image) at --opt=speed and --opt=size as at --opt=basic, including a static function with a single caller, which GCC's option would still inline (-finline-functions-called-once). embcc why not-inlined reports not-declared-inline for such a call. C++ member functions defined in their class are not marked inline in the C the C++ front end lowers to, so under this option they are not inlined. --inline=auto, the --opt=speed default, restores the default.

--unroll, --no-unroll#

GCC: -funroll-loops, -fno-unroll-loops, -funroll-all-loops.

The unroll pass, as --pass=unroll and --no-pass=unroll name it. The pass is on by default at --opt=speed. --unroll turns it on at --opt=basic and --opt=size too.

--fp-contract=off, --fp-contract=on, --fp-contract=fast#

GCC: -ffp-contract=off, -ffp-contract=on, -ffp-contract=fast.

EmbCC never fuses a multiply and an add into one instruction; an fma is only ever __builtin_fma's. So off is kept, and on and fast, which only permit fusing, change nothing.

--fast-math, --no-math-errno#

GCC: -ffast-math, -fno-math-errno.

Accepted and without effect. Both give the compiler permission to relax IEEE arithmetic or to assume the math library sets no errno; EmbCC uses neither permission. So are the permissions --fast-math is made of: -funsafe-math-optimizations, -fno-signed-zeros, -fno-trapping-math, -ffinite-math-only, -fassociative-math and -freciprocal-math. __FAST_MATH__ is not defined, so a header that tests it takes its careful path, as it should for code that is not compiled fast-math.

--remarks, --remarks=json#

Also accepted: -fremarks, -fremarks=json (EmbCC's earlier spelling).

Report what the optimization passes decided and why. The report is written to standard error after the compile, one line per decision:

w.c:3: remark: inlined 'sq': 4 instructions into f, budget 24 [inline/small-enough]
w.c:1: remark: promoted-to-register 's' [mem2reg/scalar-and-never-addressed]
w.c:1: remark: unrolled 'big': 8 copies of a 5-instruction body, the original kept for the remainder [unroll/counted-loop]

--remarks=json writes the same records as a JSON array of objects with the keys pass, decision, subject, reason, detail and location. Most decisions are made only at --opt=speed. Clang's -Rpass=, -fsave-optimization-record and GCC's -fopt-info are not accepted. See also embcc why.

Code generation#

--sections=functions, --sections=data#

GCC: -ffunction-sections, -fdata-sections.

Put each function in a section of its own, .text.NAME, and each object in .data.NAME, .rodata.NAME or .bss.NAME, as GCC names them, so that the linker's --gc-sections can drop whatever the program never reaches:

embcc --target=thumbv7em-none-eabi --opt=speed --sections=functions --sections=data -c main.c
embcc --target=thumbv7em-none-eabi --link-script=board.ld -Wl,--gc-sections startup.o main.o -o fw.elf

A function or object that names its own section keeps it, and so does a thread-local object (.tdata/.tbss). String literals stay in the unit's .rodata. A call from one function to another in the same unit becomes a relocation, as it is between units; on RISC-V that is the eight-byte call instead of a four-byte jal, because embld does not relax. -g works with either option: the unit's code is described by DW_AT_ranges. ELF output only; for Mach-O and COFF the options change nothing. They are off by default; GCC's -fno-function-sections and -fno-data-sections are accepted. What the linker keeps is in embld.

--unwind-tables#

GCC: -funwind-tables, -fasynchronous-unwind-tables.

Emit unwind tables (.eh_frame) so that a C++ exception can unwind through the unit's functions. --exceptions has the same effect for C.

The default depends on the language and target. Unwind tables are always emitted for C++ units (unless both --no-exceptions and --no-unwind-tables are given). For C they are emitted by default on the hosted ELF targets (-linux-gnu), where an exception may unwind through C library frames, and not on the freestanding targets.

--no-unwind-tables#

GCC: -fno-unwind-tables, -fno-asynchronous-unwind-tables.

Do not emit unwind tables for a C unit. For a C++ unit with exceptions enabled, tables are emitted regardless.

--no-frame-pointer, --frame-pointer#

GCC: -fomit-frame-pointer, -fno-omit-frame-pointer.

Accepted and without effect: each backend decides where a function keeps a frame pointer (Frame pointer).

--no-jump-tables, --jump-tables#

GCC: -fno-jump-tables, -fjump-tables.

--no-jump-tables is honoured on every target: no switch is lowered through a table of addresses, which code that runs before it is relocated, or from an address other than the one it was linked at, cannot use. A switch dense enough for a table (n cases, at least four, whose values span at most 4n + 4; at --opt=size at least six, spanning at most 2n) becomes the balanced compare tree that sparse switches always get. The tables this removes are x86-64's indirect jmp, AArch64's br, ARM's tbb/tbh and RISC-V's jr through a table; the AVR target never uses one. --jump-tables, the default, turns them back on.

Warnings and diagnostics#

The diagnostic format, the warnings themselves, --explain and fix-its are described in Diagnostics. This section lists the options.

--warn=all, --warn=extra#

GCC: -Wall, -Wextra, -W.

Turn on the warnings in the all or extra set. As in GCC, --warn=extra does not include --warn=all. -W is GCC's old spelling of -Wextra.

--warn=NAME#

GCC: -WNAME.

Turn on the warning NAME. A name EmbCC does not have is not an error:

embcc: warning: -Wcast-align is not a warning EmbCC has, so it turns nothing on (--help-warnings lists them)

Any --warn=NAME, and any argument beginning with -W that matches nothing else, is handled this way, including --warn=format=2. -Wa,..., -Wl,... and -Wp,... are options of their own (see Assembling and linking and -Wp,ARGS).

-Wimplicit-function-declaration, --error=implicit-function-declaration and GCC's older -Werror-implicit-function-declaration are accepted without a word: in EmbCC a call to an undeclared function is always an error ('g' is not declared in 'f', E0001), which is what they ask for. --no-error=implicit-function-declaration cannot make it a warning, and says so.

--no-warn=NAME#

GCC: -Wno-NAME.

Turn off the warning NAME. An unknown NAME is accepted silently. --no-error=NAME is a different option; see above.

--no-warnings#

GCC: -w.

Suppress all warnings. Takes precedence over every --warn= option, including --warnings-are-errors.

--warnings-are-errors, --no-warnings-are-errors#

GCC: -Werror, -Wno-error.

--warnings-are-errors reports every warning as an error, and the compile fails. --no-warnings-are-errors turns that off again; the last one wins.

A compile that fails because of a warning made an error writes no output: the object, assembly file or executable it was producing is removed, so that make does not take it as up to date. A file of that name left by an earlier compile is removed as well. The dependency file of --deps or --deps-all and the .su file of --stack-usage are still written.

--error=NAME, --no-error=NAME#

GCC: -Werror=NAME, -Wno-error=NAME.

--error=NAME reports the warning NAME as an error, with or without --warnings-are-errors, and turns the warning on. --no-error=NAME keeps NAME a warning under --warnings-are-errors; it does not turn the warning on. Either form decides for its one warning whatever --warnings-are-errors and --no-warnings-are-errors say, and wherever it appears on the command line. --no-warnings still suppresses the warning. A NAME that is not a warning EmbCC has is reported and ignored:

embcc: warning: -Werror=cast-align names no warning EmbCC has (--help-warnings lists them)

The warnings that no option controls (see Diagnostics) follow --warnings-are-errors alone.

--warn-in-system-headers#

GCC: -Wsystem-headers.

Report warnings in system headers. By default a warning whose location is in a header found through --system-include or through EmbCC's own include directories is not reported.

--pedantic, --pedantic-errors#

GCC: -pedantic, -pedantic-errors. --warn=pedantic (-Wpedantic) is --pedantic.

Report what the C standard named by --std= does not have: a later standard's feature, or a GNU extension. --pedantic reports each as a warning, and --pedantic-errors as an error:

embcc: f.c:1:9: warning: a binary constant is new in C23, and the standard here is C17 [--warn=pedantic]
embcc: f.c:4:12: warning: a statement expression is a GNU extension, not ISO C [--warn=pedantic]
embcc: f.c:7: warning: an old-style (K&R) parameter list is not C23: that standard removed it [--warn=pedantic]

Either also takes C23's keywords and syntax away from a GNU dialect before C23, so that --std=gnu17 --pedantic reads what GCC's gnu17 reads.

Reported, before the standard that has it: from C99, long long, _Bool, _Complex, designated initializers, compound literals, flexible array members, variable-length arrays, a declaration after a statement, and // comments in gnu89; from C11, _Static_assert, _Noreturn, _Thread_local, _Generic, _Alignas, _Alignof, _Atomic and anonymous struct and union members; from C23, binary constants, [[...]] attributes, #elifdef, #elifndef, #warning, an empty initializer {}, an enum's fixed underlying type, a parameter with no name in a definition, a static assertion with no message, and a label before a declaration or at the end of a block; from C2y, _Countof, 0o constants and case ranges. In every standard: statement expressions, ?: with no middle operand, arrays of size 0, a struct or union with no members, designator ranges, computed goto, a label's address, __int128 and #include_next; and, from C23, an old-style (K&R) parameter list.

Nothing is reported in a system header, or after __extension__, up to the end of the declaration or expression it begins, as in GCC.

For C++ the same options report what the --std= C++ standard does not have, from C++11's lambdas, long long, [[...]] attributes and = default in C++98 to C++23's if consteval and explicit object parameters and C++26's = delete("why"); register from C++17, which removed it; and GNU's statement expressions, case ranges, ?: with no middle operand, variable-length and zero-length arrays, flexible array members and __int128. As for C, not in a system header.

The warnings#

Every warning, what it diagnoses, and the set that turns it on, is in the warning reference, which the compiler prints from its own table (embcc --reference=warnings); embcc --help-warnings prints the same list in short.

For a C++ input, unused-variable, unused-parameter, unused-function, shadow and sign-compare are always off, even when named on the command line, because they would be reported against code the C++ lowering generated.

--diagnostics=FORMAT#

GCC: -fdiagnostics-format=FORMAT.

text (the default) prints caret diagnostics. json prints the diagnostics as one JSON array in GCC's format, on standard error. Any other value is refused with embcc: unknown diagnostic format 'FORMAT' (text, json).

--color=WHEN#

GCC: -fdiagnostics-color[=WHEN], -fno-diagnostics-color.

Control color in text diagnostics. --color=always forces it on, and --color=never turns it off. Any other value, including auto, selects the default: color when standard error is a terminal. Clang's -fcolor-diagnostics spelling is not accepted.

--max-errors=N#

GCC: -fmax-errors=N.

Stop after N errors with embcc: compilation terminated due to --max-errors=N. 0, the default, means no limit. Clang's -ferror-limit= is not accepted.

--explain=ID#

Print the long explanation of diagnostic ID (E0001 and up) and exit. Every diagnostic that has an explanation prints its ID in brackets. With no ID, list the IDs that have explanations. An unknown ID ends with embcc: no explanation for 'ID'; embcc --explain lists what there is and status 1. See Diagnostics.

--fix#

Apply the fix-its the diagnostics propose, editing the source file in place, and write no object. Implies --syntax-only. When it changed something the driver reports embcc: 1 fix applied; compile again (or N fixes) and exits with status 0; otherwise it reports embcc: nothing to fix automatically and exits with the compile's status.

Debugging and reports#

-g, --debug#

Emit DWARF version 4 debug information (.debug_info, .debug_abbrev, .debug_line) on every ELF target. See Debugging.

-g1, -g2, -g3, -ggdb, -gdwarf, -gdwarf-2, -gdwarf-3 and -gdwarf-4 are all accepted and mean -g: EmbCC emits one kind of debug information, so a level or version that asks for no more than DWARF 4 gets exactly that.

-g can change the code generated. On some targets it keeps a frame and a stack home for each variable, and it disables tail calls on ARMv7-M, so that the debugger can find every variable. Build with the same -g setting you ship with if you compare code size.

-g is refused in three situations, each with an error naming the cause:

  • for the -apple-darwin targets (-g is not supported for a Darwin target yet: its DWARF goes in a __DWARF segment this does not write ...);
  • for x86_64-windows-gnu (-g is not supported for a Windows target yet: its debug information goes in CodeView records this does not write ...);
  • for a unit that places a function in a named section with __attribute__((section)) (-g with a function in a section of its own ('NAME') is not supported yet ...).

Refused debugging options#

A DWARF version EmbCC does not emit, split DWARF and compressed debug sections are refused rather than downgraded:

embcc: error: -gdwarf-5 is not supported; EmbCC emits DWARF 4, uncompressed and in one piece

The same message is given for -gsplit-dwarf and -gz. -g0 and -gline-tables-only are not accepted (unknown argument); to compile without debug information, omit -g.

--stack-usage#

GCC: -fstack-usage.

Write a stack-usage file beside the output: the output name with its suffix replaced by .su (foo.o gives foo.su). It has one line per function that was emitted, in GCC's format:

w.c:3:f	8	static

The fields are FILE:LINE:FUNCTION, the frame size in bytes, and the qualifier: static, or dynamic for a function whose frame grows at run time (a variable-length array or alloca), whose number is then only the fixed part. The frame includes everything the prologue reserves, a variadic function's register save area among it. Functions that inlining absorbed or that were dropped as unreachable are not listed.

The .su name is derived from the output file, or, with no -o, from the source's name in the current directory, as GCC does.

--call-graph, --call-graph=su#

GCC: -fcallgraph-info, -fcallgraph-info=su.

Write the call graph beside the output as FILE.ci, in GCC's format: a VCG graph with a node per emitted function (with =su, its frame and qualifier as in --stack-usage), an elliptic node per external callee, and an edge per call site left after optimisation, labelled with its location. A call through a pointer is an edge to the __indirect_call placeholder. A function the inliner absorbed is not a node: its calls appear under its callers.

graph: { title: "w.c"
node: { title: "main" label: "main\nw.c:5:5\n16 bytes (static)" }
node: { title: "g" label: "g" shape : ellipse }
edge: { sourcename: "main" targetname: "g" label: "w.c:5:18" }
}

A backend's calls to run-time helpers (a soft-float operation, a 64-bit divide) are not in the graph: they are in the object's relocations. embrt reads all three -- .su, .ci and the object -- to bound each entry point's stack.

--time-report#

GCC: -ftime-report.

Print where the compile's time went, phase by phase, on standard error at exit, as GCC and clang do. The times are processor time (ISO C clock()), so they mean the same on every host. A phase not listed (writing the object, linking) is counted in "object and the rest". The output is otherwise unchanged: the object is the same with or without it.

Execution times (seconds, processor time)
 startup               :    0.001 (  0%)
 preprocess            :    0.039 (  4%)
 parse                 :    0.006 (  1%)
 semantic analysis     :    0.004 (  0%)
 IR generation         :    0.002 (  0%)
 optimization          :    0.958 ( 91%)
 code generation       :    0.039 (  4%)
 object and the rest   :    0.000 (  0%)
 TOTAL                 :    1.050

A C++ unit also lists "C++ front end". With several inputs, each phase is the sum over them.

--instrument-functions, --no-instrument-functions#

GCC: -finstrument-functions, -fno-instrument-functions.

GCC's: every function calls

void __cyg_profile_func_enter(void *this_fn, void *call_site);
void __cyg_profile_func_exit(void *this_fn, void *call_site);

on entry and before each return. this_fn is the function's address and call_site its return address (__builtin_return_address(0)). A return calls the exit hook after its value is computed. An inlined function keeps its calls.

Left alone:

  • a function marked no_instrument_function on any of its declarations;
  • naked functions and interrupt handlers;
  • the two hooks themselves.

lib/rt defines both hooks, weak, as EmbTrace's recorder: a ring of events that embtrace_dump() writes out for embtrace on the host. See embtrace. A program that defines the hooks itself replaces them.

--instrument-exclude-functions=LIST, --instrument-exclude-files=LIST#

GCC: -finstrument-functions-exclude-function-list=LIST, -finstrument-functions-exclude-file-list=LIST.

LIST is comma-separated.

  • Function list: a function whose name is on it, exactly, is not instrumented.
  • File list: a function is not instrumented when any item occurs in the path of the file that defines it. The match is a substring, as GCC's is, so lib2 matches src/lib2.c.

--sanitize=LIST, --no-sanitize=LIST#

GCC: -fsanitize=LIST, -fno-sanitize=LIST.

Insert run-time checks for undefined behavior. LIST is comma-separated; the checks EmbCC has are:

Name Checks
signed-integer-overflow signed +, -, * that overflow
integer-divide-by-zero integer division and remainder by zero
shift, shift-exponent a shift count out of range
undefined all three of the above

A failed check executes the target's trap instruction: under a debugger that stops at the faulting operation, and without one the program stops. There is no diagnosing runtime, so trap mode is the only mode. At --opt=speed the optimizer removes checks whose operands it can prove safe.

--no-sanitize=LIST removes checks from the set. Options accumulate in command-line order.

A check EmbCC does not have is refused by name:

embcc: error: -fsanitize=address is not supported: EmbCC's sanitizer inserts checks that TRAP, and this one needs a runtime library to report through. The ones it has are undefined, signed-integer-overflow, integer-divide-by-zero and shift

Dependencies#

A build system that tracks headers asks EmbCC for a make rule naming the headers each source includes.

--deps=FILE, --deps-all=FILE#

GCC: -MMD -MF FILE, -MD -MF FILE.

Compile normally and also write the rule (all headers, or non-system headers only) to a file named after the object with its suffix replaced by .d, or to FILE. The rule is written once the unit has passed semantic analysis.

--deps-only#

GCC: -MM.

Write the make rule for the unit instead of compiling it, leaving out headers found in system directories (those added with --system-include and EmbCC's own). GCC's -M keeps them.

--deps-target=TARGET#

GCC: -MT TARGET, -MQ TARGET.

Use TARGET as the rule's target. GCC's two spellings are identical in EmbCC: -MQ does not quote characters special to make. When given more than once, the last one is used (GCC would list them all).

--deps-phony#

GCC: -MP.

Also write an empty rule for each header, so that make does not fail when a header is deleted.

Assembling and linking#

Assembly input#

A .s or .S file is assembled by EmbCC's built-in GNU-syntax assembler for the selected target: AArch64, ARM (Thumb), RISC-V, MIPS32 or AVR. A .S file is preprocessed first; -D and -U apply, but the -I and --system-include directories are not searched (a #include "file" beside the source is found). The inline-assembly vocabulary of each target is listed in Inline assembly.

The assembler follows GNU as for each target:

  • Comments. /* ... */ anywhere; # starts a comment, except on ARM and AArch64, where it is an immediate's prefix (mov r0, #1) and starts a comment only as a line's first character. @ is ARM's comment character and ; AVR's (elsewhere ; separates statements); // works everywhere.
  • Macros and expressions. .macro, .rept, .irp, the .if family, .include, .equ/.set/.thumb_set, named sections with flags, and GNU as's expressions; the full list is in embas.
  • Alignment. .align N and .p2align N align to 2^N bytes, and .balign N to N bytes. Padding in code is the target's no-op instruction, and zero bytes in data.
  • Operand words. A special register, interrupt mask, IT condition, barrier option or floating-point register on ARM, and a CSR name or a fence set on RISC-V, is an operand and never a symbol.
  • Symbols. On ARM, a symbol is reached by bl sym, b sym, ldr rd, =sym (a load from the literal pool, as GNU as makes it), movw rd, #:lower16:sym, movt rd, #:upper16:sym and .word sym. ldr rd, label loads a word from a label defined in the same file. .thumb_func, or .type sym, %function, makes a label a Thumb function: its symbol carries the interworking bit, which a vector table entry needs. A symbol the file uses but does not define is an external reference. On MIPS32, jal sym and j sym, lui rt, %hi(sym), addiu rt, rs, %lo(sym), a load or store at %lo(sym)(rs), la rt, sym and .word sym; a branch reaches only labels in the same section.
  • MIPS32's modes. A file starts in .set reorder, GNU as's default: the assembler puts a nop in every delay slot. .set noreorder makes the instruction after a transfer its slot; .set push/.set pop save and restore the mode. .ent, .end NAME, .frame, .mask, .fmask and .insn are accepted as markers (.end with a name does not end the file there, as it does elsewhere). .abicalls, .cpload, .cprestore, .gpword, .option pic2 and the %got/%call16/%gp_rel operators are refused: EmbCC builds neither PIC nor small-data code.
  • Sizes. .size sym, .-sym records a function's size, which a debugger uses to attribute addresses to it.

For x86-64 targets, GNU-syntax assembly files are refused:

embcc: error: no assembly-file support for x86_64-elf yet; its instruction encoder exists (inline __asm__ works) but this driver has not been wired to it

A .asm file is assembled as NASM/Intel-syntax x86-64 into an x86-64 ELF64 object. This is the same assembler as the standalone embas. For any target other than x86-64 with ELF objects, a .asm input is refused:

embcc: error: 'boot.asm' is NASM-syntax x86-64 assembly, which EmbCC assembles to x86-64 ELF only, and the target is thumbv7m-none-eabi

An assembly input always produces an object: -c and -S make no difference, and no link follows. The one other mode is -E, which preprocesses a .S file to standard output. Without -o the object is the input's file name with its suffix replaced by .o, in the current directory (boot/start.S gives start.o).

--assembler-arg=ARG#

GCC: -Wa,ARGS.

Options for the assembler, separated by commas. The integrated assembler takes no options. Accepted:

  • --noexecstack, -g, any option beginning with --gdwarf, and -mrelax, which change nothing it writes;
  • GNU as's listing options -a[cdghlmns][=FILE], as an STM32CubeMX Makefile passes them (-Wa,-a,-ad,-alms=build/main.lst). The listing is the -S text of the object's own bytes, written beside the object to FILE, or to standard output when no option names a file. Xtensa and RX, which have no -S text, warn and write none.

Any other option is refused, and nothing is compiled:

embcc: error: assembler option '-z' is not one the integrated assembler has

The options are checked on every invocation, whatever the input file.

Linking#

Without -c, -S, -E or --syntax-only, embcc FILE -o OUT compiles the file and links it in the same process with EmbCC's linker, embld. This is available only for x86-64 ELF targets (x86_64-elf, x86_64-emblink, x86_64-linux-gnu and their aliases). For the firmware targets (ARMv7-M, ARMv8-M, RV32, RV64, MIPS32 and AVR) the driver links too, and the build supplies the memory map: a linker script with --link-script=FILE.ld (ARM, RISC-V and AVR), or --linker-arg=-Ttext=FLASH and --linker-arg=-Tdata=RAM (on MIPS32 the message offers only these). There is no default map, because an image linked to a guessed one runs, wrongly:

embcc: error: linking a thumbv7em-none-eabi image needs its memory map: a linker script (-T FILE.ld), or -Wl,-Ttext=FLASH and -Wl,-Tdata=RAM
embcc --target=thumbv7em-none-eabi --opt=size -c main.c -o main.o
embcc --target=thumbv7em-none-eabi --link-script=STM32F407VGTx_FLASH.ld \
      startup.o main.o -o fw.elf

embld does not read AArch64, Mach-O or COFF objects, and for those targets the driver stops with embcc: error: cannot link for TRIPLE; link them with the platform's linker.

The link line, in order:

  1. crt1.o from the target's library directory, for a hosted target (not with --no-stdlib or --no-start-files; a firmware target has none, because its startup is the program's own);
  2. the object just compiled (written to OUT.embcc-tmp.o beside the output and removed afterwards; when embld itself refuses the link, for an undefined symbol or an image over --rom-limit, the temporary object is left in place);
  3. the objects, archives and -l libraries of the command line, in their order;
  4. libcxx.a, for a C++ input;
  5. libc.a, if the target has one (not with --no-stdlib or --no-default-libs);
  6. librt.a, the compiler runtime, if the target has one (likewise).

-lNAME is libNAME.a in the first -L directory that has it. -lc and -lm name EmbCC's own libc.a (its math is in it) and -lgcc its librt.a, so a link line written for arm-none-eabi-gcc that says --no-stdlib ... -lc -lgcc gets them. A library found nowhere is refused: embcc: error: cannot find libnosys.a for -lnosys in any -L directory.

For x86_64-linux-gnu the result is a static executable with no dependence on another C library. A missing crt1.o or libc.a for that target is reported by name, with what builds it: in a build tree the make target (embcc: error: no crt1.o for x86_64-linux-gnu: its C library is not built; `make libc-linux-x86_64` in TREE builds it), and for an installed compiler make install. A missing libcxx.a for a C++ program is reported the same way. For the freestanding x86-64 targets nothing supplies the entry point: the program must define _start, or be linked with embld and a start file.

The image is written without execute permission; run chmod +x on it before running it directly.

--linker-arg=ARG#

GCC: -Wl,ARGS, -Xlinker ARG.

Pass ARG to the link as one word, as -Xlinker does. GCC's -Wl, splits ARGS at its commas, so -Wl,-Ttext,0x200000 is two words. -Xlinker passes the next argument as one word, and with none the driver stops with embcc: -Xlinker needs an option. The words are kept in command-line order and read when the driver links. A compile that does not link (-c, -S, -E, --syntax-only) ignores them, as GCC does.

These are applied, with the meaning of the embld option of the same name:

Linker option Effect
-Ttext ADDR, -Ttext=ADDR, -TtextADDR, -Ttext-segment ADDR, -Ttext-segment=ADDR the address the image starts at (default 0x400000)
-Tdata ADDR, -Tdata=ADDR, -TdataADDR the address of the writable data
--rom-limit N, --rom-limit=N refuse an image whose stored bytes exceed N
--lma-offset N, --lma-offset=N load each segment at its address minus N
-e SYM, --entry SYM, --entry=SYM the entry symbol (default _start)
-T FILE, -TFILE, --script=FILE the GNU ld linker script that lays the image out (ARM and RISC-V)
-L DIR a directory for -l and for the script's INPUT/GROUP/INCLUDE
-u SYM, --undefined=SYM treat SYM as referenced, so that an archive supplies it
--orphan-handling=place|warn|error what to do with a section the script places nowhere
--gc-sections, --no-gc-sections drop the sections nothing kept refers to (see --sections=functions)
--print-gc-sections name each section dropped, on standard error
-Map FILE, -Map=FILE, --Map=FILE write a map of where every input went, in GNU ld's format
--print-memory-usage print how full each MEMORY region of the script is, as ld does
-Tstack ADDR, -Tstack=ADDR passed on: embld emits an entry stub that sets sp (RISC-V and MIPS), and refuses it elsewhere: embld: -Tstack is a RISC-V and MIPS option: ...

These are accepted and change nothing, because nothing in an image embld makes depends on them: --as-needed, --no-as-needed, -O0, -O1, -O2, --build-id, --build-id=STYLE, --no-undefined, -s, --strip-all, -S, --strip-debug, and -z followed by noexecstack, relro, norelro, now or lazy as a separate word (-Wl,-z,now). The image keeps its symbol table under -s and --strip-all.

Any other linker option is refused, and nothing is linked:

embcc: error: linker option '--section-start=.text=0' is not one EmbLD has (it takes -T, -L, -u, -e, -Ttext, -Tdata, -Tstack, --rom-limit, --lma-offset, --orphan-handling, --gc-sections, --print-gc-sections, -Map and --print-memory-usage); dropping it could build a different image from the one asked for

This covers --section-start, -zKEYWORD written as one word, and -z with any other keyword.

Linker options#

Besides --linker-arg, the driver takes these link options:

Option What happens
-lNAME, -l NAME link libNAME.a from the -L directories, at its place on the line
-LDIR, -L DIR search DIR for -l libraries
--link-script=FILE, -T FILE link with the GNU ld linker script FILE (ARM and RISC-V)
--entry=SYM, --undefined=SYM (-e, -u) the entry symbol; a symbol to treat as referenced
--no-stdlib no crt1.o, libc.a or librt.a
--no-default-libs no libc.a or librt.a
--no-start-files no crt1.o
--static accepted: every image EmbLD writes is static; with --libc=system, glibc's libc.a
--libc=embcc|system the C library: EmbCC's own (the default), or for a Linux target the system's glibc, its headers and shared objects linked by the system's linker (The C library)
--sysroot=DIR the system's root, where --libc=system finds glibc (default /)
--linker=PATH, --ld-path=PATH, -fuse-ld=NAME the linker --libc=system runs
FILE.so, FILE.so.N a shared object, linked with --libc=system; otherwise refused: embcc: error: x.so is a shared object, which EmbLD does not link; --libc=system links a Linux program against the system's
-specs=FILE, --specs=FILE accepted: nano.specs and nosys.specs choose GCC's newlib and its system-call stubs, and EmbCC links its own C library and runtime; a link notes once embcc: note: -specs=nano.specs is ignored: EmbCC links its own C library and runtime, not newlib, and a compile says nothing
-pthread with --libc=system, _REENTRANT and -lpthread; otherwise nothing (EmbCC's C library has no threads, macOS's libSystem has them)
-rdynamic, -no-pie unknown argument
-shared, -static-pie refused: embcc: error: -shared needs position-independent code, which EmbCC does not emit

Asking EmbCC#

--help, --help=gcc#

Print the usage line and a summary of the options, then exit with status 0. Recognized anywhere on the command line. The summary names the target this compiler emits for when no --target= is given.

--help-warnings#

Print every warning option EmbCC has and the set (--warn=all or --warn=extra) each belongs to, then exit with status 0. The warning reference says what each one diagnoses.

--reference=PAGE#

Print a reference page made from the compiler's own tables, then exit. options, warnings and targets print the Markdown pages this manual includes: the option reference, the warning reference and the target list. make reference writes them into docs/manual/. man prints embcc(1) in roff, which make install installs in share/man/man1; it is dated by SOURCE_DATE_EPOCH when that is set, as reproducible builds ask.

--version#

Print the version, the target in effect, the default target, the target families this compiler was built with, the languages supported, which images embld links, and what is not supported yet, then exit with status 0. Recognized anywhere on the command line; a --target= on the same command line changes the target it reports:

EmbCC 0.0.1-beta (the Emb toolchain's C and C++ compiler, for embedded systems and operating systems)
Target: x86_64-elf
Default target: x86_64-elf
Targets (every family; make TARGETS= builds fewer):
  x86        x86-64: bare metal, EmbLinkOS, Linux, Darwin and Windows
  aarch64    AArch64: bare metal, EmbLinkOS, Linux and Darwin
  arm        Cortex-M (ARMv6-M, ARMv7-M, ARMv7E-M, ARMv8-M Baseline and Mainline) and ARMv7-A
...

A compiler built with make TARGETS= (see Choosing the targets) lists only its own families, then the others under Not built:. A --target= from a family it lacks is refused rather than reported.

--print-target#

GCC: -dumpmachine.

Print the canonical name of the target in effect and exit. The name reflects --target=, the configured default, and the ARM options that change the sub-architecture or the float ABI:

embcc --target=arm-none-eabi --print-target                  # thumbv7m-none-eabi
embcc --target=thumbv7m-none-eabi --cpu=cortex-m4 \
      -mfpu=fpv4-sp-d16 -mfloat-abi=hard -dumpmachine     # thumbv7em-none-eabihf

All other options on the command line are still checked first, so an invalid ARM option combination is reported instead of a name.

--dump-predef#

Print the target's table of predefined macros as #define lines and exit. Like --print-target, it is answered after every option has been read, so --target=, --cpu=, --fpu= and --float-abi= select the table printed.

The table is the per-target set (architecture, data model, ABI). It does not include the macros the preprocessor defines for every target (__STDC__, __STDC_VERSION__, __STDC_HOSTED__, __FILE__ and the like), command-line -D definitions, the C++ macros, or __CHAR_UNSIGNED__ as changed by --char=signed/--char=unsigned. To see exactly what a compile sees, preprocess a file that uses the macro. The complete lists are in Targets.

--print-search-dirs#

Print where the driver found its own headers and libraries, then exit:

layout: build tree
self: /home/me/EmbCC/embcc
lib: /home/me/EmbCC
include: /home/me/EmbCC/lib/libcxx/include
include: /home/me/EmbCC/lib/libc/include
include: /home/me/EmbCC/include

layout is installed, build tree or none found. With none found the line include: (none -- every header needs an explicit -I) follows. See Headers and macros.

embcc inspect STAGE FILE [OPTION...]#

Run the pipeline up to STAGE and print what it built, instead of producing an object. inspect must be the first argument and STAGE the second; the rest is an ordinary command line, so -I, -D, --target= and -O apply as they would to a compile. The output goes to standard output.

STAGE Prints
tokens the tokens of the preprocessed source, with positions
pp the preprocessed source (the same as -E)
ast the syntax tree, with resolved types
symbols every declaration in the unit, with its type and location
types structure layout: member offsets, bit-fields and padding
ir EmbIR as it stands at the selected -O level
cfg the control-flow graph of each function: blocks, edges, dominators, loops
callgraph which function calls which, from the IR

embcc inspect ir FILE.ir reads EmbIR text instead of C and prints it back, which is the IR's round-trip test. With --opt=basic, --opt=speed or --opt=size, the optimizer runs on the parsed IR before it is printed, so a pass can be tried on IR written by hand. embcc inspect mir explains that EmbCC has no machine-IR level. An unknown stage is refused with embcc: inspect: unknown stage 'STAGE'. With fewer than two arguments after inspect, the stage list is printed and the status is 1.

Comparing inspect ir at --opt=none and at --opt=speed shows what the optimizer did.

embcc why DECISION [SUBJECT] FILE [OPTION...]#

Compile FILE with remarks enabled and print the recorded decisions of kind DECISION, optionally only those about SUBJECT (a function or variable name). why must be the first argument. SUBJECT is recognized when it does not start with - and contains no .; otherwise the word is taken as the file.

$ embcc why inlined w.c --opt=speed
sq (w.c:3): inlined
  because small-enough — 4 instructions into f, budget 24
  decided by the inline pass

DECISION is any decision name that appears in --remarks output, such as inlined, not-inlined, promoted-to-register, hoisted, unrolled or spilled-to-stack. When nothing matches, why says so and reminds you that optimization decisions need --opt=speed; the status is still 0.

Machine-readable output#

The driver's machine-readable forms are --diagnostics=json, -fdiagnostics-parseable-fixits, --remarks=json, --emit-interfaces and --stack-usage. The language server embls obtains its diagnostics by running embcc.

GCC flags EmbCC accepts#

GCC has flags EmbCC has no name for. Each one here is accepted, because it changes nothing EmbCC does, or read so that it can be refused by name. A build written for GCC passes them; a new one has no reason to.

-pipe#

Accepted. EmbCC uses no temporary files between its stages.

-Ofast#

The same as -O3 (and so -O2). GCC's -Ofast adds -ffast-math, which EmbCC does not do (see --fast-math), so no result differs from --opt=max's.

Any other -O spelling (-O4, -Ox) is refused with embcc: unknown optimization flag '-O4'.

-fgnu89-inline, -fno-gnu89-inline#

Select the meaning of inline on a function with external linkage. The default, -fno-gnu89-inline, is C99's: a definition whose declarations all say inline and none extern is an inline definition, which is never emitted. -fgnu89-inline selects GNU89's: inline alone is an external definition, and extern inline is never emitted. See Inline functions.

With --std=c89, c90, iso9899:1990, gnu89 or gnu90, GNU89 semantics apply and -fno-gnu89-inline does not change them, as with Clang.

Any other name is refused: embcc: error: unknown standard '--std=NAME', or embcc: error: unknown C++ standard '--std=NAME' for a malformed c++/gnu++ name.

-ansi is not accepted (unknown argument). --pedantic and --pedantic-errors are accepted with a warning and change nothing (see --pedantic). -Wpedantic is accepted as a warning name EmbCC does not have (see --warn=NAME).

-Wp,ARGS#

Pass ARGS, split at the commas, to the preprocessor. Each must be -D, -U or -I with its value joined to it, and is applied where the -Wp option stands among the others, so -DY -Wp,-DX=1,-UY defines X and leaves Y undefined. Any other preprocessor option is refused:

embcc: error: preprocessor option '-MD' in -Wp,-MD,dep.d is not supported; pass -D, -U or -I with its value, or give the option directly

-M#

Instead of compiling, write a make rule naming the input and every header it includes, to standard output (or to the -MF file). Use it with -c or -E: without one of them the driver goes on to link and fails.

$ embcc -M -c m.c
m.o: m.c \
  /opt/embcc/lib/embcc/0.0.1-beta/include/stdio.h \
  /opt/embcc/lib/embcc/0.0.1-beta/freestanding/stddef.h \
  /opt/embcc/lib/embcc/0.0.1-beta/freestanding/stdarg.h \
  loc.h
$ embcc --deps-only -c m.c
m.o: m.c loc.h

The rule's target is the --deps-target value if one is given. Otherwise, with -c, it is the object's name (the -o name or the default object name), and with -E it is the input's file name with its suffix replaced by .o. Long rules are wrapped at about 72 columns with a backslash.

-MF FILE#

Write the rule to FILE. FILE is the next argument; with none the driver stops with embcc: -MF needs a file name.

-fno-reorder-functions, -freorder-functions, -fno-ident, -fident#

What EmbCC does anyway:

  • functions stay in source order, with no hot or cold subsections;
  • no .comment section identifies the compiler.

-finline-small-functions, -fno-inline-small-functions, -finline-limit=N#

Accepted and without effect. They are hints to GCC's inliner, whose budgets EmbCC's does not share (see Optimization).

-fno-delete-null-pointer-checks#

Accepted. EmbCC already keeps the promise: no pass infers that a pointer is non-null -- not from a dereference before the check, not from the nonnull and returns_nonnull attributes (which are ignored), not from its being an object's address -- so no comparison with a null pointer is removed, and a load from address 0 stays a load.

-fno-tree-loop-distribute-patterns#

Accepted. No loop ever becomes a call. The idiom pass turns a copy or clear loop with a constant trip count into a block copy or clear, and every backend expands those inline, as it does structure copies, so a hand-written memset or memcpy cannot become a call to itself. No EmbCC object refers to memset, memcpy or memmove unless the source calls them.

-fmerge-constants#

Accepted and without effect: it permits merging identical constants across units, and EmbCC does not.

-fno-isolate-erroneous-paths-dereference, -fno-move-loop-invariants, -fno-ipa-sra, -fno-lto#

Accepted and without effect. EmbCC turns no null dereference into a trap, and has no passes under the other three names; -fno-lto is the default (-flto is refused). EmbCC's loop-invariant motion is the licm pass, turned off with --no-pass=licm.

-fsanitize-trap, -fsanitize-trap=LIST, -fsanitize-undefined-trap-on-error#

Accepted, since trapping is the only mode. Note one difference from GCC and Clang: -fsanitize-trap=LIST with a list also enables the checks it names, as --sanitize=LIST would. An unknown name in the list is reported with the --sanitize=NAME message above.

Every other option beginning with -fsanitize (for example -fsanitize-recover=...) is refused.

-fno-stack-protector#

Accepted: EmbCC emits no stack protection. Every -fstack-protector form is refused:

embcc: '-fstack-protector-strong' is not supported (EmbCC emits no stack protection); -fno-stack-protector is

-fno-plt#

Accepted. EmbCC does not generate code that calls through a procedure linkage table.

-fno-pic, -fno-pie#

Accepted, with -fno-PIC and -fno-PIE. EmbCC's code is not position independent (-fPIC is refused): an x86-64 jump table holds absolute addresses, a Cortex-M address is a movw/movt pair, and embld links no position-independent executable. The Darwin targets' code is PC-relative because Mach-O requires it, and links the same way either way.

-fno-zero-initialized-in-bss#

Accepted. EmbCC already places every object with an initializer in .data, zeros included: int x = 0; and static char buf[64] = {0}; are in .data, so a bootloader that does not clear .bss still finds them zero. Only an object with no initializer at all (int y;) is in .bss, as with GCC's option. -fzero-initialized-in-bss, which permits the other placement, is accepted and changes nothing.

-fstrict-volatile-bitfields, -fno-strict-volatile-bitfields#

Accepted. EmbCC already reads and writes a volatile bit-field with one access of its declared type's width, as the AAPCS requires and -fstrict-volatile-bitfields asks: a volatile uint32_t f : 8 is an ldr (and an ldr/str pair to write it), never an ldrb, even when the field lies in one byte. The access is to the field's container at the type's alignment, so it can include neighbouring members. The negative form permits narrower accesses, and EmbCC does not use the permission.

-fverbose-asm#

Accepted and without effect on what -S writes.

-fgnuc-version=MAJOR[.MINOR[.PATCH]]#

Define __GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__ and __GNUC_STDC_INLINE__ in a C unit, as that version of GCC, the way clang does by default. EmbCC does not by default, because a header that sees __GNUC__ may take paths that need GCC itself. Vendor headers that pick their compiler support by it need it: CMSIS's cmsis_compiler.h stops at #error Unknown compiler without it, and compiles with -fgnuc-version=4.2.1 (see Embedded programming). 0 leaves the macros undefined. A C++ unit always presents itself as g++ (see C++).

-fmessage-length=N#

Accepted. EmbCC does not wrap diagnostics, which is what -fmessage-length=0 asks for; other values change nothing.

-fanalyzer#

Accepted with a warning: embcc: warning: -fanalyzer: EmbCC has no static analyzer, so this checks nothing.

-fdiagnostics-parseable-fixits#

After each fix-it hint, also print it in the machine-readable form Clang uses:

fix-it:"bad.c":{2:36-2:36}:";"

Refused options#

These options are refused because each is a promise about the generated code that EmbCC would not keep. The driver stops with status 1 and a message that names the option as it was typed; the messages below are what EmbCC's spellings get.

Option Message
-fPIC, -fpic, -fPIE, -fpie embcc: error: -fPIC is not supported; EmbCC would emit ordinary code and the flag's promise would not hold
-flto (same form)
-fprofile-*, --coverage, -fcoverage-mapping, -pg (same form)
-fstack-clash-protection, -fcf-protection, -fcf-protection=... (same form)
-fsanitize* other than the forms in --sanitize (same form)
--sanitize=NAME for a check EmbCC does not have --sanitize=NAME is not supported: EmbCC's sanitizer inserts checks that TRAP ...
-fstack-protector, -fstack-protector-all, -fstack-protector-strong, -fstack-protector-explicit embcc: '-fstack-protector...' is not supported (EmbCC emits no stack protection); -fno-stack-protector is
-shared, -static-pie embcc: error: -shared needs position-independent code, which EmbCC does not emit
-gdwarf-5 (any version but 2 to 4), -gsplit-dwarf, -gz embcc: error: -gdwarf-5 is not supported; EmbCC emits DWARF 4, uncompressed and in one piece
--isa=arm --isa=arm is not supported: a Cortex-M has no ARM instruction set, only Thumb
--no-unaligned-access (ARM) --no-unaligned-access is not supported: EmbCC's ARMv7-M code uses word and halfword loads and stores at unaligned addresses ...
--abi=apcs-gnu, --abi=atpcs, --abi=iwmmxt (ARM) --abi=apcs-gnu is not supported: EmbCC emits the AAPCS ...
--abi=ilp32e, lp64e (RISC-V), or a float ABI the --isa= has no FPU for embcc: error: --abi=ilp32e is not supported for riscv32-unknown-elf: EmbCC emits ilp32, ilp32f and ilp32d (not the E base's)
--isa= with an extension other than I, M, A, F, D, C, Zicsr, Zifencei, or without M or A (RISC-V) embcc: error: --isa=rv32imafcv: the 'v' extension is not supported: EmbCC emits I, M, A, F, D and C
--endian=big or --endian=little against the target's byte order embcc: error: --endian=big is not supported for x86_64-elf, which is little-endian
-fdump-rtl-*, -fdump-tree-* and every other -fdump-; -fcallgraph-info= with anything but su embcc: error: -fdump-rtl-expand is not supported: it dumps GCC's internal representation, which EmbCC does not have; ...
--common for a Mach-O or COFF target embcc: error: --common is not supported for x86_64-apple-darwin: ...
--float-constants=single for C++ embcc: error: --float-constants=single is supported for C, not C++: ...
-O4 and other -O forms not listed embcc: unknown optimization flag '-O4'
-Wl,OPTION or -Xlinker OPTION, for a linker option not listed under --linker-arg embcc: error: linker option 'OPTION' is not one EmbLD has (it takes -e, -Ttext, -Tdata, -Tstack, --rom-limit and --lma-offset); dropping it could build a different image from the one asked for
-Wa,OPTION, for an option not listed under --assembler-arg embcc: error: assembler option 'OPTION' is not one the integrated assembler has

Environment variables#

Variables the driver reads#

EMBCC_DEFAULT_TARGET : The target used when the command line has no --target=. Overrides the compiled-in default. See The default target.

EMBCC_PREFIX : An installation prefix to take headers and libraries from, ahead of the build tree or installation found beside the executable. See How the compiler finds its own files.

The driver reads no other variables: CPATH, C_INCLUDE_PATH, LIBRARY_PATH, GCC_EXEC_PREFIX, TMPDIR and NO_COLOR have no effect.

Variables for developing EmbCC#

The compiler also reads the following variables. They exist for testing and bisecting EmbCC itself; they change generated code or print internal traces, and they are not a stable interface. See Testing and Register allocation.

Variable Effect
EMBCC_VERIFY Run the IR verifier after IR generation and after optimization, and make an optimizer that fails to converge a fatal error. The test suite sets it.
EMBCC_RA_MAXPOOL=N Limit the integer register pool to its first N registers, to exercise spill paths.
EMBCC_RA_WHY Print a line to standard error for each function in which values were spilled.
EMBCC_RA_TRACE Print the register allocator's pool and each value's assignment to standard error.
EMBCC_RA_DEGREE_SPILL Choose spill candidates by interference degree instead of by cost.
EMBCC_MIPS_RA_MAX=N On MIPS, leave only the first N vregs of each function in registers (a bisection handle; always correct).
EMBCC_MIPS_PAIRS=0 On MIPS, do not give 64-bit values register pairs.
EMBCC_MIPS_NO_FILL On MIPS, leave every branch's and call's delay slot a nop (a return's still releases the frame).
EMBCC_NO_TAILCALL Disable tail calls in the AArch64, ARM, RISC-V, MIPS and AVR backends.
EMBCC_NO_JUMP_TABLES Compile as with --no-jump-tables, whatever the command line says, so a test suite's own command lines can be run without tables.
EMBCC_NO_DIVMOD_CONST, EMBCC_NO_MLAKEEP On Thumb, stop sharing one divide between a quotient and a remainder by the same constant, or stop keeping a multiply's constant in a register for mla/mls.
EMBCC_NO_SPLITLOOPS Disable the optimizer's loop-splitting step.
EMBCC_NO_LKCONST Stop deciding compares and branches from a constant the same block has just written.
EMBCC_VECDEBUG Trace the vectorizer's decisions.
EMBCC_NO_FULLUNROLL Unroll a loop with a constant trip count like any other, instead of copying it whole.
EMBCC_NO_RMW Disable x86-64 read-modify-write instruction fusion.
EMBCC_NO_SINKUPD Leave a loop counter's update where it was instead of moving it next to the copy that ends it.
EMBCC_NO_MEMOFF Disable folding constant offsets into loads and stores (ARM, RISC-V, AVR).
EMBCC_NO_IDXOFF On RISC-V, stop moving a constant out of an array index into the access's displacement.
EMBCC_RV_RA_MAX, EMBCC_RV_PAIRS, EMBCC_RV_PAIRS_ONLY, EMBCC_RV_JAL_RANGE, EMBCC_RV_LONG_CALLS RISC-V allocator, register-pair and call-range knobs.
EMBCC_T_RA_MAX, EMBCC_T_PAIRS, EMBCC_T_PAIRS_ONLY, EMBCC_T_NOLO, EMBCC_T_FPU, EMBCC_T_EXT ARM allocator, register-pair and FPU knobs; EMBCC_T_EXT=0 keeps r9-r11 out of the allocator's pool.
EMBCC_T_NOWIDEIMM, EMBCC_T_NOCMP64IMM, EMBCC_NO_SIGNTEST Build a 64-bit constant whole on Thumb instead of using it half by half (a compare's constant too); keep x >> 63 as a shift before a branch.
EMBCC_AVR_RA, EMBCC_AVR_RA_MODE, EMBCC_AVR_RA_ONLY, EMBCC_AVR_RA_LIMIT, EMBCC_AVR_RA_CAP, EMBCC_AVR_REMAT_MAX, EMBCC_AVR_NO_VOL, EMBCC_AVR_NO_OCT, EMBCC_AVR_NO_XHOME AVR register-allocation mode and bisection knobs.

The build and test scripts read further variables (EMBCC_TARGET, EMBCC_QEMU_TIMEOUT, EMBLD, EMBCC_AR and others); those are described in Getting started and Testing.