BPF List
 help / color / mirror / Atom feed
From: "Alexei Starovoitov" <alexei.starovoitov@gmail.com>
To: "Daniel Borkmann" <daniel@iogearbox.net>
Cc: <puranjay@kernel.org>, <memxor@gmail.com>,
	<xukuohai@huaweicloud.com>, <bpf@vger.kernel.org>
Subject: Re: [PATCH bpf-next v2 17/20] bpf, arm64: Use cbz, cbnz, tbz and tbnz for zero, sign and bit tests
Date: Tue, 03 Nov 2026 17:51:22 +0000	[thread overview]
Message-ID: <DMLRLX4USTMF.18P3O2OCL0BRM@gmail.com> (raw)
In-Reply-To: <20261103122733.1383002-18-daniel@iogearbox.net>

On Tue, Nov 03, 2026 at 04:27 AM Daniel Borkmann <daniel@iogearbox.net> wrote:
> pass which writes the image. The extra pass of a program with
> subprograms, whose calls turn direct, starts from the offsets of the
> first image, which bound no later layout: a call of a helper can leave
> the reach of the direct call as the code in front of it shrinks and
> take the indirect form again, three instructions more between a test
> ahead of it and its target. So the extra pass keeps the decisions for
> the tests ahead and takes only those for the tests back again, which can
> leave the image a word or two larger than the first one, within the
> maximum image size the sizing pass counted, which the check after the
> extra pass takes as its bound.

Another idea...
can we decide every test from the offsets of pass 1 ?
Pass 1 has cmp + b.cond for every test and the indirect form for every
call. It's the longest layout, ahead and back.
No later pass makes a distance longer.

  pass             this patch              decide after pass 1
  ---------------  ----------------------  -------------------
  1, sizing        -                       sets the bitmap
  2, no write      sets the bitmap         reads the bitmap
  3, write         reads the bitmap        reads the bitmap
  extra, no write  sets it for tests back  reads the bitmap
  extra, write     reads the bitmap        reads the bitmap

The bitmap doesn't change after pass 1. A test doesn't grow the image in
the extra pass.
Then no need for the extra_pass argument, max_idx and the new bound in
the "multi-func JIT bug" check.

The cost: a test back that gets in reach only after other code shrinks
keeps cmp + b.cond. A test ahead does that already.

find_tb() is new and emit_tb() only reads the bitmap. The rest is yours.
It doesn't block the patch. It can be a follow up.

Something like this (not even compile tested):

diff --git a/arch/arm64/include/asm/insn.h b/arch/arm64/include/asm/insn.h
index f38738f..ca23f3c 100644
--- a/arch/arm64/include/asm/insn.h
+++ b/arch/arm64/include/asm/insn.h
@@ -170,6 +170,8 @@ enum aarch64_insn_branch_type {
 	AARCH64_INSN_BRANCH_RETURN,
 	AARCH64_INSN_BRANCH_COMP_ZERO,
 	AARCH64_INSN_BRANCH_COMP_NONZERO,
+	AARCH64_INSN_BRANCH_TEST_ZERO,
+	AARCH64_INSN_BRANCH_TEST_NONZERO,
 };

 enum aarch64_insn_size_type {
@@ -586,6 +588,10 @@ u32 aarch64_insn_gen_comp_branch_imm(unsigned long pc, unsigned long addr,
 				     enum aarch64_insn_register reg,
 				     enum aarch64_insn_variant variant,
 				     enum aarch64_insn_branch_type type);
+u32 aarch64_insn_gen_test_branch_imm(unsigned long pc, unsigned long addr,
+				     enum aarch64_insn_register reg,
+				     int bit,
+				     enum aarch64_insn_branch_type type);
 u32 aarch64_insn_gen_cond_branch_imm(unsigned long pc, unsigned long addr,
 				     enum aarch64_insn_condition cond);

diff --git a/arch/arm64/lib/insn.c b/arch/arm64/lib/insn.c
index dc483d1..9c2fe1a 100644
--- a/arch/arm64/lib/insn.c
+++ b/arch/arm64/lib/insn.c
@@ -331,6 +331,48 @@ u32 aarch64_insn_gen_comp_branch_imm(unsigned long pc, unsigned long addr,
 					     offset >> 2);
 }

