162306a36Sopenharmony_ci/* SPDX-License-Identifier: GPL-2.0-only */ 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * BPF JIT compiler for LoongArch 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2022 Loongson Technology Corporation Limited 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci#include <linux/bitfield.h> 862306a36Sopenharmony_ci#include <linux/bpf.h> 962306a36Sopenharmony_ci#include <linux/filter.h> 1062306a36Sopenharmony_ci#include <asm/cacheflush.h> 1162306a36Sopenharmony_ci#include <asm/inst.h> 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_cistruct jit_ctx { 1462306a36Sopenharmony_ci const struct bpf_prog *prog; 1562306a36Sopenharmony_ci unsigned int idx; 1662306a36Sopenharmony_ci unsigned int flags; 1762306a36Sopenharmony_ci unsigned int epilogue_offset; 1862306a36Sopenharmony_ci u32 *offset; 1962306a36Sopenharmony_ci int num_exentries; 2062306a36Sopenharmony_ci union loongarch_instruction *image; 2162306a36Sopenharmony_ci u32 stack_size; 2262306a36Sopenharmony_ci}; 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_cistruct jit_data { 2562306a36Sopenharmony_ci struct bpf_binary_header *header; 2662306a36Sopenharmony_ci u8 *image; 2762306a36Sopenharmony_ci struct jit_ctx ctx; 2862306a36Sopenharmony_ci}; 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci#define emit_insn(ctx, func, ...) \ 3162306a36Sopenharmony_cido { \ 3262306a36Sopenharmony_ci if (ctx->image != NULL) { \ 3362306a36Sopenharmony_ci union loongarch_instruction *insn = &ctx->image[ctx->idx]; \ 3462306a36Sopenharmony_ci emit_##func(insn, ##__VA_ARGS__); \ 3562306a36Sopenharmony_ci } \ 3662306a36Sopenharmony_ci ctx->idx++; \ 3762306a36Sopenharmony_ci} while (0) 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci#define is_signed_imm12(val) signed_imm_check(val, 12) 4062306a36Sopenharmony_ci#define is_signed_imm14(val) signed_imm_check(val, 14) 4162306a36Sopenharmony_ci#define is_signed_imm16(val) signed_imm_check(val, 16) 4262306a36Sopenharmony_ci#define is_signed_imm26(val) signed_imm_check(val, 26) 4362306a36Sopenharmony_ci#define is_signed_imm32(val) signed_imm_check(val, 32) 4462306a36Sopenharmony_ci#define is_signed_imm52(val) signed_imm_check(val, 52) 4562306a36Sopenharmony_ci#define is_unsigned_imm12(val) unsigned_imm_check(val, 12) 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_cistatic inline int bpf2la_offset(int bpf_insn, int off, const struct jit_ctx *ctx) 4862306a36Sopenharmony_ci{ 4962306a36Sopenharmony_ci /* BPF JMP offset is relative to the next instruction */ 5062306a36Sopenharmony_ci bpf_insn++; 5162306a36Sopenharmony_ci /* 5262306a36Sopenharmony_ci * Whereas LoongArch branch instructions encode the offset 5362306a36Sopenharmony_ci * from the branch itself, so we must subtract 1 from the 5462306a36Sopenharmony_ci * instruction offset. 5562306a36Sopenharmony_ci */ 5662306a36Sopenharmony_ci return (ctx->offset[bpf_insn + off] - (ctx->offset[bpf_insn] - 1)); 5762306a36Sopenharmony_ci} 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_cistatic inline int epilogue_offset(const struct jit_ctx *ctx) 6062306a36Sopenharmony_ci{ 6162306a36Sopenharmony_ci int from = ctx->idx; 6262306a36Sopenharmony_ci int to = ctx->epilogue_offset; 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci return (to - from); 6562306a36Sopenharmony_ci} 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci/* Zero-extend 32 bits into 64 bits */ 6862306a36Sopenharmony_cistatic inline void emit_zext_32(struct jit_ctx *ctx, enum loongarch_gpr reg, bool is32) 6962306a36Sopenharmony_ci{ 7062306a36Sopenharmony_ci if (!is32) 7162306a36Sopenharmony_ci return; 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci emit_insn(ctx, lu32id, reg, 0); 7462306a36Sopenharmony_ci} 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci/* Signed-extend 32 bits into 64 bits */ 7762306a36Sopenharmony_cistatic inline void emit_sext_32(struct jit_ctx *ctx, enum loongarch_gpr reg, bool is32) 7862306a36Sopenharmony_ci{ 7962306a36Sopenharmony_ci if (!is32) 8062306a36Sopenharmony_ci return; 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci emit_insn(ctx, addiw, reg, reg, 0); 8362306a36Sopenharmony_ci} 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_cistatic inline void move_addr(struct jit_ctx *ctx, enum loongarch_gpr rd, u64 addr) 8662306a36Sopenharmony_ci{ 8762306a36Sopenharmony_ci u64 imm_11_0, imm_31_12, imm_51_32, imm_63_52; 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci /* lu12iw rd, imm_31_12 */ 9062306a36Sopenharmony_ci imm_31_12 = (addr >> 12) & 0xfffff; 9162306a36Sopenharmony_ci emit_insn(ctx, lu12iw, rd, imm_31_12); 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci /* ori rd, rd, imm_11_0 */ 9462306a36Sopenharmony_ci imm_11_0 = addr & 0xfff; 9562306a36Sopenharmony_ci emit_insn(ctx, ori, rd, rd, imm_11_0); 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci /* lu32id rd, imm_51_32 */ 9862306a36Sopenharmony_ci imm_51_32 = (addr >> 32) & 0xfffff; 9962306a36Sopenharmony_ci emit_insn(ctx, lu32id, rd, imm_51_32); 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci /* lu52id rd, rd, imm_63_52 */ 10262306a36Sopenharmony_ci imm_63_52 = (addr >> 52) & 0xfff; 10362306a36Sopenharmony_ci emit_insn(ctx, lu52id, rd, rd, imm_63_52); 10462306a36Sopenharmony_ci} 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_cistatic inline void move_imm(struct jit_ctx *ctx, enum loongarch_gpr rd, long imm, bool is32) 10762306a36Sopenharmony_ci{ 10862306a36Sopenharmony_ci long imm_11_0, imm_31_12, imm_51_32, imm_63_52, imm_51_0, imm_51_31; 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci /* or rd, $zero, $zero */ 11162306a36Sopenharmony_ci if (imm == 0) { 11262306a36Sopenharmony_ci emit_insn(ctx, or, rd, LOONGARCH_GPR_ZERO, LOONGARCH_GPR_ZERO); 11362306a36Sopenharmony_ci return; 11462306a36Sopenharmony_ci } 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci /* addiw rd, $zero, imm_11_0 */ 11762306a36Sopenharmony_ci if (is_signed_imm12(imm)) { 11862306a36Sopenharmony_ci emit_insn(ctx, addiw, rd, LOONGARCH_GPR_ZERO, imm); 11962306a36Sopenharmony_ci goto zext; 12062306a36Sopenharmony_ci } 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci /* ori rd, $zero, imm_11_0 */ 12362306a36Sopenharmony_ci if (is_unsigned_imm12(imm)) { 12462306a36Sopenharmony_ci emit_insn(ctx, ori, rd, LOONGARCH_GPR_ZERO, imm); 12562306a36Sopenharmony_ci goto zext; 12662306a36Sopenharmony_ci } 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci /* lu52id rd, $zero, imm_63_52 */ 12962306a36Sopenharmony_ci imm_63_52 = (imm >> 52) & 0xfff; 13062306a36Sopenharmony_ci imm_51_0 = imm & 0xfffffffffffff; 13162306a36Sopenharmony_ci if (imm_63_52 != 0 && imm_51_0 == 0) { 13262306a36Sopenharmony_ci emit_insn(ctx, lu52id, rd, LOONGARCH_GPR_ZERO, imm_63_52); 13362306a36Sopenharmony_ci return; 13462306a36Sopenharmony_ci } 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci /* lu12iw rd, imm_31_12 */ 13762306a36Sopenharmony_ci imm_31_12 = (imm >> 12) & 0xfffff; 13862306a36Sopenharmony_ci emit_insn(ctx, lu12iw, rd, imm_31_12); 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci /* ori rd, rd, imm_11_0 */ 14162306a36Sopenharmony_ci imm_11_0 = imm & 0xfff; 14262306a36Sopenharmony_ci if (imm_11_0 != 0) 14362306a36Sopenharmony_ci emit_insn(ctx, ori, rd, rd, imm_11_0); 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci if (!is_signed_imm32(imm)) { 14662306a36Sopenharmony_ci if (imm_51_0 != 0) { 14762306a36Sopenharmony_ci /* 14862306a36Sopenharmony_ci * If bit[51:31] is all 0 or all 1, 14962306a36Sopenharmony_ci * it means bit[51:32] is sign extended by lu12iw, 15062306a36Sopenharmony_ci * no need to call lu32id to do a new filled operation. 15162306a36Sopenharmony_ci */ 15262306a36Sopenharmony_ci imm_51_31 = (imm >> 31) & 0x1fffff; 15362306a36Sopenharmony_ci if (imm_51_31 != 0 && imm_51_31 != 0x1fffff) { 15462306a36Sopenharmony_ci /* lu32id rd, imm_51_32 */ 15562306a36Sopenharmony_ci imm_51_32 = (imm >> 32) & 0xfffff; 15662306a36Sopenharmony_ci emit_insn(ctx, lu32id, rd, imm_51_32); 15762306a36Sopenharmony_ci } 15862306a36Sopenharmony_ci } 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci /* lu52id rd, rd, imm_63_52 */ 16162306a36Sopenharmony_ci if (!is_signed_imm52(imm)) 16262306a36Sopenharmony_ci emit_insn(ctx, lu52id, rd, rd, imm_63_52); 16362306a36Sopenharmony_ci } 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_cizext: 16662306a36Sopenharmony_ci emit_zext_32(ctx, rd, is32); 16762306a36Sopenharmony_ci} 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_cistatic inline void move_reg(struct jit_ctx *ctx, enum loongarch_gpr rd, 17062306a36Sopenharmony_ci enum loongarch_gpr rj) 17162306a36Sopenharmony_ci{ 17262306a36Sopenharmony_ci emit_insn(ctx, or, rd, rj, LOONGARCH_GPR_ZERO); 17362306a36Sopenharmony_ci} 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_cistatic inline int invert_jmp_cond(u8 cond) 17662306a36Sopenharmony_ci{ 17762306a36Sopenharmony_ci switch (cond) { 17862306a36Sopenharmony_ci case BPF_JEQ: 17962306a36Sopenharmony_ci return BPF_JNE; 18062306a36Sopenharmony_ci case BPF_JNE: 18162306a36Sopenharmony_ci case BPF_JSET: 18262306a36Sopenharmony_ci return BPF_JEQ; 18362306a36Sopenharmony_ci case BPF_JGT: 18462306a36Sopenharmony_ci return BPF_JLE; 18562306a36Sopenharmony_ci case BPF_JGE: 18662306a36Sopenharmony_ci return BPF_JLT; 18762306a36Sopenharmony_ci case BPF_JLT: 18862306a36Sopenharmony_ci return BPF_JGE; 18962306a36Sopenharmony_ci case BPF_JLE: 19062306a36Sopenharmony_ci return BPF_JGT; 19162306a36Sopenharmony_ci case BPF_JSGT: 19262306a36Sopenharmony_ci return BPF_JSLE; 19362306a36Sopenharmony_ci case BPF_JSGE: 19462306a36Sopenharmony_ci return BPF_JSLT; 19562306a36Sopenharmony_ci case BPF_JSLT: 19662306a36Sopenharmony_ci return BPF_JSGE; 19762306a36Sopenharmony_ci case BPF_JSLE: 19862306a36Sopenharmony_ci return BPF_JSGT; 19962306a36Sopenharmony_ci } 20062306a36Sopenharmony_ci return -1; 20162306a36Sopenharmony_ci} 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_cistatic inline void cond_jmp_offset(struct jit_ctx *ctx, u8 cond, enum loongarch_gpr rj, 20462306a36Sopenharmony_ci enum loongarch_gpr rd, int jmp_offset) 20562306a36Sopenharmony_ci{ 20662306a36Sopenharmony_ci switch (cond) { 20762306a36Sopenharmony_ci case BPF_JEQ: 20862306a36Sopenharmony_ci /* PC += jmp_offset if rj == rd */ 20962306a36Sopenharmony_ci emit_insn(ctx, beq, rj, rd, jmp_offset); 21062306a36Sopenharmony_ci return; 21162306a36Sopenharmony_ci case BPF_JNE: 21262306a36Sopenharmony_ci case BPF_JSET: 21362306a36Sopenharmony_ci /* PC += jmp_offset if rj != rd */ 21462306a36Sopenharmony_ci emit_insn(ctx, bne, rj, rd, jmp_offset); 21562306a36Sopenharmony_ci return; 21662306a36Sopenharmony_ci case BPF_JGT: 21762306a36Sopenharmony_ci /* PC += jmp_offset if rj > rd (unsigned) */ 21862306a36Sopenharmony_ci emit_insn(ctx, bltu, rd, rj, jmp_offset); 21962306a36Sopenharmony_ci return; 22062306a36Sopenharmony_ci case BPF_JLT: 22162306a36Sopenharmony_ci /* PC += jmp_offset if rj < rd (unsigned) */ 22262306a36Sopenharmony_ci emit_insn(ctx, bltu, rj, rd, jmp_offset); 22362306a36Sopenharmony_ci return; 22462306a36Sopenharmony_ci case BPF_JGE: 22562306a36Sopenharmony_ci /* PC += jmp_offset if rj >= rd (unsigned) */ 22662306a36Sopenharmony_ci emit_insn(ctx, bgeu, rj, rd, jmp_offset); 22762306a36Sopenharmony_ci return; 22862306a36Sopenharmony_ci case BPF_JLE: 22962306a36Sopenharmony_ci /* PC += jmp_offset if rj <= rd (unsigned) */ 23062306a36Sopenharmony_ci emit_insn(ctx, bgeu, rd, rj, jmp_offset); 23162306a36Sopenharmony_ci return; 23262306a36Sopenharmony_ci case BPF_JSGT: 23362306a36Sopenharmony_ci /* PC += jmp_offset if rj > rd (signed) */ 23462306a36Sopenharmony_ci emit_insn(ctx, blt, rd, rj, jmp_offset); 23562306a36Sopenharmony_ci return; 23662306a36Sopenharmony_ci case BPF_JSLT: 23762306a36Sopenharmony_ci /* PC += jmp_offset if rj < rd (signed) */ 23862306a36Sopenharmony_ci emit_insn(ctx, blt, rj, rd, jmp_offset); 23962306a36Sopenharmony_ci return; 24062306a36Sopenharmony_ci case BPF_JSGE: 24162306a36Sopenharmony_ci /* PC += jmp_offset if rj >= rd (signed) */ 24262306a36Sopenharmony_ci emit_insn(ctx, bge, rj, rd, jmp_offset); 24362306a36Sopenharmony_ci return; 24462306a36Sopenharmony_ci case BPF_JSLE: 24562306a36Sopenharmony_ci /* PC += jmp_offset if rj <= rd (signed) */ 24662306a36Sopenharmony_ci emit_insn(ctx, bge, rd, rj, jmp_offset); 24762306a36Sopenharmony_ci return; 24862306a36Sopenharmony_ci } 24962306a36Sopenharmony_ci} 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_cistatic inline void cond_jmp_offs26(struct jit_ctx *ctx, u8 cond, enum loongarch_gpr rj, 25262306a36Sopenharmony_ci enum loongarch_gpr rd, int jmp_offset) 25362306a36Sopenharmony_ci{ 25462306a36Sopenharmony_ci cond = invert_jmp_cond(cond); 25562306a36Sopenharmony_ci cond_jmp_offset(ctx, cond, rj, rd, 2); 25662306a36Sopenharmony_ci emit_insn(ctx, b, jmp_offset); 25762306a36Sopenharmony_ci} 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_cistatic inline void uncond_jmp_offs26(struct jit_ctx *ctx, int jmp_offset) 26062306a36Sopenharmony_ci{ 26162306a36Sopenharmony_ci emit_insn(ctx, b, jmp_offset); 26262306a36Sopenharmony_ci} 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_cistatic inline int emit_cond_jmp(struct jit_ctx *ctx, u8 cond, enum loongarch_gpr rj, 26562306a36Sopenharmony_ci enum loongarch_gpr rd, int jmp_offset) 26662306a36Sopenharmony_ci{ 26762306a36Sopenharmony_ci /* 26862306a36Sopenharmony_ci * A large PC-relative jump offset may overflow the immediate field of 26962306a36Sopenharmony_ci * the native conditional branch instruction, triggering a conversion 27062306a36Sopenharmony_ci * to use an absolute jump instead, this jump sequence is particularly 27162306a36Sopenharmony_ci * nasty. For now, use cond_jmp_offs26() directly to keep it simple. 27262306a36Sopenharmony_ci * In the future, maybe we can add support for far branching, the branch 27362306a36Sopenharmony_ci * relaxation requires more than two passes to converge, the code seems 27462306a36Sopenharmony_ci * too complex to understand, not quite sure whether it is necessary and 27562306a36Sopenharmony_ci * worth the extra pain. Anyway, just leave it as it is to enhance code 27662306a36Sopenharmony_ci * readability now. 27762306a36Sopenharmony_ci */ 27862306a36Sopenharmony_ci if (is_signed_imm26(jmp_offset)) { 27962306a36Sopenharmony_ci cond_jmp_offs26(ctx, cond, rj, rd, jmp_offset); 28062306a36Sopenharmony_ci return 0; 28162306a36Sopenharmony_ci } 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_ci return -EINVAL; 28462306a36Sopenharmony_ci} 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_cistatic inline int emit_uncond_jmp(struct jit_ctx *ctx, int jmp_offset) 28762306a36Sopenharmony_ci{ 28862306a36Sopenharmony_ci if (is_signed_imm26(jmp_offset)) { 28962306a36Sopenharmony_ci uncond_jmp_offs26(ctx, jmp_offset); 29062306a36Sopenharmony_ci return 0; 29162306a36Sopenharmony_ci } 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_ci return -EINVAL; 29462306a36Sopenharmony_ci} 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_cistatic inline int emit_tailcall_jmp(struct jit_ctx *ctx, u8 cond, enum loongarch_gpr rj, 29762306a36Sopenharmony_ci enum loongarch_gpr rd, int jmp_offset) 29862306a36Sopenharmony_ci{ 29962306a36Sopenharmony_ci if (is_signed_imm16(jmp_offset)) { 30062306a36Sopenharmony_ci cond_jmp_offset(ctx, cond, rj, rd, jmp_offset); 30162306a36Sopenharmony_ci return 0; 30262306a36Sopenharmony_ci } 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_ci return -EINVAL; 30562306a36Sopenharmony_ci} 306