Thursday, February 28, 2008

Improvements to io.files

Most programming language libraries tend to have pretty weak support for filesystem manipulation compared to what you can find in even the most basic Unix commands.

For example, consider a command like cp (I'll be talking about the GNU variant here). It has three behaviors:
  • You can give it a source file and destination file. It copies it, preserving all attributes if you pass the -a switch.
  • You can give it a source file and a destination directory which already exists. The file is copied into a new file having the same name but inside the destination directory.
  • You can give it a list of source files and a destination directory that must already exist. In this case all the files are copied to the destination directory.

Furthermore, if you use the -r switch you get recursive directory copying.

All the traditional Unix commands, like mv, rm, and so on, support a very rich range of behaviors.

My goal was to recreate this using Factor words. As such, I've beefed up Factor's io.files vocabulary considerably. Unlike the shell, where one command can be overloaded to do various things in a "do what I mean" fashion, programming languages should be somewhat more explicit, in my opinion. So what I've done is implemented the equivalent of various Unix commands where each command maps to several words.

First up is deleting files. Factor already had delete-file and delete-directory for empty directories. What was missing was recursive deletion, so I added delete-tree.

Next up is moving files. Factor had a rename-file word but it could also "rename" it to a different directory, so I changed its name to move-file. I added a move-file-to word which takes a destination directory:
: move-file-to ( from to -- ) over file-name path+ move-file ;

Also I added a move-files-to word which takes a sequence of filenames.

For copying, I already had copy-file. I added copy-file-to and copy-files-to, together with a recursive copy-tree, copy-tree-to, and copy-trees-to.

I'm still thinking about unifying at least some of these words. For example, instead of move-file-to and move-files-to we could have a generic move-to:
GENERIC# move-to 1 ( from to -- )

M: string move-to over file-name path+ move-file ;

M: sequence move-to [ move-to ] curry each ;

Another thing I'm going to add soon is support for globs when deleting, moving and copying files. To do this I just need to add directory globbing to the glob library, which is pretty trivial.

The glob library could add a third method to the above generic:
M: glob move-to >r file-glob r> move-to ;

Once all of this is done, it should be pretty easy for someone to write a portable file manager using the above features. And don't forget portable directory change notification! Factor's I/O capabilities are looking pretty interesting these days indeed...

Friday, February 22, 2008

Improved alarms library

Until now, Factor has had several means of scheduling recurring tasks and timeouts:
  • You could just spawn a thread and call sleep
  • Recurring UI timers, with 10ms resolution
  • One-off alarms, with 100ms resolution
  • I/O timeouts system with 5 second resolution

None of these were satisfactory. The thread approach is fine for the most part, except there was no way to interrupt a sleeping thread. The other two approaches had low resolution because they relied on a thread which would poll some kind of timer queue for events.

Alarms


I unified all these approaches with a new alarms vocabulary which does not rely on polling, instead an alarm thread is woken up when alarms are added. To achieve this, I added a new word to the threads vocabulary named nap. It is like sleep except other threads can interrupt the sleep with interrupt; the nap word returns a boolean indicating whether it was interrupted or not.

The alarm thread naps until the next alarm is set to go off, however if a new timer is added in the mean time which would go off sooner, the thread is interrupted. This means it no longer has to poll regularly, and also increases accuracy.

The alarm API is straightforward:
  • add-alarm ( quot time frequency -- alarm ) adds an alarm which first fires at time and then every frequency time units thereafter. The timestamp and time deltas are the timestamp and dt types defined in the calendar vocabulary; no need to mess around with integers represeting milliseconds.
  • cancel-alarm ( alarm -- ) cancels a pending alarm.
  • later ( quot delay -- alarm ) runs a quotation once, a fixed delay from now. Code which calls it looks almost like English:
    [ "Hello world" print ] 5 minutes later

Alarms are stored in a min-heap for efficiency, and the cancel-alarm word uses a new feature of heaps, namely the ability to remove an entry in O(log n) time.

Strongly-typed timeouts


Until now the set-timeout word for streams, as well as timeouts for the process launcher took integers denoting time counts in milliseconds. That is so 1970's. Instead the new style is strongly-typed time units from the calendar library:
1 minutes stdio get set-timeout

The sleep word is generic, and if calendar is loaded it has methods which accept time units as well:
5 minutes sleep
30 seconds sleep
10 milliseconds sleep

Timeouts for locks, semaphores, promises, futures, message sends


The concurrency.messaging library for Erlang-style message passing finally supports timeouts (strongly-typed, as above). The other concurrency abstractions do as well. All of this is implemented on top of the same underlying framework which in turn sits on top of alarms, which in turn use nap.

Wednesday, February 20, 2008

Factor crash with gcc 4.3.0

If you are running the following version of gcc on Linux/x86, Factor might crash at the beginning of stage2 bootstrap:
gcc (Debian 4.3-20080202-1) 4.3.0 20080202 (experimental)

This seems to be a gcc bug. There is a workaround, however; it was discovered by jamesvnc of #concatenative. Just pass the following argument to make:
make SITE_CFLAGS=-fno-forward-propagate

Tuesday, February 19, 2008

Some changes to threads

For the last two days I've been working on Factor's threading system. First the bad news: it is still a co-operative threading system that doesn't use multiple cores. However the changes were pretty extensive.

I noticed a memory leak in the UI: sometimes opening and closing lots of windows didn't release memory. I tracked it down to a "feature" of Factor's threads: when in-thread is called, the new thread starts with a copy of the current thread's data stack and name stack. This is wrong; consider the following code:
: do-something ( x -- )
[ ... ] in-thread process-x ;

: another-word ( a x -- b )
do-something blah ;

Here, do-something is called while a happens to be on the stack, and the new thread starts with this value on the stack too. However, do-something's stack effect does not include a; it doesn't care about a, and if a is large and the thread outlives the dynamic extent of the call to another-word, then we've potentially leaked memory.

Another limitation of Factor's threads I've been annoyed with in the past is that there's no way to get a list of running threads: threads were just continuations, and threads waiting on some condition were just some random continuation stashed away somewhere.

A final issue was that Chris Double''s message-passing concurrency library needed to associate a mailbox with every thread, and he was doing this in an ad-hoc way, storing the mailbox in a dynamically-scoped variable when spawning the thread. However the problem with this approach is that dynamic scope is not exactly thread local scope: if a continuation is reified in one thread and resumed in another, it inherits all dynamic bindings, whereas in this case you would not want it to inherit the mailbox.

So with these three problems in mind, I set out to make some changes to Factor's thread system.

Spawning threads


Threads are now a first-class data type and runnable threads are registered in a global hashtable which can be printed by the threads. word.

To spawn a thread, it is now recommended that you use the following word:
: spawn ( quot name -- thread )

It now takes a name for debug purposes, and outputs the thread on the stack. The new thread starts with an empty data stack and a name stack containing the global namespace only; if you want to pass data to the thread, you must arrange for this by partially applying the data to the quotation using curry or compose.

The in-thread word is still there and has the old behavior: the new thread gets a copy of the data and name stacks, and nothing remains on the stack. It is useful for quick tests and such, but new code should use spawn instead.

Dynamic, lexical and thread-local variables


As I've mentioned, new threads no longer inherit the name stack when started with spawn (to get the old behavior, use in-thread). That is, the following will print 49:
SYMBOL: x

49 x set-global
63 x [
[ x get . ] "Test" spawn drop
] with-variable

This behavior is in line with a number of Common Lisp implementations that decided not to have threads inherit dynamic bindings for similar reasons (potential hard to find memory leaks) as well as issues unique to Common Lisp (dynamic variables can be stack-allocated if dynamic extent optimizations are used).

Note that threads still respect lexical scope just as one would expect. Here is a word which makes a closure that closes over the parameter to the word (n) as well as a binding established by [let:
:: make-thread | n |
[let | x 49 |
[ [ n x + . ] "Test" spawn ]
] ;

We can make a closure and call it; it will print 69:
20 make-thread call

This required no extra work to implement correctly; the locals vocabulary already implements correct closure semantics for all combinators.

Thread-local variables are new. I haven't found a use for them yet, but they were trivial to implement now that threads are first-class.

The tget, tset words get and set thread-local variable values, just like get and set for dynamic variables.

Message-passing concurrency


There are essentially no changes to this other than the fact that the concurrency vocabulary is now named concurrency.messaging, and the spawn word has been moved into the core threads vocabulary.

Chris Double's channels work unchanged.

Promises and futures


These have been split out and moved into concurrency.promises and concurrency.futures, respectively.

Locks


Message-passing concurrency and channels are great but sometimes you just need something simpler. I implemented some new abstractions, mostly by taking ideas from Java's java.util.concurrent package.

First up are locks, found in concurrency.locks. These come in two forms, non-reentrant and reentrant. A combinator is used to acquire and release the lock, ensuring that lock operations are paired:
SYMBOL: my-lock

<lock> my-lock set

...

my-lock get [ ... ] with-lock

Reentrant locks can be acquired recursively by a thread already holding the lock; otherwise they are the same. They are created by <reentrant-lock>.

Read/write locks


Read/write locks implement the pattern where you want multiple reader threads to have access to a shared resource, but only one writer thread to have access at a time, and only if no threads are reading.

Read/write locks are created with <rw-lock>, and acquired/released with with-read-lock and with-write-lock. Read/read, Write/read and write/write reentrancy is supported.

Count-down latches


A count-down latches, found in concurrency.count-downs, begin with some initial value; threads can either wait for its value to reach zero, or decrement its value (never past zero). They are craeted with <count-down>, decremented with count-down and can be waited on with await.

Count-down latches are used to implement a parallel-each combinator. This combinator takes a sequence and a quotation, spawns a thread for each element, and waits for them all to finish. It does this by creating a count-down latch that each thread decrements as it finishes; after spawning all threads, the caller waits for the latch to reach zero.

Exchangers


Exchangers are implemented in concurrency.exchangers. An exchanger is a synchronization point between two threads: a thread comes to the exchange point with an object, and waits for another thread to do the same. Then the first thread receives the second thread's object, and vice versa.

Exchangers are created by calling <exchanger> and objects are exchanged with exchange. Their implementation is delightfully simple thanks to boxes:
TUPLE: exchanger thread object ;

: <exchanger> ( -- exchanger )
<box> <box> exchanger construct-boa ;

: exchange ( obj exchanger -- newobj )
dup exchanger-thread box-full? [
dup exchanger-object box>
>r exchanger-thread box> resume-with r>
] [
[ exchanger-object >box ] keep
[ exchanger-thread >box ] curry "exchange" suspend
] if ;

Counting semaphores


Counting semaphores are in the concurrency.semaphores vocabulary. Semaphores are created by passing an initial non-zero value to <semaphore>. A thread can either decrement a semaphore with acquire, or increment one with release: if it is already zero when being decremented, it waits for another thread to increment it first.

Unlike locks, acquire/release does not need to be paired and can even be done in different threads: however for pairing, a with-semaphore combinator is provided which decrements the semaphore, runs a quotation, then increments it again.

Here is an example which uses parallel-each to spawn a series of jobs, the jobs proceed mostly in parallel however a semaphore is used to ensure that one particular section is not run by more than 10 threads at a time, perhaps because it spawns an external process which uses a lot of memory and we do not wish to hit the swap:
: do-stuff ( value -- ) ... ;

: do-expensive-task ( value -- ) ... ;

: do-more-stuff ( value -- ) ... ;

: do-job ( value semaphore -- )
over do-stuff
over [ do-expensive-stuff ] curry with-semaphore
do-more-stuff ;

: do-jobs ( values -- )
10 <semaphore> [ do-job ] curry parallel-each ;

Boxes

Today I was looking over some code and noticed a repetitive pattern involving tuple slots that would come up again and again:
  • One word would check if the slot was set; if not, it would throw an error, otherwise it would return the slot's value and set the slot's value to f.
  • Another word would check if a slot was set, and if so, throw an error, otherwise store the top of the stack in the slot.

I decided to abstract this out into a new "box" data type. A box can hold a single value; storing a value into a full box, or taking a value out of an empty box is an error, and taking a value out empties the box. Now the idea is that you set a tuple slot to a new box once in the constructor, and then instead of reading and writing the tuple slot you read and write the box.

I've already found several uses for this in the core/ as well as some of my libraries in extra/. I've not seen the box type in other object-oriented languages. It seems like an obvious abstraction; maybe it is considered too simple by some, but its nice to encapsulate this logic in one place. Factoring it out also let me make the logic more sophisticated: it can distinguish between a box holding f and an empty box. Most individual usages didn't bother. When Factor moves to native threading boxes will be even more valuable since boxes will be atomic.

Sunday, February 17, 2008

Some rudimentary control flow analysis

Factor's optimizer is now at the stage where further progress will require a re-design. I'm fine with that, since the current design has really lasted a lot longer than I expected -- the basic structure has remained the same since early 2004 when I started working on the native compiler, and in many ways it resembles the old Java Factor implementation's JVM compiler.

Over the last few days I picked off some of the remaining low-hanging fruit.

Hoisting code after conditionals where one branch throws an error


The first optimization concerns conditionals where one branch unconditionally throws an error. Consider the following code:
: foo [ A ] [ B throw ] if C ;

This is in fact equivalent to:
: foo [ A C ] [ B throw ] if ;

And this is precisely what the new optimization does.

Loop detection and loop tail hoisting


Another new optimization is something I like to call "loop detection". Consider a trivial Factor iteration expression:
: foo 100 [ bar ] times 3 baz ;

The times combinator is inlined:
: foo 100 [ bar ] [ drop ] swap compose each-integer 3 baz ;

The each-integer word is also inlined:
: foo 100 [ bar ] [ drop ] swap compose iterate-prep (each-integer) 3 baz ;

Now (each-integer) is also declared as inline, however it is recursive, so what this means in practice is that the compiler creates a new "generated symbol" containing a definition of (each-integer) which is specialized for that call site. Proceeding with our example, we get:
: G12342 ( i n quot -- )
[ iterate-step iterate-next G12342 ]
[ 3drop ] if-iterate? ; inline

: foo 100 [ bar ] [ drop ] swap compose iterate-prep G12342 ;

After some more inline expansion,
: G12342
[ swap >r 2dup >r >r call r> r> r> swap >r >r 1+ r> r> G12342 ]
[ 3drop ] >r >r 2over < r> r> if ; inline

: foo 100 [ drop bar ] 0 -rot G12342 3 baz ;

Now, the compiler sees that call is being applied to a literal quotation, so its contents can just be copied to the call site:
: G12342
[ swap >r 2dup >r >r drop drop bar r> r> r> swap >r >r 1+ r> r> G12342 ]
[ 3drop ] >r >r 2over < r> r> if ; inline

: foo 100 [ drop bar ] 0 -rot G12342 3 baz ;

Also, if is being applied to two literal quotations, so they can be hoisted up to their call site:
: G12342
[ swap >r 2dup >r >r drop drop bar r> r> r> swap >r >r 1+ r> r> G12342 ]
[ 3drop ] >r >r 2over < r> r> 2drop
[ swap >r 2dup >r >r drop drop bar r> r> r> swap >r >r 1+ r> r> G12342 ]
[ 3drop ] if ; inline

: foo 100 [ drop bar ] 0 -rot G12342 3 baz ;

Now, the quotations [ drop bar ], [ swap >r 2dup >r >r drop drop bar r> r> r> swap >r >r 1+ r> r> G12342 ] and [ 3drop ] are dead literals, so they are removed, and all stack shuffle words which they travel through are collapsed:
: G12342
2dup <
[ >r >r bar r> 1+ r> G12342 ]
[ 2drop ] if ; inline

: foo 100 0 swap G12342 3 baz ;

This is where previous Factor releases would stop performing control and data flow optimizations. The specialized loop body (G12342 in this example) would be compiled as its own word in the code heap, and foo would call this word. This entails some overhead in setting up and tearing down activation frames, and so on.

In the general case, this is all you can do: for example, consider the case of a binary-recursive combinator that calls itself in non-tail position; the combinator body needs its own activation frames. However, for tail recursive loops, we can do better. Since the loop only calls itself in tail position, it can share the activation frame of foo itself; that is, conceptually, we can compile it to the following:
: G12342
2dup <
[ >r >r bar r> 1+ r> G12342 ]
[ 2drop ] if ; inline

: foo
100 0 swap
G12342: ! a label
2dup <
[ >r >r bar r> 1+ r> goto: G12342 ] ! goto is not a real Factor word...
[ 2drop ] if
3 baz ;

After performing this type of inlining we need to distinguish the recursive call to G12342 from a normal call, since now it must be a tail call even though it is not in tail position!

Now with general loops, this is the best we can do. However in the above case, we can apply an optimization much like the first one described in this blog post; since only one branch of that conditional ever returns, we can hoist the code after that conditional into the returning branch:
: foo
100 0 swap
G12342: ! a label
2dup <
[ >r >r bar r> 1+ r> goto: G12342 ] ! goto is not a real Factor word...
[ 2drop 3 baz ] if ;

An example of a tail recursion where this optimization cannot be used:
: example-1
dup string? [ do-something ] [
dup integer? [ do-something-else ] [ do-another-thing example-1 ] if
] if ;

: example-2
example-1 xyz ;

Here, the code at xyz would have to be cloned twice in order to be hoisted up into the non-recursive branches above, and in general, branch splitting can lead to exponential code growth so I avoid this particular optimization.

Loop detection and nested loops


This optimization works on nested loops; for example, if you have
: foo [ [ drop ] each ] each ;

Then it optimizes down to:
: G:144972
pick pick <
[ >r >r 1+ r> r> G:144972 ]
[ 3drop r> r> r> swap >r >r 1+ r> r> G:144955 ]
if ;

: G:144955
pick pick < [
swap >r 2dup >r >r
nth-unsafe dup length swap 0 -rot G:144972
] [ 3drop ] if ;

: foo dup length swap 0 -rot G:144955 ;

Note the mutually-recursive loops that appear due to tail hoisting; also note that it appears as if the conditionals leave the retain stack unbalanced! While the compiler enforces retain stack balancing for user code, internally it is free to do whatever crazy stuff it wants as long as the result gives the same output as the user's code. Of course because of loop detection, both mutually-recursive loops share foo's activation frame:
: foo
dup length swap 0 -rot
G:144955: ! a label
pick pick < [
swap >r 2dup >r >r
nth-unsafe dup length swap 0 -rot
G:144972: ! a label
pick pick <
[ >r >r 1+ r> r> goto: G:144972 ]
[ 3drop r> r> r> swap >r >r 1+ r> r> goto: G:144955 ]
if
] [ 3drop ] if ;

Future directions


Now loop detection works quite well and the implementation is elegant however it is really quite rudimentary. I'd like to extend this to more general control flow analysis, and for this I will need to extend the compiler's intermediate representation into something more sophisticated.

For example, consider the following code:
dup bar? [ foo? ] [ drop f ] if [ A ] [ B ] if ;

Right now it is compiled down to something like this:
dup bar?
if-true-goto: 1
drop f
goto: 2
1: foo?
2: if-true-goto: 3
B
goto: 4
3: A

However if the second branch of the first conditional is taken, then clearly we don't have to test the top of the stack again because we already know it is f. So I'd like to compile the this code as follows:
dup bar?
if-true-goto: 1
drop
goto: 5
1: foo?
2: if-true-goto: 3
5: B
goto: 4
3: A
4: ...

Another thing I need to do very soon is register allocation across basic blocks. Right now the compiler uses registers to store values inside a basic block (a run of code without any subroutine calls; these can get rather long because of inlining, open-coded intrinsics,and optimizations such as the two I implemented and described here). However, it does not attempt to allocate registers across branch merge points and in particular across loop iterations. That's pretty lame.

To be able to do all of this, I need an IR which represents control flow in a more natural way, allowing more sophisticated optimizations to be expressed concisely and efficiently. This will be my next step in advancing Factor's compiler.

Thursday, February 14, 2008

Invoking the gdb disassembler to disassemble words

Until now, I've had to go through a rather laborious process to look at the machine code generated by the Factor compiler. I'd ask Factor for the address of a word with word-xt, then attach a gdb instance to Factor, then run disassemble and look at the output. This wastes time, especially when I'm debugging major changes (like I am now). So I've cooked up a quick hack to avoid having to do this in the future.

The new code is in tools.disassembler. Note that for now, it only works on Unix. If I figure out how to get the current cygwin process ID from Factor (Factor doesn't link against cygwin.dll) then I can make it work with cygwin gdb too.

The source begins with the usual boilerplate:
USING: io.files io words alien kernel math.parser alien.syntax
io.launcher system assocs arrays sequences namespaces qualified
regexp ;
QUALIFIED: unix
IN: tools.disassembler

We qualify the unix vocab since it has a write word which clashes with io:write, and we want to call the latter.

We communicate with gdb using files:
: in-file "gdb-in.txt" resource-path ;

: out-file "gdb-out.txt" resource-path ;

We cannot use pipes since there is a race condition there; gdb suspends the process while disassembling, so if the pipe fills up, then gdb hangs because Factor cannot read from the pipe since it is suspended.

We have a word which takes a pair of addresses or a word, and creates a gdb command for disassembling this object in the current process, it then writes these commands to the input file:
GENERIC: make-disassemble-cmd ( obj -- )

M: word make-disassemble-cmd
word-xt 2array make-disassemble-cmd ;

M: pair make-disassemble-cmd
in-file [
"attach " write
unix:getpid number>string print

"disassemble " write
[ number>string write bl ] each
] with-file-out ;

Then we write a word to invoke gdb:
: run-gdb ( -- lines )
[
+closed+ +stdin+ set
out-file +stdout+ set
[ "gdb" , "-x" , in-file , "-batch" , ] { } make +arguments+ set
] { } make-assoc run-process drop
out-file file-lines ;

We pass gdb the path name to the file we just saved, together with some switches.

Note that we close stdin so that if gdb attempts to read commands, it gets an EOF instead of hanging. We also redirect the output to our output file. Then we read the output file and return the results.

Finally, a couple of words to clean up the output; we filter everything that's not a line of disassembly (gdb loading messages, etc), and we convert tabs to spaces since the Factor UI doesn't display tabs:
: relevant? ( line -- ? )
R/ 0x.*:.*/ matches? ;

: tabs>spaces ( str -- str' )
[ dup CHAR: \t = [ drop CHAR: \s ] when ] map ;

Finally, we have the actual word that calls the above:
: disassemble ( word -- )
make-disassemble-cmd run-gdb
[ relevant? ] subset [ tabs>spaces ] map [ print ] each ;