All of lore.kernel.org
 help / color / mirror / Atom feed
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: [RFC bpf-next 5/6] bpf: support low-32 subreg scalar linking for sign-extending movs
Date: Tue, 18 Aug 2026 23:18:59 -0700	[thread overview]
Message-ID: <d43e9adedfeced52ccf5eafea9f2aec01b79f413.camel@gmail.com> (raw)
In-Reply-To: <20260814231945.3884596-6-vineet.gupta@linux.dev>

On Fri, 2026-08-14 at 16:19 -0700, Vineet Gupta wrote:

...

> diff --git a/kernel/bpf/states.c b/kernel/bpf/states.c
> index ef71999c4695..6aaedde6e9d1 100644
> --- a/kernel/bpf/states.c
> +++ b/kernel/bpf/states.c

...

> @@ -570,9 +572,18 @@ static bool regsafe(struct bpf_verifier_env *env, struct bpf_reg_state *rold,
>  		 * on a path that predates this series, which is a pruning change
>  		 * that wants measuring on its own; it is deliberately left
>  		 * alone here.
> +		 *
> +		 * Only demand a match when the old state carries a link at all.
> +		 * These flags are only ever set together with an ->id, so
> +		 * rold->id == 0 implies none is set, and the only case this
> +		 * admits is "old knows no low-32 relationship, cur does" -- cur
> +		 * is then strictly more constrained than old, which is the safe
> +		 * direction for pruning. The reverse is still rejected. Without
> +		 * this a register that first acquires a link inside a loop would
> +		 * never match its pre-loop state and pruning would not converge.
>  		 */
>  		if (rold->id &&
> -		    (rold->flags & BPF_FLAG_SUBREG_ZEXT) != (rcur->flags & BPF_FLAG_SUBREG_ZEXT))
> +		    (rold->flags & BPF_FLAG_SUBREG) != (rcur->flags & BPF_FLAG_SUBREG))
>  			return false;

Same comment as for ZEXT patch.

