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
95 changes: 67 additions & 28 deletions cbor/src/main/java/tools/jackson/dataformat/cbor/CBORParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,29 @@ public int getFirstTag() {
protected final static JacksonFeatureSet<StreamReadCapability> CBOR_READ_CAPABILITIES =
DEFAULT_READ_CAPABILITIES.with(StreamReadCapability.EXACT_FLOATS);

/**
* Whether VarHandles are usable on this runtime; probed once at class load.
* Being {@code static final} lets the branches in {@link #_decode32Bits()},
* {@link #_decode64Bits()} and {@link #_decodeQuad} fold away at JIT time.
*
* @since 3.3
*/
private final static boolean _VARHANDLE_AVAILABLE = _checkVarHandleAvailable();

private static boolean _checkVarHandleAvailable() {
// NOTE: this call is what first loads `CBORVarHandleUtil`, and that class
// names `VarHandle` in its field/method signatures. On a runtime lacking
// `java.lang.invoke.VarHandle` (some Android builds) loading it raises
// `NoClassDefFoundError` -- an Error, not an Exception -- so `Throwable`
// is what has to be caught here. Without this guard the failure would
// propagate out of this class's initializer and make the parser unusable.
try {
return CBORVarHandleUtil.isAvailable();
} catch (Throwable t) {
return false;
}
}

/*
/**********************************************************************
/* Configuration
Expand Down Expand Up @@ -1566,10 +1589,8 @@ private final int _nextFieldOptimized(PropertyNameMatcher matcher, final int len
int inPtr = _inputPtr;

// First quadbyte is easy
int q1 = (inBuf[inPtr++] & 0xFF);
q1 = (q1 << 8) | (inBuf[inPtr++] & 0xFF);
q1 = (q1 << 8) | (inBuf[inPtr++] & 0xFF);
q1 = (q1 << 8) | (inBuf[inPtr++] & 0xFF);
int q1 = _decodeQuad(inBuf, inPtr);
inPtr += 4;

if (len < 9) {
int q2 = (inBuf[inPtr++] & 0xFF);
Expand All @@ -1588,10 +1609,8 @@ private final int _nextFieldOptimized(PropertyNameMatcher matcher, final int len
return matcher.matchByQuad(q1, q2);
}

int q2 = (inBuf[inPtr++] & 0xFF);
q2 = (q2 << 8) | (inBuf[inPtr++] & 0xFF);
q2 = (q2 << 8) | (inBuf[inPtr++] & 0xFF);
q2 = (q2 << 8) | (inBuf[inPtr++] & 0xFF);
int q2 = _decodeQuad(inBuf, inPtr);
inPtr += 4;

if (len < 13) {
int q3 = (inBuf[inPtr++] & 0xFF);
Expand Down Expand Up @@ -1636,11 +1655,8 @@ private final int _nextFieldFromSymbolsLong(PropertyNameMatcher matcher,

final byte[] inBuf = _inputBuffer;
do {
int q = (inBuf[inPtr++] & 0xFF);
q = (q << 8) | inBuf[inPtr++] & 0xFF;
q = (q << 8) | inBuf[inPtr++] & 0xFF;
q = (q << 8) | inBuf[inPtr++] & 0xFF;
_quadBuffer[offset++] = q;
_quadBuffer[offset++] = _decodeQuad(inBuf, inPtr);
inPtr += 4;
} while ((len -= 4) > 3);
// and then leftovers
if (len > 0) {
Expand Down Expand Up @@ -3362,10 +3378,8 @@ private final String _findDecodedFromSymbols(final int len) throws JacksonExcept
int inPtr = _inputPtr;

// First quadbyte is easy
int q1 = (inBuf[inPtr++] & 0xFF);
q1 = (q1 << 8) | (inBuf[inPtr++] & 0xFF);
q1 = (q1 << 8) | (inBuf[inPtr++] & 0xFF);
q1 = (q1 << 8) | (inBuf[inPtr++] & 0xFF);
int q1 = _decodeQuad(inBuf, inPtr);
inPtr += 4;

if (len < 9) {
int q2 = _padQuadForNulls(inBuf[inPtr++]);
Expand All @@ -3384,10 +3398,8 @@ private final String _findDecodedFromSymbols(final int len) throws JacksonExcept
return _symbols.findName(q1, q2);
}

int q2 = (inBuf[inPtr++] & 0xFF);
q2 = (q2 << 8) | (inBuf[inPtr++] & 0xFF);
q2 = (q2 << 8) | (inBuf[inPtr++] & 0xFF);
q2 = (q2 << 8) | (inBuf[inPtr++] & 0xFF);
int q2 = _decodeQuad(inBuf, inPtr);
inPtr += 4;

if (len < 13) {
int q3 = _padQuadForNulls(inBuf[inPtr++]);
Expand Down Expand Up @@ -3434,11 +3446,8 @@ private final String _findDecodedLong(int len, int q1, int q2) throws JacksonExc

final byte[] inBuf = _inputBuffer;
do {
int q = (inBuf[inPtr++] & 0xFF);
q = (q << 8) | inBuf[inPtr++] & 0xFF;
q = (q << 8) | inBuf[inPtr++] & 0xFF;
q = (q << 8) | inBuf[inPtr++] & 0xFF;
_quadBuffer[offset++] = q;
_quadBuffer[offset++] = _decodeQuad(inBuf, inPtr);
inPtr += 4;
} while ((len -= 4) > 3);
// and then leftovers
if (len > 0) {
Expand Down Expand Up @@ -3484,6 +3493,23 @@ private final static int _padQuadForNulls(int firstByte) {
return (firstByte & 0xFF) | 0xFFFFFF00;
}

/**
* Helper method for decoding 4 bytes of an Object property name into the
* "quad" (big-endian {@code int}) form used by {@code ByteQuadsCanonicalizer}.
* Caller MUST have verified that 4 bytes are readable at given offset.
*
* @since 3.3
*/
private final static int _decodeQuad(byte[] buffer, int offset) {
if (_VARHANDLE_AVAILABLE) {
return CBORVarHandleUtil.getInt(buffer, offset);
}
return ((buffer[offset] & 0xFF) << 24)
| ((buffer[offset+1] & 0xFF) << 16)
| ((buffer[offset+2] & 0xFF) << 8)
| (buffer[offset+3] & 0xFF);
}

/*
/**********************************************************************
/* Internal methods, skipping
Expand Down Expand Up @@ -3776,8 +3802,14 @@ private final int _decode32Bits() throws JacksonException {
return _slow32();
}
final byte[] b = _inputBuffer;
int v = (b[ptr++] << 24) + ((b[ptr++] & 0xFF) << 16)
+ ((b[ptr++] & 0xFF) << 8) + (b[ptr++] & 0xFF);
final int v;
if (_VARHANDLE_AVAILABLE) {
v = CBORVarHandleUtil.getInt(b, ptr);
ptr += 4;
} else {
v = (b[ptr++] << 24) + ((b[ptr++] & 0xFF) << 16)
+ ((b[ptr++] & 0xFF) << 8) + (b[ptr++] & 0xFF);
}
_inputPtr = ptr;
return v;
}
Expand Down Expand Up @@ -3807,6 +3839,13 @@ private final long _decode64Bits() throws JacksonException {
return _slow64();
}
final byte[] b = _inputBuffer;
if (_VARHANDLE_AVAILABLE) {
// NOTE: identical to `_long()` of the two 32-bit halves below, since
// that is just a big-endian 8-byte read spelled out
final long l = CBORVarHandleUtil.getLong(b, ptr);
_inputPtr = ptr + 8;
return l;
}
int i1 = (b[ptr++] << 24) + ((b[ptr++] & 0xFF) << 16)
+ ((b[ptr++] & 0xFF) << 8) + (b[ptr++] & 0xFF);
int i2 = (b[ptr++] << 24) + ((b[ptr++] & 0xFF) << 16)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,27 @@

/**
* Utility class that provides {@link VarHandle} instances for efficient
* multi-byte primitive writes to byte arrays.
* multi-byte primitive reads and writes on byte arrays.
*<p>
* Handles are resolved once at class initialization. On runtimes where
* {@code MethodHandles.byteArrayViewVarHandle()} is unsupported (for example
* some Android runtimes) they are left {@code null} and {@link #isAvailable()}
* returns {@code false}. Callers MUST check {@link #isAvailable()} first: the
* {@code setXxx()} methods dereference the handles unconditionally, and the
* byte-shifting fallback lives in the caller, not here.
* {@code getXxx()} and {@code setXxx()} methods dereference the handles
* unconditionally, and the byte-shifting fallback lives in the caller, not here.
*
* @since 3.3
*/
final class CBORVarHandleUtil
{
/**
* VarHandle for writing an {@code int} as 4 big-endian bytes.
* VarHandle for reading/writing an {@code int} as 4 big-endian bytes.
* {@code null} if VarHandles are unavailable.
*/
static final VarHandle INT_BE;

/**
* VarHandle for writing a {@code long} as 8 big-endian bytes.
* VarHandle for reading/writing a {@code long} as 8 big-endian bytes.
* {@code null} if VarHandles are unavailable.
*/
static final VarHandle LONG_BE;
Expand All @@ -51,8 +51,8 @@ static boolean isAvailable() {
return LONG_BE != null;
}

// Helper methods that write primitives via the class's own VarHandle fields.
// Only called when the corresponding field is non-null, which implies
// Helper methods that read/write primitives via the class's own VarHandle
// fields. Only called when the corresponding field is non-null, which implies
// VarHandle is available on this runtime.

static void setInt(byte[] array, int offset, int value) {
Expand All @@ -62,4 +62,24 @@ static void setInt(byte[] array, int offset, int value) {
static void setLong(byte[] array, int offset, long value) {
LONG_BE.set(array, offset, value);
}

/**
* Reads 4 bytes at given offset as a big-endian {@code int}; caller MUST
* have verified that {@code offset+4} is within bounds of given array.
*
* @since 3.3
*/
static int getInt(byte[] array, int offset) {
return (int) INT_BE.get(array, offset);
}

/**
* Reads 8 bytes at given offset as a big-endian {@code long}; caller MUST
* have verified that {@code offset+8} is within bounds of given array.
*
* @since 3.3
*/
static long getLong(byte[] array, int offset) {
return (long) LONG_BE.get(array, offset);
}
}
3 changes: 3 additions & 0 deletions release-notes/CREDITS
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,6 @@ PJ Fanning (@pjfanning)
* Contributed #757: (smile) Use `VarHandle` for multi-byte primitive reads in
`SmileParser`
(3.3.0)
* Contributed #758: (cbor) Use `VarHandle` for multi-byte primitive reads in
`CBORParser`
(3.3.0)
2 changes: 2 additions & 0 deletions release-notes/VERSION
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ implementations)
(contributed by @pjfanning)
#757: (smile) Use `VarHandle` for multi-byte primitive reads in `SmileParser`
(contributed by @pjfanning)
#758: (cbor) Use `VarHandle` for multi-byte primitive reads in `CBORParser`
(contributed by @pjfanning)
#759: (smile) Use more efficient `String` construction wrt "Compact Strings"
for "long" text values
(fix by @cowtowncoder, w/ Claude code)
Expand Down
Loading