Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Alternatives to missing SSAT and USAT instructions in Arm64?

We are in the process of porting a major application from Arm32 to Arm64. Our algorithms make frequent use of the SSAT and USAT instructions. They are extremely useful, performing a left or right shift of any size then a signed or unsigned saturate to an arbitrary number of bits. This is extremely useful for image processing algorithms, because we can perform some math that generates a 32-bit integer result, then grab whatever bits we need out of that (saturated to the max/min of the bit depth of the output image) with a single instruction.

These instructions have inexplicably disappeared in Arm64 and the closest alternative we've found are SQSHRN / UQSHRN / SQSHLN / UQSHLN which perform a shift and saturate but are far more limited in the saturation they perform (USAT could saturate to any width, even 7 bits; the new instructions can only saturate to half the width of the input, e.g. 16 bits in case of a 32 bit input, which would require additional processing to achieve the needed result).

Can someone explain why these instructions were dropped, and what's the best way to efficiently port existing code that uses them?

like image 215
itaych Avatar asked Nov 13 '18 10:11

itaych


1 Answers

--UPDATE-- times on correct test were significantly slower when using non assembly code, i'll keep looking for different method

i compared this assembly code:

#define __arm_ssat(src, bits)   asm("ssat %[srcr], %[satv], %[srcr]"    :[srcr]"+r"(src):[satv]"I"(bits));

with this:

#define MAX_SIGNED_NUM(bits) ((1 << (bits -1)) -1)
#define __arm_ssat(src, bits)   {src = ((src > MAX_SIGNED_NUM(bits)) ? MAX_SIGNED_NUM(bits) : src);}

when running this --UPDATED TEST-- on 32bit device:

volatile  void assert_ssat_asm(int* buf, size_t loops){
    int64_t num = buf[0];
    int64_t num_a = buf[1];
    int64_t num_b = buf[2];
    int sum = 0;
    struct timeval tmv1; gettimeofday(&tmv1,NULL);
    for (int i = 0; i < loops; ++i){
        __arm_ssat(num, 8);
        sum+=num;
        assert( 127 == num);
        num = buf[0];

        __arm_ssat(num, 16);
        sum+=num;
        assert(32767 == num);

        __arm_ssat(num_a, 8);
        sum+=num;
        assert( 127 == num_a);
        num_a = buf[1];

        __arm_ssat(num_a, 16);
        sum+=num;
        assert( 690 == num_a);

        __arm_ssat(num_b, 8);
        sum+=num;
        assert( 127 == num_b);
        num_b = buf[2];

        __arm_ssat(num_b, 16);
        sum+=num;
        assert( 32767 == num_b);
    }
    struct timeval tmv2; gettimeofday(&tmv2,NULL);
    int tdiff_usec = (tmv2.tv_sec*1000000 + tmv2.tv_usec) - (tmv1.tv_sec*1000000 + tmv1.tv_usec);

    printf("%d\n", sum);
    printf("ran %d times, total time: %d,  average time asm: %.7f\n", loops, tdiff_usec, (double)tdiff_usec/loops);
}
int main ()
{
    int buf[] = { 69000, 690, 64000 };
    test_ssat(buf, 1000000);
}

I've got those results:

run 1000000 loops, average time reg: 0.0210270

run 1000000 loops, average time assembly: 0.0057960

like image 120
efrat Avatar answered Sep 27 '22 23:09

efrat