Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,28 @@

public class ProtobufGenerator extends GeneratorBase
{
/**
* Whether VarHandles are usable on this runtime; probed once at class load.
* Being {@code static final} lets the branches in the {@code _writeIntXX()}
* helpers fold away at JIT time.
*
* @since 3.3
*/
private static final boolean _VARHANDLE_AVAILABLE = _checkVarHandleAvailable();

private static boolean _checkVarHandleAvailable() {
// NOTE: this call is what first loads `ProtobufVarHandleUtil`, and that
// class names `VarHandle` in its field/method signatures. On a runtime
// without `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.
try {
return ProtobufVarHandleUtil.isAvailable();
} catch (Throwable t) {
return false;
}
}

/*
/**********************************************************************
/* Constants
Expand Down Expand Up @@ -1662,6 +1684,12 @@ private final void _writeInt32(int v) throws JacksonException
_ensureRoom(9); // max tag 5 bytes
int ptr = _writeTag(_currPtr);
final byte[] buf = _currBuffer;
// protobuf fixed32 is little-endian
if (_VARHANDLE_AVAILABLE) {
ProtobufVarHandleUtil.setInt(buf, ptr, v);
_currPtr = ptr + 4;
return;
}
buf[ptr++] = (byte) v;
v >>= 8;
buf[ptr++] = (byte) v;
Expand All @@ -1677,6 +1705,12 @@ private final void _writeInt32NoTag(int v) throws JacksonException
_ensureRoom(4);
int ptr = _currPtr;
final byte[] buf = _currBuffer;
// protobuf fixed32 is little-endian
if (_VARHANDLE_AVAILABLE) {
ProtobufVarHandleUtil.setInt(buf, ptr, v);
_currPtr = ptr + 4;
return;
}
buf[ptr++] = (byte) v;
v >>= 8;
buf[ptr++] = (byte) v;
Expand All @@ -1693,6 +1727,12 @@ private final void _writeInt64(long v64) throws JacksonException
int ptr = _writeTag(_currPtr);
final byte[] buf = _currBuffer;

// protobuf fixed64 is little-endian: low 32 bits first, then high
if (_VARHANDLE_AVAILABLE) {
ProtobufVarHandleUtil.setLong(buf, ptr, v64);
_currPtr = ptr + 8;
return;
}
int v = (int) v64;

buf[ptr++] = (byte) v;
Expand Down Expand Up @@ -1722,6 +1762,12 @@ private final void _writeInt64NoTag(long v64) throws JacksonException
int ptr = _currPtr;
final byte[] buf = _currBuffer;

// protobuf fixed64 is little-endian: low 32 bits first, then high
if (_VARHANDLE_AVAILABLE) {
ProtobufVarHandleUtil.setLong(buf, ptr, v64);
_currPtr = ptr + 8;
return;
}
int v = (int) v64;

buf[ptr++] = (byte) v;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,28 @@

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

private static boolean _checkVarHandleAvailable() {
// NOTE: this call is what first loads `ProtobufVarHandleUtil`, and that
// class names `VarHandle` in its field/method signatures. On a runtime
// without `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.
try {
return ProtobufVarHandleUtil.isAvailable();
} catch (Throwable t) {
return false;
}
}

// State constants

// State right after parser created; may start root Object
Expand Down Expand Up @@ -2948,8 +2970,14 @@ protected final int _decode32Bits() throws JacksonException {
return _slow32();
}
final byte[] b = _inputBuffer;
int v = (b[ptr] & 0xFF) + ((b[ptr+1] & 0xFF) << 8)
+ ((b[ptr+2] & 0xFF) << 16) + ((b[ptr+3] & 0xFF) << 24);
// protobuf fixed32 is little-endian
final int v;
if (_VARHANDLE_AVAILABLE) {
v = ProtobufVarHandleUtil.getInt(b, ptr);
} else {
v = (b[ptr] & 0xFF) + ((b[ptr+1] & 0xFF) << 8)
+ ((b[ptr+2] & 0xFF) << 16) + ((b[ptr+3] & 0xFF) << 24);
}
_inputPtr = ptr+4;
return v;
}
Expand Down Expand Up @@ -2979,6 +3007,12 @@ protected final long _decode64Bits() throws JacksonException {
return _slow64();
}
final byte[] b = _inputBuffer;
// protobuf fixed64 is little-endian; `_long()` of the two 32-bit halves
// below is just that same little-endian 8-byte read spelled out
if (_VARHANDLE_AVAILABLE) {
_inputPtr = ptr+8;
return ProtobufVarHandleUtil.getLong(b, ptr);
}
int i1 = (b[ptr++] & 0xFF) | ((b[ptr++] & 0xFF) << 8)
| ((b[ptr++] & 0xFF) << 16) | (b[ptr++] << 24);
int i2 = (b[ptr++] & 0xFF) | ((b[ptr++] & 0xFF) << 8)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package tools.jackson.dataformat.protobuf;

import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.nio.ByteOrder;

/**
* Utility class that provides {@link VarHandle} instances for efficient
* multi-byte primitive reads and writes on byte arrays.
*<p>
* NOTE: handles here are LITTLE-endian, unlike the big-endian ones CBOR needs:
* protobuf encodes its {@code fixed32}/{@code fixed64} types (and hence
* {@code float}/{@code double}) as little-endian.
*<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 getXxx()}/{@code setXxx()} methods dereference the handles
* unconditionally, and the byte-shifting fallback lives in the caller, not here.
*<p>
* Callers must also invoke {@link #isAvailable()} from within a
* {@code try}/{@code catch (Throwable)} block: this class names {@link VarHandle}
* in its field and method signatures, so on a runtime lacking
* {@code java.lang.invoke.VarHandle} entirely it fails to <i>link</i>, raising
* {@link LinkageError} before any code here can run.
*
* @since 3.3
*/
final class ProtobufVarHandleUtil
{
/**
* VarHandle for reading/writing an {@code int} as 4 little-endian bytes.
* {@code null} if VarHandles are unavailable.
*/
static final VarHandle INT_LE;

/**
* VarHandle for reading/writing a {@code long} as 8 little-endian bytes.
* {@code null} if VarHandles are unavailable.
*/
static final VarHandle LONG_LE;

static {
VarHandle intLe = null;
VarHandle longLe = null;
try {
intLe = MethodHandles.byteArrayViewVarHandle(int[].class, ByteOrder.LITTLE_ENDIAN);
longLe = MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.LITTLE_ENDIAN);
} catch (Throwable t) {
// VarHandles not available (e.g., Android): fall back to manual byte shifting
}
INT_LE = intLe;
// assigned last: non-null implies every handle above resolved too
LONG_LE = longLe;
}

private ProtobufVarHandleUtil() { }

static boolean isAvailable() {
return LONG_LE != null;
}

// 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. Caller MUST also have verified that
// the full 4/8 bytes are within bounds of the given array.

static int getInt(byte[] array, int offset) {
return (int) INT_LE.get(array, offset);
}

static long getLong(byte[] array, int offset) {
return (long) LONG_LE.get(array, offset);
}

static void setInt(byte[] array, int offset, int value) {
INT_LE.set(array, offset, value);
}

static void setLong(byte[] array, int offset, long value) {
LONG_LE.set(array, offset, value);
}
}