evocompiler.com

A compiler tuned to your project, not to the average of all programs.

General-purpose compilers optimise for every program at once — which means for none of them in particular. evolve works out which transformations are correct for your code on your target, tunes them against real measurements on that hardware, and freezes the result into a compiler you run offline.

You do not have to read any of this to use it. One command installs it and connects it to the agent you already work with; this page is for when you want to know what it is doing.

Three overlapping circles with only a small shared centre filled in
General-purpose

It may only use what is safe for all of them at once.

A single circle, filled throughout
Tuned to you

One program, one target — the whole area is open.

The opportunity

Your program is not the average program.

A general compiler has one set of heuristics for every program anyone will ever write, on every machine they will ever run it on. It can only apply what is safe across all of them at once, and it cannot measure your code on your part, because it has never seen either.

What that leaves behind is the structure specific to your workload: the shapes your data actually takes, the operations your target actually has, the sizes your loops actually run. A compiler that knew those could commit where a general one must keep hedging.

How one is made

Designed against your code, checked, measured, then frozen.

A compiler is designed by the agent you already work with, against a C program you point it at. What comes out the far end is a binary.

Schematic a drawing of how the search narrows — not a record of a run
  1. 01 proposed Rewrites are proposed. Every one is a guess until it is checked. 12 marks
  2. 02 proved What survives its obligations. Anything that cannot be discharged is dropped. 7 marks
  3. 03 faster What also measured faster on the part you ship. Correct is not the same as faster. 4 marks
  4. 04 admitted What enters the library as a pass, with its preconditions attached. 2 marks
  5. 05 one compiler The admitted set, frozen with its tuned parameters. It runs offline. one
The marks are drawn, not counted. Twelve, seven, four, two: the shape of a narrowing, chosen to fit the picture. A real run's numbers live on that run's own page, beside the machine they were measured on. This is not that page.

Your agent, your program

You connect evolve to the agent you already code with, over MCP, and point it at a C program you already have — real code rather than a description of it. What comes back is a measurement against your own host's baseline compilers at their standard optimisation levels, not a model's opinion: no agent, no search and no model anywhere on that path.

input
one C program you point it at, and the target you build for
driven by
the coding agent you already use, over MCP
today
a narrow family of programs; anything outside it is refused rather than attempted
needs
an account

Correct before it is fast

A candidate arrives carrying the preconditions it assumes and the equivalence it claims. Those are discharged mechanically against what the function actually reads, writes and does, at the scope the rewrite applies to. Anything that cannot be discharged is refused, and the reason is kept — a rejection is a record rather than a shrug.

What ends up on your machine carries the matcher, not the prover.

checks
preconditions, and the equivalence claimed
scope
the region the rewrite applies to
on failure
refused, with the reason kept

On the part you actually ship

Candidates that survive are compiled and timed on the target you build for, interleaved against your ordinary build so both arms meet the same machine in the same state. Floating-point behaviour is held to the strict setting on both arms, and the difference against the baseline is checked against a stated tolerance — evidence about this run, never a proof.

If the measured result does not beat your default compiler, the baseline is what ships.

Your ordinary build Candidates, timed on the target
Each bar is one candidate's time on the target; shorter is faster. The dashed line is your ordinary build — the top candidate landed past it and lost, and when nothing beats it the baseline is what ships. Lengths are drawn to show the shape of the comparison. They are not a measurement, and this figure carries no scale, no axis and no numbers.
Tuning on an embedded target runs your program on the real part, many times. The board has to be disconnected from anything around it while that happens. What that means.
measured on
the target you build for
compared against
your ordinary build, interleaved
if it is not faster
the baseline ships

A compiler, not a service

What survives is pinned: a frozen, versioned composition together with the parameters that were measured for it. For a given version, identical input gives identical output, and it needs nothing from us to run — no account, no lookup, no network.

That also means it is a thing you can hand to someone else. It was tuned for one program, so how much of the advantage survives depends on how close theirs is to yours.

form
a versioned binary, with its tuned parameters
determinism
same input, same output, per version
to run it
nothing from us

The command your build calls

