ffi: java interop improvements

This commit is contained in:
Alex Zenla 2023-09-07 18:16:47 -07:00
parent a2f2252965
commit 38efbe1844
Signed by: alex
GPG Key ID: C0780728420EBFE5
8 changed files with 126 additions and 36 deletions

View File

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

View File

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

View File

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

View File

@ -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<FunctionDefinition> {
val definitions = mutableMapOf<String, FunctionDefinition>()
val methodGroups = mutableMapOf<String, MutableList<Method>>()
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<String>, functionDefinition: JavaFunctionDefinition): FunctionDefinition =
private fun function(
name: String,
parameterNames: List<String>,
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")
}

View File

@ -35,12 +35,21 @@ class JavaNativeProvider : NativeFunctionProvider {
else -> lookup.findClass(name)
}
private fun mapKindToHandle(kind: String, symbol: String, javaClass: Class<*>, returnType: Class<*>, parameterTypes: List<Class<*>>) = when (kind) {
private fun mapKindToHandle(
kind: String,
symbol: String,
javaClass: Class<*>,
returnType: Class<*>,
parameterTypes: List<Class<*>>
) = 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")

View File

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

View File

@ -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*"

View File

@ -37,6 +37,12 @@ graalvmNative {
mainClass.set("gay.pizza.pork.tool.MainKt")
sharedLibrary.set(false)
buildArgs("-march=compatibility")
resources {
includedPatterns.addAll(listOf(
".*/*.pork$",
".*/*.manifest$"
))
}
}
}
}