Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion src/eval.jl
Original file line number Diff line number Diff line change
@@ -1,25 +1,52 @@
using Base: invokelatest

dummy() = return
const dummy_m = which(dummy, Tuple{})

function build_codeinfo(ir::IR)
ir = copy(ir)
ci = code_lowered(dummy, Tuple{})[1]
ci = Base.uncompressed_ir(dummy_m)
ci.inlineable = true
for arg in arguments(ir)
@static if VERSION >= v"1.10.0-DEV.870"
isnothing(ci.slottypes) && (ci.slottypes = Any[])
push!(ci.slottypes, Type)
end
push!(ci.slotnames, Symbol(""))
push!(ci.slotflags, 0)
end
argument!(ir, at = 1)
update!(ci, ir)
end

# JuliaLang/julia#48611: world age is exposed to generated functions.
if VERSION >= v"1.10.0-DEV.873"

function func(m::Module, ir::IR)
generator = @eval m begin
function $(gensym())(world::UInt, source, self,
$([Symbol(:arg, i) for i = 1:length(arguments(ir))]...))
return $build_codeinfo($ir)
end
end
@eval m begin
function $(gensym())($([Symbol(:arg, i) for i = 1:length(arguments(ir))]...))
$(Expr(:meta, :generated, generator))
$(Expr(:meta, :generated_only))
end
end
end

else

function func(m::Module, ir::IR)
@eval m (@generated function $(gensym())($([Symbol(:arg, i) for i = 1:length(arguments(ir))]...))
return $build_codeinfo($ir)
end)
end

end

func(ir::IR) = func(Main, ir)

evalir(m::Module, ir::IR, args...) = invokelatest(func(m, ir), args...)
Expand Down
80 changes: 70 additions & 10 deletions src/reflection/dynamo.jl
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,20 @@ end
# Used only for its CodeInfo
dummy(args...) = nothing

function dynamo(cache, f, args...)
function dynamo(cache, world, f, args...)
try
# XXX: @dynamo should pass the world to the transform function, so that any
# IR look-ups are done in the correct world.
spoofed_world[] = world
ir = transform(f, args...)::Union{IR,Expr,Nothing}
catch e
rethrow(CompileError(f, args, e))
finally
spoofed_world[] = nothing
end
# TODO: how to set proper edges on the returned code info? we have to
# propagate the bounds from `methods_by_ftype`, but that lookup is
# done by `IRTools.meta` and is not available here.
ir isa Expr && return ir
ir == nothing && return fallthrough(args...)
ir = lambdaself!(ir)
Expand All @@ -88,20 +96,21 @@ function dynamo(cache, f, args...)
argnames!(m, :args)
pushfirst!(m.code.slotnames, Symbol("#self#"))
else
m = @meta dummy(1)
m = @meta world dummy(1)
m === nothing && error("Error looking up metadata for $f")
m.code.method_for_inference_limit_heuristics = nothing
end
_self = splicearg!(ir)
prewalk!(x -> x === self ? _self : x, ir)
return update!(m.code, ir)
end

function dynamo_lambda(cache, f::Type{<:Lambda{S,I}}) where {S,I}
function dynamo_lambda(cache, world, f::Type{<:Lambda{S,I}}) where {S,I}
ir = cache[(S.parameters[2:end]...,)]
ir = getlambda(ir, I)
ir = lambdalift!(copy(ir), S, I)
closureargs!(ir)
m = @meta dummy(1)
m = @meta world dummy(1)
m.code.method_for_inference_limit_heuristics = nothing
return update!(m.code, ir)
end
Expand All @@ -116,6 +125,37 @@ function lifttype(x)
named ? Expr(:(::), x.args[1], T) : Expr(:(::), T)
end

const caches = Dict()

if VERSION >= v"1.10.0-DEV.873"

function dynamo_generator(world::UInt, source, self, args)
cache = if haskey(caches, self)
caches[self]
else
caches[self] = Dict()
end

ex = dynamo(cache, world, self, args...)
ex isa Core.CodeInfo && return ex

stub = Core.GeneratedFunctionStub(identity, Core.svec(:methodinstance, :args), Core.svec())
stub(world, source, ex)
end

