Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,12 @@ The text of each license is the standard Apache 2.0 license.
Files:
scala/fory-json-scala/src/main/java/org/apache/fory/json/scala/JsonEnumeration.java

* wast (https://github.com/wycst/wast)
Fory's probability-first printable-ASCII fast-path predicate is based in part on
WAST's JSON string escape predicate.
Files:
java/fory-json/src/main/java/org/apache/fory/json/writer/JsonAsciiWordPredicates.java


================================================================
BSD-3-Clause licenses
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.apache.fory.json.writer;

/** Exact packed-word predicates used after the printable-ASCII common path rejects a word. */
final class JsonAsciiWordExactPredicates {
static final long ASCII_CONTROL_OFFSET = 0x6060606060606060L;
static final long QUOTE_BYTES_COMPLEMENT = ~0x2222222222222222L;

private static final long HIGH_BITS = 0x8080808080808080L;
private static final int INT_HIGH_BITS = 0x80808080;
private static final int SHORT_HIGH_BITS = 0x8080;
private static final int INT_ASCII_CONTROL_OFFSET = 0x60606060;
private static final int SHORT_ASCII_CONTROL_OFFSET = 0x6060;
private static final long ONE_BYTES = 0x0101010101010101L;
private static final int INT_ONE_BYTES = 0x01010101;
private static final int SHORT_ONE_BYTES = 0x0101;
private static final int INT_QUOTE_BYTES_COMPLEMENT = ~0x22222222;
private static final int SHORT_QUOTE_BYTES_COMPLEMENT = ~0x2222;
private static final long BACKSLASH_BYTES_COMPLEMENT = ~0x5C5C5C5C5C5C5C5CL;

private JsonAsciiWordExactPredicates() {}

static boolean isWord(long word) {
long notBackslashMask = ((word ^ BACKSLASH_BYTES_COMPLEMENT) + ONE_BYTES) & HIGH_BITS;
return (((word + ASCII_CONTROL_OFFSET) & ~word) & HIGH_BITS) == HIGH_BITS
&& (((word ^ QUOTE_BYTES_COMPLEMENT) + ONE_BYTES) & HIGH_BITS) == HIGH_BITS
&& notBackslashMask == HIGH_BITS;
}

static boolean isWords(long word0, long word1, long notBackslashMask) {
return ((word0 + ASCII_CONTROL_OFFSET)
& (word1 + ASCII_CONTROL_OFFSET)
& ((word0 ^ QUOTE_BYTES_COMPLEMENT) + ONE_BYTES)
& ((word1 ^ QUOTE_BYTES_COMPLEMENT) + ONE_BYTES)
& notBackslashMask)
== HIGH_BITS;
}

static boolean isInt(int word, int notBackslashMask) {
return (((word + INT_ASCII_CONTROL_OFFSET) & ~word) & INT_HIGH_BITS) == INT_HIGH_BITS
&& (((word ^ INT_QUOTE_BYTES_COMPLEMENT) + INT_ONE_BYTES) & INT_HIGH_BITS) == INT_HIGH_BITS
&& notBackslashMask == INT_HIGH_BITS;
}

static boolean isShort(int word, int notBackslashMask) {
return (((word + SHORT_ASCII_CONTROL_OFFSET) & ~word) & SHORT_HIGH_BITS) == SHORT_HIGH_BITS
&& (((word ^ SHORT_QUOTE_BYTES_COMPLEMENT) + SHORT_ONE_BYTES) & SHORT_HIGH_BITS)
== SHORT_HIGH_BITS
&& notBackslashMask == SHORT_HIGH_BITS;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.apache.fory.json.writer;

/**
* Packed-word predicates for ASCII bytes that do not require JSON escaping.
*
* <p>The probability-first {@code 0x5D} printable-ASCII fast path is based in part on WAST's escape
* predicate:
* https://github.com/wycst/wast/blob/7d3d85579c831647b91af3356d4225cf65be6ea3/src/main/java/io/github/wycst/wast/json/JSONGeneral.java#L744-L815
*/
final class JsonAsciiWordPredicates {
// Utf8JsonWriter source-inlines its fixed-length fast check. Keep these constants package-visible
// so that path can share the predicate constants without adding another helper call.
static final long HIGH_BITS = 0x8080808080808080L;
static final long ASCII_GT_QUOTE_OFFSET = 0x5D5D5D5D5D5D5D5DL;
static final long ONE_BYTES = 0x0101010101010101L;
static final long BACKSLASH_BYTES_COMPLEMENT = ~0x5C5C5C5C5C5C5C5CL;

private static final int INT_HIGH_BITS = 0x80808080;
private static final int SHORT_HIGH_BITS = 0x8080;
private static final int INT_ASCII_GT_QUOTE_OFFSET = 0x5D5D5D5D;
private static final int SHORT_ASCII_GT_QUOTE_OFFSET = 0x5D5D;
private static final int INT_ONE_BYTES = 0x01010101;
private static final int SHORT_ONE_BYTES = 0x0101;
private static final int INT_BACKSLASH_BYTES_COMPLEMENT = ~0x5C5C5C5C;
private static final int SHORT_BACKSLASH_BYTES_COMPLEMENT = ~0x5C5C;

private JsonAsciiWordPredicates() {}

// Keep the exact uncommon fallback outside these per-word predicates. Folding it back in makes
// the standalone predicates too large for C2 to inline into the compact and short-string paths.
static boolean isJsonAsciiWord(long word) {
long notBackslashMask = ((word ^ BACKSLASH_BYTES_COMPLEMENT) + ONE_BYTES) & HIGH_BITS;
if ((notBackslashMask & (word + ASCII_GT_QUOTE_OFFSET)) == HIGH_BITS) {
return true;
}
return JsonAsciiWordExactPredicates.isWord(word);
}

// Aggregate every exact rejection mask before branching. Splitting this back into per-word
// calls adds one common-path branch for each eight bytes written.
static boolean isJsonAsciiWords(long word0, long word1) {
long notBackslashMask =
((word0 ^ BACKSLASH_BYTES_COMPLEMENT) + ONE_BYTES)
& ((word1 ^ BACKSLASH_BYTES_COMPLEMENT) + ONE_BYTES)
& HIGH_BITS;
if ((notBackslashMask & (word0 + ASCII_GT_QUOTE_OFFSET) & (word1 + ASCII_GT_QUOTE_OFFSET))
== HIGH_BITS) {
return true;
}
return JsonAsciiWordExactPredicates.isWords(word0, word1, notBackslashMask);
}

static boolean isJsonAsciiInt(int word) {
int notBackslashMask =
((word ^ INT_BACKSLASH_BYTES_COMPLEMENT) + INT_ONE_BYTES) & INT_HIGH_BITS;
if ((notBackslashMask & (word + INT_ASCII_GT_QUOTE_OFFSET)) == INT_HIGH_BITS) {
return true;
}
return JsonAsciiWordExactPredicates.isInt(word, notBackslashMask);
}

static boolean isJsonAsciiShort(int word) {
int notBackslashMask =
((word ^ SHORT_BACKSLASH_BYTES_COMPLEMENT) + SHORT_ONE_BYTES) & SHORT_HIGH_BITS;
if ((notBackslashMask & (word + SHORT_ASCII_GT_QUOTE_OFFSET)) == SHORT_HIGH_BITS) {
return true;
}
return JsonAsciiWordExactPredicates.isShort(word, notBackslashMask);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@

package org.apache.fory.json.writer;

import static org.apache.fory.json.writer.JsonAsciiWordPredicates.isJsonAsciiInt;
import static org.apache.fory.json.writer.JsonAsciiWordPredicates.isJsonAsciiWord;
import static org.apache.fory.json.writer.JsonAsciiWordPredicates.isJsonAsciiWords;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
Expand Down Expand Up @@ -72,18 +76,6 @@ public final class StringJsonWriter extends JsonWriter implements Appendable {
private static final byte[] NEGATIVE_INFINITY_BYTES =
"\"-Infinity\"".getBytes(StandardCharsets.ISO_8859_1);
private static final long DECIMAL_8 = 100_000_000L;
private static final long HIGH_BITS = 0x8080808080808080L;
private static final int INT_HIGH_BITS = 0x80808080;
private static final long ASCII_CONTROL_OFFSET = 0x6060606060606060L;
private static final int INT_ASCII_CONTROL_OFFSET = 0x60606060;
private static final long ASCII_GT_QUOTE_OFFSET = 0x5D5D5D5D5D5D5D5DL;
private static final int INT_ASCII_GT_QUOTE_OFFSET = 0x5D5D5D5D;
private static final long ONE_BYTES = 0x0101010101010101L;
private static final int INT_ONE_BYTES = 0x01010101;
private static final long QUOTE_BYTES_COMPLEMENT = ~0x2222222222222222L;
private static final int INT_QUOTE_BYTES_COMPLEMENT = ~0x22222222;
private static final long BACKSLASH_BYTES_COMPLEMENT = ~0x5C5C5C5C5C5C5C5CL;
private static final int INT_BACKSLASH_BYTES_COMPLEMENT = ~0x5C5C5C5C;
private static final int[] DIGIT_TRIPLES = new int[1000];
private static final int[] DIGIT_QUADS = new int[10000];
private static final long[] UTF16_DIGIT_QUADS = new long[10000];
Expand Down Expand Up @@ -2475,60 +2467,6 @@ private static boolean isJsonLatin1Byte(byte value) {
return ch > 0x1F && ch != '"' && ch != '\\';
}

// Keep the exact uncommon fallback outside these per-word predicates. Folding it back in makes
// the standalone predicates too large for C2 to inline into the compact-string writers.
private static boolean isJsonAsciiWord(long word) {
long notBackslash = ((word ^ BACKSLASH_BYTES_COMPLEMENT) + ONE_BYTES) & HIGH_BITS;
if ((notBackslash & (word + ASCII_GT_QUOTE_OFFSET)) == HIGH_BITS) {
return true;
}
return isJsonAsciiWordFallback(word);
}

private static boolean isJsonAsciiWordFallback(long word) {
long notBackslash = ((word ^ BACKSLASH_BYTES_COMPLEMENT) + ONE_BYTES) & HIGH_BITS;
return (((word + ASCII_CONTROL_OFFSET) & ~word) & HIGH_BITS) == HIGH_BITS
&& (((word ^ QUOTE_BYTES_COMPLEMENT) + ONE_BYTES) & HIGH_BITS) == HIGH_BITS
&& notBackslash == HIGH_BITS;
}

// Aggregate every exact rejection mask before branching. Splitting this back into per-word
// calls adds one common-path branch for each eight bytes written.
private static boolean isJsonAsciiWords(long word0, long word1) {
long notBackslash =
((word0 ^ BACKSLASH_BYTES_COMPLEMENT) + ONE_BYTES)
& ((word1 ^ BACKSLASH_BYTES_COMPLEMENT) + ONE_BYTES)
& HIGH_BITS;
if ((notBackslash & (word0 + ASCII_GT_QUOTE_OFFSET) & (word1 + ASCII_GT_QUOTE_OFFSET))
== HIGH_BITS) {
return true;
}
return isJsonAsciiWordsFallback(word0, word1, notBackslash);
}

private static boolean isJsonAsciiWordsFallback(long word0, long word1, long notBackslash) {
return ((word0 + ASCII_CONTROL_OFFSET)
& (word1 + ASCII_CONTROL_OFFSET)
& ((word0 ^ QUOTE_BYTES_COMPLEMENT) + ONE_BYTES)
& ((word1 ^ QUOTE_BYTES_COMPLEMENT) + ONE_BYTES)
& notBackslash)
== HIGH_BITS;
}

private static boolean isJsonAsciiInt(int word) {
int notBackslash = ((word ^ INT_BACKSLASH_BYTES_COMPLEMENT) + INT_ONE_BYTES) & INT_HIGH_BITS;
if ((notBackslash & (word + INT_ASCII_GT_QUOTE_OFFSET)) == INT_HIGH_BITS) {
return true;
}
return isJsonAsciiIntFallback(word, notBackslash);
}

private static boolean isJsonAsciiIntFallback(int word, int notBackslash) {
return (((word + INT_ASCII_CONTROL_OFFSET) & ~word) & INT_HIGH_BITS) == INT_HIGH_BITS
&& (((word ^ INT_QUOTE_BYTES_COMPLEMENT) + INT_ONE_BYTES) & INT_HIGH_BITS) == INT_HIGH_BITS
&& notBackslash == INT_HIGH_BITS;
}

private void writeIntNoEnsure(int value) {
if (coder == LATIN1) {
writeIntLatin1NoEnsure(value);
Expand Down
Loading
Loading