The ev6's two clusters keep separate register files that are resynchronized
once a cycle, so a value produced in one and consumed in the other arrives a
cycle late. Which cluster an instruction issues to follows from the
subcluster that Table 2-3 of the Compiler Writer's Guide slots it into, and
that depends on its position in the aligned octaword. Moving a loop body by
a single instruction can therefore put the two ends of a dependence on the
same side instead of opposite sides.
On an EV68AL, a conditional-move reduction and a shift/xor mixing loop each
run at 4.00 cycles per iteration with -mtune=ev6, against 3.00 with
-mtune=ev4 or -mtune=ev5, and inserting one nop recovers the cycle. A nop is
discarded at decode, so it changes only where the following instructions sit.
One of the loops has no cmov, so the cause is not the n+1 fetch rule for
cmovs in section 3.4.4.2. What differs is the subcluster Table 2-3 gives the
producer of the critical dependence.
Pad the loop with nops rather than reorder it. Reordering after scheduling
would have to rebuild the dependence graph and could not be undone if the
model guessed wrong, whereas padding can only shift alignment. Reordering
within an octaword cannot change throughput in any case: the number of
instructions slotted upper and lower is fixed by the multiset of their
requirements.
For each short loop with a single branch, try 0 to 3 nops after the loop
label. A pad is used only if it leaves the number of fetch cycles unchanged
and strictly reduces the number of split dependences. The fetch cost is paid
on every iteration, but a split saving depends on the model guessing the
hardware right. The fetch model counts one branch predicted per cycle
(section 3.4.3) and one extra cycle per cmov (section 3.4.4.2).
Only dependences that bound the loop count: those on a dependence cycle, and
those on a longest path through the body, weighted by insn_default_latency.
Other dependences are hidden by the issue queue, so repairing them costs fetch
bandwidth for nothing. Three further restrictions came from measurements:
- Only integer register dependences count. Floating-point values are
produced and consumed in the Fbox, which is not clustered.
- Loops containing a count/MVI or multiply insn are skipped. Those units
exist in one cluster each, so the cluster follows from the unit and not
from the subcluster.
- Loops with more than one branch are skipped. They are already at the
fetch limit. Padding the two-branch loops of Ackermann's function cost
1.00% +/- 0.22% where the model predicted a gain.
-mno-cluster-align turns the pass off and gives the same output as before.
Of the 54 single-file C benchmarks in llvm-test-suite, the pass changes the
code of 12. Over three interleaved sessions of min-of-7 whole-program cycles,
the nine whose timings are stable to within 5% have a geomean of 0.9962 with a
95% CI of [0.9948, 0.9977]. lowercase is 3.3% faster and huffbench 0.2%
faster; nothing regresses distinguishably. The other three are too short or
too noisy to time; including them gives 0.9929, CI [0.9797, 1.0060]. The gain
on typical code is small, and the pass is aimed at the loops that lose 25%.
Testing: 1574 gcc.c-torture/execute cases compile with the same ok and fail
counts with the option on and off. 300 of them were built and run on
hardware with 296 passing; the four failures also occur with -mtune=ev4,
-mtune=ev5 and -O0. All 56 llvm-test-suite benchmarks that run produce
identical output with and without the pass.
gcc/ChangeLog:
* config/alpha/alpha.opt (mcluster-align): New option.
* config/alpha/alpha.opt.urls: Regenerate.
* config/alpha/alpha.cc (ev6_slotting): New table.
(alpha_ev6_slot_req, alpha_ev6_depends_p, alpha_ev6_dependences)
(alpha_ev6_critical_pairs, alpha_ev6_split_deps)
(alpha_ev6_fetch_cycles, alpha_ev6_cost_at, alpha_ev6_loop_body)
(alpha_ev6_align_insns): New functions.
(alpha_align_insns): Call alpha_ev6_align_insns for ev6 when
alpha_cluster_align.
(pass_align_insns::gate): Enable for ev6.
* config/alpha/ev6.md: Replace the note on instruction order and
cluster issue.
* doc/invoke.texi (DEC Alpha Options): Document -mcluster-align.
gcc/testsuite/ChangeLog:
* gcc.target/alpha/cluster-align.c: New test.
* gcc.target/alpha/cluster-align-2.c: New test.
---
gcc/config/alpha/alpha.cc | 395 ++++++++++++++++++++++-
gcc/config/alpha/alpha.opt | 4 +
gcc/config/alpha/alpha.opt.urls | 3 +
gcc/config/alpha/ev6.md | 6 +-
gcc/doc/invoke.texi | 18 ++
gcc/testsuite/gcc.target/alpha/cluster-align-2.c | 23 ++
gcc/testsuite/gcc.target/alpha/cluster-align.c | 27 ++
7 files changed, 474 insertions(+), 2 deletions(-)
diff --git a/gcc/config/alpha/alpha.cc b/gcc/config/alpha/alpha.cc
index d0fb7eee92f..9518f977398 100644
--- a/gcc/config/alpha/alpha.cc
+++ b/gcc/config/alpha/alpha.cc
@@ -303,7 +303,8 @@ public:
alignment when trap precision is instruction. Moreover, we can
only do our job when sched2 is run. */
return ((alpha_tune == PROCESSOR_EV4
- || alpha_tune == PROCESSOR_EV5)
+ || alpha_tune == PROCESSOR_EV5
+ || alpha_tune == PROCESSOR_EV6)
&& optimize && !optimize_size
&& alpha_tp != ALPHA_TP_INSN
&& flag_schedule_insns_after_reload);
@@ -10172,6 +10173,393 @@ alpha_align_insns_1 (unsigned int max_align,
}
}
+/* Ebox slotting for the ev6, from Table 2-3 of the Compiler Writer's Guide.
+
+ The subcluster an instruction issues to is decided at map time from its
+ opcode and its position in the aligned octaword. The table is indexed by
+ the subcluster requirements of the four positions, slot 0 first, as a
+ base-3 number (see enum ev6_req). Each value is a bitmask over the four
+ slots, set where the instruction is slotted upper. */
+
+static const unsigned char ev6_slotting[81] = {
+ 0xf, 0xe, 0xe, 0xd, 0xc, 0xc, 0xd, 0xc, 0xc,
+ 0xb, 0xa, 0xa, 0x9, 0x8, 0x9, 0x9, 0xa, 0xa,
+ 0xb, 0xa, 0xa, 0x9, 0xc, 0x9, 0x9, 0xa, 0xa,
+ 0x7, 0x6, 0x6, 0x5, 0x4, 0x5, 0x5, 0x6, 0x5,
+ 0x3, 0x2, 0x3, 0x1, 0x0, 0x1, 0x3, 0x2, 0x3,
+ 0x3, 0x6, 0x6, 0x5, 0x4, 0x5, 0x5, 0x6, 0x5,
+ 0x7, 0x6, 0x6, 0x5, 0xc, 0x5, 0x5, 0x6, 0x5,
+ 0x3, 0xa, 0xa, 0x9, 0x8, 0x9, 0x9, 0xa, 0xa,
+ 0x3, 0xa, 0xa, 0x9, 0xc, 0x9, 0x9, 0xa, 0xa
+};
+
+enum ev6_req { EV6_REQ_UPPER, EV6_REQ_LOWER, EV6_REQ_EITHER };
+
+/* Longest loop body considered. A longer body has enough slack to absorb a
+ cross-cluster hop. */
+#define EV6_MAX_LOOP 32
+
+/* The body plus up to three padding unops. */
+#define EV6_MAX_BODY (EV6_MAX_LOOP + 3)
+
+/* A producer and consumer further apart than this do not see the hop, because
+ the issue queue hides it. */
+#define EV6_DEP_WINDOW 4
+
+/* A set of positions in a body. */
+typedef uint64_t ev6_set_t;
+
+/* Subcluster requirement of INSN, per Table 2-2.
+
+ Floating-point operates are not Ebox instructions, but they occupy a
+ position in the octaword and the slotting rule assigns them a subcluster
+ vacuously (Appendix B: "ADDT => L vacated slot for float"). They count as
+ "either", as does anything whose placement is not known. */
+
+static int
+alpha_ev6_slot_req (rtx_insn *insn)
+{
+ switch (get_attr_type (insn))
+ {
+ case TYPE_SHIFT:
+ case TYPE_IBR:
+ case TYPE_MVI:
+ case TYPE_IMUL:
+ return EV6_REQ_UPPER;
+
+ case TYPE_ILD:
+ case TYPE_FLD:
+ case TYPE_LDSYM:
+ case TYPE_IST:
+ case TYPE_FST:
+ case TYPE_LD_L:
+ case TYPE_ST_C:
+ case TYPE_JSR:
+ case TYPE_CALLPAL:
+ case TYPE_ITOF:
+ case TYPE_FTOI:
+ case TYPE_MB:
+ return EV6_REQ_LOWER;
+
+ default:
+ return EV6_REQ_EITHER;
+ }
+}
+
+/* True if CONS reads an integer register that PROD writes. This ignores
+ intervening redefinitions; a spurious edge can only cost an unnecessary
+ unop, never correctness. */
+
+static bool
+alpha_ev6_depends_p (rtx_insn *prod, rtx_insn *cons)
+{
+ rtx set = single_set (prod);
+
+ if (set == NULL_RTX || !REG_P (SET_DEST (set)))
+ return false;
+
+ /* Floating-point values are produced and consumed in the Fbox, which is not
+ clustered. Writes to $31 are discarded. */
+ if (REGNO (SET_DEST (set)) >= 32 || REGNO (SET_DEST (set)) == 31)
+ return false;
+
+ return reg_referenced_p (SET_DEST (set), PATTERN (cons));
+}
+
+/* Set DEP[i] to the positions whose insns read a register written by the insn
+ at position i. Padding positions have a null insn and no edges. */
+
+static void
+alpha_ev6_dependences (rtx_insn **insns, int n, ev6_set_t *dep)
+{
+ for (int i = 0; i < n; i++)
+ {
+ dep[i] = 0;
+ if (insns[i] == NULL)
+ continue;
+ for (int j = 0; j < n; j++)
+ if (i != j && insns[j] != NULL
+ && alpha_ev6_depends_p (insns[i], insns[j]))
+ dep[i] |= (ev6_set_t) 1 << j;
+ }
+}
+
+/* Set CRIT[i] to the positions that form a critical pair with position i.
+
+ A hop between clusters only slows the loop if it is on a path that bounds
+ the loop. A pair counts if the two insns are on a common dependence cycle,
+ or if the dependence between them lies on a longest path through the body.
+ The second case covers a loop with no recurrence that is still paced by a
+ chain running from one end of an iteration to the other.
+
+ Cycles: close DEP over the back edge with Warshall's algorithm and keep the
+ mutually reachable pairs. The result is written to a separate array
+ because narrowing REACH in place would change later rows.
+
+ Longest path: TOP[i] is the longest path ending at i and BOT[i] the longest
+ starting at i, over the intra-iteration edges, weighted by the default insn
+ latency. An edge is on a longest path when it lies on a path of length
+ BEST. */
+
+static void
+alpha_ev6_critical_pairs (rtx_insn **insns, int n, const ev6_set_t *dep,
+ ev6_set_t *crit)
+{
+ ev6_set_t reach[EV6_MAX_BODY];
+ int top[EV6_MAX_BODY], bot[EV6_MAX_BODY], lat[EV6_MAX_BODY];
+ int best = 0;
+
+ for (int i = 0; i < n; i++)
+ reach[i] = dep[i];
+ for (int k = 0; k < n; k++)
+ for (int i = 0; i < n; i++)
+ if (reach[i] & ((ev6_set_t) 1 << k))
+ reach[i] |= reach[k];
+
+ for (int i = 0; i < n; i++)
+ {
+ crit[i] = 0;
+ for (int j = 0; j < n; j++)
+ if (((reach[i] >> j) & 1) && ((reach[j] >> i) & 1))
+ crit[i] |= (ev6_set_t) 1 << j;
+ }
+
+ for (int i = 0; i < n; i++)
+ {
+ top[i] = bot[i] = 0;
+ lat[i] = insns[i] ? insn_default_latency (insns[i]) : 0;
+ }
+ for (int i = 0; i < n; i++)
+ for (int j = i + 1; j < n; j++)
+ if ((dep[i] >> j) & 1)
+ top[j] = MAX (top[j], top[i] + lat[i]);
+ for (int i = n - 1; i >= 0; i--)
+ for (int j = i + 1; j < n; j++)
+ if ((dep[i] >> j) & 1)
+ bot[i] = MAX (bot[i], bot[j] + lat[i]);
+ for (int i = 0; i < n; i++)
+ best = MAX (best, top[i] + bot[i]);
+
+ if (best > 0)
+ for (int i = 0; i < n; i++)
+ for (int j = i + 1; j < n; j++)
+ if (((dep[i] >> j) & 1) && top[i] + lat[i] + bot[j] == best)
+ {
+ crit[i] |= (ev6_set_t) 1 << j;
+ crit[j] |= (ev6_set_t) 1 << i;
+ }
+}
+
+/* Count the critical dependences that Table 2-3 slots into different
+ subclusters, and so charges an extra cycle.
+
+ REQS holds the subcluster requirement of each of the N positions and START
+ is the octaword slot of the first one. A dependence from the end of the
+ body to its start is included, since a loop-carried dependence is the one
+ that bounds the loop. */
+
+static int
+alpha_ev6_split_deps (const int *reqs, int n, int start, const ev6_set_t *dep,
+ const ev6_set_t *crit)
+{
+ unsigned char upper[EV6_MAX_BODY];
+ int cost = 0;
+
+ /* An aligned octaword is slotted as a unit, so walk the body in groups of
+ four from the octaword boundary at or before its start. Positions
+ outside the body belong to code that is not being looked at; treat them
+ as "either", which asks the table for no constraint. */
+ for (int i = -start; i < n; i += 4)
+ {
+ int idx = 0;
+
+ for (int k = 3; k >= 0; k--)
+ {
+ int p = i + k;
+ idx = idx * 3 + ((p >= 0 && p < n) ? reqs[p] : EV6_REQ_EITHER);
+ }
+ for (int k = 0; k < 4; k++)
+ if (i + k >= 0 && i + k < n)
+ upper[i + k] = (ev6_slotting[idx] >> k) & 1;
+ }
+
+ for (int i = 0; i < n; i++)
+ for (int j = 1; j <= EV6_DEP_WINDOW && j < n; j++)
+ {
+ int c = (i + j) % n;
+
+ if (upper[i] != upper[c] && (((dep[i] & crit[i]) >> c) & 1))
+ cost++;
+ }
+ return cost;
+}
+
+/* Cycles to fetch the octawords a body occupies, with the first position in
+ octaword slot START.
+
+ An aligned octaword normally arrives in one cycle, but only one branch is
+ predicted per cycle, so an octaword with n branches takes n (section
+ 3.4.3), and one with n CMOVs takes n+1 (section 3.4.4.2) because each CMOV
+ is cracked into two operations. Moving a body by one instruction can
+ change either count. */
+
+static int
+alpha_ev6_fetch_cycles (rtx_insn **insns, int n, int start)
+{
+ int cycles = 0;
+
+ for (int i = -start; i < n; i += 4)
+ {
+ int nbr = 0, ncmov = 0;
+
+ for (int k = 0; k < 4; k++)
+ {
+ int p = i + k;
+
+ if (p < 0 || p >= n || insns[p] == NULL)
+ continue;
+ if (JUMP_P (insns[p]))
+ nbr++;
+ else if (get_attr_type (insns[p]) == TYPE_ICMOV)
+ ncmov++;
+ }
+ cycles += MAX (1, nbr) + ncmov;
+ }
+ return cycles;
+}
+
+/* Model BODY, of N insns, with PAD leading unops and its first position in
+ octaword slot SLOT. Return the fetch cycles in *FETCH and the number of
+ critical dependences split across clusters in *SPLITS. */
+
+static void
+alpha_ev6_cost_at (rtx_insn **body, int n, int pad, int slot,
+ int *fetch, int *splits)
+{
+ rtx_insn *insns[EV6_MAX_BODY];
+ int reqs[EV6_MAX_BODY];
+ ev6_set_t dep[EV6_MAX_BODY], crit[EV6_MAX_BODY];
+
+ for (int k = 0; k < pad; k++)
+ {
+ /* The unop is LDQ_U, which is lower-only. */
+ reqs[k] = EV6_REQ_LOWER;
+ insns[k] = NULL;
+ }
+ for (int k = 0; k < n; k++)
+ {
+ reqs[pad + k] = alpha_ev6_slot_req (body[k]);
+ insns[pad + k] = body[k];
+ }
+ n += pad;
+
+ alpha_ev6_dependences (insns, n, dep);
+ alpha_ev6_critical_pairs (insns, n, dep, crit);
+ *fetch = alpha_ev6_fetch_cycles (insns, n, slot);
+ *splits = alpha_ev6_split_deps (reqs, n, slot, dep, crit);
+}
+
+/* Collect the insns of the loop that jumps from BRANCH back to LABEL into
+ BODY and return their number, or return 0 if the loop is not one to
+ consider. A loop is rejected if it is too short or too long, contains
+ something that cannot be modeled (a call, an inner label, an asm, an insn
+ of unknown length), has more than one branch, or uses the count/MVI or
+ multiply unit. Those units exist in a single cluster, so there the cluster
+ follows from the unit and not from the subcluster, and the upper/lower
+ model does not apply. A loop with several branches is already limited by
+ fetch, which the split model does not price. */
+
+static int
+alpha_ev6_loop_body (rtx_insn *label, rtx_insn *branch, rtx_insn **body)
+{
+ int n = 0, nbr = 0;
+
+ for (rtx_insn *i = NEXT_INSN (label); i && i != NEXT_INSN (branch);
+ i = NEXT_INSN (i))
+ {
+ if (LABEL_P (i) || CALL_P (i))
+ return 0;
+ if (!NONDEBUG_INSN_P (i))
+ continue;
+ if (GET_CODE (PATTERN (i)) == USE
+ || GET_CODE (PATTERN (i)) == CLOBBER
+ || asm_noperands (PATTERN (i)) >= 0
+ || recog_memoized (i) < 0
+ || get_attr_type (i) == TYPE_MULTI
+ || get_attr_type (i) == TYPE_MVI
+ || get_attr_type (i) == TYPE_IMUL
+ || n >= EV6_MAX_LOOP
+ || (JUMP_P (i) && ++nbr > 1))
+ return 0;
+ body[n++] = i;
+ }
+ return n >= 4 ? n : 0;
+}
+
+/* Place the bodies of short loops so that latency-critical dependences stay
+ within one cluster.
+
+ The ev6's two clusters keep separate register files that are resynchronized
+ once a cycle, so a value produced in one and consumed in the other arrives
+ a cycle late. Which cluster an instruction issues to follows from the
+ subcluster that Table 2-3 slots it into, which depends on its position in
+ the aligned octaword. Moving a loop body by one instruction can therefore
+ put the two ends of a dependence on the same side instead of opposite
+ sides. Ordering within an octaword cannot change throughput, so only the
+ placement of the whole body is adjusted, by padding with unops. An unop is
+ discarded at decode. */
+
+static void
+alpha_ev6_align_insns (void)
+{
+ int delta = 0;
+
+ shorten_branches (get_insns ());
+
+ for (rtx_insn *insn = get_insns (); insn; insn = NEXT_INSN (insn))
+ {
+ rtx_insn *body[EV6_MAX_LOOP], *label;
+ int n, slot, base_fetch, best_splits, best_pad = 0;
+
+ /* A loop is a jump back to a label already passed. */
+ if (!JUMP_P (insn) || !JUMP_LABEL (insn) || !LABEL_P (JUMP_LABEL (insn)))
+ continue;
+ label = as_a <rtx_insn *> (JUMP_LABEL (insn));
+ if (INSN_ADDRESSES (INSN_UID (label))
+ > INSN_ADDRESSES (INSN_UID (insn)))
+ continue;
+
+ n = alpha_ev6_loop_body (label, insn, body);
+ if (n == 0)
+ continue;
+
+ slot = ((INSN_ADDRESSES (INSN_UID (body[0])) + delta) / 4) & 3;
+
+ /* Try padding the body with 0 to 3 unops after the label. A unop is
+ LDQ_U, which is lower-only, so it changes the slotting of its
+ octaword as well as shifting what follows. Accept a pad only if it
+ leaves the fetch cycles unchanged and strictly reduces the number of
+ split dependences. The fetch cost is paid on every iteration, while
+ a split saving depends on the model guessing the hardware right. */
+ alpha_ev6_cost_at (body, n, 0, slot, &base_fetch, &best_splits);
+ for (int pad = 1; pad <= 3; pad++)
+ {
+ int fetch, splits;
+
+ alpha_ev6_cost_at (body, n, pad, slot, &fetch, &splits);
+ if (fetch == base_fetch && splits < best_splits)
+ {
+ best_splits = splits;
+ best_pad = pad;
+ }
+ }
+
+ for (int k = 0; k < best_pad; k++)
+ emit_insn_after (gen_unop (), label);
+ delta += 4 * best_pad;
+ }
+}
+
static void
alpha_align_insns (void)
{
@@ -10179,6 +10567,11 @@ alpha_align_insns (void)
alpha_align_insns_1 (8, alphaev4_next_group, alphaev4_next_nop);
else if (alpha_tune == PROCESSOR_EV5)
alpha_align_insns_1 (16, alphaev5_next_group, alphaev5_next_nop);
+ else if (alpha_tune == PROCESSOR_EV6)
+ {
+ if (alpha_cluster_align)
+ alpha_ev6_align_insns ();
+ }
else
gcc_unreachable ();
}
diff --git a/gcc/config/alpha/alpha.opt b/gcc/config/alpha/alpha.opt
index 73fab654036..e6d5af937af 100644
--- a/gcc/config/alpha/alpha.opt
+++ b/gcc/config/alpha/alpha.opt
@@ -136,3 +136,7 @@ Tune expected memory latency.
mtls-size=
Target RejectNegative Joined UInteger Var(alpha_tls_size) Init(32)
Specify bit size of immediate TLS offsets.
+
+mcluster-align
+Target Var(alpha_cluster_align) Init(1)
+Pad ev6 loop bodies so that latency-critical dependences stay within one Ebox
cluster.
diff --git a/gcc/config/alpha/alpha.opt.urls b/gcc/config/alpha/alpha.opt.urls
index e925a2b1ff6..fce87e9c882 100644
--- a/gcc/config/alpha/alpha.opt.urls
+++ b/gcc/config/alpha/alpha.opt.urls
@@ -89,3 +89,6 @@ UrlSuffix(gcc/DEC-Alpha-Options.html#index-mmemory-latency)
mtls-size=
UrlSuffix(gcc/DEC-Alpha-Options.html#index-mtls-size-1)
+mcluster-align
+UrlSuffix(gcc/DEC-Alpha-Options.html#index-mcluster-align)
+
diff --git a/gcc/config/alpha/ev6.md b/gcc/config/alpha/ev6.md
index edc2c9e0eff..221089b37be 100644
--- a/gcc/config/alpha/ev6.md
+++ b/gcc/config/alpha/ev6.md
@@ -32,7 +32,11 @@
; not two. The delay is only forced between units that exist in a single
; cluster, and is modeled below as bypasses between them.
;
-; ??? In addition, instruction order affects cluster issue.
+; Instruction order also affects cluster issue, through the slotting rules in
+; Table 2-3 of the Compiler Writer's Guide. The number of insns slotted upper
+; and lower depends only on the multiset of their requirements, so order cannot
+; change throughput. It can matter for a single latency-critical dependence,
+; which alpha_ev6_align_insns in alpha.cc tries to keep within one cluster.
(define_automaton "ev6_0,ev6_1")
(define_cpu_unit "ev6_u0,ev6_u1,ev6_l0,ev6_l1" "ev6_0")
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index ef9cb025c22..ebb01d0cb9c 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -1083,6 +1083,7 @@ Objective-C and Objective-C++ Dialects}.
-mfloat-vax -mfloat-ieee
-mexplicit-relocs -msmall-data -mlarge-data
-msmall-text -mlarge-text
+-mcluster-align -mno-cluster-align
-mmemory-latency=@var{time}
-mtls-kernel -mtls-size=@var{bitsize}
-mlong-double-128 -mlong-double-64}
@@ -26639,6 +26640,23 @@ which selects the best architecture option for the
host processor.
@option{-mtune=native} has no effect if GCC does not recognize
the processor.
+@opindex mcluster-align
+@opindex mno-cluster-align
+@item -mcluster-align
+@itemx -mno-cluster-align
+Pad the bodies of short loops with UNOPs so that the two ends of a
+latency-critical dependence are slotted into the same Ebox cluster. Enabled by
+default, and effective only with @option{-mtune=ev6}.
+
+The 21264 has two integer clusters whose register files are resynchronized once
+per cycle, so a value produced in one cluster and consumed in the other arrives
+a cycle late. Which cluster an instruction issues to follows from the
+subcluster it is slotted into, which in turn depends on its position within the
+aligned octaword. Moving a loop body by one instruction can therefore decide
+whether a dependence pays the extra cycle. Padding is used only when the
+compiler's model predicts that it does not increase the number of fetch cycles
+the loop needs.
+
@opindex mmemory-latency
@item -mmemory-latency=@var{time}
Sets the latency the scheduler should assume for typical memory
diff --git a/gcc/testsuite/gcc.target/alpha/cluster-align-2.c
b/gcc/testsuite/gcc.target/alpha/cluster-align-2.c
new file mode 100644
index 00000000000..f2dfd6555ae
--- /dev/null
+++ b/gcc/testsuite/gcc.target/alpha/cluster-align-2.c
@@ -0,0 +1,23 @@
+/* -mno-cluster-align must suppress the ev6 padding pass entirely. Same loop
+ as cluster-align.c, which does get padded with the option left at its
+ default. */
+
+/* { dg-do compile } */
+/* { dg-options "-O2 -mcpu=ev67 -mtune=ev6 -mno-cluster-align" } */
+
+long
+f (const long *x, long n)
+{
+ long s = 0, i;
+
+ for (i = 0; i < n; i++)
+ {
+ if (x[i] & 1)
+ s += x[i];
+ else
+ s ^= x[i];
+ }
+ return s;
+}
+
+/* { dg-final { scan-assembler-not "ldq_u\\s+\\\$31" } } */
diff --git a/gcc/testsuite/gcc.target/alpha/cluster-align.c
b/gcc/testsuite/gcc.target/alpha/cluster-align.c
new file mode 100644
index 00000000000..3bfc1500619
--- /dev/null
+++ b/gcc/testsuite/gcc.target/alpha/cluster-align.c
@@ -0,0 +1,27 @@
+/* Check that -mtune=ev6 pads a loop body whose critical dependence would
+ otherwise be slotted across the two Ebox clusters. The loop is a
+ conditional-move reduction: the add feeding the cmov and the cmov land in
+ different subclusters unless the body is shifted by one instruction. */
+
+/* { dg-do compile } */
+/* { dg-options "-O2 -mcpu=ev67 -mtune=ev6" } */
+/* The unrolled loop has a different shape. */
+/* { dg-skip-if "" { *-*-* } { "-funroll-loops" } { "" } } */
+
+long
+f (const long *x, long n)
+{
+ long s = 0, i;
+
+ for (i = 0; i < n; i++)
+ {
+ if (x[i] & 1)
+ s += x[i];
+ else
+ s ^= x[i];
+ }
+ return s;
+}
+
+/* The padding is UNOP, which is LDQ_U into $31. */
+/* { dg-final { scan-assembler "ldq_u\\s+\\\$31" } } */
--
2.56.1