Skip to content

Commit 7e809b0

Browse files
vtjnashaviatesk
andauthored
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 86231ce commit 7e809b0

13 files changed

+141
-57
lines changed

base/boot.jl

+2-1
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,8 @@ macro _foldable_meta()
284284
#=:inaccessiblememonly=#true,
285285
#=:noub=#true,
286286
#=:noub_if_noinbounds=#false,
287-
#=:consistent_overlay=#false))
287+
#=:consistent_overlay=#false,
288+
#=:nortcall=#true))
288289
end
289290

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

base/cmd.jl

+1-1
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

+4-3
Original file line numberDiff line numberDiff line change
@@ -980,7 +980,7 @@ function concrete_eval_eligible(interp::AbstractInterpreter,
980980
end
981981
end
982982
mi = result.edge
983-
if mi !== nothing && is_foldable(effects)
983+
if mi !== nothing && is_foldable(effects, #=check_rtcall=#true)
984984
if f !== nothing && is_all_const_arg(arginfo, #=start=#2)
985985
if (is_nonoverlayed(interp) || is_nonoverlayed(effects) ||
986986
# Even if overlay methods are involved, when `:consistent_overlay` is
@@ -2910,8 +2910,9 @@ function override_effects(effects::Effects, override::EffectsOverride)
29102910
notaskstate = override.notaskstate ? true : effects.notaskstate,
29112911
inaccessiblememonly = override.inaccessiblememonly ? ALWAYS_TRUE : effects.inaccessiblememonly,
29122912
noub = override.noub ? ALWAYS_TRUE :
2913-
(override.noub_if_noinbounds && effects.noub !== ALWAYS_TRUE) ? NOUB_IF_NOINBOUNDS :
2914-
effects.noub)
2913+
(override.noub_if_noinbounds && effects.noub !== ALWAYS_TRUE) ? NOUB_IF_NOINBOUNDS :
2914+
effects.noub,
2915+
nortcall = override.nortcall ? true : effects.nortcall)
29152916
end
29162917

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

base/compiler/compiler.jl

+7-4
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,11 @@ struct EffectsOverride
4949
noub::Bool
5050
noub_if_noinbounds::Bool
5151
consistent_overlay::Bool
52+
nortcall::Bool
5253
end
5354
function EffectsOverride(
5455
override::EffectsOverride =
55-
EffectsOverride(false, false, false, false, false, false, false, false, false, false);
56+
EffectsOverride(false, false, false, false, false, false, false, false, false, false, false);
5657
consistent::Bool = override.consistent,
5758
effect_free::Bool = override.effect_free,
5859
nothrow::Bool = override.nothrow,
@@ -62,7 +63,8 @@ function EffectsOverride(
6263
inaccessiblememonly::Bool = override.inaccessiblememonly,
6364
noub::Bool = override.noub,
6465
noub_if_noinbounds::Bool = override.noub_if_noinbounds,
65-
consistent_overlay::Bool = override.consistent_overlay)
66+
consistent_overlay::Bool = override.consistent_overlay,
67+
nortcall::Bool = override.nortcall)
6668
return EffectsOverride(
6769
consistent,
6870
effect_free,
@@ -73,9 +75,10 @@ function EffectsOverride(
7375
inaccessiblememonly,
7476
noub,
7577
noub_if_noinbounds,
76-
consistent_overlay)
78+
consistent_overlay,
79+
nortcall)
7780
end
78-
const NUM_EFFECTS_OVERRIDES = 10 # sync with julia.h
81+
const NUM_EFFECTS_OVERRIDES = 11 # sync with julia.h
7982

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

base/compiler/effects.jl

+39-16
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

+23-7
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,16 @@ const IR_FLAG_NOUB = one(UInt32) << 8
4242
const IR_FLAG_EFIIMO = one(UInt32) << 9
4343
# This statement is :inaccessiblememonly == INACCESSIBLEMEM_OR_ARGMEMONLY
4444
const IR_FLAG_INACCESSIBLEMEM_OR_ARGMEM = one(UInt32) << 10
45+
# This statement is :nortcall
46+
const IR_FLAG_NORTCALL = one(UInt32) << 11
4547
# This statement has no users and may be deleted if flags get refined to IR_FLAGS_REMOVABLE
46-
const IR_FLAG_UNUSED = one(UInt32) << 11
48+
const IR_FLAG_UNUSED = one(UInt32) << 12
4749

48-
const NUM_IR_FLAGS = 12 # sync with julia.h
50+
const NUM_IR_FLAGS = 13 # sync with julia.h
4951

5052
const IR_FLAGS_EFFECTS =
51-
IR_FLAG_CONSISTENT | IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW | IR_FLAG_TERMINATES | IR_FLAG_NOUB
53+
IR_FLAG_CONSISTENT | IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW |
54+
IR_FLAG_TERMINATES | IR_FLAG_NOUB | IR_FLAG_NORTCALL
5255

5356
const IR_FLAGS_REMOVABLE = IR_FLAG_EFFECT_FREE | IR_FLAG_NOTHROW | IR_FLAG_TERMINATES
5457

@@ -78,6 +81,9 @@ function flags_for_effects(effects::Effects)
7881
if is_noub(effects)
7982
flags |= IR_FLAG_NOUB
8083
end
84+
if is_nortcall(effects)
85+
flags |= IR_FLAG_NORTCALL
86+
end
8187
return flags
8288
end
8389

@@ -583,26 +589,28 @@ mutable struct PostOptAnalysisState
583589
all_nothrow::Bool
584590
all_noub::Bool
585591
any_conditional_ub::Bool
592+
nortcall::Bool
586593
function PostOptAnalysisState(result::InferenceResult, ir::IRCode)
587594
inconsistent = BitSetBoundedMinPrioritySet(length(ir.stmts))
588595
tpdum = TwoPhaseDefUseMap(length(ir.stmts))
589596
lazypostdomtree = LazyPostDomtree(ir)
590597
lazyagdomtree = LazyAugmentedDomtree(ir)
591598
return new(result, ir, inconsistent, tpdum, lazypostdomtree, lazyagdomtree, Int[],
592-
true, true, nothing, true, true, false)
599+
true, true, nothing, true, true, false, true)
593600
end
594601
end
595602

596603
give_up_refinements!(sv::PostOptAnalysisState) =
597604
sv.all_retpaths_consistent = sv.all_effect_free = sv.effect_free_if_argmem_only =
598-
sv.all_nothrow = sv.all_noub = false
605+
sv.all_nothrow = sv.all_noub = sv.nortcall = false
599606

600607
function any_refinable(sv::PostOptAnalysisState)
601608
effects = sv.result.ipo_effects
602609
return ((!is_consistent(effects) & sv.all_retpaths_consistent) |
603610
(!is_effect_free(effects) & sv.all_effect_free) |
604611
(!is_nothrow(effects) & sv.all_nothrow) |
605-
(!is_noub(effects) & sv.all_noub))
612+
(!is_noub(effects) & sv.all_noub) |
613+
(!is_nortcall(effects) & sv.nortcall))
606614
end
607615

