Skip to content

Commit 85eade2

Browse files
committed
Compiled frames: a sketch
1 parent 4976a36 commit 85eade2

File tree

1 file changed

+54
-0
lines changed

1 file changed

+54
-0
lines changed

src/framecompiler.jl

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
function instrument_method(m::Method)
2+
# Step 1: pick a new name
3+
name = Symbol(m.name, "#instrumented")
4+
f = Core.eval(m.module, :(function $name end)) # create the function object (with no methods)
5+
# Step 2: add an extra argument, `#framedata#`
6+
# (For simplicity here I'm cheating by assuming no internal locals and no type parameters.
7+
# In reality, among other things you'll need to renumber the slots)
8+
# For help, see https://docs.julialang.org/en/latest/devdocs/ast/#Expr-types-1, section on `method`
9+
sigm, src = m.sig, copy_codeinfo(Base.uncompressed_ast(m))
10+
sigt = Core.svec(typeof(f), sigm.parameters[2:end]..., FrameData) # first is #self#
11+
sigp = Core.svec()
12+
sigsv = Core.svec(sigt, sigp)
13+
push!(src.slotnames, Symbol("#framedata#"))
14+
# Step 3: instrument the body
15+
# - for each store to a slotnumber (SlotNumber on the LHS of :(=)), add a
16+
# "real" assignment to `#framedata#.locals`
17+
# - for each line that is `used`, add a store to `#framedata#.ssavalues`
18+
# Both of these will require renumbering the ssavalues. Again, for simplicity I'll just cheat:
19+
# change `x + 1` into `x + 2` for my `inc1` example
20+
src.code[1].args[end] = 2
21+
# Step 4: create the new method
22+
ccall(:jl_method_def, Cvoid, (Any, Any, Any), sigsv, src, m.module)
23+
# See below about "wrapper methods" that set up `#framedata#` for this method;
24+
# you might also create the wrapper here?
25+
return f
26+
end
27+
28+
function typeinf_instrumented(linfo::MethodInstance, params::Core.Compiler.Params)
29+
# This modifies the source code to add extra instrumentation
30+
# Step 1: call regular inference. Here, a major goal is to perform inlining,
31+
# so that we don't have to create so many `framedata`s
32+
src = Core.Compiler.typeinf_ext(linfo, params)
33+
# Step 2: replace the `:invoke` Exprs in `isrc` with invokes to `#framedata#`-instrumented variants.
34+
# You will also have to insert statements to create the framedata for that method.
35+
# Presumably, the best approach would be to just :invoke a wrapper method that creates a framedata
36+
# for the method we instrumented via `instrument_method`, and then calls the instrumented method.
37+
# That wrapper would have the same args as the original function, so it's really just a
38+
# case of changing which MethodInstance you call.
39+
# But of course you have to create these MethodInstances for the callees, so this will
40+
# have to recursively call `instrument_method` followed by `precompile` (which will call
41+
# this) on all the callees.
42+
return src
43+
end
44+
45+
function precompile_instrumented(f, atypes)
46+
# delightfully insane!
47+
# !!Note!!: before executing this, you need to compile `typeinf_instrumented` and anything it calls
48+
try
49+
ccall(:jl_set_typeinf_func, Cvoid, (Any,), typeinf_instrumented)
50+
precompile(f, atypes)
51+
finally
52+
ccall(:jl_set_typeinf_func, Cvoid, (Any,), Core.Compiler.typeinf_ext)
53+
end
54+
end

0 commit comments

Comments
 (0)