Skip to content

Commit

Permalink
NODE-2425 Optimization of JS compile methods (#3793)
Browse files Browse the repository at this point in the history
  • Loading branch information
xrtm000 authored Jan 30, 2023
1 parent 8f4389d commit 61b387e
Show file tree
Hide file tree
Showing 17 changed files with 1,253 additions and 299 deletions.
82 changes: 38 additions & 44 deletions lang/js/src/main/scala/com/wavesplatform/JsApiUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ package com.wavesplatform

import com.wavesplatform.lang.contract.DApp
import com.wavesplatform.lang.v1.FunctionHeader.{Native, User}
import com.wavesplatform.lang.v1.compiler.CompilationError
import com.wavesplatform.lang.v1.compiler.Terms.*
import com.wavesplatform.lang.v1.compiler.Types.{CASETYPEREF, FINAL, LIST, NOTHING, TYPE, UNION}
import com.wavesplatform.lang.v1.compiler.{CompilationError, CompilerContext}
import com.wavesplatform.lang.v1.parser.Expressions
import com.wavesplatform.lang.v1.parser.Expressions.{PART, Pos, Type}
import com.wavesplatform.lang.v1.parser.Expressions.{PART, Type}

import scala.scalajs.js
import scala.scalajs.js.Any
Expand Down Expand Up @@ -43,15 +43,15 @@ object JsApiUtils {
"posStart" -> annFunc.position.start,
"posEnd" -> annFunc.position.end,
"name" -> serPartStr(ann.name),
"argList" -> ann.args.map(serPartStr).toJSArray
"argList" -> ann.args.toJSArray.map(serPartStr)
)
}

jObj(
"type" -> "ANNOTATEDFUNC",
"posStart" -> annFunc.position.start,
"posEnd" -> annFunc.position.end,
"annList" -> annFunc.anns.map(serAnnotation).toJSArray,
"annList" -> annFunc.anns.toJSArray.map(serAnnotation),
"func" -> serDec(annFunc.f)
)
}
Expand All @@ -60,27 +60,26 @@ object JsApiUtils {
"type" -> "DAPP",
"posStart" -> ast.position.start,
"posEnd" -> ast.position.end,
"decList" -> ast.decs.map(serDec).toJSArray,
"annFuncList" -> ast.fs.map(serAnnFunc).toJSArray
"decList" -> ast.decs.toJSArray.map(serDec),
"annFuncList" -> ast.fs.toJSArray.map(serAnnFunc)
)
}

def expressionScriptToJs(ast: Expressions.SCRIPT): js.Object = {
def expressionScriptToJs(ast: Expressions.SCRIPT): js.Object =
jObj(
"type" -> "SCRIPT",
"posStart" -> ast.position.start,
"posEnd" -> ast.position.end,
"expr" -> serExpr(ast.expr)
)
}

