Skip to content

Backports for Julia 1.12-rc2 #59006

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

Merged
merged 8 commits into from
Jul 22, 2025
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
8 changes: 7 additions & 1 deletion Compiler/src/tfuncs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,13 @@ function add_tfunc(@nospecialize(f::Builtin), minarg::Int, maxarg::Int, @nospeci
end

add_tfunc(throw, 1, 1, @nospecs((𝕃::AbstractLattice, x)->Bottom), 0)
add_tfunc(Core.throw_methoderror, 1, INT_INF, @nospecs((𝕃::AbstractLattice, x)->Bottom), 0)

# FIXME: the inlining cost for this built-in should be 0 (just like throw), but it is set to
# 1000 to avoid regressions associated with the Compiler inlining too eagerly, especially when
# encounting `if @generated` expressions.
#
# c.f. https://github.com/JuliaLang/julia/issues/58915#issuecomment-3070231392
add_tfunc(Core.throw_methoderror, 1, INT_INF, @nospecs((𝕃::AbstractLattice, x)->Bottom), 1000)

# the inverse of typeof_tfunc
# returns (type, isexact, isconcrete, istype)
Expand Down
29 changes: 29 additions & 0 deletions Compiler/test/inline.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2310,3 +2310,32 @@ g_noinline_invoke(x) = f_noinline_invoke(x)
let src = code_typed1(g_noinline_invoke, (Union{Symbol,Nothing},))
@test !any(@nospecialize(x)->isa(x,GlobalRef), src.code)
end

# https://github.com/JuliaLang/julia/issues/58915
f58915(nt) = @inline Base.setindex(nt, 2, :next)
# This function should fully-inline, i.e. it should have only built-in / intrinsic calls
# and no invokes or dynamic calls of user code
let src = code_typed(f58915, Tuple{@NamedTuple{next::UInt32,prev::UInt32}})[1].first
# Any calls should be built-in calls
@test count(iscall(f->!isa(singleton_type(argextype(f, src)), Core.Builtin)), src.code) == 0
# There should be no invoke at all
@test count(isinvoke(Returns(true)), src.code) == 0
end

# https://github.com/JuliaLang/julia/issues/58915#issuecomment-3061421895
let src = code_typed(Base.setindex, (@NamedTuple{next::UInt32,prev::UInt32}, Int, Symbol))[1].first
@test count(isinvoke(:merge_fallback), src.code) == 0
@test count(iscall((src, Base.merge_fallback)), src.code) == 0
end

# https://github.com/JuliaLang/julia/pull/58996#issuecomment-3073496955
f58996(::Int) = :int
f58996(::String) = :string
call_f58996(x) = f58996(x)
callcall_f58996(x) = call_f58996(x);
let src = code_typed(callcall_f58996, (Any,))[1].first
# Any calls should be built-in calls
@test_broken count(iscall(f->!isa(singleton_type(argextype(f, src)), Core.Builtin)), src.code) == 0
# There should be no invoke at all
@test count(isinvoke(Returns(true)), src.code) == 0
end
4 changes: 2 additions & 2 deletions base/precompilation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -920,7 +920,7 @@ function _precompilepkgs(pkgs::Vector{String},
flags, cacheflags = config
task = @async begin
try
loaded = haskey(Base.loaded_modules, pkg)
loaded = warn_loaded && haskey(Base.loaded_modules, pkg)
for dep in deps # wait for deps to finish
wait(was_processed[(dep,config)])
end
Expand Down Expand Up @@ -983,7 +983,7 @@ function _precompilepkgs(pkgs::Vector{String},
delete!(std_outputs, pkg_config) # so it's not shown as warnings, given error report
failed_deps[pkg_config] = (strict || is_project_dep) ? string(sprint(showerror, err), "\n", strip(errmsg)) : ""
!fancyprint && @lock print_lock begin
println(io, " "^9, color_string(" ✗ ", Base.error_color()), name)
println(io, " "^12, color_string(" ✗ ", Base.error_color()), name)
end
else
rethrow()
Expand Down
2 changes: 1 addition & 1 deletion base/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ function _isself(ft::DataType)
name = ftname.singletonname
ftname.name === name && return false
mod = parentmodule(ft)
return invokelatest(isdefinedglobal, mod, name) && ft === typeof(invokelatest(getglobal, mod, name))
return isdefinedglobal(mod, name) && ft === typeof(getglobal(mod, name))
end

function show(io::IO, ::MIME"text/plain", f::Function)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
b6596d36c153d476101b1db14c5d1400
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1bc6f3f3d8a40f4ce51928115e44a46b89a2a1404f0ad9fa0b8cdca4641ab42eab3ff0d9a6a2cbbb57dbaf7effcf399b188716b3fc2d18b32f750d5fb363669f

This file was deleted.

This file was deleted.

1 change: 1 addition & 0 deletions doc/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ Manual = [
"manual/code-loading.md",
"manual/profile.md",
"manual/stacktraces.md",
"manual/memory-management.md",
"manual/performance-tips.md",
"manual/workflow-tips.md",
"manual/style-guide.md",
Expand Down
4 changes: 2 additions & 2 deletions doc/src/manual/command-line-interface.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ The following is a complete list of command-line switches available when launchi
|`-m`, `--module <Package> [args]` |Run entry point of `Package` (`@main` function) with `args'|
|`-L`, `--load <file>` |Load `<file>` immediately on all processors|
|`-t`, `--threads {auto\|N[,auto\|M]}` |Enable N[+M] threads; N threads are assigned to the `default` threadpool, and if M is specified, M threads are assigned to the `interactive` threadpool; `auto` tries to infer a useful default number of threads to use but the exact behavior might change in the future. Currently sets N to the number of CPUs assigned to this Julia process based on the OS-specific affinity assignment interface if supported (Linux and Windows) or to the number of CPU threads if not supported (MacOS) or if process affinity is not configured, and sets M to 1.|
| `--gcthreads=N[,M]` |Use N threads for the mark phase of GC and M (0 or 1) threads for the concurrent sweeping phase of GC. N is set to the number of compute threads and M is set to 0 if unspecified.|
| `--gcthreads=N[,M]` |Use N threads for the mark phase of GC and M (0 or 1) threads for the concurrent sweeping phase of GC. N is set to the number of compute threads and M is set to 0 if unspecified. See [Memory Management and Garbage Collection](@ref man-memory-management) for more details.|
|`-p`, `--procs {N\|auto}` |Integer value N launches N additional local worker processes; `auto` launches as many workers as the number of local CPU threads (logical cores)|
|`--machine-file <file>` |Run processes on hosts listed in `<file>`|
|`-i`, `--interactive` |Interactive mode; REPL runs and `isinteractive()` is true|
Expand All @@ -205,7 +205,7 @@ The following is a complete list of command-line switches available when launchi
|`--track-allocation=@<path>` |Count bytes but only in files that fall under the given file path/directory. The `@` prefix is required to select this option. A `@` with no path will track the current directory.|
|`--task-metrics={yes\|no*}` |Enable the collection of per-task metrics|
|`--bug-report=KIND` |Launch a bug report session. It can be used to start a REPL, run a script, or evaluate expressions. It first tries to use BugReporting.jl installed in current environment and falls back to the latest compatible BugReporting.jl if not. For more information, see `--bug-report=help`.|
|`--heap-size-hint=<size>` |Forces garbage collection if memory usage is higher than the given value. The value may be specified as a number of bytes, optionally in units of KB, MB, GB, or TB, or as a percentage of physical memory with %.|
|`--heap-size-hint=<size>` |Forces garbage collection if memory usage is higher than the given value. The value may be specified as a number of bytes, optionally in units of KB, MB, GB, or TB, or as a percentage of physical memory with %. See [Memory Management and Garbage Collection](@ref man-memory-management) for more details.|
|`--compile={yes*\|no\|all\|min}` |Enable or disable JIT compiler, or request exhaustive or minimal compilation|
|`--output-o <name>` |Generate an object file (including system image data)|
|`--output-ji <name>` |Generate a system image data file (.ji)|
Expand Down
177 changes: 177 additions & 0 deletions doc/src/manual/memory-management.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
# [Memory Management and Garbage Collection](@id man-memory-management)

Julia uses automatic memory management through its built-in garbage collector (GC). This section provides an overview of how Julia manages memory and how you can configure and optimize memory usage for your applications.

## [Garbage Collection Overview](@id man-gc-overview)

Julia features a garbage collector with the following characteristics:

* **Non-moving**: Objects are not relocated in memory during garbage collection
* **Generational**: Younger objects are collected more frequently than older ones
* **Parallel and partially concurrent**: The GC can use multiple threads and run concurrently with your program
* **Mostly precise**: The GC accurately identifies object references for pure Julia code, and it provides conservative scanning APIs for users calling Julia from C

The garbage collector automatically reclaims memory used by objects that are no longer reachable from your program, freeing you from manual memory management in most cases.

## [Memory Architecture](@id man-memory-architecture)

Julia uses a two-tier allocation strategy:

* **Small objects** (currently ≤ 2032 bytes but may change): Allocated using a fast per-thread pool allocator
* **Large objects** : Allocated directly through the system's `malloc`

This hybrid approach optimizes for both allocation speed and memory efficiency, with the pool allocator providing fast allocation for the many small objects typical in Julia programs.

## [System Memory Requirements](@id man-system-memory)

### Swap Space

Julia's garbage collector is designed with the expectation that your system has adequate swap space configured. The GC uses heuristics that assume it can allocate memory beyond physical RAM when needed, relying on the operating system's virtual memory management.

If your system has limited or no swap space, you may experience out-of-memory errors during garbage collection. In such cases, you can use the `--heap-size-hint` option to limit Julia's memory usage.

### Memory Hints

You can provide a hint to Julia about the maximum amount of memory to use:

```bash
julia --heap-size-hint=4G # To set the hint to ~4GB
julia --heap-size-hint=50% # or to 50% of physical memory
```

The `--heap-size-hint` option tells the garbage collector to trigger collection more aggressively when approaching the specified limit. This is particularly useful in:

* Containers with memory limits
* Systems without swap space
* Shared systems where you want to limit Julia's memory footprint

You can also set this via the `JULIA_HEAP_SIZE_HINT` environment variable:

```bash
export JULIA_HEAP_SIZE_HINT=2G
julia
```

## [Multithreaded Garbage Collection](@id man-gc-multithreading)

Julia's garbage collector can leverage multiple threads to improve performance on multi-core systems.

### GC Thread Configuration

By default, Julia uses multiple threads for garbage collection:

* **Mark threads**: Used during the mark phase to trace object references (default: 1, which is shared with the compute thread if there is only one, otherwise half the number of compute threads)
* **Sweep threads**: Used for concurrent sweeping of freed memory (default: 0, disabled)

You can configure GC threading using:

```bash
julia --gcthreads=4,1 # 4 mark threads, 1 sweep thread
julia --gcthreads=8 # 8 mark threads, 0 sweep threads
```

Or via environment variable:

```bash
export JULIA_NUM_GC_THREADS=4,1
julia
```

### Recommendations

For compute-intensive workloads:

* Use multiple mark threads (the default configuration is usually appropriate)
* Consider enabling concurrent sweeping with 1 sweep thread for allocation-heavy workloads

For memory-intensive workloads:

* Enable concurrent sweeping to reduce GC pauses
* Monitor GC time using `@time` and adjust thread counts accordingly

## [Monitoring and Debugging](@id man-gc-monitoring)

### Basic Memory Monitoring

Use the `@time` macro to see memory allocation and GC overhead:

```julia
julia> @time some_computation()
2.123456 seconds (1.50 M allocations: 58.725 MiB, 17.17% gc time)
```

### GC Logging

Enable detailed GC logging to understand collection patterns:

```julia
julia> GC.enable_logging(true)
julia> # Run your code
julia> GC.enable_logging(false)
```

This logs each garbage collection event with timing and memory statistics.

### Manual GC Control

While generally not recommended, you can manually trigger garbage collection:

```julia
GC.gc() # Force a garbage collection
GC.enable(false) # Disable automatic GC (use with caution!)
GC.enable(true) # Re-enable automatic GC
```

**Warning**: Disabling GC can lead to memory exhaustion. Only use this for specific performance measurements or debugging.

## [Performance Considerations](@id man-gc-performance)

### Reducing Allocations

The best way to minimize GC impact is to reduce unnecessary allocations:

* Use in-place operations when possible (e.g., `x .+= y` instead of `x = x + y`)
* Pre-allocate arrays and reuse them
* Avoid creating temporary objects in tight loops
* Consider using `StaticArrays.jl` for small, fixed-size arrays

### Memory-Efficient Patterns

* Avoid global variables that change type
* Use `const` for global constants

### Profiling Memory Usage

For detailed guidance on profiling memory allocations and identifying performance bottlenecks, see the [Profiling](@ref man-profiling) section.

## [Advanced Configuration](@id man-gc-advanced)

### Integration with System Memory Management

Julia works best when:

* The system has adequate swap space (recommended: 2x physical RAM)
* Virtual memory is properly configured
* Other processes leave sufficient memory available
* Container memory limits are set appropriately with `--heap-size-hint`

## [Troubleshooting Memory Issues](@id man-gc-troubleshooting)

### High GC Overhead

If garbage collection is taking too much time:

1. **Reduce allocation rate**: Focus on algorithmic improvements
2. **Adjust GC threads**: Experiment with different `--gcthreads` settings
3. **Use concurrent sweeping**: Enable background sweeping with `--gcthreads=N,1`
4. **Profile memory patterns**: Identify allocation hotspots and optimize them

### Memory Leaks

While Julia's GC prevents most memory leaks, issues can still occur:

* **Global references**: Avoid holding references to large objects in global variables
* **Closures**: Be careful with closures that capture large amounts of data
* **C interop**: Ensure proper cleanup when interfacing with C libraries

For more detailed information about Julia's garbage collector internals, see the Garbage Collection section in the Developer Documentation.
6 changes: 4 additions & 2 deletions doc/src/manual/multi-threading.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,15 @@ julia> Threads.threadid()

### Multiple GC Threads

The Garbage Collector (GC) can use multiple threads. The amount used is either half the number
of compute worker threads or configured by either the `--gcthreads` command line argument or by using the
The Garbage Collector (GC) can use multiple threads. The amount used by default matches the compute
worker threads or can configured by either the `--gcthreads` command line argument or by using the
[`JULIA_NUM_GC_THREADS`](@ref JULIA_NUM_GC_THREADS) environment variable.

!!! compat "Julia 1.10"
The `--gcthreads` command line argument requires at least Julia 1.10.

For more details about garbage collection configuration and performance tuning, see [Memory Management and Garbage Collection](@ref man-memory-management).

## [Threadpools](@id man-threadpools)

When a program's threads are busy with many tasks to run, tasks may experience
Expand Down
2 changes: 2 additions & 0 deletions doc/src/manual/performance-tips.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ Consequently, in addition to the allocation itself, it's very likely
that the code generated for your function is far from optimal. Take such indications seriously
and follow the advice below.

For more information about memory management and garbage collection in Julia, see [Memory Management and Garbage Collection](@ref man-memory-management).

In this particular case, the memory allocation is due to the usage of a type-unstable global variable `x`, so if we instead pass `x` as an argument to the function it no longer allocates memory
(the remaining allocation reported below is due to running the `@time` macro in global scope)
and is significantly faster after the first call:
Expand Down
2 changes: 1 addition & 1 deletion src/macroexpand.scm
Original file line number Diff line number Diff line change
Expand Up @@ -513,7 +513,7 @@
(body (cadr e))
(m (caddr e))
(lno (cdddr e)))
(resolve-expansion-vars-with-new-env body env m lno parent-scope inarg #t)))
(resolve-expansion-vars-with-new-env body '() m lno parent-scope inarg #t)))
((tuple)
(cons (car e)
(map (lambda (x)
Expand Down
10 changes: 7 additions & 3 deletions src/module.c
Original file line number Diff line number Diff line change
Expand Up @@ -1474,10 +1474,14 @@ JL_DLLEXPORT int jl_boundp(jl_module_t *m, jl_sym_t *var, int allow_import) // u
} else {
jl_walk_binding_inplace(&b, &bpart, jl_current_task->world_age);
}
if (jl_bkind_is_some_guard(jl_binding_kind(bpart)))
enum jl_partition_kind kind = jl_binding_kind(bpart);
if (jl_bkind_is_some_guard(kind))
return 0;
if (jl_bkind_is_defined_constant(jl_binding_kind(bpart))) {
// N.B.: No backdated check for isdefined
if (jl_bkind_is_defined_constant(kind)) {
if (__unlikely(kind == PARTITION_KIND_BACKDATED_CONST)) {
return !(jl_current_task->ptls->in_pure_callback || jl_options.depwarn == JL_OPTIONS_DEPWARN_ERROR);
}
// N.B.: No backdated admonition for isdefined
return 1;
}
return jl_atomic_load(&b->value) != NULL;
Expand Down
2 changes: 1 addition & 1 deletion stdlib/Pkg.version
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
PKG_BRANCH = release-1.12
PKG_SHA1 = e7a2dfecbfe43cf1c32f1ccd1e98a4dca52726ee
PKG_SHA1 = 474c628764d6562453709bff686f7fc65dd23535
PKG_GIT_URL := https://github.com/JuliaLang/Pkg.jl.git
PKG_TAR_URL = https://api.github.com/repos/JuliaLang/Pkg.jl/tarball/$1
25 changes: 25 additions & 0 deletions test/core.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2280,6 +2280,31 @@ end
x6074 = 6074
@test @X6074() == 6074

# issues #48910, 54417
macro X43151_nested()
quote my_value = "from_nested_macro" end
end
macro X43151_parent()
quote
my_value = "from_parent_macro"
@X43151_nested()
my_value
end
end
@test @X43151_parent() == "from_parent_macro"

macro X43151_nested_escaping()
quote $(esc(:my_value)) = "from_nested_macro" end
end
macro X43151_parent_escaping()
quote
my_value = "from_parent_macro"
@X43151_nested_escaping()
my_value
end
end
@test @X43151_parent_escaping() == "from_nested_macro"

# issue #5536
test5536(a::Union{Real, AbstractArray}...) = "Splatting"
test5536(a::Union{Real, AbstractArray}) = "Non-splatting"
Expand Down
8 changes: 7 additions & 1 deletion test/worlds.jl
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,13 @@ struct FooBackdated

FooBackdated() = new(FooBackdated[])
end
@test Base.invoke_in_world(before_backdate_age, isdefined, @__MODULE__, :FooBackdated)
# For depwarn == 1, this throws a warning on access, for depwarn == 2, it throws an error.
# `isdefinedglobal` changes with that, but doesn't error.
if Base.JLOptions().depwarn <= 1
@test Base.invoke_in_world(before_backdate_age, isdefinedglobal, @__MODULE__, :FooBackdated)
else
@test !Base.invoke_in_world(before_backdate_age, isdefinedglobal, @__MODULE__, :FooBackdated)
end

# Test that ambiguous binding intersect the using'd binding's world ranges
module AmbigWorldTest
Expand Down