Skip to content

Commit

Permalink
saves on-going work
Browse files Browse the repository at this point in the history
  • Loading branch information
ArquintL committed Mar 4, 2023
1 parent 844f5e7 commit 62d6349
Show file tree
Hide file tree
Showing 7 changed files with 614 additions and 55 deletions.
28 changes: 22 additions & 6 deletions src/main/scala/viper/gobra/Gobra.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import viper.gobra.ast.frontend.PPackage
import viper.gobra.ast.internal.Program
import viper.gobra.ast.internal.transform.{CGEdgesTerminationTransform, ConstantPropagation, InternalTransform, OverflowChecksTransform}
import viper.gobra.backend.BackendVerifier
import viper.gobra.frontend.info.Info.Context
import viper.gobra.frontend.info.{Info, TypeInfo}
import viper.gobra.frontend.{Config, Desugar, PackageInfo, Parser, ScallopGobraConfig}
import viper.gobra.reporting._
Expand Down Expand Up @@ -158,7 +159,7 @@ class Gobra extends GoVerifier with GoIdeVerifier {
finalConfig <- getAndMergeInFileConfig(config, pkgInfo)
_ = setLogLevel(finalConfig)
parsedPackage <- performParsing(pkgInfo, finalConfig)
typeInfo <- performTypeChecking(parsedPackage, finalConfig)
typeInfo <- performTypeChecking(parsedPackage, executor, finalConfig)
program <- performDesugaring(parsedPackage, typeInfo, finalConfig)
program <- performInternalTransformations(program, finalConfig, pkgInfo)
viperTask <- performViperEncoding(program, finalConfig, pkgInfo)
Expand Down Expand Up @@ -243,24 +244,32 @@ class Gobra extends GoVerifier with GoIdeVerifier {

private def performParsing(pkgInfo: PackageInfo, config: Config): Either[Vector[VerifierError], PPackage] = {
if (config.shouldParse) {
val startMs = System.currentTimeMillis()
val sourcesToParse = config.packageInfoInputMap(pkgInfo)
Parser.parse(sourcesToParse, pkgInfo)(config)
val res = Parser.parse(sourcesToParse, pkgInfo)(config)
val durationS = f"${(System.currentTimeMillis() - startMs) / 1000f}%.1f"
println(s"parser phase done, took ${durationS}s")
res
} else {
Left(Vector())
}
}

private def performTypeChecking(parsedPackage: PPackage, config: Config): Either[Vector[VerifierError], TypeInfo] = {
private def performTypeChecking(parsedPackage: PPackage, executionContext: GobraExecutionContext, config: Config): Either[Vector[VerifierError], TypeInfo] = {
if (config.shouldTypeCheck) {
Info.check(parsedPackage, config.packageInfoInputMap(parsedPackage.info), isMainContext = true)(config)
Info.check(parsedPackage, config.packageInfoInputMap(parsedPackage.info), isMainContext = true, context = new Context(executionContext, config))(config)
} else {
Left(Vector())
}
}

private def performDesugaring(parsedPackage: PPackage, typeInfo: TypeInfo, config: Config): Either[Vector[VerifierError], Program] = {
if (config.shouldDesugar) {
Right(Desugar.desugar(parsedPackage, typeInfo)(config))
val startMs = System.currentTimeMillis()
val res = Right(Desugar.desugar(parsedPackage, typeInfo)(config))
val durationS = f"${(System.currentTimeMillis() - startMs) / 1000f}%.1f"
println(s"desugaring done, took ${durationS}s")
res
} else {
Left(Vector())
}
Expand All @@ -282,18 +291,25 @@ class Gobra extends GoVerifier with GoIdeVerifier {
// constant propagation does not cause duplication of verification errors caused
// by overflow checks (if enabled) because all overflows in constant declarations
// can be found by the well-formedness checks.
val startMs = System.currentTimeMillis()
var transformations: Vector[InternalTransform] = Vector(CGEdgesTerminationTransform, ConstantPropagation)
if (config.checkOverflows) {
transformations :+= OverflowChecksTransform
}
val result = transformations.foldLeft(program)((prog, transf) => transf.transform(prog))
config.reporter.report(AppliedInternalTransformsMessage(config.packageInfoInputMap(pkgInfo).map(_.name), () => result))
val durationS = f"${(System.currentTimeMillis() - startMs) / 1000f}%.1f"
println(s"internal transformations done, took ${durationS}s")
Right(result)
}

private def performViperEncoding(program: Program, config: Config, pkgInfo: PackageInfo): Either[Vector[VerifierError], BackendVerifier.Task] = {
if (config.shouldViperEncode) {
Right(Translator.translate(program, pkgInfo)(config))
val startMs = System.currentTimeMillis()
val res = Right(Translator.translate(program, pkgInfo)(config))
val durationS = f"${(System.currentTimeMillis() - startMs) / 1000f}%.1f"
println(s"Viper encoding done, took ${durationS}s")
res
} else {
Left(Vector())
}
Expand Down
25 changes: 23 additions & 2 deletions src/main/scala/viper/gobra/frontend/Config.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import viper.gobra.backend.{ViperBackend, ViperBackends}
import viper.gobra.GoVerifier
import viper.gobra.frontend.PackageResolver.FileResource
import viper.gobra.frontend.Source.getPackageInfo
import viper.gobra.frontend.info.Info.TypeCheckMode
import viper.gobra.frontend.info.Info.TypeCheckMode.TypeCheckMode
import viper.gobra.reporting.{FileWriterReporter, GobraReporter, StdIOReporter}
import viper.gobra.util.{TypeBounds, Violation}
import viper.silver.ast.SourcePosition
Expand Down Expand Up @@ -68,6 +70,7 @@ object ConfigDefaults {
lazy val DefaultEnableLazyImports: Boolean = false
lazy val DefaultNoVerify: Boolean = false
lazy val DefaultNoStreamErrors: Boolean = false
lazy val DefaultTypeCheckMode: TypeCheckMode = TypeCheckMode.Parallel
}

case class Config(
Expand Down Expand Up @@ -118,7 +121,8 @@ case class Config(
disableMoreCompleteExhale: Boolean = ConfigDefaults.DefaultDisableMoreCompleteExhale,
enableLazyImports: Boolean = ConfigDefaults.DefaultEnableLazyImports,
noVerify: Boolean = ConfigDefaults.DefaultNoVerify,
noStreamErrors: Boolean = ConfigDefaults.DefaultNoStreamErrors
noStreamErrors: Boolean = ConfigDefaults.DefaultNoStreamErrors,
typeCheckMode: TypeCheckMode = ConfigDefaults.DefaultTypeCheckMode,
) {

def merge(other: Config): Config = {
Expand Down Expand Up @@ -163,7 +167,8 @@ case class Config(
disableMoreCompleteExhale = disableMoreCompleteExhale,
enableLazyImports = enableLazyImports || other.enableLazyImports,
noVerify = noVerify || other.noVerify,
noStreamErrors = noStreamErrors || other.noStreamErrors
noStreamErrors = noStreamErrors || other.noStreamErrors,
typeCheckMode = typeCheckMode
)
}

Expand Down Expand Up @@ -214,6 +219,7 @@ case class BaseConfig(gobraDirectory: Path = ConfigDefaults.DefaultGobraDirector
enableLazyImports: Boolean = ConfigDefaults.DefaultEnableLazyImports,
noVerify: Boolean = ConfigDefaults.DefaultNoVerify,
noStreamErrors: Boolean = ConfigDefaults.DefaultNoStreamErrors,
typeCheckMode: TypeCheckMode = ConfigDefaults.DefaultTypeCheckMode,
) {
def shouldParse: Boolean = true
def shouldTypeCheck: Boolean = !shouldParseOnly
Expand Down Expand Up @@ -268,6 +274,7 @@ trait RawConfig {
enableLazyImports = baseConfig.enableLazyImports,
noVerify = baseConfig.noVerify,
noStreamErrors = baseConfig.noStreamErrors,
typeCheckMode = baseConfig.typeCheckMode,
)
}

Expand Down Expand Up @@ -639,6 +646,19 @@ class ScallopGobraConfig(arguments: Seq[String], isInputOptional: Boolean = fals
noshort = true,
)

val typeCheckMode: ScallopOption[TypeCheckMode] = choice(
name = "typeCheckMode",
choices = Seq("LAZY", "SEQUENTIAL", "PARALLEL"),
descr = "Specifies the mode in which type-checking is performed.",
default = Some("PARALLEL"),
noshort = true
).map {
case "LAZY" => TypeCheckMode.Lazy
case "SEQUENTIAL" => TypeCheckMode.Sequential
case "PARALLEL" => TypeCheckMode.Parallel
case _ => ConfigDefaults.DefaultTypeCheckMode
}

/**
* Exception handling
*/
Expand Down Expand Up @@ -785,5 +805,6 @@ class ScallopGobraConfig(arguments: Seq[String], isInputOptional: Boolean = fals
enableLazyImports = enableLazyImports(),
noVerify = noVerify(),
noStreamErrors = noStreamErrors(),
typeCheckMode = typeCheckMode(),
)
}
10 changes: 9 additions & 1 deletion src/main/scala/viper/gobra/frontend/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ object Desugar {
def desugar(pkg: PPackage, info: viper.gobra.frontend.info.TypeInfo)(config: Config): in.Program = {
val importsCollector = new PackageInitSpecCollector
// independently desugar each imported package.
val importedDesugaringStartMs = System.currentTimeMillis()
val importedPrograms = info.context.getContexts map { tI => {
val typeInfo: TypeInfo = tI.getTypeInfo
val importedPackage = typeInfo.tree.originalRoot
Expand All @@ -39,12 +40,18 @@ object Desugar {
d.registerPackage(importedPackage, importsCollector)(config)
(d, d.packageD(importedPackage))
}}
val importedDurationS = f"${(System.currentTimeMillis() - importedDesugaringStartMs) / 1000f}%.1f"
println(s"desugaring imported packages done, took ${importedDurationS}s")

val desugaringStartMs = System.currentTimeMillis()
// desugar the main package, i.e. the package on which verification is performed:
val mainDesugarer = new Desugarer(pkg.positions, info)
// registers main package to generate proof obligations for its init code
mainDesugarer.registerMainPackage(pkg, importsCollector)(config)
// combine all desugared results into one Viper program:
val internalProgram = combine(mainDesugarer, mainDesugarer.packageD(pkg), importedPrograms)
val durationS = f"${(System.currentTimeMillis() - desugaringStartMs) / 1000f}%.1f"
println(s"desugaring main package done, took ${durationS}s")
config.reporter report DesugaredMessage(config.packageInfoInputMap(pkg.info).map(_.name), () => internalProgram)
internalProgram
}
Expand Down Expand Up @@ -3455,7 +3462,8 @@ object Desugar {
// Collect and register all import-preconditions
pkg.imports.foreach{ imp =>
info.context.getTypeInfo(RegularImport(imp.importPath))(config) match {
case Some(Right(tI)) =>
// case Some(Right(tI)) =>
case Right(tI) =>
val desugaredPre = imp.importPres.map(specificationD(FunctionContext.empty(), info))
Violation.violation(!config.enableLazyImports || desugaredPre.isEmpty, s"Import precondition found despite running with ${Config.enableLazyImportOptionPrettyPrinted}")
specCollector.addImportPres(tI.getTypeInfo.tree.originalRoot, desugaredPre)
Expand Down
10 changes: 6 additions & 4 deletions src/main/scala/viper/gobra/frontend/Parser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import viper.silver.ast.SourcePosition

import scala.collection.mutable.ListBuffer
import java.security.MessageDigest
import java.util.concurrent.{ConcurrentHashMap, ConcurrentMap}

object Parser {

Expand Down Expand Up @@ -59,7 +60,7 @@ object Parser {

type SourceCacheKey = String
// cache maps a key (obtained by hasing file path and file content) to the parse result
private var sourceCache: Map[SourceCacheKey, (Either[Vector[ParserError], PProgram], Positions)] = Map.empty
private var sourceCache: ConcurrentMap[SourceCacheKey, (Either[Vector[ParserError], PProgram], Positions)] = new ConcurrentHashMap()

/** computes the key for caching a particular source. This takes the name, the specOnly flag, and the file's contents into account */
private def getCacheKey(source: Source, specOnly: Boolean): SourceCacheKey = {
Expand All @@ -70,7 +71,7 @@ object Parser {
}

def flushCache(): Unit = {
sourceCache = Map.empty
sourceCache.clear()
}

private def parseSources(sources: Vector[Source], pkgInfo: PackageInfo, specOnly: Boolean)(config: Config): Either[Vector[VerifierError], PPackage] = {
Expand Down Expand Up @@ -100,10 +101,11 @@ object Parser {
def parseAndStore(): (Either[Vector[ParserError], PProgram], Positions) = {
cacheHit = false
val res = parseSource(source)
sourceCache += getCacheKey(source, specOnly) -> (res, positions)
// sourceCache.put(getCacheKey(source, specOnly), (res, positions))
(res, positions)
}
val (res, pos) = sourceCache.getOrElse(getCacheKey(source, specOnly), parseAndStore())
// val (res, pos) = sourceCache.getOrElse(getCacheKey(source, specOnly), parseAndStore())
val (res, pos) = sourceCache.computeIfAbsent(getCacheKey(source, specOnly), _ => parseAndStore())
if (cacheHit) {
// a cached AST has been found in the cache. The position manager does not yet have any positions for nodes in
// this AST. Therefore, the following strategy iterates over the entire AST and copies positional information
Expand Down
76 changes: 76 additions & 0 deletions src/main/scala/viper/gobra/frontend/TaskManager.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package viper.gobra.frontend

import viper.gobra.frontend.info.Info.TypeCheckMode
import viper.gobra.util.{GobraExecutionContext, Violation}

import java.util.concurrent.{ConcurrentHashMap, ConcurrentMap}
import scala.concurrent.duration.Duration
import scala.concurrent.{Await, Future, Promise}
import scala.util.{Failure, Success}
import scala.jdk.CollectionConverters._

trait Job[R] {
private var compututationStarted = false
private val promise: Promise[R] = Promise()
def getFuture: Future[R] = promise.future
protected def compute(): R

def call(): R = {
getFuture.value match {
case Some(Success(res)) => return res // return already computed type-checker result
case Some(Failure(exception)) => Violation.violation(s"Job resulted in exception: $exception")
case _ =>
}
Violation.violation(!compututationStarted, s"Job is already on-going")
compututationStarted = true
val res = compute()
promise.success(res)
res
}
}

class TaskManager[K, R](config: Config) {
private val jobs: ConcurrentMap[K, Job[R]] = new ConcurrentHashMap()

/**
* returns true if job has been inserted and thus was previously absent
*/
def addIfAbsent(id: K, job: Job[R], insertOnly: Boolean = false)(executionContext: GobraExecutionContext): Boolean = {
var isAbsent = false
// first insert job, then run it (if necessary)
jobs.computeIfAbsent(id, _ => {
isAbsent = true
job
})
// now run it but only if it's a new job:
if (isAbsent && !insertOnly) {
config.typeCheckMode match {
case TypeCheckMode.Lazy => // don't do anything as of now
case TypeCheckMode.Sequential => job.call()
case TypeCheckMode.Parallel => Future{ job.call() }(executionContext)
}
}
isAbsent
}

def getResult(id: K): R = {
val job = jobs.get(id)
Violation.violation(job != null, s"Task $id not found")
getResultFromJob(job)
}

def getAllResults: Iterable[R] =
jobs.values().asScala.map(getResultFromJob)

def getAllResultsWithKeys: Iterable[(K, R)] =
jobs.asScala.toVector.map { case (key, job) => (key, getResultFromJob(job)) }

private def getResultFromJob(job: Job[R]): R = config.typeCheckMode match {
case TypeCheckMode.Lazy => job.call() // we perform the computation now that we need the result
case TypeCheckMode.Sequential =>
// note that we cannot await the future here as type-checking of this package might not have started yet.
// Thus, we use `.call()` that either returns a previously calculated type-checking result or will calculate it.
job.call()
case TypeCheckMode.Parallel => Await.result(job.getFuture, Duration.Inf)
}
}
Loading

0 comments on commit 62d6349

Please sign in to comment.