style
diff --git a/src/support/permutations.h b/src/support/permutations.h
index 4e6cc18..601057b 100644
--- a/src/support/permutations.h
+++ b/src/support/permutations.h
@@ -55,10 +55,14 @@
   std::vector<std::vector<Index>> ret;
   std::vector<Index> curr;
   curr.resize(size);
-  for (auto& x : curr) x = 0;
+  for (auto& x : curr) {
+    x = 0;
+  }
   while (1) {
     std::set<Index> set;
-    for (auto x : curr) set.insert(x);
+    for (auto x : curr) {
+      set.insert(x);
+    }
     if (set.size() == size) {
       ret.push_back(curr); // this is indeed a permutation
     }
@@ -66,9 +70,13 @@
     Index toBump = size - 1;
     while (1) {
       curr[toBump]++;
-      if (curr[toBump] < size) break;
+      if (curr[toBump] < size) {
+        break;
+      }
       // an overflow
-      if (toBump == 0) return ret; // all done
+      if (toBump == 0) {
+        return ret; // all done
+      }
       curr[toBump] = 0;
       toBump--;
     }
diff --git a/src/tools/wasm-analyze.cpp b/src/tools/wasm-analyze.cpp
index db7f0bd..3278218 100644
--- a/src/tools/wasm-analyze.cpp
+++ b/src/tools/wasm-analyze.cpp
@@ -17,21 +17,22 @@
 //
 // Expression analyzer utility
 //
-// Superoptimization is based on Bansal, Sorav; Aiken, Alex (21–25 October 2006): "Automatic Generation of Peephole Superoptimizers".
+// Superoptimization is based on Bansal, Sorav; Aiken, Alex (21–25 October
+// 2006): "Automatic Generation of Peephole Superoptimizers".
 //
 
+#include "ast/cost.h"
+#include "ast_utils.h"
 #include "support/colors.h"
 #include "support/command-line.h"
 #include "support/file.h"
 #include "support/hash.h"
 #include "support/permutation.h"
-#include "wasm-s-parser.h"
-#include "wasm-traversal.h"
-#include "wasm-printing.h"
 #include "wasm-interpreter.h"
 #include "wasm-io.h"
-#include "ast_utils.h"
-#include "ast/cost.h"
+#include "wasm-printing.h"
+#include "wasm-s-parser.h"
+#include "wasm-traversal.h"
 
 using namespace cashew;
 using namespace wasm;
@@ -42,13 +43,37 @@
 
 // special values to make sure to consider in execution hashing
 #define NUM_LIMITS 6
-static int32_t LIMIT_I32S[NUM_LIMITS] = { std::numeric_limits<int32_t>::min(), std::numeric_limits<int32_t>::max(), int32_t(std::numeric_limits<uint32_t>::min()), int32_t(std::numeric_limits<uint32_t>::max()), 0xfffff, -0xfffff };
-static int64_t LIMIT_I64S[NUM_LIMITS] = { std::numeric_limits<int64_t>::min(), std::numeric_limits<int64_t>::max(), int64_t(std::numeric_limits<uint64_t>::min()), int64_t(std::numeric_limits<uint64_t>::max()), 0xfffffLL, -0xfffffLL };
-static float   LIMIT_F32S[NUM_LIMITS] = { std::numeric_limits<float>::min(), std::numeric_limits<float>::max(), std::numeric_limits<float>::quiet_NaN(), std::numeric_limits<float>::infinity(), float(0xfffff), float(-0xfffff) };
-static double  LIMIT_F64S[NUM_LIMITS] = { std::numeric_limits<double>::min(), std::numeric_limits<double>::max(), std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::infinity(), double(0xfffff), double(-0xfffff) };
+static int32_t LIMIT_I32S[NUM_LIMITS] = {
+  std::numeric_limits<int32_t>::min(),
+  std::numeric_limits<int32_t>::max(),
+  int32_t(std::numeric_limits<uint32_t>::min()),
+  int32_t(std::numeric_limits<uint32_t>::max()),
+  0xfffff,
+  -0xfffff};
+static int64_t LIMIT_I64S[NUM_LIMITS] = {
+  std::numeric_limits<int64_t>::min(),
+  std::numeric_limits<int64_t>::max(),
+  int64_t(std::numeric_limits<uint64_t>::min()),
+  int64_t(std::numeric_limits<uint64_t>::max()),
+  0xfffffLL,
+  -0xfffffLL};
+static float LIMIT_F32S[NUM_LIMITS] = {std::numeric_limits<float>::min(),
+                                       std::numeric_limits<float>::max(),
+                                       std::numeric_limits<float>::quiet_NaN(),
+                                       std::numeric_limits<float>::infinity(),
+                                       float(0xfffff),
+                                       float(-0xfffff)};
+static double LIMIT_F64S[NUM_LIMITS] = {
+  std::numeric_limits<double>::min(),
+  std::numeric_limits<double>::max(),
+  std::numeric_limits<double>::quiet_NaN(),
+  std::numeric_limits<double>::infinity(),
+  double(0xfffff),
+  double(-0xfffff)};
 
 #define MAX_SMALL 260
-#define NUM_SMALLS (MAX_SMALL + MAX_SMALL + 1) /* negatives, positives, and zero */
+#define NUM_SMALLS                                                             \
+  (MAX_SMALL + MAX_SMALL + 1) /* negatives, positives, and zero */
 
 #define NUM_SPECIALS (NUM_LIMITS + NUM_SMALLS)
 
@@ -67,27 +92,30 @@
     }
   }
 
-  HashedExpression(const HashedExpression& other) : expr(other.expr), hash(other.hash) {}
+  HashedExpression(const HashedExpression& other)
+    : expr(other.expr), hash(other.hash) {}
 };
 
 struct ExpressionHasher {
-  size_t operator()(const HashedExpression value) const {
-    return value.hash;
-  }
+  size_t operator()(const HashedExpression value) const { return value.hash; }
 };
 
 struct ExpressionComparer {
   bool operator()(const HashedExpression a, const HashedExpression b) const {
-    if (a.hash != b.hash) return false;
+    if (a.hash != b.hash)
+      return false;
     return ExpressionAnalyzer::equal(a.expr, b.expr);
   }
 };
 
 // expression -> a count
-class ExpressionIntMap : public std::unordered_map<HashedExpression, size_t, ExpressionHasher, ExpressionComparer> {};
+class ExpressionIntMap : public std::unordered_map<HashedExpression,
+                                                   size_t,
+                                                   ExpressionHasher,
+                                                   ExpressionComparer> {};
 
 // global expression state
-Module global; // a module that persists til the end
+Module global;          // a module that persists til the end
 ExpressionIntMap freqs; // expression -> its frequency
 
 // Normalize an expression, replacing irrelevant bits with
@@ -103,11 +131,13 @@
     Normalizer(Module& wasm) : wasm(wasm), builder(wasm) {}
 
     Expression* parentCopy(Expression* curr) {
-      return ExpressionManipulator::flexibleCopy(curr, wasm, [&](Expression* curr) { return this->copy(curr); });
+      return ExpressionManipulator::flexibleCopy(
+        curr, wasm, [&](Expression* curr) { return this->copy(curr); });
     }
 
     Expression* copy(Expression* curr) {
-      // For now, we only handle math-type expressions: having a return value and no side effects
+      // For now, we only handle math-type expressions: having a return value
+      // and no side effects
       // TODO: do more stuff, modeling side effects etc.
       if (!isConcreteWasmType(curr->type)) {
         return builder.makeUnreachable();
@@ -132,23 +162,29 @@
         // consider the general case of an arbitrary expression here
         return builder.makeGetLocal(nextLocal++, curr->type);
       }
-      if (curr->is<Host>() || curr->is<Call>() || curr->is<CallImport>() || curr->is<CallIndirect>() || curr->is<GetGlobal>() || curr->is<Load>() || curr->is<Return>() || curr->is<Break>() || curr->is<Switch>()) {
+      if (curr->is<Host>() || curr->is<Call>() || curr->is<CallImport>() ||
+          curr->is<CallIndirect>() || curr->is<GetGlobal>() ||
+          curr->is<Load>() || curr->is<Return>() || curr->is<Break>() ||
+          curr->is<Switch>()) {
         return builder.makeUnreachable();
       }
       return nullptr; // allow the default copy to proceed
     }
   } normalizer(wasm);
 
