diff mbox series

[RFC,bpf-next,04/14] bpf: add support for an extended JA instruction

Message ID 20250318143318.656785-5-aspsk@isovalent.com (mailing list archive)
State RFC
Delegated to: BPF
Headers show
Series instruction sets and static keys | expand

Checks

Context Check Description
bpf/vmtest-bpf-next-VM_Test-0 success Logs for Lint
bpf/vmtest-bpf-next-VM_Test-1 success Logs for ShellCheck
bpf/vmtest-bpf-next-VM_Test-2 success Logs for Unittests
bpf/vmtest-bpf-next-VM_Test-3 success Logs for Validate matrix.py
bpf/vmtest-bpf-next-PR fail PR summary
bpf/vmtest-bpf-next-VM_Test-6 success Logs for aarch64-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-7 success Logs for aarch64-gcc / veristat-kernel
bpf/vmtest-bpf-next-VM_Test-8 success Logs for aarch64-gcc / veristat-meta
bpf/vmtest-bpf-next-VM_Test-9 success Logs for s390x-gcc / GCC BPF
bpf/vmtest-bpf-next-VM_Test-10 fail Logs for s390x-gcc / build / build for s390x with gcc
bpf/vmtest-bpf-next-VM_Test-11 success Logs for s390x-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-12 success Logs for s390x-gcc / veristat-kernel
bpf/vmtest-bpf-next-VM_Test-5 fail Logs for aarch64-gcc / build / build for aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-13 success Logs for s390x-gcc / veristat-meta
bpf/vmtest-bpf-next-VM_Test-4 success Logs for aarch64-gcc / GCC BPF
bpf/vmtest-bpf-next-VM_Test-14 success Logs for set-matrix
bpf/vmtest-bpf-next-VM_Test-15 success Logs for test
bpf/vmtest-bpf-next-VM_Test-16 success Logs for test
bpf/vmtest-bpf-next-VM_Test-17 success Logs for test
bpf/vmtest-bpf-next-VM_Test-18 success Logs for test
bpf/vmtest-bpf-next-VM_Test-19 success Logs for test
bpf/vmtest-bpf-next-VM_Test-20 success Logs for x86_64-gcc / GCC BPF
bpf/vmtest-bpf-next-VM_Test-21 fail Logs for x86_64-gcc / build / build for x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-22 success Logs for x86_64-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-23 success Logs for x86_64-gcc / veristat-kernel
bpf/vmtest-bpf-next-VM_Test-24 success Logs for x86_64-gcc / veristat-meta
bpf/vmtest-bpf-next-VM_Test-25 success Logs for x86_64-llvm-17 / GCC BPF
bpf/vmtest-bpf-next-VM_Test-26 fail Logs for x86_64-llvm-17 / build / build for x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-27 fail Logs for x86_64-llvm-17 / build-release / build for x86_64 with llvm-17-O2
bpf/vmtest-bpf-next-VM_Test-28 success Logs for x86_64-llvm-17 / veristat-kernel
bpf/vmtest-bpf-next-VM_Test-29 success Logs for x86_64-llvm-17 / veristat-meta
bpf/vmtest-bpf-next-VM_Test-30 success Logs for x86_64-llvm-18 / GCC BPF
bpf/vmtest-bpf-next-VM_Test-31 fail Logs for x86_64-llvm-18 / build / build for x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-32 fail Logs for x86_64-llvm-18 / build-release / build for x86_64 with llvm-18-O2
bpf/vmtest-bpf-next-VM_Test-33 success Logs for x86_64-llvm-18 / veristat-kernel
bpf/vmtest-bpf-next-VM_Test-34 success Logs for x86_64-llvm-18 / veristat-meta
netdev/series_format success Posting correctly formatted
netdev/tree_selection success Clearly marked for bpf-next, async
netdev/ynl success Generated files up to date; no warnings/errors; no diff in generated;
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 194 this patch: 194
netdev/build_tools success Errors and warnings before: 26 (+0) this patch: 26 (+0)
netdev/cc_maintainers warning 20 maintainers not CCed: mingo@redhat.com sdf@fomichev.me nathan@kernel.org dave.hansen@linux.intel.com jolsa@kernel.org hpa@zytor.com martin.lau@linux.dev tglx@linutronix.de netdev@vger.kernel.org justinstitt@google.com nick.desaulniers+lkml@gmail.com dsahern@kernel.org morbo@google.com llvm@lists.linux.dev bp@alien8.de song@kernel.org haoluo@google.com x86@kernel.org john.fastabend@gmail.com kpsingh@kernel.org
netdev/build_clang success Errors and warnings before: 225 this patch: 225
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 7022 this patch: 7022
netdev/checkpatch warning CHECK: Alignment should match open parenthesis CHECK: Please don't use multiple blank lines WARNING: line length of 98 exceeds 80 columns
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0

Commit Message

Anton Protopopov March 18, 2025, 2:33 p.m. UTC
Add support for a new version of JA instruction, a static branch JA. Such
instructions may either jump to the specified offset or act as nops. To
distinguish such instructions from normal JA the BPF_STATIC_BRANCH_JA flag
should be set for the SRC register.

By default on program load such instructions are jitted as a normal JA.
However, if the BPF_STATIC_BRANCH_NOP flag is set in the SRC register,
then the instruction is jitted to a NOP.

In order to generate BPF_STATIC_BRANCH_JA instructions using llvm two new
instructions will be added:

	asm volatile goto ("nop_or_gotol %l[label]" :::: label);

will generate the BPF_STATIC_BRANCH_JA|BPF_STATIC_BRANCH_NOP instuction and

	asm volatile goto ("gotol_or_nop %l[label]" :::: label);

will generate a BPF_STATIC_BRANCH_JA instruction, without an extra bit set.
The reason for adding two instructions is that both are required to implement
static keys functionality for BPF, namely, to distinguish between likely and
unlikely branches.

The verifier logic is extended to check both possible paths: jump and nop.

Signed-off-by: Anton Protopopov <aspsk@isovalent.com>
---
 arch/x86/net/bpf_jit_comp.c    | 19 +++++++++++++--
 include/uapi/linux/bpf.h       | 10 ++++++++
 kernel/bpf/verifier.c          | 43 +++++++++++++++++++++++++++-------
 tools/include/uapi/linux/bpf.h | 10 ++++++++
 4 files changed, 71 insertions(+), 11 deletions(-)

Comments

David Faust March 18, 2025, 7 p.m. UTC | #1
On 3/18/25 07:33, Anton Protopopov wrote:
> Add support for a new version of JA instruction, a static branch JA. Such
> instructions may either jump to the specified offset or act as nops. To
> distinguish such instructions from normal JA the BPF_STATIC_BRANCH_JA flag
> should be set for the SRC register.
> 
> By default on program load such instructions are jitted as a normal JA.
> However, if the BPF_STATIC_BRANCH_NOP flag is set in the SRC register,
> then the instruction is jitted to a NOP.

