Skip to content

optimizer: renumber the scope operand of frame-less EnterNodes - #19

Closed
adienes wants to merge 14 commits into
masterfrom
fix-enternode-scope-renumber
Closed

optimizer: renumber the scope operand of frame-less EnterNodes#19
adienes wants to merge 14 commits into
masterfrom
fix-enternode-scope-renumber

Conversation

@adienes

@adienes adienes commented Jul 8, 2026

Copy link
Copy Markdown
Owner

renumber_ir_elements! only renumbered an EnterNode's operands when catch_dest != 0, but frame-less enters (catch_dest == 0) still carry a scope SSA operand. When convert_to_ircode inserts statements earlier in the function that reference goes stale, so Core.current_scope() observes an arbitrary value:

using Base.ScopedValues
const sval = ScopedValue(1)
@noinline getscope() = Core.current_scope()
function f(c::Bool)
    c && error("x")
    @with sval => 2 getscope()
end
f(false)   # returns `sval => 2`, should be a `Scope`

Frame-less enters only arise from the nothrow try/catch elision (JuliaLang#52527), so the @with body must be nothrow; present since 1.11.

Fixes JuliaLang#62082.

Written with AI assistance (Claude).

vtjnash and others added 13 commits July 6, 2026 15:36
This is effectively the same as marking them static, as it makes it
private to the file. Should not be a functional change, just clarifying
the usage scope.
Helps to clean up some dead code and be a bit more clear about where current
usage is located. Mark all functions as any of:
 - in a header
 - `extern` (rare)
 - `static`
 - `namespace { ... }` (anonymous)
 - `JL_DLLEXPORT` (or equivalent)

Helps to ensure we're exporting functions to gdb if intended to do so
(recent strip changes may make it harder to rely on non-exported
debugging functions being available, even though we didn't want regular
API users calling these).
…ed necessary due to static/extern/dllexport) (JuliaLang#62214)

Add a StaticOrDeclared clang-tidy check that enforces that every
function with
external linkage defined in a source file is either:

(a) declared in some header (i.e. it has a non-defining redeclaration
that
comes from an #included file, so the function is part of an API that
      other translation units can call), or
  (b) declared `static` (internal linkage), so it is private to its
      translation unit, or

(c) explicitly exported with `JL_DLLEXPORT` -- i.e. it carries an
explicit
      `__declspec(dllexport)` (on Windows) and/or
`__attribute__((visibility("default")))` (elsewhere). Such a function
is deliberately part of the public ABI even without a prototype, so it
is permitted. Which attribute is present depends on the platform the
      analysis runs on, so both are accepted, or

(d) explicitly annotated with the `extern` keyword in this file.
Functions
      are external by default, so spelling out `extern` is a deliberate
statement that the external linkage is intended, which overrides the
      warning. (This is the storage class `extern`, not the `extern "C"`
      language-linkage specifier.)

This is intended to help ensure we discourage local prototypes, which
can drift out of sync with their global prototype. It also lets the
compiler generate slightly better code optimizations (inlining
decisions) and minimizes the list of exported symbols for the linker.
…Lang#62278)

thought I'd donate some tokens
cc @benlorenz

---------------------------------------------
A keyword sorter is defined as `kwcall(::NamedTuple, callee, args...)`,
where the callee's self-type is spelled by lowering with `Core.Typeof`.
Since JuliaLang#62001, `Core.Typeof` of a type value yields the egality
`Core.TypeEgal` kind, and `jl_method_def` only normalized that back to
the equality `Type` kind for the method's own function argument
(position 0). The keyword sorter carries the real callee at argument 2,
so its self-type stayed at the egality kind while the matching primary
method used `Type`, making an inner and an outer keyword constructor
mutually ambiguous.

Normalize the kwcall callee self-type the same way as the function
argument, so the sorter and its primary method agree on the callee kind.

This pull request was written with the assistance of generative AI
(Claude Fable 5).

Fixes JuliaLang#62277

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…uliaLang#62282)

[Cherry-Picked from JuliaLang#62257, since the same issue has shown up on master
now]

GCC 16.1 raises false-positive `-Warray-bounds=` warnings against the
inline storage of llvm::unique_function (FunctionExtras.h, via
PointerIntPair.h) as inlined into
JLJITLinkMemoryManager::InFlightAlloc::finalize, and merely emitting
those diagnostics can crash the compiler outright with

    internal compiler error: in action_after_output, at context.cc:1055

as observed (nondeterministically) on the mingw64 CI builders, e.g.
https://buildkite.com/julialang/julia-pr/builds/45#019f2929-01a7-42bf-a992-f711e5cab0e7
The ICE fires in the diagnostics machinery while the warning is being
printed (other builds emit the same warnings and survive), so
suppressing the known-bogus diagnostic also sidesteps the crash. The
warnings are attributed to the LLVM header lines, so the suppression
must precede their inclusion.

Co-authored-by: Keno Fischer <Keno@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…aLang#61886)

This PR documents the known bug JuliaLang#9498.

---------

Co-authored-by: Andy Dienes <51664769+adienes@users.noreply.github.com>
…1527)

