diff --git a/dataflow.jl b/dataflow.jl index cd9b051..d129362 100644 --- a/dataflow.jl +++ b/dataflow.jl @@ -2,39 +2,38 @@ import Base: convert -abstract Exp +abstract type Exp end -immutable Sym <: Exp +struct Sym <: Exp name::Symbol end -immutable Num <: Exp +struct Num <: Exp val::Int end -type Call <: Exp +struct Call <: Exp head::Sym args::Vector{Exp} end -abstract Stmt +abstract type Stmt end -type Assign <: Stmt +struct Assign <: Stmt lhs::Sym rhs::Exp end -type Goto <: Stmt +struct Goto <: Stmt label::Int end -type GotoIf <: Stmt +struct GotoIf <: Stmt label::Int cond::Exp end -type Ret <: Stmt -end +struct Ret <: Stmt end convert(::Type{Exp}, s::Symbol) = Sym(s) convert(::Type{Sym}, s::Symbol) = Sym(s) @@ -48,7 +47,7 @@ convert(::Type{Num}, n::Int) = Num(n) import Base: <=, ==, <, show -abstract LatticeElement +abstract type LatticeElement end # Note: == and < are defined such that future LatticeElements only # need to implement <= @@ -59,8 +58,8 @@ abstract LatticeElement <(x::LatticeElement, y::LatticeElement) = x<=y && !(y<=x) -immutable TopElement <: LatticeElement; end -immutable BotElement <: LatticeElement; end +struct TopElement <: LatticeElement end +struct BotElement <: LatticeElement end const ⊤ = TopElement() const ⊥ = BotElement() @@ -83,26 +82,59 @@ show(io::IO, ::BotElement) = print(io, "⊥") # LatticeElement. -# Part 3: dataflow analysis +# Part 3: abstract value -# Note: the paper uses U+1D56E MATHEMATICAL BOLD FRAKTUR CAPITAL C for this -typealias AbstractValue Dict{Symbol,LatticeElement} +# Note: the paper (https://api.semanticscholar.org/CorpusID:28519618) uses U+1D56E MATHEMATICAL BOLD FRAKTUR CAPITAL C for this +const AbstractValue = Dict{Symbol,LatticeElement} # Here we extend lattices of values to lattices of mappings of variables # to values. meet and join operate elementwise, and from there we only # need equality on dictionaries to get <= and <. -⊔(X::AbstractValue, Y::AbstractValue) = [ v => X[v] ⊔ Y[v] for v in keys(X) ] -⊓(X::AbstractValue, Y::AbstractValue) = [ v => X[v] ⊓ Y[v] for v in keys(X) ] +⊔(X::AbstractValue, Y::AbstractValue) = AbstractValue( v => X[v] ⊔ Y[v] for v in keys(X) ) +⊓(X::AbstractValue, Y::AbstractValue) = AbstractValue( v => X[v] ⊓ Y[v] for v in keys(X) ) <=(X::AbstractValue, Y::AbstractValue) = X⊓Y == X -< (X::AbstractValue, Y::AbstractValue) = X!=Y && X<=Y +<(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 -function max_fixed_point(P::Vector, a₁::AbstractValue, eval) +# 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₁) ]) + bot = AbstractValue( v => ⊥ for v in keys(a₁) ) s = [ a₁; [ bot for i = 2:n ] ] - W = IntSet(1) + W = BitSet(1:n) while !isempty(W) pc = first(W) @@ -138,37 +170,113 @@ function max_fixed_point(P::Vector, a₁::AbstractValue, eval) 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) -immutable 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 -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) + + argvals = Int[] + for arg in x.args + arg = abstract_eval(arg, s) + arg === ⊥ && return ⊥ # bail out if any of call arguments is non-constant + push!(argvals, unwrap_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()] +# unwrap our lattice representation into actual Julia value +unwrap_val(x::Num) = x.val +unwrap_val(x::Const) = x.val -# variables initially undefined -l = AbstractValue(:x => undef, :y => undef, :z => undef) +# 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) + top = AbstractValue( v => ⊤ for v in keys(a₁) ) + s = [ a₁; [ top for i = 2:n ] ] + W = BitSet(1:n) -max_fixed_point(prog1, l, abstract_eval) + 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 + +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