Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement KT-73565 and basic box tests for memcpy, memmove, memset and memcmp intrinsics #5385

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ internal object nativeMemUtils {

fun getShort(mem: NativePointed) = unsafe.getShort(mem.address)
fun putShort(mem: NativePointed, value: Short) = unsafe.putShort(mem.address, value)

fun getInt(mem: NativePointed) = unsafe.getInt(mem.address)
fun putInt(mem: NativePointed, value: Int) = unsafe.putInt(mem.address, value)

fun getLong(mem: NativePointed) = unsafe.getLong(mem.address)
fun putLong(mem: NativePointed, value: Long) = unsafe.putLong(mem.address, value)

Expand Down Expand Up @@ -77,11 +77,19 @@ internal object nativeMemUtils {
}

fun getCharArray(source: NativePointed, dest: CharArray, length: Int) {
unsafe.copyMemory(null, source.address, dest, charArrayBaseOffset, length.toLong() * 2)
unsafe.copyMemory(null, source.address, dest, charArrayBaseOffset, length.toLong() * Char.SIZE_BYTES)
}

fun putCharArray(source: CharArray, dest: NativePointed, length: Int) {
unsafe.copyMemory(source, charArrayBaseOffset, null, dest.address, length.toLong() * 2)
unsafe.copyMemory(source, charArrayBaseOffset, null, dest.address, length.toLong() * Char.SIZE_BYTES)
}

fun getFloatArray(source: NativePointed, dest: FloatArray, length: Int) {
unsafe.copyMemory(null, source.address, dest, floatArrayBaseOffset, length.toLong() * Float.SIZE_BYTES)
}

fun putFloatArray(source: FloatArray, dest: NativePointed, length: Int) {
unsafe.copyMemory(source, floatArrayBaseOffset, null, dest.address, length.toLong() * Float.SIZE_BYTES)
}

fun zeroMemory(dest: NativePointed, length: Int): Unit =
Expand All @@ -90,6 +98,65 @@ internal object nativeMemUtils {
fun copyMemory(dest: NativePointed, length: Int, src: NativePointed) =
unsafe.copyMemory(src.address, dest.address, length.toLong())

fun memset(mem: NativePointed, value: Byte, size: Int) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no need to add intrinsics to the JVM interop as they are unused there.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh, it didn't compile without them for me in my personal fork, that's odd. Will remove ^^

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah i have to add them there too it seems, at least if i want to add this to nativeMemUtils, the code does not compile when the functions are not defined in both files. Everything breaks :/

unsafe.setMemory(mem.address, size.toLong(), value)
}

fun memset(mem: NativePointed, value: Byte, size: Long) {
unsafe.setMemory(mem.address, size, value)
}

fun memcpy(dstMem: NativePointed, srcMem: NativePointed, size: Int) {
unsafe.copyMemory(srcMem.address, dstMem.address, size.toLong())
}

fun memcpy(dstMem: NativePointed, srcMem: NativePointed, size: Long) {
unsafe.copyMemory(srcMem.address, dstMem.address, size)
}

fun memmove(dstMem: NativePointed, srcMem: NativePointed, size: Int) {
val src = srcMem.address
val dest = dstMem.address
if (src < dest) { // Backwards copy to allow overlap
var index = size - 1
while (index > 0) {
unsafe.putByte(dest + index, unsafe.getByte(src + index))
index++
}
return
}
unsafe.copyMemory(src, dest, size.toLong())
}

fun memmove(dstMem: NativePointed, srcMem: NativePointed, size: Long) {
val src = srcMem.address
val dest = dstMem.address
if (src < dest) { // Backwards copy to allow overlap
var index = size - 1L
while (index > 0) {
unsafe.putByte(dest + index, unsafe.getByte(src + index))
index++
}
return
}
unsafe.copyMemory(src, dest, size)
}

fun memcmp(memA: NativePointed, memB: NativePointed, size: Long): Int {
var index = 0L
val addressA = memA.address
val addressB = memB.address
while (index < size) {
val a = unsafe.getByte(addressA + index)
val b = unsafe.getByte(addressB + index)
when {
a < b -> return -1
a > b -> return 1
}
index++
}
return 0
}

@Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE")
inline fun <reified T> allocateInstance(): T {
Expand Down Expand Up @@ -117,4 +184,5 @@ internal object nativeMemUtils {

private val byteArrayBaseOffset = unsafe.arrayBaseOffset(ByteArray::class.java).toLong()
private val charArrayBaseOffset = unsafe.arrayBaseOffset(CharArray::class.java).toLong()
private val floatArrayBaseOffset = unsafe.arrayBaseOffset(FloatArray::class.java).toLong()
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public interface NativeFreeablePlacement : NativePlacement {

@ExperimentalForeignApi
public fun NativeFreeablePlacement.free(pointer: CPointer<*>): Unit = this.free(pointer.rawValue)

@ExperimentalForeignApi
public fun NativeFreeablePlacement.free(pointed: NativePointed): Unit = this.free(pointed.rawPtr)

Expand Down Expand Up @@ -153,10 +154,10 @@ public inline fun <reified T : CVariable> NativePlacement.allocArray(length: Int
*/
@ExperimentalForeignApi
public inline fun <reified T : CVariable> NativePlacement.allocArray(length: Long,
initializer: T.(index: Long)->Unit): CArrayPointer<T> {
initializer: T.(index: Long) -> Unit): CArrayPointer<T> {
val res = allocArray<T>(length)

(0 .. length - 1).forEach { index ->
(0..length - 1).forEach { index ->
res[index].initializer(index)
}

Expand All @@ -170,9 +171,9 @@ public inline fun <reified T : CVariable> NativePlacement.allocArray(length: Lon
*/
@ExperimentalForeignApi
public inline fun <reified T : CVariable> NativePlacement.allocArray(
length: Int, initializer: T.(index: Int)->Unit): CArrayPointer<T> = allocArray(length.toLong()) { index ->
this.initializer(index.toInt())
}
length: Int, initializer: T.(index: Int) -> Unit): CArrayPointer<T> = allocArray(length.toLong()) { index ->
this.initializer(index.toInt())
}


/**
Expand Down Expand Up @@ -228,13 +229,9 @@ public fun NativePlacement.allocArrayOf(elements: ByteArray): CArrayPointer<Byte

@ExperimentalForeignApi
public fun NativePlacement.allocArrayOf(vararg elements: Float): CArrayPointer<FloatVar> {
val res = allocArray<FloatVar>(elements.size)
var index = 0
while (index < elements.size) {
res[index] = elements[index]
++index
return allocArray<FloatVar>(elements.size).apply {
nativeMemUtils.putFloatArray(elements, pointed, elements.size)
}
return res
}

@ExperimentalForeignApi
Expand All @@ -252,11 +249,13 @@ internal class ZeroValue<T : CVariable>(private val sizeBytes: Int, private val
nativeMemUtils.zeroMemory(interpretPointed(placement.rawValue), sizeBytes)
return placement
}

override val size get() = sizeBytes

override val align get() = alignBytes

}

@Suppress("NOTHING_TO_INLINE")
@ExperimentalForeignApi
public inline fun <T : CVariable> zeroValue(size: Int, align: Int): CValue<T> = ZeroValue(size, align)
Expand All @@ -277,10 +276,12 @@ public fun <T : CVariable> CPointed.readValues(size: Int, align: Int): CValues<T
override fun getPointer(scope: AutofreeScope): CPointer<T> {
return place(interpretCPointer(scope.alloc(size, align).rawPtr)!!)
}

override fun place(placement: CPointer<T>): CPointer<T> {
nativeMemUtils.putByteArray(bytes, interpretPointed(placement.rawValue), bytes.size)
return placement
}

override val size get() = size
override val align get() = align
}
Expand All @@ -300,10 +301,12 @@ public fun <T : CVariable> CPointed.readValue(size: Long, align: Int): CValue<T>
nativeMemUtils.putByteArray(bytes, interpretPointed(placement.rawValue), bytes.size)
return placement
}

// Optimization to avoid unneeded virtual calls in base class implementation.
public override fun getPointer(scope: AutofreeScope): CPointer<T> {
return place(interpretCPointer(scope.alloc(size, align).rawPtr)!!)
}

override val size get() = size.toInt()
override val align get() = align
}
Expand Down Expand Up @@ -361,7 +364,7 @@ public inline fun <reified T : CStructVar> CValue<T>.copy(modify: T.() -> Unit):

@ExperimentalForeignApi
public inline fun <reified T : CStructVar> cValue(initialize: T.() -> Unit): CValue<T> =
zeroValue<T>().copy(modify = initialize)
zeroValue<T>().copy(modify = initialize)

@ExperimentalForeignApi
public inline fun <reified T : CVariable> createValues(count: Int, initializer: T.(index: Int) -> Unit): CValues<T> = memScoped {
Expand All @@ -379,6 +382,7 @@ public fun cValuesOf(vararg elements: Byte): CValues<ByteVar> = object : CValues
override fun getPointer(scope: AutofreeScope): CPointer<ByteVar> {
return place(interpretCPointer(scope.alloc(size, align).rawPtr)!!)
}

override fun place(placement: CPointer<ByteVar>): CPointer<ByteVar> {
nativeMemUtils.putByteArray(elements, interpretPointed(placement.rawValue), elements.size)
return placement
Expand Down Expand Up @@ -414,18 +418,25 @@ public fun <T : CPointed> cValuesOf(vararg elements: CPointer<T>?): CValues<CPoi

@ExperimentalForeignApi
public fun ByteArray.toCValues(): CValues<ByteVar> = cValuesOf(*this)

@ExperimentalForeignApi
public fun ShortArray.toCValues(): CValues<ShortVar> = cValuesOf(*this)

@ExperimentalForeignApi
public fun IntArray.toCValues(): CValues<IntVar> = cValuesOf(*this)

@ExperimentalForeignApi
public fun LongArray.toCValues(): CValues<LongVar> = cValuesOf(*this)

@ExperimentalForeignApi
public fun FloatArray.toCValues(): CValues<FloatVar> = cValuesOf(*this)

@ExperimentalForeignApi
public fun DoubleArray.toCValues(): CValues<DoubleVar> = cValuesOf(*this)

@ExperimentalForeignApi
public fun <T : CPointed> Array<CPointer<T>?>.toCValues(): CValues<CPointerVar<T>> = cValuesOf(*this)

@ExperimentalForeignApi
public fun <T : CPointed> List<CPointer<T>?>.toCValues(): CValues<CPointerVar<T>> = this.toTypedArray().toCValues()

Expand All @@ -438,6 +449,7 @@ private class CString(val bytes: ByteArray) : CValues<ByteVar>() {
override fun getPointer(scope: AutofreeScope): CPointer<ByteVar> {
return place(interpretCPointer(scope.alloc(size, align).rawPtr)!!)
}

override fun place(placement: CPointer<ByteVar>): CPointer<ByteVar> {
nativeMemUtils.putByteArray(bytes, placement.pointed, bytes.size)
placement[bytes.size] = 0.toByte()
Expand Down Expand Up @@ -497,7 +509,7 @@ public fun Array<String>.toCStringArray(autofreeScope: AutofreeScope): CPointer<


@ExperimentalForeignApi
private class U16CString(val chars: CharArray): CValues<UShortVar>() {
private class U16CString(val chars: CharArray) : CValues<UShortVar>() {
override val size get() = 2 * (chars.size + 1)

override val align get() = 2
Expand Down Expand Up @@ -593,13 +605,9 @@ public fun CPointer<ShortVar>.toKStringFromUtf16(): String {
while (nativeBytes[length] != 0.toShort()) {
++length
}
val chars = CharArray(length)
var index = 0
while (index < length) {
chars[index] = nativeBytes[index].toInt().toChar()
++index
}
return chars.concatToString()
return CharArray(length).apply {
nativeMemUtils.getCharArray(pointed, this, length)
}.concatToString()
}

/**
Expand Down Expand Up @@ -644,7 +652,7 @@ public fun CPointer<IntVar>.toKStringFromUtf32(): String {
*/
@SinceKotlin("1.3")
@ExperimentalForeignApi
public fun ByteArray.toKString() : String {
public fun ByteArray.toKString(): String {
val realEndIndex = realEndIndex(this, 0, this.size)
return decodeToString(0, realEndIndex)
}
Expand All @@ -667,7 +675,7 @@ public fun ByteArray.toKString(
startIndex: Int = 0,
endIndex: Int = this.size,
throwOnInvalidSequence: Boolean = false
) : String {
): String {
checkBoundsIndexes(startIndex, endIndex, this.size)
val realEndIndex = realEndIndex(this, startIndex, endIndex)
return decodeToString(startIndex, realEndIndex, throwOnInvalidSequence)
Expand Down Expand Up @@ -696,7 +704,7 @@ public class MemScope : ArenaBase() {
public val memScope: MemScope
get() = this

public val <T: CVariable> CValues<T>.ptr: CPointer<T>
public val <T : CVariable> CValues<T>.ptr: CPointer<T>
get() = [email protected](this@MemScope)
}

Expand All @@ -708,7 +716,7 @@ public class MemScope : ArenaBase() {
*/
@ExperimentalForeignApi
@OptIn(kotlin.contracts.ExperimentalContracts::class)
public inline fun <R> memScoped(block: MemScope.()->R): R {
public inline fun <R> memScoped(block: MemScope.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
Expand All @@ -727,3 +735,43 @@ public fun COpaquePointer.readBytes(count: Int): ByteArray {
nativeMemUtils.getByteArray(this.reinterpret<ByteVar>().pointed, result, count)
return result
}

@ExperimentalForeignApi
public fun setMemory(mem: NativePointed, value: Byte, size: Long) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please add a small KDoc for all new public functions?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do!

nativeMemUtils.memset(mem, value, size)
}

@ExperimentalForeignApi
public fun copyMemory(destMem: NativePointed, srcMem: NativePointed, size: Long) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need both moveMemory and copyMemory functions? The second one is less safe and performance benefits might not worth it.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No real reason i added memmove apart from "might as well complete the set of three" when i was reading the LLVM manual to look up the copy intrinsic. Can remove if you want :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, I tend to think that memmove should be sufficient for now.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, not entirely happy with that to be honest, since 99% of the time you'd use memcpy, not memmove but that works.

nativeMemUtils.memcpy(destMem, srcMem, size)
}

@ExperimentalForeignApi
public fun moveMemory(destMem: NativePointed, srcMem: NativePointed, size: Long) {
nativeMemUtils.memmove(destMem, srcMem, size)
}

@ExperimentalForeignApi
public fun compareMemory(destMem: NativePointed, srcMem: NativePointed, size: Long): Int {
return nativeMemUtils.memcmp(destMem, srcMem, size)
}

@ExperimentalForeignApi
public inline fun <reified T : CVariable> CPointer<T>.setBlock(value: Byte, length: Int) {
Copy link
Contributor

@sbogolepov sbogolepov Dec 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe just fillWith?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also a good name, will change.

nativeMemUtils.memset(pointed, value, length * sizeOf<T>())
}

@ExperimentalForeignApi
public inline fun <reified T : CVariable> CPointer<T>.copyTo(dest: CPointer<T>, length: Int) {
nativeMemUtils.memcpy(dest.pointed, pointed, length * sizeOf<T>())
}

@ExperimentalForeignApi
public inline fun <reified T : CVariable> CPointer<T>.moveTo(dest: CPointer<T>, length: Int) {
nativeMemUtils.memmove(dest.pointed, pointed, length * sizeOf<T>())
}

@ExperimentalForeignApi
public inline fun <reified T : CVariable> CPointer<T>.compareBlock(dest: CPointer<T>, length: Int): Int {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe compareContentsWith?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that, will change.

return nativeMemUtils.memcmp(dest.pointed, pointed, length * sizeOf<T>())
}
Loading