x86: dynamic indirect call promotion
Subsystems (5)
cover x86: dynamic indirect call promotion
This RFC introduces indirect call promotion in runtime, which for the matter of simplification (and branding) will be called here "relpolines" (relative call + trampoline). Relpolines are mainly intended as a way of reducing retpoline overheads due to Spectre v2.
Unlike indirect call promotion through profile guided optimization, the proposed approach does not require a profiling stage, works well with modules whose address is unknown and can adapt to changing workloads.
The main idea is simple: for every indirect call, we inject a piece of code with fast- and slow-path calls. The fast path is used if the target matches the expected (hot) target. The slow-path uses a retpoline. During training, the slow-path is set to call a function that saves the call source and target in a hash-table and keep count for call frequency. The most common target is then patched into the hot path.
The patching is done on-the-fly by patching the conditional branch (opcode and offset) that is used to compare the target to the hot target. This allows to direct all cores to the fast-path, while patching the slow-path and vice-versa. Patching follows 2 more rules: (1) Only patch a single byte when the code might be executed by any core. (2) When patching more than one byte, ensure that all cores do not run the to-be-patched-code by preventing this code from being preempted, and using synchronize_sched() after patching the branch that jumps over this code.
Changing all the indirect calls to use relpolines is done using assembly macro magic. There are alternative solutions, but this one is relatively simple and transparent. There is also logic to retrain the software predictor, but the policy it uses may need to be refined.
Eventually the results are not bad (2 VCPU VM, throughput reported):
base relpoline ---- ---------
nginx 22898 25178 (+10%)
redis-ycsb 24523 25486 (+4%)
dbench 2144 2103 (+2%)
When retpolines are disabled, and if retraining is off, performance benefits are up to 2% (nginx), but are much less impressive.
There are several open issues: retraining should be done when modules are removed; CPU hotplug is not supported, x86-32 is probably broken and the Makefile does not rebuild when the relpoline code is changed. Having said that, I am worried that some of the approaches I took would challenge the new code-of-conduct, so I though of getting some feedback before putting more effort into it.
Nadav Amit (5):
x86: introduce preemption disable prefix
x86: patch indirect branch promotion
x86: interface for accessing indirect branch locations
x86: learning and patching indirect branch targets
x86: relpoline: disabling interface
arch/x86/entry/entry_64.S | 10 + arch/x86/include/asm/nospec-branch.h | 158 +++++ arch/x86/include/asm/sections.h | 2 + arch/x86/kernel/Makefile | 1 + arch/x86/kernel/asm-offsets.c | 6 + arch/x86/kernel/macros.S | 1 + arch/x86/kernel/nospec-branch.c | 899 +++++++++++++++++++++++++++ arch/x86/kernel/vmlinux.lds.S | 7 + arch/x86/lib/retpoline.S | 75 +++ include/linux/module.h | 5 + kernel/module.c | 8 + kernel/seccomp.c | 2 + 12 files changed, 1174 insertions(+) create mode 100644 arch/x86/kernel/nospec-branch.c
Signature · 1 line
2.17.1
RFC PATCH 1/5 x86: introduce preemption disable prefix
It is sometimes beneficial to prevent preemption for very few instructions, or prevent preemption for some instructions that precede a branch (this latter case will be introduced in the next patches).
To provide such functionality on x86-64, we use an empty REX-prefix (opcode 0x40) as an indication that preemption is disabled for the following instruction.
It is expected that this opcode is not in common use.
--- arch/x86/entry/entry_64.S | 10 ++++++++++ arch/x86/include/asm/nospec-branch.h | 12 ++++++++++++ 2 files changed, 22 insertions(+)
arch/x86/entry/entry_64.S +10 -0
@@ -643,6 +643,16 @@ retint_kernel: jnc 1f 0: cmpl $0, PER_CPU_VAR(__preempt_count) jnz 1f + + /* + * Allow to use hint to prevent preemption on a certain instruction. + * Consider an instruction with the first byte having REX prefix + * without any bits set as an indication for preemption disabled. + */ + movq RIP(%rsp), %rax + cmpb $PREEMPT_DISABLE_PREFIX, (%rax) + jz 1f + call preempt_schedule_irq jmp 0b 1:
arch/x86/include/asm/nospec-branch.h +12 -0
@@ -52,6 +52,12 @@ jnz 771b; \ add $(BITS_PER_LONG/8) * nr, sp; +/* +* An empty REX-prefix is an indication that preemption should not take place on +* this instruction. +*/ +#define PREEMPT_DISABLE_PREFIX (0x40) +#ifdef __ASSEMBLY__/*
@@ -148,6 +154,12 @@#endif .endm +.macro preempt_disable_prefix +#ifdef CONFIG_PREEMPT + .byte PREEMPT_DISABLE_PREFIX +#endif +.endm +#else /* __ASSEMBLY__ */ #define ANNOTATE_NOSPEC_ALTERNATIVE \
Signature · 1 line
2.17.1
Andy Lutomirski
reviewerRe: [RFC PATCH 1/5] x86: introduce preemption disable prefix
Quoted 10 lines · Nadav Amit
It is sometimes beneficial to prevent preemption for very few instructions, or prevent preemption for some instructions that precede a branch (this latter case will be introduced in the next patches).
To provide such functionality on x86-64, we use an empty REX-prefix (opcode
0x40) as an indication that preemption is disabled for the following instruction.
Nifty!
That being said, I think you have a few bugs. First, you can’t just ignore a rescheduling interrupt, as you introduce unbounded latency when this happens — you’re effectively emulating preempt_enable_no_resched(), which is not a drop-in replacement for preempt_enable(). To fix this, you may need to jump to a slow-path trampoline that calls schedule() at the end or consider rewinding one instruction instead. Or use TF, which is only a little bit terrifying...
You also aren’t accounting for the case where you get an exception that is, in turn, preempted.
Quoted 63 lines
It is expected that this opcode is not in common use.
Signed-off-by Nadav Amit <[email protected]>---
arch/x86/entry/entry_64.S| 10 ++++++++++arch/x86/include/asm/nospec-branch.h| 12 ++++++++++++2 files changed, 22 insertions(+)arch/x86/entry/entry_64.S +10
-0@@ -643,6 +643,16 @@ retint_kernel: jnc 1f 0: cmpl $0, PER_CPU_VAR(__preempt_count) jnz 1f + + /* + * Allow to use hint to prevent preemption on a certain instruction. + * Consider an instruction with the first byte having REX prefix + * without any bits set as an indication for preemption disabled. + */ + movq RIP(%rsp), %rax + cmpb $PREEMPT_DISABLE_PREFIX, (%rax) + jz 1f + call preempt_schedule_irq jmp 0b1:
arch/x86/include/asm/nospec-branch.h +6
-0@@ -52,6 +52,12 @@ jnz 771b; \ add $(BITS_PER_LONG/8) * nr, sp; +/* +* An empty REX-prefix is an indication that preemption should not take place on +* this instruction. +*/ +#define PREEMPT_DISABLE_PREFIX (0x40) +#ifdef __ASSEMBLY__/* @@ -148,6 +154,12 @@#endif .endm
+.macro
preempt_disable_prefixarch/x86/include/asm/nospec-branch.h +5
-0+#ifdef CONFIG_PREEMPT + .byte PREEMPT_DISABLE_PREFIX +#endif +.endm +#else /*
__ASSEMBLY__*/#define
ANNOTATE_NOSPEC_ALTERNATIVE\Signature · 1 line
2.17.1
Re: [RFC PATCH 1/5] x86: introduce preemption disable prefix
Quoted 13 lines · Andy Lutomirski
Quoted 10 lines · Nadav Amit
It is sometimes beneficial to prevent preemption for very few instructions, or prevent preemption for some instructions that precede a branch (this latter case will be introduced in the next patches).
To provide such functionality on x86-64, we use an empty REX-prefix (opcode
0x40) as an indication that preemption is disabled for the following instruction.Nifty!
Quoted 7 lines
That being said, I think you have a few bugs. First, you can’t just ignore a rescheduling interrupt, as you introduce unbounded latency when this happens — you’re effectively emulating
preempt_enable_no_resched(), which is not a drop-in replacement forpreempt_enable(). To fix this, you may need to jump to a slow-path trampoline that callsschedule()at the end or consider rewinding one instruction instead. Or use TF, which is only a little bit terrifying…
Yes, I didn’t pay enough attention here. For my use-case, I think that the easiest solution would be to make synchronize_sched() ignore preemptions that happen while the prefix is detected. It would slightly change the meaning of the prefix.
Quoted 2 lines
You also aren’t accounting for the case where you get an exception that is, in turn, preempted.
Hmm.. Can you give me an example for such an exception in my use-case? I cannot think of an exception that might be preempted (assuming #BP, #MC cannot be preempted).
I agree that for super-general case this might be inappropriate.
Re: [RFC PATCH 1/5] x86: introduce preemption disable prefix
Quoted 22 lines · Nadav Amit
Quoted 12 lines · Andy Lutomirski
Quoted 9 lines · Nadav Amit
It is sometimes beneficial to prevent preemption for very few instructions, or prevent preemption for some instructions that precede a branch (this latter case will be introduced in the next patches).
To provide such functionality on x86-64, we use an empty REX-prefix (opcode
0x40) as an indication that preemption is disabled for the following instruction.Nifty!
Quoted 7 lines
That being said, I think you have a few bugs. First, you can’t just ignore a rescheduling interrupt, as you introduce unbounded latency when this happens — you’re effectively emulating
preempt_enable_no_resched(), which is not a drop-in replacement forpreempt_enable(). To fix this, you may need to jump to a slow-path trampoline that callsschedule()at the end or consider rewinding one instruction instead. Or use TF, which is only a little bit terrifying…
Quoted 4 lines
Yes, I didn’t pay enough attention here. For my use-case, I think that the easiest solution would be to make
synchronize_sched()ignore preemptions that happen while the prefix is detected. It would slightly change the meaning of the prefix.
Ignore this nonsense that I wrote. I’ll try to come up with a decent solution.
Quoted 8 lines
Quoted 2 lines
You also aren’t accounting for the case where you get an exception that is, in turn, preempted.
Hmm.. Can you give me an example for such an exception in my use-case? I cannot think of an exception that might be preempted (assuming #BP, #MC cannot be preempted).
I agree that for super-general case this might be inappropriate.
Andy Lutomirski
reviewerRe: [RFC PATCH 1/5] x86: introduce preemption disable prefix
In reply to Nadav AmitQuoted 29 lines · Nadav Amit
Quoted 13 lines · Andy Lutomirski
Quoted 10 lines · Nadav Amit
It is sometimes beneficial to prevent preemption for very few instructions, or prevent preemption for some instructions that precede a branch (this latter case will be introduced in the next patches).
To provide such functionality on x86-64, we use an empty REX-prefix (opcode
0x40) as an indication that preemption is disabled for the following instruction.Nifty!
Quoted 7 lines
That being said, I think you have a few bugs. First, you can’t just ignore a rescheduling interrupt, as you introduce unbounded latency when this happens — you’re effectively emulating
preempt_enable_no_resched(), which is not a drop-in replacement forpreempt_enable(). To fix this, you may need to jump to a slow-path trampoline that callsschedule()at the end or consider rewinding one instruction instead. Or use TF, which is only a little bit terrifying…Yes, I didn’t pay enough attention here. For my use-case, I think that the easiest solution would be to make
synchronize_sched()ignore preemptions that happen while the prefix is detected. It would slightly change the meaning of the prefix.
Quoted 7 lines
Quoted 2 lines
You also aren’t accounting for the case where you get an exception that is, in turn, preempted.
Hmm.. Can you give me an example for such an exception in my use-case? I cannot think of an exception that might be preempted (assuming #BP, #MC cannot be preempted).
Look for cond_local_irq_enable().
--Andy
Re: [RFC PATCH 1/5] x86: introduce preemption disable prefix
Quoted 23 lines · Andy Lutomirski
Quoted 22 lines · Nadav Amit
Quoted 19 lines · Andy Lutomirski
Quoted 9 lines · Nadav Amit
It is sometimes beneficial to prevent preemption for very few instructions, or prevent preemption for some instructions that precede a branch (this latter case will be introduced in the next patches).
To provide such functionality on x86-64, we use an empty REX-prefix (opcode
0x40) as an indication that preemption is disabled for the following instruction.Nifty!
That being said, I think you have a few bugs. First, you can’t just ignore a rescheduling interrupt, as you introduce unbounded latency when this happens — you’re effectively emulating
preempt_enable_no_resched(), which is not a drop-in replacement forpreempt_enable(). To fix this, you may need to jump to a slow-path trampoline that callsschedule()at the end or consider rewinding one instruction instead. Or use TF, which is only a little bit terrifying…
Quoted 4 lines
Quoted 4 lines
Yes, I didn’t pay enough attention here. For my use-case, I think that the easiest solution would be to make
synchronize_sched()ignore preemptions that happen while the prefix is detected. It would slightly change the meaning of the prefix.
So thinking about it further, rewinding the instruction seems the easiest and most robust solution. I’ll do it.
Quoted 8 lines
Quoted 6 lines
Quoted 2 lines
You also aren’t accounting for the case where you get an exception that is, in turn, preempted.
Hmm.. Can you give me an example for such an exception in my use-case? I cannot think of an exception that might be preempted (assuming #BP, #MC cannot be preempted).
Look for
cond_local_irq_enable().
I looked at it. Yet, I still don’t see how exceptions might happen in my use-case, but having said that - this can be fixed too.
To be frank, I paid relatively little attention to this subject. Any feedback about the other parts and especially on the high-level approach? Is modifying the retpolines in the proposed manner (assembly macros) acceptable?
Thanks,
Nadav
Andy Lutomirski
reviewerRe: [RFC PATCH 1/5] x86: introduce preemption disable prefix
Quoted 40 lines · Nadav Amit
Quoted 23 lines · Andy Lutomirski
Quoted 23 lines · Nadav Amit
Quoted 19 lines · Andy Lutomirski
Quoted 9 lines · Nadav Amit
It is sometimes beneficial to prevent preemption for very few instructions, or prevent preemption for some instructions that precede a branch (this latter case will be introduced in the next patches).
To provide such functionality on x86-64, we use an empty REX-prefix (opcode
0x40) as an indication that preemption is disabled for the following instruction.Nifty!
That being said, I think you have a few bugs. First, you can’t just ignore a rescheduling interrupt, as you introduce unbounded latency when this happens — you’re effectively emulating
preempt_enable_no_resched(), which is not a drop-in replacement forpreempt_enable(). To fix this, you may need to jump to a slow-path trampoline that callsschedule()at the end or consider rewinding one instruction instead. Or use TF, which is only a little bit terrifying…Quoted 4 lines
Quoted 4 lines
Yes, I didn’t pay enough attention here. For my use-case, I think that the easiest solution would be to make
synchronize_sched()ignore preemptions that happen while the prefix is detected. It would slightly change the meaning of the prefix.So thinking about it further, rewinding the instruction seems the easiest and most robust solution. I’ll do it.
Quoted 3 lines
Quoted 3 lines
Quoted 2 lines
You also aren’t accounting for the case where you get an exception that is, in turn, preempted.
Quoted 8 lines
Quoted 5 lines
Quoted 3 lines
Hmm.. Can you give me an example for such an exception in my use-case? I cannot think of an exception that might be preempted (assuming #BP, #MC cannot be preempted).
Look for
cond_local_irq_enable().I looked at it. Yet, I still don’t see how exceptions might happen in my use-case, but having said that - this can be fixed too.
I’m not totally certain there’s a case that matters. But it’s worth checking
Quoted 6 lines
To be frank, I paid relatively little attention to this subject. Any feedback about the other parts and especially on the high-level approach? Is modifying the retpolines in the proposed manner (assembly macros) acceptable?
It’s certainly a neat idea, and it could be a real speedup.
Quoted 2 lines
Thanks,
Nadav
Re: [RFC PATCH 1/5] x86: introduce preemption disable prefix
Quoted 49 lines · Andy Lutomirski
Quoted 40 lines · Nadav Amit
Quoted 23 lines · Andy Lutomirski
Quoted 23 lines · Nadav Amit
Quoted 19 lines · Andy Lutomirski
Quoted 9 lines · Nadav Amit
It is sometimes beneficial to prevent preemption for very few instructions, or prevent preemption for some instructions that precede a branch (this latter case will be introduced in the next patches).
To provide such functionality on x86-64, we use an empty REX-prefix (opcode
0x40) as an indication that preemption is disabled for the following instruction.Nifty!
That being said, I think you have a few bugs. First, you can’t just ignore a rescheduling interrupt, as you introduce unbounded latency when this happens — you’re effectively emulating
preempt_enable_no_resched(), which is not a drop-in replacement forpreempt_enable(). To fix this, you may need to jump to a slow-path trampoline that callsschedule()at the end or consider rewinding one instruction instead. Or use TF, which is only a little bit terrifying…Quoted 4 lines
Quoted 4 lines
Yes, I didn’t pay enough attention here. For my use-case, I think that the easiest solution would be to make
synchronize_sched()ignore preemptions that happen while the prefix is detected. It would slightly change the meaning of the prefix.So thinking about it further, rewinding the instruction seems the easiest and most robust solution. I’ll do it.
Quoted 3 lines
Quoted 3 lines
Quoted 2 lines
You also aren’t accounting for the case where you get an exception that is, in turn, preempted.
Quoted 8 lines
Quoted 5 lines
Quoted 3 lines
Hmm.. Can you give me an example for such an exception in my use-case? I cannot think of an exception that might be preempted (assuming #BP, #MC cannot be preempted).
Look for
cond_local_irq_enable().I looked at it. Yet, I still don’t see how exceptions might happen in my use-case, but having said that - this can be fixed too.
Quoted 8 lines
I’m not totally certain there’s a case that matters. But it’s worth checking
Quoted 4 lines
To be frank, I paid relatively little attention to this subject. Any feedback about the other parts and especially on the high-level approach? Is modifying the retpolines in the proposed manner (assembly macros) acceptable?
It’s certainly a neat idea, and it could be a real speedup.
Great. So I’ll try to shape things up, and I still wait for other comments (from others).
I’ll just mention two more patches I need to cleanup (I know I still owe you some work, so obviously it will be done later):
1. Seccomp trampolines. On my Ubuntu, when I run Redis, systemd installs 17 BPF filters on the Redis server process that are invoked on each system-call. Invoking each one requires an indirect branch. The patch keeps a per-process kernel code-page that holds trampolines for these functions.
2. Binary-search for system-calls. Use the per-process kernel code-page also to hold multiple trampolines for the 16 common system calls of a certain process. The patch uses an indirection table and a binary-search to find the proper trampoline.
Thanks again,
Nadav
Andy Lutomirski
reviewerRe: [RFC PATCH 1/5] x86: introduce preemption disable prefix
Quoted 64 lines · Nadav Amit
Quoted 49 lines · Andy Lutomirski
Quoted 40 lines · Nadav Amit
Quoted 23 lines · Andy Lutomirski
Quoted 23 lines · Nadav Amit
Quoted 19 lines · Andy Lutomirski
Quoted 9 lines · Nadav Amit
It is sometimes beneficial to prevent preemption for very few instructions, or prevent preemption for some instructions that precede a branch (this latter case will be introduced in the next patches).
To provide such functionality on x86-64, we use an empty REX-prefix (opcode
0x40) as an indication that preemption is disabled for the following instruction.Nifty!
That being said, I think you have a few bugs. First, you can’t just ignore a rescheduling interrupt, as you introduce unbounded latency when this happens — you’re effectively emulating
preempt_enable_no_resched(), which is not a drop-in replacement forpreempt_enable(). To fix this, you may need to jump to a slow-path trampoline that callsschedule()at the end or consider rewinding one instruction instead. Or use TF, which is only a little bit terrifying…Quoted 4 lines
Quoted 4 lines
Yes, I didn’t pay enough attention here. For my use-case, I think that the easiest solution would be to make
synchronize_sched()ignore preemptions that happen while the prefix is detected. It would slightly change the meaning of the prefix.So thinking about it further, rewinding the instruction seems the easiest and most robust solution. I’ll do it.
Quoted 3 lines
Quoted 3 lines
Quoted 2 lines
You also aren’t accounting for the case where you get an exception that is, in turn, preempted.
Quoted 8 lines
Quoted 5 lines
Quoted 3 lines
Hmm.. Can you give me an example for such an exception in my use-case? I cannot think of an exception that might be preempted (assuming #BP, #MC cannot be preempted).
Look for
cond_local_irq_enable().I looked at it. Yet, I still don’t see how exceptions might happen in my use-case, but having said that - this can be fixed too.
Quoted 8 lines
I’m not totally certain there’s a case that matters. But it’s worth checking
Quoted 4 lines
To be frank, I paid relatively little attention to this subject. Any feedback about the other parts and especially on the high-level approach? Is modifying the retpolines in the proposed manner (assembly macros) acceptable?
It’s certainly a neat idea, and it could be a real speedup.
Great. So I’ll try to shape things up, and I still wait for other comments (from others).
Quoted 7 lines
I’ll just mention two more patches I need to cleanup (I know I still owe you some work, so obviously it will be done later):
1. Seccomp trampolines. On my Ubuntu, when I run Redis, systemd installs 17 BPF filters on the Redis server process that are invoked on each system-call. Invoking each one requires an indirect branch. The patch keeps a per-process kernel code-page that holds trampolines for these functions.
I wonder how many levels of branches are needed before the branches involved exceed the retpoline cost.
Quoted 5 lines
2. Binary-search for system-calls. Use the per-process kernel code-page also to hold multiple trampolines for the 16 common system calls of a certain process. The patch uses an indirection table and a binary-search to find the proper trampoline.
Same comment applies here.
Quoted 3 lines
Thanks again,
Nadav
Signature · 2 lines
Andy Lutomirski AMA Capital Management, LLC
Re: [RFC PATCH 1/5] x86: introduce preemption disable prefix
Quoted 65 lines · Andy Lutomirski
Quoted 64 lines · Nadav Amit
Quoted 47 lines · Andy Lutomirski
Quoted 38 lines · Nadav Amit
Quoted 23 lines · Andy Lutomirski
Quoted 23 lines · Nadav Amit
Quoted 19 lines · Andy Lutomirski
Quoted 9 lines · Nadav Amit
It is sometimes beneficial to prevent preemption for very few instructions, or prevent preemption for some instructions that precede a branch (this latter case will be introduced in the next patches).
To provide such functionality on x86-64, we use an empty REX-prefix (opcode
0x40) as an indication that preemption is disabled for the following instruction.Nifty!
That being said, I think you have a few bugs. First, you can’t just ignore a rescheduling interrupt, as you introduce unbounded latency when this happens — you’re effectively emulating
preempt_enable_no_resched(), which is not a drop-in replacement forpreempt_enable(). To fix this, you may need to jump to a slow-path trampoline that callsschedule()at the end or consider rewinding one instruction instead. Or use TF, which is only a little bit terrifying…Quoted 4 lines
Quoted 4 lines
Yes, I didn’t pay enough attention here. For my use-case, I think that the easiest solution would be to make
synchronize_sched()ignore preemptions that happen while the prefix is detected. It would slightly change the meaning of the prefix.So thinking about it further, rewinding the instruction seems the easiest and most robust solution. I’ll do it.
Quoted 3 lines
Quoted 3 lines
Quoted 2 lines
You also aren’t accounting for the case where you get an exception that is, in turn, preempted.
Quoted 8 lines
Quoted 5 lines
Quoted 3 lines
Hmm.. Can you give me an example for such an exception in my use-case? I cannot think of an exception that might be preempted (assuming #BP, #MC cannot be preempted).
Look for
cond_local_irq_enable().I looked at it. Yet, I still don’t see how exceptions might happen in my use-case, but having said that - this can be fixed too.
Quoted 8 lines
I’m not totally certain there’s a case that matters. But it’s worth checking
Quoted 4 lines
To be frank, I paid relatively little attention to this subject. Any feedback about the other parts and especially on the high-level approach? Is modifying the retpolines in the proposed manner (assembly macros) acceptable?
It’s certainly a neat idea, and it could be a real speedup.
Great. So I’ll try to shape things up, and I still wait for other comments (from others).
I’ll just mention two more patches I need to cleanup (I know I still owe you some work, so obviously it will be done later):
Quoted 7 lines
Quoted 4 lines
1. Seccomp trampolines. On my Ubuntu, when I run Redis, systemd installs 17 BPF filters on the Redis server process that are invoked on each system-call. Invoking each one requires an indirect branch. The patch keeps a per-process kernel code-page that holds trampolines for these functions.
I wonder how many levels of branches are needed before the branches involved exceed the retpoline cost.
In this case there is no hierarchy, but a list of trampolines that are called one after the other, as the seccomp filter order is predefined. It does not work if different threads of the same process have different filters.
Quoted 6 lines
Quoted 4 lines
2. Binary-search for system-calls. Use the per-process kernel code-page also to hold multiple trampolines for the 16 common system calls of a certain process. The patch uses an indirection table and a binary-search to find the proper trampoline.
Same comment applies here.
Branch misprediction wastes ~7 cycles and a retpoline takes at least 30. So assuming the branch predictor is not completely stupid 3-4 levels should not be too much.
Re: [RFC PATCH 1/5] x86: introduce preemption disable prefix
In reply to Andy LutomirskiQuoted 44 lines · Andy Lutomirski
Quoted 44 lines · Nadav Amit
Quoted 23 lines · Andy Lutomirski
Quoted 23 lines · Nadav Amit
Quoted 19 lines · Andy Lutomirski
Quoted 9 lines · Nadav Amit
It is sometimes beneficial to prevent preemption for very few instructions, or prevent preemption for some instructions that precede a branch (this latter case will be introduced in the next patches).
To provide such functionality on x86-64, we use an empty REX-prefix (opcode
0x40) as an indication that preemption is disabled for the following instruction.Nifty!
That being said, I think you have a few bugs. First, you can’t just ignore a rescheduling interrupt, as you introduce unbounded latency when this happens — you’re effectively emulating
preempt_enable_no_resched(), which is not a drop-in replacement forpreempt_enable(). To fix this, you may need to jump to a slow-path trampoline that callsschedule()at the end or consider rewinding one instruction instead. Or use TF, which is only a little bit terrifying…Quoted 4 lines
Quoted 4 lines
Yes, I didn’t pay enough attention here. For my use-case, I think that the easiest solution would be to make
synchronize_sched()ignore preemptions that happen while the prefix is detected. It would slightly change the meaning of the prefix.So thinking about it further, rewinding the instruction seems the easiest and most robust solution. I’ll do it.
Quoted 7 lines
Quoted 6 lines
Quoted 2 lines
You also aren’t accounting for the case where you get an exception that is, in turn, preempted.
Hmm.. Can you give me an example for such an exception in my use-case? I cannot think of an exception that might be preempted (assuming #BP, #MC cannot be preempted).
Quoted 6 lines
Quoted 4 lines
Quoted 1 line
Look for
cond_local_irq_enable().I looked at it. Yet, I still don’t see how exceptions might happen in my use-case, but having said that - this can be fixed too.
I’m not totally certain there’s a case that matters. But it’s worth checking
I am still checking. But, I wanted to ask you whether the existing code is correct, since it seems to me that others do the same mistake I did, unless I don’t understand the code.
Consider for example do_int3(), and see my inlined comments:
dotraplinkage void notrace do_int3(struct pt_regs *regs, long error_code)
{
...
ist_enter(regs); // => preempt_disable()
cond_local_irq_enable(regs); // => assume it enables IRQs
...
// resched irq can be delivered here. It will not caused rescheduling
// since preemption is disabled
cond_local_irq_disable(regs); // => assume it disables IRQs
ist_exit(regs); // => preempt_enable_no_resched()
}At this point resched will not happen for unbounded length of time (unless there is another point when exiting the trap handler that checks if preemption should take place).
Another example is __BPF_PROG_RUN_ARRAY(), which also uses preempt_enable_no_resched().
Am I missing something?
Thanks,
Nadav
Andy Lutomirski
maintainerRe: [RFC PATCH 1/5] x86: introduce preemption disable prefix
Quoted 75 lines · Nadav Amit
Quoted 44 lines · Andy Lutomirski
Quoted 44 lines · Nadav Amit
Quoted 23 lines · Andy Lutomirski
Quoted 23 lines · Nadav Amit
Quoted 19 lines · Andy Lutomirski
Quoted 9 lines · Nadav Amit
It is sometimes beneficial to prevent preemption for very few instructions, or prevent preemption for some instructions that precede a branch (this latter case will be introduced in the next patches).
To provide such functionality on x86-64, we use an empty REX-prefix (opcode
0x40) as an indication that preemption is disabled for the following instruction.Nifty!
That being said, I think you have a few bugs. First, you can’t just ignore a rescheduling interrupt, as you introduce unbounded latency when this happens — you’re effectively emulating
preempt_enable_no_resched(), which is not a drop-in replacement forpreempt_enable(). To fix this, you may need to jump to a slow-path trampoline that callsschedule()at the end or consider rewinding one instruction instead. Or use TF, which is only a little bit terrifying…Quoted 4 lines
Quoted 4 lines
Yes, I didn’t pay enough attention here. For my use-case, I think that the easiest solution would be to make
synchronize_sched()ignore preemptions that happen while the prefix is detected. It would slightly change the meaning of the prefix.So thinking about it further, rewinding the instruction seems the easiest and most robust solution. I’ll do it.
Quoted 7 lines
Quoted 6 lines
Quoted 2 lines
You also aren’t accounting for the case where you get an exception that is, in turn, preempted.
Hmm.. Can you give me an example for such an exception in my use-case? I cannot think of an exception that might be preempted (assuming #BP, #MC cannot be preempted).
Quoted 6 lines
Quoted 4 lines
Quoted 1 line
Look for
cond_local_irq_enable().I looked at it. Yet, I still don’t see how exceptions might happen in my use-case, but having said that - this can be fixed too.
I’m not totally certain there’s a case that matters. But it’s worth checking
I am still checking. But, I wanted to ask you whether the existing code is correct, since it seems to me that others do the same mistake I did, unless I don’t understand the code.
Consider for example
do_int3(), and see my inlined comments:dotraplinkage void notrace
do_int3(struct pt_regs *regs, long error_code){
...
ist_enter(regs); // =>preempt_disable()
cond_local_irq_enable(regs); // => assume it enables IRQs... // resched irq can be delivered here. It will not caused rescheduling // since preemption is disabled cond_local_irq_disable(regs); // => assume it disables IRQs ist_exit(regs); // => preempt_enable_no_resched() }
Quoted 3 lines
At this point resched will not happen for unbounded length of time (unless there is another point when exiting the trap handler that checks if preemption should take place).
I think it's only a bug in the cases where someone uses extable to fix up an int3 (which would be nuts) or that we oops. But I should still fix it. In the normal case where int3 was in user code, we'll miss the reschedule in do_trap(), but we'll reschedule in prepare_exit_to_usermode() -> exit_to_usermode_loop().
Quoted 3 lines
Another example is
__BPF_PROG_RUN_ARRAY(), which also usespreempt_enable_no_resched().
Alexei, I think this code is just wrong. Do you know why it uses preempt_enable_no_resched()?
Oleg, the code in kernel/signal.c:
preempt_disable();
read_unlock(&tasklist_lock);
preempt_enable_no_resched();
freezable_schedule();looks bogus. I don't get what it's trying to achieve with preempt_disable(), and I also don't see why no_resched does anything. Sure, it prevents a reschedule triggered during read_unlock() from causing a reschedule, but it doesn't prevent an interrupt immediately after the preempt_enable_no_resched() call from scheduling.
--Andy
Quoted 5 lines
Am I missing something?
Thanks,
Nadav
Re: [RFC PATCH 1/5] x86: introduce preemption disable prefix
Quoted 77 lines · Andy Lutomirski
Quoted 69 lines · Nadav Amit
Quoted 42 lines · Andy Lutomirski
Quoted 42 lines · Nadav Amit
Quoted 23 lines · Andy Lutomirski
Quoted 23 lines · Nadav Amit
Quoted 19 lines · Andy Lutomirski
Quoted 9 lines · Nadav Amit
It is sometimes beneficial to prevent preemption for very few instructions, or prevent preemption for some instructions that precede a branch (this latter case will be introduced in the next patches).
To provide such functionality on x86-64, we use an empty REX-prefix (opcode
0x40) as an indication that preemption is disabled for the following instruction.Nifty!
That being said, I think you have a few bugs. First, you can’t just ignore a rescheduling interrupt, as you introduce unbounded latency when this happens — you’re effectively emulating
preempt_enable_no_resched(), which is not a drop-in replacement forpreempt_enable(). To fix this, you may need to jump to a slow-path trampoline that callsschedule()at the end or consider rewinding one instruction instead. Or use TF, which is only a little bit terrifying…Quoted 4 lines
Quoted 4 lines
Yes, I didn’t pay enough attention here. For my use-case, I think that the easiest solution would be to make
synchronize_sched()ignore preemptions that happen while the prefix is detected. It would slightly change the meaning of the prefix.So thinking about it further, rewinding the instruction seems the easiest and most robust solution. I’ll do it.
Quoted 7 lines
Quoted 6 lines
Quoted 2 lines
You also aren’t accounting for the case where you get an exception that is, in turn, preempted.
Hmm.. Can you give me an example for such an exception in my use-case? I cannot think of an exception that might be preempted (assuming #BP, #MC cannot be preempted).
Quoted 6 lines
Quoted 4 lines
Quoted 1 line
Look for
cond_local_irq_enable().I looked at it. Yet, I still don’t see how exceptions might happen in my use-case, but having said that - this can be fixed too.
I’m not totally certain there’s a case that matters. But it’s worth checking
I am still checking. But, I wanted to ask you whether the existing code is correct, since it seems to me that others do the same mistake I did, unless I don’t understand the code.
Consider for example
do_int3(), and see my inlined comments:dotraplinkage void notrace
do_int3(struct pt_regs *regs, long error_code){
...
ist_enter(regs); // =>preempt_disable()
cond_local_irq_enable(regs); // => assume it enables IRQs...
// resched irq can be delivered here. It will not caused rescheduling // since preemption is disabledQuoted 7 lines
cond_local_irq_disable(regs); // => assume it disables IRQs
ist_exit(regs); // =>preempt_enable_no_resched()
}At this point resched will not happen for unbounded length of time (unless there is another point when exiting the trap handler that checks if preemption should take place).
Quoted 5 lines
I think it's only a bug in the cases where someone uses extable to fix up an int3 (which would be nuts) or that we oops. But I should still fix it. In the normal case where int3 was in user code, we'll miss the reschedule in
do_trap(), but we'll reschedule inprepare_exit_to_usermode()->exit_to_usermode_loop().
Thanks for your quick response, and sorry for bothering instead of dealing with it. Note that do_debug() does something similar to do_int3().
And then there is optimized_callback() that also uses preempt_enable_no_resched(). I think the original use was correct, but then a19b2e3d7839 ("kprobes/x86: Remove IRQ disabling from ftrace-based/optimized kprobes”) removed the IRQ disabling, while leaving preempt_enable_no_resched() . No?
Re: [RFC PATCH 1/5] x86: introduce preemption disable prefix
Quoted 85 lines · On Fri, 19 Oct 2018 04:44:33 +0000 Nadav Amit <[email protected]> wrote:
Quoted 77 lines · Andy Lutomirski
Quoted 69 lines · Nadav Amit
Quoted 42 lines · Andy Lutomirski
Quoted 42 lines · Nadav Amit
Quoted 23 lines · Andy Lutomirski
Quoted 23 lines · Nadav Amit
Quoted 19 lines · Andy Lutomirski
Quoted 9 lines · Nadav Amit
It is sometimes beneficial to prevent preemption for very few instructions, or prevent preemption for some instructions that precede a branch (this latter case will be introduced in the next patches).
To provide such functionality on x86-64, we use an empty REX-prefix (opcode
0x40) as an indication that preemption is disabled for the following instruction.Nifty!
That being said, I think you have a few bugs. First, you can’t just ignore a rescheduling interrupt, as you introduce unbounded latency when this happens ― you’re effectively emulating
preempt_enable_no_resched(), which is not a drop-in replacement forpreempt_enable(). To fix this, you may need to jump to a slow-path trampoline that callsschedule()at the end or consider rewinding one instruction instead. Or use TF, which is only a little bit terrifying…Quoted 4 lines
Quoted 4 lines
Yes, I didn’t pay enough attention here. For my use-case, I think that the easiest solution would be to make
synchronize_sched()ignore preemptions that happen while the prefix is detected. It would slightly change the meaning of the prefix.So thinking about it further, rewinding the instruction seems the easiest and most robust solution. I’ll do it.
Quoted 7 lines
Quoted 6 lines
Quoted 2 lines
You also aren’t accounting for the case where you get an exception that is, in turn, preempted.
Hmm.. Can you give me an example for such an exception in my use-case? I cannot think of an exception that might be preempted (assuming #BP, #MC cannot be preempted).
Quoted 6 lines
Quoted 4 lines
Quoted 1 line
Look for
cond_local_irq_enable().I looked at it. Yet, I still don’t see how exceptions might happen in my use-case, but having said that - this can be fixed too.
I’m not totally certain there’s a case that matters. But it’s worth checking
I am still checking. But, I wanted to ask you whether the existing code is correct, since it seems to me that others do the same mistake I did, unless I don’t understand the code.
Consider for example
do_int3(), and see my inlined comments:dotraplinkage void notrace
do_int3(struct pt_regs *regs, long error_code){
...
ist_enter(regs); // =>preempt_disable()
cond_local_irq_enable(regs); // => assume it enables IRQs...
// resched irq can be delivered here. It will not caused rescheduling // since preemption is disabledQuoted 7 lines
cond_local_irq_disable(regs); // => assume it disables IRQs
ist_exit(regs); // =>preempt_enable_no_resched()
}At this point resched will not happen for unbounded length of time (unless there is another point when exiting the trap handler that checks if preemption should take place).
Quoted 5 lines
I think it's only a bug in the cases where someone uses extable to fix up an int3 (which would be nuts) or that we oops. But I should still fix it. In the normal case where int3 was in user code, we'll miss the reschedule in
do_trap(), but we'll reschedule inprepare_exit_to_usermode()->exit_to_usermode_loop().
Quoted 8 lines
Thanks for your quick response, and sorry for bothering instead of dealing with it. Note that
do_debug()does something similar todo_int3().And then there is
optimized_callback()that also usespreempt_enable_no_resched(). I think the original use was correct, but thena19b2e3d7839("kprobes/x86: Remove IRQ disabling from ftrace-based/optimized kprobes”) removed the IRQ disabling, while leavingpreempt_enable_no_resched(). No?
Ah, good catch!
Indeed, we don't need to stick on no_resched anymore.
Thanks!
Signature · 1 line
Masami Hiramatsu <[email protected]>
Re: [RFC PATCH 1/5] x86: introduce preemption disable prefix
In reply to Andy LutomirskiQuoted 6 lines
Quoted 4 lines
Another example is
__BPF_PROG_RUN_ARRAY(), which also usespreempt_enable_no_resched().Alexei, I think this code is just wrong.
why 'just wrong' ?
Quoted 2 lines
Do you know why it uses
preempt_enable_no_resched()?
dont recall precisely.
we could be preemptable at the point where macro is called. I think the goal of no_resched was to avoid adding scheduling points where they didn't exist before just because a prog ran for few nsec. May be Daniel or Roman remember.
Peter Zijlstra
maintainerRe: [RFC PATCH 1/5] x86: introduce preemption disable prefix
Quoted 8 lines · Alexei Starovoitov
Quoted 6 lines
Quoted 4 lines
Another example is
__BPF_PROG_RUN_ARRAY(), which also usespreempt_enable_no_resched().Alexei, I think this code is just wrong.
why 'just wrong' ?
Because you lost a preemption point, this is a no-no.
Quoted 9 lines
Quoted 3 lines
Do you know why it uses
preempt_enable_no_resched()?dont recall precisely.
we could be preemptable at the point where macro is called. I think the goal ofno_reschedwas to avoid adding scheduling points where they didn't exist before just because a prog ran for few nsec. May be Daniel or Roman remember.
No, you did the exact opposite, where there previously was a preemption, you just ate it. The band saw didn't get stopped in time, you loose your hand etc..
Re: [RFC PATCH 1/5] x86: introduce preemption disable prefix
Quoted 23 lines · Peter Zijlstra
Quoted 8 lines · Alexei Starovoitov
Quoted 6 lines
Quoted 4 lines
Another example is
__BPF_PROG_RUN_ARRAY(), which also usespreempt_enable_no_resched().Alexei, I think this code is just wrong.
why 'just wrong' ?
Because you lost a preemption point, this is a no-no.
Quoted 9 lines
Quoted 3 lines
Do you know why it uses
preempt_enable_no_resched()?dont recall precisely.
we could be preemptable at the point where macro is called. I think the goal ofno_reschedwas to avoid adding scheduling points where they didn't exist before just because a prog ran for few nsec. May be Daniel or Roman remember.
Quoted 3 lines
No, you did the exact opposite, where there previously was a preemption, you just ate it. The band saw didn't get stopped in time, you loose your hand etc..
Let me do few experiments then.
We will fix it up.
Peter Zijlstra
maintainerRe: [RFC PATCH 1/5] x86: introduce preemption disable prefix
In reply to Andy LutomirskiQuoted 5 lines · Andy Lutomirski
Quoted 2 lines
Another example is
__BPF_PROG_RUN_ARRAY(), which also usespreempt_enable_no_resched().Alexei, I think this code is just wrong. Do you know why it uses
preempt_enable_no_resched()?
Yes, that's a straight up bug.
It looks like I need to go fix up abuse again :/
Quoted 7 lines
Oleg, the code in
kernel/signal.c:preempt_disable(); read_unlock(&tasklist_lock); preempt_enable_no_resched(); freezable_schedule();
The purpose here is to avoid back-to-back schedule() calls, and this pattern is one of the few correct uses of preempt_enable_no_resched().
Suppose we got a preemption while holding the read_lock(), then when we'd do read_unlock(), we'd drop preempt_count to 0 and reschedule, then when we get back we instantly call into schedule _again_.
What this code does, is it increments preempt_count such that read_unlock() doesn't hit 0 and doesn't call schedule, then we lower it to 0 without a call to schedule() and then call schedule() explicitly.
Re: [RFC PATCH 1/5] x86: introduce preemption disable prefix
In reply to Andy LutomirskiQuoted 8 lines · Andy Lutomirski
Oleg, the code in
kernel/signal.c:preempt_disable(); read_unlock(&tasklist_lock); preempt_enable_no_resched(); freezable_schedule();
Quoted 4 lines
looks bogus. I don't get what it's trying to achieve with
preempt_disable(), and I also don't see whyno_rescheddoes anything. Sure, it prevents a reschedule triggered duringread_unlock()from causing a reschedule,
Yes. Lets suppose we remove preempt_disable/enable.
Debugger was already woken up, if it runs on the same CPU quite possibly it will preemt the tracee. After that debugger will spin in wait_task_inactive(), until it is in turn preempted or calls schedule_timeout(1), so that the tracee (current) can finally call __schedule(preempt = F) and call deactivate_task() to become inactive.
Quoted 2 lines
but it doesn't prevent an interrupt immediately after the
preempt_enable_no_resched()call from scheduling.
Yes, but this is less likely.
Oleg.
Peter Zijlstra
maintainerRe: [RFC PATCH 1/5] x86: introduce preemption disable prefix
In reply to Nadav AmitQuoted 16 lines · Nadav Amit
Consider for example
do_int3(), and see my inlined comments:dotraplinkage void notrace
do_int3(struct pt_regs *regs, long error_code){ ... ist_enter(regs); // => preempt_disable() cond_local_irq_enable(regs); // => assume it enables IRQs ... // resched irq can be delivered here. It will not caused rescheduling // since preemption is disabled cond_local_irq_disable(regs); // => assume it disables IRQs ist_exit(regs); // => preempt_enable_no_resched() }
Quoted 8 lines
At this point resched will not happen for unbounded length of time (unless there is another point when exiting the trap handler that checks if preemption should take place).
Another example is
__BPF_PROG_RUN_ARRAY(), which also usespreempt_enable_no_resched().Am I missing something?
Would not the interrupt return then check for TIF_NEED_RESCHED and call schedule() ?
I think (and this certainly wants a comment) is that the ist_exit() thing hard relies on the interrupt-return path doing the reschedule.
Andy Lutomirski
reviewerRe: [RFC PATCH 1/5] x86: introduce preemption disable prefix
Quoted 25 lines · Peter Zijlstra
Quoted 22 lines · Nadav Amit
Consider for example
do_int3(), and see my inlined comments:dotraplinkage void notrace
do_int3(struct pt_regs *regs, long error_code){
...
ist_enter(regs); // =>preempt_disable()
cond_local_irq_enable(regs); // => assume it enables IRQs... // resched irq can be delivered here. It will not caused rescheduling // since preemption is disabled cond_local_irq_disable(regs); // => assume it disables IRQs ist_exit(regs); // => preempt_enable_no_resched() }At this point resched will not happen for unbounded length of time (unless there is another point when exiting the trap handler that checks if preemption should take place).
Quoted 7 lines
Quoted 4 lines
Another example is
__BPF_PROG_RUN_ARRAY(), which also usespreempt_enable_no_resched().Am I missing something?
Would not the interrupt return then check for
TIF_NEED_RESCHEDand callschedule()?
The paranoid exit path doesn’t check TIF_NEED_RESCHED because it’s fundamentally atomic — it’s running on a percpu stack and it can’t schedule. In theory we could do some evil stack switching, but we don’t.
How does NMI handle this? If an NMI that hit interruptible kernel code overflows a perf counter, how does the wake up work?
(do_int3() is special because it’s not actually IST. But it can hit in odd places due to kprobes, and I’m nervous about recursing incorrectly into RCU and context tracking code if we were to use exception_enter().)
Quoted 3 lines
I think (and this certainly wants a comment) is that the
ist_exit()thing hard relies on the interrupt-return path doing the reschedule.
Peter Zijlstra
maintainerRe: [RFC PATCH 1/5] x86: introduce preemption disable prefix
Quoted 31 lines · Andy Lutomirski
Quoted 23 lines · Peter Zijlstra
Quoted 22 lines · Nadav Amit
Consider for example
do_int3(), and see my inlined comments:dotraplinkage void notrace
do_int3(struct pt_regs *regs, long error_code){
...
ist_enter(regs); // =>preempt_disable()
cond_local_irq_enable(regs); // => assume it enables IRQs... // resched irq can be delivered here. It will not caused rescheduling // since preemption is disabled cond_local_irq_disable(regs); // => assume it disables IRQs ist_exit(regs); // => preempt_enable_no_resched() }At this point resched will not happen for unbounded length of time (unless there is another point when exiting the trap handler that checks if preemption should take place).
Quoted 7 lines
Quoted 4 lines
Another example is
__BPF_PROG_RUN_ARRAY(), which also usespreempt_enable_no_resched().Am I missing something?
Would not the interrupt return then check for
TIF_NEED_RESCHEDand callschedule()?
Quoted 7 lines
The paranoid exit path doesn’t check
TIF_NEED_RESCHEDbecause it’s fundamentally atomic — it’s running on a percpu stack and it can’t schedule. In theory we could do some evil stack switching, but we don’t.How does NMI handle this? If an NMI that hit interruptible kernel code overflows a perf counter, how does the wake up work?
NMIs should never set NEED_RESCHED. What the perf does it self-IPI (irq_work) and do the wakeup from there.
Peter Zijlstra
maintainerRe: [RFC PATCH 1/5] x86: introduce preemption disable prefix
In reply to Andy LutomirskiQuoted 15 lines · Andy Lutomirski
Quoted 10 lines · Nadav Amit
It is sometimes beneficial to prevent preemption for very few instructions, or prevent preemption for some instructions that precede a branch (this latter case will be introduced in the next patches).
To provide such functionality on x86-64, we use an empty REX-prefix (opcode
0x40) as an indication that preemption is disabled for the following instruction.Nifty!
That being said, I think you have a few bugs.
Quoted 8 lines
First, you can’t just ignore a rescheduling interrupt, as you introduce unbounded latency when this happens — you’re effectively emulating
preempt_enable_no_resched(), which is not a drop-in replacement forpreempt_enable().To fix this, you may need to jump to a slow-path trampoline that calls
schedule()at the end or consider rewinding one instruction instead. Or use TF, which is only a little bit terrifying...
At which point we're very close to in-kernel rseq.
Re: [RFC PATCH 1/5] x86: introduce preemption disable prefix
Quoted 20 lines · Peter Zijlstra
Quoted 18 lines · Andy Lutomirski
Quoted 9 lines · Nadav Amit
It is sometimes beneficial to prevent preemption for very few instructions, or prevent preemption for some instructions that precede a branch (this latter case will be introduced in the next patches).
To provide such functionality on x86-64, we use an empty REX-prefix (opcode
0x40) as an indication that preemption is disabled for the following instruction.Nifty!
That being said, I think you have a few bugs.
First, you can’t just ignore a rescheduling interrupt, as you introduce unbounded latency when this happens — you’re effectively emulating
preempt_enable_no_resched(), which is not a drop-in replacement forpreempt_enable().
Quoted 5 lines
Quoted 3 lines
To fix this, you may need to jump to a slow-path trampoline that calls
schedule()at the end or consider rewinding one instruction instead. Or use TF, which is only a little bit terrifying...At which point we're very close to in-kernel rseq.
Interesting. I didn’t know about this feature. I’ll see if I can draw some ideas from there.
RFC PATCH 2/5 x86: patch indirect branch promotion
In reply to Nadav AmitTo perform indirect branch promotion, we need to find all the locations. Retpolines make it relatively easy to find these branches, by looking at the assembly and finding calls to the indirect thunks.
An assembly macro named CALL is used to catch all assembly calls, find these the use indirect thunks and patch them to hold the code that is needed for indirect branch promotion.
The build-system is slightly broken with this patch, as changes to nospec-branch.h should trigger a full kernel rebuild, which currently it does not.
--- arch/x86/include/asm/nospec-branch.h | 119 +++++++++++++++++++++++++++ arch/x86/kernel/Makefile | 1 + arch/x86/kernel/asm-offsets.c | 6 ++ arch/x86/kernel/macros.S | 1 + arch/x86/kernel/nospec-branch.c | 5 ++ arch/x86/kernel/vmlinux.lds.S | 7 ++ arch/x86/lib/retpoline.S | 75 +++++++++++++++++ 7 files changed, 214 insertions(+) create mode 100644 arch/x86/kernel/nospec-branch.c
arch/x86/include/asm/nospec-branch.h +119 -0
@@ -7,6 +7,27 @@#include <asm/alternative-asm.h> #include <asm/cpufeatures.h> #include <asm/msr-index.h> +#include <asm/percpu.h> + +/* +* Defining registers with the architectural order +*/ +#define ARCH_RAX 0 +#define ARCH_RCX 1 +#define ARCH_RDX 2 +#define ARCH_RBX 3 +#define ARCH_RSP 4 +#define ARCH_RBP 5 +#define ARCH_RSI 6 +#define ARCH_RDI 7 +#define ARCH_R8 8 +#define ARCH_R9 9 +#define ARCH_R10 10 +#define ARCH_R11 11 +#define ARCH_R12 12 +#define ARCH_R13 13 +#define ARCH_R14 14 +#define ARCH_R15 15/* * Fill the CPU return stack buffer.
@@ -28,6 +49,9 @@#define RSB_CLEAR_LOOPS 32/* To forcibly overwrite all entries */ #define RSB_FILL_LOOPS 16/* To avoid underflow */ +#define RELPOLINE_SAMPLES_NUM (1 << 8) +#define RELPOLINE_SAMPLES_MASK (RELPOLINE_SAMPLES_NUM - 1) +/* * Google experimented with loop-unrolling and this turned out to be * the optimal version — two calls, each with their own speculation
@@ -160,6 +184,81 @@#endif .endm +/* + * This macro performs the actual relpoline work. The machine-code is hand + * coded to avoid assembler optimizations. This code is heavily patched in + * runtime to make it do what it should. + */ +.macro relpoline_call reg:req +# cmp instruction + get_reg_num reg=\reg +.if reg_num == ARCH_RAX + .byte 0x48 + .byte 0x3d +.else +.if reg_num >= ARCH_R8 + .byte 0x49 +.else + .byte 0x48 +.endif + .byte 0x81 + .byte 0xf8 | (reg_num & 7) # modrm +.endif +1: + .long 0 + + .section .relpolines,"a" + _ASM_PTR 1b + .byte reg_num + .previous + +# cachepoling -using code + +# jnz 4f, patched to jmp while the target is changed + preempt_disable_prefix + .byte 0x75, 4f - 2f +2: +# call retpoline + preempt_disable_prefix + .byte 0xe8 + .long __x86_indirect_thunk_\reg - 3f +3: + # jmp 5f + .byte 0xeb, 5f - 4f +4: +# retpoline space + ANNOTATE_NOSPEC_ALTERNATIVE + preempt_disable_prefix + .byte 0xe8 + .long save_relpoline_\reg - 5f +5: +.endm + +#define ARCH_REG_NAMES rax,rcx,rdx,rbx,rsp,rbp,rsi,rdi,r8,r9,r10,r11,r12,r13,r14,r15 + +.macro get_reg_num reg:req + i =0 +.irp reg_it,ARCH_REG_NAMES + .ifc "\reg", "\reg_it" + reg_num=i + .endif + i = i+1 +.endr +.endm + +.macro call v:vararg + retpoline =0 +.irp reg_it,ARCH_REG_NAMES +.ifc "\v", "__x86_indirect_thunk_\reg_it" + relpoline_call reg=\reg_it + retpoline =1 +.endif +.endr +.if retpoline == 0 + {disp8} call \v +.endif +.endm + #else /* __ASSEMBLY__ */ #define ANNOTATE_NOSPEC_ALTERNATIVE \
@@ -288,6 +387,26 @@ static inline void indirect_branch_prediction_barrier(void)alternative_msr_write (MSR_IA32_PRED_CMD , val,X86_FEATURE_USE_IBPB ); } +/* Data structure that is used during the learning stage */ +struct relpoline_sample { +u32 src; +u32 dst; +u32 cnt; +u32 padding; +} __packed; + +DECLARE_PER_CPU_ALIGNED(struct relpoline_sample[RELPOLINE_SAMPLES_NUM], + relpoline_samples); + +/* +* Information for relpolines as it is saved in the source. +*/ +struct relpoline_entry { +void *rip; +u8 reg; +} __packed; + +/* The Intel SPEC CTRL MSR base value cache */ extern u64 x86_spec_ctrl_base;
arch/x86/kernel/Makefile +1 -0
@@ -138,6 +138,7 @@ obj-$(CONFIG_X86_INTEL_UMIP) += umip.o obj-$(CONFIG_UNWINDER_ORC) += unwind_orc.o obj-$(CONFIG_UNWINDER_FRAME_POINTER) += unwind_frame.o obj-$(CONFIG_UNWINDER_GUESS) += unwind_guess.o +obj-$(CONFIG_RETPOLINE) += nospec-branch.o ### # 64 bit specific files
arch/x86/kernel/asm-offsets.c +6 -0
@@ -18,6 +18,7 @@#include <asm/bootparam.h> #include <asm/suspend.h> #include <asm/tlbflush.h> +#include <asm/nospec-branch.h> #ifdef CONFIG_XEN #include <xen/interface/xen.h>
@@ -104,4 +105,9 @@ void common(void) {OFFSET (TSS_sp0, tss_struct, x86_tss.sp0);OFFSET (TSS_sp1, tss_struct, x86_tss.sp1);OFFSET (TSS_sp2, tss_struct, x86_tss.sp2); + +/* Relpolines */ +OFFSET (RELPOLINE_SAMPLE_src, relpoline_sample, src); +OFFSET (RELPOLINE_SAMPLE_dst, relpoline_sample, dst); +OFFSET (RELPOLINE_SAMPLE_cnt, relpoline_sample, cnt); }
arch/x86/kernel/macros.S +1 -0
@@ -14,3 +14,4 @@ #include <asm/asm.h> #include <asm/cpufeature.h> #include <asm/jump_label.h> +#include <asm/nospec-branch.h>
arch/x86/kernel/nospec-branch.c new file +5 -0
@@ -0,0 +1,5 @@ +#include <linux/percpu.h> +#include <asm/nospec-branch.h> + +DEFINE_PER_CPU_ALIGNED(struct relpoline_sample[RELPOLINE_SAMPLES_NUM], + relpoline_samples);
arch/x86/kernel/vmlinux.lds.S +7 -0
@@ -355,6 +355,13 @@ SECTIONS .data_nosave : AT(ADDR(.data_nosave) - LOAD_OFFSET) { NOSAVE_DATA } + + . = ALIGN(8); + .relpolines : AT(ADDR(.relpolines) - LOAD_OFFSET) { + __relpolines = .; + *(.relpolines) + __relpolines_end = .; + } #endif /* BSS */
arch/x86/lib/retpoline.S +75 -0
85 lines not on this page · raw message
Signature · 1 line
2.17.1
RFC PATCH 3/5 x86: interface for accessing indirect branch locations
In reply to Nadav AmitAdding a C interface to access the locations of indirect branches. To be used for dynamic patching.
--- arch/x86/include/asm/nospec-branch.h | 1 - arch/x86/include/asm/sections.h | 2 ++ include/linux/module.h | 5 +++++ kernel/module.c | 8 ++++++++ 4 files changed, 15 insertions(+), 1 deletion(-)
arch/x86/include/asm/nospec-branch.h +0 -1
@@ -406,7 +406,6 @@ struct relpoline_entry {u8 reg; } __packed; -/* The Intel SPEC CTRL MSR base value cache */ extern u64 x86_spec_ctrl_base;
arch/x86/include/asm/sections.h +2 -0
@@ -4,6 +4,7 @@#include <asm-generic/sections.h> #include <asm/extable.h> +#include <asm/nospec-branch.h> extern char __brk_base[], __brk_limit[];extern struct exception_table_entry __stop___ex_table[];
@@ -11,6 +12,7 @@ extern char __end_rodata_aligned[];#if defined(CONFIG_X86_64 )extern char __end_rodata_hpage_align[]; +extern const struct relpoline_entry __relpolines[], __relpolines_end[];#endif #endif /* _ASM_X86_SECTIONS_H */
include/linux/module.h +5 -0
@@ -39,6 +39,7 @@ struct modversion_info {struct module ;struct exception_table_entry ; +struct relpoline_entry ;struct module_kobject {struct kobject kobj;
@@ -387,6 +388,10 @@ struct module {unsigned int num_exentries;struct exception_table_entry *extable; +/* Cachepolines */ +unsigned int num_relpolines; +struct relpoline_entry *relpolines; +/* Startup function. */ int (*init)(void );
kernel/module.c +8 -0
@@ -64,6 +64,7 @@#include <linux/bsearch.h> #include <linux/dynamic_debug.h> #include <linux/audit.h> +#include <asm/nospec-branch.h> #include <uapi/linux/module.h> #include "module-internal.h"
@@ -256,6 +257,7 @@ static void mod_update_bounds(struct module *mod)#ifdef CONFIG_KGDB_KDB struct list_head *kdb_modules = &modules;/* kdb needs the list of modules */ #endif /* CONFIG_KGDB_KDB */ +struct list_head *cachepoline_modules = &modules;static void module_assert_mutex (void ) {
@@ -3125,6 +3127,12 @@ static int find_module_sections(struct module *mod, struct load_info *info) mod->extable =section_objs (info,"__ex_table" ,sizeof (*mod->extable), &mod->num_exentries); +#ifdef CONFIG_X86_64 + mod->relpolines =section_objs (info,"__relpolines" , +sizeof (*mod->relpolines), + &mod->num_relpolines); +#endif +if (section_addr (info,"__obsparm" ))pr_warn ("%s: Ignoring obsolete parameters\n" , mod->name);
Signature · 1 line
2.17.1
RFC PATCH 4/5 x86: learning and patching indirect branch targets
In reply to Nadav AmitDuring runtime, we collect the targets of indirect branch targets and patch them in. Patching is done asynchronously, by modifying each of the relpoline code-paths separately while diverting code execution to the other path during patching. Preemption is disabled while the code runs, and we wait for preemption to occur on each core to ensure no core is executing the patched code.
To make use of relpolines, a worker goes over the experienced indirect calls targets and sorts them according to frequency. The target that was encountered most times is patched in.
Periodically, the indirect branches are set back into learning mode to see whether the targets have changed. The current policy might be too aggressive.
--- arch/x86/include/asm/nospec-branch.h | 3 + arch/x86/kernel/nospec-branch.c | 894 +++++++++++++++++++++++++++ 2 files changed, 897 insertions(+)
arch/x86/include/asm/nospec-branch.h +3 -0
@@ -406,6 +406,9 @@ struct relpoline_entry {u8 reg; } __packed; +extern const void *indirect_thunks[16 ]; +extern const void *save_relpoline_funcs[16 ]; +/* The Intel SPEC CTRL MSR base value cache */ extern u64 x86_spec_ctrl_base;
arch/x86/kernel/nospec-branch.c +894 -0
899 lines not on this page · raw message
Signature · 1 line
2.17.1
RFC PATCH 5/5 x86: relpoline: disabling interface
In reply to Nadav AmitIn certain cases it is beneficial not to use indirect branch promotion. One such case is seccomp, which may hold multiple filters and different ones for different processes. The interface indicates to the macro not to add a relpoline to the the indirect branch.
--- arch/x86/include/asm/nospec-branch.h | 25 +++++++++++++++++++++++++ kernel/seccomp.c | 2 ++ 2 files changed, 27 insertions(+)
arch/x86/include/asm/nospec-branch.h +25 -0
@@ -246,7 +246,21 @@ .endr .endm +.L_DISABLE_INDIRECT_BRANCH_OPT =0 + +.macro disable_indirect_branch_opt +_DISABLE_INDIRECT_BRANCH_OPT =1 +.endm + +.macro enable_indirect_branch_opt +_DISABLE_INDIRECT_BRANCH_OPT =0 +.endm + .macro call v:vararg +.ifc _DISABLE_INDIRECT_BRANCH_OPT, "1" +# The pseudo-prefix is just to avoid expanding the macro + {disp8} call \v +.else retpoline =0 .irp reg_it,ARCH_REG_NAMES .ifc "\v", "__x86_indirect_thunk_\reg_it"
@@ -257,6 +271,7 @@ .if retpoline == 0 {disp8} call \v .endif +.endif .endm#else /* __ASSEMBLY__ */
@@ -409,6 +424,16 @@ struct relpoline_entry {extern const void *indirect_thunks[16 ];extern const void *save_relpoline_funcs[16 ]; +static inline void enable_relpolines (void ) +{ + asmvolatile ("enable_indirect_branch_opt" ); +} + +static inline void disable_relpolines (void ) +{ + asmvolatile ("disable_indirect_branch_opt" ); +} +/* The Intel SPEC CTRL MSR base value cache */ extern u64 x86_spec_ctrl_base;
kernel/seccomp.c +2 -0
@@ -207,6 +207,7 @@ static u32 seccomp_run_filters(const struct seccomp_data *sd,* All filters in the list are evaluated and the lowest BPF return * value always takes priority (ignoring the DATA). */ +disable_relpolines ();for (; f; f = f->prev) {u32 cur_ret =BPF_PROG_RUN (f->prog, sd);
@@ -215,6 +216,7 @@ static u32 seccomp_run_filters(const struct seccomp_data *sd, *match = f; } } +enable_relpolines ();return ret; }#endif /* CONFIG_SECCOMP_FILTER */
Signature · 1 line
2.17.1
Quoted 5 lines · Nadav Amit
base relpoline ---- ---------nginx 22898 25178 (+10%)
redis-ycsb 24523 25486 (+4%)
dbench 2144 2103 (+2%)
Just out of curiosity, which indirect branches are the culprits here for causing the slowdowns?
Quoted 9 lines · Dave Hansen
Quoted 5 lines · Nadav Amit
base relpoline
---- ---------nginx 22898 25178 (+10%)
redis-ycsb 24523 25486 (+4%)
dbench 2144 2103 (+2%)Just out of curiosity, which indirect branches are the culprits here for causing the slowdowns?
So I didn’t try to measure exactly which one. There are roughly 500 that actually “run” in my tests. Initially, I took the silly approach of trying to patch the C source-code using semi automatically-generated Coccinelle scripts, so I can tell you it is not just few branches but many. The network stack is full of function pointers (e.g., tcp_congestion_ops, tcp_sock_af_ops, dst_ops). The file-system also uses many function pointers (file_operations specifically). Compound-pages have d’tor and so on.
If you want, you can rebuild the kernel without retpolines and run
perf record -e br_inst_exec.taken_indirect_near_call:k (your workload)
For some reason I didn’t manage to use PEBS (:ppp) from either the guest or the host, so my results are a bit skewed (i.e., the sampled location is usually after the call was taken). Running dbench in the VM gives me the following “hot-spots”:
# Samples: 304 of event 'br_inst_exec.taken_indirect_near_call' # Event count (approx.): 60800912
#
# Overhead Command Shared Object Symbol
# ........ ....... ....................... .............................................
#
5.26% :197970 [guest.kernel.kallsyms] [g] __fget_light
4.28% :197969 [guest.kernel.kallsyms] [g] __fget_light
3.95% :197969 [guest.kernel.kallsyms] [g] dcache_readdir
3.29% :197970 [guest.kernel.kallsyms] [g] next_positive.isra.14
2.96% :197970 [guest.kernel.kallsyms] [g] __do_sys_kill
2.30% :197970 [guest.kernel.kallsyms] [g] apparmor_file_open
1.97% :197969 [guest.kernel.kallsyms] [g] __do_sys_kill
1.97% :197969 [guest.kernel.kallsyms] [g] next_positive.isra.14
1.97% :197970 [guest.kernel.kallsyms] [g] _raw_spin_lock
1.64% :197969 [guest.kernel.kallsyms] [g] __alloc_file
1.64% :197969 [guest.kernel.kallsyms] [g] common_file_perm
1.64% :197969 [guest.kernel.kallsyms] [g] filldir
1.64% :197970 [guest.kernel.kallsyms] [g] do_dentry_open
1.64% :197970 [guest.kernel.kallsyms] [g] kmem_cache_free
1.32% :197969 [guest.kernel.kallsyms] [g] __raw_callee_save___pv_queued_spin_unlock
1.32% :197969 [guest.kernel.kallsyms] [g] __slab_freeRegards,
Nadav
Quoted 10 lines · Nadav Amit
Quoted 8 lines · Nadav Amit
Quoted 5 lines
base relpoline
---- ---------nginx 22898 25178 (+10%)
redis-ycsb 24523 25486 (+4%)
dbench 2144 2103 (+2%)Just out of curiosity, which indirect branches are the culprits here for causing the slowdowns?
So I didn’t try to measure exactly which one. There are roughly 500 that actually “run” in my tests.
OK, cool, that's pretty much all I wanted to know, just that there aren't 3 of them or something for which we need all this infrastructure.
Quoted 33 lines · Nadav Amit
This RFC introduces indirect call promotion in runtime, which for the matter of simplification (and branding) will be called here "relpolines" (relative call + trampoline). Relpolines are mainly intended as a way of reducing retpoline overheads due to Spectre v2.
Unlike indirect call promotion through profile guided optimization, the proposed approach does not require a profiling stage, works well with modules whose address is unknown and can adapt to changing workloads.
The main idea is simple: for every indirect call, we inject a piece of code with fast- and slow-path calls. The fast path is used if the target matches the expected (hot) target. The slow-path uses a retpoline. During training, the slow-path is set to call a function that saves the call source and target in a hash-table and keep count for call frequency. The most common target is then patched into the hot path.
The patching is done on-the-fly by patching the conditional branch (opcode and offset) that is used to compare the target to the hot target. This allows to direct all cores to the fast-path, while patching the slow-path and vice-versa. Patching follows 2 more rules: (1) Only patch a single byte when the code might be executed by any core. (2) When patching more than one byte, ensure that all cores do not run the to-be-patched-code by preventing this code from being preempted, and using
synchronize_sched()after patching the branch that jumps over this code.Changing all the indirect calls to use relpolines is done using assembly macro magic. There are alternative solutions, but this one is relatively simple and transparent. There is also logic to retrain the software predictor, but the policy it uses may need to be refined.
Eventually the results are not bad (2 VCPU VM, throughput reported):
Quoted 8 lines
base relpoline ---- ---------nginx 22898 25178 (+10%)
redis-ycsb 24523 25486 (+4%)
dbench 2144 2103 (+2%)When retpolines are disabled, and if retraining is off, performance benefits are up to 2% (nginx), but are much less impressive.
Hi Nadav,
Peter pointed me to these patches during a discussion about retpoline profiling. Personally, I think this is brilliant. This could help networking and filesystem intensive workloads a lot.
Some high-level comments:
- "Relpoline" looks confusingly a lot like "retpoline". How about "optpoline"? To avoid confusing myself I will hereafter refer to it as such :-)
- Instead of patching one byte at a time, is there a reason why text_poke_bp() can't be used? That would greatly simplify the patching process, as everything could be patched in a single step.
- In many cases, a single direct call may not be sufficient, as there could be for example multiple tasks using different network protocols which need different callbacks for the same call site.
- I'm not sure about the periodic retraining logic, it seems a bit nondeterministic and bursty.
So I'd propose the following changes:
- In the optpoline, reserve space for multiple (5 or so) comparisons and direct calls. Maybe the number of reserved cmp/jne/call slots can be tweaked by the caller somehow. Or maybe it could grow as needed. Starting out, they would just be NOPs.
- Instead of the temporary learning mode, add permanent tracking to detect a direct call "miss" -- i.e., when none of the existing direct calls are applicable and the retpoline will be used.
- In the case of a miss (or N misses), it could trigger a direct call patching operation to be run later (workqueue or syscall exit). If all the direct call slots are full, it could patch the least recently modified one. If this causes thrashing (>x changes over y time), it could increase the number of direct call slots using a trampoline. Even if there were several slots, CPU branch prediction would presumably help make it much faster than a basic retpoline.
Thoughts?
Signature · 1 line
Josh
Quoted 42 lines · Josh Poimboeuf
Quoted 39 lines · Nadav Amit
This RFC introduces indirect call promotion in runtime, which for the matter of simplification (and branding) will be called here "relpolines" (relative call + trampoline). Relpolines are mainly intended as a way of reducing retpoline overheads due to Spectre v2.
Unlike indirect call promotion through profile guided optimization, the proposed approach does not require a profiling stage, works well with modules whose address is unknown and can adapt to changing workloads.
The main idea is simple: for every indirect call, we inject a piece of code with fast- and slow-path calls. The fast path is used if the target matches the expected (hot) target. The slow-path uses a retpoline. During training, the slow-path is set to call a function that saves the call source and target in a hash-table and keep count for call frequency. The most common target is then patched into the hot path.
The patching is done on-the-fly by patching the conditional branch (opcode and offset) that is used to compare the target to the hot target. This allows to direct all cores to the fast-path, while patching the slow-path and vice-versa. Patching follows 2 more rules: (1) Only patch a single byte when the code might be executed by any core. (2) When patching more than one byte, ensure that all cores do not run the to-be-patched-code by preventing this code from being preempted, and using
synchronize_sched()after patching the branch that jumps over this code.Changing all the indirect calls to use relpolines is done using assembly macro magic. There are alternative solutions, but this one is relatively simple and transparent. There is also logic to retrain the software predictor, but the policy it uses may need to be refined.
Eventually the results are not bad (2 VCPU VM, throughput reported):
base relpoline ---- ---------nginx 22898 25178 (+10%)
redis-ycsb 24523 25486 (+4%)
dbench 2144 2103 (+2%)
Quoted 8 lines
Quoted 2 lines
When retpolines are disabled, and if retraining is off, performance benefits are up to 2% (nginx), but are much less impressive.
Hi Nadav,
Peter pointed me to these patches during a discussion about retpoline profiling. Personally, I think this is brilliant. This could help networking and filesystem intensive workloads a lot.
Thanks! I was a bit held-back by the relatively limited number of responses. I finished another version two weeks ago, and every day I think: "should it be RFCv2 or v1”, ending up not sending it…
There is one issue that I realized while working on the new version: I’m not sure it is well-defined what an outline retpoline is allowed to do. The indirect branch promotion code can change rflags, which might cause correction issues. In practice, using gcc, it is not a problem.
Quoted 5 lines
Some high-level comments:
- "Relpoline" looks confusingly a lot like "retpoline". How about "optpoline"? To avoid confusing myself I will hereafter refer to it as such :-)
Sure. For the academic paper we submitted, we used a much worse name that my colleague came up with. I’m ok with anything other than that name (not mentioned to prevent double-blinding violations). I’ll go with your name.
Quoted 3 lines
- Instead of patching one byte at a time, is there a reason why
text_poke_bp()can't be used? That would greatly simplify the patching process, as everything could be patched in a single step.
I thought of it and maybe it is somehow possible, but there are several problems, for which I didn’t find a simple solution:
1. An indirect branch inside the BP handler might be the one we patch
2. An indirect branch inside an interrupt or NMI handler might be the one we patch
3. Overall, we need to patch more than a single instruction, and text_poke_bp() is not suitable
Quoted 3 lines
- In many cases, a single direct call may not be sufficient, as there could be for example multiple tasks using different network protocols which need different callbacks for the same call site.
We want to know during compilation how many targets to use. It is not super-simple to support multiple inlined targets, but it is feasible if you are willing to annotate when multiple targets are needed. We have a version which uses outlined indirect branch promotion when there are multiple targets, but it’s not ready for prime time, and the code-cache misses can induce some overheads.
Quoted 2 lines
- I'm not sure about the periodic retraining logic, it seems a bit nondeterministic and bursty.
I agree. It can be limited to cases in which modules are loaded/removed, or when the user explicitly asks for it to take place.
Quoted 20 lines
So I'd propose the following changes:
- In the optpoline, reserve space for multiple (5 or so) comparisons and direct calls. Maybe the number of reserved cmp/jne/call slots can be tweaked by the caller somehow. Or maybe it could grow as needed. Starting out, they would just be NOPs.
- Instead of the temporary learning mode, add permanent tracking to detect a direct call "miss" -- i.e., when none of the existing direct calls are applicable and the retpoline will be used.
- In the case of a miss (or N misses), it could trigger a direct call patching operation to be run later (workqueue or syscall exit). If all the direct call slots are full, it could patch the least recently modified one. If this causes thrashing (>x changes over y time), it could increase the number of direct call slots using a trampoline. Even if there were several slots, CPU branch prediction would presumably help make it much faster than a basic retpoline.
Quoted 1 line
Thoughts?
I’m ok with these changes in general, although having multiple inline targets is not super-simple. However, there are a few issues:
- There is potentially a negative impact due to code-size increase which I was worried about.
- I see no reason not to use all the available slots immediately when we encounter a miss.
- The order of the branches might be “wrong” (unoptimized) if we do not do any relearning.
- The main question is what to do if we run out of slots and still get (many?) misses. I presume the right thing is to disable the optpoline and jump over it to the retpoline.
Thanks again for the feedback, and please let me know what you think about my concerns.
Quoted 45 lines · Nadav Amit
Quoted 45 lines · Josh Poimboeuf
Quoted 41 lines · Nadav Amit
This RFC introduces indirect call promotion in runtime, which for the matter of simplification (and branding) will be called here "relpolines" (relative call + trampoline). Relpolines are mainly intended as a way of reducing retpoline overheads due to Spectre v2.
Unlike indirect call promotion through profile guided optimization, the proposed approach does not require a profiling stage, works well with modules whose address is unknown and can adapt to changing workloads.
The main idea is simple: for every indirect call, we inject a piece of code with fast- and slow-path calls. The fast path is used if the target matches the expected (hot) target. The slow-path uses a retpoline. During training, the slow-path is set to call a function that saves the call source and target in a hash-table and keep count for call frequency. The most common target is then patched into the hot path.
The patching is done on-the-fly by patching the conditional branch (opcode and offset) that is used to compare the target to the hot target. This allows to direct all cores to the fast-path, while patching the slow-path and vice-versa. Patching follows 2 more rules: (1) Only patch a single byte when the code might be executed by any core. (2) When patching more than one byte, ensure that all cores do not run the to-be-patched-code by preventing this code from being preempted, and using
synchronize_sched()after patching the branch that jumps over this code.Changing all the indirect calls to use relpolines is done using assembly macro magic. There are alternative solutions, but this one is relatively simple and transparent. There is also logic to retrain the software predictor, but the policy it uses may need to be refined.
Eventually the results are not bad (2 VCPU VM, throughput reported):
base relpoline ---- ---------nginx 22898 25178 (+10%)
redis-ycsb 24523 25486 (+4%)
dbench 2144 2103 (+2%)When retpolines are disabled, and if retraining is off, performance benefits are up to 2% (nginx), but are much less impressive.
Quoted 7 lines
Quoted 5 lines
Hi Nadav,
Peter pointed me to these patches during a discussion about retpoline profiling. Personally, I think this is brilliant. This could help networking and filesystem intensive workloads a lot.
Thanks! I was a bit held-back by the relatively limited number of responses.
It is a rather, erm, ambitious idea, maybe they were speechless :-)
Quoted 7 lines
I finished another version two weeks ago, and every day I think: "should it be RFCv2 or v1”, ending up not sending it…
There is one issue that I realized while working on the new version: I’m not sure it is well-defined what an outline retpoline is allowed to do. The indirect branch promotion code can change rflags, which might cause correction issues. In practice, using gcc, it is not a problem.
Callees can clobber flags, so it seems fine to me.
Quoted 10 lines
Quoted 5 lines
Some high-level comments:
- "Relpoline" looks confusingly a lot like "retpoline". How about "optpoline"? To avoid confusing myself I will hereafter refer to it as such :-)
Sure. For the academic paper we submitted, we used a much worse name that my colleague came up with. I’m ok with anything other than that name (not mentioned to prevent double-blinding violations). I’ll go with your name.
Quoted 8 lines
Quoted 3 lines
- Instead of patching one byte at a time, is there a reason why
text_poke_bp()can't be used? That would greatly simplify the patching process, as everything could be patched in a single step.I thought of it and maybe it is somehow possible, but there are several problems, for which I didn’t find a simple solution:
1. An indirect branch inside the BP handler might be the one we patch
I _think_ nested INT3s should be doable, because they don't use IST. Maybe Andy can clarify.
To avoid recursion we'd have to make sure not to use any function pointers in do_int3() before or during the call to poke_int3_handler().
Incidentally I'm working on a patch which adds an indirect call to poke_int3_handler(). We'd have to disable optolines for that code.
Quoted 2 lines
2. An indirect branch inside an interrupt or NMI handler might be the one we patch
But INT3s just use the existing stack, and NMIs support nesting, so I'm thinking that should also be doable. Andy?
Quoted 2 lines
3. Overall, we need to patch more than a single instruction, and
text_poke_bp()is not suitable
Hm, I suppose that's true.
Quoted 7 lines
Quoted 3 lines
- In many cases, a single direct call may not be sufficient, as there could be for example multiple tasks using different network protocols which need different callbacks for the same call site.
We want to know during compilation how many targets to use. It is not super-simple to support multiple inlined targets, but it is feasible if you are willing to annotate when multiple targets are needed.
Why would an annotation be needed? Is there a reason why the 'call' macro wouldn't work?
I hate to say it, but another option would be a compiler plugin.
Quoted 3 lines
We have a version which uses outlined indirect branch promotion when there are multiple targets, but it’s not ready for prime time, and the code-cache misses can induce some overheads.
It would be interesting to see some measurements comparing inline and out-of-line. If there were multiple direct call slots then out-of-line could have advantages in some cases since it reduces the original function's i-cache footprint. But maybe it wouldn't be worth it.
Quoted 26 lines
Quoted 2 lines
- I'm not sure about the periodic retraining logic, it seems a bit nondeterministic and bursty.
I agree. It can be limited to cases in which modules are loaded/removed, or when the user explicitly asks for it to take place.
Quoted 20 lines
So I'd propose the following changes:
- In the optpoline, reserve space for multiple (5 or so) comparisons and direct calls. Maybe the number of reserved cmp/jne/call slots can be tweaked by the caller somehow. Or maybe it could grow as needed. Starting out, they would just be NOPs.
- Instead of the temporary learning mode, add permanent tracking to detect a direct call "miss" -- i.e., when none of the existing direct calls are applicable and the retpoline will be used.
- In the case of a miss (or N misses), it could trigger a direct call patching operation to be run later (workqueue or syscall exit). If all the direct call slots are full, it could patch the least recently modified one. If this causes thrashing (>x changes over y time), it could increase the number of direct call slots using a trampoline. Even if there were several slots, CPU branch prediction would presumably help make it much faster than a basic retpoline.
Quoted 7 lines
Quoted 1 line
Thoughts?
I’m ok with these changes in general, although having multiple inline targets is not super-simple. However, there are a few issues:
- There is potentially a negative impact due to code-size increase which I was worried about.
True. Though out-of-line could help with that (but it would also have downsides of course).
Quoted 2 lines
- I see no reason not to use all the available slots immediately when we encounter a miss.
Agreed.
Quoted 2 lines
- The order of the branches might be “wrong” (unoptimized) if we do not do any relearning.
True, though branch prediction would mitigate that to a certain degree. Also if the number of slots is reasonably small (which it will probably need to be anyway) then it might be good enough.
Quoted 3 lines
- The main question is what to do if we run out of slots and still get (many?) misses. I presume the right thing is to disable the optpoline and jump over it to the retpoline.
It may need some experimentation. You could just try a simple round-robin patching scheme. Possibly only writing after X number of misses. And if that doesn't help, just disable it. Such a simple approach might work well enough in practice.
Signature · 1 line
Josh
Andy Lutomirski
maintainerQuoted 60 lines · Josh Poimboeuf
Quoted 45 lines · Nadav Amit
Quoted 45 lines · Josh Poimboeuf
Quoted 41 lines · Nadav Amit
This RFC introduces indirect call promotion in runtime, which for the matter of simplification (and branding) will be called here "relpolines" (relative call + trampoline). Relpolines are mainly intended as a way of reducing retpoline overheads due to Spectre v2.
Unlike indirect call promotion through profile guided optimization, the proposed approach does not require a profiling stage, works well with modules whose address is unknown and can adapt to changing workloads.
The main idea is simple: for every indirect call, we inject a piece of code with fast- and slow-path calls. The fast path is used if the target matches the expected (hot) target. The slow-path uses a retpoline. During training, the slow-path is set to call a function that saves the call source and target in a hash-table and keep count for call frequency. The most common target is then patched into the hot path.
The patching is done on-the-fly by patching the conditional branch (opcode and offset) that is used to compare the target to the hot target. This allows to direct all cores to the fast-path, while patching the slow-path and vice-versa. Patching follows 2 more rules: (1) Only patch a single byte when the code might be executed by any core. (2) When patching more than one byte, ensure that all cores do not run the to-be-patched-code by preventing this code from being preempted, and using
synchronize_sched()after patching the branch that jumps over this code.Changing all the indirect calls to use relpolines is done using assembly macro magic. There are alternative solutions, but this one is relatively simple and transparent. There is also logic to retrain the software predictor, but the policy it uses may need to be refined.
Eventually the results are not bad (2 VCPU VM, throughput reported):
base relpoline ---- --------- nginx 22898 25178 (+10%) redis-ycsb 24523 25486 (+4%) dbench 2144 2103 (+2%)When retpolines are disabled, and if retraining is off, performance benefits are up to 2% (nginx), but are much less impressive.
Quoted 7 lines
Quoted 5 lines
Hi Nadav,
Peter pointed me to these patches during a discussion about retpoline profiling. Personally, I think this is brilliant. This could help networking and filesystem intensive workloads a lot.
Thanks! I was a bit held-back by the relatively limited number of responses.
It is a rather, erm, ambitious idea, maybe they were speechless :-)
Quoted 3 lines
I finished another version two weeks ago, and every day I think: "should it be RFCv2 or v1”, ending up not sending it…
Quoted 6 lines
Quoted 4 lines
There is one issue that I realized while working on the new version: I’m not sure it is well-defined what an outline retpoline is allowed to do. The indirect branch promotion code can change rflags, which might cause correction issues. In practice, using gcc, it is not a problem.
Callees can clobber flags, so it seems fine to me.
Just to check I understand your approach right: you made a macro called "call", and you're therefore causing all instances of "call" to become magic? This is... terrifying. It's even plausibly worse than "#define if" :) The scariest bit is that it will impact inline asm as well. Maybe a gcc plugin would be less alarming?
Quoted 5 lines
Quoted 2 lines
1. An indirect branch inside the BP handler might be the one we patch
I _think_ nested INT3s should be doable, because they don't use IST. Maybe Andy can clarify.
int3 should survive recursion these days. Although I admit I'm currently wondering what happens if one thread puts a kprobe on an address that another thread tries to text_poke.
Also, this relpoline magic is likely to start patching text at runtime on a semi-regular basis. This type of patching is *slow*. Is it a problem?
Quoted 6 lines
Quoted 2 lines
2. An indirect branch inside an interrupt or NMI handler might be the one we patch
But INT3s just use the existing stack, and NMIs support nesting, so I'm thinking that should also be doable. Andy?
In principle, as long as the code isn't NOKPROBE_SYMBOL-ified, we should be fine, right? I'd be a little nervous if we get an int3 in the C code that handles the early part of an NMI from user mode. It's *probably* okay, but one of the alarming issues is that the int3 return path will implicitly unmask NMI, which isn't fantastic. Maybe we finally need to dust off my old "return using RET" code to get rid of that problem.
Quoted 67 lines · Andy Lutomirski
Quoted 64 lines · Josh Poimboeuf
Quoted 45 lines · Nadav Amit
Quoted 45 lines · Josh Poimboeuf
Quoted 41 lines · Nadav Amit
This RFC introduces indirect call promotion in runtime, which for the matter of simplification (and branding) will be called here "relpolines" (relative call + trampoline). Relpolines are mainly intended as a way of reducing retpoline overheads due to Spectre v2.
Unlike indirect call promotion through profile guided optimization, the proposed approach does not require a profiling stage, works well with modules whose address is unknown and can adapt to changing workloads.
The main idea is simple: for every indirect call, we inject a piece of code with fast- and slow-path calls. The fast path is used if the target matches the expected (hot) target. The slow-path uses a retpoline. During training, the slow-path is set to call a function that saves the call source and target in a hash-table and keep count for call frequency. The most common target is then patched into the hot path.
The patching is done on-the-fly by patching the conditional branch (opcode and offset) that is used to compare the target to the hot target. This allows to direct all cores to the fast-path, while patching the slow-path and vice-versa. Patching follows 2 more rules: (1) Only patch a single byte when the code might be executed by any core. (2) When patching more than one byte, ensure that all cores do not run the to-be-patched-code by preventing this code from being preempted, and using
synchronize_sched()after patching the branch that jumps over this code.Changing all the indirect calls to use relpolines is done using assembly macro magic. There are alternative solutions, but this one is relatively simple and transparent. There is also logic to retrain the software predictor, but the policy it uses may need to be refined.
Eventually the results are not bad (2 VCPU VM, throughput reported):
base relpoline ---- --------- nginx 22898 25178 (+10%) redis-ycsb 24523 25486 (+4%) dbench 2144 2103 (+2%)When retpolines are disabled, and if retraining is off, performance benefits are up to 2% (nginx), but are much less impressive.
Quoted 7 lines
Quoted 5 lines
Hi Nadav,
Peter pointed me to these patches during a discussion about retpoline profiling. Personally, I think this is brilliant. This could help networking and filesystem intensive workloads a lot.
Thanks! I was a bit held-back by the relatively limited number of responses.
It is a rather, erm, ambitious idea, maybe they were speechless :-)
Quoted 7 lines
I finished another version two weeks ago, and every day I think: "should it be RFCv2 or v1”, ending up not sending it…
There is one issue that I realized while working on the new version: I’m not sure it is well-defined what an outline retpoline is allowed to do. The indirect branch promotion code can change rflags, which might cause correction issues. In practice, using gcc, it is not a problem.
Quoted 7 lines
Quoted 1 line
Callees can clobber flags, so it seems fine to me.
Just to check I understand your approach right: you made a macro called "call", and you're therefore causing all instances of "call" to become magic? This is... terrifying. It's even plausibly worse than "#define if" :) The scariest bit is that it will impact inline asm as well. Maybe a gcc plugin would be less alarming?
It is likely to look less alarming. When I looked at the inline retpoline implementation of gcc, it didn’t look much better than what I did - it basically just emits assembly instructions.
Anyhow, I look (again) into using gcc-plugins.
Quoted 8 lines
Quoted 4 lines
Quoted 1 line
1. An indirect branch inside the BP handler might be the one we patch
I _think_ nested INT3s should be doable, because they don't use IST. Maybe Andy can clarify.
int3 should survive recursion these days. Although I admit I'm currently wondering what happens if one thread puts a kprobe on an address that another thread tries to
text_poke.
The issue I regarded is having an indirect call *inside* the the handler. For example, you try to patch the call to bp_int3_handler and then get an int3. They can be annotated to prevent them from being patched. Then again, I need to see how gcc plugins can get these annotations.
Quoted 4 lines
Also, this relpoline magic is likely to start patching text at runtime on a semi-regular basis. This type of patching is *slow*. Is it a problem?
It didn’t appear so. Although there are >10000 indirect branches in the kernel, you don’t patch too many of them even you are doing relearning.
Quoted 7 lines
Quoted 6 lines
Quoted 3 lines
2. An indirect branch inside an interrupt or NMI handler might be the one we patch
But INT3s just use the existing stack, and NMIs support nesting, so I'm thinking that should also be doable. Andy?
Quoted 7 lines
In principle, as long as the code isn't NOKPROBE_SYMBOL-ified, we should be fine, right? I'd be a little nervous if we get an int3 in the C code that handles the early part of an NMI from user mode. It's *probably* okay, but one of the alarming issues is that the int3 return path will implicitly unmask NMI, which isn't fantastic. Maybe we finally need to dust off my old "return using RET" code to get rid of that problem.
So it may be possible. It would require having a new text_poke_bp() variant for multiple instructions. text_poke_bp() might be slower though.
Andy Lutomirski
reviewerQuoted 75 lines · Nadav Amit
Quoted 67 lines · Andy Lutomirski
Quoted 66 lines · Josh Poimboeuf
Quoted 45 lines · Nadav Amit
Quoted 45 lines · Josh Poimboeuf
Quoted 43 lines · Nadav Amit
This RFC introduces indirect call promotion in runtime, which for the matter of simplification (and branding) will be called here "relpolines" (relative call + trampoline). Relpolines are mainly intended as a way of reducing retpoline overheads due to Spectre v2.
Unlike indirect call promotion through profile guided optimization, the proposed approach does not require a profiling stage, works well with modules whose address is unknown and can adapt to changing workloads.
The main idea is simple: for every indirect call, we inject a piece of code with fast- and slow-path calls. The fast path is used if the target matches the expected (hot) target. The slow-path uses a retpoline. During training, the slow-path is set to call a function that saves the call source and target in a hash-table and keep count for call frequency. The most common target is then patched into the hot path.
The patching is done on-the-fly by patching the conditional branch (opcode and offset) that is used to compare the target to the hot target. This allows to direct all cores to the fast-path, while patching the slow-path and vice-versa. Patching follows 2 more rules: (1) Only patch a single byte when the code might be executed by any core. (2) When patching more than one byte, ensure that all cores do not run the to-be-patched-code by preventing this code from being preempted, and using
synchronize_sched()after patching the branch that jumps over this code.Changing all the indirect calls to use relpolines is done using assembly macro magic. There are alternative solutions, but this one is relatively simple and transparent. There is also logic to retrain the software predictor, but the policy it uses may need to be refined.
Eventually the results are not bad (2 VCPU VM, throughput reported):
base relpoline ---- --------- nginx 22898 25178 (+10%) redis-ycsb 24523 25486 (+4%) dbench 2144 2103 (+2%)When retpolines are disabled, and if retraining is off, performance benefits are up to 2% (nginx), but are much less impressive.
Quoted 7 lines
Quoted 5 lines
Hi Nadav,
Peter pointed me to these patches during a discussion about retpoline profiling. Personally, I think this is brilliant. This could help networking and filesystem intensive workloads a lot.
Thanks! I was a bit held-back by the relatively limited number of responses.
It is a rather, erm, ambitious idea, maybe they were speechless :-)
Quoted 7 lines
I finished another version two weeks ago, and every day I think: "should it be RFCv2 or v1”, ending up not sending it…
There is one issue that I realized while working on the new version: I’m not sure it is well-defined what an outline retpoline is allowed to do. The indirect branch promotion code can change rflags, which might cause correction issues. In practice, using gcc, it is not a problem.
Quoted 7 lines
Quoted 1 line
Callees can clobber flags, so it seems fine to me.
Just to check I understand your approach right: you made a macro called "call", and you're therefore causing all instances of "call" to become magic? This is... terrifying. It's even plausibly worse than "#define if" :) The scariest bit is that it will impact inline asm as well. Maybe a gcc plugin would be less alarming?
Quoted 3 lines
It is likely to look less alarming. When I looked at the inline retpoline implementation of gcc, it didn’t look much better than what I did - it basically just emits assembly instructions.
To be clear, that wasn’t a NAK. It was merely a “this is alarming.”
Hey Josh - you could potentially do the same hack to generate the static call tables. Take that, objtool.
Quoted 8 lines
Anyhow, I look (again) into using gcc-plugins.
Quoted 5 lines
Quoted 4 lines
Quoted 1 line
1. An indirect branch inside the BP handler might be the one we patch
I _think_ nested INT3s should be doable, because they don't use IST. Maybe Andy can clarify.
Quoted 8 lines
Quoted 3 lines
int3 should survive recursion these days. Although I admit I'm currently wondering what happens if one thread puts a kprobe on an address that another thread tries to
text_poke.The issue I regarded is having an indirect call *inside* the the handler. For example, you try to patch the call to
bp_int3_handlerand then get an int3. They can be annotated to prevent them from being patched. Then again, I need to see how gcc plugins can get these annotations.
We could move the relevant code to a separate object file that disables the whole mess.
Quoted 24 lines
Quoted 5 lines
Also, this relpoline magic is likely to start patching text at runtime on a semi-regular basis. This type of patching is *slow*. Is it a problem?
It didn’t appear so. Although there are >10000 indirect branches in the kernel, you don’t patch too many of them even you are doing relearning.
Quoted 7 lines
Quoted 6 lines
Quoted 3 lines
2. An indirect branch inside an interrupt or NMI handler might be the one we patch
But INT3s just use the existing stack, and NMIs support nesting, so I'm thinking that should also be doable. Andy?
Quoted 7 lines
In principle, as long as the code isn't NOKPROBE_SYMBOL-ified, we should be fine, right? I'd be a little nervous if we get an int3 in the C code that handles the early part of an NMI from user mode. It's *probably* okay, but one of the alarming issues is that the int3 return path will implicitly unmask NMI, which isn't fantastic. Maybe we finally need to dust off my old "return using RET" code to get rid of that problem.
Quoted 4 lines
So it may be possible. It would require having a new
text_poke_bp()variant for multiple instructions.text_poke_bp()might be slower though.
Can you outline how the patching works at all? You’re getting rid of preempt disabling, right? What’s the actual sequence and how does it work?
Quoted 4 lines · Andy Lutomirski
To be clear, that wasn’t a NAK. It was merely a “this is alarming.”
Hey Josh - you could potentially do the same hack to generate the static call tables. Take that, objtool.
Ha, after witnessing Nadav's glorious hack, I considered that. But I didn't see a way to pull it off, because asm macro conditionals don't seem to have a way to test for a regex (or at least a named prefix) for the call target.
I'd need a way to detect "call __static_call_tramp_<whatever>".
Signature · 1 line
Josh
Andy Lutomirski
maintainerQuoted 79 lines · Andy Lutomirski
Quoted 75 lines · Nadav Amit
Quoted 74 lines · Andy Lutomirski
Quoted 61 lines · Josh Poimboeuf
Quoted 45 lines · Nadav Amit
Quoted 45 lines · Josh Poimboeuf
Quoted 43 lines · Nadav Amit
This RFC introduces indirect call promotion in runtime, which for the matter of simplification (and branding) will be called here "relpolines" (relative call + trampoline). Relpolines are mainly intended as a way of reducing retpoline overheads due to Spectre v2.
Unlike indirect call promotion through profile guided optimization, the proposed approach does not require a profiling stage, works well with modules whose address is unknown and can adapt to changing workloads.
The main idea is simple: for every indirect call, we inject a piece of code with fast- and slow-path calls. The fast path is used if the target matches the expected (hot) target. The slow-path uses a retpoline. During training, the slow-path is set to call a function that saves the call source and target in a hash-table and keep count for call frequency. The most common target is then patched into the hot path.
The patching is done on-the-fly by patching the conditional branch (opcode and offset) that is used to compare the target to the hot target. This allows to direct all cores to the fast-path, while patching the slow-path and vice-versa. Patching follows 2 more rules: (1) Only patch a single byte when the code might be executed by any core. (2) When patching more than one byte, ensure that all cores do not run the to-be-patched-code by preventing this code from being preempted, and using
synchronize_sched()after patching the branch that jumps over this code.Changing all the indirect calls to use relpolines is done using assembly macro magic. There are alternative solutions, but this one is relatively simple and transparent. There is also logic to retrain the software predictor, but the policy it uses may need to be refined.
Eventually the results are not bad (2 VCPU VM, throughput reported):
base relpoline ---- --------- nginx 22898 25178 (+10%) redis-ycsb 24523 25486 (+4%) dbench 2144 2103 (+2%)When retpolines are disabled, and if retraining is off, performance benefits are up to 2% (nginx), but are much less impressive.
Quoted 7 lines
Quoted 5 lines
Hi Nadav,
Peter pointed me to these patches during a discussion about retpoline profiling. Personally, I think this is brilliant. This could help networking and filesystem intensive workloads a lot.
Thanks! I was a bit held-back by the relatively limited number of responses.
It is a rather, erm, ambitious idea, maybe they were speechless :-)
Quoted 3 lines
I finished another version two weeks ago, and every day I think: "should it be RFCv2 or v1”, ending up not sending it…
Quoted 6 lines
Quoted 4 lines
There is one issue that I realized while working on the new version: I’m not sure it is well-defined what an outline retpoline is allowed to do. The indirect branch promotion code can change rflags, which might cause correction issues. In practice, using gcc, it is not a problem.
Callees can clobber flags, so it seems fine to me.
Just to check I understand your approach right: you made a macro called "call", and you're therefore causing all instances of "call" to become magic? This is... terrifying. It's even plausibly worse than "#define if" :) The scariest bit is that it will impact inline asm as well. Maybe a gcc plugin would be less alarming?
Quoted 5 lines
Quoted 3 lines
It is likely to look less alarming. When I looked at the inline retpoline implementation of gcc, it didn’t look much better than what I did - it basically just emits assembly instructions.
To be clear, that wasn’t a NAK. It was merely a “this is alarming.”
Although... how do you avoid matching on things that really don't want this treatment? paravirt ops come to mind.
Quoted 80 lines · Andy Lutomirski
Quoted 79 lines · Andy Lutomirski
Quoted 75 lines · Nadav Amit
Quoted 74 lines · Andy Lutomirski
Quoted 61 lines · Josh Poimboeuf
Quoted 45 lines · Nadav Amit
Quoted 45 lines · Josh Poimboeuf
Quoted 43 lines · Nadav Amit
This RFC introduces indirect call promotion in runtime, which for the matter of simplification (and branding) will be called here "relpolines" (relative call + trampoline). Relpolines are mainly intended as a way of reducing retpoline overheads due to Spectre v2.
Unlike indirect call promotion through profile guided optimization, the proposed approach does not require a profiling stage, works well with modules whose address is unknown and can adapt to changing workloads.
The main idea is simple: for every indirect call, we inject a piece of code with fast- and slow-path calls. The fast path is used if the target matches the expected (hot) target. The slow-path uses a retpoline. During training, the slow-path is set to call a function that saves the call source and target in a hash-table and keep count for call frequency. The most common target is then patched into the hot path.
The patching is done on-the-fly by patching the conditional branch (opcode and offset) that is used to compare the target to the hot target. This allows to direct all cores to the fast-path, while patching the slow-path and vice-versa. Patching follows 2 more rules: (1) Only patch a single byte when the code might be executed by any core. (2) When patching more than one byte, ensure that all cores do not run the to-be-patched-code by preventing this code from being preempted, and using
synchronize_sched()after patching the branch that jumps over this code.Changing all the indirect calls to use relpolines is done using assembly macro magic. There are alternative solutions, but this one is relatively simple and transparent. There is also logic to retrain the software predictor, but the policy it uses may need to be refined.
Eventually the results are not bad (2 VCPU VM, throughput reported):
base relpoline ---- --------- nginx 22898 25178 (+10%) redis-ycsb 24523 25486 (+4%) dbench 2144 2103 (+2%)When retpolines are disabled, and if retraining is off, performance benefits are up to 2% (nginx), but are much less impressive.
Quoted 7 lines
Quoted 5 lines
Hi Nadav,
Peter pointed me to these patches during a discussion about retpoline profiling. Personally, I think this is brilliant. This could help networking and filesystem intensive workloads a lot.
Thanks! I was a bit held-back by the relatively limited number of responses.
It is a rather, erm, ambitious idea, maybe they were speechless :-)
Quoted 3 lines
I finished another version two weeks ago, and every day I think: "should it be RFCv2 or v1”, ending up not sending it…
Quoted 6 lines
Quoted 4 lines
There is one issue that I realized while working on the new version: I’m not sure it is well-defined what an outline retpoline is allowed to do. The indirect branch promotion code can change rflags, which might cause correction issues. In practice, using gcc, it is not a problem.
Callees can clobber flags, so it seems fine to me.
Just to check I understand your approach right: you made a macro called "call", and you're therefore causing all instances of "call" to become magic? This is... terrifying. It's even plausibly worse than "#define if" :) The scariest bit is that it will impact inline asm as well. Maybe a gcc plugin would be less alarming?
Quoted 8 lines
Quoted 5 lines
Quoted 3 lines
It is likely to look less alarming. When I looked at the inline retpoline implementation of gcc, it didn’t look much better than what I did - it basically just emits assembly instructions.
To be clear, that wasn’t a NAK. It was merely a “this is alarming.”
Although... how do you avoid matching on things that really don't want this treatment? paravirt ops come to mind.
Paravirt ops don't use retpolines because they're patched into direct calls during boot. So Nadav's patches won't touch them.
Signature · 1 line
Josh
Quoted 85 lines · Josh Poimboeuf
Quoted 82 lines · Andy Lutomirski
Quoted 81 lines · Andy Lutomirski
Quoted 78 lines · Nadav Amit
Quoted 67 lines · Andy Lutomirski
Quoted 66 lines · Josh Poimboeuf
Quoted 45 lines · Nadav Amit
Quoted 45 lines · Josh Poimboeuf
Quoted 43 lines · Nadav Amit
This RFC introduces indirect call promotion in runtime, which for the matter of simplification (and branding) will be called here "relpolines" (relative call + trampoline). Relpolines are mainly intended as a way of reducing retpoline overheads due to Spectre v2.
Unlike indirect call promotion through profile guided optimization, the proposed approach does not require a profiling stage, works well with modules whose address is unknown and can adapt to changing workloads.
The main idea is simple: for every indirect call, we inject a piece of code with fast- and slow-path calls. The fast path is used if the target matches the expected (hot) target. The slow-path uses a retpoline. During training, the slow-path is set to call a function that saves the call source and target in a hash-table and keep count for call frequency. The most common target is then patched into the hot path.
The patching is done on-the-fly by patching the conditional branch (opcode and offset) that is used to compare the target to the hot target. This allows to direct all cores to the fast-path, while patching the slow-path and vice-versa. Patching follows 2 more rules: (1) Only patch a single byte when the code might be executed by any core. (2) When patching more than one byte, ensure that all cores do not run the to-be-patched-code by preventing this code from being preempted, and using
synchronize_sched()after patching the branch that jumps over this code.Changing all the indirect calls to use relpolines is done using assembly macro magic. There are alternative solutions, but this one is relatively simple and transparent. There is also logic to retrain the software predictor, but the policy it uses may need to be refined.
Eventually the results are not bad (2 VCPU VM, throughput reported):
base relpoline ---- --------- nginx 22898 25178 (+10%) redis-ycsb 24523 25486 (+4%) dbench 2144 2103 (+2%)When retpolines are disabled, and if retraining is off, performance benefits are up to 2% (nginx), but are much less impressive.
Quoted 7 lines
Quoted 5 lines
Hi Nadav,
Peter pointed me to these patches during a discussion about retpoline profiling. Personally, I think this is brilliant. This could help networking and filesystem intensive workloads a lot.
Thanks! I was a bit held-back by the relatively limited number of responses.
It is a rather, erm, ambitious idea, maybe they were speechless :-)
Quoted 7 lines
I finished another version two weeks ago, and every day I think: "should it be RFCv2 or v1”, ending up not sending it…
There is one issue that I realized while working on the new version: I’m not sure it is well-defined what an outline retpoline is allowed to do. The indirect branch promotion code can change rflags, which might cause correction issues. In practice, using gcc, it is not a problem.
Quoted 7 lines
Quoted 1 line
Callees can clobber flags, so it seems fine to me.
Just to check I understand your approach right: you made a macro called "call", and you're therefore causing all instances of "call" to become magic? This is... terrifying. It's even plausibly worse than "#define if" :) The scariest bit is that it will impact inline asm as well. Maybe a gcc plugin would be less alarming?
It is likely to look less alarming. When I looked at the inline retpoline implementation of gcc, it didn’t look much better than what I did - it basically just emits assembly instructions.
Quoted 7 lines
Quoted 4 lines
Quoted 1 line
To be clear, that wasn’t a NAK. It was merely a “this is alarming.”
Although... how do you avoid matching on things that really don't want this treatment? paravirt ops come to mind.
Paravirt ops don't use retpolines because they're patched into direct calls during boot. So Nadav's patches won't touch them.
Actually, the way it’s handled is slightly more complicated - yes, the CALL macro should not be applied, as Josh said, but the question is how it is achieved.
The basic idea is that the CALL macro should only be applied to C source-files and not to assembly files and for macros.s, which holds the PV call macros. I will recheck it is done this way.
Regards,
Nadav
Quoted 93 lines · Nadav Amit
Quoted 85 lines · Josh Poimboeuf
Quoted 82 lines · Andy Lutomirski
Quoted 81 lines · Andy Lutomirski
Quoted 78 lines · Nadav Amit
Quoted 67 lines · Andy Lutomirski
Quoted 66 lines · Josh Poimboeuf
Quoted 45 lines · Nadav Amit
Quoted 45 lines · Josh Poimboeuf
Quoted 43 lines · Nadav Amit
This RFC introduces indirect call promotion in runtime, which for the matter of simplification (and branding) will be called here "relpolines" (relative call + trampoline). Relpolines are mainly intended as a way of reducing retpoline overheads due to Spectre v2.
Unlike indirect call promotion through profile guided optimization, the proposed approach does not require a profiling stage, works well with modules whose address is unknown and can adapt to changing workloads.
The main idea is simple: for every indirect call, we inject a piece of code with fast- and slow-path calls. The fast path is used if the target matches the expected (hot) target. The slow-path uses a retpoline. During training, the slow-path is set to call a function that saves the call source and target in a hash-table and keep count for call frequency. The most common target is then patched into the hot path.
The patching is done on-the-fly by patching the conditional branch (opcode and offset) that is used to compare the target to the hot target. This allows to direct all cores to the fast-path, while patching the slow-path and vice-versa. Patching follows 2 more rules: (1) Only patch a single byte when the code might be executed by any core. (2) When patching more than one byte, ensure that all cores do not run the to-be-patched-code by preventing this code from being preempted, and using
synchronize_sched()after patching the branch that jumps over this code.Changing all the indirect calls to use relpolines is done using assembly macro magic. There are alternative solutions, but this one is relatively simple and transparent. There is also logic to retrain the software predictor, but the policy it uses may need to be refined.
Eventually the results are not bad (2 VCPU VM, throughput reported):
base relpoline ---- --------- nginx 22898 25178 (+10%) redis-ycsb 24523 25486 (+4%) dbench 2144 2103 (+2%)When retpolines are disabled, and if retraining is off, performance benefits are up to 2% (nginx), but are much less impressive.
Quoted 7 lines
Quoted 5 lines
Hi Nadav,
Peter pointed me to these patches during a discussion about retpoline profiling. Personally, I think this is brilliant. This could help networking and filesystem intensive workloads a lot.
Thanks! I was a bit held-back by the relatively limited number of responses.
It is a rather, erm, ambitious idea, maybe they were speechless :-)
Quoted 7 lines
I finished another version two weeks ago, and every day I think: "should it be RFCv2 or v1”, ending up not sending it…
There is one issue that I realized while working on the new version: I’m not sure it is well-defined what an outline retpoline is allowed to do. The indirect branch promotion code can change rflags, which might cause correction issues. In practice, using gcc, it is not a problem.
Quoted 7 lines
Quoted 1 line
Callees can clobber flags, so it seems fine to me.
Just to check I understand your approach right: you made a macro called "call", and you're therefore causing all instances of "call" to become magic? This is... terrifying. It's even plausibly worse than "#define if" :) The scariest bit is that it will impact inline asm as well. Maybe a gcc plugin would be less alarming?
It is likely to look less alarming. When I looked at the inline retpoline implementation of gcc, it didn’t look much better than what I did - it basically just emits assembly instructions.
Quoted 7 lines
Quoted 4 lines
Quoted 1 line
To be clear, that wasn’t a NAK. It was merely a “this is alarming.”
Although... how do you avoid matching on things that really don't want this treatment? paravirt ops come to mind.
Paravirt ops don't use retpolines because they're patched into direct calls during boot. So Nadav's patches won't touch them.
Quoted 7 lines
Actually, the way it’s handled is slightly more complicated - yes, the CALL macro should not be applied, as Josh said, but the question is how it is achieved.
The basic idea is that the CALL macro should only be applied to C source-files and not to assembly files and for macros.s, which holds the PV call macros. I will recheck it is done this way.
Even if the CALL macro were applied, it would get ignored by your code because the PARAVIRT_CALL macro doesn't use retpolines. So it would get skipped by this check:
.ifc "\v", "__x86_indirect_thunk_\reg_it"
relpoline_call reg=\reg_it
retpoline = 1
.endif
Signature · 1 line
Josh