Skip to content

Commit f4bf9cf

Browse files
Copilotkovidgoyal
authored andcommitted
Add not_index_byte and not_index_byte2 functions to simdstring package
Fixes #9646
1 parent d8af7e2 commit f4bf9cf

5 files changed

Lines changed: 231 additions & 0 deletions

File tree

tools/simdstring/benchmarks_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,45 @@ func BenchmarkIndexByte2(b *testing.B) {
6464
t(pos, "scalar")
6565
}
6666
}
67+
68+
func BenchmarkNotIndexByte(b *testing.B) {
69+
t := func(pos int, which string) {
70+
// Fill with 'a' and place 'q' (a non-matching byte) at the target position
71+
data := haystack('a', 'q', pos)
72+
f := NotIndexByte
73+
switch which {
74+
case "scalar":
75+
f = not_index_byte_scalar
76+
}
77+
b.Run(fmt.Sprintf("%s_sz=%d", which, pos), func(b *testing.B) {
78+
for b.Loop() {
79+
f(data, 'a')
80+
}
81+
})
82+
}
83+
for _, pos := range sizes {
84+
t(pos, "simdstring")
85+
t(pos, "scalar")
86+
}
87+
}
88+
89+
func BenchmarkNotIndexByte2(b *testing.B) {
90+
t := func(pos int, which string) {
91+
// Fill with 'a' and place 'q' (neither 'a' nor 'x') at the target position
92+
data := haystack('a', 'q', pos)
93+
f := NotIndexByte2
94+
switch which {
95+
case "scalar":
96+
f = not_index_byte2_scalar
97+
}
98+
b.Run(fmt.Sprintf("%s_sz=%d", which, pos), func(b *testing.B) {
99+
for b.Loop() {
100+
f(data, 'a', 'x')
101+
}
102+
})
103+
}
104+
for _, pos := range sizes {
105+
t(pos, "simdstring")
106+
t(pos, "scalar")
107+
}
108+
}

tools/simdstring/generate.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,12 @@ func encode_cmgt16b(a, b, dest Register) (ans uint32) {
404404
return 0x271<<21 | b.ARMId()<<16 | 0xd<<10 | a.ARMId()<<5 | dest.ARMId()
405405
}
406406

