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

RFC: implement proper debug mode for packages (with support for re-precompilation) #37874

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

KristofferC
Copy link
Member

@KristofferC KristofferC commented Oct 3, 2020

This adds a proper debug mode that packages can use. One of the crucial things is that the query for debug mode is available to use during precompilation and that Julia will create a new precompile cache when it changes. With the LRU cache system for .ji files one can swap between debug and non-debug mode freely without julia recompiling over and over.

This also disables the assertion macro in normal running code.

As an example: Loading a package with this content:

module FooBar

@show Base.isdebug()
err() = @assert false

end # module

in non-debug mode:

❯ julia --project -q
julia> using FooBar
[ Info: Precompiling FooBar [fad23a86-9c0b-4cc2-9d4f-c6fb60d9cd78]
Base.isdebug() = false

julia> FooBar.err() # assertion did not not fire

julia> 

in debug mode:

~/julia/julia --project -q -g
julia> using FooBar
[ Info: Precompiling FooBar [fad23a86-9c0b-4cc2-9d4f-c6fb60d9cd78]
Base.isdebug() = true

julia> FooBar.err()
ERROR: AssertionError: false
Stacktrace:
 [1] err()
   @ FooBar ~/julia/FooBar/src/FooBar.jl:4
 [2] top-level scope
   @ REPL[2]:1

and then back to non debug mode (note no re-precompilation)

~/julia/julia --project -q
julia> using FooBar

julia> FooBar.err()

julia>

Questions (for triage):

  1. Export isdebug?
  2. Is it the -g flag we want to use to enable / disable debug mode for packages?
  3. What about @assert in Base. We don't really want to ship two sysimages so maybe just leave it on there for now?
  4. Is a true/false enough for debug level or should there be more fine-grained numbers?

TODO:

  • Enable @assert in stdlibs
  • Tests- Make Pkg.test run tests in debug mode

cc @tkf, @staticfloat

@KristofferC KristofferC added needs news A NEWS entry is required for this change triage This should be discussed on a triage call labels Oct 3, 2020
@quinnj
Copy link
Member

quinnj commented Oct 3, 2020

This is super cool; could we make it a debug level::Int instead of Bool? I tend to find myself wanting to have debug code fenced for various "levels" and then being able to set the level to enable certain assertions/etc.

@KristofferC
Copy link
Member Author

KristofferC commented Oct 3, 2020

To me, that feels a bit excessive. Just enable everything if the debug level is on? It seems a bit like the Logging levels. Maybe we could have it take the same numerical value as the -g to julia? I added it to the questions to resolve.

@quinnj
Copy link
Member

quinnj commented Oct 3, 2020

Yeah, I guess it does get close to "logging" terrority of functionality

@KristofferC

This comment has been minimized.

@tkf
Copy link
Member

tkf commented Oct 3, 2020

a debug level::Int instead of Bool?

It should be possible to implement something like this with #37595 as a library.

Edit: reworded

base/loading.jl Outdated Show resolved Hide resolved
@KristofferC
Copy link
Member Author

I think something like this should be implementable with #37595 as a library.

Note that I don't want one to have to add a dependency and do changes in a project file to use debug mode.

base/error.jl Outdated
@@ -208,6 +202,7 @@ julia> @assert isodd(3) "What even are numbers?"
```
"""
macro assert(ex, msgs...)
@isdefined(isdebug) && !(isdebug()) && return nothing
Copy link
Member Author

@KristofferC KristofferC Oct 4, 2020

Choose a reason for hiding this comment

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

This is for the Compiler module where isdebug is not defined. Is this the best way?

Copy link
Member

Choose a reason for hiding this comment

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

options.jl is already included by this point during bootstrap. So you could just eliminate the julia_debug global and define isdebug() directly in error.jl as

isdebug() = JLOptions().debug_level >= 2

?

Copy link
Member Author

@KristofferC KristofferC Oct 5, 2020

Choose a reason for hiding this comment

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

Yeah, but that ties isdebug directly to the debugging level (while it is not only tied to it in the sense that it is used to set the variable on startup). With the current implementation you can force julia debug mode without having to start julia itself with -g2 (maybe Pkg wants to do that in Pkg.test?). I don't know if the extra flexibility is worth it though.

Copy link
Member

Choose a reason for hiding this comment

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

Allowing julia_debug to change dynamically seems like it could be confusing and lead to inconsistencies between the precompiled code in different modules which are loaded into the session.

But maybe that's a reasonable tradeoff for flexibility, I'm not sure.

Example crazy thing:

module A

if Base.isdebug()
    struct X
        x
        debug_field
    end
else
    struct X
        x
    end
end

end

Now other modules which depend on A can't depend on the value of isdebug() to use X correctly (including macros defined by A and expanded in another module).

Copy link
Member Author

Choose a reason for hiding this comment

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

Allowing julia_debug to change dynamically seems like it could be confusing and lead to inconsistencies between the precompiled code in different modules which are loaded into the session.

Yeah, but julia_debug is internal so we'll just not change it dynamically in a way like that :P. But yes, I did think about changing this to exactly what you say. Can be discussed at triage as well. This problem already kind of exists in changing environments dynamically, by the way.

@@ -1124,6 +1124,14 @@ static int64_t write_dependency_list(ios_t *s, jl_array_t **udepsp, jl_array_t *
}
write_int32(s, 0); // terminator, for ease of reading

// Julia debug mode
jl_value_t *julia_debug = (jl_value_t*)jl_get_global(jl_base_module, jl_symbol("julia_debug"));
if (julia_debug) {
Copy link
Member Author

Choose a reason for hiding this comment

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

Perhaps this should call isdebug instead of going for the global?

@vchuravy
Copy link
Member

vchuravy commented Oct 4, 2020

Maybe we could use this to turn @assert off in non-debug mode?

@KristofferC
Copy link
Member Author

This also disables the assertion macro in normal running code.

@tkf
Copy link
Member

tkf commented Oct 4, 2020

I think something like this should be implementable with #37595 as a library.

Note that I don't want one to have to add a dependency and do changes in a project file to use debug mode.

Sorry, my wording was not appropriate (edited now). I wasn't against adding this in Base. I just wanted to mention that users now can implement more complex @assert-like macros outside julia.

@KristofferC
Copy link
Member Author

I just wanted to mention that users now can implement more complex @assert-like macros outside julia.

Ah, yes. 👍

@@ -66,6 +66,9 @@ time_ns() = ccall(:jl_hrtime, UInt64, ())

start_base_include = time_ns()

julia_debug = true
Copy link
Member

Choose a reason for hiding this comment

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

Why not make this a Ref?

@thofma
Copy link
Contributor

thofma commented Oct 5, 2020

I want to second what @quinnj said about being super cool and also debugging levels (but those could be built on top I guess).

Apart from that, I think the additional change regarding @assert might be suprising for some people (suprising in the sense of following semver if this is supposed to be included in 1.6, although this is of course subjective). At the moment the docs say that "An assert might be disabled at various optimization levels", but I guess a lot of people expect them to be not removed by default, see for example #10614.

@KristofferC
Copy link
Member Author

suprising in the sense of following semver

What do you mean with "following semver"? It is explicitly said in the docs that this check can be omitted and the whole point of adding those docs is to allow for exactly a PR like this.

@KristofferC KristofferC added needs tests Unit tests are required for this change and removed needs news A NEWS entry is required for this change labels Oct 8, 2020
@KristofferC
Copy link
Member Author

KristofferC commented Oct 8, 2020

The main question here is probably if the current -g option to julia is only for debug mode of Julia itself (separately from package code and thus a bit similar to building clang in debug mode) and there should thus exit a completely separate option to set the debug mode of running julia code (-jg or something) or if it is fine to have these be the same.

@KristofferC KristofferC removed the needs tests Unit tests are required for this change label Oct 8, 2020
@KristofferC KristofferC force-pushed the kc/debug_mode branch 4 times, most recently from 1124b5e to 184ab91 Compare October 9, 2020 08:03
@vchuravy
Copy link
Member

The main question here is probably if the current -g option to julia is only for debug mode of Julia itself (separately from package code and thus a bit similar to building clang in debug mode) and there should thus exit a completely separate option to set the debug mode of running julia code (-jg or something) or if it is fine to have these be the same.

julia-debug is equivalent to building clang in debug mode, and -g is for user code.

@KristofferC
Copy link
Member Author

julia-debug is equivalent to building clang in debug mode, and -g is for user code.

Alright. Just to note, we do some compiler checks based on debug_level though, e.g:

if JLOptions().debug_level == 2
@timeit "verify 3" (verify_ir(ir); verify_linetable(ir.linetable))
end

@vchuravy
Copy link
Member

Yeah there is the difference between debug mode for the runtime + C++ parts of the compiler e.g. julia-debug and then the Julia parts of the compiler which use -g. If we want to split that out, I would use -g for user code and introduce a compiler specific flag, but in the end I don't think that difference matters a lot and it is fine to just use -g to mean "all Julia code".

@KristofferC
Copy link
Member Author

Okay, and now when I have someone to talk to (yay), what do you think should be done with @assert & co for Base + stdlibs. Just keep it unconditionally enabled for now? To do it "properly" we would need to build and ship two sysimages but I guess we don't think that's worth it.

@vchuravy
Copy link
Member

vchuravy commented Oct 14, 2020

what do you think should be done with @assert & co for Base + stdlibs. Just keep it unconditionally enabled for now? To do it "properly" we would need to build and ship two sysimages but I guess we don't think that's worth it.

Yeah we used to ship two systemimages back when we still shipped julia-debug, but I don't it is worth the additional complexity right now.

Of course there are evil tricks one can play with multiversioning, instead of doing this on the Julia level you codegen a call to a intrinsic function and then in when we do multiversioning you can replace that call with a constant and run a round of DCE late in the pipeline. But that suggestion is just me wanting to lure you down to the realm of LLVM ;)

@JeffBezanson JeffBezanson removed the triage This should be discussed on a triage call label Oct 22, 2020
@tkf
Copy link
Member

tkf commented Nov 24, 2020

Is it hard to replicate what --check-bounds does?

@DilumAluthge
Copy link
Member

Bump.

@KristofferC Besides the merge conflicts, is there anything else that needs to be done for this?

@MasonProtter
Copy link
Contributor

MasonProtter commented Jan 15, 2021

Even though there's always been that warning in the docs, I think there's likely a ton of code out there that's relying on 'improper' uses of @assert just becuase writing @assert cond is more convenient and legible than cond || throw("...").

If we're going to do this, I feel we should at least provide a replacement macro (or method to @assert?) that works like @assert but will throw regardless of whether you're in debug mode.

xref a discussion where this came up

@00vareladavid
Copy link
Contributor

I strongly agree with @MasonProtter. Having something which is like @assert but can not be disabled is useful. It seems some people in #25576 are ok with having a @check macro for this purpose.

@ericphanson
Copy link
Contributor

ericphanson commented Jan 9, 2022

#41342 implements an @check macro, by the way. I agree we should provide a simple correct alternative to @assert abuse first before doing something like this PR.

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.