diff --git a/core/lib/Drupal/Core/Theme/Registry.php b/core/lib/Drupal/Core/Theme/Registry.php
index 5e450ab..9459bef 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.
+        // preprocessors from being used. This allows themes or theme engines to
+        // remove variable preprocessors set earlier in the registry build.
+        // @todo check if caching is broken.
         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..268d2a6 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 array $info
+   * @param array $hook
+   * @param array $suggestions
+   *
+   */
+  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/RegistryTest.php b/core/modules/system/src/Tests/Theme/RegistryTest.php
index 763372e..b9104ee 100644
--- a/core/modules/system/src/Tests/Theme/RegistryTest.php
+++ b/core/modules/system/src/Tests/Theme/RegistryTest.php
@@ -75,42 +75,6 @@ function testRaceCondition() {
   }
 
   /**
-   * Tests the theme registry with multiple subthemes.
-   */
-  public function testMultipleSubThemes() {
-    $theme_handler = \Drupal::service('theme_handler');
-    $theme_handler->install(['test_basetheme', 'test_subtheme', 'test_subsubtheme']);
-
-    $registry_subsub_theme = new Registry(\Drupal::root(), \Drupal::cache(), \Drupal::lock(), \Drupal::moduleHandler(), $theme_handler, \Drupal::service('theme.initialization'), 'test_subsubtheme');
-    $registry_subsub_theme->setThemeManager(\Drupal::theme());
-    $registry_sub_theme = new Registry(\Drupal::root(), \Drupal::cache(), \Drupal::lock(), \Drupal::moduleHandler(), $theme_handler, \Drupal::service('theme.initialization'), 'test_subtheme');
-    $registry_sub_theme->setThemeManager(\Drupal::theme());
-    $registry_base_theme = new Registry(\Drupal::root(), \Drupal::cache(), \Drupal::lock(), \Drupal::moduleHandler(), $theme_handler, \Drupal::service('theme.initialization'), 'test_basetheme');
-    $registry_base_theme->setThemeManager(\Drupal::theme());
-
-    $preprocess_functions = $registry_subsub_theme->get()['theme_test_template_test']['preprocess functions'];
-    $this->assertIdentical([
-      'template_preprocess',
-      'test_basetheme_preprocess_theme_test_template_test',
-      'test_subtheme_preprocess_theme_test_template_test',
-      'test_subsubtheme_preprocess_theme_test_template_test',
-    ], $preprocess_functions);
-
-    $preprocess_functions = $registry_sub_theme->get()['theme_test_template_test']['preprocess functions'];
-    $this->assertIdentical([
-      'template_preprocess',
-      'test_basetheme_preprocess_theme_test_template_test',
-      'test_subtheme_preprocess_theme_test_template_test',
-    ], $preprocess_functions);
-
-    $preprocess_functions = $registry_base_theme->get()['theme_test_template_test']['preprocess functions'];
-    $this->assertIdentical([
-      'template_preprocess',
-      'test_basetheme_preprocess_theme_test_template_test',
-    ], $preprocess_functions);
-  }
-
-  /**
    * Tests that the theme registry can be altered by themes.
    */
   public function testThemeRegistryAlterByTheme() {
diff --git a/core/modules/system/src/Tests/Theme/ThemeTest.php b/core/modules/system/src/Tests/Theme/ThemeTest.php
index 0cd4e4e..fba91cc 100644
--- a/core/modules/system/src/Tests/Theme/ThemeTest.php
+++ b/core/modules/system/src/Tests/Theme/ThemeTest.php
@@ -287,4 +287,23 @@ 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() {
+    $this->config('system.theme')
+      ->set('default', 'test_theme')
+      ->save();
+    // 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_preprocess_theme_test_preprocess_suggestions__suggestion().', '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 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 }}