function dynamo_lambda_generator(world::UInt, source, self, args)
f = self.parameters[1].parameters[1]
cache = caches[f]

ex = dynamo_lambda(cache, world, self)
ex isa Core.CodeInfo && return ex

stub = Core.GeneratedFunctionStub(identity, Core.svec(:methodinstance, :args), Core.svec())
stub(world, source, ex)
end

end

macro dynamo(ex)
@capture(shortdef(ex), (name_(args__) = body_) |
(name_(args__) where {Ts__} = body_)) ||
Expand All @@ -124,12 +164,32 @@ macro dynamo(ex)
f, T = isexpr(name, :(::)) ?
(length(name.args) == 1 ? (esc(gensym()), esc(name.args[1])) : esc.(name.args)) :
(esc(gensym()), :(Core.Typeof($(esc(name)))))
gendef = quote
local cache = Dict()
@generated ($f::$T)($(esc(:args))...) where $(Ts...) =
return IRTools.dynamo(cache, $f, args...)
@generated (f::IRTools.Inner.Lambda{<:Tuple{<:$T,Vararg{Any}}})(args...) where $(Ts...) =
return IRTools.Inner.dynamo_lambda(cache, f)
gendef = if VERSION >= v"1.10.0-DEV.873"
quote
function ($f::$T)($(esc(:args))...) where $(Ts...)
$(Expr(:meta, :generated, dynamo_generator))
$(Expr(:meta, :generated_only))
end
function (f::IRTools.Inner.Lambda{<:Tuple{<:$T,Vararg{Any}}})(args...) where $(Ts...)
$(Expr(:meta, :generated, dynamo_lambda_generator))
$(Expr(:meta, :generated_only))
end
end
else
quote
@generated function ($f::$T)($(esc(:args))...) where $(Ts...)
cache = if haskey($caches, $T)
$caches[$T]
else
$caches[$T] = Dict()
end
return IRTools.dynamo(cache, nothing, $f, args...)
end
@generated function (f::IRTools.Inner.Lambda{<:Tuple{<:$T,Vararg{Any}}})(args...) where $(Ts...)
cache = $caches[$T]
return IRTools.Inner.dynamo_lambda(cache, nothing, f)
end
end
end
quote
$(isexpr(name, :(::)) || esc(:(function $name end)))
Expand Down
47 changes: 38 additions & 9 deletions src/reflection/reflection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ end
untvar(t::TypeVar) = t.ub
untvar(x) = x

const spoofed_world = Ref{Union{Nothing,UInt}}(nothing)

"""
meta(Tuple{...})

Expand All @@ -40,30 +42,49 @@ See also [`@meta`](@ref).
julia> IRTools.meta(Tuple{typeof(gcd),Int,Int})
Metadata for gcd(a::T, b::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8} in Base at intfuncs.jl:31
"""
function meta(T; types = T, world = worldcounter())
function meta(T; types = T, world=nothing)
if world === nothing
# if the user didn't specify a world, use the current world,
# or a spoofed world if we're executing a dynamo
world = something(spoofed_world[], worldcounter())
end
F = T.parameters[1]
F == typeof(invoke) && return invoke_meta(T; world = world)
F isa DataType && (F.name.module === Core.Compiler ||
F <: Core.Builtin ||
F <: Core.Builtin) && return nothing
_methods = Base._methods_by_ftype(T, -1, world)
min_world = Ref{UInt}(typemin(UInt))
max_world = Ref{UInt}(typemax(UInt))
has_ambig = Ptr{Int32}(C_NULL) # don't care about ambiguous results
_methods = if VERSION >= v"1.7.0-DEV.1297"
Base._methods_by_ftype(T, #=mt=# nothing, #=lim=# -1,
world, #=ambig=# false,
min_world, max_world, has_ambig)
else
Base._methods_by_ftype(T, #=lim=# -1,
world, #=ambig=# false,
min_world, max_world, has_ambig)
end
_methods === nothing && return nothing
_methods isa Bool && return nothing
length(_methods) == 0 && return nothing
type_signature, sps, method = last(_methods)
sps = svec(map(untvar, sps)...)
@static if VERSION >= v"1.2-"
mi = Core.Compiler.specialize_method(method, types, sps)
ci = hasgenerator(mi) ? Core.Compiler.get_staged(mi) : Base.uncompressed_ast(method)
ci = hasgenerator(mi) ? get_staged(mi, world) : Base.uncompressed_ast(method)
else
mi = Core.Compiler.code_for_method(method, types, sps, world, false)
ci = hasgenerator(mi) ? Core.Compiler.get_staged(mi) : Base.uncompressed_ast(mi)
ci = hasgenerator(mi) ? get_staged(mi, world) : Base.uncompressed_ast(mi)
end
Base.Meta.partially_inline!(ci.code, [], method.sig, Any[sps...], 0, 0, :propagate)
Meta(method, mi, ci, method.nargs, sps)
end

