Skip to content

Commit c0b83dd

Browse files
committed
add constant propagation example
1 parent 4521b5c commit c0b83dd

File tree

1 file changed

+131
-23
lines changed

1 file changed

+131
-23
lines changed

dataflow.jl

Lines changed: 131 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ show(io::IO, ::BotElement) = print(io, "⊥")
8282
# LatticeElement.
8383

8484

85-
# Part 3: dataflow analysis
85+
# Part 3: abstract value
8686

8787
# Note: the paper (https://api.semanticscholar.org/CorpusID:28519618) uses U+1D56E MATHEMATICAL BOLD FRAKTUR CAPITAL C for this
8888
const AbstractValue = Dict{Symbol,LatticeElement}
@@ -97,11 +97,44 @@ const AbstractValue = Dict{Symbol,LatticeElement}
9797
<=(X::AbstractValue, Y::AbstractValue) = XY == X
9898
<(X::AbstractValue, Y::AbstractValue) = X<=Y && X!=Y
9999

100+
101+
#########################################################
102+
# example problem 1. - find uses of undefined variables #
103+
#########################################################
104+
105+
# flat lattice of variable definedness
106+
107+
struct IsDefined <: LatticeElement
108+
is::Bool
109+
end
110+
show(io::IO, isdef::IsDefined) = print(io, isdef.is ? "defined" : "undefined")
111+
112+
const undef = IsDefined(false)
113+
const def = IsDefined(true)
114+
115+
# abstract semantics
116+
117+
abstract_eval(x::Sym, s::AbstractValue) = get(s, x.name, ⊥)
118+
119+
abstract_eval(x::Num, s::AbstractValue) = def
120+
121+
function abstract_eval(x::Call, s::AbstractValue)
122+
if any(a->(abstract_eval(a,s) == ⊥), x.args)
123+
return
124+
end
125+
return def
126+
end
127+
128+
# data flow analysis
129+
130+
# Note:
131+
# - in this problem, we make sure that states will always move to higher position in lattice, so we use ⊔ (join) operator for state update
132+
# - and the condition we use to check whether or not the statement makes a change is `!(new <= prev)`
100133
function max_fixed_point(P::Program, a₁::AbstractValue, eval) where {Program<:AbstractVector{Stmt}}
101134
n = length(P)
102135
bot = AbstractValue( v =>for v in keys(a₁) )
103136
s = [ a₁; [ bot for i = 2:n ] ]
104-
W = BitSet(1)
137+
W = BitSet(1:n)
105138

