Skip to content

Commit e5978d6

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

File tree

1 file changed

+130
-23
lines changed

1 file changed

+130
-23
lines changed

dataflow.jl

Lines changed: 130 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,112 @@ 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+
to_val(x::Num) = x.val::Int
211+
to_val(x::Const) = x.val::Int
212+
argvals = Int[]
213+
for arg in x.args
214+
arg = abstract_eval(arg, s)
215+
arg ===&& return
216+
arg ===&& return
217+
push!(argvals, to_val(arg))
160218
end
161-
return def
219+
220+
return Const(f(argvals...))
162221
end
163222

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

171-
# variables initially undefined
172-
l = AbstractValue(:x => undef, :y => undef, :z => undef)
233+
while !isempty(W)
234+
pc = first(W)
235+
while pc != n+1
236+
delete!(W, pc)
237+
I = P[pc]
238+
new = s[pc]
239+
if isa(I, Assign)
240+
# for an assignment, outgoing value is different from incoming
241+
new = copy(new)
242+
new[I.lhs.name] = eval(I.rhs, new)
243+
end
244+
if isa(I, Goto)
245+
pc´ = I.label
246+
else
247+
pc´ = pc+1
248+
if isa(I, GotoIf)
249+
l = I.label
250+
if !(new >= s[l])
251+
push!(W, l)
252+
s[l] = s[l] new
253+
end
254+
end
255+
end
256+
if pc´<=n && !(new >= s[pc´])
257+
s[pc´] = s[pc´] new
258+
pc = pc´
259+
else
260+
pc = n+1
261+
end
262+
end
263+
end
264+
s
265+
end
173266

174-
max_fixed_point(prog1, l, abstract_eval)
267+
prog1 = [Assign(:x, 1), # 1
268+
Assign(:y, 2), # 2
269+
Assign(:z, 3), # 3
270+
Goto(9), # 4
271+
Assign(:r, Call(:(+), [:y, :z])), # 5
272+
GotoIf(8, Call(:(), [:x, :z])), # 6
273+
Assign(:r, Call(:(+), [:z, :y])), # 7
274+
Assign(:x, Call(:(+), [:x, 1])), # 8
275+
GotoIf(5, Call(:(<), [:x, 10])), # 9
276+
]
277+
278+
# initially values are not constant (due to missing information)
279+
a₁ = AbstractValue(:x => ⊤, :y => ⊤, :z => ⊤, :r => ⊤)
280+
281+
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)