Hi there
Since last post about “Crazy JIT Prototype” and progress of
opsc_llvm
branch I moved little bit further. Two major achievements:- Skeleton for generating JITted Subs is done.
- JITting of simple ops with function calls and constants
- Emulator of Parrot's
runcore
from within Parrot it self.
Emulation of runcore
is quite significant in terms of prototyping progress. Now I can not only generate functions using LLVM bindings, but also invoke it. This was grown up from another “crazy” idea for accessing parrot's guts.
Major problem with “emulated runcores” was that we don't expose enough guts to external world. And my JITter is very external. I was struggling by few days thinking how I can implement it. Until after plobsing mentioned on irc channel that op dlopen
(for lookup symbols in loaded DLLs) without actual DLL will lookup symbols inside currently running application. Bingo! “I can use dlopen
for lookup inside Parrot. And use Parrot Embed API for generating required stuff”. Few hours later I hacked first version of runcore
emulator using this technique. It looks like this:
# Create interp and seed it with bytecode my $this_interp := pir::getinterp(); my $interp := func("make_interpreter", "ppi")($this_interp, 0); func("Parrot_api_load_bytecode_file", "iPSP")($this_interp, $pbc, undef); # "Invoke" target Sub inside target interp. my $sub := Q:PIR{ %r = find_sub_not_null "main" }; my $pc; $pc := func("Parrot_PMC_invoke", "ppPP")($interp, $sub, $pc);What happens in this code is:
- Create new
parrot_interp
as child of current one.
- Load PBC into it.
- Call
Sub.invoke()
which do a lot of things to prepare CallContext, Continuation, etc inside new interpreter.
# Some engine my $engine := pir::new__psp("LLVM_Engine", $module); my $call := $engine.create_call(%jit_contextAll this glued together: First — PIR:, "ppp"); # Go! say("================= INVOKE ==================="); $pc := $call($pc, $interp); say("================= DONE ===================");
.sub 'main' :main say "Answer" say 42 say "Correct" say 3.1415926 .endSecond — old good
t/jit/test.jit
(which is much shorter now due proper encapsulation inside Ops::JIT
#! parrot-nqp # We want Test::More features for testing. Not NQP's builtin. pir::load_bytecode("opsc.pbc"); # Some preparation my $debug := 0; my $pir := 't/compilers/opsc/data/03.pir'; my $pbc := subst($pir, / 'pir' $/, 'pbc'); # Generate PBC file my @args := list("./parrot", "-o", $pbc, $pir); my $res := pir::spawnw__ip(@args); # OpLib my $oplib := pir::new__psp("OpLib", "core_ops"); # Parse "jitted.ops" my $ops_file := Ops::File.new("t/jit/jitted.ops", :oplib($oplib), :core(0), :quiet(!$debug), ); my $jitter := Ops::JIT.new($pbc, $ops_file, $oplib, debug => $debug); my $start := 0; my %jit_context := $jitter.jit($start); my $module := %jit_context<_module>; #%jit_contextAnd (drum roll) test run:.dump(); #$module.dump(); $module.verify(); # Create interp and seed it with bytecode my $this_interp := pir::getinterp(); my $interp := func("make_interpreter", "ppi")($this_interp, 0); func("Parrot_api_load_bytecode_file", "iPSP")($this_interp, $pbc, undef); # "Invoke" target Sub inside target interp. my $sub := Q:PIR{ %r = find_sub_not_null "main" }; my $pc; $pc := func("Parrot_PMC_invoke", "ppPP")($interp, $sub, $pc); # Some engine my $engine := pir::new__psp("LLVM_Engine", $module); my $call := $engine.create_call(%jit_context , "ppp"); # Go! say("================= INVOKE ==================="); $pc := $call($pc, $interp); say("================= DONE ==================="); sub func($name, $sig) { pir::dlfunc__ppss(undef, $name, $sig); } #%jit_context<_module>.dump(); # vim: ft=perl6
~/src/parrot (jit_prototype)$ ./parrot-nqp t/jit/test.t Segmentation fault
Hooray!
Wait a second. Wrong window.
~/src/parrot (jit_prototype)$ ./parrot-nqp t/jit/test.t ================= INVOKE =================== Answer 42 Correct 3.1415926 ================= DONE ===================Much better now :) See you next time. Probably with some more good news. E.g. handling of control structures in Ops::JIT. E.g.
if
, for
, etc.