From 5da63dde25bad77abdf665b16ab54d361b5317f4 Mon Sep 17 00:00:00 2001
From: Sam Yates <yates@cscs.ch>
Date: Fri, 20 Jul 2018 17:08:36 +0200
Subject: [PATCH] Refactor modccutil.hpp (#542)

Fixes #139.

* Split colours and `pprintf(...)` into `io/pprintf.hpp` header.
* Remove generic `to_string()` function, replacing its very occasional usage with `pprintf`.
* Move block pretty printing into own .cpp file; this is the only place that the vector ostream printer was used.
* Remove `enum_hash`, as not needed with C++14.
* Move `is_in` utility function to `util.hpp`.
* Remove old SIMD printer backend code.
---
 modcc/CMakeLists.txt              |   1 +
 modcc/blocks.cpp                  |  79 +++++++++++
 modcc/blocks.hpp                  |  61 ++-------
 modcc/expression.hpp              |   3 +-
 modcc/functionexpander.cpp        |   1 -
 modcc/functioninliner.cpp         |   1 -
 modcc/io/pprintf.hpp              |  82 +++++++++++
 modcc/lexer.cpp                   |   2 +-
 modcc/memop.hpp                   |   3 +-
 modcc/modcc.cpp                   |   8 +-
 modcc/modccutil.hpp               | 161 ----------------------
 modcc/parser.cpp                  |  14 +-
 modcc/printer/backends/avx2.hpp   | 221 ------------------------------
 modcc/printer/backends/avx512.hpp | 162 ----------------------
 modcc/printer/backends/base.hpp   |  93 -------------
 modcc/printer/backends/simd.hpp   |   4 -
 modcc/printer/printerutil.cpp     |   2 +-
 modcc/scope.hpp                   |   5 +-
 modcc/util.hpp                    |  40 ++++++
 modcc/visitor.hpp                 |   1 -
 test/unit-modcc/common.hpp        |   1 -
 test/unit-modcc/expr_expand.cpp   |  12 +-
 test/unit-modcc/test_parser.cpp   |   1 -
 test/unit-modcc/test_symdiff.cpp  |   1 -
 test/unit-modcc/test_visitors.cpp |   1 -
 25 files changed, 238 insertions(+), 722 deletions(-)
 create mode 100644 modcc/blocks.cpp
 create mode 100644 modcc/io/pprintf.hpp
 delete mode 100644 modcc/modccutil.hpp
 delete mode 100644 modcc/printer/backends/avx2.hpp
 delete mode 100644 modcc/printer/backends/avx512.hpp
 delete mode 100644 modcc/printer/backends/base.hpp
 delete mode 100644 modcc/printer/backends/simd.hpp
 create mode 100644 modcc/util.hpp

diff --git a/modcc/CMakeLists.txt b/modcc/CMakeLists.txt
index 23c0f924..6c4d9201 100644
--- a/modcc/CMakeLists.txt
+++ b/modcc/CMakeLists.txt
@@ -4,6 +4,7 @@
 set(libmodcc_sources
 
     astmanip.cpp
+    blocks.cpp
     errorvisitor.cpp
     expression.cpp
     functionexpander.cpp
diff --git a/modcc/blocks.cpp b/modcc/blocks.cpp
new file mode 100644
index 00000000..bc9d57ec
--- /dev/null
+++ b/modcc/blocks.cpp
@@ -0,0 +1,79 @@
+#include <string>
+#include <vector>
+
+#include "blocks.hpp"
+#include "io/pprintf.hpp"
+#include "io/ostream_wrappers.hpp"
+
+// Pretty-printers for block info.
+
+using namespace io;
+
+template <typename T>
+std::ostream& operator<<(std::ostream& os, const std::vector<T>& v) {
+    separator s("[", " ");
+    for (auto& x: v) os << s << x;
+    os << ']';
+
+    return os;
+}
+
+std::ostream& operator<<(std::ostream& os, Id const& V) {
+    if(V.units.size())
+        os << "(" << V.token << "," << V.value << "," << V.units << ")";
+    else
+        os << "(" << V.token << "," << V.value << ",)";
+
+    return os;
+}
+
+std::ostream& operator<<(std::ostream& os, UnitsBlock::units_pair const& p) {
+    return os << "(" << p.first << ", " << p.second << ")";
+}
+
+std::ostream& operator<<(std::ostream& os, IonDep const& I) {
+    return os << "(" << I.name << ": read " << I.read << " write " << I.write << ")";
+}
+
+std::ostream& operator<<(std::ostream& os, moduleKind const& k) {
+    return os << (k==moduleKind::density ? "density" : "point process");
+}
+
+std::ostream& operator<<(std::ostream& os, NeuronBlock const& N) {
+    os << blue("NeuronBlock")     << std::endl;
+    os << "  kind       : " << N.kind  << std::endl;
+    os << "  name       : " << N.name  << std::endl;
+    os << "  threadsafe : " << (N.threadsafe ? "yes" : "no") << std::endl;
+    os << "  ranges     : " << N.ranges  << std::endl;
+    os << "  globals    : " << N.globals << std::endl;
+    os << "  ions       : " << N.ions    << std::endl;
+
+    return os;
+}
+
+std::ostream& operator<<(std::ostream& os, StateBlock const& B) {
+    os << blue("StateBlock")      << std::endl;
+    return os << "  variables  : " << B.state_variables << std::endl;
+
+}
+
+std::ostream& operator<<(std::ostream& os, UnitsBlock const& U) {
+    os << blue("UnitsBlock")      << std::endl;
+    os << "  aliases    : "  << U.unit_aliases << std::endl;
+
+    return os;
+}
+
+std::ostream& operator<<(std::ostream& os, ParameterBlock const& P) {
+    os << blue("ParameterBlock")   << std::endl;
+    os << "  parameters : "  << P.parameters << std::endl;
+
+    return os;
+}
+
+std::ostream& operator<<(std::ostream& os, AssignedBlock const& A) {
+    os << blue("AssignedBlock")   << std::endl;
+    os << "  parameters : "  << A.parameters << std::endl;
+
+    return os;
+}
diff --git a/modcc/blocks.hpp b/modcc/blocks.hpp
index ad31932e..9ba8c657 100644
--- a/modcc/blocks.hpp
+++ b/modcc/blocks.hpp
@@ -1,13 +1,13 @@
 #pragma once
 
 #include <algorithm>
+#include <iosfwd>
 #include <string>
 #include <vector>
 
 #include "identifier.hpp"
 #include "location.hpp"
 #include "token.hpp"
-#include "modccutil.hpp"
 
 // describes a relationship with an ion channel
 struct IonDep {
@@ -152,62 +152,21 @@ struct AssignedBlock {
 ////////////////////////////////////////////////
 // helpers for pretty printing block information
 ////////////////////////////////////////////////
-inline std::ostream& operator<< (std::ostream& os, Id const& V) {
-    if(V.units.size())
-        os << "(" << V.token << "," << V.value << "," << V.units << ")";
-    else
-        os << "(" << V.token << "," << V.value << ",)";
 
-    return os;
-}
+std::ostream& operator<<(std::ostream& os, Id const& V);
 
-inline std::ostream& operator<< (std::ostream& os, UnitsBlock::units_pair const& p) {
-    return os << "(" << p.first << ", " << p.second << ")";
-}
+std::ostream& operator<<(std::ostream& os, UnitsBlock::units_pair const& p);
 
-inline std::ostream& operator<< (std::ostream& os, IonDep const& I) {
-    return os << "(" << I.name << ": read " << I.read << " write " << I.write << ")";
-}
+std::ostream& operator<<(std::ostream& os, IonDep const& I);
 
-inline std::ostream& operator<< (std::ostream& os, moduleKind const& k) {
-    return os << (k==moduleKind::density ? "density" : "point process");
-}
+std::ostream& operator<<(std::ostream& os, moduleKind const& k);
 
-inline std::ostream& operator<< (std::ostream& os, NeuronBlock const& N) {
-    os << blue("NeuronBlock")     << std::endl;
-    os << "  kind       : " << N.kind  << std::endl;
-    os << "  name       : " << N.name  << std::endl;
-    os << "  threadsafe : " << (N.threadsafe ? "yes" : "no") << std::endl;
-    os << "  ranges     : " << N.ranges  << std::endl;
-    os << "  globals    : " << N.globals << std::endl;
-    os << "  ions       : " << N.ions    << std::endl;
+std::ostream& operator<<(std::ostream& os, NeuronBlock const& N);
 
-    return os;
-}
+std::ostream& operator<<(std::ostream& os, StateBlock const& B);
 
-inline std::ostream& operator<< (std::ostream& os, StateBlock const& B) {
-    os << blue("StateBlock")      << std::endl;
-    return os << "  variables  : " << B.state_variables << std::endl;
+std::ostream& operator<<(std::ostream& os, UnitsBlock const& U);
 
-}
+std::ostream& operator<<(std::ostream& os, ParameterBlock const& P);
 
-inline std::ostream& operator<< (std::ostream& os, UnitsBlock const& U) {
-    os << blue("UnitsBlock")      << std::endl;
-    os << "  aliases    : "  << U.unit_aliases << std::endl;
-
-    return os;
-}
-
-inline std::ostream& operator<< (std::ostream& os, ParameterBlock const& P) {
-    os << blue("ParameterBlock")   << std::endl;
-    os << "  parameters : "  << P.parameters << std::endl;
-
-    return os;
-}
-
-inline std::ostream& operator<< (std::ostream& os, AssignedBlock const& A) {
-    os << blue("AssignedBlock")   << std::endl;
-    os << "  parameters : "  << A.parameters << std::endl;
-
-    return os;
-}
+std::ostream& operator<<(std::ostream& os, AssignedBlock const& A);
diff --git a/modcc/expression.hpp b/modcc/expression.hpp
index dd018629..1b793591 100644
--- a/modcc/expression.hpp
+++ b/modcc/expression.hpp
@@ -11,7 +11,8 @@
 #include "identifier.hpp"
 #include "memop.hpp"
 #include "scope.hpp"
-#include "modccutil.hpp"
+
+#include "io/pprintf.hpp"
 
 class Visitor;
 
diff --git a/modcc/functionexpander.cpp b/modcc/functionexpander.cpp
index aedf9f16..a6b44330 100644
--- a/modcc/functionexpander.cpp
+++ b/modcc/functionexpander.cpp
@@ -4,7 +4,6 @@
 #include "astmanip.hpp"
 #include "error.hpp"
 #include "functionexpander.hpp"
-#include "modccutil.hpp"
 
 expression_ptr insert_unique_local_assignment(expr_list_type& stmts, Expression* e) {
     auto exprs = make_unique_local_assign(e->scope(), e);
diff --git a/modcc/functioninliner.cpp b/modcc/functioninliner.cpp
index a652c60a..da5dd5eb 100644
--- a/modcc/functioninliner.cpp
+++ b/modcc/functioninliner.cpp
@@ -2,7 +2,6 @@
 
 #include "error.hpp"
 #include "functioninliner.hpp"
-#include "modccutil.hpp"
 #include "errorvisitor.hpp"
 
 expression_ptr inline_function_call(Expression* e)
diff --git a/modcc/io/pprintf.hpp b/modcc/io/pprintf.hpp
new file mode 100644
index 00000000..474b84ce
--- /dev/null
+++ b/modcc/io/pprintf.hpp
@@ -0,0 +1,82 @@
+#pragma once
+
+#include <sstream>
+#include <string>
+
+//'\e[1;31m' # Red
+//'\e[1;32m' # Green
+//'\e[1;33m' # Yellow
+//'\e[1;34m' # Blue
+//'\e[1;35m' # Purple
+//'\e[1;36m' # Cyan
+//'\e[1;37m' # White
+enum class stringColor {white, red, green, blue, yellow, purple, cyan};
+
+inline std::string colorize(std::string const& s, stringColor c) {
+    switch(c) {
+        case stringColor::white :
+            return "\033[1;37m"  + s + "\033[0m";
+        case stringColor::red   :
+            return "\033[1;31m" + s + "\033[0m";
+        case stringColor::green :
+            return "\033[1;32m" + s + "\033[0m";
+        case stringColor::blue  :
+            return "\033[1;34m" + s + "\033[0m";
+        case stringColor::yellow:
+            return "\033[1;33m" + s + "\033[0m";
+        case stringColor::purple:
+            return "\033[1;35m" + s + "\033[0m";
+        case stringColor::cyan  :
+            return "\033[1;36m" + s + "\033[0m";
+    }
+    return s;
+}
+
+// helpers for inline printing
+inline std::string red(std::string const& s) {
+    return colorize(s, stringColor::red);
+}
+inline std::string green(std::string const& s) {
+    return colorize(s, stringColor::green);
+}
+inline std::string yellow(std::string const& s) {
+    return colorize(s, stringColor::yellow);
+}
+inline std::string blue(std::string const& s) {
+    return colorize(s, stringColor::blue);
+}
+inline std::string purple(std::string const& s) {
+    return colorize(s, stringColor::purple);
+}
+inline std::string cyan(std::string const& s) {
+    return colorize(s, stringColor::cyan);
+}
+inline std::string white(std::string const& s) {
+    return colorize(s, stringColor::white);
+}
+
+// variadic printf for easy error messages
+
+inline std::string pprintf(const char* s) {
+    return s;
+}
+
+template <typename T, typename ... Args>
+std::string pprintf(const char *s, T value, Args... args) {
+    std::string errstring;
+    while(*s) {
+        if(*s == '%' && s[1]!='%') {
+            std::stringstream str;
+            str << value;
+            errstring += str.str();
+            errstring += pprintf(++s, args...);
+            return errstring;
+        }
+        else {
+            errstring += *s;
+            ++s;
+        }
+    }
+    return errstring;
+}
+
diff --git a/modcc/lexer.cpp b/modcc/lexer.cpp
index 48a4e426..a2bb90af 100644
--- a/modcc/lexer.cpp
+++ b/modcc/lexer.cpp
@@ -4,7 +4,7 @@
 #include <string>
 
 #include "lexer.hpp"
-#include "modccutil.hpp"
+#include "io/pprintf.hpp"
 
 // helpers for identifying character types
 inline bool in_range(char c, char first, char last) {
diff --git a/modcc/memop.hpp b/modcc/memop.hpp
index e962a785..48232ae4 100644
--- a/modcc/memop.hpp
+++ b/modcc/memop.hpp
@@ -1,7 +1,8 @@
 #pragma once
 
-#include "modccutil.hpp"
+#include "io/pprintf.hpp"
 #include "lexer.hpp"
+#include "util.hpp"
 
 /// Defines a memory operation that is to performed by an APIMethod.
 /// Kernels can read/write global state via an index, e.g.
diff --git a/modcc/modcc.cpp b/modcc/modcc.cpp
index 13dd6003..06556f1a 100644
--- a/modcc/modcc.cpp
+++ b/modcc/modcc.cpp
@@ -12,12 +12,12 @@
 #include "printer/printeropt.hpp"
 #include "printer/simd.hpp"
 
-#include "modccutil.hpp"
 #include "module.hpp"
 #include "parser.hpp"
 #include "perfvisitor.hpp"
 
 #include "io/bulkio.hpp"
+#include "io/pprintf.hpp"
 
 using std::cout;
 using std::cerr;
@@ -68,7 +68,7 @@ struct Options {
     std::string modulename;
     bool verbose = true;
     bool analysis = false;
-    std::unordered_set<targetKind, enum_hash> targets;
+    std::unordered_set<targetKind> targets;
 };
 
 // Helper for formatting tabulated output (option reporting).
@@ -218,7 +218,7 @@ int main(int argc, char **argv) {
         }
     }
     catch(TCLAP::ArgException &e) {
-        return report_error(e.error()+" for argument "+to_string(e.argId()));
+        return report_error(pprintf("% for argument %", e.error(), e.argId()));
     }
 
     try {
@@ -312,7 +312,7 @@ int main(int argc, char **argv) {
         return report_error(e.what());
     }
     catch (compiler_exception& e) {
-        return report_ice(e.what()+std::string(" @ ")+to_string(e.location()));
+        return report_ice(pprintf("% @ %", e.what(), e.location()));
     }
     catch (std::exception& e) {
         return report_ice(e.what());
diff --git a/modcc/modccutil.hpp b/modcc/modccutil.hpp
deleted file mode 100644
index 58dc7d1a..00000000
--- a/modcc/modccutil.hpp
+++ /dev/null
@@ -1,161 +0,0 @@
-#pragma once
-
-#include <exception>
-#include <memory>
-#include <sstream>
-#include <vector>
-#include <initializer_list>
-
-namespace impl {
-    template <typename C, typename V>
-    struct has_count_method {
-        template <typename T, typename U>
-        static decltype(std::declval<T>().count(std::declval<U>()), std::true_type{}) test(int);
-        template <typename T, typename U>
-        static std::false_type test(...);
-
-        using type = decltype(test<C, V>(0));
-    };
-
-    template <typename X, typename C>
-    bool is_in(const X& x, const C& c, std::false_type) {
-        for (const auto& y: c) {
-            if (y==x) return true;
-        }
-        return false;
-    }
-
-    template <typename X, typename C>
-    bool is_in(const X& x, const C& c, std::true_type) {
-        return !!c.count(x);
-    }
-}
-
-template <typename X, typename C>
-bool is_in(const X& x, const C& c) {
-    return impl::is_in(x, c, typename impl::has_count_method<C,X>::type{});
-}
-
-template <typename X>
-bool is_in(const X& x, const std::initializer_list<X>& c) {
-    return impl::is_in(x, c, std::false_type{});
-}
-
-struct enum_hash {
-    template <typename E, typename V = typename std::underlying_type<E>::type>
-    std::size_t operator()(E e) const noexcept {
-        return std::hash<V>{}(static_cast<V>(e));
-    }
-};
-
-inline std::string pprintf(const char *s) {
-    std::string errstring;
-    while(*s) {
-        if(*s == '%' && s[1]!='%') {
-            // instead of throwing an exception, replace with ??
-            //throw std::runtime_error("pprintf: the number of arguments did not match the format ");
-            errstring += "<?>";
-        }
-        else {
-            errstring += *s;
-        }
-        ++s;
-    }
-    return errstring;
-}
-
-// variadic printf for easy error messages
-template <typename T, typename ... Args>
-std::string pprintf(const char *s, T value, Args... args) {
-    std::string errstring;
-    while(*s) {
-        if(*s == '%' && s[1]!='%') {
-            std::stringstream str;
-            str << value;
-            errstring += str.str();
-            errstring += pprintf(++s, args...);
-            return errstring;
-        }
-        else {
-            errstring += *s;
-            ++s;
-        }
-    }
-    return errstring;
-}
-
-template <typename T>
-std::string to_string(T val) {
-    std::stringstream str;
-    str << val;
-    return str.str();
-}
-
-//'\e[1;31m' # Red
-//'\e[1;32m' # Green
-//'\e[1;33m' # Yellow
-//'\e[1;34m' # Blue
-//'\e[1;35m' # Purple
-//'\e[1;36m' # Cyan
-//'\e[1;37m' # White
-enum class stringColor {white, red, green, blue, yellow, purple, cyan};
-
-#define COLOR_PRINTING
-#ifdef COLOR_PRINTING
-inline std::string colorize(std::string const& s, stringColor c) {
-    switch(c) {
-        case stringColor::white :
-            return "\033[1;37m"  + s + "\033[0m";
-        case stringColor::red   :
-            return "\033[1;31m" + s + "\033[0m";
-        case stringColor::green :
-            return "\033[1;32m" + s + "\033[0m";
-        case stringColor::blue  :
-            return "\033[1;34m" + s + "\033[0m";
-        case stringColor::yellow:
-            return "\033[1;33m" + s + "\033[0m";
-        case stringColor::purple:
-            return "\033[1;35m" + s + "\033[0m";
-        case stringColor::cyan  :
-            return "\033[1;36m" + s + "\033[0m";
-    }
-    return s;
-}
-#else
-inline std::string colorize(std::string const& s, stringColor c) {
-    return s;
-}
-#endif
-
-// helpers for inline printing
-inline std::string red(std::string const& s) {
-    return colorize(s, stringColor::red);
-}
-inline std::string green(std::string const& s) {
-    return colorize(s, stringColor::green);
-}
-inline std::string yellow(std::string const& s) {
-    return colorize(s, stringColor::yellow);
-}
-inline std::string blue(std::string const& s) {
-    return colorize(s, stringColor::blue);
-}
-inline std::string purple(std::string const& s) {
-    return colorize(s, stringColor::purple);
-}
-inline std::string cyan(std::string const& s) {
-    return colorize(s, stringColor::cyan);
-}
-inline std::string white(std::string const& s) {
-    return colorize(s, stringColor::white);
-}
-
-template <typename T>
-std::ostream& operator<< (std::ostream& os, std::vector<T> const& V) {
-    os << "[";
-    for(auto it = V.begin(); it!=V.end(); ++it) { // ugly loop, pretty printing
-        os << *it << (it==V.end()-1 ? "" : " ");
-    }
-    return os << "]";
-}
-
diff --git a/modcc/parser.cpp b/modcc/parser.cpp
index d912a441..6ce9258b 100644
--- a/modcc/parser.cpp
+++ b/modcc/parser.cpp
@@ -4,7 +4,9 @@
 #include "parser.hpp"
 #include "perfvisitor.hpp"
 #include "token.hpp"
-#include "modccutil.hpp"
+#include "util.hpp"
+
+#include "io/pprintf.hpp"
 
 // specialize on const char* for lazy evaluation of compile time strings
 bool Parser::expect(tok tok, const char* str) {
@@ -1545,9 +1547,8 @@ expression_ptr Parser::parse_block(bool is_nested) {
     }
 
     if(token_.type != tok::rbrace) {
-        error("could not find closing '" + yellow("}")
-              + "' for else statement that started at "
-              + ::to_string(block_location));
+        error(pprintf("could not find closing '%' for else statement that started at ",
+              yellow("}"), block_location));
         return nullptr;
     }
     get_token(); // consume closing '}'
@@ -1582,9 +1583,8 @@ expression_ptr Parser::parse_initial() {
     }
 
     if(token_.type != tok::rbrace) {
-        error("could not find closing '" + yellow("}")
-              + "' for else statement that started at "
-              + ::to_string(block_location));
+        error(pprintf("could not find closing '%' for else statement that started at ",
+              yellow("}"), block_location));
         return nullptr;
     }
     get_token(); // consume closing '}'
diff --git a/modcc/printer/backends/avx2.hpp b/modcc/printer/backends/avx2.hpp
deleted file mode 100644
index d72f48ea..00000000
--- a/modcc/printer/backends/avx2.hpp
+++ /dev/null
@@ -1,221 +0,0 @@
-//
-// AVX2 backend
-//
-
-#pragma once
-
-#include "backends/base.hpp"
-#include "util/compat.hpp"
-
-namespace modcc {
-
-// Specialize for the different architectures
-template<>
-struct simd_intrinsics<simdKind::avx2> {
-    static bool has_scatter() {
-        return false;
-    }
-
-    static bool has_gather() {
-        return true;
-    }
-
-    static std::string emit_headers() {
-        /*
-        std::string ret = "#include <immintrin.h>";
-        ret += "\n#include <backends/multicore/intrin.hpp>";
-        if (!compat::using_intel_compiler()) {
-            ret += "\n#include <backends/multicore/intrin.hpp>";
-        }
-        return ret;
-        */
-
-        return
-            "#include <immintrin.h>\n"
-            "#include <backends/multicore/intrin.hpp>";
-    }
-
-    static std::string emit_simd_width() {
-        return "256";
-    }
-
-    static std::string emit_value_type() {
-        return "__m256d";
-    }
-
-    static std::string emit_index_type() {
-        return "__m128i";
-    }
-
-    template<typename T1, typename T2>
-    static void emit_binary_op(TextBuffer& tb, tok op,
-                               const T1& arg1, const T2& arg2) {
-        switch (op) {
-        case tok::plus:
-            tb << "_mm256_add_pd(";
-            break;
-        case tok::minus:
-            tb << "_mm256_sub_pd(";
-            break;
-        case tok::times:
-            tb << "_mm256_mul_pd(";
-            break;
-        case tok::divide:
-            tb << "_mm256_div_pd(";
-            break;
-        case tok::max:
-            tb << "_mm256_max_pd(";
-            break;
-        case tok::min:
-            tb << "arb::multicore::arb_mm256_min_pd(";
-            break;
-        default:
-            throw std::invalid_argument("Unable to generate avx2 for binary operator " + token_map[op]);
-        }
-
-        emit_operands(tb, arg_emitter(arg1), arg_emitter(arg2));
-        tb << ")";
-    }
-
-    template<typename T>
-    static void emit_unary_op(TextBuffer& tb, tok op, const T& arg) {
-        switch (op) {
-        case tok::minus:
-            tb << "_mm256_sub_pd(_mm256_set1_pd(0), ";
-            break;
-        case tok::exp:
-            if (compat::using_intel_compiler()) {
-                tb << "_mm256_exp_pd(";
-            }
-            else {
-                tb << "arb::multicore::arb_mm256_exp_pd(";
-            }
-            break;
-        case tok::log:
-            if (compat::using_intel_compiler()) {
-                tb << "_mm256_log_pd(";
-            }
-            else {
-                tb << "arb::multicore::arb_mm256_log_pd(";
-            }
-            break;
-        case tok::abs:
-            tb << "arb::multicore::arb_mm256_abs_pd(";
-            break;
-        case tok::exprelr:
-            tb << "arb::multicore::arb_mm256_exprelr_pd(";
-            break;
-        default:
-            throw std::invalid_argument("Unable to generate avx2 for unary operator " + token_map[op]);
-        }
-
-        emit_operands(tb, arg_emitter(arg));
-        tb << ")";
-    }
-
-    template<typename B, typename E>
-    static void emit_pow(TextBuffer& tb, const B& base, const E& exp) {
-        if (compat::using_intel_compiler()) {
-            tb << "_mm256_pow_pd(";
-        }
-        else {
-            tb << "arb::multicore::arb_mm256_pow_pd(";
-        }
-
-        emit_operands(tb, arg_emitter(base), arg_emitter(exp));
-        tb << ")";
-    }
-
-    template<typename A, typename V>
-    static void emit_store_unaligned(TextBuffer& tb, const A& addr,
-                                     const V& value) {
-        tb << "_mm256_storeu_pd(";
-        emit_operands(tb, arg_emitter(addr), arg_emitter(value));
-        tb << ")";
-    }
-
-    template<typename A>
-    static void emit_load_unaligned(TextBuffer& tb, const A& addr) {
-        tb << "_mm256_loadu_pd(";
-        emit_operands(tb, arg_emitter(addr));
-        tb << ")";
-    }
-
-    template<typename A>
-    static void emit_load_index(TextBuffer& tb, const A& addr) {
-        tb << "_mm_lddqu_si128(";
-        emit_operands(tb, arg_emitter(addr));
-        tb << ")";
-    }
-
-    template<typename A, typename I, typename V, typename S>
-    static void emit_scatter(TextBuffer& tb, const A& addr,
-                             const I& index, const V& value, const S& scale) {
-        // no support of scatter in AVX2, so revert to simple scalar updates
-        std::string scalar_index_ptr = varprefix + std::to_string(varcnt++);
-        std::string scalar_value_ptr = varprefix + std::to_string(varcnt++);
-
-        tb.end_line("{");
-        tb.increase_indentation();
-
-        // FIXME: should probably read "index_type*"
-        tb.add_gutter();
-        tb << "int* " << scalar_index_ptr
-           << " = (int*) &" << index;
-        tb.end_line(";");
-
-        tb.add_gutter();
-        tb << "value_type* " << scalar_value_ptr
-           << " = (value_type*) &" << value;
-        tb.end_line(";");
-
-        tb.add_line("for (int k_ = 0; k_ < simd_width; ++k_) {");
-        tb.increase_indentation();
-        tb.add_gutter();
-        tb << addr << "[" << scalar_index_ptr << "[k_]] = "
-           << scalar_value_ptr << "[k_]";
-        tb.end_line(";");
-
-        tb.decrease_indentation();
-        tb.add_line("}");
-
-        tb.decrease_indentation();
-        tb.add_gutter();
-        tb << "}";
-    }
-
-    template<typename A, typename I, typename S>
-    static void emit_gather(TextBuffer& tb, const A& addr,
-                            const I& index, const S& scale) {
-        tb << "_mm256_i32gather_pd(";
-        emit_operands(tb, arg_emitter(addr), arg_emitter(index),
-                      arg_emitter(scale));
-        tb << ")";
-    }
-
-    // In avx2 require 4-wide gather of i32 indices.
-    template<typename A, typename I, typename S>
-    static void emit_gather_index(TextBuffer& tb, const A& addr,
-                                  const I& index, const S& scale) {
-        tb << "_mm_i32gather_epi32(";
-        emit_operands(tb, arg_emitter(addr), arg_emitter(index),
-                      arg_emitter(scale));
-        tb << ")";
-    }
-
-    template<typename T>
-    static void emit_set_value(TextBuffer& tb, const T& arg) {
-        tb << "_mm256_set1_pd(";
-        emit_operands(tb, arg_emitter(arg));
-        tb << ")";
-    }
-
-private:
-    static int varcnt;
-    const static std::string varprefix;
-};
-
-int simd_intrinsics<simdKind::avx2>::varcnt = 0;
-const std::string simd_intrinsics<simdKind::avx2>::varprefix = "_r";
-
-} // namespace modcc
diff --git a/modcc/printer/backends/avx512.hpp b/modcc/printer/backends/avx512.hpp
deleted file mode 100644
index 2d856349..00000000
--- a/modcc/printer/backends/avx512.hpp
+++ /dev/null
@@ -1,162 +0,0 @@
-//
-// AVX512 backend
-//
-
-#pragma once
-
-#include "backends/base.hpp"
-
-namespace modcc {
-
-// Specialize for the different architectures
-template<>
-struct simd_intrinsics<simdKind::avx512> {
-
-    static bool has_scatter() {
-        return true;
-    }
-
-    static bool has_gather() {
-        return true;
-    }
-
-    static std::string emit_headers() {
-        return
-            "#include <immintrin.h>\n"
-            "#include <backends/multicore/intrin.hpp>";
-    };
-
-    static std::string emit_simd_width() {
-        return "512";
-    }
-
-    static std::string emit_value_type() {
-        return "vecd_avx512";
-    }
-
-    static std::string emit_index_type() {
-        return "__m256i";
-    }
-
-    template<typename T1, typename T2>
-    static void emit_binary_op(TextBuffer& tb, tok op,
-                               const T1& arg1, const T2& arg2) {
-        switch (op) {
-        case tok::plus:
-            tb << "add(";
-            break;
-        case tok::minus:
-            tb << "sub(";
-            break;
-        case tok::times:
-            tb << "mul(";
-            break;
-        case tok::divide:
-            tb << "div(";
-            break;
-        case tok::max:
-            tb << "max(";
-            break;
-        case tok::min:
-            tb << "min(";
-            break;
-        default:
-            throw std::invalid_argument("Unable to generate avx512 for binary operator " + token_map[op]);
-        }
-
-        emit_operands(tb, arg_emitter(arg1), arg_emitter(arg2));
-        tb << ")";
-    }
-
-    template<typename T>
-    static void emit_unary_op(TextBuffer& tb, tok op, const T& arg) {
-        switch (op) {
-        case tok::minus:
-            tb << "sub(set(0), ";
-            break;
-        case tok::exp:
-            tb << "_mm512_exp_pd(";
-            break;
-        case tok::log:
-            tb << "_mm512_log_pd(";
-            break;
-        case tok::abs:
-            tb << "abs(";
-            break;
-        case tok::exprelr:
-            tb << "exprelr(";
-            break;
-        default:
-            throw std::invalid_argument("Unable to generate avx512 for unary operator " + token_map[op]);
-        }
-
-        emit_operands(tb, arg_emitter(arg));
-        tb << ")";
-    }
-
-    template<typename B, typename E>
-    static void emit_pow(TextBuffer& tb, const B& base, const E& exp) {
-        tb << "_mm512_pow_pd(";
-        emit_operands(tb, arg_emitter(base), arg_emitter(exp));
-        tb << ")";
-    }
-
-    template<typename A, typename V>
-    static void emit_store_unaligned(TextBuffer& tb, const A& addr,
-                                     const V& value) {
-        tb << "_mm512_storeu_pd(";
-        emit_operands(tb, arg_emitter(addr), arg_emitter(value));
-        tb << ")";
-    }
-
-    template<typename A>
-    static void emit_load_unaligned(TextBuffer& tb, const A& addr) {
-        tb << "_mm512_loadu_pd(";
-        emit_operands(tb, arg_emitter(addr));
-        tb << ")";
-    }
-
-    template<typename A>
-    static void emit_load_index(TextBuffer& tb, const A& addr) {
-        tb << "_mm256_lddqu_si256(";
-        emit_operands(tb, arg_emitter(addr));
-        tb << ")";
-    }
-
-    template<typename A, typename I, typename V, typename S>
-    static void emit_scatter(TextBuffer& tb, const A& addr,
-                             const I& index, const V& value, const S& scale) {
-        tb << "_mm512_i32scatter_pd(";
-        emit_operands(tb, arg_emitter(addr), arg_emitter(index),
-                      arg_emitter(value), arg_emitter(scale));
-        tb << ")";
-    }
-
-    template<typename A, typename I, typename S>
-    static void emit_gather(TextBuffer& tb, const A& addr,
-                            const I& index, const S& scale) {
-        tb << "_mm512_i32gather_pd(";
-        emit_operands(tb, arg_emitter(index), arg_emitter(addr),
-                      arg_emitter(scale));
-        tb << ")";
-    }
-
-    // In avx512 require 8-wide gather of i32 indices.
-    template<typename A, typename I, typename S>
-    static void emit_gather_index(TextBuffer& tb, const A& addr,
-                                  const I& index, const S& scale) {
-        tb << "_mm256_i32gather_epi32(";
-        emit_operands(tb, arg_emitter(addr), arg_emitter(index),
-                      arg_emitter(scale));
-        tb << ")";
-    }
-
-    template<typename T>
-    static void emit_set_value(TextBuffer& tb, const T& arg) {
-        tb << "set(";
-        emit_operands(tb, arg_emitter(arg));
-        tb << ")";
-    }
-};
-
-} // namespace modcc
diff --git a/modcc/printer/backends/base.hpp b/modcc/printer/backends/base.hpp
deleted file mode 100644
index b0b9398e..00000000
--- a/modcc/printer/backends/base.hpp
+++ /dev/null
@@ -1,93 +0,0 @@
-//
-// Base SIMD backend functionality
-//
-
-#pragma once
-
-#include <functional>
-#include <stdexcept>
-#include <string>
-#include <type_traits>
-
-#include "token.hpp"
-#include "textbuffer.hpp"
-
-enum class simdKind {
-    none, avx2, avx512
-};
-
-namespace modcc {
-
-template <bool V, typename R = void>
-using enable_if_t = typename std::enable_if<V, R>::type;
-
-using operand_fn_t = std::function<void(TextBuffer&)>;
-
-static void emit_operands(TextBuffer& tb, operand_fn_t emitter) {
-    emitter(tb);
-}
-
-template<typename ... Args>
-static void emit_operands(TextBuffer& tb, operand_fn_t emitter, Args ... args) {
-    emitter(tb);
-    tb << ", ";
-    emit_operands(tb, args ...);
-}
-
-template<typename T>
-static enable_if_t<!std::is_convertible<T, operand_fn_t>::value, operand_fn_t>
-arg_emitter(const T& arg) {
-    return [arg](TextBuffer& tb) { tb << arg; };
-}
-
-static operand_fn_t arg_emitter(const operand_fn_t& arg) {
-    return arg;
-}
-
-template<simdKind Arch>
-struct simd_intrinsics {
-    static std::string emit_headers();
-    static std::string emit_simd_width();
-    static std::string emit_simd_value_type();
-    static std::string emit_simd_index_type();
-
-    template<typename T1, typename T2>
-    static void emit_binary_op(TextBuffer& tb, tok op,
-                               const T1& arg1, const T2& arg2);
-
-    template<typename T>
-    static void emit_unary_op(TextBuffer& tb, tok op, const T& arg);
-
-    template<typename B, typename E>
-    static void emit_pow(TextBuffer& tb, const B& base, const E& exp);
-
-    template<typename A, typename V>
-    static void emit_store_unaligned(TextBuffer& tb, const A& addr, const V& value);
-
-    template<typename A>
-    static void emit_load_unaligned(TextBuffer& tb, const A& addr);
-
-    template<typename A>
-    static void emit_load_index(TextBuffer& tb, const A& addr);
-
-    template<typename A, typename I, typename V, typename S>
-    static void emit_scatter(TextBuffer& tb, const A& addr,
-                             const I& index, const V& value, const S& scale);
-
-    template<typename A, typename I, typename S>
-    static void emit_gather(TextBuffer& tb, const A& addr,
-                            const I& index, const S& scale);
-
-    // int32 value version of `emit_gather` to look up cell indices
-    template<typename A, typename I, typename S>
-    static void emit_gather_index(TextBuffer& tb, const A& addr,
-                                  const I& index, const S& scale);
-
-    template<typename T>
-    static void emit_set_value(TextBuffer& tb, const T& arg);
-
-    static bool has_gather();
-    static bool has_scatter();
-};
-
-} // namespace modcc
diff --git a/modcc/printer/backends/simd.hpp b/modcc/printer/backends/simd.hpp
deleted file mode 100644
index 63c034d7..00000000
--- a/modcc/printer/backends/simd.hpp
+++ /dev/null
@@ -1,4 +0,0 @@
-#pragma once
-
-#include "backends/avx2.hpp"
-#include "backends/avx512.hpp"
diff --git a/modcc/printer/printerutil.cpp b/modcc/printer/printerutil.cpp
index 12df7d87..6050fa06 100644
--- a/modcc/printer/printerutil.cpp
+++ b/modcc/printer/printerutil.cpp
@@ -145,7 +145,7 @@ indexed_variable_info decode_indexed_variable(IndexedVariable* sym) {
         data_var=ion_pfx+".external_concentration";
         break;
     default:
-        throw compiler_exception("unrecognized indexed data source: "+to_string(sym), sym->location());
+        throw compiler_exception(pprintf("unrecognized indexed data source: %", sym), sym->location());
     }
 
     return {data_var, index_var};
diff --git a/modcc/scope.hpp b/modcc/scope.hpp
index 831a3e6d..be2eee29 100644
--- a/modcc/scope.hpp
+++ b/modcc/scope.hpp
@@ -1,11 +1,12 @@
 #pragma once
 
-#include "modccutil.hpp"
-
 #include <memory>
 #include <string>
 #include <unordered_map>
 
+#include "io/pprintf.hpp"
+
+
 // Scope is templated to avoid circular compilation issues.
 // When performing semantic analysis of expressions via traversal of the AST
 // each node in the AST has a reference to a Scope. This leads to circular
diff --git a/modcc/util.hpp b/modcc/util.hpp
new file mode 100644
index 00000000..5d39e311
--- /dev/null
+++ b/modcc/util.hpp
@@ -0,0 +1,40 @@
+#pragma once
+
+#include <type_traits>
+#include <initializer_list>
+
+namespace impl {
+    template <typename C, typename V>
+    struct has_count_method {
+        template <typename T, typename U>
+        static decltype(std::declval<T>().count(std::declval<U>()), std::true_type{}) test(int);
+        template <typename T, typename U>
+        static std::false_type test(...);
+
+        using type = decltype(test<C, V>(0));
+    };
+
+    template <typename X, typename C>
+    bool is_in(const X& x, const C& c, std::false_type) {
+        for (const auto& y: c) {
+            if (y==x) return true;
+        }
+        return false;
+    }
+
+    template <typename X, typename C>
+    bool is_in(const X& x, const C& c, std::true_type) {
+        return !!c.count(x);
+    }
+}
+
+template <typename X, typename C>
+bool is_in(const X& x, const C& c) {
+    return impl::is_in(x, c, typename impl::has_count_method<C,X>::type{});
+}
+
+template <typename X>
+bool is_in(const X& x, const std::initializer_list<X>& c) {
+    return impl::is_in(x, c, std::false_type{});
+}
+
diff --git a/modcc/visitor.hpp b/modcc/visitor.hpp
index 8179b977..b6ae70ca 100644
--- a/modcc/visitor.hpp
+++ b/modcc/visitor.hpp
@@ -2,7 +2,6 @@
 
 #include "error.hpp"
 #include "expression.hpp"
-#include "modccutil.hpp"
 
 /// visitor base class
 /// The visitors for all AST nodes throw an exception
diff --git a/test/unit-modcc/common.hpp b/test/unit-modcc/common.hpp
index d0059e3c..e5e4ac6f 100644
--- a/test/unit-modcc/common.hpp
+++ b/test/unit-modcc/common.hpp
@@ -6,7 +6,6 @@
 
 #include "expression.hpp"
 #include "parser.hpp"
-#include "modccutil.hpp"
 
 extern bool g_verbose_flag;
 
diff --git a/test/unit-modcc/expr_expand.cpp b/test/unit-modcc/expr_expand.cpp
index 340acdcc..0dd1f3f0 100644
--- a/test/unit-modcc/expr_expand.cpp
+++ b/test/unit-modcc/expr_expand.cpp
@@ -2,7 +2,7 @@
 #include <sstream>
 
 #include "expression.hpp"
-#include "modccutil.hpp"
+#include "io/pprintf.hpp"
 #include "token.hpp"
 
 #include "alg_collect.hpp"
@@ -47,7 +47,7 @@ alg::prodsum expand_expression(Expression* e, const id_prodsum_map& exmap) {
         case tok::pow:
             if (!rhs.is_scalar()) {
                 // make an opaque term for this case (i.e. too hard to simplify)
-                return prodterm("("+to_string(lhs)+")^("+to_string(rhs)+")");
+                return prodterm(pprintf("(%)^(%)", lhs, rhs));
             }
             else return lhs.pow(rhs.first_coeff());
         default:
@@ -60,13 +60,13 @@ alg::prodsum expand_expression(Expression* e, const id_prodsum_map& exmap) {
         case tok::minus:
             return -inner;
         case tok::exp:
-            return prodterm("exp("+to_string(inner)+")");
+            return prodterm(pprintf("exp(%)", inner));
         case tok::log:
-            return prodterm("log("+to_string(inner)+")");
+            return prodterm(pprintf("log(%)", inner));
         case tok::sin:
-            return prodterm("sin("+to_string(inner)+")");
+            return prodterm(pprintf("sin(%)", inner));
         case tok::cos:
-            return prodterm("cos("+to_string(inner)+")");
+            return prodterm(pprintf("cos(%)", inner));
         default:
             throw std::runtime_error("unrecognized unaryop");
         }
diff --git a/test/unit-modcc/test_parser.cpp b/test/unit-modcc/test_parser.cpp
index 75ae2a62..6e83fc28 100644
--- a/test/unit-modcc/test_parser.cpp
+++ b/test/unit-modcc/test_parser.cpp
@@ -4,7 +4,6 @@
 
 #include "common.hpp"
 #include "module.hpp"
-#include "modccutil.hpp"
 #include "parser.hpp"
 
 #include "io/bulkio.hpp"
diff --git a/test/unit-modcc/test_symdiff.cpp b/test/unit-modcc/test_symdiff.cpp
index 9cc1ded5..410e7700 100644
--- a/test/unit-modcc/test_symdiff.cpp
+++ b/test/unit-modcc/test_symdiff.cpp
@@ -4,7 +4,6 @@
 
 #include "symdiff.hpp"
 #include "parser.hpp"
-#include "modccutil.hpp"
 
 // Test visitors for extended constant reduction,
 // identifier presence detection and symbolic differentiation.
diff --git a/test/unit-modcc/test_visitors.cpp b/test/unit-modcc/test_visitors.cpp
index 8a516001..348e4a8d 100644
--- a/test/unit-modcc/test_visitors.cpp
+++ b/test/unit-modcc/test_visitors.cpp
@@ -2,7 +2,6 @@
 
 #include "perfvisitor.hpp"
 #include "parser.hpp"
-#include "modccutil.hpp"
 
 // overload for parser errors
 template <typename EPtr>
-- 
GitLab