Diagnosing the Compiler

This guide is for contributors chasing a bug in Turmeric itself: the compiler accepts a program and then the C does not compile, the program crashes, or it prints the wrong answer. If the bug is more likely in your own program, start with Debugging Turmeric Programs; the lldb, tur debug and sanitizer recipes there apply here too.

The order of the sections is the order to work in: rule out the environment, reproduce small, find the stage, then look inside it.

1. Rule out the environment first

Most "the compiler is wrong" reports that do not reproduce come from one of these. Each takes seconds to check.

2. Reproduce it small

Get the bug into a single short .tur file before anything else. Two habits save a round trip:

3. Find the failing stage

Each command stops at a later point in the pipeline, so the first one that fails tells you where the bug is:

Command Runs A failure here means
tur parse-check a.tur b.tur.sweet the reader, on both files two spellings read to different ASTs (a reader bug)
tur check file.tur reader, macro expansion, elaboration, type/kind/effect/borrow checks a checker bug, or a genuinely wrong program
tur expand file.tur as check, printing each macro expansion a macro expands wrongly
tur emit-c file.tur as check, plus C emission the emitter crashed or produced nothing
tur build file.tur -o out as emit-c, plus the C compiler and linker check accepted something the C compiler rejects -- an emitter bug
./out the program a crash or wrong output: emitter or runtime

The last two rows are where most compiler bugs land. The source fuzzers (section 7) classify failures the same way: BUG_invalid_c (cc rejected the emitted C), BUG_link, BUG_crash, BUG_wrong_output.

4. Look inside a stage

The generated C

tur emit-c file.tur > out.c gives you the C. Add --debug to get #line directives, so a compile error or a debugger frame names .tur lines. Turmeric names are mangled into C identifiers; tur demangle reverses them, and the name mangling guide has the scheme.

TUR_CC_FLAGS replaces the flags passed to the C compiler (the default is -O2 -std=c99 -Wall -fno-strict-aliasing, or -g -Og ... under --debug). Use it to drop to -O0, to add -fsanitize=..., or to make warnings fatal.

Dump flags

These print an intermediate result and work with tur check (some write to stderr, so capture with 2>&1):

Flag Shows
--dump-kinds the kind of every type constructor and class parameter
--dump-effects the inferred effect row of every function
--dump-sizes sized-type information
--dump-cps the CPS IR, function by function
--dump-mono-specs the by-value monomorphization specializations
--dump-clone-plan, --dump-cps-coloring, --dump-reflect narrower internals of the same passes
--emit-abi-trace the ABI decisions made for each function

The documented ones are described in the compiler flags guide.

Float/int conversions

python3 tests/check-emitted-float-conversions.py FILE.tur reports every float-to-int or int-to-float value conversion in the emitted C that the program did not ask for. The corpus is at zero findings, so any finding is new and is almost certainly a bug. A conversion the emitter makes on purpose is spelled TUR_AS(T, x) and is not reported. The value representations guide explains why this checks the mechanism rather than particular program shapes.

5. Compare the back ends

Turmeric has three ways to run a program, and they share the front end but not the back end:

./build/tur run file.tur         # compile through C, run
./build/tur interpret file.tur   # tree-walking interpreter (turi)
./build/tur jit file.tur         # in-process MIR JIT

6. False failures from the harness

The suite can produce failures that look exactly like compiler regressions. Before believing a red result, check for these.

Running the suite usefully

TUR_TEST_FILTER='^hello$' bash tests/run.sh 2>&1    # one fixture (a regex)
TUR_TEST_SUITE=errors      bash tests/run.sh 2>&1    # happy | errors | snapshots
TUR_FORCE=1                bash tests/run.sh 2>&1    # ignore the stamp cache

tests/run-turi.sh is the interpreter suite and tests/run-jit.sh the JIT suite. The requires.* skip markers and the expected.xfail convention are listed in CLAUDE.md.

7. Fuzzers

The source fuzzers generate whole programs whose correct output is known by construction, run them through the real pipeline, and classify every disagreement. They find the combinations nobody wrote a fixture for.

Fuzzer Bug family
tests/type-fuzz-src.py values crossing a typed boundary (return, fn parameter, method result, container slot)
tests/refine-fuzz-src.py refinement types and the solver
tests/regions-fuzz-src.py region escapes
tests/saffron-fuzz-src.py the Saffron dialect

All take --n (cases), --seed, --jobs, --tur and --save-dir (keep the generated programs). A case's program depends only on the seed and its index, so --first K --n K+1 reruns case K alone. A small run is a reasonable smoke test after a change in the relevant area:

python3 tests/type-fuzz-src.py --n 50 --seed 1 --tur build/tur --save-dir /tmp/fuzz

tests/replay-fuzz-seeds.sh replays the seeds recorded in tests/fuzz-seed-corpus.txt, which is how a seed that once found a bug stays covered.

8. Sanitizers and leaks

What is and is not checked surprises people:

macOS: tur hangs at startup

If every tur command hangs, including tur --version, the cause may be the ASan runtime deadlocking before main when the Command Line Tools are older than the OS. Confirm it without tur:

printf 'int main(void){return 0;}\n' > bare.c
cc -fsanitize=address -g -o bare-asan bare.c
perl -e 'alarm 15; exec @ARGV' ./bare-asan     # hangs only when live

If that hangs, update the Command Line Tools (softwareupdate --list), or build with Homebrew LLVM. If it does not hang, the problem is something else. The remedies, and why sanitizers stay on by default, are in CLAUDE.md.

9. Writing it down

A bug you are not fixing right now goes in docs/reported/<slug>.md: a one-line summary with a severity, a minimal repro, the root cause with file:line when you know it, and fix directions. Add a row to docs/reported/README.md; bash tests/check-reported-index.sh fails if the index and the directory disagree. When a report is fixed, move it to docs/archive/. The full convention is in CLAUDE.md.

Before investigating a red result at all, search docs/reported/. A failure you hit is often already filed, with a repro and a root cause.

See also