* [PATCH bpf-next v2] bpf: Rewrite "rX <<= 32; rX >>= 32" into "wX = wX" to keep linked scalars
@ 2026-08-21 4:28 Yonghong Song
2026-08-21 5:30 ` bot+bpf-ci
2026-08-21 17:36 ` Eduard Zingerman
0 siblings, 2 replies; 5+ messages in thread
From: Yonghong Song @ 2026-08-21 4:28 UTC (permalink / raw)
To: bpf
Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann,
Eduard Zingerman, kernel-team
For the following test in
tools/testing/selftests/bpf/progs/verifier_linked_scalars.c:
void alu32_negative_offset(void)
{
volatile char path[5];
volatile int offset = bpf_get_prandom_u32();
int off = offset;
if (off >= 5 && off < 10)
path[off - 5] = '.';
/* So compiler doesn't say: error: variable 'path' set but not used */
__sink(path[0]);
}
Without alu32 (-mcpu=v2), the test
verifier_linked_scalars/alu32_negative_offset will fail with llvm22 and
llvm23 like below.
3: (bf) r2 = r1 ; R1=scalar(id=1,...) R2=scalar(id=1,...)
4: (07) r2 += -5 ; R2=scalar(id=1-5,smin=-5,smax=0xfffffffa)
5: (67) r2 <<= 32 ; R2=scalar(smax=0x7fffffff00000000,...)
6: (77) r2 >>= 32 ; R2=scalar(smin=0,umax=0xffffffff,...)
7: (25) if r2 > 0x4 goto pc+5 ; R2=scalar(smin=0,smax=umax=4,...)
8: (bf) r2 = r10
9: (07) r2 += -5
10: (0f) r2 += r1 ; R1=scalar(id=1,smin=0,umax=0xffffffff)
; R2=fp(smin=-5,smax=0xfffffffa)
11: (b7) r1 = 46 ; R1=46
12: (73) *(u8 *)(r2 -5) = r1
invalid unbounded variable-offset write to stack R2
R1 is never narrowed down, so the address stays unbounded and the store
is rejected.
The test is okay for llvm21 with -mcpu=v2, see below:
3: (07) r1 += -5 ; R1=scalar(smin=-5,smax=0xfffffffa)
4: (67) r1 <<= 32 ; R1=scalar(smax=0x7fffffff00000000,...)
5: (77) r1 >>= 32 ; R1=scalar(smin=0,umax=0xffffffff,...)
6: (25) if r1 > 0x4 goto pc+5 ; R1=scalar(smin=0,smax=umax=4,...)
7: (bf) r2 = r10
8: (07) r2 += -5
9: (0f) r2 += r1 ; R2=fp(smin=-5,smax=-1)
10: (b7) r1 = 46 ; R1=46
11: (73) *(u8 *)(r2 +0) = r1 ; fp-8=ppppm???
To fix the test issue with llvm22 and llvm23, note that the shift pair
computes zext32(base + delta), which is what "wX = wX" computes as well
and which is exactly the relation BPF_ADD_CONST32 describes. So rewrite
the pair into "wX = wX" and let the mov turn the 64-bit link into a
32-bit one, which makes the -mcpu=v2 sequence track like an alu32 one.
The rewrite has to keep the program length, so the second shift becomes
a second "wX = wX" rather than being removed. A "goto pc+0" nop looks
like the obvious filler, but it is a jump, and bpf_is_state_visited()
decides where to place a checkpoint based on how many jumps it has
seen. One extra jump per shift pair re-times that heuristic and moves
the checkpoints of an enclosing loop, which can lose state pruning.
For example, clear_global_array_list() in the no_alu32 flavour of the
linked_list selftest calls the always_inline clear_list(), three
256 iteration loops in a row. With the shift pair, we have
processed 15384 insns (limit 1000000) max_states_per_insn 4 total_states 260 peak_states 125 mark_read 0
With a "goto pc+0" filler the checkpoint changes its location, we have
BPF program is too large. Processed 1000001 insn
processed 1000001 insns (limit 1000000) max_states_per_insn 4 total_states 19479 peak_states 148 mark_read 0
The duplicated mov keeps both the insn and the jump counts the same as
before the rewrite. It is idempotent, the second mov re-derives the
same bounds and finds the 32-bit link already in place.
With this, the llvm22 and llvm23 code verifies, R2 keeps its id across
the zero extension and the jump narrows down R1:
3: (bf) r2 = r1 ; R1=scalar(id=1,...) R2=scalar(id=1,...)
4: (07) r2 += -5 ; R2=scalar(id=1-5,smin=-5,smax=0xfffffffa)
5: (bc) w2 = w2 ; R2=scalar(id=1-5,smin=0,umax=0xffffffff)
6: (bc) w2 = w2 ; R2=scalar(id=1-5,smin=0,umax=0xffffffff)
7: (25) if r2 > 0x4 goto pc+5 ; R1=scalar(id=1,smin=5,smax=9,...)
; R2=scalar(id=1-5,smin=0,smax=4,...)
8: (bf) r2 = r10
9: (07) r2 += -5
10: (0f) r2 += r1 ; R2=fp(smin=0,smax=4)
11: (b7) r1 = 46 ; R1=46
12: (73) *(u8 *)(r2 -5) = r1 ; fp-8=ppppm???
The llvm21 sequence is rewritten as well, the rewrite is not tied to
BPF_ADD_CONST, so both its insns 4 and 5 become "w1 = w1". R1 carries
no id there, so the mov takes the ordinary zero extension path and ends
up with the same bounds the shift pair produced, the program verifies
as before.
Signed-off-by: Yonghong Song <yonghong.song@linux.dev>
---
kernel/bpf/verifier.c | 48 ++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 47 insertions(+), 1 deletion(-)
Changelog:
v1 -> v2:
- v1: https://lore.kernel.org/bpf/20260820013925.2515018-1-yonghong.song@linux.dev/
- Replace left/right unsigned 32bit ship with 32bit mov's. This is done before
main verification.
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 821b47ac75c5..e1de442801d9 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -15610,6 +15610,14 @@ static int adjust_scalar_min_max_vals(struct bpf_verifier_env *env,
return 0;
}
+static bool linked_base_fits_u32(const struct bpf_reg_state *reg)
+{
+ if (reg->id & BPF_ADD_CONST32)
+ return true;
+ return reg_smin(reg) >= (s64)reg->delta &&
+ reg_smax(reg) <= (s64)U32_MAX + (s64)reg->delta;
+}
+
/* Handles ALU ops other than BPF_END, BPF_NEG and BPF_MOV: computes new min/max
* and var_off.
*/
@@ -15878,7 +15886,12 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
insn->src_reg);
return -EACCES;
} else if (src_reg->type == SCALAR_VALUE) {
- if (insn->off == 0) {
+ if (insn->off == 0 && insn->src_reg == insn->dst_reg &&
+ (dst_reg->id & BPF_ADD_CONST) &&
+ linked_base_fits_u32(dst_reg)) {
+ dst_reg->id = (dst_reg->id & ~BPF_ADD_CONST64) |
+ BPF_ADD_CONST32;
+ } else if (insn->off == 0) {
bool is_src_reg_u32 = get_reg_width(src_reg) <= 32;
if (is_src_reg_u32)
@@ -19183,6 +19196,36 @@ static int check_and_resolve_insns(struct bpf_verifier_env *env)
return 0;
}
+static bool is_shift_by_32(const struct bpf_insn *insn, u8 op)
+{
+ return insn->code == (BPF_ALU64 | op | BPF_K) && insn->off == 0 && insn->imm == 32;
+}
+
+/* 'rX <<= 32; rX >>= 32' => 'wX = wX; wX = wX' */
+static void bpf_rewrite_zext_shifts(struct bpf_verifier_env *env)
+{
+ struct bpf_insn *insn = env->prog->insnsi;
+ int i;
+
+ for (i = 0; i < env->prog->len - 1; i++) {
+ if (!is_shift_by_32(&insn[i], BPF_LSH) ||
+ !is_shift_by_32(&insn[i + 1], BPF_RSH) ||
+ insn[i].dst_reg != insn[i + 1].dst_reg)
+ continue;
+ if (bpf_is_jmp_point(env, i + 1))
+ continue;
+ /*
+ * The second mov is redundant, but a nop (goto pc+0) cannot
+ * be used here. A 'goto pc+0' is a jump, and the extra jump
+ * may change where checkpoints are placed, see
+ * bpf_is_state_visited(). The second mov avoids that.
+ */
+ insn[i] = BPF_MOV32_REG(insn[i].dst_reg, insn[i].dst_reg);
+ insn[i + 1] = insn[i];
+ i++;
+ }
+}
+
/* drop refcnt of maps used by the rejected program */
static void release_maps(struct bpf_verifier_env *env)
{
@@ -21149,6 +21192,9 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr,
if (ret < 0)
goto skip_full_check;
+ /* Needs the jump point marks left by bpf_check_cfg(). */
+ bpf_rewrite_zext_shifts(env);
+
ret = bpf_compute_postorder(env);
if (ret < 0)
goto skip_full_check;
--
2.53.0-Meta
^ permalink raw reply related [flat|nested] 5+ messages in thread* Re: [PATCH bpf-next v2] bpf: Rewrite "rX <<= 32; rX >>= 32" into "wX = wX" to keep linked scalars
2026-08-21 4:28 [PATCH bpf-next v2] bpf: Rewrite "rX <<= 32; rX >>= 32" into "wX = wX" to keep linked scalars Yonghong Song
@ 2026-08-21 5:30 ` bot+bpf-ci
2026-08-21 17:36 ` Eduard Zingerman
1 sibling, 0 replies; 5+ messages in thread
From: bot+bpf-ci @ 2026-08-21 5:30 UTC (permalink / raw)
To: yonghong.song, bpf
Cc: ast, andrii, daniel, eddyz87, kernel-team, ast, andrii, daniel,
martin.lau, eddyz87, yonghong.song, clm, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 5268 bytes --]
> For the following test in
> tools/testing/selftests/bpf/progs/verifier_linked_scalars.c:
>
> void alu32_negative_offset(void)
> {
> volatile char path[5];
> volatile int offset = bpf_get_prandom_u32();
> int off = offset;
>
> if (off >= 5 && off < 10)
> path[off - 5] = '.';
>
> /* So compiler doesn't say: error: variable 'path' set but not used */
> __sink(path[0]);
> }
>
> Without alu32 (-mcpu=v2), the test
> verifier_linked_scalars/alu32_negative_offset will fail with llvm22 and
> llvm23 like below.
>
> 3: (bf) r2 = r1 ; R1=scalar(id=1,...) R2=scalar(id=1,...)
> 4: (07) r2 += -5 ; R2=scalar(id=1-5,smin=-5,smax=0xfffffffa)
> 5: (67) r2 <<= 32 ; R2=scalar(smax=0x7fffffff00000000,...)
> 6: (77) r2 >>= 32 ; R2=scalar(smin=0,umax=0xffffffff,...)
> 7: (25) if r2 > 0x4 goto pc+5 ; R2=scalar(smin=0,smax=umax=4,...)
> 8: (bf) r2 = r10
> 9: (07) r2 += -5
> 10: (0f) r2 += r1 ; R1=scalar(id=1,smin=0,umax=0xffffffff)
> ; R2=fp(smin=-5,smax=0xfffffffa)
> 11: (b7) r1 = 46 ; R1=46
> 12: (73) *(u8 *)(r2 -5) = r1
> invalid unbounded variable-offset write to stack R2
>
> R1 is never narrowed down, so the address stays unbounded and the store
> is rejected.
>
> The test is okay for llvm21 with -mcpu=v2, see below:
>
> 3: (07) r1 += -5 ; R1=scalar(smin=-5,smax=0xfffffffa)
> 4: (67) r1 <<= 32 ; R1=scalar(smax=0x7fffffff00000000,...)
> 5: (77) r1 >>= 32 ; R1=scalar(smin=0,umax=0xffffffff,...)
> 6: (25) if r1 > 0x4 goto pc+5 ; R1=scalar(smin=0,smax=umax=4,...)
> 7: (bf) r2 = r10
> 8: (07) r2 += -5
> 9: (0f) r2 += r1 ; R2=fp(smin=-5,smax=-1)
> 10: (b7) r1 = 46 ; R1=46
> 11: (73) *(u8 *)(r2 +0) = r1 ; fp-8=ppppm???
>
> To fix the test issue with llvm22 and llvm23, note that the shift pair
> computes zext32(base + delta), which is what "wX = wX" computes as well
> and which is exactly the relation BPF_ADD_CONST32 describes. So rewrite
> the pair into "wX = wX" and let the mov turn the 64-bit link into a
> 32-bit one, which makes the -mcpu=v2 sequence track like an alu32 one.
>
> The rewrite has to keep the program length, so the second shift becomes
> a second "wX = wX" rather than being removed. A "goto pc+0" nop looks
> like the obvious filler, but it is a jump, and bpf_is_state_visited()
> decides where to place a checkpoint based on how many jumps it has
> seen. One extra jump per shift pair re-times that heuristic and moves
> the checkpoints of an enclosing loop, which can lose state pruning.
>
> For example, clear_global_array_list() in the no_alu32 flavour of the
> linked_list selftest calls the always_inline clear_list(), three
> 256 iteration loops in a row. With the shift pair, we have
>
> processed 15384 insns (limit 1000000) max_states_per_insn 4 total_states 260 peak_states 125 mark_read 0
>
> With a "goto pc+0" filler the checkpoint changes its location, we have
>
> BPF program is too large. Processed 1000001 insn
> processed 1000001 insns (limit 1000000) max_states_per_insn 4 total_states 19479 peak_states 148 mark_read 0
>
> The duplicated mov keeps both the insn and the jump counts the same as
> before the rewrite. It is idempotent, the second mov re-derives the
> same bounds and finds the 32-bit link already in place.
>
> With this, the llvm22 and llvm23 code verifies, R2 keeps its id across
> the zero extension and the jump narrows down R1:
>
> 3: (bf) r2 = r1 ; R1=scalar(id=1,...) R2=scalar(id=1,...)
> 4: (07) r2 += -5 ; R2=scalar(id=1-5,smin=-5,smax=0xfffffffa)
> 5: (bc) w2 = w2 ; R2=scalar(id=1-5,smin=0,umax=0xffffffff)
> 6: (bc) w2 = w2 ; R2=scalar(id=1-5,smin=0,umax=0xffffffff)
> 7: (25) if r2 > 0x4 goto pc+5 ; R1=scalar(id=1,smin=5,smax=9,...)
> ; R2=scalar(id=1-5,smin=0,smax=4,...)
> 8: (bf) r2 = r10
> 9: (07) r2 += -5
> 10: (0f) r2 += r1 ; R2=fp(smin=0,smax=4)
> 11: (b7) r1 = 46 ; R1=46
> 12: (73) *(u8 *)(r2 -5) = r1 ; fp-8=ppppm???
>
> The llvm21 sequence is rewritten as well, the rewrite is not tied to
> BPF_ADD_CONST, so both its insns 4 and 5 become "w1 = w1". R1 carries
> no id there, so the mov takes the ordinary zero extension path and ends
> up with the same bounds the shift pair produced, the program verifies
> as before.
>
> Signed-off-by: Yonghong Song <yonghong.song@linux.dev>
This looks like a bug fix for the BPF_ADD_CONST linked register tracking
mechanism. The zero-extension shift pattern wasn't properly handling
registers with BPF_ADD_CONST relationships, causing the verifier to lose
track of register bounds and reject valid programs.
Should this include:
Fixes: 98d7ca374ba4 ("bpf: Track delta between \"linked\" registers.")
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/32447591531
^ permalink raw reply [flat|nested] 5+ messages in thread* Re: [PATCH bpf-next v2] bpf: Rewrite "rX <<= 32; rX >>= 32" into "wX = wX" to keep linked scalars
2026-08-21 4:28 [PATCH bpf-next v2] bpf: Rewrite "rX <<= 32; rX >>= 32" into "wX = wX" to keep linked scalars Yonghong Song
2026-08-21 5:30 ` bot+bpf-ci
@ 2026-08-21 17:36 ` Eduard Zingerman
2026-08-22 16:47 ` Yonghong Song
1 sibling, 1 reply; 5+ messages in thread
From: Eduard Zingerman @ 2026-08-21 17:36 UTC (permalink / raw)
To: Yonghong Song, bpf
Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann, kernel-team
On Thu, 2026-08-20 at 21:28 -0700, Yonghong Song wrote:
> For the following test in
> tools/testing/selftests/bpf/progs/verifier_linked_scalars.c:
>
> void alu32_negative_offset(void)
> {
> volatile char path[5];
> volatile int offset = bpf_get_prandom_u32();
> int off = offset;
>
> if (off >= 5 && off < 10)
> path[off - 5] = '.';
>
> /* So compiler doesn't say: error: variable 'path' set but not used */
> __sink(path[0]);
> }
>
> Without alu32 (-mcpu=v2), the test
> verifier_linked_scalars/alu32_negative_offset will fail with llvm22 and
> llvm23 like below.
>
> 3: (bf) r2 = r1 ; R1=scalar(id=1,...) R2=scalar(id=1,...)
Does r1 fit into 32-bit range at this point?
I assume it does, otherwise it won't be possible to infer information
about r1 range through zero extended r2.
> 4: (07) r2 += -5 ; R2=scalar(id=1-5,smin=-5,smax=0xfffffffa)
> 5: (67) r2 <<= 32 ; R2=scalar(smax=0x7fffffff00000000,...)
> 6: (77) r2 >>= 32 ; R2=scalar(smin=0,umax=0xffffffff,...)
> 7: (25) if r2 > 0x4 goto pc+5 ; R2=scalar(smin=0,smax=umax=4,...)
> 8: (bf) r2 = r10
> 9: (07) r2 += -5
> 10: (0f) r2 += r1 ; R1=scalar(id=1,smin=0,umax=0xffffffff)
> ; R2=fp(smin=-5,smax=0xfffffffa)
> 11: (b7) r1 = 46 ; R1=46
> 12: (73) *(u8 *)(r2 -5) = r1
> invalid unbounded variable-offset write to stack R2
>
> R1 is never narrowed down, so the address stays unbounded and the store
> is rejected.
...
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 821b47ac75c5..e1de442801d9 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -15610,6 +15610,14 @@ static int adjust_scalar_min_max_vals(struct bpf_verifier_env *env,
> return 0;
> }
>
> +static bool linked_base_fits_u32(const struct bpf_reg_state *reg)
> +{
> + if (reg->id & BPF_ADD_CONST32)
> + return true;
> + return reg_smin(reg) >= (s64)reg->delta &&
> + reg_smax(reg) <= (s64)U32_MAX + (s64)reg->delta;
> +}
> +
> /* Handles ALU ops other than BPF_END, BPF_NEG and BPF_MOV: computes new min/max
> * and var_off.
> */
> @@ -15878,7 +15886,12 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
> insn->src_reg);
> return -EACCES;
> } else if (src_reg->type == SCALAR_VALUE) {
> - if (insn->off == 0) {
> + if (insn->off == 0 && insn->src_reg == insn->dst_reg &&
> + (dst_reg->id & BPF_ADD_CONST) &&
> + linked_base_fits_u32(dst_reg)) {
> + dst_reg->id = (dst_reg->id & ~BPF_ADD_CONST64) |
> + BPF_ADD_CONST32;
This commit consists of two parts:
- a special case for wA = wA assignment
- a rewrite for `rA <<= 32; rA >>= 32;` pair
Could you please split it in two with separate selftest for each.
Also, could you please comment why the special case for `wA = wA` is necessary?
Is it because assign_scalar_id_before_mov() destroys the link:
static void assign_scalar_id_before_mov(struct bpf_verifier_env *env,
struct bpf_reg_state *src_reg)
...
if (src_reg->id & BPF_ADD_CONST)
clear_scalar_id(src_reg);
?
If that's the only reason, is it possible to extend existing wA = wB
logic instead of adding a special case?
Also note that this overlaps with Vineet's series [1].
Representing zero extension as a combination of BPF_ADD_CONST32 and
delta == 0 is a valid alternative for one of the patches there,
but it also handles the value reconstruction on sync.
[1] https://lore.kernel.org/bpf/20260814231945.3884596-1-vineet.gupta@linux.dev/
> + } else if (insn->off == 0) {
> bool is_src_reg_u32 = get_reg_width(src_reg) <= 32;
>
> if (is_src_reg_u32)
...
^ permalink raw reply [flat|nested] 5+ messages in thread* Re: [PATCH bpf-next v2] bpf: Rewrite "rX <<= 32; rX >>= 32" into "wX = wX" to keep linked scalars
2026-08-21 17:36 ` Eduard Zingerman
@ 2026-08-22 16:47 ` Yonghong Song
2026-08-24 18:29 ` Eduard Zingerman
0 siblings, 1 reply; 5+ messages in thread
From: Yonghong Song @ 2026-08-22 16:47 UTC (permalink / raw)
To: Eduard Zingerman, bpf
Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann, kernel-team
On 8/21/26 10:36 AM, Eduard Zingerman wrote:
> On Thu, 2026-08-20 at 21:28 -0700, Yonghong Song wrote:
>> For the following test in
>> tools/testing/selftests/bpf/progs/verifier_linked_scalars.c:
>>
>> void alu32_negative_offset(void)
>> {
>> volatile char path[5];
>> volatile int offset = bpf_get_prandom_u32();
>> int off = offset;
>>
>> if (off >= 5 && off < 10)
>> path[off - 5] = '.';
>>
>> /* So compiler doesn't say: error: variable 'path' set but not used */
>> __sink(path[0]);
>> }
>>
>> Without alu32 (-mcpu=v2), the test
>> verifier_linked_scalars/alu32_negative_offset will fail with llvm22 and
>> llvm23 like below.
>>
>> 3: (bf) r2 = r1 ; R1=scalar(id=1,...) R2=scalar(id=1,...)
> Does r1 fit into 32-bit range at this point?
> I assume it does, otherwise it won't be possible to infer information
> about r1 range through zero extended r2.
Yes, r1 is in 32-bit range.
>
>> 4: (07) r2 += -5 ; R2=scalar(id=1-5,smin=-5,smax=0xfffffffa)
>> 5: (67) r2 <<= 32 ; R2=scalar(smax=0x7fffffff00000000,...)
>> 6: (77) r2 >>= 32 ; R2=scalar(smin=0,umax=0xffffffff,...)
>> 7: (25) if r2 > 0x4 goto pc+5 ; R2=scalar(smin=0,smax=umax=4,...)
>> 8: (bf) r2 = r10
>> 9: (07) r2 += -5
>> 10: (0f) r2 += r1 ; R1=scalar(id=1,smin=0,umax=0xffffffff)
>> ; R2=fp(smin=-5,smax=0xfffffffa)
>> 11: (b7) r1 = 46 ; R1=46
>> 12: (73) *(u8 *)(r2 -5) = r1
>> invalid unbounded variable-offset write to stack R2
>>
>> R1 is never narrowed down, so the address stays unbounded and the store
>> is rejected.
> ...
>
>> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
>> index 821b47ac75c5..e1de442801d9 100644
>> --- a/kernel/bpf/verifier.c
>> +++ b/kernel/bpf/verifier.c
>> @@ -15610,6 +15610,14 @@ static int adjust_scalar_min_max_vals(struct bpf_verifier_env *env,
>> return 0;
>> }
>>
>> +static bool linked_base_fits_u32(const struct bpf_reg_state *reg)
>> +{
>> + if (reg->id & BPF_ADD_CONST32)
>> + return true;
>> + return reg_smin(reg) >= (s64)reg->delta &&
>> + reg_smax(reg) <= (s64)U32_MAX + (s64)reg->delta;
>> +}
>> +
>> /* Handles ALU ops other than BPF_END, BPF_NEG and BPF_MOV: computes new min/max
>> * and var_off.
>> */
>> @@ -15878,7 +15886,12 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
>> insn->src_reg);
>> return -EACCES;
>> } else if (src_reg->type == SCALAR_VALUE) {
>> - if (insn->off == 0) {
>> + if (insn->off == 0 && insn->src_reg == insn->dst_reg &&
>> + (dst_reg->id & BPF_ADD_CONST) &&
>> + linked_base_fits_u32(dst_reg)) {
>> + dst_reg->id = (dst_reg->id & ~BPF_ADD_CONST64) |
>> + BPF_ADD_CONST32;
> This commit consists of two parts:
> - a special case for wA = wA assignment
> - a rewrite for `rA <<= 32; rA >>= 32;` pair
>
> Could you please split it in two with separate selftest for each.
Let me explain a little bit more.
For these four insns
3: (bf) r2 = r1 ; R1=scalar(id=1,...) R2=scalar(id=1,...)
4: (07) r2 += -5 ; R2=scalar(id=1-5,smin=-5,smax=0xfffffffa)
5: (67) r2 <<= 32 ; R2=scalar(smax=0x7fffffff00000000,...)
6: (77) r2 >>= 32 ; R2=scalar(smin=0,umax=0xffffffff,...)
Eventually, the above insns will be converted to
3: (bf) r2 = r1 ; R1=scalar(id=1,...) R2=scalar(id=1,...)
4: (07) r2 += -5 ; R2=scalar(id=1-5,smin=-5,smax=0xfffffffa)
5: (bc) w2 = w2 ; R2=scalar(id=1-5,smin=0,umax=0xffffffff)
6: (bc) w2 = w2 ; R2=scalar(id=1-5,smin=0,umax=0xffffffff)
insn 5 and 6 are the ones to be changed.
Here:
5: (67) r2 <<= 32 ; R2=scalar(smax=0x7fffffff00000000,...)
6: (77) r2 >>= 32 ; R2=scalar(smin=0,umax=0xffffffff,...)
converted to
5: (bc) w2 = w2 ; R2=scalar(id=1-5,smin=0,umax=0xffffffff)
This insn
6: (bc) w2 = w2 ; R2=scalar(id=1-5,smin=0,umax=0xffffffff)
is not really necessary. I put it here to maintain existing control flow graph.
Previously for insn 6, I used "goto pc+0" which will be removed later.
But "goto pc+0" has some impact on verification as it added yet another jump.
See bpf_is_state_visited():
if (env->jmps_processed - env->prev_jmps_processed >= 2 &&
env->insn_processed - env->prev_insn_processed >= 8)
add_new_state = true;
Here "goto pc+0" may impact for some programs. I didn't investigate this
in detail but probably I should investigate and fix the root cause.
Alternatively, we can rebuild the control flow graph by removing insn 6.
I am just not sure whether this is worthwhile or not.
> Also, could you please comment why the special case for `wA = wA` is necessary?
The second wA = wA is not needed. We can remove it and rebuild the control flow
for it, just not sure whether this is worthwhile or not. Probably yes.
> Is it because assign_scalar_id_before_mov() destroys the link:
>
> static void assign_scalar_id_before_mov(struct bpf_verifier_env *env,
> struct bpf_reg_state *src_reg)
> ...
> if (src_reg->id & BPF_ADD_CONST)
> clear_scalar_id(src_reg);
>
> ?
>
> If that's the only reason, is it possible to extend existing wA = wB
> logic instead of adding a special case?
>
> Also note that this overlaps with Vineet's series [1].
> Representing zero extension as a combination of BPF_ADD_CONST32 and
> delta == 0 is a valid alternative for one of the patches there,
> but it also handles the value reconstruction on sync.
>
> [1] https://lore.kernel.org/bpf/20260814231945.3884596-1-vineet.gupta@linux.dev/
Thanks for the link. I see there are some change for BPF_ADD_CONST32.
I will wait after the above patch is settled.
>
>> + } else if (insn->off == 0) {
>> bool is_src_reg_u32 = get_reg_width(src_reg) <= 32;
>>
>> if (is_src_reg_u32)
> ...
^ permalink raw reply [flat|nested] 5+ messages in thread* Re: [PATCH bpf-next v2] bpf: Rewrite "rX <<= 32; rX >>= 32" into "wX = wX" to keep linked scalars
2026-08-22 16:47 ` Yonghong Song
@ 2026-08-24 18:29 ` Eduard Zingerman
0 siblings, 0 replies; 5+ messages in thread
From: Eduard Zingerman @ 2026-08-24 18:29 UTC (permalink / raw)
To: Yonghong Song, bpf
Cc: Alexei Starovoitov, Andrii Nakryiko, Daniel Borkmann, kernel-team
On Sat, 2026-08-22 at 09:47 -0700, Yonghong Song wrote:
...
> > > @@ -15878,7 +15886,12 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
> > > insn->src_reg);
> > > return -EACCES;
> > > } else if (src_reg->type == SCALAR_VALUE) {
> > > - if (insn->off == 0) {
> > > + if (insn->off == 0 && insn->src_reg == insn->dst_reg &&
> > > + (dst_reg->id & BPF_ADD_CONST) &&
> > > + linked_base_fits_u32(dst_reg)) {
> > > + dst_reg->id = (dst_reg->id & ~BPF_ADD_CONST64) |
> > > + BPF_ADD_CONST32;
> > This commit consists of two parts:
> > - a special case for wA = wA assignment
> > - a rewrite for `rA <<= 32; rA >>= 32;` pair
> >
> > Could you please split it in two with separate selftest for each.
>
> Let me explain a little bit more.
>
> For these four insns
>
> 3: (bf) r2 = r1 ; R1=scalar(id=1,...) R2=scalar(id=1,...)
> 4: (07) r2 += -5 ; R2=scalar(id=1-5,smin=-5,smax=0xfffffffa)
> 5: (67) r2 <<= 32 ; R2=scalar(smax=0x7fffffff00000000,...)
> 6: (77) r2 >>= 32 ; R2=scalar(smin=0,umax=0xffffffff,...)
>
> Eventually, the above insns will be converted to
>
> 3: (bf) r2 = r1 ; R1=scalar(id=1,...) R2=scalar(id=1,...)
> 4: (07) r2 += -5 ; R2=scalar(id=1-5,smin=-5,smax=0xfffffffa)
> 5: (bc) w2 = w2 ; R2=scalar(id=1-5,smin=0,umax=0xffffffff)
> 6: (bc) w2 = w2 ; R2=scalar(id=1-5,smin=0,umax=0xffffffff)
>
> insn 5 and 6 are the ones to be changed.
>
> Here:
>
> 5: (67) r2 <<= 32 ; R2=scalar(smax=0x7fffffff00000000,...)
> 6: (77) r2 >>= 32 ; R2=scalar(smin=0,umax=0xffffffff,...)
> converted to
> 5: (bc) w2 = w2 ; R2=scalar(id=1-5,smin=0,umax=0xffffffff)
>
> This insn
> 6: (bc) w2 = w2 ; R2=scalar(id=1-5,smin=0,umax=0xffffffff)
> is not really necessary. I put it here to maintain existing control flow graph.
> Previously for insn 6, I used "goto pc+0" which will be removed later.
> But "goto pc+0" has some impact on verification as it added yet another jump.
> See bpf_is_state_visited():
>
> if (env->jmps_processed - env->prev_jmps_processed >= 2 &&
> env->insn_processed - env->prev_insn_processed >= 8)
> add_new_state = true;
>
> Here "goto pc+0" may impact for some programs. I didn't investigate this
> in detail but probably I should investigate and fix the root cause.
>
> Alternatively, we can rebuild the control flow graph by removing insn 6.
> I am just not sure whether this is worthwhile or not.
>
> > Also, could you please comment why the special case for `wA = wA` is necessary?
>
> The second wA = wA is not needed. We can remove it and rebuild the control flow
> for it, just not sure whether this is worthwhile or not. Probably yes.
I understand why you insert `wA = wA` twice, the question is about a
special case for `wA = wA` in check_alu_op(), why would you need
additional handling there specifically?
> > Is it because assign_scalar_id_before_mov() destroys the link:
> >
> > static void assign_scalar_id_before_mov(struct bpf_verifier_env *env,
> > struct bpf_reg_state *src_reg)
> > ...
> > if (src_reg->id & BPF_ADD_CONST)
> > clear_scalar_id(src_reg);
> >
> > ?
> >
> > If that's the only reason, is it possible to extend existing wA = wB
> > logic instead of adding a special case?
^^^
The above part is still relevant for the question.
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-08-24 18:29 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-21 4:28 [PATCH bpf-next v2] bpf: Rewrite "rX <<= 32; rX >>= 32" into "wX = wX" to keep linked scalars Yonghong Song
2026-08-21 5:30 ` bot+bpf-ci
2026-08-21 17:36 ` Eduard Zingerman
2026-08-22 16:47 ` Yonghong Song
2026-08-24 18:29 ` Eduard Zingerman
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.