diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php
index a7f7346..0b744b6 100644
--- a/core/lib/Drupal/Core/Render/Renderer.php
+++ b/core/lib/Drupal/Core/Render/Renderer.php
@@ -154,6 +154,17 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
       return '';
     }
 
+    if (isset($elements['#render_children']) && $elements['#theme'] != 'page') {
+      $children_keys = Element::children($elements);
+      $output = '';
+      foreach ($children_keys as $key) {
+        if (!empty($elements[$key])) {
+          $output .= $this->doRender($elements[$key]);
+        }
+      }
+      return $output;
+    }
+
     // Do not print elements twice.
     if (!empty($elements['#printed'])) {
       return '';
@@ -268,10 +279,8 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
     }
 
     // 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'])) {
+    // element have to be rendered there.
+    if ($theme_is_implemented) {
       $elements['#children'] = $this->theme->render($elements['#theme'], $elements);
 
       // If ThemeManagerInterface::render() returns FALSE this means that the
diff --git a/core/lib/Drupal/Core/Theme/Registry.php b/core/lib/Drupal/Core/Theme/Registry.php
index 5e450ab..72ef9ac 100644
--- a/core/lib/Drupal/Core/Theme/Registry.php
+++ b/core/lib/Drupal/Core/Theme/Registry.php
@@ -347,13 +347,6 @@ protected function build() {
     $this->moduleHandler->alter('theme_registry', $cache);
     $this->themeManager->alterForTheme($this->theme, 'theme_registry', $cache);
 
-    // @todo Implement more reduction of the theme registry entry.
-    // Optimize the registry to not have empty arrays for functions.
-    foreach ($cache as $hook => $info) {
-      if (empty($info['preprocess functions'])) {
-        unset($cache[$hook]['preprocess functions']);
-      }
-    }
     $this->registry = $cache;
 
     return $this->registry;
@@ -482,83 +475,19 @@ protected function processExtension(array &$cache, $name, $type, $theme, $path)
           $result[$hook] += array_intersect_key($cache[$hook], $hook_defaults);
         }
 
-        // Preprocess variables for all theming hooks, whether the hook is
-        // implemented as a template or as a function. Ensure they are arrays.
-        if (!isset($info['preprocess functions']) || !is_array($info['preprocess functions'])) {
-          $info['preprocess functions'] = array();
-          $prefixes = array();
-          if ($type == 'module') {
-            // Default variable preprocessor prefix.
-            $prefixes[] = 'template';
-            // Add all modules so they can intervene with their own variable
-            // preprocessors. This allows them to provide variable preprocessors
-            // even if they are not the owner of the current hook.
-            $prefixes = array_merge($prefixes, $module_list);
-          }
-          elseif ($type == 'theme_engine' || $type == 'base_theme_engine') {
-            // Theme engines get an extra set that come before the normally
-            // named variable preprocessors.
-            $prefixes[] = $name . '_engine';
-            // The theme engine registers on behalf of the theme using the
-            // theme's name.
-            $prefixes[] = $theme;
-          }
-          else {
-            // This applies when the theme manually registers their own variable
-            // preprocessors.
-            $prefixes[] = $name;
-          }
-          foreach ($prefixes as $prefix) {
-            // Only use non-hook-specific variable preprocessors for theming
-            // hooks implemented as templates. See _theme().
-            if (isset($info['template']) && function_exists($prefix . '_preprocess')) {
-              $info['preprocess functions'][] = $prefix . '_preprocess';
-            }
-            if (function_exists($prefix . '_preprocess_' . $hook)) {
-              $info['preprocess functions'][] = $prefix . '_preprocess_' . $hook;
-            }
-          }
-        }
         // Check for the override flag and prevent the cached variable
         // preprocessors from being used. This allows themes or theme engines
         // to remove variable preprocessors set earlier in the registry build.
+        // @todo
         if (!empty($info['override preprocess functions'])) {
           // Flag not needed inside the registry.
           unset($result[$hook]['override preprocess functions']);
         }
-        elseif (isset($cache[$hook]['preprocess functions']) && is_array($cache[$hook]['preprocess functions'])) {
-          $info['preprocess functions'] = array_merge($cache[$hook]['preprocess functions'], $info['preprocess functions']);
-        }
-        $result[$hook]['preprocess functions'] = $info['preprocess functions'];
       }
 
       // Merge the newly created theme hooks into the existing cache.
       $cache = $result + $cache;
     }
