-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
Mark codeinst->inferred as atomic and rescope type inference lock #44968
Conversation
You need to also tell Julia that it is atomic (see how we deal with code_instance_constfields, for an example) |
base/compiler/typeinfer.jl
Outdated
@@ -227,7 +227,11 @@ function finish!(interp::AbstractInterpreter, caller::InferenceResult) | |||
end | |||
|
|||
function _typeinf(interp::AbstractInterpreter, frame::InferenceState) | |||
typeinf_nocycle(interp, frame) || return false # frame is now part of a higher cycle | |||
ccall(:jl_typeinf_begin, Cvoid, ()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is still a very wide lock. You should only require it inside cache_result!
right now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the typeinf lock is now scoped to just cache_result!
rather than all of type inference, is that sufficient to deprioritize the typeinf lock to be below the codegen lock?
src/codegen.cpp
Outdated
@@ -8123,8 +8123,7 @@ void jl_compile_workqueue( | |||
else { | |||
// Reinfer the function. The JIT came along and removed the inferred | |||
// method body. See #34993 | |||
if (policy != CompilationPolicy::Default && | |||
codeinst->inferred && codeinst->inferred == jl_nothing) { | |||
if (policy != CompilationPolicy::Default && jl_atomic_load_relaxed(&codeinst->inferred) == jl_nothing) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extract into a temporary value jl_value_t *inferred = jl_atomic_load_relaxed(&codeinst->inferred)
and insert explicit NULL
check if (inferred && inferred == jl_nothing)
following what we do for the other parts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not seeing what the explicit NULL check contributes to the if statement. If it is null, it's not going to be == to jl_nothing anyways, and if it's equal to jl_nothing we know it's non-null.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are there any other fields of jl_code_instance_t that we should mark as atomic? Current non-atomic fields include:
|
method definition -- constant |
I think I'll mark these as atomic then, we can always downgrade from atomic to not-atomic if their safety is proven at a later point.
I'll go ahead and make this field as atomic as well, since our plans involve removing the codegen lock too. Should I add the first few constant fields to the code_instance_constfields array as well? |
Actually scanning through the code, these are only set at construction time, so they should also be constant. |
That would not be legal, since they are not independently atomic, but must be read synchronously with the other fields |
That would be great, except that we'd probably like to avoid making |
I've marked purity_bits as atomic then, to make it clear that the field may be modified on multiple threads. |
base/compiler/typeinfer.jl
Outdated
@@ -394,12 +395,14 @@ function cache_result!(interp::AbstractInterpreter, result::InferenceResult) | |||
if !already_inferred | |||
inferred_result = transform_result_for_cache(interp, linfo, valid_worlds, result.src, result.ipo_effects) | |||
code_cache(interp)[linfo] = CodeInstance(result, inferred_result, valid_worlds) | |||
ccall(:jl_typeinf_begin, Cvoid, ()) | |||
if track_newly_inferred[] | |||
m = linfo.def | |||
if isa(m, Method) | |||
m.module != Core && push!(newly_inferred, linfo) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vtjnash it looks like the only thing that needs to be protected at this point is this newly_inferred array (and potentially the track_newly_inferred boolean). How should this array be handled? Should it become a custom type known to the compiler so that we can put a lock around the object, or should we switch it to a different threadsafe datatype (e.g. some kind of concurrent set), or should we put a julia lock to protect these variables?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like we should delete it. I have no idea what this is. Sounds like debugging?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Judging by its uses in system image creation, it doesn't look like it's safe to remove. For now, I'll just guard it with a jl_mutex_t on the C side and remove the typeinf lock altogether, and we can come up with a better plan for dealing with this array later.
6a8a343
to
bdc6f2f
Compare
base/compiler/typeinfer.jl
Outdated
const newly_inferred = MethodInstance[] | ||
const consume = :monotonic |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tkf is this even accurate though? consume
ordering implies it does data dependency tracking, which this does not do. What we want is only monotonic
+depends as used in the linux kernel (https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p0124r7.html)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, this definition is not correct. I totally agree. The reason why I was suggesting this was that, if the author wants to have the release-consume ordering, it's useful for communicating the intention with other programmers. Otherwise, if a programmer (maybe just me) tries to understand the code purely based on the C++ memory model, it becomes really confusing. The word "consume" is a magic spell telling the reader that it's outside (the well-established subset of) the C++ memory model.
If you want READ_ONCE
/WRITE_ONCE
, I think using macros with similar names also makes sense. I still haven't followed the Linux-Kernel Memory Model so I don't understand when and how they can be used. But I think I actually prefer this idea at a high level.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should I then define this as depends
instead of consume
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The linux kernel model defines depends
as a no-op, and defines that monotonic
is sufficient for all of our uses
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's an implementation and not a definition. There is a formal definition of monotonic
. It doesn't talk about dependencies.
Also, the implementation has to use monotonic
and volatile, IIUC. This is probably not required for how it's used in this patch, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pchintalapudi Actually, this PR is probably not the right place to discuss this. Please feel free to revert my suggestion and use literal :monotonic
everywhere. I probably should open this myself sometime later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The use of volatile
is orthogonal and forces forward progress for READ_ONCE, which otherwise is not necessarily specified in the C11 memory model, and is not relevant here. All we exactly care is that the linux memory model defines that monotonic is correct here and sufficient (because it defines that depends
is a no-op).
The linux kernel has also, thus far, rejected the C11 memory model as the model definition of monotonic
does not match the usual hardware behavior. That is why I suggested we will follow the linux memory model, not C11 here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we will follow the linux memory model, not C11 here
By "here", do you mean in the implementation of the Julia compiler or in the Julia language? I gave up on complaining when I see LKMM in the compiler and the runtime (in fact, the only point I'm suggesting in this PR is to be explicit for cases when/how it uses LKMM) but I'll still strongly oppose the idea of using LKMM in the Julia language :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The compiler, runtime, and language are all implemented in the Julia language though 😂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you pick up a "non-default" memory model like LKMM for a given piece of code, you are not using the language that is typically associated with the syntax of the program you are writing. In this sense, Linux is not written in C. Likewise, I didn't mind if the Julia compiler is written in *.jl
files but does not use Julia language. All I'm suggesting here is to clearly mark the places that do not use the Julia language.
(In this comment, I'm assuming that memory ordering semantics is equivalent to C++, of course. If you specify the semantics of @atomic :monotonic ...
as something different in the Julia language, it may be possible to use LKMM.)
bdc6f2f
to
8e76caf
Compare
@nanosoldier |
1 similar comment
@nanosoldier |
Your package evaluation job has completed - possible new issues were detected. A full report can be found here. |
This coopts half of #44968 to mark jl_code_instance_t fields as either atomic or const. Co-authored-by: Valentin Churavy <[email protected]>
This coopts half of JuliaLang#44968 to mark jl_code_instance_t fields as either atomic or const. Co-authored-by: Valentin Churavy <[email protected]>
This coopts half of JuliaLang#44968 to mark jl_code_instance_t fields as either atomic or const. Co-authored-by: Valentin Churavy <[email protected]>
No description provided.