Skip to content

Commit 77eea55

Browse files
Merge patch series "bpf, riscv: use BPF prog pack allocator in BPF JIT"
Puranjay Mohan <puranjay12@gmail.com> says: Here is some data to prove the V2 fixes the problem: Without this series: root@rv-selftester:~/src/kselftest/bpf# time ./test_tag test_tag: OK (40945 tests) real 7m47.562s user 0m24.145s sys 6m37.064s With this series applied: root@rv-selftester:~/src/selftest/bpf# time ./test_tag test_tag: OK (40945 tests) real 7m29.472s user 0m25.865s sys 6m18.401s BPF programs currently consume a page each on RISCV. For systems with many BPF programs, this adds significant pressure to instruction TLB. High iTLB pressure usually causes slow down for the whole system. Song Liu introduced the BPF prog pack allocator[1] to mitigate the above issue. It packs multiple BPF programs into a single huge page. It is currently only enabled for the x86_64 BPF JIT. I enabled this allocator on the ARM64 BPF JIT[2]. It is being reviewed now. This patch series enables the BPF prog pack allocator for the RISCV BPF JIT. ====================================================== Performance Analysis of prog pack allocator on RISCV64 ====================================================== Test setup: =========== Host machine: Debian GNU/Linux 11 (bullseye) Qemu Version: QEMU emulator version 8.0.3 (Debian 1:8.0.3+dfsg-1) u-boot-qemu Version: 2023.07+dfsg-1 opensbi Version: 1.3-1 To test the performance of the BPF prog pack allocator on RV, a stresser tool[4] linked below was built. This tool loads 8 BPF programs on the system and triggers 5 of them in an infinite loop by doing system calls. The runner script starts 20 instances of the above which loads 8*20=160 BPF programs on the system, 5*20=100 of which are being constantly triggered. The script is passed a command which would be run in the above environment. The script was run with following perf command: ./run.sh "perf stat -a \ -e iTLB-load-misses \ -e dTLB-load-misses \ -e dTLB-store-misses \ -e instructions \ --timeout 60000" The output of the above command is discussed below before and after enabling the BPF prog pack allocator. The tests were run on qemu-system-riscv64 with 8 cpus, 16G memory. The rootfs was created using Bjorn's riscv-cross-builder[5] docker container linked below. Results ======= Before enabling prog pack allocator: ------------------------------------ Performance counter stats for 'system wide': 4939048 iTLB-load-misses 5468689 dTLB-load-misses 465234 dTLB-store-misses 1441082097998 instructions 60.045791200 seconds time elapsed After enabling prog pack allocator: ----------------------------------- Performance counter stats for 'system wide': 3430035 iTLB-load-misses 5008745 dTLB-load-misses 409944 dTLB-store-misses 1441535637988 instructions 60.046296600 seconds time elapsed Improvements in metrics ======================= It was expected that the iTLB-load-misses would decrease as now a single huge page is used to keep all the BPF programs compared to a single page for each program earlier. -------------------------------------------- The improvement in iTLB-load-misses: -30.5 % -------------------------------------------- I repeated this expriment more than 100 times in different setups and the improvement was always greater than 30%. This patch series is boot tested on the Starfive VisionFive 2 board[6]. The performance analysis was not done on the board because it doesn't expose iTLB-load-misses, etc. The stresser program was run on the board to test the loading and unloading of BPF programs [1] https://lore.kernel.org/bpf/20220204185742.271030-1-song@kernel.org/ [2] https://lore.kernel.org/all/20230626085811.3192402-1-puranjay12@gmail.com/ [3] https://lore.kernel.org/all/20230626085811.3192402-2-puranjay12@gmail.com/ [4] https://github.com/puranjaymohan/BPF-Allocator-Bench [5] https://github.com/bjoto/riscv-cross-builder [6] https://www.starfivetech.com/en/site/boards * b4-shazam-merge: bpf, riscv: use prog pack allocator in the BPF JIT riscv: implement a memset like function for text riscv: extend patch_text_nosync() for multiple pages bpf: make bpf_prog_pack allocator portable Link: https://lore.kernel.org/r/20230831131229.497941-1-puranjay12@gmail.com Signed-off-by: Palmer Dabbelt <palmer@rivosinc.com>
2 parents f578055 + 48a8f78 commit 77eea55

File tree

6 files changed

+255
-37
lines changed

