Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Self-Compiler

Self-Compiler is a research prototype for making the GCC C/C++ compiler AI-extensible.

The project now has a real compiler-native vertical slice: a GPL-compatible shared plugin is loaded into GCC's cc1 or cc1plus process, executes as a GIMPLE pass after SSA construction, extracts per-function IR features, and exposes a conservative policy hook over GCC's optimization-pass gates. The existing generate-and-validate repair loop remains as the driver-level supervisor for source edits that cannot safely occur inside a parser callback.

This is no longer framed as “an AI tool that happens to call GCC.” The target architecture is:

                       GCC-AI compiler

 C / C++ source
       |
       v
 GCC lexer + parser  <---- diagnostic repair advisor
       |
     GENERIC
       |
     GIMPLE / SSA   <---- learned optimization policy  [working plugin]
       |
      RTL           <---- future target-cost/scheduling policy
       |
 machine code
       |
 tests + benchmarks ----> reward/evidence ----> offline trainer

The trusted kernel remains GCC. AI proposes bounded decisions; GCC's parser, type system, IR invariants, assembler, linker, tests, analyzers, and benchmarks remain the acceptance authority.

What works now

Compiler-native GIMPLE integration

self_compiler/gcc_plugin/ai_native_plugin.cc is compiled against the exact plugin headers shipped with the host GCC. It runs inside the frontend and emits JSON Lines such as:

{"schema":"gcc-ai.telemetry.v3","event":"function-ir","function":"find","basic_blocks":9,"gimple_statements":12,"phi_nodes":2,"calls":0,"branches":2,"edges":10,"memory_reads":3,"memory_writes":1,"float_ops":0,"back_edges":1,"max_loop_depth":1,"dominator_height":4,"cyclomatic_complexity":3,"max_out_degree":2}

The initial feature surface (schema gcc-ai.telemetry.v3) includes scalar totals plus structural topology summaries derived from dominance information:

  • GIMPLE statement count
  • basic-block count and CFG edge count
  • PHI-node count
  • call count
  • conditional/switch branch count
  • memory reads/writes (virtual use/def operands)
  • float-typed assignments
  • back edges, maximum natural-loop nesting depth, dominator-tree height, McCabe cyclomatic complexity, maximum branch fan-out
  • function and source location

Raw CFG connectivity (function-cfg events: successor triples with raw edge-flag bitmasks) is exported behind the opt-in -fplugin-arg-ai_native_c-cfg=export argument. Scalar summaries are always emitted because bounded vectors are what current policies consume; full graphs remain available for future graph-neural consumers without inflating default telemetry volume.

Compiler pass-policy hook

A policy artifact can conservatively disable a named optimization pass:

disable_pass=evrp

Rules may also be conditioned on per-function IR features and restricted to one static pass instance:

disable_pass=ccp if gimple_statements<50&&basic_blocks<10
disable_pass=ccp#112 if max_loop_depth>=2

Instance numbers are GCC's static pass-instance counters, emitted in every pass-gate/gate-eval event so a workflow can discover which instances exist before targeting one.

The plugin uses GCC's PLUGIN_OVERRIDE_GATE callback and records every applied decision, including whether it came from a global or a conditional rule. Conditional rules only affect passes that run after SSA construction for that function; functions without captured features keep GCC's default decision. Malformed policy lines abort compilation with a clear diagnostic instead of silently degrading to "no policy". It deliberately cannot force-enable passes: GCC may have left a pass disabled because its prerequisites do not hold. Sub-pass cost models (unroll factors, inlining profitability) remain fork territory—the architecture document describes that milestone honestly rather than pretending gate toggles replace them.

This is the same broad research direction as MILEPOST GCC, which combined GCC plugins, program features, runtime behavior, and learned optimization selection, and later MLGO's use of learned policies inside industrial compiler heuristics. The next scientific step is not "add an LLM everywhere"; it is to choose one expensive heuristic, define a feature/action/reward contract, train against measured outcomes, and beat a fixed GCC baseline under held-out workloads.

Policy evaluation lab

The missing reward loop is a tool. gcc-ai-policy-lab builds a program under three kinds of variant—pure GCC, a plugin-loaded control (isolating plugin overhead), and one pass-disable policy per candidate—then measures them with interleaved paired sampling so machine drift hits every variant equally:

python -m self_compiler.policy_lab \
  --compiler gcc \
  --runs 7 --passes evrp,ccp,cddce \
  --source examples/slow_search.c \
  -- -O2 --benchmark-cmd "{binary}"

Verdicts are statistically guarded. Because interleaved sampling takes one sample per variant per cycle, deltas against the control are paired observations; a variant is called improving only if its median beats the required margin AND the lower bound of a seeded bootstrap confidence interval over the paired median delta excludes zero. On this repository's own development machine the guard repeatedly reclassified apparent 20%+ median wins into within-noise. Every measurement is emitted as versioned JSON Lines (gcc-ai.policy-lab.v1) including per-build SHA-256 digests, a plugin-neutrality record proving the unloaded plugin does not perturb codegen, full samples, and a decision record. --objective size switches the reward to deterministic binary size; --check-reproducible rebuilds the control and winner and requires byte-identical binaries (PE timestamp normalization is applied automatically where supported); and a winning candidate must additionally survive --differential-trials seeded random-input comparisons against the control before any policy artifact is promoted.

