Skip to content

Commit

Permalink
Remove code difference of ASCII string parsing and serialization for …
Browse files Browse the repository at this point in the history
…GraalVM and Hotspot JIT compilers + clean code for other platforms
  • Loading branch information
plokhotnyuk committed Apr 29, 2022
1 parent 51dcd60 commit 1cc9e2e
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 104 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,14 @@ final class JsonReader private[jsoniter_scala](

def readKeyAsCharBuf(): Int = {
nextTokenOrError('"', head)
val len = parseString()
val len = parseString(0, Math.min(charBuf.length, tail - head), charBuf, head)
nextTokenOrError(':', head)
len
}

def readKeyAsString(): String = {
nextTokenOrError('"', head)
val len = parseString()
val len = parseString(0, Math.min(charBuf.length, tail - head), charBuf, head)
nextTokenOrError(':', head)
new String(charBuf, 0, len)
}
Expand Down Expand Up @@ -311,7 +311,7 @@ final class JsonReader private[jsoniter_scala](

def readString(default: String): String =
if (isNextToken('"', head)) {
val len = parseString()
val len = parseString(0, Math.min(charBuf.length, tail - head), charBuf, head)
new String(charBuf, 0, len)
} else readNullOrTokenError(default, '"')

Expand Down Expand Up @@ -391,7 +391,7 @@ final class JsonReader private[jsoniter_scala](

def readStringAsCharBuf(): Int = {
nextTokenOrError('"', head)
parseString()
parseString(0, Math.min(charBuf.length, tail - head), charBuf, head)
}

def readStringAsByte(): Byte = {
Expand Down Expand Up @@ -2432,11 +2432,6 @@ final class JsonReader private[jsoniter_scala](
(leastSigBits1.toLong << 48) | leastSigBits2)
} else parseUUID(loadMoreOrError(pos))

private[this] def parseString(): Int = {
val minLim = Math.min(charBuf.length, tail - head)
parseString(0, minLim, charBuf, head)
}

@tailrec
private[this] def parseString(i: Int, minLim: Int, charBuf: Array[Char], pos: Int): Int =
if (i < minLim) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,14 @@ final class JsonReader private[jsoniter_scala](

def readKeyAsCharBuf(): Int = {
nextTokenOrError('"', head)
val len = parseString()
val len = parseString(0, Math.min(tail - head, charBuf.length), charBuf, head)
nextTokenOrError(':', head)
len
}

def readKeyAsString(): String = {
nextTokenOrError('"', head)
val len = parseString()
val len = parseString(0, Math.min(tail - head, charBuf.length), charBuf, head)
nextTokenOrError(':', head)
new String(charBuf, 0, len)
}
Expand Down Expand Up @@ -311,7 +311,7 @@ final class JsonReader private[jsoniter_scala](

def readString(default: String): String =
if (isNextToken('"', head)) {
val len = parseString()
val len = parseString(0, Math.min(tail - head, charBuf.length), charBuf, head)
new String(charBuf, 0, len)
} else readNullOrTokenError(default, '"')

Expand Down Expand Up @@ -391,7 +391,7 @@ final class JsonReader private[jsoniter_scala](

def readStringAsCharBuf(): Int = {
nextTokenOrError('"', head)
parseString()
parseString(0, Math.min(tail - head, charBuf.length), charBuf, head)
}

def readStringAsByte(): Byte = {
Expand Down Expand Up @@ -2622,65 +2622,40 @@ final class JsonReader private[jsoniter_scala](
(leastSigBits1.toLong << 48) | leastSigBits2)
} else parseUUID(loadMoreOrError(pos))

private[this] def parseString(): Int = {
val pos = head
val minLim = Math.min(charBuf.length, tail - pos)
if (isGraalVM) parseStringUnrolled(0, minLim, charBuf, pos)
else parseString(0, minLim, charBuf, pos)
}

@tailrec
private[this] def parseString(i: Int, minLim: Int, charBuf: Array[Char], pos: Int): Int =
if (i < minLim) {
val b = buf(pos)
charBuf(i) = b.toChar
if (b == '"') {
head = pos + 1
i
} else if (((b - 32) ^ 60) <= 0) parseEncodedString(i, charBuf.length - 1, charBuf, pos)
else parseString(i + 1, minLim, charBuf, pos + 1)
} else if (pos >= tail) {
val newPos = loadMoreOrError(pos)
parseString(i, Math.min(charBuf.length, i + tail - newPos), charBuf, newPos)
} else parseString(i, Math.min(growCharBuf(i + 1), i + tail - pos), this.charBuf, pos)

@tailrec
private[this] def parseStringUnrolled(i: Int, minLim: Int, charBuf: Array[Char], pos: Int): Int =
if (i + 7 < minLim) { // Based on SWAR routine of JSON string parsing: https://github.com/sirthias/borer/blob/fde9d1ce674d151b0fee1dd0c2565020c3f6633a/core/src/main/scala/io/bullet/borer/json/JsonParser.scala#L456
val bs = ByteArrayAccess.getLong(buf, pos)
val qMask = (bs ^ 0x5D5D5D5D5D5D5D5DL) + 0x0101010101010101L
val bMask = (bs ^ 0x2323232323232323L) + 0x0101010101010101L
val cMask = (bs | 0x1F1F1F1F1F1F1F1FL) - 0x2020202020202020L
val notz = java.lang.Long.numberOfTrailingZeros((qMask | bMask | cMask) & 0x8080808080808080L)
val bs1 = bs & 0x00FF00FF00FF00FFL
val bs2 = bs & 0xFF00FF00FF00FF00L
charBuf(i) = bs1.toChar
charBuf(i + 1) = (bs2 >> 8).toChar
charBuf(i + 2) = (bs1 >> 16).toChar
charBuf(i + 3) = (bs2 >> 24).toChar
charBuf(i + 4) = (bs1 >> 32).toChar
charBuf(i + 5) = (bs2 >> 40).toChar
charBuf(i + 6) = (bs1 >> 48).toChar
charBuf(i + 7) = (bs2 >>> 56).toChar
val notz = java.lang.Long.numberOfTrailingZeros(((bs ^ 0x5D5D5D5D5D5D5D5DL) + 0x0101010101010101L |
(bs ^ 0x2323232323232323L) + 0x0101010101010101L | (bs | 0x1F1F1F1F1F1F1F1FL) - 0x2020202020202020L) &
0x8080808080808080L)
charBuf(i) = (bs & 0xFF).toChar
charBuf(i + 1) = (bs >> 8 & 0xFF).toChar
charBuf(i + 2) = (bs >> 16 & 0xFF).toChar
charBuf(i + 3) = (bs >> 24 & 0xFF).toChar
charBuf(i + 4) = (bs >> 32 & 0xFF).toChar
charBuf(i + 5) = (bs >> 40 & 0xFF).toChar
charBuf(i + 6) = (bs >> 48 & 0xFF).toChar
charBuf(i + 7) = (bs >>> 56).toChar
if (notz < 64) {
val offset = notz >> 3
if (bs << ~notz >>> 56 == '"') {
head = pos + offset + 1
i + offset
} else parseEncodedString(i + offset, charBuf.length - 1, charBuf, pos + offset)
} else parseStringUnrolled(i + 8, minLim, charBuf, pos + 8)
} else parseString(i + 8, minLim, charBuf, pos + 8)
} else if (i < minLim) {
val b = buf(pos)
charBuf(i) = b.toChar
if (b == '"') {
head = pos + 1
i
} else if (((b - 32) ^ 60) <= 0) parseEncodedString(i, charBuf.length - 1, charBuf, pos)
else parseStringUnrolled(i + 1, minLim, charBuf, pos + 1)
else parseString(i + 1, minLim, charBuf, pos + 1)
} else if (pos >= tail) {
val newPos = loadMoreOrError(pos)
parseStringUnrolled(i, Math.min(charBuf.length, i + tail - newPos), charBuf, newPos)
} else parseStringUnrolled(i, Math.min(growCharBuf(i + 1), i + tail - pos), this.charBuf, pos)
parseString(i, Math.min(charBuf.length, i + tail - newPos), charBuf, newPos)
} else parseString(i, Math.min(growCharBuf(i + 1), i + tail - pos), this.charBuf, pos)

@tailrec
private[this] def parseEncodedString(i: Int, lim: Int, charBuf: Array[Char], pos: Int): Int = {
Expand Down Expand Up @@ -3204,9 +3179,6 @@ final class JsonReader private[jsoniter_scala](
}

object JsonReader {
private final val isGraalVM: Boolean =
Option(System.getProperty("java.vendor.version")).getOrElse(System.getProperty("java.vm.name")).contains("GraalVM") ||
java.lang.management.ManagementFactory.getRuntimeMXBean.getInputArguments.contains("-XX:+UseJVMCICompiler")
private final val pow10Doubles: Array[Double] =
Array(1, 1e+1, 1e+2, 1e+3, 1e+4, 1e+5, 1e+6, 1e+7, 1e+8, 1e+9, 1e+10, 1e+11,
1e+12, 1e+13, 1e+14, 1e+15, 1e+16, 1e+17, 1e+18, 1e+19, 1e+20, 1e+21, 1e+22)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,7 @@ final class JsonWriter private[jsoniter_scala](
}
buf(pos) = '"'
pos += 1
pos =
if (JsonWriter.isGraalVM) writeString(x, 0, x.length, pos, limit - 1, escapedChars)
else writeStringUnrolled(x, 0, pos, Math.min(x.length, limit - pos - 1) + pos, escapedChars)
pos = writeString(x, 0, pos, Math.min(x.length, limit - pos - 1) + pos, escapedChars)
if (pos + 4 >= limit) pos = flushAndGrowBuf(4, pos)
ByteArrayAccess.setInt(this.buf, pos, 0x203A22)
if (config.indentionStep > 0) pos += 1
Expand Down Expand Up @@ -255,9 +253,7 @@ final class JsonWriter private[jsoniter_scala](
} else comma = true
buf(pos) = '"'
pos += 1
pos =
if (JsonWriter.isGraalVM) writeString(x, 0, x.length, pos, limit - 1, escapedChars)
else writeStringUnrolled(x, 0, pos, Math.min(x.length, limit - pos - 1) + pos, escapedChars)
pos = writeString(x, 0, pos, Math.min(x.length, limit - pos - 1) + pos, escapedChars)
buf(pos) = '"'
pos + 1
}
Expand Down Expand Up @@ -865,41 +861,31 @@ final class JsonWriter private[jsoniter_scala](
}

@tailrec
private[this] def writeString(s: String, from: Int, to: Int, pos: Int, posLim: Int, escapedChars: Array[Byte]): Int =
if (from >= to) pos
else if (pos >= posLim) writeString(s, from, to, flushAndGrowBuf(2, pos), limit - 1, escapedChars)
else {
val ch = s.charAt(from)
buf(pos) = ch.toByte
if (ch < 0x80 && escapedChars(ch) == 0) writeString(s, from + 1, to, pos + 1, posLim, escapedChars)
else writeEscapedOrEncodedString(s, from, pos, escapedChars)
}

@tailrec
private[this] def writeStringUnrolled(s: String, from: Int, pos: Int, minLim: Int, escapedChars: Array[Byte]): Int =
private[this] def writeString(s: String, from: Int, pos: Int, minLim: Int, escapedChars: Array[Byte]): Int =
if (pos + 3 < minLim) {
val ch1 = s.charAt(from)
val ch2 = s.charAt(from + 1)
val ch3 = s.charAt(from + 2)
val ch4 = s.charAt(from + 3)
buf(pos) = ch1.toByte
buf(pos + 1) = ch2.toByte
buf(pos + 2) = ch3.toByte
buf(pos + 3) = ch4.toByte
if ((ch1 | ch2 | ch3 | ch4) < 0x80 &&
(escapedChars(ch1) | escapedChars(ch2) | escapedChars(ch3) | escapedChars(ch4)) == 0) {
writeStringUnrolled(s, from + 4, pos + 4, minLim, escapedChars)
} else writeEscapedOrEncodedString(s, from, pos, escapedChars)
ByteArrayAccess.setInt(buf, pos, ch1 | ch2 << 8 | ch3 << 16 | ch4 << 24)
if ((ch1 | ch2 | ch3 | ch4) >= 0x80 || {
val ech1 = escapedChars(ch1)
val ech2 = escapedChars(ch2)
val ech3 = escapedChars(ch3)
val ech4 = escapedChars(ch4)
(ech1 | ech2 | ech3 | ech4) != 0
}) writeEscapedOrEncodedString(s, from, pos, escapedChars)
else writeString(s, from + 4, pos + 4, minLim, escapedChars)
} else if (pos < minLim) {
val ch = s.charAt(from)
buf(pos) = ch.toByte
if (ch < 0x80 && escapedChars(ch) == 0) writeStringUnrolled(s, from + 1, pos + 1, minLim, escapedChars)
else writeEscapedOrEncodedString(s, from, pos, escapedChars)
if (ch >= 0x80 || escapedChars(ch) != 0) writeEscapedOrEncodedString(s, from, pos, escapedChars)
else writeString(s, from + 1, pos + 1, minLim, escapedChars)
} else {
val remaining = s.length - from
if (remaining > 0) {
val newPos = flushAndGrowBuf(2, pos)
writeStringUnrolled(s, from, newPos, Math.min(remaining, limit - newPos - 1) + newPos, escapedChars)
writeString(s, from, newPos, Math.min(remaining, limit - newPos - 1) + newPos, escapedChars)
} else pos
}

Expand Down Expand Up @@ -2032,9 +2018,6 @@ final class JsonWriter private[jsoniter_scala](
}

object JsonWriter {
private final val isGraalVM: Boolean =
Option(System.getProperty("java.vendor.version")).getOrElse(System.getProperty("java.vm.name")).contains("GraalVM") ||
java.lang.management.ManagementFactory.getRuntimeMXBean.getInputArguments.contains("-XX:+UseJVMCICompiler")
/* Use the following code to generate `escapedChars` in Scala REPL:
val es = new Array[Byte](128)
java.util.Arrays.fill(es, 0, 32, -1: Byte)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,14 @@ final class JsonReader private[jsoniter_scala](

def readKeyAsCharBuf(): Int = {
nextTokenOrError('"', head)
val len = parseString()
val len = parseString(0, Math.min(charBuf.length, tail - head), charBuf, head)
nextTokenOrError(':', head)
len
}

def readKeyAsString(): String = {
nextTokenOrError('"', head)
val len = parseString()
val len = parseString(0, Math.min(charBuf.length, tail - head), charBuf, head)
nextTokenOrError(':', head)
new String(charBuf, 0, len)
}
Expand Down Expand Up @@ -313,7 +313,7 @@ final class JsonReader private[jsoniter_scala](

def readString(default: String): String =
if (isNextToken('"', head)) {
val len = parseString()
val len = parseString(0, Math.min(charBuf.length, tail - head), charBuf, head)
new String(charBuf, 0, len)
} else readNullOrTokenError(default, '"')

Expand Down Expand Up @@ -393,7 +393,7 @@ final class JsonReader private[jsoniter_scala](

def readStringAsCharBuf(): Int = {
nextTokenOrError('"', head)
parseString()
parseString(0, Math.min(charBuf.length, tail - head), charBuf, head)
}

def readStringAsByte(): Byte = {
Expand Down Expand Up @@ -2439,12 +2439,36 @@ final class JsonReader private[jsoniter_scala](
(leastSigBits1.toLong << 48) | leastSigBits2)
} else parseUUID(loadMoreOrError(pos))

private[this] def parseString(): Int =
parseString(0, Math.min(charBuf.length, tail - head), charBuf, head)

@tailrec
private[this] def parseString(i: Int, minLim: Int, charBuf: Array[Char], pos: Int): Int =
if (i < minLim) {
if (i + 3 < minLim) {
val buf = this.buf
val b1 = buf(pos)
charBuf(i) = b1.toChar
val b2 = buf(pos + 1)
charBuf(i + 1) = b2.toChar
val b3 = buf(pos + 2)
charBuf(i + 2) = b3.toChar
val b4 = buf(pos + 3)
charBuf(i + 3) = b4.toChar
if (b1 == '"') {
head = pos + 1
i
} else if (((b1 - 32) ^ 60) <= 0) parseEncodedString(i, charBuf.length - 1, charBuf, pos)
else if (b2 == '"') {
head = pos + 2
i + 1
} else if (((b2 - 32) ^ 60) <= 0) parseEncodedString(i + 1, charBuf.length - 1, charBuf, pos + 1)
else if (b3 == '"') {
head = pos + 3
i + 2
} else if (((b3 - 32) ^ 60) <= 0) parseEncodedString(i + 2, charBuf.length - 1, charBuf, pos + 2)
else if (b4 == '"') {
head = pos + 4
i + 3
} else if (((b4 - 32) ^ 60) <= 0) parseEncodedString(i + 3, charBuf.length - 1, charBuf, pos + 3)
else parseString(i + 4, minLim, charBuf, pos + 4)
} else if (i < minLim) {
val b = buf(pos)
charBuf(i) = b.toChar
if (b == '"') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ final class JsonWriter private[jsoniter_scala](
}
buf(pos) = '"'
pos += 1
pos = writeString(x, 0, x.length, pos, limit - 1, escapedChars)
pos = writeString(x, 0, pos, Math.min(x.length, limit - pos - 1) + pos, escapedChars)
if (pos + 3 >= limit) pos = flushAndGrowBuf(3, pos)
buf = this.buf
buf(pos) = '"'
Expand Down Expand Up @@ -264,7 +264,7 @@ final class JsonWriter private[jsoniter_scala](
} else comma = true
buf(pos) = '"'
pos += 1
pos = writeString(x, 0, x.length, pos, limit - 1, escapedChars)
pos = writeString(x, 0, pos, Math.min(x.length, limit - pos - 1) + pos, escapedChars)
buf(pos) = '"'
pos + 1
}
Expand Down Expand Up @@ -926,14 +926,36 @@ final class JsonWriter private[jsoniter_scala](
}

@tailrec
private[this] def writeString(s: String, from: Int, to: Int, pos: Int, posLim: Int, escapedChars: Array[Byte]): Int =
if (from >= to) pos
else if (pos >= posLim) writeString(s, from, to, flushAndGrowBuf(2, pos), limit - 1, escapedChars)
else {
private[this] def writeString(s: String, from: Int, pos: Int, minLim: Int, escapedChars: Array[Byte]): Int =
if (pos + 3 < minLim) {
val buf = this.buf
val ch1 = s.charAt(from)
val ch2 = s.charAt(from + 1)
val ch3 = s.charAt(from + 2)
val ch4 = s.charAt(from + 3)
buf(pos) = ch1.toByte
buf(pos + 1) = ch2.toByte
buf(pos + 2) = ch3.toByte
buf(pos + 3) = ch4.toByte
if ((ch1 | ch2 | ch3 | ch4) >= 0x80 || {
val ech1 = escapedChars(ch1)
val ech2 = escapedChars(ch2)
val ech3 = escapedChars(ch3)
val ech4 = escapedChars(ch4)
(ech1 | ech2 | ech3 | ech4) != 0
}) writeEscapedOrEncodedString(s, from, pos, escapedChars)
else writeString(s, from + 4, pos + 4, minLim, escapedChars)
} else if (pos < minLim) {
val ch = s.charAt(from)
buf(pos) = ch.toByte
if (ch < 0x80 && escapedChars(ch) == 0) writeString(s, from + 1, to, pos + 1, posLim, escapedChars)
else writeEscapedOrEncodedString(s, from, pos, escapedChars)
if (ch >= 0x80 || escapedChars(ch) != 0) writeEscapedOrEncodedString(s, from, pos, escapedChars)
else writeString(s, from + 1, pos + 1, minLim, escapedChars)
} else {
val remaining = s.length - from
if (remaining > 0) {
val newPos = flushAndGrowBuf(2, pos)
writeString(s, from, newPos, Math.min(remaining, limit - newPos - 1) + newPos, escapedChars)
} else pos
}

private[this] def writeEscapedOrEncodedString(s: String, from: Int, pos: Int, escapedChars: Array[Byte]): Int =
Expand Down

0 comments on commit 1cc9e2e

Please sign in to comment.