mirror of
https://github.com/GayPizzaSpecifications/pork.git
synced 2025-11-05 02:19:38 +00:00
Merge remote-tracking branch 'origin/jlf'
This commit is contained in:
@ -30,7 +30,10 @@ class FfiFunctionDefinition(
|
||||
library,
|
||||
functionName,
|
||||
returnType,
|
||||
parameterString.split(",").map { it.trim() }
|
||||
parameterString.splitToSequence(",")
|
||||
.map { it.trim() }
|
||||
.filter { it.isNotEmpty() }
|
||||
.toList()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
46
ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiLibraryCache.kt
Normal file
46
ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiLibraryCache.kt
Normal file
@ -0,0 +1,46 @@
|
||||
package gay.pizza.pork.ffi
|
||||
|
||||
import java.lang.foreign.*
|
||||
|
||||
object FfiLibraryCache {
|
||||
private val dlopenFunctionDescriptor = FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_INT)
|
||||
private val dlsymFunctionDescriptor = FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.ADDRESS)
|
||||
|
||||
private val dlopenMemorySegment = Linker.nativeLinker().defaultLookup().find("dlopen").orElseThrow()
|
||||
private val dlsymMemorySegment = Linker.nativeLinker().defaultLookup().find("dlsym").orElseThrow()
|
||||
|
||||
private val dlopen = Linker.nativeLinker().downcallHandle(
|
||||
dlopenMemorySegment,
|
||||
dlopenFunctionDescriptor
|
||||
)
|
||||
|
||||
private val dlsym = Linker.nativeLinker().downcallHandle(
|
||||
dlsymMemorySegment,
|
||||
dlsymFunctionDescriptor
|
||||
)
|
||||
|
||||
private val libraryHandles = mutableMapOf<String, MemorySegment>()
|
||||
|
||||
private fun dlopen(name: String): MemorySegment {
|
||||
var handle = libraryHandles[name]
|
||||
if (handle != null) {
|
||||
return handle
|
||||
}
|
||||
return Arena.ofConfined().use { arena ->
|
||||
val nameStringPointer = arena.allocateUtf8String(name)
|
||||
handle = dlopen.invokeExact(nameStringPointer, 0) as MemorySegment
|
||||
if (handle == MemorySegment.NULL) {
|
||||
throw RuntimeException("Unable to dlopen library: $name")
|
||||
}
|
||||
handle!!
|
||||
}
|
||||
}
|
||||
|
||||
fun dlsym(name: String, symbol: String): MemorySegment {
|
||||
val libraryHandle = dlopen(name)
|
||||
return Arena.ofConfined().use { arena ->
|
||||
val symbolStringPointer = arena.allocateUtf8String(symbol)
|
||||
dlsym.invokeExact(libraryHandle, symbolStringPointer) as MemorySegment
|
||||
}
|
||||
}
|
||||
}
|
||||
25
ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiMacPlatform.kt
Normal file
25
ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiMacPlatform.kt
Normal file
@ -0,0 +1,25 @@
|
||||
package gay.pizza.pork.ffi
|
||||
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.*
|
||||
|
||||
object FfiMacPlatform : FfiPlatform {
|
||||
private val frameworksDirectories = listOf(
|
||||
"/Library/Frameworks"
|
||||
)
|
||||
|
||||
override fun findLibrary(name: String): Path? {
|
||||
val frameworksToCheck = frameworksDirectories.map { frameworkDirectory ->
|
||||
Path("$frameworkDirectory/$name.framework/$name")
|
||||
}
|
||||
for (framework in frameworksToCheck) {
|
||||
if (!framework.exists()) continue
|
||||
return if (framework.isSymbolicLink()) {
|
||||
return framework.parent.resolve(framework.readSymbolicLink()).absolute()
|
||||
} else {
|
||||
framework.absolute()
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
79
ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiNativeProvider.kt
Normal file
79
ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiNativeProvider.kt
Normal file
@ -0,0 +1,79 @@
|
||||
package gay.pizza.pork.ffi
|
||||
|
||||
import gay.pizza.pork.ast.ArgumentSpec
|
||||
import gay.pizza.pork.evaluator.CallableFunction
|
||||
import gay.pizza.pork.evaluator.NativeProvider
|
||||
import gay.pizza.pork.evaluator.None
|
||||
import java.lang.foreign.*
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.absolutePathString
|
||||
import kotlin.io.path.exists
|
||||
|
||||
class FfiNativeProvider : NativeProvider {
|
||||
private val ffiTypeRegistry = FfiTypeRegistry()
|
||||
|
||||
override fun provideNativeFunction(definitions: List<String>, arguments: List<ArgumentSpec>): CallableFunction {
|
||||
val functionDefinition = FfiFunctionDefinition.parse(definitions[0], definitions[1])
|
||||
val linker = Linker.nativeLinker()
|
||||
val functionAddress = lookupSymbol(functionDefinition)
|
||||
|
||||
val parameters = functionDefinition.parameters.map { id ->
|
||||
ffiTypeRegistry.lookup(id) ?: throw RuntimeException("Unknown ffi type: $id")
|
||||
}
|
||||
|
||||
val returnTypeId = functionDefinition.returnType
|
||||
val returnType = ffiTypeRegistry.lookup(returnTypeId) ?:
|
||||
throw RuntimeException("Unknown ffi return type: $returnTypeId")
|
||||
val parameterArray = parameters.map { typeAsLayout(it) }.toTypedArray()
|
||||
val descriptor = if (returnType == FfiPrimitiveType.Void)
|
||||
FunctionDescriptor.ofVoid(*parameterArray)
|
||||
else FunctionDescriptor.of(typeAsLayout(returnType), *parameterArray)
|
||||
val handle = linker.downcallHandle(functionAddress, descriptor)
|
||||
return CallableFunction { functionArguments, _ ->
|
||||
Arena.ofConfined().use { arena ->
|
||||
handle.invokeWithArguments(functionArguments.map { valueAsFfi(it, arena) }) ?: None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun lookupSymbol(functionDefinition: FfiFunctionDefinition): MemorySegment {
|
||||
if (functionDefinition.library == "c") {
|
||||
return SymbolLookup.loaderLookup().find(functionDefinition.function).orElseThrow {
|
||||
RuntimeException("Unknown function: ${functionDefinition.function}")
|
||||
}
|
||||
}
|
||||
val actualLibraryPath = findLibraryPath(functionDefinition.library)
|
||||
val functionAddress = FfiLibraryCache.dlsym(actualLibraryPath.absolutePathString(), functionDefinition.function)
|
||||
if (functionAddress.address() == 0L) {
|
||||
throw RuntimeException("Unknown function: ${functionDefinition.function} in library $actualLibraryPath")
|
||||
}
|
||||
return functionAddress
|
||||
}
|
||||
|
||||
private fun typeAsLayout(type: FfiType): MemoryLayout = when (type) {
|
||||
FfiPrimitiveType.UnsignedByte, FfiPrimitiveType.Byte -> ValueLayout.JAVA_BYTE
|
||||
FfiPrimitiveType.UnsignedInt, FfiPrimitiveType.Int -> ValueLayout.JAVA_INT
|
||||
FfiPrimitiveType.UnsignedShort, FfiPrimitiveType.Short -> ValueLayout.JAVA_SHORT
|
||||
FfiPrimitiveType.UnsignedLong, FfiPrimitiveType.Long -> ValueLayout.JAVA_LONG
|
||||
FfiPrimitiveType.String -> ValueLayout.ADDRESS
|
||||
FfiPrimitiveType.Pointer -> ValueLayout.ADDRESS
|
||||
FfiPrimitiveType.Void -> MemoryLayout.sequenceLayout(0, ValueLayout.JAVA_INT)
|
||||
else -> throw RuntimeException("Unknown ffi type to convert to memory layout: $type")
|
||||
}
|
||||
|
||||
private fun valueAsFfi(value: Any, allocator: SegmentAllocator): Any = when (value) {
|
||||
is String -> allocator.allocateUtf8String(value)
|
||||
None -> MemorySegment.NULL
|
||||
else -> value
|
||||
}
|
||||
|
||||
private fun findLibraryPath(name: String): Path {
|
||||
val initialPath = Path(name)
|
||||
if (initialPath.exists()) {
|
||||
return initialPath
|
||||
}
|
||||
return FfiPlatforms.current.platform.findLibrary(name)
|
||||
?: throw RuntimeException("Unable to find library: $name")
|
||||
}
|
||||
}
|
||||
24
ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiPlatform.kt
Normal file
24
ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiPlatform.kt
Normal file
@ -0,0 +1,24 @@
|
||||
package gay.pizza.pork.ffi
|
||||
|
||||
import java.nio.file.Path
|
||||
|
||||
enum class FfiPlatforms(val id: String, val platform: FfiPlatform) {
|
||||
Mac("macOS", FfiMacPlatform),
|
||||
Windows("Windows", FfiWindowsPlatform),
|
||||
Unix("Unix", FfiUnixPlatform);
|
||||
|
||||
companion object {
|
||||
val current by lazy {
|
||||
val operatingSystemName = System.getProperty("os.name").lowercase()
|
||||
when {
|
||||
operatingSystemName.contains("win") -> Windows
|
||||
operatingSystemName.contains("mac") -> Mac
|
||||
else -> Unix
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface FfiPlatform {
|
||||
fun findLibrary(name: String): Path?
|
||||
}
|
||||
@ -1,10 +1,11 @@
|
||||
package gay.pizza.pork.ffi
|
||||
|
||||
import gay.pizza.pork.evaluator.None
|
||||
import java.lang.foreign.MemorySegment
|
||||
|
||||
enum class FfiPrimitiveType(
|
||||
val id: kotlin.String,
|
||||
override val size: kotlin.Int,
|
||||
override val size: kotlin.Long,
|
||||
val numberConvert: (Number.() -> Number)? = null,
|
||||
val nullableConversion: (Any?.() -> Any)? = null,
|
||||
val notNullConversion: (Any.() -> Any)? = null
|
||||
@ -22,9 +23,10 @@ enum class FfiPrimitiveType(
|
||||
String("char*", 8, nullableConversion = { toString() }),
|
||||
Pointer("void*", 8, nullableConversion = {
|
||||
if (this is kotlin.Long) {
|
||||
com.sun.jna.Pointer(this)
|
||||
MemorySegment.ofAddress(this)
|
||||
} else if (this == None) {
|
||||
com.sun.jna.Pointer.NULL
|
||||
} else this as com.sun.jna.Pointer
|
||||
})
|
||||
MemorySegment.NULL
|
||||
} else this as MemorySegment
|
||||
}),
|
||||
Void("void", 0)
|
||||
}
|
||||
|
||||
@ -9,6 +9,6 @@ class FfiStruct : FfiType {
|
||||
fields.add(FfiStructField(field, type))
|
||||
}
|
||||
|
||||
override val size: Int
|
||||
override val size: Long
|
||||
get() = fields.sumOf { it.type.size }
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
package gay.pizza.pork.ffi
|
||||
|
||||
interface FfiType {
|
||||
val size: Int
|
||||
val size: Long
|
||||
}
|
||||
|
||||
18
ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiTypeRegistry.kt
Normal file
18
ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiTypeRegistry.kt
Normal file
@ -0,0 +1,18 @@
|
||||
package gay.pizza.pork.ffi
|
||||
|
||||
class FfiTypeRegistry {
|
||||
private val types = mutableMapOf<String, FfiType>()
|
||||
|
||||
init {
|
||||
for (type in FfiPrimitiveType.entries) {
|
||||
add(type.id, type)
|
||||
}
|
||||
add("size_t", FfiPrimitiveType.Long)
|
||||
}
|
||||
|
||||
fun add(name: String, type: FfiType) {
|
||||
types[name] = type
|
||||
}
|
||||
|
||||
fun lookup(name: String): FfiType? = types[name]
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
package gay.pizza.pork.ffi
|
||||
|
||||
import java.nio.file.Path
|
||||
|
||||
object FfiUnixPlatform : FfiPlatform {
|
||||
override fun findLibrary(name: String): Path? = null
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
package gay.pizza.pork.ffi
|
||||
|
||||
import java.nio.file.Path
|
||||
|
||||
object FfiWindowsPlatform : FfiPlatform {
|
||||
override fun findLibrary(name: String): Path? = null
|
||||
}
|
||||
Reference in New Issue
Block a user