Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Outline potentially undefined globals during lowering #51970

Merged
merged 13 commits into from
Nov 11, 2023
Merged

Conversation

Keno
Copy link
Member

@Keno Keno commented Nov 1, 2023

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]

elseif isa(op, GlobalRef)
if !isdefined(op.mod, op.name) || !isconst(op.mod, op.name)
@verify_error "Unbound GlobalRef not allowed in value position"
error("")
end

@Keno Keno requested a review from JeffBezanson November 1, 2023 04:15
src/ast.c Outdated
jl_ast_context_t *ctx = jl_ast_ctx(fl_ctx);
jl_sym_t *var = jl_symbol(symbol_name(fl_ctx, args[0]));
jl_binding_t *b = jl_get_module_binding(ctx->module, var, 0);
return b != NULL ? fl_ctx->T : fl_ctx->F;
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
return b != NULL ? fl_ctx->T : fl_ctx->F;
return b != NULL && b->value != NULL ? fl_ctx->T : fl_ctx->F;

@@ -5006,6 +5006,15 @@ f(x) = yt(x)
(set! code (cons e code))
(set! i (+ i 1))
(set! locs (cons current-loc locs)))
(define (maybe-outline-outerref e)
(if (and (outerref? e) (not (nothrow-julia-global (cadr e))))
Copy link
Member

Choose a reason for hiding this comment

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

I think this logic should go in compile-args instead.

@@ -2081,6 +2081,8 @@ function bodyfunction(basemethod::Method)
else
return nothing
end
elseif isa(fsym, Core.SSAValue)
fsym = ast.code[fsym.id]
Copy link
Member

Choose a reason for hiding this comment

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

We might want to add the ability to annotate uses as nothrow, for auto-generated globals we know at lowering time will be defined before they're used.

src/ast.c Outdated
(void)tosymbol(fl_ctx, args[0], "nothrow-julia-global");
jl_ast_context_t *ctx = jl_ast_ctx(fl_ctx);
jl_sym_t *var = jl_symbol(symbol_name(fl_ctx, args[0]));
jl_binding_t *b = jl_get_module_binding(ctx->module, var, 0);
Copy link
Member

Choose a reason for hiding this comment

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

    jl_binding_t *b = jl_get_binding(ctx->module, var);

you need the real binding, not the stub

Copy link
Member Author

Choose a reason for hiding this comment

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

Doesn't that cause the binding to resolve? Currently merely lowering a function that mentions a symbol does not resolve that binding in case you want to define it to something else later.

Copy link
Member

Choose a reason for hiding this comment

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

alright, jl_get_binding_if_bound then

@@ -2998,6 +2998,8 @@ end
@test ig27907(Int, Int, 1, 0) == 0

# issue #28279
# ensure that lowering doesn't move these into statement position, which would require renumbering
using Base: +, -
Copy link
Member

Choose a reason for hiding this comment

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

This state dependence, where we give very different lowering depending on whether something has been used already in the module, seems very ripe for causing future heisenbugs

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 agree, but on the other hand, this is supposed to be a storage optimization without semantic effect (though of course there can be bugs).

Copy link
Member

Choose a reason for hiding this comment

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

True. I guess in the future we may want to optimize cases like this more aggressively pre-inference too. I just worry about cases where people may pre-lower the code, then ship it off to another process to execute which as slightly different globals at the time. I think Jeff had some code where he was planning to do that and Distributed.jl does that for remotecall.

Keno added a commit that referenced this pull request Nov 2, 2023
…hi block

We don't fully set up the interpreter state when evaluating the phi block,
because we expect all statements to be valid in value-position (using
the IR definition of value position). However, the interpreter was
overapproximating the size of the phi-block, leading to the possibility
that the evaluation may throw. Correct that by truncating the phi
block to the actual last phi. Will be tested in existing tests after
#51970.
@Keno
Copy link
Member Author

Keno commented Nov 2, 2023

Looks like the sysimage size increase is about 1%. We could claw some of that back with more precise never-undef analysis, but I'm not sure it's worth it at this stage. If lowering will get rewritten in julia eventually anyway, we might as well do it then, since we have a much better algorithm for this in julia already.

Keno added a commit that referenced this pull request Nov 2, 2023
…hi block

We don't fully set up the interpreter state when evaluating the phi block,
because we expect all statements to be valid in value-position (using
the IR definition of value position). However, the interpreter was
overapproximating the size of the phi-block, leading to the possibility
that the evaluation may throw. Correct that by truncating the phi
block to the actual last phi. Will be tested in existing tests after
#51970.
@Keno
Copy link
Member Author

Keno commented Nov 2, 2023

I think this is largely complete. However, we should merge #51990 and #51991 first and then rebase this.

@vtjnash
Copy link
Member

vtjnash commented Nov 3, 2023

If you have a chance, how many megabytes larger is that, just for reference? It seems okay, and especially so that it is worthwhile

@Keno
Copy link
Member Author

Keno commented Nov 3, 2023

1.5MB

Base automatically changed from kf/51852 to master November 5, 2023 17:01
@oscardssmith oscardssmith added compiler:lowering Syntax lowering (compiler front end, 2nd stage) bugfix This change fixes an existing bug compiler:optimizer Optimization passes (mostly in base/compiler/ssair/) labels Nov 5, 2023
oscardssmith pushed a commit that referenced this pull request Nov 6, 2023
…hi block (#51990)

We don't fully set up the interpreter state when evaluating the phi
block, because we expect all statements to be valid in value-position
(using the IR definition of value position). However, the interpreter
was overapproximating the size of the phi-block, leading to the
possibility that the evaluation may throw. Correct that by truncating
the phi block to the actual last phi. Will be tested in existing tests
after #51970.
@oscardssmith
Copy link
Member

Is this ready to merge?

(and (pair? e)
(memq (car e) '(quote inert top core globalref outerref
(memq (car e) '(quote inert top core
slot static_parameter)))))
Copy link
Member

Choose a reason for hiding this comment

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

Should we remove static_parameter from here since it can also throw?

Copy link
Member Author

Choose a reason for hiding this comment

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

Hmm, I suppose, but for some reason I always see static parameters getting moved out anyway.

(define (valid-ir-argument? e)
(or (simple-atom? e) (symbol? e)
(or (simple-atom? e)
(and (outerref? e) (nothrow-julia-global (cadr e)))
Copy link
Member

Choose a reason for hiding this comment

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

nothrow-julia-global should be able to accept a globalref as well.

(cons (if (and
;; Even if we have side effects, we know that singly-assigned
;; locals cannot be affected them, so we can inline them anyway.
(or simple? (single-assign-var? aval))
Copy link
Member

Choose a reason for hiding this comment

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

I think this or should also accept simple-atom, quote, inert, top and core? (Everything in valid-ir-argument except globals, slots, and static_parameter.)

Copy link
Member

Choose a reason for hiding this comment

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

Tried it and it shaves ~450k off the sysimage.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, that seems reasonable

Keno added 5 commits November 9, 2023 02:52
In #51852, we are coercing a boxed `Union{@NamedTuple{progress::String}, @NamedTuple{progress::Float64}}`
to `@NamedTuple{progress::String}` via convert_julia_type. This results in a jl_cgval_t that has
a Vboxed that points to a boxed `@NamedTuple{progress::Float64}` but with a `@NamedTuple{progress::String}`
type tag that the up upsilonnode code then tries to unbox into the typed PhiC slot. This ends up treating
the Float64 as a pointer and crashing in GC. Avoid this by adding a runtime check that the converted value
is actually compatible (we already had this kind of check for the non-boxed cases) and then making the
unboxing runtime-conditional on the type of the union matching the type of the phic.

Fixes #51852
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.

[1] 39c278b
[2] https://github.com/JuliaLang/julia/blob/2f63cc99fb134fb4adb7f11ba86a4e2ab5adcd48/base/compiler/ssair/verify.jl#L54-L58
…hi block

We don't fully set up the interpreter state when evaluating the phi block,
because we expect all statements to be valid in value-position (using
the IR definition of value position). However, the interpreter was
overapproximating the size of the phi-block, leading to the possibility
that the evaluation may throw. Correct that by truncating the phi
block to the actual last phi. Will be tested in existing tests after
#51970.
@Keno Keno force-pushed the kf/outlineglobals branch from ea1b1f7 to 9c40f43 Compare November 9, 2023 04:20
@Keno Keno changed the title WIP: Outline potentially undefined globals during lowering Outline potentially undefined globals during lowering Nov 9, 2023
@Keno Keno force-pushed the kf/outlineglobals branch from 9c40f43 to a0c6204 Compare November 9, 2023 04:22
@Keno Keno merged commit 16e61e2 into master Nov 11, 2023
6 of 7 checks passed
@Keno Keno deleted the kf/outlineglobals branch November 11, 2023 00:47
aviatesk added a commit to aviatesk/JET.jl that referenced this pull request Nov 14, 2023
Pangoraw added a commit to Pangoraw/IRTools.jl that referenced this pull request Dec 11, 2023
In particular, JuliaLang/julia#51970 which
adds more nodes in lowered ir.
aviatesk added a commit that referenced this pull request Jan 4, 2024
After #51970, `Expr(:static_parameter, i::Int)` is now consistently
outlined during the lowering, so there's no longer a need for
`abstract_eval_value_expr` to handle this expression. This update
removes the outdated handling, clarifying where we need to handle
the expression.
aviatesk added a commit that referenced this pull request Jan 4, 2024
After #51970, `Expr(:static_parameter, i::Int)` is now consistently
outlined during the lowering, so there's no longer a need for
`abstract_eval_value_expr` to handle this expression. This update
removes the outdated handling, clarifying where we need to handle
the expression.
aviatesk added a commit that referenced this pull request Jan 5, 2024
After #51970, `Expr(:static_parameter, i::Int)` is now consistently
outlined during the lowering, so there's no longer a need for
`abstract_eval_value_expr` to handle this expression. This update
removes the outdated handling, clarifying where we need to handle the
expression.
Keno added a commit that referenced this pull request Nov 29, 2024
This partially reverts #51970, once again allowing throwing GlobalRefs
in value position for `CodeInfo`, (but not `IRCode`). The primary motivation
for this reversal is that after binding partition the throwiness of
a global is world-age dependent and we do not want to have lowering
be world-age dependent, because that would require us to rewrite
the lowered code upon invalidation (#56649).

With respect to the original motivation for this change (being able
to use the statement flag in inference), we retain the property
that the statement flags apply only to the optimizer version
of the statement (i.e. with the GlobalRefs moved out). The only
place in inference where we were not doing this, we can rely
on `exct` instead. This does assume that `exct` is precise, but
we've done a lot of work on that in the past year, so hopefully
we can rely on that now.

The revert is partial, because we go in the opposite direction in
a few places: For `GotoIfNot` cond and `EnterNode` scope operands, we
just forbid any `GlobalRef` entirely. Constants are rare in these,
so no need to burden inference with the possibility of handling
these. We do however, keep (and add appropriate handling code)
for GlobalRef arguments to `ReturnNode` to make sure that trivial
code remains one statement.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bugfix This change fixes an existing bug compiler:lowering Syntax lowering (compiler front end, 2nd stage) compiler:optimizer Optimization passes (mostly in base/compiler/ssair/)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants