From ca448546b736f3ff0ea3163220fbd7dc48fc5bbc Mon Sep 17 00:00:00 2001 From: Piotr Kubaj Date: Wed, 16 Sep 2026 14:45:19 +0200 Subject: [PATCH 1/2] Add PowerPC64 (VSX) support via clang's x86 intrinsic wrappers clang ships ppc_wrappers/{x,e,p,t,s,n}mmintrin.h that implement the SSE..SSE4.2 intrinsics on top of VSX (POWER8 and later), which is the ISA level the SSE4.2 kernels need. Treat powerpc64 hosts like the AArch64 path: define the x86 ISA macros by hand in clang.cmake, mark the platform as 64-bit, report SSE4.2 features at runtime, and opt into the wrapper headers from platform.h so that every translation unit including embree headers gets them. The few intrinsics the wrappers lack (_mm_popcnt_u32/u64, _mm_dp_ps, _mm_insert_ps, _mm_stream_load_si128) and the MXCSR control family (no POWER equivalent; FTZ/DAZ become no-ops) get small definitions in intrinsics.h. Tested with clang 19 on FreeBSD powerpc64le and powerpc64 (big-endian): the ANARI SDK helide device renders all 13 of its test scenes pixel-identical to an x86-64 build. --- CMakeLists.txt | 3 +++ common/cmake/clang.cmake | 3 +++ common/sys/intrinsics.h | 31 +++++++++++++++++++++++++++++++ common/sys/platform.h | 8 +++++++- common/sys/sysinfo.cpp | 7 +++++++ 5 files changed, 51 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 37a553dd64..c25a66c854 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -228,6 +228,9 @@ ELSEIF(CMAKE_SYSTEM_NAME STREQUAL "Windows" AND CMAKE_GENERATOR_PLATFORM STREQUA ELSEIF(CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "ARM64") MESSAGE(STATUS "Building for AArch64") SET(EMBREE_ARM ON) +ELSEIF(CMAKE_SYSTEM_PROCESSOR MATCHES "^(powerpc|ppc)64") + MESSAGE(STATUS "Building for PowerPC64") + SET(EMBREE_PPC64 ON) ENDIF() SET(EMBREE_TASKING_SYSTEM "TBB" CACHE STRING "Selects tasking system") diff --git a/common/cmake/clang.cmake b/common/cmake/clang.cmake index ecb5f399db..f13fc019d0 100644 --- a/common/cmake/clang.cmake +++ b/common/cmake/clang.cmake @@ -21,6 +21,9 @@ IF (EMBREE_ARM) SET(FLAGS_AVX "-D__AVX__ -D__SSE4_2__ -D__SSE4_1__ -D__BMI__ -D__BMI2__ -D__LZCNT__") SET(FLAGS_AVX2 "-D__AVX2__ -D__AVX__ -D__SSE4_2__ -D__SSE4_1__ -D__BMI__ -D__BMI2__ -D__LZCNT__") ENDIF () +ELSEIF (EMBREE_PPC64) + SET(FLAGS_SSE2 "-D__SSE__ -D__SSE2__ -mcpu=power8") + SET(FLAGS_SSE42 "-D__SSE4_2__ -D__SSE4_1__ -mcpu=power8") ELSE () # for `thread` keyword _SET_IF_EMPTY(FLAGS_SSE2 "-msse -msse2 -mno-sse4.2") diff --git a/common/sys/intrinsics.h b/common/sys/intrinsics.h index fb6610605d..d3851db0c1 100644 --- a/common/sys/intrinsics.h +++ b/common/sys/intrinsics.h @@ -13,6 +13,37 @@ #include "../simd/arm/emulation.h" #else #include +#if defined(__powerpc64__) && defined(__clang__) +__forceinline unsigned int _mm_getcsr() { return 0; } +__forceinline void _mm_setcsr(unsigned int) {} +__forceinline int _mm_popcnt_u32(unsigned int v) { return __builtin_popcount(v); } +__forceinline long long _mm_popcnt_u64(unsigned long long v) { return __builtin_popcountll(v); } +__forceinline __m128i _mm_stream_load_si128(__m128i* p) { return _mm_load_si128(p); } +__forceinline __m128 _mm_dp_ps(__m128 a, __m128 b, const int imm) { + const __m128i hi = _mm_set_epi32((imm & 0x80) ? -1 : 0, (imm & 0x40) ? -1 : 0, (imm & 0x20) ? -1 : 0, (imm & 0x10) ? -1 : 0); + const __m128i lo = _mm_set_epi32((imm & 0x08) ? -1 : 0, (imm & 0x04) ? -1 : 0, (imm & 0x02) ? -1 : 0, (imm & 0x01) ? -1 : 0); + __m128 p = _mm_and_ps(_mm_mul_ps(a, b), _mm_castsi128_ps(hi)); + p = _mm_hadd_ps(p, p); + p = _mm_hadd_ps(p, p); + return _mm_and_ps(p, _mm_castsi128_ps(lo)); +} +__forceinline __m128 _mm_insert_ps(__m128 a, __m128 b, const int imm) { + float ta[4], tb[4]; + _mm_storeu_ps(ta, a); + _mm_storeu_ps(tb, b); + ta[(imm >> 4) & 3] = tb[(imm >> 6) & 3]; + for (int i = 0; i < 4; i++) if (imm & (1 << i)) ta[i] = 0.0f; + return _mm_loadu_ps(ta); +} +#define _MM_MASK_DENORM 0x0100 +#define _MM_MASK_DIV_ZERO 0x0200 +#define _MM_MASK_MASK 0x1f80 +#define _MM_FLUSH_ZERO_ON 0x8000 +#define _MM_DENORMALS_ZERO_ON 0x0040 +#define _MM_DENORMALS_ZERO_OFF 0x0000 +#define _MM_DENORMALS_ZERO_MASK 0x0040 +#define _MM_SET_EXCEPTION_MASK(x) _mm_setcsr((_mm_getcsr() & ~_MM_MASK_MASK) | (x)) +#endif #if defined(__EMSCRIPTEN__) #include "../simd/wasm/emulation.h" #endif diff --git a/common/sys/platform.h b/common/sys/platform.h index 73bb412aa8..776a3b8277 100644 --- a/common/sys/platform.h +++ b/common/sys/platform.h @@ -57,12 +57,18 @@ #define __X86_ASM__ #endif +#if defined(__powerpc64__) +#define NO_WARN_X86_INTRINSICS +#define __SSE__ +#define __SSE2__ +#endif + /* detect 64 bit platform */ #if defined(__aarch64__) || defined(_M_ARM64) #define EMBREE_ARM64 #endif -#if defined(__X86_64__) || defined(EMBREE_ARM64) +#if defined(__X86_64__) || defined(EMBREE_ARM64) || defined(__powerpc64__) #define __64BIT__ #endif diff --git a/common/sys/sysinfo.cpp b/common/sys/sysinfo.cpp index 81e6cd23d8..843d9f1e1a 100644 --- a/common/sys/sysinfo.cpp +++ b/common/sys/sysinfo.cpp @@ -429,6 +429,13 @@ namespace embree #endif return cpu_features; +#elif defined(__powerpc64__) + + int cpu_features = CPU_FEATURE_SSE|CPU_FEATURE_SSE2|CPU_FEATURE_SSE3|CPU_FEATURE_SSSE3; + cpu_features |= CPU_FEATURE_SSE41|CPU_FEATURE_SSE42|CPU_FEATURE_POPCNT; + cpu_features |= CPU_FEATURE_XMM_ENABLED; + return cpu_features; + #elif defined(__ARM_NEON) || defined(EMBREE_ARM64) || defined(__EMSCRIPTEN__) int cpu_features = CPU_FEATURE_NEON|CPU_FEATURE_SSE|CPU_FEATURE_SSE2; From 586d44bfbcb6d769afc288d3ae8936abdddd3610 Mon Sep 17 00:00:00 2001 From: Piotr Kubaj Date: Mon, 21 Sep 2026 10:45:31 +0200 Subject: [PATCH 2/2] PowerPC64: honor -mcpu, fix the standalone build and add ISPC support Follow-up after building and testing embree standalone (the first commit was only exercised through the ANARI SDK's bundled copy): - Take a -mcpu= from CMAKE_CXX_FLAGS into the ISA flags instead of always forcing -mcpu=power8, so that e.g. -mcpu=power9 is not downgraded. power8 remains the default; the x86 intrinsic wrappers need it. - The AVX/AVX2/AVX-512/APX compiler probes pass trivially without any x86 flags, which made the build try to compile the AVX kernels. Switch them off explicitly. - The tutorials need _MM_SET_FLUSH_ZERO_MODE, which clang's wrappers do not provide. - ISPC 1.31.0 added ppc64le with VSX targets: use vsx-i32x4 (ISPC only allows one VSX variant per object) and --arch=ppc64le for the ISPC tutorials. Tested as a backport to embree 4.4.1 with clang 19 on FreeBSD powerpc64le and powerpc64 (big-endian): embree_verify passes (112 test groups, 0 failures) on both, and the tutorials render identically through the C++ and the ISPC 1.31.0 paths on powerpc64le. --- CMakeLists.txt | 15 +++++++++++++-- common/cmake/clang.cmake | 9 +++++++-- common/cmake/ispc.cmake | 2 ++ common/sys/intrinsics.h | 2 ++ 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c25a66c854..c04b71e0a7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -391,6 +391,13 @@ IF (EMBREE_MAX_ISA STREQUAL "NONE") TRY_COMPILE(COMPILER_SUPPORTS_AVX2 "${CMAKE_BINARY_DIR}" "${PROJECT_SOURCE_DIR}/common/cmake/check_isa.cpp" COMPILE_DEFINITIONS ${FLAGS_AVX2}) TRY_COMPILE(COMPILER_SUPPORTS_AVX512 "${CMAKE_BINARY_DIR}" "${PROJECT_SOURCE_DIR}/common/cmake/check_isa.cpp" COMPILE_DEFINITIONS ${FLAGS_AVX512}) TRY_COMPILE(COMPILER_SUPPORTS_APX "${CMAKE_BINARY_DIR}" "${PROJECT_SOURCE_DIR}/common/cmake/check_isa.cpp" COMPILE_DEFINITIONS ${FLAGS_APX}) + IF (EMBREE_PPC64) + # the probes pass trivially without any x86 flags + SET(COMPILER_SUPPORTS_AVX OFF) + SET(COMPILER_SUPPORTS_AVX2 OFF) + SET(COMPILER_SUPPORTS_AVX512 OFF) + SET(COMPILER_SUPPORTS_APX OFF) + ENDIF () OPTION(EMBREE_ISA_SSE2 "Enables SSE2 ISA." ON) OPTION(EMBREE_ISA_SSE42 "Enables SSE4.2 ISA." ON) @@ -566,6 +573,10 @@ IF (EMBREE_ARM) ENDIF() ENDIF() +IF (EMBREE_PPC64) + LIST(APPEND ISPC_TARGETS "vsx-i32x4") +ENDIF() + IF (EMBREE_ISA_NEON) SET(EMBREE_ISA_SSE2 ON) ENDIF() @@ -579,7 +590,7 @@ ENDIF() IF (EMBREE_ISA_SSE2) ADD_DEFINITIONS(-DEMBREE_TARGET_SSE2) - IF (NOT EMBREE_ARM) + IF (NOT EMBREE_ARM AND NOT EMBREE_PPC64) LIST(APPEND ISPC_TARGETS "sse2") ENDIF() IF(NOT FLAGS_LOWEST) @@ -590,7 +601,7 @@ ENDIF() IF (EMBREE_ISA_SSE42) ADD_DEFINITIONS(-DEMBREE_TARGET_SSE42) - IF (NOT EMBREE_ARM) + IF (NOT EMBREE_ARM AND NOT EMBREE_PPC64) LIST(APPEND ISPC_TARGETS "sse4") ENDIF() IF(NOT FLAGS_LOWEST) diff --git a/common/cmake/clang.cmake b/common/cmake/clang.cmake index f13fc019d0..1ba7ec1faf 100644 --- a/common/cmake/clang.cmake +++ b/common/cmake/clang.cmake @@ -22,8 +22,13 @@ IF (EMBREE_ARM) SET(FLAGS_AVX2 "-D__AVX2__ -D__AVX__ -D__SSE4_2__ -D__SSE4_1__ -D__BMI__ -D__BMI2__ -D__LZCNT__") ENDIF () ELSEIF (EMBREE_PPC64) - SET(FLAGS_SSE2 "-D__SSE__ -D__SSE2__ -mcpu=power8") - SET(FLAGS_SSE42 "-D__SSE4_2__ -D__SSE4_1__ -mcpu=power8") + # the x86 intrinsic wrappers need POWER8, honor a newer -mcpu from the user + STRING(REGEX MATCH "-mcpu=[^ ]+" FLAGS_PPC64_CPU "${CMAKE_CXX_FLAGS}") + IF (NOT FLAGS_PPC64_CPU) + SET(FLAGS_PPC64_CPU "-mcpu=power8") + ENDIF () + SET(FLAGS_SSE2 "-D__SSE__ -D__SSE2__ ${FLAGS_PPC64_CPU}") + SET(FLAGS_SSE42 "-D__SSE4_2__ -D__SSE4_1__ ${FLAGS_PPC64_CPU}") ELSE () # for `thread` keyword _SET_IF_EMPTY(FLAGS_SSE2 "-msse -msse2 -mno-sse4.2") diff --git a/common/cmake/ispc.cmake b/common/cmake/ispc.cmake index 7a4abaeb85..957dd66de9 100644 --- a/common/cmake/ispc.cmake +++ b/common/cmake/ispc.cmake @@ -75,6 +75,8 @@ MACRO (ISPC_COMPILE) IF (CMAKE_SIZEOF_VOID_P EQUAL 8) IF (${CMAKE_SYSTEM_PROCESSOR} MATCHES "arm64|aarch64") SET(ISPC_ARCHITECTURE "aarch64") + ELSEIF (${CMAKE_SYSTEM_PROCESSOR} MATCHES "^(powerpc|ppc)64le") + SET(ISPC_ARCHITECTURE "ppc64le") ELSE() SET(ISPC_ARCHITECTURE "x86-64") ENDIF() diff --git a/common/sys/intrinsics.h b/common/sys/intrinsics.h index d3851db0c1..6b58e5ecc3 100644 --- a/common/sys/intrinsics.h +++ b/common/sys/intrinsics.h @@ -38,11 +38,13 @@ __forceinline __m128 _mm_insert_ps(__m128 a, __m128 b, const int imm) { #define _MM_MASK_DENORM 0x0100 #define _MM_MASK_DIV_ZERO 0x0200 #define _MM_MASK_MASK 0x1f80 +#define _MM_FLUSH_ZERO_MASK 0x8000 #define _MM_FLUSH_ZERO_ON 0x8000 #define _MM_DENORMALS_ZERO_ON 0x0040 #define _MM_DENORMALS_ZERO_OFF 0x0000 #define _MM_DENORMALS_ZERO_MASK 0x0040 #define _MM_SET_EXCEPTION_MASK(x) _mm_setcsr((_mm_getcsr() & ~_MM_MASK_MASK) | (x)) +#define _MM_SET_FLUSH_ZERO_MODE(x) _mm_setcsr((_mm_getcsr() & ~_MM_FLUSH_ZERO_MASK) | (x)) #endif #if defined(__EMSCRIPTEN__) #include "../simd/wasm/emulation.h"