There are various problems and short-comings with the current
static_key interface:

 - static_key_{true,false}() read like a branch depending on the key
   value, instead of the actual likely/unlikely branch depending on
   init value.

 - static_key_{true,false}() are, as stated above, tied to the
   static_key init values STATIC_KEY_INIT_{TRUE,FALSE}.

 - we're limited to the 2 (out of 4) possible options that compile to
   a default NOP because that's what our arch_static_branch() assembly
   emits.

So provide a new static_key interface:

  DEFINE_STATIC_KEY_TRUE(name);
  DEFINE_STATIC_KEY_FALSE(name);

Which define a key of different types with an initial true/false
value.

Then allow:

   static_branch_likely()
   static_branch_unlikely()

to take a key of either type and emit the right instruction for the
case.

This means adding a second arch_static_branch_jump() assembly helper
which emits a JMP per default.

In order to determine the right instruction for the right state,
encode the branch type in the LSB of jump_entry::key.

Signed-off-by: Peter Zijlstra (Intel) <pet...@infradead.org>
---
 arch/arm/include/asm/jump_label.h     |   24 +++--
 arch/arm64/include/asm/jump_label.h   |   18 +++-
 arch/mips/include/asm/jump_label.h    |   19 ++++
 arch/powerpc/include/asm/jump_label.h |   19 ++++
 arch/s390/include/asm/jump_label.h    |   19 ++++
 arch/sparc/include/asm/jump_label.h   |   35 ++++++--
 arch/x86/include/asm/jump_label.h     |   21 ++++
 include/linux/jump_label.h            |  143 ++++++++++++++++++++++++++++++++--
 kernel/jump_label.c                   |   35 ++++++--
 9 files changed, 294 insertions(+), 39 deletions(-)

--- a/arch/arm/include/asm/jump_label.h
+++ b/arch/arm/include/asm/jump_label.h
@@ -7,20 +7,28 @@
 
 #define JUMP_LABEL_NOP_SIZE 4
 
-#ifdef CONFIG_THUMB2_KERNEL
-#define JUMP_LABEL_NOP "nop.w"
-#else
-#define JUMP_LABEL_NOP "nop"
-#endif
+static __always_inline bool arch_static_branch(struct static_key *key, bool 
branch)
+{
+       asm_volatile_goto("1:\n\t"
+                WASM(nop) "\n\t"
+                ".pushsection __jump_table,  \"aw\"\n\t"
+                ".word 1b, %l[l_yes], %c0\n\t"
+                ".popsection\n\t"
+                : :  "i" (&((char *)key)[branch]) :  : l_yes);
+
+       return false;
+l_yes:
+       return true;
+}
 
-static __always_inline bool arch_static_branch(struct static_key *key)
+static __always_inline bool arch_static_branch_jump(struct static_key *key, 
bool branch)
 {
        asm_volatile_goto("1:\n\t"
-                JUMP_LABEL_NOP "\n\t"
+                WASM(b) " %l[l_yes]\n\t"
                 ".pushsection __jump_table,  \"aw\"\n\t"
                 ".word 1b, %l[l_yes], %c0\n\t"
                 ".popsection\n\t"
-                : :  "i" (key) :  : l_yes);
+                : :  "i" (&((char *)key)[branch]) :  : l_yes);
 
        return false;
 l_yes:
--- a/arch/arm64/include/asm/jump_label.h
+++ b/arch/arm64/include/asm/jump_label.h
@@ -26,14 +26,28 @@
 
 #define JUMP_LABEL_NOP_SIZE            AARCH64_INSN_SIZE
 
