Index: benchmark/io/sendfile/consumer.c
===================================================================
--- benchmark/io/sendfile/consumer.c	(revision f53afafb9048d059a73cca286f45a8bfc99cc3e4)
+++ benchmark/io/sendfile/consumer.c	(revision f53afafb9048d059a73cca286f45a8bfc99cc3e4)
@@ -0,0 +1,142 @@
+// Simple sink program that opens a tcp socket on a random port
+// and then reads everything the socket has to write and then ends.
+
+#define _GNU_SOURCE
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <errno.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+enum {
+	USAGE_ERROR = 1,
+	SOCKET_ERROR,
+	BIND_INUSE,
+	BIND_ERROR,
+	GETNAME_ERROR,
+	LISTEN_ERROR,
+	ACCEPT_ERROR,
+	READ_ERROR
+};
+
+enum { buffer_len = 10240 };
+char buffer[buffer_len];
+
+enum { TIMEGRAN = 1000000000LL };
+
+int main(int argc, char * argv[]) {
+	int port = 0;
+	switch(argc) {
+	case 1: break;
+	case 2:
+		{
+			const char * const str = argv[1];
+			char* endptr;
+			unsigned long long ret = strtoul(str, &endptr, 10);
+			if('\0' != *endptr) { fprintf( stderr, "Invalid argument \"%s\" (not a number)\n", str ); goto USAGE;}
+			if(errno == ERANGE || ret > 65535)  { fprintf( stderr, "Invalid argument \"%s\" (value too large)\n", str ); goto USAGE;}
+
+			port = ret;
+			break;
+		}
+	USAGE:
+	default:
+		fprintf( stderr, "USAGE: %s [port]\n", argv[0] );
+		exit( USAGE_ERROR );
+	}
+
+	printf( "%d: Listening on port %d\n", getpid(), port );
+	int listenfd = socket(AF_INET, SOCK_STREAM, 0);
+	if(listenfd < 0) {
+		fprintf( stderr, "socket error: (%d) %s\n", (int)errno, strerror(errno) );
+		exit( SOCKET_ERROR );
+	}
+
+	int ret = 0;
+	struct sockaddr_in address;
+	socklen_t addrlen = sizeof(address);
+	memset( (char *)&address, '\0', addrlen );
+	address.sin_family = AF_INET;
+	address.sin_addr.s_addr = htonl( INADDR_ANY );
+	address.sin_port = htons( port );
+
+	ret = bind( listenfd, (struct sockaddr *) &address, addrlen );
+	if(ret < 0) {
+		if(errno == EADDRINUSE) {
+			fprintf( stderr, "Port already in use in non-interactive mode. Aborting\n" );
+			exit( BIND_INUSE );
+		}
+		fprintf( stderr, "bind error: (%d) %s\n", (int)errno, strerror(errno) );
+		exit( BIND_ERROR );
+	}
+
+	ret = getsockname( listenfd, (struct sockaddr *) &address, &addrlen );
+	if(ret < 0) {
+		fprintf( stderr, "getname error: (%d) %s\n", (int)errno, strerror(errno) );
+		exit(GETNAME_ERROR);
+	}
+
+	printf( "actial port: %d\n", ntohs(address.sin_port) );
+
+	ret = listen( listenfd, 1 );
+	if(ret < 0) {
+		fprintf( stderr, "listen error: (%d) %s\n", (int)errno, strerror(errno) );
+		exit( 5 );
+	}
+
+	for(;;) {
+		struct sockaddr_in cli_addr;
+		__socklen_t clilen = sizeof(cli_addr);
+		int fd = accept( listenfd, (struct sockaddr *) &cli_addr, &clilen );
+		if(fd < 0) {
+			fprintf( stderr, "accept error: (%d) %s\n", (int)errno, strerror(errno) );
+			exit( ACCEPT_ERROR );
+		}
+
+		int error = 0;
+		size_t calls = 0;
+		size_t bytes = 0;
+
+		struct timespec after, before;
+
+		clock_gettime(CLOCK_MONOTONIC, &before);
+
+		for(;;) {
+			ret = recv(fd, buffer, buffer_len, 0);
+			if(ret == 0 ) goto EXIT;
+			if(ret < 0 ) {
+				if( errno == EAGAIN || errno == EWOULDBLOCK) continue;
+				if( errno == ECONNRESET ) { printf("Connection reset\n"); goto EXIT; }
+				if( errno == EPIPE ) { printf("Pipe closed\n"); goto EXIT; }
+				fprintf( stderr, "accept error: (%d) %s\n", (int)errno, strerror(errno) );
+				error = READ_ERROR;
+				goto EXIT;
+			}
+			calls++;
+			bytes += ret;
+		}
+		EXIT:;
+
+		clock_gettime(CLOCK_MONOTONIC, &after);
+
+		uint64_t tb = ((int64_t)before.tv_sec * TIMEGRAN) + before.tv_nsec;
+		uint64_t ta = ((int64_t)after.tv_sec * TIMEGRAN) + after.tv_nsec;
+		double secs = ((double)ta - tb) / TIMEGRAN;
+
+		printf("Received %'zu bytes in %'zu reads, %f seconds\n", bytes, calls, secs);
+		printf(" - %'3.3f bytes per second\n", (((double)bytes) / secs));
+		printf(" - %'3.3f bytes per calls\n", (((double)bytes) / calls));
+
+		close(fd);
+		if(error != 0) exit( error );
+	}
+	close(listenfd);
+	return 0;
+}
Index: benchmark/io/sendfile/producer.c
===================================================================
--- benchmark/io/sendfile/producer.c	(revision f53afafb9048d059a73cca286f45a8bfc99cc3e4)
+++ benchmark/io/sendfile/producer.c	(revision f53afafb9048d059a73cca286f45a8bfc99cc3e4)
@@ -0,0 +1,509 @@
+// programs that sends a file many times as fast as it can
+// compares sendfile to splice
+
+#define _GNU_SOURCE
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <errno.h>
+#include <locale.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <sys/ioctl.h>
+#include <sys/sendfile.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <fcntl.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include <liburing.h>
+
+enum {
+	USAGE_ERROR = 1,
+	HOST_ERROR,
+	PIPE_ERROR,
+	FSTAT_ERROR,
+	SOCKET_ERROR,
+	CONNECT_ERROR,
+	SENDFILE_ERROR,
+	SPLICEIN_ERROR,
+	SPLICEOUT_ERROR,
+	URINGWAIT_ERROR
+};
+
+enum { buffer_len = 10240 };
+char buffer[buffer_len];
+
+enum { TIMEGRAN = 1000000000LL, TIMES = 100000 };
+
+int pipefd[2];
+struct io_uring ring;
+
+char * buf;
+
+struct stats {
+	size_t calls;
+	size_t bytes;
+	struct {
+		struct {
+			size_t cnt;
+			size_t bytes;
+		} r, w;
+	} shorts;
+};
+static void my_sendfile(int out, int in, size_t size, struct stats *);
+static void my_splice  (int out, int in, size_t size, struct stats *);
+static void my_iouring (int out, int in, size_t size, struct stats *);
+static void my_ringlink(int out, int in, size_t size, struct stats *);
+static void my_readwrit(int out, int in, size_t size, struct stats *);
+typedef void (*sender_t)(int out, int in, size_t size, struct stats *);
+
+static void run(sender_t sender, struct addrinfo * addr, int infd, size_t size);
+
+int main(int argc, char * argv[]) {
+	setlocale(LC_ALL, "");
+	const char * file_path;
+	struct addrinfo * addr;
+	int file_fd;
+	int ret;
+	switch(argc) {
+	case 3:
+		{
+			// Open the file
+			const char * const path = argv[2];
+			ret = open(path, 0, O_RDONLY);
+			if(ret < 0) {
+				fprintf( stderr, "cannot open file '%s': %s\n\n", path, strerror(errno) );
+				goto USAGE;
+			}
+
+			file_path = path;
+			file_fd = ret;
+
+
+			// connect to the address
+			char * state = 0;
+			char * str = argv[1];
+			const char * const host = strtok_r(str, ":", &state);
+			if(NULL == host) {
+				fprintf( stderr, "Invalid host:port specification, no host.\n\n" );
+				goto USAGE;
+			}
+
+			const char * const port = strtok_r(NULL, ":", &state);
+			if(NULL == port) {
+				fprintf( stderr, "Invalid host:port specification, no port.\n\n" );
+				goto USAGE;
+			}
+
+			printf("looking up '%s:%s'\n", host, port);
+
+			struct addrinfo hints = {};
+			struct addrinfo * pResultList = NULL;
+
+			hints.ai_family = AF_INET;
+			hints.ai_socktype = SOCK_STREAM;
+			hints.ai_flags = AI_NUMERICSERV;
+
+			ret = getaddrinfo(host, port, &hints, &pResultList);
+
+			switch(ret) {
+			case 0:
+				addr = pResultList;
+				goto DONE;
+
+			case EAI_ADDRFAMILY:
+				fprintf( stderr, "The specified network host does not have any network addresses in the requested address family.\n\n" );
+				break;
+
+			case EAI_AGAIN:
+				fprintf( stderr, "The name server returned a temporary failure indication. Try again later.\n\n" );
+				exit( HOST_ERROR );
+
+			case EAI_BADFLAGS:
+				fprintf( stderr, "hints.ai_flags  contains invalid flags; or, hints.ai_flags included AI_CANONNAME and name was NULL.\n\n" );
+				exit( HOST_ERROR );
+
+			case EAI_FAIL:
+				fprintf( stderr, "The name server returned a permanent failure indication.\n\n" );
+				break;
+
+			case EAI_FAMILY:
+				fprintf( stderr, "The requested address family is not supported.\n\n" );
+				exit( HOST_ERROR );
+
+			case EAI_MEMORY:
+				fprintf( stderr, "Out of memory.\n\n" );
+				exit( HOST_ERROR );
+
+			case EAI_NODATA:
+				fprintf( stderr, "The specified network host exists, but does not have any network addresses defined.\n\n" );
+				break;
+
+			case EAI_NONAME:
+				fprintf( stderr, "The unkonwn host or invalid port.\n\n" );
+				break;
+
+			case EAI_SERVICE:
+				fprintf( stderr, "The requested service is not available for the requested socket type.\n\n" );
+				break;
+
+			case EAI_SOCKTYPE:
+				fprintf( stderr, "The requested  socket  type  is  not  supported.\n\n" );
+				exit( HOST_ERROR );
+
+			case EAI_SYSTEM:
+				// Other system error, check errno for details.
+			default:
+				fprintf( stderr, "Unnown hostname error: (%d) %s\n\n", (int)errno, strerror(errno) );
+				exit( HOST_ERROR );
+			}
+			if(pResultList) freeaddrinfo(pResultList);
+			goto USAGE;
+		}
+	USAGE:
+	default:
+		fprintf( stderr, "USAGE: %s host:port file\n", argv[0] );
+		exit( USAGE_ERROR );
+	}
+
+	DONE:
+
+	io_uring_queue_init(16, &ring, 0);
+
+	size_t file_size = 0;
+	{
+		struct stat buf;
+   		ret = fstat(file_fd, &buf);
+		if(0 != ret) {
+			fprintf( stderr, "fstat error: (%d) %s\n\n", (int)errno, strerror(errno) );
+			exit( FSTAT_ERROR );
+		}
+		file_size = buf.st_size;
+	}
+
+	{
+		char addr_str[INET_ADDRSTRLEN];
+		struct sockaddr_in * address = (struct sockaddr_in *) addr->ai_addr;
+		inet_ntop( AF_INET, &address->sin_addr, addr_str, INET_ADDRSTRLEN );
+		printf("sending '%s' (%zu bytes) to '%s:%i'\n", file_path, file_size, addr_str, ntohs(address->sin_port));
+	}
+
+	ret = pipe(pipefd);
+	if( ret < 0 ) {
+		fprintf( stderr, "pipe error: (%d) %s\n\n", (int)errno, strerror(errno) );
+		exit( PIPE_ERROR );
+	}
+
+	buf = malloc(file_size);
+
+	printf("--- read + write ---\n");
+	run(my_readwrit, addr, file_fd, file_size);
+	printf("--- splice ---\n");
+	run(my_splice  , addr, file_fd, file_size);
+	printf("--- sendfile ---\n");
+	run(my_sendfile, addr, file_fd, file_size);
+	printf("--- io_uring ---\n");
+	run(my_iouring, addr, file_fd, file_size);
+	printf("--- io_uring + link ---\n");
+	run(my_ringlink, addr, file_fd, file_size);
+
+	close(pipefd[0]);
+	close(pipefd[1]);
+	close(file_fd);
+	return 0;
+}
+
+static void run(sender_t sender, struct addrinfo * addr, int infd, size_t size) {
+
+	int sock = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
+      if(sock < 0) {
+		fprintf( stderr, "socket error: (%d) %s\n\n", (int)errno, strerror(errno) );
+		exit( SOCKET_ERROR );
+      }
+
+      int ret = connect(sock, addr->ai_addr, addr->ai_addrlen);
+      if(ret < 0) {
+            fprintf( stderr, "connect error: (%d) %s\n\n", (int)errno, strerror(errno) );
+		exit( CONNECT_ERROR );
+      }
+
+	struct stats st;
+	st.calls = 0;
+	st.bytes = 0;
+	st.shorts.r.cnt = 0;
+	st.shorts.r.bytes = 0;
+	st.shorts.w.cnt = 0;
+	st.shorts.w.bytes = 0;
+
+	struct timespec after, before;
+
+	clock_gettime(CLOCK_MONOTONIC, &before);
+
+	for(long long int i = 0; i < TIMES; i++) {
+		sender( sock, infd, size, &st );
+	}
+
+	clock_gettime(CLOCK_MONOTONIC, &after);
+
+	close(sock);
+
+	uint64_t tb = ((int64_t)before.tv_sec * TIMEGRAN) + before.tv_nsec;
+	uint64_t ta = ((int64_t)after.tv_sec * TIMEGRAN) + after.tv_nsec;
+	double secs = ((double)ta - tb) / TIMEGRAN;
+
+	printf("Sent %'zu bytes in %'zu files, %f seconds\n", st.bytes, st.calls, secs);
+	printf(" - %'3.3f bytes per second\n", (((double)st.bytes) / secs));
+	printf(" - %'f seconds per file\n", secs / st.calls);
+	printf(" - %'3.3f bytes per calls\n", (((double)st.bytes) / st.calls));
+	if(st.shorts.r.cnt ){
+		printf(" - %'zu short reads\n", st.shorts.r.cnt);
+		printf(" - %'3.3f bytes per short read\n", (((double)st.shorts.r.bytes) / st.shorts.r.cnt));
+	} else printf("No short reads\n");
+	if(st.shorts.w.cnt ){
+		printf(" - %'zu short reads\n", st.shorts.w.cnt);
+		printf(" - %'3.3f bytes per short read\n", (((double)st.shorts.w.bytes) / st.shorts.w.cnt));
+	} else printf("No short writes\n");
+}
+
+static void my_sendfile(int out, int in, size_t size, struct stats * st) {
+	off_t off = 0;
+	for(;;) {
+
+		ssize_t ret = sendfile(out, in, &off, size);
+		if(ret < 0) {
+			fprintf( stderr, "connect error: (%d) %s\n\n", (int)errno, strerror(errno) );
+			exit( SENDFILE_ERROR );
+		}
+
+		st->calls++;
+		st->bytes += ret;
+		off += ret;
+		size -= ret;
+		if( size == 0 ) return;
+		st->shorts.r.cnt++;
+		st->shorts.r.bytes += ret;
+	}
+}
+
+static void my_splice  (int out, int in, size_t size, struct stats * st) {
+	unsigned flags = 0; //SPLICE_F_MOVE; // | SPLICE_F_MORE;
+	off_t offset = 0;
+	size_t writes = 0;
+	for(;;) {
+		ssize_t reti = 0;
+		reti = splice(in, &offset, pipefd[1], NULL, size, flags);
+		if( reti < 0 ) {
+			fprintf( stderr, "splice in error: (%d) %s\n\n", (int)errno, strerror(errno) );
+			exit( SPLICEIN_ERROR );
+		}
+
+		size -= reti;
+		size_t in_pipe = reti;
+		for(;;) {
+			ssize_t reto = 0;
+			reto = splice(pipefd[0], NULL, out, NULL, in_pipe, flags);
+			if( reto < 0 ) {
+				fprintf( stderr, "splice out error: (%d) %s\n\n", (int)errno, strerror(errno) );
+				exit( SPLICEOUT_ERROR );
+			}
+			in_pipe -= reto;
+			writes += reto;
+			if(0 == in_pipe) break;
+			st->shorts.w.cnt++;
+			st->shorts.w.bytes += reto;
+		}
+		if(0 == size) break;
+		st->shorts.r.cnt++;
+		st->shorts.r.bytes += reti;
+	}
+	st->calls++;
+	st->bytes += writes;
+}
+
+static ssize_t naive_splice(int fd_in, loff_t *off_in, int fd_out, loff_t *off_out, size_t len, unsigned int flags) {
+	struct io_uring_sqe * sqe = io_uring_get_sqe(&ring);
+
+	io_uring_prep_splice(sqe, fd_in, NULL != off_in ? *off_in: -1, fd_out, NULL != off_out ? *off_out: -1, len, flags);
+
+	io_uring_submit(&ring);
+
+	struct io_uring_cqe * cqe = NULL;
+	/* wait for the sqe to complete */
+	int ret = io_uring_wait_cqe_nr(&ring, &cqe, 1);
+
+	/* read and process cqe event */
+	switch(ret) {
+	case 0:
+		{
+			ssize_t val = cqe->res;
+			if( cqe->res < 0 ) {
+				printf("Completion Error : %s\n", strerror( -cqe->res ));
+				return EXIT_FAILURE;
+			}
+			io_uring_cqe_seen(&ring, cqe);
+			return val;
+		}
+	default:
+		fprintf( stderr, "io_uring_wait error: (%d) %s\n\n", (int)-ret, strerror(-ret) );
+		exit( URINGWAIT_ERROR );
+	}
+}
+
+static void my_iouring (int out, int in, size_t size, struct stats * st) {
+	unsigned flags = 0; //SPLICE_F_MOVE; // | SPLICE_F_MORE;
+	off_t offset = 0;
+	size_t writes = 0;
+	for(;;) {
+		ssize_t reti = 0;
+		reti = naive_splice(in, &offset, pipefd[1], NULL, size, flags);
+		if( reti < 0 ) {
+			fprintf( stderr, "splice in error: (%d) %s\n\n", (int)errno, strerror(errno) );
+			exit( SPLICEIN_ERROR );
+		}
+
+		size -= reti;
+		size_t in_pipe = reti;
+		for(;;) {
+			ssize_t reto = 0;
+			reto = naive_splice(pipefd[0], NULL, out, NULL, in_pipe, flags);
+			if( reto < 0 ) {
+				fprintf( stderr, "splice out error: (%d) %s\n\n", (int)errno, strerror(errno) );
+				exit( SPLICEOUT_ERROR );
+			}
+			in_pipe -= reto;
+			writes += reto;
+			if(0 == in_pipe) break;
+			st->shorts.w.cnt++;
+			st->shorts.w.bytes += reto;
+		}
+		if(0 == size) break;
+		st->shorts.r.cnt++;
+		st->shorts.r.bytes += reti;
+	}
+	st->calls++;
+	st->bytes += writes;
+}
+
+static void my_ringlink(int out, int in, size_t size, struct stats * st) {
+	enum { SPLICE_IN, SPLICE_OUT };
+
+	size_t in_pipe = size;
+	off_t offset = 0;
+	bool has_in = false;
+	bool has_out = false;
+	while(true) {
+		if(!has_in && size > 0) {
+			struct io_uring_sqe * sqe = io_uring_get_sqe(&ring);
+			io_uring_prep_splice(sqe, in, offset, pipefd[1], -1, size, 0);
+			sqe->user_data = SPLICE_IN;
+			sqe->flags = IOSQE_IO_LINK;
+			has_in = true;
+		}
+		if(!has_out) {
+			struct io_uring_sqe * sqe = io_uring_get_sqe(&ring);
+			io_uring_prep_splice(sqe, pipefd[0], -1, out, -1, in_pipe, 0);
+			sqe->user_data = SPLICE_OUT;
+			has_out = true;
+		}
+
+		int ret = io_uring_submit_and_wait(&ring, 1);
+		if(ret < 0) {
+			fprintf( stderr, "io_uring_submit error: (%d) %s\n\n", (int)-ret, strerror(-ret) );
+			exit( URINGWAIT_ERROR );
+		}
+
+		/* poll the cq and count how much polling we did */
+		while(true) {
+			struct io_uring_cqe * cqe = NULL;
+			/* wait for the sqe to complete */
+			int ret = io_uring_wait_cqe_nr(&ring, &cqe, 0);
+
+			/* read and process cqe event */
+			switch(ret) {
+			case 0:
+				if( cqe->res < 0 ) {
+					printf("Completion Error : %s\n", strerror( -cqe->res ));
+					exit( URINGWAIT_ERROR );
+				}
+
+				ssize_t write = cqe->res;
+				int which = cqe->user_data;
+				io_uring_cqe_seen(&ring, cqe);
+				switch( which ) {
+				case SPLICE_IN:
+					has_in = false;
+					size -= write;
+					offset += write;
+					if(0 == size) break;
+					st->shorts.r.cnt++;
+					st->shorts.r.bytes += write;
+					break;
+				case SPLICE_OUT:
+					has_out = false;
+					in_pipe -= write;
+					st->bytes += write;
+					if(0 == in_pipe) break;
+					st->shorts.w.cnt++;
+					st->shorts.w.bytes += write;
+					break;
+				default:
+					printf("Completion Error : unknown user data\n");
+					exit( URINGWAIT_ERROR );
+				}
+				continue;
+			case -EAGAIN:
+				goto OUTER;
+			default:
+				fprintf( stderr, "io_uring_get_cqe error: (%d) %s\n\n", (int)-ret, strerror(-ret) );
+				exit( URINGWAIT_ERROR );
+			}
+		}
+		OUTER:
+		if(0 == in_pipe) break;
+	}
+	st->calls++;
+}
+
+static void my_readwrit(int out, int in, size_t size, struct stats * st) {
+	off_t offset = 0;
+	size_t writes = 0;
+	for(;;) {
+		ssize_t reti = pread(in, buf, size, offset);
+		if( reti < 0 ) {
+			printf("Read in Error : (%d) %s\n\n", (int)errno, strerror(errno) );
+			exit( 1 );
+		}
+
+		offset += reti;
+		size -= reti;
+
+		size_t in_buf = reti;
+		for(;;) {
+			ssize_t reto = write(out, buf, in_buf);
+			if( reto < 0 ) {
+					printf("Write out Error : (%d) %s\n\n", (int)errno, strerror(errno) );
+					exit( 1 );
+				}
+
+			in_buf -= reto;
+			writes += reto;
+			if(0 == in_buf) break;
+			st->shorts.w.cnt++;
+			st->shorts.w.bytes += reto;
+		}
+		if(0 == size) break;
+		st->shorts.r.cnt++;
+		st->shorts.r.bytes += reti;
+	}
+	st->calls++;
+	st->bytes += writes;
+}
Index: driver/cc1.cc
===================================================================
--- driver/cc1.cc	(revision 5cefa433745f174ac307ad32f414eb2fef7476a2)
+++ driver/cc1.cc	(revision f53afafb9048d059a73cca286f45a8bfc99cc3e4)
@@ -10,6 +10,6 @@
 // Created On       : Fri Aug 26 14:23:51 2005
 // Last Modified By : Peter A. Buhr
-// Last Modified On : Wed Jul 21 09:46:24 2021
-// Update Count     : 419
+// Last Modified On : Thu Feb 17 18:04:23 2022
+// Update Count     : 422
 //
 
@@ -61,5 +61,5 @@
 static string __CFA_FLAGPREFIX__( "__CFA_FLAG" );		// "__CFA_FLAG__=" suffix
 
-static void checkEnv1( const char * args[], int & nargs ) { // stage 1
+static void checkEnv1() {								// stage 1
 	extern char ** environ;
 
@@ -155,5 +155,5 @@
 	cerr << "Stage1" << endl;
 	#endif // __DEBUG_H__
-	checkEnv1( args, nargs );							// arguments passed via environment variables
+	checkEnv1();										// arguments passed via environment variables
 	#ifdef __DEBUG_H__
 	for ( int i = 1; i < argc; i += 1 ) {
Index: libcfa/src/concurrency/kernel/startup.cfa
===================================================================
--- libcfa/src/concurrency/kernel/startup.cfa	(revision 5cefa433745f174ac307ad32f414eb2fef7476a2)
+++ libcfa/src/concurrency/kernel/startup.cfa	(revision f53afafb9048d059a73cca286f45a8bfc99cc3e4)
@@ -18,20 +18,20 @@
 
 // C Includes
-#include <errno.h>										// errno
+#include <errno.h>					// errno
 #include <signal.h>
-#include <string.h>										// strerror
-#include <unistd.h>										// sysconf
+#include <string.h>					// strerror
+#include <unistd.h>					// sysconf
 
 extern "C" {
-	#include <limits.h>									// PTHREAD_STACK_MIN
-	#include <unistd.h>									// syscall
-	#include <sys/eventfd.h>							// eventfd
-	#include <sys/mman.h>								// mprotect
-	#include <sys/resource.h>							// getrlimit
+	#include <limits.h>				// PTHREAD_STACK_MIN
+	#include <unistd.h>				// syscall
+	#include <sys/eventfd.h>			// eventfd
+	#include <sys/mman.h>				// mprotect
+	#include <sys/resource.h>			// getrlimit
 }
 
 // CFA Includes
 #include "kernel_private.hfa"
-#include "startup.hfa"									// STARTUP_PRIORITY_XXX
+#include "startup.hfa"					// STARTUP_PRIORITY_XXX
 #include "limits.hfa"
 #include "math.hfa"
@@ -736,5 +736,5 @@
 	check( pthread_attr_init( &attr ), "pthread_attr_init" ); // initialize attribute
 
-	size_t stacksize = DEFAULT_STACK_SIZE;
+	size_t stacksize = max( PTHREAD_STACK_MIN, DEFAULT_STACK_SIZE );
 
 	void * stack;
Index: libcfa/src/concurrency/preemption.cfa
===================================================================
--- libcfa/src/concurrency/preemption.cfa	(revision 5cefa433745f174ac307ad32f414eb2fef7476a2)
+++ libcfa/src/concurrency/preemption.cfa	(revision f53afafb9048d059a73cca286f45a8bfc99cc3e4)
@@ -10,6 +10,6 @@
 // Created On       : Mon Jun 5 14:20:42 2017
 // Last Modified By : Peter A. Buhr
-// Last Modified On : Fri Nov  6 07:42:13 2020
-// Update Count     : 54
+// Last Modified On : Thu Feb 17 11:18:57 2022
+// Update Count     : 59
 //
 
@@ -243,5 +243,5 @@
 //----------
 // special case for preemption since used often
-bool __preemption_enabled() {
+__attribute__((optimize("no-reorder-blocks"))) bool __preemption_enabled() {
 	// create a assembler label before
 	// marked as clobber all to avoid movement
Index: libcfa/src/math.trait.hfa
===================================================================
--- libcfa/src/math.trait.hfa	(revision 5cefa433745f174ac307ad32f414eb2fef7476a2)
+++ libcfa/src/math.trait.hfa	(revision f53afafb9048d059a73cca286f45a8bfc99cc3e4)
@@ -16,7 +16,7 @@
 #pragma once
 
-trait Not( T ) {
-	void ?{}( T &, zero_t );
-	int !?( T );
+trait Not( U ) {
+	void ?{}( U &, zero_t );
+	int !?( U );
 }; // Not
 
@@ -26,9 +26,9 @@
 }; // Equality
 
-trait Relational( T | Equality( T ) ) {
-	int ?<?( T, T );
-	int ?<=?( T, T );
-	int ?>?( T, T );
-	int ?>=?( T, T );
+trait Relational( U | Equality( U ) ) {
+	int ?<?( U, U );
+	int ?<=?( U, U );
+	int ?>?( U, U );
+	int ?>=?( U, U );
 }; // Relational
 
@@ -39,9 +39,9 @@
 }; // Signed
 
-trait Additive( T | Signed( T ) ) {
-	T ?+?( T, T );
-	T ?-?( T, T );
-	T ?+=?( T &, T );
-	T ?-=?( T &, T );
+trait Additive( U | Signed( U ) ) {
+	U ?+?( U, U );
+	U ?-?( U, U );
+	U ?+=?( U &, U );
+	U ?-=?( U &, U );
 }; // Additive
 
@@ -49,14 +49,14 @@
 	void ?{}( T &, one_t );
 	// T ?++( T & );
-	// T ++?( T &);
+	// T ++?( T & );
 	// T ?--( T & );
 	// T --?( T & );
 }; // Incdec
 
-trait Multiplicative( T | Incdec( T ) ) {
-	T ?*?( T, T );
-	T ?/?( T, T );
-	T ?%?( T, T );
-	T ?/=?( T &, T );
+trait Multiplicative( U | Incdec( U ) ) {
+	U ?*?( U, U );
+	U ?/?( U, U );
+	U ?%?( U, U );
+	U ?/=?( U &, U );
 }; // Multiplicative
 
Index: c/AST/AssertAcyclic.cpp
===================================================================
--- src/AST/AssertAcyclic.cpp	(revision 5cefa433745f174ac307ad32f414eb2fef7476a2)
+++ 	(revision )
@@ -1,53 +1,0 @@
-//
-// Cforall Version 1.0.0 Copyright (C) 2019 University of Waterloo
-//
-// The contents of this file are covered under the licence agreement in the
-// file "LICENCE" distributed with Cforall.
-//
-// AssertAcyclic.cpp -- Check that ast::ptr does not form a cycle.
-//
-// Author           : Andrew Beach
-// Created On       : Thu Jun 06 15:00:00 2019
-// Last Modified By : Andrew Beach
-// Last Modified On : Fri Jun 07 14:32:00 2019
-// Update Count     : 1
-//
-
-#include "AssertAcyclic.hpp"
-
-#include "AST/Pass.hpp"
-
-namespace {
-
-class NoStrongCyclesCore {
-    std::vector<const ast::Node *> parents;
-public:
-	void previsit( const ast::Node * node ) {
-		for (auto & parent : parents) {
-			assert(parent != node);
-		}
-		parents.push_back(node);
-	}
-	void postvisit( const ast::Node * ) {
-		parents.pop_back();
-	}
-};
-
-}
-
-namespace ast {
-
-void assertAcyclic( const std::list< ast::ptr< ast::Decl > > & translationUnit ) {
-   	Pass<NoStrongCyclesCore> visitor;
-	for ( auto & decl : translationUnit ) {
-		decl->accept( visitor );
-	}
-}
-
-}
-
-// Local Variables: //
-// tab-width: 4 //
-// mode: c++ //
-// compile-command: "make install" //
-// End: //
Index: c/AST/AssertAcyclic.hpp
===================================================================
--- src/AST/AssertAcyclic.hpp	(revision 5cefa433745f174ac307ad32f414eb2fef7476a2)
+++ 	(revision )
@@ -1,35 +1,0 @@
-//
-// 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.
-//
-// AssertAcyclic.hpp -- Check that ast::ptr does not form a cycle.
-//
-// Author           : Andrew Beach
-// Created On       : Thr Jun  6 15:00:00 2019
-// Last Modified By : Andrew Beach
-// Last Modified On : Fri Jun  7 14:32:00 2019
-// Update Count     : 1
-//
-
-#pragma once
-
-#include <list>
-
-#include "Node.hpp"
-namespace ast {
-    class Decl;
-};
-
-namespace ast {
-
-void assertAcyclic( const std::list< ast::ptr< ast::Decl > > & translationUnit );
-
-}
-
-// Local Variables: //
-// tab-width: 4 //
-// mode: c++ //
-// compile-command: "make install" //
-// End: //
Index: src/AST/Decl.cpp
===================================================================
--- src/AST/Decl.cpp	(revision 5cefa433745f174ac307ad32f414eb2fef7476a2)
+++ src/AST/Decl.cpp	(revision f53afafb9048d059a73cca286f45a8bfc99cc3e4)
@@ -39,8 +39,11 @@
 	if ( uniqueId ) return;  // ensure only set once
 	uniqueId = ++lastUniqueId;
-	idMap[ uniqueId ] = this;
+	// The extra readonly pointer is causing some reference counting issues.
+	// idMap[ uniqueId ] = this;
 }
 
 readonly<Decl> Decl::fromId( UniqueId id ) {
+	// Right now this map is always empty, so don't use it.
+	assert( false );
 	IdMapType::const_iterator i = idMap.find( id );
 	if ( i != idMap.end() ) return i->second;
Index: src/AST/Util.cpp
===================================================================
--- src/AST/Util.cpp	(revision f53afafb9048d059a73cca286f45a8bfc99cc3e4)
+++ src/AST/Util.cpp	(revision f53afafb9048d059a73cca286f45a8bfc99cc3e4)
@@ -0,0 +1,68 @@
+//
+// Cforall Version 1.0.0 Copyright (C) 2019 University of Waterloo
+//
+// The contents of this file are covered under the licence agreement in the
+// file "LICENCE" distributed with Cforall.
+//
+// Util.hpp -- General utilities for working with the AST.
+//
+// Author           : Andrew Beach
+// Created On       : Wed Jan 19  9:46:00 2022
+// Last Modified By : Andrew Beach
+// Last Modified On : Fri Feb 18  9:42:00 2022
+// Update Count     : 0
+//
+
+#include "Util.hpp"
+
+#include "Decl.hpp"
+#include "Node.hpp"
+#include "Pass.hpp"
+#include "TranslationUnit.hpp"
+#include "Common/ScopedMap.h"
+
+#include <vector>
+
+namespace ast {
+
+namespace {
+
+/// Check that ast::ptr/strong references do not form a cycle.
+struct NoStrongCyclesCore {
+	std::vector<const Node *> parents;
+
+	void previsit( const Node * node ) {
+		for ( auto & parent : parents ) {
+			assert( parent != node );
+		}
+		parents.push_back( node );
+	}
+
+	void postvisit( const Node * node ) {
+		assert( !parents.empty() );
+		assert( parents.back() == node );
+		parents.pop_back();
+	}
+};
+
+struct InvariantCore {
+	// To save on the number of visits: this is a kind of composed core.
+	// None of the passes should make changes so ordering doesn't matter.
+	NoStrongCyclesCore no_strong_cycles;
+
+	void previsit( const Node * node ) {
+		no_strong_cycles.previsit( node );
+	}
+
+	void postvisit( const Node * node ) {
+		no_strong_cycles.postvisit( node );
+	}
+};
+
+} // namespace
+
+void checkInvariants( TranslationUnit & transUnit ) {
+	ast::Pass<InvariantCore>::run( transUnit );
+}
+
+} // namespace ast
Index: src/AST/Util.hpp
===================================================================
--- src/AST/Util.hpp	(revision f53afafb9048d059a73cca286f45a8bfc99cc3e4)
+++ src/AST/Util.hpp	(revision f53afafb9048d059a73cca286f45a8bfc99cc3e4)
@@ -0,0 +1,26 @@
+//
+// Cforall Version 1.0.0 Copyright (C) 2019 University of Waterloo
+//
+// The contents of this file are covered under the licence agreement in the
+// file "LICENCE" distributed with Cforall.
+//
+// Util.hpp -- General utilities for working with the AST.
+//
+// Author           : Andrew Beach
+// Created On       : Wed Jan 19  9:37:00 2022
+// Last Modified By : Andrew Beach
+// Last Modified On : Fri Feb 18  9:43:00 2022
+// Update Count     : 0
+//
+
+#pragma once
+
+namespace ast {
+
+class TranslationUnit;
+
+/// Check anything that should always be true of the AST between passes.
+/// Insert this whenever you want additional debugging checks.
+void checkInvariants( TranslationUnit & transUnit );
+
+}
Index: src/AST/module.mk
===================================================================
--- src/AST/module.mk	(revision 5cefa433745f174ac307ad32f414eb2fef7476a2)
+++ src/AST/module.mk	(revision f53afafb9048d059a73cca286f45a8bfc99cc3e4)
@@ -16,6 +16,4 @@
 
 SRC_AST = \
-	AST/AssertAcyclic.cpp \
-	AST/AssertAcyclic.hpp \
 	AST/Attribute.cpp \
 	AST/Attribute.hpp \
@@ -64,4 +62,6 @@
 	AST/TypeSubstitution.cpp \
 	AST/TypeSubstitution.hpp \
+	AST/Util.cpp \
+	AST/Util.hpp \
 	AST/Visitor.hpp
 
Index: src/SymTab/Validate.cc
===================================================================
--- src/SymTab/Validate.cc	(revision 5cefa433745f174ac307ad32f414eb2fef7476a2)
+++ src/SymTab/Validate.cc	(revision f53afafb9048d059a73cca286f45a8bfc99cc3e4)
@@ -194,4 +194,26 @@
 	};
 
+	// These structs are the sub-sub-passes of ForallPointerDecay_old.
+
+	struct TraitExpander_old final {
+		void previsit( FunctionType * );
+		void previsit( StructDecl * );
+		void previsit( UnionDecl * );
+	};
+
+	struct AssertionFixer_old final {
+		void previsit( FunctionType * );
+		void previsit( StructDecl * );
+		void previsit( UnionDecl * );
+	};
+
+	struct CheckOperatorTypes_old final {
+		void previsit( ObjectDecl * );
+	};
+
+	struct FixUniqueIds_old final {
+		void previsit( DeclarationWithType * );
+	};
+
 	struct ReturnChecker : public WithGuards {
 		/// Checks that return statements return nothing if their return type is void
@@ -386,5 +408,4 @@
 
 	void validate_D( std::list< Declaration * > & translationUnit ) {
-		PassVisitor<ForallPointerDecay_old> fpd;
 		{
 			Stats::Heap::newPass("validate-D");
@@ -394,5 +415,5 @@
 			});
 			Stats::Time::TimeBlock("Forall Pointer Decay", [&]() {
-				acceptAll( translationUnit, fpd ); // must happen before autogenerateRoutines, after Concurrency::applyKeywords because uniqueIds must be set on declaration before resolution
+				decayForallPointers( translationUnit ); // must happen before autogenerateRoutines, after Concurrency::applyKeywords because uniqueIds must be set on declaration before resolution
 			});
 			Stats::Time::TimeBlock("Hoist Control Declarations", [&]() {
@@ -454,6 +475,29 @@
 
 	void decayForallPointers( std::list< Declaration * > & translationUnit ) {
-		PassVisitor<ForallPointerDecay_old> fpd;
-		acceptAll( translationUnit, fpd );
+		PassVisitor<TraitExpander_old> te;
+		acceptAll( translationUnit, te );
+		PassVisitor<AssertionFixer_old> af;
+		acceptAll( translationUnit, af );
+		PassVisitor<CheckOperatorTypes_old> cot;
+		acceptAll( translationUnit, cot );
+		PassVisitor<FixUniqueIds_old> fui;
+		acceptAll( translationUnit, fui );
+	}
+
+	void decayForallPointersA( std::list< Declaration * > & translationUnit ) {
+		PassVisitor<TraitExpander_old> te;
+		acceptAll( translationUnit, te );
+	}
+	void decayForallPointersB( std::list< Declaration * > & translationUnit ) {
+		PassVisitor<AssertionFixer_old> af;
+		acceptAll( translationUnit, af );
+	}
+	void decayForallPointersC( std::list< Declaration * > & translationUnit ) {
+		PassVisitor<CheckOperatorTypes_old> cot;
+		acceptAll( translationUnit, cot );
+	}
+	void decayForallPointersD( std::list< Declaration * > & translationUnit ) {
+		PassVisitor<FixUniqueIds_old> fui;
+		acceptAll( translationUnit, fui );
 	}
 
@@ -470,8 +514,14 @@
 		PassVisitor<EnumAndPointerDecay_old> epc;
 		PassVisitor<LinkReferenceToTypes_old> lrt( indexer );
-		PassVisitor<ForallPointerDecay_old> fpd;
+		PassVisitor<TraitExpander_old> te;
+		PassVisitor<AssertionFixer_old> af;
+		PassVisitor<CheckOperatorTypes_old> cot;
+		PassVisitor<FixUniqueIds_old> fui;
 		type->accept( epc );
 		type->accept( lrt );
-		type->accept( fpd );
+		type->accept( te );
+		type->accept( af );
+		type->accept( cot );
+		type->accept( fui );
 	}
 
@@ -972,4 +1022,36 @@
 	}
 
+	/// Replace all traits in assertion lists with their assertions.
+	void expandTraits( std::list< TypeDecl * > & forall ) {
+		for ( TypeDecl * type : forall ) {
+			std::list< DeclarationWithType * > asserts;
+			asserts.splice( asserts.end(), type->assertions );
+			// expand trait instances into their members
+			for ( DeclarationWithType * assertion : asserts ) {
+				if ( TraitInstType * traitInst = dynamic_cast< TraitInstType * >( assertion->get_type() ) ) {
+					// expand trait instance into all of its members
+					expandAssertions( traitInst, back_inserter( type->assertions ) );
+					delete traitInst;
+				} else {
+					// pass other assertions through
+					type->assertions.push_back( assertion );
+				} // if
+			} // for
+		}
+	}
+
+	/// Fix each function in the assertion list and check for invalid void type.
+	void fixAssertions(
+			std::list< TypeDecl * > & forall, BaseSyntaxNode * node ) {
+		for ( TypeDecl * type : forall ) {
+			for ( DeclarationWithType *& assertion : type->assertions ) {
+				bool isVoid = fixFunction( assertion );
+				if ( isVoid ) {
+					SemanticError( node, "invalid type void in assertion of function " );
+				} // if
+			} // for
+		}
+	}
+
 	void ForallPointerDecay_old::previsit( ObjectDecl * object ) {
 		// ensure that operator names only apply to functions or function pointers
@@ -994,4 +1076,39 @@
 	void ForallPointerDecay_old::previsit( UnionDecl * aggrDecl ) {
 		forallFixer( aggrDecl->parameters, aggrDecl );
+	}
+
+	void TraitExpander_old::previsit( FunctionType * ftype ) {
+		expandTraits( ftype->forall );
+	}
+
+	void TraitExpander_old::previsit( StructDecl * aggrDecl ) {
+		expandTraits( aggrDecl->parameters );
+	}
+
+	void TraitExpander_old::previsit( UnionDecl * aggrDecl ) {
+		expandTraits( aggrDecl->parameters );
+	}
+
+	void AssertionFixer_old::previsit( FunctionType * ftype ) {
+		fixAssertions( ftype->forall, ftype );
+	}
+
+	void AssertionFixer_old::previsit( StructDecl * aggrDecl ) {
+		fixAssertions( aggrDecl->parameters, aggrDecl );
+	}
+
+	void AssertionFixer_old::previsit( UnionDecl * aggrDecl ) {
+		fixAssertions( aggrDecl->parameters, aggrDecl );
+	}
+
+	void CheckOperatorTypes_old::previsit( ObjectDecl * object ) {
+		// ensure that operator names only apply to functions or function pointers
+		if ( CodeGen::isOperator( object->name ) && ! dynamic_cast< FunctionType * >( object->type->stripDeclarator() ) ) {
+			SemanticError( object->location, toCString( "operator ", object->name.c_str(), " is not a function or function pointer." )  );
+		}
+	}
+
+	void FixUniqueIds_old::previsit( DeclarationWithType * decl ) {
+		decl->fixUniqueId();
 	}
 
Index: src/SymTab/Validate.h
===================================================================
--- src/SymTab/Validate.h	(revision 5cefa433745f174ac307ad32f414eb2fef7476a2)
+++ src/SymTab/Validate.h	(revision f53afafb9048d059a73cca286f45a8bfc99cc3e4)
@@ -43,4 +43,8 @@
 	void validate_F( std::list< Declaration * > &translationUnit );
 	void decayForallPointers( std::list< Declaration * > & translationUnit );
+	void decayForallPointersA( std::list< Declaration * > & translationUnit );
+	void decayForallPointersB( std::list< Declaration * > & translationUnit );
+	void decayForallPointersC( std::list< Declaration * > & translationUnit );
+	void decayForallPointersD( std::list< Declaration * > & translationUnit );
 
 	const ast::Type * validateType(
Index: src/Validate/ForallPointerDecay.cpp
===================================================================
--- src/Validate/ForallPointerDecay.cpp	(revision f53afafb9048d059a73cca286f45a8bfc99cc3e4)
+++ src/Validate/ForallPointerDecay.cpp	(revision f53afafb9048d059a73cca286f45a8bfc99cc3e4)
@@ -0,0 +1,249 @@
+//
+// 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.
+//
+// ForallPointerDecay.cpp --
+//
+// Author           : Andrew Beach
+// Created On       : Tue Dec  7 16:15:00 2021
+// Last Modified By : Andrew Beach
+// Last Modified On : Fri Feb 11 10:59:00 2022
+// Update Count     : 0
+//
+
+#include "ForallPointerDecay.hpp"
+
+#include "AST/Copy.hpp"
+#include "AST/Decl.hpp"
+#include "AST/DeclReplacer.hpp"
+#include "AST/Pass.hpp"
+#include "CodeGen/OperatorTable.h"
+#include "Common/CodeLocation.h"
+#include "SymTab/FixFunction.h"
+
+#include "AST/Print.hpp"
+
+namespace Validate {
+
+namespace {
+
+// Create a function type only using information on the FunctionDecl.
+ast::FunctionType * makeFuncType( const ast::FunctionDecl * decl ) {
+	auto type = new ast::FunctionType( decl->type->isVarArgs );
+	for ( auto & param : decl->params ) {
+		type->params.emplace_back( param->get_type() );
+	}
+	for ( auto & ret : decl->returns ) {
+		type->returns.emplace_back( ret->get_type() );
+	}
+	for ( auto & type_param : decl->type_params ) {
+		type->forall.emplace_back(
+			new ast::TypeInstType( type_param->name, type_param ) );
+	}
+	for ( auto & assertion : decl->assertions ) {
+		type->assertions.emplace_back( new ast::VariableExpr(
+			assertion->location, assertion ) );
+	}
+	return type;
+}
+
+template<typename T>
+void append( std::vector<T> & dst, std::vector<T> & src ) {
+	dst.reserve( dst.size() + src.size() );
+	for ( auto el : src ) {
+		dst.emplace_back( std::move( el ) );
+	}
+	src.clear();
+}
+
+// Component Passes:
+/// Expand assertions from a trait instance,
+/// performing appropriate type variable substitutions.
+struct TraitExpander final {
+	using AssertionList = std::vector<ast::ptr<ast::DeclWithType>>;
+
+	static AssertionList expandTrait( const ast::TraitInstType * inst ) {
+		assertf( inst->base, "Trait instance not linked to base trait: %s",
+			toCString( inst ) );
+		AssertionList assertions;
+		// Substitute trait decl parameters for instance parameters.
+		ast::TypeSubstitution sub(
+			inst->base->params.begin(),
+			inst->base->params.end(),
+			inst->params.begin()
+		);
+		for ( const ast::ptr<ast::Decl> & decl : inst->base->members ) {
+			ast::ptr<ast::DeclWithType> copy =
+				ast::deepCopy( decl.strict_as<ast::DeclWithType>() );
+
+			int count = sub.apply( copy );
+			(void)count;
+
+			// Update the type (type substution does not seem to cover it).
+			if ( auto func = copy.as<ast::FunctionDecl>() ) {
+				auto mut = ast::mutate( func );
+				mut->type = makeFuncType( func );
+				copy = mut;
+			}
+			assertions.push_back( copy );
+		}
+		return assertions;
+	}
+
+	static AssertionList expandAssertions( const AssertionList & old ) {
+		AssertionList assertions;
+		for ( const ast::ptr<ast::DeclWithType> & decl : old ) {
+			if ( auto traitInst = dynamic_cast<const ast::TraitInstType *>(
+					decl->get_type() ) ) {
+				auto moreAsserts = expandTrait( traitInst );
+				append( assertions, moreAsserts );
+			} else {
+				assertions.push_back( decl );
+			}
+		}
+		return assertions;
+	}
+
+	using TypeDeclVec = std::vector<ast::ptr<ast::TypeDecl>>;
+
+	static TypeDeclVec expandTypeDecls( const TypeDeclVec & old ) {
+		TypeDeclVec typeDecls;
+		for ( const ast::TypeDecl * typeDecl : old ) {
+			typeDecls.push_back( ast::mutate_field( typeDecl,
+				&ast::TypeDecl::assertions,
+				expandAssertions( typeDecl->assertions ) ) );
+		}
+		return typeDecls;
+	}
+
+	const ast::FunctionDecl * postvisit( const ast::FunctionDecl * decl ) {
+		if ( decl->assertions.empty() ) {
+			return decl;
+		}
+		auto mut = ast::mutate( decl );
+		mut->assertions = expandAssertions( decl->assertions );
+		// Update the assertion list on the type as well.
+		auto mutType = ast::mutate( mut->type.get() );
+		mutType->assertions.clear();
+		for ( auto & assertion : mut->assertions ) {
+			mutType->assertions.emplace_back(
+				new ast::VariableExpr( mut->location, assertion ) );
+		}
+		mut->type = mutType;
+		return mut;
+	}
+
+	const ast::StructDecl * previsit( const ast::StructDecl * decl ) {
+		if ( decl->params.empty() ) {
+			return decl;
+		}
+		return ast::mutate_field( decl, &ast::StructDecl::params,
+			expandTypeDecls( decl->params ) );
+	}
+
+	const ast::UnionDecl * previsit( const ast::UnionDecl * decl ) {
+		if ( decl->params.empty() ) {
+			return decl;
+		}
+		return ast::mutate_field( decl, &ast::UnionDecl::params,
+			expandTypeDecls( decl->params ) );
+	}
+};
+
+std::vector<ast::ptr<ast::DeclWithType>> fixAssertionList(
+		const ast::ParseNode * node,
+		const std::vector<ast::ptr<ast::DeclWithType>> & assertions ) {
+	std::vector<ast::ptr<ast::DeclWithType>> ret;
+	for ( const auto & assn : assertions ) {
+		bool isVoid = false;
+		ret.push_back( SymTab::fixFunction( assn, isVoid ) );
+		if ( isVoid ) {
+			SemanticError( node->location, node,
+				"invalid type void in assertion of function " );
+		}
+	}
+	return ret;
+}
+
+std::vector<ast::ptr<ast::TypeDecl>> fixTypeDeclList(
+		const ast::ParseNode * node,
+		const std::vector<ast::ptr<ast::TypeDecl>> & type_params ) {
+	std::vector<ast::ptr<ast::TypeDecl>> ret;
+	ret.reserve( type_params.size() );
+	for ( const ast::TypeDecl * type_param : type_params ) {
+		auto mutParam = ast::mutate( type_param );
+		mutParam->assertions = fixAssertionList( node, mutParam->assertions );
+		ret.push_back( mutParam );
+	}
+	return ret;
+}
+
+struct AssertionFunctionFixer final {
+	const ast::FunctionDecl * previsit( const ast::FunctionDecl * decl ) {
+		if ( decl->assertions.empty() ) {
+			return decl;
+		}
+		return ast::mutate_field( decl, &ast::FunctionDecl::assertions,
+			fixAssertionList( decl, decl->assertions ) );
+	}
+
+	const ast::StructDecl * previsit( const ast::StructDecl * decl ) {
+		if ( decl->params.empty() ) {
+			return decl;
+		}
+		return ast::mutate_field( decl, &ast::StructDecl::params,
+			fixTypeDeclList( decl, decl->params ) );
+	}
+
+	const ast::UnionDecl * previsit( const ast::UnionDecl * decl ) {
+		if ( decl->params.empty() ) {
+			return decl;
+		}
+		return ast::mutate_field( decl, &ast::UnionDecl::params,
+			fixTypeDeclList( decl, decl->params ) );
+	}
+};
+
+struct OberatorChecker final {
+	void previsit( const ast::ObjectDecl * obj ) {
+		if ( CodeGen::isOperator( obj->name ) ) {
+			auto type = obj->type->stripDeclarator();
+			if ( ! dynamic_cast< const ast::FunctionType * >( type ) ) {
+				SemanticError( obj->location,
+					toCString( "operator ", obj->name.c_str(), " is not "
+					"a function or function pointer." ) );
+			}
+		}
+	}
+};
+
+struct UniqueFixCore final {
+	const ast::DeclWithType * postvisit( const ast::DeclWithType * decl ) {
+		if ( decl->uniqueId ) {
+			return decl;
+		} else {
+			auto mut = ast::mutate( decl );
+			mut->fixUniqueId();
+			return mut;
+		}
+	}
+};
+
+} // namespace
+
+void decayForallPointers( ast::TranslationUnit & transUnit ) {
+	ast::Pass<TraitExpander>::run( transUnit );
+	ast::Pass<AssertionFunctionFixer>::run( transUnit );
+	ast::Pass<OberatorChecker>::run( transUnit );
+	ast::Pass<UniqueFixCore>::run( transUnit );
+}
+
+} // namespace Validate
+
+// Local Variables: //
+// tab-width: 4 //
+// mode: c++ //
+// compile-command: "make install" //
+// End: //
Index: src/Validate/ForallPointerDecay.hpp
===================================================================
--- src/Validate/ForallPointerDecay.hpp	(revision f53afafb9048d059a73cca286f45a8bfc99cc3e4)
+++ src/Validate/ForallPointerDecay.hpp	(revision f53afafb9048d059a73cca286f45a8bfc99cc3e4)
@@ -0,0 +1,35 @@
+//
+// 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.
+//
+// ForallPointerDecay.hpp --
+//
+// Author           : Andrew Beach
+// Created On       : Tue Dec  7 16:15:00 2021
+// Last Modified By : Andrew Beach
+// Last Modified On : Tue Dec  8 11:50:00 2021
+// Update Count     : 0
+//
+
+#pragma once
+
+namespace ast {
+	class TranslationUnit;
+}
+
+namespace Validate {
+
+/// Cleans up assertion lists and expands traits.
+/// Also checks that operator names are used properly on functions and
+/// assigns unique IDs. This is a "legacy" pass.
+void decayForallPointers( ast::TranslationUnit & transUnit );
+
+}
+
+// Local Variables: //
+// tab-width: 4 //
+// mode: c++ //
+// compile-command: "make install" //
+// End: //
Index: src/Validate/module.mk
===================================================================
--- src/Validate/module.mk	(revision 5cefa433745f174ac307ad32f414eb2fef7476a2)
+++ src/Validate/module.mk	(revision f53afafb9048d059a73cca286f45a8bfc99cc3e4)
@@ -20,4 +20,6 @@
 	Validate/CompoundLiteral.cpp \
 	Validate/CompoundLiteral.hpp \
+	Validate/ForallPointerDecay.cpp \
+	Validate/ForallPointerDecay.hpp \
 	Validate/HandleAttributes.cc \
 	Validate/HandleAttributes.h \
Index: src/main.cc
===================================================================
--- src/main.cc	(revision 5cefa433745f174ac307ad32f414eb2fef7476a2)
+++ src/main.cc	(revision f53afafb9048d059a73cca286f45a8bfc99cc3e4)
@@ -32,4 +32,5 @@
 
 #include "AST/Convert.hpp"
+#include "AST/Print.hpp"
 #include "CompilationState.h"
 #include "../config.h"                      // for CFA_LIBDIR
@@ -76,4 +77,5 @@
 #include "Validate/Autogen.hpp"             // for autogenerateRoutines
 #include "Validate/FindSpecialDecls.h"      // for findGlobalDecls
+#include "Validate/ForallPointerDecay.hpp"  // for decayForallPointers
 #include "Validate/CompoundLiteral.hpp"     // for handleCompoundLiterals
 #include "Validate/InitializerLength.hpp"   // for setLengthFromInitializer
@@ -331,6 +333,9 @@
 
 		if( useNewAST ) {
-			PASS( "Apply Concurrent Keywords", Concurrency::applyKeywords( translationUnit ) );
-			PASS( "Forall Pointer Decay", SymTab::decayForallPointers( translationUnit ) );
+			PASS( "Implement Concurrent Keywords", Concurrency::applyKeywords( translationUnit ) );
+			//PASS( "Forall Pointer Decay - A", SymTab::decayForallPointersA( translationUnit ) );
+			//PASS( "Forall Pointer Decay - B", SymTab::decayForallPointersB( translationUnit ) );
+			//PASS( "Forall Pointer Decay - C", SymTab::decayForallPointersC( translationUnit ) );
+			//PASS( "Forall Pointer Decay - D", SymTab::decayForallPointersD( translationUnit ) );
 			CodeTools::fillLocations( translationUnit );
 
@@ -342,4 +347,9 @@
 
 			forceFillCodeLocations( transUnit );
+
+			// Must be after implement concurrent keywords; because uniqueIds
+			//   must be set on declaration before resolution.
+			// Must happen before autogen routines are added.
+			PASS( "Forall Pointer Decay", Validate::decayForallPointers( transUnit ) );
 
 			// Must happen before autogen routines are added.
Index: tests/.expect/declarationSpecifier.arm64.txt
===================================================================
--- tests/.expect/declarationSpecifier.arm64.txt	(revision 5cefa433745f174ac307ad32f414eb2fef7476a2)
+++ tests/.expect/declarationSpecifier.arm64.txt	(revision f53afafb9048d059a73cca286f45a8bfc99cc3e4)
@@ -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.arm64.txt
===================================================================
--- tests/.expect/gccExtensions.arm64.txt	(revision 5cefa433745f174ac307ad32f414eb2fef7476a2)
+++ tests/.expect/gccExtensions.arm64.txt	(revision f53afafb9048d059a73cca286f45a8bfc99cc3e4)
@@ -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/random.arm64.txt
===================================================================
--- tests/.expect/random.arm64.txt	(revision 5cefa433745f174ac307ad32f414eb2fef7476a2)
+++ tests/.expect/random.arm64.txt	(revision f53afafb9048d059a73cca286f45a8bfc99cc3e4)
@@ -1,17 +1,17 @@
 õ
 =
-V
+K
 -911259971
 6
--4
+11
 1232105397
 0
-18
+11
 -914096085
 1
-15
+20
 2077092859
 1
-11
+12
 0.677254
 0.678106775246139
Index: tests/meta/dumpable.cfa
===================================================================
--- tests/meta/dumpable.cfa	(revision 5cefa433745f174ac307ad32f414eb2fef7476a2)
+++ tests/meta/dumpable.cfa	(revision f53afafb9048d059a73cca286f45a8bfc99cc3e4)
@@ -72,6 +72,8 @@
 	}
 
-	if((buf.f_bsize * buf.f_bavail) < 536870912) {
-		serr | "Available diskspace is less than ~500Mb: " | (buf.f_bsize * buf.f_bavail);
+	uint64_t avail = buf.f_bavail;
+	avail *= buf.f_bsize;
+	if(avail < 536870912_l64u) {
+		serr | "Available diskspace is less than ~500Mb: " | avail;
 	}
 
