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