| /* Test program for 64-Bit Microsoft to System V function calls. |
| Copyright (C) 2016-2017 Free Software Foundation, Inc. |
| Contributed by Daniel Santos <daniel.santos@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. */ |
| |
| /* { dg-do run } */ |
| /* { dg-additional-sources "do-test.S" } */ |
| /* { dg-additional-options "-Wall" } */ |
| /* { dg-require-effective-target alloca } */ |
| |
| #include <stdio.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <signal.h> |
| #include <unistd.h> |
| #include <stdint.h> |
| #include <stdarg.h> |
| #include <assert.h> |
| #include <errno.h> |
| #include <ctype.h> |
| |
| #if !defined(__x86_64__) || !defined(__SSE2__) |
| # error Test only valid on x86_64 with -msse2 |
| #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; |
| |
| |
| #define PASTE_STR2(a) #a |
| #define PASTE_STR1(a, b) PASTE_STR2(a ## b) |
| #define PASTE_STR(a, b) PASTE_STR1(a, b) |
| |
| #ifdef __USER_LABEL_PREFIX__ |
| # define ASMNAME(name) PASTE_STR(__USER_LABEL_PREFIX__, name) |
| #else |
| # define ASMNAME(name) #name |
| #endif |
| |
| #ifdef __MACH__ |
| # define LOAD_TEST_DATA_ADDR(dest) \ |
| " mov " ASMNAME(test_data) "@GOTPCREL(%%rip), " dest "\n" |
| #else |
| # define LOAD_TEST_DATA_ADDR(dest) \ |
| " lea " ASMNAME(test_data) "(%%rip), " dest "\n" |
| #endif |
| |
| #define TEST_DATA_OFFSET(f) ((int)__builtin_offsetof(struct test_data, f)) |
| |
| void __attribute__((naked)) |
| do_test_body (void) |
| {__asm__ ( |
| " # rax, r10 and r11 are usable here.\n" |
| "\n" |
| " # Save registers.\n" |
| LOAD_TEST_DATA_ADDR("%%rax") |
| " lea %p0(%%rax), %%r10\n" |
| " call " ASMNAME(regs_to_mem) "\n" |
| "\n" |
| " # Load registers with random data.\n" |
| " lea %p1(%%rax), %%r10\n" |
| " call " ASMNAME(mem_to_regs) "\n" |
| "\n" |
| " # Pop and save original return address.\n" |
| " pop %%r10\n" |
| " mov %%r10, %p4(%%rax)\n" |
| "\n" |
| " # Call the test function, after which rcx, rdx and r8-11\n" |
| " # become usable.\n" |
| " lea %p3(%%rax), %%rax\n" |
| " call *(%%rax)\n" |
| "\n" |
| " # Store resulting register values.\n" |
| LOAD_TEST_DATA_ADDR("%%rcx") |
| " lea %p2(%%rcx), %%r10\n" |
| " call " ASMNAME(regs_to_mem) "\n" |
| "\n" |
| " # Push the original return address.\n" |
| " lea %p4(%%rcx), %%r10\n" |
| " push (%%r10)\n" |
| "\n" |
| " # Restore registers.\n" |
| " lea %p0(%%rcx), %%r10\n" |
| " call " ASMNAME(mem_to_regs) "\n" |
| "\n" |
| " retq\n" |
| :: |
| "i"(TEST_DATA_OFFSET(regdata[REG_SET_SAVE])), |
| "i"(TEST_DATA_OFFSET(regdata[REG_SET_INPUT])), |
| "i"(TEST_DATA_OFFSET(regdata[REG_SET_OUTPUT])), |
| "i"(TEST_DATA_OFFSET(fn)), |
| "i"(TEST_DATA_OFFSET(retaddr)) : "memory"); |
| } |
| |
| 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; |
| } |