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
runcorefrom 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_interpas 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_context, "ppp");
# Go!
say("================= INVOKE ===================");
$pc := $call($pc, $interp);
say("================= DONE ===================");
All this glued together:
First — PIR:
.sub 'main' :main
say "Answer"
say 42
say "Correct"
say 3.1415926
.end
Second — 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_context.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
And (drum roll) test run:
~/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.