xref: /aosp_15_r20/external/XNNPACK/src/qs8-igemm/4x8-aarch32-neon-mlal-lane-cortex-a7.S.in (revision 4bdc94577ba0e567308109d787f7fec7b531ce36)
1// Copyright 2021 Google LLC
2//
3// This source code is licensed under the BSD-style license found in the
4// LICENSE file in the root directory of this source tree.
5
6$assert REQUANTIZATION in ["FP32", "RNDNU"]
7$assert not CHANNELWISE or REQUANTIZATION == "FP32"
8$assert DATATYPE in ["QC8", "QS8", "QU8"]
9$assert DATATYPE != "QC8" or REQUANTIZATION == "FP32"
10
11#include <xnnpack/assembly.h>
12
13.syntax unified
14
15$PARAMS_UNION = "xnn_qs8_minmax_params" if CHANNELWISE else "xnn_qs8_conv_minmax_params"
16$ISA = "neonv8" if ARMV8 else "neon"
17$CPU = "a35" if ARMV8 else "a7"
18$XMIN = "VMIN.U8" if DATATYPE == "QU8" else "VMIN.S8"
19$XMAX = "VMAX.U8" if DATATYPE == "QU8" else "VMAX.S8"
20$XXTL = "VMOVL.U8" if DATATYPE == "QU8" else "VMOVL.S8"
21$SQXTXN = "VQMOVUN.S16" if DATATYPE == "QU8" else "VQMOVN.S16"
22$XINT8_T = "uint8_t" if DATATYPE == "QU8" else "int8_t"
23// void xnn_${DATATYPE.lower()}_igemm_minmax_${REQUANTIZATION.lower()}_ukernel_4x8__aarch32_${ISA}_mlal_lane${"_prfm" if PREFETCH else ""}_cortex_${CPU}(
24//     size_t mr,                                     (r0)
25//     size_t nc,                                      r1 -> sp + 56
26//     size_t kc,                                     (r2) -> r5 -> sp + 60
27//     size_t ks,                                     (r3) -> sp + 64 -> r14
28//     const ${XINT8_T}**restrict a,            sp + 104  -> r2
29//     const void*restrict w,              sp + 108  -> r9
30//     ${XINT8_T}*restrict c,                   sp + 112  -> r11
31//     size_t cm_stride,                   sp + 116  -> (r6)
32//     size_t cn_stride,                   sp + 120  -> (r7)
33//     size_t a_offset,                    sp + 124 -> (r5)
34//     const ${XINT8_T}* zero,                  sp + 128 -> (r7)
35//     ${PARAMS_UNION}*params); sp + 132 -> (r5)
36
37// d8-d15, r4-r11,r14(lr) need to be preserved if used. r13(sp),r15(pc) are reserved.
38
39// Register usage
40// A0   r3  d0-d1 q0
41// A1  r12  d2-d3 q1
42// A2  r10  d4-d5 q2
43// A3   r0  d6-d7 q3
44
45// B    r9  d8-d9 q4 q5
46
47// C0  r11 d16-d17  q8  d18-d19  q9
48// C1   r4 d20-d21 q10  d22-d23 q11
49// C2   r8 d24-d25 q12  d26-d27 q13
50// C3   r6 d28-d29 q14  d30-d31 q15
51
52// Unused d15
53
54$if REQUANTIZATION == "RNDNU" and DATATYPE != "QU8":
55  // params structure is 16 bytes
56  //  struct {
57  //    int32_t right_pre_shift;    d12[0]
58  //    int32_t multiplier;         d12[1]
59  //    int32_t right_post_shift;   d13[0]
60  //    int16_t output_zero_point;  d13[2]
61  //    int8_t output_min;          d13[6]
62  //    int8_t output_max;          d13[7]
63  //  } rndnu_neon;
64$elif REQUANTIZATION == "RNDNU" and DATATYPE == "QU8":
65  // params structure is 20 bytes
66  //  struct {
67  //    uint8_t kernel_zero_point[4];  d14
68  //    int32_t right_pre_shift;       d12[0]
69  //    int32_t multiplier;            d12[1]
70  //    int32_t right_post_shift;      d13[0]
71  //    int16_t output_zero_point;     d13[2]
72  //    uint8_t output_min;            d13[6]
73  //    uint8_t output_max;            d13[7]
74  //  } rndnu_neon;
75$elif DATATYPE == "QC8" and not ARMV8:
76  // params structure is 10 bytes
77  // struct {
78  //   float magic_bias;                           d12[0]
79  //   int32_t magic_bias_less_output_zero_point;  d12[1]
80  //   int8_t output_min;                          d13[6]
81  //   int8_t output_max;                          d13[7]
82  // } xnn_qs8_minmax_params.neon;
83$else:
84  // params structure is 4 bytes
85  //  struct {
86  //    int16_t output_zero_point;  d13[2]
87  //    int8_t output_min;          d13[6]
88  //    int8_t output_max;          d13[7]
89  //  } xnn_qs8_minmax_params.neonv8;
90
91BEGIN_FUNCTION xnn_${DATATYPE.lower()}_igemm_minmax_${REQUANTIZATION.lower()}_ukernel_4x8__aarch32_${ISA}_mlal_lane${"_prfm" if PREFETCH else ""}_cortex_${CPU}
92        # Push 104 bytes
93        # r1, r2 will be reloaded in outer loop.  r3 is ks
94        PUSH    {r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, lr}   // +48
95        $if DATATYPE == "QU8":
96          VPUSH   {d8-d14}                            // +56 = 104
97        $else:
98          SUB     sp, sp, 8                           // +8
99          VPUSH   {d8-d13}                            // +48 = 104
100
101        LDR     r11, [sp, 112]          // c
102        LDR     r6, [sp, 116]           // cm_stride
103        LDR     r2, [sp, 104]           // a
104        LDR     r9, [sp, 108]           // w
105        LDR     r5, [sp, 132]           // params
106        MOV     r14, r3                 // p = ks
107
108        # Clamp C pointers
109        CMP     r0, 2                   // if mr >= 2
110        ADD     r4, r11, r6             //   c1 = c0 + cm_stride
111        MOVLO   r4, r11                 // c1
112                                        // if mr > 2
113        ADD     r8, r4, r6              //   c2 = c1 + cm_stride
114        MOVLS   r8, r4                  // c2
115        CMP     r0, 4                   // if mr >=4
116        ADD     r6, r8, r6              //   c3 = c2 + cm_stride
117        MOVLO   r6, r8                  // c3
118
119        # Load params values
120        $if DATATYPE == "QU8":
121          VLD1.32 {d14[]}, [r5]!          // QU8 kernel_zero_point
122        $if REQUANTIZATION == "RNDNU":
123          VLDM    r5, {d12-d13}           // RNDNU params
124        $elif DATATYPE == "QC8" and ARMV8:
125          VLD1.32 {d13[]}, [r5]           // QC8 neonv8 params
126        $elif DATATYPE == "QC8" and not ARMV8:
127          VLDM    r5!, {d12}              // QC8 neon params
128          VLD1.16 {d13[]}, [r5]
129
130        $if PREFETCH:
131          PLD     [r9,  64]               // Prefetch B
132          PLD     [r9, 128]
133          PLD     [r9, 192]
134          PLD     [r9, 256]
135          PLD     [r9, 320]
136          PLD     [r9, 384]
137
138        .p2align 3
1390:
140        # Load initial bias from w into accumulators
141        VLDM    r9!, {d16-d19}          // Bias
142        VMOV    q10, q8
143        VMOV    q11, q9
144        STR     r1, [sp, 56]            // save nc
145        VMOV    q12, q8
146        VMOV    q13, q9
147        VMOV    q14, q8
148        VMOV    q15, q9
149
150        .p2align 3
1511:
152        # Load next 4 A pointers
153        LDR     r3, [r2,  0]
154        LDR     r12, [r2,  4]
155        LDR     r10, [r2,  8]
156        LDR     r0, [r2, 12]
157
158        # Add a_offset
159        LDR     r5, [sp, 124]           // a_offset
160        LDR     r7, [sp, 128]           // zero
161        ADD     r2, r2, 16
162        CMP     r3,  r7                 // if a0 == zero
163        ADD     r3,  r3, r5             // a0 += a_offset
164        MOVEQ   r3,  r7                 //   a0 = zero, else += a0 + a_offset
165        CMP     r12,  r7                // if a1 == zero
166        ADD     r12, r12, r5            // a1 += a_offset
167        MOVEQ   r12,  r7                //   a1 = zero, else += a1 + a_offset
168        CMP     r10,  r7                // if a2 == zero
169        ADD     r10, r10, r5            // a2 += a_offset
170        MOVEQ   r10,  r7                //   a2 = zero, else += a2 + a_offset
171        CMP     r0,  r7                 // if a3 == zero
172        ADD     r0,  r0, r5             // a3 += a_offset
173        LDR     r5, [sp, 60]            // kc
174        MOVEQ   r0,  r7                 //   a3 = zero, else += a3 + a_offset
175        SUBS    r5, r5, 8               // kc - 8
176        BLO     5f                      // less than 8 channels?
177
178        // Prologue - load 4A's and B0
179        VLD1.8  {d0},  [r3]!            // A0
180        VLD1.8  {d8},  [r9]!            // B0
181        SUBS    r5, r5, 8               // k = k - 8
182        VLD1.8  {d2}, [r12]!            // A1
183        VLD1.8  {d4}, [r10]!            // A2
184        VLD1.8  {d6},  [r0]!            // A3
185
186        BLO     3f                      // less than 8 channels?
187
188        // Main loop - 8 bytes
189        // 64 bytes for weights.
190        // 5 VMOVL = 4 A and 1 B = 5 cycles
191        // 7 blocks with VLD B, VMOVL, 8 VMLA = 10 cycles
192        // 1 blocks with VLD B, VMLA = 9 cycles
193        // total = 84 cycles
194        .p2align 3
1952:
196        // Extend - 5 cycles
197        ${XXTL} q0, d0
198        $if DATATYPE == "QU8":
199          VSUBL.U8 q4, d8, d14
200        $else:
201          VMOVL.S8 q4, d8
202        $if PREFETCH:
203          PLD     [r9, 448]
204        ${XXTL} q1, d2
205        ${XXTL} q2, d4
206        ${XXTL} q3, d6
207
208        // BLOCK 0 - 10 cycles
209        VLD1.8  {d10},  [r9]!           // B1
210        VMLAL.S16 q8, d8, d0[0]
211        VMLAL.S16 q9, d9, d0[0]
212        VMLAL.S16 q10, d8, d2[0]
213        VMLAL.S16 q11, d9, d2[0]
214        $if DATATYPE == "QU8":
215          VSUBL.U8 q5, d10, d14
216        $else:
217          VMOVL.S8 q5, d10
218        VMLAL.S16 q12, d8, d4[0]
219        VMLAL.S16 q13, d9, d4[0]
220        VMLAL.S16 q14, d8, d6[0]
221        VMLAL.S16 q15, d9, d6[0]
222
223        // BLOCK 1 - 10 cycles
224        VLD1.8  {d8},  [r9]!            // B2
225        VMLAL.S16 q8, d10, d0[1]
226        VMLAL.S16 q9, d11, d0[1]
227        VMLAL.S16 q10, d10, d2[1]
228        VMLAL.S16 q11, d11, d2[1]
229        $if DATATYPE == "QU8":
230          VSUBL.U8 q4, d8, d14
231        $else:
232          VMOVL.S8 q4, d8
233        VMLAL.S16 q12, d10, d4[1]
234        VMLAL.S16 q13, d11, d4[1]
235        VMLAL.S16 q14, d10, d6[1]
236        VMLAL.S16 q15, d11, d6[1]
237
238        // BLOCK 2 - 10 cycles
239        VLD1.8  {d10},  [r9]!           // B3
240        VMLAL.S16 q8, d8, d0[2]
241        VMLAL.S16 q9, d9, d0[2]
242        VMLAL.S16 q10, d8, d2[2]
243        VMLAL.S16 q11, d9, d2[2]
244        $if DATATYPE == "QU8":
245          VSUBL.U8 q5, d10, d14
246        $else:
247          VMOVL.S8 q5, d10
248        VMLAL.S16 q12, d8, d4[2]
249        VMLAL.S16 q13, d9, d4[2]
250        VMLAL.S16 q14, d8, d6[2]
251        VMLAL.S16 q15, d9, d6[2]
252
253        // BLOCK 3 - 10 cycles
254        VLD1.8  {d8},  [r9]!            // B4
255        VMLAL.S16 q8, d10, d0[3]
256        VMLAL.S16 q9, d11, d0[3]
257        VMLAL.S16 q10, d10, d2[3]
258        VMLAL.S16 q11, d11, d2[3]
259        VLD1.8  {d0},  [r3]!            // A0
260        $if DATATYPE == "QU8":
261          VSUBL.U8 q4, d8, d14
262        $else:
263          VMOVL.S8 q4, d8
264        VMLAL.S16 q12, d10, d4[3]
265        VMLAL.S16 q13, d11, d4[3]
266        VMLAL.S16 q14, d10, d6[3]
267        VMLAL.S16 q15, d11, d6[3]
268
269        // BLOCK 4 - 10 cycles
270        VLD1.8  {d10},  [r9]!           // B5
271        VMLAL.S16 q8, d8, d1[0]
272        VMLAL.S16 q9, d9, d1[0]
273        VMLAL.S16 q10, d8, d3[0]
274        VMLAL.S16 q11, d9, d3[0]
275        VLD1.8  {d2}, [r12]!            // A1
276        $if DATATYPE == "QU8":
277          VSUBL.U8 q5, d10, d14
278        $else:
279          VMOVL.S8 q5, d10
280        VMLAL.S16 q12, d8, d5[0]
281        VMLAL.S16 q13, d9, d5[0]
282        VMLAL.S16 q14, d8, d7[0]
283        VMLAL.S16 q15, d9, d7[0]
284
285        // BLOCK 5 - 10 cycles
286        VLD1.8  {d8},  [r9]!            // B6
287        VMLAL.S16 q8, d10, d1[1]
288        VMLAL.S16 q9, d11, d1[1]
289        VMLAL.S16 q10, d10, d3[1]
290        VMLAL.S16 q11, d11, d3[1]
291        VLD1.8  {d4}, [r10]!            // A2
292        $if DATATYPE == "QU8":
293          VSUBL.U8 q4, d8, d14
294        $else:
295          VMOVL.S8 q4, d8
296        VMLAL.S16 q12, d10, d5[1]
297        VMLAL.S16 q13, d11, d5[1]
298        VMLAL.S16 q14, d10, d7[1]
299        VMLAL.S16 q15, d11, d7[1]
300
301        // BLOCK 6 - 10 cycles
302        VLD1.8  {d10},  [r9]!           // B7
303        VMLAL.S16 q8, d8, d1[2]
304        VMLAL.S16 q9, d9, d1[2]
305        VMLAL.S16 q10, d8, d3[2]
306        VMLAL.S16 q11, d9, d3[2]
307        VLD1.8  {d6},  [r0]!            // A3
308        $if DATATYPE == "QU8":
309          VSUBL.U8 q5, d10, d14
310        $else:
311          VMOVL.S8 q5, d10
312        VMLAL.S16 q12, d8, d5[2]
313        VMLAL.S16 q13, d9, d5[2]
314        VMLAL.S16 q14, d8, d7[2]
315        VMLAL.S16 q15, d9, d7[2]
316
317        // BLOCK 7 - 9 cycles
318        VLD1.8  {d8},  [r9]!            // B0
319        VMLAL.S16 q8, d10, d1[3]
320        VMLAL.S16 q9, d11, d1[3]
321        VMLAL.S16 q10, d10, d3[3]
322        VMLAL.S16 q11, d11, d3[3]
323        VMLAL.S16 q12, d10, d5[3]
324        VMLAL.S16 q13, d11, d5[3]
325        SUBS    r5, r5, 8
326        VMLAL.S16 q14, d10, d7[3]
327        VMLAL.S16 q15, d11, d7[3]
328        BHS     2b
329
330        // Epilogue
331
332        .p2align 3
3333:
334        ${XXTL} q0, d0
335        $if DATATYPE == "QU8":
336          VSUBL.U8 q4, d8, d14
337        $else:
338          VMOVL.S8 q4, d8
339        ${XXTL} q1, d2
340        ${XXTL} q2, d4
341        ${XXTL} q3, d6
342
343        VLD1.8  {d10},  [r9]!           // B1
344        VMLAL.S16 q8, d8, d0[0]
345        VMLAL.S16 q9, d9, d0[0]
346        VMLAL.S16 q10, d8, d2[0]
347        VMLAL.S16 q11, d9, d2[0]
348        $if DATATYPE == "QU8":
349          VSUBL.U8 q5, d10, d14
350        $else:
351          VMOVL.S8 q5, d10
352        VMLAL.S16 q12, d8, d4[0]
353        VMLAL.S16 q13, d9, d4[0]
354        VMLAL.S16 q14, d8, d6[0]
355        VMLAL.S16 q15, d9, d6[0]
356
357        VLD1.8  {d8},  [r9]!            // B2
358        VMLAL.S16 q8, d10, d0[1]
359        VMLAL.S16 q9, d11, d0[1]
360        VMLAL.S16 q10, d10, d2[1]
361        VMLAL.S16 q11, d11, d2[1]
362        $if DATATYPE == "QU8":
363          VSUBL.U8 q4, d8, d14
364        $else:
365          VMOVL.S8 q4, d8
366        VMLAL.S16 q12, d10, d4[1]
367        VMLAL.S16 q13, d11, d4[1]
368        VMLAL.S16 q14, d10, d6[1]
369        VMLAL.S16 q15, d11, d6[1]
370
371        VLD1.8  {d10},  [r9]!           // B3
372        VMLAL.S16 q8, d8, d0[2]
373        VMLAL.S16 q9, d9, d0[2]
374        VMLAL.S16 q10, d8, d2[2]
375        VMLAL.S16 q11, d9, d2[2]
376        $if DATATYPE == "QU8":
377          VSUBL.U8 q5, d10, d14
378        $else:
379          VMOVL.S8 q5, d10
380        VMLAL.S16 q12, d8, d4[2]
381        VMLAL.S16 q13, d9, d4[2]
382        VMLAL.S16 q14, d8, d6[2]
383        VMLAL.S16 q15, d9, d6[2]
384
385        VLD1.8  {d8},  [r9]!            // B4
386        VMLAL.S16 q8, d10, d0[3]
387        VMLAL.S16 q9, d11, d0[3]
388        VMLAL.S16 q10, d10, d2[3]
389        VMLAL.S16 q11, d11, d2[3]
390        $if DATATYPE == "QU8":
391          VSUBL.U8 q4, d8, d14
392        $else:
393          VMOVL.S8 q4, d8
394        VMLAL.S16 q12, d10, d4[3]
395        VMLAL.S16 q13, d11, d4[3]
396        VMLAL.S16 q14, d10, d6[3]
397        VMLAL.S16 q15, d11, d6[3]
398
399        VLD1.8  {d10},  [r9]!           // B5
400        VMLAL.S16 q8, d8, d1[0]
401        VMLAL.S16 q9, d9, d1[0]
402        VMLAL.S16 q10, d8, d3[0]
403        VMLAL.S16 q11, d9, d3[0]
404        $if DATATYPE == "QU8":
405          VSUBL.U8 q5, d10, d14
406        $else:
407          VMOVL.S8 q5, d10
408        VMLAL.S16 q12, d8, d5[0]
409        VMLAL.S16 q13, d9, d5[0]
410        VMLAL.S16 q14, d8, d7[0]
411        VMLAL.S16 q15, d9, d7[0]
412
413        VLD1.8  {d8},  [r9]!            // B6
414        VMLAL.S16 q8, d10, d1[1]
415        VMLAL.S16 q9, d11, d1[1]
416        VMLAL.S16 q10, d10, d3[1]
417        VMLAL.S16 q11, d11, d3[1]
418        $if DATATYPE == "QU8":
419          VSUBL.U8 q4, d8, d14
420        $else:
421          VMOVL.S8 q4, d8
422        VMLAL.S16 q12, d10, d5[1]
423        VMLAL.S16 q13, d11, d5[1]
424        VMLAL.S16 q14, d10, d7[1]
425        VMLAL.S16 q15, d11, d7[1]
426
427        VLD1.8  {d10},  [r9]!           // B7
428        VMLAL.S16 q8, d8, d1[2]
429        VMLAL.S16 q9, d9, d1[2]
430        VMLAL.S16 q10, d8, d3[2]
431        VMLAL.S16 q11, d9, d3[2]
432        $if DATATYPE == "QU8":
433          VSUBL.U8 q5, d10, d14
434        $else:
435          VMOVL.S8 q5, d10
436        VMLAL.S16 q12, d8, d5[2]
437        VMLAL.S16 q13, d9, d5[2]
438        VMLAL.S16 q14, d8, d7[2]
439        VMLAL.S16 q15, d9, d7[2]
440
441        VMLAL.S16 q8, d10, d1[3]
442        VMLAL.S16 q9, d11, d1[3]
443        VMLAL.S16 q10, d10, d3[3]
444        VMLAL.S16 q11, d11, d3[3]
445        VMLAL.S16 q12, d10, d5[3]
446        VMLAL.S16 q13, d11, d5[3]
447        ADDS    r5, r5, 8
448        VMLAL.S16 q14, d10, d7[3]
449        VMLAL.S16 q15, d11, d7[3]
450
451        # Is there a remainder?- 1-7 bytes of A
452        BNE     6f
453
4544:
455        # ks loop
456        SUBS    r14, r14, 16            // ks -= MR * sizeof(void*)
457        BHI     1b
458
459        LDR     r7, [sp, 120]           // cn_stride
460        LDR     r14, [sp, 64]           // p = ks
461
462        $if REQUANTIZATION == "RNDNU":
463          # RNDNU quantization
464          VDUP.32 q0, d12[0]              // right_pre_shift
465
466          VQSHL.S32 q8,  q8, q0
467          VQSHL.S32 q9,  q9, q0
468          VQSHL.S32 q10, q10, q0
469          VQSHL.S32 q11, q11, q0
470          VQSHL.S32 q12, q12, q0
471          VQSHL.S32 q13, q13, q0
472          VQSHL.S32 q14, q14, q0
473          VQSHL.S32 q15, q15, q0
474
475          VDUP.32 q2, d13[0]              // right_post_shift
476
477          VQDMULH.S32 q8,  q8, d12[1]     // multiplier
478          VQDMULH.S32 q9,  q9, d12[1]
479          VQDMULH.S32 q10, q10, d12[1]
480          VQDMULH.S32 q11, q11, d12[1]
481          VQDMULH.S32 q12, q12, d12[1]
482          VQDMULH.S32 q13, q13, d12[1]
483          VQDMULH.S32 q14, q14, d12[1]
484          VQDMULH.S32 q15, q15, d12[1]
485
486          VRSHL.S32 q8,  q8, q2
487          VRSHL.S32 q9,  q9, q2
488          VRSHL.S32 q10, q10, q2
489          VRSHL.S32 q11, q11, q2
490          VRSHL.S32 q12, q12, q2
491          VRSHL.S32 q13, q13, q2
492          VRSHL.S32 q14, q14, q2
493          VRSHL.S32 q15, q15, q2
494        $elif DATATYPE == "QC8" and ARMV8:
495          # QC8 FP32 quantization
496          VLD1.8  {q0-q1},  [r9]!
497
498          VCVT.F32.S32 q8,  q8
499          VCVT.F32.S32 q9,  q9
500          VCVT.F32.S32 q10, q10
501          VCVT.F32.S32 q11, q11
502          VCVT.F32.S32 q12, q12
503          VCVT.F32.S32 q13, q13
504          VCVT.F32.S32 q14, q14
505          VCVT.F32.S32 q15, q15
506
507          VMUL.F32 q8,  q8, q0            // multiplier
508          VMUL.F32 q9,  q9, q1
509          VMUL.F32 q10, q10, q0
510          VMUL.F32 q11, q11, q1
511          VMUL.F32 q12, q12, q0
512          VMUL.F32 q13, q13, q1
513          VMUL.F32 q14, q14, q0
514          VMUL.F32 q15, q15, q1
515
516          VCVTN.S32.F32 q8,  q8
517          VCVTN.S32.F32 q9,  q9
518          VCVTN.S32.F32 q10, q10
519          VCVTN.S32.F32 q11, q11
520          VCVTN.S32.F32 q12, q12
521          VCVTN.S32.F32 q13, q13
522          VCVTN.S32.F32 q14, q14
523          VCVTN.S32.F32 q15, q15
524        $elif DATATYPE == "QC8" and not ARMV8:
525          # QC8 FP32 quantization
526          VLD1.8  {q0-q1},  [r9]!
527
528          VDUP.32 q2, d12[0]              // magic_bias
529          VDUP.32 q3, d12[1]              // magic_bias_less_output_zero_point
530
531          VCVT.F32.S32 q8,  q8
532          VCVT.F32.S32 q9,  q9
533          VCVT.F32.S32 q10, q10
534          VCVT.F32.S32 q11, q11
535          VCVT.F32.S32 q12, q12
536          VCVT.F32.S32 q13, q13
537          VCVT.F32.S32 q14, q14
538          VCVT.F32.S32 q15, q15
539
540          VMUL.F32 q8,  q8, q0            // multiplier
541          VMUL.F32 q9,  q9, q1
542          VMUL.F32 q10, q10, q0
543          VMUL.F32 q11, q11, q1
544          VMUL.F32 q12, q12, q0
545          VMUL.F32 q13, q13, q1
546          VMUL.F32 q14, q14, q0
547          VMUL.F32 q15, q15, q1
548
549          VADD.F32 q8,  q8, q2            // magic_bias
550          VADD.F32 q9,  q9, q2
551          VADD.F32 q10, q10, q2
552          VADD.F32 q11, q11, q2
553          VADD.F32 q12, q12, q2
554          VADD.F32 q13, q13, q2
555          VADD.F32 q14, q14, q2
556          VADD.F32 q15, q15, q2
557
558          VQSUB.S32 q8,  q8, q3           // magic_bias_less_output_zero_point
559          VQSUB.S32 q9,  q9, q3
560          VQSUB.S32 q10, q10, q3
561          VQSUB.S32 q11, q11, q3
562          VQSUB.S32 q12, q12, q3
563          VQSUB.S32 q13, q13, q3
564          VQSUB.S32 q14, q14, q3
565          VQSUB.S32 q15, q15, q3
566
567        $if DATATYPE != "QC8" or ARMV8:
568          VDUP.16 q0, d13[2]              // output_zero_point
569
570        VQMOVN.S32 d16, q8
571        VQMOVN.S32 d17, q9
572        VQMOVN.S32 d18, q10
573        VQMOVN.S32 d19, q11
574        VQMOVN.S32 d20, q12
575        VQMOVN.S32 d21, q13
576        VQMOVN.S32 d22, q14
577        VQMOVN.S32 d23, q15
578
579        $if DATATYPE != "QC8" or ARMV8:
580          VQADD.S16 q8,  q8, q0
581          VQADD.S16 q9,  q9, q0
582          VQADD.S16 q10, q10, q0
583          VQADD.S16 q11, q11, q0
584
585        LDR     r1, [sp, 56]            // restore nc
586        VDUP.8  q12, d13[6]             // output_min
587
588        ${SQXTXN} d0,  q8
589        ${SQXTXN} d1,  q9
590        ${SQXTXN} d2, q10
591        ${SQXTXN} d3, q11
592
593        VDUP.8  q13, d13[7]             // output_max
594
595        ${XMAX} q0, q0, q12
596        ${XMAX} q1, q1, q12
597
598        SUBS    r1, r1, 8               // nc -= 8
599
600        ${XMIN} q0, q0, q13
601        ${XMIN} q1, q1, q13
602
603        # Store full 4 x 8
604        BLO     7f
605        VST1.8  {d3}, [r6], r7
606        VST1.8  {d2}, [r8], r7
607        VST1.8  {d1}, [r4], r7
608        VST1.8  {d0}, [r11], r7
609        SUB     r2, r2, r14             // a -= ks
610        BHI     0b
611
612        $if DATATYPE == "QU8":
613          VPOP    {d8-d14}
614          ADD     sp, sp, 12              // skip r1, r2, r3
615        $else:
616          VPOP    {d8-d13}
617          ADD     sp, sp, 20              // skip pad of 8, r1, r2, r3
618        POP     {r4, r5, r6, r7, r8, r9, r10, r11, pc}
619
620        # Remainder- 1 to 7 bytes of A
621        .p2align 3
6225:
623        AND     r5, r5, 7               // kc remainder 1 to 7
6246:
625        VLD1.8  {d0},  [r3]
626        VLD1.8  {d8},  [r9]!
627        VLD1.8  {d2}, [r12]
628        VLD1.8  {d4}, [r10]
629        VLD1.8  {d6},  [r0]
630
631        ${XXTL} q0, d0
632        $if DATATYPE == "QU8":
633          VSUBL.U8 q4, d8, d14
634        $else:
635          VMOVL.S8 q4, d8
636        ${XXTL} q1, d2
637        ${XXTL} q2, d4
638        ${XXTL} q3, d6
639        VMLAL.S16 q8, d8, d0[0]
640        VMLAL.S16 q9, d9, d0[0]
641        VMLAL.S16 q10, d8, d2[0]
642        VMLAL.S16 q11, d9, d2[0]
643        VMLAL.S16 q12, d8, d4[0]
644        VMLAL.S16 q13, d9, d4[0]
645        VMLAL.S16 q14, d8, d6[0]
646        VMLAL.S16 q15, d9, d6[0]
647        CMP     r5, 2
648        BLO     4b
649
650        VLD1.8  {d8},  [r9]!
651        $if DATATYPE == "QU8":
652          VSUBL.U8 q4, d8, d14
653        $else:
654          VMOVL.S8 q4, d8
655        VMLAL.S16 q8, d8, d0[1]
656        VMLAL.S16 q9, d9, d0[1]
657        VMLAL.S16 q10, d8, d2[1]
658        VMLAL.S16 q11, d9, d2[1]
659        VMLAL.S16 q12, d8, d4[1]
660        VMLAL.S16 q13, d9, d4[1]
661        VMLAL.S16 q14, d8, d6[1]
662        VMLAL.S16 q15, d9, d6[1]
663        BEQ     4b
664
665        VLD1.8  {d8},  [r9]!
666        $if DATATYPE == "QU8":
667          VSUBL.U8 q4, d8, d14
668        $else:
669          VMOVL.S8 q4, d8
670        VMLAL.S16 q8, d8, d0[2]
671        VMLAL.S16 q9, d9, d0[2]
672        VMLAL.S16 q10, d8, d2[2]
673        VMLAL.S16 q11, d9, d2[2]
674        VMLAL.S16 q12, d8, d4[2]
675        VMLAL.S16 q13, d9, d4[2]
676        VMLAL.S16 q14, d8, d6[2]
677        VMLAL.S16 q15, d9, d6[2]
678        CMP     r5, 4
679        BLO     4b
680
681        VLD1.8  {d8},  [r9]!
682        $if DATATYPE == "QU8":
683          VSUBL.U8 q4, d8, d14
684        $else:
685          VMOVL.S8 q4, d8
686        VMLAL.S16 q8, d8, d0[3]
687        VMLAL.S16 q9, d9, d0[3]
688        VMLAL.S16 q10, d8, d2[3]
689        VMLAL.S16 q11, d9, d2[3]
690        VMLAL.S16 q12, d8, d4[3]
691        VMLAL.S16 q13, d9, d4[3]
692        VMLAL.S16 q14, d8, d6[3]
693        VMLAL.S16 q15, d9, d6[3]
694        BEQ     4b
695
696        VLD1.8  {d8},  [r9]!
697        $if DATATYPE == "QU8":
698          VSUBL.U8 q4, d8, d14
699        $else:
700          VMOVL.S8 q4, d8
701        VMLAL.S16 q8, d8, d1[0]
702        VMLAL.S16 q9, d9, d1[0]
703        VMLAL.S16 q10, d8, d3[0]
704        VMLAL.S16 q11, d9, d3[0]
705        VMLAL.S16 q12, d8, d5[0]
706        VMLAL.S16 q13, d9, d5[0]
707        VMLAL.S16 q14, d8, d7[0]
708        VMLAL.S16 q15, d9, d7[0]
709        CMP     r5, 6
710        BLO     4b
711
712        VLD1.8  {d8},  [r9]!
713        $if DATATYPE == "QU8":
714          VSUBL.U8 q4, d8, d14
715        $else:
716          VMOVL.S8 q4, d8
717        VMLAL.S16 q8, d8, d1[1]
718        VMLAL.S16 q9, d9, d1[1]
719        VMLAL.S16 q10, d8, d3[1]
720        VMLAL.S16 q11, d9, d3[1]
721        VMLAL.S16 q12, d8, d5[1]
722        VMLAL.S16 q13, d9, d5[1]
723        VMLAL.S16 q14, d8, d7[1]
724        VMLAL.S16 q15, d9, d7[1]
725        BEQ     4b
726
727        VLD1.8  {d8},  [r9]!
728        $if DATATYPE == "QU8":
729          VSUBL.U8 q4, d8, d14
730        $else:
731          VMOVL.S8 q4, d8
732        VMLAL.S16 q8, d8, d1[2]
733        VMLAL.S16 q9, d9, d1[2]
734        VMLAL.S16 q10, d8, d3[2]
735        VMLAL.S16 q11, d9, d3[2]
736        VMLAL.S16 q12, d8, d5[2]
737        VMLAL.S16 q13, d9, d5[2]
738        VMLAL.S16 q14, d8, d7[2]
739        VMLAL.S16 q15, d9, d7[2]
740        B       4b
741
742        # Store odd width
743        .p2align 3
7447:
745        TST     r1, 4
746        BEQ     8f
747        VST1.32 {d3[0]}, [r6]!
748        VST1.32 {d2[0]}, [r8]!
749        VST1.32 {d1[0]}, [r4]!
750        VST1.32 {d0[0]}, [r11]!
751        VEXT.8  q1, q1, q1, 4
752        VEXT.8  q0, q0, q0, 4
7538:
754        TST     r1, 2
755        BEQ     9f
756        VST1.16 {d3[0]}, [r6]!
757        VST1.16 {d2[0]}, [r8]!
758        VST1.16 {d1[0]}, [r4]!
759        VST1.16 {d0[0]}, [r11]!
760        VEXT.8  q1, q1, q1, 2
761        VEXT.8  q0, q0, q0, 2
762
7639:
764        TST     r1, 1
765        BEQ     10f
766        VST1.8  {d3[0]}, [r6]
767        VST1.8  {d2[0]}, [r8]
768        VST1.8  {d1[0]}, [r4]
769        VST1.8  {d0[0]}, [r11]
770
77110:
772        $if DATATYPE == "QU8":
773          VPOP    {d8-d14}
774          ADD     sp, sp, 12              // skip r1, r2, r3
775        $else:
776          VPOP    {d8-d13}
777          ADD     sp, sp, 20              // skip pad of 8, r1, r2, r3
778        POP     {r4, r5, r6, r7, r8, r9, r10, r11, pc}
779
780END_FUNCTION xnn_${DATATYPE.lower()}_igemm_minmax_${REQUANTIZATION.lower()}_ukernel_4x8__aarch32_${ISA}_mlal_lane${"_prfm" if PREFETCH else ""}_cortex_${CPU}
781
782#ifdef __ELF__
783.section ".note.GNU-stack","",%progbits
784#endif
785