Merge remote-tracking branch 'origin/jlf'

This commit is contained in:
Alex Zenla 2023-10-06 16:09:55 -07:00
commit 0d8c6723de
Signed by: alex
GPG Key ID: C0780728420EBFE5
22 changed files with 255 additions and 693 deletions

View File

@ -1,6 +1,5 @@
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> {

View File

@ -1,4 +1,6 @@
@file:Suppress("UnstableApiUsage")
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
`kotlin-dsl`
embeddedKotlin("plugin.serialization")
@ -16,8 +18,25 @@ dependencies {
implementation("com.charleskorn.kaml:kaml:0.55.0")
}
java {
sourceCompatibility = JavaVersion.toVersion(17)
targetCompatibility = JavaVersion.toVersion(17)
}
tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "17"
}
gradlePlugin {
plugins {
create("pork_root") {
id = "gay.pizza.pork.root"
implementationClass = "gay.pizza.pork.buildext.PorkRootPlugin"
displayName = "Pork Root"
description = "Root convention for pork"
}
create("pork_ast") {
id = "gay.pizza.pork.ast"
implementationClass = "gay.pizza.pork.buildext.PorkAstPlugin"

View File

@ -5,6 +5,7 @@ import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.kotlin.dsl.*
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
open class PorkModulePlugin : Plugin<Project> {

View File

@ -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) {}
}

View File

@ -7,5 +7,4 @@ dependencies {
api(project(":evaluator"))
implementation(project(":common"))
implementation("net.java.dev.jna:jna:5.13.0")
}

View File

@ -30,7 +30,10 @@ class FfiFunctionDefinition(
library,
functionName,
returnType,
parameterString.split(",").map { it.trim() }
parameterString.splitToSequence(",")
.map { it.trim() }
.filter { it.isNotEmpty() }
.toList()
)
}
}

View 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
}
}
}

View 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
}
}

View 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")
}
}

View 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?
}

View File

@ -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)
}

View File

@ -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 }
}

View File

@ -1,5 +1,5 @@
package gay.pizza.pork.ffi
interface FfiType {
val size: Int
val size: Long
}

View 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]
}

View File

@ -0,0 +1,7 @@
package gay.pizza.pork.ffi
import java.nio.file.Path
object FfiUnixPlatform : FfiPlatform {
override fun findLibrary(name: String): Path? = null
}

View File

@ -0,0 +1,7 @@
package gay.pizza.pork.ffi
import java.nio.file.Path
object FfiWindowsPlatform : FfiPlatform {
override fun findLibrary(name: String): Path? = null
}

View File

