Fix const
diff --git a/src/ir/table-utils.cpp b/src/ir/table-utils.cpp
index 364f7ba..7563782 100644
--- a/src/ir/table-utils.cpp
+++ b/src/ir/table-utils.cpp
@@ -62,15 +62,15 @@
   return ret;
 }
 
-bool usesExpressions(ElementSegment* curr, Module* module) {
+bool usesExpressions(const ElementSegment* curr, const Module* module) {
   // Binaryen IR always has ref.funcs for functions in tables for uniformity,
   // so that by itself does not indicate if expressions should be used when
   // emitting the table or not. But definitely anything that is not a ref.func
   // implies we are post-MVP and must use expressions.
   bool allElementsRefFunc =
-    std::all_of(curr->data.begin(), curr->data.end(), [](Expression* entry) {
-      return entry->is<RefFunc>();
-    });
+    std::all_of(curr->data.begin(),
+                curr->data.end(),
+                [](const Expression* entry) { return entry->is<RefFunc>(); });
 
   // If the segment has a specialized (non-MVP) type, then it must use the
   // post-MVP form of using expressions.
diff --git a/src/ir/table-utils.h b/src/ir/table-utils.h
index cee88fc..67bf585 100644
--- a/src/ir/table-utils.h
+++ b/src/ir/table-utils.h
@@ -118,7 +118,7 @@
 // Returns whether a segment uses arbitrary wasm expressions, as opposed to the
 // original tables from the MVP that use function indices. (Some post-MVP tables
 // do so, and some do not, depending on their type and use.)
-bool usesExpressions(ElementSegment* curr, Module* module);
+bool usesExpressions(const ElementSegment* curr, const Module* module);
 
 // Information about a table's optimizability.
 struct TableInfo {
diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp
index f887075..71863b5 100644
--- a/src/passes/Print.cpp
+++ b/src/passes/Print.cpp
@@ -37,14 +37,15 @@
 
 struct PrintSExpression;
 
-static std::ostream& printExpression(Expression* expression,
+static std::ostream& printExpression(const Expression* expression,
                                      std::ostream& o,
                                      bool minify = false,
                                      bool full = false,
-                                     Module* wasm = nullptr);
+                                     const Module* wasm = nullptr);
 
-static std::ostream&
-printStackInst(StackInst* inst, std::ostream& o, Function* func = nullptr);
+static std::ostream& printStackInst(const StackInst* inst,
+                                    std::ostream& o,
+                                    const Function* func = nullptr);
 
 static std::ostream& printStackIR(StackIR* ir, PrintSExpression&);
 
@@ -62,7 +63,7 @@
   return full;
 }
 
-std::ostream& printMemoryName(Name name, std::ostream& o, Module* wasm) {
+std::ostream& printMemoryName(Name name, std::ostream& o, const Module* wasm) {
   if (!wasm || wasm->memories.size() > 1) {
     o << ' ';
     name.print(o);
@@ -70,7 +71,7 @@
   return o;
 }
 
-std::ostream& printLocal(Index index, Function* func, std::ostream& o) {
+std::ostream& printLocal(Index index, const Function* func, std::ostream& o) {
   Name name;
   if (func) {
     name = func->getLocalNameOrDefault(index);
@@ -83,11 +84,11 @@
 
 // Print a name from the type section, if available. Otherwise print the type
 // normally.
-void printTypeOrName(Type type, std::ostream& o, Module* wasm) {
+void printTypeOrName(Type type, std::ostream& o, const Module* wasm) {
   struct Printer : TypeNameGeneratorBase<Printer> {
-    Module* wasm;
+    const Module* wasm;
     DefaultTypeNameGenerator fallback;
-    Printer(Module* wasm) : wasm(wasm) {}
+    Printer(const Module* wasm) : wasm(wasm) {}
     TypeNames getNames(HeapType type) {
       if (wasm) {
         if (auto it = wasm->typeNames.find(type); it != wasm->typeNames.end()) {
@@ -130,8 +131,8 @@
   // If present, it contains StackIR that we will print.
   std::optional<ModuleStackIR> moduleStackIR;
 
-  Module* currModule = nullptr;
-  Function* currFunction = nullptr;
+  const Module* currModule = nullptr;
+  const Function* currFunction = nullptr;
   // Keep track of the last printed debug location to avoid printing
   // repeated debug locations for children. nullopt means that we have
   // not yet printed any debug location, or that we last printed an
@@ -230,7 +231,7 @@
     }
   }
 
-  void setModule(Module* module);
+  void setModule(const Module* module);
 
   std::ostream& printType(Type type) { return o << typePrinter(type); }
 
@@ -297,7 +298,7 @@
   void setFull(bool full_) { full = full_; }
 
   void generateStackIR(const PassOptions& options) {
-    moduleStackIR.emplace(*currModule, options);
+    moduleStackIR.emplace(const_cast<Module&>(*currModule), options);
   }
 
   void setDebugInfo(bool debugInfo_) { debugInfo = debugInfo_; }
@@ -379,28 +380,28 @@
   }
 
   // Module-level visitors
-  void handleSignature(Function* curr, bool printImplicitNames = false);
-  void visitExport(Export* curr);
-  void emitImportHeader(Importable* curr);
-  void visitGlobal(Global* curr);
-  void emitGlobalType(Global* curr);
-  void visitImportedGlobal(Global* curr);
-  void visitDefinedGlobal(Global* curr);
-  void visitFunction(Function* curr);
-  void visitImportedFunction(Function* curr);
-  void visitDefinedFunction(Function* curr);
-  void visitTag(Tag* curr);
-  void visitImportedTag(Tag* curr);
-  void visitDefinedTag(Tag* curr);
+  void handleSignature(const Function* curr, bool printImplicitNames = false);
+  void visitExport(const Export* curr);
+  void emitImportHeader(const Importable* curr);
+  void visitGlobal(const Global* curr);
+  void emitGlobalType(const Global* curr);
+  void visitImportedGlobal(const Global* curr);
+  void visitDefinedGlobal(const Global* curr);
+  void visitFunction(const Function* curr);
+  void visitImportedFunction(const Function* curr);
+  void visitDefinedFunction(const Function* curr);
+  void visitTag(const Tag* curr);
+  void visitImportedTag(const Tag* curr);
+  void visitDefinedTag(const Tag* curr);
   void printTagType(HeapType type);
-  void printTableHeader(Table* curr);
-  void visitTable(Table* curr);
-  void visitElementSegment(ElementSegment* curr);
-  void printMemoryHeader(Memory* curr);
-  void visitMemory(Memory* curr);
-  void visitDataSegment(DataSegment* curr);
+  void printTableHeader(const Table* curr);
+  void visitTable(const Table* curr);
+  void visitElementSegment(const ElementSegment* curr);
+  void printMemoryHeader(const Memory* curr);
+  void visitMemory(const Memory* curr);
+  void visitDataSegment(const DataSegment* curr);
   void printDylinkSection(const std::unique_ptr<DylinkSection>& dylinkSection);
-  void visitModule(Module* curr);
+  void visitModule(const Module* curr);
 };
 
 // Prints the internal contents of an expression: everything but
@@ -408,8 +409,8 @@
 struct PrintExpressionContents
   : public OverriddenVisitor<PrintExpressionContents> {
   PrintSExpression& parent;
-  Module* wasm = nullptr;
-  Function* currFunction = nullptr;
+  const Module* wasm = nullptr;
+  const Function* currFunction = nullptr;
   std::ostream& o;
   FeatureSet features;
   bool full;
@@ -2688,10 +2689,12 @@
   }
 };
 
-void PrintSExpression::setModule(Module* module) {
+void PrintSExpression::setModule(const Module* module) {
   currModule = module;
   if (module) {
-    heapTypes = ModuleUtils::getOptimizedIndexedHeapTypes(*module).types;
+    heapTypes =
+      ModuleUtils::getOptimizedIndexedHeapTypes(const_cast<Module&>(*module))
+        .types;
     for (auto type : heapTypes) {
       if (type.isSignature()) {
         signatureTypes.insert({type.getSignature(), type});
@@ -3143,7 +3146,7 @@
   return type.isOpen() || type.isShared() || type.getRecGroup().size() > 1;
 }
 
-void PrintSExpression::handleSignature(Function* curr,
+void PrintSExpression::handleSignature(const Function* curr,
                                        bool printImplicitNames) {
   o << '(';
   printMajor(o, "func ");
@@ -3202,7 +3205,7 @@
   }
 }
 
-void PrintSExpression::visitExport(Export* curr) {
+void PrintSExpression::visitExport(const Export* curr) {
   o << '(';
   printMedium(o, "export ");
   std::stringstream escaped;
@@ -3229,10 +3232,10 @@
   }
   o << ' ';
   // TODO: specific case for type exports
-  curr->getInternalName()->print(o) << "))";
+  const_cast<Export*>(curr)->getInternalName()->print(o) << "))";
 }
 
-void PrintSExpression::emitImportHeader(Importable* curr) {
+void PrintSExpression::emitImportHeader(const Importable* curr) {
   printMedium(o, "import ");
   std::stringstream escapedModule, escapedBase;
   String::printEscaped(escapedModule, curr->module.str);
@@ -3241,7 +3244,7 @@
   printText(o, escapedBase.str(), false) << ' ';
 }
 
-void PrintSExpression::visitGlobal(Global* curr) {
+void PrintSExpression::visitGlobal(const Global* curr) {
   if (curr->imported()) {
     visitImportedGlobal(curr);
   } else {
@@ -3249,7 +3252,7 @@
   }
 }
 
-void PrintSExpression::emitGlobalType(Global* curr) {
+void PrintSExpression::emitGlobalType(const Global* curr) {
   if (curr->mutable_) {
     o << "(mut ";
     printType(curr->type) << ')';
@@ -3258,7 +3261,7 @@
   }
 }
 
-void PrintSExpression::visitImportedGlobal(Global* curr) {
+void PrintSExpression::visitImportedGlobal(const Global* curr) {
   doIndent(o, indent);
   o << '(';
   emitImportHeader(curr);
@@ -3268,19 +3271,19 @@
   o << "))" << maybeNewLine;
 }
 
-void PrintSExpression::visitDefinedGlobal(Global* curr) {
+void PrintSExpression::visitDefinedGlobal(const Global* curr) {
   doIndent(o, indent);
   o << '(';
   printMedium(o, "global ");
   curr->name.print(o) << ' ';
   emitGlobalType(curr);
   o << ' ';
-  visit(curr->init);
+  visit(const_cast<Expression*>(curr->init));
   o << ')';
   o << maybeNewLine;
 }
 
-void PrintSExpression::visitFunction(Function* curr) {
+void PrintSExpression::visitFunction(const Function* curr) {
   if (curr->imported()) {
     visitImportedFunction(curr);
   } else if (curr->body == nullptr) {
@@ -3291,7 +3294,7 @@
   }
 }
 
-void PrintSExpression::visitImportedFunction(Function* curr) {
+void PrintSExpression::visitImportedFunction(const Function* curr) {
   doIndent(o, indent);
   currFunction = curr;
   lastPrintedLocation = std::nullopt;
@@ -3303,7 +3306,7 @@
   o << maybeNewLine;
 }
 
-void PrintSExpression::visitDefinedFunction(Function* curr) {
+void PrintSExpression::visitDefinedFunction(const Function* curr) {
   doIndent(o, indent);
   currFunction = curr;
   lastPrintedLocation = std::nullopt;
@@ -3325,7 +3328,7 @@
   // Print the body.
   StackIR* stackIR = nullptr;
   if (moduleStackIR) {
-    stackIR = moduleStackIR->getStackIROrNull(curr);
+    stackIR = moduleStackIR->getStackIROrNull(const_cast<Function*>(curr));
   }
   if (stackIR) {
     printStackIR(stackIR, *this);
@@ -3359,7 +3362,7 @@
   o << maybeNewLine;
 }
 
-void PrintSExpression::visitTag(Tag* curr) {
+void PrintSExpression::visitTag(const Tag* curr) {
   if (curr->imported()) {
     visitImportedTag(curr);
   } else {
@@ -3367,7 +3370,7 @@
   }
 }
 
-void PrintSExpression::visitImportedTag(Tag* curr) {
+void PrintSExpression::visitImportedTag(const Tag* curr) {
   doIndent(o, indent);
   o << '(';
   emitImportHeader(curr);
@@ -3378,7 +3381,7 @@
   o << "))" << maybeNewLine;
 }
 
-void PrintSExpression::visitDefinedTag(Tag* curr) {
+void PrintSExpression::visitDefinedTag(const Tag* curr) {
   doIndent(o, indent);
   o << '(';
   printMedium(o, "tag ");
@@ -3410,7 +3413,7 @@
   }
 }
 
-void PrintSExpression::printTableHeader(Table* curr) {
+void PrintSExpression::printTableHeader(const Table* curr) {
   o << '(';
   printMedium(o, "table") << ' ';
   curr->name.print(o) << ' ';
@@ -3425,7 +3428,7 @@
   printType(curr->type) << ')';
 }
 
-void PrintSExpression::visitTable(Table* curr) {
+void PrintSExpression::visitTable(const Table* curr) {
   if (curr->imported()) {
     doIndent(o, indent);
     o << '(';
@@ -3439,7 +3442,7 @@
   }
 }
 
-void PrintSExpression::visitElementSegment(ElementSegment* curr) {
+void PrintSExpression::visitElementSegment(const ElementSegment* curr) {
   bool usesExpressions = TableUtils::usesExpressions(curr, currModule);
   auto printElemType = [&]() {
     if (!usesExpressions) {
@@ -3467,7 +3470,7 @@
     if (needExplicitOffset) {
       o << "(offset ";
     }
-    visit(curr->offset);
+    visit(const_cast<Expression*>(curr->offset));
     if (needExplicitOffset) {
       o << ')';
     }
@@ -3490,14 +3493,14 @@
   } else {
     for (auto* entry : curr->data) {
       o << " (item ";
-      visit(entry);
+      visit(const_cast<Expression*>(entry));
       o << ')';
     }
   }
   o << ')' << maybeNewLine;
 }
 
-void PrintSExpression::printMemoryHeader(Memory* curr) {
+void PrintSExpression::printMemoryHeader(const Memory* curr) {
   o << '(';
   printMedium(o, "memory") << ' ';
   curr->name.print(o) << ' ';
@@ -3519,7 +3522,7 @@
   o << ")";
 }
 
-void PrintSExpression::visitMemory(Memory* curr) {
+void PrintSExpression::visitMemory(const Memory* curr) {
   if (curr->imported()) {
     doIndent(o, indent);
     o << '(';
@@ -3533,7 +3536,7 @@
   }
 }
 
-void PrintSExpression::visitDataSegment(DataSegment* curr) {
+void PrintSExpression::visitDataSegment(const DataSegment* curr) {
   if (!curr->isPassive && !curr->offset) {
     // This data segment must have been created from the datacount section but
     // not parsed yet. Skip it.
@@ -3555,7 +3558,7 @@
     if (needExplicitOffset) {
       o << "(offset ";
     }
-    visit(curr->offset);
+    visit(const_cast<Expression*>(curr->offset));
     if (needExplicitOffset) {
       o << ")";
     }
@@ -3584,7 +3587,7 @@
   }
 }
 
-void PrintSExpression::visitModule(Module* curr) {
+void PrintSExpression::visitModule(const Module* curr) {
   setModule(curr);
   o << '(';
   printMajor(o, "module");
@@ -3623,28 +3626,29 @@
   }
   finishGroup();
 
+  auto* unconst = const_cast<Module*>(curr);
   ModuleUtils::iterImportedMemories(
-    *curr, [&](Memory* memory) { visitMemory(memory); });
-  ModuleUtils::iterImportedTables(*curr,
+    *unconst, [&](Memory* memory) { visitMemory(memory); });
+  ModuleUtils::iterImportedTables(*unconst,
                                   [&](Table* table) { visitTable(table); });
   ModuleUtils::iterImportedGlobals(
-    *curr, [&](Global* global) { visitGlobal(global); });
+    *unconst, [&](Global* global) { visitGlobal(global); });
   ModuleUtils::iterImportedFunctions(
-    *curr, [&](Function* func) { visitFunction(func); });
-  ModuleUtils::iterImportedTags(*curr, [&](Tag* tag) { visitTag(tag); });
-  ModuleUtils::iterDefinedGlobals(*curr,
+    *unconst, [&](Function* func) { visitFunction(func); });
+  ModuleUtils::iterImportedTags(*unconst, [&](Tag* tag) { visitTag(tag); });
+  ModuleUtils::iterDefinedGlobals(*unconst,
                                   [&](Global* global) { visitGlobal(global); });
   ModuleUtils::iterDefinedMemories(
-    *curr, [&](Memory* memory) { visitMemory(memory); });
+    *unconst, [&](Memory* memory) { visitMemory(memory); });
   for (auto& segment : curr->dataSegments) {
     visitDataSegment(segment.get());
   }
-  ModuleUtils::iterDefinedTables(*curr,
+  ModuleUtils::iterDefinedTables(*unconst,
                                  [&](Table* table) { visitTable(table); });
   for (auto& segment : curr->elementSegments) {
     visitElementSegment(segment.get());
   }
-  auto elemDeclareNames = TableUtils::getFunctionsNeedingElemDeclare(*curr);
+  auto elemDeclareNames = TableUtils::getFunctionsNeedingElemDeclare(*unconst);
   if (!elemDeclareNames.empty()) {
     doIndent(o, indent);
     printMedium(o, "(elem");
@@ -3655,7 +3659,7 @@
     }
     o << ')' << maybeNewLine;
   }
-  ModuleUtils::iterDefinedTags(*curr, [&](Tag* tag) { visitTag(tag); });
+  ModuleUtils::iterDefinedTags(*unconst, [&](Tag* tag) { visitTag(tag); });
   for (auto& child : curr->exports) {
     doIndent(o, indent);
     visitExport(child.get());
@@ -3669,7 +3673,7 @@
     o << maybeNewLine;
   }
   ModuleUtils::iterDefinedFunctions(
-    *curr, [&](Function* func) { visitFunction(func); });
+    *unconst, [&](Function* func) { visitFunction(func); });
   if (curr->dylinkSection) {
     printDylinkSection(curr->dylinkSection);
   }
@@ -3778,11 +3782,11 @@
   }
 };
 
-static std::ostream& printExpression(Expression* expression,
+static std::ostream& printExpression(const Expression* expression,
                                      std::ostream& o,
                                      bool minify,
                                      bool full,
-                                     Module* wasm) {
+                                     const Module* wasm) {
   if (!expression) {
     o << "(null expression)";
     return o;
@@ -3793,7 +3797,7 @@
   if (full || isFullForced()) {
     print.setFull(true);
   }
-  print.visit(expression);
+  print.visit(const_cast<Expression*>(expression));
   if (full || isFullForced()) {
     o << " (; ";
     printTypeOrName(expression->type, o, wasm);
@@ -3803,8 +3807,9 @@
 }
 
 static std::ostream&
-printStackInst(StackInst* inst, std::ostream& o, Function* func) {
+printStackInst(const StackInst* inst, std::ostream& o, const Function* func) {
   PrintSExpression printer(o);
+  printer.currFunction = func;
   switch (inst->op) {
     case StackInst::Basic:
     case StackInst::BlockBegin:
@@ -3944,38 +3949,39 @@
   return o;
 }
 
-std::ostream&
-printStackIR(std::ostream& o, Module* module, const PassOptions& options) {
-  wasm::PassRunner runner(module, options);
+std::ostream& printStackIR(std::ostream& o,
+                           const Module* module,
+                           const PassOptions& options) {
+  wasm::PassRunner runner(const_cast<Module*>(module), options);
   runner.add(std::make_unique<PrintStackIR>(&o));
   runner.run();
   return o;
 }
 
-std::ostream& operator<<(std::ostream& o, wasm::Module& module) {
-  wasm::PassRunner runner(&module);
+std::ostream& operator<<(std::ostream& o, const wasm::Module& module) {
+  wasm::PassRunner runner(const_cast<wasm::Module*>(&module));
   wasm::Printer printer(&o);
   // Do not use runner.run(), since that will cause an infinite recursion in
   // BINARYEN_PASS_DEBUG=3, which prints modules (using this function) as part
   // of running passes.
   printer.setPassRunner(&runner);
-  printer.run(&module);
+  printer.run(const_cast<wasm::Module*>(&module));
   return o;
 }
 
-std::ostream& operator<<(std::ostream& o, wasm::Function& func) {
+std::ostream& operator<<(std::ostream& o, const wasm::Function& func) {
   wasm::PrintSExpression print(o);
   print.setMinify(false);
   print.setDebugInfo(false);
-  print.visitFunction(&func);
+  print.visitFunction(const_cast<wasm::Function*>(&func));
   return o;
 }
 
-std::ostream& operator<<(std::ostream& o, wasm::Expression& expression) {
+std::ostream& operator<<(std::ostream& o, const wasm::Expression& expression) {
   return wasm::printExpression(&expression, o);
 }
 
-std::ostream& operator<<(std::ostream& o, wasm::Expression* expression) {
+std::ostream& operator<<(std::ostream& o, const wasm::Expression* expression) {
   return wasm::printExpression(expression, o);
 }
 
@@ -3983,14 +3989,16 @@
   return wasm::printExpression(pair.second, o, false, false, &pair.first);
 }
 
-std::ostream& operator<<(std::ostream& o, wasm::ShallowExpression expression) {
+std::ostream& operator<<(std::ostream& o,
+                         const wasm::ShallowExpression expression) {
   wasm::PrintSExpression printer(o);
   printer.setModule(expression.module);
-  wasm::PrintExpressionContents(printer).visit(expression.expr);
+  wasm::PrintExpressionContents(printer).visit(
+    const_cast<Expression*>(expression.expr));
   return o;
 }
 
-std::ostream& operator<<(std::ostream& o, wasm::StackInst& inst) {
+std::ostream& operator<<(std::ostream& o, const wasm::StackInst& inst) {
   return wasm::printStackInst(&inst, o);
 }
 
@@ -4032,8 +4040,7 @@
 
 std::ostream& operator<<(std::ostream& o, const Table& table) {
   wasm::PrintSExpression printer(o);
-  // TODO: printTableHeader should take a const Table*
-  printer.printTableHeader(const_cast<Table*>(&table));
+  printer.printTableHeader(&table);
   return o;
 }
 
diff --git a/src/wasm-stack.h b/src/wasm-stack.h
index 7600fc4..a14f3f3 100644
--- a/src/wasm-stack.h
+++ b/src/wasm-stack.h
@@ -565,12 +565,12 @@
 
 // Generate and emit StackIR.
 std::ostream&
-printStackIR(std::ostream& o, Module* module, const PassOptions& options);
+printStackIR(std::ostream& o, const Module* module, const PassOptions& options);
 
 } // namespace wasm
 
 namespace std {
-std::ostream& operator<<(std::ostream& o, wasm::StackInst& inst);
+std::ostream& operator<<(std::ostream& o, const wasm::StackInst& inst);
 } // namespace std
 
 #endif // wasm_stack_h
diff --git a/src/wasm.h b/src/wasm.h
index 716cf27..d99f9aa 100644
--- a/src/wasm.h
+++ b/src/wasm.h
@@ -2421,9 +2421,9 @@
   bool noPartialInline = false;
 
   // Methods
-  Signature getSig() { return type.getHeapType().getSignature(); }
-  Type getParams() { return getSig().params; }
-  Type getResults() { return getSig().results; }
+  Signature getSig() const { return type.getHeapType().getSignature(); }
+  Type getParams() const { return getSig().params; }
+  Type getResults() const { return getSig().results; }
   void setParams(Type params) {
     type = type.with(Signature(params, getResults()));
   }
@@ -2431,21 +2431,21 @@
     type = type.with(Signature(getParams(), results));
   }
 
-  size_t getNumParams();
-  size_t getNumVars();
-  size_t getNumLocals();
+  size_t getNumParams() const;
+  size_t getNumVars() const;
+  size_t getNumLocals() const;
 
-  bool isParam(Index index);
-  bool isVar(Index index);
+  bool isParam(Index index) const;
+  bool isVar(Index index) const;
 
-  Name getLocalName(Index index);
-  Index getLocalIndex(Name name);
+  Name getLocalName(Index index) const;
+  Index getLocalIndex(Name name) const;
   bool hasLocalIndex(Name name) const;
-  Index getVarIndexBase();
-  Type getLocalType(Index index);
+  Index getVarIndexBase() const;
+  Type getLocalType(Index index) const;
 
-  Name getLocalNameOrDefault(Index index);
-  Name getLocalNameOrGeneric(Index index);
+  Name getLocalNameOrDefault(Index index) const;
+  Name getLocalNameOrGeneric(Index index) const;
 
   bool hasLocalName(Index index) const;
   void setLocalName(Index index, Name name);
@@ -2540,8 +2540,8 @@
   Type addressType = Type::i32;
   Type type = Type(HeapType::func, Nullable);
 
-  bool hasMax() { return max != kUnlimitedSize; }
-  bool is64() { return addressType == Type::i64; }
+  bool hasMax() const { return max != kUnlimitedSize; }
+  bool is64() const { return addressType == Type::i64; }
   void clear() {
     name = "";
     initial = 0;
@@ -2577,8 +2577,8 @@
   bool shared = false;
   Type addressType = Type::i32;
 
-  bool hasMax() { return max != kUnlimitedSize; }
-  bool is64() { return addressType == Type::i64; }
+  bool hasMax() const { return max != kUnlimitedSize; }
+  bool is64() const { return addressType == Type::i64; }
   Address::address64_t maxSize32() const { return 1ull << (32 - pageSizeLog2); }
   Address::address64_t maxSize64() const {
     if (pageSizeLog2 == 0) {
@@ -2751,24 +2751,25 @@
 };
 
 // Utility for printing an expression with named types.
-using ModuleExpression = std::pair<Module&, Expression*>;
+using ModuleExpression = std::pair<const Module&, const Expression*>;
 
 // Utilities for printing an type with a name, if the module defines a name.
-using ModuleType = std::pair<Module&, Type>;
-using ModuleHeapType = std::pair<Module&, HeapType>;
+using ModuleType = std::pair<const Module&, Type>;
+using ModuleHeapType = std::pair<const Module&, HeapType>;
 
 // Utility for printing only the top level of an expression. Named types will be
 // used if `module` is non-null.
 struct ShallowExpression {
-  Expression* expr;
-  Module* module = nullptr;
+  const Expression* expr;
+  const Module* module = nullptr;
 };
 
-std::ostream& operator<<(std::ostream& o, wasm::Module& module);
-std::ostream& operator<<(std::ostream& o, wasm::Function& func);
-std::ostream& operator<<(std::ostream& o, wasm::Expression& expression);
+std::ostream& operator<<(std::ostream& o, const wasm::Module& module);
+std::ostream& operator<<(std::ostream& o, const wasm::Function& func);
+std::ostream& operator<<(std::ostream& o, const wasm::Expression& expression);
 std::ostream& operator<<(std::ostream& o, wasm::ModuleExpression pair);
-std::ostream& operator<<(std::ostream& o, wasm::ShallowExpression expression);
+std::ostream& operator<<(std::ostream& o,
+                         const wasm::ShallowExpression expression);
 std::ostream& operator<<(std::ostream& o, wasm::ModuleType pair);
 std::ostream& operator<<(std::ostream& o, wasm::ModuleHeapType pair);
 std::ostream& operator<<(std::ostream& os, wasm::MemoryOrder mo);
diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp
index b9df7a2..5ef3cd1 100644
--- a/src/wasm/wasm.cpp
+++ b/src/wasm/wasm.cpp
@@ -1618,19 +1618,21 @@
   type = cont.getHeapType().getContinuation().type.getSignature().params;
 }
 
-size_t Function::getNumParams() { return getParams().size(); }
+size_t Function::getNumParams() const { return getParams().size(); }
 
-size_t Function::getNumVars() { return vars.size(); }
+size_t Function::getNumVars() const { return vars.size(); }
 
-size_t Function::getNumLocals() { return getParams().size() + vars.size(); }
+size_t Function::getNumLocals() const {
+  return getParams().size() + vars.size();
+}
 
-bool Function::isParam(Index index) {
+bool Function::isParam(Index index) const {
   size_t size = getParams().size();
   assert(index < size + vars.size());
   return index < size;
 }
 
-bool Function::isVar(Index index) {
+bool Function::isVar(Index index) const {
   auto base = getVarIndexBase();
   assert(index < base + vars.size());
   return index >= base;
@@ -1640,7 +1642,7 @@
   return localNames.find(index) != localNames.end();
 }
 
-Name Function::getLocalName(Index index) { return localNames.at(index); }
+Name Function::getLocalName(Index index) const { return localNames.at(index); }
 
 void Function::setLocalName(Index index, Name name) {
   assert(index < getNumLocals());
@@ -1648,7 +1650,7 @@
   localIndices[name] = index;
 }
 
-Name Function::getLocalNameOrDefault(Index index) {
+Name Function::getLocalNameOrDefault(Index index) const {
   auto nameIt = localNames.find(index);
   if (nameIt != localNames.end()) {
     return nameIt->second;
@@ -1657,7 +1659,7 @@
   return Name();
 }
 
-Name Function::getLocalNameOrGeneric(Index index) {
+Name Function::getLocalNameOrGeneric(Index index) const {
   auto nameIt = localNames.find(index);
   if (nameIt != localNames.end()) {
     return nameIt->second;
@@ -1669,7 +1671,7 @@
   return localIndices.find(name) != localIndices.end();
 }
 
-Index Function::getLocalIndex(Name name) {
+Index Function::getLocalIndex(Name name) const {
   auto iter = localIndices.find(name);
   if (iter == localIndices.end()) {
     Fatal() << "Function::getLocalIndex: " << name << " does not exist";
@@ -1677,9 +1679,9 @@
   return iter->second;
 }
 
-Index Function::getVarIndexBase() { return getParams().size(); }
+Index Function::getVarIndexBase() const { return getParams().size(); }
 
-Type Function::getLocalType(Index index) {
+Type Function::getLocalType(Index index) const {
   auto numParams = getParams().size();
   if (index < numParams) {
     return getParams()[index];