Skip to content

inference: track reaching defs for slots #55601

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
233 changes: 138 additions & 95 deletions base/compiler/abstractinterpretation.jl

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions base/compiler/inferencestate.jl
Original file line number Diff line number Diff line change
Expand Up @@ -316,10 +316,10 @@ mutable struct InferenceState
for i = 1:nslots
argtyp = (i > nargtypes) ? Bottom : argtypes[i]
if argtyp === Bool && has_conditional(typeinf_lattice(interp))
argtyp = Conditional(i, Const(true), Const(false))
argtyp = Conditional(i, #= ssadef =# 0, Const(true), Const(false))
end
slottypes[i] = argtyp
bb_vartable1[i] = VarState(argtyp, i > nargtypes)
bb_vartable1[i] = VarState(argtyp, #= ssadef =# 0, i > nargtypes)
end
src.ssavaluetypes = ssavaluetypes = Any[ NOT_FOUND for i = 1:nssavalues ]

Expand Down Expand Up @@ -712,7 +712,7 @@ function sptypes_from_meth_instance(mi::MethodInstance)
ty = Const(v)
undef = false
end
sptypes[i] = VarState(ty, undef)
sptypes[i] = VarState(ty, typemin(Int), undef)
end
return sptypes
end
Expand Down
2 changes: 1 addition & 1 deletion base/compiler/optimize.jl
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ function OptimizationState(mi::MethodInstance, src::CodeInfo, interp::AbstractIn
bb_vartables = Union{VarTable,Nothing}[]
for block = 1:length(cfg.blocks)
push!(bb_vartables, VarState[
VarState(slottypes[slot], src.slotflags[slot] & SLOT_USEDUNDEF != 0)
VarState(slottypes[slot], typemin(Int), src.slotflags[slot] & SLOT_USEDUNDEF != 0)
for slot = 1:nslots
])
end
Expand Down
4 changes: 2 additions & 2 deletions base/compiler/ssair/irinterp.jl
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ function abstract_eval_phi_stmt(interp::AbstractInterpreter, phi::PhiNode, ::Int
return abstract_eval_phi(interp, phi, nothing, irsv)
end

function abstract_call(interp::AbstractInterpreter, arginfo::ArgInfo, irsv::IRInterpretationState)
function abstract_call(interp::AbstractInterpreter, arginfo::ArgInfo, vtypes::Union{VarTable,Nothing}, irsv::IRInterpretationState)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
function abstract_call(interp::AbstractInterpreter, arginfo::ArgInfo, vtypes::Union{VarTable,Nothing}, irsv::IRInterpretationState)
function abstract_call(interp::AbstractInterpreter, arginfo::ArgInfo, ::Nothing, irsv::IRInterpretationState)

vtypes is never available for irinterp, so it's better to restrict this signature?

si = StmtInfo(true) # TODO better job here?
call = abstract_call(interp, arginfo, si, irsv)
call = abstract_call(interp, arginfo, si, vtypes, irsv)
irsv.ir.stmts[irsv.curridx][:info] = call.info
return call
end
Expand Down
15 changes: 8 additions & 7 deletions base/compiler/tfuncs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ end

function not_tfunc(𝕃::AbstractLattice, @nospecialize(b))
if isa(b, Conditional)
return Conditional(b.slot, b.elsetype, b.thentype)
return Conditional(b.slot, b.ssadef, b.elsetype, b.thentype)
elseif isa(b, Const)
return Const(not_int(b.val))
end
Expand Down Expand Up @@ -350,14 +350,14 @@ end
if isa(x, Conditional)
y = widenconditional(y)
if isa(y, Const)
y.val === false && return Conditional(x.slot, x.elsetype, x.thentype)
y.val === false && return Conditional(x.slot, x.ssadef, x.elsetype, x.thentype)
y.val === true && return x
return Const(false)
end
elseif isa(y, Conditional)
x = widenconditional(x)
if isa(x, Const)
x.val === false && return Conditional(y.slot, y.elsetype, y.thentype)
x.val === false && return Conditional(y.slot, y.ssadef, y.elsetype, y.thentype)
x.val === true && return y
return Const(false)
end
Expand Down Expand Up @@ -1355,7 +1355,7 @@ end
return Bool
end

@nospecs function abstract_modifyop!(interp::AbstractInterpreter, ff, argtypes::Vector{Any}, si::StmtInfo, sv::AbsIntState)
@nospecs function abstract_modifyop!(interp::AbstractInterpreter, ff, argtypes::Vector{Any}, si::StmtInfo, vtypes::Union{VarTable,Nothing}, sv::AbsIntState)
if ff === modifyfield!
minargs = 5
maxargs = 6
Expand Down Expand Up @@ -1415,7 +1415,7 @@ end
# as well as compute the info for the method matches
op = unwrapva(argtypes[op_argi])
v = unwrapva(argtypes[v_argi])
callinfo = abstract_call(interp, ArgInfo(nothing, Any[op, TF, v]), StmtInfo(true), sv, #=max_methods=#1)
callinfo = abstract_call(interp, ArgInfo(nothing, Any[op, TF, v]), StmtInfo(true), vtypes, sv, #=max_methods=#1)
TF2 = tmeet(callinfo.rt, widenconst(TF))
if TF2 === Bottom
RT = Bottom
Expand Down Expand Up @@ -2931,10 +2931,11 @@ function return_type_tfunc(interp::AbstractInterpreter, argtypes::Vector{Any}, s
if isa(sv, InferenceState)
old_restrict = sv.restrict_abstract_call_sites
sv.restrict_abstract_call_sites = false
call = abstract_call(interp, ArgInfo(nothing, argtypes_vec), si, sv, #=max_methods=#-1)
# TODO: vtypes?
call = abstract_call(interp, ArgInfo(nothing, argtypes_vec), si, nothing, sv, #=max_methods=#-1)
sv.restrict_abstract_call_sites = old_restrict
else
call = abstract_call(interp, ArgInfo(nothing, argtypes_vec), si, sv, #=max_methods=#-1)
call = abstract_call(interp, ArgInfo(nothing, argtypes_vec), si, nothing, sv, #=max_methods=#-1)
end
info = verbose_stmt_info(interp) ? MethodResultPure(ReturnTypeCallInfo(call.info)) : MethodResultPure()
rt = widenslotwrapper(call.rt)
Expand Down
2 changes: 1 addition & 1 deletion base/compiler/typeinfer.jl
Original file line number Diff line number Diff line change
Expand Up @@ -744,7 +744,7 @@ function type_annotate!(interp::AbstractInterpreter, sv::InferenceState)
for slot in 1:nslots
vt = varstate[slot]
widened_type = widenslotwrapper(ignorelimited(vt.typ))
varstate[slot] = VarState(widened_type, vt.undef)
varstate[slot] = VarState(widened_type, vt.ssadef, vt.undef)
end
end
end
Expand Down
90 changes: 60 additions & 30 deletions base/compiler/typelattice.jl
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ the type of `SlotNumber(cnd.slot)` will be limited by `cnd.thentype`
and in the false branch, it will be limited by `cnd.elsetype`.
Example:
```julia
let cond = isa(x::Union{Int, Float}, Int)::Conditional(x, Int, Float)
let cond = isa(x::Union{Int, Float}, Int)::Conditional(x, _, Int, Float)
if cond
# May assume x is `Int` now
else
Expand All @@ -71,21 +71,22 @@ end
"""
struct Conditional
slot::Int
ssadef::Int
thentype
elsetype
# `isdefined` indicates this `Conditional` is from `@isdefined slot`, implying that
# the `undef` information of `slot` can be improved in the then branch.
# Since this is only beneficial for local inference, it is not translated into `InterConditional`.
isdefined::Bool
function Conditional(slot::Int, @nospecialize(thentype), @nospecialize(elsetype);
function Conditional(slot::Int, ssadef::Int, @nospecialize(thentype), @nospecialize(elsetype);
isdefined::Bool=false)
assert_nested_slotwrapper(thentype)
assert_nested_slotwrapper(elsetype)
return new(slot, thentype, elsetype, isdefined)
return new(slot, ssadef, thentype, elsetype, isdefined)
end
end
Conditional(var::SlotNumber, @nospecialize(thentype), @nospecialize(elsetype); isdefined::Bool=false) =
Conditional(slot_id(var), thentype, elsetype; isdefined)
Conditional(var::SlotNumber, ssadef::Int, @nospecialize(thentype), @nospecialize(elsetype); isdefined::Bool=false) =
Conditional(slot_id(var), ssadef, thentype, elsetype; isdefined)

import Core: InterConditional
"""
Expand All @@ -105,8 +106,10 @@ InterConditional(var::SlotNumber, @nospecialize(thentype), @nospecialize(elsetyp
InterConditional(slot_id(var), thentype, elsetype)

const AnyConditional = Union{Conditional,InterConditional}
Conditional(cnd::InterConditional) = Conditional(cnd.slot, cnd.thentype, cnd.elsetype)
InterConditional(cnd::Conditional) = InterConditional(cnd.slot, cnd.thentype, cnd.elsetype)
function InterConditional(cnd::Conditional)
@assert cnd.ssadef == 0
InterConditional(cnd.slot, cnd.thentype, cnd.elsetype)
end

"""
alias::MustAlias
Expand All @@ -133,19 +136,20 @@ N.B. currently this lattice element is only used in abstractinterpret, not in op
"""
struct MustAlias
slot::Int
ssadef::Int
vartyp::Any
fldidx::Int
fldtyp::Any
function MustAlias(slot::Int, @nospecialize(vartyp), fldidx::Int, @nospecialize(fldtyp))
function MustAlias(slot::Int, ssadef::Int, @nospecialize(vartyp), fldidx::Int, @nospecialize(fldtyp))
assert_nested_slotwrapper(vartyp)
assert_nested_slotwrapper(fldtyp)
# @assert !isalreadyconst(vartyp) "vartyp is already const"
# @assert !isalreadyconst(fldtyp) "fldtyp is already const"
return new(slot, vartyp, fldidx, fldtyp)
return new(slot, ssadef, vartyp, fldidx, fldtyp)
end
end
MustAlias(var::SlotNumber, @nospecialize(vartyp), fldidx::Int, @nospecialize(fldtyp)) =
MustAlias(slot_id(var), vartyp, fldidx, fldtyp)
MustAlias(var::SlotNumber, ssadef::Int, @nospecialize(vartyp), fldidx::Int, @nospecialize(fldtyp)) =
MustAlias(slot_id(var), ssadef, vartyp, fldidx, fldtyp)

"""
alias::InterMustAlias
Expand All @@ -169,8 +173,10 @@ InterMustAlias(var::SlotNumber, @nospecialize(vartyp), fldidx::Int, @nospecializ
InterMustAlias(slot_id(var), vartyp, fldidx, fldtyp)

const AnyMustAlias = Union{MustAlias,InterMustAlias}
MustAlias(alias::InterMustAlias) = MustAlias(alias.slot, alias.vartyp, alias.fldidx, alias.fldtyp)
InterMustAlias(alias::MustAlias) = InterMustAlias(alias.slot, alias.vartyp, alias.fldidx, alias.fldtyp)
function InterMustAlias(alias::MustAlias)
@assert alias.ssadef == 0
InterMustAlias(alias.slot, alias.vartyp, alias.fldidx, alias.fldtyp)
end

struct PartialTypeVar
tv::TypeVar
Expand All @@ -184,8 +190,20 @@ end
struct StateUpdate
var::SlotNumber
vtype::VarState
conditional::Bool
StateUpdate(var::SlotNumber, vtype::VarState, conditional::Bool=false) = new(var, vtype, conditional)
end

"""
Similar to `StateUpdate`, except with the additional guarantee that object identity
is preserved by the update (i.e. `x (before) === x (after)`).
"""
struct StateRefinement
slot::Int
# XXX: This should be an intersection of the old type with the new
# (i.e. newtyp ⊑ oldtyp)
newtyp
undef::Bool

StateRefinement(slot::Int, @nospecialize(newtyp), undef::Bool) = new(slot, newtyp, undef)
end

"""
Expand Down Expand Up @@ -328,6 +346,7 @@ end
return false
end

is_same_conditionals(a::Conditional, b::Conditional) = a.slot == b.slot && a.ssadef == b.ssadef
is_same_conditionals(a::C, b::C) where C<:AnyConditional = a.slot == b.slot

@nospecializeinfer is_lattice_bool(lattice::AbstractLattice, @nospecialize(typ)) = typ !== Bottom && ⊑(lattice, typ, Bool)
Expand Down Expand Up @@ -376,7 +395,7 @@ end
end

@nospecializeinfer function form_mustalias_conditional(alias::MustAlias, @nospecialize(thentype), @nospecialize(elsetype))
(; slot, vartyp, fldidx) = alias
(; slot, ssadef, vartyp, fldidx) = alias
if isa(vartyp, PartialStruct)
fields = vartyp.fields
thenfields = thentype === Bottom ? nothing : copy(fields)
Expand All @@ -387,7 +406,7 @@ end
elsefields === nothing || (elsefields[i] = elsetype)
end
end
return Conditional(slot,
return Conditional(slot, ssadef,
thenfields === nothing ? Bottom : PartialStruct(vartyp.typ, thenfields),
elsefields === nothing ? Bottom : PartialStruct(vartyp.typ, elsefields))
else
Expand All @@ -404,7 +423,7 @@ end
elsefields === nothing || push!(elsefields, t)
end
end
return Conditional(slot,
return Conditional(slot, ssadef,
thenfields === nothing ? Bottom : PartialStruct(vartyp_widened, thenfields),
elsefields === nothing ? Bottom : PartialStruct(vartyp_widened, elsefields))
end
Expand Down Expand Up @@ -745,34 +764,39 @@ widenconst(::LimitedAccuracy) = error("unhandled LimitedAccuracy")
# state management #
####################

function smerge(lattice::AbstractLattice, sa::Union{NotFound,VarState}, sb::Union{NotFound,VarState})
function smerge(lattice::AbstractLattice, sa::Union{NotFound,VarState}, sb::Union{NotFound,VarState}, join_pc::Int)
sa === sb && return sa
sa === NOT_FOUND && return sb
sb === NOT_FOUND && return sa
return VarState(tmerge(lattice, sa.typ, sb.typ), sa.undef | sb.undef)
return VarState(tmerge(lattice, sa.typ, sb.typ), sa.ssadef == sb.ssadef ? sa.ssadef : join_pc, sa.undef | sb.undef)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the core of the reaching def computation

It might be surprising that this doesn't use dominance, etc. but that's because dominance-based algorithms are optimized versions of the "path-convergence criterion" we solve here (both algorithms compute the same thing). Since we're solving the dataflow equations in this way for inference already, the SSA information comes mostly for free

end

@nospecializeinfer @inline schanged(lattice::AbstractLattice, @nospecialize(n), @nospecialize(o)) =
(n !== o) && (o === NOT_FOUND || (n !== NOT_FOUND && !(n.undef <= o.undef && ⊑(lattice, n.typ, o.typ))))
@nospecializeinfer @inline schanged(lattice::AbstractLattice, @nospecialize(n), @nospecialize(o), join_pc::Int) =
(n !== o) && (o === NOT_FOUND || (n !== NOT_FOUND && !(n.undef <= o.undef && (n.ssadef == o.ssadef || o.ssadef == join_pc) && ⊑(lattice, n.typ, o.typ))))

# remove any lattice elements that wrap the reassigned slot object from the vartable
function invalidate_slotwrapper(vt::VarState, changeid::Int, ignore_conditional::Bool)
function invalidate_slotwrapper(vt::VarState, changeid::Int)
newtyp = ignorelimited(vt.typ)
if (!ignore_conditional && isa(newtyp, Conditional) && newtyp.slot == changeid) ||
(isa(newtyp, MustAlias) && newtyp.slot == changeid)
if ((isa(newtyp, Conditional) && newtyp.slot == changeid) ||
(isa(newtyp, MustAlias) && newtyp.slot == changeid))
newtyp = @noinline widenwrappedslotwrapper(vt.typ)
return VarState(newtyp, vt.undef)
return VarState(newtyp, vt.ssadef, vt.undef)
end
return nothing
end

function stupdate!(lattice::AbstractLattice, state::VarTable, changes::VarTable)
function stupdate!(lattice::AbstractLattice, state::VarTable, changes::VarTable, join_pc::Int)
changed = false
for i = 1:length(state)
newtype = changes[i]
oldtype = state[i]
if schanged(lattice, newtype, oldtype)
state[i] = smerge(lattice, oldtype, newtype)
# In addition to computing the type, the merge here computes the "reaching definition"
# for a slot. The provided `join_pc` is a "virtual" PC, which corresponds to the ϕ-block
# that would exist at the beginning of the BasicBlock.
#
# This effectively applies the "path-convergence criterion" for SSA construction.
if schanged(lattice, newtype, oldtype, join_pc)
state[i] = smerge(lattice, oldtype, newtype, join_pc)
changed = true
end
end
Expand All @@ -789,7 +813,7 @@ end
function stoverwrite1!(state::VarTable, change::StateUpdate)
changeid = slot_id(change.var)
for i = 1:length(state)
invalidated = invalidate_slotwrapper(state[i], changeid, change.conditional)
invalidated = invalidate_slotwrapper(state[i], changeid)
if invalidated !== nothing
state[i] = invalidated
end
Expand All @@ -799,3 +823,9 @@ function stoverwrite1!(state::VarTable, change::StateUpdate)
state[changeid] = newtype
return state
end

function strefine1!(state::VarTable, refinement::StateRefinement)
(; newtyp, undef, slot) = refinement
state[slot] = VarState(newtyp, state[slot].ssadef, undef)
return state
end
10 changes: 5 additions & 5 deletions base/compiler/typelimits.jl
Original file line number Diff line number Diff line change
Expand Up @@ -494,24 +494,24 @@ end
# type-lattice for Conditional wrapper (NOTE never be merged with InterConditional)
if isa(typea, Conditional) && isa(typeb, Const)
if typeb.val === true
typeb = Conditional(typea.slot, Any, Union{})
typeb = Conditional(typea.slot, typea.ssadef, Any, Union{})
elseif typeb.val === false
typeb = Conditional(typea.slot, Union{}, Any)
typeb = Conditional(typea.slot, typea.ssadef, Union{}, Any)
end
end
if isa(typeb, Conditional) && isa(typea, Const)
if typea.val === true
typea = Conditional(typeb.slot, Any, Union{})
typea = Conditional(typeb.slot, typeb.ssadef, Any, Union{})
elseif typea.val === false
typea = Conditional(typeb.slot, Union{}, Any)
typea = Conditional(typeb.slot, typeb.ssadef, Union{}, Any)
end
end
if isa(typea, Conditional) && isa(typeb, Conditional)
if is_same_conditionals(typea, typeb)
thentype = tmerge(widenlattice(lattice), typea.thentype, typeb.thentype)
elsetype = tmerge(widenlattice(lattice), typea.elsetype, typeb.elsetype)
if thentype !== elsetype
return Conditional(typea.slot, thentype, elsetype)
return Conditional(typea.slot, typea.ssadef, thentype, elsetype)
end
end
val = maybe_extract_const_bool(typea)
Expand Down
8 changes: 7 additions & 1 deletion base/compiler/types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,20 @@ MethodInfo(src::CodeInfo) = MethodInfo(
A special wrapper that represents a local variable of a method being analyzed.
This does not participate in the native type system nor the inference lattice, and it thus
should be always unwrapped to `v.typ` when performing any type or lattice operations on it.
`v.undef` represents undefined-ness of this static parameter. If `true`, it means that the
variable _may_ be undefined at runtime, otherwise it is guaranteed to be defined.
If `v.typ === Bottom` it means that the variable is strictly undefined.
`v.ssadef` represents the "reaching definition" for the variable. If negative, this refers
to a "virtual ϕ-block" preceding the given index. If a slot has the same `ssadef` at two
Comment on lines +55 to +56
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does "virtual φ-block" mean? I still don't quite grasp its meaning.
Also, it might be good to mention the case when ssadef == 0, meaning it was passed as an argument IIUC.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "virtual ϕ-block" is just the usual block of ϕ-nodes that in proper SSA form would appear at the beginning of the BasicBlock. We're not actually inserting / re-indexing instructions though, so instead you get a negative index at the insertion point where the ϕ "would" go.

Also, it might be good to mention the case when ssadef == 0, meaning it was passed as an argument IIUC.

Yeah, great point - will do

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, well, is the use of the "virtual φ block" the same as the meaning of -pc passed to stupdate! in abstractinterpretation.jl? I might be misunderstanding, but my intuition is that negative ssadef means something like "we can't track the def of this slot." If that interpretation feels more natural, then maybe it would be better to represent it as ssadef::Union{Nothing,UInt}.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the same meaning as in stupdate!, but it doesn't mean "we can't track the def"

An ssadef of -pc means "If this code were in SSA form, a use of this slot would be replaced with a use of a ϕ-node." Which ϕ-node? The one that would have been inserted at pc

That's important because the rule given above:

If the reaching def is equal at two program points, then the slot contents are guaranteed to be egal (i.e. x₀ === x₁)

Also works for -pc ssadefs

different points of execution, the slot contents are guaranteed to share identity (`x₀ === x₁`).
"""
struct VarState
typ
ssadef::Int
undef::Bool
VarState(@nospecialize(typ), undef::Bool) = new(typ, undef)
VarState(@nospecialize(typ), ssadef::Int, undef::Bool) = new(typ, ssadef, undef)
end

struct AnalysisResults
Expand Down
2 changes: 1 addition & 1 deletion base/reflection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2246,7 +2246,7 @@ function print_statement_costs(io::IO, @nospecialize(tt::Type);
else
empty!(cst)
resize!(cst, length(code.code))
sptypes = Core.Compiler.VarState[Core.Compiler.VarState(sp, false) for sp in match.sparams]
sptypes = Core.Compiler.VarState[Core.Compiler.VarState(sp, #= ssadef =# typemin(Int), false) for sp in match.sparams]
maxcost = Core.Compiler.statement_costs!(cst, code.code, code, sptypes, params)
nd = ndigits(maxcost)
irshow_config = IRShow.IRShowConfig() do io, linestart, idx
Expand Down
6 changes: 3 additions & 3 deletions test/compiler/AbstractInterpreter.jl
Original file line number Diff line number Diff line change
Expand Up @@ -410,10 +410,10 @@ CC.nsplit_impl(info::NoinlineCallInfo) = CC.nsplit(info.info)
CC.getsplit_impl(info::NoinlineCallInfo, idx::Int) = CC.getsplit(info.info, idx)
CC.getresult_impl(info::NoinlineCallInfo, idx::Int) = CC.getresult(info.info, idx)

function CC.abstract_call(interp::NoinlineInterpreter,
arginfo::CC.ArgInfo, si::CC.StmtInfo, sv::CC.InferenceState, max_methods::Int)
function CC.abstract_call(interp::NoinlineInterpreter, arginfo::CC.ArgInfo, si::CC.StmtInfo,
vtypes::Union{CC.VarTable,Nothing}, sv::CC.InferenceState, max_methods::Int)
ret = @invoke CC.abstract_call(interp::CC.AbstractInterpreter,
arginfo::CC.ArgInfo, si::CC.StmtInfo, sv::CC.InferenceState, max_methods::Int)
arginfo::CC.ArgInfo, si::CC.StmtInfo, vtypes::Union{CC.VarTable,Nothing}, sv::CC.InferenceState, max_methods::Int)
if sv.mod in noinline_modules(interp)
return CC.CallMeta(ret.rt, ret.exct, ret.effects, NoinlineCallInfo(ret.info))
end
Expand Down
Loading