1. A MuDDy Experience
ML Bindings to a BDD Library
Ken Friis Larsen
kflarsen@diku.dk
Department of Computer Science
University of Copenhagen
July 15, 2009
1 / 20
3. Background
BuDDy
C library for Binary Decision Diagrams by Jørn Lind-Nielsen
State-of-art performance . . . 10 years ago
MuDDy
An ML interface to BuDDy
Comes in Moscow ML, MLton, and O’Caml flavours
Been used in many different projects over the years
Domain Specific Embedded Language (DSEL)
You embed a DSL in a general-purpose language
3 / 20
4. Binary Decision Diagrams
A BDD is:
A canonical explicit directed acyclic graph representation of a
boolean function
A boolean expression on if-then-else normal form
BDDs are mainly used for formal verification and hardware
synthesis
Excellent for representing large relations, for instance.
4 / 20
5. Binary Decision Diagrams
A BDD is:
A canonical explicit directed acyclic graph representation of a
boolean function
A boolean expression on if-then-else normal form
BDDs are mainly used for formal verification and hardware
synthesis
Excellent for representing large relations, for instance.
Did I mention it is a canonical representation?
4 / 20
6. An Example BDD
A BDD representing (x ⇔ y) ∧ ¬z
x
y y
z
0 1
With variable ordering x < y < z
5 / 20
7. MuDDy
MuDDy supports most features from BuDDy
Structure bdd contains functions for manipulating BDDs
Structure fdd contains functions for manipulating finite domain
values, represented by a set of BDDs
Structure bvec contains functions for representing and
manipulating machine words represented by a set of BDDs.
6 / 20
8. Building BDDs from SML
Building a BDD representing the expression (x ⇒ y ∧ x) ⇒ y
val (x, y) = (bdd.ithvar 0, bdd.ithvar 1)
val b = (x ==> y / x) ==> y
7 / 20
9. Building BDDs from SML
Building a BDD representing the expression (x ⇒ y ∧ x) ⇒ y
val (x, y) = (bdd.ithvar 0, bdd.ithvar 1)
val b = (x ==> y / x) ==> y
Given the syntactic sugar:
infix ==> /
val(op /, op ==>) = (bdd.AND, bdd.IMP)
7 / 20
10. Using BDDs to Analyse a DS(E)L
1. Design your language
2. Declare types for representing abstract syntax trees
3. Design a concrete syntax
4. Use BDDs to model the semantics of your language
Usually that means defining a (huge) transition predicate
5. Use BDDs to find the set of reachable states and analyse all
reachable states in one go.
8 / 20
11. Simple Guarded Command Language
e ::= true | false | x | e1 op e2 | ¬e
assignment ::= x1 , . . . , xn := e1 , . . . , en
command ::= e ? assignment
program ::= assignment
command1 || . . . || commandn
9 / 20
12. Milner’s Scheduler
h1
c1 c2
start h0 h2
c0 c3
h3
cycler i = ci ∧ ¬ti ? ti , ci , hi := true, ¬ci , true
|| hi ? ci+1 mod N , hi := true, false
10 / 20
13. SGCL Embedded in SML, Abstract syntax
type var = string
datatype boolop = AND | OR | IMP | BIIMP
datatype bexp = BVar of var
| BBin of bexp * boolop * bexp
| NOT of bexp
| TRUE | FALSE
datatype command = CMD of bexp * (var * bexp) list
datatype program = PRG of (var * bexp) list * command list
11 / 20
14. SGCL Embedded in SML, Syntactic Sugar
fun mkBBin opr (x, y) = BBin(x, opr, y)
infix / / ==> <==>
val (op /, op /, op ==>, op <==>) =
(mkBBin AND, mkBBin OR, mkBBin IMP, mkBBin BIIMP)
infix ::=
val op ::= = ListPair.zip
infix ?
fun g ? ass = [CMD(g, ass)]
infix ||
val op|| = op@
val $ = BVar
12 / 20
16. Semantics of SGCL
The semantics of a command is a predicate describing a state change
by using a ordinary variables to describe the current state and primed
variables to describe the next state.
The semantics of a program is a predicate describing the initial state,
and conjunction of the semantics of the commands.
type bdd_vars = { var: int, primed: int}
type var_map = string * bdd_vars
val commandToBDD: var_map -> command -> bdd.bdd
val programToBDD: var_map -> program -> bdd.bdd * bdd.bdd
14 / 20
17. Semantics of SGCL
fun commandToBDD allVars (CMD(guard, assignments)) =
let val changed = List.map #1 assignments
val unchanged =
List.foldl (fn ((v, {var, primed}), res) =>
if mem v changed then res
else bdd.AND(bdd.BIIMP(bdd.ithvar primed,
bdd.ithvar var),
res))
bdd.TRUE allVars
val assigns =
conj (map (fn (v,be) =>
bdd.BIIMP(primed v, bexp be))
assignments)
in bdd.IMP(bexp guard, assigns) end
15 / 20
18. Finding All Reachable States
fun reachable allVars I T =
let val renames =
List.map (fn(_,{var,primed}) => (var, primed))
allVars
val pairset = bdd.makepairSet renames
val unprimed = bdd.makeset(List.map #var allVars)
open bdd infix OR
fun loop R =
let val post = appex T R And unprimed
val next = R OR replace next pairset
in if equal R next then R else loop next end
in loop I end
16 / 20
19. Putting It All Together
To find all the reachable states of a SGCL program first call
programToBDD and then reachable:
val milner4 = ...
val allVars = ...
val (I, T) = programToBDD allVars milner4
val states = reachable allVars I T
We can now easily check some invariants. For example, that if
cycler 2 holds a token, no other cycler has a token:
val c2inv = $c2 ==> NOT($c0) / NOT($c1) / NOT($c3)
val check_c2inv = bdd.IMP (states, bexp allVars c2inv)
17 / 20
20. Keeping It Honest
The construction of the mapping between DSL variables and BDDs
variables is usually where things go sour. That is, it is hard to choose
a good BDD variable ordering.
No general algorithm, but the following heuristics gives good results:
a variable and its primed version should be next to each other in
the ordering
if two variables are “close” to each other in the syntax tree, they
should be close to each other in the ordering
if a variable occurs with high frequency in the syntax tree, it
should be in the beginning of the ordering
18 / 20
21. What About Performance?
No. of Schedulers: 50 100 150 200
C 1.63 4.69 13.66 31.20
C++ 1.66 4.82 13.89 31.51
O’Caml (native) 1.71 5.04 14.47 32.05
O’Caml (bytecode) 1.74 5.15 14.58 32.91
Moscow ML 1.76 5.15 15.12 33.42
Fresh runs on current laptop:
No. of Schedulers: 50 100 150 200
C 0.38 1.07 3.18 7.25
O’Caml (native) 0.35 1.21 3.42 7.64
O’Caml (bytecode) 0.38 1.24 3.52 7.82
Moscow ML 0.37 1.21 3.45 7.73
MLton 0.38 1.29 3.68 8.14
19 / 20
22. Summary
ML and BDDs hits a sweet spot
reasonably easy to embed DSLs in ML languages
MLs gives few surprises with respect to space usage and execution
BDDs can be used to represent many nice abstractions
symbolically
SGCL is a nice way to specify finite state machines
20 / 20