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

merge with SmartAsserts #38

Open
jw3126 opened this issue Feb 5, 2022 · 16 comments
Open

merge with SmartAsserts #38

jw3126 opened this issue Feb 5, 2022 · 16 comments

Comments

@jw3126
Copy link
Owner

jw3126 commented Feb 5, 2022

As @MrVPlusOne suggested on discourse it probably makes sense to merge this package with SmartAsserts.jl. I think the best option would be to copy the clever enable/disable mechanism to ArgCheck.jl. What do you think @MrVPlusOne ?

@jw3126
Copy link
Owner Author

jw3126 commented Feb 5, 2022

Currently OptionalArgCheck.jl gives compile time control over checks, but it is not really maintained and depends on IRTools. OTOH the mechanism of @MrVPlusOne is really lightweight and simple, so I would prefer it.

@MrVPlusOne
Copy link
Collaborator

I think the best option would be to copy the clever enable/disable mechanism to ArgCheck.jl. What do you think @MrVPlusOne ?

Yes, that sounds like a plan, and it should be very simple to implement. Also, I wonder if ArgCheck has a macro that follows @assert's signature (i.e., accepts an optional error message provided by the user). If not, would it be a good idea to implement such a macro using the existing infrastructure?

@jw3126
Copy link
Owner Author

jw3126 commented Feb 5, 2022

Awesome! Yes, you can already pass an error message as a second argument.

@jw3126 jw3126 mentioned this issue Feb 5, 2022
@jw3126
Copy link
Owner Author

jw3126 commented Feb 5, 2022

I wonder how the mechanism in SmartAsserts.jl interacts with precompilation. For instance if I disable argchecks in Main and import a package, that was already precompiled with argchecks enabled. Then checks in that package are still enabled right?

@MrVPlusOne
Copy link
Collaborator

Yes, I feel this is kind of inevitable if we want to switch the assertions at compile-time... But at least the user would be able to control the assertions inside their own packages.

@jw3126
Copy link
Owner Author

jw3126 commented Feb 6, 2022

I think this is a very serious bug. Especially in the reverse direction. Imagine a user disables checks in one session and under the hood a bunch of random packages get precompiled. In another session a user wants to track down a bug, but checks are silently still disabled.

@jw3126
Copy link
Owner Author

jw3126 commented Feb 6, 2022

Also I am not sure it is inevitable. A function redefinition triggers a recompile. Maybe check could insert a noop function imto the code and a @disable macro redefines this function into another noop

@jw3126
Copy link
Owner Author

jw3126 commented Feb 6, 2022

Or maybe just macroexpand a check into:

if ArgCheck.enabled()
    # the usual @check code
end

and we have enabled() = true in ArgCheck.jl, but of course the user can write ArgCheck.enabled() = false.

@MrVPlusOne
Copy link
Collaborator

I think this is a very serious bug. Especially in the reverse direction. Imagine a user disables checks in one session and under the hood a bunch of random packages get precompiled. In another session a user wants to track down a bug, but checks are silently still disabled.

Hmmm... I'm not familiar with how precompilation works in Julia. I kind of assumed that all dependencies are compiled before the current project for this simple approach to work, but I guess this might not always be the case? Then I agree that silently turning off checks in other packages sounds very dangerous.

@MrVPlusOne
Copy link
Collaborator

Or maybe just macroexpand a check into:

if ArgCheck.enabled()
    # the usual @check code
end

and we have enabled() = true in ArgCheck.jl, but of course the user can write ArgCheck.enabled() = false.

I wonder after the user redefined ArgCheck.enabled() = false in their package, whether it is guaranteed that all the previous @check calls will be recompiled? I remember seeing Julia complaining "incremental compilation may be fatally broken for this module" whenever I try to override a method definition of another package.

@MrVPlusOne
Copy link
Collaborator

If we only aim to let the user disable @checks in their own package, then I guess an alternative that does not have this "silent switch-off" issue would be to change ArgCheck.ENABLED::Ref{true} into ArgCheck.DISABLED_MODULES::Set{Module}. Then, in @check, we expand to noop if it's called within a module belonging to this set. And we can provide the user with another macro ArgCheck.@disable_checks_for_current_module that inserts the current module into this set.

@jw3126
Copy link
Owner Author

jw3126 commented Feb 6, 2022

I wonder after the user redefined ArgCheck.enabled() = false in their package, whether it is guaranteed that all the previous @check calls will be recompiled? I remember seeing Julia complaining "incremental compilation may be fatally broken for this module" whenever I try to override a method definition of another package.

