diff --git a/core/includes/common.inc b/core/includes/common.inc
index 3ec9576..7629d95 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -2288,6 +2288,310 @@ function drupal_render_root(&$elements) {
  */
 function drupal_render(&$elements, $is_recursive_call = FALSE) {
   return \Drupal::service('renderer')->render($elements, $is_recursive_call);
+
+ * @throws \LogicException
+ *   If a root call to drupal_render() does not result in an empty stack, this
+ *   indicates an erroneous drupal_render() root call (a root call within a root
+ *   call, which makes no sense). Therefore, a logic exception is thrown.
+ * @throws \Exception
+ *   If a #pre_render callback throws an exception, it is caught to reset the
+ *   stack used for bubbling rendering metadata, and then the exception is re-
+ *   thrown.
+ *
+ * @see element_info()
+ * @see _theme()
+ * @see drupal_process_states()
+ * @see drupal_process_attached()
+ * @see drupal_render_root()
+ */
+  static $stack;
+
+  $update_stack = function(&$element) use (&$stack) {
+    // The latest frame represents the bubbleable data for the subtree.
+    $frame = $stack->top();
+    // Update the frame, but also update the current element, to ensure it
+    // contains up-to-date information in case it gets render cached.
+    $frame->tags = $element['#cache']['tags'] = Cache::mergeTags($element['#cache']['tags'], $frame->tags);
+    $frame->attached = $element['#attached'] = drupal_merge_attached($element['#attached'], $frame->attached);
+    $frame->postRenderCache = $element['#post_render_cache'] = NestedArray::mergeDeep($element['#post_render_cache'], $frame->postRenderCache);
+  };
+
+  $bubble_stack = function() use (&$stack) {
+    // If there's only one frame on the stack, then this is the root call, and
+    // we can't bubble up further. Reset the stack for the next root call.
+    if ($stack->count() === 1) {
+      $stack = NULL;
+      return;
+    }
+
+    // Merge the current and the parent stack frame.
+    $current = $stack->pop();
+    $parent = $stack->pop();
+    $current->tags = Cache::mergeTags($current->tags, $parent->tags);
+    $current->attached = drupal_merge_attached($current->attached, $parent->attached);
+    $current->postRenderCache = NestedArray::mergeDeep($current->postRenderCache, $parent->postRenderCache);
+    $stack->push($current);
+  };
+  /** @var \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver */
+  $controller_resolver = \Drupal::service('controller_resolver');
+  if (!isset($elements['#access']) && isset($elements['#access_callback'])) {
+    if (is_string($elements['#access_callback']) && strpos($elements['#access_callback'], '::') === FALSE) {
+      $elements['#access_callback'] = $controller_resolver->getControllerFromDefinition($elements['#access_callback']);
+    }
+    $elements['#access'] = call_user_func($elements['#access_callback'], $elements);
+  }
+
+  // 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 '';
+  }
+
+  if (!isset($stack)) {
+    $stack = new \SplStack();
+  }
+  $stack->push(new RenderStackFrame());
+
+  // 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 root (non-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_root_call) {
+        _drupal_render_process_post_render_cache($elements);
+      }
+      $elements['#markup'] = SafeMarkup::set($elements['#markup']);
+      // The render cache item contains all the bubbleable rendering metadata for
+      // the subtree.
+      $update_stack($elements);
+      // Render cache hit, so rendering is finished, all necessary info collected!
+      $bubble_stack();
+      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) {
+      if (is_string($callable) && strpos($callable, '::') === FALSE) {
+        $callable = $controller_resolver->getControllerFromDefinition($callable);
+      }
+      // Since #pre_render callbacks may be used for generating a render array's
+      // content, and we might be rendering the main content for the page, it is
+      // possible that a #pre_render callback throws an exception that will
+      // cause a different page to be rendered (e.g. throwing
+      // \Symfony\Component\HttpKernel\Exception\NotFoundHttpException will
+      // cause the 404 page to be rendered). That page might also use
+      // drupal_render(), but if exceptions aren't caught here, the stack will
+      // be left in an inconsistent state.
+      // Hence, catch all exceptions and reset the stack and re-throw them.
+      try {
+        $elements = call_user_func($callable, $elements);
+      }
+      catch (\Exception $e) {
+        // Reset stack and re-throw exception.
+        $stack = NULL;
+        throw $e;
+      }
+    }
+  }
+
+  // Defaults for bubbleable rendering metadata.
+  $elements['#cache']['tags'] = isset($elements['#cache']['tags']) ? $elements['#cache']['tags'] : array();
+  $elements['#attached'] = isset($elements['#attached']) ? $elements['#attached'] : array();
+  $elements['#post_render_cache'] = isset($elements['#post_render_cache']) ? $elements['#post_render_cache'] : array();
+
+  // Allow #pre_render to abort rendering.
+  if (!empty($elements['#printed'])) {
+    // The #printed element contains all the bubbleable rendering metadata for
+    // the subtree.
+    $update_stack($elements);
+    // #printed, so rendering is finished, all necessary info collected!
+    $bubble_stack();
+    return '';
+  }
+
+  // Add any JavaScript state information associated with the element.
+  if (!empty($elements['#states'])) {
+    drupal_process_states($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'] = '';
+  }
+
+  // @todo Simplify after https://drupal.org/node/2273925
+  if (isset($elements['#markup'])) {
+    $elements['#markup'] = SafeMarkup::set($elements['#markup']);
+  }
+
+  // Assume that if #theme is set it represents an implemented hook.
+  $theme_is_implemented = isset($elements['#theme']);
+  // Check the elements for insecure HTML and pass through sanitization.
+  if (isset($elements)) {
+    $markup_keys = array(
+      '#description',
+      '#field_prefix',
+      '#field_suffix',
+    );
+    foreach ($markup_keys as $key) {
+      if (!empty($elements[$key]) && is_scalar($elements[$key])) {
+        $elements[$key] = SafeMarkup::checkAdminXss($elements[$key]);
+      }
+    }
+  }
+
+  // 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'] = \Drupal::theme()->render($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]);
+    }
+    $elements['#children'] = SafeMarkup::set($elements['#children']);
+  }
+
+  // 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'] = SafeMarkup::set($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_prepare_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'] = \Drupal::theme()->render($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 outputted text to be filtered.
+  if (isset($elements['#post_render'])) {
+    foreach ($elements['#post_render'] as $callable) {
+      if (is_string($callable) && strpos($callable, '::') === FALSE) {
+        $callable = $controller_resolver->getControllerFromDefinition($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']) ? SafeMarkup::checkAdminXss($elements['#prefix']) : '';
+  $suffix = isset($elements['#suffix']) ? SafeMarkup::checkAdminXss($elements['#suffix']) : '';
+
+  $elements['#markup'] = $prefix . $elements['#children'] . $suffix;
+
+  // We've rendered this element (and its subtree!), now update the stack.
+  $update_stack($elements);
+
+  // Cache the processed element if #cache is set.
+  if (isset($elements['#cache'])) {
+    drupal_render_cache_set($elements['#markup'], $elements);
+  }
+
+  // Only when we're in a root (non-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_root_call) {
+    // We've already called $update_stack() earlier, which updated both the
+    // element and current stack frame. However,
+    // _drupal_render_process_post_render_cache() can both change the element
+    // further and create and render new child elements, so provide a fresh
+    // stack frame to collect those additions, merge them back to the element,
+    // and then update the current frame to match the modified element state.
+    $stack->push(new RenderStackFrame());
+    _drupal_render_process_post_render_cache($elements);
+    $post_render_additions = $stack->pop();
+    $elements['#cache']['tags'] = Cache::mergeTags($elements['#cache']['tags'], $post_render_additions->tags);
+    $elements['#attached'] = drupal_merge_attached($elements['#attached'], $post_render_additions->attached);
+    $elements['#post_render_cache'] = NestedArray::mergeDeep($elements['#post_render_cache'], $post_render_additions->postRenderCache);
+    if ($stack->count() !== 1) {
+      throw new \LogicException('A stray drupal_render() invocation with $is_root_call = TRUE is causing bubbling of attached assets to break.');
+    }
+  }
+
+  // Add additional libraries, CSS, JavaScript an other custom
+  // attached data associated with this elements.
+  drupal_process_attached($elements);
+
+  // Rendering is finished, all necessary info collected!
+  $bubble_stack();
+
+  $elements['#printed'] = TRUE;
+  $elements['#markup'] = SafeMarkup::set($elements['#markup']);
+  return $elements['#markup'];
 }
 
 /**
