0

I'm looping some data, calculating some double and every 2 __m128d operations, I want to store the data on a __m128 float.

So 64+64 + 64+64 (2 __m128d) stored into 1 32+32+32+32 __m128.

I do somethings like this:

__m128d v_result;
__m128 v_result_float;

...

// some operations on v_result

// store the first two "slot" on float
v_result_float = _mm_cvtpd_ps(v_result);

// some operations on v_result
// I need to store the last two "slot" on float
v_result_float = _mm_cvtpd_ps(v_result); ?!?

But it overwrite (obviously) the first 2 float "slots" everytime.

How can I "space" the _mm_cvtpd_ps to start insert values to the 3° and 4° "slot", the second time?

Here's the complete code:

__m128d v_pA;
__m128d v_pB;
__m128d v_result;
__m128 v_result_float;

float *pCEnd = pTest + roundintup8(blockSize);
for (; pTest < pCEnd; pA += 8, pB += 8, pTest += 8) {
    v_pA = _mm_load_pd(pA);
    v_pB = _mm_load_pd(pB);
    v_result = _mm_add_pd(v_pA, v_pB);
    v_result = _mm_max_pd(v_boundLower, v_result);
    v_result = _mm_min_pd(v_boundUpper, v_result);
    v_result = _mm_mul_pd(v_rangeLn2per12, v_result);
    v_result = _mm_add_pd(v_minLn2per12, v_result);

    // two double processed: store in 1° and 2° float slot
    v_result_float = _mm_cvtpd_ps(v_result);

    v_pA = _mm_load_pd(pA + 2);
    v_pB = _mm_load_pd(pB + 2);
    v_result = _mm_add_pd(v_pA, v_pB);
    v_result = _mm_max_pd(v_boundLower, v_result);
    v_result = _mm_min_pd(v_boundUpper, v_result);
    v_result = _mm_mul_pd(v_rangeLn2per12, v_result);
    v_result = _mm_add_pd(v_minLn2per12, v_result);

    // another two double processed: store in 3° and 4° float slot
    v_result_float = _mm_cvtpd_ps(v_result); // fail
    v_result_float = someFunction(v_result_float);
    _mm_store_ps(pTest, v_result_float);

    v_pA = _mm_load_pd(pA + 4);
    v_pB = _mm_load_pd(pB + 4);
    v_result = _mm_add_pd(v_pA, v_pB);
    v_result = _mm_max_pd(v_boundLower, v_result);
    v_result = _mm_min_pd(v_boundUpper, v_result);
    v_result = _mm_mul_pd(v_rangeLn2per12, v_result);
    v_result = _mm_add_pd(v_minLn2per12, v_result);

    // two double processed: store in 1° and 2° float slot
    v_result_float = _mm_cvtpd_ps(v_result);

    v_pA = _mm_load_pd(pA + 6);
    v_pB = _mm_load_pd(pB + 6);
    v_result = _mm_add_pd(v_pA, v_pB);
    v_result = _mm_max_pd(v_boundLower, v_result);
    v_result = _mm_min_pd(v_boundUpper, v_result);
    v_result = _mm_mul_pd(v_rangeLn2per12, v_result);
    v_result = _mm_add_pd(v_minLn2per12, v_result);

    // another two double processed: store in 3° and 4° float slot
    v_result_float = _mm_cvtpd_ps(v_result); // fail
    v_result_float = someFunction(v_result_float);      
    _mm_store_ps(pTest + 4, v_result_float);
}
markzzz
  • 47,390
  • 120
  • 299
  • 507
  • 1
    `v_result_float = _mm_movelh_ps(_mm_cvtpd_ps(v_result1), _mm_cvtpd_ps(v_result2));` – chtz Feb 04 '19 at 14:30
  • @chtz this will introduce a "new" register. What If I only have `v_result`? – markzzz Feb 04 '19 at 14:32
  • 1
    Are you sure you are lacking registers at the point where you want to join them? The `_mm_cvtpd_ps` can happen in-place, if the `double` values are not required afterwards (and the compiler will do that, if it makes sense) – chtz Feb 04 '19 at 14:35
  • True ;) If you want the accepted answer, answer with this ;) – markzzz Feb 04 '19 at 14:40

1 Answers1

2

You need to move the low words of the second conversion to the high words of the result of the first conversion using movlhps (_mm_movelh_ps). Simplified example:

#include <immintrin.h>

__m128d some_double_operation(__m128d);
__m128 some_float_operation(__m128);

void foo(double const* input, float* output, int size)
{
    // assuming everything is already nicely aligned ...
    for(int i=0; i<size; i+=4, input+=4, output+=4)
    {
        __m128d res_lo = some_double_operation(_mm_load_pd(input));
        __m128d res_hi = some_double_operation(_mm_load_pd(input+2));
        __m128 res_float = _mm_movelh_ps(_mm_cvtpd_ps(res_lo), _mm_cvtpd_ps(res_hi));
        __m128 res_final = some_float_operation(res_float);
        _mm_store_ps(output, res_final);
    }
}

Godbolt-Demo: https://godbolt.org/z/wgKjxN.

If some_double_operation is inlined, the compiler will likely keep the result of the first double operation in a register not used by the second call to the function, thus not require to store anything to memory.

chtz
  • 17,329
  • 4
  • 26
  • 56
  • 1
    I might have written a `__m128 packpd_ps(__m128d low, __m128d high)` function that just wrapped the 2x convert + shuffle, which is exactly the same operation as integer pack operations like `_mm_packs_epi16` (including FP saturation to +-Infinity from `_mm_cvtpd_ps`). And maybe a `__m128 load_convertpd_ps(const double *)` wrapper for it. But yes, `movlhps` is the right instruction here, shorter machine code than `unpcklpd` which does the same shuffle. – Peter Cordes Feb 05 '19 at 02:05