From: Eduard Zingerman <eddyz87@gmail.com>
To: Vineet Gupta <vineet.gupta@linux.dev>,
ast@kernel.org, daniel@iogearbox.net, andrii@kernel.org,
memxor@gmail.com
Cc: martin.lau@linux.dev, song@kernel.org, yonghong.song@linux.dev,
jolsa@kernel.org, emil@etsalapatis.com, ihor.solodrai@linux.dev,
john.fastabend@gmail.com, shuah@kernel.org, bpf@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org
Subject: Re: [PATCH bpf-next v3 06/12] bpf: Track low-32 scalar equality across sign-extending movs
Date: Fri, 30 Oct 2026 10:52:00 -0700 [thread overview]
Message-ID: <bec189f3ba6ff18c2a09f3cbc19bb73e4d3277cf.camel@gmail.com> (raw)
In-Reply-To: <20261020161907.2115379-7-vineet.gupta@linux.dev>
On Tue, 2026-10-20 at 09:19 -0700, Vineet Gupta wrote:
> A 32-bit sign extension shares the source's low 32 bits too, filling the
> high half from bit 31:
>
> r6 = ... /* full 64-bit unknown */
> r7 = (s32)r6 /* 32-bit sign-extending mov */
> if w6 == -1 goto ... /* taken: r6's low 32 bits are all ones */
> ... /* r7 is -1, not deduced today */
>
> No constant k makes dst == (u32)(src + k) a sign extension, so this
> needs a third kind, BPF_REG_LINK_SEXT, driven by sext_32_to_64() where
> the zero-extending arm uses zext_32_to_64(). The enum already had room,
> so bpf_reg_state stays 80 bytes.
>
> Both work from the base's 32-bit range, which is what a 32-bit compare
> narrows; coerce_reg_to_size_sx() cannot serve, as it reads smin/smax,
> which straddle after such a compare. tnum_sext() is the counterpart to
> tnum_cast(), keeping the known low bits where a tnum_range() over the new
> bounds would not.
>
> sext_32_to_64() resets the register's base/step line; the comment there
> gives the arithmetic. Note it resets unconditionally, where
> coerce_reg_to_size_sx() and coerce_subreg_to_size_sx() return early when
> the extension is a provable no-op and keep the line. That costs a little
> precision in the same case here, which is not worth an extra branch on a
> path that has just rebuilt the value from the base.
>
> Unlike the zero-extending arm, the source's delta comes along. Both
> base + d and (u32)(base + d) have low half (base + d), so dst is
> (s32)(base + d) whichever the source was:
>
> w7 = w6; /* BPF_REG_LINK_ZEXT, delta 0 */
> w7 += 5; /* BPF_REG_LINK_ZEXT, delta 5 */
> r8 = (s32)r7; /* BPF_REG_LINK_SEXT, delta 5 */
>
> keeps r8 tied to r6. sext_mov_inherits_delta pins it; without the
> inheritance the program is rejected. A self-mov can form a link here too,
> which is how a sign-extended int return lands.
>
> sync_linked_regs() needs no separate sext path: the differencing arm
> already copies the base and adds the delta difference, and which extension
> follows is just the member's kind. Where the base does fit 32 bits the
> reverse direction runs for both narrow kinds with the *zero* extension --
> the base is then below 2^32 and equal to the member's low half, whichever
> way the member filled its own high half.
The above paragraph is very hard to parse.
sync_linked_regs() does call sext_32_to_64().
Please re-read whatever comments and messages the machine generates.
For whatever reason the bias is towards a wall of text and complex
sentences. I do read those and it hurts. Asking my machine rewrite
your machine's text, such that I can understand it is very time consuming.
...
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index ce7016446936..dffcaa2e1392 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -3763,7 +3763,7 @@ static void assign_scalar_id_before_mov(struct bpf_verifier_env *env,
> * rY->id has special linked register already.
> * Cleared it, since multiple rX += const are not supported.
> */
> - if (src_reg->link_kind)
> + if (src_reg->link_kind && src_reg->link_kind != BPF_REG_LINK_SEXT)
Why is this rule relaxed only for SEXT?
Can it be relaxed for ZEXT/FULL as well?
> bpf_clear_scalar_id(src_reg);
> /*
> * Ensure that src_reg has a valid ID that will be copied to
> @@ -6057,6 +6057,25 @@ static void zext_32_to_64(struct bpf_reg_state *reg)
> reg_set_urange64(reg, reg_u32_min(reg), reg_u32_max(reg));
> }
>
> +/*
> + * The sign-extending counterpart. Signed bounds carry over directly because
> + * sign extension is monotonic over the signed 32-bit range.
> + */
> +static void sext_32_to_64(struct bpf_reg_state *reg)
> +{
> + /*
> + * See comment at reg_step_check_unsigned(). Sign extension subtracts
> + * 2^32 from every value with bit 31 set, which preserves a residue
> + * mod step only when step divides 2^32, so the line cannot be kept.
> + * The zero-extending twin calls reg_step_check_unsigned(reg, 32) for
> + * the same reason; the unsigned form does not apply here because the
> + * result is not the low 32 bits but the low 32 bits moved.
> + */
> + reg_step_reset(reg);
> + reg->var_off = tnum_sext(reg->var_off, 4);
> + reg_set_srange64(reg, reg_s32_min(reg), reg_s32_max(reg));
> +}
> +
Please use coerce_reg_to_size_sx(). You can improve it by doing:
reg->var_off = tnum_sext(reg->var_off, 4);
reg_set_srange64(reg, reg_s32_min(reg), reg_s32_max(reg));
> /* truncate register to smaller size (in bytes)
> * must be called with size < BPF_REG_SIZE
> */
> @@ -17544,12 +17563,29 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
> return -EACCES;
> } else if (src_reg->type == SCALAR_VALUE) {
> bool no_sext;
> + /*
> + * A 32-bit sign extension keeps the low 32
> + * bits, so dst shares them with whatever src
> + * shares them with. Both base + d and
> + * (u32)(base + d) have low half (base + d), so
> + * dst is (s32)(base + d) either way: inherit
> + * src's id and delta and record
> + * BPF_REG_LINK_SEXT.
> + */
> + bool subreg_link =
> + (insn->off >> 3) == 4 &&
^^^^^^^^^^^^^^^^^^^^^
please make this a bool variable is_sext32
> + (src_reg != dst_reg || src_reg->id);
>
> no_sext = reg_umax(src_reg) < (1ULL << (insn->off - 1));
> if (no_sext)
> assign_scalar_id_before_mov(env, src_reg);
> + else if (subreg_link && !src_reg->id &&
> + !tnum_is_const(src_reg->var_off))
> + src_reg->id = ++env->id_gen;
I don't like how this code sidesteps assign_scalar_id_before_mov().
> *dst_reg = *src_reg;
> - if (!no_sext)
> + if (!no_sext && subreg_link && src_reg->id)
> + dst_reg->link_kind = BPF_REG_LINK_SEXT;
> + else if (!no_sext)
> bpf_clear_scalar_id(dst_reg);
> coerce_reg_to_size_sx(dst_reg, insn->off >> 3);
> } else {
> @@ -18485,17 +18521,29 @@ static void sync_linked_regs(struct bpf_verifier_env *env, struct bpf_verifier_s
> if (reg->id != known_reg->id)
> continue;
> /*
> - * A zero-extending link, spelled BPF_REG_LINK_ZEXT: the
> - * relation reg == (u32)(base + delta) does not invert once the
> - * base is wider than 32 bits, so the reverse direction is
> - * skipped.
> + * The reverse direction does not work for a narrow known_reg:
> + * it knows only its low 32 bits, which say nothing about a
> + * wider reg's high half. Neither (u32)(base + delta) nor
> + * (s32)(base + delta) inverts once the base is wider than 32
> + * bits.
Why is this "reverse direction"? what's "inverts"?
> */
> - if (known_reg->link_kind == BPF_REG_LINK_ZEXT &&
> + if ((known_reg->link_kind == BPF_REG_LINK_ZEXT ||
> + known_reg->link_kind == BPF_REG_LINK_SEXT) &&
> + reg->link_kind != BPF_REG_LINK_ZEXT &&
> + reg->link_kind != BPF_REG_LINK_SEXT &&
On a second thought, it appears it would make sense to collapse FULL
and NONE into the same category. E.g. the above condition would become
known_reg->link_kind != FULL && reg->link_kind == FULL,
much clearer.
> get_reg_width(reg) > 32)
> continue;
> /*
> - * Skip mixed 32/64-bit links: the delta relationship doesn't
> - * hold across different ALU widths.
> + * Skip a pair of differing kinds. Where BPF_REG_LINK_FULL meets
> + * a 32-bit kind the delta relationship genuinely does not hold:
> + * the deltas were added at different ALU widths.
> + * BPF_REG_LINK_ZEXT against BPF_REG_LINK_SEXT is not that case
> + * -- both are base + delta in the low 32 bits, so narrowing one
> + * really does narrow the other -- but that deduction is
> + * declined on purpose. It makes an int loop counter a fresh
> + * constant on every iteration, so no two iterations prune
> + * against each other and the program runs to the instruction
> + * limit. See sext_no_sync_from_zext_sibling.
> */
> if (reg->link_kind && known_reg->link_kind &&
> reg->link_kind != known_reg->link_kind)
Does this check skips propagation in the following case?
w7 = w6 ; r6={NONE,0}, r7={ZEXT,0}
r6 += 1 ; r6={FULL,1}
if w6 != 5 goto ...
known_reg is r6 in this case, so:
ZEXT && FULL && ZEXT != FULL -> true, continue is hit.
...
next prev parent reply other threads:[~2026-10-30 17:52 UTC|newest]
Thread overview: 29+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-20 16:18 [PATCH bpf-next v3 00/12] bpf: track scalar equality across the low 32 bits Vineet Gupta
2026-10-20 16:18 ` [PATCH bpf-next v3 01/12] bpf: Keep the 32-bit range across a sign extension that cannot change it Vineet Gupta
2026-10-29 22:30 ` Eduard Zingerman
2026-10-20 16:18 ` [PATCH bpf-next v3 02/12] selftests/bpf: Cover sign extensions that cannot change the range Vineet Gupta
2026-10-20 16:18 ` [PATCH bpf-next v3 03/12] bpf: Prepare linked scalars for relations narrower than 64 bits Vineet Gupta
2026-10-20 17:08 ` bot+bpf-ci
2026-10-26 22:52 ` Vineet Gupta
2026-10-29 22:51 ` Eduard Zingerman
2026-10-20 16:18 ` [PATCH bpf-next v3 04/12] bpf: Track low-32 scalar equality across zero-extending movs Vineet Gupta
2026-10-20 17:09 ` bot+bpf-ci
2026-10-26 20:30 ` Vineet Gupta
2026-10-29 23:59 ` Eduard Zingerman
2026-10-20 16:19 ` [PATCH bpf-next v3 05/12] selftests/bpf: Cover the low-32 link for " Vineet Gupta
2026-10-20 16:53 ` bot+bpf-ci
2026-10-26 18:36 ` Vineet Gupta
2026-10-20 16:19 ` [PATCH bpf-next v3 06/12] bpf: Track low-32 scalar equality across sign-extending movs Vineet Gupta
2026-10-30 17:52 ` Eduard Zingerman [this message]
2026-10-20 16:19 ` [PATCH bpf-next v3 07/12] selftests/bpf: Cover the low-32 link for " Vineet Gupta
2026-10-20 16:19 ` [PATCH bpf-next v3 08/12] bpf: Recover the low 32 bits on the reverse link direction Vineet Gupta
2026-10-20 16:53 ` bot+bpf-ci
2026-10-26 19:57 ` Vineet Gupta
2026-10-20 16:19 ` [PATCH bpf-next v3 09/12] bpf: Track low-32 scalar equality across narrowing stack fills Vineet Gupta
2026-10-30 18:27 ` Eduard Zingerman
2026-10-20 16:19 ` [PATCH bpf-next v3 10/12] selftests/bpf: Cover the low-32 link for " Vineet Gupta
2026-10-20 16:52 ` bot+bpf-ci
2026-10-26 20:21 ` Vineet Gupta
2026-10-20 16:19 ` [PATCH bpf-next v3 11/12] [do not merge] bpf: Track low-32 scalar equality across narrowing stack spills Vineet Gupta
2026-10-30 18:33 ` Eduard Zingerman
2026-10-20 16:19 ` [PATCH bpf-next v3 12/12] [do not merge] selftests/bpf: Cover the low-32 link for " Vineet Gupta
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=bec189f3ba6ff18c2a09f3cbc19bb73e4d3277cf.camel@gmail.com \
--to=eddyz87@gmail.com \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=emil@etsalapatis.com \
--cc=ihor.solodrai@linux.dev \
--cc=john.fastabend@gmail.com \
--cc=jolsa@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=martin.lau@linux.dev \
--cc=memxor@gmail.com \
--cc=shuah@kernel.org \
--cc=song@kernel.org \
--cc=vineet.gupta@linux.dev \
--cc=yonghong.song@linux.dev \
/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