diff --git a/core/includes/common.inc b/core/includes/common.inc
index 0735dfa..53f8db7 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -30,6 +30,7 @@
 use Drupal\Core\Routing\GeneratorNotInitializedException;
 use Drupal\Core\Template\Attribute;
 use Drupal\Core\Render\Element;
+use Drupal\Core\Render\Render;
 use Drupal\Core\Session\AnonymousUserSession;
 
 /**
@@ -2198,21 +2199,6 @@ function drupal_merge_js_settings($settings_items) {
 }
 
 /**
- * Merges two #attached arrays.
- *
- * @param array $a
- *   An #attached array.
- * @param array $b
- *   Another #attached array.
- *
- * @return array
- *   The merged #attached array.
- */
-function drupal_merge_attached(array $a, array $b) {
-  return NestedArray::mergeDeep($a, $b);
-}
-
-/**
  * #pre_render callback to add the elements needed for JavaScript tags to be rendered.
  *
  * This function evaluates the aggregation enabled/disabled condition on a group
@@ -3229,7 +3215,7 @@ function drupal_pre_render_links($element) {
     }
     // Merge attachments.
     if (isset($child['#attached'])) {
-      $element['#attached'] = drupal_merge_attached($element['#attached'], $child['#attached']);
+      $element['#attached'] = Render::mergeAttached($element['#attached'], $child['#attached']);
     }
   }
   return $element;
@@ -3351,124 +3337,6 @@ function drupal_render_page($page) {
 /**
  * Renders HTML given a structured array tree.
  *
- * Renderable arrays have two kinds of key/value pairs: properties and children.
- * Properties have keys starting with '#' and their values influence how the
- * array will be rendered. Children are all elements whose keys do not start
- * with a '#'. Their values should be renderable arrays themselves, which will
- * be rendered during the rendering of the parent array. The markup provided by
- * the children is typically inserted into the markup generated by the parent
- * array.
- *
- * The process of rendering an element is recursive unless the element defines
- * an implemented theme hook in #theme. During each call to drupal_render(), the
- * outermost renderable array (also known as an "element") is processed using
- * the following steps:
- *   - If this element has already been printed (#printed = TRUE) or the user
- *     does not have access to it (#access = FALSE), then an empty string is
- *     returned.
- *   - If this element has #cache defined then the cached markup for this
- *     element will be returned if it exists in drupal_render()'s cache. To use
- *     drupal_render() caching, set the element's #cache property to an
- *     associative array with one or several of the following keys:
- *     - 'keys': An array of one or more keys that identify the element. If
- *       'keys' is set, the cache ID is created automatically from these keys.
- *       Cache keys may either be static (just strings) or tokens (placeholders
- *       that are converted to static keys by the @cache_contexts service,
- *       depending on the request). See drupal_render_cid_create().
- *     - 'cid': Specify the cache ID directly. Either 'keys' or 'cid' is
- *       required. If 'cid' is set, 'keys' is ignored. Use only if you have
- *       special requirements.
- *     - 'expire': Set to one of the cache lifetime constants.
- *     - 'bin': Specify a cache bin to cache the element in. Default is
- *       'default'.
- *   - If this element has #type defined and the default attributes for this
- *     element have not already been merged in (#defaults_loaded = TRUE) then
- *     the defaults for this type of element, defined in hook_element_info(),
- *     are merged into the array. #defaults_loaded is set by functions that
- *     process render arrays and call element_info() before passing the array to
- *     drupal_render(), such as form_builder() in the Form API.
- *   - If this element has an array of #pre_render functions defined, they are
- *     called sequentially to modify the element before rendering. After all the
- *     #pre_render functions have been called, #printed is checked a second time
- *     in case a #pre_render function flags the element as printed.
- *   - The child elements of this element are sorted by weight using uasort() in
- *     \Drupal\Core\Render\Element::children(). Since this is expensive, when
- *     passing already sorted elements to drupal_render(), for example from a
- *     database query, set $elements['#sorted'] = TRUE to avoid sorting them a
- *     second time.
- *   - The main render phase to produce #children for this element takes place:
- *     - If this element has #theme defined and #theme is an implemented theme
- *       hook/suggestion then _theme() is called and must render both the element
- *       and its children. If #render_children is set, _theme() will not be
- *       called. #render_children is usually only set internally by _theme() so
- *       that we can avoid the situation where drupal_render() called from
- *       within a theme preprocess function creates an infinite loop.
- *     - If this element does not have a defined #theme, or the defined #theme
- *       hook is not implemented, or #render_children is set, then
- *       drupal_render() is called recursively on each of the child elements of
- *       this element, and the result of each is concatenated onto #children.
- *       This is skipped if #children is not empty at this point.
- *     - Once #children has been rendered for this element, if #theme is not
- *       implemented and #markup is set for this element, #markup will be
- *       prepended to #children.
- *   - If this element has #states defined then JavaScript state information is
- *     added to this element's #attached attribute by drupal_process_states().
- *   - If this element has #attached defined then any required libraries,
- *     JavaScript, CSS, or other custom data are added to the current page by
- *     drupal_process_attached().
- *   - If this element has an array of #theme_wrappers defined and
- *     #render_children is not set, #children is then re-rendered by passing the
- *     element in its current state to _theme() successively for each item in
- *     #theme_wrappers. Since #theme and #theme_wrappers hooks often define
- *     variables with the same names it is possible to explicitly override each
- *     attribute passed to each #theme_wrappers hook by setting the hook name as
- *     the key and an array of overrides as the value in #theme_wrappers array.
- *     For example, if we have a render element as follows:
- *     @code
- *     array(
- *       '#theme' => 'image',
- *       '#attributes' => array('class' => 'foo'),
- *       '#theme_wrappers' => array('container'),
- *     );
- *     @endcode
- *     and we need to pass the class 'bar' as an attribute for 'container', we
- *     can rewrite our element thus:
- *     @code
- *     array(
- *       '#theme' => 'image',
- *       '#attributes' => array('class' => 'foo'),
- *       '#theme_wrappers' => array(
- *         'container' => array(
- *           '#attributes' => array('class' => 'bar'),
- *         ),
- *       ),
- *     );
- *     @endcode
- *   - If this element has an array of #post_render functions defined, they are
- *     called sequentially to modify the rendered #children. Unlike #pre_render
- *     functions, #post_render functions are passed both the rendered #children
- *     attribute as a string and the element itself.
- *   - If this element has #prefix and/or #suffix defined, they are concatenated
- *     to #children.
- *   - If this element has #cache defined, the rendered output of this element
- *     is saved to drupal_render()'s internal cache. This includes the changes
- *     made by #post_render.
- *   - If this element (or any of its children) has an array of
- *     #post_render_cache functions defined, they are called sequentially to
- *     replace placeholders in the final #markup and extend #attached.
- *     Placeholders must contain a unique token, to guarantee that e.g. samples
- *     of placeholders are not replaced also. For this, a special element named
- *     'render_cache_placeholder' is provided.
- *     Note that these callbacks run always: when hitting the render cache, when
- *     missing, or when render caching is not used at all. This is done to allow
- *     any Drupal module to customize other render arrays without breaking the
- *     render cache if it is enabled, and to not require it to use other logic
- *     when render caching is disabled.
- *   - #printed is set to TRUE for this element to ensure that it is only
- *     rendered once.
- *   - The final value of #children for this element is returned as the rendered
- *     output.
- *
  * @param array $elements
  *   The structured array describing the data to be rendered.
  * @param bool $is_recursive_call
@@ -3477,196 +3345,11 @@ function drupal_render_page($page) {
  * @return string
  *   The rendered HTML.
  *
- * @see element_info()
- * @see _theme()
- * @see drupal_process_states()
- * @see drupal_process_attached()
+ * @deprecated 8.x
+ *   Use \Drupal\Core\Render\Render::render().
  */
 function drupal_render(&$elements, $is_recursive_call = FALSE) {
-  // Early-return nothing if user does not have access.
-  if (empty($elements) || (isset($elements['#access']) && !$elements['#access'])) {
-    return '';
-  }
-
-  // Do not print elements twice.
-  if (!empty($elements['#printed'])) {
-    return '';
-  }
-
-  // Try to fetch the prerendered element from cache, run any #post_render_cache
-  // callbacks and return the final markup.
-  if (isset($elements['#cache'])) {
-    $cached_element = drupal_render_cache_get($elements);
-    if ($cached_element !== FALSE) {
-      $elements = $cached_element;
-      // Only when we're not in a recursive drupal_render() call,
-      // #post_render_cache callbacks must be executed, to prevent breaking the
-      // render cache in case of nested elements with #cache set.
-      if (!$is_recursive_call) {
-        _drupal_render_process_post_render_cache($elements);
-      }
-      return $elements['#markup'];
-    }
-  }
-
-  // If the default values for this element have not been loaded yet, populate
-  // them.
-  if (isset($elements['#type']) && empty($elements['#defaults_loaded'])) {
-    $elements += element_info($elements['#type']);
-  }
-
-  // Make any final changes to the element before it is rendered. This means
-  // that the $element or the children can be altered or corrected before the
-  // element is rendered into the final text.
-  if (isset($elements['#pre_render'])) {
-    foreach ($elements['#pre_render'] as $callable) {
-      $elements = call_user_func($callable, $elements);
-    }
-  }
-
-  // Allow #pre_render to abort rendering.
-  if (!empty($elements['#printed'])) {
-    return '';
-  }
-
-  // Add any JavaScript state information associated with the element.
-  if (!empty($elements['#states'])) {
-    drupal_process_states($elements);
-  }
-
-  // Add additional libraries, CSS, JavaScript and other custom
-  // attached data associated with this element.
-  if (!empty($elements['#attached'])) {
-    drupal_process_attached($elements);
-  }
-
-  // Get the children of the element, sorted by weight.
-  $children = Element::children($elements, TRUE);
-
-  // Initialize this element's #children, unless a #pre_render callback already
-  // preset #children.
-  if (!isset($elements['#children'])) {
-    $elements['#children'] = '';
-  }
-
-  // Assume that if #theme is set it represents an implemented hook.
-  $theme_is_implemented = isset($elements['#theme']);
-
-  // Call the element's #theme function if it is set. Then any children of the
-  // element have to be rendered there. If the internal #render_children
-  // property is set, do not call the #theme function to prevent infinite
-  // recursion.
-  if ($theme_is_implemented && !isset($elements['#render_children'])) {
-    $elements['#children'] = _theme($elements['#theme'], $elements);
-
-    // If _theme() returns FALSE this means that the hook in #theme was not
-    // found in the registry and so we need to update our flag accordingly. This
-    // is common for theme suggestions.
-    $theme_is_implemented = ($elements['#children'] !== FALSE);
-  }
-
-  // If #theme is not implemented or #render_children is set and the element has
-  // an empty #children attribute, render the children now. This is the same
-  // process as drupal_render_children() but is inlined for speed.
-  if ((!$theme_is_implemented || isset($elements['#render_children'])) && empty($elements['#children'])) {
-    foreach ($children as $key) {
-      $elements['#children'] .= drupal_render($elements[$key], TRUE);
-    }
-  }
-
-  // If #theme is not implemented and the element has raw #markup as a
-  // fallback, prepend the content in #markup to #children. In this case
-  // #children will contain whatever is provided by #pre_render prepended to
-  // what is rendered recursively above. If #theme is implemented then it is
-  // the responsibility of that theme implementation to render #markup if
-  // required. Eventually #theme_wrappers will expect both #markup and
-  // #children to be a single string as #children.
-  if (!$theme_is_implemented && isset($elements['#markup'])) {
-    $elements['#children'] = $elements['#markup'] . $elements['#children'];
-  }
-
-  // Let the theme functions in #theme_wrappers add markup around the rendered
-  // children.
-  // #states and #attached have to be processed before #theme_wrappers, because
-  // the #type 'page' render array from drupal_render_page() would render the
-  // $page and wrap it into the html.html.twig template without the attached
-  // assets otherwise.
-  // If the internal #render_children property is set, do not call the
-  // #theme_wrappers function(s) to prevent infinite recursion.
-  if (isset($elements['#theme_wrappers']) && !isset($elements['#render_children'])) {
-    foreach ($elements['#theme_wrappers'] as $key => $value) {
-      // If the value of a #theme_wrappers item is an array then the theme hook
-      // is found in the key of the item and the value contains attribute
-      // overrides. Attribute overrides replace key/value pairs in $elements for
-      // only this _theme() call. This allows #theme hooks and #theme_wrappers
-      // hooks to share variable names without conflict or ambiguity.
-      $wrapper_elements = $elements;
-      if (is_string($key)) {
-        $wrapper_hook = $key;
-        foreach ($value as $attribute => $override) {
-          $wrapper_elements[$attribute] = $override;
-        }
-      }
-      else {
-        $wrapper_hook = $value;
-      }
-
-      $elements['#children'] = _theme($wrapper_hook, $wrapper_elements);
-    }
-  }
-
-  // Filter the outputted content and make any last changes before the
-  // content is sent to the browser. The changes are made on $content
-  // which allows the output'ed text to be filtered.
-  if (isset($elements['#post_render'])) {
-    foreach ($elements['#post_render'] as $callable) {
-      $elements['#children'] = call_user_func($callable, $elements['#children'], $elements);
-    }
-  }
-
-  // We store the resulting output in $elements['#markup'], to be consistent
-  // with how render cached output gets stored. This ensures that
-  // #post_render_cache callbacks get the same data to work with, no matter if
-  // #cache is disabled, #cache is enabled, there is a cache hit or miss.
-  $prefix = isset($elements['#prefix']) ? $elements['#prefix'] : '';
-  $suffix = isset($elements['#suffix']) ? $elements['#suffix'] : '';
-  $elements['#markup'] = $prefix . $elements['#children'] . $suffix;
-
-  // Collect all #post_render_cache callbacks associated with this element when:
-  // - about to store this element in the render cache, or when;
-  // - about to apply #post_render_cache callbacks.
-  if (!$is_recursive_call || isset($elements['#cache'])) {
-    $post_render_cache = drupal_render_collect_post_render_cache($elements);
-    if ($post_render_cache) {
-      $elements['#post_render_cache'] = $post_render_cache;
-    }
-  }
-  // Collect all cache tags. This allows the caller of drupal_render() to also
-  // access the complete list of cache tags.
-  if (!$is_recursive_call || isset($elements['#cache'])) {
-    $elements['#cache']['tags'] = drupal_render_collect_cache_tags($elements);
-  }
-
-  // Cache the processed element if #cache is set.
-  if (isset($elements['#cache'])) {
-    drupal_render_cache_set($elements['#markup'], $elements);
-  }
-
-  // Only when we're not in a recursive drupal_render() call,
-  // #post_render_cache callbacks must be executed, to prevent breaking the
-  // render cache in case of nested elements with #cache set.
-  //
-  // By running them here, we ensure that:
-  // - they run when #cache is disabled,
-  // - they run when #cache is enabled and there is a cache miss.
-  // Only the case of a cache hit when #cache is enabled, is not handled here,
-  // that is handled earlier in drupal_render().
-  if (!$is_recursive_call) {
-    _drupal_render_process_post_render_cache($elements);
-  }
-
-  $elements['#printed'] = TRUE;
-  return $elements['#markup'];
+  return Render::render($elements, $is_recursive_call);
 }
 
 /**
@@ -3789,332 +3472,6 @@ function show(&$element) {
 }
 
 /**
- * Gets the cached, prerendered element of a renderable element from the cache.
- *
- * @param array $elements
- *   A renderable array.
- *
- * @return array
- *   A renderable array, with the original element and all its children pre-
- *   rendered, or FALSE if no cached copy of the element is available.
- *
- * @see drupal_render()
- * @see drupal_render_cache_set()
- */
-function drupal_render_cache_get(array $elements) {
-  if (!\Drupal::request()->isMethodSafe() || !$cid = drupal_render_cid_create($elements)) {
-    return FALSE;
-  }
-  $bin = isset($elements['#cache']['bin']) ? $elements['#cache']['bin'] : 'render';
-
-  if (!empty($cid) && $cache = \Drupal::cache($bin)->get($cid)) {
-    $cached_element = $cache->data;
-    // Add additional libraries, JavaScript, CSS and other data attached
-    // to this element.
-    if (isset($cached_element['#attached'])) {
-      drupal_process_attached($cached_element);
-    }
-    // Return the cached element.
-    return $cached_element;
-  }
-  return FALSE;
-}
-
-/**
- * Caches the rendered output of a renderable element.
- *
- * This is called by drupal_render() if the #cache property is set on an
- * element.
- *
- * @param $markup
- *   The rendered output string of $elements.
- * @param array $elements
- *   A renderable array.
- *
- * @see drupal_render_cache_get()
- */
-function drupal_render_cache_set(&$markup, array $elements) {
-  // Create the cache ID for the element.
-  if (!\Drupal::request()->isMethodSafe() || !$cid = drupal_render_cid_create($elements)) {
-    return FALSE;
-  }
-
-  // Cache implementations are allowed to modify the markup, to support
-  // replacing markup with edge-side include commands. The supporting cache
-  // backend will store the markup in some other key (like
-  // $data['#real-value']) and return an include command instead. When the
-  // ESI command is executed by the content accelerator, the real value can
-  // be retrieved and used.
-  $data['#markup'] = $markup;
-
-  // Persist attached data associated with this element.
-  $attached = drupal_render_collect_attached($elements, TRUE);
-  if ($attached) {
-    $data['#attached'] = $attached;
-  }
-
-  // Persist #post_render_cache callbacks associated with this element.
-  if (isset($elements['#post_render_cache'])) {
-    $data['#post_render_cache'] = $elements['#post_render_cache'];
-  }
-
-  // Persist cache tags associated with this element.
-  if (isset($elements['#cache']['tags'])) {
-    $data['#cache']['tags'] = $elements['#cache']['tags'];
-  }
-
-  $bin = isset($elements['#cache']['bin']) ? $elements['#cache']['bin'] : 'render';
-  $expire = isset($elements['#cache']['expire']) ? $elements['#cache']['expire'] : Cache::PERMANENT;
-  \Drupal::cache($bin)->set($cid, $data, $expire, $elements['#cache']['tags']);
-}
-
-/**
- * Generates a render cache placeholder.
- *
- * This is used by drupal_pre_render_render_cache_placeholder() to generate
- * placeholders, but should also be called by #post_render_cache callbacks that
- * want to replace the placeholder with the final markup.
- *
- * @param callable $callback
- *   The #post_render_cache callback that will replace the placeholder with its
- *   eventual markup.
- * @param array $context
- *   An array providing context for the #post_render_cache callback.
- * @param string $token
- *   A unique token to uniquely identify the placeholder.
- *
- * @return string
- *   The generated placeholder HTML.
- *
- * @see drupal_render_cache_get()
- */
-function drupal_render_cache_generate_placeholder($callback, array $context, $token) {
-  // Serialize the context into a HTML attribute; unserializing is unnecessary.
-  $context_attribute = '';
-  foreach ($context as $key => $value) {
-    $context_attribute .= $key . ':' . $value . ';';
-  }
-  return '<drupal:render-cache-placeholder callback="' . $callback . '" context="' . $context_attribute . '" token="' . $token . '" />';
-}
-
-/**
- * Generates a unique token for use in a #post_render_cache placeholder.
- */
-function drupal_render_cache_generate_token() {
-  return \Drupal\Component\Utility\Crypt::randomBytesBase64(55);
-}
-
-/**
- * Processes #post_render_cache callbacks.
- *
- * #post_render_cache callbacks may modify:
- * - #markup: to replace placeholders
- * - #attached: to add libraries or JavaScript settings
- *
- * Note that in either of these cases, #post_render_cache callbacks are
- * implicitly idempotent: a placeholder that has been replaced can't be replaced
- * again, and duplicate attachments are ignored.
- *
- * @param array &$elements
- *   The structured array describing the data being rendered.
- *
- * @see drupal_render()
- * @see drupal_render_collect_post_render_cache
- */
-function _drupal_render_process_post_render_cache(array &$elements) {
-  if (isset($elements['#post_render_cache'])) {
-    // Call all #post_render_cache callbacks, while passing the provided context
-    // and if keyed by a number, no token is passed, otherwise, the token string
-    // is passed to the callback as well. This token is used to uniquely
-    // identify the placeholder in the markup.
-    foreach ($elements['#post_render_cache'] as $callback => $options) {
-      foreach ($elements['#post_render_cache'][$callback] as $token => $context) {
-        $elements = call_user_func_array($callback, array($elements, $context));
-      }
-    }
-    // Make sure that any attachments added in #post_render_cache callbacks are
-    // also executed.
-    if (isset($elements['#attached'])) {
-      drupal_process_attached($elements);
-    }
-  }
-}
-
-/**
- * Collects #post_render_cache for an element and its children into a single
- * array.
- *
- * When caching elements, it is necessary to collect all #post_render_cache
- * callbacks into a single array, from both the element itself and all child
- * elements. This allows drupal_render() to execute all of them when the element
- * is retrieved from the render cache.
- *
- * Note: the theme system may render child elements directly (e.g. rendering a
- * node causes its template to be rendered, which causes the node links to be
- * drupal_render()ed). On top of that, the theme system transforms render arrays
- * into HTML strings. These two facts combined imply that it is impossible for
- * #post_render_cache callbacks to bubble up to the root of the render array.
- * Therefore, drupal_render_collect_post_render_cache() must be called *before*
- * #theme callbacks, so that it has a chance to examine the full render array.
- * In short: in order to examine the full render array for #post_render_cache
- * callbacks, it must use post-order tree traversal, whereas drupal_render()
- * itself uses pre-order tree traversal.
- *
- * @param array &$elements
- *   The element to collect #post_render_cache callbacks for.
- * @param array $callbacks
- *   Internal use only. The #post_render_callbacks array so far.
- * @param bool $is_root_element
- *   Internal use only. Whether the element being processed is the root or not.
- *
- * @return
- *   The #post_render_cache array for this element and its descendants.
- *
- * @see drupal_render()
- * @see _drupal_render_process_post_render_cache()
- */
-function drupal_render_collect_post_render_cache(array &$elements, array $callbacks = array(), $is_root_element = TRUE) {
-  // Try to fetch the prerendered element from cache, to determine
-  // #post_render_cache callbacks for this element and all its children. If we
-  // don't do this, then the #post_render_cache tokens will be re-generated, but
-  // they would no longer match the tokens in the render cached markup, causing
-  // the render cache placeholder markup to be sent to the end user!
-  $retrieved_from_cache = FALSE;
-  if (!$is_root_element && isset($elements['#cache'])) {
-    $cached_element = drupal_render_cache_get($elements);
-    if ($cached_element !== FALSE && isset($cached_element['#post_render_cache'])) {
-      $elements['#post_render_cache'] = $cached_element['#post_render_cache'];
-      $retrieved_from_cache = TRUE;
-    }
-  }
-
-  // Collect all #post_render_cache callbacks for this element.
-  if (isset($elements['#post_render_cache'])) {
-    $callbacks = NestedArray::mergeDeep($callbacks, $elements['#post_render_cache']);
-  }
-
-  // Collect the #post_render_cache callbacks for all child elements, unless
-  // we've already collected them above by retrieving this element (and its
-  // children) from the render cache.
-  if (!$retrieved_from_cache && $children = Element::children($elements)) {
-    foreach ($children as $child) {
-      $callbacks = drupal_render_collect_post_render_cache($elements[$child], $callbacks, FALSE);
-    }
-  }
-
-  return $callbacks;
-}
-
-/**
- * Collects #attached for an element and its children into a single array.
- *
- * When caching elements, it is necessary to collect all libraries, JavaScript
- * and CSS into a single array, from both the element itself and all child
- * elements. This allows drupal_render() to add these back to the page when the
- * element is returned from cache.
- *
- * @param $elements
- *   The element to collect #attached from.
- * @param $return
- *   Whether to return the attached elements and reset the internal static.
- *
- * @return
- *   The #attached array for this element and its descendants.
- */
-function drupal_render_collect_attached($elements, $return = FALSE) {
-  $attached = &drupal_static(__FUNCTION__, array());
-
-  // Collect all #attached for this element.
-  if (isset($elements['#attached'])) {
-    $attached = drupal_merge_attached($attached, $elements['#attached']);
-  }
-  if ($children = Element::children($elements)) {
-    foreach ($children as $child) {
-      drupal_render_collect_attached($elements[$child]);
-    }
-  }
-
-  // If this was the first call to the function, return all attached elements
-  // and reset the static cache.
-  if ($return) {
-    $return = $attached;
-    $attached = array();
-    return $return;
-  }
-}
-
-/**
- * Collects cache tags for an element and its children into a single array.
- *
- * The cache tags array is returned in a format that is valid for
- * \Drupal\Core\Cache\CacheBackendInterface::set().
- *
- * When caching elements, it is necessary to collect all cache tags into a
- * single array, from both the element itself and all child elements. This
- * allows items to be invalidated based on all tags attached to the content
- * they're constituted from.
- *
- * @param array $element
- *   The element to collect cache tags from.
- * @param array $tags
- *   (optional) An array of already collected cache tags (i.e. from a parent
- *   element). Defaults to an empty array.
- *
- * @return array
- *   The cache tags array for this element and its descendants.
- */
-function drupal_render_collect_cache_tags($element, $tags = array()) {
-  if (isset($element['#cache']['tags'])) {
-    foreach ($element['#cache']['tags'] as $namespace => $values) {
-      if (is_array($values)) {
-        foreach ($values as $value) {
-          $tags[$namespace][$value] = $value;
-        }
-      }
-      else {
-        if (!isset($tags[$namespace])) {
-          $tags[$namespace] = $values;
-        }
-      }
-    }
-  }
-  if ($children = Element::children($element)) {
-    foreach ($children as $child) {
-      $tags = drupal_render_collect_cache_tags($element[$child], $tags);
-    }
-  }
-
-  return $tags;
-}
-
-/**
- * Creates the cache ID for a renderable element.
- *
- * This creates the cache ID string, either by returning the #cache['cid']
- * property if present or by building the cache ID out of the #cache['keys'].
- *
- * @param $elements
- *   A renderable array.
- *
- * @return
- *   The cache ID string, or FALSE if the element may not be cached.
- */
-function drupal_render_cid_create($elements) {
-  if (isset($elements['#cache']['cid'])) {
-    return $elements['#cache']['cid'];
-  }
-  elseif (isset($elements['#cache']['keys'])) {
-    // Cache keys may either be static (just strings) or tokens (placeholders
-    // that are converted to static keys by the @cache_contexts service,
-    // depending on the request).
-    $cache_contexts = \Drupal::service("cache_contexts");
-    $keys = $cache_contexts->convertTokensToKeys($elements['#cache']['keys']);
-    return implode(':', $keys);
-  }
-  return FALSE;
-}
-
-/**
  * Retrieves the default properties for the defined element type.
  *
  * @param $type
diff --git a/core/lib/Drupal/Core/Render/Render.php b/core/lib/Drupal/Core/Render/Render.php
new file mode 100644
index 0000000..5ef403a
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Render.php
@@ -0,0 +1,671 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Render.
+ */
+
+namespace Drupal\Core\Render;
+
+use Drupal\Component\Utility\Crypt;
+use Drupal\Component\Utility\NestedArray;
+use Drupal\Core\Cache\Cache;
+
+/**
+ * Renders a render array.
+ */
+class Render {
+
+  /**
+   * Renders HTML given a structured array tree.
+   *
+   * Renderable arrays have two kinds of key/value pairs: properties and children.
+   * Properties have keys starting with '#' and their values influence how the
+   * array will be rendered. Children are all elements whose keys do not start
+   * with a '#'. Their values should be renderable arrays themselves, which will
+   * be rendered during the rendering of the parent array. The markup provided by
+   * the children is typically inserted into the markup generated by the parent
+   * array.
+   *
+   * The process of rendering an element is recursive unless the element defines
+   * an implemented theme hook in #theme. During each call to drupal_render(), the
+   * outermost renderable array (also known as an "element") is processed using
+   * the following steps:
+   *   - If this element has already been printed (#printed = TRUE) or the user
+   *     does not have access to it (#access = FALSE), then an empty string is
+   *     returned.
+   *   - If this element has #cache defined then the cached markup for this
+   *     element will be returned if it exists in drupal_render()'s cache. To use
+   *     drupal_render() caching, set the element's #cache property to an
+   *     associative array with one or several of the following keys:
+   *     - 'keys': An array of one or more keys that identify the element. If
+   *       'keys' is set, the cache ID is created automatically from these keys.
+   *       Cache keys may either be static (just strings) or tokens (placeholders
+   *       that are converted to static keys by the @cache_contexts service,
+   *       depending on the request). See Render::generateCacheId().
+   *     - 'cid': Specify the cache ID directly. Either 'keys' or 'cid' is
+   *       required. If 'cid' is set, 'keys' is ignored. Use only if you have
+   *       special requirements.
+   *     - 'expire': Set to one of the cache lifetime constants.
+   *     - 'bin': Specify a cache bin to cache the element in. Default is
+   *       'default'.
+   *   - If this element has #type defined and the default attributes for this
+   *     element have not already been merged in (#defaults_loaded = TRUE) then
+   *     the defaults for this type of element, defined in hook_element_info(),
+   *     are merged into the array. #defaults_loaded is set by functions that
+   *     process render arrays and call element_info() before passing the array to
+   *     drupal_render(), such as form_builder() in the Form API.
+   *   - If this element has an array of #pre_render functions defined, they are
+   *     called sequentially to modify the element before rendering. After all the
+   *     #pre_render functions have been called, #printed is checked a second time
+   *     in case a #pre_render function flags the element as printed.
+   *   - The child elements of this element are sorted by weight using uasort() in
+   *     \Drupal\Core\Render\Element::children(). Since this is expensive, when
+   *     passing already sorted elements to drupal_render(), for example from a
+   *     database query, set $elements['#sorted'] = TRUE to avoid sorting them a
+   *     second time.
+   *   - The main render phase to produce #children for this element takes place:
+   *     - If this element has #theme defined and #theme is an implemented theme
+   *       hook/suggestion then _theme() is called and must render both the element
+   *       and its children. If #render_children is set, _theme() will not be
+   *       called. #render_children is usually only set internally by _theme() so
+   *       that we can avoid the situation where drupal_render() called from
+   *       within a theme preprocess function creates an infinite loop.
+   *     - If this element does not have a defined #theme, or the defined #theme
+   *       hook is not implemented, or #render_children is set, then
+   *       drupal_render() is called recursively on each of the child elements of
+   *       this element, and the result of each is concatenated onto #children.
+   *       This is skipped if #children is not empty at this point.
+   *     - Once #children has been rendered for this element, if #theme is not
+   *       implemented and #markup is set for this element, #markup will be
+   *       prepended to #children.
+   *   - If this element has #states defined then JavaScript state information is
+   *     added to this element's #attached attribute by drupal_process_states().
+   *   - If this element has #attached defined then any required libraries,
+   *     JavaScript, CSS, or other custom data are added to the current page by
+   *     drupal_process_attached().
+   *   - If this element has an array of #theme_wrappers defined and
+   *     #render_children is not set, #children is then re-rendered by passing the
+   *     element in its current state to _theme() successively for each item in
+   *     #theme_wrappers. Since #theme and #theme_wrappers hooks often define
+   *     variables with the same names it is possible to explicitly override each
+   *     attribute passed to each #theme_wrappers hook by setting the hook name as
+   *     the key and an array of overrides as the value in #theme_wrappers array.
+   *     For example, if we have a render element as follows:
+   *     @code
+   *     array(
+   *       '#theme' => 'image',
+   *       '#attributes' => array('class' => 'foo'),
+   *       '#theme_wrappers' => array('container'),
+   *     );
+   *     @endcode
+   *     and we need to pass the class 'bar' as an attribute for 'container', we
+   *     can rewrite our element thus:
+   *     @code
+   *     array(
+   *       '#theme' => 'image',
+   *       '#attributes' => array('class' => 'foo'),
+   *       '#theme_wrappers' => array(
+   *         'container' => array(
+   *           '#attributes' => array('class' => 'bar'),
+   *         ),
+   *       ),
+   *     );
+   *     @endcode
+   *   - If this element has an array of #post_render functions defined, they are
+   *     called sequentially to modify the rendered #children. Unlike #pre_render
+   *     functions, #post_render functions are passed both the rendered #children
+   *     attribute as a string and the element itself.
+   *   - If this element has #prefix and/or #suffix defined, they are concatenated
+   *     to #children.
+   *   - If this element has #cache defined, the rendered output of this element
+   *     is saved to drupal_render()'s internal cache. This includes the changes
+   *     made by #post_render.
+   *   - If this element (or any of its children) has an array of
+   *     #post_render_cache functions defined, they are called sequentially to
+   *     replace placeholders in the final #markup and extend #attached.
+   *     Placeholders must contain a unique token, to guarantee that e.g. samples
+   *     of placeholders are not replaced also. For this, a special element named
+   *     'render_cache_placeholder' is provided.
+   *     Note that these callbacks run always: when hitting the render cache, when
+   *     missing, or when render caching is not used at all. This is done to allow
+   *     any Drupal module to customize other render arrays without breaking the
+   *     render cache if it is enabled, and to not require it to use other logic
+   *     when render caching is disabled.
+   *   - #printed is set to TRUE for this element to ensure that it is only
+   *     rendered once.
+   *   - The final value of #children for this element is returned as the rendered
+   *     output.
+   *
+   * @param array $elements
+   *   The structured array describing the data to be rendered.
+   * @param bool $is_recursive_call
+   *   Whether this is a recursive call or not, for internal use.
+   *
+   * @return string
+   *   The rendered HTML.
+   *
+   * @see element_info()
+   * @see _theme()
+   * @see drupal_process_states()
+   * @see drupal_process_attached()
+   */
+  public static function render(&$elements, $is_recursive_call = FALSE) {
+    // Early-return nothing if user does not have access.
+    if (empty($elements) || (isset($elements['#access']) && !$elements['#access'])) {
+      return '';
+    }
+
+    // Do not print elements twice.
+    if (!empty($elements['#printed'])) {
+      return '';
+    }
+
+    // Try to fetch the prerendered element from cache, run any #post_render_cache
+    // callbacks and return the final markup.
+    if (isset($elements['#cache'])) {
+      $cached_element = static::getCache($elements);
+      if ($cached_element !== FALSE) {
+        $elements = $cached_element;
+        // Only when we're not in a recursive drupal_render() call,
+        // #post_render_cache callbacks must be executed, to prevent breaking the
+        // render cache in case of nested elements with #cache set.
+        if (!$is_recursive_call) {
+          static::processPostRenderCache($elements);
+        }
+        return $elements['#markup'];
+      }
+    }
+
+    // If the default values for this element have not been loaded yet, populate
+    // them.
+    if (isset($elements['#type']) && empty($elements['#defaults_loaded'])) {
+      $elements += element_info($elements['#type']);
+    }
+
+    // Make any final changes to the element before it is rendered. This means
+    // that the $element or the children can be altered or corrected before the
+    // element is rendered into the final text.
+    if (isset($elements['#pre_render'])) {
+      foreach ($elements['#pre_render'] as $callable) {
+        $elements = call_user_func($callable, $elements);
+      }
+    }
+
+    // Allow #pre_render to abort rendering.
+    if (!empty($elements['#printed'])) {
+      return '';
+    }
+
+    // Add any JavaScript state information associated with the element.
+    if (!empty($elements['#states'])) {
+      drupal_process_states($elements);
+    }
+
+    // Add additional libraries, CSS, JavaScript and other custom
+    // attached data associated with this element.
+    if (!empty($elements['#attached'])) {
+      drupal_process_attached($elements);
+    }
+
+    // Get the children of the element, sorted by weight.
+    $children = Element::children($elements, TRUE);
+
+    // Initialize this element's #children, unless a #pre_render callback already
+    // preset #children.
+    if (!isset($elements['#children'])) {
+      $elements['#children'] = '';
+    }
+
+    // Assume that if #theme is set it represents an implemented hook.
+    $theme_is_implemented = isset($elements['#theme']);
+
+    // Call the element's #theme function if it is set. Then any children of the
+    // element have to be rendered there. If the internal #render_children
+    // property is set, do not call the #theme function to prevent infinite
+    // recursion.
+    if ($theme_is_implemented && !isset($elements['#render_children'])) {
+      $elements['#children'] = _theme($elements['#theme'], $elements);
+
+      // If _theme() returns FALSE this means that the hook in #theme was not
+      // found in the registry and so we need to update our flag accordingly. This
+      // is common for theme suggestions.
+      $theme_is_implemented = ($elements['#children'] !== FALSE);
+    }
+
+    // If #theme is not implemented or #render_children is set and the element has
+    // an empty #children attribute, render the children now. This is the same
+    // process as drupal_render_children() but is inlined for speed.
+    if ((!$theme_is_implemented || isset($elements['#render_children'])) && empty($elements['#children'])) {
+      foreach ($children as $key) {
+        $elements['#children'] .= static::render($elements[$key], TRUE);
+      }
+    }
+
+    // If #theme is not implemented and the element has raw #markup as a
+    // fallback, prepend the content in #markup to #children. In this case
+    // #children will contain whatever is provided by #pre_render prepended to
+    // what is rendered recursively above. If #theme is implemented then it is
+    // the responsibility of that theme implementation to render #markup if
+    // required. Eventually #theme_wrappers will expect both #markup and
+    // #children to be a single string as #children.
+    if (!$theme_is_implemented && isset($elements['#markup'])) {
+      $elements['#children'] = $elements['#markup'] . $elements['#children'];
+    }
+
+    // Let the theme functions in #theme_wrappers add markup around the rendered
+    // children.
+    // #states and #attached have to be processed before #theme_wrappers, because
+    // the #type 'page' render array from drupal_render_page() would render the
+    // $page and wrap it into the html.html.twig template without the attached
+    // assets otherwise.
+    // If the internal #render_children property is set, do not call the
+    // #theme_wrappers function(s) to prevent infinite recursion.
+    if (isset($elements['#theme_wrappers']) && !isset($elements['#render_children'])) {
+      foreach ($elements['#theme_wrappers'] as $key => $value) {
+        // If the value of a #theme_wrappers item is an array then the theme hook
+        // is found in the key of the item and the value contains attribute
+        // overrides. Attribute overrides replace key/value pairs in $elements for
+        // only this _theme() call. This allows #theme hooks and #theme_wrappers
+        // hooks to share variable names without conflict or ambiguity.
+        $wrapper_elements = $elements;
+        if (is_string($key)) {
+          $wrapper_hook = $key;
+          foreach ($value as $attribute => $override) {
+            $wrapper_elements[$attribute] = $override;
+          }
+        }
+        else {
+          $wrapper_hook = $value;
+        }
+
+        $elements['#children'] = _theme($wrapper_hook, $wrapper_elements);
+      }
+    }
+
+    // Filter the outputted content and make any last changes before the
+    // content is sent to the browser. The changes are made on $content
+    // which allows the output'ed text to be filtered.
+    if (isset($elements['#post_render'])) {
+      foreach ($elements['#post_render'] as $callable) {
+        $elements['#children'] = call_user_func($callable, $elements['#children'], $elements);
+      }
+    }
+
+    // We store the resulting output in $elements['#markup'], to be consistent
+    // with how render cached output gets stored. This ensures that
+    // #post_render_cache callbacks get the same data to work with, no matter if
+    // #cache is disabled, #cache is enabled, there is a cache hit or miss.
+    $prefix = isset($elements['#prefix']) ? $elements['#prefix'] : '';
+    $suffix = isset($elements['#suffix']) ? $elements['#suffix'] : '';
+    $elements['#markup'] = $prefix . $elements['#children'] . $suffix;
+
+    // Collect all #post_render_cache callbacks associated with this element when:
+    // - about to store this element in the render cache, or when;
+    // - about to apply #post_render_cache callbacks.
+    if (!$is_recursive_call || isset($elements['#cache'])) {
+      $post_render_cache = static::collectPostRenderCache($elements);
+      if ($post_render_cache) {
+        $elements['#post_render_cache'] = $post_render_cache;
+      }
+    }
+
+    // Collect all cache tags. This allows the caller of drupal_render() to also
+    // access the complete list of cache tags.
+    if (!$is_recursive_call || isset($elements['#cache'])) {
+      $elements['#cache']['tags'] = static::collectCacheTags($elements);
+    }
+
+    // Cache the processed element if #cache is set.
+    if (isset($elements['#cache'])) {
+      static::setCache($elements['#markup'], $elements);
+    }
+
+    // Only when we're not in a recursive drupal_render() call,
+    // #post_render_cache callbacks must be executed, to prevent breaking the
+    // render cache in case of nested elements with #cache set.
+    //
+    // By running them here, we ensure that:
+    // - they run when #cache is disabled,
+    // - they run when #cache is enabled and there is a cache miss.
+    // Only the case of a cache hit when #cache is enabled, is not handled here,
+    // that is handled earlier in drupal_render().
+    if (!$is_recursive_call) {
+      static::processPostRenderCache($elements);
+    }
+
+    $elements['#printed'] = TRUE;
+    return $elements['#markup'];
+  }
+
+  /**
+   * Gets the cached, prerendered element of a renderable element from the cache.
+   *
+   * @param array $elements
+   *   A renderable array.
+   *
+   * @return array
+   *   A renderable array, with the original element and all its children pre-
+   *   rendered, or FALSE if no cached copy of the element is available.
+   */
+  protected static function getCache(array $elements) {
+    if (!\Drupal::request()->isMethodSafe() || !$cid = static::generateCacheId($elements)) {
+      return FALSE;
+    }
+    $bin = isset($elements['#cache']['bin']) ? $elements['#cache']['bin'] : 'render';
+
+    if (!empty($cid) && $cache = \Drupal::cache($bin)->get($cid)) {
+      $cached_element = $cache->data;
+      // Add additional libraries, JavaScript, CSS and other data attached
+      // to this element.
+      if (isset($cached_element['#attached'])) {
+        drupal_process_attached($cached_element);
+      }
+      // Return the cached element.
+      return $cached_element;
+    }
+    return FALSE;
+  }
+
+  /**
+   * Caches the rendered output of a renderable element.
+   *
+   * This is called by drupal_render() if the #cache property is set on an
+   * element.
+   *
+   * @param $markup
+   *   The rendered output string of $elements.
+   * @param array $elements
+   *   A renderable array.
+   */
+  protected static function setCache(&$markup, array $elements) {
+    // Create the cache ID for the element.
+    if (!\Drupal::request()->isMethodSafe() || !$cid = static::generateCacheId($elements)) {
+      return FALSE;
+    }
+
+    // Cache implementations are allowed to modify the markup, to support
+    // replacing markup with edge-side include commands. The supporting cache
+    // backend will store the markup in some other key (like
+    // $data['#real-value']) and return an include command instead. When the
+    // ESI command is executed by the content accelerator, the real value can
+    // be retrieved and used.
+    $data['#markup'] = $markup;
+
+    // Persist attached data associated with this element.
+    $attached = static::collectAttached($elements, TRUE);
+    if ($attached) {
+      $data['#attached'] = $attached;
+    }
+
+    // Persist #post_render_cache callbacks associated with this element.
+    if (isset($elements['#post_render_cache'])) {
+      $data['#post_render_cache'] = $elements['#post_render_cache'];
+    }
+
+    // Persist cache tags associated with this element.
+    if (isset($elements['#cache']['tags'])) {
+      $data['#cache']['tags'] = $elements['#cache']['tags'];
+    }
+
+    $bin = isset($elements['#cache']['bin']) ? $elements['#cache']['bin'] : 'render';
+    $expire = isset($elements['#cache']['expire']) ? $elements['#cache']['expire'] : Cache::PERMANENT;
+    \Drupal::cache($bin)->set($cid, $data, $expire, $elements['#cache']['tags']);
+  }
+
+  /**
+   * Generates a render cache placeholder.
+   *
+   * This is used by Render::preRenderCachePlaceholder() to generate
+   * placeholders, but should also be called by #post_render_cache callbacks that
+   * want to replace the placeholder with the final markup.
+   *
+   * @param callable $callback
+   *   The #post_render_cache callback that will replace the placeholder with its
+   *   eventual markup.
+   * @param array $context
+   *   An array providing context for the #post_render_cache callback.
+   * @param string $token
+   *   A unique token to uniquely identify the placeholder.
+   *
+   * @return string
+   *   The generated placeholder HTML.
+   */
+  public static function generateCachePlaceholder($callback, array $context, $token) {
+    // Serialize the context into a HTML attribute; unserializing is unnecessary.
+    $context_attribute = '';
+    foreach ($context as $key => $value) {
+      $context_attribute .= $key . ':' . $value . ';';
+    }
+    return '<drupal:render-cache-placeholder callback="' . $callback . '" context="' . $context_attribute . '" token="' . $token . '" />';
+  }
+
+  /**
+   * Generates a unique token for use in a #post_render_cache placeholder.
+   *
+   * @return string
+   */
+  public static function generateCacheToken() {
+    return Crypt::randomBytesBase64(55);
+  }
+
+  /**
+   * Processes #post_render_cache callbacks.
+   *
+   * #post_render_cache callbacks may modify:
+   * - #markup: to replace placeholders
+   * - #attached: to add libraries or JavaScript settings
+   *
+   * Note that in either of these cases, #post_render_cache callbacks are
+   * implicitly idempotent: a placeholder that has been replaced can't be replaced
+   * again, and duplicate attachments are ignored.
+   *
+   * @param array &$elements
+   *   The structured array describing the data being rendered.
+   */
+  protected static function processPostRenderCache(array &$elements) {
+    if (isset($elements['#post_render_cache'])) {
+      // Call all #post_render_cache callbacks, while passing the provided context
+      // and if keyed by a number, no token is passed, otherwise, the token string
+      // is passed to the callback as well. This token is used to uniquely
+      // identify the placeholder in the markup.
+      foreach ($elements['#post_render_cache'] as $callback => $options) {
+        foreach ($elements['#post_render_cache'][$callback] as $token => $context) {
+          $elements = call_user_func_array($callback, array($elements, $context));
+        }
+      }
+      // Make sure that any attachments added in #post_render_cache callbacks are
+      // also executed.
+      if (isset($elements['#attached'])) {
+        drupal_process_attached($elements);
+      }
+    }
+  }
+
+  /**
+   * Collects #post_render_cache for an element and its children into a single
+   * array.
+   *
+   * When caching elements, it is necessary to collect all #post_render_cache
+   * callbacks into a single array, from both the element itself and all child
+   * elements. This allows drupal_render() to execute all of them when the element
+   * is retrieved from the render cache.
+   *
+   * Note: the theme system may render child elements directly (e.g. rendering a
+   * node causes its template to be rendered, which causes the node links to be
+   * drupal_render()ed). On top of that, the theme system transforms render arrays
+   * into HTML strings. These two facts combined imply that it is impossible for
+   * #post_render_cache callbacks to bubble up to the root of the render array.
+   * Therefore, Render::collectPostRenderCache() must be called *before*
+   * #theme callbacks, so that it has a chance to examine the full render array.
+   * In short: in order to examine the full render array for #post_render_cache
+   * callbacks, it must use post-order tree traversal, whereas drupal_render()
+   * itself uses pre-order tree traversal.
+   *
+   * @param array &$elements
+   *   The element to collect #post_render_cache callbacks for.
+   * @param array $callbacks
+   *   Internal use only. The #post_render_callbacks array so far.
+   * @param bool $is_root_element
+   *   Internal use only. Whether the element being processed is the root or not.
+   *
+   * @return
+   *   The #post_render_cache array for this element and its descendants.
+   */
+  protected static function collectPostRenderCache(array &$elements, array $callbacks = array(), $is_root_element = TRUE) {
+    // Try to fetch the prerendered element from cache, to determine
+    // #post_render_cache callbacks for this element and all its children. If we
+    // don't do this, then the #post_render_cache tokens will be re-generated, but
+    // they would no longer match the tokens in the render cached markup, causing
+    // the render cache placeholder markup to be sent to the end user!
+    $retrieved_from_cache = FALSE;
+    if (!$is_root_element && isset($elements['#cache'])) {
+      $cached_element = static::getCache($elements);
+      if ($cached_element !== FALSE && isset($cached_element['#post_render_cache'])) {
+        $elements['#post_render_cache'] = $cached_element['#post_render_cache'];
+        $retrieved_from_cache = TRUE;
+      }
+    }
+
+    // Collect all #post_render_cache callbacks for this element.
+    if (isset($elements['#post_render_cache'])) {
+      $callbacks = NestedArray::mergeDeep($callbacks, $elements['#post_render_cache']);
+    }
+
+    // Collect the #post_render_cache callbacks for all child elements, unless
+    // we've already collected them above by retrieving this element (and its
+    // children) from the render cache.
+    if (!$retrieved_from_cache && $children = Element::children($elements)) {
+      foreach ($children as $child) {
+        $callbacks = static::collectPostRenderCache($elements[$child], $callbacks, FALSE);
+      }
+    }
+
+    return $callbacks;
+  }
+
+  /**
+   * Collects #attached for an element and its children into a single array.
+   *
+   * When caching elements, it is necessary to collect all libraries, JavaScript
+   * and CSS into a single array, from both the element itself and all child
+   * elements. This allows drupal_render() to add these back to the page when the
+   * element is returned from cache.
+   *
+   * @param array $elements
+   *   The element to collect #attached from.
+   * @param bool $return
+   *   Whether to return the attached elements and reset the internal static.
+   *
+   * @return array|null
+   *   The #attached array for this element and its descendants.
+   */
+  public static function collectAttached(array $elements, $return = FALSE) {
+    $attached = &drupal_static(__FUNCTION__, array());
+
+    // Collect all #attached for this element.
+    if (isset($elements['#attached'])) {
+      $attached = static::mergeAttached($attached, $elements['#attached']);
+    }
+    if ($children = Element::children($elements)) {
+      foreach ($children as $child) {
+        static::collectAttached($elements[$child]);
+      }
+    }
+    // If this was the first call to the function, return all attached elements
+    // and reset the static cache.
+    if ($return) {
+      $return = $attached;
+      $attached = array();
+      return $return;
+    }
+  }
+
+  /**
+   * Collects cache tags for an element and its children into a single array.
+   *
+   * The cache tags array is returned in a format that is valid for
+   * \Drupal\Core\Cache\CacheBackendInterface::set().
+   *
+   * When caching elements, it is necessary to collect all cache tags into a
+   * single array, from both the element itself and all child elements. This
+   * allows items to be invalidated based on all tags attached to the content
+   * they're constituted from.
+   *
+   * @param array $element
+   *   The element to collect cache tags from.
+   * @param array $tags
+   *   (optional) An array of already collected cache tags (i.e. from a parent
+   *   element). Defaults to an empty array.
+   *
+   * @return array
+   *   The cache tags array for this element and its descendants.
+   *
+   * @todo Only public for testing purposes. Fix tests.
+   */
+  public static function collectCacheTags($element, $tags = array()) {
+    if (isset($element['#cache']['tags'])) {
+      foreach ($element['#cache']['tags'] as $namespace => $values) {
+        if (is_array($values)) {
+          foreach ($values as $value) {
+            $tags[$namespace][$value] = $value;
+          }
+        }
+        else {
+          if (!isset($tags[$namespace])) {
+            $tags[$namespace] = $values;
+          }
+        }
+      }
+    }
+    if ($children = Element::children($element)) {
+      foreach ($children as $child) {
+        $tags = static::collectCacheTags($element[$child], $tags);
+      }
+    }
+    return $tags;
+  }
+
+  /**
+   * Creates the cache ID for a renderable element.
+   *
+   * This creates the cache ID string, either by returning the #cache['cid']
+   * property if present or by building the cache ID out of the #cache['keys'].
+   *
+   * @param array $elements
+   *   A renderable array.
+   *
+   * @return string|false
+   *   The cache ID string, or FALSE if the element may not be cached.
+   */
+  public static function generateCacheId(array $elements) {
+    if (isset($elements['#cache']['cid'])) {
+      return $elements['#cache']['cid'];
+    }
+    elseif (isset($elements['#cache']['keys'])) {
+      // Cache keys may either be static (just strings) or tokens (placeholders
+      // that are converted to static keys by the @cache_contexts service,
+      // depending on the request).
+      $cache_contexts = \Drupal::service("cache_contexts");
+      $keys = $cache_contexts->convertTokensToKeys($elements['#cache']['keys']);
+      return implode(':', $keys);
+    }
+    return FALSE;
+  }
+
+  /**
+   * Merges two #attached arrays.
+   *
+   * @param array $a
+   *   An #attached array.
+   * @param array $b
+   *   Another #attached array.
+   *
+   * @return array
+   *   The merged #attached array.
+   */
+  public static function mergeAttached(array $a, array $b) {
+    return NestedArray::mergeDeep($a, $b);
+  }
+
+}
diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockViewBuilderTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockViewBuilderTest.php
index feb5471..1e99c39 100644
--- a/core/modules/block/lib/Drupal/block/Tests/BlockViewBuilderTest.php
+++ b/core/modules/block/lib/Drupal/block/Tests/BlockViewBuilderTest.php
@@ -9,6 +9,7 @@
 
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Cache\UrlCacheContext;
+use Drupal\Core\Render\Render;
 use Drupal\simpletest\DrupalUnitTestBase;
 use Symfony\Component\HttpFoundation\Request;
 
@@ -171,7 +172,7 @@ protected function verifyRenderCacheHandling() {
 
     // Test that a cache entry is created.
     $build = $this->getBlockRenderArray();
-    $cid = drupal_render_cid_create($build);
+    $cid = Render::generateCacheId($build);
     drupal_render($build);
     $this->assertTrue($this->container->get('cache.render')->get($cid), 'The block render element has been cached.');
 
@@ -232,7 +233,7 @@ public function testBlockViewBuilderAlter() {
     $expected_keys = array_merge($default_keys, array($alter_add_key));
     $build = $this->getBlockRenderArray();
     $this->assertIdentical($expected_keys, $build['#cache']['keys'], 'An altered cacheable block has the expected cache keys.');
-    $cid = drupal_render_cid_create(array('#cache' => array('keys' => $expected_keys)));
+    $cid = Render::generateCacheId(array('#cache' => array('keys' => $expected_keys)));
     $this->assertIdentical(drupal_render($build), '');
     $cache_entry = $this->container->get('cache.render')->get($cid);
     $this->assertTrue($cache_entry, 'The block render element has been cached with the expected cache ID.');
@@ -246,7 +247,7 @@ public function testBlockViewBuilderAlter() {
     $expected_tags = NestedArray::mergeDeep($default_tags, array($alter_add_tag => TRUE));
     $build = $this->getBlockRenderArray();
     $this->assertIdentical($expected_tags, $build['#cache']['tags'], 'An altered cacheable block has the expected cache tags.');
-    $cid = drupal_render_cid_create(array('#cache' => array('keys' => $expected_keys)));
+    $cid = Render::generateCacheId(array('#cache' => array('keys' => $expected_keys)));
     $this->assertIdentical(drupal_render($build), '');
     $cache_entry = $this->container->get('cache.render')->get($cid);
     $this->assertTrue($cache_entry, 'The block render element has been cached with the expected cache ID.');
@@ -284,7 +285,7 @@ public function testBlockViewBuilderCacheContexts() {
       'max_age' => 600,
     ));
     $build = $this->getBlockRenderArray();
-    $cid = drupal_render_cid_create($build);
+    $cid = Render::generateCacheId($build);
     drupal_render($build);
     $this->assertTrue($this->container->get('cache.render', $cid), 'The block render element has been cached.');
 
@@ -295,7 +296,7 @@ public function testBlockViewBuilderCacheContexts() {
     ));
     $old_cid = $cid;
     $build = $this->getBlockRenderArray();
-    $cid = drupal_render_cid_create($build);
+    $cid = Render::generateCacheId($build);
     drupal_render($build);
     $this->assertTrue($this->container->get('cache.render', $cid), 'The block render element has been cached.');
     $this->assertNotEqual($cid, $old_cid, 'The cache ID has changed.');
@@ -306,7 +307,7 @@ public function testBlockViewBuilderCacheContexts() {
     $this->container->set('cache_context.url', $temp_context);
     $old_cid = $cid;
     $build = $this->getBlockRenderArray();
-    $cid = drupal_render_cid_create($build);
+    $cid = Render::generateCacheId($build);
     drupal_render($build);
     $this->assertTrue($this->container->get('cache.render', $cid), 'The block render element has been cached.');
     $this->assertNotEqual($cid, $old_cid, 'The cache ID has changed.');
diff --git a/core/modules/comment/lib/Drupal/comment/CommentViewBuilder.php b/core/modules/comment/lib/Drupal/comment/CommentViewBuilder.php
index 02e427f..dd073d6 100644
--- a/core/modules/comment/lib/Drupal/comment/CommentViewBuilder.php
+++ b/core/modules/comment/lib/Drupal/comment/CommentViewBuilder.php
@@ -17,6 +17,7 @@
 use Drupal\entity\Entity\EntityViewDisplay;
 use Drupal\Core\Language\Language;
 use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\Render\Render;
 use Drupal\field\FieldInfo;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -132,7 +133,7 @@ public function buildComponents(array &$build, array $entities, array $displays,
         'commented_entity_type' => $commented_entity->getEntityTypeId(),
         'commented_entity_id' => $commented_entity->id(),
         'in_preview' => !empty($entity->in_preview),
-        'token' => drupal_render_cache_generate_token(),
+        'token' => Render::generateCacheToken(),
       );
       $build[$id]['links'] = array(
         '#post_render_cache' => array(
@@ -140,7 +141,7 @@ public function buildComponents(array &$build, array $entities, array $displays,
             $context,
           ),
         ),
-        '#markup' => drupal_render_cache_generate_placeholder($callback, $context, $context['token']),
+        '#markup' => Render::generateCachePlaceholder($callback, $context, $context['token']),
       );
 
       if (!isset($build[$id]['#attached'])) {
@@ -179,7 +180,7 @@ public function buildComponents(array &$build, array $entities, array $displays,
    */
   public static function renderLinks(array $element, array $context) {
     $callback = '\Drupal\comment\CommentViewBuilder::renderLinks';
-    $placeholder = drupal_render_cache_generate_placeholder($callback, $context, $context['token']);
+    $placeholder = Render::generateCachePlaceholder($callback, $context, $context['token']);
     $links = array(
       '#theme' => 'links__comment',
       '#pre_render' => array('drupal_pre_render_links'),
diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/Field/FieldFormatter/CommentDefaultFormatter.php b/core/modules/comment/lib/Drupal/comment/Plugin/Field/FieldFormatter/CommentDefaultFormatter.php
index 19dd00f..011c5c1 100644
--- a/core/modules/comment/lib/Drupal/comment/Plugin/Field/FieldFormatter/CommentDefaultFormatter.php
+++ b/core/modules/comment/lib/Drupal/comment/Plugin/Field/FieldFormatter/CommentDefaultFormatter.php
@@ -15,6 +15,7 @@
 use Drupal\Core\Field\FieldDefinitionInterface;
 use Drupal\Core\Field\FormatterBase;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Render\Render;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -181,7 +182,7 @@ public function viewElements(FieldItemListInterface $items) {
               'entity_type' => $entity->getEntityTypeId(),
               'entity_id' => $entity->id(),
               'field_name' => $field_name,
-              'token' => drupal_render_cache_generate_token(),
+              'token' => Render::generateCacheToken(),
             );
             $output['comment_form'] = array(
               '#post_render_cache' => array(
@@ -189,7 +190,7 @@ public function viewElements(FieldItemListInterface $items) {
                   $context,
                 ),
               ),
-              '#markup' => drupal_render_cache_generate_placeholder($callback, $context, $context['token']),
+              '#markup' => Render::generateCachePlaceholder($callback, $context, $context['token']),
             );
           }
         }
@@ -223,7 +224,7 @@ public function viewElements(FieldItemListInterface $items) {
    */
   public static function renderForm(array $element, array $context) {
     $callback = '\Drupal\comment\Plugin\Field\FieldFormatter\CommentDefaultFormatter::renderForm';
-    $placeholder = drupal_render_cache_generate_placeholder($callback, $context, $context['token']);
+    $placeholder = Render::generateCachePlaceholder($callback, $context, $context['token']);
     $entity = entity_load($context['entity_type'], $context['entity_id']);
     $form = comment_add($entity, $context['field_name']);
     // @todo: This only works as long as assets are still tracked in a global
diff --git a/core/modules/node/lib/Drupal/node/NodeViewBuilder.php b/core/modules/node/lib/Drupal/node/NodeViewBuilder.php
index 494183f..a429d0c 100644
--- a/core/modules/node/lib/Drupal/node/NodeViewBuilder.php
+++ b/core/modules/node/lib/Drupal/node/NodeViewBuilder.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityViewBuilder;
+use Drupal\Core\Render\Render;
 
 /**
  * Render controller for nodes.
@@ -40,7 +41,7 @@ public function buildComponents(array &$build, array $entities, array $displays,
         'view_mode' => $view_mode,
         'langcode' => $langcode,
         'in_preview' => !empty($entity->in_preview),
-        'token' => drupal_render_cache_generate_token(),
+        'token' => Render::generateCacheToken(),
       );
 
       $build[$id]['links'] = array(
@@ -49,7 +50,7 @@ public function buildComponents(array &$build, array $entities, array $displays,
             $context,
           ),
         ),
-        '#markup' => drupal_render_cache_generate_placeholder($callback, $context, $context['token']),
+        '#markup' => Render::generateCachePlaceholder($callback, $context, $context['token']),
       );
 
 
@@ -105,7 +106,7 @@ protected function getBuildDefaults(EntityInterface $entity, $view_mode, $langco
    */
   public static function renderLinks(array $element, array $context) {
     $callback = '\Drupal\node\NodeViewBuilder::renderLinks';
-    $placeholder = drupal_render_cache_generate_placeholder($callback, $context, $context['token']);
+    $placeholder = Render::generateCachePlaceholder($callback, $context, $context['token']);
 
     $links = array(
       '#theme' => 'links__node',
diff --git a/core/modules/system/lib/Drupal/system/Tests/Common/MergeAttachmentsTest.php b/core/modules/system/lib/Drupal/system/Tests/Common/MergeAttachmentsTest.php
index 0aa9971..350c5c5 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Common/MergeAttachmentsTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Common/MergeAttachmentsTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\system\Tests\Common;
 
+use Drupal\Core\Render\Render;
 use Drupal\simpletest\DrupalUnitTestBase;
 
 /**
@@ -44,7 +45,7 @@ function testLibraryMerging() {
         'core/jquery',
       ),
     );
-    $this->assertIdentical($expected['#attached'], drupal_merge_attached($a['#attached'], $b['#attached']), 'Attachments merged correctly.');
+    $this->assertIdentical($expected['#attached'], Render::mergeAttached($a['#attached'], $b['#attached']), 'Attachments merged correctly.');
 
     // Merging in the opposite direction yields the opposite library order.
     $expected['#attached'] = array(
@@ -54,7 +55,7 @@ function testLibraryMerging() {
         'core/drupalSettings',
       ),
     );
-    $this->assertIdentical($expected['#attached'], drupal_merge_attached($b['#attached'], $a['#attached']), 'Attachments merged correctly; opposite merging yields opposite order.');
+    $this->assertIdentical($expected['#attached'], Render::mergeAttached($b['#attached'], $a['#attached']), 'Attachments merged correctly; opposite merging yields opposite order.');
 
     // Merging with duplicates: duplicates are simply retained, it's up to the
     // rest of the system to handle duplicates.
@@ -67,7 +68,7 @@ function testLibraryMerging() {
         'core/drupalSettings',
       ),
     );
-    $this->assertIdentical($expected['#attached'], drupal_merge_attached($a['#attached'], $b['#attached']), 'Attachments merged correctly; duplicates are retained.');
+    $this->assertIdentical($expected['#attached'], Render::mergeAttached($a['#attached'], $b['#attached']), 'Attachments merged correctly; duplicates are retained.');
   }
 
   /**
@@ -92,7 +93,7 @@ function testCssMerging() {
         'baz.css' => array(),
       ),
     );
-    $this->assertIdentical($expected['#attached'], drupal_merge_attached($a['#attached'], $b['#attached']), 'Attachments merged correctly.');
+    $this->assertIdentical($expected['#attached'], Render::mergeAttached($a['#attached'], $b['#attached']), 'Attachments merged correctly.');
 
     // Merging in the opposite direction yields the opposite CSS asset order.
     $expected['#attached'] = array(
@@ -102,7 +103,7 @@ function testCssMerging() {
         'bar.css' => array(),
       ),
     );
-    $this->assertIdentical($expected['#attached'], drupal_merge_attached($b['#attached'], $a['#attached']), 'Attachments merged correctly; opposite merging yields opposite order.');
+    $this->assertIdentical($expected['#attached'], Render::mergeAttached($b['#attached'], $a['#attached']), 'Attachments merged correctly; opposite merging yields opposite order.');
 
     // Merging with duplicates: duplicates are automatically removed because the
     // values have unique keys.
@@ -114,7 +115,7 @@ function testCssMerging() {
         'baz.css' => array(),
       ),
     );
-    $this->assertIdentical($expected['#attached'], drupal_merge_attached($a['#attached'], $b['#attached']), 'Attachments merged correctly; CSS asset duplicates removed.');
+    $this->assertIdentical($expected['#attached'], Render::mergeAttached($a['#attached'], $b['#attached']), 'Attachments merged correctly; CSS asset duplicates removed.');
   }
 
   /**
@@ -139,7 +140,7 @@ function testJsMerging() {
         'baz.js' => array(),
       ),
     );
-    $this->assertIdentical($expected['#attached'], drupal_merge_attached($a['#attached'], $b['#attached']), 'Attachments merged correctly.');
+    $this->assertIdentical($expected['#attached'], Render::mergeAttached($a['#attached'], $b['#attached']), 'Attachments merged correctly.');
 
     // Merging in the opposite direction yields the opposite JS asset order.
     $expected['#attached'] = array(
@@ -149,7 +150,7 @@ function testJsMerging() {
         'bar.js' => array(),
       ),
     );
-    $this->assertIdentical($expected['#attached'], drupal_merge_attached($b['#attached'], $a['#attached']), 'Attachments merged correctly; opposite merging yields opposite order.');
+    $this->assertIdentical($expected['#attached'], Render::mergeAttached($b['#attached'], $a['#attached']), 'Attachments merged correctly; opposite merging yields opposite order.');
 
     // Merging with duplicates: duplicates are automatically removed because the
     // values have unique keys.
@@ -161,7 +162,7 @@ function testJsMerging() {
         'baz.js' => array(),
       ),
     );
-    $this->assertIdentical($expected['#attached'], drupal_merge_attached($a['#attached'], $b['#attached']), 'Attachments merged correctly; JS asset duplicates removed.');
+    $this->assertIdentical($expected['#attached'], Render::mergeAttached($a['#attached'], $b['#attached']), 'Attachments merged correctly; JS asset duplicates removed.');
   }
 
   /**
@@ -202,7 +203,7 @@ function testJsSettingMerging() {
         'baz.js' => array(),
       ),
     );
-    $this->assertIdentical($expected['#attached'], drupal_merge_attached($a['#attached'], $b['#attached']), 'Attachments merged correctly.');
+    $this->assertIdentical($expected['#attached'], Render::mergeAttached($a['#attached'], $b['#attached']), 'Attachments merged correctly.');
 
     // Merging in the opposite direction yields the opposite JS setting asset
     // order.
@@ -221,7 +222,7 @@ function testJsSettingMerging() {
         'bar.js' => array(),
       ),
     );
-    $this->assertIdentical($expected['#attached'], drupal_merge_attached($b['#attached'], $a['#attached']), 'Attachments merged correctly; opposite merging yields opposite order.');
+    $this->assertIdentical($expected['#attached'], Render::mergeAttached($b['#attached'], $a['#attached']), 'Attachments merged correctly; opposite merging yields opposite order.');
 
     // Merging with duplicates: JavaScript setting duplicates are simply
     // retained, it's up to the rest of the system (drupal_merge_js_settings())
@@ -249,7 +250,7 @@ function testJsSettingMerging() {
         ),
       ),
     );
-    $this->assertIdentical($expected['#attached'], drupal_merge_attached($a['#attached'], $b['#attached']), 'Attachments merged correctly; JavaScript asset duplicates removed, JavaScript setting asset duplicates retained.');
+    $this->assertIdentical($expected['#attached'], Render::mergeAttached($a['#attached'], $b['#attached']), 'Attachments merged correctly; JavaScript asset duplicates removed, JavaScript setting asset duplicates retained.');
   }
 
 }
diff --git a/core/modules/system/lib/Drupal/system/Tests/Common/RenderTest.php b/core/modules/system/lib/Drupal/system/Tests/Common/RenderTest.php
index caa80a2..f612303 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Common/RenderTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Common/RenderTest.php
@@ -10,6 +10,7 @@
 use Drupal\Component\Serialization\Json;
 use Drupal\Component\Utility\Html;
 use Drupal\Core\Render\Element;
+use Drupal\Core\Render\Render;
 use Drupal\simpletest\DrupalUnitTestBase;
 
 /**
@@ -446,7 +447,7 @@ function testDrupalRenderCache() {
       'render_cache_tag' => TRUE,
       'render_cache_tag_child' => array(1 => 1, 2 => 2),
     );
-    $actual_tags = drupal_render_collect_cache_tags($test_element);
+    $actual_tags = Render::collectCacheTags($test_element);
     $this->assertEqual($expected_tags, $actual_tags, 'Cache tags were collected from the element and its subchild.');
 
     // Restore the previous request method.
@@ -495,7 +496,7 @@ function testDrupalRenderPostRenderCache() {
 
     // GET request: validate cached data.
     $element = array('#cache' => array('cid' => 'post_render_cache_test_GET'));
-    $cached_element = \Drupal::cache('render')->get(drupal_render_cid_create($element))->data;
+    $cached_element = \Drupal::cache('render')->get(Render::generateCacheId($element))->data;
     $expected_element = array(
       '#markup' => '<p>#cache enabled, GET</p>',
       '#attached' => $test_element['#attached'],
@@ -535,7 +536,7 @@ function testDrupalRenderPostRenderCache() {
 
     // POST request: Ensure no data was cached.
     $element = array('#cache' => array('cid' => 'post_render_cache_test_POST'));
-    $cached_element = \Drupal::cache('render')->get(drupal_render_cid_create($element));
+    $cached_element = \Drupal::cache('render')->get(Render::generateCacheId($element));
     $this->assertFalse($cached_element, 'No data is cached because this is a POST request.');
 
     // Restore the previous request method.
@@ -599,7 +600,7 @@ function testDrupalRenderChildrenPostRenderCache() {
 
     // GET request: validate cached data.
     $element = array('#cache' => $element['#cache']);
-    $cached_element = \Drupal::cache('render')->get(drupal_render_cid_create($element))->data;
+    $cached_element = \Drupal::cache('render')->get(Render::generateCacheId($element))->data;
     $expected_element = array(
       '#attached' => array(
         'js' => array(
@@ -679,8 +680,8 @@ function testDrupalRenderChildrenPostRenderCache() {
     $element = $test_element;
     $element['#cache']['keys'] = array('simpletest', 'drupal_render', 'children_post_render_cache', 'nested_cache_parent');
     $element['child']['#cache']['keys'] = array('simpletest', 'drupal_render', 'children_post_render_cache', 'nested_cache_child');
-    $cached_parent_element = \Drupal::cache('render')->get(drupal_render_cid_create($element))->data;
-    $cached_child_element = \Drupal::cache('render')->get(drupal_render_cid_create($element['child']))->data;
+    $cached_parent_element = \Drupal::cache('render')->get(Render::generateCacheId($element))->data;
+    $cached_child_element = \Drupal::cache('render')->get(Render::generateCacheId($element['child']))->data;
     $expected_parent_element = array(
       '#attached' => array(
         'js' => array(
@@ -771,7 +772,7 @@ function testDrupalRenderChildrenPostRenderCache() {
   function testDrupalRenderRenderCachePlaceholder() {
     $context = array(
       'bar' => $this->randomContextValue(),
-      'token' => drupal_render_cache_generate_token(),
+      'token' => Render::generateCacheToken(),
     );
     $callback = 'common_test_post_render_cache_placeholder';
     $test_element = array(
@@ -780,7 +781,7 @@ function testDrupalRenderRenderCachePlaceholder() {
           $context
         ),
       ),
-      '#markup' => drupal_render_cache_generate_placeholder($callback, $context, $context['token']),
+      '#markup' => Render::generateCachePlaceholder($callback, $context, $context['token']),
       '#prefix' => '<foo>',
       '#suffix' => '</foo>'
     );
@@ -812,7 +813,7 @@ function testDrupalRenderRenderCachePlaceholder() {
     // GET request: validate cached data.
     $expected_token = $element['#post_render_cache']['common_test_post_render_cache_placeholder'][0]['token'];
     $element = array('#cache' => array('cid' => 'render_cache_placeholder_test_GET'));
-    $cached_element = \Drupal::cache('render')->get(drupal_render_cid_create($element))->data;
+    $cached_element = \Drupal::cache('render')->get(Render::generateCacheId($element))->data;
     // Parse unique token out of the cached markup.
     $dom = Html::load($cached_element['#markup']);
     $xpath = new \DOMXPath($dom);
@@ -860,7 +861,7 @@ function testDrupalRenderChildElementRenderCachePlaceholder() {
     );
     $context = array(
       'bar' => $this->randomContextValue(),
-      'token' => drupal_render_cache_generate_token(),
+      'token' => Render::generateCacheToken(),
     );
     $callback = 'common_test_post_render_cache_placeholder';
     $test_element = array(
@@ -869,7 +870,7 @@ function testDrupalRenderChildElementRenderCachePlaceholder() {
           $context
         ),
       ),
-      '#markup' => drupal_render_cache_generate_placeholder($callback, $context, $context['token']),
+      '#markup' => Render::generateCachePlaceholder($callback, $context, $context['token']),
       '#prefix' => '<foo>',
       '#suffix' => '</foo>'
     );
@@ -910,7 +911,7 @@ function testDrupalRenderChildElementRenderCachePlaceholder() {
     $parent_tokens = $element['#post_render_cache']['common_test_post_render_cache_placeholder'][0]['token'];
     $expected_token = $child_tokens;
     $element = array('#cache' => array('cid' => 'render_cache_placeholder_test_child_GET'));
-    $cached_element = \Drupal::cache('render')->get(drupal_render_cid_create($element))->data;
+    $cached_element = \Drupal::cache('render')->get(Render::generateCacheId($element))->data;
     // Parse unique token out of the cached markup.
     $dom = Html::load($cached_element['#markup']);
     $xpath = new \DOMXPath($dom);
@@ -935,7 +936,7 @@ function testDrupalRenderChildElementRenderCachePlaceholder() {
 
     // GET request: validate cached data (for the parent/entire render array).
     $element = array('#cache' => array('cid' => 'render_cache_placeholder_test_GET'));
-    $cached_element = \Drupal::cache('render')->get(drupal_render_cid_create($element))->data;
+    $cached_element = \Drupal::cache('render')->get(Render::generateCacheId($element))->data;
     // Parse unique token out of the cached markup.
     $dom = Html::load($cached_element['#markup']);
     $xpath = new \DOMXPath($dom);
@@ -962,7 +963,7 @@ function testDrupalRenderChildElementRenderCachePlaceholder() {
     // Check the cache of the child element again after the parent has been
     // rendered.
     $element = array('#cache' => array('cid' => 'render_cache_placeholder_test_child_GET'));
-    $cached_element = \Drupal::cache('render')->get(drupal_render_cid_create($element))->data;
+    $cached_element = \Drupal::cache('render')->get(Render::generateCacheId($element))->data;
     // Verify that the child element contains the correct
     // render_cache_placeholder markup.
     $expected_token = $child_tokens;
diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityViewBuilderTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityViewBuilderTest.php
index 6ac4a62..e5b7f9e 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityViewBuilderTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityViewBuilderTest.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\system\Tests\Entity;
 
+use Drupal\Core\Render\Render;
+
 /**
  * Tests the entity view builder.
  */
@@ -53,7 +55,7 @@ public function testEntityViewBuilderCache() {
     // Get a fully built entity view render array.
     $entity_test->save();
     $build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test, 'full');
-    $cid = drupal_render_cid_create($build);
+    $cid = Render::generateCacheId($build);
     $bin = $build['#cache']['bin'];
 
     // Mock the build array to not require the theme registry.
@@ -96,7 +98,7 @@ public function testEntityViewBuilderCacheWithReferences() {
 
     // Get a fully built entity view render array for the referenced entity.
     $build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test_reference, 'full');
-    $cid_reference = drupal_render_cid_create($build);
+    $cid_reference = Render::generateCacheId($build);
     $bin_reference = $build['#cache']['bin'];
 
     // Mock the build array to not require the theme registry.
@@ -114,7 +116,7 @@ public function testEntityViewBuilderCacheWithReferences() {
 
     // Get a fully built entity view render array.
     $build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test, 'full');
-    $cid = drupal_render_cid_create($build);
+    $cid = Render::generateCacheId($build);
     $bin = $build['#cache']['bin'];
 
     // Mock the build array to not require the theme registry.
diff --git a/core/modules/system/tests/modules/common_test/common_test.module b/core/modules/system/tests/modules/common_test/common_test.module
index 2785be9..47a6d21 100644
--- a/core/modules/system/tests/modules/common_test/common_test.module
+++ b/core/modules/system/tests/modules/common_test/common_test.module
@@ -5,6 +5,8 @@
  * Helper module for the Common tests.
  */
 
+use Drupal\Core\Render\Render;
+
 /**
  * Applies #printed to an element to help test #pre_render.
  */
@@ -207,7 +209,7 @@ function common_test_post_render_cache(array $element, array $context) {
  *   A render array.
  */
 function common_test_post_render_cache_placeholder(array $element, array $context) {
-  $placeholder = drupal_render_cache_generate_placeholder(__FUNCTION__, $context, $context['token']);
+  $placeholder = Render::generateCachePlaceholder(__FUNCTION__, $context, $context['token']);
   $replace_element = array(
     '#markup' => '<bar>' . $context['bar'] . '</bar>',
     '#attached' => array(
diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/cache/CachePluginBase.php b/core/modules/views/lib/Drupal/views/Plugin/views/cache/CachePluginBase.php
index 4f2503c..38be8e7 100644
--- a/core/modules/views/lib/Drupal/views/Plugin/views/cache/CachePluginBase.php
+++ b/core/modules/views/lib/Drupal/views/Plugin/views/cache/CachePluginBase.php
@@ -9,6 +9,7 @@
 
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Language\Language;
+use Drupal\Core\Render\Render;
 use Drupal\views\Plugin\views\PluginBase;
 use Drupal\Core\Database\Query\Select;
 
@@ -239,7 +240,7 @@ protected function gatherHeaders() {
       $this->storage['head'] = '';
     }
 
-    $attached = drupal_render_collect_attached($this->storage['output']);
+    $attached = Render::collectAttached($this->storage['output']);
     $this->storage['css'] = $attached['css'];
     $this->storage['js'] = $attached['js'];
   }
