Skip to content

Commit

Permalink
Add runtime compilation of specific resources
Browse files Browse the repository at this point in the history
  • Loading branch information
markehammons committed Nov 15, 2022
1 parent 7a743da commit 1396cba
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 6 deletions.
2 changes: 1 addition & 1 deletion build.sc
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ object v {
}

trait BaseModule extends ScalaModule with ScalafmtModule {
def scalaVersion = "3.2.1"
def scalaVersion = "3.2.0"

val munitVersion = "1.0.0-M6"
val jmhV = "1.33"
Expand Down
22 changes: 18 additions & 4 deletions core/src/fr/hammons/slinc/LibraryI.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class LibraryI(platformSpecific: LibraryI.PlatformSpecific):
val (handles, varGens) =
MethodHandleTools.calculateMethodHandles[L](platformSpecific, addresses)

inline def binding[R]: R =
inline def binding[R]: R =
${
LibraryI.bindingImpl[R, Library]
}
Expand All @@ -30,7 +30,9 @@ object LibraryI:
descriptor: Descriptor
): MethodHandle

def getLookup(name: Option[String]): Lookup
def getLocalLookup(name: String): Lookup
def getLibraryPathLookup(name: String): Lookup
def getStandardLibLookup: Lookup

def checkMethodIsCompatible(using q: Quotes)(s: q.reflect.Symbol): Unit =
import quotes.reflect.*
Expand Down Expand Up @@ -235,8 +237,20 @@ object LibraryI:
platformSpecificExpr: Expr[PlatformSpecific]
)(using Quotes, Type[L]) =
import quotes.reflect.*
val name = LibraryName.libraryName[L]
'{ $platformSpecificExpr.getLookup(${ Expr(name) }) }
val name: Option[LibraryLocation] = LibraryName.libraryName[L]
name match
case None => '{ $platformSpecificExpr.getStandardLibLookup }
case Some(LibraryLocation.Local(s)) =>
'{ $platformSpecificExpr.getLocalLookup(${ Expr(s) }) }
case Some(LibraryLocation.Path(s)) =>
'{ $platformSpecificExpr.getLibraryPathLookup(${ Expr(s) }) }
case Some(LibraryLocation.Resource(s)) =>
val sExpr = Expr(s)
val clPath = '{
Tools.sendResourceToCache($sExpr)
Tools.compileCachedResourceIfNeeded($sExpr).toAbsolutePath().nn.toString()
}
'{ $platformSpecificExpr.getLocalLookup($clPath ) }

inline def getMethodAddress[L](l: Lookup) = ${
getMethodAddressImpl[L]('l)
Expand Down
9 changes: 9 additions & 0 deletions core/src/fr/hammons/slinc/LibraryName.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ import scala.quoted.*

class LibraryName(name: String) extends StaticAnnotation

enum LibraryLocation:
case Local(s: String)
case Resource(s: String)
case Path(s: String)

object LibraryName:
def libraryName[L](using Quotes, Type[L]) =
import quotes.reflect.*
Expand All @@ -15,4 +20,8 @@ object LibraryName:
List(Literal(StringConstant(name)))
) =>
name
}.map{
case s"@$path" => LibraryLocation.Resource(path)
case s"#$path" => LibraryLocation.Path(path)
case s => LibraryLocation.Local(s)
}.headOption
52 changes: 52 additions & 0 deletions core/src/fr/hammons/slinc/Tools.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package fr.hammons.slinc

import java.nio.file.{Files, Paths, Path}
import types.{os, OS}
import java.security.MessageDigest
import java.util.HexFormat
import fr.hammons.slinc.types.OS
import fr.hammons.slinc.types.OS
import java.nio.channels.Channel
import java.nio.channels.ByteChannel
import java.nio.channels.Channels
import scala.sys.process.*


