-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
optimizer: supports callsite annotations of inlining, fixes #18773 #41328
Changes from all commits
9c35a04
1c7482f
addd655
1e1378f
99cbff6
382a80c
4ecf149
490dda0
783d32d
322291b
3805d86
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,23 +21,28 @@ function push!(et::EdgeTracker, ci::CodeInstance) | |
push!(et, ci.def) | ||
end | ||
|
||
struct InliningState{S <: Union{EdgeTracker, Nothing}, T, P} | ||
struct InliningState{S <: Union{EdgeTracker, Nothing}, T, I<:AbstractInterpreter} | ||
params::OptimizationParams | ||
et::S | ||
mi_cache::T | ||
policy::P | ||
interp::I | ||
end | ||
|
||
function default_inlining_policy(@nospecialize(src)) | ||
function inlining_policy(interp::AbstractInterpreter, @nospecialize(src), stmt_flag::UInt8) | ||
if isa(src, CodeInfo) || isa(src, Vector{UInt8}) | ||
src_inferred = ccall(:jl_ir_flag_inferred, Bool, (Any,), src) | ||
src_inlineable = ccall(:jl_ir_flag_inlineable, Bool, (Any,), src) | ||
src_inlineable = is_stmt_inline(stmt_flag) || ccall(:jl_ir_flag_inlineable, Bool, (Any,), src) | ||
return src_inferred && src_inlineable ? src : nothing | ||
elseif isa(src, OptimizationState) && isdefined(src, :ir) | ||
return (is_stmt_inline(stmt_flag) || src.src.inlineable) ? src.ir : nothing | ||
else | ||
# maybe we want to make inference keep the source in a local cache if a statement is going to inlined | ||
# and re-optimize it here with disabling further inlining to avoid infinite optimization loop | ||
# (we can even naively try to re-infer it entirely) | ||
# but it seems like that "single-level-inlining" is more trouble and complex than it's worth | ||
# see https://github.com/JuliaLang/julia/pull/41328/commits/0fc0f71a42b8c9d04b0dafabf3f1f17703abf2e7 | ||
return nothing | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I realized that although it feels like this will make the optimization unstable and unpredictable, we actually already hit this problem in precompile/codegen/optimization for existing code. So we are not introducing a new problem here. Perhaps making the current situation worse in this case, but feels probably acceptable, since it may need to be fixed eventually anyways. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I want to fix this in another PR if I (or someone) find any value in it. For the meanwhile, I think it might be appropriated to leave this limitation in docstring ?
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Dropping a real compiler warning in case of failure isn't a possibility? (For call site inline annotations I find "force" semantics more intuitive than "wish". And when forcing something impossible, better to know it failed.) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's possible, but I'm afraid to spam with such warnings (they're really not interest from user's point of view, I think): module Pkg
recursivef(...) = ...
function userfacingf(...)
@inline recursivef(...)
end
export userfacingf
end
userfacingf(...) # we want warning ... ? We can have it as opt-in feature, but that could be addressed in another PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The warning would be printed during module development, so the module developer can fix it before releasing the code. I find the warning appropriate in your example. It is possible to imagine more complex examples, e.g. callback-like scenarios, but if the module developer thinks inlining of user-specified functions is mandantory even against the policy, then it seems ok for them to have their users warned that they should not provide recursive callbacks. I think the root problem is that now the macro has different semantics based on where it is used. A solution may be to rename this to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, let's leave this to another PR. So I don't want this discussion to hold this PR. |
||
end | ||
if isa(src, OptimizationState) && isdefined(src, :ir) | ||
return src.src.inlineable ? src.ir : nothing | ||
end | ||
return nothing | ||
end | ||
|
||
include("compiler/ssair/driver.jl") | ||
|
@@ -57,7 +62,7 @@ mutable struct OptimizationState | |
inlining = InliningState(params, | ||
EdgeTracker(s_edges, frame.valid_worlds), | ||
WorldView(code_cache(interp), frame.world), | ||
inlining_policy(interp)) | ||
interp) | ||
return new(frame.linfo, | ||
frame.src, nothing, frame.stmt_info, frame.mod, | ||
frame.sptypes, frame.slottypes, false, | ||
|
@@ -86,7 +91,7 @@ mutable struct OptimizationState | |
inlining = InliningState(params, | ||
nothing, | ||
WorldView(code_cache(interp), get_world_counter()), | ||
inlining_policy(interp)) | ||
interp) | ||
return new(linfo, | ||
src, nothing, stmt_info, mod, | ||
sptypes_from_meth_instance(linfo), slottypes, false, | ||
|
@@ -125,9 +130,15 @@ const SLOT_ASSIGNEDONCE = 16 # slot is assigned to only once | |
const SLOT_USEDUNDEF = 32 # slot has uses that might raise UndefVarError | ||
# const SLOT_CALLED = 64 | ||
|
||
# This statement was marked as @inbounds by the user. If replaced by inlining, | ||
# any contained boundschecks may be removed | ||
const IR_FLAG_INBOUNDS = 0x01 | ||
# NOTE make sure to sync the flag definitions below with julia.h and `jl_code_info_set_ir` in method.c | ||
|
||
# This statement is marked as @inbounds by user. | ||
# Ff replaced by inlining, any contained boundschecks may be removed. | ||
const IR_FLAG_INBOUNDS = 0x01 << 0 | ||
# This statement is marked as @inline by user | ||
const IR_FLAG_INLINE = 0x01 << 1 | ||
# This statement is marked as @noinline by user | ||
const IR_FLAG_NOINLINE = 0x01 << 2 | ||
# This statement may be removed if its result is unused. In particular it must | ||
# thus be both pure and effect free. | ||
const IR_FLAG_EFFECT_FREE = 0x01 << 4 | ||
|
@@ -173,6 +184,9 @@ function isinlineable(m::Method, me::OptimizationState, params::OptimizationPara | |
return inlineable | ||
end | ||
|
||
is_stmt_inline(stmt_flag::UInt8) = stmt_flag & IR_FLAG_INLINE != 0 | ||
is_stmt_noinline(stmt_flag::UInt8) = stmt_flag & IR_FLAG_NOINLINE != 0 | ||
|
||
# These affect control flow within the function (so may not be removed | ||
# if there is no usage within the function), but don't affect the purity | ||
# of the function as a whole. | ||
|
@@ -358,42 +372,22 @@ function convert_to_ircode(ci::CodeInfo, code::Vector{Any}, coverage::Bool, sv:: | |
end | ||
renumber_ir_elements!(code, changemap, labelmap) | ||
|
||
inbounds_depth = 0 # Number of stacked inbounds | ||
meta = Any[] | ||
flags = fill(0x00, length(code)) | ||
for i = 1:length(code) | ||
stmt = code[i] | ||
if isexpr(stmt, :inbounds) | ||
arg1 = stmt.args[1] | ||
if arg1 === true # push | ||
inbounds_depth += 1 | ||
elseif arg1 === false # clear | ||
inbounds_depth = 0 | ||
elseif inbounds_depth > 0 # pop | ||
inbounds_depth -= 1 | ||
end | ||
stmt = nothing | ||
else | ||
stmt = normalize(stmt, meta) | ||
end | ||
code[i] = stmt | ||
if !(stmt === nothing) | ||
if inbounds_depth > 0 | ||
flags[i] |= IR_FLAG_INBOUNDS | ||
end | ||
end | ||
code[i] = remove_meta!(code[i], meta) | ||
end | ||
aviatesk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
strip_trailing_junk!(ci, code, stmtinfo, flags) | ||
strip_trailing_junk!(ci, code, stmtinfo) | ||
cfg = compute_basic_blocks(code) | ||
types = Any[] | ||
stmts = InstructionStream(code, types, stmtinfo, ci.codelocs, flags) | ||
stmts = InstructionStream(code, types, stmtinfo, ci.codelocs, ci.ssaflags) | ||
ir = IRCode(stmts, cfg, collect(LineInfoNode, ci.linetable::Union{Vector{LineInfoNode},Vector{Any}}), sv.slottypes, meta, sv.sptypes) | ||
return ir | ||
end | ||
|
||
function normalize(@nospecialize(stmt), meta::Vector{Any}) | ||
function remove_meta!(@nospecialize(stmt), meta::Vector{Any}) | ||
if isa(stmt, Expr) | ||
if stmt.head === :meta | ||
head = stmt.head | ||
if head === :meta | ||
args = stmt.args | ||
if length(args) > 0 | ||
push!(meta, stmt) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we need to check that src is inferred and optimized here before using it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually
NativeInterpreter
won't hit this pass, becausefinish!
transformsOptimizationState
intoCodeInfo
.AFAIU this is basically external interpreter stuff, and I don't think we need more checks here because
(opt::OptimizationState).ir
is only introduced when inference is successful and the source is optimized ?@Keno might have another idea on this though.