Index: Jenkins/FullBuild
===================================================================
--- Jenkins/FullBuild	(revision f70497477cb004d4a38efd1b4002ed60f98f1997)
+++ Jenkins/FullBuild	(revision ae151cfe6e0bae407bd6e099500a00a6d4db9546)
@@ -18,12 +18,12 @@
 
 				parallel (
-					gcc_08_x86_new: { trigger_build( 'gcc-10',  'x86' ) },
-					gcc_07_x86_new: { trigger_build( 'gcc-9',   'x86' ) },
-					gcc_10_x64_new: { trigger_build( 'gcc-10',  'x64' ) },
-					gcc_09_x64_new: { trigger_build( 'gcc-9',   'x64' ) },
-					gcc_08_x64_new: { trigger_build( 'gcc-8',   'x64' ) },
-					gcc_07_x64_new: { trigger_build( 'gcc-7',   'x64' ) },
-					gcc_06_x64_new: { trigger_build( 'gcc-6',   'x64' ) },
-					clang_x64_new:  { trigger_build( 'clang',   'x64' ) },
+					gcc_08_x86_new: { trigger_build( 'gcc-10',  'x86', false ) },
+					gcc_07_x86_new: { trigger_build( 'gcc-9',   'x86', false ) },
+					gcc_10_x64_new: { trigger_build( 'gcc-10',  'x64', false ) },
+					gcc_09_x64_new: { trigger_build( 'gcc-9',   'x64', false ) },
+					gcc_08_x64_new: { trigger_build( 'gcc-8',   'x64', false ) },
+					gcc_07_x64_new: { trigger_build( 'gcc-7',   'x64', false ) },
+					gcc_06_x64_new: { trigger_build( 'gcc-6',   'x64', false ) },
+					clang_x64_new:  { trigger_build( 'clang',   'x64', true  ) },
 				)
 			}
@@ -67,5 +67,5 @@
 //===========================================================================================================
 
-def trigger_build(String cc, String arch) {
+def trigger_build(String cc, String arch, boolean doc) {
 	// Randomly delay the builds by a random amount to avoid hitting the SC server to hard
 	sleep(time: 5 * Math.random(), unit:"MINUTES")
@@ -92,5 +92,5 @@
 			[$class: 'BooleanParameterValue', 		\
 			  name: 'BuildDocumentation', 		\
-			  value: true], 					\
+			  value: doc], 					\
 			[$class: 'BooleanParameterValue', 		\
 			  name: 'Publish', 				\
Index: libcfa/src/assert.cfa
===================================================================
--- libcfa/src/assert.cfa	(revision f70497477cb004d4a38efd1b4002ed60f98f1997)
+++ libcfa/src/assert.cfa	(revision ae151cfe6e0bae407bd6e099500a00a6d4db9546)
@@ -25,4 +25,5 @@
 
 	#define CFA_ASSERT_FMT "Cforall Assertion error \"%s\" from program \"%s\" in \"%s\" at line %d in file \"%s\""
+	#define CFA_WARNING_FMT "Cforall Assertion warning \"%s\" from program \"%s\" in \"%s\" at line %d in file \"%s\""
 
 	// called by macro assert in assert.h
@@ -48,4 +49,19 @@
 		abort();
 	}
+
+	// called by macro warnf
+	// would be cool to remove libcfa_public but it's needed for libcfathread
+	void __assert_warn_f( const char assertion[], const char file[], unsigned int line, const char function[], const char fmt[], ... ) libcfa_public {
+		__cfaabi_bits_acquire();
+		__cfaabi_bits_print_nolock( STDERR_FILENO, CFA_WARNING_FMT ": ", assertion, __progname, function, line, file );
+
+		va_list args;
+		va_start( args, fmt );
+		__cfaabi_bits_print_vararg( STDERR_FILENO, fmt, args );
+		va_end( args );
+
+		__cfaabi_bits_print_nolock( STDERR_FILENO, "\n" );
+		__cfaabi_bits_release();
+	}
 }
 
Index: libcfa/src/concurrency/alarm.cfa
===================================================================
--- libcfa/src/concurrency/alarm.cfa	(revision f70497477cb004d4a38efd1b4002ed60f98f1997)
+++ libcfa/src/concurrency/alarm.cfa	(revision ae151cfe6e0bae407bd6e099500a00a6d4db9546)
@@ -55,5 +55,5 @@
 	this.period  = period;
 	this.thrd = thrd;
-	this.timeval = __kernel_get_time() + alarm;
+	this.deadline = __kernel_get_time() + alarm;
 	set = false;
 	type = User;
@@ -64,5 +64,5 @@
 	this.period  = period;
 	this.proc = proc;
-	this.timeval = __kernel_get_time() + alarm;
+	this.deadline = __kernel_get_time() + alarm;
 	set = false;
 	type = Kernel;
@@ -72,5 +72,5 @@
 	this.initial = alarm;
 	this.period  = period;
-	this.timeval = __kernel_get_time() + alarm;
+	this.deadline = __kernel_get_time() + alarm;
 	set = false;
 	type = Callback;
@@ -85,5 +85,5 @@
 void insert( alarm_list_t * this, alarm_node_t * n ) {
 	alarm_node_t * it = & (*this)`first;
-	while( it && (n->timeval > it->timeval) ) {
+	while( it && (n->deadline > it->deadline) ) {
 		it = & (*it)`next;
 	}
@@ -116,7 +116,7 @@
 
 		Time curr = __kernel_get_time();
-		__cfadbg_print_safe( preemption, " KERNEL: alarm inserting %p (%lu -> %lu).\n", this, curr.tn, this->timeval.tn );
+		__cfadbg_print_safe( preemption, " KERNEL: alarm inserting %p (%lu -> %lu).\n", this, curr.tn, this->deadline.tn );
 		insert( &alarms, this );
-		__kernel_set_timer( this->timeval - curr);
+		__kernel_set_timer( this->deadline - curr);
 		this->set = true;
 	}
Index: libcfa/src/concurrency/alarm.hfa
===================================================================
--- libcfa/src/concurrency/alarm.hfa	(revision f70497477cb004d4a38efd1b4002ed60f98f1997)
+++ libcfa/src/concurrency/alarm.hfa	(revision ae151cfe6e0bae407bd6e099500a00a6d4db9546)
@@ -57,5 +57,5 @@
 	};
 
-	Time timeval;           // actual time at which the alarm goes off
+	Time deadline;          // actual time at which the alarm goes off
 	enum alarm_type type;	// true if this is not a user defined alarm
 	bool set		:1;	// whether or not the alarm has be registered
Index: libcfa/src/concurrency/io.cfa
===================================================================
--- libcfa/src/concurrency/io.cfa	(revision f70497477cb004d4a38efd1b4002ed60f98f1997)
+++ libcfa/src/concurrency/io.cfa	(revision ae151cfe6e0bae407bd6e099500a00a6d4db9546)
@@ -201,10 +201,10 @@
 		__atomic_unlock(&ctx->cq.lock);
 
-		touch_tsc( cltr->sched.io.tscs, ctx->cq.id, ts_prev, ts_next );
+		touch_tsc( cltr->sched.io.tscs, ctx->cq.id, ts_prev, ts_next, false );
 
 		return true;
 	}
 