407+
func encode_not16b(src, dest Register) uint32 {
408+
// NOT Vd.16B, Vn.16B (alias of MVN)
409+
// Encoding: 0 Q 1 01110 size 10000 00101 10 Rn Rd (Q=1, size=00 for .16B)
410+
return 0x6E205800 | (src.ARMId() << 5) | dest.ARMId()
411+
}
412+
407413
func (f *Function) MaskForCountDestructive(vec, ans Register) {
408414
// vec is clobbered by this function
409415
f.Comment("Count the number of bytes to the first 0xff byte and put the result in", ans)
@@ -688,6 +694,24 @@ func (f *Function) Or(a, b, dest Register) {
688694
f.AddTrailingComment(dest, "=", a, "|", b, "(bitwise)")
689695
}
690696

697+
func (f *Function) NotSelf(r Register) {
698+
if f.ISA.Goarch == ARM64 {
699+
f.Comment("Go assembler doesn't support the VMVN instruction, below we have: NOT", r.ARMFullWidth()+",", r.ARMFullWidth())
700+
f.instr("WORD", fmt.Sprintf("$0x%x", encode_not16b(r, r)))
701+
f.AddTrailingComment(r, "= ~", r, "(bitwise NOT)")
702+
return
703+
}
704+
all_ones := f.Vec(r.Size)
705+
defer f.ReleaseReg(all_ones)
706+
f.AllOnesRegister(all_ones)
707+
if r.Size == 128 {
708+
f.instr("PXOR", all_ones, r)
709+
} else {
710+
f.instr("VPXOR", all_ones, r, r)
711+
}
712+
f.AddTrailingComment(r, "= ~", r, "(bitwise NOT)")
713+
}
714+
691715
func (f *Function) And(a, b, dest Register) {
692716
if f.ISA.Goarch == ARM64 {
693717
f.instr("VAND", a.ARMFullWidth(), b.ARMFullWidth(), dest.ARMFullWidth())
@@ -1504,6 +1528,54 @@ func (s *State) indexc0() {
15041528

15051529
}
15061530

1531+
func (s *State) not_index_byte_body(f *Function) {
1532+
b := f.Vec()
1533+
f.Set1Epi8("b", b)
1534+
test_bytes := func(bytes_to_test, test_ans Register) {
1535+
f.CmpEqEpi8(bytes_to_test, b, test_ans)
1536+
f.NotSelf(test_ans)
1537+
}
1538+
s.index_func(f, test_bytes)
1539+
}
1540+
1541+
func (s *State) not_index_byte() {
1542+
f := s.NewFunction("not_index_byte_asm", "Find the index of the first byte that is not b", []FunctionParam{{"data", ByteSlice}, {"b", types.Byte}}, []FunctionParam{{"ans", types.Int}})
1543+
if s.ISA.HasSIMD {
1544+
s.not_index_byte_body(f)
1545+
}
1546+
f = s.NewFunction("not_index_byte_string_asm", "Find the index of the first byte that is not b", []FunctionParam{{"data", types.String}, {"b", types.Byte}}, []FunctionParam{{"ans", types.Int}})
1547+
if s.ISA.HasSIMD {
1548+
s.not_index_byte_body(f)
1549+
}
1550+
1551+
}
1552+
1553+
func (s *State) not_index_byte2_body(f *Function) {
1554+
b1 := f.Vec()
1555+
b2 := f.Vec()
1556+
f.Set1Epi8("b1", b1)
1557+
f.Set1Epi8("b2", b2)
1558+
test_bytes := func(bytes_to_test, test_ans Register) {
1559+
f.CmpEqEpi8(bytes_to_test, b1, test_ans)
1560+
f.CmpEqEpi8(bytes_to_test, b2, bytes_to_test)
1561+
f.Or(test_ans, bytes_to_test, test_ans)
1562+
f.NotSelf(test_ans)
1563+
}
1564+
s.index_func(f, test_bytes)
1565+
}
1566+
1567+
func (s *State) not_index_byte2() {
1568+
f := s.NewFunction("not_index_byte2_asm", "Find the index of the first byte that is neither b1 nor b2", []FunctionParam{{"data", ByteSlice}, {"b1", types.Byte}, {"b2", types.Byte}}, []FunctionParam{{"ans", types.Int}})
1569+
if s.ISA.HasSIMD {
1570+
s.not_index_byte2_body(f)
1571+
}
1572+
f = s.NewFunction("not_index_byte2_string_asm", "Find the index of the first byte that is neither b1 nor b2", []FunctionParam{{"data", types.String}, {"b1", types.Byte}, {"b2", types.Byte}}, []FunctionParam{{"ans", types.Int}})
1573+
if s.ISA.HasSIMD {
1574+
s.not_index_byte2_body(f)
1575+
}
1576+
1577+
}
1578+
15071579
func (s *State) Generate() {
15081580
s.test_load()
15091581
s.test_set1_epi8()
@@ -1516,6 +1588,8 @@ func (s *State) Generate() {
15161588
s.indexbyte2()
15171589
s.indexc0()
15181590
s.indexbyte()
1591+
s.not_index_byte()
1592+
s.not_index_byte2()
15191593

15201594
s.OutputFunction()
15211595
}

tools/simdstring/intrinsics.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,18 @@ var IndexC0 func(data []byte) int = index_c0_scalar
3333
// Return the index at which the first C0 byte is found or -1 when no such bytes are present.
3434
var IndexC0String func(data string) int = index_c0_string_scalar
3535

36+
// Return the index of the first byte in data that is not equal to b. If all bytes equal b, -1 is returned.
37+
var NotIndexByte func(data []byte, b byte) int = not_index_byte_scalar
38+
39+
// Return the index of the first byte in text that is not equal to b. If all bytes equal b, -1 is returned.
40+
var NotIndexByteString func(text string, b byte) int = not_index_byte_string_scalar
41+
42+
// Return the index of the first byte in data that is neither a nor b. If all bytes are a or b, -1 is returned.
43+
var NotIndexByte2 func(data []byte, a, b byte) int = not_index_byte2_scalar
44+
45+
// Return the index of the first byte in text that is neither a nor b. If all bytes are a or b, -1 is returned.
46+
var NotIndexByte2String func(text string, a, b byte) int = not_index_byte2_string_scalar
47+
3648
func init() {
3749
switch runtime.GOARCH {
3850
case "amd64":
@@ -51,6 +63,10 @@ func init() {
5163
IndexByte2String = index_byte2_string_asm_256
5264
IndexC0 = index_c0_asm_256
5365
IndexC0String = index_c0_string_asm_256
66+
NotIndexByte = not_index_byte_asm_256
67+
NotIndexByteString = not_index_byte_string_asm_256
68+
NotIndexByte2 = not_index_byte2_asm_256
69+
NotIndexByte2String = not_index_byte2_string_asm_256
5470
VectorSize = 32
5571
} else if Have128bit {
5672
IndexByte = index_byte_asm_128
@@ -59,6 +75,10 @@ func init() {
5975
IndexByte2String = index_byte2_string_asm_128
6076
IndexC0 = index_c0_asm_128
6177
IndexC0String = index_c0_string_asm_128
78+
NotIndexByte = not_index_byte_asm_128
79+
NotIndexByteString = not_index_byte_string_asm_128
80+
NotIndexByte2 = not_index_byte2_asm_128
81+
NotIndexByte2String = not_index_byte2_string_asm_128
6282
VectorSize = 16
6383
}
6484
}

tools/simdstring/intrinsics_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,65 @@ func TestSIMDStringOps(t *testing.T) {
244244
index_test([]byte("abc"), 'x')
245245
index_test([]byte("abc"), 'b')
246246

247+
not_index_test := func(haystack []byte, needle byte) {
248+
var actual int
249+
expected := not_index_byte_scalar(haystack, needle)
250+
251+
for _, sz := range sizes {
252+
switch sz {
253+
case 16:
254+
actual = not_index_byte_asm_128(haystack, needle)
255+
case 32:
256+
actual = not_index_byte_asm_256(haystack, needle)
257+
}
258+
if actual != expected {
259+
t.Fatalf("not_index failed in: %#v (%d != %d) at size: %d with needle: %#v", string(haystack), expected, actual, sz, needle)
260+
}
261+
}
262+
}
263+
not_index_test(nil, 'a')
264+
not_index_test([]byte{}, 'a')
265+
not_index_test([]byte("aaa"), 'a')
266+
not_index_test([]byte("aaab"), 'a')
267+
not_index_test([]byte("baaa"), 'a')
268+
not_index_test([]byte("abc"), 'a')
269+
for _, sz := range []int{0, 16, 32, 64, 79} {
270+
q := strings.Repeat("a", sz) + "b"
271+
not_index_test([]byte(q), 'a')
272+
not_index_test([]byte(q), 'b')
273+
not_index_test([]byte(strings.Repeat("a", sz)), 'a')
274+
}
275+
276+
not_index2_test := func(haystack []byte, a, b byte) {
277+
var actual int
278+
expected := not_index_byte2_scalar(haystack, a, b)
279+
280+
for _, sz := range sizes {
281+
switch sz {
282+
case 16:
283+
actual = not_index_byte2_asm_128(haystack, a, b)
284+
case 32:
285+
actual = not_index_byte2_asm_256(haystack, a, b)
286+
}
287+
if actual != expected {
288+
t.Fatalf("not_index2 failed in: %#v (%d != %d) at size: %d with needles: %#v %#v", string(haystack), expected, actual, sz, a, b)
289+
}
290+
}
291+
}
292+
not_index2_test(nil, 'a', 'b')
293+
not_index2_test([]byte{}, 'a', 'b')
294+
not_index2_test([]byte("aabb"), 'a', 'b')
295+
not_index2_test([]byte("aabbc"), 'a', 'b')
296+
not_index2_test([]byte("caabb"), 'a', 'b')
297+
for _, sz := range []int{0, 16, 32, 64, 79} {
298+
q := strings.Repeat("ab", sz) + "c"
299+
not_index2_test([]byte(q), 'a', 'b')
300+
not_index2_test([]byte(strings.Repeat("ab", sz)), 'a', 'b')
301+
for align := range 32 {
302+
not_index2_test([]byte(strings.Repeat(" ", align)+q), 'a', 'b')
303+
}
304+
}
305+
247306
}
248307

249308
func TestIntrinsics(t *testing.T) {

tools/simdstring/scalar.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,39 @@ func index_c0_string_scalar(data string) int {
5757
}
5858
return -1
5959
}
60+
61+
func not_index_byte_scalar(data []byte, b byte) int {
62+
for i, ch := range data {
63+
if ch != b {
64+
return i
65+
}
66+
}
67+
return -1
68+
}
69+
70+
func not_index_byte_string_scalar(data string, b byte) int {
71+
for i := 0; i < len(data); i++ {
72+
if data[i] != b {
73+
return i
74+
}
75+
}
76+
return -1
77+
}
78+
79+
func not_index_byte2_scalar(data []byte, a, b byte) int {
80+
for i, ch := range data {
81+
if ch != a && ch != b {
82+
return i
83+
}
84+
}
85+
return -1
86+
}
87+
88+
func not_index_byte2_string_scalar(data string, a, b byte) int {
89+
for i := 0; i < len(data); i++ {
90+
if data[i] != a && data[i] != b {
91+
return i
92+
}
93+
}
94+
return -1
95+
}

0 commit comments

Comments
 (0)