@ -1,653 +0,0 @@
[
{
"name": "[Z"
},
{
"name": "com.sun.jna.Callback"
},
{
"name": "com.sun.jna.CallbackReference",
"methods": [
{
"name": "getCallback",
"parameterTypes": [
"java.lang.Class",
"com.sun.jna.Pointer",
"boolean"
]
},
{
"name": "getFunctionPointer",
"parameterTypes": [
"com.sun.jna.Callback",
"boolean"
]
},
{
"name": "getNativeString",
"parameterTypes": [
"java.lang.Object",
"boolean"
]
},
{
"name": "initializeThread",
"parameterTypes": [
"com.sun.jna.Callback",
"com.sun.jna.CallbackReference$AttachOptions"
]
}
]
},
{
"name": "com.sun.jna.CallbackReference$AttachOptions"
},
{
"name": "com.sun.jna.FromNativeConverter",
"methods": [
{
"name": "nativeType",
"parameterTypes": []
}
]
},
{
"name": "com.sun.jna.IntegerType",
"fields": [
{
"name": "value"
}
]
},
{
"name": "com.sun.jna.JNIEnv"
},
{
"name": "com.sun.jna.Native",
"methods": [
{
"name": "dispose",
"parameterTypes": []
},
{
"name": "fromNative",
"parameterTypes": [
"com.sun.jna.FromNativeConverter",
"java.lang.Object",
"java.lang.reflect.Method"
]
},
{
"name": "fromNative",
"parameterTypes": [
"java.lang.Class",
"java.lang.Object"
]
},
{
"name": "fromNative",
"parameterTypes": [
"java.lang.reflect.Method",
"java.lang.Object"
]
},
{
"name": "nativeType",
"parameterTypes": [
"java.lang.Class"
]
},
{
"name": "toNative",
"parameterTypes": [
"com.sun.jna.ToNativeConverter",
"java.lang.Object"
]
}
]
},
{
"name": "com.sun.jna.Native$ffi_callback",
"methods": [
{
"name": "invoke",
"parameterTypes": [
"long",
"long",
"long"
]
}
]
},
{
"name": "com.sun.jna.NativeMapped",
"methods": [
{
"name": "toNative",
"parameterTypes": []
}
]
},
{
"name": "com.sun.jna.Pointer",
"fields": [
{
"name": "peer"
}
],
"methods": [
{
"name": "<init>",
"parameterTypes": [
"long"
]
}
]
},
{
"name": "com.sun.jna.PointerType",
"fields": [
{
"name": "pointer"
}
]
},
{
"name": "com.sun.jna.Structure",
"fields": [
{
"name": "memory"
},
{
"name": "typeInfo"
}
],
"methods": [
{
"name": "autoRead",
"parameterTypes": []
},
{
"name": "autoWrite",
"parameterTypes": []
},
{
"name": "getTypeInfo",
"parameterTypes": []
},
{
"name": "newInstance",
"parameterTypes": [
"java.lang.Class",
"long"
]
}
]
},
{
"name": "com.sun.jna.Structure$ByValue"
},
{
"name": "com.sun.jna.Structure$FFIType$FFITypes",
"fields": [
{
"name": "ffi_type_double"
},
{
"name": "ffi_type_float"
},
{
"name": "ffi_type_longdouble"
},
{
"name": "ffi_type_pointer"
},
{
"name": "ffi_type_sint16"
},
{
"name": "ffi_type_sint32"
},
{
"name": "ffi_type_sint64"
},
{
"name": "ffi_type_sint8"
},
{
"name": "ffi_type_uint16"
},
{
"name": "ffi_type_uint32"
},
{
"name": "ffi_type_uint64"
},
{
"name": "ffi_type_uint8"
},
{
"name": "ffi_type_void"
}
]
},
{
"name": "com.sun.jna.WString",
"methods": [
{
"name": "<init>",
"parameterTypes": [
"java.lang.String"
]
}
]
},
{
"name": "java.lang.Boolean",
"fields": [
{
"name": "TYPE"
},
{
"name": "value"
}
],
"methods": [
{
"name": "<init>",
"parameterTypes": [
"boolean"
]
},
{
"name": "getBoolean",
"parameterTypes": [
"java.lang.String"
]
}
]
},
{
"name": "java.lang.Byte",
"fields": [
{
"name": "TYPE"
},
{
"name": "value"
}
],
"methods": [
{
"name": "<init>",
"parameterTypes": [
"byte"
]
}
]
},
{
"name": "java.lang.Character",
"fields": [
{
"name": "TYPE"
},
{
"name": "value"
}
],
"methods": [
{
"name": "<init>",
"parameterTypes": [
"char"
]
}
]
},
{
"name": "java.lang.Class",
"methods": [
{
"name": "getComponentType",
"parameterTypes": []
}
]
},
{
"name": "java.lang.Double",
"fields": [
{
"name": "TYPE"
},
{
"name": "value"
}
],
"methods": [
{
"name": "<init>",
"parameterTypes": [
"double"
]
}
]
},
{
"name": "java.lang.Float",
"fields": [
{
"name": "TYPE"
},
{
"name": "value"
}
],
"methods": [
{
"name": "<init>",
"parameterTypes": [
"float"
]
}
]
},
{
"name": "java.lang.Integer",
"fields": [
{
"name": "TYPE"
},
{
"name": "value"
}
],
"methods": [
{
"name": "<init>",
"parameterTypes": [
"int"
]
}
]
},
{
"name": "java.lang.Long",
"fields": [
{
"name": "TYPE"
},
{
"name": "value"
}
],
"methods": [
{
"name": "<init>",
"parameterTypes": [
"long"
]
}
]
},
{
"name": "java.lang.Object",
"methods": [
{
"name": "toString",
"parameterTypes": []
}
]
},
{
"name": "java.lang.Short",
"fields": [
{
"name": "TYPE"
},
{
"name": "value"
}
],
"methods": [
{
"name": "<init>",
"parameterTypes": [
"short"
]
}
]
},
{
"name": "java.lang.String",
"methods": [
{
"name": "<init>",
"parameterTypes": [
"byte[]"
]
},
{
"name": "<init>",
"parameterTypes": [
"byte[]",
"java.lang.String"
]
},
{
"name": "getBytes",
"parameterTypes": []
},
{
"name": "getBytes",
"parameterTypes": [
"java.lang.String"
]
},
{
"name": "lastIndexOf",
"parameterTypes": [
"int"
]
},
{
"name": "substring",
"parameterTypes": [
"int"
]
},
{
"name": "toCharArray",
"parameterTypes": []
}
]
},
{
"name": "java.lang.System",
"methods": [
{
"name": "getProperty",
"parameterTypes": [
"java.lang.String"
]
},
{
"name": "setProperty",
"parameterTypes": [
"java.lang.String",
"java.lang.String"
]
}
]
},
{
"name": "java.lang.Throwable",
"methods": [
{
"name": "toString",
"parameterTypes": []
}
]
},
{
"name": "java.lang.UnsatisfiedLinkError",
"methods": [
{
"name": "<init>",
"parameterTypes": [
"java.lang.String"
]
}
]
},
{
"name": "java.lang.Void",
"fields": [
{
"name": "TYPE"
}
]
},
{
"name": "java.lang.reflect.Method",
"methods": [
{
"name": "getParameterTypes",
"parameterTypes": []
},
{
"name": "getReturnType",
"parameterTypes": []
}
]
},
{
"name": "java.nio.Buffer",
"methods": [
{
"name": "position",
"parameterTypes": []
}
]
},
{
"name": "java.nio.ByteBuffer",
"methods": [
{
"name": "array",
"parameterTypes": []
},
{
"name": "arrayOffset",
"parameterTypes": []
}
]
},
{
"name": "java.nio.CharBuffer",
"methods": [
{
"name": "array",
"parameterTypes": []
},
{
"name": "arrayOffset",
"parameterTypes": []
}
]
},
{
"name": "java.nio.DoubleBuffer",
"methods": [
{
"name": "array",
"parameterTypes": []
},
{
"name": "arrayOffset",
"parameterTypes": []
}
]
},
{
"name": "java.nio.FloatBuffer",
"methods": [
{
"name": "array",
"parameterTypes": []
},
{
"name": "arrayOffset",
"parameterTypes": []
}
]
},
{
"name": "java.nio.IntBuffer",
"methods": [
{
"name": "array",
"parameterTypes": []
},
{
"name": "arrayOffset",
"parameterTypes": []
}
]
},
{
"name": "java.nio.LongBuffer",
"methods": [
{
"name": "array",
"parameterTypes": []
},
{
"name": "arrayOffset",
"parameterTypes": []
}
]
},
{
"name": "java.nio.ShortBuffer",
"methods": [
{
"name": "array",
"parameterTypes": []
},
{
"name": "arrayOffset",
"parameterTypes": []
}
]
},
{
"name": "sun.management.VMManagementImpl",
"fields": [
{
"name": "compTimeMonitoringSupport"
},
{
"name": "currentThreadCpuTimeSupport"
},
{
"name": "objectMonitorUsageSupport"
},
{
"name": "otherThreadCpuTimeSupport"
},
{
"name": "remoteDiagnosticCommandsSupport"
},
{
"name": "synchronizerUsageSupport"
},
{
"name": "threadAllocatedMemorySupport"
},
{
"name": "threadContentionMonitoringSupport"
}
]
}
]

