op
is smallest operation available. For example add $I0, $I1, $I2
and goto label
. Ops are implemented in some kind of C with quite few macro-substitutions for accessing registers and flow control. Let's take simple add
op. op add(out INT, in INT, in INT) { $1 = $2 + $3; }
We declare op "add" with 3 parameters: first is output INTVAL, second and third is input INTVAL.
From this declaration we generate code similar to
opcode * Parrot_add_i_i_i(opcode_t * pc, PARROT_INTERP) { IREG(1) = IREG(2) + IREG(3); return pc + 4; }
We actually generate quite similar
add_i_i_ic
, add_i_ic_i
and add_i_ic_ic
for using INTVAL constants instead of INVTAL registers. But for simplicity let's look at this generated code only.Basically we replace register access with corresponding C macro - one of IREG, SREG, PREG and NREG for INT, STR, PMC and NUM. Which register to use is defined by op signature.
Line return pc + 4
is more interesting. - Each op has size. Minimal size is "1" - op itself.
- Each argument stored in same bytecode right after op. So op with 3 arguments will have size 4.
- Main VM loop is just call each op function and use returned value as new PC (Program Counter).
For so called flow ops we use quite few special macros:
goto ADDRESS(new pc)
goto NEXT()
expr ADDRESS(new pc)
expr NEXT()
All this (and more) transformations and code generations are done by "Ops Compiler".
opsc
for short.In ancient Parrot's history opsc was implemented as bunch of Perl5 regular expressions to generate bulk of C code. Without any semantic parsing of actual op bodies. It was simplest solution which works. But for today's Parrot needs it's way too simple. In next post I'll discuss more about it.