Beginning PMCs
by Jeffrey GoffJanuary 30, 2002
The PMC, or Parrot Magic Cookie, type is a special data container for
user-defined data types. Because these user-defined types are
essentially implementations of a set of methods, we refer to them as PMC
classes. Currently, the legal PMC classes are the PerlInt,
PerlNum, PerlString, PerlArray, and PerlHash types. The
PerlInt, PerlNum and PerlString data types combine to form the
PerlScalar data type.
PMC registers, unlike the basic Integer, Number, and String registers,
must be specially allocated with the new P0, PMCType instruction.
Other operations like set P0,5 are handled by special functions that are implemented by the PMC class. The rest of this article is about how
to create your own PMC class implementation, alongside the PerlInt and
PerlHash data types.
For our example, we're going to implement a simple queue data
structure. Our queue will be a set of integers; the queue will grow when
an integer is assigned to it, and will shrink when an integer is read
from it. We'll use the PerlInt class as a basis, so it may be helpful
to look at some examples of operations that use it:
new P0, PerlInt # Create a new PMC in the 'PerlInt' class set P0, 1234 # Set the value of the PMC to 1234 set P0, "4567" # Set the value of the PMC to 4567 set P0, 12.34 # Set the value of the PMC to 12 set I0, P0 # Set I0 to the current value of the PMC print P0 print "n"
Note that no special instructions like set_string or set_float
were required to assign data of different types to the PMC. Each
instruction does the Right Thing given the initial type of the PMC. This
has several important consequences when designing new data types, the
largest of which is that it generally isn't necessary to add special
instructions to access data contained within a PMC.
On the other side, this means that PMCs should attempt to behave rationally in all situations. It's not an onerous requirement, but in some cases, rational behavior is hard to define. Queues are fairly simple to define though, in terms of behavior. A queue has one way to get data in and one way to get data out.
Since we can use one instruction for multiple classes, we'll use set Pn,In
to add an integer to the queue, and set In,Pn to get an
element out of the queue. The last operation we need to perform on a
queue is to determine whether the queue is empty. The PerlArray class uses
set In,Pn to return the length of the array into In, but we've
already decided to use that to get an integer out of the queue.
Instead of set In,Pn to determine how many elements are in the queue,
all we really need to know is whether the queue is empty or in use. For that,
we can use the handy boolean operator, if Pn,In. Here, the integer
register is actually the number of instructions to skip over if the
condition is true. We'll have it branch if the queue is empty.
So, our IntQueue data type will implement three instructions. First, the set Pn,In instruction will add an integer to the queue.
Second, when the queue is empty, if Pn,In will branch to the appropriate
offset. Finally, set In,Pn will dequeue the last integer in the queue
and place it into the appropriate integer register.
Some sample source using IntQueue may come in handy at this time:
new P0, IntQueue # Create the queue set P0, 7 # Enqueue 7 set P0, -43 # Enqueue -43 set I0, P0 # Dequeue 7 print I0 # Should print '7'. if P0, QUEUE_emPTY # Goto label 'QUEUE_emPTY'
Core Operations
Before forging ahead with the IntQueue, let's take a look at the core
operations file. Within your CVS tarball, open parrot/core.ops and
search for the set operations. While there are files such as
parrot/core_ops.c and parrot/Parrot/OpLib/core_ops.pm, this is the
master file. Changes in parrot/core_ops.c will be overwritten the
next time you build, so make your edits to parrot/core.ops.
Having said that, let's look at a sample PMC operation.
inline op set(out PMC, in NUM) {
$1->vtable->set_number(interpreter,$1,$2);
goto NEXT();
}
Since core.ops is split into a Perl and C source file, the syntax is,
of necessity, a mixture of Perl and C. The 'inline' declaration is a
hint to the JIT compiler, which is beyond the scope of the article.
Parameters also have hints for the JIT compiler, but the most important
bits here are the PMC and NUM tags, because these let the compiler
know what types this operation can take.
When preprocessing into Perl, the prototype is the only piece of interest, as the assembler only needs to know the name and parameter list in order to build the assembly code.
C preprocessing is a bit more complicated, but still fairly
straightforward. Tokens like $2 are replaced with the appropriate
code to access the declared parameter, and a few keywords like NEXT()
are replaced with code to return the next instruction in the stream.
With the exception of those tags, the rest of the code is pure C, with access to all of the Parrot internals. Of course, you shouldn't access such things as the register internals, but the rest of the C API is available, the most common APIs being located in parrot/include/parrot/string.h and parrot/include/parrot/key.h, the latter primarily being used for aggregate data structures.
The preprocessor, while slightly confusing, is much more flexible than
the current system of nested CPP macros that Perl currently uses, and
hopefully easier to understand.
Pages: 1, 2 |

