[llvm] 5cef6f3 - [llvm][mustache] Use BumpPtrAllocator to save ASTNodes (#159194)

via llvm-commits llvm-commits at lists.llvm.org
Thu Oct 9 10:46:45 PDT 2025


Author: Paul Kirth
Date: 2025-10-09T10:46:39-07:00
New Revision: 5cef6f38c0c7a59d8ce0854d04613d8606326023

URL: https://github.com/llvm/llvm-project/commit/5cef6f38c0c7a59d8ce0854d04613d8606326023
DIFF: https://github.com/llvm/llvm-project/commit/5cef6f38c0c7a59d8ce0854d04613d8606326023.diff

LOG: [llvm][mustache] Use BumpPtrAllocator to save ASTNodes (#159194)

We make the Mustache ASTNodes usable in the arena by first removing all
of the memory owning data structures, like std::vector, std::unique_ptr,
and SmallVector. We use standard LLVM list types to hold this data
instead, and make use of a UniqueStringSaver to hold the various
templates strings.

Additionally, update clang-doc APIs to use the new interfaces.

Future work can make better use of Twine interfaces to help avoid any
intermediate copies or allocations.

Added: 
    

Modified: 
    clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp
    llvm/benchmarks/Mustache.cpp
    llvm/include/llvm/Support/Mustache.h
    llvm/lib/Support/Mustache.cpp
    llvm/unittests/Support/MustacheTest.cpp
    llvm/utils/llvm-test-mustache-spec/llvm-test-mustache-spec.cpp

Removed: 
    


################################################################################
diff  --git a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp
index b37dc272ea156..b4b9322b0500a 100644
--- a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp
+++ b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp
@@ -46,7 +46,13 @@ class MustacheHTMLGenerator : public Generator {
                            const ClangDocContext &CDCtx) override;
 };
 
-class MustacheTemplateFile : public Template {
+class MustacheTemplateFile {
+  BumpPtrAllocator Allocator;
+  StringSaver Saver;
+  MustacheContext Ctx;
+  Template T;
+  std::unique_ptr<MemoryBuffer> Buffer;
+
 public:
   static Expected<std::unique_ptr<MustacheTemplateFile>>
   createMustacheFile(StringRef FileName) {
@@ -54,10 +60,8 @@ class MustacheTemplateFile : public Template {
         MemoryBuffer::getFile(FileName);
     if (auto EC = BufferOrError.getError())
       return createFileOpenError(FileName, EC);
-
-    std::unique_ptr<MemoryBuffer> Buffer = std::move(BufferOrError.get());
-    StringRef FileContent = Buffer->getBuffer();
-    return std::make_unique<MustacheTemplateFile>(FileContent);
+    return std::make_unique<MustacheTemplateFile>(
+        std::move(BufferOrError.get()));
   }
 
   Error registerPartialFile(StringRef Name, StringRef FileName) {
@@ -68,11 +72,15 @@ class MustacheTemplateFile : public Template {
 
     std::unique_ptr<MemoryBuffer> Buffer = std::move(BufferOrError.get());
     StringRef FileContent = Buffer->getBuffer();
-    registerPartial(Name.str(), FileContent.str());
+    T.registerPartial(Name.str(), FileContent.str());
     return Error::success();
   }
 
-  MustacheTemplateFile(StringRef TemplateStr) : Template(TemplateStr) {}
+  void render(json::Value &V, raw_ostream &OS) { T.render(V, OS); }
+
+  MustacheTemplateFile(std::unique_ptr<MemoryBuffer> &&B)
+      : Saver(Allocator), Ctx(Allocator, Saver), T(B->getBuffer(), Ctx),
+        Buffer(std::move(B)) {}
 };
 
 static std::unique_ptr<MustacheTemplateFile> NamespaceTemplate = nullptr;

diff  --git a/llvm/benchmarks/Mustache.cpp b/llvm/benchmarks/Mustache.cpp
index 6d24f5442e274..996eca41dc5b3 100644
--- a/llvm/benchmarks/Mustache.cpp
+++ b/llvm/benchmarks/Mustache.cpp
@@ -8,7 +8,7 @@
 static const std::string LongHtmlString = [] {
   std::string S;
   S.reserve(500000);
-  for (int i = 0; i < 50000; ++i) {
+  for (int Idx = 0; Idx < 50000; ++Idx) {
     S += "<script>alert('xss');</script>";
   }
   return S;
@@ -153,7 +153,11 @@ static const std::string LargeOutputStringTemplate = "{{long_string}}";
 // syntaxes.
 static void BM_Mustache_StringRendering(benchmark::State &state,
                                         const std::string &TplStr) {
-  llvm::mustache::Template Tpl(TplStr);
+  llvm::BumpPtrAllocator Allocator;
+  llvm::StringSaver Saver(Allocator);
+  llvm::mustache::MustacheContext Ctx(Allocator, Saver);
+
+  llvm::mustache::Template Tpl(TplStr, Ctx);
   llvm::json::Value Data =
       llvm::json::Object({{"content", llvm::json::Value(LongHtmlString)}});
   for (auto _ : state) {
@@ -172,7 +176,11 @@ BENCHMARK_CAPTURE(BM_Mustache_StringRendering, Unescaped_Ampersand,
 // Tests the "hot render" cost of repeatedly traversing a deep and wide
 // JSON object.
 static void BM_Mustache_DeepTraversal(benchmark::State &state) {
-  llvm::mustache::Template Tpl(DeepTraversalTemplate);
+  llvm::BumpPtrAllocator Allocator;
+  llvm::StringSaver Saver(Allocator);
+  llvm::mustache::MustacheContext Ctx(Allocator, Saver);
+
+  llvm::mustache::Template Tpl(DeepTraversalTemplate, Ctx);
   for (auto _ : state) {
     std::string Result;
     llvm::raw_string_ostream OS(Result);
@@ -184,7 +192,12 @@ BENCHMARK(BM_Mustache_DeepTraversal);
 
 // Tests the "hot render" cost of pushing and popping a deep context stack.
 static void BM_Mustache_DeeplyNestedRendering(benchmark::State &state) {
-  llvm::mustache::Template Tpl(DeeplyNestedRenderingTemplate);
+
+  llvm::BumpPtrAllocator Allocator;
+  llvm::StringSaver Saver(Allocator);
+  llvm::mustache::MustacheContext Ctx(Allocator, Saver);
+
+  llvm::mustache::Template Tpl(DeeplyNestedRenderingTemplate, Ctx);
   for (auto _ : state) {
     std::string Result;
     llvm::raw_string_ostream OS(Result);
@@ -197,7 +210,11 @@ BENCHMARK(BM_Mustache_DeeplyNestedRendering);
 // Tests the performance of the loop logic when iterating over a huge number of
 // items.
 static void BM_Mustache_HugeArrayIteration(benchmark::State &state) {
-  llvm::mustache::Template Tpl(HugeArrayIterationTemplate);
+  llvm::BumpPtrAllocator Allocator;
+  llvm::StringSaver Saver(Allocator);
+  llvm::mustache::MustacheContext Ctx(Allocator, Saver);
+
+  llvm::mustache::Template Tpl(HugeArrayIterationTemplate, Ctx);
   for (auto _ : state) {
     std::string Result;
     llvm::raw_string_ostream OS(Result);
@@ -209,8 +226,12 @@ BENCHMARK(BM_Mustache_HugeArrayIteration);
 
 // Tests the performance of the parser on a large, "wide" template.
 static void BM_Mustache_ComplexTemplateParsing(benchmark::State &state) {
+  llvm::BumpPtrAllocator Allocator;
+  llvm::StringSaver Saver(Allocator);
+  llvm::mustache::MustacheContext Ctx(Allocator, Saver);
+
   for (auto _ : state) {
-    llvm::mustache::Template Tpl(ComplexTemplateParsingTemplate);
+    llvm::mustache::Template Tpl(ComplexTemplateParsingTemplate, Ctx);
     benchmark::DoNotOptimize(Tpl);
   }
 }
@@ -218,8 +239,12 @@ BENCHMARK(BM_Mustache_ComplexTemplateParsing);
 
 // Tests the performance of the parser on a small, "deep" template.
 static void BM_Mustache_SmallTemplateParsing(benchmark::State &state) {
+  llvm::BumpPtrAllocator Allocator;
+  llvm::StringSaver Saver(Allocator);
+  llvm::mustache::MustacheContext Ctx(Allocator, Saver);
+
   for (auto _ : state) {
-    llvm::mustache::Template Tpl(SmallTemplateParsingTemplate);
+    llvm::mustache::Template Tpl(SmallTemplateParsingTemplate, Ctx);
     benchmark::DoNotOptimize(Tpl);
   }
 }
@@ -227,7 +252,11 @@ BENCHMARK(BM_Mustache_SmallTemplateParsing);
 
 // Tests the performance of rendering a template that includes a partial.
 static void BM_Mustache_PartialsRendering(benchmark::State &state) {
-  llvm::mustache::Template Tpl(ComplexPartialTemplate);
+  llvm::BumpPtrAllocator Allocator;
+  llvm::StringSaver Saver(Allocator);
+  llvm::mustache::MustacheContext Ctx(Allocator, Saver);
+
+  llvm::mustache::Template Tpl(ComplexPartialTemplate, Ctx);
   Tpl.registerPartial("item_partial", ItemPartialTemplate);
   llvm::json::Value Data = HugeArrayData;
 
@@ -243,7 +272,11 @@ BENCHMARK(BM_Mustache_PartialsRendering);
 // Tests the performance of the underlying buffer management when generating a
 // very large output.
 static void BM_Mustache_LargeOutputString(benchmark::State &state) {
-  llvm::mustache::Template Tpl(LargeOutputStringTemplate);
+  llvm::BumpPtrAllocator Allocator;
+  llvm::StringSaver Saver(Allocator);
+  llvm::mustache::MustacheContext Ctx(Allocator, Saver);
+
+  llvm::mustache::Template Tpl(LargeOutputStringTemplate, Ctx);
   for (auto _ : state) {
     std::string Result;
     llvm::raw_string_ostream OS(Result);

diff  --git a/llvm/include/llvm/Support/Mustache.h b/llvm/include/llvm/Support/Mustache.h
index ee9f40638fd12..83047f2aafff6 100644
--- a/llvm/include/llvm/Support/Mustache.h
+++ b/llvm/include/llvm/Support/Mustache.h
@@ -71,6 +71,8 @@
 
 #include "Error.h"
 #include "llvm/ADT/StringMap.h"
+#include "llvm/ADT/ilist.h"
+#include "llvm/ADT/ilist_node.h"
 #include "llvm/Support/Allocator.h"
 #include "llvm/Support/Compiler.h"
 #include "llvm/Support/JSON.h"
@@ -84,10 +86,15 @@ using Lambda = std::function<llvm::json::Value()>;
 using SectionLambda = std::function<llvm::json::Value(std::string)>;
 
 class ASTNode;
-using AstPtr = std::unique_ptr<ASTNode>;
+using AstPtr = ASTNode *;
 using EscapeMap = DenseMap<char, std::string>;
+using ASTNodeList = iplist<ASTNode>;
 
 struct MustacheContext {
+  MustacheContext(BumpPtrAllocator &Allocator, StringSaver &Saver)
+      : Allocator(Allocator), Saver(Saver) {}
+  BumpPtrAllocator &Allocator;
+  StringSaver &Saver;
   StringMap<AstPtr> Partials;
   StringMap<Lambda> Lambdas;
   StringMap<SectionLambda> SectionLambdas;
@@ -98,7 +105,7 @@ struct MustacheContext {
 // and Lambdas that are registered with it.
 class Template {
 public:
-  LLVM_ABI Template(StringRef TemplateStr);
+  LLVM_ABI Template(StringRef TemplateStr, MustacheContext &Ctx);
 
   Template(const Template &) = delete;
 
@@ -110,7 +117,7 @@ class Template {
   // type.
   LLVM_ABI ~Template();
 
-  LLVM_ABI Template &operator=(Template &&Other) noexcept;
+  Template &operator=(Template &&) = delete;
 
   LLVM_ABI void render(const llvm::json::Value &Data, llvm::raw_ostream &OS);
 
@@ -126,7 +133,7 @@ class Template {
   LLVM_ABI void overrideEscapeCharacters(DenseMap<char, std::string> Escapes);
 
 private:
-  MustacheContext Ctx;
+  MustacheContext &Ctx;
   AstPtr Tree;
 };
 } // namespace llvm::mustache

diff  --git a/llvm/lib/Support/Mustache.cpp b/llvm/lib/Support/Mustache.cpp
index 47860c00be610..6fe1eaf4ce09a 100644
--- a/llvm/lib/Support/Mustache.cpp
+++ b/llvm/lib/Support/Mustache.cpp
@@ -20,7 +20,7 @@ using namespace llvm::mustache;
 
 namespace {
 
-using Accessor = SmallVector<std::string>;
+using Accessor = ArrayRef<StringRef>;
 
 static bool isFalsey(const json::Value &V) {
   return V.getAsNull() || (V.getAsBoolean() && !V.getAsBoolean().value()) ||
@@ -34,23 +34,32 @@ static bool isContextFalsey(const json::Value *V) {
   return isFalsey(*V);
 }
 
-static Accessor splitMustacheString(StringRef Str) {
+static Accessor splitMustacheString(StringRef Str, MustacheContext &Ctx) {
   // We split the mustache string into an accessor.
   // For example:
   //    "a.b.c" would be split into {"a", "b", "c"}
   // We make an exception for a single dot which
   // refers to the current context.
-  Accessor Tokens;
+  SmallVector<StringRef> Tokens;
   if (Str == ".") {
-    Tokens.emplace_back(Str);
-    return Tokens;
-  }
-  while (!Str.empty()) {
-    StringRef Part;
-    std::tie(Part, Str) = Str.split(".");
-    Tokens.emplace_back(Part.trim());
+    // "." is a special accessor that refers to the current context.
+    // It's a literal, so it doesn't need to be saved.
+    Tokens.push_back(".");
+  } else {
+    while (!Str.empty()) {
+      StringRef Part;
+      std::tie(Part, Str) = Str.split('.');
+      // Each part of the accessor needs to be saved to the arena
+      // to ensure it has a stable address.
+      Tokens.push_back(Ctx.Saver.save(Part.trim()));
+    }
   }
-  return Tokens;
+  // Now, allocate memory for the array of StringRefs in the arena.
+  StringRef *ArenaTokens = Ctx.Allocator.Allocate<StringRef>(Tokens.size());
+  // Copy the StringRefs from the stack vector to the arena.
+  std::copy(Tokens.begin(), Tokens.end(), ArenaTokens);
+  // Return an ArrayRef pointing to the stable arena memory.
+  return ArrayRef<StringRef>(ArenaTokens, Tokens.size());
 }
 } // namespace
 
@@ -97,23 +106,23 @@ class Token {
     SetDelimiter,
   };
 
-  Token(std::string Str)
-      : TokenType(Type::Text), RawBody(std::move(Str)), TokenBody(RawBody),
+  Token(StringRef Str)
+      : TokenType(Type::Text), RawBody(Str), TokenBody(RawBody),
         AccessorValue({}), Indentation(0) {};
 
-  Token(std::string RawBody, std::string TokenBody, char Identifier)
-      : RawBody(std::move(RawBody)), TokenBody(std::move(TokenBody)),
-        Indentation(0) {
+  Token(StringRef RawBody, StringRef TokenBody, char Identifier,
+        MustacheContext &Ctx)
+      : RawBody(RawBody), TokenBody(TokenBody), Indentation(0) {
     TokenType = getTokenType(Identifier);
     if (TokenType == Type::Comment)
       return;
     StringRef AccessorStr(this->TokenBody);
     if (TokenType != Type::Variable)
       AccessorStr = AccessorStr.substr(1);
-    AccessorValue = splitMustacheString(StringRef(AccessorStr).trim());
+    AccessorValue = splitMustacheString(StringRef(AccessorStr).trim(), Ctx);
   }
 
-  Accessor getAccessor() const { return AccessorValue; }
+  ArrayRef<StringRef> getAccessor() const { return AccessorValue; }
 
   Type getType() const { return TokenType; }
 
@@ -144,16 +153,16 @@ class Token {
 
   Type TokenType;
   // RawBody is the original string that was tokenized.
-  std::string RawBody;
+  StringRef RawBody;
   // TokenBody is the original string with the identifier removed.
-  std::string TokenBody;
-  Accessor AccessorValue;
+  StringRef TokenBody;
+  ArrayRef<StringRef> AccessorValue;
   size_t Indentation;
 };
 
 using EscapeMap = DenseMap<char, std::string>;
 
-class ASTNode {
+class ASTNode : public ilist_node<ASTNode> {
 public:
   enum Type {
     Root,
@@ -168,18 +177,19 @@ class ASTNode {
   ASTNode(MustacheContext &Ctx)
       : Ctx(Ctx), Ty(Type::Root), Parent(nullptr), ParentContext(nullptr) {}
 
-  ASTNode(MustacheContext &Ctx, std::string Body, ASTNode *Parent)
-      : Ctx(Ctx), Ty(Type::Text), Body(std::move(Body)), Parent(Parent),
+  ASTNode(MustacheContext &Ctx, StringRef Body, ASTNode *Parent)
+      : Ctx(Ctx), Ty(Type::Text), Body(Body), Parent(Parent),
         ParentContext(nullptr) {}
 
   // Constructor for Section/InvertSection/Variable/UnescapeVariable Nodes
-  ASTNode(MustacheContext &Ctx, Type Ty, Accessor Accessor, ASTNode *Parent)
-      : Ctx(Ctx), Ty(Ty), Parent(Parent), AccessorValue(std::move(Accessor)),
+  ASTNode(MustacheContext &Ctx, Type Ty, ArrayRef<StringRef> Accessor,
+          ASTNode *Parent)
+      : Ctx(Ctx), Ty(Ty), Parent(Parent), AccessorValue(Accessor),
         ParentContext(nullptr) {}
 
-  void addChild(AstPtr Child) { Children.emplace_back(std::move(Child)); };
+  void addChild(AstPtr Child) { Children.push_back(Child); };
 
-  void setRawBody(std::string NewBody) { RawBody = std::move(NewBody); };
+  void setRawBody(StringRef NewBody) { RawBody = NewBody; };
 
   void setIndentation(size_t NewIndentation) { Indentation = NewIndentation; };
 
@@ -212,28 +222,27 @@ class ASTNode {
   MustacheContext &Ctx;
   Type Ty;
   size_t Indentation = 0;
-  std::string RawBody;
-  std::string Body;
+  StringRef RawBody;
+  StringRef Body;
   ASTNode *Parent;
-  // TODO: switch implementation to SmallVector<T>
-  std::vector<AstPtr> Children;
-  const Accessor AccessorValue;
+  ASTNodeList Children;
+  const ArrayRef<StringRef> AccessorValue;
   const llvm::json::Value *ParentContext;
 };
 
 // A wrapper for arena allocator for ASTNodes
 static AstPtr createRootNode(MustacheContext &Ctx) {
-  return std::make_unique<ASTNode>(Ctx);
+  return new (Ctx.Allocator.Allocate<ASTNode>()) ASTNode(Ctx);
 }
 
-static AstPtr createNode(MustacheContext &Ctx, ASTNode::Type T, Accessor A,
-                         ASTNode *Parent) {
-  return std::make_unique<ASTNode>(Ctx, T, std::move(A), Parent);
+static AstPtr createNode(MustacheContext &Ctx, ASTNode::Type T,
+                         ArrayRef<StringRef> A, ASTNode *Parent) {
+  return new (Ctx.Allocator.Allocate<ASTNode>()) ASTNode(Ctx, T, A, Parent);
 }
 
-static AstPtr createTextNode(MustacheContext &Ctx, std::string Body,
+static AstPtr createTextNode(MustacheContext &Ctx, StringRef Body,
                              ASTNode *Parent) {
-  return std::make_unique<ASTNode>(Ctx, std::move(Body), Parent);
+  return new (Ctx.Allocator.Allocate<ASTNode>()) ASTNode(Ctx, Body, Parent);
 }
 
 // Function to check if there is meaningful text behind.
@@ -295,9 +304,9 @@ static void stripTokenAhead(SmallVectorImpl<Token> &Tokens, size_t Idx) {
   StringRef NextTokenBody = NextToken.TokenBody;
   // Cut off the leading newline which could be \n or \r\n.
   if (NextTokenBody.starts_with("\r\n"))
-    NextToken.TokenBody = NextTokenBody.substr(2).str();
+    NextToken.TokenBody = NextTokenBody.substr(2);
   else if (NextTokenBody.starts_with("\n"))
-    NextToken.TokenBody = NextTokenBody.substr(1).str();
+    NextToken.TokenBody = NextTokenBody.substr(1);
 }
 
 // Adjust previous token body if there no text behind.
@@ -312,7 +321,7 @@ void stripTokenBefore(SmallVectorImpl<Token> &Tokens, size_t Idx,
   StringRef PrevTokenBody = PrevToken.TokenBody;
   StringRef Unindented = PrevTokenBody.rtrim(" \r\t\v");
   size_t Indentation = PrevTokenBody.size() - Unindented.size();
-  PrevToken.TokenBody = Unindented.str();
+  PrevToken.TokenBody = Unindented;
   CurrentToken.setIndentation(Indentation);
 }
 
@@ -402,21 +411,20 @@ static Tag findNextTag(StringRef Template, size_t StartPos, StringRef Open,
 }
 
 static std::optional<std::pair<StringRef, StringRef>>
-processTag(const Tag &T, SmallVectorImpl<Token> &Tokens) {
+processTag(const Tag &T, SmallVectorImpl<Token> &Tokens, MustacheContext &Ctx) {
   LLVM_DEBUG(dbgs() << "[Tag] " << T.FullMatch << ", Content: " << T.Content
                     << ", Kind: " << tagKindToString(T.TagKind) << "\n");
   if (T.TagKind == Tag::Kind::Triple) {
-    Tokens.emplace_back(T.FullMatch.str(), "&" + T.Content.str(), '&');
+    Tokens.emplace_back(T.FullMatch, Ctx.Saver.save("&" + T.Content), '&', Ctx);
     return std::nullopt;
   }
   StringRef Interpolated = T.Content;
-  std::string RawBody = T.FullMatch.str();
   if (!Interpolated.trim().starts_with("=")) {
     char Front = Interpolated.empty() ? ' ' : Interpolated.trim().front();
-    Tokens.emplace_back(RawBody, Interpolated.str(), Front);
+    Tokens.emplace_back(T.FullMatch, Interpolated, Front, Ctx);
     return std::nullopt;
   }
-  Tokens.emplace_back(RawBody, Interpolated.str(), '=');
+  Tokens.emplace_back(T.FullMatch, Interpolated, '=', Ctx);
   StringRef DelimSpec = Interpolated.trim();
   DelimSpec = DelimSpec.drop_front(1);
   DelimSpec = DelimSpec.take_until([](char C) { return C == '='; });
@@ -432,7 +440,7 @@ processTag(const Tag &T, SmallVectorImpl<Token> &Tokens) {
 // The mustache spec allows {{{ }}} to unescape variables,
 // but we don't support that here. An unescape variable
 // is represented only by {{& variable}}.
-static SmallVector<Token> tokenize(StringRef Template) {
+static SmallVector<Token> tokenize(StringRef Template, MustacheContext &Ctx) {
   LLVM_DEBUG(dbgs() << "[Tokenize Template] \"" << Template << "\"\n");
   SmallVector<Token> Tokens;
   SmallString<8> Open("{{");
@@ -446,19 +454,17 @@ static SmallVector<Token> tokenize(StringRef Template) {
 
     if (T.TagKind == Tag::Kind::None) {
       // No more tags, the rest is text.
-      Tokens.emplace_back(Template.substr(Start).str());
-      LLVM_DEBUG(dbgs() << "  No more tags. Created final Text token: \""
-                        << Template.substr(Start) << "\"\n");
+      Tokens.emplace_back(Template.substr(Start));
       break;
     }
 
     // Add the text before the tag.
     if (T.StartPosition > Start) {
       StringRef Text = Template.substr(Start, T.StartPosition - Start);
-      Tokens.emplace_back(Text.str());
+      Tokens.emplace_back(Text);
     }
 
-    if (auto NewDelims = processTag(T, Tokens)) {
+    if (auto NewDelims = processTag(T, Tokens, Ctx)) {
       std::tie(Open, Close) = *NewDelims;
     }
 
@@ -614,20 +620,20 @@ void Parser::parseSection(ASTNode *Parent, ASTNode::Type Ty,
                           const Accessor &A) {
   AstPtr CurrentNode = createNode(Ctx, Ty, A, Parent);
   size_t Start = CurrentPtr;
-  parseMustache(CurrentNode.get());
+  parseMustache(CurrentNode);
   const size_t End = CurrentPtr - 1;
-  std::string RawBody;
+  SmallString<128> RawBody;
   for (std::size_t I = Start; I < End; I++)
     RawBody += Tokens[I].RawBody;
-  CurrentNode->setRawBody(std::move(RawBody));
-  Parent->addChild(std::move(CurrentNode));
+  CurrentNode->setRawBody(Ctx.Saver.save(StringRef(RawBody)));
+  Parent->addChild(CurrentNode);
 }
 
 AstPtr Parser::parse() {
-  Tokens = tokenize(TemplateStr);
+  Tokens = tokenize(TemplateStr, Ctx);
   CurrentPtr = 0;
   AstPtr RootNode = createRootNode(Ctx);
-  parseMustache(RootNode.get());
+  parseMustache(RootNode);
   return RootNode;
 }
 
@@ -636,31 +642,29 @@ void Parser::parseMustache(ASTNode *Parent) {
   while (CurrentPtr < Tokens.size()) {
     Token CurrentToken = Tokens[CurrentPtr];
     CurrentPtr++;
-    Accessor A = CurrentToken.getAccessor();
+    ArrayRef<StringRef> A = CurrentToken.getAccessor();
     AstPtr CurrentNode;
 
     switch (CurrentToken.getType()) {
     case Token::Type::Text: {
-      CurrentNode =
-          createTextNode(Ctx, std::move(CurrentToken.TokenBody), Parent);
-      Parent->addChild(std::move(CurrentNode));
+      CurrentNode = createTextNode(Ctx, CurrentToken.TokenBody, Parent);
+      Parent->addChild(CurrentNode);
       break;
     }
     case Token::Type::Variable: {
-      CurrentNode = createNode(Ctx, ASTNode::Variable, std::move(A), Parent);
-      Parent->addChild(std::move(CurrentNode));
+      CurrentNode = createNode(Ctx, ASTNode::Variable, A, Parent);
+      Parent->addChild(CurrentNode);
       break;
     }
     case Token::Type::UnescapeVariable: {
-      CurrentNode =
-          createNode(Ctx, ASTNode::UnescapeVariable, std::move(A), Parent);
-      Parent->addChild(std::move(CurrentNode));
+      CurrentNode = createNode(Ctx, ASTNode::UnescapeVariable, A, Parent);
+      Parent->addChild(CurrentNode);
       break;
     }
     case Token::Type::Partial: {
-      CurrentNode = createNode(Ctx, ASTNode::Partial, std::move(A), Parent);
+      CurrentNode = createNode(Ctx, ASTNode::Partial, A, Parent);
       CurrentNode->setIndentation(CurrentToken.getIndentation());
-      Parent->addChild(std::move(CurrentNode));
+      Parent->addChild(CurrentNode);
       break;
     }
     case Token::Type::SectionOpen: {
@@ -727,7 +731,7 @@ void ASTNode::renderPartial(const json::Value &CurrentCtx,
                     << ", Indentation:" << Indentation << "\n");
   auto Partial = Ctx.Partials.find(AccessorValue[0]);
   if (Partial != Ctx.Partials.end())
-    renderPartial(CurrentCtx, OS, Partial->getValue().get());
+    renderPartial(CurrentCtx, OS, Partial->getValue());
 }
 
 void ASTNode::renderVariable(const json::Value &CurrentCtx,
@@ -858,8 +862,8 @@ const json::Value *ASTNode::findContext() {
 
 void ASTNode::renderChild(const json::Value &Contexts,
                           MustacheOutputStream &OS) {
-  for (AstPtr &Child : Children)
-    Child->render(Contexts, OS);
+  for (ASTNode &Child : Children)
+    Child.render(Contexts, OS);
 }
 
 void ASTNode::renderPartial(const json::Value &Contexts,
@@ -869,7 +873,7 @@ void ASTNode::renderPartial(const json::Value &Contexts,
   Partial->render(Contexts, IS);
 }
 
-void ASTNode::renderLambdas(const json::Value &Contexts,
+void ASTNode::renderLambdas(const llvm::json::Value &Contexts,
                             MustacheOutputStream &OS, Lambda &L) {
   json::Value LambdaResult = L();
   std::string LambdaStr;
@@ -886,9 +890,9 @@ void ASTNode::renderLambdas(const json::Value &Contexts,
   LambdaNode->render(Contexts, OS);
 }
 
-void ASTNode::renderSectionLambdas(const json::Value &Contexts,
+void ASTNode::renderSectionLambdas(const llvm::json::Value &Contexts,
                                    MustacheOutputStream &OS, SectionLambda &L) {
-  json::Value Return = L(RawBody);
+  json::Value Return = L(RawBody.str());
   if (isFalsey(Return))
     return;
   std::string LambdaStr;
@@ -899,15 +903,16 @@ void ASTNode::renderSectionLambdas(const json::Value &Contexts,
   LambdaNode->render(Contexts, OS);
 }
 
-void Template::render(const json::Value &Data, llvm::raw_ostream &OS) {
+void Template::render(const llvm::json::Value &Data, llvm::raw_ostream &OS) {
   RawMustacheOutputStream MOS(OS);
   Tree->render(Data, MOS);
 }
 
 void Template::registerPartial(std::string Name, std::string Partial) {
-  Parser P(Partial, Ctx);
+  StringRef SavedPartial = Ctx.Saver.save(Partial);
+  Parser P(SavedPartial, Ctx);
   AstPtr PartialTree = P.parse();
-  Ctx.Partials.insert(std::make_pair(Name, std::move(PartialTree)));
+  Ctx.Partials.insert(std::make_pair(Name, PartialTree));
 }
 
 void Template::registerLambda(std::string Name, Lambda L) {
@@ -922,7 +927,7 @@ void Template::overrideEscapeCharacters(EscapeMap E) {
   Ctx.Escapes = std::move(E);
 }
 
-Template::Template(StringRef TemplateStr) {
+Template::Template(StringRef TemplateStr, MustacheContext &Ctx) : Ctx(Ctx) {
   Parser P(TemplateStr, Ctx);
   Tree = P.parse();
   // The default behavior is to escape html entities.
@@ -935,18 +940,12 @@ Template::Template(StringRef TemplateStr) {
 }
 
 Template::Template(Template &&Other) noexcept
-    : Ctx(std::move(Other.Ctx)), Tree(std::move(Other.Tree)) {}
+    : Ctx(Other.Ctx), Tree(Other.Tree) {
+  Other.Tree = nullptr;
+}
 
 Template::~Template() = default;
 
-Template &Template::operator=(Template &&Other) noexcept {
-  if (this != &Other) {
-    Ctx = std::move(Other.Ctx);
-    Tree = std::move(Other.Tree);
-    Other.Tree = nullptr;
-  }
-  return *this;
-}
 } // namespace llvm::mustache
 
 #undef DEBUG_TYPE

diff  --git a/llvm/unittests/Support/MustacheTest.cpp b/llvm/unittests/Support/MustacheTest.cpp
index e2c4422f32fd1..3cad4a4562c2b 100644
--- a/llvm/unittests/Support/MustacheTest.cpp
+++ b/llvm/unittests/Support/MustacheTest.cpp
@@ -22,7 +22,10 @@ using namespace llvm::json;
 TEST(MustacheInterpolation, NoInterpolation) {
   // Mustache-free templates should render as-is.
   Value D = {};
-  Template T("Hello from {Mustache}!\n");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("Hello from {Mustache}!\n", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -32,7 +35,10 @@ TEST(MustacheInterpolation, NoInterpolation) {
 TEST(MustacheInterpolation, BasicInterpolation) {
   // Unadorned tags should interpolate content into the template.
   Value D = Object{{"subject", "World"}};
-  Template T("Hello, {{subject}}!");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("Hello, {{subject}}!", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -42,7 +48,10 @@ TEST(MustacheInterpolation, BasicInterpolation) {
 TEST(MustacheInterpolation, NoReinterpolation) {
   // Interpolated tag output should not be re-interpolated.
   Value D = Object{{"template", "{{planet}}"}, {"planet", "Earth"}};
-  Template T("{{template}}: {{planet}}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{template}}: {{planet}}", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -54,7 +63,10 @@ TEST(MustacheInterpolation, HTMLEscaping) {
   Value D = Object{
       {"forbidden", "& \" < >"},
   };
-  Template T("These characters should be HTML escaped: {{forbidden}}\n");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("These characters should be HTML escaped: {{forbidden}}\n", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -67,7 +79,11 @@ TEST(MustacheInterpolation, Ampersand) {
   Value D = Object{
       {"forbidden", "& \" < >"},
   };
-  Template T("These characters should not be HTML escaped: {{&forbidden}}\n");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("These characters should not be HTML escaped: {{&forbidden}}\n",
+             Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -77,7 +93,10 @@ TEST(MustacheInterpolation, Ampersand) {
 TEST(MustacheInterpolation, BasicIntegerInterpolation) {
   // Integers should interpolate seamlessly.
   Value D = Object{{"mph", 85}};
-  Template T("{{mph}} miles an hour!");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{mph}} miles an hour!", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -87,7 +106,10 @@ TEST(MustacheInterpolation, BasicIntegerInterpolation) {
 TEST(MustacheInterpolation, AmpersandIntegerInterpolation) {
   // Integers should interpolate seamlessly.
   Value D = Object{{"mph", 85}};
-  Template T("{{&mph}} miles an hour!");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{&mph}} miles an hour!", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -97,7 +119,10 @@ TEST(MustacheInterpolation, AmpersandIntegerInterpolation) {
 TEST(MustacheInterpolation, BasicDecimalInterpolation) {
   // Decimals should interpolate seamlessly with proper significance.
   Value D = Object{{"power", 1.21}};
-  Template T("{{power}} jiggawatts!");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{power}} jiggawatts!", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -107,7 +132,10 @@ TEST(MustacheInterpolation, BasicDecimalInterpolation) {
 TEST(MustacheInterpolation, BasicNullInterpolation) {
   // Nulls should interpolate as the empty string.
   Value D = Object{{"cannot", nullptr}};
-  Template T("I ({{cannot}}) be seen!");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("I ({{cannot}}) be seen!", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -117,7 +145,10 @@ TEST(MustacheInterpolation, BasicNullInterpolation) {
 TEST(MustacheInterpolation, AmpersandNullInterpolation) {
   // Nulls should interpolate as the empty string.
   Value D = Object{{"cannot", nullptr}};
-  Template T("I ({{&cannot}}) be seen!");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("I ({{&cannot}}) be seen!", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -127,7 +158,10 @@ TEST(MustacheInterpolation, AmpersandNullInterpolation) {
 TEST(MustacheInterpolation, BasicContextMissInterpolation) {
   // Failed context lookups should default to empty strings.
   Value D = Object{};
-  Template T("I ({{cannot}}) be seen!");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("I ({{cannot}}) be seen!", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -137,7 +171,10 @@ TEST(MustacheInterpolation, BasicContextMissInterpolation) {
 TEST(MustacheInterpolation, DottedNamesBasicInterpolation) {
   // Dotted names should be considered a form of shorthand for sections.
   Value D = Object{{"person", Object{{"name", "Joe"}}}};
-  Template T("{{person.name}} == {{#person}}{{name}}{{/person}}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{person.name}} == {{#person}}{{name}}{{/person}}", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -147,7 +184,10 @@ TEST(MustacheInterpolation, DottedNamesBasicInterpolation) {
 TEST(MustacheInterpolation, DottedNamesAmpersandInterpolation) {
   // Dotted names should be considered a form of shorthand for sections.
   Value D = Object{{"person", Object{{"name", "Joe"}}}};
-  Template T("{{&person.name}} == {{#person}}{{&name}}{{/person}}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{&person.name}} == {{#person}}{{&name}}{{/person}}", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -162,7 +202,10 @@ TEST(MustacheInterpolation, DottedNamesArbitraryDepth) {
                Object{{"c",
                        Object{{"d",
                                Object{{"e", Object{{"name", "Phil"}}}}}}}}}}}};
-  Template T("{{a.b.c.d.e.name}}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{a.b.c.d.e.name}}", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -172,7 +215,10 @@ TEST(MustacheInterpolation, DottedNamesArbitraryDepth) {
 TEST(MustacheInterpolation, DottedNamesBrokenChains) {
   // Any falsey value prior to the last part of the name should yield ''.
   Value D = Object{{"a", Object{}}};
-  Template T("{{a.b.c}} == ");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{a.b.c}} == ", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -183,7 +229,10 @@ TEST(MustacheInterpolation, DottedNamesBrokenChainResolution) {
   // Each part of a dotted name should resolve only against its parent.
   Value D =
       Object{{"a", Object{{"b", Object{}}}}, {"c", Object{{"name", "Jim"}}}};
-  Template T("{{a.b.c.name}} == ");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{a.b.c.name}} == ", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -200,7 +249,10 @@ TEST(MustacheInterpolation, DottedNamesInitialResolution) {
                     Object{{"d", Object{{"e", Object{{"name", "Phil"}}}}}}}}}}},
       {"b",
        Object{{"c", Object{{"d", Object{{"e", Object{{"name", "Wrong"}}}}}}}}}};
-  Template T("{{#a}}{{b.c.d.e.name}}{{/a}}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{#a}}{{b.c.d.e.name}}{{/a}}", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -211,7 +263,10 @@ TEST(MustacheInterpolation, DottedNamesContextPrecedence) {
   // Dotted names should be resolved against former resolutions.
   Value D =
       Object{{"a", Object{{"b", Object{}}}}, {"b", Object{{"c", "ERROR"}}}};
-  Template T("{{#a}}{{b.c}}{{/a}}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{#a}}{{b.c}}{{/a}}", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -221,7 +276,10 @@ TEST(MustacheInterpolation, DottedNamesContextPrecedence) {
 TEST(MustacheInterpolation, DottedNamesAreNotSingleKeys) {
   // Dotted names shall not be parsed as single, atomic keys
   Value D = Object{{"a.b", "c"}};
-  Template T("{{a.b}}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{a.b}}", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -231,7 +289,10 @@ TEST(MustacheInterpolation, DottedNamesAreNotSingleKeys) {
 TEST(MustacheInterpolation, DottedNamesNoMasking) {
   // Dotted Names in a given context are unavailable due to dot splitting
   Value D = Object{{"a.b", "c"}, {"a", Object{{"b", "d"}}}};
-  Template T("{{a.b}}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{a.b}}", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -241,7 +302,10 @@ TEST(MustacheInterpolation, DottedNamesNoMasking) {
 TEST(MustacheInterpolation, ImplicitIteratorsBasicInterpolation) {
   // Unadorned tags should interpolate content into the template.
   Value D = "world";
-  Template T("Hello, {{.}}!\n");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("Hello, {{.}}!\n", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -251,7 +315,10 @@ TEST(MustacheInterpolation, ImplicitIteratorsBasicInterpolation) {
 TEST(MustacheInterpolation, ImplicitIteratorsAmersand) {
   // Basic interpolation should be HTML escaped.
   Value D = "& \" < >";
-  Template T("These characters should not be HTML escaped: {{&.}}\n");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("These characters should not be HTML escaped: {{&.}}\n", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -261,7 +328,10 @@ TEST(MustacheInterpolation, ImplicitIteratorsAmersand) {
 TEST(MustacheInterpolation, ImplicitIteratorsInteger) {
   // Integers should interpolate seamlessly.
   Value D = 85;
-  Template T("{{.}} miles an hour!\n");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{.}} miles an hour!\n", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -271,7 +341,10 @@ TEST(MustacheInterpolation, ImplicitIteratorsInteger) {
 TEST(MustacheInterpolation, InterpolationSurroundingWhitespace) {
   // Interpolation should not alter surrounding whitespace.
   Value D = Object{{"string", "---"}};
-  Template T("| {{string}} |");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("| {{string}} |", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -281,7 +354,10 @@ TEST(MustacheInterpolation, InterpolationSurroundingWhitespace) {
 TEST(MustacheInterpolation, AmersandSurroundingWhitespace) {
   // Interpolation should not alter surrounding whitespace.
   Value D = Object{{"string", "---"}};
-  Template T("| {{&string}} |");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("| {{&string}} |", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -291,7 +367,10 @@ TEST(MustacheInterpolation, AmersandSurroundingWhitespace) {
 TEST(MustacheInterpolation, StandaloneInterpolationWithWhitespace) {
   // Standalone interpolation should not alter surrounding whitespace.
   Value D = Object{{"string", "---"}};
-  Template T("  {{string}}\n");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("  {{string}}\n", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -301,7 +380,10 @@ TEST(MustacheInterpolation, StandaloneInterpolationWithWhitespace) {
 TEST(MustacheInterpolation, StandaloneAmpersandWithWhitespace) {
   // Standalone interpolation should not alter surrounding whitespace.
   Value D = Object{{"string", "---"}};
-  Template T("  {{&string}}\n");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("  {{&string}}\n", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -311,7 +393,10 @@ TEST(MustacheInterpolation, StandaloneAmpersandWithWhitespace) {
 TEST(MustacheInterpolation, InterpolationWithPadding) {
   // Superfluous in-tag whitespace should be ignored.
   Value D = Object{{"string", "---"}};
-  Template T("|{{ string }}|");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("|{{ string }}|", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -321,7 +406,10 @@ TEST(MustacheInterpolation, InterpolationWithPadding) {
 TEST(MustacheInterpolation, AmpersandWithPadding) {
   // Superfluous in-tag whitespace should be ignored.
   Value D = Object{{"string", "---"}};
-  Template T("|{{& string }}|");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("|{{& string }}|", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -331,7 +419,10 @@ TEST(MustacheInterpolation, AmpersandWithPadding) {
 TEST(MustacheInterpolation, InterpolationWithPaddingAndNewlines) {
   // Superfluous in-tag whitespace should be ignored.
   Value D = Object{{"string", "---"}};
-  Template T("|{{ string \n\n\n }}|");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("|{{ string \n\n\n }}|", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -340,7 +431,10 @@ TEST(MustacheInterpolation, InterpolationWithPaddingAndNewlines) {
 
 TEST(MustacheSections, Truthy) {
   Value D = Object{{"boolean", true}};
-  Template T("{{#boolean}}This should be rendered.{{/boolean}}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{#boolean}}This should be rendered.{{/boolean}}", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -349,7 +443,10 @@ TEST(MustacheSections, Truthy) {
 
 TEST(MustacheSections, Falsey) {
   Value D = Object{{"boolean", false}};
-  Template T("{{#boolean}}This should not be rendered.{{/boolean}}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{#boolean}}This should not be rendered.{{/boolean}}", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -359,7 +456,10 @@ TEST(MustacheSections, Falsey) {
 TEST(MustacheInterpolation, IsFalseyNull) {
   // Mustache-free templates should render as-is.
   Value D = Object{{"boolean", nullptr}};
-  Template T("Hello, {{#boolean}}World{{/boolean}}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("Hello, {{#boolean}}World{{/boolean}}", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -369,7 +469,10 @@ TEST(MustacheInterpolation, IsFalseyNull) {
 TEST(MustacheInterpolation, IsFalseyArray) {
   // Mustache-free templates should render as-is.
   Value D = Object{{"boolean", Array()}};
-  Template T("Hello, {{#boolean}}World{{/boolean}}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("Hello, {{#boolean}}World{{/boolean}}", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -379,7 +482,10 @@ TEST(MustacheInterpolation, IsFalseyArray) {
 TEST(MustacheInterpolation, IsFalseyObject) {
   // Mustache-free templates should render as-is.
   Value D = Object{{"boolean", Object{}}};
-  Template T("Hello, {{#boolean}}World{{/boolean}}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("Hello, {{#boolean}}World{{/boolean}}", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -389,7 +495,10 @@ TEST(MustacheInterpolation, IsFalseyObject) {
 TEST(MustacheInterpolation, DoubleRendering) {
   // Mustache-free templates should render as-is.
   Value D1 = Object{{"subject", "World"}};
-  Template T("Hello, {{subject}}!");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("Hello, {{subject}}!", Ctx);
   std::string Out1;
   raw_string_ostream OS1(Out1);
   T.render(D1, OS1);
@@ -403,7 +512,10 @@ TEST(MustacheInterpolation, DoubleRendering) {
 
 TEST(MustacheSections, NullIsFalsey) {
   Value D = Object{{"null", nullptr}};
-  Template T("{{#null}}This should not be rendered.{{/null}}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{#null}}This should not be rendered.{{/null}}", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -412,7 +524,10 @@ TEST(MustacheSections, NullIsFalsey) {
 
 TEST(MustacheSections, Context) {
   Value D = Object{{"context", Object{{"name", "Joe"}}}};
-  Template T("{{#context}}Hi {{name}}.{{/context}}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{#context}}Hi {{name}}.{{/context}}", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -424,7 +539,10 @@ TEST(MustacheSections, ParentContexts) {
                    {"b", "wrong"},
                    {"sec", Object{{"b", "bar"}}},
                    {"c", Object{{"d", "baz"}}}};
-  Template T("{{#sec}}{{a}}, {{b}}, {{c.d}}{{/sec}}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{#sec}}{{a}}, {{b}}, {{c.d}}{{/sec}}", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -433,7 +551,10 @@ TEST(MustacheSections, ParentContexts) {
 
 TEST(MustacheSections, VariableTest) {
   Value D = Object{{"foo", "bar"}};
-  Template T("{{#foo}}{{.}} is {{foo}}{{/foo}}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{#foo}}{{.}} is {{foo}}{{/foo}}", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -449,6 +570,9 @@ TEST(MustacheSections, ListContexts) {
             Array{Object{{"mname", "1"},
                          {"bottoms", Array{Object{{"bname", "x"}},
                                            Object{{"bname", "y"}}}}}}}}}}};
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
   Template T("{{#tops}}"
              "{{#middles}}"
              "{{tname.lower}}{{mname}}."
@@ -456,7 +580,8 @@ TEST(MustacheSections, ListContexts) {
              "{{tname.upper}}{{mname}}{{bname}}."
              "{{/bottoms}}"
              "{{/middles}}"
-             "{{/tops}}");
+             "{{/tops}}",
+             Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -468,6 +593,9 @@ TEST(MustacheSections, DeeplyNestedContexts) {
       {"a", Object{{"one", 1}}},
       {"b", Object{{"two", 2}}},
       {"c", Object{{"three", 3}, {"d", Object{{"four", 4}, {"five", 5}}}}}};
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
   Template T(
       "{{#a}}\n{{one}}\n{{#b}}\n{{one}}{{two}}{{one}}\n{{#c}}\n{{one}}{{two}}{{"
       "three}}{{two}}{{one}}\n{{#d}}\n{{one}}{{two}}{{three}}{{four}}{{three}}{"
@@ -477,7 +605,8 @@ TEST(MustacheSections, DeeplyNestedContexts) {
       "four}}{{three}}{{two}}{{one}}\n{{/"
       "five}}\n{{one}}{{two}}{{three}}{{four}}{{three}}{{two}}{{one}}\n{{/"
       "d}}\n{{one}}{{two}}{{three}}{{two}}{{one}}\n{{/"
-      "c}}\n{{one}}{{two}}{{one}}\n{{/b}}\n{{one}}\n{{/a}}\n");
+      "c}}\n{{one}}{{two}}{{one}}\n{{/b}}\n{{one}}\n{{/a}}\n",
+      Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -489,7 +618,10 @@ TEST(MustacheSections, DeeplyNestedContexts) {
 TEST(MustacheSections, List) {
   Value D = Object{{"list", Array{Object{{"item", 1}}, Object{{"item", 2}},
                                   Object{{"item", 3}}}}};
-  Template T("{{#list}}{{item}}{{/list}}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{#list}}{{item}}{{/list}}", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -498,7 +630,10 @@ TEST(MustacheSections, List) {
 
 TEST(MustacheSections, EmptyList) {
   Value D = Object{{"list", Array{}}};
-  Template T("{{#list}}Yay lists!{{/list}}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{#list}}Yay lists!{{/list}}", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -507,8 +642,12 @@ TEST(MustacheSections, EmptyList) {
 
 TEST(MustacheSections, Doubled) {
   Value D = Object{{"bool", true}, {"two", "second"}};
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
   Template T("{{#bool}}\n* first\n{{/bool}}\n* "
-             "{{two}}\n{{#bool}}\n* third\n{{/bool}}\n");
+             "{{two}}\n{{#bool}}\n* third\n{{/bool}}\n",
+             Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -517,7 +656,10 @@ TEST(MustacheSections, Doubled) {
 
 TEST(MustacheSections, NestedTruthy) {
   Value D = Object{{"bool", true}};
-  Template T("| A {{#bool}}B {{#bool}}C{{/bool}} D{{/bool}} E |");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("| A {{#bool}}B {{#bool}}C{{/bool}} D{{/bool}} E |", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -526,7 +668,10 @@ TEST(MustacheSections, NestedTruthy) {
 
 TEST(MustacheSections, NestedFalsey) {
   Value D = Object{{"bool", false}};
-  Template T("| A {{#bool}}B {{#bool}}C{{/bool}} D{{/bool}} E |");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("| A {{#bool}}B {{#bool}}C{{/bool}} D{{/bool}} E |", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -535,7 +680,10 @@ TEST(MustacheSections, NestedFalsey) {
 
 TEST(MustacheSections, ContextMisses) {
   Value D = Object{};
-  Template T("[{{#missing}}Found key 'missing'!{{/missing}}]");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("[{{#missing}}Found key 'missing'!{{/missing}}]", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -544,7 +692,10 @@ TEST(MustacheSections, ContextMisses) {
 
 TEST(MustacheSections, ImplicitIteratorString) {
   Value D = Object{{"list", Array{"a", "b", "c", "d", "e"}}};
-  Template T("{{#list}}({{.}}){{/list}}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{#list}}({{.}}){{/list}}", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -553,7 +704,10 @@ TEST(MustacheSections, ImplicitIteratorString) {
 
 TEST(MustacheSections, ImplicitIteratorInteger) {
   Value D = Object{{"list", Array{1, 2, 3, 4, 5}}};
-  Template T("{{#list}}({{.}}){{/list}}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{#list}}({{.}}){{/list}}", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -562,7 +716,10 @@ TEST(MustacheSections, ImplicitIteratorInteger) {
 
 TEST(MustacheSections, ImplicitIteratorArray) {
   Value D = Object{{"list", Array{Array{1, 2, 3}, Array{"a", "b", "c"}}}};
-  Template T("{{#list}}({{#.}}{{.}}{{/.}}){{/list}}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{#list}}({{#.}}{{.}}{{/.}}){{/list}}", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -571,7 +728,10 @@ TEST(MustacheSections, ImplicitIteratorArray) {
 
 TEST(MustacheSections, ImplicitIteratorHTMLEscaping) {
   Value D = Object{{"list", Array{"&", "\"", "<", ">"}}};
-  Template T("{{#list}}({{.}}){{/list}}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{#list}}({{.}}){{/list}}", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -580,7 +740,10 @@ TEST(MustacheSections, ImplicitIteratorHTMLEscaping) {
 
 TEST(MustacheSections, ImplicitIteratorAmpersand) {
   Value D = Object{{"list", Array{"&", "\"", "<", ">"}}};
-  Template T("{{#list}}({{&.}}){{/list}}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{#list}}({{&.}}){{/list}}", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -589,7 +752,10 @@ TEST(MustacheSections, ImplicitIteratorAmpersand) {
 
 TEST(MustacheSections, ImplicitIteratorRootLevel) {
   Value D = Array{Object{{"value", "a"}}, Object{{"value", "b"}}};
-  Template T("{{#.}}({{value}}){{/.}}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{#.}}({{value}}){{/.}}", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -598,7 +764,10 @@ TEST(MustacheSections, ImplicitIteratorRootLevel) {
 
 TEST(MustacheSections, DottedNamesTruthy) {
   Value D = Object{{"a", Object{{"b", Object{{"c", true}}}}}};
-  Template T("{{#a.b.c}}Here{{/a.b.c}} == Here");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{#a.b.c}}Here{{/a.b.c}} == Here", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -607,7 +776,10 @@ TEST(MustacheSections, DottedNamesTruthy) {
 
 TEST(MustacheSections, DottedNamesFalsey) {
   Value D = Object{{"a", Object{{"b", Object{{"c", false}}}}}};
-  Template T("{{#a.b.c}}Here{{/a.b.c}} == ");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{#a.b.c}}Here{{/a.b.c}} == ", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -615,8 +787,11 @@ TEST(MustacheSections, DottedNamesFalsey) {
 }
 
 TEST(MustacheSections, DottedNamesBrokenChains) {
-  Value D = Object{{"a", Object{}}};
-  Template T("{{#a.b.c}}Here{{/a.b.c}} == ");
+  Value D = Object{{"a", Object{{}}}};
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{#a.b.c}}Here{{/a.b.c}} == ", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -625,7 +800,10 @@ TEST(MustacheSections, DottedNamesBrokenChains) {
 
 TEST(MustacheSections, SurroundingWhitespace) {
   Value D = Object{{"boolean", true}};
-  Template T(" | {{#boolean}}\t|\t{{/boolean}} | \n");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T(" | {{#boolean}}\t|\t{{/boolean}} | \n", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -634,7 +812,11 @@ TEST(MustacheSections, SurroundingWhitespace) {
 
 TEST(MustacheSections, InternalWhitespace) {
   Value D = Object{{"boolean", true}};
-  Template T(" | {{#boolean}} {{! Important Whitespace }}\n {{/boolean}} | \n");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T(" | {{#boolean}} {{! Important Whitespace }}\n {{/boolean}} | \n",
+             Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -643,7 +825,11 @@ TEST(MustacheSections, InternalWhitespace) {
 
 TEST(MustacheSections, IndentedInlineSections) {
   Value D = Object{{"boolean", true}};
-  Template T(" {{#boolean}}YES{{/boolean}}\n {{#boolean}}GOOD{{/boolean}}\n");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T(" {{#boolean}}YES{{/boolean}}\n {{#boolean}}GOOD{{/boolean}}\n",
+             Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -652,7 +838,10 @@ TEST(MustacheSections, IndentedInlineSections) {
 
 TEST(MustacheSections, StandaloneLines) {
   Value D = Object{{"boolean", true}};
-  Template T("| This Is\n{{#boolean}}\n|\n{{/boolean}}\n| A Line\n");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("| This Is\n{{#boolean}}\n|\n{{/boolean}}\n| A Line\n", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -661,7 +850,10 @@ TEST(MustacheSections, StandaloneLines) {
 
 TEST(MustacheSections, IndentedStandaloneLines) {
   Value D = Object{{"boolean", true}};
-  Template T("| This Is\n  {{#boolean}}\n|\n  {{/boolean}}\n| A Line\n");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("| This Is\n  {{#boolean}}\n|\n  {{/boolean}}\n| A Line\n", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -670,7 +862,10 @@ TEST(MustacheSections, IndentedStandaloneLines) {
 
 TEST(MustacheSections, StandaloneLineEndings) {
   Value D = Object{{"boolean", true}};
-  Template T("|\r\n{{#boolean}}\r\n{{/boolean}}\r\n|");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("|\r\n{{#boolean}}\r\n{{/boolean}}\r\n|", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -679,7 +874,10 @@ TEST(MustacheSections, StandaloneLineEndings) {
 
 TEST(MustacheSections, StandaloneWithoutPreviousLine) {
   Value D = Object{{"boolean", true}};
-  Template T("  {{#boolean}}\n#{{/boolean}}\n/");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("  {{#boolean}}\n#{{/boolean}}\n/", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -688,7 +886,10 @@ TEST(MustacheSections, StandaloneWithoutPreviousLine) {
 
 TEST(MustacheSections, StandaloneWithoutNewline) {
   Value D = Object{{"boolean", true}};
-  Template T("#{{#boolean}}\n/\n  {{/boolean}}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("#{{#boolean}}\n/\n  {{/boolean}}", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -697,7 +898,10 @@ TEST(MustacheSections, StandaloneWithoutNewline) {
 
 TEST(MustacheSections, Padding) {
   Value D = Object{{"boolean", true}};
-  Template T("|{{# boolean }}={{/ boolean }}|");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("|{{# boolean }}={{/ boolean }}|", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -706,7 +910,10 @@ TEST(MustacheSections, Padding) {
 
 TEST(MustacheInvertedSections, Falsey) {
   Value D = Object{{"boolean", false}};
-  Template T("{{^boolean}}This should be rendered.{{/boolean}}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{^boolean}}This should be rendered.{{/boolean}}", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -715,7 +922,10 @@ TEST(MustacheInvertedSections, Falsey) {
 
 TEST(MustacheInvertedSections, Truthy) {
   Value D = Object{{"boolean", true}};
-  Template T("{{^boolean}}This should not be rendered.{{/boolean}}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{^boolean}}This should not be rendered.{{/boolean}}", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -724,7 +934,10 @@ TEST(MustacheInvertedSections, Truthy) {
 
 TEST(MustacheInvertedSections, NullIsFalsey) {
   Value D = Object{{"null", nullptr}};
-  Template T("{{^null}}This should be rendered.{{/null}}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{^null}}This should be rendered.{{/null}}", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -733,7 +946,10 @@ TEST(MustacheInvertedSections, NullIsFalsey) {
 
 TEST(MustacheInvertedSections, Context) {
   Value D = Object{{"context", Object{{"name", "Joe"}}}};
-  Template T("{{^context}}Hi {{name}}.{{/context}}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{^context}}Hi {{name}}.{{/context}}", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -743,7 +959,10 @@ TEST(MustacheInvertedSections, Context) {
 TEST(MustacheInvertedSections, List) {
   Value D = Object{
       {"list", Array{Object{{"n", 1}}, Object{{"n", 2}}, Object{{"n", 3}}}}};
-  Template T("{{^list}}{{n}}{{/list}}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{^list}}{{n}}{{/list}}", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -752,7 +971,10 @@ TEST(MustacheInvertedSections, List) {
 
 TEST(MustacheInvertedSections, EmptyList) {
   Value D = Object{{"list", Array{}}};
-  Template T("{{^list}}Yay lists!{{/list}}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{^list}}Yay lists!{{/list}}", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -761,8 +983,12 @@ TEST(MustacheInvertedSections, EmptyList) {
 
 TEST(MustacheInvertedSections, Doubled) {
   Value D = Object{{"bool", false}, {"two", "second"}};
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
   Template T("{{^bool}}\n* first\n{{/bool}}\n* "
-             "{{two}}\n{{^bool}}\n* third\n{{/bool}}\n");
+             "{{two}}\n{{^bool}}\n* third\n{{/bool}}\n",
+             Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -771,7 +997,10 @@ TEST(MustacheInvertedSections, Doubled) {
 
 TEST(MustacheInvertedSections, NestedFalsey) {
   Value D = Object{{"bool", false}};
-  Template T("| A {{^bool}}B {{^bool}}C{{/bool}} D{{/bool}} E |");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("| A {{^bool}}B {{^bool}}C{{/bool}} D{{/bool}} E |", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -780,7 +1009,10 @@ TEST(MustacheInvertedSections, NestedFalsey) {
 
 TEST(MustacheInvertedSections, NestedTruthy) {
   Value D = Object{{"bool", true}};
-  Template T("| A {{^bool}}B {{^bool}}C{{/bool}} D{{/bool}} E |");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("| A {{^bool}}B {{^bool}}C{{/bool}} D{{/bool}} E |", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -789,7 +1021,10 @@ TEST(MustacheInvertedSections, NestedTruthy) {
 
 TEST(MustacheInvertedSections, ContextMisses) {
   Value D = Object{};
-  Template T("[{{^missing}}Cannot find key 'missing'!{{/missing}}]");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("[{{^missing}}Cannot find key 'missing'!{{/missing}}]", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -798,7 +1033,10 @@ TEST(MustacheInvertedSections, ContextMisses) {
 
 TEST(MustacheInvertedSections, DottedNamesTruthy) {
   Value D = Object{{"a", Object{{"b", Object{{"c", true}}}}}};
-  Template T("{{^a.b.c}}Not Here{{/a.b.c}} == ");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{^a.b.c}}Not Here{{/a.b.c}} == ", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -807,7 +1045,10 @@ TEST(MustacheInvertedSections, DottedNamesTruthy) {
 
 TEST(MustacheInvertedSections, DottedNamesFalsey) {
   Value D = Object{{"a", Object{{"b", Object{{"c", false}}}}}};
-  Template T("{{^a.b.c}}Not Here{{/a.b.c}} == Not Here");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{^a.b.c}}Not Here{{/a.b.c}} == Not Here", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -816,7 +1057,10 @@ TEST(MustacheInvertedSections, DottedNamesFalsey) {
 
 TEST(MustacheInvertedSections, DottedNamesBrokenChains) {
   Value D = Object{{"a", Object{}}};
-  Template T("{{^a.b.c}}Not Here{{/a.b.c}} == Not Here");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{^a.b.c}}Not Here{{/a.b.c}} == Not Here", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -825,7 +1069,10 @@ TEST(MustacheInvertedSections, DottedNamesBrokenChains) {
 
 TEST(MustacheInvertedSections, SurroundingWhitespace) {
   Value D = Object{{"boolean", false}};
-  Template T(" | {{^boolean}}\t|\t{{/boolean}} | \n");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T(" | {{^boolean}}\t|\t{{/boolean}} | \n", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -834,7 +1081,11 @@ TEST(MustacheInvertedSections, SurroundingWhitespace) {
 
 TEST(MustacheInvertedSections, InternalWhitespace) {
   Value D = Object{{"boolean", false}};
-  Template T(" | {{^boolean}} {{! Important Whitespace }}\n {{/boolean}} | \n");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T(" | {{^boolean}} {{! Important Whitespace }}\n {{/boolean}} | \n",
+             Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -843,7 +1094,11 @@ TEST(MustacheInvertedSections, InternalWhitespace) {
 
 TEST(MustacheInvertedSections, IndentedInlineSections) {
   Value D = Object{{"boolean", false}};
-  Template T(" {{^boolean}}NO{{/boolean}}\n {{^boolean}}WAY{{/boolean}}\n");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T(" {{^boolean}}NO{{/boolean}}\n {{^boolean}}WAY{{/boolean}}\n",
+             Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -852,7 +1107,10 @@ TEST(MustacheInvertedSections, IndentedInlineSections) {
 
 TEST(MustacheInvertedSections, StandaloneLines) {
   Value D = Object{{"boolean", false}};
-  Template T("| This Is\n{{^boolean}}\n|\n{{/boolean}}\n| A Line\n");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("| This Is\n{{^boolean}}\n|\n{{/boolean}}\n| A Line\n", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -861,7 +1119,10 @@ TEST(MustacheInvertedSections, StandaloneLines) {
 
 TEST(MustacheInvertedSections, StandaloneIndentedLines) {
   Value D = Object{{"boolean", false}};
-  Template T("| This Is\n  {{^boolean}}\n|\n  {{/boolean}}\n| A Line\n");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("| This Is\n  {{^boolean}}\n|\n  {{/boolean}}\n| A Line\n", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -870,7 +1131,10 @@ TEST(MustacheInvertedSections, StandaloneIndentedLines) {
 
 TEST(MustacheInvertedSections, StandaloneLineEndings) {
   Value D = Object{{"boolean", false}};
-  Template T("|\r\n{{^boolean}}\r\n{{/boolean}}\r\n|");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("|\r\n{{^boolean}}\r\n{{/boolean}}\r\n|", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -879,7 +1143,10 @@ TEST(MustacheInvertedSections, StandaloneLineEndings) {
 
 TEST(MustacheInvertedSections, StandaloneWithoutPreviousLine) {
   Value D = Object{{"boolean", false}};
-  Template T("  {{^boolean}}\n^{{/boolean}}\n/");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("  {{^boolean}}\n^{{/boolean}}\n/", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -888,7 +1155,10 @@ TEST(MustacheInvertedSections, StandaloneWithoutPreviousLine) {
 
 TEST(MustacheInvertedSections, StandaloneWithoutNewline) {
   Value D = Object{{"boolean", false}};
-  Template T("^{{^boolean}}\n/\n  {{/boolean}}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("^{{^boolean}}\n/\n  {{/boolean}}", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -897,7 +1167,10 @@ TEST(MustacheInvertedSections, StandaloneWithoutNewline) {
 
 TEST(MustacheInvertedSections, Padding) {
   Value D = Object{{"boolean", false}};
-  Template T("|{{^ boolean }}={{/ boolean }}|");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("|{{^ boolean }}={{/ boolean }}|", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -906,7 +1179,10 @@ TEST(MustacheInvertedSections, Padding) {
 
 TEST(MustachePartials, BasicBehavior) {
   Value D = Object{};
-  Template T("{{>text}}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{>text}}", Ctx);
   T.registerPartial("text", "from partial");
   std::string Out;
   raw_string_ostream OS(Out);
@@ -916,7 +1192,10 @@ TEST(MustachePartials, BasicBehavior) {
 
 TEST(MustachePartials, FailedLookup) {
   Value D = Object{};
-  Template T("{{>text}}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{>text}}", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -925,7 +1204,10 @@ TEST(MustachePartials, FailedLookup) {
 
 TEST(MustachePartials, Context) {
   Value D = Object{{"text", "content"}};
-  Template T("{{>partial}}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{>partial}}", Ctx);
   T.registerPartial("partial", "*{{text}}*");
   std::string Out;
   raw_string_ostream OS(Out);
@@ -937,7 +1219,10 @@ TEST(MustachePartials, Recursion) {
   Value D =
       Object{{"content", "X"},
              {"nodes", Array{Object{{"content", "Y"}, {"nodes", Array{}}}}}};
-  Template T("{{>node}}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{>node}}", Ctx);
   T.registerPartial("node", "{{content}}({{#nodes}}{{>node}}{{/nodes}})");
   std::string Out;
   raw_string_ostream OS(Out);
@@ -947,7 +1232,10 @@ TEST(MustachePartials, Recursion) {
 
 TEST(MustachePartials, Nested) {
   Value D = Object{{"a", "hello"}, {"b", "world"}};
-  Template T("{{>outer}}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{>outer}}", Ctx);
   T.registerPartial("outer", "*{{a}} {{>inner}}*");
   T.registerPartial("inner", "{{b}}!");
   std::string Out;
@@ -958,7 +1246,10 @@ TEST(MustachePartials, Nested) {
 
 TEST(MustachePartials, SurroundingWhitespace) {
   Value D = Object{};
-  Template T("| {{>partial}} |");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("| {{>partial}} |", Ctx);
   T.registerPartial("partial", "\t|\t");
   std::string Out;
   raw_string_ostream OS(Out);
@@ -968,7 +1259,10 @@ TEST(MustachePartials, SurroundingWhitespace) {
 
 TEST(MustachePartials, InlineIndentation) {
   Value D = Object{{"data", "|"}};
-  Template T("  {{data}}  {{> partial}}\n");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("  {{data}}  {{> partial}}\n", Ctx);
   T.registerPartial("partial", "<\n<");
   std::string Out;
   raw_string_ostream OS(Out);
@@ -978,7 +1272,10 @@ TEST(MustachePartials, InlineIndentation) {
 
 TEST(MustachePartials, PaddingWhitespace) {
   Value D = Object{{"boolean", true}};
-  Template T("|{{> partial }}|");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("|{{> partial }}|", Ctx);
   T.registerPartial("partial", "[]");
   std::string Out;
   raw_string_ostream OS(Out);
@@ -987,7 +1284,10 @@ TEST(MustachePartials, PaddingWhitespace) {
 }
 
 TEST(MustachePartials, StandaloneIndentation) {
-  mustache::Template T("\\\n {{>partial}}\n/\n");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  mustache::Template T("\\\n {{>partial}}\n/\n", Ctx);
   T.registerPartial("partial", "|\n{{{content}}}\n|\n");
   std::string O;
   raw_string_ostream OS(O);
@@ -998,7 +1298,10 @@ TEST(MustachePartials, StandaloneIndentation) {
 
 TEST(MustacheLambdas, BasicInterpolation) {
   Value D = Object{};
-  Template T("Hello, {{lambda}}!");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("Hello, {{lambda}}!", Ctx);
   Lambda L = []() -> llvm::json::Value { return "World"; };
   T.registerLambda("lambda", L);
   std::string Out;
@@ -1009,7 +1312,10 @@ TEST(MustacheLambdas, BasicInterpolation) {
 
 TEST(MustacheLambdas, InterpolationExpansion) {
   Value D = Object{{"planet", "World"}};
-  Template T("Hello, {{lambda}}!");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("Hello, {{lambda}}!", Ctx);
   Lambda L = []() -> llvm::json::Value { return "{{planet}}"; };
   T.registerLambda("lambda", L);
   std::string Out;
@@ -1020,7 +1326,10 @@ TEST(MustacheLambdas, InterpolationExpansion) {
 
 TEST(MustacheLambdas, BasicMultipleCalls) {
   Value D = Object{};
-  Template T("{{lambda}} == {{lambda}} == {{lambda}}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{lambda}} == {{lambda}} == {{lambda}}", Ctx);
   int I = 0;
   Lambda L = [&I]() -> llvm::json::Value {
     I += 1;
@@ -1035,7 +1344,10 @@ TEST(MustacheLambdas, BasicMultipleCalls) {
 
 TEST(MustacheLambdas, Escaping) {
   Value D = Object{};
-  Template T("<{{lambda}}{{&lambda}}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("<{{lambda}}{{&lambda}}", Ctx);
   Lambda L = []() -> llvm::json::Value { return ">"; };
   T.registerLambda("lambda", L);
   std::string Out;
@@ -1046,7 +1358,10 @@ TEST(MustacheLambdas, Escaping) {
 
 TEST(MustacheLambdas, Sections) {
   Value D = Object{};
-  Template T("<{{#lambda}}{{x}}{{/lambda}}>");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("<{{#lambda}}{{x}}{{/lambda}}>", Ctx);
   SectionLambda L = [](StringRef Text) -> llvm::json::Value {
     if (Text == "{{x}}") {
       return "yes";
@@ -1064,7 +1379,10 @@ TEST(MustacheLambdas, SectionExpansion) {
   Value D = Object{
       {"planet", "Earth"},
   };
-  Template T("<{{#lambda}}-{{/lambda}}>");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("<{{#lambda}}-{{/lambda}}>", Ctx);
   SectionLambda L = [](StringRef Text) -> llvm::json::Value {
     SmallString<128> Result;
     Result += Text;
@@ -1081,7 +1399,10 @@ TEST(MustacheLambdas, SectionExpansion) {
 
 TEST(MustacheLambdas, SectionsMultipleCalls) {
   Value D = Object{};
-  Template T("{{#lambda}}FILE{{/lambda}} != {{#lambda}}LINE{{/lambda}}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{#lambda}}FILE{{/lambda}} != {{#lambda}}LINE{{/lambda}}", Ctx);
   SectionLambda L = [](StringRef Text) -> llvm::json::Value {
     SmallString<128> Result;
     Result += "__";
@@ -1098,7 +1419,10 @@ TEST(MustacheLambdas, SectionsMultipleCalls) {
 
 TEST(MustacheLambdas, InvertedSections) {
   Value D = Object{{"static", "static"}};
-  Template T("<{{^lambda}}{{static}}{{/lambda}}>");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("<{{^lambda}}{{static}}{{/lambda}}>", Ctx);
   SectionLambda L = [](StringRef Text) -> llvm::json::Value { return false; };
   T.registerLambda("lambda", L);
   std::string Out;
@@ -1110,7 +1434,10 @@ TEST(MustacheLambdas, InvertedSections) {
 TEST(MustacheComments, Inline) {
   // Comment blocks should be removed from the template.
   Value D = {};
-  Template T("12345{{! Comment Block! }}67890");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("12345{{! Comment Block! }}67890", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -1120,7 +1447,10 @@ TEST(MustacheComments, Inline) {
 TEST(MustacheComments, Multiline) {
   // Multiline comments should be permitted.
   Value D = {};
-  Template T("12345{{!\n  This is a\n  multi-line comment...\n}}67890\n");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("12345{{!\n  This is a\n  multi-line comment...\n}}67890\n", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -1130,7 +1460,10 @@ TEST(MustacheComments, Multiline) {
 TEST(MustacheComments, Standalone) {
   // All standalone comment lines should be removed.
   Value D = {};
-  Template T("Begin.\n{{! Comment Block! }}\nEnd.\n");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("Begin.\n{{! Comment Block! }}\nEnd.\n", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -1140,7 +1473,10 @@ TEST(MustacheComments, Standalone) {
 TEST(MustacheComments, IndentedStandalone) {
   // All standalone comment lines should be removed.
   Value D = {};
-  Template T("Begin.\n  {{! Indented Comment Block! }}\nEnd.\n");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("Begin.\n  {{! Indented Comment Block! }}\nEnd.\n", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -1150,7 +1486,10 @@ TEST(MustacheComments, IndentedStandalone) {
 TEST(MustacheComments, StandaloneLineEndings) {
   // "\r\n" should be considered a newline for standalone tags.
   Value D = {};
-  Template T("|\r\n{{! Standalone Comment }}\r\n|");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("|\r\n{{! Standalone Comment }}\r\n|", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -1160,7 +1499,10 @@ TEST(MustacheComments, StandaloneLineEndings) {
 TEST(MustacheComments, StandaloneWithoutPreviousLine) {
   // Standalone tags should not require a newline to precede them.
   Value D = {};
-  Template T("  {{! I'm Still Standalone }}\n!");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("  {{! I'm Still Standalone }}\n!", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -1170,7 +1512,10 @@ TEST(MustacheComments, StandaloneWithoutPreviousLine) {
 TEST(MustacheComments, StandaloneWithoutNewline) {
   // Standalone tags should not require a newline to follow them.
   Value D = {};
-  Template T("!\n  {{! I'm Still Standalone }}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("!\n  {{! I'm Still Standalone }}", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -1180,7 +1525,10 @@ TEST(MustacheComments, StandaloneWithoutNewline) {
 TEST(MustacheComments, MultilineStandalone) {
   // All standalone comment lines should be removed.
   Value D = {};
-  Template T("Begin.\n{{!\nSomething's going on here...\n}}\nEnd.\n");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("Begin.\n{{!\nSomething's going on here...\n}}\nEnd.\n", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -1190,7 +1538,11 @@ TEST(MustacheComments, MultilineStandalone) {
 TEST(MustacheComments, IndentedMultilineStandalone) {
   // All standalone comment lines should be removed.
   Value D = {};
-  Template T("Begin.\n  {{!\n    Something's going on here...\n  }}\nEnd.\n");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("Begin.\n  {{!\n    Something's going on here...\n  }}\nEnd.\n",
+             Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -1200,7 +1552,10 @@ TEST(MustacheComments, IndentedMultilineStandalone) {
 TEST(MustacheComments, IndentedInline) {
   // Inline comments should not strip whitespace.
   Value D = {};
-  Template T("  12 {{! 34 }}\n");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("  12 {{! 34 }}\n", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -1210,7 +1565,10 @@ TEST(MustacheComments, IndentedInline) {
 TEST(MustacheComments, SurroundingWhitespace) {
   // Comment removal should preserve surrounding whitespace.
   Value D = {};
-  Template T("12345 {{! Comment Block! }} 67890");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("12345 {{! Comment Block! }} 67890", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -1221,7 +1579,10 @@ TEST(MustacheComments, VariableNameCollision) {
   // Comments must never render, even if a variable with the same name exists.
   Value D = Object{
       {"! comment", 1}, {"! comment ", 2}, {"!comment", 3}, {"comment", 4}};
-  Template T("comments never show: >{{! comment }}<");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("comments never show: >{{! comment }}<", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -1234,7 +1595,10 @@ TEST(MustacheComments, VariableNameCollision) {
 // implemented, these assertions should be changed back to EXPECT_EQ.
 TEST(MustacheTripleMustache, Basic) {
   Value D = Object{{"subject", "<b>World</b>"}};
-  Template T("Hello, {{{subject}}}!");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("Hello, {{{subject}}}!", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -1243,7 +1607,10 @@ TEST(MustacheTripleMustache, Basic) {
 
 TEST(MustacheTripleMustache, IntegerInterpolation) {
   Value D = Object{{"mph", 85}};
-  Template T("{{{mph}}} miles an hour!");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{{mph}}} miles an hour!", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -1252,7 +1619,10 @@ TEST(MustacheTripleMustache, IntegerInterpolation) {
 
 TEST(MustacheTripleMustache, DecimalInterpolation) {
   Value D = Object{{"power", 1.21}};
-  Template T("{{{power}}} jiggawatts!");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{{power}}} jiggawatts!", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -1261,7 +1631,10 @@ TEST(MustacheTripleMustache, DecimalInterpolation) {
 
 TEST(MustacheTripleMustache, NullInterpolation) {
   Value D = Object{{"cannot", nullptr}};
-  Template T("I ({{{cannot}}}) be seen!");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("I ({{{cannot}}}) be seen!", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -1270,7 +1643,10 @@ TEST(MustacheTripleMustache, NullInterpolation) {
 
 TEST(MustacheTripleMustache, ContextMissInterpolation) {
   Value D = Object{};
-  Template T("I ({{{cannot}}}) be seen!");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("I ({{{cannot}}}) be seen!", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -1279,7 +1655,10 @@ TEST(MustacheTripleMustache, ContextMissInterpolation) {
 
 TEST(MustacheTripleMustache, DottedNames) {
   Value D = Object{{"person", Object{{"name", "<b>Joe</b>"}}}};
-  Template T("{{{person.name}}}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{{person.name}}}", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -1288,7 +1667,10 @@ TEST(MustacheTripleMustache, DottedNames) {
 
 TEST(MustacheTripleMustache, ImplicitIterator) {
   Value D = Object{{"list", Array{"<a>", "<b>"}}};
-  Template T("{{#list}}({{{.}}}){{/list}}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{#list}}({{{.}}}){{/list}}", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -1297,7 +1679,10 @@ TEST(MustacheTripleMustache, ImplicitIterator) {
 
 TEST(MustacheTripleMustache, SurroundingWhitespace) {
   Value D = Object{{"string", "---"}};
-  Template T("| {{{string}}} |");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("| {{{string}}} |", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -1306,7 +1691,10 @@ TEST(MustacheTripleMustache, SurroundingWhitespace) {
 
 TEST(MustacheTripleMustache, Standalone) {
   Value D = Object{{"string", "---"}};
-  Template T("  {{{string}}}\n");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("  {{{string}}}\n", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -1315,7 +1703,10 @@ TEST(MustacheTripleMustache, Standalone) {
 
 TEST(MustacheTripleMustache, WithPadding) {
   Value D = Object{{"string", "---"}};
-  Template T("|{{{ string }}}|");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("|{{{ string }}}|", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -1324,7 +1715,10 @@ TEST(MustacheTripleMustache, WithPadding) {
 
 TEST(MustacheDelimiters, PairBehavior) {
   Value D = Object{{"text", "Hey!"}};
-  Template T("{{=<% %>=}}(<%text%>)");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("{{=<% %>=}}(<%text%>)", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -1333,7 +1727,10 @@ TEST(MustacheDelimiters, PairBehavior) {
 
 TEST(MustacheDelimiters, SpecialCharacters) {
   Value D = Object{{"text", "It worked!"}};
-  Template T("({{=[ ]=}}[text])");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("({{=[ ]=}}[text])", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -1342,9 +1739,12 @@ TEST(MustacheDelimiters, SpecialCharacters) {
 
 TEST(MustacheDelimiters, Sections) {
   Value D = Object{{"section", true}, {"data", "I got interpolated."}};
-  auto T =
-      Template("[\n{{#section}}\n  {{data}}\n  |data|\n{{/section}}\n\n{{= "
-               "| | =}}\n|#section|\n  {{data}}\n  |data|\n|/section|\n]\n");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("[\n{{#section}}\n  {{data}}\n  |data|\n{{/section}}\n\n{{= "
+             "| | =}}\n|#section|\n  {{data}}\n  |data|\n|/section|\n]\n",
+             Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -1355,9 +1755,12 @@ TEST(MustacheDelimiters, Sections) {
 
 TEST(MustacheDelimiters, InvertedSections) {
   Value D = Object{{"section", false}, {"data", "I got interpolated."}};
-  auto T =
-      Template("[\n{{^section}}\n  {{data}}\n  |data|\n{{/section}}\n\n{{= "
-               "| | =}}\n|^section|\n  {{data}}\n  |data|\n|/section|\n]\n");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("[\n{{^section}}\n  {{data}}\n  |data|\n{{/section}}\n\n{{= "
+             "| | =}}\n|^section|\n  {{data}}\n  |data|\n|/section|\n]\n",
+             Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -1368,7 +1771,10 @@ TEST(MustacheDelimiters, InvertedSections) {
 
 TEST(MustacheDelimiters, PartialInheritence) {
   Value D = Object{{"value", "yes"}};
-  Template T("[ {{>include}} ]\n{{= | | =}}\n[ |>include| ]\n");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("[ {{>include}} ]\n{{= | | =}}\n[ |>include| ]\n", Ctx);
   T.registerPartial("include", ".{{value}}.");
   std::string Out;
   raw_string_ostream OS(Out);
@@ -1378,7 +1784,10 @@ TEST(MustacheDelimiters, PartialInheritence) {
 
 TEST(MustacheDelimiters, PostPartialBehavior) {
   Value D = Object{{"value", "yes"}};
-  Template T("[ {{>include}} ]\n[ .{{value}}.  .|value|. ]\n");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("[ {{>include}} ]\n[ .{{value}}.  .|value|. ]\n", Ctx);
   T.registerPartial("include", ".{{value}}. {{= | | =}} .|value|.");
   std::string Out;
   raw_string_ostream OS(Out);
@@ -1388,7 +1797,10 @@ TEST(MustacheDelimiters, PostPartialBehavior) {
 
 TEST(MustacheDelimiters, SurroundingWhitespace) {
   Value D = Object{};
-  Template T("| {{=@ @=}} |");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("| {{=@ @=}} |", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -1397,7 +1809,10 @@ TEST(MustacheDelimiters, SurroundingWhitespace) {
 
 TEST(MustacheDelimiters, OutlyingWhitespaceInline) {
   Value D = Object{};
-  Template T(" | {{=@ @=}}\n");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T(" | {{=@ @=}}\n", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -1406,7 +1821,10 @@ TEST(MustacheDelimiters, OutlyingWhitespaceInline) {
 
 TEST(MustacheDelimiters, StandaloneTag) {
   Value D = Object{};
-  Template T("Begin.\n{{=@ @=}}\nEnd.\n");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("Begin.\n{{=@ @=}}\nEnd.\n", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -1415,7 +1833,10 @@ TEST(MustacheDelimiters, StandaloneTag) {
 
 TEST(MustacheDelimiters, IndentedStandaloneTag) {
   Value D = Object{};
-  Template T("Begin.\n  {{=@ @=}}\nEnd.\n");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("Begin.\n  {{=@ @=}}\nEnd.\n", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -1424,7 +1845,10 @@ TEST(MustacheDelimiters, IndentedStandaloneTag) {
 
 TEST(MustacheDelimiters, StandaloneLineEndings) {
   Value D = Object{};
-  Template T("|\r\n{{= @ @ =}}\r\n|");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("|\r\n{{= @ @ =}}\r\n|", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -1433,7 +1857,10 @@ TEST(MustacheDelimiters, StandaloneLineEndings) {
 
 TEST(MustacheDelimiters, StandaloneWithoutPreviousLine) {
   Value D = Object{};
-  Template T("  {{=@ @=}}\n=");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("  {{=@ @=}}\n=", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -1442,7 +1869,10 @@ TEST(MustacheDelimiters, StandaloneWithoutPreviousLine) {
 
 TEST(MustacheDelimiters, StandaloneWithoutNewline) {
   Value D = Object{};
-  Template T("=\n  {{=@ @=}}");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("=\n  {{=@ @=}}", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);
@@ -1451,7 +1881,10 @@ TEST(MustacheDelimiters, StandaloneWithoutNewline) {
 
 TEST(MustacheDelimiters, PairwithPadding) {
   Value D = Object{};
-  Template T("|{{= @   @ =}}|");
+  BumpPtrAllocator Allocator;
+  StringSaver Saver(Allocator);
+  MustacheContext Ctx(Allocator, Saver);
+  Template T("|{{= @   @ =}}|", Ctx);
   std::string Out;
   raw_string_ostream OS(Out);
   T.render(D, OS);

diff  --git a/llvm/utils/llvm-test-mustache-spec/llvm-test-mustache-spec.cpp b/llvm/utils/llvm-test-mustache-spec/llvm-test-mustache-spec.cpp
index 9007eb365a15f..93e2efef17117 100644
--- a/llvm/utils/llvm-test-mustache-spec/llvm-test-mustache-spec.cpp
+++ b/llvm/utils/llvm-test-mustache-spec/llvm-test-mustache-spec.cpp
@@ -212,7 +212,10 @@ static void runTest(StringRef InputFile) {
   for (Value V : *TestArray) {
     auto TestData =
         ExitOnErr(TestData::createTestData(V.getAsObject(), InputFile));
-    Template T(TestData.TemplateStr);
+    BumpPtrAllocator Allocator;
+    StringSaver Saver(Allocator);
+    MustacheContext Ctx(Allocator, Saver);
+    Template T(TestData.TemplateStr, Ctx);
     registerPartials(TestData.Partials, T);
 
     std::string ActualStr;


        


More information about the llvm-commits mailing list