Skip to content

Repository files navigation

TinyCC for ARMv8-M — Tiny C Compiler fork for ARMv8-M (Cortex-M33, Cortex-M23)
=================================================================================

This is a fork of the Tiny C Compiler (TCC) by Fabrice Bellard, modified for
**ARMv8-M architecture** with a custom IR, register allocator, and Thumb-2
code generator.

Differences from Original TinyCC
--------------------------------

**1. Target Architecture**
Original TCC targets x86/x86_64/aarch64/riscv64 on desktop/server OSes.
This fork targets **ARMv8-M** microcontrollers (Cortex-M33, Cortex-M23, etc.)
with the Thumb-2 instruction set.

**2. Custom IR (Intermediate Representation)**
The original TCC uses a simple, direct translation to machine code. This fork
introduces a **three-address code IR** with explicit register operands, enabling
a separate front-end and back-end, and an SSA form built on top of it.
Public interface: `source/ir/tccir.h`; construction in `source/ir/gen/`.

**3. Optimizer**
Upstream TCC does not optimize. This fork carries a full optimizer under
`source/opt/`, driven by a pass pipeline table (`source/opt/engine/`) with two
phases — flat-IR peepholes/fusion (`opt/flat/`) and SSA passes (`opt/ssa/`) —
plus alias and def-use analysis, pre/post-RA passes, and a declarative pass DSL
(`opt/framework/`). `-O1` does scalar cleanup, ARM addressing-mode fusion,
constant/range propagation, loop rotation and light inlining; `-O2` adds the
heavy tier (loop unrolling, IV strength reduction, LICM, re-rolling, MUL+ADD→MLA
fusion, interprocedural constant propagation, full small-function inlining).

**4. Register Allocation**
An **SSA register allocator** (`source/ir/regalloc.c`, parameterized by
`RegAllocTarget`) with a linear-scan core (`source/machine/tccls.c`) performs
liveness analysis and assigns physical registers, including the VFP bank. The
original TCC uses a simpler approach without liveness analysis.

**5. Code Generation**
Instead of x86 code generation, this fork generates **Thumb-2 machine code**
via `source/backend/arch/arm/thumb/`: `arm-thumb-gen.c` drives the per-opcode
`thop_*.c` encoders (ALU, memory, branch, DSP, coprocessor, VFP, exclusive
access, …), which walk instruction variants narrow→wide and pick the first
whose constraints hold. Covers the ARMv8-M instruction set including DSP
extensions.

**6. Floating Point**
Hardware floating point is supported: VFP single-precision codegen, the full
AAPCS-VFP hard-float ABI, and inline RP2350 DCP sequences for `double`.
See the *Floating Point* section below for flags, coverage and limits.

**7. Library Mode**
Can be used as `libtcc.a` for **JIT compilation** in host applications.

**8. ARM-Specific Features**
- ARM Procedure Call Standard (AAPCS/AAPCS-VFP) support
- ARMv8-M EABI helper functions
- ARM assembler parser for inline assembly
- ARM-specific ELF linking (`source/backend/arch/arm/arm-link.c`)

**9. YAFF Output for YasOS**
Besides ELF, the linker emits **YAFF** (`source/obj/tccyaff.c`), a flat
position-independent module format for the YasOS loader, selected with
`-Wl,-oformat=yaff` (executables and shared libraries alike). The YasOS target
also defaults to PIC with text/data separation, shared read-only `.rodata`, and
SB-relative GOT addressing — `-no-share-rodata` and `-mno-sb-relative-got` opt
back out.

**10. Self-Hosting on Target**
The compiler builds for ARMv8-M itself and runs on-device under YasOS, so the
same sources produce both the host cross compiler and an on-target compiler
(`tests/selfhost/`).

**11. Runtime Library**
Includes a custom runtime library (`libtcc1`) with ARM EABI helpers in
`lib/armeabi.c` and `lib/armv8m_eabi.c`.

Project Structure
-----------------

All compiler code lives under `source/`, one directory per module. Each module
owns its headers, carries its own `Makefile` and builds to its own static
library; the top-level `Makefile` links them into `armv8m-tcc`. The repo root
holds only build inputs, generated headers and project metadata.

