Message ID | 20180112131549.18143-2-hjl.tools@gmail.com |
---|---|
State | New |
Headers | show |
Series | x86: CVE-2017-5715, aka Spectre | expand |
> gcc/ > > * 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): 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. > > diff --git a/gcc/config/i386/i386-opts.h b/gcc/config/i386/i386-opts.h > index f245c1573cf..f14cbeee7a1 100644 > --- a/gcc/config/i386/i386-opts.h > +++ b/gcc/config/i386/i386-opts.h > @@ -106,4 +106,12 @@ enum prefer_vector_width { > PVW_AVX512 > }; > > +enum indirect_branch { > + indirect_branch_unset = 0, > + indirect_branch_keep, > + indirect_branch_thunk, > + indirect_branch_thunk_inline, > + indirect_branch_thunk_extern > +}; I think it would make sense to simply place the body of your introduction email here as a comment explaining what enum indirect_branhc does. > > -/* 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. */ Technically we can use red-zone if we reserve space for the call address, right? Perhaps mention it as TODO, that should even not be too hard to implement but is probably not being that important code quality wise in practice. > > 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 > @@ -5797,6 +5804,37 @@ ix86_set_func_type (tree fndecl) > } > } > > @@ -10639,6 +10681,191 @@ ix86_setup_frame_addresses (void) > # endif > #endif > > +static int indirectlabelno; > +static bool indirect_thunk_needed = false; > +static bool indirect_thunk_bnd_needed = false; > + > +static int indirect_thunks_used; > +static int indirect_thunks_bnd_used; Add comments for variables. > + > +#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. Otherwise, the function address is on the > + top of stack. */ Add here comment which gives example of full thunk body so it is easier to see what is going on here. > + > +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 . */ > + fprintf (asm_out_file, "\tpause\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; DECL_ARTIFICIAL as well? > + > +#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); > + } > + Why do you need to output asm visibility directives by hand when you create symbol for the function anyway? I would expect that you can just use similar code as cgraph_node::expand_thunk when calls output_mi_thunk and get this done in a way that is independent of target assembler. > > +/* 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. */ Again please add example of code sequences output here to make code easier to follow. > + > +static void > +ix86_output_indirect_branch (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; > + > + if (REG_P (call_op)) > + regno = REGNO (call_op); > + else > + regno = -1; > + > + if (cfun->machine->indirect_branch_type > + != indirect_branch_thunk_inline) > + { > + if (cfun->machine->indirect_branch_type == indirect_branch_thunk) > + { > + if (regno >= 0) > + { > + 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; > + } > + else > + { > + 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) > + { > + if (regno < 0) > + 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 > + { > + if (regno >= 0 && 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 (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); > + } > + } > + > + if (regno < 0) > + 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); > + } > +} It is really not very pretty that the whole sequence is injected into insn stream as a single blob. How opaque it is? Does it need to be patched at dynamic link time? I suppose it may make sense to split the insn and at least explicitly represent the fact that we (sometimes) push the target to stack. Why the memory variant exists at first place? > > (define_insn "*indirect_jump" > [(set (pc) (match_operand:W 0 "indirect_branch_operand" "rBw"))] > "" > - "%!jmp\t%A0" > + "* return ix86_output_indirect_jmp (operands[0], false);" > [(set_attr "type" "ibr") > (set_attr "length_immediate" "0") > (set_attr "maybe_prefix_bnd" "1")]) I think you also want to update type to "many" when we do more than just indirect branch. We do not care much about this, but it feels wrong to have attributes off reality. > diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi > index f3e4a63ab46..ddb6035be96 100644 > --- a/gcc/doc/extend.texi > +++ b/gcc/doc/extend.texi > @@ -5754,6 +5754,16 @@ Specify which floating-point unit to use. You must specify the > @code{target("fpmath=sse+387")} because the comma would separate > different options. > > +@item indirect_branch("@var{choice}") > +@cindex @code{indirect_branch} function attribute, x86 > +On x86 targets, the @code{indirect_branch} attribute causes the compiler > +to convert indirect call and jump with @var{choice}. @samp{keep} > +keeps indirect call and jump unmodified. @samp{thunk} converts indirect > +call and jump to call and return thunk. @samp{thunk-inline} converts > +indirect call and jump to inlined call and return thunk. > +@samp{thunk-extern} converts indirect call and jump to external call > +and return thunk provided in a separate object file. Please expand the documentation in a way that random user who is not aware of the issue will understand that those are security features that come at a cost. > +@opindex -mindirect-branch > +Convert indirect call and jump with @var{choice}. The default is > +@samp{keep}, which keeps indirect call and jump unmodified. > +@samp{thunk} converts indirect call and jump to call and return thunk. > +@samp{thunk-inline} converts indirect call and jump to inlined call > +and return thunk. @samp{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 @code{indirect_branch}. @xref{Function Attributes}. Similarly here. Rest of the patch seems OK. We may want incrementally represent more of the indirect jump/call seqeuence in RTL, but at this point probably keeping things simple and localized is not a bad idea. This can be done incrementally. Please make updated patch and I would like to give others chance to comment today. Honza
On Fri, Jan 12, 2018 at 9:47 AM, Jan Hubicka <hubicka@ucw.cz> wrote: >> gcc/ >> >> * 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): 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. >> >> diff --git a/gcc/config/i386/i386-opts.h b/gcc/config/i386/i386-opts.h >> index f245c1573cf..f14cbeee7a1 100644 >> --- a/gcc/config/i386/i386-opts.h >> +++ b/gcc/config/i386/i386-opts.h >> @@ -106,4 +106,12 @@ enum prefer_vector_width { >> PVW_AVX512 >> }; >> >> +enum indirect_branch { >> + indirect_branch_unset = 0, >> + indirect_branch_keep, >> + indirect_branch_thunk, >> + indirect_branch_thunk_inline, >> + indirect_branch_thunk_extern >> +}; > I think it would make sense to simply place the body of your introduction email > here as a comment explaining what enum indirect_branhc does. Will do. >> -/* 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. */ > > Technically we can use red-zone if we reserve space for the call address, right? > Perhaps mention it as TODO, that should even not be too hard to implement > but is probably not being that important code quality wise in practice. Will do, >> 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 >> @@ -5797,6 +5804,37 @@ ix86_set_func_type (tree fndecl) >> } >> } >> >> @@ -10639,6 +10681,191 @@ ix86_setup_frame_addresses (void) >> # endif >> #endif >> >> +static int indirectlabelno; >> +static bool indirect_thunk_needed = false; >> +static bool indirect_thunk_bnd_needed = false; >> + >> +static int indirect_thunks_used; >> +static int indirect_thunks_bnd_used; > > Add comments for variables. Will do. >> + >> +#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. Otherwise, the function address is on the >> + top of stack. */ > > Add here comment which gives example of full thunk body so it is easier > to see what is going on here. Sure. >> + >> +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 . */ >> + fprintf (asm_out_file, "\tpause\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; > > DECL_ARTIFICIAL as well? This is done exactly the same way as PIC thunk. I don't think we should change it here. > >> + >> +#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); >> + } >> + > > Why do you need to output asm visibility directives by hand when you create > symbol for the function anyway? This is done exactly the same way as PIC thunk. I don't think we should change it here. > I would expect that you can just use similar code as cgraph_node::expand_thunk > when calls output_mi_thunk and get this done in a way that is independent of > target assembler. I took a look. I don't see an easy to do it. I'd like to keep it exactly the same as PIC thunk. And we change both thunks together later if needed. >> >> +/* 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. */ > > Again please add example of code sequences output here to make code easier to follow. Will do. >> + >> +static void >> +ix86_output_indirect_branch (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; >> + >> + if (REG_P (call_op)) >> + regno = REGNO (call_op); >> + else >> + regno = -1; >> + >> + if (cfun->machine->indirect_branch_type >> + != indirect_branch_thunk_inline) >> + { >> + if (cfun->machine->indirect_branch_type == indirect_branch_thunk) >> + { >> + if (regno >= 0) >> + { >> + 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; >> + } >> + else >> + { >> + 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) >> + { >> + if (regno < 0) >> + 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 >> + { >> + if (regno >= 0 && 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 (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); >> + } >> + } >> + >> + if (regno < 0) >> + 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); >> + } >> +} > > It is really not very pretty that the whole sequence is injected into insn stream > as a single blob. How opaque it is? Does it need to be patched at dynamic link time? It must be very opaque to optimizers. No, we don't patch at load-time. > I suppose it may make sense to split the insn and at least explicitly represent > the fact that we (sometimes) push the target to stack. Did you mean "split the function"? I will break it into ix86_output_indirect_branch_via_reg and ix86_output_indirect_branch_via_push. > Why the memory variant exists at first place? When the function address is in memory, we can push it onto stack to save a register. Also it is needed to cover "call foo" to "call [foo@GOT]" conversion. >> (define_insn "*indirect_jump" >> [(set (pc) (match_operand:W 0 "indirect_branch_operand" "rBw"))] >> "" >> - "%!jmp\t%A0" >> + "* return ix86_output_indirect_jmp (operands[0], false);" >> [(set_attr "type" "ibr") >> (set_attr "length_immediate" "0") >> (set_attr "maybe_prefix_bnd" "1")]) > > I think you also want to update type to "many" when we do more than just indirect branch. Did you mean "multi"? I will change to "multi". > We do not care much about this, but it feels wrong to have attributes off reality. >> diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi >> index f3e4a63ab46..ddb6035be96 100644 >> --- a/gcc/doc/extend.texi >> +++ b/gcc/doc/extend.texi >> @@ -5754,6 +5754,16 @@ Specify which floating-point unit to use. You must specify the >> @code{target("fpmath=sse+387")} because the comma would separate >> different options. >> >> +@item indirect_branch("@var{choice}") >> +@cindex @code{indirect_branch} function attribute, x86 >> +On x86 targets, the @code{indirect_branch} attribute causes the compiler >> +to convert indirect call and jump with @var{choice}. @samp{keep} >> +keeps indirect call and jump unmodified. @samp{thunk} converts indirect >> +call and jump to call and return thunk. @samp{thunk-inline} converts >> +indirect call and jump to inlined call and return thunk. >> +@samp{thunk-extern} converts indirect call and jump to external call >> +and return thunk provided in a separate object file. > > Please expand the documentation in a way that random user who is not aware of the > issue will understand that those are security features that come at a cost. > >> +@opindex -mindirect-branch >> +Convert indirect call and jump with @var{choice}. The default is >> +@samp{keep}, which keeps indirect call and jump unmodified. >> +@samp{thunk} converts indirect call and jump to call and return thunk. >> +@samp{thunk-inline} converts indirect call and jump to inlined call >> +and return thunk. @samp{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 @code{indirect_branch}. @xref{Function Attributes}. > > Similarly here. I will update documentation with user guide info after Intel white paper is published. > Rest of the patch seems OK. We may want incrementally represent more of the > indirect jump/call seqeuence in RTL, but at this point probably keeping things > simple and localized is not a bad idea. This can be done incrementally. > > Please make updated patch and I would like to give others chance to comment today. > > Honza
> >> +/* 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; > > > > DECL_ARTIFICIAL as well? > > This is done exactly the same way as PIC thunk. I don't think we > should change it here. > > > > Why do you need to output asm visibility directives by hand when you create > > symbol for the function anyway? > > This is done exactly the same way as PIC thunk. I don't think we > should change it here. I see, it is pretty ancient code. Perhaps you can at least commonize the uglness so we don't duplicate the ifdefs for MACHO? > > > I would expect that you can just use similar code as cgraph_node::expand_thunk > > when calls output_mi_thunk and get this done in a way that is independent of > > target assembler. > > I took a look. I don't see an easy to do it. I'd like to keep it exactly the > same as PIC thunk. And we change both thunks together later if needed. OK, lets keep it done same was as PIC thunk. Next stage1 we could clean up both. > > I suppose it may make sense to split the insn and at least explicitly represent > > the fact that we (sometimes) push the target to stack. > > Did you mean "split the function"? I will break it into > ix86_output_indirect_branch_via_reg and > ix86_output_indirect_branch_via_push. I mean define_split to insert push instruction into the instruction stream rather then printing it as part of the indirect jump insn. > > > Why the memory variant exists at first place? > > When the function address is in memory, we can push it onto stack > to save a register. Also it is needed to cover "call foo" to "call [foo@GOT]" > conversion. > > > > I think you also want to update type to "many" when we do more than just indirect branch. > > Did you mean "multi"? I will change to "multi". Yes. > > > We do not care much about this, but it feels wrong to have attributes off reality. > >> diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi > >> index f3e4a63ab46..ddb6035be96 100644 > >> --- a/gcc/doc/extend.texi > >> +++ b/gcc/doc/extend.texi > >> @@ -5754,6 +5754,16 @@ Specify which floating-point unit to use. You must specify the > >> @code{target("fpmath=sse+387")} because the comma would separate > >> different options. > >> > >> +@item indirect_branch("@var{choice}") > >> +@cindex @code{indirect_branch} function attribute, x86 > >> +On x86 targets, the @code{indirect_branch} attribute causes the compiler > >> +to convert indirect call and jump with @var{choice}. @samp{keep} > >> +keeps indirect call and jump unmodified. @samp{thunk} converts indirect > >> +call and jump to call and return thunk. @samp{thunk-inline} converts > >> +indirect call and jump to inlined call and return thunk. > >> +@samp{thunk-extern} converts indirect call and jump to external call > >> +and return thunk provided in a separate object file. > > > > Please expand the documentation in a way that random user who is not aware of the > > issue will understand that those are security features that come at a cost. > > > >> +@opindex -mindirect-branch > >> +Convert indirect call and jump with @var{choice}. The default is > >> +@samp{keep}, which keeps indirect call and jump unmodified. > >> +@samp{thunk} converts indirect call and jump to call and return thunk. > >> +@samp{thunk-inline} converts indirect call and jump to inlined call > >> +and return thunk. @samp{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 @code{indirect_branch}. @xref{Function Attributes}. > > > > Similarly here. > > I will update documentation with user guide info after Intel white > paper is published. OK, thanks! Honza > > > Rest of the patch seems OK. We may want incrementally represent more of the > > indirect jump/call seqeuence in RTL, but at this point probably keeping things > > simple and localized is not a bad idea. This can be done incrementally. > > > > Please make updated patch and I would like to give others chance to comment today. > > > > Honza > > > > -- > H.J.
On Sat, Jan 13, 2018 at 7:56 AM, Jan Hubicka <hubicka@ucw.cz> wrote: >> >> +/* 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; >> > >> > DECL_ARTIFICIAL as well? >> >> This is done exactly the same way as PIC thunk. I don't think we >> should change it here. >> > >> > Why do you need to output asm visibility directives by hand when you create >> > symbol for the function anyway? >> >> This is done exactly the same way as PIC thunk. I don't think we >> should change it here. > > I see, it is pretty ancient code. Perhaps you can at least commonize > the uglness so we don't duplicate the ifdefs for MACHO? I don't think we should such surgery at such late stage. I prefer to keep it for later cleanup. >> >> > I would expect that you can just use similar code as cgraph_node::expand_thunk >> > when calls output_mi_thunk and get this done in a way that is independent of >> > target assembler. >> >> I took a look. I don't see an easy to do it. I'd like to keep it exactly the >> same as PIC thunk. And we change both thunks together later if needed. > > OK, lets keep it done same was as PIC thunk. > Next stage1 we could clean up both. >> > I suppose it may make sense to split the insn and at least explicitly represent >> > the fact that we (sometimes) push the target to stack. >> >> Did you mean "split the function"? I will break it into >> ix86_output_indirect_branch_via_reg and >> ix86_output_indirect_branch_via_push. > > I mean define_split to insert push instruction into the instruction > stream rather then printing it as part of the indirect jump insn. We don't want anything added between these instructions. Split it may lead to trouble. >> >> > Why the memory variant exists at first place? >> >> When the function address is in memory, we can push it onto stack >> to save a register. Also it is needed to cover "call foo" to "call [foo@GOT]" >> conversion. >> > >> > I think you also want to update type to "many" when we do more than just indirect branch. >> >> Did you mean "multi"? I will change to "multi". > > Yes. > >> >> > We do not care much about this, but it feels wrong to have attributes off reality. >> >> diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi >> >> index f3e4a63ab46..ddb6035be96 100644 >> >> --- a/gcc/doc/extend.texi >> >> +++ b/gcc/doc/extend.texi >> >> @@ -5754,6 +5754,16 @@ Specify which floating-point unit to use. You must specify the >> >> @code{target("fpmath=sse+387")} because the comma would separate >> >> different options. >> >> >> >> +@item indirect_branch("@var{choice}") >> >> +@cindex @code{indirect_branch} function attribute, x86 >> >> +On x86 targets, the @code{indirect_branch} attribute causes the compiler >> >> +to convert indirect call and jump with @var{choice}. @samp{keep} >> >> +keeps indirect call and jump unmodified. @samp{thunk} converts indirect >> >> +call and jump to call and return thunk. @samp{thunk-inline} converts >> >> +indirect call and jump to inlined call and return thunk. >> >> +@samp{thunk-extern} converts indirect call and jump to external call >> >> +and return thunk provided in a separate object file. >> > >> > Please expand the documentation in a way that random user who is not aware of the >> > issue will understand that those are security features that come at a cost. >> > >> >> +@opindex -mindirect-branch >> >> +Convert indirect call and jump with @var{choice}. The default is >> >> +@samp{keep}, which keeps indirect call and jump unmodified. >> >> +@samp{thunk} converts indirect call and jump to call and return thunk. >> >> +@samp{thunk-inline} converts indirect call and jump to inlined call >> >> +and return thunk. @samp{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 @code{indirect_branch}. @xref{Function Attributes}. >> > >> > Similarly here. >> >> I will update documentation with user guide info after Intel white >> paper is published. > > OK, > thanks! > Honza >> >> > Rest of the patch seems OK. We may want incrementally represent more of the >> > indirect jump/call seqeuence in RTL, but at this point probably keeping things >> > simple and localized is not a bad idea. This can be done incrementally. >> > >> > Please make updated patch and I would like to give others chance to comment today. >> > >> > Honza >> >> >> >> -- >> H.J.
> On Sat, Jan 13, 2018 at 7:56 AM, Jan Hubicka <hubicka@ucw.cz> wrote: > >> >> +/* 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; > >> > > >> > DECL_ARTIFICIAL as well? > >> > >> This is done exactly the same way as PIC thunk. I don't think we > >> should change it here. > >> > > >> > Why do you need to output asm visibility directives by hand when you create > >> > symbol for the function anyway? > >> > >> This is done exactly the same way as PIC thunk. I don't think we > >> should change it here. > > > > I see, it is pretty ancient code. Perhaps you can at least commonize > > the uglness so we don't duplicate the ifdefs for MACHO? > > I don't think we should such surgery at such late stage. I prefer to > keep it for later cleanup. OK, lets keep it as it is and clean up next stage1. Honza > > >> > >> > I would expect that you can just use similar code as cgraph_node::expand_thunk > >> > when calls output_mi_thunk and get this done in a way that is independent of > >> > target assembler. > >> > >> I took a look. I don't see an easy to do it. I'd like to keep it exactly the > >> same as PIC thunk. And we change both thunks together later if needed. > > > > OK, lets keep it done same was as PIC thunk. > > Next stage1 we could clean up both. > >> > I suppose it may make sense to split the insn and at least explicitly represent > >> > the fact that we (sometimes) push the target to stack. > >> > >> Did you mean "split the function"? I will break it into > >> ix86_output_indirect_branch_via_reg and > >> ix86_output_indirect_branch_via_push. > > > > I mean define_split to insert push instruction into the instruction > > stream rather then printing it as part of the indirect jump insn. > > We don't want anything added between these instructions. > Split it may lead to trouble. > > >> > >> > Why the memory variant exists at first place? > >> > >> When the function address is in memory, we can push it onto stack > >> to save a register. Also it is needed to cover "call foo" to "call [foo@GOT]" > >> conversion. > >> > > >> > I think you also want to update type to "many" when we do more than just indirect branch. > >> > >> Did you mean "multi"? I will change to "multi". > > > > Yes. > > > >> > >> > We do not care much about this, but it feels wrong to have attributes off reality. > >> >> diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi > >> >> index f3e4a63ab46..ddb6035be96 100644 > >> >> --- a/gcc/doc/extend.texi > >> >> +++ b/gcc/doc/extend.texi > >> >> @@ -5754,6 +5754,16 @@ Specify which floating-point unit to use. You must specify the > >> >> @code{target("fpmath=sse+387")} because the comma would separate > >> >> different options. > >> >> > >> >> +@item indirect_branch("@var{choice}") > >> >> +@cindex @code{indirect_branch} function attribute, x86 > >> >> +On x86 targets, the @code{indirect_branch} attribute causes the compiler > >> >> +to convert indirect call and jump with @var{choice}. @samp{keep} > >> >> +keeps indirect call and jump unmodified. @samp{thunk} converts indirect > >> >> +call and jump to call and return thunk. @samp{thunk-inline} converts > >> >> +indirect call and jump to inlined call and return thunk. > >> >> +@samp{thunk-extern} converts indirect call and jump to external call > >> >> +and return thunk provided in a separate object file. > >> > > >> > Please expand the documentation in a way that random user who is not aware of the > >> > issue will understand that those are security features that come at a cost. > >> > > >> >> +@opindex -mindirect-branch > >> >> +Convert indirect call and jump with @var{choice}. The default is > >> >> +@samp{keep}, which keeps indirect call and jump unmodified. > >> >> +@samp{thunk} converts indirect call and jump to call and return thunk. > >> >> +@samp{thunk-inline} converts indirect call and jump to inlined call > >> >> +and return thunk. @samp{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 @code{indirect_branch}. @xref{Function Attributes}. > >> > > >> > Similarly here. > >> > >> I will update documentation with user guide info after Intel white > >> paper is published. > > > > OK, > > thanks! > > Honza > >> > >> > Rest of the patch seems OK. We may want incrementally represent more of the > >> > indirect jump/call seqeuence in RTL, but at this point probably keeping things > >> > simple and localized is not a bad idea. This can be done incrementally. > >> > > >> > Please make updated patch and I would like to give others chance to comment today. > >> > > >> > Honza > >> > >> > >> > >> -- > >> H.J. > > > > -- > H.J.
On Sat, Jan 13, 2018 at 9:30 AM, Jan Hubicka <hubicka@ucw.cz> wrote: >> On Sat, Jan 13, 2018 at 7:56 AM, Jan Hubicka <hubicka@ucw.cz> wrote: >> >> >> +/* 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; >> >> > >> >> > DECL_ARTIFICIAL as well? >> >> >> >> This is done exactly the same way as PIC thunk. I don't think we >> >> should change it here. >> >> > >> >> > Why do you need to output asm visibility directives by hand when you create >> >> > symbol for the function anyway? >> >> >> >> This is done exactly the same way as PIC thunk. I don't think we >> >> should change it here. >> > >> > I see, it is pretty ancient code. Perhaps you can at least commonize >> > the uglness so we don't duplicate the ifdefs for MACHO? >> >> I don't think we should such surgery at such late stage. I prefer to >> keep it for later cleanup. > > OK, lets keep it as it is and clean up next stage1. > > The new set of patches are at https://gcc.gnu.org/ml/gcc-patches/2018-01/msg01200.html https://gcc.gnu.org/ml/gcc-patches/2018-01/msg01197.html https://gcc.gnu.org/ml/gcc-patches/2018-01/msg01198.html https://gcc.gnu.org/ml/gcc-patches/2018-01/msg01201.html https://gcc.gnu.org/ml/gcc-patches/2018-01/msg01199.html
diff --git a/gcc/config/i386/i386-opts.h b/gcc/config/i386/i386-opts.h index f245c1573cf..f14cbeee7a1 100644 --- a/gcc/config/i386/i386-opts.h +++ b/gcc/config/i386/i386-opts.h @@ -106,4 +106,12 @@ enum prefer_vector_width { PVW_AVX512 }; +enum indirect_branch { + indirect_branch_unset = 0, + indirect_branch_keep, + indirect_branch_thunk, + indirect_branch_thunk_inline, + indirect_branch_thunk_extern +}; + #endif diff --git a/gcc/config/i386/i386-protos.h b/gcc/config/i386/i386-protos.h index 0e49652898c..bf11cc426f9 100644 --- a/gcc/config/i386/i386-protos.h +++ b/gcc/config/i386/i386-protos.h @@ -305,6 +305,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); extern bool ix86_operands_ok_for_move_multiple (rtx *operands, bool load, machine_mode mode); extern int ix86_min_insn_size (rtx_insn *); diff --git a/gcc/config/i386/i386.c b/gcc/config/i386/i386.c index 5ee3be386df..cead7a61a91 100644 --- a/gcc/config/i386/i386.c +++ b/gcc/config/i386/i386.c @@ -2724,12 +2724,19 @@ make_pass_insert_endbranch (gcc::context *ctxt) return new pass_insert_endbranch (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. */ 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 @@ -5797,6 +5804,37 @@ ix86_set_func_type (tree fndecl) } } +/* 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. */ @@ -5812,7 +5850,10 @@ ix86_set_current_function (tree fndecl) one is extern inline and one isn't. Call ix86_set_func_type to set the func_type field. */ if (fndecl != NULL_TREE) - ix86_set_func_type (fndecl); + { + ix86_set_func_type (fndecl); + ix86_set_indirect_branch_type (fndecl); + } return; } @@ -5832,6 +5873,7 @@ ix86_set_current_function (tree fndecl) } ix86_set_func_type (fndecl); + ix86_set_indirect_branch_type (fndecl); tree new_tree = DECL_FUNCTION_SPECIFIC_TARGET (fndecl); if (new_tree == NULL_TREE) @@ -10639,6 +10681,191 @@ ix86_setup_frame_addresses (void) # endif #endif +static int indirectlabelno; +static bool indirect_thunk_needed = false; +static bool indirect_thunk_bnd_needed = false; + +static int indirect_thunks_used; +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. Otherwise, the function address is on the + top of stack. */ + +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 . */ + fprintf (asm_out_file, "\tpause\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 @@ -10665,11 +10892,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 = FIRST_INT_REG; regno <= LAST_INT_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; @@ -28150,12 +28398,189 @@ ix86_nopic_noplt_attribute_p (rtx call_op) return false; } +/* 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) +{ + 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; + + if (REG_P (call_op)) + regno = REGNO (call_op); + else + regno = -1; + + if (cfun->machine->indirect_branch_type + != indirect_branch_thunk_inline) + { + if (cfun->machine->indirect_branch_type == indirect_branch_thunk) + { + if (regno >= 0) + { + 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; + } + else + { + 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) + { + if (regno < 0) + 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 + { + if (regno >= 0 && 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 (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); + } + } + + if (regno < 0) + 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 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; @@ -28165,10 +28590,21 @@ ix86_output_call_insn (rtx_insn *insn, rtx call_op) { if (ix86_nopic_noplt_attribute_p (call_op)) { + direct_p = false; if (TARGET_64BIT) - xasm = "%!jmp\t{*%p0@GOTPCREL(%%rip)|[QWORD PTR %p0@GOTPCREL[rip]]}"; + { + if (output_indirect_p) + xasm = "{%p0@GOTPCREL(%%rip)|[QWORD PTR %p0@GOTPCREL[rip]]}"; + else + xasm = "%!jmp\t{*%p0@GOTPCREL(%%rip)|[QWORD PTR %p0@GOTPCREL[rip]]}"; + } else - xasm = "%!jmp\t{*%p0@GOT|[DWORD PTR %p0@GOT]}"; + { + if (output_indirect_p) + xasm = "{%p0@GOT|[DWORD PTR %p0@GOT]}"; + else + xasm = "%!jmp\t{*%p0@GOT|[DWORD PTR %p0@GOT]}"; + } } else xasm = "%!jmp\t%P0"; @@ -28178,9 +28614,17 @@ ix86_output_call_insn (rtx_insn *insn, rtx call_op) else if (TARGET_SEH) xasm = "%!rex.W jmp\t%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 ""; } @@ -28218,18 +28662,37 @@ ix86_output_call_insn (rtx_insn *insn, rtx call_op) { if (ix86_nopic_noplt_attribute_p (call_op)) { + direct_p = false; if (TARGET_64BIT) - xasm = "%!call\t{*%p0@GOTPCREL(%%rip)|[QWORD PTR %p0@GOTPCREL[rip]]}"; + { + if (output_indirect_p) + xasm = "{%p0@GOTPCREL(%%rip)|[QWORD PTR %p0@GOTPCREL[rip]]}"; + else + xasm = "%!call\t{*%p0@GOTPCREL(%%rip)|[QWORD PTR %p0@GOTPCREL[rip]]}"; + } else - xasm = "%!call\t{*%p0@GOT|[DWORD PTR %p0@GOT]}"; + { + if (output_indirect_p) + xasm = "{%p0@GOT|[DWORD PTR %p0@GOT]}"; + else + xasm = "%!call\t{*%p0@GOT|[DWORD PTR %p0@GOT]}"; + } } else 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"; @@ -40387,7 +40850,7 @@ ix86_handle_struct_attribute (tree *node, tree name, tree, int, } 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) @@ -40396,6 +40859,29 @@ ix86_handle_fndecl_attribute (tree *node, tree name, tree, int, 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; } @@ -44842,6 +45328,8 @@ static const struct attribute_spec ix86_attribute_table[] = ix86_handle_no_caller_saved_registers_attribute, NULL }, { "naked", 0, 0, true, false, false, false, ix86_handle_fndecl_attribute, NULL }, + { "indirect_branch", 1, 1, true, false, false, false, + ix86_handle_fndecl_attribute, NULL }, /* End element. */ { NULL, 0, 0, false, false, false, false, NULL, NULL } diff --git a/gcc/config/i386/i386.h b/gcc/config/i386/i386.h index 933f261ea66..3b939086112 100644 --- a/gcc/config/i386/i386.h +++ b/gcc/config/i386/i386.h @@ -2570,6 +2570,13 @@ struct GTY(()) machine_function { /* Function type. */ ENUM_BITFIELD(function_type) func_type : 2; + /* 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; + /* If true, the current function is a function specified with the "interrupt" or "no_caller_saved_registers" attribute. */ BOOL_BITFIELD no_caller_saved_registers : 1; diff --git a/gcc/config/i386/i386.md b/gcc/config/i386/i386.md index 3f587806407..a7573c468ae 100644 --- a/gcc/config/i386/i386.md +++ b/gcc/config/i386/i386.md @@ -12313,12 +12313,13 @@ { 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" + "* return ix86_output_indirect_jmp (operands[0], false);" [(set_attr "type" "ibr") (set_attr "length_immediate" "0") (set_attr "maybe_prefix_bnd" "1")]) @@ -12362,13 +12363,14 @@ 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" + "* return ix86_output_indirect_jmp (operands[0], false);" [(set_attr "type" "ibr") (set_attr "length_immediate" "0") (set_attr "maybe_prefix_bnd" "1")]) @@ -13097,7 +13099,7 @@ [(simple_return) (use (match_operand 0 "register_operand" "r"))] "reload_completed" - "%!jmp\t%A0" + "* return ix86_output_indirect_jmp (operands[0], true);" [(set_attr "type" "ibr") (set_attr "length_immediate" "0") (set_attr "maybe_prefix_bnd" "1")]) diff --git a/gcc/config/i386/i386.opt b/gcc/config/i386/i386.opt index 09aaa97c2fc..59e5cc8e7e4 100644 --- a/gcc/config/i386/i386.opt +++ b/gcc/config/i386/i386.opt @@ -1021,3 +1021,23 @@ indirect jump. mforce-indirect-call Target Report Var(flag_force_indirect_call) Init(0) Make all function calls indirect. + +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) diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi index f3e4a63ab46..ddb6035be96 100644 --- a/gcc/doc/extend.texi +++ b/gcc/doc/extend.texi @@ -5754,6 +5754,16 @@ Specify which floating-point unit to use. You must specify the @code{target("fpmath=sse+387")} because the comma would separate different options. +@item indirect_branch("@var{choice}") +@cindex @code{indirect_branch} function attribute, x86 +On x86 targets, the @code{indirect_branch} attribute causes the compiler +to convert indirect call and jump with @var{choice}. @samp{keep} +keeps indirect call and jump unmodified. @samp{thunk} converts indirect +call and jump to call and return thunk. @samp{thunk-inline} converts +indirect call and jump to inlined call and return thunk. +@samp{thunk-extern} converts indirect call and jump to external call +and return thunk provided in a separate object file. + @item nocf_check @cindex @code{nocf_check} function attribute The @code{nocf_check} attribute on a function is used to inform the diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index a9449a86064..0d685c3576b 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -1229,7 +1229,8 @@ See RS/6000 and PowerPC Options. -mstack-protector-guard-reg=@var{reg} @gol -mstack-protector-guard-offset=@var{offset} @gol -mstack-protector-guard-symbol=@var{symbol} -mmitigate-rop @gol --mgeneral-regs-only -mcall-ms2sysv-xlogues} +-mgeneral-regs-only -mcall-ms2sysv-xlogues @gol +-mindirect-branch=@var{choice}} @emph{x86 Windows Options} @gccoptlist{-mconsole -mcygwin -mno-cygwin -mdll @gol @@ -26838,6 +26839,17 @@ Generate code that uses only the general-purpose registers. This prevents the compiler from using floating-point, vector, mask and bound registers. +@item -mindirect-branch=@var{choice} +@opindex -mindirect-branch +Convert indirect call and jump with @var{choice}. The default is +@samp{keep}, which keeps indirect call and jump unmodified. +@samp{thunk} converts indirect call and jump to call and return thunk. +@samp{thunk-inline} converts indirect call and jump to inlined call +and return thunk. @samp{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 @code{indirect_branch}. @xref{Function Attributes}. + @end table These @samp{-m} switches are supported in addition to the above diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-1.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-1.c new file mode 100644 index 00000000000..d1d2ee78797 --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-1.c @@ -0,0 +1,19 @@ +/* { 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} } } */ diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-2.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-2.c new file mode 100644 index 00000000000..08646c6b823 --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-2.c @@ -0,0 +1,19 @@ +/* { 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} } } */ diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-3.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-3.c new file mode 100644 index 00000000000..af244de2238 --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-3.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; + +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} } } */ diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-4.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-4.c new file mode 100644 index 00000000000..b8aedd5a4e6 --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-4.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]; + +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} } } */ diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-5.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-5.c new file mode 100644 index 00000000000..6ffb9235f94 --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-5.c @@ -0,0 +1,16 @@ +/* { dg-do compile { target *-*-linux* } } */ +/* { dg-options "-O2 -fpic -fno-plt -mindirect-branch=thunk" } */ + +extern void bar (void); + +void +foo (void) +{ + bar (); +} + +/* { dg-final { scan-assembler "push(?:l|q)\[ \t\]*bar@GOT" } } */ +/* { dg-final { scan-assembler "jmp\[ \t\]*__x86_indirect_thunk" } } */ +/* { dg-final { scan-assembler "jmp\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler "call\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler {\tpause} } } */ diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-6.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-6.c new file mode 100644 index 00000000000..e6d9d148cd2 --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-6.c @@ -0,0 +1,17 @@ +/* { dg-do compile { target *-*-linux* } } */ +/* { dg-options "-O2 -fpic -fno-plt -mindirect-branch=thunk" } */ + +extern void bar (void); + +int +foo (void) +{ + bar (); + return 0; +} + +/* { dg-final { scan-assembler "push(?:l|q)\[ \t\]*bar@GOT" } } */ +/* { dg-final { scan-assembler "jmp\[ \t\]*__x86_indirect_thunk" } } */ +/* { dg-final { scan-assembler-times "jmp\[ \t\]*\.LIND" 2 } } */ +/* { dg-final { scan-assembler-times "call\[ \t\]*\.LIND" 2 } } */ +/* { dg-final { scan-assembler {\tpause} } } */ diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-7.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-7.c new file mode 100644 index 00000000000..d892d8f5992 --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-7.c @@ -0,0 +1,43 @@ +/* { 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} } } */ diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-1.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-1.c new file mode 100644 index 00000000000..24188d0b62d --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-1.c @@ -0,0 +1,22 @@ +/* { 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} } } */ diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-2.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-2.c new file mode 100644 index 00000000000..03184b90cda --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-2.c @@ -0,0 +1,20 @@ +/* { 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} } } */ diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-3.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-3.c new file mode 100644 index 00000000000..af167840b81 --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-3.c @@ -0,0 +1,21 @@ +/* { 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-not "__x86_indirect_thunk" } } */ +/* { dg-final { scan-assembler-not "pushq\[ \t\]%rax" { target x32 } } } */ diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-4.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-4.c new file mode 100644 index 00000000000..146124894a0 --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-4.c @@ -0,0 +1,20 @@ +/* { 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-not "__x86_indirect_thunk" } } */ +/* { dg-final { scan-assembler-not "pushq\[ \t\]%rax" { target x32 } } } */ diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-5.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-5.c new file mode 100644 index 00000000000..568327cd8e7 --- /dev/null +++ b/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(pause|pause)} } } */ diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-6.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-6.c new file mode 100644 index 00000000000..bd8a99e7828 --- /dev/null +++ b/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(pause|pause)} } } */ diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-7.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-7.c new file mode 100644 index 00000000000..356015c9799 --- /dev/null +++ b/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(pause|pause)} } } */ +/* { dg-final { scan-assembler-not "jmp\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler-not "call\[ \t\]*\.LIND" } } */ diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-8.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-8.c new file mode 100644 index 00000000000..6960fa0bbfb --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-attr-8.c @@ -0,0 +1,41 @@ +/* { 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 "jmp\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler-not "call\[ \t\]*\.LIND" } } */ diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-bnd-1.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-bnd-1.c new file mode 100644 index 00000000000..febf32d76ea --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-bnd-1.c @@ -0,0 +1,19 @@ +/* { 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} } } */ diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-bnd-2.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-bnd-2.c new file mode 100644 index 00000000000..319ba30b78b --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-bnd-2.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]; + +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} } } */ diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-bnd-3.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-bnd-3.c new file mode 100644 index 00000000000..9168b3146f5 --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-bnd-3.c @@ -0,0 +1,18 @@ +/* { dg-do compile { target { *-*-linux* && { ! x32 } } } } */ +/* { dg-options "-O2 -mindirect-branch=thunk -fcheck-pointer-bounds -mmpx -fpic -fno-plt" } */ + +void bar (char *); +char buf[10]; + +void +foo (void) +{ + bar (buf); +} + +/* { dg-final { scan-assembler "push(?:l|q)\[ \t\]*bar@GOT" } } */ +/* { 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} } } */ diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-bnd-4.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-bnd-4.c new file mode 100644 index 00000000000..d3b36d44c7c --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-bnd-4.c @@ -0,0 +1,19 @@ +/* { dg-do compile { target { *-*-linux* && { ! x32 } } } } */ +/* { dg-options "-O2 -mindirect-branch=thunk -fcheck-pointer-bounds -mmpx -fpic -fno-plt" } */ + +void bar (char *); +char buf[10]; + +int +foo (void) +{ + bar (buf); + return 0; +} + +/* { dg-final { scan-assembler "push(?:l|q)\[ \t\]*bar@GOT" } } */ +/* { dg-final { scan-assembler "bnd jmp\[ \t\]*__x86_indirect_thunk" } } */ +/* { dg-final { scan-assembler "bnd jmp\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler-times "bnd call\[ \t\]*\.LIND" 2 } } */ +/* { dg-final { scan-assembler "bnd ret" } } */ +/* { dg-final { scan-assembler {\tpause} } } */ diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-1.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-1.c new file mode 100644 index 00000000000..9e50b282f77 --- /dev/null +++ b/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(pause|pause)} } } */ +/* { dg-final { scan-assembler-not "jmp\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler-not "call\[ \t\]*\.LIND" } } */ diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-2.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-2.c new file mode 100644 index 00000000000..f897d1c0497 --- /dev/null +++ b/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(pause|pause)} } } */ +/* { dg-final { scan-assembler-not "jmp\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler-not "call\[ \t\]*\.LIND" } } */ diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-3.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-3.c new file mode 100644 index 00000000000..25905cd0016 --- /dev/null +++ b/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(pause|pause)} } } */ diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-4.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-4.c new file mode 100644 index 00000000000..a7fa12183af --- /dev/null +++ b/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(pause|pause)} } } */ diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-5.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-5.c new file mode 100644 index 00000000000..48a49760be6 --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-5.c @@ -0,0 +1,16 @@ +/* { dg-do compile { target *-*-linux* } } */ +/* { dg-options "-O2 -fpic -fno-plt -mindirect-branch=thunk-extern" } */ + +extern void bar (void); + +void +foo (void) +{ + bar (); +} + +/* { dg-final { scan-assembler "push(?:l|q)\[ \t\]*bar@GOT" } } */ +/* { dg-final { scan-assembler "jmp\[ \t\]*__x86_indirect_thunk" } } */ +/* { dg-final { scan-assembler-not {\t(pause|pause)} } } */ +/* { dg-final { scan-assembler-not "jmp\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler-not "call\[ \t\]*\.LIND" } } */ diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-6.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-6.c new file mode 100644 index 00000000000..a1c662f7d23 --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-6.c @@ -0,0 +1,17 @@ +/* { dg-do compile { target *-*-linux* } } */ +/* { dg-options "-O2 -fpic -fno-plt -mindirect-branch=thunk-extern" } */ + +extern void bar (void); + +int +foo (void) +{ + bar (); + return 0; +} + +/* { dg-final { scan-assembler "push(?:l|q)\[ \t\]*bar@GOT" } } */ +/* { dg-final { scan-assembler-times "jmp\[ \t\]*\.LIND" 1 } } */ +/* { dg-final { scan-assembler-times "call\[ \t\]*\.LIND" 1 } } */ +/* { dg-final { scan-assembler "jmp\[ \t\]*__x86_indirect_thunk" } } */ +/* { dg-final { scan-assembler-not {\t(pause|pause)} } } */ diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-7.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-extern-7.c new file mode 100644 index 00000000000..40a665ea640 --- /dev/null +++ b/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(pause|pause)} } } */ +/* { dg-final { scan-assembler-not "jmp\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler-not "call\[ \t\]*\.LIND" } } */ diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-1.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-1.c new file mode 100644 index 00000000000..3ace8d1b031 --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-1.c @@ -0,0 +1,18 @@ +/* { 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-not "__x86_indirect_thunk" } } */ +/* { dg-final { scan-assembler-not "pushq\[ \t\]%rax" { target x32 } } } */ diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-2.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-2.c new file mode 100644 index 00000000000..6c97b96f1f2 --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-2.c @@ -0,0 +1,18 @@ +/* { 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-not "__x86_indirect_thunk" } } */ +/* { dg-final { scan-assembler-not "pushq\[ \t\]%rax" { target x32 } } } */ diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-3.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-3.c new file mode 100644 index 00000000000..8f6759cbf06 --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-3.c @@ -0,0 +1,19 @@ +/* { 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-not "__x86_indirect_thunk" } } */ +/* { dg-final { scan-assembler-not "pushq\[ \t\]%rax" { target x32 } } } */ diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-4.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-4.c new file mode 100644 index 00000000000..b07d08cab0f --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-4.c @@ -0,0 +1,19 @@ +/* { 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-not "__x86_indirect_thunk" } } */ +/* { dg-final { scan-assembler-not "pushq\[ \t\]%rax" { target x32 } } } */ diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-5.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-5.c new file mode 100644 index 00000000000..10794886b1b --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-5.c @@ -0,0 +1,15 @@ +/* { dg-do compile { target *-*-linux* } } */ +/* { dg-options "-O2 -fpic -fno-plt -mindirect-branch=thunk-inline" } */ + +extern void bar (void); + +void +foo (void) +{ + bar (); +} + +/* { dg-final { scan-assembler "push(?:l|q)\[ \t\]*bar@GOT" } } */ +/* { dg-final { scan-assembler "jmp\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler "call\[ \t\]*\.LIND" } } */ +/* { dg-final { scan-assembler-not "__x86_indirect_thunk" } } */ diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-6.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-6.c new file mode 100644 index 00000000000..a26ec4b06ed --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-6.c @@ -0,0 +1,16 @@ +/* { dg-do compile { target *-*-linux* } } */ +/* { dg-options "-O2 -fpic -fno-plt -mindirect-branch=thunk-inline" } */ + +extern void bar (void); + +int +foo (void) +{ + bar (); + return 0; +} + +/* { dg-final { scan-assembler "push(?:l|q)\[ \t\]*bar@GOT" } } */ +/* { dg-final { scan-assembler-times "jmp\[ \t\]*\.LIND" 2 } } */ +/* { dg-final { scan-assembler-times "call\[ \t\]*\.LIND" 2 } } */ +/* { dg-final { scan-assembler-not "__x86_indirect_thunk" } } */ diff --git a/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-7.c b/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-7.c new file mode 100644 index 00000000000..77253af17c6 --- /dev/null +++ b/gcc/testsuite/gcc.target/i386/indirect-thunk-inline-7.c @@ -0,0 +1,42 @@ +/* { 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-not "__x86_indirect_thunk" } } */