silk.simd.x86.
x86-64 SIMD instructions that the portable silk.simd.simd operations do not express: byte table
lookups, multiply-add steps, saturating packs, sums of absolute differences, AES rounds and
carry-less multiplication.
When to use#
Use this module in kernels such as UTF-8 validation, base64 and hex decoding, where one of
these instructions replaces several portable steps. Use silk.simd.simd for everything else.
Details#
Each function names the instruction it stands for and the CPU feature that instruction
needs. When the build targets x86-64 with that feature, the function compiles to the one
instruction. Otherwise it computes the same lanes with portable code, which is correct on
every target but slower, so a kernel that depends on speed checks
Target.hasCpuFeature with static if before choosing this path. x86-64-v2 enables
SSSE3 and SSE4.1, and x86-64-v3 adds AVX2. The AES rounds need aes and carry-less
multiplication needs pclmul; no microarchitecture level includes them.
Examples#
Look up sixteen nibbles at once#
import silk.simd.x86
import silk.simd.u8x16
pub fn main() -> i32 {
let digits = u8x16.fromArray([48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 97, 98, 99, 100, 101, 102])
let nibbles = u8x16.fromArray([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])
let hex = x86.shuffleBytes(digits, nibbles)
if u8x16.extract(hex, 10) != 97 {
return 1
}
return 0
}Import as x86 with import silk.simd.x86 as x86.
Public declarations: 22.
shuffleBytes#
pub fn shuffleBytes(table: simd<u8, 16>, indices: simd<u8, 16>) -> simd<u8, 16>pshufb (SSSE3): lane i is table[indices[i] & 15], or 0 when bit 7 of indices[i] is
set.
When the build targets x86-64 with the ssse3 feature, it compiles to this one instruction.
Otherwise it computes the same lanes with portable code, which is correct but slower.
Check Target.hasCpuFeature with static if before you select this path for speed.
shuffleBytes256#
pub fn shuffleBytes256(table: simd<u8, 32>, indices: simd<u8, 32>) -> simd<u8, 32>vpshufb (AVX2): in each 128-bit half, lane i is table[indices[i] & 15] from the same
half, or 0 when bit 7 of indices[i] is set. An index selects only within its own half.
When the build targets x86-64 with the avx2 feature, it compiles to this one instruction.
Otherwise it computes the same lanes with portable code, which is correct but slower.
Check Target.hasCpuFeature with static if before you select this path for speed.
multiplyAddBytes#
pub fn multiplyAddBytes(left: simd<u8, 16>, right: simd<i8, 16>) -> simd<i16, 8>pmaddubsw (SSSE3): each unsigned byte of left times the signed byte of right, with each
adjacent pair of products summed into one i16 lane with signed saturation.
When the build targets x86-64 with the ssse3 feature, it compiles to this one instruction.
Otherwise it computes the same lanes with portable code, which is correct but slower.
Check Target.hasCpuFeature with static if before you select this path for speed.
multiplyAddBytes256#
pub fn multiplyAddBytes256(left: simd<u8, 32>, right: simd<i8, 32>) -> simd<i16, 16>vpmaddubsw (AVX2): each unsigned byte of left times the signed byte of right, over 32
bytes, with each adjacent pair of products summed into one i16 lane with signed saturation.
When the build targets x86-64 with the avx2 feature, it compiles to this one instruction.
Otherwise it computes the same lanes with portable code, which is correct but slower.
Check Target.hasCpuFeature with static if before you select this path for speed.
multiplyAddWords#
pub fn multiplyAddWords(left: simd<i16, 8>, right: simd<i16, 8>) -> simd<i32, 4>pmaddwd (SSE2): each i16 lane of left times that of right, with each adjacent pair of
products summed into one i32 lane. The sum wraps only for four lanes of -32768.
When the build targets x86-64 with the sse2 feature, it compiles to this one instruction.
Otherwise it computes the same lanes with portable code, which is correct but slower.
Check Target.hasCpuFeature with static if before you select this path for speed.
multiplyAddWords256#
pub fn multiplyAddWords256(left: simd<i16, 16>, right: simd<i16, 16>) -> simd<i32, 8>vpmaddwd (AVX2): each of the sixteen i16 lanes of left times that of right, with each
adjacent pair of products summed into one i32 lane. The sum wraps only for four lanes of
-32768.
When the build targets x86-64 with the avx2 feature, it compiles to this one instruction.
Otherwise it computes the same lanes with portable code, which is correct but slower.
Check Target.hasCpuFeature with static if before you select this path for speed.
packUnsigned16#
pub fn packUnsigned16(left: simd<i16, 8>, right: simd<i16, 8>) -> simd<u8, 16>packuswb (SSE2): the lanes of left, then of right, each clamped to 0..=255.
When the build targets x86-64 with the sse2 feature, it compiles to this one instruction.
Otherwise it computes the same lanes with portable code, which is correct but slower.
Check Target.hasCpuFeature with static if before you select this path for speed.
packUnsigned16x256#
pub fn packUnsigned16x256(left: simd<i16, 16>, right: simd<i16, 16>) -> simd<u8, 32>vpackuswb (AVX2): each lane clamped to 0..=255, packed on each 128-bit half, so the result
is the low half of left, the low half of right, the high half of left, then the high half
of right.
When the build targets x86-64 with the avx2 feature, it compiles to this one instruction.
Otherwise it computes the same lanes with portable code, which is correct but slower.
Check Target.hasCpuFeature with static if before you select this path for speed.
packUnsigned32#
pub fn packUnsigned32(left: simd<i32, 4>, right: simd<i32, 4>) -> simd<u16, 8>packusdw (SSE4.1): the lanes of left, then of right, each clamped to 0..=65535.
When the build targets x86-64 with the sse4.1 feature, it compiles to this one instruction.
Otherwise it computes the same lanes with portable code, which is correct but slower.
Check Target.hasCpuFeature with static if before you select this path for speed.
packSigned16#
pub fn packSigned16(left: simd<i16, 8>, right: simd<i16, 8>) -> simd<i8, 16>packsswb (SSE2): the lanes of left, then of right, each clamped to the i8 range.
When the build targets x86-64 with the sse2 feature, it compiles to this one instruction.
Otherwise it computes the same lanes with portable code, which is correct but slower.
Check Target.hasCpuFeature with static if before you select this path for speed.
packSigned32#
pub fn packSigned32(left: simd<i32, 4>, right: simd<i32, 4>) -> simd<i16, 8>packssdw (SSE2): the lanes of left, then of right, each clamped to the i16 range.
When the build targets x86-64 with the sse2 feature, it compiles to this one instruction.
Otherwise it computes the same lanes with portable code, which is correct but slower.
Check Target.hasCpuFeature with static if before you select this path for speed.
sumAbsoluteDifferences#
pub fn sumAbsoluteDifferences(left: simd<u8, 16>, right: simd<u8, 16>) -> simd<u64, 2>psadbw (SSE2): for each group of eight bytes, the sum of the absolute differences of the
lanes of left and right.
When the build targets x86-64 with the sse2 feature, it compiles to this one instruction.
Otherwise it computes the same lanes with portable code, which is correct but slower.
Check Target.hasCpuFeature with static if before you select this path for speed.
sumAbsoluteDifferences256#
pub fn sumAbsoluteDifferences256(left: simd<u8, 32>, right: simd<u8, 32>) -> simd<u64, 4>vpsadbw (AVX2): for each group of eight bytes of 32, the sum of the absolute differences of
the lanes of left and right.
When the build targets x86-64 with the avx2 feature, it compiles to this one instruction.
Otherwise it computes the same lanes with portable code, which is correct but slower.
Check Target.hasCpuFeature with static if before you select this path for speed.
aesEncrypt#
pub fn aesEncrypt(state: simd<u8, 16>, key: simd<u8, 16>) -> simd<u8, 16>aesenc (AES): one AES encryption round of state with the round key key: ShiftRows,
SubBytes and MixColumns, then key added.
When the build targets x86-64 with the aes feature, it compiles to this one instruction.
Otherwise it computes the same lanes with portable code, which is correct but slower.
Check Target.hasCpuFeature with static if before you select this path for speed.
aesEncryptLast#
pub fn aesEncryptLast(state: simd<u8, 16>, key: simd<u8, 16>) -> simd<u8, 16>aesenclast (AES): the last AES encryption round of state with the round key key: ShiftRows
and SubBytes, then key added. It skips MixColumns.
When the build targets x86-64 with the aes feature, it compiles to this one instruction.
Otherwise it computes the same lanes with portable code, which is correct but slower.
Check Target.hasCpuFeature with static if before you select this path for speed.
aesDecrypt#
pub fn aesDecrypt(state: simd<u8, 16>, key: simd<u8, 16>) -> simd<u8, 16>aesdec (AES): one round of the AES equivalent inverse cipher: InvShiftRows, InvSubBytes
and InvMixColumns, then key added. Its round keys are the encryption keys passed through
aesInverseMixColumns.
When the build targets x86-64 with the aes feature, it compiles to this one instruction.
Otherwise it computes the same lanes with portable code, which is correct but slower.
Check Target.hasCpuFeature with static if before you select this path for speed.
aesDecryptLast#
pub fn aesDecryptLast(state: simd<u8, 16>, key: simd<u8, 16>) -> simd<u8, 16>aesdeclast (AES): the last AES decryption round of state with the round key key:
InvShiftRows and InvSubBytes, then key added. It skips InvMixColumns.
When the build targets x86-64 with the aes feature, it compiles to this one instruction.
Otherwise it computes the same lanes with portable code, which is correct but slower.
Check Target.hasCpuFeature with static if before you select this path for speed.
aesInverseMixColumns#
pub fn aesInverseMixColumns(key: simd<u8, 16>) -> simd<u8, 16>aesimc (AES): InvMixColumns of one round key, for the decryption key schedule.
When the build targets x86-64 with the aes feature, it compiles to this one instruction.
Otherwise it computes the same lanes with portable code, which is correct but slower.
Check Target.hasCpuFeature with static if before you select this path for speed.
carrylessMultiply#
pub fn carrylessMultiply(left: simd<u64, 2>, right: simd<u64, 2>) -> simd<u64, 2>pclmulqdq with selector 0 (PCLMUL): the 128-bit carry-less product of lane 0 of left
and lane 0 of right, with its low 64 bits in lane 0. Lane 1 of each operand is ignored.
When the build targets x86-64 with the pclmul feature, it compiles to this one instruction.
Otherwise it computes the same lanes with portable code, which is correct but slower.
Check Target.hasCpuFeature with static if before you select this path for speed.
sha256Rounds2#
pub fn sha256Rounds2(cdgh: simd<u32, 4>, abef: simd<u32, 4>, wk: simd<u32, 4>) -> simd<u32, 4>sha256rnds2 (SHA): two SHA-256 rounds. cdgh holds C, D, G and H in lanes 3, 2, 1 and
0, abef holds A, B, E and F the same way, and lanes 0 and 1 of wk hold the next two
message words plus their round constants. Returns the new A, B, E and F; the new C, D, G
and H are the old abef.
When the build targets x86-64 with the sha feature, it compiles to this one instruction.
Otherwise it computes the same lanes with portable code, which is correct but slower.
Check Target.hasCpuFeature with static if before you select this path for speed.
sha256Message1#
pub fn sha256Message1(left: simd<u32, 4>, right: simd<u32, 4>) -> simd<u32, 4>sha256msg1 (SHA): the first step of the next four SHA-256 message words. With W[0..3]
in left and W[4] in lane 0 of right, lane i is W[i] + sigma0(W[i + 1]).
When the build targets x86-64 with the sha feature, it compiles to this one instruction.
Otherwise it computes the same lanes with portable code, which is correct but slower.
Check Target.hasCpuFeature with static if before you select this path for speed.
sha256Message2#
pub fn sha256Message2(left: simd<u32, 4>, right: simd<u32, 4>) -> simd<u32, 4>sha256msg2 (SHA): the last step of the next four SHA-256 message words W[16..19].
left holds the partial sums, and lanes 2 and 3 of right hold W[14] and W[15];
lane i is left[i] + sigma1(W[i + 14]).
When the build targets x86-64 with the sha feature, it compiles to this one instruction.
Otherwise it computes the same lanes with portable code, which is correct but slower.
Check Target.hasCpuFeature with static if before you select this path for speed.
Category: SIMD.
Profiles: aarch64-apple-darwin, aarch64-unknown-linux-gnu, aarch64-unknown-linux-gnu-no-libc, x86_64-unknown-linux-gnu, x86_64-unknown-linux-gnu-no-libc, wasm32-unknown-unknown, wasm32-wasip1, wasm32-wasip2.