```
.
├── source/                  # Compiler sources (one module per directory)
│   ├── include/             # tcc.h (umbrella header), tcctypes.h
│   ├── driver/              # tcc.c (CLI), libtcc.c (TCCState/options/driver), tcctools.c
│   ├── frontend/            # tccpp.c (preprocessor), tccasm.c (GAS asm), svalue.c
│   │   └── gen/             # Parser, type checker, IR emission (the former tccgen.c)
│   ├── ir/                  # Target-independent IR
│   │   ├── tccir.h          # Public IR interface (opcodes, builder)
│   │   ├── gen/             # IR construction (arith, control, float, params, vla, ...)
│   │   ├── cfg.c ssa.c      # CFG and SSA form
│   │   ├── codegen.c        # Two-pass dispatch (dry-run + real-run) to backend _mop handlers
│   │   ├── regalloc.c       # SSA register allocator (parameterized by RegAllocTarget)
│   │   └── vreg.c stack.c   # Virtual registers, stack frame layout
│   ├── opt/                 # Optimizer
│   │   ├── engine/          # Pass registry, pipeline table, pass timing
│   │   ├── flat/            # Flat-IR passes (cfg, dce, fusion, loop, memory, scalar, ipa)
│   │   ├── ssa/             # SSA passes (cfg, dce, loop, memory, scalar, string)
│   │   ├── analysis/        # Alias analysis, def-use chains, memory SSA
│   │   ├── ra/              # Pre/post register-allocation passes
│   │   └── framework/       # Declarative pass DSL
│   ├── machine/             # Backend boundary: tccls.c (linear scan), tccmachine.c, tccabi.h
│   ├── backend/
│   │   ├── arch/arm/        # arm.c, arm_aapcs.c, arm-link.c, arm_regalloc.c, ssa_opt_arm.c
│   │   │   └── thumb/       # arm-thumb-gen.c, arm-thumb-asm.c, thop_*.c encoders
│   │   ├── arch/fpu/        # FPU capability descriptions
│   │   └── generators/      # Target-independent generator helpers
│   ├── obj/                 # tccelf.c, tccld.c, tccyaff.c (YAFF), tccdbg.c (DWARF/STABS)
│   ├── support/             # tccdebug.c (gdb printers), logging, debug-env knobs
│   ├── memory/              # vector.c, unique_ptr.c, container headers
│   └── utils/               # Header-only helpers
│
├── lib/                     # Runtime library sources (libtcc1)
│   ├── libtcc1.c            # Core runtime functions
│   ├── armeabi.c            # ARM EABI helpers
│   ├── armv8m_eabi.c        # ARMv8-M EABI specific
│   ├── builtin.c            # Freestanding builtin fallbacks
│   └── fp/                  # Floating point libraries (see Floating Point)
├── include/                 # Headers shipped with the compiler
│
├── tests/
│   ├── ir_tests/            # Primary suite: compile + run under QEMU (pytest)
│   ├── unit/                # Host unit tests for individual passes (make ut)
│   ├── thumb/armv8m/        # Thumb-2 encoder tests
│   ├── frontend/ linker/    # Per-subsystem pytest suites
│   ├── debug/ runtime/      #  "
│   ├── selfhost/            # The compiler compiling itself
│   ├── gcctestsuite/        # GCC torture tests (fetched on demand)
│   ├── benchmarks/          # Performance, code size, FP conformance
│   ├── fuzz/                # Fuzzing and divergence triage
│   └── tests2/ pp/          # Upstream legacy suites
│
├── scripts/                 # Bisect, reduce, disassembly diff, container runner
├── metrics/                 # Code size / performance tracking, Grafana dashboards
├── docs/                    # Design documents and plans
│
├── configure                # Configuration script
├── Makefile                 # Top-level build rules (module Makefiles under source/*)
├── config.h / config.mak    # Generated configuration
└── tcc-doc.texi             # Texinfo documentation source
```

Build
-----

```bash
# Configure
./configure [options]
```

Frequently used `./configure` options:

| Flag | Description |
|------|-------------|
| `--enable-cross` | Build the ARMv8-M cross compiler (`armv8m-tcc`) |
| `--enable-O0` … `--enable-O3`, `--enable-Os`, `--enable-Og` | Optimization level TCC itself is built with (`-O2` default) |
| `--disable-asan` | **AddressSanitizer is ON by default**; disable it for fast or production builds |
| `--enable-ubsan`, `--enable-lsan` | Additional sanitizers |
| `--debug` | Include debug info in the resulting binaries |

Example:
```bash
./configure --enable-cross --disable-asan
make cross
```

Run `./configure --help` for the full list.

```bash
# Build ARMv8-M cross compiler
make cross

# Build everything including FP libraries
make cross fp-libs

# Run the full test suite
make test -j16
```

Two build-time knobs affect what ends up in the binary:

