Skip to content

Commit

Permalink
add constant propagation example
Browse files Browse the repository at this point in the history
  • Loading branch information
aviatesk committed Oct 24, 2020
1 parent 4521b5c commit e5978d6
Showing 1 changed file with 130 additions and 23 deletions.
153 changes: 130 additions & 23 deletions dataflow.jl
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ show(io::IO, ::BotElement) = print(io, "⊥")
# LatticeElement.


# Part 3: dataflow analysis
# Part 3: abstract value

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


#########################################################
# example problem 1. - find uses of undefined variables #
#########################################################

# flat lattice of variable definedness

struct IsDefined <: LatticeElement
is::Bool
end
show(io::IO, isdef::IsDefined) = print(io, isdef.is ? "defined" : "undefined")

const undef = IsDefined(false)
const def = IsDefined(true)

# abstract semantics

abstract_eval(x::Sym, s::AbstractValue) = get(s, x.name, ⊥)

abstract_eval(x::Num, s::AbstractValue) = def

function abstract_eval(x::Call, s::AbstractValue)
if any(a->(abstract_eval(a,s) == ⊥), x.args)
return
end
return def
end

# data flow analysis

# Note:
# - in this problem, we make sure that states will always move to higher position in lattice, so we use ⊔ (join) operator for state update
# - and the condition we use to check whether or not the statement makes a change is `!(new <= prev)`
function max_fixed_point(P::Program, a₁::AbstractValue, eval) where {Program<:AbstractVector{Stmt}}
n = length(P)
bot = AbstractValue( v =>for v in keys(a₁) )
s = [ a₁; [ bot for i = 2:n ] ]
W = BitSet(1)
W = BitSet(1:n)

while !isempty(W)
pc = first(W)
Expand Down Expand Up @@ -137,38 +170,112 @@ function max_fixed_point(P::Program, a₁::AbstractValue, eval) where {Program<:
s
end

prog1 = [Assign(:x, 0), # 1
GotoIf(5, Call(:randbool, Exp[])), # 2
Assign(:y, 1), # 3
Goto(5), # 4
Assign(:z, Call(:pair, Exp[:x,:y])), # 5
Ret()]

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

# flat lattice of variable definedness
max_fixed_point(prog1, l, abstract_eval)

struct IsDefined <: LatticeElement
is::Bool

#########################################################################
# example problem 2. - constant folding propagation (the paper example) #
#########################################################################

# lattice

# Note: intuitively, each lattice element can be interpreted in the following way:
# - `Int` means "constant" value
# - `⊤` means "not constant due to missing information"
# - `⊥` means "not constant due to conflict"

struct Const <: LatticeElement
val::Int
end
show(io::IO, isdef::IsDefined) = print(io, isdef.is ? "defined" : "undefined")

const undef = IsDefined(false)
const def = IsDefined(true)
# abstract semantics

abstract_eval(x::Sym, s::AbstractValue) = get(s, x.name, ⊥)
abstract_eval(x::Num, s::AbstractValue) = Const(x.val)

abstract_eval(x::Num, s::AbstractValue) = def
abstract_eval(x::Sym, s::AbstractValue) = get(s, x.name, ⊤)

function abstract_eval(x::Call, s::AbstractValue)
if any(a->(abstract_eval(a,s) == ⊥), x.args)
return
f = getfield(@__MODULE__, x.head.name)

to_val(x::Num) = x.val::Int
to_val(x::Const) = x.val::Int
argvals = Int[]
for arg in x.args
arg = abstract_eval(arg, s)
arg ===&& return
arg ===&& return
push!(argvals, to_val(arg))
end
return def

return Const(f(argvals...))
end

prog1 = [Assign(:x, 0), # 1
GotoIf(5, Call(:randbool, Exp[])), # 2
Assign(:y, 1), # 3
Goto(5), # 4
Assign(:z, Call(:pair, Exp[:x,:y])), # 5
Ret()]
# Note: in this problem, we make sure that states will always move to _lower_ position in lattice, so
# - initialize states with `⊤`
# - we use `⊓` (meet) operator to update states,
# - and the condition we use to check whether or not the statement makes a change is `!(new >= prev)`
function max_fixed_point(P::Program, a₁::AbstractValue, eval) where {Program<:AbstractVector{Stmt}}
n = length(P)
init = AbstractValue( v =>for v in keys(a₁) )
s = [ a₁; [ init for i = 2:n ] ]
W = BitSet(1:n)

# variables initially undefined
l = AbstractValue(:x => undef, :y => undef, :z => undef)
while !isempty(W)
pc = first(W)
while pc != n+1
delete!(W, pc)
I = P[pc]
new = s[pc]
if isa(I, Assign)
# for an assignment, outgoing value is different from incoming
new = copy(new)
new[I.lhs.name] = eval(I.rhs, new)
end
if isa(I, Goto)
pc´ = I.label
else
pc´ = pc+1
if isa(I, GotoIf)
l = I.label
if !(new >= s[l])
push!(W, l)
s[l] = s[l] new
end
end
end
if pc´<=n && !(new >= s[pc´])
s[pc´] = s[pc´] new
pc = pc´
else
pc = n+1
end
end
end
s
end

max_fixed_point(prog1, l, abstract_eval)
prog1 = [Assign(:x, 1), # 1
Assign(:y, 2), # 2
Assign(:z, 3), # 3
Goto(9), # 4
Assign(:r, Call(:(+), [:y, :z])), # 5
GotoIf(8, Call(:(), [:x, :z])), # 6
Assign(:r, Call(:(+), [:z, :y])), # 7
Assign(:x, Call(:(+), [:x, 1])), # 8
GotoIf(5, Call(:(<), [:x, 10])), # 9
]

# initially values are not constant (due to missing information)
a₁ = AbstractValue(:x => ⊤, :y => ⊤, :z => ⊤, :r => ⊤)

max_fixed_point(prog1, a₁, abstract_eval) # The solution contains the `:r => Const(5)`, which is not found in the program

0 comments on commit e5978d6

Please sign in to comment.