From 3ca899f619acdbce2bb2420b498982b269474908 Mon Sep 17 00:00:00 2001 From: Artyom Sayadyan Date: Mon, 18 Oct 2021 15:57:43 +0300 Subject: [PATCH] SC-792 Rebenchmark, add function families and optimize (#3534) --- .../lang/v1/AddressToStringBenchmark.scala | 46 +++ .../lang/v1/BigIntToStringBenchmark.scala | 37 +++ .../lang/v1/FractionBigIntBenchmark.scala | 83 ++++++ .../lang/v1/FractionIntBenchmark.scala | 36 +-- .../lang/v1/MakeStringBenchmark.scala | 84 +++++- .../lang/v1/PowBigIntBenchmark.scala | 81 ++++++ .../lang/v1/PowIntBenchmark.scala | 73 +++++ .../lang/v1/PureFunctionsRebenchmark.scala | 63 +--- .../lang/v1/SqrtCbrtBigIntBenchmark.scala | 49 ++++ .../lang/v1/SqrtIntBenchmark.scala | 39 +++ .../lang/v1/StringSplitBenchmark.scala | 77 +++++ .../lang/v1/SumStringBenchmark.scala | 78 +++++ .../com/wavesplatform/lang/v1/package.scala | 21 ++ lang/doc/v3/funcs/math-functions.hjson | 4 +- lang/doc/v4/funcs/math-functions.hjson | 4 +- lang/doc/v5/funcs/math-functions.hjson | 8 +- lang/doc/v6/funcs/converting-functions.hjson | 4 +- lang/doc/v6/funcs/list-functions.hjson | 16 +- lang/doc/v6/funcs/math-functions.hjson | 44 ++- lang/doc/v6/funcs/operators.hjson | 2 +- lang/doc/v6/funcs/string-functions.hjson | 23 +- .../scala/com/wavesplatform/lang/Global.scala | 2 +- .../scala/com/wavesplatform/lang/Global.scala | 30 +- .../wavesplatform/lang/v1/BaseGlobal.scala | 2 +- .../lang/v1/compiler/Terms.scala | 13 +- .../lang/v1/evaluator/FunctionIds.scala | 75 ++--- .../v1/evaluator/ctx/impl/PureContext.scala | 179 +++++++++--- .../evaluator/ctx/impl/waves/Functions.scala | 2 +- .../wavesplatform/lang/IntegrationTest.scala | 7 +- .../lang/evaluator/BigIntTest.scala | 4 +- .../evaluator/FractionIntRoundsTest.scala | 23 +- .../lang/evaluator/MakeStringTest.scala | 58 ++++ .../lang/evaluator/MathFunctionsTest.scala | 6 +- .../lang/evaluator/SplitFunctionTest.scala | 273 +++++++++--------- .../lang/evaluator/StringFunctionsTest.scala | 16 - .../sync/smartcontract/BigStringSuite.scala | 2 +- project/Dependencies.scala | 5 +- project/plugins.sbt | 2 +- 38 files changed, 1205 insertions(+), 366 deletions(-) create mode 100644 benchmark/src/test/scala/com/wavesplatform/lang/v1/AddressToStringBenchmark.scala create mode 100644 benchmark/src/test/scala/com/wavesplatform/lang/v1/BigIntToStringBenchmark.scala create mode 100644 benchmark/src/test/scala/com/wavesplatform/lang/v1/FractionBigIntBenchmark.scala create mode 100644 benchmark/src/test/scala/com/wavesplatform/lang/v1/PowBigIntBenchmark.scala create mode 100644 benchmark/src/test/scala/com/wavesplatform/lang/v1/PowIntBenchmark.scala create mode 100644 benchmark/src/test/scala/com/wavesplatform/lang/v1/SqrtCbrtBigIntBenchmark.scala create mode 100644 benchmark/src/test/scala/com/wavesplatform/lang/v1/SqrtIntBenchmark.scala create mode 100644 benchmark/src/test/scala/com/wavesplatform/lang/v1/StringSplitBenchmark.scala create mode 100644 benchmark/src/test/scala/com/wavesplatform/lang/v1/SumStringBenchmark.scala create mode 100644 benchmark/src/test/scala/com/wavesplatform/lang/v1/package.scala create mode 100644 lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/MakeStringTest.scala diff --git a/benchmark/src/test/scala/com/wavesplatform/lang/v1/AddressToStringBenchmark.scala b/benchmark/src/test/scala/com/wavesplatform/lang/v1/AddressToStringBenchmark.scala new file mode 100644 index 00000000000..f16909d47b8 --- /dev/null +++ b/benchmark/src/test/scala/com/wavesplatform/lang/v1/AddressToStringBenchmark.scala @@ -0,0 +1,46 @@ +package com.wavesplatform.lang.v1 + +import java.util.concurrent.TimeUnit + +import com.wavesplatform.account.{Address, PublicKey} +import com.wavesplatform.common.state.ByteStr +import com.wavesplatform.crypto.Curve25519 +import com.wavesplatform.lang.v1.FunctionHeader.Native +import com.wavesplatform.lang.v1.compiler.Terms.{CONST_BYTESTR, CaseObj, FUNCTION_CALL} +import com.wavesplatform.lang.v1.evaluator.FunctionIds +import com.wavesplatform.lang.v1.evaluator.ctx.impl.waves.Types +import org.openjdk.jmh.annotations._ +import com.wavesplatform.common.utils._ +import com.wavesplatform.lang.v1.AddressToStringBenchmark.AddressToString +import org.openjdk.jmh.infra.Blackhole + +import scala.util.Random +import com.wavesplatform.lang.v1.PureFunctionsRebenchmark.evalV5 + +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@BenchmarkMode(Array(Mode.AverageTime)) +@Threads(1) +@Fork(1) +@Warmup(iterations = 10, time = 1) +@Measurement(iterations = 10, time = 1) +class AddressToStringBenchmark { + @Benchmark + def addressToString(bh: Blackhole, st: AddressToString): Unit = + bh.consume(evalV5(st.expr)) +} + +object AddressToStringBenchmark { + @State(Scope.Benchmark) + class AddressToString { + val publicKey = new Array[Byte](Curve25519.KeyLength) + Random.nextBytes(publicKey) + + val address = Address.fromPublicKey(PublicKey(publicKey)).bytes + + val expr = + FUNCTION_CALL( + Native(FunctionIds.ADDRESSTOSTRING), + List(CaseObj(Types.addressType, Map("bytes" -> CONST_BYTESTR(ByteStr(address)).explicitGet()))) + ) + } +} \ No newline at end of file diff --git a/benchmark/src/test/scala/com/wavesplatform/lang/v1/BigIntToStringBenchmark.scala b/benchmark/src/test/scala/com/wavesplatform/lang/v1/BigIntToStringBenchmark.scala new file mode 100644 index 00000000000..a87564cc73c --- /dev/null +++ b/benchmark/src/test/scala/com/wavesplatform/lang/v1/BigIntToStringBenchmark.scala @@ -0,0 +1,37 @@ +package com.wavesplatform.lang.v1 + +import java.util.concurrent.TimeUnit + +import com.wavesplatform.lang.Common +import com.wavesplatform.lang.directives.DirectiveSet +import com.wavesplatform.lang.directives.values.{Account, Expression, V5} +import com.wavesplatform.lang.utils.lazyContexts +import com.wavesplatform.lang.v1.FunctionHeader.Native +import com.wavesplatform.lang.v1.compiler.Terms.{CONST_BIGINT, FUNCTION_CALL} +import com.wavesplatform.lang.v1.evaluator.EvaluatorV2 +import com.wavesplatform.lang.v1.evaluator.FunctionIds.BIGINT_TO_STRING +import com.wavesplatform.lang.v1.evaluator.ctx.impl.PureContext +import org.openjdk.jmh.annotations.{State, _} +import org.openjdk.jmh.infra.Blackhole + +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@BenchmarkMode(Array(Mode.AverageTime)) +@Threads(1) +@Fork(1) +@Warmup(iterations = 10, time = 1) +@Measurement(iterations = 10, time = 1) +class BigIntToStringBenchmark { + @Benchmark + def toString(bh: Blackhole, s: BigIntToStringSt): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr, V5)) +} + +@State(Scope.Benchmark) +class BigIntToStringSt { + val ds = DirectiveSet(V5, Account, Expression).fold(null, identity) + val ctx = lazyContexts(ds).value().evaluationContext(Common.emptyBlockchainEnvironment()) + + val expr = FUNCTION_CALL( + Native(BIGINT_TO_STRING), + List(CONST_BIGINT(PureContext.BigIntMin)) + ) +} diff --git a/benchmark/src/test/scala/com/wavesplatform/lang/v1/FractionBigIntBenchmark.scala b/benchmark/src/test/scala/com/wavesplatform/lang/v1/FractionBigIntBenchmark.scala new file mode 100644 index 00000000000..e1889242539 --- /dev/null +++ b/benchmark/src/test/scala/com/wavesplatform/lang/v1/FractionBigIntBenchmark.scala @@ -0,0 +1,83 @@ +package com.wavesplatform.lang.v1 + +import java.util.concurrent.TimeUnit + +import com.wavesplatform.lang.Common +import com.wavesplatform.lang.directives.DirectiveSet +import com.wavesplatform.lang.directives.values.{Account, Expression, V5} +import com.wavesplatform.lang.utils.lazyContexts +import com.wavesplatform.lang.v1.FunctionHeader.Native +import com.wavesplatform.lang.v1.compiler.Terms.{CONST_BIGINT, FUNCTION_CALL} +import com.wavesplatform.lang.v1.evaluator.EvaluatorV2 +import com.wavesplatform.lang.v1.evaluator.FunctionIds.{FRACTION_BIGINT, FRACTION_BIGINT_ROUNDS} +import com.wavesplatform.lang.v1.evaluator.ctx.impl.{PureContext, Rounding} +import org.openjdk.jmh.annotations.{State, _} +import org.openjdk.jmh.infra.Blackhole + +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@BenchmarkMode(Array(Mode.AverageTime)) +@Threads(1) +@Fork(1) +@Warmup(iterations = 10, time = 1) +@Measurement(iterations = 10, time = 1) +class FractionBigIntBenchmark { + @Benchmark + def fraction1(bh: Blackhole, s: FractionBigIntSt): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr1, V5)) + + @Benchmark + def fraction2(bh: Blackhole, s: FractionBigIntSt): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr2, V5)) + + @Benchmark + def fraction3(bh: Blackhole, s: FractionBigIntSt): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr3, V5)) + + @Benchmark + def fraction1Round(bh: Blackhole, s: FractionBigIntSt): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr1Round, V5)) + + @Benchmark + def fraction2Round(bh: Blackhole, s: FractionBigIntSt): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr2Round, V5)) + + @Benchmark + def fraction3Round(bh: Blackhole, s: FractionBigIntSt): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr3Round, V5)) +} + +@State(Scope.Benchmark) +class FractionBigIntSt { + val ds = DirectiveSet(V5, Account, Expression).fold(null, identity) + val ctx = lazyContexts(ds).value().evaluationContext(Common.emptyBlockchainEnvironment()) + + val max = CONST_BIGINT(PureContext.BigIntMax) + val halfMax = CONST_BIGINT(PureContext.BigIntMax / 2) + val min = CONST_BIGINT(PureContext.BigIntMin) + val maxSqrt = CONST_BIGINT(BigInt("57896044618658097711785492504343953926634992332820282019728792003956564819968")) + val three = CONST_BIGINT(3) + + val expr1 = FUNCTION_CALL( + Native(FRACTION_BIGINT), + List(halfMax, three, three) + ) + + val expr2 = FUNCTION_CALL( + Native(FRACTION_BIGINT), + List(max, min, min) + ) + + val expr3 = FUNCTION_CALL( + Native(FRACTION_BIGINT), + List(maxSqrt, maxSqrt, maxSqrt) + ) + + val expr1Round = FUNCTION_CALL( + Native(FRACTION_BIGINT_ROUNDS), + List(halfMax, three, three, Rounding.HalfEven.value) + ) + + val expr2Round = FUNCTION_CALL( + Native(FRACTION_BIGINT_ROUNDS), + List(max, min, min, Rounding.HalfEven.value) + ) + + val expr3Round = FUNCTION_CALL( + Native(FRACTION_BIGINT_ROUNDS), + List(maxSqrt, maxSqrt, maxSqrt, Rounding.HalfEven.value) + ) +} diff --git a/benchmark/src/test/scala/com/wavesplatform/lang/v1/FractionIntBenchmark.scala b/benchmark/src/test/scala/com/wavesplatform/lang/v1/FractionIntBenchmark.scala index 88b10924e69..8a05b957856 100644 --- a/benchmark/src/test/scala/com/wavesplatform/lang/v1/FractionIntBenchmark.scala +++ b/benchmark/src/test/scala/com/wavesplatform/lang/v1/FractionIntBenchmark.scala @@ -2,7 +2,7 @@ package com.wavesplatform.lang.v1 import com.wavesplatform.lang.Common import com.wavesplatform.lang.directives.DirectiveSet -import com.wavesplatform.lang.directives.values.{Account, Expression, V5} +import com.wavesplatform.lang.directives.values.{Account, Expression, V6} import com.wavesplatform.lang.utils.lazyContexts import com.wavesplatform.lang.v1.compiler.Terms.EXPR import com.wavesplatform.lang.v1.compiler.TestCompiler @@ -16,39 +16,41 @@ import java.util.concurrent.TimeUnit @BenchmarkMode(Array(Mode.AverageTime)) @Threads(1) @Fork(1) -@Warmup(iterations = 30) -@Measurement(iterations = 30) +@Warmup(iterations = 10, time = 1) +@Measurement(iterations = 10, time = 1) class FractionIntBenchmark { @Benchmark - def fraction1(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr1, V5)) + def fraction1(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr1, V6)) @Benchmark - def fraction2(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr2, V5)) + def fraction2(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr2, V6)) @Benchmark - def fraction3(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr3, V5)) + def fraction3(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr3, V6)) @Benchmark - def fraction1Round(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr1Round, V5)) + def fraction1Round(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr1Round, V6)) @Benchmark - def fraction2Round(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr2Round, V5)) + def fraction2Round(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr2Round, V6)) @Benchmark - def fraction3Round(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr3Round, V5)) + def fraction3Round(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr3Round, V6)) } @State(Scope.Benchmark) class St { - val ds = DirectiveSet(V5, Account, Expression).fold(null, identity) + val ds = DirectiveSet(V6, Account, Expression).fold(null, identity) val ctx = lazyContexts(ds).value().evaluationContext(Common.emptyBlockchainEnvironment()) - val max = Long.MaxValue - val expr1 = TestCompiler(V5).compileExpression(s"fraction($max, -$max, 1)").expr.asInstanceOf[EXPR] - val expr2 = TestCompiler(V5).compileExpression(s"fraction($max, -$max, -$max)").expr.asInstanceOf[EXPR] - val expr3 = TestCompiler(V5).compileExpression(s"fraction($max, $max, ${-max / 2 + 1})").expr.asInstanceOf[EXPR] + val max = Long.MaxValue + val maxSqrt = 3037000499L - val expr1Round = TestCompiler(V5).compileExpression(s"fraction($max, -$max, 1, HALFEVEN)").expr.asInstanceOf[EXPR] - val expr2Round = TestCompiler(V5).compileExpression(s"fraction($max, -$max, -$max, HALFEVEN)").expr.asInstanceOf[EXPR] - val expr3Round = TestCompiler(V5).compileExpression(s"fraction($max, $max, ${-max / 2 + 1}, HALFEVEN)").expr.asInstanceOf[EXPR] + val expr1 = TestCompiler(V6).compileExpression(s"fraction($max / 2, 3, 3)").expr.asInstanceOf[EXPR] + val expr2 = TestCompiler(V6).compileExpression(s"fraction($max, -$max, -$max)").expr.asInstanceOf[EXPR] + val expr3 = TestCompiler(V6).compileExpression(s"fraction($maxSqrt, $maxSqrt, $maxSqrt)").expr.asInstanceOf[EXPR] + + val expr1Round = TestCompiler(V6).compileExpression(s"fraction($max / 2, 3, 3, HALFEVEN)").expr.asInstanceOf[EXPR] + val expr2Round = TestCompiler(V6).compileExpression(s"fraction($max, -$max, -$max, HALFEVEN)").expr.asInstanceOf[EXPR] + val expr3Round = TestCompiler(V6).compileExpression(s"fraction($maxSqrt, $maxSqrt, $maxSqrt, HALFEVEN)").expr.asInstanceOf[EXPR] } diff --git a/benchmark/src/test/scala/com/wavesplatform/lang/v1/MakeStringBenchmark.scala b/benchmark/src/test/scala/com/wavesplatform/lang/v1/MakeStringBenchmark.scala index 77ecdf4366b..a798067c01d 100644 --- a/benchmark/src/test/scala/com/wavesplatform/lang/v1/MakeStringBenchmark.scala +++ b/benchmark/src/test/scala/com/wavesplatform/lang/v1/MakeStringBenchmark.scala @@ -2,26 +2,92 @@ package com.wavesplatform.lang.v1 import java.util.concurrent.TimeUnit -import com.wavesplatform.lang.v1.MakeStringBenchmark.MakeStringSt +import com.wavesplatform.common.utils._ +import com.wavesplatform.lang.v1.FunctionHeader.Native +import com.wavesplatform.lang.v1.MakeStringBenchmark._ +import com.wavesplatform.lang.v1.PureFunctionsRebenchmark.evalV5 +import com.wavesplatform.lang.v1.compiler.Terms.{ARR, CONST_STRING, FUNCTION_CALL} +import com.wavesplatform.lang.v1.evaluator.FunctionIds import org.openjdk.jmh.annotations._ import org.openjdk.jmh.infra.Blackhole +import scala.util.Random + @OutputTimeUnit(TimeUnit.MICROSECONDS) @BenchmarkMode(Array(Mode.AverageTime)) @Threads(1) @Fork(1) -@Warmup(iterations = 30) -@Measurement(iterations = 30) +@Warmup(iterations = 10, time = 1) +@Measurement(iterations = 10, time = 1) class MakeStringBenchmark { + @Benchmark + def makeStringSep31x1000(st: MakeStringSep31x1000, bh: Blackhole): Unit = + bh.consume(evalV5(st.expr)) + + @Benchmark + def makeString31x1000(st: MakeString31x1000, bh: Blackhole): Unit = + bh.consume(evalV5(st.expr)) + + @Benchmark + def makeString31x100(st: MakeString31x100, bh: Blackhole): Unit = + bh.consume(evalV5(st.expr)) + + @Benchmark + def makeString31x10(st: MakeString31x10, bh: Blackhole): Unit = + bh.consume(evalV5(st.expr)) @Benchmark - def makeString(st: MakeStringSt, bh: Blackhole): Unit = - bh.consume(st.stringList.mkString(",")) + def makeString310x100(st: MakeString310x100, bh: Blackhole): Unit = + bh.consume(evalV5(st.expr)) + + @Benchmark + def makeString3100x10(st: MakeString3100x10, bh: Blackhole): Unit = + bh.consume(evalV5(st.expr)) + + @Benchmark + def makeString1x70(st: MakeString1x70, bh: Blackhole): Unit = + bh.consume(evalV5(st.expr)) + + @Benchmark + def makeString7x70(st: MakeString7x70, bh: Blackhole): Unit = + bh.consume(evalV5(st.expr)) } object MakeStringBenchmark { - @State(Scope.Benchmark) - class MakeStringSt { - val stringList = List.fill(1000)("a" * 33) + abstract class MakeString(listSize: Int, stringSize: Int = 1, separatorSize: Int = 1) { + val string = Random.nextPrintableChar().toString * stringSize + val separator = Random.nextPrintableChar().toString * separatorSize + val expr = + FUNCTION_CALL( + Native(FunctionIds.MAKESTRING), + List( + ARR(Vector.fill(listSize)(CONST_STRING(string).explicitGet()), limited = true).explicitGet(), + CONST_STRING(separator).explicitGet() + ) + ) } -} \ No newline at end of file + + @State(Scope.Benchmark) + class MakeStringSep31x1000 extends MakeString(listSize = 1000, separatorSize = 31) + + @State(Scope.Benchmark) + class MakeString31x1000 extends MakeString(listSize = 1000, stringSize = 31) + + @State(Scope.Benchmark) + class MakeString31x100 extends MakeString(listSize = 100, stringSize = 31) + + @State(Scope.Benchmark) + class MakeString31x10 extends MakeString(listSize = 100, stringSize = 31) + + @State(Scope.Benchmark) + class MakeString310x100 extends MakeString(listSize = 100, stringSize = 310) + + @State(Scope.Benchmark) + class MakeString3100x10 extends MakeString(listSize = 10, stringSize = 3100) + + @State(Scope.Benchmark) + class MakeString1x70 extends MakeString(listSize = 70, stringSize = 1) + + @State(Scope.Benchmark) + class MakeString7x70 extends MakeString(listSize = 70, stringSize = 7) +} diff --git a/benchmark/src/test/scala/com/wavesplatform/lang/v1/PowBigIntBenchmark.scala b/benchmark/src/test/scala/com/wavesplatform/lang/v1/PowBigIntBenchmark.scala new file mode 100644 index 00000000000..614eac8d90d --- /dev/null +++ b/benchmark/src/test/scala/com/wavesplatform/lang/v1/PowBigIntBenchmark.scala @@ -0,0 +1,81 @@ +package com.wavesplatform.lang.v1 + +import java.util.concurrent.TimeUnit + +import com.wavesplatform.lang.Common +import com.wavesplatform.lang.directives.DirectiveSet +import com.wavesplatform.lang.directives.values.{Account, Expression, V5} +import com.wavesplatform.lang.utils.lazyContexts +import com.wavesplatform.lang.v1.FunctionHeader.Native +import com.wavesplatform.lang.v1.compiler.Terms.{CONST_BIGINT, CONST_LONG, EXPR, FUNCTION_CALL} +import com.wavesplatform.lang.v1.evaluator.EvaluatorV2 +import com.wavesplatform.lang.v1.evaluator.FunctionIds.POW_BIGINT +import com.wavesplatform.lang.v1.evaluator.ctx.impl.{PureContext, Rounding} +import org.openjdk.jmh.annotations.{State, _} +import org.openjdk.jmh.infra.Blackhole + +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@BenchmarkMode(Array(Mode.AverageTime)) +@Threads(1) +@Fork(1) +@Warmup(iterations = 10, time = 1) +@Measurement(iterations = 10, time = 1) +class PowBigIntBenchmark { + @Benchmark + def pow1(bh: Blackhole, s: PowBigIntSt): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr1, V5)) + + @Benchmark + def pow2(bh: Blackhole, s: PowBigIntSt): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr2, V5)) + + @Benchmark + def pow3(bh: Blackhole, s: PowBigIntSt): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr3, V5)) + + @Benchmark + def pow4(bh: Blackhole, s: PowBigIntSt): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr4, V5)) + + @Benchmark + def pow5(bh: Blackhole, s: PowBigIntSt): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr5, V5)) + + @Benchmark + def pow6(bh: Blackhole, s: PowBigIntSt): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr6, V5)) + + @Benchmark + def pow7(bh: Blackhole, s: PowBigIntSt): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr7, V5)) + + @Benchmark + def pow8(bh: Blackhole, s: PowBigIntSt): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr8, V5)) + + @Benchmark + def pow9(bh: Blackhole, s: PowBigIntSt): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr9, V5)) + + @Benchmark + def pow10(bh: Blackhole, s: PowBigIntSt): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr10, V5)) +} + +@State(Scope.Benchmark) +class PowBigIntSt { + val ds = DirectiveSet(V5, Account, Expression).fold(null, identity) + val ctx = lazyContexts(ds).value().evaluationContext(Common.emptyBlockchainEnvironment()) + + val max = PureContext.BigIntMax + val min = PureContext.BigIntMin + val one = BigInt(1) + + val d18 = BigInt(987654321012345678L) + val d19 = BigInt(1987654321012345678L) + + val e1 = BigInt("3259987654320123456789") + val e2 = BigInt("515598765432101234567") + val e3 = max / (BigInt(Long.MaxValue).pow(7) / BigInt(4)) + + val expr1 = pow(max, 0, max, 18, 18) // ERROR + val expr2 = pow(max, 0, max, 0, 0) // ERROR + val expr3 = pow(min, 0, max, 0, 0) // ERROR + val expr4 = pow(max, 0, min, 0, 18) // ERROR + val expr5 = pow(one, 18, min, 0, 18) // ERROR + val expr6 = pow(d18, 18, min, 0, 18) // ERROR + val expr7 = pow(d18, 18, max, 0, 18) // ERROR + val expr8 = pow(d18, 18, e3, 18, 18) // 0 + val expr9 = pow(d18, 18, e1, 18, 18) // 2 + val expr10 = pow(d19, 18, e2, 18, 0) // ≈ max +} diff --git a/benchmark/src/test/scala/com/wavesplatform/lang/v1/PowIntBenchmark.scala b/benchmark/src/test/scala/com/wavesplatform/lang/v1/PowIntBenchmark.scala new file mode 100644 index 00000000000..92c7b150555 --- /dev/null +++ b/benchmark/src/test/scala/com/wavesplatform/lang/v1/PowIntBenchmark.scala @@ -0,0 +1,73 @@ +package com.wavesplatform.lang.v1 + +import java.util.concurrent.TimeUnit + +import com.wavesplatform.lang.Common +import com.wavesplatform.lang.directives.DirectiveSet +import com.wavesplatform.lang.directives.values.{Account, Expression, V5} +import com.wavesplatform.lang.utils.lazyContexts +import com.wavesplatform.lang.v1.compiler.Terms.EXPR +import com.wavesplatform.lang.v1.compiler.TestCompiler +import com.wavesplatform.lang.v1.evaluator.EvaluatorV2 +import org.openjdk.jmh.annotations.{State, _} +import org.openjdk.jmh.infra.Blackhole + +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@BenchmarkMode(Array(Mode.AverageTime)) +@Threads(1) +@Fork(1) +@Warmup(iterations = 10, time = 1) +@Measurement(iterations = 10, time = 1) +class PowIntBenchmark { + @Benchmark + def pow1(bh: Blackhole, s: PowIntSt): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr1, V5)) + + @Benchmark + def pow2(bh: Blackhole, s: PowIntSt): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr2, V5)) + + @Benchmark + def pow3(bh: Blackhole, s: PowIntSt): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr3, V5)) + + @Benchmark + def pow4(bh: Blackhole, s: PowIntSt): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr4, V5)) + + @Benchmark + def pow5(bh: Blackhole, s: PowIntSt): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr5, V5)) + + @Benchmark + def pow6(bh: Blackhole, s: PowIntSt): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr6, V5)) + + @Benchmark + def pow7(bh: Blackhole, s: PowIntSt): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr7, V5)) + + @Benchmark + def pow8(bh: Blackhole, s: PowIntSt): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr8, V5)) + + @Benchmark + def pow9(bh: Blackhole, s: PowIntSt): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr9, V5)) + + @Benchmark + def pow10(bh: Blackhole, s: PowIntSt): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr10, V5)) +} + +@State(Scope.Benchmark) +class PowIntSt { + val ds = DirectiveSet(V5, Account, Expression).fold(null, identity) + val ctx = lazyContexts(ds).value().evaluationContext(Common.emptyBlockchainEnvironment()) + + val max = Long.MaxValue + + val expr1 = compile(s"pow($max, 0, $max, 8, 8, DOWN)") // ERROR + val expr2 = compile(s"pow($max, 0, $max, 0, 0, DOWN)") // ERROR + val expr3 = compile(s"pow(-$max, 0, $max, 0, 0, DOWN)") // ERROR + val expr4 = compile(s"pow($max, 0, -$max, 0, 8, DOWN)") // ERROR + val expr5 = compile(s"pow(1, 8, -$max, 0, 8, DOWN)") // ERROR + val expr6 = compile(s"pow(98765432, 8, -$max, 0, 8, DOWN)") // ERROR + val expr7 = compile(s"pow(98765432, 8, $max, 0, 8, DOWN)") // ERROR + val expr8 = compile(s"pow(98765432, 8, $max, 8, 8, DOWN)") // 0 + val expr9 = compile(s"pow(98765432, 8, 145998765432, 8, 8, HALFUP)") // 1 + val expr10 = compile(s"pow(198765432, 8, 6298765432, 8, 0, DOWN)") // ≈ 6 * 10^18 + + private def compile(e: String): EXPR = + TestCompiler(V5).compileExpression(e).expr.asInstanceOf[EXPR] +} diff --git a/benchmark/src/test/scala/com/wavesplatform/lang/v1/PureFunctionsRebenchmark.scala b/benchmark/src/test/scala/com/wavesplatform/lang/v1/PureFunctionsRebenchmark.scala index cd6941c2ba2..588d1c71def 100644 --- a/benchmark/src/test/scala/com/wavesplatform/lang/v1/PureFunctionsRebenchmark.scala +++ b/benchmark/src/test/scala/com/wavesplatform/lang/v1/PureFunctionsRebenchmark.scala @@ -3,20 +3,21 @@ package com.wavesplatform.lang.v1 import java.util.concurrent.{ThreadLocalRandom, TimeUnit} import cats.Id -import cats.kernel.Monoid import com.google.common.primitives.Longs import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils._ +import com.wavesplatform.lang.directives.DirectiveSet import com.wavesplatform.lang.directives.values._ +import com.wavesplatform.lang.utils._ import com.wavesplatform.lang.v1.FunctionHeader.Native import com.wavesplatform.lang.v1.PureFunctionsRebenchmark._ import com.wavesplatform.lang.v1.compiler.Terms import com.wavesplatform.lang.v1.compiler.Terms._ import com.wavesplatform.lang.v1.evaluator.ctx.EvaluationContext -import com.wavesplatform.lang.v1.evaluator.ctx.impl.{CryptoContext, PureContext} +import com.wavesplatform.lang.v1.evaluator.ctx.impl.PureContext import com.wavesplatform.lang.v1.evaluator.{EvaluatorV2, FunctionIds, Log} import com.wavesplatform.lang.v1.traits.Environment -import com.wavesplatform.lang.{ExecutionError, Global} +import com.wavesplatform.lang.{Common, ExecutionError} import org.openjdk.jmh.annotations._ import org.openjdk.jmh.infra.Blackhole @@ -26,17 +27,13 @@ import scala.util.Random @BenchmarkMode(Array(Mode.AverageTime)) @Threads(1) @Fork(1) -@Warmup(iterations = 30) -@Measurement(iterations = 20) +@Warmup(iterations = 20, time = 1) +@Measurement(iterations = 20, time = 1) class PureFunctionsRebenchmark { @Benchmark def parseIntValue(st: ParseIntVal, bh: Blackhole): Unit = bh.consume(eval(st.expr)) - @Benchmark - def splitString(st: SplitString, bh: Blackhole): Unit = - bh.consume(eval(st.expr)) - @Benchmark def toBase58(st: ToBase58, bh: Blackhole): Unit = bh.consume(eval(st.expr)) @@ -53,10 +50,6 @@ class PureFunctionsRebenchmark { def fromBase64(st: FromBase64, bh: Blackhole): Unit = bh.consume(eval(st.expr)) - @Benchmark - def sumString(st: SumString, bh: Blackhole): Unit = - bh.consume(eval(st.expr)) - @Benchmark def sumByteString(st: SumByteString, bh: Blackhole): Unit = bh.consume(eval(st.expr)) @@ -134,10 +127,6 @@ class PureFunctionsRebenchmark { def parseIntValueV5(st: ParseIntVal, bh: Blackhole): Unit = bh.consume(evalV5(st.expr)) - @Benchmark - def splitStringV5(st: SplitString, bh: Blackhole): Unit = - bh.consume(evalV5(st.expr)) - @Benchmark def toBase58V5(st: ToBase58, bh: Blackhole): Unit = bh.consume(evalV5(st.expr)) @@ -154,10 +143,6 @@ class PureFunctionsRebenchmark { def fromBase64V5(st: FromBase64, bh: Blackhole): Unit = bh.consume(evalV5(st.expr)) - @Benchmark - def sumStringV5(st: SumString, bh: Blackhole): Unit = - bh.consume(evalV5(st.expr)) - @Benchmark def sumByteStringV5(st: SumByteString, bh: Blackhole): Unit = bh.consume(evalV5(st.expr)) @@ -233,12 +218,8 @@ class PureFunctionsRebenchmark { object PureFunctionsRebenchmark { val context: EvaluationContext[Environment, Id] = - Monoid - .combine( - PureContext.build(V4, fixUnicodeFunctions = true).evaluationContext[Id], - CryptoContext.build(Global, V4).evaluationContext[Id] - ) - .asInstanceOf[EvaluationContext[Environment, Id]] + lazyContexts(DirectiveSet(V5, Account, Expression).explicitGet())() + .evaluationContext(Common.emptyBlockchainEnvironment()) val eval: EXPR => (Log[Id], Int, Either[ExecutionError, EVALUATED]) = EvaluatorV2.applyCompleted(context, _, V4) @@ -252,20 +233,6 @@ object PureFunctionsRebenchmark { bytes } - @State(Scope.Benchmark) - class SplitString { - val separator = "," - val separatedString = List.fill(1000)(Random.nextPrintableChar().toString * 31).mkString(separator) - val expr: EXPR = - FUNCTION_CALL( - PureContext.splitStr, - List( - CONST_STRING(separatedString).explicitGet(), - CONST_STRING(separator).explicitGet() - ) - ) - } - @State(Scope.Benchmark) class ParseIntVal { val numStr = Long.MinValue.toString @@ -326,20 +293,6 @@ object PureFunctionsRebenchmark { ) } - @State(Scope.Benchmark) - class SumString { - val string1 = "a" - val string2 = Random.nextPrintableChar().toString * 32766 - val expr: EXPR = - FUNCTION_CALL( - Native(FunctionIds.SUM_STRING), - List( - CONST_STRING(string1).explicitGet(), - CONST_STRING(string2).explicitGet() - ) - ) - } - @State(Scope.Benchmark) class SumByteString { val byteString1 = ByteStr.fromBytes(1) diff --git a/benchmark/src/test/scala/com/wavesplatform/lang/v1/SqrtCbrtBigIntBenchmark.scala b/benchmark/src/test/scala/com/wavesplatform/lang/v1/SqrtCbrtBigIntBenchmark.scala new file mode 100644 index 00000000000..e374c20e8d3 --- /dev/null +++ b/benchmark/src/test/scala/com/wavesplatform/lang/v1/SqrtCbrtBigIntBenchmark.scala @@ -0,0 +1,49 @@ +package com.wavesplatform.lang.v1 + +import java.util.concurrent.TimeUnit + +import com.wavesplatform.lang.Common +import com.wavesplatform.lang.directives.DirectiveSet +import com.wavesplatform.lang.directives.values.{Account, Expression, V5} +import com.wavesplatform.lang.utils.lazyContexts +import com.wavesplatform.lang.v1.evaluator.EvaluatorV2 +import com.wavesplatform.lang.v1.evaluator.ctx.impl.PureContext +import org.openjdk.jmh.annotations.{State, _} +import org.openjdk.jmh.infra.Blackhole + +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@BenchmarkMode(Array(Mode.AverageTime)) +@Threads(1) +@Fork(1) +@Warmup(iterations = 10, time = 1) +@Measurement(iterations = 10, time = 1) +class SqrtCbrtBigIntBenchmark { + @Benchmark + def sqrt1(bh: Blackhole, s: SqrtBigIntSt): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr1, V5)) + + @Benchmark + def sqrt2(bh: Blackhole, s: SqrtBigIntSt): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr2, V5)) + + @Benchmark + def cbrt1(bh: Blackhole, s: SqrtBigIntSt): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr3, V5)) + + @Benchmark + def cbrt2(bh: Blackhole, s: SqrtBigIntSt): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr4, V5)) +} + +@State(Scope.Benchmark) +class SqrtBigIntSt { + val ds = DirectiveSet(V5, Account, Expression).fold(null, identity) + val ctx = lazyContexts(ds).value().evaluationContext(Common.emptyBlockchainEnvironment()) + + val max = PureContext.BigIntMax + val min = PureContext.BigIntMin + + val e1 = BigInt(5) + val e2 = BigInt(3333333333333333L) + + val expr1 = pow(max, 0, e1, 1, 18) + val expr2 = pow(max, 18, e1, 1, 18) + val expr3 = pow(max, 0, e2, 16, 18) + val expr4 = pow(min, 18, e2, 16, 18) +} diff --git a/benchmark/src/test/scala/com/wavesplatform/lang/v1/SqrtIntBenchmark.scala b/benchmark/src/test/scala/com/wavesplatform/lang/v1/SqrtIntBenchmark.scala new file mode 100644 index 00000000000..fd4d9a70aac --- /dev/null +++ b/benchmark/src/test/scala/com/wavesplatform/lang/v1/SqrtIntBenchmark.scala @@ -0,0 +1,39 @@ +package com.wavesplatform.lang.v1 + +import java.util.concurrent.TimeUnit + +import com.wavesplatform.lang.Common +import com.wavesplatform.lang.directives.DirectiveSet +import com.wavesplatform.lang.directives.values.{Account, Expression, V5} +import com.wavesplatform.lang.utils.lazyContexts +import com.wavesplatform.lang.v1.compiler.Terms.EXPR +import com.wavesplatform.lang.v1.compiler.TestCompiler +import com.wavesplatform.lang.v1.evaluator.EvaluatorV2 +import org.openjdk.jmh.annotations.{State, _} +import org.openjdk.jmh.infra.Blackhole + +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@BenchmarkMode(Array(Mode.AverageTime)) +@Threads(1) +@Fork(1) +@Warmup(iterations = 10, time = 1) +@Measurement(iterations = 10, time = 1) +class SqrtIntBenchmark { + @Benchmark + def sqrt1(bh: Blackhole, s: SqrtIntSt): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr1, V5)) + + @Benchmark + def sqrt2(bh: Blackhole, s: SqrtIntSt): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr2, V5)) +} + +@State(Scope.Benchmark) +class SqrtIntSt { + val ds = DirectiveSet(V5, Account, Expression).fold(null, identity) + val ctx = lazyContexts(ds).value().evaluationContext(Common.emptyBlockchainEnvironment()) + + val expr1 = compile(s"pow(${Long.MaxValue}, 0, 5, 1, 8, DOWN)") + val expr2 = compile(s"pow(${Long.MaxValue}, 8, 5, 1, 8, DOWN)") + + private def compile(e: String): EXPR = + TestCompiler(V5).compileExpression(e).expr.asInstanceOf[EXPR] +} diff --git a/benchmark/src/test/scala/com/wavesplatform/lang/v1/StringSplitBenchmark.scala b/benchmark/src/test/scala/com/wavesplatform/lang/v1/StringSplitBenchmark.scala new file mode 100644 index 00000000000..f7aee42c75c --- /dev/null +++ b/benchmark/src/test/scala/com/wavesplatform/lang/v1/StringSplitBenchmark.scala @@ -0,0 +1,77 @@ +package com.wavesplatform.lang.v1 +import java.util.concurrent.TimeUnit + +import com.wavesplatform.lang.v1.compiler.Terms.{CONST_STRING, EXPR, FUNCTION_CALL} +import com.wavesplatform.lang.v1.evaluator.ctx.impl.PureContext +import org.openjdk.jmh.annotations._ +import com.wavesplatform.common.utils._ +import com.wavesplatform.lang.v1.PureFunctionsRebenchmark.evalV5 +import com.wavesplatform.lang.v1.StringSplitBenchmark._ +import org.openjdk.jmh.infra.Blackhole + +import scala.util.Random + +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@BenchmarkMode(Array(Mode.AverageTime)) +@Threads(1) +@Fork(1) +@Warmup(iterations = 10, time = 1) +@Measurement(iterations = 10, time = 1) +class StringSplitBenchmark { + @Benchmark + def splitString(st: SplitString, bh: Blackhole): Unit = + bh.consume(evalV5(st.expr)) + + @Benchmark + def splitString200x30(st: SplitString200x30, bh: Blackhole): Unit = + bh.consume(evalV5(st.expr)) + + @Benchmark + def splitString100x50(st: SplitString100x50, bh: Blackhole): Unit = + bh.consume(evalV5(st.expr)) + + @Benchmark + def splitString100x60(st: SplitString100x60, bh: Blackhole): Unit = + bh.consume(evalV5(st.expr)) + + @Benchmark + def splitString20x25(st: SplitString20x25, bh: Blackhole): Unit = + bh.consume(evalV5(st.expr)) + + @Benchmark + def splitString20x10(st: SplitString20x10, bh: Blackhole): Unit = + bh.consume(evalV5(st.expr)) +} + +object StringSplitBenchmark { + abstract class StringSplit(listSize: Int, elementSize: Int) { + val separator = "," + val separatedString = List.fill(listSize)(Random.nextPrintableChar().toString * elementSize).mkString(separator) + val expr: EXPR = + FUNCTION_CALL( + PureContext.splitStr, + List( + CONST_STRING(separatedString).explicitGet(), + CONST_STRING(separator).explicitGet() + ) + ) + } + + @State(Scope.Benchmark) + class SplitString extends StringSplit(1000, 31) + + @State(Scope.Benchmark) + class SplitString200x30 extends StringSplit(200, 30) + + @State(Scope.Benchmark) + class SplitString100x50 extends StringSplit(100, 50) + + @State(Scope.Benchmark) + class SplitString100x60 extends StringSplit(100, 60) + + @State(Scope.Benchmark) + class SplitString20x25 extends StringSplit(20, 25) + + @State(Scope.Benchmark) + class SplitString20x10 extends StringSplit(20, 10) +} diff --git a/benchmark/src/test/scala/com/wavesplatform/lang/v1/SumStringBenchmark.scala b/benchmark/src/test/scala/com/wavesplatform/lang/v1/SumStringBenchmark.scala new file mode 100644 index 00000000000..16697073b3d --- /dev/null +++ b/benchmark/src/test/scala/com/wavesplatform/lang/v1/SumStringBenchmark.scala @@ -0,0 +1,78 @@ +package com.wavesplatform.lang.v1 +import java.util.concurrent.TimeUnit + +import com.wavesplatform.common.utils._ +import com.wavesplatform.lang.v1.FunctionHeader.Native +import com.wavesplatform.lang.v1.PureFunctionsRebenchmark.evalV5 +import com.wavesplatform.lang.v1.SumStringBenchmark._ +import com.wavesplatform.lang.v1.compiler.Terms.{CONST_STRING, FUNCTION_CALL} +import com.wavesplatform.lang.v1.evaluator.FunctionIds +import org.openjdk.jmh.annotations._ +import org.openjdk.jmh.infra.Blackhole + +import scala.util.Random + +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@BenchmarkMode(Array(Mode.AverageTime)) +@Threads(1) +@Fork(1) +@Warmup(iterations = 10, time = 1) +@Measurement(iterations = 10, time = 1) +class SumStringBenchmark { + @Benchmark + def sumString1_32766(st: SumString1_32766, bh: Blackhole): Unit = + bh.consume(evalV5(st.expr)) + + @Benchmark + def sumString32766_1(st: SumString32766_1, bh: Blackhole): Unit = + bh.consume(evalV5(st.expr)) + + @Benchmark + def sumString16383_16383(st: SumString16383_16383, bh: Blackhole): Unit = + bh.consume(evalV5(st.expr)) + + @Benchmark + def sumString1_10000(st: SumString1_10000, bh: Blackhole): Unit = + bh.consume(evalV5(st.expr)) + + @Benchmark + def sumString10000_1(st: SumString10000_1, bh: Blackhole): Unit = + bh.consume(evalV5(st.expr)) + + @Benchmark + def sumString5000_5000(st: SumString5000_5000, bh: Blackhole): Unit = + bh.consume(evalV5(st.expr)) +} + +object SumStringBenchmark { + abstract class SumString(size1: Int, size2: Int) { + val string1 = Random.nextPrintableChar().toString * size1 + val string2 = Random.nextPrintableChar().toString * size2 + val expr = + FUNCTION_CALL( + Native(FunctionIds.SUM_STRING), + List( + CONST_STRING(string1).explicitGet(), + CONST_STRING(string2).explicitGet() + ) + ) + } + + @State(Scope.Benchmark) + class SumString1_32766 extends SumString(1, 32766) + + @State(Scope.Benchmark) + class SumString32766_1 extends SumString(32766, 1) + + @State(Scope.Benchmark) + class SumString16383_16383 extends SumString(32766 / 2, 32766 / 2) + + @State(Scope.Benchmark) + class SumString1_10000 extends SumString(1, 10000) + + @State(Scope.Benchmark) + class SumString10000_1 extends SumString(10000, 1) + + @State(Scope.Benchmark) + class SumString5000_5000 extends SumString(5000, 5000) +} diff --git a/benchmark/src/test/scala/com/wavesplatform/lang/v1/package.scala b/benchmark/src/test/scala/com/wavesplatform/lang/v1/package.scala new file mode 100644 index 00000000000..5045eb95228 --- /dev/null +++ b/benchmark/src/test/scala/com/wavesplatform/lang/v1/package.scala @@ -0,0 +1,21 @@ +package com.wavesplatform.lang + +import com.wavesplatform.lang.v1.FunctionHeader.Native +import com.wavesplatform.lang.v1.compiler.Terms.{CONST_BIGINT, CONST_LONG, EXPR, FUNCTION_CALL} +import com.wavesplatform.lang.v1.evaluator.FunctionIds.POW_BIGINT +import com.wavesplatform.lang.v1.evaluator.ctx.impl.Rounding + +package object v1 { + def pow(base: BigInt, basePrecision: Int, exponent: BigInt, exponentPrecision: Int, resultPrecision: Int): EXPR = + FUNCTION_CALL( + Native(POW_BIGINT), + List( + CONST_BIGINT(base), + CONST_LONG(basePrecision), + CONST_BIGINT(exponent), + CONST_LONG(exponentPrecision), + CONST_LONG(resultPrecision), + Rounding.Down.value + ) + ) +} diff --git a/lang/doc/v3/funcs/math-functions.hjson b/lang/doc/v3/funcs/math-functions.hjson index a846a1faeda..5d60f29f901 100644 --- a/lang/doc/v3/funcs/math-functions.hjson +++ b/lang/doc/v3/funcs/math-functions.hjson @@ -10,7 +10,7 @@ "The exponent." "The number of decimals of the exponent." "The number of decimals of the resulting value." - "One of the rounding functions. The HalfUp() function may be used as the default value." + "One of the rounding functions." ] complexity: 100 } @@ -26,7 +26,7 @@ "The number of decimals of the resulting value." ''' The rounding function. - The HalfUp() function may be used as the default value. + ''' ] complexity: 100 diff --git a/lang/doc/v4/funcs/math-functions.hjson b/lang/doc/v4/funcs/math-functions.hjson index a846a1faeda..5d60f29f901 100644 --- a/lang/doc/v4/funcs/math-functions.hjson +++ b/lang/doc/v4/funcs/math-functions.hjson @@ -10,7 +10,7 @@ "The exponent." "The number of decimals of the exponent." "The number of decimals of the resulting value." - "One of the rounding functions. The HalfUp() function may be used as the default value." + "One of the rounding functions." ] complexity: 100 } @@ -26,7 +26,7 @@ "The number of decimals of the resulting value." ''' The rounding function. - The HalfUp() function may be used as the default value. + ''' ] complexity: 100 diff --git a/lang/doc/v5/funcs/math-functions.hjson b/lang/doc/v5/funcs/math-functions.hjson index 4163ed01d85..ea94ecf86c8 100644 --- a/lang/doc/v5/funcs/math-functions.hjson +++ b/lang/doc/v5/funcs/math-functions.hjson @@ -10,7 +10,7 @@ "The exponent." "The number of decimals of the exponent." "The number of decimals of the resulting value." - "One of the rounding functions. The HalfUp() function may be used as the default value." + "One of the rounding functions." ] complexity: 100 } @@ -26,7 +26,7 @@ "The number of decimals of the resulting value." ''' The rounding function. - The HalfUp() function may be used as the default value. + ''' ] complexity: 100 @@ -41,7 +41,7 @@ "The exponent." "The number of decimals of the exponent." "The number of decimals of the resulting value." - "One of the rounding functions. The HalfUp() function may be used as the default value." + "One of the rounding functions." ] complexity: 200 } @@ -57,7 +57,7 @@ "The number of decimals of the resulting value." ''' The rounding function. - The HalfUp() function may be used as the default value. + ''' ] complexity: 200 diff --git a/lang/doc/v6/funcs/converting-functions.hjson b/lang/doc/v6/funcs/converting-functions.hjson index 41e121a7608..ae4c7444418 100644 --- a/lang/doc/v6/funcs/converting-functions.hjson +++ b/lang/doc/v6/funcs/converting-functions.hjson @@ -121,7 +121,7 @@ params: [ "Address" ] doc: "Convert address bytes to string" paramsDoc: [ "The address to convert" ] - complexity: 10 + complexity: 1 } { name: "toString" @@ -142,7 +142,7 @@ params: [ "BigInt" ] doc: "Convert BigInt to string" paramsDoc: [ "The BigInt to convert" ] - complexity: 65 + complexity: 1 } { name: "toUtf8String" diff --git a/lang/doc/v6/funcs/list-functions.hjson b/lang/doc/v6/funcs/list-functions.hjson index 17ed4b5a863..76546ce87ea 100644 --- a/lang/doc/v6/funcs/list-functions.hjson +++ b/lang/doc/v6/funcs/list-functions.hjson @@ -89,7 +89,21 @@ params: [ "List[String]", "String" ] doc: "Returns all the elements of the list in a string using the separator string." paramsDoc: [ "The list of strings.", "The separator" ] - complexity: 30 + complexity: 11 + } + { + name: "makeString_1C" + params: [ "List[String]", "String" ] + doc: "Returns all the elements of the list in a string using the separator string. Input list size limit = 70. Output string size limit = 500 bytes." + paramsDoc: [ "The list of strings.", "The separator" ] + complexity: 1 + } + { + name: "makeString_2C" + params: [ "List[String]", "String" ] + doc: "Returns all the elements of the list in a string using the separator string. Input list size limit = 100. Output string size limit = 6000 bytes." + paramsDoc: [ "The list of strings.", "The separator" ] + complexity: 2 } { name: "removeByIndex" diff --git a/lang/doc/v6/funcs/math-functions.hjson b/lang/doc/v6/funcs/math-functions.hjson index 4163ed01d85..94f3b1323f2 100644 --- a/lang/doc/v6/funcs/math-functions.hjson +++ b/lang/doc/v6/funcs/math-functions.hjson @@ -10,9 +10,21 @@ "The exponent." "The number of decimals of the exponent." "The number of decimals of the resulting value." - "One of the rounding functions. The HalfUp() function may be used as the default value." + "One of the rounding functions." ] - complexity: 100 + complexity: 28 + } + { + name: "sqrt" + params: [ "Int", "Int", "Int", "Ceiling|Down|Floor|HalfEven|HalfUp" ] + doc: "Returns a square root." + paramsDoc: [ + "The number" + "The number of decimals of the number." + "The number of decimals of the resulting value." + "One of the rounding functions." + ] + complexity: 2 } { name: "log" @@ -26,7 +38,7 @@ "The number of decimals of the resulting value." ''' The rounding function. - The HalfUp() function may be used as the default value. + ''' ] complexity: 100 @@ -41,9 +53,21 @@ "The exponent." "The number of decimals of the exponent." "The number of decimals of the resulting value." - "One of the rounding functions. The HalfUp() function may be used as the default value." + "One of the rounding functions." ] - complexity: 200 + complexity: 270 + } + { + name: "sqrt" + params: [ "BigInt", "Int", "Int", "Ceiling|Down|Floor|HalfEven|HalfUp" ] + doc: "Returns a square root." + paramsDoc: [ + "The base." + "The number of decimals of the base." + "The number of decimals of the resulting value." + "One of the rounding functions." + ] + complexity: 5 } { name: "log" @@ -57,7 +81,7 @@ "The number of decimals of the resulting value." ''' The rounding function. - The HalfUp() function may be used as the default value. + ''' ] complexity: 200 @@ -67,28 +91,28 @@ params: [ "Int", "Int", "Int" ] doc: "Multiply and division with unlimited intermediate representation" paramsDoc: [ "Multiplier", "Multiplier", "Divisor" ] - complexity: 14 + complexity: 1 } { name: "fraction" params: [ "Int", "Int", "Int", "Ceiling|Down|Floor|HalfEven|HalfUp" ] doc: "Multiply and division with big integer intermediate representation" paramsDoc: [ "Multiplier", "Multiplier", "Divisor", "Rounding mode" ] - complexity: 17 + complexity: 1 } { name: "fraction" params: [ "BigInt", "BigInt", "BigInt" ] doc: "Multiply and division with unlimited intermediate representation" paramsDoc: [ "Multiplier", "Multiplier", "Divisor" ] - complexity: 128 + complexity: 1 } { name: "fraction" params: [ "BigInt", "BigInt", "BigInt", "Ceiling|Down|Floor|HalfEven|HalfUp" ] doc: "Multiply and division with unlimited intermediate representation" paramsDoc: [ "Multiplier", "Multiplier", "Divisor", "Rounding mode" ] - complexity: 128 + complexity: 1 } ] } diff --git a/lang/doc/v6/funcs/operators.hjson b/lang/doc/v6/funcs/operators.hjson index 65a4e8886cc..54d51cc6340 100644 --- a/lang/doc/v6/funcs/operators.hjson +++ b/lang/doc/v6/funcs/operators.hjson @@ -75,7 +75,7 @@ params: [ "String", "String" ] doc: "Concat limited strings." paramsDoc: [ "First value", "Second value" ] - complexity: 20 + complexity: 1 } { name: "+" diff --git a/lang/doc/v6/funcs/string-functions.hjson b/lang/doc/v6/funcs/string-functions.hjson index b74d9a0af4f..43977771caf 100644 --- a/lang/doc/v6/funcs/string-functions.hjson +++ b/lang/doc/v6/funcs/string-functions.hjson @@ -54,7 +54,28 @@ params: [ "String", "String" ] doc: "Splits a string delimited by a separator into a list of substrings" paramsDoc: [ "The string.", "The separator." ] - complexity: 75 + complexity: 51 + } + { + name: "split" + params: [ "String", "String" ] + doc: "Splits a string delimited by a separator into a list of substrings" + paramsDoc: [ "The string.", "The separator." ] + complexity: 51 + } + { + name: "split_1C" + params: [ "String", "String" ] + doc: "Splits a string delimited by a separator into a list of substrings. Input string size limit = 500 bytes. Output list size limit = 20" + paramsDoc: [ "The string.", "The separator." ] + complexity: 1 + } + { + name: "split_4C" + params: [ "String", "String" ] + doc: "Splits a string delimited by a separator into a list of substrings. Input string size limit = 6000 bytes. Output list size limit = 100" + paramsDoc: [ "The string.", "The separator." ] + complexity: 4 } { name: "size" diff --git a/lang/js/src/main/scala/com/wavesplatform/lang/Global.scala b/lang/js/src/main/scala/com/wavesplatform/lang/Global.scala index 0554ce7541e..56e1d30e8ea 100644 --- a/lang/js/src/main/scala/com/wavesplatform/lang/Global.scala +++ b/lang/js/src/main/scala/com/wavesplatform/lang/Global.scala @@ -86,7 +86,7 @@ object Global extends BaseGlobal { def toLongExact(v: BigInt) = Either.cond(v.isValidLong, v.toLong, s"$v out of range") - override def pow(b: Long, bp: Long, e: Long, ep: Long, rp: Long, round: Rounding): Either[String, Long] = + override def pow(b: Long, bp: Int, e: Long, ep: Int, rp: Int, round: Rounding): Either[String, Long] = calcScaled(Math.pow)(b, bp, e, ep, rp, round).flatMap(toLongExact) override def log(b: Long, bp: Long, e: Long, ep: Long, rp: Long, round: Rounding): Either[String, Long] = diff --git a/lang/jvm/src/main/scala/com/wavesplatform/lang/Global.scala b/lang/jvm/src/main/scala/com/wavesplatform/lang/Global.scala index 7b6343ca32d..ee900eb4985 100644 --- a/lang/jvm/src/main/scala/com/wavesplatform/lang/Global.scala +++ b/lang/jvm/src/main/scala/com/wavesplatform/lang/Global.scala @@ -81,27 +81,27 @@ object Global extends BaseGlobal { private val longDigits = 19 private val longContext = new MathContext(longDigits) - private val bigIntDigits = 154 + private val bigIntDigits = 154 private val bigMathContext = new MathContext(bigIntDigits) // Math functions def pow( base: Long, - basePrecision: Long, + basePrecision: Int, exponent: Long, - exponentPrecision: Long, - resultPrecision: Long, + exponentPrecision: Int, + resultPrecision: Int, round: Rounding ): Either[String, Long] = tryEither { - val baseBD = BD.valueOf(base, basePrecision.toInt) - val expBD = BD.valueOf(exponent, exponentPrecision.toInt) + val baseBD = BD.valueOf(base, basePrecision) + val expBD = BD.valueOf(exponent, exponentPrecision) val result = if (expBD == BigDecimal(0.5).bigDecimal) { BigDecimalMath.sqrt(baseBD, longContext) } else { BigDecimalMath.pow(baseBD, expBD, longContext) } - setScale(resultPrecision.toInt, round, longDigits, result) + setScale(resultPrecision, round, longDigits, result) }.flatten.map(_.bigInteger.longValueExact()) def log(b: Long, bp: Long, e: Long, ep: Long, rp: Long, round: Rounding): Either[String, Long] = @@ -126,6 +126,14 @@ object Global extends BaseGlobal { setScale(rp.toInt, round, bigIntDigits, res) }.flatten + def logBigInt(b: BigInt, bp: Long, e: BigInt, ep: Long, rp: Long, round: Rounding): Either[String, BigInt] = + tryEither { + val base = toJBig(b, bp) + val exp = toJBig(e, ep) + val res = BigDecimalMath.log(base, bigMathContext).divide(BigDecimalMath.log(exp, bigMathContext), bigMathContext) + BigInt(res.setScale(rp.toInt, round.mode).unscaledValue) + } + private def setScale( resultPrecision: Int, round: Rounding, @@ -145,14 +153,6 @@ object Global extends BaseGlobal { Right(BigInt(value) * BigInteger.TEN.pow(resultPrecision - scale)) } - def logBigInt(b: BigInt, bp: Long, e: BigInt, ep: Long, rp: Long, round: Rounding): Either[String, BigInt] = - tryEither { - val base = toJBig(b, bp) - val exp = toJBig(e, ep) - val res = BigDecimalMath.log(base, bigMathContext).divide(BigDecimalMath.log(exp, bigMathContext), bigMathContext) - BigInt(res.setScale(rp.toInt, round.mode).unscaledValue) - } - override def groth16Verify(verifyingKey: Array[Byte], proof: Array[Byte], inputs: Array[Byte]): Boolean = Bls12Groth16.verify(verifyingKey, proof, inputs) diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/BaseGlobal.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/BaseGlobal.scala index 5dc7727e155..1ac2a8bdc46 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/BaseGlobal.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/BaseGlobal.scala @@ -259,7 +259,7 @@ trait BaseGlobal { // Math functions - def pow(b: Long, bp: Long, e: Long, ep: Long, rp: Long, round: Rounding): Either[String, Long] + def pow(b: Long, bp: Int, e: Long, ep: Int, rp: Int, round: Rounding): Either[String, Long] def log(b: Long, bp: Long, e: Long, ep: Long, rp: Long, round: Rounding): Either[String, Long] def powBigInt(b: BigInt, bp: Long, e: BigInt, ep: Long, rp: Long, round: Rounding): Either[String, BigInt] def logBigInt(b: BigInt, bp: Long, e: BigInt, ep: Long, rp: Long, round: Rounding): Either[String, BigInt] diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/Terms.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/Terms.scala index c8aed6b8749..0ec553f7841 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/Terms.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/compiler/Terms.scala @@ -206,12 +206,12 @@ object Terms { Some(arg.bs) } - class CONST_STRING private (val s: String) extends EVALUATED { + class CONST_STRING private (val s: String, bytesLength: Int) extends EVALUATED { override def toString: String = s override def prettyString(level: Int): String = "\"" ++ escape(s) ++ "\"" - override val weight: Long = s.getBytes.length + override lazy val weight: Long = bytesLength - override val getType: REAL = STRING + override val getType: REAL = STRING override def equals(obj: Any): Boolean = obj match { @@ -221,17 +221,18 @@ object Terms { override def hashCode(): Int = s.hashCode } + object CONST_STRING { - def apply(s: String, reduceLimit: Boolean = true): Either[ExecutionError, CONST_STRING] = { + def apply(s: String, reduceLimit: Boolean = true, bytesLength: Option[Int] = None): Either[ExecutionError, CONST_STRING] = { val limit = if (reduceLimit) DataEntryValueMax else DataTxMaxBytes - val actualSize = s.getBytes(StandardCharsets.UTF_8).length + val actualSize = bytesLength.getOrElse(s.getBytes(StandardCharsets.UTF_8).length) Either.cond( actualSize <= limit, - new CONST_STRING(s), + new CONST_STRING(s, actualSize), s"String size=$actualSize exceeds $limit bytes" ) } 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 55124b24627..ba0603209ae 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 @@ -13,7 +13,9 @@ object FunctionIds { val MUL_LONG: Short = 104 val DIV_LONG: Short = 105 val MOD_LONG: Short = 106 - val FRACTION: Short = 107 + + val FRACTION: Short = 107 + val FRACTION_ROUNDS: Short = 110 val POW: Short = 108 val LOG: Short = 109 @@ -31,38 +33,38 @@ object FunctionIds { val DROP_STRING: Short = 304 val SIZE_STRING: Short = 305 - val TO_BIGINT: Short = 310 - val SUM_BIGINT: Short = 311 - val SUB_BIGINT: Short = 312 - val MUL_BIGINT: Short = 313 - val DIV_BIGINT: Short = 314 - val MOD_BIGINT: Short = 315 - val FRACTION_BIGINT: Short = 316 + val TO_BIGINT: Short = 310 + val SUM_BIGINT: Short = 311 + val SUB_BIGINT: Short = 312 + val MUL_BIGINT: Short = 313 + val DIV_BIGINT: Short = 314 + val MOD_BIGINT: Short = 315 + val FRACTION_BIGINT: Short = 316 val FRACTION_BIGINT_ROUNDS: Short = 317 - val UMINUS_BIGINT: Short = 318 - val GT_BIGINT: Short = 319 - val GE_BIGINT: Short = 320 - - val SIZE_LIST: Short = 400 - val GET_LIST: Short = 401 - val MEDIAN_LIST: Short = 405 - val MAX_LIST: Short = 406 - val MIN_LIST: Short = 407 - val MAX_LIST_BIGINT: Short = 408 - val MIN_LIST_BIGINT: Short = 409 - val LONG_TO_BYTES: Short = 410 - val STRING_TO_BYTES: Short = 411 - val BOOLEAN_TO_BYTES: Short = 412 - val BIGINT_TO_BYTES: Short = 413 - val BYTES_TO_BIGINT: Short = 414 + val UMINUS_BIGINT: Short = 318 + val GT_BIGINT: Short = 319 + val GE_BIGINT: Short = 320 + + val SIZE_LIST: Short = 400 + val GET_LIST: Short = 401 + val MEDIAN_LIST: Short = 405 + val MAX_LIST: Short = 406 + val MIN_LIST: Short = 407 + val MAX_LIST_BIGINT: Short = 408 + val MIN_LIST_BIGINT: Short = 409 + val LONG_TO_BYTES: Short = 410 + val STRING_TO_BYTES: Short = 411 + val BOOLEAN_TO_BYTES: Short = 412 + val BIGINT_TO_BYTES: Short = 413 + val BYTES_TO_BIGINT: Short = 414 val BYTES_TO_BIGINT_LIM: Short = 415 - val BIGINT_TO_INT: Short = 416 - val LONG_TO_STRING: Short = 420 - val BOOLEAN_TO_STRING: Short = 421 - val BIGINT_TO_STRING: Short = 422 - val STRING_TO_BIGINT: Short = 423 - val STRING_TO_BIGINTOPT: Short = 424 - val MEDIAN_LISTBIGINT: Short = 425 + val BIGINT_TO_INT: Short = 416 + val LONG_TO_STRING: Short = 420 + val BOOLEAN_TO_STRING: Short = 421 + val BIGINT_TO_STRING: Short = 422 + val STRING_TO_BIGINT: Short = 423 + val STRING_TO_BIGINTOPT: Short = 424 + val MEDIAN_LISTBIGINT: Short = 425 val FOLD: Short = 450 @@ -84,6 +86,11 @@ object FunctionIds { val LASTINDEXOFN: Short = 1208 val MAKESTRING: Short = 1209 + val MAKESTRING1C: Short = 1210 + val MAKESTRING2C: Short = 1211 + val SPLIT1C: Short = 1212 + val SPLIT4C: Short = 1213 + val CREATE_TUPLE: Short = 1300 // Reserved 22 id for tuple constructors val SIZE_TUPLE: Short = 1350 @@ -128,8 +135,8 @@ object FunctionIds { val ACCOUNTASSETONLYBALANCE: Short = 1008 val ACCOUNTSCRIPTHASH: Short = 1009 - val CALLDAPP: Short = 1020 - val CALLDAPPREENTRANT: Short = 1021 + val CALLDAPP: Short = 1020 + val CALLDAPPREENTRANT: Short = 1021 val DATA_LONG_FROM_ARRAY: Short = 1040 val DATA_BOOLEAN_FROM_ARRAY: Short = 1041 @@ -141,7 +148,7 @@ object FunctionIds { val DATA_BYTES_FROM_STATE: Short = 1052 val DATA_STRING_FROM_STATE: Short = 1053 - val IS_UNTOUCHED: Short = 1054 + val IS_UNTOUCHED: Short = 1054 val DATA_LONG_FROM_STATE_SELF: Short = 1055 val DATA_BOOLEAN_FROM_STATE_SELF: Short = 1056 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 4dccf59ca58..3c30467641b 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 @@ -61,23 +61,26 @@ object PureContext { createTryOp(SUM_OP, LONG, LONG, SUM_LONG)((a, b) => Math.addExact(a, b)) lazy val subLong: BaseFunction[NoContext] = createTryOp(SUB_OP, LONG, LONG, SUB_LONG)((a, b) => Math.subtractExact(a, b)) + lazy val sumString: BaseFunction[NoContext] = createRawOp( SUM_OP, STRING, STRING, SUM_STRING, - Map[StdLibVersion, Long](V1 -> 10L, V2 -> 10L, V3 -> 10L, V4 -> 20L) + Map[StdLibVersion, Long](V1 -> 10L, V2 -> 10L, V3 -> 10L, V4 -> 20L, V5 -> 20L, V6 -> 1L) ) { - case (CONST_STRING(a), CONST_STRING(b)) => - if (a.length + b.length <= Terms.DataEntryValueMax) { - CONST_STRING(a + b) + case (s1 @ CONST_STRING(a), s2 @ CONST_STRING(b)) => + val sumWeight = (s1.weight + s2.weight).toInt + if (sumWeight <= Terms.DataEntryValueMax) { + CONST_STRING(a + b, bytesLength = Some(sumWeight)) } else { - Left(s"String length = ${a.length + b.length} exceeds ${Terms.DataEntryValueMax}") + Left(s"String size = $sumWeight exceeds ${Terms.DataEntryValueMax} bytes") } case args => Left(s"Unexpected args $args for string concatenation operator") } + lazy val sumByteStr: BaseFunction[NoContext] = createRawOp( SUM_OP, @@ -127,7 +130,7 @@ object PureContext { } lazy val bigIntToString: BaseFunction[NoContext] = - NativeFunction("toString", 65, BIGINT_TO_STRING, STRING, ("n", BIGINT)) { + NativeFunction("toString", Map(V5 -> 65L, V6 -> 1L), BIGINT_TO_STRING, STRING, ("n", BIGINT)) { case CONST_BIGINT(n) :: Nil => CONST_STRING(n.toString) case xs => notImplemented[Id, EVALUATED]("toString(n: BigInt)", xs) } @@ -327,7 +330,7 @@ object PureContext { def fraction(fixLimitCheck: Boolean): BaseFunction[NoContext] = NativeFunction( "fraction", - Map[StdLibVersion, Long](V1 -> 1, V2 -> 1, V3 -> 1, V4 -> 1, V5 -> 14), + Map[StdLibVersion, Long](V1 -> 1, V2 -> 1, V3 -> 1, V4 -> 1, V5 -> 14, V6 -> 1), FRACTION, LONG, ("value", LONG), @@ -352,7 +355,7 @@ object PureContext { def fractionIntRounds(roundTypes: UNION): BaseFunction[NoContext] = UserFunction( "fraction", - 17, + Map(V5 -> 17L, V6 -> 4L), LONG, ("@value", LONG), ("@numerator", LONG), @@ -371,10 +374,34 @@ object PureContext { FUNCTION_CALL(Native(BIGINT_TO_INT), List(r)) } + val fractionIntRoundsNative: BaseFunction[NoContext] = + NativeFunction( + "fraction", + 1L, + FRACTION_ROUNDS, + LONG, + ("value", LONG), + ("numerator", LONG), + ("denominator", LONG), + ("round", UNION(fromV5RoundTypes)) + ) { + case CONST_LONG(v) :: CONST_LONG(n) :: CONST_LONG(d) :: (r: CaseObj) :: Nil => + for { + _ <- Either.cond(d != 0, (), "Fraction: division by zero") + r <- global.divide(BigInt(v) * BigInt(n), d, Rounding.byValue(r)) + _ <- Either.cond(r.isValidLong, (), s"Fraction result $r out of integers range") + } yield CONST_LONG(r.longValue) + case xs => + notImplemented[Id, EVALUATED]( + "fraction(value: Int, numerator: Int, denominator: Int, round: Ceiling|Down|Floor|HalfEven|HalfUp)", + xs + ) + } + val fractionBigInt: BaseFunction[NoContext] = NativeFunction( "fraction", - 128, + Map(V5 -> 128L, V6 -> 1L), FRACTION_BIGINT, BIGINT, ("value", BIGINT), @@ -394,7 +421,7 @@ object PureContext { def fractionBigIntRounds(roundTypes: UNION): BaseFunction[NoContext] = NativeFunction( "fraction", - 128, + Map(V5 -> 128L, V6 -> 1L), FRACTION_BIGINT_ROUNDS, BIGINT, ("value", BIGINT), @@ -970,20 +997,34 @@ object PureContext { } lazy val splitStr: BaseFunction[NoContext] = - NativeFunction("split", Map(V3 -> 100L, V4 -> 75L), SPLIT, listString, ("str", STRING), ("separator", STRING)) { + NativeFunction("split", Map(V3 -> 100L, V4 -> 75L, V5 -> 75L, V6 -> 51L), SPLIT, listString, ("str", STRING), ("separator", STRING)) { case CONST_STRING(str) :: CONST_STRING(sep) :: Nil => ARR(split(str, sep, unicode = false).toIndexedSeq, limited = true) case xs => notImplemented[Id, EVALUATED]("split(str: String, separator: String)", xs) } - lazy val splitStrFixed: BaseFunction[NoContext] = - NativeFunction("split", Map(V3 -> 100L, V4 -> 75L), SPLIT, listString, ("str", STRING), ("separator", STRING)) { - case CONST_STRING(str) :: CONST_STRING(sep) :: Nil => - ARR(split(str, sep, unicode = true).toIndexedSeq, limited = true) + def splitStrFixedF(id: Short, inputLimit: Int, outputLimit: Int, v6Complexity: Long): BaseFunction[NoContext] = { + val name = if (id == SPLIT) "split" else s"split_${v6Complexity}C" + NativeFunction(name, Map(V3 -> 100L, V4 -> 75L, V5 -> 75L, V6 -> v6Complexity), id, listString, ("str", STRING), ("separator", STRING)) { + case (s @ CONST_STRING(str)) :: CONST_STRING(sep) :: Nil => + if (s.weight > inputLimit) + Left(s"Input string size = ${s.weight} bytes exceeds limit = $inputLimit for $name") + else { + val result = split(str, sep, unicode = true).toIndexedSeq + if (result.size > outputLimit) + Left(s"Output list size = ${result.size} exceeds limit = $outputLimit for $name") + else + ARR(result, limited = true) + } case xs => - notImplemented[Id, EVALUATED]("split(str: String, separator: String)", xs) + notImplemented[Id, EVALUATED](s"$name(str: String, separator: String)", xs) } + } + + val splitStrFixed = splitStrFixedF(SPLIT, DataEntryValueMax, MaxListLengthV4, 51) + val splitStr1C = splitStrFixedF(SPLIT1C, 500, 20, 1) + val splitStr4C = splitStrFixedF(SPLIT4C, 6000, 100, 4) private def split(str: String, sep: String, unicode: Boolean): Iterable[CONST_STRING] = { if (str == "") listWithEmptyStr @@ -1017,20 +1058,30 @@ object PureContext { ) } - lazy val makeString: BaseFunction[NoContext] = - NativeFunction("makeString", 30, MAKESTRING, STRING, ("list", LIST(STRING)), ("separator", STRING)) { + def makeStringF(id: Short, complexityV6: Long, inputLimit: Int, outputLimit: Int): BaseFunction[NoContext] = { + val name = if (inputLimit == MaxListLengthV4) "makeString" else s"makeString_${complexityV6}C" + NativeFunction(name, Map(V4 -> 30L, V5 -> 30L, V6 -> complexityV6), id, STRING, ("list", LIST(STRING)), ("separator", STRING)) { case (arr: ARR) :: CONST_STRING(separator) :: Nil => - val separatorStringSize = - if (arr.xs.length > 1) (arr.xs.length - 1) * separator.length - else 0 - val expectedStringSize = arr.elementsWeightSum + separatorStringSize - if (expectedStringSize <= DataEntryValueMax) - CONST_STRING(arr.xs.mkString(separator)) - else - Left(s"Constructing string size = $expectedStringSize bytes will exceed $DataEntryValueMax") + if (arr.xs.length > inputLimit) + Left(s"Input list size = ${arr.xs.length} for $name should not exceed $inputLimit") + else { + val separatorStringSize = + if (arr.xs.length > 1) (arr.xs.length - 1) * separator.length + else 0 + val expectedStringSize = arr.elementsWeightSum + separatorStringSize + if (expectedStringSize <= outputLimit) + CONST_STRING(arr.xs.mkString(separator)) + else + Left(s"Constructing string size = $expectedStringSize bytes will exceed $outputLimit") + } case xs => - notImplemented[Id, EVALUATED]("makeString(list: List[String], separator: String)", xs) + notImplemented[Id, EVALUATED](s"$name(list: List[String], separator: String)", xs) } + } + + val makeString: BaseFunction[NoContext] = makeStringF(MAKESTRING, 11, MaxListLengthV4, DataEntryValueMax) + val makeString1C: BaseFunction[NoContext] = makeStringF(MAKESTRING1C, 1, 70, 500) + val makeString2C: BaseFunction[NoContext] = makeStringF(MAKESTRING2C, 2, 100, 6000) lazy val contains: BaseFunction[NoContext] = UserFunction( @@ -1308,7 +1359,16 @@ object PureContext { } def pow(roundTypes: UNION): BaseFunction[NoContext] = { - NativeFunction("pow", 100, POW, LONG, ("base", LONG), ("bp", LONG), ("exponent", LONG), ("ep", LONG), ("rp", LONG), ("round", roundTypes)) { + NativeFunction("pow", + Map(V3 -> 100L, V4 -> 100L, V5 -> 100L, V6 -> 28L), + POW, + LONG, + ("base", LONG), + ("bp", LONG), + ("exponent", LONG), + ("ep", LONG), + ("rp", LONG), + ("round", roundTypes)) { case CONST_LONG(b) :: CONST_LONG(bp) :: CONST_LONG(e) :: CONST_LONG(ep) :: CONST_LONG(rp) :: round :: Nil => if (bp < 0 || bp > 8 @@ -1318,12 +1378,27 @@ object PureContext { || rp > 8) { Left("pow: scale out of range 0-8") } else { - global.pow(b, bp, e, ep, rp, Rounding.byValue(round)).map(CONST_LONG) + global.pow(b, bp.toInt, e, ep.toInt, rp.toInt, Rounding.byValue(round)).map(CONST_LONG) } case xs => notImplemented[Id, EVALUATED]("pow(base: Int, bp: Int, exponent: Int, ep: Int, rp: Int, round: Rounds)", xs) } } + val sqrtInt: BaseFunction[NoContext] = + UserFunction("sqrt", 2, LONG, ("@number", LONG), ("@precision", LONG), ("@resultPrecision", LONG), ("@round", UNION(fromV5RoundTypes))) { + FUNCTION_CALL( + Native(POW), + List( + REF("@number"), + REF("@precision"), + CONST_LONG(5), + CONST_LONG(1), + REF("@resultPrecision"), + REF("@round") + ) + ) + } + def log(roundTypes: UNION): BaseFunction[NoContext] = { NativeFunction("log", 100, LOG, LONG, ("base", LONG), ("bp", LONG), ("exponent", LONG), ("ep", LONG), ("rp", LONG), ("round", roundTypes)) { case CONST_LONG(b) :: CONST_LONG(bp) :: CONST_LONG(e) :: CONST_LONG(ep) :: CONST_LONG(rp) :: round :: Nil => @@ -1344,7 +1419,7 @@ object PureContext { def powBigInt(roundTypes: UNION): BaseFunction[NoContext] = NativeFunction( "pow", - 200, + Map(V5 -> 200L, V6 -> 270L), POW_BIGINT, BIGINT, ("base", BIGINT), @@ -1361,7 +1436,7 @@ object PureContext { || ep > 18 || rp < 0 || rp > 18) { - Left("pow: scale out of range 0-12") + Left("pow: scale out of range 0-18") } else { global .powBigInt(b, bp, e, ep, rp, Rounding.byValue(round)) @@ -1371,6 +1446,30 @@ object PureContext { case xs => notImplemented[Id, EVALUATED]("pow(base: BigInt, bp: Int, exponent:Big Int, ep: Int, rp: Int, round: Rounds)", xs) } + val sqrtBigInt: BaseFunction[NoContext] = + UserFunction( + "sqrt", + "sqrtBigInt", + 5, + BIGINT, + ("@number", BIGINT), + ("@precision", LONG), + ("@resultPrecision", LONG), + ("@round", UNION(fromV5RoundTypes)) + ) { + FUNCTION_CALL( + Native(POW_BIGINT), + List( + REF("@number"), + REF("@precision"), + CONST_BIGINT(5), + CONST_LONG(1), + REF("@resultPrecision"), + REF("@round") + ) + ) + } + def logBigInt(roundTypes: UNION): BaseFunction[NoContext] = NativeFunction( "log", @@ -1392,7 +1491,7 @@ object PureContext { || ep > 18 || rp < 0 || rp > 18) { - Left("Scale out of range 0-12") + Left("Scale out of range 0-18") } else { global.logBigInt(b, bp, e, ep, rp, Rounding.byValue(round)).map(CONST_BIGINT) } @@ -1402,7 +1501,7 @@ object PureContext { val getListMedian: BaseFunction[NoContext] = NativeFunction("median", 20, MEDIAN_LIST, LONG, ("arr", PARAMETERIZEDLIST(LONG))) { - case xs @ (ARR(arr) :: Nil) => + case xs @ ARR(arr) :: Nil => if (arr.headOption.forall(_.isInstanceOf[CONST_LONG])) { if (arr.nonEmpty) Right(CONST_LONG(global.median(arr.asInstanceOf[IndexedSeq[CONST_LONG]].map(_.t).toArray))) @@ -1416,7 +1515,7 @@ object PureContext { val getBigIntListMedian: BaseFunction[NoContext] = NativeFunction("median", 20 * 8, MEDIAN_LISTBIGINT, BIGINT, ("arr", PARAMETERIZEDLIST(BIGINT))) { - case xs @ (ARR(arr) :: Nil) => + case xs @ ARR(arr) :: Nil => if (arr.headOption.forall(_.isInstanceOf[CONST_BIGINT])) { if (arr.nonEmpty) Right(CONST_BIGINT(global.median(arr.asInstanceOf[IndexedSeq[CONST_BIGINT]].map(_.t).toArray))) @@ -1512,8 +1611,8 @@ object PureContext { STRING ) - private val allRoundTypes: List[CASETYPEREF] = Rounding.all.map(_.`type`) - private val fromV5RoundTypes: List[CASETYPEREF] = Rounding.fromV5.map(_.`type`) + private val allRoundTypes: List[CASETYPEREF] = Rounding.all.map(_.`type`) + private lazy val fromV5RoundTypes: List[CASETYPEREF] = Rounding.fromV5.map(_.`type`) private val v1v2v3v4Types: Seq[REAL] = commonTypes ++ allRoundTypes private val v5Types: Seq[REAL] = commonTypes ++ fromV5RoundTypes ++ Seq(BIGINT) @@ -1682,7 +1781,7 @@ object PureContext { private def v4Functions(fixUnicodeFunctions: Boolean) = if (fixUnicodeFunctions) v4FunctionsFixed else v4FunctionsUnfixed - private val v5Functions = + private val fromV5Functions = v4V5Functions ++ Array( indexOfFixed, @@ -1714,7 +1813,6 @@ object PureContext { listBigIntMin, fractionBigInt, fractionBigIntRounds(UNION(fromV5RoundTypes)), - fractionIntRounds(UNION(fromV5RoundTypes)), negativeBigInt, getBigIntListMedian, powBigInt(UNION(fromV5RoundTypes)), @@ -1724,8 +1822,11 @@ object PureContext { fraction(fixLimitCheck = true), ) + private val v5Functions = + fromV5Functions :+ fractionIntRounds(UNION(fromV5RoundTypes)) + private val v6Functions = - v5Functions ++ folds.map(_._2) ++ Array(sizeTuple) + fromV5Functions ++ folds.map(_._2) ++ Array(sizeTuple, makeString1C, makeString2C, splitStr1C, splitStr4C, sqrtInt, sqrtBigInt, fractionIntRoundsNative) private def v1V2Ctx(fixUnicodeFunctions: Boolean) = CTX[NoContext]( diff --git a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/waves/Functions.scala b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/waves/Functions.scala index 610f91d0a57..2e682db803a 100644 --- a/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/waves/Functions.scala +++ b/lang/shared/src/main/scala/com/wavesplatform/lang/v1/evaluator/ctx/impl/waves/Functions.scala @@ -367,7 +367,7 @@ object Functions { val stringFromAddressF: BaseFunction[Environment] = NativeFunction( "toString", - 10, + Map(V3 -> 10L, V4 -> 10L, V5 -> 10L, V6 -> 1L), ADDRESSTOSTRING, STRING, ("Address", addressType) diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/IntegrationTest.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/IntegrationTest.scala index 948a801c84b..dd453957bfc 100755 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/IntegrationTest.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/IntegrationTest.scala @@ -1,5 +1,7 @@ package com.wavesplatform.lang +import java.nio.charset.StandardCharsets + import cats.Id import cats.kernel.Monoid import cats.syntax.either._ @@ -29,7 +31,6 @@ import com.wavesplatform.test._ import org.scalatest.Inside import org.web3j.crypto.Keys -import java.nio.charset.StandardCharsets import scala.util.{Random, Try} class IntegrationTest extends PropSpec with Inside { @@ -1411,8 +1412,8 @@ class IntegrationTest extends PropSpec with Inside { eval(constructingMaxStringAndBytes, version = V3) shouldBe CONST_BYTESTR(ByteStr(maxBytes)) eval(constructingMaxStringAndBytes, version = V4) shouldBe CONST_BYTESTR(ByteStr(maxBytes)) - eval(constructingTooBigString, version = V3) should produce("String length = 32768 exceeds 32767") - eval(constructingTooBigString, version = V4) should produce("String length = 32768 exceeds 32767") + eval(constructingTooBigString, version = V3) should produce("String size = 32768 exceeds 32767 bytes") + eval(constructingTooBigString, version = V4) should produce("String size = 32768 exceeds 32767 bytes") } property("bytes limit") { diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/BigIntTest.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/BigIntTest.scala index 36135cc3d1e..4650eca4c50 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/BigIntTest.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/BigIntTest.scala @@ -1,7 +1,7 @@ package com.wavesplatform.lang.evaluator import com.wavesplatform.common.utils.EitherExt2 -import com.wavesplatform.lang.directives.values.{StdLibVersion, V5} +import com.wavesplatform.lang.directives.values.{StdLibVersion, V5, V6} import com.wavesplatform.lang.v1.compiler.Terms.{CONST_BIGINT, CONST_BOOLEAN, CONST_LONG, CONST_STRING} import com.wavesplatform.lang.v1.evaluator.ctx.impl.{PureContext, unit} import com.wavesplatform.test.produce @@ -267,5 +267,7 @@ class BigIntTest extends EvaluatorSpec { property("sqrt") { eval(s"pow($max, 0, toBigInt(5), 1, 18, DOWN)") shouldBe Right(CONST_BIGINT(BigInt("81877371507464127617551201542979628307507432471243237061821853600756754782485292915524036944801"))) eval(s"pow($max, 18, toBigInt(5), 1, 18, DOWN)") shouldBe Right(CONST_BIGINT(BigInt("81877371507464127617551201542979628307507432471243237061821853600756754782485292915524"))) + eval(s"sqrt($max, 0, 18, DOWN)")(V6) shouldBe Right(CONST_BIGINT(BigInt("81877371507464127617551201542979628307507432471243237061821853600756754782485292915524036944801"))) + eval(s"sqrt($max, 18, 18, DOWN)")(V6) shouldBe Right(CONST_BIGINT(BigInt("81877371507464127617551201542979628307507432471243237061821853600756754782485292915524"))) } } diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/FractionIntRoundsTest.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/FractionIntRoundsTest.scala index dfbef046483..9ff917e3c58 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/FractionIntRoundsTest.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/FractionIntRoundsTest.scala @@ -1,7 +1,7 @@ package com.wavesplatform.lang.evaluator import com.wavesplatform.test._ import com.wavesplatform.lang.directives.DirectiveDictionary -import com.wavesplatform.lang.directives.values.{StdLibVersion, V5} +import com.wavesplatform.lang.directives.values.{StdLibVersion, V5, V6} import com.wavesplatform.lang.v1.compiler.Terms.CONST_LONG import com.wavesplatform.lang.v1.evaluator.ctx.impl.Rounding._ @@ -12,7 +12,10 @@ class FractionIntRoundsTest extends EvaluatorSpec { private val min = Long.MinValue property("fraction with long limits") { - eval(s"fraction($max, $min, 1, HALFEVEN)") should produce("out of integers range") + val limitError = "-85070591730234615856620279821087277056 out of integers range" + eval(s"fraction($max, $min, 1, HALFEVEN)")(V5, checkNext = false) should produce("toInt: BigInt " + limitError) + eval(s"fraction($max, $min, 1, HALFEVEN)")(V6) should produce("Fraction result " + limitError) + eval(s"fraction($max, $min, $min, HALFEVEN)") shouldBe Right(CONST_LONG(max)) eval(s"fraction(1, $min, 1, HALFEVEN)") shouldBe Right(CONST_LONG(min)) } @@ -22,11 +25,11 @@ class FractionIntRoundsTest extends EvaluatorSpec { eval(s"fraction($max, $min, $min)") shouldBe Right(CONST_LONG(max)) eval(s"fraction(1, $min, 1)") shouldBe Right(CONST_LONG(min)) - DirectiveDictionary[StdLibVersion].all.filter(_ < V5) - .foreach { - v => - eval(s"fraction($max, $min, $min)")(v, checkNext = false) should produce("Long overflow") - eval(s"fraction(1, $min, 1)")(v, checkNext = false) should produce("Long overflow") + DirectiveDictionary[StdLibVersion].all + .filter(_ < V5) + .foreach { v => + eval(s"fraction($max, $min, $min)")(v, checkNext = false) should produce("Long overflow") + eval(s"fraction(1, $min, 1)")(v, checkNext = false) should produce("Long overflow") } } @@ -38,9 +41,9 @@ class FractionIntRoundsTest extends EvaluatorSpec { property("fraction roundings") { // https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/math/RoundingMode.html val exprs = List[String => String]( - r => s"fraction(5, 1, 2, $r)", // 2.5 - r => s"fraction(2, 4, 5, $r)", // 1.6 - r => s"fraction(-2, 4, 5, $r)", // -1.6 + r => s"fraction(5, 1, 2, $r)", // 2.5 + r => s"fraction(2, 4, 5, $r)", // 1.6 + r => s"fraction(-2, 4, 5, $r)", // -1.6 r => s"fraction(-5, 11, 10, $r)" // -5.5 ) val resultByRounding = Map( diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/MakeStringTest.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/MakeStringTest.scala new file mode 100644 index 00000000000..554c2f22b9e --- /dev/null +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/MakeStringTest.scala @@ -0,0 +1,58 @@ +package com.wavesplatform.lang.evaluator + +import com.wavesplatform.common.utils.EitherExt2 +import com.wavesplatform.lang.directives.values.{StdLibVersion, V4, V6} +import com.wavesplatform.lang.v1.compiler.Terms.{CONST_BOOLEAN, CONST_STRING} +import com.wavesplatform.lang.v1.evaluator.ctx.impl.PureContext +import com.wavesplatform.test.produce + +class MakeStringTest extends EvaluatorSpec { + implicit val v: StdLibVersion = V6 + + property("correct results") { + for (f <- List(PureContext.makeString, PureContext.makeString1C, PureContext.makeString2C)) { + eval(s""" ${f.name}(["cat", "dog", "pig"], ", ") """) shouldBe CONST_STRING("cat, dog, pig") + eval(s""" ${f.name}([], ", ") """) shouldBe CONST_STRING("") + eval(s""" ${f.name}(["abc"], ", ") == "abc" """) shouldBe Right(CONST_BOOLEAN(true)) + } + } + + property("makeString limit") { + implicit val v: StdLibVersion = V4 + + val script = s""" [${s""" "${"a" * 1000}", """ * 32} "${"a" * 704}"].makeString(", ") """ + eval(script) should produce("Constructing string size = 32768 bytes will exceed 32767") + // 1000 * 32 + 704 + 2 * 32 = 32768 + + val script2 = s""" [${s""" "${"a" * 1000}", """ * 32} "${"a" * 703}"].makeString(", ") """ + eval(script2).explicitGet().asInstanceOf[CONST_STRING].s.length shouldBe 32767 + } + + property("makeString function family input limit") { + for ((f, limit) <- List((PureContext.makeString1C, 70), (PureContext.makeString2C, 100))) { + val script = s""" ${f.name}([${s""" "${"a" * 5}", """ * limit} "a"], ", ") """ + eval(script) should produce(s"Input list size = ${limit + 1} for ${f.name} should not exceed $limit") + + val script2 = s""" ${f.name}([${s""" "${"a" * 5}", """ * (limit - 1)} "a"], ", ") """ + eval(script2) shouldBe a [Right[_, _]] + } + } + + property("makeString_1C output limit") { + val script = s""" makeString_1C([${s""" "${"a" * 10}", """ * 42} "a"], ", ") """ + eval(script) should produce("Constructing string size = 505 bytes will exceed 500") + // 10 * 42 + 1 + 2 * 42 = 505 + + val script2 = s""" makeString_1C([${s""" "${"a" * 10}", """ * 41} "a"], ", ") """ + eval(script2).explicitGet().asInstanceOf[CONST_STRING].s.length shouldBe 493 + } + + property("makeString_2C output limit") { + val script = s""" makeString_2C([${s""" "${"a" * 59}", """ * 99} "a"], ", ") """ + eval(script) should produce("Constructing string size = 6040 bytes will exceed 6000") + // 59 * 99 + 1 + 2 * 99 = 6040 + + val script2 = s""" makeString_2C([${s""" "${"a" * 58}", """ * 99} "a"], ", ") """ + eval(script2).explicitGet().asInstanceOf[CONST_STRING].s.length shouldBe 5941 + } +} diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/MathFunctionsTest.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/MathFunctionsTest.scala index 1dcc1d4d82b..09460149ebb 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/MathFunctionsTest.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/MathFunctionsTest.scala @@ -1,5 +1,5 @@ package com.wavesplatform.lang.evaluator -import com.wavesplatform.lang.directives.values.{StdLibVersion, V3, V5} +import com.wavesplatform.lang.directives.values.{StdLibVersion, V3, V5, V6} import com.wavesplatform.lang.v1.compiler.Terms.CONST_LONG import com.wavesplatform.test.produce @@ -71,5 +71,7 @@ class MathFunctionsTest extends EvaluatorSpec { property("sqrt") { eval(s"pow(${Long.MaxValue}, 0, 5, 1, 8, DOWN)") shouldBe Right(CONST_LONG(303700049997604969L)) eval(s"pow(${Long.MaxValue}, 8, 5, 1, 8, DOWN)") shouldBe Right(CONST_LONG(30370004999760L)) + eval(s"sqrt(${Long.MaxValue}, 0, 8, DOWN)")(V6) shouldBe Right(CONST_LONG(303700049997604969L)) + eval(s"sqrt(${Long.MaxValue}, 8, 8, DOWN)")(V6) shouldBe Right(CONST_LONG(30370004999760L)) } -} \ No newline at end of file +} diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/SplitFunctionTest.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/SplitFunctionTest.scala index a5049c6e312..f196c8c60c0 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/SplitFunctionTest.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/SplitFunctionTest.scala @@ -1,167 +1,180 @@ package com.wavesplatform.lang.evaluator -import com.wavesplatform.lang.directives.values.{StdLibVersion, V3} +import com.wavesplatform.lang.directives.values.{V3, V6} import com.wavesplatform.lang.v1.compiler.Terms.CONST_BOOLEAN +import com.wavesplatform.lang.v1.evaluator.Contextful.NoContext +import com.wavesplatform.lang.v1.evaluator.ctx.BaseFunction +import com.wavesplatform.lang.v1.evaluator.ctx.impl.PureContext class SplitFunctionTest extends EvaluatorSpec { - implicit val startVersion: StdLibVersion = V3 + private def assertSuccess(script: String => String): Unit = + for ((f, v) <- Seq((PureContext.splitStrFixed, V3), (PureContext.splitStr1C, V6), (PureContext.splitStr4C, V6))) { + eval(script(f.name))(v) shouldBe Right(CONST_BOOLEAN(true)) + } property("split string containing separators") { - val script = - s""" - | let strs = split("str1;str2;str3;str4", ";") - | strs.size() == 4 && - | strs[0] == "str1" && - | strs[1] == "str2" && - | strs[2] == "str3" && - | strs[3] == "str4" - | - """.stripMargin - - eval(script) shouldBe Right(CONST_BOOLEAN(true)) + assertSuccess( + f => s""" + | let strs = $f("str1;str2;str3;str4", ";") + | strs.size() == 4 && + | strs[0] == "str1" && + | strs[1] == "str2" && + | strs[2] == "str3" && + | strs[3] == "str4" + """.stripMargin + ) } property("split around empty string") { - val script = - s""" - | let strs = split("some", "") - | strs.size() == 4 && - | strs[0] == "s" && - | strs[1] == "o" && - | strs[2] == "m" && - | strs[3] == "e" - | - """.stripMargin - - eval(script) shouldBe Right(CONST_BOOLEAN(true)) + assertSuccess( + f => s""" + | let strs = $f("some", "") + | strs.size() == 4 && + | strs[0] == "s" && + | strs[1] == "o" && + | strs[2] == "m" && + | strs[3] == "e" + | + """.stripMargin + ) } property("splitted string containing empty strings") { - val script = - s""" - | let strs1 = split(";;some;", ";") - | let result1 = strs1.size() == 4 && - | strs1[0] == "" && - | strs1[1] == "" && - | strs1[2] == "some" && - | strs1[3] == "" - | - | let strs2 = split(";Q;Qsome;Q", ";Q") - | let result2 = strs2.size() == 4 && - | strs2[0] == "" && - | strs2[1] == "" && - | strs2[2] == "some" && - | strs2[3] == "" - | - | let strs3 = split("QQ;someQ;Q;;", "Q;") - | let result3 = strs3.size() == 4 && - | strs3[0] == "Q" && - | strs3[1] == "some" && - | strs3[2] == "" && - | strs3[3] == ";" - | - | let strs4 = split("q;Q;someQ;Q;;", "Q;") - | let result4 = strs4.size() == 4 && - | strs4[0] == "q;" && - | strs4[1] == "some" && - | strs4[2] == "" && - | strs4[3] == ";" - | - | result1 && result2 && result3 && result4 - | - """.stripMargin - - eval(script) shouldBe Right(CONST_BOOLEAN(true)) + assertSuccess( + f => s""" + | let strs1 = $f(";;some;", ";") + | let result1 = strs1.size() == 4 && + | strs1[0] == "" && + | strs1[1] == "" && + | strs1[2] == "some" && + | strs1[3] == "" + | + | let strs2 = $f(";Q;Qsome;Q", ";Q") + | let result2 = strs2.size() == 4 && + | strs2[0] == "" && + | strs2[1] == "" && + | strs2[2] == "some" && + | strs2[3] == "" + | + | let strs3 = $f("QQ;someQ;Q;;", "Q;") + | let result3 = strs3.size() == 4 && + | strs3[0] == "Q" && + | strs3[1] == "some" && + | strs3[2] == "" && + | strs3[3] == ";" + | + | let strs4 = $f("q;Q;someQ;Q;;", "Q;") + | let result4 = strs4.size() == 4 && + | strs4[0] == "q;" && + | strs4[1] == "some" && + | strs4[2] == "" && + | strs4[3] == ";" + | + | result1 && result2 && result3 && result4 + """.stripMargin + ) } property("split around unexisting separator") { - val script = - s""" - | let str = "a;b;c" - | let splitted = split(str, ",") - | splitted.size() == 1 && - | splitted[0] == str - | - """.stripMargin - - eval(script) shouldBe Right(CONST_BOOLEAN(true)) + assertSuccess( + f => s""" + | let str = "a;b;c" + | let splitted = $f(str, ",") + | splitted.size() == 1 && + | splitted[0] == str + | + """.stripMargin + ) } property("split empty string") { - val script = - s""" - | let strs1 = split("", ",") - | let strs2 = split("", ",,,") - | let strs3 = split("", "") - | - | strs1.size() == 1 && - | strs2.size() == 1 && - | strs3.size() == 1 && - | strs1[0] == "" && - | strs2[0] == "" && - | strs3[0] == "" - | - """.stripMargin - - eval(script) shouldBe Right(CONST_BOOLEAN(true)) + assertSuccess( + f => s""" + | let strs1 = $f("", ",") + | let strs2 = $f("", ",,,") + | let strs3 = $f("", "") + | + | strs1.size() == 1 && + | strs2.size() == 1 && + | strs3.size() == 1 && + | strs1[0] == "" && + | strs2[0] == "" && + | strs3[0] == "" + | + """.stripMargin + ) } property("split separator around separator") { - val script = - s""" - | let strs1 = split(",", ",") - | let strs2 = split(",x,", ",x,") - | - | strs1.size() == 2 && - | strs1[0] == "" && - | strs1[1] == "" && - | strs2.size() == 2 && - | strs2[0] == "" && - | strs2[1] == "" - | - """.stripMargin - - eval(script) shouldBe Right(CONST_BOOLEAN(true)) + assertSuccess( + f => s""" + | let strs1 = $f(",", ",") + | let strs2 = $f(",x,", ",x,") + | + | strs1.size() == 2 && + | strs1[0] == "" && + | strs1[1] == "" && + | strs2.size() == 2 && + | strs2[0] == "" && + | strs2[1] == "" + | + """.stripMargin + ) } property("split string containing only separators") { val sep = ";;;" val count = 10 - val script = - s""" - | let strs = "${sep * count}".split("$sep") - | strs.size() == ${count + 1} && - | ${(0 to count).map(i => s"""strs[$i] == "" """).mkString(" && ")} - | - """.stripMargin - - eval(script) shouldBe Right(CONST_BOOLEAN(true)) + assertSuccess( + f => s""" + | let strs = $f("${sep * count}", "$sep") + | strs.size() == ${count + 1} && + | ${(0 to count).map(i => s"""strs[$i] == "" """).mkString(" && ")} + """.stripMargin + ) } property("split perceives regex as plain text") { val strContainingRegex = "aaa1bbb2ccc" val regex = "[12]+" strContainingRegex.split(regex) shouldBe Array("aaa", "bbb", "ccc") + assertSuccess( + f => s""" + | let regex = "$regex" + | + | let strContainingRegex = "$strContainingRegex" + | let splitted1 = $f(strContainingRegex, regex) + | let result1 = splitted1.size() == 1 && + | splitted1[0] == strContainingRegex + | + | let strContainingRegexText = "aaa${regex}bbb" + | let splitted2 = $f(strContainingRegexText, regex) + | let result2 = splitted2.size() == 2 && + | splitted2[0] == "aaa" && + | splitted2[1] == "bbb" + | + | result1 && result2 + """.stripMargin + ) + } - val script = - s""" - | let regex = "$regex" - | - | let strContainingRegex = "$strContainingRegex" - | let splitted1 = split(strContainingRegex, regex) - | let result1 = splitted1.size() == 1 && - | splitted1[0] == strContainingRegex - | - | let strContainingRegexText = "aaa${regex}bbb" - | let splitted2 = split(strContainingRegexText, regex) - | let result2 = splitted2.size() == 2 && - | splitted2[0] == "aaa" && - | splitted2[1] == "bbb" - | - | result1 && result2 - | - """.stripMargin + property("function family input limits") { + val count = 100 + val elem = "a" * count + def str(f: BaseFunction[NoContext], limit: Int, c: Int) = s""" ${f.name}("${s"$elem," * (limit / c)}$elem", ",") """ + for ((f, limit) <- List((PureContext.splitStr1C, 500), (PureContext.splitStr4C, 6000))) { + val actualSize = limit / (count + 1) * (count + 1) + count + eval(str(f, limit, count + 1))(V6) shouldBe Left(s"Input string size = $actualSize bytes exceeds limit = $limit for ${f.name}") + eval(str(f, limit, count * 2))(V6) shouldBe a[Right[_, _]] + } + } - eval(script) shouldBe Right(CONST_BOOLEAN(true)) + property("function family output limits") { + val elem = "a" + def str(f: BaseFunction[NoContext], n: Int) = s""" ${f.name}("${s"$elem," * (n - 1)}$elem", ",") """ + for ((f, limit) <- List((PureContext.splitStr1C, 20), (PureContext.splitStr4C, 100))) { + eval(str(f, limit + 1))(V6) shouldBe Left(s"Output list size = ${limit + 1} exceeds limit = $limit for ${f.name}") + eval(str(f, limit))(V6) shouldBe a[Right[_, _]] + } } } diff --git a/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/StringFunctionsTest.scala b/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/StringFunctionsTest.scala index 901054282ae..ebeb81e1fc2 100644 --- a/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/StringFunctionsTest.scala +++ b/lang/tests/src/test/scala/com/wavesplatform/lang/evaluator/StringFunctionsTest.scala @@ -1,10 +1,8 @@ package com.wavesplatform.lang.evaluator -import com.wavesplatform.common.utils.EitherExt2 import com.wavesplatform.lang.directives.values.{StdLibVersion, V3, V4} import com.wavesplatform.lang.v1.compiler.Terms.{CONST_BOOLEAN, CONST_LONG, CONST_STRING} import com.wavesplatform.lang.v1.evaluator.ctx.impl.unit -import com.wavesplatform.test.produce class StringFunctionsTest extends EvaluatorSpec { property("take") { @@ -154,18 +152,4 @@ class StringFunctionsTest extends EvaluatorSpec { eval(""" "qwerty".contains("xx") """) shouldBe Right(CONST_BOOLEAN(false)) eval(s""" "${"a" * Short.MaxValue}".contains("${"a" * Short.MaxValue}") """) shouldBe Right(CONST_BOOLEAN(true)) } - - property("makeString") { - implicit val v: StdLibVersion = V4 - eval(""" ["cat", "dog", "pig"].makeString(", ") """) shouldBe CONST_STRING("cat, dog, pig") - eval(""" [].makeString(", ") """) shouldBe CONST_STRING("") - eval(""" ["abc"].makeString(", ") == "abc" """) shouldBe Right(CONST_BOOLEAN(true)) - - val script = s""" [${s""" "${"a" * 1000}", """ * 32} "${"a" * 704}"].makeString(", ") """ - eval(script) should produce("Constructing string size = 32768 bytes will exceed 32767") - // 1000 * 32 + 704 + 2 * 32 = 32768 - - val script2 = s""" [${s""" "${"a" * 1000}", """ * 32} "${"a" * 703}"].makeString(", ") """ - eval(script2).explicitGet().asInstanceOf[CONST_STRING].s.length shouldBe 32767 - } } diff --git a/node-it/src/test/scala/com/wavesplatform/it/sync/smartcontract/BigStringSuite.scala b/node-it/src/test/scala/com/wavesplatform/it/sync/smartcontract/BigStringSuite.scala index a65151d27cb..cccf4b886a8 100644 --- a/node-it/src/test/scala/com/wavesplatform/it/sync/smartcontract/BigStringSuite.scala +++ b/node-it/src/test/scala/com/wavesplatform/it/sync/smartcontract/BigStringSuite.scala @@ -73,7 +73,7 @@ class BigStringSuite extends BaseTransactionSuite with CancelAfterFailure { val signedLeasing = unsignedLeasing.copy(proofs = Proofs(Seq(sigLeasingA, ByteStr.empty, sigLeasingC))) - assertBadRequestAndMessage(sender.signedBroadcast(signedLeasing.json()).id, "String size=32768 exceeds 32767 bytes") + assertBadRequestAndMessage(sender.signedBroadcast(signedLeasing.json()).id, "String size = 32768 exceeds 32767 bytes") val leasingId = Base58.encode(unsignedLeasing.id().arr) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 6eccade0f7c..43c637dee5a 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -41,7 +41,10 @@ object Dependencies { val sttp3 = "com.softwaremill.sttp.client3" % "core_2.13" % "3.3.13" - val bouncyCastleProvider = "org.bouncycastle" % s"bcprov-jdk15on" % "1.69" + // v1.67 introduced unnecessary conversions which slowed down hash computation by a factor of 3-4: + // https://github.com/bcgit/bc-java/blob/r1rv67/core/src/main/java/org/bouncycastle/crypto/digests/KeccakDigest.java#L318 + // Before upping the version, make sure conversions are no longer there. + val bouncyCastleProvider = "org.bouncycastle" % s"bcprov-jdk15on" % "1.66" val enforcedVersions = Def.setting( Seq( diff --git a/project/plugins.sbt b/project/plugins.sbt index 8927876def0..09d4543927d 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -25,4 +25,4 @@ libraryDependencies ++= Seq( "org.slf4j" % "jcl-over-slf4j" % "1.7.30", ("com.spotify" % "docker-client" % "8.16.0") .exclude("commons-logging", "commons-logging") -) +) \ No newline at end of file