Skip to content

Commit

Permalink
LoongArch: Add alternative runtime patching mechanism
Browse files Browse the repository at this point in the history
Introduce the "alternative" mechanism from ARM64 and x86 for LoongArch
to apply runtime patching. The main purpose of this patch is to provide
a framework. In future we can use this mechanism (i.e., the ALTERNATIVE
and ALTERNATIVE_2 macros) to optimize hotspot functions according to cpu
features.

Signed-off-by: Jun Yi <yijun@loongson.cn>
Signed-off-by: Huacai Chen <chenhuacai@loongson.cn>
  • Loading branch information
Huacai Chen committed Dec 14, 2022
1 parent 61a6fcc commit 19e5eb1
Show file tree
Hide file tree
Showing 9 changed files with 507 additions and 1 deletion.
82 changes: 82 additions & 0 deletions arch/loongarch/include/asm/alternative-asm.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _ASM_ALTERNATIVE_ASM_H
#define _ASM_ALTERNATIVE_ASM_H

#ifdef __ASSEMBLY__

#include <asm/asm.h>

/*
* Issue one struct alt_instr descriptor entry (need to put it into
* the section .altinstructions, see below). This entry contains
* enough information for the alternatives patching code to patch an
* instruction. See apply_alternatives().
*/
.macro altinstruction_entry orig alt feature orig_len alt_len
.long \orig - .
.long \alt - .
.short \feature
.byte \orig_len
.byte \alt_len
.endm

/*
* Define an alternative between two instructions. If @feature is
* present, early code in apply_alternatives() replaces @oldinstr with
* @newinstr. ".fill" directive takes care of proper instruction padding
* in case @newinstr is longer than @oldinstr.
*/
.macro ALTERNATIVE oldinstr, newinstr, feature
140 :
\oldinstr
141 :
.fill - (((144f-143f)-(141b-140b)) > 0) * ((144f-143f)-(141b-140b)) / 4, 4, 0x03400000
142 :

.pushsection .altinstructions, "a"
altinstruction_entry 140b, 143f, \feature, 142b-140b, 144f-143f
.popsection

.subsection 1
143 :
\newinstr
144 :
.previous
.endm

#define old_len (141b-140b)
#define new_len1 (144f-143f)
#define new_len2 (145f-144f)

#define alt_max_short(a, b) ((a) ^ (((a) ^ (b)) & -(-((a) < (b)))))

/*
* Same as ALTERNATIVE macro above but for two alternatives. If CPU
* has @feature1, it replaces @oldinstr with @newinstr1. If CPU has
* @feature2, it replaces @oldinstr with @feature2.
*/
.macro ALTERNATIVE_2 oldinstr, newinstr1, feature1, newinstr2, feature2
140 :
\oldinstr
141 :
.fill - ((alt_max_short(new_len1, new_len2) - (old_len)) > 0) * \
(alt_max_short(new_len1, new_len2) - (old_len)) / 4, 4, 0x03400000
142 :

.pushsection .altinstructions, "a"
altinstruction_entry 140b, 143f, \feature1, 142b-140b, 144f-143f, 142b-141b
altinstruction_entry 140b, 144f, \feature2, 142b-140b, 145f-144f, 142b-141b
.popsection

.subsection 1
143 :
\newinstr1
144 :
\newinstr2
145 :
.previous
.endm

#endif /* __ASSEMBLY__ */

#endif /* _ASM_ALTERNATIVE_ASM_H */
111 changes: 111 additions & 0 deletions arch/loongarch/include/asm/alternative.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _ASM_ALTERNATIVE_H
#define _ASM_ALTERNATIVE_H

#ifndef __ASSEMBLY__

#include <linux/types.h>
#include <linux/stddef.h>
#include <linux/stringify.h>
#include <asm/asm.h>

struct alt_instr {
s32 instr_offset; /* offset to original instruction */
s32 replace_offset; /* offset to replacement instruction */
u16 feature; /* feature bit set for replacement */
u8 instrlen; /* length of original instruction */
u8 replacementlen; /* length of new instruction */
} __packed;

/*
* Debug flag that can be tested to see whether alternative
* instructions were patched in already:
*/
extern int alternatives_patched;
extern struct alt_instr __alt_instructions[], __alt_instructions_end[];

extern void alternative_instructions(void);
extern void apply_alternatives(struct alt_instr *start, struct alt_instr *end);

#define b_replacement(num) "664"#num
#define e_replacement(num) "665"#num

#define alt_end_marker "663"
#define alt_slen "662b-661b"
#define alt_total_slen alt_end_marker"b-661b"
#define alt_rlen(num) e_replacement(num)"f-"b_replacement(num)"f"

#define __OLDINSTR(oldinstr, num) \
"661:\n\t" oldinstr "\n662:\n" \
".fill -(((" alt_rlen(num) ")-(" alt_slen ")) > 0) * " \
"((" alt_rlen(num) ")-(" alt_slen ")) / 4, 4, 0x03400000\n"

#define OLDINSTR(oldinstr, num) \
__OLDINSTR(oldinstr, num) \
alt_end_marker ":\n"

#define alt_max_short(a, b) "((" a ") ^ (((" a ") ^ (" b ")) & -(-((" a ") < (" b ")))))"

/*
* Pad the second replacement alternative with additional NOPs if it is
* additionally longer than the first replacement alternative.
*/
#define OLDINSTR_2(oldinstr, num1, num2) \
"661:\n\t" oldinstr "\n662:\n" \
".fill -((" alt_max_short(alt_rlen(num1), alt_rlen(num2)) " - (" alt_slen ")) > 0) * " \
"(" alt_max_short(alt_rlen(num1), alt_rlen(num2)) " - (" alt_slen ")) / 4, " \
"4, 0x03400000\n" \
alt_end_marker ":\n"

#define ALTINSTR_ENTRY(feature, num) \
" .long 661b - .\n" /* label */ \
" .long " b_replacement(num)"f - .\n" /* new instruction */ \
" .short " __stringify(feature) "\n" /* feature bit */ \
" .byte " alt_total_slen "\n" /* source len */ \
" .byte " alt_rlen(num) "\n" /* replacement len */

#define ALTINSTR_REPLACEMENT(newinstr, feature, num) /* replacement */ \
b_replacement(num)":\n\t" newinstr "\n" e_replacement(num) ":\n\t"

/* alternative assembly primitive: */
#define ALTERNATIVE(oldinstr, newinstr, feature) \
OLDINSTR(oldinstr, 1) \
".pushsection .altinstructions,\"a\"\n" \
ALTINSTR_ENTRY(feature, 1) \
".popsection\n" \
".subsection 1\n" \
ALTINSTR_REPLACEMENT(newinstr, feature, 1) \
".previous\n"

#define ALTERNATIVE_2(oldinstr, newinstr1, feature1, newinstr2, feature2)\
OLDINSTR_2(oldinstr, 1, 2) \
".pushsection .altinstructions,\"a\"\n" \
ALTINSTR_ENTRY(feature1, 1) \
ALTINSTR_ENTRY(feature2, 2) \
".popsection\n" \
".subsection 1\n" \
ALTINSTR_REPLACEMENT(newinstr1, feature1, 1) \
ALTINSTR_REPLACEMENT(newinstr2, feature2, 2) \
".previous\n"

/*
* Alternative instructions for different CPU types or capabilities.
*
* This allows to use optimized instructions even on generic binary
* kernels.
*
* length of oldinstr must be longer or equal the length of newinstr
* It can be padded with nops as needed.
*
* For non barrier like inlines please define new variants
* without volatile and memory clobber.
*/
#define alternative(oldinstr, newinstr, feature) \
(asm volatile (ALTERNATIVE(oldinstr, newinstr, feature) : : : "memory"))

#define alternative_2(oldinstr, newinstr1, feature1, newinstr2, feature2) \
(asm volatile(ALTERNATIVE_2(oldinstr, newinstr1, feature1, newinstr2, feature2) ::: "memory"))

#endif /* __ASSEMBLY__ */

#endif /* _ASM_ALTERNATIVE_H */
15 changes: 15 additions & 0 deletions arch/loongarch/include/asm/bugs.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* This is included by init/main.c to check for architecture-dependent bugs.
*
* Copyright (C) 2020-2022 Loongson Technology Corporation Limited
*/
#ifndef _ASM_BUGS_H
#define _ASM_BUGS_H

#include <asm/cpu.h>
#include <asm/cpu-info.h>

extern void check_bugs(void);

#endif /* _ASM_BUGS_H */
18 changes: 18 additions & 0 deletions arch/loongarch/include/asm/inst.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <linux/types.h>
#include <asm/asm.h>

#define INSN_NOP 0x03400000
#define INSN_BREAK 0x002a0000

#define ADDR_IMMMASK_LU52ID 0xFFF0000000000000
Expand All @@ -28,13 +29,16 @@ enum reg0i26_op {
enum reg1i20_op {
lu12iw_op = 0x0a,
lu32id_op = 0x0b,
pcaddi_op = 0x0c,
pcaddu12i_op = 0x0e,
pcaddu18i_op = 0x0f,
};

enum reg1i21_op {
beqz_op = 0x10,
bnez_op = 0x11,
bceqz_op = 0x12, /* bits[9:8] = 0x00 */
bcnez_op = 0x12, /* bits[9:8] = 0x01 */
};

enum reg2_op {
Expand Down Expand Up @@ -315,6 +319,12 @@ static inline bool is_imm_negative(unsigned long val, unsigned int bit)
return val & (1UL << (bit - 1));
}

static inline bool is_pc_ins(union loongarch_instruction *ip)
{
return ip->reg1i20_format.opcode >= pcaddi_op &&
ip->reg1i20_format.opcode <= pcaddu18i_op;
}

static inline bool is_branch_ins(union loongarch_instruction *ip)
{
return ip->reg1i21_format.opcode >= beqz_op &&
Expand Down Expand Up @@ -353,6 +363,14 @@ static inline bool unsigned_imm_check(unsigned long val, unsigned int bit)
return val < (1UL << bit);
}

static inline unsigned long sign_extend(unsigned long val, unsigned int idx)
{
if (!is_imm_negative(val, idx + 1))
return ((1UL << idx) - 1) & val;
else
return ~((1UL << idx) - 1) | val;
}

#define DEF_EMIT_REG0I26_FORMAT(NAME, OP) \
static inline void emit_##NAME(union loongarch_instruction *insn, \
int offset) \
Expand Down
2 changes: 1 addition & 1 deletion arch/loongarch/kernel/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ extra-y := vmlinux.lds
obj-y += head.o cpu-probe.o cacheinfo.o env.o setup.o entry.o genex.o \
traps.o irq.o idle.o process.o dma.o mem.o io.o reset.o switch.o \
elf.o syscall.o signal.o time.o topology.o inst.o ptrace.o vdso.o \
unaligned.o
alternative.o unaligned.o

obj-$(CONFIG_ACPI) += acpi.o
obj-$(CONFIG_EFI) += efi.o
Expand Down
Loading

0 comments on commit 19e5eb1

Please sign in to comment.