-
-    // Let themes have variable preprocessors even if they didn't register a
-    // template.
-    if ($type == 'theme' || $type == 'base_theme') {
-      foreach ($cache as $hook => $info) {
-        // Check only if not registered by the theme or engine.
-        if (empty($result[$hook])) {
-          if (!isset($info['preprocess functions'])) {
-            $cache[$hook]['preprocess functions'] = array();
-          }
-          // Only use non-hook-specific variable preprocessors for theme hooks
-          // implemented as templates. See _theme().
-          if (isset($info['template']) && function_exists($name . '_preprocess')) {
-            $cache[$hook]['preprocess functions'][] = $name . '_preprocess';
-          }
-          if (function_exists($name . '_preprocess_' . $hook)) {
-            $cache[$hook]['preprocess functions'][] = $name . '_preprocess_' . $hook;
-            $cache[$hook]['theme path'] = $path;
-          }
-          // Ensure uniqueness.
-          $cache[$hook]['preprocess functions'] = array_unique($cache[$hook]['preprocess functions']);
-        }
-      }
-    }
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Theme/ThemeManager.php b/core/lib/Drupal/Core/Theme/ThemeManager.php
index 6850fcb..45ee78a 100644
--- a/core/lib/Drupal/Core/Theme/ThemeManager.php
+++ b/core/lib/Drupal/Core/Theme/ThemeManager.php
@@ -286,16 +286,12 @@ protected function theme($hook, $variables = array()) {
           include_once $this->root . '/' . $include_file;
         }
       }
-      // Replace the preprocess functions with those from the base hook.
-      if (isset($base_hook_info['preprocess functions'])) {
-        // Set a variable for the 'theme_hook_suggestion'. This is used to
-        // maintain backwards compatibility with template engines.
-        $theme_hook_suggestion = $hook;
-        $info['preprocess functions'] = $base_hook_info['preprocess functions'];
-      }
+      $theme_hook_suggestion = $hook;
     }
-    if (isset($info['preprocess functions'])) {
-      foreach ($info['preprocess functions'] as $preprocessor_function) {
+
+    $preprocess_functions = $this->buildPreprocess($info, $hook, $suggestions);
+    if (!empty($preprocess_functions)) {
+      foreach ($preprocess_functions as $preprocessor_function) {
         if (function_exists($preprocessor_function)) {
           $preprocessor_function($variables, $hook, $info);
         }
@@ -389,6 +385,69 @@ protected function theme($hook, $variables = array()) {
   }
 
   /**
+   * Create a list of preprocess functions to be run for a single theme output.
+   *
+   * @param $info
+   * @param $hook
+   * @param $suggesitons
+   *
+   */
+  protected function buildPreprocess($info, $hook, $suggestions) {
+    $module_list = array_keys((array) $this->moduleHandler->getModuleList());
+    $theme = $this->getActiveTheme();
+
+    // Add base hook and the hook that is being called to suggestions.
+    $suggestions[] = $hook;
+    if (isset($info['base hook'])) {
+      $suggestions[] = $info['base hook'];
+    }
+
+    $prefixes = [];
+    // Default variable preprocessor prefix.
+    $prefixes[] = 'template';
+    // Add all modules so they can intervene with their own variable
+    // preprocessors. This allows them to provide variable preprocessors
+    // even if they are not the owner of the current hook.
+    $prefixes = array_merge($prefixes, $module_list);
+
+    foreach (array_reverse($theme->getBaseThemes()) as $base) {
+      if ($base->getEngine()) {
+        $prefixes[] = $base->getEngine() . '_engine';
+      }
+      $prefixes[] = $base->getName();
+    }
+
+    if ($theme->getEngine()) {
+        $prefixes[] = $theme->getEngine() . '_engine';
+        $prefixes[] = $theme->getName();
+    }
+
+    foreach (array_reverse($theme->getBaseThemes()) as $base) {
+      // This applies when the theme manually registers their own variable
+      // preprocessors.
+      $prefixes[] = $base->getName();
+    }
+
+    $prefixes[] = $theme->getName();
+
+    $preprocess_functions = [];
+    foreach (array_reverse($suggestions) as $suggestion) {
+      foreach ($prefixes as $prefix) {
+        // Only use non-hook-specific variable preprocessors for theming
+        // hooks implemented as templates. See _theme().
+        if (isset($info['template']) && function_exists($prefix . '_preprocess')) {
+          $preprocess_functions[] = $prefix . '_preprocess';
+        }
+        if (function_exists($prefix . '_preprocess_' . $suggestion)) {
+          $preprocess_functions[] = $prefix . '_preprocess_' . $suggestion;
+        }
+      }
+    }
+
+    return array_unique($preprocess_functions);
+  }
+
+  /**
    * Initializes the active theme for a given route match.
    *
    * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
diff --git a/core/modules/system/src/Tests/Theme/ThemeTest.php b/core/modules/system/src/Tests/Theme/ThemeTest.php
index 0cd4e4e..bb6c822 100644
--- a/core/modules/system/src/Tests/Theme/ThemeTest.php
+++ b/core/modules/system/src/Tests/Theme/ThemeTest.php
@@ -287,4 +287,19 @@ function testRegionClass() {
     $this->assertEqual(count($elements), 1, 'New class found.');
   }
 
+  /**
+   * Ensures suggestion preprocess functions run even for default implementations.
+   *
+   * The theme hook used by this test has its base preprocess function in a
+   * separate file, so this test also ensures that that file is correctly loaded
+   * when needed.
+   */
+  function testSuggestionPreprocessForDefaults() {
+    // Test with both an unprimed and primed theme registry.
+    drupal_theme_rebuild();
+    for ($i = 0; $i < 2; $i++) {
+      $this->drupalGet('theme-test/preprocess-suggestions');
+      $this->assertText('Theme hook implementor=test_theme_theme_test_preprocess__suggestion(). Foo=template_preprocess_theme_test_preprocess', 'Theme hook ran with data available from a preprocess function for the suggested hook.');
+    }
+  }
 }
diff --git a/core/modules/system/tests/modules/theme_test/src/.ThemeTestController.php.swp b/core/modules/system/tests/modules/theme_test/src/.ThemeTestController.php.swp
new file mode 100644
index 0000000..e69de29
diff --git a/core/modules/system/tests/modules/theme_test/src/ThemeTestController.php b/core/modules/system/tests/modules/theme_test/src/ThemeTestController.php
index 335ed83..c82b8ec 100644
--- a/core/modules/system/tests/modules/theme_test/src/ThemeTestController.php
+++ b/core/modules/system/tests/modules/theme_test/src/ThemeTestController.php
@@ -143,4 +143,12 @@ public function nonHtml() {
     return new JsonResponse(['theme_initialized' => $theme_initialized]);
   }
 
+  /**
+   * Menu callback for testing preprocess functions are being run for theme
+   * suggestions.
+   */
+  function preprocessSuggestions() {
+    return array('#theme' => 'theme_test_preprocess_suggestions__suggestion');
+  }
+
 }
diff --git a/core/modules/system/tests/modules/theme_test/templates/theme-test-preprocess-suggestions.html.twig b/core/modules/system/tests/modules/theme_test/templates/theme-test-preprocess-suggestions.html.twig
new file mode 100644
index 0000000..db8a8a4
--- /dev/null
+++ b/core/modules/system/tests/modules/theme_test/templates/theme-test-preprocess-suggestions.html.twig
@@ -0,0 +1 @@
+{{ foo }}
diff --git a/core/modules/system/tests/modules/theme_test/theme_test.module b/core/modules/system/tests/modules/theme_test/theme_test.module
index 7ba5c2d..64d45e7 100644
--- a/core/modules/system/tests/modules/theme_test/theme_test.module
+++ b/core/modules/system/tests/modules/theme_test/theme_test.module
@@ -55,6 +55,9 @@ function theme_test_theme($existing, $type, $theme, $path) {
   $info['test_theme_not_existing_function'] = array(
     'function' => 'test_theme_not_existing_function',
   );
+  $items['theme_test_preprocess_suggestions'] = array(
+    'variables' => array('foo' => ''),
+  );
   return $items;
 }
 
@@ -90,6 +93,17 @@ function theme_theme_test_function_template_override($variables) {
 }
 
 /**
+ * Implements hook_theme_suggestions_HOOK().
+ */
+function theme_test_theme_suggestions_theme_test_preprocess_suggestions($variables) {
+  return array('theme_test_preprocess_suggestions__' . 'suggestion');
+}
+
+function theme_test_preprocess_theme_test_preprocess_suggestions(&$variables) {
+  $variables['foo'] =  'Theme hook implementor=theme_theme_test_preprocess_suggestions().';
+}
+
+/**
  * Prepares variables for test render element templates.
  *
  * Default template: theme-test-render-element.html.twig.
diff --git a/core/modules/system/tests/modules/theme_test/theme_test.routing.yml b/core/modules/system/tests/modules/theme_test/theme_test.routing.yml
index 2dbd188..1ff61cf 100644
--- a/core/modules/system/tests/modules/theme_test/theme_test.routing.yml
+++ b/core/modules/system/tests/modules/theme_test/theme_test.routing.yml
@@ -103,3 +103,10 @@ theme_test.non_html:
     _controller: '\Drupal\theme_test\ThemeTestController::nonHtml'
   requirements:
     _access: 'TRUE'
+
+theme_test.preprocess_suggestions:
+  path: '/theme-test/preprocess-suggestions'
+  defaults:
+    _controller: '\Drupal\theme_test\ThemeTestController::preprocessSuggestions'
+  requirements:
+    _access: 'TRUE'
diff --git a/core/modules/system/tests/themes/test_theme/test_theme.theme b/core/modules/system/tests/themes/test_theme/test_theme.theme
index 951fd58..541f0bf 100644
--- a/core/modules/system/tests/themes/test_theme/test_theme.theme
+++ b/core/modules/system/tests/themes/test_theme/test_theme.theme
@@ -101,3 +101,18 @@ function test_theme_theme_test_function_suggestions__module_override($variables)
 function test_theme_theme_registry_alter(&$registry) {
   $registry['theme_test_template_test']['variables']['additional'] = 'value';
 }
+
+/**
+ * Tests a theme overriding a default hook with a suggestion.
+ */
+function test_theme_preprocess_theme_test_preprocess_suggestions(&$variables) {
+  $variables['foo'] = 'Theme hook implementor=test_theme_preprocess_theme_test_preprocess_suggestions().';
+}
+
+/**
+ * Tests a theme overriding a default hook with a suggestion.
+ */
+function test_theme_preprocess_theme_test_preprocess_suggestions__suggestion(&$variables) {
+  $variables['foo'] = 'Theme hook implementor=test_theme_preprocess_theme_test_preprocess_suggestions__suggestion().';
+}
+
diff --git a/core/themes/bartik/templates/theme-test-preprocess-suggestions--suggestion.html.twig b/core/themes/bartik/templates/theme-test-preprocess-suggestions--suggestion.html.twig
new file mode 100644
index 0000000..db8a8a4
--- /dev/null
+++ b/core/themes/bartik/templates/theme-test-preprocess-suggestions--suggestion.html.twig
@@ -0,0 +1 @@
+{{ foo }}