-	bool __cfa_io_drain( processor * proc ) {
+	bool __cfa_io_drain( struct processor * proc ) {
 		bool local = false;
 		bool remote = false;
@@ -243,6 +243,6 @@
 				/* paranoid */ verify( io.tscs[target].t.tv != ULLONG_MAX );
 				HELP: if(target < ctxs_count) {
-					const unsigned long long cutoff = calc_cutoff(ctsc, ctx->cq.id, ctxs_count, io.data, io.tscs, __shard_factor.io);
-					const unsigned long long age = moving_average(ctsc, io.tscs[target].t.tv, io.tscs[target].t.ma);
+					const unsigned long long cutoff = calc_cutoff(ctsc, ctx->cq.id, ctxs_count, io.data, io.tscs, __shard_factor.io, false);
+					const unsigned long long age = moving_average(ctsc, io.tscs[target].t.tv, io.tscs[target].t.ma, false);
 					__cfadbg_print_safe(io, "Kernel I/O: Help attempt on %u from %u, age %'llu vs cutoff %'llu, %s\n", target, ctx->cq.id, age, cutoff, age > cutoff ? "yes" : "no");
 					if(age <= cutoff) break HELP;
@@ -273,5 +273,5 @@
 	}
 
-	bool __cfa_io_flush( processor * proc ) {
+	bool __cfa_io_flush( struct processor * proc ) {
 		/* paranoid */ verify( ! __preemption_enabled() );
 		/* paranoid */ verify( proc );
@@ -353,5 +353,5 @@
 
 		disable_interrupts();
-		processor * proc = __cfaabi_tls.this_processor;
+		struct processor * proc = __cfaabi_tls.this_processor;
 		io_context$ * ctx = proc->io.ctx;
 		/* paranoid */ verify( __cfaabi_tls.this_processor );
@@ -433,5 +433,5 @@
 		disable_interrupts();
 		__STATS__( true, if(!lazy) io.submit.eagr += 1; )
-		processor * proc = __cfaabi_tls.this_processor;
+		struct processor * proc = __cfaabi_tls.this_processor;
 		io_context$ * ctx = proc->io.ctx;
 		/* paranoid */ verify( __cfaabi_tls.this_processor );
@@ -641,5 +641,5 @@
 
 	#if defined(CFA_WITH_IO_URING_IDLE)
-		bool __kernel_read(processor * proc, io_future_t & future, iovec & iov, int fd) {
+		bool __kernel_read(struct processor * proc, io_future_t & future, iovec & iov, int fd) {
 			io_context$ * ctx = proc->io.ctx;
 			/* paranoid */ verify( ! __preemption_enabled() );
@@ -692,5 +692,5 @@
 		}
 
-		void __cfa_io_idle( processor * proc ) {
+		void __cfa_io_idle( struct processor * proc ) {
 			iovec iov;
 			__atomic_acquire( &proc->io.ctx->cq.lock );
Index: libcfa/src/concurrency/io/types.hfa
===================================================================
--- libcfa/src/concurrency/io/types.hfa	(revision f70497477cb004d4a38efd1b4002ed60f98f1997)
+++ libcfa/src/concurrency/io/types.hfa	(revision ae151cfe6e0bae407bd6e099500a00a6d4db9546)
@@ -127,5 +127,5 @@
 	struct __attribute__((aligned(64))) io_context$ {
 		io_arbiter$ * arbiter;
-		processor * proc;
+		struct processor * proc;
 
 		__outstanding_io_queue ext_sq;
Index: libcfa/src/concurrency/kernel/cluster.cfa
===================================================================
--- libcfa/src/concurrency/kernel/cluster.cfa	(revision f70497477cb004d4a38efd1b4002ed60f98f1997)
+++ libcfa/src/concurrency/kernel/cluster.cfa	(revision ae151cfe6e0bae407bd6e099500a00a6d4db9546)
@@ -254,6 +254,6 @@
 }
 
-static void assign_list(unsigned & valrq, unsigned & valio, dlist(processor) & list, unsigned count) {
-	processor * it = &list`first;
+static void assign_list(unsigned & valrq, unsigned & valio, dlist(struct processor) & list, unsigned count) {
+	struct processor * it = &list`first;
 	for(unsigned i = 0; i < count; i++) {
 		/* paranoid */ verifyf( it, "Unexpected null iterator, at index %u of %u\n", i, count);
@@ -278,6 +278,6 @@
 
 #if defined(CFA_HAVE_LINUX_IO_URING_H)
-	static void assign_io(io_context$ ** data, size_t count, dlist(processor) & list) {
-		processor * it = &list`first;
+	static void assign_io(io_context$ ** data, size_t count, dlist(struct processor) & list) {
+		struct processor * it = &list`first;
 		while(it) {
 			/* paranoid */ verifyf( it, "Unexpected null iterator\n");
Index: libcfa/src/concurrency/kernel/cluster.hfa
===================================================================
--- libcfa/src/concurrency/kernel/cluster.hfa	(revision f70497477cb004d4a38efd1b4002ed60f98f1997)
+++ libcfa/src/concurrency/kernel/cluster.hfa	(revision ae151cfe6e0bae407bd6e099500a00a6d4db9546)
@@ -21,8 +21,11 @@
 #include <limits.h>
 
+#include "clock.hfa"
+
 //-----------------------------------------------------------------------
 // Calc moving average based on existing average, before and current time.
-static inline unsigned long long moving_average(unsigned long long currtsc, unsigned long long instsc, unsigned long long old_avg) {
-	/* paranoid */ verifyf( old_avg < 15000000000000, "Suspiciously large previous average: %'llu (%llx)\n", old_avg, old_avg );
+static inline unsigned long long moving_average(unsigned long long currtsc, unsigned long long instsc, unsigned long long old_avg, bool strict) {
+	(void)strict; // disable the warning around the fact this is unused in release.
+	/* paranoid */ warnf( !strict || old_avg < 33_000_000_000, "Suspiciously large previous average: %'llu (%llx), %'ldms \n", old_avg, old_avg, program()`ms );
 
 	const unsigned long long new_val = currtsc > instsc ? currtsc - instsc : 0;
@@ -31,13 +34,15 @@
 	const unsigned long long old_weight = total_weight - new_weight;
 	const unsigned long long ret = ((new_weight * new_val) + (old_weight * old_avg)) / total_weight;
+
+	/* paranoid */ warnf( !strict || ret < 33_000_000_000, "Suspiciously large new average after %'ldms cputime: %'llu (%llx) from %'llu-%'llu (%'llu, %'llu) and %'llu\n", program()`ms, ret, ret, currtsc, instsc, new_val, new_val / 1000000, old_avg );
 	return ret;
 }
 
-static inline void touch_tsc(__timestamp_t * tscs, size_t idx, unsigned long long ts_prev, unsigned long long ts_next) {
+static inline void touch_tsc(__timestamp_t * tscs, size_t idx, unsigned long long ts_prev, unsigned long long ts_next, bool strict) {
 	if (ts_next == ULLONG_MAX) return;
 	unsigned long long now = rdtscl();
 	unsigned long long pma = __atomic_load_n(&tscs[ idx ].t.ma, __ATOMIC_RELAXED);
 	__atomic_store_n(&tscs[ idx ].t.tv, ts_next, __ATOMIC_RELAXED);
-	__atomic_store_n(&tscs[ idx ].t.ma, moving_average(now, ts_prev, pma), __ATOMIC_RELAXED);
+	__atomic_store_n(&tscs[ idx ].t.ma, moving_average(now, ts_prev, pma, strict), __ATOMIC_RELAXED);
 }
 
@@ -51,5 +56,6 @@
 	Data_t * data,
 	__timestamp_t * tscs,
-	const unsigned shard_factor
+	const unsigned shard_factor,
+	bool strict
 ) {
 	unsigned start = procid;
@@ -59,5 +65,5 @@
 		if(ptsc != ULLONG_MAX) {
 			/* paranoid */ verify( start + i < count );
-			unsigned long long tsc = moving_average(ctsc, ptsc, tscs[start + i].t.ma);
+			unsigned long long tsc = moving_average(ctsc, ptsc, tscs[start + i].t.ma, strict);
 			if(tsc > max) max = tsc;
 		}
Index: libcfa/src/concurrency/preemption.cfa
===================================================================
--- libcfa/src/concurrency/preemption.cfa	(revision f70497477cb004d4a38efd1b4002ed60f98f1997)
+++ libcfa/src/concurrency/preemption.cfa	(revision ae151cfe6e0bae407bd6e099500a00a6d4db9546)
@@ -104,5 +104,5 @@
 static inline alarm_node_t * get_expired( alarm_list_t * alarms, Time currtime ) {
 	if( ! & (*alarms)`first ) return 0p;						// If no alarms return null
-	if( (*alarms)`first.timeval >= currtime ) return 0p;	// If alarms head not expired return null
+	if( (*alarms)`first.deadline >= currtime ) return 0p;	// If alarms head not expired return null
 	return pop(alarms);									// Otherwise just pop head
 }
@@ -140,5 +140,5 @@
 		if( period > 0 ) {
 			__cfadbg_print_buffer_local( preemption, " KERNEL: alarm period is %lu.\n", period`ns );
-			node->timeval = currtime + period;  // Alarm is periodic, add currtime to it (used cached current time)
+			node->deadline = currtime + period;  // Alarm is periodic, add currtime to it (used cached current time)
 			insert( alarms, node );             // Reinsert the node for the next time it triggers
 		}
@@ -147,5 +147,5 @@
 	// If there are still alarms pending, reset the timer
 	if( & (*alarms)`first ) {
-		Duration delta = (*alarms)`first.timeval - currtime;
+		Duration delta = (*alarms)`first.deadline - currtime;
 		__kernel_set_timer( delta );
 	}
Index: libcfa/src/concurrency/ready_queue.cfa
===================================================================
--- libcfa/src/concurrency/ready_queue.cfa	(revision f70497477cb004d4a38efd1b4002ed60f98f1997)
+++ libcfa/src/concurrency/ready_queue.cfa	(revision ae151cfe6e0bae407bd6e099500a00a6d4db9546)
@@ -62,5 +62,5 @@
 //-----------------------------------------------------------------------
 __attribute__((hot)) void push(struct cluster * cltr, struct thread$ * thrd, unpark_hint hint) with (cltr->sched) {
-	processor * const proc = kernelTLS().this_processor;
+	struct processor * const proc = kernelTLS().this_processor;
 	const bool external = (!proc) || (cltr != proc->cltr);
 	const bool remote   = hint == UNPARK_REMOTE;
@@ -116,5 +116,5 @@
 	/* paranoid */ verify( kernelTLS().this_processor->rdq.id < lanes_count );
 
-	processor * const proc = kernelTLS().this_processor;
+	struct processor * const proc = kernelTLS().this_processor;
 	unsigned this = proc->rdq.id;
 	/* paranoid */ verify( this < lanes_count );
@@ -139,6 +139,6 @@
 		/* paranoid */ verify( readyQ.tscs[target].t.tv != ULLONG_MAX );
 		if(target < lanes_count) {
-			const unsigned long long cutoff = calc_cutoff(ctsc, proc->rdq.id, lanes_count, cltr->sched.readyQ.data, cltr->sched.readyQ.tscs, __shard_factor.readyq);
-			const unsigned long long age = moving_average(ctsc, readyQ.tscs[target].t.tv, readyQ.tscs[target].t.ma);
+			const unsigned long long cutoff = calc_cutoff(ctsc, proc->rdq.id, lanes_count, cltr->sched.readyQ.data, cltr->sched.readyQ.tscs, __shard_factor.readyq, true);
+			const unsigned long long age = moving_average(ctsc, readyQ.tscs[target].t.tv, readyQ.tscs[target].t.ma, false);
 			__cfadbg_print_safe(ready_queue, "Kernel : Help attempt on %u from %u, age %'llu vs cutoff %'llu, %s\n", target, this, age, cutoff, age > cutoff ? "yes" : "no");
 			if(age > cutoff) {
@@ -214,5 +214,5 @@
 	__STATS( stats.success++; )
 
-	touch_tsc(readyQ.tscs, w, ts_prev, ts_next);
+	touch_tsc(readyQ.tscs, w, ts_prev, ts_next, true);
 
 	thrd->preferred = w / __shard_factor.readyq;
Index: libcfa/src/stdhdr/assert.h
===================================================================
--- libcfa/src/stdhdr/assert.h	(revision f70497477cb004d4a38efd1b4002ed60f98f1997)
+++ libcfa/src/stdhdr/assert.h	(revision ae151cfe6e0bae407bd6e099500a00a6d4db9546)
@@ -27,16 +27,19 @@
 	#define assertf( expr, fmt, ... ) ((expr) ? ((void)0) : __assert_fail_f(__VSTRINGIFY__(expr), __FILE__, __LINE__, __PRETTY_FUNCTION__, fmt, ## __VA_ARGS__ ))
 
+	void __assert_warn_f( const char assertion[], const char file[], unsigned int line, const char function[], const char fmt[], ... ) __attribute__((format( printf, 5, 6) ));
 	void __assert_fail_f( const char assertion[], const char file[], unsigned int line, const char function[], const char fmt[], ... ) __attribute__((noreturn, format( printf, 5, 6) ));
 #endif
 
 #if !defined(NDEBUG) && (defined(__CFA_DEBUG__) || defined(__CFA_VERIFY__))
+	#define __CFA_WITH_VERIFY__
 	#define verify(x) assert(x)
 	#define verifyf(x, ...) assertf(x, __VA_ARGS__)
 	#define verifyfail(...)
-	#define __CFA_WITH_VERIFY__
+	#define warnf( expr, fmt, ... ) ({ static bool check_once##__LINE__ = false; if( false == check_once##__LINE__ && false == (expr)) { check_once##__LINE__ = true; __assert_warn_f(__VSTRINGIFY__(expr), __FILE__, __LINE__, __PRETTY_FUNCTION__, fmt, ## __VA_ARGS__ ); } })
 #else
 	#define verify(x)
 	#define verifyf(x, ...)
 	#define verifyfail(...)
+	#define warnf( expr, fmt, ... )
 #endif
 
Index: src/Tuples/TupleExpansionNew.cpp
===================================================================
--- src/Tuples/TupleExpansionNew.cpp	(revision f70497477cb004d4a38efd1b4002ed60f98f1997)
+++ src/Tuples/TupleExpansionNew.cpp	(revision ae151cfe6e0bae407bd6e099500a00a6d4db9546)
@@ -10,6 +10,6 @@
 // Created On       : Mon Aug 23 15:36:09 2021
 // Last Modified By : Andrew Beach
-// Last Modified On : Tue Sep 20 16:17:00 2022
-// Update Count     : 4
+// Last Modified On : Mon Sep 26 10:25:00 2022
+// Update Count     : 5
 //
 
@@ -30,5 +30,27 @@
 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
+	// Not a vector, because they may not be adding in increasing order.
+	std::map< int, ast::ptr<ast::Expr> > decls;
+};
+
+/// Replaces Tuple Assign & Index Expressions, and Tuple Types.
+struct TupleMainExpander final :
+		public ast::WithCodeLocation,
+		public ast::WithDeclsToAdd<>,
+		public ast::WithGuards,
+		public ast::WithVisitorRef<TupleMainExpander> {
+	ast::Expr const * postvisit( ast::TupleAssignExpr const * expr );
+
+	void previsit( ast::CompoundStmt const * );
+	ast::Expr const * postvisit( ast::Expr const * expr );
+	ast::Type const * postvisit( ast::TupleType const * type );
+
+	ast::Expr const * postvisit( ast::TupleIndexExpr const * expr );
+private:
+	ScopedMap< int, ast::StructDecl const * > typeMap;
+};
+
+struct TupleExprExpander final {
+	ast::Expr const * postvisit( ast::TupleExpr const * expr );
 };
 
@@ -100,118 +122,120 @@
 }
 
-/// Replaces Tuple Assign & Index Expressions, and Tuple Types.
-struct TupleMainExpander final :
-		public ast::WithCodeLocation,
-		public ast::WithDeclsToAdd<>,
-		public ast::WithGuards,
-		public ast::WithVisitorRef<TupleMainExpander> {
-	ast::Expr const * postvisit( ast::TupleAssignExpr const * expr ) {
-		// Just move the env on the new top level expression.
-		return ast::mutate_field( expr->stmtExpr.get(),
-			&ast::TupleAssignExpr::env, expr->env.get() );
-	}
-
-	void previsit( ast::CompoundStmt const * ) {
-		GuardScope( typeMap );
-	}
-
-	ast::Expr const * postvisit( ast::Expr const * expr ) {
-		if ( nullptr == expr->env ) {
-			return expr;
-		}
-		// TypeSubstitutions are never visited in the new Pass template,
-		// so it is done manually here, where all types have to be replaced.
-		return ast::mutate_field( expr, &ast::Expr::env,
-			expr->env->accept( *visitor ) );
-	}
-
-	ast::Type const * postvisit( ast::TupleType const * type ) {
-		assert( location );
-		unsigned tupleSize = type->size();
-		if ( !typeMap.count( tupleSize ) ) {
-			ast::StructDecl * decl = new ast::StructDecl( *location,
-				toString( "_tuple", tupleSize, "_" )
+// Handles expansion of tuple assignment.
+ast::Expr const * TupleMainExpander::postvisit(
+		ast::TupleAssignExpr const * expr ) {
+	// Just move the env on the new top level expression.
+	return ast::mutate_field( expr->stmtExpr.get(),
+		&ast::TupleAssignExpr::env, expr->env.get() );
+}
+
+// Context information for tuple type expansion.
+void TupleMainExpander::previsit( ast::CompoundStmt const * ) {
+	GuardScope( typeMap );
+}
+
+// Make sure types in a TypeSubstitution are expanded.
+ast::Expr const * TupleMainExpander::postvisit( ast::Expr const * expr ) {
+	if ( nullptr == expr->env ) {
+		return expr;
+	}
+	return ast::mutate_field( expr, &ast::Expr::env,
+		expr->env->accept( *visitor ) );
+}
+
+// Create a generic tuple structure of a given size.
+ast::StructDecl * createTupleStruct(
+		unsigned int tupleSize, CodeLocation const & location ) {
+	ast::StructDecl * decl = new ast::StructDecl( location,
+		toString( "_tuple", tupleSize, "_" )
+	);
+	decl->body = true;
+
+	for ( size_t i = 0 ; i < tupleSize ; ++i ) {
+		ast::TypeDecl * typeParam = new ast::TypeDecl( location,
+			toString( "tuple_param_", tupleSize, "_", i ),
+			ast::Storage::Classes(),
+			nullptr,
+			ast::TypeDecl::Dtype,
+			true
 			);
-			decl->body = true;
-
-			for ( size_t i = 0 ; i < tupleSize ; ++i ) {
-				ast::TypeDecl * typeParam = new ast::TypeDecl( *location,
-					toString( "tuple_param_", tupleSize, "_", i ),
-					ast::Storage::Classes(),
-					nullptr,
-					ast::TypeDecl::Dtype,
-					true
-					);
-				ast::ObjectDecl * member = new ast::ObjectDecl( *location,
-					toString( "field_", i ),
-					new ast::TypeInstType( typeParam )
-					);
-				decl->params.push_back( typeParam );
-				decl->members.push_back( member );
-			}
-
-			// Empty structures are not standard C. Add a dummy field to
-			// empty tuples to silence warnings when a compound literal
-			// `_tuple0_` is created.
-			if ( tupleSize == 0 ) {
-				decl->members.push_back(
-					new ast::ObjectDecl( *location,
-						"dummy",
-						new ast::BasicType( ast::BasicType::SignedInt ),
-						nullptr,
-						ast::Storage::Classes(),
-						// Does this have to be a C linkage?
-						ast::Linkage::C
-					)
-				);
-			}
-			typeMap[tupleSize] = decl;
-			declsToAddBefore.push_back( decl );
-		}
-
-		ast::StructDecl const * decl = typeMap[ tupleSize ];
-		ast::StructInstType * newType =
-			new ast::StructInstType( decl, type->qualifiers );
-		for ( auto pair : group_iterate( type->types, decl->params ) ) {
-			ast::Type const * t = std::get<0>( pair );
-			newType->params.push_back(
-				new ast::TypeExpr( *location, ast::deepCopy( t ) ) );
-		}
-		return newType;
-	}
-
-	ast::Expr const * postvisit( ast::TupleIndexExpr const * expr ) {
-		CodeLocation const & location = expr->location;
-		ast::Expr const * tuple = expr->tuple.get();
-		assert( tuple );
-		unsigned int index = expr->index;
-		ast::TypeSubstitution const * env = expr->env.get();
-
-		if ( auto tupleExpr = dynamic_cast<ast::TupleExpr const *>( tuple ) ) {
-			// Optimization: If it is a definitely pure tuple expr,
-			// then it can reduce to the only relevant component.
-			if ( !maybeImpureIgnoreUnique( tupleExpr ) ) {
-				assert( index < tupleExpr->exprs.size() );
-				ast::ptr<ast::Expr> const & expr =
-					*std::next( tupleExpr->exprs.begin(), index );
-				ast::Expr * ret = ast::mutate( expr.get() );
-				ret->env = env;
-				return ret;
-			}
-		}
-
-		auto type = tuple->result.strict_as<ast::StructInstType>();
-		ast::StructDecl const * structDecl = type->base;
-		assert( index < structDecl->members.size() );
-		ast::ptr<ast::Decl> const & member =
-			*std::next( structDecl->members.begin(), index );
-		ast::MemberExpr * memberExpr = new ast::MemberExpr( location,
-			member.strict_as<ast::DeclWithType>(), tuple );
-		memberExpr->env = env;
-		return memberExpr;
-	}
-private:
-	ScopedMap< int, ast::StructDecl const * > typeMap;
-};
+		ast::ObjectDecl * member = new ast::ObjectDecl( location,
+			toString( "field_", i ),
+			new ast::TypeInstType( typeParam )
+			);
+		decl->params.push_back( typeParam );
+		decl->members.push_back( member );
+	}
+
+	// Empty structures are not standard C. Add a dummy field to
+	// empty tuples to silence warnings when a compound literal
+	// `_tuple0_` is created.
+	if ( tupleSize == 0 ) {
+		decl->members.push_back(
+			new ast::ObjectDecl( location,
+				"dummy",
+				new ast::BasicType( ast::BasicType::SignedInt ),
+				nullptr,
+				ast::Storage::Classes(),
+				// Does this have to be a C linkage?
+				ast::Linkage::C
+			)
+		);
+	}
+	return decl;
+}
+
+ast::Type const * TupleMainExpander::postvisit( ast::TupleType const * type ) {
+	assert( location );
+	unsigned tupleSize = type->size();
+	if ( !typeMap.count( tupleSize ) ) {
+		ast::StructDecl * decl = createTupleStruct( tupleSize, *location );
+		typeMap[tupleSize] = decl;
+		declsToAddBefore.push_back( decl );
+	}
+
+	ast::StructDecl const * decl = typeMap[ tupleSize ];
+	ast::StructInstType * newType =
+		new ast::StructInstType( decl, type->qualifiers );
+	for ( auto pair : group_iterate( type->types, decl->params ) ) {
+		ast::Type const * t = std::get<0>( pair );
+		newType->params.push_back(
+			new ast::TypeExpr( *location, ast::deepCopy( t ) ) );
+	}
+	return newType;
+}
+
+// Expand a tuple index into a field access in the underlying structure.
+ast::Expr const * TupleMainExpander::postvisit(
+		ast::TupleIndexExpr const * expr ) {
+	CodeLocation const & location = expr->location;
+	ast::Expr const * tuple = expr->tuple.get();
+	assert( tuple );
+	unsigned int index = expr->index;
+	ast::TypeSubstitution const * env = expr->env.get();
+
+	if ( auto tupleExpr = dynamic_cast<ast::TupleExpr const *>( tuple ) ) {
+		// Optimization: If it is a definitely pure tuple expr,
+		// then it can reduce to the only relevant component.
+		if ( !maybeImpureIgnoreUnique( tupleExpr ) ) {
+			assert( index < tupleExpr->exprs.size() );
+			ast::ptr<ast::Expr> const & expr =
+				*std::next( tupleExpr->exprs.begin(), index );
+			ast::Expr * ret = ast::mutate( expr.get() );
+			ret->env = env;
+			return ret;
+		}
+	}
+
+	auto type = tuple->result.strict_as<ast::StructInstType>();
+	ast::StructDecl const * structDecl = type->base;
+	assert( index < structDecl->members.size() );
+	ast::ptr<ast::Decl> const & member =
+		*std::next( structDecl->members.begin(), index );
+	ast::MemberExpr * memberExpr = new ast::MemberExpr( location,
+		member.strict_as<ast::DeclWithType>(), tuple );
+	memberExpr->env = env;
+	return memberExpr;
+}
 
 ast::Expr const * replaceTupleExpr(
@@ -249,10 +273,8 @@
 }
 
-struct TupleExprExpander final {
-	ast::Expr const * postvisit( ast::TupleExpr const * expr ) {
-		return replaceTupleExpr( expr->location,
-			expr->result, expr->exprs, expr->env );
-	}
-};
+ast::Expr const * TupleExprExpander::postvisit( ast::TupleExpr const * expr ) {
+	return replaceTupleExpr( expr->location,
+		expr->result, expr->exprs, expr->env );
+}
 
 } // namespace
Index: sts/.expect/parseconfig.txt
===================================================================
--- tests/.expect/parseconfig.txt	(revision f70497477cb004d4a38efd1b4002ed60f98f1997)
+++ 	(revision )
@@ -1,33 +1,0 @@
-Different types of destination addresses
-Stop cost: 1
-Number of students: 2
-Number of stops: 2
-Maximum number of students: 5
-Timer delay: 2
-Groupoff delay: 10
-Conductor delay: 5
-Parental delay: 5
-Number of couriers: 1
-Maximum student delay: 10
-Maximum student trips: 3
-
-Open_Failure thrown when config file does not exist
-Failed to open the config file
-
-Missing_Config_Entries thrown when config file is missing entries we want
-The config file is missing 1 entry.
-
-Parse_Failure thrown when an entry cannot be parsed
-Config entry AnothaOne could not be parsed. It has value DjKhaled.
-
-Validation_Failure thrown when an entry fails validation
-Config entry StopCost could not be validated. It has value -1.
-
-No error is thrown when validation succeeds
-Stop cost: 1
-
-A custom parse function can be accepted
-Stop cost: 100
-
-Custom parse and validation functions can be provided together
-Stop cost: 100
Index: sts/.in/parseconfig-all.txt
===================================================================
--- tests/.in/parseconfig-all.txt	(revision f70497477cb004d4a38efd1b4002ed60f98f1997)
+++ 	(revision )
@@ -1,12 +1,0 @@
-StopCost				1	# amount to charge per train stop
-NumStudents				2	# number of students to create
-NumStops				2	# number of train stops; minimum of 2
-MaxNumStudents 			5  	# maximum students each train can carry
-TimerDelay 				2	# length of time between each tick of the timer
-# Going to add a comment here
-MaxStudentDelay			10	# maximum random student delay between trips
-MaxStudentTrips 		3	# maximum number of train trips each student takes
-GroupoffDelay			10	# length of time between initializing gift cards
-ConductorDelay			5  	# length of time between checking on passenger POPs
-ParentalDelay			5	# length of time between cash deposits
-NumCouriers				1	# number of WATCard office couriers in the pool
Index: sts/.in/parseconfig-errors.txt
===================================================================
--- tests/.in/parseconfig-errors.txt	(revision f70497477cb004d4a38efd1b4002ed60f98f1997)
+++ 	(revision )
@@ -1,12 +1,0 @@
-StopCost				-1	# amount to charge per train stop
-NumStudents				2	# number of students to create
-NumStops				2	# number of train stops; minimum of 2
-MaxNumStudents 			5  	# maximum students each train can carry
-TimerDelay 				2	# length of time between each tick of the timer
-MaxStudentDelay			10	# maximum random student delay between trips
-MaxStudentTrips 		3	# maximum number of train trips each student takes
-GroupoffDelay			10	# length of time between initializing gift cards
-ConductorDelay			5  	# length of time between checking on passenger POPs
-ParentalDelay			5	# length of time between cash deposits
-NumCouriers				1	# number of WATCard office couriers in the pool
-AnothaOne               DjKhaled    # this one will not be used by the user
Index: sts/.in/parseconfig-missing.txt
===================================================================
--- tests/.in/parseconfig-missing.txt	(revision f70497477cb004d4a38efd1b4002ed60f98f1997)
+++ 	(revision )
@@ -1,12 +1,0 @@
-StopCost				-1	# amount to charge per train stop
-NumStudents				2	# number of students to create
-NumStops				2	# number of train stops; minimum of 2
-MaxNumStudents 			5  	# maximum students each train can carry
-TimerDelay 				2	# length of time between each tick of the timer
-MaxStudentDelay			10	# maximum random student delay between trips
-MaxStudentTrips 		3	# maximum number of train trips each student takes
-GroupoffDelay			10	# length of time between initializing gift cards
-ConductorDelay			5  	# length of time between checking on passenger POPs
-# ParentalDelay			5	# length of time between cash deposits
-NumCouriers				1	# number of WATCard office couriers in the pool
-# Notice I've commented out one of the wanted entries
Index: tests/configs/.expect/parseconfig.txt
===================================================================
--- tests/configs/.expect/parseconfig.txt	(revision ae151cfe6e0bae407bd6e099500a00a6d4db9546)
+++ tests/configs/.expect/parseconfig.txt	(revision ae151cfe6e0bae407bd6e099500a00a6d4db9546)
@@ -0,0 +1,33 @@
+Different types of destination addresses
+Stop cost: 1
+Number of students: 2
+Number of stops: 2
+Maximum number of students: 5
+Timer delay: 2
+Groupoff delay: 10
+Conductor delay: 5
+Parental delay: 5
+Number of couriers: 1
+Maximum student delay: 10
+Maximum student trips: 3
+
+Open_Failure thrown when config file does not exist
+Failed to open the config file
+
+Missing_Config_Entries thrown when config file is missing entries we want
+The config file is missing 1 entry.
+
+Parse_Failure thrown when an entry cannot be parsed
+Config entry AnothaOne could not be parsed. It has value DjKhaled.
+
+Validation_Failure thrown when an entry fails validation
+Config entry StopCost could not be validated. It has value -1.
+
+No error is thrown when validation succeeds
+Stop cost: 1
+
+A custom parse function can be accepted
+Stop cost: 100
+
+Custom parse and validation functions can be provided together
+Stop cost: 100
Index: tests/configs/.in/parseconfig-all.txt
===================================================================
--- tests/configs/.in/parseconfig-all.txt	(revision ae151cfe6e0bae407bd6e099500a00a6d4db9546)
+++ tests/configs/.in/parseconfig-all.txt	(revision ae151cfe6e0bae407bd6e099500a00a6d4db9546)
@@ -0,0 +1,12 @@
+StopCost				1	# amount to charge per train stop
+NumStudents				2	# number of students to create
+NumStops				2	# number of train stops; minimum of 2
+MaxNumStudents 			5  	# maximum students each train can carry
+TimerDelay 				2	# length of time between each tick of the timer
+# Going to add a comment here
+MaxStudentDelay			10	# maximum random student delay between trips
+MaxStudentTrips 		3	# maximum number of train trips each student takes
+GroupoffDelay			10	# length of time between initializing gift cards
+ConductorDelay			5  	# length of time between checking on passenger POPs
+ParentalDelay			5	# length of time between cash deposits
+NumCouriers				1	# number of WATCard office couriers in the pool
Index: tests/configs/.in/parseconfig-errors.txt
===================================================================
--- tests/configs/.in/parseconfig-errors.txt	(revision ae151cfe6e0bae407bd6e099500a00a6d4db9546)
+++ tests/configs/.in/parseconfig-errors.txt	(revision ae151cfe6e0bae407bd6e099500a00a6d4db9546)
@@ -0,0 +1,12 @@
+StopCost				-1	# amount to charge per train stop
+NumStudents				2	# number of students to create
+NumStops				2	# number of train stops; minimum of 2
+MaxNumStudents 			5  	# maximum students each train can carry
+TimerDelay 				2	# length of time between each tick of the timer
+MaxStudentDelay			10	# maximum random student delay between trips
+MaxStudentTrips 		3	# maximum number of train trips each student takes
+GroupoffDelay			10	# length of time between initializing gift cards
+ConductorDelay			5  	# length of time between checking on passenger POPs
+ParentalDelay			5	# length of time between cash deposits
+NumCouriers				1	# number of WATCard office couriers in the pool
+AnothaOne               DjKhaled    # this one will not be used by the user
Index: tests/configs/.in/parseconfig-missing.txt
===================================================================
--- tests/configs/.in/parseconfig-missing.txt	(revision ae151cfe6e0bae407bd6e099500a00a6d4db9546)
+++ tests/configs/.in/parseconfig-missing.txt	(revision ae151cfe6e0bae407bd6e099500a00a6d4db9546)
@@ -0,0 +1,12 @@
+StopCost				-1	# amount to charge per train stop
+NumStudents				2	# number of students to create
+NumStops				2	# number of train stops; minimum of 2
+MaxNumStudents 			5  	# maximum students each train can carry
+TimerDelay 				2	# length of time between each tick of the timer
+MaxStudentDelay			10	# maximum random student delay between trips
+MaxStudentTrips 		3	# maximum number of train trips each student takes
+GroupoffDelay			10	# length of time between initializing gift cards
+ConductorDelay			5  	# length of time between checking on passenger POPs
+# ParentalDelay			5	# length of time between cash deposits
+NumCouriers				1	# number of WATCard office couriers in the pool
+# Notice I've commented out one of the wanted entries
Index: tests/configs/parseconfig.cfa
===================================================================
--- tests/configs/parseconfig.cfa	(revision ae151cfe6e0bae407bd6e099500a00a6d4db9546)
+++ tests/configs/parseconfig.cfa	(revision ae151cfe6e0bae407bd6e099500a00a6d4db9546)
@@ -0,0 +1,146 @@
+#include <fstream.hfa>
+#include <parseconfig.hfa>
+#include <stdlib.hfa>
+
+extern "C" {
+	extern long long int strtoll( const char* str, char** endptr, int base );
+}
+
+#define xstr(s) str(s)
+#define str(s) #s
+
+bool custom_parse( const char * arg, int & value ) {
+	char * end;
+	int r = strtoll( arg, &end, 10 );
+  if ( *end != '\0' ) return false;
+
+	value = r + 99;
+	return true;
+}
+
+int main() {
+	struct {
+        int stop_cost;
+        int num_students;
+        int num_stops;
+        int max_num_students;
+        int timer_delay;
+        int groupoff_delay;
+    } config_params;
+    int conductor_delay;
+    [2] int parental_delay_and_num_couriers;
+    [ int, int ] max_student_delay_and_trips;
+
+	const size_t NUM_ENTRIES = 11;
+	config_entry entries[NUM_ENTRIES] = {
+		{ "StopCost", config_params.stop_cost },
+        { "NumStudents", config_params.num_students },
+        { "NumStops", config_params.num_stops },
+        { "MaxNumStudents", config_params.max_num_students },
+        { "TimerDelay", config_params.timer_delay },
+        { "GroupoffDelay", config_params.groupoff_delay },
+        { "ConductorDelay", conductor_delay },
+        { "ParentalDelay", parental_delay_and_num_couriers[0] },
+        { "NumCouriers", parental_delay_and_num_couriers[1] },
+        { "MaxStudentDelay", max_student_delay_and_trips.0 },
+        { "MaxStudentTrips", max_student_delay_and_trips.1 }
+    };
+
+
+	sout | "Different types of destination addresses";
+
+	parse_config( xstr(IN_DIR) "parseconfig-all.txt", entries, NUM_ENTRIES, parse_tabular_config_format );
+
+    sout | "Stop cost: " | config_params.stop_cost;
+    sout | "Number of students: " | config_params.num_students;
+    sout | "Number of stops: " | config_params.num_stops;
+    sout | "Maximum number of students: " | config_params.max_num_students;
+    sout | "Timer delay: " | config_params.timer_delay;
+    sout | "Groupoff delay: " | config_params.groupoff_delay;
+    sout | "Conductor delay: " | conductor_delay;
+    sout | "Parental delay: " | parental_delay_and_num_couriers[0];
+    sout | "Number of couriers: " | parental_delay_and_num_couriers[1];
+    sout | "Maximum student delay: " | max_student_delay_and_trips.0;
+    sout | "Maximum student trips: " | max_student_delay_and_trips.1;
+	sout | nl;
+
+
+	sout | "Open_Failure thrown when config file does not exist";
+	try {
+		parse_config( xstr(IN_DIR) "doesnt-exist.txt", entries, NUM_ENTRIES, parse_tabular_config_format );
+	} catch( Open_Failure * ex ) {
+		sout | "Failed to open the config file";
+	}
+	sout | nl;
+
+
+	sout | "Missing_Config_Entries thrown when config file is missing entries we want";
+	try {
+		parse_config( xstr(IN_DIR) "parseconfig-missing.txt", entries, NUM_ENTRIES, parse_tabular_config_format );
+	} catch( Missing_Config_Entries * ex ) {
+		msg( ex );
+	}
+	sout | nl;
+
+
+	sout | "Parse_Failure thrown when an entry cannot be parsed";
+
+	int non_int_val;
+	config_entry entry[1] = {
+		{ "AnothaOne", non_int_val }
+	};
+
+	try {
+		parse_config( xstr(IN_DIR) "parseconfig-errors.txt", entry, 1, parse_tabular_config_format );
+	} catch( Parse_Failure * ex ) {
+		msg( ex );
+	}
+	sout | nl;
+
+
+	sout | "Validation_Failure thrown when an entry fails validation";
+
+	// TODO: Fix compiler bug that makes casting necessary
+	config_entry new_entry1 = { "StopCost", config_params.stop_cost, (bool (*)(int &))is_positive };
+	entries[0] = new_entry1;
+
+	try {
+		parse_config( xstr(IN_DIR) "parseconfig-errors.txt", entries, NUM_ENTRIES, parse_tabular_config_format );
+	} catch( Validation_Failure * ex ) {
+		msg( ex );
+	}
+	sout | nl;
+
+
+	sout | "No error is thrown when validation succeeds";
+	config_params.stop_cost = -1; // Reset value
+	parse_config( xstr(IN_DIR) "parseconfig-all.txt", entries, NUM_ENTRIES, parse_tabular_config_format );
+	sout | "Stop cost: " | config_params.stop_cost;
+	sout | nl;
+
+
+	sout | "A custom parse function can be accepted";
+
+	config_entry new_entry2 = { "StopCost", config_params.stop_cost, custom_parse };
+	entries[0] = new_entry2;
+
+	config_params.stop_cost = -1; // Reset value
+	parse_config( xstr(IN_DIR) "parseconfig-all.txt", entries, NUM_ENTRIES, parse_tabular_config_format );
+
+	sout | "Stop cost: " | config_params.stop_cost;
+	sout | nl;
+
+
+	sout | "Custom parse and validation functions can be provided together";
+
+	// TODO: Fix compiler bug that makes casting necessary
+	config_entry new_entry3 = { "StopCost", config_params.stop_cost, custom_parse, (bool (*)(int &))is_positive };
+	entries[0] = new_entry3;
+
+	config_params.stop_cost = -1; // Reset value
+	parse_config( xstr(IN_DIR) "parseconfig-all.txt", entries, NUM_ENTRIES, parse_tabular_config_format );
+
+	sout | "Stop cost: " | config_params.stop_cost;
+
+	exit( EXIT_SUCCESS );  // This is to avoid memory leak messages from the above exceptions
+}
Index: tests/meta/.expect/fork+exec.txt
===================================================================
--- tests/meta/.expect/fork+exec.txt	(revision ae151cfe6e0bae407bd6e099500a00a6d4db9546)
+++ tests/meta/.expect/fork+exec.txt	(revision ae151cfe6e0bae407bd6e099500a00a6d4db9546)
@@ -0,0 +1,44 @@
+no arg:
+arguments are:
+    None
+Success!
+Child status:
+    WIFEXITED   : 1
+    WEXITSTATUS : 0
+    WIFSIGNALED : 0
+    WTERMSIG    : 0
+    WCOREDUMP   : 0
+    WIFSTOPPED  : 0
+    WSTOPSIG    : 0
+    WIFCONTINUED: 0
+1 arg:
+arguments are:
+    'Hello World!'
+Success!
+Child status:
+    WIFEXITED   : 1
+    WEXITSTATUS : 0
+    WIFSIGNALED : 0
+    WTERMSIG    : 0
+    WCOREDUMP   : 0
+    WIFSTOPPED  : 0
+    WSTOPSIG    : 0
+    WIFCONTINUED: 0
+5 arg:
+arguments are:
+    'Hi,'
+    'my'
+    'name'
+    'is'
+    'Fred'
+Success!
+Child status:
+    WIFEXITED   : 1
+    WEXITSTATUS : 0
+    WIFSIGNALED : 0
+    WTERMSIG    : 0
+    WCOREDUMP   : 0
+    WIFSTOPPED  : 0
+    WSTOPSIG    : 0
+    WIFCONTINUED: 0
+All Done!
Index: tests/meta/fork+exec.cfa
===================================================================
--- tests/meta/fork+exec.cfa	(revision ae151cfe6e0bae407bd6e099500a00a6d4db9546)
+++ tests/meta/fork+exec.cfa	(revision ae151cfe6e0bae407bd6e099500a00a6d4db9546)
@@ -0,0 +1,123 @@
+//
+// Cforall Version 1.0.0 Copyright (C) 2022 University of Waterloo
+//
+// The contents of this file are covered under the licence agreement in the
+// file "LICENCE" distributed with Cforall.
+//
+// fork+exec.cfa -- Check that we can use fork+exec to test parameters.
+//
+// Author           : Thierry Delisle
+// Created On       : Wed Sep 27 10:44:38 2022
+// Last Modified By :
+// Last Modified On :
+// Update Count     :
+//
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <errno.h>
+#include <signal.h>
+
+extern "C" {
+	#include <sys/types.h>
+	#include <sys/wait.h>
+	#include <unistd.h>
+}
+
+int true_main(const char * exec);
+
+int main(int argc, char * argv[]) {
+	if(!getenv("CFATEST_FORK_EXEC_TEXT")) return true_main(argv[0]);
+
+	printf("arguments are:\n");
+	if(argc == 1) printf("    None\n");
+	for(int i = 1; i < argc; i++) {
+		printf("    '%s'\n", argv[i]);
+	}
+
+	printf("Success!\n");
+	fflush(stdout);
+	return 0;
+}
+
+int do_wait(pid_t pid) {
+	int wstatus;
+	int options = 0;
+	pid_t ret = waitpid(pid, &wstatus, options);
+	fflush(stdout);
+	if(ret < 0) {
+		fprintf(stderr, "Fork returned with error: %d '%s'\n", errno, strerror(errno));
+		exit(1);
+	}
+	return wstatus;
+}
+
+pid_t strict_fork(void) {
+	fflush(stdout);
+	pid_t ret = fork();
+	if(ret < 0) {
+		fprintf(stderr, "Fork returned with error: %d '%s'\n", errno, strerror(errno));
+		exit(1);
+	}
+	return ret;
+}
+
+void print_status(int wstatus) {
+	printf("Child status:\n");
+	printf("    WIFEXITED   : %d\n", WIFEXITED(wstatus));
+	printf("    WEXITSTATUS : %d\n", WEXITSTATUS(wstatus));
+	printf("    WIFSIGNALED : %d\n", WIFSIGNALED(wstatus));
+	printf("    WTERMSIG    : %d\n", WTERMSIG(wstatus));
+	printf("    WCOREDUMP   : %d\n", WCOREDUMP(wstatus));
+	printf("    WIFSTOPPED  : %d\n", WIFSTOPPED(wstatus));
+	printf("    WSTOPSIG    : %d\n", WSTOPSIG(wstatus));
+	printf("    WIFCONTINUED: %d\n", WIFCONTINUED(wstatus));
+}
+
+int true_main(const char * path) {
+	char * env[] = { "CFATEST_FORK_EXEC_TEXT=1", 0p };
+
+	printf("no arg:\n");
+	if(pid_t child = strict_fork(); child == 0) {
+		int ret = execle(path, path, (const char*)0p, env);
+		if(ret < 0) {
+			fprintf(stderr, "Execl 1 returned with error: %d '%s'\n", errno, strerror(errno));
+			exit(1);
+		}
+	}
+	else {
+		int status = do_wait(child);
+		print_status(status);
+	}
+
+	printf("1 arg:\n");
+	if(pid_t child = strict_fork(); child == 0) {
+		int ret = execle(path, path, "Hello World!", (const char*)0p, env);
+		if(ret < 0) {
+			fprintf(stderr, "Execl 2 returned with error: %d '%s'\n", errno, strerror(errno));
+			exit(1);
+		}
+	}
+	else {
+		int status = do_wait(child);
+		print_status(status);
+	}
+
+	printf("5 arg:\n");
+	if(pid_t child = strict_fork(); child == 0) {
+		int ret = execle(path, path, "Hi,", "my", "name", "is", "Fred", (const char*)0p, env);
+		if(ret < 0) {
+			fprintf(stderr, "Execl 3 returned with error: %d '%s'\n", errno, strerror(errno));
+			exit(1);
+		}
+	}
+	else {
+		int status = do_wait(child);
+		print_status(status);
+	}
+
+	printf("All Done!\n");
+
+	return 0;
+}
Index: sts/parseconfig.cfa
===================================================================
--- tests/parseconfig.cfa	(revision f70497477cb004d4a38efd1b4002ed60f98f1997)
+++ 	(revision )
@@ -1,146 +1,0 @@
-#include <fstream.hfa>
-#include <parseconfig.hfa>
-#include <stdlib.hfa>
-
-extern "C" {
-	extern long long int strtoll( const char* str, char** endptr, int base );
-}
-
-#define xstr(s) str(s)
-#define str(s) #s
-
-bool custom_parse( const char * arg, int & value ) {
-	char * end;
-	int r = strtoll( arg, &end, 10 );
-  if ( *end != '\0' ) return false;
-
-	value = r + 99;
-	return true;
-}
-
-int main() {
-	struct {
-        int stop_cost;
-        int num_students;
-        int num_stops;
-        int max_num_students;
-        int timer_delay;
-        int groupoff_delay;
-    } config_params;
-    int conductor_delay;
-    [2] int parental_delay_and_num_couriers;
-    [ int, int ] max_student_delay_and_trips;
-
-	const size_t NUM_ENTRIES = 11;
-	config_entry entries[NUM_ENTRIES] = {
-		{ "StopCost", config_params.stop_cost },
-        { "NumStudents", config_params.num_students },
-        { "NumStops", config_params.num_stops },
-        { "MaxNumStudents", config_params.max_num_students },
-        { "TimerDelay", config_params.timer_delay },
-        { "GroupoffDelay", config_params.groupoff_delay },
-        { "ConductorDelay", conductor_delay },
-        { "ParentalDelay", parental_delay_and_num_couriers[0] },
-        { "NumCouriers", parental_delay_and_num_couriers[1] },
-        { "MaxStudentDelay", max_student_delay_and_trips.0 },
-        { "MaxStudentTrips", max_student_delay_and_trips.1 }
-    };
-
-
-	sout | "Different types of destination addresses";
-
-	parse_config( xstr(IN_DIR) "parseconfig-all.txt", entries, NUM_ENTRIES, parse_tabular_config_format );
-
-    sout | "Stop cost: " | config_params.stop_cost;
-    sout | "Number of students: " | config_params.num_students;
-    sout | "Number of stops: " | config_params.num_stops;
-    sout | "Maximum number of students: " | config_params.max_num_students;
-    sout | "Timer delay: " | config_params.timer_delay;
-    sout | "Groupoff delay: " | config_params.groupoff_delay;
-    sout | "Conductor delay: " | conductor_delay;
-    sout | "Parental delay: " | parental_delay_and_num_couriers[0];
-    sout | "Number of couriers: " | parental_delay_and_num_couriers[1];
-    sout | "Maximum student delay: " | max_student_delay_and_trips.0;
-    sout | "Maximum student trips: " | max_student_delay_and_trips.1;
-	sout | nl;
-
-
-	sout | "Open_Failure thrown when config file does not exist";
-	try {
-		parse_config( xstr(IN_DIR) "doesnt-exist.txt", entries, NUM_ENTRIES, parse_tabular_config_format );
-	} catch( Open_Failure * ex ) {
-		sout | "Failed to open the config file";
-	}
-	sout | nl;
-
-
-	sout | "Missing_Config_Entries thrown when config file is missing entries we want";
-	try {
-		parse_config( xstr(IN_DIR) "parseconfig-missing.txt", entries, NUM_ENTRIES, parse_tabular_config_format );
-	} catch( Missing_Config_Entries * ex ) {
-		msg( ex );
-	}
-	sout | nl;
-
-
-	sout | "Parse_Failure thrown when an entry cannot be parsed";
-
-	int non_int_val;
-	config_entry entry[1] = {
-		{ "AnothaOne", non_int_val }
-	};
-
-	try {
-		parse_config( xstr(IN_DIR) "parseconfig-errors.txt", entry, 1, parse_tabular_config_format );
-	} catch( Parse_Failure * ex ) {
-		msg( ex );
-	}
-	sout | nl;
-
-
-	sout | "Validation_Failure thrown when an entry fails validation";
-
-	// TODO: Fix compiler bug that makes casting necessary
-	config_entry new_entry1 = { "StopCost", config_params.stop_cost, (bool (*)(int &))is_positive };
-	entries[0] = new_entry1;
-
-	try {
-		parse_config( xstr(IN_DIR) "parseconfig-errors.txt", entries, NUM_ENTRIES, parse_tabular_config_format );
-	} catch( Validation_Failure * ex ) {
-		msg( ex );
-	}
-	sout | nl;
-
-
-	sout | "No error is thrown when validation succeeds";
-	config_params.stop_cost = -1; // Reset value
-	parse_config( xstr(IN_DIR) "parseconfig-all.txt", entries, NUM_ENTRIES, parse_tabular_config_format );
-	sout | "Stop cost: " | config_params.stop_cost;
-	sout | nl;
-
-
-	sout | "A custom parse function can be accepted";
-
-	config_entry new_entry2 = { "StopCost", config_params.stop_cost, custom_parse };
-	entries[0] = new_entry2;
-
-	config_params.stop_cost = -1; // Reset value
-	parse_config( xstr(IN_DIR) "parseconfig-all.txt", entries, NUM_ENTRIES, parse_tabular_config_format );
-
-	sout | "Stop cost: " | config_params.stop_cost;
-	sout | nl;
-
-
-	sout | "Custom parse and validation functions can be provided together";
-
-	// TODO: Fix compiler bug that makes casting necessary
-	config_entry new_entry3 = { "StopCost", config_params.stop_cost, custom_parse, (bool (*)(int &))is_positive };
-	entries[0] = new_entry3;
-
-	config_params.stop_cost = -1; // Reset value
-	parse_config( xstr(IN_DIR) "parseconfig-all.txt", entries, NUM_ENTRIES, parse_tabular_config_format );
-
-	sout | "Stop cost: " | config_params.stop_cost;
-
-	exit( EXIT_SUCCESS );  // This is to avoid memory leak messages from the above exceptions
-}
Index: tests/pybin/tools.py
===================================================================
--- tests/pybin/tools.py	(revision f70497477cb004d4a38efd1b4002ed60f98f1997)
+++ tests/pybin/tools.py	(revision ae151cfe6e0bae407bd6e099500a00a6d4db9546)
@@ -23,5 +23,5 @@
 
 # helper functions to run terminal commands
-def sh(*cmd, timeout = False, output_file = None, input_file = None, input_text = None, error = subprocess.STDOUT, ignore_dry_run = False, pass_fds = []):
+def sh(*cmd, timeout = False, output_file = None, input_file = None, input_text = None, error = subprocess.STDOUT, ignore_dry_run = False, pass_fds = [], nice = False):
 	try:
 		cmd = list(cmd)
@@ -58,15 +58,25 @@
 			error = openfd(error, 'w', onexit, False)
 
+			# prepare the parameters to the call
+			popen_kwargs = {
+				'stdout' : output_file,
+				'stderr' : error,
+				'pass_fds': pass_fds,
+			}
+
+			# depending on how we are passing inputs we need to set a different argument to popen
+			if input_text:
+				popen_kwargs['input'] = bytes(input_text, encoding='utf-8')
+			else:
+				popen_kwargs['stdin'] = input_file
+
+			# we might want to nice this so it's not to obnixious to users
+			if nice:
+				popen_kwargs['preexec_fn'] = lambda: os.nice(5)
+
 			# run the desired command
 			# use with statement to make sure proc is cleaned
 			# don't use subprocess.run because we want to send SIGABRT on exit
-			with subprocess.Popen(
-				cmd,
-				**({'input' : bytes(input_text, encoding='utf-8')} if input_text else {'stdin' : input_file}),
-				stdout  = output_file,
-				stderr  = error,
-				pass_fds = pass_fds
-			) as proc:
-
+			with subprocess.Popen( cmd, **popen_kwargs ) as proc:
 				try:
 					out, errout = proc.communicate(
Index: tests/test.py
===================================================================
--- tests/test.py	(revision f70497477cb004d4a38efd1b4002ed60f98f1997)
+++ tests/test.py	(revision ae151cfe6e0bae407bd6e099500a00a6d4db9546)
@@ -23,5 +23,5 @@
 
 	def match_test(path):
-		match = re.search("^%s\/([\w\/\-_]*).expect\/([\w\-_]+)(\.[\w\-_]+)?\.txt$" % settings.SRCDIR, path)
+		match = re.search("^%s\/([\w\/\-_]*).expect\/([\w\-_\+]+)(\.[\w\-_]+)?\.txt$" % settings.SRCDIR, path)
 		if match :
 			test = Test()
@@ -190,5 +190,5 @@
 				if settings.dry_run or is_exe(exe_file):
 					# run test
-					retcode, _, _ = sh(exe_file, output_file=out_file, input_file=in_file, timeout=True)
+					retcode, _, _ = sh(exe_file, output_file=out_file, input_file=in_file, timeout=True, nice=True)
 				else :
 					# simply cat the result into the output
Index: tools/gdb/utils-gdb.py
===================================================================
--- tools/gdb/utils-gdb.py	(revision f70497477cb004d4a38efd1b4002ed60f98f1997)
+++ tools/gdb/utils-gdb.py	(revision ae151cfe6e0bae407bd6e099500a00a6d4db9546)
@@ -159,5 +159,5 @@
 
 		# if we already saw the root, then go forward
-		my_next = self.curr['_X4link']['_X4nextPY13__tE_generic__1']
+		my_next = self.curr['_X4linkS5dlink_S9processor__1']['_X4nextPY13__tE_generic__1']
 		self.curr = my_next.cast(cfa_t.processor_ptr)
 
@@ -355,5 +355,5 @@
 		))
 		tls = tls_for_proc( processor )
-		thrd = tls['_X11this_threadVPS7thread$_1']
+		thrd = thread_for_proc( processor )
 		if thrd != 0x0:
 			tname = '{} {}'.format(thrd['self_cor']['name'].string(), str(thrd))