function invoke_tweaks!(ci::CodeInfo)
if VERSION >= v"1.10.0-DEV.870" && ci.slottypes !== nothing
ci.slottypes = [typeof(invoke), ci.slottypes[1], Type, ci.slottypes[2:end]...]
end
ci.slotnames = [:invoke, ci.slotnames[1], :T, ci.slotnames[2:end]...]
ci.slotflags = [0x00, ci.slotflags[1], 0x00, ci.slotflags[2:end]...]
ci.code = map(ci.code) do x
Expand All @@ -85,17 +106,25 @@ function invoke_meta(T; world)
end

"""
@meta f(args...)
@meta [world] f(args...)

Convenience macro for retrieving metadata without writing a full type signature.

julia> IRTools.@meta gcd(10, 5)
Metadata for gcd(a::T, b::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8} in Base at intfuncs.jl:31
"""
macro meta(ex)
isexpr(ex, :call) || error("@meta f(args...)")
f, args = ex.args[1], ex.args[2:end]
:(meta(typesof($(esc.((f, args...))...))))
macro meta(ex...)
if length(ex) == 1
world = nothing
call = ex[1]
elseif length(ex) == 2
world, call = ex
else
error("@meta [world] f(args...)")
end
isexpr(call, :call) || error("@meta [world] f(args...)")
f, args = call.args[1], call.args[2:end]
:(meta(typesof($(esc.((f, args...))...)); world=$(esc(world))))
end

function code_ir(f, T)
Expand Down
20 changes: 19 additions & 1 deletion src/reflection/utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ function slots!(ci::CodeInfo)
function f(x)
x isa Slot || return x
haskey(ss, x) && return ss[x]
@static if VERSION >= v"1.10.0-DEV.870"
push!(ci.slottypes, x.type)
end
push!(ci.slotnames, x.id)
push!(ci.slotflags, 0x00)
ss[x] = SlotNumber(length(ci.slotnames))
Expand Down Expand Up @@ -128,14 +131,29 @@ function splicearg!(ir::IR)
return arg
end

@static if VERSION < v"1.8.0-DEV.267"
@static if VERSION >= v"1.10.0-DEV.870"
function replace_code_newstyle!(ci, ir, _)
isnothing(ci.slottypes) && (ci.slottypes = Any[])
return Core.Compiler.replace_code_newstyle!(ci, ir)
Copy link
Contributor

Choose a reason for hiding this comment

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

you also might need to allocate slottypes, if you didn't already do that

    ci.slottypes isa Array || (ci.slottypes = [])

end
elseif VERSION < v"1.8.0-DEV.267"
function replace_code_newstyle!(ci, ir, n_argtypes)
return Core.Compiler.replace_code_newstyle!(ci, ir, n_argtypes-1)
end
else
using Core.Compiler: replace_code_newstyle!
end

@static if VERSION >= v"1.10.0-DEV.870"
function get_staged(mi, world)
return Core.Compiler.get_staged(mi, world)
end
else
function get_staged(mi, _)
return Core.Compiler.get_staged(mi)
end
end

function update!(ci::CodeInfo, ir::Core.Compiler.IRCode)
replace_code_newstyle!(ci, ir, length(ir.argtypes))
ci.inferred = false
Expand Down