[adding context from the cover letter:]
> 3) Patch 4 adds support for a new BPF instruction. It looks
> reasonable to use an extended BPF_JMP|BPF_JA instruction, and not
> may_goto. Reasons: a follow up will add support for
> BPF_JMP|BPF_JA|BPF_X (indirect jumps), which also utilizes INSN_SET maps (see [2]).
> Then another follow up will add support CALL|BPF_X, for which there's
> no corresponding magic instruction (to be discussed at the following
> LSF/MM/BPF).

I don't understand the reasoning to extend JA rather than JCOND.

Couldn't the followup for indirect jumps also use JCOND, with BPF_SRC_X
set to distinguish from the absolute jumps here?
If the problem is that the indirect version will need both reigster
fields and JCOND is using SRC to encode the operation (0 for may_goto),
aren't you going to have exactly the same problem with BPF_JA|BPF_X and
the BPF_STATIC_BRANCH flag?  Or is the plan to stick the flag in another
different field of BPF_JA|BPF_X instruction...?

It seems like the general problem here that there is a growing class of
statically-decided-post-compiler conditional jumps, but no more free
insn class bits to use.  I suggest we try hard to encapsulate them as
much as possible under JCOND rather than (ab)using different fields of
different JMP insns to encode the 'maybe' versions on a case-by-case
basis.

To put it another way, why not make BPF_JMP|BPF_JCOND its own "class"
of insn and encode all of these conditional pseudo-jumps under it?

As far as I am aware (I may be out of date), the only JCOND insn
currently is may_goto (src_reg == 0), and may_goto only uses the 16-bit
offset. That seems to leave a lot of room (and bits) to design a whole
sub-class of JMP|JCOND instructions in a backwards compatible way...

> 
> In order to generate BPF_STATIC_BRANCH_JA instructions using llvm two new
> instructions will be added:
> 
> 	asm volatile goto ("nop_or_gotol %l[label]" :::: label);
> 
> will generate the BPF_STATIC_BRANCH_JA|BPF_STATIC_BRANCH_NOP instuction and
> 
> 	asm volatile goto ("gotol_or_nop %l[label]" :::: label);
> 
> will generate a BPF_STATIC_BRANCH_JA instruction, without an extra bit set.
> The reason for adding two instructions is that both are required to implement
> static keys functionality for BPF, namely, to distinguish between likely and
> unlikely branches.
> 
> The verifier logic is extended to check both possible paths: jump and nop.
> 
> Signed-off-by: Anton Protopopov <aspsk@isovalent.com>
> ---
>  arch/x86/net/bpf_jit_comp.c    | 19 +++++++++++++--
>  include/uapi/linux/bpf.h       | 10 ++++++++
>  kernel/bpf/verifier.c          | 43 +++++++++++++++++++++++++++-------
>  tools/include/uapi/linux/bpf.h | 10 ++++++++
>  4 files changed, 71 insertions(+), 11 deletions(-)
> 
> diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
> index d3491cc0898b..5856ac1aab80 100644
> --- a/arch/x86/net/bpf_jit_comp.c
> +++ b/arch/x86/net/bpf_jit_comp.c
> @@ -1482,6 +1482,15 @@ static void emit_priv_frame_ptr(u8 **pprog, void __percpu *priv_frame_ptr)
>  	*pprog = prog;
>  }
>  
> +static bool is_static_ja_nop(const struct bpf_insn *insn)
> +{
> +	u8 code = insn->code;
> +
> +	return (code == (BPF_JMP | BPF_JA) || code == (BPF_JMP32 | BPF_JA)) &&
> +	       (insn->src_reg & BPF_STATIC_BRANCH_JA) &&
> +	       (insn->src_reg & BPF_STATIC_BRANCH_NOP);
> +}
> +
>  #define INSN_SZ_DIFF (((addrs[i] - addrs[i - 1]) - (prog - temp)))
>  
>  #define __LOAD_TCC_PTR(off)			\
> @@ -2519,9 +2528,15 @@ st:			if (is_imm8(insn->off))
>  					}
>  					emit_nops(&prog, INSN_SZ_DIFF - 2);
>  				}
> -				EMIT2(0xEB, jmp_offset);
> +				if (is_static_ja_nop(insn))
> +					emit_nops(&prog, 2);
> +				else
> +					EMIT2(0xEB, jmp_offset);
>  			} else if (is_simm32(jmp_offset)) {
> -				EMIT1_off32(0xE9, jmp_offset);
> +				if (is_static_ja_nop(insn))
> +					emit_nops(&prog, 5);
> +				else
> +					EMIT1_off32(0xE9, jmp_offset);
>  			} else {
>  				pr_err("jmp gen bug %llx\n", jmp_offset);
>  				return -EFAULT;
> diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
> index b8e588ed6406..57e0fd636a27 100644
> --- a/include/uapi/linux/bpf.h
> +++ b/include/uapi/linux/bpf.h
> @@ -1462,6 +1462,16 @@ struct bpf_stack_build_id {
>  	};
>  };
>  
> +/* Flags for JA insn, passed in SRC_REG */
> +enum {
> +	BPF_STATIC_BRANCH_JA  = 1 << 0,
> +	BPF_STATIC_BRANCH_NOP = 1 << 1,
> +};
> +
> +#define BPF_STATIC_BRANCH_MASK (BPF_STATIC_BRANCH_JA | \
> +				BPF_STATIC_BRANCH_NOP)
> +
> +
>  #define BPF_OBJ_NAME_LEN 16U
>  
>  union bpf_attr {
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 6554f7aea0d8..0860ef57d5af 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -17374,14 +17374,24 @@ static int visit_insn(int t, struct bpf_verifier_env *env)
>  		else
>  			off = insn->imm;
>  
> -		/* unconditional jump with single edge */
> -		ret = push_insn(t, t + off + 1, FALLTHROUGH, env);
> -		if (ret)
> -			return ret;
> +		if (insn->src_reg & BPF_STATIC_BRANCH_JA) {
> +			/* static branch - jump with two edges */
> +			mark_prune_point(env, t);
>  
> -		mark_prune_point(env, t + off + 1);
> -		mark_jmp_point(env, t + off + 1);
> +			ret = push_insn(t, t + 1, FALLTHROUGH, env);
> +			if (ret)
> +				return ret;
> +
> +			ret = push_insn(t, t + off + 1, BRANCH, env);
> +		} else {
> +			/* unconditional jump with single edge */
> +			ret = push_insn(t, t + off + 1, FALLTHROUGH, env);
> +			if (ret)
> +				return ret;
>  
> +			mark_prune_point(env, t + off + 1);
> +			mark_jmp_point(env, t + off + 1);
> +		}
>  		return ret;
>  
>  	default:
> @@ -19414,8 +19424,11 @@ static int do_check(struct bpf_verifier_env *env)
>  
>  				mark_reg_scratched(env, BPF_REG_0);
>  			} else if (opcode == BPF_JA) {
> +				struct bpf_verifier_state *other_branch;
> +				u32 jmp_offset;
> +
>  				if (BPF_SRC(insn->code) != BPF_K ||
> -				    insn->src_reg != BPF_REG_0 ||
> +				    (insn->src_reg & ~BPF_STATIC_BRANCH_MASK) ||
>  				    insn->dst_reg != BPF_REG_0 ||
>  				    (class == BPF_JMP && insn->imm != 0) ||
>  				    (class == BPF_JMP32 && insn->off != 0)) {
> @@ -19424,9 +19437,21 @@ static int do_check(struct bpf_verifier_env *env)
>  				}
>  
>  				if (class == BPF_JMP)
> -					env->insn_idx += insn->off + 1;
> +					jmp_offset = insn->off + 1;
>  				else
> -					env->insn_idx += insn->imm + 1;
> +					jmp_offset = insn->imm + 1;
> +
> +				/* Staic branch can either jump to +off or +0 */
> +				if (insn->src_reg & BPF_STATIC_BRANCH_JA) {
> +					other_branch = push_stack(env, env->insn_idx + jmp_offset,
> +							env->insn_idx, false);
> +					if (!other_branch)
> +						return -EFAULT;
> +
> +					jmp_offset = 1;
> +				}
> +
> +				env->insn_idx += jmp_offset;
>  				continue;
>  
>  			} else if (opcode == BPF_EXIT) {
> diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h
> index b8e588ed6406..57e0fd636a27 100644
> --- a/tools/include/uapi/linux/bpf.h
> +++ b/tools/include/uapi/linux/bpf.h
> @@ -1462,6 +1462,16 @@ struct bpf_stack_build_id {
>  	};
>  };
>  
> +/* Flags for JA insn, passed in SRC_REG */
> +enum {
> +	BPF_STATIC_BRANCH_JA  = 1 << 0,
> +	BPF_STATIC_BRANCH_NOP = 1 << 1,
> +};
> +
> +#define BPF_STATIC_BRANCH_MASK (BPF_STATIC_BRANCH_JA | \
> +				BPF_STATIC_BRANCH_NOP)
> +
> +
>  #define BPF_OBJ_NAME_LEN 16U
>  
>  union bpf_attr {
Anton Protopopov March 18, 2025, 7:24 p.m. UTC | #2
On 25/03/18 12:00PM, David Faust wrote:
> 
> 
> On 3/18/25 07:33, Anton Protopopov wrote:
> > Add support for a new version of JA instruction, a static branch JA. Such
> > instructions may either jump to the specified offset or act as nops. To
> > distinguish such instructions from normal JA the BPF_STATIC_BRANCH_JA flag
> > should be set for the SRC register.
> > 
> > By default on program load such instructions are jitted as a normal JA.
> > However, if the BPF_STATIC_BRANCH_NOP flag is set in the SRC register,
> > then the instruction is jitted to a NOP.
> 
> [adding context from the cover letter:]
> > 3) Patch 4 adds support for a new BPF instruction. It looks
> > reasonable to use an extended BPF_JMP|BPF_JA instruction, and not
> > may_goto. Reasons: a follow up will add support for
> > BPF_JMP|BPF_JA|BPF_X (indirect jumps), which also utilizes INSN_SET maps (see [2]).
> > Then another follow up will add support CALL|BPF_X, for which there's
> > no corresponding magic instruction (to be discussed at the following
> > LSF/MM/BPF).
> 
> I don't understand the reasoning to extend JA rather than JCOND.
> 
> Couldn't the followup for indirect jumps also use JCOND, with BPF_SRC_X
> set to distinguish from the absolute jumps here?
>
> If the problem is that the indirect version will need both reigster
> fields and JCOND is using SRC to encode the operation (0 for may_goto),
> aren't you going to have exactly the same problem with BPF_JA|BPF_X and
> the BPF_STATIC_BRANCH flag?  Or is the plan to stick the flag in another
> different field of BPF_JA|BPF_X instruction...?
> 
> It seems like the general problem here that there is a growing class of
> statically-decided-post-compiler conditional jumps, but no more free
> insn class bits to use.  I suggest we try hard to encapsulate them as
> much as possible under JCOND rather than (ab)using different fields of
> different JMP insns to encode the 'maybe' versions on a case-by-case
> basis.
> 
> To put it another way, why not make BPF_JMP|BPF_JCOND its own "class"
> of insn and encode all of these conditional pseudo-jumps under it?

I agree that the "magic jump" (BPF_STATIC_BRANCH) is, maybe, better to go with
BPF_JMP|BPF_JCOND, as it emphasizes that the instruction is a special one.

However, for indirect jumps the BPF_JMP|BPF_JA|BPF_X looks more natural.

> As far as I am aware (I may be out of date), the only JCOND insn
> currently is may_goto (src_reg == 0), and may_goto only uses the 16-bit
> offset. That seems to leave a lot of room (and bits) to design a whole
> sub-class of JMP|JCOND instructions in a backwards compatible way...
> 
> > 
> > In order to generate BPF_STATIC_BRANCH_JA instructions using llvm two new
> > instructions will be added:
> > 
> > 	asm volatile goto ("nop_or_gotol %l[label]" :::: label);
> > 
> > will generate the BPF_STATIC_BRANCH_JA|BPF_STATIC_BRANCH_NOP instuction and
> > 
> > 	asm volatile goto ("gotol_or_nop %l[label]" :::: label);
> > 
> > will generate a BPF_STATIC_BRANCH_JA instruction, without an extra bit set.
> > The reason for adding two instructions is that both are required to implement
> > static keys functionality for BPF, namely, to distinguish between likely and
> > unlikely branches.
> > 
> > The verifier logic is extended to check both possible paths: jump and nop.
> > 
> > Signed-off-by: Anton Protopopov <aspsk@isovalent.com>
> > ---
> >  arch/x86/net/bpf_jit_comp.c    | 19 +++++++++++++--
> >  include/uapi/linux/bpf.h       | 10 ++++++++
> >  kernel/bpf/verifier.c          | 43 +++++++++++++++++++++++++++-------
> >  tools/include/uapi/linux/bpf.h | 10 ++++++++
> >  4 files changed, 71 insertions(+), 11 deletions(-)
> > 
> > diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
> > index d3491cc0898b..5856ac1aab80 100644
> > --- a/arch/x86/net/bpf_jit_comp.c
> > +++ b/arch/x86/net/bpf_jit_comp.c
> > @@ -1482,6 +1482,15 @@ static void emit_priv_frame_ptr(u8 **pprog, void __percpu *priv_frame_ptr)
> >  	*pprog = prog;
> >  }
> >  
> > +static bool is_static_ja_nop(const struct bpf_insn *insn)
> > +{
> > +	u8 code = insn->code;
> > +
> > +	return (code == (BPF_JMP | BPF_JA) || code == (BPF_JMP32 | BPF_JA)) &&
> > +	       (insn->src_reg & BPF_STATIC_BRANCH_JA) &&
> > +	       (insn->src_reg & BPF_STATIC_BRANCH_NOP);
> > +}
> > +
> >  #define INSN_SZ_DIFF (((addrs[i] - addrs[i - 1]) - (prog - temp)))
> >  
> >  #define __LOAD_TCC_PTR(off)			\
> > @@ -2519,9 +2528,15 @@ st:			if (is_imm8(insn->off))
> >  					}
> >  					emit_nops(&prog, INSN_SZ_DIFF - 2);
> >  				}
> > -				EMIT2(0xEB, jmp_offset);
> > +				if (is_static_ja_nop(insn))
> > +					emit_nops(&prog, 2);
> > +				else
> > +					EMIT2(0xEB, jmp_offset);
> >  			} else if (is_simm32(jmp_offset)) {
> > -				EMIT1_off32(0xE9, jmp_offset);
> > +				if (is_static_ja_nop(insn))
> > +					emit_nops(&prog, 5);
> > +				else
> > +					EMIT1_off32(0xE9, jmp_offset);
> >  			} else {
> >  				pr_err("jmp gen bug %llx\n", jmp_offset);
> >  				return -EFAULT;
> > diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
> > index b8e588ed6406..57e0fd636a27 100644
> > --- a/include/uapi/linux/bpf.h
> > +++ b/include/uapi/linux/bpf.h
> > @@ -1462,6 +1462,16 @@ struct bpf_stack_build_id {
> >  	};
> >  };
> >  
> > +/* Flags for JA insn, passed in SRC_REG */
> > +enum {
> > +	BPF_STATIC_BRANCH_JA  = 1 << 0,
> > +	BPF_STATIC_BRANCH_NOP = 1 << 1,
> > +};
> > +
> > +#define BPF_STATIC_BRANCH_MASK (BPF_STATIC_BRANCH_JA | \
> > +				BPF_STATIC_BRANCH_NOP)
> > +
> > +
> >  #define BPF_OBJ_NAME_LEN 16U
> >  
> >  union bpf_attr {
> > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> > index 6554f7aea0d8..0860ef57d5af 100644
> > --- a/kernel/bpf/verifier.c
> > +++ b/kernel/bpf/verifier.c
> > @@ -17374,14 +17374,24 @@ static int visit_insn(int t, struct bpf_verifier_env *env)
> >  		else
> >  			off = insn->imm;
> >  
> > -		/* unconditional jump with single edge */
> > -		ret = push_insn(t, t + off + 1, FALLTHROUGH, env);
> > -		if (ret)
> > -			return ret;
> > +		if (insn->src_reg & BPF_STATIC_BRANCH_JA) {
> > +			/* static branch - jump with two edges */
> > +			mark_prune_point(env, t);
> >  
> > -		mark_prune_point(env, t + off + 1);
> > -		mark_jmp_point(env, t + off + 1);
> > +			ret = push_insn(t, t + 1, FALLTHROUGH, env);
> > +			if (ret)
> > +				return ret;
> > +
> > +			ret = push_insn(t, t + off + 1, BRANCH, env);
> > +		} else {
> > +			/* unconditional jump with single edge */
> > +			ret = push_insn(t, t + off + 1, FALLTHROUGH, env);
> > +			if (ret)
> > +				return ret;
> >  
> > +			mark_prune_point(env, t + off + 1);
> > +			mark_jmp_point(env, t + off + 1);
> > +		}
> >  		return ret;
> >  
> >  	default:
> > @@ -19414,8 +19424,11 @@ static int do_check(struct bpf_verifier_env *env)
> >  
> >  				mark_reg_scratched(env, BPF_REG_0);
> >  			} else if (opcode == BPF_JA) {
> > +				struct bpf_verifier_state *other_branch;
> > +				u32 jmp_offset;
> > +
> >  				if (BPF_SRC(insn->code) != BPF_K ||
> > -				    insn->src_reg != BPF_REG_0 ||
> > +				    (insn->src_reg & ~BPF_STATIC_BRANCH_MASK) ||
> >  				    insn->dst_reg != BPF_REG_0 ||
> >  				    (class == BPF_JMP && insn->imm != 0) ||
> >  				    (class == BPF_JMP32 && insn->off != 0)) {
> > @@ -19424,9 +19437,21 @@ static int do_check(struct bpf_verifier_env *env)
> >  				}
> >  
> >  				if (class == BPF_JMP)
> > -					env->insn_idx += insn->off + 1;
> > +					jmp_offset = insn->off + 1;
> >  				else
> > -					env->insn_idx += insn->imm + 1;
> > +					jmp_offset = insn->imm + 1;
> > +
> > +				/* Staic branch can either jump to +off or +0 */
> > +				if (insn->src_reg & BPF_STATIC_BRANCH_JA) {
> > +					other_branch = push_stack(env, env->insn_idx + jmp_offset,
> > +							env->insn_idx, false);
> > +					if (!other_branch)
> > +						return -EFAULT;
> > +
> > +					jmp_offset = 1;
> > +				}
> > +
> > +				env->insn_idx += jmp_offset;
> >  				continue;
> >  
> >  			} else if (opcode == BPF_EXIT) {
> > diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h
> > index b8e588ed6406..57e0fd636a27 100644
> > --- a/tools/include/uapi/linux/bpf.h
> > +++ b/tools/include/uapi/linux/bpf.h
> > @@ -1462,6 +1462,16 @@ struct bpf_stack_build_id {
> >  	};
> >  };
> >  
> > +/* Flags for JA insn, passed in SRC_REG */
> > +enum {
> > +	BPF_STATIC_BRANCH_JA  = 1 << 0,
> > +	BPF_STATIC_BRANCH_NOP = 1 << 1,
> > +};
> > +
> > +#define BPF_STATIC_BRANCH_MASK (BPF_STATIC_BRANCH_JA | \
> > +				BPF_STATIC_BRANCH_NOP)
> > +
> > +
> >  #define BPF_OBJ_NAME_LEN 16U
> >  
> >  union bpf_attr {
>
David Faust March 18, 2025, 7:30 p.m. UTC | #3
On 3/18/25 12:24, Anton Protopopov wrote:
> On 25/03/18 12:00PM, David Faust wrote:
>>
>>
>> On 3/18/25 07:33, Anton Protopopov wrote:
>>> Add support for a new version of JA instruction, a static branch JA. Such
>>> instructions may either jump to the specified offset or act as nops. To
>>> distinguish such instructions from normal JA the BPF_STATIC_BRANCH_JA flag
>>> should be set for the SRC register.
>>>
>>> By default on program load such instructions are jitted as a normal JA.
>>> However, if the BPF_STATIC_BRANCH_NOP flag is set in the SRC register,
>>> then the instruction is jitted to a NOP.
>>
>> [adding context from the cover letter:]
>>> 3) Patch 4 adds support for a new BPF instruction. It looks
>>> reasonable to use an extended BPF_JMP|BPF_JA instruction, and not
>>> may_goto. Reasons: a follow up will add support for
>>> BPF_JMP|BPF_JA|BPF_X (indirect jumps), which also utilizes INSN_SET maps (see [2]).
>>> Then another follow up will add support CALL|BPF_X, for which there's
>>> no corresponding magic instruction (to be discussed at the following
>>> LSF/MM/BPF).
>>
>> I don't understand the reasoning to extend JA rather than JCOND.
>>
>> Couldn't the followup for indirect jumps also use JCOND, with BPF_SRC_X
>> set to distinguish from the absolute jumps here?
>>
>> If the problem is that the indirect version will need both reigster
>> fields and JCOND is using SRC to encode the operation (0 for may_goto),
>> aren't you going to have exactly the same problem with BPF_JA|BPF_X and
>> the BPF_STATIC_BRANCH flag?  Or is the plan to stick the flag in another
>> different field of BPF_JA|BPF_X instruction...?
>>
>> It seems like the general problem here that there is a growing class of
>> statically-decided-post-compiler conditional jumps, but no more free
>> insn class bits to use.  I suggest we try hard to encapsulate them as
>> much as possible under JCOND rather than (ab)using different fields of
>> different JMP insns to encode the 'maybe' versions on a case-by-case
>> basis.
>>
>> To put it another way, why not make BPF_JMP|BPF_JCOND its own "class"
>> of insn and encode all of these conditional pseudo-jumps under it?
> 
> I agree that the "magic jump" (BPF_STATIC_BRANCH) is, maybe, better to go with
> BPF_JMP|BPF_JCOND, as it emphasizes that the instruction is a special one.
> 
> However, for indirect jumps the BPF_JMP|BPF_JA|BPF_X looks more natural.

