在 ARM 上初始化 uint32x4_t 时出现错误 C2078?
Error C2078 when initializing uint32x4_t on ARM?
我正在使用 Visual Studio 2013 测试 ARM 版本。我在初始化 uint32x4_t
时遇到编译错误。错误是error C2078: too many initializers
.
const uint32x4_t CTRS[3] = {
{1,0,0,0}, {2,0,0,0}, {3,0,0,0}
};
结果是:
cl.exe /nologo /W4 /wd4231 /wd4511 /wd4156 /D_MBCS /Zi /TP /GR /EHsc /DNDEBUG /D_
NDEBUG /Oi /Oy /O2 /MT /FI sdkddkver.h /FI winapifamily.h /DWINAPI_FAMILY=WINAPI_
FAMILY_PHONE_APP /c chacha_simd.cpp
chacha_simd.cpp
chacha_simd.cpp(306) : error C2078: too many initializers
NMAKE : fatal error U1077: '"C:\Program Files (x86)\Microsoft Visual Studio 12.0
\VC\BIN\x86_ARM\cl.exe"' : return code '0x2'
Stop.
我从 MSDN 论坛上看到这是一个已知问题 "error C2078: too many initializers" when using ARM NEON。已确认,但未提供解决方法。
我也尝试过这种可怕的方式(借鉴 PowerPC 风格):
const uint32x4_t CTRS[3] = {
vld1q_u32({1,0,0,0}),
vld1q_u32({2,0,0,0}),
vld1q_u32({3,0,0,0})
};
结果是:
chacha_simd.cpp(309) : warning C4002: too many actual parameters for macro 'vld1
q_u32'
chacha_simd.cpp(309) : error C2143: syntax error : missing '}' before ')'
chacha_simd.cpp(309) : error C2664: 'const __n64 *__uint32ToN64_c(const uint32_t
*)' : cannot convert argument 1 from 'initializer-list' to 'const uint32_t *'
Reason: cannot convert from 'int' to 'const uint32_t *'
Conversion from integral type to pointer type requires reinterpret_cast,
C-style cast or function-style cast
chacha_simd.cpp(309) : error C2660: '__neon_Q1Adr' : function does not take 1 ar
guments
chacha_simd.cpp(310) : warning C4002: too many actual parameters for macro 'vld1
q_u32'
chacha_simd.cpp(310) : error C2143: syntax error : missing '}' before ')'
chacha_simd.cpp(310) : error C2664: 'const __n64 *__uint32ToN64_c(const uint32_t
*)' : cannot convert argument 1 from 'initializer-list' to 'const uint32_t *'
Reason: cannot convert from 'int' to 'const uint32_t *'
Conversion from integral type to pointer type requires reinterpret_cast,
C-style cast or function-style cast
chacha_simd.cpp(310) : error C2660: '__neon_Q1Adr' : function does not take 1 ar
guments
chacha_simd.cpp(310) : fatal error C1903: unable to recover from previous error(
s); stopping compilation
根据 arm_neon.h
on GitHub 的一些源代码,__neon_Q1Adr
和 vld1q_u32
是:
__n128 __neon_Q1Adr(unsigned int, const __n64*);
#define vld1q_u32(pcD) ( __neon_Q1Adr( 0xf4200a8f, __uint32ToN64_c(pcD)) )
事情越来越乱了。搜索 "arm initialize "uint32x4_t" site:microsoft.com" 和 "arm initialize "uint32x4_t" site:msdn.com" 返回 0 个匹配。
如何使用 Microsoft 编译器初始化 uint32x4_t
?
下面会做:
static const uint32_t array[] = {1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0};
const uint32x4_t CTRS[3] = {vld1q_u32(&array[0]), vld1q_u32(&array[4]), vld1q_u32(&array[8])};
Jake 的回答可移植编译,但是(就像 x86 内在函数一样),编译器很愚蠢,当您使用内在函数作为静态初始值设定项时,实际上会在 run-time 处复制数组。 (在函数内部,或者在 constructor-like 静态初始值设定项中一次。)编写索引底层标量数组的代码会更有效,例如 vld1q_u32(&array[idx*4])
您链接的 winddk-8.1 header,arm_neon.h
,非常清楚地显示了 typedef __n128 uint32x4_t;
(与 128 位向量的其他元素宽度相同),并且底层 __n128
类型被定义为首先与 __int64[2]
成员的联合。
typedef union __declspec(intrin_type) _ADVSIMD_ALIGN(8) __n128
{
unsigned __int64 n128_u64[2];
unsigned __int32 n128_u32[4];
unsigned __int16 n128_u16[8];
unsigned __int8 n128_u8[16];
__int64 n128_i64[2];
__int32 n128_i32[4];
__int16 n128_i16[8];
__int8 n128_i8[16];
float n128_f32[4];
struct
{
__n64 low64;
__n64 high64;
} DUMMYNEONSTRUCT;
} __n128;
如果您想编写 MSVC-only 依赖于 header 内部的代码,您可以简单地将成对的 32 位整数组合成 64 位整数。 对于 little-endian ARM,这意味着使第二个 32 位元素成为组合 64 位元素的 high 32 位。
#ifdef _MSC_VER
// MSVC only; will silently compile differently on others
static const uint32x4_t CTRS[3] = {
// The .n128_u64 field is first in the definition of uint32x4_t
{1 + (0ULL<<32), 0 + (0ULL<<32)}, // ARM is little-endian
{2 + (0ULL<<32), 0 + (0ULL<<32)},
{3 + (0ULL<<32), 0 + (0ULL<<32)},
};
我们可以用 CPP 宏将其包装起来,使其在编译器之间可移植
我为整个 uint32x4_t
创建了一个宏,而不是一对您也可以用于 64 位向量的宏。这使得实际的声明不再是大括号和宏名称的混乱,因为我们可以在这个宏中包含外部 {}
.
#ifdef _MSC_VER
// The .n128_u64 field is first. Combine pairs of 32-bit integers in little-endian order.
#define INITu32x4(w,x,y,z) { ((w) + (unsigned long long(x) << 32)), ((y) + (unsigned long long(z) << 32)) }
#else
#define INITu32x4(w,x,y,z) { (w), (x), (y), (z) }
#endif
static const uint32x4_t CTRS[3] = {
INITu32x4(1,0,0,0),
INITu32x4(2,0,0,0),
INITu32x4(3,0,0,0),
};
在 GCC 和 MSVC 上正确+高效地编译到 read-only 数据部分(.rodata
或 .rdata
)中的正确数据,没有运行时初始化.
From the Godbolt compiler explorer:
uint32x4_t access(int idx) {
return CTRS[idx];
}
@ g++5.4 -O3 -Wall -mcpu=cortex-a53 -mfpu=neon -mfloat-abi=hard -std=gnu++11
access(int):
movw r3, #:lower16:.LANCHOR0
movt r3, #:upper16:.LANCHOR0 @ gcc chooses to construct the address with movw/movt
@ instead of loading from a literal pool when optimizing for cortex-a53
add r0, r3, r0, lsl #4
vld1.64 {d0-d1}, [r0:64]
bx lr
.section .rodata
.align 3
.set .LANCHOR0,. + 0 @@ equivalent to .LANCHOR0: here.
@@ Reference point that could be used for other .rodata objects if needed.
.type CTRS, %object
.size CTRS, 48
CTRS:
.word 1
.word 0
.word 0
.word 0
.word 2
.word 0
...
和 MSVC -Ox:我不知道为什么 MSVC 的 DCQ
指令仍然需要 2 个参数来构造一个 64 位值,如果你制作一个 [=27= 的数组,则与 DCD 完全相同].这似乎与 Keil 的 DCQ directive / pseudo-instruction 不同,其中每个 comma-separated arg 是 一个 64 位整数。
但是 AFAICT,MSVC 添加的注释是每行数字的准确表示。
;; ARM msvc19.14 -O2
.rdata
|__n128 const * const CTRS| DCQ 0x1, 0x0 ; = 0x0000000000000001 ; CTRS
DCQ 0x0, 0x0 ; = 0x0000000000000000
DCQ 0x2, 0x0 ; = 0x0000000000000002
DCQ 0x0, 0x0 ; = 0x0000000000000000
DCQ 0x3, 0x0 ; = 0x0000000000000003
DCQ 0x0, 0x0 ; = 0x0000000000000000
EXPORT |__n128 access(int)| ; access
.text$mn SEGMENT
|__n128 access(int)| PROC ; access
movw r3,|__n128 const * const CTRS|
movt r3,|__n128 const * const CTRS|
add r3,r3,r0,lsl #4
vldm r3,{d0,d1}
|$M4|
bx lr
ENDP ; |__n128 access(int)|, access
在 C(但不是 C++)中,MSVC 允许 designated-initializer 语法
static const uint32x4_t CTRS[3] = { [0].n128_u32 = {1, 0, 0, 0}, [1].n128_u32 = {2, 0, 0, 0}, [2].n128_u32 = {3, 0, 0, 0} };
uint32x4_t access(int idx) {
return CTRS[idx];
}
这在 MSVC 的 C 模式下编译得很好,但不是 C++。您可以将其用于 INITu32x4
的稍微 future-proof 定义,如果出现问题,它会大声失败,如果 MS 决定重新排序联合定义,则不会中断。
Godbolt有C语言模式。我通常从不使用它(只对 g++/clang++ 使用 -xc
),因为在两者之间切换很不方便,但我不知道 command-line 选项可以使 MSVC 编译为 C。无论如何, this on Godbolt.
我正在使用 Visual Studio 2013 测试 ARM 版本。我在初始化 uint32x4_t
时遇到编译错误。错误是error C2078: too many initializers
.
const uint32x4_t CTRS[3] = {
{1,0,0,0}, {2,0,0,0}, {3,0,0,0}
};
结果是:
cl.exe /nologo /W4 /wd4231 /wd4511 /wd4156 /D_MBCS /Zi /TP /GR /EHsc /DNDEBUG /D_
NDEBUG /Oi /Oy /O2 /MT /FI sdkddkver.h /FI winapifamily.h /DWINAPI_FAMILY=WINAPI_
FAMILY_PHONE_APP /c chacha_simd.cpp
chacha_simd.cpp
chacha_simd.cpp(306) : error C2078: too many initializers
NMAKE : fatal error U1077: '"C:\Program Files (x86)\Microsoft Visual Studio 12.0
\VC\BIN\x86_ARM\cl.exe"' : return code '0x2'
Stop.
我从 MSDN 论坛上看到这是一个已知问题 "error C2078: too many initializers" when using ARM NEON。已确认,但未提供解决方法。
我也尝试过这种可怕的方式(借鉴 PowerPC 风格):
const uint32x4_t CTRS[3] = {
vld1q_u32({1,0,0,0}),
vld1q_u32({2,0,0,0}),
vld1q_u32({3,0,0,0})
};
结果是:
chacha_simd.cpp(309) : warning C4002: too many actual parameters for macro 'vld1
q_u32'
chacha_simd.cpp(309) : error C2143: syntax error : missing '}' before ')'
chacha_simd.cpp(309) : error C2664: 'const __n64 *__uint32ToN64_c(const uint32_t
*)' : cannot convert argument 1 from 'initializer-list' to 'const uint32_t *'
Reason: cannot convert from 'int' to 'const uint32_t *'
Conversion from integral type to pointer type requires reinterpret_cast,
C-style cast or function-style cast
chacha_simd.cpp(309) : error C2660: '__neon_Q1Adr' : function does not take 1 ar
guments
chacha_simd.cpp(310) : warning C4002: too many actual parameters for macro 'vld1
q_u32'
chacha_simd.cpp(310) : error C2143: syntax error : missing '}' before ')'
chacha_simd.cpp(310) : error C2664: 'const __n64 *__uint32ToN64_c(const uint32_t
*)' : cannot convert argument 1 from 'initializer-list' to 'const uint32_t *'
Reason: cannot convert from 'int' to 'const uint32_t *'
Conversion from integral type to pointer type requires reinterpret_cast,
C-style cast or function-style cast
chacha_simd.cpp(310) : error C2660: '__neon_Q1Adr' : function does not take 1 ar
guments
chacha_simd.cpp(310) : fatal error C1903: unable to recover from previous error(
s); stopping compilation
根据 arm_neon.h
on GitHub 的一些源代码,__neon_Q1Adr
和 vld1q_u32
是:
__n128 __neon_Q1Adr(unsigned int, const __n64*);
#define vld1q_u32(pcD) ( __neon_Q1Adr( 0xf4200a8f, __uint32ToN64_c(pcD)) )
事情越来越乱了。搜索 "arm initialize "uint32x4_t" site:microsoft.com" 和 "arm initialize "uint32x4_t" site:msdn.com" 返回 0 个匹配。
如何使用 Microsoft 编译器初始化 uint32x4_t
?
下面会做:
static const uint32_t array[] = {1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0};
const uint32x4_t CTRS[3] = {vld1q_u32(&array[0]), vld1q_u32(&array[4]), vld1q_u32(&array[8])};
Jake 的回答可移植编译,但是(就像 x86 内在函数一样),编译器很愚蠢,当您使用内在函数作为静态初始值设定项时,实际上会在 run-time 处复制数组。 (在函数内部,或者在 constructor-like 静态初始值设定项中一次。)编写索引底层标量数组的代码会更有效,例如 vld1q_u32(&array[idx*4])
您链接的 winddk-8.1 header,arm_neon.h
,非常清楚地显示了 typedef __n128 uint32x4_t;
(与 128 位向量的其他元素宽度相同),并且底层 __n128
类型被定义为首先与 __int64[2]
成员的联合。
typedef union __declspec(intrin_type) _ADVSIMD_ALIGN(8) __n128
{
unsigned __int64 n128_u64[2];
unsigned __int32 n128_u32[4];
unsigned __int16 n128_u16[8];
unsigned __int8 n128_u8[16];
__int64 n128_i64[2];
__int32 n128_i32[4];
__int16 n128_i16[8];
__int8 n128_i8[16];
float n128_f32[4];
struct
{
__n64 low64;
__n64 high64;
} DUMMYNEONSTRUCT;
} __n128;
如果您想编写 MSVC-only 依赖于 header 内部的代码,您可以简单地将成对的 32 位整数组合成 64 位整数。 对于 little-endian ARM,这意味着使第二个 32 位元素成为组合 64 位元素的 high 32 位。
#ifdef _MSC_VER
// MSVC only; will silently compile differently on others
static const uint32x4_t CTRS[3] = {
// The .n128_u64 field is first in the definition of uint32x4_t
{1 + (0ULL<<32), 0 + (0ULL<<32)}, // ARM is little-endian
{2 + (0ULL<<32), 0 + (0ULL<<32)},
{3 + (0ULL<<32), 0 + (0ULL<<32)},
};
我们可以用 CPP 宏将其包装起来,使其在编译器之间可移植
我为整个 uint32x4_t
创建了一个宏,而不是一对您也可以用于 64 位向量的宏。这使得实际的声明不再是大括号和宏名称的混乱,因为我们可以在这个宏中包含外部 {}
.
#ifdef _MSC_VER
// The .n128_u64 field is first. Combine pairs of 32-bit integers in little-endian order.
#define INITu32x4(w,x,y,z) { ((w) + (unsigned long long(x) << 32)), ((y) + (unsigned long long(z) << 32)) }
#else
#define INITu32x4(w,x,y,z) { (w), (x), (y), (z) }
#endif
static const uint32x4_t CTRS[3] = {
INITu32x4(1,0,0,0),
INITu32x4(2,0,0,0),
INITu32x4(3,0,0,0),
};
在 GCC 和 MSVC 上正确+高效地编译到 read-only 数据部分(.rodata
或 .rdata
)中的正确数据,没有运行时初始化.
From the Godbolt compiler explorer:
uint32x4_t access(int idx) {
return CTRS[idx];
}
@ g++5.4 -O3 -Wall -mcpu=cortex-a53 -mfpu=neon -mfloat-abi=hard -std=gnu++11
access(int):
movw r3, #:lower16:.LANCHOR0
movt r3, #:upper16:.LANCHOR0 @ gcc chooses to construct the address with movw/movt
@ instead of loading from a literal pool when optimizing for cortex-a53
add r0, r3, r0, lsl #4
vld1.64 {d0-d1}, [r0:64]
bx lr
.section .rodata
.align 3
.set .LANCHOR0,. + 0 @@ equivalent to .LANCHOR0: here.
@@ Reference point that could be used for other .rodata objects if needed.
.type CTRS, %object
.size CTRS, 48
CTRS:
.word 1
.word 0
.word 0
.word 0
.word 2
.word 0
...
和 MSVC -Ox:我不知道为什么 MSVC 的 DCQ
指令仍然需要 2 个参数来构造一个 64 位值,如果你制作一个 [=27= 的数组,则与 DCD 完全相同].这似乎与 Keil 的 DCQ directive / pseudo-instruction 不同,其中每个 comma-separated arg 是 一个 64 位整数。
但是 AFAICT,MSVC 添加的注释是每行数字的准确表示。
;; ARM msvc19.14 -O2
.rdata
|__n128 const * const CTRS| DCQ 0x1, 0x0 ; = 0x0000000000000001 ; CTRS
DCQ 0x0, 0x0 ; = 0x0000000000000000
DCQ 0x2, 0x0 ; = 0x0000000000000002
DCQ 0x0, 0x0 ; = 0x0000000000000000
DCQ 0x3, 0x0 ; = 0x0000000000000003
DCQ 0x0, 0x0 ; = 0x0000000000000000
EXPORT |__n128 access(int)| ; access
.text$mn SEGMENT
|__n128 access(int)| PROC ; access
movw r3,|__n128 const * const CTRS|
movt r3,|__n128 const * const CTRS|
add r3,r3,r0,lsl #4
vldm r3,{d0,d1}
|$M4|
bx lr
ENDP ; |__n128 access(int)|, access
在 C(但不是 C++)中,MSVC 允许 designated-initializer 语法
static const uint32x4_t CTRS[3] = { [0].n128_u32 = {1, 0, 0, 0}, [1].n128_u32 = {2, 0, 0, 0}, [2].n128_u32 = {3, 0, 0, 0} };
uint32x4_t access(int idx) {
return CTRS[idx];
}
这在 MSVC 的 C 模式下编译得很好,但不是 C++。您可以将其用于 INITu32x4
的稍微 future-proof 定义,如果出现问题,它会大声失败,如果 MS 决定重新排序联合定义,则不会中断。
Godbolt有C语言模式。我通常从不使用它(只对 g++/clang++ 使用 -xc
),因为在两者之间切换很不方便,但我不知道 command-line 选项可以使 MSVC 编译为 C。无论如何, this on Godbolt.