Sunday, July 1, 2012

Rev 2.6 of alpha release 0.5 now available

Revision 2.6 of the 0.5 alpha release of the ParaSail compiler and virtual machine is now available:

  http://bit.ly/LZvZc2

This new release includes a number of enhancements, as well as much more stable support for synchronization via concurrent objects.  The enhancements include: conditional expressions (using either "(if X then Y else Z)" or "X? Y : Z" syntax; initial implementation of compile-time checks for race conditions; container "comprehensions" -- an iterator inside a container aggregate, such as:

  [for I in 1..N => I**2]

to create a table of squares.  See the release notes for all the details.

Friday, June 29, 2012

ParaSail in print

An article entitled "ParaSail: Less is More with Multicore" was published recently on eetimes.com (and its affiliates) Here is a link to a newsletter where the editor (Bernie Cole) highlights the article:

    http://i.cmpnet.com/audiencedevelopment/newsletters/06-21-2012-EmbeddedNL.html

Here is a direct link to the article:

    http://www.eetimes.com/design/embedded/4375616/ParaSail--Less-is-more-with-multicore

In addition to the above, a subset of this blog is also now available online in a somewhat more polished format as a section of the following Ada User Journal issue (covers Sept 2009 to February 2011):

    http://www.ada-europe.org/archive/auj/auj-32-1.pdf

Finally, we participated in the New England Programming Languages and Systems Symposium on June 1 at the University of Southern Maine:

    http://www.nepls.org/Events/26/

Here are the slides we presented:

   https://groups.google.com/group/parasail-programming-language/attach/25ebc77d83d31736/ParaSail_NEPLS_Jun_2012.pps?part=4&authuser=0

Wednesday, March 14, 2012

Identifying "new" vs. "overriding" operations when extending a module

We have recently implemented inheritance of operations and components using the extends keyword in ParaSail.  One of the frequent problems with inheritance is that the programmer might think they are or are not overriding an inherited operation when they explicitly declare an operation, but in fact they are wrong.

Object-oriented languages have addressed the issue of overriding "intent" in various ways.  In C#, a method must have the word "override" if it overrides (as opposed to hides), and may have the word "new" if it doesn't.  In Java 5+, there is an "@Override" annotation.  In Eiffel, "redefine" may be used to indicate overriding.  In Ada 2005, "overriding" or "not overriding" may be indicated.  In C++11, the (non-reserved) keyword "override" may be used to indicate the intent to override.

Our initial design for ParaSail lacked any way to indicate overriding intent, but given the fact that essentially all mainstream object-oriented programming languages now have that ability, it seemed like an oversight.  Therefore, we now allow an interface that extends another to separate the overridings from the new operations.  Here is an example of an Expr interface, and an interface Binary that extends it:

interface Expr<> is
    type Type_Enum is Enum<[#bool, #int, #real, #array, #record];>;

    abstract func Eval(E : Expr) -> Univ_Real;
      // Operation to evaluate expression tree to a univ-real result
    abstract func Display(E : Expr; Indent : Univ_Integer := 0);
      // Operation to display an expression tree in an indented format

    func Init_Type(T : Type_Enum; Count : Univ_Integer) -> Expr;
      // Create an initialized Expr object as needed for
      // creating a class-aggregate in any descendant of Expr.
      // Such a constructor is needed because Expr has hidden
      // components (the "Type" component is hidden).
end interface Expr; 

interface Binary<> extends Expr is
    type Binop is Enum<[#plus, #minus, #times, #divide, #pow]>;

    // Override abstract "Expr" operations
    func Eval(B : Binary) -> Univ_Real;
    func Display(B : Binary; Indent : Univ_Integer := 0);

    // Override inherited constructor, which "becomes" abstract
    // because Binary has its own constructor, and hence might have
    // its own private components.
    func Init_Type(T : Expr::Type_Enum; Count : Univ_Integer) -> Binary; 
  new
    // Define constructor for Binary nodes
    func Create(Op : Binop; Left, Right : Expr+) -> Binary;
end interface Binary;

The reserved word new, if present, separates the declarations which are overriding inherited operations, from the declarations which correspond to new operations.  The compiler will complain if declarations in the first set do not override an inherited operation, or declarations in the second set do override an inherited operation.

One open design question at the moment is whether an operation should be declared before or after new if it implements an operation declared in an interface named in the implements list, but does not override an operation inherited from the parent interface named after extends.  It is not actually overriding anything that was inherited, but it is not really new since it is presumably intended to match an existing operation in some implemented interface. 

My sense is that we will catch more errors by requiring that an operation that comes after new is really new, and does not override an inherited operation nor does it implement an operation of an implemented interface.  And for those who like to think that operations are effectively inherited from implemented (as opposed to extended) interfaces, then distinguishing the two kinds of inheritance (interface inheritance vs. implementation inheritance) might be confusing. 

Note that one issue is that ParaSail allows an interface to implement another parameterless interface without mentioning it explicitly in the implements list.  This provides a kind of ad hoc matching and reduces the number of interfaces that need to be mentioned explicitly.  This kind of ad hoc matching is clearly not considered when checking the proper placement of the new separator.

Friday, March 9, 2012

Rev 2.1 of alpha release 0.5 now available

Revision 2.1 of the alpha 0.5 release of the ParaSail prototype compiler and virtual machine is now available:

http://bit.ly/y06SQW

This includes a new set of modules for supporting Matrix/Vector arithmetic, and more robust support for combining inheritance, type inference, and "generic" operations.  This allows the interfaces Col_Vec and Row_Vec to be extensions of the interface Matrix, with a generic "*" operator that can combine a Matrix and a Col_Vec to produce another Matrix, or a Col_Vec and a Matrix to produce a Row_Vec.  Similarly a Transpose operation can convert a Col_Vec into a Row_Vec, or one kind of Matrix into another.  See the "mat_vec.psi" and "mat_vec.psl" files in the "example" subdirectory for illustrations of this.

Sunday, February 5, 2012

Full Object-Oriented Programming support in rev 2.0 alpha 0.5

We have just made available a new alpha release of the prototype ParaSail compiler and virtual machine, rev 2.0, alpha 0.5:

http://bit.ly/zuSIz1

This new release has full support for inheritance and polymorphism.  In ParaSail terms, this means that you can now use polymorphic types, using the T+ syntax, and you can define one module as an extension of another, using the extends keyword.  (The implements keyword was already supported in earlier releases.)  This release includes a new example, "expr_tree.psl", which illustrates a hierarchy of types defined using module extension and polymorphic components.  As usual, we include executables for Mac, Linux, and Windows.

The prior blog entry discussed the implementation of polymorphic objects.  An upcoming blog entry will discuss the implementation of the inheritance of operations and components.  As in Java, C#, and Ada, ParaSail supports single inheritance of implementation, and multiple inheritance of interfaces.  The multiple inheritance of interfaces was implemented from the very first ParaSail release, but this is the first ParaSail release that supports inheritance of the implementation of operations, and of components.  This involved some additions to the virtual machine instruction set, and significant work in the compiler.

Both polymorphism and inheritance (of implementation) are handled by ParaSail in a somewhat non-traditional way, in part because there are no pointers in ParaSail, and in part because objects are expandable "in place" without the use of pointers.  Polymorphic objects in ParaSail can be assigned new values which are of different types in the hierarchy.  In most object-oriented languages, only pointers or references can be truly polymorphic; the type of a particular object is set when the object is created and cannot change during its lifetime.  See the prior and upcoming blog entries for more details, as well as the reference manual included with the above release in the "doc" subdirectory and the "expr_tree.psl" example in the "examples" subdirectory.

Tuesday, January 24, 2012

Implementing Polymorphic Objects

ParaSail has a somewhat different approach to (run-time) polymorphism than is typical.  There is a separate notation for indicating an object is polymorphic.  If you declare "var X : T" then X is of type T.  If you declare "var X : T+" then X is a polymorphic object, meaning it can hold an object of type T or any type that extends or implements T.  Adding a "+" sign to the end of a type name is used to indicate a polymorphic type rooted at the given type.  For a longer discussion of ParaSail's object-oriented model, see:

http://parasail-programming-language.blogspot.com/2009/09/parasail-extension-inheritance-and.html

We have just completed the initial implementation of polymorphic objects, and there were some interesting trade-offs involved.  First we should recap the implementation of "regular" objects in ParaSail.  Some of this is covered in:

http://parasail-programming-language.blogspot.com/2011/05/when-null-isnt-just-null-value.html

Regular objects come in two basic varieties, small and large.  A small object is represented by a single 64-bit word.  It has no type information at run-time.  The compiler "knows" from context the type of the object.  One bit pattern is reserved to represent null for a given small type.  As mentioned in the above blog entry, we use NaN for a floating point null, and -2**63 for a 64-bit integer null.  Other values can be used for types with fewer values, of course, and one less than the smallest normal value or one greater than the largest normal value would work.

A large object is represented by a 64-bit address, pointing to a sequence of 64-bit words, the first of which is a header, followed by the components.  The header identifies the object's size (in 64-bit words), its structural type-id (primarily an indication of which components are small and which are large), its storage region-id, and a lock-id if the object is a concurrent object.

If a composite type has exactly one component, then rather than creating another level of indirection, we simply adopt the representation for the component as the representation for the composite type.  This means that a composite type whose only component is of a small type is itself represented as a small object.  Hence, there is no extra storage overhead in wrapping an object in another type, just to give it a new interface.  So a user-defined numeric type, for instance, need be no bigger than the underlying component used to represent its value.  We call these wrapper types.  Objects of a wrapper type are indistinguishable at run-time from objects of their component type, and if their component is large, then the type-id in the header of an object of the wrapper type will be the same as the type-id in the header of an object of the component type.  This is one reason we call the type-id in the header of a large object its structural type-id.

A null for a large type was originally represented by the 64-bit address of an object consisting only of a header, with a structural type-id indicating it was a null object, and the storage region-id indicating where to allocate storage for it when it is assigned a non-null value.  We have since switched over to a representation where the 64-bit address itself is recognizable as representing a large null value, with the storage region-id encoded in 16 bits of the 64-bit address representation, avoiding the extra level of indirection, and the need to create null objects when first creating a new region.

So where does that leave us for polymorphic objects?  Polymorphic objects need to carry around an indication of their true run-time type-id, not just structural information.  For example, a polymorphic object that started out as an object of a wrapper type, needs to be distinguishable at run-time from a polymorphic object that started out as an object of the wrapper's component type.  So we choose to make polymorphic objects always use the large representation, meaning they always have a header.  Furthermore, the type-id in this header explicitly identifies this object as being a polymorphic object, with a single "component." The true (run-time) type-id of this single "component" is also identified by the polymorphic type-id.  Furthermore, if this true type implements (rather than extends) the root type of the polymorphic type, an operation map is provided to map the true type's operations to those of the root type.  For a discussion of these operation maps (called slot-number mappings in the blog entry), see:

http://parasail-programming-language.blogspot.com/2010/08/initial-implementation-model-for.html

So the representation of a polymorphic object is essentially a "real" wrapper object, to allow us to specify the true type of the wrapped underlying object.  To some degree this is the price we pay for having small objects that don't carry around any type information, and for allowing wrapper types to be represented identically to their component types.  When we really need a polymorphic object, we need to add an extra level of header, so we can record the true type information.

What can you do with polymorphic objects?  They can be assigned a value of any type that extends or implements the root type of the polymorphic type.  Furthermore, when passed as a parameter to an operation of the root type, the true type-id of the wrapped object is consulted, and that type-id is used to find the actual body to execute for this given operation.

For example, if T has a visible "Print(X : T)" operation, then any type NT that extends or implements T must also have a corresponding Print operation, but taking NT rather than T (i.e. "Print(X : NT)").  If we pass a polymorphic object of type T+ to the Print operation of T, it will dispatch to the appropriate body for Print.  So if the true type-id for the polymorphic object passed identifies NT, then we will execute the "Print(X : NT)" operation.

As part of passing the polymorphic object, the polymorphic "wrapper" is stripped off, so by the time we get to the appropriate operation's body, the representation for the parameter is back to the "normal" representation expected by that operation.  For example, this means that operations designed to take "small" representations for values, will work fine in this context, because the large-object, polymorphic header will have been stripped off, and just the small value will be passed to the operation's body, as expected.

Friday, January 6, 2012

Rev 1.8 of alpha 0.3 release of ParaSail compiler and virtual machine

Rev 1.8 of the 0.3 Alpha release of ParaSail is now available at:

    http://bit.ly/AAWk7Y

This release has initial support for generic operations, where the type of one of the operands is not specified exactly, but merely as being any type that implements a given interface.  For example, to declare a concatenation operator between Univ_String and any type that implements the Imageable interface, you can write:

op "|"(Left : Univ_String; Right : Right_Type is Imageable<>) -> Univ_String;

This operator can be implemented using operations on Right that are defined in the Imageable interface, such as To_String.