106139
while !isempty(W)
107140
pc = first(W)
@@ -137,38 +170,113 @@ function max_fixed_point(P::Program, a₁::AbstractValue, eval) where {Program<:
137170
s
138171
end
139172

173+
prog1 = [Assign(:x, 0), # 1
174+
GotoIf(5, Call(:randbool, Exp[])), # 2
175+
Assign(:y, 1), # 3
176+
Goto(5), # 4
177+
Assign(:z, Call(:pair, Exp[:x,:y])), # 5
178+
Ret()]
140179

141-
# example problem - find uses of undefined variables
180+
# variables initially undefined
181+
l = AbstractValue(:x => undef, :y => undef, :z => undef)
142182

143-
# flat lattice of variable definedness
183+
max_fixed_point(prog1, l, abstract_eval)
144184

145-
struct IsDefined <: LatticeElement
146-
is::Bool
185+
186+
#########################################################################
187+
# example problem 2. - constant folding propagation (the paper example) #
188+
#########################################################################
189+
190+
# lattice
191+
192+
# Note: intuitively, each lattice element can be interpreted in the following way:
193+
# - `Int` means "constant" value
194+
# - `⊤` means "not constant due to missing information"
195+
# - `⊥` means "not constant due to conflict"
196+
197+
struct Const <: LatticeElement
198+
val::Int
147199
end
148-
show(io::IO, isdef::IsDefined) = print(io, isdef.is ? "defined" : "undefined")
149200

150-
const undef = IsDefined(false)
151-
const def = IsDefined(true)
201+
# abstract semantics
152202

153-
abstract_eval(x::Sym, s::AbstractValue) = get(s, x.name, ⊥)
203+
abstract_eval(x::Num, s::AbstractValue) = Const(x.val)
154204

155-
abstract_eval(x::Num, s::AbstractValue) = def
205+
abstract_eval(x::Sym, s::AbstractValue) = get(s, x.name, ⊤)
156206

157207
function abstract_eval(x::Call, s::AbstractValue)
158-
if any(a->(abstract_eval(a,s) == ⊥), x.args)
159-
return
208+
f = getfield(@__MODULE__, x.head.name)
209+
210+
argvals = Int[]
211+
for arg in x.args
212+
arg = abstract_eval(arg, s)
213+
arg ===&& return# bail out if any of call arguments is non-constant
214+
push!(argvals, unwrap_val(arg))
160215
end
161-
return def
216+
217+
return Const(f(argvals...))
162218
end
163219

164-
prog1 = [Assign(:x, 0), # 1
165-
GotoIf(5, Call(:randbool, Exp[])), # 2
166-
Assign(:y, 1), # 3
167-
Goto(5), # 4
168-
Assign(:z, Call(:pair, Exp[:x,:y])), # 5
169-
Ret()]
220+
# unwrap our lattice representation into actual Julia value
221+
unwrap_val(x::Num) = x.val
222+
unwrap_val(x::Const) = x.val
170223

171-
# variables initially undefined
172-
l = AbstractValue(:x => undef, :y => undef, :z => undef)
224+
# Note: in this problem, we make sure that states will always move to _lower_ position in lattice, so
225+
# - initialize states with `⊤`
226+
# - we use `⊓` (meet) operator to update states,
227+
# - and the condition we use to check whether or not the statement makes a change is `!(new >= prev)`
228+
function max_fixed_point(P::Program, a₁::AbstractValue, eval) where {Program<:AbstractVector{Stmt}}
229+
n = length(P)
230+
top = AbstractValue( v =>for v in keys(a₁) )
231+
s = [ a₁; [ top for i = 2:n ] ]
232+
W = BitSet(1:n)
173233

174-
max_fixed_point(prog1, l, abstract_eval)
234+
while !isempty(W)
235+
pc = first(W)
236+
while pc != n+1
237+
delete!(W, pc)
238+
I = P[pc]
239+
new = s[pc]
240+
if isa(I, Assign)
241+
# for an assignment, outgoing value is different from incoming
242+
new = copy(new)
243+
new[I.lhs.name] = eval(I.rhs, new)
244+
end
245+
if isa(I, Goto)
246+
pc´ = I.label
247+
else
248+
pc´ = pc+1
249+
if isa(I, GotoIf)
250+
l = I.label
251+
if !(new >= s[l])
252+
push!(W, l)
253+
s[l] = s[l] new
254+
end
255+
end
256+
end
257+
if pc´<=n && !(new >= s[pc´])
258+
s[pc´] = s[pc´] new
259+
pc = pc´
260+
else
261+
pc = n+1
262+
end
263+
end
264+
end
265+
s
266+
end
267+
268+
prog1 = [Assign(:x, 1), # 1
269+
Assign(:y, 2), # 2
270+
Assign(:z, 3), # 3
271+
Goto(9), # 4
272+
Assign(:r, Call(:(+), [:y, :z])), # 5
273+
GotoIf(8, Call(:(), [:x, :z])), # 6
274+
Assign(:r, Call(:(+), [:z, :y])), # 7
275+
Assign(:x, Call(:(+), [:x, 1])), # 8
276+
GotoIf(5, Call(:(<), [:x, 10])), # 9
277+
]
278+
279+
# initially values are not constant (due to missing information)
280+
a₁ = AbstractValue(:x => ⊤, :y => ⊤, :z => ⊤, :r => ⊤)
281+
282+
max_fixed_point(prog1, a₁, abstract_eval) # The solution contains the `:r => Const(5)`, which is not found in the program

0 commit comments

Comments
 (0)