ARM での software packed 演算による飽和加算(2)

とりあえず RGB565 の飽和加算でのレジスタの数を出来る限り少なくしてみることにします。

    ; r7 = 0xf7de8410
    ; r8 = 0xfbdf0
    ; r9 = 0x7beffbff
    AND     r0, c0, c1          ; c0 & c1
    EOR     r1, c0, c1          ; c0 ^ c1
    AND     r1, r1, r7, LSR #16 ; r1 = (c0 ^ c1) & 0xf7de
    ADD     r0, r0, r1, LSR #1  ; r0 = (c0 & c1) + (r1 >> 1)
    AND     r0, r0, r7          ; c = r0 & 0x8410

    ADD     r0, r0, r8          ; c + 0xfbdf0
    AND     r0, r9, r0, LSR #5  ; r0 = (((c + 0xfbdf0)) >> 5) & 0xfbff;
    ADD     r0, r0, #0x200      ; r0 = r0 + 0x200
    EOR     r0, r0, r9, LSR #16 ; r0 = r0 ^ 0x7bef
    ADD     r1, c0, c1          ; c0 + c1
    SUB     r1, r1, r0          ; c0 + c1 - c
    ORR     r0, r1, r0          ; (c0 + c1 - c) | c

定数が 16bit だったので、それらを一つにまとめ、演算しています。
これでレジスタが2つ減りました。


ただ、この software packed 演算より、

u_short AddBlend(u_short c0, u_short c1)
{
    c = ((c0 & c1) + (((c0 ^ c1) & 0xf7de) >> 1)) & 0x8410;
    c = ((((c + 0x8010) >> 5) & 0x0821) + 0x7bef)^0x7bef;
    return (c0 + c1 - c) | c;
}

この式を計算した方が、レジスタの数は減りそうです。
さっきと同じように最適化してやると、

    ; r7 = 0xf7de8410
    ; r8 = 0x8010
    ; r9 = 0x7bef0821
    AND     r0, c0, c1          ; c0 & c1
    EOR     r1, c0, c1          ; c0 ^ c1
    AND     r1, r1, r7, LSR #16 ; r1 = (c0 ^ c1) & 0xf7de
    ADD     r0, r0, r1, LSR #1  ; r0 = (c0 & c1) + (r1 >> 1)
    AND     r0, r0, r7          ; c = r0 & 0x8410

    ADD     r0, r0, r8          ; r0 = c + 0x8010
    AND     r0, r9, r0, LSR #5  ; r0 = (r0 >> 5) & 0x0821
    ADD     r0, r0, r9, LSR #16 ; r0 = r0 + 0x7bef
    EOR     r0, r0, r9, LSR #16 ; r0 = r0 ^ 0x7bef
    ADD     r1, c0, c1          ; c0 + c1
    SUB     r1, r1, r0          ; c0 + c1 - c
    ORR     r0, r1, r0          ; (c0 + c1 - c) | c

このようになり、これだとレジスタの数は変わりませんが、0x8410, 0x8010, 0x0821 の値が似ていることを利用して、

0x8410 : 1000 0100 0001 0000
0x8010 : 1000 0000 0001 0000
0x0821 : 0000 1000 0010 0001

これらを一つにまとめてやります。
いろいろビットを動かして共通なビットを探してやると、

0x8010 : 1000000000010000
0x8410 :            1000010000010000
0x0821 :             0000100000100001

→ 1000 0000 0001 0000 1000 0010 0001

となるので、0x08010821 というビット列が求められます。つまり、

0x8410 : 0x08010821 >> 1
0x8010 : 0x08010821 >> 12
0x0821 : 0x08010821

で、それぞれの値を求めることが出来ます。


これを使ってもう一度最適化してやると、

    ; r7 = 0x7beff7de
    ; r8 = 0x08010821
    AND     r0, c0, c1          ; c0 & c1
    EOR     r1, c0, c1          ; c0 ^ c1
    AND     r1, r1, r7          ; r1 = (c0 ^ c1) & 0xf7de
    ADD     r0, r0, r1, LSR #1  ; r0 = (c0 & c1) + (r1 >> 1)
    AND     r0, r0, r8, LSR #1  ; c = r0 & 0x8410

    ADD     r0, r0, r8, LSR #12 ; r0 = c + 0x8010
    AND     r0, r8, r0, LSR #5  ; r0 = (r0 >> 5) & 0x0821
    ADD     r0, r7, r0, LSL #16 ; r0 = (r0 << 16) + 0x7beff7de
    EOR     r0, r0, r7          ; r0 = r0 ^ 0x7beff7de
    ADD     r1, c0, c1          ; r1 = c0 + c1
    SUB     r1, r1, r0, LSR #16 ; r1 = r1 - (r0 >> 16)
    ORR     r0, r1, r0, LSR #16 ; r1 | (r0 >> 16)

こうなって、更に一つレジスタが減りました。
途中で左16ビットシフトしながら計算して、最後に16ビット右シフトしていますが、これは最後の減算で、上位に変な値が残らないようにするためです。


これで、全部で6つのレジスタを使用しているわけですが、c0, c1 は RGB565 なので、これを一つのレジスタに詰めて、シフトしながら計算すれば、全部で5つのレジスタになります。

    ; r7 = 0x7beff7de
    ; r8 = 0x08010821
    ; r2 = c0 | (c1 << 16)
    AND     r0, r2, r2, LSR #16 ; r0 = r2 & (r2 >> 16)
    EOR     r1, r2, r2, LSR #16 ; r1 = r2 ^ (r2 >> 16)
    AND     r1, r1, r7          ; r1 = r1 & 0x7beff7de
    ADD     r0, r0, r1, LSR #1  ; r0 = r0 + (r1 >> 1)
    AND     r0, r0, r8, LSR #1  ; r0 = r0 & (0x08010821 >> 1)

    ADD     r0, r0, r8, LSR #12 ; r0 = r0 + (0x08010821 >> 1)
    AND     r0, r8, r0, LSR #5  ; r0 = (r0 >> 5) & 0x08010821
    ADD     r0, r7, r0, LSL #16 ; r0 = (r0 << 16) + 0x7beff7de
    EOR     r0, r0, r7          ; r0 = r0 ^ 0x7beff7de
    ADD     r1, r2, r2, LSR #16 ; r1 = r2 + (r2 >> 16)
    SUB     r1, r1, r0, LSR #16 ; r1 = r1 - (r0 >> 16)
    ORR     r0, r1, r0, LSR #16 ; r0 = r1 | (r0 >> 16)

上位16ビットにゴミが乗ってますが、ハーフワードでストアするので大丈夫でしょう。


最初が9つのレジスタを使用していたので、かなり削れましたヽ(´ー`)ノ



でもこれ、ちゃんと動くのかなぁ(;´Д`)