From c6b72be421ded17e0c156070ba6e90aa6c335ed6 Mon Sep 17 00:00:00 2001 From: hjl Date: Tue, 16 Jan 2018 10:59:42 +0000 Subject: [PATCH 5/9] x86: Add -mindirect-branch= Add -mindirect-branch= option to convert indirect call and jump to call and return thunks. The default is 'keep', which keeps indirect call and jump unmodified. 'thunk' converts indirect call and jump to call and return thunk. 'thunk-inline' converts indirect call and jump to inlined call and return thunk. 'thunk-extern' converts indirect call and jump to external call and return thunk provided in a separate object file. You can control this behavior for a specific function by using the function attribute indirect_branch. 2 kinds of thunks are geneated. Memory thunk where the function address is at the top of the stack: __x86_indirect_thunk: call L2 L1: pause lfence jmp L1 L2: lea 8(%rsp), %rsp|lea 4(%esp), %esp ret Indirect jmp via memory, "jmp mem", is converted to push memory jmp __x86_indirect_thunk Indirect call via memory, "call mem", is converted to jmp L2 L1: push [mem] jmp __x86_indirect_thunk L2: call L1 Register thunk where the function address is in a register, reg: __x86_indirect_thunk_reg: call L2 L1: pause lfence jmp L1 L2: movq %reg, (%rsp)|movl %reg, (%esp) ret where reg is one of (r|e)ax, (r|e)dx, (r|e)cx, (r|e)bx, (r|e)si, (r|e)di, (r|e)bp, r8, r9, r10, r11, r12, r13, r14 and r15. Indirect jmp via register, "jmp reg", is converted to jmp __x86_indirect_thunk_reg Indirect call via register, "call reg", is converted to call __x86_indirect_thunk_reg gcc/ Backport from mainline * config/i386/i386-opts.h (indirect_branch): New. * config/i386/i386-protos.h (ix86_output_indirect_jmp): Likewise. * config/i386/i386.c (ix86_using_red_zone): Disallow red-zone with local indirect jump when converting indirect call and jump. (ix86_set_indirect_branch_type): New. (ix86_set_current_function): Call ix86_set_indirect_branch_type. (indirectlabelno): New. (indirect_thunk_needed): Likewise. (indirect_thunk_bnd_needed): Likewise. (indirect_thunks_used): Likewise. (indirect_thunks_bnd_used): Likewise. (INDIRECT_LABEL): Likewise. (indirect_thunk_name): Likewise. (output_indirect_thunk): Likewise. (output_indirect_thunk_function): Likewise. (ix86_output_indirect_branch_via_reg): Likewise. (ix86_output_indirect_branch_via_push): Likewise. (ix86_output_indirect_branch): Likewise. (ix86_output_indirect_jmp): Likewise. (ix86_code_end): Call output_indirect_thunk_function if needed. (ix86_output_call_insn): Call ix86_output_indirect_branch if needed. (ix86_handle_fndecl_attribute): Handle indirect_branch. (ix86_attribute_table): Add indirect_branch. * config/i386/i386.h (machine_function): Add indirect_branch_type and has_local_indirect_jump. * config/i386/i386.md (indirect_jump): Set has_local_indirect_jump to true. (tablejump): Likewise. (*indirect_jump): Use ix86_output_indirect_jmp. (*tablejump_1): Likewise. (simple_return_indirect_internal): Likewise. * config/i386/i386.opt (mindirect-branch=): New option. (indirect_branch): New. (keep): Likewise. (thunk): Likewise. (thunk-inline): Likewise. (thunk-extern): Likewise. * doc/extend.texi: Document indirect_branch function attribute. * doc/invoke.texi: Document -mindirect-branch= option. gcc/testsuite/ Backport from mainline * gcc.target/i386/indirect-thunk-1.c: New test. * gcc.target/i386/indirect-thunk-2.c: Likewise. * gcc.target/i386/indirect-thunk-3.c: Likewise. * gcc.target/i386/indirect-thunk-4.c: Likewise. * gcc.target/i386/indirect-thunk-5.c: Likewise. * gcc.target/i386/indirect-thunk-6.c: Likewise. * gcc.target/i386/indirect-thunk-7.c: Likewise. * gcc.target/i386/indirect-thunk-attr-1.c: Likewise. * gcc.target/i386/indirect-thunk-attr-2.c: Likewise. * gcc.target/i386/indirect-thunk-attr-3.c: Likewise. * gcc.target/i386/indirect-thunk-attr-4.c: Likewise. * gcc.target/i386/indirect-thunk-attr-5.c: Likewise. * gcc.target/i386/indirect-thunk-attr-6.c: Likewise. * gcc.target/i386/indirect-thunk-attr-7.c: Likewise. * gcc.target/i386/indirect-thunk-attr-8.c: Likewise. * gcc.target/i386/indirect-thunk-bnd-1.c: Likewise. * gcc.target/i386/indirect-thunk-bnd-2.c: Likewise. * gcc.target/i386/indirect-thunk-bnd-3.c: Likewise. * gcc.target/i386/indirect-thunk-bnd-4.c: Likewise. * gcc.target/i386/indirect-thunk-extern-1.c: Likewise. * gcc.target/i386/indirect-thunk-extern-2.c: Likewise. * gcc.target/i386/indirect-thunk-extern-3.c: Likewise. * gcc.target/i386/indirect-thunk-extern-4.c: Likewise. * gcc.target/i386/indirect-thunk-extern-5.c: Likewise. * gcc.target/i386/indirect-thunk-extern-6.c: Likewise. * gcc.target/i386/indirect-thunk-extern-7.c: Likewise. * gcc.target/i386/indirect-thunk-inline-1.c: Likewise. * gcc.target/i386/indirect-thunk-inline-2.c: Likewise. * gcc.target/i386/indirect-thunk-inline-3.c: Likewise. * gcc.target/i386/indirect-thunk-inline-4.c: Likewise. * gcc.target/i386/indirect-thunk-inline-5.c: Likewise. * gcc.target/i386/indirect-thunk-inline-6.c: Likewise. * gcc.target/i386/indirect-thunk-inline-7.c: Likewise. git-svn-id: svn+ssh://gcc.gnu.org/svn/gcc/branches/gcc-7-branch@256732 138bc75d-0d04-0410-961f-82ee72b054a4 [UBUNTU NOTES: Updated for gcc-5.4 to include defines for FIRST_INT_REG, LAST_INT_REG, and LEGACY_INT_REGNO_P as defined in https://gcc.gnu.org/viewcvs/gcc?view=revision&revision=222269. Dropped indirect-thunk-5.c, indirect-thunk-6.c, indirect-thunk-bnd-3.c, indirect-thunk-bnd-4.c, indirect-thunk-extern-5.c, indirect-thunk-extern-6.c, indirect-thunk-inline-5.c, and indirect-thunk-inline-6.c tests due to gcc 5.4 and earlier not supporting the -fno-plt option. --sbeattie, tyhicks] --- src/gcc/config/i386/i386-opts.h | 13 src/gcc/config/i386/i386-protos.h | 1 src/gcc/config/i386/i386.c | 621 +++++++++++- src/gcc/config/i386/i386.h | 12 src/gcc/config/i386/i386.md | 26 src/gcc/config/i386/i386.opt | 20 src/gcc/doc/extend.texi | 10 src/gcc/doc/invoke.texi | 14 src/gcc/testsuite/gcc.target/i386/indirect-thunk-1.c | 20 src/gcc/testsuite/gcc.target/i386/indirect-thunk-2.c | 20 src/gcc/testsuite/gcc.target/i386/indirect-thunk-3.c | 21 src/gcc/testsuite/gcc.target/i386/indirect-thunk-4.c | 21 src/gcc/testsuite/gcc.target/i386/indirect-thunk-7.c | 44 src/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-1.c | 23 src/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-2.c | 21 src/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-3.c | 23 src/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-4.c | 22 src/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-5.c | 22 src/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-6.c | 21 src/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-7.c | 44 src/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-8.c | 42 src/gcc/testsuite/gcc.target/i386/indirect-thunk-bnd-1.c | 20 src/gcc/testsuite/gcc.target/i386/indirect-thunk-bnd-2.c | 21 src/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-1.c | 19 src/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-2.c | 19 src/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-3.c | 20 src/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-4.c | 20 src/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-7.c | 43 src/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-1.c | 20 src/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-2.c | 20 src/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-3.c | 21 src/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-4.c | 21 src/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-7.c | 44 33 files changed, 1334 insertions(+), 15 deletions(-) create mode 100644 gcc/testsuite/gcc.target/i386/indirect-thunk-1.c create mode 100644 gcc/testsuite/gcc.target/i386/indirect-thunk-2.c create mode 100644 gcc/testsuite/gcc.target/i386/indirect-thunk-3.c create mode 100644 gcc/testsuite/gcc.target/i386/indirect-thunk-4.c create mode 100644 gcc/testsuite/gcc.target/i386/indirect-thunk-5.c create mode 100644 gcc/testsuite/gcc.target/i386/indirect-thunk-6.c create mode 100644 gcc/testsuite/gcc.target/i386/indirect-thunk-7.c create mode 100644 gcc/testsuite/gcc.target/i386/indirect-thunk-attr-1.c create mode 100644 gcc/testsuite/gcc.target/i386/indirect-thunk-attr-2.c create mode 100644 gcc/testsuite/gcc.target/i386/indirect-thunk-attr-3.c create mode 100644 gcc/testsuite/gcc.target/i386/indirect-thunk-attr-4.c create mode 100644 gcc/testsuite/gcc.target/i386/indirect-thunk-attr-5.c create mode 100644 gcc/testsuite/gcc.target/i386/indirect-thunk-attr-6.c create mode 100644 gcc/testsuite/gcc.target/i386/indirect-thunk-attr-7.c create mode 100644 gcc/testsuite/gcc.target/i386/indirect-thunk-attr-8.c create mode 100644 gcc/testsuite/gcc.target/i386/indirect-thunk-bnd-1.c create mode 100644 gcc/testsuite/gcc.target/i386/indirect-thunk-bnd-2.c create mode 100644 gcc/testsuite/gcc.target/i386/indirect-thunk-bnd-3.c create mode 100644 gcc/testsuite/gcc.target/i386/indirect-thunk-bnd-4.c create mode 100644 gcc/testsuite/gcc.target/i386/indirect-thunk-extern-1.c create mode 100644 gcc/testsuite/gcc.target/i386/indirect-thunk-extern-2.c create mode 100644 gcc/testsuite/gcc.target/i386/indirect-thunk-extern-3.c create mode 100644 gcc/testsuite/gcc.target/i386/indirect-thunk-extern-4.c create mode 100644 gcc/testsuite/gcc.target/i386/indirect-thunk-extern-5.c create mode 100644 gcc/testsuite/gcc.target/i386/indirect-thunk-extern-6.c create mode 100644 gcc/testsuite/gcc.target/i386/indirect-thunk-extern-7.c create mode 100644 gcc/testsuite/gcc.target/i386/indirect-thunk-inline-1.c create mode 100644 gcc/testsuite/gcc.target/i386/indirect-thunk-inline-2.c create mode 100644 gcc/testsuite/gcc.target/i386/indirect-thunk-inline-3.c create mode 100644 gcc/testsuite/gcc.target/i386/indirect-thunk-inline-4.c create mode 100644 gcc/testsuite/gcc.target/i386/indirect-thunk-inline-5.c create mode 100644 gcc/testsuite/gcc.target/i386/indirect-thunk-inline-6.c create mode 100644 gcc/testsuite/gcc.target/i386/indirect-thunk-inline-7.c Index: b/src/gcc/config/i386/i386-opts.h =================================================================== --- a/src/gcc/config/i386/i386-opts.h +++ b/src/gcc/config/i386/i386-opts.h @@ -99,4 +99,17 @@ enum stack_protector_guard { SSP_GLOBAL /* global canary */ }; +/* This is used to mitigate variant #2 of the speculative execution + vulnerabilities on x86 processors identified by CVE-2017-5715, aka + Spectre. They convert indirect branches and function returns to + call and return thunks to avoid speculative execution via indirect + call, jmp and ret. */ +enum indirect_branch { + indirect_branch_unset = 0, + indirect_branch_keep, + indirect_branch_thunk, + indirect_branch_thunk_inline, + indirect_branch_thunk_extern +}; + #endif Index: b/src/gcc/config/i386/i386-protos.h =================================================================== --- a/src/gcc/config/i386/i386-protos.h +++ b/src/gcc/config/i386/i386-protos.h @@ -306,6 +306,7 @@ extern enum attr_cpu ix86_schedule; #endif extern const char * ix86_output_call_insn (rtx_insn *insn, rtx call_op); +extern const char * ix86_output_indirect_jmp (rtx call_op, bool ret_p); #ifdef RTX_CODE /* Target data for multipass lookahead scheduling. Index: b/src/gcc/config/i386/i386.c =================================================================== --- a/src/gcc/config/i386/i386.c +++ b/src/gcc/config/i386/i386.c @@ -2554,12 +2554,23 @@ make_pass_insert_vzeroupper (gcc::contex return new pass_insert_vzeroupper (ctxt); } -/* Return true if a red-zone is in use. */ +/* Return true if a red-zone is in use. We can't use red-zone when + there are local indirect jumps, like "indirect_jump" or "tablejump", + which jumps to another place in the function, since "call" in the + indirect thunk pushes the return address onto stack, destroying + red-zone. + + TODO: If we can reserve the first 2 WORDs, for PUSH and, another + for CALL, in red-zone, we can allow local indirect jumps with + indirect thunk. */ static inline bool ix86_using_red_zone (void) { - return TARGET_RED_ZONE && !TARGET_64BIT_MS_ABI; + return (TARGET_RED_ZONE + && !TARGET_64BIT_MS_ABI + && (!cfun->machine->has_local_indirect_jump + || cfun->machine->indirect_branch_type == indirect_branch_keep)); } /* Return a string that documents the current -m options. The caller is @@ -5126,6 +5137,37 @@ ix86_reset_previous_fndecl (void) ix86_previous_fndecl = NULL_TREE; } +/* Set the indirect_branch_type field from the function FNDECL. */ + +static void +ix86_set_indirect_branch_type (tree fndecl) +{ + if (cfun->machine->indirect_branch_type == indirect_branch_unset) + { + tree attr = lookup_attribute ("indirect_branch", + DECL_ATTRIBUTES (fndecl)); + if (attr != NULL) + { + tree args = TREE_VALUE (attr); + if (args == NULL) + gcc_unreachable (); + tree cst = TREE_VALUE (args); + if (strcmp (TREE_STRING_POINTER (cst), "keep") == 0) + cfun->machine->indirect_branch_type = indirect_branch_keep; + else if (strcmp (TREE_STRING_POINTER (cst), "thunk") == 0) + cfun->machine->indirect_branch_type = indirect_branch_thunk; + else if (strcmp (TREE_STRING_POINTER (cst), "thunk-inline") == 0) + cfun->machine->indirect_branch_type = indirect_branch_thunk_inline; + else if (strcmp (TREE_STRING_POINTER (cst), "thunk-extern") == 0) + cfun->machine->indirect_branch_type = indirect_branch_thunk_extern; + else + gcc_unreachable (); + } + else + cfun->machine->indirect_branch_type = ix86_indirect_branch; + } +} + /* Establish appropriate back-end context for processing the function FNDECL. The argument might be NULL to indicate processing at top level, outside of any function scope. */ @@ -5136,7 +5178,11 @@ ix86_set_current_function (tree fndecl) several times in the course of compiling a function, and we don't want to slow things down too much or call target_reinit when it isn't safe. */ if (fndecl == ix86_previous_fndecl) - return; + { + if (fndecl != NULL_TREE) + ix86_set_indirect_branch_type (fndecl); + return; + } tree old_tree; if (ix86_previous_fndecl == NULL_TREE) @@ -5153,6 +5199,8 @@ ix86_set_current_function (tree fndecl) return; } + ix86_set_indirect_branch_type (fndecl); + tree new_tree = DECL_FUNCTION_SPECIFIC_TARGET (fndecl); if (new_tree == NULL_TREE) new_tree = target_option_default_node; @@ -9681,6 +9729,220 @@ ix86_setup_frame_addresses (void) # endif #endif +/* Label count for call and return thunks. It is used to make unique + labels in call and return thunks. */ +static int indirectlabelno; + +/* True if call and return thunk functions are needed. */ +static bool indirect_thunk_needed = false; +/* True if call and return thunk functions with the BND prefix are + needed. */ +static bool indirect_thunk_bnd_needed = false; + +/* Bit masks of integer registers, which contain branch target, used + by call and return thunks functions. */ +static int indirect_thunks_used; +/* Bit masks of integer registers, which contain branch target, used + by call and return thunks functions with the BND prefix. */ +static int indirect_thunks_bnd_used; + +#ifndef INDIRECT_LABEL +# define INDIRECT_LABEL "LIND" +#endif + +/* Fills in the label name that should be used for the indirect thunk. */ + +static void +indirect_thunk_name (char name[32], int regno, bool need_bnd_p) +{ + if (USE_HIDDEN_LINKONCE) + { + const char *bnd = need_bnd_p ? "_bnd" : ""; + if (regno >= 0) + { + const char *reg_prefix; + if (LEGACY_INT_REGNO_P (regno)) + reg_prefix = TARGET_64BIT ? "r" : "e"; + else + reg_prefix = ""; + sprintf (name, "__x86_indirect_thunk%s_%s%s", + bnd, reg_prefix, reg_names[regno]); + } + else + sprintf (name, "__x86_indirect_thunk%s", bnd); + } + else + { + if (regno >= 0) + { + if (need_bnd_p) + ASM_GENERATE_INTERNAL_LABEL (name, "LITBR", regno); + else + ASM_GENERATE_INTERNAL_LABEL (name, "LITR", regno); + } + else + { + if (need_bnd_p) + ASM_GENERATE_INTERNAL_LABEL (name, "LITB", 0); + else + ASM_GENERATE_INTERNAL_LABEL (name, "LIT", 0); + } + } +} + +/* Output a call and return thunk for indirect branch. If BND_P is + true, the BND prefix is needed. If REGNO != -1, the function + address is in REGNO and the call and return thunk looks like: + + call L2 + L1: + pause + jmp L1 + L2: + mov %REG, (%sp) + ret + + Otherwise, the function address is on the top of stack and the + call and return thunk looks like: + + call L2 + L1: + pause + jmp L1 + L2: + lea WORD_SIZE(%sp), %sp + ret + */ + +static void +output_indirect_thunk (bool need_bnd_p, int regno) +{ + char indirectlabel1[32]; + char indirectlabel2[32]; + + ASM_GENERATE_INTERNAL_LABEL (indirectlabel1, INDIRECT_LABEL, + indirectlabelno++); + ASM_GENERATE_INTERNAL_LABEL (indirectlabel2, INDIRECT_LABEL, + indirectlabelno++); + + /* Call */ + if (need_bnd_p) + fputs ("\tbnd call\t", asm_out_file); + else + fputs ("\tcall\t", asm_out_file); + assemble_name_raw (asm_out_file, indirectlabel2); + fputc ('\n', asm_out_file); + + ASM_OUTPUT_INTERNAL_LABEL (asm_out_file, indirectlabel1); + + /* Pause + lfence. */ + fprintf (asm_out_file, "\tpause\n\tlfence\n"); + + /* Jump. */ + fputs ("\tjmp\t", asm_out_file); + assemble_name_raw (asm_out_file, indirectlabel1); + fputc ('\n', asm_out_file); + + ASM_OUTPUT_INTERNAL_LABEL (asm_out_file, indirectlabel2); + + if (regno >= 0) + { + /* MOV. */ + rtx xops[2]; + xops[0] = gen_rtx_MEM (word_mode, stack_pointer_rtx); + xops[1] = gen_rtx_REG (word_mode, regno); + output_asm_insn ("mov\t{%1, %0|%0, %1}", xops); + } + else + { + /* LEA. */ + rtx xops[2]; + xops[0] = stack_pointer_rtx; + xops[1] = plus_constant (Pmode, stack_pointer_rtx, UNITS_PER_WORD); + output_asm_insn ("lea\t{%E1, %0|%0, %E1}", xops); + } + + if (need_bnd_p) + fputs ("\tbnd ret\n", asm_out_file); + else + fputs ("\tret\n", asm_out_file); +} + +/* Output a funtion with a call and return thunk for indirect branch. + If BND_P is true, the BND prefix is needed. If REGNO != -1, the + function address is in REGNO. Otherwise, the function address is + on the top of stack. */ + +static void +output_indirect_thunk_function (bool need_bnd_p, int regno) +{ + char name[32]; + tree decl; + + /* Create __x86_indirect_thunk/__x86_indirect_thunk_bnd. */ + indirect_thunk_name (name, regno, need_bnd_p); + decl = build_decl (BUILTINS_LOCATION, FUNCTION_DECL, + get_identifier (name), + build_function_type_list (void_type_node, NULL_TREE)); + DECL_RESULT (decl) = build_decl (BUILTINS_LOCATION, RESULT_DECL, + NULL_TREE, void_type_node); + TREE_PUBLIC (decl) = 1; + TREE_STATIC (decl) = 1; + DECL_IGNORED_P (decl) = 1; + +#if TARGET_MACHO + if (TARGET_MACHO) + { + switch_to_section (darwin_sections[picbase_thunk_section]); + fputs ("\t.weak_definition\t", asm_out_file); + assemble_name (asm_out_file, name); + fputs ("\n\t.private_extern\t", asm_out_file); + assemble_name (asm_out_file, name); + putc ('\n', asm_out_file); + ASM_OUTPUT_LABEL (asm_out_file, name); + DECL_WEAK (decl) = 1; + } + else +#endif + if (USE_HIDDEN_LINKONCE) + { + cgraph_node::create (decl)->set_comdat_group (DECL_ASSEMBLER_NAME (decl)); + + targetm.asm_out.unique_section (decl, 0); + switch_to_section (get_named_section (decl, NULL, 0)); + + targetm.asm_out.globalize_label (asm_out_file, name); + fputs ("\t.hidden\t", asm_out_file); + assemble_name (asm_out_file, name); + putc ('\n', asm_out_file); + ASM_DECLARE_FUNCTION_NAME (asm_out_file, name, decl); + } + else + { + switch_to_section (text_section); + ASM_OUTPUT_LABEL (asm_out_file, name); + } + + DECL_INITIAL (decl) = make_node (BLOCK); + current_function_decl = decl; + allocate_struct_function (decl, false); + init_function_start (decl); + /* We're about to hide the function body from callees of final_* by + emitting it directly; tell them we're a thunk, if they care. */ + cfun->is_thunk = true; + first_function_block_is_cold = false; + /* Make sure unwind info is emitted for the thunk if needed. */ + final_start_function (emit_barrier (), asm_out_file, 1); + + output_indirect_thunk (need_bnd_p, regno); + + final_end_function (); + init_insn_lengths (); + free_after_compilation (cfun); + set_cfun (NULL); + current_function_decl = NULL; +} + static int pic_labels_used; /* Fills in the label name that should be used for a pc thunk for @@ -9707,11 +9969,32 @@ ix86_code_end (void) rtx xops[2]; int regno; + if (indirect_thunk_needed) + output_indirect_thunk_function (false, -1); + if (indirect_thunk_bnd_needed) + output_indirect_thunk_function (true, -1); + + for (regno = FIRST_REX_INT_REG; regno <= LAST_REX_INT_REG; regno++) + { + int i = regno - FIRST_REX_INT_REG + LAST_INT_REG + 1; + if ((indirect_thunks_used & (1 << i))) + output_indirect_thunk_function (false, regno); + + if ((indirect_thunks_bnd_used & (1 << i))) + output_indirect_thunk_function (true, regno); + } + for (regno = AX_REG; regno <= SP_REG; regno++) { char name[32]; tree decl; + if ((indirect_thunks_used & (1 << regno))) + output_indirect_thunk_function (false, regno); + + if ((indirect_thunks_bnd_used & (1 << regno))) + output_indirect_thunk_function (true, regno); + if (!(pic_labels_used & (1 << regno))) continue; @@ -25805,12 +26088,292 @@ ix86_expand_call (rtx retval, rtx fnaddr return call; } +/* Output indirect branch via a call and return thunk. CALL_OP is a + register which contains the branch target. XASM is the assembly + template for CALL_OP. Branch is a tail call if SIBCALL_P is true. + A normal call is converted to: + + call __x86_indirect_thunk_reg + + and a tail call is converted to: + + jmp __x86_indirect_thunk_reg + */ + +static void +ix86_output_indirect_branch_via_reg (rtx call_op, bool sibcall_p) +{ + char thunk_name_buf[32]; + char *thunk_name; + bool need_bnd_p = ix86_bnd_prefixed_insn_p (current_output_insn); + int regno = REGNO (call_op); + + if (cfun->machine->indirect_branch_type + != indirect_branch_thunk_inline) + { + if (cfun->machine->indirect_branch_type == indirect_branch_thunk) + { + int i = regno; + if (i >= FIRST_REX_INT_REG) + i -= (FIRST_REX_INT_REG - LAST_INT_REG - 1); + if (need_bnd_p) + indirect_thunks_bnd_used |= 1 << i; + else + indirect_thunks_used |= 1 << i; + } + indirect_thunk_name (thunk_name_buf, regno, need_bnd_p); + thunk_name = thunk_name_buf; + } + else + thunk_name = NULL; + + if (sibcall_p) + { + if (thunk_name != NULL) + { + if (need_bnd_p) + fprintf (asm_out_file, "\tbnd jmp\t%s\n", thunk_name); + else + fprintf (asm_out_file, "\tjmp\t%s\n", thunk_name); + } + else + output_indirect_thunk (need_bnd_p, regno); + } + else + { + if (thunk_name != NULL) + { + if (need_bnd_p) + fprintf (asm_out_file, "\tbnd call\t%s\n", thunk_name); + else + fprintf (asm_out_file, "\tcall\t%s\n", thunk_name); + return; + } + + char indirectlabel1[32]; + char indirectlabel2[32]; + + ASM_GENERATE_INTERNAL_LABEL (indirectlabel1, + INDIRECT_LABEL, + indirectlabelno++); + ASM_GENERATE_INTERNAL_LABEL (indirectlabel2, + INDIRECT_LABEL, + indirectlabelno++); + + /* Jump. */ + if (need_bnd_p) + fputs ("\tbnd jmp\t", asm_out_file); + else + fputs ("\tjmp\t", asm_out_file); + assemble_name_raw (asm_out_file, indirectlabel2); + fputc ('\n', asm_out_file); + + ASM_OUTPUT_INTERNAL_LABEL (asm_out_file, indirectlabel1); + + if (thunk_name != NULL) + { + if (need_bnd_p) + fprintf (asm_out_file, "\tbnd jmp\t%s\n", thunk_name); + else + fprintf (asm_out_file, "\tjmp\t%s\n", thunk_name); + } + else + output_indirect_thunk (need_bnd_p, regno); + + ASM_OUTPUT_INTERNAL_LABEL (asm_out_file, indirectlabel2); + + /* Call. */ + if (need_bnd_p) + fputs ("\tbnd call\t", asm_out_file); + else + fputs ("\tcall\t", asm_out_file); + assemble_name_raw (asm_out_file, indirectlabel1); + fputc ('\n', asm_out_file); + } +} + +/* Output indirect branch via a call and return thunk. CALL_OP is + the branch target. XASM is the assembly template for CALL_OP. + Branch is a tail call if SIBCALL_P is true. A normal call is + converted to: + + jmp L2 + L1: + push CALL_OP + jmp __x86_indirect_thunk + L2: + call L1 + + and a tail call is converted to: + + push CALL_OP + jmp __x86_indirect_thunk + */ + +static void +ix86_output_indirect_branch_via_push (rtx call_op, const char *xasm, + bool sibcall_p) +{ + char thunk_name_buf[32]; + char *thunk_name; + char push_buf[64]; + bool need_bnd_p = ix86_bnd_prefixed_insn_p (current_output_insn); + int regno = -1; + + if (cfun->machine->indirect_branch_type + != indirect_branch_thunk_inline) + { + if (cfun->machine->indirect_branch_type == indirect_branch_thunk) + { + if (need_bnd_p) + indirect_thunk_bnd_needed = true; + else + indirect_thunk_needed = true; + } + indirect_thunk_name (thunk_name_buf, regno, need_bnd_p); + thunk_name = thunk_name_buf; + } + else + thunk_name = NULL; + + snprintf (push_buf, sizeof (push_buf), "push{%c}\t%s", + TARGET_64BIT ? 'q' : 'l', xasm); + + if (sibcall_p) + { + output_asm_insn (push_buf, &call_op); + if (thunk_name != NULL) + { + if (need_bnd_p) + fprintf (asm_out_file, "\tbnd jmp\t%s\n", thunk_name); + else + fprintf (asm_out_file, "\tjmp\t%s\n", thunk_name); + } + else + output_indirect_thunk (need_bnd_p, regno); + } + else + { + char indirectlabel1[32]; + char indirectlabel2[32]; + + ASM_GENERATE_INTERNAL_LABEL (indirectlabel1, + INDIRECT_LABEL, + indirectlabelno++); + ASM_GENERATE_INTERNAL_LABEL (indirectlabel2, + INDIRECT_LABEL, + indirectlabelno++); + + /* Jump. */ + if (need_bnd_p) + fputs ("\tbnd jmp\t", asm_out_file); + else + fputs ("\tjmp\t", asm_out_file); + assemble_name_raw (asm_out_file, indirectlabel2); + fputc ('\n', asm_out_file); + + ASM_OUTPUT_INTERNAL_LABEL (asm_out_file, indirectlabel1); + + /* An external function may be called via GOT, instead of PLT. */ + if (MEM_P (call_op)) + { + struct ix86_address parts; + rtx addr = XEXP (call_op, 0); + if (ix86_decompose_address (addr, &parts) + && parts.base == stack_pointer_rtx) + { + /* Since call will adjust stack by -UNITS_PER_WORD, + we must convert "disp(stack, index, scale)" to + "disp+UNITS_PER_WORD(stack, index, scale)". */ + if (parts.index) + { + addr = gen_rtx_MULT (Pmode, parts.index, + GEN_INT (parts.scale)); + addr = gen_rtx_PLUS (Pmode, stack_pointer_rtx, + addr); + } + else + addr = stack_pointer_rtx; + + rtx disp; + if (parts.disp != NULL_RTX) + disp = plus_constant (Pmode, parts.disp, + UNITS_PER_WORD); + else + disp = GEN_INT (UNITS_PER_WORD); + + addr = gen_rtx_PLUS (Pmode, addr, disp); + call_op = gen_rtx_MEM (GET_MODE (call_op), addr); + } + } + + output_asm_insn (push_buf, &call_op); + + if (thunk_name != NULL) + { + if (need_bnd_p) + fprintf (asm_out_file, "\tbnd jmp\t%s\n", thunk_name); + else + fprintf (asm_out_file, "\tjmp\t%s\n", thunk_name); + } + else + output_indirect_thunk (need_bnd_p, regno); + + ASM_OUTPUT_INTERNAL_LABEL (asm_out_file, indirectlabel2); + + /* Call. */ + if (need_bnd_p) + fputs ("\tbnd call\t", asm_out_file); + else + fputs ("\tcall\t", asm_out_file); + assemble_name_raw (asm_out_file, indirectlabel1); + fputc ('\n', asm_out_file); + } +} + +/* Output indirect branch via a call and return thunk. CALL_OP is + the branch target. XASM is the assembly template for CALL_OP. + Branch is a tail call if SIBCALL_P is true. */ + +static void +ix86_output_indirect_branch (rtx call_op, const char *xasm, + bool sibcall_p) +{ + if (REG_P (call_op)) + ix86_output_indirect_branch_via_reg (call_op, sibcall_p); + else + ix86_output_indirect_branch_via_push (call_op, xasm, sibcall_p); +} +/* Output indirect jump. CALL_OP is the jump target. Jump is a + function return if RET_P is true. */ + +const char * +ix86_output_indirect_jmp (rtx call_op, bool ret_p) +{ + if (cfun->machine->indirect_branch_type != indirect_branch_keep) + { + /* We can't have red-zone if this isn't a function return since + "call" in the indirect thunk pushes the return address onto + stack, destroying red-zone. */ + if (!ret_p && ix86_red_zone_size != 0) + gcc_unreachable (); + + ix86_output_indirect_branch (call_op, "%0", true); + return ""; + } + else + return "%!jmp\t%A0"; +} + /* Output the assembly for a call instruction. */ const char * ix86_output_call_insn (rtx_insn *insn, rtx call_op) { bool direct_p = constant_call_address_operand (call_op, VOIDmode); + bool output_indirect_p + = (!TARGET_SEH + && cfun->machine->indirect_branch_type != indirect_branch_keep); bool seh_nop_p = false; const char *xasm; @@ -25823,9 +26386,17 @@ ix86_output_call_insn (rtx_insn *insn, r else if (TARGET_SEH) xasm = "%!rex.W jmp %A0"; else - xasm = "%!jmp\t%A0"; + { + if (output_indirect_p) + xasm = "%0"; + else + xasm = "%!jmp\t%A0"; + } - output_asm_insn (xasm, &call_op); + if (output_indirect_p && !direct_p) + ix86_output_indirect_branch (call_op, xasm, true); + else + output_asm_insn (xasm, &call_op); return ""; } @@ -25862,9 +26433,17 @@ ix86_output_call_insn (rtx_insn *insn, r if (direct_p) xasm = "%!call\t%P0"; else - xasm = "%!call\t%A0"; + { + if (output_indirect_p) + xasm = "%0"; + else + xasm = "%!call\t%A0"; + } - output_asm_insn (xasm, &call_op); + if (output_indirect_p && !direct_p) + ix86_output_indirect_branch (call_op, xasm, false); + else + output_asm_insn (xasm, &call_op); if (seh_nop_p) return "nop"; @@ -43014,7 +43593,7 @@ ix86_handle_struct_attribute (tree *node } static tree -ix86_handle_fndecl_attribute (tree *node, tree name, tree, int, +ix86_handle_fndecl_attribute (tree *node, tree name, tree args, int, bool *no_add_attrs) { if (TREE_CODE (*node) != FUNCTION_DECL) @@ -43023,6 +43602,29 @@ ix86_handle_fndecl_attribute (tree *node name); *no_add_attrs = true; } + + if (is_attribute_p ("indirect_branch", name)) + { + tree cst = TREE_VALUE (args); + if (TREE_CODE (cst) != STRING_CST) + { + warning (OPT_Wattributes, + "%qE attribute requires a string constant argument", + name); + *no_add_attrs = true; + } + else if (strcmp (TREE_STRING_POINTER (cst), "keep") != 0 + && strcmp (TREE_STRING_POINTER (cst), "thunk") != 0 + && strcmp (TREE_STRING_POINTER (cst), "thunk-inline") != 0 + && strcmp (TREE_STRING_POINTER (cst), "thunk-extern") != 0) + { + warning (OPT_Wattributes, + "argument to %qE attribute is not " + "(keep|thunk|thunk-inline|thunk-extern)", name); + *no_add_attrs = true; + } + } + return NULL_TREE; } @@ -46915,6 +47517,9 @@ static const struct attribute_spec ix86_ false }, { "callee_pop_aggregate_return", 1, 1, false, true, true, ix86_handle_callee_pop_aggregate_return, true }, + { "indirect_branch", 1, 1, true, false, false, + ix86_handle_fndecl_attribute, false }, + /* End element. */ { NULL, 0, 0, false, false, false, NULL, false } }; Index: b/src/gcc/config/i386/i386.h =================================================================== --- a/src/gcc/config/i386/i386.h +++ b/src/gcc/config/i386/i386.h @@ -1229,6 +1229,9 @@ extern const char *host_detect_local_cpu /* Base register for access to local variables of the function. */ #define FRAME_POINTER_REGNUM 20 +#define FIRST_INT_REG AX_REG +#define LAST_INT_REG SP_REG + /* First floating point reg */ #define FIRST_FLOAT_REG 8 @@ -1465,6 +1468,8 @@ enum reg_class registers. */ #define TARGET_SMALL_REGISTER_CLASSES_FOR_MODE_P hook_bool_mode_true +#define LEGACY_INT_REGNO_P(N) (IN_RANGE ((N), FIRST_INT_REG, LAST_INT_REG)) + #define QI_REG_P(X) (REG_P (X) && QI_REGNO_P (REGNO (X))) #define QI_REGNO_P(N) IN_RANGE ((N), AX_REG, BX_REG) @@ -2550,6 +2555,13 @@ struct GTY(()) machine_function { /* If true, it is safe to not save/restore DRAP register. */ BOOL_BITFIELD no_drap_save_restore : 1; + /* How to generate indirec branch. */ + ENUM_BITFIELD(indirect_branch) indirect_branch_type : 3; + + /* If true, the current function has local indirect jumps, like + "indirect_jump" or "tablejump". */ + BOOL_BITFIELD has_local_indirect_jump : 1; + /* During prologue/epilogue generation, the current frame state. Otherwise, the frame state at the end of the prologue. */ struct machine_frame_state fs; Index: b/src/gcc/config/i386/i386.md =================================================================== --- a/src/gcc/config/i386/i386.md +++ b/src/gcc/config/i386/i386.md @@ -11556,13 +11556,18 @@ { if (TARGET_X32) operands[0] = convert_memory_address (word_mode, operands[0]); + cfun->machine->has_local_indirect_jump = true; }) (define_insn "*indirect_jump" [(set (pc) (match_operand:W 0 "indirect_branch_operand" "rBw"))] "" - "%!jmp\t%A0" - [(set_attr "type" "ibr") + "* return ix86_output_indirect_jmp (operands[0], false);" + [(set (attr "type") + (if_then_else (match_test "(cfun->machine->indirect_branch_type + != indirect_branch_keep)") + (const_string "multi") + (const_string "ibr"))) (set_attr "length_immediate" "0")]) (define_expand "tablejump" @@ -11604,14 +11609,19 @@ if (TARGET_X32) operands[0] = convert_memory_address (word_mode, operands[0]); + cfun->machine->has_local_indirect_jump = true; }) (define_insn "*tablejump_1" [(set (pc) (match_operand:W 0 "indirect_branch_operand" "rBw")) (use (label_ref (match_operand 1)))] "" - "%!jmp\t%A0" - [(set_attr "type" "ibr") + "* return ix86_output_indirect_jmp (operands[0], false);" + [(set (attr "type") + (if_then_else (match_test "(cfun->machine->indirect_branch_type + != indirect_branch_keep)") + (const_string "multi") + (const_string "ibr"))) (set_attr "length_immediate" "0")]) ;; Convert setcc + movzbl to xor + setcc if operands don't overlap. @@ -12198,8 +12208,12 @@ [(simple_return) (use (match_operand:SI 0 "register_operand" "r"))] "reload_completed" - "%!jmp\t%A0" - [(set_attr "type" "ibr") + "* return ix86_output_indirect_jmp (operands[0], true);" + [(set (attr "type") + (if_then_else (match_test "(cfun->machine->indirect_branch_type + != indirect_branch_keep)") + (const_string "multi") + (const_string "ibr"))) (set_attr "length_immediate" "0")]) (define_insn "nop" Index: b/src/gcc/config/i386/i386.opt =================================================================== --- a/src/gcc/config/i386/i386.opt +++ b/src/gcc/config/i386/i386.opt @@ -876,3 +876,23 @@ Enum(stack_protector_guard) String(tls) EnumValue Enum(stack_protector_guard) String(global) Value(SSP_GLOBAL) + +mindirect-branch= +Target Report RejectNegative Joined Enum(indirect_branch) Var(ix86_indirect_branch) Init(indirect_branch_keep) +Convert indirect call and jump to call and return thunks. + +Enum +Name(indirect_branch) Type(enum indirect_branch) +Known indirect branch choices (for use with the -mindirect-branch= option): + +EnumValue +Enum(indirect_branch) String(keep) Value(indirect_branch_keep) + +EnumValue +Enum(indirect_branch) String(thunk) Value(indirect_branch_thunk) + +EnumValue +Enum(indirect_branch) String(thunk-inline) Value(indirect_branch_thunk_inline) + +EnumValue +Enum(indirect_branch) String(thunk-extern) Value(indirect_branch_thunk_extern) Index: b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-1.c =================================================================== --- /dev/null +++ b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-1.c @@ -0,0 +1,20 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -mindirect-branch=thunk -fno-pic" } */ + +typedef void (*dispatch_t)(long offset); + +dispatch_t dispatch; + +void +male_indirect_jump (long offset) +{ + dispatch(offset); +} + +/* { dg-final { scan-assembler "push(?:l|q)\[ \t\]*_?dispatch" { target { ! x32 } } } } */ +/* { dg-final { scan-assembler "jmp\[ \t\]*__x86_indirect_thunk" { target { ! x32 } } } } */ +/* { dg-final { scan-assembler "jmp\[ \t\]*__x86_indirect_thunk_(r|e)ax" { target x32 } } } */ +/* { dg-final { scan-assembler "jmp\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler "call\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler {\tpause} } } */ +/* { dg-final { scan-assembler {\tlfence} } } */ Index: b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-2.c =================================================================== --- /dev/null +++ b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-2.c @@ -0,0 +1,20 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -mindirect-branch=thunk -fno-pic" } */ + +typedef void (*dispatch_t)(long offset); + +dispatch_t dispatch[256]; + +void +male_indirect_jump (long offset) +{ + dispatch[offset](offset); +} + +/* { dg-final { scan-assembler "push(?:l|q)\[ \t\]*_?dispatch" { target { ! x32 } } } } */ +/* { dg-final { scan-assembler "jmp\[ \t\]*__x86_indirect_thunk" { target { ! x32 } } } } */ +/* { dg-final { scan-assembler "jmp\[ \t\]*__x86_indirect_thunk_(r|e)ax" { target x32 } } } */ +/* { dg-final { scan-assembler "jmp\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler "call\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler {\tpause} } } */ +/* { dg-final { scan-assembler {\tlfence} } } */ Index: b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-3.c =================================================================== --- /dev/null +++ b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-3.c @@ -0,0 +1,21 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -mindirect-branch=thunk -fno-pic" } */ + +typedef void (*dispatch_t)(long offset); + +dispatch_t dispatch; + +int +male_indirect_jump (long offset) +{ + dispatch(offset); + return 0; +} + +/* { dg-final { scan-assembler "push(?:l|q)\[ \t\]*_?dispatch" { target { ! x32 } } } } */ +/* { dg-final { scan-assembler "jmp\[ \t\]*__x86_indirect_thunk" { target { ! x32 } } } } */ +/* { dg-final { scan-assembler "call\[ \t\]*__x86_indirect_thunk_(r|e)ax" { target x32 } } } */ +/* { dg-final { scan-assembler "jmp\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler "call\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler {\tpause} } } */ +/* { dg-final { scan-assembler {\tlfence} } } */ Index: b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-4.c =================================================================== --- /dev/null +++ b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-4.c @@ -0,0 +1,21 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -mindirect-branch=thunk -fno-pic" } */ + +typedef void (*dispatch_t)(long offset); + +dispatch_t dispatch[256]; + +int +male_indirect_jump (long offset) +{ + dispatch[offset](offset); + return 0; +} + +/* { dg-final { scan-assembler "push(?:l|q)\[ \t\]*_?dispatch" { target { ! x32 } } } } */ +/* { dg-final { scan-assembler "jmp\[ \t\]*__x86_indirect_thunk" { target { ! x32 } } } } */ +/* { dg-final { scan-assembler "call\[ \t\]*__x86_indirect_thunk_(r|e)ax" { target x32 } } } */ +/* { dg-final { scan-assembler "jmp\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler "call\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler {\tpause} } } */ +/* { dg-final { scan-assembler {\tlfence} } } */ Index: b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-7.c =================================================================== --- /dev/null +++ b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-7.c @@ -0,0 +1,44 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -mindirect-branch=thunk -fno-pic" } */ + +void func0 (void); +void func1 (void); +void func2 (void); +void func3 (void); +void func4 (void); +void func4 (void); +void func5 (void); + +void +bar (int i) +{ + switch (i) + { + default: + func0 (); + break; + case 1: + func1 (); + break; + case 2: + func2 (); + break; + case 3: + func3 (); + break; + case 4: + func4 (); + break; + case 5: + func5 (); + break; + } +} + +/* { dg-final { scan-assembler "push(?:l|q)\[ \t\]*\.L\[0-9\]+\\(,%" { target { ! x32 } } } } */ +/* { dg-final { scan-assembler "jmp\[ \t\]*__x86_indirect_thunk" { target { ! x32 } } } } */ +/* { dg-final { scan-assembler "jmp\[ \t\]*__x86_indirect_thunk_(r|e)ax" { target x32 } } } */ +/* { dg-final { scan-assembler "jmp\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler "call\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler {\tpause} } } */ +/* { dg-final { scan-assembler {\tlfence} } } */ Index: b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-1.c =================================================================== --- /dev/null +++ b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-1.c @@ -0,0 +1,23 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -fno-pic" } */ + +typedef void (*dispatch_t)(long offset); + +dispatch_t dispatch; + +extern void male_indirect_jump (long) + __attribute__ ((indirect_branch("thunk"))); + +void +male_indirect_jump (long offset) +{ + dispatch(offset); +} + +/* { dg-final { scan-assembler "push(?:l|q)\[ \t\]*_?dispatch" { target { ! x32 } } } } */ +/* { dg-final { scan-assembler "jmp\[ \t\]*__x86_indirect_thunk" { target { ! x32 } } } } */ +/* { dg-final { scan-assembler "jmp\[ \t\]*__x86_indirect_thunk_(r|e)ax" { target x32 } } } */ +/* { dg-final { scan-assembler "jmp\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler "call\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler {\tpause} } } */ +/* { dg-final { scan-assembler {\tlfence} } } */ Index: b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-2.c =================================================================== --- /dev/null +++ b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-2.c @@ -0,0 +1,21 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -fno-pic" } */ + +typedef void (*dispatch_t)(long offset); + +dispatch_t dispatch[256]; + +__attribute__ ((indirect_branch("thunk"))) +void +male_indirect_jump (long offset) +{ + dispatch[offset](offset); +} + +/* { dg-final { scan-assembler "push(?:l|q)\[ \t\]*_?dispatch" { target { ! x32 } } } } */ +/* { dg-final { scan-assembler "jmp\[ \t\]*__x86_indirect_thunk" { target { ! x32 } } } } */ +/* { dg-final { scan-assembler "jmp\[ \t\]*__x86_indirect_thunk_(r|e)ax" { target x32 } } } */ +/* { dg-final { scan-assembler "jmp\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler "call\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler {\tpause} } } */ +/* { dg-final { scan-assembler {\tlfence} } } */ Index: b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-3.c =================================================================== --- /dev/null +++ b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-3.c @@ -0,0 +1,23 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -fno-pic" } */ + +typedef void (*dispatch_t)(long offset); + +dispatch_t dispatch; +extern int male_indirect_jump (long) + __attribute__ ((indirect_branch("thunk-inline"))); + +int +male_indirect_jump (long offset) +{ + dispatch(offset); + return 0; +} + +/* { dg-final { scan-assembler "push(?:l|q)\[ \t\]*_?dispatch" { target { ! x32 } } } } */ +/* { dg-final { scan-assembler-times "jmp\[ \t\]*\.LIND" 2 } } */ +/* { dg-final { scan-assembler-times "call\[ \t\]*\.LIND" 2 } } */ +/* { dg-final { scan-assembler {\tpause} } } */ +/* { dg-final { scan-assembler {\tlfence} } } */ +/* { dg-final { scan-assembler-not "__x86_indirect_thunk" } } */ +/* { dg-final { scan-assembler-not "pushq\[ \t\]%rax" { target x32 } } } */ Index: b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-4.c =================================================================== --- /dev/null +++ b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-4.c @@ -0,0 +1,22 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -fno-pic" } */ + +typedef void (*dispatch_t)(long offset); + +dispatch_t dispatch[256]; + +__attribute__ ((indirect_branch("thunk-inline"))) +int +male_indirect_jump (long offset) +{ + dispatch[offset](offset); + return 0; +} + +/* { dg-final { scan-assembler "push(?:l|q)\[ \t\]*_?dispatch" { target { ! x32 } } } } */ +/* { dg-final { scan-assembler-times "jmp\[ \t\]*\.LIND" 2 } } */ +/* { dg-final { scan-assembler-times "call\[ \t\]*\.LIND" 2 } } */ +/* { dg-final { scan-assembler {\tpause} } } */ +/* { dg-final { scan-assembler {\tlfence} } } */ +/* { dg-final { scan-assembler-not "__x86_indirect_thunk" } } */ +/* { dg-final { scan-assembler-not "pushq\[ \t\]%rax" { target x32 } } } */ Index: b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-5.c =================================================================== --- /dev/null +++ b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-5.c @@ -0,0 +1,22 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -fno-pic" } */ + +typedef void (*dispatch_t)(long offset); + +dispatch_t dispatch; +extern int male_indirect_jump (long) + __attribute__ ((indirect_branch("thunk-extern"))); + +int +male_indirect_jump (long offset) +{ + dispatch(offset); + return 0; +} + +/* { dg-final { scan-assembler "push(?:l|q)\[ \t\]*_?dispatch" { target { ! x32 } } } } */ +/* { dg-final { scan-assembler-times "jmp\[ \t\]*\.LIND" 1 { target { ! x32 } } } } */ +/* { dg-final { scan-assembler-times "call\[ \t\]*\.LIND" 1 { target { ! x32 } } } } */ +/* { dg-final { scan-assembler "jmp\[ \t\]*__x86_indirect_thunk" { target { ! x32 } } } } */ +/* { dg-final { scan-assembler "call\[ \t\]*__x86_indirect_thunk_(r|e)ax" { target x32 } } } */ +/* { dg-final { scan-assembler-not {\t(lfence|pause)} } } */ Index: b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-6.c =================================================================== --- /dev/null +++ b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-6.c @@ -0,0 +1,21 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -fno-pic" } */ + +typedef void (*dispatch_t)(long offset); + +dispatch_t dispatch[256]; + +__attribute__ ((indirect_branch("thunk-extern"))) +int +male_indirect_jump (long offset) +{ + dispatch[offset](offset); + return 0; +} + +/* { dg-final { scan-assembler "push(?:l|q)\[ \t\]*_?dispatch" { target { ! x32 } } } } */ +/* { dg-final { scan-assembler-times "jmp\[ \t\]*\.LIND" 1 { target { ! x32 } } } } */ +/* { dg-final { scan-assembler-times "call\[ \t\]*\.LIND" 1 { target { ! x32 } } } } */ +/* { dg-final { scan-assembler "jmp\[ \t\]*__x86_indirect_thunk" { target { ! x32 } } } } */ +/* { dg-final { scan-assembler "call\[ \t\]*__x86_indirect_thunk_(r|e)ax" { target x32 } } } */ +/* { dg-final { scan-assembler-not {\t(lfence|pause)} } } */ Index: b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-7.c =================================================================== --- /dev/null +++ b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-7.c @@ -0,0 +1,44 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -fno-pic" } */ + +void func0 (void); +void func1 (void); +void func2 (void); +void func3 (void); +void func4 (void); +void func4 (void); +void func5 (void); + +__attribute__ ((indirect_branch("thunk-extern"))) +void +bar (int i) +{ + switch (i) + { + default: + func0 (); + break; + case 1: + func1 (); + break; + case 2: + func2 (); + break; + case 3: + func3 (); + break; + case 4: + func4 (); + break; + case 5: + func5 (); + break; + } +} + +/* { dg-final { scan-assembler "push(?:l|q)\[ \t\]*\.L\[0-9\]+\\(,%" { target { ! x32 } } } } */ +/* { dg-final { scan-assembler "jmp\[ \t\]*__x86_indirect_thunk_(r|e)ax" { target x32 } } } */ +/* { dg-final { scan-assembler "jmp\[ \t\]*__x86_indirect_thunk" } } */ +/* { dg-final { scan-assembler-not {\t(lfence|pause)} } } */ +/* { dg-final { scan-assembler-not "jmp\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler-not "call\[ \t\]*\.LIND" } } */ Index: b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-8.c =================================================================== --- /dev/null +++ b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-8.c @@ -0,0 +1,42 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -mindirect-branch=thunk -fno-pic" } */ + +void func0 (void); +void func1 (void); +void func2 (void); +void func3 (void); +void func4 (void); +void func4 (void); +void func5 (void); + +__attribute__ ((indirect_branch("keep"))) +void +bar (int i) +{ + switch (i) + { + default: + func0 (); + break; + case 1: + func1 (); + break; + case 2: + func2 (); + break; + case 3: + func3 (); + break; + case 4: + func4 (); + break; + case 5: + func5 (); + break; + } +} + +/* { dg-final { scan-assembler-not "__x86_indirect_thunk" } } */ +/* { dg-final { scan-assembler-not {\t(lfence|pause)} } } */ +/* { dg-final { scan-assembler-not "jmp\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler-not "call\[ \t\]*\.LIND" } } */ Index: b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-bnd-1.c =================================================================== --- /dev/null +++ b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-bnd-1.c @@ -0,0 +1,20 @@ +/* { dg-do compile { target { ! x32 } } } */ +/* { dg-options "-O2 -mindirect-branch=thunk -fcheck-pointer-bounds -mmpx -fno-pic" } */ + +void (*dispatch) (char *); +char buf[10]; + +void +foo (void) +{ + dispatch (buf); +} + +/* { dg-final { scan-assembler "push(?:l|q)\[ \t\]*_?dispatch" { target { ! x32 } } } } */ +/* { dg-final { scan-assembler "pushq\[ \t\]%rax" { target x32 } } } */ +/* { dg-final { scan-assembler "bnd jmp\[ \t\]*__x86_indirect_thunk_bnd" } } */ +/* { dg-final { scan-assembler "jmp\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler "bnd call\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler "bnd ret" } } */ +/* { dg-final { scan-assembler {\tpause} } } */ +/* { dg-final { scan-assembler {\tlfence} } } */ Index: b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-bnd-2.c =================================================================== --- /dev/null +++ b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-bnd-2.c @@ -0,0 +1,21 @@ +/* { dg-do compile { target { ! x32 } } } */ +/* { dg-options "-O2 -mindirect-branch=thunk -fcheck-pointer-bounds -mmpx -fno-pic" } */ + +void (*dispatch) (char *); +char buf[10]; + +int +foo (void) +{ + dispatch (buf); + return 0; +} + +/* { dg-final { scan-assembler "push(?:l|q)\[ \t\]*_?dispatch" { target { ! x32 } } } } */ +/* { dg-final { scan-assembler "pushq\[ \t\]%rax" { target x32 } } } */ +/* { dg-final { scan-assembler "bnd jmp\[ \t\]*__x86_indirect_thunk_bnd" } } */ +/* { dg-final { scan-assembler "bnd jmp\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler "bnd call\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler "bnd ret" } } */ +/* { dg-final { scan-assembler {\tpause} } } */ +/* { dg-final { scan-assembler {\tlfence} } } */ Index: b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-1.c =================================================================== --- /dev/null +++ b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-1.c @@ -0,0 +1,19 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -mindirect-branch=thunk-extern -fno-pic" } */ + +typedef void (*dispatch_t)(long offset); + +dispatch_t dispatch; + +void +male_indirect_jump (long offset) +{ + dispatch(offset); +} + +/* { dg-final { scan-assembler "push(?:l|q)\[ \t\]*_?dispatch" { target { ! x32 } } } } */ +/* { dg-final { scan-assembler "jmp\[ \t\]*__x86_indirect_thunk" { target { ! x32 } } } } */ +/* { dg-final { scan-assembler "jmp\[ \t\]*__x86_indirect_thunk_(r|e)ax" { target x32 } } } */ +/* { dg-final { scan-assembler-not {\t(lfence|pause)} } } */ +/* { dg-final { scan-assembler-not "jmp\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler-not "call\[ \t\]*\.LIND" } } */ Index: b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-2.c =================================================================== --- /dev/null +++ b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-2.c @@ -0,0 +1,19 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -mindirect-branch=thunk-extern -fno-pic" } */ + +typedef void (*dispatch_t)(long offset); + +dispatch_t dispatch[256]; + +void +male_indirect_jump (long offset) +{ + dispatch[offset](offset); +} + +/* { dg-final { scan-assembler "push(?:l|q)\[ \t\]*_?dispatch" { target { ! x32 } } } } */ +/* { dg-final { scan-assembler "jmp\[ \t\]*__x86_indirect_thunk" { target { ! x32 } } } } */ +/* { dg-final { scan-assembler "jmp\[ \t\]*__x86_indirect_thunk_(r|e)ax" { target x32 } } } */ +/* { dg-final { scan-assembler-not {\t(lfence|pause)} } } */ +/* { dg-final { scan-assembler-not "jmp\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler-not "call\[ \t\]*\.LIND" } } */ Index: b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-3.c =================================================================== --- /dev/null +++ b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-3.c @@ -0,0 +1,20 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -mindirect-branch=thunk-extern -fno-pic" } */ + +typedef void (*dispatch_t)(long offset); + +dispatch_t dispatch; + +int +male_indirect_jump (long offset) +{ + dispatch(offset); + return 0; +} + +/* { dg-final { scan-assembler "push(?:l|q)\[ \t\]*_?dispatch" { target { ! x32 } } } } */ +/* { dg-final { scan-assembler "jmp\[ \t\]*__x86_indirect_thunk" { target { ! x32 } } } } */ +/* { dg-final { scan-assembler-times "jmp\[ \t\]*\.LIND" 1 { target { ! x32 } } } } */ +/* { dg-final { scan-assembler-times "call\[ \t\]*\.LIND" 1 { target { ! x32 } } } } */ +/* { dg-final { scan-assembler "call\[ \t\]*__x86_indirect_thunk_(r|e)ax" { target x32 } } } */ +/* { dg-final { scan-assembler-not {\t(lfence|pause)} } } */ Index: b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-4.c =================================================================== --- /dev/null +++ b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-4.c @@ -0,0 +1,20 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -mindirect-branch=thunk-extern -fno-pic" } */ + +typedef void (*dispatch_t)(long offset); + +dispatch_t dispatch[256]; + +int +male_indirect_jump (long offset) +{ + dispatch[offset](offset); + return 0; +} + +/* { dg-final { scan-assembler "push(?:l|q)\[ \t\]*_?dispatch" { target { ! x32 } } } } */ +/* { dg-final { scan-assembler "jmp\[ \t\]*__x86_indirect_thunk" { target { ! x32 } } } } */ +/* { dg-final { scan-assembler-times "jmp\[ \t\]*\.LIND" 1 { target { ! x32 } } } } */ +/* { dg-final { scan-assembler-times "call\[ \t\]*\.LIND" 1 { target { ! x32 } } } } */ +/* { dg-final { scan-assembler "call\[ \t\]*__x86_indirect_thunk_(r|e)ax" { target x32 } } } */ +/* { dg-final { scan-assembler-not {\t(lfence|pause)} } } */ Index: b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-7.c =================================================================== --- /dev/null +++ b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-7.c @@ -0,0 +1,43 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -mindirect-branch=thunk-extern -fno-pic" } */ + +void func0 (void); +void func1 (void); +void func2 (void); +void func3 (void); +void func4 (void); +void func4 (void); +void func5 (void); + +void +bar (int i) +{ + switch (i) + { + default: + func0 (); + break; + case 1: + func1 (); + break; + case 2: + func2 (); + break; + case 3: + func3 (); + break; + case 4: + func4 (); + break; + case 5: + func5 (); + break; + } +} + +/* { dg-final { scan-assembler "push(?:l|q)\[ \t\]*\.L\[0-9\]+\\(,%" { target { ! x32 } } } } */ +/* { dg-final { scan-assembler "jmp\[ \t\]*__x86_indirect_thunk" { target { ! x32 } } } } */ +/* { dg-final { scan-assembler "jmp\[ \t\]*__x86_indirect_thunk_(r|e)ax" { target x32 } } } */ +/* { dg-final { scan-assembler-not {\t(lfence|pause)} } } */ +/* { dg-final { scan-assembler-not "jmp\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler-not "call\[ \t\]*\.LIND" } } */ Index: b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-1.c =================================================================== --- /dev/null +++ b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-1.c @@ -0,0 +1,20 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -mindirect-branch=thunk-inline -fno-pic" } */ + +typedef void (*dispatch_t)(long offset); + +dispatch_t dispatch; + +void +male_indirect_jump (long offset) +{ + dispatch(offset); +} + +/* { dg-final { scan-assembler "push(?:l|q)\[ \t\]*_?dispatch" { target { ! x32 } } } } */ +/* { dg-final { scan-assembler "jmp\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler "call\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler {\tpause} } } */ +/* { dg-final { scan-assembler {\tlfence} } } */ +/* { dg-final { scan-assembler-not "__x86_indirect_thunk" } } */ +/* { dg-final { scan-assembler-not "pushq\[ \t\]%rax" { target x32 } } } */ Index: b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-2.c =================================================================== --- /dev/null +++ b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-2.c @@ -0,0 +1,20 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -mindirect-branch=thunk-inline -fno-pic" } */ + +typedef void (*dispatch_t)(long offset); + +dispatch_t dispatch[256]; + +void +male_indirect_jump (long offset) +{ + dispatch[offset](offset); +} + +/* { dg-final { scan-assembler "push(?:l|q)\[ \t\]*_?dispatch" { target { ! x32 } } } } */ +/* { dg-final { scan-assembler "jmp\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler "call\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler {\tpause} } } */ +/* { dg-final { scan-assembler {\tlfence} } } */ +/* { dg-final { scan-assembler-not "__x86_indirect_thunk" } } */ +/* { dg-final { scan-assembler-not "pushq\[ \t\]%rax" { target x32 } } } */ Index: b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-3.c =================================================================== --- /dev/null +++ b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-3.c @@ -0,0 +1,21 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -mindirect-branch=thunk-inline -fno-pic" } */ + +typedef void (*dispatch_t)(long offset); + +dispatch_t dispatch; + +int +male_indirect_jump (long offset) +{ + dispatch(offset); + return 0; +} + +/* { dg-final { scan-assembler "push(?:l|q)\[ \t\]*_?dispatch" { target { ! x32 } } } } */ +/* { dg-final { scan-assembler-times "jmp\[ \t\]*\.LIND" 2 } } */ +/* { dg-final { scan-assembler-times "call\[ \t\]*\.LIND" 2 } } */ +/* { dg-final { scan-assembler-times {\tpause} 1 } } */ +/* { dg-final { scan-assembler-times {\tlfence} 1 } } */ +/* { dg-final { scan-assembler-not "__x86_indirect_thunk" } } */ +/* { dg-final { scan-assembler-not "pushq\[ \t\]%rax" { target x32 } } } */ Index: b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-4.c =================================================================== --- /dev/null +++ b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-4.c @@ -0,0 +1,21 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -mindirect-branch=thunk-inline -fno-pic" } */ + +typedef void (*dispatch_t)(long offset); + +dispatch_t dispatch[256]; + +int +male_indirect_jump (long offset) +{ + dispatch[offset](offset); + return 0; +} + +/* { dg-final { scan-assembler "push(?:l|q)\[ \t\]*_?dispatch" { target { ! x32 } } } } */ +/* { dg-final { scan-assembler-times "jmp\[ \t\]*\.LIND" 2 } } */ +/* { dg-final { scan-assembler-times "call\[ \t\]*\.LIND" 2 } } */ +/* { dg-final { scan-assembler-times {\tpause} 1 } } */ +/* { dg-final { scan-assembler-times {\tlfence} 1 } } */ +/* { dg-final { scan-assembler-not "__x86_indirect_thunk" } } */ +/* { dg-final { scan-assembler-not "pushq\[ \t\]%rax" { target x32 } } } */ Index: b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-7.c =================================================================== --- /dev/null +++ b/src/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-7.c @@ -0,0 +1,44 @@ +/* { dg-do compile } */ +/* { dg-options "-O2 -mindirect-branch=thunk-inline -fno-pic" } */ + +void func0 (void); +void func1 (void); +void func2 (void); +void func3 (void); +void func4 (void); +void func4 (void); +void func5 (void); + +void +bar (int i) +{ + switch (i) + { + default: + func0 (); + break; + case 1: + func1 (); + break; + case 2: + func2 (); + break; + case 3: + func3 (); + break; + case 4: + func4 (); + break; + case 5: + func5 (); + break; + } +} + +/* { dg-final { scan-assembler "push(?:l|q)\[ \t\]*\.L\[0-9\]+\\(,%" { target { ! x32 } } } } */ +/* { dg-final { scan-assembler-not "pushq\[ \t\]%rax" { target x32 } } } */ +/* { dg-final { scan-assembler "jmp\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler "call\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler {\tpause} } } */ +/* { dg-final { scan-assembler {\tlfence} } } */ +/* { dg-final { scan-assembler-not "__x86_indirect_thunk" } } */