608616
struct GetNativeEscapeCache{CodeCache}
@@ -647,7 +655,8 @@ function refine_effects!(interp::AbstractInterpreter, sv::PostOptAnalysisState)
647655
effect_free = sv.all_effect_free ? ALWAYS_TRUE :
648656
sv.effect_free_if_argmem_only === true ? EFFECT_FREE_IF_INACCESSIBLEMEMONLY : effects.effect_free,
649657
nothrow = sv.all_nothrow ? true : effects.nothrow,
650-
noub = sv.all_noub ? (sv.any_conditional_ub ? NOUB_IF_NOINBOUNDS : ALWAYS_TRUE) : effects.noub)
658+
noub = sv.all_noub ? (sv.any_conditional_ub ? NOUB_IF_NOINBOUNDS : ALWAYS_TRUE) : effects.noub,
659+
nortcall = sv.nortcall ? true : effects.nortcall)
651660
return true
652661
end
653662

@@ -772,6 +781,13 @@ function scan_non_dataflow_flags!(inst::Instruction, sv::PostOptAnalysisState)
772781
sv.all_noub = false
773782
end
774783
end
784+
if !has_flag(flag, IR_FLAG_NORTCALL)
785+
# if a function call that might invoke `Core.Compiler.return_type` has been deleted,
786+
# there's no need to taint with `:nortcall`, allowing concrete evaluation
787+
if isexpr(stmt, :call) || isexpr(stmt, :invoke)
788+
sv.nortcall = false
789+
end
790+
end
775791
end
776792

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

base/compiler/ssair/show.jl

