We would like to start pushing -mcpu=generic towards enabling the set of
features that improves performance for some CPUs, without hurting any
others. A blend of the performance options hopefully beneficial to all
CPUs. The largest part of that is enabling in-order scheduling using the
Cortex-A55 schedule model. This is similar to the Arm backend change
from eecb353d0e which made -mcpu=generic perform in-order scheduling
using the cortex-a8 schedule model.
The idea is that in-order cpu's require the most help in instruction
scheduling, whereas out-of-order cpus can for the most part out-of-order
schedule around different codegen. Our benchmarking suggests that
hypothesis holds. When running on an in-order core this improved
performance by 3.8% geomean on a set of DSP workloads, 2% geomean on
some other embedded benchmark and between 1% and 1.8% on a set of
singlecore and multicore workloads, all running on a Cortex-A55 cluster.
On an out-of-order cpu the results are a lot more noisy but show flat
performance or an improvement. On the set of DSP and embedded
benchmarks, run on a Cortex-A78 there was a very noisy 1% speed
improvement. Using the most detailed results I could find, SPEC2006 runs
on a Neoverse N1 show a small increase in instruction count (+0.127%),
but a decrease in cycle counts (-0.155%, on average). The instruction
count is very low noise, the cycle count is more noisy with a 0.15%
decrease not being significant. SPEC2k17 shows a small decrease (-0.2%)
in instruction count leading to a -0.296% decrease in cycle count. These
results are within noise margins but tend to show a small improvement in
general.
When specifying an Apple target, clang will set "-target-cpu apple-a7"
on the command line, so should not be affected by this change when
running from clang. This also doesn't enable more runtime unrolling like
-mcpu=cortex-a55 does, only changing the schedule used.
A lot of existing tests have updated. This is a summary of the important
differences:
- Most changes are the same instructions in a different order.
- Sometimes this leads to very minor inefficiencies, such as requiring
an extra mov to move variables into r0/v0 for the return value of a test
function.
- misched-fusion.ll was no longer fusing the pairs of instructions it
should, as per D110561. I've changed the schedule used in the test
for now.
- neon-mla-mls.ll now uses "mul; sub" as opposed to "neg; mla" due to
the different latencies. This seems fine to me.
- Some SVE tests do not always remove movprfx where they did before due
to different register allocation giving different destructive forms.
- The tests argument-blocks-array-of-struct.ll and arm64-windows-calls.ll
produce two LDR where they previously produced an LDP due to
store-pair-suppress kicking in.
- arm64-ldp.ll and arm64-neon-copy.ll are missing pre/postinc on LPD.
- Some tests such as arm64-neon-mul-div.ll and
ragreedy-local-interval-cost.ll have more, less or just different
spilling.
- In aarch64_generated_funcs.ll.generated.expected one part of the
function is no longer outlined. Interestingly if I switch this to use
any other scheduled even less is outlined.
Some of these are expected to happen, such as differences in outlining
or register spilling. There will be places where these result in worse
codegen, places where they are better, with the SPEC instruction counts
suggesting it is not a decrease overall, on average.
Differential Revision: https://reviews.llvm.org/D110830
203 lines
6.1 KiB
YAML
203 lines
6.1 KiB
YAML
# RUN: llc -verify-machineinstrs -mtriple=aarch64-none-linux-gnu \
|
|
# RUN: -start-before aarch64-speculation-hardening -o - %s \
|
|
# RUN: | FileCheck %s
|
|
|
|
# Check that the speculation hardening pass generates code as expected for
|
|
# basic blocks ending with a variety of branch patterns:
|
|
# - (1) no branches (fallthrough)
|
|
# - (2) one unconditional branch
|
|
# - (3) one conditional branch + fall-through
|
|
# - (4) one conditional branch + one unconditional branch
|
|
# - other direct branches don't seem to be generated by the AArch64 codegen
|
|
--- |
|
|
define void @nobranch_fallthrough(i32 %a, i32 %b) speculative_load_hardening {
|
|
ret void
|
|
}
|
|
define void @uncondbranch(i32 %a, i32 %b) speculative_load_hardening {
|
|
ret void
|
|
}
|
|
define void @condbranch_fallthrough(i32 %a, i32 %b) speculative_load_hardening {
|
|
ret void
|
|
}
|
|
define void @condbranch_uncondbranch(i32 %a, i32 %b) speculative_load_hardening {
|
|
ret void
|
|
}
|
|
define void @indirectbranch(i32 %a, i32 %b) speculative_load_hardening {
|
|
ret void
|
|
}
|
|
; Also check that a non-default temporary register gets picked correctly to
|
|
; transfer the SP to to and it with the taint register when the default
|
|
; temporary isn't available.
|
|
define void @indirect_call_x17(i32 %a, i32 %b) speculative_load_hardening {
|
|
ret void
|
|
}
|
|
@g = common dso_local local_unnamed_addr global i64 (...)* null, align 8
|
|
define void @indirect_tailcall_x17(i32 %a, i32 %b) speculative_load_hardening {
|
|
ret void
|
|
}
|
|
define void @indirect_call_lr(i32 %a, i32 %b) speculative_load_hardening {
|
|
ret void
|
|
}
|
|
define void @RS_cannot_find_available_regs() speculative_load_hardening {
|
|
ret void
|
|
}
|
|
...
|
|
---
|
|
name: nobranch_fallthrough
|
|
tracksRegLiveness: true
|
|
body: |
|
|
; CHECK-LABEL: nobranch_fallthrough
|
|
bb.0:
|
|
successors: %bb.1
|
|
liveins: $w0, $w1
|
|
; CHECK-NOT: csel
|
|
bb.1:
|
|
liveins: $w0
|
|
RET undef $lr, implicit $w0
|
|
...
|
|
---
|
|
name: uncondbranch
|
|
tracksRegLiveness: true
|
|
body: |
|
|
; CHECK-LABEL: uncondbranch
|
|
bb.0:
|
|
successors: %bb.1
|
|
liveins: $w0, $w1
|
|
B %bb.1
|
|
; CHECK-NOT: csel
|
|
bb.1:
|
|
liveins: $w0
|
|
RET undef $lr, implicit $w0
|
|
...
|
|
---
|
|
name: condbranch_fallthrough
|
|
tracksRegLiveness: true
|
|
body: |
|
|
; CHECK-LABEL: condbranch_fallthrough
|
|
bb.0:
|
|
successors: %bb.1, %bb.2
|
|
liveins: $w0, $w1
|
|
$wzr = SUBSWrs renamable $w0, renamable $w1, 0, implicit-def $nzcv, implicit-def $nzcv
|
|
Bcc 11, %bb.2, implicit $nzcv
|
|
; CHECK: b.lt [[BB_LT_T:\.LBB[0-9_]+]]
|
|
|
|
bb.1:
|
|
liveins: $nzcv, $w0
|
|
; CHECK: csel x16, x16, xzr, ge
|
|
RET undef $lr, implicit $w0
|
|
bb.2:
|
|
liveins: $nzcv, $w0
|
|
; CHECK: csel x16, x16, xzr, lt
|
|
RET undef $lr, implicit $w0
|
|
...
|
|
---
|
|
name: condbranch_uncondbranch
|
|
tracksRegLiveness: true
|
|
body: |
|
|
; CHECK-LABEL: condbranch_uncondbranch
|
|
bb.0:
|
|
successors: %bb.1, %bb.2
|
|
liveins: $w0, $w1
|
|
$wzr = SUBSWrs renamable $w0, renamable $w1, 0, implicit-def $nzcv, implicit-def $nzcv
|
|
Bcc 11, %bb.2, implicit $nzcv
|
|
B %bb.1, implicit $nzcv
|
|
; CHECK: b.lt [[BB_LT_T:\.LBB[0-9_]+]]
|
|
|
|
bb.1:
|
|
liveins: $nzcv, $w0
|
|
; CHECK: csel x16, x16, xzr, ge
|
|
RET undef $lr, implicit $w0
|
|
bb.2:
|
|
liveins: $nzcv, $w0
|
|
; CHECK: csel x16, x16, xzr, lt
|
|
RET undef $lr, implicit $w0
|
|
...
|
|
---
|
|
name: indirectbranch
|
|
tracksRegLiveness: true
|
|
body: |
|
|
; Check that no instrumentation is done on indirect branches (for now).
|
|
; CHECK-LABEL: indirectbranch
|
|
bb.0:
|
|
successors: %bb.1, %bb.2
|
|
liveins: $x0
|
|
BR $x0
|
|
bb.1:
|
|
liveins: $x0
|
|
; CHECK-NOT: csel
|
|
RET undef $lr, implicit $x0
|
|
bb.2:
|
|
liveins: $x0
|
|
; CHECK-NOT: csel
|
|
RET undef $lr, implicit $x0
|
|
...
|
|
---
|
|
name: indirect_call_x17
|
|
tracksRegLiveness: true
|
|
body: |
|
|
bb.0:
|
|
liveins: $x17
|
|
; CHECK-LABEL: indirect_call_x17
|
|
; CHECK: mov x0, sp
|
|
; CHECK: and x0, x0, x16
|
|
; CHECK: mov sp, x0
|
|
; CHECK: blr x17
|
|
BLR killed renamable $x17, implicit-def dead $lr, implicit $sp
|
|
RET undef $lr, implicit undef $w0
|
|
...
|
|
---
|
|
name: indirect_tailcall_x17
|
|
tracksRegLiveness: true
|
|
body: |
|
|
bb.0:
|
|
liveins: $x0
|
|
; CHECK-LABEL: indirect_tailcall_x17
|
|
; CHECK: mov x1, sp
|
|
; CHECK: and x1, x1, x16
|
|
; CHECK: mov sp, x1
|
|
; CHECK: br x17
|
|
$x8 = ADRP target-flags(aarch64-page) @g
|
|
$x17 = LDRXui killed $x8, target-flags(aarch64-pageoff, aarch64-nc) @g
|
|
TCRETURNri killed $x17, 0, implicit $sp, implicit $x0
|
|
...
|
|
---
|
|
name: indirect_call_lr
|
|
tracksRegLiveness: true
|
|
body: |
|
|
bb.0:
|
|
; CHECK-LABEL: indirect_call_lr
|
|
; CHECK: mov x1, sp
|
|
; CHECK: and x1, x1, x16
|
|
; CHECK-NEXT: mov sp, x1
|
|
; CHECK-NEXT: blr x30
|
|
liveins: $x0, $lr
|
|
BLR killed renamable $lr, implicit-def dead $lr, implicit $sp, implicit-def $sp, implicit-def $w0
|
|
$w0 = nsw ADDWri killed $w0, 1, 0
|
|
RET undef $lr, implicit $w0
|
|
...
|
|
---
|
|
name: RS_cannot_find_available_regs
|
|
tracksRegLiveness: true
|
|
body: |
|
|
bb.0:
|
|
; In the rare case when no free temporary register is available for the
|
|
; propagate taint-to-sp operation, just put in a full speculation barrier
|
|
; (isb+dsb sy) at the start of the basic block. And don't put masks on
|
|
; instructions for the rest of the basic block, since speculation in that
|
|
; basic block was already done, so no need to do masking.
|
|
; CHECK-LABEL: RS_cannot_find_available_regs
|
|
; CHECK: dsb sy
|
|
; CHECK-NEXT: isb
|
|
; CHECK-NEXT: ldr x0, [x0]
|
|
; The following 2 instructions come from propagating the taint encoded in
|
|
; sp at function entry to x16. It turns out the taint info in x16 is not
|
|
; used in this function, so those instructions could be optimized away. An
|
|
; optimization for later if it turns out this situation occurs often enough.
|
|
; CHECK-NEXT: cmp sp, #0
|
|
; CHECK-NEXT: csetm x16, ne
|
|
; CHECK-NEXT: ret
|
|
liveins: $x0, $x1, $x2, $x3, $x4, $x5, $x6, $x7, $x8, $x9, $x10, $x11, $x12, $x13, $x14, $x15, $x17, $x18, $x19, $x20, $x21, $x22, $x23, $x24, $x25, $x26, $x27, $x28, $fp, $lr
|
|
$x0 = LDRXui killed $x0, 0
|
|
RET undef $lr, implicit $x0
|
|
...
|