| Variable | Effect |
|----------|--------|
| `make cross CONFIG_minimal=yes` | Drops the `-dump-ir` / `-dump-ir-passes` machinery (`CONFIG_TCC_DEBUG`), which is on by default so IR tooling and the golden-IR tests work with a plain `make cross`. Generated code is unaffected either way. |
| `make cross CONFIG_debugenv=no` | Compiles out the getenv-driven debug/bisect knobs (`TCC_DISABLE_PASS`, `TCC_NO_COALESCE`, …); each folds to a compile-time constant. Worth ~17 KB on the device compiler, but disables the host bisect tooling. |

Floating Point
--------------

Both software and **hardware** floating point are supported. Two flags select
the behaviour:

| Flag | Values | Meaning |
|------|--------|---------|
| `-mfloat-abi=` | `soft`, `softfp` (default), `hard` | how FP values cross a call boundary |
| `-mfpu=` | `none`, `fpv4-sp-d16`, `fpv5-sp-d16`, `fpv5-d16`, `rp2350` (= `rp2350-dcp`) | which FP unit may be used inside a function |

The compile-time default can be changed for a whole toolchain with
`-DCONFIG_TCC_DEFAULT_FPU=ARM_FPU_...` (e.g. `ARM_FPU_RP2350`); an explicit
`-mfpu=` on the command line still wins, and the float ABI is untouched, so
objects stay link-compatible across the switch. Code that must stay soft-float
has to say `-mfpu=none` rather than rely on the default.

**What is emitted inline** (with `-mfloat-abi=softfp` or `hard`;
`-mfloat-abi=soft` disables all inline FP codegen regardless of `-mfpu`)

| Operation | `-mfpu=none` | `fpv4/fpv5-sp-d16` | `-mfpu=rp2350` |
|---|---|---|---|
| `float` add/sub/mul/div | `__aeabi_f*` | `vadd/vsub/vmul/vdiv.f32` | `vadd/vsub/vmul/vdiv.f32` |
| `float` compare/convert/negate | `__aeabi_*` | `__aeabi_*` | `__aeabi_*` |
| `double` add/sub/compare | `__aeabi_d*` | `__aeabi_d*` | inline DCP (CP4) sequences |
| `double` mul/div/convert | `__aeabi_d*` | `__aeabi_d*` | `__aeabi_d*` (DCP-backed library) |

**Hard-float ABI** (`-mfloat-abi=hard`) is complete: `float` arguments in
`s0-s15`, `double` arguments in `d0-d7` with AAPCS back-filling, returns in
`s0`/`d0`, VFP spills/reloads via `vldr`/`vstr`, and `.ARM.attributes`
(`Tag_FP_arch`, `Tag_ABI_HardFP_use`, `Tag_ABI_VFP_args`) matching
`arm-none-eabi-gcc` byte-for-byte. `softfp` keeps the base PCS at call
boundaries while still using the FPU internally, so it links against
soft-float libraries unchanged.

Note that AAPCS-VFP passes doubles in `d0-d7` even on a single-precision-only
FPU — the ABI says where arguments live, not which arithmetic exists, so a
callee unpacks `d0` into a GPR pair to call `__aeabi_dadd`.

**Runtime libraries** (`make fp-libs`, sources in `lib/fp/`):

| Library | `-mfpu` | Contents |
|---------|---------|----------|
| `libsoftfp` | `none` | pure software IEEE-754 |
| `libvfpv4sp` | `fpv4-sp-d16`, `fpv5-sp-d16` | float in HW, double in SW (self-contained) |
| `libvfpv5dp` | `fpv5-d16` | float and double in HW |
| `librp2350fp` | `rp2350` | double via the RP2350 DCP coprocessor |

**Testing**

```bash
# Float tests under one ABI (soft | softfp | hard)
make test-fp FLOAT_ABI=hard

# IEEE-754 conformance on real RP2350 silicon (inline DCP/VFP codegen,
# which QEMU cannot model); the same vectors run under QEMU as
# tests/ir_tests/421_fp_conformance.c
python3 tests/benchmarks/run_fp_conformance.py <host> --opt-level 1 \
    --fp-lib rp2350fp --mfpu rp2350 --allow-ftz
```

`--allow-ftz` applies only to the DCP configuration: the coprocessor flushes
subnormals to zero and has no path that doesn't. The soft-float baseline (the
same script without those flags) stays held to full IEEE-754.

**Current limits**

- `float` compares, conversions and negate still go through `__aeabi_*`
  helpers even with an FPU selected.
- No native `vadd.f64` on a double-precision unit (`-mfpu=fpv5-d16`); doubles
  always call, or use DCP on RP2350.
- DCP `dmul`, `dneg` and the double conversions are not inlined yet.
- `.ARM.attributes` is written at link time only — object files carry none, so
  GNU `ld` cannot ABI-check them.
- `-mfloat-abi=hard` on RP2350 silicon is untested (needs pico-sdk rebuilt for
  the hard ABI); RP2350 uses `softfp` today.
- The DCP save/restore hooks exist in `dcp_aeabi.S` but the library entry
  points do not use them, so an interrupt handler doing `double` math can
  corrupt an interrupted user-space DCP sequence. The OS context switch half
  is handled on YasOS.

Details: `docs/plan_vfp_hard_float.md` (VFP / hard-float ABI),
`docs/plan_rp2350_dcp.md` (DCP), `lib/fp/README.md` (runtime libraries).

Container
---------

A Dockerfile provides a reproducible build environment, published as
`ghcr.io/matgla/tinycc-armv8m`. The `run` target pulls it and mounts the repo
(`scripts/run_container.py` does the plumbing; podman by default, or
`CONTAINER_RUNTIME=docker`):

```bash
# Interactive shell in the container
make run

# Run one command inside it
make run CMD='make test -j16'

# Build the multi-arch image locally / push it
make build_container
make push_container
```

`container-build` / `container-pull` / `container-push` / `docker-build` /
`docker-start` remain as aliases.

Testing
-------

```bash
# Fetch the GCC torture tests (one-time). Only gcc.c-torture (~16 MB) is
# pulled, sparsely, not the whole gcc repo (~1.3 GB):
make download-gcc-tests        # or: bash tests/gcctestsuite/download_gcc_tests.sh

# Full suite: IR tests + asm + unit tests + frontend/linker/debug/runtime/selfhost
make test -j16

# Same, but sequential — clean logs
make test-sequential

# Everything above plus the GCC torture tests
make test-all
```

Individual suites:

| Target | Scope |
|--------|-------|
| `make test-ir` | The primary QEMU corpus in `tests/ir_tests/` |
| `make ut` | Host unit tests for individual optimizer passes |
| `make test-asm` | Thumb-2 encoder tests (`tests/thumb/armv8m/`) |
| `make test-fp FLOAT_ABI=…` | Float tests under one ABI (see *Floating Point*) |
| `make test-selfhost` | The compiler compiling its own sources |
| `make test-frontend`, `test-linker`, `test-debug`, `test-runtime` | Per-subsystem suites |
| `make test-gcc-torture-compile`, `test-gcc-torture-execute` | GCC torture, split by phase |
| `make test-golden-ir` | Golden IR dumps (needs a `CONFIG_TCC_DEBUG` build) |

`make help` lists the rest.

Quick Test Runner
-----------------

```bash
cd tests/ir_tests

# Compile and run a single file
python run.py -c mytest.c

# With optimization
python run.py -c mytest.c --cflags="-O1"

# Dump IR
python run.py -c mytest.c --cflags="-O1" --dump-ir

# Compare against GCC, pass program arguments, or attach a debugger
python run.py -c mytest.c --gcc arm-none-eabi-gcc
python run.py -c mytest.c -a arg1 arg2
python run.py -c mytest.c --gdb
```

Debugging
---------

```bash
# Show IR output
./armv8m-tcc -dump-ir -c test.c

# Dump IR after each named optimizer pass (comma-separated, or 'all')
./armv8m-tcc -dump-ir-passes=all -O2 -c test.c

# Verbose: version banner plus every file read and written
./armv8m-tcc -vv -c test.c
```

Both dump options need a compiler built with `CONFIG_TCC_DEBUG`, which is the
default (see *Build*).

The getenv knobs described in `source/support/tccdbgenv.h` — `TCC_DISABLE_PASS`
and friends — bisect a miscompile to a single pass without rebuilding;
`scripts/bisect_pass.sh` and `scripts/opt_profile.py` drive them.

For extra compile-time logging (e.g. `-DTCC_LS_DEBUG` for the register
allocator), add the define to `CFLAGS` in `config.mak` or pass it to
`./configure --extra-cflags=`. Do **not** use `make CFLAGS+=-DFOO`: that
overrides the flags the sub-makes compute, so the affected TUs silently lose
`-fsanitize=address` and the resulting mixed binary crashes in unrelated code.

License
-------

TCC is distributed under the GNU Lesser General Public License (LGPL).
See the COPYING file for details.

This fork is maintained for ARMv8-M embedded development.

About

No description, website, or topics provided.

Resources

Stars

14 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages