tag:blogger.com,1999:blog-170878502024-09-07T23:38:39.314-04:00Factor: a practical stack language<a href="http://factorcode.org/slava">Slava Pestov</a>'s weblog, primarily about <a href="http://factorcode.org">Factor</a>.Slava Pestovhttp://www.blogger.com/profile/02768382790667979877[email protected]Blogger520125tag:blogger.com,1999:blog-17087850.post-24827090337251828542010-09-18T23:40:00.003-04:002010-09-18T23:54:35.251-04:00Factor 0.94 now available<p>Factor 0.94 is now available from the <a href="http://factorcode.org">Factor website</a>, five months after the previous release, Factor 0.93. Binaries are provided for 10 platforms.</p> <p>As usual, contributors did most of the work. Thanks to Daniel Ehrenberg, Dmitry Shubin, Doug Coleman, Erik Charlebois, Joe Groff, John Benediktsson, Jose A. Ortega Ruiz, Niklas Waern, Samuel Tardieu, Sascha Matzke and everyone else who helped out this time around!</p> <h3>Incompatible changes:</h3> <ul> <li>The PowerPC architecture is no longer supported. (Slava Pestov)</li> <li>The <a href="http://docs.factorcode.org/content/word-require-when%2Cvocabs.loader.html">require-when</a> word now supports dependencies on multiple vocabularies. (Daniel Ehrenberg)</li> <li>The <code>C-ENUM:</code> word in the C library interface has been replaced with <a href="http://docs.factorcode.org/content/word-ENUM__colon__%2Calien.syntax.html">ENUM:</a>, a much improved word for defining type-safe enumerations. (Erik Charlebois, Joe Groff)</li> <li>Tuple slot setter words with stack effect <code>( value object -- )</code> are now named <code>foo<<</code> instead of <code>(>>foo)</code>. Most code is unaffected since it uses the <code>>>foo</code> form. (Slava Pestov)</li> <li>The older <code>system-micros</code> word, which returned microseconds since the Unix epoch as an integer, has been removed. For a while, the recommended way to get the current time has been to call the <code>now</code> word from the <a href="http://docs.factorcode.org/content/vocab-calendar.html">calendar</a> vocabulary, which returned a <a href="http://docs.factorcode.org/content/word-timestamp%2Ccalendar.html">timestamp</a> instance. (Doug Coleman)</li> <li>A few sequence-related words were moved from the <a href="http://docs.factorcode.org/content/vocab-generalizations.html">generalizations</a> vocabulary to <a href="http://docs.factorcode.org/content/vocab-sequences.generalizations.html">sequences.generalizations</a>. (Slava Pestov)</li> <li>The <code>alarms</code> vocabulary has been renamed to <a href="http://docs.factorcode.org/content/vocab-timers.html">timers</a> to better explain its true purpose, with improved timing accuracy and robustness. (Doug Coleman)</li> <li><a href="http://docs.factorcode.org/content/vocab-cocoa.subclassing.html">cocoa.subclassing</a>: the syntax for defining new Objective-C classes has been changed to improve readability. (Slava Pestov)</li> <li><a href="http://docs.factorcode.org/content/vocab-io.streams.limited.html">io.streams.limited</a>: the ability to throw errors on EOF was extracted from limited streams, and limited streams simplified as a result. Throwing on EOF is now implemented by the <a href="http://docs.factorcode.org/content/vocab-io.streams.throwing.html">io.streams.throwing</a> vocabulary. (Doug Coleman)</li> </ul> <h3>New libraries:</h3> <ul> <li><a href="http://docs.factorcode.org/content/vocab-boyer-moore.html">boyer-moore</a>: efficient text search algorithm (Dmitry Shubin)</li> <li><a href="http://docs.factorcode.org/content/vocab-checksums.internet.html">checksums.internet</a>: implementation of checksum algorithm used by ICMP for the <a href="http://docs.factorcode.org/content/vocab-checksums.html">checksums</a> framework (John Benediktsson)</li> <li><a href="http://docs.factorcode.org/content/vocab-gdbm.html">gdbm</a>: disk-based hash library binding (Dmitry Shubin)</li> <li><a href="http://docs.factorcode.org/content/vocab-io.encodings.detect.html">io.encodings.detect</a>: binary file/text encoding detection heuristics from jEdit (Joe Groff)</li> <li><a href="http://docs.factorcode.org/content/vocab-javascriptcore.html">javascriptcore</a>: FFI to the WebKit JavaScript engine (Doug Coleman)</li> <li><a href="http://docs.factorcode.org/content/vocab-lua.html">lua</a>: FFI to the Lua scripting language (Erik Charlebois)</li> <li><a href="http://docs.factorcode.org/content/vocab-oauth.html">oauth</a>: minimal implementation of client-side OAuth (Slava Pestov)</li> <li><a href="http://docs.factorcode.org/content/vocab-sequences.unrolled.html">sequences.unrolled</a>: efficient unrolled loops with constant iteration count (Joe Groff)</li> </ul> <h3>Improved libraries:</h3> <ul> <li><a href="http://docs.factorcode.org/content/vocab-cuda.html">cuda</a>: various improvements (Joe Groff, Doug Coleman)</li> <li><a href="http://docs.factorcode.org/content/vocab-game.input.html">game.input</a>: now uses XInput2 on X11 (Niklas Waern)</li> <li><a href="http://docs.factorcode.org/content/vocab-gpu.render.html">gpu.render</a>, <a href="http://docs.factorcode.org/content/vocab-gpu.buffers.html">gpu.buffers</a>: various improvements (Joe Groff)</li> <li><a href="http://docs.factorcode.org/content/vocab-math.combinatorics.html">math.combinatorics</a>: various improvements (John Benediktsson)</li> <li><a href="http://docs.factorcode.org/content/vocab-math.primes.html">math.primes</a>: various improvements (Samuel Tardieu)</li> <li><a href="http://docs.factorcode.org/content/vocab-math.vectors.simd.cords.html">math.vectors.simd.cords</a>: compound "256-bit" SIMD types now support the full set of SIMD operations (Joe Groff)</li> <li><a href="http://docs.factorcode.org/content/vocab-mongodb.html">mongodb</a>: various improvements (Sascha Matzke)</li> <li><a href="http://docs.factorcode.org/content/vocab-twitter.html">twitter</a>: now uses OAuth as required by Twitter (Slava Pestov)</li> <li><a href="http://docs.factorcode.org/content/vocab-unix.users.html">unix.users,</a> <a href="http://docs.factorcode.org/content/vocab-unix.groups.html">unix.groups</a>: various improvements (Doug Coleman)</li> </ul> <h3>Compiler improvements:</h3> <ul> <li>Improved instruction selection, copy propagation, representation selection, and register allocation; details in a <a href="http://factor-language.blogspot.com/2010/05/collection-of-small-compiler.html">blog post</a>. (Slava Pestov) <li>An instruction scheduling pass now runs prior to register allocation, intended to reduce register pressure by moving uses closer to definitions; details in a <a href="http://useless-factor.blogspot.com/2010/02/instruction-scheduling-for-register.html">blog post</a>. (Daniel Ehrenberg)</li> <li>The code generation for the C library interface has been revamped; details in a <a href="http://factor-language.blogspot.com/2010/07/overhauling-factors-c-library-interface.html">blog post</a>. (Slava Pestov) <li>Something similar to what C++ and C# programmers refer to as "value types"; binary data can now be allocated on the call stack, and passed to C functions like any other pointer. The <a href="http://docs.factorcode.org/content/word-with-out-parameters%2Calien.data.html">with-out-parameters</a> combinator replaces tricky code for allocating and juggling multiple temporary byte arrays used as out parameters for C function calls, making this idiom easier to read and more efficient. The <a href="http://docs.factorcode.org/content/word-with-scoped-allocation,alien.data.html">with-scoped-allocation</a> combinator presents a more general, lower-level interface. (Slava Pestov)</li> <li>The compiler can now use the x87 floating point unit on older CPUs where SSE2 is not available. However, this is not recommended, because the build farm does not test Factor (or build any binaries) in x87 mode, so this support can break at any time. To use x87 code generation, you must download the source code and bootstrap Factor yourself, on a CPU without SSE2. (Slava Pestov)</li> </ul> <h3>Miscellaneous improvements:</h3> <ul> <li><code>fuel</code>: Factor's Ultimate Emacs Library has seen many improvements, and also some keyboard shortcuts have changed; see the <a href="">README</a>. (Erik Charlebois, Dmitry Shubin, Jose A. Ortega Ruiz)</li> <li>A new <code>factor.cmd</code> script is now included in the <code>build-support</code> directory, to automate the update/build/bootstrap cycle for those who build from source. Its functionality is a subset of the <code>factor.sh</code> script for Unix. (Joe Groff)</li> <li>The default set of icons shipped in <code>misc</code> has been tweaked, with better contrast and improved appearance when scaled down. (Joe Groff)</li> </ul>Slava Pestovhttp://www.blogger.com/profile/02768382790667979877[email protected]0tag:blogger.com,1999:blog-17087850.post-32199213637609458182010-09-11T22:23:00.006-04:002010-09-13T23:53:29.344-04:00An overview of Factor's I/O library<p>Factor has grown a very powerful and high-level I/O library over the years, however it is easy to get lost in the forest of reference documentation surrounding the <a href="http://docs.factorcode.org/content/vocab-io.html">io</a> vocabulary hierarchy. In this blog post I'm attempting to give an overview of the functionality available, with some easy-to-digest examples, along with links for futher reading. I will also touch upon some common themes that come up throughout the library, such as encoding support, timeouts, and uses for dynamically-scoped variables.</p> <p>Factor's I/O library is the work of many contributors over the years. Implementing FFI bindings to native I/O APIs, developing high-level abstractions on top, and making the whole thing cross-platform takes many people. In particular <a href="http://docs.factorcode.org/content/author-Doug%20Coleman.html">Doug Coleman</a> did a lot of heavy lifting early on for the Windows port, and also implemented several new cross-platform features such as file system metadata and memory mapped files.</p> <p>Our I/O library is competitive with Python's APIs and Java's upcoming NIO2 in breadth of functionality. I like to think the design is quite a bit cleaner too, because instead of being a thin wrapper over POSIX we try to come up with clear and conherent APIs that make sense on both Windows and Unix.</p> <h3>First example: converting a text file from MacRoman to UTF8</h3> <p>The <a href="http://docs.factorcode.org/content/vocab-io.files.html">io.files</a> vocabulary defines words for reading and writing files. It supports two modes of operation in a pretty standard fashion:</p> <ul> <li>Stream-based: <a href="http://docs.factorcode.org/content/word-with-file-reader,io.files.html">with-file-reader</a>, <a href="http://docs.factorcode.org/content/word-with-file-writer,io.files.html">with-file-writer</a></li> <li>Entire file: <a href="http://docs.factorcode.org/content/word-file-contents,io.files.html">file-contents</a>, <a href="http://docs.factorcode.org/content/word-set-file-contents,io.files.html">set-file-contents</a>, <a href="http://docs.factorcode.org/content/word-file-lines,io.files.html">file-lines</a>, <a href="http://docs.factorcode.org/content/word-set-file-lines,io.files.html">set-file-lines</a></li> </ul> <p>What makes Factor's file I/O interesting is that it takes advantage of pervasive support for I/O encoding. In Factor, a string is not a sequence of bytes; it is a sequence of Unicode code points. When reading and writing strings on external resources, which only consist of bytes, an encoding parameter is given to specify the conversion from strings to byte arrays.</p> <p>Let's convert <code>foo.txt</code> from MacRoman, an older encoding primarily used by classic Mac OS, to UTF8:</p> <pre>USING: io.encodings.8-bit.mac-roman io.encodings.utf8 io.files ;<br /><br />"foo.txt" mac-roman file-contents<br />"foo.txt" utf8 set-file-contents</pre> <p>This is a very simple and concise implementation but it has the downside that the entire file is read into memory. For most small text files this does not matter, but if efficiency is a concern then we can do the conversion a line at a time:</p> <pre>USING: io io.encodings.8-bit.mac-roman io.encodings.utf8<br />io.files ;<br /><br />"out.txt" utf8 [<br /> "in.txt" mac-roman [<br /> [ print ] each-line<br /> ] with-file-reader<br />] with-file-writer</pre> <h3>Converting a directory full of files from MacRoman to UTF8</h3> <p>The <a href="http://docs.factorcode.org/content/vocab-io.files.html">io.files</a> vocabulary defines words for listing and modifying directories. Let's make the above example more interesting by performing the conversion on a directory full of files:</p> <pre>USING: io.directories io.encodings.8-bit.mac-roman<br />io.encodings.utf8 io.files ;<br /><br />: convert-directory ( path -- )<br /> [<br /> [<br /> [ mac-roman file-contents ] keep<br /> utf8 set-file-contents<br /> ] each<br /> ] with-directory-files ;</pre> <h3>An aside: generalizing the "current working directory"</h3> <p>If you run the following, you will see that <code>with-directory-files</code> returns relative, and not absolute, file names:</p> <pre>"/path/to/some/directory"<br />[ [ print ] each ] with-directory-files</pre> <p>So the question is, how did <code>file-contents</code> above know what directory to look for files in? The answer is that in addition to calling the quotation with the directory's contents, the <code>with-directory-files</code> word also rebinds the <a href="http://docs.factorcode.org/content/word-current-directory,io.pathnames.html">current-directory</a> dynamic variable.</p> <p>This directory is the Factor equivalent of the familiar Unix notion of "current working directory". It generalizes the Unix feature by making it dynamically-scoped; within the quotation passed to the <code>with-directory</code> combinator, relative paths are resolved relative to that directory, but other coroutines executing at the time, or code after the quotation, is unaffected. This functionality is implemented entirely at the library level; all pathname strings are "normalized" with the <code>normalize-pathname</code> word before being handed off to the operating system.</p> <p>When calling a shell command with <code>io.launcher</code>, the child process is run from the Factor <code>current-directory</code> so relative pathnames passed on the command line will just work. However, when making C FFI calls which take pathnames, you pass in absolute paths only, or normalize the path with <code>normalize-path</code> first, otherwise the C code wlll search for it in the wrong place.</p> <h3>Checking free disk space</h3> <p>The <a href="http://docs.factorcode.org/content/vocab-io.files.info.html">io.files.info</a> vocabulary defines two words which return tuples containing information about a file, and the file system containing the file, respectively: <ul> <li><a href="http://docs.factorcode.org/content/word-file-info,io.files.info.html">file-info</a></li> <li><a href="http://docs.factorcode.org/content/word-file-system-info,io.files.info.html">file-system-info</a></li> </ul> <p>Let's say your application needs to install some files in the user's home directory, but instead of failing half-way through in the event that there is insufficient space, you'd rather display a friendly error message upfront:</p> <pre>ERROR: buy-a-new-disk ;<br /><br />: gb ( m -- n ) 30 2^ * ;<br /><br />: check-space ( -- )<br /> home file-system-info free-space>> 10 gb <<br /> [ buy-a-new-disk ] when ;</pre> <p>Now if there is less than 10gb available, the <code>check-space</code> word will throw a <code>buy-a-new-disk</code> error.</p> <p>The <code>file-system-info</code> word reports a bunch of other info. There is a Factor implementation of the Unix <code>df</code> command in the <a href="http://docs.factorcode.org/content/vocab-tools.files.html">tools.files</a> vocabulary: <pre>( scratchpad ) file-systems.<br />+device-name+ +available-space+ +free-space+ +used-space+ +total-space+ +percent-used+ +mount-point+<br />/dev/disk0s2 15955816448 16217960448 183487713280 199705673728 91 /<br />fdesc 0 0 1024 1024 100 /dev<br />fdesc 0 0 1024 1024 100 /dev<br />map -hosts 0 0 0 0 0 /net<br />map auto_home 0 0 0 0 0 /home<br />/dev/disk1s2 15922262016 15922262016 383489052672 399411314688 96 /Users/slava</pre> <p>Doug has two blog posts about these features, <a href="http://code-factor.blogspot.com/2009/01/files-and-file-systems-in-factor-part-1.html">part 1</a> and <a href="http://code-factor.blogspot.com/2009/01/files-and-file-systems-in-factor-part-2.html">part 2</a>.</p> <h3>Unix only: symbolic links</h3> <p>Factor knows about symbolic links on Unix. The <a href="http://docs.factorcode.org/content/vocab-io.files.links.html">io.files.links</a> vocabulary defines a pair of words, <a href="http://docs.factorcode.org/content/word-make-link,io.files.links.html">make-link</a> and <a href="http://docs.factorcode.org/content/word-make-hard-link,io.files.links.html">make-hard-link</a>. The <a href="http://docs.factorcode.org/content/word-link-info,io.files.info.html">link-info</a> word is like <a href="http://docs.factorcode.org/content/word-file-info,io.files.info.html">file-info</a> except it doesn't follow symbolic links. Finally, the <a href="http://docs.factorcode.org/content/vocab-io.directories.hierarchy.html">directory hierarchy traversal words</a> don't follow links, so a link cycle or bogus link to / somewhere won't break everything.</p> <h3>File system monitoring</h3> <p>The <a href="http://docs.factorcode.org/content/vocab-io.monitors.html">io.monitors</a> vocabulary implements real-time file and directory change monitoring. Unfortunately at this point in time, it is only supported on Windows, Linux and Mac. Neither one of FreeBSD and OpenBSD exposes the necessary information to user-space.</p> <p>Here is an example for watching a directory for changes, and logging them:</p> <pre>USE: io.monitors<br /><br />: watch-loop ( monitor -- )<br /> dup next-change path>> print flush watch-loop ;<br /><br />: watch-directory ( path -- )<br /> [ t [ watch-loop ] with-monitor ] with-monitors ;</pre> <p>Try pasting the above code into a Factor listener window, and then run <code>home watch-directory</code>. Every time a file in your home directory is modified, its full pathname will be printed in the listener.</p> <p>Java will only begin to support symbolic links and directory monitoring in the upcoming JDK7 release.</p> <h3>Memory mapped files</h3> <p>The <a href="http://docs.factorcode.org/content/vocab-io.mmap.html">io.mmap</a> vocabulary defines support for working with memory-mapped files. The highest-level and easiest to use interface is the <a href="http://docs.factorcode.org/content/word-with-mapped-array,io.mmap.html">with-mapped-array</a> combinator. It takes a file name, a <a href="http://docs.factorcode.org/content/article-c-types-specs.html">C type</a>, and a quotation. The quotation can perform generic sequence operations on the mapped file.</p> <p>Here is an example which reverses each group of 4 bytes:</p> <pre>USING: alien.c-types grouping io.mmap sequences<br />specialized-arrays ;<br />SPECIALIZED-ARRAY: char<br /><br />"mydata.dat" char [<br /> 4 <sliced-groups><br /> [ reverse! drop ] each<br />] with-mapped-array</pre> <p>The <a href="http://docs.factorcode.org/content/word-__lt__sliced-groups__gt__%2Cgrouping.html"><sliced-groups></a> word returns a view of an underlying sequence, grouped into n-element subsequences. Mutating one of these subsequences in-place mutates the underlying sequence, which in our case is a mapped view of a file.</p> <p>A more efficient implementation of the above is also possible, by mapping in the file as an <code>int</code> array and then performing bitwise arithmetic on the elements.</p> <h3>Launching processes</h3> <p>Factor's <a href="http://docs.factorcode.org/content/vocab-io.launcher.html">io.launcher</a> vocabulary was originally developed for use by the <a href="http://factor-language.blogspot.com/2010/09/making-factors-continuous-build-system.html">build farm</a>. The build farm needs to launch processes with precise control over I/O redirection and timeouts, and so a rich set of cross-platform functionality was born.</p> <p>The central concept in the library is the <code>process</code>, tuple, constructed by calling <code><process></code>. Various slots of the process tuple can be filled in to specify the command line, environment variables, redirection, and so on. Then the process can be run in various ways, running in the foreground, in the background, or with input and output attached to Factor streams.</p> <p>The launcher's I/O redirection is very flexible. If you don't touch the redirection slots in a process tuple, the subprocess will just inherit the current standard input and output. You can specify a file name to read or write from, a file name to append to, or even supply a pipe object, constructed from the <a href="http://docs.factorcode.org/content/vocab-io.pipes.html">io.pipes</a> vocabulary.</p> <pre><process><br /> "rotate-logs" >>command<br /> +closed+ >>stdin<br /> "out.txt" >>stdout<br /> "error.log" <appender> >>stderr</pre> <p>It is possible to specify a timeout when running a process:</p> <pre><process><br /> { "ssh" "myhost" "-l" "jane" "do-calculation" } >>command<br /> 15 minutes >>timeout<br /> "results.txt" >>stdout<br />run-process</pre> The process will be killed if it runs for longer than the timeout period. Many other features are supported; setting environment variables, setting process priority, and so on. The <a href="http://docs.factorcode.org/content/vocab-io.launcher.html">io.launcher</a> vocabulary has all the details. <p>Support for timeouts is a cross-cutting concern that touches many ports of the I/O API. This support is consolidated in the <a href="http://docs.factorcode.org/content/vocab-io.timeouts.html">io.timeouts</a> vocabulary. The <code>set-timeout</code> generic word is supported by all external resources which provide interruptible blocking operations.</p> <p>Timeouts are implemented on top of our <a href="http://code-factor.blogspot.com/2009/11/monotonic-timers.html">monotonic timer support</a>; changing your system clock while Factor is running won't screw with active network connections.</p> <h3>Unix only: file ownership and permissions</h3> <p>The <code>io.files.unix</code> vocabulary defines words for reading and writing file ownership and permissions. Using this vocabulary, we can write a shell script to a file, make it executable, and run it. An essential component of any multi-language quine:</p> <pre>USING: io.encodings.ascii io.files io.files.info.unix<br />io.launcher ;<br /><br />"""<br />#!/bin/sh<br />echo "Hello, polyglot"<br />""" "script.sh" ascii set-file-contents<br />OCT: 755 "script.sh" set-file-permissions<br />"./script.sh" run-process</pre> <p>There are even more Unix-specific words in the <a href="http://docs.factorcode.org/content/vocab-unix.users.html">unix.users</a> and <a href="http://docs.factorcode.org/content/vocab-unix.groups.html">unix.groups</a> vocabularies. Using these words enables listing all users on the system, converting user names to UIDs and back, and even <code>setuid</code> and <code>setgid</code>.</p> <h3>Networking</h3> <p>Factor's <a href="http://docs.factorcode.org/content/vocab-io.sockets.html">io.sockets</a> vocabulary supports stream and packet-based networking.</p> <ul> <li>Stream-based: <a href="http://docs.factorcode.org/content/word-with-client,io.sockets.html">with-client</a>, <a href="http://docs.factorcode.org/content/word-__lt__server__gt__,io.sockets.html"><server></a>, <a href="http://docs.factorcode.org/content/word-accept,io.sockets.html">accept</a></li> <li>Packet-based: <a href="http://docs.factorcode.org/content/word-__lt__datagram__gt__,io.sockets.html"><datagram></a>, <a href="http://docs.factorcode.org/content/word-send%2Cio.sockets.html">send</a>, <a href="http://docs.factorcode.org/content/word-receive%2Cio.sockets.html">receive</a></li> </ul> <p>Network addresses are specified in a flexible manner. Specific classes exist for IPv4, IPv6 and Unix domain socket addressing. When a network socket is constructed, that endpoint is bound to a given address specifier.</p> <p>Connecting to <code>http://www.apple.com</code>, sending a GET request, and reading the result:</p> <pre>USING: io io.encodings.utf8 io.sockets ;<br /><br />"www.apple.com" 80 <inet> utf8 [<br /> """GET / HTTP/1.1\r<br />host: www.apple.com\r<br />connection: close\r<br />\r<br />""" write flush<br /> contents<br />] with-client<br />print</pre> <p>SSL support is almost transparent; the only difference is that the address specifier is wrapped in <code><secure></code>:</p> <pre>USING: io io.encodings.utf8 io.sockets<br />io.sockets.secure ;<br /><br />"www.cia.gov" 443 <inet> <secure> utf8 [<br /> """GET / HTTP/1.1\r<br />host: www.cia.gov\r<br />connection: close\r<br />\r<br />""" write flush<br /> contents<br />] with-client<br />print</pre> <p>For details, see the <a href="http://docs.factorcode.org/content/vocab-io.sockets.secure.html">io.sockets.secure</a> documentation, and my <a href="http://factor-language.blogspot.com/2008/05/ssltls-support-added.html">blog post about SSL in Factor.</a>.</p> <p>Of course you'd never send HTTP requests directly using sockets; instead you'd use the <a href="http://docs.factorcode.org/content/vocab-http.client.html">http.client</a> vocabulary.</p> <h3>Network servers</h3> <p>Factor's <a href="http://docs.factorcode.org/content/vocab-io.servers.connection.html">io.servers.connection</a> vocabulary is so cool, that a couple of years back I made a <a href="http://factor.blip.tv/file/1316060/">screencast about it</a>. Nowadays the sample application developed in that screencast is in the <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=extra/time-server/time-server.factor;hb=HEAD">extra/time-server</a>; the implementation is very concise and elegant.</p> <h3>Under the hood</h3> <p>All of this functionality is implemented in pure Factor code on top of our excellent <a href="http://docs.factorcode.org/content/article-alien.html">C library interface</a> and extensive bindings to POSIX and Win32 in the <a href="http://docs.factorcode.org/content/vocab-unix.html">unix</a> and <a href="http://docs.factorcode.org/content/vocab-windows.html">windows</a> vocabulary hierarchies, respectively.</p> <p>As much as possible, I/O is performed with non-blocking operations; synchronous reads and writes only suspend the current coroutine and switch to the next runnable one rather than hanging the entire VM. I recently <a href="http://factor-language.blogspot.com/2010/04/switching-call-stacks-on-different.html">rewrote the coroutines implementation to use direct context switching rather than continuations</a>.</p> <p>Co-ordination and scheduling of coroutines is handled with a series of <a href="http://factor-language.blogspot.com/2008/02/some-changes-to-threads.html">simple concurrency abstractions</a>.</p>Slava Pestovhttp://www.blogger.com/profile/02768382790667979877[email protected]5tag:blogger.com,1999:blog-17087850.post-35853000479220662652010-09-05T20:50:00.006-04:002010-09-06T02:11:35.333-04:00Making Factor's continuous build system more robust<p>I've done some work on <a href="http://concatenative.org/wiki/view/Factor/Build%20farm">Factor's continuous build system</a> over the weekend to make it more robust in the face of failure, with improved error reporting and less manual intervention required to fix problems when they come up. The current build system is called "mason", because it is based on an earlier build system named "builder" that was written by Eduardo Cavazos. Every binary package you download from <a href="http://factorcode.org">factorcode.org</a> was built, tested and benchmarked by mason.</p> <h3>Checking for disk space</h3> <p>Every once in a while build machines run out of disk space. This is a condition that Git doesn't handle very gracefully; if a git pull fails half way through, it leaves the repository in an inconsistent state. Instead of failing during source code checkout or testing, mason now checks disk usage before attempting a build. If less than 1 Gb is free, it sends out a warning e-mail and takes no further action. Disk usage is also now part of every build report; for example, take a look at the <a href="http://builds.factorcode.org/report?os=macosx&cpu=x86.32">latest Mac OS X report</a>. Finally, mason does a better job of cleaning up after itself when builds fail, reducing the rate of disk waste overall.</p> <p>I must say the disk space check was very easy to implement using Doug Coleman's excellent cross-platform <code>file-system-info</code> library. Factor's I/O libraries are top-notch and everything works as expected across all of the platforms we run on.</p> <h3>Git repository corruption</h3> <p>Git is not 100% reliable, and sometimes repositories will end up in a funny state. One cause is when the disk fills up in the middle of a pull, but it seems to happen in other cases too. For example, just a few days ago, our 64-bit Windows build machine started failing builds with the following error: <pre>From git://factorcode.org/git/factor<br /> * branch master -> FETCH_HEAD<br />Updating d386ea7..eece1e3<br /><br />error: Entry 'basis/io/sockets/windows/windows.factor' not uptodate. Cannot merge.</pre> <p>Of course nobody actually edits files in the repository in question, its a clone of the official repo that gets updated every 5 minutes. Why git messed up here I have no clue, but instead of expecting software to be perfect, we can design for failure.</p> <p>If a pull fails with a merge error, or if the working copy somehow ends up containing modified or untracked files, mason deletes the repository and clones it again from scratch, instead of just falling over and requiring manual intervention.</p> <h3>Error e-mail throttling</h3> <p>Any time mason encounters an error, such as not being able to pull from the Factor Git repository, disk space exhaustion, or intermittent network failure, it sends out an e-mail to <a href="http://sourceforge.net/mailarchive/forum.php?forum_name=factor-builds">Factor-builds</a>. Since it checks for new code every 5 minutes, this can get very annoying if there is a problem with the machine and nobody is able to fix it immediately; the <a href="http://sourceforge.net/mailarchive/forum.php?forum_name=factor-builds">Factor-builds list</a> would get spammed with hundreds of duplicate messages. Now, mason uses a heuristic to limit the number of error e-mails sent out. If two errors are sent within twenty minutes of each other, no further errors are sent for another 6 hours.</p> <h3>More robust new code check</h3> <p>Previously mason would initiate a build if a git pull had pulled in patches. This was insufficient though, because if a build was killed half way through, for example due to power failure or machine reboot, it would not re-attempt a build when it came back up until new patches were pushed. Now mason compares the latest git revision with the last one that was actually built to completion (whether or not there were errors).</p> <h3>Build system dashboard</h3> <p>I've put together <a href="http://builds.factorcode.org/dashboard">a simple dashboard page</a> showing build system status. Sometimes VMs will crash (FreeBSD is particularly flaky when running under VirtualBox, for example) and we don't always notice that a VM is down until several days after, when no builds are being uploaded. Since mason now sends out heartbeats every 5 minutes to a central server, it was easy to put together a dashboard showing which machines have not sent any heartbeats for a while. These machines are likely to be down. The dashboard also allows a build to be forced even if no new code was pushed to the repository; this is useful to test things out after changing machine configuration.</p> <p>The dashboard nicely complements my earlier work on <a href="http://factor-language.blogspot.com/2009/05/live-status-display-for-factors-build.html">live status display</a> for the build farm.</p> <h3>Conclusion</h3> <p>I think mason is one of the most advanced continuous integration systems among open source language implementations, nevermind the less mainstream ones such as Factor. And thanks to Factor's advanced libraries, it is only 1600 lines of code. Here is a selection of the functionality from Factor's standard library used by mason:</p> <ul> <li><a href="http://docs.factorcode.org/content/vocab-io.files.info.html">io.files.info</a> - checking disk usage</li> <li><a href="http://docs.factorcode.org/content/vocab-io.launcher.html">io.launcher</a> - running processes, such as git, make, zip, tar, ssh, and of course the actual Factor instance being tested</li> <li><a href="http://docs.factorcode.org/content/vocab-io.timeouts.html">io.timeouts</a> - timeouts on network operations and child processes are invaluable; Factor's consistent and widely-used timeout API makes it easy</li> <li><a href="http://docs.factorcode.org/content/vocab-http.client.html">http.client</a> - downloading boot images, making POST requests to builds.factorcode.org for the live status display feature</li> <li><a href="http://docs.factorcode.org/content/vocab-smtp.html">smtp</a> - sending build report e-mails</li> <li><a href="http://docs.factorcode.org/content/vocab-twitter.html">twitter</a> - Tweeting binary upload notifications</li><li><a href="http://docs.factorcode.org/content/vocab-oauth.html">oauth</a> - yes, Factor has a library to support the feared OAuth. Everyone complains about how hard OAuth is, but if you have easy to use libraries for HMAC, SHA1 and HTTP then it's no big deal at all.<li><a href="http://docs.factorcode.org/content/vocab-xml.syntax.html">xml.syntax</a> - constructing HTML-formatted build reports using XML literal syntax</li> </ul>Slava Pestovhttp://www.blogger.com/profile/02768382790667979877[email protected]0tag:blogger.com,1999:blog-17087850.post-58080765481282501842010-09-03T23:57:00.003-04:002010-09-04T15:06:56.438-04:00Two things every Unix developer should know<p>Unix programming can be tricky. There are many subtleties many developers are not aware of. In this post, I will describe just two of them... my favorite Unix quirks, if you will.</p> <h3>Interruptible system calls</h3> <p>On Unix, any system call which blocks can potentially fail with an errno of <code>EINTR</code>, which indicates that the caller must retry the system call. The <code>EINTR</code> error can be raised at any time for any reason, so essentially <i>every</i> I/O operation on a Unix system must be prepared to handle this error properly. Surprisingly to some, this includes the C standard library functions such as <code>fread()</code>, <code>fwrite()</code>, and so on.</p> <p>For example, if you are writing a network server, then most of the time, you want to ignore the <code>SIGPIPE</code> signal which is raised when the client closes its end of a socket. However, this ignored signal can cause some pending I/O in the server to return <code>EINTR</code>.</p> <p>A commonly-held belief is that setting the <code>SA_RESTART</code> flag with the <code>sigaction()</code> system call means that if that signal is delivered, system calls are restarted for you and <code>EINTR</code> doesn't need to be handled. Unfortunately this is not true. The reason is that certain signals are unmaskable. For instance, on Mac OS X, if your process is blocking reading on standard input, and the user suspends the program by sending it <code>SIGSTOP</code> (usually by pressing ^Z in the terminal), then upon resumption, your <code>read()</code> call will immediately fail with <code>EINTR</code>.</p> <p>Don't believe me? The Mac OS X <code>cat</code> program is not actually interrupt-safe, and has this bug. Run <code>cat</code> with no arguments in a terminal, press ^Z, then type <code>%1</code>, and you'll get an error from cat!</p> <pre>$ cat<br />^Z<br />[1]+ Stopped cat<br />$ %1<br />cat<br />cat: stdin: Interrupted system call<br />$</pre> <p>As far as I'm aware, Factor properly handles interruptible system calls, and has for a while now, thanks to <a href="http://code-factor.blogspot.com">Doug Coleman</a> explaining the issue to me 4 years ago. Not having to deal with crap like this (not to mention being able to write cross-platform code that runs on both Unix and Windows) is one of the advantages of using a high-level language like Factor or Java over C.</p> <h3>Subprocesses inherit semi-random things from the parent process</h3> <p>When you <code>fork()</code> your process, various things are copied from the parent to the child; environment variables, file descriptors, the ignored signal mask, and so on. Less obvious is the fact that <code>exec()</code> doesn't reset everything. If shared file descriptors such as stdin and stdout were set to non-blocking in the parent, the child will start with these descriptors non-blocking also, which will most likely break most programs. <a href="http://factor-language.blogspot.com/2008/07/dont-set-shared-file-descriptors-to-non.html">I've blogged about this problem before</a>.</p> <p>A similar issue is that if you elect to ignore certain signals with the <code>SIG_IGN</code> action using <code>sigaction()</code>, then subprocesses will inherit this behavior. Again, this can break processes. Until yesterday, Factor would ignore <code>SIGPIPE</code> using this mechanism, and child processes spawned with the <code>io.launcher</code> vocabulary that expected to receive <code>SIGPIPE</code> would not work properly. There are various workarounds; you can reset the signal mask before the <code>exec()</code> call, or you can do what I did in Factor, and ignore the signal by giving it an empty signal handler instead of a <code>SIG_IGN</code> action.</p>Slava Pestovhttp://www.blogger.com/profile/02768382790667979877[email protected]7tag:blogger.com,1999:blog-17087850.post-34749270883540235662010-07-28T02:36:00.002-04:002010-07-28T02:36:39.168-04:00Overhauling Factor's C library interface<p>For the last while I've been working on an overhaul of Factor's <a href="http://docs.factorcode.org/content/article-alien.html">C library interface</a> and associated compiler infrastructure. The goals of the rewrite were three-fold:</p> <ul> <li>Improving performance</li> <li>Cleaning up some crusty old code that dates back to my earliest experiments with native code generation</li> <li>Laying the groundwork for future compiler improvements</li> </ul> <p>These changes were all behind-the-scenes; I did not introduce any new functionality or language changes, and I think Factor's FFI is already quite stable and feature-complete.</p> <h3>The previous FFI implementation</h3> <p>I started work on Factor's C library interface (FFI) almost immediately after I bootstrapped the native implementation off of JFactor. I began experimenting with an SDL-based UI early on and immediately decided I wanted to have a real FFI, instead of extending the VM with primitives which wrap C functions by hand.</p> <p>Over time, both the FFI and compiler have evolved in parallel, but there was little integration between them, other than the fact that both used the same <a href="http://factor-language.blogspot.com/2009/09/survey-of-domain-specific-languages-in.html">assembler DSL</a> under the hood. The result is that FFI calls were generated in a somewhat inefficient way. Since the optimizing compiler had no knowledge of how they work, it would save all values to the data stack first. Then the FFI code generator would take over; it would pop the input parameters one by one, pass them to a per-type C function in the Factor VM which unboxed the value, then stored the value in the stack frame. Finally, the target C function would be invoked, then another C function in the VM would box the return value, and the return value would be pushed to the stack. The optimizing compiler would then take over, possibily generating code to pop the value from the stack.</p> <p>The redundant stack traffic was wasteful. In some cases, the optimizing compiler would generate code to box a value and push it to the stack, only to have the FFI then generate code to pop it and unbox it immediately after. To make matters worse, over time the optimizing compiler gained the ability to box and unbox values with open-coded assembly sequences, but the FFI would still make calls into the VM to do it.</p> <p>All in all, it was about time I rewrote the FFI, modernizing it and integrating it with the rest of the compiler in the process.</p> <h3>Factoring FFI calls into simpler instructions</h3> <p>Most <a href="http://github.com/slavapestov/factor/blob/master/basis/compiler/cfg/instructions/instructions.factor">low-level IR instructions</a> are very simple; FFI calls used to be the exception. Now I've split these up into smaller instructions. Parameters and return values are now read and written from the stack with the same <code>##peek</code> and <code>##replace</code> instructions that everything else uses, and boxing and unboxing parameters and return values is now done with the <a href="http://factor-language.blogspot.com/2010/05/collection-of-small-compiler.html">representation selection pass</a>. A couple of oddball C types, such as <code>long long</code> on x86-32, still require VM calls to box and unbox, and I added new instructions for those.</p> <p>One slightly tricky thing that came up was that I had to re-engineer low-level IR to support instructions with multiple output values. This is required for C calls which return structs and <code>long long</code> types by value, since each word-size chunk of the return value is returned in its own register, and these chunks have to be re-assembled later. In the future, I will be able to use this support to add instructions such as the x86 division instruction, which computes <code>x / y</code> and <code>x mod y</code> simultaneously.</p> <p>I also had to change low-level IR to distinguish between instructions with side effects and those without. Previously, optimization passes would assume any instruction with an output value did not have side effects, and could be deleted if the output value was not used. This is no longer true for C calls; a C function might both have a side effect and return a value.</p> <h3>GC maps</h3> <p>Now that FFI calls no longer force the optimizer to sync all live values to the data and retain stacks, it can happen that SSA values are live across an FFI call. These values get spilled to the call stack by the register allocator. Spilling to the call stack is cheaper than spilling to the data and retain stacks, because floating point values and integers do not need to be boxed, and since the spilling is done later in the optimization process, optimizations can proceed across the call site instead of being stumped by pushes and pops on either side.</p> <p>However, since FFI calls can invoke callbacks, which in turn run Factor code, which can trigger a garbage collection, the garbage collector must be able to identify spill slots in call frames which contain tagged pointers.</p> <p>The technique I'm using is to record "GC maps". This idea comes from a paper titled <a href="http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.87.71&rep=rep1&type=pdf">Compiler Support for Garbage Collection in a Statically Typed Language</a> (Factor is dynamically typed, and the paper itself doesn't have anything specific to static typing in it, so I found the title a bit odd). The Java HotSpot VM uses the same technique. The basic idea is that for every call site, you record a bitmap indicating which spill slots contain tagged pointers. This information is then stored in a per-code-block map, where the keys are return addresses and the values are these bitmaps.</p> <p>In the future, I intend on using GC maps at call sites of Factor words as well, instead of spilling temporary values to the retain stack; then I can eliminate the retain stack altogether, freeing up a register. After this is done the data stack will only be used to pass parameters between words, and not to store temporaries within a word. This will allow more values to be unboxed in more situations, and it will improve accuracy of compiler analyses.</p> <p>In fact, getting GC maps worked out was my primary motivation for this FFI rewrite; the code cleanups and performance improvements were just gravy.</p> <h3>Callback support improvements</h3> <p>Another one of those things that only makes sense when you look at how Factor evolved is that the body of an FFI callback was compiled with the non-optimizing compiler, rather than the optimizing compiler. It used to be that only certain definitions could be optimized, because static stack effects were optional and there were many common idioms which did not have static stack effects. It has been more than a year since I undertook the engineering effort to make the compiler enforce <a href="http://factor-language.blogspot.com/2009/03/better-static-safety-for-higher-order.html">static stack safety</a>, and the implementation of callbacks was the last vestigial remnant from the bad old days.</p> <p>This design made callbacks harder to debug than they should be; if you used up too many parameters, or forgot to push a return value, you'd be greeted with a runtime error instead of a compiler error. Now this has been fixed, and callbacks are fully compiled with the optimizing compiler.</p>Slava Pestovhttp://www.blogger.com/profile/02768382790667979877[email protected]1tag:blogger.com,1999:blog-17087850.post-49425850957673930472010-07-02T00:53:00.002-04:002010-07-02T00:57:54.288-04:00Factor talk in Boston, July 26thI will be presenting Factor at the Boston Lisp Users' Group on July 26th, 2010. Details in <a href="http://fare.livejournal.com/157280.html">François-René Rideau's announcement</a>. I'm also going to be giving a quick talk at the <a href="http://emerginglangs.com/">Emerging Languages Camp</a> in Portland, July 22nd. Unfortunately registration for this camp is already full.Slava Pestovhttp://www.blogger.com/profile/02768382790667979877[email protected]1tag:blogger.com,1999:blog-17087850.post-21770550530702994072010-05-29T04:46:00.019-04:002010-05-31T15:06:35.879-04:00Comparing Factor's performance against V8, LuaJIT, SBCL, and CPython<p>Together with <a href="http://useless-factor.blogspot.com">Daniel Ehrenberg</a> and <a href="http://duriansoftware.com/joe">Joe Groff</a>, I'm writing a paper about Factor for <a href="http://www.wikicfp.com/cfp/servlet/event.showcfp?eventid=9566©ownerid=3264">DLS2010</a>. We would appreciate feedback about the <a href="http://useless-factor.blogspot.com/2010/05/paper-for-dls-2010.html">draft version of the paper</a>. As part of the paper we include a performance comparison between Factor, V8, LuaJIT, SBCL, and Python. The performance comparison consists of some benchmarks from the <a href="http://shootout.alioth.debian.org/">The Computer Language Benchmarks Game</a>. I'm posting the results here first, in case there's something really stupid here.</p> <h3>Language implementations</h3> <p>Factor and V8 were built from their respective repositories. SBCL is version 1.0.38. LuaJIT is version 2.0.0beta4. CPython is version 3.1.2. All language implementations were built as 64-bit binaries and run on an 2.4 GHz Intel Core 2 Duo.</p> <h3>Benchmark implementations</h3> <p>Factor implementations of the benchmarks can be found in our source repository:</p> <ul> <li><a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=extra/benchmark/binary-trees/binary-trees.factor;hb=HEAD">binary-trees</a></li> <li><a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=extra/benchmark/fasta/fasta.factor;hb=HEAD">fasta</a></li> <li><a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=extra/benchmark/knucleotide/knucleotide.factor;hb=HEAD">knucleotide</a></li> <li><a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=extra/benchmark/nbody-simd/nbody-simd.factor;hb=HEAD">nbody</a></li> <li><a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=extra/benchmark/regex-dna/regex-dna.factor;hb=HEAD">regex-dna</a></li> <li><a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=extra/benchmark/reverse-complement/reverse-complement.factor;hb=HEAD">reverse-complement</a></li> <li><a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=extra/benchmark/spectral-norm/spectral-norm.factor;hb=HEAD">spectral-norm</a></li> </ul> <p>Implementations for the other languages can be found at the language benchmark game <a href="https://alioth.debian.org/scm/?group_id=30402">CVS repository</a>:</p><table border="3"> <tr><th></th><th>LuaJIT</th><th>SBCL</th><th>V8</th><th>CPython</th></tr> <tr><td>binary-trees </td><td><a href="https://alioth.debian.org/scm/viewvc.php/shootout/bench/binarytrees/binarytrees.lua-2.lua?view=markup&root=shootout">binarytrees.lua-2.lua</a></td><td><a href="https://alioth.debian.org/scm/viewvc.php/shootout/bench/binarytrees/binarytrees.sbcl?view=markup&root=shootout">binarytrees.sbcl</a> </td><td><a href="https://alioth.debian.org/scm/viewvc.php/shootout/bench/binarytrees/binarytrees.javascript?view=markup&root=shootout">binarytrees.javascript</a> </td><td><a href="https://alioth.debian.org/scm/viewvc.php/shootout/bench/binarytrees/binarytrees.python3-6.python3?view=markup&root=shootout">binarytrees.python3-6.python3</a> </tr> <tr><td>fasta </td><td><a href="https://alioth.debian.org/scm/viewvc.php/shootout/bench/fasta/fasta.lua?view=markup&root=shootout">fasta.lua</a> </td><td><a href="https://alioth.debian.org/scm/viewvc.php/shootout/bench/fasta/fasta.sbcl?view=markup&root=shootout">fasta.sbcl</a> </td><td><a href="https://alioth.debian.org/scm/viewvc.php/shootout/bench/fasta/fasta.javascript-2.javascript?view=markup&root=shootout">fasta.javascript-2.javascript</a> </td><td><a href="https://alioth.debian.org/scm/viewvc.php/shootout/bench/fasta/fasta.python3-2.python3?view=markup&root=shootout">fasta.python3-2.python3</a> </td></tr> <tr><td>knucleotide </td><td><a href="https://alioth.debian.org/scm/viewvc.php/shootout/bench/knucleotide/knucleotide.lua-2.lua?view=markup&root=shootout">knucleotide.lua-2.lua</a></td><td><a href="https://alioth.debian.org/scm/viewvc.php/shootout/bench/knucleotide/knucleotide.sbcl-3.sbcl?view=markup&root=shootout">knucleotide.sbcl-3.sbcl</a> </td><td><a href="https://alioth.debian.org/scm/viewvc.php/shootout/bench/knucleotide/knucleotide.javascript-3.javascript?view=markup&root=shootout">knucleotide.javascript-3.javascript</a></td><td><a href="https://alioth.debian.org/scm/viewvc.php/shootout/bench/knucleotide/knucleotide.python3-4.python3?view=markup&root=shootout">knucleotide.python3-4.python3</a> </td></tr> <tr><td>nbody </td><td><a href="https://alioth.debian.org/scm/viewvc.php/shootout/bench/nbody/nbody.lua-2.lua?view=markup&root=shootout">nbody.lua-2.lua</a> </td><td><a href="https://alioth.debian.org/scm/viewvc.php/shootout/bench/nbody/nbody.sbcl?view=markup&root=shootout">nbody.sbcl</a> </td><td><a href="https://alioth.debian.org/scm/viewvc.php/shootout/bench/nbody/nbody.javascript?view=markup&root=shootout">nbody.javascript</a> </td><td><a href="https://alioth.debian.org/scm/viewvc.php/shootout/bench/nbody/nbody.python3-4.python3 ?view=markup&root=shootout">nbody.python3-4.python3</a> </td></tr> <tr><td>regex-dna </td><td> </td><td><a href="https://alioth.debian.org/scm/viewvc.php/shootout/bench/regexdna/regexdna.sbcl-3.sbcl?view=markup&root=shootout">regexdna.sbcl-3.sbcl</a> </td><td><a href="https://alioth.debian.org/scm/viewvc.php/shootout/bench/regexdna/regexdna.javascript?view=markup&root=shootout">regexdna.javascript</a> </td><td><a href="https://alioth.debian.org/scm/viewvc.php/shootout/bench/regexdna/regexdna.python3 ?view=markup&root=shootout">regexdna.python3</a> </td></tr> <tr><td>reverse-complement </td><td><a href="https://alioth.debian.org/scm/viewvc.php/shootout/bench/revcomp/revcomp.lua?view=markup&root=shootout">revcomp.lua</a> </td><td><a href="https://alioth.debian.org/scm/viewvc.php/shootout/bench/revcomp/revcomp.sbcl?view=markup&root=shootout">revcomp.sbcl</a> </td><td><a href="https://alioth.debian.org/scm/viewvc.php/shootout/bench/revcomp/revcomp.javascript-2.javascript?view=markup&root=shootout">revcomp.javascript-2.javascript</a> </td><td><a href="https://alioth.debian.org/scm/viewvc.php/shootout/bench/revcomp/revcomp.python3-4.python3 ?view=markup&root=shootout">revcomp.python3-4.python3</a> </td></tr> <tr><td>spectral-norm </td><td><a href="https://alioth.debian.org/scm/viewvc.php/shootout/bench/spectralnorm/spectralnorm.lua?view=markup&root=shootout">spectralnorm.lua</a> </td><td><a href="https://alioth.debian.org/scm/viewvc.php/shootout/bench/spectralnorm/spectralnorm.sbcl-3.sbcl?view=markup&root=shootout">spectralnorm.sbcl-3.sbcl</a></td><td><a href="https://alioth.debian.org/scm/viewvc.php/shootout/bench/spectralnorm/spectralnorm.javascript?view=markup&root=shootout">spectralnorm.javascript</a> </td><td><a href="https://alioth.debian.org/scm/viewvc.php/shootout/bench/spectralnorm/spectralnorm.python3-5.python3?view=markup&root=shootout">spectralnorm.python3-5.python3</a></td></tr> </table><p>In order to make the reverse complement benchmark work with SBCL on Mac OS X, I had to apply this patch; I don't understand why:</p> <pre>--- bench/revcomp/revcomp.sbcl 9 Feb 2007 17:17:26 -0000 1.4<br />+++ bench/revcomp/revcomp.sbcl 29 May 2010 08:32:19 -0000<br />@@ -26,8 +26,7 @@<br /> <br /> (defun main ()<br /> (declare (optimize (speed 3) (safety 0)))<br />- (with-open-file (in "/dev/stdin" :element-type +ub+)<br />- (with-open-file (out "/dev/stdout" :element-type +ub+ :direction :output :if-exists :append)<br />+ (let ((in sb-sys:*stdin*) (out sb-sys:*stdout*))<br /> (let ((i-buf (make-array +buffer-size+ :element-type +ub+))<br /> (o-buf (make-array +buffer-size+ :element-type +ub+))<br /> (chunks nil))<br />@@ -72,4 +71,4 @@<br /> (setf start 0)<br /> (go read-chunk))))<br /> end-of-input<br />- (flush-chunks)))))))<br />+ (flush-chunks))))))<br /></pre> <h3>Running the benchmarks</h3> <p>I used Factor's deploy tool to generate minimal images for the Factor benchmarks, and then ran them from the command line:</P> <pre>./factor -e='USE: tools.deploy "benchmark.nbody-simd" deploy'<br />time benchmark.nbody-simd.app/Contents/MacOS/benchmark.nbody-simd</pre> <p>For the scripting language implementations (LuaJIT and V8) I ran the scripts from the command line:</p> <pre>time ./d8 ~/perf/shootout/bench/nbody/nbody.javascript -- 1000000<br />time ./src/luajit ~/perf/shootout/bench/nbody/nbody.lua-2.lua 1000000</pre> <p>For SBCL, I did what the shootout does, and compiled each file into a new core:</p> <pre>ln -s ~/perf/shootout/bench/nbody/nbody.sbcl .<br /><br />cat > nbody.sbcl_compile <<EOF<br />(proclaim '(optimize (speed 3) (safety 0) (debug 0) (compilation-speed 0) (space 0)))<br />(handler-bind ((sb-ext:defconstant-uneql (lambda (c) (abort c))))<br /> (load (compile-file "nbody.sbcl" )))<br />(save-lisp-and-die "nbody.core" :purify t)<br />EOF<br /><br />sbcl --userinit /dev/null --load nbody.sbcl_compile<br /><br />cat > nbody.sbcl_run <<EOF<br />(proclaim '(optimize (speed 3) (safety 0) (debug 0) (compilation-speed 0) (space 0)))<br />(main) (quit)<br />EOF<br /><br />time sbcl --dynamic-space-size 500 --noinform --core nbody.core --userinit /dev/null --load nbody.sbcl_run 1000000</pre><p>For CPython, I precompiled each script into bytecode first:</p><pre>python3.1 -OO -c "from py_compile import compile; compile('nbody.python3-4.py')"</pre> <h3>Benchmark results</h3><p>All running times are wall clock time from the Unix <code>time</code> command. I ran each benchmark 5 times and used the best result.</p><table border='3'> <tr><td> </td><th>Factor</th><th>LuaJIT</th><th>SBCL </th><th>V8 </th><th>CPython </th></tr> <tr><td>fasta </td><td>2.597s</td><td>1.689s</td><td>2.105s</td><td>3.948s </td><td>35.234s </td></tr> <tr><td>reverse-complement</td><td>2.377s</td><td>1.764s</td><td>2.955s</td><td>3.884s </td><td>1.669s </td></tr> <tr><td>nbody </td><td>0.393s</td><td>0.604s</td><td>0.402s</td><td>4.569s </td><td>37.086s </td></tr> <tr><td>binary-trees </td><td>1.764s</td><td>6.295s</td><td>1.349s</td><td>2.119s </td><td>19.886s </td></tr> <tr><td>spectral-norm </td><td>1.377s</td><td>1.358s</td><td>2.229s</td><td>12.227s</td><td>1m44.675s</td></tr> <tr><td>regex-dna </td><td>0.990s</td><td>N/A </td><td>0.973s</td><td>0.166s </td><td>0.874s </td></tr> <tr><td>knucleotide </td><td>1.820s</td><td>0.573s</td><td>0.766s</td><td>1.876s </td><td>1.805s </td></tr> </table><br /><h3>Benchmark analysis</h3> <p>Some notes on the results:</p> <ul> <li>There is no Lua implementation of the regex-dna benchmark.</li><li>Some of the SBCL benchmark implementations can make use of multiple cores if SBCL is compiled with thread support. However, by default, thread support seems to be disabled on Mac OS X. None of the other language implementations being tested have native thread support, so this is a single-core performance test.</li><li>Factor's string manipulation still needs work. The fasta, knucleotide and reverse-complement benchmarks are not as fast as they should be.</li> <li>The binary-trees benchmark is a measure of how fast objects can be allocated, and how fast the garbage collector can reclaim dead objects. LuaJIT loses big here, perhaps because it lacks generational garbage collection, and because Lua's tables are an inefficient object representation.</li> <li>The regex-dna benchmark is a measure of how efficient the regular expression implementation is in the language. V8 wins here, because it uses Google's heavily-optimized Irregexp library.</li> <li>Factor beats the other implementations on the nbody benchmark because it is able to make use of SIMD.</li> <li>For some reason SBCL is slower than the others on spectral-norm. It should be generating the same code.</li> <li>The benchmarks exercise insufficiently-many language features. Any benchmark that uses native-sized integers (for example, an implementation of the SHA1 algorithm) would shine on SBCL and suffer on all the others. Similarly, any benchmark that requires packed binary data support would shine on Factor and suffer on all the others. However, the benchmarks in the shootout mostly consist of scalar floating point code, and text manipulation only.</li></ul><h3>Conclusions</h3><p>Factor's performance is coming along nicely. I'd like to submit Factor to the computer language shootout soon. Before doing that, we need a Debian package, and the deploy tool needs to be easier to use from the command line.</p>Slava Pestovhttp://www.blogger.com/profile/02768382790667979877[email protected]16tag:blogger.com,1999:blog-17087850.post-50435528124337529252010-05-10T18:23:00.003-04:002010-05-10T18:27:55.947-04:00A collection of small compiler improvements<p>One of the big optimizations I'm planning on implementing in Factor is support for computations on machine-word-sized integers. The motivation is as follows. While code that operates on small integers (fixnums) does not currently allocate memory for intermediate values, and as a result can compile very efficiently if type checks are eliminated, sometimes fixnum precision is not quite enough. Using bignums in algorithms such as SHA1 that require 32-bit or 64-bit arithmetic incurs a big performance hit over writing the code in a <a href="http://bitbucket.org/kssreeram/clay">systems language</a>. My plan is to have the compiler support machine integers as an intermediate step between fixnums and bignums. Machine integers would be boxed and unboxed at call boundaries, but tight loops operating on them will run in registers.</p> <p>While I haven't yet implemented the above optimization, I've laid the groundwork and made it possible to represent operations on untagged integers at the level of compiler IR at least. While unboxed floats do not cause any complications for the GC, unboxed integers do, because they share a register file with tagged pointers. To make Factor's precise GC work, the compiler can now distinguish registers containing tagged pointers from those containing arbitrary integer data, by breaking down the register class of integer registers into two "representations", tagged and untagged.</p> <p>Since the above reworking touched many different parts of the compiler, I also took the time to implement some minor optimizations and clean up some older code. These improvements will be the topic of this post.</p> <p>To understand this post better, you should probably skim the list of low-level IR instructions defined in <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=basis/compiler/cfg/instructions/instructions.factor;hb=HEAD">compiler.cfg.instructions</a> first. Also, feel free to post comments here or e-mail me questions about the design of the Factor compiler.</p> <h3>Improvements to value numbering</h3> <p>In the x86 architecture, almost any instruction can take a memory operand, and memory operands take the form <code>(base + displacement * scale + offset)</code>, where <code>base</code> and <code>displacement</code> are registers, <code>offset</code> is a 32-bit signed integer, and <code>scale</code> is 1, 2, 4 or 8. While using a complex addressing mode is not any faster than writing out the arithmetic by hand with a temporary register used to store the final address, it can reduce register pressure and code size.</p> <p>The compiler now makes better use of complex addressing modes. Prior to optimization, the low level IR builder lowers specialized array access to the <code>##load-memory-imm</code> and <code>##store-memory-imm</code> instructions. For example, consider the following code:</p> <pre>USING: alien.c-types specialized-arrays ;<br />SPECIALIZED-ARRAY: float<br /><br />: make-data ( -- seq ) 10 iota [ 3.5 + sqrt ] float-array{ } map-as ;</pre> The inner loop consists of the following low level IR: <pre>##integer>float 23 14<br />##sqrt 24 23<br />##load-integer 25 2<br />##slot-imm 26 15 2 7<br />##load-integer 27 4<br />##mul 28 14 27<br />##tagged>integer 29 26<br />##add-imm 30 29 7<br />##add 31 30 28<br />##store-memory-imm 24 31 0 float-rep f<br />##load-integer 32 1<br />##add 33 14 32</pre> <p>The <code>##mul</code>, <code>##add-imm</code> and <code>##add</code> instructions compute the address input to <code>##store-memory-imm</code>. After value numbering, these three instructions have been fused with <code>##store-memory-imm</code> to form <code>##store-memory</code>:</p> <pre>##integer>float 62 57<br />##sqrt 63 62<br />##slot-imm 65 48 2 7<br />##tagged>integer 68 65<br />##store-memory 63 68 57 2 7 float-rep f<br />##add-imm 72 57 1</pre> <h3>Optimistic copy propagation</h3> <p>While the low-level optimizer's value numbering and alias analysis passes only operate on individual basic blocks (<i>local</i> optimizations), the copy propagation pass is <i>global</i>, meaning it takes the entire procedure being compiled into account.</p> <p>Eventually I will merge the local alias analysis and value numbering passes with the copy propagation pass to get an optimization known as "global value numbering with redundant load elimination". One step along this road is a rewrite that makes the copy propagation pass <i>optimistic</i>, in the following sense.</p> <p>Copy propagation concerns itself with eliminating <code>##copy</code> instructions, which correspond to assignments from one SSA value to another (new) value:</p> <pre>y = x</pre> <p>When such an instruction is processed, all usages of the value <code>y</code> can be replaced with the value <code>x</code>, in every basic block, and the definition of <code>y</code> deleted.</p> <p>A simple extension of the above treats a <code>##phi</code> instruction where all inputs are equal the same as a copy. This optimizes cases such as:</p> <pre> a = ...<br />if(...)<br />{<br /> b = a<br />}<br />c = phi(b,a)</pre> <p>The case of <code>##phi</code> instructions where one of the inputs is carried by a back edge in the control flow graph (ie, a loop) is where the optimistic assumption comes in. Consider the following code, where the definition of <code>x''</code> is carried by a back edge:</p> <pre>x' = 0<br /><br />while(...)<br />{<br /> x = phi(x',x'')<br /> x'' = ...<br />}</pre> <p>Optimistic copy propagation is able to eliminate the phi node entirely. Switching from pessimistic to optimistic copy propagation eliminated about 10% of copy instructions. While this is not a great amount, it did reduce spilling in a few subroutines I looked at, and there is no increase in code complexity from this new approach.</p> <h3>Representation selection improvements</h3> <p>The low-level IR now includes three new instructions, <code>##load-double</code>, <code>##load-float</code> and <code>##load-vector</code>. These are inteded for loading unboxed floating point values and SIMD vectors into vector registers without using an integer register to temporarily hold a pointer to the boxed value.</p> <p>Uses of these instructions are inserted by the representation selection pass. If the destination of a <code>##load-reference</code> is used in an unboxed representation, then instead of inserting a memory load following the <code>##load-reference</code>, the instruction is replaced with one of the new variants.</p> <p>Loads from constant addresses directly into floating point and vector registers are only possible on x86, and not PowerPC, so the optimization is not performed on the latter architecture. On x86-32, an immediate displacement can encode any 32-bit address. On x86-64, loading from an absolute 64-bit address requires an integer register, however instruction pointer-relative addressing is supported with a 32-bit displacement. To make use of this capability, the compiler now supports a new "binary literal area" for unboxed data that compiled code can reference. The unboxed data is placed immediately following a word's machine code, allowing RIP-relative addressing to be used on x86-64.</p> <p>This optimization, together with value numbering improvements, helps reduce pressure on integer registers in floating point loops.</p> <h3>Register allocator improvements</h3> <p>Once representation selection has run, each SSA value has an associated representation and register class. Each representation always belongs to one specific register class; a register class is a finite set of registers from which the register allocator is allowed to choose a register for this type of value.</p> <p>Before register allocation, the SSA destruction pass transforms <code>##phi</code> instructions into <code>##copy</code>s, and then subsequently performs live range coalescing to eliminate as many of the copies as possible.</p> <p>Coalescing two SSA values from different register classes does not make sense; the compiler will not be able to generate valid machine code if a single SSA value is used as both a float and an integer, since on most CPUs the integer and floating point register files are distinct.</p> <p>However, coalescing values with different representations, but the same register class, is okay. Consider the case where a double-precision float is computed, and then converted into a single precision float, and subsequently stored into memory. If the double-precision value is not used after the conversion, then the same register that held the input value can be reused to store the result of the single-precision conversion.</p> <p>Previously this pass only coalesced values with identical representations; now I've generalized it to coalescing values with the same register class but possibly different representations. This reduces register pressure and spilling.</p> <p>The tricky part about making it work is that the register allocator needs to know the value's representation, not just its register class, for generating correct spills and reloads. For example, a spilled value with register class <code>float-regs</code> can use anywhere from 4 to 16 bytes in the stack frame, depending on the specific representation: single precision float, double precision float, or SIMD vector. Clearly, if coalescing simply lost this fine-grained representation information and only retained register classes, the register allocator would not have enough information to generate valid code.</p> <p>The solution is to have live range coalescing compute the equivalence classes of SSA values without actually renaming any usages to point to the canonical representative. The renaming map is made available to the register allocator. Most places in the register allocator where instruction operands are examined make use of the renaming map.</p> <p>Until the splitting phase, these equivalence classes are in one-to-one correspondence with live intervals, and each live interval has a single machine register and spill slot. However, when spill and reload decisions are made, the register allocator uses the original SSA values to look up representations.</p> <p>If Factor's compiler did not use SSA form at all, there would still be a copy coalescing pass, and the register allocator could also support coalescing values with different representations, by first performing a dataflow analysis known as <a href="http://en.wikipedia.org/wiki/Reaching_definition">reaching definitions</a>. This would propagate representation information to use sites.</p> <p>Retaining SSA structure all the way until register allocation gives you the results of this reaching definitions analysis "for free". In both the SSA and non-SSA case, you still don't want definitions with multiple different representations reaching the same call site. The SSA equivalent of this would be a phi instruction whose inputs had different representations. The compiler ensures this doesn't happen by first splitting up the set of SSA values into strongly-connected components (SCCs) whose edges are phi nodes. The same representation is then assigned to each member of every SCC; if an agreeable representation is not found then it falls back on boxing and tagging all members.</p> <p>Notice that while both representation selection and SSA destruction group values into equivalence classes, the groupings do not correspond to each other and neither one is a refinement of the other. Not all copies resulting from phi nodes get coalesced away, so a single SCC may intersect multiple coalescing classes. This can happen if the first input to a phi node is live at the definition point of the second: <pre>b = ...<br /><br />if(...)<br />{<br /> a = ...<br /> foo(b)<br />}<br />/* 'c' can be coalesced with 'a' or 'b' -- but not both.<br />All three values belong to the same SCC and must have the same<br />representation */<br />c = phi(a,b)</pre> On the other hand, two values from different SCCs might also get coalesced together:</p> <pre>a = some-double-float<br />/* if 'a' is not live after this instruction, then 'a' and 'b'<br />can share a register. They belong to different SCCs and have<br />different representations */<br />b = double-to-single-precision-float(a)</pre> <h3>Compiler code cleanups</h3> <p>In addition to just adding new optimizations I've also simplified and refactored the internals of some of the passes I worked on.</p> <p>One big change is that the "machine representation" used at the very end of the compilation process is gone. Previously, after register allocation had run, the control flow graph would be flattened into a list of instructions with CFG edges replaced by labels and jumps. The code generator would then iterate over this flat representation and emit machine code for each virtual instruction. However since no optimization passes ran on the flat representation, it was generated and then immediately consumed. Combining the linearization pass with the code generation pass let me eliminate about a dozen IR instructions that were specific to the flat representation (anything dealing with labels and jumps to labels). It also sped up compilation time slightly.</p> <p>Value numbering's common subexpression elimination now uses a simpler and more efficient scheme for hashed lookup of expressions. New algebraic simplifications are also easier to add now, because the def-use graph is easier to traverse.</p> <p>Some instructions which could be expressed in terms of others were removed, namely the <code>##string-nth</code> and <code>##set-string-nth-fast</code> instructions. Reading and writing characters from strings can be done by combining simpler memory operations already present in the IR.</p> <p>A number of instructions were combined. All of the instructions for reading from memory in different formats, <code>##alien-unsigned-1</code>, <code>##alien-signed-4</code>, <code>##alien-double</code>, have been merged into a single <code>##load-memory</code> instruction which has a constant operands storing the C type and representation of the data being loaded. Similarly, <code>##set-alien-signed-8</code>, etc have all been merged into <code>##store-memory</code>. This restructuring allows optimization passes to more easily treat all memory accesses in a uniform way.</p> <h3>A bug fix for alias analysis</h3> <p>While working on the above I found an interesting bug in alias analysis. It would incorrectly eliminate loads and stores in certain cases. The optimizer assumed that a pointer to a freshly-allocated object could never alias a pointer read from the heap. However the problem of course is that such a pointer could be stored in another object, and then read back in.<p> <p>Here is a Factor example that demonstrates the problem. First, a couple of tuple definitions; the type declarations and initial value are important further on.</p> <pre>USING: typed locals ;<br /><br />TUPLE: inner { value initial: 5 } ;<br />TUPLE: outer { value inner } ;</pre> <p>Now, a very contrieved word which takes two instances of <code>outer</code>,<br />mutates one, and reads a value out of the other:</p> <pre>TYPED: testcase ( x: outer y: outer -- )<br /> inner new :> z ! Create a new instance of 'inner'<br /> z x value<< ! Store 'z' in 'x'<br /> y value>> :> new-z ! Load 'new-z' from 'y'<br /> 10 new-z value<< ! Store a value inside 'new-z' <br /> z value>> ! Read a value out of 'z'<br /> ;</pre> <p>Note that the initial value of the <code>value</code> slot in the <code>inner</code> tuple is 5, so <code>inner new</code> creates a new instance holding the value 5. In the case where <code>x</code> and <code>y</code> point at the same object, <code>new-z</code> and <code>z</code> will point to the same object, and the newly-allocated object's value is set to 10. However, the compiler did not realize this, and erronously constant-folded <code>z value>></code> down to 5!</p> <p>This bug never came up in actual code; the facepalm moment came while I was doing some code refactoring and noticed that the above case was not being handled.</p> <h3>Instruction scheduling to reduce register pressure</h3> <p>I'm not the only one who's been busy improving the Factor compiler. One of the <a href="http://useless-factor.blogspot.com">co-inventors of Factor</a> has cooked up an <a href="http://useless-factor.blogspot.com/2010/02/instruction-scheduling-for-register.html">instruction scheduler</a> and <a href="http://useless-factor.blogspot.com/2010/04/guarded-method-inlining-for-factor.html">improved method inlining</a>. The scheduler has been merged, and the it looks like the method inlining improvements should be ready in a few days.</p> <h3>Some benchmarks</h3> <p>Here is a comparison between the April 17th build of Factor with the latest code from the GIT repository. I ran a few of the <a href="http://shootout.alioth.debian.org/">language shootout benchmarks</a> on a 2.4 GHz MacBook Pro. Factor was built in 32-bit mode, because the new optimizations are most effective on the register-starved x86.</p> <table> <tr><th>Benchmark</th><th>Before (ms)</th><th>After (ms)</th><tr><td>nbody-simd</td><td>415</td><td>349</td></tr> <tr><td>fasta</td><td>2667</td><td>2485</td></tr> <tr><td>knucleotide</td><td>182</td><td>177</td></tr> <tr><td>regex-dna</td><td>109</td><td>86</td></tr> <tr><td>spectral-norm</td><td>1641</td><td>1347</td></tr> </table>Slava Pestovhttp://www.blogger.com/profile/02768382790667979877[email protected]2tag:blogger.com,1999:blog-17087850.post-57406824513404690202010-04-16T18:24:00.004-04:002010-04-17T01:01:56.876-04:00Factor 0.93 now available<p>After two months of development, Factor 0.93 is now available for download from the <a href="http://factorcode.org">Factor website</a>. A big thanks to all the contributors, testers and users. This release would not be possible without your help. A summary of the most important changes follows:</p><h3>Incompatible changes:</h3> <ul> <li>Factor no longer supports NetBSD, due to limitations in that operating system. (Slava Pestov)</li> <li><a href="http://docs.factorcode.org/content/article-sets.html">sets</a>, <a href="http://docs.factorcode.org/content/article-hash-sets.html">hash-sets</a>, <a href="http://docs.factorcode.org/content/article-bit-sets.html">bit-sets</a>: these vocabularies have been redesigned around a new <a href="http://useless-factor.blogspot.com/2010/03/protocol-for-sets.html">generic protocol for sets</a> (Daniel Ehrenberg) <li>Strings can no longer be used to refer to C types in FFI calls; you must use C type words instead. Also, ABI specifiers are now symbols rather than strings. So for example, the following code will no longer work: <pre>: my-callback ( -- callback ) "int" { "int" "int" } "cdecl" [ + ] alien-callback ;</pre> you must now write: <pre>: my-callback ( -- callback ) int { int int } cdecl [ + ] alien-callback ;</pre> (Joe Groff)</li> <li>The behavior of string types has changed. The <code>char*</code> C type is now just a bare pointer; to get the automatic conversion to and from Factor strings, use the <code>c-string</code> type. See <a href="http://docs.factorcode.org/content/article-c-strings.html">the documentation</a> for details. (Joe Groff)</li> <li>FFI function return values which are pointers to structs are now boxed in a struct class, instead of returning a bare alien. This means that many <code>memory>struct</code> calls can be removed. If the return value is actually a pointer to an array of structs, use specialized arrays as before. (Joe Groff)</li> <li>C-ENUM: now takes an additional parameter which is either the name of a new C type to define, or <code>f</code>. This type is aliased to <code>int</code>. (Erik Charlebois)</li> <li><a href="http://duriansoftware.com/joe/Improving-Factor's-error-messages.html">The stack checker now supports row polymorphic stack effects</a>, for improved error checking. See <a href="http://docs.factorcode.org/content/article-effects-variables.html">the documentation</a> for details. (Joe Groff)</li> </ul> <h3>New features:</h3> <ul> <li>Co-operative threads now use efficient context-switching primitives instead of copying stacks with continuations (Slava Pestov)</li> <li>Added <a href="http://docs.factorcode.org/content/word-final%2Csyntax.html">final class declarations</a>, to prohibit classes from being subclassed. This enables <a href="http://factor-language.blogspot.com/2010/02/final-classes-and-platform-specific.html">a few compiler optimizations</a> (Slava Pestov)</li> <li>Added <a href="http://factor-language.blogspot.com/2010/02/final-classes-and-platform-specific.html">platform support metadata</a> for vocabularies. Vocabulary directories can now contain a <code>platforms.txt</code> file listing operating system names which they can be loaded under. (Slava Pestov)</li> <li>The deploy tool can now deploy <a href="http://docs.factorcode.org/content/article-loading-libs.html">native libraries</a> and <a href="http://docs.factorcode.org/content/article-deploy-resources.html">resource files</a>, and supports <a href="http://docs.factorcode.org/content/article-vocabs.icons.html">custom icons</a>. (Joe Groff)</li> <li><a href="http://useless-factor.blogspot.com/2010/03/expressing-joint-behavior-of-modules.html">Joint behavior of vocabularies</a> - the new <a href="http://docs.factorcode.org/content/word-require-when%2Cvocabs.loader.html">require-when</a> word can be used to express that a vocabulary should be loaded if two existing vocabularies have been loaded (Daniel Ehrenberg)</li> <li>Callstack overflow is now caught and reported on all platforms except for Windows and OpenBSD. (Slava Pestov)</li> <li>The prettyprinter now has some limits set by default. This prevents out of memory errors when printing large structures, such as the global namespace. Use the <a href="http://docs.factorcode.org/content/word-without-limits%2Cprettyprint.config.html">without-limits</a> combinator to disable limits. (Slava Pestov)</li> <li>Added <a href="http://docs.factorcode.org/content/word-fastcall%2Calien.html">fastcall</a> and <a href="http://docs.factorcode.org/content/word-thiscall%2Calien.html">thiscall</a> ABIs on x86-32 (Joe Groff)</li> <li>The build farm's version release process is now more automated (Slava Pestov)</li> </ul> Improved libraries: <ul> <li><a href="http://docs.factorcode.org/content/vocab-delegate.html">delegate</a>: add <a href="http://docs.factorcode.org/content/word-BROADCAST__colon__%2Cdelegate.html">BROADCAST:</a> syntax, which delegates a generic word with no outputs to an array of multiple delegates. (Joe Groff)</li> <li><a href="http://docs.factorcode.org/content/vocab-game.input.html">game.input</a>: X11 support. (William Schlieper)</li> <li><a href="http://docs.factorcode.org/content/vocab-gpu.html">gpu</a>: geometry shader support. (Joe Groff)</li> <li><a href="http://docs.factorcode.org/content/vocab-opengl.gl.html">opengl</a>: OpenGL 4.0 support. (Joe Groff)</li> </ul> New libraries: <ul> <li><a href="http://docs.factorcode.org/content/vocab-bit.ly.html">bit.ly</a>: Factor interface to the <a href="http://bit.ly">bit.ly</a> URL shortening service. (Slava Pestov)</li> <li><a href="http://docs.factorcode.org/content/vocab-chipmunk.html">chipmunk</a>: binding for Chipmunk 2D physics library. (Erik Charlebois)</li> <li><a href="http://docs.factorcode.org/content/vocab-cuda.html">cuda</a>: binding to NVidia's CUDA API for GPU computing. (Doug Coleman, Joe Groff)</li> <li><a href="http://docs.factorcode.org/content/vocab-cursors.html">cursors</a>: experimental library for iterating over collections, inspired by STL iterators. (Joe Groff)</li> <li><a href="http://docs.factorcode.org/content/vocab-elf.html">elf</a>: parsing ELF binaries. The <a href="http://docs.factorcode.org/content/vocab-elf.nm.html">elf.nm</a> vocabulary is a demo which prints all symbols in an ELF binary. (Erik Charlebois)</li> <li><a href="http://docs.factorcode.org/content/vocab-images.pbm.html">images.pbm</a>, <a href="http://docs.factorcode.org/content/vocab-images.pgm.html">images.pgm</a>, <a href="http://docs.factorcode.org/content/vocab-ppm.html">images.ppm</a>: libraries for loading PBM, PGM and PPM images (Erik Charlebois)</li> <li><a href="http://docs.factorcode.org/content/vocab-macho.html">macho</a>: parsing Mach-O binaries. (Erik Charlebois)</li> <li><a href="http://docs.factorcode.org/content/vocab-opencl.html">opencl</a>: binding for the OpenCL standard interface for GPU computing. (Erik Charlebois)</li> <li><a href="http://docs.factorcode.org/content/vocab-path-finding.html">path-finding</a>: implementation of A* and BFS path finding algorithms. (Samuel Tardieu)</li> <li><a href="http://docs.factorcode.org/content/vocab-windows.ddk.html">windows.ddk</a>: binding for Windows hardware abstraction layer. (Erik Charlebois)</li> </ul>Slava Pestovhttp://www.blogger.com/profile/02768382790667979877[email protected]0tag:blogger.com,1999:blog-17087850.post-79294957129300342372010-04-08T20:16:00.002-04:002010-04-09T14:54:28.047-04:00Frame-based structured exception handling on Windows x86-64<p>Factor used to use vectored exception handlers, registered with <a href="http://msdn.microsoft.com/en-us/library/ms680588(VS.85).aspx">AddVectoredExceptionHandler</a>, however vectored handlers are somewhat problematic. A vectored handler is always called prior to any frame-based handlers, so Factor could end up reporting bogus exceptions if the FFI is used to call a library that uses SEH internally. This prompted me to switch to frame-based exception handlers. Unfortunately, these are considerably more complex to use, and the implementation differs between 32-bit and 64-bit Windows.</p> <p>I briefly discussed frame-based SEH on 32-bit Windows in my <a href="http://factor-language.blogspot.com/2010/04/switching-call-stacks-on-different.html">previous post</a>. During the switch to 64 bits, Microsoft got rid of the old frame-based SEH implementation and introduced a new, lower-overhead approach. Instead of pushing and popping exception handlers onto a linked list at runtime, the system maintains a set of function tables, where each function table stores exception handling and stack frame unwinding information.</p> <p>Normally, the 64-bit Windows function tables are written into the executable by the native compiler. However, language implementations which generate code at runtime need to be able to define new function tables dynamically. This is done with the <a href="http://msdn.microsoft.com/en-us/library/ms680588(VS.85).aspx">RtlAddFunctionTable()</a> function.</p> <p>It took me a while to figure out the correct way to call this function. I found the <a href="http://hg.openjdk.java.net/jdk7/jsn/hotspot/file/d9bc824aa078/src/os_cpu/windows_x86/vm/os_windows_x86.cpp">os_windows_x86.cpp</a> source file from Sun's HotSpot Java implementation was very helpful, and I based my code on the <code>register_code_area()</code> function from this file.</p> <p>Factor and HotSpot only use function tables in a very simple manner, to set up an exception handler. Function tables can also be used to define stack unwinding behavior; this allows debuggers to generate backtraces, and so on. Doing that is more complicated and I don't understand how it works, so I won't attempt to discuss it here.</p> <p>The <code>RtlAddFunctionTable()</code> function takes an array of <code>RUNTIME_FUNCTION</code> structures and a base address. For some unknown reason, all pointers in the structures passed to this function are 32-bit integers relative to the base address.</p> <p>For a runtime compiler that does not need to perform unwinding, it suffices to map the entire code heap to one <code>RUNTIME_FUNCTION</code>. A <code>RUNTIME_FUNCTION</code> has three fields:</p> <ul> <li><code>BeginAddress</code> - the start of the function</li> <li><code>EndAddress</code> - the end of the function</li> <li><code>UnwindData</code> - a pointer to unwind data</li> </ul> <p>All pointers are relative to the base address passed into <code>RtlAddFunctionTable()</code>. The unwind data can take various forms. For the simple case of no unwind codes and an exception handler, the following structure is used:</p> <pre>struct UNWIND_INFO {<br /> UBYTE Version:3;<br /> UBYTE Flags:5;<br /> UBYTE SizeOfProlog;<br /> UBYTE CountOfCodes;<br /> UBYTE FrameRegister:4;<br /> UBYTE FrameOffset:4;<br /> ULONG ExceptionHandler;<br /> ULONG ExceptionData[1];<br />};</pre> <p>The <code>Version</code> and <code>Flags</code> fields should be set to 1, the <code>ExceptionHandler</code> field set to a function pointer, and the rest of the fields set to 0. The exception handler pointer must be within relative to the base address, and it must also be within the memory range specified by the <code>BeginAddress</code> and <code>EndAddress</code> fields of the <code>RUNTIME_FUNCTION</code> structure. The exception handler has the same function signature as in the 32-bit SEH case:</p> <pre>LONG exception_handler(PEXCEPTION_RECORD e, void *frame, PCONTEXT c, void *dispatch)</pre> <p>In both Factor and HotSpot, the exception handler is a C function, however the <code>RtlAddFunctionTable()</code> API requires that it be within the bounds of the runtime code heap. To get around the restriction, both VMs allocate a small trampoline in the code heap which simply jumps to the exception handler, and use a pointer to the trampoline instead. Similarly, because the "pointers" in these structures are actually 32-bit integers, it helps to allocate the <code>RUNTIME_FUNCTION</code> and <code>UNWIND_INFO</code> in the code heap as well, to ensure that everything is within the same 4Gb memory range.</p><p>The above explanation probably didn't make much sense, so I invite you to check out the source code instead: <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=vm/os-windows-nt-x86.64.cpp;hb=HEAD">os-windows-nt-x86.64.cpp</a>.</p>Slava Pestovhttp://www.blogger.com/profile/02768382790667979877[email protected]2tag:blogger.com,1999:blog-17087850.post-37950581336141231712010-04-04T16:24:00.005-04:002010-04-04T16:35:09.363-04:00Switching call stacks on different platforms<p>User-space implementations of coroutines and green threads need to be able to switch the CPU's call stack pointer between different memory regions. Since this is inherently CPU- and OS-specific, I'll limit my discussion to CPUs and platforms that Factor supports.</p> <h3>System APIs for switching call stacks</h3> <ul> <li>Windows has an API for creating and switching between contexts, which it calls "fibers". The main functions to look at are <a href="http://msdn.microsoft.com/en-us/library/ms682402(VS.85).aspx">CreateFiber()</a> and <a href="http://msdn.microsoft.com/en-us/library/ms686350(VS.85).aspx">SwitchToFiber()</a>. On Windows, by far the easiest way to switch call stacks is to just use fibers.</li> <li>Most Unix systems have a set of functions operating on <code>ucontext</code>s, such as <code>makecontext()</code>, <code>swapcontext()</code> and so on. On some systems, these APIs are poorly implemented and documented.</li> <li>The C standard library functions <code>setjmp()</code> and <code>longjmp()</code> can be (ab)used to switch contexts. The <code>jmpbuf</code> structure stored to by <code>setjmp()</code> and read from by <code>longjmp()</code> contains a snapshot of all registers, including the call stack pointer. Once you've allocated your own call stack, you can capture a <code>jmp_buf</code> with <code>setjmp()</code>, change the call stack pointer, and then resume execution with <code>longjmp()</code>. As far as I know, this is completely undocumented and unsupported with every major C compiler.</li> <li>The most direct way is to write some assembly to switch the relevant registers directly. Paradoxically, this approach is the most portable out of all of these, because while it is CPU-specific, the details are pretty much the same across most OSes, Windows being the exception. Switching call stacks on Windows is a bit more involved than just changing the <code>ESP</code> register, and requires updating some Windows-specific in-memory structures. However, it is still easy enough to do directly, if you do not wish to use the fiber API. I will describe the details at the end of this post.</li> </ul> <h3>High-level libraries for switching call stacks</h3> <p>A couple of existing libraries implement high-level, cross-platform abstractions using some combination of the above mechanisms.</p> <ul> <li><a href="http://www.dekorte.com/projects/opensource/libcoroutine/">libcoroutine</a> uses fibers on Windows, and ucontext and longjmp on Unix.</li> <li><a href="http://software.schmorp.de/pkg/libcoro.html">libcoro</a> uses a handful of Unix-specific APIs if they're available, or falls back onto some hand-coded assembly routines. The latter works on Windows.</li> </ul> <h3>NetBSD/x86 limitation</h3> <p>There is no realiable way to switch call stacks on NetBSD/x86, because of a limitation in the implementation of <code>libpthread</code>. Even if your program doesn't use pthreads, it might link with a library that does, such as Glib. The pthread library replaces various C standard library functions with its own versions, and since this includes some extremely common calls such as <code>malloc()</code>, almost nothing will work as a result.</p> <p>The reason is that the NetBSD pthread library uses a silly trick to implement thread-local storage, one which unfortunately assumes that every native thread has exactly one call stack. The trick is to always allocate thread call stacks on multiples of the maximum call stack size. Then, each thread's unique ID can just be the result of masking the call stack pointer by the maximum call stack size.</p> <h3>Windows-specific concerns</h3> <p>So far, I've only worked out the details of switching call stacks on 32-bit Windows. I'll talk about 64-bit Windows in another post.</p> <p>Windows has a mechanism known as <a href="http://msdn.microsoft.com/en-us/library/ms680657(VS.85).aspx">Structured Exception Handling</a>, which is a sort of scoped exception handling mechanism with kernel support. Windows uses SEH to deliver processor traps to user-space applications (illegal memory access, division by zero, etc). Some C++ compilers also use SEH to implement C++ exceptions.</p> <p>On Windows, simply changing the <code>ESP</code> register to point at another memory region is insufficient because structured exception handling must know the current call stack's beginning and end, as well as a pointer to the innermost exception handler record.</p> <p>These three pieces of information are stored in a per-thread information block, known as the TIB. The fiber switching APIs update the TIB for you, which is why Microsoft recommends their use.</p> <h4>The TIB</h4> <p>The TIB is defined by the <code>NT_TIB</code> struct in <code>winnt.h</code>:</p> <pre>typedef struct _NT_TIB {<br /> struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList;<br /> PVOID StackBase;<br /> PVOID StackLimit;<br /> PVOID SubSystemTib;<br /> _ANONYMOUS_UNION union {<br /> PVOID FiberData;<br /> DWORD Version;<br /> } DUMMYUNIONNAME;<br /> PVOID ArbitraryUserPointer;<br /> struct _NT_TIB *Self;<br />} NT_TIB,*PNT_TIB;</pre> <p>The relevant fields that must be updated when switching call stacks are <code>ExceptionList</code>, <code>StackBase</code> and <code>StackLimit</code>.</p> Note that since the x86 call stack grows down, <code>StackLimit</code> is the start of the call stack's memory region and <code>StackBase</code> is the end.</p> <h4>Structured exception handlers</h4> <p>Structured exception handlers are stored in a linked list on the call stack, with the head of the list pointed at by the <code>ExceptionList</code> field of the TIB:</p> <pre>struct _EXCEPTION_REGISTRATION_RECORD<br />{<br /> PEXCEPTION_REGISTRATION_RECORD Next;<br /> PEXCEPTION_DISPOSITION Handler;<br />} EXCEPTION_REGISTRATION_RECORD, *PEXCEPTION_REGISTRATION_RECORD;</pre> <p>The exception list should be saved and restored when switching between call stacks, and a new call stack should begin with an empty list (<code>ExceptionList</code> set to <code>NULL</code>).</p> <p>If the <code>ExceptionList</code> field of the TIB or the <code>Next</code> field of an exception record point outside the call stack, then the handler in question will not be called at all.</p> <h4>Accessing the TIB</h4> <p>On x86-32, the current thread's TIB is stored starting at address 0 in the segment pointed at by the FS segment register. The <code>Self</code> field always points at the struct itself. Since Windows uses flat addressing, the segment storing the TIB begins somewhere in the linear 32-bit address space, so an assembly routine to fetch the TIB and return a pointer to it in EAX looks like this:</p> <pre>fetch_tib:<br /> mov eax,[fs:24]<br /> ret</pre> <p>This assembly routine can then be called from C code, which can manipulate the TIB like any other structure.</p><h4>Sample code for updating Windows-specific structures</h4><p>The <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=basis/cpu/x86/32/winnt/bootstrap.factor;hb=HEAD">basis/cpu/x86/32/winnt/bootstrap.factor</a> file defines assembly subroutines for updating the TIB when switching call stacks. These routines are invoked by the x86-32 non-optimizing compiler backend:</p> <ul> <li><a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=basis/cpu/x86/bootstrap.factor;hb=HEAD">basis/cpu/x86/bootstrap.factor</a></li> <li><a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=basis/cpu/x86/32/bootstrap.factor;hb=HEAD">basis/cpu/x86/32/bootstrap.factor</a></li></ul>Slava Pestovhttp://www.blogger.com/profile/02768382790667979877[email protected]2tag:blogger.com,1999:blog-17087850.post-65847845443026505302010-03-23T04:29:00.004-04:002010-03-23T04:41:46.088-04:00Incompatible change in Mach exception handler behavior on Mac OS X 10.6<p>On Mac OS X, Factor uses Mach exceptions rather than Unix signals to receive notifications of illegal memory accesses and arithmetic exceptions from the operating system. This is used to catch datastack underflows, division by zero, and the like. The code implementing this can be found in <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=vm/mach_signal.cpp;hb=HEAD">vm/mach_signal.c</a> in the Factor repository. This file is based on code from the <a href="http://libsigsegv.sourceforge.net/">GNU libsigsegv</a> project, with <a href="http://sourceforge.net/mailarchive/message.php?msg_name=200503102200.32002.bruno%40clisp.org">special permission</a> to use it under a BSD license instead of the restrictive GPL.</p><p>It seems that as of Mac OS X 10.6, exceptions raised by child processes are now reported to the parent if the parent has an exception handler thread. This caused a problem in Factor if a child process crashed with an access violation; Factor would think the access violation occurred in Factor code, and die with an assertion failure when attempting to map the thread ID back into a Factor VM object. It seems the simplest way is to add the following clause to the <code>catch_exception_raise()</code> function:</p><pre>if(task != mach_task_self()) return KERN_FAILURE;</pre><p>This fix should be applicable to the original <code>libsigsegv</code> code as well, along with other projects, such as CLisp, that use this library.</p>Slava Pestovhttp://www.blogger.com/profile/02768382790667979877[email protected]2tag:blogger.com,1999:blog-17087850.post-56496260094830504152010-03-11T03:16:00.005-05:002010-03-11T03:55:58.344-05:00Adding a recaptcha to the Factor pastebin<p>Lately the <a href="http://paste.factorcode.org">Factor pastebin</a> has been flooded with penis enlargement spam. The pastebin had its own very weak captcha that worked well enough for a while: there was a form field that was required to be left blank, and if it was not blank validation would fail. However, spammers have started working around it so we needed something stronger. I decided to solve the problem once and for all by integrating <a href="http://code-factor.blogspot.com">Doug Coleman</a>'s <a href="http://docs.factorcode.org/content/article-furnace.recaptcha.html">furnace.recaptcha</a> vocabulary into the pastebin. Doing this was surprisingly easy.</p><p>First, I changed the <code>USING:</code> form in <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=extra/webapps/pastebin/pastebin.factor;hb=HEAD">webapps.pastebin</a> to reference the <code>furnace.recaptcha</code> vocabulary:</p><pre>USING: namespaces assocs sorting sequences kernel accessors<br />hashtables db.types db.tuples db combinators<br />calendar calendar.format math.parser math.order syndication urls<br />xml.writer xmode.catalog validators<br />html.forms<br />html.components<br />html.templates.chloe<br />http.server<br />http.server.dispatchers<br />http.server.redirection<br />http.server.responses<br />furnace<br />furnace.actions<br />furnace.redirection<br />furnace.auth<br />furnace.auth.login<br />furnace.boilerplate<br />furnace.recaptcha<br />furnace.syndication<br />furnace.conversations ;</pre><p>Next, I changed the <code>validate-entity</code> word. This word is used to validate a form submission when adding a new paste or a new annotation. Instead of validating a captcha using the old simple scheme, it now calls <code>validate-recaptcha</code>:</p><pre>: validate-entity ( -- )<br /> {<br /> { "summary" [ v-one-line ] }<br /> { "author" [ v-one-line ] }<br /> { "mode" [ v-mode ] }<br /> { "contents" [ v-required ] }<br /> } validate-params<br /> validate-recaptcha ;</pre><p>Finally, I edited the <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=extra/webapps/pastebin/new-paste.xml;hb=HEAD">new-paste.xml</a> and <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=extra/webapps/pastebin/new-annotation.xml;hb=HEAD">new-annotation.xml</a> templates to add a recaptcha component inside the <code>t:form</code> tag:</p><pre><tr><td colspan="2"><t:recaptcha /></td></tr></pre><p>The implementation of the <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=basis/furnace/recaptcha/recaptcha.factor;hb=HEAD">furnace.recaptcha</a> vocabulary is very straightforward and takes advantage of several features of Factor's web framework. It uses <a href="http://docs.factorcode.org/content/article-http.client.html">http.client</a> to communicate with the recaptcha server, and <a href="http://docs.factorcode.org/content/article-furnace.conversations.html">furnace.conversations</a> to pass the validation error between requests. Finally, it uses the <code>CHLOE:</code> parsing word to define the <code>t:recaptcha</code> tag for use in templates:</p><pre>CHLOE: recaptcha drop [ render-recaptcha ] [xml-code] ;</pre>Slava Pestovhttp://www.blogger.com/profile/02768382790667979877[email protected]0tag:blogger.com,1999:blog-17087850.post-6462412966115457192010-02-24T02:35:00.006-05:002010-02-24T03:27:29.644-05:00Final classes and platform-specific vocabularies<h3>Final classes</h3> <p>I added final classes, in the Java sense. Attempting to inherit from a final class raises an error. To declare a class as final, suffix the definition with "final", in the same manner that words can be declared "inline" or "foldable":</p> <pre>TUPLE: point x y z ; final</pre> <p>The motivation for final classes was not obvious. There are three main reasons I added this feature.</p> <p><a href="http://factor-language.blogspot.com/2009/05/unboxed-tuple-arrays-in-factor.html">Unboxed tuple arrays</a> used to have the caveat that if you store an instance of a subclass into a tuple array of a superclass, then the slots of the subclass would be "sliced off":</p> <pre>TUPLE: point-2d x y ;<br /><br />TUPLE-ARRAY: point-2d<br /><br />TUPLE: point-3d < point-2d z ;<br /><br />SYMBOL: my-array<br /><br />1 <point-2d> my-array set<br /><br />1 2 3 point-3d boa my-array get set-first<br /><br />my-array get first .<br />=> T{ point-2d { x 1 } { y 2 } }</pre> <p>This warranted a paragraph in the documentation, and vigilance on the part of the programmer. Now, tuple arrays simply enforce that the element class is final, and if it is not, an error is raised. This removes a potential source of confusion; it is always nice when written warnings in the documentation can be replaced by language features.</p> <p>Joe Groff's <a href="http://docs.factorcode.org/content/article-typed.html">typed</a> library has a similar problem. This library has a feature where input and output parameters which are read-only tuples are passed by value to improve performance. This could cause the same "slicing problem" as above. Now, <code>typed</code> only passes final read-only tuples by value.</p> <p>Finally, there was a previous mechanism for prohibiting subclassing, but it wasn't exposed as part of the syntax. It was used by the implementation of <a href="http://docs.factorcode.org/content/article-classes.struct.html">struct classes</a> to prevent subclassing of structs. The struct class implementation now simply declares struct classes as final.</p> <h3>Typed words can now be declared foldable and flushable</h3> <p>Factor has a pretty unique feature; the user can declare words <a href="http://docs.factorcode.org/content/word-foldable%2Csyntax.html">foldable</a> (which makes them eligible for constant folding at compile time if the inputs are all literal) or <a href="http://docs.factorcode.org/content/word-flushable%2Csyntax.html">flushable</a> (which makes them eligible for dead code elimination if the output results are not used). These declarations now work with typed words.</p> <h3>Platform-specific vocabularies</h3> <p>I added a facility to declare what operating systems a vocabulary runs on. Loading a vocabulary on an unsupported platform raises an error, with a restart if you know what you're doing. The <code>load-all</code> word skips vocabularies which are not supported by the current platform.</p> <p>If a <code>platforms.txt</code> file exists in the vocabulary's directory, this file is interpreted as a newline-separated list of operating system names from <a href="http://docs.factorcode.org/content/vocab-system.html">the system vocabulary</a>. This complements the existing <a href="http://docs.factorcode.org/content/article-vocabs.metadata.html">vocabulary metadata</a> for authors, tags, and summary.</p> <p>This feature helps the build farm avoid loading code for the wrong platform. It used to be that vocabularies with the "unportable" tag set would be skipped by <code>load-all</code>, however this was too coarse-grained. For example, both the curses and DirectX bindings were tagged as unportable, and so the build farm was not loading or testing them on any platform. However, curses is available on Unix systems, and DirectX is available on Windows systems. With the new approach, there is a <code>extra/curses/platforms.txt</code> file listing <code>unix</code> as a supported platform, and the various DirectX vocabulary directories have <code>platforms.txt</code> files listing <code>windows</code>.</p>Slava Pestovhttp://www.blogger.com/profile/02768382790667979877[email protected]2tag:blogger.com,1999:blog-17087850.post-81095469370233913452010-02-16T03:47:00.003-05:002010-02-16T05:49:10.945-05:00Factor 0.92 now available<p>I'm proud to announce the release of Factor 0.92. This release comes two years after the last release, <a href="http://factor-language.blogspot.com/2007/12/factor-091-now-available.html">0.91</a>. Proceed to <a href="http://factorcode.org/">the Factor website</a> or directly to <a href="http://downloads.factorcode.org/releases/0.92/">downloads directory</a> to obtain a copy.</p> <p>More than 30 developers have contributed code to this release. I would like to thank all of the users and contributors for their efforts; Factor would not be at the stage it is today without your help.</p> <p>Since there have been so many changes since the last release, the below changelog is only an incomplete summary. In particular, there have been many incompatible syntax and core library changes since the last release.</p> <p>The next release cycle will not last as long as this one; from this point on, I'm planning on having monthly releases or so. There will be fewer incompatible language changes in the upcoming months; the core language has almost been finalized at this point.</p> <h3>Language improvements</h3> <ul> <li><a href="http://docs.factorcode.org/content/article-inference.html">Static stack effects enforced for all words</a> (Slava Pestov, Daniel Ehrenberg)</li> <li><a href="http://docs.factorcode.org/content/article-objects.html">New object system</a>: generic accessors, inheritance replaces delegation, type declarations on slots, read only slots, singletons (Slava Pestov)</li> <li><a href="http://docs.factorcode.org/content/article-dataflow-combinators.html">Data flow combinators</a> (Eduardo Cavazos)</li> <li><a href="http://docs.factorcode.org/content/article-fry.html">Partial application syntax</a> (Eduardo Cavazos)</li> <li>Integers-as-sequences is no longer supported; use the <a href="http://docs.factorcode.org/content/article-sequences-integers.html">iota</a> virtual sequence instead (Doug Coleman, Slava Pestov)</li> </ul> <h3>Notable new libraries</h3> <ul> <li><a href="http://docs.factorcode.org/content/article-db.html">db</a>: Relational database access library with SQLite and PostgreSQL backends (Doug Coleman)</li> <li><a href="http://docs.factorcode.org/content/article-furnace.html">furnace</a>: web framework that runs <a href="http://concatenative.org">concatenative.org</a> (Slava Pestov)</li> <li><a href="http://docs.factorcode.org/content/vocab-io.encodings.html">io.encodings</a>: Extensive support for I/O encodings. All operations that transform strings to bytes and vice versa now take an encoding parameter (Daniel Ehrenberg)</li> <li><a href="http://docs.factorcode.org/content/article-unicode.html">unicode</a>: Unicode-aware case conversion, character classes, collation, breaking, and normalization (Daniel Ehrenberg)</li> <li><a href="http://docs.factorcode.org/content/article-regexp.html">regexp</a>: DFA-based regular expression matching. Supports Unicode and compiles to machine code (Daniel Ehrenberg)</li> <li><a href="http://docs.factorcode.org/content/article-io.sockets.secure.html">io.sockets.secure</a>: secure sockets with OpenSSL (Slava Pestov)</li> <li><a href="http://docs.factorcode.org/content/vocab-io.files.info.html">io.files.info</a>, <a href="http://docs.factorcode.org/content/vocab-io.monitors.html">io.monitors</a>: File system metadata and change monitoring (Doug Coleman, Slava Pestov)</li> <li><a href="http://docs.factorcode.org/content/vocab-images.html">images</a>: loading and displaying BMP, TIFF, PNG, JPEG and GIF images (Doug Coleman, Marc Fauconneau)</li> <li><a href="http://docs.factorcode.org/content/article-gpu-summary.html">gpu</a>, <a href="http://docs.factorcode.org/content/vocab-game.html">game</a>: Frameworks for GPU rendering and game development using OpenGL (Joe Groff)</li> <li><a href="http://factor-language.blogspot.com/2009/09/advanced-floating-point-features.html">math.floats.env</a>: advanced IEEE floating point features (Joe Groff)</li> <li><a href="http://docs.factorcode.org/content/vocab-math.blas.html">math.blas</a>, <a href="http://docs.factorcode.org/content/article-alien.fortran.html">alien.fortran</a>: bindings to BLAS linear algebra library, built on top of a new Fortran FFI</li> <li><a href="http://docs.factorcode.org/content/article-math.vectors.simd.html">math.vectors.simd</a>: high-performance SIMD arithmetic with support for SSE versions up to 4.2 (Joe Groff)</li> <li><a href="http://docs.factorcode.org/content/article-specialized-arrays.html">specialized-arrays</a>, <a href="http://docs.factorcode.org/content/article-specialized-vectors.html">specialized-vectors</a>, <a href="http://docs.factorcode.org/content/article-classes.struct.html">classes.struct</a>: high-performance support for unboxed value types (Slava Pestov, Joe Groff)</li> </ul> <h3>Implementation improvements</h3> <ul> <li>Thanks to the continuous integration system, binary packages are now available for 13 platforms (Eduardo Cavazos, Slava Pestov)</li> <li>The compiler has been rewritten to improve compile time, performance of generated code, and correctness (<a href="http://factor-language.blogspot.com/2008/08/new-optimizer.html">1</a>, <a href="http://factor-language.blogspot.com/2008/11/new-low-level-optimizer-and-code.html">2</a>, <a href="http://factor-language.blogspot.com/2009/07/improvements-to-factors-register.html">3</a>, <a href="http://factor-language.blogspot.com/2009/07/improved-value-numbering-branch.html">4</a>, <a href="http://factor-language.blogspot.com/2009/07/dataflow-analysis-computing-dominance.html">5</a>, <a href="http://factor-language.blogspot.com/2009/08/global-float-unboxing-and-some-other.html">6</a>) (Slava Pestov)</li> <li><a href="http://factor-language.blogspot.com/2009/05/factors-implementation-of-polymorphic.html">Polymorphic inline caching</a> (Slava Pestov)</li> <li>The garbage collector has been rewritten. <a href="http://factor-language.blogspot.com/2009/10/improved-write-barriers-in-factors.html">A more precise write barrier speeds up minor collections</a>, and <a href="http://factor-language.blogspot.com/2009/11/mark-compact-garbage-collection-for.html">a mark-sweep-compact algorithm is now used for full collections</a> (Slava Pestov)</li> <li>The Factor UI now supports Unicode font rendering, and the UI developer tools have all seen significant improvements (Slava Pestov)</li> </ul> <h3>Editor integration</h3> <ul> <li><a href="http://factor-language.blogspot.com/2009/01/screencast-editing-factor-code-with.html">fuel</a>: Factor's Ultimate Emacs Library, a powerful emacs mode for editing Factor code. See <code>misc/fuel/README</code> in the Factor download for details (Jose A Ortega)</li> </ul>Slava Pestovhttp://www.blogger.com/profile/02768382790667979877[email protected]1tag:blogger.com,1999:blog-17087850.post-19546665207364344242010-01-25T14:44:00.013-05:002010-01-26T01:08:54.834-05:00Replacing GNU assembler with Factor codeLately a major goal of mine has been to get the Factor VM to be as close as possible to standard C++, and to compile it with at least one non-gcc compiler, namely Microsoft's Windows SDK.<br /><br />I've already eliminated usages of gcc-specific features from the C++ source (register variables mostly) and <a href="http://factor-language.blogspot.com/2009/12/freeing-factor-from-gccs-embrace-and.html">blogged about it</a>.<br /><br />The remaining hurdle was that several of the VM's low-level subroutines were written directly in GNU assembler, and not C++. Microsoft's toolchain uses a different assembler syntax, and I don't fancy writing the same routines twice, especially since the code in question is already x86-specific. Instead, I've decided to eliminate assembly code from the VM altogether. Factor's compiler infrastructure has perfectly good assembler DSLs for x86 and PowerPC written in Factor itself already.<br /><br />Essentially, I rewrite the GNU assembler source files in Factor itself. The individual assembler routines have been replaced by new functionality added to both the non-optimizing and optimizing compiler backends. This avoids the issue of the GNU assembler -vs- Microsoft assembler syntax entirely. Now all assembly in the implementation is consistently written in the same postfix Factor assembler syntax.<br /><br />The non-optimizing compiler already supports "sub-primitives", words whose definition consists of assembler opcodes, inlined at each call site. I added new sub-primitives to these files to replace some of the VM's assembly routines:<br /><ul> <li><a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=basis/cpu/x86/bootstrap.factor;hb=HEAD">basis/cpu/x86/bootstrap.factor</a></li> <li><a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=basis/cpu/x86/32/bootstrap.factor;hb=HEAD">basis/cpu/x86/32/bootstrap.factor</a></li> <li><a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=basis/cpu/x86/64/bootstrap.factor;hb=HEAD">basis/cpu/x86/64/bootstrap.factor</a></li> <li><a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=basis/cpu/ppc/bootstrap.factor;hb=HEAD">basis/cpu/ppc/bootstrap.factor</a></li> </ul><br /><br />A few entry points are now generated by the optimizing compiler, too. The optimizing compiler has complicated machinery for generating arbitrary machine code. I extended this with a Factor language feature similar to C's inline assembly, where Factor's assembler DSL is used to generate arbitrary assembly within a word's compiled definition. This is more flexible than the non-optimizing compiler's sub-primitives, and has wide applications beyond the immediate task of replacing a few GNU assembler routines with Factor code.<br /><br /><h3>Factor platform ABI</h3><br />Before jumping into a discussion of the various assembly routines in the VM, it is important to understand the Factor calling convention first, and how it differs from C.<br /><br />Factor's machine language calling convention in a nutshell:<br /><ul><br /><li>Two registers are reserved, for the data and retain stacks, respectively.</li><br /><li>Both registers point into an array of tagged pointers. Quotations and words pass and receive parameters as tagged pointers on the data stack.</li><br /><li>On PowerPC and x86-64, an additional register is reserved for the current VM instance.</li><br /><li>The call stack register (ESP on x86, r1 on PowerPC) is used like in C, with call frames stored in a contiguous fashion.</li><br /><li>Call frames must have a bit of metadata so that the garbage collector can mark code blocks that are referenced via return addresses. This ensures that currently-executing code is not deallocated, even if no other references to it remain.</li><br /><li>This GC meta-data consists of three things: the stack frame's size in bytes, a pointer to the start of a compiled code block, and a return address inside that code block. Since every frame records its size and the next frame immediately follows, the garbage collector can trace and update all return addresses accurately.</li><br /><li>A call frame can have arbitrary size, but the garbage collector does not inspect the additional payload; its can be any blob of binary data at all. The optimizing compiler generates large call frames in a handful of rare situations when a scratch area is needed for raw data.</li><br /><li>Quotations must be called with a pointer to the quotation object in a distinguished register (even on x86-32, where the C ABI does not use registers at all). Remaining registers do not have to be preserved, and can be used for any purpose in the compiled code.</li><br /><li>Tail calls to compiled words must load the program counter into a special register (EBX on x86-32). This allows polymorphic inline caches at tail call sites to patch the call address if the cache misses. A non-tail call PIC can look at the return address on the call stack, but for a space-saving tail-call, this is not available, so to make inline caching work in all cases, tail calls have to pass this address directly. The only compiled blocks that read the value of this register on entry are tail call PIC miss stubs.</li><br /><li>All other calls to compiled words are made without any registers having defined contents at all, so effectively all registers that are not reserved for a specific purpose are volatile.</li><br /><li>The call stack pointer must be suitably aligned so that SIMD code can spill vector data to the call frame. This is already the case in the C ABI on all platforms except non-Mac 32-bit x86.</li></ul><br /><br /><h3>Replacing the <code>c_to_factor()</code> entry point</h3><br />There are two situations where C code needs to jump into Factor; when Factor is starting up, and when C functions invoke function pointers generated by <code>alien-callback</code>.<br /><br />There used to be a <code>c_to_factor()</code> function defined in a GNU assembly source file as part of the VM, that would take care of translating from the C ABI to the Factor ABI. C++ code can call assembly routines that obey the C calling convention directly.<br /><br />Now that the special assembly entry point is gone from the VM, a valid question to ask is how is it even possible to switch ABIs and jump out into Factor-land, without stepping outside the bounds of C++, by writing some inline assembler at least. It seems like an impossible dilemma.<br /><br />The Factor VM ensures that the transition stub that C code uses to call into Factor is generated seemingly out of thin air. It turns out the only unportable C++ feature you really need when bootstrapping a JIT is the ability to cast a data pointer into a function pointer, and call the result.<br /><br />The new <code>factor_vm::c_to_factor()</code> method, called on VM startup, looks for a function pointer in a member variable named <code>factor_vm::c_to_factor_func</code>. Initially, the value is NULL, and if this is the case, it dynamically generates the entry point and then calls the brand-new function pointer:<br /><pre>void factor_vm::c_to_factor(cell quot)<br />{<br /> /* First time this is called, wrap the c-to-factor sub-primitive inside<br /> of a callback stub, which saves and restores non-volatile registers<br /> as per platform ABI conventions, so that the Factor compiler can treat<br /> all registers as volatile */<br /> if(!c_to_factor_func)<br /> {<br /> tagged<word> c_to_factor_word(special_objects[C_TO_FACTOR_WORD]);<br /> code_block *c_to_factor_block = callbacks->add(c_to_factor_word.value(),0);<br /> c_to_factor_func = (c_to_factor_func_type)c_to_factor_block->entry_point();<br /> }<br /><br /> c_to_factor_func(quot);<br />}</pre><br />All machine code generated by the Factor compiler is stored in the code heap, where blocks of code can move. But <code>c_to_factor()</code> needs a stable function pointer to make the initial jump out of C and into Factor. As I briefly mentioned in a blog post about <a href="http://factor-language.blogspot.com/2009/11/mark-compact-garbage-collection-for.html">mark sweep compact garbage collection</a>, Factor has a separate <i>callback heap</i> for allocating unmovable function pointers intended to be passed to C.<br /><br />This callback heap is used for the initial startup entry point too, as well as callbacks generated by <code>alien-callback</code>..<br /><br />As mentioned in the comment, the callback stub now takes care of saving<br />and restoring non-volatile registers, as well as aligning the stack frame. You can see how callback stubs are defined with the Factor assembler by grepping for <code>callback-stub</code> in the non-optimizing compiler backend.<br /><br />The new callback stub covers part of what the old assembly <code>c_to_factor()</code> entry point did. The remaining component is calling the quotation itself, and this is now done by a special word <code>c-to-factor</code>.<br /><br />The <code>c-to-factor</code> word loads the data stack and retain stack pointers and jumps to the quotation's compiled definition. Grep for <code>c-to-factor-impl</code> in the non-optimizing compiler backend.<br /><br />In effect, by abusing the non-optimizing compiler's support for "subprimitives", machine code for the C-to-Factor entry point can be generated by the VM itself.<br /><br />Callbacks generated by <code>alien-callback</code> in the optimizing compiler used to contain a call to the <code>c_to_factor()</code> assembly routine. The equivalent machine code is now generated directly by the optimizing compiler.<br /><br /><h3>Replacing the <code>throw_impl()</code> entry point</h3><br />When Factor code throws an error, a continuation is popped off the catch stack, and resumed. When the VM needs to throw an error, it has to go through the same motions, but perform a non-local return to unwind any C++ stack frames first, before it can jump back into Factor and resume another continuation.<br /><br />There used to be an assembly routine named <code>throw_impl()</code> which would take a quotation and a new value for the stack pointer.<br /><br />This is now handled in a similar manner to <code>c_to_factor()</code>. The <code>unwind-native-frames</code> word in <code>kernel.private</code> is another one of those very special sub-primitives that uses the C calling convention for receiving parameters. It reloads the data and retain stack registers, and changes the call stack pointer to the given parameter. The call is coming in from C++ code, and the contents of these registers are not guaranteed since they play no special role in the C ABI. Grep for <code>unwind-native-frames</code> in the non-optimizing compiler backend.<br /><br /><h3>Replacing the <code>lazy_jit_compile_impl()</code> and <code>set_callstack()</code>entry points</h3><br />As I discussed in my most recent blog post, on the <a href="http://factor-language.blogspot.com/2010/01/how-factor-implements-closures.html">implementation of closures in Factor</a>, quotation compilation is deferred, and initially all quotations point to the same shared entry point. This shared entry point used to be an assembly routine in the VM. It is now the <code>lazy-jit-compile</code> sub-primitive.<br /><br />The <code>set-callstack</code> primitive predates the existence of sub-primitives, so it was implemented as an assembly routine in the VM for historical reasons.<br /><br />These two entry points are never called directly from C++ code in the VM, so unlike <code>c_to_factor()</code> and <code>throw_impl()</code>, there is no C++ code to fish out generated code from a special word and turn it into a function pointer.<br /><br /><h3>Inline assembly with the <code>alien-assembly</code> combinator</h3><br />I added a new word, <code>alien-assembly</code>. In the same way as <code>alien-invoke</code>, it generates code which marshals Factor data into C values, and passes them according to the C calling convention; but where <code>alien-invoke</code> would generate a<br />subroutine call, <code>alien-assembly</code> just calls the quotation at<br />compile time instead, no questions asked. The quotation can emit<br />any machine code it desires, but the result has to obey the C calling<br />convention.<br /><br />Here is an example: a horribly unportable way to add two floating-point numbers that only works on x86.64:<br /><pre>: add ( a b -- c )<br /> double { double double } "cdecl"<br /> [ XMM0 XMM1 ADDPD ]<br /> alien-assembly ;<br /><br />1.5 2.0 add .<br />3.5</pre><br />The important thing is that unlike assembly code in the VM, using this feature the same assembly code will work regardless of whether the Factor VM was compiled with gcc or the Microsoft toolchain!<br /><br /><h3>Replacing FPU state entry points</h3><br />The remaining VM assembly routines were used to save and restore FPU state used for <a href="http://factor-language.blogspot.com/2009/09/advanced-floating-point-features.html">advanced floating point features</a>. The <code>math.floats.env</code> vocabulary would call these assembly routines as if they were ordinary C functions, using <code>alien-invoke</code>. After the refactoring, the optimizing compiler now generates code for these routines using <code>alien-assembly</code> instead. A hook dispatching on CPU type is used to pick the implementation for the current CPU.<br /><br />See these files for details:<br /><ul> <li><a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=basis/math/floats/env/x86/32/32.factor">basis/math/floats/env/x86/32/32.factor</a></li> <li><a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=basis/math/floats/env/x86/64/64.factor">basis/math/floats/env/x86/64/64.factor</a></li> </ul><br /><br />On PowerPC, using non-gcc compilers is not a goal, so these routines remain in the VM, and the <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=vm/cpu-ppc.S">vm/cpu-ppc.S</a> source file still exists. It contains these FPU routines along with one other entry point for flushing the instruction cache (this is a no-op on x86).<br /><br /><h3>Compiling Factor with the Windows SDK</h3><br />With this refactoring out of the way, the Factor VM can now be compiled using the Microsoft toolchain, in addition to Cygwin and Mingw. The primary benefit of using the Microsoft toolchain is that it has allowed me to revive the 64-bit Windows support.<br /><br />Last time I managed to <a href="http://factor-language.blogspot.com/2008/11/factor-port-to-win64-in-progress.html">get gcc working successfully on Win64</a>, the Factor VM was written in C. The switch to C++ killed the Win64 port, because the 64-bit Windows Mingw port is a huge pain to install correctly. I never got gcc to produce a working executable after the C++ rewrite of the VM, and as a result we haven't had a binary package on this platform since April 2009.<br /><br />Microsoft's free (as in beer) <a href="http://www.microsoft.com/downloads/details.aspx?FamilyID=c17ba869-9671-4330-a63e-1fd44e0e2505&displaylang=en">Windows SDK</a> includes command-line compilers for x86-32 and x86-64, together with various tools, such as a linker, and <code>nmake</code>, a program similar to Unix <code>make</code>. Factor now ships with an <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=Nmakefile">Nmakefile</a> that uses the SDK tools to build the VM:<br /><pre>nmake /f nmakefile</pre><br /><br />After fixing some minor compile errors, the Windows SDK produced a working Win64 executable. After updating the FFI code a little, I quickly got it passing all of the compiler tests. As a result of this work, Factor binaries for 64-bit Windows will be available again soon.Slava Pestovhttp://www.blogger.com/profile/02768382790667979877[email protected]5tag:blogger.com,1999:blog-17087850.post-7277035385176911442010-01-18T02:45:00.009-05:002010-01-18T07:08:34.824-05:00How Factor implements closuresThe recent blog post from the Clozure CL team on <a href="http://ccl.clozure.com/blog/?p=53">Clozure CL's implementation of closures</a> inspired me to do a similar writeup about Factor's implementation. It is often said that "closures are a poor man's objects", or "objects are a poor man's closures". Factor takes the former view, because as you will see they are largely implemented within the object system itself.<br /><br /><h3>Quotations</h3><br />First, let us look at quotations, and what happens internally when a quotation is called. A <a href="http://docs.factorcode.org/content/article-quotations.html">quotation</a> is a sequence of literals and words. Quotations do not close over any lexical environment; they are entirely self-contained, and their evaluation semantics only depend on their elements, not any state from the time they were constructed. So quotations are anonymous functions but not closures.<br /><br />Internally, a quotation is a pair, consisting of an array and a machine code entry point. The array stores the quotation's elements, and when you print a quotation with the prettyprinter, this is how Factor knows what its elements are: <pre>( scrachpad ) [ "out.txt" utf8 [ "Hi" print ] with-file-writer ] .<br />[ "out.txt" utf8 [ "Hi" print ] with-file-writer ]</pre><br />The machine code entry point refers to a code block in the code heap containing the quotation's compiled definition.<br /><br />The <a href="http://docs.factorcode.org/content/word-call%2Ckernel.html">call</a> generic word calls quotations. This word can take any <a href="http://docs.factorcode.org/content/word-callable%2Cquotations.html">callable</a>, not just a quotation, and has a method for each type of callable. The <code>callable</code> class includes quotations, as well as <code>curry</code> and <code>compose</code> which are discussed below. This means that closure invocation is implemented on top of method dispatch in Factor.<br /> <br />For reasons that will become clear in the last section on compiler optimizations, most quotations never have their entry points called directly, and so it would be a waste of time to compile all quotations as they are read by the parser.<br /><br />Instead, all freshly-parsed quotations have their entry points set to the <code>lazy-jit-compile</code> primitive from the <a href="http://docs.factorcode.org/content/vocab-kernel.private.html">kernel.private</a> vocabulary.<br /><br />The <code>call</code> generic word has a method on the quotation class. This method invokes the <a href="http://docs.factorcode.org/content/word-%28call%29%2Ckernel.private.html">(call)</a> primitive from the kernel.private vocabulary. The <code>(call)</code> primitive does not type check, since by the time it is called it is known that the input is in fact a quotation. This primitive has a very simple machine code definition: <pre>mov eax,[esi] ! Pop datastack<br />sub esi,4<br />jmp [eax+13] ! Jump to quotation's entry point</pre>Note that the quotation is stored in the EAX register; this is important. Recall that initially, a quotation's entry point is set to the <code>lazy-jit-compile</code> word, and that all quotations initially share this entry point.<br /><br />This word, which is not meant to be invoked directly, compiles quotations the first time they are called. Since all quotations share the same initial entry point, it needs to know which quotation invoked it. This is done by passing the quotation to this word in the EAX register. The <code>lazy-jit-compile</code> word compiles this quotation, sets its entry point to the new compiled code block, and then calls it again. On subsequent calls of the same quotation, the new compiled definition will be jumped to directly, and the <code>lazy-jit-compile</code> entry point is not involved.<br /><br />If you're interested in the definition of <code>lazy-jit-compile</code>, search for it in these files:<ul><li><a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=basis/cpu/x86/32/bootstrap.factor;hb=HEAD">basis/cpu/x86/32/bootstrap.factor</a></li><li><a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=basis/cpu/x86/64/bootstrap.factor;hb=HEAD">basis/cpu/x86/64/bootstrap.factor</a></li><li><a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=basis/cpu/ppc/bootstrap.factor;hb=HEAD">basis/cpu/ppc/bootstrap.factor</a></li></ul><br /><h3>The curry and compose words</h3> <h4>curry</h4><br />The <a href="http://docs.factorcode.org/content/word-curry%2Ckernel.html">curry</a> word is the fundamental constructor for making closures. It takes a value and a <code>callable</code>, and returns something that prints out as if it were a new quotation:<pre>( scratchpad ) 5 [ + 2 * ] curry .<br />[ 5 + 2 * ]</pre><br />This is not a quotation though, but rather an instance of the <code>curry</code> class. Instances of this class are pairs of elements: an object, and another <code>callable</code>.<br /><br />Instances of <code>curry</code> are <code>callable</code>, because the <code>call</code> generic word has a method on <code>curry</code>. This method pushes the first element of the pair on the data stack, then recursively calls <code>call</code> on the second element.<br /> <br />Calls to <code>curry</code> can be chained together: <pre>( scratchpad ) { "a" "b" "c" } 1 2 [ 3array ] curry curry map .<br />{ { "a" 1 2 } { "b" 1 2 } { "c" 1 2 } }</pre><br />Note that using <code>curry</code>, many callables can be constructed which share the same compiled definition; only the data value differes. <br /><br />Technically, <code>curry</code> is just an optimization -- it would be possible to simulate it by using sequence words to construct a new quotation with the desired value prepended, but this would be extremely inefficient. Prepending an element would take O(n) time, and furthermore, result in the new quotation being compiled the first time the result was called. Indeed, in some simpler concatenative languages such as <a href="http://www.latrobe.edu.au/philosophy/phimvt/joy.html">Joy</a>, quotations are just linked lists, and they execute in the interpreter, so partial application can be done by using the <code>cons</code> primitive for creating a new linked list.<br /><br /><h4>compose</h4><br />The <a href="http://docs.factorcode.org/content/word-compose%2Ckernel.html">compose</a> word takes two <code>callable</code>s and returns a new instance of the <code>compose</code> class. Instances of this class are pairs of <code>callable</code>s. <pre>( scratchpad ) [ 3 + ] [ sqrt ] compose .<br />[ 3 + sqrt ]</pre><br />As with <code>curry</code>, the <code>call</code> generic word has a method on the <code>compose</code> class. This method recursively applies <code>call</code> to both elements.<br /><br />It is possible to express the effect of <code>compose</code> using <code>curry</code> and <code>dip</code>: <pre>: my-compose ( quot1 quot2 -- compose )<br /> [ [ call ] dip call ] curry curry ; inline</pre><br />The main reason <code>compose</code> exists as a distinct type is to make the result prettyprint better. Were it defined as above, the result would not prettyprint in a nice way: <pre>( scratchpad ) [ 3 + ] [ sqrt ] [ [ call ] dip call ] curry curry .<br />[ [ 3 + ] [ sqrt ] [ call ] dip call ]</pre><br />The <code>curry</code> and <code>compose</code> constructors are sufficient to express all higher-level forms of closures.<br /><br /><h4>An aside: compose and dip</h4><br />It is possible to express <code>compose</code> using <code>curry</code> and <code>dip</code>. Conversely, it is also possible to express <code>dip</code> using <code>compose</code> and <code>curry</code>. <pre>: my-dip ( a quot -- ) swap [ ] curry compose call ;</pre><br />Here is an example of how this works. Suppose we have the following code: <pre>123 321 [ number>string ] my-dip</pre><br />Using the above definition of <code>my-dip</code>, we get <pre>123 321 [ number>string ] swap [ ] curry compose call ! substitute definition of 'my-dip'<br />123 [ number>string ] 321 [ ] curry compose call ! evaluate swap<br />123 [ number>string ] [ 321 ] compose call ! evaluate curry<br />123 [ number>string 321 ] call ! evaluate compose<br />123 number>string 321 ! evaluate call<br />"123" 321 ! evaluate number>string</pre><br /><h3>Fry syntax</h3><br />The <a href="http://docs.factorcode.org/content/article-fry.html">fry syntax</a> provides nicer syntax sugar for more complicated usages of <code>curry</code> and <code>compose</code>. Beginners learning Factor should start with fry syntax, and probably don't need to know about <code>curry</code> and <code>compose</code> at all; but this syntax desugars trivially into <code>curry</code> and <code>compose</code>, as explained in the documentation.<br /><br /><h3>Lexical variables</h3><br />Code written with <a href="http://docs.factorcode.org/content/article-locals.html">the locals vocabulary</a> can create closures by referencing lexical variables from nested quotations. For example, here is a word from the compiler which computes the breadth-first order on a control-flow graph: <pre>:: breadth-first-order ( cfg -- bfo )<br /> <dlist> :> work-list<br /> cfg post-order length <vector> :> accum<br /> cfg entry>> work-list push-front<br /> work-list [<br /> [ accum push ]<br /> [ dom-children work-list push-all-front ] bi<br /> ] slurp-deque<br /> accum ;</pre><br />In the body of the word, three lexical variables are used; the input parameter <code>cfg</code>, and the two local bindings <code>work-list</code> and <code>accum</code>.<br /><br />When the parser reads the above definition, it creates several quotations whose bodies reference local variables, for example, <code>[ accum push ]</code>. Before defining the new word, however, the :: parsing word rewrites this into concatenative code.<br /><br />Suppose we were doing this rewrite by hand. The first step would be to make closed-over variables explicit. We can do this by currying the values of <code>accum</code> and <code>work-list</code> onto the two quotations passed to <code>bi</code>: <pre>:: breadth-first-order ( cfg -- bfo )<br /> <dlist> :> work-list<br /> cfg post-order length <vector> :> accum<br /> cfg entry>> work-list push-front<br /> work-list [<br /> accum [ push ] curry<br /> work-list [ [ dom-children ] dip push-all-front ] curry bi<br /> ] slurp-deque<br /> accum ;</pre><br />And now, we can do the same transformation on the quotation passed to <code>slurp-deque</code>: <pre>:: breadth-first-order ( cfg -- bfo )<br /> <dlist> :> work-list<br /> cfg post-order length <vector> :> accum<br /> cfg entry>> work-list push-front<br /> work-list accum work-list [<br /> [ [ push ] curry ] dip<br /> [ [ dom-children ] dip push-all-front ] curry bi<br /> ] curry curry slurp-deque<br /> accum ;</pre><br />Note how three usages of <code>curry</code> have appeared, and all lexical variable usage now only occurs at the same level where the variable is actually defined. The final rewrite into concatenative code is trivial, and involves some tricks with <code>dup</code> and <code>dip</code>.<br /><br />A lexical closure closing over many variables will be rewritten into code which builds a long chain of <code>curry</code> instances, essentially a linked list. This is less efficient than closure representations used in Lisp implementations, where typically all closed-over values are stored in a single array. However in practice this is not usually a problem, because of optimizations outlined below.<br /><h4>Mutable local variables</h4><br />Mutable local variables are denoted by suffixing their name with <code>!</code>. Here is an example of a loop that counts a mutable variable up to 100:<pre>:: counted-loop-test ( -- )<br /> 0 :> i!<br /> 100 [ i 1 + i! ] times ;</pre><br />Factor distinguishes between binding (done with <code>:></code>) and assignment (done with <code>foo!</code> where <code>foo</code> is a local previously declared to be mutable). At a fundamental level, all bindings and closures are immutable; code using mutable locals is rewritten to close over a mutable heap-allocated box instead, and reads and writes involve an extra indirection. The previous example is roughly equivalent to the following, where we use an immutable local variable holding a mutable one-element array:<pre>:: counted-loop-test ( -- )<br /> { 0 } clone :> i<br /> 100 [ i first 1 + i set-first ] times ;</pre><br />For this reason, code that uses mutable locals does not optimize as well, and iterative loops that uses mutable local variables can run slower than tail-recursive functions which uses immutable local variables. This might be addressed in the future with improved compiler optimizations.<br /><br /><h3>Optimizations</h3> <h4>Quotation inlining</h4><br />If after inlining, the compiler sees that <code>call</code> is applied to a literal quotation, it inlines the quotation's body at the call site. This optimization works very well when quotations are used as "downward closures", and this is why most quotations never have their dynamic entry point invoked at all.<br /><br /><h4>Escape analysis</h4><br />Since <code>curry</code> and <code>compose</code> are ordinary tuple classes, any time some code constructs instances of <code>curry</code> and <code>compose</code>, and immediately unpacks them, the compiler's escape analysis pass can eliminate the allocations entirely.<br /> <br />The escape analysis pass has no special knowledge of <code>curry</code> and <code>compose</code>; it applies an optimization intended for object-oriented code.<br /> <br />Again, this helps optimize code with "downward closures", with the result that most usages of <code>curry</code> and <code>compose</code> will never allocate any memory at run time.<br /><br />For more details, see my blog post about <a href="http://factor-language.blogspot.com/2008/08/algorithm-for-escape-analysis.html">Factor's escape analysis algorithm</a>.Slava Pestovhttp://www.blogger.com/profile/02768382790667979877[email protected]2tag:blogger.com,1999:blog-17087850.post-6573779102238107922010-01-10T09:34:00.001-05:002010-01-10T09:35:50.280-05:00Factor's bootstrap process explained<h3>Separation of concerns between Factor VM and library code</h3><br />The Factor VM implements an abstract machine consisting of a data heap of objects, a code heap of machine code blocks, and a set of stacks. The VM loads an image file on startup, which becomes the data and code heap. It then begins executing code in the image, by calling a special <i>startup quotation</i>.<br /><br />When new source files are loaded into a running Factor instance by the developer, they are parsed and compiled into a collection of objects -- words, quotations, and other literals, along with executable machine code. The new data and code heaps can then be saved into another image file, for faster loading in the future.<br /><br />Factor's core data structures, object system, and source parser are implemented in Factor and live in the image, so the Factor VM does not have enough machinery to start with an empty data and code heap and parse Factor source files by itself. Instead, the VM needs to start from a data and code heap that already contains enough Factor code to parse source files. This poses a chicken-and-egg problem; how do you build a Factor system from source code? The VM can be compiled with a C++ compiler, but the result is not sufficient by itself.<br /><br />Some image-based language systems cannot generate new images from scratch at all, and the only way to create a new image is to snapshot an existing session. This is the simplest approach but it has serious downsides -- it lacks determinism and reproducability, and it is difficult to make big changes to the system.<br /><br />While Factor can snapshot the current execution state into an image, it also has a tool to generate "fresh" image from source. While this tool is written in Factor and runs inside of an existing Factor system, the resulting images depend as little as possible on the particular state of the system that generated it.<br /><br /><h3>Stage 1 bootstrap: creating a boot image</h3><br />The initial data heap comes from a <i>boot image</i>, which is built from an existing Factor system, known as the <i>host</i>. The result is a new boot image which can run in the <i>target</i> system. Boot images are created using the <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=basis/bootstrap/image/image.factor;hb=HEAD">bootstrap.image</a> tool, whose main entry point is the <a href="http://docs.factorcode.org/content/word-make-image%2Cbootstrap.image.html">make-image</a> tool. This word can be invoked from the listener in a running Factor instance: <pre>"x86.32" make-image</pre><br /><br />The <a href="http://docs.factorcode.org/content/word-make-image%2Cbootstrap.image.html">make-image</a> word parses source files using the host's parser, and the code in those source files forms the target image. This tool can be thought of as a form of cross-compiler, except boot images only contain a data heap, and not a code heap. The code heap is generated on the target by the VM, and later by the target's optimizing compiler in Factor.<br /> <br />The <a href="http://docs.factorcode.org/content/word-make-image%2Cbootstrap.image.html">make-image</a> word runs the source file <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=core/bootstrap/stage1.factor;hb=HEAD">core/bootstrap/stage1.factor</a>, which kicks off the bootstrap process.<br /><br /><h4>Building the embryo for the new image</h4><br />The global namespace is most important object stored in an image file. The global namespace contains various global variables that are used by the parser, along them the <a href="http://docs.factorcode.org/content/word-dictionary%2Cvocabs.html">dictionary</a>. The dictionary is a hashtable mapping vocabulary names to vocabulary objects. Vocabulary objects contain various bits of state, among them a hashtable mapping word names to word objects. Word objects store their definition as a quotation. The dictionary is how code is represented in memory in Factor; it is built and modified by loading source files from disk.<br /><br />One of the tasks performed by <code>stage1.factor</code> is to read the source file <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=core/bootstrap/primitives.factor;hb=HEAD">core/bootstrap/primitives.factor</a>. This source file creates a minimal global namespace and dictionary that target code can be loaded into. This initial dictionary consists of primitive words corresponding to all primitives implemented in the VM, along with some initial state for the object system, consisting of built-in classes such as <a href="http://docs.factorcode.org/content/word-array%2Carrays.html">array</a>. The code in this file runs in the host, but it constructs objects that will ultimately end up in the boot image of the target.<br /><br />A second piece of code that runs in order to prepare the environment for the target is the CPU-specific backend for the VM's non-optimizing compiler. Again, these are source files which run on the host:<br /><ul><br /><li><a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=basis/cpu/x86/bootstrap.factor;hb=HEAD">basis/cpu/x86/bootstrap.factor</a></li><br /><li><a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=basis/cpu/x86/32/bootstrap.factor;hb=HEAD">basis/cpu/x86/32/bootstrap.factor</a></li><br /><li><a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=basis/cpu/x86/64/bootstrap.factor;hb=HEAD">basis/cpu/x86/64/bootstrap.factor</a></li><br /><li><a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=basis/cpu/ppc/bootstrap.factor;hb=HEAD">basis/cpu/ppc/bootstrap.factor</a></li><br /></ul><br />The non-optimizing compiler does little more than glue chunks of machine code together, so the backends are relatively simple and consist of several dozen short machine code definitions. These machine code chunks are stored as byte arrays, constructed by Factor's x86 and PowerPC assemblers.<br /><br /><h4>Loading core source files</h4><br />Once the initial global environment consisting of primitives and built-in classes has been prepared, source files comprising the core library are loaded in. From this point on, code read from disk does not run in the host, only in the target. The host's parser is still being used, though.<br /><br />Factor's vocabulary system loads dependencies automatically, so <code>stage1.factor</code> simply calls <a href="http://docs.factorcode.org/content/word-require%2Cvocabs.loader.html">require</a> on a few essential vocabularies which end up pulling in everything in the <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=tree;f=core;hb=HEAD">core</a> <a href="http://docs.factorcode.org/content/article-vocabs.roots.html">vocabulary root</a>.<br /><br />During normal operation, any source code at the top level of a source file, not in any definition, is run when the source file is loaded. During stage1 bootstrap, top-level forms from source files in <code>core</code> are not run on the host. Instead, they need to be run on the target, when the VM is is launched with the new boot image.<br /><br />After loading all source files from <code>core</code>, this startup quotation is constructed. The startup quotation begins by calling top-level forms in core source files in the order in which they were loaded, and then runs <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=basis/bootstrap/stage2.factor;hb=HEAD">basis/bootstrap/stage2.factor</a>.<br /><br /><h4>Serializing the result</h4><br />At this point, stage1 bootstrap has constructed a new global namespace consisting of a dictionary, object system meta-data, and other objects, together with a startup quotation which can kick off the next stage of bootstrap.<br /><br />Data heap objects that the VM needs to know about, such as the global namespace, startup quotation, and non-optimizing compiler definitions, are stored in an array of "special objects". Entries are defined in <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=vm/objects.hpp;hb=HEAD">vm/objects.hpp</a>, and in the image file they are stored in the image header.<br /><br />This object graph, rooted at the special objects array, is now serialized to disk into an image file. The bootstrap image generator serializes objects in the same format in which they are stored in the VM's heap, but it does this without dumping VM's memory directly. This allows object layouts to be changed relatively easily, by first updating the bootstrap image tool, generating an image with the new layouts, then updating the VM and running the new VM with the new image.<br /><br />The bootstrap image generator also takes care to write the resulting data with the correct cell size and endianness. Along with containing CPU-specific machine code templates for the non-optimizing compiler, this is what makes boot images platform-specific.<br /><br /><h3>Stage 2 bootstrap: fleshing out a development image</h3><br />At this point, the host system has writen a boot image file to disk, and the next stage of bootstrap can begin. This stage runs on the target, and is initiated by starting the Factor VM with the new boot image: <pre>./factor -i=boot.x86.32.image</pre> The VM reads the new image into an empty data heap. At this point, it also notices that the boot image does not have a code heap, so it cannot start executing the boot quotation just yet.<br /><br /><h4>Early initialization</h4><br />Boot images have a special flag set in them which kicks off the "early init" process in the VM. This only takes a few seconds, and entails compiling all words in the image with the non-optimizing compiler. Once this is done, the VM can call the startup quotation. Quotations are also compiled by the non-optimizing compiler the first time they are called.<br /><br />This startup quotation was constructed during stage1 bootstrap. It runs top-level forms in core source files, then runs <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=basis/bootstrap/stage2.factor;hb=HEAD">basis/bootstrap/stage2.factor</a>.<br /><br /><h4>Loading major subsystems</h4><br />Whereas the goal of stage1 bootstrap is to generate a minimal image that contains barely enough code to be able to load additional source files, stage2 creates a usable development image containing the optimizing compiler, documentation, UI tools, and everything else that makes it into a recognizable Factor system.<br /><br />The major vocabularies loaded by stage2 bootstrap include: <ul> <li><a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=basis/bootstrap/compiler/compiler.factor;hb=HEAD">Optimizing compiler</a> -- after the optimizing compiler has been loaded, all words in the image are compiled again. This is the longest part of stage2 bootstrap, taking several minutes.</li> <li><a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=basis/bootstrap/help/help.factor;hb=HEAD">Help system</a></li> <li><a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=basis/bootstrap/tools/tools.factor;hb=HEAD">Developer tools</a></li> <li><a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=basis/bootstrap/ui/ui.factor;hb=HEAD">UI framework</a></li> <li><a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=basis/bootstrap/ui/tools/tools.factor;hb=HEAD">UI tools</a></li> </ul><br /><h4>Finishing up</h4><br />The last step taken by stage2 bootstrap is to install a new startup quotation. This startup quotation does the usual command-line processing; if no switches are specified, it starts the UI listener, otherwise it runs a source file or vocabulary given on the command line.<br /><br />Once the new startup quotation has been installed, the current session is saved to a new image file using the <a href="http://docs.factorcode.org/content/word-save-image-and-exit%2Cmemory.html">save-image-and-exit</a> word.Slava Pestovhttp://www.blogger.com/profile/02768382790667979877[email protected]0tag:blogger.com,1999:blog-17087850.post-10721600699464479752009-12-27T06:59:00.001-05:002009-12-27T07:04:36.373-05:00Freeing Factor from gcc's embrace-and-extend C language extensionsI have completed a refactoring of the Factor VM, eliminating usage of gcc-specific language extensions, namely <a href="http://gcc.gnu.org/onlinedocs/gcc/Global-Reg-Vars.html">global register variables</a>, and on x86-32, the <a href="http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html">regparm calling convention</a>. My motivation for this is two-fold.<br /><br />First of all, I'd like to have the option of compiling the Factor VM with compilers other than gcc, such as Visual Studio. While gcc runs on all of Factor's supported platforms and then some, setting up a build environment using the GNU toolchain on Windows takes a bit of work, especially on 64-bit Windows. Visual Studio will provide an easier alternative for people who wish to build Factor from source on that platform. In the future, I'd also like to try using <a href="http://clang.llvm.org/">Clang</a> to build Factor.<br /><br />The second reason is that the global register variable extension is poorly implemented in gcc. Anyone who has followed Factor development for a while will know that gcc bugs come up pretty frequently, and most of these seem to be related to global register variables. This is quite simply one of the less well-tested corners of gcc, and the gcc developers seem to mostly ignore optimizer bugs triggered by this feature.<br /><br />The Factor VM used a pair of register variables to hold data stack and retain stack pointers. These are just ordinary fields in a struct now. Back in the days when Factor was interpreted and the interpreter was part of the VM, a lot of time was spent executing code within the VM itself, and keeping these pointers in registers was important. Nowadays the Factor implementation compiles to machine code even during interactive use, using a pair of compilers called the non-optimizing compiler and optimizing compiler. Code generated by Factor's compilers tends to dominate the performance profile, rather than code in the VM itself. Compiled code can utilize registers in any matter desired, and so it continues to access the data stack and retain stack through registers. To make it work with the modified VM, the compiler generates code for saving and restoring these registers in the VM's <code>context</code> structure before and after calls into the VM.<br /><br />A few functions defined in the VM used gcc's <code>regparm</code> calling convention. Normally, on x86-32, function parameters are always passed in an array on the call stack in the <code>esp</code> register; <code>regparm</code> functions instead pass the first 1, 2 or 3 arguments in registers. Whether or not this results in a performance boost is debatable, but my motivation for using this feature was not performance but perceived simplicity. The optimizing compiler would generate calls to these functions, and the machine code generated is a little simpler if it can simply stick parameters in registers instead of storing them on the call stack. Eliminating <code>regparm</code> did not in reality make anything more complex, as only a few locations were affected and the issue was limited to the x86-32 backend only.<br /><br />I'm pretty happy with how the refactoring turned out. It did not seem to affect performance at all, which was not too surprising, since code generated by Factor's optimizing compiler did not change, and the additional overhead surrounding Factor to C calls is lost in the noise.<br /><br />My goal of getting Factor to build with other compilers is not quite achieved yet, however. While the gcc extensions are gone from C++ code, the VM still has some 800 lines of assembly source using GNU assembler syntax, in the files <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=vm/cpu-x86.32.S;hb=HEAD">cpu-x86.32.S</a>, <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=vm/cpu-x86.64.S;hb=HEAD">cpu-x86.64.S</a>, and <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=vm/cpu-ppc.S;hb=HEAD">cpu-ppc.S</a>. This code includes fundamental facilities which cannot be implemented in C++, such as the main C to Factor call gate, the low-level <a href="http://docs.factorcode.org/content/word-set-callstack%2Ckernel.html">set-callstack</a> primitive used by the <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=core/continuations/continuations.factor;hb=HEAD">continuations implementation</a>, and a few other things. The assembly source also has a few auxilliary CPU-dependent functions, for example for saving and restoring <a href="http://factor-language.blogspot.com/2009/09/advanced-floating-point-features.html">FPU flags</a>, and detecting the SSE version on x86.<br /><br />I plan on elimiating the assembly from the VM entirely, by having the Factor compiler generate this code instead. The non-optimizing compiler can generate things such as the C to Factor call gate. For the the remaining assembly routines, such as FPU feature access and SSE version detection, I plan on adding an "inline assembly" facility to Factor itself, much like gcc's <code>asm</code> statement. The result will be that the VM will be a pure C++ codebase, and the machine code generation will be entirely offloaded into Factor code, where it belongs. Factor's assembler DSL is much nicer to use than GNU assembler.Slava Pestovhttp://www.blogger.com/profile/02768382790667979877[email protected]5tag:blogger.com,1999:blog-17087850.post-14504016633533613032009-12-06T02:00:00.001-05:002009-12-06T02:27:53.835-05:00Reducing image size by eliminating literal tables from code heap entries<h3>Introduction</h3> The compiled code heap consists of code blocks which reference other code blocks and objects in the data heap. For example, consider the following word:<pre>: hello ( -- ) "Hello world" print ;</pre><br />The machine code for this word is the following:<br /><pre>000000010ef55f10: 48b87b3fa40d01000000 mov rax, 0x10da43f7b<br />000000010ef55f1a: 4983c608 add r14, 0x8<br />000000010ef55f1e: 498906 mov [r14], rax<br />000000010ef55f21: 48bb305ff50e01000000 mov rbx, 0x10ef55f30 (hello+0x20)<br />000000010ef55f2b: e9e0ffabff jmp 0x10ea15f10 (print)<br /></pre><br />The immediate operand of the first <code>mov</code> instruction (<code>0x10da43f7b</code>) is the address of the string <code>"Hello world"</code> in the data heap. The immediate operand of the last <code>jmp</code> instruction (<code>0x10ea15f10</code>) is the address of the machine code of the <code>print</code> word in the code heap.<br />Unlike some dynamic language JITs where all references to data and compiled code from machine code are done via indirection tables, Factor embeds the actual addresses of the data in the code. This means that the garbage collector needs to be able to find all pointers in a code heap block (for the "sweep" phase of garbage collection), and update them (for the "compact" phase). <br /><h3>Relocation tables</h3> Associated to each code block is a relocation table, which tells the VM what instruction operands contain special values that it must be aware of. The relocation table is a list of triples, packed into a byte array: <ul> <li>The <i>relocation type</i> is an instance of the <code>relocation_type</code> enum in <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=vm/instruction_operands.hpp;hb=HEAD">instruction_operands.hpp</a>. This value tells the VM what kind of value to deposit in the operand -- possibilities include a data heap pointer, the machine code of a word, and so on.</li> <li>The <i>relocation class</i> is an instance of the <code>relocation_class</code> enum in <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=vm/instruction_operands.hpp;hb=HEAD">instruction_operands.hpp</a>. This value tells the VM how the operand is represented -- the instruction format, whether or not it is a relative address, and such.</li> <li>The <i>relocation offset</i> is a byte offset from the start of the code block where the value is to be stored.</li> </ul> Code that needs to inspect relocation table entries uses the <code>each_instruction_operand()</code> method defined in <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=vm/code_blocks.hpp;hb=HEAD">code_block.hpp</a>. This is a template method which can accept any object overloading <code>operator()</code>.<br /><h3>Literal tables</h3> The next part is what I just finished refactoring. I'll describe the old approach first. The simplest way, and what Factor used until now, is the following. Relocation table entries that expect a parameter, such as those that deposit addresses from the data heap and code heap, take the parameter from a literal table associated to each code block. When the compiler compiles a word, it spits out some machine code and a literal table. It hands these off to the Factor VM. The "sweep" phase of the garbage collector traces each code block's literal table, and the "compact" phase, after updating the literal table, stores the operands back in each instruction referenced from the relocation table.<br /><h3>Eliminating literal tables</h3> The problem with the old approach is that the garbage collector doesn't really need the literal table. The address of each data heap object and code block referenced from machine code is already stored in the machine code itself. Indeed, the only thing missing until now was a way to read instruction operands out of instructions. With this in place, code blocks no longer had to hold on to the literal table after being constructed. Each code block's literal table is only used to deposit the initial values into machine code when a code block is first compiled by the compiler. Subsequently, the literal table becomes garbage and is collected by the garbage collector. When tracing code blocks, the garbage collector traverses the instruction operands themselves, using the relocation table alone. In addition to the space savings gained by not keeping these arrays of literals around, another interesting side-effect of this refactoring is that a full garbage collection no longer resets generic word call sites back to the cold call entry point, which would effectively discard all inline caches (read about <a href="http://factor-language.blogspot.com/2009/05/factors-implementation-of-polymorphic.html">inline caching in Factor</a>).<br /><h3>Coping with code redefinition</h3> A call to a word is compiled as a direct jump in Factor. This means that if a word is redefined and recompiled, existing call sites need to be updated to point to the new definition. The implementation of this is slightly more subtle now that literal tables are gone. Every code block in the code heap has a reference to an <code>owner</code> object in its header (see the <code>code_block</code> struct in <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=vm/code_blocks.cpp;hb=HEAD">code_blocks.cpp</a>). The owner is either a word or a quotation. Words and quotations, in turn, have a field which references a compiled code heap block. The latter always points at the most recent compiled definition of that object (note that quotations are only ever compiled once, because they are immutable. Words, however, can be redefined by reloading source files). At the end of each compilation unit, the code heap is traversed, and each code block is updated in turn. The code block's relocation table is consulted, and instruction operands which reference compiled code heap blocks are updated. Before this would be done by overwriting all call targets from the literal table. Now, this is accomplished by looking at the owner object of the current target, and then storing the owner's most recent code block back in the instruction operand. This is implemented in the <code>update_word_references()</code> method defined in <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=vm/code_blocks.cpp;hb=HEAD">code_blocks.cpp</a>. In addition to helping with redefinition, the owner object reference is used to construct call stack traces.<br /><h3>Additional space savings in deployed binaries</h3> Normally, every compiled code block references its owner object, so that code redefinition can work. This means that if a word or quotation is live, then the code block corresponding to its most recent definition will be live, and vice versa. In deployed images where the compiler and debugger have been stripped out, words cannot be redefined and stack traces are not needed, so the owner field can be stripped out. This means that a word object can get garbage collected at deploy time even if its compiled code block is called. As it turns out, most words are never used as objects, and can be stripped out in this manner. So the literal table removal has an even bigger effect in deployed images than development images.<br /><h3>Size comparison</h3> The following table shows image sizes before and after this change on Mac OS X x86-64. <table> <tr><th>Image</th><th>Before (Megabytes)</th><th>After (Megabytes)</th></tr> <tr><td>Development image</td><td>92 Mb</td><td>90 Mb</td></tr> <tr><td>Minimal development image</td><td>8.6 Mb</td><td>8.2 Mb</td></tr> <tr><td>Deployed <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=tree;f=extra/hello-ui;hb=HEAD">hello-ui</a></td><td>2.3 Mb</td><td>1.5 Mb</td></tr> <tr><td>Deployed <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=tree;f=extra/bunny;hb=HEAD">bunny</a></td><td>3.5 Mb</td><td>3.1 Mb</td></tr> </table>Slava Pestovhttp://www.blogger.com/profile/02768382790667979877[email protected]3tag:blogger.com,1999:blog-17087850.post-85012924109148218732009-11-16T03:24:00.006-05:002009-11-16T13:30:06.482-05:00Mark-compact garbage collection for oldest generation, and other improvementsFactor now uses a mark-sweep-compact garbage collector for the oldest generation (known as tenured space), in place of the copying collector that it used before. This reduces memory usage. The mark-sweep collector used for the code heap has also been improved. It now shares code with the data heap collector and uses the same compaction algorithm. <br /><br /><h3>Mark-sweep-compact garbage collection for tenured space</h3> <h4>Mark phase</h4> During the mark phase, the garbage collector computes the set of reachable objects by starting from the "roots"; global variables, and objects referenced from runtime stacks. When an object is visited, the mark phase checks if the object has already been marked. If it hasn't yet been marked, it is marked, and any object that it refers to are then also visited in turn. If an object has already been marked, nothing is done. As described above, the algorithm is recursive, which is problematic. There are two approaches to turn it into an iterative algorithm; either objects yet to be visited are pushed on a mark stack, and a top-level loop drains this stack, or a more complicated scheme known as "pointer inversion" is used. I decided against pointer inversion, since the mark stack approach is simpler and I have yet to observe the mark stack grow beyond 128Kb or so anyway. I use an <code>std::vector</code> for the mark stack and it works well enough. The mark stack and the loop that drains it are implemented in <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=vm/full_collector.cpp;hb=HEAD">full_collector.cpp</a>. There are several approaches to representing the set of objects which are currently marked, also. The two most common are storing a mark bit for each object in the object's header, and storing mark bits in a bitmap off to the side. I chose the latter approach, since it speeds up the sweep and compact phases of the garbage collector, and doesn't require changing object header layout. Each bit in the mark bitmap corresponds to 16 bytes of heap space. For reasons that will become clear, when an object is marked mark bitmap, every bit corresponding to space taken up by the object is marked, not just the first bit. The mark bitmap is implemented in <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=vm/mark_bits.hpp;hb=HEAD">mark_bits.hpp</a>. <h4>Sweep phase</h4> Once the mark phase is complete, the mark bitmap now has an up-to-date picture of what regions in the heap correspond to reachable objects, and which are free. The sweep phase begins by clearing the free list, and then computes a new one by traversing the mark bitmap. For every contiguous range of clear bits, a new entry is added to the free list. This is why I use a mark bitmap instead of mark bits in object headers; if I had used mark bits in headers, then the sweep phase would need to traverse the entire heap, not just the mark bitmap, which has significantly worse locality. The sweep phase is implemented by the <code>free_list_allocator::sweep()</code> method in <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=vm/free_list_allocator.hpp;hb=HEAD">free_list_allocator.hpp</a>. The key to an efficient implementation of the sweep algorithm is the <code>log2()</code> function in <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=vm/bitwise_hacks.hpp;hb=HEAD">bitwise_hacks.hpp</a>; it is used to find the first set bit in a cell. I use the <code>BSR</code> instruction on x86 and <code>cntlzw</code> on PowerPC. The sweep phase also needs to update the object start offset map used for <a href="http://factor-language.blogspot.com/2009/10/improved-write-barriers-in-factors.html">the card marking write barrier</a>. When collecting a young generation, the garbage collector scans the set of marked cards. It needs to know where the first object in each card is, so that it can properly identify pointers. This information is maintained by the <code>object_start_map</code> class defined in <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=vm/object_start_map.cpp;hb=HEAD">object_start_map.cpp</a>. If an object that happens to be the first object in a cardwas deallocated by the sweep phase, the object start map must be updated to point at a subsequent object in that card. This is done by the <code>object_start_map::update_for_sweep()</code> method. <h4>Compact phase</h4> The compact phase is optional; it only runs if the garbage collector determines that there is too much fragmentation, or if the user explicitly requests it. The compact phase does not require that the sweep phase has been run, only the mark phase. Like the sweep phase, the compact phase relies on the mark bitmap having been computed. Whereas the sweep phase identifies free blocks and adds them to the free list, the compact phase slides allocated space up so that all free space ends up at the end of the heap, in a single block. The compact phase has two steps. The first step computes a forwarding map; a data structure that can tell you the final destination of every heap block. It is easy to see that the final destination of every block can be determined from the number of set bits in the mark bitmap that precede it. Since the forwarding map is consulted frequently -- once for every pointer in every object that is live during compaction -- it is important that lookups are as fast as possible. The forwarding map should also be space-efficient. This completely rules out using a hashtable (with many small objects in the heap, it would grow to be almost as big as the heap itself) or simply scanning the bitmap and counting bits every time (since now compaction will become an <code>O(n^2)</code> algorithm). The correct solution is very simple, and well-known in language implementation circles, but I wasn't aware of it until I studied the <a href="http://ccl.clozure.com/manual/chapter16.4.html">Clozure Common Lisp garbage collector</a>. You count the bits set in every group of 32 (or 64) bits in the mark bitmap, building an array of cumulative sums as you go. Then, to count the number of bits that are set up to a given element, you look up the pre-computed population count for the nearest 32 (or 64) bit boundary, and manually compute the population count for the rest. This gives you a forwarding map with <code>O(1)</code> lookup time. This algorithm relies on a fast population count algorithm; I used the standard CPU-independent technique in the <code>popcount()</code> function of <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=vm/bitwise_hacks.hpp;hb=HEAD">bitwise_hacks.hpp</a>. Nowadys, as of SSE 4.2, x86 CPUs even include a <code>POPCNT</code> instruction, but since compaction spends most of its time in <code>memmove()</code>, I didn't investigate if this would offer a speedup. It would require a CPUID check at runtime and the fallback would still need to be there for pre-Intel i7 CPUs and PowerPC, so it didn't seem worth the extra complexity to me. Once the forwarding map has been computed, objects can be moved up and pointers that they contain updated in one pass. A mark-compact cycle takes roughly twice as long as a mark-sweep, which is why I elected not to perform a mark-compact on every full collection. The latter leads to a simpler implementation (no sweep phase, and no free list; allocation in tenured space is done by bumping a pointer just as with a copying collector) however the performance penalty didn't seem worth the minor code size reduction to me. <br /><br /><h3>Code heap compaction</h3> Code heap compaction has been in Factor for a while, in the form of the <a href="http://docs.factorcode.org/content/word-save-image-and-exit,memory.html">save-image-and-exit</a> word. This used an inefficient multi-pass algorithm (known as the "LISP-2 compaction algorithm") however since it only ran right before exiting Factor, it didn't matter too much. Now, code heap compaction can happen at any time as a result of heap fragmentation, and uses the same efficient algorithm as tenured space compaction. Compaction moves code around, and doing this at a time other than right before the VM exiting creates a few complications: <ul> <li>Return addresses in the callstack need to be updated.</li> <li>If an inline cache misses, a new inline cache is compiled, and the call site for the old cache is patched. Inline cache construction allocates memory and can trigger a GC, which may in turn trigger a code heap compaction; if this occurs, the return address passed into the inline cache miss stub may have moved, and the code to patch the call site needs to be aware of this.</li> <li>If a Factor callback is passed into C code, then moving code around in the code heap may invalidate the callback, and the garbage collector has no way to update any function pointers that C libraries might be referencing.</li> </ul> The solution to the first problem was straightforward and involved adding some additional code to the code heap compaction pass. The second problem is trickier. I added a new <code>code_root</code> smart pointer class, defined in <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=vm/code_roots.hpp;hb=HEAD">code_roots.hpp</a>, to complement the <code>data_root</code> smart pointer (see my blog post about <a href="http://factor-language.blogspot.com/2009/05/factor-vm-ported-to-c.html">moving the VM to C++</a> for details about that). The inline cache compiler wraps the return address in a <code>code_root</code> to ensure that if the code block that contains it is moved by any allocations, the return address can be updated properly. I solved the last problem with a layer of indirection (that's how all problems are solved in CS, right?). Callbacks are still allocated in the code heap, but the function pointer passed to C is actually stored in a separate "callback heap" where every block consists of a single jump instruction and nothing else. When a code heap compaction occurs, code blocks in the code heap might be moved around, and all jumps in the callback heap are updated. Blocks within the callback heap are never moved around (or even deallocated, since that isn't safe either). <br /><br /><h3>New object alignment and tagging scheme</h3> The last major change I wanted to discuss is that objects are now aligned on 16-byte boundaries, rather than 8-byte boundaries. This wastes more memory, but I've already offset most of the increase with some space optimizations, with more to follow. There are several benefits to this new system. First of all, the binary payload of byte array objects now begins on a 16-byte boundary, which allows SIMD intrinsics to use aligned access instructions, which are much faster. Second, it simplifies the machine code for inline caches and megamorphic method dispatch. Since the low 4 bits of every pointer are now clear, this allows all built-in VM types to fit in the pointer tag itself, so the logic to get a class descriptor for an object in a dispatch stub is very simple now; here is pseudo-code for the assembly that gets generated: <pre>cell tag = ptr & 15; if(tag == tuple_tag) tag = ptr[cell - tuple_tag]; ... dispatch on tag ...</pre>Slava Pestovhttp://www.blogger.com/profile/02768382790667979877[email protected]3tag:blogger.com,1999:blog-17087850.post-56759668091024465072009-10-15T07:16:00.009-04:002009-10-16T00:13:07.306-04:00Improved write barriers in Factor's garbage collectorThe Factor compiler has been evolving very quickly lately; it has been almost completely rewritten several times in the last couple of years. The garbage collector, on the other hand, hasn't seen nearly as much action. The last time I did any work on it was <a href="http://factor-language.blogspot.com/2008/05/garbage-collection-throughput.html">May 2008</a>, and before that, <a href="http://factor-language.blogspot.com/2007/05/garbage-collector-improvements.html">May 2007</a>. Now more than a year later, I've devoted a week or so to working on it. The result is a cleaner implementation, and improved performance.<br /><br /><h3>Code restructuring</h3>I re-organized the garbage collector code to be more extensible and easier to maintain. I did this by splitting off a bunch of the garbage collector methods from the <code>factor_vm</code> class into their own set of classes. I made extensive use of template metaprogramming to help structure code in a natural way. Many people dislike C++, primarily because of templates, but I don't feel that way at all. Templates are my favorite C++ feature, and if it wasn't for templates C++ would just be a shitty object-oriented dialect of C.<br /><br />First up is the <code>collector</code> template class, defined in <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=vm/collector.hpp;hb=HEAD">collector.hpp</a>:<pre>template<typename TargetGeneration, typename Policy> struct collector</pre>This class has two template parameters:<ul><li><code>TargetGeneration</code> - this is the generation that the collector will be copying objects to. A generation is any class that implements the <code>allot()</code> method.</li><li><code>Policy</code> - this is a class that simulates a higher-order function. It implements a <code>should_copy_p()</code> method that tells the collector if a given object should be promoted to the target generation, or left alone.</li></ul>On its own, the collector class can't do any garbage collection itself; it just implements methods which trace GC roots: <code>trace_contexts()</code> (traces active stacks), <code>trace_roots()</code> (traces global roots), and <code>trace_handle()</code> (traces one pointer).<br /><br />Next up is the <code>copying_collector</code> template class, defined in <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=vm/copying_collector.hpp;hb=HEAD">copying_collector.hpp</a>:<pre>template<typename TargetGeneration, typename Policy> struct copying_collector</pre>This class has the same two template parameters as <code>collector</code>; the target generation must define one additional method, <code>next_object_after()</code>. This is used when scanning newly copied objects. This class implements logic for scanning marked cards, as well as Cheney's algorithm for copying garbage collection.<br /><br />Then, there are four subclasses implementing each type of GC pass:<ul><li><code>nursery_collector</code> - copies live objects from the nursery into aging space, defined in <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=vm/nursery_collector.hpp;hb=HEAD">nursery_collector.hpp</a> and <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=vm/nursery_collector.cpp;hb=HEAD">nursery_collector.cpp</a></li><li><code>aging_collector</code> - copies live objects from the first aging semi-space to the second aging semi-space, defined in <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=vm/aging_collector.hpp;hb=HEAD">aging_collector.hpp</a> and <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=vm/aging_collector.cpp;hb=HEAD">aging_collector.cpp</a><br /></li><li><code>to_tenured_collector</code> - copies live objects from aging space into tenured space, defined in <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=vm/to_tenured_collector.hpp;hb=HEAD">to_tenured_collector.hpp</a> and <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=vm/to_tenured_collector.cpp;hb=HEAD">to_tenured_collector.cpp</a><br /></code></li><li><code>full_collector</code> - copies live objects from the first tenured semi-space to the second tenured semi-space, defined in <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=vm/nursery_collector.hpp;hb=HEAD">nursery_collector.hpp</a> and <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=vm/nursery_collector.cpp;hb=HEAD">nursery_collector.cpp</a></li></ul>Each class subclasses <code>copying_collector</code> and specializes the two template arguments. For example, let's take a look at the declaration of the nursery collector:<pre>struct nursery_collector : copying_collector<aging_space,nursery_policy></pre>This subclass specializes its superclass to copy objects to tenured space, using the following policy class:<br /><pre>struct nursery_policy {<br /> factor_vm *myvm;<br /><br /> nursery_policy(factor_vm *myvm_) : myvm(myvm_) {}<br /><br /> bool should_copy_p(object *untagged)<br /> {<br /> return myvm->nursery.contains_p(untagged);<br /> }<br />};</pre><br />That is, any object that is in the nursery will be copied to aging space by the <code>nursery_collector</code>. Other collector subclasses are similar.<br /> <br />This code all uses templates, rather than virtual methods, so every collector pass will have a specialized code path generated for it. This gives higher performance, with cleaner code, than was is possible in C. The old garbage collector was a tangle of conditionals, C functions, and global state.<br /><br /><h3>Partial array card marking</h3>When a pointer to an object in the nursery is stored into a container in aging or tenured space, the container object must be added to a "remembered set" so that on the next minor collection, so that it can be scanned, and its elements considered as GC roots.<br /><h4>Old way</h4>Storing a pointer into an object marks the card containing the header of the object. On a minor GC, all marked cards are be scanned; if a marked card was bound, then every object whose header is contained in this card would be scanned.<br /><h4>Problem</h4>Storing a pointer into an array would necessitate the array to be scanned in its entirety on the next minor collection. This is bad if the array is large. Consider an algorithm that stores successive elements into an array on every iteration, and also performs enough work per iteration to trigger a nursery collection. Now every nursery collection -- and hence every iteration of the loop -- is scanning the entire array. We're doing a quadratic amount of work for what should be a linear-time algorithm.<br /><h4>New way</h4>Storing a pointer into an object now marks the card containing the slot that was mutated. On a minor GC, all marked cards are scanned. Every object in every marked card is inspected, but only the subrange of slots that fit inside the card are scanned. This greatly reduces the burden placed on the GC from mutation of large arrays. The implementation is tricky. I need to spend some time thinking about and simplifying the code, as it stands the card scanning routine has three nested loops, and two usages of goto!<br /><h4>Implementation</h4><a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=vm/copying_collector.hpp;hb=HEAD">copying_collector.hpp</a>, <code>trace_cards()</code><br /><br /><h3>New object promotion policy</h3>When aging space is being collected, objects contained in marked cards in tenured space must be traced.<br /><h4>Old way</h4>These cards would be scanned, but could not be unmarked, since the objects they refer to were copied to the other aging semi-space, and would need to be traced on the next aging collection.<br /><h4>The problem</h4>The old way was bad because these cards would remain marked for a long time, and would be re-scanned on every collection. Furthermore the objects they reference would likely live on for a long time, since they're referenced from a tenured object, and would needlessly bounce back and forth between the two aging semi-spaces.<br /><h4>New way</h4>Instead, an aging collection proceeds in two phases: the first phase promotes aging space objects referenced from tenured space to tenured space, unmarking all marked cards. The second phase copies all reachable objects from aging to second aging semi-space. This promotes objects likely to live for a long time all the way to tenured space, and scans less cards on an aging collection since more cards can get unmarked.<br /><h4>Implementation</h4><a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=vm/aging_collector.cpp;hb=HEAD">aging_collector.cpp</a><br /><br /><h3>Faster code heap remembered set</h3>If a code block references objects in the nursery, the code block needs to be updated after a nursery collection. This is because the machine code of compiled words directly refers to objects; there's no indirection through a literal table at runtime. This improves performance but increases garbage collector complexity.<br /><h4>Old way</h4>When a new code block was allocated, a global flag would be set. A flag would also be set in the code block's header. On the next nursery collection, the entire code heap would be scanned, and any code blocks with this flag set in them would have their literals traced by the garbage collector.<br /><h4>New way</h4>The problem with the old approach is that adding a single code block entails a traversal of the entire code heap on the next minor GC, which is bad for cache. While most code does not allocate in the code heap, the one major exception is the compiler itself. When loading source code, a significant portion of running time was spent scanning the code heap during minor collections. Now, the list of code blocks containing literals which refer to the nursery and aging spaces are stored in a pair of STL sets. On a nursery or aging collection, these sets are traversed and the code blocks they contain are traced. These sets are typically very small, and in fact empty most of the time.<br /><h4>Implementation</h4><a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=vm/code_heap.cpp;hb=HEAD">code_heap.cpp</a>, <code>write_barrier()</code><br /><a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=vm/copying_collector.hpp;hb=HEAD">copying_collector.hpp</a>, <code>trace_code_heap_roots()</code><br /><br /><h3>Faster card marking and object allocation</h3>The compiler's code generator now generates tighter code for common GC-related operations too. A write barrier looks like this in pseudo-C:<pre>cards[(obj - data_heap_start) >> card_bits] = card_mark_mask;</pre>Writing out the pointer arithmetic by hand, we get:<pre>*(cards + (obj - data_heap_start) >> card_bits) = card_mark_mask;</pre>Re-arranging some operations,<pre>*(obj >> card_bits + (cards - data_heap_start >> card_bits) = card_mark_mask;</pre>Now, the entire expression <pre>(cards - data_heap_start >> card_bits)</pre> is a constant. Factor stores this in a VM-global variable, named <code>cards_offset</code>. The value used to be loaded from the global variable every time a write barrier would execute. Now, its value is inlined directly into machine code. This requires code heap updates if the data heap grows, since then either the data heap start or the card array base pointer might change. However, the upside is that it eliminates several instructions from the write barrier. Here is a sample generated write barrier sequence; only 5 instructions on x86.32:<pre>0x08e3ae84: lea (%edx,%eax,1),%ebp<br />0x08e3ae87: shr $0x8,%ebp<br />0x08e3ae8a: movb $0xc0,0x20a08000(%ebp)<br />0x08e3ae91: shr $0xa,%ebp<br />0x08e3ae94: movb $0xc0,0x8de784(%ebp)</pre><br />Object allocations had a slight inefficiency; the code generated to compute the effective address of the nursery allocation pointer did too much arithmetic. Adding support to the VM for embedding offsets of VM global variables directly into machine code saved one instruction from every object allocation. Here is some generated machine code to box a floating point number; only 6 instructions on x86.32 (of course Factor does <a href="http://factor-language.blogspot.com/2009/08/global-float-unboxing-and-some-other.html">float unboxing</a> to make your code even faster):<pre>0x08664a33: mov $0x802604,%ecx<br />0x08664a38: mov (%ecx),%eax<br />0x08664a3a: movl $0x18,(%eax)<br />0x08664a40: or $0x3,%eax<br />0x08664a43: addl $0x10,(%ecx)<br />0x08664a46: movsd %xmm0,0x5(%eax)</pre><h4>Implementation</h4><a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=basis/cpu/x86/x86.factor;hb=HEAD">cpu/x86/x86.factor</a>: <code>%write-barrier</code>, <code>%allot</code><br /><a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=basis/cpu/ppc/ppc.factor;hb=HEAD">cpu/ppc/ppc.factor</a>: <code>%write-barrier</code>, <code>%allot</code><br /><br /><h3>Performance comparison</h3><br />I compared the performance of a Mac OS X x86-32 build from October 5th, with the latest sources as of today.<br /><br />Bootstrap time saw a nice boost, going from 363 seconds, down to 332 seconds.<br /><br />The time to load and compile all libraries in the source tree (<code>load-all</code>) was reduced from 537 seconds to 426 seconds.<br /><br />Here is a microbenchmark demonstrating the faster card marking in a very dramatic way:<br /><pre>: bench ( -- seq ) 10000000 [ >bignum 1 + ] map ;</pre><br />The best time out of 5 iterations on the old build was 12.9 seconds. Now, it has gone down to 1.9 seconds.Slava Pestovhttp://www.blogger.com/profile/02768382790667979877[email protected]3tag:blogger.com,1999:blog-17087850.post-25790549264412200992009-09-30T23:52:00.007-04:002009-10-01T17:13:37.022-04:00A survey of domain-specific languages in FactorFactor has good support for implementing mini-languages as libraries. In this post I'll describe the general techniques and look at some specific examples. I don't claim any of this is original research -- Lisp and Forth programmers have been doing DSLs for decades, and recently the Ruby, Haskell and even Java communities are discovering some of these concepts and adding a few of their own to the mix. However, I believe Factor brings some interesting incremental improvements to the table, and the specific combination of capabilities found in Factor is unique.<br /><br /><h3>Preliminaries</h3><br />How does one embed a mini-language in Factor? Let us look at what goes on when a source file is parsed:<br /><ul><li>The parser reads the input program. This consists of definitions and top-level forms. The parser constructs syntax trees, and adds definitions to the dictionary. The result of parsing is the set of top-level forms in the file.</li><li>The compiler is run with all changed definitions. The compiler essentially takes syntax trees as input, and produces machine code as output. Once the compiler finishes compiling the new definitions, they are added to the VM's code heap and may be executed.</li><li>The top level forms in the file are run, if any.</li></ul><br />In Factor, all of these stages are extensible. Note that all of this happens when the source file is loaded into memory -- Factor is biased towards compile-time meta-programming.<br /><br /><h4>Extending the parser with parsing words</h4><br />Parsing words which execute at parse time can be defined. Parsing words can take over the parser entirely and parse custom syntax. All of Factor's standard syntax, such as <a href="http://docs.factorcode.org/content/word-__colon__,syntax.html">:</a> for defining words and <a href="[">[</a> for reading a quotation, is actually parsing words defined in the <a href="http://docs.factorcode.org/content/vocab-syntax.html">syntax</a> vocabulary. Commonly-used libraries such as <a href="http://docs.factorcode.org/content/vocab-memoize.html">memoize</a> and <a href="http://docs.factorcode.org/content/vocab-specialized-arrays.html">specialized-arrays</a> add their own parsing words for custom definitions and data types. These don't qualify as domain-specific languages since they're too trivial, but they're very useful.<br /><br /><h4>Homoiconic syntax</h4><br />In most mainstream languages, the data types found in the syntax tree are quite different from the data types you operate on at runtime. For example, consider the following Java program:<br /><pre>if(x < 3) { return x + 3; } else { return foo(x); }</pre><br />This might parse into something like<br /><pre>IfNode(<br /> condition: LessThan(Identifier(x),Integer(3))<br /> trueBranch: ReturnNode(Add(Identifier(x),Integer(3)))<br /> falseBranch: ReturnNode(MethodCall(foo,Identifier(x)))<br />)</pre><br />You cannot work with an "if node" in your program, the identifier <code>x</code> does not exist at runtime, and so on.<br /><br />In Factor and Lisp, the parser constructs objects such as strings, numbers and lists directly, and identifiers (known as symbols in Lisp, and words in Factor) are first-class types. Consider the following Factor code,<br /><pre>x 3 < [ x 3 + ] [ x foo ] if</pre><br />This parses as a quotation of 6 elements,<br /><ul><li>The word <code>x</code></li><li>The integer 3</li><li>The word <code><</code><li>A quotation with three elements, <code>x</code>, <code>3</code>, and <code>+</code></li><li>A quotation with two elements, <code>x</code>, and <code>foo</code></li><li>The word <code>if</code></li></ul><br />An array literal, like <code>{ 1 2 3 }</code>, parses as an array of three integers; not an AST node representing an array with three child AST nodes representing integers.<br /><br />The flipside of homoiconic syntax is being able to print out (almost) any data in a form that can be parsed back in; in Factor, the <a href="http://docs.factorcode.org/content/word-.,prettyprint.html">.</a> word does this.<br /><br />What homoiconic syntax gives you as a library author is the ability to write mini-languages without writing a parser. Since you can input almost any data type wth literal syntax, you can program your mini-language in parse trees directly. The mini-language can process nested arrays, quotations, tuples and words instead of parsing a string representation first.<br /></li><br /><br /><h4>Compile-time macros</h4><br />All of Factor's data types, including quotations and words, can be constructed at runtime. Factor also supports <a href="http://docs.factorcode.org/content/vocab-macros.html">compile-time macros</a> in the Lisp sense, but unlike Lisp where they are used to prevent evaluation, a Factor macro is called just like an ordinary word, except the parameters have to be compile-time literals. The macro evaluates to a quotation, and the quotation is compiled in place of the macro call.<br /><br />Everything that can be done with macros can also be done by constructing quotations at runtime and using <a href="http://docs.factorcode.org/content/word-call(,syntax.html">call(</a> -- macros just provide a speed boost.<br /><br /><h3>Parsing word-based DSLs</h3><br />Many DSLs have a parsing word as their main entry point. The parsing word either takes over the parser completely to parse custom syntax, or it defines some new words and then lets the main parser take over again.<br /><br /><h4>Local variables</h4><br />A common misconception among people taking a casual look at Factor is that it doesn't offer any form of lexical scoping or named values at all. For example, Reg Braithwaite authoritatively states on <a href="http://weblog.raganwald.com/2008/03/tool-time.html">his weblog</a>:<br /><blockquote><i>the Factor programming language imposes a single, constraining set of rules on the programmer: programmers switching to Factor must relinquish their local variables to gain Factor’s higher-order programming power.</i></blockquote><br />In fact, Factor supports lexically scoped local variables via the <a href="http://docs.factorcode.org/content/vocab-locals.html">locals</a> vocabulary. and this library is used throughout the codebase. It looks like in a default image, about 1% of all words use lexical variables.<br /><br />The locals vocabulary implements a set of parsing words which augment the standard defining words. For example, <a href="http://docs.factorcode.org/content/word-__colon____colon__,locals.html">::</a> reads a word definition where input arguments are stored in local variables, instead of being on the stack:<br /><pre>:: lerp ( a b c -- d )<br /> a c *<br /> b 1 c - *<br /> + ;</pre><br />The locals vocabulary also supports "let" statements, lambdas with full lexical closure semantics, and mutable variables. The locals vocabulary compiles lexical variable usage down to stack shuffling, and <a href="http://docs.factorcode.org/content/word-curry,kernel.html">curry</a> calls (for constructing quotations that close over a variable). This makes it quite efficient, especially since in many cases the Factor compiler can eliminate the closure construction using escape analysis. The choice of whether or not to use locals is one that can be made purely on a stylistic basis, since it has very little effect on performance.<br /><br /><h4>Parsing expression grammars</h4><br /><a href="http://en.wikipedia.org/wiki/Parsing_expression_grammar">Parsing expression grammars</a> describe a certain class of languages, as well as a formalism for parsing these languages.<br /><br /><a href="http://bluishcoder.co.nz">Chris Double</a> implemented a PEG library in Factor. The <a href="http://docs.factorcode.org/content/vocab-peg.html">peg</a> vocabulary offers a combinator-style interface for constructing parsers, and <a href="http://www.bluishcoder.co.nz/2008/04/factor-parsing-dsl.html">peg.ebnf</a> builds on top of this and defines a declarative syntax for specifying parsers.<br /><br />A simple example of a PEG grammar can be found in Chris Double's <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=tree;f=extra/peg/pl0;hb=HEAD">peg.pl0</a> vocabulary. More elaborate examples can be found in <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=tree;f=extra/peg/javascript;hb=HEAD">peg.javascript</a> (JavaScript parser by Chris Double) and <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=tree;f=extra/smalltalk;hb=HEAD">smalltalk.parser</a> (Smalltalk parser by me).<br /><br />One downside of PEGs is that they have some performance problems; the standard formulation has exponential runtime in the worst case, and the "Packrat" variant that Factor uses runs in linear time but also linear space. For heavy-duty parsing, it appears as if LL and LR parsers are best, and it would be nice if Factor had an implementation of such a parser generator.<br /><br />However, PEGs are still useful for simple parsing tasks and prototyping. They are used throughout the Factor codebase for many things, including but not limited to:<br /><ul><li>Parsing regular expressions in the <a href="http://docs.factorcode.org/content/vocab-regexp.html">regexp</a> vocabulary (<a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=tree;f=basis/regexp/parser;hb=HEAD">source</a>)</li><li>Parsing URLs in the <a href="http://docs.factorcode.org/content/vocab-urls.html">urls</a> vocabulary (<a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=tree;f=basis/urls;hb=HEAD">source</a>)</li><li>Parsing HTTP headers in the <a href="http://docs.factorcode.org/content/vocab-http.server.html">http.server</a> and <a href="http://docs.factorcode.org/content/vocab-http.client.html">http.client</a> vocabularies (<a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=tree;f=basis/http/parsers;hb=HEAD">source</a>)</li></ul><br />PEGs can also be used in conjunction with parsing words to embed source code written with custom grammars in Factor source files directly. The next DSL is an example of that.<br /><br /><h4>Infix expressions</h4><br />Philipp Brüschweiler's <a href="http://docs.factorcode.org/content/vocab-infix.html">infix</a> vocabulary defines a parsing word which parses an infix math expression using PEGs. The result is then compiled down to <a href="http://docs.factorcode.org/content/vocab-locals.html">locals</a>, which in turn compile down to stack code.<br /><br />Here is a word which solves a quadratic equation <code>ax^2 + bx + c = 0</code> using the quadratic formula. Infix expressions can only return one value, so this word computes the first root only:<br /><pre>USING: infix locals ;<br /><br />:: solve-quadratic ( a b c -- r )<br /> [infix (-b + sqrt(b*b-4*a*c))/2*a infix] ;</pre><br />Note that we're using two mini-languages here; <code>::</code> begins a definition with named parameters stored in local variables, and <code>[infix</code> parses an infix expression which can access these local variables.<br /><br /><h4>XML literals</h4><br /><a href="http://useless-factor.blogspot.com">Daniel Ehrenberg's</a> XML library defines a convenient syntax for constructing XML documents. Dan describes it in detail in <a href="http://useless-factor.blogspot.com/2009/01/factor-supports-xml-literal-syntax.html">a blog post</a>, with plenty of examples, so I won't repeat it here. The neat thing here is that by adding a pair of parsing words, <code>[XML</code> and <code><XML</code>, he was able to integrate XML snippets into Factor, with parse-time well-formedness checking, no less.<br /><br /><a href="http://docs.factorcode.org/content/vocab-xml.html">Dan's XML library</a> is now used throughout the Factor codebase, particularly in the web framework, for both parsing and generating XML. For example, the <a href="http://concatenative.org">concatenative.org wiki</a> uses a markup language called "farkup". The <a href="http://concatenative.org/wiki/view/Farkup">farkup</a> markup language, developed by Dan, <a href="http://code-factor.blogspot.com">Doug Coleman</a> and myself, makes heavy use of XML literals. Farkup is implemented by first parsing the markup into an abstract syntax tree, and then converting this to HTML using a recursive tree walk that builds XML literals. We avoid constructing XML and HTML through raw string concatenation; instead we use XML literals everywhere now. This results in <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=tree;f=basis/farkup;hb=HEAD">cleaner, more secure code</a>.<br /><br />Compare the design of our farkup library with <a href="http://code.unicoders.org/browser/django/trunk/contrib/markdown.py">markdown.py</a> used by <a href="http://reddit.com">reddit.com</a>. The latter is implemented with a series of regular expression hacks and lots of ad-hoc string processing which attempts to produce something resembling HTML in the end. New markup injection attacks are found all the time; there was a particularly clever one involving a JavaScript worm that knocked reddit right down a few days ago. I don't claim that Farkup is 100% secure by any means, and certainly it has had an order of magnitude less testing, but without a doubt centralizing XHTML generation makes it much easier to audit and identify potential injection problems.<br /><br /><h4>C library interface</h4><br />Our C library interface (or FFI) was quite low-level initially but after a ton of work by Alex Chapman, Joe Groff, and myself, it has quite a DSLish flavor. C library bindings resemble C header files on mushrooms. Here is a taste:<br /><pre>TYPEDEF: int cairo_bool_t<br /><br />CONSTANT: CAIRO_CONTENT_COLOR HEX: 1000<br />CONSTANT: CAIRO_CONTENT_ALPHA HEX: 2000<br />CONSTANT: CAIRO_CONTENT_COLOR_ALPHA HEX: 3000<br /><br />STRUCT: cairo_matrix_t<br /> { xx double }<br /> { yx double }<br /> { xy double }<br /> { yy double }<br /> { x0 double }<br /> { y0 double } ;<br /> <br />FUNCTION: void<br />cairo_transform ( cairo_t* cr, cairo_matrix_t* matrix ) ;</pre><br />The Factor compiler generates stubs for calling C functions on the fly from these declarative descriptions; there is no C code generator and no dependency on a C compiler. In fact, C library bindings are so easy to write that for many contributors, it is their first project in Factor. When <a href="http://code-factor.blogspot.com">Doug Coleman</a> first got involved in Factor, he began by writing a PostgreSQL binding, followed by an implementation of the MD5 checksum. Both libraries have been heavily worked on since then are still in use.<br /><br />For complete examples of FFI usage, check out any of the following:<br /><ul><li><a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=tree;f=basis/unix;hb=HEAD">unix</a> - POSIX bindings</li><li><a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=tree;f=basis/windows;hb=HEAD">windows</a> - Windows API bindings</li><li><a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=tree;f=basis/db/sqlite/ffi;hb=HEAD">db.sqlite.ffi</a> - SQLite bindings</li><li><a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=tree;f=basis/opengl/gl;hb=HEAD">opengl.gl</a> - OpenGL bindings</li></ul><br />There are many more usages of the FFI of course. Since Factor has a minimal VM, all I/O, graphics and interaction with the outside world in general is done with the FFI. Search the Factor source tree for source files that use the <a href="http://docs.factorcode.org/content/vocab-alien.syntax.html">alien.syntax</a> vocabulary.<br /><br /><h4>GPU shaders</h4><br /><a href="http://duriansoftware.com">Joe Groff</a> cooked up a nice DSL for passing uniform parameters to pixel and vertex shaders. In his <a href="http://duriansoftware.com/joe/Spring-cleaning.html">blog post</a>, Joe writes:<br /><blockquote><i>The library makes it easy to load and interactively update shaders, define binary formats for GPU vertex buffers, and feed parameters to shader code using Factor objects. </i></blockquote><br />Here is a snippet from the <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=tree;f=extra/gpu/demos/raytrace;hb=HEAD">gpu.demos.raytrace</a> demo:<br /><pre>GLSL-SHADER-FILE: raytrace-vertex-shader vertex-shader "raytrace.v.glsl"<br />GLSL-SHADER-FILE: raytrace-fragment-shader fragment-shader "raytrace.f.glsl"<br />GLSL-PROGRAM: raytrace-program<br /> raytrace-vertex-shader raytrace-fragment-shader ;<br /><br />UNIFORM-TUPLE: sphere-uniforms<br /> { "center" vec3-uniform f }<br /> { "radius" float-uniform f }<br /> { "color" vec4-uniform f } ;<br /><br />UNIFORM-TUPLE: raytrace-uniforms<br /> { "mv-inv-matrix" mat4-uniform f }<br /> { "fov" vec2-uniform f }<br /> <br /> { "spheres" sphere-uniforms 4 }<br /><br /> { "floor-height" float-uniform f }<br /> { "floor-color" vec4-uniform 2 }<br /> { "background-color" vec4-uniform f }<br /> { "light-direction" vec3-uniform f } ;</pre><br />The <a href="http://docs.factorcode.org/content/word-GLSL-SHADER-FILE__colon__,gpu.shaders.html">GLSL-SHADER-FILE:</a> parsing word tells Factor to load a GLSL shader program. The GPU framework automatically checks the file for modification, reloading it if necessary.<br /><br />The <a href="http://docs.factorcode.org/content/word-UNIFORM-TUPLE__colon__,gpu.render.html">UNIFORM-TUPLE:</a> parsing word defines a new tuple class, together with methods which destructure the tuple and bind textures and uniform parameters. Uniform parameters are named as such because they define values which remain constant at every pixel or vertex that the shader program operates on.<br /><br /><h4>Instruction definitions in the compiler</h4><br />This one is rather obscure and technical, but it has made my job easier over the last few weeks. <a href="http://factor-language.blogspot.com/2009/09/eliminating-some-boilerplate-in.html">I blogged about it already</a>.<br /><br /><h3>Other examples</h3><br />The next set of DSLs don't involve parsing words as much as just clever tricks with evaluation semantics.<br /><br /><h4>Inverse</h4><br /><a href="http://useless-factor.blogspot.com">Daniel Ehrenberg's</a> <a href="http://docs.factorcode.org/content/vocab-inverse.html">inverse</a> library implements a form of pattern matching by computing the inverse of a Factor quotation. The fundamental combinator, <a href="http://docs.factorcode.org/content/word-undo,inverse.html">undo</a>, takes a Factor quotation, and executes it "in reverse". So if there is a constructed tuple on the stack, undoing the constructor will leave the slots on the stack. If the top of the stack doesn't match anything that the constructor could've produced, then the inverse fails, and pattern matching can move on to the next clause. This library works by introspecting quotations and the words they contain. Dan gives many details and examples in <a href="http://www.scribd.com/doc/12803254/Pattern-matching-in-concatenative-languages">his paper on inverse</a>.<br /><br /><h4>Help system</h4><br /><a href="http://docs.factorcode.org/content/article-help.html">Factor's help system</a> uses an s-expression-like markup language. Help markup is parsed by the Factor parser without any special parsing words. A markup element is an array where the first element is a distinguished word and the rest are parameters. Examples:<br /><pre>"This is " { $strong "not" } " a good idea"<br /><br />{ $list<br /> "milk"<br /> "flour"<br /> "eggs"<br />}<br /><br />{ $link "help" }</pre><br />This markup is rendered either directly on the Factor UI (like in this <a href="http://factorcode.org/factor-windows7.png">screenshot</a>) or via HTML, as on the <a href="http://docs.factorcode.org">docs.factorcode.org</a> site.<br /><br />The nice thing about being able to view help in the UI environment is the sheer interactive nature of it. Unlike something like javadoc, there is no offline processing step which takes your source file and spits out rendered markup. You just load a vocabulary into your Factor instance and the documentation is available instantly. You can look at the help for any documented word by simply typing something like <pre>\ append help</pre> in the UI listener. While working on your own vocabulary, you can reload changes to the documentation and see them appear instantly in the UI's help browser.<br /><br />Finally, it is worth mentioning that because of the high degree of semantic information encoded in documentation, many kinds of mistakes can be caught in an automated fashion. The <a href="http://docs.factorcode.org/content/article-help.lint.html">help lint</a> tool finds inconsistencies between the actual parameters that a function takes, and the documented parameters, as well as code examples that don't evaluate to what the documentation claims they evaluate to, and a few other things.<br /><br />You won't find a lot of comments in Factor source, because the help system is much more useful. Instead of plain-text comments that can go out of date, why not have rich text with hyperlinks and semantic information?<br /><br />For examples of help markup, look at any file whose name ends with <code>-docs.factor</code> in the Factor source tree. There are plenty.<br /><br /><h4>x86 and PowerPC assemblers</h4><br />I put this one last since its not really a DSL at all, just a nice API. The lowest level of Factor's compiler generates machine code from the compiler's intermediate form in a CPU-specific way. The CPU backends for x86 and PowerPC use the <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=tree;f=basis/cpu/x86/assembler;hb=HEAD">cpu.x86.assembler</a> and <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=tree;f=basis/cpu/ppc/assembler;hb=HEAD">cpu.ppc.assembler</a> vocabularies for this, respectively. The way the assemblers work is that they define a set of words corresponding to CPU instructions. Instruction words takes operands from the stack -- which are objects representing registers, immediate values, and in the case of the x86, addressing modes. They then combine the operands and the instruction into a binary opcode, and add it to a byte vector stored in a dynamically-scoped variable. So instead of calling methods on, and passing around, an 'assembler object' as you would in say, a JIT coded in C++, you wrap the code generation in a call to Factor's <a href="http://docs.factorcode.org/content/word-make,make.html">make</a> word, and simply invoke instruction words therein. The result looks like assembler source, except it is postfix. Here is an x86 example:<br /><pre>[<br /> EAX ECX ADD<br /> XMM0 XMM1 HEX: ff SHUFPS<br /> AL 7 OR<br /> RAX 15 [+] RDI MOV<br />] B{ } make .</pre><br />When evaluated, the above will print out the following;<br /><pre>B{ 1 200 15 198 193 255 131 200 7 72 137 120 15 }</pre><br />Of course, <a href="http://docs.factorcode.org/content/word-B{,syntax.html">B{</a> is the homoiconic syntax for a byte array. Note the way indirect memory operands are constructed; first, we push the register (<code>RAX</code>) then the displacement (here the constant 15, but register displacement is supported by x86 too). Then we call a word <code>[+]</code> which constructs an object representing the addressing mode <code>[RAX+15]</code>.<br /><br />The rationale for choosing this somewhat funny syntax for indirect operands (there is also a <code>[]</code> word for memory loads without a displacement), rather than some kind of parser hack that allows one to write <code>[RAX]</code> or <code>[R14+RDI]</code> directly, is that in reality the compiler only rarely deals with hard-coded register assignments. Instead, the register allocator makes decisions a level above, and passes them to the code generator. Here is a typical compiler code generation template from the <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=basis/cpu/x86/x86.factor;hb=HEAD">cpu.x86</a> vocabulary:<br /><pre>M:: x86 %check-nursery ( label temp1 temp2 -- )<br /> temp1 load-zone-ptr<br /> temp2 temp1 cell [+] MOV<br /> temp2 1024 ADD<br /> temp1 temp1 3 cells [+] MOV<br /> temp2 temp1 CMP<br /> label JLE ;</pre><br />Here, I'm using the locals vocabulary together with the assembler. The <code>temp1</code> and <code>temp2</code> parameters are registers and <code>label</code> is, as its name implies, a label to jump to. This snippet generates machine code that checks whether or not the new object allocation area has enough space; if so, it jumps to the label, otherwise it falls through (code to save live registers and call the GC is generated next). The <code>load-zone-ptr</code> word is like an assembler macro here; it takes a register and generates some more code with it.<br /><br />The PowerPC assembler is a tad more interesting. Since the x86 instruction set is so complex, with many addressing modes and so on, the x86 assembler is implemented in a rather tedious manner. Obvious duplication is abstracted out. However, there is a lot of case-by-case code for different groups of instructions, with no coherent underlying abstraction allowing the instruction set to be described in a declarative way.<br /><br />On PowerPC, the situation is better. Since the instruction set is a lot more regular (fixed width instructions, only a few distinct instruction formats, no addressing modes), the PowerPC assembler itself is built using a DSL specifically for describing PowerPC instructions:<br /><pre>D: ADDI 14<br />D: ADDIC 12<br />D: ADDIC. 13<br />D: ADDIS 15<br />D: CMPI 11<br />D: CMPLI 10<br />...</pre><br />The PowerPC instruction format DSL is defined in the <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=tree;f=basis/cpu/ppc/assembler/backend;hb=HEAD">cpu.ppc.assembler.backend</a> vocabulary, and as a result the <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=basis/cpu/ppc/assembler/assembler.factor;hb=HEAD">cpu.ppc.assembler</a> vocabulary itself is mostly trivial.<br /><br /><h3>Last words</h3><br />Usually my blog posts describe recent progress in the Factor implementation, and I tend to write about what I'm working on right now. I'm currently working on code generation for SIMD vector instructions in the Factor compiler. I was going to blog about this instead, but decided not to do it until the SIMD implementation and API settles down some more.<br /><br />With this post I decided to try something a bit different, and instead just describe an aspect of Factor that interests to me, without any of it being particularly breaking news. If you've been following Factor development closely, there is literally nothing in this post that you would not have seen already, however I figured people who don't track the project so closely might appreciate a general survey like this. I'm also thinking of writing a post describing Factor's various high-level I/O libraries. I'd appreciate any feedback, suggestions and ideas on this matter.Slava Pestovhttp://www.blogger.com/profile/02768382790667979877[email protected]10tag:blogger.com,1999:blog-17087850.post-10917974223199166112009-09-12T23:20:00.010-04:002009-09-13T17:05:16.230-04:00Advanced floating point features: exceptions, rounding modes, denormals, unordered compares, and moreFactor now has a nice library for introspecting and modifying the floating point environment. <a href="http://duriansoftware.com/joe">Joe Groff</a> implemented most of it, and I helped out with debugging and additional floating point comparison operations. All of these features are part of the <a href="http://en.wikipedia.org/wiki/IEEE_754-2008">IEEE floating point specification</a> and are implemented on all modern CPUs, however few programming languages expose a nice interface to working with them. C compilers typically provide hard-to-use low-level intrinsics and other languages don't tend to bother at all. Two exceptions are the <a href="http://sbcl.org">SBCL compiler</a> and the <a href="http://www.digitalmars.com/d/">D language</a>.<br /><br />The new functionality is mostly contained in the <code>math.floats.env</code> vocabulary, with a few new words in <code>math</code> for good measure. The new code is in the repository but it is not completely stable yet; there are still some issues we need to work out on the more obscure platforms.<br /><br />To follow along with the examples below, you'll need to get a git checkout from the master branch and load the vocabulary in your listener:<br /><pre>USE: math.floats.env</pre><br />The first two features, floating point exceptions and traps, are useful for debugging numerical algorithms and detecting potentially undesirable situations (NaNs appearing, underflow, overflow, etc).<br /><br /><h3>Floating point exceptions</h3><br />One of the first things people learn about floating point is that it has "special" values: positive and negative infinity, and not-a-number (NaN) values. These appear as the results of computations where the answer is undefined (division by zero, square root of -1, etc) or the answer is too small or large to be represented as a float (2 to the power of 10000, etc). A less widely-known fact is that when a special value is computed, "exception flags" are set in a hardware register which can be read back in. Most languages do not offer any way to access this functionality.<br /><br />In Factor, exception flags can be read using the <code>collect-fp-exceptions</code> combinator, which first clears the flags, calls a quotation, then outputs any flags which were set. For example, division by zero sets the division by zero exception flag and returns infinity:<br /><pre>( scratchpad ) [ 1.0 0.0 / ] collect-fp-exceptions . .<br />{ +fp-zero-divide+ }<br />1/0.</pre><br />Dividing 1 by 3 sets the inexact flag, because the result (0.333....) cannot be represented as a float:<br /><pre>( scratchpad ) [ 1.0 3.0 / ] collect-fp-exceptions . .<br />{ +fp-inexact+ }<br />0.3333333333333333</pre><br />The fact that 1/3 does not have a terminating decimal or binary expansion is well-known, however one thing that many beginners find surprising is that some numbers which have terminating decimal expansions nevertheless cannot be represented precisely as floats because they do not terminate in binary (one classic case is 1.0 - 0.9 - 0.1 != 0.0):<br /><pre>( scratchpad ) [ 4.0 10.0 / ] collect-fp-exceptions . .<br />{ +fp-inexact+ }<br />0.4</pre><br />Raising a number to a power that is too large sets both the inexact and overflow flags, and returns infinity:<br /><pre>( scratchpad ) [ 2.0 10000.0 ^ ] collect-fp-exceptions . .<br />{ +fp-inexact+ +fp-overflow+ }<br />1/0.</pre><br />The square root of 4 is an exact value; no exceptions were set:<br /><pre>( scratchpad ) [ 4.0 sqrt ] collect-fp-exceptions . .<br />{ }<br />2.0</pre><br />The square root of 2 is not exact on the other hand:<br /><pre>( scratchpad ) [ 2.0 sqrt ] collect-fp-exceptions . .<br />{ +fp-inexact+ }<br />1.414213562373095</pre><br />Factor supports complex numbers, so taking the square root of -1 returns an exact value and does not set any exceptions:<br /><pre>( scratchpad ) [ -1.0 sqrt ] collect-fp-exceptions . .<br />{ }<br />C{ 0.0 1.0 }</pre><br />However, we can observe the invalid operation exception flag being set if we call the internal <code>fsqrt</code> word, which operates on floats only and calls the libc function (or uses the <code>SQRTSD</code> instruction on SSE2):<br /><pre>( scratchpad ) USE: math.libm [ -1.0 fsqrt ] collect-fp-exceptions . .<br />{ +fp-invalid-operation+ }<br />NAN: 8000000000000</pre><br />I describe the new <code>NAN:</code> syntax later in this post.<br /><br /><h3>Signaling traps</h3><br />Being able to inspect floating point exceptions set after a piece of code runs is all well and good, but what if you have a tricky underflow bug, or a NaN is popping up somewhere, and you want to know exactly where? In this case it is possible to set a flag in the FPU's control register which triggers a trap when an exception is raised. This trap is delivered to the Factor process as a signal (Unix), Mach exception (Mac OS X), or SEH exception (Windows). Factor then throws it as an exception which can be caught using any of Factor's <a href="http://docs.factorcode.org/content/article-errors.html">error handling</a> words, or just left unhandled in which case it will bubble up to the listener.<br /><br />The <code>with-fp-traps</code> combinator takes a list of traps and runs a quotation with those traps enabled; when the quotation completes (or throws an error) the former FPU state is restored again (indeed it has to be this way, since running the Factor UI's rendering code with traps enabled quickly kills it). The <code>all-fp-exceptions</code> word is equivalent to specifying <code>{ +fp-invalid-operation+ +fp-overflow+ +fp-underflow+ +fp-zero-divide+ +fp-inexact+ }</code>. Here is an example:<br /><pre>( scratchpad ) all-fp-exceptions [ 0.0 0.0 / ] with-fp-traps<br />Floating point trap</pre><br />Without the combinator wrapped around it, <code>0.0 0.0 /</code> simply returns a NaN value without throwing anything.<br /><br /><h3>Rounding modes</h3><br />Unlike exceptions and traps, which do not change the result of a computation but merely set status flags (or interrupt it), the next two features, the rounding mode and denormal mode, actually change the results of computations. As with exceptions and traps, they are implemented as scoped combinators rather than global state changes to ensure that code using these features is 'safe' and cannot change floating point state of surrounding code.<br /><br />If a floating point operation produces an inexact result, there is the question of how the result will be rounded to a value representable as a float. There are four rounding modes in IEEE floating point:<ul><li><code>+round-nearest+</code></li><li><code>+round-down+</code></li><li><code>+round-up+</code></li><li><code>+round-zero+</code></li></ul><br />Here is an example of an inexact computation done with two different rounding modes; the default (<code>+round-nearest+</code>) and <code>+round-up+</code>:<br /><pre>( scratchpad ) 1.0 3.0 / .<br />0.3333333333333333<br />( scratchpad ) +round-up+ [ 1.0 3.0 / ] with-rounding-mode .<br />0.3333333333333334</pre><br /><br /><h3>Denormals</h3><br /><a href="http://en.wikipedia.org/wiki/Denormal_number">Denormal numbers</a> are numbers where the exponent consists of zero bits (the minimum value) but the mantissa is not all zeros. Denormal numbers are undesirable because they have lower precision than normal floats, and on some CPUs computations with denormals are less efficient than with normals. IEEE floating point supports two denormal modes: you can elect to have denormals "flush" to zero (<code>+denormal-flush+</code>), or you can "keep" denormals (<code>+denormal-keep+</code>). The latter is the default:<br /><pre>( scratchpad ) +denormal-flush+ [ 51 2^ bits>double 0.0 + ] with-denormal-mode .<br />0.0<br />( scratchpad ) 51 2^ bits>double 0.0 + .<br />1.112536929253601e-308</pre><br /><br /><h3>Ordered and unordered comparisons</h3><br />In math, for any two numbers <code>a</code> and <code>b</code>, one of the following three properties hold:<br /><ul><li>a < b</li><li>a = b</li><li>a > b</li></ul><br />In floating point, there is a fourth possibility; <code>a</code> and <code>b</code> are <i>unordered</i>. This occurs if one of the two values is a NaN. The <code>unordered?</code> predicate tests for this possibility:<br /><pre>( scratchpad ) NAN: 8000000000000 1.0 unordered? .<br />t</pre><br />If an ordered comparison word such as <code><</code> or <code>>=</code> is called with two values which are unordered, they return <code>f</code> and set the <code>+fp-invalid-operation+</code> exception:<br /><pre>( scratchpad ) NAN: 8000000000000 1.0 [ < ] collect-fp-exceptions . .<br />{ +fp-invalid-operation+ }<br />f</pre><br />If traps are enabled this will throw an error:<br /><pre>( scratchpad ) NAN: 8000000000000 1.0 { +fp-invalid-operation+ } [ < ] with-fp-traps <br />Floating point trap</pre><br />If your numerical algorithm has a legitimate use for NaNs, and you wish to run it with traps enabled, and have certain comparisons not signal traps when inputs are NaNs, you can use <i>unordered</i> comparisons in those cases instead:<br /><pre>( scratchpad ) NAN: 8000000000000 1.0 [ u< ] collect-fp-exceptions . .<br />{ }<br />f</pre><br />Unordered versions of all the comparisons are defined now, <code>u<</code>, <code>u<=</code>, <code>u></code>, and <code>u>=</code>. Equality of numbers is always unordered, so it does not raise traps if one of the inputs is a NaN. In particular, if both inputs are NaNs, equality always returns <code>f</code>:<br /><pre>( scratchpad ) NAN: 8000000000000 dup [ number= ] collect-fp-exceptions . .<br />{ }<br />f</pre><br /><br /><h3>Half-precision floats</h3><br />Everyone and their drunk buddy know about IEEE single (32-bit) and double (64-bit) floats; IEEE also defines half-precision 16-bit floats. These are not used nearly as much; they come up in graphics programming for example, since GPUs use them for certain calculations with color components where you don't need more accuracy. The <code>half-floats</code> vocabulary provides some support for working with half-floats. It defines a pair of words for converting Factor's double-precision floats to and from half-floats, as well as C type support for passing half-floats to C functions via FFI, and building packed arrays of half-floats for passing to the GPU.<br /><br /><h3>Literal syntax for NaNs and hexadecimal floats</h3><br />You may have noticed the funny <code>NAN:</code> syntax above. Previously all NaN values would print as <code>0/0.</code>, however this is inaccurate since not all NaNs are created equal; because of how IEEE floating point works, a value is a NaN if the exponent consists of all ones, leaving the mantissa unspecified. The mantissa is known as the "NaN payload" in this case. NaNs now print out, and can be parsed back in, using a syntax that makes the payload explicit. A NaN can also be constructed with an arbitrary payload using the <code><fp-nan></code> word:<br /><pre>( scratchpad ) HEX: deadbeef <fp-nan> .<br />NAN: deadbeef</pre><br />The old <code>0/0.</code> syntax still works; it is shorthand for <code>NAN: 8000000000000</code>, the canonical "quiet" NaN.<br /><br />Some operations produce NaNs with different payloads:<br /><pre>( scratchpad ) USE: math.libm<br />( scratchpad ) 2.0 facos .<br />NAN: 8000000000022</pre><br />In general, there is very little you can do with the NaN payload.<br /><br />A more useful feature is hexadecimal float literals. When reading a float from a decimal string, or printing a float to a decimal string, there is sometimes ambiguity due to rounding. No such problem exists with hexadecimal floats.<br /><br />An example of printing a number as a decimal and a hexadecimal float:<br /><pre>( scratchpad ) USE: math.constants<br />( scratchpad ) pi .<br />3.141592653589793<br />( scratchpad ) pi .h<br />1.921fb54442d18p1</pre><br />Java supports hexadecimal float literals as of Java 1.5. Hats off to the Java designers for adding this! It would be nice if they would add the rest of the IEEE floating point functionality in Java 7.<br /><h3>Signed zero</h3><br />Unlike twos-complement integer arithmetic, IEEE floating point has both positive and negative zero. Negative zero is used as a result of computations of very small negative numbers that underflowed. They also have applications in complex analysis because they allow a choice of branch cut to be made. Factor's <code>abs</code> word used to be implemented incorrectly on floats; it would check if the input was negative, and if so multiply it by negative one. However this was a problem because negative zero is not less than zero, and so the absolute value of negative zero would be reported as negative zero. The correct implementation of the absolute value function on floats is to simply clear the sign bit. It works properly now:<br /><pre>( scratchpad ) -0.0 abs .<br />0.0</pre><br /><br /><h3>Implementation</h3><br />The implementation of the above features consists of several parts:<br /><ul><li>Cross-platform Factor code in the <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=tree;f=basis/math/floats/env;hb=HEAD">math.floats.env</a> vocabulary implementing the high-level API</li><li>Assembly code in <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=vm/cpu-x86.32.S;hb=HEAD">vm/cpu-x86.32.S</a>, <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=vm/cpu-x86.64.S;hb=HEAD">vm/cpu-x86.64.S</a>, and <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=vm/cpu-ppc.S;hb=HEAD">vm/cpu-ppc.S</a> to read and write x87, SSE2 and PowerPC FPU control registers</li><li>Low-level code in <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=tree;f=basis/math/floats/env/x86;hb=HEAD">math.floats.env.x86</a> and <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=tree;f=basis/math/floats/env/ppc;hb=HEAD">math.floats.env.ppc</a> which implements the high-level API in terms of the assembly functions, by calling them via <a href="http://docs.factorcode.org/content/article-alien.html">Factor's FFI</a> and parsing control registers into a cross-platform representation in terms of Factor symbols</li><li>Miscellaneous words for taking floats apart into their bitwise representation in the <code>math</code> vocabulary</li><li>Compiler support for ordered and unordered floating point compare instructions in <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=basis/compiler/cfg/instructions/instructions.factor;hb=HEAD">compiler.cfg.instructions</a></li><li>CPU-specific code generation for ordered and unordered floating point compare instructions in <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=basis/cpu/x86/x86.factor;hb=HEAD">cpu.x86</a> and <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=basis/cpu/ppc/ppc.factor;hb=HEAD">cpu.ppc</a></li></ul>Slava Pestovhttp://www.blogger.com/profile/02768382790667979877[email protected]2tag:blogger.com,1999:blog-17087850.post-20148145482956210532009-09-02T06:59:00.003-04:002009-09-02T07:20:06.195-04:00Eliminating some boilerplate in the compilerAdding new instructions to the low-level optimizer was too hard. Multiple places had to be updated, and I would do all this by hand:<br /><ul><li>The instruction tuple itself is defined in the <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=basis/compiler/cfg/instructions/instructions.factor;hb=HEAD">compiler.cfg.instructions</a> vocabulary with the <code>INSN:</code> class, which also creates a word with the same name that constructs the instruction and adds it to the current sequence.</li><li>Instructions which have a destination register have convenient constructors in <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=basis/compiler/cfg/hats/hats.factor;hb=HEAD">compiler.cfg.hats</a> which creates a fresh virtual register, creates an instruction with this register as the destination, and outputs it. So for example, <code>1 2 ^^add</code> would create an add instruction with a fresh destination register, and output this register. It might be equivalent to something like <code>0 1 2 ##add</code>.</li><li>Instructions that use virtual registers are be added to the <code>vreg-insn</code> union, and respond to methods <code>defs-vreg</code>, <code>uses-vregs</code>, and <code>temp-vregs</code> in <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=basis/compiler/cfg/def-use/def-use.factor;hb=HEAD">compiler.cfg.def-use</a>. This 'def-use' information is used by SSA construction, dead code elimination, copy coalescing, register allocation, among other things.</li><li>Methods have to be defined for the instruction in <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=basis/compiler/cfg/renaming/functor/functor.factor;hb=HEAD">compiler.cfg.renaming.functor</a>. This functor is used to generate code for renaming virtual registers in instructions. The renaming code is used for SSA construction, representation selection, register allocation, among other things.</li><li>Instructions which use non-integer representations (eg, operations on floats) must respond to methods <code>defs-vreg-rep</code>, <code>uses-vreg-reps</code>, and <code>temp-vreg-reps</code> in <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=basis/compiler/cfg/representations/preferred/preferred.factor">compiler.cfg.representations.preferred</a>.</li><li>Instructions must respond to the <code>generate-insn</code> method, defined in <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=basis/compiler/codegen/codegen.factor;hb=HEAD">compiler.codegen</a>.</li><li>Instructions which participate in value numbering must define an "expression" variant, and respond to the <code>>expr</code> method defined in <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=basis/compiler/cfg/value-numbering/expressions/expressions.factor;hb=HEAD">compiler.cfg.value-numbering.expressions</a></ul><br />As you can see, this is a lot of duplicated work. I used inheritance and mixins to model relationships between instructions and reduce some of this duplication by defining methods on common superclasses rather than individual instructions where possible, but this never seemed to work out well.<br /><br />If you look at the latest versions of the source files I linked to above, you'll see that all the repetitive copy-pasted code has been replaced with meta-programming. The new approach extends the <code>INSN:</code> parsing word so now all relevant information is specified in one place. Also there is a new <code>PURE-INSN:</code> parsing word, to mark instructions which participate in value numbering; previously this was done with a superclass. For example,<br /><pre>PURE-INSN: ##add<br />def: dst/int-rep<br />use: src1/int-rep src2/int-rep ;</pre><br />This defines an instruction tuple, an expression tuple, def/use information, representation information, a method for converting instructions to expressions, and a constructor, all at the same time.<br /><br />For the code generator's <code>generate-insn</code> method, not every instruction has a straightforward implementation; some, like GC checks and FFI calls, postpone a fair amount of work until the very last stage of compilation. However, for most instructions, this method simply extracts all the slots from the instruction tuple, then calls a CPU-specific hook in the <a href="http://gitweb.factorcode.org/gitweb.cgi?p=factor/.git;a=blob;f=basis/cpu/architecture/architecture.factor;hb=HEAD">cpu.architecture</a> vocabulary to generate code. For these cases, a <code>CODEGEN:</code> parsing word sets up the relevant boilerplate;<br /><pre>CODEGEN: ##add %add</pre><br />is equivalent to<br /><pre>M: ##add generate-insn [ dst>> ] [ src1>> ] [ src2>> ] tri %add ;</pre><br />This nicely cleans up all the repetition I mentioned in the bullet points at the top.<br /><br />I've been aware of this boilerplate for a while but wanted the design of the compiler to settle down first. Now that most of the machinery is in place, I feel comfortable cooking up some complex meta-programming to clean things up. Adding new instructions should be easier. I plan on adding some SSE2 vector operations soon, and this was the main motivation behind this cleanup.<br /><br />How would you do this in other languages? In Lisp, you would use a macro which expands into a bunch of <code>defclass</code>, <code>defmethod</code>, etc forms. In Java, you might use annotations:<br /><pre>public class AddInsn extends Insn {<br /> @Def @Representation("int") Register dst;<br /> @Use @Representation("int") Register src1;<br /> @Use @Representation("int") Register src2;<br />}</pre>Slava Pestovhttp://www.blogger.com/profile/02768382790667979877[email protected]0