A comprehensive program for testing x86_64 ms_abi functions that call sysv_abi functions to help validate -mcall-ms2sysv-xlogues and use of aligned SSE MOVs after a (non-DRAP) realigned stack.
Signed-off-by: Daniel Santos <daniel.san...@pobox.com> --- gcc/Makefile.in | 2 + .../gcc.target/x86_64/abi/ms-sysv/do-test.S | 163 +++++ gcc/testsuite/gcc.target/x86_64/abi/ms-sysv/gen.cc | 807 +++++++++++++++++++++ .../gcc.target/x86_64/abi/ms-sysv/ms-sysv.c | 373 ++++++++++ .../gcc.target/x86_64/abi/ms-sysv/ms-sysv.exp | 178 +++++ 5 files changed, 1523 insertions(+) create mode 100644 gcc/testsuite/gcc.target/x86_64/abi/ms-sysv/do-test.S create mode 100644 gcc/testsuite/gcc.target/x86_64/abi/ms-sysv/gen.cc create mode 100644 gcc/testsuite/gcc.target/x86_64/abi/ms-sysv/ms-sysv.c create mode 100644 gcc/testsuite/gcc.target/x86_64/abi/ms-sysv/ms-sysv.exp diff --git a/gcc/Makefile.in b/gcc/Makefile.in index f675e073ecc..7f7c238127b 100644 --- a/gcc/Makefile.in +++ b/gcc/Makefile.in @@ -3807,7 +3807,9 @@ site.exp: ./config.status Makefile @echo "set CFLAGS \"\"" >> ./site.tmp @echo "set CXXFLAGS \"\"" >> ./site.tmp @echo "set HOSTCC \"$(CC)\"" >> ./site.tmp + @echo "set HOSTCXX \"$(CXX)\"" >> ./site.tmp @echo "set HOSTCFLAGS \"$(CFLAGS)\"" >> ./site.tmp + @echo "set HOSTCXXFLAGS \"$(CXXFLAGS)\"" >> ./site.tmp # TEST_ALWAYS_FLAGS are flags that should be passed to every compilation. # They are passed first to allow individual tests to override them. @echo "set TEST_ALWAYS_FLAGS \"$(SYSROOT_CFLAGS_FOR_TARGET)\"" >> ./site.tmp diff --git a/gcc/testsuite/gcc.target/x86_64/abi/ms-sysv/do-test.S b/gcc/testsuite/gcc.target/x86_64/abi/ms-sysv/do-test.S new file mode 100644 index 00000000000..1395235fd1e --- /dev/null +++ b/gcc/testsuite/gcc.target/x86_64/abi/ms-sysv/do-test.S @@ -0,0 +1,163 @@ +/* Assembly proxy functions for ms_abi tests. + Copyright (C) 2016-2017 Free Software Foundation, Inc. + Contributed by Daniel Santos <daniel.san...@pobox.com> + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3, or (at your option) +any later version. + +GCC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +Under Section 7 of GPL version 3, you are granted additional +permissions described in the GCC Runtime Library Exception, version +3.1, as published by the Free Software Foundation. + +You should have received a copy of the GNU General Public License and +a copy of the GCC Runtime Library Exception along with this program; +see the files COPYING3 and COPYING.RUNTIME respectively. If not, see +<http://www.gnu.org/licenses/>. */ + +#ifdef __x86_64__ + +# ifdef __ELF__ +# define ELFFN_BEGIN(fn) .type fn,@function +# define ELFFN_END(fn) .size fn,.-fn +# else +# define ELFFN_BEGIN(fn) +# define ELFFN_END(fn) +# endif + +# define FUNC(fn) \ + .global fn; \ + ELFFN_BEGIN(fn); \ +fn: + +#define FUNC_END(fn) ELFFN_END(fn) + +# ifdef __AVX__ +# define MOVAPS vmovaps +# else +# define MOVAPS movaps +# endif + +/* TODO: Is there a cleaner way to provide these offsets? */ + .struct 0 +test_data_save: + + .struct test_data_save + 224 +test_data_input: + + .struct test_data_save + 448 +test_data_output: + + .struct test_data_save + 672 +test_data_fn: + + .struct test_data_save + 680 +test_data_retaddr: + + .text + +regs_to_mem: + MOVAPS %xmm6, (%rax) + MOVAPS %xmm7, 0x10(%rax) + MOVAPS %xmm8, 0x20(%rax) + MOVAPS %xmm9, 0x30(%rax) + MOVAPS %xmm10, 0x40(%rax) + MOVAPS %xmm11, 0x50(%rax) + MOVAPS %xmm12, 0x60(%rax) + MOVAPS %xmm13, 0x70(%rax) + MOVAPS %xmm14, 0x80(%rax) + MOVAPS %xmm15, 0x90(%rax) + mov %rsi, 0xa0(%rax) + mov %rdi, 0xa8(%rax) + mov %rbx, 0xb0(%rax) + mov %rbp, 0xb8(%rax) + mov %r12, 0xc0(%rax) + mov %r13, 0xc8(%rax) + mov %r14, 0xd0(%rax) + mov %r15, 0xd8(%rax) + retq + +mem_to_regs: + MOVAPS (%rax), %xmm6 + MOVAPS 0x10(%rax),%xmm7 + MOVAPS 0x20(%rax),%xmm8 + MOVAPS 0x30(%rax),%xmm9 + MOVAPS 0x40(%rax),%xmm10 + MOVAPS 0x50(%rax),%xmm11 + MOVAPS 0x60(%rax),%xmm12 + MOVAPS 0x70(%rax),%xmm13 + MOVAPS 0x80(%rax),%xmm14 + MOVAPS 0x90(%rax),%xmm15 + mov 0xa0(%rax),%rsi + mov 0xa8(%rax),%rdi + mov 0xb0(%rax),%rbx + mov 0xb8(%rax),%rbp + mov 0xc0(%rax),%r12 + mov 0xc8(%rax),%r13 + mov 0xd0(%rax),%r14 + mov 0xd8(%rax),%r15 + retq + +# NOTE: Not MT safe +FUNC(do_test_unaligned) + .cfi_startproc + # The below alignment checks are to verify correctness of the test + # its self. + + # Verify that incoming stack is aligned + 8 + pushf + test $0x8, %rsp + jne L0 + int $3 # Stack not unaligned + +FUNC(do_test_aligned) + # Verify that incoming stack is aligned + pushf + test $0xf, %rsp + je L0 + int $3 # Stack not aligned +L0: + popf + + # Save registers + lea test_data(%rip), %rax + call regs_to_mem + + # Load register with random data + lea test_data + test_data_input(%rip), %rax + call mem_to_regs + + # Save original return address + pop %rax + movq %rax, test_data + test_data_retaddr(%rip) + + # Call the test function + call *test_data + test_data_fn(%rip) + + # Restore the original return address + movq test_data + test_data_retaddr(%rip), %rcx + push %rcx + + # Save test function return value and store resulting register values + push %rax + lea test_data + test_data_output(%rip), %rax + call regs_to_mem + + # Restore registers + lea test_data(%rip), %rax + call mem_to_regs + pop %rax + retq + .cfi_endproc +FUNC_END(do_test_aligned) +FUNC_END(do_test_unaligned) + +#endif /* __x86_64__ */ diff --git a/gcc/testsuite/gcc.target/x86_64/abi/ms-sysv/gen.cc b/gcc/testsuite/gcc.target/x86_64/abi/ms-sysv/gen.cc new file mode 100644 index 00000000000..947a12bf2ae --- /dev/null +++ b/gcc/testsuite/gcc.target/x86_64/abi/ms-sysv/gen.cc @@ -0,0 +1,807 @@ +/* Test program generator for 64-bit Microsoft ABI. + Copyright (C) 2016-2017 Free Software Foundation, Inc. + Contributed by Daniel Santos <daniel.san...@pobox.com> + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3, or (at your option) +any later version. + +GCC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +Under Section 7 of GPL version 3, you are granted additional +permissions described in the GCC Runtime Library Exception, version +3.1, as published by the Free Software Foundation. + +You should have received a copy of the GNU General Public License and +a copy of the GCC Runtime Library Exception along with this program; +see the files COPYING3 and COPYING.RUNTIME respectively. If not, see +<http://www.gnu.org/licenses/>. */ + +#include <cstdio> +#include <cassert> +#include <vector> +#include <string> +#include <cstring> +#include <iostream> +#include <algorithm> +#include <ios> +#include <iomanip> +#include <sstream> +#include <fstream> +#include <memory> +#include <regex> +#include <stdexcept> + +#include <unistd.h> +#include <getopt.h> + +using namespace std; + +/* A basic Effective C++ Item 6. */ +class uncopyable +{ +private: + uncopyable (const uncopyable &) = delete; + const uncopyable& operator= (const uncopyable &) = delete; + +protected: + uncopyable() {} + ~uncopyable() {} +}; + +/* A simple class for adding text delimiters. */ +class list_delimiter : protected uncopyable +{ + int m_pos; + string m_delim; + static string s_empty; + + list_delimiter (); + +public: + list_delimiter (const char *delim, int init_pos = 0) + : m_pos (init_pos), m_delim(delim) {} + const string &get () {return m_pos++ ? m_delim : s_empty;} + void reset () {m_pos = 0;} + int get_pos () {return m_pos;} +}; + +string list_delimiter::s_empty = ""; + +/* Bitmasks for representing non-volatile retisters of an ms_abi call that + are not already clobbered by a sysv_abi call. */ +enum optional_regs +{ + OPTIONAL_REG_RBX = 0x01, + OPTIONAL_REG_RBP = 0x02, + OPTIONAL_REG_R12 = 0x04, + OPTIONAL_REG_R13 = 0x08, + OPTIONAL_REG_R14 = 0x10, + OPTIONAL_REG_R15 = 0x20, + + OPTIONAL_REG_ALL = 0x3f, + OPTIONAL_REG_HFP_ALL = OPTIONAL_REG_ALL & (~OPTIONAL_REG_RBP) +}; + +static const char * const optional_regs_str[] = { + "rbx", + "rbp", + "r12", + "r13", + "r14", + "r15", +}; + +/* A simple type & name representation of a function parameter. */ +class arg +{ + string name; + string type; + bool type_is_integral:1; + +public: + arg(const char *name, const char *type, bool type_is_integral); + + bool is_type_integral () const {return type_is_integral;} + const string &get_name () const {return name;} + const string &get_type () const {return type;} +}; + +arg::arg(const char *name, const char *type, bool type_is_integral) + : name (name), type (type), type_is_integral (type_is_integral) +{ +} + +/* A stupid operator<< implementation for arg objects. */ +template<class T> T &operator<< (T &out, const arg &a) +{ + return out << a.get_type () << " " << a.get_name (); +} + +/* Bitmask representation of all possible varients of a test function. The + value FN_VAR_MSABI is only used internally to distinguish between an + ms_abi and sysv_abi function. */ +enum fn_variants { + FN_VAR_MSABI = 0x01, + FN_VAR_HFP = 0x02, + FN_VAR_REALIGN = 0x04, + FN_VAR_ALLOCA = 0x08, + FN_VAR_VARARGS = 0x10, + FN_VAR_SIBCALL = 0x20, + FN_VAR_SHRINK_WRAP = 0x40, + + FN_VAR_HFP_OR_REALIGN = FN_VAR_HFP | FN_VAR_REALIGN, + FN_VAR_MASK = 0x7f, + FN_VAR_COUNT = 7 +}; + +/* Representation of a Microsoft or System V ABI function with varying + parameters, quirks and optimization goals. + + Function name nomenclature: + (msabi|sysv)_[xx_][r|f][a][v][s][w]<n> + | | | | | | | | + | | | | | | | Number of extra (long) parameters + | | | | | | shrink wrap + | | | | | sibling call + | | | | varargs + | | | alloca + | | Forced realignment or hard frame pointer + | Explicit clobbers (hexidecimal mask, ms_abi only) + Calling Convention */ +class fn : protected uncopyable +{ +private: + const vector<arg> &m_args; + string m_name; + string m_attr_decl_str; + string m_attr_def_str; + int m_clobbers:FN_VAR_COUNT; + int m_var; + +public: + fn (const vector<arg> &args, int clobbers, int var); + + void print_params (ostream &out) const; + void print_decl (ostream &out, bool for_def = false) const; + void print_noinfo_def (ostream &out) const; + void print_def (ostream &out) const; + const string &get_name () const {return m_name;} + const vector<arg> &get_args () const {return m_args;} + + bool get_hfp_or_realign () const {return m_var & FN_VAR_HFP_OR_REALIGN;} + bool get_msabi () const {return m_var & FN_VAR_MSABI;} + bool get_hfp () const {return m_var & FN_VAR_HFP;} + bool get_realign () const {return m_var & FN_VAR_REALIGN;} + bool get_alloca () const {return m_var & FN_VAR_ALLOCA;} + bool get_varargs () const {return m_var & FN_VAR_VARARGS;} + bool get_sibcall () const {return m_var & FN_VAR_SIBCALL;} + bool get_shrink_wrap () const {return m_var & FN_VAR_SHRINK_WRAP;} +}; + +fn::fn (const vector<arg> &args, int clobbers, int var) + : m_args (args) + , m_name () + , m_attr_decl_str () + , m_attr_def_str ("noinline") + , m_clobbers (clobbers) + , m_var (var) +{ + assert (!(var & ~FN_VAR_MASK)); + + if (get_hfp () && get_realign ()) + throw invalid_argument ("`hfp' with `realign' does nothing."); + + if (get_varargs () && args.empty ()) + throw invalid_argument ("Need at least one normal argument to use varargs"); + + assert (!(get_hfp () || get_realign ()) || !(clobbers & OPTIONAL_REG_RBP)); + + stringstream name; + name << (get_msabi () ? "msabi_" : "sysv_"); + if (get_msabi ()) + name << setfill('0') << setw(2) << hex << m_clobbers << "_"; + name << (get_realign () ? "r" : (get_hfp () ? "f" : "")) + << (get_alloca () ? "a" : "") + << (get_varargs () ? "v" : "") + << (get_sibcall () ? "s" : "") + << (get_shrink_wrap () ? "w" : "") + << setw(0) << dec << (unsigned)args.size(); + m_name = name.str(); + + list_delimiter decl_comma (", ", !m_attr_decl_str.empty ()); + list_delimiter def_comma (", ", !m_attr_def_str.empty ()); + if (get_msabi ()) + { + m_attr_decl_str += decl_comma.get (); + m_attr_decl_str += "ms_abi"; + m_attr_def_str += def_comma.get (); + m_attr_def_str += "ms_abi"; + } + + if (get_realign ()) + { + m_attr_def_str += def_comma.get(); + m_attr_def_str += "__force_align_arg_pointer__"; + } + else if (get_hfp ()) + { + m_attr_def_str += def_comma.get(); + m_attr_def_str += "optimize (\"no-omit-frame-pointer\")"; + } +} + +/* Print the parameters for a function declaration. */ +void fn::print_params (ostream &out) const +{ + list_delimiter comma (", "); + + vector<arg>::const_iterator i; + if (get_alloca () && !get_msabi ()) + out << comma.get () << "void *alloca_mem"; + for (i = m_args.begin(); i != m_args.end(); ++i) + out << comma.get () << *i; + + if (get_varargs ()) + out << comma.get () << (get_msabi () ? "..." : "va_list argptr"); +} + +/* Print the declaration for a function. */ +void fn::print_decl (ostream &out, bool for_def) const +{ + const string &attr_str = (for_def ? m_attr_def_str : m_attr_decl_str); + if (!for_def) + out << "extern "; + + if (!attr_str.empty ()) + out << "__attribute__ ((" << attr_str << ")) "; + + out << "long " << m_name << " ("; + print_params (out); + out << ")"; + if (!for_def) + out << ";" << endl; +} + +/* Output a volatile "_noinfo" function pointer definition. */ +void fn::print_noinfo_def (ostream &out) const +{ + out << "static "; + if (!m_attr_decl_str.empty ()) + out << "__attribute__ ((" << m_attr_decl_str << ")) "; + out << "long (*const volatile " << m_name << "_noinfo) ("; + print_params (out); + out << ") = " << m_name << ";" << endl; +} + +/* Print the definition of a function. */ +void fn::print_def (ostream &out) const +{ + vector<arg>::const_iterator i; + + print_decl (out, true); + out << endl << "{" << endl; + + if (get_msabi () && get_alloca ()) + { + const char *size_str = m_args.empty () ? "42" : "a"; + out << " void *alloca_mem = alloca (8 + " << size_str << ");" << endl + << " *(long*)alloca_mem = FLAG_ALLOCA;" << endl; + } + if (get_msabi () && get_varargs ()) + out << " va_list argptr;" << endl; + if (get_shrink_wrap ()) + out << " if (shrink_wrap_global == FLAG_SHRINK_WRAP_FAST_PATH)" << endl + << " return FLAG_SHRINK_WRAP_FAST_PATH;" << endl; + + list_delimiter comma (", "); + if (m_clobbers) + { + out << " __asm__ __volatile__ (\"\" :::"; + unsigned c; + unsigned mask = m_clobbers; + comma.reset (); + for (c = 0, mask = m_clobbers; mask; ++c, mask >>= 1) + if (mask & 1) + out << comma.get () << "\"" << optional_regs_str[c] << "\""; + out << ");" << endl; + } + + if (get_msabi () && get_varargs ()) + { + assert (!m_args.empty ()); + out << " va_start(argptr, " << m_args.back ().get_name () << ");" << endl; + } + + out << " return "; + if (get_msabi ()) + { + if (get_sibcall ()) + out << "do_sibcall_noinfo ("; + + comma.reset (); + out << "sysv_" + << (get_alloca () ? "a" : "") + << (get_varargs () ? "v" : "") + << m_args.size () + << "_noinfo ("; + + if (get_alloca ()) + out << comma.get () << "alloca_mem"; + for (i = m_args.begin(); i != m_args.end(); ++i) + out << comma.get () << i->get_name (); + if (get_varargs ()) + out << comma.get () << "argptr"; + out << ")"; + if (get_shrink_wrap ()) + out << " + FLAG_SHRINK_WRAP_SLOW_PATH"; + if (get_sibcall ()) + out << ")"; + } + else + { + list_delimiter plus (" + "); + for (i = m_args.begin(); i != m_args.end(); ++i) + if (i->is_type_integral ()) + out << plus.get () << i->get_name (); + if (get_alloca ()) + out << plus.get () << "*(long*)alloca_mem"; + if (!plus.get_pos ()) + out << "0"; + } + out << ";" << endl; + if (get_msabi () && get_varargs ()) + out << " va_end(argptr);" << endl; + out << "}" << endl << endl; +} + +/* Global variables. */ +string argv0; +string out_file_name; +unsigned int extra_params_min = 0; +unsigned int extra_params_max = 5; +unsigned fn_variant_mask = FN_VAR_MASK; +bool omit_rbp_clobbers = false; +vector<class fn*> sysv_funcs; +vector<class fn*> msabi_funcs; + + +/* Emit extern for do_test_aligned and do_test_unaligned (defined in do_test.S) + followed by all of the various do_test* function function pointers that + are just aliases of them. */ +static void make_do_tests_decl (const vector<class arg> &args, ostream &out) +{ + vector<class arg>::const_iterator ai; + unsigned i, varargs, unaligned; + + out << "extern __attribute__ ((ms_abi)) long do_test_aligned ();" << endl + << "extern __attribute__ ((ms_abi)) long do_test_unaligned ();" << endl; + + list_delimiter comma (", "); + for (i = extra_params_min; i <= args.size (); ++i) + for (unaligned = 0; unaligned <= 1; ++unaligned) + for (varargs = 0; varargs <= 1; ++varargs) + { + if (!i && varargs) /* skip varargs version when no other args */ + continue; + + comma.reset (); + out << "static __attribute__ ((ms_abi)) long (*const do_test_" + << (unaligned ? "u" : "") + << (varargs ? "v" : "") << i << ") ("; + + unsigned j; + for (j = 0, ai = args.begin (); j < i; ++j, ++ai) + out << comma.get () << ai->get_type () << " " + << ai->get_name (); + if (varargs) + out << comma.get () << "..."; + out << ") = (void*)do_test_" << (unaligned ? "un" : "") + << "aligned;" << endl; + } +} + +/* Generate do_tests function. We actually break it up into multiple + do_test_xxxx functions to keep compile times down (with just one large + function, it is a very slow build). */ +void make_do_test (const vector<class arg> &args, + const vector<class fn*> &msabi_funcs, + ostream &out) +{ + const unsigned TESTS_PER_FN_MAX = 64; + unsigned i; + vector<string> do_tests_fn_names; + unsigned fn_count = 0; + unsigned test_count = TESTS_PER_FN_MAX; + string params_str; + string param_names_str; + string param_types_str; + + /* Init some commonly used strings. */ + { + stringstream s1, s2, s3; + list_delimiter comma(", "); + for (auto arg : args) + { + const string &c = comma.get (); + s1 << c << arg; + s2 << c << arg.get_name (); + s3 << c << arg.get_type (); + } + params_str = s1.str (); + param_names_str = s2.str (); + param_types_str = s3.str (); + } + + vector<class fn*>::const_iterator fi; + for (fi = msabi_funcs.begin(); fi != msabi_funcs.end(); ++fi) + { + const fn &f = **fi; + unsigned unaligned, shrink_wrap; + + for (unaligned = 0; unaligned <= !!f.get_realign (); ++unaligned) + for (shrink_wrap = 0; shrink_wrap <= !!f.get_shrink_wrap (); + ++shrink_wrap) + { + const vector<class arg> &fargs = f.get_args (); + + /* To prevent unwieldy build times, we split up tests to 64-ish per + function. */ + if (++test_count > TESTS_PER_FN_MAX) + { + test_count = 1; + if (fn_count > 0) { + out << "}" << endl << endl; + } + + stringstream fn_name; + fn_name << "do_tests_" << setfill('0') << setw(4) << hex + << fn_count++; + do_tests_fn_names.push_back (fn_name.str ()); + + out << "static __attribute__((noinline)) void " + << fn_name.str () << " (" << params_str << ")" << endl + << "{" << endl + << " long ret;" << endl; + } + + /* Call init_test. */ + out << endl + << " init_test (" << f.get_name () << ", \"" + << f.get_name () << "\", "; + + if (f.get_realign ()) + out << (unaligned ? "ALIGNMENT_MISALIGNED" + : "ALIGNMENT_ALIGNED"); + else + out << "ALIGNMENT_NOT_TESTED"; + + out << ", "; + if (f.get_shrink_wrap ()) + out << (shrink_wrap ? "SHRINK_WRAP_SLOW_PATH" + : "SHRINK_WRAP_FAST_PATH"); + else + out << "SHRINK_WRAP_NONE"; + out << ", "; + + /* Calculated the expected return value. */ + if (f.get_shrink_wrap () && shrink_wrap == 0) + out << "FLAG_SHRINK_WRAP_FAST_PATH"; + else + { + list_delimiter plus (" + "); + for (auto const &arg : fargs) + out << plus.get () << arg.get_name (); + if (f.get_sibcall ()) + out << plus.get () << "FLAG_SIBCALL"; + if (f.get_alloca ()) + out << plus.get () << "FLAG_ALLOCA"; + if (f.get_shrink_wrap () && shrink_wrap == 1) + out << plus.get () << "FLAG_SHRINK_WRAP_SLOW_PATH"; + if (!plus.get_pos ()) + out << "0"; + } + out << ");" << endl; + /* End if init_test call. */ + + if (f.get_realign () && unaligned == 1) + out << " __asm__ __volatile__ (\"subq $8,%%rsp\":::\"cc\");" + << endl; + + out << " ret = do_test_" + << (f.get_realign () && unaligned == 1 ? "u" : "") + << (f.get_varargs () ? "v" : "") + << fargs.size () << " ("; + + list_delimiter comma (", "); + for (auto const &arg : fargs) + out << comma.get () << arg.get_name (); + out << ");" << endl; + + if (f.get_realign () && unaligned == 1) + out << " __asm__ __volatile__ (\"addq $8,%%rsp\":::\"cc\");" + << endl; + + out << " check_results (ret);" << endl; + } + } + + /* Close the last function and define the main do_tests function. */ + out << "}" << endl << endl; + + /* Define _noinfo pointers to each do_tests_* function. */ + for (auto const &fn_name : do_tests_fn_names) + out << " static void (*volatile " << fn_name << "_noinfo) (" + << param_types_str << ") = " << fn_name << ";" << endl; + + /* Define main do_tests () function. */ + out << endl + << "void do_tests ()" << endl + << "{" << endl; + i = 1; + for (auto const &arg : args) + { + out << " " << arg.get_type () << " " << arg.get_name () << " = " << i + << ";" << endl; + i <<= 1; + } + out << endl; + + /* Call do_tests_*_noinfo functions. */ + for (auto const &fn_name : do_tests_fn_names) + out << " " << fn_name << "_noinfo (" << param_names_str << ");" << endl; + out << "}" << endl << endl; +} + +/* Generate output file. */ +void generate_header (const string &args) +{ + vector<class arg> all_args; + vector<vector<class arg> > arg_sets; + + ofstream out; + out.exceptions (ios::failbit | ios::badbit); + out.open (out_file_name); + out << "/* Generated with " << args << " */" << endl << endl; + + assert (extra_params_max < 26); + + /* Build the extra argument array. */ + for (unsigned int i = 0; i < extra_params_max; ++i) + { + char name[2] = "a"; + name[0] += i; + class arg myarg (name, "long", true); + + all_args.push_back (myarg); + } + + arg_sets.resize (extra_params_max - extra_params_min + 1); + for (unsigned int i = 0; i < arg_sets.size (); ++i) + arg_sets[i].insert (arg_sets[i].end(), all_args.begin(), + all_args.begin () + i + extra_params_min); + + /* Print sysv functions */ + for (const vector<class arg> &as : arg_sets) + { + const int alloca_max = !!(fn_variant_mask & FN_VAR_MSABI); + const int varargs_max = !!(fn_variant_mask & FN_VAR_VARARGS); + fn *fn; + for (int _alloca = 0; _alloca <= alloca_max; ++_alloca) + for (int varargs = 0; varargs <= varargs_max; ++varargs) + { + try { + int var = (_alloca ? FN_VAR_ALLOCA : 0) + | (varargs ? FN_VAR_VARARGS : 0); + fn = new ::fn (as, 0, var); + } catch (invalid_argument) { + continue; + } + sysv_funcs.push_back (fn); + fn->print_def (out); + } + } + + /* Print _noinfo function pointers for sysv functions. */ + for (const fn *f : sysv_funcs) + f->print_noinfo_def (out); + + /* Print ms_abi functions. */ + unsigned int var; + for (var = 0; var <= FN_VAR_MASK; ++var) + { + /* We only want ms_abi fns for this. */ + if (! (var & FN_VAR_MSABI)) + continue; + + /* */ + if ((var & fn_variant_mask) != var) + continue; + + unsigned clobbers; + for (clobbers = 0; clobbers <= OPTIONAL_REG_ALL; ++clobbers) + { + /* Skip clobbers that would be invalid. */ + if (clobbers & OPTIONAL_REG_RBP) + { + /* Whole program built with hard frame pointer. */ + if (omit_rbp_clobbers) + continue; + + /* Uses BP explicitly. */ + if (var & FN_VAR_HFP_OR_REALIGN) + continue; + + /* Alloca seems to require DRAP, which uses BP. */ + if (var & FN_VAR_ALLOCA) + continue; + } + + for (auto const &as : arg_sets) + { + fn *fn; + try { + fn = new ::fn (as, clobbers, var); + } catch (invalid_argument) { + continue; + } + + msabi_funcs.push_back (fn); + fn->print_def (out); + } + } + } + + out << endl; + make_do_tests_decl (all_args, out); + out << endl; + + make_do_test (all_args, msabi_funcs, out); + out.close (); +} + +/* Parse a string into a long and return true upon success. */ +static bool long_optarg (const char *optarg, long &dest) +{ + char *end; + + errno = 0; + dest = strtol(optarg, &end, 0); + if (errno) + cerr << strerror(errno) << endl; + + while (isspace(*end)) + ++end; + + /* Error if errno non-zero or junk at end of string. */ + return errno || *end; +} + +void usage () +{ + cerr +<< "Usage: " << argv0 << " [options] <output_file>" << endl +<< endl +<< " -p <n|n-n>, --max-extra-params <expr>" << endl +<< " A single or range of extra parameters" << endl +<< " Examples:" << endl +<< " -p0-5" << endl +<< " -p12" << endl +<< endl +<< " -v <n>, --variant-mask <n>" << endl +<< " Set mask of test variants (see enum fn_variants for values," << endl +<< " defaults to 0x" << hex << FN_VAR_MASK << " [FN_VAR_MASK])" << endl +<< endl +<< " -0, --omit-rbp-clobbers" << endl +<< " Omit tests that clobber RBP." << endl; + exit (-1); +} + +/* Parse string representing a number range or a list of numbers. */ +void set_extra_param_counts (const char *str) +{ + char copy[0x40]; + char *max_str; + bool bad = false; + long int min, max; + + strncpy (copy, str, sizeof (copy) - 1); + max_str = strchr(copy, '-'); + if (max_str) + *max_str++ = 0; + + bad = long_optarg (copy, min); + if (max_str) + bad = bad || long_optarg (max_str, max); + else + max = min; + + if (min > max) + usage (); + + extra_params_min = min; + extra_params_max = max; +} + +int main (int argc, char *argv[]) +{ + argv0 = argv[0]; + const char *short_options = "p:v:0"; + const struct option long_options[] = { + {"extra-params", required_argument, 0, 'p'}, + {"variant-mask", required_argument, 0, 'v'}, + {"omit-rbp-clobbers", no_argument, 0, '0'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0}, + }; + + int option_index = 0; + int c; + while ((c = getopt_long (argc, argv, short_options, long_options, + &option_index)) != -1) + { + switch (c) + { + long l; + + case 'p': + set_extra_param_counts (optarg); + break; + + case 'v': + if (long_optarg (optarg, l) || (l & ~FN_VAR_MASK)) + { + cerr << "ERROR: Bad value for -v: `" << optarg << "`" << endl; + usage (); + } + fn_variant_mask = (unsigned)l; + break; + + case '0': + omit_rbp_clobbers = true; + break; + + case 'h': + default: + usage (); + } + } + + if (argc - optind != 1) + usage (); + out_file_name = argv[optind]; + + /* Can't skip msabi funcions. */ + fn_variant_mask |= FN_VAR_MSABI; + + /* If whole program has HFP, explicit tests that enable it are redundant. */ + if (omit_rbp_clobbers) + fn_variant_mask &= ~FN_VAR_HFP; + + stringstream argv_str; + + for (int i = 0; i < argc; ++i) + argv_str << (i ? " " : "") << argv[i]; + + int ret = 0; + try + { + generate_header (argv_str.str()); + } + catch (exception &e) + { + cerr << "ERROR: While writing `" << out_file_name << "': " + << strerror(errno) << endl; + ret = 1; + } + for_each (sysv_funcs.begin (), sysv_funcs.end (), default_delete<fn> ()); + for_each (msabi_funcs.begin (), msabi_funcs.end (), default_delete<fn> ()); + + return ret; +} diff --git a/gcc/testsuite/gcc.target/x86_64/abi/ms-sysv/ms-sysv.c b/gcc/testsuite/gcc.target/x86_64/abi/ms-sysv/ms-sysv.c new file mode 100644 index 00000000000..2a011f5103d --- /dev/null +++ b/gcc/testsuite/gcc.target/x86_64/abi/ms-sysv/ms-sysv.c @@ -0,0 +1,373 @@ +/* Test program for 64-Bit Microsoft to System V function calls. + Copyright (C) 2016-2017 Free Software Foundation, Inc. + Contributed by Daniel Santos <daniel.san...@pobox.com> + +This file is part of GCC. + +GCC is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3, or (at your option) +any later version. + +GCC is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +Under Section 7 of GPL version 3, you are granted additional +permissions described in the GCC Runtime Library Exception, version +3.1, as published by the Free Software Foundation. + +You should have received a copy of the GNU General Public License and +a copy of the GCC Runtime Library Exception along with this program; +see the files COPYING3 and COPYING.RUNTIME respectively. If not, see +<http://www.gnu.org/licenses/>. */ + +/* This is a single-threaded test program for Microsoft 64-bit ABI functions. + It is aimed at verifying correctness of pro/epilogues of ms_abi functions + that call sysv_abi functions to assure clobbered registers are properly + saved and restored and attempt to detect any flaws in the behavior of these + functions. The following variants are tested: + + * Either uses hard frame pointer, re-aligns the stack or neither, + * Uses alloca (and thus DRAP) or not, + * Uses sibling call optimization or not, + * Uses variable argument list or not, and + * Has shrink-wrapped code or not. + + In addition, an ms_abi function is generated for each of these combinations + clobbering each unique combination additional registers (excluding BP when + a frame pointer is used). Shrink-wrap variants are called in a way that + both the fast and slow path are used. Re-aligned variants are called with + an aligned and mis-aligned stack. + + Each ms_abi function is called via an assembly stub that first saves all + volatile registers and fills them with random values. The ms_abi function + is then called. After the function returns, the value of all volatile + registers is verified against the random data and then restored. */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <signal.h> +#include <unistd.h> +#include <stdint.h> +#include <alloca.h> +#include <stdarg.h> +#include <assert.h> +#include <errno.h> +#include <ctype.h> + +#ifndef __x86_64__ +# error Test only valid on x86_64 +#endif + +enum reg_data_sets +{ + REG_SET_SAVE, + REG_SET_INPUT, + REG_SET_OUTPUT, + + REG_SET_COUNT +}; + +enum flags +{ + FLAG_ALLOCA = 0x01000000, + FLAG_SIBCALL = 0x02000000, + FLAG_SHRINK_WRAP_FAST_PATH = 0x08000000, + FLAG_SHRINK_WRAP_SLOW_PATH = 0x0c000000, +}; + +enum alignment_option +{ + ALIGNMENT_NOT_TESTED, + ALIGNMENT_ALIGNED, + ALIGNMENT_MISALIGNED, + + ALIGNMENT_COUNT, +}; + +enum shrink_wrap_option +{ + SHRINK_WRAP_NONE, + SHRINK_WRAP_FAST_PATH, + SHRINK_WRAP_SLOW_PATH, + + SHRINK_WRAP_COUNT +}; + +union regdata { + struct { + __uint128_t sseregs[10]; + union { + uint64_t intregs[8]; + struct { + uint64_t rsi; + uint64_t rdi; + uint64_t rbx; + uint64_t rbp; + uint64_t r12; + uint64_t r13; + uint64_t r14; + uint64_t r15; + }; + }; + }; + uint32_t u32_arr[56]; +} __attribute__((aligned (16))); + +struct test_data +{ + union regdata regdata[REG_SET_COUNT]; + void *fn; + void *retaddr; + const char *name; + enum alignment_option alignment; + enum shrink_wrap_option shrink_wrap; + long ret_expected; +} test_data; + +static int shrink_wrap_global; +static void __attribute((sysv_abi)) do_tests (); +static void init_test (void *fn, const char *name, + enum alignment_option alignment, + enum shrink_wrap_option shrink_wrap, long ret_expected); +static void check_results (long ret); +static __attribute__((ms_abi)) long do_sibcall (long arg); +static __attribute__((ms_abi)) long +(*const volatile do_sibcall_noinfo) (long) = do_sibcall; + +/* Defines do_tests (). */ +#include "ms-sysv-generated.h" + +static int arbitrarily_fail; +static const char *argv0; + +static void __attribute__((noinline)) +init_test (void *fn, const char *name, enum alignment_option alignment, + enum shrink_wrap_option shrink_wrap, long ret_expected) +{ + int i; + union regdata *data = &test_data.regdata[REG_SET_INPUT]; + + assert (alignment < ALIGNMENT_COUNT); + assert (shrink_wrap < SHRINK_WRAP_COUNT); + + memset (&test_data, 0, sizeof (test_data)); + for (i = 55; i >= 0; --i) + data->u32_arr[i] = (uint32_t)lrand48 (); + test_data.fn = fn; + test_data.name = name; + test_data.alignment = alignment; + test_data.shrink_wrap = shrink_wrap; + test_data.ret_expected = ret_expected; + + switch (shrink_wrap) + { + case SHRINK_WRAP_NONE: + case SHRINK_WRAP_COUNT: + break; + case SHRINK_WRAP_FAST_PATH: + shrink_wrap_global = FLAG_SHRINK_WRAP_FAST_PATH; + break; + case SHRINK_WRAP_SLOW_PATH: + shrink_wrap_global = FLAG_SHRINK_WRAP_SLOW_PATH; + break; + } +} + +static const char *alignment_str[ALIGNMENT_COUNT] = +{ + "", "aligned", "misaligned" +}; + +static const char *shrink_wrap_str[SHRINK_WRAP_COUNT] = +{ + "", "shrink-wrap fast path", "shrink-wrap slow path" +}; + +static const char *test_descr () +{ + static char buffer[0x400]; + + if (test_data.alignment || test_data.shrink_wrap) + snprintf (buffer, sizeof (buffer) - 1, "`%s' (%s%s%s)", + test_data.name, + alignment_str[test_data.alignment], + (test_data.alignment && test_data.shrink_wrap ? ", " : ""), + shrink_wrap_str[test_data.shrink_wrap]); + else + snprintf (buffer, sizeof (buffer) - 1, "`%s'", test_data.name); + + return buffer; +} + +static const char *regnames[] = { + "XMM6", + "XMM7", + "XMM8", + "XMM9", + "XMM10", + "XMM11", + "XMM12", + "XMM13", + "XMM14", + "XMM15", + "RSI", + "RDI", + "RBX", + "RBP", + "R12", + "R13", + "R14", + "R15", +}; + +static void print_header (int *header_printed) +{ + if (!*header_printed) + fprintf (stderr, " %-35s %-35s\n", "Expected", "Got"); + *header_printed = 1; +} + +static int compare_reg128 (const __uint128_t *a, const __uint128_t *b, + const char *name, int *header_printed) +{ + if (!memcmp (a, b, sizeof (*a))) + return 0; + else + { + long ha = *((long*)a); + long la = *((long*)a + 16); + long hb = *((long*)b); + long lb = *((long*)a + 16); + print_header (header_printed); + fprintf (stderr, "%-5s: 0x%016lx %016lx != 0x%016lx %016lx\n", + name, ha, la, hb, lb); + return 1; + } +} + +static int compare_reg64 (long a, long b, const char *name, + int *header_printed) +{ + if (a == b) + return 0; + else + { + print_header (header_printed); + fprintf (stderr, "%s: 0x%016lx != 0x%016lx\n", name, a, b); + return 1; + } +} + + +static void __attribute__((noinline)) check_results (long ret) +{ + unsigned i; + unsigned bad = 0; + int header_printed = 0; + + union regdata *a = &test_data.regdata[REG_SET_INPUT]; + union regdata *b = &test_data.regdata[REG_SET_OUTPUT]; + + a = __builtin_assume_aligned(a, 16); + b = __builtin_assume_aligned(b, 16); + + if (arbitrarily_fail) { + uint64_t u64 = lrand48 (); + if (u64 % 100 == 0) + b->u32_arr[u64 % 56] = 0xfdfdfdfd; + } + + for (i = 0; i < 10; ++i) + bad |= compare_reg128 (&a->sseregs[i], &b->sseregs[i], regnames[i], + &header_printed); + + for (i = 0; i < 8; ++i) + bad |= compare_reg64 (a->intregs[i], b->intregs[i], regnames[i + 10], + &header_printed); + + if (ret != test_data.ret_expected) + { + fprintf (stderr, "Wrong return value: got 0x%016lx, expected 0x%016lx\n", + ret, test_data.ret_expected); + bad = 1; + } + + if (bad) + { + fprintf (stderr, "Failed on test function %s\n", test_descr ()); + raise (SIGTRAP); + exit (-1); + } +} + +static __attribute__((ms_abi, noinline)) long do_sibcall (long arg) { + return arg + FLAG_SIBCALL; +} + +void usage () +{ + fprintf (stderr, "Usage: %s [-s <seed>] [-f]\n", argv0); + exit (-1); +} + +static long long_optarg (const char *optarg, const char *optstr) +{ + char *end; + long ret; + + errno = 0; + ret = strtol(optarg, &end, 0); + + while (isspace (*end)) + ++end; + + if (errno || *end) + { + fprintf (stderr, "ERROR: Bad value for %s: `%s`\n", optstr, optarg); + if (errno) + fprintf (stderr, "%s\n", strerror (errno)); + exit (-1); + } + + return ret; +} + +int main (int argc, char *argv[]) +{ + long seed = 0; + int c; + argv0 = argv[0]; + + assert (!((long)&test_data.regdata[REG_SET_SAVE] & 15)); + assert (!((long)&test_data.regdata[REG_SET_INPUT] & 15)); + assert (!((long)&test_data.regdata[REG_SET_OUTPUT] & 15)); + + while ((c = getopt (argc, argv, "s:f")) != -1) + { + switch (c) + { + case 's': + seed = long_optarg (optarg, "-s"); + break; + + case 'f': + arbitrarily_fail = 1; + fprintf (stderr, "NOTE: Aribrary failure enabled (-f).\n"); + break; + } + } + + srand48 (seed); + do_tests (); + + /* Just in case we don't have enough tests to randomly trigger the + failure. */ + if (arbitrarily_fail) + return -1; + + return 0; +} diff --git a/gcc/testsuite/gcc.target/x86_64/abi/ms-sysv/ms-sysv.exp b/gcc/testsuite/gcc.target/x86_64/abi/ms-sysv/ms-sysv.exp new file mode 100644 index 00000000000..e317af9bd85 --- /dev/null +++ b/gcc/testsuite/gcc.target/x86_64/abi/ms-sysv/ms-sysv.exp @@ -0,0 +1,178 @@ +# Tests for ms_abi to sysv_abi calls. +# Copyright (C) 2016-2017 Free Software Foundation, Inc. +# Contributed by Daniel Santos <daniel.san...@pobox.com> +# +# This file is part of GCC. +# +# GCC is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# GCC is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# Under Section 7 of GPL version 3, you are granted additional +# permissions described in the GCC Runtime Library Exception, version +# 3.1, as published by the Free Software Foundation. +# +# You should have received a copy of the GNU General Public License and +# a copy of the GCC Runtime Library Exception along with this program; +# see the files COPYING3 and COPYING.RUNTIME respectively. If not, see +# <http://www.gnu.org/licenses/>. + +# Exit immediately if this isn't a native x86_64 target. +if { (![istarget x86_64-*-*] && ![istarget i?86-*-*]) + || ![is-effective-target lp64] || ![isnative] } then { + unsupported "$subdir" + return +} + +global GCC_RUNTEST_PARALLELIZE_DIR + +load_lib gcc-dg.exp + +proc runtest_ms_sysv { cflags generator_args } { + global GCC_UNDER_TEST HOSTCXX HOSTCXXFLAGS tmpdir srcdir subdir \ + parallel_dir next_test + + set objdir "$tmpdir/ms-sysv" + set generator "$tmpdir/ms-sysv-generate.exe" + set generated_header "$objdir/ms-sysv-generated.h" + set do_test_o "$objdir/do-test.o" + set ms_sysv_o "$objdir/ms-sysv.o" + set ms_sysv_exe "$objdir/ms-sysv.exe" + set status 0 + set warn_flags "-Wall" + set this_test $next_test + incr next_test + + # Do parallelization here + if [catch {set fd [open "$parallel_dir/$this_test" \ + [list RDWR CREAT EXCL]]} ] { + if { [lindex $::errorCode 1] eq "EEXIST" } then { + # Another job is running this test + return + } else { + error "Failed to open $parallel_dir/$this_test: $::errorCode" + set status 1 + } + } else { + close $fd + } + + # Detect when hard frame pointers are enabled (or required) so we know not + # to generate bp clobbers. + if [regexp "^(.+ +| *)-(O0|fno-omit-frame-pointer|p|pg)( +.*)?$" \ + $cflags match] then { + set generator_args "$generator_args --omit-rbp-clobbers" + } + + set descr "$subdir CFLAGS=\"$cflags\" generator_args=\"$generator_args\"" + verbose "$tmpdir: Running test $descr" 1 + + # Cleanup any previous test in objdir + file delete -force $objdir + file mkdir $objdir + + # Build the generator (only needs to be done once). + set src "$srcdir/$subdir/gen.cc" + if { $status == 0 } then { + if { (![file exists "$generator"]) || ([file mtime "$generator"] + < [file mtime "$src"]) } { + # Temporarily switch to the environment for the host compiler. + restore_ld_library_path_env_vars + set cxx "$HOSTCXX $HOSTCXXFLAGS $warn_flags -std=c++11" + set status [remote_exec host "$cxx -o $generator $src"] + set status [lindex $status 0] + set_ld_library_path_env_vars + if { $status != 0 } then { + warning "Could not build $subdir generator" + } + } + } + + # Generate header + if { $status == 0 } then { + set status [remote_exec host "$generator $generator_args $generated_header"] + set status [lindex $status 0] + if { $status != 0 } then { + warning "Could not generate $generated_header" + } + } + + set cc "$GCC_UNDER_TEST -I$objdir -I$srcdir/$subdir $cflags $warn_flags" + + # Assemble do-test.S + set src "$srcdir/$subdir/do-test.S" + if { $status == 0 } then { + set status [remote_exec build "$cc -c -o $do_test_o $src"] + set status [lindex $status 0] + if { $status != 0 } then { + warning "Could not assemble $src" + } + } + + # Build ms-sysv.c + set src "$srcdir/$subdir/ms-sysv.c" + if { $status == 0 } then { + set status [remote_exec build "$cc -c -o $ms_sysv_o $src" "" "" "" 1200] + set status [lindex $status 0] + if { $status != 0 } then { + warning "Could not build $src." + } + } + + # Link + if { $status == 0 } then { + set status [remote_exec build "$cc -o $ms_sysv_exe $ms_sysv_o $do_test_o"] + set status [lindex $status 0] + if { $status != 0 } then { + warning "Link failed." + } + } + + # Execute + if { $status == 0 } then { + set status [remote_exec build "$ms_sysv_exe"] + set status [lindex $status 0] + } + + if { $status != 0 } then { + fail $descr + } else { + pass $descr + } +} + +dg-init + +# Setup parallelization +set next_test 0 +set parallel_dir "$env(GCC_RUNTEST_PARALLELIZE_DIR)/abi-ms-sysv" +file mkdir "$env(GCC_RUNTEST_PARALLELIZE_DIR)" +file mkdir "$parallel_dir" + +if { ![file isdirectory "$parallel_dir"] } then { + error "Failed to create directory $parallel_dir: $::errorCode" + return +} + +set gen_opts "-p0-5" +set all_options [list "-O2" "-O0 -g3"] + +# Run without -mcall-ms2sysv-xlogues always +foreach opt $all_options { + runtest_ms_sysv "$opt" "$gen_opts" +} + +# Skip -mcall-ms2sysv-xlogues on Windows (not supported) +if { ![istarget *-*-cygwin*] && ![istarget *-*-mingw*] } { + foreach opt $all_options { + runtest_ms_sysv "-mcall-ms2sysv-xlogues $opt" "$gen_opts" + } +} + +dg-finish -- 2.11.0