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

Mutable let block variable treated as immutable in Julia 1.10 #52531

Closed
omlins opened this issue Dec 14, 2023 · 16 comments · Fixed by #52548 or #52596
Closed

Mutable let block variable treated as immutable in Julia 1.10 #52531

omlins opened this issue Dec 14, 2023 · 16 comments · Fixed by #52548 or #52596
Assignees
Labels
compiler:effects effect analysis compiler:lowering Syntax lowering (compiler front end, 2nd stage) priority This should be addressed urgently
Milestone

Comments

@omlins
Copy link

omlins commented Dec 14, 2023

MWE:

let
    global set_a, get_a
    _a::Int       = -1
    set_a(a::Int) = (_a = a; return get_a())
    get_a()       = _a
end
    
set_a(1)
get_a()
set_a(2)
get_a()

Wrong output in Julia 1.10:

julia> let
           global set_a, get_a
           _a::Int       = -1
           set_a(a::Int) = (_a = a; return get_a())
           get_a()       = _a
       end
get_a (generic function with 1 method)

julia> set_a(1)
-1

julia> get_a()
-1

julia> set_a(2)
-1

julia> get_a()
-1

Correct output in Julia 1.9.4:

julia> let
           global set_a, get_a
           _a::Int       = -1
           set_a(a::Int) = (_a = a; return get_a())
           get_a()       = _a
       end
get_a (generic function with 1 method)

julia> set_a(1)
1

julia> get_a()
1

julia> set_a(2)
2

julia> get_a()
2
@vchuravy
Copy link
Member

@aviatesk this seems possibly effects related. It seems like we might miss-model the setfield/getfield on the box.

julia> @code_llvm set_a(2)
;  @ REPL[1]:4 within `set_a`
define i64 @julia_set_a_371(i64 signext %0) #0 {
top:
  ret i64 -1
}

julia> @code_typed set_a(2)
CodeInfo(
1 ─     nothing::Nothing
│       Core.setfield!(Core.Box(-1), :contents, a)::Int64
└──     return -1
) => Int64

julia> @code_llvm optimize=false set_a(2)
;  @ REPL[1]:4 within `set_a`
define i64 @julia_set_a_386(i64 signext %0) #0 {
top:
  %1 = call {}*** @julia.get_pgcstack()
  %2 = bitcast {}*** %1 to {}**
  %current_task = getelementptr inbounds {}*, {}** %2, i64 -14
  %3 = bitcast {}** %current_task to i64*
  %world_age = getelementptr inbounds i64, i64* %3, i64 15
  ret i64 -1
}

@vchuravy
Copy link
Member

∘ ─ %0 = invoke set_a(::Int64)::Core.Const(-1) (+c,+e,+n,+t,+s,+m,+i)
    @ REPL[1]:4 within `set_a`
1 ─ %1  = a::Int64
│   %2  = Core.Box(-1)::Core.Const(Core.Box(-1))
│         (@_3 = %1)::Int64
│         (@_3 isa Main.Int)::Core.Const(true) (+c,+e,+n,+t,+s,+m,+i)
│         nothing::Any
└──       goto #3
2 ─       Core.Const(:(Base.convert(Main.Int, @_3)))::Union{}
└──       Core.Const(:(@_3 = Core.typeassert(%7, Main.Int)))::Union{}
3 ┄ %9  = @_3::Int64
│         Core.setfield!(%2, :contents, %9)::Int64 (+c,?e,+n,+t,+s,?m,+i)
│   %11 = Main.get_a()::Core.Const(-1) (+c,+e,+n,+t,+s,+m,+i)
└──       return %11

Why is get_a consistent?

∘ ─ %0 = invoke get_a()::Int64 (+c,+e,!n,+t,+s,+m,+i)
    @ REPL[1]:5 within `get_a`
1 ─ %1 = $(QuoteNode(Core.Box(-1)))
│        Core.isdefined(%1, :contents)::Core.Const(true) (+c,+e,+n,+t,+s,?m,+i)
│        nothing::Any
└──      goto #3
2 ─      Core.Const(Core.NewvarNode(:(_a)))::Union{}
└──      Core.Const(:(_a))::Union{}
3 ┄ %7 = Core.getfield(%1, :contents)::Any (?c,+e,+n,+t,+s,?m,+i)
│   %8 = Core.typeassert(%7, Main.Int)::Int64 (+c,+e,!n,+t,+s,+m,+i)
└──      return %8

So we decided to concrete eval it, but we shouldn't have since the getfield is ?c.

@vchuravy vchuravy added priority This should be addressed urgently compiler:effects effect analysis labels Dec 14, 2023
@aviatesk
Copy link
Member

I will look into this tomorrow. It's certainly a bug of the effects system.

@aviatesk aviatesk self-assigned this Dec 14, 2023
@vchuravy
Copy link
Member

On main we get even further xD:

julia> @code_typed set_a(2)
CodeInfo(
1 ─     return -1
) => Int64

@gbaraldi
Copy link
Member

I think the bigger question is why is set_a(2) effect free?

@vtjnash
Copy link
Member

vtjnash commented Dec 14, 2023

If read that right, it is saying the result is consistent as long as the return value is immutable (since then we didn't use the result of that load?), which, since we asserted the return value is an Int, it thus determines the result is consistent?

@vchuravy
Copy link
Member

julia> @eval getbox() = $(QuoteNode(Core.Box(1))).contents
getbox (generic function with 1 method)

julia> @eval getref() = $(QuoteNode(Core.Ref(1)))[]
getref (generic function with 1 method)

julia> fbox() = getbox()
fbox (generic function with 1 method)

julia> fref() = getref()
fref (generic function with 1 method)

julia> @code_typed fref()
CodeInfo(
1 ─ %1 = Base.getfield($(QuoteNode(Base.RefValue{Int64}(1))), :x)::Int64
└──      return %1
) => Int64

julia> @code_typed fbox()
CodeInfo(
1 ─     return 1
) => Int64

So maybe it's Core.Box specifically.

@vchuravy
Copy link
Member

vchuravy commented Dec 14, 2023

%1 = < constprop > getproperty(::Base.RefValue{Int64},::Core.Const(:x))::Int64 (!c,+e,+n,+t,+s,?m,+i)
│   %2 = Base.getfield(x, f)::Int64 (!c,+e,+n,+t,+s,?m,+i)

vs

%1 = < constprop > getproperty(::Core.Const(Core.Box(1)),::Core.Const(:contents))::Any (?c,+e,+n,+t,+s,?m,+i)
│   %2 = Base.getfield(x, f)::Any (?c,+e,+n,+t,+s,?m,+i)

@gbaraldi
Copy link
Member

Those effects for box seem fine though, the weird thing is that ?m should not refine to inaccessiblememonly because Core.Box is mutable

@vchuravy
Copy link
Member

vchuravy commented Dec 14, 2023

We can also observe the same for Ref if we avoid the getindex...

julia> @eval getref() = $(QuoteNode(Core.Ref(1))).x
getref (generic function with 1 method)

julia> @code_typed fref()
CodeInfo(
1 ─     return 1
) => Int64

@gbaraldi
Copy link
Member

gbaraldi commented Dec 14, 2023

julia> const a = Core.Ref(1)
Base.RefValue{Int64}(1)

julia> @eval getref() = $(QuoteNode(a)).x
getref (generic function with 1 method)

julia> Base.infer_effects(getref,())
(+c,+e,+n,+t,+s,+m,+u)

julia> getref()
1

julia> a.x = 9
9

julia> getref()
9

if is_consistent_if_inaccessiblememonly(ipo_effects)
if is_inaccessiblememonly(ipo_effects)
consistent = ipo_effects.consistent & ~CONSISTENT_IF_INACCESSIBLEMEMONLY
ipo_effects = Effects(ipo_effects; consistent)
elseif is_inaccessiblemem_or_argmemonly(ipo_effects)
else # `:inaccessiblememonly` is already tainted, there will be no chance to refine this
ipo_effects = Effects(ipo_effects; consistent=ALWAYS_FALSE)
end

I suspect that the else if branch here is incorrect. We don't refine the memory effects after this, so this function will never be consistent.

@gbaraldi
Copy link
Member

gbaraldi commented Dec 14, 2023

So testing just removing those elseif calls does fix my minimized case, but does not fix the original case. I believe that happens because in that case we emit a direct getfield call that doesn't go through the function that I modified, which begs the question.
When is it legal to refine a getfield with ?m to one with m, because if the memory is mutable, it's going to continue to be mutable. The only case where I can see this being consistent is if we can prove that the memory accessed here does not escape.

aviatesk added a commit that referenced this issue Dec 15, 2023
What observed in #52531 is that `QuoteNode` can embed global variables
that users can modify. Therefore, when dealing with `QuoteNode`, it's
necessary to taint its `:inaccessiblememonly` just like we do for
`GlobalRef`.
KristofferC pushed a commit that referenced this issue Dec 15, 2023
What observed in #52531 is that `QuoteNode` can embed global variables
that users can modify. Therefore, when dealing with `QuoteNode`, it's
necessary to taint its `:inaccessiblememonly` just like we do for
`GlobalRef`.

- fixes #52531
- replaces #52536
aviatesk added a commit that referenced this issue Dec 16, 2023
What observed in #52531 is that `QuoteNode` can embed global variables
that users can modify. Therefore, when dealing with `QuoteNode`, it's
necessary to taint its `:inaccessiblememonly` just like we do for
`GlobalRef`.

- fixes #52531
- replaces #52536
@gbaraldi gbaraldi reopened this Dec 20, 2023
@gbaraldi
Copy link
Member

I'm not sure this is 100% fixed, the original issue in ParallelStencil still seems to be there

@gbaraldi
Copy link
Member

New MWE

julia> foo(x) = (set_initialized(true); nothing)
foo (generic function with 1 method)

julia> let
           global is_initialized, set_initialized
           _is_initialized = false
           set_initialized(flag::Bool) = (_is_initialized = flag)
           is_initialized()            = _is_initialized
       end
is_initialized (generic function with 1 method)

julia> @show is_initialized()
is_initialized() = false
false

julia> @show set_initialized(true)
set_initialized(true) = true
true

julia> @show is_initialized()
is_initialized() = true
true

julia> @show set_initialized(false)
set_initialized(false) = false
false

julia> @show is_initialized()
is_initialized() = false
false

julia> foo(4)

julia> @show is_initialized()
is_initialized() = false
false

@vchuravy vchuravy added compiler:lowering Syntax lowering (compiler front end, 2nd stage) and removed compiler:effects effect analysis labels Dec 20, 2023
@vchuravy
Copy link
Member

vchuravy commented Dec 20, 2023

This looks like a bug in lowering. At least @code_lowered on is_initialized returns false, but it also does so in 1.9

NVM... I didn't define set_initalize

@vchuravy vchuravy added compiler:effects effect analysis and removed compiler:lowering Syntax lowering (compiler front end, 2nd stage) labels Dec 20, 2023
@vchuravy
Copy link
Member

vchuravy commented Dec 20, 2023

Yes inside foo set_initalize effects are +c and +m

The Box is not lowered to be a QuoteNode. It's just a Box.

That feels wrong since Box is not defined to be self-quoting...

@vchuravy vchuravy added the compiler:lowering Syntax lowering (compiler front end, 2nd stage) label Dec 20, 2023
@vchuravy vchuravy self-assigned this Dec 20, 2023
aviatesk added a commit that referenced this issue Dec 22, 2023
`Core.Box` is not self-quoting so it should be captured in a QuoteNode.
This has been benign until the improved effect system, we handle
`QuoteNode`
of a mutable value as a global access, whereas a direct reference to a
value
is treated as inaccessible memory.

Fixes #52531

---------

Co-authored-by: Gabriel Baraldi <[email protected]>
Co-authored-by: Shuhei Kadowaki <[email protected]>
KristofferC pushed a commit that referenced this issue Dec 23, 2023
`Core.Box` is not self-quoting so it should be captured in a QuoteNode.
This has been benign until the improved effect system, we handle
`QuoteNode`
of a mutable value as a global access, whereas a direct reference to a
value
is treated as inaccessible memory.

Fixes #52531

---------

Co-authored-by: Gabriel Baraldi <[email protected]>
Co-authored-by: Shuhei Kadowaki <[email protected]>
(cherry picked from commit 1290b51)
aviatesk added a commit that referenced this issue Jan 11, 2024
aviatesk added a commit that referenced this issue Jan 11, 2024
topolarity added a commit that referenced this issue Jan 11, 2024
aviatesk added a commit that referenced this issue Jan 12, 2024
aviatesk added a commit that referenced this issue Jan 12, 2024
Issues like #52531 were more broadly fixed by #52856. This commit
partially reverts #52596, while leaving the added tests.
aviatesk added a commit that referenced this issue Jan 13, 2024
Issues like #52531 were more broadly fixed by #52856. This commit
partially reverts #52596, while leaving the added tests.
aviatesk added a commit that referenced this issue Jan 13, 2024
Issues like #52531 were more broadly fixed by #52856. This commit
partially reverts #52596, while leaving the added tests.
Drvi pushed a commit to RelationalAI/julia that referenced this issue Jun 7, 2024
Drvi pushed a commit to RelationalAI/julia that referenced this issue Jun 7, 2024
…ng#52878)

Issues like JuliaLang#52531 were more broadly fixed by JuliaLang#52856. This commit
partially reverts JuliaLang#52596, while leaving the added tests.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment