Arm 内联汇编 volatile 导致 clang 与 asan 计算结果错误
Arm inline assembly volatile caused compute result error in clang with asan
我的问题是,当变量 nn 没有可变标识符时,我得到了错误的结果,但是当我向 nn 添加一个可变标识符时,我得到了正确的结果。当我添加挥发物时发生了什么?
我的错误代码如下:
int size = outw * outh;
(volatile)int nn = size >> 2;
int remain = size & 3;
float32x4_t _bias0 = bias ? vld1q_f32(&bias[p]) : vdupq_n_f32(0.0f);
int chanel_offset=in_channel_offset*4;
int kernel_offset=inch*4;
if (nn > 0)
{
// fprintf(stderr, "%s %d\n", __FILE__, __LINE__);
asm volatile(
"add r0, %[kernel0], %[kernel_offset] \n"
"vld1.f32 {d0-d3}, [%[kernel0]] \n"
"vld1.f32 {d4-d7}, [r0] \n"
"add r0, r0, %[kernel_offset] \n"
"vld1.f32 {d8-d11}, [r0] \n"
"add r0, r0, %[kernel_offset] \n"
"vld1.f32 {d12-d15}, [r0] \n"
"add r1, %[r0], %[channel_offset] \n"
"pld [%[r0], #128] \n"
"vld1.f32 {q13}, [%[r0] :128]! \n" // q13 = r0
"pld [r1, #128] \n"
"vld1.f32 {q14}, [r1 :128] \n" // q14 = r1
"pld [%[outptr0], #128] \n"
"vdup.32 q9, %e[_bias0][0] \n"
"pld [%[outptr1], #128] \n"
"vdup.32 q10, %e[_bias0][1] \n"
"0: \n"
"vmla.f32 q9, q13, d0[0] \n"
"vmla.f32 q10, q13, d4[0] \n"
"add r1, r1, %[channel_offset] \n"
"pld [%[outptr2], #128] \n"
"vdup.32 q11, %f[_bias0][0] \n"
"vmla.f32 q9, q14, d0[1] \n"
"vmla.f32 q10, q14, d4[1] \n"
"pld [%[outptr3], #128] \n"
"vdup.32 q12, %f[_bias0][1] \n"
"vmla.f32 q11, q13, d8[0] \n"
"vmla.f32 q11, q14, d8[1] \n"
"pld [r1, #128] \n"
"vld1.f32 {q15}, [r1 :128] \n" // q15 = r2
"vmla.f32 q12, q13, d12[0] \n"
"vmla.f32 q12, q14, d12[1] \n"
"add r1, r1, %[channel_offset] \n"
"vmla.f32 q9, q15, d1[0] \n"
"vmla.f32 q10, q15, d5[0] \n"
"pld [r1, #128] \n"
"vld1.f32 {q13}, [r1 :128] \n" // q13 = r3
"vmla.f32 q11, q15, d9[0] \n"
"vmla.f32 q12, q15, d13[0] \n"
"add r1, r1, %[channel_offset] \n"
"vmla.f32 q9, q13, d1[1] \n"
"vmla.f32 q10, q13, d5[1] \n"
"pld [r1, #128] \n"
"vld1.f32 {q14}, [r1 :128] \n" // q14 = r4
"vmla.f32 q11, q13, d9[1] \n"
"add r1, r1, %[channel_offset] \n"
"vmla.f32 q12, q13, d13[1] \n"
"pld [r1, #128] \n"
"vld1.f32 {q15}, [r1 :128] \n" // q15 = r5
"vmla.f32 q9, q14, d2[0] \n"
"vmla.f32 q10, q14, d6[0] \n"
"add r1, r1, %[channel_offset] \n"
"pld [%[r0], #128] \n"
"vld1.f32 {q13}, [%[r0] :128] \n" // q13 = r0
"vmla.f32 q11, q14, d10[0] \n"
"vmla.f32 q12, q14, d14[0] \n"
"pld [r1, #128] \n"
"vld1.f32 {q14}, [r1 :128] \n" // q14 = r6
"vmla.f32 q9, q15, d2[1] \n"
"vmla.f32 q10, q15, d6[1] \n"
"add r1, r1, %[channel_offset] \n"
"vmla.f32 q11, q15, d10[1] \n"
"vmla.f32 q12, q15, d14[1] \n"
"pld [r1, #128] \n"
"vld1.f32 {q15}, [r1 :128] \n" // q15 = r7
"vmla.f32 q9, q14, d3[0] \n"
"vmla.f32 q10, q14, d7[0] \n"
"add r1, %[r0], %[channel_offset]\n"
"vmla.f32 q11, q14, d11[0] \n"
"vmla.f32 q12, q14, d15[0] \n"
"add %[r0], %[r0], #16 \n"
"vmla.f32 q9, q15, d3[1] \n"
"vmla.f32 q10, q15, d7[1] \n"
"pld [r1, #128] \n"
"vld1.f32 {q14}, [r1 :128] \n" // q14 = r1
"vst1.f32 {q9}, [%[outptr0] :128]! \n"
"vst1.f32 {q10}, [%[outptr1] :128]! \n"
"vmla.f32 q11, q15, d11[1] \n"
"vmla.f32 q12, q15, d15[1] \n"
"subs %[nn], #1 \n"
"pld [%[outptr0], #128] \n"
//"vld1.f32 {q9}, [%0 :128] \n" // q9 = outptr0
"vdup.32 q9, %e[_bias0][0] \n"
"pld [%[outptr1], #128] \n"
//"vld1.f32 {q10}, [%1 :128] \n" // q10 = outptr1
"vdup.32 q10, %e[_bias0][1] \n"
"vst1.f32 {q11}, [%[outptr2] :128]! \n"
"vst1.f32 {q12}, [%[outptr3] :128]! \n"
"bne 0b \n"
"sub %[r0], #16 \n"
: [outptr0] "+r"(outptr0),
[outptr1] "+r"(outptr1),
[outptr2] "+r"(outptr2),
[outptr3] "+r"(outptr3),
[r0] "+r"(r0),
[channel_offset] "+r"(chanel_offset)
: [nn] "r"(nn),
[kernel0] "r" (kernel0),
[kernel_offset] "r" (kernel_offset),
[_bias0] "w" (_bias0)
: "cc", "memory", "r0", "r1", "q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7",
"q9", "q10", "q11", "q12", "q13", "q14", "q15");
}
上面的代码使用输入内核、r0 和偏置(内核 * r0 + 偏置)计算结果 outprt0、outptr1、outptr2、outptr3。
您正在修改一个输入操作数,对编译器说谎。
volatile int nn
让编译器每次都从内存中重新加载它,而不是在你的 asm
语句之后使用应该仍在寄存器中的值,该语句向编译器承诺 [nn] "r" (nn)
是一个输入(只读)操作数。
但是你的代码有 "subs %[nn], #1
.
对nn
的(副本)使用"+r"
约束。
int nn_tmp = nn; // the asm destroys this copy
asm ("..." : [nn] "+r"(nn_tmp) : ...);
或者使用带有匹配约束的虚拟输出,比如
int dummy;
asm ("..." : "=r"(dummy) : [nn] "0" (nn) : ...);
其中 "0"
是虚拟输出的操作数。
你 可以 save/restore %[nn]
以某种方式在 asm 内部,但这几乎总是比让编译器重新实现更糟糕 nn
if/when需要。
您通常不需要 asm volatile
,除非您采用指针输入并使用 "memory"
破坏器,因此某些输出不在 "=m"
的约束中或 [dummy_in_out] "+m"( *(float (*)[]) my_array)
告诉它通过该指针访问的整个数组或任意大小是 RMW 操作数。
在您的情况下,内存破坏可能是最简单的,并且编译器围绕此语句展开或使用相同输入多次运行 CSE 的范围可能很小。或者,如果所有输出都未使用,则将其优化掉。
我的问题是,当变量 nn 没有可变标识符时,我得到了错误的结果,但是当我向 nn 添加一个可变标识符时,我得到了正确的结果。当我添加挥发物时发生了什么? 我的错误代码如下:
int size = outw * outh;
(volatile)int nn = size >> 2;
int remain = size & 3;
float32x4_t _bias0 = bias ? vld1q_f32(&bias[p]) : vdupq_n_f32(0.0f);
int chanel_offset=in_channel_offset*4;
int kernel_offset=inch*4;
if (nn > 0)
{
// fprintf(stderr, "%s %d\n", __FILE__, __LINE__);
asm volatile(
"add r0, %[kernel0], %[kernel_offset] \n"
"vld1.f32 {d0-d3}, [%[kernel0]] \n"
"vld1.f32 {d4-d7}, [r0] \n"
"add r0, r0, %[kernel_offset] \n"
"vld1.f32 {d8-d11}, [r0] \n"
"add r0, r0, %[kernel_offset] \n"
"vld1.f32 {d12-d15}, [r0] \n"
"add r1, %[r0], %[channel_offset] \n"
"pld [%[r0], #128] \n"
"vld1.f32 {q13}, [%[r0] :128]! \n" // q13 = r0
"pld [r1, #128] \n"
"vld1.f32 {q14}, [r1 :128] \n" // q14 = r1
"pld [%[outptr0], #128] \n"
"vdup.32 q9, %e[_bias0][0] \n"
"pld [%[outptr1], #128] \n"
"vdup.32 q10, %e[_bias0][1] \n"
"0: \n"
"vmla.f32 q9, q13, d0[0] \n"
"vmla.f32 q10, q13, d4[0] \n"
"add r1, r1, %[channel_offset] \n"
"pld [%[outptr2], #128] \n"
"vdup.32 q11, %f[_bias0][0] \n"
"vmla.f32 q9, q14, d0[1] \n"
"vmla.f32 q10, q14, d4[1] \n"
"pld [%[outptr3], #128] \n"
"vdup.32 q12, %f[_bias0][1] \n"
"vmla.f32 q11, q13, d8[0] \n"
"vmla.f32 q11, q14, d8[1] \n"
"pld [r1, #128] \n"
"vld1.f32 {q15}, [r1 :128] \n" // q15 = r2
"vmla.f32 q12, q13, d12[0] \n"
"vmla.f32 q12, q14, d12[1] \n"
"add r1, r1, %[channel_offset] \n"
"vmla.f32 q9, q15, d1[0] \n"
"vmla.f32 q10, q15, d5[0] \n"
"pld [r1, #128] \n"
"vld1.f32 {q13}, [r1 :128] \n" // q13 = r3
"vmla.f32 q11, q15, d9[0] \n"
"vmla.f32 q12, q15, d13[0] \n"
"add r1, r1, %[channel_offset] \n"
"vmla.f32 q9, q13, d1[1] \n"
"vmla.f32 q10, q13, d5[1] \n"
"pld [r1, #128] \n"
"vld1.f32 {q14}, [r1 :128] \n" // q14 = r4
"vmla.f32 q11, q13, d9[1] \n"
"add r1, r1, %[channel_offset] \n"
"vmla.f32 q12, q13, d13[1] \n"
"pld [r1, #128] \n"
"vld1.f32 {q15}, [r1 :128] \n" // q15 = r5
"vmla.f32 q9, q14, d2[0] \n"
"vmla.f32 q10, q14, d6[0] \n"
"add r1, r1, %[channel_offset] \n"
"pld [%[r0], #128] \n"
"vld1.f32 {q13}, [%[r0] :128] \n" // q13 = r0
"vmla.f32 q11, q14, d10[0] \n"
"vmla.f32 q12, q14, d14[0] \n"
"pld [r1, #128] \n"
"vld1.f32 {q14}, [r1 :128] \n" // q14 = r6
"vmla.f32 q9, q15, d2[1] \n"
"vmla.f32 q10, q15, d6[1] \n"
"add r1, r1, %[channel_offset] \n"
"vmla.f32 q11, q15, d10[1] \n"
"vmla.f32 q12, q15, d14[1] \n"
"pld [r1, #128] \n"
"vld1.f32 {q15}, [r1 :128] \n" // q15 = r7
"vmla.f32 q9, q14, d3[0] \n"
"vmla.f32 q10, q14, d7[0] \n"
"add r1, %[r0], %[channel_offset]\n"
"vmla.f32 q11, q14, d11[0] \n"
"vmla.f32 q12, q14, d15[0] \n"
"add %[r0], %[r0], #16 \n"
"vmla.f32 q9, q15, d3[1] \n"
"vmla.f32 q10, q15, d7[1] \n"
"pld [r1, #128] \n"
"vld1.f32 {q14}, [r1 :128] \n" // q14 = r1
"vst1.f32 {q9}, [%[outptr0] :128]! \n"
"vst1.f32 {q10}, [%[outptr1] :128]! \n"
"vmla.f32 q11, q15, d11[1] \n"
"vmla.f32 q12, q15, d15[1] \n"
"subs %[nn], #1 \n"
"pld [%[outptr0], #128] \n"
//"vld1.f32 {q9}, [%0 :128] \n" // q9 = outptr0
"vdup.32 q9, %e[_bias0][0] \n"
"pld [%[outptr1], #128] \n"
//"vld1.f32 {q10}, [%1 :128] \n" // q10 = outptr1
"vdup.32 q10, %e[_bias0][1] \n"
"vst1.f32 {q11}, [%[outptr2] :128]! \n"
"vst1.f32 {q12}, [%[outptr3] :128]! \n"
"bne 0b \n"
"sub %[r0], #16 \n"
: [outptr0] "+r"(outptr0),
[outptr1] "+r"(outptr1),
[outptr2] "+r"(outptr2),
[outptr3] "+r"(outptr3),
[r0] "+r"(r0),
[channel_offset] "+r"(chanel_offset)
: [nn] "r"(nn),
[kernel0] "r" (kernel0),
[kernel_offset] "r" (kernel_offset),
[_bias0] "w" (_bias0)
: "cc", "memory", "r0", "r1", "q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7",
"q9", "q10", "q11", "q12", "q13", "q14", "q15");
}
上面的代码使用输入内核、r0 和偏置(内核 * r0 + 偏置)计算结果 outprt0、outptr1、outptr2、outptr3。
您正在修改一个输入操作数,对编译器说谎。
volatile int nn
让编译器每次都从内存中重新加载它,而不是在你的 asm
语句之后使用应该仍在寄存器中的值,该语句向编译器承诺 [nn] "r" (nn)
是一个输入(只读)操作数。
但是你的代码有 "subs %[nn], #1
.
对nn
的(副本)使用"+r"
约束。
int nn_tmp = nn; // the asm destroys this copy
asm ("..." : [nn] "+r"(nn_tmp) : ...);
或者使用带有匹配约束的虚拟输出,比如
int dummy;
asm ("..." : "=r"(dummy) : [nn] "0" (nn) : ...);
其中 "0"
是虚拟输出的操作数。
你 可以 save/restore %[nn]
以某种方式在 asm 内部,但这几乎总是比让编译器重新实现更糟糕 nn
if/when需要。
您通常不需要 asm volatile
,除非您采用指针输入并使用 "memory"
破坏器,因此某些输出不在 "=m"
的约束中或 [dummy_in_out] "+m"( *(float (*)[]) my_array)
告诉它通过该指针访问的整个数组或任意大小是 RMW 操作数。
在您的情况下,内存破坏可能是最简单的,并且编译器围绕此语句展开或使用相同输入多次运行 CSE 的范围可能很小。或者,如果所有输出都未使用,则将其优化掉。