From 253ccacac0bd4c3901e540252b158a43b305e097 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Fri, 14 Aug 2026 00:32:55 +0100 Subject: [PATCH 1/5] use varhandle for array access --- .../dataformat/cbor/CBORGenerator.java | 137 ++++++++++-------- .../dataformat/cbor/CBORVarHandleUtil.java | 58 ++++++++ 2 files changed, 136 insertions(+), 59 deletions(-) create mode 100644 cbor/src/main/java/tools/jackson/dataformat/cbor/CBORVarHandleUtil.java diff --git a/cbor/src/main/java/tools/jackson/dataformat/cbor/CBORGenerator.java b/cbor/src/main/java/tools/jackson/dataformat/cbor/CBORGenerator.java index f5d61e628..b383896bd 100644 --- a/cbor/src/main/java/tools/jackson/dataformat/cbor/CBORGenerator.java +++ b/cbor/src/main/java/tools/jackson/dataformat/cbor/CBORGenerator.java @@ -603,10 +603,15 @@ private final void _writeIntFull(int markerBase, int i) throws JacksonException _ensureRoomForOutput(5); _outputBuffer[_outputTail++] = (byte) (markerBase + SUFFIX_UINT32_ELEMENTS); - _outputBuffer[_outputTail++] = (byte) (i >> 24); - _outputBuffer[_outputTail++] = (byte) (i >> 16); - _outputBuffer[_outputTail++] = (byte) (i >> 8); - _outputBuffer[_outputTail++] = (byte) i; + if (CBORVarHandleUtil.INT_BE != null) { + CBORVarHandleUtil.INT_BE.set(_outputBuffer, _outputTail, i); + _outputTail += 4; + } else { + _outputBuffer[_outputTail++] = (byte) (i >> 24); + _outputBuffer[_outputTail++] = (byte) (i >> 16); + _outputBuffer[_outputTail++] = (byte) (i >> 8); + _outputBuffer[_outputTail++] = (byte) i; + } } // Helper method that works like `writeNumber(long)` but DOES NOT @@ -634,53 +639,57 @@ private final void _writeLongNoCheck(long l) throws JacksonException } else { _outputBuffer[_outputTail++] = (PREFIX_TYPE_INT_POS + SUFFIX_UINT64_ELEMENTS); } - int i = (int) (l >> 32); - _outputBuffer[_outputTail++] = (byte) (i >> 24); - _outputBuffer[_outputTail++] = (byte) (i >> 16); - _outputBuffer[_outputTail++] = (byte) (i >> 8); - _outputBuffer[_outputTail++] = (byte) i; - i = (int) l; - _outputBuffer[_outputTail++] = (byte) (i >> 24); - _outputBuffer[_outputTail++] = (byte) (i >> 16); - _outputBuffer[_outputTail++] = (byte) (i >> 8); - _outputBuffer[_outputTail++] = (byte) i; + if (CBORVarHandleUtil.LONG_BE != null) { + CBORVarHandleUtil.LONG_BE.set(_outputBuffer, _outputTail, l); + _outputTail += 8; + } else { + int i = (int) (l >> 32); + _outputBuffer[_outputTail++] = (byte) (i >> 24); + _outputBuffer[_outputTail++] = (byte) (i >> 16); + _outputBuffer[_outputTail++] = (byte) (i >> 8); + _outputBuffer[_outputTail++] = (byte) i; + i = (int) l; + _outputBuffer[_outputTail++] = (byte) (i >> 24); + _outputBuffer[_outputTail++] = (byte) (i >> 16); + _outputBuffer[_outputTail++] = (byte) (i >> 8); + _outputBuffer[_outputTail++] = (byte) i; + } } private final void _writeFloatNoCheck(float f) throws JacksonException { _ensureRoomForOutput(5); - /* - * 17-Apr-2010, tatu: could also use 'floatToIntBits', but it seems more - * accurate to use exact representation; and possibly faster. However, - * if there are cases where collapsing of NaN was needed (for non-Java - * clients), this can be changed - */ - int i = Float.floatToRawIntBits(f); _outputBuffer[_outputTail++] = BYTE_FLOAT32; - _outputBuffer[_outputTail++] = (byte) (i >> 24); - _outputBuffer[_outputTail++] = (byte) (i >> 16); - _outputBuffer[_outputTail++] = (byte) (i >> 8); - _outputBuffer[_outputTail++] = (byte) i; + if (CBORVarHandleUtil.FLOAT_BE != null) { + CBORVarHandleUtil.FLOAT_BE.set(_outputBuffer, _outputTail, f); + _outputTail += 4; + } else { + int i = Float.floatToRawIntBits(f); + _outputBuffer[_outputTail++] = (byte) (i >> 24); + _outputBuffer[_outputTail++] = (byte) (i >> 16); + _outputBuffer[_outputTail++] = (byte) (i >> 8); + _outputBuffer[_outputTail++] = (byte) i; + } } private final void _writeDoubleNoCheck(double d) throws JacksonException { _ensureRoomForOutput(9); - // 17-Apr-2010, tatu: could also use 'doubleToIntBits', but it seems - // more accurate to use exact representation; and possibly faster. - // However, if there are cases where collapsing of NaN was needed (for - // non-Java clients), this can be changed - long l = Double.doubleToRawLongBits(d); _outputBuffer[_outputTail++] = BYTE_FLOAT64; - - int i = (int) (l >> 32); - _outputBuffer[_outputTail++] = (byte) (i >> 24); - _outputBuffer[_outputTail++] = (byte) (i >> 16); - _outputBuffer[_outputTail++] = (byte) (i >> 8); - _outputBuffer[_outputTail++] = (byte) i; - i = (int) l; - _outputBuffer[_outputTail++] = (byte) (i >> 24); - _outputBuffer[_outputTail++] = (byte) (i >> 16); - _outputBuffer[_outputTail++] = (byte) (i >> 8); - _outputBuffer[_outputTail++] = (byte) i; + if (CBORVarHandleUtil.DOUBLE_BE != null) { + CBORVarHandleUtil.DOUBLE_BE.set(_outputBuffer, _outputTail, d); + _outputTail += 8; + } else { + long l = Double.doubleToRawLongBits(d); + int i = (int) (l >> 32); + _outputBuffer[_outputTail++] = (byte) (i >> 24); + _outputBuffer[_outputTail++] = (byte) (i >> 16); + _outputBuffer[_outputTail++] = (byte) (i >> 8); + _outputBuffer[_outputTail++] = (byte) i; + i = (int) l; + _outputBuffer[_outputTail++] = (byte) (i >> 24); + _outputBuffer[_outputTail++] = (byte) (i >> 16); + _outputBuffer[_outputTail++] = (byte) (i >> 8); + _outputBuffer[_outputTail++] = (byte) i; + } } private final void _writeDoubleMinimal(double d) throws JacksonException { @@ -1024,14 +1033,19 @@ public void writeNumberUnsigned(long l) throws IOException { _verifyValueWrite("write number unsigned"); _ensureRoomForOutput(9); _outputBuffer[_outputTail++] = (byte) (PREFIX_TYPE_INT_POS + SUFFIX_UINT64_ELEMENTS); - _outputBuffer[_outputTail++] = (byte) (l >> 56); - _outputBuffer[_outputTail++] = (byte) (l >> 48); - _outputBuffer[_outputTail++] = (byte) (l >> 40); - _outputBuffer[_outputTail++] = (byte) (l >> 32); - _outputBuffer[_outputTail++] = (byte) (l >> 24); - _outputBuffer[_outputTail++] = (byte) (l >> 16); - _outputBuffer[_outputTail++] = (byte) (l >> 8); - _outputBuffer[_outputTail++] = (byte) l; + if (CBORVarHandleUtil.LONG_BE != null) { + CBORVarHandleUtil.LONG_BE.set(_outputBuffer, _outputTail, l); + _outputTail += 8; + } else { + _outputBuffer[_outputTail++] = (byte) (l >> 56); + _outputBuffer[_outputTail++] = (byte) (l >> 48); + _outputBuffer[_outputTail++] = (byte) (l >> 40); + _outputBuffer[_outputTail++] = (byte) (l >> 32); + _outputBuffer[_outputTail++] = (byte) (l >> 24); + _outputBuffer[_outputTail++] = (byte) (l >> 16); + _outputBuffer[_outputTail++] = (byte) (l >> 8); + _outputBuffer[_outputTail++] = (byte) l; + } } @Override @@ -1058,16 +1072,21 @@ public JsonGenerator writeNumber(long l) throws JacksonException { } else { _outputBuffer[_outputTail++] = (PREFIX_TYPE_INT_POS + SUFFIX_UINT64_ELEMENTS); } - int i = (int) (l >> 32); - _outputBuffer[_outputTail++] = (byte) (i >> 24); - _outputBuffer[_outputTail++] = (byte) (i >> 16); - _outputBuffer[_outputTail++] = (byte) (i >> 8); - _outputBuffer[_outputTail++] = (byte) i; - i = (int) l; - _outputBuffer[_outputTail++] = (byte) (i >> 24); - _outputBuffer[_outputTail++] = (byte) (i >> 16); - _outputBuffer[_outputTail++] = (byte) (i >> 8); - _outputBuffer[_outputTail++] = (byte) i; + if (CBORVarHandleUtil.LONG_BE != null) { + CBORVarHandleUtil.LONG_BE.set(_outputBuffer, _outputTail, l); + _outputTail += 8; + } else { + int i = (int) (l >> 32); + _outputBuffer[_outputTail++] = (byte) (i >> 24); + _outputBuffer[_outputTail++] = (byte) (i >> 16); + _outputBuffer[_outputTail++] = (byte) (i >> 8); + _outputBuffer[_outputTail++] = (byte) i; + i = (int) l; + _outputBuffer[_outputTail++] = (byte) (i >> 24); + _outputBuffer[_outputTail++] = (byte) (i >> 16); + _outputBuffer[_outputTail++] = (byte) (i >> 8); + _outputBuffer[_outputTail++] = (byte) i; + } return this; } diff --git a/cbor/src/main/java/tools/jackson/dataformat/cbor/CBORVarHandleUtil.java b/cbor/src/main/java/tools/jackson/dataformat/cbor/CBORVarHandleUtil.java new file mode 100644 index 000000000..c5f2363f0 --- /dev/null +++ b/cbor/src/main/java/tools/jackson/dataformat/cbor/CBORVarHandleUtil.java @@ -0,0 +1,58 @@ +package tools.jackson.dataformat.cbor; + +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 writes to byte arrays. Falls back gracefully + * if VarHandles are not available (e.g., on some Android runtimes). + */ +final class CBORVarHandleUtil +{ + /** + * VarHandle for writing a {@code float} as 4 big-endian bytes. + * {@code null} if VarHandles are unavailable. + */ + static final VarHandle FLOAT_BE; + + /** + * VarHandle for writing a {@code double} as 8 big-endian bytes. + * {@code null} if VarHandles are unavailable. + */ + static final VarHandle DOUBLE_BE; + + /** + * VarHandle for 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. + * {@code null} if VarHandles are unavailable. + */ + static final VarHandle LONG_BE; + + static { + VarHandle floatBe = null; + VarHandle doubleBe = null; + VarHandle intBe = null; + VarHandle longBe = null; + try { + floatBe = MethodHandles.byteArrayViewVarHandle(float[].class, ByteOrder.BIG_ENDIAN); + doubleBe = MethodHandles.byteArrayViewVarHandle(double[].class, ByteOrder.BIG_ENDIAN); + intBe = MethodHandles.byteArrayViewVarHandle(int[].class, ByteOrder.BIG_ENDIAN); + longBe = MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.BIG_ENDIAN); + } catch (Exception e) { + // VarHandles not available (e.g., Android) — fall back to manual byte shifting + } + FLOAT_BE = floatBe; + DOUBLE_BE = doubleBe; + INT_BE = intBe; + LONG_BE = longBe; + } + + private CBORVarHandleUtil() { } +} From 5a99bd1848274a2ed00aa1e4c33c523d1bad4497 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Fri, 14 Aug 2026 00:54:05 +0100 Subject: [PATCH 2/5] Enhance comments in _writeFloatNoCheck method Added comments explaining the choice of float representation in _writeFloatNoCheck method. --- .../java/tools/jackson/dataformat/cbor/CBORGenerator.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cbor/src/main/java/tools/jackson/dataformat/cbor/CBORGenerator.java b/cbor/src/main/java/tools/jackson/dataformat/cbor/CBORGenerator.java index b383896bd..c29c62f4c 100644 --- a/cbor/src/main/java/tools/jackson/dataformat/cbor/CBORGenerator.java +++ b/cbor/src/main/java/tools/jackson/dataformat/cbor/CBORGenerator.java @@ -658,6 +658,12 @@ private final void _writeLongNoCheck(long l) throws JacksonException private final void _writeFloatNoCheck(float f) throws JacksonException { _ensureRoomForOutput(5); + /* + * 17-Apr-2010, tatu: could also use 'floatToIntBits', but it seems more + * accurate to use exact representation; and possibly faster. However, + * if there are cases where collapsing of NaN was needed (for non-Java + * clients), this can be changed + */ _outputBuffer[_outputTail++] = BYTE_FLOAT32; if (CBORVarHandleUtil.FLOAT_BE != null) { CBORVarHandleUtil.FLOAT_BE.set(_outputBuffer, _outputTail, f); From 71c951cf957ee7763f37a78689f8912ca9beaf06 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Fri, 14 Aug 2026 00:55:37 +0100 Subject: [PATCH 3/5] Remove unused comment in _writeFloatNoCheck method Removed commented-out code regarding float handling. --- .../main/java/tools/jackson/dataformat/cbor/CBORGenerator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cbor/src/main/java/tools/jackson/dataformat/cbor/CBORGenerator.java b/cbor/src/main/java/tools/jackson/dataformat/cbor/CBORGenerator.java index c29c62f4c..c677bf6c9 100644 --- a/cbor/src/main/java/tools/jackson/dataformat/cbor/CBORGenerator.java +++ b/cbor/src/main/java/tools/jackson/dataformat/cbor/CBORGenerator.java @@ -658,7 +658,7 @@ private final void _writeLongNoCheck(long l) throws JacksonException private final void _writeFloatNoCheck(float f) throws JacksonException { _ensureRoomForOutput(5); - /* + /* * 17-Apr-2010, tatu: could also use 'floatToIntBits', but it seems more * accurate to use exact representation; and possibly faster. However, * if there are cases where collapsing of NaN was needed (for non-Java From 4af37f1f7a0b17cfa0070f64b7be52e22932d05d Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Fri, 14 Aug 2026 21:02:11 +0100 Subject: [PATCH 4/5] Update CBORVarHandleUtil class documentation --- .../java/tools/jackson/dataformat/cbor/CBORVarHandleUtil.java | 1 + 1 file changed, 1 insertion(+) diff --git a/cbor/src/main/java/tools/jackson/dataformat/cbor/CBORVarHandleUtil.java b/cbor/src/main/java/tools/jackson/dataformat/cbor/CBORVarHandleUtil.java index c5f2363f0..c0e625a16 100644 --- a/cbor/src/main/java/tools/jackson/dataformat/cbor/CBORVarHandleUtil.java +++ b/cbor/src/main/java/tools/jackson/dataformat/cbor/CBORVarHandleUtil.java @@ -8,6 +8,7 @@ * Utility class that provides {@link VarHandle} instances for efficient * multi-byte primitive writes to byte arrays. Falls back gracefully * if VarHandles are not available (e.g., on some Android runtimes). + * @since 3.3 */ final class CBORVarHandleUtil { From 7463a93236559e74e33e3df2db26a787e1040738 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Sat, 15 Aug 2026 13:53:31 +0100 Subject: [PATCH 5/5] android safe --- .../dataformat/cbor/CBORGenerator.java | 39 +++++++++++++------ .../dataformat/cbor/CBORVarHandleUtil.java | 22 ++++++++++- 2 files changed, 48 insertions(+), 13 deletions(-) diff --git a/cbor/src/main/java/tools/jackson/dataformat/cbor/CBORGenerator.java b/cbor/src/main/java/tools/jackson/dataformat/cbor/CBORGenerator.java index c677bf6c9..e91739bf7 100644 --- a/cbor/src/main/java/tools/jackson/dataformat/cbor/CBORGenerator.java +++ b/cbor/src/main/java/tools/jackson/dataformat/cbor/CBORGenerator.java @@ -68,6 +68,21 @@ public class CBORGenerator extends GeneratorBase */ private final static int INDEFINITE_LENGTH = -2; // just to allow -1 as marker for "one too many" + // True when VarHandles are available on this runtime. Checked once at class + // load so that if CBORVarHandleUtil itself fails to load (e.g. Android + // without java.lang.invoke), we fall back to manual byte shifting instead + // of propagating NoClassDefFoundError. + private static final boolean _VARHANDLE_AVAILABLE; + static { + boolean available = false; + try { + available = CBORVarHandleUtil.INT_BE != null; + } catch (Throwable t) { + // CBORVarHandleUtil class not loadable — fall back to manual byte shifting + } + _VARHANDLE_AVAILABLE = available; + } + /* /********************************************************************** /* Configuration @@ -603,8 +618,8 @@ private final void _writeIntFull(int markerBase, int i) throws JacksonException _ensureRoomForOutput(5); _outputBuffer[_outputTail++] = (byte) (markerBase + SUFFIX_UINT32_ELEMENTS); - if (CBORVarHandleUtil.INT_BE != null) { - CBORVarHandleUtil.INT_BE.set(_outputBuffer, _outputTail, i); + if (_VARHANDLE_AVAILABLE) { + CBORVarHandleUtil.setInt(_outputBuffer, _outputTail, i); _outputTail += 4; } else { _outputBuffer[_outputTail++] = (byte) (i >> 24); @@ -639,8 +654,8 @@ private final void _writeLongNoCheck(long l) throws JacksonException } else { _outputBuffer[_outputTail++] = (PREFIX_TYPE_INT_POS + SUFFIX_UINT64_ELEMENTS); } - if (CBORVarHandleUtil.LONG_BE != null) { - CBORVarHandleUtil.LONG_BE.set(_outputBuffer, _outputTail, l); + if (_VARHANDLE_AVAILABLE) { + CBORVarHandleUtil.setLong(_outputBuffer, _outputTail, l); _outputTail += 8; } else { int i = (int) (l >> 32); @@ -665,8 +680,8 @@ private final void _writeFloatNoCheck(float f) throws JacksonException { * clients), this can be changed */ _outputBuffer[_outputTail++] = BYTE_FLOAT32; - if (CBORVarHandleUtil.FLOAT_BE != null) { - CBORVarHandleUtil.FLOAT_BE.set(_outputBuffer, _outputTail, f); + if (_VARHANDLE_AVAILABLE) { + CBORVarHandleUtil.setFloat(_outputBuffer, _outputTail, f); _outputTail += 4; } else { int i = Float.floatToRawIntBits(f); @@ -680,8 +695,8 @@ private final void _writeFloatNoCheck(float f) throws JacksonException { private final void _writeDoubleNoCheck(double d) throws JacksonException { _ensureRoomForOutput(9); _outputBuffer[_outputTail++] = BYTE_FLOAT64; - if (CBORVarHandleUtil.DOUBLE_BE != null) { - CBORVarHandleUtil.DOUBLE_BE.set(_outputBuffer, _outputTail, d); + if (_VARHANDLE_AVAILABLE) { + CBORVarHandleUtil.setDouble(_outputBuffer, _outputTail, d); _outputTail += 8; } else { long l = Double.doubleToRawLongBits(d); @@ -1039,8 +1054,8 @@ public void writeNumberUnsigned(long l) throws IOException { _verifyValueWrite("write number unsigned"); _ensureRoomForOutput(9); _outputBuffer[_outputTail++] = (byte) (PREFIX_TYPE_INT_POS + SUFFIX_UINT64_ELEMENTS); - if (CBORVarHandleUtil.LONG_BE != null) { - CBORVarHandleUtil.LONG_BE.set(_outputBuffer, _outputTail, l); + if (_VARHANDLE_AVAILABLE) { + CBORVarHandleUtil.setLong(_outputBuffer, _outputTail, l); _outputTail += 8; } else { _outputBuffer[_outputTail++] = (byte) (l >> 56); @@ -1078,8 +1093,8 @@ public JsonGenerator writeNumber(long l) throws JacksonException { } else { _outputBuffer[_outputTail++] = (PREFIX_TYPE_INT_POS + SUFFIX_UINT64_ELEMENTS); } - if (CBORVarHandleUtil.LONG_BE != null) { - CBORVarHandleUtil.LONG_BE.set(_outputBuffer, _outputTail, l); + if (_VARHANDLE_AVAILABLE) { + CBORVarHandleUtil.setLong(_outputBuffer, _outputTail, l); _outputTail += 8; } else { int i = (int) (l >> 32); diff --git a/cbor/src/main/java/tools/jackson/dataformat/cbor/CBORVarHandleUtil.java b/cbor/src/main/java/tools/jackson/dataformat/cbor/CBORVarHandleUtil.java index c0e625a16..24e79dfe6 100644 --- a/cbor/src/main/java/tools/jackson/dataformat/cbor/CBORVarHandleUtil.java +++ b/cbor/src/main/java/tools/jackson/dataformat/cbor/CBORVarHandleUtil.java @@ -46,7 +46,7 @@ final class CBORVarHandleUtil doubleBe = MethodHandles.byteArrayViewVarHandle(double[].class, ByteOrder.BIG_ENDIAN); intBe = MethodHandles.byteArrayViewVarHandle(int[].class, ByteOrder.BIG_ENDIAN); longBe = MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.BIG_ENDIAN); - } catch (Exception e) { + } catch (Throwable t) { // VarHandles not available (e.g., Android) — fall back to manual byte shifting } FLOAT_BE = floatBe; @@ -56,4 +56,24 @@ final class CBORVarHandleUtil } private CBORVarHandleUtil() { } + + // Helper methods that 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) { + INT_BE.set(array, offset, value); + } + + static void setLong(byte[] array, int offset, long value) { + LONG_BE.set(array, offset, value); + } + + static void setFloat(byte[] array, int offset, float value) { + FLOAT_BE.set(array, offset, value); + } + + static void setDouble(byte[] array, int offset, double value) { + DOUBLE_BE.set(array, offset, value); + } }