Current Status of the Fortress Project

Note: The following content was extracted from the ProjectFortress/README file.

Fortress Interpreter v.0.1 Alpha

This software is an interpreter of a small core of the Fortress Programming Language. At the moment the Fortress interpreter is only partially feature-complete; there's a good deal of functionality missing or only partially implemented. One of the obvious shortcomings is a rather small initial set of libraries. This document explains how to compile and run the interpreter, and describes the language and libraries as it currently stands.

The Fortress Programming Language

The Fortress Programming Language is a general-purpose, statically typed, component-based programming language designed for producing robust high-performance software with high programmability. In many ways, Fortress is intended to be a growable language that can be gracefully extended through its own library system. For more information on Fortress:

The Fortress Language Specification, at:

http://research.sun.com/projects/plrg/fortress.pdf
Errata are listed in FortressLanguageSpecificationErrata

You can find supplementary materials on the Sun Microsystems Programming Languages Research Group at:

http://research.sun.com/projects/plrg/

A Fortress FAQ is available at:

http://research.sun.com/projects/plrg/faq/index.html

but is being replaced by the wikified

http://projectfortress.sun.com/Projects/Community/wiki/FortressQuestions

There is also a discussion@fortress mailing list, which you can join at:

http://fortress.sunsource.net

but we are trying to move this, too.

Building the Code

This software is intended to compile and run on any platform that has working installations of all of the following:

To build this code, run the following command from directory ProjectFortress:

ant clean compile

It is necessary to run the local bash script "./ant" because this script passes command-line arguments to Ant that must be read before Ant starts up a JVM.

Running the Interpreter

Set an environment variable FORTRESS_HOME to refer to the trunk of the Fortress Subversion repository (i.e., the directory containing ProjectFortress). Provided that the 'java' command is on your path, you can now run the command ./fortress hello.fss. If you put the ./fortress script on your path, you should also be able to call fortress from outside the interpreter directory (as you will normally want to do). Typing ./fortress --help will give usage information for the driver.

Or you can invoke the interpreter directly by replacing 'hello.fss' with the name of your program in the following command:

java -cp "build:third_party/xtc/xtc.jar:third_party/FJ/concurrent.jar:third_party/plt/plt.jar" com.sun.fortress.interpreter.drivers.fs hello.fss

and get the usage of the driver by:

java -cp "build:third_party/xtc/xtc.jar:third_party/FJ/concurrent.jar:third_party/plt/plt.jar" com.sun.fortress.interpreter.drivers.fs --help

If all else fails, look at the ./fortress script to see if your system has peculiarities (for example cygwin requires ; separators in the classpath).

Demo Programs

The directory demos/ contains some demonstration Fortress programs:

buffons.fss
Buffon's needle. Estimates pi using a Monte Carlo simulation.
fingerTree.fss
Finger trees. This demo parses but does not currently work.
lutx.fss
Naive dense LU decomposition. Demonstrates how to define new subclasses of Array2.
mm.fss, mm64.fss, mm64x.fss
Matrix multiplication by recursive decomposition. The library routine for matrix multiplication uses a similar cache-oblivious multiplication routine.
sudoku.fss
Solve a simple sudoku by elimination. Includes a tree-based set implementation.
tree.fss
Tree-based wavelet functions, inspired by ORNL Madness library. This was used to prototype our matrix and vector implementations, but that code has been moved to the libraries and this demo doesn't quite work.

Components

Fortress currently lacks a full-blown component system. All the code in your Fortress program will reside in one of two places: in the library file, FortressLibrary.fss, or in the (single) program file you have written.

If you take a look at all the Fortress programs in tests/ and demos/ you'll see that they have the same overall structure:

component MyComponent
exports Executable

...  Your program here ...

run(args:String...):() = ...

end

Your program and the libraries are run in a single common namespace. This means that you will not be able to reuse names which are defined by the libraries (except by overloading). It also means that you can use functionality which is conceptually library-internal. Remember that the libraries you see here are preliminary and certain to change; even the type hierarchy you see defined is different than the one we actually want, due to missing features in the language.

Language Features that are Implemented

  • Object and trait declarations, including polymorphic traits. Polymorphic singletons are not yet accepted. Constructor invocations must always provide the type arguments explicitly.
  • Overloaded functions and ordinary methods. Top-level overloaded functions can be polymorphic. Nested functions and methods must be monomorphic.
  • Polymorphic top-level functions and methods, so long as the methods are not overloaded.
  • Top-level operator functions. Functional methods are not supported yet.
  • Checking and inference of argument types to functions, methods, and operators. These checks use the dynamic types of the arguments. Return types are NOT checked. Inference of type parameters is not complete yet; it is often necessary to provide type arguments explicitly. It is *always* necessary to do so in a constructor call and in any situation where a type parameter occurs only in the result and not in the arguments to a function. For example, you must always provide the array element type E and size n when invoking the factory array1[\E,n\]().
  • Arrays of up to three dimensions. Note that there isn't yet a single overarching Array type. For more details on the array types and operations defined see below. In particular, note that array comprehensions are not yet implemented; the array types provide functions to work around this lack. Another caveat: due to a bug we haven't fully understood, some (but not all) uses of the compact notation T[n,m] for an array type cause the interpreter to fail. Desugaring the code by hand to e.g. Array2[\T,0,n,0,m\] works around this bug.
  • Array aggregates except singleton arrays.
  • Parallel tupling and argument evaluation.
  • Parallel for loops over simple ranges such as 0#n. Only values of type ZZ32Range can currently be used as for loop generators; true generators do not yet exist due to shortcomings in the type system and the absence of loop desugaring.
  • Sequential for loops over simple ranges. The seq() and sequential() functions (which are identical) take a ZZ32Range and return an equivalent sequential ZZ32Range. Every use of the resulting range as a loop generator will cause the loop to run sequentially.
  • While loops, typecase, if, etc. Note that for parametric types typecase isn't nearly as useful as you might think, since it cannot bind type variables; we are working to address this shortcoming.
  • The atomic construct uses code from the DSTM2 transactional memory library. Nested transactions are flattened. We use their obstruction free algorithm with a simple backoff contention manager. Reductions are not yet implemented, so perform an explicit atomic update instead.
  • throw and catch.
  • spawn

Language Features that are Not Implemented

The following features are not yet implemented. See also the FortressLanguageSpecificationErrata

  • Numerals with radix specifiers (which implies that some numerals may be recognized as identifiers)
  • Unicode names
  • Apis
  • Dimensions and units
  • Static arguments: nat (using minus), int, bool, dimension, and unit
  • Modifiers
  • Keyword arguments
  • True type inference
  • Any checking of return types at all
  • Where clauses
  • Coercion
  • Syntax for aggregate expressions such as lists sets maps is not supported.
  • Constraint solving for nat parameters
  • opr parameters
  • Functional methods
  • Overloaded parametric methods. You can have one or the other, but not both.
  • Arithmetic range checks.
  • Generators and reduction variables
  • Comprehensions and BIG operators
  • at and other data placement
  • also (multiple parallel blocks; use tuples of blocks instead.)
  • Any of the types which classify operator properties
  • Non-println I/O
  • Any of the bits and storage types
  • Non-ZZ64 floats
  • Integers other than ZZ32 and ZZ64
  • Use of ZZ64 for indexing
  • Contracts and contract checking: requires, ensures, invariant clauses do not work.

Syntax Changes Since Fortress Language Specification v.1.0 Alpha

  • A file may contain a single component or API. The enclosing component or API declaration may be omitted.
  • At least one export statement is required for a component.
  • Top-level variable declarations and field declarations should have initial-value expressions.
  • A single declaration may declare multiple top-level variables, local variables, or fields depending on the context. Immutable variables cannot be declared using the := token.
  • A default unit for a dimension must be an identifier.
  • No more abstract declarations of dimensions, units, and type aliases
  • Trait headers can have the excludes, comprises, and where clauses in any order.
  • No more empty comprises clauses. A comprises clause of a trait T may include ... to hide some of T's subtraits. A type listed in the comprises clause must be defined within the same component (or possibly an API imported by the component, if there is a cycle in the API import chain).
  • More where-clause constraints and bool static arguments are added.
  • Each contract clause (requires clause, ensures clause, and invariant clause) requires its subclauses or subexpressions to be separated by commas and enclosed by curly braces.
  • Each modifier must not appear multiple times.
  • method declarations occur syntactically after field declarations and getter and setter declarations. Property declarations can be freely commingled with the field and method declarations. A getter or setter modifier should be the last modifier of a method declaration.
  • Local function declarations have the same syntax with top-level function declarations except that local function declarations must not have the modifiers private and test.
  • A second value parameter of a subscripted assignment operator method declaration must contain exactly one non-keyword value parameter.
  • Left-hand-sides of assignment expressions are (possibly multiple of) array indexing, field accesses, the _ token, or identifiers.
  • Object expressions may include property declarations.
  • else clauses of case and typecase expressions have the => token right after else.
  • A typecase expression uses of instead of in after its bindings.
  • A parallel block expression syntax is enriched.
  • The _ token can be used in any place where a binding can occur.
  • An index in left-hand sides of array comprehensions is either an identifier or an integer numeral.
  • Matrix and vector types can be abbreviated using superscripts.
  • Alternative mathematical notations for arrow types are not supported any more.
  • DimRef, unitRef, NatRef, IntRef, and BoolRef are merged with StaticArg.

Built-In Types

There are a bunch of types that are defined internally by the Fortress interpreter. With the exception of Any these cannot be overridden. Most built-in types do not have any methods. The built-in types are:

trait Number extends { Any } excludes { String, Boolean }
trait Integral extends { Number } excludes { String, Boolean, RR64, FloatLiteral }
object ZZ32 extends { Integral } excludes { String, Boolean, RR64, FloatLiteral }
object ZZ32Range extends { Any }
object ZZ64 extends { Integral } excludes { String, Boolean, RR64, FloatLiteral }
object RR64 extends { Number } excludes { String, Boolean }
object String extends { Any } excludes { IntLiteral, FloatLiteral, Boolean }
object IntLiteral extends { ZZ32, ZZ64, RR64 }
object FloatLiteral extends { RR64 }
object Boolean extends { Any }
Tuple and arrow types (that are always built-in)

object FlatStorageMaker[\T, n\]::

built-in flat indexed storage of size n containing objects of type T. This type defines get and put methods, but only checks bounds at the java level. It is not intended for programmer consumption, but is used to bootstrap support for arrays.

trait Any extends {} ::

Note that everything is considered to extend the type Any.
Note also that there isn't (yet) a trait Object! Eventually user-written trait and object declarations will extend Object by default; right now they instead extend Any by default.

The library defines primitive functions on the primitive Numbers::

+ -(unary and binary) *(juxtaposition) DOT = <= ^ MIN MAX |x| > < >= =/= are derived from these.

For integral types::

DIV REM MOD GCD LCM CHOOSE BITAND BITOR BITXOR LSHIFT RSHIFT BITNOT widen for ZZ32 narrow for ZZ64

For ZZ64::

> < >= =/= MIN MAX |\x/| |/x\| truncate sqrt sin cos tanasin acos atan atan2 floor ceiling random |x| plus the constants pi and infinity.

For String::

= =/= < <= > >= juxtaposition means string append, and can include non-string left or right arguments. This is presently the only way to convert numbers to strings for output.

For Boolean (all derived):: AND OR NOT = =/= For output:: print(Any) println(Any)

The Library

FortressLibrary.fss is loaded whenever any Fortress program is run. It contains declarations for every function available to you except for get and put on FlatStorageMaker.

Note that portions of the library code are commented out; these are opened and closed by tear lines (*********** and **********). Much of this is code transcribed from the language specification for prototyping and testing purposes. We intend to make it work one day.

Library Types

Your best guide to library functionality is the library code itself (which is less than 1000 lines including comments). This section provides an overview and describes the most non-trivial functionality.

trait Maybe[\T\] comprises { Nothing[\T\], Just[\T\] }
object Nothing[\T\]() extends { Maybe[\T\] }
object Just[\T\](x:T) extends { Maybe[\T\] }

Note that the type Nothing should actually be a singleton without a type parameter; the absence of where clauses prevents us from writing it monomorphically, and the absence of polymorphic singletons forces us to construct a fresh one.

trait Exception comprises { UncheckedException, CheckedException }
trait UncheckedException extends Exception excludes { CheckedException }
trait CheckedException extends Exception excludes { UncheckedException }

These are stubs for a time when exceptions are implemented.

trait Rank[\ nat n \]

There are separate types Rank1, Rank2, and Rank3 which give appropriate exclusions (since the absence of where clauses prevents us from giving these exclusions directly).

trait Indexed1[\ nat n \] end
trait Indexed2[\ nat n \] end
trait Indexed3[\ nat n \] end

These indicate that an object has an ith dimension of size n.

trait Indexed[\T extends Indexed[\T, E, I\], E, I\]
  opr[i:I] : E
  opr[i:I]:=(v:E) : ()
  assign(v:T):T = fill(fn (i:I):E => v[i])
  fill(f:I->E):T
  fill(v:E):T = fill(fn (i:I):E => v)
  copy():T
  mapReduce[\R\](f:(I,E)->R, j:(R,R)->R, z:R):R
  reduce(j:(E,E)->E, z:E):E = mapReduce[\E\](fn (i:I,e:E)=>e, j, z)
end

This defines most of the core array functionality; due to implementation shortcomings it is not yet fully implemented for the entire array type hierarchy, though the corresponding methods exist for every array type. We read Indexed[\T,E,I\] as objects of type T have elements of type E indexed by type I. This contains indexing operations. It also contains functions which compensate for the absence of array comprehensions and reductions:

fill
fills an array either using a function from index to value, or with a fixed value.
A.reduce(j,z)
is equivalent to BIG j [i <- A.indices] A[i], where j has zero z. But note that j is a function, not an operator.
A.mapReduce(f,j,z)
is equivalent to BIG j [i <- A.indices] f(i,A[i]) with the same caveats as above.

Note that these functions actually use generator-style iteration internally, so it is possible to define new array layouts and experiment with generators by using these functions rather than looping.

trait Array1[\T, nat b0, nat s0\]
    extends { Indexed1 [\s0\], Rank1, Indexed[\Array1[\T,b0,s0\],T,ZZ32\] }
    excludes { Number, String }

1-D arrays. Note the use of nat types for base b0 and size s0. Note also that indices are ZZ32 rather than ZZ64; this is because we're running inside Java, which uses 32-bit array indices. Internal methods (which you shouldn't use) include get, put, and offset. The most interesting methods beyond those in Indexed are:

  subarray[\nat b, nat s, nat o\]():Array1[\T, b, s\]

This returns a structure-sharing subarray with base b and size s starting from offset o in the current array. Structure sharing means updates to one array will be reflected in the other. To avoid the structure sharing just call the copy() method.

  replica[\U\]():Array1[\U,b0,s0\]

This returns a "replica" of the array with a different element type. By "replica" we intend "an array similar in structure to this one, but with a different element type and fresh storage."

Note that Array1 is a trait; its subclasses are unimportant (unless you want to define your own, in which case they are instructive) and they're subject to change anyway.

To create an Array1 you must either write a 1-D aggregate in your program:

z : ZZ32[3] = [1 2 3]

Or you must replicate an existing array:

v : RR64[3] = z.replica[\RR64\]()

Or you must call a factory function:

w : ZZ64[1000] = array1[\ZZ64,1000\]()
x : ZZ64[1000] = array1[\ZZ64,1000\](17)
y : ZZ64[1000] = array1[\ZZ64,1000\](fn i => 2 i + 1)

