diff --git a/emcc.py b/emcc.py index faa017c62dcba..7d1097c908c09 100755 --- a/emcc.py +++ b/emcc.py @@ -1407,6 +1407,8 @@ def has_c_source(args): # We add these to the user's flags (newargs), but not when building .s or .S assembly files cflags = shared.get_cflags(newargs) + newargs = [x for x in newargs if x not in ['-msse']] + if not shared.Settings.STRICT: # The preprocessor define EMSCRIPTEN is deprecated. Don't pass it to code # in strict mode. Code should use the define __EMSCRIPTEN__ instead. diff --git a/site/source/docs/porting/simd.rst b/site/source/docs/porting/simd.rst index 92ce5da17c1f7..f17d7ff486467 100644 --- a/site/source/docs/porting/simd.rst +++ b/site/source/docs/porting/simd.rst @@ -1,5 +1,8 @@ .. Porting SIMD code: +.. role:: raw-html(raw) + :format: html + ======================================= Porting SIMD code targeting WebAssembly ======================================= @@ -20,14 +23,270 @@ WebAssembly SIMD is not supported when using the Fastcomp backend. Limitations and behavioral differences ====================================== -When porting native SIMD code, it should be noted that because of portability concerns, the WebAssembly SIMD proposal does not expose the full native instruction sets. In particular the following changes exist: +When porting native SIMD code, it should be noted that because of portability concerns, the WebAssembly SIMD specification does not expose the full native instruction sets. In particular the following changes exist: - Emscripten does not support x86 or any other native inline SIMD assembly or building .s assembly files, so all code should be written to use SIMD intrinsic functions or compiler vector extensions. - WebAssembly SIMD does not have control over managing floating point rounding modes or handling denormals. - - Cache line prefetch instructions are not available, and calls to these functions will compile, but be treated as no-ops. + - Cache line prefetch instructions are not available, and calls to these functions will compile, but are treated as no-ops. - Asymmetric memory fence operations are not available, but will be implemented as fully synchronous memory fences when SharedArrayBuffer is enabled (-s USE_PTHREADS=1) or as no-ops when multithreading is not enabled (default, -s USE_PTHREADS=0). SIMD-related bug reports are tracked in the `Emscripten bug tracker with the label SIMD `_. + +===================================================== +Compiling SIMD code targeting x86 SSE instruction set +===================================================== + +Emscripten supports compiling existing x86 SSE utilizing codebases by passing the `-msse` directive to the compiler, and including the header ``. + +Currently only the SSE1 instruction set is supported. + +The following table highlights the performance landscape that can be expected from the different SSE1 instrinsics. Even if you are directly targeting the native Wasm SIMD opcodes via wasm_simd128.h header, this table can be useful for understanding the performance limitations that the Wasm SIMD specification has when running on x86 hardware. + +For detailed information on each SSE intrinsic function, visit the excellent `Intel Intrinsics Guide on SSE1 `_. + +The following legend is used to highlight the expected performance of various instructions: + - ✅ Wasm SIMD has a native opcode that matches the x86 SSE instruction, should yield native performance + - 💡 while the Wasm SIMD spec does not provide a proper performance guarantee, given a suitably smart enough compiler and a runtime VM path, this intrinsic should be able to generate the identical native SSE instruction. + - 🟡 there is some information missing (e.g. type or alignment information) for a Wasm VM to be guaranteed to be able to reconstruct the intended x86 SSE opcode. This might cause a penalty depending on the target CPU hardware family, especially on older CPU generations. + - ⚠️ the underlying x86 SSE instruction is not available, but it is emulated via at most few other Wasm SIMD instructions, causing a small penalty. + - ❌ the underlying x86 SSE instruction is not exposed by the Wasm SIMD specification, so it must be emulated via a slow path, e.g. a sequence of several slower SIMD instructions, or a scalar implementation. + - 💣 the underlying x86 SSE opcode is not available in Wasm SIMD, and the implementation must resort to such a slow emulated path, that a workaround rethinking the algorithm at a higher level is advised. + - 💭 the given SSE intrinsic is available to let applications compile, but does nothing. + - ⚫ the given SSE intrinsic is not available. Referencing the intrinsic will cause a compiler error. + +Certain intrinsics in the table below are marked "virtual". This means that there does not actually exist a native x86 SSE instruction set opcode to implement them, but native compilers offer the function as a convenience. Different compilers might generate a different instruction sequence for these. + +.. list-table:: x86 SSE intrinsics available via #include + :widths: 20 30 + :header-rows: 1 + + * - Intrinsic name + - WebAssembly SIMD support + * - _mm_set_ps + - ✅ wasm_f32x4_make + * - _mm_setr_ps + - ✅ wasm_f32x4_make + * - _mm_set_ss + - ⚠️ emulated with wasm_f32x4_make + * - _mm_set_ps1 (_mm_set1_ps) + - ✅ wasm_f32x4_splat + * - _mm_setzero_ps + - 💡 emulated with wasm_f32x4_const(0) + * - _mm_load_ps + - 🟡 wasm_v128_load. VM must guess type. :raw-html:`
` Unaligned load on x86 CPUs. + * - _mm_loadl_pi + - ❌ No Wasm SIMD support. :raw-html:`
` Emulated with scalar loads + shuffle. + * - _mm_loadh_pi + - ❌ No Wasm SIMD support. :raw-html:`
` Emulated with scalar loads + shuffle. + * - _mm_loadr_ps + - 💡 Virtual. Simd load + shuffle. + * - _mm_loadu_ps + - 🟡 wasm_v128_load. VM must guess type. + * - _mm_load_ps1 (_mm_load1_ps) + - 🟡 Virtual. Simd load + shuffle. + * - _mm_load_ss + - ❌ emulated with wasm_f32x4_make + * - _mm_storel_pi + - ❌ scalar stores + * - _mm_storeh_pi + - ❌ shuffle + scalar stores + * - _mm_store_ps + - 🟡 wasm_v128_store. VM must guess type. :raw-html:`
` Unaligned store on x86 CPUs. + * - _mm_stream_ps + - 🟡 wasm_v128_store. VM must guess type. + * - _mm_prefetch + - 💭 No-op. + * - _mm_sfence + - ⚠️ A full barrier in multithreaded builds. + * - _mm_shuffle_ps + - 🟡 wasm_v32x4_shuffle. VM must guess type. + * - _mm_storer_ps + - 💡 Virtual. Shuffle + Simd store. + * - _mm_store_ps1 (_mm_store1_ps) + - 🟡 Virtual. Emulated with shuffle. :raw-html:`
` Unaligned store on x86 CPUs. + * - _mm_store_ss + - 💡 emulated with scalar store + * - _mm_storeu_ps + - 🟡 wasm_v128_store. VM must guess type. + * - _mm_storeu_si16 + - 💡 emulated with scalar store + * - _mm_storeu_si64 + - 💡 emulated with scalar store + * - _mm_movemask_ps + - 💣 No Wasm SIMD support. Emulated in scalar. `simd/#131 `_ + * - _mm_move_ss + - 💡 emulated with a shuffle + * - _mm_add_ps + - ✅ wasm_f32x4_add + * - _mm_add_ss + - ⚠️ emulated with a shuffle + * - _mm_sub_ps + - ✅ wasm_f32x4_sub + * - _mm_sub_ss + - ⚠️ emulated with a shuffle + * - _mm_mul_ps + - ✅ wasm_f32x4_mul + * - _mm_mul_ss + - ⚠️ emulated with a shuffle + * - _mm_div_ps + - ✅ wasm_f32x4_div + * - _mm_div_ss + - ⚠️ emulated with a shuffle + * - _mm_min_ps + - TODO: pmin once it works + * - _mm_min_ss + - ⚠️ emulated with a shuffle + * - _mm_max_ps + - TODO: pmax once it works + * - _mm_max_ss + - ⚠️ emulated with a shuffle + * - _mm_rcp_ps + - ❌ No Wasm SIMD support. :raw-html:`
` Emulated with full precision div. `simd/#3 `_ + * - _mm_rcp_ss + - ❌ No Wasm SIMD support. :raw-html:`
` Emulated with full precision div+shuffle `simd/#3 `_ + * - _mm_sqrt_ps + - ✅ wasm_f32x4_sqrt + * - _mm_sqrt_ss + - ⚠️ emulated with a shuffle + * - _mm_rsqrt_ps + - ❌ No Wasm SIMD support. :raw-html:`
` Emulated with full precision div+sqrt. `simd/#3 `_ + * - _mm_rsqrt_ss + - ❌ No Wasm SIMD support. :raw-html:`
` Emulated with full precision div+sqrt+shuffle. `simd/#3 `_ + * - _mm_unpackhi_ps + - 💡 emulated with a shuffle + * - _mm_unpacklo_ps + - 💡 emulated with a shuffle + * - _mm_movehl_ps + - 💡 emulated with a shuffle + * - _mm_movelh_ps + - 💡 emulated with a shuffle + * - _MM_TRANSPOSE4_PS + - 💡 emulated with a shuffle + * - _mm_cmplt_ps + - ✅ wasm_f32x4_lt + * - _mm_cmplt_ss + - ⚠️ emulated with a shuffle + * - _mm_cmple_ps + - ✅ wasm_f32x4_le + * - _mm_cmple_ss + - ⚠️ emulated with a shuffle + * - _mm_cmpeq_ps + - ✅ wasm_f32x4_eq + * - _mm_cmpeq_ss + - ⚠️ emulated with a shuffle + * - _mm_cmpge_ps + - ✅ wasm_f32x4_ge + * - _mm_cmpge_ss + - ⚠️ emulated with a shuffle + * - _mm_cmpgt_ps + - ✅ wasm_f32x4_gt + * - _mm_cmpgt_ss + - ⚠️ emulated with a shuffle + * - _mm_cmpord_ps + - ❌ emulated with 2xcmp+and + * - _mm_cmpord_ss + - ❌ emulated with 2xcmp+and+shuffle + * - _mm_cmpunord_ps + - ❌ emulated with 2xcmp+or + * - _mm_cmpunord_ss + - ❌ emulated with 2xcmp+or+shuffle + * - _mm_and_ps + - 🟡 wasm_v128_and. VM must guess type. + * - _mm_andnot_ps + - 🟡 wasm_v128_andnot. VM must guess type. + * - _mm_or_ps + - 🟡 wasm_v128_or. VM must guess type. + * - _mm_xor_ps + - 🟡 wasm_v128_xor. VM must guess type. + * - _mm_cmpneq_ps + - ✅ wasm_f32x4_ne + * - _mm_cmpneq_ss + - ⚠️ emulated with a shuffle + * - _mm_cmpnge_ps + - ⚠️ emulated with not+ge + * - _mm_cmpnge_ss + - ⚠️ emulated with not+ge+shuffle + * - _mm_cmpngt_ps + - ⚠️ emulated with not+gt + * - _mm_cmpngt_ss + - ⚠️ emulated with not+gt+shuffle + * - _mm_cmpnle_ps + - ⚠️ emulated with not+le + * - _mm_cmpnle_ss + - ⚠️ emulated with not+le+shuffle + * - _mm_cmpnlt_ps + - ⚠️ emulated with not+lt + * - _mm_cmpnlt_ss + - ⚠️ emulated with not+lt+shuffle + * - _mm_comieq_ss + - ❌ scalarized + * - _mm_comige_ss + - ❌ scalarized + * - _mm_comigt_ss + - ❌ scalarized + * - _mm_comile_ss + - ❌ scalarized + * - _mm_comilt_ss + - ❌ scalarized + * - _mm_comineq_ss + - ❌ scalarized + * - _mm_ucomieq_ss + - ❌ scalarized + * - _mm_ucomige_ss + - ❌ scalarized + * - _mm_ucomigt_ss + - ❌ scalarized + * - _mm_ucomile_ss + - ❌ scalarized + * - _mm_ucomilt_ss + - ❌ scalarized + * - _mm_ucomineq_ss + - ❌ scalarized + * - _mm_cvtsi32_ss (_mm_cvt_si2ss) + - ❌ scalarized + * - _mm_cvtss_si32 (_mm_cvt_ss2si) + - 💣 scalar with complex emulated semantics + * - _mm_cvttss_si32 (_mm_cvtt_ss2si) + - 💣 scalar with complex emulated semantics + * - _mm_cvtsi64_ss + - ❌ scalarized + * - _mm_cvtss_si64 + - 💣 scalar with complex emulated semantics + * - _mm_cvttss_si64 + - 💣 scalar with complex emulated semantics + * - _mm_cvtss_f32 + - 💡 scalar get + * - _mm_malloc + - ✅ Allocates memory with specified alignment. + * - _mm_free + - ✅ Aliases to free(). + * - _MM_GET_EXCEPTION_MASK + - ✅ Always returns all exceptions masked (0x1f80). + * - _MM_GET_EXCEPTION_STATE + - ❌ Exception state is not tracked. Always returns 0. + * - _MM_GET_FLUSH_ZERO_MODE + - ✅ Always returns _MM_FLUSH_ZERO_OFF. + * - _MM_GET_ROUNDING_MODE + - ✅ Always returns _MM_ROUND_NEAREST. + * - _mm_getcsr + - ✅ Always returns _MM_FLUSH_ZERO_OFF :raw-html:`
` | _MM_ROUND_NEAREST | all exceptions masked (0x1f80). + * - _MM_SET_EXCEPTION_MASK + - ⚫ Not available. Fixed to all exceptions masked. + * - _MM_SET_EXCEPTION_STATE + - ⚫ Not available. Fixed to zero/clear state. + * - _MM_SET_FLUSH_ZERO_MODE + - ⚫ Not available. Fixed to _MM_FLUSH_ZERO_OFF. + * - _MM_SET_ROUNDING_MODE + - ⚫ Not available. Fixed to _MM_ROUND_NEAREST. + * - _mm_setcsr + - ⚫ Not available. + * - _mm_undefined_ps + - ✅ Virtual + +⚫ The following extensions that SSE1 instruction set brought to 64-bit wide MMX registers are not available: + - _mm_avg_pu8, _mm_avg_pu16, _mm_cvt_pi2ps, _mm_cvt_ps2pi, _mm_cvt_pi16_ps, _mm_cvt_pi32_ps, _mm_cvt_pi32x2_ps, _mm_cvt_pi8_ps, _mm_cvt_ps_pi16, _mm_cvt_ps_pi32, _mm_cvt_ps_pi8, _mm_cvt_pu16_ps, _mm_cvt_pu8_ps, _mm_cvtt_ps2pi, _mm_cvtt_pi16_ps, _mm_cvttps_pi32, _mm_extract_pi16, _mm_insert_pi16, _mm_maskmove_si64, _m_maskmovq, _mm_max_pi16, _mm_max_pu8, _mm_min_pi16, _mm_min_pu8, _mm_movemask_pi8, _mm_mulhi_pu16, _m_pavgb, _m_pavgw, _m_pextrw, _m_pinsrw, _m_pmaxsw, _m_pmaxub, _m_pminsw, _m_pminub, _m_pmovmskb, _m_pmulhuw, _m_psadbw, _m_pshufw, _mm_sad_pu8, _mm_shuffle_pi16 and _mm_stream_pi. + +Any code referencing these intrinsics will not compile. \ No newline at end of file diff --git a/system/include/SSE/xmmintrin.h b/system/include/SSE/xmmintrin.h new file mode 100644 index 0000000000000..5602490064188 --- /dev/null +++ b/system/include/SSE/xmmintrin.h @@ -0,0 +1,713 @@ +#ifndef __emscripten_xmmintrin_h__ +#define __emscripten_xmmintrin_h__ + +#include + +#include +#include + +#ifndef __SSE__ +#error "SSE instruction set not enabled" +#endif + +// Emscripten SIMD support doesn't support MMX/float32x2/__m64. +// However, we support loading and storing 2-vectors, so +// treat "__m64 *" as "void *" for that purpose. +typedef float __m64 __attribute__((__vector_size__(16), __aligned__(16))); +typedef v128_t __m128; +typedef v128_t __m128i; + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_set_ps(float __z, float __y, float __x, float __w) +{ + return wasm_f32x4_make(__w, __x, __y, __z); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_setr_ps(float __z, float __y, float __x, float __w) +{ + return wasm_f32x4_make(__z, __y, __x, __w); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_set_ss(float __w) +{ + return wasm_f32x4_make(__w, 0, 0, 0); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_set_ps1(float __w) +{ + return wasm_f32x4_splat(__w); +} + +#define _mm_set1_ps _mm_set_ps1 + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_setzero_ps(void) +{ + return wasm_f32x4_const(0.f, 0.f, 0.f, 0.f); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_load_ps(const float *__p) +{ + return *(__m128*)__p; +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_loadl_pi(__m128 __a, const void /*__m64*/ *__p) +{ + return __builtin_shufflevector(wasm_f32x4_make(((float*)__p)[0], ((float*)__p)[1], 0.f, 0.f), __a, 0, 1, 6, 7); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_loadh_pi(__m128 __a, const void /*__m64*/ *__p) +{ + return __builtin_shufflevector(__a, wasm_f32x4_make(((float*)__p)[0], ((float*)__p)[1], 0.f, 0.f), 0, 1, 4, 5); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_loadr_ps(const float *__p) +{ + __m128 __v = _mm_load_ps(__p); + return __builtin_shufflevector(__v, __v, 3, 2, 1, 0); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_loadu_ps(const float *__p) +{ + return wasm_v128_load(__p); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_load_ps1(const float *__p) +{ + return wasm_v32x4_load_splat(__p); +} +#define _mm_load1_ps _mm_load_ps1 + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_load_ss(const float *__p) +{ + return wasm_f32x4_make(*__p, 0.f, 0.f, 0.f); +} + +static __inline__ void __attribute__((__always_inline__)) +_mm_storel_pi(__m64 *__p, __m128 __a) +{ + *__p = (__m64)__a; +} + +static __inline__ void __attribute__((__always_inline__)) +_mm_storeh_pi(__m64 *__p, __m128 __a) +{ + *__p = (__m64)__builtin_shufflevector(__a, __a, 6, 7, 2, 3); // _mm_movehl_ps pattern +} + +static __inline__ void __attribute__((__always_inline__)) +_mm_store_ps(float *__p, __m128 __a) +{ + *(__m128 *)__p = __a; +} +// No NTA cache hint available. +#define _mm_stream_ps _mm_store_ps + +#define _MM_HINT_T0 3 +#define _MM_HINT_T1 2 +#define _MM_HINT_T2 1 +#define _MM_HINT_NTA 0 +// No prefetch available, dummy it out. +static __inline__ void __attribute__((__always_inline__)) +_mm_prefetch(const void *__p, int __i) +{ + ((void)__p); + ((void)__i); +} + +static __inline__ void __attribute__((__always_inline__)) +_mm_sfence(void) +{ + // Wasm/SharedArrayBuffer memory model is sequentially consistent. + // Perhaps a future version of the spec can provide a related fence. + __sync_synchronize(); +} + +#define _MM_SHUFFLE(w, z, y, x) (((w) << 6) | ((z) << 4) | ((y) << 2) | (x)) + +// This is defined as a macro because __builtin_shufflevector requires its +// mask argument to be a compile-time constant. +#define _mm_shuffle_ps(a, b, mask) \ + ((__m128)__builtin_shufflevector((__f32x4)(a), (__f32x4)(b), \ + (((mask) >> 0) & 0x3) + 0, \ + (((mask) >> 2) & 0x3) + 0, \ + (((mask) >> 4) & 0x3) + 4, \ + (((mask) >> 6) & 0x3) + 4)) + +static __inline__ void __attribute__((__always_inline__)) +_mm_storer_ps(float *__p, __m128 __a) +{ + _mm_store_ps(__p, _mm_shuffle_ps(__a, __a, _MM_SHUFFLE(0, 1, 2, 3))); +} + +static __inline__ void __attribute__((__always_inline__)) +_mm_store_ps1(float *__p, __m128 __a) +{ + _mm_store_ps(__p, _mm_shuffle_ps(__a, __a, _MM_SHUFFLE(0, 0, 0, 0))); +} +#define _mm_store1_ps _mm_store_ps1 + +static __inline__ void __attribute__((__always_inline__)) +_mm_store_ss(float *__p, __m128 __a) +{ + struct __unaligned { + float __v; + } __attribute__((__packed__, __may_alias__)); + ((struct __unaligned *)__p)->__v = ((__f32x4)__a)[0]; +} + +static __inline__ void __attribute__((__always_inline__)) +_mm_storeu_ps(float *__p, __m128 __a) +{ + struct __unaligned { + __m128 __v; + } __attribute__((__packed__, __may_alias__)); + ((struct __unaligned *)__p)->__v = __a; +} + +static __inline__ void __attribute__((__always_inline__)) +_mm_storeu_si16(void *__p, __m128i __a) +{ + struct __unaligned { + unsigned short __u; + } __attribute__((__packed__, __may_alias__)); + ((struct __unaligned *)__p)->__u = ((__u16x8)__a)[0]; +} + +static __inline__ void __attribute__((__always_inline__)) +_mm_storeu_si64(void *__p, __m128i __a) +{ + struct __unaligned { + unsigned long long __u; + } __attribute__((__packed__, __may_alias__)); + ((struct __unaligned *)__p)->__u = ((__u64x2)__a)[0]; +} + +static __inline__ int __attribute__((__always_inline__)) +_mm_movemask_ps(__m128 __a) +{ + // TODO: Use .bitmask instruction when available: + // https://github.com/WebAssembly/simd/pull/201 + union { + __m128 __v; + unsigned int __x[4]; + } __attribute__((__packed__, __may_alias__)) __p; + __p.__v = __a; + return (__p.__x[0] >> 31) + | ((__p.__x[1] >> 30) & 2) + | ((__p.__x[2] >> 29) & 4) + | ((__p.__x[3] >> 28) & 8); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_move_ss(__m128 __a, __m128 __b) +{ + return __builtin_shufflevector(__a, __b, 4, 1, 2, 3); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_add_ps(__m128 __a, __m128 __b) +{ + return wasm_f32x4_add(__a, __b); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_add_ss(__m128 __a, __m128 __b) +{ + return _mm_move_ss(__a, _mm_add_ps(__a, __b)); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_sub_ps(__m128 __a, __m128 __b) +{ + return wasm_f32x4_sub(__a, __b); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_sub_ss(__m128 __a, __m128 __b) +{ + return _mm_move_ss(__a, _mm_sub_ps(__a, __b)); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_mul_ps(__m128 __a, __m128 __b) +{ + return wasm_f32x4_mul(__a, __b); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_mul_ss(__m128 __a, __m128 __b) +{ + return _mm_move_ss(__a, _mm_mul_ps(__a, __b)); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_div_ps(__m128 __a, __m128 __b) +{ + return wasm_f32x4_div(__a, __b); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_div_ss(__m128 __a, __m128 __b) +{ + return _mm_move_ss(__a, _mm_div_ps(__a, __b)); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_min_ps(__m128 __a, __m128 __b) +{ +// return wasm_f32x4_pmin(__a, __b); // TODO: Migrate to this, once it works in VMs + return wasm_v128_bitselect(__a, __b, wasm_f32x4_lt(__a, __b)); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_min_ss(__m128 __a, __m128 __b) +{ + return _mm_move_ss(__a, _mm_min_ps(__a, __b)); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_max_ps(__m128 __a, __m128 __b) +{ +// return wasm_f32x4_pmax(__a, __b); // TODO: Migrate to this, once it works in VMs + return wasm_v128_bitselect(__a, __b, wasm_f32x4_gt(__a, __b)); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_max_ss(__m128 __a, __m128 __b) +{ + return _mm_move_ss(__a, _mm_max_ps(__a, __b)); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_rcp_ps(__m128 __a) +{ + return wasm_f32x4_div(_mm_set1_ps(1.0f), __a); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_rcp_ss(__m128 __a) +{ + return _mm_move_ss(__a, _mm_rcp_ps(__a)); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_sqrt_ps(__m128 __a) +{ + return wasm_f32x4_sqrt(__a); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_sqrt_ss(__m128 __a) +{ + return _mm_move_ss(__a, _mm_sqrt_ps(__a)); +} + +#define _mm_rsqrt_ps(__a) _mm_rcp_ps(_mm_sqrt_ps((__a))) + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_rsqrt_ss(__m128 __a) +{ + return _mm_move_ss(__a, _mm_rsqrt_ps(__a)); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_unpackhi_ps(__m128 __a, __m128 __b) +{ + return __builtin_shufflevector(__a, __b, 2, 6, 3, 7); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_unpacklo_ps(__m128 __a, __m128 __b) +{ + return __builtin_shufflevector(__a, __b, 0, 4, 1, 5); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_movehl_ps(__m128 __a, __m128 __b) +{ + return __builtin_shufflevector(__a, __b, 6, 7, 2, 3); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_movelh_ps(__m128 __a, __m128 __b) +{ + return __builtin_shufflevector(__a, __b, 0, 1, 4, 5); +} + +#define _MM_TRANSPOSE4_PS(row0, row1, row2, row3) \ + do { \ + __m128 __row0 = (row0); \ + __m128 __row1 = (row1); \ + __m128 __row2 = (row2); \ + __m128 __row3 = (row3); \ + __m128 __tmp0 = _mm_unpacklo_ps(__row0, __row1); \ + __m128 __tmp1 = _mm_unpackhi_ps(__row0, __row1); \ + __m128 __tmp2 = _mm_unpacklo_ps(__row2, __row3); \ + __m128 __tmp3 = _mm_unpackhi_ps(__row2, __row3); \ + (row0) = _mm_movelh_ps(__tmp0, __tmp2); \ + (row1) = _mm_movehl_ps(__tmp2, __tmp0); \ + (row2) = _mm_movelh_ps(__tmp1, __tmp3); \ + (row3) = _mm_movehl_ps(__tmp3, __tmp1); \ + } while (0) + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_cmplt_ps(__m128 __a, __m128 __b) +{ + return wasm_f32x4_lt(__a, __b); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_cmplt_ss(__m128 __a, __m128 __b) +{ + return _mm_move_ss(__a, _mm_cmplt_ps(__a, __b)); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_cmple_ps(__m128 __a, __m128 __b) +{ + return wasm_f32x4_le(__a, __b); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_cmple_ss(__m128 __a, __m128 __b) +{ + return _mm_move_ss(__a, _mm_cmple_ps(__a, __b)); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_cmpeq_ps(__m128 __a, __m128 __b) +{ + return wasm_f32x4_eq(__a, __b); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_cmpeq_ss(__m128 __a, __m128 __b) +{ + return _mm_move_ss(__a, _mm_cmpeq_ps(__a, __b)); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_cmpge_ps(__m128 __a, __m128 __b) +{ + return wasm_f32x4_ge(__a, __b); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_cmpge_ss(__m128 __a, __m128 __b) +{ + return _mm_move_ss(__a, _mm_cmpge_ps(__a, __b)); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_cmpgt_ps(__m128 __a, __m128 __b) +{ + return wasm_f32x4_gt(__a, __b); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_cmpgt_ss(__m128 __a, __m128 __b) +{ + return _mm_move_ss(__a, _mm_cmpgt_ps(__a, __b)); +} + +static __inline__ __m128 __attribute__((__always_inline__)) _mm_cmpord_ps(__m128 __a, __m128 __b) +{ + return wasm_v128_and(wasm_f32x4_eq(__a, __a), + wasm_f32x4_eq(__b, __b)); +} + +static __inline__ __m128 __attribute__((__always_inline__, __nodebug__)) _mm_cmpord_ss(__m128 __a, __m128 __b) +{ + return _mm_move_ss(__a, _mm_cmpord_ps(__a, __b)); +} + +static __inline__ __m128 __attribute__((__always_inline__, __nodebug__)) _mm_cmpunord_ps(__m128 __a, __m128 __b) +{ + return wasm_v128_or(wasm_f32x4_ne(__a, __a), + wasm_f32x4_ne(__b, __b)); +} + +static __inline__ __m128 __attribute__((__always_inline__, __nodebug__)) _mm_cmpunord_ss(__m128 __a, __m128 __b) +{ + return _mm_move_ss(__a, _mm_cmpunord_ps(__a, __b)); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_and_ps(__m128 __a, __m128 __b) +{ + return wasm_v128_and(__a, __b); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_andnot_ps(__m128 __a, __m128 __b) +{ + return wasm_v128_andnot(__b, __a); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_or_ps(__m128 __a, __m128 __b) +{ + return wasm_v128_or(__a, __b); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_xor_ps(__m128 __a, __m128 __b) +{ + return wasm_v128_xor(__a, __b); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_cmpneq_ps(__m128 __a, __m128 __b) +{ + return wasm_f32x4_ne(__a, __b); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_cmpneq_ss(__m128 __a, __m128 __b) +{ + return _mm_move_ss(__a, _mm_cmpneq_ps(__a, __b)); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_cmpnge_ps(__m128 __a, __m128 __b) +{ + return wasm_v128_not(_mm_cmpge_ps(__a, __b)); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_cmpnge_ss(__m128 __a, __m128 __b) +{ + return _mm_move_ss(__a, _mm_cmpnge_ps(__a, __b)); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_cmpngt_ps(__m128 __a, __m128 __b) +{ + return wasm_v128_not(_mm_cmpgt_ps(__a, __b)); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_cmpngt_ss(__m128 __a, __m128 __b) +{ + return _mm_move_ss(__a, _mm_cmpngt_ps(__a, __b)); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_cmpnle_ps(__m128 __a, __m128 __b) +{ + return wasm_v128_not(_mm_cmple_ps(__a, __b)); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_cmpnle_ss(__m128 __a, __m128 __b) +{ + return _mm_move_ss(__a, _mm_cmpnle_ps(__a, __b)); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_cmpnlt_ps(__m128 __a, __m128 __b) +{ + return wasm_v128_not(_mm_cmplt_ps(__a, __b)); +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_cmpnlt_ss(__m128 __a, __m128 __b) +{ + return _mm_move_ss(__a, _mm_cmpnlt_ps(__a, __b)); +} + +static __inline__ int __attribute__((__always_inline__)) +_mm_comieq_ss(__m128 __a, __m128 __b) +{ + return ((__f32x4)__a)[0] == ((__f32x4)__b)[0]; +} + +static __inline__ int __attribute__((__always_inline__)) +_mm_comige_ss(__m128 __a, __m128 __b) +{ + return ((__f32x4)__a)[0] >= ((__f32x4)__b)[0]; +} + +static __inline__ int __attribute__((__always_inline__)) +_mm_comigt_ss(__m128 __a, __m128 __b) +{ + return ((__f32x4)__a)[0] > ((__f32x4)__b)[0]; +} + +static __inline__ int __attribute__((__always_inline__)) +_mm_comile_ss(__m128 __a, __m128 __b) +{ + return ((__f32x4)__a)[0] <= ((__f32x4)__b)[0]; +} + +static __inline__ int __attribute__((__always_inline__)) +_mm_comilt_ss(__m128 __a, __m128 __b) +{ + return ((__f32x4)__a)[0] < ((__f32x4)__b)[0]; +} + +static __inline__ int __attribute__((__always_inline__)) +_mm_comineq_ss(__m128 __a, __m128 __b) +{ + return ((__f32x4)__a)[0] != ((__f32x4)__b)[0]; +} + +static __inline__ int __attribute__((__always_inline__)) +_mm_ucomieq_ss(__m128 __a, __m128 __b) +{ + return ((__f32x4)__a)[0] == ((__f32x4)__b)[0]; +} + +static __inline__ int __attribute__((__always_inline__)) +_mm_ucomige_ss(__m128 __a, __m128 __b) +{ + return ((__f32x4)__a)[0] >= ((__f32x4)__b)[0]; +} + +static __inline__ int __attribute__((__always_inline__)) +_mm_ucomigt_ss(__m128 __a, __m128 __b) +{ + return ((__f32x4)__a)[0] > ((__f32x4)__b)[0]; +} + +static __inline__ int __attribute__((__always_inline__)) +_mm_ucomile_ss(__m128 __a, __m128 __b) +{ + return ((__f32x4)__a)[0] <= ((__f32x4)__b)[0]; +} + +static __inline__ int __attribute__((__always_inline__)) +_mm_ucomilt_ss(__m128 __a, __m128 __b) +{ + return ((__f32x4)__a)[0] < ((__f32x4)__b)[0]; +} + +static __inline__ int __attribute__((__always_inline__)) +_mm_ucomineq_ss(__m128 __a, __m128 __b) +{ + return ((__f32x4)__a)[0] != ((__f32x4)__b)[0]; +} + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_cvtsi32_ss(__m128 __a, int __b) +{ + __f32x4 __v = (__f32x4)__a; + __v[0] = (float)__b; + return (__m128)__v; +} +#define _mm_cvt_si2ss _mm_cvtsi32_ss + +static __inline__ int __attribute__((__always_inline__)) _mm_cvtss_si32(__m128 __a) +{ + int x = lrint(((__f32x4)__a)[0]); + if (x != 0 || fabsf(((__f32x4)__a)[0]) < 2.f) + return x; + else + return (int)0x80000000; +} +#define _mm_cvt_ss2si _mm_cvtss_si32 + +static __inline__ int __attribute__((__always_inline__)) _mm_cvttss_si32(__m128 __a) +{ + int x = lrint(((__f32x4)__a)[0]); + if (x != 0 || fabsf(((__f32x4)__a)[0]) < 2.f) + return (int)((__f32x4)__a)[0]; + else + return (int)0x80000000; +} +#define _mm_cvtt_ss2si _mm_cvttss_si32 + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_cvtsi64_ss(__m128 __a, long long __b) +{ + __f32x4 __v = (__f32x4)__a; + __v[0] = (float)__b; + return (__m128)__v; +} + +static __inline__ long long __attribute__((__always_inline__)) +_mm_cvtss_si64(__m128 __a) +{ + if (isnan(((__f32x4)__a)[0]) || isinf(((__f32x4)__a)[0])) return 0x8000000000000000LL; + long long x = llrint(((__f32x4)__a)[0]); + if (x != 0xFFFFFFFF00000000ULL && (x != 0 || fabsf(((__f32x4)__a)[0]) < 2.f)) + return x; + else + return 0x8000000000000000LL; +} + +static __inline__ long long __attribute__((__always_inline__)) +_mm_cvttss_si64(__m128 __a) +{ + if (isnan(((__f32x4)__a)[0]) || isinf(((__f32x4)__a)[0])) return 0x8000000000000000LL; + long long x = llrint(((__f32x4)__a)[0]); + if (x != 0xFFFFFFFF00000000ULL && (x != 0 || fabsf(((__f32x4)__a)[0]) < 2.f)) + return (long long)((__f32x4)__a)[0]; + else + return 0x8000000000000000LL; +} + +static __inline__ float __attribute__((__always_inline__)) +_mm_cvtss_f32(__m128 __a) +{ + return (float)((__f32x4)__a)[0]; +} + +#define _mm_malloc(__size, __align) memalign((__align), (__size)) +#define _mm_free free + +static __inline__ __m128 __attribute__((__always_inline__)) +_mm_undefined() +{ + return __m128(); +} + +#define _MM_EXCEPT_MASK 0x003f +#define _MM_EXCEPT_INVALID 0x0001 +#define _MM_EXCEPT_DENORM 0x0002 +#define _MM_EXCEPT_DIV_ZERO 0x0004 +#define _MM_EXCEPT_OVERFLOW 0x0008 +#define _MM_EXCEPT_UNDERFLOW 0x0010 +#define _MM_EXCEPT_INEXACT 0x0020 + +#define _MM_MASK_MASK 0x1f80 +#define _MM_MASK_INVALID 0x0080 +#define _MM_MASK_DENORM 0x0100 +#define _MM_MASK_DIV_ZERO 0x0200 +#define _MM_MASK_OVERFLOW 0x0400 +#define _MM_MASK_UNDERFLOW 0x0800 +#define _MM_MASK_INEXACT 0x1000 + +#define _MM_ROUND_MASK 0x6000 +#define _MM_ROUND_NEAREST 0x0000 +#define _MM_ROUND_DOWN 0x2000 +#define _MM_ROUND_UP 0x4000 +#define _MM_ROUND_TOWARD_ZERO 0x6000 + +#define _MM_FLUSH_ZERO_MASK 0x8000 +#define _MM_FLUSH_ZERO_ON 0x8000 +#define _MM_FLUSH_ZERO_OFF 0x0000 + +static __inline__ int __attribute__((__always_inline__)) _mm_getcsr() +{ + return _MM_MASK_INEXACT | _MM_MASK_DENORM | _MM_MASK_DIV_ZERO | _MM_MASK_OVERFLOW | _MM_MASK_UNDERFLOW | _MM_MASK_INVALID + | _MM_ROUND_NEAREST | _MM_FLUSH_ZERO_OFF; +} + +#define _MM_GET_EXCEPTION_STATE() (_mm_getcsr() & _MM_EXCEPT_MASK) +#define _MM_GET_EXCEPTION_MASK() (_mm_getcsr() & _MM_MASK_MASK) +#define _MM_GET_ROUNDING_MODE() (_mm_getcsr() & _MM_ROUND_MASK) +#define _MM_GET_FLUSH_ZERO_MODE() (_mm_getcsr() & _MM_FLUSH_ZERO_MASK) + +#endif diff --git a/tests/benchmark_sse1.py b/tests/benchmark_sse1.py new file mode 100644 index 0000000000000..dfac603490542 --- /dev/null +++ b/tests/benchmark_sse1.py @@ -0,0 +1,282 @@ +#!/usr/bin/env python +# flake8: noqa +# Copyright 2014 The Emscripten Authors. All rights reserved. +# Emscripten is available under two separate licenses, the MIT license and the +# University of Illinois/NCSA Open Source License. Both these licenses can be +# found in the LICENSE file. + +import tempfile, os, sys, shutil, json, re + +__rootpath__ = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +def path_from_root(*pathelems): + return os.path.join(__rootpath__, *pathelems) +sys.path += [path_from_root('')] +from tools import shared +from tools.shared import PYTHON, WINDOWS, CLANG_CXX, EMCC, PIPE, V8_ENGINE +from tools.shared import Popen + + +temp_dir = tempfile.mkdtemp() + +# System info +system_info = Popen([PYTHON, path_from_root('emrun'), '--system_info'], stdout=PIPE, stderr=PIPE).communicate() + +# Native info +native_info = Popen(['clang', '-v'], stdout=PIPE, stderr=PIPE).communicate() + +# Emscripten info +emscripten_info = Popen([PYTHON, EMCC, '-v'], stdout=PIPE, stderr=PIPE).communicate() + +# Run native build +out_file = os.path.join(temp_dir, 'benchmark_sse1_native') +if WINDOWS: out_file += '.exe' +cmd = [CLANG_CXX] + shared.Building.get_native_building_args() + [path_from_root('tests', 'sse', 'benchmark_sse1.cpp'), '-O3', '-o', out_file] +print 'Building native version of the benchmark:' +print ' '.join(cmd) +build = Popen(cmd, env=shared.Building.get_building_env(native=True)) +out = build.communicate() +if build.returncode != 0: + sys.exit(1) + +native_results = Popen([out_file], stdout=PIPE, stderr=PIPE).communicate() +print native_results[0] + +# Run emscripten build +out_file = os.path.join(temp_dir, 'benchmark_sse1_html.js') +cmd = [PYTHON, EMCC, path_from_root('tests', 'sse', 'benchmark_sse1.cpp'), '-O3', '-msse', '-s', 'SIMD=1', '-s', 'TOTAL_MEMORY=536870912', '-o', out_file] +print 'Building Emscripten version of the benchmark:' +print ' '.join(cmd) +build = Popen(cmd) +out = build.communicate() +if build.returncode != 0: + sys.exit(1) + +cmd = V8_ENGINE + ['--experimental-wasm-simd', os.path.basename(out_file)] +print ' '.join(cmd) +old_dir = os.getcwd() +os.chdir(os.path.dirname(out_file)) +wasm_results = Popen(cmd, stdout=PIPE, stderr=PIPE).communicate() +os.chdir(old_dir) + +if not wasm_results: + raise Exception('Unable to run benchmark in V8!') + +if not wasm_results[0].strip(): + print wasm_results[1] + sys.exit(1) + +print wasm_results[0] +def strip_comments(text): + return re.sub('//.*?\n|/\*.*?\*/', '', text, re.S) +benchmark_results = strip_comments(wasm_results[0]) + +# Strip out unwanted print output. +benchmark_results = benchmark_results[benchmark_results.find('{'):].strip() +if '*************************' in benchmark_results: + benchmark_results = benchmark_results[:benchmark_results.find('*************************')].strip() + +print benchmark_results + +shutil.rmtree(temp_dir) + +native_results = json.loads(native_results[0]) +benchmark_results = benchmark_results[benchmark_results.index('{'):benchmark_results.rindex('}')+1] +wasm_results = json.loads(benchmark_results) + +native_workload = native_results['workload'] +html_workload = wasm_results['workload'] + +html = '''

SSE1 JavaScript Benchmark

+ + +System Info:
+''' + system_info[0].replace('\n', '
') + ''' +Native Clang Compiler:
+''' + native_info[1].replace('\n', '
') + ''' +Emscripten Compiler:
+''' + emscripten_info[0].replace('\n', '
') + +charts_native = {} +charts_html = {} +for result in native_results['results']: + ch = result['chart'] + if ch not in charts_native: charts_native[ch] = [] + charts_native[ch] += [result] +for result in wasm_results['results']: + ch = result['chart'] + if ch not in charts_html: charts_html[ch] = [] + charts_html[ch] += [result] + +def find_result_in_category(results, category): + for result in results: + if result['category'] == category: + return result + return None + +def format_comparison(a, b): + if a <= b: return " {:10.2f}".format(b/a) + 'x FASTER' + elif b != 0: return " {:10.2f}".format(a/b) + 'x SLOWER' + else: return " NaN " + +chartNumber = 0 + +total_time_native_scalar = 0 +total_time_native_simd = 0 +total_time_html_scalar = 0 +total_time_html_simd = 0 + +for chart_name in charts_native.keys(): + # Extract data for each chart. + categories = [] + nativeScalarResults = [] + nativeSimdResults = [] + htmlScalarResults = [] + htmlSimdResults = [] + native_results = charts_native[chart_name] + wasm_results = charts_html[chart_name] + textual_results_native = '

' + textual_results_html = '

' + textual_results_html2 = '

' + textual_results_html3 = '

' + for result in native_results: + categories += ["'" + result['category'] + "'"] + nsc = result['scalar'] + nsi = result['simd'] + nativeScalarResults += [str(nsc)] + nativeSimdResults += [str(nsi)] + html_result = find_result_in_category(wasm_results, result['category']) + textual_results_native += 'Native ' + result['category'] + ': ' + "{:10.4f}".format(nsc) + 'ns -> ' + "{:10.4f}".format(nsi) + 'ns. ' + textual_results_native += 'Native SSE1 is ' + format_comparison(nsi, nsc) + ' than native scalar.        
' + + if html_result is not None: + hsc = html_result['scalar'] + htmlScalarResults += [str(hsc)] + hsi = html_result['simd'] + htmlSimdResults += [str(hsi)] + textual_results_html += 'JS ' + result['category'] + ': ' + "{:10.4f}".format(hsc) + 'ns -> ' + "{:10.4f}".format(hsi) + 'ns. ' + textual_results_html += 'JS SSE1 is ' + format_comparison(hsi, hsc) + ' than JS scalar.        
' + textual_results_html2 += 'JS ' + result['category'] + ': JS scalar is ' + format_comparison(hsc, nsc) + ' than native scalar.        
' + textual_results_html3 += 'JS ' + result['category'] + ': JS SSE1 is ' + format_comparison(hsi, nsi) + ' than native SSE1.        
' + total_time_native_scalar += nsc + total_time_native_simd += nsi + total_time_html_scalar += hsc + total_time_html_simd += hsi + else: + htmlScalarResults += [str(-1)] + htmlSimdResults += [str(-1)] + + chartNumber += 1 + html += '

' + html += '''''' + '
' + textual_results_native + '' + textual_results_html + '
' + textual_results_html2 + '' + textual_results_html3 + '
' + +# Final overall score + +html += '
' +html += '''''' + +html += '' + +open('results_sse1.html', 'w').write(html) +print 'Wrote ' + str(len(html)) + ' bytes to file results_sse1.html.' diff --git a/tests/sse/benchmark_sse1.cpp b/tests/sse/benchmark_sse1.cpp new file mode 100644 index 0000000000000..eabc122afebe1 --- /dev/null +++ b/tests/sse/benchmark_sse1.cpp @@ -0,0 +1,154 @@ +// Copyright 2014 The Emscripten Authors. All rights reserved. +// Emscripten is available under two separate licenses, the MIT license and the +// University of Illinois/NCSA Open Source License. Both these licenses can be +// found in the LICENSE file. + +#include +#include "benchmark_sse1.h" + +int dst_int = 0; + +int main() +{ + printf ("{ \"workload\": %u, \"results\": [\n", N); + assert(N%4 == 0); // Don't care about the tail for now. + float *src = alloc_float_buffer(); + for(int i = 0; i < N; ++i) + src[i] = 1.0f + (float)rand() / (float)RAND_MAX; + float *src2 = alloc_float_buffer(); + for(int i = 0; i < N; ++i) + src2[i] = 1.0f + (float)rand() / (float)RAND_MAX; + float *dst = alloc_float_buffer(); + + float scalarTime; + SETCHART("load"); + START(); + for(int i = 0; i < N; ++i) + dst[i] = src[i]; + ENDSCALAR(checksum_dst(dst), "scalar"); + + LS_TEST("_mm_load_ps", _mm_load_ps, 0, _mm_store_ps, float*, 0, 4); + LS_TEST("_mm_load_ps1", _mm_load_ps1, 1, _mm_store_ps, float*, 0, 4); + LS_TEST("_mm_load_ss", _mm_load_ss, 1, _mm_store_ps, float*, 0, 4); + LS_TEST("_mm_load1_ps", _mm_load1_ps, 1, _mm_store_ps, float*, 0, 4); + + __m128 tempReg = _mm_set_ps(1.f, 2.f, 3.f, 4.f); + LSH_TEST("_mm_loadh_pi", tempReg, _mm_loadh_pi, __m64*, 1, _mm_store_ps, float*, 0, 4); + LSH_TEST("_mm_loadl_pi", tempReg, _mm_loadh_pi, __m64*, 1, _mm_store_ps, float*, 0, 4); + + LS_TEST("_mm_loadr_ps", _mm_loadr_ps, 0, _mm_store_ps, float*, 0, 4); + LS_TEST("_mm_loadu_ps", _mm_loadu_ps, 1, _mm_store_ps, float*, 0, 4); + + SETCHART("set"); + SS_TEST("_mm_set_ps", _mm_set_ps(src[i+2], src[i+1], src[i+5], src[i+0])); + SS_TEST("_mm_set_ps1", _mm_set_ps1(src[i])); + SS_TEST("_mm_set_ss", _mm_set_ss(src[i])); + SS_TEST("_mm_set1_ps", _mm_set1_ps(src[i])); + SS_TEST("_mm_setr_ps", _mm_set_ps(src[i+2], src[i+1], src[i+5], src[i+0])); + SS_TEST("_mm_setzero_ps", _mm_setzero_ps()); + + SETCHART("move"); + SS_TEST("_mm_move_ss", _mm_move_ss(_mm_load_ps(src+i), _mm_load_ps(src2+i))); + SS_TEST("_mm_movehl_ps", _mm_movehl_ps(_mm_load_ps(src+i), _mm_load_ps(src2+i))); + SS_TEST("_mm_movelh_ps", _mm_movelh_ps(_mm_load_ps(src+i), _mm_load_ps(src2+i))); + + SETCHART("movemask"); + START(); for(int i = 0; i < N; i += 4) { int movemask = ((unsigned int)src[i] >> 31) | (((unsigned int)src[i+1] >> 30) & 2) | (((unsigned int)src[i+2] >> 29) & 4) | (((unsigned int)src[i+3] >> 28) & 8); dst_int += movemask; } ENDSCALAR(checksum_dst(&dst_int), "scalar movemask"); + UNARYOP_INT_TEST("_mm_movemask_ps", _mm_movemask_ps(_mm_load_ps(src+i))); + + SETCHART("store"); + LS_TEST("_mm_store_ps", _mm_load_ps, 0, _mm_store_ps, float*, 0, 4); + LS_TEST("_mm_store_ps1", _mm_load_ps, 0, _mm_store_ps1, float*, 0, 4); + LS_TEST("_mm_store_ss", _mm_load_ps, 0, _mm_store_ss, float*, 1, 4); + LS64_TEST("_mm_storeh_pi", _mm_load_ps, 0, _mm_storeh_pi, 1, 4); + LS64_TEST("_mm_storel_pi", _mm_load_ps, 0, _mm_storel_pi, 1, 4); + LS_TEST("_mm_storer_ps", _mm_load_ps, 0, _mm_storer_ps, float*, 0, 4); + LS_TEST("_mm_storeu_ps", _mm_load_ps, 0, _mm_storeu_ps, float*, 1, 4); + LS_TEST("_mm_stream_ps", _mm_load_ps, 0, _mm_stream_ps, float*, 0, 4); + + SETCHART("arithmetic"); + START(); dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; dst[3] = src[3]; for(int i = 0; i < N; ++i) { dst[0] += src2[0]; dst[1] += src2[1]; dst[2] += src2[2]; dst[3] += src2[3]; } ENDSCALAR(checksum_dst(dst), "scalar add"); + BINARYOP_TEST("_mm_add_ps", _mm_add_ps, _mm_load_ps(src), _mm_load_ps(src2)); + BINARYOP_TEST("_mm_add_ss", _mm_add_ss, _mm_load_ps(src), _mm_load_ps(src2)); + START(); dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; dst[3] = src[3]; for(int i = 0; i < N; ++i) { dst[0] /= src2[0]; dst[1] /= src2[1]; dst[2] /= src2[2]; dst[3] /= src2[3]; } ENDSCALAR(checksum_dst(dst), "scalar div"); + BINARYOP_TEST("_mm_div_ps", _mm_div_ps, _mm_load_ps(src), _mm_load_ps(src2)); + BINARYOP_TEST("_mm_div_ss", _mm_div_ss, _mm_load_ps(src), _mm_load_ps(src2)); + START(); dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; dst[3] = src[3]; for(int i = 0; i < N; ++i) { dst[0] *= src2[0]; dst[1] *= src2[1]; dst[2] *= src2[2]; dst[3] *= src2[3]; } ENDSCALAR(checksum_dst(dst), "scalar mul"); + BINARYOP_TEST("_mm_mul_ps", _mm_mul_ps, _mm_load_ps(src), _mm_load_ps(src2)); + BINARYOP_TEST("_mm_mul_ss", _mm_mul_ss, _mm_load_ps(src), _mm_load_ps(src2)); + START(); dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; dst[3] = src[3]; for(int i = 0; i < N; ++i) { dst[0] -= src2[0]; dst[1] -= src2[1]; dst[2] -= src2[2]; dst[3] -= src2[3]; } ENDSCALAR(checksum_dst(dst), "scalar sub"); + BINARYOP_TEST("_mm_sub_ps", _mm_sub_ps, _mm_load_ps(src), _mm_load_ps(src2)); + BINARYOP_TEST("_mm_sub_ss", _mm_sub_ss, _mm_load_ps(src), _mm_load_ps(src2)); + + SETCHART("roots"); + START(); dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; dst[3] = src[3]; for(int i = 0; i < N; ++i) { dst[0] = 1.f / dst[0]; dst[1] = 1.f / dst[1]; dst[2] = 1.f / dst[2]; dst[3] = 1.f / dst[3]; } ENDSCALAR(checksum_dst(dst), "scalar rcp"); + UNARYOP_TEST("_mm_rcp_ps", _mm_rcp_ps, _mm_load_ps(src)); + UNARYOP_TEST("_mm_rcp_ss", _mm_rcp_ss, _mm_load_ps(src)); + + START(); dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; dst[3] = src[3]; for(int i = 0; i < N; ++i) { dst[0] = 1.f / sqrtf(dst[0]); dst[1] = 1.f / sqrtf(dst[1]); dst[2] = 1.f / sqrtf(dst[2]); dst[3] = 1.f / sqrtf(dst[3]); } ENDSCALAR(checksum_dst(dst), "scalar rsqrt"); + UNARYOP_TEST("_mm_rsqrt_ps", _mm_rsqrt_ps, _mm_load_ps(src)); + UNARYOP_TEST("_mm_rsqrt_ss", _mm_rsqrt_ss, _mm_load_ps(src)); + + START(); dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; dst[3] = src[3]; for(int i = 0; i < N; ++i) { dst[0] = sqrtf(dst[0]); dst[1] = sqrtf(dst[1]); dst[2] = sqrtf(dst[2]); dst[3] = sqrtf(dst[3]); } ENDSCALAR(checksum_dst(dst), "scalar sqrt"); + UNARYOP_TEST("_mm_sqrt_ps", _mm_sqrt_ps, _mm_load_ps(src)); + UNARYOP_TEST("_mm_sqrt_ss", _mm_sqrt_ss, _mm_load_ps(src)); + + SETCHART("logical"); + START(); dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; dst[3] = src[3]; for(int i = 0; i < N; ++i) { dst[0] = ucastf(fcastu(dst[0]) & fcastu(src2[0])); dst[1] = ucastf(fcastu(dst[1]) & fcastu(src2[1])); dst[2] = ucastf(fcastu(dst[2]) & fcastu(src2[2])); dst[3] = ucastf(fcastu(dst[3]) & fcastu(src2[3])); } ENDSCALAR(checksum_dst(dst), "scalar and"); + BINARYOP_TEST("_mm_and_ps", _mm_and_ps, _mm_load_ps(src), _mm_load_ps(src2)); + START(); dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; dst[3] = src[3]; for(int i = 0; i < N; ++i) { dst[0] = ucastf((~fcastu(dst[0])) & fcastu(src2[0])); dst[1] = ucastf((~fcastu(dst[1])) & fcastu(src2[1])); dst[2] = ucastf((~fcastu(dst[2])) & fcastu(src2[2])); dst[3] = ucastf((~fcastu(dst[3])) & fcastu(src2[3])); } ENDSCALAR(checksum_dst(dst), "scalar andnot"); + BINARYOP_TEST("_mm_andnot_ps", _mm_andnot_ps, _mm_load_ps(src), _mm_load_ps(src2)); + START(); dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; dst[3] = src[3]; for(int i = 0; i < N; ++i) { dst[0] = ucastf(fcastu(dst[0]) | fcastu(src2[0])); dst[1] = ucastf(fcastu(dst[1]) | fcastu(src2[1])); dst[2] = ucastf(fcastu(dst[2]) | fcastu(src2[2])); dst[3] = ucastf(fcastu(dst[3]) | fcastu(src2[3])); } ENDSCALAR(checksum_dst(dst), "scalar or"); + BINARYOP_TEST("_mm_or_ps", _mm_or_ps, _mm_load_ps(src), _mm_load_ps(src2)); + START(); dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; dst[3] = src[3]; for(int i = 0; i < N; ++i) { dst[0] = ucastf(fcastu(dst[0]) ^ fcastu(src2[0])); dst[1] = ucastf(fcastu(dst[1]) ^ fcastu(src2[1])); dst[2] = ucastf(fcastu(dst[2]) ^ fcastu(src2[2])); dst[3] = ucastf(fcastu(dst[3]) ^ fcastu(src2[3])); } ENDSCALAR(checksum_dst(dst), "scalar xor"); + BINARYOP_TEST("_mm_xor_ps", _mm_xor_ps, _mm_load_ps(src), _mm_load_ps(src2)); + + SETCHART("cmp"); + START(); dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; dst[3] = src[3]; for(int i = 0; i < N; ++i) { dst[0] = (dst[0] == src2[0]) ? ucastf(0xFFFFFFFFU) : 0.f; dst[1] = (dst[1] == src2[1]) ? ucastf(0xFFFFFFFFU) : 0.f; dst[2] = (dst[2] == src2[2]) ? ucastf(0xFFFFFFFFU) : 0.f; dst[3] = (dst[3] == src2[3]) ? ucastf(0xFFFFFFFFU) : 0.f; } ENDSCALAR(checksum_dst(dst), "scalar cmp=="); + BINARYOP_TEST("_mm_cmpeq_ps", _mm_cmpeq_ps, _mm_load_ps(src), _mm_load_ps(src2)); + BINARYOP_TEST("_mm_cmpeq_ss", _mm_cmpeq_ss, _mm_load_ps(src), _mm_load_ps(src2)); + START(); dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; dst[3] = src[3]; for(int i = 0; i < N; ++i) { dst[0] = (dst[0] >= src2[0]) ? ucastf(0xFFFFFFFFU) : 0.f; dst[1] = (dst[1] >= src2[1]) ? ucastf(0xFFFFFFFFU) : 0.f; dst[2] = (dst[2] >= src2[2]) ? ucastf(0xFFFFFFFFU) : 0.f; dst[3] = (dst[3] >= src2[3]) ? ucastf(0xFFFFFFFFU) : 0.f; } ENDSCALAR(checksum_dst(dst), "scalar cmp>="); + BINARYOP_TEST("_mm_cmpge_ps", _mm_cmpge_ps, _mm_load_ps(src), _mm_load_ps(src2)); + BINARYOP_TEST("_mm_cmpge_ss", _mm_cmpge_ss, _mm_load_ps(src), _mm_load_ps(src2)); + START(); dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; dst[3] = src[3]; for(int i = 0; i < N; ++i) { dst[0] = (dst[0] > src2[0]) ? ucastf(0xFFFFFFFFU) : 0.f; dst[1] = (dst[1] > src2[1]) ? ucastf(0xFFFFFFFFU) : 0.f; dst[2] = (dst[2] > src2[2]) ? ucastf(0xFFFFFFFFU) : 0.f; dst[3] = (dst[3] > src2[3]) ? ucastf(0xFFFFFFFFU) : 0.f; } ENDSCALAR(checksum_dst(dst), "scalar cmp>"); + BINARYOP_TEST("_mm_cmpgt_ps", _mm_cmpgt_ps, _mm_load_ps(src), _mm_load_ps(src2)); + BINARYOP_TEST("_mm_cmpgt_ss", _mm_cmpgt_ss, _mm_load_ps(src), _mm_load_ps(src2)); + START(); dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; dst[3] = src[3]; for(int i = 0; i < N; ++i) { dst[0] = (dst[0] <= src2[0]) ? ucastf(0xFFFFFFFFU) : 0.f; dst[1] = (dst[1] <= src2[1]) ? ucastf(0xFFFFFFFFU) : 0.f; dst[2] = (dst[2] <= src2[2]) ? ucastf(0xFFFFFFFFU) : 0.f; dst[3] = (dst[3] <= src2[3]) ? ucastf(0xFFFFFFFFU) : 0.f; } ENDSCALAR(checksum_dst(dst), "scalar cmp<="); + BINARYOP_TEST("_mm_cmple_ps", _mm_cmple_ps, _mm_load_ps(src), _mm_load_ps(src2)); + BINARYOP_TEST("_mm_cmple_ss", _mm_cmple_ss, _mm_load_ps(src), _mm_load_ps(src2)); + START(); dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; dst[3] = src[3]; for(int i = 0; i < N; ++i) { dst[0] = (dst[0] < src2[0]) ? ucastf(0xFFFFFFFFU) : 0.f; dst[1] = (dst[1] < src2[1]) ? ucastf(0xFFFFFFFFU) : 0.f; dst[2] = (dst[2] < src2[2]) ? ucastf(0xFFFFFFFFU) : 0.f; dst[3] = (dst[3] < src2[3]) ? ucastf(0xFFFFFFFFU) : 0.f; } ENDSCALAR(checksum_dst(dst), "scalar cmp<"); + BINARYOP_TEST("_mm_cmplt_ps", _mm_cmplt_ps, _mm_load_ps(src), _mm_load_ps(src2)); + BINARYOP_TEST("_mm_cmplt_ss", _mm_cmplt_ss, _mm_load_ps(src), _mm_load_ps(src2)); + + START(); dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; dst[3] = src[3]; for(int i = 0; i < N; ++i) { dst[0] = (!Isnan(dst[0]) && !Isnan(src2[0])) ? ucastf(0xFFFFFFFFU) : 0.f; dst[1] = (!Isnan(dst[1]) && !Isnan(src2[1])) ? ucastf(0xFFFFFFFFU) : 0.f; dst[2] = (!Isnan(dst[2]) && !Isnan(src2[2])) ? ucastf(0xFFFFFFFFU) : 0.f; dst[3] = (!Isnan(dst[3]) && !Isnan(src2[3])) ? ucastf(0xFFFFFFFFU) : 0.f; } ENDSCALAR(checksum_dst(dst), "scalar cmpord"); + BINARYOP_TEST("_mm_cmpord_ps", _mm_cmpord_ps, _mm_load_ps(src), _mm_load_ps(src2)); + BINARYOP_TEST("_mm_cmpord_ss", _mm_cmpord_ss, _mm_load_ps(src), _mm_load_ps(src2)); + START(); dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; dst[3] = src[3]; for(int i = 0; i < N; ++i) { dst[0] = (Isnan(dst[0]) || Isnan(src2[0])) ? ucastf(0xFFFFFFFFU) : 0.f; dst[1] = (Isnan(dst[1]) || Isnan(src2[1])) ? ucastf(0xFFFFFFFFU) : 0.f; dst[2] = (Isnan(dst[2]) || Isnan(src2[2])) ? ucastf(0xFFFFFFFFU) : 0.f; dst[3] = (Isnan(dst[3]) || Isnan(src2[3])) ? ucastf(0xFFFFFFFFU) : 0.f; } ENDSCALAR(checksum_dst(dst), "scalar cmpunord"); + BINARYOP_TEST("_mm_cmpunord_ps", _mm_cmpunord_ps, _mm_load_ps(src), _mm_load_ps(src2)); + BINARYOP_TEST("_mm_cmpunord_ss", _mm_cmpunord_ss, _mm_load_ps(src), _mm_load_ps(src2)); + + SETCHART("max"); + START(); dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; dst[3] = src[3]; for(int i = 0; i < N; ++i) { dst[0] = Max(dst[0], src2[0]); dst[1] = Max(dst[1], src2[1]); dst[2] = Max(dst[2], src2[2]); dst[3] = Max(dst[3], src2[3]); } ENDSCALAR(checksum_dst(dst), "scalar max"); + BINARYOP_TEST("_mm_max_ps", _mm_max_ps, _mm_load_ps(src), _mm_load_ps(src2)); + BINARYOP_TEST("_mm_max_ss", _mm_max_ss, _mm_load_ps(src), _mm_load_ps(src2)); + START(); dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; dst[3] = src[3]; for(int i = 0; i < N; ++i) { dst[0] = Min(dst[0], src2[0]); dst[1] = Min(dst[1], src2[1]); dst[2] = Min(dst[2], src2[2]); dst[3] = Min(dst[3], src2[3]); } ENDSCALAR(checksum_dst(dst), "scalar min"); + BINARYOP_TEST("_mm_min_ps", _mm_min_ps, _mm_load_ps(src), _mm_load_ps(src2)); + BINARYOP_TEST("_mm_min_ss", _mm_min_ss, _mm_load_ps(src), _mm_load_ps(src2)); + + SETCHART("shuffle"); + START(); dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; dst[3] = src[3]; for(int i = 0; i < N; ++i) { dst[3] = dst[1]; dst[2] = dst[0]; dst[1] = src2[3]; dst[0] = src2[2]; } ENDSCALAR(checksum_dst(dst), "scalar shuffle"); + + START(); + __m128 o0 = _mm_load_ps(src); + __m128 o1 = _mm_load_ps(src2); + for(int i = 0; i < N; i += 4) + o0 = _mm_shuffle_ps(o0, o1, _MM_SHUFFLE(1, 0, 3, 2)); + _mm_store_ps(dst, o0); + END(checksum_dst(dst), "_mm_shuffle_ps"); + + START(); dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; dst[3] = src[3]; for(int i = 0; i < N; ++i) { dst[0] = dst[2]; dst[1] = src2[2]; dst[2] = dst[3]; dst[3] = src2[3]; } ENDSCALAR(checksum_dst(dst), "scalar unpackhi_ps"); + BINARYOP_TEST("_mm_unpackhi_ps", _mm_unpackhi_ps, _mm_load_ps(src), _mm_load_ps(src2)); + START(); dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; dst[3] = src[3]; for(int i = 0; i < N; ++i) { dst[2] = dst[1]; dst[1] = dst[0]; dst[0] = src2[0]; dst[3] = src2[1]; } ENDSCALAR(checksum_dst(dst), "scalar unpacklo_ps"); + BINARYOP_TEST("_mm_unpacklo_ps", _mm_unpacklo_ps, _mm_load_ps(src), _mm_load_ps(src2)); + printf("]}\n"); +} diff --git a/tests/sse/benchmark_sse1.h b/tests/sse/benchmark_sse1.h new file mode 100644 index 0000000000000..4bc268f012610 --- /dev/null +++ b/tests/sse/benchmark_sse1.h @@ -0,0 +1,185 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#ifdef __EMSCRIPTEN__ +#include +#endif + +#if defined(__unix__) && !defined(__EMSCRIPTEN__) // Native build without Emscripten. +#include +#include +#include +#endif + +#ifdef __APPLE__ +#define aligned_alloc(align, size) malloc((size)) +#endif + +#ifdef WIN32 +#include +#define aligned_alloc(align, size) _aligned_malloc((size), (align)) +#endif + +// Scalar horizonal max across four lanes. +float hmax(__m128 m) +{ + float f[4]; + _mm_storeu_ps(f, m); + return fmax(fmax(f[0], f[1]), fmax(f[2], f[3])); +} + +#include "../tick.h" + +const int N = 16*1024*1024; + +tick_t scalarTotalTicks = 0; +tick_t simdTotalTicks = 0; +tick_t scalarTicks = 0; +const char *chartName = ""; +#define SETCHART(x) chartName = (x); + +#define START() \ + do { \ + tick_t start = tick(); + +bool comma=false; +#define END(result, name) \ + tick_t end = tick(); \ + tick_t ticks = end - start; \ + scalarTotalTicks += scalarTicks; \ + simdTotalTicks += ticks; \ + double nsecs = (double)ticks * 1000.0 * 1000.0 * 1000.0 / ticks_per_sec() / N; \ + printf("%s{ \"chart\": \"%s\", \"category\": \"%s\", \"scalar\": %f, \"simd\": %f }\n", comma?",":"", chartName, name, scalarTime, nsecs); \ + comma = true; \ + printf("%s", (result) != 0 ? "Error!" : ""); \ + } while(0) + +#define ENDSCALAR(result, name) \ + tick_t end = tick(); \ + scalarTicks = end - start; \ + scalarTime = (double)scalarTicks * 1000.0 * 1000.0 * 1000.0 / ticks_per_sec() / N; \ + printf("%s", (result) != 0 ? "Error!" : ""); \ + } while(0) + +void Print(__m128 m) +{ + float val[4]; + _mm_storeu_ps(val, m); + fprintf(stderr, "[%g, %g, %g, %g]\n", val[3], val[2], val[1], val[0]); +} + +bool always_true() { return time(NULL) != 0; } // This function always returns true, but the compiler should not know this. + +#ifdef _MSC_VER +#define NOINLINE __declspec(noinline) +#define INLINE __forceinline +#else +#define NOINLINE __attribute__((noinline)) +#define INLINE __inline__ +#endif + +float NOINLINE *alloc_float_buffer() { return always_true() ? (float*)aligned_alloc(16, (N+16)*sizeof(float)) : 0; } +double NOINLINE *alloc_double_buffer() { return always_true() ? (double*)aligned_alloc(16, (N+16)*sizeof(double)) : 0; } + +template +T checksum_dst(T *dst) +{ + if (always_true()) return 0.f; + else + { + T s = 0.f; for(int i = 0; i < N; ++i) s += dst[i]; + return s; + } +} + +uint32_t fcastu(float f) { return *(uint32_t*)&f; } +uint64_t dcastu(double f) { return *(uint64_t*)&f; } +float ucastf(uint32_t t) { return *(float*)&t; } +double ucastd(uint64_t t) { return *(double*)&t; } + +// load-store test +#define LS_TEST(msg, load_instr, load_offset, store_instr, store_ptr_type, store_offset, num_elems_stride) \ + START(); \ + for(int i = 0; i < N; i += num_elems_stride) \ + store_instr((store_ptr_type)dst+store_offset+i, load_instr(src+load_offset+i)); \ + END(checksum_dst(dst), msg); + +// loadh/l - store test +#define LSH_TEST(msg, reg, load_instr, load_ptr_type, load_offset, store_instr, store_ptr_type, store_offset, num_elems_stride) \ + START(); \ + for(int i = 0; i < N; i += num_elems_stride) \ + store_instr((store_ptr_type)dst+store_offset+i, load_instr(reg, (load_ptr_type)(src+load_offset+i))); \ + END(checksum_dst(dst), msg); + +#define LS64_TEST(msg, load_instr, load_offset, store_instr, store_offset, num_elems_stride) \ + START(); \ + for(int i = 0; i < N; i += num_elems_stride) \ + store_instr((__m64*)(dst+store_offset+i), load_instr(src+load_offset+i)); \ + END(checksum_dst(dst), msg); + +// set-store test +#define SS_TEST(msg, set_instr) \ + START(); \ + for(int i = 0; i < N; i += 4) \ + _mm_store_ps((float*)dst+i, set_instr); \ + END(checksum_dst(dst), msg); + +#define UNARYOP_TEST(msg, instr, op0) \ + START(); \ + __m128 o = op0; \ + for(int i = 0; i < N; i += 4) \ + o = instr(o); \ + _mm_store_ps(dst, o); \ + END(checksum_dst(dst), msg); + +#define UNARYOP_INT_TEST(msg, instr) \ + START(); \ + for(int i = 0; i < N; i += 4) \ + dst_int += instr; \ + END(checksum_dst(&dst_int), msg); + +#define BINARYOP_TEST(msg, instr, op0, op1) \ + START(); \ + __m128 o0 = op0; \ + __m128 o1 = op1; \ + for(int i = 0; i < N; i += 4) \ + o0 = instr(o0, o1); \ + _mm_store_ps(dst, o0); \ + END(checksum_dst(dst), msg); + +#define SS_TEST_D(msg, set_instr) \ + START(); \ + for(int i = 0; i < N; i += 2) \ + _mm_store_pd((double*)dst+i, set_instr); \ + END(checksum_dst(dst), msg); + +#define UNARYOP_TEST_D(msg, instr, op0) \ + START(); \ + __m128d o = op0; \ + for(int i = 0; i < N; i += 2) \ + o = instr(o); \ + _mm_store_pd(dst, o); \ + END(checksum_dst(dst), msg); + +#define BINARYOP_TEST_D(msg, instr, op0, op1) \ + START(); \ + __m128d o0 = op0; \ + __m128d o1 = op1; \ + for(int i = 0; i < N; i += 2) \ + o0 = instr(o0, o1); \ + _mm_store_pd(dst, o0); \ + END(checksum_dst(dst), msg); + +#define Max(a,b) ((a) >= (b) ? (a) : (b)) +#define Min(a,b) ((a) <= (b) ? (a) : (b)) + +static INLINE int Isnan(float __f) +{ + return (*(unsigned int*)&__f << 1) > 0xFF000000u; +} diff --git a/tests/sse/test_sse.h b/tests/sse/test_sse.h new file mode 100644 index 0000000000000..97c433cb952d4 --- /dev/null +++ b/tests/sse/test_sse.h @@ -0,0 +1,747 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#ifdef __EMSCRIPTEN__ +#include +#define align1_int emscripten_align1_int +#define align1_int64 emscripten_align1_int64 +#define align1_float emscripten_align1_float +#define align1_double emscripten_align1_double +#else +#define align1_int64 int64_t +#define align1_int int +#define align1_float float +#define align1_double double +#endif + +// Recasts floating point representation of f to an integer. +uint32_t fcastu(float f) { return *(uint32_t*)&f; } +uint64_t dcastu(double f) { return *(uint64_t*)&f; } +float ucastf(uint32_t t) { return *(float*)&t; } +double ucastd(uint64_t t) { return *(double*)&t; } + +// Data used in test. Store them global and access via a getter to confuse optimizer to not "solve" the whole test suite at compile-time, +// so that the operation will actually be performed at runtime, and not at compile-time. (Testing the capacity of the compiler to perform +// SIMD ops at compile-time would be interesting as well, but that's for another test) +float interesting_floats_[] = { -INFINITY, -FLT_MAX, -2.5f, -1.5f, -1.4f, -1.0f, -0.5f, -0.2f, -FLT_MIN, -0.f, 0.f, + 1.401298464e-45f, FLT_MIN, 0.3f, 0.5f, 0.8f, 1.0f, 1.5f, 2.5f, 3.5f, 3.6f, FLT_MAX, INFINITY, NAN, + ucastf(0x01020304), ucastf(0x80000000), ucastf(0x7FFFFFFF), ucastf(0xFFFFFFFF) + }; + +double interesting_doubles_[] = { -INFINITY, -FLT_MAX, -2.5, -1.5, -1.4, -1.0, -0.5, -0.2, -FLT_MIN, -0.0, 0.0, + 1.401298464e-45, FLT_MIN, 0.3, 0.5, 0.8, 1.0, 1.5, 2.5, 3.5, 3.6, FLT_MAX, INFINITY, NAN, + ucastd(0x0102030405060708ULL), ucastd(0x8000000000000000ULL), ucastd(0x7FFFFFFFFFFFFFFFULL), ucastd(0xFFFFFFFFFFFFFFFFULL) + }; + +uint32_t interesting_ints_[] = { 0, 1, 2, 3, 0x01020304, 0x10203040, 0x7FFFFFFF, 0xFFFFFFFF, 0xFFFFFFFE, 0x12345678, 0x9ABCDEF1, 0x80000000, + 0x80808080, 0x7F7F7F7F, 0x01010101, 0x11111111, 0x20202020, 0x0F0F0F0F, 0xF0F0F0F0, + fcastu(-INFINITY), fcastu(-FLT_MAX), fcastu(-2.5f), fcastu(-1.5f), fcastu(-1.4f), fcastu(-1.0f), fcastu(-0.5f), + fcastu(-0.2f), fcastu(-FLT_MIN), 0xF9301AB9, 0x0039AB12, 0x19302BCD, + fcastu(1.401298464e-45f), fcastu(FLT_MIN), fcastu(0.3f), fcastu(0.5f), fcastu(0.8f), fcastu(1.0f), fcastu(1.5f), + fcastu(2.5f), fcastu(3.5f), fcastu(3.6f), fcastu(FLT_MAX), fcastu(INFINITY), fcastu(NAN) }; + +bool always_true() { return time(NULL) != 0; } // This function always returns true, but the compiler should not know this. + +bool IsNan(float f) { return (fcastu(f) << 1) > 0xFF000000u; } + +// Replaces all occurrences of 'src' in string 'str' with 'dst', operating in place. strlen(dst) <= strlen(src). +void contract_inplace(char *str, const char *src, const char *dst) +{ + int dstLen = strlen(dst); + int srcLen = strlen(src); + int diff = srcLen - dstLen; + assert(diff >= 0); + + while(true) + { + char *pos = strstr(str, src); + if (!pos) return; + str = pos; + strcpy(pos, dst); + pos += dstLen; + strcpy(pos, pos + diff); + } +} + +// sprintf standard does not allow controlling how many leading zeros to use +// for printing out the exponent, and different compilers give different +// values. Perform a canonicalization step that enforces the printouts are +// the same. +void CanonicalizeStringComparisons(char *s) +{ + contract_inplace(s, "e+00", "e+"); + contract_inplace(s, "e-00", "e-"); + contract_inplace(s, "e+0", "e+"); + contract_inplace(s, "e-0", "e-"); + contract_inplace(s, "1.#INF", "inf"); +} + +char *SerializeFloat(float f, char *dstStr, bool approximate = false) +{ + if (IsNan(f)) + { + uint32_t u = fcastu(f); + int numChars = sprintf(dstStr, "NaN(0x%8X)", (unsigned int)u); + return dstStr + numChars; + } + else + { + if (approximate > 0) + { + if (fabs(f) < FLT_MIN) // Flush denormals to zero (for _mm_rcp_ps) + sprintf(dstStr, "%f", copysign(0.f, f)); + else if (fabs(f) >= 2.6e22f) // Flush large numbers to infinity (for _mm_rsqrt_ps) + sprintf(dstStr, "%f", copysign(INFINITY, f)); + else + sprintf(dstStr, "%.2g", f); + } + else + sprintf(dstStr, "%.9g", f); + CanonicalizeStringComparisons(dstStr); + return dstStr + strlen(dstStr); + } +} + +char *SerializeDouble(double f, char *dstStr) +{ + if (IsNan(f)) + { + uint64_t u = dcastu(f); + int numChars = sprintf(dstStr, "NaN(0x%08X%08X)", (unsigned int)(u>>32), (unsigned int)u); + return dstStr + numChars; + } + else + { + sprintf(dstStr, "%.17g", f); + CanonicalizeStringComparisons(dstStr); + return dstStr + strlen(dstStr); + } +} + +void tostr(__m128 *m, char *outstr) +{ + union { __m128 m; float val[4]; } u; + u.m = *m; + char s[4][32]; + SerializeFloat(u.val[0], s[0]); + SerializeFloat(u.val[1], s[1]); + SerializeFloat(u.val[2], s[2]); + SerializeFloat(u.val[3], s[3]); + sprintf(outstr, "[%s,%s,%s,%s]", s[3], s[2], s[1], s[0]); +} + +void tostr_approx(__m128 *m, char *outstr, bool approximate) +{ + union { __m128 m; float val[4]; } u; + u.m = *m; + char s[4][32]; + SerializeFloat(u.val[0], s[0], approximate); + SerializeFloat(u.val[1], s[1], approximate); + SerializeFloat(u.val[2], s[2], approximate); + SerializeFloat(u.val[3], s[3], approximate); + sprintf(outstr, "[%s,%s,%s,%s]", s[3], s[2], s[1], s[0]); +} + +#ifdef __SSE2__ + +void tostr(__m128i *m, char *outstr) +{ + union { __m128i m; uint32_t val[4]; } u; + u.m = *m; + sprintf(outstr, "[0x%08X,0x%08X,0x%08X,0x%08X]", u.val[3], u.val[2], u.val[1], u.val[0]); +} + +void tostr(__m128d *m, char *outstr) +{ + union { __m128d m; double val[2]; } u; + u.m = *m; + char s[2][64]; + SerializeDouble(u.val[0], s[0]); + SerializeDouble(u.val[1], s[1]); + sprintf(outstr, "[%s,%s]", s[1], s[0]); +} + +__m128i ExtractInRandomOrder(uint32_t *arr, int i, int n, int prime) +{ + return _mm_set_epi32(arr[(i*prime)%n], arr[((i+1)*prime)%n], arr[((i+2)*prime)%n], arr[((i+3)*prime)%n]); +} + +__m128d ExtractInRandomOrder(double *arr, int i, int n, int prime) +{ + return _mm_set_pd(arr[(i*prime)%n], arr[((i+1)*prime)%n]); +} +#endif + +void tostr(align1_int *m, char *outstr) +{ + sprintf(outstr, "0x%08X", *m); +} + +void tostr(align1_int64 *m, char *outstr) +{ + sprintf(outstr, "0x%08X%08X", (int)(*m >> 32), (int)*m); +} + +void tostr(align1_float *m, char *outstr) +{ + SerializeFloat(*m, outstr); +} + +void tostr(align1_double *m, char *outstr) +{ + SerializeDouble(*m, outstr); +} + +void tostr(align1_double *m, int numElems, char *outstr) +{ + char s[2][64]; + for(int i = 0; i < numElems; ++i) + SerializeDouble(m[i], s[i]); + switch(numElems) + { + case 1: sprintf(outstr, "{%s}", s[0]); break; + case 2: sprintf(outstr, "{%s,%s}", s[0], s[1]); break; + } +} + +void tostr(align1_float *m, int numElems, char *outstr) +{ + char s[4][64]; + for(int i = 0; i < numElems; ++i) + SerializeFloat(m[i], s[i]); + switch(numElems) + { + case 1: sprintf(outstr, "{%s}", s[0]); break; + case 2: sprintf(outstr, "{%s,%s}", s[0], s[1]); break; + case 3: sprintf(outstr, "{%s,%s,%s}", s[0], s[1], s[2]); break; + case 4: sprintf(outstr, "{%s,%s,%s,%s}", s[0], s[1], s[2], s[3]); break; + } +} + +void tostr(align1_int *s, int numElems, char *outstr) +{ + switch(numElems) + { + case 1: sprintf(outstr, "{0x%08X}", s[0]); break; + case 2: sprintf(outstr, "{0x%08X,0x%08X}", s[0], s[1]); break; + case 3: sprintf(outstr, "{0x%08X,0x%08X,0x%08X}", s[0], s[1], s[2]); break; + case 4: sprintf(outstr, "{0x%08X,0x%08X,0x%08X,0x%08X}", s[0], s[1], s[2], s[3]); break; + } +} + +void tostr(align1_int64 *m, int numElems, char *outstr) +{ + switch(numElems) + { + case 1: sprintf(outstr, "{0x%08X%08X}", (int)(*m >> 32), (int)*m); break; + case 2: sprintf(outstr, "{0x%08X%08X,0x%08X%08X}", (int)(*m >> 32), (int)*m, (int)(m[1] >> 32), (int)m[1]); + } +} + +// Accessors to the test data in a way that the compiler can't optimize at compile-time. +__attribute__((noinline)) float *get_interesting_floats() +{ + return always_true() ? interesting_floats_ : 0; +} + +__attribute__((noinline)) uint32_t *get_interesting_ints() +{ + return always_true() ? interesting_ints_ : 0; +} + +__attribute__((noinline)) double *get_interesting_doubles() +{ + return always_true() ? interesting_doubles_ : 0; +} + +__m128 ExtractFloatInRandomOrder(float *arr, int i, int n, int prime) +{ + return _mm_set_ps(arr[(i*prime)%n], arr[((i+1)*prime)%n], arr[((i+2)*prime)%n], arr[((i+3)*prime)%n]); +} + +__m128 ExtractIntInRandomOrder(unsigned int *arr, int i, int n, int prime) +{ + return _mm_set_ps(*(float*)&arr[(i*prime)%n], *(float*)&arr[((i+1)*prime)%n], *(float*)&arr[((i+2)*prime)%n], *(float*)&arr[((i+3)*prime)%n]); +} + +#define E1(arr, i, n) ExtractFloatInRandomOrder(arr, i, n, 1) +#define E2(arr, i, n) ExtractFloatInRandomOrder(arr, i, n, 1787) + +#define E1_Int(arr, i, n) ExtractIntInRandomOrder(arr, i, n, 1) +#define E2_Int(arr, i, n) ExtractIntInRandomOrder(arr, i, n, 1787) + +#define M128i_M128i_M128i(func) \ + for(int i = 0; i < numInterestingInts / 4; ++i) \ + for(int k = 0; k < 4; ++k) \ + for(int j = 0; j < numInterestingInts / 4; ++j) \ + { \ + __m128i m1 = E1(interesting_ints, i*4+k, numInterestingInts); \ + __m128i m2 = E2(interesting_ints, j*4, numInterestingInts); \ + __m128i ret = func(m1, m2); \ + /* a op b */ \ + char str[256]; tostr(&m1, str); \ + char str2[256]; tostr(&m2, str2); \ + char str3[256]; tostr(&ret, str3); \ + printf("%s(%s, %s, %d) = %s\n", #func, str, str2, Tint, str3); \ + /* b op a */ \ + ret = func(m2, m1, Tint); \ + tostr(&m1, str); \ + tostr(&m2, str2); \ + tostr(&ret, str3); \ + printf("%s(%s, %s, %d) = %s\n", #func, str, str2, Tint, str3); \ + } + +#define Ret_M128_Tint_body(Ret_type, func, Tint) \ + for(int i = 0; i < numInterestingFloats / 4; ++i) \ + for(int k = 0; k < 4; ++k) \ + { \ + __m128 m1 = E1(interesting_floats, i*4+k, numInterestingFloats); \ + Ret_type ret = func(m1, Tint); \ + char str[256]; tostr(&m1, str); \ + char str2[256]; tostr(&ret, str2); \ + printf("%s(%s, %d) = %s\n", #func, str, Tint, str2); \ + } + +#define Ret_M128i_Tint_body(Ret_type, func, Tint) \ + for(int i = 0; i < numInterestingInts / 4; ++i) \ + for(int k = 0; k < 4; ++k) \ + { \ + __m128i m1 = E1(interesting_ints, i*4+k, numInterestingInts); \ + Ret_type ret = func(m1, Tint); \ + char str[256]; tostr(&m1, str); \ + char str2[256]; tostr(&ret, str2); \ + printf("%s(%s, %d) = %s\n", #func, str, Tint, str2); \ + } + +#define Ret_M128i_int_Tint_body(Ret_type, func, Tint) \ + for(int i = 0; i < numInterestingInts / 4; ++i) \ + for(int j = 0; j < numInterestingInts; ++j) \ + for(int k = 0; k < 4; ++k) \ + { \ + __m128i m1 = E1(interesting_ints, i*4+k, numInterestingInts); \ + Ret_type ret = func(m1, interesting_ints[j], Tint); \ + char str[256]; tostr(&m1, str); \ + char str2[256]; tostr(&ret, str2); \ + printf("%s(%s, 0x%08X, %d) = %s\n", #func, str, interesting_ints[j], Tint, str2); \ + } + +#define Ret_M128d_M128d_Tint_body(Ret_type, func, Tint) \ + for(int i = 0; i < numInterestingDoubles / 2; ++i) \ + for(int k = 0; k < 2; ++k) \ + for(int j = 0; j < numInterestingDoubles / 2; ++j) \ + { \ + __m128d m1 = E1(interesting_doubles, i*2+k, numInterestingDoubles); \ + __m128d m2 = E2(interesting_doubles, j*2, numInterestingDoubles); \ + Ret_type ret = func(m1, m2, Tint); \ + /* a op b */ \ + char str[256]; tostr(&m1, str); \ + char str2[256]; tostr(&m2, str2); \ + char str3[256]; tostr(&ret, str3); \ + printf("%s(%s, %s, %d) = %s\n", #func, str, str2, Tint, str3); \ + /* b op a */ \ + ret = func(m2, m1, Tint); \ + tostr(&m1, str); \ + tostr(&m2, str2); \ + tostr(&ret, str3); \ + printf("%s(%s, %s, %d) = %s\n", #func, str, str2, Tint, str3); \ + } + +#define Ret_M128i_M128i_Tint_body(Ret_type, func, Tint) \ + for(int i = 0; i < numInterestingInts / 4; ++i) \ + for(int k = 0; k < 4; ++k) \ + for(int j = 0; j < numInterestingInts / 4; ++j) \ + { \ + __m128i m1 = E1(interesting_ints, i*4+k, numInterestingInts); \ + __m128i m2 = E2(interesting_ints, j*4, numInterestingInts); \ + Ret_type ret = func(m1, m2, Tint); \ + /* a op b */ \ + char str[256]; tostr(&m1, str); \ + char str2[256]; tostr(&m2, str2); \ + char str3[256]; tostr(&ret, str3); \ + printf("%s(%s, %s, %d) = %s\n", #func, str, str2, Tint, str3); \ + /* b op a */ \ + ret = func(m2, m1, Tint); \ + tostr(&m1, str); \ + tostr(&m2, str2); \ + tostr(&ret, str3); \ + printf("%s(%s, %s, %d) = %s\n", #func, str, str2, Tint, str3); \ + } + +#define Ret_M128_M128_Tint_body(Ret_type, func, Tint) \ + for(int i = 0; i < numInterestingFloats / 4; ++i) \ + for(int k = 0; k < 4; ++k) \ + for(int j = 0; j < numInterestingFloats / 4; ++j) \ + { \ + __m128 m1 = E1(interesting_floats, i*4+k, numInterestingFloats); \ + __m128 m2 = E2(interesting_floats, j*4, numInterestingFloats); \ + Ret_type ret = func(m1, m2, Tint); \ + /* a op b */ \ + char str[256]; tostr(&m1, str); \ + char str2[256]; tostr(&m2, str2); \ + char str3[256]; tostr(&ret, str3); \ + printf("%s(%s, %s, %d) = %s\n", #func, str, str2, Tint, str3); \ + /* b op a */ \ + ret = func(m2, m1, Tint); \ + tostr(&m1, str); \ + tostr(&m2, str2); \ + tostr(&ret, str3); \ + printf("%s(%s, %s, %d) = %s\n", #func, str, str2, Tint, str3); \ + } + +#define const_int8_unroll(Ret_type, F, func) \ + F(Ret_type, func, 0); \ + F(Ret_type, func, 1); \ + F(Ret_type, func, 2); \ + F(Ret_type, func, 3); \ + F(Ret_type, func, 5); \ + F(Ret_type, func, 7); \ + F(Ret_type, func, 11); \ + F(Ret_type, func, 13); \ + F(Ret_type, func, 15); \ + F(Ret_type, func, 16); \ + F(Ret_type, func, 17); \ + F(Ret_type, func, 23); \ + F(Ret_type, func, 29); \ + F(Ret_type, func, 31); \ + F(Ret_type, func, 37); \ + F(Ret_type, func, 43); \ + F(Ret_type, func, 47); \ + F(Ret_type, func, 59); \ + F(Ret_type, func, 127); \ + F(Ret_type, func, 128); \ + F(Ret_type, func, 191); \ + F(Ret_type, func, 254); \ + F(Ret_type, func, 255); + +#define Ret_M128_Tint(Ret_type, func) const_int8_unroll(Ret_type, Ret_M128_Tint_body, func) +#define Ret_M128i_Tint(Ret_type, func) const_int8_unroll(Ret_type, Ret_M128i_Tint_body, func) +#define Ret_M128i_int_Tint(Ret_type, func) const_int8_unroll(Ret_type, Ret_M128i_int_Tint_body, func) +#define Ret_M128i_M128i_Tint(Ret_type, func) const_int8_unroll(Ret_type, Ret_M128i_M128i_Tint_body, func) +#define Ret_M128d_M128d_Tint(Ret_type, func) const_int8_unroll(Ret_type, Ret_M128d_M128d_Tint_body, func) +#define Ret_M128_M128_Tint(Ret_type, func) const_int8_unroll(Ret_type, Ret_M128_M128_Tint_body, func) + +#define Ret_M128d_M128d(Ret_type, func) \ + for(int i = 0; i < numInterestingDoubles / 2; ++i) \ + for(int k = 0; k < 2; ++k) \ + for(int j = 0; j < numInterestingDoubles / 2; ++j) \ + { \ + __m128d m1 = E1(interesting_doubles, i*2+k, numInterestingDoubles); \ + __m128d m2 = E2(interesting_doubles, j*2, numInterestingDoubles); \ + Ret_type ret = func(m1, m2); \ + /* a op b */ \ + char str[256]; tostr(&m1, str); \ + char str2[256]; tostr(&m2, str2); \ + char str3[256]; tostr(&ret, str3); \ + printf("%s(%s, %s, %d) = %s\n", #func, str, str2, Tint, str3); \ + /* b op a */ \ + ret = func(m2, m1, Tint); \ + tostr(&m1, str); \ + tostr(&m2, str2); \ + tostr(&ret, str3); \ + printf("%s(%s, %s, %d) = %s\n", #func, str, str2, Tint, str3); \ + } + +#define Ret_M128d_int(Ret_type, func) \ + for(int i = 0; i < numInterestingDoubles / 2; ++i) \ + for(int k = 0; k < 2; ++k) \ + for(int j = 0; j < numInterestingInts; ++j) \ + { \ + __m128d m1 = E1(interesting_doubles, i*2+k, numInterestingDoubles); \ + int m2 = interesting_ints[j]; \ + Ret_type ret = func(m1, m2); \ + char str[256]; tostr(&m1, str); \ + char str2[256]; tostr(&m2, str2); \ + char str3[256]; tostr(&ret, str3); \ + printf("%s(%s, %s) = %s\n", #func, str, str2, str3); \ + } + +#define Ret_M128d_int64(Ret_type, func) \ + for(int i = 0; i < numInterestingDoubles / 2; ++i) \ + for(int k = 0; k < 2; ++k) \ + for(int j = 0; j < numInterestingInts; ++j) \ + for(int l = 0; l < numInterestingInts; ++l) \ + { \ + __m128d m1 = E1(interesting_doubles, i*2+k, numInterestingDoubles); \ + int64_t m2 = (int64_t)(((uint64_t)interesting_ints[j]) << 32 | (uint64_t)interesting_ints[l]); \ + Ret_type ret = func(m1, m2); \ + char str[256]; tostr(&m1, str); \ + char str2[256]; tostr(&m2, str2); \ + char str3[256]; tostr(&ret, str3); \ + printf("%s(%s, %s) = %s\n", #func, str, str2, str3); \ + } + +#define Ret_M128d(Ret_type, func) \ + for(int i = 0; i < numInterestingDoubles / 2; ++i) \ + for(int k = 0; k < 2; ++k) \ + { \ + __m128d m1 = E1(interesting_doubles, i*2+k, numInterestingDoubles); \ + Ret_type ret = func(m1); \ + char str[256]; tostr(&m1, str); \ + char str2[256]; tostr(&ret, str2); \ + printf("%s(%s) = %s\n", #func, str, str2); \ + } + +#define Ret_DoublePtr(Ret_type, func, numElemsAccessed, inc) \ + for(int i = 0; i+numElemsAccessed <= numInterestingDoubles; i += inc) \ + { \ + double *ptr = interesting_doubles + i; \ + Ret_type ret = func(ptr); \ + char str[256]; tostr(ptr, numElemsAccessed, str); \ + char str2[256]; tostr(&ret, str2); \ + printf("%s(%s) = %s\n", #func, str, str2); \ + } + +float tempOutFloatStore[16]; +float *getTempOutFloatStore(int alignmentBytes) +{ + memset(tempOutFloatStore, 0, sizeof(tempOutFloatStore)); + uintptr_t addr = (uintptr_t)tempOutFloatStore; + addr = (addr + alignmentBytes - 1) & ~(alignmentBytes-1); + return (float*)addr; +} + +int *getTempOutIntStore(int alignmentBytes) { return (int*)getTempOutFloatStore(alignmentBytes); } +double *getTempOutDoubleStore(int alignmentBytes) { return (double*)getTempOutFloatStore(alignmentBytes); } + +#define void_OutFloatPtr_M128(func, Ptr_type, numBytesWritten, alignmentBytes) \ + for(int i = 0; i < numInterestingFloats / 4; ++i) \ + for(int offset = 0; offset < numBytesWritten; offset += alignmentBytes) \ + for(int k = 0; k < 4; ++k) \ + { \ + uintptr_t base = (uintptr_t)getTempOutFloatStore(16); \ + __m128 m1 = E1(interesting_floats, i*4+k, numInterestingFloats); \ + align1_float *out = (align1_float*)(base + offset); \ + func((Ptr_type)out, m1); \ + char str[256]; tostr(&m1, str); \ + char str2[256]; tostr(out, numBytesWritten/sizeof(float), str2); \ + printf("%s(p:align=%d, %s) = %s\n", #func, offset, str, str2); \ + } + +#define void_OutDoublePtr_M128d(func, Ptr_type, numBytesWritten, alignmentBytes) \ + for(int i = 0; i < numInterestingDoubles / 2; ++i) \ + for(int offset = 0; offset < numBytesWritten; offset += alignmentBytes) \ + for(int k = 0; k < 2; ++k) \ + { \ + uintptr_t base = (uintptr_t)getTempOutDoubleStore(16); \ + __m128d m1 = E1(interesting_doubles, i*2+k, numInterestingDoubles); \ + align1_double *out = (align1_double*)(base + offset); \ + func((Ptr_type)out, m1); \ + char str[256]; tostr(&m1, str); \ + char str2[256]; tostr(out, numBytesWritten/sizeof(double), str2); \ + printf("%s(p:align=%d, %s) = %s\n", #func, offset, str, str2); \ + } + +#define void_OutIntPtr_M128i(func, Ptr_type, numBytesWritten, alignmentBytes) \ + for(int i = 0; i < numInterestingInts / 4; ++i) \ + for(int offset = 0; offset < numBytesWritten; offset += alignmentBytes) \ + for(int k = 0; k < 4; ++k) \ + { \ + uintptr_t base = (uintptr_t)getTempOutIntStore(16); \ + __m128 m1 = E1_Int(interesting_ints, i*4+k, numInterestingInts); \ + align1_int *out = (align1_int*)(base + offset); \ + func((Ptr_type)out, m1); \ + char str[256]; tostr(&m1, str); \ + char str2[256]; tostr(out, (numBytesWritten+sizeof(int)-1)/sizeof(int), str2); \ + printf("%s(p:align=%d, %s) = %s\n", #func, offset, str, str2); \ + } + +#define void_OutIntPtr_int(func, Ptr_type, numBytesWritten, alignmentBytes) \ + for(int i = 0; i < numInterestingInts; ++i) \ + for(int offset = 0; offset < numBytesWritten; offset += alignmentBytes) \ + for(int k = 0; k < 4; ++k) \ + { \ + uintptr_t base = (uintptr_t)getTempOutIntStore(16); \ + int m1 = interesting_ints[i]; \ + align1_int *out = (align1_int*)(base + offset); \ + func((Ptr_type)out, m1); \ + char str[256]; tostr(&m1, str); \ + char str2[256]; tostr(out, numBytesWritten/sizeof(int), str2); \ + printf("%s(p:align=%d, %s) = %s\n", #func, offset, str, str2); \ + } + +#define void_OutIntPtr_int64(func, Ptr_type, numBytesWritten, alignmentBytes) \ + for(int i = 0; i < numInterestingInts; ++i) \ + for(int j = 0; j < numInterestingInts; ++j) \ + for(int offset = 0; offset < numBytesWritten; offset += alignmentBytes) \ + { \ + uintptr_t base = (uintptr_t)getTempOutIntStore(16); \ + int64_t m1 = (int64_t)(((uint64_t)interesting_ints[i]) << 32 | (uint64_t)interesting_ints[j]); \ + align1_int64 *out = (align1_int64*)(base + offset); \ + func((Ptr_type)out, m1); \ + char str[256]; tostr(&m1, str); \ + char str2[256]; tostr(out, numBytesWritten/sizeof(int64_t), str2); \ + printf("%s(p:align=%d, %s) = %s\n", #func, offset, str, str2); \ + } + +#define void_M128i_M128i_OutIntPtr(func, Ptr_type, numBytesWritten, alignmentBytes) \ + for(int i = 0; i < numInterestingInts / 4; ++i) \ + for(int j = 0; j < numInterestingInts / 4; ++j) \ + for(int offset = 0; offset < numBytesWritten; offset += alignmentBytes) \ + for(int k = 0; k < 4; ++k) \ + { \ + uintptr_t base = (uintptr_t)getTempOutIntStore(16); \ + __m128d m1 = E1(interesting_ints, i*4+k, numInterestingInts); \ + __m128i m2 = E2(interesting_ints, j*4, numInterestingInts); \ + align1_int *out = (int*)(base + offset); \ + func(m1, m2, (Ptr_type)out); \ + char str[256]; tostr(&m1, str); \ + char str2[256]; tostr(&m2, str2); \ + char str3[256]; tostr(out, numBytesWritten/sizeof(int), str3); \ + printf("%s(%s, %s, p:align=%d) = %s\n", #func, str, str2, offset, str3); \ + } + +#define Ret_M128(Ret_type, func) \ + for(int i = 0; i < numInterestingFloats / 4; ++i) \ + for(int k = 0; k < 4; ++k) \ + { \ + __m128 m1 = E1(interesting_floats, i*4+k, numInterestingFloats); \ + Ret_type ret = func(m1); \ + char str[256]; tostr(&m1, str); \ + char str2[256]; tostr(&ret, str2); \ + printf("%s(%s) = %s\n", #func, str, str2); \ + } + +#define Ret_M128approx(Ret_type, func) \ + for(int i = 0; i < numInterestingFloats / 4; ++i) \ + for(int k = 0; k < 4; ++k) \ + { \ + __m128 m1 = E1(interesting_floats, i*4+k, numInterestingFloats); \ + Ret_type ret = func(m1); \ + char str[256]; tostr(&m1, str); \ + char str2[256]; tostr_approx(&ret, str2, true/*approximate*/); \ + printf("%s(%s) = %s\n", #func, str, str2); \ + } + +#define Ret_FloatPtr(Ret_type, func, numElemsAccessed, inc) \ + for(int i = 0; i+numElemsAccessed <= numInterestingFloats; i += inc) \ + { \ + float *ptr = interesting_floats + i; \ + Ret_type ret = func(ptr); \ + char str[256]; tostr(ptr, numElemsAccessed, str); \ + char str2[256]; tostr(&ret, str2); \ + printf("%s(%s) = %s\n", #func, str, str2); \ + } + +#define Ret_Float4(Ret_type, func, inc) \ + for(int i = 0; i+4 <= numInterestingFloats; i += inc) \ + { \ + float *ptr = interesting_floats + i; \ + Ret_type ret = func(ptr[0], ptr[1], ptr[2], ptr[3]); \ + char str[256]; tostr(ptr, 4, str); \ + char str2[256]; tostr(&ret, str2); \ + printf("%s(%s) = %s\n", #func, str, str2); \ + } + +#define Ret_Float(Ret_type, func, inc) \ + for(int i = 0; i+1 <= numInterestingFloats; i += inc) \ + { \ + float *ptr = interesting_floats + i; \ + Ret_type ret = func(*ptr); \ + char str[256]; tostr(ptr, 1, str); \ + char str2[256]; tostr(&ret, str2); \ + printf("%s(%s) = %s\n", #func, str, str2); \ + } + +#define Ret_IntPtr(Ret_type, func, Ptr_type, numElemsAccessed, inc) \ + for(int i = 0; i+numElemsAccessed <= numInterestingInts; i += inc) \ + { \ + uint32_t *ptr = interesting_ints + i; \ + Ret_type ret = func((Ptr_type)ptr); \ + char str[256]; tostr((int*)ptr, numElemsAccessed, str); \ + char str2[256]; tostr(&ret, str2); \ + printf("%s(%s) = %s\n", #func, str, str2); \ + } + +#define Ret_M128_FloatPtr(Ret_type, func, Ptr_type, numElemsAccessed, inc) \ + for(int i = 0; i < numInterestingFloats / 4; ++i) \ + for(int k = 0; k < 4; ++k) \ + for(int j = 0; j+numElemsAccessed <= numInterestingFloats; j += inc) \ + { \ + __m128 m1 = E1(interesting_floats, i*4+k, numInterestingFloats); \ + float *ptr = interesting_floats + j; \ + Ret_type ret = func(m1, (Ptr_type)ptr); \ + char str[256]; tostr(&m1, str); \ + char str2[256]; tostr(ptr, numElemsAccessed, str2); \ + char str3[256]; tostr(&ret, str3); \ + printf("%s(%s, %s) = %s\n", #func, str, str2, str3); \ + } + +#define Ret_M128d_DoublePtr(Ret_type, func, Ptr_type, numElemsAccessed, inc) \ + for(int i = 0; i < numInterestingDoubles / 2; ++i) \ + for(int k = 0; k < 2; ++k) \ + for(int j = 0; j+numElemsAccessed <= numInterestingDoubles; j += inc) \ + { \ + __m128d m1 = E1(interesting_doubles, i*2+k, numInterestingDoubles); \ + double *ptr = interesting_doubles + j; \ + Ret_type ret = func(m1, (Ptr_type)ptr); \ + char str[256]; tostr(&m1, str); \ + char str2[256]; tostr(ptr, numElemsAccessed, str2); \ + char str3[256]; tostr(&ret, str3); \ + printf("%s(%s, %s) = %s\n", #func, str, str2, str3); \ + } + +#define Ret_M128i(Ret_type, func) \ + for(int i = 0; i < numInterestingInts / 4; ++i) \ + for(int k = 0; k < 4; ++k) \ + { \ + __m128i m1 = E1(interesting_ints, i*4+k, numInterestingInts); \ + Ret_type ret = func(m1); \ + char str[256]; tostr(&m1, str); \ + char str2[256]; tostr(&ret, str2); \ + printf("%s(%s) = %s\n", #func, str, str2); \ + } + +#define Ret_int(Ret_type, func) \ + for(int i = 0; i < numInterestingInts; ++i) \ + { \ + Ret_type ret = func(interesting_ints[i]); \ + char str[256]; tostr((int*)&interesting_ints[i], str); \ + char str2[256]; tostr(&ret, str2); \ + printf("%s(%s) = %s\n", #func, str, str2); \ + } + +#define Ret_int64(Ret_type, func) \ + for(int i = 0; i < numInterestingInts; ++i) \ + for(int j = 0; j < numInterestingInts; ++j) \ + { \ + int64_t m1 = (int64_t)(((uint64_t)interesting_ints[i]) << 32 | (uint64_t)interesting_ints[j]); \ + Ret_type ret = func(m1); \ + char str[256]; tostr(&m1, str); \ + char str2[256]; tostr(&ret, str2); \ + printf("%s(%s) = %s\n", #func, str, str2); \ + } + +#define Ret_M128_M128(Ret_type, func) \ + for(int i = 0; i < numInterestingFloats / 4; ++i) \ + for(int k = 0; k < 4; ++k) \ + for(int j = 0; j < numInterestingFloats / 4; ++j) \ + { \ + __m128 m1 = E1(interesting_floats, i*4+k, numInterestingFloats); \ + __m128 m2 = E2(interesting_floats, j*4, numInterestingFloats); \ + Ret_type ret = func(m1, m2); \ + char str[256]; tostr(&m1, str); \ + char str2[256]; tostr(&m2, str2); \ + char str3[256]; tostr(&ret, str3); \ + printf("%s(%s, %s) = %s\n", #func, str, str2, str3); \ + } + +#define Ret_M128_int(Ret_type, func) \ + for(int i = 0; i < numInterestingFloats / 4; ++i) \ + for(int k = 0; k < 4; ++k) \ + for(int j = 0; j < numInterestingInts; ++j) \ + { \ + __m128 m1 = E1(interesting_floats, i*4+k, numInterestingFloats); \ + int m2 = interesting_ints[j]; \ + Ret_type ret = func(m1, m2); \ + char str[256]; tostr(&m1, str); \ + char str2[256]; tostr(&m2, str2); \ + char str3[256]; tostr(&ret, str3); \ + printf("%s(%s, %s) = %s\n", #func, str, str2, str3); \ + } diff --git a/tests/sse/test_sse1.cpp b/tests/sse/test_sse1.cpp new file mode 100644 index 0000000000000..5817853105a43 --- /dev/null +++ b/tests/sse/test_sse1.cpp @@ -0,0 +1,175 @@ +// Copyright 2015 The Emscripten Authors. All rights reserved. +// Emscripten is available under two separate licenses, the MIT license and the +// University of Illinois/NCSA Open Source License. Both these licenses can be +// found in the LICENSE file. + +// This file uses SSE1 by calling different functions with different interesting inputs and prints the results. +// Use a diff tool to compare the results between platforms. + +#include +#include "test_sse.h" + +int main() +{ + float *interesting_floats = get_interesting_floats(); + int numInterestingFloats = sizeof(interesting_floats_)/sizeof(interesting_floats_[0]); + assert(numInterestingFloats % 4 == 0); + + uint32_t *interesting_ints = get_interesting_ints(); + int numInterestingInts = sizeof(interesting_ints_)/sizeof(interesting_ints_[0]); + assert(numInterestingInts % 4 == 0); + + // MXCSR register state + printf("%08X\n", _mm_getcsr()); + printf("%08X\n", _MM_GET_EXCEPTION_MASK()); + printf("%08X\n", _MM_GET_EXCEPTION_STATE()); + printf("%08X\n", _MM_GET_FLUSH_ZERO_MODE()); + printf("%08X\n", _MM_GET_ROUNDING_MODE()); + + // SSE1 Arithmetic instructions: + Ret_M128_M128(__m128, _mm_add_ps); + Ret_M128_M128(__m128, _mm_add_ss); + Ret_M128_M128(__m128, _mm_div_ps); + Ret_M128_M128(__m128, _mm_div_ss); + Ret_M128_M128(__m128, _mm_mul_ps); + Ret_M128_M128(__m128, _mm_mul_ss); + Ret_M128_M128(__m128, _mm_sub_ps); + Ret_M128_M128(__m128, _mm_sub_ss); + + // SSE1 Elementary Math functions: + Ret_M128approx(__m128, _mm_rcp_ps); + Ret_M128approx(__m128, _mm_rcp_ss); + Ret_M128approx(__m128, _mm_rsqrt_ps); + Ret_M128approx(__m128, _mm_rsqrt_ss); + Ret_M128approx(__m128, _mm_sqrt_ps); + Ret_M128approx(__m128, _mm_sqrt_ss); + + // SSE1 Logical instructions: + Ret_M128_M128(__m128, _mm_and_ps); + Ret_M128_M128(__m128, _mm_andnot_ps); + Ret_M128_M128(__m128, _mm_or_ps); + Ret_M128_M128(__m128, _mm_xor_ps); + + // SSE1 Compare instructions: + Ret_M128_M128(__m128, _mm_cmpeq_ps); + Ret_M128_M128(__m128, _mm_cmpeq_ss); + Ret_M128_M128(__m128, _mm_cmpge_ps); + Ret_M128_M128(__m128, _mm_cmpge_ss); + Ret_M128_M128(__m128, _mm_cmpgt_ps); + Ret_M128_M128(__m128, _mm_cmpgt_ss); + Ret_M128_M128(__m128, _mm_cmple_ps); + Ret_M128_M128(__m128, _mm_cmple_ss); + Ret_M128_M128(__m128, _mm_cmplt_ps); + Ret_M128_M128(__m128, _mm_cmplt_ss); + Ret_M128_M128(__m128, _mm_cmpneq_ps); + Ret_M128_M128(__m128, _mm_cmpneq_ss); + Ret_M128_M128(__m128, _mm_cmpnge_ps); + Ret_M128_M128(__m128, _mm_cmpnge_ss); + Ret_M128_M128(__m128, _mm_cmpngt_ps); + Ret_M128_M128(__m128, _mm_cmpngt_ss); + Ret_M128_M128(__m128, _mm_cmpnle_ps); + Ret_M128_M128(__m128, _mm_cmpnle_ss); + Ret_M128_M128(__m128, _mm_cmpnlt_ps); + Ret_M128_M128(__m128, _mm_cmpnlt_ss); + Ret_M128_M128(__m128, _mm_cmpord_ps); + Ret_M128_M128(__m128, _mm_cmpord_ss); + Ret_M128_M128(__m128, _mm_cmpunord_ps); + Ret_M128_M128(__m128, _mm_cmpunord_ss); + + Ret_M128_M128(int, _mm_comieq_ss); + Ret_M128_M128(int, _mm_comige_ss); + Ret_M128_M128(int, _mm_comigt_ss); + Ret_M128_M128(int, _mm_comile_ss); + Ret_M128_M128(int, _mm_comilt_ss); + Ret_M128_M128(int, _mm_comineq_ss); + Ret_M128_M128(int, _mm_ucomieq_ss); + Ret_M128_M128(int, _mm_ucomige_ss); + Ret_M128_M128(int, _mm_ucomigt_ss); + Ret_M128_M128(int, _mm_ucomile_ss); + Ret_M128_M128(int, _mm_ucomilt_ss); + Ret_M128_M128(int, _mm_ucomineq_ss); + + // SSE1 Convert instructions: + Ret_M128_int(__m128, _mm_cvt_si2ss); + Ret_M128(int, _mm_cvt_ss2si); + Ret_M128_int(__m128, _mm_cvtsi32_ss); + Ret_M128(float, _mm_cvtss_f32); + Ret_M128(int, _mm_cvtss_si32); + Ret_M128(int64_t, _mm_cvtss_si64); + Ret_M128(int, _mm_cvtt_ss2si); + Ret_M128(int, _mm_cvttss_si32); + Ret_M128(int64_t, _mm_cvttss_si64); + + // SSE1 Load functions: + Ret_FloatPtr(__m128, _mm_load_ps, 4, 4); + Ret_FloatPtr(__m128, _mm_load_ps1, 1, 1); + Ret_FloatPtr(__m128, _mm_load_ss, 1, 1); + Ret_FloatPtr(__m128, _mm_load1_ps, 1, 1); + Ret_M128_FloatPtr(__m128, _mm_loadh_pi, __m64*, 2, 1); + Ret_M128_FloatPtr(__m128, _mm_loadl_pi, __m64*, 2, 1); + Ret_FloatPtr(__m128, _mm_loadr_ps, 4, 4); + Ret_FloatPtr(__m128, _mm_loadu_ps, 4, 1); + + // SSE1 Miscellaneous functions: + Ret_M128(int, _mm_movemask_ps); + + // SSE1 Move functions: + Ret_M128_M128(__m128, _mm_move_ss); + Ret_M128_M128(__m128, _mm_movehl_ps); + Ret_M128_M128(__m128, _mm_movelh_ps); + + // SSE1 Set functions: + Ret_Float4(__m128, _mm_set_ps, 1); + Ret_Float(__m128, _mm_set_ps1, 1); + Ret_Float(__m128, _mm_set_ss, 1); + Ret_Float(__m128, _mm_set1_ps, 1); + Ret_Float4(__m128, _mm_setr_ps, 1); + + __m128 zero = _mm_setzero_ps(); + char str[256]; tostr(&zero, str); + printf("_mm_setzero_ps() = %s\n", str); + + // SSE1 Special Math instructions: + Ret_M128_M128(__m128, _mm_max_ps); + Ret_M128_M128(__m128, _mm_max_ss); + Ret_M128_M128(__m128, _mm_min_ps); + Ret_M128_M128(__m128, _mm_min_ss); + + // SSE1 Store instructions: + void_OutFloatPtr_M128(_mm_store_ps, float*, 16, 16); + void_OutFloatPtr_M128(_mm_store_ps1, float*, 16, 16); + void_OutFloatPtr_M128(_mm_store_ss, float*, 4, 1); + void_OutFloatPtr_M128(_mm_store1_ps, float*, 16, 16); + void_OutFloatPtr_M128(_mm_storeh_pi, __m64*, 8, 1); + void_OutFloatPtr_M128(_mm_storel_pi, __m64*, 8, 1); + void_OutFloatPtr_M128(_mm_storer_ps, float*, 16, 16); + void_OutFloatPtr_M128(_mm_storeu_ps, float*, 16, 1); + void_OutIntPtr_M128i(_mm_storeu_si16, unsigned short*, 2, 1); + void_OutIntPtr_M128i(_mm_storeu_si64, __m64*, 8, 1); + void_OutFloatPtr_M128(_mm_stream_ps, float*, 16, 16); + + // SSE1 Swizzle instructions: + Ret_M128_M128_Tint(__m128, _mm_shuffle_ps); + + __m128 m1 = _mm_set_ps(1.f, 2.f, 3.f, 4.f); + __m128 m2 = _mm_set_ps(5.f, 6.f, 7.f, 8.f); + __m128 m3 = _mm_set_ps(9.f, 10.f, 11.f, 12.f); + __m128 m4 = _mm_set_ps(13.f, 14.f, 15.f, 16.f); + _MM_TRANSPOSE4_PS(m1, m2, m3, m4); + tostr(&m1, str); printf("_MM_TRANSPOSE4_PS: m1 = %s\n", str); + tostr(&m2, str); printf("_MM_TRANSPOSE4_PS: m2 = %s\n", str); + tostr(&m3, str); printf("_MM_TRANSPOSE4_PS: m3 = %s\n", str); + tostr(&m4, str); printf("_MM_TRANSPOSE4_PS: m4 = %s\n", str); + + Ret_M128_M128(__m128, _mm_unpackhi_ps); + Ret_M128_M128(__m128, _mm_unpacklo_ps); + + // _mm_malloc and _mm_free + void *ptr = _mm_malloc(32, 16); + assert(((uintptr_t)ptr & 0xF) == 0); + _mm_free(ptr); + +#ifdef __EMSCRIPTEN__ + _mm_undefined(); +#endif +} diff --git a/tests/test_core.py b/tests/test_core.py index d983b44f46d7e..d75fcf1f98d8c 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -6017,6 +6017,19 @@ def run(): self.build(open(path_from_root('tests', 'test_wasm_intrinsics_simd.c')).read(), self.get_dir(), os.path.join(self.get_dir(), 'src.cpp')) + # Tests invoking the SIMD API via x86 SSE1 xmmintrin.h header (_mm_x() functions) + @wasm_simd + def test_sse1(self): + src = path_from_root('tests', 'sse', 'test_sse1.cpp') + run_process([shared.CLANG_CXX, src, '-msse', '-o', 'test_sse1', '-D_CRT_SECURE_NO_WARNINGS=1'] + shared.Building.get_native_building_args(), stdout=PIPE) + native_result = run_process('./test_sse1', stdout=PIPE, env=shared.Building.get_building_env(native=True)).stdout + + orig_args = self.emcc_args + self.emcc_args = orig_args + ['-I' + path_from_root('tests', 'sse'), '-msse'] + self.maybe_closure() + + self.do_run(open(src).read(), native_result) + @no_asan('call stack exceeded on some versions of node') def test_gcc_unmangler(self): self.emcc_args += ['-I' + path_from_root('third_party')] diff --git a/tools/shared.py b/tools/shared.py index 6f965839941af..e104f316c0353 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -706,7 +706,7 @@ def emsdk_ldflags(user_args): return ldflags -def emsdk_cflags(): +def emsdk_cflags(user_args=[]): # Disable system C and C++ include directories, and add our own (using # -isystem so they are last, like system dirs, which allows projects to # override them) @@ -734,6 +734,9 @@ def include_directive(paths): result += ['-Xclang', '-isystem' + path] return result + if '-msse' in user_args: + c_opts += ['-D__SSE__=1'] + include_directive([path_from_root('system', 'include', 'SSE')]) + # libcxx include paths must be defined before libc's include paths otherwise libcxx will not build if Settings.USE_CXX: c_opts += include_directive(cxx_include_paths) @@ -772,7 +775,7 @@ def get_cflags(user_args): if os.environ.get('EMMAKEN_NO_SDK') or '-nostdinc' in user_args: return c_opts - return c_opts + emsdk_cflags() + return c_opts + emsdk_cflags(user_args) # Utilities @@ -1245,6 +1248,13 @@ def remove_quotes(arg): else: return arg + @staticmethod + def get_native_building_args(): + if MACOS: + return ['--sysroot=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk'] + else: + return [''] + @staticmethod def get_building_env(native=False, doublequote_commands=False, cflags=[]): def nop(arg):