Skip to content

Commit 821b1bc

Browse files
authored
[Unsafe API 4/5] Rewrite existing extensions using unsafe API (#337)
* Reimplement some methods to use Unsafe API instead of internal one * Reimplement UTF-8-related functions using Unsafe API * Remove last direct Segment.data use * Reimplement Buffer.snapshot to use bulk append
1 parent 046523f commit 821b1bc

File tree

14 files changed

+366
-354
lines changed

14 files changed

+366
-354
lines changed

core/apple/src/AppleCore.kt

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
package kotlinx.io
99

1010
import kotlinx.cinterop.*
11+
import kotlinx.io.unsafe.UnsafeBufferOperations
1112
import platform.Foundation.NSInputStream
1213
import platform.Foundation.NSOutputStream
1314
import platform.Foundation.NSStreamStatusClosed
@@ -31,29 +32,28 @@ private open class OutputStreamSink(
3132
if (out.streamStatus == NSStreamStatusNotOpen) out.open()
3233
}
3334

35+
@OptIn(UnsafeIoApi::class)
3436
override fun write(source: Buffer, byteCount: Long) {
3537
if (out.streamStatus == NSStreamStatusClosed) throw IOException("Stream Closed")
3638

3739
checkOffsetAndCount(source.size, 0, byteCount)
3840
var remaining = byteCount
41+
var bytesWritten = 0L
3942
while (remaining > 0) {
40-
val head = source.head!!
41-
val toCopy = minOf(remaining, head.limit - head.pos).toInt()
42-
val bytesWritten = head.data.usePinned {
43-
val bytes = it.addressOf(head.pos).reinterpret<uint8_tVar>()
44-
out.write(bytes, toCopy.convert()).toLong()
43+
UnsafeBufferOperations.readFromHead(source) { data, pos, limit ->
44+
val toCopy = minOf(remaining, limit - pos).toInt()
45+
bytesWritten = data.usePinned {
46+
val bytes = it.addressOf(pos).reinterpret<uint8_tVar>()
47+
out.write(bytes, toCopy.convert()).toLong()
48+
}
49+
0
4550
}
4651

4752
if (bytesWritten < 0L) throw IOException(out.streamError?.localizedDescription ?: "Unknown error")
4853
if (bytesWritten == 0L) throw IOException("NSOutputStream reached capacity")
4954

50-
head.pos += bytesWritten.toInt()
55+
source.skip(bytesWritten)
5156
remaining -= bytesWritten
52-
source.sizeMut -= bytesWritten
53-
54-
if (head.pos == head.limit) {
55-
source.recycleHead()
56-
}
5757
}
5858
}
5959

@@ -83,29 +83,26 @@ private open class NSInputStreamSource(
8383
if (input.streamStatus == NSStreamStatusNotOpen) input.open()
8484
}
8585

86+
@OptIn(UnsafeIoApi::class)
8687
override fun readAtMostTo(sink: Buffer, byteCount: Long): Long {
8788
if (input.streamStatus == NSStreamStatusClosed) throw IOException("Stream Closed")
8889

8990
if (byteCount == 0L) return 0L
9091
checkByteCount(byteCount)
9192

92-
val tail = sink.writableSegment(1)
93-
val maxToCopy = minOf(byteCount, Segment.SIZE - tail.limit)
94-
val bytesRead = tail.data.usePinned {
95-
val bytes = it.addressOf(tail.limit).reinterpret<uint8_tVar>()
96-
input.read(bytes, maxToCopy.convert()).toLong()
93+
var bytesRead = 0L
94+
UnsafeBufferOperations.writeToTail(sink, 1) { data, pos, limit ->
95+
val maxToCopy = minOf(byteCount, limit - pos)
96+
val read = data.usePinned { ba ->
97+
val bytes = ba.addressOf(pos).reinterpret<uint8_tVar>()
98+
input.read(bytes, maxToCopy.convert()).toLong()
99+
}
100+
bytesRead = read
101+
maxOf(read.toInt(), 0)
97102
}
98103

99104
if (bytesRead < 0L) throw IOException(input.streamError?.localizedDescription ?: "Unknown error")
100-
if (bytesRead == 0L) {
101-
if (tail.pos == tail.limit) {
102-
// We allocated a tail segment, but didn't end up needing it. Recycle!
103-
sink.recycleTail()
104-
}
105-
return -1
106-
}
107-
tail.limit += bytesRead.toInt()
108-
sink.sizeMut += bytesRead
105+
if (bytesRead == 0L) return -1
109106
return bytesRead
110107
}
111108

core/apple/src/BuffersApple.kt

Lines changed: 34 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -8,69 +8,70 @@
88
package kotlinx.io
99

1010
import kotlinx.cinterop.*
11+
import kotlinx.io.unsafe.UnsafeBufferOperations
12+
import kotlinx.io.unsafe.withData
1113
import platform.Foundation.NSData
1214
import platform.Foundation.create
1315
import platform.Foundation.data
1416
import platform.darwin.NSUIntegerMax
1517
import platform.posix.*
1618

17-
@OptIn(ExperimentalForeignApi::class)
19+
@OptIn(ExperimentalForeignApi::class, UnsafeIoApi::class)
1820
internal fun Buffer.write(source: CPointer<uint8_tVar>, maxLength: Int) {
1921
require(maxLength >= 0) { "maxLength ($maxLength) must not be negative" }
2022

2123
var currentOffset = 0
2224
while (currentOffset < maxLength) {
23-
val tail = writableSegment(1)
24-
25-
val toCopy = minOf(maxLength - currentOffset, Segment.SIZE - tail.limit)
26-
tail.data.usePinned {
27-
memcpy(it.addressOf(tail.limit), source + currentOffset, toCopy.convert())
25+
UnsafeBufferOperations.writeToTail(this, 1) { data, pos, limit ->
26+
val toCopy = minOf(maxLength - currentOffset, limit - pos)
27+
data.usePinned {
28+
memcpy(it.addressOf(pos), source + currentOffset, toCopy.convert())
29+
}
30+
currentOffset += toCopy
31+
toCopy
2832
}
29-
30-
currentOffset += toCopy
31-
tail.limit += toCopy
3233
}
33-
this.sizeMut += maxLength
3434
}
3535

36+
@OptIn(UnsafeIoApi::class)
3637
internal fun Buffer.readAtMostTo(sink: CPointer<uint8_tVar>, maxLength: Int): Int {
3738
require(maxLength >= 0) { "maxLength ($maxLength) must not be negative" }
3839

39-
val s = head ?: return 0
40-
val toCopy = minOf(maxLength, s.limit - s.pos)
41-
s.data.usePinned {
42-
memcpy(sink, it.addressOf(s.pos), toCopy.convert())
43-
}
44-
45-
s.pos += toCopy
46-
this.sizeMut -= toCopy.toLong()
47-
48-
if (s.pos == s.limit) {
49-
recycleHead()
40+
var toCopy = 0
41+
UnsafeBufferOperations.readFromHead(this) { data, pos, limit ->
42+
toCopy = minOf(maxLength, limit - pos)
43+
data.usePinned {
44+
memcpy(sink, it.addressOf(pos), toCopy.convert())
45+
}
46+
toCopy
5047
}
5148

5249
return toCopy
5350
}
5451

55-
@OptIn(BetaInteropApi::class)
52+
@OptIn(BetaInteropApi::class, UnsafeIoApi::class)
5653
internal fun Buffer.snapshotAsNSData(): NSData {
5754
if (size == 0L) return NSData.data()
5855

5956
check(size.toULong() <= NSUIntegerMax) { "Buffer is too long ($size) to be converted into NSData." }
6057

6158
val bytes = malloc(size.convert())?.reinterpret<uint8_tVar>()
6259
?: throw Error("malloc failed: ${strerror(errno)?.toKString()}")
63-
var curr = head
64-
var index = 0
65-
do {
66-
check(curr != null) { "Current segment is null" }
67-
val pos = curr.pos
68-
val length = curr.limit - pos
69-
curr.data.usePinned {
70-
memcpy(bytes + index, it.addressOf(pos), length.convert())
60+
61+
UnsafeBufferOperations.iterate(this) { ctx, head ->
62+
var curr: Segment? = head
63+
var index = 0
64+
while (curr != null) {
65+
val segment: Segment = curr
66+
ctx.withData(segment) { data, pos, limit ->
67+
val length = limit - pos
68+
data.usePinned {
69+
memcpy(bytes + index, it.addressOf(pos), length.convert())
70+
}
71+
index += length
72+
}
73+
curr = ctx.next(segment)
7174
}
72-
curr = curr.next
73-
index += length
74-
} while (curr != null)
75+
}
7576
return NSData.create(bytesNoCopy = bytes, length = size.convert())
7677
}

core/common/src/Buffer.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,9 @@ public class Buffer : Source, Sink {
291291
if (position < 0 || position >= size) {
292292
throw IndexOutOfBoundsException("position ($position) is not within the range [0..size($size))")
293293
}
294+
if (position == 0L) {
295+
return head!!.getUnchecked(0)
296+
}
294297
seek(position) { s, offset ->
295298
return s!!.data[(s.pos + position - offset).toInt()]
296299
}

core/common/src/Buffers.kt

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,28 @@ package kotlinx.io
77

88
import kotlinx.io.bytestring.ByteString
99
import kotlinx.io.bytestring.buildByteString
10+
import kotlinx.io.unsafe.UnsafeBufferOperations
11+
import kotlinx.io.unsafe.withData
1012

1113
/**
1214
* Creates a byte string containing a copy of all the data from this buffer.
1315
*
1416
* This call doesn't consume data from the buffer, but instead copies it.
1517
*/
18+
@OptIn(UnsafeIoApi::class)
1619
public fun Buffer.snapshot(): ByteString {
1720
if (size == 0L) return ByteString()
1821

1922
check(size <= Int.MAX_VALUE) { "Buffer is too long ($size) to be converted into a byte string." }
2023

2124
return buildByteString(size.toInt()) {
22-
var curr = head
23-
do {
24-
check(curr != null) { "Current segment is null" }
25-
append(curr.data, curr.pos, curr.limit)
26-
curr = curr.next
27-
} while (curr != null)
25+
UnsafeBufferOperations.iterate(this@snapshot) { ctx, head ->
26+
var curr = head
27+
while (curr != null) {
28+
ctx.withData(curr, this::append)
29+
curr = ctx.next(curr)
30+
}
31+
}
2832
}
2933
}
3034

core/common/src/ByteStrings.kt

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import kotlinx.io.bytestring.ByteString
99
import kotlinx.io.bytestring.isEmpty
1010
import kotlinx.io.bytestring.unsafe.UnsafeByteStringApi
1111
import kotlinx.io.bytestring.unsafe.UnsafeByteStringOperations
12+
import kotlinx.io.unsafe.UnsafeBufferOperations
1213
import kotlin.math.min
1314

1415
/**
@@ -25,7 +26,7 @@ import kotlin.math.min
2526
*
2627
* @sample kotlinx.io.samples.ByteStringSamples.writeByteString
2728
*/
28-
@OptIn(DelicateIoApi::class)
29+
@OptIn(DelicateIoApi::class, UnsafeByteStringApi::class, UnsafeIoApi::class)
2930
public fun Sink.write(byteString: ByteString, startIndex: Int = 0, endIndex: Int = byteString.size) {
3031
checkBounds(byteString.size, startIndex, endIndex)
3132
if (endIndex == startIndex) {
@@ -34,21 +35,17 @@ public fun Sink.write(byteString: ByteString, startIndex: Int = 0, endIndex: Int
3435

3536
writeToInternalBuffer { buffer ->
3637
var offset = startIndex
37-
val tail = buffer.head?.prev
38-
if (tail != null) {
39-
val bytesToWrite = min(tail.data.size - tail.limit, endIndex - offset)
40-
byteString.copyInto(tail.data, tail.limit, offset, offset + bytesToWrite)
41-
offset += bytesToWrite
42-
tail.limit += bytesToWrite
43-
buffer.sizeMut += bytesToWrite
44-
}
45-
while (offset < endIndex) {
46-
val bytesToWrite = min(endIndex - offset, Segment.SIZE)
47-
val seg = buffer.writableSegment(bytesToWrite)
48-
byteString.copyInto(seg.data, seg.limit, offset, offset + bytesToWrite)
49-
seg.limit += bytesToWrite
50-
buffer.sizeMut += bytesToWrite
51-
offset += bytesToWrite
38+
39+
UnsafeByteStringOperations.withByteArrayUnsafe(byteString) { data ->
40+
while (offset < endIndex) {
41+
var written = 0
42+
UnsafeBufferOperations.writeToTail(buffer, 1) { segData, pos, limit ->
43+
written = min(endIndex - offset, limit - pos)
44+
data.copyInto(segData, pos, offset, offset + written)
45+
written
46+
}
47+
offset += written
48+
}
5249
}
5350
}
5451
}

core/common/src/Sinks.kt

Lines changed: 19 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package kotlinx.io
88
import kotlin.contracts.ExperimentalContracts
99
import kotlin.contracts.InvocationKind.EXACTLY_ONCE
1010
import kotlin.contracts.contract
11+
import kotlinx.io.unsafe.UnsafeBufferOperations
1112

1213
private val HEX_DIGIT_BYTES = ByteArray(16) {
1314
((if (it < 10) '0'.code else ('a'.code - 10)) + it).toByte()
@@ -67,7 +68,7 @@ public fun Sink.writeLongLe(long: Long) {
6768
*
6869
* @sample kotlinx.io.samples.KotlinxIoCoreCommonSamples.writeDecimalLong
6970
*/
70-
@OptIn(DelicateIoApi::class)
71+
@OptIn(DelicateIoApi::class, UnsafeIoApi::class)
7172
public fun Sink.writeDecimalLong(long: Long) {
7273
var v = long
7374
if (v == 0L) {
@@ -120,20 +121,17 @@ public fun Sink.writeDecimalLong(long: Long) {
120121
}
121122

122123
writeToInternalBuffer { buffer ->
123-
val tail = buffer.writableSegment(width)
124-
val data = tail.data
125-
var pos = tail.limit + width // We write backwards from right to left.
126-
while (v != 0L) {
127-
val digit = (v % 10).toInt()
128-
data[--pos] = HEX_DIGIT_BYTES[digit]
129-
v /= 10
124+
UnsafeBufferOperations.writeToTail(buffer, width) { ctx, segment ->
125+
for (pos in width - 1 downTo if (negative) 1 else 0) {
126+
val digit = (v % 10).toByte()
127+
ctx.setUnchecked(segment, pos, HEX_DIGIT_BYTES[digit.toInt()])
128+
v /= 10
129+
}
130+
if (negative) {
131+
ctx.setUnchecked(segment, 0, '-'.code.toByte())
132+
}
133+
width
130134
}
131-
if (negative) {
132-
data[--pos] = '-'.code.toByte()
133-
}
134-
135-
tail.limit += width
136-
buffer.sizeMut += width.toLong()
137135
}
138136
}
139137

@@ -149,7 +147,7 @@ public fun Sink.writeDecimalLong(long: Long) {
149147
*
150148
* @sample kotlinx.io.samples.KotlinxIoCoreCommonSamples.writeHexLong
151149
*/
152-
@OptIn(DelicateIoApi::class)
150+
@OptIn(DelicateIoApi::class, UnsafeIoApi::class)
153151
public fun Sink.writeHexadecimalUnsignedLong(long: Long) {
154152
var v = long
155153
if (v == 0L) {
@@ -161,17 +159,13 @@ public fun Sink.writeHexadecimalUnsignedLong(long: Long) {
161159
val width = hexNumberLength(v)
162160

163161
writeToInternalBuffer { buffer ->
164-
val tail = buffer.writableSegment(width)
165-
val data = tail.data
166-
var pos = tail.limit + width - 1
167-
val start = tail.limit
168-
while (pos >= start) {
169-
data[pos] = HEX_DIGIT_BYTES[(v and 0xF).toInt()]
170-
v = v ushr 4
171-
pos--
162+
UnsafeBufferOperations.writeToTail(buffer, width) { ctx, segment ->
163+
for (pos in width - 1 downTo 0) {
164+
ctx.setUnchecked(segment, pos, HEX_DIGIT_BYTES[v.toInt().and(0xF)])
165+
v = v ushr 4
166+
}
167+
width
172168
}
173-
tail.limit += width
174-
buffer.sizeMut += width.toLong()
175169
}
176170
}
177171

0 commit comments

Comments
 (0)