Ah right, that is problematic.

@jw3126
Copy link
Owner Author

jw3126 commented Feb 6, 2022

If we only aim to let the user disable @Checks in their own package, then I guess an alternative that does not have this "silent switch-off" issue would be to change ArgCheck.ENABLED::Ref{true} into ArgCheck.DISABLED_MODULES::Set{Module}. Then, in @check, we expand to noop if it's called within a module belonging to this set. And we can provide the user with another macro ArgCheck.@disable_checks_for_current_module that inserts the current module into this set.

That would probably work. But I am not sure how useful it is. Personally, I have two main use cases for turning off checks.

  • One is I run a simulation, I am sure it is correct and I want best possible speed. In this situation, I want to disable all checks everywhere.
  • The other is I have a small immutable struct that gets instantiated in a hot loop and has an argcheck in its constructor. Say Particle(position, direction, energy) and (@argcheck energy > 0 in its constructor). The check introduces a branch, that prevents many optimizations in the hot loop. In this case, I would like to disable checks very locally in the hot loop only. Pretty much what @inbounds does.

What are your use cases? Would the ArgCheck.@disable_checks_for_current_module be useful for you?

@MrVPlusOne
Copy link
Collaborator

What are your use cases? Would the ArgCheck.@disable_checks_for_current_module be useful for you?

Personally, in most of my use cases, I don't really mind letting the checks stay. And for cases where I do perform very expensive checks (typically when I'm debugging something), I used to manually code up some API to control the checks (e.g., should_check && @assert <some expensive checks>), or simply comment out the expensive checks after the bug is fixed. But I guess sometimes it might be very handy for the user if they can turn off all their own tests using a simple switch.

I'm not sure about the idea of letting the user turn off the checks in other packages, though. It feels like a more principled approach might be for the other (performance-critical) packages to provide an API to turn off their checks?

The other is I have a small immutable struct that gets instantiated in a hot loop and has an argcheck in its constructor. Say Particle(position, direction, energy) and (@argcheck energy > 0 in its constructor). The check introduces a branch, that prevents many optimizations in the hot loop. In this case, I would like to disable checks very locally in the hot loop only. Pretty much what @inbounds does.

For this use case, can we implement something like @disable_checks_for_function which internally call disable_checks_for_current_module and enable_checks_for_current_module at the beginning and end of the function body?

@jw3126
Copy link
Owner Author

jw3126 commented Feb 7, 2022

I'm not sure about the idea of letting the user turn off the checks in other packages, though. It feels like a more principled approach might be for the other (performance-critical) packages to provide an API to turn off their checks?

That's true, I like the inbounds mechanism in Base. Would be good to have such a thing, but for other kinds of checks as well. https://github.com/simeonschaub/OptionalArgChecks.jl lets you sprinkle your code with markers and provides means to erase all code sections with a given marker at compile time. This allows to have custom mechanisms, close to the boundschecking in Base.
Sadly the package is based on IRTools. But I hope/expect that in future there will be stable apis for compiler plugins, that can be used instead of IRTools.

For this use case, can we implement something like @disable_checks_for_function which internally call disable_checks_for_current_module and enable_checks_for_current_module at the beginning and end of the function body?

I think @disable_checks_for_function would need to be added to the source code of a method? In that case I one can directly comment out the checks as well I guess?

I remember seeing Julia complaining "incremental compilation may be fatally broken for this module" whenever I try to override a method definition of another package.

There might be a hack to work around this. In Argcheck we define enabled(args...) = true, while the use can overload the more specific enabled() = true...

Right now I don't find any of the options we have very appealing. Personally, I would probably wait until there is a more stable replacement for IRTools and revive OptionalArgChecks. However, I am open to adding something in the meantime if you need it.

@MrVPlusOne
Copy link
Collaborator

I think @disable_checks_for_function would need to be added to the source code of a method? In that case I one can directly comment out the checks as well I guess?

Right, I probably misunderstood your use case. Now it sounds like you only want to disable the checks for specific uses of a method, not to turn off all of them. So the approach taken by OptionalArgChecks would be more suitable.

Right now I don't find any of the options we have very appealing. Personally, I would probably wait until there is a more stable replacement for IRTools and revive OptionalArgChecks. However, I am open to adding something in the meantime if you need it.

Sounds good. I guess in the meantime, I can implement a run-time checking mechanism for SmartAsserts so that people can still use this functionality if they do not mind a little bit of extra overhead.

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

No branches or pull requests

2 participants