From 6e9d43e085c01969a0e117e6b953de868e6c9e9e Mon Sep 17 00:00:00 2001 From: DRC Date: Wed, 6 Jul 2016 16:58:28 +0000 Subject: [PATCH] Linux/PPC: Only enable AltiVec if CPU supports it This eliminates "illegal instruction" errors when running libjpeg-turbo under Linux on PowerPC chips that lack AltiVec support (e.g. the old 7XX/G3 models but also the newer e5500 series.) --- ChangeLog.md | 13 +++++++ simd/Makefile.am | 9 ++++- simd/jsimd_powerpc.c | 91 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 109 insertions(+), 4 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 9653471..426a83b 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -20,6 +20,19 @@ These environment variables provide a workaround for those attempting to test ARM and MIPS builds of libjpeg-turbo in QEMU, which passes through /proc/cpuinfo from the host system. +2. libjpeg-turbo previously assumed that AltiVec instructions were always +available on PowerPC platforms, which led to "illegal instruction" errors when +running on PowerPC chips that lack AltiVec support (such as the older 7xx/G3 +and newer e5500 series.) libjpeg-turbo now examines /proc/cpuinfo on +Linux/Android systems and enables AltiVec instructions only if the CPU supports +them. It also now provides two environment variables, `JSIMD_FORCEALTIVEC` and +`JSIMD_FORCENONE`, to force-enable and force-disable AltiVec instructions in +environments where /proc/cpuinfo is an unreliable means of CPU feature +detection (such as when running in QEMU.) On OS X, libjpeg-turbo continues to +assume that AltiVec support is always available, which means that libjpeg-turbo +cannot be used with G3 Macs unless you set the environment variable +`JSIMD_FORCENONE` to `1`. + 1.5.0 ===== diff --git a/simd/Makefile.am b/simd/Makefile.am index fad6c8c..b8660d1 100644 --- a/simd/Makefile.am +++ b/simd/Makefile.am @@ -73,19 +73,24 @@ endif if SIMD_POWERPC -libsimd_la_SOURCES = jsimd_powerpc.c jsimd_altivec.h jcsample.h \ +noinst_LTLIBRARIES += libsimd_altivec.la + +libsimd_altivec_la_SOURCES = \ jccolor-altivec.c jcgray-altivec.c jcsample-altivec.c \ jdcolor-altivec.c jdmerge-altivec.c jdsample-altivec.c \ jfdctfst-altivec.c jfdctint-altivec.c \ jidctfst-altivec.c jidctint-altivec.c \ jquanti-altivec.c -libsimd_la_CFLAGS = -maltivec +libsimd_altivec_la_CFLAGS = -maltivec jccolor-altivec.lo: jccolext-altivec.c jcgray-altivec.lo: jcgryext-altivec.c jdcolor-altivec.lo: jdcolext-altivec.c jdmerge-altivec.lo: jdmrgext-altivec.c +libsimd_la_SOURCES = jsimd_powerpc.c jsimd_altivec.h jcsample.h +libsimd_la_LIBADD = libsimd_altivec.la + endif AM_CPPFLAGS = -I$(top_srcdir) diff --git a/simd/jsimd_powerpc.c b/simd/jsimd_powerpc.c index 9edd50a..42dc1e0 100644 --- a/simd/jsimd_powerpc.c +++ b/simd/jsimd_powerpc.c @@ -2,7 +2,7 @@ * jsimd_powerpc.c * * Copyright 2009 Pierre Ossman for Cendio AB - * Copyright (C) 2009-2011, 2014-2015, D. R. Commander. + * Copyright (C) 2009-2011, 2014-2016, D. R. Commander. * Copyright (C) 2015, Matthieu Darbois. * * Based on the x86 SIMD extension for IJG JPEG library, @@ -22,19 +22,106 @@ #include "../jsimddct.h" #include "jsimd.h" +#include +#include +#include + static unsigned int simd_support = ~0; +#if defined(__linux__) || defined(ANDROID) || defined(__ANDROID__) + +#define SOMEWHAT_SANE_PROC_CPUINFO_SIZE_LIMIT (1024 * 1024) + +LOCAL(int) +check_feature (char *buffer, char *feature) +{ + char *p; + if (*feature == 0) + return 0; + if (strncmp(buffer, "cpu", 3) != 0) + return 0; + buffer += 3; + while (isspace(*buffer)) + buffer++; + + /* Check if 'feature' is present in the buffer as a separate word */ + while ((p = strstr(buffer, feature))) { + if (p > buffer && !isspace(*(p - 1))) { + buffer++; + continue; + } + p += strlen(feature); + if (*p != 0 && !isspace(*p)) { + buffer++; + continue; + } + return 1; + } + return 0; +} + +LOCAL(int) +parse_proc_cpuinfo (int bufsize) +{ + char *buffer = (char *)malloc(bufsize); + FILE *fd; + simd_support = 0; + + if (!buffer) + return 0; + + fd = fopen("/proc/cpuinfo", "r"); + if (fd) { + while (fgets(buffer, bufsize, fd)) { + if (!strchr(buffer, '\n') && !feof(fd)) { + /* "impossible" happened - insufficient size of the buffer! */ + fclose(fd); + free(buffer); + return 0; + } + if (check_feature(buffer, "altivec")) + simd_support |= JSIMD_ALTIVEC; + } + fclose(fd); + } + free(buffer); + return 1; +} + +#endif + +/* + * Check what SIMD accelerations are supported. + * + * FIXME: This code is racy under a multi-threaded environment. + */ LOCAL(void) init_simd (void) { char *env = NULL; +#if !defined(__ALTIVEC__) && (defined(__linux__) || defined(ANDROID) || defined(__ANDROID__)) + int bufsize = 1024; /* an initial guess for the line buffer size limit */ +#endif if (simd_support != ~0U) return; - simd_support = JSIMD_ALTIVEC; + simd_support = 0; + +#if defined(__ALTIVEC__) || defined(__APPLE__) + simd_support |= JSIMD_ALTIVEC; +#elif defined(__linux__) || defined(ANDROID) || defined(__ANDROID__) + while (!parse_proc_cpuinfo(bufsize)) { + bufsize *= 2; + if (bufsize > SOMEWHAT_SANE_PROC_CPUINFO_SIZE_LIMIT) + break; + } +#endif /* Force different settings through environment variables */ + env = getenv("JSIMD_FORCEALTIVEC"); + if ((env != NULL) && (strcmp(env, "1") == 0)) + simd_support = JSIMD_ALTIVEC; env = getenv("JSIMD_FORCENONE"); if ((env != NULL) && (strcmp(env, "1") == 0)) simd_support = 0; -- 2.40.0