evocc is what a build system calls in place of gcc; evolve manages your projects and the compilers on this machine. A build whose compiler changed with nothing on screen saying so is the failure this product exists to avoid — so invoking it, including under a compiler's name, resolves a .evolve/ folder in your project that names which compiler to use, and it stops if that folder is not there. The choice lives in your repository, where it can be read and reviewed, rather than in your PATH.

Install v0.1.33
curl -fsSL --proto '=https' --proto-redir '=https' https://api.evocompiler.com/install.sh | sh

Click the line to select all of it · ⌘C to copy. Tap and hold the line to select it, then choose Copy.

The longer way in.

the compiler command
evocc
the management command
evolve
what selects the compiler
a .evolve/ folder in your project, not your PATH

Why measure instead of assume

On one worked example a hand-set parameter measured 3.13× where the searched one measured 9.77× on the same hardware. That is one example, measured on real hardware, not proof-backed, and not a promise about your code. It is the argument for measuring rather than assuming, and nothing more than that.

Walk one example

One small program, four times over.

The same small function as source, as an intermediate form, after the rewrites that survived, and as instructions for one part. Beside it, the record of what each rewrite had to hold and what could not be discharged. Nothing here was produced by a compiler, and every pane says so on its own header.

Representation
Source written for this page
/* Add up n bytes. This says what, and nothing about how. */
#include <stddef.h>
#include <stdint.h>

uint32_t sum_bytes(const uint8_t *restrict p, size_t n)
{
    uint32_t acc = 0;
    for (size_t i = 0; i < n; ++i)
        acc += p[i];
    return acc;
}
IR illustrative, not the form evocc uses
region @sum_bytes {
  facts
    shapes     p:[n] bytes              n is known only at run time
    aliasing   p is not written here    -- from restrict and const
    numerics   uint32 accumulator       wraparound is defined
    effects    reads p[0..n), writes nothing
    contract   p addresses n readable bytes   -- the caller's promise

  reduce   %acc = sum_i ( zext32( p[i] ) )
  return   %acc

  hot      the i-loop: n iterations, one byte each
}
Optimised illustrative in the same way, not compiler output
region @sum_bytes.tgt {
  peel    i until p + i is 4-byte aligned
          held: the peel establishes the alignment rather than
                assuming it, and it runs at most 3 times

  widen   the aligned body loads 4 bytes at once
          held: every byte it reads is inside p[0..n)

  split   %acc into four partial sums, added at the end
          held: uint32 addition wraps, so regrouping returns
                the same value for every input

  guard   tail of 0..3 bytes, added one at a time, kept

  not admitted for this target:
    read past the end to finish the last word
          -- reads outside the object; the page after p may not exist
    hoist the n == 0 test out of the loop
          -- n == 0 is reachable, and the answer there is 0
}
Target hand-written for this page, not compiler output
        @ hand-written for this page; not emitted by any compiler
        @ target: armv7e-m, DSP extension present
        @ on entry p is 4-byte aligned and r3 holds the number of
        @ whole words; the peel above established both.
        movs    r5, #0              @ zero, for the byte differences
.Lword:
        ldr     r4, [r0], #4        @ four bytes at once
        usada8  r2, r4, r5, r2      @ r2 += b0 + b1 + b2 + b3
        subs    r3, r3, #1
        bne     .Lword
        @ tail: 0..3 bytes, still added one at a time
        @ without the DSP extension this lowers to four widening adds
The record
Baseline what a general C compiler has to assume here
loop order
as written, one iteration at a time
loads
one byte per iteration
alignment
none assumed, because none is known
trip count
not known until the call happens
None of that is a fault. It is what being correct for every input costs.
Transformation peel and widen, one candidate
rewrite
peel, widen, split, guard the tail
assumes
p is readable across the whole of p[0..n)
assumes
uint32 addition wraps
Held
the value returned, for every n including 0
Held
nothing outside p[0..n) is read
A transformation is not the rewrite. It is the rewrite plus the conditions under which the rewrite is allowed.
Obligations what had to be discharged, and what could not be
Held
every load lies inside p[0..n)
Held
alignment is established by the peel, not assumed
Held
regrouping returns the same value, since uint32 addition wraps
Held
the 0..3 tail bytes are still added one at a time
Held
n == 0 is reachable, and the answer there is 0
Not admitted
read past the end to finish the last word
Not admitted
hoist the n == 0 test out of the loop
Anything that cannot be discharged is dropped. That is the whole gate.
A worked example written for this page, in the four shapes the same program passes through. The IR is a sketch of what such a form has to record rather than evocc's own, and no timing of any kind appears here. What each pane is really showing is which rewrites were allowed and why the others were not. Doing four bytes at once is not four times faster — what it is worth is a question only measurement answers.
The boundary

Designing needs us; using what you designed does not.

Everything expensive — the agent, the search, the proving, the runs on real hardware — happens on our side of a line. What crosses the line is a binary. After that your build is source, compiler, binary, and nothing else.

Choose a side of the boundary to read in detail
agent the one you already code with, over MCP, against a C program you point it at evolve the factory that assembles one compiler for one target search parameters searched against real runs rather than set by hand proving what a rewrite claims is discharged mechanically, or it is refused measurement on the part you actually ship, not on a model of it
One artifact crosses the boundary, in one direction A single arrow points downward from the design side to the build side, ending on a solid bar. Nothing is drawn travelling back up. the frozen compiler, once
01

It needs an account

Designing a compiler needs an account and a connection to the factory. Making one needs us; running one you already have does not. Where that stands today.

02

It needs your agent

A compiler is designed over MCP, by the agent you already code with, against a C program you point it at — real code rather than a description of it. What that involves.

03

It needs the real hardware

Tuning against a board means your code runs on that board repeatedly, in orders and timings you did not choose. Disconnect it from anything around it first — why, and the checklist.

04

It carries a credential

The agent connection is tied to your account, which is why the configuration lives behind sign-in rather than on a public page.

01

It runs offline

A compiler you have saved is yours. It runs on your own machine with no account, no lookup and no network, for as long as you like — that half is built, and a test walks the runner's import closure on every push so that nothing able to open a connection can get into it. You cannot get one today: tuning runs reach our server and finish, and nothing has been frozen into a compiler through the service yet.

02

It is never slower

If the measured result does not beat your default compiler, the baseline is what ships, silently. The floor is the compiler you already had. Half of that floor is enforced today. At compile time it holds completely: any failure on the tuned path yields a byte-identical ordinary build, and a test suite holds it there. The other half — the same check made when a compiler is released rather than when it runs — is not written yet.

03

It says when it found nothing

If no matching optimisation exists for your program it is compiled unchanged and you are told so. That is a normal outcome, not an error.

04

It is meant to be handed on

The design is that you give a colleague the one you built and it runs on their machine, offline — tuned for your code rather than theirs, so how much of the advantage survives depends on how close the two are. The step that works out which parts of it fit a different program is built and passes end to end. What is missing is the compiler itself: nothing has been frozen through the service yet, so there is nothing to hand on. If you were sent here holding one, start here.

A drawing of how the two halves relate. It carries no measurement, no timing and no quantity of any kind.

What a result means

Correctness is a question with several answers, and they are not the same one.

“It gives the same answer” is not one claim. These are the different things somebody can mean by it, and which of them this system makes.

Technique The question it answers Where this system stands
A parser or a type checker Is this program structurally valid and well typed? Necessary, and it says nothing about whether two versions compute the same thing.
Tests run on both versions Did both agree on the examples we actually executed? This is what your own test suite does, and it is what the adapter you write runs on every trial. Evidence, not a proof.
A bounded check Can a difference exist inside this explicit model, up to these sizes? This is what the free proposal checker does. It is the strongest claim here and it is still bounded.
Universal equivalence Is this rewrite equivalent for every input it could ever be given? Not what a bounded result says, and we do not present one as if it were.
A verified compiler Does a proof cover the compiler itself and every transformation it makes? Not what this system claims.
A bounded result is not a universal proof. It means no difference was found at the supplied bounds, inside the model that was stated. It does not cover a symbolic loop bound, arbitrary memory effects or aliasing, undefined behaviour, or bit-exact floating point — floating-point values in that path are modelled as exact reals, so a reassociation can pass there and still round differently in a real program.
Afterwards

What you designed is yours to run.

A compiler you have saved runs on your own machine with no account, no lookup and no network. Designing needs us; using what you designed does not.

Install Evo · The commands, written out · What your agent reads