JuliaLang / julia

The Julia Programming Language
https://julialang.org/
MIT License
45.78k stars 5.49k forks source link

Propagate current abstract interpreter through runtime calls #46220

Open vchuravy opened 2 years ago

vchuravy commented 2 years ago

@aviatesk and I looked at executing the result of a custom AbstractInterpreter by stashing the output of the top-level inferred code into an OpaqueClosure. While this worked as a first approximation, the moment you encounter a runtime function jl_invoke or jl_apply_generic execution is swapped back to the NativeInterpreter.

I tried addressing this for invokelatest through a late Cassette-style transform, but that was rather unsatisfactory and for completeness we would need an early Cassette-style transform which would minimize the usefulness.

I could see two approaches, either similar to world-age we set a PTLS field that holds the current AbstractInterpreter or we thread it through as a proper "contextual" argument in codegen.

@keno do you have any other thoughts on how to handle this?

This got spurred by a discussion at JuliaCon whether we could do something like @oxinabox MetalReadAhead.jl with OverlayMethodTables + AbstractInterpreter. I played around with this during the hackathon today and the result of that is https://github.com/vchuravy/FancyDebugger.jl

Keno commented 2 years ago

Codegen has support to do this rewrite:

https://github.com/JuliaLang/julia/blob/5d2e24f4767a020caf5e2c52d2c198ebd7e93c03/src/julia.h#L2203-L2205

The rough design I had in mind here is that you'd have something like:

struct Context <: Core.CompilerPlugin;
end

function Core.Compiler.do_plugin_stuff(::Type{Context}, frame::InferenceState)
[...]
insert_into_global_cache!(Tuple{Context, frame.linfo.specTypes...}, ...) 
end

and then use the codegen support to address the issue you're raising.

vchuravy commented 2 years ago

Hm okay, I see how that would work. We would still need to add a invoke_with_cgparams or more likely invoke_in_context to set the CG params.

What about the interpreter? And we will need this scoped to a task, and inherited to tasks created from it. Reflection like return_type and effects need to be consistent as well.

@aviatesk do I understand this right that in NativeInterpreter we have a local ephemeral cache? And so we wouldn't want to pass-through an actual instance of the abstract interpreter, but rather a (world)->NativeInterpreter(world) closure?