1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328
|
# This file is a part of Julia. License is MIT: https://julialang.org/license
#####################
# structs/constants #
#####################
# The type of a value might be constant
struct Const
val
actual::Bool # if true, we obtained `val` by actually calling a @pure function
Const(@nospecialize(v)) = new(v, false)
Const(@nospecialize(v), a::Bool) = new(v, a)
end
# The type of this value might be Bool.
# However, to enable a limited amount of back-propagagation,
# we also keep some information about how this Bool value was created.
# In particular, if you branch on this value, then may assume that in
# the true branch, the type of `var` will be limited by `vtype` and in
# the false branch, it will be limited by `elsetype`. Example:
# ```
# cond = isa(x::Union{Int, Float}, Int)::Conditional(x, Int, Float)
# if cond
# # May assume x is `Int` now
# else
# # May assume x is `Float` now
# end
# ```
struct Conditional
var::Slot
vtype
elsetype
function Conditional(
var,
@nospecialize(vtype),
@nospecialize(nottype))
return new(var, vtype, nottype)
end
end
struct PartialTypeVar
tv::TypeVar
# N.B.: Currently unused, but would allow turning something back
# into Const, if the bounds are pulled out of this TypeVar
lb_certain::Bool
ub_certain::Bool
PartialTypeVar(tv::TypeVar, lb_certain::Bool, ub_certain::Bool) = new(tv, lb_certain, ub_certain)
end
# Wraps a type and represents that the value may also be undef at this point.
# (only used in optimize, not abstractinterpret)
struct MaybeUndef
typ
MaybeUndef(@nospecialize(typ)) = new(typ)
end
# The type of a variable load is either a value or an UndefVarError
# (only used in abstractinterpret, doesn't appear in optimize)
struct VarState
typ
undef::Bool
VarState(@nospecialize(typ), undef::Bool) = new(typ, undef)
end
const VarTable = Array{Any,1}
struct StateUpdate
var::Union{Slot,SSAValue}
vtype::VarState
state::VarTable
end
struct PartialStruct
typ
fields::Vector{Any} # elements are other type lattice members
end
struct NotFound end
const NOT_FOUND = NotFound()
#################
# lattice logic #
#################
function issubconditional(a::Conditional, b::Conditional)
avar = a.var
bvar = b.var
if (isa(avar, Slot) && isa(bvar, Slot) && slot_id(avar) === slot_id(bvar)) ||
(isa(avar, SSAValue) && isa(bvar, SSAValue) && avar === bvar)
if a.vtype ⊑ b.vtype
if a.elsetype ⊑ b.elsetype
return true
end
end
end
return false
end
maybe_extract_const_bool(c::Const) = isa(c.val, Bool) ? c.val : nothing
function maybe_extract_const_bool(c::Conditional)
(c.vtype === Bottom && !(c.elsetype === Bottom)) && return false
(c.elsetype === Bottom && !(c.vtype === Bottom)) && return true
nothing
end
maybe_extract_const_bool(@nospecialize c) = nothing
function ⊑(@nospecialize(a), @nospecialize(b))
if isa(a, MaybeUndef) && !isa(b, MaybeUndef)
return false
end
isa(a, MaybeUndef) && (a = a.typ)
isa(b, MaybeUndef) && (b = b.typ)
(a === NOT_FOUND || b === Any) && return true
(a === Any || b === NOT_FOUND) && return false
a === Union{} && return true
b === Union{} && return false
if isa(a, Conditional)
if isa(b, Conditional)
return issubconditional(a, b)
elseif isa(b, Const) && isa(b.val, Bool)
return maybe_extract_const_bool(a) === b.val
end
a = Bool
elseif isa(b, Conditional)
return false
end
if isa(a, PartialStruct)
if isa(b, PartialStruct)
if !(length(a.fields) == length(b.fields) && a.typ <: b.typ)
return false
end
for i in 1:length(b.fields)
# XXX: let's handle varargs later
⊑(a.fields[i], b.fields[i]) || return false
end
return true
end
return isa(b, Type) && a.typ <: b
elseif isa(b, PartialStruct)
if isa(a, Const)
nfields(a.val) == length(b.fields) || return false
widenconst(b).name === widenconst(a).name || return false
# We can skip the subtype check if b is a Tuple, since in that
# case, the ⊑ of the elements is sufficient.
if b.typ.name !== Tuple.name && !(widenconst(a) <: widenconst(b))
return false
end
for i in 1:nfields(a.val)
# XXX: let's handle varargs later
isdefined(a.val, i) || return false
⊑(Const(getfield(a.val, i)), b.fields[i]) || return false
end
return true
end
return false
end
if isa(a, Const)
if isa(b, Const)
return a.val === b.val
end
# TODO: `b` could potentially be a `PartialTypeVar` here, in which case we might be
# able to return `true` in more cases; in the meantime, just returning this is the
# most conservative option.
return isa(b, Type) && isa(a.val, b)
elseif isa(b, Const)
if isa(a, DataType) && isdefined(a, :instance)
return a.instance === b.val
end
return false
elseif isa(a, PartialTypeVar) && b === TypeVar
return true
elseif !(isa(a, Type) || isa(a, TypeVar)) ||
!(isa(b, Type) || isa(b, TypeVar))
return a === b
else
return a <: b
end
end
# Check if two lattice elements are partial order equivalent. This is basically
# `a ⊑ b && b ⊑ a` but with extra performance optimizations.
function is_lattice_equal(@nospecialize(a), @nospecialize(b))
a === b && return true
if isa(a, PartialStruct)
isa(b, PartialStruct) || return false
length(a.fields) == length(b.fields) || return false
widenconst(a) == widenconst(b) || return false
for i in 1:length(a.fields)
is_lattice_equal(a.fields[i], b.fields[i]) || return false
end
return true
end
isa(b, PartialStruct) && return false
a isa Const && return false
b isa Const && return false
return a ⊑ b && b ⊑ a
end
widenconst(c::Conditional) = Bool
function widenconst(c::Const)
if isa(c.val, Type)
if isvarargtype(c.val)
return Type
end
return Type{c.val}
else
return typeof(c.val)
end
end
widenconst(m::MaybeUndef) = widenconst(m.typ)
widenconst(c::PartialTypeVar) = TypeVar
widenconst(t::PartialStruct) = t.typ
widenconst(@nospecialize(t)) = t
issubstate(a::VarState, b::VarState) = (a.typ ⊑ b.typ && a.undef <= b.undef)
function smerge(sa::Union{NotFound,VarState}, sb::Union{NotFound,VarState})
sa === sb && return sa
sa === NOT_FOUND && return sb
sb === NOT_FOUND && return sa
issubstate(sa, sb) && return sb
issubstate(sb, sa) && return sa
return VarState(tmerge(sa.typ, sb.typ), sa.undef | sb.undef)
end
@inline tchanged(@nospecialize(n), @nospecialize(o)) = o === NOT_FOUND || (n !== NOT_FOUND && !(n ⊑ o))
@inline schanged(@nospecialize(n), @nospecialize(o)) = (n !== o) && (o === NOT_FOUND || (n !== NOT_FOUND && !issubstate(n, o)))
widenconditional(@nospecialize typ) = typ
function widenconditional(typ::Conditional)
if typ.vtype == Union{}
return Const(false)
elseif typ.elsetype == Union{}
return Const(true)
else
return Bool
end
end
function stupdate!(state::Nothing, changes::StateUpdate)
newst = copy(changes.state)
if isa(changes.var, Slot)
changeid = slot_id(changes.var::Slot)
newst[changeid] = changes.vtype
# remove any Conditional for this Slot from the vtable
for i = 1:length(newst)
newtype = newst[i]
if isa(newtype, VarState)
newtypetyp = newtype.typ
if isa(newtypetyp, Conditional) && slot_id(newtypetyp.var) == changeid
newst[i] = VarState(widenconditional(newtypetyp), newtype.undef)
end
end
end
end
return newst
end
function stupdate!(state::VarTable, changes::StateUpdate)
if !isa(changes.var, Slot)
return stupdate!(state, changes.state)
end
newstate = false
changeid = slot_id(changes.var::Slot)
for i = 1:length(state)
if i == changeid
newtype = changes.vtype
else
newtype = changes.state[i]
end
oldtype = state[i]
# remove any Conditional for this Slot from the vtable
if isa(newtype, VarState)
newtypetyp = newtype.typ
if isa(newtypetyp, Conditional) && slot_id(newtypetyp.var) == changeid
newtype = VarState(widenconditional(newtypetyp), newtype.undef)
end
end
if schanged(newtype, oldtype)
newstate = state
state[i] = smerge(oldtype, newtype)
end
end
return newstate
end
function stupdate!(state::VarTable, changes::VarTable)
newstate = false
for i = 1:length(state)
newtype = changes[i]
oldtype = state[i]
if schanged(newtype, oldtype)
newstate = state
state[i] = smerge(oldtype, newtype)
end
end
return newstate
end
stupdate!(state::Nothing, changes::VarTable) = copy(changes)
stupdate!(state::Nothing, changes::Nothing) = false
function stupdate1!(state::VarTable, change::StateUpdate)
if !isa(change.var, Slot)
return false
end
changeid = slot_id(change.var::Slot)
# remove any Conditional for this Slot from the catch block vtable
for i = 1:length(state)
oldtype = state[i]
if isa(oldtype, VarState)
oldtypetyp = oldtype.typ
if isa(oldtypetyp, Conditional) && slot_id(oldtypetyp.var) == changeid
state[i] = VarState(widenconditional(oldtypetyp), oldtype.undef)
end
end
end
# and update the type of it
newtype = change.vtype
oldtype = state[changeid]
if schanged(newtype, oldtype)
state[changeid] = smerge(oldtype, newtype)
return true
end
return false
end
|