Index: benchmark/Makefile.am
===================================================================
--- benchmark/Makefile.am	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ benchmark/Makefile.am	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -613,4 +613,5 @@
 
 RDQBENCHES = \
+	rdq-churn-cfa \
 	rdq-cycle-cfa \
 	rdq-cycle-tokio \
Index: benchmark/io/http/filecache.cfa
===================================================================
--- benchmark/io/http/filecache.cfa	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ benchmark/io/http/filecache.cfa	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -185,5 +185,5 @@
 	sout | "Filled cache from path \"" | path | "\" with" | fcount | "files";
 	if( conflicts > 0 ) {
-		sout | "Found" | conflicts | "conflicts (seed: " | options.file_cache.hash_seed | ")";
+		sout | "Found" | conflicts | "conflicts (size: " | file_cache.size | ", seed: " | options.file_cache.hash_seed | ")";
 		#if defined(REJECT_CONFLICTS)
 			abort("Conflicts found in the cache");
Index: benchmark/io/http/http_ring.cpp
===================================================================
--- benchmark/io/http/http_ring.cpp	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ benchmark/io/http/http_ring.cpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -118,11 +118,11 @@
 // Get a fix reply based on the return code
 const char * http_msgs[] = {
-	"HTTP/1.1 200 OK\r\nServer: HttoForall\r\nContent-Type: text/plain\r\nContent-Length: 15\r\nConnection: keep-alive\r\n\r\nHello, World!\r\n",
-	"HTTP/1.1 400 Bad Request\r\nServer: HttoForall\r\nContent-Type: text/plain\r\nContent-Length: 0 \r\n\r\n",
-	"HTTP/1.1 404 Not Found\r\nServer: HttoForall\r\nContent-Type: text/plain\r\nContent-Length: 0 \r\n\r\n",
-	"HTTP/1.1 405 Method Not \r\nServer: HttoForall\r\nContent-Type: text/plain\r\nContent-Length: 0 \r\n\r\n",
-	"HTTP/1.1 408 Request Timeout\r\nServer: HttoForall\r\nContent-Type: text/plain\r\nContent-Length: 0 \r\n\r\n",
-	"HTTP/1.1 413 Payload Too Large\r\nServer: HttoForall\r\nContent-Type: text/plain\r\nContent-Length: 0 \r\n\r\n",
-	"HTTP/1.1 414 URI Too Long\r\nServer: HttoForall\r\nContent-Type: text/plain\r\nContent-Length: 0 \r\n\r\n",
+	"HTTP/1.1 200 OK\r\nServer: HttpForall\r\nContent-Type: text/plain\r\nContent-Length: 15\r\nConnection: keep-alive\r\n\r\nHello, World!\r\n",
+	"HTTP/1.1 400 Bad Request\r\nServer: HttpForall\r\nContent-Type: text/plain\r\nContent-Length: 0 \r\n\r\n",
+	"HTTP/1.1 404 Not Found\r\nServer: HttpForall\r\nContent-Type: text/plain\r\nContent-Length: 0 \r\n\r\n",
+	"HTTP/1.1 405 Method Not \r\nServer: HttpForall\r\nContent-Type: text/plain\r\nContent-Length: 0 \r\n\r\n",
+	"HTTP/1.1 408 Request Timeout\r\nServer: HttpForall\r\nContent-Type: text/plain\r\nContent-Length: 0 \r\n\r\n",
+	"HTTP/1.1 413 Payload Too Large\r\nServer: HttpForall\r\nContent-Type: text/plain\r\nContent-Length: 0 \r\n\r\n",
+	"HTTP/1.1 414 URI Too Long\r\nServer: HttpForall\r\nContent-Type: text/plain\r\nContent-Length: 0 \r\n\r\n",
 };
 static_assert( KNOWN_CODES == (sizeof(http_msgs) / sizeof(http_msgs[0])) );
Index: benchmark/io/http/main.cfa
===================================================================
--- benchmark/io/http/main.cfa	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ benchmark/io/http/main.cfa	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -150,4 +150,5 @@
 			if(errno == EADDRINUSE) {
 				if(waited == 0) {
+					if(!options.interactive) abort | "Port already in use in non-interactive mode. Aborting";
 					sout | "Waiting for port";
 				} else {
@@ -190,5 +191,5 @@
 			init_protocol();
 			{
-				Worker workers[options.clopts.nworkers];
+				Worker * workers = anew(options.clopts.nworkers);
 				for(i; options.clopts.nworkers) {
 					// if( options.file_cache.fixed_fds ) {
@@ -212,4 +213,5 @@
 				}
 				sout | nl;
+				if(!options.interactive) park();
 				{
 					char buffer[128];
@@ -249,4 +251,5 @@
 
 				sout | "Stopping connection threads..." | nonl; flush( sout );
+				adelete(workers);
 			}
 			sout | "done";
Index: benchmark/io/http/options.cfa
===================================================================
--- benchmark/io/http/options.cfa	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ benchmark/io/http/options.cfa	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -21,4 +21,7 @@
 	false, // log
 	false, // stats
+	true, // interactive
+	0, // redirect
+	0, // redirect
 
 	{ // file_cache
@@ -52,5 +55,5 @@
 	// bool sqkpoll = false;
 	// bool iokpoll = false;
-	unsigned nentries = 16;
+	unsigned nentries = 0;
 	bool isolate = false;
 
@@ -62,5 +65,8 @@
 		{'\0', "isolate",        "Create one cluster per processor", isolate, parse_settrue},
 		{'\0', "log",            "Enable logs", options.log, parse_settrue},
+		{'\0', "sout",           "Redirect standard out to file", options.reopen_stdout},
+		{'\0', "serr",           "Redirect standard error to file", options.reopen_stderr},
 		{'\0', "stats",          "Enable statistics", options.stats, parse_settrue},
+		{'\0', "shell",          "Disable interactive mode", options.interactive, parse_setfalse},
 		{'\0', "accept-backlog", "Maximum number of pending accepts", options.socket.backlog},
 		{'\0', "request_len",    "Maximum number of bytes in the http request, requests with more data will be answered with Http Code 414", options.socket.buflen},
@@ -79,5 +85,5 @@
 	parse_args( argc, argv, opt, opt_cnt, "[OPTIONS]... [PATH]\ncforall http server", left );
 
-	if( !is_pow2(nentries) ) {
+	if( nentries != 0 && !is_pow2(nentries) ) {
 		unsigned v = nentries;
 		v--;
@@ -131,3 +137,26 @@
 
 	options.file_cache.path = path;
+
+	if( options.reopen_stdout && options.reopen_stderr && 0 == strcmp(options.reopen_stdout, options.reopen_stderr) ) {
+		serr | "Redirect sout and serr to the same file is not supported";
+		exit(EXIT_FAILURE);
+	}
+
+	if( options.reopen_stdout ) {
+		sout | "redirecting sout to '" | options.reopen_stdout | "'";
+		FILE  * ret = freopen( options.reopen_stdout, "w", stdout);
+		if( ret == 0p ) {
+			serr | "Failed to redirect sout to '" | options.reopen_stdout | "'";
+			exit(EXIT_FAILURE);
+		}
+	}
+
+	if( options.reopen_stderr ) {
+		sout | "redirecting serr to '" | options.reopen_stderr | "'";
+		FILE  * ret = freopen( options.reopen_stderr, "w", stderr);
+		if( ret == 0p ) {
+			serr | "Failed to redirect serr to '" | options.reopen_stderr | "'";
+			exit(EXIT_FAILURE);
+		}
+	}
 }
Index: benchmark/io/http/options.hfa
===================================================================
--- benchmark/io/http/options.hfa	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ benchmark/io/http/options.hfa	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -10,4 +10,7 @@
 	bool log;
 	bool stats;
+	bool interactive;
+	const char * reopen_stdout;
+	const char * reopen_stderr;
 
 	struct {
Index: benchmark/io/http/protocol.cfa
===================================================================
--- benchmark/io/http/protocol.cfa	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ benchmark/io/http/protocol.cfa	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -11,4 +11,6 @@
 #include <fstream.hfa>
 #include <iofwd.hfa>
+#include <io/types.hfa>
+#include <mutex_stmt.hfa>
 
 #include <assert.h>
@@ -26,4 +28,5 @@
 #define PLAINTEXT_MEMCPY
 #define PLAINTEXT_NOCOPY
+#define LINKED_IO
 
 struct https_msg_str {
@@ -53,10 +56,10 @@
 }
 
-static inline int answer( int fd, const char * it, int len) {
+static inline int answer( int fd, const char * it, int len ) {
 	while(len > 0) {
 		// Call write
 		int ret = cfa_send(fd, it, len, 0, CFA_IO_LAZY);
 		if( ret < 0 ) {
-			if( errno == ECONNRESET || errno == EPIPE ) return -ECONNRESET;
+			if( errno == ECONNRESET || errno == EPIPE ) { close(fd); return -ECONNRESET; }
 			if( errno == EAGAIN || errno == EWOULDBLOCK) return -EAGAIN;
 
@@ -77,11 +80,15 @@
 }
 
-int answer_header( int fd, size_t size ) {
-	char buffer[512];
-	char * it = buffer;
+static int fill_header(char * it, size_t size) {
 	memcpy(it, http_msgs[OK200]->msg, http_msgs[OK200]->len);
 	it += http_msgs[OK200]->len;
 	int len = http_msgs[OK200]->len;
 	len += snprintf(it, 512 - len, "%d \n\n", size);
+	return len;
+}
+
+static int answer_header( int fd, size_t size ) {
+	char buffer[512];
+	int len = fill_header(buffer, size);
 	return answer( fd, buffer, len );
 }
@@ -135,46 +142,5 @@
 }
 
-
-[HttpCode code, bool closed, * const char file, size_t len] http_read(int fd, []char buffer, size_t len) {
-	char * it = buffer;
-	size_t count = len - 1;
-	int rlen = 0;
-	READ:
-	for() {
-		int ret = cfa_recv(fd, (void*)it, count, 0, CFA_IO_LAZY);
-		// int ret = read(fd, (void*)it, count);
-		if(ret == 0 ) return [OK200, true, 0, 0];
-		if(ret < 0 ) {
-			if( errno == EAGAIN || errno == EWOULDBLOCK) continue READ;
-			if( errno == ECONNRESET ) return [E408, true, 0, 0];
-			if( errno == EPIPE ) return [E408, true, 0, 0];
-			abort( "read error: (%d) %s\n", (int)errno, strerror(errno) );
-		}
-		it[ret + 1] = '\0';
-		rlen += ret;
-
-		if( strstr( it, "\r\n\r\n" ) ) break;
-
-		it += ret;
-		count -= ret;
-
-		if( count < 1 ) return [E414, false, 0, 0];
-	}
-
-	if( options.log ) {
-		write(sout, buffer, rlen);
-		sout | nl;
-	}
-
-	it = buffer;
-	int ret = memcmp(it, "GET /", 5);
-	if( ret != 0 ) return [E400, false, 0, 0];
-	it += 5;
-
-	char * end = strstr( it, " " );
-	return [OK200, false, it, end - it];
-}
-
-int sendfile( int pipe[2], int fd, int ans_fd, size_t count ) {
+static int sendfile( int pipe[2], int fd, int ans_fd, size_t count ) {
 	unsigned sflags = SPLICE_F_MOVE; // | SPLICE_F_MORE;
 	off_t offset = 0;
@@ -207,4 +173,367 @@
 }
 
+static void zero_sqe(struct io_uring_sqe * sqe) {
+	sqe->flags = 0;
+	sqe->ioprio = 0;
+	sqe->fd = 0;
+	sqe->off = 0;
+	sqe->addr = 0;
+	sqe->len = 0;
+	sqe->fsync_flags = 0;
+	sqe->__pad2[0] = 0;
+	sqe->__pad2[1] = 0;
+	sqe->__pad2[2] = 0;
+	sqe->fd = 0;
+	sqe->off = 0;
+	sqe->addr = 0;
+	sqe->len = 0;
+}
+
+enum FSM_STATE {
+	Initial,
+	Retry,
+	Error,
+	Done,
+};
+
+struct FSM_Result {
+	FSM_STATE state;
+	int error;
+};
+
+static inline void ?{}(FSM_Result & this) { this.state = Initial; this.error = 0; }
+static inline bool is_error(FSM_Result & this) { return Error == this.state; }
+static inline bool is_done(FSM_Result & this) { return Done == this.state; }
+
+static inline int error(FSM_Result & this, int error) {
+	this.error = error;
+	this.state = Error;
+	return error;
+}
+
+static inline int done(FSM_Result & this) {
+	this.state = Done;
+	return 0;
+}
+
+static inline int retry(FSM_Result & this) {
+	this.state = Retry;
+	return 0;
+}
+
+static inline int need(FSM_Result & this) {
+	switch(this.state) {
+		case Initial:
+		case Retry:
+			return 1;
+		case Error:
+			if(this.error == 0) mutex(serr) serr | "State marked error but code is 0";
+		case Done:
+			return 0;
+	}
+}
+
+// Generator that handles sending the header
+generator header_g {
+	io_future_t f;
+	const char * next;
+	int fd; size_t len;
+	FSM_Result res;
+};
+
+static inline void ?{}(header_g & this, int fd, const char * it, size_t len ) {
+	this.next = it;
+	this.fd = fd;
+	this.len = len;
+}
+
+static inline void fill(header_g & this, struct io_uring_sqe * sqe) {
+	zero_sqe(sqe);
+	sqe->opcode = IORING_OP_SEND;
+	sqe->user_data = (uintptr_t)&this.f;
+	sqe->flags = IOSQE_IO_LINK;
+	sqe->fd = this.fd;
+	sqe->addr = (uintptr_t)this.next;
+	sqe->len = this.len;
+}
+
+static inline int error(header_g & this, int error) {
+	int ret = close(this.fd);
+	if( ret != 0 ) {
+		mutex(serr) serr | "Failed to close fd" | errno;
+	}
+	return error(this.res, error);
+}
+
+static inline int wait_and_process(header_g & this) {
+	wait(this.f);
+
+	// Did something crazy happen?
+	if(this.f.result > this.len) {
+		mutex(serr) serr | "HEADER sent too much!";
+		return error(this, -ERANGE);
+	}
+
+	// Something failed?
+	if(this.f.result < 0) {
+		int error = -this.f.result;
+		if( error == ECONNRESET ) return error(this, -ECONNRESET);
+		if( error == EPIPE ) return error(this, -EPIPE);
+		if( error == ECANCELED ) {
+			mutex(serr) serr | "HEADER was cancelled, WTF!";
+			return error(this, -ECONNRESET);
+		}
+		if( error == EAGAIN || error == EWOULDBLOCK) {
+			mutex(serr) serr | "HEADER got eagain, WTF!";
+			return error(this, -ECONNRESET);
+		}
+	}
+
+	// Done?
+	if(this.f.result == this.len) {
+		return done(this.res);
+	}
+
+	// It must be a Short read
+	this.len  -= this.f.result;
+	this.next += this.f.result;
+	reset(this.f);
+	return retry(this.res);
+}
+
+// Generator that handles splicing in a file
+struct splice_in_t {
+	io_future_t f;
+	int fd; int pipe; size_t len; off_t off;
+	FSM_Result res;
+};
+
+static inline void ?{}(splice_in_t & this, int fd, int pipe, size_t len) {
+	this.fd = fd;
+	this.pipe = pipe;
+	this.len = len;
+	this.off = 0;
+}
+
+static inline void fill(splice_in_t & this, struct io_uring_sqe * sqe) {
+	zero_sqe(sqe);
+	sqe->opcode = IORING_OP_SPLICE;
+	sqe->user_data = (uintptr_t)&this.f;
+	sqe->flags = 0;
+	sqe->splice_fd_in = this.fd;
+	sqe->splice_off_in = this.off;
+	sqe->fd = this.pipe;
+	sqe->off = (__u64)-1;
+	sqe->len = this.len;
+	sqe->splice_flags = SPLICE_F_MOVE;
+}
+
+static inline int wait_and_process(splice_in_t & this) {
+	wait(this.f);
+
+	// Something failed?
+	if(this.f.result < 0) {
+		int error = -this.f.result;
+		if( error == ECONNRESET ) return error(this.res, -ECONNRESET);
+		if( error == EPIPE ) return error(this.res, -EPIPE);
+		if( error == ECANCELED ) {
+			mutex(serr) serr | "SPLICE IN was cancelled, WTF!";
+			return error(this.res, -ECONNRESET);
+		}
+		if( error == EAGAIN || error == EWOULDBLOCK) {
+			mutex(serr) serr | "SPLICE IN got eagain, WTF!";
+			return error(this.res, -ECONNRESET);
+		}
+	}
+
+	// Did something crazy happen?
+	if(this.f.result > this.len) {
+		mutex(serr) serr | "SPLICE IN spliced too much!";
+		return error(this.res, -ERANGE);
+	}
+
+	// Done?
+	if(this.f.result == this.len) {
+		return done(this.res);
+	}
+
+	// It must be a Short read
+	this.len -= this.f.result;
+	this.off += this.f.result;
+	reset(this.f);
+	return retry(this.res);
+}
+
+generator splice_out_g {
+	io_future_t f;
+	int pipe; int fd; size_t len;
+	FSM_Result res;
+};
+
+static inline void ?{}(splice_out_g & this, int pipe, int fd, size_t len) {
+	this.pipe = pipe;
+	this.fd = fd;
+	this.len = len;
+}
+
+static inline void fill(splice_out_g & this, struct io_uring_sqe * sqe) {
+	zero_sqe(sqe);
+	sqe->opcode = IORING_OP_SPLICE;
+	sqe->user_data = (uintptr_t)&this.f;
+	sqe->flags = 0;
+	sqe->splice_fd_in = this.pipe;
+	sqe->splice_off_in = (__u64)-1;
+	sqe->fd = this.fd;
+	sqe->off = (__u64)-1;
+	sqe->len = this.len;
+	sqe->splice_flags = SPLICE_F_MOVE;
+}
+
+static inline int error(splice_out_g & this, int error) {
+	int ret = close(this.fd);
+	if( ret != 0 ) {
+		mutex(serr) serr | "Failed to close fd" | errno;
+	}
+	return error(this.res, error);
+}
+
+static inline void wait_and_process(splice_out_g & this) {
+	wait(this.f);
+
+	// Something failed?
+	if(this.f.result < 0) {
+		int error = -this.f.result;
+		if( error == ECONNRESET ) return error(this, -ECONNRESET);
+		if( error == EPIPE ) return error(this, -EPIPE);
+		if( error == ECANCELED ) {
+			this.f.result = 0;
+			goto SHORT_WRITE;
+		}
+		if( error == EAGAIN || error == EWOULDBLOCK) {
+			mutex(serr) serr | "SPLICE OUT got eagain, WTF!";
+			return error(this, -ECONNRESET);
+		}
+	}
+
+	// Did something crazy happen?
+	if(this.f.result > this.len) {
+		mutex(serr) serr | "SPLICE OUT spliced too much!" | this.f.result | ">" | this.len;
+		return error(this.res, -ERANGE);
+	}
+
+	// Done?
+	if(this.f.result == this.len) {
+		return done(this.res);
+	}
+
+SHORT_WRITE:
+	// It must be a Short Write
+	this.len -= this.f.result;
+	reset(this.f);
+	return retry(this.res);
+}
+
+int answer_sendfile( int pipe[2], int fd, int ans_fd, size_t fsize ) {
+	#if defined(LINKED_IO)
+		char buffer[512];
+		int len = fill_header(buffer, fsize);
+		header_g header = { fd, buffer, len };
+		splice_in_t splice_in = { ans_fd, pipe[1], fsize };
+		splice_out_g splice_out = { pipe[0], fd, fsize };
+
+		RETRY_LOOP: for() {
+			int have = need(header.res) + need(splice_in.res) + 1;
+			int idx = 0;
+			struct io_uring_sqe * sqes[3];
+			__u32 idxs[3];
+			struct $io_context * ctx = cfa_io_allocate(sqes, idxs, have);
+
+			if(need(splice_in.res)) { fill(splice_in, sqes[idx++]); }
+			if(need(   header.res)) { fill(header   , sqes[idx++]); }
+			fill(splice_out, sqes[idx]);
+
+			// Submit everything
+			asm volatile("": : :"memory");
+			cfa_io_submit( ctx, idxs, have, false );
+
+			// wait for the results
+			// Always wait for splice-in to complete as
+			// we may need to kill the connection if it fails
+			// If it already completed, this is a no-op
+			wait_and_process(splice_in);
+
+			if(is_error(splice_in.res)) {
+				mutex(serr) serr | "SPLICE IN failed with" | splice_in.res.error;
+				close(fd);
+			}
+
+			// Process the other 2
+			wait_and_process(header);
+			wait_and_process(splice_out);
+
+			if(is_done(splice_out.res)) {
+				break RETRY_LOOP;
+			}
+
+			// We need to wait for the completion if
+			// - both completed
+			// - the header failed
+			// -
+
+			if(  is_error(header.res)
+			  || is_error(splice_in.res)
+			  || is_error(splice_out.res)) {
+				return -ECONNRESET;
+			}
+		}
+
+		return len + fsize;
+	#else
+		int ret = answer_header(fd, fsize);
+		if( ret < 0 ) { close(fd); return ret; }
+		return sendfile(pipe, fd, ans_fd, fsize);
+	#endif
+}
+
+[HttpCode code, bool closed, * const char file, size_t len] http_read(int fd, []char buffer, size_t len) {
+	char * it = buffer;
+	size_t count = len - 1;
+	int rlen = 0;
+	READ:
+	for() {
+		int ret = cfa_recv(fd, (void*)it, count, 0, CFA_IO_LAZY);
+		// int ret = read(fd, (void*)it, count);
+		if(ret == 0 ) return [OK200, true, 0, 0];
+		if(ret < 0 ) {
+			if( errno == EAGAIN || errno == EWOULDBLOCK) continue READ;
+			if( errno == ECONNRESET ) { close(fd); return [E408, true, 0, 0]; }
+			if( errno == EPIPE ) { close(fd); return [E408, true, 0, 0]; }
+			abort( "read error: (%d) %s\n", (int)errno, strerror(errno) );
+		}
+		it[ret + 1] = '\0';
+		rlen += ret;
+
+		if( strstr( it, "\r\n\r\n" ) ) break;
+
+		it += ret;
+		count -= ret;
+
+		if( count < 1 ) return [E414, false, 0, 0];
+	}
+
+	if( options.log ) {
+		write(sout, buffer, rlen);
+		sout | nl;
+	}
+
+	it = buffer;
+	int ret = memcmp(it, "GET /", 5);
+	if( ret != 0 ) return [E400, false, 0, 0];
+	it += 5;
+
+	char * end = strstr( it, " " );
+	return [OK200, false, it, end - it];
+}
+
 //=============================================================================================
 
@@ -214,12 +543,12 @@
 
 const char * original_http_msgs[] = {
-	"HTTP/1.1 200 OK\nServer: HttoForall\nDate: %s \nContent-Type: text/plain\nContent-Length: ",
-	"HTTP/1.1 200 OK\nServer: HttoForall\nDate: %s \nContent-Type: text/plain\nContent-Length: 15\n\nHello, World!\n\n",
-	"HTTP/1.1 400 Bad Request\nServer: HttoForall\nDate: %s \nContent-Type: text/plain\nContent-Length: 0 \n\n",
-	"HTTP/1.1 404 Not Found\nServer: HttoForall\nDate: %s \nContent-Type: text/plain\nContent-Length: 0 \n\n",
-	"HTTP/1.1 405 Method Not Allowed\nServer: HttoForall\nDate: %s \nContent-Type: text/plain\nContent-Length: 0 \n\n",
-	"HTTP/1.1 408 Request Timeout\nServer: HttoForall\nDate: %s \nContent-Type: text/plain\nContent-Length: 0 \n\n",
-	"HTTP/1.1 413 Payload Too Large\nServer: HttoForall\nDate: %s \nContent-Type: text/plain\nContent-Length: 0 \n\n",
-	"HTTP/1.1 414 URI Too Long\nServer: HttoForall\nDate: %s \nContent-Type: text/plain\nContent-Length: 0 \n\n",
+	"HTTP/1.1 200 OK\nServer: HttpForall\nDate: %s \nContent-Type: text/plain\nContent-Length: ",
+	"HTTP/1.1 200 OK\r\nServer: HttpForall\r\nConnection: keep-alive\r\nContent-Length: 15\r\nContent-Type: text/html\r\nDate: %s \r\n\r\nHello, World!\r\n",
+	"HTTP/1.1 400 Bad Request\nServer: HttpForall\nDate: %s \nContent-Type: text/plain\nContent-Length: 0 \n\n",
+	"HTTP/1.1 404 Not Found\nServer: HttpForall\nDate: %s \nContent-Type: text/plain\nContent-Length: 0 \n\n",
+	"HTTP/1.1 405 Method Not Allowed\nServer: HttpForall\nDate: %s \nContent-Type: text/plain\nContent-Length: 0 \n\n",
+	"HTTP/1.1 408 Request Timeout\nServer: HttpForall\nDate: %s \nContent-Type: text/plain\nContent-Length: 0 \n\n",
+	"HTTP/1.1 413 Payload Too Large\nServer: HttpForall\nDate: %s \nContent-Type: text/plain\nContent-Length: 0 \n\n",
+	"HTTP/1.1 414 URI Too Long\nServer: HttpForall\nDate: %s \nContent-Type: text/plain\nContent-Length: 0 \n\n",
 };
 
@@ -251,5 +580,5 @@
 		Time now = timeHiRes();
 		strftime( buff, 100, "%a, %d %b %Y %H:%M:%S %Z", now );
-		sout | "Updated date to '" | buff | "'";
+		// if( options.log ) sout | "Updated date to '" | buff | "'";
 
 		for(i; KNOWN_CODES) {
@@ -264,5 +593,5 @@
 		this.idx = (this.idx + 1) % 2;
 
-		sout | "Date thread sleeping";
+		// if( options.log ) sout | "Date thread sleeping";
 
 		sleep(1`s);
Index: benchmark/io/http/protocol.hfa
===================================================================
--- benchmark/io/http/protocol.hfa	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ benchmark/io/http/protocol.hfa	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -16,9 +16,7 @@
 
 int answer_error( int fd, HttpCode code );
-int answer_header( int fd, size_t size );
 int answer_plaintext( int fd );
 int answer_empty( int fd );
+int answer_sendfile( int pipe[2], int fd, int ans_fd, size_t count );
 
 [HttpCode code, bool closed, * const char file, size_t len] http_read(int fd, []char buffer, size_t len);
-
-int sendfile( int pipe[2], int fd, int ans_fd, size_t count );
Index: benchmark/io/http/worker.cfa
===================================================================
--- benchmark/io/http/worker.cfa	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ benchmark/io/http/worker.cfa	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -18,5 +18,5 @@
 void ?{}( Worker & this ) {
 	size_t cli = rand() % options.clopts.cltr_cnt;
-	((thread&)this){ "Server Worker Thread", *options.clopts.instance[cli], 512000 };
+	((thread&)this){ "Server Worker Thread", *options.clopts.instance[cli], 64000 };
 	options.clopts.thrd_cnt[cli]++;
 	this.pipe[0] = -1;
@@ -122,10 +122,6 @@
 			}
 
-			// Send the header
-			int ret = answer_header(fd, count);
-			if( ret == -ECONNRESET ) break REQUEST;
-
 			// Send the desired file
-			ret = sendfile( this.pipe, fd, ans_fd, count);
+			int ret = answer_sendfile( this.pipe, fd, ans_fd, count);
 			if( ret == -ECONNRESET ) break REQUEST;
 
@@ -134,5 +130,4 @@
 
 		if( options.log ) sout | "=== Connection closed ===";
-		close(fd);
 		continue CONNECTION;
 	}
Index: benchmark/readyQ/churn.cfa
===================================================================
--- benchmark/readyQ/churn.cfa	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
+++ benchmark/readyQ/churn.cfa	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -0,0 +1,101 @@
+#include "rq_bench.hfa"
+
+unsigned spot_cnt = 2;
+bench_sem * volatile * spots;
+
+thread BThrd {
+	unsigned long long count;
+	unsigned long long blocks;
+	bench_sem sem;
+};
+
+void ?{}( BThrd & this ) {
+	((thread&)this){ bench_cluster };
+	this.count  = 0;
+	this.blocks = 0;
+}
+
+void ^?{}( BThrd & mutex this ) {}
+
+void main( BThrd & this ) with( this ) {
+	wait( sem );
+	for() {
+		uint64_t r = thread_rand();
+		bench_sem * next = __atomic_exchange_n(&spots[r % spot_cnt], &sem, __ATOMIC_SEQ_CST);
+		if(next) post( *next );
+		blocks += wait( sem );
+		count ++;
+		if( clock_mode && stop) break;
+		if(!clock_mode && count >= stop_count) break;
+	}
+
+	__atomic_fetch_add(&threads_left, -1, __ATOMIC_SEQ_CST);
+}
+
+
+int main(int argc, char * argv[]) {
+	cfa_option opt[] = {
+		BENCH_OPT,
+		{ 's', "spots", "Number of spots in the system", spot_cnt }
+	};
+	BENCH_OPT_PARSE("cforall cycle benchmark");
+
+	{
+		unsigned long long global_counter = 0;
+		unsigned long long global_blocks  = 0;
+		Time start, end;
+		BenchCluster bc = { nprocs };
+		{
+			spots = aalloc(spot_cnt);
+			for(i; spot_cnt) {
+				spots[i] = 0p;
+			}
+
+			threads_left = nthreads;
+			BThrd * threads[nthreads];
+			for(i; nthreads ) {
+				threads[i] = malloc();
+				(*threads[i]){};
+			}
+			printf("Starting\n");
+
+			bool is_tty = isatty(STDOUT_FILENO);
+			start = timeHiRes();
+
+			for(i; nthreads) {
+				post( threads[i]->sem );
+			}
+			wait(start, is_tty);
+
+			stop = true;
+			end = timeHiRes();
+			printf("\nDone\n");
+
+			for(i; nthreads) {
+				post( threads[i]->sem );
+				BThrd & thrd = join( *threads[i] );
+				global_counter += thrd.count;
+				global_blocks  += thrd.blocks;
+				delete(threads[i]);
+			}
+
+			free(spots);
+		}
+
+		printf("Duration (ms)        : %'lf\n", (end - start)`dms);
+		printf("Number of processors : %'d\n", nprocs);
+		printf("Number of threads    : %'d\n", nthreads);
+		printf("Number of spots      : %'d\n", spot_cnt);
+		printf("Total Operations(ops): %'15llu\n", global_counter);
+		printf("Total blocks         : %'15llu\n", global_blocks);
+		printf("Ops per second       : %'18.2lf\n", ((double)global_counter) / (end - start)`ds);
+		printf("ns per ops           : %'18.2lf\n", (end - start)`dns / global_counter);
+		printf("Ops per threads      : %'15llu\n", global_counter / nthreads);
+		printf("Ops per procs        : %'15llu\n", global_counter / nprocs);
+		printf("Ops/sec/procs        : %'18.2lf\n", (((double)global_counter) / nprocs) / (end - start)`ds);
+		printf("ns per ops/procs     : %'18.2lf\n", (end - start)`dns / (global_counter / nprocs));
+		fflush(stdout);
+	}
+
+	return 0;
+}
Index: doc/theses/mubeen_zulfiqar_MMath/allocator.tex
===================================================================
--- doc/theses/mubeen_zulfiqar_MMath/allocator.tex	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ doc/theses/mubeen_zulfiqar_MMath/allocator.tex	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -24,6 +24,7 @@
 \end{itemize}
 
-The new features added to uHeapLmmm (incl. @malloc_size@ routine)
+The new features added to uHeapLmmm (incl. @malloc\_size@ routine)
 \CFA alloc interface with examples.
+
 \begin{itemize}
 \item
@@ -117,8 +118,9 @@
 We added a few more features and routines to the allocator's C interface that can make the allocator more usable to the programmers. THese features will programmer more control on the dynamic memory allocation.
 
-\subsubsection void * aalloc( size_t dim, size_t elemSize )
+\subsubsection void * aalloc( size\_t dim, size\_t elemSize )
 aalloc is an extension of malloc. It allows programmer to allocate a dynamic array of objects without calculating the total size of array explicitly. The only alternate of this routine in the other allocators is calloc but calloc also fills the dynamic memory with 0 which makes it slower for a programmer who only wants to dynamically allocate an array of objects without filling it with 0.
 \paragraph{Usage}
 aalloc takes two parameters.
+
 \begin{itemize}
 \item
@@ -129,8 +131,9 @@
 It returns address of dynamic object allocatoed on heap that can contain dim number of objects of the size elemSize. On failure, it returns NULL pointer.
 
-\subsubsection void * resize( void * oaddr, size_t size )
+\subsubsection void * resize( void * oaddr, size\_t size )
 resize is an extension of relloc. It allows programmer to reuse a cuurently allocated dynamic object with a new size requirement. Its alternate in the other allocators is realloc but relloc also copy the data in old object to the new object which makes it slower for the programmer who only wants to reuse an old dynamic object for a new size requirement but does not want to preserve the data in the old object to the new object.
 \paragraph{Usage}
 resize takes two parameters.
+
 \begin{itemize}
 \item
@@ -141,8 +144,9 @@
 It returns an object that is of the size given but it does not preserve the data in the old object. On failure, it returns NULL pointer.
 
-\subsubsection void * resize( void * oaddr, size_t nalign, size_t size )
+\subsubsection void * resize( void * oaddr, size\_t nalign, size\_t size )
 This resize is an extension of the above resize (FIX ME: cite above resize). In addition to resizing the size of of an old object, it can also realign the old object to a new alignment requirement.
 \paragraph{Usage}
 This resize takes three parameters. It takes an additional parameter of nalign as compared to the above resize (FIX ME: cite above resize).
+
 \begin{itemize}
 \item
@@ -155,8 +159,9 @@
 It returns an object with the size and alignment given in the parameters. On failure, it returns a NULL pointer.
 
-\subsubsection void * amemalign( size_t alignment, size_t dim, size_t elemSize )
+\subsubsection void * amemalign( size\_t alignment, size\_t dim, size\_t elemSize )
 amemalign is a hybrid of memalign and aalloc. It allows programmer to allocate an aligned dynamic array of objects without calculating the total size of the array explicitly. It frees the programmer from calculating the total size of the array.
 \paragraph{Usage}
 amemalign takes three parameters.
+
 \begin{itemize}
 \item
@@ -169,8 +174,9 @@
 It returns a dynamic array of objects that has the capacity to contain dim number of objects of the size of elemSize. The returned dynamic array is aligned to the given alignment. On failure, it returns NULL pointer.
 
-\subsubsection void * cmemalign( size_t alignment, size_t dim, size_t elemSize )
+\subsubsection void * cmemalign( size\_t alignment, size\_t dim, size\_t elemSize )
 cmemalign is a hybrid of amemalign and calloc. It allows programmer to allocate an aligned dynamic array of objects that is 0 filled. The current way to do this in other allocators is to allocate an aligned object with memalign and then fill it with 0 explicitly. This routine provides both features of aligning and 0 filling, implicitly.
 \paragraph{Usage}
 cmemalign takes three parameters.
+
 \begin{itemize}
 \item
@@ -183,38 +189,42 @@
 It returns a dynamic array of objects that has the capacity to contain dim number of objects of the size of elemSize. The returned dynamic array is aligned to the given alignment and is 0 filled. On failure, it returns NULL pointer.
 
-\subsubsection size_t malloc_alignment( void * addr )
-malloc_alignment returns the alignment of a currently allocated dynamic object. It allows the programmer in memory management and personal bookkeeping. It helps the programmer in verofying the alignment of a dynamic object especially in a scenerio similar to prudcer-consumer where a producer allocates a dynamic object and the consumer needs to assure that the dynamic object was allocated with the required alignment.
-\paragraph{Usage}
-malloc_alignment takes one parameters.
+\subsubsection size\_t malloc\_alignment( void * addr )
+malloc\_alignment returns the alignment of a currently allocated dynamic object. It allows the programmer in memory management and personal bookkeeping. It helps the programmer in verofying the alignment of a dynamic object especially in a scenerio similar to prudcer-consumer where a producer allocates a dynamic object and the consumer needs to assure that the dynamic object was allocated with the required alignment.
+\paragraph{Usage}
+malloc\_alignment takes one parameters.
+
 \begin{itemize}
 \item
 addr: the address of the currently allocated dynamic object.
 \end{itemize}
-malloc_alignment returns the alignment of the given dynamic object. On failure, it return the value of default alignment of the uHeapLmmm allocator.
-
-\subsubsection bool malloc_zero_fill( void * addr )
-malloc_zero_fill returns whether a currently allocated dynamic object was initially zero filled at the time of allocation. It allows the programmer in memory management and personal bookkeeping. It helps the programmer in verifying the zero filled property of a dynamic object especially in a scenerio similar to prudcer-consumer where a producer allocates a dynamic object and the consumer needs to assure that the dynamic object was zero filled at the time of allocation.
-\paragraph{Usage}
-malloc_zero_fill takes one parameters.
+malloc\_alignment returns the alignment of the given dynamic object. On failure, it return the value of default alignment of the uHeapLmmm allocator.
+
+\subsubsection bool malloc\_zero\_fill( void * addr )
+malloc\_zero\_fill returns whether a currently allocated dynamic object was initially zero filled at the time of allocation. It allows the programmer in memory management and personal bookkeeping. It helps the programmer in verifying the zero filled property of a dynamic object especially in a scenerio similar to prudcer-consumer where a producer allocates a dynamic object and the consumer needs to assure that the dynamic object was zero filled at the time of allocation.
+\paragraph{Usage}
+malloc\_zero\_fill takes one parameters.
+
 \begin{itemize}
 \item
 addr: the address of the currently allocated dynamic object.
 \end{itemize}
-malloc_zero_fill returns true if the dynamic object was initially zero filled and return false otherwise. On failure, it returns false.
-
-\subsubsection size_t malloc_size( void * addr )
-malloc_size returns the allocation size of a currently allocated dynamic object. It allows the programmer in memory management and personal bookkeeping. It helps the programmer in verofying the alignment of a dynamic object especially in a scenerio similar to prudcer-consumer where a producer allocates a dynamic object and the consumer needs to assure that the dynamic object was allocated with the required size. Its current alternate in the other allocators is malloc_usable_size. But, malloc_size is different from malloc_usable_size as malloc_usabe_size returns the total data capacity of dynamic object including the extra space at the end of the dynamic object. On the other hand, malloc_size returns the size that was given to the allocator at the allocation of the dynamic object. This size is updated when an object is realloced, resized, or passed through a similar allocator routine.
-\paragraph{Usage}
-malloc_size takes one parameters.
+malloc\_zero\_fill returns true if the dynamic object was initially zero filled and return false otherwise. On failure, it returns false.
+
+\subsubsection size\_t malloc\_size( void * addr )
+malloc\_size returns the allocation size of a currently allocated dynamic object. It allows the programmer in memory management and personal bookkeeping. It helps the programmer in verofying the alignment of a dynamic object especially in a scenerio similar to prudcer-consumer where a producer allocates a dynamic object and the consumer needs to assure that the dynamic object was allocated with the required size. Its current alternate in the other allocators is malloc\_usable\_size. But, malloc\_size is different from malloc\_usable\_size as malloc\_usabe\_size returns the total data capacity of dynamic object including the extra space at the end of the dynamic object. On the other hand, malloc\_size returns the size that was given to the allocator at the allocation of the dynamic object. This size is updated when an object is realloced, resized, or passed through a similar allocator routine.
+\paragraph{Usage}
+malloc\_size takes one parameters.
+
 \begin{itemize}
 \item
 addr: the address of the currently allocated dynamic object.
 \end{itemize}
-malloc_size returns the allocation size of the given dynamic object. On failure, it return zero.
-
-\subsubsection void * realloc( void * oaddr, size_t nalign, size_t size )
+malloc\_size returns the allocation size of the given dynamic object. On failure, it return zero.
+
+\subsubsection void * realloc( void * oaddr, size\_t nalign, size\_t size )
 This realloc is an extension of the default realloc (FIX ME: cite default realloc). In addition to reallocating an old object and preserving the data in old object, it can also realign the old object to a new alignment requirement.
 \paragraph{Usage}
 This realloc takes three parameters. It takes an additional parameter of nalign as compared to the default realloc.
+
 \begin{itemize}
 \item
@@ -237,8 +247,9 @@
 It returns a dynamic object of the size of type T. On failure, it return NULL pointer.
 
-\subsubsection T * aalloc( size_t dim )
+\subsubsection T * aalloc( size\_t dim )
 This aalloc is a simplified polymorphic form of above aalloc (FIX ME: cite aalloc). It takes one parameter as compared to the above aalloc that takes two parameters.
 \paragraph{Usage}
 aalloc takes one parameters.
+
 \begin{itemize}
 \item
@@ -247,8 +258,9 @@
 It returns a dynamic object that has the capacity to contain dim number of objects, each of the size of type T. On failure, it return NULL pointer.
 
-\subsubsection T * calloc( size_t dim )
+\subsubsection T * calloc( size\_t dim )
 This calloc is a simplified polymorphic form of defualt calloc (FIX ME: cite calloc). It takes one parameter as compared to the default calloc that takes two parameters.
 \paragraph{Usage}
 This calloc takes one parameter.
+
 \begin{itemize}
 \item
@@ -257,8 +269,9 @@
 It returns a dynamic object that has the capacity to contain dim number of objects, each of the size of type T. On failure, it return NULL pointer.
 
-\subsubsection T * resize( T * ptr, size_t size )
+\subsubsection T * resize( T * ptr, size\_t size )
 This resize is a simplified polymorphic form of above resize (FIX ME: cite resize with alignment). It takes two parameters as compared to the above resize that takes three parameters. It frees the programmer from explicitly mentioning the alignment of the allocation as CFA provides gives allocator the liberty to get the alignment of the returned type.
 \paragraph{Usage}
 This resize takes two parameters.
+
 \begin{itemize}
 \item
@@ -269,8 +282,9 @@
 It returns a dynamic object of the size given in paramters. The returned object is aligned to the alignemtn of type T. On failure, it return NULL pointer.
 
-\subsubsection T * realloc( T * ptr, size_t size )
+\subsubsection T * realloc( T * ptr, size\_t size )
 This realloc is a simplified polymorphic form of defualt realloc (FIX ME: cite realloc with align). It takes two parameters as compared to the above realloc that takes three parameters. It frees the programmer from explicitly mentioning the alignment of the allocation as CFA provides gives allocator the liberty to get the alignment of the returned type.
 \paragraph{Usage}
 This realloc takes two parameters.
+
 \begin{itemize}
 \item
@@ -281,8 +295,9 @@
 It returns a dynamic object of the size given in paramters that preserves the data in the given object. The returned object is aligned to the alignemtn of type T. On failure, it return NULL pointer.
 
-\subsubsection T * memalign( size_t align )
+\subsubsection T * memalign( size\_t align )
 This memalign is a simplified polymorphic form of defualt memalign (FIX ME: cite memalign). It takes one parameters as compared to the default memalign that takes two parameters.
 \paragraph{Usage}
 memalign takes one parameters.
+
 \begin{itemize}
 \item
@@ -291,8 +306,9 @@
 It returns a dynamic object of the size of type T that is aligned to given parameter align. On failure, it return NULL pointer.
 
-\subsubsection T * amemalign( size_t align, size_t dim )
+\subsubsection T * amemalign( size\_t align, size\_t dim )
 This amemalign is a simplified polymorphic form of above amemalign (FIX ME: cite amemalign). It takes two parameter as compared to the above amemalign that takes three parameters.
 \paragraph{Usage}
 amemalign takes two parameters.
+
 \begin{itemize}
 \item
@@ -303,8 +319,9 @@
 It returns a dynamic object that has the capacity to contain dim number of objects, each of the size of type T. The returned object is aligned to the given parameter align. On failure, it return NULL pointer.
 
-\subsubsection T * cmemalign( size_t align, size_t dim  )
+\subsubsection T * cmemalign( size\_t align, size\_t dim  )
 This cmemalign is a simplified polymorphic form of above cmemalign (FIX ME: cite cmemalign). It takes two parameter as compared to the above cmemalign that takes three parameters.
 \paragraph{Usage}
 cmemalign takes two parameters.
+
 \begin{itemize}
 \item
@@ -315,8 +332,9 @@
 It returns a dynamic object that has the capacity to contain dim number of objects, each of the size of type T. The returned object is aligned to the given parameter align and is zero filled. On failure, it return NULL pointer.
 
-\subsubsection T * aligned_alloc( size_t align )
-This aligned_alloc is a simplified polymorphic form of defualt aligned_alloc (FIX ME: cite aligned_alloc). It takes one parameter as compared to the default aligned_alloc that takes two parameters.
-\paragraph{Usage}
-This aligned_alloc takes one parameter.
+\subsubsection T * aligned\_alloc( size\_t align )
+This aligned\_alloc is a simplified polymorphic form of defualt aligned\_alloc (FIX ME: cite aligned\_alloc). It takes one parameter as compared to the default aligned\_alloc that takes two parameters.
+\paragraph{Usage}
+This aligned\_alloc takes one parameter.
+
 \begin{itemize}
 \item
@@ -325,8 +343,9 @@
 It returns a dynamic object of the size of type T that is aligned to the given parameter. On failure, it return NULL pointer.
 
-\subsubsection int posix_memalign( T ** ptr, size_t align )
-This posix_memalign is a simplified polymorphic form of defualt posix_memalign (FIX ME: cite posix_memalign). It takes two parameters as compared to the default posix_memalign that takes three parameters.
-\paragraph{Usage}
-This posix_memalign takes two parameter.
+\subsubsection int posix\_memalign( T ** ptr, size\_t align )
+This posix\_memalign is a simplified polymorphic form of defualt posix\_memalign (FIX ME: cite posix\_memalign). It takes two parameters as compared to the default posix\_memalign that takes three parameters.
+\paragraph{Usage}
+This posix\_memalign takes two parameter.
+
 \begin{itemize}
 \item
@@ -335,4 +354,5 @@
 align: required alignment of the dynamic object.
 \end{itemize}
+
 It stores address of the dynamic object of the size of type T in given parameter ptr. This object is aligned to the given parameter. On failure, it return NULL pointer.
 
@@ -349,7 +369,8 @@
 It returns a dynamic object of the size that is calcutaed by rouding the size of type T. The returned object is also aligned to the page size. On failure, it return NULL pointer.
 
-\subsection{Alloc Interface}
+\subsection Alloc Interface
 In addition to improve allocator interface both for CFA and our standalone allocator uHeapLmmm in C. We also added a new alloc interface in CFA that increases usability of dynamic memory allocation.
 This interface helps programmers in three major ways.
+
 \begin{itemize}
 \item
@@ -371,5 +392,5 @@
 This is the only parameter in the alloc routine that has a fixed-position and it is also the only parameter that does not use a backtick function. It has to be passed at the first position to alloc call in-case of an array allocation of objects of type T.
 It represents the required number of members in the array allocation as in CFA's aalloc (FIX ME: cite aalloc).
-This parameter should be of type size_t.
+This parameter should be of type size\_t.
 
 Example: int a = alloc( 5 )
@@ -377,5 +398,5 @@
 
 \paragraph{Align}
-This parameter is position-free and uses a backtick routine align (`align). The parameter passed with `align should be of type size_t. If the alignment parameter is not a power of two or is less than the default alignment of the allocator (that can be found out using routine libAlign in CFA) then the passed alignment parameter will be rejected and the default alignment will be used.
+This parameter is position-free and uses a backtick routine align (`align). The parameter passed with `align should be of type size\_t. If the alignment parameter is not a power of two or is less than the default alignment of the allocator (that can be found out using routine libAlign in CFA) then the passed alignment parameter will be rejected and the default alignment will be used.
 
 Example: int b = alloc( 5 , 64`align )
@@ -385,4 +406,5 @@
 This parameter is position-free and uses a backtick routine fill (`fill). In case of realloc, only the extra space after copying the data in the old object will be filled with given parameter.
 Three types of parameters can be passed using `fill.
+
 \begin{itemize}
 \item
Index: doc/theses/mubeen_zulfiqar_MMath/background.tex
===================================================================
--- doc/theses/mubeen_zulfiqar_MMath/background.tex	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ doc/theses/mubeen_zulfiqar_MMath/background.tex	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -23,3 +23,42 @@
 ====================
 
-\cite{Wasik08}
+\section{Background}
+
+% FIXME: cite wasik
+\cite{wasik.thesis}
+
+\subsection{Memory Allocation}
+With dynamic allocation being an important feature of C, there are many standalone memory allocators that have been designed for different purposes. For this thesis, we chose 7 of the most popular and widely used memory allocators.
+
+\paragraph{dlmalloc}
+dlmalloc (FIX ME: cite allocator) is a thread-safe allocator that is single threaded and single heap. dlmalloc maintains free-lists of different sizes to store freed dynamic memory. (FIX ME: cite wasik)
+
+\paragraph{hoard}
+Hoard (FIX ME: cite allocator) is a thread-safe allocator that is multi-threaded and using a heap layer framework. It has per-thred heaps that have thread-local free-lists, and a gloabl shared heap. (FIX ME: cite wasik)
+
+\paragraph{jemalloc}
+jemalloc (FIX ME: cite allocator) is a thread-safe allocator that uses multiple arenas. Each thread is assigned an arena. Each arena has chunks that contain contagious memory regions of same size. An arena has multiple chunks that contain regions of multiple sizes.
+
+\paragraph{ptmalloc}
+ptmalloc (FIX ME: cite allocator) is a modification of dlmalloc. It is a thread-safe multi-threaded memory allocator that uses multiple heaps. ptmalloc heap has similar design to dlmalloc's heap.
+
+\paragraph{rpmalloc}
+rpmalloc (FIX ME: cite allocator) is a thread-safe allocator that is multi-threaded and uses per-thread heap. Each heap has multiple size-classes and each size-calss contains memory regions of the relevant size.
+
+\paragraph{tbb malloc}
+tbb malloc (FIX ME: cite allocator) is a thread-safe allocator that is multi-threaded and uses private heap for each thread. Each private-heap has multiple bins of different sizes. Each bin contains free regions of the same size.
+
+\paragraph{tc malloc}
+tcmalloc (FIX ME: cite allocator) is a thread-safe allocator. It uses per-thread cache to store free objects that prevents contention on shared resources in multi-threaded application. A central free-list is used to refill per-thread cache when it gets empty.
+
+\subsection{Benchmarks}
+There are multiple benchmarks that are built individually and evaluate different aspects of a memory allocator. But, there is not standard set of benchamrks that can be used to evaluate multiple aspects of memory allocators.
+
+\paragraph{threadtest}
+(FIX ME: cite benchmark and hoard) Each thread repeatedly allocates and then deallocates 100,000 objects. Runtime of the benchmark evaluates its efficiency.
+
+\paragraph{shbench}
+(FIX ME: cite benchmark and hoard) Each thread allocates and randomly frees a number of random-sized objects. It is a stress test that also uses runtime to determine efficiency of the allocator.
+
+\paragraph{larson}
+(FIX ME: cite benchmark and hoard) Larson simulates a server environment. Multiple threads are created where each thread allocator and free a number of objects within a size range. Some objects are passed from threads to the child threads to free. It caluculates memory operations per second as an indicator of memory allocator's performance.
Index: doc/theses/mubeen_zulfiqar_MMath/benchmarks.tex
===================================================================
--- doc/theses/mubeen_zulfiqar_MMath/benchmarks.tex	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ doc/theses/mubeen_zulfiqar_MMath/benchmarks.tex	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -149,5 +149,5 @@
 *** FIX ME: Insert a figure of above benchmark with description
 
-\paragrpah{Relevant Knobs}
+\paragraph{Relevant Knobs}
 *** FIX ME: Insert Relevant Knobs
 
Index: doc/theses/mubeen_zulfiqar_MMath/intro.tex
===================================================================
--- doc/theses/mubeen_zulfiqar_MMath/intro.tex	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ doc/theses/mubeen_zulfiqar_MMath/intro.tex	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -47,11 +47,11 @@
 \begin{itemize}
 \item
-aligned_alloc
+aligned\_alloc
 \item
-malloc_usable_size
+malloc\_usable\_size
 \item
 memalign
 \item
-posix_memalign
+posix\_memalign
 \item
 pvalloc
@@ -61,42 +61,4 @@
 
 With the rise of concurrent applications, memory allocators should be able to fulfill dynamic memory requests from multiple threads in parallel without causing contention on shared resources. There needs to be a set of a standard benchmarks that can be used to evaluate an allocator's performance in different scenerios.
-
-\section{Background}
-
-\subsection{Memory Allocation}
-With dynamic allocation being an important feature of C, there are many standalone memory allocators that have been designed for different purposes. For this thesis, we chose 7 of the most popular and widely used memory allocators.
-
-\paragraph{dlmalloc}
-dlmalloc (FIX ME: cite allocator) is a thread-safe allocator that is single threaded and single heap. dlmalloc maintains free-lists of different sizes to store freed dynamic memory. (FIX ME: cite wasik)
-
-\paragraph{hoard}
-Hoard (FIX ME: cite allocator) is a thread-safe allocator that is multi-threaded and using a heap layer framework. It has per-thred heaps that have thread-local free-lists, and a gloabl shared heap. (FIX ME: cite wasik)
-
-\paragraph{jemalloc}
-jemalloc (FIX ME: cite allocator) is a thread-safe allocator that uses multiple arenas. Each thread is assigned an arena. Each arena has chunks that contain contagious memory regions of same size. An arena has multiple chunks that contain regions of multiple sizes.
-
-\paragraph{ptmalloc}
-ptmalloc (FIX ME: cite allocator) is a modification of dlmalloc. It is a thread-safe multi-threaded memory allocator that uses multiple heaps. ptmalloc heap has similar design to dlmalloc's heap.
-
-\paragraph{rpmalloc}
-rpmalloc (FIX ME: cite allocator) is a thread-safe allocator that is multi-threaded and uses per-thread heap. Each heap has multiple size-classes and each size-calss contains memory regions of the relevant size.
-
-\paragraph{tbb malloc}
-tbb malloc (FIX ME: cite allocator) is a thread-safe allocator that is multi-threaded and uses private heap for each thread. Each private-heap has multiple bins of different sizes. Each bin contains free regions of the same size.
-
-\paragraph{tc malloc}
-tcmalloc (FIX ME: cite allocator) is a thread-safe allocator. It uses per-thread cache to store free objects that prevents contention on shared resources in multi-threaded application. A central free-list is used to refill per-thread cache when it gets empty.
-
-\subsection{Benchmarks}
-There are multiple benchmarks that are built individually and evaluate different aspects of a memory allocator. But, there is not standard set of benchamrks that can be used to evaluate multiple aspects of memory allocators.
-
-\paragraph{threadtest}
-(FIX ME: cite benchmark and hoard) Each thread repeatedly allocates and then deallocates 100,000 objects. Runtime of the benchmark evaluates its efficiency.
-
-\paragraph{shbench}
-(FIX ME: cite benchmark and hoard) Each thread allocates and randomly frees a number of random-sized objects. It is a stress test that also uses runtime to determine efficiency of the allocator.
-
-\paragraph{larson}
-(FIX ME: cite benchmark and hoard) Larson simulates a server environment. Multiple threads are created where each thread allocator and free a number of objects within a size range. Some objects are passed from threads to the child threads to free. It caluculates memory operations per second as an indicator of memory allocator's performance.
 
 \section{Research Objectives}
Index: doc/theses/mubeen_zulfiqar_MMath/performance.tex
===================================================================
--- doc/theses/mubeen_zulfiqar_MMath/performance.tex	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ doc/theses/mubeen_zulfiqar_MMath/performance.tex	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -44,5 +44,6 @@
 tc               &             &  \\
 \end{tabularx}
-(FIX ME: complete table)
+
+%(FIX ME: complete table)
 
 \section{Experiment Environment}
Index: c/theses/mubeen_zulfiqar_MMath/thesis.tex
===================================================================
--- doc/theses/mubeen_zulfiqar_MMath/thesis.tex	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ 	(revision )
@@ -1,12 +1,0 @@
-\documentclass[letterpaper,12pt,titlepage,oneside,final]{book}
-
-\usepackage{amsmath,amssymb,amsfonts}
-
-\begin{document}
-
-\section{Benchmark Suite}
-
-\section{Memory Allocator}
-
-\end{document}
-
Index: doc/theses/mubeen_zulfiqar_MMath/uw-ethesis.bib
===================================================================
--- doc/theses/mubeen_zulfiqar_MMath/uw-ethesis.bib	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ doc/theses/mubeen_zulfiqar_MMath/uw-ethesis.bib	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -27,2 +27,9 @@
 	address =       "Reading, Massachusetts"
 }
+
+@article{wasik.thesis,
+    author        = "Ayelet Wasik",
+    title         = "Features of A Multi-Threaded Memory Alloator",
+    publisher	  = "University of Waterloo",
+    year          = "2008"
+}
Index: doc/theses/mubeen_zulfiqar_MMath/uw-ethesis.tex
===================================================================
--- doc/theses/mubeen_zulfiqar_MMath/uw-ethesis.tex	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ doc/theses/mubeen_zulfiqar_MMath/uw-ethesis.tex	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -84,4 +84,5 @@
 \usepackage{graphicx}
 \usepackage{comment} % Removes large sections of the document.
+\usepackage{tabularx}
 
 % Hyperlinks make it very easy to navigate an electronic document.
@@ -191,4 +192,6 @@
 % Tip: Putting each sentence on a new line is a way to simplify later editing.
 %----------------------------------------------------------------------
+\begin{sloppypar}
+
 \input{intro}
 \input{background}
@@ -197,4 +200,6 @@
 \input{performance}
 \input{conclusion}
+
+\end{sloppypar}
 
 %----------------------------------------------------------------------
Index: doc/theses/thierry_delisle_PhD/.gitignore
===================================================================
--- doc/theses/thierry_delisle_PhD/.gitignore	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ doc/theses/thierry_delisle_PhD/.gitignore	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -13,4 +13,8 @@
 comp_II/presentation.pdf
 
+seminars/build/
+seminars/img/*.fig.bak
+seminars/*.pdf
+
 thesis/build/
 thesis/fig/*.fig.bak
Index: doc/theses/thierry_delisle_PhD/thesis/Makefile
===================================================================
--- doc/theses/thierry_delisle_PhD/thesis/Makefile	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ doc/theses/thierry_delisle_PhD/thesis/Makefile	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -20,4 +20,6 @@
 	practice \
 	io \
+	eval_micro \
+	eval_macro \
 }}
 
@@ -35,4 +37,5 @@
 	pivot_ring \
 	system \
+	cycle \
 }
 
Index: doc/theses/thierry_delisle_PhD/thesis/fig/cycle.fig
===================================================================
--- doc/theses/thierry_delisle_PhD/thesis/fig/cycle.fig	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
+++ doc/theses/thierry_delisle_PhD/thesis/fig/cycle.fig	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -0,0 +1,34 @@
+#FIG 3.2  Produced by xfig version 3.2.7b
+Landscape
+Center
+Inches
+Letter
+100.00
+Single
+-2
+1200 2
+5 1 0 1 0 7 50 -1 -1 0.000 0 1 1 0 3144.643 2341.072 3525 2250 3375 2025 3150 1950
+	2 0 1.00 60.00 120.00
+5 1 0 1 0 7 50 -1 -1 0.000 0 1 1 0 1955.357 2341.072 1950 1950 1725 2025 1575 2250
+	2 0 1.00 60.00 120.00
+5 1 0 1 0 7 50 -1 -1 0.000 0 1 1 0 3637.500 3487.500 3750 3750 3900 3600 3900 3375
+	2 0 1.00 60.00 120.00
+5 1 0 1 0 7 50 -1 -1 0.000 0 1 1 0 2587.500 4087.500 2325 4500 2550 4575 2850 4500
+	2 0 1.00 60.00 120.00
+5 1 0 1 0 7 50 -1 -1 0.000 0 1 1 0 1612.500 3487.500 1200 3375 1200 3600 1350 3825
+	2 0 1.00 60.00 120.00
+1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 3675 2850 586 586 3675 2850 4125 3225
+1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 3300 4125 586 586 3300 4125 3750 4500
+1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 1875 4125 586 586 1875 4125 2325 4500
+1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 1425 2850 586 586 1425 2850 1875 3225
+1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 2550 1950 586 586 2550 1950 3000 2325
+4 0 0 50 -1 0 11 0.0000 2 135 720 1125 2925 Thread 2\001
+4 2 0 50 -1 0 11 0.0000 2 165 540 1650 1950 Unpark\001
+4 0 0 50 -1 0 11 0.0000 2 165 540 4050 3600 Unpark\001
+4 2 0 50 -1 0 11 0.0000 2 165 540 1125 3750 Unpark\001
+4 2 0 50 -1 0 11 0.0000 2 165 540 2850 4800 Unpark\001
+4 0 0 50 -1 0 11 0.0000 2 135 720 2250 2025 Thread 1\001
+4 0 0 50 -1 0 11 0.0000 2 135 720 3000 4200 Thread 4\001
+4 0 0 50 -1 0 11 0.0000 2 135 720 1575 4200 Thread 3\001
+4 0 0 50 -1 0 11 0.0000 2 165 540 3525 2025 Unpark\001
+4 0 0 50 -1 0 11 0.0000 2 135 720 3375 2925 Thread 5\001
Index: doc/theses/thierry_delisle_PhD/thesis/text/eval_macro.tex
===================================================================
--- doc/theses/thierry_delisle_PhD/thesis/text/eval_macro.tex	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
+++ doc/theses/thierry_delisle_PhD/thesis/text/eval_macro.tex	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -0,0 +1,15 @@
+\chapter{Macro-Benchmarks}\label{macrobench}
+
+\section{Static Web-Server}
+
+In Memory Plain Text
+
+Networked Plain Text
+
+Networked ZIPF
+
+\section{Memcached}
+
+In Memory
+
+Networked
Index: doc/theses/thierry_delisle_PhD/thesis/text/eval_micro.tex
===================================================================
--- doc/theses/thierry_delisle_PhD/thesis/text/eval_micro.tex	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
+++ doc/theses/thierry_delisle_PhD/thesis/text/eval_micro.tex	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -0,0 +1,50 @@
+\chapter{Micro-Benchmarks}\label{microbench}
+
+The first step of evaluation is always to test-out small controlled cases, to ensure that the basics are working properly.
+This sections presents four different experimental setup, evaluating some of the basic features of \CFA's scheduler.
+
+\section{Cycling latency}
+The most basic evaluation of any ready queue is to evaluate the latency needed to push and pop one element from the ready-queue.
+While these two operation also describe a \texttt{yield} operation, many systems use this as the most basic benchmark.
+However, yielding can be treated as a special case, since it also carries the information that the length of the ready queue will not change.
+Not all systems use this information, but those which do may appear to have better performance than they would for disconnected push/pop pairs.
+For this reason, I chose a different first benchmark, which I call the Cycle Benchmark.
+This benchmark arranges many threads into multiple rings of threads.
+Each ring is effectively a circular singly-linked list.
+At runtime, each thread unparks the next thread before parking itself.
+This corresponds to the desired pair of ready queue operations.
+Unparking the next thread requires pushing that thread onto the ready queue and the ensuing park will cause the runtime to pop a thread from the ready-queue.
+Figure~\ref{fig:cycle} shows a visual representation of this arrangement.
+
+The goal of this ring is that the underlying runtime cannot rely on the guarantee that the number of ready threads will stay constant over the duration of the experiment.
+In fact, the total number of threads waiting on the ready is expected to vary a little because of the race between the next thread unparking and the current thread parking.
+The size of the cycle is also decided based on this race: cycles that are too small may see the
+chain of unparks go full circle before the first thread can park.
+While this would not be a correctness problem, every runtime system must handle that race, it could lead to pushes and pops being optimized away.
+Since silently omitting ready-queue operations would throw off the measuring of these operations.
+Therefore the ring of threads must be big enough so the threads have the time to fully park before they are unparked.
+Note that this problem is only present on SMP machines and is significantly mitigated by the fact that there are multiple rings in the system.
+
+\begin{figure}
+	\centering
+	\input{cycle.pstex_t}
+	\caption[Cycle benchmark]{Cycle benchmark\smallskip\newline Each thread unparks the next thread in the cycle before parking itself.}
+	\label{fig:cycle}
+\end{figure}
+
+\todo{check term ``idle sleep handling''}
+To avoid this benchmark from being dominated by the idle sleep handling, the number of rings is kept at least as high as the number of processors available.
+Beyond this point, adding more rings serves to mitigate even more the idle sleep handling.
+This is to avoid the case where one of the worker threads runs out of work because of the variation on the number of ready threads mentionned above.
+
+The actual benchmark is more complicated to handle termination, but that simply requires using a binary semphore or a channel instead of raw \texttt{park}/\texttt{unpark} and carefully picking the order of the \texttt{P} and \texttt{V} with respect to the loop condition.
+
+\todo{mention where to get the code.}
+
+\section{Yield}
+For completion, I also include the yield benchmark.
+This benchmark is much simpler than the cycle tests, it simply creates many threads that call \texttt{yield}.
+
+\section{Locality}
+
+\section{Transfer}
Index: doc/theses/thierry_delisle_PhD/thesis/thesis.tex
===================================================================
--- doc/theses/thierry_delisle_PhD/thesis/thesis.tex	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ doc/theses/thierry_delisle_PhD/thesis/thesis.tex	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -1,6 +1,6 @@
 %======================================================================
-% University of Waterloo Thesis Template for LaTeX 
-% Last Updated November, 2020 
-% by Stephen Carr, IST Client Services, 
+% University of Waterloo Thesis Template for LaTeX
+% Last Updated November, 2020
+% by Stephen Carr, IST Client Services,
 % University of Waterloo, 200 University Ave. W., Waterloo, Ontario, Canada
 % FOR ASSISTANCE, please send mail to request@uwaterloo.ca
@@ -15,15 +15,15 @@
 % Some important notes on using this template and making it your own...
 
-% The University of Waterloo has required electronic thesis submission since October 2006. 
+% The University of Waterloo has required electronic thesis submission since October 2006.
 % See the uWaterloo thesis regulations at
 % https://uwaterloo.ca/graduate-studies/thesis.
 % This thesis template is geared towards generating a PDF version optimized for viewing on an electronic display, including hyperlinks within the PDF.
 
-% DON'T FORGET TO ADD YOUR OWN NAME AND TITLE in the "hyperref" package configuration below. 
+% DON'T FORGET TO ADD YOUR OWN NAME AND TITLE in the "hyperref" package configuration below.
 % THIS INFORMATION GETS EMBEDDED IN THE PDF FINAL PDF DOCUMENT.
 % You can view the information if you view properties of the PDF document.
 
-% Many faculties/departments also require one or more printed copies. 
-% This template attempts to satisfy both types of output. 
+% Many faculties/departments also require one or more printed copies.
+% This template attempts to satisfy both types of output.
 % See additional notes below.
 % It is based on the standard "book" document class which provides all necessary sectioning structures and allows multi-part theses.
@@ -32,15 +32,15 @@
 
 % For people who prefer to install their own LaTeX distributions on their own computers, and process the source files manually, the following notes provide the sequence of tasks:
- 
+
 % E.g. to process a thesis called "mythesis.tex" based on this template, run:
 
 % pdflatex mythesis	-- first pass of the pdflatex processor
 % bibtex mythesis	-- generates bibliography from .bib data file(s)
-% makeindex         -- should be run only if an index is used 
+% makeindex         -- should be run only if an index is used
 % pdflatex mythesis	-- fixes numbering in cross-references, bibliographic references, glossaries, index, etc.
 % pdflatex mythesis	-- it takes a couple of passes to completely process all cross-references
 
 % If you use the recommended LaTeX editor, Texmaker, you would open the mythesis.tex file, then click the PDFLaTeX button. Then run BibTeX (under the Tools menu).
-% Then click the PDFLaTeX button two more times. 
+% Then click the PDFLaTeX button two more times.
 % If you have an index as well,you'll need to run MakeIndex from the Tools menu as well, before running pdflatex
 % the last two times.
@@ -51,5 +51,5 @@
 % Tip: Photographs should be cropped and compressed so as not to be too large.
 
-% To create a PDF output that is optimized for double-sided printing: 
+% To create a PDF output that is optimized for double-sided printing:
 % 1) comment-out the \documentclass statement in the preamble below, and un-comment the second \documentclass line.
 % 2) change the value assigned below to the boolean variable "PrintVersion" from " false" to "true".
@@ -67,5 +67,5 @@
 % If you have to, it's easier to make changes to nomenclature once here than in a million places throughout your thesis!
 \newcommand{\package}[1]{\textbf{#1}} % package names in bold text
-\newcommand{\cmmd}[1]{\textbackslash\texttt{#1}} % command name in tt font 
+\newcommand{\cmmd}[1]{\textbackslash\texttt{#1}} % command name in tt font
 \newcommand{\href}[1]{#1} % does nothing, but defines the command so the print-optimized version will ignore \href tags (redefined by hyperref pkg).
 %\newcommand{\texorpdfstring}[2]{#1} % does nothing, but defines the command
@@ -235,7 +235,7 @@
 \part{Evaluation}
 \label{Evaluation}
-\chapter{Theoretical and Existance Proofs}
-\chapter{Micro-Benchmarks}
-\chapter{Larger-Scale applications}
+% \chapter{Theoretical and Existance Proofs}
+\input{text/eval_micro.tex}
+\input{text/eval_macro.tex}
 \part{Conclusion \& Annexes}
 
Index: doc/user/user.tex
===================================================================
--- doc/user/user.tex	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ doc/user/user.tex	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -11,6 +11,6 @@
 %% Created On       : Wed Apr  6 14:53:29 2016
 %% Last Modified By : Peter A. Buhr
-%% Last Modified On : Mon May 31 09:03:34 2021
-%% Update Count     : 5071
+%% Last Modified On : Sun Oct 10 12:45:00 2021
+%% Update Count     : 5095
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 
@@ -4444,8 +4444,8 @@
 \CFA provides a fine-grained solution where a \Index{recursive lock} is acquired and released indirectly via a manipulator ©acquire© or instantiating an \Index{RAII} type specific for the kind of stream: ©osacquire©\index{ostream@©ostream©!osacquire@©osacquire©} for output streams and ©isacquire©\index{isacquire@©isacquire©}\index{istream@©istream©!isacquire@©isacquire©} for input streams.
 
-The common usage is manipulator ©acquire©\index{ostream@©ostream©!acquire@©acquire©} to lock a stream during a single cascaded I/O expression, with the manipulator appearing as the first item in a cascade list, \eg:
-\begin{cfa}
-$\emph{thread\(_1\)}$ : sout | ®acquire® | "abc " | "def ";   // manipulator
-$\emph{thread\(_2\)}$ : sout | ®acquire® | "uvw " | "xyz ";
+The common usage is the short form of the mutex statement\index{ostream@©ostream©!mutex@©mutex©} to lock a stream during a single cascaded I/O expression, \eg:
+\begin{cfa}
+$\emph{thread\(_1\)}$ : ®mutex()® sout | "abc " | "def ";
+$\emph{thread\(_2\)}$ : ®mutex()® sout | "uvw " | "xyz ";
 \end{cfa}
 Now, the order of the thread execution is still non-deterministic, but the output is constrained to two possible lines in either order.
@@ -4466,25 +4466,23 @@
 In summary, the stream lock is acquired by the ©acquire© manipulator and implicitly released at the end of the cascaded I/O expression ensuring all operations in the expression occur atomically.
 
-To lock a stream across multiple I/O operations, an object of type ©osacquire© or ©isacquire© is declared to implicitly acquire/release the stream lock providing mutual exclusion for the object's duration, \eg:
-\begin{cfa}
-{	// acquire sout for block duration
-	®osacquire® acq = { sout };				$\C{// named stream locker}$
+To lock a stream across multiple I/O operations, he long form of the mutex statement is used, \eg:
+\begin{cfa}
+®mutex( sout )® {
 	sout | 1;
-	sout | ®acquire® | 2 | 3;				$\C{// unnecessary, but ok to acquire and release again}$
+	®mutex() sout® | 2 | 3;				$\C{// unnecessary, but ok because of recursive lock}$
 	sout | 4;
-}	// implicitly release the lock when "acq" is deallocated
-\end{cfa}
-Note, the unnecessary ©acquire© manipulator works because the recursive stream-lock can be acquired/released multiple times by the owner thread.
+} // implicitly release sout lock
+\end{cfa}
+Note, the unnecessary ©mutex© in the middle of the mutex statement, works because the recursive stream-lock can be acquired/released multiple times by the owner thread.
 Hence, calls to functions that also acquire a stream lock for their output do not result in \Index{deadlock}.
 
 The previous values written by threads 1 and 2 can be read in concurrently:
 \begin{cfa}
-{	// acquire sin lock for block duration
-	®isacquire acq = { sin };®				$\C{// named stream locker}$
+®mutex( sin )® {
 	int x, y, z, w;
 	sin | x;
-	sin | ®acquire® | y | z;				$\C{// unnecessary, but ok to acquire and release again}$
+	®mutex() sin® | y | z;				$\C{// unnecessary, but ok because of recursive lock}$
 	sin | w;
-}	// implicitly release the lock when "acq" is deallocated
+} // implicitly release sin lock
 \end{cfa}
 Again, the order of the reading threads is non-deterministic.
@@ -4493,5 +4491,5 @@
 \Textbf{WARNING:} The general problem of \Index{nested locking} can occur if routines are called in an I/O sequence that block, \eg:
 \begin{cfa}
-sout | ®acquire® | "data:" | rtn( mon );	$\C{// mutex call on monitor}$
+®mutex() sout® | "data:" | rtn( mon );	$\C{// mutex call on monitor}$
 \end{cfa}
 If the thread executing the I/O expression blocks in the monitor with the ©sout© lock, other threads writing to ©sout© also block until the thread holding the lock is unblocked and releases it.
@@ -4500,5 +4498,5 @@
 \begin{cfa}
 int ®data® = rtn( mon );
-sout | acquire | "data:" | ®data®;
+mutex() sout | "data:" | ®data®;
 \end{cfa}
 
@@ -4506,6 +4504,22 @@
 \section{String Stream}
 
-All the stream formatting capabilities are available to format text to/from a C string rather than to a stream file.
-\VRef[Figure]{f:StringStreamProcessing} shows writing (output) and reading (input) from a C string.
+The stream types ©ostrstream© and ©istrstream© provide all the stream formatting capabilities to/from a C string rather than a stream file.
+\VRef[Figure]{f:StringStreamProcessing} shows writing (output) to and reading (input) from a C string.
+The only string stream operations different from a file stream are:
+\begin{itemize}[topsep=4pt,itemsep=2pt,parsep=0pt]
+\item
+constructors to create a stream that writes to a write buffer (©ostrstream©) of ©size©, or reads from a read buffer (©istrstream©) containing a C string terminated with ©'\0'©.
+\begin{cfa}
+void ?{}( ostrstream &, char buf[], size_t size );
+void ?{}( istrstream & is, char buf[] );
+\end{cfa}
+\item
+\Indexc{write} (©ostrstream© only) writes all the buffered characters to the specified stream (©stdout© default).
+\begin{cfa}
+ostrstream & write( ostrstream & os, FILE * stream = stdout );
+\end{cfa}
+There is no ©read© for ©istrstream©.
+\end{itemize}
+
 \begin{figure}
 \begin{cfa}
@@ -4520,46 +4534,24 @@
 	double x = 12345678.9, y = 98765.4321e-11;
 
-	osstr | i | hex(j) | wd(10, k) | sci(x) | unit(eng(y)); $\C{// same lines of output}$
-	write( osstr );
-	printf( "%s", buf );
-	sout | i | hex(j) | wd(10, k) | sci(x) | unit(eng(y));
-
-	char buf2[] = "12 14 15 3.5 7e4"; $\C{// input buffer}$
+	osstr | i | hex(j) | wd(10, k) | sci(x) | unit(eng(y)) | "abc";
+	write( osstr ); $\C{// write string to stdout}$
+	printf( "%s", buf ); $\C{// same lines of output}$
+	sout | i | hex(j) | wd(10, k) | sci(x) | unit(eng(y)) | "abc";
+
+	char buf2[] = "12 14 15 3.5 7e4 abc"; $\C{// input buffer}$
 	®istrstream isstr = { buf2 };®
-	isstr | i | j | k | x | y;
-	sout | i | j | k | x | y;
-}
+	char s[10];
+	isstr | i | j | k | x | y | s;
+	sout  | i | j | k | x | y | s;
+}
+
+3 0x5          7 1.234568e+07 987.654n abc
+3 0x5          7 1.234568e+07 987.654n abc
+3 0x5          7 1.234568e+07 987.654n abc
+12 14 15 3.5 70000. abc
 \end{cfa}
 \caption{String Stream Processing}
 \label{f:StringStreamProcessing}
 \end{figure}
-
-\VRef[Figure]{f:StringStreamFunctions} shows the string stream operations.
-\begin{itemize}[topsep=4pt,itemsep=2pt,parsep=0pt]
-\item
-\Indexc{write} (©ostrstream© only) writes all the buffered characters to the specified stream (©stdout© default).
-\end{itemize}
-The constructor functions:
-\begin{itemize}[topsep=4pt,itemsep=2pt,parsep=0pt]
-\item
-create a bound stream to a write buffer (©ostrstream©) of ©size© or a read buffer (©istrstream©) containing a C string terminated with ©'\0'©.
-\end{itemize}
-
-\begin{figure}
-\begin{cfa}
-// *********************************** ostrstream ***********************************
-
-ostrstream & write( ostrstream & os, FILE * stream = stdout );
-
-void ?{}( ostrstream &, char buf[], size_t size );
-
-// *********************************** istrstream ***********************************
-
-void ?{}( istrstream & is, char buf[] );
-\end{cfa}
-\caption{String Stream Functions}
-\label{f:StringStreamFunctions}
-\end{figure}
-
 
 \begin{comment}
Index: libcfa/prelude/bootloader.cf
===================================================================
--- libcfa/prelude/bootloader.cf	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ libcfa/prelude/bootloader.cf	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -3,5 +3,5 @@
 char ** cfa_args_argv;
 char ** cfa_args_envp;
-int cfa_main_returned = 0;
+__attribute__((weak)) extern int cfa_main_returned;
 
 int main(int argc, char* argv[], char* envp[]) {
@@ -10,5 +10,5 @@
 	cfa_args_envp = envp;
 	int ret = invoke_main(argc, argv, envp);
-	cfa_main_returned = 1;
+	if(&cfa_main_returned) cfa_main_returned = 1;
 	return ret;
 }
Index: libcfa/src/concurrency/io.cfa
===================================================================
--- libcfa/src/concurrency/io.cfa	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ libcfa/src/concurrency/io.cfa	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -133,5 +133,5 @@
 	}
 
-	void __cfa_io_flush( processor * proc ) {
+	bool __cfa_io_flush( processor * proc, bool wait ) {
 		/* paranoid */ verify( ! __preemption_enabled() );
 		/* paranoid */ verify( proc );
@@ -141,13 +141,8 @@
 		$io_context & ctx = *proc->io.ctx;
 
-		// for(i; 2) {
-		// 	unsigned idx = proc->rdq.id + i;
-		// 	cltr->ready_queue.lanes.tscs[idx].tv = -1ull;
-		// }
-
 		__ioarbiter_flush( ctx );
 
 		__STATS__( true, io.calls.flush++; )
-		int ret = syscall( __NR_io_uring_enter, ctx.fd, ctx.sq.to_submit, 0, 0, (sigset_t *)0p, _NSIG / 8);
+		int ret = syscall( __NR_io_uring_enter, ctx.fd, ctx.sq.to_submit, wait ? 1 : 0, 0, (sigset_t *)0p, _NSIG / 8);
 		if( ret < 0 ) {
 			switch((int)errno) {
@@ -157,9 +152,5 @@
 				// Update statistics
 				__STATS__( false, io.calls.errors.busy ++; )
-				// for(i; 2) {
-				// 	unsigned idx = proc->rdq.id + i;
-				// 	cltr->ready_queue.lanes.tscs[idx].tv = rdtscl();
-				// }
-				return;
+				return false;
 			default:
 				abort( "KERNEL ERROR: IO_URING SYSCALL - (%d) %s\n", (int)errno, strerror(errno) );
@@ -182,12 +173,8 @@
 
 		ctx.proc->io.pending = false;
-
 		ready_schedule_lock();
-		__cfa_io_drain( proc );
+		bool ret = __cfa_io_drain( proc );
 		ready_schedule_unlock();
-		// for(i; 2) {
-		// 	unsigned idx = proc->rdq.id + i;
-		// 	cltr->ready_queue.lanes.tscs[idx].tv = rdtscl();
-		// }
+		return ret;
 	}
 
@@ -293,5 +280,4 @@
 	}
 
-
 	//=============================================================================================
 	// submission
@@ -311,10 +297,10 @@
 		// Make the sqes visible to the submitter
 		__atomic_store_n(sq.kring.tail, tail + have, __ATOMIC_RELEASE);
-		sq.to_submit++;
+		sq.to_submit += have;
 
 		ctx->proc->io.pending = true;
 		ctx->proc->io.dirty   = true;
 		if(sq.to_submit > 30 || !lazy) {
-			__cfa_io_flush( ctx->proc );
+			__cfa_io_flush( ctx->proc, false );
 		}
 	}
@@ -515,3 +501,42 @@
 		}
 	}
+
+	#if defined(IO_URING_IDLE)
+		bool __kernel_read(processor * proc, io_future_t & future, char buf[], int fd) {
+			$io_context * ctx = proc->io.ctx;
+			/* paranoid */ verify( ! __preemption_enabled() );
+			/* paranoid */ verify( proc == __cfaabi_tls.this_processor );
+			/* paranoid */ verify( ctx );
+
+			__u32 idx;
+			struct io_uring_sqe * sqe;
+
+			// We can proceed to the fast path
+			if( !__alloc(ctx, &idx, 1) ) return false;
+
+			// Allocation was successful
+			__fill( &sqe, 1, &idx, ctx );
+
+			sqe->opcode = IORING_OP_READ;
+			sqe->user_data = (uintptr_t)&future;
+			sqe->flags = 0;
+			sqe->ioprio = 0;
+			sqe->fd = 0;
+			sqe->off = 0;
+			sqe->fsync_flags = 0;
+			sqe->__pad2[0] = 0;
+			sqe->__pad2[1] = 0;
+			sqe->__pad2[2] = 0;
+			sqe->addr = (uintptr_t)buf;
+			sqe->len = sizeof(uint64_t);
+
+			asm volatile("": : :"memory");
+
+			/* paranoid */ verify( sqe->user_data == (uintptr_t)&future );
+			__submit( ctx, &idx, 1, true );
+
+			/* paranoid */ verify( proc == __cfaabi_tls.this_processor );
+			/* paranoid */ verify( ! __preemption_enabled() );
+		}
+	#endif
 #endif
Index: libcfa/src/concurrency/io/setup.cfa
===================================================================
--- libcfa/src/concurrency/io/setup.cfa	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ libcfa/src/concurrency/io/setup.cfa	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -32,5 +32,5 @@
 
 	void __cfa_io_start( processor * proc ) {}
-	void __cfa_io_flush( processor * proc ) {}
+	bool __cfa_io_flush( processor * proc, bool ) {}
 	void __cfa_io_stop ( processor * proc ) {}
 
@@ -111,5 +111,5 @@
 		this.ext_sq.empty = true;
 		(this.ext_sq.queue){};
-		__io_uring_setup( this, cl.io.params, proc->idle );
+		__io_uring_setup( this, cl.io.params, proc->idle_fd );
 		__cfadbg_print_safe(io_core, "Kernel I/O : Created ring for io_context %u (%p)\n", this.fd, &this);
 	}
@@ -220,19 +220,21 @@
 		cq.cqes = (struct io_uring_cqe *)(((intptr_t)cq.ring_ptr) + params.cq_off.cqes);
 
-		// Step 4 : eventfd
-		// io_uring_register is so f*cking slow on some machine that it
-		// will never succeed if preemption isn't hard blocked
-		__cfadbg_print_safe(io_core, "Kernel I/O : registering %d for completion with ring %d\n", procfd, fd);
-
-		__disable_interrupts_hard();
-
-		int ret = syscall( __NR_io_uring_register, fd, IORING_REGISTER_EVENTFD, &procfd, 1);
-		if (ret < 0) {
-			abort("KERNEL ERROR: IO_URING EVENTFD REGISTER - %s\n", strerror(errno));
-		}
-
-		__enable_interrupts_hard();
-
-		__cfadbg_print_safe(io_core, "Kernel I/O : registered %d for completion with ring %d\n", procfd, fd);
+		#if !defined(IO_URING_IDLE)
+			// Step 4 : eventfd
+			// io_uring_register is so f*cking slow on some machine that it
+			// will never succeed if preemption isn't hard blocked
+			__cfadbg_print_safe(io_core, "Kernel I/O : registering %d for completion with ring %d\n", procfd, fd);
+
+			__disable_interrupts_hard();
+
+			int ret = syscall( __NR_io_uring_register, fd, IORING_REGISTER_EVENTFD, &procfd, 1);
+			if (ret < 0) {
+				abort("KERNEL ERROR: IO_URING EVENTFD REGISTER - %s\n", strerror(errno));
+			}
+
+			__enable_interrupts_hard();
+
+			__cfadbg_print_safe(io_core, "Kernel I/O : registered %d for completion with ring %d\n", procfd, fd);
+		#endif
 
 		// some paranoid checks
Index: libcfa/src/concurrency/io/types.hfa
===================================================================
--- libcfa/src/concurrency/io/types.hfa	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ libcfa/src/concurrency/io/types.hfa	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -185,6 +185,6 @@
 
 	// Wait for the future to be fulfilled
-	bool wait( io_future_t & this ) {
-		return wait(this.self);
-	}
+	bool wait     ( io_future_t & this ) { return wait     (this.self); }
+	void reset    ( io_future_t & this ) { return reset    (this.self); }
+	bool available( io_future_t & this ) { return available(this.self); }
 }
Index: libcfa/src/concurrency/kernel.cfa
===================================================================
--- libcfa/src/concurrency/kernel.cfa	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ libcfa/src/concurrency/kernel.cfa	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -34,4 +34,5 @@
 #include "strstream.hfa"
 #include "device/cpu.hfa"
+#include "io/types.hfa"
 
 //Private includes
@@ -124,13 +125,17 @@
 static void __wake_one(cluster * cltr);
 
-static void mark_idle (__cluster_proc_list & idles, processor & proc);
+static void idle_sleep(processor * proc, io_future_t & future, char buf[]);
+static bool mark_idle (__cluster_proc_list & idles, processor & proc);
 static void mark_awake(__cluster_proc_list & idles, processor & proc);
-static [unsigned idle, unsigned total, * processor] query_idles( & __cluster_proc_list idles );
 
 extern void __cfa_io_start( processor * );
 extern bool __cfa_io_drain( processor * );
-extern void __cfa_io_flush( processor * );
+extern bool __cfa_io_flush( processor *, bool wait );
 extern void __cfa_io_stop ( processor * );
 static inline bool __maybe_io_drain( processor * );
+
+#if defined(IO_URING_IDLE) && defined(CFA_HAVE_LINUX_IO_URING_H)
+	extern bool __kernel_read(processor * proc, io_future_t & future, char buf[], int fd);
+#endif
 
 extern void __disable_interrupts_hard();
@@ -148,4 +153,5 @@
 	/* paranoid */ verify( __preemption_enabled() );
 }
+
 
 //=============================================================================================
@@ -163,4 +169,8 @@
 	verify(this);
 
+	io_future_t future; // used for idle sleep when io_uring is present
+	future.self.ptr = 1p;  // mark it as already fulfilled so we know if there is a pending request or not
+	char buf[sizeof(uint64_t)];
+
 	__cfa_io_start( this );
 
@@ -196,5 +206,6 @@
 
 			if( !readyThread ) {
-				__cfa_io_flush( this );
+				__cfa_io_flush( this, false );
+
 				readyThread = __next_thread_slow( this->cltr );
 			}
@@ -210,5 +221,5 @@
 
 				// Push self to idle stack
-				mark_idle(this->cltr->procs, * this);
+				if(!mark_idle(this->cltr->procs, * this)) continue MAIN_LOOP;
 
 				// Confirm the ready-queue is empty
@@ -226,15 +237,98 @@
 				}
 
-				#if !defined(__CFA_NO_STATISTICS__)
-					if(this->print_halts) {
-						__cfaabi_bits_print_safe( STDOUT_FILENO, "PH:%d - %lld 0\n", this->unique_id, rdtscl());
+				idle_sleep( this, future, buf );
+
+				// We were woken up, remove self from idle
+				mark_awake(this->cltr->procs, * this);
+
+				// DON'T just proceed, start looking again
+				continue MAIN_LOOP;
+			}
+
+			/* paranoid */ verify( readyThread );
+
+			// Reset io dirty bit
+			this->io.dirty = false;
+
+			// We found a thread run it
+			__run_thread(this, readyThread);
+
+			// Are we done?
+			if( __atomic_load_n(&this->do_terminate, __ATOMIC_SEQ_CST) ) break MAIN_LOOP;
+
+			if(this->io.pending && !this->io.dirty) {
+				__cfa_io_flush( this, false );
+			}
+
+			#else
+				#warning new kernel loop
+			SEARCH: {
+				/* paranoid */ verify( ! __preemption_enabled() );
+
+				// First, lock the scheduler since we are searching for a thread
+				ready_schedule_lock();
+
+				// Try to get the next thread
+				readyThread = pop_fast( this->cltr );
+				if(readyThread) { ready_schedule_unlock(); break SEARCH; }
+
+				// If we can't find a thread, might as well flush any outstanding I/O
+				if(this->io.pending) { __cfa_io_flush( this, false ); }
+
+				// Spin a little on I/O, just in case
+				for(5) {
+					__maybe_io_drain( this );
+					readyThread = pop_fast( this->cltr );
+					if(readyThread) { ready_schedule_unlock(); break SEARCH; }
+				}
+
+				// no luck, try stealing a few times
+				for(5) {
+					if( __maybe_io_drain( this ) ) {
+						readyThread = pop_fast( this->cltr );
+					} else {
+						readyThread = pop_slow( this->cltr );
 					}
-				#endif
-
-				__cfadbg_print_safe(runtime_core, "Kernel : core %p waiting on eventfd %d\n", this, this->idle);
+					if(readyThread) { ready_schedule_unlock(); break SEARCH; }
+				}
+
+				// still no luck, search for a thread
+				readyThread = pop_search( this->cltr );
+				if(readyThread) { ready_schedule_unlock(); break SEARCH; }
+
+				// Don't block if we are done
+				if( __atomic_load_n(&this->do_terminate, __ATOMIC_SEQ_CST) ) {
+					ready_schedule_unlock();
+					break MAIN_LOOP;
+				}
+
+				__STATS( __tls_stats()->ready.sleep.halts++; )
+
+				// Push self to idle stack
+				ready_schedule_unlock();
+				if(!mark_idle(this->cltr->procs, * this)) goto SEARCH;
+				ready_schedule_lock();
+
+				// Confirm the ready-queue is empty
+				__maybe_io_drain( this );
+				readyThread = pop_search( this->cltr );
+				ready_schedule_unlock();
+
+				if( readyThread ) {
+					// A thread was found, cancel the halt
+					mark_awake(this->cltr->procs, * this);
+
+					__STATS( __tls_stats()->ready.sleep.cancels++; )
+
+					// continue the main loop
+					break SEARCH;
+				}
+
+				__STATS( if(this->print_halts) __cfaabi_bits_print_safe( STDOUT_FILENO, "PH:%d - %lld 0\n", this->unique_id, rdtscl()); )
+				__cfadbg_print_safe(runtime_core, "Kernel : core %p waiting on eventfd %d\n", this, this->idle_fd);
 
 				{
 					eventfd_t val;
-					ssize_t ret = read( this->idle, &val, sizeof(val) );
+					ssize_t ret = read( this->idle_fd, &val, sizeof(val) );
 					if(ret < 0) {
 						switch((int)errno) {
@@ -252,9 +346,5 @@
 				}
 
-				#if !defined(__CFA_NO_STATISTICS__)
-					if(this->print_halts) {
-						__cfaabi_bits_print_safe( STDOUT_FILENO, "PH:%d - %lld 1\n", this->unique_id, rdtscl());
-					}
-				#endif
+					__STATS( if(this->print_halts) __cfaabi_bits_print_safe( STDOUT_FILENO, "PH:%d - %lld 1\n", this->unique_id, rdtscl()); )
 
 				// We were woken up, remove self from idle
@@ -265,110 +355,4 @@
 			}
 
-			/* paranoid */ verify( readyThread );
-
-			// Reset io dirty bit
-			this->io.dirty = false;
-
-			// We found a thread run it
-			__run_thread(this, readyThread);
-
-			// Are we done?
-			if( __atomic_load_n(&this->do_terminate, __ATOMIC_SEQ_CST) ) break MAIN_LOOP;
-
-			if(this->io.pending && !this->io.dirty) {
-				__cfa_io_flush( this );
-			}
-
-			#else
-				#warning new kernel loop
-			SEARCH: {
-				/* paranoid */ verify( ! __preemption_enabled() );
-
-				// First, lock the scheduler since we are searching for a thread
-				ready_schedule_lock();
-
-				// Try to get the next thread
-				readyThread = pop_fast( this->cltr );
-				if(readyThread) { ready_schedule_unlock(); break SEARCH; }
-
-				// If we can't find a thread, might as well flush any outstanding I/O
-				if(this->io.pending) { __cfa_io_flush( this ); }
-
-				// Spin a little on I/O, just in case
-				for(5) {
-					__maybe_io_drain( this );
-					readyThread = pop_fast( this->cltr );
-					if(readyThread) { ready_schedule_unlock(); break SEARCH; }
-				}
-
-				// no luck, try stealing a few times
-				for(5) {
-					if( __maybe_io_drain( this ) ) {
-						readyThread = pop_fast( this->cltr );
-					} else {
-						readyThread = pop_slow( this->cltr );
-					}
-					if(readyThread) { ready_schedule_unlock(); break SEARCH; }
-				}
-
-				// still no luck, search for a thread
-				readyThread = pop_search( this->cltr );
-				if(readyThread) { ready_schedule_unlock(); break SEARCH; }
-
-				// Don't block if we are done
-				if( __atomic_load_n(&this->do_terminate, __ATOMIC_SEQ_CST) ) break MAIN_LOOP;
-
-				__STATS( __tls_stats()->ready.sleep.halts++; )
-
-				// Push self to idle stack
-				ready_schedule_unlock();
-				mark_idle(this->cltr->procs, * this);
-				ready_schedule_lock();
-
-				// Confirm the ready-queue is empty
-				__maybe_io_drain( this );
-				readyThread = pop_search( this->cltr );
-				ready_schedule_unlock();
-
-				if( readyThread ) {
-					// A thread was found, cancel the halt
-					mark_awake(this->cltr->procs, * this);
-
-					__STATS( __tls_stats()->ready.sleep.cancels++; )
-
-					// continue the main loop
-					break SEARCH;
-				}
-
-				__STATS( if(this->print_halts) __cfaabi_bits_print_safe( STDOUT_FILENO, "PH:%d - %lld 0\n", this->unique_id, rdtscl()); )
-				__cfadbg_print_safe(runtime_core, "Kernel : core %p waiting on eventfd %d\n", this, this->idle);
-
-				{
-					eventfd_t val;
-					ssize_t ret = read( this->idle, &val, sizeof(val) );
-					if(ret < 0) {
-						switch((int)errno) {
-						case EAGAIN:
-						#if EAGAIN != EWOULDBLOCK
-							case EWOULDBLOCK:
-						#endif
-						case EINTR:
-							// No need to do anything special here, just assume it's a legitimate wake-up
-							break;
-						default:
-							abort( "KERNEL : internal error, read failure on idle eventfd, error(%d) %s.", (int)errno, strerror( (int)errno ) );
-						}
-					}
-				}
-
-					__STATS( if(this->print_halts) __cfaabi_bits_print_safe( STDOUT_FILENO, "PH:%d - %lld 1\n", this->unique_id, rdtscl()); )
-
-				// We were woken up, remove self from idle
-				mark_awake(this->cltr->procs, * this);
-
-				// DON'T just proceed, start looking again
-				continue MAIN_LOOP;
-			}
-
 		RUN_THREAD:
 			/* paranoid */ verify( ! __preemption_enabled() );
@@ -385,5 +369,5 @@
 
 			if(this->io.pending && !this->io.dirty) {
-				__cfa_io_flush( this );
+				__cfa_io_flush( this, false );
 			}
 
@@ -758,16 +742,13 @@
 
 	// Check if there is a sleeping processor
-	processor * p;
-	unsigned idle;
-	unsigned total;
-	[idle, total, p] = query_idles(this->procs);
+	int fd = __atomic_load_n(&this->procs.fd, __ATOMIC_SEQ_CST);
 
 	// If no one is sleeping, we are done
-	if( idle == 0 ) return;
+	if( fd == 0 ) return;
 
 	// We found a processor, wake it up
 	eventfd_t val;
 	val = 1;
-	eventfd_write( p->idle, val );
+	eventfd_write( fd, val );
 
 	#if !defined(__CFA_NO_STATISTICS__)
@@ -794,17 +775,70 @@
 		eventfd_t val;
 		val = 1;
-		eventfd_write( this->idle, val );
+		eventfd_write( this->idle_fd, val );
 	__enable_interrupts_checked();
 }
 
-static void mark_idle(__cluster_proc_list & this, processor & proc) {
-	/* paranoid */ verify( ! __preemption_enabled() );
-	lock( this );
+static void idle_sleep(processor * this, io_future_t & future, char buf[]) {
+	#if !defined(IO_URING_IDLE) || !defined(CFA_HAVE_LINUX_IO_URING_H)
+		#if !defined(__CFA_NO_STATISTICS__)
+			if(this->print_halts) {
+				__cfaabi_bits_print_safe( STDOUT_FILENO, "PH:%d - %lld 0\n", this->unique_id, rdtscl());
+			}
+		#endif
+
+		__cfadbg_print_safe(runtime_core, "Kernel : core %p waiting on eventfd %d\n", this, this->idle_fd);
+
+		{
+			eventfd_t val;
+			ssize_t ret = read( this->idle_fd, &val, sizeof(val) );
+			if(ret < 0) {
+				switch((int)errno) {
+				case EAGAIN:
+				#if EAGAIN != EWOULDBLOCK
+					case EWOULDBLOCK:
+				#endif
+				case EINTR:
+					// No need to do anything special here, just assume it's a legitimate wake-up
+					break;
+				default:
+					abort( "KERNEL : internal error, read failure on idle eventfd, error(%d) %s.", (int)errno, strerror( (int)errno ) );
+				}
+			}
+		}
+
+		#if !defined(__CFA_NO_STATISTICS__)
+			if(this->print_halts) {
+				__cfaabi_bits_print_safe( STDOUT_FILENO, "PH:%d - %lld 1\n", this->unique_id, rdtscl());
+			}
+		#endif
+	#else
+		#if !defined(CFA_HAVE_IORING_OP_READ)
+			#error this is only implemented if the read is present
+		#endif
+		// Do we already have a pending read
+		if(available(future)) {
+			// There is no pending read, we need to add one
+			reset(future);
+
+			__kernel_read(this, future, buf, this->idle_fd );
+		}
+
+		__cfa_io_flush( this, true );
+	#endif
+}
+
+static bool mark_idle(__cluster_proc_list & this, processor & proc) {
+	/* paranoid */ verify( ! __preemption_enabled() );
+	if(!try_lock( this )) return false;
 		this.idle++;
 		/* paranoid */ verify( this.idle <= this.total );
 		remove(proc);
 		insert_first(this.idles, proc);
+
+		__atomic_store_n(&this.fd, proc.idle_fd, __ATOMIC_SEQ_CST);
 	unlock( this );
 	/* paranoid */ verify( ! __preemption_enabled() );
+
+	return true;
 }
 
@@ -816,25 +850,12 @@
 		remove(proc);
 		insert_last(this.actives, proc);
+
+		{
+			int fd = 0;
+			if(!this.idles`isEmpty) fd = this.idles`first.idle_fd;
+			__atomic_store_n(&this.fd, fd, __ATOMIC_SEQ_CST);
+		}
+
 	unlock( this );
-	/* paranoid */ verify( ! __preemption_enabled() );
-}
-
-static [unsigned idle, unsigned total, * processor] query_idles( & __cluster_proc_list this ) {
-	/* paranoid */ verify( ! __preemption_enabled() );
-	/* paranoid */ verify( ready_schedule_islocked() );
-
-	for() {
-		uint64_t l = __atomic_load_n(&this.lock, __ATOMIC_SEQ_CST);
-		if( 1 == (l % 2) ) { Pause(); continue; }
-		unsigned idle    = this.idle;
-		unsigned total   = this.total;
-		processor * proc = &this.idles`first;
-		// Compiler fence is unnecessary, but gcc-8 and older incorrectly reorder code without it
-		asm volatile("": : :"memory");
-		if(l != __atomic_load_n(&this.lock, __ATOMIC_SEQ_CST)) { Pause(); continue; }
-		return [idle, total, proc];
-	}
-
-	/* paranoid */ verify( ready_schedule_islocked() );
 	/* paranoid */ verify( ! __preemption_enabled() );
 }
@@ -898,10 +919,10 @@
 		if(head == tail) return false;
 		#if OLD_MAIN
-		ready_schedule_lock();
-		ret = __cfa_io_drain( proc );
-		ready_schedule_unlock();
+			ready_schedule_lock();
+			ret = __cfa_io_drain( proc );
+			ready_schedule_unlock();
 		#else
 			ret = __cfa_io_drain( proc );
-	#endif
+		#endif
 	#endif
 	return ret;
@@ -939,4 +960,5 @@
 			/* paranoid */ verifyf( it, "Unexpected null iterator, at index %u of %u\n", i, count);
 			/* paranoid */ verify( it->local_data->this_stats );
+			// __print_stats( it->local_data->this_stats, cltr->print_stats, "Processor", it->name, (void*)it );
 			__tally_stats( cltr->stats, it->local_data->this_stats );
 			it = &(*it)`next;
@@ -948,4 +970,5 @@
 		// this doesn't solve all problems but does solve many
 		// so it's probably good enough
+		disable_interrupts();
 		uint_fast32_t last_size = ready_mutate_lock();
 
@@ -955,4 +978,5 @@
 		// Unlock the RWlock
 		ready_mutate_unlock( last_size );
+		enable_interrupts();
 	}
 
Index: libcfa/src/concurrency/kernel.hfa
===================================================================
--- libcfa/src/concurrency/kernel.hfa	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ libcfa/src/concurrency/kernel.hfa	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -100,5 +100,5 @@
 
 	// Idle lock (kernel semaphore)
-	int idle;
+	int idle_fd;
 
 	// Termination synchronisation (user semaphore)
@@ -195,5 +195,8 @@
 struct __cluster_proc_list {
 	// Spin lock protecting the queue
-	volatile uint64_t lock;
+	__spinlock_t lock;
+
+	// FD to use to wake a processor
+	volatile int fd;
 
 	// Total number of processors
Index: libcfa/src/concurrency/kernel/startup.cfa
===================================================================
--- libcfa/src/concurrency/kernel/startup.cfa	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ libcfa/src/concurrency/kernel/startup.cfa	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -100,4 +100,5 @@
 // Other Forward Declarations
 extern void __wake_proc(processor *);
+extern int cfa_main_returned;							// from interpose.cfa
 
 //-----------------------------------------------------------------------------
@@ -268,4 +269,5 @@
 
 static void __kernel_shutdown(void) {
+	if(!cfa_main_returned) return;
 	/* paranoid */ verify( __preemption_enabled() );
 	disable_interrupts();
@@ -525,6 +527,6 @@
 	this.local_data = 0p;
 
-	this.idle = eventfd(0, 0);
-	if (idle < 0) {
+	this.idle_fd = eventfd(0, 0);
+	if (idle_fd < 0) {
 		abort("KERNEL ERROR: PROCESSOR EVENTFD - %s\n", strerror(errno));
 	}
@@ -540,5 +542,5 @@
 // Not a ctor, it just preps the destruction but should not destroy members
 static void deinit(processor & this) {
-	close(this.idle);
+	close(this.idle_fd);
 }
 
@@ -582,5 +584,5 @@
 // Cluster
 static void ?{}(__cluster_proc_list & this) {
-	this.lock  = 0;
+	this.fd    = 0;
 	this.idle  = 0;
 	this.total = 0;
Index: libcfa/src/concurrency/kernel_private.hfa
===================================================================
--- libcfa/src/concurrency/kernel_private.hfa	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ libcfa/src/concurrency/kernel_private.hfa	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -39,4 +39,6 @@
 }
 
+// #define IO_URING_IDLE
+
 //-----------------------------------------------------------------------------
 // Scheduler
@@ -149,8 +151,4 @@
 	__atomic_store_n(ll, (bool)false, __ATOMIC_RELEASE);
 }
-
-
-
-
 
 //-----------------------------------------------------------------------
@@ -268,16 +266,27 @@
 	ready_schedule_lock();
 
-	// Simple counting lock, acquired, acquired by incrementing the counter
-	// to an odd number
-	for() {
-		uint64_t l = this.lock;
-		if(
-			(0 == (l % 2))
-			&& __atomic_compare_exchange_n(&this.lock, &l, l + 1, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)
-		) return;
-		Pause();
-	}
-
-	/* paranoid */ verify( ! __preemption_enabled() );
+	lock( this.lock __cfaabi_dbg_ctx2 );
+
+	/* paranoid */ verify( ! __preemption_enabled() );
+}
+
+static inline bool try_lock(__cluster_proc_list & this) {
+	/* paranoid */ verify( ! __preemption_enabled() );
+
+	// Start by locking the global RWlock so that we know no-one is
+	// adding/removing processors while we mess with the idle lock
+	ready_schedule_lock();
+
+	if(try_lock( this.lock __cfaabi_dbg_ctx2 )) {
+		// success
+		/* paranoid */ verify( ! __preemption_enabled() );
+		return true;
+	}
+
+	// failed to lock
+	ready_schedule_unlock();
+
+	/* paranoid */ verify( ! __preemption_enabled() );
+	return false;
 }
 
@@ -285,7 +294,5 @@
 	/* paranoid */ verify( ! __preemption_enabled() );
 
-	/* paranoid */ verify( 1 == (this.lock % 2) );
-	// Simple couting lock, release by incrementing to an even number
-	__atomic_fetch_add( &this.lock, 1, __ATOMIC_SEQ_CST );
+	unlock(this.lock);
 
 	// Release the global lock, which we acquired when locking
Index: libcfa/src/concurrency/monitor.hfa
===================================================================
--- libcfa/src/concurrency/monitor.hfa	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ libcfa/src/concurrency/monitor.hfa	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -65,4 +65,15 @@
 	free( th );
 }
+
+static inline forall( T & | sized(T) | { void ^?{}( T & mutex ); } )
+void adelete( T arr[] ) {
+	if ( arr ) {										// ignore null
+		size_t dim = malloc_size( arr ) / sizeof( T );
+		for ( int i = dim - 1; i >= 0; i -= 1 ) {		// reverse allocation order, must be unsigned
+			^(arr[i]){};								// run destructor
+		} // for
+		free( arr );
+	} // if
+} // adelete
 
 //-----------------------------------------------------------------------------
Index: libcfa/src/concurrency/mutex_stmt.hfa
===================================================================
--- libcfa/src/concurrency/mutex_stmt.hfa	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ libcfa/src/concurrency/mutex_stmt.hfa	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -1,7 +1,4 @@
 #include "bits/algorithm.hfa"
-#include <assert.h>
-#include "invoke.h"
-#include "stdlib.hfa"
-#include <stdio.h>
+#include "bits/defs.hfa"
 
 //-----------------------------------------------------------------------------
Index: libcfa/src/device/cpu.cfa
===================================================================
--- libcfa/src/device/cpu.cfa	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ libcfa/src/device/cpu.cfa	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -144,5 +144,5 @@
 // Count number of cpus in the system
 static int count_cpus(void) {
-	const char * fpath = "/sys/devices/system/cpu/present";
+	const char * fpath = "/sys/devices/system/cpu/online";
 	int fd = open(fpath, 0, O_RDONLY);
 	/* paranoid */ verifyf(fd >= 0, "Could not open file %s", fpath);
Index: libcfa/src/fstream.cfa
===================================================================
--- libcfa/src/fstream.cfa	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ libcfa/src/fstream.cfa	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -10,6 +10,6 @@
 // Created On       : Wed May 27 17:56:53 2015
 // Last Modified By : Peter A. Buhr
-// Last Modified On : Tue Sep 21 21:51:38 2021
-// Update Count     : 460
+// Last Modified On : Sun Oct 10 11:23:05 2021
+// Update Count     : 512
 //
 
@@ -28,5 +28,6 @@
 #define IO_MSG "I/O error: "
 
-void ?{}( ofstream & os, void * file ) with(os) {
+// private
+void ?{}( ofstream & os, void * file ) with( os ) {
 	file$ = file;
 	sepDefault$ = true;
@@ -35,5 +36,4 @@
 	prt$ = false;
 	sawNL$ = false;
-	acquired$ = false;
 	sepSetCur$( os, sepGet( os ) );
 	sepSet( os, " " );
@@ -41,30 +41,23 @@
 } // ?{}
 
-// private
-bool sepPrt$( ofstream & os ) { setNL$( os, false ); return os.sepOnOff$; }
-void sepReset$( ofstream & os ) { os.sepOnOff$ = os.sepDefault$; }
-void sepReset$( ofstream & os, bool reset ) { os.sepDefault$ = reset; os.sepOnOff$ = os.sepDefault$; }
-const char * sepGetCur$( ofstream & os ) { return os.sepCur$; }
-void sepSetCur$( ofstream & os, const char sepCur[] ) { os.sepCur$ = sepCur; }
-bool getNL$( ofstream & os ) { return os.sawNL$; }
-void setNL$( ofstream & os, bool state ) { os.sawNL$ = state; }
-bool getANL$( ofstream & os ) { return os.nlOnOff$; }
-bool getPrt$( ofstream & os ) { return os.prt$; }
-void setPrt$( ofstream & os, bool state ) { os.prt$ = state; }
+inline bool sepPrt$( ofstream & os ) { setNL$( os, false ); return os.sepOnOff$; }
+inline void sepReset$( ofstream & os ) { os.sepOnOff$ = os.sepDefault$; }
+inline void sepReset$( ofstream & os, bool reset ) { os.sepDefault$ = reset; os.sepOnOff$ = os.sepDefault$; }
+inline const char * sepGetCur$( ofstream & os ) { return os.sepCur$; }
+inline void sepSetCur$( ofstream & os, const char sepCur[] ) { os.sepCur$ = sepCur; }
+inline bool getNL$( ofstream & os ) { return os.sawNL$; }
+inline void setNL$( ofstream & os, bool state ) { os.sawNL$ = state; }
+inline bool getANL$( ofstream & os ) { return os.nlOnOff$; }
+inline bool getPrt$( ofstream & os ) { return os.prt$; }
+inline void setPrt$( ofstream & os, bool state ) { os.prt$ = state; }
+
+inline void lock( ofstream & os ) with( os ) {	lock( os.lock$ ); }
+inline void unlock( ofstream & os ) { unlock( os.lock$ ); }
 
 // public
 void ?{}( ofstream & os ) { os.file$ = 0p; }
-
-void ?{}( ofstream & os, const char name[], const char mode[] ) {
-	open( os, name, mode );
-} // ?{}
-
-void ?{}( ofstream & os, const char name[] ) {
-	open( os, name, "w" );
-} // ?{}
-
-void ^?{}( ofstream & os ) {
-	close( os );
-} // ^?{}
+void ?{}( ofstream & os, const char name[], const char mode[] ) { open( os, name, mode ); }
+void ?{}( ofstream & os, const char name[] ) { open( os, name, "w" ); }
+void ^?{}( ofstream & os ) { close( os ); }
 
 void sepOn( ofstream & os ) { os.sepOnOff$ = ! getNL$( os ); }
@@ -107,21 +100,18 @@
 	if ( &os == &exit ) exit( EXIT_FAILURE );
 	if ( &os == &abort ) abort();
-	if ( os.acquired$ ) { os.acquired$ = false; release( os ); }
 } // ends
 
-bool fail( ofstream & os ) {
-	return os.file$ == 0 || ferror( (FILE *)(os.file$) );
-} // fail
-
-void clear( ofstream & os ) {
-	clearerr( (FILE *)(os.file$) );
-} // clear
-
-int flush( ofstream & os ) {
-	return fflush( (FILE *)(os.file$) );
-} // flush
+bool fail( ofstream & os ) { return os.file$ == 0 || ferror( (FILE *)(os.file$) ); }
+void clear( ofstream & os ) { clearerr( (FILE *)(os.file$) ); }
+int flush( ofstream & os ) { return fflush( (FILE *)(os.file$) ); }
 
 void open( ofstream & os, const char name[], const char mode[] ) {
-	FILE * file = fopen( name, mode );
+	FILE * file;
+    for ( cnt; 10 ) {
+		errno = 0;
+		file = fopen( name, mode );
+	  if ( file != 0p || errno != EINTR ) break;		// timer interrupt ?
+	  if ( cnt == 9 ) abort( "ofstream open EINTR spinning exceeded" );
+    } // for
 	if ( file == 0p ) {
 		throw (Open_Failure){ os };
@@ -131,17 +121,22 @@
 } // open
 
-void open( ofstream & os, const char name[] ) {
-	open( os, name, "w" );
-} // open
-
-void close( ofstream & os ) with(os) {
+void open( ofstream & os, const char name[] ) { open( os, name, "w" ); }
+
+void close( ofstream & os ) with( os ) {
   if ( (FILE *)(file$) == 0p ) return;
   if ( (FILE *)(file$) == (FILE *)stdout || (FILE *)(file$) == (FILE *)stderr ) return;
 
-	if ( fclose( (FILE *)(file$) ) == EOF ) {
+	int ret;
+    for ( cnt; 10 ) {
+		errno = 0;
+		ret = fclose( (FILE *)(file$) );
+	  if ( ret != EOF || errno != EINTR ) break;		// timer interrupt ?
+	  if ( cnt == 9 ) abort( "ofstream open EINTR spinning exceeded" );
+    } // for
+	if ( ret == EOF ) {
 		throw (Close_Failure){ os };
 		// abort | IO_MSG "close output" | nl | strerror( errno );
 	} // if
-	file$ = 0p;
+	file$ = 0p;											// safety after close
 } // close
 
@@ -162,5 +157,12 @@
 	va_list args;
 	va_start( args, format );
-	int len = vfprintf( (FILE *)(os.file$), format, args );
+		
+	int len;
+    for ( cnt; 10 ) {
+		errno = 0;
+		len = vfprintf( (FILE *)(os.file$), format, args );
+	  if ( len != EOF || errno != EINTR ) break;		// timer interrupt ?
+	  if ( cnt == 9 ) abort( "ofstream fmt EINTR spinning exceeded" );
+    } // for
 	if ( len == EOF ) {
 		if ( ferror( (FILE *)(os.file$) ) ) {
@@ -175,17 +177,4 @@
 } // fmt
 
-inline void acquire( ofstream & os ) with(os) {
-	lock( lock$ );										// may increase recursive lock
-	if ( ! acquired$ ) acquired$ = true;				// not locked ?
-	else unlock( lock$ );								// unwind recursive lock at start
-} // acquire
-
-inline void release( ofstream & os ) {
-	unlock( os.lock$ );
-} // release
-
-void ?{}( osacquire & acq, ofstream & os ) { lock( os.lock$ ); &acq.os = &os; }
-void ^?{}( osacquire & acq ) { release( acq.os ); }
-
 static ofstream soutFile = { (FILE *)stdout };
 ofstream & sout = soutFile, & stdout = soutFile;
@@ -205,9 +194,4 @@
 	flush( os );
 	return os;
-	// (ofstream &)(os | '\n');
-	// setPrt$( os, false );							// turn off
-	// setNL$( os, true );
-	// flush( os );
-	// return sepOff( os );							// prepare for next line
 } // nl
 
@@ -217,47 +201,38 @@
 
 // private
-void ?{}( ifstream & is, void * file ) with(is) {
+void ?{}( ifstream & is, void * file ) with( is ) {
 	file$ = file;
 	nlOnOff$ = false;
-	acquired$ = false;
-} // ?{}
+} // ?{}
+
+bool getANL$( ifstream & os ) { return os.nlOnOff$; }
+
+inline void lock( ifstream & os ) with( os ) { lock( os.lock$ ); }
+inline void unlock( ifstream & os ) { unlock( os.lock$ ); }
 
 // public
 void ?{}( ifstream & is ) { is.file$ = 0p; }
-
-void ?{}( ifstream & is, const char name[], const char mode[] ) {
-	open( is, name, mode );
-} // ?{}
-
-void ?{}( ifstream & is, const char name[] ) {
-	open( is, name, "r" );
-} // ?{}
-
-void ^?{}( ifstream & is ) {
-	close( is );
-} // ^?{}
+void ?{}( ifstream & is, const char name[], const char mode[] ) { open( is, name, mode ); }
+void ?{}( ifstream & is, const char name[] ) { open( is, name, "r" ); }
+void ^?{}( ifstream & is ) { close( is ); }
+
+bool fail( ifstream & is ) { return is.file$ == 0p || ferror( (FILE *)(is.file$) ); }
+void clear( ifstream & is ) { clearerr( (FILE *)(is.file$) ); }
 
 void nlOn( ifstream & os ) { os.nlOnOff$ = true; }
 void nlOff( ifstream & os ) { os.nlOnOff$ = false; }
-bool getANL( ifstream & os ) { return os.nlOnOff$; }
-
-bool fail( ifstream & is ) {
-	return is.file$ == 0p || ferror( (FILE *)(is.file$) );
-} // fail
-
-void clear( ifstream & is ) {
-	clearerr( (FILE *)(is.file$) );
-} // clear
-
-void ends( ifstream & is ) {
-	if ( is.acquired$ ) { is.acquired$ = false; release( is ); }
-} // ends
-
-bool eof( ifstream & is ) {
-	return feof( (FILE *)(is.file$) );
-} // eof
+
+void ends( ifstream & is ) {}
+
+bool eof( ifstream & is ) { return feof( (FILE *)(is.file$) ) != 0; }
 
 void open( ifstream & is, const char name[], const char mode[] ) {
-	FILE * file = fopen( name, mode );
+	FILE * file;
+    for ( cnt; 10 ) {
+		errno = 0;
+		file = fopen( name, mode );
+	  if ( file != 0p || errno != EINTR ) break;		// timer interrupt ?
+	  if ( cnt == 9 ) abort( "ifstream open EINTR spinning exceeded" );
+    } // for
 	if ( file == 0p ) {
 		throw (Open_Failure){ is };
@@ -267,17 +242,22 @@
 } // open
 
-void open( ifstream & is, const char name[] ) {
-	open( is, name, "r" );
-} // open
-
-void close( ifstream & is ) with(is) {
+void open( ifstream & is, const char name[] ) { open( is, name, "r" ); }
+
+void close( ifstream & is ) with( is ) {
   if ( (FILE *)(file$) == 0p ) return;
   if ( (FILE *)(file$) == (FILE *)stdin ) return;
 
-	if ( fclose( (FILE *)(file$) ) == EOF ) {
+	int ret;
+    for ( cnt; 10 ) {
+		errno = 0;
+		ret = fclose( (FILE *)(file$) );
+	  if ( ret != EOF || errno != EINTR ) break;		// timer interrupt ?
+	  if ( cnt == 9 ) abort( "ifstream close EINTR spinning exceeded" );
+    } // for
+	if ( ret == EOF ) {
 		throw (Close_Failure){ is };
 		// abort | IO_MSG "close input" | nl | strerror( errno );
 	} // if
-	file$ = 0p;
+	file$ = 0p;											// safety after close
 } // close
 
@@ -308,7 +288,12 @@
 int fmt( ifstream & is, const char format[], ... ) {
 	va_list args;
-
 	va_start( args, format );
-	int len = vfscanf( (FILE *)(is.file$), format, args );
+
+	int len;
+    for () {											// no check for EINTR limit waiting for keyboard input
+		errno = 0;
+		len = vfscanf( (FILE *)(is.file$), format, args );
+	  if ( len != EOF || errno != EINTR ) break;		// timer interrupt ?
+    } // for
 	if ( len == EOF ) {
 		if ( ferror( (FILE *)(is.file$) ) ) {
@@ -319,17 +304,4 @@
 	return len;
 } // fmt
-
-inline void acquire( ifstream & is ) with(is) {
-	lock( lock$ );										// may increase recursive lock
-	if ( ! acquired$ ) acquired$ = true;				// not locked ?
-	else unlock( lock$ );								// unwind recursive lock at start
-} // acquire
-
-inline void release( ifstream & is ) {
-	unlock( is.lock$ );
-} // release
-
-void ?{}( isacquire & acq, ifstream & is ) { lock( is.lock$ ); &acq.is = &is; }
-void ^?{}( isacquire & acq ) { release( acq.is ); }
 
 static ifstream sinFile = { (FILE *)stdin };
Index: libcfa/src/fstream.hfa
===================================================================
--- libcfa/src/fstream.hfa	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ libcfa/src/fstream.hfa	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -10,6 +10,6 @@
 // Created On       : Wed May 27 17:56:53 2015
 // Last Modified By : Peter A. Buhr
-// Last Modified On : Wed Jul 28 07:35:50 2021
-// Update Count     : 234
+// Last Modified On : Sun Oct 10 09:37:32 2021
+// Update Count     : 243
 //
 
@@ -36,5 +36,4 @@
 	char tupleSeparator$[ofstream_sepSize];
 	multiple_acquisition_lock lock$;
-	bool acquired$;
 }; // ofstream
 
@@ -52,4 +51,7 @@
 bool getPrt$( ofstream & );
 void setPrt$( ofstream &, bool );
+
+void lock( ofstream & );
+void unlock( ofstream & );
 
 // public
@@ -75,17 +77,6 @@
 void open( ofstream &, const char name[] );
 void close( ofstream & );
+
 ofstream & write( ofstream &, const char data[], size_t size );
-
-void acquire( ofstream & );
-void release( ofstream & );
-
-void lock( ofstream & );
-void unlock( ofstream & );
-
-struct osacquire {
-	ofstream & os;
-};
-void ?{}( osacquire & acq, ofstream & );
-void ^?{}( osacquire & acq );
 
 void ?{}( ofstream & );
@@ -110,13 +101,17 @@
 	bool nlOnOff$;
 	multiple_acquisition_lock lock$;
-	bool acquired$;
 }; // ifstream
 
 // Satisfies istream
 
+// private
+bool getANL$( ifstream & );
+
+void lock( ifstream & );
+void unlock( ifstream & );
+
 // public
 void nlOn( ifstream & );
 void nlOff( ifstream & );
-bool getANL( ifstream & );
 void ends( ifstream & );
 int fmt( ifstream &, const char format[], ... ) __attribute__(( format(scanf, 2, 3) ));
@@ -128,15 +123,7 @@
 void open( ifstream & is, const char name[] );
 void close( ifstream & is );
+
 ifstream & read( ifstream & is, char data[], size_t size );
 ifstream & ungetc( ifstream & is, char c );
-
-void acquire( ifstream & is );
-void release( ifstream & is );
-
-struct isacquire {
-	ifstream & is;
-};
-void ?{}( isacquire & acq, ifstream & is );
-void ^?{}( isacquire & acq );
 
 void ?{}( ifstream & is );
Index: libcfa/src/heap.cfa
===================================================================
--- libcfa/src/heap.cfa	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ libcfa/src/heap.cfa	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -102,5 +102,5 @@
 } // prtUnfreed
 
-extern int cfa_main_returned;							// from bootloader.cf
+extern int cfa_main_returned;							// from interpose.cfa
 extern "C" {
 	void heapAppStart() {								// called by __cfaabi_appready_startup
Index: libcfa/src/interpose.cfa
===================================================================
--- libcfa/src/interpose.cfa	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ libcfa/src/interpose.cfa	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -94,7 +94,10 @@
 } __cabi_libc;
 
+int cfa_main_returned;
+
 extern "C" {
 	void __cfaabi_interpose_startup( void ) {
 		const char *version = 0p;
+		cfa_main_returned = 0;
 
 		preload_libgcc();
Index: libcfa/src/iostream.cfa
===================================================================
--- libcfa/src/iostream.cfa	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ libcfa/src/iostream.cfa	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -10,6 +10,6 @@
 // Created On       : Wed May 27 17:56:53 2015
 // Last Modified By : Peter A. Buhr
-// Last Modified On : Sat May 15 09:39:21 2021
-// Update Count     : 1342
+// Last Modified On : Sun Oct 10 09:28:17 2021
+// Update Count     : 1345
 //
 
@@ -398,11 +398,4 @@
 		return os;
 	} // nlOff
-} // distribution
-
-forall( ostype & | ostream( ostype ) ) {
-	ostype & acquire( ostype & os ) {
-		acquire( os );									// call void returning
-		return os;
-	} // acquire
 } // distribution
 
@@ -829,5 +822,5 @@
 			fmt( is, "%c", &temp );						// must pass pointer through varg to fmt
 			// do not overwrite parameter with newline unless appropriate
-			if ( temp != '\n' || getANL( is ) ) { c = temp; break; }
+			if ( temp != '\n' || getANL$( is ) ) { c = temp; break; }
 			if ( eof( is ) ) break;
 		} // for
@@ -1035,11 +1028,4 @@
 		return is;
 	} // nlOff
-} // distribution
-
-forall( istype & | istream( istype ) ) {
-	istype & acquire( istype & is ) {
-		acquire( is );									// call void returning
-		return is;
-	} // acquire
 } // distribution
 
Index: libcfa/src/iostream.hfa
===================================================================
--- libcfa/src/iostream.hfa	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ libcfa/src/iostream.hfa	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -10,6 +10,6 @@
 // Created On       : Wed May 27 17:56:53 2015
 // Last Modified By : Peter A. Buhr
-// Last Modified On : Wed Apr 28 20:37:56 2021
-// Update Count     : 401
+// Last Modified On : Sun Oct 10 10:02:07 2021
+// Update Count     : 407
 //
 
@@ -58,5 +58,4 @@
 	void close( ostype & );
 	ostype & write( ostype &, const char [], size_t );
-	void acquire( ostype & );							// concurrent access
 }; // ostream
 
@@ -142,8 +141,4 @@
 	ostype & nlOn( ostype & );
 	ostype & nlOff( ostype & );
-} // distribution
-
-forall( ostype & | ostream( ostype ) ) {
-	ostype & acquire( ostype & );
 } // distribution
 
@@ -296,8 +291,9 @@
 
 trait basic_istream( istype & ) {
-	bool getANL( istype & );							// get scan newline (on/off)
+	// private
+	bool getANL$( istype & );							// get scan newline (on/off)
+	// public
 	void nlOn( istype & );								// read newline
 	void nlOff( istype & );								// scan newline
-
 	void ends( istype & os );							// end of output statement
 	int fmt( istype &, const char format[], ... ) __attribute__(( format(scanf, 2, 3) ));
@@ -312,5 +308,4 @@
 	void close( istype & is );
 	istype & read( istype &, char [], size_t );
-	void acquire( istype & );							// concurrent access
 }; // istream
 
@@ -379,8 +374,4 @@
 } // distribution
 
-forall( istype & | istream( istype ) ) {
-	istype & acquire( istype & );
-} // distribution
-
 // *********************************** manipulators ***********************************
 
Index: libcfa/src/strstream.cfa
===================================================================
--- libcfa/src/strstream.cfa	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ libcfa/src/strstream.cfa	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -10,9 +10,10 @@
 // Created On       : Thu Apr 22 22:24:35 2021
 // Last Modified By : Peter A. Buhr
-// Last Modified On : Tue Apr 27 20:59:53 2021
-// Update Count     : 78
+// Last Modified On : Sun Oct 10 16:13:20 2021
+// Update Count     : 101
 // 
 
 #include "strstream.hfa"
+#include "fstream.hfa"									// abort
 
 #include <stdio.h>										// vsnprintf
@@ -30,14 +31,14 @@
 
 // private
-bool sepPrt$( ostrstream & os ) { setNL$( os, false ); return os.sepOnOff$; }
-void sepReset$( ostrstream & os ) { os.sepOnOff$ = os.sepDefault$; }
-void sepReset$( ostrstream & os, bool reset ) { os.sepDefault$ = reset; os.sepOnOff$ = os.sepDefault$; }
-const char * sepGetCur$( ostrstream & os ) { return os.sepCur$; }
-void sepSetCur$( ostrstream & os, const char sepCur[] ) { os.sepCur$ = sepCur; }
-bool getNL$( ostrstream & os ) { return os.sawNL$; }
-void setNL$( ostrstream & os, bool state ) { os.sawNL$ = state; }
-bool getANL$( ostrstream & os ) { return os.nlOnOff$; }
-bool getPrt$( ostrstream & os ) { return os.prt$; }
-void setPrt$( ostrstream & os, bool state ) { os.prt$ = state; }
+inline bool sepPrt$( ostrstream & os ) { setNL$( os, false ); return os.sepOnOff$; }
+inline void sepReset$( ostrstream & os ) { os.sepOnOff$ = os.sepDefault$; }
+inline void sepReset$( ostrstream & os, bool reset ) { os.sepDefault$ = reset; os.sepOnOff$ = os.sepDefault$; }
+inline const char * sepGetCur$( ostrstream & os ) { return os.sepCur$; }
+inline void sepSetCur$( ostrstream & os, const char sepCur[] ) { os.sepCur$ = sepCur; }
+inline bool getNL$( ostrstream & os ) { return os.sawNL$; }
+inline void setNL$( ostrstream & os, bool state ) { os.sawNL$ = state; }
+inline bool getANL$( ostrstream & os ) { return os.nlOnOff$; }
+inline bool getPrt$( ostrstream & os ) { return os.prt$; }
+inline void setPrt$( ostrstream & os, bool state ) { os.prt$ = state; }
 
 // public
@@ -128,4 +129,6 @@
 // *********************************** istrstream ***********************************
 
+// private
+bool getANL$( istrstream & is ) { return is.nlOnOff$; }
 
 // public
@@ -136,14 +139,27 @@
 } // ?{}
 
-bool getANL( istrstream & is ) { return is.nlOnOff$; }
 void nlOn( istrstream & is ) { is.nlOnOff$ = true; }
 void nlOff( istrstream & is ) { is.nlOnOff$ = false; }
 
-void ends( istrstream & is ) {
-} // ends
+void ends( istrstream & is ) {}
+bool eof( istrstream & is ) { return false; }
 
-int eof( istrstream & is ) {
-	return 0;
-} // eof
+int fmt( istrstream & is, const char format[], ... ) with(is) {
+	va_list args;
+	va_start( args, format );
+	// THIS DOES NOT WORK BECAUSE VSSCANF RETURNS NUMBER OF VALUES READ VERSUS BUFFER POSITION SCANNED.
+	int len = vsscanf( buf$ + cursor$, format, args );
+	va_end( args );
+	if ( len == EOF ) {
+		abort | IO_MSG "invalid read";
+	} // if
+	// SKULLDUGGERY: This hack skips over characters read by vsscanf by moving to the next whitespace but it does not
+	// handle C reads with wdi manipulators that leave the cursor at a non-whitespace character.
+	for ( ; buf$[cursor$] != ' ' && buf$[cursor$] != '\t' && buf$[cursor$] != '\0'; cursor$ += 1 ) {
+		//printf( "X \'%c\'\n", buf$[cursor$] );
+	} // for
+	if ( buf$[cursor$] != '\0' ) cursor$ += 1;	// advance to whitespace
+	return len;
+} // fmt
 
 istrstream &ungetc( istrstream & is, char c ) {
@@ -154,18 +170,4 @@
 } // ungetc
 
-int fmt( istrstream & is, const char format[], ... ) {
-	va_list args;
-	va_start( args, format );
-	// This does not work because vsscanf does not return buffer position.
-	int len = vsscanf( is.buf$ + is.cursor$, format, args );
-	va_end( args );
-	if ( len == EOF ) {
-		int j;
-		printf( "X %d%n\n", len, &j );
-	} // if
-	is.cursor$ += len;
-	return len;
-} // fmt
-
 // Local Variables: //
 // tab-width: 4 //
Index: libcfa/src/strstream.hfa
===================================================================
--- libcfa/src/strstream.hfa	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ libcfa/src/strstream.hfa	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -10,6 +10,6 @@
 // Created On       : Thu Apr 22 22:20:59 2021
 // Last Modified By : Peter A. Buhr
-// Last Modified On : Tue Apr 27 20:58:50 2021
-// Update Count     : 41
+// Last Modified On : Sun Oct 10 10:14:22 2021
+// Update Count     : 47
 // 
 
@@ -85,14 +85,17 @@
 // Satisfies basic_istream
 
+// private
+bool getANL$( istrstream & );
+
 // public
-bool getANL( istrstream & );
 void nlOn( istrstream & );
 void nlOff( istrstream & );
 void ends( istrstream & );
+
 int fmt( istrstream &, const char format[], ... ) __attribute__(( format(scanf, 2, 3) ));
-istrstream & ungetc( istrstream & is, char c );
-int eof( istrstream & is );
+istrstream & ungetc( istrstream &, char );
+bool eof( istrstream & );
 
-void ?{}( istrstream & is, char buf[] );
+void ?{}( istrstream &, char buf[] );
 
 // Local Variables: //
Index: src/AST/Convert.cpp
===================================================================
--- src/AST/Convert.cpp	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ src/AST/Convert.cpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -1041,18 +1041,16 @@
 
 	const ast::Expr * visit( const ast::StmtExpr * node ) override final {
-		auto stmts = node->stmts;
-		// disable sharing between multiple StmtExprs explicitly.
-		// this should no longer be true.
-
 		auto rslt = new StmtExpr(
-			get<CompoundStmt>().accept1(stmts)
+			get<CompoundStmt>().accept1(node->stmts)
 		);
 
 		rslt->returnDecls = get<ObjectDecl>().acceptL(node->returnDecls);
 		rslt->dtors       = get<Expression>().acceptL(node->dtors);
-		if (node->resultExpr) {
-			// this MUST be found by children visit
-			rslt->resultExpr  = strict_dynamic_cast<ExprStmt *>(readonlyCache.at(node->resultExpr));
-		}
+
+		// is this even used after convert?
+		//if (tmp->resultExpr) {
+		//	// this MUST be found by children visit
+		//	rslt->resultExpr  = strict_dynamic_cast<ExprStmt *>(readonlyCache.at(tmp->resultExpr));
+		//}
 
 		auto expr = visitBaseExpr( node, rslt );
@@ -1446,4 +1444,10 @@
 
 std::list< Declaration * > convert( const ast::TranslationUnit && translationUnit ) {
+	// Copy values from the global store to the local static variables.
+	ast::sizeType = translationUnit.global.sizeType;
+	ast::dereferenceOperator = translationUnit.global.dereference;
+	ast::dtorStruct = translationUnit.global.dtorStruct;
+	ast::dtorStructDestroy = translationUnit.global.dtorDestroy;
+
 	ConverterNewToOld c;
 	std::list< Declaration * > decls;
Index: src/AST/Copy.cpp
===================================================================
--- src/AST/Copy.cpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
+++ src/AST/Copy.cpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -0,0 +1,121 @@
+//
+// Cforall Version 1.0.0 Copyright (C) 2015 University of Waterloo
+//
+// The contents of this file are covered under the licence agreement in the
+// file "LICENCE" distributed with Cforall.
+//
+// Copy.cpp -- Provides functions to copy the AST.
+//
+// Author           : Andrew Beach
+// Created On       : Thr Nov 11  9:16:00 2019
+// Last Modified By : Andrew Beach
+// Last Modified On : Thr Nov 11  9:28:00 2021
+// Update Count     : 0
+//
+
+#include "Copy.hpp"
+
+#include "Decl.hpp"
+#include "Expr.hpp"
+#include "Pass.hpp"
+#include "Stmt.hpp"
+#include "Type.hpp"
+#include <unordered_set>
+#include <unordered_map>
+
+namespace ast {
+
+namespace {
+
+class DeepCopyCore {
+	std::unordered_map< const Node *, const Node * > nodeCache;
+	std::unordered_set< readonly<Node> * > readonlyCache;
+
+	template<typename node_t>
+	void readonlyInsert( const readonly<node_t> * ptrptr ) {
+		readonlyCache.insert( (readonly<Node> *) ptrptr );
+	}
+
+public:
+	template<typename node_t>
+	const node_t * previsit( const node_t * node ) {
+		const node_t * copy = shallowCopy( node );
+		nodeCache.insert( std::make_pair( node, copy ) );
+		return copy;
+	}
+
+	void postvisit( const AggregateDecl * node ) {
+		readonlyInsert( &node->parent );
+	}
+
+	void postvisit( const StructInstType * node ) {
+		readonlyInsert( &node->base );
+	}
+
+	void postvisit( const UnionInstType * node ) {
+		readonlyInsert( &node->base );
+	}
+
+	void postvisit( const EnumInstType * node ) {
+		readonlyInsert( &node->base );
+	}
+
+	void postvisit( const TraitInstType * node ) {
+		readonlyInsert( &node->base );
+	}
+
+	void postvisit( const TypeInstType * node ) {
+		readonlyInsert( &node->base );
+	}
+
+	void postvisit( const ImplicitCtorDtorStmt * node ) {
+		readonlyInsert( (const readonly<Stmt> *) &node->callStmt );
+	}
+
+	void postvisit( const StmtExpr * node ) {
+		readonlyInsert( &node->resultExpr );
+	}
+
+	void postvisit( const MemberExpr * node ) {
+		readonlyInsert( &node->member );
+	}
+
+	void postvisit( const VariableExpr * node ) {
+		readonlyInsert( &node->var );
+	}
+
+	void postvisit( const OffsetofExpr * node ) {
+		readonlyInsert( &node->member );
+	}
+
+	void postvisit( const DeletedExpr * node ) {
+		readonlyInsert( &node->deleteStmt );
+	}
+
+	void readonlyUpdates() {
+		for ( readonly<Node> * ptr : readonlyCache ) {
+			auto it = nodeCache.find( ptr->get() );
+			if ( nodeCache.end() != it ) {
+				*ptr = it->second;
+			}
+		}
+	}
+};
+
+}
+
+template<>
+Node * deepCopy<Node>( const Node * localRoot ) {
+	Pass< DeepCopyCore > dc;
+	Node const * newRoot = localRoot->accept( dc );
+	dc.core.readonlyUpdates();
+	return const_cast< Node * >( newRoot );
+}
+
+}
+
+// Local Variables: //
+// tab-width: 4 //
+// mode: c++ //
+// compile-command: "make install" //
+// End: //
Index: src/AST/Copy.hpp
===================================================================
--- src/AST/Copy.hpp	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ src/AST/Copy.hpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -10,17 +10,12 @@
 // Created On       : Wed Jul 10 16:13:00 2019
 // Last Modified By : Andrew Beach
-// Last Modified On : Fri Jun 19 16:43:00 2020
-// Update Count     : 1
+// Last Modified On : Thr Nov 11  9:22:00 2021
+// Update Count     : 2
 //
 
 #pragma once
 
-#include "Decl.hpp"
-#include "Expr.hpp"
-#include "Pass.hpp"
-#include "Stmt.hpp"
-#include "Type.hpp"
-#include <unordered_set>
-#include <unordered_map>
+#include "Node.hpp"
+#include <cassert>
 
 namespace ast {
@@ -43,75 +38,5 @@
  */
 
-class DeepCopyCore {
-	std::unordered_map< const Node *, const Node * > nodeCache;
-	std::unordered_set< readonly<Node> * > readonlyCache;
-
-	template<typename node_t>
-	void readonlyInsert( const readonly<node_t> * ptrptr ) {
-		readonlyCache.insert( (readonly<Node> *) ptrptr );
-	}
-
-public:
-	template<typename node_t>
-	const node_t * previsit( const node_t * node ) {
-		const node_t * copy = shallowCopy( node );
-		nodeCache.insert( std::make_pair( node, copy ) );
-		return copy;
-	}
-
-	void postvisit( const AggregateDecl * node ) {
-		readonlyInsert( &node->parent );
-	}
-
-	void postvisit( const StructInstType * node ) {
-		readonlyInsert( &node->base );
-	}
-
-	void postvisit( const UnionInstType * node ) {
-		readonlyInsert( &node->base );
-	}
-
-	void postvisit( const EnumInstType * node ) {
-		readonlyInsert( &node->base );
-	}
-
-	void postvisit( const TraitInstType * node ) {
-		readonlyInsert( &node->base );
-	}
-
-	void postvisit( const TypeInstType * node ) {
-		readonlyInsert( &node->base );
-	}
-
-	void postvisit( const ImplicitCtorDtorStmt * node ) {
-		readonlyInsert( (const readonly<Stmt> *) &node->callStmt );
-	}
-
-	void postvisit( const MemberExpr * node ) {
-		readonlyInsert( &node->member );
-	}
-
-	void postvisit( const VariableExpr * node ) {
-		readonlyInsert( &node->var );
-	}
-
-	void postvisit( const OffsetofExpr * node ) {
-		readonlyInsert( &node->member );
-	}
-
-	void postvisit( const DeletedExpr * node ) {
-		readonlyInsert( &node->deleteStmt );
-	}
-
-	void readonlyUpdates() {
-		for ( readonly<Node> * ptr : readonlyCache ) {
-			auto it = nodeCache.find( ptr->get() );
-			if ( nodeCache.end() != it ) {
-				*ptr = it->second;
-			}
-		}
-	}
-};
-
+// Implementations:
 template<typename node_t>
 node_t * shallowCopy( const node_t * localRoot ) {
@@ -121,9 +46,9 @@
 template<typename node_t>
 node_t * deepCopy( const node_t * localRoot ) {
-	Pass< DeepCopyCore > dc;
-	node_t const * newRoot = localRoot->accept( dc );
-	dc.core.readonlyUpdates();
-	return const_cast< node_t * >( newRoot );
+	return strict_dynamic_cast<node_t *>( deepCopy<Node>( localRoot ) );
 }
+
+template<>
+Node * deepCopy<Node>( const Node * localRoot );
 
 }
Index: src/AST/Decl.hpp
===================================================================
--- src/AST/Decl.hpp	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ src/AST/Decl.hpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -131,4 +131,5 @@
 	// declared type, derived from parameter declarations
 	ptr<FunctionType> type;
+	/// Null for the forward declaration of a function.
 	ptr<CompoundStmt> stmts;
 	std::vector< ptr<Expr> > withExprs;
@@ -269,8 +270,8 @@
 	: AggregateDecl( loc, name, std::move(attrs), linkage ), kind( kind ) {}
 
-	bool is_coroutine() { return kind == Coroutine; }
-	bool is_generator() { return kind == Generator; }
-	bool is_monitor  () { return kind == Monitor  ; }
-	bool is_thread   () { return kind == Thread   ; }
+	bool is_coroutine() const { return kind == Coroutine; }
+	bool is_generator() const { return kind == Generator; }
+	bool is_monitor  () const { return kind == Monitor  ; }
+	bool is_thread   () const { return kind == Thread   ; }
 
 	const Decl * accept( Visitor & v ) const override { return v.visit( this ); }
Index: src/AST/Init.hpp
===================================================================
--- src/AST/Init.hpp	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ src/AST/Init.hpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -98,4 +98,5 @@
 	const_iterator begin() const { return initializers.begin(); }
 	const_iterator end() const { return initializers.end(); }
+	size_t size() const { return initializers.size(); }
 
 	const Init * accept( Visitor & v ) const override { return v.visit( this ); }
Index: src/AST/Pass.hpp
===================================================================
--- src/AST/Pass.hpp	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ src/AST/Pass.hpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -109,5 +109,5 @@
 	static auto read( node_type const * node, Args&&... args ) {
 		Pass<core_t> visitor( std::forward<Args>( args )... );
-		node_type const * temp = node->accept( visitor );
+		auto const * temp = node->accept( visitor );
 		assert( temp == node );
 		return visitor.get_result();
@@ -124,5 +124,5 @@
 	static auto read( node_type const * node ) {
 		Pass<core_t> visitor;
-		node_type const * temp = node->accept( visitor );
+		auto const * temp = node->accept( visitor );
 		assert( temp == node );
 		return visitor.get_result();
@@ -348,9 +348,11 @@
 
 	/// When this node is finished being visited, restore the value of a variable
+	/// You may assign to the return value to set the new value in the same statement.
 	template< typename T >
-	void GuardValue( T& val ) {
+	T& GuardValue( T& val ) {
 		at_cleanup( [ val ]( void * newVal ) {
 			* static_cast< T * >( newVal ) = val;
 		}, static_cast< void * >( & val ) );
+		return val;
 	}
 
@@ -394,4 +396,14 @@
 };
 
+/// Used to get a pointer to the wrapping TranslationUnit.
+struct WithConstTranslationUnit {
+	const TranslationUnit * translationUnit = nullptr;
+
+	const TranslationUnit & transUnit() const {
+		assertf( translationUnit, "WithConstTranslationUnit not set-up." );
+		return *translationUnit;
+	}
+};
+
 }
 
Index: src/AST/Pass.impl.hpp
===================================================================
--- src/AST/Pass.impl.hpp	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ src/AST/Pass.impl.hpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -420,5 +420,11 @@
 template< typename core_t >
 inline void ast::accept_all( ast::TranslationUnit & unit, ast::Pass< core_t > & visitor ) {
-	return ast::accept_all( unit.decls, visitor );
+	if ( auto ptr = __pass::translation_unit::get_cptr( visitor.core, 0 ) ) {
+		ValueGuard<const TranslationUnit *> guard( *ptr );
+		*ptr = &unit;
+		return ast::accept_all( unit.decls, visitor );
+	} else {
+		return ast::accept_all( unit.decls, visitor );
+	}
 }
 
Index: src/AST/Pass.proto.hpp
===================================================================
--- src/AST/Pass.proto.hpp	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ src/AST/Pass.proto.hpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -426,4 +426,18 @@
 	} // namespace forall
 
+	// For passes that need access to the global context. Sreaches `translationUnit`
+	namespace translation_unit {
+		template<typename core_t>
+		static inline auto get_cptr( core_t & core, int )
+				-> decltype( &core.translationUnit ) {
+			return &core.translationUnit;
+		}
+
+		template<typename core_t>
+		static inline const TranslationUnit ** get_cptr( core_t &, long ) {
+			return nullptr;
+		}
+	}
+
 	template<typename core_t>
 	static inline auto get_result( core_t & core, char ) -> decltype( core.result() ) {
Index: src/AST/Stmt.hpp
===================================================================
--- src/AST/Stmt.hpp	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ src/AST/Stmt.hpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -175,4 +175,5 @@
 class CaseStmt final : public Stmt {
 public:
+	/// Null for the default label.
 	ptr<Expr> cond;
 	std::vector<ptr<Stmt>> stmts;
Index: src/AST/TranslationUnit.hpp
===================================================================
--- src/AST/TranslationUnit.hpp	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ src/AST/TranslationUnit.hpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -26,8 +26,8 @@
 	std::list< ptr< Decl > > decls;
 
-	struct Globals {
+	struct Global {
 		std::map< UniqueId, Decl * > idMap;
 
-		const Type * sizeType;
+		ptr<Type> sizeType;
 		const FunctionDecl * dereference;
 		const StructDecl * dtorStruct;
Index: src/AST/module.mk
===================================================================
--- src/AST/module.mk	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ src/AST/module.mk	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -24,4 +24,5 @@
 	AST/Convert.cpp \
 	AST/Convert.hpp \
+	AST/Copy.cpp \
 	AST/Copy.hpp \
 	AST/CVQualifiers.hpp \
Index: src/AST/porting.md
===================================================================
--- src/AST/porting.md	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ src/AST/porting.md	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -98,4 +98,5 @@
 	* `Initializer` => `ast::Init`
     * `Statement` => `ast::Stmt`
+    * `ReferenceToType` => `ast::BaseInstType`
 	* any field names should follow a similar renaming
   * because they don't really belong to `Type` (and for consistency with `Linkage::Spec`):
Index: src/CodeGen/FixMain.cc
===================================================================
--- src/CodeGen/FixMain.cc	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ src/CodeGen/FixMain.cc	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -22,4 +22,7 @@
 #include <string>                  // for operator<<
 
+#include "AST/Decl.hpp"
+#include "AST/Type.hpp"
+#include "Common/PassVisitor.h"
 #include "Common/SemanticError.h"  // for SemanticError
 #include "CodeGen/GenType.h"       // for GenType
@@ -29,6 +32,23 @@
 
 namespace CodeGen {
+
+namespace {
+
+struct FindMainCore {
+	FunctionDecl * main_signature = nullptr;
+
+	void previsit( FunctionDecl * decl ) {
+		if ( FixMain::isMain( decl ) ) {
+			if ( main_signature ) {
+				SemanticError( decl, "Multiple definition of main routine\n" );
+			}
+			main_signature = decl;
+		}
+	}
+};
+
+}
+
 	bool FixMain::replace_main = false;
-	std::unique_ptr<FunctionDecl> FixMain::main_signature = nullptr;
 
 	template<typename container>
@@ -37,16 +57,13 @@
 	}
 
-	void FixMain::registerMain(FunctionDecl* functionDecl)
-	{
-		if(main_signature) {
-			SemanticError(functionDecl, "Multiple definition of main routine\n");
-		}
-		main_signature.reset( functionDecl->clone() );
-	}
+	void FixMain::fix( std::list< Declaration * > & translationUnit,
+			std::ostream &os, const char* bootloader_filename ) {
+		PassVisitor< FindMainCore > main_finder;
+		acceptAll( translationUnit, main_finder );
+		FunctionDecl * main_signature = main_finder.pass.main_signature;
 
-	void FixMain::fix(std::ostream &os, const char* bootloader_filename) {
 		if( main_signature ) {
 			os << "static inline int invoke_main(int argc, char* argv[], char* envp[]) { (void)argc; (void)argv; (void)envp; return ";
-			main_signature->mangleName = SymTab::Mangler::mangle(main_signature.get());
+			main_signature->mangleName = SymTab::Mangler::mangle(main_signature);
 
 			os << main_signature->get_scopedMangleName() << "(";
@@ -65,3 +82,72 @@
 		}
 	}
+
+namespace {
+
+ObjectDecl * signedIntObj() {
+	return new ObjectDecl(
+		"", Type::StorageClasses(), LinkageSpec::Cforall, 0,
+		new BasicType( Type::Qualifiers(), BasicType::SignedInt ), nullptr );
+}
+
+ObjectDecl * charStarObj() {
+	return new ObjectDecl(
+		"", Type::StorageClasses(), LinkageSpec::Cforall, 0,
+		new PointerType( Type::Qualifiers(),
+			new PointerType( Type::Qualifiers(),
+				new BasicType( Type::Qualifiers(), BasicType::Char ) ) ),
+		nullptr );
+}
+
+std::string create_mangled_main_function_name( FunctionType * function_type ) {
+	std::unique_ptr<FunctionDecl> decl( new FunctionDecl(
+		"main", Type::StorageClasses(), LinkageSpec::Cforall,
+		function_type, nullptr ) );
+	return SymTab::Mangler::mangle( decl.get() );
+}
+
+std::string mangled_0_argument_main() {
+	FunctionType* main_type = new FunctionType( Type::Qualifiers(), true );
+	main_type->get_returnVals().push_back( signedIntObj() );
+	return create_mangled_main_function_name( main_type );
+}
+
+std::string mangled_2_argument_main() {
+	FunctionType* main_type = new FunctionType( Type::Qualifiers(), false );
+	main_type->get_returnVals().push_back( signedIntObj() );
+	main_type->get_parameters().push_back( signedIntObj() );
+	main_type->get_parameters().push_back( charStarObj() );
+	return create_mangled_main_function_name( main_type );
+}
+
+bool is_main( const std::string & mangled_name ) {
+	// This breaks if you move it out of the function.
+	static const std::string mangled_mains[] = {
+		mangled_0_argument_main(),
+		mangled_2_argument_main(),
+		//mangled_3_argument_main(),
+	};
+
+	for ( auto main_name : mangled_mains ) {
+		if ( main_name == mangled_name ) return true;
+	}
+	return false;
+}
+
+} // namespace
+
+bool FixMain::isMain( FunctionDecl * decl ) {
+	if ( std::string("main") != decl->name ) {
+		return false;
+	}
+	return is_main( SymTab::Mangler::mangle( decl, true, true ) );
+}
+
+bool FixMain::isMain( const ast::FunctionDecl * decl ) {
+	if ( std::string("main") != decl->name ) {
+		return false;
+	}
+	return is_main( Mangle::mangle( decl, Mangle::Type ) );
+}
+
 };
Index: src/CodeGen/FixMain.h
===================================================================
--- src/CodeGen/FixMain.h	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ src/CodeGen/FixMain.h	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -9,7 +9,7 @@
 // Author           : Thierry Delisle
 // Created On       : Thr Jan 12 14:11:09 2017
-// Last Modified By : Peter A. Buhr
-// Last Modified On : Sun Feb 16 03:24:32 2020
-// Update Count     : 5
+// Last Modified By : Andrew Beach
+// Last Modified On : Fri Oct 29 16:20:00 2021
+// Update Count     : 8
 //
 
@@ -18,27 +18,35 @@
 #include <iosfwd>
 #include <memory>
+#include <list>
 
 #include "SynTree/LinkageSpec.h"
 
+class Declaration;
 class FunctionDecl;
+namespace ast {
+	class FunctionDecl;
+}
 
 namespace CodeGen {
-	class FixMain {
-	  public :
-		static inline LinkageSpec::Spec mainLinkage() {
-			return replace_main ? LinkageSpec::Cforall : LinkageSpec::C;
-		}
-		
-		static inline void setReplaceMain(bool val) {
-			replace_main = val;
-		}
 
-		static void registerMain(FunctionDecl* val);
+class FixMain {
+public :
+	static inline LinkageSpec::Spec mainLinkage() {
+		return replace_main ? LinkageSpec::Cforall : LinkageSpec::C;
+	}
 
-		static void fix(std::ostream &os, const char* bootloader_filename);
+	static inline void setReplaceMain(bool val) {
+		replace_main = val;
+	}
 
-	  private:
-  		static bool replace_main;
-		static std::unique_ptr<FunctionDecl> main_signature;
-	};
+	static bool isMain(FunctionDecl* decl);
+	static bool isMain(const ast::FunctionDecl * decl);
+
+	static void fix( std::list< Declaration * > & decls,
+			std::ostream &os, const char* bootloader_filename );
+
+private:
+	static bool replace_main;
+};
+
 } // namespace CodeGen
Index: src/CodeGen/FixNames.cc
===================================================================
--- src/CodeGen/FixNames.cc	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ src/CodeGen/FixNames.cc	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -9,7 +9,7 @@
 // Author           : Richard C. Bilson
 // Created On       : Mon May 18 07:44:20 2015
-// Last Modified By : Peter A. Buhr
-// Last Modified On : Fri Dec 13 23:39:14 2019
-// Update Count     : 21
+// Last Modified By : Andrew Beach
+// Last Modified On : Fri Oct 29 15:49:00 2021
+// Update Count     : 23
 //
 
@@ -19,4 +19,7 @@
 #include <string>                  // for string, operator!=, operator==
 
+#include "AST/Chain.hpp"
+#include "AST/Expr.hpp"
+#include "AST/Pass.hpp"
 #include "Common/PassVisitor.h"
 #include "Common/SemanticError.h"  // for SemanticError
@@ -46,53 +49,4 @@
 	};
 
-	std::string mangle_main() {
-		FunctionType* main_type;
-		std::unique_ptr<FunctionDecl> mainDecl { new FunctionDecl( "main", Type::StorageClasses(), LinkageSpec::Cforall,
-																   main_type = new FunctionType( Type::Qualifiers(), true ), nullptr )
-				};
-		main_type->get_returnVals().push_back(
-			new ObjectDecl( "", Type::StorageClasses(), LinkageSpec::Cforall, 0, new BasicType( Type::Qualifiers(), BasicType::SignedInt ), nullptr )
-		);
-
-		auto && name = SymTab::Mangler::mangle( mainDecl.get() );
-		// std::cerr << name << std::endl;
-		return std::move(name);
-	}
-	std::string mangle_main_args() {
-		FunctionType* main_type;
-		std::unique_ptr<FunctionDecl> mainDecl { new FunctionDecl( "main", Type::StorageClasses(), LinkageSpec::Cforall,
-																   main_type = new FunctionType( Type::Qualifiers(), false ), nullptr )
-				};
-		main_type->get_returnVals().push_back(
-			new ObjectDecl( "", Type::StorageClasses(), LinkageSpec::Cforall, 0, new BasicType( Type::Qualifiers(), BasicType::SignedInt ), nullptr )
-		);
-
-		main_type->get_parameters().push_back(
-			new ObjectDecl( "", Type::StorageClasses(), LinkageSpec::Cforall, 0, new BasicType( Type::Qualifiers(), BasicType::SignedInt ), nullptr )
-		);
-
-		main_type->get_parameters().push_back(
-			new ObjectDecl( "", Type::StorageClasses(), LinkageSpec::Cforall, 0,
-			new PointerType( Type::Qualifiers(), new PointerType( Type::Qualifiers(), new BasicType( Type::Qualifiers(), BasicType::Char ) ) ),
-			nullptr )
-		);
-
-		auto&& name = SymTab::Mangler::mangle( mainDecl.get() );
-		// std::cerr << name << std::endl;
-		return std::move(name);
-	}
-
-	bool is_main(const std::string& name) {
-		static std::string mains[] = {
-			mangle_main(),
-			mangle_main_args()
-		};
-
-		for(const auto& m : mains) {
-			if( name == m ) return true;
-		}
-		return false;
-	}
-
 	void fixNames( std::list< Declaration* > & translationUnit ) {
 		PassVisitor<FixNames> fixer;
@@ -118,5 +72,5 @@
 		fixDWT( functionDecl );
 
-		if(is_main( SymTab::Mangler::mangle(functionDecl, true, true) )) {
+		if ( FixMain::isMain( functionDecl ) ) {
 			int nargs = functionDecl->get_functionType()->get_parameters().size();
 			if( !(nargs == 0 || nargs == 2 || nargs == 3) ) {
@@ -124,5 +78,4 @@
 			}
 			functionDecl->get_statements()->get_kids().push_back( new ReturnStmt( new ConstantExpr( Constant::from_int( 0 ) ) ) );
-			CodeGen::FixMain::registerMain( functionDecl );
 		}
 	}
@@ -132,4 +85,56 @@
 		GuardAction( [this](){ scopeLevel--; } );
 	}
+
+/// Does work with the main function and scopeLevels.
+class FixNames_new : public ast::WithGuards {
+	int scopeLevel = 1;
+
+	bool shouldSetScopeLevel( const ast::DeclWithType * dwt ) {
+		return !dwt->name.empty() && dwt->linkage.is_mangled
+			&& dwt->scopeLevel != scopeLevel;
+	}
+public:
+	const ast::ObjectDecl *postvisit( const ast::ObjectDecl *objectDecl ) {
+		if ( shouldSetScopeLevel( objectDecl ) ) {
+			return ast::mutate_field( objectDecl, &ast::ObjectDecl::scopeLevel, scopeLevel );
+		}
+		return objectDecl;
+	}
+
+	const ast::FunctionDecl *postvisit( const ast::FunctionDecl *functionDecl ) {
+		// This store is used to ensure a maximum of one call to mutate.
+		ast::FunctionDecl * mutDecl = nullptr;
+
+		if ( shouldSetScopeLevel( functionDecl ) ) {
+			mutDecl = ast::mutate( functionDecl );
+			mutDecl->scopeLevel = scopeLevel;
+		}
+
+		if ( FixMain::isMain( functionDecl ) ) {
+			if ( !mutDecl ) { mutDecl = ast::mutate( functionDecl ); }
+
+			int nargs = mutDecl->params.size();
+			if ( 0 != nargs && 2 != nargs && 3 != nargs ) {
+				SemanticError( functionDecl, "Main expected to have 0, 2 or 3 arguments\n" );
+			}
+			ast::chain_mutate( mutDecl->stmts )->kids.push_back(
+				new ast::ReturnStmt(
+					mutDecl->location,
+					ast::ConstantExpr::from_int( mutDecl->location, 0 )
+				)
+			);
+		}
+		return mutDecl ? mutDecl : functionDecl;
+	}
+
+	void previsit( const ast::CompoundStmt * ) {
+		GuardValue( scopeLevel ) += 1;
+	}
+};
+
+void fixNames( ast::TranslationUnit & translationUnit ) {
+	ast::Pass<FixNames_new>::run( translationUnit );
+}
+
 } // namespace CodeGen
 
Index: src/CodeGen/FixNames.h
===================================================================
--- src/CodeGen/FixNames.h	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ src/CodeGen/FixNames.h	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -9,7 +9,7 @@
 // Author           : Richard C. Bilson
 // Created On       : Mon May 18 07:44:20 2015
-// Last Modified By : Peter A. Buhr
-// Last Modified On : Fri Jul 21 22:17:33 2017
-// Update Count     : 3
+// Last Modified By : Andrew Beach
+// Last Modified On : Tue Oct 26 13:47:00 2021
+// Update Count     : 4
 //
 
@@ -19,8 +19,12 @@
 
 class Declaration;
+namespace ast {
+	struct TranslationUnit;
+}
 
 namespace CodeGen {
 	/// mangles object and function names
 	void fixNames( std::list< Declaration* > & translationUnit );
+	void fixNames( ast::TranslationUnit & translationUnit );
 } // namespace CodeGen
 
Index: src/CodeTools/DeclStats.cc
===================================================================
--- src/CodeTools/DeclStats.cc	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ src/CodeTools/DeclStats.cc	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -156,6 +156,17 @@
 		/// number of counting bins for linkages
 		static const unsigned n_named_specs = 8;
-		/// map from total number of specs to bins
-		static const unsigned ind_for_linkage[16];
+		/// Mapping function from linkage to bin.
+		static unsigned linkage_index( LinkageSpec::Spec spec ) {
+			switch ( spec ) {
+			case LinkageSpec::Intrinsic:  return 0;
+			case LinkageSpec::C:          return 1;
+			case LinkageSpec::Cforall:    return 2;
+			case LinkageSpec::AutoGen:    return 3;
+			case LinkageSpec::Compiler:   return 4;
+			case LinkageSpec::BuiltinCFA: return 5;
+			case LinkageSpec::BuiltinC:   return 6;
+			default:                      return 7;
+			}
+		}
 
 		Stats for_linkage[n_named_specs];            ///< Stores separate stats per linkage
@@ -366,5 +377,5 @@
 			const std::string& mangleName = decl->get_mangleName().empty() ? decl->name : decl->get_mangleName();
 			if ( seen_names.insert( mangleName ).second ) {
-				Stats& stats = for_linkage[ ind_for_linkage[ decl->linkage ] ];
+				Stats& stats = for_linkage[ linkage_index( decl->linkage ) ];
 
 				++stats.n_decls;
@@ -527,7 +538,4 @@
 	};
 
-	const unsigned DeclStats::ind_for_linkage[]
-		= { 7, 7, 2, 1,   7, 7, 7, 3,   4, 7, 6, 5,   7, 7, 7, 0 };
-
 	void printDeclStats( std::list< Declaration * > &translationUnit ) {
 		PassVisitor<DeclStats> stats;
Index: src/Common/DeclStats.cpp
===================================================================
--- src/Common/DeclStats.cpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
+++ src/Common/DeclStats.cpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -0,0 +1,575 @@
+//
+// Cforall Version 1.0.0 Copyright (C) 2015 University of Waterloo
+//
+// The contents of this file are covered under the licence agreement in the
+// file "LICENCE" distributed with Cforall.
+//
+// DeclStats.cpp -- Print statistics about a translation unit's declarations.
+//
+// Author           : Andrew Beach
+// Created On       : Fri Oct  1 14:26:00 2021
+// Last Modified By : Andrew Beach
+// Last Modified On : Wed Oct  8 11:24:00 2021
+// Update Count     : 0
+//
+
+#include "DeclStats.hpp"
+
+#include "AST/LinkageSpec.hpp"
+#include "AST/Pass.hpp"
+#include "AST/Print.hpp"
+#include "Common/VectorMap.h"
+
+#include <iostream>
+#include <map>
+#include <unordered_map>
+#include <unordered_set>
+
+// Everything but printDeclStats at the bottom is hidden.
+namespace {
+
+template<typename T>
+void sum( T & l, const T & r ) { l += r; }
+
+void sum( VectorMap<unsigned> & l, const VectorMap<unsigned> & r ) {
+	l.reserve( r.size() );
+	for ( unsigned i = 0 ; i < r.size() ; ++i ) {
+		l[i] += r[i];
+	}
+}
+
+template<typename KeyT>
+void sum( std::map<KeyT, unsigned> & l, const std::map<KeyT, unsigned> & r ) {
+	for ( const auto & entry : r ) {
+		l[ entry.first ] += entry.second;
+	}
+}
+
+template<typename KeyT>
+void sum( std::unordered_map<KeyT, unsigned> & l,
+		const std::unordered_map<KeyT, unsigned> & r ) {
+	for ( const auto & entry : r ) {
+		l[ entry.first ] += entry.second;
+	}
+}
+
+/// Stores statistics on a single group of arguments or return values.
+struct ArgPackStats {
+	/// Count of decls with each number of elements.
+	VectorMap<unsigned> n;
+	/// Count of decls with each number of basic type elements.
+	VectorMap<unsigned> n_basic;
+	/// Count of decls with each number of generic type elements.
+	VectorMap<unsigned> n_generic;
+	/// Count of decls with each number of polymorphic elements.
+	VectorMap<unsigned> n_poly;
+	/// Count of decls with each number of non-generic compound types.
+	VectorMap<unsigned> n_compound;
+	/// Count of decls with each percentage of basic type elements.
+	std::map<unsigned, unsigned> p_basic;
+	/// Count of decls with each percentage of generic type elements.
+	std::map<unsigned, unsigned> p_generic;
+	/// Count of decls with each percentage of polymorphic elements.
+	std::map<unsigned, unsigned> p_poly;
+	/// Count of decls with each percentage of non-generic compound type elements.
+	std::map<unsigned, unsigned> p_compound;
+	/// Count of decls with each number of distinct types in the pack.
+	VectorMap<unsigned> n_types;
+	/// Count of decls with each percentage of new types in lists.
+	/// Types used in the parameter list that recur in the return list are not considered to be new.
+	std::map<unsigned, unsigned> p_new;
+
+	ArgPackStats& operator+=( const ArgPackStats& other ) {
+		sum(n, other.n);
+		sum(n_basic, other.n_basic);
+		sum(n_generic, other.n_generic);
+		sum(n_poly, other.n_poly);
+		sum(n_compound, other.n_compound);
+		sum(p_basic, other.p_basic);
+		sum(p_generic, other.p_generic);
+		sum(p_poly, other.p_poly);
+		sum(p_compound, other.p_compound);
+		sum(n_types, other.n_types);
+		sum(p_new, other.p_new);
+
+		return *this;
+	}
+};
+
+/// Collected statistics on a group of declarations.
+struct Stats {
+	/// Total number of declarations in these statistics.
+	unsigned n_decls;
+	/// Count of declarations with each number of assertion parameters.
+	VectorMap<unsigned> n_type_params;
+	/// Count of generic types with each number of type parameters.
+	VectorMap<unsigned> n_generic_params;
+	/// Count of maximum nesting depth of types.
+	VectorMap<unsigned> n_generic_nesting;
+	/// Count of declarations with each name.
+	std::unordered_map<std::string, unsigned> by_name;
+	/// Count of uses of each basic type.
+	std::unordered_map<std::string, unsigned> basic_type_names;
+	/// Count of uses of each generic type name (includes "*", "[]", "(*)", "[,]").
+	std::unordered_map<std::string, unsigned> generic_type_names;
+	/// Count of uses of each non-generic aggregate type.
+	std::unordered_map<std::string, unsigned> compound_type_names;
+	/// Count of decls using each basic type.
+	std::unordered_map<std::string, unsigned> basic_type_decls;
+	/// Count of decls using each generic type (includes "*", "[]", "(*)", "[,]").
+	std::unordered_map<std::string, unsigned> generic_type_decls;
+	/// Count of decls using each compound type.
+	std::unordered_map<std::string, unsigned> compound_type_decls;
+	/// Stats for the parameter lists.
+	ArgPackStats params;
+	/// Stats for the return lists.
+	ArgPackStats returns;
+
+	/// Count of declarations with each number of assertions.
+	std::map<unsigned, unsigned> n_assns;
+	/// Stats for the assertions' parameters.
+	ArgPackStats assn_params;
+	/// Stats for the assertions' return types.
+	ArgPackStats assn_returns;
+
+	Stats& operator+=( const Stats& other ) {
+		sum( n_decls, other.n_decls );
+		sum( n_type_params, other.n_type_params );
+		sum( n_generic_params, other.n_generic_params );
+		sum( n_generic_nesting, other.n_generic_nesting );
+		sum( by_name, other.by_name );
+		sum( basic_type_names, other.basic_type_names );
+		sum( generic_type_names, other.generic_type_names );
+		sum( compound_type_names, other.compound_type_names );
+		sum( basic_type_decls, other.basic_type_decls );
+		sum( generic_type_decls, other.generic_type_decls );
+		sum( compound_type_decls, other.compound_type_decls );
+		sum( params, other.params );
+		sum( returns, other.returns );
+		sum( n_assns, other.n_assns );
+		sum( assn_params, other.assn_params );
+		sum( assn_returns, other.assn_returns );
+
+		return *this;
+	}
+
+};
+
+void update_max( unsigned & max, unsigned value ) {
+	if ( max < value ) max = value;
+}
+
+// Where all unnamed specs are counted as one named spec group.
+constexpr unsigned num_named_specs = 8;
+
+unsigned linkage_index( ast::Linkage::Spec spec ) {
+	switch ( spec.val ) {
+	case ast::Linkage::Intrinsic.val:  return 0;
+	case ast::Linkage::C.val:          return 1;
+	case ast::Linkage::Cforall.val:    return 2;
+	case ast::Linkage::AutoGen.val:    return 3;
+	case ast::Linkage::Compiler.val:   return 4;
+	case ast::Linkage::BuiltinCFA.val: return 5;
+	case ast::Linkage::BuiltinC.val:   return 6;
+	default:                           return 7;
+	}
+}
+
+struct DeclStats : public ast::WithShortCircuiting {
+	/// Stores separate stats per linkage.
+	Stats by_linkage[num_named_specs];
+	/// Stores manglenames already seen to avoid double-counting.
+	std::unordered_set<std::string> seen_names;
+	/// Overall stats.
+	Stats total;
+	/// Count of expressions with (depth, fanout)
+	std::map<std::pair<unsigned, unsigned>, unsigned> exprs_by_fanout_at_depth;
+
+	/// Count that we have seen a named type.
+	void countType(
+			const std::string & name, unsigned & n,
+			std::unordered_map<std::string, unsigned> & names,
+			std::unordered_map<std::string, unsigned> & decls,
+			std::unordered_set<std::string> & elSeen ) {
+		++n;
+		++names[ name ];
+		if ( elSeen.insert( name ).second ) {
+			++decls[ name ];
+		}
+	}
+
+	/// Perform type analysis on a subtype.
+	void analyzeSubtype( const ast::Type * type, Stats & stats,
+			std::unordered_set<std::string> & elSeen, unsigned & n_poly,
+			bool & seen_poly, unsigned & max_depth, unsigned depth ) {
+		// This kind of gets in the way of grouping arguments.
+		unsigned ignored = 0;
+		analyzeType(
+			type, stats, elSeen, ignored, ignored, n_poly, ignored,
+			seen_poly, max_depth, depth + 1 );
+	}
+
+	/// Perform type analysis on each subtype.
+	void analyzeSubtypes(
+			const std::vector<ast::ptr<ast::Type>> & types, Stats & stats,
+			std::unordered_set<std::string> & elSeen, unsigned & n_poly,
+			bool & seen_poly, unsigned & max_depth, unsigned depth ) {
+		for ( const auto & type : types ) {
+			analyzeSubtype( type, stats, elSeen, n_poly, seen_poly, max_depth, depth );
+		}
+	}
+
+	/// Perform sub-type analysis on each subtype in an argument pack.
+	void analyzeSubPack(
+			const std::vector<ast::ptr<ast::Type>> & types, Stats & stats,
+			std::unordered_set<std::string> & elSeen, unsigned & n_poly,
+			bool & seen_poly, unsigned & max_depth, unsigned depth,
+			unsigned & n_subs ) {
+		// ... and count voids?
+		for ( const auto & type : types ) {
+			if ( type.as<ast::VoidType>() ) {
+				++n_subs;
+			}
+			// Could this be in `else`?
+			analyzeSubtype( type, stats, elSeen, n_poly, seen_poly, max_depth, depth );
+		}
+	}
+
+	/// Analyze and gather stats from a single type.
+	void analyzeType( const ast::ptr<ast::Type> & type, Stats & stats,
+			std::unordered_set<std::string> & elSeen,
+			unsigned & n_basic, unsigned & n_generic, unsigned & n_poly,
+			unsigned & n_agg, bool & seen_poly,
+			unsigned & max_depth, unsigned depth ) {
+		// Almost a visit, except it is only types.
+		if ( const ast::BasicType * t = type.as<ast::BasicType>() ) {
+			const std::string name = ast::BasicType::typeNames[ t->kind ];
+			countType( name, n_basic, stats.basic_type_names, stats.basic_type_decls, elSeen );
+			update_max( max_depth, depth );
+		} else if ( auto t = type.as<ast::PointerType>() ) {
+			static const std::string name = "*";
+			countType( name, n_generic, stats.generic_type_names, stats.generic_type_decls, elSeen );
+			analyzeSubtype( t->base, stats, elSeen, n_poly, seen_poly, max_depth, depth );
+			++stats.n_generic_params.at( 1 );
+		} else if ( auto t = type.as<ast::ArrayType>() ) {
+			static const std::string name = "[]";
+			countType( name, n_generic, stats.generic_type_names, stats.generic_type_decls, elSeen );
+			analyzeSubtype( t->base, stats, elSeen, n_poly, seen_poly, max_depth, depth );
+			++stats.n_generic_params.at( 1 );
+		} else if ( auto t = type.as<ast::ReferenceType>() ) {
+			static const std::string name = "&";
+			countType( name, n_generic, stats.generic_type_names, stats.generic_type_decls, elSeen );
+			analyzeSubtype( t->base, stats, elSeen, n_poly, seen_poly, max_depth, depth );
+			++stats.n_generic_params.at( 1 );
+		} else if ( auto t = type.as<ast::FunctionType>() ) {
+			static const std::string name = "(*)";
+			countType( name, n_generic, stats.generic_type_names, stats.generic_type_decls, elSeen );
+			unsigned n_subs = 0;
+			analyzeSubPack( t->returns, stats, elSeen, n_poly, seen_poly, max_depth, depth, n_subs );
+			analyzeSubPack( t->params, stats, elSeen, n_poly, seen_poly, max_depth, depth, n_subs );
+			++stats.n_generic_params.at( n_subs );
+		} else if ( auto t = type.as<ast::TypeInstType>() ) {
+			if ( !seen_poly ) {
+				++n_poly;
+				seen_poly = true;
+			}
+			countType( t->name, n_agg, stats.compound_type_names,
+					stats.compound_type_decls, elSeen );
+			update_max( max_depth, depth );
+		} else if ( auto t = type.as<ast::BaseInstType>() ) {
+			auto & params = t->params;
+			if ( params.empty() ) {
+				countType( t->name, n_agg, stats.compound_type_names,
+						stats.compound_type_decls, elSeen );
+				update_max( max_depth, depth );
+			} else {
+				countType( t->name, n_generic, stats.generic_type_names,
+						stats.generic_type_decls, elSeen );
+				++stats.n_generic_params.at( params.size() );
+			}
+		} else if ( auto t = type.as<ast::TupleType>() ) {
+			static const std::string name = "[,]";
+			countType( name, n_generic, stats.generic_type_names, stats.generic_type_decls, elSeen);
+			analyzeSubtypes( t->types, stats, elSeen, n_poly, seen_poly, max_depth, depth );
+			++stats.n_generic_params.at( t->size() );
+		} else if ( type.as<ast::VarArgsType>() ) {
+			static const std::string name = "...";
+			countType( name, n_agg, stats.compound_type_names, stats.compound_type_decls, elSeen );
+			update_max( max_depth, depth );
+		} else if ( type.as<ast::ZeroType>() ) {
+			static const std::string name = "0";
+			countType( name, n_basic, stats.basic_type_names, stats.basic_type_decls, elSeen );
+			update_max( max_depth, depth );
+		} else if ( type.as<ast::OneType>() ) {
+			static const std::string name = "1";
+			countType( name, n_basic, stats.basic_type_names, stats.basic_type_decls, elSeen );
+			update_max( max_depth, depth );
+		}
+	}
+
+	/// Update an ArgPackStats based on the list of types it repersents.
+	void analyzeArgPack(
+			const std::vector<ast::ptr<ast::Type>> & types,
+			Stats & stats,
+			ArgPackStats & packStats,
+			// What are these two used for?
+			std::unordered_set<std::string> & seen,
+			std::unordered_set<std::string> & elSeen ) {
+		std::unordered_set<std::string> type_names;
+		unsigned n = 0;
+		unsigned n_basic = 0;
+		unsigned n_generic = 0;
+		unsigned n_poly = 0;
+		unsigned n_compound = 0;
+		unsigned n_new = 0;
+
+		for ( auto & type : types ) {
+			n += type->size();
+
+			std::stringstream ss;
+			ast::print( ss, type );
+			type_names.insert( ss.str() );
+			if ( seen.insert( ss.str() ).second ) {
+				++n_new;
+			}
+
+			bool seen_poly = false;
+			unsigned max_depth = 0;
+			analyzeType(
+				type, stats, elSeen, n_basic, n_generic, n_poly, n_compound,
+				seen_poly, max_depth, 0
+			);
+			++stats.n_generic_nesting.at( max_depth );
+		}
+
+		++packStats.n.at( n );
+		++packStats.n_basic.at( n_basic );
+		++packStats.n_generic.at( n_generic );
+		++packStats.n_poly.at( n_poly );
+		++packStats.n_compound.at( n_compound );
+		if ( n > 0 ) {
+			++packStats.p_basic[ n_basic * 100 / n ];
+			++packStats.p_generic[ n_generic * 100 / n ];
+			++packStats.p_poly[ n_poly * 100 / n ];
+			++packStats.p_compound[ n_compound * 100 / n ];
+			if ( n > 1 ) {
+				++packStats.p_new[ (n_new - 1) * 100 / (n - 1) ];
+			}
+		}
+		++packStats.n_types.at( types.size() );
+	}
+
+	/// Perform type analysis on a function type, storing information in the
+	/// given ArgPackStats.
+	void analyzeFunctionType( const ast::FunctionType * type, Stats& stats,
+			ArgPackStats Stats::* param_pack,
+			ArgPackStats Stats::* return_pack ) {
+		// I still don't know what these are for.
+		std::unordered_set<std::string> seen;
+		std::unordered_set<std::string> elSeen;
+		analyzeArgPack( type->params, stats, stats.*param_pack, seen, elSeen );
+		analyzeArgPack( type->returns, stats, stats.*return_pack, seen, elSeen );
+	}
+
+	/// If the assertion is a function, return the function type.
+	static const ast::FunctionType * getAssertionFunctionType(
+			const ast::ptr<ast::DeclWithType> & assertion ) {
+		if ( auto * assertionObject = assertion.as<ast::ObjectDecl>() ) {
+			if ( auto * ptrTy = assertionObject->type.as<ast::PointerType>() ) {
+				return ptrTy->base.as<ast::FunctionType>();
+			} else {
+				return assertionObject->type.as<ast::FunctionType>();
+			}
+		} else if ( auto * assertionDecl = assertion.as<ast::FunctionDecl>() ) {
+			return assertionDecl->type;
+		}
+		return nullptr;
+	}
+
+	void analyzeFunctionDecl( const ast::FunctionDecl * decl ) {
+		Stats & stats = by_linkage[ linkage_index( decl->linkage ) ];
+
+		++stats.n_decls;
+		const ast::FunctionType * type = decl->type.get();
+		const ast::FunctionType::ForallList & forall = type->forall;
+		++stats.n_type_params.at( forall.size() );
+		unsigned num_assertions = 0;
+		for ( const ast::ptr<ast::TypeInstType> & instType : forall ) {
+			num_assertions += instType->base->assertions.size();
+			for ( const auto & assertion : instType->base->assertions ) {
+				if ( auto assertionType = getAssertionFunctionType( assertion ) ) {
+					analyzeFunctionType( assertionType, stats,
+							&Stats::assn_params, &Stats::assn_returns );
+				}
+			}
+		}
+		++stats.n_assns[ num_assertions ];
+		++stats.by_name[ decl->name ];
+		analyzeFunctionType( type, stats, &Stats::params, &Stats::returns );
+	}
+
+	void analyzeUntypedExpr( const ast::UntypedExpr * expr, unsigned depth ) {
+		unsigned fanout = expr->args.size();
+		++exprs_by_fanout_at_depth[ std::make_pair( depth, fanout ) ];
+
+		for ( const ast::ptr<ast::Expr> & arg : expr->args ) {
+			if ( const auto * untyped = arg.as<ast::UntypedExpr>() ) {
+				analyzeUntypedExpr( untyped, depth + 1 );
+			}
+		}
+	}
+
+public:
+	void previsit( const ast::UntypedExpr * expr ) {
+		visit_children = false;
+		analyzeUntypedExpr( expr, 0 );
+	}
+
+	void previsit( const ast::FunctionDecl * decl ) {
+		const std::string & mangleName = decl->mangleName;
+		const std::string & indexName = mangleName.empty() ? decl->name : mangleName;
+		if ( seen_names.insert( indexName ).second ) {
+			analyzeFunctionDecl( decl );
+		}
+	}
+
+private:
+
+	// Trying to avoid duplication by templates.
+	// I couldn't do it in all cases.
+	template<typename T, typename U>
+	using getter = std::function<U(T const &)>;
+
+	/// Print a single role, for every linkage and the totals.
+	void printRow( const std::string & name, getter<Stats, unsigned> extract ) {
+		std::cout << "\"" << name << "\",";
+		for ( const Stats & stats : by_linkage ) {
+			std::cout << "," << extract( stats );
+		}
+		std::cout << "," << extract( total ) << std::endl;
+	}
+
+	/// Print every row in a group of maps.
+	template<typename Func>
+	void printAllMap( const std::string & name, Func && extract ) {
+		// Get all rows from the total stats.
+		for ( auto & entry : extract( total ) ) {
+			auto & key = entry.first;
+			std::cout << "\"" << name << "\"," << key;
+			for ( const auto & stats : by_linkage ) {
+				const auto & map = extract( stats );
+				auto it = map.find( key );
+				if ( map.end() == it ) {
+					std::cout << ",0";
+				} else {
+					std::cout << "," << it->second;
+				}
+			}
+			std::cout << "," << entry.second << std::endl;
+		}
+	}
+
+	/// Accumalate information, then print every row in the remaining maps.
+	template<typename Func>
+	void printAllSparseHisto( const std::string & name, Func && extract ) {
+		std::map<unsigned, unsigned> histos[num_named_specs];
+		std::map<unsigned, unsigned> histo_total;
+
+		// Collect all data into the histograms.
+		for ( const auto & entry : extract( total ) ) {
+			++histo_total[ entry.second ];
+		}
+
+		for ( unsigned i = 0 ; i < num_named_specs ; ++i ) {
+			for ( const auto & entry : extract( by_linkage[i] ) ) {
+				++histos[ i ][ entry.second ];
+			}
+		}
+
+		// Get all rows from the total stats.
+		for ( const auto & entry : histo_total ) {
+			const unsigned & key = entry.first;
+			std::cout << "\"" << name << "\"," << key;
+			for ( unsigned i = 0 ; i < num_named_specs ; ++i ) {
+				auto it = histos[i].find( key );
+				if ( histos[i].end() == it ) {
+					std::cout << ",0";
+				} else {
+					std::cout << "," << it->second;
+				}
+			}
+			std::cout << "," << entry.second << std::endl;
+		}
+	}
+
+	void printAllPack( const std::string & name, ArgPackStats Stats::* field ) {
+		printAllMap("n_basic_" + name, [&field](const Stats& stats) { return (stats.*field).n_basic; });
+		printAllMap("n_generic_" + name, [&field](const Stats& stats) { return (stats.*field).n_generic; });
+		printAllMap("n_poly_" + name, [&field](const Stats& stats) { return (stats.*field).n_poly; });
+		printAllMap("n_compound_" + name, [&field](const Stats& stats) { return (stats.*field).n_compound; });
+		printAllMap("n_" + name, [&field](const Stats& stats) { return (stats.*field).n; });
+		printAllMap("%_basic_" + name, [&field](const Stats& stats) { return (stats.*field).p_basic; });
+		printAllMap("%_generic_" + name, [&field](const Stats& stats) { return (stats.*field).p_generic; });
+		printAllMap("%_poly_" + name, [&field](const Stats& stats) { return (stats.*field).p_poly; });
+		printAllMap("%_compound_" + name, [&field](const Stats& stats) { return (stats.*field).p_compound; });
+		printAllMap("n_distinct_types_" + name, [&field](const Stats& stats) { return (stats.*field).n_types; });
+		printAllMap("%_new_types_in_" + name, [&field](const Stats& stats) { return (stats.*field).p_new; });
+	}
+
+	static void printPairMap (
+			const std::string & name,
+			const std::map<std::pair<unsigned, unsigned>, unsigned> & map ) {
+		for ( const auto & entry : map ) {
+			const auto & key = entry.first;
+			std::cout << "\"" << name << "\"," << key.first << ','
+				<< key.second << ',' << entry.second << std::endl;
+		}
+	}
+
+public:
+	void print() {
+		for ( auto & stats : by_linkage ) {
+			total += stats;
+		}
+
+		std::cout << ",,\"intrinsic\",\"Cforall\",\"C\",\"autogen\",\"compiler\",\"builtinCFA\",\"builtinC\",\"other\",\"TOTAL\"" << std::endl;
+
+		printAllMap("n_type_params", [](const Stats& stats) { return stats.n_type_params; });
+		printAllMap("n_generic_params", [](const Stats& stats) { return stats.n_generic_params; });
+		printAllMap("n_generic_nesting", [](const Stats& stats) { return stats.n_generic_nesting; });
+		printRow("n_decls", [](const Stats& stats) { return stats.n_decls; });
+		printRow("unique_names", [](const Stats& stats) { return stats.by_name.size(); });
+		printAllSparseHisto("overloads", [](const Stats& stats) { return stats.by_name; });
+		printRow("basic_type_names", [](const Stats& stats) { return stats.basic_type_names.size(); });
+		printAllSparseHisto("basic_type_uses", [](const Stats& stats) { return stats.basic_type_names; });
+		printAllSparseHisto("decls_using_basic_type", [](const Stats& stats) { return stats.basic_type_decls; });
+		printRow("generic_type_names", [](const Stats& stats) { return stats.generic_type_names.size(); });
+		printAllSparseHisto("generic_type_uses", [](const Stats& stats) { return stats.generic_type_names; });
+		printAllSparseHisto("decls_using_generic_type", [](const Stats& stats) { return stats.generic_type_decls; });
+		printRow("compound_type_names", [](const Stats& stats) { return stats.compound_type_names.size(); });
+		printAllSparseHisto("compound_type_uses", [](const Stats& stats) { return stats.compound_type_names; });
+		printAllSparseHisto("decls_using_compound_type", [](const Stats& stats) { return stats.compound_type_decls; });
+		printAllPack("params", &Stats::params);
+		printAllPack("returns", &Stats::returns);
+		printAllMap("n_assns", [](const Stats& stats) { return stats.n_assns; });
+		printAllPack("assn_params", &Stats::assn_params);
+		printAllPack("assn_returns", &Stats::assn_returns);
+		std::cout << std::endl;
+
+		printPairMap( "exprs by depth+fanout", exprs_by_fanout_at_depth );
+	}
+};
+
+} // namespace
+
+void printDeclStats( ast::TranslationUnit & translationUnit ) {
+	ast::Pass<DeclStats> stats;
+	accept_all( translationUnit, stats );
+	stats.core.print();
+}
+
+// Local Variables: //
+// tab-width: 4 //
+// mode: c++ //
+// compile-command: "make install" //
+// End: //
Index: src/Common/DeclStats.hpp
===================================================================
--- src/Common/DeclStats.hpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
+++ src/Common/DeclStats.hpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -0,0 +1,29 @@
+//
+// Cforall Version 1.0.0 Copyright (C) 2015 University of Waterloo
+//
+// The contents of this file are covered under the licence agreement in the
+// file "LICENCE" distributed with Cforall.
+//
+// DeclStats.hpp -- Print statistics about a translation unit's declarations.
+//
+// Author           : Andrew Beach
+// Created On       : Fri Oct  1 14:20:00 2021
+// Last Modified By : Andrew Beach
+// Last Modified On : Fri Oct  1 14:28:00 2021
+// Update Count     : 0
+//
+
+#pragma once
+
+namespace ast {
+	class TranslationUnit;
+}
+
+/// Print statistics about a translation unit's declarations.
+void printDeclStats( ast::TranslationUnit &translationUnit );
+
+// Local Variables: //
+// tab-width: 4 //
+// mode: c++ //
+// compile-command: "make install" //
+// End: //
Index: src/Common/ResolvProtoDump.cpp
===================================================================
--- src/Common/ResolvProtoDump.cpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
+++ src/Common/ResolvProtoDump.cpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -0,0 +1,808 @@
+//
+// Cforall Version 1.0.0 Copyright (C) 2015 University of Waterloo
+//
+// The contents of this file are covered under the licence agreement in the
+// file "LICENCE" distributed with Cforall.
+//
+// ResolvProtoDump.cpp -- Prints AST as instances for resolv-proto.
+//
+// Author           : Andrew Beach
+// Created On       : Wed Oct  6 14:10:00 2021
+// Last Modified By : Andrew Beach
+// Last Modified On : Tue Oct 18 11:23:00 2021
+// Update Count     : 0
+//
+
+#include "ResolvProtoDump.hpp"
+
+#include <cctype>
+#include <iostream>
+#include <set>
+#include <unordered_set>
+
+#include "AST/Copy.hpp"
+#include "AST/Pass.hpp"
+#include "AST/TranslationUnit.hpp"
+#include "AST/Type.hpp"
+#include "CodeGen/OperatorTable.h"
+#include "Common/utility.h"
+
+namespace {
+
+/// Add a prefix to an existing name.
+std::string add_prefix( const std::string & prefix, const char * added ) {
+	if ( prefix.empty() ) {
+		return std::string("$") + added;
+	} else {
+		return prefix + added;
+	}
+}
+
+/// Shortens operator names.
+std::string op_name( const std::string & name ) {
+	if ( name.compare( 0, 10, "_operator_" ) == 0 ) {
+		return name.substr( 10 );
+	} else if ( name.compare( "_constructor" ) == 0
+			|| name.compare( "_destructor" ) == 0 ) {
+		return name.substr( 1 );
+	} else if ( name.compare( 0, 11, "__operator_" ) == 0 ) {
+		return name.substr( 11 );
+	} else {
+		return name;
+	}
+}
+
+/// Get the resolv-proto names for operators.
+std::string rp_name( const std::string & name, std::string && pre = "" ) {
+	// Check for anonymous names.
+	if ( name.empty() ) {
+		return add_prefix( pre, "anon" );
+	}
+
+	// Replace operator names.
+	const CodeGen::OperatorInfo * opInfo = CodeGen::operatorLookup( name );
+	if ( nullptr != opInfo ) {
+		return add_prefix( pre, "" ) + op_name( opInfo->outputName );
+	}
+
+	// Replace return value prefix.
+	if ( name.compare( 0, 8, "_retval_" ) == 0 ) {
+		return add_prefix( pre, "rtn_" ) + op_name( name.substr( 8 ) );
+	}
+
+	// Default to just name, with first character in lowercase.
+	if ( std::isupper( name[0] ) ) {
+		std::string copy = name;
+		copy[0] = std::tolower( copy[0] );
+		return pre + copy;
+	}
+	return pre + name;
+}
+
+/// Normalise a type instance name.
+std::string ti_name( const std::string & name ) {
+	// Replace built-in names
+	if ( name == "char16_t" || name == "char32_t" || name == "wchar_t" ) {
+		return std::string("#") + name;
+	}
+
+	// Strip leadng underscores.
+	unsigned i = 0;
+	while ( i < name.size() && name[i] == '_' ) { ++i; }
+	if ( i == name.size() ) {
+		return "Anon";
+	}
+
+	std::string stripped = name.substr( i );
+	// Strip trailing generic from autogen names ()
+	static char generic[] = "_generic_";
+	static size_t n_generic = sizeof(generic) - 1;
+	if ( stripped.size() >= n_generic
+			&& stripped.substr( stripped.size() - n_generic ) == generic ) {
+		stripped.resize( stripped.size() - n_generic );
+	}
+
+	// Uppercase first character.
+	stripped[0] = std::toupper( stripped[0] );
+	return stripped;
+}
+
+std::vector<ast::ptr<ast::Type>> to_types(
+		const std::vector<ast::ptr<ast::Expr>> & data ) {
+	std::vector<ast::ptr<ast::Type>> ret_val;
+	ret_val.reserve( data.size() );
+	for ( auto entry : data ) {
+		if ( auto * typeExpr = entry.as<ast::TypeExpr>() ) {
+			ret_val.emplace_back( typeExpr->type );
+		}
+	}
+	return ret_val;
+}
+
+enum class septype { separated, terminated, preceded };
+
+template<typename V>
+void build(
+		V & visitor,
+		const std::vector<ast::ptr<ast::Type>> & types,
+		std::stringstream & ss,
+		septype mode );
+
+template<typename V>
+void buildAsTuple(
+		V & visitor, const std::vector<ast::ptr<ast::Type>> & types,
+		std::stringstream & ss );
+
+struct TypePrinter : public ast::WithShortCircuiting, ast::WithVisitorRef<TypePrinter> {
+	/// Accumulator for the printed type.
+	std::stringstream ss;
+	/// Closed type variables.
+	const std::unordered_set<std::string> & closed;
+	/// Depth of nesting from root type.
+	unsigned depth;
+
+	TypePrinter( const std::unordered_set<std::string> & closed ) :
+		ss(), closed(closed), depth(0)
+	{}
+
+	std::string result() const {
+		return ss.str();
+	}
+
+	// Basic type represented as an integer type.
+	// TODO: Maybe hard-code conversion graph and make named type.
+	void previsit( const ast::BasicType * type ) {
+		ss << (int)type->kind;
+	}
+
+	// Pointers (except function pointers) are represented as generic type.
+	void previsit( const ast::PointerType * type ) {
+		if ( nullptr == type->base.as<ast::FunctionType>() ) {
+			ss << "#$ptr<";
+			++depth;
+		}
+	}
+	void postvisit( const ast::PointerType * type ) {
+		if ( nullptr == type->base.as<ast::FunctionType>() ) {
+			--depth;
+			ss << '>';
+		}
+	}
+
+	// Arrays repersented as pointers.
+	void previsit( const ast::ArrayType * type ) {
+		ss << "#$ptr<";
+		++depth;
+		type->base->accept( *visitor );
+		--depth;
+		ss << '>';
+		visit_children = false;
+	}
+
+	// Ignore top-level references as they are mostly transparent to resolution.
+	void previsit( const ast::ReferenceType * ) {
+		if ( !atTopLevel() ) { ss << "#$ref<"; }
+		++depth;
+	}
+	void postvisit( const ast::ReferenceType * ) {
+		--depth;
+		if ( !atTopLevel() ) { ss << '>'; }
+	}
+
+	void previsit( const ast::FunctionType * type ) {
+		ss << '[';
+		++depth;
+		build( *visitor, type->returns, ss, septype::preceded );
+		ss << " : ";
+		build( *visitor, type->params, ss, septype::terminated );
+		--depth;
+		ss << ']';
+		visit_children = false;
+	}
+
+private:
+	bool atTopLevel() const {
+		return 0 == depth;
+	}
+
+	void handleAggregate( const ast::BaseInstType * type ) {
+		ss << '#' << type->name;
+		if ( !type->params.empty() ) {
+			ss << '<';
+			++depth;
+			build( *visitor, to_types( type->params ), ss, septype::separated );
+			--depth;
+			ss << '>';
+		}
+		visit_children = false;
+	}
+public:
+
+	void previsit( const ast::StructInstType * type ) {
+		handleAggregate( type );
+	}
+
+	void previsit( const ast::UnionInstType * type ) {
+		handleAggregate( type );
+	}
+
+	void previsit( const ast::EnumInstType * ) {
+		ss << (int)ast::BasicType::SignedInt;
+	}
+
+	void previsit( const ast::TypeInstType * type ) {
+		// Print closed variables as named types.
+		if ( closed.count( type->name ) ) {
+			ss << '#' << type->name;
+		// Otherwise normalize the name.
+		} else {
+			ss << ti_name( type->name );
+		}
+	}
+
+	void previsit( const ast::TupleType * tupleType ) {
+		++depth;
+		buildAsTuple( *visitor, tupleType->types, ss );
+		--depth;
+		visit_children = false;
+	}
+
+	void previsit( const ast::VarArgsType * ) {
+		if ( atTopLevel() ) ss << "#$varargs";
+	}
+
+	// TODO: Support 0 and 1 with their type names and conversions.
+	void previsit( const ast::ZeroType * ) {
+		ss << (int)ast::BasicType::SignedInt;
+	}
+
+	void previsit( const ast::OneType * ) {
+		ss << (int)ast::BasicType::SignedInt;
+	}
+
+	void previsit( const ast::VoidType * ) {
+		if ( !atTopLevel() ) {
+			ss << "#void";
+		}
+	}
+};
+
+struct ExprPrinter : public ast::WithShortCircuiting, ast::WithVisitorRef<ExprPrinter> {
+	// TODO: Change interface to generate multiple expression canditates.
+	/// Accumulator of the printed expression.
+	std::stringstream ss;
+	/// Set of closed type variables.
+	const std::unordered_set<std::string> & closed;
+
+	ExprPrinter( const std::unordered_set<std::string> & closed ) :
+		ss(), closed( closed )
+	{}
+
+	std::string result() const {
+		return ss.str();
+	}
+
+	void previsit( const ast::NameExpr * expr ) {
+		ss << '&' << rp_name( expr->name );
+	}
+
+	/// Handle already resolved variables as type constants.
+	void previsit( const ast::VariableExpr * expr ) {
+		ss << ast::Pass<TypePrinter>::read( expr->var->get_type(), closed );
+		visit_children = false;
+	}
+
+	void previsit( const ast::UntypedExpr * expr ) {
+		// TODO: Handle name extraction more generally.
+		const ast::NameExpr * name = expr->func.as<ast::NameExpr>();
+
+		// TODO: Incorporate function type into resolv-proto.
+		if ( !name ) {
+			expr->func->accept( *visitor );
+			visit_children = false;
+			return;
+		}
+
+		ss << rp_name( name->name );
+		if ( expr->args.empty() ) {
+			ss << "()";
+		} else {
+			ss << "( ";
+			auto it = expr->args.begin();
+			while (true) {
+				(*it)->accept( *visitor );
+				if ( ++it == expr->args.end() ) break;
+				ss << ' ';
+			}
+			ss << " )";
+		}
+		visit_children = false;
+	}
+
+	void previsit( const ast::ApplicationExpr * expr ) {
+		ss << ast::Pass<TypePrinter>::read( static_cast<const ast::Expr *>( expr ), closed );
+		visit_children = false;
+	}
+
+	void previsit( const ast::AddressExpr * expr ) {
+		ss << "$addr( ";
+		expr->arg->accept( *visitor );
+		ss << " )";
+		visit_children = false;
+	}
+
+	void previsit( const ast::CastExpr * expr ) {
+		ss << ast::Pass<TypePrinter>::read( expr->result.get(), closed );
+		visit_children = false;
+	}
+
+	/// Member access handled as function from aggregate to member.
+	void previsit( const ast::UntypedMemberExpr * expr ) {
+		// TODO: Handle name extraction more generally.
+		const ast::NameExpr * name = expr->member.as<ast::NameExpr>();
+
+		// TODO: Incorporate function type into resolve-proto.
+		if ( !name ) {
+			expr->member->accept( *visitor );
+			visit_children = false;
+			return;
+		}
+
+		ss << rp_name( name->name, "$field_" );
+		ss << "( ";
+		expr->aggregate->accept( *visitor );
+		ss << " )";
+		visit_children = false;
+	}
+
+	/// Constant expression replaced by its type.
+	void previsit( const ast::ConstantExpr * expr ) {
+		ss << ast::Pass<TypePrinter>::read( static_cast<const ast::Expr *>( expr ), closed );
+		visit_children = false;
+	}
+
+	/// sizeof, alignof, & offsetof are replaced by constant type.
+	// TODO: Extra expression to resolve argument.
+	void previsit( const ast::SizeofExpr * ) {
+		ss << (int)ast::BasicType::LongUnsignedInt;
+		visit_children = false;
+	}
+	void previsit( const ast::AlignofExpr * ) {
+		ss << (int)ast::BasicType::LongUnsignedInt;
+		visit_children = false;
+	}
+	void previsit( const ast::UntypedOffsetofExpr * ) {
+		ss << (int)ast::BasicType::LongUnsignedInt;
+		visit_children = false;
+	}
+
+	/// Logical expressions represented as operators.
+	void previsit( const ast::LogicalExpr * expr ) {
+		ss << ( (ast::AndExpr == expr->isAnd) ? "$and( " : "$or( " );
+		expr->arg1->accept( *visitor );
+		ss << ' ';
+		expr->arg2->accept( *visitor );
+		ss << " )";
+		visit_children = false;
+	}
+
+	/// Conditional expression represented as an operator.
+	void previsit( const ast::ConditionalExpr * expr ) {
+		ss << "$if( ";
+		expr->arg1->accept( *visitor );
+		ss << ' ';
+		expr->arg2->accept( *visitor );
+		ss << ' ';
+		expr->arg3->accept( *visitor );
+		ss << " )";
+		visit_children = false;
+	}
+
+	/// Comma expression represented as on operator.
+	void previsit( const ast::CommaExpr * expr ) {
+		ss << "$seq( ";
+		expr->arg1->accept( *visitor );
+		ss << ' ';
+		expr->arg2->accept( *visitor );
+		ss << " )";
+		visit_children = false;
+	}
+
+	// TODO: Handle ignored ImplicitCopyCtorExpr and below.
+};
+
+template<typename V>
+void build(
+		V & visitor,
+		const std::vector<ast::ptr<ast::Type>> & types,
+		std::stringstream & ss,
+		septype mode ) {
+	if ( types.empty() ) return;
+
+	if ( septype::preceded == mode ) { ss << ' '; }
+
+	auto it = types.begin();
+	(*it)->accept( visitor );
+
+	while ( ++it != types.end() ) {
+		ss << ' ';
+		(*it)->accept( visitor );
+	}
+
+	if ( septype::terminated == mode ) { ss << ' '; }
+}
+
+std::string buildType(
+		const std::string & name, const ast::Type * type,
+		const std::unordered_set<std::string> & closed );
+
+/// Build a string representing a function type.
+std::string buildFunctionType(
+		const std::string & name, const ast::FunctionType * type,
+		const std::unordered_set<std::string> & closed ) {
+	ast::Pass<TypePrinter> printer( closed );
+	std::stringstream & ss = printer.core.ss;
+
+	build( printer, type->returns, ss, septype::terminated );
+	ss << rp_name( name );
+	build( printer, type->params, ss, septype::preceded );
+	for ( const auto & assertion : type->assertions ) {
+		auto var = assertion->var;
+		ss << " | " << buildType( var->name, var->get_type(), closed );
+	}
+	return ss.str();
+}
+
+/// Build a description of a type.
+std::string buildType(
+		const std::string & name, const ast::Type * type,
+		const std::unordered_set<std::string> & closed ) {
+	const ast::Type * norefs = type->stripReferences();
+
+	if ( const auto & ptrType = dynamic_cast<const ast::PointerType *>( norefs ) ) {
+		if ( const auto & funcType = ptrType->base.as<ast::FunctionType>() ) {
+			return buildFunctionType( name, funcType, closed );
+		}
+	} else if ( const auto & funcType = dynamic_cast<const ast::FunctionType *>( norefs ) ) {
+		return buildFunctionType( name, funcType, closed );
+	}
+
+	std::stringstream ss;
+	ss << ast::Pass<TypePrinter>::read( norefs, closed );
+	ss << " &" << rp_name( name );
+	return ss.str();
+}
+
+/// Builds description of a field access.
+std::string buildAggregateDecl( const std::string & name,
+		const ast::AggregateDecl * agg, const ast::Type * type,
+		const std::unordered_set<std::string> & closed ) {
+	const ast::Type * norefs = type->stripReferences();
+	std::stringstream ss;
+
+	ss << ast::Pass<TypePrinter>::read( norefs, closed ) << ' ';
+	ss << rp_name( name, "$field_" );
+	ss << " #" << agg->name;
+	if ( !agg->params.empty() ) {
+		ss << '<';
+		auto it = agg->params.begin();
+		while (true) {
+			ss << ti_name( (*it)->name );
+			if ( ++it == agg->params.end() ) break;
+			ss << ' ';
+		}
+		ss << '>';
+	}
+	return ss.str();
+}
+
+template<typename V>
+void buildAsTuple(
+		V & visitor, const std::vector<ast::ptr<ast::Type>> & types,
+		std::stringstream & ss ) {
+	switch ( types.size() ) {
+	case 0:
+		ss << "#void";
+		break;
+	case 1:
+		types.front()->accept( visitor );
+		break;
+	default:
+		ss << "#$" << types.size() << '<';
+		build( visitor, types, ss, septype::separated );
+		ss << '>';
+		break;
+	}
+}
+
+/// Adds a return
+std::string buildReturn(
+		const ast::Type * returnType,
+		const ast::Expr * expr,
+		const std::unordered_set<std::string> & closed ) {
+	std::stringstream ss;
+	ss << "$constructor( ";
+	ss << ast::Pass<TypePrinter>::read( returnType, closed );
+	ss << ' ';
+	ss << ast::Pass<ExprPrinter>::read( expr, closed );
+	ss << " )";
+	return ss.str();
+}
+
+void buildInitComponent(
+		std::stringstream & out, const ast::Init * init,
+		const std::unordered_set<std::string> & closed ) {
+	if ( const auto * s = dynamic_cast<const ast::SingleInit *>( init ) ) {
+		out << ast::Pass<ExprPrinter>::read( s->value.get(), closed ) << ' ';
+	} else if ( const auto * l = dynamic_cast<const ast::ListInit *>( init ) ) {
+		for ( const auto & it : l->initializers ) {
+			buildInitComponent( out, it, closed );
+		}
+	}
+}
+
+/// Build a representation of an initializer.
+std::string buildInitializer(
+		const std::string & name, const ast::Init * init,
+		const std::unordered_set<std::string> & closed ) {
+	std::stringstream ss;
+	ss << "$constructor( &";
+	ss << rp_name( name );
+	ss << ' ';
+	buildInitComponent( ss, init, closed );
+	ss << ')';
+	return ss.str();
+}
+
+/// Visitor for collecting and printing resolver prototype output.
+class ProtoDump : public ast::WithShortCircuiting, ast::WithVisitorRef<ProtoDump> {
+	/// Declarations in this scope.
+	// Set is used for ordering of printing.
+	std::set<std::string> decls;
+	/// Expressions in this scope.
+	std::vector<std::string> exprs;
+	/// Sub-scopes
+	std::vector<ProtoDump> subs;
+	/// Closed type variables
+	std::unordered_set<std::string> closed;
+	/// Outer lexical scope
+	const ProtoDump * parent;
+	/// Return type for this scope
+	ast::ptr<ast::Type> returnType;
+
+	/// Is the declaration in this scope or a parent scope?
+	bool hasDecl( const std::string & decl ) const {
+		return decls.count( decl ) || (parent && parent->hasDecl( decl ));
+	}
+
+	/// Adds a declaration to this scope if it is new.
+	void addDecl( const std::string & decl ) {
+		if ( !hasDecl( decl ) ) decls.insert( decl );
+	}
+
+	/// Adds a new expression to this scope.
+	void addExpr( const std::string & expr ) {
+		if ( !expr.empty() ) exprs.emplace_back( expr );
+	}
+
+	/// Adds a new scope as a child scope.
+	void addSub( ast::Pass<ProtoDump> && pass ) {
+		subs.emplace_back( std::move( pass.core ) );
+	}
+
+	/// Adds all named declaration in a list to the local scope.
+	void addAll( const std::vector<ast::ptr<ast::DeclWithType>> & decls ) {
+		for ( auto decl : decls ) {
+			// Skip anonymous decls.
+			if ( decl->name.empty() ) continue;
+
+			if ( const auto & obj = decl.as<ast::ObjectDecl>() ) {
+				previsit( obj );
+			}
+		}
+	}
+
+public:
+	ProtoDump() :
+		parent( nullptr ), returnType( nullptr )
+	{}
+
+	ProtoDump( const ProtoDump * parent, const ast::Type * returnType ) :
+		closed( parent->closed ), parent( parent ), returnType( returnType )
+	{}
+
+	ProtoDump( const ProtoDump & other ) :
+		decls( other.decls ), exprs( other.exprs ), subs( other.subs ),
+		closed( other.closed ), parent( other.parent ),
+		returnType( other.returnType )
+	{}
+
+	ProtoDump( ProtoDump && ) = default;
+
+	ProtoDump & operator=( const ProtoDump & ) = delete;
+	ProtoDump & operator=( ProtoDump && ) = delete;
+
+	void previsit( const ast::ObjectDecl * decl ) {
+		// Add variable as declaration.
+		addDecl( buildType( decl->name, decl->type, closed ) );
+
+		// Add initializer as expression if applicable.
+		if ( decl->init ) {
+			addExpr( buildInitializer( decl->name, decl->init, closed ) );
+		}
+	}
+
+	void previsit( const ast::FunctionDecl * decl ) {
+		visit_children = false;
+
+		// Skips declarations with ftype parameters.
+		for ( const auto & typeDecl : decl->type->forall ) {
+			if ( ast::TypeDecl::Ftype == typeDecl->kind ) {
+				return;
+			}
+		}
+
+		// Add function as declaration.
+		// NOTE: I'm not sure why the assertions are only present on the
+		// declaration and not the function type. Is that an error?
+		ast::FunctionType * new_type = ast::shallowCopy( decl->type.get() );
+		for ( const ast::ptr<ast::DeclWithType> & assertion : decl->assertions ) {
+			new_type->assertions.push_back(
+				new ast::VariableExpr( assertion->location , assertion )
+			);
+		}
+		addDecl( buildFunctionType( decl->name, new_type, closed ) );
+		delete new_type;
+
+		// Add information body if available.
+		if ( !decl->stmts ) return;
+		const std::vector<ast::ptr<ast::Type>> & returns =
+				decl->type->returns;
+
+		// Add the return statement.
+		ast::ptr<ast::Type> retType = nullptr;
+		if ( 1 == returns.size() ) {
+			if ( !returns.front().as<ast::VoidType>() ) {
+				retType = returns.front();
+			}
+		} else if ( 1 < returns.size() ) {
+			retType = new ast::TupleType( copy( returns ) );
+		}
+		ast::Pass<ProtoDump> body( this, retType.get() );
+
+		// Handle the forall clause (type parameters and assertions).
+		for ( const ast::ptr<ast::TypeDecl> & typeDecl : decl->type_params ) {
+			// Add set of "closed" types to body so that it can print them as NamedType.
+			body.core.closed.insert( typeDecl->name );
+
+			// Add assertions to local scope as declarations as well.
+			for ( const ast::DeclWithType * assertion : typeDecl->assertions ) {
+				assertion->accept( body );
+			}
+		}
+
+		// NOTE: Related to the last NOTE; this is where the assertions are now.
+		for ( const ast::ptr<ast::DeclWithType> & assertion : decl->assertions ) {
+			assertion->accept( body );
+		}
+
+		// Add named parameters and returns to local scope.
+		body.core.addAll( decl->returns );
+		body.core.addAll( decl->params );
+
+		// Add contents of the function to a new scope.
+		decl->stmts->accept( body );
+
+		// Store sub-scope
+		addSub( std::move( body ) );
+	}
+
+private:
+	void addAggregateFields( const ast::AggregateDecl * agg ) {
+		for ( const auto & member : agg->members ) {
+			if ( const ast::ObjectDecl * obj = member.as<ast::ObjectDecl>() ) {
+				addDecl( buildAggregateDecl( obj->name, agg, obj->type, closed ) );
+			}
+		}
+		visit_children = false;
+	}
+
+public:
+
+	void previsit( const ast::StructDecl * decl ) {
+		addAggregateFields( decl );
+	}
+
+	void previsit( const ast::UnionDecl * decl ) {
+		addAggregateFields( decl );
+	}
+
+	void previsit( const ast::EnumDecl * decl ) {
+		for ( const auto & member : decl->members ) {
+			if ( const auto * obj = member.as<ast::ObjectDecl>() ) {
+				previsit( obj );
+			}
+		}
+
+		visit_children = false;
+	}
+
+	void previsit( const ast::ReturnStmt * stmt ) {
+		// Do nothing for void-returning functions or statements returning nothing.
+		if ( !returnType || !stmt->expr ) return;
+
+		// Otherwise constuct the return type from the expression.
+		addExpr( buildReturn( returnType.get(), stmt->expr, closed ) );
+		visit_children = false;
+	}
+
+	void previsit( const ast::AsmStmt * ) {
+		// Skip asm statements.
+		visit_children = false;
+	}
+
+	void previsit( const ast::Expr * expr ) {
+		addExpr( ast::Pass<ExprPrinter>::read( expr, closed ) );
+		visit_children = false;
+	}
+
+private:
+	/// Print the pesudo-declarations not in any scope.
+	void printGlobal( std::ostream & out ) const {
+		// &? Address of operator.
+		out << "#$ptr<T> $addr T" << std::endl;
+		const int intId = (int)ast::BasicType::SignedInt;
+		// ?&&? ?||? ?: Logical operators.
+		out << intId << " $and " << intId << ' ' << intId << std::endl;
+		out << intId << " $or " << intId << ' ' << intId << std::endl;
+		out << "T $if " << intId << " T T" << std::endl;
+		// ?,? Sequencing.
+		out << "T $seq X T" << std::endl;
+	}
+
+	/// Print everything in this scope and its child scopes.
+	void printLocal( std::ostream & out, unsigned indent ) const {
+		const std::string tab( indent, '\t' );
+
+		// Print Declarations:
+		for ( const std::string & decl : decls ) {
+			out << tab << decl << std::endl;
+		}
+
+		// Print Divider:
+		out << '\n' << tab << "%%\n" << std::endl;
+
+		// Print Top-Level Expressions:
+		for ( const std::string & expr : exprs ) {
+			out << tab << expr << std::endl;
+		}
+
+		// Print Children Scopes:
+		++indent;
+		for ( const ProtoDump & sub : subs ) {
+			out << tab << '{' << std::endl;
+			sub.printLocal( out, indent );
+			out << tab << '}' << std::endl;
+		}
+	}
+public:
+	/// Start printing, the collected information.
+	void print( std::ostream & out ) const {
+		printGlobal( out );
+		printLocal( out, 0 );
+	}
+};
+
+} // namespace
+
+void dumpAsResolverProto( ast::TranslationUnit & transUnit ) {
+	ast::Pass<ProtoDump> dump;
+	accept_all( transUnit, dump );
+	dump.core.print( std::cout );
+}
+
+// Local Variables: //
+// tab-width: 4 //
+// mode: c++ //
+// compile-command: "make install" //
+// End: //
Index: src/Common/ResolvProtoDump.hpp
===================================================================
--- src/Common/ResolvProtoDump.hpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
+++ src/Common/ResolvProtoDump.hpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -0,0 +1,29 @@
+//
+// Cforall Version 1.0.0 Copyright (C) 2015 University of Waterloo
+//
+// The contents of this file are covered under the licence agreement in the
+// file "LICENCE" distributed with Cforall.
+//
+// ResolvProtoDump.hpp -- Prints AST as instances for resolv-proto.
+//
+// Author           : Andrew Beach
+// Created On       : Wed Oct  6 14:05:00 2021
+// Last Modified By : Andrew Beach
+// Last Modified On : Wed Oct  6 14:29:00 2021
+// Update Count     : 0
+//
+
+#pragma once
+
+namespace ast {
+	struct TranslationUnit;
+}
+
+/// Prints AST as instances for resolv-proto.
+void dumpAsResolverProto( ast::TranslationUnit & transUnit );
+
+// Local Variables: //
+// tab-width: 4 //
+// mode: c++ //
+// compile-command: "make install" //
+// End: //
Index: src/Common/module.mk
===================================================================
--- src/Common/module.mk	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ src/Common/module.mk	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -22,4 +22,6 @@
       Common/CompilerError.h \
       Common/Debug.h \
+      Common/DeclStats.hpp \
+      Common/DeclStats.cpp \
       Common/ErrorObjects.h \
       Common/Eval.cc \
@@ -33,4 +35,6 @@
       Common/PassVisitor.proto.h \
       Common/PersistentMap.h \
+      Common/ResolvProtoDump.hpp \
+      Common/ResolvProtoDump.cpp \
       Common/ScopedMap.h \
       Common/SemanticError.cc \
Index: src/Concurrency/Keywords.cc
===================================================================
--- src/Concurrency/Keywords.cc	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ src/Concurrency/Keywords.cc	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -979,4 +979,5 @@
 			// If this is the destructor for a monitor it must be mutex
 			if(isDtor) {
+				// This reflects MutexKeyword::validate, except does not produce an error.
 				Type* ty = decl->get_functionType()->get_parameters().front()->get_type();
 
Index: src/ControlStruct/ExceptTranslate.cc
===================================================================
--- src/ControlStruct/ExceptTranslate.cc	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ src/ControlStruct/ExceptTranslate.cc	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -5,5 +5,5 @@
 // file "LICENCE" distributed with Cforall.
 //
-// ExceptVisitor.cc --
+// ExceptTranslate.cc -- Conversion of exception control flow structures.
 //
 // Author           : Andrew Beach
Index: src/ControlStruct/ExceptTranslate.h
===================================================================
--- src/ControlStruct/ExceptTranslate.h	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ src/ControlStruct/ExceptTranslate.h	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -5,11 +5,11 @@
 // file "LICENCE" distributed with Cforall.
 //
-// ExceptTranslate.h --
+// ExceptTranslate.h -- Conversion of exception control flow structures.
 //
 // Author           : Andrew Beach
 // Created On       : Tus Jun 06 10:13:00 2017
 // Last Modified By : Andrew Beach
-// Last Modified On : Tus May 19 11:47:00 2020
-// Update Count     : 5
+// Last Modified On : Mon Nov  8 11:43:00 2020
+// Update Count     : 6
 //
 
@@ -19,7 +19,11 @@
 
 class Declaration;
+namespace ast {
+	class TranslationUnit;
+}
 
 namespace ControlStruct {
 	void translateThrows( std::list< Declaration *> & translationUnit );
+	void translateThrows( ast::TranslationUnit & transUnit );
 	/* Replaces all throw & throwResume statements with function calls.
 	 * These still need to be resolved, so call this before the reslover.
Index: src/ControlStruct/ExceptTranslateNew.cpp
===================================================================
--- src/ControlStruct/ExceptTranslateNew.cpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
+++ src/ControlStruct/ExceptTranslateNew.cpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -0,0 +1,142 @@
+//
+// Cforall Version 1.0.0 Copyright (C) 2015 University of Waterloo
+//
+// The contents of this file are covered under the licence agreement in the
+// file "LICENCE" distributed with Cforall.
+//
+// ExceptTranslateNew.cpp -- Conversion of exception control flow structures.
+//
+// Author           : Andrew Beach
+// Created On       : Mon Nov  8 11:53:00 2021
+// Last Modified By : Andrew Beach
+// Last Modified On : Mon Nov  8 16:50:00 2021
+// Update Count     : 0
+//
+
+#include "ExceptTranslate.h"
+
+#include "AST/Expr.hpp"
+#include "AST/Pass.hpp"
+#include "AST/Stmt.hpp"
+#include "AST/TranslationUnit.hpp"
+
+namespace ControlStruct {
+
+namespace {
+
+class TranslateThrowsCore : public ast::WithGuards {
+	const ast::ObjectDecl * terminateHandlerExcept;
+	enum Context { NoHandler, TerHandler, ResHandler } currentContext;
+
+	const ast::Stmt * createEitherThrow(
+		const ast::ThrowStmt * throwStmt, const char * funcName );
+	const ast::Stmt * createTerminateRethrow( const ast::ThrowStmt * );
+
+public:
+	TranslateThrowsCore() :
+		terminateHandlerExcept( nullptr ), currentContext( NoHandler )
+	{}
+
+	void previsit( const ast::CatchStmt * stmt );
+	const ast::Stmt * postvisit( const ast::ThrowStmt * stmt );
+};
+
+const ast::Stmt * TranslateThrowsCore::createEitherThrow(
+		const ast::ThrowStmt * throwStmt, const char * funcName ) {
+	// `throwFunc`( `throwStmt->name` );
+	ast::UntypedExpr * call = new ast::UntypedExpr( throwStmt->location,
+		new ast::NameExpr( throwStmt->location, funcName )
+	);
+	call->args.push_back( throwStmt->expr );
+	return new ast::ExprStmt( throwStmt->location, call );
+}
+
+ast::VariableExpr * varOf( const ast::DeclWithType * decl ) {
+	return new ast::VariableExpr( decl->location, decl );
+}
+
+const ast::Stmt * TranslateThrowsCore::createTerminateRethrow(
+		const ast::ThrowStmt * stmt ) {
+	// { `terminate_handler_except` = 0p; __rethrow_terminate(); }
+	assert( nullptr == stmt->expr );
+	assert( terminateHandlerExcept );
+
+	ast::CompoundStmt * result = new ast::CompoundStmt(
+		stmt->location, {}, std::vector<ast::Label>( stmt->labels ) );
+	result->push_back( new ast::ExprStmt( stmt->location,
+		ast::UntypedExpr::createAssign(
+			stmt->location,
+			varOf( terminateHandlerExcept ),
+			ast::ConstantExpr::null(
+				stmt->location,
+				terminateHandlerExcept->type
+			)
+		)
+	) );
+	result->push_back( new ast::ExprStmt( stmt->location, new ast::UntypedExpr(
+		stmt->location,
+		new ast::NameExpr( stmt->location, "__cfaehm_rethrow_terminate" )
+	) ) );
+	return result;
+}
+
+void TranslateThrowsCore::previsit( const ast::CatchStmt * stmt ) {
+	// Validate the statement's form.
+	const ast::ObjectDecl * decl = stmt->decl.as<ast::ObjectDecl>();
+	// Also checking the type would be nice.
+	if ( !decl || !decl->type.as<ast::PointerType>() ) {
+		std::string kind = (ast::Terminate == stmt->kind) ? "catch" : "catchResume";
+		SemanticError( stmt->location, kind + " must have pointer to an exception type" );
+	}
+
+	// Track the handler context.
+	if ( ast::Terminate == stmt->kind ) {
+		GuardValue( currentContext ) = TerHandler;
+		GuardValue( terminateHandlerExcept ) = decl;
+	} else {
+		GuardValue( currentContext ) = ResHandler;
+	}
+}
+
+const ast::Stmt * TranslateThrowsCore::postvisit(
+		const ast::ThrowStmt * stmt ) {
+	// Ignoring ThrowStmt::target for now.
+	// Handle Termination (Raise, Reraise, Error):
+	if ( ast::Terminate == stmt->kind ) {
+		if ( stmt->expr ) {
+			return createEitherThrow( stmt, "$throw" );
+		} else if ( TerHandler == currentContext ) {
+			return createTerminateRethrow( stmt );
+		} else {
+			abort( "Invalid throw in %s at %i\n",
+				stmt->location.filename.c_str(),
+				stmt->location.first_line);
+		}
+	// Handle Resumption (Raise, Reraise, Error):
+	} else {
+		if ( stmt->expr ) {
+			return createEitherThrow( stmt, "$throwResume" );
+		} else if ( ResHandler == currentContext ) {
+			// This has to be handled later.
+			return stmt;
+		} else {
+			abort( "Invalid throwResume in %s at %i\n",
+				stmt->location.filename.c_str(),
+				stmt->location.first_line);
+		}
+	}
+}
+
+} // namespace
+
+void translateThrows( ast::TranslationUnit & transUnit ) {
+	ast::Pass<TranslateThrowsCore>::run( transUnit );
+}
+
+} // namespace ControlStruct
+
+// Local Variables: //
+// tab-width: 4 //
+// mode: c++ //
+// compile-command: "make install" //
+// End: //
Index: src/ControlStruct/FixLabels.cpp
===================================================================
--- src/ControlStruct/FixLabels.cpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
+++ src/ControlStruct/FixLabels.cpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -0,0 +1,118 @@
+//
+// Cforall Version 1.0.0 Copyright (C) 2015 University of Waterloo
+//
+// The contents of this file are covered under the licence agreement in the
+// file "LICENCE" distributed with Cforall.
+//
+// FixLabels.cpp -- Normalizes labels and handles multi-level exit labels.
+//
+// Author           : Andrew Beach
+// Created On       : Mon Nov  1 09:39:00 2021
+// Last Modified By : Andrew Beach
+// Last Modified On : Mon Nov  8 10:53:00 2021
+// Update Count     : 3
+//
+
+#include "FixLabels.hpp"
+
+#include "AST/Label.hpp"
+#include "AST/Pass.hpp"
+#include "AST/Stmt.hpp"
+#include "ControlStruct/MultiLevelExit.hpp"
+
+namespace ControlStruct {
+
+namespace {
+
+class FixLabelsCore final : public ast::WithGuards {
+	LabelToStmt labelTable;
+public:
+	FixLabelsCore() : labelTable() {}
+
+	void previsit( const ast::FunctionDecl * );
+	const ast::FunctionDecl * postvisit( const ast::FunctionDecl * );
+	void previsit( const ast::Stmt * );
+	void previsit( const ast::BranchStmt * );
+	void previsit( const ast::LabelAddressExpr * );
+
+	void setLabelsDef( const std::vector<ast::Label> &, const ast::Stmt * );
+	void setLabelsUsage( const ast::Label & );
+};
+
+void FixLabelsCore::previsit( const ast::FunctionDecl * ) {
+	GuardValue( labelTable ).clear();
+}
+
+const ast::FunctionDecl * FixLabelsCore::postvisit(
+		const ast::FunctionDecl * decl ) {
+	if ( nullptr == decl->stmts ) return decl;
+	for ( auto kvp : labelTable ) {
+		if ( nullptr == kvp.second ) {
+			SemanticError( kvp.first.location,
+				"Use of undefined label: " + kvp.first.name );
+		}
+	}
+	return ast::mutate_field( decl, &ast::FunctionDecl::stmts,
+		multiLevelExitUpdate( decl->stmts.get(), labelTable ) );
+}
+
+void FixLabelsCore::previsit( const ast::Stmt * stmt ) {
+	if ( !stmt->labels.empty() ) {
+		setLabelsDef( stmt->labels, stmt );
+	}
+}
+
+void FixLabelsCore::previsit( const ast::BranchStmt * stmt ) {
+	if ( !stmt->labels.empty() ) {
+		setLabelsDef( stmt->labels, stmt );
+	}
+	if ( !stmt->target.empty() ) {
+		setLabelsUsage( stmt->target );
+	}
+}
+
+void FixLabelsCore::previsit( const ast::LabelAddressExpr * expr ) {
+	assert( !expr->arg.empty() );
+	setLabelsUsage( expr->arg );
+}
+
+void FixLabelsCore::setLabelsDef(
+		const std::vector<ast::Label> & labels, const ast::Stmt * stmt ) {
+	assert( !labels.empty() );
+	assert( stmt );
+
+	for ( auto label : labels ) {
+		if ( labelTable.find( label ) == labelTable.end() ) {
+			// Make sure to only create the entry once.
+			labelTable[ label ] = stmt;
+		} else if ( nullptr != labelTable[ label ] ) {
+			// Duplicate definition, this is an error.
+			SemanticError( label.location,
+				"Duplicate definition of label: " + label.name );
+		} else {
+			// Perviously used, but not defined until now.
+			labelTable[ label ] = stmt;
+		}
+	}
+}
+
+// Label was used, if it is new add it to the table.
+void FixLabelsCore::setLabelsUsage( const ast::Label & label ) {
+	if ( labelTable.find( label ) == labelTable.end() ) {
+		labelTable[ label ] = nullptr;
+	}
+}
+
+} // namespace
+
+void fixLabels( ast::TranslationUnit & translationUnit ) {
+	ast::Pass<FixLabelsCore>::run( translationUnit );
+}
+
+} // namespace ControlStruct
+
+// Local Variables: //
+// tab-width: 4 //
+// mode: c++ //
+// compile-command: "make install" //
+// End: //
Index: src/ControlStruct/FixLabels.hpp
===================================================================
--- src/ControlStruct/FixLabels.hpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
+++ src/ControlStruct/FixLabels.hpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -0,0 +1,33 @@
+//
+// Cforall Version 1.0.0 Copyright (C) 2015 University of Waterloo
+//
+// The contents of this file are covered under the licence agreement in the
+// file "LICENCE" distributed with Cforall.
+//
+// FixLabels.hpp -- Normalizes labels and handles multi-level exit labels.
+//
+// Author           : Andrew Beach
+// Created On       : Mon Nov  1 09:36:00 2021
+// Last Modified By : Andrew Beach
+// Last Modified On : Mon Nov  1 09:40:00 2021
+// Update Count     : 0
+//
+
+#pragma once
+
+namespace ast {
+	class TranslationUnit;
+}
+
+namespace ControlStruct {
+
+/// normalizes label definitions and generates multi-level exit labels
+void fixLabels( ast::TranslationUnit & translationUnit );
+
+}
+
+// Local Variables: //
+// tab-width: 4 //
+// mode: c++ //
+// compile-command: "make install" //
+// End: //
Index: src/ControlStruct/LabelGenerator.cc
===================================================================
--- src/ControlStruct/LabelGenerator.cc	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ src/ControlStruct/LabelGenerator.cc	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -9,7 +9,7 @@
 // Author           : Rodolfo G. Esteves
 // Created On       : Mon May 18 07:44:20 2015
-// Last Modified By : Peter A. Buhr
-// Last Modified On : Mon Mar 11 22:23:20 2019
-// Update Count     : 15
+// Last Modified By : Andrew Beach
+// Last Modified On : Mon Nov  8 10:18:00 2021
+// Update Count     : 17
 //
 
@@ -19,4 +19,8 @@
 
 #include "LabelGenerator.h"
+
+#include "AST/Attribute.hpp"
+#include "AST/Label.hpp"
+#include "AST/Stmt.hpp"
 #include "SynTree/Attribute.h"  // for Attribute
 #include "SynTree/Label.h"      // for Label, operator<<
@@ -24,5 +28,7 @@
 
 namespace ControlStruct {
-	LabelGenerator * LabelGenerator::labelGenerator = 0;
+
+int LabelGenerator::current = 0;
+LabelGenerator * LabelGenerator::labelGenerator = nullptr;
 
 	LabelGenerator * LabelGenerator::getGenerator() {
@@ -43,4 +49,19 @@
 		return l;
 	}
+
+ast::Label LabelGenerator::newLabel(
+		const std::string & suffix, const ast::Stmt * stmt ) {
+	assert( stmt );
+
+	std::ostringstream os;
+	os << "__L" << current++ << "__" << suffix;
+	if ( stmt && !stmt->labels.empty() ) {
+		os << "_" << stmt->labels.front() << "__";
+	}
+	ast::Label ret_label( stmt->location, os.str() );
+	ret_label.attributes.push_back( new ast::Attribute( "unused" ) );
+	return ret_label;
+}
+
 } // namespace ControlStruct
 
Index: src/ControlStruct/LabelGenerator.h
===================================================================
--- src/ControlStruct/LabelGenerator.h	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ src/ControlStruct/LabelGenerator.h	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -9,7 +9,7 @@
 // Author           : Rodolfo G. Esteves
 // Created On       : Mon May 18 07:44:20 2015
-// Last Modified By : Peter A. Buhr
-// Last Modified On : Sat Jul 22 09:20:14 2017
-// Update Count     : 6
+// Last Modified By : Andrew Beach
+// Last Modified On : Mon Nov  8 10:16:00 2021
+// Update Count     : 8
 //
 
@@ -21,18 +21,24 @@
 
 class Statement;
+namespace ast {
+	class Stmt;
+	class Label;
+}
 
 namespace ControlStruct {
-	class LabelGenerator {
-	  public:
-		static LabelGenerator *getGenerator();
-		Label newLabel(std::string suffix, Statement * stmt = nullptr);
-		void reset() { current = 0; }
-		void rewind() { current--; }
-	  protected:
-		LabelGenerator(): current(0) {}
-	  private:
-		int current;
-		static LabelGenerator *labelGenerator;
-	};
+
+class LabelGenerator {
+	static int current;
+	static LabelGenerator *labelGenerator;
+protected:
+	LabelGenerator() {}
+public:
+	static LabelGenerator *getGenerator();
+	static Label newLabel(std::string suffix, Statement * stmt = nullptr);
+	static ast::Label newLabel( const std::string&, const ast::Stmt * );
+	static void reset() { current = 0; }
+	static void rewind() { current--; }
+};
+
 } // namespace ControlStruct
 
Index: src/ControlStruct/MultiLevelExit.cpp
===================================================================
--- src/ControlStruct/MultiLevelExit.cpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
+++ src/ControlStruct/MultiLevelExit.cpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -0,0 +1,620 @@
+//
+// Cforall Version 1.0.0 Copyright (C) 2015 University of Waterloo
+//
+// The contents of this file are covered under the licence agreement in the
+// file "LICENCE" distributed with Cforall.
+//
+// MultiLevelExit.cpp -- Replaces CFA's local control flow with C's versions.
+//
+// Author           : Andrew Beach
+// Created On       : Mon Nov  1 13:48:00 2021
+// Last Modified By : Andrew Beach
+// Last Modified On : Mon Nov  8 10:56:00 2021
+// Update Count     : 2
+//
+
+#include "MultiLevelExit.hpp"
+
+#include "AST/Pass.hpp"
+#include "AST/Stmt.hpp"
+#include "ControlStruct/LabelGenerator.h"
+
+#include <set>
+
+namespace ControlStruct {
+
+namespace {
+
+class Entry {
+public:
+	const ast::Stmt * stmt;
+private:
+	// Organized like a manual ADT. Avoids creating a bunch of dead data.
+	struct Target {
+		ast::Label label;
+		bool used = false;
+		Target( const ast::Label & label ) : label( label ) {}
+		Target() : label( CodeLocation() ) {}
+	};
+	Target firstTarget;
+	Target secondTarget;
+
+	enum Kind {
+		ForStmt, WhileStmt, CompoundStmt, IfStmt, CaseStmt, SwitchStmt, TryStmt
+	} kind;
+
+	bool fallDefaultValid = true;
+
+	static ast::Label & useTarget( Target & target ) {
+		target.used = true;
+		return target.label;
+	}
+
+public:
+	Entry( const ast::ForStmt * stmt, ast::Label breakExit, ast::Label contExit ) :
+		stmt( stmt ), firstTarget( breakExit ), secondTarget( contExit ), kind( ForStmt ) {}
+	Entry( const ast::WhileStmt * stmt, ast::Label breakExit, ast::Label contExit ) :
+		stmt( stmt ), firstTarget( breakExit ), secondTarget( contExit ), kind( WhileStmt ) {}
+	Entry( const ast::CompoundStmt *stmt, ast::Label breakExit ) :
+		stmt( stmt ), firstTarget( breakExit ), secondTarget(), kind( CompoundStmt ) {}
+	Entry( const ast::IfStmt *stmt, ast::Label breakExit ) :
+		stmt( stmt ), firstTarget( breakExit ), secondTarget(), kind( IfStmt ) {}
+	Entry( const ast::CaseStmt *stmt, ast::Label fallExit ) :
+		stmt( stmt ), firstTarget( fallExit ), secondTarget(), kind( CaseStmt ) {}
+	Entry( const ast::SwitchStmt *stmt, ast::Label breakExit, ast::Label fallDefaultExit ) :
+		stmt( stmt ), firstTarget( breakExit ), secondTarget( fallDefaultExit ), kind( SwitchStmt ) {}
+	Entry( const ast::TryStmt *stmt, ast::Label breakExit ) :
+		stmt( stmt ), firstTarget( breakExit ), secondTarget(), kind( TryStmt ) {}
+
+	bool isContTarget() const { return kind <= WhileStmt; }
+	bool isBreakTarget() const { return CaseStmt != kind; }
+	bool isFallTarget() const { return CaseStmt == kind; }
+	bool isFallDefaultTarget() const { return SwitchStmt == kind; }
+
+	ast::Label useContExit() { assert( kind <= WhileStmt ); return useTarget(secondTarget); }
+	ast::Label useBreakExit() { assert( CaseStmt != kind ); return useTarget(firstTarget); }
+	ast::Label useFallExit() { assert( CaseStmt == kind );  return useTarget(firstTarget); }
+	ast::Label useFallDefaultExit() { assert( SwitchStmt == kind ); return useTarget(secondTarget); }
+
+	bool isContUsed() const { assert( kind <= WhileStmt ); return secondTarget.used; }
+	bool isBreakUsed() const { assert( CaseStmt != kind ); return firstTarget.used; }
+	bool isFallUsed() const { assert( CaseStmt == kind ); return firstTarget.used; }
+	bool isFallDefaultUsed() const { assert( SwitchStmt == kind ); return secondTarget.used; }
+	void seenDefault() { fallDefaultValid = false; }
+	bool isFallDefaultValid() const { return fallDefaultValid; }
+};
+
+// Helper predicates used in std::find_if calls (it doesn't take methods):
+bool isBreakTarget( const Entry & entry ) {
+	return entry.isBreakTarget();
+}
+
+bool isContinueTarget( const Entry & entry ) {
+	return entry.isContTarget();
+}
+
+bool isFallthroughTarget( const Entry & entry ) {
+	return entry.isFallTarget();
+}
+
+bool isFallthroughDefaultTarget( const Entry & entry ) {
+	return entry.isFallDefaultTarget();
+}
+
+struct MultiLevelExitCore final :
+		public ast::WithVisitorRef<MultiLevelExitCore>,
+		public ast::WithShortCircuiting, public ast::WithGuards {
+	MultiLevelExitCore( const LabelToStmt & lt );
+
+	void previsit( const ast::FunctionDecl * );
+
+	const ast::CompoundStmt * previsit( const ast::CompoundStmt * );
+	const ast::BranchStmt * postvisit( const ast::BranchStmt * );
+	void previsit( const ast::WhileStmt * );
+	const ast::WhileStmt * postvisit( const ast::WhileStmt * );
+	void previsit( const ast::ForStmt * );
+	const ast::ForStmt * postvisit( const ast::ForStmt * );
+	const ast::CaseStmt * previsit( const ast::CaseStmt * );
+	void previsit( const ast::IfStmt * );
+	const ast::IfStmt * postvisit( const ast::IfStmt * );
+	void previsit( const ast::SwitchStmt * );
+	const ast::SwitchStmt * postvisit( const ast::SwitchStmt * );
+	void previsit( const ast::ReturnStmt * );
+	void previsit( const ast::TryStmt * );
+	void postvisit( const ast::TryStmt * );
+	void previsit( const ast::FinallyStmt * );
+
+	const ast::Stmt * mutateLoop( const ast::Stmt * body, Entry& );
+
+	const LabelToStmt & target_table;
+	std::set<ast::Label> fallthrough_labels;
+	std::vector<Entry> enclosing_control_structures;
+	ast::Label break_label;
+	bool inFinally;
+
+	template<typename LoopNode>
+	void prehandleLoopStmt( const LoopNode * loopStmt );
+	template<typename LoopNode>
+	const LoopNode * posthandleLoopStmt( const LoopNode * loopStmt );
+
+	std::list<ast::ptr<ast::Stmt>> fixBlock(
+		const std::list<ast::ptr<ast::Stmt>> & kids, bool caseClause );
+
+	template<typename UnaryPredicate>
+	auto findEnclosingControlStructure( UnaryPredicate pred ) {
+		return std::find_if( enclosing_control_structures.rbegin(),
+			enclosing_control_structures.rend(), pred );
+	}
+};
+
+ast::NullStmt * labelledNullStmt(
+		const CodeLocation & cl, const ast::Label & label ) {
+	return new ast::NullStmt( cl, std::vector<ast::Label>{ label } );
+}
+
+MultiLevelExitCore::MultiLevelExitCore( const LabelToStmt & lt ) :
+	target_table( lt ), break_label( CodeLocation(), "" ),
+	inFinally( false )
+{}
+
+void MultiLevelExitCore::previsit( const ast::FunctionDecl * ) {
+	visit_children = false;
+}
+
+const ast::CompoundStmt * MultiLevelExitCore::previsit(
+		const ast::CompoundStmt * stmt ) {
+	visit_children = false;
+	bool isLabeled = !stmt->labels.empty();
+	if ( isLabeled ) {
+		ast::Label breakLabel = LabelGenerator::newLabel( "blockBreak", stmt );
+		enclosing_control_structures.emplace_back( stmt, breakLabel );
+		GuardAction( [this]() { enclosing_control_structures.pop_back(); } );
+	}
+
+	auto mutStmt = ast::mutate( stmt );
+	// A child statement may set the break label.
+	mutStmt->kids = std::move( fixBlock( stmt->kids, false ) );
+
+	if ( isLabeled ) {
+		assert( !enclosing_control_structures.empty() );
+		Entry & entry = enclosing_control_structures.back();
+		if ( !entry.useBreakExit().empty() ) {
+			break_label = entry.useBreakExit();
+		}
+	}
+	return mutStmt;
+}
+
+size_t getUnusedIndex(
+		const ast::Stmt * stmt, const ast::Label & originalTarget ) {
+	const size_t size = stmt->labels.size();
+
+	// If the label is empty, we can skip adding the unused attribute:
+	if ( originalTarget.empty() ) return size;
+
+	// Search for a label that matches the originalTarget.
+	for ( size_t i = 0 ; i < size ; ++i ) {
+		const ast::Label & label = stmt->labels[i];
+		if ( label == originalTarget ) {
+			for ( const ast::Attribute * attr : label.attributes ) {
+				if ( attr->name == "unused" ) return size;
+			}
+			return i;
+		}
+	}
+	assertf( false, "Could not find label '%s' on statement %s",
+		originalTarget.name.c_str(), toString( stmt ).c_str() );
+}
+
+const ast::Stmt * addUnused(
+		const ast::Stmt * stmt, const ast::Label & originalTarget ) {
+	size_t i = getUnusedIndex( stmt, originalTarget );
+	if ( i == stmt->labels.size() ) {
+		return stmt;
+	}
+	ast::Stmt * mutStmt = ast::mutate( stmt );
+	mutStmt->labels[i].attributes.push_back( new ast::Attribute( "unused" ) );
+	return mutStmt;
+}
+
+const ast::BranchStmt * MultiLevelExitCore::postvisit( const ast::BranchStmt * stmt ) {
+	std::vector<Entry>::reverse_iterator targetEntry =
+		enclosing_control_structures.rend();
+	switch ( stmt->kind ) {
+	case ast::BranchStmt::Goto:
+		return stmt;
+	case ast::BranchStmt::Continue:
+	case ast::BranchStmt::Break: {
+		bool isContinue = stmt->kind == ast::BranchStmt::Continue;
+		// Handle unlabeled break and continue.
+		if ( stmt->target.empty() ) {
+			if ( isContinue ) {
+				targetEntry = findEnclosingControlStructure( isContinueTarget );
+			} else {
+				if ( enclosing_control_structures.empty() ) {
+					SemanticError( stmt->location,
+						"'break' outside a loop, 'switch', or labelled block" );
+				}
+				targetEntry = findEnclosingControlStructure( isBreakTarget );
+			}
+		// Handle labeled break and continue.
+		} else {
+			// Lookup label in table to find attached control structure.
+			targetEntry = findEnclosingControlStructure(
+				[ targetStmt = target_table.at(stmt->target) ](auto entry){
+					return entry.stmt == targetStmt;
+				} );
+		}
+		// Ensure that selected target is valid.
+		if ( targetEntry == enclosing_control_structures.rend() || ( isContinue && !isContinueTarget( *targetEntry ) ) ) {
+			SemanticError(
+				stmt->location,
+				toString( (isContinue ? "'continue'" : "'break'"),
+					" target must be an enclosing ",
+					(isContinue ? "loop: " : "control structure: "),
+					stmt->originalTarget ) );
+		}
+		break;
+	}
+	case ast::BranchStmt::FallThrough: {
+		targetEntry = findEnclosingControlStructure( isFallthroughTarget );
+		// Check that target is valid.
+		if ( targetEntry == enclosing_control_structures.rend() ) {
+			SemanticError( stmt->location, "'fallthrough' must be enclosed in a 'switch' or 'choose'" );
+		}
+		if ( !stmt->target.empty() ) {
+			// Labelled fallthrough: target must be a valid fallthough label.
+			if ( !fallthrough_labels.count( stmt->target ) ) {
+				SemanticError( stmt->location, toString( "'fallthrough' target must be a later case statement: ", stmt->originalTarget ) );
+			}
+			return new ast::BranchStmt(
+				stmt->location, ast::BranchStmt::Goto, stmt->originalTarget );
+		}
+		break;
+	}
+	case ast::BranchStmt::FallThroughDefault: {
+		targetEntry = findEnclosingControlStructure( isFallthroughDefaultTarget );
+
+		// Check that this is in a switch or choose statement.
+		if ( targetEntry == enclosing_control_structures.rend() ) {
+			SemanticError( stmt->location, "'fallthrough' must be enclosed in a 'switch' or 'choose'" );
+		}
+
+		// Check that the switch or choose has a default clause.
+		auto switchStmt = strict_dynamic_cast< const ast::SwitchStmt * >(
+			targetEntry->stmt );
+		bool foundDefault = false;
+		for ( auto subStmt : switchStmt->stmts ) {
+			const ast::CaseStmt * caseStmt = subStmt.strict_as<ast::CaseStmt>();
+			if ( caseStmt->isDefault() ) {
+				foundDefault = true;
+				break;
+			}
+		}
+		if ( !foundDefault ) {
+			SemanticError( stmt->location, "'fallthrough default' must be enclosed in a 'switch' or 'choose' control structure with a 'default' clause" );
+		}
+		break;
+	}
+	default:
+		assert( false );
+	}
+
+	// Branch error checks: get the appropriate label name:
+	// (This label will always be replaced.)
+	ast::Label exitLabel( CodeLocation(), "" );
+	switch ( stmt->kind ) {
+	case ast::BranchStmt::Break:
+		assert( !targetEntry->useBreakExit().empty() );
+		exitLabel = targetEntry->useBreakExit();
+		break;
+	case ast::BranchStmt::Continue:
+		assert( !targetEntry->useContExit().empty() );
+		exitLabel = targetEntry->useContExit();
+		break;
+	case ast::BranchStmt::FallThrough:
+		assert( !targetEntry->useFallExit().empty() );
+		exitLabel = targetEntry->useFallExit();
+		break;
+	case ast::BranchStmt::FallThroughDefault:
+		assert( !targetEntry->useFallDefaultExit().empty() );
+		exitLabel = targetEntry->useFallDefaultExit();
+		// Check that fallthrough default comes before the default clause.
+		if ( !targetEntry->isFallDefaultValid() ) {
+			SemanticError( stmt->location,
+				"'fallthrough default' must precede the 'default' clause" );
+		}
+		break;
+	default:
+		assert(0);
+	}
+
+	// Add unused attribute to silence warnings.
+	targetEntry->stmt = addUnused( targetEntry->stmt, stmt->originalTarget );
+
+	// Replace this with a goto to make later passes more uniform.
+	return new ast::BranchStmt( stmt->location, ast::BranchStmt::Goto, exitLabel );
+}
+
+void MultiLevelExitCore::previsit( const ast::WhileStmt * stmt ) {
+	return prehandleLoopStmt( stmt );
+}
+
+const ast::WhileStmt * MultiLevelExitCore::postvisit( const ast::WhileStmt * stmt ) {
+	return posthandleLoopStmt( stmt );
+}
+
+void MultiLevelExitCore::previsit( const ast::ForStmt * stmt ) {
+	return prehandleLoopStmt( stmt );
+}
+
+const ast::ForStmt * MultiLevelExitCore::postvisit( const ast::ForStmt * stmt ) {
+	return posthandleLoopStmt( stmt );
+}
+
+// Mimic what the built-in push_front would do anyways. It is O(n).
+void push_front(
+		std::vector<ast::ptr<ast::Stmt>> & vec, const ast::Stmt * element ) {
+	vec.emplace_back( nullptr );
+	for ( size_t i = vec.size() - 1 ; 0 < i ; --i ) {
+		vec[ i ] = std::move( vec[ i - 1 ] );
+	}
+	vec[ 0 ] = element;
+}
+
+const ast::CaseStmt * MultiLevelExitCore::previsit( const ast::CaseStmt * stmt ) {
+	visit_children = false;
+
+	// If it is the default, mark the default as seen.
+	if ( stmt->isDefault() ) {
+		assert( !enclosing_control_structures.empty() );
+		enclosing_control_structures.back().seenDefault();
+	}
+
+	// The cond may not exist, but if it does update it now.
+	visitor->maybe_accept( stmt, &ast::CaseStmt::cond );
+
+	// Just save the mutated node for simplicity.
+	ast::CaseStmt * mutStmt = ast::mutate( stmt );
+
+	ast::Label fallLabel = LabelGenerator::newLabel( "fallThrough", stmt );
+	if ( !mutStmt->stmts.empty() ) {
+		// Ensure that the stack isn't corrupted by exceptions in fixBlock.
+		auto guard = makeFuncGuard(
+			[&](){ enclosing_control_structures.emplace_back( mutStmt, fallLabel ); },
+			[this](){ enclosing_control_structures.pop_back(); }
+		);
+
+		// These should already be in a block.
+		auto block = ast::mutate( mutStmt->stmts.front().strict_as<ast::CompoundStmt>() );
+		block->kids = fixBlock( block->kids, true );
+
+		// Add fallthrough label if necessary.
+		assert( !enclosing_control_structures.empty() );
+		Entry & entry = enclosing_control_structures.back();
+		if ( entry.isFallUsed() ) {
+			mutStmt->stmts.push_back(
+				labelledNullStmt( mutStmt->location, entry.useFallExit() ) );
+		}
+	}
+	assert( !enclosing_control_structures.empty() );
+	Entry & entry = enclosing_control_structures.back();
+	assertf( dynamic_cast< const ast::SwitchStmt * >( entry.stmt ),
+		"Control structure enclosing a case clause must be a switch, but is: %s",
+		toString( entry.stmt ).c_str() );
+	if ( mutStmt->isDefault() ) {
+		if ( entry.isFallDefaultUsed() ) {
+			// Add fallthrough default label if necessary.
+			push_front( mutStmt->stmts, labelledNullStmt(
+				stmt->location, entry.useFallDefaultExit()
+			) );
+		}
+	}
+	return mutStmt;
+}
+
+void MultiLevelExitCore::previsit( const ast::IfStmt * stmt ) {
+	bool labeledBlock = !stmt->labels.empty();
+	if ( labeledBlock ) {
+		ast::Label breakLabel = LabelGenerator::newLabel( "blockBreak", stmt );
+		enclosing_control_structures.emplace_back( stmt, breakLabel );
+		GuardAction( [this](){ enclosing_control_structures.pop_back(); } );
+	}
+}
+
+const ast::IfStmt * MultiLevelExitCore::postvisit( const ast::IfStmt * stmt ) {
+	bool labeledBlock = !stmt->labels.empty();
+	if ( labeledBlock ) {
+		auto this_label = enclosing_control_structures.back().useBreakExit();
+		if ( !this_label.empty() ) {
+			break_label = this_label;
+		}
+	}
+	return stmt;
+}
+
+bool isDefaultCase( const ast::ptr<ast::Stmt> & stmt ) {
+	const ast::CaseStmt * caseStmt = stmt.strict_as<ast::CaseStmt>();
+	return caseStmt->isDefault();
+}
+
+void MultiLevelExitCore::previsit( const ast::SwitchStmt * stmt ) {
+	ast::Label label = LabelGenerator::newLabel( "switchBreak", stmt );
+	auto it = std::find_if( stmt->stmts.rbegin(), stmt->stmts.rend(), isDefaultCase );
+
+	const ast::CaseStmt * defaultCase = it != stmt->stmts.rend()
+		? (it)->strict_as<ast::CaseStmt>() : nullptr;
+	ast::Label defaultLabel = defaultCase
+		? LabelGenerator::newLabel( "fallThroughDefault", defaultCase )
+		: ast::Label( stmt->location, "" );
+	enclosing_control_structures.emplace_back( stmt, label, defaultLabel );
+	GuardAction( [this]() { enclosing_control_structures.pop_back(); } );
+
+	// Collect valid labels for fallthrough. It starts with all labels at
+	// this level, then removed as we see them in traversal.
+	for ( const ast::Stmt * stmt : stmt->stmts ) {
+		auto * caseStmt = strict_dynamic_cast< const ast::CaseStmt * >( stmt );
+		if ( caseStmt->stmts.empty() ) continue;
+		auto block = caseStmt->stmts.front().strict_as<ast::CompoundStmt>();
+		for ( const ast::Stmt * stmt : block->kids ) {
+			for ( const ast::Label & l : stmt->labels ) {
+				fallthrough_labels.insert( l );
+			}
+		}
+	}
+}
+
+const ast::SwitchStmt * MultiLevelExitCore::postvisit( const ast::SwitchStmt * stmt ) {
+	assert( !enclosing_control_structures.empty() );
+	Entry & entry = enclosing_control_structures.back();
+	assert( entry.stmt == stmt );
+
+	// Only run if we need to generate the break label.
+	if ( entry.isBreakUsed() ) {
+		// To keep the switch statements uniform (all direct children of a
+		// SwitchStmt should be CastStmts), append the exit label and break
+		// to the last case, create a default case is there are no cases.
+		ast::SwitchStmt * mutStmt = ast::mutate( stmt );
+		if ( mutStmt->stmts.empty() ) {
+			mutStmt->stmts.push_back( new ast::CaseStmt(
+				mutStmt->location, nullptr, {} ));
+		}
+
+		auto caseStmt = mutStmt->stmts.back().strict_as<ast::CaseStmt>();
+		auto mutCase = ast::mutate( caseStmt );
+		mutStmt->stmts.back() = mutCase;
+
+		ast::Label label( mutCase->location, "breakLabel" );
+		auto branch = new ast::BranchStmt( mutCase->location, ast::BranchStmt::Break, label );
+		branch->labels.push_back( entry.useBreakExit() );
+		mutCase->stmts.push_back( branch );
+
+		return mutStmt;
+	}
+	return stmt;
+}
+
+void MultiLevelExitCore::previsit( const ast::ReturnStmt * stmt ) {
+	if ( inFinally ) {
+		SemanticError( stmt->location, "'return' may not appear in a finally clause" );
+	}
+}
+
+void MultiLevelExitCore::previsit( const ast::TryStmt * stmt ) {
+	bool isLabeled = !stmt->labels.empty();
+	if ( isLabeled ) {
+		ast::Label breakLabel = LabelGenerator::newLabel( "blockBreak", stmt );
+		enclosing_control_structures.emplace_back( stmt, breakLabel );
+		GuardAction([this](){ enclosing_control_structures.pop_back(); } );
+	}
+}
+
+void MultiLevelExitCore::postvisit( const ast::TryStmt * stmt ) {
+	bool isLabeled = !stmt->labels.empty();
+	if ( isLabeled ) {
+		auto this_label = enclosing_control_structures.back().useBreakExit();
+		if ( !this_label.empty() ) {
+			break_label = this_label;
+		}
+	}
+}
+
+void MultiLevelExitCore::previsit( const ast::FinallyStmt * ) {
+	GuardAction([this, old = std::move(enclosing_control_structures)](){
+		enclosing_control_structures = std::move(old);
+	});
+	enclosing_control_structures = std::vector<Entry>();
+	GuardValue( inFinally ) = true;
+}
+
+const ast::Stmt * MultiLevelExitCore::mutateLoop(
+		const ast::Stmt * body, Entry & entry ) {
+	if ( entry.isBreakUsed() ) {
+		break_label = entry.useBreakExit();
+	}
+
+	if ( entry.isContUsed() ) {
+		ast::CompoundStmt * new_body = new ast::CompoundStmt( body->location );
+		new_body->kids.push_back( body );
+		new_body->kids.push_back(
+			labelledNullStmt( body->location, entry.useContExit() ) );
+		return new_body;
+	}
+
+	return body;
+}
+
+template<typename LoopNode>
+void MultiLevelExitCore::prehandleLoopStmt( const LoopNode * loopStmt ) {
+	// Remember is loop before going onto mutate the body.
+	// The labels will be folded in if they are used.
+	ast::Label breakLabel = LabelGenerator::newLabel( "loopBreak", loopStmt );
+	ast::Label contLabel = LabelGenerator::newLabel( "loopContinue", loopStmt );
+	enclosing_control_structures.emplace_back( loopStmt, breakLabel, contLabel );
+	GuardAction( [this](){ enclosing_control_structures.pop_back(); } );
+}
+
+template<typename LoopNode>
+const LoopNode * MultiLevelExitCore::posthandleLoopStmt( const LoopNode * loopStmt ) {
+	assert( !enclosing_control_structures.empty() );
+	Entry & entry = enclosing_control_structures.back();
+	assert( entry.stmt == loopStmt );
+
+	// Now we check if the labels are used and add them if so.
+	return ast::mutate_field(
+		loopStmt, &LoopNode::body, mutateLoop( loopStmt->body, entry ) );
+}
+
+std::list<ast::ptr<ast::Stmt>> MultiLevelExitCore::fixBlock(
+		const std::list<ast::ptr<ast::Stmt>> & kids, bool is_case_clause ) {
+	// Unfortunately we can't use the automatic error collection.
+	SemanticErrorException errors;
+
+	std::list<ast::ptr<ast::Stmt>> ret;
+
+	// Manually visit each child.
+	for ( const ast::ptr<ast::Stmt> & kid : kids ) {
+		if ( is_case_clause ) {
+			// Once a label is seen, it's no longer a valid for fallthrough.
+			for ( const ast::Label & l : kid->labels ) {
+				fallthrough_labels.erase( l );
+			}
+		}
+
+		try {
+			ret.push_back( kid->accept( *visitor ) );
+		} catch ( SemanticErrorException & e ) {
+			errors.append( e );
+		}
+
+		if ( !break_label.empty() ) {
+			ret.push_back(
+				labelledNullStmt( ret.back()->location, break_label ) );
+			break_label = ast::Label( CodeLocation(), "" );
+		}
+	}
+
+	if ( !errors.isEmpty() ) {
+		throw errors;
+	}
+	return ret;
+}
+
+} // namespace
+
+const ast::CompoundStmt * multiLevelExitUpdate(
+    	const ast::CompoundStmt * stmt,
+		const LabelToStmt & labelTable ) {
+	// Must start in the body, so FunctionDecls can be a stopping point.
+	ast::Pass<MultiLevelExitCore> visitor( labelTable );
+	const ast::CompoundStmt * ret = stmt->accept( visitor );
+	return ret;
+}
+
+} // namespace ControlStruct
+
+// Local Variables: //
+// tab-width: 4 //
+// mode: c++ //
+// compile-command: "make install" //
+// End: //
Index: src/ControlStruct/MultiLevelExit.hpp
===================================================================
--- src/ControlStruct/MultiLevelExit.hpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
+++ src/ControlStruct/MultiLevelExit.hpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -0,0 +1,40 @@
+//
+// Cforall Version 1.0.0 Copyright (C) 2015 University of Waterloo
+//
+// The contents of this file are covered under the licence agreement in the
+// file "LICENCE" distributed with Cforall.
+//
+// MultiLevelExit.hpp -- Replaces CFA's local control flow with C's versions.
+//
+// Author           : Andrew Beach
+// Created On       : Mon Nov  1 13:49:00 2021
+// Last Modified By : Andrew Beach
+// Last Modified On : Mon Nov  8 10:53:00 2021
+// Update Count     : 3
+//
+
+#pragma once
+
+#include <map>
+
+namespace ast {
+	class CompoundStmt;
+	class Label;
+	class Stmt;
+}
+
+namespace ControlStruct {
+
+using LabelToStmt = std::map<ast::Label, const ast::Stmt *>;
+
+/// Mutate a function body to handle multi-level exits.
+const ast::CompoundStmt * multiLevelExitUpdate(
+	const ast::CompoundStmt *, const LabelToStmt & );
+
+}
+
+// Local Variables: //
+// tab-width: 4 //
+// mode: c++ //
+// compile-command: "make install" //
+// End: //
Index: src/ControlStruct/module.mk
===================================================================
--- src/ControlStruct/module.mk	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ src/ControlStruct/module.mk	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -18,4 +18,6 @@
 	ControlStruct/ExceptDecl.cc \
 	ControlStruct/ExceptDecl.h \
+	ControlStruct/FixLabels.cpp \
+	ControlStruct/FixLabels.hpp \
 	ControlStruct/ForExprMutator.cc \
 	ControlStruct/ForExprMutator.h \
@@ -26,8 +28,14 @@
 	ControlStruct/MLEMutator.cc \
 	ControlStruct/MLEMutator.h \
+	ControlStruct/MultiLevelExit.cpp \
+	ControlStruct/MultiLevelExit.hpp \
 	ControlStruct/Mutate.cc \
 	ControlStruct/Mutate.h
 
-SRC += $(SRC_CONTROLSTRUCT) ControlStruct/ExceptTranslate.cc ControlStruct/ExceptTranslate.h
+SRC += $(SRC_CONTROLSTRUCT) \
+	ControlStruct/ExceptTranslateNew.cpp \
+	ControlStruct/ExceptTranslate.cc \
+	ControlStruct/ExceptTranslate.h
+
 SRCDEMANGLE += $(SRC_CONTROLSTRUCT)
 
Index: src/InitTweak/FixInitNew.cpp
===================================================================
--- src/InitTweak/FixInitNew.cpp	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ src/InitTweak/FixInitNew.cpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -591,5 +591,6 @@
 		// need to add __Destructor for _tmp_cp variables as well
 
-		assertf( ast::dtorStruct && ast::dtorStruct->members.size() == 2, "Destructor generation requires __Destructor definition." );
+		assertf( ast::dtorStruct, "Destructor generation requires __Destructor definition." );
+		assertf( ast::dtorStruct->members.size() == 2, "__Destructor definition does not have expected fields." );
 		assertf( ast::dtorStructDestroy, "Destructor generation requires __destroy_Destructor." );
 
@@ -1216,6 +1217,6 @@
 
 							static UniqueName memberDtorNamer = { "__memberDtor" };
-							assertf( Validate::dtorStruct, "builtin __Destructor not found." );
-							assertf( Validate::dtorStructDestroy, "builtin __destroy_Destructor not found." );
+							assertf( ast::dtorStruct, "builtin __Destructor not found." );
+							assertf( ast::dtorStructDestroy, "builtin __destroy_Destructor not found." );
 
 							ast::Expr * thisExpr = new ast::CastExpr( new ast::AddressExpr( new ast::VariableExpr(loc, thisParam ) ), new ast::PointerType( new ast::VoidType(), ast::CV::Qualifiers() ) );
Index: src/InitTweak/GenInit.cc
===================================================================
--- src/InitTweak/GenInit.cc	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ src/InitTweak/GenInit.cc	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -9,7 +9,7 @@
 // Author           : Rob Schluntz
 // Created On       : Mon May 18 07:44:20 2015
-// Last Modified By : Peter A. Buhr
-// Last Modified On : Fri Dec 13 23:15:10 2019
-// Update Count     : 184
+// Last Modified By : Andrew Beach
+// Last Modified On : Mon Oct 25 13:53:00 2021
+// Update Count     : 186
 //
 #include "GenInit.h"
@@ -24,4 +24,5 @@
 #include "AST/Decl.hpp"
 #include "AST/Init.hpp"
+#include "AST/Pass.hpp"
 #include "AST/Node.hpp"
 #include "AST/Stmt.hpp"
@@ -294,4 +295,133 @@
 	}
 
+namespace {
+
+#	warning Remove the _New suffix after the conversion is complete.
+	struct HoistArrayDimension_NoResolve_New final :
+			public ast::WithDeclsToAdd<>, public ast::WithShortCircuiting,
+			public ast::WithGuards, public ast::WithConstTranslationUnit,
+			public ast::WithVisitorRef<HoistArrayDimension_NoResolve_New> {
+		void previsit( const ast::ObjectDecl * decl );
+		const ast::DeclWithType * postvisit( const ast::ObjectDecl * decl );
+		// Do not look for objects inside there declarations (and type).
+		void previsit( const ast::AggregateDecl * ) { visit_children = false; }
+		void previsit( const ast::NamedTypeDecl * ) { visit_children = false; }
+		void previsit( const ast::FunctionType * ) { visit_children = false; }
+
+		const ast::Type * hoist( const ast::Type * type );
+
+		ast::Storage::Classes storageClasses;
+	};
+
+	void HoistArrayDimension_NoResolve_New::previsit(
+			const ast::ObjectDecl * decl ) {
+		GuardValue( storageClasses ) = decl->storage;
+	}
+
+	const ast::DeclWithType * HoistArrayDimension_NoResolve_New::postvisit(
+			const ast::ObjectDecl * objectDecl ) {
+		return mutate_field( objectDecl, &ast::ObjectDecl::type,
+				hoist( objectDecl->type ) );
+	}
+
+	const ast::Type * HoistArrayDimension_NoResolve_New::hoist(
+			const ast::Type * type ) {
+		static UniqueName dimensionName( "_array_dim" );
+
+		if ( !isInFunction() || storageClasses.is_static ) {
+			return type;
+		}
+
+		if ( auto arrayType = dynamic_cast< const ast::ArrayType * >( type ) ) {
+			if ( nullptr == arrayType->dimension ) {
+				return type;
+			}
+
+			if ( !Tuples::maybeImpure( arrayType->dimension ) ) {
+				return type;
+			}
+
+			ast::ptr<ast::Type> dimType = transUnit().global.sizeType;
+			assert( dimType );
+			add_qualifiers( dimType, ast::CV::Qualifiers( ast::CV::Const ) );
+
+			ast::ObjectDecl * arrayDimension = new ast::ObjectDecl(
+				arrayType->dimension->location,
+				dimensionName.newName(),
+				dimType,
+				new ast::SingleInit(
+					arrayType->dimension->location,
+					arrayType->dimension
+				)
+			);
+
+			ast::ArrayType * mutType = ast::mutate( arrayType );
+			mutType->dimension = new ast::VariableExpr(
+					arrayDimension->location, arrayDimension );
+			declsToAddBefore.push_back( arrayDimension );
+
+			mutType->base = hoist( mutType->base );
+			return mutType;
+		}
+		return type;
+	}
+
+	struct ReturnFixer_New final :
+			public ast::WithStmtsToAdd<>, ast::WithGuards {
+		void previsit( const ast::FunctionDecl * decl );
+		const ast::ReturnStmt * previsit( const ast::ReturnStmt * stmt );
+	private:
+		const ast::FunctionDecl * funcDecl = nullptr;
+	};
+
+	void ReturnFixer_New::previsit( const ast::FunctionDecl * decl ) {
+		GuardValue( funcDecl ) = decl;
+	}
+
+	const ast::ReturnStmt * ReturnFixer_New::previsit(
+			const ast::ReturnStmt * stmt ) {
+		auto & returns = funcDecl->returns;
+		assert( returns.size() < 2 );
+		// Hands off if the function returns a reference.
+		// Don't allocate a temporary if the address is returned.
+		if ( stmt->expr && 1 == returns.size() ) {
+			ast::ptr<ast::DeclWithType> retDecl = returns.front();
+			if ( isConstructable( retDecl->get_type() ) ) {
+				// Explicitly construct the return value using the return
+				// expression and the retVal object.
+				assertf( "" != retDecl->name,
+					"Function %s has unnamed return value.\n",
+					funcDecl->name.c_str() );
+
+				auto retVal = retDecl.strict_as<ast::ObjectDecl>();
+				if ( auto varExpr = stmt->expr.as<ast::VariableExpr>() ) {
+					// Check if the return statement is already set up.
+					if ( varExpr->var == retVal ) return stmt;
+				}
+				ast::ptr<ast::Stmt> ctorStmt = genCtorDtor(
+					retVal->location, "?{}", retVal, stmt->expr );
+				assertf( ctorStmt,
+					"ReturnFixer: genCtorDtor returned nllptr: %s / %s",
+					toString( retVal ).c_str(),
+					toString( stmt->expr ).c_str() );
+					stmtsToAddBefore.push_back( ctorStmt );
+
+				// Return the retVal object.
+				ast::ReturnStmt * mutStmt = ast::mutate( stmt );
+				mutStmt->expr = new ast::VariableExpr(
+					stmt->location, retDecl );
+				return mutStmt;
+			}
+		}
+		return stmt;
+	}
+
+} // namespace
+
+	void genInit( ast::TranslationUnit & transUnit ) {
+		ast::Pass<HoistArrayDimension_NoResolve_New>::run( transUnit );
+		ast::Pass<ReturnFixer_New>::run( transUnit );
+	}
+
 	void CtorDtor::generateCtorDtor( std::list< Declaration * > & translationUnit ) {
 		PassVisitor<CtorDtor> ctordtor;
Index: src/InitTweak/GenInit.h
===================================================================
--- src/InitTweak/GenInit.h	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ src/InitTweak/GenInit.h	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -9,7 +9,7 @@
 // Author           : Rodolfo G. Esteves
 // Created On       : Mon May 18 07:44:20 2015
-// Last Modified By : Peter A. Buhr
-// Last Modified On : Sat Jul 22 09:31:19 2017
-// Update Count     : 4
+// Last Modified By : Andrew Beach
+// Last Modified On : Fri Oct 22 16:08:00 2021
+// Update Count     : 6
 //
 
@@ -27,4 +27,5 @@
 	/// Adds return value temporaries and wraps Initializers in ConstructorInit nodes
 	void genInit( std::list< Declaration * > & translationUnit );
+	void genInit( ast::TranslationUnit & translationUnit );
 
 	/// Converts return statements into copy constructor calls on the hidden return variable
Index: src/InitTweak/InitTweak.cc
===================================================================
--- src/InitTweak/InitTweak.cc	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ src/InitTweak/InitTweak.cc	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -9,7 +9,7 @@
 // Author           : Rob Schluntz
 // Created On       : Fri May 13 11:26:36 2016
-// Last Modified By : Peter A. Buhr
-// Last Modified On : Wed Jun 16 20:57:22 2021
-// Update Count     : 18
+// Last Modified By : Andrew Beach
+// Last Modified On : Fri Nov 19 19:22:00 2021
+// Update Count     : 19
 //
 
@@ -540,4 +540,14 @@
 	}
 
+	const ast::Type * getTypeofThis( const ast::FunctionType * ftype ) {
+		assertf( ftype, "getTypeofThis: nullptr ftype" );
+		const std::vector<ast::ptr<ast::Type>> & params = ftype->params;
+		assertf( !params.empty(), "getTypeofThis: ftype with 0 parameters: %s",
+				toString( ftype ).c_str() );
+		const ast::ReferenceType * refType =
+			params.front().strict_as<ast::ReferenceType>();
+		return refType->base;
+	}
+
 	ObjectDecl * getParamThis( FunctionType * ftype ) {
 		assertf( ftype, "getParamThis: nullptr ftype" );
Index: src/InitTweak/InitTweak.h
===================================================================
--- src/InitTweak/InitTweak.h	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ src/InitTweak/InitTweak.h	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -10,6 +10,6 @@
 // Created On       : Fri May 13 11:26:36 2016
 // Last Modified By : Andrew Beach
-// Last Modified On : Fri Jul 19 14:18:00 2019
-// Update Count     : 6
+// Last Modified On : Fri Nov 19 14:18:00 2021
+// Update Count     : 7
 //
 
@@ -35,4 +35,5 @@
 	/// returns the base type of the first parameter to a constructor/destructor/assignment function
 	Type * getTypeofThis( FunctionType * ftype );
+	const ast::Type * getTypeofThis( const ast::FunctionType * ftype );
 
 	/// returns the first parameter of a constructor/destructor/assignment function
Index: src/Parser/parser.yy
===================================================================
--- src/Parser/parser.yy	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ src/Parser/parser.yy	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -10,6 +10,6 @@
 // Created On       : Sat Sep  1 20:22:55 2001
 // Last Modified By : Peter A. Buhr
-// Last Modified On : Sat Sep 11 08:20:44 2021
-// Update Count     : 5040
+// Last Modified On : Fri Oct 15 09:20:17 2021
+// Update Count     : 5163
 //
 
@@ -31,5 +31,5 @@
 // from ANSI90 to ANSI11 C are marked with the comment "C99/C11".
 
-// This grammar also has two levels of extensions. The first extensions cover most of the GCC C extensions All of the
+// This grammar also has two levels of extensions. The first extensions cover most of the GCC C extensions. All of the
 // syntactic extensions for GCC C are marked with the comment "GCC". The second extensions are for Cforall (CFA), which
 // fixes several of C's outstanding problems and extends C with many modern language concepts. All of the syntactic
@@ -69,5 +69,5 @@
 	// 2. String encodings are transformed into canonical form (one encoding at start) so the encoding can be found
 	//    without searching the string, e.g.: "abc" L"def" L"ghi" => L"abc" "def" "ghi". Multiple encodings must match,
-	//    i.e., u"a" U"b" L"c" is disallowed.
+	//    e.g., u"a" U"b" L"c" is disallowed.
 
 	if ( from[0] != '"' ) {								// encoding ?
@@ -310,6 +310,5 @@
 %token ATassign											// @=
 
-%type<tok> identifier
-%type<tok> identifier_or_type_name  attr_name
+%type<tok> identifier					identifier_at				identifier_or_type_name		attr_name
 %type<tok> quasi_keyword
 %type<constant> string_literal
@@ -327,5 +326,5 @@
 %type<en> conditional_expression		constant_expression			assignment_expression		assignment_expression_opt
 %type<en> comma_expression				comma_expression_opt
-%type<en> argument_expression_list_opt	argument_expression			default_initializer_opt
+%type<en> argument_expression_list_opt	argument_expression_list	argument_expression			default_initializer_opt
 %type<ifctl> if_control_expression
 %type<fctl> for_control_expression		for_control_expression_list
@@ -559,4 +558,8 @@
 	IDENTIFIER
 	| quasi_keyword
+	;
+
+identifier_at:
+	identifier
 	| '@'												// CFA
 		{ Token tok = { new string( DeclarationNode::anonymous.newName() ), yylval.tok.loc }; $$ = tok; }
@@ -693,5 +696,9 @@
 	// empty
 		{ $$ = nullptr; }
-	| argument_expression
+	| argument_expression_list
+	;
+
+argument_expression_list:
+	argument_expression
 	| argument_expression_list_opt ',' argument_expression
 		{ $$ = (ExpressionNode *)($1->set_last( $3 )); }
@@ -731,5 +738,5 @@
 	| FLOATINGconstant fraction_constants_opt
 		{ $$ = new ExpressionNode( build_field_name_fraction_constants( build_field_name_FLOATINGconstant( *$1 ), $2 ) ); }
-	| identifier fraction_constants_opt
+	| identifier_at fraction_constants_opt				// CFA, allow anonymous fields
 		{
 			$$ = new ExpressionNode( build_field_name_fraction_constants( build_varref( $1 ), $2 ) );
@@ -1084,4 +1091,7 @@
 	comma_expression_opt ';'
 		{ $$ = new StatementNode( build_expr( $1 ) ); }
+	| MUTEX '(' ')' comma_expression ';'
+		{ $$ = new StatementNode( build_mutex( nullptr, new StatementNode( build_expr( $4 ) ) ) ); }
+		// { SemanticError( yylloc, "Mutex expression is currently unimplemented." ); $$ = nullptr; }
 	;
 
@@ -1182,16 +1192,22 @@
 
 iteration_statement:
-	WHILE '(' push if_control_expression ')' statement pop
-		{ $$ = new StatementNode( build_while( $4, maybe_build_compound( $6 ) ) ); }
-	| WHILE '(' ')' statement							// CFA => while ( 1 )
+	WHILE '(' ')' statement								// CFA => while ( 1 )
 		{ $$ = new StatementNode( build_while( new IfCtrl( nullptr, new ExpressionNode( build_constantInteger( *new string( "1" ) ) ) ), maybe_build_compound( $4 ) ) ); }
-	| DO statement WHILE '(' comma_expression ')' ';'
-		{ $$ = new StatementNode( build_do_while( $5, maybe_build_compound( $2 ) ) ); }
+	| WHILE '(' if_control_expression ')' statement		%prec THEN
+		{ $$ = new StatementNode( build_while( $3, maybe_build_compound( $5 ) ) ); }
+	| WHILE '(' if_control_expression ')' statement ELSE statement // CFA
+		{ SemanticError( yylloc, "Loop default block is currently unimplemented." ); $$ = nullptr; }
 	| DO statement WHILE '(' ')' ';'					// CFA => do while( 1 )
 		{ $$ = new StatementNode( build_do_while( new ExpressionNode( build_constantInteger( *new string( "1" ) ) ), maybe_build_compound( $2 ) ) ); }
-	| FOR '(' push for_control_expression_list ')' statement pop
-		{ $$ = new StatementNode( build_for( $4, maybe_build_compound( $6 ) ) ); }
+	| DO statement WHILE '(' comma_expression ')' ';'	%prec THEN
+		{ $$ = new StatementNode( build_do_while( $5, maybe_build_compound( $2 ) ) ); }
+	| DO statement WHILE '(' comma_expression ')' ELSE statement // CFA
+		{ SemanticError( yylloc, "Loop default block is currently unimplemented." ); $$ = nullptr; }
 	| FOR '(' ')' statement								// CFA => for ( ;; )
 		{ $$ = new StatementNode( build_for( new ForCtrl( (ExpressionNode * )nullptr, (ExpressionNode * )nullptr, (ExpressionNode * )nullptr ), maybe_build_compound( $4 ) ) ); }
+	| FOR '(' for_control_expression_list ')' statement	%prec THEN
+	  	{ $$ = new StatementNode( build_for( $3, maybe_build_compound( $5 ) ) ); }
+	| FOR '(' for_control_expression_list ')' statement ELSE statement // CFA
+		{ SemanticError( yylloc, "Loop default block is currently unimplemented." ); $$ = nullptr; }
 	;
 
@@ -1339,12 +1355,10 @@
 with_statement:
 	WITH '(' tuple_expression_list ')' statement
-		{
-			$$ = new StatementNode( build_with( $3, $5 ) );
-		}
+		{ $$ = new StatementNode( build_with( $3, $5 ) ); }
 	;
 
 // If MUTEX becomes a general qualifier, there are shift/reduce conflicts, so change syntax to "with mutex".
 mutex_statement:
-	MUTEX '(' argument_expression_list_opt ')' statement
+	MUTEX '(' argument_expression_list ')' statement
 		{ $$ = new StatementNode( build_mutex( $3, $5 ) ); }
 	;
@@ -2475,5 +2489,5 @@
 designation:
 	designator_list ':'									// C99, CFA uses ":" instead of "="
-	| identifier ':'									// GCC, field name
+	| identifier_at ':'									// GCC, field name
 		{ $$ = new ExpressionNode( build_varref( $1 ) ); }
 	;
@@ -2487,5 +2501,5 @@
 
 designator:
-	'.' identifier										// C99, field name
+	'.' identifier_at									// C99, field name
 		{ $$ = new ExpressionNode( build_varref( $2 ) ); }
 	| '[' push assignment_expression pop ']'			// C99, single array element
@@ -2919,5 +2933,5 @@
 
 paren_identifier:
-	identifier
+	identifier_at
 		{ $$ = DeclarationNode::newName( $1 ); }
 	| '(' paren_identifier ')'							// redundant parenthesis
Index: src/ResolvExpr/CandidatePrinter.cpp
===================================================================
--- src/ResolvExpr/CandidatePrinter.cpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
+++ src/ResolvExpr/CandidatePrinter.cpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -0,0 +1,62 @@
+//
+// Cforall Version 1.0.0 Copyright (C) 2015 University of Waterloo
+//
+// The contents of this file are covered under the licence agreement in the
+// file "LICENCE" distributed with Cforall.
+//
+// CandidatePrinter.cpp -- Print expression canditates.
+//
+// Author           : Andrew Beach
+// Created On       : Tue Nov  9  9:54:00 2021
+// Last Modified By : Andrew Beach
+// Last Modified On : Tue Nov  9 15:47:00 2021
+// Update Count     : 0
+//
+
+#include "CandidatePrinter.hpp"
+
+#include "AST/Expr.hpp"
+#include "AST/Pass.hpp"
+#include "AST/Print.hpp"
+#include "AST/Stmt.hpp"
+#include "AST/TranslationUnit.hpp"
+#include "ResolvExpr/CandidateFinder.hpp"
+
+#include <iostream>
+
+namespace ResolvExpr {
+
+namespace {
+
+class CandidatePrintCore : public ast::WithSymbolTable {
+	std::ostream & os;
+public:
+	CandidatePrintCore( std::ostream & os ) : os( os ) {}
+
+	void postvisit( const ast::ExprStmt * stmt ) {
+		ast::TypeEnvironment env;
+		CandidateFinder finder( symtab, env );
+		finder.find( stmt->expr, ResolvMode::withAdjustment() );
+		int count = 1;
+		os << "There are " << finder.candidates.size() << " candidates\n";
+		for ( const std::shared_ptr<Candidate> & cand : finder ) {
+			os << "Candidate " << count++ << " ==============\n";
+			ast::print( os, cand->expr->result.get() );
+			os << std::endl;
+		}
+	}
+};
+
+} // namespace
+
+void printCandidates( ast::TranslationUnit & transUnit ) {
+	ast::Pass<CandidatePrintCore>::run( transUnit, std::cout );
+}
+
+} // namespace ResolvExpr
+
+// Local Variables: //
+// tab-width: 4 //
+// mode: c++ //
+// compile-command: "make install" //
+// End: //
Index: src/ResolvExpr/CandidatePrinter.hpp
===================================================================
--- src/ResolvExpr/CandidatePrinter.hpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
+++ src/ResolvExpr/CandidatePrinter.hpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -0,0 +1,35 @@
+//
+// Cforall Version 1.0.0 Copyright (C) 2015 University of Waterloo
+//
+// The contents of this file are covered under the licence agreement in the
+// file "LICENCE" distributed with Cforall.
+//
+// CandidatePrinter.hpp -- Print expression canditates.
+//
+// Author           : Andrew Beach
+// Created On       : Tue Nov  9  9:49:00 2021
+// Last Modified By : Andrew Beach
+// Last Modified On : Tue Nov  9 15:33:00 2021
+// Update Count     : 0
+//
+
+#pragma once
+
+namespace ast {
+    class TranslationUnit;
+}
+
+namespace ResolvExpr {
+
+void printCandidates( ast::TranslationUnit & transUnit );
+/* Traverse over the entire translation unit, printing candidates for each
+ * top level expression. See CandidateFinder.
+ */
+
+} // namespace ResolvExpr
+
+// Local Variables: //
+// tab-width: 4 //
+// mode: c++ //
+// compile-command: "make install" //
+// End: //
Index: src/ResolvExpr/module.mk
===================================================================
--- src/ResolvExpr/module.mk	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ src/ResolvExpr/module.mk	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -61,5 +61,9 @@
       ResolvExpr/WidenMode.h
 
+SRC += $(SRC_RESOLVEXPR) \
+	ResolvExpr/AlternativePrinter.cc \
+	ResolvExpr/AlternativePrinter.h \
+	ResolvExpr/CandidatePrinter.cpp \
+	ResolvExpr/CandidatePrinter.hpp
 
-SRC += $(SRC_RESOLVEXPR) ResolvExpr/AlternativePrinter.cc ResolvExpr/AlternativePrinter.h
 SRCDEMANGLE += $(SRC_RESOLVEXPR)
Index: src/SymTab/Validate.cc
===================================================================
--- src/SymTab/Validate.cc	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ src/SymTab/Validate.cc	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -9,7 +9,7 @@
 // Author           : Richard C. Bilson
 // Created On       : Sun May 17 21:50:04 2015
-// Last Modified By : Peter A. Buhr
-// Last Modified On : Fri Dec 13 23:43:34 2019
-// Update Count     : 363
+// Last Modified By : Andrew Beach
+// Last Modified On : Fri Nov 12 11:00:00 2021
+// Update Count     : 364
 //
 
@@ -334,15 +334,7 @@
 	};
 
-	void validate( std::list< Declaration * > &translationUnit, __attribute__((unused)) bool doDebug ) {
+	void validate_A( std::list< Declaration * > & translationUnit ) {
 		PassVisitor<EnumAndPointerDecay_old> epc;
-		PassVisitor<LinkReferenceToTypes_old> lrt( nullptr );
-		PassVisitor<ResolveEnumInitializers> rei( nullptr );
-		PassVisitor<ForallPointerDecay_old> fpd;
-		PassVisitor<CompoundLiteral> compoundliteral;
-		PassVisitor<ValidateGenericParameters> genericParams;
-		PassVisitor<LabelAddressFixer> labelAddrFixer;
 		PassVisitor<HoistTypeDecls> hoistDecls;
-		PassVisitor<FixQualifiedTypes> fixQual;
-
 		{
 			Stats::Heap::newPass("validate-A");
@@ -354,4 +346,9 @@
 			acceptAll( translationUnit, epc ); // must happen before VerifyCtorDtorAssign, because void return objects should not exist; before LinkReferenceToTypes_old because it is an indexer and needs correct types for mangling
 		}
+	}
+
+	void validate_B( std::list< Declaration * > & translationUnit ) {
+		PassVisitor<LinkReferenceToTypes_old> lrt( nullptr );
+		PassVisitor<FixQualifiedTypes> fixQual;
 		{
 			Stats::Heap::newPass("validate-B");
@@ -362,4 +359,9 @@
 			EliminateTypedef::eliminateTypedef( translationUnit );
 		}
+	}
+
+	void validate_C( std::list< Declaration * > & translationUnit ) {
+		PassVisitor<ValidateGenericParameters> genericParams;
+		PassVisitor<ResolveEnumInitializers> rei( nullptr );
 		{
 			Stats::Heap::newPass("validate-C");
@@ -381,4 +383,8 @@
 			});
 		}
+	}
+
+	void validate_D( std::list< Declaration * > & translationUnit ) {
+		PassVisitor<ForallPointerDecay_old> fpd;
 		{
 			Stats::Heap::newPass("validate-D");
@@ -397,4 +403,8 @@
 			});
 		}
+	}
+
+	void validate_E( std::list< Declaration * > & translationUnit ) {
+		PassVisitor<CompoundLiteral> compoundliteral;
 		{
 			Stats::Heap::newPass("validate-E");
@@ -415,4 +425,8 @@
 			}
 		}
+	}
+
+	void validate_F( std::list< Declaration * > & translationUnit ) {
+		PassVisitor<LabelAddressFixer> labelAddrFixer;
 		{
 			Stats::Heap::newPass("validate-F");
@@ -437,4 +451,13 @@
 			}
 		}
+	}
+
+	void validate( std::list< Declaration * > &translationUnit, __attribute__((unused)) bool doDebug ) {
+		validate_A( translationUnit );
+		validate_B( translationUnit );
+		validate_C( translationUnit );
+		validate_D( translationUnit );
+		validate_E( translationUnit );
+		validate_F( translationUnit );
 	}
 
Index: src/SymTab/Validate.h
===================================================================
--- src/SymTab/Validate.h	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ src/SymTab/Validate.h	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -35,4 +35,12 @@
 	void validateType( Type *type, const Indexer *indexer );
 
+	// Sub-passes of validate.
+	void validate_A( std::list< Declaration * > &translationUnit );
+	void validate_B( std::list< Declaration * > &translationUnit );
+	void validate_C( std::list< Declaration * > &translationUnit );
+	void validate_D( std::list< Declaration * > &translationUnit );
+	void validate_E( std::list< Declaration * > &translationUnit );
+	void validate_F( std::list< Declaration * > &translationUnit );
+
 	const ast::Type * validateType(
 		const CodeLocation & loc, const ast::Type * type, const ast::SymbolTable & symtab );
Index: src/Tuples/TupleExpansionNew.cpp
===================================================================
--- src/Tuples/TupleExpansionNew.cpp	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ src/Tuples/TupleExpansionNew.cpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -21,4 +21,8 @@
 		void previsit( const ast::UntypedMemberExpr * ) { visit_children = false; }
         const ast::Expr * postvisit( const ast::UntypedMemberExpr * memberExpr );
+	};
+	struct UniqueExprExpander final : public ast::WithDeclsToAdd<> {
+		const ast::Expr * postvisit( const ast::UniqueExpr * unqExpr );
+		std::map< int, ast::ptr<ast::Expr> > decls; // not vector, because order added may not be increasing order
 	};
 } // namespace
@@ -66,3 +70,44 @@
 	}
 } // namespace
+
+void expandUniqueExpr( ast::TranslationUnit & translationUnit ) {
+	ast::Pass< UniqueExprExpander >::run( translationUnit );
+}
+
+namespace {
+	const ast::Expr * UniqueExprExpander::postvisit( const ast::UniqueExpr * unqExpr ) {
+		const CodeLocation loc = unqExpr->location;
+		const int id = unqExpr->id;
+
+		// on first time visiting a unique expr with a particular ID, generate the expression that replaces all UniqueExprs with that ID,
+		// and lookup on subsequent hits. This ensures that all unique exprs with the same ID reference the same variable.
+		if ( ! decls.count( id ) ) {
+			ast::ptr< ast::Expr > assignUnq;
+			const ast::VariableExpr * var = unqExpr->var;
+			if ( unqExpr->object ) {
+				// an object was generated to represent this unique expression -- it should be added to the list of declarations now
+				declsToAddBefore.push_back( unqExpr->object.as< ast::Decl >() );
+				// deep copy required due to unresolved issues with UniqueExpr
+				assignUnq = ast::UntypedExpr::createAssign( loc, var, unqExpr->expr );
+			} else {
+				const auto commaExpr = unqExpr->expr.strict_as< ast::CommaExpr >();
+				assignUnq = commaExpr->arg1;
+			}
+			auto finished = new ast::ObjectDecl( loc, toString( "_unq", id, "_finished_" ), new ast::BasicType( ast::BasicType::Kind::Bool ),
+				new ast::SingleInit( loc, ast::ConstantExpr::from_int( loc, 0 ) ), {}, ast::Linkage::Cforall );
+			declsToAddBefore.push_back( finished );
+			// (finished ? _unq_expr_N : (_unq_expr_N = <unqExpr->get_expr()>, finished = 1, _unq_expr_N))
+			// This pattern ensures that each unique expression is evaluated once, regardless of evaluation order of the generated C code.
+			auto assignFinished = ast::UntypedExpr::createAssign( loc, new ast::VariableExpr( loc, finished ),
+				ast::ConstantExpr::from_int( loc, 1 ) );
+			auto condExpr = new ast::ConditionalExpr( loc, new ast::VariableExpr( loc, finished ), var,
+				new ast::CommaExpr( loc, new ast::CommaExpr( loc, assignUnq, assignFinished ), var ) );
+			condExpr->result = var->result;
+			condExpr->env = unqExpr->env;
+			decls[id] = condExpr;
+		}
+		//delete unqExpr;
+		return ast::deepCopy(decls[id].get());
+	}
+} // namespace
 } // namespace Tuples
Index: src/Tuples/Tuples.h
===================================================================
--- src/Tuples/Tuples.h	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ src/Tuples/Tuples.h	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -46,4 +46,5 @@
 	/// replaces UniqueExprs with a temporary variable and one call
 	void expandUniqueExpr( std::list< Declaration * > & translationUnit );
+	void expandUniqueExpr( ast::TranslationUnit & translationUnit );
 
 	/// returns VoidType if any of the expressions have Voidtype, otherwise TupleType of the Expression result types
Index: src/Validate/FindSpecialDecls.h
===================================================================
--- src/Validate/FindSpecialDecls.h	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ src/Validate/FindSpecialDecls.h	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -9,7 +9,7 @@
 // Author           : Rob Schluntz
 // Created On       : Thu Aug 30 09:49:02 2018
-// Last Modified By : Rob Schluntz
-// Last Modified On : Thu Aug 30 09:51:12 2018
-// Update Count     : 2
+// Last Modified By : Andrew Beach
+// Last Modified On : Wed Nov 10 15:16:00 2021
+// Update Count     : 3
 //
 
@@ -22,4 +22,8 @@
 class StructDecl;
 class Type;
+
+namespace ast {
+	class TranslationUnit;
+}
 
 namespace Validate {
@@ -38,4 +42,9 @@
 	/// find and remember some of the special declarations that are useful for generating code, so that they do not have to be discovered multiple times.
 	void findSpecialDecls( std::list< Declaration * > & translationUnit );
+
+/// find and remember some of the special declarations that are useful for
+/// generating code, so that they do not have to be discovered multiple times.
+void findGlobalDecls( ast::TranslationUnit & translationUnit );
+
 } // namespace Validate
 
Index: src/Validate/FindSpecialDeclsNew.cpp
===================================================================
--- src/Validate/FindSpecialDeclsNew.cpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
+++ src/Validate/FindSpecialDeclsNew.cpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -0,0 +1,93 @@
+//
+// Cforall Version 1.0.0 Copyright (C) 2016 University of Waterloo
+//
+// The contents of this file are covered under the licence agreement in the
+// file "LICENCE" distributed with Cforall.
+//
+// FindSpecialDeclsNew.cpp -- Find special declarations used in the compiler.
+//
+// Author           : Andrew Beach
+// Created On       : Wed Nov 10 13:51:00 2021
+// Last Modified By : Andrew Beach
+// Last Modified On : Wed Nov 10 15:22:00 2021
+// Update Count     : 0
+//
+
+#include "Validate/FindSpecialDecls.h"
+
+#include "AST/Decl.hpp"
+#include "AST/Pass.hpp"
+#include "AST/TranslationUnit.hpp"
+
+// NOTE: currently, it is assumed that every special declaration occurs at the
+// top-level, so function bodies, aggregate bodies, object initializers, etc.
+// are not visited. If this assumption changes, e.g., with the introduction
+// of namespaces, remove the visit_children assignments.
+
+namespace Validate {
+
+namespace {
+
+struct FindDeclsCore : public ast::WithShortCircuiting {
+	ast::TranslationUnit::Global & global;
+	FindDeclsCore( ast::TranslationUnit::Global & g ) : global( g ) {}
+
+	void previsit( const ast::Decl * decl );
+	void previsit( const ast::FunctionDecl * decl );
+	void previsit( const ast::StructDecl * decl );
+};
+
+void FindDeclsCore::previsit( const ast::Decl * ) {
+	visit_children = false;
+}
+
+void FindDeclsCore::previsit( const ast::FunctionDecl * decl ) {
+	visit_children = false;
+	if ( !global.dereference && decl->name == "*?" ) {
+		const ast::FunctionType * type = decl->type.get();
+		if ( decl->linkage == ast::Linkage::Intrinsic && type->params.size() == 1 ) {
+			const ast::PointerType * ptrType = type->params.front().strict_as<ast::PointerType>();
+			ast::ptr<ast::Type> baseType = ptrType->base;
+			if ( baseType->qualifiers == ast::CV::Qualifiers() ) {
+				const ast::TypeInstType * inst = baseType.as<ast::TypeInstType>();
+				if ( inst || inst->kind != ast::TypeDecl::Ftype ) {
+					global.dereference = decl;
+				}
+			}
+		}
+	} else if ( !global.dtorDestroy && decl->name == "__destroy_Destructor" ) {
+		global.dtorDestroy = decl;
+	}
+}
+
+void FindDeclsCore::previsit( const ast::StructDecl * decl ) {
+	visit_children = false;
+	if ( !global.dtorStruct && decl->name == "__Destructor" ) {
+		global.dtorStruct = decl;
+	}
+}
+
+} // namespace
+
+// Fill the TranslationUnit's dereference, dtorStruct and dtorDestroy fields.
+void findGlobalDecls( ast::TranslationUnit & translationUnit ) {
+	ast::Pass<FindDeclsCore>::run( translationUnit, translationUnit.global );
+
+	// TODO: When everything gets the globals from the translation unit,
+	// remove these.
+	ast::dereferenceOperator = translationUnit.global.dereference;
+	ast::dtorStruct = translationUnit.global.dtorStruct;
+	ast::dtorStructDestroy = translationUnit.global.dtorDestroy;
+
+	// TODO: conditionally generate 'fake' declarations for missing features,
+	// so that translation can proceed in the event that builtins, prelude,
+	// etc. are missing.
+}
+
+} // namespace Validate
+
+// Local Variables: //
+// tab-width: 4 //
+// mode: c++ //
+// compile-command: "make install" //
+// End: //
Index: src/Validate/InitializerLength.cpp
===================================================================
--- src/Validate/InitializerLength.cpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
+++ src/Validate/InitializerLength.cpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -0,0 +1,65 @@
+//
+// Cforall Version 1.0.0 Copyright (C) 2018 University of Waterloo
+//
+// The contents of this file are covered under the licence agreement in the
+// file "LICENCE" distributed with Cforall.
+//
+// InitializerLength.cpp -- Calculate the length of arrays from initializers.
+//
+// Author           : Andrew Beach
+// Created On       : Fri Nov 12 11:46:00 2021
+// Last Modified By : Andrew Beach
+// Last Modified On : Fri Nov 12 13:35:00 2021
+// Update Count     : 0
+//
+
+//#include "InitializerLength.hpp"
+
+#include "AST/Expr.hpp"
+#include "AST/Decl.hpp"
+#include "AST/Pass.hpp"
+#include "AST/TranslationUnit.hpp"
+
+namespace Validate {
+
+namespace {
+
+/// for array types without an explicit length, compute the length and store it so that it
+/// is known to the rest of the phases. For example,
+///   int x[] = { 1, 2, 3 };
+///   int y[][2] = { { 1, 2, 3 }, { 1, 2, 3 } };
+/// here x and y are known at compile-time to have length 3, so change this into
+///   int x[3] = { 1, 2, 3 };
+///   int y[3][2] = { { 1, 2, 3 }, { 1, 2, 3 } };
+struct InitializerLength {
+	const ast::ObjectDecl * previsit( const ast::ObjectDecl * decl );
+};
+
+const ast::ObjectDecl * InitializerLength::previsit( const ast::ObjectDecl * decl ) {
+	if ( auto type = decl->type.as<ast::ArrayType>() ) {
+		if ( type->dimension ) return decl;
+		if ( auto init = decl->init.as<ast::ListInit>() ) {
+			ast::ObjectDecl * mutDecl = ast::mutate( decl );
+			ast::ArrayType * mutType = ast::mutate( type );
+			mutType->dimension = ast::ConstantExpr::from_ulong(
+				mutDecl->location, init->size() );
+			mutDecl->type = mutType;
+			return mutDecl;
+		}
+	}
+	return decl;
+}
+
+} // namespace
+
+void setLengthFromInitializer( ast::TranslationUnit & translationUnit ) {
+	ast::Pass<InitializerLength>::run( translationUnit );
+}
+
+} // namespace Validate
+
+// Local Variables: //
+// tab-width: 4 //
+// mode: c++ //
+// compile-command: "make install" //
+// End: //
Index: src/Validate/InitializerLength.hpp
===================================================================
--- src/Validate/InitializerLength.hpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
+++ src/Validate/InitializerLength.hpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -0,0 +1,27 @@
+//
+// Cforall Version 1.0.0 Copyright (C) 2018 University of Waterloo
+//
+// The contents of this file are covered under the licence agreement in the
+// file "LICENCE" distributed with Cforall.
+//
+// InitializerLength.hpp -- Calculate the length of arrays from initializers.
+//
+// Author           : Andrew Beach
+// Created On       : Fri Nov 12 16:25:00 2021
+// Last Modified By : Andrew Beach
+// Last Modified On : Fri Nov 12 16:28:00 2021
+// Update Count     : 0
+//
+
+namespace Validate {
+
+/// Set implicit length of array from the initializer.
+void setLengthFromInitializer( ast::TranslationUnit & translationUnit );
+
+}
+
+// Local Variables: //
+// tab-width: 4 //
+// mode: c++ //
+// compile-command: "make install" //
+// End: //
Index: src/Validate/LabelAddressFixer.cpp
===================================================================
--- src/Validate/LabelAddressFixer.cpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
+++ src/Validate/LabelAddressFixer.cpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -0,0 +1,74 @@
+//
+// Cforall Version 1.0.0 Copyright (C) 2018 University of Waterloo
+//
+// The contents of this file are covered under the licence agreement in the
+// file "LICENCE" distributed with Cforall.
+//
+// LabelAddressFixer.cpp --
+//
+// Author           : Andrew Beach
+// Created On       : Fri Nov 12 16:30:00 2021
+// Last Modified By : Andrew Beach
+// Last Modified On : Fri Nov 12 16:30:00 2021
+// Update Count     : 0
+//
+
+#include "Validate/LabelAddressFixer.hpp"
+
+#include "AST/Decl.hpp"
+#include "AST/Expr.hpp"
+#include "AST/Pass.hpp"
+#include "AST/TranslationUnit.hpp"
+
+#include <set>
+
+namespace Validate {
+
+namespace {
+
+struct LabelFinder {
+	std::set<ast::Label> & labels;
+	LabelFinder( std::set<ast::Label> & labels ) : labels( labels ) {}
+	void previsit( const ast::Stmt * stmt ) {
+		for ( const ast::Label & label : stmt->labels ) {
+			labels.insert( label );
+		}
+	}
+};
+
+struct LabelAddressFixer : public ast::WithGuards {
+	std::set<ast::Label> labels;
+	void previsit( const ast::FunctionDecl * decl );
+	const ast::Expr * postvisit( const ast::AddressExpr * expr );
+};
+
+void LabelAddressFixer::previsit( const ast::FunctionDecl * decl ) {
+	GuardValue( labels );
+	ast::Pass<LabelFinder>::read( decl, labels );
+}
+
+const ast::Expr * LabelAddressFixer::postvisit( const ast::AddressExpr * expr ) {
+	if ( auto inner = expr->arg.as<ast::AddressExpr>() ) {
+		if ( auto nameExpr = inner->arg.as<ast::NameExpr>() ) {
+			ast::Label label( nameExpr->location, nameExpr->name );
+			if ( labels.count( label ) ) {
+				return new ast::LabelAddressExpr( nameExpr->location, std::move( label ) );
+			}
+		}
+	}
+	return expr;
+}
+
+} // namespace
+
+void fixLabelAddresses( ast::TranslationUnit & translationUnit ) {
+	ast::Pass<LabelAddressFixer>::run( translationUnit );
+}
+
+} // namespace Validate
+
+// Local Variables: //
+// tab-width: 4 //
+// mode: c++ //
+// compile-command: "make install" //
+// End: //
Index: src/Validate/LabelAddressFixer.hpp
===================================================================
--- src/Validate/LabelAddressFixer.hpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
+++ src/Validate/LabelAddressFixer.hpp	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -0,0 +1,30 @@
+//
+// Cforall Version 1.0.0 Copyright (C) 2018 University of Waterloo
+//
+// The contents of this file are covered under the licence agreement in the
+// file "LICENCE" distributed with Cforall.
+//
+// LabelAddressFixer.hpp --
+//
+// Author           : Andrew Beach
+// Created On       : Fri Nov 12 16:29:00 2021
+// Last Modified By : Andrew Beach
+// Last Modified On : Fri Nov 12 16:35:00 2021
+// Update Count     : 0
+//
+
+namespace ast {
+	class TranslationUnit;
+}
+
+namespace Validate {
+
+void fixLabelAddresses( ast::TranslationUnit & translationUnit );
+
+}
+
+// Local Variables: //
+// tab-width: 4 //
+// mode: c++ //
+// compile-command: "make install" //
+// End: //
Index: src/Validate/module.mk
===================================================================
--- src/Validate/module.mk	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ src/Validate/module.mk	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -15,4 +15,15 @@
 ###############################################################################
 
-SRC += Validate/HandleAttributes.cc Validate/HandleAttributes.h Validate/FindSpecialDecls.cc Validate/FindSpecialDecls.h
-SRCDEMANGLE += Validate/HandleAttributes.cc Validate/HandleAttributes.h Validate/FindSpecialDecls.cc Validate/FindSpecialDecls.h
+SRC_VALIDATE = \
+	Validate/HandleAttributes.cc \
+	Validate/HandleAttributes.h \
+	Validate/InitializerLength.cpp \
+	Validate/InitializerLength.hpp \
+	Validate/LabelAddressFixer.cpp \
+	Validate/LabelAddressFixer.hpp \
+	Validate/FindSpecialDeclsNew.cpp \
+	Validate/FindSpecialDecls.cc \
+	Validate/FindSpecialDecls.h
+
+SRC += $(SRC_VALIDATE)
+SRCDEMANGLE += $(SRC_VALIDATE)
Index: src/main.cc
===================================================================
--- src/main.cc	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ src/main.cc	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -9,7 +9,7 @@
 // Author           : Peter Buhr and Rob Schluntz
 // Created On       : Fri May 15 23:12:02 2015
-// Last Modified By : Henry Xue
-// Last Modified On : Mon Aug 23 15:42:08 2021
-// Update Count     : 650
+// Last Modified By : Andrew Beach
+// Last Modified On : Fri Nov 12 11:06:00 2021
+// Update Count     : 658
 //
 
@@ -43,4 +43,6 @@
 #include "Common/CodeLocationTools.hpp"     // for forceFillCodeLocations
 #include "Common/CompilerError.h"           // for CompilerError
+#include "Common/DeclStats.hpp"             // for printDeclStats
+#include "Common/ResolvProtoDump.hpp"       // for dumpAsResolverProto
 #include "Common/Stats.h"
 #include "Common/PassVisitor.h"
@@ -51,4 +53,5 @@
 #include "ControlStruct/ExceptDecl.h"       // for translateExcept
 #include "ControlStruct/ExceptTranslate.h"  // for translateEHM
+#include "ControlStruct/FixLabels.hpp"      // for fixLabels
 #include "ControlStruct/Mutate.h"           // for mutate
 #include "GenPoly/Box.h"                    // for box
@@ -62,4 +65,5 @@
 #include "Parser/TypedefTable.h"            // for TypedefTable
 #include "ResolvExpr/AlternativePrinter.h"  // for AlternativePrinter
+#include "ResolvExpr/CandidatePrinter.hpp"  // for printCandidates
 #include "ResolvExpr/Resolver.h"            // for resolve
 #include "SymTab/Validate.h"                // for validate
@@ -68,4 +72,7 @@
 #include "SynTree/Visitor.h"                // for acceptAll
 #include "Tuples/Tuples.h"                  // for expandMemberTuples, expan...
+#include "Validate/FindSpecialDecls.h"      // for findGlobalDecls
+#include "Validate/InitializerLength.hpp"   // for setLengthFromInitializer
+#include "Validate/LabelAddressFixer.hpp"   // for fixLabelAddresses
 #include "Virtual/ExpandCasts.h"            // for expandCasts
 
@@ -314,48 +321,11 @@
 
 		// add the assignment statement after the initialization of a type parameter
-		PASS( "Validate", SymTab::validate( translationUnit, symtabp ) );
-		if ( symtabp ) {
-			deleteAll( translationUnit );
-			return EXIT_SUCCESS;
-		} // if
-
-		if ( expraltp ) {
-			PassVisitor<ResolvExpr::AlternativePrinter> printer( cout );
-			acceptAll( translationUnit, printer );
-			return EXIT_SUCCESS;
-		} // if
-
-		if ( validp ) {
-			dump( translationUnit );
-			return EXIT_SUCCESS;
-		} // if
-
-		PASS( "Translate Throws", ControlStruct::translateThrows( translationUnit ) );
-		PASS( "Fix Labels", ControlStruct::fixLabels( translationUnit ) );
-		PASS( "Fix Names", CodeGen::fixNames( translationUnit ) );
-		PASS( "Gen Init", InitTweak::genInit( translationUnit ) );
-
-		if ( libcfap ) {
-			// generate the bodies of cfa library functions
-			LibCfa::makeLibCfa( translationUnit );
-		} // if
-
-		if ( declstatsp ) {
-			CodeTools::printDeclStats( translationUnit );
-			deleteAll( translationUnit );
-			return EXIT_SUCCESS;
-		} // if
-
-		if ( bresolvep ) {
-			dump( translationUnit );
-			return EXIT_SUCCESS;
-		} // if
+		PASS( "Validate-A", SymTab::validate_A( translationUnit ) );
+		PASS( "Validate-B", SymTab::validate_B( translationUnit ) );
+		PASS( "Validate-C", SymTab::validate_C( translationUnit ) );
+		PASS( "Validate-D", SymTab::validate_D( translationUnit ) );
+		PASS( "Validate-E", SymTab::validate_E( translationUnit ) );
 
 		CodeTools::fillLocations( translationUnit );
-
-		if ( resolvprotop ) {
-			CodeTools::dumpAsResolvProto( translationUnit );
-			return EXIT_SUCCESS;
-		} // if
 
 		if( useNewAST ) {
@@ -366,6 +336,50 @@
 			auto transUnit = convert( move( translationUnit ) );
 
+			forceFillCodeLocations( transUnit );
+
+			PASS( "Set Length From Initializer", Validate::setLengthFromInitializer( transUnit ) );
+			PASS( "Find Global Decls", Validate::findGlobalDecls( transUnit ) );
+			PASS( "Fix Label Address", Validate::fixLabelAddresses( transUnit ) );
+
+			if ( symtabp ) {
+				return EXIT_SUCCESS;
+			} // if
+
+			if ( expraltp ) {
+				ResolvExpr::printCandidates( transUnit );
+				return EXIT_SUCCESS;
+			} // if
+
+			if ( validp ) {
+				dump( move( transUnit ) );
+				return EXIT_SUCCESS;
+			} // if
+
+			PASS( "Translate Throws", ControlStruct::translateThrows( transUnit ) );
+			PASS( "Fix Labels", ControlStruct::fixLabels( transUnit ) );
+			PASS( "Fix Names", CodeGen::fixNames( transUnit ) );
+			PASS( "Gen Init", InitTweak::genInit( transUnit ) );
 			PASS( "Expand Member Tuples" , Tuples::expandMemberTuples( transUnit ) );
-			
+
+			if ( libcfap ) {
+				// Generate the bodies of cfa library functions.
+				LibCfa::makeLibCfa( transUnit );
+			} // if
+
+			if ( declstatsp ) {
+				printDeclStats( transUnit );
+				return EXIT_SUCCESS;
+			} // if
+
+			if ( bresolvep ) {
+				dump( move( transUnit ) );
+				return EXIT_SUCCESS;
+			} // if
+
+			if ( resolvprotop ) {
+				dumpAsResolverProto( transUnit );
+				return EXIT_SUCCESS;
+			} // if
+
 			PASS( "Resolve", ResolvExpr::resolve( transUnit ) );
 			if ( exprp ) {
@@ -377,7 +391,61 @@
 
 			PASS( "Fix Init", InitTweak::fix(transUnit, buildingLibrary()));
+
+			// fix ObjectDecl - replaces ConstructorInit nodes
+			if ( ctorinitp ) {
+				dump( move( transUnit ) );
+				return EXIT_SUCCESS;
+			} // if
+
+			// Currently not working due to unresolved issues with UniqueExpr
+			PASS( "Expand Unique Expr", Tuples::expandUniqueExpr( transUnit ) ); // xxx - is this the right place for this? want to expand ASAP so tha, sequent passes don't need to worry about double-visiting a unique expr - needs to go after InitTweak::fix so that copy constructed return declarations are reused
 			translationUnit = convert( move( transUnit ) );
 		} else {
+			PASS( "Validate-F", SymTab::validate_F( translationUnit ) );
+
+			if ( symtabp ) {
+				deleteAll( translationUnit );
+				return EXIT_SUCCESS;
+			} // if
+
+			if ( expraltp ) {
+				PassVisitor<ResolvExpr::AlternativePrinter> printer( cout );
+				acceptAll( translationUnit, printer );
+				return EXIT_SUCCESS;
+			} // if
+
+			if ( validp ) {
+				dump( translationUnit );
+				return EXIT_SUCCESS;
+			} // if
+
+			PASS( "Translate Throws", ControlStruct::translateThrows( translationUnit ) );
+			PASS( "Fix Labels", ControlStruct::fixLabels( translationUnit ) );
+			PASS( "Fix Names", CodeGen::fixNames( translationUnit ) );
+			PASS( "Gen Init", InitTweak::genInit( translationUnit ) );
 			PASS( "Expand Member Tuples" , Tuples::expandMemberTuples( translationUnit ) );
+
+			if ( libcfap ) {
+				// Generate the bodies of cfa library functions.
+				LibCfa::makeLibCfa( translationUnit );
+			} // if
+
+			if ( declstatsp ) {
+				CodeTools::printDeclStats( translationUnit );
+				deleteAll( translationUnit );
+				return EXIT_SUCCESS;
+			} // if
+
+			if ( bresolvep ) {
+				dump( translationUnit );
+				return EXIT_SUCCESS;
+			} // if
+
+			CodeTools::fillLocations( translationUnit );
+
+			if ( resolvprotop ) {
+				CodeTools::dumpAsResolvProto( translationUnit );
+				return EXIT_SUCCESS;
+			} // if
 
 			PASS( "Resolve", ResolvExpr::resolve( translationUnit ) );
@@ -388,13 +456,13 @@
 
 			PASS( "Fix Init", InitTweak::fix( translationUnit, buildingLibrary() ) );
+
+			// fix ObjectDecl - replaces ConstructorInit nodes
+			if ( ctorinitp ) {
+				dump ( translationUnit );
+				return EXIT_SUCCESS;
+			} // if
+
+			PASS( "Expand Unique Expr", Tuples::expandUniqueExpr( translationUnit ) ); // xxx - is this the right place for this? want to expand ASAP so tha, sequent passes don't need to worry about double-visiting a unique expr - needs to go after InitTweak::fix so that copy constructed return declarations are reused
 		}
-
-		// fix ObjectDecl - replaces ConstructorInit nodes
-		if ( ctorinitp ) {
-			dump ( translationUnit );
-			return EXIT_SUCCESS;
-		} // if
-
-		PASS( "Expand Unique Expr", Tuples::expandUniqueExpr( translationUnit ) ); // xxx - is this the right place for this? want to expand ASAP so tha, sequent passes don't need to worry about double-visiting a unique expr - needs to go after InitTweak::fix so that copy constructed return declarations are reused
 
 		PASS( "Translate Tries" , ControlStruct::translateTries( translationUnit ) );
@@ -443,5 +511,6 @@
 		PASS( "Code Gen", CodeGen::generate( translationUnit, *output, ! genproto, prettycodegenp, true, linemarks ) );
 
-		CodeGen::FixMain::fix( *output, (PreludeDirector + "/bootloader.c").c_str() );
+		CodeGen::FixMain::fix( translationUnit, *output,
+				(PreludeDirector + "/bootloader.c").c_str() );
 		if ( output != &cout ) {
 			delete output;
Index: tests/.expect/declarationSpecifier.x64.txt
===================================================================
--- tests/.expect/declarationSpecifier.x64.txt	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ tests/.expect/declarationSpecifier.x64.txt	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -1132,5 +1132,5 @@
 char **_X13cfa_args_argvPPc_1;
 char **_X13cfa_args_envpPPc_1;
-signed int _X17cfa_main_returnedi_1 = ((signed int )0);
+__attribute__ ((weak)) extern signed int _X17cfa_main_returnedi_1;
 signed int main(signed int _X4argci_1, char **_X4argvPPc_1, char **_X4envpPPc_1){
     __attribute__ ((unused)) signed int _X12_retval_maini_1;
@@ -1149,6 +1149,9 @@
     signed int _tmp_cp_ret6;
     signed int _X3reti_2 = (((void)(_tmp_cp_ret6=invoke_main(_X4argci_1, _X4argvPPc_1, _X4envpPPc_1))) , _tmp_cp_ret6);
-    {
-        ((void)(_X17cfa_main_returnedi_1=((signed int )1)));
+    if ( ((&_X17cfa_main_returnedi_1)!=((signed int *)0)) ) {
+        {
+            ((void)(_X17cfa_main_returnedi_1=((signed int )1)));
+        }
+
     }
 
Index: tests/.expect/declarationSpecifier.x86.txt
===================================================================
--- tests/.expect/declarationSpecifier.x86.txt	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ tests/.expect/declarationSpecifier.x86.txt	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -1132,5 +1132,5 @@
 char **_X13cfa_args_argvPPc_1;
 char **_X13cfa_args_envpPPc_1;
-signed int _X17cfa_main_returnedi_1 = ((signed int )0);
+__attribute__ ((weak)) extern signed int _X17cfa_main_returnedi_1;
 signed int main(signed int _X4argci_1, char **_X4argvPPc_1, char **_X4envpPPc_1){
     __attribute__ ((unused)) signed int _X12_retval_maini_1;
@@ -1149,6 +1149,9 @@
     signed int _tmp_cp_ret6;
     signed int _X3reti_2 = (((void)(_tmp_cp_ret6=invoke_main(_X4argci_1, _X4argvPPc_1, _X4envpPPc_1))) , _tmp_cp_ret6);
-    {
-        ((void)(_X17cfa_main_returnedi_1=((signed int )1)));
+    if ( ((&_X17cfa_main_returnedi_1)!=((signed int *)0)) ) {
+        {
+            ((void)(_X17cfa_main_returnedi_1=((signed int )1)));
+        }
+
     }
 
Index: tests/.expect/gccExtensions.x64.txt
===================================================================
--- tests/.expect/gccExtensions.x64.txt	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ tests/.expect/gccExtensions.x64.txt	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -324,5 +324,5 @@
 char **_X13cfa_args_argvPPc_1;
 char **_X13cfa_args_envpPPc_1;
-signed int _X17cfa_main_returnedi_1 = ((signed int )0);
+__attribute__ ((weak)) extern signed int _X17cfa_main_returnedi_1;
 signed int main(signed int _X4argci_1, char **_X4argvPPc_1, char **_X4envpPPc_1){
     __attribute__ ((unused)) signed int _X12_retval_maini_1;
@@ -341,6 +341,9 @@
     signed int _tmp_cp_ret6;
     signed int _X3reti_2 = (((void)(_tmp_cp_ret6=invoke_main(_X4argci_1, _X4argvPPc_1, _X4envpPPc_1))) , _tmp_cp_ret6);
-    {
-        ((void)(_X17cfa_main_returnedi_1=((signed int )1)));
+    if ( ((&_X17cfa_main_returnedi_1)!=((signed int *)0)) ) {
+        {
+            ((void)(_X17cfa_main_returnedi_1=((signed int )1)));
+        }
+
     }
 
Index: tests/.expect/gccExtensions.x86.txt
===================================================================
--- tests/.expect/gccExtensions.x86.txt	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ tests/.expect/gccExtensions.x86.txt	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -302,5 +302,5 @@
 char **_X13cfa_args_argvPPc_1;
 char **_X13cfa_args_envpPPc_1;
-signed int _X17cfa_main_returnedi_1 = ((signed int )0);
+__attribute__ ((weak)) extern signed int _X17cfa_main_returnedi_1;
 signed int main(signed int _X4argci_1, char **_X4argvPPc_1, char **_X4envpPPc_1){
     __attribute__ ((unused)) signed int _X12_retval_maini_1;
@@ -319,6 +319,9 @@
     signed int _tmp_cp_ret6;
     signed int _X3reti_2 = (((void)(_tmp_cp_ret6=invoke_main(_X4argci_1, _X4argvPPc_1, _X4envpPPc_1))) , _tmp_cp_ret6);
-    {
-        ((void)(_X17cfa_main_returnedi_1=((signed int )1)));
+    if ( ((&_X17cfa_main_returnedi_1)!=((signed int *)0)) ) {
+        {
+            ((void)(_X17cfa_main_returnedi_1=((signed int )1)));
+        }
+
     }
 
Index: tests/concurrent/semaphore.cfa
===================================================================
--- tests/concurrent/semaphore.cfa	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ tests/concurrent/semaphore.cfa	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -2,4 +2,5 @@
 #include <locks.hfa>
 #include <thread.hfa>
+#include <mutex_stmt.hfa>
 
 enum { num_blockers = 17, num_unblockers = 13 };
@@ -28,5 +29,5 @@
 		thrash();
 		P(ben);
-		if(((thread&)this).seqable.next != 0p) sout | acquire |"Link not invalidated";
+		if(((thread&)this).seqable.next != 0p) mutex(sout) sout | "Link not invalidated";
 		thrash();
 	}
Index: tests/concurrent/sleep.cfa
===================================================================
--- tests/concurrent/sleep.cfa	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ tests/concurrent/sleep.cfa	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -1,4 +1,5 @@
 #include <fstream.hfa>
 #include <thread.hfa>
+#include <mutex_stmt.hfa>
 #include <time.hfa>
 
@@ -29,5 +30,5 @@
 
 int main() {
-	sout | acquire | "start";
+	mutex( sout ) sout | "start";
 	{
 		slow_sleeper slow;
@@ -36,5 +37,5 @@
 		yield();
 	}
-	sout | acquire | "done";
+	mutex( sout ) sout | "done";
 }
 
Index: tests/io/io-acquire.cfa
===================================================================
--- tests/io/io-acquire.cfa	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ tests/io/io-acquire.cfa	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -10,10 +10,11 @@
 // Created On       : Mon Mar  1 18:40:09 2021
 // Last Modified By : Peter A. Buhr
-// Last Modified On : Tue Apr 27 11:49:34 2021
-// Update Count     : 18
+// Last Modified On : Wed Oct  6 18:04:58 2021
+// Update Count     : 72
 // 
 
 #include <fstream.hfa>
 #include <thread.hfa>
+#include <mutex_stmt.hfa>
 
 thread T {};
@@ -21,9 +22,8 @@
 	// output from parallel threads should not be scrambled
 
-	for ( 100 ) {										// local protection
-		sout | acquire | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
+	for ( 100 ) {										// expression protection
+		mutex(sout) sout | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
 	}
-	{													// global protection (RAII)
-		osacquire acq = { sout };
+	mutex( sout ) {										// statement protection
 		for ( 100 ) {
 			sout | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
@@ -31,9 +31,17 @@
 	}
 	{													// duplicate protection demonstrating recursive lock
-		osacquire acq = { sout };
-		for ( 100 ) {
-			osacquire acq = { sout };
-			sout | acquire | 1 | 2 | 3 | 4 | 5 | acquire | 6 | 7 | 8 | 9;
-			sout | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
+		ofstream & h1( ofstream & os ) {				// helper
+			mutex( os ) return os | 1 | 2 | 3 | 4;		// unnecessary mutex
+		}
+		ofstream & h2( ofstream & os ) {				// helper
+			mutex( os ) return os | 6 | 7 | 8 | 9;		// unnecessary mutex
+		}
+		mutex( sout ) {									// unnecessary mutex
+			for ( 100 ) {
+				mutex( sout ) {
+					sout | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
+					sout | h1 | 5 | h2;					// refactored code
+				}
+			}
 		}
 	}
@@ -42,9 +50,8 @@
 
 	int a, b, c, d, e, f, g, h, i;
-	for ( 100 ) {										// local protection
-		sin | acquire | a | b | c | d | e | f | g | h | i;
+	for ( 100 ) {										// expression protection
+		mutex(sin) sin | a | b | c | d | e | f | g | h | i;
 	}
-	{													// global protection (RAII)
-		isacquire acq = { sin };
+	mutex( sin ) {										// statement protection
 		for ( 100 ) {
 			sin  | a | b | c | d | e | f | g | h | i;
@@ -52,9 +59,17 @@
 	}
 	{													// duplicate protection demonstrating recursive lock
-		isacquire acq = { sin };
-		for ( 100 ) {
-			isacquire acq = { sin };
-			sin | acquire | a | b | c | d | e | acquire | f | g | h | i;
-			sin | a | b | c | d | e | f | g | h | i;
+		ifstream & h1( ifstream & is ) {				// helper
+			mutex( is ) return is | a | b | c | d;		// unnecessary mutex
+		}
+		ifstream & h2( ifstream & is ) {				// helper
+			mutex( is ) return is | f | g | h | i;		// unnecessary mutex
+		}
+		mutex( sin ) {									// unnecessary mutex
+			for ( 5 ) {
+				mutex( sin ) {
+					sin  | a | b | c | d | e | f | g | h | i;
+					sin  | h1 | e | h2;					// refactored code
+				}
+			}
 		}
 	}
Index: tests/linking/io-acquire.cfa
===================================================================
--- tests/linking/io-acquire.cfa	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ tests/linking/io-acquire.cfa	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -17,12 +17,13 @@
 #include <fstream.hfa>
 #include <stdlib.hfa>
+#include <mutex_stmt.hfa>
 
 int main() {
 	int i;
 	if(threading_enabled()) {
-		stdout | acquire | "YES";
+		mutex( stdout ) stdout | "YES";
 		stdin | i;
 	} else {
-		stdout | acquire | "NO";
+		mutex( stdout ) stdout | "NO";
 		stdin | i;
 	}
Index: tests/pybin/test_run.py
===================================================================
--- tests/pybin/test_run.py	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ tests/pybin/test_run.py	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -65,12 +65,12 @@
 	def toString( cls, retcode, duration ):
 		if settings.generating :
-			if   retcode == TestResult.SUCCESS: 	text = "Done   "
-			elif retcode == TestResult.TIMEOUT: 	text = "TIMEOUT"
-			else :						text = "ERROR code %d" % retcode
+			if   retcode == TestResult.SUCCESS: 	key = 'pass'; text = "Done   "
+			elif retcode == TestResult.TIMEOUT: 	key = 'time'; text = "TIMEOUT"
+			else :	key = 'fail';	text = "ERROR code %d" % retcode
 		else :
-			if   retcode == TestResult.SUCCESS: 	text = "PASSED "
-			elif retcode == TestResult.TIMEOUT: 	text = "TIMEOUT"
-			else :						text = "FAILED with code %d" % retcode
+			if   retcode == TestResult.SUCCESS: 	key = 'pass'; text = "PASSED "
+			elif retcode == TestResult.TIMEOUT: 	key = 'time'; text = "TIMEOUT"
+			else :	key = 'fail';	text = "FAILED with code %d" % retcode
 
 		text += "    C%s - R%s" % (fmtDur(duration[0]), fmtDur(duration[1]))
-		return text
+		return key, text
Index: tests/test.py
===================================================================
--- tests/test.py	(revision 94647b0b7573effbb2f73c03b06886b22af1249a)
+++ tests/test.py	(revision 7770cc8a0981a8a6485f2ec3ee49b6a51f60afde)
@@ -257,5 +257,5 @@
 
 		# update output based on current action
-		result_txt = TestResult.toString( retcode, duration )
+		result_key, result_txt = TestResult.toString( retcode, duration )
 
 		#print result with error if needed
@@ -265,7 +265,7 @@
 			text = text + '\n' + error
 
-		return retcode == TestResult.SUCCESS, text
+		return retcode == TestResult.SUCCESS, result_key, text
 	except KeyboardInterrupt:
-		return False, ""
+		return False, 'keybrd', ""
 	# except Exception as ex:
 	# 	print("Unexpected error in worker thread running {}: {}".format(t.target(), ex), file=sys.stderr)
@@ -283,4 +283,6 @@
 
 	failed = False
+	rescnts = {	'pass': 0, 'fail': 0, 'time': 0, 'keybrd': 0 }
+	other = 0
 
 	# for each test to run
@@ -294,5 +296,10 @@
 		)
 
-		for i, (succ, txt) in enumerate(timed(results, timeout = settings.timeout.total), 1) :
+		for i, (succ, code, txt) in enumerate(timed(results, timeout = settings.timeout.total), 1) :
+			if code in rescnts.keys():
+				rescnts[code] += 1
+			else:
+				other += 1
+
 			if not succ :
 				failed = True
@@ -319,4 +326,6 @@
 	# clean the workspace
 	make('clean', output_file=subprocess.DEVNULL, error=subprocess.DEVNULL)
+
+	print("{} passes, {} failures, {} timeouts, {} cancelled, {} other".format(rescnts['pass'], rescnts['fail'], rescnts['time'], rescnts['keybrd'], other))
 
 	return failed
@@ -443,5 +452,4 @@
 			failed = run_tests(local_tests, options.jobs)
 			if failed:
-				result = 1
 				if not settings.continue_:
 					break
