NAME

docs/pdds/pddXX_pmc.pod - PMCs

STATUS

Proposal

VERSION

$Revision: 18216 $

ABSTRACT

This document defines Parrot Magic Cookies (PMCs).

[[ maybe rename PMC to PO (Parrot Objects) or such to reduce confusion with Perl5's PMC (compiled .pm files). ]]

TERMINOLOGY

This document uses OPMC, when speaking of "old" PMCs of Parrot Version 0.4.6 or less. PMC is the new layout as proposed in this document.

An attribute is otherwise also known as a field or structure element, but I'm using attribute here because the difference of PMCs and Parrot Objects should be minimized.

DESCRIPTION

PMCs are Parrot's low-level objects implemented in C. PMCs are small non-resizable variable-sized structures. The PMC itself is the common part of all PMCs. The per-PMC payload holds PMC-specific attributes.

PMC Layout

+---------------+
|   vtable      |	# common PMC attribute
+---------------+
|   flags       |     # common PMC attribute
+---------------+
|   attrib_1    |     # "user" defined part 
|   ...         |
+---------------+

#define THE_PMC \
  VTABLE *vtable; \
  UINTVAL flags

An Integer Value PMC could be defined with:

struct VInt_PMC {
  THE_PMC;
  INTVAL val;
};

Such PMC definitions are typically private to the .pmc files. All access to PMCs shall be through VTABLE functions or methods. OTOH some widely used PMCs might export their attributes for public use and are then part of the Parrot API.

A typical VInt vtable function would look like this:

INTVAL get_integer()
  VInt_PMC *me = (VInt_PMC*)SELF;     # [1]
  return me->val;
}

The OPMC can be defined in terms of a PMC by rearranging the structure elements.

[1] The PMC compiler could provide this line automagically and define a convenience variable ME similar to the current SELF.

PMC creation

PMCs are created via VTABLE_new or variants of _new. It's up to the PMC to initialize it's attributes. new is a class method, i.e. it's called with the PMC's class as SELF.

PMC* new() {
  VInt_PMC *me = new_bufferlike_header(INTERP, sizeof(VInt_PMC));
  me->val = 0;
  return (PMC*)me;
}

[[ rename new_bufferlike_header to something more meaningful ]]

Optimization

The vtable can provide a pointer to the sized header pool to possibly speedup allocation.

OPMC vs. PMC creation

PMCs with a non-default new method are PMCs, The old scheme via pmc_new and VTABLE_init provides a fallback of creating OPMCs.

Additional PMC attributes

pmc->_next_for_GC / opmc->pmc_ext->_next_for_GC

All PMCs that refer to other PMCs have a 3rd mandatory attribute _next_for_GC, used for garbage collection, The presence of this attribute is signaled by the flag bit PObj_is_PMC_EXT_FLAG.

+---------------+
|   vtable      |	
+---------------+
|   flags       |
+---------------+
| _next_for_GC  |
+---------------+
|   ...         |
+---------------+

Properties opmc->pmc_ext->_metadata

PMCs do not support properties universally, If properties are still desired, these can be implemented in one of the following ways:

[[ TODO define something canonical ]]

Per PMC type

Each PMC that wants this extra hash can just provide an attribute for it and implement the property vtable functions.

interpreter->prop_hash

This will be a Hash, indexed by the PMC's address, containing the property Hash. An additional flag can be provided, if such a property hash exists for a PMC. During collection of a PMC, this hash is invalidated too.

PropRef

A transparent Ref PMC can point to a structure holding the original PMC and the property Hash.

Locking or opmc->pmc_ext->_synchronize

PMCs do no support locking universally. Creating sharable PMCs at runtime (from standard PMCs) is again done by transparent Refs like SharedRef or STMRef.

Shared PMCs

If needed, we can define shared PMCs by allocating the _Sync structure in front of the PMC:

+---------------+
|   struct      |	
|   _Sync       |	
+---------------+ <--- pmc points here
|   vtable      |	
+---------------+
|   flags       |
+---------------+

This works of course only, if PMCs are created as shared in the first place. The presence of the _Sync structure is stated by a PMC flag bit.

PMCs and morphing

PMCs (like current OPMCs), which may morph themselves, and thereby change their vtable and the meaning of their attributes shall use a union of the desired attributes, e.g.:

struct Integer_PMC {
  THE_PMC;
  union {
    INTVAL int_val;
    FLOATVAL num_val;
    STRING *str_val;
  } u;
};

ATTACHMENTS

(none)

REFERENCES

TODO pdd02_vtables.pod
TODO pddXX_interfaces.pod
TODO pddXX_classes.pod
TODO pddXX_objects.pod
TODO pddXX_cstruct.pod   [2]

[2] PMCs need a class object that defines their attributes to properly allow subclassing. The attribute definition is held by a CStruct PMC, the meta class of all Parrot PMCs. It's a list of attribute names, their types, and possibly the offsets in the PMC structure. See also the UnManangedStruct PMC.

RATIONAL

Current OPMCs are too rigid: mostly either too small or too big. A lot of information is hanging off secondary malloced structures like PMC_sub in the Sub OPMC.

But more importantly, OPMCs don't properly allow subclassing. E.g.

cl = subclass 'Hash', 'PGE::Match'

is currently done by creating a ParrotClass. When instantiate, this is a "hidden" __value element as first attribute, which is a pointer to the hash parent PMC. This is creating internal structures which aren't compatible, because the object attributes are differently arranged. That is, above subclassing is mainly: PGE::Match hasa Hash instead of isa, when it comes to attribute relationship.

This limitation prevents further implementation of already (at least partially) documented APIs, like the Compiler one.

A Compiler object is either a Parrot Sub like PGE::P6Regex or a builtin that is NCI compiler like PIR. But Sub and NCI objects are that different that even currently needed attributes aren't consistently arranged (e.g. multi_sig or namespace_stash). Creating proper compiler objects like PIR_Compiler with common and needed Compiler attributes isn't possible now.

[[ Well, with another one or two indirections all can be implemented, but that's just adding to code complexity. ]]

Please note that the mentioned Compiler PMC ist just one of many PMCs that exhibit the same problem.

In combination with a proper metaclass for PMCs, PMCs and "real" Parrot objects should be able to work together seemlessly.

PERFORMANCE CONSIDERATIONS

Due to reduced memory consumption and reduced allocation of secondary helper structures, this change will very likely speed up Parrot performance slightly to moderate. No negative performance impact is forseeable due to these changes.

IMPLEMENTATION

Parrot core needs very little changes to be able to deal with differently sized PMCs. All the GC infrastructure is already there for Buffer_like objects, which are managed in sized header pools.

TBD is:

  • Change PObj/PMC structs

    Rearrange PObj/Buffer/PMC struct items in such away that item #1 is vtable, #2 is flags, rest is per PObj. The easierst way to achieve this ist to waste one word and give Buffers and empty vtable.

  • Adjust GC

    Then remove the Buffer/PMC discerns from GC code, just treat them alike, but honor the PObj_is_PMC_FLAG.

This should be all to be able to build and GC PMCs of arbitrary albeit fixed size.

Changing PMCs to the new scheme can be done as needed and isn't mandatory.

OPMCs attribute access is currently already done through C macros, like PMC_int_val or PMC_struct_val. These macros can cast the passed pointer to (OPMC*), so that all these OPMCs will still be working. New PMCs shall use explicit and more verbose attribute names, which don't collide with present OPMC attributes.

There'll be no implications to existing PASM or PIR code nor to existing dynamic PMCs.

ALTERNATIVE PMC IMPLEMENTATIONS

I've in another document (PMC.pod) already layed out a PMC scheme optimized for generational garbage collection. The PMC layout is using also differently but fixed sized user parts of PMCs, but these are subject of one more indirection. If we see the need for optimized GC, this PMC scheme can still be implemented. We probably could take provisions that such a change is not too intrusive by cleverly using the PMC compiler and/or C macros like the proposed ME in [1] above.

The payload of PMCs in this scheme is hanging off a pmc_body pointer:

+---------------+
|   pmc_body    |  --> body (buffer) memory
+---------------+
|   vtable      |
+---------------+
|   flags       |
+---------------+

The implementation of PMC might take into consideration that the PMC layout could change further.