+2
Original file line numberDiff line numberDiff line change
@@ -1050,6 +1050,8 @@ function Base.show(io::IO, e::Effects)
10501050
printstyled(io, effectbits_letter(e, :noub, 'u'); color=effectbits_color(e, :noub))
10511051
print(io, ',')
10521052
printstyled(io, effectbits_letter(e, :nonoverlayed, 'o'); color=effectbits_color(e, :nonoverlayed))
1053+
print(io, ',')
1054+
printstyled(io, effectbits_letter(e, :nortcall, 'r'); color=effectbits_color(e, :nortcall))
10531055
print(io, ')')
10541056
end
10551057

base/compiler/tfuncs.jl

+12-8
Original file line numberDiff line numberDiff line change
@@ -2871,7 +2871,7 @@ end
28712871
# since abstract_call_gf_by_type is a very inaccurate model of _method and of typeinf_type,
28722872
# while this assumes that it is an absolutely precise and accurate and exact model of both
28732873
function return_type_tfunc(interp::AbstractInterpreter, argtypes::Vector{Any}, si::StmtInfo, sv::AbsIntState)
2874-
UNKNOWN = CallMeta(Type, Any, EFFECTS_THROWS, NoCallInfo())
2874+
UNKNOWN = CallMeta(Type, Any, Effects(EFFECTS_THROWS; nortcall=false), NoCallInfo())
28752875
if !(2 <= length(argtypes) <= 3)
28762876
return UNKNOWN
28772877
end
@@ -2899,8 +2899,12 @@ function return_type_tfunc(interp::AbstractInterpreter, argtypes::Vector{Any}, s
28992899
return UNKNOWN
29002900
end
29012901

2902+
# effects are not an issue if we know this statement will get removed, but if it does not get removed,
2903+
# then this could be recursively re-entering inference (via concrete-eval), which will not terminate
2904+
RT_CALL_EFFECTS = Effects(EFFECTS_TOTAL; nortcall=false)
2905+
29022906
if contains_is(argtypes_vec, Union{})
2903-
return CallMeta(Const(Union{}), Union{}, EFFECTS_TOTAL, NoCallInfo())
2907+
return CallMeta(Const(Union{}), Union{}, RT_CALL_EFFECTS, NoCallInfo())
29042908
end
29052909

29062910
# Run the abstract_call without restricting abstract call
@@ -2918,25 +2922,25 @@ function return_type_tfunc(interp::AbstractInterpreter, argtypes::Vector{Any}, s
29182922
rt = widenslotwrapper(call.rt)
29192923
if isa(rt, Const)
29202924
# output was computed to be constant
2921-
return CallMeta(Const(typeof(rt.val)), Union{}, EFFECTS_TOTAL, info)
2925+
return CallMeta(Const(typeof(rt.val)), Union{}, RT_CALL_EFFECTS, info)
29222926
end
29232927
rt = widenconst(rt)
29242928
if rt === Bottom || (isconcretetype(rt) && !iskindtype(rt))
29252929
# output cannot be improved so it is known for certain
2926-
return CallMeta(Const(rt), Union{}, EFFECTS_TOTAL, info)
2930+
return CallMeta(Const(rt), Union{}, RT_CALL_EFFECTS, info)
29272931
elseif isa(sv, InferenceState) && !isempty(sv.pclimitations)
29282932
# conservatively express uncertainty of this result
29292933
# in two ways: both as being a subtype of this, and
29302934
# because of LimitedAccuracy causes
2931-
return CallMeta(Type{<:rt}, Union{}, EFFECTS_TOTAL, info)
2935+
return CallMeta(Type{<:rt}, Union{}, RT_CALL_EFFECTS, info)
29322936
elseif isa(tt, Const) || isconstType(tt)
29332937
# input arguments were known for certain
29342938
# XXX: this doesn't imply we know anything about rt
2935-
return CallMeta(Const(rt), Union{}, EFFECTS_TOTAL, info)
2939+
return CallMeta(Const(rt), Union{}, RT_CALL_EFFECTS, info)
29362940
elseif isType(rt)
2937-
return CallMeta(Type{rt}, Union{}, EFFECTS_TOTAL, info)
2941+
return CallMeta(Type{rt}, Union{}, RT_CALL_EFFECTS, info)
29382942
else
2939-
return CallMeta(Type{<:rt}, Union{}, EFFECTS_TOTAL, info)
2943+
return CallMeta(Type{<:rt}, Union{}, RT_CALL_EFFECTS, info)
29402944
end
29412945
end
29422946

0 commit comments

Comments
 (0)