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

Make world-age increments explicit #56509

Merged
merged 6 commits into from
Nov 21, 2024
Merged

Make world-age increments explicit #56509

merged 6 commits into from
Nov 21, 2024

Conversation

Keno
Copy link
Member

@Keno Keno commented Nov 9, 2024

This PR introduces a new, toplevel-only, syntax form :worldinc that semantically represents the effect of raising the current task's world age to the latest world for the remainder of the current toplevel evaluation (that context being an entry to eval or a module expression). For detailed motivation on why this is desirable, see #55145, which I won't repeat here, but the gist is that we never really defined when world-age increments and worse are inconsistent about it. This is something we need to figure out now, because the bindings partition work will make world age even more observable via bindings.

Having created a mechanism for world age increments, the big question is one of policy, i.e. when should these world age increments be inserted.

Several reasonable options exist:

  1. After world-age affecting syntax constructs (as proprosed in Make precise semantics of world-age increments in top-level thunks #55145)
  2. Option 1 + some reasonable additional cases that people rely on
  3. Before any top level call expression
  4. Before any expression at toplevel whatsover

As an example, case, consider a == a at toplevel. Depending on the semantics that could either be the same as in local scope, or each of the four world age dependent lookups (three binding lookups, one method lookup) could (potentially) occur in a different world age.

The general tradeoff here is between the risk of exposing the user to confusing world age errors and our ability to optimize top-level code (in general, any :worldinc statement will require us to fully pessimize or recompile all following code).

This PR basically implements option 2 with the following semantics:

  1. The interpreter explicit raises the world age only at :worldinc exprs or after :module exprs.
  2. The frontend inserts :worldinc after all struct definitions, method definitions, using and `import.
  3. The @eval macro inserts a worldinc following the call to eval if at toplevel
  4. A literal (syntactic) call to include gains an implicit worldinc.

Of these the fourth is probably the most questionable, but is necessary to make this non-breaking for most code patterns. Perhaps it would have been better to make include a macro from the beginning (esp because it already has semantics that look a little like reaching into the calling module), but that ship has sailed.

Unfortunately, I don't see any good intermediate options between this PR and option #3 above. I think option #3 is closest to what we have right now, but if we were to choose it and actually fix the soundness issues, I expect that we would be destroying all performance of global-scope code. For this reason, I would like to try to make the version in this PR work, even if the semantics are a little ugly.

The biggest pattern that this PR does not catch is:

eval(:(f() = 1))
f()

We could apply the same include special case to eval, but given the existence of @eval which allows addressing this at the macro level, I decided not to. We can decide which way we want to go on this based on what the package ecosystem looks like.

@Keno
Copy link
Member Author

Keno commented Nov 9, 2024

The SparseArrays test failure is known - SparseArrays.allowscalar redefines indexing methods and the test relies on that getting picked up right away. Should be changed to a macro with a worldage increment probably, but before going through the hassle of the multi repo coordination, let's finish this PR.

@Keno
Copy link
Member Author

Keno commented Nov 9, 2024

@nanosoldier runtests()

@aviatesk
Copy link
Member

aviatesk commented Nov 9, 2024

I’m not opposed to this PR, but what’s the specific reason for introducing :worldinc? From what I understand from reading #55145, it seems like modifying the runtime to adhere to these rules would avoid the discussed issues?

We stop incrementing the world age for every statement. Instead, world age (at the toplevel) is incremented to the latest world age only at the following points:
a. Entry into toplevel evaluation (i.e., at the beginning of Core.eval)
b. After any evaluation of :method
c. On the bindings branch, after introducing a new binding

(plus an increment after a :module expression as per you mentioned above)

That said, I think introducing :worldinc would make it easier to fix inference-related problems, like ensuring world-age is incremented correctly for include as done in this PR. So I’m actually in favor of adding this expression type, but I’d like to understand the reasoning clearly.

@@ -777,6 +777,7 @@ faz1(x) = foo1(x)
@test faz1(1.0) == 1
m = first(methods(bar1, Tuple{Int}))
Base.delete_method(m)
Copy link
Member

Choose a reason for hiding this comment

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

Does this mean that the result of Base.delete_method (and Base.delete_binding) won’t take effect until the user explicitly calls Core.@worldinc? That sounds like it would be a pretty breaking change. I agree with the idea of restricting the effect of :worldinc to the top level (for helping inference), but it seems like Base.delete_method might need some adjustments, such as calling @eval internally or something similar.

Copy link
Member Author

Choose a reason for hiding this comment

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

Does this mean that the result of Base.delete_method (and Base.delete_binding) won’t take effect until the user explicitly calls Core.@WorldInc?

It takes effect immediately (in a new world age), but the effect is not visible in the current task until it raises it world age.

Calling @eval internally will not raise the world age, because it's not at top level. delete_method is mostly used internally by Revise which can take an appropriate adjustment. There could also be a delete_method macro that does the world age raising implicitly.

@Keno
Copy link
Member Author

Keno commented Nov 10, 2024

but what’s the specific reason for introducing :worldinc?

Two reasons:

  1. As a separation of concerns issue and to put the more complicated world-age increment decisions into lowering rather than forcing the logic to be duplicated in several places
  2. To enable it to be inserted by macros that may want it

@Keno
Copy link
Member Author

Keno commented Nov 10, 2024

@vtjnash suggests implicit world-age increments between the args of :toplevel and :module. I think this is reasonable and I'll make an appropriate adjustment.

@Keno
Copy link
Member Author

Keno commented Nov 10, 2024

@vtjnash suggests implicit world-age increments between the args of :toplevel and :module.

Forgot to mention that @vtjnash pointed out that I already made world age increment for macro purposes in #53515, so it would be inconsistent for it not to increment the world age for execution, which I found convincing.

@nanosoldier
Copy link
Collaborator

The package evaluation job you requested has completed - possible new issues were detected.
The full report is available.

@Keno
Copy link
Member Author

Keno commented Nov 10, 2024

@nanosoldier runtests(["Tricks", "MethodInspector", "ExprTools", "OptimizingIR", "OverflowContexts", "CBOOCall", "LazyModules", "PowerSeries", "Purses", "Hygienic", "MullerPlot", "SyntaxTree", "Plugins", "ParameterisedModule", "ApproximationAnalysis", "VectorInterface", "Expronicon", "TestExtras", "Hyperspecialize", "TimerOutputs", "Behavior", "ProblemSet", "FinitePosets", "CommandLiner", "TrixiBase", "IRTools", "TypeStability", "LoweredCodeUtils", "LispSyntax", "FileIO", "Automa", "ComoniconTargetExpr", "SplittablesBase", "HAML", "JSON3", "PDDL", "Continuables", "DataSets", "DataFlowTasks", "GraphQLGen", "Revise", "ConfigurationsENV", "DebugAdapter", "LIBSVM", "FromFile", "Groups", "LazyReports", "Polyester", "Handcalcs", "PkgJogger", "IntervalLinearAlgebra", "TypeClasses", "Open62541", "MHLib", "ExtensibleEffects", "Arblib", "PlutoVista", "LegendrePolynomials", "LinkedInAPI", "SwagUI", "GraphQLClient", "Folds", "Serde", "FlatRBAC", "BenchmarkProfiles", "DataToolkitCommon", "ACSets", "SphericalHarmonicExpansions", "CrystalInfoFramework", "Experimenter", "MATDaemon", "DrelTools", "DistributedSparseGrids", "TransitionalMCMC", "AstroRepresentations", "TransformSpecifications", "PlutoPlotly", "Bukdu", "OndaEDFSchemas", "AppleHealthParser", "MultivariateChebyshev", "Onda", "AlignedSpans", "YasolSolver", "JSXGraph", "Semagrams", "Gen", "MaterialDecomposition", "CSetAutomorphisms", "GeniePlugins", "GenieSession", "GeniePackageManager", "GenieCache", "GenieDeployHeroku", "GenieCacheFileCache", "GenieDeployDocker", "GenieSessionFileSession", "InfiniteOpt", "EnergyModelsBase", "EnergyModelsCO2", "EnergyModelsRenewableProducers", "EnergyModelsGeography", "OndaEDF", "MembraneBase", "Books", "StippleMathjs", "Stipple", "StippleUI", "SparseArrayKit", "QWignerSymbols", "RegNets", "GenieDevTools", "HerbSearch", "Equate", "StippleCharts", "Herb", "StipplePlotly", "SteadyStateDiffEq", "StipplePlotlyExport", "TaylorInversion", "GenieAuthorisation", "CategoryData", "GenieFramework", "ReversePropagation", "GivEmXL", "GenericCharacterTables", "SphericalFunctions", "StippleKeplerGL", "Spehulak", "PlutoPages", "GeometryOptimization", "ProToPortal", "JuliaBUGS", "MLJTestIntegration", "CausalGPSLC", "MLJ", "WaveSpec", "Individual", "AntennaPattern", "SimpleCrop", "UnfoldBIDS", "LeafGasExchange", "Garlic", "EMpht", "Vahana", "Petri", "Lighthouse", "AffineMotions", "PlantGeomTurtle", "HetaSimulator", "Serialization", "CropRootBox"])

@nanosoldier
Copy link
Collaborator

The package evaluation job you requested has completed - possible new issues were detected.
The full report is available.

@Keno
Copy link
Member Author

Keno commented Nov 11, 2024

Alright, a fair number of tests with dependencies on JuliaInterpreter or Revise, which need to be taught about :worldinc, but also some that noticed the behavior change. I think the thing to do is

  1. an independent PR that just introduces :worldinc, but doesn't use it yet
  2. Fix JuliaInterpreter/Revise
  3. Re-run PkgEval and see what to do about the rest of the packages

Keno added a commit that referenced this pull request Nov 11, 2024
Split out from #56509 to facilitate adjusting downstream packages.
Keno added a commit that referenced this pull request Nov 11, 2024
This was part of #56509, but is an independent bugfix. The basic
issue is that these macro were using `do` block internally. This
is undesirable for test macros, because we would like them not to
affect the behavior of what they're testing. E.g. right now:
```
julia> using Test

julia> const x = 1
1

julia> @test_nowarn const x = 1
ERROR: syntax: `global const` declaration not allowed inside function around /home/keno/julia/usr/share/julia/stdlib/v1.12/Test/src/Test.jl:927
Stacktrace:
 [1] top-level scope
   @ REPL[3]:1
```

This PR just writes out the try/finally manually, so the above
works fine after this PR.
aviatesk pushed a commit that referenced this pull request Nov 11, 2024
This was part of #56509, but is an independent bugfix. The basic issue
is that these macro were using `do` block internally. This is
undesirable for test macros, because we would like them not to affect
the behavior of what they're testing. E.g. right now:
```
julia> using Test

julia> const x = 1
1

julia> @test_nowarn const x = 1
ERROR: syntax: `global const` declaration not allowed inside function around /home/keno/julia/usr/share/julia/stdlib/v1.12/Test/src/Test.jl:927
Stacktrace:
 [1] top-level scope
   @ REPL[3]:1
```

This PR just writes out the try/finally manually, so the above works
fine after this PR.
Keno added a commit that referenced this pull request Nov 12, 2024
Split out from #56509 to facilitate adjusting downstream packages.
@@ -367,6 +367,7 @@ function eval_user_input(@nospecialize(ast), backend::REPLBackend, mod::Module)
for xf in backend.ast_transforms
ast = Base.invokelatest(xf, ast)
end
toplevel_eval_with_hooks(mod, Expr(:worldinc))
Copy link
Member

Choose a reason for hiding this comment

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

This is a no-op (if the implementation is not broken)

Suggested change
toplevel_eval_with_hooks(mod, Expr(:worldinc))

Copy link
Member

@vtjnash vtjnash left a comment

Choose a reason for hiding this comment

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

Overall SGTM (though needs a rebase against the parts that are merged already).

Looks like a lot of the needed added Core.@worldinc also will be already solved by making each toplevel / module statement continue to imply an updated world (since they already imply an updated macro world, and the statements are supposed to run in the world after that macro expansion, which wouldn't be possible to express with these explicit markers anyways)

One other remaining todo item is adding this to the Compiler verifier code. I don't know if we run any debug tests on PRs, but I believe we run those on nightly, and it likely should error on the unexpected Expr head.

Keno added a commit to JuliaDebug/JuliaInterpreter.jl that referenced this pull request Nov 14, 2024
These were added in JuliaLang/julia#56523.
This simply adds tracking for the world age to the interpreter, but
does not use it for anything. JuliaInterpreter's current model
of world age does not match Base anyway. We should fix that eventually,
but it's probably easier to do that once JuliaLang/julia#56509
and binding partitions are merged.
Keno added a commit to JuliaDebug/JuliaInterpreter.jl that referenced this pull request Nov 14, 2024
* Handle `:latestworld` expr

These were added in JuliaLang/julia#56523.
This simply adds tracking for the world age to the interpreter, but
does not use it for anything. JuliaInterpreter's current model
of world age does not match Base anyway. We should fix that eventually,
but it's probably easier to do that once JuliaLang/julia#56509
and binding partitions are merged.

* 1.6 compat
Keno added a commit to timholy/Revise.jl that referenced this pull request Nov 15, 2024
Upcoming base PR JuliaLang/julia#56509 makes precise
when exactly world age increments automatically at top-level. Packages are
mostly not supposed to notice, but of course Revise is a bit patholgical in
that it does basically expect asynchronous changes to have top level effects.
After that PR, that requires an explicit opt-in. This PR makes the appropriate
adjustments to the tests.
Keno added a commit to timholy/Revise.jl that referenced this pull request Nov 15, 2024
Upcoming base PR JuliaLang/julia#56509 makes precise
when exactly world age increments automatically at top-level. Packages are
mostly not supposed to notice, but of course Revise is a bit patholgical in
that it does basically expect asynchronous changes to have top level effects.
After that PR, that requires an explicit opt-in. This PR makes the appropriate
adjustments to the tests.
@Keno
Copy link
Member Author

Keno commented Nov 18, 2024

@nanosoldier runtests(["Tricks", "MethodInspector", "ExprTools", "OptimizingIR", "OverflowContexts", "CBOOCall", "LazyModules", "PowerSeries", "Purses", "Hygienic", "MullerPlot", "SyntaxTree", "Plugins", "ParameterisedModule", "ApproximationAnalysis", "VectorInterface", "Expronicon", "TestExtras", "Hyperspecialize", "TimerOutputs", "Behavior", "ProblemSet", "FinitePosets", "CommandLiner", "TrixiBase", "IRTools", "TypeStability", "LoweredCodeUtils", "LispSyntax", "FileIO", "Automa", "ComoniconTargetExpr", "SplittablesBase", "HAML", "JSON3", "PDDL", "Continuables", "DataSets", "DataFlowTasks", "GraphQLGen", "Revise", "ConfigurationsENV", "DebugAdapter", "LIBSVM", "FromFile", "Groups", "LazyReports", "Polyester", "Handcalcs", "PkgJogger", "IntervalLinearAlgebra", "TypeClasses", "Open62541", "MHLib", "ExtensibleEffects", "Arblib", "PlutoVista", "LegendrePolynomials", "LinkedInAPI", "SwagUI", "GraphQLClient", "Folds", "Serde", "FlatRBAC", "BenchmarkProfiles", "DataToolkitCommon", "ACSets", "SphericalHarmonicExpansions", "CrystalInfoFramework", "Experimenter", "MATDaemon", "DrelTools", "DistributedSparseGrids", "TransitionalMCMC", "AstroRepresentations", "TransformSpecifications", "PlutoPlotly", "Bukdu", "OndaEDFSchemas", "AppleHealthParser", "MultivariateChebyshev", "Onda", "AlignedSpans", "YasolSolver", "JSXGraph", "Semagrams", "Gen", "MaterialDecomposition", "CSetAutomorphisms", "GeniePlugins", "GenieSession", "GeniePackageManager", "GenieCache", "GenieDeployHeroku", "GenieCacheFileCache", "GenieDeployDocker", "GenieSessionFileSession", "InfiniteOpt", "EnergyModelsBase", "EnergyModelsCO2", "EnergyModelsRenewableProducers", "EnergyModelsGeography", "OndaEDF", "MembraneBase", "Books", "StippleMathjs", "Stipple", "StippleUI", "SparseArrayKit", "QWignerSymbols", "RegNets", "GenieDevTools", "HerbSearch", "Equate", "StippleCharts", "Herb", "StipplePlotly", "SteadyStateDiffEq", "StipplePlotlyExport", "TaylorInversion", "GenieAuthorisation", "CategoryData", "GenieFramework", "ReversePropagation", "GivEmXL", "GenericCharacterTables", "SphericalFunctions", "StippleKeplerGL", "Spehulak", "PlutoPages", "GeometryOptimization", "ProToPortal", "JuliaBUGS", "MLJTestIntegration", "CausalGPSLC", "MLJ", "WaveSpec", "Individual", "AntennaPattern", "SimpleCrop", "UnfoldBIDS", "LeafGasExchange", "Garlic", "EMpht", "Vahana", "Petri", "Lighthouse", "AffineMotions", "PlantGeomTurtle", "HetaSimulator", "Serialization", "CropRootBox"])

@Keno
Copy link
Member Author

Keno commented Nov 20, 2024

(And just to complete example, on this PR, all of those example cases will return 2 as written in both the interpreter and compiler. This also matches what would happen in ordinary local scope. If you want 3, you can remove the begin/end - taking advantage of the retained implicit increment at toplevel scope - or add an explicit world age increment between the calls to foo and bar)

@Keno Keno added the minor change Marginal behavior change acceptable for a minor release label Nov 20, 2024
@Keno
Copy link
Member Author

Keno commented Nov 20, 2024

Looking good on CI. Planning to merge shortly.

@vtjnash
Copy link
Member

vtjnash commented Nov 20, 2024

Can we give some time to let the ecosystem changes percolate through (so that we don't break PkgEval too much tonight), or is this in the way of followup work?

@Keno
Copy link
Member Author

Keno commented Nov 20, 2024

I don't think it's extremely urgent or anything, but I also don't want things to get stale and I do want to get the next PR up. We already got TestExtras and Legolas which were the two packages that had downstream deps. I'll ask for tags on those. I think for the 10 other ones, they'll just need to come in as they come in, many of them are not super actively developed.

base/essentials.jl Outdated Show resolved Hide resolved
base/essentials.jl Outdated Show resolved Hide resolved
base/tuple.jl Show resolved Hide resolved
src/codegen.cpp Outdated Show resolved Hide resolved
src/toplevel.c Outdated Show resolved Hide resolved
src/toplevel.c Outdated Show resolved Hide resolved
src/toplevel.c Outdated Show resolved Hide resolved
src/toplevel.c Outdated Show resolved Hide resolved
src/toplevel.c Outdated Show resolved Hide resolved
src/toplevel.c Outdated Show resolved Hide resolved
src/toplevel.c Outdated Show resolved Hide resolved
src/interpreter.c Outdated Show resolved Hide resolved
test/ccall.jl Outdated Show resolved Hide resolved
test/core.jl Outdated Show resolved Hide resolved
test/error.jl Outdated Show resolved Hide resolved
@Keno Keno merged commit 034e609 into master Nov 21, 2024
5 of 7 checks passed
@Keno Keno deleted the kf/explicitageinc branch November 21, 2024 04:51
fingolfin pushed a commit to oscar-system/GenericCharacterTables.jl that referenced this pull request Nov 28, 2024
This test currently relies on implicit world age increments at top
level. We're re-evaluating where these go because julia is currently
inconsistent about it in the interpreter, compiler and inference.
To make sure this test keeps working on 1.12, add an explicit
world age increment. See JuliaLang/julia#56509.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
minor change Marginal behavior change acceptable for a minor release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants