// Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. #include "textflag.h" // func IndexByte(b []byte, c byte) int // input: // R0: b ptr // R1: b len // R2: b cap (unused) // R3: c byte to search // return // R0: result TEXT ·IndexByte(SB),NOSPLIT,$0-40 MOVD R3, R2 B ·IndexByteString(SB) // func IndexByteString(s string, c byte) int // input: // R0: s ptr // R1: s len // R2: c byte to search // return // R0: result TEXT ·IndexByteString(SB),NOSPLIT,$0-32 // Core algorithm: // We load chunks of data, 16 bytes at a time. // We compare them with the target byte using // a vector comparison. The vector comparison // generates byte mask outputs. We convert the byte // mask to a nibble mask and move it to an int register. // The lowest bit index / 4 is the matching lane. // // An example (using 8 byte vectors for clarity - // the real code uses 16 byte vectors): // // target: [92 92 92 92 92 92 92 92] repeat of input byte // data: [3a 92 3c 47 21 03 92 b9] loaded from input string // equalMask: [00 ff 00 00 00 00 ff 00] comparison (VCMEQ) // nibbleMask: [f0 00 00 0f .. .. .. ..] narrow to nibbles (VSHRN $4) // register: 0x0f0000f0 move to int register // idx: 1 lowest set bit / 4 // // If there is any match, the int register is nonzero and // we can read the match index off from it. Otherwise, there // is no match in those 16 bytes. // // For the small-input cases (<16 bytes), we have to be careful // not to read outside 16-byte aligned chunks because of MTE. // We use some additional masking to extract only matches that // are valid for the input data. #define PTR R0 #define LEN R1 #define TARGET R2 #define BASE R3 #define MASK R4 #define VTARGET V0.B16 // Length 0, nothing to do. CBZ LEN, fail // Make vector containing the byte we're searching for in each lane. VMOV TARGET, VTARGET // [c c c ... c c c] // Small, need to be extra careful about out of bounds. CMP $16, LEN BLT small // Save original location (for computing result). MOVD PTR, BASE // Check low bits of length. AND $0xf, LEN, R9 CBZ R9, multipleOf16 // Length is not a multiple of 16. // Check 16 bytes, but then advance less to make length aligned. VLD1 (PTR), [V2.B16] // load data VCMEQ VTARGET, V2.B16, V2.B16 // compare each byte against the target byte VSHRN $4, V2.H8, V2.B8 // compact to one nibble per byte VMOV V2.D[0], MASK // move to general purpose register CBNZ MASK, foundStart ADD R9, PTR SUB R9, LEN // Length is a nonzero multiple of 16. multipleOf16: TBZ $4, LEN, multipleOf32 VLD1.P (PTR), [V2.B16] SUB $16, LEN VCMEQ VTARGET, V2.B16, V2.B16 VSHRN $4, V2.H8, V2.B8 VMOV V2.D[0], MASK CBNZ MASK, found16 CBZ LEN, fail // Length is a nonzero multiple of 32. multipleOf32: TBZ $5, LEN, multipleOf64 VLD1.P (PTR), [V2.B16, V3.B16] // load data, PTR += 32 SUB $32, LEN VCMEQ VTARGET, V2.B16, V2.B16 VCMEQ VTARGET, V3.B16, V3.B16 VSHRN $4, V2.H8, V2.B8 VSHRN $4, V3.H8, V3.B8 VMOV V2.D[0], MASK CBNZ MASK, found32 VMOV V3.D[0], MASK CBNZ MASK, found16 CBZ LEN, fail // Length is a nonzero multiple of 64. multipleOf64: VLD1.P (PTR), [V2.B16, V3.B16, V4.B16, V5.B16] // load data, PTR += 64 SUB $64, LEN VCMEQ VTARGET, V2.B16, V2.B16 VCMEQ VTARGET, V3.B16, V3.B16 VCMEQ VTARGET, V4.B16, V4.B16 VCMEQ VTARGET, V5.B16, V5.B16 VORR V2.B16, V3.B16, V10.B16 VORR V4.B16, V5.B16, V11.B16 VORR V10.B16, V11.B16, V10.B16 VADDP V10.D2, V10.D2, V10.D2 VMOV V10.D[0], R10 CBNZ R10, found64 // at least one lane matched CBNZ LEN, multipleOf64 fail: MOVD $-1, R0 RET foundStart: RBIT MASK, MASK CLZ MASK, R9 // count trailing zeros LSR $2, R9, R0 // divide by 4 RET found16: SUB $16, PTR // undo .P // On entry to found0, MASK contains a nonzero nibble bitmask // of matches starting at PTR. found0: SUB BASE, PTR // convert pointer to offset RBIT MASK, MASK CLZ MASK, R9 // count trailing zeros ADD R9>>2, PTR, R0 // add nibble index to offset RET found32: SUB $32, PTR // undo .P B found0 found64: SUB $64, PTR // undo .P VSHRN $4, V2.H8, V2.B8 VMOV V2.D[0], MASK CBNZ MASK, found0 ADD $16, PTR // redo 1/4 of .P VSHRN $4, V3.H8, V3.B8 VMOV V3.D[0], MASK CBNZ MASK, found0 ADD $16, PTR // redo 1/4 of .P VSHRN $4, V4.H8, V4.B8 VMOV V4.D[0], MASK CBNZ MASK, found0 ADD $16, PTR // redo 1/4 of .P VSHRN $4, V5.H8, V5.B8 VMOV V5.D[0], MASK B found0 // 1-15 bytes PCALIGN $16 small: AND $0xf, PTR, R8 // R8 = offset of start in 16-byte region ADD R8, LEN, R9 // R9 = offset of end (from start's 16-byte region boundary) MOVD $0, R10 // R10 = low bits of match data to throw away CMP $16, R9 BGT noAdjust // straddles two 16-byte regions - safe to load directly // data is all within a single 16-byte region BIC $0xf, PTR, PTR // round down to start of 16-byte region LSL $2, R8, R10 // throw away the match bits below original start noAdjust: VLD1 (PTR), [V2.B16] VCMEQ VTARGET, V2.B16, V2.B16 VSHRN $4, V2.H8, V2.B8 // compact to one nibble per byte VMOV V2.D[0], MASK // move to general purpose register LSR R10, MASK, MASK // discard matches before string start RBIT MASK, MASK CLZ MASK, R9 LSR $2, R9, R9 CMP LEN, R9 CSINV LT, R9, ZR, R0 // if first match past end of string, set return value to -1 RET