Skip to content

Commit

Permalink
Calculate minifiable rate
Browse files Browse the repository at this point in the history
  • Loading branch information
p51lee committed Oct 30, 2024
1 parent 516df15 commit 4183d25
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 25 deletions.
54 changes: 51 additions & 3 deletions src/main/scala/esmeta/es/util/Coverage.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import esmeta.js.minifier.Minifier
import io.circe.*, io.circe.syntax.*

import scala.math.Ordering.Implicits.seqOrdering
import esmeta.es.util.fuzzer.FSTrieConfig

/** coverage measurement of cfg */
case class Coverage(
Expand All @@ -31,16 +32,21 @@ case class Coverage(
import jsonProtocol.given

// TODO: replace with real minify checker
val swcMinifyChecker = MinifyChecker(cfg, s => Minifier.minifySwc(s).toOption)
val swcMinifyChecker =
MinifyChecker(cfg.spec, MinifyChecker.swcMinifyFunction)

val fsTrie = new FSTrieWrapper[String]()
val fsTrie =
new FSTrieWrapper[String](config = FSTrieConfig(maxSensitivity = kFs))

// minimal scripts
def minimalScripts: Set[Script] = _minimalScripts
private var _minimalScripts: Set[Script] = Set()

// meta-info of each script
private var _minimalInfo: Map[String, ScriptInfo] = Map()
def minifiableRate: Double = _minimalInfo.values.count(
_.minifiable.getOrElse(false),
) / _minimalInfo.size.toDouble

// mapping from nodes/conditions to scripts
private var nodeViewMap: Map[Node, Map[View, Script]] = Map()
Expand Down Expand Up @@ -111,8 +117,10 @@ case class Coverage(
var touchedNodeViews: Map[NodeView, Option[Nearest]] = Map()
var touchedCondViews: Map[CondView, Option[Nearest]] = Map()

val strictCode = USE_STRICT + code

val isMinifierHit =
swcMinifyChecker.check(script.code).map(_.diff.nonEmpty).getOrElse(false)
swcMinifyChecker.check(strictCode).map(_.diff.nonEmpty).getOrElse(false)

val rawStacks =
interp.touchedNodeViews.keys
Expand Down Expand Up @@ -170,6 +178,7 @@ case class Coverage(
ConformTest.createTest(cfg, finalSt),
touchedNodeViews.keys,
touchedCondViews.keys,
minifiable = Some(isMinifierHit),
)

// TODO: impl checkWithBlocking using `blockingScripts`
Expand Down Expand Up @@ -249,6 +258,7 @@ case class Coverage(
withScriptInfo = true,
withTargetCondViews = true,
withUnreachableFuncs = true,
withFStrie = true,
withMsg = withMsg,
)

Expand All @@ -259,6 +269,7 @@ case class Coverage(
withScriptInfo: Boolean = false,
withTargetCondViews: Boolean = false,
withUnreachableFuncs: Boolean = false,
withFStrie: Boolean = false,
// TODO(@hyp3rflow): use this for ignoring dump messages
withMsg: Boolean = false,
): Unit =
Expand Down Expand Up @@ -299,6 +310,25 @@ case class Coverage(
getData = USE_STRICT + _.code + LINE_SEP,
remove = true,
)
val minifiableMinimalScripts = _minimalScripts.filter(s =>
_minimalInfo.get(s.name).exists(_.minifiable.getOrElse(false)),
)
dumpDir[Script](
name = "minimal ECMAScript programs (minifiable)",
iterable = minifiableMinimalScripts,
dirname = s"$baseDir/minimal-minifiable",
getName = _.name,
getData = USE_STRICT + _.code + LINE_SEP,
remove = true,
)
dumpDir[Script](
name = "minimal ECMAScript programs (not minifiable)",
iterable = _minimalScripts -- minifiableMinimalScripts,
dirname = s"$baseDir/minimal-not-minifiable",
getName = _.name,
getData = USE_STRICT + _.code + LINE_SEP,
remove = true,
)
log("Dumped scripts")
if (withScriptInfo)
dumpDir[(String, ScriptInfo)](
Expand Down Expand Up @@ -332,6 +362,23 @@ case class Coverage(
filename = s"$baseDir/unreach-funcs",
)
log("dumped unreachable functions")
if (withFStrie)
import fsTrie.given
val fsTrieConfig = fsTrie.config
val fsTrieRoot = fsTrie.root
dumpJson(
name = "fstrie config",
data = fsTrieConfig,
filename = s"$baseDir/fstrie-config.json",
noSpace = false,
)
dumpJson(
name = "fstrie root",
data = fsTrieRoot,
filename = s"$baseDir/fstrie-root.json",
noSpace = false,
)
log("dumped fstriewrapper")

/** conversion to string */
private def percent(n: Double, t: Double): Double = n / t * 100
Expand Down Expand Up @@ -500,6 +547,7 @@ object Coverage {
test: ConformTest,
touchedNodeViews: Iterable[NodeView],
touchedCondViews: Iterable[CondView],
minifiable: Option[Boolean] = None,
)

/** syntax-sensitive view */
Expand Down
29 changes: 14 additions & 15 deletions src/main/scala/esmeta/es/util/fuzzer/FSTrie.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,19 @@ import scala.collection.mutable.Map as MMap
import esmeta.util.BaseUtils.chiSquaredTest
import io.circe.*, io.circe.syntax.*, io.circe.generic.semiauto.*

class FSTrieWrapper[K] {
val config: FSTrieConfig = FSTrieConfig(
scoringFunction = (hits, misses) => {
val absentHits = rootHits - hits
val absentMisses = rootMisses - misses
chiSquaredTest(hits, misses, absentHits, absentMisses)
},
promotionCriteria = 3,
demotionCriteria = 3,
maxSensitivity = 3,
)

class FSTrieWrapper[K](
val config: FSTrieConfig,
) {
val root: FSTrie[K] = FSTrie[K](status = FSTrieStatus.Noticed)

val scoringFunction: (Int, Int) => Double = (hits, misses) => {
val absentHits = rootHits - hits
val absentMisses = rootMisses - misses
chiSquaredTest(hits, misses, absentHits, absentMisses)
}
// val scoringFunction: (Int, Int) => Double = (hits, misses) =>
// hits.toDouble / (hits + misses)

private var rootHits: Int = 0
private var rootMisses: Int = 0

Expand Down Expand Up @@ -52,6 +51,8 @@ class FSTrieWrapper[K] {

given fSTrieEncoder: Encoder[FSTrie[String]] = deriveEncoder
given fsTrieDecoder: Decoder[FSTrie[String]] = deriveDecoder
given fsTrieConfigEncoder: Encoder[FSTrieConfig] = deriveEncoder
given fsTrieConfigDecoder: Decoder[FSTrieConfig] = deriveDecoder

/** A trie that stores the status of each node in the trie, and calculates the
* score of each node based on the hits and misses of the node and its
Expand Down Expand Up @@ -146,7 +147,7 @@ class FSTrieWrapper[K] {
node.dirty = false
node.status match {
case Promotable => // calculate the score based on hits and misses
node.avgScore = config.scoringFunction(node.hits, node.misses)
node.avgScore = scoringFunction(node.hits, node.misses)
node.avgScoreSq = node.avgScore * node.avgScore
node.promotables = 1
case Noticed => // calculate the average score and number of the node's promotables
Expand Down Expand Up @@ -272,8 +273,6 @@ class FSTrieWrapper[K] {
* scoring function, at which a node is demoted from Demotable to Ignored
*/
case class FSTrieConfig(
scoringFunction: (Int, Int) => Double = (hits, misses) =>
hits.toDouble / (hits + misses),
promotionCriteria: Int = 3,
demotionCriteria: Int = 3,
maxSensitivity: Int = 3,
Expand Down
4 changes: 3 additions & 1 deletion src/main/scala/esmeta/es/util/fuzzer/Fuzzer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ class Fuzzer(
"minimal(#)",
"node(#)",
"branch(#)",
"minifiable(%)",
)
if (kFs > 0) header ++= Vector(s"sens-node(#)", s"sens-branch(#)")
header ++= Vector("target-conds(#)")
Expand Down Expand Up @@ -351,7 +352,8 @@ class Fuzzer(
val bv = cov.branchViewCov
val tc = cov.targetCondViews.size
val tcv = cov.targetCondViews.map(_._2.size).fold(0)(_ + _)
var row = Vector(iter, e, t, visited.size, pool.size, n, b)
val mr = (cov.minifiableRate * 100 * 1000).round / 1000.0
var row = Vector(iter, e, t, visited.size, pool.size, n, b, mr)
if (kFs > 0) row ++= Vector(nv, bv)
row ++= Vector(tc)
if (kFs > 0) row ++= Vector(tcv)
Expand Down
21 changes: 15 additions & 6 deletions src/main/scala/esmeta/es/util/fuzzer/MinifyChecker.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ import esmeta.es.*
import esmeta.parser.ESParser
import esmeta.spec.Spec
import akka.http.scaladsl.model.headers.CacheDirectives.`max-age`
import esmeta.js.minifier.Minifier

object MinifyChecker {
val swcMinifyFunction: String => Option[String] = code =>
Minifier.minifySwc(code) match
case Failure(exception) =>
println(s"[minify-check] $code $exception"); None
case Success(minified) => Some(minified)
}

class MinifyChecker(
spec: Spec,
Expand Down Expand Up @@ -62,13 +71,13 @@ class MinifyChecker(

var result = acc

println("------flattenedAst1-----")
flattenedAst1.foreach(ast => println(ast.name))
println("------------------------")
// println("------flattenedAst1-----")
// flattenedAst1.foreach(ast => println(ast.name))
// println("------------------------")

println("------flattenedAst2-----")
flattenedAst2.foreach(ast => println(ast.name))
println("------------------------")
// println("------flattenedAst2-----")
// flattenedAst2.foreach(ast => println(ast.name))
// println("------------------------")

// implement diff algorithm here
// partially implement just comparing ast names
Expand Down

0 comments on commit 4183d25

Please sign in to comment.