shithub: dav1d

Download patch

ref: 8dd9c651857dc1dcb9dd2b0ffca4177b47779905
parent: b4291523d926658c68c29f7f5d2b270e19ed39c2
author: Martin Storsjö <martin@martin.st>
date: Sat Jun 20 19:46:55 EDT 2020

arm32: ipred: Port 8 bpc NEON implementations of remaining arm64 funtions

This matches was is implemented for arm64 so far.

Align the dav1d_sm_weights table to allow aligned loads from it.

Relative speedups over C code (vs potentially autovectorized code, built
with Clang):

                                Cortex A7     A8     A9    A53    A72    A73
intra_pred_paeth_w4_8bpc_neon:       4.81   7.61   5.82   5.50   5.61   6.94
intra_pred_paeth_w8_8bpc_neon:       7.83  11.95   9.51  11.05   8.90  10.51
intra_pred_paeth_w16_8bpc_neon:      4.86   4.49   3.90   4.60   3.76   3.54
intra_pred_paeth_w32_8bpc_neon:      4.55   4.03   3.52   4.27   3.30   3.21
intra_pred_paeth_w64_8bpc_neon:      4.38   3.72   3.32   3.95   3.08   3.00
intra_pred_smooth_h_w4_8bpc_neon:    5.74  10.80   5.32   6.79   4.77   6.48
intra_pred_smooth_h_w8_8bpc_neon:   10.59  17.95   9.39  16.03   6.94   8.98
intra_pred_smooth_h_w16_8bpc_neon:   2.81   3.19   2.12   3.70   2.90   3.59
intra_pred_smooth_h_w32_8bpc_neon:   2.63   2.41   1.86   3.44   2.24   2.66
intra_pred_smooth_h_w64_8bpc_neon:   2.42   2.52   1.79   3.24   1.81   2.11
intra_pred_smooth_v_w4_8bpc_neon:    4.15   7.99   3.46   4.63   3.83   4.39
intra_pred_smooth_v_w8_8bpc_neon:    7.31  12.42   7.04  10.00   4.26   6.20
intra_pred_smooth_v_w16_8bpc_neon:   3.70   3.44   2.53   3.33   2.76   3.21
intra_pred_smooth_v_w32_8bpc_neon:   3.91   3.74   2.70   3.51   2.50   2.96
intra_pred_smooth_v_w64_8bpc_neon:   4.03   3.94   2.80   3.64   2.36   2.80
intra_pred_smooth_w4_8bpc_neon:      4.09   7.74   4.54   4.79   3.26   5.10
intra_pred_smooth_w8_8bpc_neon:      5.63   8.93   6.62   8.28   3.73   6.04
intra_pred_smooth_w16_8bpc_neon:     3.97   3.40   3.32   3.74   3.01   3.77
intra_pred_smooth_w32_8bpc_neon:     3.75   3.14   3.07   3.28   2.65   3.17
intra_pred_smooth_w64_8bpc_neon:     3.60   3.04   2.93   2.97   2.35   2.85
intra_pred_filter_w4_8bpc_neon:      5.54   6.43   4.90   7.26   3.44   4.61
intra_pred_filter_w8_8bpc_neon:      7.05   7.15   5.50  10.05   4.29   6.02
intra_pred_filter_w16_8bpc_neon:     7.36   6.46   5.27  11.51   4.75   6.70
intra_pred_filter_w32_8bpc_neon:     7.56   6.32   5.01  12.34   4.47   6.97
pal_pred_w4_8bpc_neon:               5.47   7.76   4.40   5.20   8.32   7.03
pal_pred_w8_8bpc_neon:              11.11  14.12   8.44  13.95  11.88  12.43
pal_pred_w16_8bpc_neon:             14.38  20.95   9.84  17.43  14.77  13.56
pal_pred_w32_8bpc_neon:             12.91  19.85  10.87  19.03  14.63  14.62
pal_pred_w64_8bpc_neon:             14.01  19.23  10.82  19.82  16.23  16.32
cfl_ac_420_w4_8bpc_neon:             8.11  13.41   7.92   9.26  10.55   9.36
cfl_ac_420_w8_8bpc_neon:             7.77  15.71   7.69   8.94   9.76   8.56
cfl_ac_420_w16_8bpc_neon:            7.72  13.71   8.30   9.05   9.81   9.02
cfl_ac_422_w4_8bpc_neon:             8.85  15.80   8.26  10.97  13.04  10.00
cfl_ac_422_w8_8bpc_neon:             8.77  16.96   7.57  10.46  12.16   9.92
cfl_ac_422_w16_8bpc_neon:            8.28  14.91   7.16   9.69  10.57   9.18
cfl_ac_444_w4_8bpc_neon:             7.47  14.13   7.50   9.76  11.11   9.39
cfl_ac_444_w8_8bpc_neon:             6.81  15.46   5.27   9.11  12.09   9.76
cfl_ac_444_w16_8bpc_neon:            6.11  13.68   4.62   8.17  10.78   8.92
cfl_ac_444_w32_8bpc_neon:            5.71  12.11   4.28   7.53   9.53   8.52
cfl_pred_cfl_128_w4_8bpc_neon:       7.46  12.63   8.48   8.03   7.64   9.29
cfl_pred_cfl_128_w8_8bpc_neon:       5.05   5.16   3.79   4.64   5.07   4.42
cfl_pred_cfl_128_w16_8bpc_neon:      4.44   5.17   3.65   4.20   4.41   4.74
cfl_pred_cfl_128_w32_8bpc_neon:      4.51   5.25   3.67   4.29   4.39   4.73
cfl_pred_cfl_left_w4_8bpc_neon:      6.60  11.74   7.75   6.91   7.44   9.14
cfl_pred_cfl_left_w8_8bpc_neon:      4.92   5.15   3.80   4.41   5.44   4.81
cfl_pred_cfl_left_w16_8bpc_neon:     4.40   5.26   3.66   4.10   4.63   4.94
cfl_pred_cfl_left_w32_8bpc_neon:     4.50   5.31   3.68   4.25   4.43   4.82
cfl_pred_cfl_top_w4_8bpc_neon:       7.00  11.88   7.88   7.50   7.43   9.68
cfl_pred_cfl_top_w8_8bpc_neon:       4.96   5.07   3.78   4.51   5.31   4.75
cfl_pred_cfl_top_w16_8bpc_neon:      4.42   5.31   3.69   4.16   4.60   4.93
cfl_pred_cfl_top_w32_8bpc_neon:      4.52   5.36   3.71   4.29   4.47   4.83
cfl_pred_cfl_w4_8bpc_neon:           5.92  10.54   7.25   6.21   6.79   8.33
cfl_pred_cfl_w8_8bpc_neon:           4.67   5.16   3.77   4.14   5.20   4.71
cfl_pred_cfl_w16_8bpc_neon:          4.29   5.29   3.70   3.97   4.53   4.86
cfl_pred_cfl_w32_8bpc_neon:          4.47   5.34   3.72   4.20   4.42   4.83

--- a/src/arm/32/ipred.S
+++ b/src/arm/32/ipred.S
@@ -1,6 +1,6 @@
 /*
  * Copyright © 2018, VideoLAN and dav1d authors
- * Copyright © 2019, Martin Storsjo
+ * Copyright © 2020, Martin Storsjo
  * Copyright © 2019, B Krishnan Iyer
  * All rights reserved.
  *
@@ -817,3 +817,2143 @@
         pop             {r4-r6, pc}
 endfunc
 
+// void ipred_paeth_8bpc_neon(pixel *dst, const ptrdiff_t stride,
+//                            const pixel *const topleft,
+//                            const int width, const int height, const int a,
+//                            const int max_width, const int max_height);
+function ipred_paeth_8bpc_neon, export=1
+        push            {r4-r8, lr}
+        ldr             r4,  [sp, #24]
+        clz             lr,  r3
+        adr             r5,  L(ipred_paeth_tbl)
+        sub             lr,  lr,  #25
+        ldr             lr,  [r5, lr, lsl #2]
+        vld1.8          {d4[], d5[]},  [r2]
+        add             r8,  r2,  #1
+        sub             r2,  r2,  #4
+        add             r5,  r5,  lr
+        mov             r7,  #-4
+        add             r6,  r0,  r1
+        lsl             r1,  r1,  #1
+        bx              r5
+
+        .align 2
+L(ipred_paeth_tbl):
+        .word 640f - L(ipred_paeth_tbl) + CONFIG_THUMB
+        .word 320f - L(ipred_paeth_tbl) + CONFIG_THUMB
+        .word 160f - L(ipred_paeth_tbl) + CONFIG_THUMB
+        .word 80f  - L(ipred_paeth_tbl) + CONFIG_THUMB
+        .word 40f  - L(ipred_paeth_tbl) + CONFIG_THUMB
+
+40:
+        vld1.32         {d6[], d7[]},  [r8]
+        vsubl.u8        q8,  d6,  d4  // top - topleft
+4:
+        vld4.8          {d0[], d1[], d2[], d3[]},  [r2, :32], r7
+        vzip.32         d0,  d1
+        vzip.32         d2,  d3
+        vaddw.u8        q9,  q8,  d0
+        vaddw.u8        q10, q8,  d2
+        vqmovun.s16     d18, q9       // base
+        vqmovun.s16     d19, q10
+        vmov            d1,  d2
+        vabd.u8         q10, q3,  q9  // tdiff
+        vabd.u8         q11, q2,  q9  // tldiff
+        vabd.u8         q9,  q0,  q9  // ldiff
+        vmin.u8         q12, q10, q11 // min(tdiff, tldiff)
+        vcge.u8         q10, q11, q10 // tldiff >= tdiff
+        vcge.u8         q9,  q12, q9  // min(tdiff, tldiff) >= ldiff
+        vbsl            q10, q3,  q2  // tdiff <= tldiff ? top : topleft
+        vbit            q10, q0,  q9  // ldiff <= min ? left : ...
+        vst1.32         {d21[1]}, [r0, :32], r1
+        vst1.32         {d21[0]}, [r6, :32], r1
+        subs            r4,  r4,  #4
+        vst1.32         {d20[1]}, [r0, :32], r1
+        vst1.32         {d20[0]}, [r6, :32], r1
+        bgt             4b
+        pop             {r4-r8, pc}
+80:
+        vld1.8          {d6},  [r8]
+        vsubl.u8        q8,  d6,  d4  // top - topleft
+        vmov            d7,  d6
+8:
+        vld4.8          {d0[], d1[], d2[], d3[]},  [r2, :32], r7
+        vaddw.u8        q9,  q8,  d0
+        vaddw.u8        q10, q8,  d1
+        vaddw.u8        q11, q8,  d2
+        vaddw.u8        q12, q8,  d3
+        vqmovun.s16     d18, q9       // base
+        vqmovun.s16     d19, q10
+        vqmovun.s16     d20, q11
+        vqmovun.s16     d21, q12
+        vabd.u8         q11, q3,  q9  // tdiff
+        vabd.u8         q12, q3,  q10
+        vabd.u8         q13, q2,  q9  // tldiff
+        vabd.u8         q14, q2,  q10
+        vabd.u8         q10, q1,  q10 // ldiff
+        vabd.u8         q9,  q0,  q9
+        vmin.u8         q15, q12, q14 // min(tdiff, tldiff)
+        vcge.u8         q12, q14, q12 // tldiff >= tdiff
+        vmin.u8         q14, q11, q13 // min(tdiff, tldiff)
+        vcge.u8         q11, q13, q11 // tldiff >= tdiff
+        vcge.u8         q10, q15, q10 // min(tdiff, tldiff) >= ldiff
+        vcge.u8         q9,  q14, q9
+        vbsl            q12, q3,  q2  // tdiff <= tldiff ? top : topleft
+        vbsl            q11, q3,  q2
+        vbit            q12, q1,  q10 // ldiff <= min ? left : ...
+        vbit            q11, q0,  q9
+        vst1.8          {d25}, [r0, :64], r1
+        vst1.8          {d24}, [r6, :64], r1
+        subs            r4,  r4,  #4
+        vst1.8          {d23}, [r0, :64], r1
+        vst1.8          {d22}, [r6, :64], r1
+        bgt             8b
+        pop             {r4-r8, pc}
+160:
+320:
+640:
+        vld1.8          {d6},  [r8]!
+        mov             r12, r3
+        // Set up pointers for four rows in parallel; r0, r6, r5, lr
+        add             r5,  r0,  r1
+        add             lr,  r6,  r1
+        lsl             r1,  r1,  #1
+        sub             r1,  r1,  r3
+1:
+        vld4.8          {d0[], d1[], d2[], d3[]},  [r2, :32], r7
+2:
+        vsubl.u8        q8,  d6,  d4  // top - topleft
+        vmov            d7,  d6
+        vaddw.u8        q9,  q8,  d0
+        vaddw.u8        q10, q8,  d1
+        vaddw.u8        q11, q8,  d2
+        vaddw.u8        q12, q8,  d3
+        vqmovun.s16     d18, q9       // base
+        vqmovun.s16     d19, q10
+        vqmovun.s16     d20, q11
+        vqmovun.s16     d21, q12
+        vabd.u8         q11, q3,  q9  // tdiff
+        vabd.u8         q12, q3,  q10
+        vabd.u8         q13, q2,  q9  // tldiff
+        vabd.u8         q14, q2,  q10
+        vabd.u8         q10, q1,  q10 // ldiff
+        vabd.u8         q9,  q0,  q9
+        vmin.u8         q15, q12, q14 // min(tdiff, tldiff)
+        vcge.u8         q12, q14, q12 // tldiff >= tdiff
+        vmin.u8         q14, q11, q13 // min(tdiff, tldiff)
+        vcge.u8         q11, q13, q11 // tldiff >= tdiff
+        vcge.u8         q10, q15, q10 // min(tdiff, tldiff) >= ldiff
+        vcge.u8         q9,  q14, q9
+        vbsl            q12, q3,  q2  // tdiff <= tldiff ? top : topleft
+        vbsl            q11, q3,  q2
+        vbit            q12, q1,  q10 // ldiff <= min ? left : ...
+        vbit            q11, q0,  q9
+        subs            r3,  r3,  #8
+        vst1.8          {d25}, [r0, :64]!
+        vst1.8          {d24}, [r6, :64]!
+        vst1.8          {d23}, [r5, :64]!
+        vst1.8          {d22}, [lr, :64]!
+        ble             8f
+        vld1.8          {d6},  [r8]!
+        b               2b
+8:
+        subs            r4,  r4,  #4
+        ble             9f
+        // End of horizontal loop, move pointers to next four rows
+        sub             r8,  r8,  r12
+        add             r0,  r0,  r1
+        add             r6,  r6,  r1
+        vld1.8          {d6},  [r8]!
+        add             r5,  r5,  r1
+        add             lr,  lr,  r1
+        mov             r3,  r12
+        b               1b
+9:
+        pop             {r4-r8, pc}
+endfunc
+
+// void ipred_smooth_8bpc_neon(pixel *dst, const ptrdiff_t stride,
+//                             const pixel *const topleft,
+//                             const int width, const int height, const int a,
+//                             const int max_width, const int max_height);
+function ipred_smooth_8bpc_neon, export=1
+        push            {r4-r10, lr}
+        ldr             r4,  [sp, #32]
+        movrel          r10, X(sm_weights)
+        add             r12, r10, r4
+        add             r10, r10, r3
+        clz             r9,  r3
+        adr             r5,  L(ipred_smooth_tbl)
+        sub             lr,  r2,  r4
+        sub             r9,  r9,  #25
+        ldr             r9,  [r5, r9, lsl #2]
+        vld1.8          {d4[]},  [lr] // bottom
+        add             r8,  r2,  #1
+        add             r5,  r5,  r9
+        add             r6,  r0,  r1
+        lsl             r1,  r1,  #1
+        bx              r5
+
+        .align 2
+L(ipred_smooth_tbl):
+        .word 640f - L(ipred_smooth_tbl) + CONFIG_THUMB
+        .word 320f - L(ipred_smooth_tbl) + CONFIG_THUMB
+        .word 160f - L(ipred_smooth_tbl) + CONFIG_THUMB
+        .word 80f  - L(ipred_smooth_tbl) + CONFIG_THUMB
+        .word 40f  - L(ipred_smooth_tbl) + CONFIG_THUMB
+
+40:
+        vld1.32         {d16[]}, [r8]       // top
+        vld1.32         {d18[]}, [r10, :32] // weights_hor
+        sub             r2,  r2,  #4
+        mov             r7,  #-4
+        vdup.8          q3,  d16[3]   // right
+        vsubl.u8        q8,  d16, d4  // top-bottom
+        vmovl.u8        q9,  d18      // weights_hor
+4:
+        vld4.8          {d0[],  d1[],  d2[],  d3[]},  [r2,  :32], r7 // left
+        vld4.8          {d20[], d21[], d22[], d23[]}, [r12, :32]!    // weights_ver
+        vshll.i8        q12, d6,  #8  // right*256
+        vshll.i8        q13, d6,  #8
+        vzip.32         d1,  d0       // left, flipped
+        vzip.32         d3,  d2
+        vzip.32         d20, d21      // weights_ver
+        vzip.32         d22, d23
+        vshll.i8        q14, d4,  #8  // bottom*256
+        vshll.i8        q15, d4,  #8
+        vsubl.u8        q0,  d1,  d6  // left-right
+        vsubl.u8        q1,  d3,  d6
+        vmovl.u8        q10, d20      // weights_ver
+        vmovl.u8        q11, d22
+        vmla.i16        q12, q1,  q9  // right*256  + (left-right)*weights_hor
+        vmla.i16        q13, q0,  q9  // (left flipped)
+        vmla.i16        q14, q8,  q10 // bottom*256 + (top-bottom)*weights_ver
+        vmla.i16        q15, q8,  q11
+        vhadd.u16       q12, q12, q14
+        vhadd.u16       q13, q13, q15
+        vrshrn.i16      d24, q12, #8
+        vrshrn.i16      d25, q13, #8
+        vst1.32         {d24[0]}, [r0, :32], r1
+        vst1.32         {d24[1]}, [r6, :32], r1
+        subs            r4,  r4,  #4
+        vst1.32         {d25[0]}, [r0, :32], r1
+        vst1.32         {d25[1]}, [r6, :32], r1
+        bgt             4b
+        pop             {r4-r10, pc}
+80:
+        vld1.8          {d16}, [r8]       // top
+        vld1.8          {d18}, [r10, :64] // weights_hor
+        sub             r2,  r2,  #2
+        mov             r7,  #-2
+        vdup.8          q3,  d16[7]   // right
+        vsubl.u8        q8,  d16, d4  // top-bottom
+        vmovl.u8        q9,  d18      // weights_hor
+8:
+        vld2.8          {d0[],  d1[]},  [r2,  :16], r7 // left
+        vld2.8          {d20[], d22[]}, [r12, :16]!    // weights_ver
+        vshll.i8        q12, d6,  #8  // right*256
+        vshll.i8        q13, d6,  #8
+        vshll.i8        q14, d4,  #8  // bottom*256
+        vshll.i8        q15, d4,  #8
+        vsubl.u8        q1,  d0,  d6  // left-right (left flipped)
+        vsubl.u8        q0,  d1,  d6
+        vmovl.u8        q10, d20      // weights_ver
+        vmovl.u8        q11, d22
+        vmla.i16        q12, q0,  q9  // right*256  + (left-right)*weights_hor
+        vmla.i16        q13, q1,  q9
+        vmla.i16        q14, q8,  q10 // bottom*256 + (top-bottom)*weights_ver
+        vmla.i16        q15, q8,  q11
+        vhadd.u16       q12, q12, q14
+        vhadd.u16       q13, q13, q15
+        vrshrn.i16      d24, q12, #8
+        vrshrn.i16      d25, q13, #8
+        subs            r4,  r4,  #2
+        vst1.8          {d24}, [r0, :64], r1
+        vst1.8          {d25}, [r6, :64], r1
+        bgt             8b
+        pop             {r4-r10, pc}
+160:
+320:
+640:
+        add             lr,  r2,  r3
+        sub             r2,  r2,  #2
+        mov             r7,  #-2
+        vld1.8          {d6[], d7[]}, [lr] // right
+        sub             r1,  r1,  r3
+        mov             r9,  r3
+
+1:
+        vld2.8          {d0[],  d1[]},  [r2,  :16], r7 // left
+        vld2.8          {d20[], d22[]}, [r12, :16]!    // weights_ver
+        vsubl.u8        q1,  d0,  d6  // left-right (left flipped)
+        vsubl.u8        q0,  d1,  d6
+        vmovl.u8        q10, d20      // weights_ver
+        vmovl.u8        q11, d22
+2:
+        vld1.8          {d16}, [r8]!       // top
+        vld1.8          {d18}, [r10, :64]! // weights_hor
+        vshll.i8        q12, d6,  #8  // right*256
+        vshll.i8        q13, d6,  #8
+        vmovl.u8        q9,  d18      // weights_hor
+        vshll.i8        q14, d4,  #8  // bottom*256
+        vshll.i8        q15, d4,  #8
+        vsubl.u8        q8,  d16, d4  // top-bottom
+        vmla.i16        q12, q0,  q9  // right*256  + (left-right)*weights_hor
+        vmla.i16        q13, q1,  q9
+        vmla.i16        q14, q8,  q10 // bottom*256 + (top-bottom)*weights_ver
+        vmla.i16        q15, q8,  q11
+        vhadd.u16       q12, q12, q14
+        vhadd.u16       q13, q13, q15
+        vrshrn.i16      d24, q12, #8
+        vrshrn.i16      d25, q13, #8
+        subs            r3,  r3,  #8
+        vst1.8          {d24}, [r0, :64]!
+        vst1.8          {d25}, [r6, :64]!
+        bgt             2b
+        subs            r4,  r4,  #2
+        ble             9f
+        sub             r8,  r8,  r9
+        sub             r10, r10, r9
+        add             r0,  r0,  r1
+        add             r6,  r6,  r1
+        mov             r3,  r9
+        b               1b
+9:
+        pop             {r4-r10, pc}
+endfunc
+
+// void ipred_smooth_v_8bpc_neon(pixel *dst, const ptrdiff_t stride,
+//                               const pixel *const topleft,
+//                               const int width, const int height, const int a,
+//                               const int max_width, const int max_height);
+function ipred_smooth_v_8bpc_neon, export=1
+        push            {r4-r7, lr}
+        ldr             r4,  [sp, #20]
+        movrel          r7,  X(sm_weights)
+        add             r7,  r7,  r4
+        clz             lr,  r3
+        adr             r5,  L(ipred_smooth_v_tbl)
+        sub             r12, r2,  r4
+        sub             lr,  lr,  #25
+        ldr             lr,  [r5, lr, lsl #2]
+        vld1.8          {d4[]},  [r12] // bottom
+        add             r2,  r2,  #1
+        add             r5,  r5,  lr
+        add             r6,  r0,  r1
+        lsl             r1,  r1,  #1
+        bx              r5
+
+        .align 2
+L(ipred_smooth_v_tbl):
+        .word 640f - L(ipred_smooth_v_tbl) + CONFIG_THUMB
+        .word 320f - L(ipred_smooth_v_tbl) + CONFIG_THUMB
+        .word 160f - L(ipred_smooth_v_tbl) + CONFIG_THUMB
+        .word 80f  - L(ipred_smooth_v_tbl) + CONFIG_THUMB
+        .word 40f  - L(ipred_smooth_v_tbl) + CONFIG_THUMB
+
+40:
+        vld1.32         {d6[]}, [r2]  // top
+        vsubl.u8        q3,  d6,  d4  // top-bottom
+4:
+        vld4.8          {d16[], d17[], d18[], d19[]}, [r7, :32]! // weights_ver
+        vshll.i8        q10, d4,  #8  // bottom*256
+        vshll.i8        q11, d4,  #8
+        vzip.32         d16, d17      // weights_ver
+        vzip.32         d18, d19
+        vmovl.u8        q8,  d16      // weights_ver
+        vmovl.u8        q9,  d18
+        subs            r4,  r4,  #4
+        vmla.i16        q10, q3,  q8  // bottom*256 + (top-bottom)*weights_ver
+        vmla.i16        q11, q3,  q9
+        vrshrn.i16      d20, q10, #8
+        vrshrn.i16      d21, q11, #8
+        vst1.32         {d20[0]}, [r0, :32], r1
+        vst1.32         {d20[1]}, [r6, :32], r1
+        vst1.32         {d21[0]}, [r0, :32], r1
+        vst1.32         {d21[1]}, [r6, :32], r1
+        bgt             4b
+        pop             {r4-r7, pc}
+80:
+        vld1.8          {d6}, [r2]    // top
+        vsubl.u8        q3,  d6,  d4  // top-bottom
+8:
+        vld4.8          {d16[], d18[], d20[], d22[]}, [r7, :32]! // weights_ver
+        vshll.i8        q12, d4,  #8  // bottom*256
+        vshll.i8        q13, d4,  #8
+        vshll.i8        q14, d4,  #8
+        vshll.i8        q15, d4,  #8
+        vmovl.u8        q8,  d16      // weights_ver
+        vmovl.u8        q9,  d18
+        vmovl.u8        q10, d20
+        vmovl.u8        q11, d22
+        vmla.i16        q12, q3,  q8  // bottom*256 + (top-bottom)*weights_ver
+        vmla.i16        q13, q3,  q9
+        vmla.i16        q14, q3,  q10
+        vmla.i16        q15, q3,  q11
+        vrshrn.i16      d24, q12, #8
+        vrshrn.i16      d25, q13, #8
+        vrshrn.i16      d26, q14, #8
+        vrshrn.i16      d27, q15, #8
+        vst1.8          {d24}, [r0, :64], r1
+        vst1.8          {d25}, [r6, :64], r1
+        subs            r4,  r4,  #4
+        vst1.8          {d26}, [r0, :64], r1
+        vst1.8          {d27}, [r6, :64], r1
+        bgt             8b
+        pop             {r4-r7, pc}
+160:
+320:
+640:
+        vpush           {q4-q7}
+        // Set up pointers for four rows in parallel; r0, r6, r5, lr
+        add             r5,  r0,  r1
+        add             lr,  r6,  r1
+        lsl             r1,  r1,  #1
+        sub             r1,  r1,  r3
+        mov             r12, r3
+
+1:
+        vld4.8          {d8[], d10[], d12[], d14[]}, [r7, :32]! // weights_ver
+        vmovl.u8        q4,  d8       // weights_ver
+        vmovl.u8        q5,  d10
+        vmovl.u8        q6,  d12
+        vmovl.u8        q7,  d14
+2:
+        vld1.8          {q3}, [r2]!   // top
+        vshll.i8        q8,  d4,  #8  // bottom*256
+        vshll.i8        q9,  d4,  #8
+        vshll.i8        q10, d4,  #8
+        vshll.i8        q11, d4,  #8
+        vsubl.u8        q0,  d6,  d4  // top-bottom
+        vsubl.u8        q1,  d7,  d4
+        vshll.i8        q12, d4,  #8
+        vshll.i8        q13, d4,  #8
+        vshll.i8        q14, d4,  #8
+        vshll.i8        q15, d4,  #8
+        vmla.i16        q8,  q0,  q4  // bottom*256 + (top-bottom)*weights_ver
+        vmla.i16        q9,  q1,  q4
+        vmla.i16        q10, q0,  q5
+        vmla.i16        q11, q1,  q5
+        vmla.i16        q12, q0,  q6  // bottom*256 + (top-bottom)*weights_ver
+        vmla.i16        q13, q1,  q6
+        vmla.i16        q14, q0,  q7
+        vmla.i16        q15, q1,  q7
+        vrshrn.i16      d16, q8,  #8
+        vrshrn.i16      d17, q9,  #8
+        vrshrn.i16      d18, q10, #8
+        vrshrn.i16      d19, q11, #8
+        vrshrn.i16      d20, q12, #8
+        vrshrn.i16      d21, q13, #8
+        vrshrn.i16      d22, q14, #8
+        vrshrn.i16      d23, q15, #8
+        subs            r3,  r3,  #16
+        vst1.8          {q8},  [r0, :128]!
+        vst1.8          {q9},  [r6, :128]!
+        vst1.8          {q10}, [r5, :128]!
+        vst1.8          {q11}, [lr, :128]!
+        bgt             2b
+        subs            r4,  r4,  #4
+        ble             9f
+        sub             r2,  r2,  r12
+        add             r0,  r0,  r1
+        add             r6,  r6,  r1
+        add             r5,  r5,  r1
+        add             lr,  lr,  r1
+        mov             r3,  r12
+        b               1b
+9:
+        vpop            {q4-q7}
+        pop             {r4-r7, pc}
+endfunc
+
+// void ipred_smooth_h_8bpc_neon(pixel *dst, const ptrdiff_t stride,
+//                               const pixel *const topleft,
+//                               const int width, const int height, const int a,
+//                               const int max_width, const int max_height);
+function ipred_smooth_h_8bpc_neon, export=1
+        push            {r4-r8, lr}
+        ldr             r4,  [sp, #24]
+        movrel          r8,  X(sm_weights)
+        add             r8,  r8,  r3
+        clz             lr,  r3
+        adr             r5,  L(ipred_smooth_h_tbl)
+        add             r12, r2,  r3
+        sub             lr,  lr,  #25
+        ldr             lr,  [r5, lr, lsl #2]
+        vld1.8          {d4[]},  [r12] // right
+        add             r5,  r5,  lr
+        add             r6,  r0,  r1
+        lsl             r1,  r1,  #1
+        bx              r5
+
+        .align 2
+L(ipred_smooth_h_tbl):
+        .word 640f - L(ipred_smooth_h_tbl) + CONFIG_THUMB
+        .word 320f - L(ipred_smooth_h_tbl) + CONFIG_THUMB
+        .word 160f - L(ipred_smooth_h_tbl) + CONFIG_THUMB
+        .word 80f  - L(ipred_smooth_h_tbl) + CONFIG_THUMB
+        .word 40f  - L(ipred_smooth_h_tbl) + CONFIG_THUMB
+
+40:
+        vld1.32         {d6[]}, [r8, :32] // weights_hor
+        sub             r2,  r2,  #4
+        mov             r7,  #-4
+        vmovl.u8        q3,  d6       // weights_hor
+4:
+        vld4.8          {d0[], d1[], d2[], d3[]},  [r2, :32], r7 // left
+        vshll.i8        q8,  d4,  #8  // right*256
+        vshll.i8        q9,  d4,  #8
+        vzip.32         d3,  d2       // left, flipped
+        vzip.32         d1,  d0
+        vsubl.u8        q1,  d3,  d4  // left-right
+        vsubl.u8        q0,  d1,  d4
+        subs            r4,  r4,  #4
+        vmla.i16        q8,  q1,  q3  // right*256  + (left-right)*weights_hor
+        vmla.i16        q9,  q0,  q3
+        vrshrn.i16      d16, q8,  #8
+        vrshrn.i16      d17, q9,  #8
+        vst1.32         {d16[0]}, [r0, :32], r1
+        vst1.32         {d16[1]}, [r6, :32], r1
+        vst1.32         {d17[0]}, [r0, :32], r1
+        vst1.32         {d17[1]}, [r6, :32], r1
+        bgt             4b
+        pop             {r4-r8, pc}
+80:
+        vld1.8          {d6}, [r8, :64] // weights_hor
+        sub             r2,  r2,  #4
+        mov             r7,  #-4
+        vmovl.u8        q3,  d6       // weights_hor
+8:
+        vld4.8          {d16[], d18[], d20[], d22[]},  [r2, :32], r7 // left
+        vshll.i8        q12, d4,  #8  // right*256
+        vshll.i8        q13, d4,  #8
+        vshll.i8        q14, d4,  #8
+        vshll.i8        q15, d4,  #8
+        vsubl.u8        q11, d22, d4  // left-right
+        vsubl.u8        q10, d20, d4
+        vsubl.u8        q9,  d18, d4
+        vsubl.u8        q8,  d16, d4
+        vmla.i16        q12, q11, q3  // right*256  + (left-right)*weights_hor
+        vmla.i16        q13, q10, q3  // (left flipped)
+        vmla.i16        q14, q9,  q3
+        vmla.i16        q15, q8,  q3
+        vrshrn.i16      d24, q12, #8
+        vrshrn.i16      d25, q13, #8
+        vrshrn.i16      d26, q14, #8
+        vrshrn.i16      d27, q15, #8
+        vst1.8          {d24}, [r0, :64], r1
+        vst1.8          {d25}, [r6, :64], r1
+        subs            r4,  r4,  #4
+        vst1.8          {d26}, [r0, :64], r1
+        vst1.8          {d27}, [r6, :64], r1
+        bgt             8b
+        pop             {r4-r8, pc}
+160:
+320:
+640:
+        vpush           {q4-q7}
+        sub             r2,  r2,  #4
+        mov             r7,  #-4
+        // Set up pointers for four rows in parallel; r0, r6, r5, lr
+        add             r5,  r0,  r1
+        add             lr,  r6,  r1
+        lsl             r1,  r1,  #1
+        sub             r1,  r1,  r3
+        mov             r12, r3
+
+1:
+        vld4.8          {d8[], d10[], d12[], d14[]},  [r2, :32], r7 // left
+        vsubl.u8        q4,  d8,  d4  // left-right
+        vsubl.u8        q5,  d10, d4
+        vsubl.u8        q6,  d12, d4
+        vsubl.u8        q7,  d14, d4
+2:
+        vld1.8          {q1}, [r8, :128]! // weights_hor
+        vshll.i8        q8,  d4,  #8  // right*256
+        vshll.i8        q9,  d4,  #8
+        vshll.i8        q10, d4,  #8
+        vshll.i8        q11, d4,  #8
+        vmovl.u8        q0,  d2       // weights_hor
+        vmovl.u8        q1,  d3
+        vshll.i8        q12, d4,  #8
+        vshll.i8        q13, d4,  #8
+        vshll.i8        q14, d4,  #8
+        vshll.i8        q15, d4,  #8
+        vmla.i16        q8,  q7,  q0  // right*256  + (left-right)*weights_hor
+        vmla.i16        q9,  q7,  q1  // (left flipped)
+        vmla.i16        q10, q6,  q0
+        vmla.i16        q11, q6,  q1
+        vmla.i16        q12, q5,  q0
+        vmla.i16        q13, q5,  q1
+        vmla.i16        q14, q4,  q0
+        vmla.i16        q15, q4,  q1
+        vrshrn.i16      d16, q8,  #8
+        vrshrn.i16      d17, q9,  #8
+        vrshrn.i16      d18, q10, #8
+        vrshrn.i16      d19, q11, #8
+        vrshrn.i16      d20, q12, #8
+        vrshrn.i16      d21, q13, #8
+        vrshrn.i16      d22, q14, #8
+        vrshrn.i16      d23, q15, #8
+        subs            r3,  r3,  #16
+        vst1.8          {q8},  [r0, :128]!
+        vst1.8          {q9},  [r6, :128]!
+        vst1.8          {q10}, [r5, :128]!
+        vst1.8          {q11}, [lr, :128]!
+        bgt             2b
+        subs            r4,  r4,  #4
+        ble             9f
+        sub             r8,  r8,  r12
+        add             r0,  r0,  r1
+        add             r6,  r6,  r1
+        add             r5,  r5,  r1
+        add             lr,  lr,  r1
+        mov             r3,  r12
+        b               1b
+9:
+        vpop            {q4-q7}
+        pop             {r4-r8, pc}
+endfunc
+
+// void ipred_filter_8bpc_neon(pixel *dst, const ptrdiff_t stride,
+//                             const pixel *const topleft,
+//                             const int width, const int height, const int filt_idx,
+//                             const int max_width, const int max_height);
+function ipred_filter_8bpc_neon, export=1
+        push            {r4-r8, lr}
+        movw            r12, #511
+        ldr             r5, [sp, #28]
+        ldr             r4, [sp, #24]
+        and             r5,  r5,  r12 // 511
+        movrel          r6,  X(filter_intra_taps)
+        lsl             r5,  r5,  #6
+        add             r6,  r6,  r5
+        vld1.8          {d20, d21, d22, d23}, [r6, :128]!
+        clz             lr,  r3
+        adr             r5,  L(ipred_filter_tbl)
+        vld1.8          {d27, d28, d29}, [r6, :64]
+        sub             lr,  lr,  #26
+        ldr             lr,  [r5, lr, lsl #2]
+        vmovl.s8        q8,  d20
+        vmovl.s8        q9,  d21
+        add             r5,  r5,  lr
+        vmovl.s8        q10, d22
+        vmovl.s8        q11, d23
+        add             r6,  r0,  r1
+        lsl             r1,  r1,  #1
+        vmovl.s8        q12, d27
+        vmovl.s8        q13, d28
+        vmovl.s8        q14, d29
+        add             r8,  r2,  #1
+        bx              r5
+
+        .align 2
+L(ipred_filter_tbl):
+        .word 320f - L(ipred_filter_tbl) + CONFIG_THUMB
+        .word 160f - L(ipred_filter_tbl) + CONFIG_THUMB
+        .word 80f  - L(ipred_filter_tbl) + CONFIG_THUMB
+        .word 40f  - L(ipred_filter_tbl) + CONFIG_THUMB
+
+40:
+        vld1.32         {d0[]}, [r8]     // top (0-3)
+        sub             r2,  r2,  #2
+        mov             r7,  #-2
+        vmovl.u8        q0,  d0          // top (0-3)
+4:
+        vld1.32         {d2[]}, [r2], r7 // left (0-1) + topleft (2)
+        vmul.i16        q2,  q9,  d0[0]  // p1(top[0]) * filter(1)
+        vmla.i16        q2,  q10, d0[1]  // p2(top[1]) * filter(2)
+        vmla.i16        q2,  q11, d0[2]  // p3(top[2]) * filter(3)
+        vmovl.u8        q1,  d2          // left (0-1) + topleft (2)
+        vmla.i16        q2,  q12, d0[3]  // p4(top[3]) * filter(4)
+        vmla.i16        q2,  q8,  d2[2]  // p0(topleft) * filter(0)
+        vmla.i16        q2,  q13, d2[1]  // p5(left[0]) * filter(5)
+        vmla.i16        q2,  q14, d2[0]  // p6(left[1]) * filter(6)
+        vqrshrun.s16    d4,  q2,  #4
+        subs            r4,  r4,  #2
+        vst1.32         {d4[0]}, [r0, :32], r1
+        vmovl.u8        q0,  d4
+        vst1.32         {d4[1]}, [r6, :32], r1
+        vext.8          q0,  q0,  q0,  #8 // move top from [4-7] to [0-3]
+        bgt             4b
+        pop             {r4-r8, pc}
+80:
+        vld1.8          {d0},  [r8]      // top (0-7)
+        sub             r2,  r2,  #2
+        mov             r7,  #-2
+        vmovl.u8        q0,  d0          // top (0-7)
+8:
+        vld1.32         {d2[]}, [r2], r7 // left (0-1) + topleft (2)
+        vmul.i16        q2,  q9,  d0[0]  // p1(top[0]) * filter(1)
+        vmla.i16        q2,  q10, d0[1]  // p2(top[1]) * filter(2)
+        vmla.i16        q2,  q11, d0[2]  // p3(top[2]) * filter(3)
+        vmovl.u8        q1,  d2          // left (0-1) + topleft (2)
+        vmla.i16        q2,  q12, d0[3]  // p4(top[3]) * filter(4)
+        vmla.i16        q2,  q8,  d2[2]  // p0(topleft) * filter(0)
+        vmla.i16        q2,  q13, d2[1]  // p5(left[0]) * filter(5)
+        vmla.i16        q2,  q14, d2[0]  // p6(left[1]) * filter(6)
+        vmul.i16        q3,  q9,  d1[0]  // p1(top[0]) * filter(1)
+        vmla.i16        q3,  q10, d1[1]  // p2(top[1]) * filter(2)
+        vmla.i16        q3,  q11, d1[2]  // p3(top[2]) * filter(3)
+        vqrshrun.s16    d4,  q2,  #4
+        vmovl.u8        q1,  d4          // first block, in 16 bit
+        vmla.i16        q3,  q12, d1[3]  // p4(top[3]) * filter(4)
+        vmla.i16        q3,  q8,  d0[3]  // p0(topleft) * filter(0)
+        vmla.i16        q3,  q13, d2[3]  // p5(left[0]) * filter(5)
+        vmla.i16        q3,  q14, d3[3]  // p6(left[1]) * filter(6)
+        vqrshrun.s16    d5,  q3,  #4
+        vzip.32         d4,  d5
+        subs            r4,  r4,  #2
+        vst1.64         {d4}, [r0, :64], r1
+        vmovl.u8        q0,  d5
+        vst1.64         {d5}, [r6, :64], r1
+        bgt             8b
+        pop             {r4-r8, pc}
+160:
+320:
+        vpush           {q4-q5}
+        sub             r2,  r2,  #2
+        mov             r7,  #-2
+        sub             r1,  r1,  r3
+        mov             lr,  r3
+
+1:
+        vld1.32         {d0[]}, [r2], r7 // left (0-1) + topleft (2)
+        vmovl.u8        q0,  d0          // left (0-1) + topleft (2)
+2:
+        vld1.8          {q2}, [r8]!      // top(0-15)
+        vmul.i16        q3,  q8,  d0[2]  // p0(topleft) * filter(0)
+        vmla.i16        q3,  q13, d0[1]  // p5(left[0]) * filter(5)
+        vmovl.u8        q1,  d4          // top(0-7)
+        vmovl.u8        q2,  d5          // top(8-15)
+        vmla.i16        q3,  q14, d0[0]  // p6(left[1]) * filter(6)
+        vmla.i16        q3,  q9,  d2[0]  // p1(top[0]) * filter(1)
+        vmla.i16        q3,  q10, d2[1]  // p2(top[1]) * filter(2)
+        vmla.i16        q3,  q11, d2[2]  // p3(top[2]) * filter(3)
+        vmla.i16        q3,  q12, d2[3]  // p4(top[3]) * filter(4)
+
+        vmul.i16        q4,  q9,  d3[0]  // p1(top[0]) * filter(1)
+        vmla.i16        q4,  q10, d3[1]  // p2(top[1]) * filter(2)
+        vmla.i16        q4,  q11, d3[2]  // p3(top[2]) * filter(3)
+        vqrshrun.s16    d6,  q3,  #4
+        vmovl.u8        q0,  d6          // first block, in 16 bit
+        vmla.i16        q4,  q12, d3[3]  // p4(top[3]) * filter(4)
+        vmla.i16        q4,  q8,  d2[3]  // p0(topleft) * filter(0)
+        vmla.i16        q4,  q13, d0[3]  // p5(left[0]) * filter(5)
+        vmla.i16        q4,  q14, d1[3]  // p6(left[1]) * filter(6)
+
+        vmul.i16        q5,  q9,  d4[0]  // p1(top[0]) * filter(1)
+        vmla.i16        q5,  q10, d4[1]  // p2(top[1]) * filter(2)
+        vmla.i16        q5,  q11, d4[2]  // p3(top[2]) * filter(3)
+        vqrshrun.s16    d7,  q4,  #4
+        vmovl.u8        q0,  d7          // second block, in 16 bit
+        vmla.i16        q5,  q12, d4[3]  // p4(top[3]) * filter(4)
+        vmla.i16        q5,  q8,  d3[3]  // p0(topleft) * filter(0)
+        vmla.i16        q5,  q13, d0[3]  // p5(left[0]) * filter(5)
+        vmla.i16        q5,  q14, d1[3]  // p6(left[1]) * filter(6)
+
+        vmul.i16        q15, q9,  d5[0]  // p1(top[0]) * filter(1)
+        vmla.i16        q15, q10, d5[1]  // p2(top[1]) * filter(2)
+        vmla.i16        q15, q11, d5[2]  // p3(top[2]) * filter(3)
+        vqrshrun.s16    d8,  q5,  #4
+        vmovl.u8        q0,  d8          // third block, in 16 bit
+        vmov.u8         r12, d5[6]
+        vmla.i16        q15, q12, d5[3]  // p4(top[3]) * filter(4)
+        vmla.i16        q15, q8,  d4[3]  // p0(topleft) * filter(0)
+        vmla.i16        q15, q13, d0[3]  // p5(left[0]) * filter(5)
+        vmla.i16        q15, q14, d1[3]  // p6(left[1]) * filter(6)
+        vmov.8          d0[4], r12
+
+        subs            r3,  r3,  #16
+        vqrshrun.s16    d9,  q15, #4
+
+        vst4.32         {d6[0], d7[0], d8[0], d9[0]}, [r0, :128]!
+        vst4.32         {d6[1], d7[1], d8[1], d9[1]}, [r6, :128]!
+        ble             8f
+        vmov.u8         r12, d9[7]
+        vmov.8          d0[0], r12
+        vmov.u8         r12, d9[3]
+        vmov.8          d0[2], r12
+        b               2b
+8:
+        subs            r4,  r4,  #2
+
+        ble             9f
+        sub             r8,  r6,  lr
+        add             r0,  r0,  r1
+        add             r6,  r6,  r1
+        mov             r3,  lr
+        b               1b
+9:
+        vpop            {q4-q5}
+        pop             {r4-r8, pc}
+endfunc
+
+// void pal_pred_8bpc_neon(pixel *dst, const ptrdiff_t stride,
+//                         const uint16_t *const pal, const uint8_t *idx,
+//                         const int w, const int h);
+function pal_pred_8bpc_neon, export=1
+        push            {r4-r5, lr}
+        ldr             r4,  [sp, #12]
+        ldr             r5,  [sp, #16]
+        vld1.16         {q0}, [r2, :128]
+        clz             lr,  r4
+        adr             r12, L(pal_pred_tbl)
+        sub             lr,  lr,  #25
+        ldr             lr,  [r12, lr, lsl #2]
+        vmovn.i16       d0,  q0
+        add             r12, r12, lr
+        add             r2,  r0,  r1
+        bx              r12
+
+        .align 2
+L(pal_pred_tbl):
+        .word 640f - L(pal_pred_tbl) + CONFIG_THUMB
+        .word 320f - L(pal_pred_tbl) + CONFIG_THUMB
+        .word 160f - L(pal_pred_tbl) + CONFIG_THUMB
+        .word 80f  - L(pal_pred_tbl) + CONFIG_THUMB
+        .word 40f  - L(pal_pred_tbl) + CONFIG_THUMB
+
+40:
+        lsl             r1,  r1,  #1
+4:
+        vld1.8          {q1}, [r3, :128]!
+        subs            r5,  r5,  #4
+        vtbl.8          d2, {d0}, d2
+        vtbl.8          d3, {d0}, d3
+        vst1.32         {d2[0]}, [r0, :32], r1
+        vst1.32         {d2[1]}, [r2, :32], r1
+        vst1.32         {d3[0]}, [r0, :32], r1
+        vst1.32         {d3[1]}, [r2, :32], r1
+        bgt             4b
+        pop             {r4-r5, pc}
+80:
+        lsl             r1,  r1,  #1
+8:
+        vld1.8          {q1, q2}, [r3, :128]!
+        subs            r5,  r5,  #4
+        vtbl.8          d2, {d0}, d2
+        vtbl.8          d3, {d0}, d3
+        vst1.8          {d2}, [r0, :64], r1
+        vtbl.8          d4, {d0}, d4
+        vst1.8          {d3}, [r2, :64], r1
+        vtbl.8          d5, {d0}, d5
+        vst1.8          {d4}, [r0, :64], r1
+        vst1.8          {d5}, [r2, :64], r1
+        bgt             8b
+        pop             {r4-r5, pc}
+160:
+        lsl             r1,  r1,  #1
+16:
+        vld1.8          {q8,  q9},  [r3, :128]!
+        subs            r5,  r5,  #4
+        vld1.8          {q10, q11}, [r3, :128]!
+        vtbl.8          d16, {d0}, d16
+        vtbl.8          d17, {d0}, d17
+        vtbl.8          d18, {d0}, d18
+        vtbl.8          d19, {d0}, d19
+        vtbl.8          d20, {d0}, d20
+        vtbl.8          d21, {d0}, d21
+        vst1.8          {q8},  [r0, :128], r1
+        vtbl.8          d22, {d0}, d22
+        vst1.8          {q9},  [r2, :128], r1
+        vtbl.8          d23, {d0}, d23
+        vst1.8          {q10}, [r0, :128], r1
+        vst1.8          {q11}, [r2, :128], r1
+        bgt             16b
+        pop             {r4-r5, pc}
+320:
+        lsl             r1,  r1,  #1
+32:
+        vld1.8          {q8,  q9},  [r3, :128]!
+        subs            r5,  r5,  #2
+        vld1.8          {q10, q11}, [r3, :128]!
+        vtbl.8          d16, {d0}, d16
+        vtbl.8          d17, {d0}, d17
+        vtbl.8          d18, {d0}, d18
+        vtbl.8          d19, {d0}, d19
+        vtbl.8          d20, {d0}, d20
+        vtbl.8          d21, {d0}, d21
+        vst1.8          {q8,  q9},  [r0, :128], r1
+        vtbl.8          d22, {d0}, d22
+        vtbl.8          d23, {d0}, d23
+        vst1.8          {q10, q11}, [r2, :128], r1
+        bgt             32b
+        pop             {r4-r5, pc}
+640:
+        sub             r1,  r1,  #32
+64:
+        vld1.8          {q8,  q9},  [r3, :128]!
+        subs            r5,  r5,  #1
+        vld1.8          {q10, q11}, [r3, :128]!
+        vtbl.8          d16, {d0}, d16
+        vtbl.8          d17, {d0}, d17
+        vtbl.8          d18, {d0}, d18
+        vtbl.8          d19, {d0}, d19
+        vtbl.8          d20, {d0}, d20
+        vtbl.8          d21, {d0}, d21
+        vst1.8          {q8,  q9},  [r0, :128]!
+        vtbl.8          d22, {d0}, d22
+        vtbl.8          d23, {d0}, d23
+        vst1.8          {q10, q11}, [r0, :128], r1
+        bgt             64b
+        pop             {r4-r5, pc}
+endfunc
+
+// void ipred_cfl_128_8bpc_neon(pixel *dst, const ptrdiff_t stride,
+//                              const pixel *const topleft,
+//                              const int width, const int height,
+//                              const int16_t *ac, const int alpha);
+function ipred_cfl_128_8bpc_neon, export=1
+        push            {r4-r8, lr}
+        ldr             r4,  [sp, #24]
+        ldr             r5,  [sp, #28]
+        ldr             r6,  [sp, #32]
+        clz             lr,  r3
+        adr             r12, L(ipred_cfl_128_tbl)
+        sub             lr,  lr,  #26
+        ldr             lr,  [r12, lr, lsl #2]
+        vmov.i16        q0,  #128     // dc
+        vdup.i16        q1,  r6       // alpha
+        add             r12, r12, lr
+        add             r6,  r0,  r1
+        lsl             r1,  r1,  #1
+        bx              r12
+
+        .align 2
+L(ipred_cfl_128_tbl):
+L(ipred_cfl_splat_tbl):
+        .word L(ipred_cfl_splat_w16) - L(ipred_cfl_128_tbl) + CONFIG_THUMB
+        .word L(ipred_cfl_splat_w16) - L(ipred_cfl_128_tbl) + CONFIG_THUMB
+        .word L(ipred_cfl_splat_w8)  - L(ipred_cfl_128_tbl) + CONFIG_THUMB
+        .word L(ipred_cfl_splat_w4)  - L(ipred_cfl_128_tbl) + CONFIG_THUMB
+
+L(ipred_cfl_splat_w4):
+        vld1.16         {q2, q3}, [r5, :128]!
+        vmul.i16        q2,  q2,  q1  // diff = ac * alpha
+        vmul.i16        q3,  q3,  q1
+        vshr.s16        q8,  q2,  #15 // sign = diff >> 15
+        vshr.s16        q9,  q3,  #15
+        vadd.i16        q2,  q2,  q8  // diff + sign
+        vadd.i16        q3,  q3,  q9
+        vrshr.s16       q2,  q2,  #6  // (diff + sign + 32) >> 6 = apply_sign()
+        vrshr.s16       q3,  q3,  #6
+        vadd.i16        q2,  q2,  q0  // dc + apply_sign()
+        vadd.i16        q3,  q3,  q0
+        vqmovun.s16     d4,  q2       // iclip_pixel(dc + apply_sign())
+        vqmovun.s16     d5,  q3
+        vst1.32         {d4[0]}, [r0, :32], r1
+        vst1.32         {d4[1]}, [r6, :32], r1
+        subs            r4,  r4,  #4
+        vst1.32         {d5[0]}, [r0, :32], r1
+        vst1.32         {d5[1]}, [r6, :32], r1
+        bgt             L(ipred_cfl_splat_w4)
+        pop             {r4-r8, pc}
+L(ipred_cfl_splat_w8):
+        vld1.16         {q8, q9},   [r5, :128]!
+        vld1.16         {q10, q11}, [r5, :128]!
+        vmul.i16        q8,  q8,  q1  // diff = ac * alpha
+        vmul.i16        q9,  q9,  q1
+        vmul.i16        q10, q10, q1
+        vmul.i16        q11, q11, q1
+        vshr.s16        q12, q8,  #15 // sign = diff >> 15
+        vshr.s16        q13, q9,  #15
+        vshr.s16        q14, q10, #15
+        vshr.s16        q15, q11, #15
+        vadd.i16        q8,  q8,  q12 // diff + sign
+        vadd.i16        q9,  q9,  q13
+        vadd.i16        q10, q10, q14
+        vadd.i16        q11, q11, q15
+        vrshr.s16       q8,  q8,  #6  // (diff + sign + 32) >> 6 = apply_sign()
+        vrshr.s16       q9,  q9,  #6
+        vrshr.s16       q10, q10, #6
+        vrshr.s16       q11, q11, #6
+        vadd.i16        q8,  q8,  q0  // dc + apply_sign()
+        vadd.i16        q9,  q9,  q0
+        vadd.i16        q10, q10, q0
+        vadd.i16        q11, q11, q0
+        vqmovun.s16     d16, q8       // iclip_pixel(dc + apply_sign())
+        vqmovun.s16     d17, q9
+        vqmovun.s16     d18, q10
+        vqmovun.s16     d19, q11
+        vst1.8          {d16}, [r0, :64], r1
+        vst1.8          {d17}, [r6, :64], r1
+        subs            r4,  r4,  #4
+        vst1.8          {d18}, [r0, :64], r1
+        vst1.8          {d19}, [r6, :64], r1
+        bgt             L(ipred_cfl_splat_w8)
+        pop             {r4-r8, pc}
+L(ipred_cfl_splat_w16):
+        add             r12, r5,  r3, lsl #1
+        sub             r1,  r1,  r3
+        mov             lr,  r3
+1:
+        vld1.16         {q8, q9},   [r5, :128]!
+        vmul.i16        q8,  q8,  q1  // diff = ac * alpha
+        vld1.16         {q10, q11}, [r12, :128]!
+        vmul.i16        q9,  q9,  q1
+        vmul.i16        q10, q10, q1
+        vmul.i16        q11, q11, q1
+        vshr.s16        q12, q8,  #15 // sign = diff >> 15
+        vshr.s16        q13, q9,  #15
+        vshr.s16        q14, q10, #15
+        vshr.s16        q15, q11, #15
+        vadd.i16        q8,  q8,  q12 // diff + sign
+        vadd.i16        q9,  q9,  q13
+        vadd.i16        q10, q10, q14
+        vadd.i16        q11, q11, q15
+        vrshr.s16       q8,  q8,  #6  // (diff + sign + 32) >> 6 = apply_sign()
+        vrshr.s16       q9,  q9,  #6
+        vrshr.s16       q10, q10, #6
+        vrshr.s16       q11, q11, #6
+        vadd.i16        q8,  q8,  q0  // dc + apply_sign()
+        vadd.i16        q9,  q9,  q0
+        vadd.i16        q10, q10, q0
+        vadd.i16        q11, q11, q0
+        vqmovun.s16     d16, q8       // iclip_pixel(dc + apply_sign())
+        vqmovun.s16     d17, q9
+        vqmovun.s16     d18, q10
+        vqmovun.s16     d19, q11
+        subs            r3,  r3,  #16
+        vst1.16         {q8}, [r0, :128]!
+        vst1.16         {q9}, [r6, :128]!
+        bgt             1b
+        subs            r4,  r4,  #2
+        add             r5,  r5,  lr, lsl #1
+        add             r12, r12, lr, lsl #1
+        add             r0,  r0,  r1
+        add             r6,  r6,  r1
+        mov             r3,  lr
+        bgt             1b
+        pop             {r4-r8, pc}
+endfunc
+
+// void ipred_cfl_top_8bpc_neon(pixel *dst, const ptrdiff_t stride,
+//                              const pixel *const topleft,
+//                              const int width, const int height,
+//                              const int16_t *ac, const int alpha);
+function ipred_cfl_top_8bpc_neon, export=1
+        push            {r4-r8, lr}
+        ldr             r4,  [sp, #24]
+        ldr             r5,  [sp, #28]
+        ldr             r6,  [sp, #32]
+        clz             lr,  r3
+        adr             r12, L(ipred_cfl_top_tbl)
+        sub             lr,  lr,  #26
+        ldr             lr,  [r12, lr, lsl #2]
+        vdup.16         q1,  r6   // alpha
+        add             r2,  r2,  #1
+        add             r12, r12, lr
+        add             r6,  r0,  r1
+        lsl             r1,  r1,  #1
+        bx              r12
+
+        .align 2
+L(ipred_cfl_top_tbl):
+        .word 32f - L(ipred_cfl_top_tbl) + CONFIG_THUMB
+        .word 16f - L(ipred_cfl_top_tbl) + CONFIG_THUMB
+        .word 8f  - L(ipred_cfl_top_tbl) + CONFIG_THUMB
+        .word 4f  - L(ipred_cfl_top_tbl) + CONFIG_THUMB
+
+4:
+        vld1.32         {d0[]}, [r2]
+        vpaddl.u8       d0,  d0
+        vpadd.u16       d0,  d0
+        vrshr.u16       d0,  d0,  #2
+        vdup.16         q0,  d0[0]
+        b               L(ipred_cfl_splat_w4)
+8:
+        vld1.8          {d0}, [r2]
+        vpaddl.u8       d0,  d0
+        vpadd.u16       d0,  d0
+        vpadd.u16       d0,  d0
+        vrshr.u16       d0,  d0,  #3
+        vdup.16         q0,  d0[0]
+        b               L(ipred_cfl_splat_w8)
+16:
+        vld1.8          {q0}, [r2]
+        vaddl.u8        q0,  d0,  d1
+        vadd.u16        d0,  d0,  d1
+        vpadd.u16       d0,  d0
+        vpadd.u16       d0,  d0
+        vrshr.u16       d0,  d0,  #4
+        vdup.16         q0,  d0[0]
+        b               L(ipred_cfl_splat_w16)
+32:
+        vld1.8          {q2, q3}, [r2]
+        vaddl.u8        q2,  d4,  d5
+        vaddl.u8        q3,  d6,  d7
+        vadd.u16        q0,  q2,  q3
+        vadd.u16        d0,  d0,  d1
+        vpadd.u16       d0,  d0
+        vpadd.u16       d0,  d0
+        vrshr.u16       d0,  d0,  #5
+        vdup.16         q0,  d0[0]
+        b               L(ipred_cfl_splat_w16)
+endfunc
+
+// void ipred_cfl_left_8bpc_neon(pixel *dst, const ptrdiff_t stride,
+//                               const pixel *const topleft,
+//                               const int width, const int height,
+//                               const int16_t *ac, const int alpha);
+function ipred_cfl_left_8bpc_neon, export=1
+        push            {r4-r8, lr}
+        ldr             r4,  [sp, #24]
+        ldr             r5,  [sp, #28]
+        ldr             r6,  [sp, #32]
+        sub             r2,  r2,  r4
+        clz             lr,  r3
+        clz             r8,  r4
+        adr             r12, L(ipred_cfl_splat_tbl)
+        adr             r7,  L(ipred_cfl_left_tbl)
+        sub             lr,  lr,  #26
+        sub             r8,  r8,  #26
+        ldr             lr,  [r12, lr, lsl #2]
+        ldr             r8,  [r7,  r8, lsl #2]
+        vdup.16         q1,  r6   // alpha
+        add             r12, r12, lr
+        add             r7,  r7,  r8
+        add             r6,  r0,  r1
+        lsl             r1,  r1,  #1
+        bx              r7
+
+        .align 2
+L(ipred_cfl_left_tbl):
+        .word L(ipred_cfl_left_h32) - L(ipred_cfl_left_tbl) + CONFIG_THUMB
+        .word L(ipred_cfl_left_h16) - L(ipred_cfl_left_tbl) + CONFIG_THUMB
+        .word L(ipred_cfl_left_h8)  - L(ipred_cfl_left_tbl) + CONFIG_THUMB
+        .word L(ipred_cfl_left_h4)  - L(ipred_cfl_left_tbl) + CONFIG_THUMB
+
+L(ipred_cfl_left_h4):
+        vld1.32         {d0[]}, [r2, :32]
+        vpaddl.u8       d0,  d0
+        vpadd.u16       d0,  d0
+        vrshr.u16       d0,  d0,  #2
+        vdup.16         q0,  d0[0]
+        bx              r12
+
+L(ipred_cfl_left_h8):
+        vld1.8          {d0}, [r2, :64]
+        vpaddl.u8       d0,  d0
+        vpadd.u16       d0,  d0
+        vpadd.u16       d0,  d0
+        vrshr.u16       d0,  d0,  #3
+        vdup.16         q0,  d0[0]
+        bx              r12
+
+L(ipred_cfl_left_h16):
+        vld1.8          {q0}, [r2, :128]
+        vaddl.u8        q0,  d0,  d1
+        vadd.u16        d0,  d0,  d1
+        vpadd.u16       d0,  d0
+        vpadd.u16       d0,  d0
+        vrshr.u16       d0,  d0,  #4
+        vdup.16         q0,  d0[0]
+        bx              r12
+
+L(ipred_cfl_left_h32):
+        vld1.8          {q2, q3}, [r2, :128]
+        vaddl.u8        q2,  d4,  d5
+        vaddl.u8        q3,  d6,  d7
+        vadd.u16        q0,  q2,  q3
+        vadd.u16        d0,  d0,  d1
+        vpadd.u16       d0,  d0
+        vpadd.u16       d0,  d0
+        vrshr.u16       d0,  d0,  #5
+        vdup.16         q0,  d0[0]
+        bx              r12
+endfunc
+
+// void ipred_cfl_8bpc_neon(pixel *dst, const ptrdiff_t stride,
+//                          const pixel *const topleft,
+//                          const int width, const int height,
+//                          const int16_t *ac, const int alpha);
+function ipred_cfl_8bpc_neon, export=1
+        push            {r4-r8, lr}
+        ldr             r4,  [sp, #24]
+        ldr             r5,  [sp, #28]
+        ldr             r6,  [sp, #32]
+        sub             r2,  r2,  r4
+        add             r8,  r3,  r4  // width + height
+        vdup.16         q1,  r6       // alpha
+        clz             lr,  r3
+        clz             r6,  r4
+        vdup.16         d16, r8       // width + height
+        adr             r7,  L(ipred_cfl_tbl)
+        rbit            r8,  r8       // rbit(width + height)
+        sub             lr,  lr,  #22 // 26 leading bits, minus table offset 4
+        sub             r6,  r6,  #26
+        clz             r8,  r8       // ctz(width + height)
+        ldr             lr,  [r7, lr, lsl #2]
+        ldr             r6,  [r7, r6, lsl #2]
+        neg             r8,  r8       // -ctz(width + height)
+        add             r12, r7,  lr
+        add             r7,  r7,  r6
+        vshr.u16        d16, d16, #1  // (width + height) >> 1
+        vdup.16         d17, r8       // -ctz(width + height)
+        add             r6,  r0,  r1
+        lsl             r1,  r1,  #1
+        bx              r7
+
+        .align 2
+L(ipred_cfl_tbl):
+        .word L(ipred_cfl_h32) - L(ipred_cfl_tbl) + CONFIG_THUMB
+        .word L(ipred_cfl_h16) - L(ipred_cfl_tbl) + CONFIG_THUMB
+        .word L(ipred_cfl_h8)  - L(ipred_cfl_tbl) + CONFIG_THUMB
+        .word L(ipred_cfl_h4)  - L(ipred_cfl_tbl) + CONFIG_THUMB
+        .word L(ipred_cfl_w32) - L(ipred_cfl_tbl) + CONFIG_THUMB
+        .word L(ipred_cfl_w16) - L(ipred_cfl_tbl) + CONFIG_THUMB
+        .word L(ipred_cfl_w8)  - L(ipred_cfl_tbl) + CONFIG_THUMB
+        .word L(ipred_cfl_w4)  - L(ipred_cfl_tbl) + CONFIG_THUMB
+
+L(ipred_cfl_h4):
+        vld1.32         {d0[]}, [r2, :32]!
+        vpaddl.u8       d0,  d0
+        vpadd.i16       d0,  d0
+        bx              r12
+L(ipred_cfl_w4):
+        add             r2,  r2,  #1
+        vld1.32         {d1[]},  [r2]
+        vadd.i16        d0,  d0,  d16
+        vpaddl.u8       d1,  d1
+        vpadd.u16       d1,  d1
+        cmp             r4,  #4
+        vadd.i16        d0,  d0,  d1
+        vshl.u16        d0,  d0,  d17
+        beq             1f
+        // h = 8/16
+        movw            lr,  #(0x3334/2)
+        movw            r8,  #(0x5556/2)
+        cmp             r4,  #16
+        it              ne
+        movne           lr,  r8
+        vdup.16         d18, lr
+        vqdmulh.s16     d0,  d0,  d18
+1:
+        vdup.16         q0,  d0[0]
+        b               L(ipred_cfl_splat_w4)
+
+L(ipred_cfl_h8):
+        vld1.8          {d0}, [r2, :64]!
+        vpaddl.u8       d0,  d0
+        vpadd.i16       d0,  d0
+        vpadd.i16       d0,  d0
+        bx              r12
+L(ipred_cfl_w8):
+        add             r2,  r2,  #1
+        vld1.8          {d1}, [r2]
+        vadd.i16        d0,  d0,  d16
+        vpaddl.u8       d1,  d1
+        vpadd.i16       d1,  d1
+        vpadd.i16       d1,  d1
+        cmp             r4,  #8
+        vadd.i16        d0,  d0,  d1
+        vshl.u16        d0,  d0,  d17
+        beq             1f
+        // h = 4/16/32
+        cmp             r4,  #32
+        movw            lr,  #(0x3334/2)
+        movw            r8,  #(0x5556/2)
+        it              ne
+        movne           lr,  r8
+        vdup.16         d18, lr
+        vqdmulh.s16     d0,  d0,  d18
+1:
+        vdup.16         q0,  d0[0]
+        b               L(ipred_cfl_splat_w8)
+
+L(ipred_cfl_h16):
+        vld1.8          {q0}, [r2, :128]!
+        vaddl.u8        q0,  d0,  d1
+        vadd.i16        d0,  d0,  d1
+        vpadd.i16       d0,  d0
+        vpadd.i16       d0,  d0
+        bx              r12
+L(ipred_cfl_w16):
+        add             r2,  r2,  #1
+        vld1.8          {q2}, [r2]
+        vadd.i16        d0,  d0,  d16
+        vaddl.u8        q2,  d4,  d5
+        vadd.i16        d4,  d4,  d5
+        vpadd.i16       d4,  d4
+        vpadd.i16       d4,  d4
+        cmp             r4,  #16
+        vadd.i16        d0,  d0,  d4
+        vshl.u16        d0,  d0,  d17
+        beq             1f
+        // h = 4/8/32/64
+        tst             r4,  #(32+16+8)  // 16 added to make a consecutive bitmask
+        movw            lr,  #(0x3334/2)
+        movw            r8,  #(0x5556/2)
+        it              ne
+        movne           lr,  r8
+        vdup.16         d18, lr
+        vqdmulh.s16     d0,  d0,  d18
+1:
+        vdup.16         q0,  d0[0]
+        b               L(ipred_cfl_splat_w16)
+
+L(ipred_cfl_h32):
+        vld1.8          {q2, q3}, [r2, :128]!
+        vaddl.u8        q2,  d4,  d5
+        vaddl.u8        q3,  d6,  d7
+        vadd.i16        q0,  q2,  q3
+        vadd.i16        d0,  d0,  d1
+        vpadd.i16       d0,  d0
+        vpadd.i16       d0,  d0
+        bx              r12
+L(ipred_cfl_w32):
+        add             r2,  r2,  #1
+        vld1.8          {q2, q3},  [r2]
+        vadd.i16        d0,  d0,  d16
+        vaddl.u8        q2,  d4,  d5
+        vaddl.u8        q3,  d6,  d7
+        vadd.i16        q2,  q2,  q3
+        vadd.i16        d4,  d4,  d5
+        vpadd.i16       d4,  d4
+        vpadd.i16       d4,  d4
+        cmp             r4,  #32
+        vadd.i16        d0,  d0,  d4
+        vshl.u16        d0,  d0,  d17
+        beq             1f
+        // h = 8/16/64
+        cmp             r4,  #8
+        movw            lr,  #(0x3334/2)
+        movw            r8,  #(0x5556/2)
+        it              ne
+        movne           lr,  r8
+        vdup.16         d18, lr
+        vqdmulh.s16     d0,  d0,  d18
+1:
+        vdup.16         q0,  d0[0]
+        b               L(ipred_cfl_splat_w16)
+endfunc
+
+// void cfl_ac_420_8bpc_neon(int16_t *const ac, const pixel *const ypx,
+//                           const ptrdiff_t stride, const int w_pad,
+//                           const int h_pad, const int cw, const int ch);
+function ipred_cfl_ac_420_8bpc_neon, export=1
+        push            {r4-r8,lr}
+        ldr             r4,  [sp, #24]
+        ldr             r5,  [sp, #28]
+        ldr             r6,  [sp, #32]
+        clz             r8,  r5
+        lsl             r4,  r4,  #2
+        adr             r7,  L(ipred_cfl_ac_420_tbl)
+        sub             r8,  r8,  #27
+        ldr             r8,  [r7, r8, lsl #2]
+        vmov.i16        q8,  #0
+        vmov.i16        q9,  #0
+        vmov.i16        q10, #0
+        vmov.i16        q11, #0
+        add             r7,  r7,  r8
+        sub             r8,  r6,  r4  // height - h_pad
+        rbit            lr,  r5       // rbit(width)
+        rbit            r12, r6       // rbit(height)
+        clz             lr,  lr       // ctz(width)
+        clz             r12, r12      // ctz(height)
+        add             lr,  lr,  r12 // log2sz
+        add             r12, r1,  r2
+        vdup.32         d31, lr
+        lsl             r2,  r2,  #1
+        vneg.s32        d31, d31      // -log2sz
+        bx              r7
+
+        .align 2
+L(ipred_cfl_ac_420_tbl):
+        .word L(ipred_cfl_ac_420_w16) - L(ipred_cfl_ac_420_tbl) + CONFIG_THUMB
+        .word L(ipred_cfl_ac_420_w8)  - L(ipred_cfl_ac_420_tbl) + CONFIG_THUMB
+        .word L(ipred_cfl_ac_420_w4)  - L(ipred_cfl_ac_420_tbl) + CONFIG_THUMB
+
+L(ipred_cfl_ac_420_w4):
+1:      // Copy and subsample input
+        vld1.8          {d0}, [r1,  :64], r2
+        vld1.8          {d2}, [r12, :64], r2
+        vld1.8          {d1}, [r1,  :64], r2
+        vld1.8          {d3}, [r12, :64], r2
+        vpaddl.u8       q0,  q0
+        vpaddl.u8       q1,  q1
+        vadd.i16        q0,  q0,  q1
+        vshl.i16        q0,  q0,  #1
+        subs            r8,  r8,  #2
+        vst1.16         {q0}, [r0, :128]!
+        vadd.i16        q8,  q8,  q0
+        bgt             1b
+        cmp             r4,  #0
+        vmov            d0,  d1
+        vmov            d2,  d1
+        vmov            d3,  d1
+L(ipred_cfl_ac_420_w4_hpad):
+        beq             3f // This assumes that all callers already did "cmp r4, #0"
+2:      // Vertical padding (h_pad > 0)
+        subs            r4,  r4,  #4
+        vst1.16         {q0, q1}, [r0, :128]!
+        vadd.i16        q8,  q8,  q0
+        vadd.i16        q8,  q8,  q1
+        bgt             2b
+3:
+L(ipred_cfl_ac_420_w4_calc_subtract_dc):
+        // Aggregate the sums
+        vadd.i16        q0,  q8,  q9
+        vadd.i16        q1,  q10, q11
+        vpaddl.u16      q0,  q0
+        vpaddl.u16      q1,  q1
+        vadd.i32        q0,  q1
+        vadd.i32        d0,  d0,  d1
+        vpadd.i32       d0,  d0,  d0  // sum
+        sub             r0,  r0,  r6, lsl #3
+        vrshl.u32       d16, d0,  d31 // (sum + (1 << (log2sz - 1))) >>= log2sz
+        vdup.16         q8,  d16[0]
+L(ipred_cfl_ac_420_w4_subtract_dc):
+6:      // Subtract dc from ac
+        vld1.16         {q0, q1}, [r0, :128]
+        subs            r6,  r6,  #4
+        vsub.i16        q0,  q0,  q8
+        vsub.i16        q1,  q1,  q8
+        vst1.16         {q0, q1}, [r0, :128]!
+        bgt             6b
+        pop             {r4-r8, pc}
+
+L(ipred_cfl_ac_420_w8):
+        cmp             r3,  #0
+        bne             L(ipred_cfl_ac_420_w8_wpad)
+1:      // Copy and subsample input, without padding
+        vld1.8          {q0}, [r1,  :128], r2
+        vld1.8          {q1}, [r12, :128], r2
+        vld1.8          {q2}, [r1,  :128], r2
+        vpaddl.u8       q0,  q0
+        vld1.8          {q3}, [r12, :128], r2
+        vpaddl.u8       q1,  q1
+        vpaddl.u8       q2,  q2
+        vpaddl.u8       q3,  q3
+        vadd.i16        q0,  q0,  q1
+        vadd.i16        q2,  q2,  q3
+        vshl.i16        q0,  q0,  #1
+        vshl.i16        q1,  q2,  #1
+        subs            r8,  r8,  #2
+        vst1.16         {q0, q1}, [r0, :128]!
+        vadd.i16        q8,  q8,  q0
+        vadd.i16        q9,  q9,  q1
+        bgt             1b
+        cmp             r4,  #0
+        vmov            q0,  q1
+        b               L(ipred_cfl_ac_420_w8_hpad)
+
+L(ipred_cfl_ac_420_w8_wpad):
+1:      // Copy and subsample input, padding 4
+        vld1.16         {d0}, [r1,  :64], r2
+        vld1.16         {d2}, [r12, :64], r2
+        vld1.16         {d1}, [r1,  :64], r2
+        vld1.16         {d3}, [r12, :64], r2
+        vpaddl.u8       q0,  q0
+        vpaddl.u8       q1,  q1
+        vadd.i16        q0,  q0,  q1
+        vshl.i16        q0,  q0,  #1
+        vdup.16         d3,  d1[3]
+        vmov            d2,  d1
+        vdup.16         d1,  d0[3]
+        subs            r8,  r8,  #2
+        vst1.16         {q0, q1}, [r0, :128]!
+        vadd.i16        q8,  q8,  q0
+        vadd.i16        q9,  q9,  q1
+        bgt             1b
+        cmp             r4,  #0
+        vmov            q0,  q1
+
+L(ipred_cfl_ac_420_w8_hpad):
+        beq             3f // This assumes that all callers already did "cmp r4, #0"
+2:      // Vertical padding (h_pad > 0)
+        subs            r4,  r4,  #4
+        vst1.16         {q0, q1}, [r0, :128]!
+        vadd.i16        q8,  q8,  q0
+        vadd.i16        q9,  q9,  q1
+        vst1.16         {q0, q1}, [r0, :128]!
+        vadd.i16        q10, q10, q0
+        vadd.i16        q11, q11, q1
+        bgt             2b
+3:
+
+        // Double the height and reuse the w4 summing/subtracting
+        lsl             r6,  r6,  #1
+        b               L(ipred_cfl_ac_420_w4_calc_subtract_dc)
+
+L(ipred_cfl_ac_420_w16):
+        adr             r7,  L(ipred_cfl_ac_420_w16_tbl)
+        ldr             r3,  [r7, r3, lsl #2]
+        add             r7,  r7,  r3
+        bx              r7
+
+        .align 2
+L(ipred_cfl_ac_420_w16_tbl):
+        .word L(ipred_cfl_ac_420_w16_wpad0) - L(ipred_cfl_ac_420_w16_tbl) + CONFIG_THUMB
+        .word L(ipred_cfl_ac_420_w16_wpad1) - L(ipred_cfl_ac_420_w16_tbl) + CONFIG_THUMB
+        .word L(ipred_cfl_ac_420_w16_wpad2) - L(ipred_cfl_ac_420_w16_tbl) + CONFIG_THUMB
+        .word L(ipred_cfl_ac_420_w16_wpad3) - L(ipred_cfl_ac_420_w16_tbl) + CONFIG_THUMB
+
+L(ipred_cfl_ac_420_w16_wpad0):
+1:      // Copy and subsample input, without padding
+        vld1.8          {q0, q1},   [r1,  :128], r2
+        vld1.8          {q2, q3},   [r12, :128], r2
+        vpaddl.u8       q0,  q0
+        vld1.8          {q12, q13}, [r1,  :128], r2
+        vpaddl.u8       q1,  q1
+        vpaddl.u8       q2,  q2
+        vpaddl.u8       q3,  q3
+        vadd.i16        q0,  q0,  q2
+        vadd.i16        q1,  q1,  q3
+        vld1.8          {q2, q3},   [r12, :128], r2
+        vpaddl.u8       q12, q12
+        vpaddl.u8       q13, q13
+        vpaddl.u8       q2,  q2
+        vpaddl.u8       q3,  q3
+        vadd.i16        q12, q12, q2
+        vadd.i16        q13, q13, q3
+        vshl.i16        q0,  q0,  #1
+        vshl.i16        q1,  q1,  #1
+        vshl.i16        q2,  q12, #1
+        vshl.i16        q3,  q13, #1
+        subs            r8,  r8,  #2
+        vst1.16         {q0, q1}, [r0, :128]!
+        vadd.i16        q8,  q8,  q0
+        vadd.i16        q9,  q9,  q1
+        vst1.16         {q2, q3}, [r0, :128]!
+        vadd.i16        q10, q10, q2
+        vadd.i16        q11, q11, q3
+        bgt             1b
+        cmp             r4,  #0
+        vmov            q0,  q2
+        vmov            q1,  q3
+        b               L(ipred_cfl_ac_420_w16_hpad)
+
+L(ipred_cfl_ac_420_w16_wpad1):
+1:      // Copy and subsample input, padding 4
+        vldr            d2,    [r1,  #16]
+        vld1.8          {q0},  [r1,  :128], r2
+        vldr            d6,    [r12, #16]
+        vld1.8          {q2},  [r12, :128], r2
+        vpaddl.u8       d2,  d2
+        vldr            d26,   [r1,  #16]
+        vpaddl.u8       q0,  q0
+        vld1.8          {q12}, [r1,  :128], r2
+        vpaddl.u8       d6,  d6
+        vldr            d30,   [r12, #16]
+        vpaddl.u8       q2,  q2
+        vld1.8          {q14}, [r12, :128], r2
+        vpaddl.u8       d26, d26
+        vpaddl.u8       q12, q12
+        vpaddl.u8       d30, d30
+        vpaddl.u8       q14, q14
+        vadd.i16        d2,  d2,  d6
+        vadd.i16        q0,  q0,  q2
+        vadd.i16        d26, d26, d30
+        vadd.i16        q12, q12, q14
+        vshl.i16        d2,  d2,  #1
+        vshl.i16        q0,  q0,  #1
+        vshl.i16        d6,  d26, #1
+        vshl.i16        q2,  q12, #1
+        vdup.16         d3,  d2[3]
+        vdup.16         d7,  d6[3]
+        subs            r8,  r8,  #2
+        vst1.16         {q0, q1}, [r0, :128]!
+        vadd.i16        q8,  q8,  q0
+        vadd.i16        q9,  q9,  q1
+        vst1.16         {q2, q3}, [r0, :128]!
+        vadd.i16        q10, q10, q2
+        vadd.i16        q11, q11, q3
+        bgt             1b
+        cmp             r4,  #0
+        vmov            q0,  q2
+        vmov            q1,  q3
+        b               L(ipred_cfl_ac_420_w16_hpad)
+
+L(ipred_cfl_ac_420_w16_wpad2):
+1:      // Copy and subsample input, padding 8
+        vld1.8          {q0}, [r1,  :128], r2
+        vld1.8          {q1}, [r12, :128], r2
+        vld1.8          {q2}, [r1,  :128], r2
+        vpaddl.u8       q0,  q0
+        vld1.8          {q3}, [r12, :128], r2
+        vpaddl.u8       q1,  q1
+        vpaddl.u8       q2,  q2
+        vpaddl.u8       q3,  q3
+        vadd.i16        q0,  q0,  q1
+        vadd.i16        q2,  q2,  q3
+        vshl.i16        q0,  q0,  #1
+        vshl.i16        q2,  q2,  #1
+        vdup.16         q1,  d1[3]
+        vdup.16         q3,  d5[3]
+        subs            r8,  r8,  #2
+        vst1.16         {q0, q1}, [r0, :128]!
+        vadd.i16        q8,  q8,  q0
+        vadd.i16        q9,  q9,  q1
+        vst1.16         {q2, q3}, [r0, :128]!
+        vadd.i16        q10, q10, q2
+        vadd.i16        q11, q11, q3
+        bgt             1b
+        cmp             r4,  #0
+        vmov            q0,  q2
+        vmov            q1,  q3
+        b               L(ipred_cfl_ac_420_w16_hpad)
+
+L(ipred_cfl_ac_420_w16_wpad3):
+1:      // Copy and subsample input, padding 12
+        vld1.8          {d0}, [r1,  :64], r2
+        vld1.8          {d1}, [r12, :64], r2
+        vld1.8          {d4}, [r1,  :64], r2
+        vpaddl.u8       q0,  q0
+        vld1.8          {d5}, [r12, :64], r2
+        vpaddl.u8       q2,  q2
+        vadd.i16        d0,  d0,  d1
+        vadd.i16        d4,  d4,  d5
+        vshl.i16        d0,  d0,  #1
+        vshl.i16        d4,  d4,  #1
+        vdup.16         q1,  d0[3]
+        vdup.16         q3,  d4[3]
+        vdup.16         d1,  d0[3]
+        vdup.16         d5,  d4[3]
+        subs            r8,  r8,  #2
+        vst1.16         {q0, q1}, [r0, :128]!
+        vadd.i16        q8,  q8,  q0
+        vadd.i16        q9,  q9,  q1
+        vst1.16         {q2, q3}, [r0, :128]!
+        vadd.i16        q10, q10, q2
+        vadd.i16        q11, q11, q3
+        bgt             1b
+        cmp             r4,  #0
+        vmov            q0,  q2
+        vmov            q1,  q3
+        b               L(ipred_cfl_ac_420_w16_hpad)
+
+L(ipred_cfl_ac_420_w16_hpad):
+        beq             3f // This assumes that all callers already did "cmp r4, #0"
+2:      // Vertical padding (h_pad > 0)
+        subs            r4,  r4,  #2
+        vst1.16         {q0, q1}, [r0, :128]!
+        vadd.i16        q8,  q8,  q0
+        vadd.i16        q9,  q9,  q1
+        vst1.16         {q2, q3}, [r0, :128]!
+        vadd.i16        q10, q10, q2
+        vadd.i16        q11, q11, q3
+        bgt             2b
+3:
+
+        // Quadruple the height and reuse the w4 summing/subtracting
+        lsl             r6,  r6,  #2
+        b               L(ipred_cfl_ac_420_w4_calc_subtract_dc)
+endfunc
+
+// void cfl_ac_422_8bpc_neon(int16_t *const ac, const pixel *const ypx,
+//                           const ptrdiff_t stride, const int w_pad,
+//                           const int h_pad, const int cw, const int ch);
+function ipred_cfl_ac_422_8bpc_neon, export=1
+        push            {r4-r8,lr}
+        ldr             r4,  [sp, #24]
+        ldr             r5,  [sp, #28]
+        ldr             r6,  [sp, #32]
+        clz             r8,  r5
+        lsl             r4,  r4,  #2
+        adr             r7,  L(ipred_cfl_ac_422_tbl)
+        sub             r8,  r8,  #27
+        ldr             r8,  [r7, r8, lsl #2]
+        vmov.i16        q8,  #0
+        vmov.i16        q9,  #0
+        vmov.i16        q10, #0
+        vmov.i16        q11, #0
+        add             r7,  r7,  r8
+        sub             r8,  r6,  r4  // height - h_pad
+        rbit            lr,  r5       // rbit(width)
+        rbit            r12, r6       // rbit(height)
+        clz             lr,  lr       // ctz(width)
+        clz             r12, r12      // ctz(height)
+        add             lr,  lr,  r12 // log2sz
+        add             r12, r1,  r2
+        vdup.32         d31, lr
+        lsl             r2,  r2,  #1
+        vneg.s32        d31, d31      // -log2sz
+        bx              r7
+
+        .align 2
+L(ipred_cfl_ac_422_tbl):
+        .word L(ipred_cfl_ac_422_w16) - L(ipred_cfl_ac_422_tbl) + CONFIG_THUMB
+        .word L(ipred_cfl_ac_422_w8) - L(ipred_cfl_ac_422_tbl) + CONFIG_THUMB
+        .word L(ipred_cfl_ac_422_w4) - L(ipred_cfl_ac_422_tbl) + CONFIG_THUMB
+
+L(ipred_cfl_ac_422_w4):
+1:      // Copy and subsample input
+        vld1.8          {d0}, [r1,  :64], r2
+        vld1.8          {d1}, [r12, :64], r2
+        vld1.8          {d2}, [r1,  :64], r2
+        vld1.8          {d3}, [r12, :64], r2
+        vpaddl.u8       q0,  q0
+        vpaddl.u8       q1,  q1
+        vshl.i16        q0,  q0,  #2
+        vshl.i16        q1,  q1,  #2
+        subs            r8,  r8,  #4
+        vst1.16         {q0, q1}, [r0, :128]!
+        vadd.i16        q8,  q8,  q0
+        vadd.i16        q9,  q9,  q1
+        bgt             1b
+        cmp             r4,  #0
+        vmov            d0,  d3
+        vmov            d1,  d3
+        vmov            d2,  d3
+        b               L(ipred_cfl_ac_420_w4_hpad)
+
+L(ipred_cfl_ac_422_w8):
+        cmp             r3,  #0
+        bne             L(ipred_cfl_ac_422_w8_wpad)
+1:      // Copy and subsample input, without padding
+        vld1.8          {q0}, [r1,  :128], r2
+        vld1.8          {q1}, [r12, :128], r2
+        vld1.8          {q2}, [r1,  :128], r2
+        vpaddl.u8       q0,  q0
+        vld1.8          {q3}, [r12, :128], r2
+        vpaddl.u8       q1,  q1
+        vpaddl.u8       q2,  q2
+        vpaddl.u8       q3,  q3
+        vshl.i16        q0,  q0,  #2
+        vshl.i16        q1,  q1,  #2
+        vshl.i16        q2,  q2,  #2
+        vshl.i16        q3,  q3,  #2
+        subs            r8,  r8,  #4
+        vst1.16         {q0, q1}, [r0, :128]!
+        vadd.i16        q8,  q8,  q0
+        vadd.i16        q9,  q9,  q1
+        vst1.16         {q2, q3}, [r0, :128]!
+        vadd.i16        q10, q10, q2
+        vadd.i16        q11, q11, q3
+        bgt             1b
+        cmp             r4,  #0
+        vmov            q0,  q3
+        vmov            q1,  q3
+        b               L(ipred_cfl_ac_420_w8_hpad)
+
+L(ipred_cfl_ac_422_w8_wpad):
+1:      // Copy and subsample input, padding 4
+        vld1.8          {d0}, [r1,  :64], r2
+        vld1.8          {d1}, [r12, :64], r2
+        vld1.8          {d2}, [r1,  :64], r2
+        vld1.8          {d3}, [r12, :64], r2
+        vpaddl.u8       q0,  q0
+        vpaddl.u8       q1,  q1
+        vshl.i16        q0,  q0,  #2
+        vshl.i16        q1,  q1,  #2
+        vdup.16         d7,  d3[3]
+        vmov            d6,  d3
+        vdup.16         d5,  d2[3]
+        vmov            d4,  d2
+        vdup.16         d3,  d1[3]
+        vmov            d2,  d1
+        vdup.16         d1,  d0[3]
+        subs            r8,  r8,  #4
+        vst1.16         {q0, q1}, [r0, :128]!
+        vadd.i16        q8,  q8,  q0
+        vadd.i16        q9,  q9,  q1
+        vst1.16         {q2, q3}, [r0, :128]!
+        vadd.i16        q10, q10, q2
+        vadd.i16        q11, q11, q3
+        bgt             1b
+        cmp             r4,  #0
+        vmov            q0,  q3
+        vmov            q1,  q3
+        b               L(ipred_cfl_ac_420_w8_hpad)
+
+L(ipred_cfl_ac_422_w16):
+        adr             r7,  L(ipred_cfl_ac_422_w16_tbl)
+        ldr             r3,  [r7, r3, lsl #2]
+        add             r7,  r7,  r3
+        bx              r7
+
+        .align 2
+L(ipred_cfl_ac_422_w16_tbl):
+        .word L(ipred_cfl_ac_422_w16_wpad0) - L(ipred_cfl_ac_422_w16_tbl) + CONFIG_THUMB
+        .word L(ipred_cfl_ac_422_w16_wpad1) - L(ipred_cfl_ac_422_w16_tbl) + CONFIG_THUMB
+        .word L(ipred_cfl_ac_422_w16_wpad2) - L(ipred_cfl_ac_422_w16_tbl) + CONFIG_THUMB
+        .word L(ipred_cfl_ac_422_w16_wpad3) - L(ipred_cfl_ac_422_w16_tbl) + CONFIG_THUMB
+
+L(ipred_cfl_ac_422_w16_wpad0):
+1:      // Copy and subsample input, without padding
+        vld1.8          {q0, q1}, [r1,  :128], r2
+        vld1.8          {q2, q3}, [r12, :128], r2
+        vpaddl.u8       q0,  q0
+        vpaddl.u8       q1,  q1
+        vpaddl.u8       q2,  q2
+        vpaddl.u8       q3,  q3
+        vshl.i16        q0,  q0,  #2
+        vshl.i16        q1,  q1,  #2
+        vshl.i16        q2,  q2,  #2
+        vshl.i16        q3,  q3,  #2
+        subs            r8,  r8,  #2
+        vst1.16         {q0, q1}, [r0, :128]!
+        vadd.i16        q8,  q8,  q0
+        vadd.i16        q9,  q9,  q1
+        vst1.16         {q2, q3}, [r0, :128]!
+        vadd.i16        q10, q10, q2
+        vadd.i16        q11, q11, q3
+        bgt             1b
+        cmp             r4,  #0
+        vmov            q0,  q2
+        vmov            q1,  q3
+        b               L(ipred_cfl_ac_420_w16_hpad)
+
+L(ipred_cfl_ac_422_w16_wpad1):
+1:      // Copy and subsample input, padding 4
+        vldr            d2,   [r1,  #16]
+        vld1.8          {q0}, [r1,  :128], r2
+        vldr            d6,   [r12, #16]
+        vld1.8          {q2}, [r12, :128], r2
+        vpaddl.u8       d2,  d2
+        vpaddl.u8       q0,  q0
+        vpaddl.u8       d6,  d6
+        vpaddl.u8       q2,  q2
+        vshl.i16        d2,  d2,  #2
+        vshl.i16        q0,  q0,  #2
+        vshl.i16        d6,  d6,  #2
+        vshl.i16        q2,  q2,  #2
+        vdup.16         d3,  d2[3]
+        vdup.16         d7,  d6[3]
+        subs            r8,  r8,  #2
+        vst1.16         {q0, q1}, [r0, :128]!
+        vadd.i16        q8,  q8,  q0
+        vadd.i16        q9,  q9,  q1
+        vst1.16         {q2, q3}, [r0, :128]!
+        vadd.i16        q10, q10, q2
+        vadd.i16        q11, q11, q3
+        bgt             1b
+        cmp             r4,  #0
+        vmov            q0,  q2
+        vmov            q1,  q3
+        b               L(ipred_cfl_ac_420_w16_hpad)
+
+L(ipred_cfl_ac_422_w16_wpad2):
+1:      // Copy and subsample input, padding 8
+        vld1.8          {q0}, [r1,  :128], r2
+        vld1.8          {q2}, [r12, :128], r2
+        vpaddl.u8       q0,  q0
+        vpaddl.u8       q2,  q2
+        vshl.i16        q0,  q0,  #2
+        vshl.i16        q2,  q2,  #2
+        vdup.16         q1,  d1[3]
+        vdup.16         q3,  d5[3]
+        subs            r8,  r8,  #2
+        vst1.16         {q0, q1}, [r0, :128]!
+        vadd.i16        q8,  q8,  q0
+        vadd.i16        q9,  q9,  q1
+        vst1.16         {q2, q3}, [r0, :128]!
+        vadd.i16        q10, q10, q2
+        vadd.i16        q11, q11, q3
+        bgt             1b
+        cmp             r4,  #0
+        vmov            q0,  q2
+        vmov            q1,  q3
+        b               L(ipred_cfl_ac_420_w16_hpad)
+
+L(ipred_cfl_ac_422_w16_wpad3):
+1:      // Copy and subsample input, padding 12
+        vld1.8          {d0}, [r1,  :64], r2
+        vld1.8          {d1}, [r12, :64], r2
+        vpaddl.u8       q0,  q0
+        vshl.i16        q0,  q0,  #2
+        vdup.16         q3,  d1[3]
+        vdup.16         q1,  d0[3]
+        vdup.16         d5,  d1[3]
+        vmov            d4,  d1
+        vdup.16         d1,  d0[3]
+        subs            r8,  r8,  #2
+        vst1.16         {q0, q1}, [r0, :128]!
+        vadd.i16        q8,  q8,  q0
+        vadd.i16        q9,  q9,  q1
+        vst1.16         {q2, q3}, [r0, :128]!
+        vadd.i16        q10, q10, q2
+        vadd.i16        q11, q11, q3
+        bgt             1b
+        cmp             r4,  #0
+        vmov            q0,  q2
+        vmov            q1,  q3
+        b               L(ipred_cfl_ac_420_w16_hpad)
+endfunc
+
+// void cfl_ac_444_8bpc_neon(int16_t *const ac, const pixel *const ypx,
+//                           const ptrdiff_t stride, const int w_pad,
+//                           const int h_pad, const int cw, const int ch);
+function ipred_cfl_ac_444_8bpc_neon, export=1
+        push            {r4-r8,lr}
+        ldr             r4,  [sp, #24]
+        ldr             r5,  [sp, #28]
+        ldr             r6,  [sp, #32]
+        clz             r8,  r5
+        lsl             r4,  r4,  #2
+        adr             r7,  L(ipred_cfl_ac_444_tbl)
+        sub             r8,  r8,  #26
+        ldr             r8,  [r7, r8, lsl #2]
+        vmov.i16        q8,  #0
+        vmov.i16        q9,  #0
+        vmov.i16        q10, #0
+        vmov.i16        q11, #0
+        add             r7,  r7,  r8
+        sub             r8,  r6,  r4  // height - h_pad
+        rbit            lr,  r5       // rbit(width)
+        rbit            r12, r6       // rbit(height)
+        clz             lr,  lr       // ctz(width)
+        clz             r12, r12      // ctz(height)
+        add             lr,  lr,  r12 // log2sz
+        add             r12, r1,  r2
+        vdup.32         d31, lr
+        lsl             r2,  r2,  #1
+        vneg.s32        d31, d31      // -log2sz
+        bx              r7
+
+        .align 2
+L(ipred_cfl_ac_444_tbl):
+        .word L(ipred_cfl_ac_444_w32) - L(ipred_cfl_ac_444_tbl) + CONFIG_THUMB
+        .word L(ipred_cfl_ac_444_w16) - L(ipred_cfl_ac_444_tbl) + CONFIG_THUMB
+        .word L(ipred_cfl_ac_444_w8)  - L(ipred_cfl_ac_444_tbl) + CONFIG_THUMB
+        .word L(ipred_cfl_ac_444_w4)  - L(ipred_cfl_ac_444_tbl) + CONFIG_THUMB
+
+L(ipred_cfl_ac_444_w4):
+1:      // Copy and expand input
+        vld1.32         {d0[]},  [r1,  :32], r2
+        vld1.32         {d0[1]}, [r12, :32], r2
+        vld1.32         {d2[]},  [r1,  :32], r2
+        vld1.32         {d2[1]}, [r12, :32], r2
+        vshll.u8        q0,  d0,  #3
+        vshll.u8        q1,  d2,  #3
+        subs            r8,  r8,  #4
+        vst1.16         {q0, q1}, [r0, :128]!
+        vadd.i16        q8,  q8,  q0
+        vadd.i16        q9,  q9,  q1
+        bgt             1b
+        cmp             r4,  #0
+        vmov            d0,  d3
+        vmov            d1,  d3
+        vmov            d2,  d3
+        b               L(ipred_cfl_ac_420_w4_hpad)
+
+L(ipred_cfl_ac_444_w8):
+1:      // Copy and expand input
+        vld1.16         {d0}, [r1,  :64], r2
+        vld1.16         {d2}, [r12, :64], r2
+        vld1.16         {d4}, [r1,  :64], r2
+        vshll.u8        q0,  d0,  #3
+        vld1.16         {d6}, [r12, :64], r2
+        vshll.u8        q1,  d2,  #3
+        vshll.u8        q2,  d4,  #3
+        vshll.u8        q3,  d6,  #3
+        subs            r8,  r8,  #4
+        vst1.16         {q0, q1}, [r0, :128]!
+        vadd.i16        q8,  q8,  q0
+        vadd.i16        q9,  q9,  q1
+        vst1.16         {q2, q3}, [r0, :128]!
+        vadd.i16        q10, q10, q2
+        vadd.i16        q11, q11, q3
+        bgt             1b
+        cmp             r4,  #0
+        vmov            q0,  q3
+        vmov            q1,  q3
+        b               L(ipred_cfl_ac_420_w8_hpad)
+
+L(ipred_cfl_ac_444_w16):
+        cmp             r3,  #0
+        bne             L(ipred_cfl_ac_444_w16_wpad)
+1:      // Copy and expand input, without padding
+        vld1.8          {q1}, [r1,  :128], r2
+        vld1.8          {q3}, [r12, :128], r2
+        vshll.u8        q0,  d2,  #3
+        vshll.u8        q1,  d3,  #3
+        vshll.u8        q2,  d6,  #3
+        vshll.u8        q3,  d7,  #3
+        subs            r8,  r8,  #2
+        vst1.16         {q0, q1}, [r0, :128]!
+        vadd.i16        q8,  q8,  q0
+        vadd.i16        q9,  q9,  q1
+        vst1.16         {q2, q3}, [r0, :128]!
+        vadd.i16        q10, q10, q2
+        vadd.i16        q11, q11, q3
+        bgt             1b
+        cmp             r4,  #0
+        vmov            q0,  q2
+        vmov            q1,  q3
+        b               L(ipred_cfl_ac_420_w16_hpad)
+
+L(ipred_cfl_ac_444_w16_wpad):
+1:      // Copy and expand input, padding 8
+        vld1.8          {d0}, [r1,  :64], r2
+        vld1.8          {d4}, [r12, :64], r2
+        vshll.u8        q0,  d0,  #3
+        vshll.u8        q2,  d4,  #3
+        vdup.16         q1,  d1[3]
+        vdup.16         q3,  d5[3]
+        subs            r8,  r8,  #2
+        vst1.16         {q0, q1}, [r0, :128]!
+        vadd.i16        q8,  q8,  q0
+        vadd.i16        q9,  q9,  q1
+        vst1.16         {q2, q3}, [r0, :128]!
+        vadd.i16        q10, q10, q2
+        vadd.i16        q11, q11, q3
+        bgt             1b
+        cmp             r4,  #0
+        vmov            q0,  q2
+        vmov            q1,  q3
+        b               L(ipred_cfl_ac_420_w16_hpad)
+
+L(ipred_cfl_ac_444_w32):
+        adr             r7,  L(ipred_cfl_ac_444_w32_tbl)
+        ldr             r3,  [r7, r3, lsl #1] // (w3>>1) << 2
+        add             r7,  r7,  r3
+        bx              r7
+
+        .align 2
+L(ipred_cfl_ac_444_w32_tbl):
+        .word L(ipred_cfl_ac_444_w32_wpad0) - L(ipred_cfl_ac_444_w32_tbl) + CONFIG_THUMB
+        .word L(ipred_cfl_ac_444_w32_wpad2) - L(ipred_cfl_ac_444_w32_tbl) + CONFIG_THUMB
+        .word L(ipred_cfl_ac_444_w32_wpad4) - L(ipred_cfl_ac_444_w32_tbl) + CONFIG_THUMB
+        .word L(ipred_cfl_ac_444_w32_wpad6) - L(ipred_cfl_ac_444_w32_tbl) + CONFIG_THUMB
+
+L(ipred_cfl_ac_444_w32_wpad0):
+1:      // Copy and expand input, without padding
+        vld1.8          {q2, q3},   [r1,  :128], r2
+        vld1.8          {q13, q14}, [r12, :128], r2
+        vshll.u8        q0,  d4,  #3
+        vshll.u8        q1,  d5,  #3
+        vshll.u8        q2,  d6,  #3
+        vshll.u8        q3,  d7,  #3
+        vshll.u8        q12, d26, #3
+        vshll.u8        q13, d27, #3
+        subs            r8,  r8,  #2
+        vst1.16         {q0, q1},   [r0, :128]!
+        vadd.i16        q8,  q8,  q0
+        vadd.i16        q9,  q9,  q1
+        vshll.u8        q0,  d28, #3
+        vshll.u8        q1,  d29, #3
+        vst1.16         {q2, q3},   [r0, :128]!
+        vadd.i16        q10, q10, q2
+        vadd.i16        q11, q11, q3
+        vst1.16         {q12, q13}, [r0, :128]!
+        vadd.i16        q8,  q8,  q12
+        vadd.i16        q9,  q9,  q13
+        vst1.16         {q0, q1},   [r0, :128]!
+        vadd.i16        q10, q10, q0
+        vadd.i16        q11, q11, q1
+        bgt             1b
+        cmp             r4,  #0
+        b               L(ipred_cfl_ac_444_w32_hpad)
+
+L(ipred_cfl_ac_444_w32_wpad2):
+1:      // Copy and expand input, padding 8
+        vldr            d4,    [r1,  #16]
+        vld1.8          {q1},  [r1,  :128], r2
+        vldr            d28,   [r12, #16]
+        vld1.8          {q13}, [r12, :128], r2
+        vshll.u8        q2,  d4,  #3
+        vshll.u8        q0,  d2,  #3
+        vshll.u8        q1,  d3,  #3
+        vshll.u8        q12, d26, #3
+        vshll.u8        q13, d27, #3
+        vdup.16         q3,  d5[3]
+        subs            r8,  r8,  #2
+        vst1.16         {q0, q1},   [r0, :128]!
+        vadd.i16        q8,  q8,  q0
+        vadd.i16        q9,  q9,  q1
+        vshll.u8        q0,  d28, #3
+        vst1.16         {q2, q3},   [r0, :128]!
+        vadd.i16        q10, q10, q2
+        vadd.i16        q11, q11, q3
+        vdup.16         q1,  d1[3]
+        vst1.16         {q12, q13}, [r0, :128]!
+        vadd.i16        q8,  q8,  q12
+        vadd.i16        q9,  q9,  q13
+        vst1.16         {q0, q1},   [r0, :128]!
+        vadd.i16        q10, q10, q0
+        vadd.i16        q11, q11, q1
+        bgt             1b
+        cmp             r4,  #0
+        b               L(ipred_cfl_ac_444_w32_hpad)
+
+L(ipred_cfl_ac_444_w32_wpad4):
+1:      // Copy and expand input, padding 16
+        vld1.8          {q1},  [r1,  :128], r2
+        vld1.8          {q13}, [r12, :128], r2
+        vshll.u8        q0,  d2,  #3
+        vshll.u8        q1,  d3,  #3
+        vshll.u8        q12, d26, #3
+        vshll.u8        q13, d27, #3
+        vdup.16         q2,  d3[3]
+        vdup.16         q3,  d3[3]
+        subs            r8,  r8,  #2
+        vst1.16         {q0, q1},   [r0, :128]!
+        vadd.i16        q8,  q8,  q0
+        vadd.i16        q9,  q9,  q1
+        vdup.16         q0,  d27[3]
+        vdup.16         q1,  d27[3]
+        vst1.16         {q2, q3},   [r0, :128]!
+        vadd.i16        q10, q10, q2
+        vadd.i16        q11, q11, q3
+        vst1.16         {q12, q13}, [r0, :128]!
+        vadd.i16        q8,  q8,  q12
+        vadd.i16        q9,  q9,  q13
+        vst1.16         {q0, q1},   [r0, :128]!
+        vadd.i16        q10, q10, q0
+        vadd.i16        q11, q11, q1
+        bgt             1b
+        cmp             r4,  #0
+        b               L(ipred_cfl_ac_444_w32_hpad)
+
+L(ipred_cfl_ac_444_w32_wpad6):
+1:      // Copy and expand input, padding 24
+        vld1.8          {d0},  [r1,  :64], r2
+        vld1.8          {d24}, [r12, :64], r2
+        vshll.u8        q0,  d0,  #3
+        vshll.u8        q12, d24, #3
+        subs            r8,  r8,  #2
+        vdup.16         q1,  d1[3]
+        vdup.16         q2,  d1[3]
+        vdup.16         q3,  d1[3]
+        vst1.16         {q0, q1},   [r0, :128]!
+        vadd.i16        q8,  q8,  q0
+        vadd.i16        q9,  q9,  q1
+        vdup.16         q13, d25[3]
+        vdup.16         q0,  d25[3]
+        vdup.16         q1,  d25[3]
+        vst1.16         {q2, q3},   [r0, :128]!
+        vadd.i16        q10, q10, q2
+        vadd.i16        q11, q11, q3
+        vst1.16         {q12, q13}, [r0, :128]!
+        vadd.i16        q8,  q8,  q12
+        vadd.i16        q9,  q9,  q13
+        vst1.16         {q0, q1},   [r0, :128]!
+        vadd.i16        q10, q10, q0
+        vadd.i16        q11, q11, q1
+        bgt             1b
+        cmp             r4,  #0
+
+L(ipred_cfl_ac_444_w32_hpad):
+        beq             3f // This assumes that all callers already did "cmp r4, #0"
+2:      // Vertical padding (h_pad > 0)
+        subs            r4,  r4,  #1
+        vst1.16         {q12, q13}, [r0, :128]!
+        vadd.i16        q8,  q8,  q12
+        vadd.i16        q9,  q9,  q13
+        vst1.16         {q0, q1},   [r0, :128]!
+        vadd.i16        q10, q10, q0
+        vadd.i16        q11, q11, q1
+        bgt             2b
+3:
+
+        //  Multiply the height by eight and reuse the w4 subtracting
+        lsl             r6,  r6,  #3
+        // Aggregate the sums, with wider intermediates earlier than in
+        // ipred_cfl_ac_420_w8_calc_subtract_dc.
+        vpaddl.u16      q0,  q8
+        vpaddl.u16      q1,  q9
+        vpaddl.u16      q2,  q10
+        vpaddl.u16      q3,  q11
+        vadd.i32        q0,  q0,  q1
+        vadd.i32        q2,  q2,  q3
+        vadd.i32        q0,  q0,  q2
+        vadd.i32        d0,  d0,  d1
+        vpadd.i32       d0,  d0,  d0  // sum
+        sub             r0,  r0,  r6, lsl #3
+        vrshl.u32       d16, d0,  d31 // (sum + (1 << (log2sz - 1))) >>= log2sz
+        vdup.16         q8,  d16[0]
+        b               L(ipred_cfl_ac_420_w4_subtract_dc)
+endfunc
--- a/src/arm/ipred_init_tmpl.c
+++ b/src/arm/ipred_init_tmpl.c
@@ -62,7 +62,6 @@
     c->intra_pred[LEFT_DC_PRED]  = BF(dav1d_ipred_dc_left, neon);
     c->intra_pred[HOR_PRED]      = BF(dav1d_ipred_h, neon);
     c->intra_pred[VERT_PRED]     = BF(dav1d_ipred_v, neon);
-#if ARCH_AARCH64
     c->intra_pred[PAETH_PRED]    = BF(dav1d_ipred_paeth, neon);
     c->intra_pred[SMOOTH_PRED]   = BF(dav1d_ipred_smooth, neon);
     c->intra_pred[SMOOTH_V_PRED] = BF(dav1d_ipred_smooth_v, neon);
@@ -79,6 +78,5 @@
     c->cfl_ac[DAV1D_PIXEL_LAYOUT_I444 - 1] = BF(dav1d_ipred_cfl_ac_444, neon);
 
     c->pal_pred                  = BF(dav1d_pal_pred, neon);
-#endif
 #endif
 }
--- a/src/tables.c
+++ b/src/tables.c
@@ -692,7 +692,7 @@
     { 0, -1,  2,   -4, -127,  3, -1, 0 }, { 0,  0,  1,   -2, -128,  1,  0, 0 },
 };
 
-const uint8_t dav1d_sm_weights[128] = {
+const uint8_t ALIGN(dav1d_sm_weights[128], 16) = {
     // Unused, because we always offset by bs, which is at least 2.
       0,   0,
     // bs = 2