diff --git a/core/lib/Drupal/Core/Theme/Registry.php b/core/lib/Drupal/Core/Theme/Registry.php
index 5e450ab..38ab0b7 100644
--- a/core/lib/Drupal/Core/Theme/Registry.php
+++ b/core/lib/Drupal/Core/Theme/Registry.php
@@ -340,9 +340,12 @@ protected function build() {
       $this->processExtension($cache, $this->theme->getEngine(), 'theme_engine', $this->theme->getName(), $this->theme->getPath());
     }
 
-    // Finally, hooks provided by the theme itself.
+    // Hooks provided by the theme itself.
     $this->processExtension($cache, $this->theme->getName(), 'theme', $this->theme->getName(), $this->theme->getPath());
 
+    // Discover and add all preprocess functions for theme hook suggestions.
+    $this->postProcessExtension($cache, $this->theme);
+
     // Let modules and themes alter the registry.
     $this->moduleHandler->alter('theme_registry', $cache);
     $this->themeManager->alterForTheme($this->theme, 'theme_registry', $cache);
@@ -554,14 +557,106 @@ protected function processExtension(array &$cache, $name, $type, $theme, $path)
             $cache[$hook]['preprocess functions'][] = $name . '_preprocess_' . $hook;
             $cache[$hook]['theme path'] = $path;
           }
-          // Ensure uniqueness.
-          $cache[$hook]['preprocess functions'] = array_unique($cache[$hook]['preprocess functions']);
         }
       }
     }
   }
 
   /**
+   * This completes the theme registry adding discovered functions and hooks.
+   *
+   * @param array $cache
+   *   The theme registry.
+   * @param \Drupal\Core\Theme\ActiveTheme $theme
+   *   Current active theme.
+   *
+   * @see ::processExtension()
+   */
+  protected function postProcessExtension(array &$cache, ActiveTheme $theme) {
+    $grouped_functions = drupal_group_functions_by_prefix();
+
+    // Gather prefixes. This will be used to limit the found functions to the
+    // expected naming conventions.
+    $prefixes = array_keys((array) $this->moduleHandler->getModuleList());
+    foreach (array_reverse($theme->getBaseThemes()) as $base) {
+      $prefixes[] = $base->getName();
+    }
+    if ($theme->getEngine()) {
+      $prefixes[] = $theme->getEngine() . '_engine';
+    }
+    $prefixes[] = $theme->getName();
+
+    // Collect all variable processor functions in the correct order.
+    $processors = [];
+    $matches = [];
+    // Look for functions named according to the pattern and add them if they
+    // have matching hooks in the registry.
+    foreach ($prefixes as $prefix) {
+      // Grep only the functions which are within the prefix group.
+      list($first_prefix,) = explode('_', $prefix, 2);
+      if (!isset($grouped_functions[$first_prefix])) {
+        continue;
+      }
+      // Add the function and the name of the associated theme hook to the list
+      // of processors if a matching base hook is found.
+      foreach ($grouped_functions[$first_prefix] as $candidate) {
+        if (preg_match("/^{$prefix}_preprocess_(((?:[^_]++|_(?!_))+)__.*)/", $candidate, $matches)) {
+          if (isset($cache[$matches[2]])) {
+            $processors[$candidate] = $matches[1];
+          }
+        }
+      }
+    }
+
+    // Add missing variable processors. This is needed for hooks that do not
+    // explicitly register the hook. For example, when a theme contains a
+    // variable process function but it does not implement a template, it will
+    // go missing. This will add the expected function. It also allows modules
+    // or themes to have a variable process function based on a pattern even if
+    // the hook does not exist.
+    foreach ($processors as $processor => $hook) {
+      if (isset($cache[$hook]['preprocess functions']) && !in_array($hook, $cache[$hook]['preprocess functions'])) {
+        // Add missing processor to existing hook.
+        $cache[$hook]['preprocess functions'][] = $processor;
+      }
+      elseif (!isset($cache[$hook]) && strpos($hook, '__')) {
+        // Process non-existing hook and register it.
+        // Search for the base hook.
+        $base_hook = $hook;
+        while (!isset($cache[$base_hook]) && $pos = strrpos($base_hook, '__')) {
+          $base_hook = substr($base_hook, 0, $pos);
+          $cache[$base_hook]['preprocess functions'][] = $processor;
+          // If the current hook is based on a pattern, get the base hook.
+          if (isset($cache[$base_hook]['base hook'])) {
+            $base_hook = $cache[$base_hook]['base hook'];
+          }
+        }
+      }
+    }
+    // Inherit all base hook variable processors into pattern hooks.
+    // This ensures that derivative hooks have a complete set of variable
+    // process functions.
+    foreach ($cache as $hook => $info) {
+      // The 'base hook' is only applied to derivative hooks already registered
+      // from a pattern. This is typically set from
+      // drupal_find_theme_functions() and drupal_find_theme_templates().
+      if (isset($info['base hook']) && isset($cache[$info['base hook']]['preprocess functions'])) {
+        $diff = array_diff($cache[$info['base hook']]['preprocess functions'], $info['preprocess functions']);
+        $cache[$hook]['preprocess functions'] = array_merge($diff, $info['preprocess functions']);
+      }
+
+      // Optimize the registry.
+      if (isset($cache[$hook]['preprocess functions']) && empty($cache[$hook]['preprocess functions'])) {
+        unset($cache[$hook]['preprocess functions']);
+      }
+      // Ensure uniqueness.
+      if (isset($cache[$hook]['preprocess functions'])) {
+        $cache[$hook]['preprocess functions'] = array_unique($cache[$hook]['preprocess functions']);
+      }
+    }
+  }
+
+  /**
    * Invalidates theme registry caches.
    *
    * To be called when the list of enabled extensions is changed.
diff --git a/core/modules/system/src/Tests/Theme/ThemeTest.php b/core/modules/system/src/Tests/Theme/ThemeTest.php
index e62a199..1655c69 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 a8f16c6..8ae644a 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..7eeee9d 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,20 @@ 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');
+}
+
+/**
+ * Implements hook_preprocess_HOOK().
+ */
+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 9350755..7a0fced 100644
--- a/core/modules/system/tests/themes/test_theme/test_theme.theme
+++ b/core/modules/system/tests/themes/test_theme/test_theme.theme
@@ -105,3 +105,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().';
+}
+
