diff --git includes/common.inc includes/common.inc
index 75953a0..8bd0b37 100644
--- includes/common.inc
+++ includes/common.inc
@@ -4620,6 +4620,12 @@ function drupal_system_listing($mask, $directory, $key = 'name', $min_depth = 1)
  * @param $type
  *   A string describing the data type of the alterable $data. 'form', 'links',
  *   'node_content', and so on are several examples.
+ *   Alternatively, can be an array, in which case hook_TYPE_alter() is invoked
+ *   for each value in the array, ordered first by module, and then for each
+ *   module, in the order of values in $type. For example, when the Drupal Form
+ *   API is using drupal_alter() to execute both hook_form_alter() and
+ *   hook_form_FORM_ID_alter() implementations, it passes
+ *   array('form', 'form_' . $form_id) for this parameter.
  * @param &$data
  *   The primary data to be altered.
  * @param &$context1
@@ -4637,14 +4643,69 @@ function drupal_alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
   }
   $functions = &$drupal_static_fast['functions'];
 
+  // Most of the time, $type is passed as a string, so for performance,
+  // normalize it to that. When passed as an array, usually the first item in
+  // the array is a generic type, and additional items in the array are more
+  // specific variants of it, as in the case of array('form', 'form_FORM_ID').
+  if (is_array($type)) {
+    $cid = implode(',', $type);
+    $extra_types = $type;
+    $type = array_shift($extra_types);
+    // Allow if statements in this function to use the faster isset() rather
+    // than !empty() both when $type is passed as a string, or as an array with
+    // one item.
+    if (empty($extra_types)) {
+      unset($extra_types);
+    }
+  }
+  else {
+    $cid = $type;
+  }
+
   // Some alter hooks are invoked many times per page request, so statically
   // cache the list of functions to call, and on subsequent calls, iterate
   // through them quickly.
-  if (!isset($functions[$type])) {
-    $functions[$type] = array();
+  if (!isset($functions[$cid])) {
+    $functions[$cid] = array();
     $hook = $type . '_alter';
-    foreach (module_implements($hook) as $module) {
-      $functions[$type][] = $module . '_' . $hook;
+    $modules = module_implements($hook);
+    if (!isset($extra_types)) {
+      // For the more common case of a single hook, we do not need to call
+      // function_exists(), since module_implements() returns only modules with
+      // implementations.
+      foreach ($modules as $module) {
+        $functions[$cid][] = $module . '_' . $hook;
+      }
+    }
+    else {
+      // For multiple hooks, we need $modules to contain every module that
+      // implements at least one of them.
+      $extra_modules = array();
+      foreach ($extra_types as $extra_type) {
+        $extra_modules = array_merge($extra_modules, module_implements($extra_type . '_alter'));
+      }
+      // If any modules implement one of the extra hooks that do not implement
+      // the primary hook, we need to add them to the $modules array in their
+      // appropriate order.
+      if (array_diff($extra_modules, $modules)) {
+        // Order the modules by the order returned by module_list().
+        $modules = array_intersect(module_list(), array_merge($modules, $extra_modules));
+      }
+      foreach ($modules as $module) {
+        // Since $modules is a merged array, for any given module, we do not
+        // know whether it has any particular implementation, so we need a
+        // function_exists().
+        $function = $module . '_' . $hook;
+        if (function_exists($function)) {
+          $functions[$cid][] = $function;
+        }
+        foreach ($extra_types as $extra_type) {
+          $function = $module . '_' . $extra_type . '_alter';
+          if (function_exists($function)) {
+            $functions[$cid][] = $function;
+          }
+        }
+      }
     }
     // Allow the theme to alter variables after the theme system has been
     // initialized.
@@ -4658,12 +4719,20 @@ function drupal_alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
       foreach ($theme_keys as $theme_key) {
         $function = $theme_key . '_' . $hook;
         if (function_exists($function)) {
-          $functions[$type][] = $function;
+          $functions[$cid][] = $function;
+        }
+        if (isset($extra_types)) {
+          foreach ($extra_types as $extra_type) {
+            $function = $theme_key . '_' . $extra_type . '_alter';
+            if (function_exists($function)) {
+              $functions[$cid][] = $function;
+            }
+          }
         }
       }
     }
   }
-  foreach ($functions[$type] as $function) {
+  foreach ($functions[$cid] as $function) {
     $function($data, $context1, $context2);
   }
 }
diff --git includes/database/select.inc includes/database/select.inc
index 61c85c6..2870838 100644
--- includes/database/select.inc
+++ includes/database/select.inc
@@ -1069,10 +1069,11 @@ class SelectQuery extends Query implements SelectQueryInterface {
 
     // Modules may alter all queries or only those having a particular tag.
     if (isset($this->alterTags)) {
-      drupal_alter('query', $query);
+      $hooks = array('query');
       foreach ($this->alterTags as $tag => $value) {
-        drupal_alter("query_$tag", $query);
+        $hooks[] = 'query_' . $tag;
       }
+      drupal_alter($hooks, $query);
     }
     return $this->prepared = TRUE;
   }
diff --git includes/form.inc includes/form.inc
index 82835a7..c3e96e7 100644
--- includes/form.inc
+++ includes/form.inc
@@ -761,11 +761,8 @@ function drupal_prepare_form($form_id, &$form, &$form_state) {
     }
   }
 
-  // Invoke hook_form_FORM_ID_alter() implementations.
-  drupal_alter('form_' . $form_id, $form, $form_state);
-
-  // Invoke hook_form_alter() implementations.
-  drupal_alter('form', $form, $form_state, $form_id);
+  // Invoke hook_form_alter() and hook_form_FORM_ID_alter() implementations.
+  drupal_alter(array('form', 'form_' . $form_id), $form, $form_state, $form_id);
 }
 
 
diff --git modules/block/block.api.php modules/block/block.api.php
index 8bdab9f..8dbe2a1 100644
--- modules/block/block.api.php
+++ modules/block/block.api.php
@@ -184,15 +184,15 @@ function hook_block_view($delta = '') {
  *   the module that defined the block:
  *   - subject: The localized title of the block.
  *   - content: Either a string or a renderable array representing the content
- *     of the block. You should check that the content is an array before trying
- *     to modify parts of the renderable structure.
+ *     of the block. You should check that the content is an array before
+ *     trying to modify parts of the renderable structure.
  * @param $block
  *   The block object, as loaded from the database, having the main properties:
  *   - module: The name of the module that defined the block.
- *   - delta: The identifier for the block within that module, as defined within
- *     hook_block_info().
+ *   - delta: The identifier for the block within that module, as defined
+ *     within hook_block_info().
  *
- * @see hook_block_view_alter()
+ * @see hook_block_view_MODULE_DELTA_alter()
  * @see hook_block_view()
  */
 function hook_block_view_alter(&$data, $block) {
@@ -213,22 +213,18 @@ function hook_block_view_alter(&$data, $block) {
  * Modules can implement hook_block_view_MODULE_DELTA_alter() to modify a
  * specific block, rather than implementing hook_block_view_alter().
  *
- * Note that this hook fires before hook_block_view_alter(). Therefore, all
- * implementations of hook_block_view_MODULE_DELTA_alter() will run before all
- * implementations of hook_block_view_alter(), regardless of the module order.
- *
  * @param $data
  *   An array of data, as returned from the hook_block_view() implementation of
  *   the module that defined the block:
  *   - subject: The localized title of the block.
  *   - content: Either a string or a renderable array representing the content
- *     of the block. You should check that the content is an array before trying
- *     to modify parts of the renderable structure.
+ *     of the block. You should check that the content is an array before
+ *     trying to modify parts of the renderable structure.
  * @param $block
  *   The block object, as loaded from the database, having the main properties:
  *   - module: The name of the module that defined the block.
- *   - delta: The identifier for the block within that module, as defined within
- *     hook_block_info().
+ *   - delta: The identifier for the block within that module, as defined
+ *     within hook_block_info().
  *
  * @see hook_block_view_alter()
  * @see hook_block_view()
diff --git modules/block/block.module modules/block/block.module
index 82cb11e..7c43543 100644
--- modules/block/block.module
+++ modules/block/block.module
@@ -758,9 +758,8 @@ function _block_render_blocks($region_blocks) {
         $array = module_invoke($block->module, 'block_view', $block->delta);
 
         // Allow modules to modify the block before it is viewed, via either
-        // hook_block_view_MODULE_DELTA_alter() or hook_block_view_alter().
-        drupal_alter("block_view_{$block->module}_{$block->delta}", $array, $block);
-        drupal_alter('block_view', $array, $block);
+        // hook_block_view_alter() or hook_block_view_MODULE_DELTA_alter().
+        drupal_alter(array('block_view', "block_view_{$block->module}_{$block->delta}"), $array, $block);
 
         if (isset($cid)) {
           cache_set($cid, $array, 'cache_block', CACHE_TEMPORARY);
diff --git modules/simpletest/tests/form.test modules/simpletest/tests/form.test
index cf4765f..f172c81 100644
--- modules/simpletest/tests/form.test
+++ modules/simpletest/tests/form.test
@@ -210,6 +210,40 @@ class FormsTestCase extends DrupalWebTestCase {
 }
 
 /**
+ * Test form alter hooks.
+ */
+class FormAlterTestCase extends DrupalWebTestCase {
+  public static function getInfo() {
+    return array(
+      'name' => 'Form alter hooks',
+      'description' => 'Tests hook_form_alter() and hook_form_FORM_ID_alter().',
+      'group' => 'Form API',
+    );
+  }
+
+  function setUp() {
+    parent::setUp('form_test');
+  }
+
+  /**
+   * Tests execution order of hook_form_alter() and hook_form_FORM_ID_alter().
+   */
+  function testExecutionOrder() {
+    $this->drupalGet('form-test/alter');
+    // Ensure that the order is first by module, then for a given module, the
+    // id-specific one after the generic one.
+    $expected = array(
+      'block_form_form_test_alter_form_alter() executed.',
+      'form_test_form_alter() executed.',
+      'form_test_form_form_test_alter_form_alter() executed.',
+      'system_form_form_test_alter_form_alter() executed.',
+    );
+    $content = preg_replace('/\s+/', ' ', filter_xss($this->content, array()));
+    $this->assert(strpos($content, implode(' ', $expected)) !== FALSE, t('Form alter hooks executed in the expected order.'));
+  }
+}
+
+/**
  * Test form validation handlers.
  */
 class FormValidationTestCase extends DrupalWebTestCase {
diff --git modules/simpletest/tests/form_test.module modules/simpletest/tests/form_test.module
index 9f8094c..1b91c7b 100644
--- modules/simpletest/tests/form_test.module
+++ modules/simpletest/tests/form_test.module
@@ -10,6 +10,13 @@
  * Implements hook_menu().
  */
 function form_test_menu() {
+  $items['form-test/alter'] = array(
+    'title' => 'Form altering test',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('form_test_alter_form'),
+    'access arguments' => array('access content'),
+    'type' => MENU_CALLBACK,
+  );
   $items['form-test/validate'] = array(
     'title' => 'Form validation handlers test',
     'page callback' => 'drupal_get_form',
@@ -142,6 +149,46 @@ function form_test_menu() {
 }
 
 /**
+ * Form builder for testing hook_form_alter() and hook_form_FORM_ID_alter().
+ */
+function form_test_alter_form($form, &$form_state) {
+  // Elements can be added as needed for future testing needs, but for now,
+  // we're only testing alter hooks that do not require any elements added by
+  // this function.
+  return $form;
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter() on behalf of block.module.
+ */
+function block_form_form_test_alter_form_alter(&$form, &$form_state) {
+  drupal_set_message('block_form_form_test_alter_form_alter() executed.');
+}
+
+/**
+ * Implements hook_form_alter().
+ */
+function form_test_form_alter(&$form, &$form_state, $form_id) {
+  if ($form_id == 'form_test_alter_form') {
+    drupal_set_message('form_test_form_alter() executed.');
+  }
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function form_test_form_form_test_alter_form_alter(&$form, &$form_state) {
+  drupal_set_message('form_test_form_form_test_alter_form_alter() executed.');
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter() on behalf of system.module.
+ */
+function system_form_form_test_alter_form_alter(&$form, &$form_state) {
+  drupal_set_message('system_form_form_test_alter_form_alter() executed.');
+}
+
+/**
  * Form builder for testing drupal_validate_form().
  *
  * Serves for testing form processing and alterations by form validation
diff --git modules/system/system.api.php modules/system/system.api.php
index a881083..6cf377a 100644
--- modules/system/system.api.php
+++ modules/system/system.api.php
@@ -746,7 +746,11 @@ function hook_page_alter(&$page) {
  * altering a node form, the node object retrieved at from $form['#node'].
  *
  * Note that instead of hook_form_alter(), which is called for all forms, you
- * can also use hook_form_FORM_ID_alter() to alter a specific form.
+ * can also use hook_form_FORM_ID_alter() to alter a specific form. For each
+ * module (in system weight order) the general form alter hook implementation
+ * is invoked first, then the form ID specific alter implementation is called.
+ * After all module hook implementations are invoked, the hook_form_alter()
+ * implementations from themes are invoked in the same manner.
  *
  * @param $form
  *   Nested array of form elements that comprise the form.
@@ -755,6 +759,8 @@ function hook_page_alter(&$page) {
  * @param $form_id
  *   String representing the name of the form itself. Typically this is the
  *   name of the function that generated the form.
+ *
+ * @see hook_form_FORM_ID_alter()
  */
 function hook_form_alter(&$form, &$form_state, $form_id) {
   if (isset($form['type']) && $form['type']['#value'] . '_node_settings' == $form_id) {
@@ -774,15 +780,12 @@ function hook_form_alter(&$form, &$form_state, $form_id) {
  * rather than implementing hook_form_alter() and checking the form ID, or
  * using long switch statements to alter multiple forms.
  *
- * Note that this hook fires before hook_form_alter(). Therefore all
- * implementations of hook_form_FORM_ID_alter() will run before all implementations
- * of hook_form_alter(), regardless of the module order.
- *
  * @param $form
  *   Nested array of form elements that comprise the form.
  * @param $form_state
  *   A keyed array containing the current state of the form.
  *
+ * @see hook_form_alter()
  * @see drupal_prepare_form()
  */
 function hook_form_FORM_ID_alter(&$form, &$form_state) {
diff --git sites/default/default.settings.php sites/default/default.settings.php
old mode 100644
new mode 100755