WIP

We have enough of the JIT changes to get some cache hits now, so this
can be a useful experiment. This branch puts the native code cache in
`~/.julia/cache/v1.14/`, doesn't do eviction, and has some logging you
can trigger by setting `JULIA_OBJCACHE_LOG` to a file.

The overall design and `src/objcache.{cpp,h}` are now ready for review,
but a few TODOs remain:
- `optimizeDLSyms` if we can merge something like JuliaLang#61569.
- `LowerPTLS` can still put incorrect code into the cache, since it will
embed the TLS variable offset for the running process into the compile
code on x86.
…2287)

Since JuliaLang#62001, `Core.Typeof` of a type value yields the egality
`Core.TypeEgal` kind, but method definitions are still made at the
equality level, so `jl_method_def` had to normalize the self-type back
to the `Type` kind — first for the method's own function argument
(JuliaLang#62001), then again for the kwcall callee (JuliaLang#62278), which had been
missed and made inner and outer keyword constructors mutually ambiguous.

Fix this in lowering instead, as suggested in JuliaLang#62278: add
`Core.TypeEqOf`, which yields the equality kind `Type{x}` for type
values (the pre-JuliaLang#62001 `Core.Typeof` semantics), spell the implicit
self-type of method definitions with it in both flisp lowering and
JuliaLowering, and drop both normalization branches from
`jl_method_def`.

Co-authored-by: Keno Fischer <Keno@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
An attempt at fixing interrupt handling, also some specific interrupt
hardening from manual tests, and adds interrupt tests.

Fixes JuliaLang#58689
Closes JuliaLang#58849

Developed with Claude Fable 5:

----

Since JuliaLang#57544, an idle thread parks in a per-thread internal scheduler
task. A SIGINT is always delivered to thread 1, which is almost always
parked when the signal arrives, so the resulting `InterruptException`
landed in the scheduler task's `wait_forever`, where it was reported as
a confusing `Internal Task ERROR: InterruptException` and then dropped.
As a result Ctrl-C no longer reached user code:

- scripts blocked in `sleep`/IO could not be interrupted at all,
- the REPL printed internal task errors on every Ctrl-C,
- `Distributed.interrupt` (which just sends SIGINT to the worker
process) became a silent no-op, breaking remote interrupts in
Distributed, Malt.jl/Pluto, and IJulia.

1.12 and 1.13 worked around this by reverting JuliaLang#57544 and its follow-ups;
master still had the scheduler task and the broken behavior.

## What this does

**`base/task.jl`** — Each thread now remembers the last user task that
yielded into the scheduler while going idle (never recording a completed
task, so this does not delay collection of done tasks — the problem
JuliaLang#57544 fixed). When an `InterruptException` is delivered to the
scheduler task, it is re-thrown into a task that can meaningfully
observe it instead of being dropped: the REPL backend if it is
evaluating user code; silently dropped at an idle REPL prompt; otherwise
the last idle task, falling back to the root task. Expected failures of
the redirect (the victim raced to be rescheduled, or a second interrupt
arrived mid-switch) drop the interrupt; anything unexpected is still
reported. Delivery remains best-effort, as it always has been; robust
cancellation is left to JuliaLang#60281.

**`src/gf.c`** — Interrupting a process that is compiling (e.g. Ctrl-C
during `Pkg.test`) frequently threw the `InterruptException` into type
inference via the safepoint, unwinding the compiler mid-flight
("`Internal error: during type inference of ...`", an abort in assertion
builds, and a lost interrupt). The inference entry point is now
signal-atomic, so the interrupt is deferred and rethrown once compiler
state is consistent. Forced interrupts (repeated Ctrl-C) bypass the
deferral as before.

**`stdlib/REPL/src/REPL.jl`** — An interrupt forwarded to the REPL
backend just as user code finished evaluating (the forwarder checks
`in_eval`, but eval can complete before the throw lands) was raised at
`take!(backend.repl_channel)` and tore down the whole REPL session. The
backend loop now ignores a stray `InterruptException` there and keeps
serving.

## Validation

New regression tests in `test/misc.jl` (pty-driven REPL + subprocess
scenarios, Unix-only) and `stdlib/REPL/test/repl.jl`, all derived from
the issue reports:

| Scenario | 1.11 | master before | master after |
|---|---|---|---|
| SIGINT at idle REPL prompt | ok | internal-error noise | ok |
| SIGINT during REPL `sleep` loop | ok | noise + interrupt works | ok |
| SIGINT to `julia -e 'sleep(600)'` | exits after 2nd SIGINT | never
exits | exits cleanly on 1st |
| `Distributed.interrupt` of a busy worker | `RemoteException` | silent
no-op | `RemoteException` |
| SIGINT during `Pkg.test`-style run (compiling) | — | inference
internal error / abort | clean `InterruptException` |

The fix is platform-independent (the Windows delivery path in
`signals-win.c` lands in the same scheduler task), but the tests are
Unix-only since sending a console Ctrl-C from the test harness on
Windows requires `GenerateConsoleCtrlEvent`/`CREATE_NEW_PROCESS_GROUP`,
which the spawn API doesn't expose.

This also adds the Unix portion of the CI coverage requested in JuliaLang#58849,
and likely fixes the crash class in JuliaLang#50045 (unverified, needs network).

Fixes JuliaLang#58689
Fixes JuliaLang#29369
Fixes JuliaLang#43451
Closes JuliaLang#58849
Fixes JuliaLang#50045

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
@adienes
adienes force-pushed the fix-enternode-scope-renumber branch from 1736ecf to 0513e55 Compare July 8, 2026 17:13
@adienes adienes changed the title optimizer, Meta: fix stale EnterNode scope operands after IR renumbering optimizer: renumber the scope operand of frame-less EnterNodes Jul 8, 2026
@adienes
adienes force-pushed the fix-enternode-scope-renumber branch 2 times, most recently from e8e0609 to 698d6e8 Compare July 8, 2026 17:31
`renumber_ir_elements!` skipped its EnterNode handling entirely when
`catch_dest == 0`, leaving the SSA reference of the scope operand stale
whenever `convert_to_ircode` inserted statements (unreachable markers
after `Union{}` calls, or coverage statements) earlier in the function.
The dangling reference makes the entered scope an arbitrary value, which
`Core.current_scope()` then observes at runtime inside the scoped region:

    using Base.ScopedValues
    const sval = ScopedValue(1)
    @noinline getscope() = Core.current_scope()
    function f(c::Bool)
        if c
            error("x")
        end
        @with sval => 2 getscope()
    end
    f(false) # Pair{ScopedValue{Int64}, Int64}, should be a Scope

Frame-less scope enters are currently only produced by the try/catch
elision for provably-nothrow regions (JuliaLang#52527), so this requires the
`@with` body to be nothrow; it affects all Julia versions since 1.11.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@adienes
adienes force-pushed the fix-enternode-scope-renumber branch from 698d6e8 to 60b3833 Compare July 8, 2026 17:41
@adienes

adienes commented Jul 8, 2026

Copy link
Copy Markdown
Owner Author

Superseded by JuliaLang#62300, which targets upstream master.

@adienes adienes closed this Jul 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

miscompile / scoping issue with ScopedValue

7 participants