Corpus and rule synthesis

Single-workload evidence cannot justify a policy, so the same methodology is available across many programs:

python -m self_compiler.corpus --out dataset.jsonl \
  --compiler gcc --runs 5 --passes evrp,ccp \
  --source workloads/a.c --source workloads/b.c -- -O2

python -m self_compiler.synthesis --corpus dataset.jsonl \
  --policy-out learned.policy --report synthesis.jsonl

gcc-ai-corpus emits gcc-ai.corpus.v1: per-workload source digests, raw per-function telemetry captured during the control build, deterministic feature aggregates, and every variant's outcome with paired-delta confidence intervals. gcc-ai-synthesize searches cross-validated single-feature decision stumps over that dataset and emits conditional rules in the plugin's exact grammar (disable_pass=ccp if total_gimple_statements>=400) only when held-out accuracy beats the majority baseline by the required margin and both sides of the split retain minimum support. Refusals are recorded with reasons—on small corpora the expected outcome is an explicit, evidence-backed refusal, not a rule.

Differential testing

gcc-ai-differential --reference ref.exe --candidate cand.exe compares two binaries across seeded pseudo-random inputs (edge cases plus generated blobs, all replayable from the recorded seed) and fails on any exit-status or stdout divergence. The lab invokes it automatically before promoting a winning policy.

Validated repair supervisor

The Python repair layer handles compilation failure, static-analysis repair, reproducible runtime correction, and measured optimization experiments. Candidate source is staged without touching the original, compiled, tested, promoted atomically, and then revalidated from the real source path. Unexpected failure rolls back automatically.

The supervisor now consumes GCC's native JSON diagnostic format. Compiler-authored fix-its are applied first as surgical, half-open byte-range edits; they support UTF-8 source and are accepted only when every edit in a diagnostic is valid for the current translation unit. AI is consulted only when GCC did not provide an applicable edit.

Runtime self-correction can use GDB's machine-interface protocol. A failing direct execution is reproduced under GDB, and the model receives a structured signal plus stack frames, function names, source locations, and addresses rather than undifferentiated stderr.

This layer currently lives above the GCC process because syntax errors occur before a GIMPLE plugin can run. Moving the diagnostic/edit channel into a GCC fork is the next frontend milestone, described in the architecture document.

Build the real GCC plugins

Requirements:

  • GCC and G++ from the same installation
  • GCC plugin development headers
  • Python 3.10+

Portable builder:

python -m self_compiler.build_plugin

On Windows, GCC frontends are separate PE executables, so the builder produces two matched DLLs:

build/gcc-plugin/ai_native_c.dll
build/gcc-plugin/ai_native_cpp.dll

On ELF platforms it produces one ai_native.so whose unresolved GCC symbols are resolved by the loading frontend.

PowerShell users can also run:

powershell -NoProfile -ExecutionPolicy Bypass -File scripts/build_gcc_plugin.ps1

Compile through GCC-AI

C:

python -m self_compiler.gcc_native \
  --compiler gcc \
  --telemetry build/c-run.jsonl \
  -- -O2 program.c -o program

C++:

python -m self_compiler.gcc_native \
  --compiler g++ \
  --telemetry build/cpp-run.jsonl \
  -- -O2 program.cpp -o program

Apply an experimental policy:

python -m self_compiler.gcc_native \
  --compiler gcc \
  --telemetry build/policy-run.jsonl \
  --policy gcc_plugin/example.policy \
  -- -O2 program.c -o program

After installation, the equivalent commands are gcc-ai-build-plugin and gcc-ai.

The plugin can also be loaded without the Python launcher:

gcc -O2 \
  -fplugin=/absolute/path/ai_native_c.dll \
  -fplugin-arg-ai_native_c-output=/absolute/path/run.jsonl \
  program.c -o program

Syntax repair and the semicolon example

A missing semicolon prevents the frontend from producing valid GENERIC/GIMPLE, so a normal optimization plugin never sees that function. The rigorous repair sequence is:

parse error
   |
   +--> deterministic GCC fix-it, when available
   |
   +--> bounded AI edit proposal for ambiguous cases
             |
             v
        in-memory source overlay
             |
       re-lex + re-parse
             |
       semantic compilation
             |
        tests / analyzers
             |
     emit patch or promote source

The current supervisor implements that hierarchy. This command demonstrates the deliberately limited offline fallback for a missing semicolon that GCC 14 does not fix automatically in this diagnostic shape:

python -m self_compiler.cli \
  --repair --offline --max-attempts 1 \
  examples/missing_semicolon.c -- -Wall -Wextra

The --offline repair is deliberately just a semicolon demonstration (it refuses lines that end in comments, braces, labels, or preprocessor directives, and preserves the file's line endings). Gemini-backed repair requires GEMINI_API_KEY; missing credentials are never silently treated as AI.

Bounded prompts: diagnostic-driven localization

Whole production translation units are never shipped to the model. When every located diagnostic falls inside one top-level function of a large unit (more than 120 lines), the repair loop sends only that function's text to the model under a return-this-function contract, splices the validated replacement back byte-exactly (brace balance is checked before splicing), and lets the normal compile/test gauntlet judge the result. If localization is impossible—multiple functions implicated, no enclosing function found, or an unbalanced candidate—the loop transparently falls back to the whole-unit prompt. The journal records which strategy produced each candidate (localized-function or whole-unit).

For a reproducible crash with compiler debug information:

python -m self_compiler.cli \
  --self-correct --debugger gdb \
  examples/null_deref.c -- -g -O0

Repeat --program-arg VALUE to pass arguments during direct execution. GDB capture currently applies to direct execution; an arbitrary shell-based --test-cmd cannot be safely reconstructed as debugger arguments.

What “AI-native GCC” should mean

  • Frontend: AI augments ambiguous diagnostics and repair proposals, but the deterministic parser must accept the result.
  • Middle end: learned models replace selected hand-tuned heuristics such as inlining, vectorization profitability, unrolling, or pass ordering.
  • Backend: learned cost models may advise instruction selection/scheduling or register-allocation heuristics, while RTL legality remains deterministic.
  • Feedback: real code size, compile time, runtime, energy, and hardware counters provide rewards—not an LLM's opinion.
  • Deployment: small pinned local models or policy tables run in bounded time. Network LLM calls do not sit in GCC's hot compilation path.
  • Safety: every model and feature schema is versioned; deterministic fallback, replay, differential testing, and rollback are mandatory.

“Self-correcting program” here means the compiler can propose and validate a new program revision. Runtime self-modifying machine code is a different—and much riskier—system. Learned on-chip execution control from the attached idea is a hardware/microarchitecture project, not a GCC compiler phase, and should be pursued separately after the compiler policy loop is empirically sound.

Research precedent and novelty boundary

  • GCC officially supports plugins that inspect and transform code and register new passes through compiler callbacks: GCC Plugin API.
  • GCC uses GENERIC, GIMPLE, and RTL as progressively lower representations: GENERIC, GIMPLE, and RTL.
  • MILEPOST GCC already demonstrated a machine-learning-enabled self-tuning GCC using program features and optimization feedback: IBM Research summary.
  • MLGO demonstrated that a learned policy can replace a bounded compiler heuristic and reported up to 7% size reduction for its LLVM inlining case: MLGO paper.
  • GCC diagnostics already support machine-readable fix-it hints, and GCC's own guidance says those edits should be verified to compile: GCC diagnostic guidelines.

Therefore, “GCC plus AI” alone is not novel. A credible contribution needs a sharply defined decision point, a reproducible training/evaluation corpus, held-out workloads and architectures, comparison against GCC's heuristic and autotuning baselines, and evidence for runtime, code size, compile-time overhead, determinism, and semantic preservation.

Verification

ruff format --check .
ruff check .
python -m pytest -q

The integration suite builds plugins against the installed GCC, loads them into both the real C and C++ frontends, compiles and runs programs, validates JSON telemetry, proves that global and per-function conditional policies reach an actual GCC pass gate, applies a real GCC-authored source fix before AI, captures a real SIGSEGV call chain through GDB/MI, and runs the policy lab end-to-end on a real toolchain.

google-genai is an optional extra (pip install gcc-ai-native[gemini]); everything except live Gemini repair works without it.

Current boundary

  • The GIMPLE telemetry/pass-gate vertical slice is real and locally verified; policies may be conditioned per function on scalar totals and dominance-derived structural features (loop depth, back edges, dominator height), with opt-in raw CFG export, rule-audit denominators, and pass-instance targeting.
  • No learned model has beaten GCC on held-out workloads yet. The pipeline that would prove one now exists end to end: paired-significance lab → multi-workload corpus → cross-validated synthesis → differential gate → reproducible artifact. On today's noisy single machine the honest result is refusal, and the tooling says so explicitly.
  • Single-session timing verdicts are unstable on shared machines (the same program can flip between improves and regresses across runs); only corpus-aggregated, CI-gated conclusions should be trusted.
  • Gate toggles remain the safe action vocabulary; replacing sub-pass cost models (unroll factors, inlining profitability) requires the maintained-fork milestone and is not claimed here.
  • Syntax repair is still supervised at driver level, not patched into a maintained GCC fork; every run can emit a versioned JSONL journal (--journal) tracing each edit to GCC or the named model, and large-unit repairs are localized to single functions to bound model exposure.
  • Structural telemetry covers control-flow shape; data-flow facts (alias structure, live ranges, value ranges) and true loop-tree metadata remain future work.
  • Differential testing covers exit status and stdout under seeded inputs; stderr content and environment-dependent behavior are out of scope for equivalence.
  • LTO/frontend-plugin compatibility has not yet been established; current evidence covers ordinary C and C++ compilation.

About

Making the GCC C/C++ compiler AI-extensible with a observe, diagnose, patch, verify loop

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages