Skip to content

Expose function to check if precompile guaranteed to fail (similar to hasmethod) #228

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

Draft
wants to merge 1 commit into
base: v1.10.2+RAI
Choose a base branch
from

Conversation

nickrobinson251
Copy link
Member

@nickrobinson251 nickrobinson251 commented Apr 4, 2025

PR Description

What does this PR do?
adds a function to check if a precompile statement will have no effect

use-case

  • we want to precompile some subset specializations of a function (into our sysimg)
  • since this is not all specialization we don't generate the list by reflection, but instead write them out by hand
  • However, this is error-prone and liable to going stale... so the question is: how can we be sure the precompile calls are valid?
  • The safest way is: run the precompiles and throw is they fail, like precompile(...) || @assert false , but this means we have to actually wait for the compilation to happen (which is prohibitively slow)
  • Is there a faster way that would still give us good signal? the most common way (we suspect) for hand written precompile(f, argtypes) to fail is because their is no method specialization for f with those argtypes (as opposed to because compilation of such a method specialization fails), and that we can check with the function added in this PR!

i should run some timings but undoubtedly looking up a method specialization is the faster part compared to actually compiling, so this (I believe) strikes a good balance of being useful enough and fast enough.

We can't use hasmethod as it has both false positives (too loose):

julia> hasmethod(sum, (AbstractVector,))
true

julia> precompile(sum, (AbstractVector,))
false

julia> Base.precompilable(sum, (AbstractVector,)) # <- this PR
false

and also false negatives (too strict):

julia> bar(@nospecialize(x::AbstractVector{Int})) = 42
bar (generic function with 1 method)

julia> hasmethod(bar, (AbstractVector,))
false

julia> precompile(bar, (AbstractVector,))
true

julia> Base.precompilable(bar, (AbstractVector,)) # <- this PR
true

We can't use e.g. hasmethod && isconcretype as it has false negatives (too strict):

julia> has_concrete_method(f, argtypes) = all(isconcretetype, argtypes) && hasmethod(f, argtypes)
has_concrete_method (generic function with 1 method)

julia> has_concrete_method(bar, (AbstractVector,))
false

julia> has_concrete_method(convert, (Type{Int}, Int32))
false

julia> precompile(convert, (Type{Int}, Int32))
true

julia> Base.precompilable(convert, (Type{Int}, Int32))  # <- this PR
true

Open questions

  • shall we open this PR upstream?
  • what shall we name this?
    • isprecompilable? has_precompilable_specialization? has_precompilable_method?
  • where should the code live? in loading.jl next to precompile or in reflection.jl next to hasmethod?
  • should we mark this public (if opened upstream)?

Checklist

Requirements for merging:

  • I have opened an issue or PR upstream on JuliaLang/julia: <link to JuliaLang/julia>
  • I have removed the port-to-* labels that don't apply.
  • I have opened a PR on raicode to test these changes:

@@ -2942,6 +2942,19 @@ JL_DLLEXPORT int jl_compile_hint(jl_tupletype_t *types)
return 1;
}

// true if jl_get_compile_hint_specialization can find a method instance for us to try
// to compile
JL_DLLEXPORT int jl_has_compile_hint_specialization(jl_tupletype_t *types)
Copy link
Member Author

@nickrobinson251 nickrobinson251 Apr 4, 2025

Choose a reason for hiding this comment

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

this is just jl_compile_hint from above (which is what precompile(f, argtypes) calls), but without the jl_compile_method_instance call

size_t world = jl_atomic_load_acquire(&jl_world_counter);
size_t min_valid = 0;
size_t max_valid = ~(size_t)0;
jl_method_instance_t *mi = jl_get_compile_hint_specialization(types, world, &min_valid, &max_valid, 1);
Copy link
Member Author

Choose a reason for hiding this comment

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

jl_get_compile_hint_specialization first finds a "compilable method match" then calls jl_method_match_to_mi to create a "method instance" -- if that step can't return NULL then i suppose we don't need to spend the time creating the "method instance" and could just check if we found a "method match" ?

Copy link
Member

Choose a reason for hiding this comment

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

yeah interesting. i'm not sure whether that step is generally nullable or not. 🤔

The code you have here, now, roughly matches what i had in mind when you described this idea to me 👍

Copy link
Member Author

Choose a reason for hiding this comment

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

@kpamnany is going to ask Gabriel about this

@nickrobinson251 nickrobinson251 changed the title Expose function to check if precompile guaranteed to fail Expose function to check if precompile guaranteed to fail (similar to hasmethod but stricter) Apr 7, 2025
@nickrobinson251 nickrobinson251 changed the title Expose function to check if precompile guaranteed to fail (similar to hasmethod but stricter) Expose function to check if precompile guaranteed to fail (similar to hasmethod) Apr 7, 2025
Comment on lines +3223 to +3225
function precompilable(@nospecialize(f), @nospecialize(argtypes::Tuple))
precompile(Tuple{Core.Typeof(f), argtypes...})
end
Copy link
Member

Choose a reason for hiding this comment

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

maybe we could call this has_method_instance_match?

Comment on lines +1061 to +1073
module PrecompilableTest
foo(v::AbstractVector) = first(v) +1
end
@testset "precompilable" begin
@test !Base.precompilable(PrecompilableTest.foo, (AbstractVector,))
@test !Base.precompilable(PrecompilableTest.foo, (AbstractVector{Int},))
@test !Base.precompilable(PrecompilableTest.foo, (Vector,))
@test Base.precompilable(PrecompilableTest.foo, (Vector{Int},))
@test hasmethod(PrecompilableTest.foo, (AbstractVector,))
@test hasmethod(PrecompilableTest.foo, (AbstractVector{Int},))
@test hasmethod(PrecompilableTest.foo, (Vector,))
@test hasmethod(PrecompilableTest.foo, (Vector{Int},))
end
Copy link
Member

Choose a reason for hiding this comment

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

Would it make sense to add tests for all the false positive and false negative cases in the PR description?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants