1 /*
   2  * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 import jdk.internal.misc.Unsafe;
  25 import java.lang.reflect.Field;
  26 
  27 /**
  28  * Helper class to support testing of Unsafe.copyMemory and Unsafe.copySwapMemory
  29  */
  30 public class CopyCommon {
  31     private static final boolean DEBUG = Boolean.getBoolean("CopyCommon.DEBUG");
  32 
  33     public static final long KB = 1024;
  34     public static final long MB = KB * 1024;
  35     public static final long GB = MB * 1024;
  36 
  37     static final int SMALL_COPY_SIZE = 32;
  38     static final int BASE_ALIGNMENT = 16;
  39 
  40     private static final Unsafe UNSAFE;
  41 
  42     static {
  43         try {
  44             Field f = jdk.internal.misc.Unsafe.class.getDeclaredField("theUnsafe");
  45             f.setAccessible(true);
  46             UNSAFE = (jdk.internal.misc.Unsafe) f.get(null);
  47         } catch (Exception e) {
  48             throw new RuntimeException("Unable to get Unsafe instance.", e);
  49         }
  50     }
  51 
  52     static long alignDown(long value, long alignment) {
  53         return value & ~(alignment - 1);
  54     }
  55 
  56     static long alignUp(long value, long alignment) {
  57         return (value + alignment - 1) & ~(alignment - 1);
  58     }
  59 
  60     static boolean isAligned(long value, long alignment) {
  61         return value == alignDown(value, alignment);
  62     }
  63 
  64     CopyCommon() {
  65     }
  66 
  67     /**
  68      * Generate verification data for a given offset
  69      *
  70      * The verification data is used to verify that the correct bytes
  71      * have indeed been copied and byte swapped.
  72      *
  73      * The data is generated based on the offset (in bytes) into the
  74      * source buffer. For a native buffer the offset is relative to
  75      * the base pointer. For a heap array it is relative to the
  76      * address of the first array element.
  77      *
  78      * This method will return the result of doing an elementSize byte
  79      * read starting at offset (in bytes).
  80      *
  81      * @param offset offset into buffer
  82      * @param elemSize size (in bytes) of the element
  83      *
  84      * @return the verification data, only the least significant
  85      * elemSize*8 bits are set, zero extended
  86      */
  87     private long getVerificationDataForOffset(long offset, long elemSize) {
  88         byte[] bytes = new byte[(int)elemSize];
  89 
  90         for (long i = 0; i < elemSize; i++) {
  91             bytes[(int)i] = (byte)(offset + i);
  92         }
  93 
  94         long o = UNSAFE.arrayBaseOffset(byte[].class);
  95 
  96         switch ((int)elemSize) {
  97         case 1: return Byte.toUnsignedLong(UNSAFE.getByte(bytes, o));
  98         case 2: return Short.toUnsignedLong(UNSAFE.getShortUnaligned(bytes, o));
  99         case 4: return Integer.toUnsignedLong(UNSAFE.getIntUnaligned(bytes, o));
 100         case 8: return UNSAFE.getLongUnaligned(bytes, o);
 101         default: throw new IllegalArgumentException("Invalid element size: " + elemSize);
 102         }
 103     }
 104 
 105     /**
 106      * Verify byte swapped data
 107      *
 108      * @param ptr the data to verify
 109      * @param srcOffset the srcOffset (in bytes) from which the copy started,
 110      *        used as key to regenerate the verification data
 111      * @param dstOffset the offset (in bytes) in the array at which to start
 112      *        the verification, relative to the first element in the array
 113      * @param size size (in bytes) of data to to verify
 114      * @param elemSize size (in bytes) of the individual array elements
 115      *
 116      * @throws RuntimeException if an error is found
 117      */
 118     private void verifySwappedData(GenericPointer ptr, long srcOffset, long dstOffset, long size, long elemSize) {
 119         for (long offset = 0; offset < size; offset += elemSize) {
 120             long expectedUnswapped = getVerificationDataForOffset(srcOffset + offset, elemSize);
 121             long expected = byteSwap(expectedUnswapped, elemSize);
 122 
 123             long actual = getArrayElem(ptr, dstOffset + offset, elemSize);
 124 
 125             if (expected != actual) {
 126                 throw new RuntimeException("srcOffset: 0x" + Long.toHexString(srcOffset) +
 127                                            " dstOffset: 0x" + Long.toHexString(dstOffset) +
 128                                            " size: 0x" + Long.toHexString(size) +
 129                                            " offset: 0x" + Long.toHexString(offset) +
 130                                            " expectedUnswapped: 0x" + Long.toHexString(expectedUnswapped) +
 131                                            " expected: 0x" + Long.toHexString(expected) +
 132                                            " != actual: 0x" + Long.toHexString(actual));
 133             }
 134         }
 135     }
 136 
 137     /**
 138      * Initialize an array with verification friendly data
 139      *
 140      * @param ptr pointer to the data to initialize
 141      * @param size size (in bytes) of the data
 142      * @param elemSize size (in bytes) of the individual elements
 143      */
 144     private void initVerificationData(GenericPointer ptr, long size, long elemSize) {
 145         for (long offset = 0; offset < size; offset++) {
 146             byte data = (byte)getVerificationDataForOffset(offset, 1);
 147 
 148             if (ptr.isOnHeap()) {
 149                 UNSAFE.putByte(ptr.getObject(), ptr.getOffset() + offset, data);
 150             } else {
 151                 UNSAFE.putByte(ptr.getOffset() + offset, data);
 152             }
 153         }
 154     }
 155 
 156     /**
 157      * Allocate a primitive array
 158      *
 159      * @param size size (in bytes) of all the array elements (elemSize * length)
 160      * @param elemSize the size of the array elements
 161      *
 162      * @return a newly allocated primitive array
 163      */
 164     Object allocArray(long size, long elemSize) {
 165         int length = (int)(size / elemSize);
 166 
 167         switch ((int)elemSize) {
 168         case 1: return new byte[length];
 169         case 2: return new short[length];
 170         case 4: return new int[length];
 171         case 8: return new long[length];
 172         default:
 173             throw new IllegalArgumentException("Invalid element size: " + elemSize);
 174         }
 175     }
 176 
 177     /**
 178      * Get the value of a primitive array entry
 179      *
 180      * @param ptr pointer to the data
 181      * @param offset offset (in bytes) of the array element, relative to the first element in the array
 182      *
 183      * @return the array element, as an unsigned long
 184      */
 185     private long getArrayElem(GenericPointer ptr, long offset, long elemSize) {
 186         if (ptr.isOnHeap()) {
 187             Object o = ptr.getObject();
 188             int index = (int)(offset / elemSize);
 189 
 190             if (o instanceof short[]) {
 191                 short[] arr = (short[])o;
 192                 return Short.toUnsignedLong(arr[index]);
 193             } else if (o instanceof int[]) {
 194                 int[] arr = (int[])o;
 195                 return Integer.toUnsignedLong(arr[index]);
 196             } else if (o instanceof long[]) {
 197                 long[] arr = (long[])o;
 198                 return arr[index];
 199             } else {
 200                 throw new IllegalArgumentException("Invalid object type: " + o.getClass().getName());
 201             }
 202         } else {
 203             long addr = ptr.getOffset() + offset;
 204 
 205             switch ((int)elemSize) {
 206             case 1: return Byte.toUnsignedLong(UNSAFE.getByte(addr));
 207             case 2: return Short.toUnsignedLong(UNSAFE.getShortUnaligned(null, addr));
 208             case 4: return Integer.toUnsignedLong(UNSAFE.getIntUnaligned(null, addr));
 209             case 8: return UNSAFE.getLongUnaligned(null, addr);
 210             default: throw new IllegalArgumentException("Invalid element size: " + elemSize);
 211             }
 212         }
 213     }
 214 
 215     private void putValue(long addr, long elemSize, long value) {
 216         switch ((int)elemSize) {
 217         case 1: UNSAFE.putByte(addr, (byte)value); break;
 218         case 2: UNSAFE.putShortUnaligned(null, addr, (short)value); break;
 219         case 4: UNSAFE.putIntUnaligned(null, addr, (int)value); break;
 220         case 8: UNSAFE.putLongUnaligned(null, addr, value); break;
 221         default: throw new IllegalArgumentException("Invalid element size: " + elemSize);
 222         }
 223     }
 224 
 225     /**
 226      * Get the size of the elements for an array
 227      *
 228      * @param o a primitive heap array
 229      *
 230      * @return the size (in bytes) of the individual array elements
 231      */
 232     private long getArrayElemSize(Object o) {
 233         if (o instanceof short[]) {
 234             return 2;
 235         } else if (o instanceof int[]) {
 236             return 4;
 237         } else if (o instanceof long[]) {
 238             return 8;
 239         } else {
 240             throw new IllegalArgumentException("Invalid object type: " + o.getClass().getName());
 241         }
 242     }
 243 
 244     /**
 245      * Byte swap a value
 246      *
 247      * @param value the value to swap, only the bytes*8 least significant bits are used
 248      * @param size size (in bytes) of the value
 249      *
 250      * @return the byte swapped value in the bytes*8 least significant bits
 251      */
 252     private long byteSwap(long value, long size) {
 253         switch ((int)size) {
 254         case 2: return Short.toUnsignedLong(Short.reverseBytes((short)value));
 255         case 4: return Integer.toUnsignedLong(Integer.reverseBytes((int)value));
 256         case 8: return Long.reverseBytes(value);
 257         default: throw new IllegalArgumentException("Invalid element size: " + size);
 258         }
 259     }
 260 
 261     /**
 262      * Verify data which has *not* been byte swapped
 263      *
 264      * @param ptr the data to verify
 265      * @param startOffset the offset (in bytes) at which to start the verification
 266      * @param size size (in bytes) of the data to verify
 267      *
 268      * @throws RuntimeException if an error is found
 269      */
 270     private void verifyUnswappedData(GenericPointer ptr, long startOffset, long srcOffset, long size) {
 271         for (long i = 0; i < size; i++) {
 272             byte expected = (byte)getVerificationDataForOffset(srcOffset + i, 1);
 273 
 274             byte actual;
 275             if (ptr.isOnHeap()) {
 276                 actual = UNSAFE.getByte(ptr.getObject(), ptr.getOffset() + startOffset + i);
 277             } else {
 278                 actual = UNSAFE.getByte(ptr.getOffset() + startOffset + i);
 279             }
 280 
 281             if (expected != actual) {
 282                 throw new RuntimeException("startOffset: 0x" + Long.toHexString(startOffset) +
 283                                            " srcOffset: 0x" + Long.toHexString(srcOffset) +
 284                                            " size: 0x" + Long.toHexString(size) +
 285                                            " i: 0x" + Long.toHexString(i) +
 286                                            " expected: 0x" + Long.toHexString(expected) +
 287                                            " != actual: 0x" + Long.toHexString(actual));
 288             }
 289         }
 290     }
 291 
 292 
 293     /**
 294      * Copy and byte swap data from the source to the destination
 295      *
 296      * This method will pre-populate the whole source and destination
 297      * buffers with verification friendly data. It will then copy data
 298      * to fill part of the destination buffer with data from the
 299      * source, optionally byte swapping the copied elements on the
 300      * fly. Some space (padding) will be left before and after the
 301      * data in the destination buffer, which should not be
 302      * touched/overwritten by the copy call.
 303      *
 304      * Note: Both source and destination buffers will be overwritten!
 305      *
 306      * @param src source buffer to copy from
 307      * @param srcOffset the offset (in bytes) in the source buffer, relative to
 308      *        the first array element, at which to start reading data
 309      * @param dst destination buffer to copy to
 310      * @param dstOffset the offset (in bytes) in the destination
 311      *        buffer, relative to the first array element, at which to
 312      *        start writing data
 313      * @param bufSize the size (in bytes) of the src and dst arrays
 314      * @param copyBytes the size (in bytes) of the copy to perform,
 315      *        must be a multiple of elemSize
 316      * @param elemSize the size (in bytes) of the elements
 317      * @param swap true if elements should be byte swapped
 318      *
 319      * @throws RuntimeException if an error is found
 320      */
 321     void testCopyGeneric(GenericPointer src, long srcOffset,
 322                          GenericPointer dst, long dstOffset,
 323                          long bufSize, long copyBytes, long elemSize, boolean swap) {
 324         if (swap) {
 325             if (!isAligned(copyBytes, elemSize)) {
 326                 throw new IllegalArgumentException(
 327                     "copyBytes (" + copyBytes + ") must be a multiple of elemSize (" + elemSize + ")");
 328             }
 329             if (src.isOnHeap() && !isAligned(srcOffset, elemSize)) {
 330                 throw new IllegalArgumentException(
 331                     "srcOffset (" + srcOffset + ") must be a multiple of elemSize (" + elemSize + ")");
 332             }
 333             if (dst.isOnHeap() && !isAligned(dstOffset, elemSize)) {
 334                 throw new IllegalArgumentException(
 335                     "dstOffset (" + dstOffset + ") must be a multiple of elemSize (" + elemSize + ")");
 336             }
 337         }
 338 
 339         if (srcOffset + copyBytes > bufSize) {
 340             throw new IllegalArgumentException(
 341                 "srcOffset (" + srcOffset + ") + copyBytes (" + copyBytes + ") > bufSize (" + bufSize + ")");
 342         }
 343         if (dstOffset + copyBytes > bufSize) {
 344             throw new IllegalArgumentException(
 345                 "dstOffset (" + dstOffset + ") + copyBytes (" + copyBytes + ") > bufSize (" + bufSize + ")");
 346         }
 347 
 348         // Initialize the whole source buffer with a verification friendly pattern (no 0x00 bytes)
 349         initVerificationData(src, bufSize, elemSize);
 350         if (!src.equals(dst)) {
 351             initVerificationData(dst, bufSize, elemSize);
 352         }
 353 
 354         if (DEBUG) {
 355             System.out.println("===before===");
 356             for (int offset = 0; offset < bufSize; offset += elemSize) {
 357                 long srcValue = getArrayElem(src, offset, elemSize);
 358                 long dstValue = getArrayElem(dst, offset, elemSize);
 359 
 360                 System.out.println("offs=0x" + Long.toHexString(Integer.toUnsignedLong(offset)) +
 361                                  " src=0x" + Long.toHexString(srcValue) +
 362                                  " dst=0x" + Long.toHexString(dstValue));
 363             }
 364         }
 365 
 366         if (swap) {
 367             // Copy & swap data into the middle of the destination buffer
 368             UNSAFE.copySwapMemory(src.getObject(),
 369                                   src.getOffset() + srcOffset,
 370                                   dst.getObject(),
 371                                   dst.getOffset() + dstOffset,
 372                                   copyBytes,
 373                                   elemSize);
 374         } else {
 375             // Copy & swap data into the middle of the destination buffer
 376             UNSAFE.copyMemory(src.getObject(),
 377                               src.getOffset() + srcOffset,
 378                               dst.getObject(),
 379                               dst.getOffset() + dstOffset,
 380                               copyBytes);
 381         }
 382 
 383         if (DEBUG) {
 384             System.out.println("===after===");
 385             for (int offset = 0; offset < bufSize; offset += elemSize) {
 386                 long srcValue = getArrayElem(src, offset, elemSize);
 387                 long dstValue = getArrayElem(dst, offset, elemSize);
 388 
 389                 System.out.println("offs=0x" + Long.toHexString(Integer.toUnsignedLong(offset)) +
 390                                  " src=0x" + Long.toHexString(srcValue) +
 391                                  " dst=0x" + Long.toHexString(dstValue));
 392             }
 393         }
 394 
 395         // Verify the the front padding is unchanged
 396         verifyUnswappedData(dst, 0, 0, dstOffset);
 397 
 398         if (swap) {
 399             // Verify swapped data
 400             verifySwappedData(dst, srcOffset, dstOffset, copyBytes, elemSize);
 401         } else {
 402             // Verify copied/unswapped data
 403             verifyUnswappedData(dst, dstOffset, srcOffset, copyBytes);
 404         }
 405 
 406         // Verify that the back padding is unchanged
 407         long frontAndCopyBytes = dstOffset + copyBytes;
 408         long trailingBytes = bufSize - frontAndCopyBytes;
 409         verifyUnswappedData(dst, frontAndCopyBytes, frontAndCopyBytes, trailingBytes);
 410     }
 411 
 412     /**
 413      * Test various configurations of copying and optionally swapping data
 414      *
 415      * @param src the source buffer to copy from
 416      * @param dst the destination buffer to copy to
 417      * @param size size (in bytes) of the buffers
 418      * @param elemSize size (in bytes) of the individual elements
 419      *
 420      * @throws RuntimeException if an error is found
 421      */
 422     public void testBufferPair(GenericPointer src, GenericPointer dst, long size, long elemSize, boolean swap) {
 423         // offset in source from which to start reading data
 424         for (long srcOffset = 0; srcOffset < size; srcOffset += (src.isOnHeap() ? elemSize : 1)) {
 425 
 426             // offset in destination at which to start writing data
 427             for (int dstOffset = 0; dstOffset < size; dstOffset += (dst.isOnHeap() ? elemSize : 1)) {
 428 
 429                 // number of bytes to copy
 430                 long maxCopyBytes = Math.min(size - srcOffset, size - dstOffset);
 431                 for (long copyBytes = 0; copyBytes < maxCopyBytes; copyBytes += elemSize) {
 432                     try {
 433                         testCopyGeneric(src, srcOffset, dst, dstOffset, size, copyBytes, elemSize, swap);
 434                     } catch (RuntimeException e) {
 435                         // Wrap the exception in another exception to catch the relevant configuration data
 436                         throw new RuntimeException("testBufferPair: " +
 437                                                    "src=" + src +
 438                                                    " dst=" + dst +
 439                                                    " elemSize=0x" + Long.toHexString(elemSize) +
 440                                                    " copyBytes=0x" + Long.toHexString(copyBytes) +
 441                                                    " srcOffset=0x" + Long.toHexString(srcOffset) +
 442                                                    " dstOffset=0x" + Long.toHexString(dstOffset) +
 443                                                    " swap=" + swap,
 444                                                    e);
 445                     }
 446                 }
 447             }
 448         }
 449     }
 450 
 451     /**
 452      * Test copying between various permutations of buffers
 453      *
 454      * @param buffers buffers to permute (src x dst)
 455      * @param size size (in bytes) of buffers
 456      * @param elemSize size (in bytes) of individual elements
 457      *
 458      * @throws RuntimeException if an error is found
 459      */
 460     public void testPermuteBuffers(GenericPointer[] buffers, long size, long elemSize, boolean swap) {
 461         System.out.println("testPermuteBuffers(buffers, " + size + ", " + elemSize + ", " + swap + ")");
 462         for (int srcIndex = 0; srcIndex < buffers.length; srcIndex++) {
 463             for (int dstIndex = 0; dstIndex < buffers.length; dstIndex++) {
 464                 testBufferPair(buffers[srcIndex], buffers[dstIndex], size, elemSize, swap);
 465             }
 466         }
 467     }
 468 
 469     /**
 470      * Test copying of a specific element size
 471      *
 472      * @param size size (in bytes) of buffers to allocate
 473      * @param elemSize size (in bytes) of individual elements
 474      *
 475      * @throws RuntimeException if an error is found
 476      */
 477     private void testElemSize(long size, long elemSize, boolean swap) {
 478         long buf1Raw = 0;
 479         long buf2Raw = 0;
 480 
 481         try {
 482             buf1Raw = UNSAFE.allocateMemory(size + BASE_ALIGNMENT);
 483             long buf1 = alignUp(buf1Raw, BASE_ALIGNMENT);
 484 
 485             buf2Raw = UNSAFE.allocateMemory(size + BASE_ALIGNMENT);
 486             long buf2 = alignUp(buf2Raw, BASE_ALIGNMENT);
 487 
 488             GenericPointer[] buffers = {
 489                 new GenericPointer(buf1),
 490                 new GenericPointer(buf2),
 491                 new GenericPointer(allocArray(size, elemSize)),
 492                 new GenericPointer(allocArray(size, elemSize))
 493             };
 494 
 495             testPermuteBuffers(buffers, size, elemSize, swap);
 496         } finally {
 497             if (buf1Raw != 0) {
 498                 UNSAFE.freeMemory(buf1Raw);
 499             }
 500             if (buf2Raw != 0) {
 501                 UNSAFE.freeMemory(buf2Raw);
 502             }
 503         }
 504     }
 505 
 506     /**
 507      * Verify that small copies work
 508      */
 509     void testSmallCopy(boolean swap) {
 510         int smallBufSize = SMALL_COPY_SIZE;
 511         int minElemSize = swap ? 2 : 1;
 512         int maxElemSize = swap ? 8 : 1;
 513 
 514         // Test various element types and heap/native combinations
 515         for (long elemSize = minElemSize; elemSize <= maxElemSize; elemSize <<= 1) {
 516             testElemSize(smallBufSize, elemSize, swap);
 517         }
 518     }
 519 
 520 
 521     /**
 522      * Verify that large copies work
 523      */
 524     void testLargeCopy(boolean swap) {
 525         long size = 2 * GB + 8;
 526         long bufRaw = 0;
 527 
 528         // Check that a large native copy succeeds
 529         try {
 530             try {
 531                 bufRaw = UNSAFE.allocateMemory(size + BASE_ALIGNMENT);
 532             } catch (OutOfMemoryError e) {
 533                 // Accept failure, skip test
 534                 return;
 535             }
 536 
 537             long buf = alignUp(bufRaw, BASE_ALIGNMENT);
 538 
 539             if (swap) {
 540                 UNSAFE.copySwapMemory(null, buf, null, buf, size, 8);
 541             } else {
 542                 UNSAFE.copyMemory(null, buf, null, buf, size);
 543             }
 544         } catch (Exception e) {
 545             throw new RuntimeException("copy of large buffer failed (swap=" + swap + ")");
 546         } finally {
 547             if (bufRaw != 0) {
 548                 UNSAFE.freeMemory(bufRaw);
 549             }
 550         }
 551     }
 552 
 553     /**
 554      * Helper class to represent a "pointer" - either a heap array or
 555      * a pointer to a native buffer.
 556      *
 557      * In the case of a native pointer, the Object is null and the offset is
 558      * the absolute address of the native buffer.
 559      *
 560      * In the case of a heap object, the Object is a primitive array, and
 561      * the offset will be set to the base offset to the first element, meaning
 562      * the object and the offset together form a double-register pointer.
 563      */
 564     static class GenericPointer {
 565         private final Object o;
 566         private final long offset;
 567 
 568         private GenericPointer(Object o, long offset) {
 569             this.o = o;
 570             this.offset = offset;
 571         }
 572 
 573         public String toString() {
 574             return "GenericPointer(o={" + o + "}, offset=0x" + Long.toHexString(offset) + ")";
 575         }
 576 
 577         public boolean equals(Object other) {
 578             if (!(other instanceof GenericPointer)) {
 579                 return false;
 580             }
 581 
 582             GenericPointer otherp = (GenericPointer)other;
 583 
 584             return o == otherp.o && offset == otherp.offset;
 585         }
 586 
 587         GenericPointer(Object o) {
 588             this(o, UNSAFE.arrayBaseOffset(o.getClass()));
 589         }
 590 
 591         GenericPointer(long offset) {
 592             this(null, offset);
 593         }
 594 
 595         public boolean isOnHeap() {
 596             return o != null;
 597         }
 598 
 599         public Object getObject() {
 600             return o;
 601         }
 602 
 603         public long getOffset() {
 604             return offset;
 605         }
 606     }
 607 }