mirror of
https://github.com/torvalds/linux.git
synced 2024-11-10 14:11:52 +00:00
LoongArch: Add BPF JIT support
BPF programs are normally handled by a BPF interpreter, add BPF JIT support for LoongArch to allow the kernel to generate native code when a program is loaded into the kernel. This will significantly speed-up processing of BPF programs. Co-developed-by: Youling Tang <tangyouling@loongson.cn> Signed-off-by: Youling Tang <tangyouling@loongson.cn> Signed-off-by: Tiezhu Yang <yangtiezhu@loongson.cn> Signed-off-by: Huacai Chen <chenhuacai@loongson.cn>
This commit is contained in:
parent
4e59e5a469
commit
5dc615520c
@ -1,5 +1,6 @@
|
|||||||
obj-y += kernel/
|
obj-y += kernel/
|
||||||
obj-y += mm/
|
obj-y += mm/
|
||||||
|
obj-y += net/
|
||||||
obj-y += vdso/
|
obj-y += vdso/
|
||||||
|
|
||||||
# for cleaning
|
# for cleaning
|
||||||
|
@ -87,6 +87,7 @@ config LOONGARCH
|
|||||||
select HAVE_CONTEXT_TRACKING_USER
|
select HAVE_CONTEXT_TRACKING_USER
|
||||||
select HAVE_DEBUG_STACKOVERFLOW
|
select HAVE_DEBUG_STACKOVERFLOW
|
||||||
select HAVE_DMA_CONTIGUOUS
|
select HAVE_DMA_CONTIGUOUS
|
||||||
|
select HAVE_EBPF_JIT
|
||||||
select HAVE_EXIT_THREAD
|
select HAVE_EXIT_THREAD
|
||||||
select HAVE_FAST_GUP
|
select HAVE_FAST_GUP
|
||||||
select HAVE_GENERIC_VDSO
|
select HAVE_GENERIC_VDSO
|
||||||
|
@ -345,4 +345,225 @@ static inline bool unsigned_imm_check(unsigned long val, unsigned int bit)
|
|||||||
return val < (1UL << bit);
|
return val < (1UL << bit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define DEF_EMIT_REG0I26_FORMAT(NAME, OP) \
|
||||||
|
static inline void emit_##NAME(union loongarch_instruction *insn, \
|
||||||
|
int offset) \
|
||||||
|
{ \
|
||||||
|
unsigned int immediate_l, immediate_h; \
|
||||||
|
\
|
||||||
|
immediate_l = offset & 0xffff; \
|
||||||
|
offset >>= 16; \
|
||||||
|
immediate_h = offset & 0x3ff; \
|
||||||
|
\
|
||||||
|
insn->reg0i26_format.opcode = OP; \
|
||||||
|
insn->reg0i26_format.immediate_l = immediate_l; \
|
||||||
|
insn->reg0i26_format.immediate_h = immediate_h; \
|
||||||
|
}
|
||||||
|
|
||||||
|
DEF_EMIT_REG0I26_FORMAT(b, b_op)
|
||||||
|
|
||||||
|
#define DEF_EMIT_REG1I20_FORMAT(NAME, OP) \
|
||||||
|
static inline void emit_##NAME(union loongarch_instruction *insn, \
|
||||||
|
enum loongarch_gpr rd, int imm) \
|
||||||
|
{ \
|
||||||
|
insn->reg1i20_format.opcode = OP; \
|
||||||
|
insn->reg1i20_format.immediate = imm; \
|
||||||
|
insn->reg1i20_format.rd = rd; \
|
||||||
|
}
|
||||||
|
|
||||||
|
DEF_EMIT_REG1I20_FORMAT(lu12iw, lu12iw_op)
|
||||||
|
DEF_EMIT_REG1I20_FORMAT(lu32id, lu32id_op)
|
||||||
|
DEF_EMIT_REG1I20_FORMAT(pcaddu18i, pcaddu18i_op)
|
||||||
|
|
||||||
|
#define DEF_EMIT_REG2_FORMAT(NAME, OP) \
|
||||||
|
static inline void emit_##NAME(union loongarch_instruction *insn, \
|
||||||
|
enum loongarch_gpr rd, \
|
||||||
|
enum loongarch_gpr rj) \
|
||||||
|
{ \
|
||||||
|
insn->reg2_format.opcode = OP; \
|
||||||
|
insn->reg2_format.rd = rd; \
|
||||||
|
insn->reg2_format.rj = rj; \
|
||||||
|
}
|
||||||
|
|
||||||
|
DEF_EMIT_REG2_FORMAT(revb2h, revb2h_op)
|
||||||
|
DEF_EMIT_REG2_FORMAT(revb2w, revb2w_op)
|
||||||
|
DEF_EMIT_REG2_FORMAT(revbd, revbd_op)
|
||||||
|
|
||||||
|
#define DEF_EMIT_REG2I5_FORMAT(NAME, OP) \
|
||||||
|
static inline void emit_##NAME(union loongarch_instruction *insn, \
|
||||||
|
enum loongarch_gpr rd, \
|
||||||
|
enum loongarch_gpr rj, \
|
||||||
|
int imm) \
|
||||||
|
{ \
|
||||||
|
insn->reg2i5_format.opcode = OP; \
|
||||||
|
insn->reg2i5_format.immediate = imm; \
|
||||||
|
insn->reg2i5_format.rd = rd; \
|
||||||
|
insn->reg2i5_format.rj = rj; \
|
||||||
|
}
|
||||||
|
|
||||||
|
DEF_EMIT_REG2I5_FORMAT(slliw, slliw_op)
|
||||||
|
DEF_EMIT_REG2I5_FORMAT(srliw, srliw_op)
|
||||||
|
DEF_EMIT_REG2I5_FORMAT(sraiw, sraiw_op)
|
||||||
|
|
||||||
|
#define DEF_EMIT_REG2I6_FORMAT(NAME, OP) \
|
||||||
|
static inline void emit_##NAME(union loongarch_instruction *insn, \
|
||||||
|
enum loongarch_gpr rd, \
|
||||||
|
enum loongarch_gpr rj, \
|
||||||
|
int imm) \
|
||||||
|
{ \
|
||||||
|
insn->reg2i6_format.opcode = OP; \
|
||||||
|
insn->reg2i6_format.immediate = imm; \
|
||||||
|
insn->reg2i6_format.rd = rd; \
|
||||||
|
insn->reg2i6_format.rj = rj; \
|
||||||
|
}
|
||||||
|
|
||||||
|
DEF_EMIT_REG2I6_FORMAT(sllid, sllid_op)
|
||||||
|
DEF_EMIT_REG2I6_FORMAT(srlid, srlid_op)
|
||||||
|
DEF_EMIT_REG2I6_FORMAT(sraid, sraid_op)
|
||||||
|
|
||||||
|
#define DEF_EMIT_REG2I12_FORMAT(NAME, OP) \
|
||||||
|
static inline void emit_##NAME(union loongarch_instruction *insn, \
|
||||||
|
enum loongarch_gpr rd, \
|
||||||
|
enum loongarch_gpr rj, \
|
||||||
|
int imm) \
|
||||||
|
{ \
|
||||||
|
insn->reg2i12_format.opcode = OP; \
|
||||||
|
insn->reg2i12_format.immediate = imm; \
|
||||||
|
insn->reg2i12_format.rd = rd; \
|
||||||
|
insn->reg2i12_format.rj = rj; \
|
||||||
|
}
|
||||||
|
|
||||||
|
DEF_EMIT_REG2I12_FORMAT(addiw, addiw_op)
|
||||||
|
DEF_EMIT_REG2I12_FORMAT(addid, addid_op)
|
||||||
|
DEF_EMIT_REG2I12_FORMAT(lu52id, lu52id_op)
|
||||||
|
DEF_EMIT_REG2I12_FORMAT(andi, andi_op)
|
||||||
|
DEF_EMIT_REG2I12_FORMAT(ori, ori_op)
|
||||||
|
DEF_EMIT_REG2I12_FORMAT(xori, xori_op)
|
||||||
|
DEF_EMIT_REG2I12_FORMAT(ldbu, ldbu_op)
|
||||||
|
DEF_EMIT_REG2I12_FORMAT(ldhu, ldhu_op)
|
||||||
|
DEF_EMIT_REG2I12_FORMAT(ldwu, ldwu_op)
|
||||||
|
DEF_EMIT_REG2I12_FORMAT(ldd, ldd_op)
|
||||||
|
DEF_EMIT_REG2I12_FORMAT(stb, stb_op)
|
||||||
|
DEF_EMIT_REG2I12_FORMAT(sth, sth_op)
|
||||||
|
DEF_EMIT_REG2I12_FORMAT(stw, stw_op)
|
||||||
|
DEF_EMIT_REG2I12_FORMAT(std, std_op)
|
||||||
|
|
||||||
|
#define DEF_EMIT_REG2I14_FORMAT(NAME, OP) \
|
||||||
|
static inline void emit_##NAME(union loongarch_instruction *insn, \
|
||||||
|
enum loongarch_gpr rd, \
|
||||||
|
enum loongarch_gpr rj, \
|
||||||
|
int imm) \
|
||||||
|
{ \
|
||||||
|
insn->reg2i14_format.opcode = OP; \
|
||||||
|
insn->reg2i14_format.immediate = imm; \
|
||||||
|
insn->reg2i14_format.rd = rd; \
|
||||||
|
insn->reg2i14_format.rj = rj; \
|
||||||
|
}
|
||||||
|
|
||||||
|
DEF_EMIT_REG2I14_FORMAT(llw, llw_op)
|
||||||
|
DEF_EMIT_REG2I14_FORMAT(scw, scw_op)
|
||||||
|
DEF_EMIT_REG2I14_FORMAT(lld, lld_op)
|
||||||
|
DEF_EMIT_REG2I14_FORMAT(scd, scd_op)
|
||||||
|
DEF_EMIT_REG2I14_FORMAT(ldptrw, ldptrw_op)
|
||||||
|
DEF_EMIT_REG2I14_FORMAT(stptrw, stptrw_op)
|
||||||
|
DEF_EMIT_REG2I14_FORMAT(ldptrd, ldptrd_op)
|
||||||
|
DEF_EMIT_REG2I14_FORMAT(stptrd, stptrd_op)
|
||||||
|
|
||||||
|
#define DEF_EMIT_REG2I16_FORMAT(NAME, OP) \
|
||||||
|
static inline void emit_##NAME(union loongarch_instruction *insn, \
|
||||||
|
enum loongarch_gpr rj, \
|
||||||
|
enum loongarch_gpr rd, \
|
||||||
|
int offset) \
|
||||||
|
{ \
|
||||||
|
insn->reg2i16_format.opcode = OP; \
|
||||||
|
insn->reg2i16_format.immediate = offset; \
|
||||||
|
insn->reg2i16_format.rj = rj; \
|
||||||
|
insn->reg2i16_format.rd = rd; \
|
||||||
|
}
|
||||||
|
|
||||||
|
DEF_EMIT_REG2I16_FORMAT(beq, beq_op)
|
||||||
|
DEF_EMIT_REG2I16_FORMAT(bne, bne_op)
|
||||||
|
DEF_EMIT_REG2I16_FORMAT(blt, blt_op)
|
||||||
|
DEF_EMIT_REG2I16_FORMAT(bge, bge_op)
|
||||||
|
DEF_EMIT_REG2I16_FORMAT(bltu, bltu_op)
|
||||||
|
DEF_EMIT_REG2I16_FORMAT(bgeu, bgeu_op)
|
||||||
|
DEF_EMIT_REG2I16_FORMAT(jirl, jirl_op)
|
||||||
|
|
||||||
|
#define DEF_EMIT_REG2BSTRD_FORMAT(NAME, OP) \
|
||||||
|
static inline void emit_##NAME(union loongarch_instruction *insn, \
|
||||||
|
enum loongarch_gpr rd, \
|
||||||
|
enum loongarch_gpr rj, \
|
||||||
|
int msbd, \
|
||||||
|
int lsbd) \
|
||||||
|
{ \
|
||||||
|
insn->reg2bstrd_format.opcode = OP; \
|
||||||
|
insn->reg2bstrd_format.msbd = msbd; \
|
||||||
|
insn->reg2bstrd_format.lsbd = lsbd; \
|
||||||
|
insn->reg2bstrd_format.rj = rj; \
|
||||||
|
insn->reg2bstrd_format.rd = rd; \
|
||||||
|
}
|
||||||
|
|
||||||
|
DEF_EMIT_REG2BSTRD_FORMAT(bstrpickd, bstrpickd_op)
|
||||||
|
|
||||||
|
#define DEF_EMIT_REG3_FORMAT(NAME, OP) \
|
||||||
|
static inline void emit_##NAME(union loongarch_instruction *insn, \
|
||||||
|
enum loongarch_gpr rd, \
|
||||||
|
enum loongarch_gpr rj, \
|
||||||
|
enum loongarch_gpr rk) \
|
||||||
|
{ \
|
||||||
|
insn->reg3_format.opcode = OP; \
|
||||||
|
insn->reg3_format.rd = rd; \
|
||||||
|
insn->reg3_format.rj = rj; \
|
||||||
|
insn->reg3_format.rk = rk; \
|
||||||
|
}
|
||||||
|
|
||||||
|
DEF_EMIT_REG3_FORMAT(addd, addd_op)
|
||||||
|
DEF_EMIT_REG3_FORMAT(subd, subd_op)
|
||||||
|
DEF_EMIT_REG3_FORMAT(muld, muld_op)
|
||||||
|
DEF_EMIT_REG3_FORMAT(divdu, divdu_op)
|
||||||
|
DEF_EMIT_REG3_FORMAT(moddu, moddu_op)
|
||||||
|
DEF_EMIT_REG3_FORMAT(and, and_op)
|
||||||
|
DEF_EMIT_REG3_FORMAT(or, or_op)
|
||||||
|
DEF_EMIT_REG3_FORMAT(xor, xor_op)
|
||||||
|
DEF_EMIT_REG3_FORMAT(sllw, sllw_op)
|
||||||
|
DEF_EMIT_REG3_FORMAT(slld, slld_op)
|
||||||
|
DEF_EMIT_REG3_FORMAT(srlw, srlw_op)
|
||||||
|
DEF_EMIT_REG3_FORMAT(srld, srld_op)
|
||||||
|
DEF_EMIT_REG3_FORMAT(sraw, sraw_op)
|
||||||
|
DEF_EMIT_REG3_FORMAT(srad, srad_op)
|
||||||
|
DEF_EMIT_REG3_FORMAT(ldxbu, ldxbu_op)
|
||||||
|
DEF_EMIT_REG3_FORMAT(ldxhu, ldxhu_op)
|
||||||
|
DEF_EMIT_REG3_FORMAT(ldxwu, ldxwu_op)
|
||||||
|
DEF_EMIT_REG3_FORMAT(ldxd, ldxd_op)
|
||||||
|
DEF_EMIT_REG3_FORMAT(stxb, stxb_op)
|
||||||
|
DEF_EMIT_REG3_FORMAT(stxh, stxh_op)
|
||||||
|
DEF_EMIT_REG3_FORMAT(stxw, stxw_op)
|
||||||
|
DEF_EMIT_REG3_FORMAT(stxd, stxd_op)
|
||||||
|
DEF_EMIT_REG3_FORMAT(amaddw, amaddw_op)
|
||||||
|
DEF_EMIT_REG3_FORMAT(amaddd, amaddd_op)
|
||||||
|
DEF_EMIT_REG3_FORMAT(amandw, amandw_op)
|
||||||
|
DEF_EMIT_REG3_FORMAT(amandd, amandd_op)
|
||||||
|
DEF_EMIT_REG3_FORMAT(amorw, amorw_op)
|
||||||
|
DEF_EMIT_REG3_FORMAT(amord, amord_op)
|
||||||
|
DEF_EMIT_REG3_FORMAT(amxorw, amxorw_op)
|
||||||
|
DEF_EMIT_REG3_FORMAT(amxord, amxord_op)
|
||||||
|
DEF_EMIT_REG3_FORMAT(amswapw, amswapw_op)
|
||||||
|
DEF_EMIT_REG3_FORMAT(amswapd, amswapd_op)
|
||||||
|
|
||||||
|
#define DEF_EMIT_REG3SA2_FORMAT(NAME, OP) \
|
||||||
|
static inline void emit_##NAME(union loongarch_instruction *insn, \
|
||||||
|
enum loongarch_gpr rd, \
|
||||||
|
enum loongarch_gpr rj, \
|
||||||
|
enum loongarch_gpr rk, \
|
||||||
|
int imm) \
|
||||||
|
{ \
|
||||||
|
insn->reg3sa2_format.opcode = OP; \
|
||||||
|
insn->reg3sa2_format.immediate = imm; \
|
||||||
|
insn->reg3sa2_format.rd = rd; \
|
||||||
|
insn->reg3sa2_format.rj = rj; \
|
||||||
|
insn->reg3sa2_format.rk = rk; \
|
||||||
|
}
|
||||||
|
|
||||||
|
DEF_EMIT_REG3SA2_FORMAT(alsld, alsld_op)
|
||||||
|
|
||||||
#endif /* _ASM_INST_H */
|
#endif /* _ASM_INST_H */
|
||||||
|
9
arch/loongarch/include/uapi/asm/bpf_perf_event.h
Normal file
9
arch/loongarch/include/uapi/asm/bpf_perf_event.h
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
|
||||||
|
#ifndef _UAPI__ASM_BPF_PERF_EVENT_H__
|
||||||
|
#define _UAPI__ASM_BPF_PERF_EVENT_H__
|
||||||
|
|
||||||
|
#include <linux/ptrace.h>
|
||||||
|
|
||||||
|
typedef struct user_pt_regs bpf_user_pt_regs_t;
|
||||||
|
|
||||||
|
#endif /* _UAPI__ASM_BPF_PERF_EVENT_H__ */
|
7
arch/loongarch/net/Makefile
Normal file
7
arch/loongarch/net/Makefile
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
#
|
||||||
|
# Makefile for arch/loongarch/net
|
||||||
|
#
|
||||||
|
# Copyright (C) 2022 Loongson Technology Corporation Limited
|
||||||
|
#
|
||||||
|
obj-$(CONFIG_BPF_JIT) += bpf_jit.o
|
1179
arch/loongarch/net/bpf_jit.c
Normal file
1179
arch/loongarch/net/bpf_jit.c
Normal file
File diff suppressed because it is too large
Load Diff
282
arch/loongarch/net/bpf_jit.h
Normal file
282
arch/loongarch/net/bpf_jit.h
Normal file
@ -0,0 +1,282 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||||
|
/*
|
||||||
|
* BPF JIT compiler for LoongArch
|
||||||
|
*
|
||||||
|
* Copyright (C) 2022 Loongson Technology Corporation Limited
|
||||||
|
*/
|
||||||
|
#include <linux/bpf.h>
|
||||||
|
#include <linux/filter.h>
|
||||||
|
#include <asm/cacheflush.h>
|
||||||
|
#include <asm/inst.h>
|
||||||
|
|
||||||
|
struct jit_ctx {
|
||||||
|
const struct bpf_prog *prog;
|
||||||
|
unsigned int idx;
|
||||||
|
unsigned int flags;
|
||||||
|
unsigned int epilogue_offset;
|
||||||
|
u32 *offset;
|
||||||
|
union loongarch_instruction *image;
|
||||||
|
u32 stack_size;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct jit_data {
|
||||||
|
struct bpf_binary_header *header;
|
||||||
|
u8 *image;
|
||||||
|
struct jit_ctx ctx;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define emit_insn(ctx, func, ...) \
|
||||||
|
do { \
|
||||||
|
if (ctx->image != NULL) { \
|
||||||
|
union loongarch_instruction *insn = &ctx->image[ctx->idx]; \
|
||||||
|
emit_##func(insn, ##__VA_ARGS__); \
|
||||||
|
} \
|
||||||
|
ctx->idx++; \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define is_signed_imm12(val) signed_imm_check(val, 12)
|
||||||
|
#define is_signed_imm14(val) signed_imm_check(val, 14)
|
||||||
|
#define is_signed_imm16(val) signed_imm_check(val, 16)
|
||||||
|
#define is_signed_imm26(val) signed_imm_check(val, 26)
|
||||||
|
#define is_signed_imm32(val) signed_imm_check(val, 32)
|
||||||
|
#define is_signed_imm52(val) signed_imm_check(val, 52)
|
||||||
|
#define is_unsigned_imm12(val) unsigned_imm_check(val, 12)
|
||||||
|
|
||||||
|
static inline int bpf2la_offset(int bpf_insn, int off, const struct jit_ctx *ctx)
|
||||||
|
{
|
||||||
|
/* BPF JMP offset is relative to the next instruction */
|
||||||
|
bpf_insn++;
|
||||||
|
/*
|
||||||
|
* Whereas LoongArch branch instructions encode the offset
|
||||||
|
* from the branch itself, so we must subtract 1 from the
|
||||||
|
* instruction offset.
|
||||||
|
*/
|
||||||
|
return (ctx->offset[bpf_insn + off] - (ctx->offset[bpf_insn] - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int epilogue_offset(const struct jit_ctx *ctx)
|
||||||
|
{
|
||||||
|
int from = ctx->idx;
|
||||||
|
int to = ctx->epilogue_offset;
|
||||||
|
|
||||||
|
return (to - from);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Zero-extend 32 bits into 64 bits */
|
||||||
|
static inline void emit_zext_32(struct jit_ctx *ctx, enum loongarch_gpr reg, bool is32)
|
||||||
|
{
|
||||||
|
if (!is32)
|
||||||
|
return;
|
||||||
|
|
||||||
|
emit_insn(ctx, lu32id, reg, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Signed-extend 32 bits into 64 bits */
|
||||||
|
static inline void emit_sext_32(struct jit_ctx *ctx, enum loongarch_gpr reg, bool is32)
|
||||||
|
{
|
||||||
|
if (!is32)
|
||||||
|
return;
|
||||||
|
|
||||||
|
emit_insn(ctx, addiw, reg, reg, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void move_imm(struct jit_ctx *ctx, enum loongarch_gpr rd, long imm, bool is32)
|
||||||
|
{
|
||||||
|
long imm_11_0, imm_31_12, imm_51_32, imm_63_52, imm_51_0, imm_51_31;
|
||||||
|
|
||||||
|
/* or rd, $zero, $zero */
|
||||||
|
if (imm == 0) {
|
||||||
|
emit_insn(ctx, or, rd, LOONGARCH_GPR_ZERO, LOONGARCH_GPR_ZERO);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* addiw rd, $zero, imm_11_0 */
|
||||||
|
if (is_signed_imm12(imm)) {
|
||||||
|
emit_insn(ctx, addiw, rd, LOONGARCH_GPR_ZERO, imm);
|
||||||
|
goto zext;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ori rd, $zero, imm_11_0 */
|
||||||
|
if (is_unsigned_imm12(imm)) {
|
||||||
|
emit_insn(ctx, ori, rd, LOONGARCH_GPR_ZERO, imm);
|
||||||
|
goto zext;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* lu52id rd, $zero, imm_63_52 */
|
||||||
|
imm_63_52 = (imm >> 52) & 0xfff;
|
||||||
|
imm_51_0 = imm & 0xfffffffffffff;
|
||||||
|
if (imm_63_52 != 0 && imm_51_0 == 0) {
|
||||||
|
emit_insn(ctx, lu52id, rd, LOONGARCH_GPR_ZERO, imm_63_52);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* lu12iw rd, imm_31_12 */
|
||||||
|
imm_31_12 = (imm >> 12) & 0xfffff;
|
||||||
|
emit_insn(ctx, lu12iw, rd, imm_31_12);
|
||||||
|
|
||||||
|
/* ori rd, rd, imm_11_0 */
|
||||||
|
imm_11_0 = imm & 0xfff;
|
||||||
|
if (imm_11_0 != 0)
|
||||||
|
emit_insn(ctx, ori, rd, rd, imm_11_0);
|
||||||
|
|
||||||
|
if (!is_signed_imm32(imm)) {
|
||||||
|
if (imm_51_0 != 0) {
|
||||||
|
/*
|
||||||
|
* If bit[51:31] is all 0 or all 1,
|
||||||
|
* it means bit[51:32] is sign extended by lu12iw,
|
||||||
|
* no need to call lu32id to do a new filled operation.
|
||||||
|
*/
|
||||||
|
imm_51_31 = (imm >> 31) & 0x1fffff;
|
||||||
|
if (imm_51_31 != 0 || imm_51_31 != 0x1fffff) {
|
||||||
|
/* lu32id rd, imm_51_32 */
|
||||||
|
imm_51_32 = (imm >> 32) & 0xfffff;
|
||||||
|
emit_insn(ctx, lu32id, rd, imm_51_32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* lu52id rd, rd, imm_63_52 */
|
||||||
|
if (!is_signed_imm52(imm))
|
||||||
|
emit_insn(ctx, lu52id, rd, rd, imm_63_52);
|
||||||
|
}
|
||||||
|
|
||||||
|
zext:
|
||||||
|
emit_zext_32(ctx, rd, is32);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void move_reg(struct jit_ctx *ctx, enum loongarch_gpr rd,
|
||||||
|
enum loongarch_gpr rj)
|
||||||
|
{
|
||||||
|
emit_insn(ctx, or, rd, rj, LOONGARCH_GPR_ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int invert_jmp_cond(u8 cond)
|
||||||
|
{
|
||||||
|
switch (cond) {
|
||||||
|
case BPF_JEQ:
|
||||||
|
return BPF_JNE;
|
||||||
|
case BPF_JNE:
|
||||||
|
case BPF_JSET:
|
||||||
|
return BPF_JEQ;
|
||||||
|
case BPF_JGT:
|
||||||
|
return BPF_JLE;
|
||||||
|
case BPF_JGE:
|
||||||
|
return BPF_JLT;
|
||||||
|
case BPF_JLT:
|
||||||
|
return BPF_JGE;
|
||||||
|
case BPF_JLE:
|
||||||
|
return BPF_JGT;
|
||||||
|
case BPF_JSGT:
|
||||||
|
return BPF_JSLE;
|
||||||
|
case BPF_JSGE:
|
||||||
|
return BPF_JSLT;
|
||||||
|
case BPF_JSLT:
|
||||||
|
return BPF_JSGE;
|
||||||
|
case BPF_JSLE:
|
||||||
|
return BPF_JSGT;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void cond_jmp_offset(struct jit_ctx *ctx, u8 cond, enum loongarch_gpr rj,
|
||||||
|
enum loongarch_gpr rd, int jmp_offset)
|
||||||
|
{
|
||||||
|
switch (cond) {
|
||||||
|
case BPF_JEQ:
|
||||||
|
/* PC += jmp_offset if rj == rd */
|
||||||
|
emit_insn(ctx, beq, rj, rd, jmp_offset);
|
||||||
|
return;
|
||||||
|
case BPF_JNE:
|
||||||
|
case BPF_JSET:
|
||||||
|
/* PC += jmp_offset if rj != rd */
|
||||||
|
emit_insn(ctx, bne, rj, rd, jmp_offset);
|
||||||
|
return;
|
||||||
|
case BPF_JGT:
|
||||||
|
/* PC += jmp_offset if rj > rd (unsigned) */
|
||||||
|
emit_insn(ctx, bltu, rd, rj, jmp_offset);
|
||||||
|
return;
|
||||||
|
case BPF_JLT:
|
||||||
|
/* PC += jmp_offset if rj < rd (unsigned) */
|
||||||
|
emit_insn(ctx, bltu, rj, rd, jmp_offset);
|
||||||
|
return;
|
||||||
|
case BPF_JGE:
|
||||||
|
/* PC += jmp_offset if rj >= rd (unsigned) */
|
||||||
|
emit_insn(ctx, bgeu, rj, rd, jmp_offset);
|
||||||
|
return;
|
||||||
|
case BPF_JLE:
|
||||||
|
/* PC += jmp_offset if rj <= rd (unsigned) */
|
||||||
|
emit_insn(ctx, bgeu, rd, rj, jmp_offset);
|
||||||
|
return;
|
||||||
|
case BPF_JSGT:
|
||||||
|
/* PC += jmp_offset if rj > rd (signed) */
|
||||||
|
emit_insn(ctx, blt, rd, rj, jmp_offset);
|
||||||
|
return;
|
||||||
|
case BPF_JSLT:
|
||||||
|
/* PC += jmp_offset if rj < rd (signed) */
|
||||||
|
emit_insn(ctx, blt, rj, rd, jmp_offset);
|
||||||
|
return;
|
||||||
|
case BPF_JSGE:
|
||||||
|
/* PC += jmp_offset if rj >= rd (signed) */
|
||||||
|
emit_insn(ctx, bge, rj, rd, jmp_offset);
|
||||||
|
return;
|
||||||
|
case BPF_JSLE:
|
||||||
|
/* PC += jmp_offset if rj <= rd (signed) */
|
||||||
|
emit_insn(ctx, bge, rd, rj, jmp_offset);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void cond_jmp_offs26(struct jit_ctx *ctx, u8 cond, enum loongarch_gpr rj,
|
||||||
|
enum loongarch_gpr rd, int jmp_offset)
|
||||||
|
{
|
||||||
|
cond = invert_jmp_cond(cond);
|
||||||
|
cond_jmp_offset(ctx, cond, rj, rd, 2);
|
||||||
|
emit_insn(ctx, b, jmp_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void uncond_jmp_offs26(struct jit_ctx *ctx, int jmp_offset)
|
||||||
|
{
|
||||||
|
emit_insn(ctx, b, jmp_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int emit_cond_jmp(struct jit_ctx *ctx, u8 cond, enum loongarch_gpr rj,
|
||||||
|
enum loongarch_gpr rd, int jmp_offset)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* A large PC-relative jump offset may overflow the immediate field of
|
||||||
|
* the native conditional branch instruction, triggering a conversion
|
||||||
|
* to use an absolute jump instead, this jump sequence is particularly
|
||||||
|
* nasty. For now, use cond_jmp_offs26() directly to keep it simple.
|
||||||
|
* In the future, maybe we can add support for far branching, the branch
|
||||||
|
* relaxation requires more than two passes to converge, the code seems
|
||||||
|
* too complex to understand, not quite sure whether it is necessary and
|
||||||
|
* worth the extra pain. Anyway, just leave it as it is to enhance code
|
||||||
|
* readability now.
|
||||||
|
*/
|
||||||
|
if (is_signed_imm26(jmp_offset)) {
|
||||||
|
cond_jmp_offs26(ctx, cond, rj, rd, jmp_offset);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int emit_uncond_jmp(struct jit_ctx *ctx, int jmp_offset)
|
||||||
|
{
|
||||||
|
if (is_signed_imm26(jmp_offset)) {
|
||||||
|
uncond_jmp_offs26(ctx, jmp_offset);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int emit_tailcall_jmp(struct jit_ctx *ctx, u8 cond, enum loongarch_gpr rj,
|
||||||
|
enum loongarch_gpr rd, int jmp_offset)
|
||||||
|
{
|
||||||
|
if (is_signed_imm16(jmp_offset)) {
|
||||||
|
cond_jmp_offset(ctx, cond, rj, rd, jmp_offset);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user