Ah, maybe I misunderstood the context; will these BPF_JMP|BPF_JA|BPF_X indirect
jumps exclusively be "real" or was the intent that they also have the "maybe"
variant (could be jitted to no-op)? I was thinking the latter.

For the always-real version I agree BPF_JA|BPF_X makes total sense.
If there is a "maybe" variant that could be jitted to no-op then that should
fall under JCOND.

> 
>> As far as I am aware (I may be out of date), the only JCOND insn
>> currently is may_goto (src_reg == 0), and may_goto only uses the 16-bit
>> offset. That seems to leave a lot of room (and bits) to design a whole
>> sub-class of JMP|JCOND instructions in a backwards compatible way...
>>
>>>
>>> In order to generate BPF_STATIC_BRANCH_JA instructions using llvm two new
>>> instructions will be added:
>>>
>>> 	asm volatile goto ("nop_or_gotol %l[label]" :::: label);
>>>
>>> will generate the BPF_STATIC_BRANCH_JA|BPF_STATIC_BRANCH_NOP instuction and
>>>
>>> 	asm volatile goto ("gotol_or_nop %l[label]" :::: label);
>>>
>>> will generate a BPF_STATIC_BRANCH_JA instruction, without an extra bit set.
>>> The reason for adding two instructions is that both are required to implement
>>> static keys functionality for BPF, namely, to distinguish between likely and
>>> unlikely branches.
>>>
>>> The verifier logic is extended to check both possible paths: jump and nop.
>>>
>>> Signed-off-by: Anton Protopopov <aspsk@isovalent.com>
>>> ---
>>>  arch/x86/net/bpf_jit_comp.c    | 19 +++++++++++++--
>>>  include/uapi/linux/bpf.h       | 10 ++++++++
>>>  kernel/bpf/verifier.c          | 43 +++++++++++++++++++++++++++-------
>>>  tools/include/uapi/linux/bpf.h | 10 ++++++++
>>>  4 files changed, 71 insertions(+), 11 deletions(-)
>>>
>>> diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
>>> index d3491cc0898b..5856ac1aab80 100644
>>> --- a/arch/x86/net/bpf_jit_comp.c
>>> +++ b/arch/x86/net/bpf_jit_comp.c
>>> @@ -1482,6 +1482,15 @@ static void emit_priv_frame_ptr(u8 **pprog, void __percpu *priv_frame_ptr)
>>>  	*pprog = prog;
>>>  }
>>>  
>>> +static bool is_static_ja_nop(const struct bpf_insn *insn)
>>> +{
>>> +	u8 code = insn->code;
>>> +
>>> +	return (code == (BPF_JMP | BPF_JA) || code == (BPF_JMP32 | BPF_JA)) &&
>>> +	       (insn->src_reg & BPF_STATIC_BRANCH_JA) &&
>>> +	       (insn->src_reg & BPF_STATIC_BRANCH_NOP);
>>> +}
>>> +
>>>  #define INSN_SZ_DIFF (((addrs[i] - addrs[i - 1]) - (prog - temp)))
>>>  
>>>  #define __LOAD_TCC_PTR(off)			\
>>> @@ -2519,9 +2528,15 @@ st:			if (is_imm8(insn->off))
>>>  					}
>>>  					emit_nops(&prog, INSN_SZ_DIFF - 2);
>>>  				}
>>> -				EMIT2(0xEB, jmp_offset);
>>> +				if (is_static_ja_nop(insn))
>>> +					emit_nops(&prog, 2);
>>> +				else
>>> +					EMIT2(0xEB, jmp_offset);
>>>  			} else if (is_simm32(jmp_offset)) {
>>> -				EMIT1_off32(0xE9, jmp_offset);
>>> +				if (is_static_ja_nop(insn))
>>> +					emit_nops(&prog, 5);
>>> +				else
>>> +					EMIT1_off32(0xE9, jmp_offset);
>>>  			} else {
>>>  				pr_err("jmp gen bug %llx\n", jmp_offset);
>>>  				return -EFAULT;
>>> diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
>>> index b8e588ed6406..57e0fd636a27 100644
>>> --- a/include/uapi/linux/bpf.h
>>> +++ b/include/uapi/linux/bpf.h
>>> @@ -1462,6 +1462,16 @@ struct bpf_stack_build_id {
>>>  	};
>>>  };
>>>  
>>> +/* Flags for JA insn, passed in SRC_REG */
>>> +enum {
>>> +	BPF_STATIC_BRANCH_JA  = 1 << 0,
>>> +	BPF_STATIC_BRANCH_NOP = 1 << 1,
>>> +};
>>> +
>>> +#define BPF_STATIC_BRANCH_MASK (BPF_STATIC_BRANCH_JA | \
>>> +				BPF_STATIC_BRANCH_NOP)
>>> +
>>> +
>>>  #define BPF_OBJ_NAME_LEN 16U
>>>  
>>>  union bpf_attr {
>>> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
>>> index 6554f7aea0d8..0860ef57d5af 100644
>>> --- a/kernel/bpf/verifier.c
>>> +++ b/kernel/bpf/verifier.c
>>> @@ -17374,14 +17374,24 @@ static int visit_insn(int t, struct bpf_verifier_env *env)
>>>  		else
>>>  			off = insn->imm;
>>>  
>>> -		/* unconditional jump with single edge */
>>> -		ret = push_insn(t, t + off + 1, FALLTHROUGH, env);
>>> -		if (ret)
>>> -			return ret;
>>> +		if (insn->src_reg & BPF_STATIC_BRANCH_JA) {
>>> +			/* static branch - jump with two edges */
>>> +			mark_prune_point(env, t);
>>>  
>>> -		mark_prune_point(env, t + off + 1);
>>> -		mark_jmp_point(env, t + off + 1);
>>> +			ret = push_insn(t, t + 1, FALLTHROUGH, env);
>>> +			if (ret)
>>> +				return ret;
>>> +
>>> +			ret = push_insn(t, t + off + 1, BRANCH, env);
>>> +		} else {
>>> +			/* unconditional jump with single edge */
>>> +			ret = push_insn(t, t + off + 1, FALLTHROUGH, env);
>>> +			if (ret)
>>> +				return ret;
>>>  
>>> +			mark_prune_point(env, t + off + 1);
>>> +			mark_jmp_point(env, t + off + 1);
>>> +		}
>>>  		return ret;
>>>  
>>>  	default:
>>> @@ -19414,8 +19424,11 @@ static int do_check(struct bpf_verifier_env *env)
>>>  
>>>  				mark_reg_scratched(env, BPF_REG_0);
>>>  			} else if (opcode == BPF_JA) {
>>> +				struct bpf_verifier_state *other_branch;
>>> +				u32 jmp_offset;
>>> +
>>>  				if (BPF_SRC(insn->code) != BPF_K ||
>>> -				    insn->src_reg != BPF_REG_0 ||
>>> +				    (insn->src_reg & ~BPF_STATIC_BRANCH_MASK) ||
>>>  				    insn->dst_reg != BPF_REG_0 ||
>>>  				    (class == BPF_JMP && insn->imm != 0) ||
>>>  				    (class == BPF_JMP32 && insn->off != 0)) {
>>> @@ -19424,9 +19437,21 @@ static int do_check(struct bpf_verifier_env *env)
>>>  				}
>>>  
>>>  				if (class == BPF_JMP)
>>> -					env->insn_idx += insn->off + 1;
>>> +					jmp_offset = insn->off + 1;
>>>  				else
>>> -					env->insn_idx += insn->imm + 1;
>>> +					jmp_offset = insn->imm + 1;
>>> +
>>> +				/* Staic branch can either jump to +off or +0 */
>>> +				if (insn->src_reg & BPF_STATIC_BRANCH_JA) {
>>> +					other_branch = push_stack(env, env->insn_idx + jmp_offset,
>>> +							env->insn_idx, false);
>>> +					if (!other_branch)
>>> +						return -EFAULT;
>>> +
>>> +					jmp_offset = 1;
>>> +				}
>>> +
>>> +				env->insn_idx += jmp_offset;
>>>  				continue;
>>>  
>>>  			} else if (opcode == BPF_EXIT) {
>>> diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h
>>> index b8e588ed6406..57e0fd636a27 100644
>>> --- a/tools/include/uapi/linux/bpf.h
>>> +++ b/tools/include/uapi/linux/bpf.h
>>> @@ -1462,6 +1462,16 @@ struct bpf_stack_build_id {
>>>  	};
>>>  };
>>>  
>>> +/* Flags for JA insn, passed in SRC_REG */
>>> +enum {
>>> +	BPF_STATIC_BRANCH_JA  = 1 << 0,
>>> +	BPF_STATIC_BRANCH_NOP = 1 << 1,
>>> +};
>>> +
>>> +#define BPF_STATIC_BRANCH_MASK (BPF_STATIC_BRANCH_JA | \
>>> +				BPF_STATIC_BRANCH_NOP)
>>> +
>>> +
>>>  #define BPF_OBJ_NAME_LEN 16U
>>>  
>>>  union bpf_attr {
>>
Anton Protopopov March 18, 2025, 7:47 p.m. UTC | #4
On 25/03/18 12:30PM, David Faust wrote:
> 
> 
> On 3/18/25 12:24, Anton Protopopov wrote:
> > On 25/03/18 12:00PM, David Faust wrote:
> >>
> >>
> >> On 3/18/25 07:33, Anton Protopopov wrote:
> >>> Add support for a new version of JA instruction, a static branch JA. Such
> >>> instructions may either jump to the specified offset or act as nops. To
> >>> distinguish such instructions from normal JA the BPF_STATIC_BRANCH_JA flag
> >>> should be set for the SRC register.
> >>>
> >>> By default on program load such instructions are jitted as a normal JA.
> >>> However, if the BPF_STATIC_BRANCH_NOP flag is set in the SRC register,
> >>> then the instruction is jitted to a NOP.
> >>
> >> [adding context from the cover letter:]
> >>> 3) Patch 4 adds support for a new BPF instruction. It looks
> >>> reasonable to use an extended BPF_JMP|BPF_JA instruction, and not
> >>> may_goto. Reasons: a follow up will add support for
> >>> BPF_JMP|BPF_JA|BPF_X (indirect jumps), which also utilizes INSN_SET maps (see [2]).
> >>> Then another follow up will add support CALL|BPF_X, for which there's
> >>> no corresponding magic instruction (to be discussed at the following
> >>> LSF/MM/BPF).
> >>
> >> I don't understand the reasoning to extend JA rather than JCOND.
> >>
> >> Couldn't the followup for indirect jumps also use JCOND, with BPF_SRC_X
> >> set to distinguish from the absolute jumps here?
> >>
> >> If the problem is that the indirect version will need both reigster
> >> fields and JCOND is using SRC to encode the operation (0 for may_goto),
> >> aren't you going to have exactly the same problem with BPF_JA|BPF_X and
> >> the BPF_STATIC_BRANCH flag?  Or is the plan to stick the flag in another
> >> different field of BPF_JA|BPF_X instruction...?
> >>
> >> It seems like the general problem here that there is a growing class of
> >> statically-decided-post-compiler conditional jumps, but no more free
> >> insn class bits to use.  I suggest we try hard to encapsulate them as
> >> much as possible under JCOND rather than (ab)using different fields of
> >> different JMP insns to encode the 'maybe' versions on a case-by-case
> >> basis.
> >>
> >> To put it another way, why not make BPF_JMP|BPF_JCOND its own "class"
> >> of insn and encode all of these conditional pseudo-jumps under it?
> > 
> > I agree that the "magic jump" (BPF_STATIC_BRANCH) is, maybe, better to go with
> > BPF_JMP|BPF_JCOND, as it emphasizes that the instruction is a special one.
> > 
> > However, for indirect jumps the BPF_JMP|BPF_JA|BPF_X looks more natural.
> 
> Ah, maybe I misunderstood the context; will these BPF_JMP|BPF_JA|BPF_X indirect
> jumps exclusively be "real" or was the intent that they also have the "maybe"
> variant (could be jitted to no-op)? I was thinking the latter.
> 
> For the always-real version I agree BPF_JA|BPF_X makes total sense.
> If there is a "maybe" variant that could be jitted to no-op then that should
> fall under JCOND.

Yes, those are real, no patching: goto rX (where rX is properly loaded
from an instruction set).

> > 
> >> As far as I am aware (I may be out of date), the only JCOND insn
> >> currently is may_goto (src_reg == 0), and may_goto only uses the 16-bit
> >> offset. That seems to leave a lot of room (and bits) to design a whole
> >> sub-class of JMP|JCOND instructions in a backwards compatible way...
> >>
> >>>
> >>> In order to generate BPF_STATIC_BRANCH_JA instructions using llvm two new
> >>> instructions will be added:
> >>>
> >>> 	asm volatile goto ("nop_or_gotol %l[label]" :::: label);
> >>>
> >>> will generate the BPF_STATIC_BRANCH_JA|BPF_STATIC_BRANCH_NOP instuction and
> >>>
> >>> 	asm volatile goto ("gotol_or_nop %l[label]" :::: label);
> >>>
> >>> will generate a BPF_STATIC_BRANCH_JA instruction, without an extra bit set.
> >>> The reason for adding two instructions is that both are required to implement
> >>> static keys functionality for BPF, namely, to distinguish between likely and
> >>> unlikely branches.
> >>>
> >>> The verifier logic is extended to check both possible paths: jump and nop.
> >>>
> >>> Signed-off-by: Anton Protopopov <aspsk@isovalent.com>
> >>> ---
> >>>  arch/x86/net/bpf_jit_comp.c    | 19 +++++++++++++--
> >>>  include/uapi/linux/bpf.h       | 10 ++++++++
> >>>  kernel/bpf/verifier.c          | 43 +++++++++++++++++++++++++++-------
> >>>  tools/include/uapi/linux/bpf.h | 10 ++++++++
> >>>  4 files changed, 71 insertions(+), 11 deletions(-)
> >>>
> >>> diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
> >>> index d3491cc0898b..5856ac1aab80 100644
> >>> --- a/arch/x86/net/bpf_jit_comp.c
> >>> +++ b/arch/x86/net/bpf_jit_comp.c
> >>> @@ -1482,6 +1482,15 @@ static void emit_priv_frame_ptr(u8 **pprog, void __percpu *priv_frame_ptr)
> >>>  	*pprog = prog;
> >>>  }
> >>>  
> >>> +static bool is_static_ja_nop(const struct bpf_insn *insn)
> >>> +{
> >>> +	u8 code = insn->code;
> >>> +
> >>> +	return (code == (BPF_JMP | BPF_JA) || code == (BPF_JMP32 | BPF_JA)) &&
> >>> +	       (insn->src_reg & BPF_STATIC_BRANCH_JA) &&
> >>> +	       (insn->src_reg & BPF_STATIC_BRANCH_NOP);
> >>> +}
> >>> +
> >>>  #define INSN_SZ_DIFF (((addrs[i] - addrs[i - 1]) - (prog - temp)))
> >>>  
> >>>  #define __LOAD_TCC_PTR(off)			\
> >>> @@ -2519,9 +2528,15 @@ st:			if (is_imm8(insn->off))
> >>>  					}
> >>>  					emit_nops(&prog, INSN_SZ_DIFF - 2);
> >>>  				}
> >>> -				EMIT2(0xEB, jmp_offset);
> >>> +				if (is_static_ja_nop(insn))
> >>> +					emit_nops(&prog, 2);
> >>> +				else
> >>> +					EMIT2(0xEB, jmp_offset);
> >>>  			} else if (is_simm32(jmp_offset)) {
> >>> -				EMIT1_off32(0xE9, jmp_offset);
> >>> +				if (is_static_ja_nop(insn))
> >>> +					emit_nops(&prog, 5);
> >>> +				else
> >>> +					EMIT1_off32(0xE9, jmp_offset);
> >>>  			} else {
> >>>  				pr_err("jmp gen bug %llx\n", jmp_offset);
> >>>  				return -EFAULT;
> >>> diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
> >>> index b8e588ed6406..57e0fd636a27 100644
> >>> --- a/include/uapi/linux/bpf.h
> >>> +++ b/include/uapi/linux/bpf.h
> >>> @@ -1462,6 +1462,16 @@ struct bpf_stack_build_id {
> >>>  	};
> >>>  };
> >>>  
> >>> +/* Flags for JA insn, passed in SRC_REG */
> >>> +enum {
> >>> +	BPF_STATIC_BRANCH_JA  = 1 << 0,
> >>> +	BPF_STATIC_BRANCH_NOP = 1 << 1,
> >>> +};
> >>> +
> >>> +#define BPF_STATIC_BRANCH_MASK (BPF_STATIC_BRANCH_JA | \
> >>> +				BPF_STATIC_BRANCH_NOP)
> >>> +
> >>> +
> >>>  #define BPF_OBJ_NAME_LEN 16U
> >>>  
> >>>  union bpf_attr {
> >>> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> >>> index 6554f7aea0d8..0860ef57d5af 100644
> >>> --- a/kernel/bpf/verifier.c
> >>> +++ b/kernel/bpf/verifier.c
> >>> @@ -17374,14 +17374,24 @@ static int visit_insn(int t, struct bpf_verifier_env *env)
> >>>  		else
> >>>  			off = insn->imm;
> >>>  
> >>> -		/* unconditional jump with single edge */
> >>> -		ret = push_insn(t, t + off + 1, FALLTHROUGH, env);
> >>> -		if (ret)
> >>> -			return ret;
> >>> +		if (insn->src_reg & BPF_STATIC_BRANCH_JA) {
> >>> +			/* static branch - jump with two edges */
> >>> +			mark_prune_point(env, t);
> >>>  
> >>> -		mark_prune_point(env, t + off + 1);
> >>> -		mark_jmp_point(env, t + off + 1);
> >>> +			ret = push_insn(t, t + 1, FALLTHROUGH, env);
> >>> +			if (ret)
> >>> +				return ret;
> >>> +
> >>> +			ret = push_insn(t, t + off + 1, BRANCH, env);
> >>> +		} else {
> >>> +			/* unconditional jump with single edge */
> >>> +			ret = push_insn(t, t + off + 1, FALLTHROUGH, env);
> >>> +			if (ret)
> >>> +				return ret;
> >>>  
> >>> +			mark_prune_point(env, t + off + 1);
> >>> +			mark_jmp_point(env, t + off + 1);
> >>> +		}
> >>>  		return ret;
> >>>  
> >>>  	default:
> >>> @@ -19414,8 +19424,11 @@ static int do_check(struct bpf_verifier_env *env)
> >>>  
> >>>  				mark_reg_scratched(env, BPF_REG_0);
> >>>  			} else if (opcode == BPF_JA) {
> >>> +				struct bpf_verifier_state *other_branch;
> >>> +				u32 jmp_offset;
> >>> +
> >>>  				if (BPF_SRC(insn->code) != BPF_K ||
> >>> -				    insn->src_reg != BPF_REG_0 ||
> >>> +				    (insn->src_reg & ~BPF_STATIC_BRANCH_MASK) ||
> >>>  				    insn->dst_reg != BPF_REG_0 ||
> >>>  				    (class == BPF_JMP && insn->imm != 0) ||
> >>>  				    (class == BPF_JMP32 && insn->off != 0)) {
> >>> @@ -19424,9 +19437,21 @@ static int do_check(struct bpf_verifier_env *env)
> >>>  				}
> >>>  
> >>>  				if (class == BPF_JMP)
> >>> -					env->insn_idx += insn->off + 1;
> >>> +					jmp_offset = insn->off + 1;
> >>>  				else
> >>> -					env->insn_idx += insn->imm + 1;
> >>> +					jmp_offset = insn->imm + 1;
> >>> +
> >>> +				/* Staic branch can either jump to +off or +0 */
> >>> +				if (insn->src_reg & BPF_STATIC_BRANCH_JA) {
> >>> +					other_branch = push_stack(env, env->insn_idx + jmp_offset,
> >>> +							env->insn_idx, false);
> >>> +					if (!other_branch)
> >>> +						return -EFAULT;
> >>> +
> >>> +					jmp_offset = 1;
> >>> +				}
> >>> +
> >>> +				env->insn_idx += jmp_offset;
> >>>  				continue;
> >>>  
> >>>  			} else if (opcode == BPF_EXIT) {
> >>> diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h
> >>> index b8e588ed6406..57e0fd636a27 100644
> >>> --- a/tools/include/uapi/linux/bpf.h
> >>> +++ b/tools/include/uapi/linux/bpf.h
> >>> @@ -1462,6 +1462,16 @@ struct bpf_stack_build_id {
> >>>  	};
> >>>  };
> >>>  
> >>> +/* Flags for JA insn, passed in SRC_REG */
> >>> +enum {
> >>> +	BPF_STATIC_BRANCH_JA  = 1 << 0,
> >>> +	BPF_STATIC_BRANCH_NOP = 1 << 1,
> >>> +};
> >>> +
> >>> +#define BPF_STATIC_BRANCH_MASK (BPF_STATIC_BRANCH_JA | \
> >>> +				BPF_STATIC_BRANCH_NOP)
> >>> +
> >>> +
> >>>  #define BPF_OBJ_NAME_LEN 16U
> >>>  
> >>>  union bpf_attr {
> >>
>
diff mbox series

Patch

diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
index d3491cc0898b..5856ac1aab80 100644
--- a/arch/x86/net/bpf_jit_comp.c
+++ b/arch/x86/net/bpf_jit_comp.c
@@ -1482,6 +1482,15 @@  static void emit_priv_frame_ptr(u8 **pprog, void __percpu *priv_frame_ptr)
 	*pprog = prog;
 }
 
+static bool is_static_ja_nop(const struct bpf_insn *insn)
+{
+	u8 code = insn->code;
+
+	return (code == (BPF_JMP | BPF_JA) || code == (BPF_JMP32 | BPF_JA)) &&
+	       (insn->src_reg & BPF_STATIC_BRANCH_JA) &&
+	       (insn->src_reg & BPF_STATIC_BRANCH_NOP);
+}
+
 #define INSN_SZ_DIFF (((addrs[i] - addrs[i - 1]) - (prog - temp)))
 
 #define __LOAD_TCC_PTR(off)			\
@@ -2519,9 +2528,15 @@  st:			if (is_imm8(insn->off))
 					}
 					emit_nops(&prog, INSN_SZ_DIFF - 2);
 				}
-				EMIT2(0xEB, jmp_offset);
+				if (is_static_ja_nop(insn))
+					emit_nops(&prog, 2);
+				else
+					EMIT2(0xEB, jmp_offset);
 			} else if (is_simm32(jmp_offset)) {
-				EMIT1_off32(0xE9, jmp_offset);
+				if (is_static_ja_nop(insn))
+					emit_nops(&prog, 5);
+				else
+					EMIT1_off32(0xE9, jmp_offset);
 			} else {
 				pr_err("jmp gen bug %llx\n", jmp_offset);
 				return -EFAULT;
diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
index b8e588ed6406..57e0fd636a27 100644
--- a/include/uapi/linux/bpf.h
+++ b/include/uapi/linux/bpf.h
@@ -1462,6 +1462,16 @@  struct bpf_stack_build_id {
 	};
 };
 
