From 38efbe1844112cf23799b948c7a48c5fa1b17989 Mon Sep 17 00:00:00 2001 From: Alex Zenla Date: Thu, 7 Sep 2023 18:16:47 -0700 Subject: [PATCH] ffi: java interop improvements --- examples/ffi.pork | 8 +- examples/java.pork | 11 +- .../pizza/pork/ffi/FfiFunctionDefinition.kt | 7 +- .../kotlin/gay/pizza/pork/ffi/JavaAutogen.kt | 104 +++++++++++++++--- .../gay/pizza/pork/ffi/JavaNativeProvider.kt | 17 ++- .../gay/pizza/pork/ffi/JnaNativeProvider.kt | 2 +- stdlib/src/main/pork/ffi/malloc.pork | 7 +- tool/build.gradle.kts | 6 + 8 files changed, 126 insertions(+), 36 deletions(-) diff --git a/examples/ffi.pork b/examples/ffi.pork index 715fe4c..c2a1af3 100644 --- a/examples/ffi.pork +++ b/examples/ffi.pork @@ -1,9 +1,7 @@ import std ffi.malloc export func main() { - while true { - let pointer = malloc(8192) - println(pointer) - free(pointer) - } + let pointer = malloc(8192) + println(pointer) + free(pointer) } diff --git a/examples/java.pork b/examples/java.pork index 4c1ab86..456a80a 100644 --- a/examples/java.pork +++ b/examples/java.pork @@ -1,8 +1,13 @@ import java java.lang.System - -func java_io_PrintStream_println(a) native java "java.io.PrintStream:virtual:println:void:String" +import java java.io.PrintStream +import java java.io.InputStreamReader +import java java.io.BufferedReader export func main() { + let input = java_lang_System_in_get() + let reader = java_io_InputStreamReader_new_inputstream(input) + let bufferedReader = java_io_BufferedReader_new_reader(reader) + let line = java_io_BufferedReader_readLine(bufferedReader) let stream = java_lang_System_err_get() - java_io_PrintStream_println(stream, "Hello World") + java_io_PrintStream_println_string(stream, line) } diff --git a/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiFunctionDefinition.kt b/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiFunctionDefinition.kt index 65f1683..be94144 100644 --- a/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiFunctionDefinition.kt +++ b/ffi/src/main/kotlin/gay/pizza/pork/ffi/FfiFunctionDefinition.kt @@ -7,11 +7,12 @@ class FfiFunctionDefinition( ) { companion object { fun parse(def: String): FfiFunctionDefinition { - val parts = def.split(":", limit = 3) - if (parts.size != 3 || parts.any { it.trim().isEmpty() }) { + val parts = def.split(":", limit = 4) + if (parts.size !in arrayOf(3, 4) || parts.any { it.trim().isEmpty() }) { throw RuntimeException( "FFI function definition is invalid, " + - "excepted format is 'library:function:return-type' but '${def}' was specified") + "excepted format is 'library:function:return-type:(optional)parameters'" + + " but '${def}' was specified") } val (library, function, returnType) = parts return FfiFunctionDefinition(library, function, returnType) diff --git a/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaAutogen.kt b/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaAutogen.kt index 3bddef9..4f27916 100644 --- a/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaAutogen.kt +++ b/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaAutogen.kt @@ -1,8 +1,14 @@ package gay.pizza.pork.ffi -import gay.pizza.pork.ast.* -import java.io.PrintStream +import gay.pizza.pork.ast.CompilationUnit +import gay.pizza.pork.ast.DefinitionModifiers +import gay.pizza.pork.ast.FunctionDefinition +import gay.pizza.pork.ast.Native +import gay.pizza.pork.ast.StringLiteral +import gay.pizza.pork.ast.Symbol +import java.lang.reflect.Method import java.lang.reflect.Modifier +import java.lang.reflect.Parameter class JavaAutogen(val javaClass: Class<*>) { private val prefix = javaClass.name.replace(".", "_") @@ -16,23 +22,62 @@ class JavaAutogen(val javaClass: Class<*>) { fun generateFunctionDefinitions(): List { val definitions = mutableMapOf() + + val methodGroups = mutableMapOf>() for (method in javaClass.methods) { if (!Modifier.isPublic(method.modifiers)) { continue } + methodGroups.getOrPut(method.name) { mutableListOf() }.add(method) + } - val name = method.name - val returnTypeName = method.returnType.name - val parameterNames = method.parameters.indices.map { ('a' + it).toString() } - val parameterTypeNames = method.parameters.map { it.type.name } + for ((baseName, methods) in methodGroups) { + for (method in methods) { + var name = baseName + if (methods.size > 1 && method.parameters.isNotEmpty()) { + name += "_" + method.parameters.joinToString("_") { + discriminate(it) + } + } + val returnTypeName = method.returnType.name + val parameterNames = method.parameters.indices.map { ('a' + it).toString() } + val parameterTypeNames = method.parameters.map { it.type.name } - fun form(kind: String): JavaFunctionDefinition = - JavaFunctionDefinition(javaClass.name, kind, name, returnTypeName, parameterTypeNames) + fun form(kind: String): JavaFunctionDefinition = + JavaFunctionDefinition( + javaClass.name, + kind, + method.name, + returnTypeName, + parameterTypeNames + ) - if (Modifier.isStatic(method.modifiers)) { - definitions[name] = function(name, parameterNames, form("static")) - } else { - definitions[name] = function(name, parameterNames, form("virtual")) + if (Modifier.isStatic(method.modifiers)) { + definitions[name] = function(name, parameterNames, form("static")) + } else { + definitions[name] = function(name, parameterNames, form("virtual")) + } + } + + for (constructor in javaClass.constructors) { + val parameterNames = constructor.parameters.indices.map { ('a' + it).toString() } + val parameterTypeNames = constructor.parameters.map { it.type.name } + + var name = "new" + if (javaClass.constructors.isNotEmpty()) { + name += "_" + constructor.parameters.joinToString("_") { + discriminate(it) + } + } + + val javaFunctionDefinition = JavaFunctionDefinition( + javaClass.name, + "constructor", + "new", + javaClass.name, + parameterTypeNames + ) + definitions[name] = function(name, parameterNames, javaFunctionDefinition) } } @@ -44,8 +89,8 @@ class JavaAutogen(val javaClass: Class<*>) { val name = field.name val valueTypeName = field.type.name val isStatic = Modifier.isStatic(field.modifiers) - fun form(kind: String, getOrSet: Boolean): JavaFunctionDefinition = - JavaFunctionDefinition(javaClass.name, kind, name, valueTypeName, if (getOrSet) { + fun form(kind: String, getOrSet: Boolean): JavaFunctionDefinition { + val parameters = if (getOrSet) { if (isStatic) { emptyList() } else { @@ -57,7 +102,15 @@ class JavaAutogen(val javaClass: Class<*>) { } else { listOf(javaClass.name, valueTypeName) } - }) + } + return JavaFunctionDefinition( + javaClass.name, + kind, + name, + valueTypeName, + parameters + ) + } val parametersForGetter = if (isStatic) { emptyList() @@ -73,14 +126,26 @@ class JavaAutogen(val javaClass: Class<*>) { val getterKind = if (isStatic) "static-getter" else "getter" val setterKind = if (isStatic) "static-setter" else "setter" - definitions[name + "_get"] = function(name + "_get", parametersForGetter, form(getterKind, true)) - definitions[name + "_set"] = function(name + "_set", parametersForSetter, form(setterKind, false)) + definitions[name + "_get"] = function( + name + "_get", + parametersForGetter, + form(getterKind, true) + ) + definitions[name + "_set"] = function( + name + "_set", + parametersForSetter, + form(setterKind, false) + ) } return definitions.values.toList() } - private fun function(name: String, parameterNames: List, functionDefinition: JavaFunctionDefinition): FunctionDefinition = + private fun function( + name: String, + parameterNames: List, + functionDefinition: JavaFunctionDefinition + ): FunctionDefinition = FunctionDefinition( modifiers = DefinitionModifiers(true), symbol = Symbol("${prefix}_${name}"), @@ -91,4 +156,7 @@ class JavaAutogen(val javaClass: Class<*>) { private fun asNative(functionDefinition: JavaFunctionDefinition): Native = Native(Symbol("java"), StringLiteral(functionDefinition.encode())) + + private fun discriminate(parameter: Parameter): String = + parameter.type.simpleName.lowercase().replace("[]", "_array") } diff --git a/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaNativeProvider.kt b/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaNativeProvider.kt index 4a3698e..f0eed5c 100644 --- a/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaNativeProvider.kt +++ b/ffi/src/main/kotlin/gay/pizza/pork/ffi/JavaNativeProvider.kt @@ -35,12 +35,21 @@ class JavaNativeProvider : NativeFunctionProvider { else -> lookup.findClass(name) } - private fun mapKindToHandle(kind: String, symbol: String, javaClass: Class<*>, returnType: Class<*>, parameterTypes: List>) = when (kind) { + private fun mapKindToHandle( + kind: String, + symbol: String, + javaClass: Class<*>, + returnType: Class<*>, + parameterTypes: List> + ) = when (kind) { "getter" -> lookup.findGetter(javaClass, symbol, returnType) "setter" -> lookup.findSetter(javaClass, symbol, returnType) - "constructor" -> lookup.findConstructor(javaClass, MethodType.methodType(returnType, parameterTypes)) - "static" -> lookup.findStatic(javaClass, symbol, MethodType.methodType(returnType, parameterTypes)) - "virtual" -> lookup.findVirtual(javaClass, symbol, MethodType.methodType(returnType, parameterTypes)) + "constructor" -> + lookup.findConstructor(javaClass, MethodType.methodType(Void.TYPE, parameterTypes)) + "static" -> + lookup.findStatic(javaClass, symbol, MethodType.methodType(returnType, parameterTypes)) + "virtual" -> + lookup.findVirtual(javaClass, symbol, MethodType.methodType(returnType, parameterTypes)) "static-getter" -> lookup.findStaticGetter(javaClass, symbol, returnType) "static-setter" -> lookup.findStaticSetter(javaClass, symbol, returnType) else -> throw RuntimeException("Unknown Handle Kind: $kind") diff --git a/ffi/src/main/kotlin/gay/pizza/pork/ffi/JnaNativeProvider.kt b/ffi/src/main/kotlin/gay/pizza/pork/ffi/JnaNativeProvider.kt index 4016e30..a94c8be 100644 --- a/ffi/src/main/kotlin/gay/pizza/pork/ffi/JnaNativeProvider.kt +++ b/ffi/src/main/kotlin/gay/pizza/pork/ffi/JnaNativeProvider.kt @@ -9,7 +9,7 @@ class JnaNativeProvider : NativeFunctionProvider { val functionDefinition = FfiFunctionDefinition.parse(definition) val function = Function.getFunction(functionDefinition.library, functionDefinition.function) return CallableFunction { - return@CallableFunction invoke(function, it.values.toTypedArray(), functionDefinition.returnType) + invoke(function, it.values.toTypedArray(), functionDefinition.returnType) } } diff --git a/stdlib/src/main/pork/ffi/malloc.pork b/stdlib/src/main/pork/ffi/malloc.pork index 0854d36..4e955fc 100644 --- a/stdlib/src/main/pork/ffi/malloc.pork +++ b/stdlib/src/main/pork/ffi/malloc.pork @@ -1,5 +1,8 @@ export func malloc(size) - native ffi "c:malloc:void*" + native ffi "c:malloc:void*:size_t" + +export func calloc(size, count) + native ffi "c:calloc:void*:size_t,size_t" export func free(pointer) - native ffi "c:free:void" + native ffi "c:free:void:void*" diff --git a/tool/build.gradle.kts b/tool/build.gradle.kts index 9e623ea..3fecbd9 100644 --- a/tool/build.gradle.kts +++ b/tool/build.gradle.kts @@ -37,6 +37,12 @@ graalvmNative { mainClass.set("gay.pizza.pork.tool.MainKt") sharedLibrary.set(false) buildArgs("-march=compatibility") + resources { + includedPatterns.addAll(listOf( + ".*/*.pork$", + ".*/*.manifest$" + )) + } } } }