Skip to content

Commit caf10d7

Browse files
authored
Implement OpaqueClosure return type narrowing (#39917)
Allows the optimizer to rewrite the return type parameter of the OpaqueClosure based on inference results of the partially specialized (i.e. specialized on the closure environment, but not on the argument types of the opaque closure). This helps by forcing an inference barrier to occur if the PartialOpaque-ness information gets lost, causing a re-infer with at least the rt information we have from inference.
1 parent abde2f1 commit caf10d7

File tree

4 files changed

+40
-4
lines changed

4 files changed

+40
-4
lines changed

base/compiler/abstractinterpretation.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1467,8 +1467,9 @@ function abstract_eval_statement(interp::AbstractInterpreter, @nospecialize(e),
14671467
if isa(t, PartialOpaque)
14681468
# Infer this now so that the specialization is available to
14691469
# optimization.
1470-
abstract_call_opaque_closure(interp, t,
1470+
callinfo = abstract_call_opaque_closure(interp, t,
14711471
most_general_argtypes(t), sv)
1472+
sv.stmt_info[sv.currpc] = OpaqueClosureCreateInfo(callinfo)
14721473
end
14731474
end
14741475
end

base/compiler/ssair/inlining.jl

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1048,6 +1048,32 @@ function inline_invoke!(ir::IRCode, idx::Int, sig::Signature, info::InvokeCallIn
10481048
return nothing
10491049
end
10501050

1051+
function narrow_opaque_closure!(ir::IRCode, stmt::Expr, @nospecialize(info), state::InliningState)
1052+
if isa(info, OpaqueClosureCreateInfo)
1053+
unspec_call_info = info.unspec.info
1054+
if isa(unspec_call_info, ConstCallInfo)
1055+
unspec_call_info = unspec_call_info.call
1056+
end
1057+
isa(unspec_call_info, OpaqueClosureCallInfo) || return
1058+
lbt = argextype(stmt.args[3], ir, ir.sptypes)
1059+
lb, exact = instanceof_tfunc(lbt)
1060+
exact || return
1061+
ubt = argextype(stmt.args[4], ir, ir.sptypes)
1062+
ub, exact = instanceof_tfunc(ubt)
1063+
exact || return
1064+
# Narrow opaque closure type
1065+
1066+
newT = widenconst(tmeet(tmerge(lb, info.unspec.rt), ub))
1067+
if newT != ub
1068+
# N.B.: Narrowing the ub requires a backdge on the mi whose type
1069+
# information we're using, since a change in that function may
1070+
# invalidate ub result.
1071+
push!(state.et, unspec_call_info.mi)
1072+
stmt.args[4] = newT
1073+
end
1074+
end
1075+
end
1076+
10511077
# Handles all analysis and inlining of intrinsics and builtins. In particular,
10521078
# this method does not access the method table or otherwise process generic
10531079
# functions.
@@ -1057,6 +1083,9 @@ function process_simple!(ir::IRCode, todo::Vector{Pair{Int, Any}}, idx::Int, sta
10571083
if stmt.head === :splatnew
10581084
inline_splatnew!(ir, idx)
10591085
return nothing
1086+
elseif stmt.head === :new_opaque_closure
1087+
narrow_opaque_closure!(ir, stmt, ir.stmts[idx][:info], state)
1088+
return nothing
10601089
end
10611090

10621091
stmt.head === :call || return nothing

base/compiler/stmtinfo.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,10 @@ struct OpaqueClosureCallInfo
114114
mi::MethodInstance
115115
end
116116

117+
struct OpaqueClosureCreateInfo
118+
unspec::CallMeta
119+
end
120+
117121
# Stmt infos that are used by external consumers, but not by optimization.
118122
# These are not produced by default and must be explicitly opted into by
119123
# the AbstractInterpreter.

test/opaque_closure.jl

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ let ci = @code_lowered const_int()
2020
Expr(:opaque_closure_method, 0, lno, ci)))
2121
end
2222
end
23-
@test_broken isa(oc_simple_inf(), Core.OpaqueClosure{Tuple{}, Int})
23+
@test isa(oc_simple_inf(), Core.OpaqueClosure{Tuple{}, Int})
2424
@test oc_simple_inf()() == 1
2525

2626
struct OcClos2Int
@@ -71,8 +71,8 @@ let ci = @code_lowered OcClos1Any(1)()
7171
:x))
7272
end
7373
end
74-
@test_broken isa(oc_infer_pass_clos(1), Core.OpaqueClosure{Tuple{}, typeof(1)})
75-
@test_broken isa(oc_infer_pass_clos("a"), Core.OpaqueClosure{Tuple{}, typeof("a")})
74+
@test isa(oc_infer_pass_clos(1), Core.OpaqueClosure{Tuple{}, typeof(1)})
75+
@test isa(oc_infer_pass_clos("a"), Core.OpaqueClosure{Tuple{}, typeof("a")})
7676
@test oc_infer_pass_clos(1)() == 1
7777
@test oc_infer_pass_clos("a")() == "a"
7878

@@ -131,12 +131,14 @@ function test_oc_world_age end
131131
mk_oc_world_age() = @opaque ()->test_oc_world_age()
132132
g_world_age = @opaque ()->test_oc_world_age()
133133
h_world_age = mk_oc_world_age()
134+
@test isa(h_world_age, Core.OpaqueClosure{Tuple{}, Union{}})
134135
test_oc_world_age() = 1
135136
@test_throws MethodError g_world_age()
136137
@test_throws MethodError h_world_age()
137138
@test mk_oc_world_age()() == 1
138139
g_world_age = @opaque ()->test_oc_world_age()
139140
@test g_world_age() == 1
141+
@test isa(mk_oc_world_age(), Core.OpaqueClosure{Tuple{}, Int})
140142

141143
# Evil, dynamic Vararg stuff (don't do this - made to work for consistency)
142144
function maybe_opaque(isva::Bool)

0 commit comments

Comments
 (0)