-static __always_inline bool arch_static_branch(struct static_key *key)
+static __always_inline bool arch_static_branch(struct static_key *key, bool 
branch)
 {
        asm goto("1: nop\n\t"
                 ".pushsection __jump_table,  \"aw\"\n\t"
                 ".align 3\n\t"
                 ".quad 1b, %l[l_yes], %c0\n\t"
                 ".popsection\n\t"
-                :  :  "i"(key) :  : l_yes);
+                :  :  "i"(&((char *)key)[branch]) :  : l_yes);
+
+       return false;
+l_yes:
+       return true;
+}
+
+static __always_inline bool arch_static_branch_jump(struct static_key *key, 
bool branch)
+{
+       asm goto("1: b %l[l_yes]\n\t"
+                ".pushsection __jump_table,  \"aw\"\n\t"
+                ".align 3\n\t"
+                ".quad 1b, %l[l_yes], %c0\n\t"
+                ".popsection\n\t"
+                :  :  "i"(&((char *)key)[branch]) :  : l_yes);
 
        return false;
 l_yes:
--- a/arch/mips/include/asm/jump_label.h
+++ b/arch/mips/include/asm/jump_label.h
@@ -26,14 +26,29 @@
 #define NOP_INSN "nop"
 #endif
 
-static __always_inline bool arch_static_branch(struct static_key *key)
+static __always_inline bool arch_static_branch(struct static_key *key, bool 
branch)
 {
        asm_volatile_goto("1:\t" NOP_INSN "\n\t"
                "nop\n\t"
                ".pushsection __jump_table,  \"aw\"\n\t"
                WORD_INSN " 1b, %l[l_yes], %0\n\t"
                ".popsection\n\t"
-               : :  "i" (key) : : l_yes);
+               : :  "i" (&((char *)key)[branch]) : : l_yes);
+
+       return false;
+l_yes:
+       return true;
+}
+
+static __always_inline bool arch_static_branch_jump(struct static_key *key, 
bool branch)
+{
+       asm_volatile_goto("1:\tj %l[l_yes]\n\t"
+               "nop\n\t"
+               ".pushsection __jump_table,  \"aw\"\n\t"
+               WORD_INSN " 1b, %l[l_yes], %0\n\t"
+               ".popsection\n\t"
+               : :  "i" (&((char *)key)[branch]) : : l_yes);
+
        return false;
 l_yes:
        return true;
--- a/arch/powerpc/include/asm/jump_label.h
+++ b/arch/powerpc/include/asm/jump_label.h
@@ -18,14 +18,29 @@
 #define JUMP_ENTRY_TYPE                stringify_in_c(FTR_ENTRY_LONG)
 #define JUMP_LABEL_NOP_SIZE    4
 
-static __always_inline bool arch_static_branch(struct static_key *key)
+static __always_inline bool arch_static_branch(struct static_key *key, bool 
branch)
 {
        asm_volatile_goto("1:\n\t"
                 "nop\n\t"
                 ".pushsection __jump_table,  \"aw\"\n\t"
                 JUMP_ENTRY_TYPE "1b, %l[l_yes], %c0\n\t"
                 ".popsection \n\t"
-                : :  "i" (key) : : l_yes);
+                : :  "i" (&((char *)key)[branch]) : : l_yes);
+
+       return false;
+l_yes:
+       return true;
+}
+
+static __always_inline bool arch_static_branch_jump(struct static_key *key, 
bool branch)
+{
+       asm_volatile_goto("1:\n\t"
+                "b %l[l_yes]\n\t"
+                ".pushsection __jump_table,  \"aw\"\n\t"
+                JUMP_ENTRY_TYPE "1b, %l[l_yes], %c0\n\t"
+                ".popsection \n\t"
+                : :  "i" (&((char *)key)[branch]) : : l_yes);
+
        return false;
 l_yes:
        return true;
--- a/arch/s390/include/asm/jump_label.h
+++ b/arch/s390/include/asm/jump_label.h
@@ -12,14 +12,29 @@
  * We use a brcl 0,2 instruction for jump labels at compile time so it
  * can be easily distinguished from a hotpatch generated instruction.
  */
-static __always_inline bool arch_static_branch(struct static_key *key)
+static __always_inline bool arch_static_branch(struct static_key *key, bool 
branch)
 {
        asm_volatile_goto("0:   brcl 0,"__stringify(JUMP_LABEL_NOP_OFFSET)"\n"
                ".pushsection __jump_table, \"aw\"\n"
                ".balign 8\n"
                ".quad 0b, %l[label], %0\n"
                ".popsection\n"
-               : : "X" (key) : : label);
+               : : "X" (&((char *)key)[branch]) : : label);
+
+       return false;
+label:
+       return true;
+}
+
+static __always_inline bool arch_static_branch_jump(struct static_key *key, 
bool branch)
+{
+       asm_volatile_goto("0:   brcl 15, %l[label]\n"
+               ".pushsection __jump_table, \"aw\"\n"
+               ".balign 8\n"
+               ".quad 0b, %l[label], %0\n"
+               ".popsection\n"
+               : : "X" (&((char *)key)[branch]) : : label);
+
        return false;
 label:
        return true;
--- a/arch/sparc/include/asm/jump_label.h
+++ b/arch/sparc/include/asm/jump_label.h
@@ -7,16 +7,33 @@
 
 #define JUMP_LABEL_NOP_SIZE 4
 
-static __always_inline bool arch_static_branch(struct static_key *key)
+static __always_inline bool arch_static_branch(struct static_key *key, bool 
branch)
 {
-               asm_volatile_goto("1:\n\t"
-                        "nop\n\t"
-                        "nop\n\t"
-                        ".pushsection __jump_table,  \"aw\"\n\t"
-                        ".align 4\n\t"
-                        ".word 1b, %l[l_yes], %c0\n\t"
-                        ".popsection \n\t"
-                        : :  "i" (key) : : l_yes);
+       asm_volatile_goto("1:\n\t"
+                "nop\n\t"
+                "nop\n\t"
+                ".pushsection __jump_table,  \"aw\"\n\t"
+                ".align 4\n\t"
+                ".word 1b, %l[l_yes], %c0\n\t"
+                ".popsection \n\t"
+                : :  "i" (&((char *)key)[branch]) : : l_yes);
+
+       return false;
+l_yes:
+       return true;
+}
+
+static __always_inline bool arch_static_branch_jump(struct static_key *key, 
bool branch)
+{
+       asm_volatile_goto("1:\n\t"
+                "b %l[l_yes]\n\t"
+                "nop\n\t"
+                ".pushsection __jump_table,  \"aw\"\n\t"
+                ".align 4\n\t"
+                ".word 1b, %l[l_yes], %c0\n\t"
+                ".popsection \n\t"
+                : :  "i" (&((char *)key)[branch]) : : l_yes);
+
        return false;
 l_yes:
        return true;
--- a/arch/x86/include/asm/jump_label.h
+++ b/arch/x86/include/asm/jump_label.h
@@ -16,7 +16,7 @@
 # define STATIC_KEY_INIT_NOP GENERIC_NOP5_ATOMIC
 #endif
 
-static __always_inline bool arch_static_branch(struct static_key *key)
+static __always_inline bool arch_static_branch(struct static_key *key, bool 
branch)
 {
        asm_volatile_goto("1:"
                ".byte " __stringify(STATIC_KEY_INIT_NOP) "\n\t"
@@ -24,7 +24,24 @@ static __always_inline bool arch_static_
                _ASM_ALIGN "\n\t"
                _ASM_PTR "1b, %l[l_yes], %c0 \n\t"
                ".popsection \n\t"
-               : :  "i" (key) : : l_yes);
+               : :  "i" (&((char *)key)[branch]) : : l_yes);
+
+       return false;
+l_yes:
+       return true;
+}
+
+static __always_inline bool arch_static_branch_jump(struct static_key *key, 
bool branch)
+{
+       asm_volatile_goto("1:"
+               ".byte 0xe9\n\t .long %l[l_yes] - 2f\n\t"
+               "2:\n\t"
+               ".pushsection __jump_table,  \"aw\" \n\t"
+               _ASM_ALIGN "\n\t"
+               _ASM_PTR "1b, %l[l_yes], %c0 \n\t"
+               ".popsection \n\t"
+               : :  "i" (&((char *)key)[branch]) : : l_yes);
+
        return false;
 l_yes:
        return true;
--- a/include/linux/jump_label.h
+++ b/include/linux/jump_label.h
@@ -107,12 +107,12 @@ static inline int static_key_count(struc
 
 static __always_inline bool static_key_false(struct static_key *key)
 {
-       return arch_static_branch(key);
+       return arch_static_branch(key, false);
 }
 
 static __always_inline bool static_key_true(struct static_key *key)
 {
-       return !static_key_false(key);
+       return !arch_static_branch(key, true);
 }
 
 extern struct jump_entry __start___jump_table[];
@@ -130,12 +130,12 @@ extern void static_key_slow_inc(struct s
 extern void static_key_slow_dec(struct static_key *key);
 extern void jump_label_apply_nops(struct module *mod);
 
-#define STATIC_KEY_INIT_TRUE ((struct static_key)              \
+#define STATIC_KEY_INIT_TRUE                                   \
        { .enabled = ATOMIC_INIT(1),                            \
-         .entries = (void *)JUMP_TYPE_TRUE })
-#define STATIC_KEY_INIT_FALSE ((struct static_key)             \
+         .entries = (void *)JUMP_TYPE_TRUE }
+#define STATIC_KEY_INIT_FALSE                                  \
        { .enabled = ATOMIC_INIT(0),                            \
-         .entries = (void *)JUMP_TYPE_FALSE })
+         .entries = (void *)JUMP_TYPE_FALSE }
 
 #else  /* !HAVE_JUMP_LABEL */
 
@@ -218,6 +218,137 @@ static inline void static_key_disable(st
                static_key_slow_dec(key);
 }
 
+/* -------------------------------------------------------------------------- 
*/
+
+/*
+ * Two type wrappers around static_key, such that we can use compile time
+ * type differentiation to emit the right code.
+ *
+ * All the below code is macros in order to play type games.
+ */
+
+struct static_key_true {
+       struct static_key key;
+};
+
+struct static_key_false {
+       struct static_key key;
+};
+
+#define STATIC_KEY_TRUE_INIT  (struct static_key_true) { .key = 
STATIC_KEY_INIT_TRUE,  }
+#define STATIC_KEY_FALSE_INIT (struct static_key_false){ .key = 
STATIC_KEY_INIT_FALSE, }
+
+#define DEFINE_STATIC_KEY_TRUE(name)   \
+       struct static_key_true name = STATIC_KEY_TRUE_INIT
+
+#define DEFINE_STATIC_KEY_FALSE(name)  \
+       struct static_key_false name = STATIC_KEY_FALSE_INIT
+
+#ifdef HAVE_JUMP_LABEL
+
+/*
+ * Combine the right initial value (type) with the right branch order
+ * to generate the desired result.
+ *
+ *
+ * type\branch|        likely (1)            | unlikely (0)
+ * -----------+-----------------------+------------------
+ *            |                       |
+ *  true (1)  |           ...                |    ...
+ *            |    NOP               |    JMP L
+ *            |    <br-stmts>        | 1: ...
+ *            |        L: ...                |
+ *            |                              |
+ *            |                              | L: <br-stmts>
+ *            |                              |    jmp 1b
+ *            |                       |
+ * -----------+-----------------------+------------------
+ *            |                       |
+ *  false (0) |           ...                |    ...
+ *            |    JMP L             |    NOP
+ *            |    <br-stmts>        | 1: ...
+ *            |        L: ...                |
+ *            |                              |
+ *            |                              | L: <br-stmts>
+ *            |                              |    jmp 1b
+ *            |                       |
+ * -----------+-----------------------+------------------
+ *
+ * The initial value is encoded in the LSB of static_key::entries,
+ * type: 0 = false, 1 = true.
+ *
+ * The branch type is encoded in the LSB of jump_entry::key,
+ * branch: 0 = unlikely, 1 = likely.
+ *
+ * This gives the following logic table:
+ *
+ *     enabled type    branch    instuction
+ * -----------------------------+-----------
+ *     0       0       0       | NOP
+ *     0       0       1       | JMP
+ *     0       1       0       | NOP
+ *     0       1       1       | JMP
+ *
+ *     1       0       0       | JMP
+ *     1       0       1       | NOP
+ *     1       1       0       | JMP
+ *     1       1       1       | NOP
+ *
+ * Which gives the following functions:
+ *
+ *   dynamic: instruction = enabled ^ branch
+ *   static:  instruction = type ^ branch
+ *
+ * See jump_label_type() / jump_label_init_type().
+ */
+
+extern bool ____wrong_branch_error(void);
+
+#define static_branch_likely(x)                                                
        \
+({                                                                             
\
+       bool branch;                                                            
\
+       if (__builtin_types_compatible_p(typeof(*x), struct static_key_true))   
\
+               branch = !arch_static_branch(&(x)->key, true);                  
\
+       else if (__builtin_types_compatible_p(typeof(*x), struct 
static_key_false)) \
+               branch = !arch_static_branch_jump(&(x)->key, true);             
\
+       else                                                                    
\
+               branch = ____wrong_branch_error();                              
\
+       branch;                                                                 
\
+})
+
+#define static_branch_unlikely(x)                                              
\
+({                                                                             
\
+       bool branch;                                                            
\
+       if (__builtin_types_compatible_p(typeof(*x), struct static_key_true))   
\
+               branch = arch_static_branch_jump(&(x)->key, false);             
\
+       else if (__builtin_types_compatible_p(typeof(*x), struct 
static_key_false)) \
+               branch = arch_static_branch(&(x)->key, false);                  
\
+       else                                                                    
\
+               branch = ____wrong_branch_error();                              
\
+       branch;                                                                 
\
+})
+
+#else /* !HAVE_JUMP_LABEL */
+
+#define static_branch_likely(x)                
likely(static_key_enabled(&(x)->key))
+#define static_branch_unlikely(x)      unlikely(static_key_enabled(&(x)->key))
+
+#endif /* HAVE_JUMP_LABEL */
+
+/*
+ * Advanced usage; refcount, branch is enabled when: count != 0
+ */
+
+#define static_branch_inc(x)           static_key_slow_inc(&(x)->key)
+#define static_branch_dec(x)           static_key_slow_dec(&(x)->key)
+
+/*
+ * Normal usage; boolean enable/disable.
+ */
+
+#define static_branch_enable(x)                static_key_enable(&(x)->key)
+#define static_branch_disable(x)       static_key_disable(&(x)->key)
+
 #endif /* _LINUX_JUMP_LABEL_H */
 
 #endif /* __ASSEMBLY__ */
