From f0de132bae56bc6b4f57ec918ed3e68cccc7bf2a Mon Sep 17 00:00:00 2001 From: Mattias Persson Date: Wed, 9 Nov 2016 11:09:52 +0100 Subject: [PATCH] Respects aligned memory access constraint (#8329) * Respects aligned memory access constraint specifically in off-heap versions of primitive-collections and NumberArray. UnsafeUtil provides means of knowing about aligned memory access constraint, but doesn't automatically conforms access by it. Need for aligned memory access is hardware dependent. Currently the muninn page cache has some logic for this. Other users of UnsafeUtil are the primitive-collections and NumberArray. They have different constraints themselves than the page cache do and so they have been changed to conform to alignment constraints, but can get away with doing less adaptations than the page cache does. This is the primary reason UnsafeUtil doesn't do these things automatically, because there are different optimizations that apply to different use cases. Original issue came up when noticing that using primitive-collections or NumberArray on Solaris could crash the JVM. * Fixed some typos and better method naming in UnsafeUtil --- .../batchimport/cache/OffHeapByteArray.java | 78 ++++++++-- .../batchimport/cache/OffHeapNumberArray.java | 21 ++- .../LongKeyLongValueUnsafeTable.java | 18 ++- .../hopscotch/LongKeyUnsafeTable.java | 6 +- .../primitive/hopscotch/UnsafeTable.java | 67 ++++++++- .../primitive/hopscotch/BasicTableTest.java | 13 +- .../impl/internal/dragons/UnsafeUtil.java | 136 ++++++++++++++++++ .../impl/internal/dragons/UnsafeUtilTest.java | 74 +++++++++- 8 files changed, 379 insertions(+), 34 deletions(-) diff --git a/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/OffHeapByteArray.java b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/OffHeapByteArray.java index 68fe3c90b9e4..36d1fd32561f 100644 --- a/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/OffHeapByteArray.java +++ b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/OffHeapByteArray.java @@ -98,28 +98,54 @@ public byte getByte( long index, int offset ) @Override public short getShort( long index, int offset ) { - return UnsafeUtil.getShort( address( index, offset ) ); + return getShort( address( index, offset ) ); + } + + private short getShort( long p ) + { + if ( UnsafeUtil.allowUnalignedMemoryAccess ) + { + return UnsafeUtil.getShort( p ); + } + + return UnsafeUtil.getShortByteWiseLittleEndian( p ); } @Override public int getInt( long index, int offset ) { - return UnsafeUtil.getInt( address( index, offset ) ); + return getInt( address( index, offset ) ); + } + + private int getInt( long p ) + { + if ( UnsafeUtil.allowUnalignedMemoryAccess ) + { + return UnsafeUtil.getInt( p ); + } + + return UnsafeUtil.getIntByteWiseLittleEndian( p ); } @Override public long get6ByteLong( long index, int offset ) { long address = address( index, offset ); - long low4b = (UnsafeUtil.getInt( address )) & 0xFFFFFFFFL; - long high2b = UnsafeUtil.getShort( address + Integer.BYTES ); + long low4b = getInt( address ) & 0xFFFFFFFFL; + long high2b = getShort( address + Integer.BYTES ); return low4b | (high2b << 32); } @Override public long getLong( long index, int offset ) { - return UnsafeUtil.getLong( address( index, offset ) ); + long p = address( index, offset ); + if ( UnsafeUtil.allowUnalignedMemoryAccess ) + { + return UnsafeUtil.getLong( p ); + } + + return UnsafeUtil.getLongByteWiseLittleEndian( p ); } @Override @@ -141,27 +167,59 @@ public void setByte( long index, int offset, byte value ) @Override public void setShort( long index, int offset, short value ) { - UnsafeUtil.putShort( address( index, offset ), value ); + putShort( address( index, offset ), value ); + } + + private void putShort( long p, short value ) + { + if ( UnsafeUtil.allowUnalignedMemoryAccess ) + { + UnsafeUtil.putShort( p, value ); + } + else + { + UnsafeUtil.putShortByteWiseLittleEndian( p, value ); + } } @Override public void setInt( long index, int offset, int value ) { - UnsafeUtil.putInt( address( index, offset ), value ); + putInt( address( index, offset ), value ); + } + + private void putInt( long p, int value ) + { + if ( UnsafeUtil.allowUnalignedMemoryAccess ) + { + UnsafeUtil.putInt( p, value ); + } + else + { + UnsafeUtil.putIntByteWiseLittleEndian( p, value ); + } } @Override public void set6ByteLong( long index, int offset, long value ) { long address = address( index, offset ); - UnsafeUtil.putInt( address, (int) value ); - UnsafeUtil.putShort( address + Integer.BYTES, (short) (value >>> 32) ); + putInt( address, (int) value ); + putShort( address + Integer.BYTES, (short) (value >>> 32) ); } @Override public void setLong( long index, int offset, long value ) { - UnsafeUtil.putLong( address( index, offset ), value ); + long p = address( index, offset ); + if ( UnsafeUtil.allowUnalignedMemoryAccess ) + { + UnsafeUtil.putLong( p, value ); + } + else + { + UnsafeUtil.putLongByteWiseLittleEndian( p, value ); + } } private long address( long index, int offset ) diff --git a/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/OffHeapNumberArray.java b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/OffHeapNumberArray.java index a2fbebe66263..2880a736d2ba 100644 --- a/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/OffHeapNumberArray.java +++ b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/OffHeapNumberArray.java @@ -23,6 +23,7 @@ public abstract class OffHeapNumberArray> extends BaseNumberArray { + private final long allocatedAddress; protected final long address; protected final long length; private boolean closed; @@ -32,7 +33,23 @@ protected OffHeapNumberArray( long length, int itemSize, long base ) super( itemSize, base ); UnsafeUtil.assertHasUnsafe(); this.length = length; - this.address = UnsafeUtil.allocateMemory( length * itemSize ); + + long dataSize = length * itemSize; + boolean itemSizeIsPowerOfTwo = Integer.bitCount( itemSize ) == 1; + if ( UnsafeUtil.allowUnalignedMemoryAccess || !itemSizeIsPowerOfTwo ) + { + // we can end up here even if we require aligned memory access. Reason is that item size + // isn't power of two anyway and so we have to fallback to safer means of accessing the memory, + // i.e. byte for byte. + this.allocatedAddress = this.address = UnsafeUtil.allocateMemory( dataSize ); + } + else + { + // the item size is a power of two and we're required to access memory aligned + // so we can allocate a bit more to ensure we can get an aligned memory address to start from. + this.allocatedAddress = UnsafeUtil.allocateMemory( dataSize + itemSize - 1 ); + this.address = UnsafeUtil.alignedMemory( allocatedAddress, itemSize ); + } } @Override @@ -55,7 +72,7 @@ public void close() if ( length > 0 ) { // Allocating 0 bytes actually returns address 0 - UnsafeUtil.free( address ); + UnsafeUtil.free( allocatedAddress ); } closed = true; } diff --git a/community/primitive-collections/src/main/java/org/neo4j/collection/primitive/hopscotch/LongKeyLongValueUnsafeTable.java b/community/primitive-collections/src/main/java/org/neo4j/collection/primitive/hopscotch/LongKeyLongValueUnsafeTable.java index 859f89064da6..72f43d63e2fb 100644 --- a/community/primitive-collections/src/main/java/org/neo4j/collection/primitive/hopscotch/LongKeyLongValueUnsafeTable.java +++ b/community/primitive-collections/src/main/java/org/neo4j/collection/primitive/hopscotch/LongKeyLongValueUnsafeTable.java @@ -19,8 +19,6 @@ */ package org.neo4j.collection.primitive.hopscotch; -import org.neo4j.unsafe.impl.internal.dragons.UnsafeUtil; - public class LongKeyLongValueUnsafeTable extends UnsafeTable { public LongKeyLongValueUnsafeTable( int capacity ) @@ -31,21 +29,21 @@ public LongKeyLongValueUnsafeTable( int capacity ) @Override protected long internalKey( long keyAddress ) { - return UnsafeUtil.getLong( keyAddress ); + return alignmentSafeGetLongAsTwoInts( keyAddress ); } @Override protected void internalPut( long keyAddress, long key, long[] value ) { - UnsafeUtil.putLong( keyAddress, key ); - UnsafeUtil.putLong( keyAddress+8, value[0] ); + alignmentSafePutLongAsTwoInts( keyAddress, key ); + alignmentSafePutLongAsTwoInts( keyAddress + 8, value[0] ); } @Override protected long[] internalRemove( long keyAddress ) { - valueMarker[0] = UnsafeUtil.getLong( keyAddress+8 ); - UnsafeUtil.putLong( keyAddress, -1 ); + valueMarker[0] = alignmentSafeGetLongAsTwoInts( keyAddress+8 ); + alignmentSafePutLongAsTwoInts( keyAddress, -1 ); return valueMarker; } @@ -53,8 +51,8 @@ protected long[] internalRemove( long keyAddress ) public long[] putValue( int index, long[] value ) { long valueAddress = valueAddress( index ); - long oldValue = UnsafeUtil.getLong( valueAddress ); - UnsafeUtil.putLong( valueAddress, value[0] ); + long oldValue = alignmentSafeGetLongAsTwoInts( valueAddress ); + alignmentSafePutLongAsTwoInts( valueAddress, value[0] ); return pack( oldValue ); } @@ -72,7 +70,7 @@ private long valueAddress( int index ) @Override public long[] value( int index ) { - long value = UnsafeUtil.getLong( valueAddress( index ) ); + long value = alignmentSafeGetLongAsTwoInts( valueAddress( index ) ); return pack( value ); } diff --git a/community/primitive-collections/src/main/java/org/neo4j/collection/primitive/hopscotch/LongKeyUnsafeTable.java b/community/primitive-collections/src/main/java/org/neo4j/collection/primitive/hopscotch/LongKeyUnsafeTable.java index 57d11a41202f..97cb55b62943 100644 --- a/community/primitive-collections/src/main/java/org/neo4j/collection/primitive/hopscotch/LongKeyUnsafeTable.java +++ b/community/primitive-collections/src/main/java/org/neo4j/collection/primitive/hopscotch/LongKeyUnsafeTable.java @@ -19,8 +19,6 @@ */ package org.neo4j.collection.primitive.hopscotch; -import org.neo4j.unsafe.impl.internal.dragons.UnsafeUtil; - public class LongKeyUnsafeTable extends UnsafeTable { public LongKeyUnsafeTable( int capacity, VALUE valueMarker ) @@ -31,13 +29,13 @@ public LongKeyUnsafeTable( int capacity, VALUE valueMarker ) @Override protected long internalKey( long keyAddress ) { - return UnsafeUtil.getLong( keyAddress ); + return alignmentSafeGetLongAsTwoInts( keyAddress ); } @Override protected void internalPut( long keyAddress, long key, VALUE value ) { - UnsafeUtil.putLong( keyAddress, key ); + alignmentSafePutLongAsTwoInts( keyAddress, key ); } @Override diff --git a/community/primitive-collections/src/main/java/org/neo4j/collection/primitive/hopscotch/UnsafeTable.java b/community/primitive-collections/src/main/java/org/neo4j/collection/primitive/hopscotch/UnsafeTable.java index eac3bd746246..a3eb7037c102 100644 --- a/community/primitive-collections/src/main/java/org/neo4j/collection/primitive/hopscotch/UnsafeTable.java +++ b/community/primitive-collections/src/main/java/org/neo4j/collection/primitive/hopscotch/UnsafeTable.java @@ -19,7 +19,6 @@ */ package org.neo4j.collection.primitive.hopscotch; - import org.neo4j.unsafe.impl.internal.dragons.UnsafeUtil; public abstract class UnsafeTable extends PowerOfTwoQuantizedTable @@ -27,6 +26,9 @@ public abstract class UnsafeTable extends PowerOfTwoQuantizedTable private final int bytesPerKey; private final int bytesPerEntry; private final long dataSize; + // address which should be free when closing + private final long allocatedAddress; + // address which should be used to access the table, the address where the table actually starts at private final long address; protected final VALUE valueMarker; @@ -38,7 +40,39 @@ protected UnsafeTable( int capacity, int bytesPerKey, VALUE valueMarker ) this.bytesPerEntry = 4+bytesPerKey; this.valueMarker = valueMarker; this.dataSize = (long)this.capacity*bytesPerEntry; - this.address = UnsafeUtil.allocateMemory( dataSize ); + + // Below is a piece of code which ensures that allocated memory is aligned to 4-byte boundary + // if memory system requires aligned memory access. The reason we pick 4-byte boundary is that + // it's the lowest common denominator and the size of our hop-bits field for every entry. + // So even for a table which would only deal with, say longs (8-byte), it would still need to + // read and write 4-byte hop-bits fields. Therefore this table can, if required to, read anything + // bigger than 4-byte fields as multiple 4-byte fields. This way it can play well with aligned + // memory access requirements. + + assert bytesPerEntry % Integer.BYTES == 0 : "Bytes per entry needs to be divisible by 4, this constraint " + + "is checked because on memory systems requiring aligned memory access this would otherwise break."; + + if ( UnsafeUtil.allowUnalignedMemoryAccess ) + { + this.allocatedAddress = this.address = UnsafeUtil.allocateMemory( dataSize ); + } + else + { + // There's an assertion above also verifying this, but it's only an actual problem if our memory system + // requires aligned access, which seems to be the case right here and now. + if ( (bytesPerEntry % Integer.BYTES) != 0 ) + { + throw new IllegalArgumentException( "Memory system requires aligned memory access and " + + getClass().getSimpleName() + " was designed to cope with this requirement by " + + "being able to accessing data in 4-byte chunks, if needed to. " + + "Although this table tried to be constructed with bytesPerKey:" + bytesPerKey + + " yielding a bytesPerEntry:" + bytesPerEntry + ", which isn't 4-byte aligned." ); + } + + this.allocatedAddress = UnsafeUtil.allocateMemory( dataSize + Integer.BYTES - 1 ); + this.address = UnsafeUtil.alignedMemory( allocatedAddress, Integer.BYTES ); + } + clearMemory(); } @@ -156,6 +190,33 @@ public void removeHopBit( int index, int hd ) @Override public void close() { - UnsafeUtil.free( address ); + UnsafeUtil.free( allocatedAddress ); + } + + protected static void alignmentSafePutLongAsTwoInts( long address, long value ) + { + if ( UnsafeUtil.allowUnalignedMemoryAccess ) + { + UnsafeUtil.putLong( address, value ); + } + else + { + // See javadoc in constructor as to why we do this + UnsafeUtil.putInt( address, (int) value ); + UnsafeUtil.putInt( address + Integer.BYTES, (int) (value >>> Integer.SIZE) ); + } + } + + protected static long alignmentSafeGetLongAsTwoInts( long address ) + { + if ( UnsafeUtil.allowUnalignedMemoryAccess ) + { + return UnsafeUtil.getLong( address ); + } + + // See javadoc in constructor as to why we do this + long lsb = UnsafeUtil.getInt( address ) & 0xFFFFFFFFL; + long msb = UnsafeUtil.getInt( address + Integer.BYTES ) & 0xFFFFFFFFL; + return lsb | (msb << Integer.SIZE); } } diff --git a/community/primitive-collections/src/test/java/org/neo4j/collection/primitive/hopscotch/BasicTableTest.java b/community/primitive-collections/src/test/java/org/neo4j/collection/primitive/hopscotch/BasicTableTest.java index 9c266dd5fe95..3d46a3774fe4 100644 --- a/community/primitive-collections/src/test/java/org/neo4j/collection/primitive/hopscotch/BasicTableTest.java +++ b/community/primitive-collections/src/test/java/org/neo4j/collection/primitive/hopscotch/BasicTableTest.java @@ -25,11 +25,15 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.Random; import org.neo4j.collection.primitive.Primitive; import static org.junit.Assert.assertEquals; import static org.junit.Assume.assumeTrue; + +import static java.lang.System.currentTimeMillis; + import static org.neo4j.collection.primitive.Primitive.VALUE_MARKER; @RunWith( Parameterized.class ) @@ -37,6 +41,9 @@ public class BasicTableTest { private final TableFactory factory; + private static final long seed = currentTimeMillis(); + private static final Random random = new Random( seed ); + @Parameterized.Parameters public static Collection data() { @@ -138,7 +145,7 @@ public boolean supportsLongs() @Override public Object sampleValue() { - return new int[] {4}; + return new int[] {random.nextInt( Integer.MAX_VALUE )}; } } } ); result.add( new Object[] { new TableFactory() @@ -158,7 +165,7 @@ public boolean supportsLongs() @Override public Object sampleValue() { - return new long[] {1458489572354L}; + return new long[] {Math.abs( random.nextLong() )}; } } } ); result.add( new Object[] { new TableFactory() @@ -178,7 +185,7 @@ public boolean supportsLongs() @Override public Object sampleValue() { - return new long[] {1458489572354L}; + return new long[] {Math.abs( random.nextLong() )}; } } } ); return result; diff --git a/community/unsafe/src/main/java/org/neo4j/unsafe/impl/internal/dragons/UnsafeUtil.java b/community/unsafe/src/main/java/org/neo4j/unsafe/impl/internal/dragons/UnsafeUtil.java index ed11afada9ff..246d488e22d2 100644 --- a/community/unsafe/src/main/java/org/neo4j/unsafe/impl/internal/dragons/UnsafeUtil.java +++ b/community/unsafe/src/main/java/org/neo4j/unsafe/impl/internal/dragons/UnsafeUtil.java @@ -332,6 +332,33 @@ public static long allocateMemory( long sizeInBytes ) return pointer; } + /** + * Returns address pointer equal to or slightly after the given {@code pointer}. + * The returned pointer as aligned with {@code alignBy} such that {@code pointer % alignBy == 0}. + * The given pointer should be allocated with at least the requested size + {@code alignBy - 1}, + * where the additional bytes will serve as padding for the worst case where the start of the usable + * area of the allocated memory will need to be shifted at most {@code alignBy - 1} bytes to the right. + * + *

+     * 0   4   8   12  16  20        ; 4-byte alignments
+     * |---|---|---|---|---|         ; memory
+     *        --------===            ; allocated memory (-required, =padding)
+     *         ^------^              ; used memory
+     * 
+ * + * @param pointer pointer to allocated memory from {@link #allocateMemory(long)}. + * @param alignBy power-of-two size to align to, e.g. 4 or 8. + * @return pointer to place inside the allocated memory to consider the effective start of the + * memory, which from that point is aligned by {@code alignBy}. + */ + public static long alignedMemory( long pointer, int alignBy ) + { + assert Integer.bitCount( alignBy ) == 1 : "Requires alignment to be power of 2, but was " + alignBy; + + long misalignment = pointer % alignBy; + return misalignment == 0 ? pointer : pointer + (alignBy - misalignment); + } + /** * Free the memory that was allocated with {@link #allocateMemory}. */ @@ -896,4 +923,113 @@ public static boolean exchangeNativeAccessCheckEnabled( boolean newSetting ) nativeAccessCheckEnabled = newSetting; return previousSetting; } + + /** + * Gets a {@code short} at memory address {@code p} by reading byte for byte, instead of the whole value + * in one go. This can be useful, even necessary in some scenarios where {@link #allowUnalignedMemoryAccess} + * is {@code false} and {@code p} isn't aligned properly. Values read with this method should have been + * previously put using {@link #putShortByteWiseLittleEndian(long, short)}. + * + * @param p address pointer to start reading at. + * @return the read value, which was read byte for byte. + */ + public static short getShortByteWiseLittleEndian( long p ) + { + short a = (short) (UnsafeUtil.getByte( p ) & 0xFF); + short b = (short) (UnsafeUtil.getByte( p + 1 ) & 0xFF); + return (short) ((b << 8) | a); + } + + /** + * Gets a {@code int} at memory address {@code p} by reading byte for byte, instead of the whole value + * in one go. This can be useful, even necessary in some scenarios where {@link #allowUnalignedMemoryAccess} + * is {@code false} and {@code p} isn't aligned properly. Values read with this method should have been + * previously put using {@link #putIntByteWiseLittleEndian(long, int)}. + * + * @param p address pointer to start reading at. + * @return the read value, which was read byte for byte. + */ + public static int getIntByteWiseLittleEndian( long p ) + { + int a = UnsafeUtil.getByte( p ) & 0xFF; + int b = UnsafeUtil.getByte( p + 1 ) & 0xFF; + int c = UnsafeUtil.getByte( p + 2 ) & 0xFF; + int d = UnsafeUtil.getByte( p + 3 ) & 0xFF; + return (d << 24) | (c << 16) | (b << 8) | a; + } + + /** + * Gets a {@code long} at memory address {@code p} by reading byte for byte, instead of the whole value + * in one go. This can be useful, even necessary in some scenarios where {@link #allowUnalignedMemoryAccess} + * is {@code false} and {@code p} isn't aligned properly. Values read with this method should have been + * previously put using {@link #putLongByteWiseLittleEndian(long, long)}. + * + * @param p address pointer to start reading at. + * @return the read value, which was read byte for byte. + */ + public static long getLongByteWiseLittleEndian( long p ) + { + long a = UnsafeUtil.getByte( p ) & 0xFF; + long b = UnsafeUtil.getByte( p + 1 ) & 0xFF; + long c = UnsafeUtil.getByte( p + 2 ) & 0xFF; + long d = UnsafeUtil.getByte( p + 3 ) & 0xFF; + long e = UnsafeUtil.getByte( p + 4 ) & 0xFF; + long f = UnsafeUtil.getByte( p + 5 ) & 0xFF; + long g = UnsafeUtil.getByte( p + 6 ) & 0xFF; + long h = UnsafeUtil.getByte( p + 7 ) & 0xFF; + return (h << 56) | (g << 48) | (f << 40) | (e << 32) | (d << 24) | (c << 16) | (b << 8) | a; + } + + /** + * Puts a {@code short} at memory address {@code p} by writing byte for byte, instead of the whole value + * in one go. This can be useful, even necessary in some scenarios where {@link #allowUnalignedMemoryAccess} + * is {@code false} and {@code p} isn't aligned properly. Values written with this method should be + * read using {@link #getShortByteWiseLittleEndian(long)}. + * + * @param p address pointer to start writing at. + * @param value value to write byte for byte. + */ + public static void putShortByteWiseLittleEndian( long p, short value ) + { + UnsafeUtil.putByte( p , (byte)( value ) ); + UnsafeUtil.putByte( p + 1, (byte)( value >> 8 ) ); + } + + /** + * Puts a {@code int} at memory address {@code p} by writing byte for byte, instead of the whole value + * in one go. This can be useful, even necessary in some scenarios where {@link #allowUnalignedMemoryAccess} + * is {@code false} and {@code p} isn't aligned properly. Values written with this method should be + * read using {@link #getIntByteWiseLittleEndian(long)}. + * + * @param p address pointer to start writing at. + * @param value value to write byte for byte. + */ + public static void putIntByteWiseLittleEndian( long p, int value ) + { + UnsafeUtil.putByte( p , (byte)( value ) ); + UnsafeUtil.putByte( p + 1, (byte)( value >> 8 ) ); + UnsafeUtil.putByte( p + 2, (byte)( value >> 16 ) ); + UnsafeUtil.putByte( p + 3, (byte)( value >> 24 ) ); + } + + /** + * Puts a {@code long} at memory address {@code p} by writing byte for byte, instead of the whole value + * in one go. This can be useful, even necessary in some scenarios where {@link #allowUnalignedMemoryAccess} + * is {@code false} and {@code p} isn't aligned properly. Values written with this method should be + * read using {@link #getShortByteWiseLittleEndian(long)}. + * + * @param p address pointer to start writing at. + * @param value value to write byte for byte. + */ + public static void putLongByteWiseLittleEndian( long p, long value ) + { + UnsafeUtil.putByte( p , (byte)( value ) ); + UnsafeUtil.putByte( p + 1, (byte)( value >> 8 ) ); + UnsafeUtil.putByte( p + 2, (byte)( value >> 16 ) ); + UnsafeUtil.putByte( p + 3, (byte)( value >> 24 ) ); + UnsafeUtil.putByte( p + 4, (byte)( value >> 32 ) ); + UnsafeUtil.putByte( p + 5, (byte)( value >> 40 ) ); + UnsafeUtil.putByte( p + 6, (byte)( value >> 48 ) ); + UnsafeUtil.putByte( p + 7, (byte)( value >> 56 ) ); + } } diff --git a/community/unsafe/src/test/java/org/neo4j/unsafe/impl/internal/dragons/UnsafeUtilTest.java b/community/unsafe/src/test/java/org/neo4j/unsafe/impl/internal/dragons/UnsafeUtilTest.java index 7d3ceba9a90b..a6ffdea3c127 100644 --- a/community/unsafe/src/test/java/org/neo4j/unsafe/impl/internal/dragons/UnsafeUtilTest.java +++ b/community/unsafe/src/test/java/org/neo4j/unsafe/impl/internal/dragons/UnsafeUtilTest.java @@ -28,9 +28,12 @@ import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.sameInstance; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; +import static java.lang.System.currentTimeMillis; + import static org.neo4j.unsafe.impl.internal.dragons.UnsafeUtil.*; public class UnsafeUtilTest @@ -230,12 +233,12 @@ public void mustSupportReadingAndWritingOfPrimitivesToMemory() throws Exception setMemory( address, 8, (byte) 0 ); assertThat( getShortVolatile( address ), is( (short) 0 ) ); - putFloat( address, (float) 1 ); + putFloat( address, 1 ); assertThat( getFloat( address ), is( (float) 1 ) ); setMemory( address, 8, (byte) 0 ); assertThat( getFloat( address ), is( (float) 0 ) ); - putFloatVolatile( address, (float) 1 ); + putFloatVolatile( address, 1 ); assertThat( getFloatVolatile( address ), is( (float) 1 ) ); setMemory( address, 8, (byte) 0 ); assertThat( getFloatVolatile( address ), is( (float) 0 ) ); @@ -458,4 +461,71 @@ public void directByteBufferCreationAndInitialisation() throws Exception free( address ); } } + + @Test + public void shouldAlignMemoryTo4ByteBoundary() throws Exception + { + // GIVEN + long allocatedMemory = currentTimeMillis(); + int alignBy = 4; + + // WHEN + for ( int i = 0; i < 10; i++ ) + { + // THEN + long alignedMemory = UnsafeUtil.alignedMemory( allocatedMemory, alignBy ); + assertTrue( alignedMemory >= allocatedMemory ); + assertEquals( 0, alignedMemory % Integer.BYTES ); + assertTrue( alignedMemory - allocatedMemory <= 3 ); + allocatedMemory++; + } + } + + @Test + public void shouldPutAndGetByteWiseLittleEndianShort() throws Exception + { + // GIVEN + long p = UnsafeUtil.allocateMemory( 2 ); + short value = (short) 0b11001100_10101010; + + // WHEN + UnsafeUtil.putShortByteWiseLittleEndian( p, value ); + short readValue = UnsafeUtil.getShortByteWiseLittleEndian( p ); + + // THEN + UnsafeUtil.free( p ); + assertEquals( value, readValue ); + } + + @Test + public void shouldPutAndGetByteWiseLittleEndianInt() throws Exception + { + // GIVEN + long p = UnsafeUtil.allocateMemory( 4 ); + int value = 0b11001100_10101010_10011001_01100110; + + // WHEN + UnsafeUtil.putIntByteWiseLittleEndian( p, value ); + int readValue = UnsafeUtil.getIntByteWiseLittleEndian( p ); + + // THEN + UnsafeUtil.free( p ); + assertEquals( value, readValue ); + } + + @Test + public void shouldPutAndGetByteWiseLittleEndianLong() throws Exception + { + // GIVEN + long p = UnsafeUtil.allocateMemory( 8 ); + long value = 0b11001100_10101010_10011001_01100110__10001000_01000100_00100010_00010001L; + + // WHEN + UnsafeUtil.putLongByteWiseLittleEndian( p, value ); + long readValue = UnsafeUtil.getLongByteWiseLittleEndian( p ); + + // THEN + UnsafeUtil.free( p ); + assertEquals( value, readValue ); + } }