Skip to content

Commit b7d4aeb

Browse files
vtjnashaviatesk
andcommitted
compiler: apply more accurate effects to return_type_tfunc (#55338)
In extreme cases, the compiler could mark this function for concrete-eval, even though that is illegal unless the compiler has first deleted this instruction. Otherwise the attempt to concrete-eval will re-run the function repeatedly until it hits a StackOverflow. Workaround to fix #55147 @aviatesk You might know how to solve this even better, using post-optimization effect refinements? Since we should actually only apply the refinement of terminates=false => terminates=true (and thus allowing concrete eval) if the optimization occurs, and not just in inference thinks the optimization would be legal. --------- Co-authored-by: Shuhei Kadowaki <[email protected]>
1 parent 894454c commit b7d4aeb

File tree

13 files changed

+142
-56
lines changed

13 files changed

+142
-56
lines changed

base/boot.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,8 @@ macro _foldable_meta()
283283
#=:inaccessiblememonly=#true,
284284
#=:noub=#true,
285285
#=:noub_if_noinbounds=#false,
286-
#=:consistent_overlay=#false))
286+
#=:consistent_overlay=#false,
287+
#=:nortcall=#true))
287288
end
288289

289290
macro inline() Expr(:meta, :inline) end

base/cmd.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,7 @@ function cmd_gen(parsed)
482482
end
483483
end
484484

485-
@assume_effects :effect_free :terminates_globally :noub function cmd_gen(
485+
@assume_effects :foldable !:consistent function cmd_gen(
486486
parsed::Tuple{Vararg{Tuple{Vararg{Union{String, SubString{String}}}}}}
487487
)
488488
return @invoke cmd_gen(parsed::Any)

base/compiler/abstractinterpretation.jl

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -897,7 +897,7 @@ function concrete_eval_eligible(interp::AbstractInterpreter,
897897
end
898898
end
899899
mi = result.edge
900-
if mi !== nothing && is_foldable(effects)
900+
if mi !== nothing && is_foldable(effects, #=check_rtcall=#true)
901901
if f !== nothing && is_all_const_arg(arginfo, #=start=#2)
902902
if (is_nonoverlayed(interp) || is_nonoverlayed(effects) ||
903903
# Even if overlay methods are involved, when `:consistent_overlay` is
@@ -2788,8 +2788,9 @@ function override_effects(effects::Effects, override::EffectsOverride)
27882788
notaskstate = override.notaskstate ? true : effects.notaskstate,
27892789
inaccessiblememonly = override.inaccessiblememonly ? ALWAYS_TRUE : effects.inaccessiblememonly,
27902790
noub = override.noub ? ALWAYS_TRUE :
2791-
(override.noub_if_noinbounds && effects.noub !== ALWAYS_TRUE) ? NOUB_IF_NOINBOUNDS :
2792-
effects.noub)
2791+
(override.noub_if_noinbounds && effects.noub !== ALWAYS_TRUE) ? NOUB_IF_NOINBOUNDS :
2792+
effects.noub,
2793+
nortcall = override.nortcall ? true : effects.nortcall)
27932794
end
27942795

27952796
isdefined_globalref(g::GlobalRef) = !iszero(ccall(:jl_globalref_boundp, Cint, (Any,), g))

base/compiler/compiler.jl

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,11 @@ struct EffectsOverride
4848
noub::Bool
4949
noub_if_noinbounds::Bool
5050
consistent_overlay::Bool
51+
nortcall::Bool
5152
end
5253
function EffectsOverride(
5354
override::EffectsOverride =
54-
EffectsOverride(false, false, false, false, false, false, false, false, false, false);
55+
EffectsOverride(false, false, false, false, false, false, false, false, false, false, false);
5556
consistent::Bool = override.consistent,
5657
effect_free::Bool = override.effect_free,
5758
nothrow::Bool = override.nothrow,
@@ -61,7 +62,8 @@ function EffectsOverride(
6162
inaccessiblememonly::Bool = override.inaccessiblememonly,
6263
noub::Bool = override.noub,
6364
noub_if_noinbounds::Bool = override.noub_if_noinbounds,
64-
consistent_overlay::Bool = override.consistent_overlay)
65+
consistent_overlay::Bool = override.consistent_overlay,
66+
nortcall::Bool = override.nortcall)
6567
return EffectsOverride(
6668
consistent,
6769
effect_free,
@@ -72,9 +74,10 @@ function EffectsOverride(
7274
inaccessiblememonly,
7375
noub,
7476
noub_if_noinbounds,
75-
consistent_overlay)
77+
consistent_overlay,
78+
nortcall)
7679
end
77-
const NUM_EFFECTS_OVERRIDES = 10 # sync with julia.h
80+
const NUM_EFFECTS_OVERRIDES = 11 # sync with julia.h
7881

7982
# essential files and libraries
8083
include("essentials.jl")

base/compiler/effects.jl

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ following meanings:
5858
methods are `:consistent` with their non-overlayed original counterparts
5959
(see [`Base.@assume_effects`](@ref) for the exact definition of `:consistenct`-cy).
6060
* `ALWAYS_FALSE`: this method may invoke overlayed methods.
61+
- `nortcall::Bool`: this method does not call `Core.Compiler.return_type`,
62+
and it is guaranteed that any other methods this method might call also do not call
63+
`Core.Compiler.return_type`.
6164
6265
Note that the representations above are just internal implementation details and thus likely
6366
to change in the future. See [`Base.@assume_effects`](@ref) for more detailed explanation
@@ -103,6 +106,9 @@ The output represents the state of different effect properties in the following
103106
- `+o` (green): `ALWAYS_TRUE`
104107
- `-o` (red): `ALWAYS_FALSE`
105108
- `?o` (yellow): `CONSISTENT_OVERLAY`
109+
9. `:nortcall` (`r`):
110+
- `+r` (green): `true`
111+
- `-r` (red): `false`
106112
"""
107113
struct Effects
108114
consistent::UInt8
@@ -113,6 +119,7 @@ struct Effects
113119
inaccessiblememonly::UInt8
114120
noub::UInt8
115121
nonoverlayed::UInt8
122+
nortcall::Bool
116123
function Effects(
117124
consistent::UInt8,
118125
effect_free::UInt8,
@@ -121,7 +128,8 @@ struct Effects
121128
notaskstate::Bool,
122129
inaccessiblememonly::UInt8,
123130
noub::UInt8,
124-
nonoverlayed::UInt8)
131+
nonoverlayed::UInt8,
132+
nortcall::Bool)
125133
return new(
126134
consistent,
127135
effect_free,
@@ -130,7 +138,8 @@ struct Effects
130138
notaskstate,
131139
inaccessiblememonly,
132140
noub,
133-
nonoverlayed)
141+
nonoverlayed,
142+
nortcall)
134143
end
135144
end
136145

@@ -160,10 +169,10 @@ const NOUB_IF_NOINBOUNDS = 0x01 << 1
160169
# :nonoverlayed bits
161170
const CONSISTENT_OVERLAY = 0x01 << 1
162171

163-
const EFFECTS_TOTAL = Effects(ALWAYS_TRUE, ALWAYS_TRUE, true, true, true, ALWAYS_TRUE, ALWAYS_TRUE, ALWAYS_TRUE)
164-
const EFFECTS_THROWS = Effects(ALWAYS_TRUE, ALWAYS_TRUE, false, true, true, ALWAYS_TRUE, ALWAYS_TRUE, ALWAYS_TRUE)
165-
const EFFECTS_UNKNOWN = Effects(ALWAYS_FALSE, ALWAYS_FALSE, false, false, false, ALWAYS_FALSE, ALWAYS_FALSE, ALWAYS_TRUE) # unknown mostly, but it's not overlayed at least (e.g. it's not a call)
166-
const _EFFECTS_UNKNOWN = Effects(ALWAYS_FALSE, ALWAYS_FALSE, false, false, false, ALWAYS_FALSE, ALWAYS_FALSE, ALWAYS_FALSE) # unknown really
172+
const EFFECTS_TOTAL = Effects(ALWAYS_TRUE, ALWAYS_TRUE, true, true, true, ALWAYS_TRUE, ALWAYS_TRUE, ALWAYS_TRUE, true)
173+
const EFFECTS_THROWS = Effects(ALWAYS_TRUE, ALWAYS_TRUE, false, true, true, ALWAYS_TRUE, ALWAYS_TRUE, ALWAYS_TRUE, true)
174+
const EFFECTS_UNKNOWN = Effects(ALWAYS_FALSE, ALWAYS_FALSE, false, false, false, ALWAYS_FALSE, ALWAYS_FALSE, ALWAYS_TRUE, false) # unknown mostly, but it's not overlayed at least (e.g. it's not a call)
175+
const _EFFECTS_UNKNOWN = Effects(ALWAYS_FALSE, ALWAYS_FALSE, false, false, false, ALWAYS_FALSE, ALWAYS_FALSE, ALWAYS_FALSE, false) # unknown really
167176

168177
function Effects(effects::Effects = _EFFECTS_UNKNOWN;
169178
consistent::UInt8 = effects.consistent,
@@ -173,7 +182,8 @@ function Effects(effects::Effects = _EFFECTS_UNKNOWN;
173182
notaskstate::Bool = effects.notaskstate,
174183
inaccessiblememonly::UInt8 = effects.inaccessiblememonly,
175184
noub::UInt8 = effects.noub,
176-
nonoverlayed::UInt8 = effects.nonoverlayed)
185+
nonoverlayed::UInt8 = effects.nonoverlayed,
186+
nortcall::Bool = effects.nortcall)
177187
return Effects(
178188
consistent,
179189
effect_free,
@@ -182,7 +192,8 @@ function Effects(effects::Effects = _EFFECTS_UNKNOWN;
182192
notaskstate,
183193
inaccessiblememonly,
184194
noub,
185-
nonoverlayed)
195+
nonoverlayed,
196+
nortcall)
186197
end
187198

188199
function is_better_effects(new::Effects, old::Effects)
@@ -247,6 +258,11 @@ function is_better_effects(new::Effects, old::Effects)
247258
elseif new.nonoverlayed != old.nonoverlayed
248259
return false
249260
end
261+
if new.nortcall
262+
any_improved |= !old.nortcall
263+
elseif new.nortcall != old.nortcall
264+
return false
265+
end
250266
return any_improved
251267
end
252268

@@ -259,7 +275,8 @@ function merge_effects(old::Effects, new::Effects)
259275
merge_effectbits(old.notaskstate, new.notaskstate),
260276
merge_effectbits(old.inaccessiblememonly, new.inaccessiblememonly),
261277
merge_effectbits(old.noub, new.noub),
262-
merge_effectbits(old.nonoverlayed, new.nonoverlayed))
278+
merge_effectbits(old.nonoverlayed, new.nonoverlayed),
279+
merge_effectbits(old.nortcall, new.nortcall))
263280
end
264281

265282
function merge_effectbits(old::UInt8, new::UInt8)
@@ -279,16 +296,18 @@ is_inaccessiblememonly(effects::Effects) = effects.inaccessiblememonly === ALWAY
279296
is_noub(effects::Effects) = effects.noub === ALWAYS_TRUE
280297
is_noub_if_noinbounds(effects::Effects) = effects.noub === NOUB_IF_NOINBOUNDS
281298
is_nonoverlayed(effects::Effects) = effects.nonoverlayed === ALWAYS_TRUE
299+
is_nortcall(effects::Effects) = effects.nortcall
282300

283301
# implies `is_notaskstate` & `is_inaccessiblememonly`, but not explicitly checked here
284-
is_foldable(effects::Effects) =
302+
is_foldable(effects::Effects, check_rtcall::Bool=false) =
285303
is_consistent(effects) &&
286304
(is_noub(effects) || is_noub_if_noinbounds(effects)) &&
287305
is_effect_free(effects) &&
288-
is_terminates(effects)
306+
is_terminates(effects) &&
307+
(!check_rtcall || is_nortcall(effects))
289308

290-
is_foldable_nothrow(effects::Effects) =
291-
is_foldable(effects) &&
309+
is_foldable_nothrow(effects::Effects, check_rtcall::Bool=false) =
310+
is_foldable(effects, check_rtcall) &&
292311
is_nothrow(effects)
293312

294313
# TODO add `is_noub` here?
@@ -318,7 +337,8 @@ function encode_effects(e::Effects)
318337
((e.notaskstate % UInt32) << 7) |
319338
((e.inaccessiblememonly % UInt32) << 8) |
320339
((e.noub % UInt32) << 10) |
321-
((e.nonoverlayed % UInt32) << 12)
340+
((e.nonoverlayed % UInt32) << 12) |
341+
((e.nortcall % UInt32) << 14)
322342
end
323343

324344
function decode_effects(e::UInt32)
@@ -330,7 +350,8 @@ function decode_effects(e::UInt32)
330350
_Bool((e >> 7) & 0x01),
331351
UInt8((e >> 8) & 0x03),
332352
UInt8((e >> 10) & 0x03),
333-
UInt8((e >> 12) & 0x03))
353+
UInt8((e >> 12) & 0x03),
354+
_Bool((e >> 14) & 0x01))
334355
end
335356

336357
function encode_effects_override(eo::EffectsOverride)
@@ -345,6 +366,7 @@ function encode_effects_override(eo::EffectsOverride)
345366
eo.noub && (e |= (0x0001 << 7))
346367
eo.noub_if_noinbounds && (e |= (0x0001 << 8))
347368
eo.consistent_overlay && (e |= (0x0001 << 9))
369+
eo.nortcall && (e |= (0x0001 << 10))
348370
return e
349371
end
350372

@@ -359,7 +381,8 @@ function decode_effects_override(e::UInt16)
359381
!iszero(e & (0x0001 << 6)),
360382
!iszero(e & (0x0001 << 7)),
361383
!iszero(e & (0x0001 << 8)),
362-
!iszero(e & (0x0001 << 9)))
384+
!iszero(e & (0x0001 << 9)),
385+
!iszero(e & (0x0001 << 10)))
363386
end
364387

365388
decode_statement_effects_override(ssaflag::UInt32) =

base/compiler/optimize.jl

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,16 @@ const IR_FLAG_NOUB = one(UInt32) << 9
4444
const IR_FLAG_EFIIMO = one(UInt32) << 10
4545
# This statement is :inaccessiblememonly == INACCESSIBLEMEM_OR_ARGMEMONLY
4646
const IR_FLAG_INACCESSIBLEMEM_OR_ARGMEM = one(UInt32) << 11
47+
# This statement is :nortcall
48+
const IR_FLAG_NORTCALL = one(UInt32) << 12
49+
# This statement has no users and may be deleted if flags get refined to IR_FLAGS_REMOVABLE
50+
const IR_FLAG_UNUSED = one(UInt32) << 13
4751

48-
const NUM_IR_FLAGS = 12 # sync with julia.h
52+
const NUM_IR_FLAGS = 14 # sync with julia.h
4953

5054
const IR_FLAGS_EFFECTS =
51-
IR_FLAG_CONSISTENT | IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW | IR_FLAG_NOUB
55+
IR_FLAG_CONSISTENT | IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW |
56+
IR_FLAG_NOUB | IR_FLAG_NORTCALL
5257

5358
const IR_FLAGS_REMOVABLE = IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW
5459

@@ -75,6 +80,9 @@ function flags_for_effects(effects::Effects)
7580
if is_noub(effects)
7681
flags |= IR_FLAG_NOUB
7782
end
83+
if is_nortcall(effects)
84+
flags |= IR_FLAG_NORTCALL
85+
end
7886
return flags
7987
end
8088

@@ -597,26 +605,28 @@ mutable struct PostOptAnalysisState
597605
all_nothrow::Bool
598606
all_noub::Bool
599607
any_conditional_ub::Bool
608+
nortcall::Bool
600609
function PostOptAnalysisState(result::InferenceResult, ir::IRCode)
601610
inconsistent = BitSetBoundedMinPrioritySet(length(ir.stmts))
602611
tpdum = TwoPhaseDefUseMap(length(ir.stmts))
603612
lazypostdomtree = LazyPostDomtree(ir)
604613
lazyagdomtree = LazyAugmentedDomtree(ir)
605614
return new(result, ir, inconsistent, tpdum, lazypostdomtree, lazyagdomtree, Int[],
606-
true, true, nothing, true, true, false)
615+
true, true, nothing, true, true, false, true)
607616
end
608617
end
609618

610619
give_up_refinements!(sv::PostOptAnalysisState) =
611620
sv.all_retpaths_consistent = sv.all_effect_free = sv.effect_free_if_argmem_only =
612-
sv.all_nothrow = sv.all_noub = false
621+
sv.all_nothrow = sv.all_noub = sv.nortcall = false
613622

614623
function any_refinable(sv::PostOptAnalysisState)
615624
effects = sv.result.ipo_effects
616625
return ((!is_consistent(effects) & sv.all_retpaths_consistent) |
617626
(!is_effect_free(effects) & sv.all_effect_free) |
618627
(!is_nothrow(effects) & sv.all_nothrow) |
619-
(!is_noub(effects) & sv.all_noub))
628+
(!is_noub(effects) & sv.all_noub) |
629+
(!is_nortcall(effects) & sv.nortcall))
620630
end
621631

622632
struct GetNativeEscapeCache{CodeCache}
@@ -661,7 +671,8 @@ function refine_effects!(interp::AbstractInterpreter, sv::PostOptAnalysisState)
661671
effect_free = sv.all_effect_free ? ALWAYS_TRUE :
662672
sv.effect_free_if_argmem_only === true ? EFFECT_FREE_IF_INACCESSIBLEMEMONLY : effects.effect_free,
663673
nothrow = sv.all_nothrow ? true : effects.nothrow,
664-
noub = sv.all_noub ? (sv.any_conditional_ub ? NOUB_IF_NOINBOUNDS : ALWAYS_TRUE) : effects.noub)
674+
noub = sv.all_noub ? (sv.any_conditional_ub ? NOUB_IF_NOINBOUNDS : ALWAYS_TRUE) : effects.noub,
675+
nortcall = sv.nortcall ? true : effects.nortcall)
665676
return true
666677
end
667678

@@ -786,6 +797,13 @@ function scan_non_dataflow_flags!(inst::Instruction, sv::PostOptAnalysisState)
786797
sv.all_noub = false
787798
end
788799
end
800+
if !has_flag(flag, IR_FLAG_NORTCALL)
801+
# if a function call that might invoke `Core.Compiler.return_type` has been deleted,
802+
# there's no need to taint with `:nortcall`, allowing concrete evaluation
803+
if isexpr(stmt, :call) || isexpr(stmt, :invoke)
804+
sv.nortcall = false
805+
end
806+
end
789807
end
790808

791809
function scan_inconsistency!(inst::Instruction, sv::PostOptAnalysisState)

base/compiler/ssair/show.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1021,6 +1021,8 @@ function Base.show(io::IO, e::Effects)
10211021
printstyled(io, effectbits_letter(e, :noub, 'u'); color=effectbits_color(e, :noub))
10221022
print(io, ',')
10231023
printstyled(io, effectbits_letter(e, :nonoverlayed, 'o'); color=effectbits_color(e, :nonoverlayed))
1024+
print(io, ',')
1025+
printstyled(io, effectbits_letter(e, :nortcall, 'r'); color=effectbits_color(e, :nortcall))
10241026
print(io, ')')
10251027
end
10261028

base/compiler/tfuncs.jl

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2922,7 +2922,7 @@ end
29222922
# since abstract_call_gf_by_type is a very inaccurate model of _method and of typeinf_type,
29232923
# while this assumes that it is an absolutely precise and accurate and exact model of both
29242924
function return_type_tfunc(interp::AbstractInterpreter, argtypes::Vector{Any}, si::StmtInfo, sv::AbsIntState)
2925-
UNKNOWN = CallMeta(Type, Any, EFFECTS_THROWS, NoCallInfo())
2925+
UNKNOWN = CallMeta(Type, Any, Effects(EFFECTS_THROWS; nortcall=false), NoCallInfo())
29262926
if !(2 <= length(argtypes) <= 3)
29272927
return UNKNOWN
29282928
end
@@ -2950,8 +2950,12 @@ function return_type_tfunc(interp::AbstractInterpreter, argtypes::Vector{Any}, s
29502950
return UNKNOWN
29512951
end
29522952

2953+
# effects are not an issue if we know this statement will get removed, but if it does not get removed,
2954+
# then this could be recursively re-entering inference (via concrete-eval), which will not terminate
2955+
RT_CALL_EFFECTS = Effects(EFFECTS_TOTAL; nortcall=false)
2956+
29532957
if contains_is(argtypes_vec, Union{})
2954-
return CallMeta(Const(Union{}), Union{}, EFFECTS_TOTAL, NoCallInfo())
2958+
return CallMeta(Const(Union{}), Union{}, RT_CALL_EFFECTS, NoCallInfo())
29552959
end
29562960

29572961
# Run the abstract_call without restricting abstract call
@@ -2969,25 +2973,25 @@ function return_type_tfunc(interp::AbstractInterpreter, argtypes::Vector{Any}, s
29692973
rt = widenslotwrapper(call.rt)
29702974
if isa(rt, Const)
29712975
# output was computed to be constant
2972-
return CallMeta(Const(typeof(rt.val)), Union{}, EFFECTS_TOTAL, info)
2976+
return CallMeta(Const(typeof(rt.val)), Union{}, RT_CALL_EFFECTS, info)
29732977
end
29742978
rt = widenconst(rt)
29752979
if rt === Bottom || (isconcretetype(rt) && !iskindtype(rt))
29762980
# output cannot be improved so it is known for certain
2977-
return CallMeta(Const(rt), Union{}, EFFECTS_TOTAL, info)
2981+
return CallMeta(Const(rt), Union{}, RT_CALL_EFFECTS, info)
29782982
elseif isa(sv, InferenceState) && !isempty(sv.pclimitations)
29792983
# conservatively express uncertainty of this result
29802984
# in two ways: both as being a subtype of this, and
29812985
# because of LimitedAccuracy causes
2982-
return CallMeta(Type{<:rt}, Union{}, EFFECTS_TOTAL, info)
2986+
return CallMeta(Type{<:rt}, Union{}, RT_CALL_EFFECTS, info)
29832987
elseif isa(tt, Const) || isconstType(tt)
29842988
# input arguments were known for certain
29852989
# XXX: this doesn't imply we know anything about rt
2986-
return CallMeta(Const(rt), Union{}, EFFECTS_TOTAL, info)
2990+
return CallMeta(Const(rt), Union{}, RT_CALL_EFFECTS, info)
29872991
elseif isType(rt)
2988-
return CallMeta(Type{rt}, Union{}, EFFECTS_TOTAL, info)
2992+
return CallMeta(Type{rt}, Union{}, RT_CALL_EFFECTS, info)
29892993
else
2990-
return CallMeta(Type{<:rt}, Union{}, EFFECTS_TOTAL, info)
2994+
return CallMeta(Type{<:rt}, Union{}, RT_CALL_EFFECTS, info)
29912995
end
29922996
end
29932997

0 commit comments

Comments
 (0)