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