--- a/kernel/jump_label.c
+++ b/kernel/jump_label.c
@@ -165,16 +165,32 @@ static inline bool static_key_type(struc
 
 static inline struct static_key *jump_entry_key(struct jump_entry *entry)
 {
-       return (struct static_key *)((unsigned long)entry->key);
+       return (struct static_key *)((unsigned long)entry->key & ~1UL);
+}
+
+static bool jump_entry_branch(struct jump_entry *entry)
+{
+       return (unsigned long)entry->key & 1UL;
 }
 
 static enum jump_label_type jump_label_type(struct jump_entry *entry)
 {
        struct static_key *key = jump_entry_key(entry);
        bool enabled = static_key_enabled(key);
+       bool branch = jump_entry_branch(entry);
+
+       /* See the comment in linux/jump_label.h */
+       return enabled ^ branch;
+}
+
+static enum jump_label_type jump_label_init_type(struct jump_entry *entry)
+{
+       struct static_key *key = jump_entry_key(entry);
        bool type = static_key_type(key);
+       bool branch = jump_entry_branch(entry);
 
-       return enabled ^ type;
+       /* See the comment in linux/jump_label.h */
+       return type ^ branch;
 }
 
 static void __jump_label_update(struct static_key *key,
@@ -205,7 +221,10 @@ void __init jump_label_init(void)
        for (iter = iter_start; iter < iter_stop; iter++) {
                struct static_key *iterk;
 
-               arch_jump_label_transform_static(iter, jump_label_type(iter));
+               /* rewrite NOPs */
+               if (jump_label_type(iter) == JUMP_LABEL_NOP)
+                       arch_jump_label_transform_static(iter, JUMP_LABEL_NOP);
+
                iterk = jump_entry_key(iter);
                if (iterk == key)
                        continue;
@@ -276,8 +295,11 @@ void jump_label_apply_nops(struct module
        if (iter_start == iter_stop)
                return;
 
-       for (iter = iter_start; iter < iter_stop; iter++)
-               arch_jump_label_transform_static(iter, JUMP_LABEL_NOP);
+       for (iter = iter_start; iter < iter_stop; iter++) {
+               /* Only write NOPs for arch_branch_static(). */
+               if (jump_label_init_type(iter) == JUMP_LABEL_NOP)
+                       arch_jump_label_transform_static(iter, JUMP_LABEL_NOP);
+       }
 }
 
 static int jump_label_add_module(struct module *mod)
@@ -318,7 +340,8 @@ static int jump_label_add_module(struct
                jlm->next = key->next;
                key->next = jlm;
 
-               if (jump_label_type(iter) == JUMP_LABEL_JMP)
+               /* Only update if we've changed from our initial state */
+               if (jump_label_type(iter) != jump_label_init_type(iter))
                        __jump_label_update(key, iter, iter_stop);
        }
 


--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to