1 /*
  2  * Copyright (c) 2021, 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.  Oracle designates this
  8  * particular file as subject to the "Classpath" exception as provided
  9  * by Oracle in the LICENSE file that accompanied this code.
 10  *
 11  * This code is distributed in the hope that it will be useful, but WITHOUT
 12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 14  * version 2 for more details (a copy is included in the LICENSE file that
 15  * accompanied this code).
 16  *
 17  * You should have received a copy of the GNU General Public License version
 18  * 2 along with this work; if not, write to the Free Software Foundation,
 19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 20  *
 21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 22  * or visit www.oracle.com if you need additional information or have any
 23  * questions.
 24  */
 25 
 26 package jdk.incubator.foreign;
 27 
 28 import jdk.internal.foreign.ArenaAllocator;
 29 import jdk.internal.foreign.AbstractMemorySegmentImpl;
 30 import jdk.internal.foreign.ResourceScopeImpl;
 31 import jdk.internal.foreign.Utils;
 32 
 33 import java.lang.invoke.VarHandle;
 34 import java.lang.reflect.Array;
 35 import java.nio.ByteOrder;
 36 import java.util.Objects;
 37 import java.util.function.Function;
 38 import java.util.stream.Stream;
 39 
 40 /**
 41  * This interface models a memory allocator. Clients implementing this interface
 42  * must implement the {@link #allocate(long, long)} method. This interface defines several default methods
 43  * which can be useful to create segments from several kinds of Java values such as primitives and arrays.
 44  * This interface can be seen as a thin wrapper around the basic capabilities for creating native segments
 45  * (e.g. {@link MemorySegment#allocateNative(long, long, ResourceScope)}); since {@link SegmentAllocator} is a <em>functional interface</em>,
 46  * clients can easily obtain a native allocator by using either a lambda expression or a method reference.
 47  * <p>
 48  * This interface provides a factory, namely {@link SegmentAllocator#ofScope(ResourceScope)} which can be used to obtain
 49  * a <em>scoped</em> allocator, that is, an allocator which creates segment bound by a given scope. This can be useful
 50  * when working inside a <em>try-with-resources</em> construct:
 51  *
 52  * <blockquote><pre>{@code
 53 try (ResourceScope scope = ResourceScope.newConfinedScope()) {
 54    SegmentAllocator allocator = SegmentAllocator.ofScope(scope);
 55    ...
 56 }
 57  * }</pre></blockquote>
 58  *
 59  * In addition, this interface also defines factories for commonly used allocators; for instance {@link #arenaAllocator(ResourceScope)}
 60  * and {@link #arenaAllocator(long, ResourceScope)} are arena-style native allocators. Finally {@link #ofSegment(MemorySegment)}
 61  * returns an allocator which wraps a segment (either on-heap or off-heap) and recycles its content upon each new allocation request.
 62  */
 63 @FunctionalInterface
 64 public interface SegmentAllocator {
 65 
 66     /**
 67      * Allocate a block of memory with given layout and initialize it with given byte value.
 68      * @implSpec the default implementation for this method calls {@code this.allocate(layout)}.
 69      * @param layout the layout of the block of memory to be allocated.
 70      * @param value the value to be set on the newly allocated memory block.
 71      * @return a segment for the newly allocated memory block.
 72      * @throws IllegalArgumentException if {@code layout.byteSize()} does not conform to the size of a byte value.
 73      */
 74     default MemorySegment allocate(ValueLayout layout, byte value) {
 75         Objects.requireNonNull(layout);
 76         VarHandle handle = layout.varHandle(byte.class);
 77         MemorySegment addr = allocate(layout);
 78         handle.set(addr, value);
 79         return addr;
 80     }
 81 
 82     /**
 83      * Allocate a block of memory with given layout and initialize it with given char value.
 84      * @implSpec the default implementation for this method calls {@code this.allocate(layout)}.
 85      * @param layout the layout of the block of memory to be allocated.
 86      * @param value the value to be set on the newly allocated memory block.
 87      * @return a segment for the newly allocated memory block.
 88      * @throws IllegalArgumentException if {@code layout.byteSize()} does not conform to the size of a char value.
 89      */
 90     default MemorySegment allocate(ValueLayout layout, char value) {
 91         Objects.requireNonNull(layout);
 92         VarHandle handle = layout.varHandle(char.class);
 93         MemorySegment addr = allocate(layout);
 94         handle.set(addr, value);
 95         return addr;
 96     }
 97 
 98     /**
 99      * Allocate a block of memory with given layout and initialize it with given short value.
100      * @implSpec the default implementation for this method calls {@code this.allocate(layout)}.
101      * @param layout the layout of the block of memory to be allocated.
102      * @param value the value to be set on the newly allocated memory block.
103      * @return a segment for the newly allocated memory block.
104      * @throws IllegalArgumentException if {@code layout.byteSize()} does not conform to the size of a short value.
105      */
106     default MemorySegment allocate(ValueLayout layout, short value) {
107         Objects.requireNonNull(layout);
108         VarHandle handle = layout.varHandle(short.class);
109         MemorySegment addr = allocate(layout);
110         handle.set(addr, value);
111         return addr;
112     }
113 
114     /**
115      * Allocate a block of memory with given layout and initialize it with given int value.
116      * @implSpec the default implementation for this method calls {@code this.allocate(layout)}.
117      * @param layout the layout of the block of memory to be allocated.
118      * @param value the value to be set on the newly allocated memory block.
119      * @return a segment for the newly allocated memory block.
120      * @throws IllegalArgumentException if {@code layout.byteSize()} does not conform to the size of a int value.
121      */
122     default MemorySegment allocate(ValueLayout layout, int value) {
123         Objects.requireNonNull(layout);
124         VarHandle handle = layout.varHandle(int.class);
125         MemorySegment addr = allocate(layout);
126         handle.set(addr, value);
127         return addr;
128     }
129 
130     /**
131      * Allocate a block of memory with given layout and initialize it with given float value.
132      * @implSpec the default implementation for this method calls {@code this.allocate(layout)}.
133      * @param layout the layout of the block of memory to be allocated.
134      * @param value the value to be set on the newly allocated memory block.
135      * @return a segment for the newly allocated memory block.
136      * @throws IllegalArgumentException if {@code layout.byteSize()} does not conform to the size of a float value.
137      */
138     default MemorySegment allocate(ValueLayout layout, float value) {
139         Objects.requireNonNull(layout);
140         VarHandle handle = layout.varHandle(float.class);
141         MemorySegment addr = allocate(layout);
142         handle.set(addr, value);
143         return addr;
144     }
145 
146     /**
147      * Allocate a block of memory with given layout and initialize it with given long value.
148      * @implSpec the default implementation for this method calls {@code this.allocate(layout)}.
149      * @param layout the layout of the block of memory to be allocated.
150      * @param value the value to be set on the newly allocated memory block.
151      * @return a segment for the newly allocated memory block.
152      * @throws IllegalArgumentException if {@code layout.byteSize()} does not conform to the size of a long value.
153      */
154     default MemorySegment allocate(ValueLayout layout, long value) {
155         Objects.requireNonNull(layout);
156         VarHandle handle = layout.varHandle(long.class);
157         MemorySegment addr = allocate(layout);
158         handle.set(addr, value);
159         return addr;
160     }
161 
162     /**
163      * Allocate a block of memory with given layout and initialize it with given double value.
164      * @implSpec the default implementation for this method calls {@code this.allocate(layout)}.
165      * @param layout the layout of the block of memory to be allocated.
166      * @param value the value to be set on the newly allocated memory block.
167      * @return a segment for the newly allocated memory block.
168      * @throws IllegalArgumentException if {@code layout.byteSize()} does not conform to the size of a double value.
169      */
170     default MemorySegment allocate(ValueLayout layout, double value) {
171         Objects.requireNonNull(layout);
172         VarHandle handle = layout.varHandle(double.class);
173         MemorySegment addr = allocate(layout);
174         handle.set(addr, value);
175         return addr;
176     }
177 
178     /**
179      * Allocate a block of memory with given layout and initialize it with given address value
180      * (expressed as an {@link Addressable} instance).
181      * The address value might be narrowed according to the platform address size (see {@link MemoryLayouts#ADDRESS}).
182      * @implSpec the default implementation for this method calls {@code this.allocate(layout)}.
183      * @param layout the layout of the block of memory to be allocated.
184      * @param value the value to be set on the newly allocated memory block.
185      * @return a segment for the newly allocated memory block.
186      * @throws IllegalArgumentException if {@code layout.byteSize() != MemoryLayouts.ADDRESS.byteSize()}.
187      */
188     default MemorySegment allocate(ValueLayout layout, Addressable value) {
189         Objects.requireNonNull(value);
190         Objects.requireNonNull(layout);
191         if (MemoryLayouts.ADDRESS.byteSize() != layout.byteSize()) {
192             throw new IllegalArgumentException("Layout size mismatch - " + layout.byteSize() + " != " + MemoryLayouts.ADDRESS.byteSize());
193         }
194         return switch ((int)layout.byteSize()) {
195             case 4 -> allocate(layout, (int)value.address().toRawLongValue());
196             case 8 -> allocate(layout, value.address().toRawLongValue());
197             default -> throw new UnsupportedOperationException("Unsupported pointer size"); // should not get here
198         };
199     }
200 
201     /**
202      * Allocate a block of memory with given layout and initialize it with given byte array.
203      * @implSpec the default implementation for this method calls {@code this.allocateArray(layout, array.length)}.
204      * @param elementLayout the element layout of the array to be allocated.
205      * @param array the array to be copied on the newly allocated memory block.
206      * @return a segment for the newly allocated memory block.
207      * @throws IllegalArgumentException if {@code elementLayout.byteSize()} does not conform to the size of a byte value.
208      */
209     default MemorySegment allocateArray(ValueLayout elementLayout, byte[] array) {
210         return copyArrayWithSwapIfNeeded(array, elementLayout, MemorySegment::ofArray);
211     }
212 
213     /**
214      * Allocate a block of memory with given layout and initialize it with given short array.
215      * @implSpec the default implementation for this method calls {@code this.allocateArray(layout, array.length)}.
216      * @param elementLayout the element layout of the array to be allocated.
217      * @param array the array to be copied on the newly allocated memory block.
218      * @return a segment for the newly allocated memory block.
219      * @throws IllegalArgumentException if {@code elementLayout.byteSize()} does not conform to the size of a short value.
220      */
221     default MemorySegment allocateArray(ValueLayout elementLayout, short[] array) {
222         return copyArrayWithSwapIfNeeded(array, elementLayout, MemorySegment::ofArray);
223     }
224 
225     /**
226      * Allocate a block of memory with given layout and initialize it with given char array.
227      * @implSpec the default implementation for this method calls {@code this.allocateArray(layout, array.length)}.
228      * @param elementLayout the element layout of the array to be allocated.
229      * @param array the array to be copied on the newly allocated memory block.
230      * @return a segment for the newly allocated memory block.
231      * @throws IllegalArgumentException if {@code elementLayout.byteSize()} does not conform to the size of a char value.
232      */
233     default MemorySegment allocateArray(ValueLayout elementLayout, char[] array) {
234         return copyArrayWithSwapIfNeeded(array, elementLayout, MemorySegment::ofArray);
235     }
236 
237     /**
238      * Allocate a block of memory with given layout and initialize it with given int array.
239      * @implSpec the default implementation for this method calls {@code this.allocateArray(layout, array.length)}.
240      * @param elementLayout the element layout of the array to be allocated.
241      * @param array the array to be copied on the newly allocated memory block.
242      * @return a segment for the newly allocated memory block.
243      * @throws IllegalArgumentException if {@code elementLayout.byteSize()} does not conform to the size of a int value.
244      */
245     default MemorySegment allocateArray(ValueLayout elementLayout, int[] array) {
246         return copyArrayWithSwapIfNeeded(array, elementLayout, MemorySegment::ofArray);
247     }
248 
249     /**
250      * Allocate a block of memory with given layout and initialize it with given float array.
251      * @implSpec the default implementation for this method calls {@code this.allocateArray(layout, array.length)}.
252      * @param elementLayout the element layout of the array to be allocated.
253      * @param array the array to be copied on the newly allocated memory block.
254      * @return a segment for the newly allocated memory block.
255      * @throws IllegalArgumentException if {@code elementLayout.byteSize()} does not conform to the size of a float value.
256      */
257     default MemorySegment allocateArray(ValueLayout elementLayout, float[] array) {
258         return copyArrayWithSwapIfNeeded(array, elementLayout, MemorySegment::ofArray);
259     }
260 
261     /**
262      * Allocate a block of memory with given layout and initialize it with given long array.
263      * @implSpec the default implementation for this method calls {@code this.allocateArray(layout, array.length)}.
264      * @param elementLayout the element layout of the array to be allocated.
265      * @param array the array to be copied on the newly allocated memory block.
266      * @return a segment for the newly allocated memory block.
267      * @throws IllegalArgumentException if {@code elementLayout.byteSize()} does not conform to the size of a long value.
268      */
269     default MemorySegment allocateArray(ValueLayout elementLayout, long[] array) {
270         return copyArrayWithSwapIfNeeded(array, elementLayout, MemorySegment::ofArray);
271     }
272 
273     /**
274      * Allocate a block of memory with given layout and initialize it with given double array.
275      * @implSpec the default implementation for this method calls {@code this.allocateArray(layout, array.length)}.
276      * @param elementLayout the element layout of the array to be allocated.
277      * @param array the array to be copied on the newly allocated memory block.
278      * @return a segment for the newly allocated memory block.
279      * @throws IllegalArgumentException if {@code elementLayout.byteSize()} does not conform to the size of a double value.
280      */
281     default MemorySegment allocateArray(ValueLayout elementLayout, double[] array) {
282         return copyArrayWithSwapIfNeeded(array, elementLayout, MemorySegment::ofArray);
283     }
284 
285     /**
286      * Allocate a block of memory with given layout and initialize it with given address array.
287      * The address value of each array element might be narrowed according to the platform address size (see {@link MemoryLayouts#ADDRESS}).
288      * @implSpec the default implementation for this method calls {@code this.allocateArray(layout, array.length)}.
289      * @param elementLayout the element layout of the array to be allocated.
290      * @param array the array to be copied on the newly allocated memory block.
291      * @return a segment for the newly allocated memory block.
292      * @throws IllegalArgumentException if {@code layout.byteSize() != MemoryLayouts.ADDRESS.byteSize()}.
293      */
294     default MemorySegment allocateArray(ValueLayout elementLayout, Addressable[] array) {
295         Objects.requireNonNull(elementLayout);
296         Objects.requireNonNull(array);
297         Stream.of(array).forEach(Objects::requireNonNull);
298         if (MemoryLayouts.ADDRESS.byteSize() != elementLayout.byteSize()) {
299             throw new IllegalArgumentException("Layout size mismatch - " + elementLayout.byteSize() + " != " + MemoryLayouts.ADDRESS.byteSize());
300         }
301         return switch ((int)elementLayout.byteSize()) {
302             case 4 -> copyArrayWithSwapIfNeeded(Stream.of(array)
303                             .mapToInt(a -> (int)a.address().toRawLongValue()).toArray(),
304                     elementLayout, MemorySegment::ofArray);
305             case 8 -> copyArrayWithSwapIfNeeded(Stream.of(array)
306                             .mapToLong(a -> a.address().toRawLongValue()).toArray(),
307                     elementLayout, MemorySegment::ofArray);
308             default -> throw new UnsupportedOperationException("Unsupported pointer size"); // should not get here
309         };
310     }
311 
312     private <Z> MemorySegment copyArrayWithSwapIfNeeded(Z array, ValueLayout elementLayout,
313                                                         Function<Z, MemorySegment> heapSegmentFactory) {
314         Objects.requireNonNull(array);
315         Objects.requireNonNull(elementLayout);
316         Utils.checkPrimitiveCarrierCompat(array.getClass().componentType(), elementLayout);
317         MemorySegment addr = allocate(MemoryLayout.sequenceLayout(Array.getLength(array), elementLayout));
318         if (elementLayout.byteSize() == 1 || (elementLayout.order() == ByteOrder.nativeOrder())) {
319             addr.copyFrom(heapSegmentFactory.apply(array));
320         } else {
321             ((AbstractMemorySegmentImpl)addr).copyFromSwap(heapSegmentFactory.apply(array), elementLayout.byteSize());
322         }
323         return addr;
324     }
325 
326     /**
327      * Allocate a block of memory  with given layout.
328      * @implSpec the default implementation for this method calls {@code this.allocate(layout.byteSize(), layout.byteAlignment())}.
329      * @param layout the layout of the block of memory to be allocated.
330      * @return a segment for the newly allocated memory block.
331      */
332     default MemorySegment allocate(MemoryLayout layout) {
333         Objects.requireNonNull(layout);
334         return allocate(layout.byteSize(), layout.byteAlignment());
335     }
336 
337     /**
338      * Allocate a block of memory corresponding to an array with given element layout and size.
339      * @implSpec the default implementation for this method calls {@code this.allocate(MemoryLayout.sequenceLayout(count, elementLayout))}.
340      * @param elementLayout the array element layout.
341      * @param count the array element count.
342      * @return a segment for the newly allocated memory block.
343      */
344     default MemorySegment allocateArray(MemoryLayout elementLayout, long count) {
345         Objects.requireNonNull(elementLayout);
346         return allocate(MemoryLayout.sequenceLayout(count, elementLayout));
347     }
348 
349     /**
350      * Allocate a block of memory with given size, with default alignment (1-byte aligned).
351      * @implSpec the default implementation for this method calls {@code this.allocate(bytesSize, 1)}.
352      * @param bytesSize the size (in bytes) of the block of memory to be allocated.
353      * @return a segment for the newly allocated memory block.
354      */
355     default MemorySegment allocate(long bytesSize) {
356         return allocate(bytesSize, 1);
357     }
358 
359     /**
360      * Allocate a block of memory  with given size and alignment constraint.
361      * @param bytesSize the size (in bytes) of the block of memory to be allocated.
362      * @param bytesAlignment the alignment (in bytes) of the block of memory to be allocated.
363      * @return a segment for the newly allocated memory block.
364      */
365     MemorySegment allocate(long bytesSize, long bytesAlignment);
366 
367     /**
368      * Returns a native arena-based allocator which allocates a single memory segment, of given size (using malloc),
369      * and then responds to allocation request by returning different slices of that same segment
370      * (until no further allocation is possible).
371      * This can be useful when clients want to perform multiple allocation requests while avoiding the cost associated
372      * with allocating a new off-heap memory region upon each allocation request.
373      * <p>
374      * An allocator associated with a <em>shared</em> resource scope is thread-safe and allocation requests may be
375      * performed concurrently; conversely, if the arena allocator is associated with a <em>confined</em> resource scope,
376      * allocation requests can only occur from the thread owning the allocator's resource scope.
377      * <p>
378      * The returned allocator might throw an {@link OutOfMemoryError} if an incoming allocation request exceeds
379      * the allocator capacity.
380      *
381      * @param size the size (in bytes) of the allocation arena.
382      * @param scope the scope associated with the segments returned by this allocator.
383      * @return a new bounded arena-based allocator
384      * @throws IllegalArgumentException if {@code size <= 0}.
385      * @throws IllegalStateException if {@code scope} has been already closed, or if access occurs from a thread other
386      * than the thread owning {@code scope}.
387      */
388     static SegmentAllocator arenaAllocator(long size, ResourceScope scope) {
389         Objects.requireNonNull(scope);
390         return scope.ownerThread() == null ?
391                 new ArenaAllocator.BoundedSharedArenaAllocator(scope, size) :
392                 new ArenaAllocator.BoundedArenaAllocator(scope, size);
393     }
394 
395     /**
396      * Returns a native unbounded arena-based allocator.
397      * <p>
398      * The returned allocator allocates a memory segment {@code S} of a certain fixed size (using malloc) and then
399      * responds to allocation requests in one of the following ways:
400      * <ul>
401      *     <li>if the size of the allocation requests is smaller than the size of {@code S}, and {@code S} has a <em>free</em>
402      *     slice {@code S'} which fits that allocation request, return that {@code S'}.
403      *     <li>if the size of the allocation requests is smaller than the size of {@code S}, and {@code S} has no <em>free</em>
404      *     slices which fits that allocation request, allocate a new segment {@code S'} (using malloc), which has same size as {@code S}
405      *     and set {@code S = S'}; the allocator then tries to respond to the same allocation request again.
406      *     <li>if the size of the allocation requests is bigger than the size of {@code S}, allocate a new segment {@code S'}
407      *     (using malloc), which has a sufficient size to satisfy the allocation request, and return {@code S'}.
408      * </ul>
409      * <p>
410      * This segment allocator can be useful when clients want to perform multiple allocation requests while avoiding the
411      * cost associated with allocating a new off-heap memory region upon each allocation request.
412      * <p>
413      * An allocator associated with a <em>shared</em> resource scope is thread-safe and allocation requests may be
414      * performed concurrently; conversely, if the arena allocator is associated with a <em>confined</em> resource scope,
415      * allocation requests can only occur from the thread owning the allocator's resource scope.
416      * <p>
417      * The returned allocator might throw an {@link OutOfMemoryError} if an incoming allocation request exceeds
418      * the system capacity.
419      *
420      * @param scope the scope associated with the segments returned by this allocator.
421      * @return a new unbounded arena-based allocator
422      * @throws IllegalStateException if {@code scope} has been already closed, or if access occurs from a thread other
423      * than the thread owning {@code scope}.
424      */
425     static SegmentAllocator arenaAllocator(ResourceScope scope) {
426         Objects.requireNonNull(scope);
427         return scope.ownerThread() == null ?
428                 new ArenaAllocator.UnboundedSharedArenaAllocator(scope) :
429                 new ArenaAllocator.UnboundedArenaAllocator(scope);
430     }
431 
432     /**
433      * Returns a segment allocator which responds to allocation requests by recycling a single segment; that is,
434      * each new allocation request will return a new slice starting at the segment offset {@code 0} (alignment
435      * constraints are ignored by this allocator). This can be useful to limit allocation requests in case a client
436      * knows that they have fully processed the contents of the allocated segment before the subsequent allocation request
437      * takes place.
438      * <p>
439      * While the allocator returned by this method is <em>thread-safe</em>, concurrent access on the same recycling
440      * allocator might cause a thread to overwrite contents written to the underlying segment by a different thread.
441      *
442      * @param segment the memory segment to be recycled by the returned allocator.
443      * @return an allocator which recycles an existing segment upon each new allocation request.
444      */
445     static SegmentAllocator ofSegment(MemorySegment segment) {
446         Objects.requireNonNull(segment);
447         return (size, align) -> segment.asSlice(0, size);
448     }
449 
450     /**
451      * Returns a native allocator which responds to allocation requests by allocating new segments
452      * bound by the given resource scope, using the {@link MemorySegment#allocateNative(long, long, ResourceScope)}
453      * factory. This code is equivalent (but likely more efficient) to the following:
454      * <blockquote><pre>{@code
455     Resource scope = ...
456     SegmentAllocator scoped = (size, align) -> MemorySegment.allocateNative(size, align, scope);
457      * }</pre></blockquote>
458      *
459      * @param scope the resource scope associated with the segments created by the returned allocator.
460      * @return an allocator which allocates new memory segment bound by the provided resource scope.
461      */
462     static SegmentAllocator ofScope(ResourceScope scope) {
463         Objects.requireNonNull(scope);
464         return (ResourceScopeImpl)scope;
465     }
466 }