Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement script level cache #403

Merged
merged 1 commit into from Jul 3, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 4 additions & 1 deletion repl/src/main/scala/ammonite/repl/Main.scala
Expand Up @@ -9,6 +9,9 @@ import scala.reflect.internal.annotations.compileTimeOnly
import scala.reflect.runtime.universe.TypeTag
import language.experimental.macros
import reflect.macros.Context
import scala.collection.mutable
import scala.util._
import Util.CompileCache


/**
Expand Down Expand Up @@ -90,7 +93,7 @@ case class Main(predef: String = "",
) match{
case x: Res.Failing => x
case Res.Success(imports) =>
repl.interp.init()
repl.interp.reInit()
imports.value.find(_.toName == Name("main")) match {
case None => Res.Success(imports)
case Some(i) =>
Expand Down
1 change: 1 addition & 0 deletions repl/src/main/scala/ammonite/repl/Repl.scala
Expand Up @@ -78,6 +78,7 @@ class Repl(input: InputStream,

def run(): Any = {
welcomeBanner.foreach(printStream.println)
interp.init()
@tailrec def loop(): Any = {
val actionResult = action()
Timer("End Of Loop")
Expand Down
101 changes: 100 additions & 1 deletion repl/src/main/scala/ammonite/repl/Storage.scala
Expand Up @@ -2,7 +2,8 @@ package ammonite.repl

import acyclic.file
import ammonite.ops._
import ammonite.repl.Util.{IvyMap, CompileCache, ClassFiles}
import Parsers.ImportTree
import ammonite.repl.Util.{IvyMap, CompileCache, ClassFiles, CacheOutput}
import org.apache.ivy.plugins.resolver.RepositoryResolver

import scala.util.Try
Expand All @@ -22,6 +23,15 @@ trait Storage{
val ivyCache: StableRef[IvyMap]
def compileCacheSave(path: String, tag: String, data: CompileCache): Unit
def compileCacheLoad(path: String, tag: String): Option[CompileCache]
def classFilesListSave(pkg: String,
wrapper: String,
dataList: Seq[(String, String)],
imports: Imports,
tag: String,
importTreesList: Seq[ImportTree]): Unit
def classFilesListLoad(pkg: String,
wrapper: String,
cacheTag: String): Option[CacheOutput]
}

object Storage{
Expand All @@ -42,6 +52,9 @@ object Storage{
}

var compileCache: mutable.Map[String, (String, CompileCache)] = mutable.Map.empty
val classFilesListcache = {
mutable.Map.empty[String, (String, Seq[(String, String)], Imports, Seq[ImportTree])]
}
def compileCacheSave(path: String, tag: String, data: CompileCache): Unit = {
compileCache(path) = (tag, data)
}
Expand All @@ -51,6 +64,35 @@ object Storage{
if loadedTag == tag
} yield data
}

def classFilesListSave(pkg: String,
wrapper: String,
dataList: Seq[(String, String)],
imports: Imports,
tag: String,
importTreesList: Seq[ImportTree]): Unit = {
val dir = pkg + "." + wrapper
classFilesListcache(dir) = (tag, dataList.reverse, imports, importTreesList)
}

def classFilesListLoad(pkg: String,
wrapper: String,
cacheTag: String): Option[CacheOutput] = {
val dir = pkg + "." + wrapper
classFilesListcache.get(dir) match{
case None => None
case Some((loadedTag, classFilesList, imports, importTreesList)) =>
if (loadedTag == cacheTag) {
val res = {
for((path, tag) <- classFilesList) yield {
compileCacheLoad(path, tag)
}
}.flatten
Some((classFilesList.unzip._1, res.unzip._1, imports, importTreesList))
}
else None
}
}
}


Expand All @@ -62,6 +104,7 @@ object Storage{
// someone upgrades Ammonite.
val cacheDir = dir/'cache/ammonite.Constants.version
val compileCacheDir = cacheDir/'compile
val classFilesOrder = "classFilesOrder.json"
val ivyCacheFile = cacheDir/"ivycache.json"
val metadataFile = "metadata.json"
val historyFile = dir/'history
Expand Down Expand Up @@ -90,6 +133,62 @@ object Storage{
}
}

def classFilesListSave(pkg: String,
wrapper: String,
dataList: Seq[(String, String)],
imports: Imports,
tag: String,
importTreesList: Seq[ImportTree]): Unit = {
val dir = pkg.replace("/", "$div") + "." + wrapper.replace("/", "$div")
val codeCacheDir = compileCacheDir/'scriptCaches/dir
if(!exists(codeCacheDir)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This exists check is not necessary; mkdir does it for you already

mkdir(codeCacheDir)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indentation

write(
codeCacheDir/classFilesOrder,
upickle.default.write((tag, dataList.reverse), indent = 4)
)
write(codeCacheDir/"imports.list", upickle.default.write(imports))
write(codeCacheDir/"importTrees.json", upickle.default.write(importTreesList, indent = 4))
}
}

def classFilesListLoad(pkg: String,
wrapper: String,
cacheTag: String): Option[CacheOutput] = {

val dir = pkg.replace("/", "$div") + "." + wrapper.replace("/", "$div")
val codeCacheDir = compileCacheDir/'scriptCaches/dir
if(!exists(codeCacheDir)) None
else {
val metadataJson = Try {
read(codeCacheDir/classFilesOrder)
}.toOption
val impFile = Try {
read(codeCacheDir/"imports.list")
}.toOption
val impTrees = Try{
read(codeCacheDir/"importTrees.json")
}.toOption
(metadataJson, impFile, impTrees) match{
case (Some(metadata), Some(imp), Some(importTrees)) =>
val (loadedTag, classFilesList) =
upickle.default.read[(String, Seq[(String, String)])](metadata)
if (cacheTag == loadedTag){
val res = {
for((path, tag) <- classFilesList) yield {
compileCacheLoad(path, tag)
}
}.flatten
val imports = upickle.default.read[Imports](imp)
val impTreeList = upickle.default.read[Seq[ImportTree]](importTrees)
Some((classFilesList.unzip._1, res.unzip._1, imports, impTreeList))
}
else None
case _ => None
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This if-statement is copy-pasted from above. Can you do something about it?

}
}

def compileCacheLoad(path: String, tag: String): Option[CompileCache] = {
val tagCacheDir = compileCacheDir/path.replace("/", "$div").replace(":", "$colon")
if(!exists(tagCacheDir)) None
Expand Down
8 changes: 7 additions & 1 deletion repl/src/main/scala/ammonite/repl/Util.scala
Expand Up @@ -6,6 +6,7 @@ import ammonite.ops._
import acyclic.file
import fansi.Attrs
import pprint.{PPrint, PPrinter}
import ammonite.repl.Parsers.ImportTree

import scala.collection.mutable
import scala.reflect.NameTransformer
Expand Down Expand Up @@ -124,7 +125,8 @@ case class Catching(handler: PartialFunction[Throwable, Res.Failing]) {
}

case class Evaluated(wrapper: Seq[Name],
imports: Imports)
imports: Imports,
tag: String)

/**
* Represents the importing of a single name in the Ammonite REPL, of the
Expand Down Expand Up @@ -307,8 +309,12 @@ object Util{
}

// Type aliases for common things

type CacheDetails = (String, String)
// Wrapper HashVal
type IvyMap = Map[(String, String, String, String), Set[String]]
type ClassFiles = Traversable[(String, Array[Byte])]
type CacheOutput = (Seq[String], Seq[ClassFiles], Imports, Seq[ImportTree])
type CompileCache = (ClassFiles, Imports)


Expand Down
Expand Up @@ -107,7 +107,6 @@ class SpecialClassLoader(parent: ClassLoader, parentHash: Array[Byte])
if (!specialLocalClasses(name)) None
else{
import ammonite.ops._
// println("Custom finding class! " + name)
val bytes = read.bytes(
this.getResourceAsStream(name.replace('.', '/') + ".class")
)
Expand Down
46 changes: 24 additions & 22 deletions repl/src/main/scala/ammonite/repl/interp/Compiler.scala
Expand Up @@ -47,10 +47,6 @@ trait Compiler{
def parse(line: String): Either[String, Seq[Global#Tree]]
var importsLen = 0

/**
* Writes files to dynamicClasspath. Needed for loading cached classes.
*/
def addToClasspath(classFiles: ClassFiles): Unit
}
object Compiler{
/**
Expand Down Expand Up @@ -140,6 +136,30 @@ object Compiler{
(reporter, vd, jcp)
}

def writeDeep(d: VirtualDirectory,
path: List[String],
suffix: String): OutputStream = path match {
case head :: Nil => d.fileNamed(path.head + suffix).output
case head :: rest =>
writeDeep(
d.subdirectoryNamed(head).asInstanceOf[VirtualDirectory],
rest, suffix
)
}

/**
* Writes files to dynamicClasspath. Needed for loading cached classes.
*/
def addToClasspath(classFiles: Traversable[(String, Array[Byte])],
dynamicClasspath: VirtualDirectory): Unit = {
val names = classFiles.map(_._1)
for((name, bytes) <- classFiles){
val output = writeDeep(dynamicClasspath, name.split('.').toList, ".class")
output.write(bytes)
output.close()
}
}

def apply(classpath: Seq[java.io.File],
dynamicClasspath: VirtualDirectory,
evalClassloader: => ClassLoader,
Expand Down Expand Up @@ -256,16 +276,6 @@ object Compiler{
}


def writeDeep(d: VirtualDirectory,
path: List[String],
suffix: String): OutputStream = path match {
case head :: Nil => d.fileNamed(path.head + suffix).output
case head :: rest =>
writeDeep(
d.subdirectoryNamed(head).asInstanceOf[VirtualDirectory],
rest, suffix
)
}

/**
* Compiles a blob of bytes and spits of a list of classfiles
Expand Down Expand Up @@ -317,14 +327,6 @@ object Compiler{
}
}

def addToClasspath(classFiles: Traversable[(String, Array[Byte])]): Unit = {
val names = classFiles.map(_._1)
for((name, bytes) <- classFiles){
val output = writeDeep(dynamicClasspath, name.split('.').toList, ".class")
output.write(bytes)
output.close()
}
}

def parse(line: String): Either[String, Seq[Global#Tree]]= {
val errors = mutable.Buffer.empty[String]
Expand Down
46 changes: 40 additions & 6 deletions repl/src/main/scala/ammonite/repl/interp/Evaluator.scala
Expand Up @@ -5,10 +5,12 @@ import java.lang.reflect.InvocationTargetException
import acyclic.file
import ammonite.repl.frontend.{SessionChanged, Session, ReplExit}
import ammonite.repl._
import Parsers.ImportTree

import Util.{CompileCache, ClassFiles}

import scala.collection.immutable.ListMap
import scala.reflect.io.VirtualDirectory
import scala.collection.mutable
import scala.util.Try

Expand All @@ -21,6 +23,7 @@ import scala.util.Try
*/
trait Evaluator{
def loadClass(wrapperName: String, classFiles: ClassFiles): Res[Class[_]]
def evalMain(cls: Class[_]): Any
def getCurrentLine: String
def update(newImports: Imports): Unit

Expand All @@ -33,10 +36,17 @@ trait Evaluator{
def processScriptBlock(cls: Class[_],
newImports: Imports,
wrapperName: Name,
pkgName: Seq[Name]): Res[Evaluated]
pkgName: Seq[Name],
tag: String): Res[Evaluated]

def sess: Session

def evalCachedClassFiles(cachedData: Seq[ClassFiles],
pkg: String,
wrapper: String,
dynamicClasspath: VirtualDirectory,
classFilesList: Seq[String]): Res[Seq[_]]

}

object Evaluator{
Expand Down Expand Up @@ -174,32 +184,55 @@ object Evaluator{
Timer("eval.processLine evaluatorRunPrinter 1")
evaluatorRunPrinter(iter.foreach(printer.out))
Timer("eval.processLine evaluatorRunPrinter end")
evaluationResult(Seq(Name("$sess"), indexedWrapperName), newImports)

// "" Empty string as cache tag of repl code
evaluationResult(Seq(Name("$sess"), indexedWrapperName), newImports, "")
}
}


def processScriptBlock(cls: Class[_],
newImports: Imports,
wrapperName: Name,
pkgName: Seq[Name]) = for {
pkgName: Seq[Name],
tag: String) = for {
_ <- Catching{userCodeExceptionHandler}
} yield {
Timer("cachedCompileBlock")
evalMain(cls)
Timer("evalMain")
val res = evaluationResult(pkgName :+ wrapperName, newImports)
val res = evaluationResult(pkgName :+ wrapperName, newImports, tag)
Timer("evaluationResult")
res
}

def evalCachedClassFiles(cachedData: Seq[ClassFiles],
pkg: String,
wrapper: String,
dynamicClasspath: VirtualDirectory,
classFilesList: Seq[String]): Res[Seq[_]] = {

val res = Res.map(cachedData.zipWithIndex) {
case (clsFiles, index) =>
Compiler.addToClasspath(clsFiles, dynamicClasspath)
eval.loadClass(classFilesList(index), clsFiles)
}

try {
for {
r <- res
} yield r.map(eval.evalMain(_))
}
catch {userCodeExceptionHandler}
}

def update(newImports: Imports) = {
frames.head.addImports(newImports)
}

def evaluationResult(wrapperName: Seq[Name],
imports: Imports) = {
imports: Imports,
tag: String) = {
Evaluated(
wrapperName,
Imports(
Expand All @@ -218,7 +251,8 @@ object Evaluator{

id.copy(prefix = rootedPrefix)
}
)
),
tag
)
}
}
Expand Down