diff --git a/core/lib/Drupal/Component/Utility/SafeMarkup.php b/core/lib/Drupal/Component/Utility/SafeMarkup.php
index 7a7b8ea6581c20d2bd17967ad86abea3fe6372b4..15d522341d753119b0d856c9f1f76fbb0039e70a 100644
--- a/core/lib/Drupal/Component/Utility/SafeMarkup.php
+++ b/core/lib/Drupal/Component/Utility/SafeMarkup.php
@@ -25,7 +25,7 @@
  * @link theme_render theme and render systems @endlink so that the output can
  * can be themed, escaped, and altered properly.
  *
- * @see twig_drupal_escape_filter()
+ * @see TwigExtension::escapeFilter()
  * @see twig_render_template()
  * @see sanitization
  * @see theme_render
diff --git a/core/lib/Drupal/Core/Template/TwigEnvironment.php b/core/lib/Drupal/Core/Template/TwigEnvironment.php
index 8118105ebfc38eaac9cfb706a72ef23ee524393d..92257120c7c3a9d39bf1b4ae4b476e1e21210feb 100644
--- a/core/lib/Drupal/Core/Template/TwigEnvironment.php
+++ b/core/lib/Drupal/Core/Template/TwigEnvironment.php
@@ -44,7 +44,7 @@ public function __construct($root, \Twig_LoaderInterface $loader = NULL, $option
     $this->cache_object = \Drupal::cache();
 
     // Ensure that twig.engine is loaded, given that it is needed to render a
-    // template because functions like twig_drupal_escape_filter are called.
+    // template because functions like TwigExtension::escapeFilter() are called.
     require_once $root . '/core/themes/engines/twig/twig.engine';
 
     $this->templateClasses = array();
diff --git a/core/lib/Drupal/Core/Template/TwigExtension.php b/core/lib/Drupal/Core/Template/TwigExtension.php
index 44b0a2ab554c1d69d76ccf8646ea8d3e15858dd4..bb29cd172e6d6b93d7a9ffbc7f88edc0cefa0d78 100644
--- a/core/lib/Drupal/Core/Template/TwigExtension.php
+++ b/core/lib/Drupal/Core/Template/TwigExtension.php
@@ -12,6 +12,7 @@
 
 namespace Drupal\Core\Template;
 
+use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Core\Render\RendererInterface;
 use Drupal\Core\Routing\UrlGeneratorInterface;
 use Drupal\Core\Url;
@@ -89,7 +90,7 @@ public function setLinkGenerator(LinkGeneratorInterface $link_generator) {
   public function getFunctions() {
     return array(
       // This function will receive a renderable array, if an array is detected.
-      new \Twig_SimpleFunction('render_var', 'twig_render_var'),
+      new \Twig_SimpleFunction('render_var', array($this, 'renderVar')),
       // The url and path function are defined in close parallel to those found
       // in \Symfony\Bridge\Twig\Extension\RoutingExtension
       new \Twig_SimpleFunction('url', array($this, 'getUrl'), array('is_safe_callback' => array($this, 'isUrlGenerationSafe'))),
@@ -118,7 +119,7 @@ public function getFilters() {
       new \Twig_SimpleFilter('placeholder', 'twig_raw_filter', array('is_safe' => array('html'))),
 
       // Replace twig's escape filter with our own.
-      new \Twig_SimpleFilter('drupal_escape', 'twig_drupal_escape_filter', array('needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe')),
+      new \Twig_SimpleFilter('drupal_escape', [$this, 'escapeFilter'], array('needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe')),
 
       // Implements safe joining.
       // @todo Make that the default for |join? Upstream issue:
@@ -132,7 +133,7 @@ public function getFilters() {
       new \Twig_SimpleFilter('clean_class', '\Drupal\Component\Utility\Html::getClass'),
       new \Twig_SimpleFilter('clean_id', '\Drupal\Component\Utility\Html::getId'),
       // This filter will render a renderable array to use the string results.
-      new \Twig_SimpleFilter('render', 'twig_render_var'),
+      new \Twig_SimpleFilter('render', array($this, 'renderVar')),
     );
   }
 
@@ -141,7 +142,7 @@ public function getFilters() {
    */
   public function getNodeVisitors() {
     // The node visitor is needed to wrap all variables with
-    // render_var -> twig_render_var() function.
+    // render_var -> TwigExtension->renderVar() function.
     return array(
       new TwigNodeVisitor(),
     );
@@ -298,4 +299,148 @@ public function attachLibrary($library) {
     $this->renderer->render($template_attached);
   }
 
+  /**
+   * Overrides twig_escape_filter().
+   *
+   * Replacement function for Twig's escape filter.
+   *
+   * @param \Twig_Environment $env
+   *   A Twig_Environment instance.
+   * @param mixed $arg
+   *   The value to be escaped.
+   * @param string $strategy
+   *   The escaping strategy. Defaults to 'html'.
+   * @param string $charset
+   *   The charset.
+   * @param bool $autoescape
+   *   Whether the function is called by the auto-escaping feature (TRUE) or by
+   *   the developer (FALSE).
+   *
+   * @return string|null
+   *   The escaped, rendered output, or NULL if there is no valid output.
+   */
+  public function escapeFilter(\Twig_Environment $env, $arg, $strategy = 'html', $charset = NULL, $autoescape = FALSE) {
+    // Check for a numeric zero int or float.
+    if ($arg === 0 || $arg === 0.0) {
+      return 0;
+    }
+
+    // Return early for NULL and empty arrays.
+    if ($arg == NULL) {
+      return NULL;
+    }
+
+    // Keep Twig_Markup objects intact to support autoescaping.
+    if ($autoescape && $arg instanceOf \Twig_Markup) {
+      return $arg;
+    }
+
+    $return = NULL;
+
+    if (is_scalar($arg)) {
+      $return = (string) $arg;
+    }
+    elseif (is_object($arg)) {
+      if (method_exists($arg, '__toString')) {
+        $return = (string) $arg;
+      }
+      // You can't throw exceptions in the magic PHP __toString methods, see
+      // http://php.net/manual/en/language.oop5.magic.php#object.tostring so
+      // we also support a toString method.
+      elseif (method_exists($arg, 'toString')) {
+        $return = $arg->toString();
+      }
+      else {
+        throw new \Exception(t('Object of type "@class" cannot be printed.', array('@class' => get_class($arg))));
+      }
+    }
+
+    // We have a string or an object converted to a string: Autoescape it!
+    if (isset($return)) {
+      if ($autoescape && SafeMarkup::isSafe($return, $strategy)) {
+        return $return;
+      }
+      // Drupal only supports the HTML escaping strategy, so provide a
+      // fallback for other strategies.
+      if ($strategy == 'html') {
+        return SafeMarkup::checkPlain($return);
+      }
+      return twig_escape_filter($env, $return, $strategy, $charset, $autoescape);
+    }
+
+    // This is a normal render array, which is safe by definition, with
+    // special simple cases already handled.
+
+    // Early return if this element was pre-rendered (no need to re-render).
+    if (isset($arg['#printed']) && $arg['#printed'] == TRUE && isset($arg['#markup']) && strlen($arg['#markup']) > 0) {
+      return $arg['#markup'];
+    }
+    $arg['#printed'] = FALSE;
+    return $this->renderer->render($arg);
+  }
+
+  /**
+   * Wrapper around render() for twig printed output.
+   *
+   * If an object is passed that has no __toString method an exception is thrown;
+   * other objects are casted to string. However in the case that the object is an
+   * instance of a Twig_Markup object it is returned directly to support auto
+   * escaping.
+   *
+   * If an array is passed it is rendered via render() and scalar values are
+   * returned directly.
+   *
+   * @param mixed $arg
+   *   String, Object or Render Array.
+   *
+   * @return mixed
+   *   The rendered output or an Twig_Markup object.
+   *
+   * @see render
+   * @see TwigNodeVisitor
+   */
+  public function renderVar($arg) {
+    // Check for a numeric zero int or float.
+    if ($arg === 0 || $arg === 0.0) {
+      return 0;
+    }
+
+    // Return early for NULL and empty arrays.
+    if ($arg == NULL) {
+      return NULL;
+    }
+
+    // Optimize for strings as it is likely they come from the escape filter.
+    if (is_string($arg)) {
+      return $arg;
+    }
+
+    if (is_scalar($arg)) {
+      return $arg;
+    }
+
+    if (is_object($arg)) {
+      if (method_exists($arg, '__toString')) {
+        return (string) $arg;
+      }
+      // You can't throw exceptions in the magic PHP __toString methods, see
+      // http://php.net/manual/en/language.oop5.magic.php#object.tostring so
+      // we also support a toString method.
+      elseif (method_exists($arg, 'toString')) {
+        return $arg->toString();
+      }
+      else {
+        throw new \Exception(t('Object of type "@class" cannot be printed.', array('@class' => get_class($arg))));
+      }
+    }
+
+    // This is a render array, with special simple cases already handled.
+    // Early return if this element was pre-rendered (no need to re-render).
+    if (isset($arg['#printed']) && $arg['#printed'] == TRUE && isset($arg['#markup']) && strlen($arg['#markup']) > 0) {
+      return $arg['#markup'];
+    }
+    $arg['#printed'] = FALSE;
+    return $this->renderer->render($arg);
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Template/TwigNodeTrans.php b/core/lib/Drupal/Core/Template/TwigNodeTrans.php
index c4fd4efce02f3d83b0ccca3c734e1cec9d4cfe2f..1bacb9ec68e76ba2dd3db879b57c3fc860ab7448 100644
--- a/core/lib/Drupal/Core/Template/TwigNodeTrans.php
+++ b/core/lib/Drupal/Core/Template/TwigNodeTrans.php
@@ -136,7 +136,7 @@ protected function compileString(\Twig_NodeInterface $body) {
 
           $args = $n;
 
-          // Support twig_render_var function in chain.
+          // Support TwigExtension->renderVar() function in chain.
           if ($args instanceof \Twig_Node_Expression_Function) {
             $args = $n->getNode('arguments')->getNode(0);
           }
diff --git a/core/lib/Drupal/Core/Template/TwigNodeVisitor.php b/core/lib/Drupal/Core/Template/TwigNodeVisitor.php
index 4915c16a350c44f8825ceb983ced6d5be3d065aa..a08ec2d47ae8fb4b748041bfe60f96d934ff5d17 100644
--- a/core/lib/Drupal/Core/Template/TwigNodeVisitor.php
+++ b/core/lib/Drupal/Core/Template/TwigNodeVisitor.php
@@ -11,7 +11,7 @@
  * Provides a Twig_NodeVisitor to change the generated parse-tree.
  *
  * This is used to ensure that everything printed is wrapped via the
- * twig_render_var() function in order to just write {{ content }}
+ * TwigExtension->renderVar() function in order to just write {{ content }}
  * in templates instead of having to write {{ render_var(content) }}.
  *
  * @see twig_render
@@ -29,7 +29,7 @@ function enterNode(\Twig_NodeInterface $node, \Twig_Environment $env) {
    * {@inheritdoc}
    */
   function leaveNode(\Twig_NodeInterface $node, \Twig_Environment $env) {
-    // We use this to inject a call to render_var -> twig_render_var()
+    // We use this to inject a call to render_var -> TwigExtension->renderVar()
     // before anything is printed.
     if ($node instanceof \Twig_Node_Print) {
       if (!empty($this->skipRenderVarFunction)) {
diff --git a/core/modules/system/src/Tests/Theme/TwigExtensionTest.php b/core/modules/system/src/Tests/Theme/TwigExtensionTest.php
index 6719ab72b36b0f0e7b6e07d13670fc8055539772..7e25ae501620b2368f233af5c5a816357d256b53 100644
--- a/core/modules/system/src/Tests/Theme/TwigExtensionTest.php
+++ b/core/modules/system/src/Tests/Theme/TwigExtensionTest.php
@@ -63,4 +63,30 @@ function testTwigExtensionFunction() {
     $this->assertNoText('The Quick Brown Fox Jumps Over The Lazy Dog 123.', 'Success: No text left behind.');
   }
 
+  /**
+   * Tests output of integer and double 0 values of TwigExtension::escapeFilter().
+   *
+   * @see https://www.drupal.org/node/2417733
+   */
+  public function testsRenderEscapedZeroValue() {
+    /** @var \Drupal\Core\Template\TwigExtension $extension */
+    $extension = \Drupal::service('twig.extension');
+    /** @var \Drupal\Core\Template\TwigEnvironment $twig */
+    $twig = \Drupal::service('twig');
+    $this->assertIdentical($extension->escapeFilter($twig, 0), 0, 'TwigExtension::escapeFilter() returns zero correctly when provided as an integer.');
+    $this->assertIdentical($extension->escapeFilter($twig, 0.0), 0, 'TwigExtension::escapeFilter() returns zero correctly when provided as a double.');
+  }
+
+  /**
+   * Tests output of integer and double 0 values of TwigExtension->renderVar().
+   *
+   * @see https://www.drupal.org/node/2417733
+   */
+  public function testsRenderZeroValue() {
+    /** @var \Drupal\Core\Template\TwigExtension $extension */
+    $extension = \Drupal::service('twig.extension');
+    $this->assertIdentical($extension->renderVar(0), 0, 'TwigExtension::renderVar() renders zero correctly when provided as an integer.');
+    $this->assertIdentical($extension->renderVar(0.0), 0, 'TwigExtension::renderVar() renders zero correctly when provided as a double.');
+  }
+
 }
diff --git a/core/tests/Drupal/Tests/Core/Theme/TwigEngineTest.php b/core/tests/Drupal/Tests/Core/Theme/TwigEngineTest.php
deleted file mode 100644
index baf45c1a4510ba3e498b228c95aafa28d3e6fcbe..0000000000000000000000000000000000000000
--- a/core/tests/Drupal/Tests/Core/Theme/TwigEngineTest.php
+++ /dev/null
@@ -1,59 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\Tests\Core\Theme\TwigEngineTest.
- */
-
-namespace Drupal\Tests\Core\Theme;
-
-use Drupal\Tests\UnitTestCase;
-
-/**
- * Test coverage for the file core/themes/engines/twig/twig.engine.
- *
- * @group Theme
- */
-class TwigEngineTest extends UnitTestCase {
-
-  /**
-   * The mocked Twig environment.
-   *
-   * @var \Twig_Environment|\PHPUnit_Framework_MockObject_MockObject
-   */
-  protected $twigEnvironment;
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function setUp() {
-    parent::setUp();
-
-    // Ensure that twig.engine is loaded, it is needed to access
-    // twig_drupal_escape_filter().
-    require_once $this->root . '/core/themes/engines/twig/twig.engine';
-
-    $this->twigEnvironment = $this->getMock('\Twig_Environment');
-  }
-
-  /**
-   * Tests output of integer and double 0 values of twig_render_var().
-   *
-   * @see https://www.drupal.org/node/2417733
-   */
-  public function testsRenderZeroValue() {
-    $this->assertSame(twig_render_var(0), 0, 'twig_render_var() renders zero correctly when provided as an integer.');
-    $this->assertSame(twig_render_var(0.0), 0, 'twig_render_var() renders zero correctly when provided as a double.');
-  }
-
-  /**
-   * Tests output of integer and double 0 values of twig_drupal_escape_filter().
-   *
-   * @see https://www.drupal.org/node/2417733
-   */
-  public function testsRenderEscapedZeroValue() {
-    $this->assertSame(twig_drupal_escape_filter($this->twigEnvironment, 0), 0, 'twig_escape_filter() returns zero correctly when provided as an integer.');
-    $this->assertSame(twig_drupal_escape_filter($this->twigEnvironment, 0.0), 0, 'twig_escape_filter() returns zero correctly when provided as a double.');
-  }
-
-}
diff --git a/core/themes/engines/twig/twig.engine b/core/themes/engines/twig/twig.engine
index 324203e225a2d4be4a469e3567f08afd19170a53..9f8bc4a044bf0d1f180de6a580b499b69e5afbce 100644
--- a/core/themes/engines/twig/twig.engine
+++ b/core/themes/engines/twig/twig.engine
@@ -106,65 +106,6 @@ function twig_render_template($template_file, array $variables) {
   return SafeMarkup::set(implode('', $output));
 }
 
-/**
- * Wrapper around render() for twig printed output.
- *
- * If an object is passed that has no __toString method an exception is thrown;
- * other objects are casted to string. However in the case that the object is an
- * instance of a Twig_Markup object it is returned directly to support auto
- * escaping.
- *
- * If an array is passed it is rendered via render() and scalar values are
- * returned directly.
- *
- * @param mixed $arg
- *   String, Object or Render Array.
- *
- * @return mixed
- *   The rendered output or an Twig_Markup object.
- *
- * @see render
- * @see TwigNodeVisitor
- */
-function twig_render_var($arg) {
-  // Check for a numeric zero int or float.
-  if ($arg === 0 || $arg === 0.0) {
-    return 0;
-  }
-
-  // Return early for NULL and also true for empty arrays.
-  if ($arg == NULL) {
-    return NULL;
-  }
-
-  // Optimize for strings as it is likely they come from the escape filter.
-  if (is_string($arg)) {
-    return $arg;
-  }
-
-  if (is_scalar($arg)) {
-    return $arg;
-  }
-
-  if (is_object($arg)) {
-    if (method_exists($arg, '__toString')) {
-      return (string) $arg;
-    }
-    // You can't throw exceptions in the magic PHP __toString methods, see
-    // http://php.net/manual/en/language.oop5.magic.php#object.tostring so
-    // we also support a toString method.
-    elseif (method_exists($arg, 'toString')) {
-      return $arg->toString();
-    }
-    else {
-      throw new Exception(t('Object of type "@class" cannot be printed.', array('@class' => get_class($arg))));
-    }
-  }
-
-  // This is a normal render array.
-  return render($arg);
-}
-
 /**
  * Removes child elements from a copy of the original array.
  *
@@ -198,79 +139,6 @@ function twig_without($element) {
   return $filtered_element;
 }
 
-/**
- * Overrides twig_escape_filter().
- *
- * Replacement function for Twig's escape filter.
- *
- * @param \Twig_Environment $env
- *   A Twig_Environment instance.
- * @param string $string
- *   The value to be escaped.
- * @param string $strategy
- *   The escaping strategy. Defaults to 'html'.
- * @param string $charset
- *   The charset.
- * @param bool $autoescape
- *   Whether the function is called by the auto-escaping feature (TRUE) or by
- *   the developer (FALSE).
- *
- * @return string|null
- *   The escaped, rendered output, or NULL if there is no valid output.
- */
-function twig_drupal_escape_filter(\Twig_Environment $env, $string, $strategy = 'html', $charset = NULL, $autoescape = FALSE) {
-  // Check for a numeric zero int or float.
-  if ($string === 0 || $string === 0.0) {
-    return 0;
-  }
-
-  // Return early for NULL or an empty array.
-  if ($string == NULL) {
-    return NULL;
-  }
-
-  // Keep Twig_Markup objects intact to support autoescaping.
-  if ($autoescape && $string instanceOf \Twig_Markup) {
-    return $string;
-  }
-
-  $return = NULL;
-
-  if (is_scalar($string)) {
-    $return = (string) $string;
-  }
-  elseif (is_object($string)) {
-    if (method_exists($string, '__toString')) {
-      $return = (string) $string;
-    }
-    // You can't throw exceptions in the magic PHP __toString methods, see
-    // http://php.net/manual/en/language.oop5.magic.php#object.tostring so
-    // we also support a toString method.
-    elseif (method_exists($string, 'toString')) {
-      $return = $string->toString();
-    }
-    else {
-      throw new \Exception(t('Object of type "@class" cannot be printed.', array('@class' => get_class($string))));
-    }
-  }
-
-  // We have a string or an object converted to a string: Autoescape it!
-  if (isset($return)) {
-    if ($autoescape && SafeMarkup::isSafe($return, $strategy)) {
-      return $return;
-    }
-    // Drupal only supports the HTML escaping strategy, so provide a
-    // fallback for other strategies.
-    if ($strategy == 'html') {
-      return SafeMarkup::checkPlain($return);
-    }
-    return twig_escape_filter($env, $return, $strategy, $charset, $autoescape);
-  }
-
-  // This is a normal render array, which is safe by definition.
-  return render($string);
-}
-
 /**
  * Overrides twig_join_filter().
  *