Skip to content

Commit

Permalink
Relax constraints on inlining for some single calls
Browse files Browse the repository at this point in the history
  • Loading branch information
Ian Atol committed Nov 17, 2021
1 parent bc8337a commit 28dc0eb
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 3 deletions.
20 changes: 17 additions & 3 deletions base/compiler/ssair/inlining.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1169,9 +1169,21 @@ function analyze_single_call!(
end
end

# if the signature is fully covered and there is only one applicable method,
local mostly_covered = true
if typeof(signature_union) == DataType && typeof(atype) == DataType
sig_union_ts = datatype_fieldtypes(signature_union)
atype_ts = datatype_fieldtypes(atype)
for (idx, atype_t) in enumerate(atype_ts)
sigu_t = sig_union_ts[idx]
sub = (atype_t <: sigu_t) || atype_t == Any
mostly_covered = mostly_covered && sub
end
end
mostly_covered = mostly_covered && !(atype <: signature_union)

# if the signature is fully or mostly covered and there is only one applicable method,
# we can try to inline it even if the signature is not a dispatch tuple
if atype <: signature_union
if atype <: signature_union || mostly_covered
if length(cases) == 0 && only_method isa Method
if length(infos) > 1
(metharg, methsp) = ccall(:jl_type_intersection_with_env, Any, (Any, Any),
Expand All @@ -1185,7 +1197,9 @@ function analyze_single_call!(
item = analyze_method!(match, argtypes, state, flag)
item === nothing && return
push!(cases, InliningCase(match.spec_types, item))
fully_covered = true
if !mostly_covered
fully_covered = true
end
end
else
fully_covered = false
Expand Down
47 changes: 47 additions & 0 deletions test/compiler/inline.jl
Original file line number Diff line number Diff line change
Expand Up @@ -817,3 +817,50 @@ let
invoke(xs) = validate_unionsplit_inlining(true, xs[1])
@test invoke(Any[10]) === false
end

# issue 43104
import Core.Compiler: argextype, singleton_type
const EMPTY_SPTYPES = Core.Compiler.EMPTY_SLOTTYPES

# check if `x` is a dynamic call of a given function
iscall(y) = @nospecialize(x) -> iscall(y, x)
function iscall((src, f)::Tuple{Core.CodeInfo,Function}, @nospecialize(x))
return iscall(x) do @nospecialize x
singleton_type(argextype(x, src, EMPTY_SPTYPES)) === f
end
end
iscall(pred::Function, @nospecialize(x)) = Meta.isexpr(x, :call) && pred(x.args[1])

# check if `x` is a statically-resolved call of a function whose name is `sym`
isinvoke(y) = @nospecialize(x) -> isinvoke(y, x)
isinvoke(sym::Symbol, @nospecialize(x)) = isinvoke(mi->mi.def.name===sym, x)
isinvoke(pred::Function, @nospecialize(x)) = Meta.isexpr(x, :invoke) && pred(x.args[1]::Core.MethodInstance)

# NOTE we actually don't need any `@nospecialize` annotation here
@inline isGoodType(@nospecialize x::Type) =
x !== Any && !(@noinline Base.has_free_typevars(x))
let # aggressive inlining of single, abstract method match
src = code_typed((Type, Any,)) do x, y
isGoodType(x), isGoodType(y)
end |> only |> first
# both callsite should be inlined
@test count(isinvoke(:has_free_typevars), src.code) == 2
# `isGoodType(y::Any)` isn't fully convered, thus the inlining should be via runtime type check
@test count(iscall((src,isGoodType)), src.code) == 1
end

@noinline function checkBadType!(@nospecialize x::Type)
if x === Any || Base.has_free_typevars(x)
println(x)
end
return nothing
end
let # aggressive inlining of single, abstract method match
src = code_typed((Type, Any,)) do x, y
checkBadType!(x), checkBadType!(y)
end |> only |> first
# both callsite should be resolved statically
@test count(isinvoke(:checkBadType!), src.code) == 2
# `checkBadType!(y::Any)` isn't fully convered, thus the static dispatch should be via runtime type check
@test count(iscall((src,checkBadType!)), src.code) == 1
end

0 comments on commit 28dc0eb

Please sign in to comment.