The special factory function vector is restricted to numeric argument types:

x' : ZZ64[1000] = vector[\ZZ64,1000\](17)

At the moment, any Array1 whose element type extends Number is considered to be a valid vector (this will eventually be accomplished by coercion, and vectors will be a distinct type). The pmul operation is elementwise multiplication; DOT is dot product, as is juxtaposition; DOT, CROSS, or juxtaposition with a scalar is scalar multiplication. ||v|| returns the 2-norm (pythagorean length) of a vector.

trait Array2[\T, nat b0, nat s0, nat b1, nat s1\]
    extends { Indexed1 [\ s0 \], Indexed2 [\ s1 \] , Rank2 (* ,
              Indexed[\Array2[\T,b0,s0,b1,s1\],T, (ZZ32,ZZ32)\] *) }
    excludes { Number, String }

This trait is structured much like Array1, and also provides:

  replica[\U\]():Array2[\U,b0,s0,b1,s1\]
  t():Array2[\T,b1,s1,b0,s0\]

The latter operation is transposition, and should properly be opr ()^T when functional methods exist. Subarray operations aren't defined yet for two-dimensional arrays.

The factories are also similar to the 1-D case:

array2[\T, nat s0, nat s1\]():Array2[\T,0,s0,0,s1\]
array2[\T, nat s0, nat s1\](v:T):Array2[\T,0,s0,0,s1\]
array2[\T, nat s0, nat s1\](f:(ZZ32,ZZ32)->T):Array2[\T,0,s0,0,s1\]

We consider any Array2 whose element type extends Number to be a matrix (again this will eventually use coercion). Matrix arithmetic defines much the same operators as vector arithmetic; all multiplication operators are treated the same way. When both arguments are matrices, this is matrix multiplication. When one argument is a vector, it's matrix/vector or vector/matrix multiplication. When one argument is a scalar, it's scalar multiplication.

Finally Array3 is similar to Array1 and Array2. It does not yet offer factories with arguments, nor subarrays, and we do not treat numeric 3-D arrays specially.

Other Functions

A number of simple functions from the spec are provided:

  • cast[\T\](x:Any):T
  • instanceOf[\T\](x:Any):Boolean
  • ignore(x:Any):() = ()
  • identity[\T\](x:T):T = x
  • tuple[\T\](x:T):T = x

Some Possibly Useful Utility Functions and Classes

These classes aren't strictly intended for external use, but may be handy as guides to how to write recursively-decomposed computations or otherwise get things done in the current version of Fortress:

  • partition(x:ZZ32):(ZZ32,ZZ32)::
    canonically partition positive number n into two pieces (a,b) such that 0 < a <= b, n = a+b.
  • trait ReductionBase[\T\]
  • trait Reduction1[\T, nat s\] extends ReductionBase[\T\]
  • trait Reduction2[\T, nat s0, nat s1\] extends ReductionBase[\T\]
  • trait Reduction3[\R,nat s0,nat s1,nat s2\] extends ReductionBase[\R\]

Reductions over 1-, 2-, and 3-D 0-based index spaces. Used for defining most of the array methods.

Defining New Primitive Functions

It is relatively easy to add new primitive functions to Fortress. To do this, you simply invoke the builtinPrimitive function with the name of a loadable Java class which extends glue.NativeApp. Useful subclasses are NativeFn1 and NativeFn2, and any of the classes in glue.prim (particularly the classes in glue.prim.Util). Here's a sample native binding, which defines the floor operator which returns an integer:

opr |\a:RR64/|:ZZ64 = builtinPrimitive("glue.prim.Float$IFloor")

You should not mention the type parameter to builtinPrimitive when invoking it; doing so will confuse the interpreter. Note also that the interpreter requires that you declare appropriate argument and return types for your native functions as shown above. If you give an incorrect type declaration on the Fortress side, you'll get non-user-friendly error messages when the Java code is run.