Prexonite CIL Functions

Save the "What the f…" for later and just look at the two snippets below.

ldloc.1ldc.i4.5addstloc.1

Listing 1: a = a + 5 in CIL assembler

ldloci  1ldc.int 5addstloci  1

Listing 2: a = a + 5 in Prexonite assembler

On the left you see four CIL assembler op codes, while the other snippet represents the exact same program, just written in Prexonite byte code assembler. The fact that the two programs look so similar is no coincidence as the Prexonite virtual machine was actually modelled after the CIL’s execution model. This exact similarity can be exploited to make Prexonite a lot faster.

A Prexonite to CIL compiler

Now before you get too excited, Prexonite Script still is what they call a “Dynamic Language” and a lot of its features are implemented in the underlying Prexonite virtual machine instead of the language compiler. Also, Prexonite byte code is not statically typed, which makes a straight translation to CIL impossible without very sophisticated data flow analysis and complete type inference. As I am not familiar with either of these topics, I decided to keep the Prexonite functions untyped. This is where the PValue class comes into play. It encapsulates a dynamically typed piece of data and provides many methods to interact with the contained data via late binding.

In all cases, an implementation of a Prexonite function in CIL must show the exact same behaviour as the original, interpreted implementation. Functions that interact with Prexonite stack frames cannot be compiled to CIL as they are no longer executed on the virtual machine’s stack but the CLR’s instead. Therefore, CIL implementations must be able to exist alongside interpreted implementations and that as transparently as possible. Also, since the Prexonite virtual machine allows for code generation and manipulation at runtime, CIL implementations must be replaceable. This unfortunately also means that function calls inside CIL implementations cannot be statically linked as the target function might change the implementation strategy (interpreted, CIL) every moment.

How it’s done

Since the Prexonite to CIL compiler operates on Prexonite byte code, it would not make much sense to use the C# or VB CodeDOM and the corresponding compiler. Instead System.Reflection.Emit provides the necessary API. Since implementations must be replaceable, dynamic types are not an option and the so called lightweight functions are used.

The compiler is designed to operate at runtime, invoked by the running program itself. This is, because it analyses the whole application to identify functions that are not compatible with compilation to CIL. Such functions are marked with the Meta entry volatile.

The compilation process itself is actually quite straight forward. First the function is analysed in order to determine the number of temporary variables required, to build up a symbol table and to identify shared (via closures) and non-shared variables. Then the common function header is emitted including the creation of PVariable objects for shared variables and the initialisation of non-shared variables with PType.Null.
Then, the variables representing arguments are initialised with either PType.Null or the value supplied in the arguments array and finally the special variable args is set to a list of those same arguments if required by the function.

What follows is a huge loop that iterates over every instruction in the functions code and passes it into a giant switch statement, which translates every Prexonite byte code instruction into a series of CIL op codes.

Therefore, the CIL implementation of the program in Listing 2 will look like in the pseudo CIL in Listing 3.

As you can see, an untyped implementation of this simple program expands into quite some code. Notice that due to the absence of a rotation op code, the implementation requires temporary variables to insert the local stack context in the call to Addition.

ldloc var1ldc.i4.5box int32call IntPType PType::get_Int()newobj instance void PValue::.ctor(object, PType)stloc temp1ldloc sctxldloc temp1call instance class PValue PValue::Addition(StackContext, PValue)stloc var1

Listing 3: Actual CIL implementation of the program in Listing 2
Note: I have shortened the fully qualified type names for better readability.

Is it worth the effort?

As with all optimization techniques, we must ask ourselves whether the effort for implementing it is worth the gain in performance (be it memory or speed). At this point, let me just throw the results of an amateurish micro benchmark at you.

CIl_micro_benchmark

One can clearly see that CIL implementations are superior. They perform the same tasks in 60% (empty_loop) to 30% (rec_echo x 100) of the time required by the interpreted versions. Since the CIL compiler performs many of the Meta data lookups required for the creation of a stack frame at compile time, function calls to CIL implementations are much faster. Keep in mind though that only interpreted functions can take advantage of tail calls. To prevent an overflow of the managed stack, you should implement infinite recursive loops in interpreted functions.

Overall, you could say that compilation to CIL will result in a free performance improvement of over 65 percent in most cases.

function rec_echo(n) =if(n == 0)else1 + rec_echo(n-1);
function rec_echo_direct(n,r) =if(n == 0)relserec_echo(n-1,(r??0)+1);

A functional touch

The last days, I've been working on two things: The reorganization of built-in commands and the improvement of the "Functional Experience".Why do commands need reordering? Because it gets difficult to find the right file among over 40 commands.Why the sudden increase in numbers? I added proxies for System.Math methods for both easy and fast access to mathematical functions such as Sqrt and Sin, but also Pi.Additionally, the most important coroutines from the Prexonite Standard Repository for list processing have been implemented in managed code, again for performance reasons. Map, Where, Limit, Skip and friends now inject managed coroutines into the stack.The commands are now organized in the namespaces Core, List, Math and Text. The latter currently contains the fixed layout functions SetCenter, SetLeft and SetRight, which fill a given string with some character sequence until it has a certain length and is aligned correctly.Now what the hell do you mean by "Functional Experience"?I haven't told anyone but the Prexonite VM is absolutely terrible when it comes to recursion. Unfortunately, recursion happens to be one of the key elements in functional programming and, as you might have noticed, Prexonite Script comes with a lot of syntactic sugar that makes it look like a functional programming language.Ok, lambda expressions and closures are "true" functional features but the lack of a sophisticated type system makes it almost impossible to reason about a program in the way functional compilers do. Nonetheless, I added two features with the last commit, that make PXS a tiny little bit more functional.

First of all: Tail Call Optimization
Yes, the thing that helps with recursion.
function fac(n,r) =
    if(n == 1)
        r
    else
        fac(n-1,n*r);

I benchmarked this function three times, with different tail call optimization strategies. The difference is huge. See for yourself (10'000 computations of 16!):Comparison of different tail call optimization strategies.Two strategies are employed: An implementation of tail call optimization for directly recursive functions inside the compiler, that turns recursive calls into direct iterations (jumps to the beginning of the function with different arguments). What I call "virtual machine optimization" is a special tail call instruction that removes the current stack frame after having called the function or closure.Now apparently the virtual machine "optimization" is not particularly fast but uses far less memory than the normal invocation.Prexonite will never be able to recognize indirect recursion due to the lack of control flow analysis. This, however, does not mean that return statements inside conditions or calls in tail position are not recognized. I'm not sure if Prexonite will ever handle simple recursive return expressions like the normal definition of the factorial:
function fac n =
    if (n==1)
        1
    else
        n*fac(n-1);

Also in the repository is an experimental and partial implementation of the famous call-with-current-continuation from Scheme. In PXS it is known as callcc.I must admit that I don't really know much about call/cc and how it works, especially regarding the stack. Creating a callable object from the current state of a function invocation is no problem. I just don't understand some of the scheme samples, I've been looking at (terribly difficult to read...)The following snippet stores a continuation of the function two in the global variable plusone. Invoking this continuation with, say, 6 returns 7 as the name suggests.
var plusone;

function two =
    1 + call\cc(->one);
       
function one(continuation)
{
    plusone = continuation;
    return 1;
}

The Philosophy Behind: The Prexonite Type System

This is the second article in the "Philosophy Behind"-series, picking up a specialty of one of my projects and explaining how it came to be made. Last time I wrote about the "auto dereferencing" concept in Prexonite Script.In today's article I will explain the reasons behind the design of the Prexonite type system.Prexonite faces the same problem as other implementations of late-bound languages on the .NET platform: How to map the CTS to the languages type system.Prexonite_TypeSystemI think the basic types Int32, Double, Boolean and String are more suited for a statically typed environment, so my type system must allow me to provide wrappers around third-party classes/structs.Wrapping and unwrapping objects must be as transparent as possible. Return values from base class library methods have to be wrapped in their Prexonite equivalent.At the same time, it is not practical to write a custom wrapper for every possible C# or VB.NET library, so there must be some sort of universal wrapper for CLR objects. With users of Prexonite being able to write their own wrappers, it must be possible to have multiple wrappers for the same CTS type. Also, some wrappers might handle more than one type.The solution for Prexonite is the abstract class PType and some concrete subclasses, including the universal ObjectPType, which does all the late binding. Since Prexonite Script performs type checks at runtime, type information has to be associated with every data object, which is just what the class PValue does.What might surprise you, is the fact that Null is considered a type. Every null reference automatically has type Null. Unlike the sturdy null references in C#, instances of Prexonite Null are completely functional objects. They react to operators, can be converted to basic values (Int, String,...) and even provide a ToString method. However, Null does have a special position in the Prexonite type system: it is not possible to write and use your own null reference wrapper.

Prexonite standard repository finally released.

I just checked the collection helper functions I call Prexonite standard repository (psr) into SVN. As I am too lazy to create a full release, I will just supply you with a trac generated zip file.

Following is a short documentation (or rather an overview) of psr:

The Prexonite Standard Library is a collection of scripts that help in day-to-day hacking with Prexonite Script. This page shortly outlines the contents of each of the currently available files.

debug.pxs

Dependencies
none

The script enables special treating of the debug command using compiler hooks for increased performance. For each call to the debug command, it checks whether the function requests debugging (through the debugging MetaKey). Unless that is the case, the call will be removed. if-Blocks using debug as their condition will be evaluated at compile time in respect to the debugging key.

It is possible to use the debug command without including this script, in that case, however, your scripts will also contain calls to debug when not being debugged.

The actual functionality of this script has been moved to managed code inside the Prexonite.dll for performance reasons in #18. CompilerHooks have to be used with care. While the loss in compiler performance is barely noticeable with just one user defined CompilerHook, many of them can really slow the translation down. The managed implementation uses a shared CompilerHook to further save time, should Prexonite.dll ever include additional CompilerHooks

[Read more →]

Prexonite October07 Edition

I doubt anyone has noticed it, but yesterday I published a new version of Prexonite. It sort-of replaces the fourth beta preview I had planned to ship earlier.First, I have to disappoint you: There is not going to be anything like tab-completion in the near feature. Prexonite fully supports Unicode and I couldn't find a library that supports non-ASCII character encoding. Although Prx.exe currently contains an experimental tab-completion solution, I refuse to enable it by default as long as it ignored my umlauts.Yet another disappointment is, that Prexonie October07 does not yet contain the collection of scripts I plan to include in the Prexonite Standard Repository, but I promise you, to release them in the next edition.

"Did you actually change anything?"

... you might be wondering. In addition to the features announced in the last post, I have added/changed the following:Improved local variable accessBy addressing local variables using an index instead of looking up a name in a dictionary, the time required to access a local variable has measurably decreased. Specialized variants for all instructions dealing with local variables have been added. This is necessary because global code cannot take advantage of this feature.Also note that this optimization happens after the code has been emitted, so even assembler code takes advantage of the new access method.Experimental 'data flow' operatorWhen chaining functions that manipulate lists, I often found myself tediously counting brackets as the expressions got more and more sophisticated. Brackets don't scale very well, so a different way of chaining functions had to be found. While an actual 'chain' operator, like those found in many functional programming languages, might have been the solution, I went a different way.I wanted pipe the result of one function into another function like it is done in shell scripts. Instead of overloading the '|' operator though, I went with '>>' and '<<'.

function main =
    ::Console.ReadLine~Int>> [1,5,7,1,5].Insert(3)>>
        map(x => 2*x)>> where(x => x mod 3 == 0)>>
        foldl((l,r)=>l+r,"")>> println("[ ") <<"] ";

This sample reads an integer from stdin, inserts it after the third element in the list, which is then restricted to numbers divisible by three. The result is then concatenated and printed.Although the compiler allows multiple arguments to be added in the way, functions still can only return one value. It might look like tuples are used but that's just a facade for plain vanilla arguments.Misc.Prexonite already contains some extensions, that were previously part of the Standard Repository. Most notably the debug command and a set of benchmarking classes.Also: Tomorrow I will hand in a paper I have been writing on for the past few months. 'Creating a programming language' is a short overview over the concepts of the implementation of a programming language, from its design over the compiler to the runtime environment. As I cannot just print out the Prexonite source code (about 460 pages), the paper will point to the October07 release available from SealedSun.ch.If you think, the new additions are worth an update, goGet Prx

New features in the 4th beta preview

Hey, it's been a while but I have added a number of neat features to Prexonite and especially to Prexonite Script.Conditional expressions

function max(a,b) =
    if(a> b)
        a
    else
        b;

You can also use the 'traditional' {cond} ? {expr} : {expr} syntax but it won't be as easy to read.Loop expressions
function main()
{
    var xs = for(i = 0; i <100; i++)
                yield i;
    ;
    var ys = foreach(var x in xs)
                if(x mod 2 == 0)
                    yield 2*x;
    ;
}

Loops can be used as expressions. Their 'value' is a list of all values 'returned' via the yield keyword. Although it may look like a coroutine, in reality a Prexonite list is returned and not a coroutine reference. [Read more →]

Beta Preview 3 available

Hi,I finally found time to polish and wrap a copy of Prexonite in the two usual *.zip files. This third beta preview contains a lot of bugfixes and also marks the transition from the private SVN repository at unfuddle.com to the public one at assembla.com.I will use a trac site to keep track of changes made to the Prexonite SVN repository (located at http://tools.assembla.com/svn/prx).Downloads

There still s no ReadMe.txt but I have included the partial documentation as a *.chm help file.If you have any questions, please ask through the comments on this site.

Finally trying hard to catch exceptions

Prexonite now comes with exception handling in the form of try-catch-finally blocks. Their implementation is a bit different from the ones in C#.

Add System::IO to Imports;

function main
{
    try
    {
        var sw = new StreamWriter("foo.txt");
        sw.WriteLine("It worked!");
    }
    catch(var exc)
    {
        println(exc);
    }
    finally
    {
        dispose(sw);
    }
}

[Read more →]

Coroutines in Prexonite

Like the title reads: The Prexonite Scripting language and the virtual machine now support coroutines, routines with multiple entry points. They can be used to implement iterators, generators and infinite lists.

coroutine map(ref f, xs)
{
    foreach(var x in xs)
        yield f(x);
}

[Read more →]

Public Prexonite Beta Preview

Here it comes, the first public distribution of Prexonite. I call it a "beta preview" as it is no longer in alpha state (all major features are working; at least all unit tests pass.) but there is little to no documentation. I'm still working on both the API and the language reference.The preview consists of two *.zip archives: The sources (including unit tests and all dependencies) and pre-compiled binaries of the runtime (Prexonite.dll) and the standalone host (Prx.exe).

Ah, and by "little to no documentation" I mean partial (20%) XML documentation. Heck I don't even have a readme.txt. If you have any questions, please ask (through comments or using the e-mail address in the LICENSE.txt file) me.