Alternatives to missing SSAT and USAT instructions in Arm64?










8















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?










share|improve this question






















  • Write their equivalent in C and let the compiler translate that code?

    – Ira Baxter
    Nov 13 '18 at 13:19











  • @IraBaxter writing saturation code in C requires a branch ("if value is over 255 then set it to 255"), which is something I'd rather avoid deep within an algorithm kernel.

    – itaych
    Nov 13 '18 at 13:53






  • 1





    See my answer for clamping to 255: codereview.stackexchange.com/a/6504/8792

    – Ira Baxter
    Nov 13 '18 at 14:31












  • @itaych - why can't you(r compiler) use conditional instructions instead of branching?

    – Toby Speight
    Nov 13 '18 at 14:57












  • @TobySpeight because arm64 doesn't have a whole lot of those. Do you have a particular solution in mind?

    – Jester
    Nov 13 '18 at 15:12















8















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?










share|improve this question






















  • Write their equivalent in C and let the compiler translate that code?

    – Ira Baxter
    Nov 13 '18 at 13:19











  • @IraBaxter writing saturation code in C requires a branch ("if value is over 255 then set it to 255"), which is something I'd rather avoid deep within an algorithm kernel.

    – itaych
    Nov 13 '18 at 13:53






  • 1





    See my answer for clamping to 255: codereview.stackexchange.com/a/6504/8792

    – Ira Baxter
    Nov 13 '18 at 14:31












  • @itaych - why can't you(r compiler) use conditional instructions instead of branching?

    – Toby Speight
    Nov 13 '18 at 14:57












  • @TobySpeight because arm64 doesn't have a whole lot of those. Do you have a particular solution in mind?

    – Jester
    Nov 13 '18 at 15:12













8












8








8








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?










share|improve this question














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?







assembly arm arm64






share|improve this question













share|improve this question











share|improve this question




share|improve this question










asked Nov 13 '18 at 10:46









itaychitaych

143111




143111












  • Write their equivalent in C and let the compiler translate that code?

    – Ira Baxter
    Nov 13 '18 at 13:19











  • @IraBaxter writing saturation code in C requires a branch ("if value is over 255 then set it to 255"), which is something I'd rather avoid deep within an algorithm kernel.

    – itaych
    Nov 13 '18 at 13:53






  • 1





    See my answer for clamping to 255: codereview.stackexchange.com/a/6504/8792

    – Ira Baxter
    Nov 13 '18 at 14:31












  • @itaych - why can't you(r compiler) use conditional instructions instead of branching?

    – Toby Speight
    Nov 13 '18 at 14:57












  • @TobySpeight because arm64 doesn't have a whole lot of those. Do you have a particular solution in mind?

    – Jester
    Nov 13 '18 at 15:12

















  • Write their equivalent in C and let the compiler translate that code?

    – Ira Baxter
    Nov 13 '18 at 13:19











  • @IraBaxter writing saturation code in C requires a branch ("if value is over 255 then set it to 255"), which is something I'd rather avoid deep within an algorithm kernel.

    – itaych
    Nov 13 '18 at 13:53






  • 1





    See my answer for clamping to 255: codereview.stackexchange.com/a/6504/8792

    – Ira Baxter
    Nov 13 '18 at 14:31












  • @itaych - why can't you(r compiler) use conditional instructions instead of branching?

    – Toby Speight
    Nov 13 '18 at 14:57












  • @TobySpeight because arm64 doesn't have a whole lot of those. Do you have a particular solution in mind?

    – Jester
    Nov 13 '18 at 15:12
















Write their equivalent in C and let the compiler translate that code?

– Ira Baxter
Nov 13 '18 at 13:19





Write their equivalent in C and let the compiler translate that code?

– Ira Baxter
Nov 13 '18 at 13:19













@IraBaxter writing saturation code in C requires a branch ("if value is over 255 then set it to 255"), which is something I'd rather avoid deep within an algorithm kernel.

– itaych
Nov 13 '18 at 13:53





@IraBaxter writing saturation code in C requires a branch ("if value is over 255 then set it to 255"), which is something I'd rather avoid deep within an algorithm kernel.

– itaych
Nov 13 '18 at 13:53




1




1





See my answer for clamping to 255: codereview.stackexchange.com/a/6504/8792

– Ira Baxter
Nov 13 '18 at 14:31






See my answer for clamping to 255: codereview.stackexchange.com/a/6504/8792

– Ira Baxter
Nov 13 '18 at 14:31














@itaych - why can't you(r compiler) use conditional instructions instead of branching?

– Toby Speight
Nov 13 '18 at 14:57






@itaych - why can't you(r compiler) use conditional instructions instead of branching?

– Toby Speight
Nov 13 '18 at 14:57














@TobySpeight because arm64 doesn't have a whole lot of those. Do you have a particular solution in mind?

– Jester
Nov 13 '18 at 15:12





@TobySpeight because arm64 doesn't have a whole lot of those. Do you have a particular solution in mind?

– Jester
Nov 13 '18 at 15:12












1 Answer
1






active

oldest

votes


















1














--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("%dn", sum);
printf("ran %d times, total time: %d, average time asm: %.7fn", 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







share|improve this answer

























  • Your 0.0 average time probably just shows that everything optimized away (like it should) with pure C, because you aren't using the results.

    – Peter Cordes
    Nov 14 '18 at 11:59











  • @PeterCordes thought about it, so i've added: volatile int64_t rr; and rr = num; after every __arm_ssat. still the same results.

    – efrat
    Nov 14 '18 at 13:53











Your Answer






StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");

StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "1"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);

else
createEditor();

);

function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);



);













draft saved

draft discarded


















StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53279286%2falternatives-to-missing-ssat-and-usat-instructions-in-arm64%23new-answer', 'question_page');

);

Post as a guest















Required, but never shown

























1 Answer
1






active

oldest

votes








1 Answer
1






active

oldest

votes









active

oldest

votes






active

oldest

votes









1














--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("%dn", sum);
printf("ran %d times, total time: %d, average time asm: %.7fn", 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







share|improve this answer

























  • Your 0.0 average time probably just shows that everything optimized away (like it should) with pure C, because you aren't using the results.

    – Peter Cordes
    Nov 14 '18 at 11:59











  • @PeterCordes thought about it, so i've added: volatile int64_t rr; and rr = num; after every __arm_ssat. still the same results.

    – efrat
    Nov 14 '18 at 13:53
















1














--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("%dn", sum);
printf("ran %d times, total time: %d, average time asm: %.7fn", 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







share|improve this answer

























  • Your 0.0 average time probably just shows that everything optimized away (like it should) with pure C, because you aren't using the results.

    – Peter Cordes
    Nov 14 '18 at 11:59











  • @PeterCordes thought about it, so i've added: volatile int64_t rr; and rr = num; after every __arm_ssat. still the same results.

    – efrat
    Nov 14 '18 at 13:53














1












1








1







--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("%dn", sum);
printf("ran %d times, total time: %d, average time asm: %.7fn", 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







share|improve this answer















--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("%dn", sum);
printf("ran %d times, total time: %d, average time asm: %.7fn", 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








share|improve this answer














share|improve this answer



share|improve this answer








edited Nov 14 '18 at 17:14

























answered Nov 14 '18 at 9:19









efratefrat

114




114












  • Your 0.0 average time probably just shows that everything optimized away (like it should) with pure C, because you aren't using the results.

    – Peter Cordes
    Nov 14 '18 at 11:59











  • @PeterCordes thought about it, so i've added: volatile int64_t rr; and rr = num; after every __arm_ssat. still the same results.

    – efrat
    Nov 14 '18 at 13:53


















  • Your 0.0 average time probably just shows that everything optimized away (like it should) with pure C, because you aren't using the results.

    – Peter Cordes
    Nov 14 '18 at 11:59











  • @PeterCordes thought about it, so i've added: volatile int64_t rr; and rr = num; after every __arm_ssat. still the same results.

    – efrat
    Nov 14 '18 at 13:53

















Your 0.0 average time probably just shows that everything optimized away (like it should) with pure C, because you aren't using the results.

– Peter Cordes
Nov 14 '18 at 11:59





Your 0.0 average time probably just shows that everything optimized away (like it should) with pure C, because you aren't using the results.

– Peter Cordes
Nov 14 '18 at 11:59













@PeterCordes thought about it, so i've added: volatile int64_t rr; and rr = num; after every __arm_ssat. still the same results.

– efrat
Nov 14 '18 at 13:53






@PeterCordes thought about it, so i've added: volatile int64_t rr; and rr = num; after every __arm_ssat. still the same results.

– efrat
Nov 14 '18 at 13:53




















draft saved

draft discarded
















































Thanks for contributing an answer to Stack Overflow!


  • Please be sure to answer the question. Provide details and share your research!

But avoid


  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.

To learn more, see our tips on writing great answers.




draft saved


draft discarded














StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53279286%2falternatives-to-missing-ssat-and-usat-instructions-in-arm64%23new-answer', 'question_page');

);

Post as a guest















Required, but never shown





















































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown

































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown







Popular posts from this blog

Use pre created SQLite database for Android project in kotlin

Darth Vader #20

Ondo