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@xxxxxxxxxxxxx> >>> --- >>> 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 { >>