6 files changed

+255
-37
lines changed

arch/riscv/include/asm/patch.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#define _ASM_RISCV_PATCH_H
88

99
int patch_text_nosync(void *addr, const void *insns, size_t len);
10+
int patch_text_set_nosync(void *addr, u8 c, size_t len);
1011
int patch_text(void *addr, u32 *insns, int ninsns);
1112

1213
extern int riscv_patch_in_stop_machine;

arch/riscv/kernel/patch.c

Lines changed: 109 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <linux/spinlock.h>
77
#include <linux/mm.h>
88
#include <linux/memory.h>
9+
#include <linux/string.h>
910
#include <linux/uaccess.h>
1011
#include <linux/stop_machine.h>
1112
#include <asm/kprobes.h>
@@ -53,12 +54,51 @@ static void patch_unmap(int fixmap)
5354
}
5455
NOKPROBE_SYMBOL(patch_unmap);
5556

56-
static int patch_insn_write(void *addr, const void *insn, size_t len)
57+
static int __patch_insn_set(void *addr, u8 c, size_t len)
58+
{
59+
void *waddr = addr;
60+
bool across_pages = (((uintptr_t)addr & ~PAGE_MASK) + len) > PAGE_SIZE;
61+
62+
/*
63+
* Only two pages can be mapped at a time for writing.
64+
*/
65+
if (len + offset_in_page(addr) > 2 * PAGE_SIZE)
66+
return -EINVAL;
67+
/*
68+
* Before reaching here, it was expected to lock the text_mutex
69+
* already, so we don't need to give another lock here and could
70+
* ensure that it was safe between each cores.
71+
*/
72+
lockdep_assert_held(&text_mutex);
73+
74+
if (across_pages)
75+
patch_map(addr + PAGE_SIZE, FIX_TEXT_POKE1);
76+
77+
waddr = patch_map(addr, FIX_TEXT_POKE0);
78+
79+
memset(waddr, c, len);
80+
81+
patch_unmap(FIX_TEXT_POKE0);
82+
83+
if (across_pages)
84+
patch_unmap(FIX_TEXT_POKE1);
85+
86+
return 0;
87+
}
88+
NOKPROBE_SYMBOL(__patch_insn_set);
89+
90+
static int __patch_insn_write(void *addr, const void *insn, size_t len)
5791
{
5892
void *waddr = addr;
5993
bool across_pages = (((uintptr_t) addr & ~PAGE_MASK) + len) > PAGE_SIZE;
6094
int ret;
6195

96+
/*
97+
* Only two pages can be mapped at a time for writing.
98+
*/
99+
if (len + offset_in_page(addr) > 2 * PAGE_SIZE)
100+
return -EINVAL;
101+
62102
/*
63103
* Before reaching here, it was expected to lock the text_mutex
64104
* already, so we don't need to give another lock here and could
@@ -74,7 +114,7 @@ static int patch_insn_write(void *addr, const void *insn, size_t len)
74114
lockdep_assert_held(&text_mutex);
75115

76116
if (across_pages)
77-
patch_map(addr + len, FIX_TEXT_POKE1);
117+
patch_map(addr + PAGE_SIZE, FIX_TEXT_POKE1);
78118

79119
waddr = patch_map(addr, FIX_TEXT_POKE0);
80120

@@ -87,15 +127,79 @@ static int patch_insn_write(void *addr, const void *insn, size_t len)
87127

88128
return ret;
89129
}
90-
NOKPROBE_SYMBOL(patch_insn_write);
130+
NOKPROBE_SYMBOL(__patch_insn_write);
91131
#else
92-
static int patch_insn_write(void *addr, const void *insn, size_t len)
132+
static int __patch_insn_set(void *addr, u8 c, size_t len)
133+
{
134+
memset(addr, c, len);
135+
136+
return 0;
137+
}
138+
NOKPROBE_SYMBOL(__patch_insn_set);
139+
140+
static int __patch_insn_write(void *addr, const void *insn, size_t len)
93141
{
94142
return copy_to_kernel_nofault(addr, insn, len);
95143
}
96-
NOKPROBE_SYMBOL(patch_insn_write);
144+
NOKPROBE_SYMBOL(__patch_insn_write);
97145
#endif /* CONFIG_MMU */
98146

147+
static int patch_insn_set(void *addr, u8 c, size_t len)
148+
{
149+
size_t patched = 0;
150+
size_t size;
151+
int ret = 0;
152+
153+
/*
154+
* __patch_insn_set() can only work on 2 pages at a time so call it in a
155+
* loop with len <= 2 * PAGE_SIZE.
156+
*/
157+
while (patched < len && !ret) {
158+
size = min_t(size_t, PAGE_SIZE * 2 - offset_in_page(addr + patched), len - patched);
159+
ret = __patch_insn_set(addr + patched, c, size);
160+
161+
patched += size;
162+
}
163+
164+
return ret;
165+
}
166+
NOKPROBE_SYMBOL(patch_insn_set);
167+
168+
int patch_text_set_nosync(void *addr, u8 c, size_t len)
169+
{
170+
u32 *tp = addr;
171+
int ret;
172+
173+
ret = patch_insn_set(tp, c, len);
174+
175+
if (!ret)
176+
flush_icache_range((uintptr_t)tp, (uintptr_t)tp + len);
177+
178+
return ret;
179+
}
180+
NOKPROBE_SYMBOL(patch_text_set_nosync);
181+
182+
static int patch_insn_write(void *addr, const void *insn, size_t len)
183+
{
184+
size_t patched = 0;
185+
size_t size;
186+
int ret = 0;
187+
188+
/*
189+
* Copy the instructions to the destination address, two pages at a time
190+
* because __patch_insn_write() can only handle len <= 2 * PAGE_SIZE.
191+
*/
192+
while (patched < len && !ret) {
193+
size = min_t(size_t, PAGE_SIZE * 2 - offset_in_page(addr + patched), len - patched);
194+
ret = __patch_insn_write(addr + patched, insn + patched, size);
195+
196+
patched += size;
197+
}
198+
199+
return ret;
200+
}
201+
NOKPROBE_SYMBOL(patch_insn_write);
202+
99203
int patch_text_nosync(void *addr, const void *insns, size_t len)
100204
{
101205
u32 *tp = addr;

arch/riscv/net/bpf_jit.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ static inline bool is_creg(u8 reg)
6868
struct rv_jit_context {
6969
struct bpf_prog *prog;
7070
u16 *insns; /* RV insns */
71+
u16 *ro_insns;
7172
int ninsns;
7273
int prologue_len;
7374
int epilogue_offset;
@@ -85,7 +86,9 @@ static inline int ninsns_rvoff(int ninsns)
8586

8687
struct rv_jit_data {
8788
struct bpf_binary_header *header;
89+
struct bpf_binary_header *ro_header;
8890
u8 *image;
91+
u8 *ro_image;
8992
struct rv_jit_context ctx;
9093
};
9194

arch/riscv/net/bpf_jit_comp64.c

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,11 @@ static bool in_auipc_jalr_range(s64 val)
144144
/* Emit fixed-length instructions for address */
145145
static int emit_addr(u8 rd, u64 addr, bool extra_pass, struct rv_jit_context *ctx)
146146
{
147-
u64 ip = (u64)(ctx->insns + ctx->ninsns);
147+
/*
148+
* Use the ro_insns(RX) to calculate the offset as the BPF program will
149+
* finally run from this memory region.
150+
*/
151+
u64 ip = (u64)(ctx->ro_insns + ctx->ninsns);
148152
s64 off = addr - ip;
149153
s64 upper = (off + (1 << 11)) >> 12;
150154
s64 lower = off & 0xfff;
@@ -464,8 +468,12 @@ static int emit_call(u64 addr, bool fixed_addr, struct rv_jit_context *ctx)
464468
s64 off = 0;
465469
u64 ip;
466470

467-
if (addr && ctx->insns) {
468-
ip = (u64)(long)(ctx->insns + ctx->ninsns);
471+
if (addr && ctx->insns && ctx->ro_insns) {
472+
/*
473+
* Use the ro_insns(RX) to calculate the offset as the BPF
474+
* program will finally run from this memory region.
475+
*/
476+
ip = (u64)(long)(ctx->ro_insns + ctx->ninsns);
469477
off = addr - ip;
470478
}
471479

@@ -578,9 +586,10 @@ static int add_exception_handler(const struct bpf_insn *insn,
578586
{
579587
struct exception_table_entry *ex;
580588
unsigned long pc;
581-
off_t offset;
589+
off_t ins_offset;
590+
off_t fixup_offset;
582591

583-
if (!ctx->insns || !ctx->prog->aux->extable ||
592+
if (!ctx->insns || !ctx->ro_insns || !ctx->prog->aux->extable ||
584593
(BPF_MODE(insn->code) != BPF_PROBE_MEM && BPF_MODE(insn->code) != BPF_PROBE_MEMSX))
585594
return 0;
586595

@@ -594,12 +603,17 @@ static int add_exception_handler(const struct bpf_insn *insn,
594603
return -EINVAL;
595604

596605
ex = &ctx->prog->aux->extable[ctx->nexentries];
597-
pc = (unsigned long)&ctx->insns[ctx->ninsns - insn_len];
606+
pc = (unsigned long)&ctx->ro_insns[ctx->ninsns - insn_len];
598607

599-
offset = pc - (long)&ex->insn;
600-
if (WARN_ON_ONCE(offset >= 0 || offset < INT_MIN))
608+
/*
609+
* This is the relative offset of the instruction that may fault from
610+
* the exception table itself. This will be written to the exception
611+
* table and if this instruction faults, the destination register will
612+
* be set to '0' and the execution will jump to the next instruction.
613+
*/
614+
ins_offset = pc - (long)&ex->insn;
615+
if (WARN_ON_ONCE(ins_offset >= 0 || ins_offset < INT_MIN))
601616
return -ERANGE;
602-
ex->insn = offset;
603617

604618
/*
605619
* Since the extable follows the program, the fixup offset is always
@@ -608,12 +622,25 @@ static int add_exception_handler(const struct bpf_insn *insn,
608622
* bits. We don't need to worry about buildtime or runtime sort
609623
* modifying the upper bits because the table is already sorted, and
610624
* isn't part of the main exception table.
625+
*
626+
* The fixup_offset is set to the next instruction from the instruction
627+
* that may fault. The execution will jump to this after handling the
628+
* fault.
611629
*/
612-
offset = (long)&ex->fixup - (pc + insn_len * sizeof(u16));
613-
if (!FIELD_FIT(BPF_FIXUP_OFFSET_MASK, offset))
630+
fixup_offset = (long)&ex->fixup - (pc + insn_len * sizeof(u16));
631+
if (!FIELD_FIT(BPF_FIXUP_OFFSET_MASK, fixup_offset))
614632
return -ERANGE;
615633

616-
ex->fixup = FIELD_PREP(BPF_FIXUP_OFFSET_MASK, offset) |
634+
/*
635+
* The offsets above have been calculated using the RO buffer but we
636+
* need to use the R/W buffer for writes.
637+
* switch ex to rw buffer for writing.
638+
*/
639+
ex = (void *)ctx->insns + ((void *)ex - (void *)ctx->ro_insns);
640+
641+
ex->insn = ins_offset;
642+
643+
ex->fixup = FIELD_PREP(BPF_FIXUP_OFFSET_MASK, fixup_offset) |
617644
FIELD_PREP(BPF_FIXUP_REG_MASK, dst_reg);
618645
ex->type = EX_TYPE_BPF;
619646

@@ -1007,6 +1034,7 @@ int arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *image,
10071034

10081035
ctx.ninsns = 0;
10091036
ctx.insns = NULL;
1037+
ctx.ro_insns = NULL;
10101038
ret = __arch_prepare_bpf_trampoline(im, m, tlinks, func_addr, flags, &ctx);
10111039
if (ret < 0)
10121040
return ret;
@@ -1015,7 +1043,15 @@ int arch_prepare_bpf_trampoline(struct bpf_tramp_image *im, void *image,
10151043
return -EFBIG;
10161044

10171045
ctx.ninsns = 0;
1046+
/*
1047+
* The bpf_int_jit_compile() uses a RW buffer (ctx.insns) to write the
1048+
* JITed instructions and later copies it to a RX region (ctx.ro_insns).
1049+
* It also uses ctx.ro_insns to calculate offsets for jumps etc. As the
1050+
* trampoline image uses the same memory area for writing and execution,
1051+
* both ctx.insns and ctx.ro_insns can be set to image.
1052+
*/
10181053
ctx.insns = image;
1054+
ctx.ro_insns = image;
10191055
ret = __arch_prepare_bpf_trampoline(im, m, tlinks, func_addr, flags, &ctx);
10201056
if (ret < 0)
10211057
return ret;

0 commit comments

Comments
 (0)