From 83f42e358bff2c80a299e8c3dd6505ef6bad1b29 Mon Sep 17 00:00:00 2001 From: Josh Coalson Date: Sat, 3 Mar 2007 01:52:19 +0000 Subject: [PATCH] add thread-safe runtime SSE OS support detection for most operating systems; clean up altivec detection --- doc/html/changelog.html | 2 +- src/libFLAC/cpu.c | 245 ++++++++++++++++++++++++++++++++++------ 2 files changed, 209 insertions(+), 38 deletions(-) diff --git a/doc/html/changelog.html b/doc/html/changelog.html index 668404f4..b91843f1 100644 --- a/doc/html/changelog.html +++ b/doc/html/changelog.html @@ -104,7 +104,7 @@
  • libraries:
  • diff --git a/src/libFLAC/cpu.c b/src/libFLAC/cpu.c index d78c7e17..50f7d26a 100644 --- a/src/libFLAC/cpu.c +++ b/src/libFLAC/cpu.c @@ -37,7 +37,9 @@ #include #include -#if defined FLAC__CPU_PPC +#if defined FLAC__CPU_IA32 +# include +#elif defined FLAC__CPU_PPC # if !defined FLAC__NO_ASM # if defined FLAC__SYS_DARWIN # include @@ -50,11 +52,6 @@ # endif # else /* FLAC__SYS_DARWIN */ -# ifdef __FreeBSD__ -# include -# include -# endif - # include # include @@ -74,6 +71,21 @@ static void sigill_handler (int sig) # endif /* FLAC__NO_ASM */ #endif /* FLAC__CPU_PPC */ +#if defined (__NetBSD__) || defined(__OpenBSD__) +#include +#include +#include +#endif + +#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) +#include +#include +#endif + +#if defined(__APPLE__) +/* how to get sysctlbyname()? */ +#endif + const unsigned FLAC__CPUINFO_IA32_CPUID_CMOV = 0x00008000; const unsigned FLAC__CPUINFO_IA32_CPUID_MMX = 0x00800000; const unsigned FLAC__CPUINFO_IA32_CPUID_FXSR = 0x01000000; @@ -85,8 +97,62 @@ const unsigned FLAC__CPUINFO_IA32_CPUID_EXTENDED_AMD_EXT3DNOW = 0x40000000; const unsigned FLAC__CPUINFO_IA32_CPUID_EXTENDED_AMD_EXTMMX = 0x00400000; +/* + * Extra stuff needed for detection of OS support for SSE on IA-32 + */ +#if defined(FLAC__CPU_IA32) && !defined FLAC__NO_ASM && defined FLAC__HAS_NASM && !defined FLAC__NO_SSE_OS && !defined FLAC__SSE_OS +# if defined(__linux__) +/* + * If the OS doesn't support SSE, we will get here with a SIGILL. We + * modify the return address to jump over the offending SSE instruction + * and also the operation following it that indicates the instruction + * executed successfully. In this way we use no global variables and + * stay thread-safe. + * + * 3 + 3 + 6: + * 3 bytes for "xorps xmm0,xmm0" + * 3 bytes for estimate of how long the follwing "inc var" instruction is + * 6 bytes extra in case our estimate is wrong + * 12 bytes puts us in the NOP "landing zone" + */ +# define USE_OBSOLETE_SIGCONTEXT_FLAVOR /*@@@@@@ #define this to use the older signal handler method */ +# ifdef USE_OBSOLETE_SIGCONTEXT_FLAVOR + static void sigill_handler_sse_os(int signal, struct sigcontext sc) + { + (void)signal; + sc.eip += 3 + 3 + 6; + } +# else +# include + static void sigill_handler_sse_os(int signal, siginfo_t *si, void *uc) + { + (void)signal, (void)si; + ((ucontext_t*)uc)->uc_mcontext.gregs[14/*REG_EIP*/] += 3 + 3 + 6; + } +# endif +# elif defined(_MSC_VER) +# include +# define USE_TRY_CATCH_FLAVOR /*@@@@@@ #define this to use the try/catch method for catching illegal opcode exception */ +# ifdef USE_TRY_CATCH_FLAVOR +# else + LONG CALLBACK sigill_handler_sse_os(EXCEPTION_POINTERS *ep) + { + if(ep->ExceptionRecord->ExceptionCode == EXCEPTION_ILLEGAL_INSTRUCTION) { + ep->ContextRecord->Eip += 3 + 3 + 6; + return EXCEPTION_CONTINUE_EXECUTION; + } + return EXCEPTION_CONTINUE_SEARCH; + } +# endif +# endif +#endif + + void FLAC__cpu_info(FLAC__CPUInfo *info) { +/* + * IA32-specific + */ #ifdef FLAC__CPU_IA32 info->type = FLAC__CPUINFO_TYPE_IA32; #if !defined FLAC__NO_ASM && defined FLAC__HAS_NASM @@ -94,23 +160,11 @@ void FLAC__cpu_info(FLAC__CPUInfo *info) { unsigned cpuid = FLAC__cpu_info_asm_ia32(); info->data.ia32.cmov = (cpuid & FLAC__CPUINFO_IA32_CPUID_CMOV)? true : false; - info->data.ia32.mmx = (cpuid & FLAC__CPUINFO_IA32_CPUID_MMX)? true : false; + info->data.ia32.mmx = (cpuid & FLAC__CPUINFO_IA32_CPUID_MMX )? true : false; info->data.ia32.fxsr = (cpuid & FLAC__CPUINFO_IA32_CPUID_FXSR)? true : false; - info->data.ia32.sse = (cpuid & FLAC__CPUINFO_IA32_CPUID_SSE)? true : false; + info->data.ia32.sse = (cpuid & FLAC__CPUINFO_IA32_CPUID_SSE )? true : false; info->data.ia32.sse2 = (cpuid & FLAC__CPUINFO_IA32_CPUID_SSE2)? true : false; -#ifndef FLAC__SSE_OS - info->data.ia32.fxsr = info->data.ia32.sse = info->data.ia32.sse2 = false; -#elif defined(__FreeBSD__) - /* on FreeBSD we can double-check via sysctl whether the OS supports SSE */ - { - int sse; - size_t len = sizeof(sse); - if (sysctlbyname("hw.instruction_sse", &sse, &len, NULL, 0) || !sse) - info->data.ia32.fxsr = info->data.ia32.sse = info->data.ia32.sse2 = false; - } -#endif - #ifdef FLAC__USE_3DNOW cpuid = FLAC__cpu_info_extended_amd_asm_ia32(); info->data.ia32._3dnow = (cpuid & FLAC__CPUINFO_IA32_CPUID_EXTENDED_AMD_3DNOW)? true : false; @@ -119,23 +173,135 @@ void FLAC__cpu_info(FLAC__CPUInfo *info) #else info->data.ia32._3dnow = info->data.ia32.ext3dnow = info->data.ia32.extmmx = false; #endif + + /* + * now have to check for OS support of SSE/SSE2 + */ + if(info->data.ia32.fxsr || info->data.ia32.sse || info->data.ia32.sse2) { +#if defined FLAC__NO_SSE_OS + /* assume user knows better than us; turn it off */ + info->data.ia32.fxsr = info->data.ia32.sse = info->data.ia32.sse2 = false; +#elif defined FLAC__SSE_OS + /* assume user knows better than us; leave as detected above */ +#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) || defined(__APPLE__) + int sse = 0; + size_t len; + /* at least one of these must work: */ + len = sizeof(sse); sse = sse || (sysctlbyname("hw.instruction_sse", &sse, &len, NULL, 0) == 0 && sse); + len = sizeof(sse); sse = sse || (sysctlbyname("hw.optional.sse" , &sse, &len, NULL, 0) == 0 && sse); /* __APPLE__ ? */ + if(!sse) + info->data.ia32.fxsr = info->data.ia32.sse = info->data.ia32.sse2 = false; +#elif defined(__NetBSD__) || defined (__OpenBSD__) +# if __NetBSD_Version__ >= 105250000 || (defined __OpenBSD__) + int val = 0, mib[2] = { CTL_MACHDEP, CPU_SSE }; + size_t len = sizeof(val); + if(sysctl(mib, 2, &val, &len, NULL, 0) < 0 || !val) + info->data.ia32.fxsr = info->data.ia32.sse = info->data.ia32.sse2 = false; + else { /* double-check SSE2 */ + mib[1] = CPU_SSE2; + len = sizeof(val); + if(sysctl(mib, 2, &val, &len, NULL, 0) < 0 || !val) + info->data.ia32.sse2 = false; + } +# else + info->data.ia32.fxsr = info->data.ia32.sse = info->data.ia32.sse2 = false; +# endif +#elif defined(__linux__) + int sse = 0; + struct sigaction sigill_save; +#ifdef USE_OBSOLETE_SIGCONTEXT_FLAVOR + if(0 == sigaction(SIGILL, NULL, &sigill_save) && signal(SIGILL, (void (*)(int))sigill_handler_sse_os) != SIG_ERR) +#else + struct sigaction sigill_sse; + sigill_sse.sa_sigaction = sigill_handler_sse_os; + __sigemptyset(&sigill_sse.sa_mask); + sigill_sse.sa_flags = SA_SIGINFO | SA_RESETHAND; /* SA_RESETHAND just in case our SIGILL return jump breaks, so we don't get stuck in a loop */ + if(0 == sigaction(SIGILL, &sigill_sse, &sigill_save)) +#endif + { + /* http://www.ibiblio.org/gferg/ldp/GCC-Inline-Assembly-HOWTO.html */ + /* see sigill_handler_sse_os() for an explanation of the following: */ + asm volatile ( + "xorl %0,%0\n\t" /* for some reason, still need to do this to clear 'sse' var */ + "xorps %%xmm0,%%xmm0\n\t" /* will cause SIGILL if unsupported by OS */ + "incl %0\n\t" /* SIGILL handler will jump over this */ + /* landing zone */ + "nop\n\t" /* SIGILL jump lands here if "inc" is 9 bytes */ + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" /* SIGILL jump lands here if "inc" is 3 bytes (expected) */ + "nop\n\t" + "nop" /* SIGILL jump lands here if "inc" is 1 byte */ + : "=r"(sse) + : "r"(sse) + ); + + sigaction(SIGILL, &sigill_save, NULL); + } + + if(!sse) + info->data.ia32.fxsr = info->data.ia32.sse = info->data.ia32.sse2 = false; +#elif defined(_MSC_VER) +# ifdef USE_TRY_CATCH_FLAVOR + _try { + __asm xorps xmm0,xmm0 + } + _except(EXCEPTION_EXECUTE_HANDLER) { + if (_exception_code() == STATUS_ILLEGAL_INSTRUCTION) + info->data.ia32.fxsr = info->data.ia32.sse = info->data.ia32.sse2 = false; + } +# else + int sse = 0; + LPTOP_LEVEL_EXCEPTION_FILTER save = SetUnhandledExceptionFilter(sigill_handler_sse_os); + /* see GCC version above for explanation */ + //@@@@@@ http://msdn2.microsoft.com/en-us/library/4ks26t93.aspx + //@@@@@@ http://www.daniweb.com/techtalkforums/thread8072.html + //@@@@@@ http://www.codeproject.com/cpp/gccasm.asp + //@@@@@@ http://www.hick.org/~mmiller/msvc_inline_asm.html + __asm { + xorps xmm0,xmm0 + inc sse + nop + nop + nop + nop + nop + nop + nop + nop + nop + } + SetUnhandledExceptionFilter(save); + if(!sse) + info->data.ia32.fxsr = info->data.ia32.sse = info->data.ia32.sse2 = false; +fprintf(stderr,"\n@@@@@@ SSE OS=%d %d\n",sse?1:0,sse); +# endif +#else + /* no way to test, disable to be safe */ + info->data.ia32.fxsr = info->data.ia32.sse = info->data.ia32.sse2 = false; +#endif + } } #else info->use_asm = false; #endif + +/* + * PPC-specific + */ #elif defined FLAC__CPU_PPC info->type = FLAC__CPUINFO_TYPE_PPC; -#if !defined FLAC__NO_ASM +# if !defined FLAC__NO_ASM info->use_asm = true; -#ifdef FLAC__USE_ALTIVEC -#if defined FLAC__SYS_DARWIN +# ifdef FLAC__USE_ALTIVEC +# if defined FLAC__SYS_DARWIN { - int selectors[2] = { CTL_HW, HW_VECTORUNIT }; - int result = 0; - size_t length = sizeof(result); - int error = sysctl(selectors, 2, &result, &length, 0, 0); - - info->data.ppc.altivec = error==0 ? result!=0 : 0; + int val = 0, mib[2] = { CTL_HW, HW_VECTORUNIT }; + size_t len = sizeof(val); + info->data.ppc.altivec = !(sysctl(mib, 2, &val, &len, NULL, 0) || !val); } { host_basic_info_data_t hostInfo; @@ -146,14 +312,15 @@ void FLAC__cpu_info(FLAC__CPUInfo *info) info->data.ppc.ppc64 = (hostInfo.cpu_type == CPU_TYPE_POWERPC) && (hostInfo.cpu_subtype == CPU_SUBTYPE_POWERPC_970); } -#else /* FLAC__SYS_DARWIN */ +# else /* FLAC__USE_ALTIVEC && !FLAC__SYS_DARWIN */ { /* no Darwin, do it the brute-force way */ - /* this is borrowed from MPlayer from the libmpeg2 library */ + /* @@@@@@ this is not thread-safe; replace with SSE OS method above or remove */ info->data.ppc.altivec = 0; info->data.ppc.ppc64 = 0; signal (SIGILL, sigill_handler); + canjump = 0; if (!sigsetjmp (jmpbuf, 1)) { canjump = 1; @@ -176,16 +343,20 @@ void FLAC__cpu_info(FLAC__CPUInfo *info) info->data.ppc.ppc64 = 1; } - signal (SIGILL, SIG_DFL); + signal (SIGILL, SIG_DFL); /*@@@@@@ should save and restore old signal */ } -#endif /* FLAC__SYS_DARWIN */ -#else /* FLAC__USE_ALTIVEC */ +# endif +# else /* !FLAC__USE_ALTIVEC */ info->data.ppc.altivec = 0; info->data.ppc.ppc64 = 0; -#endif /* FLAC__USE_ALTIVEC */ -#else /* FLAC__NO_ASM */ +# endif +# else info->use_asm = false; -#endif /* FLAC__NO_ASM */ +# endif + +/* + * unknown CPI + */ #else info->type = FLAC__CPUINFO_TYPE_UNKNOWN; info->use_asm = false;