LLVM leaves a multiplication by a constant other than a power of two as is, and the JIT emitted it as a mov of the constant and a mul of two to four cycles. For constants of the form 2^n, 2^n + 1 or 2^n - 1, optionally negated or multiplied by 2^m, emit at most two shifts, adds or subs instead. Only sequences of at most two cycles are used, so they are never slower than the mul. A multiplication by 0 becomes a mov, by -1 a neg, and by 1 nothing beyond the zero extension of the 32-bit variant. Before/after for r1 *= 3, r1 *= 7 and r1 *= 24: d280006a mov x10, #0x3 9b0a7c00 mul x0, x0, x10 8b000400 add x0, x0, x0, lsl #1 d28000ea mov x10, #0x7 9b0a7c00 mul x0, x0, x10 d37df00a lsl x10, x0, #3 cb000140 sub x0, x10, x0 d280030a mov x10, #0x18 9b0a7c00 mul x0, x0, x10 8b000400 add x0, x0, x0, lsl #1 d37df000 lsl x0, x0, #3 Signed-off-by: Daniel Borkmann --- arch/arm64/net/bpf_jit.h | 12 ++++- arch/arm64/net/bpf_jit_comp.c | 86 ++++++++++++++++++++++++++++++++++- 2 files changed, 94 insertions(+), 4 deletions(-) diff --git a/arch/arm64/net/bpf_jit.h b/arch/arm64/net/bpf_jit.h index 7566698a38a8..3bc88db350f7 100644 --- a/arch/arm64/net/bpf_jit.h +++ b/arch/arm64/net/bpf_jit.h @@ -227,11 +227,19 @@ #define A64_MOVK(sf, Rd, imm16, shift) A64_MOVEW(sf, Rd, imm16, shift, KEEP) /* Add/subtract (shifted register) */ -#define A64_ADDSUB_SREG(sf, Rd, Rn, Rm, type) \ - aarch64_insn_gen_add_sub_shifted_reg(Rd, Rn, Rm, 0, \ +#define A64_ADDSUB_SHIFT(sf, Rd, Rn, Rm, shift, type) \ + aarch64_insn_gen_add_sub_shifted_reg(Rd, Rn, Rm, shift, \ A64_VARIANT(sf), AARCH64_INSN_ADSB_##type) +#define A64_ADDSUB_SREG(sf, Rd, Rn, Rm, type) \ + A64_ADDSUB_SHIFT(sf, Rd, Rn, Rm, 0, type) /* Rd = Rn OP Rm */ #define A64_ADD(sf, Rd, Rn, Rm) A64_ADDSUB_SREG(sf, Rd, Rn, Rm, ADD) +/* Rd = Rn + (Rm << shift) */ +#define A64_ADD_LSL(sf, Rd, Rn, Rm, shift) \ + A64_ADDSUB_SHIFT(sf, Rd, Rn, Rm, shift, ADD) +/* Rd = Rn - (Rm << shift) */ +#define A64_SUB_LSL(sf, Rd, Rn, Rm, shift) \ + A64_ADDSUB_SHIFT(sf, Rd, Rn, Rm, shift, SUB) #define A64_SUB(sf, Rd, Rn, Rm) A64_ADDSUB_SREG(sf, Rd, Rn, Rm, SUB) #define A64_SUBS(sf, Rd, Rn, Rm) A64_ADDSUB_SREG(sf, Rd, Rn, Rm, SUB_SETFLAGS) /* Rd = -Rm */ diff --git a/arch/arm64/net/bpf_jit_comp.c b/arch/arm64/net/bpf_jit_comp.c index 53b2647c6a1b..b8d765d6ab07 100644 --- a/arch/arm64/net/bpf_jit_comp.c +++ b/arch/arm64/net/bpf_jit_comp.c @@ -693,6 +693,86 @@ static bool emit_tb(bool nonzero, u8 reg, int bit, int i, s16 off, struct jit_ct return true; } +/* + * dst *= 2^n, 2^n + 1 or 2^n - 1, negated or, but for -(2^n + 1), times 2^m, + * in at most two shifts, adds or subs of two cycles, never slower than the + * mul. The low 32 bits of the product are those of the signed or unsigned imm. + */ +static bool emit_mul_const(bool is64, u8 dst, u8 tmp, s32 imm, struct jit_ctx *ctx) +{ + u64 c = imm < 0 ? -(u64)(s64)imm : imm; + int tz, n; + + switch (imm) { + case 0: + emit_a64_mov_i(is64, dst, 0, ctx); + return true; + case 1: + /* The 32-bit variant still zero extends */ + if (!is64) + emit(A64_MOV(0, dst, dst), ctx); + return true; + case -1: + emit(A64_NEG(is64, dst, dst), ctx); + return true; + } + /* The odd part of the magnitude and the power of two taken off it */ + tz = __ffs(c); + c >>= tz; + if (c == 1) { + if (imm > 0) + emit(A64_LSL(is64, dst, dst, tz), ctx); + else + emit(A64_SUB_LSL(is64, dst, A64_ZR, dst, tz), ctx); + return true; + } + if (imm > 0) { + if (is_power_of_2(c - 1)) { + n = ilog2(c - 1); + /* The add of two cycles and the shift, three */ + if (n > 4 && tz) + return false; + emit(A64_ADD_LSL(is64, dst, dst, dst, n), ctx); + if (tz) + emit(A64_LSL(is64, dst, dst, tz), ctx); + return true; + } + if (is_power_of_2(c + 1)) { + n = ilog2(c + 1); + /* The shift and the sub of two cycles, three */ + if (tz > 4) + return false; + emit(A64_LSL(is64, tmp, dst, n + tz), ctx); + emit(A64_SUB_LSL(is64, dst, tmp, dst, tz), ctx); + return true; + } + } else { + if (is_power_of_2(c + 1)) { + n = ilog2(c + 1); + if (!tz) { + emit(A64_SUB_LSL(is64, dst, dst, dst, n), ctx); + return true; + } + /* The shift and the sub of two cycles, three */ + if (n + tz > 4) + return false; + emit(A64_LSL(is64, tmp, dst, tz), ctx); + emit(A64_SUB_LSL(is64, dst, tmp, dst, n + tz), ctx); + return true; + } + if (!tz && is_power_of_2(c - 1)) { + n = ilog2(c - 1); + /* The add of two cycles and the neg, three */ + if (n > 4) + return false; + emit(A64_ADD_LSL(is64, dst, dst, dst, n), ctx); + emit(A64_NEG(is64, dst, dst), ctx); + return true; + } + } + return false; +} + /* generated main prog prologue: * bti c // if CONFIG_ARM64_BTI_KERNEL * mov x9, lr @@ -2049,8 +2129,10 @@ static int build_insn(const struct bpf_verifier_env *env, const struct bpf_insn break; case BPF_ALU | BPF_MUL | BPF_K: case BPF_ALU64 | BPF_MUL | BPF_K: - emit_a64_mov_i(is64, tmp, imm, ctx); - emit_a64_mul(is64, dst, tmp, off, ctx); + if (off || !emit_mul_const(is64, dst, tmp, imm, ctx)) { + emit_a64_mov_i(is64, tmp, imm, ctx); + emit_a64_mul(is64, dst, tmp, off, ctx); + } break; case BPF_ALU | BPF_DIV | BPF_K: case BPF_ALU64 | BPF_DIV | BPF_K: -- 2.43.0