Skip to content

Instantly share code, notes, and snippets.

@skids
Created June 27, 2009 04:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save skids/136886 to your computer and use it in GitHub Desktop.
Save skids/136886 to your computer and use it in GitHub Desktop.

NAME

Base - SMOP basic structures

REVISION

$Id: 01_base.pod 25888 2009-03-18 18:14:09Z ruoso $

SMOP__Object

In SMOP, every single value must be binary-compatible with the SMOP__Object struct. This includes even core level constructs like the interpreter and the native types. This idea comes directly from how perl5 works, with the SV struct.

Unlike p5, however, the SMOP__Object struct is absolutely minimalist; It defines no flags and no introspection information. It defines only that every SMOP__Object has a Responder Interface, so the structure is merely:

struct SMOP__Object {
  SMOP__ResponderInterface* RI;
  /* Maybe there is something here, maybe there is nothing here.
   * Only the responder interface knows.
   */
}

This means that you can't really do anything to the object yourself, you can only talk to its responder interface. The object serves as both a way to find the correct responder interface, and a way to tell the responder interface which instance data to operate on -- and that is all.

For all but singleton classes, one responder interface will be used by multiple object structs.

There may be additional data below the RI member, but if so, only the responder interface knows how to use it. The data for the object instance may, in fact, NOT be stored in the structure at all -- it could be looked up using the object's address in a completely separate data store.

As such it is incorrect to attempt to copy a SMOP_Object struct using a simple memory copy like C's memcpy().

SMOP__ResponderInterface

The Responder Interface (which, of course, is also binary-compatible with SMOP__Object) implements the low-level part of the meta object protocol. It's through the Responder Interface that you can perform any action on the object.

Using the responder interface, arbitrary methods may be invoked on the object. It's important to realize that this method invocation happens at the same level that any high-level language might call. This means that there's no distinction between native operators and high-level operators, nor between native values and high-level values.

The structure of a responder interface is as follows.

struct SMOP__ResponderInterface {
  SMOP__ResponderInterface* RI;
  SMOP__Object* (*MESSAGE)  (SMOP__Object* interpreter,
                             SMOP__ResponderInterface* self,
                             SMOP__Object* identifier,
                             SMOP__Object* capture);
  SMOP__Object* (*REFERENCE)(SMOP__Object* interpreter,
                             SMOP__ResponderInterface* self,
                             SMOP__Object* object);
  SMOP__Object* (*RELEASE)  (SMOP__Object* interpreter,
                             SMOP__ResponderInterface* self,
                             SMOP__Object* object);
  SMOP__Object* (*WEAKREF)  (SMOP__Object* interpreter,
                             SMOP__ResponderInterface* self,
                             SMOP__Object* object);
  char* id;
  /* Maybe there is something here, maybe there is nothing here.
   * Only the responder interface in member .RI knows.
   */
}
MESSAGE

This is the function that handles the method invocation for the objects which this responder interface oversees. As you might have noticed, it receives objects as arguments and returns, of course, an object.

The specific objects interpreter, identifier and capture will be described later.

REFERENCE/RELEASE

Initially, the a reference counting garbage collector was selected, since this type of garbage collector is considerably simpler to implement (even if considerably harder to debug and maintain.) However, when design goals expanded to include interoperability with perl5, it became evident that following reference counting conventions would be a necessity in making SMOP and perl5 work together.

One thing that might not be obvious here is that it's up to each responder interface to implement its own garbage collector, meaning that we can have several garbage collectors coexisting at the same process, for instance, the SMOP default low-level and the perl5 garbage collectors.

The other consequence of this choice is that you can have objects that are not subject to garbage collection, and this can be achieved simply by making REFERENCE and RELEASE no-ops.

The policy on the use of REFERENCE and RELEASE is described below.

WEAKREF

This can be used wherever you would normally use a REFERENCE to tell that you want a weak reference. This call is allowed to return you a different object, and you are supposed to use that as a proxy. The implementation of the weak-reference is private to the object's gc implementation.

Macros

The SMOP base defines a few macros that should be used when interacting with SMOP Objects. While in theory, the use of those macros is optional, it's strongly advised that you stick with them, to make transitions to newer versions easier.

SMOP__Object__BASE

This macro defines the head of every SMOP Object, basically defining the members documented in the section above. Currently that is just the RI member, but should members be added, they will appear in this list.

SMOP__ResponderInterface__BASE

Like the above macro, except that this defines the members present in all responder interface objects, as documented above.

SMOP_RI(value)

Shorthand to dereference the .RI member of a SMOP Object given the address of the SMOP__Object structure.

SMOP_DISPATCH(interpreter, responder, identifier, capture)

Invoke the method "identifier" on an object handled by "responder", in the context of the given "interpreter" with the given "capture." Each of those concepts should be expanded later, but if you are wondering where the object itself is, it is inside the capture.

SMOP_REFERENCE(interpreter, value) / SMOP_RELEASE(interpreter, value)

Increments/decrements the reference count of the given "value" in the context of the given "interpreter". This macro returns the value itself, so you can use the macro in the place of the value itself.

SMOP_WEAKREF(interpreter, value)

Returns a proxy for a value...

REFERENCE/RELEASE Policy

Even if not all object systems use reference counting garbage collection, all objects at least pretend to implement the mechanisms that make it possible. That is why, besides MESSAGE, three other functions are defined in the responder interface base members. (Relatively few objects should be responder interfaces, so it is more efficient for them just to carry the vestigial members.)

The REFERENCE, RELEASE and WEAKREF functions should be sufficient to interact with reference counting garbage collectors.

Who owns the object?

This is the most important question in the matter of when to call REFERENCE and when to call RELEASE. And a simple policy shall describe this decision:

  • When an object is created, it becomes owned by the code who called the method that created it.

  • Installing an object in a capture implies transferring the ownership of the object to the capture object.

  • When a capture is passed to a MESSAGE as the capture parameter, ownership of all objects installed in the capture is transferred to the code receiving the capture.

  • A call to REFERENCE defines an additional owner to the object. Since it is so easy to give away ownership, REFERENCE is an important tool for keeping objects alive.

  • A call to RELEASE implies that this owner no longer wants the object.

  • Using an object as the intepreter, self, or identifier parameters of a MESSAGE does not transfer the ownership.

  • WEAKREF is used to return a weak reference to that object, it returns a different pointer, owned by the code that called it. Calling WEAKREF doesn't change the refcount.

IMPORTANT SPEC NOTICE

This document describes everything that you can assume on an arbitrary object, which means that you can only introspect in more detail by either calling a method, or via special knowlege of the internals of the responder interface of the given object (for example, inside the code of the responder interface itself.)

It is erroneous to assume anything about the internal structure of any object, even responder interface objects, beyond what is described in this document.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment