From 4e30c2cb7ca6faca5b3a67bef3c1f0d17cac437d Mon Sep 17 00:00:00 2001 From: Tee-Kiah Chia Date: Wed, 18 Apr 2018 00:27:53 +0800 Subject: [PATCH] IA-16: add `assume_ds_data', `no_assume_ds_data' func. attribs. This allows one to specify whether each individual function assumes %ds == .data. Hopefully this change will make it easier, in the future, to add support for expressing far callbacks, far IRQ handlers, etc. (https://github.com/tkchia/gcc-ia16/issues/19). The test cases and documentation have also been cleaned up and updated. --- gcc/config/ia16/ia16-c.c | 2 + gcc/config/ia16/ia16-protos.h | 4 +- gcc/config/ia16/ia16.c | 281 +++++++++++++----- gcc/config/ia16/ia16.h | 11 +- gcc/config/ia16/ia16.md | 12 +- gcc/doc/extend.texi | 27 +- gcc/doc/invoke.texi | 12 +- .../gcc.target/ia16/torture/abi-ds-1.c | 80 +++++ .../gcc.target/ia16/torture/abi-ds-2.c | 80 +++++ .../gcc.target/ia16/torture/abi-ds-3.c | 76 +++++ .../gcc.target/ia16/torture/abi-ds-4.c | 68 +++++ .../gcc.target/ia16/torture/far-addr-29.c | 2 +- .../gcc.target/ia16/torture/ia16-torture.exp | 53 +++- .../gcc.target/ia16/torture/prot-mode-1.c | 2 +- 14 files changed, 612 insertions(+), 98 deletions(-) create mode 100644 gcc/testsuite/gcc.target/ia16/torture/abi-ds-1.c create mode 100644 gcc/testsuite/gcc.target/ia16/torture/abi-ds-2.c create mode 100644 gcc/testsuite/gcc.target/ia16/torture/abi-ds-3.c create mode 100644 gcc/testsuite/gcc.target/ia16/torture/abi-ds-4.c diff --git a/gcc/config/ia16/ia16-c.c b/gcc/config/ia16/ia16-c.c index 49cd4146bb9bd..742d7637d6d38 100644 --- a/gcc/config/ia16/ia16-c.c +++ b/gcc/config/ia16/ia16-c.c @@ -76,6 +76,8 @@ ia16_cpu_cpp_builtins (void) cpp_define (parse_in, "__IA16_FEATURE_FAR_FUNCTION_SYNTAX"); cpp_define (parse_in, "__IA16_FEATURE_ATTRIBUTE_CDECL"); cpp_define (parse_in, "__IA16_FEATURE_ATTRIBUTE_STDCALL"); + cpp_define (parse_in, "__IA16_FEATURE_ATTRIBUTE_ASSUME_DS_DATA"); + cpp_define (parse_in, "__IA16_FEATURE_ATTRIBUTE_NO_ASSUME_DS_DATA"); /* Also define a macro to give the function calling convention settings in use. */ diff --git a/gcc/config/ia16/ia16-protos.h b/gcc/config/ia16/ia16-protos.h index 5f36b2051ce72..f41f5a87a5794 100644 --- a/gcc/config/ia16/ia16-protos.h +++ b/gcc/config/ia16/ia16-protos.h @@ -24,9 +24,7 @@ extern struct gcc_target targetm; extern unsigned char ia16_hard_regno_nregs[17][FIRST_PSEUDO_REGISTER]; extern enum reg_class const ia16_regno_class[FIRST_PSEUDO_REGISTER]; extern int ia16_regno_in_class_p (unsigned, unsigned); -extern int ia16_far_function_type_p (const_tree funtype); extern int ia16_in_far_function_p (void); -extern int ia16_far_function_rtx_p (rtx addr); extern bool ia16_have_seg_override_p (rtx x); extern HOST_WIDE_INT ia16_first_parm_offset (tree fundecl); extern HOST_WIDE_INT ia16_initial_frame_pointer_offset (void); @@ -62,7 +60,7 @@ extern bool ia16_non_overlapping_mem_p (rtx m1, rtx m2); #endif extern void ia16_expand_prologue (void); -extern void ia16_expand_reset_ds_for_call (void); +extern void ia16_expand_reset_ds_for_call (rtx addr); extern void ia16_expand_epilogue (bool sibcall); extern const char * ia16_get_call_expansion (rtx, machine_mode, bool); diff --git a/gcc/config/ia16/ia16.c b/gcc/config/ia16/ia16.c index 6594a524fd69f..32ec99f76afd2 100755 --- a/gcc/config/ia16/ia16.c +++ b/gcc/config/ia16/ia16.c @@ -225,12 +225,53 @@ ia16_save_reg_p (unsigned int r) (df_regs_ever_live_p (r + 1) && !call_used_regs[r + 1])); } +/* Given the address ADDR of a function, return its declaration node, or + NULL_TREE if none can be found. */ +static tree +ia16_get_function_decl_for_addr (rtx addr) +{ + switch (GET_CODE (addr)) + { + case MEM: + return MEM_EXPR (addr); + break; + + case REG: + return REG_EXPR (addr); + break; + + case SYMBOL_REF: + return SYMBOL_REF_DECL (addr); + break; + + default: + return NULL_TREE; + } +} + +/* Given the address ADDR of a function, return its type node, or NULL_TREE + if no type node can be found. */ +static tree +ia16_get_function_type_for_addr (rtx addr) +{ + tree decl = ia16_get_function_decl_for_addr (addr), type; + + if (! decl) + return NULL_TREE; + + type = TREE_TYPE (decl); + while (POINTER_TYPE_P (type)) + type = TREE_TYPE (type); + + return type; +} + /* Return true iff TYPE is a type for a far function (which returns with `lret'). Currently a function is considered to be "far" if the function type itself is in __far space. The `-mfar-function-if-far-return-type' switch will also cause a __far on the return type to magically become a __far on the function itself. */ -int +static int ia16_far_function_type_p (const_tree funtype) { return TYPE_ADDR_SPACE (funtype) == ADDR_SPACE_FAR; @@ -240,42 +281,73 @@ ia16_far_function_type_p (const_tree funtype) int ia16_in_far_function_p (void) { - return ia16_far_function_type_p (TREE_TYPE (cfun->decl)); + return cfun && cfun->decl + && ia16_far_function_type_p (TREE_TYPE (cfun->decl)); } /* Return true if ADDR is known to be the address of a far function. FIXME: this may not be 100% reliable... */ -int +static int ia16_far_function_rtx_p (rtx addr) { - tree decl, type; + tree type = ia16_get_function_type_for_addr (addr); + return type && ia16_far_function_type_p (type); +} - switch (GET_CODE (addr)) - { - case MEM: - decl = MEM_EXPR (addr); - break; +/* Return true iff TYPE is a type for a function which assumes that %ds + points to the program's data segment on function entry. - case REG: - decl = REG_EXPR (addr); - break; + If %ds is a call-saved register (-fcall-saved-ds), this is always false. */ +int +ia16_ds_data_function_type_p (const_tree funtype) +{ + if (! call_used_regs[DS_REG]) + return 0; + else if (TARGET_ASSUME_DS_DATA) + return ! lookup_attribute ("no_assume_ds_data", TYPE_ATTRIBUTES (funtype)); + else + return lookup_attribute ("assume_ds_data", TYPE_ATTRIBUTES (funtype)) + != NULL_TREE; +} - case SYMBOL_REF: - decl = SYMBOL_REF_DECL (addr); - break; +/* Return true iff TYPE is a type for a function which follows the default + ABI for %ds --- which says that %ds + * is considered (by the GCC middle-end) to be a call-used register, + * is assumed to point to the program's data segment on function entry, + * and is restored to the data segment on function exit. */ +static int +ia16_default_ds_abi_function_type_p (const_tree funtype) +{ + return TARGET_ALLOCABLE_DS_REG + && ia16_ds_data_function_type_p (funtype); +} - default: - return 0; - } +static int +ia16_in_ds_data_function_p (void) +{ + return cfun && cfun->decl + && ia16_ds_data_function_type_p (TREE_TYPE (cfun->decl)); +} - if (! decl) - return 0; +static int +ia16_in_default_ds_abi_function_p (void) +{ + return cfun && cfun->decl + && ia16_default_ds_abi_function_type_p (TREE_TYPE (cfun->decl)); +} - type = TREE_TYPE (decl); - while (POINTER_TYPE_P (type)) - type = TREE_TYPE (type); +static int +ia16_ds_data_function_rtx_p (rtx addr) +{ + tree type = ia16_get_function_type_for_addr (addr); + return type && ia16_ds_data_function_type_p (type); +} - return ia16_far_function_type_p (type); +static int +ia16_default_ds_abi_function_rtx_p (rtx addr) +{ + tree type = ia16_get_function_type_for_addr (addr); + return type && ia16_default_ds_abi_function_type_p (type); } /* Basic Stack Layout */ @@ -453,6 +525,7 @@ ia16_function_ok_for_sibcall (tree decl, tree exp ATTRIBUTE_UNUSED) #define IA16_CALLCVT_CDECL 0x01 #define IA16_CALLCVT_STDCALL 0x02 #define IA16_CALLCVT_FAR 0x04 +#define IA16_CALLCVT_DS_DATA 0x08 static unsigned ia16_get_callcvt (const_tree type) @@ -468,6 +541,9 @@ ia16_get_callcvt (const_tree type) callcvt = IA16_CALLCVT_STDCALL; } + if (ia16_ds_data_function_type_p (type)) + callcvt |= IA16_CALLCVT_DS_DATA; + if (ia16_far_function_type_p (type)) callcvt |= IA16_CALLCVT_FAR; @@ -484,13 +560,12 @@ ia16_return_pops_args (tree fundecl ATTRIBUTE_UNUSED, tree funtype, int size) if (stdarg_p (funtype)) return 0; - switch (ia16_get_callcvt (funtype)) + switch (ia16_get_callcvt (funtype) + & (IA16_CALLCVT_STDCALL | IA16_CALLCVT_CDECL)) { case IA16_CALLCVT_STDCALL: - case IA16_CALLCVT_STDCALL | IA16_CALLCVT_FAR: return size; case IA16_CALLCVT_CDECL: - case IA16_CALLCVT_CDECL | IA16_CALLCVT_FAR: return 0; default: gcc_unreachable (); @@ -520,18 +595,47 @@ ia16_handle_cconv_attribute (tree *node, tree name, tree args ATTRIBUTE_UNUSED, return NULL_TREE; } - if (is_attribute_p ("stdcall", name) - && lookup_attribute ("cdecl", TYPE_ATTRIBUTES (*node))) + if (is_attribute_p ("stdcall", name)) { - error ("stdcall and cdecl attributes are not compatible"); - *no_add_attrs = true; + if (lookup_attribute ("cdecl", TYPE_ATTRIBUTES (*node))) + { + error ("stdcall and cdecl attributes are not compatible"); + *no_add_attrs = true; + } + } + else if (is_attribute_p ("cdecl", name)) + { + if (lookup_attribute ("stdcall", TYPE_ATTRIBUTES (*node))) + { + error ("stdcall and cdecl attributes are not compatible"); + *no_add_attrs = true; + } } - else if (is_attribute_p ("cdecl", name) - && lookup_attribute ("stdcall", TYPE_ATTRIBUTES (*node))) + /* The following attributes are ignored if %ds is a call-saved register. */ + else if (! call_used_regs[DS_REG]) { - error ("stdcall and cdecl attributes are not compatible"); + warning (OPT_Wattributes, "%qE attribute directive ignored as %%ds is " + "a call-saved register", name); *no_add_attrs = true; } + else if (is_attribute_p ("assume_ds_data", name)) + { + if (lookup_attribute ("no_assume_ds_data", TYPE_ATTRIBUTES (*node))) + { + error ("assume_ds_data and no_assume_ds_data attributes are not " + "compatible"); + *no_add_attrs = true; + } + } + else if (is_attribute_p ("no_assume_ds_data", name)) + { + if (lookup_attribute ("assume_ds_data", TYPE_ATTRIBUTES (*node))) + { + error ("assume_ds_data and no_assume_ds_data attributes are not " + "compatible"); + *no_add_attrs = true; + } + } return NULL_TREE; } @@ -540,6 +644,10 @@ static const struct attribute_spec ia16_attribute_table[] = { { "stdcall", 0, 0, false, true, true, ia16_handle_cconv_attribute, true }, { "cdecl", 0, 0, false, true, true, ia16_handle_cconv_attribute, true }, + { "assume_ds_data", + 0, 0, false, true, true, ia16_handle_cconv_attribute, true }, + { "no_assume_ds_data", + 0, 0, false, true, true, ia16_handle_cconv_attribute, true }, { NULL, 0, 0, false, false, false, NULL, false } }; @@ -2889,10 +2997,11 @@ ia16_print_operand_address (FILE *file, rtx e) On the RTL side: * A null segment override (RS) means we are accessing an operand in - the generic address space. We can assume that %ss points to this - space. %ds _might_ point to this space --- if the function we are - compiling does not use %ds for anything (liveness information for - %ds is available to us at this stage). + the generic address space. We currently assume that %ss points to + this space. %ds _might_ point to this space --- if the function + assumes that %ds == .data on startup, and if we are compiling does + not use %ds for anything (liveness information for %ds is available + to us at this stage). * If RS is not null, then we are accessing an operand at an offset from the specified segment. @@ -2905,12 +3014,8 @@ ia16_print_operand_address (FILE *file, rtx e) if (ia16_to_print_seg_override_p (REGNO (rs), rb)) fprintf (file, "%s%s:", REGISTER_PREFIX, reg_HInames[REGNO (rs)]); } - else if (! TARGET_ALLOCABLE_DS_REG && ! TARGET_ASSUME_DS_DATA) - { - if (ia16_to_print_seg_override_p (SS_REG, rb)) - fprintf (file, "%s%s:", REGISTER_PREFIX, reg_HInames[SS_REG]); - } - else if (TARGET_DEFAULT_DS_ABI && df_regs_ever_live_p (DS_REG)) + else if (TARGET_ALLOCABLE_DS_REG ? df_regs_ever_live_p (DS_REG) + : ! ia16_in_ds_data_function_p ()) { if (ia16_to_print_seg_override_p (SS_REG, rb)) fprintf (file, "%s%s:", REGISTER_PREFIX, reg_HInames[SS_REG]); @@ -3140,7 +3245,7 @@ ia16_shift_truncation_mask (enum machine_mode mode) /* Return true iff INSN is a (set (reg:SEG DS_REG) (reg:SEG SS_REG)). */ static bool -ia16_insn_is_set_ds_ss_p (rtx_insn *insn) +ia16_insn_is_reset_ds_p (rtx_insn *insn) { rtx pat, op; @@ -3196,29 +3301,57 @@ ia16_elide_unneeded_ss_stuff (void) int sp = 0; rtx ds = gen_rtx_REG (SEGmode, DS_REG), override = ia16_seg_override_term (ds); + bool default_ds_abi_p = ia16_in_ds_data_function_p (); bool keep_any_resets_p = false; + edge e; + edge_iterator ei; - /* Starting from insns which set (or clobber) %ds to not-%ss, "flood" + /* Starting from insns which set (or clobber) %ds to not-.data, "flood" insns along the control flow until we hit (set (reg:SEG DS_REG) (reg:SEG SS_REG)). + For now we assume %ss == .data always. + + If the current function does not assume %ds == .data on entry, also + flood from the function entry point. - Mark all the insns along the path --- excluding the first set to - non-%ss, but including the final %ds <- %ss --- as insns we want to + Mark all the insns along each path --- excluding the first set to + non-.data, but including the final %ds <- .data --- as insns we want to "keep" as is. */ + if (! default_ds_abi_p) + { + FOR_EACH_EDGE (e, ei, ENTRY_BLOCK_PTR_FOR_FN (cfun)->succs) + { + insn = BB_HEAD (e->dest); + if (! insn_pushed_p[INSN_UID (insn)]) + { + stack[sp++] = insn; + insn_pushed_p[INSN_UID (insn)] = true; + } + } + } + for (insn = get_insns(); insn; insn = NEXT_INSN (insn)) { - /* GCC's machine-independent code thinks function calls clobber %ds, - but we know that this is not really true. */ + /* GCC's machine-independent code thinks function calls always clobber + %ds, but we know that this is not really true. */ if (CALL_P (insn)) - continue; + { + rtx call = get_call_rtx_from (insn); + rtx callee = XEXP (XEXP (call, 0), 0); + if (ia16_ds_data_function_rtx_p (callee)) + continue; + } if (NOTE_P (insn) || ! reg_set_p (ds, insn) - || ia16_insn_is_set_ds_ss_p (insn)) + || ia16_insn_is_reset_ds_p (insn)) continue; - stack[sp++] = insn; - insn_pushed_p[INSN_UID (insn)] = true; + if (! insn_pushed_p[INSN_UID (insn)]) + { + stack[sp++] = insn; + insn_pushed_p[INSN_UID (insn)] = true; + } } while (sp != 0) @@ -3229,7 +3362,7 @@ ia16_elide_unneeded_ss_stuff (void) gcc_assert (sp > 0 && sp <= max_insn_uid); insn = stack[--sp]; - if (ia16_insn_is_set_ds_ss_p (insn)) + if (ia16_insn_is_reset_ds_p (insn)) { keep_any_resets_p = true; continue; @@ -3248,9 +3381,6 @@ ia16_elide_unneeded_ss_stuff (void) } else { - edge e; - edge_iterator ei; - FOR_EACH_EDGE (e, ei, bb->succs) { if (e->dest == EXIT_BLOCK_PTR_FOR_FN (cfun)) @@ -3278,7 +3408,7 @@ ia16_elide_unneeded_ss_stuff (void) if (keep_insn_p[INSN_UID (insn)]) continue; - if (ia16_insn_is_set_ds_ss_p (insn)) + if (ia16_insn_is_reset_ds_p (insn)) { /* Rewrite (set (reg:SEG DS_REG) (reg:SEG SS_REG)) @@ -3347,9 +3477,9 @@ ia16_elide_unneeded_ss_stuff (void) } } - /* If at no point in the function do we need to reset %ds to %ss, then + /* If at no point in the function do we need to reset %ds to .data, then deduce that %ds is no longer live within the function. */ - if (! keep_any_resets_p) + if (default_ds_abi_p && ! keep_any_resets_p) df_set_regs_ever_live (DS_REG, false); free (insn_pushed_p); @@ -3366,7 +3496,7 @@ ia16_machine_dependent_reorg (void) if (! optimize) return; - if (TARGET_DEFAULT_DS_ABI) + if (TARGET_ALLOCABLE_DS_REG && call_used_regs[DS_REG]) { compute_bb_for_insn (); ia16_elide_unneeded_ss_stuff (); @@ -3634,7 +3764,7 @@ ia16_non_overlapping_mem_p (rtx m1, rtx m2) return (false); } -/* Emit instructions to reset %ds to %ss. REGNO is either a scratch +/* Emit instructions to reset %ds to .data. REGNO is either a scratch register number, or a large number. */ static void ia16_expand_reset_ds_internal (void) @@ -3647,21 +3777,28 @@ ia16_expand_reset_ds_internal (void) } void -ia16_expand_reset_ds_for_call (void) +ia16_expand_reset_ds_for_call (rtx addr) { - if (! TARGET_DEFAULT_DS_ABI) - return; - - ia16_expand_reset_ds_internal (); + if (ia16_default_ds_abi_function_rtx_p (addr)) + ia16_expand_reset_ds_internal (); + else if (! TARGET_ALLOCABLE_DS_REG + && ! ia16_in_ds_data_function_p () + && ia16_ds_data_function_rtx_p (addr)) + { + /* An __attribute__ ((no_assume_ds_data)) function wants to call an + __attribute__ ((assume_ds_data)) function. This is probably not a + good thing to do. */ + warning (OPT_Wmaybe_uninitialized, + "%%ds is fixed, not resetting %%ds for call to assume_ds_ss " + "function"); + } } static void ia16_expand_reset_ds_for_epilogue (void) { - if (! TARGET_DEFAULT_DS_ABI) - return; - - ia16_expand_reset_ds_internal (); + if (ia16_in_default_ds_abi_function_p ()) + ia16_expand_reset_ds_internal (); } void diff --git a/gcc/config/ia16/ia16.h b/gcc/config/ia16/ia16.h index b59f864e44f64..a3b7be9148852 100644 --- a/gcc/config/ia16/ia16.h +++ b/gcc/config/ia16/ia16.h @@ -32,9 +32,6 @@ #define TARGET_TUNE_8BIT (ia16_features & 16) #define TARGET_ALLOCABLE_DS_REG (! fixed_regs[DS_REG]) -#define TARGET_DEFAULT_DS_ABI (TARGET_ALLOCABLE_DS_REG \ - && call_used_regs[DS_REG] \ - && TARGET_ASSUME_DS_DATA) /* Run-time Target Specification */ #define TARGET_CPU_CPP_BUILTINS() ia16_cpu_cpp_builtins () @@ -390,11 +387,11 @@ enum reg_class { /* 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 */ #define CUMULATIVE_ARGS int #define INIT_CUMULATIVE_ARGS(CUM, FNTYPE, LIBNAME, INDIRECT, N_NAMED_ARGS) \ ((CUM) = 0) -/* If we are using the default ABI, pretend that %ds "might" be used to pass - arguments to functions, so that when GCC sees a %ds <- %ss just before a - subroutine call, it does not try to separate the two. */ +/* If %ds is an allocatable register, pretend that it "might" be used to + pass arguments to functions, so that when GCC sees a %ds <- %ss just + before a subroutine call, it does not try to separate the two. */ #define FUNCTION_ARG_REGNO_P(regno) \ - (TARGET_DEFAULT_DS_ABI ? (regno) == DS_REG : false) + (TARGET_ALLOCABLE_DS_REG ? (regno) == DS_REG : false) /* How Scalar Function Values Are Returned */ #define LIBCALL_VALUE(mode) \ diff --git a/gcc/config/ia16/ia16.md b/gcc/config/ia16/ia16.md index 4226e3a266c20..b2ec4068fc07a 100755 --- a/gcc/config/ia16/ia16.md +++ b/gcc/config/ia16/ia16.md @@ -2705,7 +2705,7 @@ (match_operand:HI 1 "general_operand"))] "" { - ia16_expand_reset_ds_for_call (); + ia16_expand_reset_ds_for_call (XEXP (operands[0], 0)); }) (define_expand "call_pop" @@ -2716,7 +2716,7 @@ )] "" { - ia16_expand_reset_ds_for_call (); + ia16_expand_reset_ds_for_call (XEXP (operands[0], 0)); }) (define_expand "sibcall" @@ -2724,7 +2724,7 @@ (match_operand:HI 1 "general_operand"))] "" { - ia16_expand_reset_ds_for_call (); + ia16_expand_reset_ds_for_call (XEXP (operands[0], 0)); }) (define_insn "*call" @@ -2773,7 +2773,7 @@ (match_operand:HI 2 "general_operand")))] "" { - ia16_expand_reset_ds_for_call (); + ia16_expand_reset_ds_for_call (XEXP (operands[1], 0)); }) (define_expand "call_value_pop" @@ -2786,7 +2786,7 @@ )] "" { - ia16_expand_reset_ds_for_call (); + ia16_expand_reset_ds_for_call (XEXP (operands[1], 0)); }) (define_expand "sibcall_value" @@ -2795,7 +2795,7 @@ (match_operand:HI 2 "general_operand")))] "" { - ia16_expand_reset_ds_for_call (); + ia16_expand_reset_ds_for_call (XEXP (operands[1], 0)); }) (define_insn "*call_value" diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi index df8c666bccc44..a8f477b04a3d5 100644 --- a/gcc/doc/extend.texi +++ b/gcc/doc/extend.texi @@ -1509,6 +1509,9 @@ int bar (void) __far @c 3) The classical `int (__far *) (char);' declaration syntax does not work @c (see below). -- tkchia +@noindent +@b{Preprocessor symbols} + The preprocessor symbol @code{__FAR} is defined when far pointers are supported. In addition, @code{__IA16_FEATURE_FAR_FUNCTION_SYNTAX} is defined if the above syntax for declaring far functions is supported, and @@ -1546,9 +1549,10 @@ static int __far x, y, z; @end smallexample there is @emph{no} guarantee that the variables @code{x}, @code{y}, and -@code{z} will be placed in the same far segment. GCC only guarantees that -each of @code{x}, @code{y}, and @code{z} will be placed outside the generic -(near) data address space. +@code{z} will be placed in the same far segment. GCC only +guarantees---given an appropriate linker script at link time---that each of +@code{x}, @code{y}, and @code{z} will be placed outside the generic (near) +data address space. To place a set of variables in a single far segment, you can either wrap them in a struct, or use the @code{section} attribute (@pxref{Variable @@ -4158,6 +4162,23 @@ useful to override the effects of the @option{-mrtd} switch. On the IA-16 target, the @code{stdcall} attribute causes the compiler to assume that the called function pops off the stack space used to pass arguments, unless it takes a variable number of arguments. + +@item assume_ds_data +@cindex @code{assume_ds_data} function attribute, IA-16 + +This attribute, attached to a function, causes the compiler to assume that +the @code{ds} register points to the program's data segment on function +entry. This is useful to override the effects of the +@option{-mno-callee-assume-ds-data-segment} switch. + +This attribute has no effect if @code{ds} is a call-saved register +(@option{-fcall-saved-ds}). + +@item no_assume_ds_data +@cindex @code{no_assume_ds_data} function attribute, IA-16 +This attribute, attached to a function, causes the compiler @emph{not} to +assume that @code{ds} points to the program's data segment on function +entry. It has no effect if @code{ds} is a call-saved register. @end table @node IA-64 Function Attributes diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index aa4ed45e18ab3..02ff58e8d1261 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -16596,13 +16596,13 @@ harmlessly ignored.) Use (or do not use) a different function-calling convention, in which the @code{ds} register is not assumed to point to the program's data segment (the generic address space) at function entry, and is treated as just an -ordinary callee-used register (with @option{-fcall-used-ds}) or fixed +ordinary call-used register (with @option{-fcall-used-ds}) or fixed register (@option{-ffixed-ds}) with no special meaning. The @code{ss} register is still assumed to point to the data segment---data items in the generic address space will be accessed via @code{ss}. -This option is ignored if @code{ds} is set to be a callee-saved register +This option is ignored if @code{ds} is set to be a call-saved register (@option{-fcall-saved-ds}). In the default configuration (@option{-mcallee-assume-ds-data-segment @@ -16610,6 +16610,10 @@ In the default configuration (@option{-mcallee-assume-ds-data-segment program's data segment at function entry, and will arrange to reset @code{ds} to @code{ss}---if needed---just before returning. +You can override this option for individual functions, by using the +@code{assume_ds_data} and @code{no_assume_ds_data} function attributes. +@xref{Function Attributes}. + @item -mfar-function-if-far-return-type @opindex mfar-function-if-far-return-type Enable a highly experimental feature, where a function with a @code{__far} @@ -16698,8 +16702,8 @@ This macro is defined only if @option{-mprotected-mode} is in effect. @item __IA16_FEATURE_ALLOCABLE_DS_REG This macro is defined only if the @code{ds} register is configured to be -available for register allocation---that is, it is either callee-used or -callee-saved, not fixed. +available for register allocation---that is, it is either call-used or +call-saved, not fixed. @item __IA16_FEATURE_FAR_FUNCTION_SYNTAX This macro is defined only if the postfix @code{__far} syntax for declaring diff --git a/gcc/testsuite/gcc.target/ia16/torture/abi-ds-1.c b/gcc/testsuite/gcc.target/ia16/torture/abi-ds-1.c new file mode 100644 index 0000000000000..fa48e53122305 --- /dev/null +++ b/gcc/testsuite/gcc.target/ia16/torture/abi-ds-1.c @@ -0,0 +1,80 @@ +/* { dg-options "-std=gnu11 -fno-inline --save-temps" } */ +/* { dg-do run } */ + +/* Test that the `assume_ds_data' and `no_assume_ds_data' attributes work + properly under `-mcallee-assume-ds-data-segment'. */ + +int puts (const char *); +void abort (void); + +volatile int qux = 1; + +__attribute__ ((assume_ds_data)) void +foo (unsigned p) +{ + qux = 10; + __asm volatile ("# 1" : : "Rds" (__builtin_ia16_selector (p))); + qux = 11; +} + +__attribute__ ((no_assume_ds_data)) void +bar (unsigned p) +{ + qux = 6; + + foo (p); + + if (qux != 11) + abort (); + qux = 7; + + __asm volatile ("# 2" : : "Rds" (__builtin_ia16_selector (p))); + + if (qux != 7) + abort (); + qux = 8; + + foo (p); + + if (qux != 11) + abort (); + qux = 9; +} + +void +baz (unsigned p) +{ + if (qux != 1) + abort (); + qux = 2; + + bar (p); + + if (qux != 9) + abort (); + qux = 3; + + __asm volatile ("# 3" : : "Rds" (__builtin_ia16_selector (p))); + + if (qux != 3) + abort (); + qux = 4; + + bar (p); + + if (qux != 9) + abort (); + qux = 5; +} + +int +main (void) +{ + baz (0xc000); + + if (qux != 5) + abort (); + + puts ("Hello world!"); + return 0; +} diff --git a/gcc/testsuite/gcc.target/ia16/torture/abi-ds-2.c b/gcc/testsuite/gcc.target/ia16/torture/abi-ds-2.c new file mode 100644 index 0000000000000..b7e0392bb4642 --- /dev/null +++ b/gcc/testsuite/gcc.target/ia16/torture/abi-ds-2.c @@ -0,0 +1,80 @@ +/* { dg-options "-std=gnu11 -mno-callee-assume-ds-data-segment -fno-inline --save-temps" } */ +/* { dg-do run } */ + +/* Test that the `assume_ds_data' and `no_assume_ds_data' attributes work + properly under `-mno-callee-assume-ds-data-segment'. */ + +int puts (const char *) __attribute__ ((assume_ds_data)); +void abort (void) __attribute__ ((assume_ds_data)); + +volatile int qux = 1; + +__attribute__ ((assume_ds_data)) void +foo (unsigned p) +{ + qux = 10; + __asm volatile ("# 1" : : "Rds" (__builtin_ia16_selector (p))); + qux = 11; +} + +__attribute__ ((no_assume_ds_data)) void +bar (unsigned p) +{ + qux = 6; + + foo (p); + + if (qux != 11) + abort (); + qux = 7; + + __asm volatile ("# 2" : : "Rds" (__builtin_ia16_selector (p))); + + if (qux != 7) + abort (); + qux = 8; + + foo (p); + + if (qux != 11) + abort (); + qux = 9; +} + +void +baz (unsigned p) +{ + if (qux != 1) + abort (); + qux = 2; + + bar (p); + + if (qux != 9) + abort (); + qux = 3; + + __asm volatile ("# 3" : : "Rds" (__builtin_ia16_selector (p))); + + if (qux != 3) + abort (); + qux = 4; + + bar (p); + + if (qux != 9) + abort (); + qux = 5; +} + +__attribute__ ((assume_ds_data)) int +main (void) +{ + baz (0xc000); + + if (qux != 5) + abort (); + + puts ("Hello world!"); + return 0; +} diff --git a/gcc/testsuite/gcc.target/ia16/torture/abi-ds-3.c b/gcc/testsuite/gcc.target/ia16/torture/abi-ds-3.c new file mode 100644 index 0000000000000..82bdc23958938 --- /dev/null +++ b/gcc/testsuite/gcc.target/ia16/torture/abi-ds-3.c @@ -0,0 +1,76 @@ +/* { dg-skip-if "" { *-*-* } { "-flto" } { "" } } */ +/* { dg-do assemble } */ + +/* Test that a warning is issued when an `no_assume_ds_data' function calls + an `assume_ds_data' function and %ds is a fixed register. + + FIXME: no warnings are issued if LTO is in effect. */ + +int puts (const char *); +void abort (void); + +volatile int qux = 1; + +__attribute__ ((assume_ds_data)) void +foo (unsigned p) +{ + qux = 10; + __asm volatile ("# 1" : : "e" (__builtin_ia16_selector (p))); + qux = 11; +} + +__attribute__ ((no_assume_ds_data)) void +bar (unsigned p) +{ + qux = 6; + + foo (p); /* { dg-warning "%ds is fixed" "" { xfail lto } } */ + + if (qux != 11) + abort (); /* { dg-warning "%ds is fixed" "" { xfail lto } } */ + qux = 7; + + __asm volatile ("# 2" : : "e" (__builtin_ia16_selector (p))); + qux = 8; + + foo (p); /* { dg-warning "%ds is fixed" "" { xfail lto } } */ + qux = 9; +} + +void +baz (unsigned p) +{ + if (qux != 1) + abort (); + qux = 2; + + bar (p); + + if (qux != 9) + abort (); + qux = 3; + + __asm volatile ("# 3" : : "e" (__builtin_ia16_selector (p))); + + if (qux != 3) + abort (); + qux = 4; + + bar (p); + + if (qux != 9) + abort (); + qux = 5; +} + +int +main (void) +{ + baz (0xc000); + + if (qux != 5) + abort (); + + puts ("Hello world!"); + return 0; +} diff --git a/gcc/testsuite/gcc.target/ia16/torture/abi-ds-4.c b/gcc/testsuite/gcc.target/ia16/torture/abi-ds-4.c new file mode 100644 index 0000000000000..384d19c54fa83 --- /dev/null +++ b/gcc/testsuite/gcc.target/ia16/torture/abi-ds-4.c @@ -0,0 +1,68 @@ +/* { dg-options "-std=gnu11 -fcall-saved-ds -fno-inline -Wall --save-temps" } */ +/* { dg-do assemble } */ + +/* Test that the `assume_ds_data' and `no_assume_ds_data' attributes trigger + warnings when %ds is a call-saved register. */ + +int puts (const char *); +void abort (void); + +volatile int qux = 1; + +__attribute__ ((assume_ds_data)) void +foo (unsigned p) +{ /* { dg-warning "%ds is a call-saved register" } */ + qux = 10; + __asm volatile ("# 1" : : "Rds" (__builtin_ia16_selector (p))); + qux = 11; +} + +__attribute__ ((no_assume_ds_data)) void +bar (unsigned p) +{ /* { dg-warning "%ds is a call-saved register" } */ + qux = 6; + + foo (p); + + if (qux != 11) + abort (); + qux = 7; + + __asm volatile ("# 2" : : "Rds" (__builtin_ia16_selector (p))); + + if (qux != 7) + abort (); + qux = 8; + + foo (p); + + if (qux != 11) + abort (); + qux = 9; +} + +void +baz (unsigned p) +{ + if (qux != 1) + abort (); + qux = 2; + + bar (p); + + if (qux != 9) + abort (); + qux = 3; + + __asm volatile ("# 3" : : "Rds" (__builtin_ia16_selector (p))); + + if (qux != 3) + abort (); + qux = 4; + + bar (p); + + if (qux != 9) + abort (); + qux = 5; +} diff --git a/gcc/testsuite/gcc.target/ia16/torture/far-addr-29.c b/gcc/testsuite/gcc.target/ia16/torture/far-addr-29.c index d4f727dfd35a0..a2bb3d8352111 100644 --- a/gcc/testsuite/gcc.target/ia16/torture/far-addr-29.c +++ b/gcc/testsuite/gcc.target/ia16/torture/far-addr-29.c @@ -1,4 +1,4 @@ -/* { dg-options "-mcmodel=small -std=gnu11 --save-temps" } */ +/* { dg-options "-std=gnu11 --save-temps" } */ /* { dg-do assemble } */ /* Test that we can pass an absolute-address far pointer as a parameter to a diff --git a/gcc/testsuite/gcc.target/ia16/torture/ia16-torture.exp b/gcc/testsuite/gcc.target/ia16/torture/ia16-torture.exp index 64e1cf32c3d0d..bd1a011aa0d70 100644 --- a/gcc/testsuite/gcc.target/ia16/torture/ia16-torture.exp +++ b/gcc/testsuite/gcc.target/ia16/torture/ia16-torture.exp @@ -38,6 +38,9 @@ dg-init { -O0 } \ { -O0 -mprotected-mode } \ { -O0 -mrtd } \ + { -O0 -mcmodel=small } \ + { -O0 -mcmodel=small -mprotected-mode } \ + { -O0 -mcmodel=small -mrtd } \ { -O1 } \ { -O1 -funroll-loops } \ { -O1 -frename-registers } \ @@ -48,6 +51,17 @@ dg-init { -O1 -mprotected-mode -frename-registers } \ { -O1 -mprotected-mode -funroll-loops -fno-rename-registers } \ { -O1 -mprotected-mode -mrtd } \ + { -O1 -mcmodel=small } \ + { -O1 -mcmodel=small -funroll-loops } \ + { -O1 -mcmodel=small -frename-registers } \ + { -O1 -mcmodel=small -funroll-loops -fno-rename-registers } \ + { -O1 -mcmodel=small -mrtd } \ + { -O1 -mcmodel=small -mprotected-mode } \ + { -O1 -mcmodel=small -mprotected-mode -funroll-loops } \ + { -O1 -mcmodel=small -mprotected-mode -frename-registers } \ + { -O1 -mcmodel=small -mprotected-mode -funroll-loops \ + -fno-rename-registers } \ + { -O1 -mcmodel=small -mprotected-mode -mrtd } \ { -O2 } \ { -O2 -funroll-loops } \ { -O2 -frename-registers } \ @@ -58,6 +72,17 @@ dg-init { -O2 -mprotected-mode -frename-registers } \ { -O2 -mprotected-mode -funroll-loops -fno-rename-registers } \ { -O2 -mprotected-mode -mrtd } \ + { -O2 -mcmodel=small } \ + { -O2 -mcmodel=small -funroll-loops } \ + { -O2 -mcmodel=small -frename-registers } \ + { -O2 -mcmodel=small -funroll-loops -fno-rename-registers } \ + { -O2 -mcmodel=small -mrtd } \ + { -O2 -mcmodel=small -mprotected-mode } \ + { -O2 -mcmodel=small -mprotected-mode -funroll-loops } \ + { -O2 -mcmodel=small -mprotected-mode -frename-registers } \ + { -O2 -mcmodel=small -mprotected-mode -funroll-loops \ + -fno-rename-registers } \ + { -O2 -mcmodel=small -mprotected-mode -mrtd } \ { -Os } \ { -Os -funroll-loops } \ { -Os -frename-registers } \ @@ -72,6 +97,21 @@ dg-init { -Os -mprotected-mode -mrtd } \ { -Os -mprotected-mode -flto } \ { -Os -mprotected-mode -finline-functions } \ + { -Os -mcmodel=small } \ + { -Os -mcmodel=small -funroll-loops } \ + { -Os -mcmodel=small -frename-registers } \ + { -Os -mcmodel=small -funroll-loops -fno-rename-registers } \ + { -Os -mcmodel=small -mrtd } \ + { -Os -mcmodel=small -flto } \ + { -Os -mcmodel=small -finline-functions } \ + { -Os -mcmodel=small -mprotected-mode } \ + { -Os -mcmodel=small -mprotected-mode -funroll-loops } \ + { -Os -mcmodel=small -mprotected-mode -frename-registers } \ + { -Os -mcmodel=small -mprotected-mode -funroll-loops \ + -fno-rename-registers } \ + { -Os -mcmodel=small -mprotected-mode -mrtd } \ + { -Os -mcmodel=small -mprotected-mode -flto } \ + { -Os -mcmodel=small -mprotected-mode -finline-functions } \ { -O3 } \ { -O3 -funroll-loops } \ { -O3 -frename-registers } \ @@ -81,7 +121,18 @@ dg-init { -O3 -mprotected-mode -funroll-loops } \ { -O3 -mprotected-mode -frename-registers } \ { -O3 -mprotected-mode -funroll-loops -fno-rename-registers } \ - { -O3 -mprotected-mode -mrtd } ] + { -O3 -mprotected-mode -mrtd } \ + { -O3 -mcmodel=small } \ + { -O3 -mcmodel=small -funroll-loops } \ + { -O3 -mcmodel=small -frename-registers } \ + { -O3 -mcmodel=small -funroll-loops -fno-rename-registers } \ + { -O3 -mcmodel=small -mrtd } \ + { -O3 -mcmodel=small -mprotected-mode } \ + { -O3 -mcmodel=small -mprotected-mode -funroll-loops } \ + { -O3 -mcmodel=small -mprotected-mode -frename-registers } \ + { -O3 -mcmodel=small -mprotected-mode -funroll-loops \ + -fno-rename-registers } \ + { -O3 -mcmodel=small -mprotected-mode -mrtd } ] #Initialize use of torture lists. torture-init diff --git a/gcc/testsuite/gcc.target/ia16/torture/prot-mode-1.c b/gcc/testsuite/gcc.target/ia16/torture/prot-mode-1.c index f52413808b780..a527716c675d1 100644 --- a/gcc/testsuite/gcc.target/ia16/torture/prot-mode-1.c +++ b/gcc/testsuite/gcc.target/ia16/torture/prot-mode-1.c @@ -1,4 +1,4 @@ -/* { dg-options "-mcmodel=small -std=gnu11 --save-temps" } */ +/* { dg-options "-std=gnu11 --save-temps" } */ /* { dg-do assemble } */ /* Test that the __builtin_ia16_selector (.) function allows a value to be