Skip to content

Commit 5907ce1

Browse files
authored
Merge pull request #44 from jogasser/master
Write Cache to file
2 parents 5bc98f0 + 2ac2de2 commit 5907ce1

7 files changed

Lines changed: 236 additions & 138 deletions

File tree

build.sbt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ lazy val server = (project in file("."))
2929
// classpath used by Scala's reflection.
3030
Test / fork := true,
3131

32-
// Compilation settings
32+
libraryDependencies += "net.liftweb" %% "lift-json" % "3.5.0",
3333
libraryDependencies += "com.typesafe.akka" %% "akka-actor" % "2.6.10",
3434
libraryDependencies += "com.typesafe.akka" %% "akka-http-spray-json" % "10.2.1",
3535
libraryDependencies += "com.typesafe.akka" %% "akka-stream" % "2.6.10",

src/main/scala/viper/server/ViperConfig.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,15 @@ class ViperConfig(args: Seq[String]) extends ScallopConf(args) {
102102
hidden = false
103103
)
104104

105+
val cacheFile: ScallopOption[String] = opt[String]("cacheFile",
106+
descr = ("Specifies the file from which the cache gets initialized on startup and to which the resulting cache gets written to."
107+
+ "If it is not set, the cache will be initially empty and is only kept in memory, so it is not persisted during runs"
108+
),
109+
default = None,
110+
noshort = true,
111+
hidden = false
112+
)
113+
105114
val nThreads: ScallopOption[Int] = opt[Int](
106115
name = "nThreads",
107116
descr = s"Maximal number of threads that should be used (not taking threads used by backend into account)\n" +

src/main/scala/viper/server/core/VerificationWorker.scala

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ class ViperBackend(private val _frontend: SilFrontend, private val programId: St
143143
_frontend.logger.debug(s"Verification has failed (errors: ${f.errors.map(_.readableMessage).mkString("\n")}; members: ${_ast.members.map(_.name).mkString(", ")})")
144144
_frontend.reporter report OverallFailureMessage(_frontend.getVerifierName,
145145
System.currentTimeMillis() - _frontend.startTime,
146-
Failure(f.errors.filter { e => !e.cached }))
146+
Failure(f.errors))
147147
case None =>
148148
}
149149
}
@@ -181,8 +181,6 @@ class ViperBackend(private val _frontend: SilFrontend, private val programId: St
181181
_frontend.setVerificationResult(ver_result)
182182
_frontend.setState(DefaultStates.Verification)
183183

184-
_frontend.logger.debug(s"Latest verification result: $ver_result")
185-
186184
val meth_to_err_map: Seq[(Method, Option[List[AbstractVerificationError]])] = methodsToVerify.map((m: Method) => {
187185
// Results come back irrespective of program Member.
188186
val cacheable_errors: Option[List[AbstractVerificationError]] = for {
@@ -228,9 +226,12 @@ class ViperBackend(private val _frontend: SilFrontend, private val programId: St
228226
}
229227
}
230228
} else {
231-
_frontend.logger.debug(s"Inconsistent error splitting; no cache update for this verification attempt with ProgramID $programId.")
229+
_frontend.logger.warn(s"Inconsistent error splitting; no cache update for this verification attempt with ProgramID $programId.")
232230
}
233231

232+
// Write cache to file at the end of a verification run
233+
ViperCache.writeToFile()
234+
234235
// combine errors:
235236
if (all_cached_errors.nonEmpty) {
236237
_frontend.getVerificationResult.get match {

src/main/scala/viper/server/core/ViperCache.scala

Lines changed: 165 additions & 84 deletions
Large diffs are not rendered by default.

src/main/scala/viper/server/core/ViperCoreServer.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class ViperCoreServer(val config: ViperConfig)(implicit val executor: Verificati
3939
println(s"Writing [level:${config.logLevel()}] logs into " +
4040
s"${if (!config.logFile.isSupplied) "(default) " else ""}journal: ${logger.file.get}")
4141

42-
ViperCache.initialize(logger.get, config.backendSpecificCache())
42+
ViperCache.initialize(logger.get, config.backendSpecificCache(), config.cacheFile.toOption)
4343

4444
super.start(config.maximumActiveJobs()) map { _ =>
4545
logger.get.info(s"ViperCoreServer has started.")

src/main/scala/viper/server/frontends/http/ViperHttpServer.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class ViperHttpServer(config: ViperConfig)(executor: VerificationExecutionContex
3535
_logger = ViperLogger("ViperServerLogger", config.getLogFileWithGuarantee, config.logLevel())
3636
println(s"Writing [level:${config.logLevel()}] logs into ${if (!config.logFile.isSupplied) "(default) " else ""}journal: ${logger.file.get}")
3737

38-
ViperCache.initialize(logger.get, config.backendSpecificCache())
38+
ViperCache.initialize(logger.get, config.backendSpecificCache(), config.cacheFile.toOption)
3939

4040
port = config.port.toOption.getOrElse(0) // 0 causes HTTP server to automatically select a free port
4141
super.start(config.maximumActiveJobs()).map({ _ =>

src/main/scala/viper/server/vsi/Cache.scala

Lines changed: 54 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ package viper.server.vsi
88

99
import viper.silver.utility.CacheHelper
1010

11-
import scala.collection.mutable.{ListBuffer, Map => MutableMap}
11+
import java.security.MessageDigest
12+
import java.time.Instant
13+
import scala.collection.mutable.ListBuffer
1214

1315
/** The goal of this generic caching trait is to provide:
1416
*
@@ -46,10 +48,16 @@ abstract class Cache {
4648
* The inner map is referred to as the fileCache. As the name indicates, it stores, for each
4749
* file, a number of hashes and corresponding cache entries.
4850
*/
49-
protected val _cache: MutableMap[String, FileCash] = MutableMap()
50-
type FileCash = MutableMap[String, List[CacheEntry]]
51+
type FileKey = String
52+
type MemberHash = String
53+
type FileCache = Map[MemberHash, List[CacheEntry]]
54+
type Cache = Map[FileKey, FileCache]
55+
protected var _cache: Cache = Map()
5156

52-
protected val _program_cache: MutableMap[Ast, MutableMap[CacheableMember, List[Member]]] = MutableMap()
57+
type ProgramHash = String
58+
type DependencyHash = String
59+
type DependencyMap = Map[MemberHash, DependencyHash]
60+
protected var _program_cache: Map[ProgramHash, DependencyMap] = Map()
5361

5462
/** This method transforms a program and returns verification results based on the cache's
5563
* current state.
@@ -72,40 +80,40 @@ abstract class Cache {
7280
//read errors from cache
7381
val cachable_members = input_prog.decompose()
7482

75-
val prog_dependencies = _program_cache.find({
76-
case (k, _) => k.equals(input_prog)
77-
})
83+
val input_prog_hex = MessageDigest.getInstance("SHA-1")
84+
.digest(input_prog.toString.getBytes("UTF-8"))
85+
.map("%02x".format(_)).mkString
7886

79-
prog_dependencies match {
80-
case Some((_, dep_map)) =>
81-
cachable_members.foreach(cm => {
82-
val dependencies = dep_map(cm)
83-
get(file_key, cm, dependencies) match {
84-
case Some(matched_entry) =>
85-
concerningsToCache += cm.transform
86-
cache_entries += matched_entry
87-
case None =>
88-
//Nothing in cache, request verification
89-
concerningsToVerify += cm
90-
}
91-
})
87+
val prog_dependencies = _program_cache.get(input_prog_hex)
88+
89+
val dep_map = prog_dependencies match {
90+
case Some(dep_map) => dep_map
9291
case None =>
93-
val dep_map = MutableMap[CacheableMember, List[Member]]()
92+
var dep_map = Map[String, String]()
9493
cachable_members.foreach(cm => {
95-
val dependencies = cm.getDependencies(input_prog)
96-
dep_map += (cm -> dependencies)
97-
get(file_key, cm, dependencies) match {
98-
case Some(matched_entry) =>
99-
concerningsToCache += cm.transform
100-
cache_entries += matched_entry
101-
case None =>
102-
//Nothing in cache, request verification
103-
concerningsToVerify += cm
104-
}
94+
val concerning_hash = cm.hash()
95+
val dependency_hash = CacheHelper.buildHash(concerning_hash + cm.getDependencies(input_prog).map(_.hash()).mkString(" "))
96+
dep_map = dep_map + (concerning_hash -> dependency_hash)
10597
})
106-
_program_cache += (input_prog -> dep_map)
98+
_program_cache = _program_cache + (input_prog_hex -> dep_map)
99+
dep_map
107100
}
108101

102+
cachable_members.foreach(cm => {
103+
val concerning_hash = cm.hash()
104+
val dependency_hash = dep_map(concerning_hash)
105+
106+
get(file_key, concerning_hash, dependency_hash) match {
107+
case Some(matched_entry) =>
108+
matched_entry.lastAccessed = Instant.now()
109+
concerningsToCache += cm.transform
110+
cache_entries += matched_entry
111+
case None =>
112+
//Nothing in cache, request verification
113+
concerningsToCache += cm
114+
}
115+
})
116+
109117
val all_concernings: List[CacheableMember] = concerningsToCache.toList ++ concerningsToVerify.toList
110118
val output_prog: Ast = input_prog.compose(all_concernings)
111119
(output_prog, cache_entries.toList)
@@ -114,18 +122,13 @@ abstract class Cache {
114122
/** Utility function to retrieve entries for single members.
115123
* */
116124
final def get(file_key: String,
117-
key: CacheableMember,
118-
dependencies: List[Member]): Option[CacheEntry] = {
119-
120-
val concerning_hash = key.hash()
121-
val dependencies_hash = dependencies.map(_.hash()).mkString(" ")
122-
val dependency_hash = CacheHelper.buildHash(concerning_hash + dependencies_hash)
125+
concerning_hash: String,
126+
dependency_hash: String): Option[CacheEntry] = {
123127
assert(concerning_hash != null)
124-
125128
for {
126129
fileCache <- _cache.get(file_key)
127130
cacheEntries <- fileCache.get(concerning_hash)
128-
validEntry <- cacheEntries.find(_.depency_hash == dependency_hash)
131+
validEntry <- cacheEntries.find(_.dependencyHash == dependency_hash)
129132
} yield validEntry
130133
}
131134

@@ -147,7 +150,7 @@ abstract class Cache {
147150
val concerning_hash = key.hash()
148151
val dependencies_hash = dependencies.map(_.hash()).mkString(" ")
149152
val dependency_hash = CacheHelper.buildHash(concerning_hash + dependencies_hash)
150-
val new_entry: CacheEntry = new CacheEntry(key, content, dependency_hash)
153+
val new_entry: CacheEntry = CacheEntry(key.hash(), content, dependency_hash)
151154

152155
assert(concerning_hash != null)
153156

@@ -156,28 +159,32 @@ abstract class Cache {
156159
val existing_entries = fileCache.getOrElse(concerning_hash, Nil)
157160
val updated_cacheEntries = new_entry :: existing_entries
158161

159-
fileCache(concerning_hash) = updated_cacheEntries
162+
val updatedFileCache = fileCache + (concerning_hash -> updated_cacheEntries)
163+
_cache = _cache + (file_key -> updatedFileCache)
160164
updated_cacheEntries
161165
case None =>
162166
//if file not in cache yet, create new map entry for it. Recall the function.
163-
_cache += (file_key -> collection.mutable.Map[String, List[CacheEntry]]())
167+
_cache = _cache + (file_key -> Map())
164168
update(file_key, key, dependencies, content)
165169
}
166170
}
167171

168172
/** Resets the cache for a particular file.
169173
* */
170174
def forgetFile(file_key: String): Option[String] = {
171-
_cache.remove(file_key) match {
175+
val key = _cache.get(file_key) match {
172176
case Some(_) => Some(file_key)
173177
case None => None
174178
}
179+
_cache -= file_key
180+
key
175181
}
176182

177183
/** Resets the entire cache.
178184
* */
179185
def resetCache(): Unit = {
180-
_cache.clear()
186+
_program_cache = Map()
187+
_cache = Map()
181188
}
182189
}
183190

@@ -194,7 +201,7 @@ abstract class Cache {
194201
* created. If, at some point, a members dependency hash is no longer equal to the one stored
195202
* here, the entry is no longer valid.
196203
* */
197-
case class CacheEntry(concerning: CacheableMember, content: CacheContent, depency_hash: String)
204+
case class CacheEntry(concerningHash: String, content: CacheContent, dependencyHash: String, created: Instant = Instant.now(), var lastAccessed: Instant = Instant.now())
198205

199206

200207
// ===== AUXILIARY TRAITS ==================================================================
@@ -245,7 +252,7 @@ trait CacheableMember extends Member {
245252
* terms of verification.
246253
*
247254
* A member may hit the cache, but the attached verification results might be invalid. Reason
248-
* for this is that other members in the program have might have changed in such a way that
255+
* for this is that other members in the program might have changed in such a way that
249256
* they influenced this member's verification outcome. These influencing members are called
250257
* dependencies and need to be checked when retrieving a member's attached result from cache.
251258
* */

0 commit comments

Comments
 (0)