|
5 | 5 |
|
6 | 6 | import java.util.Arrays; |
7 | 7 |
|
| 8 | +import static jdk.incubator.vector.ByteVector.SPECIES_128; |
8 | 9 | import static jdk.incubator.vector.ByteVector.SPECIES_256; |
9 | 10 | import static jdk.incubator.vector.ByteVector.SPECIES_512; |
10 | 11 | import static jdk.incubator.vector.VectorOperators.ULE; |
@@ -42,12 +43,179 @@ class StructuralIndexer { |
42 | 43 | void index(byte[] buffer, int length) { |
43 | 44 | bitIndexes.reset(); |
44 | 45 | switch (VECTOR_BIT_SIZE) { |
| 46 | + case 128 -> index128(buffer, length); |
45 | 47 | case 256 -> index256(buffer, length); |
46 | 48 | case 512 -> index512(buffer, length); |
47 | | - default -> throw new UnsupportedOperationException("Unsupported vector width: " + VECTOR_BIT_SIZE * 64); |
| 49 | + default -> throw new UnsupportedOperationException("Unsupported vector width: " + VECTOR_BIT_SIZE); |
48 | 50 | } |
49 | 51 | } |
50 | 52 |
|
| 53 | + /** |
| 54 | + * Stage-1 indexer for 128-bit vectors: four 16-byte lanes per 64-byte block (same layout as simdjson ARM64 |
| 55 | + * {@code simd8x64} / {@code index<64>}). |
| 56 | + */ |
| 57 | + private void index128(byte[] buffer, int length) { |
| 58 | + long prevInString = 0; |
| 59 | + long prevEscaped = 0; |
| 60 | + long prevStructurals = 0; |
| 61 | + long unescapedCharsError = 0; |
| 62 | + long prevScalar = 0; |
| 63 | + |
| 64 | + int loopBound = SPECIES_512.loopBound(length); |
| 65 | + int offset = 0; |
| 66 | + int blockIndex = 0; |
| 67 | + for (; offset < loopBound; offset += STEP_SIZE) { |
| 68 | + ByteVector chunk0 = ByteVector.fromArray(SPECIES_128, buffer, offset); |
| 69 | + ByteVector chunk1 = ByteVector.fromArray(SPECIES_128, buffer, offset + 16); |
| 70 | + ByteVector chunk2 = ByteVector.fromArray(SPECIES_128, buffer, offset + 32); |
| 71 | + ByteVector chunk3 = ByteVector.fromArray(SPECIES_128, buffer, offset + 48); |
| 72 | + |
| 73 | + long backslash = pack128( |
| 74 | + chunk0.eq(BACKSLASH).toLong(), |
| 75 | + chunk1.eq(BACKSLASH).toLong(), |
| 76 | + chunk2.eq(BACKSLASH).toLong(), |
| 77 | + chunk3.eq(BACKSLASH).toLong()); |
| 78 | + |
| 79 | + long escaped; |
| 80 | + if (backslash == 0) { |
| 81 | + escaped = prevEscaped; |
| 82 | + prevEscaped = 0; |
| 83 | + } else { |
| 84 | + backslash &= ~prevEscaped; |
| 85 | + long followsEscape = backslash << 1 | prevEscaped; |
| 86 | + long oddSequenceStarts = backslash & ODD_BITS_MASK & ~followsEscape; |
| 87 | + |
| 88 | + long sequencesStartingOnEvenBits = oddSequenceStarts + backslash; |
| 89 | + prevEscaped = ((oddSequenceStarts >>> 1) + (backslash >>> 1) + ((oddSequenceStarts & backslash) & 1)) >>> 63; |
| 90 | + |
| 91 | + long invertMask = sequencesStartingOnEvenBits << 1; |
| 92 | + escaped = (EVEN_BITS_MASK ^ invertMask) & followsEscape; |
| 93 | + } |
| 94 | + |
| 95 | + long unescaped = pack128( |
| 96 | + chunk0.compare(ULE, LAST_CONTROL_CHARACTER).toLong(), |
| 97 | + chunk1.compare(ULE, LAST_CONTROL_CHARACTER).toLong(), |
| 98 | + chunk2.compare(ULE, LAST_CONTROL_CHARACTER).toLong(), |
| 99 | + chunk3.compare(ULE, LAST_CONTROL_CHARACTER).toLong()); |
| 100 | + |
| 101 | + long quote0 = chunk0.eq(QUOTE).toLong(); |
| 102 | + long quote1 = chunk1.eq(QUOTE).toLong(); |
| 103 | + long quote2 = chunk2.eq(QUOTE).toLong(); |
| 104 | + long quote3 = chunk3.eq(QUOTE).toLong(); |
| 105 | + long quote = pack128(quote0, quote1, quote2, quote3) & ~escaped; |
| 106 | + |
| 107 | + long inString = prefixXor(quote) ^ prevInString; |
| 108 | + prevInString = inString >> 63; |
| 109 | + |
| 110 | + VectorShuffle<Byte> chunk0Low = chunk0.and(LOW_NIBBLE_MASK).toShuffle(); |
| 111 | + VectorShuffle<Byte> chunk1Low = chunk1.and(LOW_NIBBLE_MASK).toShuffle(); |
| 112 | + VectorShuffle<Byte> chunk2Low = chunk2.and(LOW_NIBBLE_MASK).toShuffle(); |
| 113 | + VectorShuffle<Byte> chunk3Low = chunk3.and(LOW_NIBBLE_MASK).toShuffle(); |
| 114 | + |
| 115 | + long whitespace = pack128( |
| 116 | + chunk0.eq(WHITESPACE_TABLE.rearrange(chunk0Low)).toLong(), |
| 117 | + chunk1.eq(WHITESPACE_TABLE.rearrange(chunk1Low)).toLong(), |
| 118 | + chunk2.eq(WHITESPACE_TABLE.rearrange(chunk2Low)).toLong(), |
| 119 | + chunk3.eq(WHITESPACE_TABLE.rearrange(chunk3Low)).toLong()); |
| 120 | + |
| 121 | + long op = pack128( |
| 122 | + chunk0.or((byte) 0x20).eq(OP_TABLE.rearrange(chunk0Low)).toLong(), |
| 123 | + chunk1.or((byte) 0x20).eq(OP_TABLE.rearrange(chunk1Low)).toLong(), |
| 124 | + chunk2.or((byte) 0x20).eq(OP_TABLE.rearrange(chunk2Low)).toLong(), |
| 125 | + chunk3.or((byte) 0x20).eq(OP_TABLE.rearrange(chunk3Low)).toLong()); |
| 126 | + |
| 127 | + long scalar = ~(op | whitespace); |
| 128 | + long nonQuoteScalar = scalar & ~quote; |
| 129 | + long followsNonQuoteScalar = nonQuoteScalar << 1 | prevScalar; |
| 130 | + prevScalar = nonQuoteScalar >>> 63; |
| 131 | + long potentialScalarStart = scalar & ~followsNonQuoteScalar; |
| 132 | + long potentialStructuralStart = op | potentialScalarStart; |
| 133 | + bitIndexes.write(blockIndex, prevStructurals); |
| 134 | + blockIndex += STEP_SIZE; |
| 135 | + prevStructurals = potentialStructuralStart & ~(inString ^ quote); |
| 136 | + unescapedCharsError |= unescaped & inString; |
| 137 | + } |
| 138 | + |
| 139 | + byte[] remainder = remainder(buffer, length, blockIndex); |
| 140 | + ByteVector chunk0 = ByteVector.fromArray(SPECIES_128, remainder, 0); |
| 141 | + ByteVector chunk1 = ByteVector.fromArray(SPECIES_128, remainder, 16); |
| 142 | + ByteVector chunk2 = ByteVector.fromArray(SPECIES_128, remainder, 32); |
| 143 | + ByteVector chunk3 = ByteVector.fromArray(SPECIES_128, remainder, 48); |
| 144 | + |
| 145 | + long backslash = pack128( |
| 146 | + chunk0.eq(BACKSLASH).toLong(), |
| 147 | + chunk1.eq(BACKSLASH).toLong(), |
| 148 | + chunk2.eq(BACKSLASH).toLong(), |
| 149 | + chunk3.eq(BACKSLASH).toLong()); |
| 150 | + |
| 151 | + long escaped; |
| 152 | + if (backslash == 0) { |
| 153 | + escaped = prevEscaped; |
| 154 | + } else { |
| 155 | + backslash &= ~prevEscaped; |
| 156 | + long followsEscape = backslash << 1 | prevEscaped; |
| 157 | + long oddSequenceStarts = backslash & ODD_BITS_MASK & ~followsEscape; |
| 158 | + |
| 159 | + long sequencesStartingOnEvenBits = oddSequenceStarts + backslash; |
| 160 | + long invertMask = sequencesStartingOnEvenBits << 1; |
| 161 | + escaped = (EVEN_BITS_MASK ^ invertMask) & followsEscape; |
| 162 | + } |
| 163 | + |
| 164 | + long unescaped = pack128( |
| 165 | + chunk0.compare(ULE, LAST_CONTROL_CHARACTER).toLong(), |
| 166 | + chunk1.compare(ULE, LAST_CONTROL_CHARACTER).toLong(), |
| 167 | + chunk2.compare(ULE, LAST_CONTROL_CHARACTER).toLong(), |
| 168 | + chunk3.compare(ULE, LAST_CONTROL_CHARACTER).toLong()); |
| 169 | + |
| 170 | + long quote = pack128( |
| 171 | + chunk0.eq(QUOTE).toLong(), |
| 172 | + chunk1.eq(QUOTE).toLong(), |
| 173 | + chunk2.eq(QUOTE).toLong(), |
| 174 | + chunk3.eq(QUOTE).toLong()) & ~escaped; |
| 175 | + |
| 176 | + long inString = prefixXor(quote) ^ prevInString; |
| 177 | + prevInString = inString >> 63; |
| 178 | + |
| 179 | + VectorShuffle<Byte> chunk0Low = chunk0.and(LOW_NIBBLE_MASK).toShuffle(); |
| 180 | + VectorShuffle<Byte> chunk1Low = chunk1.and(LOW_NIBBLE_MASK).toShuffle(); |
| 181 | + VectorShuffle<Byte> chunk2Low = chunk2.and(LOW_NIBBLE_MASK).toShuffle(); |
| 182 | + VectorShuffle<Byte> chunk3Low = chunk3.and(LOW_NIBBLE_MASK).toShuffle(); |
| 183 | + |
| 184 | + long whitespace = pack128( |
| 185 | + chunk0.eq(WHITESPACE_TABLE.rearrange(chunk0Low)).toLong(), |
| 186 | + chunk1.eq(WHITESPACE_TABLE.rearrange(chunk1Low)).toLong(), |
| 187 | + chunk2.eq(WHITESPACE_TABLE.rearrange(chunk2Low)).toLong(), |
| 188 | + chunk3.eq(WHITESPACE_TABLE.rearrange(chunk3Low)).toLong()); |
| 189 | + |
| 190 | + long op = pack128( |
| 191 | + chunk0.or((byte) 0x20).eq(OP_TABLE.rearrange(chunk0Low)).toLong(), |
| 192 | + chunk1.or((byte) 0x20).eq(OP_TABLE.rearrange(chunk1Low)).toLong(), |
| 193 | + chunk2.or((byte) 0x20).eq(OP_TABLE.rearrange(chunk2Low)).toLong(), |
| 194 | + chunk3.or((byte) 0x20).eq(OP_TABLE.rearrange(chunk3Low)).toLong()); |
| 195 | + |
| 196 | + long scalar = ~(op | whitespace); |
| 197 | + long nonQuoteScalar = scalar & ~quote; |
| 198 | + long followsNonQuoteScalar = nonQuoteScalar << 1 | prevScalar; |
| 199 | + long potentialScalarStart = scalar & ~followsNonQuoteScalar; |
| 200 | + long potentialStructuralStart = op | potentialScalarStart; |
| 201 | + bitIndexes.write(blockIndex, prevStructurals); |
| 202 | + blockIndex += STEP_SIZE; |
| 203 | + prevStructurals = potentialStructuralStart & ~(inString ^ quote); |
| 204 | + unescapedCharsError |= unescaped & inString; |
| 205 | + bitIndexes.write(blockIndex, prevStructurals); |
| 206 | + bitIndexes.finish(); |
| 207 | + if (prevInString != 0) { |
| 208 | + throw new JsonParsingException("Unclosed string. A string is opened, but never closed."); |
| 209 | + } |
| 210 | + if (unescapedCharsError != 0) { |
| 211 | + throw new JsonParsingException("Unescaped characters. Within strings, there are characters that should be escaped."); |
| 212 | + } |
| 213 | + } |
| 214 | + |
| 215 | + private static long pack128(long mask0, long mask1, long mask2, long mask3) { |
| 216 | + return mask0 | (mask1 << 16) | (mask2 << 32) | (mask3 << 48); |
| 217 | + } |
| 218 | + |
51 | 219 | private void index256(byte[] buffer, int length) { |
52 | 220 | long prevInString = 0; |
53 | 221 | long prevEscaped = 0; |
|
0 commit comments