diff --git a/src/framecompiler.jl b/src/framecompiler.jl new file mode 100644 index 00000000..7d83cfa5 --- /dev/null +++ b/src/framecompiler.jl @@ -0,0 +1,54 @@ +function instrument_method(m::Method) + # Step 1: pick a new name + name = Symbol(m.name, "#instrumented") + f = Core.eval(m.module, :(function $name end)) # create the function object (with no methods) + # Step 2: add an extra argument, `#framedata#` + # (For simplicity here I'm cheating by assuming no internal locals and no type parameters. + # In reality, among other things you'll need to renumber the slots) + # For help, see https://docs.julialang.org/en/latest/devdocs/ast/#Expr-types-1, section on `method` + sigm, src = m.sig, copy_codeinfo(Base.uncompressed_ast(m)) + sigt = Core.svec(typeof(f), sigm.parameters[2:end]..., FrameData) # first is #self# + sigp = Core.svec() + sigsv = Core.svec(sigt, sigp) + push!(src.slotnames, Symbol("#framedata#")) + # Step 3: instrument the body + # - for each store to a slotnumber (SlotNumber on the LHS of :(=)), add a + # "real" assignment to `#framedata#.locals` + # - for each line that is `used`, add a store to `#framedata#.ssavalues` + # Both of these will require renumbering the ssavalues. Again, for simplicity I'll just cheat: + # change `x + 1` into `x + 2` for my `inc1` example + src.code[1].args[end] = 2 + # Step 4: create the new method + ccall(:jl_method_def, Cvoid, (Any, Any, Any), sigsv, src, m.module) + # See below about "wrapper methods" that set up `#framedata#` for this method; + # you might also create the wrapper here? + return f +end + +function typeinf_instrumented(linfo::MethodInstance, params::Core.Compiler.Params) + # This modifies the source code to add extra instrumentation + # Step 1: call regular inference. Here, a major goal is to perform inlining, + # so that we don't have to create so many `framedata`s + src = Core.Compiler.typeinf_ext(linfo, params) + # Step 2: replace the `:invoke` Exprs in `isrc` with invokes to `#framedata#`-instrumented variants. + # You will also have to insert statements to create the framedata for that method. + # Presumably, the best approach would be to just :invoke a wrapper method that creates a framedata + # for the method we instrumented via `instrument_method`, and then calls the instrumented method. + # That wrapper would have the same args as the original function, so it's really just a + # case of changing which MethodInstance you call. + # But of course you have to create these MethodInstances for the callees, so this will + # have to recursively call `instrument_method` followed by `precompile` (which will call + # this) on all the callees. + return src +end + +function precompile_instrumented(f, atypes) + # delightfully insane! + # !!Note!!: before executing this, you need to compile `typeinf_instrumented` and anything it calls + try + ccall(:jl_set_typeinf_func, Cvoid, (Any,), typeinf_instrumented) + precompile(f, atypes) + finally + ccall(:jl_set_typeinf_func, Cvoid, (Any,), Core.Compiler.typeinf_ext) + end +end