A formally verified compiler for Lustre
Timothy Bourke
This course describes an ongoing research collaboration with Lélio Brun,
Pierre-Évariste Dagand, Xavier Leroy, Marc Pouzet, and Lionel Rieg.
Cours MPRI Parallélisme synchrone, November 2016
1 / 47
et al. (1987): “LUSTRE: A declarative language
]
Lustre [Caspi
for programming synchronous systems”
node count (ini, inc: int; res: bool)
returns (n: int)
let
n = if (true fby false) or res then ini
else (0 fby n) + inc;
tel
ini
inc
res
count
n
val count(ini : int :: .; inc : int :: .; res : bool :: .)
returns (n : int :: .)
2 / 47
ini
inc
res
et al. (1987): “LUSTRE: A declarative language
]
Lustre [Caspi
for programming synchronous systems”
node count (ini, inc: int; res: bool)
returns (n: int)
let
n = if (true fby false) or res then ini
else (0 fby n) + inc;
tel
count
n
val count(ini : int :: .; inc : int :: .; res : bool :: .)
returns (n : int :: .)
ini
inc
res
b
c
n
0
0
F
T
0
0
0
1
F
F
0
1
0
2
F
F
1
3
0
1
F
F
3
4
0
2
T
F
4
0
0
3
F
F
0
3
0
0
F
F
3
3
···
···
···
···
···
···
• Semantic model: discrete streams.
• Nodes define a (functional) relation between input and output streams.
• Sets of ‘causal’ equations/definitions (always variable at left).
2 / 47
since if x and X’ are variables of respective clocks B and B’,
and if B and
B’al.have
the“LUSTRE:
same A
clock,
ini
Caspi et
(1987):
declarative language
Lustre [for programming synchronous systems”
current
(X1 op current
]
(X’>
inc
res
count
n
node count (ini, inc: int; res: bool)
is areturns
legal expression
(n: int) for every binary operator op.
let
n = if Nodes
(true fby and
false) or
res then ini
1.3
Nets
else (0 fby n) + inc;
A node is a LUSTRE subprogram.
It receives input varitel
ables,count(ini
computes : output
and ::
possibly
vari-:: .)
val
int :: variables,
.; inc : int
.; reslocal
: bool
ables, by (nmeans
a system
of equations. For instance, we
returns
: intof::
.)
can define a general counter as follows:
node COUNT (init.
incr:
int;
reset:
boo11
returns
(n: int):
let
n = init
->
if reset then init
else prefn)
+ incr:
tel:
Node instantiation
takes a functional
name of a node declared with heading
node N ( ir : 71; ir : 7s; . . . ; & : rP)
form:
if N is the
2 / 47
et al. (1987): “LUSTRE: A declarative language
]
Lustre [Caspi
for programming synchronous systems”
node count (ini, inc: int; res: bool)
returns (n: int)
let
n = if (true fby false) or res then ini
else (0 fby n) + inc;
tel
ini
inc
res
count
n
val count(ini : int :: .; inc : int :: .; res : bool :: .)
returns (n : int :: .)
node count (ini, inc: int) returns (n: int)
let
n = if (true fby false) then ini else (0 fby n) + inc;
tel
node nats (res: bool) returns (n: int)
let
reset
n = count(0, 1)
every res
tel
2 / 47
Sampling and Merging
node avgvelocity(delta: int; sec: bool) returns (v: int)
var r, t: int;
let
r = count(0, delta, false);
t = count((1, 1, false) when sec);
v = merge sec ((r when sec) / t) ((0 fby v) whenot sec);
tel
val avgvelocity(delta : int :: .; sec : bool :: .) returns (v : int :: .)
delta
sec
r
(c1 )
r when sec
t
(c2 )
0 fby v
(0 fby v) whenot sec
v
0
F
0
0
1
F
1
0
2
F
3
1
0
0
0
0
0
0
0
0
0
1
T
4
3
4
1
0
0
4
2
F
6
4
4
4
4
3
T
9
6
9
2
1
4
0
T
9
9
9
3
2
4
4
3
3
F
12
9
3
3
3
···
···
···
···
···
···
···
···
···
···
3 / 47
Sampling and Merging
node avgvelocity(delta: int; sec: bool) returns (v: int)
var r, t: int;
let
r = count(0, delta, false);
t = count((1, 1, false) when sec);
v = merge sec ((r when sec) / t) ((0 fby v) whenot sec);
tel
val avgvelocity(delta : int :: .; sec : bool :: .) returns (v : int :: .)
delta
sec
r
(c1 )
r when sec
t
(c2 )
0 fby v
(0 fby v) whenot sec
v
0
F
0
0
1
F
1
0
2
F
3
1
0
0
0
0
0
0
0
0
0
1
T
4
3
4
1
0
0
4
2
F
6
4
4
4
4
3
T
9
6
9
2
1
4
0
T
9
9
9
3
2
4
4
3
3
F
12
9
3
3
3
···
···
···
···
···
···
···
···
···
···
3 / 47
Sampling and Merging
node avgvelocity(delta: int; sec: bool) returns (v: int)
var r, t: int;
let
r = count(0, delta, false);
t = count((1, 1, false) when sec);
v = merge sec ((r when sec) / t) ((0 fby v) whenot sec);
tel
val avgvelocity(delta : int :: .; sec : bool :: .) returns (v : int :: .)
delta
sec
r
(c1 )
r when sec
t
(c2 )
0 fby v
(0 fby v) whenot sec
v
0
F
0
0
1
F
1
0
2
F
3
1
0
0
0
0
0
0
0
0
0
1
T
4
3
4
1
0
0
4
2
F
6
4
4
4
4
3
T
9
6
9
2
1
4
0
T
9
9
9
3
2
4
4
3
3
F
12
9
3
3
3
···
···
···
···
···
···
···
···
···
···
3 / 47
Sampling and Merging
node avgvelocity(delta: int; sec: bool) returns (v: int)
var r, t: int;
let
r = count(0, delta, false);
t = count((1, 1, false) when sec);
v = merge sec ((r when sec) / t) ((0 fby v) whenot sec);
tel
val avgvelocity(delta : int :: .; sec : bool :: .) returns (v : int :: .)
delta
sec
r
(c1 )
r when sec
t
(c2 )
0 fby v
(0 fby v) whenot sec
v
0
F
0
0
1
F
1
0
2
F
3
1
0
0
0
0
0
0
0
0
0
1
T
4
3
4
1
0
0
4
2
F
6
4
4
4
4
3
T
9
6
9
2
1
4
0
T
9
9
9
3
2
4
4
3
3
F
12
9
3
3
3
···
···
···
···
···
···
···
···
···
···
3 / 47
Sampling and Merging
node avgvelocity(delta: int; sec: bool) returns (v: int)
var r, t: int;
let
r = count(0, delta, false);
t = count((1, 1, false) when sec);
v = merge sec ((r when sec) / t) ((0 fby v) whenot sec);
tel
val avgvelocity(delta : int :: .; sec : bool :: .) returns (v : int :: .)
delta
sec
r
(c1 )
r when sec
t
(c2 )
0 fby v
(0 fby v) whenot sec
v
0
F
0
0
1
F
1
0
2
F
3
1
0
0
0
0
0
0
0
0
0
1
T
4
3
4
1
0
0
4
2
F
6
4
4
4
4
3
T
9
6
9
2
1
4
0
T
9
9
9
3
2
4
4
3
3
F
12
9
3
3
3
···
···
···
···
···
···
···
···
···
···
3 / 47
Sampling and Merging
node avgvelocity(delta: int; sec: bool) returns (v: int)
var r, t: int;
let
r = count(0, delta, false);
t = count((1, 1, false) when sec);
v = merge sec ((r when sec) / t) ((0 fby v) whenot sec);
tel
val avgvelocity(delta : int :: .; sec : bool :: .) returns (v : int :: .)
delta
sec
r
(c1 )
r when sec
t
(c2 )
0 fby v
(0 fby v) whenot sec
v
0
F
0
0
1
F
1
0
2
F
3
1
0
0
0
0
0
0
0
0
0
1
T
4
3
4
1
0
0
4
2
F
6
4
4
4
4
3
T
9
6
9
2
1
4
0
T
9
9
9
3
2
4
4
3
3
F
12
9
3
3
3
···
···
···
···
···
···
···
···
···
···
3 / 47
Static Clocking
node avgvelocity(delta: int; sec: bool) returns (v: int)
var r, t: int;
let
r = count(0, delta, false);
t = count((1, 1, false) when sec);
v = merge sec ((r when sec) / t) ((0 fby v) whenot sec);
tel
val avgvelocity(delta : int :: .; sec : bool :: .) returns (v : int :: .)
sec
r
t
(0 fby v) whenot sec
v
base
base
base on (sec = T)
base on (sec = F)
base
F
0
F
1
F
3
0
0
0
0
0
0
T
4
1
4
F
6
4
4
T
9
2
4
···
···
···
···
···
4 / 47
Static Clocking
node avgvelocity(delta: int; sec: bool) returns (v: int)
var r, t: int;
let
r = count(0, delta, false);
t = count((1, 1, false) when sec);
v = merge sec ((r when sec) / t) ((0 fby v) whenot sec);
tel
val avgvelocity(delta : int :: .; sec : bool :: .) returns (v : int :: .)
sec
r
t
(0 fby v) whenot sec
v
base
base
base on (sec = T)
base on (sec = F)
base
C ` e :: ck
F
0
F
1
F
3
0
0
0
0
0
0
T
4
1
4
F
6
4
4
T
9
2
4
···
···
···
···
···
C ` x :: ck
C ` e when x :: ck on (x = T )
C ` x :: ck
C ` et :: ck on (x = T )
C ` ef :: ck on (x = F )
C ` merge x et ef :: ck
4 / 47
Static Clocking
node avgvelocity(delta: int; sec: bool) returns (v: int)
var r, t: int;
let
r = count(0, delta, false);
t = count((1, 1, false) when sec);
v = merge sec ((r when sec) / t) ((0 fby v) whenot sec);
tel
val avgvelocity(delta : int :: .; sec : bool :: .) returns (v : int :: .)
sec
r
t
(0 fby v) whenot sec
v
base
base
base on (sec = T)
base on (sec = F)
base
F
0
F
1
F
3
0
0
0
0
0
0
T
4
1
4
F
6
4
4
T
9
2
4
···
···
···
···
···
• Static inference/verification of clocking.
• “Clocks in the source language are transformed into control structures
Biernacki et al. (2008): “Clock-directed modular code in the target language.”
generation for synchronous data-flow languages”
4 / 47
Sampling and merging: what for?
• Provide a means of conditional activation.
• Programming directly with them can be tricky.
• Serve as a target for more complicated structures.
node main (go : bool)
returns (x : int)
var last_x : int;
let
last_x = 0 fby x;
automaton
state Up
do x = last_x + 1
until x >= 5 then Down
state Down
do x = last_x − 1
until x <= 0 then Up
end;
tel
x = last_x + 1
x <= 0
x >= 5
x = last_x - 1
5 / 47
Sampling and merging: what for?
• Provide a means of conditional activation.
• Programming directly with them can be tricky.
• Serve as a target for more complicated structures.
node main (go : bool)
returns (x : int)
var last_x : int;
let
last_x = 0 fby x;
automaton
state Up
do x = last_x + 1
until x >= 5 then Down
state Down
do x = last_x − 1
until x <= 0 then Up
end;
tel
type st = St_Up | St_Down
(∗ ... ∗)
last_x = 0 fby x
x_St_Down = (last_x when St_Down(ck)) − 1
x_St_Up = (last_x when St_Up(ck)) + 1
x = merge ck (St_Down: x_St_Down)
(St_Up: x_St_Up);
ck = St_Up fby ns
ns = ...
Colaço, Pagano, and Pouzet (2005): “A Conservative Extension of Synchronous Data-flow with State Machines”
5 / 47
Verifying Lustre compilation in Coq
Synchronous
Block
?????
Diagrams
Mini Lustre This Talk
Simple
Imperative
Language
TODO
Clight
CompCert
executable
binary
6 / 47
Verifying Lustre compilation in Coq
Synchronous
Block
?????
Diagrams
LS
velus
Mini Lustre This Talk
ck
LS
velus
Simple
Imperative
Language
LSN
Clight
TODO
sch
velus
LSN
Rustre
CompCert
executable
binary
Minimp
optimizations
The Velus project (2008–2010, 2010–2013)
• Pouzet, Hamon, Auger, and others.
Auger (2013): “Compilation certifiée de SCADE/LUSTRE”
• Much formalized in Coq, some pen-and-paper proofs.
• Succession of source-to-source passes.
• Normalization in OCaml with validation in Coq.
• Scheduling in OCaml with validation in Coq.
• Streams modelled as lists.
6 / 47
Verifying Lustre compilation in Coq
Synchronous
Block
?????
Diagrams
LS
velus
Mini Lustre This Talk
ck
LS
velus
Simple
Imperative
Language
LSN
velus
Clight
TODO
sch
LSN
Rustre
CompCert
executable
binary
Minimp
optimizations
The Rustre project (2015–)
• A bit less: tupling
%, modular resets %, n-ary merge %
• A bit different: streams as functions from nats to values.
• Complete:
• Translation to imperative code and optimisation.
• Generation of Clight code.
• Ongoing:
• Refine semantic model / proof of existence.
• Verification of Lustre programs. . .
6 / 47
Verifying Lustre compilation in Coq
Synchronous
Block
?????
Diagrams
LS
velus
Mini Lustre This Talk
ck
LS
velus
Simple
Imperative
Language
LSN
velus
Clight
TODO
sch
LSN
Rustre
executable
binary
CompCert
Minimp
optimizations
Integration with CompCert
Blazy, Dargaye, and Leroy (2006): “Formal Leroy (2009):
Verification of a C Compiler Front-End”
“Formal verification of a realistic compiler”
Bedin França et al. (2011): “Towards Formally Verified
Optimizing Compilation in
Flight Control Software”
• Incorporate types and operations from CompCert into Lustre.
• Generate Clight from Obc and show correctness.
• Part of ITEA 3 14014 ASSUME Project.
• MPRI Summer internship of Lélio Brun, now PhD in PARKAS group.
• What about external functions? external nodes? external types?
6 / 47
(2013): “Compilation certifiée de
Normalization [Auger
]
SCADE/LUSTRE”
node avgvelocity(delta: int; sec: bool)
returns (v: int)
var r, t: int;
normalize
let
r = count(0, delta, false);
t = count((1, 1, false) when sec);
v = merge sec ((r when sec) / t)
((0 fby v) whenot sec);
tel
node avgvelocity(delta: int; sec: bool)
returns (v: int)
var r, t, w: int;
let
w = 0 fby v;
r = count(0, delta, false);
t = count((1, 1, false) when sec);
v = merge sec ((r when sec) / t)
(w whenot sec);
tel
• Rewrite to give each fby and node instantiation its own equation.
• Group merges at tops of equations.
• Introduce fresh variables; exploit referential transparency.
• Proof by validation
• External program produces w = e; eqs2.
• Coq validator checks that substituting w = e into eqs2 gives eqs.
7 / 47
(2013): “Compilation certifiée de
Scheduling [Auger
]
SCADE/LUSTRE”
node avgvelocity(delta: int; sec: bool)
returns (v: int)
var r, t, w: int;
schedule
let
w = 0 fby v;
r = count(0, delta, false);
t = count((1, 1, false) when sec);
v = merge sec ((r when sec) / t)
(w whenot sec);
tel
node avgvelocity(delta: int; sec: bool)
returns (v: int)
var r, t, w: int;
let
r = count(0, delta, false);
t = count((1, 1, false) when sec);
v = merge sec ((r when sec) / t)
(w whenot sec);
w = 0 fby v;
tel
• Equations semantics are independent of order;
but correct compilation will depend on order.
• Rewrite to define most variables before they are read,
. . . and fby variables after they are read; optimize adjacencies.
• Proof by validation
• External program generates a sequence of permutations.
• Verified Coq function applies them successively.
8 / 47
Outline
A simple program in Lustre
Overview of Verifying Lustre compilation in Coq
Dataflow language: syntax and semantics
Imperative language: syntax and semantics
Relating the Dataflow and Imperative models
Optimization of control structures
Compiling Obc into Clight
9 / 47
SN-Lustre: dataflow language
Expressions
e ::=
|
|
|
|
ce ::=
|
|
x
k
e
e⊕e
e when (x = k)
variable
constant
unary operator
binary operator
sub-sampling
merge x ce t ce f
if e then ce t else ce f
binary merge
conditional
non-control expression
e
Equations
eq ::=
|
|
(Scheduled) Nodes
ck
x = (ce)
x = (k0 fby e)ck
x = (f e)ck
node f (x : τ ) returns (x : τ )
var x : τ, . . . , x : τ
let eq; · · · ; eq tel
Clocks
ck ::= base | ck on (x = k)
10 / 47
Inductive clock : Set :=
| Cbase : clock
| Con : clock → ident → bool → clock.
Inductive
| Econst :
| Evar :
| Ewhen :
| Eunop :
| Ebinop :
lexp : Type :=
const → lexp
ident → type → lexp
lexp → ident → bool → lexp
unop → lexp → type → lexp
binop → lexp → lexp → type → lexp.
Inductive
| Emerge :
| Eite :
| Eexp :
cexp : Type :=
ident → cexp → cexp → cexp
lexp → cexp → cexp → cexp
lexp → cexp.
Inductive equation : Type :=
| EqDef : ident → clock → cexp → equation
| EqApp : idents → clock → ident → lexps → equation
| EqFby : ident → clock → const → lexp → equation.
11 / 47
Semantics: Dataflow models
n
ck
1
F
2
F
x = n when ck
y = 0 fby x
z = 9 whenot ck
w = merge ck y z
9
9
9
9
3
T
3
0
0
4
F
9
9
5
T
5
3
3
6
T
6
5
5
7
F
9
9
···
···
···
···
···
···
base
base
base on (ck = T)
base on (ck = T)
base on (ck = F)
base
• Model absence
Inductive value := absent | present (v : const).
[Boulmé and Hamon (2001): A clocked denotational semantics for Lucid-Synchrone in Coq ]
[Paulin-Mohring (2009): “A constructive denotational semantics for Kahn networks in Coq” ]
• Lists: 1 :: (2 :: (3 :: (4 :: [])))
or
((( · 1) · 2) · 3) · 4
• Coinductive streams?
• Functions from natural numbers to values
Notation stream A := (nat → A).
12 / 47
SN-Lustre: semantic model
Definition global := list node.
Definition history := Coq.FSets.FMapPositive.PositiveMap.t (stream value).
Inductive sem_equation (G: global)
: history → equation → Prop :=
| ( SEqDef: )
sem_var H x xs →
sem_cexp H ce xs →
sem_equation G H (EqDef x ck ce)
x = (ce)ck
| ( SEqApp: )
sem_lexp H le ls →
sem_var H x xs →
sem_node G f ls xs →
sem_equation G H (EqApp x ck f le)
x = (f le)ck
with sem_node (G: global)
: ident
→ stream value
→ stream value
→ Prop :=
| (SNode: )
find_node f G
= Some (mk_node f i o eqs) →
(∃ H, sem_var H i xs
∧ sem_var H o ys
∧ ...
∧ Forall (sem_equation G H) eqs)
→ sem_node G f xs ys.
| ( SEqFby: )
sem_lexp H le ls →
sem_var H x xs →
x = (v0 fby le)ck
xs = fby v0 ls →
sem_equation G H (EqFby x ck v0 le)
13 / 47
SN-Lustre: semantic model
Definition global := list node.
Definition history := Coq.FSets.FMapPositive.PositiveMap.t (stream value).
Inductive sem_equation (G: global)
: history → equation → Prop :=
| ( SEqDef: )
sem_var H x xs →
sem_cexp H ce xs →
sem_equation G H (EqDef x ck ce)
x = (ce)ck
| ( SEqApp: )
sem_lexp H le ls →
sem_var H x xs →
sem_node G f ls xs →
sem_equation G H (EqApp x ck f le)
x = (f le)ck
f : stream(T ) → stream(T 0 )
with sem_node (G: global)
: ident
→ stream value
→ stream value
→ Prop :=
| (SNode: )
find_node f G
= Some (mk_node f i o eqs) →
(∃ H, sem_var H i xs
∧ sem_var H o ys
∧ ...
∧ Forall (sem_equation G H) eqs)
→ sem_node G f xs ys.
| ( SEqFby: )
sem_lexp H le ls →
sem_var H x xs →
x = (v0 fby le)ck
xs = fby v0 ls →
sem_equation G H (EqFby x ck v0 le)
13 / 47
SN-Lustre: semantic model: fby
#
fby#
v0 (v .s) = v0 .fbyv (s)
#
fby#
v0 (abs.s) = abs.fbyv0 (s)
fby#
v0 () = Fixpoint hold (v0: const) (xs: stream value) (n: nat) : const :=
match n with
| 0 ⇒ v0
| S m ⇒ match xs m with
| absent ⇒ hold v0 xs m
| present hv ⇒ hv
end
end.
Definition fby (v0: const) (xs: stream value) (n: nat) : value :=
match xs n with
| absent ⇒ absent
| _ ⇒ present (hold v0 xs n)
end.
14 / 47
Outline
A simple program in Lustre
Overview of Verifying Lustre compilation in Coq
Dataflow language: syntax and semantics
Imperative language: syntax and semantics
Relating the Dataflow and Imperative models
Optimization of control structures
Compiling Obc into Clight
15 / 47
Simple Imperative Language: Obc
c ::=
|
|
|
|
x ty
state(x)ty
k
ty e
e ⊕ty e
variable
memory
constant
unary operator
binary operator
s ::=
|
|
|
|
|
x := c
state(x) := c
if c then {s} else {s}
s; s
x, . . . , x := cl . m o (x, . . . , x)
skip
variable assignment
memory assignment
conditional branching
sequential composition
class method call
nop
Big-step semantics: me, ve `prog s ⇓ me 0 , ve 0
me : · · ·
ve : ident → option val
16 / 47
Obc: methods and classes
Record method : Type :=
mk_method {
m_name : ident;
m_in
: list (ident ∗ type);
m_vars : list (ident ∗ type);
m_out : list (ident ∗ type);
m_body : stmt;
}.
m_nodupvars : NoDupMembers (m_in ++ m_vars ++ m_out);
m_good
: Forall NotReserved (m_in ++ m_vars ++ m_out)
Record class : Type :=
mk_class {
c_name
: ident;
c_mems
: list (ident ∗ type);
c_objs
: list (ident ∗ ident);
c_methods : list method;
}.
c_nodups
c_nodupm
(* (instance, class) *)
: NoDup (map fst c_mems ++ map fst c_objs)
: NoDup (map m_name c_methods)
Definition program : Type := list class.
• Structural invariants included in dependent records (as in CompCert).
• Typing predicate not shown:
• Memories used in methods are declared in class.
• Variables used in methods are declared in methods.
• Classes and methods used in methods are declared in programs.
• Variable, memory, and parameter types match.
17 / 47
Obc: semantic model
Inductive stmt_eval :
program → heap → stack → stmt → heap ∗ stack → Prop :=
| Iassign:
exp_eval menv env e v →
PM.add x v env = env' →
stmt_eval prog menv env (Assign x e) (menv, env')
x := e
| Iassignst:
exp_eval menv env e v →
madd_mem x v menv = menv' →
stmt_eval prog menv env (AssignSt x e) (menv', env)
state(x) := e
| Istep:
exp_eval menv env e v →
mfind_inst o menv = Some(omenv) →
stmt_step_eval prog omenv fcls v omenv' rv →
madd_obj o omenv' menv = menv' →
PM.add x rv env = env' →
stmt_eval prog menv env (Step_ap x fcls o e) (menv', env')
..
.
x := fcls.step o (e)
18 / 47
Obc: semantic model
Inductive stmt_eval :
program → heap → stack → stmt → heap ∗ stack → Prop :=
| Iassign:
exp_eval menv env e v →
PM.add x v env = env' →
stmt_eval prog menv env (Assign x e) (menv, env')
x := e
| Iassignst:
exp_eval menv env e v →
madd_mem x v menv = menv' →
stmt_eval prog menv env (AssignSt x e) (menv', env)
state(x) := e
S × T → T0 × S
| Istep:
exp_eval menv env e v →
mfind_inst o menv = Some(omenv) →
stmt_step_eval prog omenv fcls v omenv' rv →
madd_obj o omenv' menv = menv' →
PM.add x rv env = env' →
stmt_eval prog menv env (Step_ap x fcls o e) (menv', env')
..
.
(ft , s0 )
S
x := fcls.step o (e)
18 / 47
et al. (2008): “Clock-directed modular code
]
Generation of imperative code [Biernacki
generation for synchronous data-flow languages”
class avgvelocity {
int w;
node avgvelocity(delta: int; sec: bool) instance count o1, o2;
returns (v: int)
var r, t, w: int;
step(int delta, bool sec) returns (v: int) {
let
int r, t;
r = count(0, delta, false);
t = count((1, 1, false) when sec);
r := count.step o1 (0, delta, false);
v = merge sec ((r when sec) / t)
if sec { t := count.step o2 (1, 1, false) };
(w whenot sec);
if sec { v := r / t }
w = 0 fby v;
else { v := mem(w) };
tel
mem(w) := v
}
}
reset() returns () {
count.reset o1;
count.reset o2;
mem(w) := 0
}
19 / 47
et al. (2008): “Clock-directed modular code
]
Generation of imperative code [Biernacki
generation for synchronous data-flow languages”
node avgvelocity(delta: int; sec: bool) class avgvelocity {
returns (v: int)
int w;
var r, t, w: int;
instance count o1, o2;
let
r = count(0, delta, false);
step(int delta, bool sec) returns (v: int) {
t = count((1, 1, false) when sec);
int r, t;
v = merge sec ((r when sec) / t)
(w whenot sec);
r := count.step o1 (0, delta, false);
w = 0 fby v;
if sec { t := count.step o2 (1, 1, false) };
tel
if sec { v := r / t }
me
else { v := mem(w) };
mem(w) := v
}
o1
state(w)
o2
reset() returns () {
count.reset o1;
count.reset o2;
state(i) state(v) state(i) state(v) mem(w) := 0
}
}
19 / 47
Outline
A simple program in Lustre
Overview of Verifying Lustre compilation in Coq
Dataflow language: syntax and semantics
Imperative language: syntax and semantics
Relating the Dataflow and Imperative models
Optimization of control structures
Compiling Obc into Clight
20 / 47
Is well scheduled
Variables (mems : PS.t) (arg: Nelist.nelist ident).
Inductive Is_well_sch : list equation → Prop :=
| WSchNil: Is_well_sch []
| WSchEq:
Is_well_sch eqs →
(∀ i, Is_free_in_eq i eq →
(¬PS.In i mems → Is_variable_in i eqs
∨ Nelist.In i arg)
∧ (PS.In i mems → ¬Is_defined_in i eqs)) →
(∀ i, Is_defined_in_eq i eq → ¬Is_defined_in i eqs) →
Is_well_sch (eq :: eqs).
| ...
alleqs
eq
[· · · ; w = v0 fby e ; · · · ]
eqs
++ (x = e :: [· · · ; y = e; · · · ]) input
21 / 47
Is well scheduled
Variables (mems : PS.t) (arg: Nelist.nelist ident).
Inductive Is_well_sch : list equation → Prop :=
| WSchNil: Is_well_sch []
| WSchEq:
Is_well_sch eqs →
(∀ i, Is_free_in_eq i eq →
(¬PS.In i mems → Is_variable_in i eqs
∨ Nelist.In i arg)
∧ (PS.In i mems → ¬Is_defined_in i eqs)) →
(∀ i, Is_defined_in_eq i eq → ¬Is_defined_in i eqs) →
Is_well_sch (eq :: eqs).
| ...
alleqs
eq
[· · · ; w = v0 fby e ; · · · ]
eqs
++ (x = e :: [· · · ; y = e; · · · ]) input
21 / 47
Is well scheduled
Variables (mems : PS.t) (arg: Nelist.nelist ident).
Inductive Is_well_sch : list equation → Prop :=
| WSchNil: Is_well_sch []
| WSchEq:
Is_well_sch eqs →
(∀ i, Is_free_in_eq i eq →
(¬PS.In i mems → Is_variable_in i eqs
∨ Nelist.In i arg)
∧ (PS.In i mems → ¬Is_defined_in i eqs)) →
(∀ i, Is_defined_in_eq i eq → ¬Is_defined_in i eqs) →
Is_well_sch (eq :: eqs).
| ...
alleqs
eq
[· · · ; w = v0 fby e ; · · · ]
eqs
++ (x = e :: [· · · ; y = e; · · · ]) input
21 / 47
Translation: definition
Variable mems : PS.t.
Definition tovar (x: ident) : exp := if PS.mem x mems then State x else Var x.
Fixpoint Control (ck: clock) (s: stmt) : stmt :=
match ck with
| Cbase ⇒ s
| Con ck x true ⇒ Control ck (Ifte (tovar x) s Skip)
| Con ck x false ⇒ Control ck (Ifte (tovar x) Skip s)
end.
Fixpoint translate_cexp (x: ident) (e : cexp) {struct e} : stmt :=
match e with
| Emerge y t f ⇒ Ifte (tovar y) (translate_cexp x t) (translate_cexp x f)
| Eexp l ⇒ Assign x (translate_lexp l)
end.
Definition translate_eqn (eqn: equation) : stmt :=
match eqn with
| EqDef x (CAexp ck ce) ⇒ Control ck (translate_cexp x ce)
| EqApp x f (LAexp ck le) ⇒ Control ck (Step_ap x f x (translate_lexp le))
| EqFby x v (LAexp ck le) ⇒ Control ck (AssignSt x (translate_lexp le))
end.
22 / 47
Translation: definition
Variable mems : PS.t.
Definition tovar (x: ident) : exp := if PS.mem x mems then State x else Var x.
Fixpoint Control (ck: clock) (s: stmt) : stmt :=
match ck with
| Cbase ⇒ s
| Con ck x true ⇒ Control ck (Ifte (tovar x) s Skip)
| Con ck x false ⇒ Control ck (Ifte (tovar x) Skip s)
end.
Fixpoint translate_cexp (x: ident)(e : cexp) {struct e} : stmt := ...
Definition translate_eqn (eqn: equation) : stmt :=
match eqn with
| EqDef x (CAexp ck ce) ⇒ Control ck (translate_cexp x ce)
| EqApp x f (LAexp ck le) ⇒ Control ck (Step_ap x f x (translate_lexp le))
| EqFby x v (LAexp ck le) ⇒ Control ck (AssignSt x (translate_lexp le))
end.
Definition translate_eqns (eqns: list equation): stmt :=
List.fold_left (fun i eq ⇒ Comp (translate_eqn eq) i) eqns Skip.
22 / 47
Correctness theorem
Variables (G
: global)
(Hwdef : Welldef_global G).
Theorem is_event_loop_correct:
co 0 · co 1 · co 2 · · · = f (xss 0
sem_node G main xss ys →
∀ n, ∃ menv env,
step (S n) (translate G) r main obj css menv env
∧ (∀ co, ys n = present co ↔ PM.find r env = Some co).
· xss 1 · xss 2 · · · )
clock-directed translation
Fixpoint step (n: nat) P r main obj css menv' env': Prop :=
match n with
| 0 ⇒ stmt_eval P hempty sempty (Reset_ap main obj) (menv', env')
| S n ⇒ let xss := Nelist.map Const (css n) in
∃ menvN envN,
step n P r main obj css menv env
∧ stmt_eval P menv env (Step_ap r main obj xss) (menv', env')
end.
f.reset obj;
repeat (n + 1) {
r := f.step obj (css n)
}
23 / 47
Induction on n: (internal) memories
∗
sem_node G f xs ys
stmt_eval (translate G) menv env (r := f.step obj (ci)) (menv’, env’)
24 / 47
M
Induction on n: (internal) memories
c0 · c1 · c2 · · · M1
..
.
M2
..
.
..
.
∃M, msem_node G f xs M ys
∗
sem_node G f xs ys
stmt_eval (translate G) menv env (r := f.step obj (ci)) (menv’, env’)
24 / 47
M
Induction on n: (internal) memories
Inductive memory (A: Type): Type := mk_memory {
mm_values : PM.t A;
c0
mm_instances : PM.t (memory A)
}.
· c1 · c2 · · · M1
Definition memory := memory (stream const).
..
.
M2
..
.
..
.
∃M, msem_node G f xs M ys
∗
sem_node G f xs ys
stmt_eval (translate G) menv env (r := f.step obj (ci)) (menv’, env’)
24 / 47
Inductive sem_equation (G: global)
: history → equation → Prop :=
| (SEqDef: )
sem_var H x xs →
sem_caexp H cae xs →
sem_equation G H (EqDef x cae)
...
x = (ce)ck
| (SEqFby: )
sem_laexp H lae ls →
sem_var H x xs →
xs = fby v0 ls →
sem_equation G H (EqFby x v0 lae)
x = (v0 fby le)ck
25 / 47
Inductive sem_equation (G: global)
: history → equation → Prop :=
Inductive msem_equation G
: history → memory → equation
→ Prop :=
| (SEqDef: )
| SEqDef:
sem_var H x xs →
sem_var H x xs →
sem_caexp H cae xs →
sem_caexp H cae xs →
sem_equation G H (EqDef x cae)
msem_equation G H M (EqDef x cae)
...
x = (ce)ck
...
| SEqFby:
mfind_mem x M = Some ms →
| (SEqFby: )
ms 0 = v0 →
sem_laexp H lae ls →
sem_laexp H lae ls →
sem_var H x xs →
sem_var H x xS →
xs = fby v0 ls →
(∀ n,
sem_equation G H (EqFby x v0 lae)
match ls n with
| absent ⇒ ms (S n) = ms n
x = (v0 fby le)ck
∧ xs n = absent
| present v ⇒ ms (S n) = v
∧ xs n = present (ms n)
end) →
msem_equation G H M (EqFby x v0 lae)
25 / 47
Inductive sem_equation (G: global)
0 Prop
1 :=
2
: historyn→ equation →
ck
F F T
| xSEqDef:
= n when (ck = T)
3
sem_var
x xsx →
y = 0 Hfby
0
sem_caexp H cae xs →
0 x 0cae)0
yM = 0 mby xG H (EqDef
sem_equation
...
| SEqFby:
sem_laexp H lae ls →
sem_var H x xs →
xs = fby v0 ls →
sem_equation G H (EqFby x v0 lae)
Inductive msem_equation G
3 : 4history
5 →
6 memory
· · · base
→ equation
F T→ Prop
T :=
F · · · base
| SEqDef:
5 6
· · · base on (ck = T)
3sem_var
5 H x xs
· ·→
· base on (ck = T)
sem_caexp H cae xs base
→
3 3msem_equation
5 6 · · G· H M (EqDef x cae)
...
| SEqFby:
mfind_mem x M = Some ms →
ms 0 = v0 →
sem_laexp H lae ls →
sem_var H x xS →
(∀ n,
match ls n with
| absent ⇒ ms (S n) = ms n
∧ xs n = absent
| present v ⇒ ms (S n) = v
∧ xs n = present (ms n)
end) →
msem_equation G H M (EqFby x v0 lae)
25 / 47
Inductive sem_equation (G: global)
0 Prop
1 :=
2
: historyn→ equation →
ck
F F T
| xSEqDef:
= n when (ck = T)
3
sem_var
x xsx →
y = 0 Hfby
0
sem_caexp H cae xs →
0 x 0cae)0
yM = 0 mby xG H (EqDef
sem_equation
...
| SEqFby:
sem_laexp H lae ls →
sem_var H x xs →
xs = fby v0 ls →
sem_equation G H (EqFby x v0 lae)
Inductive msem_equation G
3 : 4history
5 →
6 memory
· · · base
→ equation
F T→ Prop
T :=
F · · · base
| SEqDef:
5 6
· · · base on (ck = T)
3sem_var
5 H x xs
· ·→
· base on (ck = T)
sem_caexp H cae xs base
→
3 3msem_equation
5 6 · · G· H M (EqDef x cae)
...
| SEqFby:
mfind_mem x M = Some ms →
ms 0 = v0 →
sem_laexp H lae ls →
sem_var H x xS →
(∀ n,
match ls n with
| absent ⇒ ms (S n) = ms n
∧ xs n = absent
| present v ⇒ ms (S n) = v
∧ xs n = present (ms n)
end) →
msem_equation G H M (EqFby x v0 lae)
25 / 47
Inductive sem_equation (G: global)
0 Prop
1 :=
2
: historyn→ equation →
ck
F F T
| xSEqDef:
= n when (ck = T)
3
sem_var
x xsx →
y = 0 Hfby
0
sem_caexp H cae xs →
0 x 0cae)0
yM = 0 mby xG H (EqDef
sem_equation
...
| SEqFby:
sem_laexp H lae ls →
sem_var H x xs →
xs = fby v0 ls →
sem_equation G H (EqFby x v0 lae)
Inductive msem_equation G
3 : 4history
5 →
6 memory
· · · base
→ equation
F T→ Prop
T :=
F · · · base
| SEqDef:
5 6
· · · base on (ck = T)
3sem_var
5 H x xs
· ·→
· base on (ck = T)
sem_caexp H cae xs base
→
3 3msem_equation
5 6 · · G· H M (EqDef x cae)
...
| SEqFby:
mfind_mem x M = Some ms →
ms 0 = v0 →
sem_laexp H lae ls →
sem_var H x xS →
(∀ n,
match ls n with
| absent ⇒ ms (S n) = ms n
∧ xs n = absent
| present v ⇒ ms (S n) = v
∧ xs n = present (ms n)
end) →
msem_equation G H M (EqFby x v0 lae)
25 / 47
Inductive sem_equation (G: global)
0 Prop
1 :=
2
: historyn→ equation →
ck
F F T
| xSEqDef:
= n when (ck = T)
3
sem_var
x xsx →
y = 0 Hfby
0
sem_caexp H cae xs →
0 x 0cae)0
yM = 0 mby xG H (EqDef
sem_equation
...
| SEqFby:
sem_laexp H lae ls →
sem_var H x xs →
xs = fby v0 ls →
sem_equation G H (EqFby x v0 lae)
Inductive msem_equation G
3 : 4history
5 →
6 memory
· · · base
→ equation
F T→ Prop
T :=
F · · · base
| SEqDef:
5 6
· · · base on (ck = T)
3sem_var
5 H x xs
· ·→
· base on (ck = T)
sem_caexp H cae xs base
→
3 3msem_equation
5 6 · · G· H M (EqDef x cae)
...
| SEqFby:
mfind_mem x M = Some ms →
ms 0 = v0 →
sem_laexp H lae ls →
sem_var H x xS →
(∀ n,
match ls n with
| absent ⇒ ms (S n) = ms n
∧ xs n = absent
| present v ⇒ ms (S n) = v
∧ xs n = present (ms n)
end) →
msem_equation G H M (EqFby x v0 lae)
25 / 47
Inductive sem_equation (G: global)
0 Prop
1 :=
2
: historyn→ equation →
ck
F F T
| xSEqDef:
= n when (ck = T)
3
sem_var
x xsx →
y = 0 Hfby
0
sem_caexp H cae xs →
0 x 0cae)0
yM = 0 mby xG H (EqDef
sem_equation
...
| SEqFby:
sem_laexp H lae ls →
sem_var H x xs →
xs = fby v0 ls →
sem_equation G H (EqFby x v0 lae)
Inductive msem_equation G
3 : 4history
5 →
6 memory
· · · base
→ equation
F T→ Prop
T :=
F · · · base
| SEqDef:
5 6
· · · base on (ck = T)
3sem_var
5 H x xs
· ·→
· base on (ck = T)
sem_caexp H cae xs base
→
3 3msem_equation
5 6 · · G· H M (EqDef x cae)
...
| SEqFby:
mfind_mem x M = Some ms →
ms 0 = v0 →
sem_laexp H lae ls →
sem_var H x xS →
(∀ n,
match ls n with
| absent ⇒ ms (S n) = ms n
∧ xs n = absent
| present v ⇒ ms (S n) = v
∧ xs n = present (ms n)
end) →
msem_equation G H M (EqFby x v0 lae)
25 / 47
Inductive sem_equation (G: global)
0 Prop
1 :=
2
: historyn→ equation →
ck
F F T
| xSEqDef:
= n when (ck = T)
3
sem_var
x xsx →
y = 0 Hfby
0
sem_caexp H cae xs →
0 x 0cae)0
yM = 0 mby xG H (EqDef
sem_equation
...
| SEqFby:
sem_laexp H lae ls →
sem_var H x xs →
xs = fby v0 ls →
sem_equation G H (EqFby x v0 lae)
Inductive msem_equation G
3 : 4history
5 →
6 memory
· · · base
→ equation
F T→ Prop
T :=
F · · · base
| SEqDef:
5 6
· · · base on (ck = T)
3sem_var
5 H x xs
· ·→
· base on (ck = T)
sem_caexp H cae xs base
→
3 3msem_equation
5 6 · · G· H M (EqDef x cae)
...
| SEqFby:
mfind_mem x M = Some ms →
ms 0 = v0 →
sem_laexp H lae ls →
sem_var H x xS →
(∀ n,
match ls n with
| absent ⇒ ms (S n) = ms n
∧ xs n = absent
| present v ⇒ ms (S n) = v
∧ xs n = present (ms n)
end) →
msem_equation G H M (EqFby x v0 lae)
25 / 47
Inductive sem_equation (G: global)
0 Prop
1 :=
2
: historyn→ equation →
ck
F F T
| xSEqDef:
= n when (ck = T)
3
sem_var
x xsx →
y = 0 Hfby
0
sem_caexp H cae xs →
0 x 0cae)0
yM = 0 mby xG H (EqDef
sem_equation
...
| SEqFby:
sem_laexp H lae ls →
sem_var H x xs →
xs = fby v0 ls →
sem_equation G H (EqFby x v0 lae)
Inductive msem_equation G
3 : 4history
5 →
6 memory
· · · base
→ equation
F T→ Prop
T :=
F · · · base
| SEqDef:
5 6
· · · base on (ck = T)
3sem_var
5 H x xs
· ·→
· base on (ck = T)
sem_caexp H cae xs base
→
3 3msem_equation
5 6 · · G· H M (EqDef x cae)
...
| SEqFby:
mfind_mem x M = Some ms →
ms 0 = v0 →
sem_laexp H lae ls →
sem_var H x xS →
(∀ n,
match ls n with
| absent ⇒ ms (S n) = ms n
∧ xs n = absent
| present v ⇒ ms (S n) = v
∧ xs n = present (ms n)
end) →
msem_equation G H M (EqFby x v0 lae)
25 / 47
Memory Correspondence
menv
M
c0 · c1 · c2 · · · M1
..
.
state(x3 ) o2 : f1
M2
..
.
..
.
..
.
o4 : f1
..
.
..
.
Inductive Memory_Corres (G: global) (n: nat) :
ident → memory → heap → Prop :=
| MemC:
find_node f G = Some(mk_node f i o eqs) →
Forall (Memory_Corres_eq G n M menv) eqs →
Memory_Corres G n f M menv
26 / 47
Memory Correspondence
M
Mem ory
c0 · c1 · c2 · · · M1
..
.
C o r r e s G n M m env
state(x3 ) o2 : f1
M2
..
.
menv
..
.
..
.
o4 : f1
..
.
..
.
Inductive Memory_Corres (G: global) (n: nat) :
ident → memory → heap → Prop :=
| MemC:
find_node f G = Some(mk_node f i o eqs) →
Forall (Memory_Corres_eq G n M menv) eqs →
Memory_Corres G n f M menv
26 / 47
Memory Correspondence
M
Mem ory
menv
i n s t a nt n
c0 · c1 · c2 · · · M1
..
.
C o r r e s G n M m env
state(x3 ) o2 : f1
M2
..
.
..
.
..
.
o4 : f1
..
.
..
.
Inductive Memory_Corres_eq (G: global) (n: nat) :
memory → heap → equation → Prop :=
...
| MemC_EqFby:
(∀ ms, mfind_mem x M = Some ms
→ mfind_mem x menv = Some (ms n))
→ Memory_Corres_eq G n M menv (EqFby x v0 lae).
26 / 47
Memory Correspondence
M
Mem ory
menv
i n s t a nt n
c0 · c1 · c2 · · · M1
..
.
C o r r e s G n M m env
state(x3 ) o2 : f1
M2
..
.
..
.
..
.
o4 : f1
..
.
Inductive Memory_Corres_eq (G: global) (n: nat) :
memory → heap → equation → Prop :=
...
| MemC_EqApp:
(∀ Mo, mfind_inst x M = Some Mo →
(∃ omenv, mfind_inst x menv = Some omenv
∧ Memory_Corres G n f Mo omenv))
→ Memory_Corres_eq G n M menv (EqApp x f lae)
..
.
26 / 47
Memory Correspondence
M
Mem ory
menv
i n s t a nt n
c0 · c1 · c2 · · · M1
..
.
C o r r e s G n M m env
state(x3 ) o2 : f1
M2
..
.
..
.
..
.
o4 : f1
..
.
Inductive Memory_Corres_eq (G: global) (n: nat) :
memory → heap → equation → Prop :=
...
| MemC_EqApp:
(∀ Mo, mfind_inst x M = Some Mo →
(∃ omenv, mfind_inst x menv = Some omenv
∧ Memory_Corres G n f Mo omenv))
→ Memory_Corres_eq G n M menv (EqApp x f lae)
..
.
26 / 47
Lemma is_step_correct:
Forall (msem_equation G H M) alleqs
(∃ oeqs, alleqs = oeqs ++ eqs)
induction n
induction G
Welldef_global G →
induction eqs
(∀ c, sem_var_instant (restr H n) input (present c)
↔ PM.find input env = Some c) →
¬ Is_defined_in input eqs →
case: x = (ce)ck
case: present
case: absent
Is_well_sch mems input eqs →
case: x = (f e)ck
(* hypothesis for earlier nodes... *)
Forall (Memory_Corres_eq G n M menv) alleqs →
(∃ menv' env',
case:
stmt_eval (translate G) menv env
(translate_eqns mems eqs) (menv', env')
∧ (∀ x, Is_variable_in x eqs →
∀ c, sem_var_instant (restr H n) x (present c)
↔ PM.find x env' = Some c)
∧ Forall (Memory_Corres_eq G (S n) M menv') eqs).
case: present
case: absent
x = (k fby e)ck
case: present
case: absent
27 / 47
Lemma is_step_correct:
Forall (msem_equation G H M) alleqs
(∃ oeqs, alleqs = oeqs ++ eqs)
induction n
induction G
induction eqs
Welldef_global G →
alleqs
(∀ c, sem_var_instant (restr H n) input (present c)
oeqs
eq
↔ PM.find input env = Some c) →
¬ Is_defined_in input eqs →
[· · · ; w = v0 fby e ; · · · ]
case: x = (ce)ck
eqs
case: present
++ (x = e :: [· · · ; y = e; · · · ]) input
Is_well_sch mems input eqs →
case: absent
case: x = (f e)ck
(* hypothesis for earlier nodes... *)
Forall (Memory_Corres_eq G n M menv) alleqs →
(∃ menv' env',
case:
stmt_eval (translate G) menv env
(translate_eqns mems eqs) (menv', env')
∧ (∀ x, Is_variable_in x eqs →
∀ c, sem_var_instant (restr H n) x (present c)
↔ PM.find x env' = Some c)
∧ Forall (Memory_Corres_eq G (S n) M menv') eqs).
case: present
case: absent
x = (k fby e)ck
case: present
case: absent
27 / 47
Outline
A simple program in Lustre
Overview of Verifying Lustre compilation in Coq
Dataflow language: syntax and semantics
Imperative language: syntax and semantics
Relating the Dataflow and Imperative models
Optimization of control structures
Compiling Obc into Clight
28 / 47
et al. (2008): “Clock-directed modular code
]
Fusion of control structures [Biernacki
generation for synchronous data-flow languages”
step(delta: int, sec: bool)
returns (v: int) {
var r, t : int;
}
r := count.step o1 (0, delta, false);
if sec {
t := count.step o2 (1, 1, false)
};
if sec {
v := r / t
} else {
v := mem(w)
};
mem(w) := v
step(delta: int, sec: bool)
returns (v: int) {
var r, t : int;
r := count.step o1 (0, delta, false);
if sec {
t := count.step o2 (1, 1, false);
v := r / t
} else {
v := mem(w)
};
}
mem(w) := v
• Generate control for each equation (simpler to implement and prove).
• Afterward fuse control structures together.
• Effective if scheduler places similarly clocked equations together.
29 / 47
Fusion of control structures: requires invariant
if e {s1} else {s2};
if e {t1} else {t2}
w
if e {s1; t1} else {s2; t2};
30 / 47
Fusion of control structures: requires invariant
if e {s1} else {s2};
if e {t1} else {t2}
w
if e {s1; t1} else {s2; t2};
if x {x := false} else {x := true};
if x {t1} else {t2}
%
30 / 47
Fusion of control structures: requires invariant
if e {s1} else {s2};
if e {t1} else {t2}
w
if e {s1; t1} else {s2; t2};
if x {x := false} else {x := true};
if x {t1} else {t2}
%
fusible(s1 )
fusible(s2 )
∀x ∈ free(e), ¬maywrite x s1 ∧ ¬maywrite x s2
fusible(s1 ) fusible(s2 )
fusible(if e then {s1 } else {s2 })
fusible(s1 ; s2 )
...
30 / 47
Fusion of control structures: implementation
;
!
= fuse0 (s, t)
fuse
s
t
fuse (s) = s
;
;
0
0
fuse s,
= fuse (zip (s, t1 ) , t2 )
t1
t2
;
if e1
0
fuse (s, t) = zip (s, t)
zip
if e
s1
if e
,
s2 t1
=
t2
;
, t
s1
if e
zip (s1 , t1 )
zip (s2 , t2 )
;
!
zip
s1
=
s2
s2
if e2
t1
t2
;
if e3 · · ·
u1 u2
zip (s2 , t)
s1
;
zip (s, t) =
s
t
31 / 47
Fusion of control structures: correctness
eqs
;
translate_eqns
fusible?
;
if e
s1
s2
if e
t1
t2
;
if e 0 · · ·
u1 u2
fuse/fuse’/zip
preserves fusible?
32 / 47
Fusion of control structures: correctness
eqs
;
translate_eqns
fusible?
;
if e
s1
s2
if e
t1
t2
;
if e 0 · · ·
u1 u2
fuse/fuse’/zip
preserves fusible?
base on ck
x = (merge b e1 e2)
if ck {
if b {
x := e1
} else {
x := e2
}
}
• In a well scheduled dataflow program it is
not possible to read x before writing it.
• Compiling x = (ce)ck and x = (f le)ck gives
fusible imperative code.
32 / 47
Fusion of control structures: correctness
eqs
;
translate_eqns
fusible?
;
if e
s1
s2
if e
t1
t2
;
if e 0 · · ·
u1 u2
fuse/fuse’/zip
preserves fusible?
base on ck
x = (0 fby (x + 1))
• But for fby equations, we must read x
if ck {
mem(x) := mem(x) + 1
}
before writing it.
• A different invariant?
Once we write x, we never read it again.
Trickier to express. Trickier to work with.
32 / 47
Fusion of control structures: correctness
eqs
;
translate_eqns
fusible?
;
if e
s1
s2
if e
t1
t2
;
if e 0 · · ·
u1 u2
fuse/fuse’/zip
preserves fusible?
base on x
y = (true when x)
x = (true fby y)base on x
if mem(x) {
y := true
}
if mem(x) {
mem(x) := y
}
• Happily, such programs are not well clocked.
C ` true :: base
C ` x :: base
C ` true when x :: base on (x = T )
C ` x :: base on (x = T )
32 / 47
Fusion of control structures: correctness
eqs
;
translate_eqns
fusible.
;
if e
s1
s2
if e
t1
t2
;
if e 0 · · ·
u1 u2
fuse/fuse’/zip
preserves fusible?
base on x
y = (true when x)
x = (true fby y)base on x
if mem(x) {
y := true
}
if mem(x) {
mem(x) := y
}
• Happily, such programs are not well clocked.
• Show that a variable x is never free in its own
clock in a well clocked program:
C 6` x :: base on · · · on x on · · ·
• Compiling x = (v0 fby le)ck also gives fusible
imperative code.
32 / 47
Fusion of control structures: correctness
eqs
;
translate_eqns
fusible.
;
if e
s1
s2
if e
t1
t2
;
if e 0 · · ·
u1 u2
fuse/fuse’/zip
preserves fusible?
• Define s1 ≈eval s2
Definition stmt_eval_eq s1 s2: Prop :=
∀ prog menv env menv' env',
stmt_eval prog menv env s1 (menv', env')
↔
stmt_eval prog menv env s2 (menv', env').
32 / 47
Fusion of control structures: correctness
eqs
;
translate_eqns
fusible.
;
if e
s1
s2
if e
t1
t2
;
if e 0 · · ·
u1 u2
• Define s1 ≈eval
• Define s1 ≈fuse
fuse/fuse’/zip
preserves fusible.
s2
s2
as
s1 ≈eval s2 ∧ fusible(s1 ) ∧ fusible(s2 )
• Show congruence (‘Proper’ instances) for ;/fuse/fuse’/zip.
• Rewrite until
fusible(s)
fuse (s) ≈eval s
32 / 47
Outline
A simple program in Lustre
Overview of Verifying Lustre compilation in Coq
Dataflow language: syntax and semantics
Imperative language: syntax and semantics
Relating the Dataflow and Imperative models
Optimization of control structures
Compiling Obc into Clight
33 / 47
Compiling Obc into Clight
Integrate Clight into Lustre/Obc
• Introduce an abstract interface for values, types, and operators.
• Define Lustre and Obc syntax and semantics against this interface.
• Likewise for the Lustre to Obc translation and proof.
• Instantiate with definitions for the Obc to Clight translation and proof.
Translate Obc to Clight. . .
34 / 47
Module Type OPERATORS.
Parameter val
: Type.
Parameter type : Type.
Parameter const : Type.
(* Boolean values *)
Parameter true_val : val.
Parameter false_val : val.
Axiom true_not_false_val :
true_val <> false_val.
(* Constants *)
Parameter type_const : const → type.
Parameter sem_const : const → val.
(* Operators *)
Parameter unop : Type.
Parameter binop : Type.
Parameter sem_unop :
unop → val → type → option val.
Parameter sem_binop :
binop → val → type → val → type
→ option val.
(* Typing *)
Parameter bool_type : type.
Parameter type_unop :
unop → type → option type.
Parameter type_binop :
binop → type → type → option type.
(* ... *)
End OPERATORS.
35 / 47
Module Type OPERATORS.
Parameter val
: Type.
Parameter type : Type.
Parameter const : Type.
(* Boolean values *)
Parameter true_val : val.
Parameter false_val : val.
Axiom true_not_false_val :
true_val <> false_val.
(* Constants *)
Parameter type_const : const → type.
Parameter sem_const : const → val.
(* Operators *)
Parameter unop : Type.
Parameter binop : Type.
Parameter sem_unop :
unop → val → type → option val.
Parameter sem_binop :
binop → val → type → val → type
→ option val.
(* Typing *)
Parameter bool_type : type.
Parameter type_unop :
unop → type → option type.
Parameter type_binop :
binop → type → type → option type.
(* ... *)
End OPERATORS.
Module Export Op <: OPERATORS.
Definition val: Type := Values. val.
Inductive type : Type :=
| Tint
: intsize → signedness → type
| Tlong : signedness → type
| Tfloat : floatsize → type.
Inductive
| Cint
| Clong
| Cfloat
| Csingle
const : Type :=
: int → intsize → signedness → const
: int64 → signedness → const
: float → const
: float32 → const.
Definition true_val := Vtrue. (* Vint Int.one *)
Definition false_val := Vfalse. (* Vint Int.zero *)
Lemma true_not_false_val: true_val <> false_val.
Proof. discriminate. Qed.
Definition bool_type : type := Tint IBool Signed.
Inductive unop : Type :=
| UnaryOp: Cop. unary_operation → unop
| CastOp: type → unop.
Definition binop := Cop. binary_operation.
Definition sem_unop (uop: unop) (v: val) (ty: type) : option val
:= match uop with
| UnaryOp op ⇒ sem_unary_operation op v (cltype ty) Mem. empty
| CastOp ty' ⇒ sem_cast v (cltype ty) (cltype ty') Mem. empty
end.
(* ... *)
End Op.
35 / 47
CompCert: values and types
Inductive val: Type :=
| Vundef : val
| Vint
: int → val
| Vlong
: int64 → val
| Vfloat : float → val
| Vsingle : float32 → val
| Vptr
: block → int → val.
Inductive unary_operation : Type :=
| Onotbool : unary_operation
| Onotint
: unary_operation
| Oneg
: unary_operation
| Oabsfloat : unary_operation.
Inductive binary_operation : Type :=
| Oadd : binary_operation
| Osub : binary_operation
| Omul : binary_operation
| Odiv : binary_operation
| Omod : binary_operation
| Oand : binary_operation
| Oor : binary_operation
| Oxor : binary_operation
| Oshl : binary_operation
| Oshr : binary_operation
| Oeq : binary_operation
| One : binary_operation
| Olt : binary_operation
| Ogt : binary_operation
| Ole : binary_operation
| Oge : binary_operation.
Inductive signedness : Type :=
| Signed
: signedness
| Unsigned : signedness.
Inductive
| I8
| I16
| I32
| IBool
intsize : Type :=
: intsize
(* char
: intsize
(* short
: intsize
(* int
: intsize.
(* bool
*)
*)
*)
*)
Inductive floatsize : Type :=
| F32 : floatsize
(* float *)
| F64 : floatsize.
(* double *)
Record attr : Type := mk_attr { attr_volatile: bool;
attr_alignas: option N }.
Inductive type : Type :=
| Tvoid
: type
| Tint
: intsize → signedness → attr → type
| Tlong
: signedness → attr → type
| Tfloat
: floatsize → attr → type
| Tpointer : type → attr → type
| Tarray
: type → Z → attr → type
| Tfunction : typelist → type → calling_convention → type
| Tstruct
: ident → attr → type
| Tunion
: ident → attr → type
(* ... *)
36 / 47
Module Type SYNTAX (Import Ids
: IDS)
(Import Op
: OPERATORS)
(Import OpAux : OPERATORS_AUX Op).
Inductive
| Var
:
| State :
| Const :
| Unop :
| Binop :
exp : Type :=
ident → type → exp
(* variable *)
ident → type → exp
(* state variable *)
const→ exp
(* constant *)
unop → exp → type → exp
(* unary operator *)
binop → exp → exp → type → exp. (* binary operator *)
(* ... *)
End SYNTAX.
Module SyntaxFun (Import Ids
: IDS)
(Import Op
: OPERATORS)
(Import OpAux : OPERATORS_AUX Op) <: SYNTAX Ids Op OpAux.
Include SYNTAX Ids Op OpAux.
End SyntaxFun.
37 / 47
Module Type SYNTAX (Import Ids
: IDS)
(Import Op
: OPERATORS)
(Import OpAux : OPERATORS_AUX Op).
Inductive
| Var
:
| State :
| Const :
| Unop :
| Binop :
exp : Type :=
ident → type → exp
(* variable *)
ident → type → exp
(* state variable *)
const→ exp
(* constant *)
unop → exp → type → exp
(* unary operator *)
binop → exp → exp → type → exp. (* binary operator *)
(* ... *)
End SYNTAX.
Module SyntaxFun (Import Ids
: IDS)
(Import Op
: OPERATORS)
(Import OpAux : OPERATORS_AUX Op) <: SYNTAX Ids Op OpAux.
Include SYNTAX Ids Op OpAux.
End SyntaxFun.
Module Type CORRECTNESS (Import
(Import
(Import
(Import
(Import
(Import
(Import
(Import
(Import
(Import
(* ... *)
Ids
Op
OpAux
DF
MP
Mem
Trans
IsP
MemCor
Fus
:
:
:
:
:
:
:
:
:
:
IDS)
OPERATORS)
OPERATORS_AUX Op)
DATAFLOW Ids Op OpAux)
OBC Ids Op OpAux)
MEMORIES Ids Op DF. Syn)
TRANSLATION Ids Op OpAux DF. Syn MP. Syn Mem)
ISPRESENT Ids Op OpAux DF. Syn MP. Syn MP. Sem Mem Trans)
MEMORYCORRES Ids Op OpAux DF MP)
FUSEIFTE Ids Op OpAux DF. Syn MP. Syn MP. Sem MP. Equ).
37 / 47
Language design: operator types
• (+) : int → int → int
• (+) : double → double → double
38 / 47
Language design: operator types
• (+) : int → int → int
• (+) : double → double → double
• (+) : unsigned char → unsigned char → ?
38 / 47
Language design: operator types
• (+) : int → int → int
• (+) : double → double → double
• (+) : unsigned char → unsigned char → int
38 / 47
Language design: operator types
•
•
•
•
(+) : int → int → int
(+) : double → double → double
(+) : unsigned char → unsigned char → int
(+) : double → unsigned short → double
38 / 47
Language design: operator types
•
•
•
•
(+) : int → int → int
(+) : double → double → double
(+) : unsigned char → unsigned char → int
(+) : double → unsigned short → double
node pita(x: signed char; y: signed char)
returns (z: signed char)
var w : signed char;
let
w = x + y;
z = (w / 2) + x;
tel
38 / 47
Language design: operator types
•
•
•
•
(+) : int → int → int
(+) : double → double → double
(+) : unsigned char → unsigned char → int
(+) : double → unsigned short → double
node pita(x: signed char; y: signed char)
returns (z: signed char)
var w : signed char;
let
w = x + y;
z = (w / 2) + x;
tel
node pita(x: signed char; y: signed char)
returns (z: signed char)
var w : signed char;
let
w = (x + y : signed char);
z = ((w / (2 : signed char)) + x : signed char);
tel
38 / 47
Language design: operator types
•
•
•
•
(+) : int → int → int
(+) : double → double → double
(+) : unsigned char → unsigned char → int
(+) : double → unsigned short → double
node pita(x: signed char; y: signed char)
returns (z: signed char)
var w : signed char;
let
w = x + y;
z = (w / 2) + x;
tel
node pita(x: signed char; y: signed char)
returns (z: signed char)
var w : signed char, t1 : int;
let
w = (x + y : signed char);
t1 = w / (2 : signed char);
z = (t1 + x : signed char);
tel
node pita(x: signed char; y: signed char)
returns (z: signed char)
var w : signed char;
let
w = (x + y : signed char);
z = ((w / (2 : signed char)) + x : signed char);
tel
38 / 47
Language design: operator domains
• Partial operators:
• integer division/modulo x/y , x % y : y = 0 ∨ (x = MIN_INT ∧ y = −1)
• shifts x << y , x >> y : y < 0 ∨ y ≥ 32
• ‘Dynamic’ precondition in the existence proof.
(∀i, sem_binopi <> None)
• Alternative OPERATORS implementation and translation:
x / y becomes if x != MIN_INT && y != 0 then x / y else 0
• (Verification must also consider such details as well as machine
arithmetic.)
39 / 47
Compiling Obc into Clight
Integrating Clight into Lustre/Obc. . .
Translating Obc to Clight
• Define a translation function in Coq.
• Introduce invariants to relate Obc state to Clight state
(using separating predicates).
• Proof by induction on big-step semantics.
40 / 47
CompCert’s Clight frontend
Formal model
context:
genv
ident → {type, function, global}
state:
m
e
le
block → offset → memval × permission
ident → block × type
ident → val
Four semantic models:
(m, e, le, s) → (m0 , e 0 , le 0 , s 0 )
genv , e ` m, le, s ⇓ m0 , le 0
func. params in m
clight1
bigstep clight1
func. params in le
clight2
bigstep clight2
bigstep clight2 =⇒ clight2 =⇒ clight1 and bigstep clight1 =⇒ clight1
• Model of the C ‘abstract machine’ (standard + implementation).
• Data representations, types, and operators.
• Byte-level memory model: load(), store(), alloc(), and free().
41 / 47
class A {
...
f(int x)
returns (bool y, int z) {
...
}
}
...
class B {
int m1;
bool m2;
instance A i_a;
f(int x1, bool x2)
returns (int y1, bool y2, int y3)
{
int v1;
bool v2;
v1 := state(m1) + 4;
y1 := 2 ∗ v1;
state(m1) := x1 + v1 + y1;
struct A { ... };
struct A$f {
bool y;
int z;
};
void A$f(struct A ∗self, struct A$f ∗out, int x);
struct B {
int m1;
bool m2;
struct A i_a;
};
struct B$f {
int y1;
bool y2;
int y3;
};
void B$f(struct B ∗self, struct B$f ∗out, int x1, bool x2) {
register int v1;
register bool v2;
struct A$f o;
v1 = (∗self).m1 + 4;
(∗out).y1 = 2 ∗ v1;
(∗self).m1 = x1 + v1 + (∗out).y1;
v2, y3 := A.f i_a (y1);
}
}
A$f(&(∗self).i_a, &o, (∗out).y1);
v2 = o.y;
(∗out).y3 = o.z;
y2 := v2 || (state(m2) && x2);
state(m2) := not state(m2)
}
(∗out).y2 = v2 || ((∗self).m2 && x2);
(∗self).m2 = not (∗self).m2;
return;
42 / 47
Main invariant
• Relate Obc (me, ve) and Clight (m, e, le) states.
• Reasoning about m involves alignment, padding, and disjointness.
• Manage invariants and proofs with separating conjunction and wand.
• Two requirements:
• State and prove inductive relations between source and target.
• Transfer properties of Lustre programs to generated assembler.
match_states =∀c f me ve e le sb sofs outb outco,
pure (le(self) = (sb, sofs))
∗ pure (le(out) = (outb, 0))
∗ pure (ge(c$f) = outco)
∗ staterep prog c me sb sofs
∗ blockrep ve outco outb
∗ varsrep f ve le
∗ subrep f e
∗ subrep f e −−∗ subrep_range e
43 / 47
(* Xavier's Separation.v *)
Record massert : Type := { m_pred : mem → Prop;
m_footprint : block → Z → Prop; ...
}.
Notation "m |= p" := (m_pred p m) : sep_scope.
Definition disjoint_footprint (P Q: massert) : Prop :=
∀ b ofs, m_footprint P b ofs → m_footprint Q b ofs → False.
Definition sepconj (P Q: massert) : massert := {|
m_pred := fun m ⇒ m_pred P m ∧ m_pred Q m ∧ disjoint_footprint P Q;
m_footprint := fun b ofs ⇒ m_footprint P b ofs ∨ m_footprint Q b ofs |}.
Infix "**" := sepconj : sep_scope.
(* Blockrep *)
Fixpoint sepall (p: A → massert) (xs: list A) : massert := match xs with
| nil ⇒ sepemp
| x:: xs ⇒ p x ∗∗ sepall p xs
end.
Definition match_value (e: PM. t val) (x: ident) (v': val) : Prop := match PM. find x e with
| None ⇒ True
| Some v ⇒ v' = v
end.
Definition blockrep (ve: venv) (flds: members) (b: block) : massert :=
sepall (fun (x, ty) : ident ∗ type ⇒
match field_offset ge x flds, access_mode ty with
| OK d, By_value chunk ⇒ contains chunk b d (match_value ve x)
| _, _ ⇒ sepfalse
end) flds.
44 / 47
Invariant: staterep
me
o1
state(w)
o2
int w;
int i;
int v;
int i;
int v;
o1;
o2;
state(i) state(v) state(i) state(v)
45 / 47
Invariant: staterep
me
o1
state(w)
o2
int w;
int i;
int v;
int i;
int v;
o1;
o2;
state(i) state(v) state(i) state(v)
Inductive memory (V: Type): Type := mk_memory {
mm_values : PM. t V;
mm_instances : PM. t (memory V) }.
Definition staterep_mems (cls: class) (me: menv) (b: block) (ofs: Z) (( x, ty) : ident ∗ typ) :=
match field_offset ge x (make_members cls) with
| OK d ⇒ contains (chunk_of_type ty) b (ofs + d) (match_value me.( mm_values) x)
| Error _ ⇒ sepfalse
end.
Fixpoint staterep (p: program) (clsnm: ident) (me: menv) (b: block) (ofs: Z): massert :=
match p with
| nil ⇒ sepfalse
| cls :: p' ⇒ if ident_eqb clsnm cls.(c_name) then
sepall (staterep_mems cls me b ofs) cls.( c_mems)
∗∗ sepall (fun (( i, c) : ident ∗ ident) ⇒ match field_offset ge i (make_members cls) with
| OK d ⇒ staterep p' c (instance_match me i) b (ofs + d)
| Error _ ⇒ sepfalse
end) cls.( c_objs)
else staterep p' clsnm me b ofs
45 / 47
end.
Proof structure: (big-step) induction to show invariance
;
x := e
;
;
;
x := C .f σ (e)
x := e
;
x =e
;
skip
C $f (σ, o, e)
x = o.y1
;
;
x =e
Sskip
x = o.y2
46 / 47
Proof structure: (big-step) induction to show invariance
;
x := e
;
;
;
x := C .f σ (e)
x := e
;
x =e
;
skip
C $f (σ, o, e)
x = o.y1
;
;
x =e
Sskip
x = o.y2
46 / 47
Proof structure: (big-step) induction to show invariance
;
x := e
;
;
;
x := C .f σ (e)
x := e
;
x =e
;
skip
C $f (σ, o, e)
x = o.y1
;
;
x =e
Sskip
x = o.y2
46 / 47
Proof structure: (big-step) induction to show invariance
;
x := e
x := C .f σ (e)
;
;
;
x =e
sp e ci a li z e d i n v a ria nt
;
x := e
skip
;
C $f (σ, o, e)
x = o.y1
;
;
x =e
Sskip
x = o.y2
46 / 47
Proof structure: (big-step) induction to show invariance
in d u c ti o n h y p o t h e sis
;
x := e
;
;
;
x := C .f σ (e)
x := e
;
x =e
;
skip
C $f (σ, o, e)
x = o.y1
;
;
x =e
Sskip
x = o.y2
46 / 47
Freeing memory/struct padding
• Keep permissions on local memory to ‘free’ it before return.
• Permissions are ‘in’ the contains predicate.
• But, the padding is not included. How can we free it?
match_states =∀c f me ve e le sb sofs outb outco,
···
∗ subrep f e
∗ subrep f e −−∗ subrep_range e
Separating wand
• Key property:
P ∗ (P −−∗ Q) =⇒ Q
• Works together with the frame condition on other lemmas.
47 / 47
References I
Auger, C. (2013). “Compilation certifiée de SCADE/LUSTRE”. PhD thesis.
Orsay, France: Univ. Paris Sud 11.
Bedin França, R. et al. (2011). “Towards Formally Verified Optimizing
Compilation in Flight Control Software”. In: Workshop on Bringing Theory to
Practice: Predictability and Performance in Embedded Systems (PPES 2011).
Ed. by P. Lucas et al. Vol. 18. OpenAccess Series in Informatics (OASIcs).
Grenoble, France: Schloss Daghstuhl, pp. 59–68.
Biernacki, D. et al. (2008). “Clock-directed modular code generation for
synchronous data-flow languages”. In: Proc. 2008 ACM SIGPLAN Conf. on
Languages, Compilers, and Tools for Embedded Systems (LCTES 2008).
ACM. Tucson, AZ, USA: ACM Press, pp. 121–130.
Blazy, S., Z. Dargaye, and X. Leroy (2006). “Formal Verification of a C
Compiler Front-End”. In: Proc. 14th Int. Symp. Formal Methods (FM 2006).
Vol. 4085. Lecture Notes in Comp. Sci. Hamilton, Canada: Springer,
pp. 460–475.
Boulmé, S. and G. Hamon (2001). A clocked denotational semantics for
Lucid-Synchrone in Coq. Tech. rep. LIP6.
I
References II
Caspi, P. et al. (1987). “LUSTRE: A declarative language for programming
synchronous systems”. In: Proc. 14th ACM SIGPLAN-SIGACT Symp.
Principles Of Programming Languages (POPL 1987). ACM. Munich,
Germany: ACM Press, pp. 178–188.
Colaço, J.-L., B. Pagano, and M. Pouzet (2005). “A Conservative Extension
of Synchronous Data-flow with State Machines”. In: Proc. 5th ACM Int.
Conf. on Embedded Software (EMSOFT 2005). Ed. by W. Wolf. Jersey City,
USA: ACM Press, pp. 173–182. ISBN: 1-59593-091-4.
Leroy, X. (2009). “Formal verification of a realistic compiler”. In: Comms.
ACM 52.7, pp. 107–115.
McCoy, F. (1885). Natural history of Victoria: Prodromus of the Zoology of
Victoria. Frog images.
Paulin-Mohring, C. (2009). “A constructive denotational semantics for Kahn
networks in Coq”. In: From Semantics to Computer Science: Essays in Honour
of Gilles Kahn. Ed. by Y. Bertot et al. Cambridge University Press,
pp. 383–413.
II
© Copyright 2026 Paperzz