Skip to content

Instantly share code, notes, and snippets.

@curioustorvald
Last active January 2, 2024 20:17
Show Gist options
  • Save curioustorvald/d60cf6b7ff7717a9694de741f22127b4 to your computer and use it in GitHub Desktop.
Save curioustorvald/d60cf6b7ff7717a9694de741f22127b4 to your computer and use it in GitHub Desktop.
Java/Kotlin unsafe pointer for unsafe array
package net.torvald
import sun.misc.Unsafe
/**
* Created by minjaesong on 2019-06-21.
*/
object UnsafeHelper {
internal val unsafe: Unsafe
init {
val unsafeConstructor = Unsafe::class.java.getDeclaredConstructor()
unsafeConstructor.isAccessible = true
unsafe = unsafeConstructor.newInstance()
}
/**
* A factory method to allocate a memory of given size and return its starting address as a pointer.
*/
fun allocate(size: Long): UnsafePtr {
val ptr = unsafe.allocateMemory(size)
return UnsafePtr(ptr, size)
}
fun memcpy(src: UnsafePtr, fromIndex: Long, dest: UnsafePtr, toIndex: Long, copyLength: Long) =
unsafe.copyMemory(src.ptr + fromIndex, dest.ptr + toIndex, copyLength)
fun memcpy(srcAddress: Long, destAddress: Long, copyLength: Long) =
unsafe.copyMemory(srcAddress, destAddress, copyLength)
fun memcpyRaw(srcObj: Any?, srcPos: Long, destObj: Any?, destPos: Long, len: Long) =
unsafe.copyMemory(srcObj, srcPos, destObj, destPos, len)
/**
* The array object in JVM is stored in this memory map:
*
* 0 w 2w *
* | Some identifier | Other identifier | the actual data ... |
*
* (where w = 4 for 32-bit JVM and 8 for 64-bit JVM. If Compressed-OOP is involved, things may get complicated)
*
* @return offset from the array's base memory address (aka pointer) that the actual data begins.
*/
fun getArrayOffset(obj: Any) = unsafe.arrayBaseOffset(obj.javaClass)
}
/**
* To allocate a memory, use UnsafeHelper.allocate(long)
*
* All the getFloat/Int/whatever methods will follow the endianness of your system,
* e.g. it'll be Little Endian on x86, Big Endian on PPC, User-defined on ARM; therefore these functions should not be
* used when the portability matters (e.g. Savefile). In such situations, do byte-wise operations will be needed.
*/
class UnsafePtr(val ptr: Long, val allocSize: Long) {
var destroyed = false
private set
fun destroy() {
if (!destroyed) {
UnsafeHelper.unsafe.freeMemory(ptr)
println("[UnsafePtr] Destroying pointer $this; called from:")
Thread.currentThread().stackTrace.forEach { println(it) }
destroyed = true
}
}
private inline fun checkNullPtr(index: Long) { // ignore what IDEA says and do inline this
if (destroyed) throw NullPointerException("The pointer is already destroyed ($this)")
// OOB Check: debugging purposes only -- comment out for the production
//if (index !in 0 until allocSize) throw IndexOutOfBoundsException("Index: $index; alloc size: $allocSize")
}
operator fun get(index: Long): Byte {
checkNullPtr(index)
return UnsafeHelper.unsafe.getByte(ptr + index)
}
fun getFloat(index: Long): Float {
checkNullPtr(index)
return UnsafeHelper.unsafe.getFloat(ptr + index)
}
fun getInt(index: Long): Int {
checkNullPtr(index)
return UnsafeHelper.unsafe.getInt(ptr + index)
}
operator fun set(index: Long, value: Byte) {
checkNullPtr(index)
UnsafeHelper.unsafe.putByte(ptr + index, value)
}
fun setFloat(index: Long, value: Float) {
checkNullPtr(index)
UnsafeHelper.unsafe.putFloat(ptr + index, value)
}
fun setInt(index: Long, value: Int) {
checkNullPtr(index)
UnsafeHelper.unsafe.putInt(ptr + index, value)
}
fun fillWith(byte: Byte) {
UnsafeHelper.unsafe.setMemory(ptr, allocSize, byte)
}
override fun toString() = "0x${ptr.toString(16)} with size $allocSize"
override fun equals(other: Any?) = this.ptr == (other as UnsafePtr).ptr
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment