Skip to content

Commit

Permalink
Merge branch 'version-1.5.x' into NODE-2606
Browse files Browse the repository at this point in the history
  • Loading branch information
Ivan Mashonskii committed Dec 4, 2023
2 parents 199e717 + 7f9de1e commit 6ade140
Show file tree
Hide file tree
Showing 65 changed files with 533 additions and 244 deletions.
33 changes: 33 additions & 0 deletions lang/jvm/src/main/scala/com/wavesplatform/lang/FileCompiler.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.wavesplatform.lang

import com.google.common.base.Charsets
import com.google.common.io
import com.wavesplatform.lang.v1.estimator.v3.ScriptEstimatorV3

import java.io.File

object FileCompiler extends App {
private val estimator = ScriptEstimatorV3.latest

args
.foreach { path =>
val scriptFile = new File(path).getAbsoluteFile
require(scriptFile.isFile, s"$path is not a file")
val baseDirectory = scriptFile.getParentFile
val imports = baseDirectory
.listFiles({ (pathname: File) =>
pathname.isFile && pathname.getAbsoluteFile != scriptFile
})
.map { f =>
f.getName -> io.Files.asCharSource(f, Charsets.UTF_8).read()
}
.toMap

API
.compile(io.Files.asCharSource(scriptFile, Charsets.UTF_8).read(), estimator, libraries = imports)
.fold(
error => throw new RuntimeException(s"$error while compiling $path"),
_ => println(s"successfully compiled $path")
)
}
}
2 changes: 1 addition & 1 deletion lang/jvm/src/main/scala/com/wavesplatform/lang/Lang.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ object Lang {

def compileDApp(input: String): DAppWithMeta =
API
.compile(input, ScriptEstimatorV3(fixOverflow = true, overhead = false))
.compile(input, ScriptEstimatorV3.latest)
.flatMap {
case r: CompileResult.DApp =>
val javaMeta = Meta(
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ trait ScriptEstimator {

object ScriptEstimator {
def all(fixOverflow: Boolean): List[ScriptEstimator] =
List(ScriptEstimatorV1, ScriptEstimatorV2, ScriptEstimatorV3(fixOverflow, overhead = false))
List(ScriptEstimatorV1, ScriptEstimatorV2, ScriptEstimatorV3(fixOverflow, overhead = false, letFixes = true))
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import shapeless.{Lens, lens}

private[v3] case class EstimatorContext(
funcs: Map[FunctionHeader, (Coeval[Long], Set[String])],
usedRefs: Set[String] = Set.empty,
usedRefs: Set[String] = Set(),
refsCosts: Map[String, Long] = Map(),
globalFunctionsCosts: Map[String, Long] = Map(), //
globalLetsCosts: Map[String, Long] = Map(), // only for globalDeclarationsMode
globalLetEvals: Map[String, EvalM[Long]] = Map() //
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package com.wavesplatform.lang.v1.estimator.v3

import cats.implicits.*
import cats.implicits.{toBifunctorOps, toFoldableOps, toTraverseOps}
import cats.{Id, Monad}
import cats.syntax.functor.*
import com.wavesplatform.lang.v1.FunctionHeader
import com.wavesplatform.lang.v1.FunctionHeader.User
import com.wavesplatform.lang.v1.compiler.Terms.*
Expand All @@ -13,7 +14,7 @@ import monix.eval.Coeval

import scala.util.Try

case class ScriptEstimatorV3(fixOverflow: Boolean, overhead: Boolean) extends ScriptEstimator {
case class ScriptEstimatorV3(fixOverflow: Boolean, overhead: Boolean, letFixes: Boolean) extends ScriptEstimator {
private val overheadCost: Long = if (overhead) 1 else 0

override val version: Int = 3
Expand All @@ -39,60 +40,50 @@ case class ScriptEstimatorV3(fixOverflow: Boolean, overhead: Boolean) extends Sc
globalDeclarationsMode: Boolean
): (EstimatorContext, Either[EstimationError, Long]) = {
val ctxFuncs = funcs.view.mapValues((_, Set[String]())).toMap
evalExpr(expr, globalDeclarationsMode).run(EstimatorContext(ctxFuncs)).value
evalExpr(expr, Set(), globalDeclarationsMode).run(EstimatorContext(ctxFuncs)).value
}

private def evalExpr(t: EXPR, globalDeclarationsMode: Boolean = false): EvalM[Long] =
private def evalExpr(t: EXPR, activeFuncArgs: Set[String], globalDeclarationsMode: Boolean = false): EvalM[Long] =
if (Thread.currentThread().isInterrupted)
raiseError("Script estimation was interrupted")
else
t match {
case LET_BLOCK(let, inner) => evalLetBlock(let, inner, globalDeclarationsMode)
case BLOCK(let: LET, inner) => evalLetBlock(let, inner, globalDeclarationsMode)
case BLOCK(f: FUNC, inner) => evalFuncBlock(f, inner, globalDeclarationsMode)
case LET_BLOCK(let, inner) => evalLetBlock(let, inner, activeFuncArgs, globalDeclarationsMode)
case BLOCK(let: LET, inner) => evalLetBlock(let, inner, activeFuncArgs, globalDeclarationsMode)
case BLOCK(f: FUNC, inner) => evalFuncBlock(f, inner, activeFuncArgs, globalDeclarationsMode)
case BLOCK(_: FAILED_DEC, _) => zero
case REF(str) => markRef(str)
case REF(str) => evalRef(str, activeFuncArgs)
case _: EVALUATED => const(overheadCost)
case IF(cond, t1, t2) => evalIF(cond, t1, t2)
case GETTER(expr, _) => evalGetter(expr)
case FUNCTION_CALL(header, args) => evalFuncCall(header, args)
case IF(cond, t1, t2) => evalIF(cond, t1, t2, activeFuncArgs)
case GETTER(expr, _) => evalGetter(expr, activeFuncArgs)
case FUNCTION_CALL(header, args) => evalFuncCall(header, args, activeFuncArgs)
case _: FAILED_EXPR => zero
}

private def evalHoldingFuncs(expr: EXPR, ctxFuncsOpt: Option[Map[FunctionHeader, (Coeval[Long], Set[String])]] = None): EvalM[Long] =
private def evalLetBlock(let: LET, nextExpr: EXPR, activeFuncArgs: Set[String], globalDeclarationsMode: Boolean): EvalM[Long] =
for {
_ <- if (globalDeclarationsMode) saveGlobalLetCost(let, activeFuncArgs) else doNothing
startCtx <- get[Id, EstimatorContext, EstimationError]
_ <- ctxFuncsOpt.fold(doNothing.void)(ctxFuncs => update(funcs.set(_)(ctxFuncs)))
cost <- evalExpr(expr)
_ <- update(funcs.set(_)(startCtx.funcs))
} yield cost

private def evalLetBlock(let: LET, inner: EXPR, globalDeclarationsMode: Boolean): EvalM[Long] =
for {
startCtx <- get[Id, EstimatorContext, EstimationError]
overlap = startCtx.usedRefs.contains(let.name)
_ <- update(usedRefs.modify(_)(_ - let.name))
letEval = evalHoldingFuncs(let.value, Some(startCtx.funcs))
_ <- if (globalDeclarationsMode) saveGlobalLetCost(let) else doNothing
nextCost <- evalExpr(inner, globalDeclarationsMode)
ctx <- get[Id, EstimatorContext, EstimationError]
letCost <- if (ctx.usedRefs.contains(let.name)) letEval else zero
_ <- update(usedRefs.modify(_)(r => if (overlap) r + let.name else r - let.name))
result <- sum(nextCost, letCost)
letEval = evalHoldingFuncs(let.value, activeFuncArgs, Some(startCtx.funcs))
_ <- beforeNextExprEval(let, letEval)
nextExprCost <- evalExpr(nextExpr, activeFuncArgs, globalDeclarationsMode)
nextExprCtx <- get[Id, EstimatorContext, EstimationError]
_ <- afterNextExprEval(let, startCtx)
letCost <- if (nextExprCtx.usedRefs.contains(let.name)) letEval else const(0L)
result <- sum(nextExprCost, letCost)
} yield result

private def saveGlobalLetCost(let: LET): EvalM[Unit] = {
private def saveGlobalLetCost(let: LET, activeFuncArgs: Set[String]): EvalM[Unit] = {
val costEvaluation =
for {
startCtx <- get[Id, EstimatorContext, EstimationError]
bodyCost <- evalExpr(let.value)
bodyEvalCtx <- get[Id, EstimatorContext, EstimationError]
usedRefs = bodyEvalCtx.usedRefs diff startCtx.usedRefs
startCtx <- get[Id, EstimatorContext, EstimationError]
(bodyCost, usedRefs) <- withUsedRefs(evalExpr(let.value, activeFuncArgs))
ctx <- get[Id, EstimatorContext, EstimationError]
letCosts <- usedRefs.toSeq.traverse { ref =>
local {
for {
_ <- update(funcs.set(_)(startCtx.funcs))
cost <- bodyEvalCtx.globalLetEvals.getOrElse(ref, zero)
cost <- ctx.globalLetEvals.getOrElse(ref, zero)
} yield cost
}
}
Expand All @@ -108,26 +99,47 @@ case class ScriptEstimatorV3(fixOverflow: Boolean, overhead: Boolean) extends Sc
} yield ()
}

private def evalFuncBlock(func: FUNC, inner: EXPR, globalDeclarationsMode: Boolean): EvalM[Long] =
private def beforeNextExprEval(let: LET, eval: EvalM[Long]): EvalM[Unit] =
for {
cost <- local(eval)
_ <- update(ctx =>
usedRefs
.modify(ctx)(_ - let.name)
.copy(refsCosts = ctx.refsCosts + (let.name -> cost))
)
} yield ()

private def afterNextExprEval(let: LET, startCtx: EstimatorContext): EvalM[Unit] =
update(ctx =>
usedRefs
.modify(ctx)(r => if (startCtx.usedRefs.contains(let.name)) r + let.name else r - let.name)
.copy(refsCosts =
if (startCtx.refsCosts.contains(let.name))
ctx.refsCosts + (let.name -> startCtx.refsCosts(let.name))
else
ctx.refsCosts - let.name
)
)

private def evalFuncBlock(func: FUNC, nextExpr: EXPR, activeFuncArgs: Set[String], globalDeclarationsMode: Boolean): EvalM[Long] =
for {
startCtx <- get[Id, EstimatorContext, EstimationError]
_ <- checkShadowing(func, startCtx)
funcCost <- evalHoldingFuncs(func.body)
bodyEvalCtx <- get[Id, EstimatorContext, EstimationError]
refsUsedInBody = bodyEvalCtx.usedRefs diff startCtx.usedRefs
_ <- if (globalDeclarationsMode) saveGlobalFuncCost(func.name, funcCost, bodyEvalCtx, refsUsedInBody) else doNothing
_ <- handleUsedRefs(func.name, funcCost, startCtx, refsUsedInBody)
nextCost <- evalExpr(inner, globalDeclarationsMode)
} yield nextCost
startCtx <- get[Id, EstimatorContext, EstimationError]
_ <- checkShadowing(func, startCtx)
(funcCost, refsUsedInBody) <- withUsedRefs(evalHoldingFuncs(func.body, activeFuncArgs ++ func.args))
_ <- if (globalDeclarationsMode) saveGlobalFuncCost(func.name, funcCost, refsUsedInBody) else doNothing
_ <- handleUsedRefs(func.name, funcCost, startCtx, refsUsedInBody)
nextExprCost <- evalExpr(nextExpr, activeFuncArgs, globalDeclarationsMode)
} yield nextExprCost

private def checkShadowing(func: FUNC, startCtx: EstimatorContext): EvalM[Any] =
if (fixOverflow && startCtx.funcs.contains(FunctionHeader.User(func.name)))
raiseError(s"Function '${func.name}${func.args.mkString("(", ", ", ")")}' shadows preceding declaration")
else
doNothing

private def saveGlobalFuncCost(name: String, funcCost: Long, ctx: EstimatorContext, refsUsedInBody: Set[String]): EvalM[Unit] =
private def saveGlobalFuncCost(name: String, funcCost: Long, refsUsedInBody: Set[String]): EvalM[Unit] =
for {
ctx <- get[Id, EstimatorContext, EstimationError]
letCosts <- local(refsUsedInBody.toSeq.traverse(ctx.globalLetEvals.getOrElse(_, zero)))
totalCost = math.max(1, funcCost + letCosts.sum)
_ <- set[Id, EstimatorContext, EstimationError](ctx.copy(globalFunctionsCosts = ctx.globalFunctionsCosts + (name -> totalCost)))
Expand All @@ -143,46 +155,80 @@ case class ScriptEstimatorV3(fixOverflow: Boolean, overhead: Boolean) extends Sc
}
)

private def evalIF(cond: EXPR, ifTrue: EXPR, ifFalse: EXPR): EvalM[Long] =
private def evalIF(cond: EXPR, ifTrue: EXPR, ifFalse: EXPR, activeFuncArgs: Set[String]): EvalM[Long] =
for {
cond <- evalHoldingFuncs(cond)
right <- evalHoldingFuncs(ifTrue)
left <- evalHoldingFuncs(ifFalse)
cond <- evalHoldingFuncs(cond, activeFuncArgs)
right <- evalHoldingFuncs(ifTrue, activeFuncArgs)
left <- evalHoldingFuncs(ifFalse, activeFuncArgs)
r1 <- sum(cond, Math.max(right, left))
r2 <- sum(r1, overheadCost)
} yield r2

private def markRef(key: String): EvalM[Long] =
update(usedRefs.modify(_)(_ + key)).map(_ => overheadCost)
private def evalRef(key: String, activeFuncArgs: Set[String]): EvalM[Long] =
if (activeFuncArgs.contains(key) && letFixes)
const(overheadCost)
else
update(usedRefs.modify(_)(_ + key)).map(_ => overheadCost)

private def evalGetter(expr: EXPR): EvalM[Long] =
evalExpr(expr).flatMap(sum(_, overheadCost))
private def evalGetter(expr: EXPR, activeFuncArgs: Set[String]): EvalM[Long] =
evalExpr(expr, activeFuncArgs).flatMap(sum(_, overheadCost))

private def evalFuncCall(header: FunctionHeader, args: List[EXPR]): EvalM[Long] =
private def evalFuncCall(header: FunctionHeader, args: List[EXPR], activeFuncArgs: Set[String]): EvalM[Long] =
for {
ctx <- get[Id, EstimatorContext, EstimationError]
(bodyCost, bodyUsedRefs) <- funcs
.get(ctx)
.get(header)
.map(const)
.getOrElse(
raiseError[Id, EstimatorContext, EstimationError, (Coeval[Long], Set[String])](s"function '$header' not found")
)
_ <- update(
(funcs ~ usedRefs).modify(_) { case (funcs, usedRefs) =>
(
funcs + ((header, (bodyCost, Set[String]()))),
usedRefs ++ bodyUsedRefs
)
}
)
argsCosts <- args.traverse(evalHoldingFuncs(_))
argsCostsSum <- argsCosts.foldM(0L)(sum)
bodyCostV = bodyCost.value()
correctedBodyCost = if (!overhead && bodyCostV == 0) 1 else bodyCostV
ctx <- get[Id, EstimatorContext, EstimationError]
(bodyCost, bodyUsedRefs) <- getFuncCost(header, ctx)
_ <- setFuncToCtx(header, bodyCost, bodyUsedRefs)
(argsCosts, _) <- withUsedRefs(args.traverse(evalHoldingFuncs(_, activeFuncArgs)))
argsCostsSum <- argsCosts.foldM(0L)(sum)
bodyCostV = bodyCost.value()
correctedBodyCost =
if (!overhead && !letFixes && bodyCostV == 0) 1
else if (letFixes && bodyCostV == 0 && isBlankFunc(bodyUsedRefs, ctx.refsCosts)) 1
else bodyCostV
result <- sum(argsCostsSum, correctedBodyCost)
} yield result

private def setFuncToCtx(header: FunctionHeader, bodyCost: Coeval[Long], bodyUsedRefs: Set[EstimationError]): EvalM[Unit] =
update(
(funcs ~ usedRefs).modify(_) { case (funcs, usedRefs) =>
(
funcs + (header -> (bodyCost, Set())),
usedRefs ++ bodyUsedRefs
)
}
)

private def getFuncCost(header: FunctionHeader, ctx: EstimatorContext): EvalM[(Coeval[Long], Set[EstimationError])] =
funcs
.get(ctx)
.get(header)
.map(const)
.getOrElse(
raiseError[Id, EstimatorContext, EstimationError, (Coeval[Long], Set[EstimationError])](s"function '$header' not found")
)

private def isBlankFunc(usedRefs: Set[String], refsCosts: Map[String, Long]): Boolean =
!usedRefs.exists(refsCosts.get(_).exists(_ > 0))

private def evalHoldingFuncs(
expr: EXPR,
activeFuncArgs: Set[String],
ctxFuncsOpt: Option[Map[FunctionHeader, (Coeval[Long], Set[String])]] = None
): EvalM[Long] =
for {
startCtx <- get[Id, EstimatorContext, EstimationError]
_ <- ctxFuncsOpt.fold(doNothing.void)(ctxFuncs => update(funcs.set(_)(ctxFuncs)))
cost <- evalExpr(expr, activeFuncArgs)
_ <- update(funcs.set(_)(startCtx.funcs))
} yield cost

private def withUsedRefs[A](eval: EvalM[A]): EvalM[(A, Set[String])] =
for {
ctxBefore <- get[Id, EstimatorContext, EstimationError]
result <- eval
ctxAfter <- get[Id, EstimatorContext, EstimationError]
} yield (result, ctxAfter.usedRefs diff ctxBefore.usedRefs)

private def update(f: EstimatorContext => EstimatorContext): EvalM[Unit] =
modify[Id, EstimatorContext, EstimationError](f)

Expand All @@ -200,3 +246,7 @@ case class ScriptEstimatorV3(fixOverflow: Boolean, overhead: Boolean) extends Sc
liftEither(Try(r).toEither.leftMap(_ => "Illegal script"))
}
}

object ScriptEstimatorV3 {
val latest = ScriptEstimatorV3(fixOverflow = true, overhead = false, letFixes = true)
}
Original file line number Diff line number Diff line change
Expand Up @@ -883,7 +883,7 @@ class ContractCompilerTest extends PropSpec {
NoLibraries,
dAppV4Ctx,
V4,
ScriptEstimatorV3(fixOverflow = true, overhead = true),
ScriptEstimatorV3(fixOverflow = true, overhead = true, letFixes = true),
needCompaction = false,
removeUnusedCode = false
) should produce("Script is too large: 165187 bytes > 163840 bytes")
Expand Down Expand Up @@ -1042,7 +1042,7 @@ class ContractCompilerTest extends PropSpec {
NoLibraries,
getTestContext(V4).compilerContext,
V4,
ScriptEstimatorV3(fixOverflow = true, overhead = true),
ScriptEstimatorV3(fixOverflow = true, overhead = true, letFixes = true),
needCompaction = false,
removeUnusedCode = false
) shouldBe Symbol("right")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ class ExpressionCompilerV1Test extends PropSpec {
)
.compilerContext

val e = ScriptEstimatorV3(fixOverflow = true, overhead = true)
val e = ScriptEstimatorV3(fixOverflow = true, overhead = true, letFixes = true)
Global.compileExpression(expr, NoLibraries, ctx, V4, Account, e) should produce("Script is too large: 8756 bytes > 8192 bytes")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class FunctionComplexityDocTest extends PropSpec {
val arg = CONST_STRING("throw").explicitGet()
val expr = FUNCTION_CALL(function.header, List.fill(function.args.size)(arg))
val estimatedCost =
ScriptEstimatorV3(fixOverflow = true, overhead = false)(
ScriptEstimatorV3(fixOverflow = true, overhead = false, letFixes = true)(
varNames(ds.stdLibVersion, ds.contentType),
functionCosts(ds.stdLibVersion, ds.contentType),
expr
Expand Down
Loading

0 comments on commit 6ade140

Please sign in to comment.