Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion base/boot.jl
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,7 @@ eval(Core, quote
ReturnNode() = $(Expr(:new, :ReturnNode)) # unassigned val indicates unreachable
GotoIfNot(@nospecialize(cond), dest::Int) = $(Expr(:new, :GotoIfNot, :cond, :dest))
EnterNode(dest::Int) = $(Expr(:new, :EnterNode, :dest))
EnterNode(dest::Int, @nospecialize(scope)) = $(Expr(:new, :EnterNode, :dest, :scope))
LineNumberNode(l::Int) = $(Expr(:new, :LineNumberNode, :l, nothing))
function LineNumberNode(l::Int, @nospecialize(f))
isa(f, String) && (f = Symbol(f))
Expand Down Expand Up @@ -966,7 +967,8 @@ arraysize(a::Array, i::Int) = sle_int(i, nfields(a.size)) ? getfield(a.size, i)
export arrayref, arrayset, arraysize, const_arrayref

# For convenience
EnterNode(old::EnterNode, new_dest::Int) = EnterNode(new_dest)
EnterNode(old::EnterNode, new_dest::Int) = isdefined(old, :scope) ?
EnterNode(new_dest, old.scope) : EnterNode(new_dest)
Comment on lines +970 to +971
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
EnterNode(old::EnterNode, new_dest::Int) = isdefined(old, :scope) ?
EnterNode(new_dest, old.scope) : EnterNode(new_dest)
function EnterNode(node::EnterNode;
dest::Int=node.dest,
scope)
@nospecialize scope
if !(@isdefined scope)
isdefined(node, :scope) || return EnterNode(dest)
scope = node.scope
end
return EnterNode(dest, scope)
end

Considering the possibility that EnterNode could get more complicated in the future (I'm not sure if it is likely though), defining it as Effects(effects::Effects; overrides...)-like constructor might be a better move?


include(Core, "optimized_generics.jl")

Expand Down
13 changes: 13 additions & 0 deletions base/compiler/abstractinterpretation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3265,6 +3265,19 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState)
elseif isa(stmt, EnterNode)
ssavaluetypes[currpc] = Any
add_curr_ssaflag!(frame, IR_FLAG_NOTHROW)
if isdefined(stmt, :scope)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does inference / SROA need to understand that this is now a clobbering statement for the current_task scope field, since the scope field is still publicly visible? Or should we hide the field so that users can't break the invariants expected and proven of it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think either of them can optimize these fields on the current task. I would be open to hiding the scope field further, but I didn't think it was that necessary.

scopet = abstract_eval_value(interp, stmt.scope, currstate, frame)
handler = frame.handlers[frame.handler_at[frame.currpc+1][1]]
@assert handler.scopet !== nothing
if !⊑(𝕃ᵢ, scopet, handler.scopet)
handler.scopet = tmerge(𝕃ᵢ, scopet, handler.scopet)
if isdefined(handler, :scope_uses)
for bb in handler.scope_uses
push!(W, bb)
end
end
end
end
@goto fallthrough
elseif isexpr(stmt, :leave)
ssavaluetypes[currpc] = Any
Expand Down
6 changes: 4 additions & 2 deletions base/compiler/inferencestate.jl
Original file line number Diff line number Diff line change
Expand Up @@ -205,8 +205,10 @@ const CACHE_MODE_VOLATILE = 0x01 << 2 # not cached, optimization allowed

mutable struct TryCatchFrame
exct
scopet
const enter_idx::Int
TryCatchFrame(@nospecialize(exct), enter_idx::Int) = new(exct, enter_idx)
scope_uses::Vector{Int}
TryCatchFrame(@nospecialize(exct), @nospecialize(scopet), enter_idx::Int) = new(exct, scopet, enter_idx)
end

mutable struct InferenceState
Expand Down Expand Up @@ -364,7 +366,7 @@ function compute_trycatch(code::Vector{Any}, ip::BitSet)
stmt = code[pc]
if isa(stmt, EnterNode)
l = stmt.catch_dest
push!(handlers, TryCatchFrame(Bottom, pc))
push!(handlers, TryCatchFrame(Bottom, isdefined(stmt, :scope) ? Bottom : nothing, pc))
handler_id = length(handlers)
handler_at[pc + 1] = (handler_id, 0)
push!(ip, pc + 1)
Expand Down
1 change: 1 addition & 0 deletions base/compiler/ssair/ir.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1414,6 +1414,7 @@ function process_node!(compact::IncrementalCompact, result_idx::Int, inst::Instr
result_idx += 1
end
elseif cfg_transforms_enabled && isa(stmt, EnterNode)
stmt = renumber_ssa2!(stmt, ssa_rename, used_ssas, new_new_used_ssas, late_fixup, result_idx, do_rename_ssa, mark_refined!)::EnterNode
label = bb_rename_succ[stmt.catch_dest]
@assert label > 0
ssa_rename[idx] = SSAValue(result_idx)
Expand Down
25 changes: 25 additions & 0 deletions base/compiler/ssair/passes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1076,6 +1076,29 @@ function fold_ifelse!(compact::IncrementalCompact, idx::Int, stmt::Expr)
return false
end

function fold_current_scope!(compact::IncrementalCompact, idx::Int, stmt::Expr, lazydomtree::LazyDomtree)
domtree = get!(lazydomtree)

# The frontend enforces the invariant that any :enter dominates its active
# region, so all we have to do here is walk the domtree to find it.
dombb = block_for_inst(compact, SSAValue(idx))

local bbterminator
while true
dombb = domtree.idoms_bb[dombb]

# Did not find any dominating :enter - scope is inherited from the outside
dombb == 0 && return nothing

bbterminator = compact[SSAValue(last(compact.cfg_transform.result_bbs[dombb].stmts))][:stmt]
isa(bbterminator, EnterNode) || continue
isdefined(bbterminator, :scope) || continue
compact[idx] = bbterminator.scope
return nothing
end
end


# NOTE we use `IdSet{Int}` instead of `BitSet` for in these passes since they work on IR after inlining,
# which can be very large sometimes, and program counters in question are often very sparse
const SPCSet = IdSet{Int}
Expand Down Expand Up @@ -1208,6 +1231,8 @@ function sroa_pass!(ir::IRCode, inlining::Union{Nothing,InliningState}=nothing)
elseif is_known_invoke_or_call(stmt, Core.OptimizedGenerics.KeyValue.get, compact)
2 == (length(stmt.args) - (isexpr(stmt, :invoke) ? 2 : 1)) || continue
lift_keyvalue_get!(compact, idx, stmt, 𝕃ₒ)
elseif is_known_call(stmt, Core.current_scope, compact)
fold_current_scope!(compact, idx, stmt, lazydomtree)
elseif isexpr(stmt, :new)
refine_new_effects!(𝕃ₒ, compact, idx, stmt)
end
Expand Down
4 changes: 4 additions & 0 deletions base/compiler/ssair/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ function print_stmt(io::IO, idx::Int, @nospecialize(stmt), used::BitSet, maxleng
# given control flow information, we prefer to print these with the basic block #, instead of the ssa %
elseif isa(stmt, EnterNode)
print(io, "enter #", stmt.catch_dest, "")
if isdefined(stmt, :scope)
print(io, " with scope ")
show_unquoted(io, stmt.scope, indent)
end
elseif stmt isa GotoNode
print(io, "goto #", stmt.label)
elseif stmt isa PhiNode
Expand Down
2 changes: 1 addition & 1 deletion base/compiler/ssair/verify.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

function maybe_show_ir(ir::IRCode)
if isdefined(Core, :Main)
Core.Main.Base.display(ir)
invokelatest(Core.Main.Base.display, ir)
end
end

Expand Down
52 changes: 49 additions & 3 deletions base/compiler/tfuncs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2488,6 +2488,19 @@ function builtin_effects(𝕃::AbstractLattice, @nospecialize(f::Builtin), argty
return Effects(EFFECTS_TOTAL;
consistent = (isa(setting, Const) && setting.val === :conditional) ? ALWAYS_TRUE : ALWAYS_FALSE,
nothrow = compilerbarrier_nothrow(setting, nothing))
elseif f === Core.current_scope
nothrow = true
if length(argtypes) != 0
if length(argtypes) != 1 || !isvarargtype(argtypes[1])
return EFFECTS_THROWS
end
nothrow = false
end
return Effects(EFFECTS_TOTAL;
consistent = ALWAYS_FALSE,
notaskstate = false,
nothrow
)
else
if contains_is(_CONSISTENT_BUILTINS, f)
consistent = ALWAYS_TRUE
Expand Down Expand Up @@ -2554,6 +2567,32 @@ function memoryop_noub(@nospecialize(f), argtypes::Vector{Any})
return false
end

function current_scope_tfunc(interp::AbstractInterpreter, sv::InferenceState)
pc = sv.currpc
while true
handleridx = sv.handler_at[pc][1]
if handleridx == 0
# No local scope available - inherited from the outside
return Any
end
pchandler = sv.handlers[handleridx]
# Remember that we looked at this handler, so we get re-scheduled
# if the scope information changes
isdefined(pchandler, :scope_uses) || (pchandler.scope_uses = Int[])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we set scope_uses within compute_trycatch?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We cannot, because we don't necessarily know all the callsites that will infer to a call to current_scope. We could make current_scope an expr to make it obvious, but I don't think it's required.

pcbb = block_for_inst(sv.cfg, pc)
if findfirst(==(pcbb), pchandler.scope_uses) === nothing
push!(pchandler.scope_uses, pcbb)
end
scope = pchandler.scopet
if scope !== nothing
# Found the scope - forward it
return scope
end
pc = pchandler.enter_idx
end
end
current_scope_tfunc(interp::AbstractInterpreter, sv) = Any

"""
builtin_nothrow(𝕃::AbstractLattice, f::Builtin, argtypes::Vector{Any}, rt) -> Bool

Expand All @@ -2568,9 +2607,6 @@ end
function builtin_tfunction(interp::AbstractInterpreter, @nospecialize(f), argtypes::Vector{Any},
sv::Union{AbsIntState, Nothing})
𝕃ᵢ = typeinf_lattice(interp)
if f === tuple
return tuple_tfunc(𝕃ᵢ, argtypes)
end
if isa(f, IntrinsicFunction)
if is_pure_intrinsic_infer(f) && all(@nospecialize(a) -> isa(a, Const), argtypes)
argvals = anymap(@nospecialize(a) -> (a::Const).val, argtypes)
Expand All @@ -2596,6 +2632,16 @@ function builtin_tfunction(interp::AbstractInterpreter, @nospecialize(f), argtyp
end
tf = T_IFUNC[iidx]
else
if f === tuple
return tuple_tfunc(𝕃ᵢ, argtypes)
elseif f === Core.current_scope
if length(argtypes) != 0
if length(argtypes) != 1 || !isvarargtype(argtypes[1])
return Bottom
end
end
return current_scope_tfunc(interp, sv)
end
fidx = find_tfunc(f)
if fidx === nothing
# unknown/unhandled builtin function
Expand Down
9 changes: 8 additions & 1 deletion base/compiler/validation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const VALID_EXPR_HEADS = IdDict{Symbol,UnitRange{Int}}(
:new => 1:typemax(Int),
:splatnew => 2:2,
:the_exception => 0:0,
:enter => 1:1,
:enter => 1:2,
:leave => 1:typemax(Int),
:pop_exception => 1:1,
:inbounds => 1:1,
Expand Down Expand Up @@ -160,6 +160,13 @@ function validate_code!(errors::Vector{InvalidCodeError}, c::CodeInfo, is_top_le
push!(errors, InvalidCodeError(INVALID_CALL_ARG, x.cond))
end
validate_val!(x.cond)
elseif isa(x, EnterNode)
if isdefined(x, :scope)
if !is_valid_argument(x.scope)
push!(errors, InvalidCodeError(INVALID_CALL_ARG, x.scope))
end
validate_val!(x.scope)
end
elseif isa(x, ReturnNode)
if isdefined(x, :val)
if !is_valid_return(x.val)
Expand Down
51 changes: 12 additions & 39 deletions base/scopedvalues.jl
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,6 @@ function Scope(scope, pairs::Pair{<:ScopedValue}...)
end
Scope(::Nothing) = nothing

"""
current_scope()::Union{Nothing, Scope}

Return the current dynamic scope.
"""
current_scope() = current_task().scope::Union{Nothing, Scope}

function Base.show(io::IO, scope::Scope)
print(io, Scope, "(")
first = true
Expand Down Expand Up @@ -113,8 +106,7 @@ return `nothing`. Otherwise returns `Some{T}` with the current
value.
"""
function get(val::ScopedValue{T}) where {T}
# Inline current_scope to avoid doing the type assertion twice.
scope = current_task().scope
scope = Core.current_scope()::Union{Scope, Nothing}
if scope === nothing
isassigned(val) && return Some{T}(val.default)
return nothing
Expand Down Expand Up @@ -148,25 +140,6 @@ function Base.show(io::IO, val::ScopedValue)
print(io, ')')
end

"""
with(f, (var::ScopedValue{T} => val::T)...)

Execute `f` in a new scope with `var` set to `val`.
"""
function with(f, pair::Pair{<:ScopedValue}, rest::Pair{<:ScopedValue}...)
@nospecialize
ct = Base.current_task()
current_scope = ct.scope::Union{Nothing, Scope}
ct.scope = Scope(current_scope, pair, rest...)
try
return f()
finally
ct.scope = current_scope
end
end

with(@nospecialize(f)) = f()

"""
@with vars... expr

Expand All @@ -184,18 +157,18 @@ macro with(exprs...)
else
error("@with expects at least one argument")
end
for expr in exprs
if expr.head !== :call || first(expr.args) !== :(=>)
error("@with expects arguments of the form `A => 2` got $expr")
end
end
exprs = map(esc, exprs)
quote
ct = $(Base.current_task)()
current_scope = ct.scope::$(Union{Nothing, Scope})
ct.scope = $(Scope)(current_scope, $(exprs...))
$(Expr(:tryfinally, esc(ex), :(ct.scope = current_scope)))
end
Expr(:tryfinally, esc(ex), :(), :(Scope(Core.current_scope()::Union{Nothing, Scope}, $(exprs...))))
end

"""
with(f, (var::ScopedValue{T} => val::T)...)

Execute `f` in a new scope with `var` set to `val`.
"""
function with(f, pair::Pair{<:ScopedValue}, rest::Pair{<:ScopedValue}...)
@with(pair, rest..., f())
end
with(@nospecialize(f)) = f()

end # module ScopedValues
9 changes: 9 additions & 0 deletions base/task.jl
Original file line number Diff line number Diff line change
Expand Up @@ -180,11 +180,20 @@ end
elseif field === :exception
# TODO: this field name should be deprecated in 2.0
return t._isexception ? t.result : nothing
elseif field === :scope
error("Querying `scope` is disallowed. Use `current_scope` instead.")
else
return getfield(t, field)
end
end

@inline function setproperty!(t::Task, field::Symbol, @nospecialize(v))
if field === :scope
istaskstarted(t) && error("Setting scope on a started task directly is disallowed.")
end
return @invoke setproperty!(t::Any, field, v)
end

"""
istaskdone(t::Task) -> Bool

Expand Down
4 changes: 4 additions & 0 deletions src/ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -606,7 +606,11 @@ static jl_value_t *scm_to_julia_(fl_context_t *fl_ctx, value_t e, jl_module_t *m
else if (sym == jl_enter_sym) {
ex = scm_to_julia_(fl_ctx, car_(e), mod);
temp = jl_new_struct_uninit(jl_enternode_type);
jl_enternode_scope(temp) = NULL;
jl_enternode_catch_dest(temp) = jl_unbox_long(ex);
if (n == 2) {
jl_enternode_scope(temp) = scm_to_julia(fl_ctx, car_(cdr_(e)), mod);
}
}
else if (sym == jl_newvar_sym) {
ex = scm_to_julia_(fl_ctx, car_(e), mod);
Expand Down
1 change: 1 addition & 0 deletions src/builtin_proto.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ DECLARE_BUILTIN(setglobal);
DECLARE_BUILTIN(finalizer);
DECLARE_BUILTIN(_compute_sparams);
DECLARE_BUILTIN(_svec_ref);
DECLARE_BUILTIN(current_scope);

JL_CALLABLE(jl_f__structtype);
JL_CALLABLE(jl_f__abstracttype);
Expand Down
7 changes: 7 additions & 0 deletions src/builtins.c
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,12 @@ JL_CALLABLE(jl_f_ifelse)
return (args[0] == jl_false ? args[2] : args[1]);
}

JL_CALLABLE(jl_f_current_scope)
{
JL_NARGS(current_scope, 0, 0);
return jl_current_task->scope;
}

// apply ----------------------------------------------------------------------

static NOINLINE jl_svec_t *_copy_to(size_t newalloc, jl_value_t **oldargs, size_t oldalloc)
Expand Down Expand Up @@ -2158,6 +2164,7 @@ void jl_init_primitives(void) JL_GC_DISABLED
add_builtin_func("finalizer", jl_f_finalizer);
add_builtin_func("_compute_sparams", jl_f__compute_sparams);
add_builtin_func("_svec_ref", jl_f__svec_ref);
add_builtin_func("current_scope", jl_f_current_scope);

// builtin types
add_builtin("Any", (jl_value_t*)jl_any_type);
Expand Down
Loading