mirror of
				https://github.com/GayPizzaSpecifications/pork.git
				synced 2025-11-04 09:59:39 +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