mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 10:40:15 +02: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
					
				
					 7 changed files with 1700 additions and 0 deletions
				
			
		| 
						 | 
				
			
			@ -1,5 +1,6 @@
 | 
			
		|||
obj-y += kernel/
 | 
			
		||||
obj-y += mm/
 | 
			
		||||
obj-y += net/
 | 
			
		||||
obj-y += vdso/
 | 
			
		||||
 | 
			
		||||
# for cleaning
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -87,6 +87,7 @@ config LOONGARCH
 | 
			
		|||
	select HAVE_CONTEXT_TRACKING_USER
 | 
			
		||||
	select HAVE_DEBUG_STACKOVERFLOW
 | 
			
		||||
	select HAVE_DMA_CONTIGUOUS
 | 
			
		||||
	select HAVE_EBPF_JIT
 | 
			
		||||
	select HAVE_EXIT_THREAD
 | 
			
		||||
	select HAVE_FAST_GUP
 | 
			
		||||
	select HAVE_GENERIC_VDSO
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -345,4 +345,225 @@ static inline bool unsigned_imm_check(unsigned long val, unsigned int 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 */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										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 a new issue