+/* Flags for JA insn, passed in SRC_REG */
+enum {
+	BPF_STATIC_BRANCH_JA  = 1 << 0,
+	BPF_STATIC_BRANCH_NOP = 1 << 1,
+};
+
+#define BPF_STATIC_BRANCH_MASK (BPF_STATIC_BRANCH_JA | \
+				BPF_STATIC_BRANCH_NOP)
+
+
 #define BPF_OBJ_NAME_LEN 16U
 
 union bpf_attr {
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 6554f7aea0d8..0860ef57d5af 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -17374,14 +17374,24 @@  static int visit_insn(int t, struct bpf_verifier_env *env)
 		else
 			off = insn->imm;
 
-		/* unconditional jump with single edge */
-		ret = push_insn(t, t + off + 1, FALLTHROUGH, env);
-		if (ret)
-			return ret;
+		if (insn->src_reg & BPF_STATIC_BRANCH_JA) {
+			/* static branch - jump with two edges */
+			mark_prune_point(env, t);
 
-		mark_prune_point(env, t + off + 1);
-		mark_jmp_point(env, t + off + 1);
+			ret = push_insn(t, t + 1, FALLTHROUGH, env);
+			if (ret)
+				return ret;
+
+			ret = push_insn(t, t + off + 1, BRANCH, env);
+		} else {
+			/* unconditional jump with single edge */
+			ret = push_insn(t, t + off + 1, FALLTHROUGH, env);
+			if (ret)
+				return ret;
 
+			mark_prune_point(env, t + off + 1);
+			mark_jmp_point(env, t + off + 1);
+		}
 		return ret;
 
 	default:
@@ -19414,8 +19424,11 @@  static int do_check(struct bpf_verifier_env *env)
 
 				mark_reg_scratched(env, BPF_REG_0);
 			} else if (opcode == BPF_JA) {
+				struct bpf_verifier_state *other_branch;
+				u32 jmp_offset;
+
 				if (BPF_SRC(insn->code) != BPF_K ||
-				    insn->src_reg != BPF_REG_0 ||
+				    (insn->src_reg & ~BPF_STATIC_BRANCH_MASK) ||
 				    insn->dst_reg != BPF_REG_0 ||
 				    (class == BPF_JMP && insn->imm != 0) ||
 				    (class == BPF_JMP32 && insn->off != 0)) {
@@ -19424,9 +19437,21 @@  static int do_check(struct bpf_verifier_env *env)
 				}
 
 				if (class == BPF_JMP)
-					env->insn_idx += insn->off + 1;
+					jmp_offset = insn->off + 1;
 				else
-					env->insn_idx += insn->imm + 1;
+					jmp_offset = insn->imm + 1;
+
+				/* Staic branch can either jump to +off or +0 */
+				if (insn->src_reg & BPF_STATIC_BRANCH_JA) {
+					other_branch = push_stack(env, env->insn_idx + jmp_offset,
+							env->insn_idx, false);
+					if (!other_branch)
+						return -EFAULT;
+
+					jmp_offset = 1;
+				}
+
+				env->insn_idx += jmp_offset;
 				continue;
 
 			} else if (opcode == BPF_EXIT) {
diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h
index b8e588ed6406..57e0fd636a27 100644
--- a/tools/include/uapi/linux/bpf.h
+++ b/tools/include/uapi/linux/bpf.h
@@ -1462,6 +1462,16 @@  struct bpf_stack_build_id {
 	};
 };
 
+/* Flags for JA insn, passed in SRC_REG */
+enum {
+	BPF_STATIC_BRANCH_JA  = 1 << 0,
+	BPF_STATIC_BRANCH_NOP = 1 << 1,
+};
+
+#define BPF_STATIC_BRANCH_MASK (BPF_STATIC_BRANCH_JA | \
+				BPF_STATIC_BRANCH_NOP)
+
+
 #define BPF_OBJ_NAME_LEN 16U
 
 union bpf_attr {