Skip to content

Commit 16e61e2

Browse files
KenoJeffBezansonoscardssmith
authored
Outline potentially undefined globals during lowering (#51970)
Currently [1] it is illegal [2] in IRCode to have a GlobalRef in value position that could potentially throw. This is because in IRCode, we want to assign flags to every statement and if there are multiple things with effects in a statement, we lose precision in tracking which they apply to. However, we currently do allow this in `CodeInfo`. Now that we're starting to make more use of flags in inference also, this is becoming annoying (as it did for IRCode), so I would like to do this transformation earlier. This is an attempt to do this during lowering. It is not entirely clear that this is precisely the correct place for it. We could alternatively consider doing it during the global resolve pass in method.c, but that currently does not renumber SSAValues, so doing it during the renumbering inside lowering may be easier. N.B.: This is against #51853, because this needs some of the inference precision improvements in that PR to avoid regressing the try/catch elision tests (which before that PR, we were incorrectly computing effects for statement-position GlobalRefs). [1] 39c278b [2] https://github.com/JuliaLang/julia/blob/2f63cc99fb134fb4adb7f11ba86a4e2ab5adcd48/base/compiler/ssair/verify.jl#L54-L58 --------- Co-authored-by: Jeff Bezanson <jeff.bezanson@gmail.com> Co-authored-by: Oscar Smith <oscardssmith@gmail.com>
1 parent 8944a22 commit 16e61e2

File tree

13 files changed

+185
-94
lines changed

13 files changed

+185
-94
lines changed

base/compiler/abstractinterpretation.jl

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ function from_interprocedural!(interp::AbstractInterpreter, @nospecialize(rt), s
320320
arginfo::ArgInfo, @nospecialize(maybecondinfo))
321321
rt = collect_limitations!(rt, sv)
322322
if isa(rt, InterMustAlias)
323-
rt = from_intermustalias(rt, arginfo)
323+
rt = from_intermustalias(rt, arginfo, sv)
324324
elseif is_lattice_bool(ipo_lattice(interp), rt)
325325
if maybecondinfo === nothing
326326
rt = widenconditional(rt)
@@ -340,10 +340,10 @@ function collect_limitations!(@nospecialize(typ), sv::InferenceState)
340340
return typ
341341
end
342342

343-
function from_intermustalias(rt::InterMustAlias, arginfo::ArgInfo)
343+
function from_intermustalias(rt::InterMustAlias, arginfo::ArgInfo, sv::AbsIntState)
344344
fargs = arginfo.fargs
345345
if fargs !== nothing && 1 rt.slot length(fargs)
346-
arg = fargs[rt.slot]
346+
arg = ssa_def_slot(fargs[rt.slot], sv)
347347
if isa(arg, SlotNumber)
348348
argtyp = widenslotwrapper(arginfo.argtypes[rt.slot])
349349
if rt.vartyp argtyp
@@ -1334,6 +1334,9 @@ function ssa_def_slot(@nospecialize(arg), sv::InferenceState)
13341334
return arg
13351335
end
13361336

1337+
# No slots in irinterp
1338+
ssa_def_slot(@nospecialize(arg), sv::IRInterpretationState) = nothing
1339+
13371340
struct AbstractIterationResult
13381341
cti::Vector{Any}
13391342
info::MaybeAbstractIterationInfo
@@ -1743,7 +1746,7 @@ function abstract_call_builtin(interp::AbstractInterpreter, f::Builtin, (; fargs
17431746
a3 = argtypes[3]
17441747
if isa(a3, Const)
17451748
if rt !== Bottom && !isalreadyconst(rt)
1746-
var = fargs[2]
1749+
var = ssa_def_slot(fargs[2], sv)
17471750
if isa(var, SlotNumber)
17481751
vartyp = widenslotwrapper(argtypes[2])
17491752
fldidx = maybe_const_fldidx(vartyp, a3.val)
@@ -3047,17 +3050,18 @@ function typeinf_local(interp::AbstractInterpreter, frame::InferenceState)
30473050
@goto branch
30483051
elseif isa(stmt, GotoIfNot)
30493052
condx = stmt.cond
3053+
condxslot = ssa_def_slot(condx, frame)
30503054
condt = abstract_eval_value(interp, condx, currstate, frame)
30513055
if condt === Bottom
30523056
ssavaluetypes[currpc] = Bottom
30533057
empty!(frame.pclimitations)
30543058
@goto find_next_bb
30553059
end
30563060
orig_condt = condt
3057-
if !(isa(condt, Const) || isa(condt, Conditional)) && isa(condx, SlotNumber)
3061+
if !(isa(condt, Const) || isa(condt, Conditional)) && isa(condxslot, SlotNumber)
30583062
# if this non-`Conditional` object is a slot, we form and propagate
30593063
# the conditional constraint on it
3060-
condt = Conditional(condx, Const(true), Const(false))
3064+
condt = Conditional(condxslot, Const(true), Const(false))
30613065
end
30623066
condval = maybe_extract_const_bool(condt)
30633067
nothrow = (condval !== nothing) || (𝕃ᵢ, orig_condt, Bool)

base/compiler/utilities.jl

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -446,9 +446,12 @@ function find_ssavalue_uses(e::PhiNode, uses::Vector{BitSet}, line::Int)
446446
end
447447
end
448448

449-
function is_throw_call(e::Expr)
449+
function is_throw_call(e::Expr, code::Vector{Any})
450450
if e.head === :call
451451
f = e.args[1]
452+
if isa(f, SSAValue)
453+
f = code[f.id]
454+
end
452455
if isa(f, GlobalRef)
453456
ff = abstract_eval_globalref_type(f)
454457
if isa(ff, Const) && ff.val === Core.throw
@@ -478,7 +481,7 @@ function find_throw_blocks(code::Vector{Any}, handler_at::Vector{Int})
478481
end
479482
elseif s.head === :return
480483
# see `ReturnNode` handling
481-
elseif is_throw_call(s)
484+
elseif is_throw_call(s, code)
482485
if handler_at[i] == 0
483486
push!(stmts, i)
484487
end

base/reflection.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2081,6 +2081,8 @@ function bodyfunction(basemethod::Method)
20812081
else
20822082
return nothing
20832083
end
2084+
elseif isa(fsym, Core.SSAValue)
2085+
fsym = ast.code[fsym.id]
20842086
else
20852087
return nothing
20862088
end

doc/src/base/reflection.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ Finally, the [`Meta.lower`](@ref) function gives the `lowered` form of any expre
9797
particular interest for understanding how language constructs map to primitive operations such
9898
as assignments, branches, and calls:
9999

100-
```jldoctest
100+
```jldoctest; setup = (using Base: +, sin)
101101
julia> Meta.lower(@__MODULE__, :( [1+2, sin(0.5)] ))
102102
:($(Expr(:thunk, CodeInfo(
103103
@ none within `top-level scope`

src/ast.c

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -153,17 +153,61 @@ static jl_value_t *scm_to_julia(fl_context_t *fl_ctx, value_t e, jl_module_t *mo
153153
static value_t julia_to_scm(fl_context_t *fl_ctx, jl_value_t *v);
154154
static jl_value_t *jl_expand_macros(jl_value_t *expr, jl_module_t *inmodule, struct macroctx_stack *macroctx, int onelevel, size_t world, int throw_load_error);
155155

156+
static jl_sym_t *scmsym_to_julia(fl_context_t *fl_ctx, value_t s)
157+
{
158+
assert(issymbol(s));
159+
if (fl_isgensym(fl_ctx, s)) {
160+
char gsname[16];
161+
char *n = uint2str(&gsname[1], sizeof(gsname)-1,
162+
((gensym_t*)ptr(s))->id, 10);
163+
*(--n) = '#';
164+
return jl_symbol(n);
165+
}
166+
return jl_symbol(symbol_name(fl_ctx, s));
167+
}
168+
156169
static value_t fl_defined_julia_global(fl_context_t *fl_ctx, value_t *args, uint32_t nargs)
157170
{
158171
// tells whether a var is defined in and *by* the current module
159172
argcount(fl_ctx, "defined-julia-global", nargs, 1);
160173
(void)tosymbol(fl_ctx, args[0], "defined-julia-global");
161174
jl_ast_context_t *ctx = jl_ast_ctx(fl_ctx);
162-
jl_sym_t *var = jl_symbol(symbol_name(fl_ctx, args[0]));
175+
jl_sym_t *var = scmsym_to_julia(fl_ctx, args[0]);
163176
jl_binding_t *b = jl_get_module_binding(ctx->module, var, 0);
164177
return (b != NULL && jl_atomic_load_relaxed(&b->owner) == b) ? fl_ctx->T : fl_ctx->F;
165178
}
166179

180+
static value_t fl_nothrow_julia_global(fl_context_t *fl_ctx, value_t *args, uint32_t nargs)
181+
{
182+
// tells whether a var is defined, in the sense that accessing it is nothrow
183+
// can take either a symbol or a module and a symbol
184+
jl_ast_context_t *ctx = jl_ast_ctx(fl_ctx);
185+
jl_module_t *mod = ctx->module;
186+
jl_sym_t *var = NULL;
187+
if (nargs == 1) {
188+
(void)tosymbol(fl_ctx, args[0], "nothrow-julia-global");
189+
var = scmsym_to_julia(fl_ctx, args[0]);
190+
}
191+
else {
192+
argcount(fl_ctx, "nothrow-julia-global", nargs, 2);
193+
value_t argmod = args[0];
194+
if (iscvalue(argmod) && cv_class((cvalue_t*)ptr(argmod)) == jl_ast_ctx(fl_ctx)->jvtype) {
195+
mod = *(jl_module_t**)cv_data((cvalue_t*)ptr(argmod));
196+
JL_GC_PROMISE_ROOTED(mod);
197+
} else {
198+
(void)tosymbol(fl_ctx, argmod, "nothrow-julia-global");
199+
if (scmsym_to_julia(fl_ctx, argmod) != jl_thismodule_sym) {
200+
lerrorf(fl_ctx, fl_ctx->ArgError, "nothrow-julia-global: Unknown globalref module kind");
201+
}
202+
}
203+
(void)tosymbol(fl_ctx, args[1], "nothrow-julia-global");
204+
var = scmsym_to_julia(fl_ctx, args[1]);
205+
}
206+
jl_binding_t *b = jl_get_module_binding(mod, var, 0);
207+
b = b ? jl_atomic_load_relaxed(&b->owner) : NULL;
208+
return b != NULL && jl_atomic_load_relaxed(&b->value) != NULL ? fl_ctx->T : fl_ctx->F;
209+
}
210+
167211
static value_t fl_current_module_counter(fl_context_t *fl_ctx, value_t *args, uint32_t nargs) JL_NOTSAFEPOINT
168212
{
169213
jl_ast_context_t *ctx = jl_ast_ctx(fl_ctx);
@@ -210,6 +254,7 @@ static jl_value_t *scm_to_julia_(fl_context_t *fl_ctx, value_t e, jl_module_t *m
210254

211255
static const builtinspec_t julia_flisp_ast_ext[] = {
212256
{ "defined-julia-global", fl_defined_julia_global }, // TODO: can we kill this safepoint
257+
{ "nothrow-julia-global", fl_nothrow_julia_global },
213258
{ "current-julia-module-counter", fl_current_module_counter },
214259
{ "julia-scalar?", fl_julia_scalar },
215260
{ "julia-current-file", fl_julia_current_file },
@@ -421,20 +466,6 @@ JL_DLLEXPORT void fl_profile(const char *fname)
421466
jl_ast_ctx_leave(ctx);
422467
}
423468

424-
425-
static jl_sym_t *scmsym_to_julia(fl_context_t *fl_ctx, value_t s)
426-
{
427-
assert(issymbol(s));
428-
if (fl_isgensym(fl_ctx, s)) {
429-
char gsname[16];
430-
char *n = uint2str(&gsname[1], sizeof(gsname)-1,
431-
((gensym_t*)ptr(s))->id, 10);
432-
*(--n) = '#';
433-
return jl_symbol(n);
434-
}
435-
return jl_symbol(symbol_name(fl_ctx, s));
436-
}
437-
438469
static jl_value_t *scm_to_julia(fl_context_t *fl_ctx, value_t e, jl_module_t *mod)
439470
{
440471
jl_value_t *v = NULL;

src/jlfrontend.scm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232
;; this is overwritten when we run in actual julia
3333
(define (defined-julia-global v) #f)
34+
(define (nothrow-julia-global v) #f)
3435
(define (julia-current-file) 'none)
3536
(define (julia-current-line) 0)
3637

src/julia-syntax.scm

Lines changed: 51 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4254,17 +4254,21 @@ f(x) = yt(x)
42544254
(else (for-each linearize (cdr e))))
42554255
e)
42564256

4257+
;; N.B.: This assumes that resolve-scopes has run, so outerref is equivalent to
4258+
;; a global in the current scope.
42574259
(define (valid-ir-argument? e)
4258-
(or (simple-atom? e) (symbol? e)
4260+
(or (simple-atom? e)
4261+
(and (outerref? e) (nothrow-julia-global (cadr e)))
4262+
(and (globalref? e) (nothrow-julia-global (cadr e) (caddr e)))
42594263
(and (pair? e)
4260-
(memq (car e) '(quote inert top core globalref outerref
4264+
(memq (car e) '(quote inert top core
42614265
slot static_parameter)))))
42624266

42634267
(define (valid-ir-rvalue? lhs e)
42644268
(or (ssavalue? lhs)
42654269
(valid-ir-argument? e)
42664270
(and (symbol? lhs) (pair? e)
4267-
(memq (car e) '(new splatnew the_exception isdefined call invoke foreigncall cfunction gc_preserve_begin copyast new_opaque_closure)))))
4271+
(memq (car e) '(new splatnew the_exception isdefined call invoke foreigncall cfunction gc_preserve_begin copyast new_opaque_closure globalref outerref)))))
42684272

42694273
(define (valid-ir-return? e)
42704274
;; returning lambda directly is needed for @generated
@@ -4410,48 +4414,59 @@ f(x) = yt(x)
44104414
(else (string "\"" h "\" expression"))))
44114415
(if (not (null? (cadr lam)))
44124416
(error (string (head-to-text (car e)) " not at top level"))))
4417+
(define (valid-body-ir-argument? aval)
4418+
(or (valid-ir-argument? aval)
4419+
(and (symbol? aval) ; Arguments are always defined slots
4420+
(or (memq aval (lam:args lam))
4421+
(let ((vi (get vinfo-table aval #f)))
4422+
(and vi (vinfo:never-undef vi)))))))
4423+
(define (single-assign-var? aval)
4424+
(and (symbol? aval) ; Arguments are always sa
4425+
(or (memq aval (lam:args lam))
4426+
(let ((vi (get vinfo-table aval #f)))
4427+
(and vi (vinfo:sa vi))))))
4428+
;; TODO: We could also allow const globals here
4429+
(define (const-read-arg? x)
4430+
;; Even if we have side effects, we know that singly-assigned
4431+
;; locals cannot be affected them, so we can inline them anyway.
4432+
(or (simple-atom? x) (single-assign-var? x)
4433+
(and (pair? x)
4434+
(memq (car x) '(quote inert top core)))))
44134435
;; evaluate the arguments of a call, creating temporary locations as needed
44144436
(define (compile-args lst break-labels)
44154437
(if (null? lst) '()
4416-
(let ((simple? (every (lambda (x) (or (simple-atom? x) (symbol? x)
4417-
(and (pair? x)
4418-
(memq (car x) '(quote inert top core globalref outerref)))))
4419-
lst)))
4420-
(let loop ((lst lst)
4421-
(vals '()))
4422-
(if (null? lst)
4423-
(reverse! vals)
4424-
(let* ((arg (car lst))
4425-
(aval (or (compile arg break-labels #t #f)
4426-
;; TODO: argument exprs that don't yield a value?
4427-
'(null))))
4428-
(loop (cdr lst)
4429-
(cons (if (and (not simple?)
4430-
(not (simple-atom? arg))
4431-
(not (simple-atom? aval))
4432-
(not (and (pair? arg)
4433-
(memq (car arg) '(quote inert top core))))
4434-
(not (and (symbol? aval) ;; function args are immutable and always assigned
4435-
(memq aval (lam:args lam))))
4436-
(not (and (or (symbol? arg)
4437-
(and (pair? arg)
4438-
(memq (car arg) '(globalref outerref))))
4439-
(or (null? (cdr lst))
4440-
(null? vals)))))
4441-
(let ((tmp (make-ssavalue)))
4442-
(emit `(= ,tmp ,aval))
4443-
tmp)
4444-
aval)
4445-
vals))))))))
4438+
;; First check if all the arguments as simple (and therefore side-effect free).
4439+
;; Otherwise, we need to use ssa values for all arguments to ensure proper
4440+
;; left-to-right evaluation semantics.
4441+
(let ((simple? (every (lambda (x) (or (simple-atom? x) (symbol? x)
4442+
(and (pair? x)
4443+
(memq (car x) '(quote inert top core globalref outerref)))))
4444+
lst)))
4445+
(let loop ((lst lst)
4446+
(vals '()))
4447+
(if (null? lst)
4448+
(reverse! vals)
4449+
(let* ((arg (car lst))
4450+
(aval (or (compile arg break-labels #t #f)
4451+
;; TODO: argument exprs that don't yield a value?
4452+
'(null))))
4453+
(loop (cdr lst)
4454+
(cons (if (and
4455+
(or simple? (const-read-arg? aval))
4456+
(valid-body-ir-argument? aval))
4457+
aval
4458+
(let ((tmp (make-ssavalue)))
4459+
(emit `(= ,tmp ,aval))
4460+
tmp))
4461+
vals))))))))
44464462
(define (compile-cond ex break-labels)
44474463
(let ((cnd (or (compile ex break-labels #t #f)
44484464
;; TODO: condition exprs that don't yield a value?
44494465
'(null))))
4450-
(if (not (valid-ir-argument? cnd))
4466+
(if (valid-body-ir-argument? cnd) cnd
44514467
(let ((tmp (make-ssavalue)))
44524468
(emit `(= ,tmp ,cnd))
4453-
tmp)
4454-
cnd)))
4469+
tmp))))
44554470
(define (emit-cond cnd break-labels endl)
44564471
(let* ((cnd (if (and (pair? cnd) (eq? (car cnd) 'block))
44574472
(flatten-ex 'block cnd)

src/toplevel.c

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ JL_DLLEXPORT jl_module_t *jl_base_relative_to(jl_module_t *m)
352352
return jl_top_module;
353353
}
354354

355-
static void expr_attributes(jl_value_t *v, int *has_ccall, int *has_defs, int *has_opaque)
355+
static void expr_attributes(jl_value_t *v, jl_array_t *body, int *has_ccall, int *has_defs, int *has_opaque)
356356
{
357357
if (!jl_is_expr(v))
358358
return;
@@ -390,6 +390,9 @@ static void expr_attributes(jl_value_t *v, int *has_ccall, int *has_defs, int *h
390390
else if (head == jl_call_sym && jl_expr_nargs(e) > 0) {
391391
jl_value_t *called = NULL;
392392
jl_value_t *f = jl_exprarg(e, 0);
393+
if (jl_is_ssavalue(f)) {
394+
f = jl_array_ptr_ref(body, ((jl_ssavalue_t*)f)->id - 1);
395+
}
393396
if (jl_is_globalref(f)) {
394397
jl_module_t *mod = jl_globalref_mod(f);
395398
jl_sym_t *name = jl_globalref_name(f);
@@ -417,7 +420,7 @@ static void expr_attributes(jl_value_t *v, int *has_ccall, int *has_defs, int *h
417420
for (i = 0; i < jl_array_nrows(e->args); i++) {
418421
jl_value_t *a = jl_exprarg(e, i);
419422
if (jl_is_expr(a))
420-
expr_attributes(a, has_ccall, has_defs, has_opaque);
423+
expr_attributes(a, body, has_ccall, has_defs, has_opaque);
421424
}
422425
}
423426

@@ -431,7 +434,7 @@ int jl_code_requires_compiler(jl_code_info_t *src, int include_force_compile)
431434
return 1;
432435
for(i=0; i < jl_array_nrows(body); i++) {
433436
jl_value_t *stmt = jl_array_ptr_ref(body,i);
434-
expr_attributes(stmt, &has_ccall, &has_defs, &has_opaque);
437+
expr_attributes(stmt, body, &has_ccall, &has_defs, &has_opaque);
435438
if (has_ccall)
436439
return 1;
437440
}
@@ -454,7 +457,7 @@ static void body_attributes(jl_array_t *body, int *has_ccall, int *has_defs, int
454457
*has_loops = 1;
455458
}
456459
}
457-
expr_attributes(stmt, has_ccall, has_defs, has_opaque);
460+
expr_attributes(stmt, body, has_ccall, has_defs, has_opaque);
458461
}
459462
*forced_compile = jl_has_meta(body, jl_force_compile_sym);
460463
}

stdlib/Test/src/Test.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1814,7 +1814,7 @@ matches the inferred type modulo `AllowedType`, or when the return type is a sub
18141814
`AllowedType`. This is useful when testing type stability of functions returning a small
18151815
union such as `Union{Nothing, T}` or `Union{Missing, T}`.
18161816
1817-
```jldoctest; setup = :(using InteractiveUtils), filter = r"begin\\n(.|\\n)*end"
1817+
```jldoctest; setup = :(using InteractiveUtils; using Base: >), filter = r"begin\\n(.|\\n)*end"
18181818
julia> f(a) = a > 1 ? 1 : 1.0
18191819
f (generic function with 1 method)
18201820

0 commit comments

Comments
 (0)