Re: [PATCH bpf-next] bpf, mips: Add support for BPF_SDIV and BPF_SMOD in the mips JIT

Philippe Mathieu-Daudé <[email protected]> Tue, 21 Jul 2026 16:50:21 +0200
Newsgroups org.kernel.vger.linux-mips,org.kernel.vger.bpf,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
Hi Nicholas,

On 16/7/26 23:10, Nicholas Dudar wrote:
> build_insn() picks the div/mod opcode by ALU width alone and never
> reads insn->off, so the mips32 and mips64 JITs do not implement signed
> BPF_SDIV and BPF_SMOD (off == 1). Signed div/mod get an unsigned
> quotient and remainder rather than the signed result the verifier and
> interpreter produce for negative operands. Neither JIT has a signed
> divide opcode or C helper.
> 
> Add the signed path alongside the unsigned one:
> 
> - uasm gains div/ddiv/mod/dmod emitters (and their R6 forms) next to
>    the existing divu/ddivu/modu/dmodu. This is shared MIPS assembler
>    infrastructure, so it lives with the unsigned emitters it mirrors,
>    not in the JIT.
> - The 32-bit and 64-bit JITs thread an is_signed flag (from
>    insn->off == 1) through emit_alu_r()/emit_alu_r64() and pick the
>    signed opcode. On 32-bit MIPS, ALU64 runs through a C helper, so
>    add div64_s64() and a signed remainder helper beside the existing
>    div64_u64()/jit_mod64().
> - valid_alu_i() drops the divide-by-power-of-two strength reduction
>    (/ (1<<n) -> >> n, % (1<<n) -> & mask) for the signed case. An
>    arithmetic shift rounds toward negative infinity and a mask drops
>    the sign, so the signed K-form falls through to the register path.
> 
> The K-form rejects imm == 0 at verify time, and MIPS div/ddiv, like
> divu/ddivu, does not trap on S32_MIN/-1 or S64_MIN/-1. The ALU32
> result is zero-extended after each div/mod, for the signed and
> unsigned cases.
> 
> Signed-off-by: Nicholas Dudar <[email protected]>
> Assisted-by: Claude:claude-opus-4-8
> ---
>   arch/mips/include/asm/uasm.h   |  6 ++++
>   arch/mips/mm/uasm-mips.c       | 10 ++++++
>   arch/mips/mm/uasm.c            | 15 ++++++---
>   arch/mips/net/bpf_jit_comp.c   | 47 ++++++++++++++++++++++------
>   arch/mips/net/bpf_jit_comp.h   |  4 +--
>   arch/mips/net/bpf_jit_comp32.c | 28 +++++++++++------
>   arch/mips/net/bpf_jit_comp64.c | 57 ++++++++++++++++++++++------------
>   7 files changed, 122 insertions(+), 45 deletions(-)


> diff --git a/arch/mips/net/bpf_jit_comp.c b/arch/mips/net/bpf_jit_comp.c
> index 6ee4abe6a1f7..504daea4aac2 100644
> --- a/arch/mips/net/bpf_jit_comp.c
> +++ b/arch/mips/net/bpf_jit_comp.c
> @@ -208,7 +208,7 @@ void emit_mov_r(struct jit_context *ctx, u8 dst, u8 src)
>   }
>   
>   /* Validate ALU immediate range */
> -bool valid_alu_i(u8 op, s32 imm)
> +bool valid_alu_i(u8 op, s32 imm, bool is_signed)
>   {
>   	switch (BPF_OP(op)) {
>   	case BPF_NEG:
> @@ -237,6 +237,15 @@ bool valid_alu_i(u8 op, s32 imm)
>   		return imm == 0 || (imm > 0 && is_power_of_2(imm));
>   	case BPF_DIV:
>   	case BPF_MOD:
> +		/*
> +		 * The rewrite to a shift/mask below is an unsigned-only
> +		 * optimization: arithmetic right shift rounds toward
> +		 * negative infinity rather than zero, and masking does not
> +		 * reproduce a negative dividend's sign. Force the signed
> +		 * case through the general register path instead.
> +		 */
> +		if (is_signed)
> +			return false;
>   		/* imm must be an 17-bit power of two */
>   		return (u32)imm <= 0x10000 && is_power_of_2((u32)imm);
>   	}
> @@ -339,7 +348,7 @@ void emit_alu_i(struct jit_context *ctx, u8 dst, s32 imm, u8 op)
>   }
>   
>   /* ALU register operation (32-bit) */
> -void emit_alu_r(struct jit_context *ctx, u8 dst, u8 src, u8 op)
> +void emit_alu_r(struct jit_context *ctx, u8 dst, u8 src, u8 op, bool is_signed)
>   {
>   	switch (BPF_OP(op)) {
>   	/* dst = dst & src */
> @@ -385,20 +394,38 @@ void emit_alu_r(struct jit_context *ctx, u8 dst, u8 src, u8 op)
>   		break;
>   	/* dst = dst / src */
>   	case BPF_DIV:
> -		if (cpu_has_mips32r6) {
> -			emit(ctx, divu_r6, dst, dst, src);
> +		if (is_signed) {
> +			if (cpu_has_mips32r6) {
> +				emit(ctx, div_r6, dst, dst, src);
> +			} else {
> +				emit(ctx, div, dst, src);
> +				emit(ctx, mflo, dst);
> +			}
>   		} else {
> -			emit(ctx, divu, dst, src);
> -			emit(ctx, mflo, dst);
> +			if (cpu_has_mips32r6) {
> +				emit(ctx, divu_r6, dst, dst, src);
> +			} else {
> +				emit(ctx, divu, dst, src);
> +				emit(ctx, mflo, dst);
> +			}
>   		}
>   		break;
>   	/* dst = dst % src */
>   	case BPF_MOD:
> -		if (cpu_has_mips32r6) {
> -			emit(ctx, modu, dst, dst, src);
> +		if (is_signed) {
> +			if (cpu_has_mips32r6) {
> +				emit(ctx, mod, dst, dst, src);
> +			} else {
> +				emit(ctx, div, dst, src);
> +				emit(ctx, mfhi, dst);
> +			}
>   		} else {
> -			emit(ctx, divu, dst, src);
> -			emit(ctx, mfhi, dst);
> +			if (cpu_has_mips32r6) {
> +				emit(ctx, modu, dst, dst, src);
> +			} else {
> +				emit(ctx, divu, dst, src);
> +				emit(ctx, mfhi, dst);
> +			}
>   		}
>   		break;
>   	}
Your patch would be clearer if you extract a pair of emit_div()
and emit_mod() helpers in a preliminary patch, then include the
32r6 support (this patch).

My 2 cents anyway.

Regards,

Phil.