Sign In/My Account | View Cart  
advertisement


Listen Print

Beginning PMCs
by Jeffrey Goff | Pages: 1, 2

Virtual Tables

The code above used a curious construct:

  $1->vtable->set_number(INTERP,$1,$2);

Parameter $1 is a PMC, and since these are user-defined types, the code simply can't assign $2 to $1, as the non-PMC operations would do. Instead, each PMC has a table of function pointers assigned to it, and the interpreter calls the appropriate function.

For example, assuming that the P0 register is being initialized by the new P0,IntQueue instruction, the above code would run the set_number member of the IntQueue class. Since the type of P0 is decided on at runtime, the dispatch mechanism is completely independent of the parameter type. What this means in the case of the IntQueue type is that no modifications need to be made to the parrot/core.ops file.

Parrot Class Files

parrot/classes contains all of the PMC classes used by Parrot. Like the parrot/core.ops file, this too is preprocessed before final compilation, so all edits should be made to the parrot/classes/*.pmc files.

Creating a new class file from scratch is somewhat daunting, so we'll use an existing class file to base IntQueue on. While IntQueue is an aggregate type like PerlHash, the interface matches PerlInt closest, in that it only deals with one element at a time.

Start by copying parrot/classes/PerlInt.pmc to parrot/classes/IntQueue.pmc, and replace all instances of PerlInt with IntQueue. There will be some additional C code necessary that will be available in the sample source at the end of the article, but not discussed beyond the API.

Registering the parrot/classes/IntQueue.pmc is done in two files. Add the appropriate lines to parrot/global_setup.c to initialize the new PMC type, and add the new vtable entry to parrot/include/parrot/pmc.h. This is only done in the case of types that are intended to be part of Parrot itself; when Parrot has the ability to dynamically load PMC classes at runtime, a more flexible mechanism will be derived for registering classes, but for now, we'll pretend that IntQueue is going to be a core interpreter data type.

Within parrot/core.ops, the instructions the IntQueue type uses look like this:

  op new(out PMC, in INT) {
    PMC* newpmc;
    if ($2 <0 || $2 >= enum_class_max) {
      abort(); /* Deserve to lose */
    }
    newpmc = pmc_new(interpreter, $2);
    $1 = newpmc;
    goto NEXT();
  }
  
  inline op set(out PMC, in INT) {
    $1->vtable->set_integer_native(interpreter, $1, $2);
    goto NEXT();
  }
  
  inline op set(out INT, in PMC) {
    $1 = $2->vtable->get_integer(interpreter, $2);
    goto NEXT();
  }
  
  op if(in PMC, in INT) {
    if ($1->vtable->get_bool(interpreter, $1)) {
      goto OFFSET($2);
    }
    goto NEXT();
  }

Naturally, each of these call PMC vtable entries, and each one of these has to be implemented. As of this writing, the appropriate vtable entries as they are in parrot/classes/perlint.pmc look like this:

    void init () { /* This is called from pmc_new() */
        SELF->cache.int_val = 0;
    }
    void set_integer_native (INTVAL value) {
        SELF->cache.int_val = value;
    }
    INTVAL get_integer () {
        return SELF->cache.int_val;
    }
    BOOLVAL get_bool () {
        return pmc->cache.int_val != 0;
    }

Any code before the pmclass declaration in a parrot/classes/*.pmc file is literally copied into the C source, so we'll use this area to store our data structures and APIs. In order to make matters simple, we'll assume that the following API is available for our use:

  static CONTAINER* new_container ( void );
  static void enqueue ( CONTAINER* container, INTVAL value );
  static INTVAL dequeue ( CONTAINER* container );
  static INTVAL queue_length ( CONTAINER* container );

The API should be fairly straightforward to use. Initializing the container is done with new_container, which returns a pointer to our new queue data type. Adding a new queue element is done with enqueue, and deleting an element is done with dequeue. The queue's length can be found with queue_length.

The CONTAINER data type has to be stored somewhere, and we look into parrot/include/parrot/pmc.h to find out where to store it. We find the definition of the PMC structure to be:

    struct PMC {
      VTABLE *vtable;
      INTVAL flags;
      DPOINTER *data;
      union {
        INTVAL int_val;
        FLOATVAL num_val;
        DPOINTER *struct_val;
      } cache;
      SYNC *synchronize;
    };

There are two areas we can store data: data is used as a general dumping ground for a data type's internal data structures, and the cache union is used for fast access to simpler data structures. data is the right place to hang our CONTAINER structure.

Like most of the other files within Parrot, the IntQueue class is also preprocessed. The major preprocessing done here is to replace the SELF tag with a reference to the current PMC. In the rare case that you need a reference to the current interpreter, that tag is INTERP.

Initializing the IntQueue class is done with the init member. Since we're storing our queue in the data, we'll let the new_container function hand us a pointer to our new queue, and save that.

    void init () {
        SELF->data = new_container();
    }

Getting an integer out of the queue is done with the get_integer member. This isn't meant to be production-quality, so we won't worry about error checking. So, we'll simply return the integer from the container.

    INTVAL get_integer () {
        return dequeue((CONTAINER*)SELF->data);
    }

Adding an integer to the queue is done with the set_integer_native member. We'll simply use the enqueue function to place the integer onto the queue like so:

    void set_integer_native (INTVAL value) {
        enqueue(SELF->data,value);
    }

The final function we need to support is being able to determine whether the queue is empty, and we use the queue_length function for that. The PMC member function that does this is get_bool, and the code to access this is pretty straightforward:

    BOOLVAL get_bool () {
        return queue_length(SELF->data) != 0;
    }

This code has been checked in to the Parrot CVS, so feel free to look at the full version there. We've now walked through the major files needed to implement a Parrot Magic Cookie. Next time, we'll explore the functions needed to implement aggregate data types like hashes and arrays, and learn about the new garbage collection system.

In the meantime, if you want to play with implementing your own data types for Parrot, then take a look at docs/vtables.pod in the Parrot source tree for more information about the members that you can implement and how to design your own classes from scratch.