object Tools {
private val appDataStore =
Paths.get(os match
case OS.Windows =>
s"${System.getenv("APPDATA").nn}\\local\\slinc\\libstore\\"
case OS.Linux => s"${System.getProperty("user.home").nn}/.cache/slinc/"
case OS.Darwin =>
s"${System.getProperty("user.home").nn}/Library/Application Support/"
).nn

private val sharedLibSuffix =
os match
case OS.Linux | OS.Darwin => ".so"
case OS.Windows => ".dll"
case OS.Unknown => throw Error("Cannot do lib compilation on this platform, it's unknown.")

def sendResourceToCache(name: String) =
println(name)
Files.createDirectories(appDataStore)
val cacheLocation = appDataStore.resolve(s"$name.c")
if !Files.exists(appDataStore.resolve(s"$name.c")) then
Files.copy(getClass().getResourceAsStream(s"/native/$name.c"), cacheLocation)

def compileCachedResourceIfNeeded(name: String): Path =
val cacheLocation = appDataStore.resolve(s"$name$sharedLibSuffix")
val headerLocation = appDataStore.resolve(s"$name.c")

if !Files.exists(cacheLocation) then
val cmd = Seq("clang", "-shared", "-Os", "-o", cacheLocation.nn.toAbsolutePath().nn.toString(), headerLocation.nn.toAbsolutePath().nn.toString())
if cmd.! != 0 then throw Error(s"failed to compile $headerLocation")

cacheLocation.nn

def loadCachedLibrary(name: String) =
val cacheLocation = appDataStore.resolve(s"$name.$sharedLibSuffix")
System.loadLibrary(cacheLocation.nn.toAbsolutePath().nn.toString())

}
8 changes: 8 additions & 0 deletions core/test/src/fr/hammons/slinc/BindingsSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ import fr.hammons.slinc.types.OS
trait BindingsSpec(val slinc: Slinc) extends ScalaCheckSuite:
import slinc.{given, *}

@LibraryName("@test")
object Test derives Library:
def identity_int(i: CInt): CInt = Library.binding

object Cstd derives Library:
def abs(a: Int): Int = Library.binding
def labs(l: CLong): CLong = Library.binding
Expand Down Expand Up @@ -167,5 +171,9 @@ trait BindingsSpec(val slinc: Slinc) extends ScalaCheckSuite:
)
}

test("int_identity") {
assertEquals(Test.identity_int(5), 5)
}

object BindingsSpec:
case class div_t(quot: Int, rem: Int)
42 changes: 41 additions & 1 deletion j17/src/fr/hammons/slinc/Library17.scala
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,47 @@ class Library17(layoutI: LayoutI, linker: CLinker)

linker.downcallHandle(address.asInstanceOf[Addressable], md, fd).nn

override def getLookup(name: Option[String]): Lookup =
private val standardLibLookup = new Lookup:
val lookupImpl = CLinker.systemLookup.nn
def lookup(name: String) = lookupImpl
.lookup(name)
.nn
.orElseThrow(() =>
throw Error(s"Failed to load $name from the standard library")
)
.nn

override def getStandardLibLookup: Lookup =
Tools.hashCode()

standardLibLookup

override def getLibraryPathLookup(libName: String): Lookup =
new Lookup:
System.loadLibrary(libName)
val lookupImpl = SymbolLookup.loaderLookup().nn
def lookup(symbolName: String) = lookupImpl
.lookup(symbolName)
.nn
.orElseThrow(() =>
throw Error(s"Failed to load ${symbolName} from $libName")
)
.nn

override def getLocalLookup(libPath: String): Lookup =
new Lookup:
System.load(libPath)

val lookupImpl = SymbolLookup.loaderLookup().nn
def lookup(symbolName: String) = lookupImpl
.lookup(symbolName)
.nn
.orElseThrow(() =>
throw Error(s"Failed to load $symbolName from $libPath")
)
.nn

def getLookup(name: Option[String]): Lookup =
import scala.jdk.OptionConverters.*
name match
case Some(n) =>
Expand Down
3 changes: 3 additions & 0 deletions j17/test/resources/native/test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
int identity_int(int i) {
return i;
}

0 comments on commit 1396cba

Please sign in to comment.