-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ffi: migrate to java.lang.foreign and require Java 21
- Loading branch information
Showing
26 changed files
with
270 additions
and
798 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,7 @@ | ||
plugins { | ||
kotlin("jvm") version "1.9.10" apply false | ||
kotlin("plugin.serialization") version "1.9.10" apply false | ||
id("gay.pizza.pork.root") | ||
} | ||
|
||
tasks.withType<Wrapper> { | ||
gradleVersion = "8.3" | ||
gradleVersion = "8.4-rc-1" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
8 changes: 8 additions & 0 deletions
8
buildext/src/main/kotlin/gay/pizza/pork/buildext/PorkRootPlugin.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package gay.pizza.pork.buildext | ||
|
||
import org.gradle.api.Plugin | ||
import org.gradle.api.Project | ||
|
||
class PorkRootPlugin : Plugin<Project> { | ||
override fun apply(target: Project) {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 changes: 79 additions & 0 deletions
79
ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiNativeProvider.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
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") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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? | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
package gay.pizza.pork.ffi | ||
|
||
interface FfiType { | ||
val size: Int | ||
val size: Long | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package gay.pizza.pork.ffi | ||
|
||
import java.nio.file.Path | ||
|
||
object FfiUnixPlatform : FfiPlatform { | ||
override fun findLibrary(name: String): Path? = null | ||
} |
Oops, something went wrong.