>  
>  		if (env->explore_alu_limits) {
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 8a802d49d0a4..45cb67dc3999 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -14976,6 +14976,8 @@ static int adjust_reg_min_max_vals(struct bpf_verifier_env *env,
>  	return 0;
>  }
>  
> +static void reconstruct_sext32(struct bpf_reg_state *reg, struct bpf_reg_state *src);

Nit: is it possible to avoid forward declaration?

> +
>  /* check validity of 32-bit and 64-bit arithmetic operations */
>  static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
>  {
> @@ -15052,15 +15054,65 @@ 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) {
> +						int sz = insn->off >> 3;
>  						bool no_sext;
> +						bool subreg_link;
>  
>  						no_sext = reg_umax(src_reg) < (1ULL << (insn->off - 1));
> -						if (no_sext)
> +						/*
> +						 * When no_sext, dst == src exactly, so link them
> +						 * (existing behavior). When !no_sext for a 32-bit sign
> +						 * extension the low 32 bits are still identical (sext
> +						 * preserves them), so form a BPF_FLAG_SUBREG_SEXT
> +						 * link: a later narrowing of the low 32 bits
> +						 * propagates here, and sync_linked_regs() rebuilds
> +						 * the high half via reconstruct_sext32().
> +						 *
> +						 * An ADD_CONST-linked src is excluded for the same
> +						 * reason as in the zero-extending arm below:
> +						 * assign_scalar_id_before_mov() would clear its
> +						 * base+delta link, and a combined subreg+delta link
> +						 * isn't modeled anyway. Unlike that arm a self-mov is
> +						 * NOT excluded -- r0 = (s32)r0 is the case this is
> +						 * here for.
> +						 */
> +						subreg_link = (sz == 4) &&
> +							      !(src_reg->flags & BPF_FLAG_ADD_CONST);
> +
> +						if (no_sext || subreg_link)
>  							assign_scalar_id_before_mov(env, src_reg);
>  						*dst_reg = *src_reg;
> -						if (!no_sext)
> -							clear_scalar_id(dst_reg);
> -						coerce_reg_to_size_sx(dst_reg, insn->off >> 3);
> +						if (!no_sext) {
> +							if (subreg_link && src_reg->id) {
> +								/* ->id already copied above */
> +								dst_reg->flags = (dst_reg->flags & ~BPF_FLAG_SUBREG) |
> +										 BPF_FLAG_SUBREG_SEXT;
> +							} else {
> +								clear_scalar_id(dst_reg);
> +							}
> +						}
> +						/*
> +						 * coerce_reg_to_size_sx() falls back to the full sext
> +						 * range when smin/smax straddle the sign boundary (e.g.
> +						 * an errno-or-zero value clamped to [-4095, 0]). For a
> +						 * register tracked as the sign-extension of its low 32
> +						 * bits the high half IS that sign-extension, so rebuild
> +						 * the tighter 64-bit range from the low bounds, taken
> +						 * from a snapshot because coerce overwrites them.
> +						 *
> +						 * Gated on sz == 4, not on the flag alone: an (s8)/(s16)
> +						 * mov whose src is already SEXT-linked copies the flag
> +						 * across in the *dst_reg = *src_reg above, and a 32-bit
> +						 * reconstruction must not run for a narrower operation.
> +						 */
> +						if (sz == 4 && (dst_reg->flags & BPF_FLAG_SUBREG_SEXT)) {
> +							struct bpf_reg_state sext_src = *dst_reg;
> +
> +							coerce_reg_to_size_sx(dst_reg, sz);
> +							reconstruct_sext32(dst_reg, &sext_src);

It does not make sense to maintain two functions that do register sign
extension. If coerce_reg_to_size_sx() is not precise enough for the
32-bit case, then it should be adapted instead of special-cased.

> +						} else {
> +							coerce_reg_to_size_sx(dst_reg, sz);
> +						}
>  					} else {
>  						mark_reg_unknown(env, regs, insn->dst_reg);
>  					}
> @@ -15107,7 +15159,15 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
>  						if (!is_src_reg_u32) {
>  							if (wide_subreg_link && src_reg->id) {
>  								/* ->id already copied above */
> -								dst_reg->flags |= BPF_FLAG_SUBREG_ZEXT;
> +								/*
> +								 * Zero-extension: high bits are 0, not a
> +								 * sign-extension of the low field. Drop any
> +								 * SUBREG_SEXT copied from a sext-linked src
> +								 * so sync_linked_regs() rebuilds dst by
> +								 * zero-extension, not reconstruct_sext32().
> +								 */
> +								dst_reg->flags = (dst_reg->flags & ~BPF_FLAG_SUBREG) |
> +										 BPF_FLAG_SUBREG_ZEXT;

Nit: please find a way to reduce indentation (e.g. less if-nesting, or a utility function).

>  							} else {
>  								clear_scalar_id(dst_reg);
>  							}
> @@ -15961,6 +16021,32 @@ static void collect_linked_regs(struct bpf_verifier_env *env,
>  	}
>  }
>  
> +/*
> + * Set @reg to the sign-extension of the low 32 bits currently held by @src.
> + * A BPF_FLAG_SUBREG_SEXT-linked register came from a 32-bit sign
> + * extension (r0 = (s32)r0): it shares @src's low 32 bits and its high bits are
> + * the sign-extension of that low field. Only the value fields are written;
> + * @reg's linkage fields (id, delta, flags) are left intact by
> + * the caller (___mark_reg_known touches only var_off/r64/r32). Callers must
> + * ensure no ADD_CONST delta is involved (see sync_linked_regs()).
> + */
> +static void reconstruct_sext32(struct bpf_reg_state *reg, struct bpf_reg_state *src)

Nit: let's rename src -> known_reg, to make reading sync_linked_regs() simpler.

> +{
> +	s32 s32min = reg_s32_min(src);
> +	s32 s32max = reg_s32_max(src);
> +
> +	if (s32min == s32max) {
> +		/* Low 32 bits are constant -> the whole value is the sext constant. */
> +		___mark_reg_known(reg, (u64)(s64)s32min);
> +	} else {
> +		/* Sign-extension is monotonic over the signed-32 range. */
> +		reg_set_srange64(reg, (s64)s32min, (s64)s32max);
> +		reg_set_srange32(reg, s32min, s32max);
> +		reg->var_off = tnum_range((u64)(s64)s32min, (u64)(s64)s32max);

Note that known lower 32-bits of the known_reg->var_off are lost,
we might benefit from adding a dedicated tnum_sext().

> +		reg_bounds_sync(reg);
> +	}
> +}
> +
>  /* For all R in linked_regs, copy known_reg range into R
>   * if R->id == known_reg->id.
>   */
> @@ -15984,17 +16070,21 @@ static void sync_linked_regs(struct bpf_verifier_env *env, struct bpf_verifier_s
>  		 * A low-32 linked register shares only the base's low 32 bits;
>  		 * the flag says how its high bits are derived. For
>  		 * BPF_FLAG_SUBREG_ZEXT they are zero (32-bit zero-extending mov).
> +		 * For BPF_FLAG_SUBREG_SEXT they are the sign-extension of the low
> +		 * field (32-bit sign extension).
>  		 * Rebuild it from known_reg's low 32 bits accordingly, but only
>  		 * when neither side carries an ADD_CONST delta -- with a delta
>  		 * the low bits differ from the base by that delta and the combined
>  		 * subreg+ADD_CONST reconstruction isn't modeled here, so leave reg
>  		 * unchanged (sound, just less precise).
>  		 */
> -		if (reg->flags & BPF_FLAG_SUBREG_ZEXT) {
> +		if (reg->flags & BPF_FLAG_SUBREG) {
>  			if (!((reg->flags | known_reg->flags) & BPF_FLAG_ADD_CONST)) {
> -				{
> +				if (reg->flags & BPF_FLAG_SUBREG_SEXT) {
> +					reconstruct_sext32(reg, known_reg);
> +				} else {

Let's move this branch to a dedicated utility function as well.

>  					u32 saved_id = reg->id;
> -					u8 saved_subreg = reg->flags & BPF_FLAG_SUBREG_ZEXT;
> +					u8 saved_subreg = reg->flags & BPF_FLAG_SUBREG;
>  
>  					/*
>  					 * reg = zext32(known_reg): its low 32 bits come from

...

  reply	other threads:[~2026-08-19  6:19 UTC|newest]

Thread overview: 17+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-14 23:19 [RFC bpf-next 0/6] bpf: track scalar equality across the low 32 bits Vineet Gupta
2026-08-14 23:19 ` [RFC bpf-next 1/6] bpf: turn bpf_reg_state->precise into a flags field [NFC] Vineet Gupta
2026-08-18 21:38   ` Eduard Zingerman
2026-08-14 23:19 ` [RFC bpf-next 2/6] bpf: move the linked-scalar flags into bpf_reg_state->flags [NFC] Vineet Gupta
2026-08-14 23:34   ` sashiko-bot
2026-08-18 22:51   ` Eduard Zingerman
2026-08-14 23:19 ` [RFC bpf-next 3/6] bpf: support low-32 subreg scalar linking for zero-extending movs Vineet Gupta
2026-08-19  3:39   ` Eduard Zingerman
2026-08-19  4:07   ` Eduard Zingerman
2026-08-14 23:19 ` [RFC bpf-next 4/6] selftests/bpf: cover low-32 subreg-equal link " Vineet Gupta
2026-08-14 23:27   ` sashiko-bot
2026-08-19  5:05   ` Eduard Zingerman
2026-08-14 23:19 ` [RFC bpf-next 5/6] bpf: support low-32 subreg scalar linking for sign-extending movs Vineet Gupta
2026-08-19  6:18   ` Eduard Zingerman [this message]
2026-08-14 23:19 ` [RFC bpf-next 6/6] selftests/bpf: cover 32-bit sign-extension low-32 links Vineet Gupta
2026-08-14 23:27   ` sashiko-bot
2026-08-19  4:35 ` [RFC bpf-next 0/6] bpf: track scalar equality across the low 32 bits Eduard Zingerman

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=d43e9adedfeced52ccf5eafea9f2aec01b79f413.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 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.