hyperscan的shufti匹配,用于单字符集的匹配,核心使用了intel的_mm_shuffle_epi8指令,其为针对16字节变量的指令。如下函数pshufb_m128,根据第二个参数b中保存的索引值,由参数a中获得相应位置的值,保存到result中。参数b中每个字节的低四位作为索引值,索引值的范围为:[0, 15],对应参数b中的16个字节,查询不会超出范围。
另外,如果参数b中某个字节的最高位为1,不执行查询操作,结果固定为0。
static really_inline
m128 pshufb_m128(m128 a, m128 b) {
m128 result;
result = _mm_shuffle_epi8(a, b);
return result;
}
如下示例,参数b中的第4、7、12字节的索引值2、9、e对应的结果为c、h、a。参数b中的第10个字节值为8a,最高位为1,结果值为0。
offset 0 1 2 3 4 5 6 7 8 9 a b c d e f
a 9 4 c h 2a n s x a h 3 7 1a 3c 9 m (byte)
\ / /
\ / /
\ / /
\ / /
b . . . . 2 . . 9 . . 8a . e . . . (byte)
| | | |
result . . . . c . . h . . 0 . a . . . (byte)
函数shuftiExec如下,参数mask_lo和mask_hi向量分别对应输入数据字节的低四位和高四位要查询的目的表,两次查询结果相与不为0,表明发送匹配。例如,对于匹配项:[/dabc],hyperscan在编译器构建如下的表。mask_hi每个字节表示一行,mask_lo表示列中所有非空行的并集。
offset 0 1 2 3 4 5 6 7 8 9 ... f
x8 x18 x18 x18 x18 x8 x8 x8 x8 x8 (mask_lo)
0 x1
1 x2
2 x4
3 x8 0 1 2 3 4 5 6 7 8 9
4 x10 a b c
5 x20
6 x40
7 x80
8 x0
. .
(mask_hi)
对于输入字符为ASCII字符1时,其十六进制值为0x31,高四位值为索引3在mask_hi中的值为0x8;低四位值为索引1,在mask_lo中的值为0x18。结果值0x8与0x18相与等于0x8,即ASCII字符1查表结果不等于零,与表达式[/dabc]产生了匹配。
对于输入字符为ASCII字符a时,其十六进制值为0x41,在mask_hi中查询结果值为0x10,在mask_lo中查询结果值为0x18,两者相与为0x10,不等于零,与表达式[/dabc]产生了匹配。
首先,如果输入数据的长度不足16字节,由函数shuftiFwdSlow处理。
const u8 *shuftiExec(m128 mask_lo, m128 mask_hi, const u8 *buf, const u8 *buf_end) {
assert(buf && buf_end);
assert(buf < buf_end);
// Slow path for small cases.
if (buf_end - buf < 16) {
return shuftiFwdSlow((const u8 *)&mask_lo, (const u8 *)&mask_hi, buf, buf_end);
}
其次,对buf开始位置的16个字节执行匹配检测,发生匹配返回匹配位置。
const m128 zeroes = zeroes128();
const m128 low4bits = _mm_set1_epi8(0xf);
const u8 *rv;
size_t min = (size_t)buf % 16;
assert(buf_end - buf >= 16);
// Preconditioning: most of the time our buffer won't be aligned.
m128 chars = loadu128(buf);
rv = fwdBlock(mask_lo, mask_hi, chars, buf, low4bits, zeroes);
if (rv) {
return rv;
}
否则,没有匹配的情况下,buf位置并不向前移动16字节,而是移动到与16对齐的地址上。之后,按照16字节对输入数据进行匹配处理,在buf起始地址不是16对齐的情况下,对非对齐部分数据重复处理了。
buf += (16 - min);
// Unrolling was here, but it wasn't doing anything but taking up space.
// Reroll FTW.
const u8 *last_block = buf_end - 16;
while (buf < last_block) {
m128 lchars = load128(buf);
rv = fwdBlock(mask_lo, mask_hi, lchars, buf, low4bits, zeroes);
if (rv) {
return rv;
}
buf += 16;
}
最后,处理输入数据中剩余的小于16字节的数据,但是这里也是按照16字节进行处理,可能对部分数据重复处理了。所有匹配都没有成功,返回输入数据的末尾。
// Use an unaligned load to mop up the last 16 bytes and get an accurate
// picture to buf_end.
assert(buf <= buf_end && buf >= buf_end - 16);
chars = loadu128(buf_end - 16);
rv = fwdBlock(mask_lo, mask_hi, chars, buf_end - 16, low4bits, zeroes);
if (rv) {
return rv;
}
return buf_end;
}
对于不足16字节的输入数据,每个字节的低四位在lo向量中索引取值,高四位在hi向量中索引取值,两次取值进行与操作,如果结果不为零,表示发送匹配,返回匹配位置。
/** \brief Naive byte-by-byte implementation. */
static really_inline
const u8 *shuftiFwdSlow(const u8 *lo, const u8 *hi, const u8 *buf, const u8 *buf_end) {
assert(buf < buf_end);
for (; buf < buf_end; ++buf) {
u8 c = *buf;
if (lo[c & 0xf] & hi[c >> 4]) {
break;
}
}
return buf;
}
函数block的匹配结果(z)如果等于0xffff,表明没有任何匹配发生。否则,将z取反,此时1表示匹配,通过ctz32计算末尾的0的个数,得到匹配发生的字节位置。
static really_inline
const u8 *firstMatch(const u8 *buf, u32 z) {
if (unlikely(z != 0xffff)) {
u32 pos = ctz32(~z & 0xffff);
assert(pos < 16);
return buf + pos;
} else {
return NULL; // no match
}
}
static really_inline
const u8 *fwdBlock(m128 mask_lo, m128 mask_hi, m128 chars, const u8 *buf,
const m128 low4bits, const m128 zeroes) {
u32 z = block(mask_lo, mask_hi, chars, low4bits, zeroes);
return firstMatch(buf, z);
}
函数block执行16字节输入数据(chars)的匹配工作。首先,取得chars中每个字节的低四位,在mask_lo向量中执行索引查找;其次,取得chars中每个字节的高四位,在mask_hi向量中执行索引查找。
最后,将两次查找结果执行与操作。与全零值以字节为单位进行比较(eq128),相等的字节比较结果为0xff,不相等结果为0x0。函数movemask128,将16个字节的比较结果压缩为16个比特位返回。
#define GET_LO_4(chars) and128(chars, low4bits)
#define GET_HI_4(chars) rshift64_m128(andnot128(low4bits, chars), 4)
static really_inline
u32 block(m128 mask_lo, m128 mask_hi, m128 chars, const m128 low4bits, const m128 compare) {
m128 c_lo = pshufb_m128(mask_lo, GET_LO_4(chars));
m128 c_hi = pshufb_m128(mask_hi, GET_HI_4(chars));
m128 t = and128(c_lo, c_hi);
return movemask128(eq128(t, compare));
}