Встроенный ассемблер gcc arm% e0 и модификаторы% f0 для 16-байтовых операндов NEON?

gcc arm inline-assembly neon

283 просмотра

1 ответ

Найден следующий встроенный ассемблер codeдля вычисления векторного перекрестного произведения:

float32x4_t cross_test( const float32x4_t& lhs, const float32x4_t& rhs )
{
    float32x4_t result;

    asm volatile(
    "vext.8 d6, %e2, %f2, #4 \n\t"          
    "vext.8 d7, %e1, %f1, #4 \n\t"  
    "vmul.f32 %e0, %f1, %e2  \n\t" 
    "vmul.f32 %f0, %e1, d6   \n\t" 
    "vmls.f32 %e0, %f2, %e1  \n\t" 
    "vmls.f32 %f0, %e2, d7   \n\t" 
    "vext.8 %e0, %f0, %e0, #4    "      
    : "+w" ( result )                  
    : "w" ( lhs ), "w" ( rhs )            
    : "d6", "d7" );

    return result;
}

Что означают модификаторы eи fпосле '%'(например %e2)? Я не могу найти ссылку на это.

Это код ассемблера, сгенерированный gcc:

vext.8 d6, d20, d21, #4 
vext.8 d7, d18, d19, #4 
vmul.f32 d16, d19, d20  
vmul.f32 d17, d18, d6   
vmls.f32 d16, d21, d18  
vmls.f32 d17, d20, d7   
vext.8 d16, d17, d16, #4

Теперь я понял значение используемых модификаторов. Теперь я попытался следовать алгоритму кросс-произведения. Для этого я добавил несколько дополнительных комментариев к коду ассемблера, но результат не соответствует моим ожиданиям:

    // History:
    // - '%e'  = lower register part
    // - '%f'  = higher register part
    // - '%?0' = res = [ x2 y2 | z2 v2 ]
    // - '%?1' = lhs = [ x0 y0 | z0 v0 ]
    // - '%?2' = rhs = [ x1 y1 | z1 v1 ]
    // - '%e0'       = [ x2 y2 ]
    // - '%f0'       = [ z2 v2 ]
    // - '%e1'       = [ x0 y0 ]
    // - '%f1'       = [ z0 v0 ]
    // - '%e2'       = [ x1 y1 ]
    // - '%f2'       = [ z1 v1 ]
    // Implemented algorithm:
    // |x2|   |y0 * z1 - z0 * y1|
    // |y2| = |z0 * x1 - x0 * z1|
    // |z2|   |x0 * y1 - y0 * x1|
    asm (
    "vext.8 d6, %e2, %f2, #4 \n\t" // e2=[ x1 y1 ], f2=[ z1 v1 ] -> d6=[ v1 x1 ]
    "vext.8 d7, %e1, %f1, #4 \n\t" // e1=[ x0 y0 ], f1=[ z0 v0 ] -> d7=[ v0 x0 ]
    "vmul.f32 %e0, %f1, %e2  \n\t" // f1=[ z0 v0 ], e2=[ x1 y1 ] -> e0=[ z0 * x1, v0 * y1 ]
    "vmul.f32 %f0, %e1, d6   \n\t" // e1=[ x0 y0 ], d6=[ v1 x1 ] -> f0=[ x0 * v1, y0 * x1 ]
    "vmls.f32 %e0, %f2, %e1  \n\t" // f2=[ z1 v1 ], e1=[ x0 y0 ] -> e0=[ z0 * x1 - z1 * x0, v0 * y1 - v1 * y0 ] = [ y2, - ]
    "vmls.f32 %f0, %e2, d7   \n\t" // e2=[ x1 y1 ], d7=[ v0 x0 ] -> f0=[ x0 * v1 - x1 * v0, y0 * x1 - y1 * x0 ] = [  -, - ]
    "vext.8 %e0, %f0, %e0, #4    " // 
    : "+w" ( result )              // Output section: 'w'='VFP floating point register', '+'='read/write'
    : "w" ( lhs ), "w" ( rhs )     // Input section : 'w'='VFP floating point register'
    : "d6", "d7" );                // Temporary 64[bit] register.
Автор: Mark Источник Размещён: 31.07.2019 07:57

Ответы (1)


3 плюса

Решение

Во-первых, это странно. resultне инициализируется перед оператором asm, но используется как операнд ввода / вывода с "+w" ( result ). Я думаю "=w" (result), будет лучше. Это также не имеет смысла, что это volatile; выход является чистой функцией входных данных без каких-либо побочных эффектов или зависимости от каких-либо «скрытых» входных данных, поэтому одни и те же входные данные будут давать одинаковый результат каждый раз. Таким образом, опущение volatileпозволит компилятору выполнить CSE и вывести его из циклов, если это возможно, вместо того, чтобы заставлять его пересчитывать каждый раз, когда источник запускает его с одними и теми же входами.


Я не мог найти никакой ссылки либо; Страница расширенного ASM руководства gcc содержит только модификаторы операнда для x86 , а не ARM.

Но я думаю, что мы можем увидеть модификаторы операндов, посмотрев на вывод asm:

%e0замещен d16, %f0замещен d17. %e1есть d18и %f1есть d19. %2находится в d20иd21

Ваши входы представляют собой 16-байтовые векторы NEON в qрегистрах. В ARM32 верхняя и нижняя половина каждого qрегистра доступны отдельно как dрегистр. (В отличие от AArch64, где каждый регистр s / d является нижним элементом различных регистров q.) Похоже, что этот код использует это преимущество для свободного тасования, используя 64-битную SIMD на старшей и младшей паре floats, после делать 4-байтовое vextперемешивание, чтобы смешать эти пары поплавков.

%e[operand]нижний dрегистр операнда, %f[operand]верхний dрегистр. Они не задокументированы, но исходный код gcc говорит ( arm_print_operandв gcc/config/arm/arm.c#L22486:

Эти два кода печатают регистр нижнего / верхнего двойного слова регистра неоновых квадраторов соответственно. Для типов с парной структурой можно также печатать регистры с четырьмя словами низкого / высокого слов.

Я не проверял, что произойдет, если вы примените эти модификаторы к 64-битным операндам, как это float32x2_t, и это только я, обратный инжиниринг из одного примера. Но вполне логично, что для этого найдутся модификаторы.

Модификаторы x86 включают один для младших и старших 8 битов целочисленных регистров (так что вы можете получить AL / AH, если ваш вход как в EAX), так что вещи с частичным регистром - это определенно то, что могут делать встроенные модификаторы операнда asm GNU C.


Остерегайтесь, что недокументированные средства не поддерживаются .

Автор: Peter Cordes Размещён: 23.07.2018 03:19
Вопросы из категории :
32x32