1. General architecture The libprim project consists of 3 language layers: - LEAF: a collection of C objects, written according to conventional object-oriented C style. Each object's first field is a pointer to a class struct containing methods. All leaf objects implement the methods: * free recursively free resources * write serialize to stream * dump write out raw data (i.e. video/audio frames) Leaf objects use manual memory management based on libc malloc() and free(). They can be arranged in a tree structure (one leaf object A containing other leaf objects not referenced outside of A), or a directed graph structure using the refcount object. - EX: a mark-sweep garbage collector for arbitrary graph-structured data + wrappers around the leaf objects. This essentially behaves as a language with Scheme's data model, but C's execution model (evaluation uses the C stack, EX primitive functions are C functions). - SC/PF: scripting language interpreters written on top of the EX data model and function primitives. These provide control semantics that are different from C. The rationale behind the layered design is to make it easier to fall back to a lower level whenever execution or memory performance becomes an issue. I.e. dropping from SC to EX eliminates the language interpreter. Dropping from EX to LEAF eliminates the garbage collector. All drops are at the expense of reduced expressiveness. This architecture seems to work particulary well for multimedia applications, where the basic data types and operations are coded in C, wrapped in the EX memory model and made available to a powerful composition model in SC or PF. At the highest level the inefficiency of the interpreter can be largely ignored if the granulairity of the basic operations isn't too fine (i.e. operations that process entire video frames in one step). 2. Writing leaf objects Have a look at a simple example, i.e. the yuv object. The general principles to keep in mind when designing leaf objects is: Separate constructors from operations: make operations in-place when possible so data can be re-used by upper layers. 3. Writing wrappers Have a look at libprim/sc/media.c for an example of how to wrap leaf objects and operations as Scheme primitives. Essentially, writing a wrapped function consists of: - casting wrapped objects to leaf objects and C primitives - invoking a leaf procedure - checking for errors and propagating exceptions - wrap any constructed leaf objects 4. Glue automation A limited amount of red-tape can be automatically generated. Currently this consists mostly of the EX -> SC/PF function wrapping. Automatic LEAF -> EX data wrapping will follow in the future. 5 PF: bridging tree and graph memory PF implements a tree memory core embedded in a graph memory core, based on these properties: - Tree memory can be restructured without performing allocation. E.g. 'drop' moves a cons cell from the stack to the freelist. - Leaf nodes have a reference-counting (RC) memory manager, and each occurance in the tree counts as one reference. As a consequence, if the graph memory does not have a reference to a leaf node (e.g. for intermediate data), leaf nodes will be freed as soon as possible, and the use of a LIFO freelist (freestack) ensures data locality. Tree operations do not create garbage, so the GC can be switched off. Doing the embedding inside a graph memory is a matter of convenience: it allows code to be represented as graph memory, and allows a program to be split into two phases: - boot: compile code, call GC when done - loop: perform only tree operations (GC switched off) Implementing this as a stack language is a sure way of keeping the language itself simple, while keeping semantics close to funciton evaluation (as opposed to a register machine).