Вы ориентируетесь на x86-64 с AVX2 для 256-битных векторов? Я подумал, что это интересный случай для ответа.
Если это так, вы можете сделать это в нескольких инструкциях, используя насыщающее вычитание и переменное число смещений.
x86 SIMD сдвиги как vpsrlvq
насыщают счет сдвига , сдвигая все биты, когда счет>> = ширина элемента. В отличие от целочисленных сдвигов счетчик сдвигов маскируется (и, следовательно, оборачивается).
Для самого низкого элемента u64
, начиная со всех единиц, нам нужно оставить его неизменным для bitpos
> = 64. Или для меньших битовых позиций, сдвинуть его вправо на 64-bitpos
, Беззнаковое вычитающее насыщение выглядит как способ, как вы заметили, создать отсчет сдвига 0 для больших битовых постов. Но x86 имеет только SIMD-насыщающее вычитание, и только для байтов или элементов слова. Но если мы не заботимся о битовых позициях> 256, это нормально, мы можем использовать 16-битные элементы внизу каждого u64, и позволить 0-0
произойти в остальной части u64
.
Ваш код выглядит довольно сложным, создавая (1<<n) - 1
и XORing. Я думаю, что намного проще просто использовать переменное число для элементов 0xFFFF...FF
напрямую.
Я не знаю Зига, поэтому делай все, что тебе нужно, чтобы заставить его излучать асм вот так. Надеюсь, это полезно, потому что вы пометили эту сборку ; должно быть легко перевести на встроенные для C или Zig, если они есть.
default rel
section .rodata
shift_offsets: dw 64, 128, 192, 256 ; 16-bit elements, to be loaded with zero-extension to 64
section .text
pos_to_mask256:
vpmovzxwq ymm2, [shift_offsets] ; _mm256_set1_epi64x(256, 192, 128, 64)
vpcmpeqd ymm1, ymm1,ymm1 ; ymm1 = all-ones
; set up vector constants, can be hoisted
vmovd xmm0, edi
vpbroadcastq ymm0, xmm0 ; ymm0 = _mm256_set1_epi64(bitpos)
vpsubusw ymm0, ymm2, ymm0 ; ymm0 = {256,192,128,64}-bitpos with unsigned saturation
vpsrlvq ymm0, ymm1, ymm0 ; mask[i] >>= count, where counts >= 64 create 0s.
ret
Если входное целое число начинается в памяти, вы, конечно, можете эффективно транслировать его прямо в регистр ymm.
Вектор смещений сдвига, конечно, можно вывести из цикла, как и все единицы.
При входе = 77 старшие 2 элемента обнуляются сдвигами 256-77 = 179 и 192-77 = 115 бит. Протестировано с NASM + GDB для EDI = 77, и результат равен
(gdb) p /x $ymm0.v4_int64
{0xffffffffffffffff, 0x1fff, 0x0, 0x0}
GDB сначала печатает нижний элемент, в отличие от нотации / диаграмм Intel. Этот вектор на самом деле равен 0, 0, 0x1fff, 0xffffffffffffffff
, то есть 64 + 13 = 77 единиц, а все остальные нули. Другие тестовые случаи
edi=0
: маска = все ноль
edi=1
: маска = 1
- ...: mask =
edi
один бит внизу, затем нули
edi=255
: mask = все единицы, кроме верхнего бита верхнего элемента
edi=256
: маска = все единицы
edi>256
: маска = все единицы. (вычитание без знака насыщается до 0 везде.)
Вам необходим AVX2 для смены с переменным счетом. psubusb/w
- это SSE2 , поэтому вы можете рассмотреть возможность выполнения этой части с SIMD, а затем вернуться к скалярному целому числу для смен или просто использовать сдвиги SSE2 для одного элемента за раз. Как и psrlq xmm1, xmm0
, который принимает младшие 64 бита xmm0
в качестве счетчика сдвига для всех элементов xmm1.
Большинство МСА не имеют насыщающее скалярное вычитание . Я думаю, что некоторые процессоры ARM используют скалярное целое число, а x86 - нет. IDK, что вы используете.
На x86 (и многих других ISA) у вас есть 2 проблемы:
- оставить все единицы для младших элементов (либо изменить результат сдвига, либо перенастроить счетчик сдвига до 0)
- выдает
0
для старших элементов над элементом, содержащим верхний бит маски. Скалярные сдвиги x86 вообще не могут этого сделать, поэтому для этого случая вы можете ввести сдвиг 0
. Возможно, используя cmov
, чтобы создать его на основе флагов, установленных sub
для 192-w
или чего-то еще.
count = 192-w;
shift_input = count<0 ? 0 : ~0ULL;
shift_input >>= count & 63; // mask to avoid UB in C. Optimizes away on x86 where shr does this anyway.
Хм, это не справится с насыщением вычитания до 0, чтобы сохранить все.
Если вы настраиваете для ISA, отличных от x86, возможно, посмотрите на некоторые другие варианты. Или, может быть, есть что-то лучше и на x86. Создание единичных или всех нулей с sar reg,63
- интересный вариант (широковещательный бит), но нам действительно нужны единичные единицы, когда 192-count
имеет знаковый бит = 0.