Skip to content

Commit 640028d

Browse files
Implement java.nio.ByteBuffers backed by unsafe.Ptr (scala-native#3532)
* Replace existing copy-pasted java.nio classes with code generated from templates * Implement java.nio.PointerByteBuffer backed by unsafe.Ptr * Add javalib-intf project used to provide extended javalib API * Add PointerByteBuffer tests * Use common _offset for all types of Buffer implementation
1 parent 5b617a4 commit 640028d

File tree

59 files changed

+4575
-2316
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+4575
-2316
lines changed

build.sbt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ lazy val clib = Build.clib
1717
lazy val posixlib = Build.posixlib
1818
lazy val windowslib = Build.windowslib
1919
lazy val javalib = Build.javalib
20+
lazy val javalibintf = Build.javalibintf
2021
lazy val javalibExtDummies = Build.javalibExtDummies
2122
lazy val auxlib = Build.auxlib
2223
lazy val scalalib = Build.scalalib
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package scala.scalanative.javalibintf;
2+
3+
import java.nio.ByteBuffer;
4+
import java.nio.Buffer;
5+
6+
/**
7+
* Utilities to interface {@link java.nio.Buffer}s and Scala Native Ptr[_].
8+
*
9+
* <p>{@link java.nio.Buffer}s can be <em>direct</em> buffers or
10+
* <em>indirect</em> buffers. Indirect buffers use an underlying array (like
11+
* {@code int[]} in Java or {@code Array[Int]} in Scala). Direct buffers are
12+
* supposed to use off-heap memory.
13+
*
14+
* <p>In a Scala Native environment, the equivalent of off-heap memory for
15+
* buffers of primitive numeric types can be access via pointers.
16+
*
17+
* <p>This class provides methods to wrap Ptr[_] as direct Buffers, and
18+
* extract references to TypedArrays from direct Buffers.
19+
*/
20+
public final class PointerBuffer {
21+
private PointerBuffer() {}
22+
23+
/**
24+
* Wraps a ScalaNative {@code Ptr[Byte]} as a direct
25+
* {@link java.nio.ByteBuffer}.
26+
*
27+
* <p>The provided {@code ptr} and ${@code size} parametesr must be a valid Scala Native
28+
* {@code Ptr[Byte]} and size of referenced memory expressed in bytes,
29+
* otherwise the behavior of this method is not specified.
30+
*
31+
* <p>The returned {@link java.nio.ByteBuffer} has the following properties:
32+
*
33+
* <ul>
34+
* <li>It has a {@code capacity()} equal to the {@code size}.</li>
35+
* <li>Its initial {@code position()} is 0 and its {@code limit()} is its capacity.</li>
36+
* <li>It is a direct buffer backed by the provided {@code Ptr[Byte]}:
37+
* changes to one are reflected on the other.</li>
38+
* </ul>
39+
*
40+
* @param ptr a ScalaNative {@code Ptr[_]}
41+
* @param size size of memory chunk passed by @param ptr
42+
* @return direct ByteBuffer backed by @param ptr and capacity/limit of @param size
43+
*/
44+
public static final ByteBuffer wrapPointerByte(Object ptr, int size) {
45+
throw new AssertionError("stub");
46+
}
47+
48+
/**
49+
* Tests whether the given {@link java.nio.Buffer} is backed by an accessible
50+
* Scala Native {@code Ptr[_]}.
51+
*
52+
* <p>In particular, it is true for all {@link java.nio.Buffer}s created with
53+
* any of the {@code wrapPointerX} methods of this class.
54+
*
55+
* <p>If this method returns {@code true}, then {@code pointer(buffer)}
56+
* does not throw any {@link UnsupportedOperationException}.
57+
*
58+
* @param buffer Any valid {@link Buffer} instance
59+
* @return
60+
* true if and only if the provided {@code buffer} is backed by an
61+
* accessible ScalaNative {@code Ptr[_]}
62+
*
63+
* @see PointerBuffer#pointer(Buffer)
64+
*/
65+
public static final boolean hasPointer(Buffer buffer) {
66+
throw new AssertionError("stub");
67+
}
68+
69+
/**
70+
* Returns a ScalaNative {@code Ptr[_]} view of the provided
71+
* {@link java.nio.Buffer}.
72+
*
73+
* @param buffer Any valid {@link Buffer} instance
74+
* @return
75+
* a ScalaNative {@code Ptr[_]} view of the provided {@code buffer}
76+
*
77+
* @throws UnsupportedOperationException
78+
* if the provided {@code buffer} is read-only or is not backed by a
79+
* ScalaNative {@code Ptr[_]}, i.e., if {@code hasPointer(buffer)}
80+
* returns {@code false}
81+
*
82+
* @see PointerBuffer#hasPointer(Buffer)
83+
*/
84+
public static final Object pointer(Buffer buffer) {
85+
throw new AssertionError("stub");
86+
}
87+
}

javalib/src/main/scala/java/nio/Buffer.scala

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package java.nio
22

33
// Ported from Scala.js
4+
import scala.scalanative.unsafe
45

56
abstract class Buffer private[nio] (val _capacity: Int) {
67
private[nio] type ElementType
@@ -98,15 +99,36 @@ abstract class Buffer private[nio] (val _capacity: Int) {
9899
override def toString(): String =
99100
s"${getClass.getName}[pos=${position()} lim=${limit()} cap=${capacity()}]"
100101

102+
// Extended API
103+
final def hasPointer(): Boolean = _rawDataPointer != null && !isReadOnly()
104+
105+
final def pointer(): unsafe.Ptr[Byte] = {
106+
val ptr = _rawDataPointer
107+
if (ptr == null || isReadOnly())
108+
throw new UnsupportedOperationException
109+
ptr
110+
}
111+
101112
/* Generic access to methods declared in subclasses.
102113
* These methods allow to write generic algorithms on any kind of Buffer.
103114
* The optimizer will get rid of all the overhead.
104115
* We only declare the methods we need somewhere.
105116
*/
106117

107-
private[nio] def _array: Array[ElementType]
108-
private[nio] def _arrayOffset: Int
109-
private[nio] def _mappedData: MappedByteBufferData // Added to ScalaNative
118+
private[nio] def _array: Array[ElementType] = null
119+
private[nio] def _offset: Int
120+
121+
// MappedByteBuffer specific
122+
private[nio] def _mappedData: MappedByteBufferData = null
123+
124+
// PointerByteBuffer specific
125+
private[nio] def _rawDataPointer: unsafe.Ptr[Byte] = null
126+
127+
// HeapByteBuffer specific
128+
private[nio] def _byteArray: Array[Byte] =
129+
throw new UnsupportedOperationException
130+
private[nio] def isBigEndian: Boolean =
131+
throw new UnsupportedOperationException
110132

111133
/** Loads an element at the given absolute, unchecked index. */
112134
private[nio] def load(index: Int): ElementType
@@ -130,16 +152,6 @@ abstract class Buffer private[nio] (val _capacity: Int) {
130152
length: Int
131153
): Unit
132154

133-
/* Only for HeapByteBufferViews -- but that's the only place we can put it.
134-
* For all other types, it will be dce'ed.
135-
*/
136-
private[nio] def _byteArray: Array[Byte] =
137-
throw new UnsupportedOperationException
138-
private[nio] def _byteArrayOffset: Int =
139-
throw new UnsupportedOperationException
140-
private[nio] def isBigEndian: Boolean =
141-
throw new UnsupportedOperationException
142-
143155
// Helpers
144156

145157
@inline private[nio] def ensureNotReadOnly(): Unit = {

javalib/src/main/scala/java/nio/ByteBuffer.scala

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package java.nio
22

33
// Ported from Scala.js
4-
4+
import scala.scalanative.unsafe
55
object ByteBuffer {
66
private final val HashSeed = -547316498 // "java.nio.ByteBuffer".##
77

@@ -15,13 +15,17 @@ object ByteBuffer {
1515

1616
def wrap(array: Array[Byte]): ByteBuffer =
1717
wrap(array, 0, array.length)
18+
19+
// Extended API
20+
21+
def wrapPointerByte(array: unsafe.Ptr[Byte], length: Int): ByteBuffer =
22+
PointerByteBuffer.wrap(array, length)
1823
}
1924

2025
abstract class ByteBuffer private[nio] (
2126
_capacity: Int,
22-
private[nio] val _array: Array[Byte],
23-
private[nio] val _mappedData: MappedByteBufferData,
24-
private[nio] val _arrayOffset: Int
27+
override private[nio] val _array: Array[Byte],
28+
private[nio] val _offset: Int
2529
) extends Buffer(_capacity)
2630
with Comparable[ByteBuffer] {
2731

@@ -30,7 +34,7 @@ abstract class ByteBuffer private[nio] (
3034

3135
private def genBuffer = GenBuffer[ByteBuffer](this)
3236

33-
def this(_capacity: Int) = this(_capacity, null, null, -1)
37+
def this(_capacity: Int) = this(_capacity, null: Array[Byte], -1)
3438

3539
private[nio] var _isBigEndian: Boolean = true
3640

@@ -73,7 +77,7 @@ abstract class ByteBuffer private[nio] (
7377
genBuffer.generic_array()
7478

7579
@inline final def arrayOffset(): Int =
76-
genBuffer.generic_arrayOffset()
80+
genBuffer.generic_offset()
7781

7882
@inline override def position(newPosition: Int): ByteBuffer = {
7983
super.position(newPosition)

javalib/src/main/scala/java/nio/CharBuffer.scala

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,8 @@ object CharBuffer {
2121

2222
abstract class CharBuffer private[nio] (
2323
_capacity: Int,
24-
private[nio] val _array: Array[Char],
25-
private[nio] val _mappedData: MappedByteBufferData,
26-
private[nio] val _arrayOffset: Int
24+
override private[nio] val _array: Array[Char],
25+
private[nio] val _offset: Int
2726
) extends Buffer(_capacity)
2827
with Comparable[CharBuffer]
2928
with CharSequence
@@ -35,15 +34,15 @@ abstract class CharBuffer private[nio] (
3534

3635
private def genBuffer = GenBuffer[CharBuffer](this)
3736

38-
def this(_capacity: Int) = this(_capacity, null, null, -1)
37+
def this(_capacity: Int) = this(_capacity, null: Array[Char], -1)
3938

4039
def read(target: CharBuffer): Int = {
4140
// Attention: this method must not change this buffer's position
4241
val n = remaining()
4342
if (n == 0) -1
4443
else if (_array != null) {
4544
// even if read-only
46-
genBuffer.generic_put(_array, _arrayOffset, n)
45+
genBuffer.generic_put(_array, _offset, n)
4746
n
4847
} else {
4948
val savedPos = position()
@@ -98,7 +97,7 @@ abstract class CharBuffer private[nio] (
9897
genBuffer.generic_array()
9998

10099
@inline final def arrayOffset(): Int =
101-
genBuffer.generic_arrayOffset()
100+
genBuffer.generic_offset()
102101

103102
@inline override def position(newPosition: Int): CharBuffer = {
104103
super.position(newPosition)
@@ -155,7 +154,7 @@ abstract class CharBuffer private[nio] (
155154
override def toString(): String = {
156155
if (_array != null) {
157156
// even if read-only
158-
new String(_array, position() + _arrayOffset, remaining())
157+
new String(_array, position() + _offset, remaining())
159158
} else {
160159
val chars = new Array[Char](remaining())
161160
val savedPos = position()

javalib/src/main/scala/java/nio/DoubleBuffer.scala

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,8 @@ object DoubleBuffer {
1616

1717
abstract class DoubleBuffer private[nio] (
1818
_capacity: Int,
19-
private[nio] val _array: Array[Double],
20-
private[nio] val _mappedData: MappedByteBufferData,
21-
private[nio] val _arrayOffset: Int
19+
override private[nio] val _array: Array[Double],
20+
private[nio] val _offset: Int
2221
) extends Buffer(_capacity)
2322
with Comparable[DoubleBuffer] {
2423

@@ -27,7 +26,7 @@ abstract class DoubleBuffer private[nio] (
2726

2827
private def genBuffer = GenBuffer[DoubleBuffer](this)
2928

30-
def this(_capacity: Int) = this(_capacity, null, null, -1)
29+
def this(_capacity: Int) = this(_capacity, null, -1)
3130

3231
def slice(): DoubleBuffer
3332

@@ -68,7 +67,7 @@ abstract class DoubleBuffer private[nio] (
6867
genBuffer.generic_array()
6968

7069
@inline final def arrayOffset(): Int =
71-
genBuffer.generic_arrayOffset()
70+
genBuffer.generic_offset()
7271

7372
@inline override def position(newPosition: Int): DoubleBuffer = {
7473
super.position(newPosition)

javalib/src/main/scala/java/nio/FloatBuffer.scala

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,8 @@ object FloatBuffer {
1616

1717
abstract class FloatBuffer private[nio] (
1818
_capacity: Int,
19-
private[nio] val _array: Array[Float],
20-
private[nio] val _mappedData: MappedByteBufferData,
21-
private[nio] val _arrayOffset: Int
19+
override private[nio] val _array: Array[Float],
20+
private[nio] val _offset: Int
2221
) extends Buffer(_capacity)
2322
with Comparable[FloatBuffer] {
2423

@@ -27,7 +26,7 @@ abstract class FloatBuffer private[nio] (
2726

2827
private def genBuffer = GenBuffer[FloatBuffer](this)
2928

30-
def this(_capacity: Int) = this(_capacity, null, null, -1)
29+
def this(_capacity: Int) = this(_capacity, null, -1)
3130

3231
def slice(): FloatBuffer
3332

@@ -68,7 +67,7 @@ abstract class FloatBuffer private[nio] (
6867
genBuffer.generic_array()
6968

7069
@inline final def arrayOffset(): Int =
71-
genBuffer.generic_arrayOffset()
70+
genBuffer.generic_offset()
7271

7372
@inline override def position(newPosition: Int): FloatBuffer = {
7473
super.position(newPosition)

javalib/src/main/scala/java/nio/GenBuffer.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ private[nio] final class GenBuffer[B <: Buffer](val self: B) extends AnyVal {
5656

5757
val srcArray = src._array // even if read-only
5858
if (srcArray != null) {
59-
store(selfPos, srcArray, src._arrayOffset + srcPos, length)
59+
store(selfPos, srcArray, src._offset + srcPos, length)
6060
} else {
6161
while (srcPos != srcLimit) {
6262
store(selfPos, src.load(srcPos))
@@ -95,8 +95,8 @@ private[nio] final class GenBuffer[B <: Buffer](val self: B) extends AnyVal {
9595
}
9696

9797
@inline
98-
def generic_arrayOffset(): Int = {
99-
val o = _arrayOffset
98+
def generic_offset(): Int = {
99+
val o = _offset
100100
if (o == -1)
101101
throw new UnsupportedOperationException
102102
if (isReadOnly())

0 commit comments

Comments
 (0)