+/*
+ * TBZ and TBNZ test @bit of @reg, 0 to 63 with the 64-bit register implied
+ * from bit 32 on, and reach [-32K, 32K) bytes.
+ */
+u32 aarch64_insn_gen_test_branch_imm(unsigned long pc, unsigned long addr,
+				     enum aarch64_insn_register reg,
+				     int bit,
+				     enum aarch64_insn_branch_type type)
+{
+	u32 insn;
+	long offset;
+
+	offset = label_imm_common(pc, addr, SZ_32K);
+	if (offset >= SZ_32K)
+		return AARCH64_BREAK_FAULT;
+
+	switch (type) {
+	case AARCH64_INSN_BRANCH_TEST_ZERO:
+		insn = aarch64_insn_get_tbz_value();
+		break;
+	case AARCH64_INSN_BRANCH_TEST_NONZERO:
+		insn = aarch64_insn_get_tbnz_value();
+		break;
+	default:
+		pr_err("%s: unknown branch encoding %d\n", __func__, type);
+		return AARCH64_BREAK_FAULT;
+	}
+
+	if (bit < 0 || bit > 63) {
+		pr_err("%s: invalid bit %d\n", __func__, bit);
+		return AARCH64_BREAK_FAULT;
+	}
+	/* b5 in bit 31 and b40 in bits [23:19] hold the bit number */
+	insn |= (u32)(bit >> 5) << 31;
+	insn |= (bit & 0x1f) << 19;
+
+	insn = aarch64_insn_encode_register(AARCH64_INSN_REGTYPE_RT, insn, reg);
+
+	return aarch64_insn_encode_immediate(AARCH64_INSN_IMM_14, insn,
+					     offset >> 2);
+}
+
 u32 aarch64_insn_gen_cond_branch_imm(unsigned long pc, unsigned long addr,
 				     enum aarch64_insn_condition cond)
 {
diff --git a/arch/arm64/net/bpf_jit.h b/arch/arm64/net/bpf_jit.h
index 70fc329..a8b43eb 100644
--- a/arch/arm64/net/bpf_jit.h
+++ b/arch/arm64/net/bpf_jit.h
@@ -26,6 +26,13 @@
 #define A64_CBZ(sf, Rt, imm19) A64_COMP_BRANCH(sf, Rt, (imm19) << 2, ZERO)
 #define A64_CBNZ(sf, Rt, imm19) A64_COMP_BRANCH(sf, Rt, (imm19) << 2, NONZERO)

+/* Test bit & branch (immediate) */
+#define A64_TEST_BRANCH(Rt, bit, offset, type) \
+	aarch64_insn_gen_test_branch_imm(0, offset, Rt, bit, \
+		AARCH64_INSN_BRANCH_TEST_##type)
+#define A64_TBZ(Rt, bit, imm14) A64_TEST_BRANCH(Rt, bit, (imm14) << 2, ZERO)
+#define A64_TBNZ(Rt, bit, imm14) A64_TEST_BRANCH(Rt, bit, (imm14) << 2, NONZERO)
+
 /* Conditional branch (immediate) */
 #define A64_COND_BRANCH(cond, offset) \
 	aarch64_insn_gen_cond_branch_imm(0, offset, cond)
diff --git a/arch/arm64/net/bpf_jit_comp.c b/arch/arm64/net/bpf_jit_comp.c
index e99f105..3613e21 100644
--- a/arch/arm64/net/bpf_jit_comp.c
+++ b/arch/arm64/net/bpf_jit_comp.c
@@ -92,6 +92,7 @@ struct jit_ctx {
 	bool fp_used;
 	bool priv_sp_used;
 	bool write;
+	unsigned long *tb;
 };

 struct bpf_plt {
@@ -268,8 +269,12 @@ static inline int bpf2a64_offset(int bpf_insn, int off,
 	/*
 	 * Whereas arm64 branch instructions encode the offset
 	 * from the branch itself, so we must subtract 1 from the
-	 * instruction offset.
+	 * instruction offset. A branch back takes its own position,
+	 * the next one emitted, as the offsets from the next
+	 * instruction on are still those of the previous pass.
 	 */
+	if (off < 0)
+		return ctx->offset[bpf_insn + off] - ctx->idx;
 	return ctx->offset[bpf_insn + off] - (ctx->offset[bpf_insn] - 1);
 }

@@ -357,6 +362,98 @@ static bool is_lsi_offset(int offset, int scale)
 	return true;
 }

+/*
+ * A compare against zero or minus one for the sign, an unsigned 32-bit
+ * compare at the sign bit and a test of a single bit test a bit of the
+ * register: returns the bit, with *nonzero set when the branch is taken
+ * with it set, or -1.
+ */
+static int tb_bit(u8 op, bool is64, s32 imm, bool *nonzero)
+{
+	const int sign = is64 ? 63 : 31;
+	u64 mask;
+
+	switch (op) {
+	case BPF_JSLT:
+	case BPF_JSGE:
+		/* x < 0, x >= 0 */
+		if (imm)
+			return -1;
+		*nonzero = op == BPF_JSLT;
+		return sign;
+	case BPF_JSGT:
+	case BPF_JSLE:
+		/* x > -1, x <= -1 */
+		if (imm != -1)
+			return -1;
+		*nonzero = op == BPF_JSLE;
+		return sign;
+	case BPF_JGT:
+	case BPF_JLE:
+		/* x > 0x7fffffff, x <= 0x7fffffff in 32 bits */
+		if (is64 || imm != 0x7fffffff)
+			return -1;
+		*nonzero = op == BPF_JGT;
+		return 31;
+	case BPF_JGE:
+	case BPF_JLT:
+		/* x >= 0x80000000, x < 0x80000000 in 32 bits */
+		if (is64 || imm != INT_MIN)
+			return -1;
+		*nonzero = op == BPF_JGE;
+		return 31;
+	case BPF_JSET:
+		mask = is64 ? (u64)(s64)imm : (u32)imm;
+		if (!is_power_of_2(mask))
+			return -1;
+		*nonzero = true;
+		return ilog2(mask);
+	default:
+		return -1;
+	}
+}
+
+/*
+ * The test bit and branch reaches 8K instructions either way. Pass 1 takes
+ * the compare and the conditional branch for every test and the indirect form
+ * for every call. No later pass makes a distance longer, the extra pass
+ * included. A test that fits in the layout of pass 1 fits in every image.
+ */
+static void find_tb(struct jit_ctx *ctx)
+{
+	const struct bpf_prog *prog = ctx->prog;
+	bool nonzero;
+	int i, d;
+
+	for (i = 0; i < prog->len; i++) {
+		const struct bpf_insn *insn = &prog->insnsi[i];
+		const u8 class = BPF_CLASS(insn->code);
+
+		if ((class != BPF_JMP && class != BPF_JMP32) ||
+		    BPF_SRC(insn->code) != BPF_K)
+			continue;
+		if (tb_bit(BPF_OP(insn->code), class == BPF_JMP, insn->imm, &nonzero) < 0)
+			continue;
+		d = ctx->offset[i + 1 + insn->off] - (ctx->offset[i + 1] - 1);
+		if (d >= -SZ_8K && d < SZ_8K)
+			__set_bit(i, ctx->tb);
+	}
+}
+
+static bool emit_tb(bool nonzero, u8 reg, int bit, int i, s16 off, struct jit_ctx *ctx)
+{
+	int jmp_offset;
+
+	if (!test_bit(i, ctx->tb))
+		return false;
+	jmp_offset = bpf2a64_offset(i, off, ctx);
+	if (nonzero)
+		emit(A64_TBNZ(reg, bit, jmp_offset), ctx);
+	else
+		emit(A64_TBZ(reg, bit, jmp_offset), ctx);
+	return true;
+}
+
 /* generated main prog prologue:
  *      bti c // if CONFIG_ARM64_BTI_KERNEL
  *      mov x9, lr
@@ -1465,6 +1562,8 @@ static int build_insn(const struct bpf_verifier_env *env, const struct bpf_insn
 	int off_adj;
 	int ret;
 	bool sign_extend;
+	bool nonzero;
+	int bit;

 	if (bpf_insn_is_indirect_target(env, ctx->prog, i))
 		emit_bti(A64_BTI_J, ctx);
@@ -1799,6 +1898,20 @@ emit_cond_jmp:
 	case BPF_JMP32 | BPF_JSLT | BPF_K:
 	case BPF_JMP32 | BPF_JSGE | BPF_K:
 	case BPF_JMP32 | BPF_JSLE | BPF_K:
+		/* A test of the sign within reach is a test bit and branch */
+		bit = tb_bit(BPF_OP(code), is64, imm, &nonzero);
+		if (bit >= 0 && emit_tb(nonzero, dst, bit, i, off, ctx))
+			break;
+		/* A compare against zero for equality is a compare and branch */
+		if (!imm && (BPF_OP(code) == BPF_JEQ || BPF_OP(code) == BPF_JNE)) {
+			jmp_offset = bpf2a64_offset(i, off, ctx);
+			check_imm19(jmp_offset);
+			if (BPF_OP(code) == BPF_JEQ)
+				emit(A64_CBZ(is64, dst, jmp_offset), ctx);
+			else
+				emit(A64_CBNZ(is64, dst, jmp_offset), ctx);
+			break;
+		}
 		if (is_addsub_imm(imm)) {
 			emit(A64_CMP_I(is64, dst, imm), ctx);
 		} else if (is_addsub_imm(-(u32)imm)) {
@@ -1810,6 +1923,10 @@ emit_cond_jmp:
 		goto emit_cond_jmp;
 	case BPF_JMP | BPF_JSET | BPF_K:
 	case BPF_JMP32 | BPF_JSET | BPF_K:
+		/* A test of a single bit within reach is a test bit and branch */
+		bit = tb_bit(BPF_OP(code), is64, imm, &nonzero);
+		if (bit >= 0 && emit_tb(nonzero, dst, bit, i, off, ctx))
+			break;
 		a64_insn = A64_TST_I(is64, dst, imm);
 		if (a64_insn != AARCH64_BREAK_FAULT) {
 			emit(a64_insn, ctx);
@@ -2375,6 +2492,9 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_pr
 	ctx.offset = kvzalloc_objs(int, prog->len + 1);
 	if (ctx.offset == NULL)
 		goto out_off;
+	ctx.tb = kvzalloc_objs(unsigned long, BITS_TO_LONGS(prog->len));
+	if (ctx.tb == NULL)
+		goto out_off;

 	if (bpf_prog_uses_arena_insns(prog)) {
 		ctx.user_vm_start = bpf_arena_get_user_vm_start(prog->aux->arena);
@@ -2402,6 +2522,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_pr

 	if (build_body(env, &ctx, extra_pass))
 		goto out_off;
+	find_tb(&ctx);

 	ctx.epilogue_offset = ctx.idx;
 	build_epilogue(&ctx, was_classic, false);
@@ -2522,6 +2643,7 @@ out_off:
 			prog->aux->priv_stack_ptr = NULL;
 		}
 		kvfree(ctx.offset);
+		kvfree(ctx.tb);
 out_priv_stack:
 		kfree(jit_data);
 		prog->aux->jit_data = NULL;
@@ -3580,6 +3702,7 @@ void bpf_jit_free(struct bpf_prog *prog)
 		 */
 		if (jit_data) {
 			bpf_jit_binary_pack_finalize(jit_data->ro_header, jit_data->header);
+			kvfree(jit_data->ctx.tb);
 			kfree(jit_data);
 		}
 		prog->bpf_func = (void *)prog->bpf_func - cfi_get_offset();

  reply	other threads:[~2026-11-03 17:51 UTC|newest]

Thread overview: 32+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-03 12:27 [PATCH bpf-next v2 00/20] bpf: Various arm64 BPF JIT improvements Daniel Borkmann
2026-11-03 12:27 ` [PATCH bpf-next v2 01/20] bpf, arm64: Free the offset table of a subprog in bpf_jit_free Daniel Borkmann
2026-11-03 12:27 ` [PATCH bpf-next v2 02/20] bpf, arm64: Free the private stack when a pass over the image fails Daniel Borkmann
2026-11-03 12:27 ` [PATCH bpf-next v2 03/20] bpf, arm64: Emit ISB SY in the speculation barriers Daniel Borkmann
2026-11-03 12:27 ` [PATCH bpf-next v2 04/20] arm64, insn: Reject sign extending loads of 64 bits Daniel Borkmann
2026-11-03 12:27 ` [PATCH bpf-next v2 05/20] bpf, arm64: Keep the ll/sc atomics address apart from the old value Daniel Borkmann
2026-11-03 12:27 ` [PATCH bpf-next v2 06/20] arm64, insn: Add the LDUR/STUR and TBZ/TBNZ encodings Daniel Borkmann
2026-11-03 17:46   ` Alexei Starovoitov
2026-11-03 12:27 ` [PATCH bpf-next v2 07/20] bpf: Move the division magic number selection out of the x86 JIT Daniel Borkmann
2026-11-03 13:06   ` bot+bpf-ci
2026-11-03 13:16     ` Daniel Borkmann
2026-11-03 12:27 ` [PATCH bpf-next v2 08/20] bpf, arm64: Consolidate the load and store emission into a helper Daniel Borkmann
2026-11-03 17:45   ` Alexei Starovoitov
2026-11-03 12:27 ` [PATCH bpf-next v2 09/20] bpf, arm64: Use the unscaled forms for negative and unaligned offsets Daniel Borkmann
2026-11-03 12:27 ` [PATCH bpf-next v2 10/20] bpf, arm64: Store zero immediates from the zero register Daniel Borkmann
2026-11-03 12:27 ` [PATCH bpf-next v2 11/20] bpf, arm64: Zero extend the arena offset in register offset accesses Daniel Borkmann
2026-11-03 12:27 ` [PATCH bpf-next v2 12/20] bpf, arm64: Adjust compares with unencodable immediates by one Daniel Borkmann
2026-11-03 13:06   ` bot+bpf-ci
2026-11-03 12:27 ` [PATCH bpf-next v2 13/20] bpf, arm64: Materialize immediates with fewer instructions Daniel Borkmann
2026-11-03 12:27 ` [PATCH bpf-next v2 14/20] bpf, arm64: Shorten the tail call sequence Daniel Borkmann
2026-11-03 12:36   ` sashiko-bot
2026-11-03 12:57     ` Daniel Borkmann
2026-11-03 12:27 ` [PATCH bpf-next v2 15/20] bpf, arm64: Divide by constants via multiplication with a magic number Daniel Borkmann
2026-11-03 17:49   ` Alexei Starovoitov
2026-11-03 12:27 ` [PATCH bpf-next v2 16/20] bpf, arm64: Use a move or mvn for the and and xor with all ones Daniel Borkmann
2026-11-03 12:27 ` [PATCH bpf-next v2 17/20] bpf, arm64: Use cbz, cbnz, tbz and tbnz for zero, sign and bit tests Daniel Borkmann
2026-11-03 17:51   ` Alexei Starovoitov [this message]
2026-11-03 12:27 ` [PATCH bpf-next v2 18/20] bpf, arm64: Multiply by constants with shifts, adds and subs Daniel Borkmann
2026-11-03 12:27 ` [PATCH bpf-next v2 19/20] selftests/bpf: Let the arm64 private stack check skip the BTI pad Daniel Borkmann
2026-11-03 12:27 ` [PATCH bpf-next v2 20/20] selftests/bpf: Extend the JIT instruction tests for arm64 encodings Daniel Borkmann
2026-11-03 13:06   ` bot+bpf-ci
2026-11-03 13:15     ` Daniel Borkmann

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=DMLRLX4USTMF.18P3O2OCL0BRM@gmail.com \
    --to=alexei.starovoitov@gmail.com \
    --cc=bpf@vger.kernel.org \
    --cc=daniel@iogearbox.net \
    --cc=memxor@gmail.com \
    --cc=puranjay@kernel.org \
    --cc=xukuohai@huaweicloud.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox