From d1b232a621a735070cb1249bc98d886728c8bafd Mon Sep 17 00:00:00 2001 From: Artyom Sayadyan Date: Mon, 25 Sep 2023 15:37:30 +0300 Subject: [PATCH 1/7] NODE-2612 RIDE replaceByIndex() --- .../lang/v1/ListReplaceByIndexBenchmark.scala | 71 +++++++++++++++++++ lang/doc/v8/funcs/list-functions.hjson | 9 ++- .../lang/v1/evaluator/FunctionIds.scala | 13 ++-- .../v1/evaluator/ctx/impl/PureContext.scala | 33 ++++++++- .../evaluator/ListReplaceByIndexTest.scala | 30 ++++++++ 5 files changed, 148 insertions(+), 8 deletions(-) create mode 100644 benchmark/src/test/scala/com/wavesplatform/lang/v1/ListReplaceByIndexBenchmark.scala create mode 100644 lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/ListReplaceByIndexTest.scala diff --git a/benchmark/src/test/scala/com/wavesplatform/lang/v1/ListReplaceByIndexBenchmark.scala b/benchmark/src/test/scala/com/wavesplatform/lang/v1/ListReplaceByIndexBenchmark.scala new file mode 100644 index 00000000000..1e711bca3c0 --- /dev/null +++ b/benchmark/src/test/scala/com/wavesplatform/lang/v1/ListReplaceByIndexBenchmark.scala @@ -0,0 +1,71 @@ +package com.wavesplatform.lang.v1 + +import com.wavesplatform.common.utils.EitherExt2 +import com.wavesplatform.lang.Common +import com.wavesplatform.lang.directives.values.V8 +import com.wavesplatform.lang.v1.FunctionHeader.Native +import com.wavesplatform.lang.v1.compiler.Terms.* +import com.wavesplatform.lang.v1.evaluator.FunctionIds.REPLACE_BY_INDEX_OF_LIST +import com.wavesplatform.lang.v1.evaluator.ctx.impl.PureContext +import com.wavesplatform.lang.v1.traits.Environment +import org.openjdk.jmh.annotations.* +import org.openjdk.jmh.infra.Blackhole + +import scala.concurrent.duration.{MICROSECONDS, SECONDS} + +@OutputTimeUnit(MICROSECONDS) +@BenchmarkMode(Array(Mode.AverageTime)) +@Threads(1) +@Fork(1) +@Warmup(iterations = 10, time = 1, timeUnit = SECONDS) +@Measurement(iterations = 10, time = 1, timeUnit = SECONDS) +class ListReplaceByIndexBenchmark { + @Benchmark + def listReplaceFirstByIndex(st: ListReplaceByIndexSt, bh: Blackhole): Unit = + bh.consume(eval(st.ctx, st.replaceFirst, V8)) + + @Benchmark + def listReplaceMiddleByIndex(st: ListReplaceByIndexSt, bh: Blackhole): Unit = + bh.consume(eval(st.ctx, st.replaceMiddle, V8)) + + @Benchmark + def listReplaceLastByIndex(st: ListReplaceByIndexSt, bh: Blackhole): Unit = + bh.consume(eval(st.ctx, st.replaceLast, V8)) +} + +@State(Scope.Benchmark) +class ListReplaceByIndexSt { + val ctx = PureContext.build(V8, useNewPowPrecision = true).withEnvironment[Environment].evaluationContext(Common.emptyBlockchainEnvironment()) + + val list = ARR(Vector.fill(1000)(CONST_LONG(Long.MaxValue)), limited = true).explicitGet() + + val replaceFirst = + FUNCTION_CALL( + Native(REPLACE_BY_INDEX_OF_LIST), + List( + list, + CONST_LONG(0), + CONST_LONG(777) + ) + ) + + val replaceMiddle = + FUNCTION_CALL( + Native(REPLACE_BY_INDEX_OF_LIST), + List( + list, + CONST_LONG(500), + CONST_LONG(777) + ) + ) + + val replaceLast = + FUNCTION_CALL( + Native(REPLACE_BY_INDEX_OF_LIST), + List( + list, + CONST_LONG(999), + CONST_LONG(777) + ) + ) +} diff --git a/lang/doc/v8/funcs/list-functions.hjson b/lang/doc/v8/funcs/list-functions.hjson index b8cb2beaa13..cc13858b18e 100644 --- a/lang/doc/v8/funcs/list-functions.hjson +++ b/lang/doc/v8/funcs/list-functions.hjson @@ -109,8 +109,15 @@ name: "removeByIndex" params: [ "List[T]", "Int" ] doc: "Removes the element at given index from the list and returns new list." - paramsDoc: [ "The list.", "The element." ] + paramsDoc: [ "The list.", "The index." ] complexity: 7 + } + { + name: "replaceByIndex" + params: [ "List[T]", "Int", "T" ] + doc: "Replaces the element at given index in the list and returns new list." + paramsDoc: [ "The list.", "The index.", "New element." ] + complexity: 5 } ] } diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/FunctionIds.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/FunctionIds.scala index da973f82308..f4705d519cc 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/FunctionIds.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/FunctionIds.scala @@ -71,12 +71,13 @@ object FunctionIds { val STRING_TO_BIGINTOPT: Short = 424 val MEDIAN_LISTBIGINT: Short = 425 - val CREATE_LIST: Short = 1100 - val APPEND_LIST: Short = 1101 - val CONCAT_LIST: Short = 1102 - val INDEX_OF_LIST: Short = 1103 - val LAST_INDEX_OF_LIST: Short = 1104 - val REMOVE_BY_INDEX_OF_LIST: Short = 1105 + val CREATE_LIST: Short = 1100 + val APPEND_LIST: Short = 1101 + val CONCAT_LIST: Short = 1102 + val INDEX_OF_LIST: Short = 1103 + val LAST_INDEX_OF_LIST: Short = 1104 + val REMOVE_BY_INDEX_OF_LIST: Short = 1105 + val REPLACE_BY_INDEX_OF_LIST: Short = 1106 val UTF8STRING: Short = 1200 val BININT: Short = 1201 diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/PureContext.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/PureContext.scala index e3f5f319635..b7003e0aaf5 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/PureContext.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/PureContext.scala @@ -1471,6 +1471,29 @@ object PureContext { notImplemented[Id, EVALUATED]("removeByIndex(list: List[T], index: Int)", xs) } + private val listReplaceByIndex: BaseFunction[NoContext] = + NativeFunction( + "replaceByIndex", + 5, + REPLACE_BY_INDEX_OF_LIST, + PARAMETERIZEDLIST(TYPEPARAM('T')), + ("list", PARAMETERIZEDLIST(TYPEPARAM('T'))), + ("index", LONG), + ("element", TYPEPARAM('T')) + ) { + case ARR(list) :: CONST_LONG(index) :: element :: Nil => + if (list.isEmpty) + Left("Can't replace an element in empty list") + else if (index < 0) + Left(s"Index of the replacing element should be positive, but $index was passed") + else if (index >= list.size) + Left(s"Index of the replacing element should be lower than list size = ${list.length}, but $index was passed") + else + ARR(list.take(index.toInt) ++: element +: list.drop(index.toInt + 1), limited = true) + case xs => + notImplemented[Id, EVALUATED]("replaceByIndex(list: List[T], index: Int, element: T)", xs) + } + @VisibleForTesting private[v1] def genericListIndexOf( element: EVALUATED, @@ -2041,12 +2064,20 @@ object PureContext { v6Functions ) + private[this] val v8Ctx = + CTX[NoContext]( + v5Types, + v5Vars, + v6Functions :+ listReplaceByIndex + ) + def build(version: StdLibVersion, useNewPowPrecision: Boolean): CTX[NoContext] = version match { case V1 | V2 => v1V2Ctx(useNewPowPrecision) case V3 => v3Ctx(useNewPowPrecision) case V4 => v4Ctx(useNewPowPrecision) case V5 => v5Ctx(useNewPowPrecision) - case _ => v6Ctx + case V6 | V7 => v6Ctx + case V8 => v8Ctx } } diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/ListReplaceByIndexTest.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/ListReplaceByIndexTest.scala new file mode 100644 index 00000000000..97d60feb352 --- /dev/null +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/ListReplaceByIndexTest.scala @@ -0,0 +1,30 @@ +package com.wavesplatform.lang.evaluator + +import com.wavesplatform.lang.directives.values.{StdLibVersion, V8} +import com.wavesplatform.lang.v1.compiler.Terms.CONST_BOOLEAN +import com.wavesplatform.test.produce + +class ListReplaceByIndexTest extends EvaluatorSpec { + private implicit val version: StdLibVersion = V8 + + property("successful results") { + val bigList = (1 to 1000).map(_ => """"a"""").mkString("[", ",", "]") + eval( + s""" + | [0].replaceByIndex(0, 9) == [9] && + | [0, 1, 2, 3, 4].replaceByIndex(0, 9) == [9, 1, 2, 3, 4] && + | [0, 1, 2, 3, 4].replaceByIndex(2, 9) == [0, 1, 9, 3, 4] && + | [0, 1, 2, 3, 4].replaceByIndex(4, 9) == [0, 1, 2, 3, 9] && + | $bigList.replaceByIndex(0, "b")[0] == "b" && # can't compare the whole list due to + | $bigList.replaceByIndex(500, "b")[500] == "b" && # "Comparable value too heavy" error + | $bigList.replaceByIndex(999, "b")[999] == "b" + """.stripMargin + ) shouldBe Right(CONST_BOOLEAN(true)) + } + + property("errors") { + eval("[].replaceByIndex(0, 1)") should produce("Can't replace an element in empty list") + eval("[1, 2, 3].replaceByIndex(-1, 1)") should produce("Index of the replacing element should be positive, but -1 was passed") + eval("[1, 2, 3].replaceByIndex(3, 1)") should produce("Index of the replacing element should be lower than list size = 3, but 3 was passed") + } +} From 4a3bdaae2af7a1c06386177b27be7d9e736556bf Mon Sep 17 00:00:00 2001 From: Artyom Sayadyan Date: Mon, 25 Sep 2023 16:27:23 +0300 Subject: [PATCH 2/7] Set removeByIndex complexity to 5 --- lang/doc/v8/funcs/list-functions.hjson | 2 +- .../wavesplatform/lang/v1/evaluator/ctx/impl/PureContext.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lang/doc/v8/funcs/list-functions.hjson b/lang/doc/v8/funcs/list-functions.hjson index cc13858b18e..5e08dc5c83d 100644 --- a/lang/doc/v8/funcs/list-functions.hjson +++ b/lang/doc/v8/funcs/list-functions.hjson @@ -110,7 +110,7 @@ params: [ "List[T]", "Int" ] doc: "Removes the element at given index from the list and returns new list." paramsDoc: [ "The list.", "The index." ] - complexity: 7 + complexity: 5 } { name: "replaceByIndex" diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/PureContext.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/PureContext.scala index b7003e0aaf5..00bd34c6d69 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/PureContext.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/PureContext.scala @@ -1452,7 +1452,7 @@ object PureContext { lazy val listRemoveByIndex: BaseFunction[NoContext] = NativeFunction( "removeByIndex", - 7, + Map(V4 -> 7L, V5 -> 7L, V6 -> 7L, V7 -> 7L, V8 -> 5L), REMOVE_BY_INDEX_OF_LIST, PARAMETERIZEDLIST(TYPEPARAM('T')), ("list", PARAMETERIZEDLIST(TYPEPARAM('T'))), From 886609118594ef089979c9f3cf880c5e90fc3dbd Mon Sep 17 00:00:00 2001 From: Artyom Sayadyan Date: Tue, 26 Sep 2023 11:53:05 +0300 Subject: [PATCH 3/7] Added test and optimized --- lang/doc/v8/funcs/list-functions.hjson | 2 +- .../lang/v1/evaluator/ctx/impl/PureContext.scala | 4 ++-- .../wavesplatform/lang/evaluator/ListReplaceByIndexTest.scala | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lang/doc/v8/funcs/list-functions.hjson b/lang/doc/v8/funcs/list-functions.hjson index 5e08dc5c83d..bf8b44b85f5 100644 --- a/lang/doc/v8/funcs/list-functions.hjson +++ b/lang/doc/v8/funcs/list-functions.hjson @@ -117,7 +117,7 @@ params: [ "List[T]", "Int", "T" ] doc: "Replaces the element at given index in the list and returns new list." paramsDoc: [ "The list.", "The index.", "New element." ] - complexity: 5 + complexity: 4 } ] } diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/PureContext.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/PureContext.scala index 00bd34c6d69..49cec761300 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/PureContext.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/PureContext.scala @@ -1474,7 +1474,7 @@ object PureContext { private val listReplaceByIndex: BaseFunction[NoContext] = NativeFunction( "replaceByIndex", - 5, + 4, REPLACE_BY_INDEX_OF_LIST, PARAMETERIZEDLIST(TYPEPARAM('T')), ("list", PARAMETERIZEDLIST(TYPEPARAM('T'))), @@ -1489,7 +1489,7 @@ object PureContext { else if (index >= list.size) Left(s"Index of the replacing element should be lower than list size = ${list.length}, but $index was passed") else - ARR(list.take(index.toInt) ++: element +: list.drop(index.toInt + 1), limited = true) + ARR(list.updated(index.toInt, element), limited = true) case xs => notImplemented[Id, EVALUATED]("replaceByIndex(list: List[T], index: Int, element: T)", xs) } diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/ListReplaceByIndexTest.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/ListReplaceByIndexTest.scala index 97d60feb352..54939326479 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/ListReplaceByIndexTest.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/ListReplaceByIndexTest.scala @@ -26,5 +26,6 @@ class ListReplaceByIndexTest extends EvaluatorSpec { eval("[].replaceByIndex(0, 1)") should produce("Can't replace an element in empty list") eval("[1, 2, 3].replaceByIndex(-1, 1)") should produce("Index of the replacing element should be positive, but -1 was passed") eval("[1, 2, 3].replaceByIndex(3, 1)") should produce("Index of the replacing element should be lower than list size = 3, but 3 was passed") + eval("[1, 2, 3].replaceByIndex(2, true)") should produce("Compilation failed: [Can't match inferred types of T over Int, Boolean in 0-33]") } } From 163e1fed88306164a865d599efd7954c4eb003bc Mon Sep 17 00:00:00 2001 From: Artyom Sayadyan Date: Tue, 26 Sep 2023 14:00:18 +0300 Subject: [PATCH 4/7] Updated benchmarks using old evaluator --- .../lang/v1/ListReplaceByIndexBenchmark.scala | 6 +- .../lang/v1/ScriptEvaluatorBenchmark.scala | 83 +++++++------------ .../com/wavesplatform/lang/v1/package.scala | 2 +- 3 files changed, 34 insertions(+), 57 deletions(-) diff --git a/benchmark/src/test/scala/com/wavesplatform/lang/v1/ListReplaceByIndexBenchmark.scala b/benchmark/src/test/scala/com/wavesplatform/lang/v1/ListReplaceByIndexBenchmark.scala index 1e711bca3c0..78da3014f2c 100644 --- a/benchmark/src/test/scala/com/wavesplatform/lang/v1/ListReplaceByIndexBenchmark.scala +++ b/benchmark/src/test/scala/com/wavesplatform/lang/v1/ListReplaceByIndexBenchmark.scala @@ -22,15 +22,15 @@ import scala.concurrent.duration.{MICROSECONDS, SECONDS} class ListReplaceByIndexBenchmark { @Benchmark def listReplaceFirstByIndex(st: ListReplaceByIndexSt, bh: Blackhole): Unit = - bh.consume(eval(st.ctx, st.replaceFirst, V8)) + bh.consume(eval(st.ctx, st.replaceFirst)) @Benchmark def listReplaceMiddleByIndex(st: ListReplaceByIndexSt, bh: Blackhole): Unit = - bh.consume(eval(st.ctx, st.replaceMiddle, V8)) + bh.consume(eval(st.ctx, st.replaceMiddle)) @Benchmark def listReplaceLastByIndex(st: ListReplaceByIndexSt, bh: Blackhole): Unit = - bh.consume(eval(st.ctx, st.replaceLast, V8)) + bh.consume(eval(st.ctx, st.replaceLast)) } @State(Scope.Benchmark) diff --git a/benchmark/src/test/scala/com/wavesplatform/lang/v1/ScriptEvaluatorBenchmark.scala b/benchmark/src/test/scala/com/wavesplatform/lang/v1/ScriptEvaluatorBenchmark.scala index 1ebc949f183..e50e6711a1e 100644 --- a/benchmark/src/test/scala/com/wavesplatform/lang/v1/ScriptEvaluatorBenchmark.scala +++ b/benchmark/src/test/scala/com/wavesplatform/lang/v1/ScriptEvaluatorBenchmark.scala @@ -1,100 +1,96 @@ package com.wavesplatform.lang.v1 -import java.util.concurrent.TimeUnit - -import cats.Id -import cats.kernel.Monoid +import cats.implicits.catsSyntaxSemigroup import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.{Base58, EitherExt2} import com.wavesplatform.crypto.Curve25519 -import com.wavesplatform.lang.Global -import com.wavesplatform.lang.directives.values.{V1, V4} +import com.wavesplatform.lang.directives.values.V8 import com.wavesplatform.lang.v1.EnvironmentFunctionsBenchmark.{curve25519, randomBytes} import com.wavesplatform.lang.v1.FunctionHeader.Native import com.wavesplatform.lang.v1.ScriptEvaluatorBenchmark.* import com.wavesplatform.lang.v1.compiler.Terms.* -import com.wavesplatform.lang.v1.evaluator.Contextful.NoContext -import com.wavesplatform.lang.v1.evaluator.EvaluatorV1.* +import com.wavesplatform.lang.v1.evaluator.FunctionIds import com.wavesplatform.lang.v1.evaluator.FunctionIds.{FROMBASE58, SIGVERIFY, TOBASE58} -import com.wavesplatform.lang.v1.evaluator.ctx.EvaluationContext import com.wavesplatform.lang.v1.evaluator.ctx.impl.{CryptoContext, PureContext} -import com.wavesplatform.lang.v1.evaluator.{EvaluatorV1, FunctionIds} +import com.wavesplatform.lang.v1.traits.Environment +import com.wavesplatform.lang.{Common, Global} import org.openjdk.jmh.annotations.* import org.openjdk.jmh.infra.Blackhole +import java.util.concurrent.TimeUnit +import scala.concurrent.duration.SECONDS import scala.util.Random object ScriptEvaluatorBenchmark { - val version = V1 - val pureEvalContext: EvaluationContext[NoContext, Id] = - PureContext.build(V1, useNewPowPrecision = true).evaluationContext - val evaluatorV1: EvaluatorV1[Id, NoContext] = new EvaluatorV1[Id, NoContext]() + val context = + (PureContext.build(V8, useNewPowPrecision = true) |+| CryptoContext.build(Global, V8)) + .withEnvironment[Environment] + .evaluationContext(Common.emptyBlockchainEnvironment()) } @OutputTimeUnit(TimeUnit.MICROSECONDS) @BenchmarkMode(Array(Mode.AverageTime)) @Threads(1) @Fork(1) -@Warmup(iterations = 10) -@Measurement(iterations = 10) +@Warmup(iterations = 10, time = 1, timeUnit = SECONDS) +@Measurement(iterations = 10, time = 1, timeUnit = SECONDS) class ScriptEvaluatorBenchmark { @Benchmark - def bigSum(st: BigSum, bh: Blackhole): Unit = bh.consume(evaluatorV1.apply[EVALUATED](pureEvalContext, st.expr)) + def bigSum(st: BigSum, bh: Blackhole): Unit = bh.consume(eval(context, st.expr)) @Benchmark - def nestedBlocks(st: NestedBlocks, bh: Blackhole): Unit = bh.consume(evaluatorV1.apply[EVALUATED](st.context, st.expr)) + def nestedBlocks(st: NestedBlocks, bh: Blackhole): Unit = bh.consume(eval(context, st.expr)) @Benchmark - def signatures(st: Signatures, bh: Blackhole): Unit = bh.consume(evaluatorV1.apply[EVALUATED](st.context, st.expr)) + def signatures(st: Signatures, bh: Blackhole): Unit = bh.consume(eval(context, st.expr)) @Benchmark - def base58encode(st: Base58Perf, bh: Blackhole): Unit = bh.consume(evaluatorV1.apply[EVALUATED](st.context, st.encode)) + def base58encode(st: Base58Perf, bh: Blackhole): Unit = bh.consume(eval(context, st.encode)) @Benchmark - def base58decode(st: Base58Perf, bh: Blackhole): Unit = bh.consume(evaluatorV1.apply[EVALUATED](st.context, st.decode)) + def base58decode(st: Base58Perf, bh: Blackhole): Unit = bh.consume(eval(context, st.decode)) @Benchmark - def stringConcat(st: Concat, bh: Blackhole): Unit = bh.consume(evaluatorV1.apply[EVALUATED](st.context, st.strings)) + def stringConcat(st: Concat, bh: Blackhole): Unit = bh.consume(eval(context, st.strings)) @Benchmark - def bytesConcat(st: Concat, bh: Blackhole): Unit = bh.consume(evaluatorV1.apply[EVALUATED](st.context, st.bytes)) + def bytesConcat(st: Concat, bh: Blackhole): Unit = bh.consume(eval(context, st.bytes)) @Benchmark def listMedianRandomElements(st: Median, bh: Blackhole): Unit = - bh.consume(evaluatorV1.apply[EVALUATED](st.context, st.randomElements(Random.nextInt(10000)))) + bh.consume(eval(context, st.randomElements(Random.nextInt(10000)))) @Benchmark def listMedianSortedElements(st: Median, bh: Blackhole): Unit = - bh.consume(evaluatorV1.apply[EVALUATED](st.context, st.sortedElements)) + bh.consume(eval(context, st.sortedElements)) @Benchmark def listMedianSortedReverseElements(st: Median, bh: Blackhole): Unit = - bh.consume(evaluatorV1.apply[EVALUATED](st.context, st.sortedReverseElements)) + bh.consume(eval(context, st.sortedReverseElements)) @Benchmark def listMedianEqualElements(st: Median, bh: Blackhole): Unit = - bh.consume(evaluatorV1.apply[EVALUATED](st.context, st.equalElements)) + bh.consume(eval(context, st.equalElements)) @Benchmark def listRemoveFirstByIndex(st: ListRemoveByIndex, bh: Blackhole): Unit = - bh.consume(evaluatorV1.apply[EVALUATED](st.context, st.removeFirst)) + bh.consume(eval(context, st.removeFirst)) @Benchmark def listRemoveMiddleByIndex(st: ListRemoveByIndex, bh: Blackhole): Unit = - bh.consume(evaluatorV1.apply[EVALUATED](st.context, st.removeMiddle)) + bh.consume(eval(context, st.removeMiddle)) @Benchmark def listRemoveLastByIndex(st: ListRemoveByIndex, bh: Blackhole): Unit = - bh.consume(evaluatorV1.apply[EVALUATED](st.context, st.removeLast)) + bh.consume(eval(context, st.removeLast)) @Benchmark - def sigVerify32Kb(st: SigVerify32Kb, bh: Blackhole): Unit = bh.consume(evaluatorV1.apply[EVALUATED](st.context, st.expr)) + def sigVerify32Kb(st: SigVerify32Kb, bh: Blackhole): Unit = + bh.consume(eval(context, st.expr)) } @State(Scope.Benchmark) class NestedBlocks { - val context: EvaluationContext[NoContext, Id] = pureEvalContext - val expr: EXPR = { val blockCount = 300 val cond = FUNCTION_CALL(PureContext.eq, List(REF(s"v$blockCount"), CONST_LONG(0))) @@ -107,9 +103,6 @@ class NestedBlocks { @State(Scope.Benchmark) class Base58Perf { - val context: EvaluationContext[NoContext, Id] = - Monoid.combine(pureEvalContext, CryptoContext.build(Global, version).evaluationContext) - val encode: EXPR = { val base58Count = 120 val sum = (1 to base58Count).foldRight[EXPR](CONST_LONG(0)) { case (i, e) => @@ -150,9 +143,6 @@ class Base58Perf { @State(Scope.Benchmark) class Signatures { - val context: EvaluationContext[NoContext, Id] = - Monoid.combine(pureEvalContext, CryptoContext.build(Global, version).evaluationContext) - val expr: EXPR = { val sigCount = 20 val sum = (1 to sigCount).foldRight[EXPR](CONST_LONG(0)) { case (i, e) => @@ -191,8 +181,6 @@ class Signatures { @State(Scope.Benchmark) class Concat { - val context: EvaluationContext[NoContext, Id] = pureEvalContext - private val Steps = 180 private def expr(init: EXPR, func: FunctionHeader, operand: EXPR, count: Int) = @@ -218,8 +206,6 @@ class Concat { @State(Scope.Benchmark) class Median { - val context: EvaluationContext[NoContext, Id] = PureContext.build(V4, useNewPowPrecision = true).evaluationContext - val randomElements: Array[EXPR] = (1 to 10000).map { _ => val listOfLong = (1 to 1000).map(_ => CONST_LONG(Random.nextLong())) @@ -260,9 +246,6 @@ class Median { @State(Scope.Benchmark) class SigVerify32Kb { - val context: EvaluationContext[NoContext, Id] = - Monoid.combine(PureContext.build(V4, useNewPowPrecision = true).evaluationContext, CryptoContext.build(Global, V4).evaluationContext) - val expr: EXPR = { val (privateKey, publicKey) = curve25519.generateKeypair val message = randomBytes(32 * 1024 - 1) @@ -281,12 +264,6 @@ class SigVerify32Kb { @State(Scope.Benchmark) class ListRemoveByIndex { - val context: EvaluationContext[NoContext, Id] = - Monoid.combine( - PureContext.build(V4, useNewPowPrecision = true).evaluationContext, - CryptoContext.build(Global, V4).evaluationContext - ) - val list: ARR = ARR(Vector.fill(1000)(CONST_LONG(Long.MaxValue)), limited = true).explicitGet() val removeFirst: EXPR = @@ -312,7 +289,7 @@ class ListRemoveByIndex { Native(FunctionIds.REMOVE_BY_INDEX_OF_LIST), List( list, - CONST_LONG(1000) + CONST_LONG(999) ) ) } diff --git a/benchmark/src/test/scala/com/wavesplatform/lang/v1/package.scala b/benchmark/src/test/scala/com/wavesplatform/lang/v1/package.scala index 076708514ad..95367b95d8d 100644 --- a/benchmark/src/test/scala/com/wavesplatform/lang/v1/package.scala +++ b/benchmark/src/test/scala/com/wavesplatform/lang/v1/package.scala @@ -29,7 +29,7 @@ package object v1 { def eval( ctx: EvaluationContext[Environment, Id], expr: EXPR, - stdLibVersion: StdLibVersion + stdLibVersion: StdLibVersion = StdLibVersion.VersionDic.all.max ): (Log[Id], Int, Either[ExecutionError, Terms.EVALUATED]) = EvaluatorV2.applyCompleted(ctx, expr, LogExtraInfo(), stdLibVersion, newMode = true, correctFunctionCallScope = true, enableExecutionLog = false) } From 792890b4a4357c126566d5ef7c640c0863548cb4 Mon Sep 17 00:00:00 2001 From: Artyom Sayadyan Date: Tue, 26 Sep 2023 14:01:10 +0300 Subject: [PATCH 5/7] Set removeByIndex complexity to 4 --- lang/doc/v8/funcs/list-functions.hjson | 2 +- .../wavesplatform/lang/v1/evaluator/ctx/impl/PureContext.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lang/doc/v8/funcs/list-functions.hjson b/lang/doc/v8/funcs/list-functions.hjson index bf8b44b85f5..d19439d4d31 100644 --- a/lang/doc/v8/funcs/list-functions.hjson +++ b/lang/doc/v8/funcs/list-functions.hjson @@ -110,7 +110,7 @@ params: [ "List[T]", "Int" ] doc: "Removes the element at given index from the list and returns new list." paramsDoc: [ "The list.", "The index." ] - complexity: 5 + complexity: 4 } { name: "replaceByIndex" diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/PureContext.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/PureContext.scala index 49cec761300..6d33727b6e5 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/PureContext.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/PureContext.scala @@ -1452,7 +1452,7 @@ object PureContext { lazy val listRemoveByIndex: BaseFunction[NoContext] = NativeFunction( "removeByIndex", - Map(V4 -> 7L, V5 -> 7L, V6 -> 7L, V7 -> 7L, V8 -> 5L), + Map(V4 -> 7L, V5 -> 7L, V6 -> 7L, V7 -> 7L, V8 -> 4L), REMOVE_BY_INDEX_OF_LIST, PARAMETERIZEDLIST(TYPEPARAM('T')), ("list", PARAMETERIZEDLIST(TYPEPARAM('T'))), From 8bea26512496c7c38b505608f4f473a854439147 Mon Sep 17 00:00:00 2001 From: Artyom Sayadyan Date: Tue, 26 Sep 2023 14:05:36 +0300 Subject: [PATCH 6/7] Better code --- .../lang/v1/ListReplaceByIndexBenchmark.scala | 8 ++++++-- .../wavesplatform/lang/v1/ScriptEvaluatorBenchmark.scala | 5 +++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/benchmark/src/test/scala/com/wavesplatform/lang/v1/ListReplaceByIndexBenchmark.scala b/benchmark/src/test/scala/com/wavesplatform/lang/v1/ListReplaceByIndexBenchmark.scala index 78da3014f2c..c64b7130f29 100644 --- a/benchmark/src/test/scala/com/wavesplatform/lang/v1/ListReplaceByIndexBenchmark.scala +++ b/benchmark/src/test/scala/com/wavesplatform/lang/v1/ListReplaceByIndexBenchmark.scala @@ -2,7 +2,7 @@ package com.wavesplatform.lang.v1 import com.wavesplatform.common.utils.EitherExt2 import com.wavesplatform.lang.Common -import com.wavesplatform.lang.directives.values.V8 +import com.wavesplatform.lang.directives.values.StdLibVersion import com.wavesplatform.lang.v1.FunctionHeader.Native import com.wavesplatform.lang.v1.compiler.Terms.* import com.wavesplatform.lang.v1.evaluator.FunctionIds.REPLACE_BY_INDEX_OF_LIST @@ -35,7 +35,11 @@ class ListReplaceByIndexBenchmark { @State(Scope.Benchmark) class ListReplaceByIndexSt { - val ctx = PureContext.build(V8, useNewPowPrecision = true).withEnvironment[Environment].evaluationContext(Common.emptyBlockchainEnvironment()) + val ctx = + PureContext + .build(StdLibVersion.VersionDic.all.max, useNewPowPrecision = true) + .withEnvironment[Environment] + .evaluationContext(Common.emptyBlockchainEnvironment()) val list = ARR(Vector.fill(1000)(CONST_LONG(Long.MaxValue)), limited = true).explicitGet() diff --git a/benchmark/src/test/scala/com/wavesplatform/lang/v1/ScriptEvaluatorBenchmark.scala b/benchmark/src/test/scala/com/wavesplatform/lang/v1/ScriptEvaluatorBenchmark.scala index e50e6711a1e..b820b97c285 100644 --- a/benchmark/src/test/scala/com/wavesplatform/lang/v1/ScriptEvaluatorBenchmark.scala +++ b/benchmark/src/test/scala/com/wavesplatform/lang/v1/ScriptEvaluatorBenchmark.scala @@ -4,7 +4,7 @@ import cats.implicits.catsSyntaxSemigroup import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.{Base58, EitherExt2} import com.wavesplatform.crypto.Curve25519 -import com.wavesplatform.lang.directives.values.V8 +import com.wavesplatform.lang.directives.values.StdLibVersion import com.wavesplatform.lang.v1.EnvironmentFunctionsBenchmark.{curve25519, randomBytes} import com.wavesplatform.lang.v1.FunctionHeader.Native import com.wavesplatform.lang.v1.ScriptEvaluatorBenchmark.* @@ -22,8 +22,9 @@ import scala.concurrent.duration.SECONDS import scala.util.Random object ScriptEvaluatorBenchmark { + val lastVersion = StdLibVersion.VersionDic.all.max val context = - (PureContext.build(V8, useNewPowPrecision = true) |+| CryptoContext.build(Global, V8)) + (PureContext.build(lastVersion, useNewPowPrecision = true) |+| CryptoContext.build(Global, lastVersion)) .withEnvironment[Environment] .evaluationContext(Common.emptyBlockchainEnvironment()) } From 113be98826ee555419d33be6a64e43c3a4019665 Mon Sep 17 00:00:00 2001 From: Artyom Sayadyan Date: Thu, 28 Sep 2023 11:45:32 +0300 Subject: [PATCH 7/7] JS test --- .../list/ReplaceByIndex.scala | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 lang/tests-js/src/test/scala/com/wavesplatform/test/builtInFunctions/list/ReplaceByIndex.scala diff --git a/lang/tests-js/src/test/scala/com/wavesplatform/test/builtInFunctions/list/ReplaceByIndex.scala b/lang/tests-js/src/test/scala/com/wavesplatform/test/builtInFunctions/list/ReplaceByIndex.scala new file mode 100644 index 00000000000..6eb499dac6b --- /dev/null +++ b/lang/tests-js/src/test/scala/com/wavesplatform/test/builtInFunctions/list/ReplaceByIndex.scala @@ -0,0 +1,51 @@ +package com.wavesplatform.test.builtInFunctions.list + +import com.wavesplatform.JsTestBase +import com.wavesplatform.lang.directives.DirectiveDictionary +import com.wavesplatform.lang.directives.values.{StdLibVersion, V8} +import testHelpers.GeneratorContractsForBuiltInFunctions +import testHelpers.RandomDataGenerator.{randomAliasDataArrayElement, randomInt, randomIssuesArrayElement, randomStringArrayElement} +import testHelpers.TestDataConstantsAndMethods.{nonMatchingTypes, stringList} +import utest.{Tests, test} + +object ReplaceByIndex extends JsTestBase { + private val replaceByIndex = "replaceByIndex(bar, 1, foo)" + private val replaceByIndexArgBeforeFunc = "bar.replaceByIndex(1, foo)" + private val invalidReplaceByIndex = "replaceByIndex(1, foo)" + private val invalidReplaceByIndexArgBeforeFunc = "foo.replaceByIndex(bar, 1, foo)" + private val invalidErrorReplaceByIndex = testData.invalidFunctionError("replaceByIndex", numberOfArguments = 3) + + val tests: Tests = Tests { + test("replaceByIndex functions compiles with a list") { + for (version <- DirectiveDictionary[StdLibVersion].all.filter(_ >= V8)) { + val precondition = new GeneratorContractsForBuiltInFunctions("", version) + for ( + (data, list, function) <- Seq( + (randomStringArrayElement, stringList, replaceByIndex), + (randomStringArrayElement, stringList, replaceByIndexArgBeforeFunc) + ) + ) { + val script = precondition.simpleRideCode(data, list, function) + assertCompileSuccessDApp(script, version) + } + } + } + + test("Compilation errors ReplaceByIndex functions") { + for (version <- DirectiveDictionary[StdLibVersion].all.filter(_ >= V8)) { + val precondition = new GeneratorContractsForBuiltInFunctions("", version) + for ( + (data, list, function, error) <- Seq( + (randomInt.toString, randomAliasDataArrayElement, replaceByIndex, nonMatchingTypes("List[T]")), + (randomInt.toString, randomIssuesArrayElement, replaceByIndexArgBeforeFunc, nonMatchingTypes("List[T]")), + (randomInt.toString, stringList, invalidReplaceByIndex, invalidErrorReplaceByIndex), + (randomInt.toString, stringList, invalidReplaceByIndexArgBeforeFunc, invalidErrorReplaceByIndex) + ) + ) { + val script = precondition.simpleRideCode(data, list, function) + assertCompileErrorDApp(script, version, error) + } + } + } + } +}