View File

@ -1,25 +0,0 @@
{
"resources": {
"includes": [
{
"pattern": "\\Qcom/sun/jna/darwin-x86-64/libjnidispatch.jnilib\\E"
},
{
"pattern": "\\Qcom/sun/jna/linux-x86-64/libjnidispatch.so\\E"
},
{
"pattern": "\\Qcom/sun/jna/win32-x86-64/jnidispatch.dll\\E"
},
{
"pattern": "\\Qcom/sun/jna/darwin-aarch64/libjnidispatch.jnilib\\E"
},
{
"pattern": "\\Qcom/sun/jna/linux-aarch64/libjnidispatch.so\\E"
},
{
"pattern": "\\Qcom/sun/jna/win32-aarch64/jnidispatch.dll\\E"
}
]
},
"bundles": []
}

View File

@ -18,6 +18,8 @@ dependencies {
application {
applicationName = "pork-rt"
mainClass.set("gay.pizza.pork.minimal.MainKt")
applicationDefaultJvmArgs += "-XstartOnFirstThread"
applicationDefaultJvmArgs += "--enable-native-access=ALL-UNNAMED"
}
for (task in arrayOf(tasks.shadowDistTar, tasks.shadowDistZip, tasks.shadowJar)) {

View File

@ -4,9 +4,9 @@ import gay.pizza.pork.ast.gen.CompilationUnit
import gay.pizza.pork.ast.gen.NodeVisitor
import gay.pizza.pork.ast.gen.visit
import gay.pizza.pork.evaluator.*
import gay.pizza.pork.ffi.FfiNativeProvider
import gay.pizza.pork.ffi.JavaAutogenContentSource
import gay.pizza.pork.ffi.JavaNativeProvider
import gay.pizza.pork.ffi.JnaNativeProvider
import gay.pizza.pork.frontend.ContentSource
import gay.pizza.pork.frontend.ImportLocator
import gay.pizza.pork.frontend.DynamicImportSource
@ -55,7 +55,7 @@ abstract class Tool {
fun run(scope: Scope, quiet: Boolean = false) {
val main = loadMainFunction(scope, setupEvaluator = {
addNativeProvider("internal", InternalNativeProvider(quiet = quiet))
addNativeProvider("ffi", JnaNativeProvider())
addNativeProvider("ffi", FfiNativeProvider())
addNativeProvider("java", JavaNativeProvider())
})
main.call(emptyList(), CallStack())

View File

@ -14,6 +14,7 @@ application {
applicationName = "pork"
mainClass.set("gay.pizza.pork.tool.MainKt")
applicationDefaultJvmArgs += "-XstartOnFirstThread"
applicationDefaultJvmArgs += "--enable-native-access=ALL-UNNAMED"
}
for (task in arrayOf(tasks.shadowDistTar, tasks.shadowDistZip, tasks.shadowJar)) {

View File

@ -6,8 +6,8 @@ import com.github.ajalt.clikt.parameters.options.flag
import com.github.ajalt.clikt.parameters.options.option
import gay.pizza.dough.fs.PlatformFsProvider
import gay.pizza.pork.evaluator.*
import gay.pizza.pork.ffi.FfiNativeProvider
import gay.pizza.pork.ffi.JavaNativeProvider
import gay.pizza.pork.ffi.JnaNativeProvider
import gay.pizza.pork.minimal.FileTool
class RunCommand : CliktCommand(help = "Run Program", name = "run") {
@ -22,7 +22,7 @@ class RunCommand : CliktCommand(help = "Run Program", name = "run") {
val scope = Scope.root()
val main = tool.loadMainFunction(scope, setupEvaluator = {
addNativeProvider("internal", InternalNativeProvider(quiet = quiet))
addNativeProvider("ffi", JnaNativeProvider())
addNativeProvider("ffi", FfiNativeProvider())
addNativeProvider("java", JavaNativeProvider())
})