@@ -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
8888const AbstractValue = Dict{Symbol,LatticeElement}
@@ -97,11 +97,44 @@ const AbstractValue = Dict{Symbol,LatticeElement}
9797<= (X:: AbstractValue , Y:: AbstractValue ) = X⊓ Y == 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)`
100133function 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
138171end
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
147199end
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
157207function 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... ))
162218end
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