diff --git a/arbor/hardware/memory.cpp b/arbor/hardware/memory.cpp
index 9495327b6d18e8744a75040ca0091773c7e62750..4274ede47640a81846f117cba6944885a75eede8 100644
--- a/arbor/hardware/memory.cpp
+++ b/arbor/hardware/memory.cpp
@@ -15,7 +15,11 @@ namespace hw {
 
 #if defined(__linux__)
 memory_size_type allocated_memory() {
+#if defined(__GLIBC__) && (__GLIBC__ > 2 || ((__GLIBC__ == 2) && (__GLIBC_MINOR__ >= 33)))
+    auto m = mallinfo2();
+#else
     auto m = mallinfo();
+#endif
     return m.hblkhd + m.uordblks;
 }
 #else
diff --git a/arbor/include/arbor/simd/simd.hpp b/arbor/include/arbor/simd/simd.hpp
index 0c5f05cd9ce3f3beffc080e5d1f37110f59ebd2c..b0d3b028578995af89ddc29bda2dc3663cc2c495 100644
--- a/arbor/include/arbor/simd/simd.hpp
+++ b/arbor/include/arbor/simd/simd.hpp
@@ -174,7 +174,7 @@ namespace detail {
         {}
 
         indirect_indexed_expression& operator=(V s) {
-            typename simd_traits<ImplIndex>::scalar_type idx[width];
+            typename simd_traits<ImplIndex>::scalar_type idx[simd_traits<ImplIndex>::width];
             ImplIndex::copy_to(index.value_, idx);
             for (unsigned i = 0; i < width; ++i) {
                 p[idx[i]] = s;
@@ -244,10 +244,10 @@ namespace detail {
         switch (constraint) {
             case index_constraint::none:
             {
-                typename ImplIndex::scalar_type o[width];
+                typename ImplIndex::scalar_type o[simd_traits<ImplIndex>::width];
                 ImplIndex::copy_to(index.value_, o);
 
-                V a[width];
+                V a[simd_traits<Impl>::width];
                 Impl::copy_to(s.value_, a);
 
                 V temp = 0;
diff --git a/arbor/include/arbor/util/pp_util.hpp b/arbor/include/arbor/util/pp_util.hpp
index 5956ad65e16ae50e23f48176fe0661fdfdf63059..0b05f6f9b8d125e97e39e29e834e3912688a3b59 100644
--- a/arbor/include/arbor/util/pp_util.hpp
+++ b/arbor/include/arbor/util/pp_util.hpp
@@ -34,7 +34,7 @@
 
 // Implementation macros for ARB_PP_FOREACH:
 
-#define ARB_PP_FOREACH_1_(M, A, ...)  M(A)
+#define ARB_PP_FOREACH_1_(M, A)  M(A)
 #define ARB_PP_FOREACH_2_(M, A, ...)  M(A) ARB_PP_FOREACH_1_(M, __VA_ARGS__)
 #define ARB_PP_FOREACH_3_(M, A, ...)  M(A) ARB_PP_FOREACH_2_(M, __VA_ARGS__)
 #define ARB_PP_FOREACH_4_(M, A, ...)  M(A) ARB_PP_FOREACH_3_(M, __VA_ARGS__)
diff --git a/arbor/io/trace.hpp b/arbor/io/trace.hpp
index c9f9be8fbb250cd17d13b28c92dd7a8893447d7e..5b50b7d60ac3aea08c84d4ac5b2039da0ad63758 100644
--- a/arbor/io/trace.hpp
+++ b/arbor/io/trace.hpp
@@ -21,7 +21,7 @@
 //
 // TRACE output is to std::cerr is serialized.
 
-#define TRACE(vars...) arb::impl::debug_emit_trace(__FILE__, __LINE__, #vars, ##vars)
+#define TRACE(...) arb::impl::debug_emit_trace(__FILE__, __LINE__, #__VA_ARGS__, ##__VA_ARGS__)
 
 
 // DEBUG << ...;
diff --git a/arbor/mc_cell_group.cpp b/arbor/mc_cell_group.cpp
index 78d2d791c120450eead105f7b643ee25c8ffeadd..fa0f6a4d9fe790fa379f6eec03cce30e24ccbcee 100644
--- a/arbor/mc_cell_group.cpp
+++ b/arbor/mc_cell_group.cpp
@@ -109,7 +109,13 @@ using fvm_probe_scratch = std::tuple<std::vector<double>, std::vector<cable_samp
 
 template <typename VoidFn, typename... A>
 void tuple_foreach(VoidFn&& f, std::tuple<A...>& t) {
-    (void)(int []){(f(std::get<A>(t)), 0)...};
+    // executes functions in order (pack expansion)
+    // uses comma operator (unary left fold)
+    // avoids potentially overloaded comma operator (cast to void)
+    std::apply(
+        [g = std::forward<VoidFn>(f)](auto&& ...x){
+            (..., static_cast<void>(g(std::forward<decltype(x)>(x))));},
+        t);
 }
 
 void reserve_scratch(fvm_probe_scratch& scratch, std::size_t n) {
diff --git a/arbor/s_expr.cpp b/arbor/s_expr.cpp
index 3de9491574fdba10c9fcbded8d5e7a535e7c3010..fed7878a8173a0d3f28dbbf29faea409906c5348 100644
--- a/arbor/s_expr.cpp
+++ b/arbor/s_expr.cpp
@@ -125,6 +125,16 @@ private:
     // Consume and return the next token in the stream.
     void parse() {
         using namespace std::string_literals;
+#define ARB_CASE_LETTERS                                                                           \
+        case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i':  \
+        case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r':  \
+        case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z':            \
+        case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I':  \
+        case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R':  \
+        case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z':
+#define ARB_CASE_DIGITS                                                                            \
+        case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8':  \
+        case '9':
 
         while (!empty()) {
             switch (*stream_) {
@@ -166,11 +176,10 @@ private:
                 case ')':
                     token_ = {loc(), tok::rparen, {character()}};
                     return;
-                case 'a' ... 'z':
-                case 'A' ... 'Z':
+                ARB_CASE_LETTERS
                     token_ = symbol();
                     return;
-                case '0' ... '9':
+                ARB_CASE_DIGITS
                     token_ = number();
                     return;
                 case '"':
@@ -200,6 +209,8 @@ private:
                     return;
             }
         }
+#undef ARB_CASE_LETTERS
+#undef ARB_CASE_DIGITS
 
         if (!empty()) {
             // todo: handle error: should never hit this
diff --git a/arborio/asc_lexer.cpp b/arborio/asc_lexer.cpp
index a93299efb7af20bd82ded3f4d21d891196332fdd..c51bf8793996eae2da70b1e4180cdd1d5104430d 100644
--- a/arborio/asc_lexer.cpp
+++ b/arborio/asc_lexer.cpp
@@ -136,6 +136,16 @@ private:
     // Consume and return the next token in the stream.
     void parse() {
         using namespace std::string_literals;
+#define ARB_CASE_LETTERS                                                                           \
+        case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i':  \
+        case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r':  \
+        case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z':            \
+        case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I':  \
+        case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R':  \
+        case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z':
+#define ARB_CASE_DIGITS                                                                            \
+        case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8':  \
+        case '9':
 
         while (!empty()) {
             switch (*stream_) {
@@ -177,11 +187,10 @@ private:
                 case ')':
                     token_ = {loc(), tok::rparen, {character()}};
                     return;
-                case 'a' ... 'z':
-                case 'A' ... 'Z':
+                ARB_CASE_LETTERS
                     token_ = symbol();
                     return;
-                case '0' ... '9':
+                ARB_CASE_DIGITS
                     token_ = number();
                     return;
                 case '"':
@@ -225,6 +234,8 @@ private:
                     return;
             }
         }
+#undef ARB_CASE_LETTERS
+#undef ARB_CASE_DIGITS
 
         if (!empty()) {
             token_ = {loc(), tok::error, "Internal lexer error: expected end of input, please open a bug report"s};
diff --git a/modcc/printer/infoprinter.cpp b/modcc/printer/infoprinter.cpp
index 421a5b67de00608b4843a882d4a2b4d26da9b4ea..e0b729fc21f4f7bf427e0d7b9f5f08f6c5d1230c 100644
--- a/modcc/printer/infoprinter.cpp
+++ b/modcc/printer/infoprinter.cpp
@@ -35,54 +35,55 @@ std::string build_info_header(const Module& m, const printer_options& opt, bool
                        "#include <{}mechanism_abi.h>\n\n",
                        arb_header_prefix());
 
+    out << fmt::format("extern \"C\" {{\n"
+                       "  arb_mechanism_type make_{0}_{1}() {{\n"
+                       "    // Tables\n",
+                       std::regex_replace(opt.cpp_namespace, std::regex{"::"}, "_"),
+                       name);
+
     const auto& [state_ids, global_ids, param_ids] = public_variable_ids(m);
     const auto& assigned_ids = m.assigned_block().parameters;
-    auto fmt_var = [&](const auto& id) {
+    auto fmt_id = [&](const auto& id) {
         auto lo  = id.has_range() ? id.range.first  : lowest;
         auto hi  = id.has_range() ? id.range.second : max;
         auto val = id.has_value() ? id.value        : "NAN";
-        return fmt::format(FMT_COMPILE("{{ \"{}\", \"{}\", {}, {}, {} }}"), id.name(), id.unit_string(), val, lo, hi);
+        return fmt::format(FMT_COMPILE("{{ \"{}\", \"{}\", {}, {}, {} }}"), id.name(),
+                id.unit_string(), val, lo, hi);
+    };
+    auto fmt_ion = [&](const auto& ion) {
+        return fmt::format(FMT_COMPILE("{{ \"{}\", {}, {}, {}, {}, {}, {}, {} }}"),
+           ion.name,
+           ion.writes_concentration_int(), ion.writes_concentration_ext(),
+           ion.writes_rev_potential(), ion.uses_rev_potential(),
+           ion.uses_valence(), ion.verifies_valence(), ion.expected_valence);
+    };
+    auto print_array_head = [&](char const * type, char const * name, auto size) {
+        out << "    static " << type;
+        if (size) out << " " << name << "[] = {";
+        else out << "* " << name << " = NULL;";
+    };
+    auto print_array_tail = [&](char const * type, char const * name, auto size) {
+        if (size) out << " };";
+        out << fmt::format("\n    static arb_size_type n_{} = {};\n", name, size);
+    };
+    auto print_array = [&](char const* type, char const * name, auto & fmt_func, auto const & arr) {
+        io::separator sep("", ",\n        ");
+        print_array_head(type, name, arr.size());
+        for (const auto& var: arr) out << sep << fmt_func(var);
+        print_array_tail(type, name, arr.size());
+    };
+    auto print_arrays = [&](char const* type, char const * name, auto & fmt_func, auto const & arr0, auto const & arr1) {
+        io::separator sep("", ",\n        ");
+        print_array_head(type, name, arr0.size() + arr1.size());
+        for (const auto& var: arr0) out << sep << fmt_func(var);
+        for (const auto& var: arr1) out << sep << fmt_func(var);
+        print_array_tail(type, name, arr0.size() + arr1.size());
     };
 
-    out << fmt::format("extern \"C\" {{\n"
-                       "  arb_mechanism_type make_{0}_{1}() {{\n"
-                       "    // Tables\n",
-                       std::regex_replace(opt.cpp_namespace, std::regex{"::"}, "_"),
-                       name);
-    {
-        io::separator sep("", ",\n                                        ");
-        out << "    static arb_field_info globals[] = { ";
-        for (const auto& var: global_ids) out << sep << fmt_var(var);
-        out << fmt::format(" }};\n"
-                           "    static arb_size_type n_globals = {};\n", global_ids.size());
-    }
-    {
-        io::separator sep("", ",\n                                           ");
-        out << "    static arb_field_info state_vars[] = { ";
-        for (const auto& id: state_ids)    out << sep << fmt_var(id);
-        for (const auto& id: assigned_ids) out << sep << fmt_var(id);
-        out << fmt::format(" }};\n"
-                           "    static arb_size_type n_state_vars = {};\n", assigned_ids.size() + state_ids.size());
-    }
-    {
-        io::separator sep("", ",\n                                           ");
-        out << "    static arb_field_info parameters[] = { ";
-        for (const auto& id: param_ids) out << sep << fmt_var(id);
-        out << fmt::format(" }};\n"
-                           "    static arb_size_type n_parameters = {};\n", param_ids.size());
-    }
-    {
-        io::separator sep("", ",\n");
-        out << "    static arb_ion_info ions[] = { ";
-        for (const auto& ion: m.ion_deps()) out << sep
-                                                << fmt::format(FMT_COMPILE("{{ \"{}\", {}, {}, {}, {}, {}, {}, {} }}"),
-                                                               ion.name,
-                                                               ion.writes_concentration_int(), ion.writes_concentration_ext(),
-                                                               ion.writes_rev_potential(), ion.uses_rev_potential(),
-                                                               ion.uses_valence(), ion.verifies_valence(), ion.expected_valence);
-        out << fmt::format(" }};\n"
-                           "    static arb_size_type n_ions = {};\n", m.ion_deps().size());
-    }
+    print_array("arb_field_info", "globals", fmt_id, global_ids);
+    print_arrays("arb_field_info", "state_vars", fmt_id, state_ids, assigned_ids);
+    print_array("arb_field_info", "parameters", fmt_id, param_ids);
+    print_array("arb_ion_info", "ions", fmt_ion, m.ion_deps());
 
     out << fmt::format(FMT_COMPILE("\n"
                                    "    arb_mechanism_type result;\n"