Monday, August 16, 2010

Tart status update

I've been busy this last week re-doing Tart's reflection data structures. Unlike the previous scheme, which used fully-realized, statically-compiled Tart objects to represent the various types, method descriptors, and other metadata needed for reflection, the new scheme uses a highly-compressed byte-encoding of the data. The compressed data is converted into objects lazily on first access.

By the way, all of this lazy evaluation makes heavy use of Tart's 'lazyEval' macro, which is part of the core library:

    return lazyEval(<variable>, <initialization-expression>);

Where 'initialization-expression' has type T, and 'variable' is declared as having type 'optional T'. Basically, lazyEval checks to see if 'variable' is initialized, and if not, it initializes it using 'initialization-expression'. The initialization expression is not evaluated if the variable is already initialized.

At some point I'll need to also do a 'lockedLazyEval' which is a synchronized version. But before that can happen I'll need mutexes (mutices?) to be implemened. That's going to be a bit tricky, for a number of reasons. For example, Tart objects can move around in memory, but I'm not sure whether or not it's legal to relocate a pthread_mutex_t. I suspect it isn't. Also, it gets into the whole realm of writing platform-dependent code in Tart (as opposed to platform-dependent code in the C runtime library, which is no problem), which I haven't really worked out yet.

Anyway, lazy evaluation is one of those common code patterns you see everywhere, but it's hard to refactor into a library routine unless your language gives you some way to selectively control evaluation. Macros are one way to do it - closures are another, although they are a bit more wordy.

Another aspect of reflection I am working on at the moment is the "invocation adapters". These are wrapper functions used when calling a method via reflection - it converts the argument list from type Object[] to the native types of the called method's argument list. Each distinct function type requires a distinct invocation adapter - in other words, if two functions take the same argument types, then they can share the adapter function. (Note that the invocation adapter does not handle the 'self' argument - that is handled by a prior stage in the calling process - so two instances methods can share the same adapter, even if they are members of different classes).

When the compiler generates the reflection metadata for a class, it creates a set of all of the unique function types defined by that class. Each of these is assigned an index. Each method definition in the class contains the index corresponding to it's function type, which is encoded using a VarInt, which typically takes one byte. The class metadata also contains a table of pointers to invocation adapter functions, which is also referenced by index. This information is used to lazily construct a method descriptor object, which contains a pointer to both the method itself and the invocation adapter for that method.

In other news, I implemented the Python-style String.join method: So now you can say either ", ".join(list) or String.join(", ", list). The 'list' argument must either be an Iterable[String], or a variable number of String arguments.

No comments:

Post a Comment