Monday, August 06, 2007

"er" words

Take a look at the new implementation of subset:
: pusher ( quot -- quot accum )
V{ } clone [ [ push-if ] 2curry ] keep ; inline

: subset ( seq quot -- subseq )
over >r pusher >r each r> r> like ; inline

The idea of having a pusher word was originally suggested by Eduardo Cavazos. This word takes a predicate quotation and yields two values, the first being a quotation taking an object as input, and the second being a vector. If you call the quotation with an object for which the predicate yields true, the object is pushed on the vector. The generated quotation "closes over" the vector. Now the only thing subset has to do is pass the pusher quotation to each, and hide the pusher vector on the retain stack until after the iteration is done. When iteration has completed, the pusher vector contains all elements which passed the predicate.

I think there is a very interesting idiom here, and it is worth exploring this idea more.

Also, note that while from the outside, subset is referentially transparent, it uses mutable state internally. While it may be possible to write something just as simple without mutation, I think in many cases, being able to mutate considerably simplifies code; certain algorithms are just more naturally expressed with mutation, at least in a stack language, and integrating abstractions such as monads or uniqueness types into a stack language is still an open research problem (but Christopher Diggins is working on it...) There is something to be said for allowing different programming styles in a language, instead of forcing a single style on the programmer (as in Haskell and other purely functional languages, or even Smalltalk's "everything-is-message-passing-OOP".)

Efficient partial function application (aka compiled curry)

Note: the code described here is not in darcs yet, but will make it there within the new few days after I fix a few residual bugs.

Ever since quotations became a first-class data type, Factor has had a curry word:
  3 [ + ] curry .
[ 3 + ]

This is essentially a partial function application. (I'm aware that currying and function application is not the same, but this word was named before I was aware of that fact, and anyway, curry reads better than papply or partial.)

Previously, this was an expensive word to use. Not only did it allocate a new quotation and copy the existing quotation's elements, but words which called quotations produced by curry did not compile, and had to run in the interpreter; this is due to the compiler's design, which "lifts" all quotations up to their call site.

Now, this has all changed. For interpreted code, curry now runs in constant time, not linear time proportional to the quotation's length, because it allocates a small object holding the object together with the quotation. These objects are instances of a curry class, but they print like quotations and are equal to quotations having the same elements, so they're essentially identical:
3 [ + ] curry [ 3 + ] = .
t

When a curried quotation is called by call in the interpreter, the VM simply pushes the curried object first, then calls the quotation.

In compiled code, we can do even better now. The compiler knows about curry and applies a rewrite rule to the dataflow graph. Essentially, it replaces all occurrences of curry's output value with a pair of values, being the object and quotation; it also "widens" all stack shuffling from the point where the curry is created to where it is called. If the curry is passed to an ordinary word, the compiler inserts a real call to curry which allocates memory; however, the key idea behind this rewrite rule is that curry instantiation is "lazy", and is not done at all if the curry is simply passed downward to a combinator.

For example, consider this example:
: foo ( x y -- x' y' ) 3 [ + ] curry 2apply ;

This word adds 3 to the top two stack elements. The compiler first inlines the 2apply word:
: foo ( x y -- x' y' ) 3 [ + ] curry tuck >r >r call r> r> call ;

Now it applies the rewrite rule:
: foo ( x y -- x' y' ) 3 [ + ] abc--bcabc >r >r >r call r> r> r> call ;

The compiler represents stack shuffles in symbolic form, internally; I'm writing abc--bcabc to mean a shuffle with that effect, for which no word exists in the library.

Now, it lifts the quotation up to the call site:
: foo ( x y -- x' y' ) 3 [ + ] abc--bcabc >r >r >r drop + r> r> r> drop + ;

Finally, it removes the literal value [ + ], since it is now dead; it travels around the stack without ever being used for anything, and is dropped:
: foo ( x y -- x' y' ) 3 ab--bab >r >r + r> r> + ;

So in this case, the compiler will produce the exact same code for the following two snippets:
3 [ + ] curry 2apply
3 tuck >r >r + r> r> +

While [ + ] curry 2apply is not a useful thing to write, since the direct expansion is simpler, it does demonstrate how the compiler is able to remove the object allocation from this code.

So, curry is efficient now, both in the interpreter and compiler. But what does this mean?

The role played by curry here is somewhat like a Lisp lambda which closes over one free variable. We can use curry to package up a bunch of values, pass them to the combinator, and fiddle with them from inside a quotation. Until now, you either had to pay a performance penalty for using curry, or you would use stack juggling tricks. For example, consider the each and 2each combinators. Here is the current implementation:
: (each) ( seq quot i -- seq quot i )
[ rot nth-unsafe swap call ] 3keep ; inline

: each ( seq quot -- )
over length [ (each) ] repeat 2drop ; inline

: (2each) ( quot seq seq i -- quot seq seq i )
[ 2nth-unsafe rot dup slip ] 3keep ; inline

: 2each ( seq1 seq2 quot -- )
-rot 2dup min-length [ (2each) ] repeat 3drop ; inline

Here is an implementation using curry:
: (each) ( seq quot -- n quot' )
>r [ length ] keep r>
[ >r nth-unsafe r> call ] 2curry ; inline

: each ( seq quot -- )
(each) each-integer ; inline

: (2each) ( seq1 seq2 quot -- n quot' )
>r [ min-length ] 2keep r>
[ >r 2nth-unsafe r> call ] 3curry ; inline

: 2each ( seq1 seq2 quot -- )
(2each) each-integer ; inline

Note that each-integer is the new version of repeat, with a slightly altered stack effect.

In the old code, the quotation passed to repeat has to manually save and restore various values that it needs; now that curry is efficient, we just package those values up with the quotation before passing it along to each-integer.

It is somewhat ironic that while Factor is as "concatenative" language, until now concatenating quotations together was discouraged, at least where performance was important! But now, we can easily write an efficient compose word which takes a pair of quotations:
: compose [ >r call r> call ] 2curry ; inline

For example,
[ 2 2 ] [ + ] compose .
[ [ 2 2 ] [ + ] >r call r> call ]

It is pretty clear that this quotation has the same effect when called as [ 2 2 + ]. The difference between compose, which only works on quotations and produces a funny result, and append, which works on all sequences, is that since compose is built from curry, it runs in O(1) time, and the compiler is able to eliminate its runtime cost altogether.

For example, consider the assoc-each combinator. It is implemented in terms of the assoc-find combinator, since assoc-find is the only primitive combinator each assoc instance must implement. On each iteration, we want to call the quotation, but always output f, forcing assoc-find to continue the iteration until the end. Formerly, assoc-each was implemented as follows. First, we had a utility word, assoc-find-with, which called assoc-find while retaining a parameter on the stack. It was in implemented using a assoc-with word which was also used for assoc-each-with, and so on:
: assoc-with 2swap [ >r -rot r> call ] 2keep ; inline

: assoc-find-with ( obj assoc quot -- key value ? )
swap [ assoc-with rot ] assoc-find
>r >r 2nip r> r> ; inline

: assoc-each ( assoc quot -- )
swap [ rot call f ] assoc-find-with 3drop ; inline

This is ugly as hell, and essentially boilerplate. Here is the new way:
: assoc-each ( assoc quot -- )
[ f ] compose assoc-find 3drop ; inline

That's it; no need to define -with words, and no unnecessary stack shuffling. We exploit the "concatenative" nature of Factor here -- composition of sequences is the same as composition of functions!

Which brings me to the next topic; all the "manually curried" combinators you know and love, such as each-with, map-with, and so forth, have been moved to a deprecated vocabulary. They're going away soon. Updating code is easy.
swap [ swap XYZ ] each-with  ===  [ XYZ ] curry each
[ XYZ ] each-with === [ XYZ ] curry* each

Same for map-with, subset-with, and so on. The curry* word is just partial application on the second stack element; it is defined in terms of curry.

All the sequence and assoc combinators have been greatly simplified. In fact, I was able to move the 2swap word out of the core and into extra/shuffle; stack shuffling has been simplified by curry to such an extent that in particular, 2swap is simply not needed anymore. Other more complex shuffle words such as roll and pick are used less frequently now, as well.

By lowering the cost of an abstraction, I was able to simplify code by a non-trivial amount. There's a general theme here: the goal of a compiler is to make idiomatic code run fast. It is not good enough to only accelerate hand-unrolled, tuned code chock-full of irrelevant detail and declarations. Nobody wants to write unnecessarily complex code just to please the compiler.

Wednesday, July 25, 2007

Smoother stand-alone app deployment

I polished the deployment tool a bit. Now all it takes is a vocab name:
USE: deploy.app
"lsys.ui" deploy.app

Each vocabulary has a deployment configuration. The configuration can be inspected and set with deploy-config and set-deploy-config:
( scratchpad ) "lsys.ui" deploy-config .
V{
{ strip-word-props? t }
{ strip-word-names? t }
{ strip-dictionary? t }
{ strip-debugger? t }
{ strip-c-types? t }
{ deploy-math? t }
{ deploy-compiled? t }
{ deploy-io? f }
{ deploy-ui? t }
{ "bundle-name" "Lindenmayer Systems.app" }
}

You can also change the configuration:
f strip-word-names? "lsys.ui" set-deploy-flag

The configuration is stored in a deploy.factor file of the vocabulary directory.

Deployment has been tested to work with the following vocabs; they all include sensible deploy configs now:
  • automata.ui
  • boids.ui
  • bunny
  • color-picker
  • gesture-logger
  • golden-section
  • hello-ui
  • lsys.ui
  • maze
  • nehe
  • tetris

To deploy your own app, you should only have to set two flags:
  • deploy-ui? - set this to true if your app uses the UI
  • deploy-io? - set this to true if you want non-blocking I/O or sockets

Specialized float arrays

I added float arrays to Factor. A float array behaves like an array of floats, except the representation is more efficient; individual elements are not boxed.

Literal float arrays look like F{ 0.64 0.85 0.43 0.16 0.37 }.

Float arrays can provide a performance benefit if the compiler is able to infer enough information to unbox float values. For example, consider the following word:
: v+ [ + ] 2map ;

It takes two arrays and adds elements pairwise. Let's try timing the performance of this word with normal arrays:
( scratchpad ) { 0.64 0.85 0.43 0.16 0.37 0.64 0.85 0.43 0.16 0.37 } dup [ 1000000 [ 2dup v+ drop ] times ] time
3200 ms run / 25 ms GC time

Now float arrays:
( scratchpad ) F{ 0.64 0.85 0.43 0.16 0.37 0.64 0.85 0.43 0.16 0.37 } dup [ 1000000 [ 2dup v+ drop ] times ] time
3653 ms run / 70 ms GC time

It is actually slower! This is because each element access has to allocate a new float on the heap. But now, lets use the new hints vocabulary to give a hint to the compiler that v+ should be optimized for float arrays:
HINTS: v+ float-array float-array ;

This has the effect of compiling a version of this word specialized to float arrays. Here, the compiler can work some magic and eliminate boxing altogether:
( scratchpad ) F{ 0.64 0.85 0.43 0.16 0.37 0.64 0.85 0.43 0.16 0.37 } dup [ 1000000 [ 2dup v+ drop ] times ] time
974 ms run / 10 ms GC time

I used float arrays to make the spectral norm benchmark faster: the run time went from 120 seconds to 30 seconds. The raytracer was not improved by float arrays, though; I need to investigate why.

Also float array operations are only compiled efficiently on PowerPC right now. I need to code some new assembly intrinsics for the other platforms.

Float arrays can be passed to C functions directly. Long-term, somebody should look into using SSE2 and AltiVec to optimize vector operations on float arrays. That would really rock.

Sunday, July 22, 2007

execve() returning ENOTSUP on Mac OS X

Apple's man page for execve() does not list ENOTSUP as a possible error code. This problem had me stumped for hours. After I was convinced I wasn't doing anything wrong, and there was no bug with the Factor FFI, I did some Googling and finally stumbled across this.

Turns out execve() returns this error if your program is multi-threaded. Makes perfect sense, but I wish Apple had kept the man page up to date!

In any case, I'm posting it here so that any other poor sucker who runs into this can find the solution with Google in less time than I did.

Saturday, July 21, 2007

Support for IPv6 and Unix domain sockets

Some very new code, not completely tested.

The <client> and <server> words used to take a host/port pair, and a port number, respectively. This was too limiting. Now both words take address specifiers, of which are there four types:
  • "/path/to/socket" <local> - an IPC socket with the given path (Unix domain sockets on Unix)
  • "dotted.quad" 123 <inet4> - an IPv4 address
  • "ipv6.addr" 1234 <inet6> - an IPv6 address
  • "hostname" "http" <inet> - Internet host named by DNS entry; will be either IPv4 or IPv6

Note that in most cases, <client> will be called with an instance of inet; this invokes the domain name resolver, which may produce a list of multiple IPv4 and IPv6 addresses. Factor tries each one in turn until a connection succeeds. This is the expected behavior for client sockets, since users generally input host names and not IP addresses, and don't care if the connection is made over IPv4 or IPv6.

The <server> word requires an instance of the more specific inet4 and inet6 classes. Since in most cases, a server interested in connections on, say, port 8080 wants to receive connections over both IPv4 and IPv6, the with-server combinator should be used instead of calling <server> directly. Here's a usage example which waits for connections on port 8080 and sends "Hello, client" to each one:
8080 internet-server [ "Hello, client." print ] with-server

If your system is configured for IPv6, this example will spawn two threads, for IPv4 and IPv6 connections.

Server sockets can now be restricted to the loopback network interface by passing 8080 local-server instead of 8080 internet-server. Before this was provided by the Unix-only loopback-server module; this module is now obsolete.

The domain name resolver can now be invoked directly with the resolve-host word. Given a host name and port name or number, it returns a list of address specifiers.

UDP/IP works as before; words which used to take host/port pairs now take address specifiers. Note that the inet address specifier is not supported for UDP/IP; clients must first resolve the destination host name, and pick the appropriate address specifier, whether it be an IPv4 or IPv6 address, from the result.

Unix domain sockets are accessed with the local address specifier.

Stream Unix domain sockets are very commonly used, but a little-known fact is that datagram Unix domain sockets are supported too. Here is some proof.

Enter this in one listener:
"/tmp/dgram-dst" <local> <datagram>
dup receive
stream-close

Then this in another:
"/tmp/dgram-src" <local> <datagram>
B{ 1 2 3 4 5 } "/tmp/dgram-dst" <local> pick send
stream-close

The first machine should now have the following two objects on the stack:
B{ 1 2 3 4 }
T{ local f "/tmp/dgram-src" }

I still have to fix assorted bugs in this code. Also Windows network support is lagging; not only does it have to be updated to support address specifiers, but it is also missing UDP hooks at this stage.

I won't be adding more features to networking for a little while, but the next thing I will do is probably raw sockets.

Thursday, July 19, 2007

Generating stand-alone Mac OS X applications from Factor

This is all subject to change, but right now, the following works:
USE: tools.deploy
USE: tools.deploy.app
"Tetris.app" "tetris" H{ { deploy-compiled? t } { deploy-ui? t } } deploy.app

After a rather slow bootstrap and deploy process, you're left with Tetris.app, 2.2 Mb in size. Of this, 1.3 Mb is the FreeType font rendering library; soon, Factor will use native font rendering on Mac OS X, thus we won't have to ship FreeType anymore.

A lot remains to be done: we need a graphical way to configure the deployment tool (there are a lot of switches which can be toggled in the hashtable passed to deploy.app), and there needs to be an automated way of packaging required resources and native libraries with the app. And of course, deployment of native Linux and Windows binaries is a whole other kettle of fish. But over time, all of this will be implemented.