From 86187bd3cd5a15b3f5b8023ef7123abc919c9bfe Mon Sep 17 00:00:00 2001 From: Nicholas Dudar Date: Sat, 12 Sep 2026 00:12:19 -0400 Subject: [PATCH 1/2] bpf, mips: Factor register moves into helpers Factor ALU32 and ALU64 register moves into backend-local helpers, keeping register-pair handling and zero extension with the move. This prepares for decoding the MOVSX width in those helpers. Keep the verifier-inserted zero-extension marker on its existing path. No functional change intended. Signed-off-by: Nicholas Dudar Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20260912041220.444715-2-main.kalliope@gmail.com --- arch/mips/net/bpf_jit_comp32.c | 22 ++++++++++++++++++---- arch/mips/net/bpf_jit_comp64.c | 18 +++++++++++++++--- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/arch/mips/net/bpf_jit_comp32.c b/arch/mips/net/bpf_jit_comp32.c index 6619182b5ef3..6d603d9eeb1a 100644 --- a/arch/mips/net/bpf_jit_comp32.c +++ b/arch/mips/net/bpf_jit_comp32.c @@ -190,6 +190,22 @@ static void emit_zext_ver(struct jit_context *ctx, const u8 dst[]) } } +/* Register move operation (32-bit) */ +static void emit_mov_r32(struct jit_context *ctx, const u8 dst[], + const u8 src[]) +{ + emit_mov_r(ctx, lo(dst), lo(src)); + emit_zext_ver(ctx, dst); +} + +/* Register move operation (64-bit) */ +static void emit_mov_r64(struct jit_context *ctx, const u8 dst[], + const u8 src[]) +{ + emit_mov_r(ctx, lo(dst), lo(src)); + emit_mov_r(ctx, hi(dst), hi(src)); +} + /* Load delay slot, if ISA mandates it */ static void emit_load_delay(struct jit_context *ctx) { @@ -1494,8 +1510,7 @@ int build_insn(const struct bpf_insn *insn, struct jit_context *ctx) /* Special mov32 for zext */ emit_mov_i(ctx, hi(dst), 0); } else { - emit_mov_r(ctx, lo(dst), lo(src)); - emit_zext_ver(ctx, dst); + emit_mov_r32(ctx, dst, src); } break; /* dst = -dst */ @@ -1564,8 +1579,7 @@ int build_insn(const struct bpf_insn *insn, struct jit_context *ctx) break; /* dst = src (64-bit) */ case BPF_ALU64 | BPF_MOV | BPF_X: - emit_mov_r(ctx, lo(dst), lo(src)); - emit_mov_r(ctx, hi(dst), hi(src)); + emit_mov_r64(ctx, dst, src); break; /* dst = -dst (64-bit) */ case BPF_ALU64 | BPF_NEG: diff --git a/arch/mips/net/bpf_jit_comp64.c b/arch/mips/net/bpf_jit_comp64.c index db027c2d9221..92df2eba1390 100644 --- a/arch/mips/net/bpf_jit_comp64.c +++ b/arch/mips/net/bpf_jit_comp64.c @@ -120,6 +120,19 @@ static void emit_zext_ver(struct jit_context *ctx, u8 dst) emit_zext(ctx, dst); } +/* Register move operation (32-bit) */ +static void emit_mov_r32(struct jit_context *ctx, u8 dst, u8 src) +{ + emit_mov_r(ctx, dst, src); + emit_zext_ver(ctx, dst); +} + +/* Register move operation (64-bit) */ +static void emit_mov_r64(struct jit_context *ctx, u8 dst, u8 src) +{ + emit_mov_r(ctx, dst, src); +} + /* dst = imm (64-bit) */ static void emit_mov_i64(struct jit_context *ctx, u8 dst, u64 imm64) { @@ -681,8 +694,7 @@ int build_insn(const struct bpf_insn *insn, struct jit_context *ctx) /* Special mov32 for zext */ emit_zext(ctx, dst); } else { - emit_mov_r(ctx, dst, src); - emit_zext_ver(ctx, dst); + emit_mov_r32(ctx, dst, src); } break; /* dst = -dst */ @@ -767,7 +779,7 @@ int build_insn(const struct bpf_insn *insn, struct jit_context *ctx) break; /* dst = src (64-bit) */ case BPF_ALU64 | BPF_MOV | BPF_X: - emit_mov_r(ctx, dst, src); + emit_mov_r64(ctx, dst, src); break; /* dst = -dst (64-bit) */ case BPF_ALU64 | BPF_NEG: From 07ebb8c386d384d69e6f2c17304f9cb381d4e55a Mon Sep 17 00:00:00 2001 From: Nicholas Dudar Date: Sat, 12 Sep 2026 00:12:20 -0400 Subject: [PATCH 2/2] bpf, mips: Add support for BPF_MOVSX in the JITs Both MIPS JITs ignore insn->off when lowering register MOV instructions, so BPF_MOVSX is emitted as an ordinary move. Since build_insn() accepts it, affected programs are silently miscompiled instead of falling back to the interpreter. Decode the MOVSX width from insn->off in the register-move helpers. On MIPS32, propagate the sign into the high word for ALU64 while preserving the existing ALU32 zero-extension handling. Use seb/seh on MIPS32 R2-or-newer CPUs and shift pairs otherwise. On MIPS64, seb/seh require a sign-extended 32-bit source. Normalizing an arbitrary BPF register first would cost another instruction, so retain the doubleword shift pairs. Signed-off-by: Nicholas Dudar Signed-off-by: Alexei Starovoitov Link: https://patch.msgid.link/20260912041220.444715-3-main.kalliope@gmail.com --- arch/mips/net/bpf_jit_comp32.c | 67 +++++++++++++++++++++++++++++----- arch/mips/net/bpf_jit_comp64.c | 55 ++++++++++++++++++++++++---- 2 files changed, 104 insertions(+), 18 deletions(-) diff --git a/arch/mips/net/bpf_jit_comp32.c b/arch/mips/net/bpf_jit_comp32.c index 6d603d9eeb1a..8f1ab851863f 100644 --- a/arch/mips/net/bpf_jit_comp32.c +++ b/arch/mips/net/bpf_jit_comp32.c @@ -190,20 +190,67 @@ static void emit_zext_ver(struct jit_context *ctx, const u8 dst[]) } } -/* Register move operation (32-bit) */ -static void emit_mov_r32(struct jit_context *ctx, const u8 dst[], - const u8 src[]) +/* Sign-extend an 8- or 16-bit field into a native register. */ +static void emit_movsx_r(struct jit_context *ctx, u8 dst, u8 src, s16 off) { - emit_mov_r(ctx, lo(dst), lo(src)); + int shift; + + if (cpu_has_mips32r2 || cpu_has_mips32r6) { + if (off == 8) + emit(ctx, seb, dst, src); + else + emit(ctx, seh, dst, src); + } else { + shift = 32 - off; + emit(ctx, sll, dst, src, shift); + emit(ctx, sra, dst, dst, shift); + } +} + +/* Register move operation (32-bit), optionally with sign extension */ +static void emit_mov_r32(struct jit_context *ctx, const u8 dst[], + const u8 src[], s16 off) +{ + switch (off) { + case 8: + case 16: + emit_movsx_r(ctx, lo(dst), lo(src), off); + break; + default: + /* off == 0 is MOV; the verifier rejects other offsets. */ + emit_mov_r(ctx, lo(dst), lo(src)); + break; + } + clobber_reg(ctx, lo(dst)); emit_zext_ver(ctx, dst); } -/* Register move operation (64-bit) */ +/* Register move operation (64-bit), optionally with sign extension */ static void emit_mov_r64(struct jit_context *ctx, const u8 dst[], - const u8 src[]) + const u8 src[], s16 off) { - emit_mov_r(ctx, lo(dst), lo(src)); - emit_mov_r(ctx, hi(dst), hi(src)); + switch (off) { + case 8: + case 16: + emit_movsx_r(ctx, lo(dst), lo(src), off); + emit(ctx, sra, hi(dst), lo(dst), 31); + break; + case 32: + emit(ctx, move, lo(dst), lo(src)); + emit(ctx, sra, hi(dst), lo(dst), 31); + break; + default: + /* + * off == 0 is ordinary MOV. The verifier rejects other + * offsets; defined exceptions require + * bpf_jit_supports_percpu_insn() or bpf_jit_supports_arena(), + * neither implemented by MIPS. + */ + emit_mov_r(ctx, lo(dst), lo(src)); + emit_mov_r(ctx, hi(dst), hi(src)); + break; + } + clobber_reg64(ctx, dst); } /* Load delay slot, if ISA mandates it */ @@ -1510,7 +1557,7 @@ int build_insn(const struct bpf_insn *insn, struct jit_context *ctx) /* Special mov32 for zext */ emit_mov_i(ctx, hi(dst), 0); } else { - emit_mov_r32(ctx, dst, src); + emit_mov_r32(ctx, dst, src, off); } break; /* dst = -dst */ @@ -1579,7 +1626,7 @@ int build_insn(const struct bpf_insn *insn, struct jit_context *ctx) break; /* dst = src (64-bit) */ case BPF_ALU64 | BPF_MOV | BPF_X: - emit_mov_r64(ctx, dst, src); + emit_mov_r64(ctx, dst, src, off); break; /* dst = -dst (64-bit) */ case BPF_ALU64 | BPF_NEG: diff --git a/arch/mips/net/bpf_jit_comp64.c b/arch/mips/net/bpf_jit_comp64.c index 92df2eba1390..4507841a9224 100644 --- a/arch/mips/net/bpf_jit_comp64.c +++ b/arch/mips/net/bpf_jit_comp64.c @@ -120,17 +120,56 @@ static void emit_zext_ver(struct jit_context *ctx, u8 dst) emit_zext(ctx, dst); } -/* Register move operation (32-bit) */ -static void emit_mov_r32(struct jit_context *ctx, u8 dst, u8 src) +/* Sign-extend an 8- or 16-bit field into a native register. */ +static void emit_movsx_r(struct jit_context *ctx, u8 dst, u8 src, s16 off) { - emit_mov_r(ctx, dst, src); + /* seb/seh would need a sign-extended 32-bit source first. */ + int shift = 32 - off; + + /* dsll32 and dsra32 add 32 to the shift argument. */ + emit(ctx, dsll32, dst, src, shift); + emit(ctx, dsra32, dst, dst, shift); +} + +/* Register move operation (32-bit), optionally with sign extension */ +static void emit_mov_r32(struct jit_context *ctx, u8 dst, u8 src, s16 off) +{ + switch (off) { + case 8: + case 16: + emit_movsx_r(ctx, dst, src, off); + break; + default: + /* off == 0 is MOV; the verifier rejects other offsets. */ + emit_mov_r(ctx, dst, src); + break; + } + clobber_reg(ctx, dst); emit_zext_ver(ctx, dst); } -/* Register move operation (64-bit) */ -static void emit_mov_r64(struct jit_context *ctx, u8 dst, u8 src) +/* Register move operation (64-bit), optionally with sign extension */ +static void emit_mov_r64(struct jit_context *ctx, u8 dst, u8 src, s16 off) { - emit_mov_r(ctx, dst, src); + switch (off) { + case 8: + case 16: + emit_movsx_r(ctx, dst, src, off); + break; + case 32: + emit_sext(ctx, dst, src); + break; + default: + /* + * off == 0 is ordinary MOV. The verifier rejects other + * offsets; defined exceptions require + * bpf_jit_supports_percpu_insn() or bpf_jit_supports_arena(), + * neither implemented by MIPS. + */ + emit_mov_r(ctx, dst, src); + break; + } + clobber_reg(ctx, dst); } /* dst = imm (64-bit) */ @@ -694,7 +733,7 @@ int build_insn(const struct bpf_insn *insn, struct jit_context *ctx) /* Special mov32 for zext */ emit_zext(ctx, dst); } else { - emit_mov_r32(ctx, dst, src); + emit_mov_r32(ctx, dst, src, off); } break; /* dst = -dst */ @@ -779,7 +818,7 @@ int build_insn(const struct bpf_insn *insn, struct jit_context *ctx) break; /* dst = src (64-bit) */ case BPF_ALU64 | BPF_MOV | BPF_X: - emit_mov_r64(ctx, dst, src); + emit_mov_r64(ctx, dst, src, off); break; /* dst = -dst (64-bit) */ case BPF_ALU64 | BPF_NEG: