Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
173 changes: 171 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,53 @@ on:
branches: [mob]
pull_request:
branches: [mob]
workflow_dispatch:

env:
# Stable synthetic key both metrics jobs record under (metrics.db keys runs
# by commit_sha+host) -- decoupled from the Pi's actual hostname so the
# cloud-recorded codesize/compile-time rows and the Pi-recorded perf rows
# land on the SAME run instead of two separate per-host rows.
METRICS_HOST: armv8m-metrics

jobs:
# Builds the cross compiler once and shares it (via artifact) with
# build-and-measure and rp2350-perf below, so metrics never repeats this
# build. build-and-test does NOT consume this artifact: `make test` has
# `cross` as a prerequisite that reaches through object files and
# checksum/fp-libs/PCH stamp files (Makefile:206-234), not just the final
# binary, so dropping in a pre-built armv8m-tcc wouldn't save it a
# recompile -- make would just rebuild the missing intermediates anyway.
build:
runs-on: ubuntu-latest
permissions:
packages: read
container:
image: ghcr.io/matgla/tinycc-armv8m:latest
options: --user root
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0

- name: Build cross compiler
run: ./configure --enable-cross --enable-O2 --debug && make cross -j$(nproc)

- name: Upload tcc build
uses: actions/upload-artifact@v4
with:
name: tcc-cross-build-${{ github.sha }}
path: |
armv8m-tcc
armv8m-libtcc1.a
retention-days: 1

build-and-test:
runs-on: ubuntu-latest
permissions:
Expand All @@ -25,11 +70,135 @@ jobs:
submodules: recursive

- name: Configure
run: ./configure --enable-cross --enable-O2
# --debug enables CONFIG_TCC_DEBUG so the compiler supports -dump-ir;
# without it the frontend types/ tests skip (they need IR dumps).
run: ./configure --enable-cross --enable-O2 --debug

- name: Build and test
shell: bash
env:
# Write a JUnit report from every pytest run (the final ir_tests run
# overwrites it last) so the failure collector knows which tests failed.
PYTEST_ADDOPTS: "--junitxml=/tmp/ci-junit.xml"
run: |
virtualenv .venv
source .venv/bin/activate
make test -j$(nproc)
# `shell: bash` runs with -eo pipefail, so a failing make still fails
# the step even though its output is teed to a log we upload on failure.
make test -j$(nproc) 2>&1 | tee /tmp/make-test.log

- name: Collect failure artifacts
if: failure()
shell: bash
env:
MAKE_TEST_LOG: /tmp/make-test.log
PYTEST_JUNIT_XML: /tmp/ci-junit.xml
run: |
source .venv/bin/activate 2>/dev/null || true
bash scripts/collect_ci_failure_artifacts.sh "$PWD/ci-failure-artifacts"

- name: Upload failure artifacts
if: failure()
uses: actions/upload-artifact@v4
with:
name: make-test-failure-artifacts
path: ci-failure-artifacts.tar.gz
retention-days: 14
if-no-files-found: warn

# Code size + compile time: no RP2350 board needed, so this reuses the
# `build` job's artifact on a regular (fast) GitHub-hosted runner instead
# of rebuilding (as it used to) or running on the shared Pi.
build-and-measure:
needs: build
runs-on: ubuntu-latest
permissions:
packages: read
container:
image: ghcr.io/matgla/tinycc-armv8m:latest
options: --user root
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0

- name: Mark workspace as a safe git directory
# actions/checkout runs on the runner host and configures safe.directory
# there, but `run:` steps in a container job execute as a different
# user/HOME inside the container -- that config never reaches it, so any
# git command run from a `run:` step (e.g. metrics/record.py) fails with
# "detected dubious ownership" on the bind-mounted repo.
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"

- name: Download tcc build
uses: actions/download-artifact@v4
with:
name: tcc-cross-build-${{ github.sha }}

- name: Make tcc executable
run: chmod +x armv8m-tcc

- name: Record codesize + compile time
run: |
python3 metrics/record.py --db /tmp/metrics-scratch.db --rev HEAD \
--no-correctness --jobs "$(nproc)" --host "$METRICS_HOST" \
--trigger "${{ github.event_name }}"

- name: Upload metrics scratch db
uses: actions/upload-artifact@v4
with:
name: metrics-scratch-${{ github.sha }}
path: /tmp/metrics-scratch.db
retention-days: 1

# RP2350 hardware perf: the only part of this workflow that actually needs
# the board, so it's the only part still pinned to the self-hosted Pi.
rp2350-perf:
needs: build-and-measure
runs-on: [self-hosted, rpi5, pimoroni_pico_plus2]
timeout-minutes: 90
concurrency:
group: metrics-rpi5
cancel-in-progress: false
env:
METRICS_DB: /var/lib/tcc-metrics/metrics.db
PERF_HOST: 127.0.0.1
PERF_IDENTITY: /home/runner/.ssh/id_rp

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0

- name: Download tcc build
uses: actions/download-artifact@v4
with:
name: tcc-cross-build-${{ github.sha }}

- name: Download metrics scratch db
uses: actions/download-artifact@v4
with:
name: metrics-scratch-${{ github.sha }}
path: /tmp/downloaded-metrics

- name: Make tcc executable
run: chmod +x armv8m-tcc

