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