-  auto* ret = ExpressionManipulator::flexibleCopy(expr, wasm, [&](Expression* curr) { return normalizer.copy(curr); });
+  auto* ret = ExpressionManipulator::flexibleCopy(
+    expr, wasm, [&](Expression* curr) { return normalizer.copy(curr); });
 
-  if (!isConcreteWasmType(ret->type) || normalizer.nextLocal >= MAX_LOCAL || Measurer::measure(ret) > MAX_EXPRESSION_SIZE) {
+  if (!isConcreteWasmType(ret->type) || normalizer.nextLocal >= MAX_LOCAL ||
+      Measurer::measure(ret) > MAX_EXPRESSION_SIZE) {
     return nullptr;
   }
   return ret;
 }
 
 // Scan an expression for local types. Assumes it has MAX_LOCAL locals at most
-struct ScanLocals : public WalkerPass<PostWalker<ScanLocals, Visitor<ScanLocals>>> {
+struct ScanLocals
+  : public WalkerPass<PostWalker<ScanLocals, Visitor<ScanLocals>>> {
   WasmType localTypes[MAX_LOCAL];
 
   ScanLocals(Expression* expr) {
@@ -165,10 +201,12 @@
 };
 
 // Remap locals
-struct RemapLocals : public WalkerPass<PostWalker<RemapLocals, Visitor<RemapLocals>>> {
+struct RemapLocals
+  : public WalkerPass<PostWalker<RemapLocals, Visitor<RemapLocals>>> {
   std::vector<Index>& mapping;
 
-  RemapLocals(Expression* expr, std::vector<Index>& mapping) : mapping(mapping) {
+  RemapLocals(Expression* expr, std::vector<Index>& mapping)
+    : mapping(mapping) {
     walk(expr);
   }
 
@@ -186,17 +224,19 @@
 struct ScanSettings {
   Index* totalExpressions;
   bool adviseOnly;
-  ScanSettings(Index* totalExpressions, bool adviseOnly) : totalExpressions(totalExpressions), adviseOnly(adviseOnly) {}
+  ScanSettings(Index* totalExpressions, bool adviseOnly)
+    : totalExpressions(totalExpressions), adviseOnly(adviseOnly) {}
 };
 
 // Scan a module for expressions
-struct Scan : public WalkerPass<PostWalker<Scan, UnifiedExpressionVisitor<Scan>>> {
+struct Scan
+  : public WalkerPass<PostWalker<Scan, UnifiedExpressionVisitor<Scan>>> {
   ScanSettings settings;
 
   Scan(ScanSettings settings) : settings(settings) {}
 
   void doWalkFunction(Function* func) {
-    //std::cout << "  [" << func->name << ']' << '\n';
+    // std::cout << "  [" << func->name << ']' << '\n';
     walk(func->body);
   }
 
@@ -205,7 +245,8 @@
     // which is ephemeral TODO: avoid keeping them alive til the end of
     // module processing to reduce peak mem usage?
     auto* normalized = normalize(curr, *getModule());
-    if (!normalized) return;
+    if (!normalized)
+      return;
     if (!settings.adviseOnly) {
       (*settings.totalExpressions)++; // this is relevant, count it
     }
@@ -216,16 +257,19 @@
         iter->second++; // just increment it
       }
     } else {
-      // create a persistent copy in the global module TODO: avoid the rehash here
+      // create a persistent copy in the global module TODO: avoid the rehash
+      // here
       auto* copy = ExpressionManipulator::copy(normalized, global);
       freqs[HashedExpression(copy)] = settings.adviseOnly ? 0 : 1;
 #if 1
-      // add the permutations on the locals as well, with freq 0, as we just want to use them as optimization targets,
-      // we don't need to optimize them, we optimize the canonical first form.
+      // add the permutations on the locals as well, with freq 0, as we just
+      // want to use them as optimization targets, we don't need to optimize
+      // them, we optimize the canonical first form.
       ScanLocals scanner(copy);
       if (scanner.localTypes[1] != none) {
         struct PermutationsLister {
-          std::vector<std::vector<std::vector<Index>>> list; // index => list of permutations of that size
+          std::vector<std::vector<std::vector<Index>>>
+            list; // index => list of permutations of that size
           PermutationsLister() {
             list.resize(MAX_LOCAL + 1);
             for (size_t i = 1; i < MAX_LOCAL + 1; i++) {
@@ -258,24 +302,31 @@
 // Generate local values deterministically, using a seed
 class LocalGenerator {
   Index seed;
+
 public:
   LocalGenerator(Index seed) : seed(seed) {}
 
   Literal get(Index index, WasmType type) {
     // use low indexes to ensure we get representation of a few special values
     // TODO: get each of the MAX_LOCALS to all of its NUM_SPECIALS values
-    int64_t special = seed; // start with 0-NS having them all taking the same value
+    int64_t special =
+      seed; // start with 0-NS having them all taking the same value
     if (special >= NUM_SPECIALS) { // then give each a range for itself
       special = int64_t(seed) - int64_t(NUM_SPECIALS * (index + 1));
     }
     if (special >= 0 && special < NUM_SPECIALS) {
       if (special < NUM_LIMITS) {
         switch (type) {
-          case i32: return Literal(LIMIT_I32S[special]);
-          case i64: return Literal(LIMIT_I64S[special]);
-          case f32: return Literal(LIMIT_F32S[special]);
-          case f64: return Literal(LIMIT_F64S[special]);
-          default: WASM_UNREACHABLE();
+          case i32:
+            return Literal(LIMIT_I32S[special]);
+          case i64:
+            return Literal(LIMIT_I64S[special]);
+          case f32:
+            return Literal(LIMIT_F32S[special]);
+          case f64:
+            return Literal(LIMIT_F64S[special]);
+          default:
+            WASM_UNREACHABLE();
         }
       } else {
         special -= NUM_LIMITS;
@@ -283,11 +334,16 @@
         special -= MAX_SMALL;
         assert(special >= -MAX_SMALL && special <= MAX_SMALL);
         switch (type) {
-          case i32: return Literal(int32_t(special));
-          case i64: return Literal(int64_t(special));
-          case f32: return Literal(float(special));
-          case f64: return Literal(double(special));
-          default: WASM_UNREACHABLE();
+          case i32:
+            return Literal(int32_t(special));
+          case i64:
+            return Literal(int64_t(special));
+          case f32:
+            return Literal(float(special));
+          case f64:
+            return Literal(double(special));
+          default:
+            WASM_UNREACHABLE();
         }
       }
     }
@@ -297,16 +353,20 @@
       case i32:
       case f32: {
         auto ret = Literal(rehash(base, Index(type)));
-        if (type == f32) ret = ret.castToF32();
+        if (type == f32)
+          ret = ret.castToF32();
         return ret;
       }
       case i64:
       case f64: {
-        auto ret = Literal(rehash(base, Index(type)) | (int64_t(rehash(base, Index(type + 1000))) << 32));
-        if (type == f64) ret = ret.castToF64();
+        auto ret = Literal(rehash(base, Index(type)) |
+                           (int64_t(rehash(base, Index(type + 1000))) << 32));
+        if (type == f64)
+          ret = ret.castToF64();
         return ret;
       }
-      default: WASM_UNREACHABLE();
+      default:
+        WASM_UNREACHABLE();
     }
   }
 };
@@ -316,12 +376,14 @@
 // Execute the expression over a set of local values
 class Runner : public ExpressionRunner<Runner> {
   LocalGenerator& localGenerator;
+
 public:
   Runner(LocalGenerator& localGenerator) : localGenerator(localGenerator) {}
 
   Flow visitLoop(Loop* curr) {
     // loops might be infinite, so must be careful
-    // but we can't tell if non-infinite, since we don't have state, so loops are just impossible to optimize for now
+    // but we can't tell if non-infinite, since we don't have state, so loops
+    // are just impossible to optimize for now
     trap("loop");
     WASM_UNREACHABLE();
   }
@@ -357,24 +419,26 @@
     abort(); // we should not see this
   }
 
-  void trap(const char* why) override {
-    throw TrapException();
-  }
+  void trap(const char* why) override { throw TrapException(); }
 };
 
 // Calculate a hash value based on executing an expression
 struct ExecutionHasher {
-  std::unordered_map<size_t, std::vector<Expression*>> hashClasses; // hash value => list of expressions that have it, so they may be equal
+  std::unordered_map<size_t, std::vector<Expression*>>
+    hashClasses; // hash value => list of expressions that have it, so they may
+                 // be equal
 
   void note(Expression* expr) {
     size_t hash;
     try {
       hash = doHash(expr);
     } catch (TrapException& e) {
-      // we don't bother trying to handle things that trap TODO: maybe abort the whole thing, move try out, for speed?
+      // we don't bother trying to handle things that trap TODO: maybe abort the
+      // whole thing, move try out, for speed?
       return;
     }
-    hashClasses[hash].push_back(expr); // we depend on expr being unique, so the classes are mathematical sets
+    hashClasses[hash].push_back(expr); // we depend on expr being unique, so the
+                                       // classes are mathematical sets
   }
 
   size_t doHash(Expression* expr) {
@@ -392,15 +456,30 @@
         hash = rehash(hash, 4);
         hash = rehash(hash, flow.value.type);
         switch (flow.value.type) {
-          case f32: flow.value = flow.value.castToI32(); break;
-          case f64: flow.value = flow.value.castToI64(); break;
-          default: {}
+          case f32:
+            flow.value = flow.value.castToI32();
+            break;
+          case f64:
+            flow.value = flow.value.castToI64();
+            break;
+          default: {
+          }
         }
         switch (flow.value.type) {
-          case none: hash = rehash(hash, 5); hash = rehash(hash, 6); break;
-          case i32: hash = rehash(hash, flow.value.geti32()); hash = rehash(hash, 7); break;
-          case i64: hash = rehash(hash, flow.value.geti64()); hash = rehash(hash, flow.value.geti64() >> 32); break;
-          default: WASM_UNREACHABLE();
+          case none:
+            hash = rehash(hash, 5);
+            hash = rehash(hash, 6);
+            break;
+          case i32:
+            hash = rehash(hash, flow.value.geti32());
+            hash = rehash(hash, 7);
+            break;
+          case i64:
+            hash = rehash(hash, flow.value.geti64());
+            hash = rehash(hash, flow.value.geti64() >> 32);
+            break;
+          default:
+            WASM_UNREACHABLE();
         }
       }
     }
@@ -414,9 +493,12 @@
 }
 
 // can our optimizer do better on a than b?
-static bool alreadyOptimizable(Expression* input, WasmType localTypes[MAX_LOCAL], Expression* output) {
+static bool alreadyOptimizable(Expression* input,
+                               WasmType localTypes[MAX_LOCAL],
+                               Expression* output) {
   Module temp;
-  // make a single function that receives the expressions locals and returns its output
+  // make a single function that receives the expressions locals and returns its
+  // output
   auto* func = new Function();
   func->name = Name("temp");
   func->result = input->type;
@@ -442,7 +524,8 @@
 // Given two expressions that hashing suggests might be the same, try
 // harder directly on the two to prove or disprove equivalence
 bool looksValid(Expression* a, Expression* b) {
-  if (a->type != b->type) return false; // hash collision, these are not even the same type
+  if (a->type != b->type)
+    return false; // hash collision, these are not even the same type
   // local types must be identical, otherwise the rule isn't even valid to apply
   ScanLocals aScanner(a), bScanner(b);
   for (Index i = 0; i < MAX_LOCAL; i++) {
@@ -458,12 +541,14 @@
     Flow aFlow = Runner(localGenerator).visit(a);
     Flow bFlow = Runner(localGenerator).visit(b);
     // TODO: breaking
-    if (aFlow.value != bFlow.value) return false;
+    if (aFlow.value != bFlow.value)
+      return false;
   }
   // let's see if this possible optimization is already something our
   // optimizer can do: if we optimize the input, do we get something
   // as good or better than the output?
-  if (alreadyOptimizable(a, aScanner.localTypes, b)) return false;
+  if (alreadyOptimizable(a, aScanner.localTypes, b))
+    return false;
   // we see no reason these two should not be joined together in holy optimony
   return true;
 }
@@ -487,15 +572,21 @@
     }
   } generalizer(wasm);
 
-  return ExpressionManipulator::flexibleCopy(expr, wasm, [&](Expression* curr) { return generalizer.copy(curr); });
+  return ExpressionManipulator::flexibleCopy(
+    expr, wasm, [&](Expression* curr) { return generalizer.copy(curr); });
 }
 
-int main(int argc, const char *argv[]) {
+int main(int argc, const char* argv[]) {
   // receive arguments
   std::vector<std::string> filenames;
-  Options options("wasm-analyze", "Analyze a set of wasm modules. Provide a set of input files, optionally split by 'advice:' (in which case files afterwards are just advice, used to find optimization outputs but not inputs we focus on optimizing)");
-  options.add_positional("INFILES", Options::Arguments::N,
-                         [&](Options *o, const std::string &argument) {
+  Options options(
+    "wasm-analyze",
+    "Analyze a set of wasm modules. Provide a set of input files, optionally "
+    "split by 'advice:' (in which case files afterwards are just advice, used "
+    "to find optimization outputs but not inputs we focus on optimizing)");
+  options.add_positional("INFILES",
+                         Options::Arguments::N,
+                         [&](Options* o, const std::string& argument) {
                            filenames.push_back(argument);
                          });
   options.parse(argc, argv);
@@ -550,8 +641,8 @@
   }
 #endif
 
-  // perform execution hashing, looking for expressions that are functionally equivalent,
-  // so one can be optimized to the other
+  // perform execution hashing, looking for expressions that are functionally
+  // equivalent, so one can be optimized to the other
 
   std::cerr << "[hashing executions]\n";
 
@@ -574,7 +665,8 @@
     }
     std::cout << "  num relevant expressions: " << total << '\n';
   }
-  std::cout << "  num execution classes: " << executionHasher.hashClasses.size() << '\n';
+  std::cout << "  num execution classes: " << executionHasher.hashClasses.size()
+            << '\n';
   {
     size_t max = 0;
     for (auto& pair : executionHasher.hashClasses) {
@@ -585,15 +677,17 @@
 
   // Detailed output
   {
-    // a rule is a connection between one pattern and another, which we think may be equivalent to it,
-    // and which may provide a measured benefit
-    // TODO: test rules on more random inputs, trying to prove they are not equivalent?
+    // a rule is a connection between one pattern and another, which we think
+    // may be equivalent to it, and which may provide a measured benefit
+    // TODO: test rules on more random inputs, trying to prove they are not
+    // equivalent?
     struct Rule {
       Expression* from;
       Expression* to;
       size_t benefit;
 
-      Rule(Expression* from, Expression* to, size_t benefit) : from(from), to(to), benefit(benefit) {}
+      Rule(Expression* from, Expression* to, size_t benefit)
+        : from(from), to(to), benefit(benefit) {}
     };
 
     std::vector<Rule> rules;
@@ -603,23 +697,32 @@
     for (auto& pair : executionHasher.hashClasses) {
       auto& clazz = pair.second;
       Index size = clazz.size();
-      if (size < 2) continue;
+      if (size < 2)
+        continue;
       // consider all pairs, since some may be spurious hash collisions
       for (Index i = 0; i < size; i++) {
         auto* iExpr = clazz[i];
         auto iFreq = freqs[iExpr];
-        if (iFreq == 0) continue; // no frequency means no benefit to optimize it; this expression is just a target of optimization, not an origin
+        if (iFreq == 0)
+          continue; // no frequency means no benefit to optimize it; this
+                    // expression is just a target of optimization, not an
+                    // origin
         Index iSize = calcWeight(iExpr);
         Expression* best = nullptr;
         Index bestSize = -1;
         for (Index j = 0; j < size; j++) {
-          if (i == j) continue;
+          if (i == j)
+            continue;
           auto* jExpr = clazz[j];
           Index jSize = calcWeight(jExpr);
           // we are looking for a rule where i => j, so we need j to be smaller
-          if (iSize <= jSize) continue; // TODO: for equality, look not just at size, but cost etc.
-          // a likely candidate, if direct attempts to prove they differ fail, this is worth reporting to the user
-          if (best && jSize >= bestSize) continue; // we can't do better
+          if (iSize <= jSize)
+            continue; // TODO: for equality, look not just at size, but cost
+                      // etc.
+          // a likely candidate, if direct attempts to prove they differ fail,
+          // this is worth reporting to the user
+          if (best && jSize >= bestSize)
+            continue; // we can't do better
           if (looksValid(iExpr, jExpr)) {
             best = jExpr;
             bestSize = jSize;
@@ -631,15 +734,16 @@
       }
     }
 
-    // Many rules are part of a more general pattern, for example x + 1 + 1 === x + 2 is
-    // closely related to x + 1 + 2 === x + 3. The generalized rule is what the human would
-    // write in the optimizer, so to assess the benefit of rules, we must generalize in
-    // our output.
+    // Many rules are part of a more general pattern, for example x + 1 + 1 ===
+    // x + 2 is closely related to x + 1 + 2 === x + 3. The generalized rule is
+    // what the human would write in the optimizer, so to assess the benefit of
+    // rules, we must generalize in our output.
 
     std::cerr << "[generalizing]\n";
 
     struct GeneralizedRule : public Rule {
-      std::vector<Rule*> rules; // the specific rules underlying this generalization
+      std::vector<Rule*>
+        rules; // the specific rules underlying this generalization
 
       GeneralizedRule(Expression* from, Rule* rule) : Rule(from, nullptr, 0) {
         addRule(rule);
@@ -652,13 +756,20 @@
     };
 
     // hashed from expression => the generalized rules for that expression
-    std::unordered_map<HashedExpression, GeneralizedRule, ExpressionHasher, ExpressionComparer> generalizedRules;
+    std::unordered_map<HashedExpression,
+                       GeneralizedRule,
+                       ExpressionHasher,
+                       ExpressionComparer>
+      generalizedRules;
 
     for (auto& rule : rules) {
-      auto generalizedFrom = HashedExpression(generalize(rule.from, global)); // TODO: save memory, don't use global unless needed?
+      auto generalizedFrom = HashedExpression(generalize(
+        rule.from,
+        global)); // TODO: save memory, don't use global unless needed?
       auto iter = generalizedRules.find(generalizedFrom);
       if (iter == generalizedRules.end()) {
-        generalizedRules.emplace(generalizedFrom, GeneralizedRule(generalizedFrom.expr, &rule));
+        generalizedRules.emplace(generalizedFrom,
+                                 GeneralizedRule(generalizedFrom.expr, &rule));
       } else {
         iter->second.addRule(&rule);
       }
@@ -676,14 +787,19 @@
     auto ruleSorter = [](const Rule* a, const Rule* b) {
       // primary sorting criteria is the size benefit
       auto diff = int64_t(a->benefit) - int64_t(b->benefit);
-      if (diff > 0) return true;
-      if (diff < 0) return false;
+      if (diff > 0)
+        return true;
+      if (diff < 0)
+        return false;
       return size_t(a->from) < size_t(b->from);
     };
 
-    std::sort(sortedGeneralizedRules.begin(), sortedGeneralizedRules.end(), [&ruleSorter](const GeneralizedRule* a, const GeneralizedRule* b) {
-      return ruleSorter(a, b);
-    });
+    std::sort(
+      sortedGeneralizedRules.begin(),
+      sortedGeneralizedRules.end(),
+      [&ruleSorter](const GeneralizedRule* a, const GeneralizedRule* b) {
+        return ruleSorter(a, b);
+      });
 
     std::cout << "sorted possible optimization rules:\n";
 
@@ -691,15 +807,23 @@
 
     size_t i = 0;
     for (auto* item : sortedGeneralizedRules) {
-      std::cout << "\n[generalized rule " << (i++) << ": benefit: " << item->benefit << ", (" << (100*double(item->benefit)/totalWeight) << "%)], input pattern:\n" << item->from << '\n';
+      std::cout << "\n[generalized rule " << (i++)
+                << ": benefit: " << item->benefit << ", ("
+                << (100 * double(item->benefit) / totalWeight)
+                << "%)], input pattern:\n"
+                << item->from << '\n';
       // show the specific rules underlying the generalized one
       std::sort(item->rules.begin(), item->rules.end(), ruleSorter);
       for (auto* rule : item->rules) {
-        std::cout << "\n[child specific rule benefit: " << rule->benefit << ", (" << (100*double(rule->benefit)/totalWeight) << "%)], possible rule:\n" << rule->from << "\n =->\n" << rule->to << '\n';
+        std::cout << "\n[child specific rule benefit: " << rule->benefit
+                  << ", (" << (100 * double(rule->benefit) / totalWeight)
+                  << "%)], possible rule:\n"
+                  << rule->from << "\n =->\n"
+                  << rule->to << '\n';
       }
     }
   }
 
-  // TODO TODO: if all execution hashes of expr are the same, it might be constant (avoids needing to have all constants hashed)
+  // TODO TODO: if all execution hashes of expr are the same, it might be
+  // constant (avoids needing to have all constants hashed)
 }
-