- name: Record perf (import codesize/compile time from the cloud build)
run: |
python3 metrics/record.py --db "$METRICS_DB" --rev HEAD --no-correctness \
--import-codesize-from /tmp/downloaded-metrics/metrics-scratch.db \
--jobs "$(nproc)" --host "$METRICS_HOST" --trigger "${{ github.event_name }}" \
--perf-host "$PERF_HOST" --perf-identity "$PERF_IDENTITY"

- name: Gate
if: ${{ vars.METRICS_GATE_ENABLED == 'true' }}
run: python3 metrics/gate.py --db "$METRICS_DB" --rev HEAD --host "$METRICS_HOST" --strict
20 changes: 20 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ tests/hello
tests/tests2/fred.txt
libtcc.dylib
build/
build_backend/
build_libtcc_api/
build_tccgen/
build_tccopt/
build_tccelf/
build_tccpp/
build_tcctools/
build_tccyaff/
rootfs/
__pycache__/
tests/ir_tests/qemu/mps2-an505/newlib_build/
Expand All @@ -97,8 +105,20 @@ tests/ir_tests/dump_ir.txt
tests/ir_tests/dump.txt
tests/ir_tests/dump_fine.txt
tests/ir_tests/dump_ir_fine.txt
tests/ir_tests/build/
armv8m-tcc.debug
.aider*
.claude
.cache
scripts/.disasm_cache.json
scripts/.disasm_cache.pending.json

# Python test artifacts
__pycache__/
.pytest_cache/
*.pyc
tests/fuzz/results/*
tests/fuzz/fuzz_triage_repros/
/tests/fuzz/.sweep_cache/
tests/unit/arm/armv8m/build*

4 changes: 3 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
{}
{
"cmake.sourceDirectory": "/home/mateusz/repos/tinycc/tests/benchmarks/libs/pico-sdk"
}
51 changes: 51 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# AGENTS.md

Guidance for autonomous coding agents working in this repository (TinyCC fork
targeting ARMv8-M). Read this first, then `CLAUDE.md` for the full project
overview, build commands, and architecture.

## Build & test (always current)

```bash
make cross -j$(nproc) # build armv8m-tcc (rebuild after EVERY edit)
make test -j16 # IR test suite (primary gate)
python3 scripts/diff_olevels.py --seeds 0-5000 --require-qemu # fuzz self-consistency
```

Style: `-std=c11 -Wunused-function -Werror` (treat warnings as build failures).
Function-body brace on its own line; see `.clang-format` and `CLAUDE.md`.

## Debugging an optimizer miscompilation

When a fuzz seed diverges between O-levels (tcc -O0 correct, -O1/-O2 wrong),
follow **`docs/debugging_fuzz_divergences.md`** end-to-end:

1. `scripts/bisect_opt.py --seed N --high=-O1` — QEMU-confirms the culprit
knob(s) and flags the exact IR line where a memory read is misfolded to a
constant, naming the pass group and the gated pass functions.
2. Write a **regression test first** (`tests/ir_tests/NN_fuzz_<cause>.c` +
`.expect`, registered in `tests/ir_tests/test_qemu.py`); confirm it fails
before the fix and passes after.
3. Fix, rebuild, re-run the IR suite + a fuzz sweep; confirm zero *new*
divergences.

Ground truth oracle is `gcc -m32 -funsigned-char` (ARM ABI: unsigned char,
32-bit long). Sweep/triage infrastructure is documented in
`docs/fuzz_triage_guide.md`.

## Conventions for changes

- **Never commit without a regression test** for a bug fix — verbatim or reduced
repro under `tests/ir_tests/`, expected output in a `.expect` file.
- New IR opcode → lowering in `arm-thumb-gen.c` + test. New asm instruction →
builder in `arm-thumb-opcodes.c` + token + parser + test.
- IR internals live in `ir/` (included via `ir/ir.h`); the public IR interface
is `tccir.h`. Internal IR functions are `ir_<module>_<action>()`.
- Don't commit the temporary `TCC_SKIP_SSA*` env-var bisection gates (see the
triage guide); they are investigation-only scaffolding.

## Don't

- Don't disable ASan/leak checks to "fix" a failure; investigate the root cause.
(ASan is ON by default; `./configure --disable-asan` for fast builds only.)
- Don't commit secrets, force-push, or create empty commits.
9 changes: 8 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ This is a specialized fork of **TinyCC (Tiny C Compiler)** targeting **ARMv8-M**

```bash
# One-time setup
./configure
./configure # AddressSanitizer is ON by default; ./configure --disable-asan for fast/production builds
make download-gcc-tests # optional: sparse-fetch GCC torture tests (~16 MB, not the full gcc repo)

# Build ARMv8-M cross compiler
Expand Down Expand Up @@ -140,6 +140,13 @@ Build uses `-std=c11 -Wunused-function -Werror`.

## Debug Logging

For debugging **optimizer miscompilations** found by the differential fuzzer
(tcc -O0 correct, -O1/-O2 wrong), see
[`docs/debugging_fuzz_divergences.md`](docs/debugging_fuzz_divergences.md) — the
end-to-end workflow built around `scripts/bisect_opt.py` (QEMU-confirmed culprit
knob + the exact IR line where a memory read is misfolded to a constant).
`docs/fuzz_triage_guide.md` covers the sweep/triage infrastructure.

Unified logging system defined in `log.h`. Each scope is a compile-time switch:

```bash
Expand Down
Loading
Loading