Ответ Джейка переносится компиляцией, но (как и для встроенных функций x86) компиляторы глупы и фактически копируют массив во время выполнения, когда вы используете встроенную функцию в качестве статического инициализатора.(Либо внутри функции, либо один раз в конструкторе, подобном статическому инициализатору.) Было бы более эффективно писать код, который индексирует базовый массив скаляров, например vld1q_u32(&array[idx*4])
Заголовок winddk-8.1Вы связали 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, который зависит от внутренних элементов заголовка, вы можете просто объединить пары 32-разрядных целых чисел в 64-разрядные целые числа. Дляlittle-endian ARM, это означает, что второй 32-битный элемент должен быть high 32-битный из комбинированного 64-битного элемента.
#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 для нужных данных в разделе данных только для чтения (.rodata
или .rdata
), без инициализации во время выполнения. Из проводника компилятора Godbolt :
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-битного значения, точно такого же, как DCD, если вы создаете массив int
.Кажется, это отличается от директивы / псевдоинструкции Кейла , где каждый разделенный запятыми аргумент равен 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 допускает синтаксис назначенного инициализатора
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];
}
.хорошо в режиме C MSVC, но не C ++.Вы можете использовать это для чуть более ориентированного на будущее определения INITu32x4
, которое с шумом дает сбой, если что-то не так, и не сломается, если MS решит изменить порядок определения объединения.
У Godbolt есть режим языка C,Я обычно никогда не использую его (и просто использую -xc
для g ++ / clang ++), потому что переключаться между ними неудобно, но я не знаю параметра командной строки, чтобы MSVC компилировался как C. В любом случае, thisна Годболте .