def serExpr(expr: Expressions.EXPR): js.Object = {

def serType(t: FINAL): js.Object = {
t match {
case ut: UNION =>
jObj(
"unionTypes" -> ut.typeList.map(serType(_)).toJSArray
"unionTypes" -> ut.typeList.toJSArray.map(serType(_))
)
case lt: LIST =>
jObj(
Expand All @@ -98,7 +97,7 @@ object JsApiUtils {
"posStart" -> expr.position.start,
"posEnd" -> expr.position.end,
"resultType" -> serType(expr.resultType.getOrElse(NOTHING)),
"ctx" -> serCtx(expr.ctxOpt.getOrElse(Map.empty))
"ctx" -> serCtx(expr.ctxOpt.getOrElse(CompilerContext.empty))
)

expr match {
Expand All @@ -110,65 +109,56 @@ object JsApiUtils {

case x: Expressions.REF =>
val additionalDataObj = jObj("name" -> Expressions.PART.toOption[String](x.key).getOrElse("").toString)
mergeJSObjects(commonDataObj, additionalDataObj)
js.Object.assign(additionalDataObj, commonDataObj)

case Expressions.GETTER(_, ref, field, _, _, _) =>
val additionalDataObj = jObj(
"ref" -> serExpr(ref),
"field" -> serPartStr(field)
)
mergeJSObjects(commonDataObj, additionalDataObj)
js.Object.assign(additionalDataObj, commonDataObj)

case Expressions.BLOCK(_, dec, body, _, _) =>
val additionalDataObj = jObj(
"dec" -> serDec(dec),
"body" -> serExpr(body)
)
mergeJSObjects(commonDataObj, additionalDataObj)
js.Object.assign(additionalDataObj, commonDataObj)

case Expressions.IF(_, cond, ifTrue, ifFalse, _, _) =>
val additionalDataObj = jObj(
"cond" -> serExpr(cond),
"ifTrue" -> serExpr(ifTrue),
"ifFalse" -> serExpr(ifFalse)
)
mergeJSObjects(commonDataObj, additionalDataObj)
js.Object.assign(additionalDataObj, commonDataObj)

case Expressions.FUNCTION_CALL(_, name, args, _, _) =>
val additionalDataObj = jObj(
"name" -> serPartStr(name),
"args" -> args.map(serExpr).toJSArray
"args" -> args.toJSArray.map(serExpr)
)
mergeJSObjects(commonDataObj, additionalDataObj)
js.Object.assign(additionalDataObj, commonDataObj)

case Expressions.FOLD(_, limit, value, acc, func, _, _) =>
val additionalDataObj = jObj(
"name" -> s"FOLD<$limit>",
"args" -> js.Array(serExpr(value), serExpr(acc), func.key.toString: js.Any)
)
mergeJSObjects(commonDataObj, additionalDataObj)
js.Object.assign(additionalDataObj, commonDataObj)

case Expressions.MATCH(_, expr, cases, _, ctxOpt) =>
val additionalDataObj = jObj(
"expr" -> serExpr(expr),
"cases" -> cases.map(serMatchCase(_, ctxOpt.getOrElse(Map.empty))).toJSArray
"cases" -> cases.toJSArray.map(serMatchCase(_, ctxOpt.getOrElse(CompilerContext.empty)))
)
mergeJSObjects(commonDataObj, additionalDataObj)
js.Object.assign(additionalDataObj, commonDataObj)

case t => jObj("[not_supported]stringRepr" -> t.toString)
}
}

def mergeJSObjects(objs: js.Dynamic*): js.Object = {
val result = js.Dictionary.empty[Any]
for (source <- objs) {
for ((key, value) <- source.asInstanceOf[js.Dictionary[Any]])
result(key) = value
}
result.asInstanceOf[js.Object]
}

def serMatchCase(c: Expressions.MATCH_CASE, simpleCtx: Map[String, Pos]): js.Object = {
def serMatchCase(c: Expressions.MATCH_CASE, simpleCtx: CompilerContext): js.Object = {
val vars = c.pattern.subpatterns.collect { case (Expressions.TypedVar(Some(newVarName), caseType), _) =>
(serPartStr(newVarName), serType(caseType))
}
Expand All @@ -184,14 +174,18 @@ object JsApiUtils {
)
}

def serCtx(simpleCtx: Map[String, Pos]): js.Object = {
simpleCtx.map { ctxEl =>
jObj(
"name" -> ctxEl._1,
"posStart" -> ctxEl._2.start,
"posEnd" -> ctxEl._2.end
)
}.toJSArray
def serCtx(ctx: CompilerContext): js.Object = {
val r = js.Array[js.Dynamic]()
def addInfo(decl: (String, CompilerContext.PositionedInfo)): Unit =
if (decl._2.pos.start != -1)
r += jObj(
"name" -> decl._1,
"posStart" -> decl._2.pos.start,
"posEnd" -> decl._2.pos.end
)
ctx.varDefs.foreach(addInfo)
ctx.functionDefs.foreach(addInfo)
r
}

def serType(t: Type): js.Object =
Expand All @@ -208,12 +202,12 @@ object JsApiUtils {
case Expressions.Union(types) =>
jObj(
"isUnion" -> "true",
"typeList" -> types.map(serType).toJSArray
"typeList" -> types.toJSArray.map(serType)
)
case Expressions.Tuple(types) =>
jObj(
"isTuple" -> "true",
"typeList" -> types.map(serType).toJSArray
"typeList" -> types.toJSArray.map(serType)
)
}

Expand All @@ -239,7 +233,7 @@ object JsApiUtils {
"posStart" -> p.start,
"posEnd" -> p.end,
"name" -> serPartStr(name),
"argList" -> args.map(arg => serFuncArg(arg._1, arg._2)).toJSArray,
"argList" -> args.toJSArray.map(arg => serFuncArg(arg._1, arg._2)),
"expr" -> serExpr(expr)
)
case t => jObj("[not_supported]stringRepr" -> t.toString)
Expand Down Expand Up @@ -278,7 +272,7 @@ object JsApiUtils {
case Native(name) => name.toString()
case User(internalName, _) => internalName
}),
"args" -> args.map(r).toJSArray
"args" -> args.toJSArray.map(r)
)
case t => jObj("[not_supported]stringRepr" -> t.toString)
}
Expand All @@ -292,10 +286,10 @@ object JsApiUtils {
}

def typeRepr(t: TYPE): js.Any = t match {
case UNION(l, _) => l.map(typeRepr).toJSArray
case UNION(l, _) => l.toJSArray.map(typeRepr)
case CASETYPEREF(name, fields, false) =>
js.Dynamic.literal("typeName" -> name, "fields" -> fields.map(f => js.Dynamic.literal("name" -> f._1, "type" -> typeRepr(f._2))).toJSArray)
case LIST(t) => js.Dynamic.literal("listOf" -> typeRepr(t))
jObj("typeName" -> name, "fields" -> fields.toJSArray.map(f => jObj("name" -> f._1, "type" -> typeRepr(f._2))))
case LIST(t) => jObj("listOf" -> typeRepr(t))
case t => t.toString
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import com.wavesplatform.lang.v1.ContractLimits.*
import com.wavesplatform.lang.v1.compiler.Terms
import com.wavesplatform.lang.v1.compiler.Terms.*
import com.wavesplatform.lang.v1.estimator.ScriptEstimator
import com.wavesplatform.lang.v1.estimator.v3.{DAppEstimation, GlobalDeclarationsCosts, ScriptEstimatorV3}
import com.wavesplatform.lang.v1.{BaseGlobal, FunctionHeader}
import monix.eval.Coeval

Expand Down Expand Up @@ -64,10 +65,9 @@ object ContractScript {
if (stdLibVersion < V6) {
Right(true)
} else {
MetaMapper.dicFromProto(expr)
.map(!_.callableFuncTypes
.exists(_.flatten.exists(_.containsUnion))
)
MetaMapper
.dicFromProto(expr)
.map(!_.callableFuncTypes.exists(_.flatten.exists(_.containsUnion)))
}
}

Expand All @@ -82,39 +82,25 @@ object ContractScript {
verifier <- dApp.verifierFuncOpt.traverse(estimateVerifier(version, dApp, estimator, fixEstimateOfVerifier, _))
} yield verifier ++ callables

def estimateUserFunctions(
version: StdLibVersion,
dApp: DApp,
estimator: ScriptEstimator
): Either[String, List[(String, Long)]] =
estimateDeclarations(version, dApp, estimator, dApp.decs.collect { case f: FUNC => (None, f) }, preserveDefinition = false)

def estimateGlobalVariables(
version: StdLibVersion,
dApp: DApp,
estimator: ScriptEstimator
): Either[String, List[(String, Long)]] =
estimateDeclarations(version, dApp, estimator, dApp.decs.collect { case l: LET => (None, l) }, preserveDefinition = false)

private def callables(dApp: DApp): List[(Some[String], FUNC)] =
dApp.callableFuncs
.map(func => (Some(func.annotation.invocationArgName), func.u))

private def estimateDeclarations(
def estimateDeclarations(
version: StdLibVersion,
dApp: DApp,
estimator: ScriptEstimator,
functions: List[(Option[String], DECLARATION)],
declarations: List[(Option[String], DECLARATION)],
preserveDefinition: Boolean
): Either[String, List[(String, Long)]] =
functions.traverse {
case (annotationArgName, funcExpr) =>
declarations
.traverse { case (annotationArgName, funcExpr) =>
estimator(
varNames(version, DAppType),
functionCosts(version, DAppType),
constructExprFromDeclAndContext(dApp.decs, annotationArgName, funcExpr, preserveDefinition)
).map((funcExpr.name, _))
}
}

private def estimateVerifier(
version: StdLibVersion,
Expand Down Expand Up @@ -155,10 +141,7 @@ object ContractScript {
case Terms.FAILED_DEC() =>
FAILED_EXPR()
}
val funcWithContext =
annotationArgNameOpt.fold(declExpr)(
annotationArgName => BLOCK(LET(annotationArgName, TRUE), declExpr)
)
val funcWithContext = annotationArgNameOpt.fold(declExpr)(argName => BLOCK(LET(argName, TRUE), declExpr))
dec.foldRight(funcWithContext)((declaration, expr) => BLOCK(declaration, expr))
}

Expand Down Expand Up @@ -216,4 +199,34 @@ object ContractScript {
annotatedFunctionComplexities <- estimateAnnotatedFunctions(version, dApp, estimator, fixEstimateOfVerifier)
max = annotatedFunctionComplexities.toList.maximumOption(_._2 compareTo _._2).getOrElse(("", 0L))
} yield (max, annotatedFunctionComplexities.toMap)

def estimateFully(version: StdLibVersion, dApp: DApp, estimator: ScriptEstimator): Either[String, DAppEstimation] = {
estimator match {
case estimatorV3: ScriptEstimatorV3 =>
val allDecs = dApp.decs ++ dApp.callableFuncs.map(_.u) ++ dApp.verifierFuncOpt.map(_.u)
val singleAst = allDecs.foldRight[EXPR](TRUE) { case (decl, expr) => BLOCK(decl, expr) }
val libCosts = functionCosts(version, DAppType)
for {
GlobalDeclarationsCosts(lets, functions) <- estimatorV3.globalDeclarationsCosts(libCosts, singleAst)
nonAnnotatedFunctions = functions.view.filterKeys(k => dApp.decs.exists(_.name == k)).toMap
annotatedFunctions = functions.view.filterKeys(!nonAnnotatedFunctions.contains(_)).toMap
} yield DAppEstimation(annotatedFunctions, lets, nonAnnotatedFunctions)
case oldEstimator =>
for {
annotatedComplexities <- estimateAnnotatedFunctions(version, dApp, oldEstimator, fixEstimateOfVerifier = true)
letsCosts <- oldGlobalLetsCosts(version, dApp, oldEstimator)
functionsCosts <- oldGlobalFunctionsCosts(version, dApp, oldEstimator)
} yield DAppEstimation(annotatedComplexities.toMap, letsCosts, functionsCosts)
}
}

def oldGlobalFunctionsCosts(version: StdLibVersion, dApp: DApp, estimator: ScriptEstimator): Either[String, Map[String, Long]] =
ContractScript
.estimateDeclarations(version, dApp, estimator, dApp.decs.collect { case f: FUNC => (None, f) }, preserveDefinition = false)
.map(_.toMap)

def oldGlobalLetsCosts(version: StdLibVersion, dApp: DApp, estimator: ScriptEstimator): Either[String, Map[String, Long]] =
ContractScript
.estimateDeclarations(version, dApp, estimator, dApp.decs.collect { case l: LET => (None, l) }, preserveDefinition = false)
.map(_.toMap)
}
Loading

0 comments on commit 61b387e

Please sign in to comment.