---
title: 'CoCompiler: Bidirectional C-Lustre Compiler'
url: https://www.emergentmind.com/topics/cocompiler
type: topic
---

# CoCompiler: Bidirectional C-Lustre Compiler

Searching arXiv for the primary CoCompiler paper and closely related context.
CoCompiler is a prototype **bidirectional compiler and lifter between C and Lustre**, where Lustre is a **synchronous dataflow language used for reactive systems**. Its central premise is that if a compiler is implemented **as a relation rather than as a traditional function**, the same artifact can be run forward for compilation and backward for lifting. In CoCompiler, this idea is realized by rewriting the verified Lustre-to-C compiler **Vélus** in the **Walrus relational programming language**, then supplementing the relational core with **semantics-preserving C-to-C canonicalization passes in Haskell**. The resulting system addresses what it calls the **vertical lifting problem**, partially addresses the **horizontal problem**, supports lifting **real reactive C code into Lustre**, and can continue onward into **graphical behavioral models** such as SCADE block diagrams [2510.00210].

## 1. Conceptual basis

CoCompiler is organized around the **DSL lifting problem**: translating low-level or legacy code into a domain-specific language so that the recovered program is easier to understand, analyze, verify, and modify. Its target domain is **reactive systems**, for which Lustre is a natural DSL because it makes **time**, **streams**, and **synchronous execution** explicit [2510.00210].

The system distinguishes two problems. The **vertical lifting problem** is the problem of moving upward in abstraction from **canonical C** into Lustre. This is the part solved by the relationalized compiler core. The **horizontal problem** is the problem of handling real C programs that are **outside the compiler’s image** but are still “morally” reactive programs; CoCompiler addresses this with **semantics-preserving canonicalization passes** that rewrite C into a more canonical form before lifting [2510.00210].

The paper also characterizes an ideal bidirectional compiler/lifter relation as **many-to-many**, because many source and target programs can be semantically equivalent. CoCompiler does not implement that ideal relation in full. Instead, its practical design is closer to **“one to one” + canonicalization**: a relational core that works well on canonicalized inputs, plus one-direction normalization passes that expand the reachable C surface [2510.00210].

## 2. Relational compilation

The key technical move is to encode compilation relationally. Instead of a one-way mapping from Lustre to C, CoCompiler defines compilation in Walrus in a form approximated by:

```haskell
compile :: Lustre -> C -> Goal ()
```

In this form, compilation succeeds when the two programs satisfy the compilation relation. The same relation can be executed in two directions:

```haskell
(run 1 (cFile) (compile lustreFile cFile))
```

for compilation, and

```haskell
(run 1 (lustreFile) (compile lustreFile cFile))
```

for lifting [2510.00210].

Walrus is used as a **miniKanren-style relational language** embedded in Haskell. CoCompiler ports the core structure of Vélus into this setting, preserving much of its **compilation phases, AST designs, helper functions, and call-graph structure**. That choice is central to the system’s methodology: the lifting relation is not invented independently of the compiler, but obtained by **repurposing the compiler itself** [2510.00210].

This yields a concrete form of **bidirectional compilation**. The same artifact can be used as a compiler from Lustre to C or as a lifter from canonical C to Lustre. In the paper’s formulation, this makes it possible to lift **everything in `compile`’s image**, which is the essence of its solution to the vertical problem [2510.00210].

## 3. Architecture and intermediate languages

CoCompiler inherits the core structure of Vélus and ports four phases into Walrus:

- **Lustre ↔ NLustre**
- **NLustre ↔ Stc**
- **Stc ↔ Obc**
- **Obc ↔ Clight**

Here, **NLustre** is normalized Lustre, **Stc** is synchronous transition code, **Obc** is object code, and **Clight** is the C-like intermediate language used by CompCert [2510.00210].

In the **forward** direction, the intended flow is: parse Lustre, elaborate it with type and clock information, bridge into the internal AST, run the relational compiler forward, and pretty-print Clight/C. In the **backward** direction, which is the more developed use case, the flow is:

1. parse C with **CompCert’s `clightgen`** into Clight,
2. apply **Haskell canonicalization passes**,
3. run the relational compiler backward,
4. emit **normalized Lustre**,
5. optionally translate onward to **SCADE** and then to **`.xscade`** [2510.00210].

The Haskell canonicalization stage is the system’s main answer to the horizontal problem. The examples given are deliberately simple and mechanical, including **re-associating sequences of statements** and **inserting no-op `skip` commands**. These rewrites do not add semantic interpretation; they exist to make Clight structurally resemble the compiler image expected by the relational core [2510.00210].

Because **normalization** and **scheduling** are not themselves made bidirectional in the current implementation, CoCompiler does not accept arbitrary Lustre as forward input, and the backward direction produces **normalized Lustre** rather than a source-level reconstruction faithful to the original stylistic surface form [2510.00210].

## 4. Lifted structure and reactive-state recovery

The system’s worked example is the Lustre node `count`, which implements cumulative summation over time. The handwritten source is:

```lustre
node count (i:int) returns (o:int)
let
  o = (0 fby o) + i;
tel
```

The corresponding canonical C representation consists of a **state struct**, a **step function**, and a **reset function**:

```c
struct count {
  int norm1$1;
};

int fun$step$count(struct count *obc2c$self, int i) {
  register int o;
  o = (*obc2c$self).norm1$1 + i;
  (*obc2c$self).norm1$1 = o;
  return o;
}

void fun$reset$count(struct count *obc2c$self) {
  (*obc2c$self).norm1$1 = 0;
  return;
}
```

CoCompiler lifts this into normalized Lustre:

```lustre
node count (i : int32) returns (o : int32)
var norm1$1 : int32;
let
  o = norm1$1 + i;
  norm1$1 = 0 fby o;
tel
```

The recovered program is less readable than the handwritten version because it preserves normalized state variables such as `norm1$1`, but it reconstructs the essential reactive structure: `reset` becomes the initialization for `0 fby`, and the `step` function becomes the per-tick Lustre equations [2510.00210].

This example is representative of CoCompiler’s target class. It does not attempt to infer arbitrary high-level intent from arbitrary C. Instead, it recognizes a very specific organization of reactive code—explicit state, `reset`, `step`, and canonical Clight structure—and lifts that organization into Lustre’s stream-based form [2510.00210].

## 5. Correctness model and scope boundaries

CoCompiler’s trust argument is inherited indirectly from **Vélus**. Vélus provides syntax and semantics for each intermediate representation in **Rocq**, and each compilation phase is accompanied by a **machine-checked proof** that it preserves semantics. This is one reason CoCompiler is built by porting Vélus rather than by designing a decompiler from scratch [2510.00210].

At the same time, CoCompiler itself is **not presented as a fully verified system**. The Walrus relational port is manual, the Haskell canonicalization passes are not machine-checked, and the Lustre-to-SCADE path is also not end-to-end verified. The strongest claim is therefore not formal verification of the full bidirectional tool, but confidence derived from the verified source compiler and the close structural correspondence of the port [2510.00210].

Its limitations are explicit. The current system:

- lifts only **C in or near Vélus’s image**,
- relies on **limited, example-driven horizontal transformations**,
- produces **normalized Lustre** rather than denormalized source-like Lustre,
- does not yet support some Lustre features such as **`merge`** and **`reset`**,
- omits bidirectional **normalization**, **scheduling**, and some **optimization** passes,
- and remains brittle with respect to small syntactic changes that place the C program outside the expected canonical form [2510.00210].

The paper is especially clear that **any syntactic change that moves a program outside Vélus’s image** may render it unliftable by the current relation. That brittleness is not incidental; it follows directly from the fact that the implemented relation approximates a semantic many-to-many correspondence by starting from a particular compiler image and widening it only partially through canonicalization [2510.00210].

## 6. Significance and broader implications

CoCompiler’s main significance is methodological. It proposes **rapid DSL lifter prototyping by relationalizing an existing compiler**, rather than hand-building a decompiler for a DSL from first principles. In the paper’s account, the core relational port required **just over three engineer-weeks**, with four compiler passes each taking approximately **32 hours**, even though some of that effort also advanced Walrus itself [2510.00210].

This suggests a broader pattern: if a domain already has a compiler from DSL \(X\) to language \(Y\), then rewriting that compiler relationally can yield an \(X \leftrightarrow Y\) compiler/lifter pair, with additional **canonicalization** used to widen the reachable subset of \(Y\). The paper presents this as **modular**, **language-agnostic**, and **fast to implement** [2510.00210].

For reactive systems specifically, the benefit is immediate. CoCompiler provides a path from **legacy or low-level C** back to **Lustre**, and from there onward to **graphical behavioral models** such as SCADE. That enables existing verification and model-based workflows to be applied to code that would otherwise remain trapped at the imperative level [2510.00210].

The obvious next steps, all identified by the paper, are to broaden the horizontal canonicalization repertoire, port more of Vélus, improve readability through denormalization and naming recovery, and extend the same relational-compilation idea beyond C↔Lustre to other DSL ecosystems. In that sense, CoCompiler is less a finished universal decompiler than a concrete demonstration that **relational programming offers a practical foundation for building DSL lifters by repurposing existing compilers** [2510.00210].

Source: https://www.emergentmind.com/topics/cocompiler