From 0c8eb320b38416b0724ec4d2393495dbea51c75c Mon Sep 17 00:00:00 2001 From: Denys Zadorozhnyi Date: Tue, 22 Jun 2021 15:34:15 +0300 Subject: [PATCH 01/56] add ProveDHTuple test vectors to SigningSpecification; --- .../crypto/SigningSpecification.scala | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/sigmastate/src/test/scala/sigmastate/crypto/SigningSpecification.scala b/sigmastate/src/test/scala/sigmastate/crypto/SigningSpecification.scala index 9390e1b4b3..5d880dc4c5 100644 --- a/sigmastate/src/test/scala/sigmastate/crypto/SigningSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/crypto/SigningSpecification.scala @@ -7,6 +7,10 @@ import sigmastate.Values.SigmaBoolean import sigmastate.basics.DLogProtocol.DLogProverInput import sigmastate.helpers.{ErgoLikeTestInterpreter, ErgoLikeTestProvingInterpreter, SigmaTestingCommons} import sigmastate.interpreter.{ContextExtension, HintsBag, ProverResult} +import sigmastate.serialization.ValueSerializer +import sigmastate.serialization.transformers.ProveDHTupleSerializer +import sigmastate.lang.StdSigmaBuilder +import sigmastate.basics.ProveDHTuple class SigningSpecification extends SigmaTestingCommons { implicit lazy val IR: TestingIRContext = new TestingIRContext @@ -29,6 +33,17 @@ class SigningSpecification extends SigmaTestingCommons { printSimpleSignature(msg: Array[Byte]) } + property("ProveDHT signature test vector") { + // test vector data is generated by prover and tested in signing_spec::sig_test_vector_prove_dht + val msg = Base16.decode("1dc01772ee0171f5f614c673e3c7fa1107a8cf727bdf5a6dadb379e93c0d1d00").get + val pdht = ProveDHTupleSerializer(ProveDHTuple.apply).fromBytes(Base16.decode(input = "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817980280c66feee88d56e47bf3f47c4109d9218c60c373a472a0d9537507c7ee828c4802a96f19e97df31606183c1719400682d1d40b1ce50c9a1ed1b19845e2b1b551bf0255ac02191cb229891fb1b674ea9df7fc8426350131d821fc4a53f29c3b1cb21a").get) + val signature = Base16.decode("eba93a69b28cfdea261e9ea8914fca9a0b3868d50ce68c94f32e875730f8ca361bd3783c5d3e25802e54f49bd4fb9fafe51f4e8aafbf9815").get + // check that signature is correct + val verifier = new ErgoLikeTestInterpreter + val proverResult = ProverResult(signature, ContextExtension.empty) + verifier.verify(pdht, fakeContext, proverResult, msg).get._1 shouldBe true + } + property("handle improper signature") { val pi = new ErgoLikeTestProvingInterpreter() val sigmaTree: SigmaBoolean = pi.publicKeys.head @@ -62,6 +77,19 @@ class SigningSpecification extends SigmaTestingCommons { verifier.verify(sigmaTree, fakeContext, proverResult, msg).get._1 shouldBe true } + property("OR with ProveDHT signature test vector") { + // test vector data is generated by prover and tested in signing_spec::sig_test_vector_conj_or_prove_dht + val msg = Base16.decode("1dc01772ee0171f5f614c673e3c7fa1107a8cf727bdf5a6dadb379e93c0d1d00").get + val sk1 = DLogProverInput(BigInt("109749205800194830127901595352600384558037183218698112947062497909408298157746").bigInteger) + val pdht = ProveDHTupleSerializer(ProveDHTuple.apply).fromBytes(Base16.decode(input = "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817980214487635ebffa60b13a166bd0721c5f0ab603fc74168d7764d7ec5ef2107f5d40334c5b7efa5a4a22b83d102d2e6521eaa660fa911c5a213af63c8460f2327513b026a0be2a277291d42daad3830cb16a4ef20e4f1f7c36384f3fee065f0f143a355").get) + val signature = Base16.decode("a80daebdcd57874296f49fd9910ddaefbf517ca076b6e16b97678e96a20239978836e7ec5b795cf3a55616d394f07c004f85e0d3e71880d4734b57ea874c7eba724e8887280f1affadaad962ee916b39207af2d2ab2a69a2e6f4d652f7389cc4f582bbe6d7937c59aa64cf2965a8b36a").get + // check that signature is correct + val verifier = new ErgoLikeTestInterpreter + val proverResult = ProverResult(signature, ContextExtension.empty) + val sigmaTree: SigmaBoolean = COR(Seq(sk1.publicImage, pdht)) + verifier.verify(sigmaTree, fakeContext, proverResult, msg).get._1 shouldBe true + } + property("AND with OR signature test vector") { val msg = Base16.decode("1dc01772ee0171f5f614c673e3c7fa1107a8cf727bdf5a6dadb379e93c0d1d00").get val sk1 = DLogProverInput(BigInt("109749205800194830127901595352600384558037183218698112947062497909408298157746").bigInteger) From e5127f6743db824f7280881cd5c4ecd336075e2f Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Fri, 25 Jun 2021 15:28:47 +0300 Subject: [PATCH 02/56] prepare-v5.0: part 3: TODO optimize --- .../src/main/scala/org/ergoplatform/ErgoLikeContext.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeContext.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeContext.scala index 22edda7ab0..aa59a85d67 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeContext.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeContext.scala @@ -129,7 +129,7 @@ class ErgoLikeContext(val lastBlockUtxoRoot: AvlTreeData, import Evaluation._ def contextVars(m: Map[Byte, AnyValue]): Coll[AnyValue] = { - val maxKey = if (m.keys.isEmpty) 0 else m.keys.max + val maxKey = if (m.keys.isEmpty) 0 else m.keys.max // TODO optimize: max takes 90% of this method val res = new Array[AnyValue](maxKey + 1) for ((id, v) <- m) { res(id) = v From ae03f0d6f2a11ea37d9812c49e1ed218c79f2139 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Fri, 25 Jun 2021 15:31:23 +0300 Subject: [PATCH 03/56] prepare-v5.0: part 3: CompilerSettings added --- .../ergoplatform/ErgoLikeInterpreter.scala | 2 +- .../org/ergoplatform/ErgoScriptPredef.scala | 5 ++-- .../scala/sigmastate/lang/SigmaCompiler.scala | 20 +++++++++++---- .../src/test/scala/sigmastate/TestsBase.scala | 25 +++++++++++++++++-- .../sigmastate/eval/ErgoScriptTestkit.scala | 10 +++++--- .../sigmastate/lang/SigmaParserTest.scala | 5 +++- .../scala/sigmastate/utxo/SigmaContract.scala | 5 ++-- 7 files changed, 56 insertions(+), 16 deletions(-) diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeInterpreter.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeInterpreter.scala index 57fc66853e..7e54647a06 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeInterpreter.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeInterpreter.scala @@ -40,5 +40,5 @@ class ErgoLikeInterpreter(implicit val IR: IRContext) extends Interpreter { }.orElse(d.default) case _ => super.substDeserialize(context, updateContext, node) } - + } \ No newline at end of file diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoScriptPredef.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoScriptPredef.scala index 6699cb1d91..049ceb9029 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/ErgoScriptPredef.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoScriptPredef.scala @@ -9,7 +9,7 @@ import sigmastate.eval.IRContext import sigmastate.interpreter.CryptoConstants import sigmastate.lang.Terms.ValueOps import sigmastate.{SLong, _} -import sigmastate.lang.{SigmaCompiler, TransformingSigmaBuilder} +import sigmastate.lang.{TransformingSigmaBuilder, SigmaCompiler, CompilerSettings} import sigmastate.serialization.ErgoTreeSerializer.DefaultSerializer import sigmastate.utxo._ @@ -18,7 +18,8 @@ object ErgoScriptPredef { import sigmastate.interpreter.Interpreter._ def compileWithCosting(env: ScriptEnv, code: String, networkPrefix: NetworkPrefix)(implicit IR: IRContext): Value[SType] = { - val compiler = new SigmaCompiler(networkPrefix, TransformingSigmaBuilder) + val compiler = new SigmaCompiler(CompilerSettings( + networkPrefix, TransformingSigmaBuilder, lowerMethodCalls = true)) val interProp = compiler.typecheck(env, code) val IR.Pair(calcF, _) = IR.doCosting(env, interProp) IR.buildTree(calcF) diff --git a/sigmastate/src/main/scala/sigmastate/lang/SigmaCompiler.scala b/sigmastate/src/main/scala/sigmastate/lang/SigmaCompiler.scala index d41c2c1220..70ba8007db 100644 --- a/sigmastate/src/main/scala/sigmastate/lang/SigmaCompiler.scala +++ b/sigmastate/src/main/scala/sigmastate/lang/SigmaCompiler.scala @@ -11,10 +11,20 @@ import sigmastate.lang.SigmaPredef.PredefinedFuncRegistry import sigmastate.lang.syntax.ParserException /** - * @param networkPrefix network prefix to decode an ergo address from string (PK op) - * @param builder + * @param networkPrefix network prefix to decode an ergo address from string (PK op) + * @param builder used to create ErgoTree nodes + * @param lowerMethodCalls if true, then MethodCall nodes are lowered to ErgoTree nodes + * when [[sigmastate.SMethod.irInfo.irBuilder]] is defined */ -class SigmaCompiler(networkPrefix: NetworkPrefix, builder: SigmaBuilder) { +case class CompilerSettings( + networkPrefix: NetworkPrefix, + builder: SigmaBuilder, + lowerMethodCalls: Boolean +) + +class SigmaCompiler(settings: CompilerSettings) { + @inline final def builder = settings.builder + @inline final def networkPrefix = settings.networkPrefix def parse(x: String): SValue = { SigmaParser(x, builder) match { @@ -53,6 +63,6 @@ class SigmaCompiler(networkPrefix: NetworkPrefix, builder: SigmaBuilder) { } object SigmaCompiler { - def apply(networkPrefix: NetworkPrefix, builder: SigmaBuilder = TransformingSigmaBuilder): SigmaCompiler = - new SigmaCompiler(networkPrefix, builder) + def apply(settings: CompilerSettings): SigmaCompiler = + new SigmaCompiler(settings) } diff --git a/sigmastate/src/test/scala/sigmastate/TestsBase.scala b/sigmastate/src/test/scala/sigmastate/TestsBase.scala index dfb1619cac..48ba284840 100644 --- a/sigmastate/src/test/scala/sigmastate/TestsBase.scala +++ b/sigmastate/src/test/scala/sigmastate/TestsBase.scala @@ -7,7 +7,7 @@ import sigmastate.Values.{SValue, Value, SigmaPropValue, ErgoTree, SigmaBoolean} import sigmastate.eval.IRContext import sigmastate.interpreter.Interpreter import sigmastate.interpreter.Interpreter.ScriptEnv -import sigmastate.lang.{SigmaCompiler, TransformingSigmaBuilder} +import sigmastate.lang.{TransformingSigmaBuilder, SigmaCompiler, CompilerSettings} import sigmastate.lang.Terms.ValueOps import sigmastate.serialization.ValueSerializer @@ -49,7 +49,28 @@ trait TestsBase extends Matchers { def mkTestErgoTree(prop: SigmaBoolean): ErgoTree = ErgoTree.fromSigmaBoolean(ergoTreeHeaderInTests, prop) - lazy val compiler = SigmaCompiler(TestnetNetworkPrefix, TransformingSigmaBuilder) + protected val _lowerMethodCalls = new DynamicVariable[Boolean](true) + + /** Returns true if MethodCall nodes should be lowered by TypeChecker to the + * corresponding ErgoTree nodes. E.g. xs.map(f) --> MapCollection(xs, f). + * NOTE: The value of the flag is assigned dynamically using _lowerMethodCalls + * DynamicVariable. */ + def lowerMethodCallsInTests: Boolean = _lowerMethodCalls.value + + /** If true, then all suite properties are executed with _lowerMethodCalls set to false. + * This allow to test execution of MethodCall nodes in ErgoTree. + */ + val okRunTestsWithoutMCLowering: Boolean = false + + val defaultCompilerSettings: CompilerSettings = CompilerSettings( + TestnetNetworkPrefix, TransformingSigmaBuilder, + lowerMethodCalls = true + ) + + def compilerSettingsInTests: CompilerSettings = + defaultCompilerSettings.copy(lowerMethodCalls = lowerMethodCallsInTests) + + def compiler = SigmaCompiler(compilerSettingsInTests) def checkSerializationRoundTrip(v: SValue): Unit = { val compiledTreeBytes = ValueSerializer.serialize(v) diff --git a/sigmastate/src/test/scala/sigmastate/eval/ErgoScriptTestkit.scala b/sigmastate/src/test/scala/sigmastate/eval/ErgoScriptTestkit.scala index ac35f6844c..4bb14d65ee 100644 --- a/sigmastate/src/test/scala/sigmastate/eval/ErgoScriptTestkit.scala +++ b/sigmastate/src/test/scala/sigmastate/eval/ErgoScriptTestkit.scala @@ -4,12 +4,12 @@ import org.ergoplatform.ErgoAddressEncoder.TestnetNetworkPrefix import org.ergoplatform.validation.ValidationSpecification import scala.util.Success -import sigmastate.{AvlTreeData, SType, TestsBase} +import sigmastate.{AvlTreeData, TestsBase, SType} import sigmastate.Values.{EvaluatedValue, SValue, SigmaPropConstant, Value, BigIntArrayConstant} import org.ergoplatform.{Context => _, _} import sigmastate.utxo.CostTable import scalan.BaseCtxTests -import sigmastate.lang.{LangTests, SigmaCompiler} +import sigmastate.lang.{LangTests, SigmaCompiler, CompilerSettings} import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting} import sigmastate.helpers.TestingHelpers._ import sigmastate.interpreter.ContextExtension @@ -31,7 +31,11 @@ trait ErgoScriptTestkit extends ContractsTestkit with LangTests import Size._ import BigInt._ - override lazy val compiler = new SigmaCompiler(TestnetNetworkPrefix, IR.builder) + override lazy val compiler = new SigmaCompiler(CompilerSettings( + TestnetNetworkPrefix, + IR.builder, + lowerMethodCalls = true + )) def newErgoContext(height: Int, boxToSpend: ErgoBox, extension: Map[Byte, EvaluatedValue[SType]] = Map()): ErgoLikeContext = { val tx1 = new ErgoLikeTransaction(IndexedSeq(), IndexedSeq(), IndexedSeq(boxToSpend)) diff --git a/sigmastate/src/test/scala/sigmastate/lang/SigmaParserTest.scala b/sigmastate/src/test/scala/sigmastate/lang/SigmaParserTest.scala index 0eab9f2550..dff4713b27 100644 --- a/sigmastate/src/test/scala/sigmastate/lang/SigmaParserTest.scala +++ b/sigmastate/src/test/scala/sigmastate/lang/SigmaParserTest.scala @@ -36,7 +36,10 @@ class SigmaParserTest extends PropSpec with PropertyChecks with Matchers with La } def fail(x: String, expectedLine: Int, expectedCol: Int): Unit = { - val compiler = SigmaCompiler(ErgoAddressEncoder.TestnetNetworkPrefix) + val compiler = SigmaCompiler(CompilerSettings( + ErgoAddressEncoder.TestnetNetworkPrefix, + TransformingSigmaBuilder, + lowerMethodCalls = true)) val exception = the[ParserException] thrownBy compiler.parse(x) withClue(s"Exception: $exception, is missing source context:") { exception.source shouldBe defined } val sourceContext = exception.source.get diff --git a/sigmastate/src/test/scala/sigmastate/utxo/SigmaContract.scala b/sigmastate/src/test/scala/sigmastate/utxo/SigmaContract.scala index 0d2ef4529a..33ebc9c1ff 100644 --- a/sigmastate/src/test/scala/sigmastate/utxo/SigmaContract.scala +++ b/sigmastate/src/test/scala/sigmastate/utxo/SigmaContract.scala @@ -1,8 +1,9 @@ package sigmastate.utxo import org.ergoplatform.ErgoAddressEncoder.TestnetNetworkPrefix -import sigmastate.lang.{SigmaCompiler, TransformingSigmaBuilder} +import sigmastate.lang.{TransformingSigmaBuilder, SigmaCompiler, CompilerSettings} abstract class SigmaContract { - val compiler = new SigmaCompiler(TestnetNetworkPrefix, TransformingSigmaBuilder) + val compiler = new SigmaCompiler(CompilerSettings( + TestnetNetworkPrefix, TransformingSigmaBuilder, lowerMethodCalls = true)) } From 42c002fba5bc36f561c7a9019617ad38142802ef Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Fri, 25 Jun 2021 16:16:00 +0300 Subject: [PATCH 04/56] prepare-v5.0: part 3: refactor estimateCost --- .../src/main/scala/sigmastate/Values.scala | 58 +++++++++++-------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/Values.scala b/sigmastate/src/main/scala/sigmastate/Values.scala index 86512a5e13..ed7d0ce264 100644 --- a/sigmastate/src/main/scala/sigmastate/Values.scala +++ b/sigmastate/src/main/scala/sigmastate/Values.scala @@ -44,8 +44,13 @@ object Values { type SValue = Value[SType] type Idn = String + /** Base class for all ErgoTree expression nodes. + * @see [[sigmastate.Values.ErgoTree]] + */ trait Value[+S <: SType] extends SigmaNode { + /** The companion node descriptor with opCode, cost and other metadata. */ def companion: ValueCompanion + /** Unique id of the node class used in serialization of ErgoTree. */ def opCode: OpCode = companion.opCode @@ -112,7 +117,7 @@ object Values { object Typed { def unapply(v: SValue): Option[(SValue, SType)] = Some((v, v.tpe)) } - def notSupportedError(v: SValue, opName: String) = + def notSupportedError(v: Any, opName: String) = throw new IllegalArgumentException(s"Method $opName is not supported for node $v") /** Immutable empty array of values. Can be used to avoid allocation. */ @@ -133,12 +138,13 @@ object Values { } } + /** Base class for all companion objects which are used as operation descriptors. */ trait ValueCompanion extends SigmaNodeCompanion { import ValueCompanion._ /** Unique id of the node class used in serialization of ErgoTree. */ def opCode: OpCode - override def toString: Idn = s"${this.getClass.getSimpleName}(${opCode.toUByte})" + override def toString: String = s"${this.getClass.getSimpleName}(${opCode.toUByte})" def typeName: String = this.getClass.getSimpleName.replace("$", "") @@ -170,7 +176,7 @@ object Values { } } - trait Constant[+S <: SType] extends EvaluatedValue[S] {} + abstract class Constant[+S <: SType] extends EvaluatedValue[S] {} case class ConstantNode[S <: SType](value: S#WrappedType, tpe: S) extends Constant[S] { require(Constant.isCorrectType(value, tpe), s"Invalid type of constant value $value, expected type $tpe") @@ -288,7 +294,7 @@ object Values { override def companion: ValueCompanion = UnitConstant } object UnitConstant extends ValueCompanion { - override val opCode = UnitConstantCode + override def opCode = UnitConstantCode } type BoolValue = Value[SBoolean.type] @@ -581,29 +587,31 @@ object Values { * * HOTSPOT: don't beautify the code */ - def estimateCost(sb: SigmaBoolean): Int = sb match { - case _: ProveDlog => CostTable.proveDlogEvalCost - case _: ProveDHTuple => CostTable.proveDHTupleEvalCost - case and: CAND => - childrenCost(and.children) - case or: COR => - childrenCost(or.children) - case th: CTHRESHOLD => - childrenCost(th.children) - case _ => - CostTable.MinimalCost - } + def estimateCost(sb: SigmaBoolean): Int = { + /** Compute the total cost of the given children. */ + def childrenCost(children: Seq[SigmaBoolean]): Int = { + val childrenArr = children.toArray + val nChildren = childrenArr.length + var sum = 0 + cfor(0)(_ < nChildren, _ + 1) { i => + val c = estimateCost(childrenArr(i)) + sum = Math.addExact(sum, c) + } + sum + } - /** Compute the total cost of the given children. */ - private def childrenCost(children: Seq[SigmaBoolean]): Int = { - val childrenArr = children.toArray - val nChildren = childrenArr.length - var sum = 0 - cfor(0)(_ < nChildren, _ + 1) { i => - val c = estimateCost(childrenArr(i)) - sum = Math.addExact(sum, c) + sb match { + case _: ProveDlog => CostTable.proveDlogEvalCost + case _: ProveDHTuple => CostTable.proveDHTupleEvalCost + case and: CAND => + childrenCost(and.children) + case or: COR => + childrenCost(or.children) + case th: CTHRESHOLD => + childrenCost(th.children) + case _ => + CostTable.MinimalCost } - sum } /** HOTSPOT: don't beautify this code */ From c2de7459f53052c0a2dda9d4afe2d8154e9d04e5 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Fri, 25 Jun 2021 16:42:21 +0300 Subject: [PATCH 05/56] prepare-v5.0: part 3: changes in Values.scala --- .../src/main/scala/sigmastate/Values.scala | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/Values.scala b/sigmastate/src/main/scala/sigmastate/Values.scala index ed7d0ce264..2c8c0a6589 100644 --- a/sigmastate/src/main/scala/sigmastate/Values.scala +++ b/sigmastate/src/main/scala/sigmastate/Values.scala @@ -694,11 +694,12 @@ object Values { def tpe = SBox } + // TODO refactor: only Constant make sense to inherit from EvaluatedValue case class Tuple(items: IndexedSeq[Value[SType]]) extends EvaluatedValue[STuple] with EvaluatedCollection[SAny.type, STuple] { override def companion = Tuple override def elementType = SAny lazy val tpe = STuple(items.map(_.tpe)) - lazy val value = { + lazy val value = { // TODO coverage val xs = items.cast[EvaluatedValue[SAny.type]].map(_.value) Colls.fromArray(xs.toArray(SAny.classTag.asInstanceOf[ClassTag[SAny.WrappedType]]))(RType.AnyType) } @@ -753,12 +754,12 @@ object Values { else ConcreteCollection val tpe = SCollection[V](elementType) + implicit lazy val tElement: RType[V#WrappedType] = Evaluation.stypeToRType(elementType) // TODO refactor: this method is not used and can be removed lazy val value = { val xs = items.cast[EvaluatedValue[V]].map(_.value) - val tElement = Evaluation.stypeToRType(elementType) - Colls.fromArray(xs.toArray(elementType.classTag.asInstanceOf[ClassTag[V#WrappedType]]))(tElement) + Colls.fromArray(xs.toArray(elementType.classTag.asInstanceOf[ClassTag[V#WrappedType]])) } } object ConcreteCollection extends ValueCompanion { @@ -843,24 +844,25 @@ object Values { override val rhs: SValue) extends BlockItem { require(id >= 0, "id must be >= 0") override def companion = if (tpeArgs.isEmpty) ValDef else FunDef - def tpe: SType = rhs.tpe - def isValDef: Boolean = tpeArgs.isEmpty + override def tpe: SType = rhs.tpe + override def isValDef: Boolean = tpeArgs.isEmpty /** This is not used as operation, but rather to form a program structure */ - def opType: SFunc = Value.notSupportedError(this, "opType") + override def opType: SFunc = Value.notSupportedError(this, "opType") } object ValDef extends ValueCompanion { - def opCode: OpCode = ValDefCode + override def opCode: OpCode = ValDefCode def apply(id: Int, rhs: SValue): ValDef = ValDef(id, Nil, rhs) } object FunDef extends ValueCompanion { - def opCode: OpCode = FunDefCode + override def opCode: OpCode = FunDefCode def unapply(d: BlockItem): Option[(Int, Seq[STypeVar], SValue)] = d match { case ValDef(id, targs, rhs) if !d.isValDef => Some((id, targs, rhs)) case _ => None } } - /** Special node which represents a reference to ValDef in was introduced as result of CSE. */ + /** Special node which represents a reference to ValDef it was introduced as result of + * CSE. */ case class ValUse[T <: SType](valId: Int, tpe: T) extends NotReadyValue[T] { override def companion = ValUse /** This is not used as operation, but rather to form a program structure */ @@ -880,6 +882,7 @@ object Values { case class BlockValue(items: IndexedSeq[BlockItem], result: SValue) extends NotReadyValue[SType] { override def companion = BlockValue def tpe: SType = result.tpe + /** This is not used as operation, but rather to form a program structure */ def opType: SFunc = Value.notSupportedError(this, "opType") } From 448fcdb1933dfc3d2dd3cd2cc93b071840d3acd3 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Fri, 25 Jun 2021 18:34:34 +0300 Subject: [PATCH 06/56] prepare-v5.0: part 3: changes in Evaluation.scala --- .../src/main/scala/sigmastate/eval/Evaluation.scala | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/eval/Evaluation.scala b/sigmastate/src/main/scala/sigmastate/eval/Evaluation.scala index ce7107476e..94204c9a0d 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/Evaluation.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/Evaluation.scala @@ -347,10 +347,9 @@ trait Evaluation extends RuntimeCosting { IR: IRContext => case arr: Array[_] => s"Array(${trim(arr).mkString(",")})" case col: special.collection.Coll[_] => s"Coll(${trim(col.toArray).mkString(",")})" case p: SGroupElement => p.showToString - case ProveDlog(GroupElementConstant(g)) => s"ProveDlog(${g.showToString})" - case ProveDHTuple( - GroupElementConstant(g), GroupElementConstant(h), GroupElementConstant(u), GroupElementConstant(v)) => - s"ProveDHT(${g.showToString},${h.showToString},${u.showToString},${v.showToString})" + case ProveDlog(g) => s"ProveDlog(${showECPoint(g)})" + case ProveDHTuple(g, h, u, v) => + s"ProveDHT(${showECPoint(g)},${showECPoint(h)},${showECPoint(u)},${showECPoint(v)})" case _ => x.toString } sym match { @@ -370,7 +369,7 @@ trait Evaluation extends RuntimeCosting { IR: IRContext => case _ => error(s"Cannot find value in environment for $s (dataEnv = $dataEnv)") } - /** Incapsulate simple monotonic (add only) counter with reset. */ + /** Encapsulate simple monotonic (add only) counter with reset. */ class CostCounter(val initialCost: Int) { private var _currentCost: Int = initialCost From 19c3cb3b44fc5254171cf4bacce03983f8e0ff44 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Fri, 25 Jun 2021 18:34:53 +0300 Subject: [PATCH 07/56] prepare-v5.0: part 3: changes in Sized.scala --- .../src/main/scala/sigmastate/eval/Sized.scala | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/sigmastate/src/main/scala/sigmastate/eval/Sized.scala b/sigmastate/src/main/scala/sigmastate/eval/Sized.scala index a6362ffbb1..47aefed55b 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/Sized.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/Sized.scala @@ -38,7 +38,14 @@ trait SizedLowPriority { object Sized extends SizedLowPriority { def apply[T](implicit sz: Sized[T]): Sized[T] = sz + + /** Creates a size descriptor for a given data instance provided T is Sized. + * @param x data instance + * @tparam T Sized data type + */ def sizeOf[T: Sized](x: T): Size[T] = Sized[T].size(x) + + /** Helper constructor to support Scala 2.11. */ def instance[T](f: T => Size[T]) = new Sized[T] { override def size(x: T): Size[T] = f(x) } @@ -63,6 +70,13 @@ object Sized extends SizedLowPriority { implicit val SigmaPropIsSized: Sized[SigmaProp] = Sized.instance((_: SigmaProp) => SizeSigmaProp) implicit val AvlTreeIsSized: Sized[AvlTree] = Sized.instance((_: AvlTree) => SizeAvlTree) + /** Constructs a new [[Sized]] instance for a given [[SType]] type descriptor. */ + def stypeToSized[T <: SType](t: T): Sized[T#WrappedType] = { + val rtype = Evaluation.stypeToRType(t) + typeToSized[T#WrappedType](rtype) + } + + /** Constructs a new [[Sized]] instance for a given [[RType]] type descriptor. */ def typeToSized[T](t: RType[T]): Sized[T] = (t match { case BooleanType => BooleanIsSized case ByteType => ByteIsSized From 30ab433ccc5bd31e084181c42dd9c351f2639086 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Fri, 25 Jun 2021 18:35:52 +0300 Subject: [PATCH 08/56] prepare-v5.0: part 3: changes in DLogProtocol.scala and DiffieHellmanTupleProtocol.scala --- sigmastate/src/main/scala/sigmastate/basics/DLogProtocol.scala | 2 +- .../scala/sigmastate/basics/DiffieHellmanTupleProtocol.scala | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/basics/DLogProtocol.scala b/sigmastate/src/main/scala/sigmastate/basics/DLogProtocol.scala index 364dc41e57..8e7dce3162 100644 --- a/sigmastate/src/main/scala/sigmastate/basics/DLogProtocol.scala +++ b/sigmastate/src/main/scala/sigmastate/basics/DLogProtocol.scala @@ -72,7 +72,7 @@ object DLogProtocol { GroupElementSerializer.toBytes(ecData) } - override def toString: Idn = s"FirstDLogProverMessage(${Base16.encode(bytes)})" + override def toString = s"FirstDLogProverMessage(${Base16.encode(bytes)})" } case class SecondDLogProverMessage(z: BigInt) extends SecondProverMessage { diff --git a/sigmastate/src/main/scala/sigmastate/basics/DiffieHellmanTupleProtocol.scala b/sigmastate/src/main/scala/sigmastate/basics/DiffieHellmanTupleProtocol.scala index bda3f4ec56..221122982b 100644 --- a/sigmastate/src/main/scala/sigmastate/basics/DiffieHellmanTupleProtocol.scala +++ b/sigmastate/src/main/scala/sigmastate/basics/DiffieHellmanTupleProtocol.scala @@ -63,8 +63,7 @@ case class SecondDiffieHellmanTupleProverMessage(z: BigInteger) extends SecondPr /** Construct a new SigmaProp value representing public key of Diffie Hellman signature protocol. * Common input: (g,h,u,v) */ case class ProveDHTuple(gv: EcPointType, hv: EcPointType, uv: EcPointType, vv: EcPointType) - extends SigmaProtocolCommonInput[DiffieHellmanTupleProtocol] - with SigmaProofOfKnowledgeLeaf[DiffieHellmanTupleProtocol, DiffieHellmanTupleProverInput] { + extends SigmaProofOfKnowledgeLeaf[DiffieHellmanTupleProtocol, DiffieHellmanTupleProverInput] { override val opCode: OpCode = OpCodes.ProveDiffieHellmanTupleCode lazy val g = gv lazy val h = hv From 4331fc41477da488badca95f2c3f2ba2b2e9118c Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Fri, 25 Jun 2021 18:44:42 +0300 Subject: [PATCH 09/56] prepare-v5.0: part 3: changes in Interpreter.scala --- .../scala/sigmastate/interpreter/Interpreter.scala | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala index bdb379f72f..611dc6d9f0 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala @@ -114,14 +114,13 @@ trait Interpreter extends ScorexLogging { /** This method is used in both prover and verifier to compute SigmaBoolean value. * As the first step the cost of computing the `exp` expression in the given context is estimated. - * If cost is above limit - * then exception is returned and `exp` is not executed - * else `exp` is computed in the given context and the resulting SigmaBoolean returned. + * If cost is above limit then exception is returned and `exp` is not executed + * else `exp` is computed in the given context and the resulting SigmaBoolean returned. * - * @param context the context in which `exp` should be executed - * @param env environment of system variables used by the interpreter internally - * @param exp expression to be executed in the given `context` - * @return result of script reduction + * @param context the context in which `exp` should be executed + * @param env environment of system variables used by the interpreter internally + * @param exp expression to be executed in the given `context` + * @return result of script reduction * @see `ReductionResult` */ def reduceToCrypto(context: CTX, env: ScriptEnv, exp: Value[SType]): Try[ReductionResult] = Try { @@ -150,6 +149,7 @@ trait Interpreter extends ScorexLogging { } } + /** Reduces `exp` to SigmaProp under the default (empty) environment. */ def reduceToCrypto(context: CTX, exp: Value[SType]): Try[ReductionResult] = reduceToCrypto(context, Interpreter.emptyEnv, exp) From 90b92035607aba769a2b418ef2e2ff2b6dc106ff Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Fri, 25 Jun 2021 18:45:05 +0300 Subject: [PATCH 10/56] prepare-v5.0: part 3: changes in CryptoConstants.scala and CryptoFunctions.scala --- .../sigmastate/interpreter/CryptoConstants.scala | 11 ++++++----- .../sigmastate/interpreter/CryptoFunctions.scala | 6 +++++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/CryptoConstants.scala b/sigmastate/src/main/scala/sigmastate/interpreter/CryptoConstants.scala index f097acb8af..1da6c8c27e 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/CryptoConstants.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/CryptoConstants.scala @@ -29,11 +29,12 @@ object CryptoConstants { val hashLength: Int = hashLengthBits / 8 - //size of challenge in Sigma protocols, in bits - //if this anything but 192, threshold won't work, because we have polynomials over GF(2^192) and no others - //so DO NOT change the value without implementing polynomials over GF(2^soundnessBits) first - //and changing code that calls on GF2_192 and GF2_192_Poly classes!!! - //we get the challenge by reducing hash function output to proper value + /** A size of challenge in Sigma protocols, in bits. + * If this anything but 192, threshold won't work, because we have polynomials over GF(2^192) and no others. + * So DO NOT change the value without implementing polynomials over GF(2^soundnessBits) first + * and changing code that calls on GF2_192 and GF2_192_Poly classes!!! + * We get the challenge by reducing hash function output to proper value. + */ implicit val soundnessBits: Int = 192.ensuring(_ < groupSizeBits, "2^t < q condition is broken!") def secureRandomBytes(howMany: Int): Array[Byte] = { diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/CryptoFunctions.scala b/sigmastate/src/main/scala/sigmastate/interpreter/CryptoFunctions.scala index 008048605c..b9852d5211 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/CryptoFunctions.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/CryptoFunctions.scala @@ -5,8 +5,12 @@ import scorex.crypto.hash.Blake2b256 object CryptoFunctions { lazy val soundnessBytes: Int = CryptoConstants.soundnessBits / 8 + /** @hotspot don't beautify this code. Used in Interpreter.verify. */ def hashFn(input: Array[Byte]): Array[Byte] = { - Blake2b256.hash(input).take(soundnessBytes) + val h = Blake2b256.hash(input) + val res = new Array[Byte](soundnessBytes) + Array.copy(h, 0, res, 0, soundnessBytes) + res } } From 373727c3b586ac9aa16a249d8511d1c5f0df9412 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Tue, 29 Jun 2021 19:06:03 +0300 Subject: [PATCH 11/56] prepare-v5.0: part 3: changes in InterpreterContext.scala --- .../interpreter/InterpreterContext.scala | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/InterpreterContext.scala b/sigmastate/src/main/scala/sigmastate/interpreter/InterpreterContext.scala index fd9f054ca7..84f9569c6f 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/InterpreterContext.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/InterpreterContext.scala @@ -3,6 +3,7 @@ package sigmastate.interpreter import org.ergoplatform.validation.SigmaValidationSettings import sigmastate.SType import sigmastate.Values.EvaluatedValue +import sigmastate.interpreter.ContextExtension.VarBinding import sigmastate.serialization.SigmaSerializer import sigmastate.utils.{SigmaByteReader, SigmaByteWriter} import special.sigma @@ -20,13 +21,18 @@ import special.sigma.AnyValue * @param values internal container of the key-value pairs */ case class ContextExtension(values: Map[Byte, EvaluatedValue[_ <: SType]]) { - def add(bindings: (Byte, EvaluatedValue[_ <: SType])*): ContextExtension = + def add(bindings: VarBinding*): ContextExtension = ContextExtension(values ++ bindings) } object ContextExtension { + /** Immutable instance of empty ContextExtension, which can be shared to avoid + * allocations. */ val empty = ContextExtension(Map()) + /** Type of context variable binding. */ + type VarBinding = (Byte, EvaluatedValue[_ <: SType]) + object serializer extends SigmaSerializer[ContextExtension, ContextExtension] { override def serialize(obj: ContextExtension, w: SigmaByteWriter): Unit = { @@ -91,7 +97,7 @@ trait InterpreterContext { def withExtension(newExtension: ContextExtension): InterpreterContext /** Creates a new instance with given bindings added to extension. */ - def withBindings(bindings: (Byte, EvaluatedValue[_ <: SType])*): InterpreterContext = { + def withBindings(bindings: VarBinding*): InterpreterContext = { val ext = extension.add(bindings: _*) withExtension(ext) } @@ -104,6 +110,13 @@ trait InterpreterContext { * These types are used internally by ErgoTree interpreter. * Thus, this method performs transformation from Ergo to internal Sigma representation * of all context data. + * + * @param isCost == true if the resulting context will be used in AOT cost estimation + * otherwise it should be false + * @param extensions additional context variables which will be merged with those in the + * `extension` of this instance, overriding existing bindings in case + * variable ids overlap. + * * @see sigmastate.eval.Evaluation */ def toSigmaContext(isCost: Boolean, extensions: Map[Byte, AnyValue] = Map()): sigma.Context From f24833d8d4572d77e4a93e5b69360335cb2d7dc1 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Tue, 29 Jun 2021 19:12:10 +0300 Subject: [PATCH 12/56] prepare-v5.0: part 3: changes in ProverInterpreter.scala and ProverResult.scala --- .../sigmastate/interpreter/ProverInterpreter.scala | 12 ++++++------ .../scala/sigmastate/interpreter/ProverResult.scala | 3 +-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala b/sigmastate/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala index a7b174ed6c..9f3f70d62e 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/ProverInterpreter.scala @@ -59,12 +59,12 @@ trait ProverInterpreter extends Interpreter with ProverUtils with AttributionCor * https://ergoplatform.org/docs/ErgoScript.pdf , Appendix A * */ - // todo: if we are concerned about timing attacks against the prover, we should make sure that this code - // todo: takes the same amount of time regardless of which nodes are real and which nodes are simulated - // todo: In particular, we should avoid the use of exists and forall, because they short-circuit the evaluation - // todo: once the right value is (or is not) found. We should also make all loops look similar, the same - // todo: amount of copying is done regardless of what's real or simulated, - // todo: real vs. simulated computations take the same time, etc. + // TODO: if we are concerned about timing attacks against the prover, we should make sure that this code + // takes the same amount of time regardless of which nodes are real and which nodes are simulated + // In particular, we should avoid the use of exists and forall, because they short-circuit the evaluation + // once the right value is (or is not) found. We should also make all loops look similar, the same + // amount of copying is done regardless of what's real or simulated, + // real vs. simulated computations take the same time, etc. protected def prove(unprovenTree: UnprovenTree, message: Array[Byte], hintsBag: HintsBag): ProofT = { // Prover Step 1: Mark as real everything the prover can prove diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/ProverResult.scala b/sigmastate/src/main/scala/sigmastate/interpreter/ProverResult.scala index 9c14d295c9..8e8c979ebd 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/ProverResult.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/ProverResult.scala @@ -3,7 +3,6 @@ package sigmastate.interpreter import java.util import scorex.util.encode.Base16 -import sigmastate.Values.Idn import sigmastate.serialization.SigmaSerializer import sigmastate.utils.{SigmaByteReader, SigmaByteWriter} @@ -22,7 +21,7 @@ class ProverResult(val proof: Array[Byte], val extension: ContextExtension) { case _ => false } - override def toString: Idn = s"ProverResult(${Base16.encode(proof)},$extension)" + override def toString = s"ProverResult(${Base16.encode(proof)},$extension)" } object ProverResult { From dc390c7140eaea6caa6a796376a2f6b01b40bfc7 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Tue, 29 Jun 2021 22:43:04 +0300 Subject: [PATCH 13/56] prepare-v5.0: part 3: added SigmaTyper.lowerMethodCalls --- .../scala/sigmastate/lang/SigmaCompiler.scala | 2 +- .../scala/sigmastate/lang/SigmaTyper.scala | 26 ++++++++++++++----- .../lang/SigmaSpecializerTest.scala | 2 +- .../sigmastate/lang/SigmaTyperTest.scala | 4 +-- 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/lang/SigmaCompiler.scala b/sigmastate/src/main/scala/sigmastate/lang/SigmaCompiler.scala index 70ba8007db..659e23e19b 100644 --- a/sigmastate/src/main/scala/sigmastate/lang/SigmaCompiler.scala +++ b/sigmastate/src/main/scala/sigmastate/lang/SigmaCompiler.scala @@ -38,7 +38,7 @@ class SigmaCompiler(settings: CompilerSettings) { val predefinedFuncRegistry = new PredefinedFuncRegistry(builder) val binder = new SigmaBinder(env, builder, networkPrefix, predefinedFuncRegistry) val bound = binder.bind(parsed) - val typer = new SigmaTyper(builder, predefinedFuncRegistry) + val typer = new SigmaTyper(builder, predefinedFuncRegistry, settings.lowerMethodCalls) val typed = typer.typecheck(bound) typed } diff --git a/sigmastate/src/main/scala/sigmastate/lang/SigmaTyper.scala b/sigmastate/src/main/scala/sigmastate/lang/SigmaTyper.scala index 79f24ab509..4e24c9c2d5 100644 --- a/sigmastate/src/main/scala/sigmastate/lang/SigmaTyper.scala +++ b/sigmastate/src/main/scala/sigmastate/lang/SigmaTyper.scala @@ -18,7 +18,9 @@ import scala.collection.mutable.ArrayBuffer /** * Type inference and analysis for Sigma expressions. */ -class SigmaTyper(val builder: SigmaBuilder, predefFuncRegistry: PredefinedFuncRegistry) { +class SigmaTyper(val builder: SigmaBuilder, + predefFuncRegistry: PredefinedFuncRegistry, + lowerMethodCalls: Boolean) { import SigmaTyper._ import builder._ import predefFuncRegistry._ @@ -35,7 +37,7 @@ class SigmaTyper(val builder: SigmaBuilder, predefFuncRegistry: PredefinedFuncRe args: IndexedSeq[SValue]) = { val global = Global.withPropagatedSrcCtx(srcCtx) val node = for { - pf <- method.irInfo.irBuilder + pf <- method.irInfo.irBuilder if lowerMethodCalls res <- pf.lift((builder, global, method, args, EmptySubst)) } yield res node.getOrElse(mkMethodCall(global, method, args, EmptySubst).withPropagatedSrcCtx(srcCtx)) @@ -105,8 +107,12 @@ class SigmaTyper(val builder: SigmaBuilder, predefFuncRegistry: PredefinedFuncRe if (method.irInfo.irBuilder.isDefined && !tRes.isFunc) { // this is MethodCall of parameter-less property, so invoke builder and/or fallback to just MethodCall val methodConcrType = method.withSType(SFunc(newObj.tpe, tRes)) - methodConcrType.irInfo.irBuilder.flatMap(_.lift(builder, newObj, methodConcrType, IndexedSeq(), Map())) - .getOrElse(mkMethodCall(newObj, methodConcrType, IndexedSeq(), Map())) + val nodeOpt = methodConcrType.irInfo.irBuilder match { + case Some(b) if lowerMethodCalls => + b.lift(builder, newObj, methodConcrType, IndexedSeq(), Map()) + case _ => None + } + nodeOpt.getOrElse(mkMethodCall(newObj, methodConcrType, IndexedSeq(), Map())) } else { mkSelect(newObj, n, Some(tRes)) } @@ -142,7 +148,9 @@ class SigmaTyper(val builder: SigmaBuilder, predefFuncRegistry: PredefinedFuncRe || !expectedArgs.zip(newArgTypes).forall { case (ea, na) => ea == SAny || ea == na }) error(s"For method $n expected args: $expectedArgs; actual: $newArgTypes", sel.sourceContext) if (method.irInfo.irBuilder.isDefined) { - method.irInfo.irBuilder.flatMap(_.lift(builder, newObj, method, newArgs, subst)) + method.irInfo.irBuilder + .filter(_ => lowerMethodCalls) + .flatMap(_.lift(builder, newObj, method, newArgs, subst)) .getOrElse(mkMethodCall(newObj, method, newArgs, subst)) } else { val newSelect = mkSelect(newObj, n, Some(concrFunTpe)).withSrcCtx(sel.sourceContext) @@ -175,7 +183,9 @@ class SigmaTyper(val builder: SigmaBuilder, predefFuncRegistry: PredefinedFuncRe || !expectedArgs.zip(newArgTypes).forall { case (ea, na) => ea == SAny || ea == na }) error(s"For method $n expected args: $expectedArgs; actual: $newArgTypes", sel.sourceContext) val methodConcrType = method.withSType(concrFunTpe.asFunc.withReceiverType(newObj.tpe)) - methodConcrType.irInfo.irBuilder.flatMap(_.lift(builder, newObj, methodConcrType, newArgs, Map())) + methodConcrType.irInfo.irBuilder + .filter(_ => lowerMethodCalls) + .flatMap(_.lift(builder, newObj, methodConcrType, newArgs, Map())) .getOrElse(mkMethodCall(newObj, methodConcrType, newArgs, Map())) case _ => val newSelect = mkSelect(newObj, n, Some(concrFunTpe)).withSrcCtx(sel.sourceContext) @@ -288,7 +298,9 @@ class SigmaTyper(val builder: SigmaBuilder, predefFuncRegistry: PredefinedFuncRe } case _ => EmptySubst } - method.irInfo.irBuilder.flatMap(_.lift(builder, newObj, method, newArgs, typeSubst)) + method.irInfo.irBuilder + .filter(_ => lowerMethodCalls) + .flatMap(_.lift(builder, newObj, method, newArgs, typeSubst)) .getOrElse(mkMethodCall(newObj, method, newArgs, typeSubst)) case _ => diff --git a/sigmastate/src/test/scala/sigmastate/lang/SigmaSpecializerTest.scala b/sigmastate/src/test/scala/sigmastate/lang/SigmaSpecializerTest.scala index a2ceefd828..b661ae5924 100644 --- a/sigmastate/src/test/scala/sigmastate/lang/SigmaSpecializerTest.scala +++ b/sigmastate/src/test/scala/sigmastate/lang/SigmaSpecializerTest.scala @@ -37,7 +37,7 @@ class SigmaSpecializerTest extends PropSpec val predefinedFuncRegistry = new PredefinedFuncRegistry(builder) val binder = new SigmaBinder(env, builder, TestnetNetworkPrefix, predefinedFuncRegistry) val bound = binder.bind(parsed) - val typer = new SigmaTyper(builder, predefinedFuncRegistry) + val typer = new SigmaTyper(builder, predefinedFuncRegistry, lowerMethodCalls = true) val typed = typer.typecheck(bound) typed } diff --git a/sigmastate/src/test/scala/sigmastate/lang/SigmaTyperTest.scala b/sigmastate/src/test/scala/sigmastate/lang/SigmaTyperTest.scala index 4926824bb1..25ea44b839 100644 --- a/sigmastate/src/test/scala/sigmastate/lang/SigmaTyperTest.scala +++ b/sigmastate/src/test/scala/sigmastate/lang/SigmaTyperTest.scala @@ -31,7 +31,7 @@ class SigmaTyperTest extends PropSpec with PropertyChecks with Matchers with Lan val predefinedFuncRegistry = new PredefinedFuncRegistry(builder) val binder = new SigmaBinder(env, builder, TestnetNetworkPrefix, predefinedFuncRegistry) val bound = binder.bind(parsed) - val typer = new SigmaTyper(builder, predefinedFuncRegistry) + val typer = new SigmaTyper(builder, predefinedFuncRegistry, lowerMethodCalls = true) val typed = typer.typecheck(bound) assertSrcCtxForAllNodes(typed) if (expected != null) typed shouldBe expected @@ -48,7 +48,7 @@ class SigmaTyperTest extends PropSpec with PropertyChecks with Matchers with Lan val predefinedFuncRegistry = new PredefinedFuncRegistry(builder) val binder = new SigmaBinder(env, builder, TestnetNetworkPrefix, predefinedFuncRegistry) val bound = binder.bind(parsed) - val typer = new SigmaTyper(builder, predefinedFuncRegistry) + val typer = new SigmaTyper(builder, predefinedFuncRegistry, lowerMethodCalls = true) typer.typecheck(bound) }, { case te: TyperException => From fd7619cc64e47f39646db46e8a7aaedd50565f57 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Wed, 30 Jun 2021 00:23:23 +0400 Subject: [PATCH 14/56] prepare-v5.0: part 3: added BigIntOpType constant --- .../src/main/scala/sigmastate/eval/RuntimeCosting.scala | 7 ++----- sigmastate/src/main/scala/sigmastate/lang/Terms.scala | 8 ++++---- .../scala/sigmastate/serialization/TupleSerializer.scala | 1 + .../scala/sigmastate/serialization/ValueSerializer.scala | 2 +- sigmastate/src/main/scala/sigmastate/trees.scala | 2 ++ 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/eval/RuntimeCosting.scala b/sigmastate/src/main/scala/sigmastate/eval/RuntimeCosting.scala index acb955264a..c0d9b2a0c9 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/RuntimeCosting.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/RuntimeCosting.scala @@ -191,18 +191,15 @@ trait RuntimeCosting extends CostingRules { IR: IRContext => constCost(tpe) } - val UpcastBigIntOpType = SFunc(sigmastate.Upcast.tT, SBigInt) - val DowncastBigIntOpType = SFunc(SBigInt, sigmastate.Upcast.tR) - def costOf(v: SValue): Ref[Int] = v match { case l: Terms.Lambda => constCost(l.tpe) case l: FuncValue => constCost(l.tpe) case sigmastate.Upcast(_, SBigInt) => - costOf("Upcast", UpcastBigIntOpType) + costOf("Upcast", sigmastate.Upcast.BigIntOpType) case sigmastate.Downcast(v, _) if v.tpe == SBigInt => - costOf("Downcast", DowncastBigIntOpType) + costOf("Downcast", sigmastate.Downcast.BigIntOpType) case _ => costOf(v.opName, v.opType) } diff --git a/sigmastate/src/main/scala/sigmastate/lang/Terms.scala b/sigmastate/src/main/scala/sigmastate/lang/Terms.scala index 333a08dc0c..ac7cbac32c 100644 --- a/sigmastate/src/main/scala/sigmastate/lang/Terms.scala +++ b/sigmastate/src/main/scala/sigmastate/lang/Terms.scala @@ -165,15 +165,15 @@ object Terms { * The SMethod instances in STypeCompanions may have type STypeIdent in methods types, * but valid ErgoTree should have SMethod instances specialized for specific types of * obj and args using `specializeFor`. - * This means, if we save typeId, mathodId, and we save all the arguments, + * This means, if we save typeId, methodId, and we save all the arguments, * we can restore the specialized SMethod instance. * This work by induction, if we assume all arguments are monomorphic, * then we can make MethodCall monomorphic. * Thus, all ErgoTree instances are monomorphic by construction. * - * @param obj object on which method will be invoked - * @param method method to be invoked - * @param args arguments passed to the method on invocation + * @param obj object on which method will be invoked + * @param method method to be invoked + * @param args arguments passed to the method on invocation * @param typeSubst a map of concrete type for each generic type parameter */ case class MethodCall(obj: Value[SType], diff --git a/sigmastate/src/main/scala/sigmastate/serialization/TupleSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/TupleSerializer.scala index f579803f62..ec97631f01 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/TupleSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/TupleSerializer.scala @@ -14,6 +14,7 @@ case class TupleSerializer(cons: Seq[Value[SType]] => Value[SType]) val itemInfo: DataInfo[SValue] = ArgInfo("item_i", "tuple's item in i-th position") override def serialize(obj: Tuple, w: SigmaByteWriter): Unit = { + // TODO refactor: avoid usage of extension method `length` val length = obj.length w.putUByte(length, numItemsInfo) foreach(numItemsInfo.info.name, obj.items) { i => diff --git a/sigmastate/src/main/scala/sigmastate/serialization/ValueSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/ValueSerializer.scala index bc826dbf71..af7b9a4a5e 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/ValueSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/ValueSerializer.scala @@ -197,7 +197,7 @@ object ValueSerializer extends SigmaSerializerCompanion[Value[SType]] { def name = s"Serializer of ${serializer.opDesc}" override def parent: Scope = null override def showInScope(v: String): String = name + "/" + v - override def toString: Idn = s"SerScope(${serializer.opDesc}, $children)" + override def toString: String = s"SerScope(${serializer.opDesc}, $children)" } case class DataScope(parent: Scope, data: DataInfo[_]) extends Scope { diff --git a/sigmastate/src/main/scala/sigmastate/trees.scala b/sigmastate/src/main/scala/sigmastate/trees.scala index d2c32cb158..8987684882 100644 --- a/sigmastate/src/main/scala/sigmastate/trees.scala +++ b/sigmastate/src/main/scala/sigmastate/trees.scala @@ -392,6 +392,7 @@ object Upcast extends NumericCastCompanion { override def argInfos: Seq[ArgInfo] = UpcastInfo.argInfos def tT = SType.tT def tR = SType.tR + val BigIntOpType = SFunc(tT, SBigInt) } /** @@ -409,6 +410,7 @@ object Downcast extends NumericCastCompanion { override def argInfos: Seq[ArgInfo] = DowncastInfo.argInfos def tT = SType.tT def tR = SType.tR + val BigIntOpType = SFunc(SBigInt, tR) } /** From e7da9c176b88172c01963f47a4d7603ba1263d4e Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Wed, 30 Jun 2021 00:39:14 +0400 Subject: [PATCH 15/56] prepare-v5.0: part 3: changes in trees.scala --- sigmastate/src/main/scala/sigmastate/trees.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/trees.scala b/sigmastate/src/main/scala/sigmastate/trees.scala index 8987684882..2be2afb08f 100644 --- a/sigmastate/src/main/scala/sigmastate/trees.scala +++ b/sigmastate/src/main/scala/sigmastate/trees.scala @@ -519,12 +519,12 @@ object CalcSha256 extends SimpleTransformerCompanion { case class SubstConstants[T <: SType](scriptBytes: Value[SByteArray], positions: Value[SIntArray], newValues: Value[SCollection[T]]) extends NotReadyValueByteArray { override def companion = SubstConstants - override val opType = SFunc(Array(SByteArray, SIntArray, SCollection(SType.tT)), SByteArray) + override val opType = SubstConstants.OpType } object SubstConstants extends ValueCompanion { override def opCode: OpCode = OpCodes.SubstConstantsCode - + val OpType = SFunc(Array(SByteArray, SIntArray, SCollection(SType.tT)), SByteArray) def eval(scriptBytes: Array[Byte], positions: Array[Int], newVals: Array[Value[SType]])(implicit vs: SigmaValidationSettings): Array[Byte] = @@ -557,7 +557,7 @@ trait TwoArgumentOperationCompanion extends ValueCompanion { case class ArithOp[T <: SType](left: Value[T], right: Value[T], override val opCode: OpCode) extends TwoArgumentsOperation[T, T, T] with NotReadyValue[T] { - override def companion: ValueCompanion = ArithOp.operations(opCode) + override def companion: ArithOpCompanion = ArithOp.operations(opCode) override def tpe: T = left.tpe override val opType = SFunc(Array[SType](left.tpe, right.tpe), tpe) override def opName: String = ArithOp.opcodeToArithOpName(opCode) From d21b4e3cead950f69a7d2dcb271ba4b5ef6e6c0f Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Wed, 30 Jun 2021 18:29:46 +0400 Subject: [PATCH 16/56] prepare-v5.0: part 3: removed unfinished Coll methods together with ignored tests --- .../src/main/scala/sigmastate/types.scala | 92 ++------ .../sigmastate/lang/SigmaCompilerTest.scala | 202 ------------------ 2 files changed, 15 insertions(+), 279 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/types.scala b/sigmastate/src/main/scala/sigmastate/types.scala index 4f9f604d20..16e6487efa 100644 --- a/sigmastate/src/main/scala/sigmastate/types.scala +++ b/sigmastate/src/main/scala/sigmastate/types.scala @@ -151,6 +151,7 @@ object SType { val paramR = STypeParam(tR) val paramIV = STypeParam(tIV) val paramOV = STypeParam(tOV) + val paramIVSeq: Seq[STypeParam] = Array(paramIV) val IndexedSeqOfT1: IndexedSeq[SType] = Array(SType.tT) val IndexedSeqOfT2: IndexedSeq[SType] = Array(SType.tT, SType.tT) @@ -319,8 +320,10 @@ trait STypeCompanion { /** Lookup method by its id in this type. */ @inline def getMethodById(methodId: Byte): Option[SMethod] = - _methodsMap.get(typeId) - .flatMap(ms => ms.get(methodId)) + _methodsMap.get(typeId) match { + case Some(ms) => ms.get(methodId) + case None => None + } /** Lookup method in this type by method's id or throw ValidationException. * This method can be used in trySoftForkable section to either obtain valid method @@ -614,11 +617,14 @@ trait SNumericType extends SProduct { @inline def max(that: SNumericType): SNumericType = if (this.numericTypeIndex > that.numericTypeIndex) this else that + /** Returns true if this numeric type is larger than that. */ + @inline final def >(that: SNumericType): Boolean = this.numericTypeIndex > that.numericTypeIndex + /** Numeric types are ordered by the number of bytes to store the numeric values. * @return index in the array of all numeric types. */ - @inline protected def numericTypeIndex: Int + protected def numericTypeIndex: Int - override def toString: Idn = this.getClass.getSimpleName + override def toString: String = this.getClass.getSimpleName } object SNumericType extends STypeCompanion { /** Array of all numeric types ordered by number of bytes in the representation. */ @@ -626,7 +632,9 @@ object SNumericType extends STypeCompanion { // TODO HF (4h): this typeId is now shadowed by SGlobal.typeId // see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/667 - def typeId: TypeCode = 106: Byte + override def typeId: TypeCode = 106: Byte + + /** Type variable used in generic signatures of method descriptors. */ val tNum = STypeVar("TNum") val ToByteMethod: SMethod = SMethod(this, "toByte", SFunc(tNum, SByte), 1) @@ -1177,13 +1185,6 @@ object SCollection extends STypeCompanion with MethodByNameUnapply { | Throws an exception if \lst{i < 0} or \lst{length <= i} """.stripMargin, ArgInfo("i", "the index")) - val BitShiftLeftMethod = SMethod(this, "<<", - SFunc(IndexedSeq(ThisType, SInt), ThisType, Seq(paramIV)), 11) - val BitShiftRightMethod = SMethod(this, ">>", - SFunc(IndexedSeq(ThisType, SInt), ThisType, Seq(paramIV)), 12) - val BitShiftRightZeroedMethod = SMethod(this, ">>>", - SFunc(IndexedSeq(SCollection(SBoolean), SInt), SCollection(SBoolean)), 13) - val IndicesMethod = SMethod(this, "indices", SFunc(ThisType, SCollection(SInt)), 14) .withIRInfo(MethodCallIrBuilder) .withInfo(PropertyCall, @@ -1217,57 +1218,14 @@ object SCollection extends STypeCompanion with MethodByNameUnapply { SFunc(IndexedSeq(ThisType, SCollection(SInt), ThisType), ThisType, Seq(paramIV)), 21) .withIRInfo(MethodCallIrBuilder).withInfo(MethodCall, "") - val UnionSetsMethod = SMethod(this, "unionSets", - SFunc(IndexedSeq(ThisType, ThisType), ThisType, Seq(paramIV)), 22) - .withIRInfo(MethodCallIrBuilder).withInfo(MethodCall, "") - - val DiffMethod = SMethod(this, "diff", - SFunc(IndexedSeq(ThisType, ThisType), ThisType, Seq(paramIV)), 23) - .withIRInfo(MethodCallIrBuilder).withInfo(MethodCall, "") - val IntersectMethod = SMethod(this, "intersect", - SFunc(IndexedSeq(ThisType, ThisType), ThisType, Seq(paramIV)), 24) - .withIRInfo(MethodCallIrBuilder).withInfo(MethodCall, "") - - val PrefixLengthMethod = SMethod(this, "prefixLength", - SFunc(IndexedSeq(ThisType, tPredicate), SInt, Seq(paramIV)), 25) - .withIRInfo(MethodCallIrBuilder).withInfo(MethodCall, "") - val IndexOfMethod = SMethod(this, "indexOf", SFunc(IndexedSeq(ThisType, tIV, SInt), SInt, Seq(paramIV)), 26) .withIRInfo(MethodCallIrBuilder).withInfo(MethodCall, "") - val LastIndexOfMethod = SMethod(this, "lastIndexOf", - SFunc(IndexedSeq(ThisType, tIV, SInt), SInt, Seq(paramIV)), 27) - .withIRInfo(MethodCallIrBuilder).withInfo(MethodCall, "") - - // TODO HF (1h): related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 - lazy val FindMethod = SMethod(this, "find", - SFunc(IndexedSeq(ThisType, tPredicate), SOption(tIV), Seq(paramIV)), 28) - .withIRInfo(MethodCallIrBuilder).withInfo(MethodCall, "") - val ZipMethod = SMethod(this, "zip", SFunc(IndexedSeq(ThisType, tOVColl), SCollection(STuple(tIV, tOV)), Seq(tIV, tOV)), 29) .withIRInfo(MethodCallIrBuilder).withInfo(MethodCall, "") - val DistinctMethod = SMethod(this, "distinct", - SFunc(IndexedSeq(ThisType), ThisType, Seq(tIV)), 30) - .withIRInfo(MethodCallIrBuilder).withInfo(PropertyCall, "") - - val StartsWithMethod = SMethod(this, "startsWith", - SFunc(IndexedSeq(ThisType, ThisType, SInt), SBoolean, Seq(paramIV)), 31) - .withIRInfo(MethodCallIrBuilder).withInfo(MethodCall, "") - - val EndsWithMethod = SMethod(this, "endsWith", - SFunc(IndexedSeq(ThisType, ThisType), SBoolean, Seq(paramIV)), 32) - .withIRInfo(MethodCallIrBuilder).withInfo(MethodCall, "") - - val MapReduceMethod = SMethod(this, "mapReduce", - SFunc( - IndexedSeq(ThisType, SFunc(tIV, STuple(tK, tV)), SFunc(STuple(tV, tV), tV)), - SCollection(STuple(tK, tV)), - Seq(paramIV, STypeParam(tK), STypeParam(tV))), 34) - .withIRInfo(MethodCallIrBuilder).withInfo(MethodCall, "") - lazy val methods: Seq[SMethod] = Seq( SizeMethod, GetOrElseMethod, @@ -1279,34 +1237,13 @@ object SCollection extends STypeCompanion with MethodByNameUnapply { FilterMethod, AppendMethod, ApplyMethod, - /* TODO HF (1h): https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 - BitShiftLeftMethod, - BitShiftRightMethod, - BitShiftRightZeroedMethod, - */ IndicesMethod, FlatMapMethod, PatchMethod, UpdatedMethod, UpdateManyMethod, - /*TODO HF (1h): https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 - UnionSetsMethod, - DiffMethod, - IntersectMethod, - PrefixLengthMethod, - */ IndexOfMethod, - /* TODO HF (1h): https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 - LastIndexOfMethod, - FindMethod, - */ ZipMethod - /* TODO HF (1h): https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 - DistinctMethod, - StartsWithMethod, - EndsWithMethod, - MapReduceMethod, - */ ) def apply[T <: SType](elemType: T): SCollection[T] = SCollectionType(elemType) def apply[T <: SType](implicit elemType: T, ov: Overload1): SCollection[T] = SCollectionType(elemType) @@ -1544,6 +1481,7 @@ case object SBox extends SProduct with SPredefType with SMonoType { } } } + val PropositionBytes = "propositionBytes" val Value = "value" val Id = "id" @@ -1802,7 +1740,7 @@ case object SAvlTree extends SProduct with SPredefType with SMonoType { } case object SContext extends SProduct with SPredefType with SMonoType { - override type WrappedType = ErgoLikeContext + override type WrappedType = Context override val typeCode: TypeCode = 101: Byte override def typeId = typeCode diff --git a/sigmastate/src/test/scala/sigmastate/lang/SigmaCompilerTest.scala b/sigmastate/src/test/scala/sigmastate/lang/SigmaCompilerTest.scala index 9f76b96108..e9f1557ab9 100644 --- a/sigmastate/src/test/scala/sigmastate/lang/SigmaCompilerTest.scala +++ b/sigmastate/src/test/scala/sigmastate/lang/SigmaCompilerTest.scala @@ -222,36 +222,6 @@ class SigmaCompilerTest extends SigmaTestingCommons with LangTests with ObjectGe testMissingCosting("1 >>> 2", mkBitShiftRightZeroed(IntConstant(1), IntConstant(2))) } - // TODO soft-fork: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/418 - ignore("Collection.BitShiftLeft") { - comp("Coll(1,2) << 2") shouldBe - mkMethodCall( - ConcreteCollection.fromItems(IntConstant(1), IntConstant(2)), - SCollection.BitShiftLeftMethod.withConcreteTypes(Map(SCollection.tIV -> SInt)), - Vector(IntConstant(2)), Map()) - } - - // TODO soft-fork: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/418 - ignore("Collection.BitShiftRight") { - testMissingCosting("Coll(1,2) >> 2", - mkMethodCall( - ConcreteCollection.fromItems(IntConstant(1), IntConstant(2)), - SCollection.BitShiftRightMethod, - Vector(IntConstant(2)), - Map(SCollection.tIV -> SInt)) - ) - } - - // TODO soft-fork: 1) implement method for special.collection.Coll 2) add rule to CollCoster - ignore("Collection.BitShiftRightZeroed") { - comp("Coll(true, false) >>> 2") shouldBe - mkMethodCall( - ConcreteCollection.fromItems(TrueLeaf, FalseLeaf), - SCollection.BitShiftRightZeroedMethod, - Vector(IntConstant(2)) - ) - } - property("Collection.indices") { comp("Coll(true, false).indices") shouldBe mkMethodCall( @@ -261,45 +231,11 @@ class SigmaCompilerTest extends SigmaTestingCommons with LangTests with ObjectGe ) } - // TODO soft-fork: enable after such lambda is implemented in CollCoster.flatMap - ignore("SCollection.flatMap") { - comp("OUTPUTS.flatMap({ (out: Box) => Coll(out.value >= 1L) })") shouldBe - mkMethodCall(Outputs, - SCollection.FlatMapMethod.withConcreteTypes(Map(SCollection.tIV -> SBox, SCollection.tOV -> SBoolean)), - Vector(FuncValue(1,SBox, - ConcreteCollection(Array(GE(ExtractAmount(ValUse(1, SBox)), LongConstant(1))), SBoolean))), Map()) - } - - // TODO soft-fork: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/486 - ignore("SNumeric.toBytes") { - comp("4.toBytes") shouldBe - mkMethodCall(IntConstant(4), SInt.method("toBytes").get, IndexedSeq()) - } - - // TODO soft-fork: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/486 - ignore("SNumeric.toBits") { - comp("4.toBits") shouldBe - mkMethodCall(IntConstant(4), SInt.method("toBits").get, IndexedSeq()) - } - - // TODO soft-fork: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/327 - ignore("SBigInt.multModQ") { - comp("1.toBigInt.multModQ(2.toBigInt)") shouldBe - mkMethodCall(BigIntConstant(1), SBigInt.MultModQMethod, IndexedSeq(BigIntConstant(2))) - } - property("SBox.tokens") { comp("SELF.tokens") shouldBe mkMethodCall(Self, SBox.tokensMethod, IndexedSeq()) } -//TODO soft-fork: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 -// property("SOption.toColl") { -// comp("getVar[Int](1).toColl") shouldBe -// mkMethodCall(GetVarInt(1), -// SOption.ToCollMethod.withConcreteTypes(Map(SOption.tT -> SInt)), IndexedSeq(), Map()) -// } - property("SContext.dataInputs") { comp("CONTEXT.dataInputs") shouldBe mkMethodCall(Context, SContext.dataInputsMethod, IndexedSeq()) @@ -336,18 +272,6 @@ class SigmaCompilerTest extends SigmaTestingCommons with LangTests with ObjectGe ) } -// TODO soft-fork: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 -// property("SOption.flatMap") { -// comp("getVar[Int](1).flatMap({(i: Int) => getVar[Int](2)})") shouldBe -// mkMethodCall(GetVarInt(1), -// SOption.FlatMapMethod.withConcreteTypes(Map(SOption.tT -> SInt, SOption.tR -> SInt)), -// IndexedSeq(Terms.Lambda( -// Vector(("i", SInt)), -// SOption(SInt), -// Some(GetVarInt(2)))), -// Map()) -// } - property("SCollection.patch") { comp("Coll(1, 2).patch(1, Coll(3), 1)") shouldBe mkMethodCall( @@ -375,50 +299,6 @@ class SigmaCompilerTest extends SigmaTestingCommons with LangTests with ObjectGe Map()) } - // TODO soft-fork: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 - ignore("SCollection.unionSets") { - comp("Coll(1, 2).unionSets(Coll(1))") shouldBe - mkMethodCall( - ConcreteCollection.fromItems(IntConstant(1), IntConstant(2)), - SCollection.UnionSetsMethod.withConcreteTypes(Map(SCollection.tIV -> SInt)), - Vector(ConcreteCollection.fromItems(IntConstant(1))), - Map()) - } - - // TODO soft-fork: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 - ignore("SCollection.diff") { - comp("Coll(1, 2).diff(Coll(1))") shouldBe - mkMethodCall( - ConcreteCollection.fromItems(IntConstant(1), IntConstant(2)), - SCollection.DiffMethod.withConcreteTypes(Map(SCollection.tIV -> SInt)), - Vector(ConcreteCollection.fromItems(IntConstant(1))), - Map()) - } - - // TODO soft-fork: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 - ignore("SCollection.intersect") { - comp("Coll(1, 2).intersect(Coll(1))") shouldBe - mkMethodCall( - ConcreteCollection.fromItems(IntConstant(1), IntConstant(2)), - SCollection.IntersectMethod.withConcreteTypes(Map(SCollection.tIV -> SInt)), - Vector(ConcreteCollection.fromItems(IntConstant(1))), - Map()) - } - - // TODO soft-fork: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 - ignore("SCollection.prefixLength") { - comp("OUTPUTS.prefixLength({ (out: Box) => out.value >= 1L })") shouldBe - mkMethodCall(Outputs, - SCollection.PrefixLengthMethod.withConcreteTypes(Map(SCollection.tIV -> SBox)), - Vector( - Terms.Lambda( - Vector(("out",SBox)), - SBoolean, - Some(GE(ExtractAmount(Ident("out",SBox).asBox),LongConstant(1)))) - ), - Map()) - } - property("SCollection.indexOf") { comp("Coll(1, 2).indexOf(1, 0)") shouldBe mkMethodCall( @@ -428,60 +308,6 @@ class SigmaCompilerTest extends SigmaTestingCommons with LangTests with ObjectGe Map()) } - // TODO soft-fork: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 - ignore("SCollection.lastIndexOf") { - comp("Coll(1, 2).lastIndexOf(1, 0)") shouldBe - mkMethodCall( - ConcreteCollection.fromItems(IntConstant(1), IntConstant(2)), - SCollection.LastIndexOfMethod.withConcreteTypes(Map(SCollection.tIV -> SInt)), - Vector(IntConstant(1), IntConstant(0)), - Map()) - } - - // TODO soft-fork: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 - ignore("SCollection.find") { - comp("OUTPUTS.find({ (out: Box) => out.value >= 1L })") shouldBe - mkMethodCall(Outputs, - SCollection.FindMethod.withConcreteTypes(Map(SCollection.tIV -> SBox)), - Vector( - Terms.Lambda( - Vector(("out",SBox)), - SBoolean, - Some(GE(ExtractAmount(Ident("out",SBox).asBox),LongConstant(1)))) - ), - Map()) - } - - // TODO soft-fork: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 - ignore("Collection.distinct") { - comp("Coll(true, false).distinct") shouldBe - mkMethodCall( - ConcreteCollection.fromItems(TrueLeaf, FalseLeaf), - SCollection.DistinctMethod.withConcreteTypes(Map(SCollection.tIV -> SBoolean)), - Vector() - ) - } - - // TODO soft-fork: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 - ignore("SCollection.startsWith") { - comp("Coll(1, 2).startsWith(Coll(1), 1)") shouldBe - mkMethodCall( - ConcreteCollection.fromItems(IntConstant(1), IntConstant(2)), - SCollection.StartsWithMethod.withConcreteTypes(Map(SCollection.tIV -> SInt)), - Vector(ConcreteCollection.fromItems(IntConstant(1)), IntConstant(1)), - Map()) - } - - // TODO soft-fork: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 - ignore("SCollection.endsWith") { - comp("Coll(1, 2).endsWith(Coll(1))") shouldBe - mkMethodCall( - ConcreteCollection.fromItems(IntConstant(1), IntConstant(2)), - SCollection.EndsWithMethod.withConcreteTypes(Map(SCollection.tIV -> SInt)), - Vector(ConcreteCollection.fromItems(IntConstant(1))), - Map()) - } - property("SCollection.zip") { comp("Coll(1, 2).zip(Coll(1, 1))") shouldBe mkMethodCall( @@ -491,34 +317,6 @@ class SigmaCompilerTest extends SigmaTestingCommons with LangTests with ObjectGe ) } - // TODO soft-fork: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 - ignore("SCollection.mapReduce") { - comp( - "Coll(1, 2).mapReduce({ (i: Int) => (i > 0, i.toLong) }, { (tl: (Long, Long)) => tl._1 + tl._2 })") shouldBe - mkMethodCall( - ConcreteCollection.fromItems(IntConstant(1), IntConstant(2)), - SCollection.MapReduceMethod.withConcreteTypes(Map(SCollection.tIV -> SInt, SType.tK -> SBoolean, SType.tV -> SLong)), - Vector( - Lambda(List(), - Vector(("i", SInt)), - STuple(SBoolean, SLong), - Some(Tuple(Vector( - GT(Ident("i", SInt).asIntValue, IntConstant(0)), - Upcast(Ident("i", SInt).asIntValue, SLong) - ))) - ), - Lambda(List(), - Vector(("tl", STuple(SLong, SLong))), - SLong, - Some(Plus( - SelectField(Ident("tl", STuple(SLong, SLong)).asValue[STuple], 1).asInstanceOf[Value[SLong.type]], - SelectField(Ident("tl", STuple(SLong, SLong)).asValue[STuple], 2).asInstanceOf[Value[SLong.type]]) - ) - ) - ), - Map()) - } - property("SCollection.filter") { comp("OUTPUTS.filter({ (out: Box) => out.value >= 1L })") shouldBe mkFilter(Outputs, From 04da87a7b887745cda544e873855525ba9958ef1 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Wed, 30 Jun 2021 19:07:18 +0400 Subject: [PATCH 17/56] prepare-v5.0: part 3: ScalaDocs in transformers.scala --- .../sigmastate/utxo/ComplexityTableStat.scala | 2 +- .../scala/sigmastate/utxo/transformers.scala | 101 +++++++++++++++++- 2 files changed, 101 insertions(+), 2 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/utxo/ComplexityTableStat.scala b/sigmastate/src/main/scala/sigmastate/utxo/ComplexityTableStat.scala index a8502849d3..c9f68138d9 100644 --- a/sigmastate/src/main/scala/sigmastate/utxo/ComplexityTableStat.scala +++ b/sigmastate/src/main/scala/sigmastate/utxo/ComplexityTableStat.scala @@ -9,7 +9,7 @@ import sigmastate.serialization.OpCodes import scala.collection.mutable object ComplexityTableStat { - // NOTE: make immutable before making public + // NOTE: this class is mutable so better to keep it private private class StatItem( /** How many times the operation has been executed */ var count: Long, diff --git a/sigmastate/src/main/scala/sigmastate/utxo/transformers.scala b/sigmastate/src/main/scala/sigmastate/utxo/transformers.scala index 9deb31c0e1..11376c95f8 100644 --- a/sigmastate/src/main/scala/sigmastate/utxo/transformers.scala +++ b/sigmastate/src/main/scala/sigmastate/utxo/transformers.scala @@ -9,12 +9,27 @@ import sigmastate.serialization.OpCodes import org.ergoplatform.ErgoBox.RegisterId import sigmastate.Operations._ import sigmastate.lang.exceptions.InterpreterException +import special.sigma.Box +// TODO refactor: remove this trait as it doesn't have semantic meaning + +/** Every operation is a transformer of some kind. + * This trait is used merely to simplify implementation and avoid copy-paste. + */ trait Transformer[IV <: SType, OV <: SType] extends NotReadyValue[OV] { val input: Value[IV] } +/** Builds a new collection by applying a function to all elements of this collection. + * + * @param input the collection to be mapped + * @param mapper the function to apply to each element. + * @tparam IV the element type of the input collection. + * @tparam OV the element type of the returned collection. + * @return a new collection of type `Coll[OV]` resulting from applying the given function + * `mapper` to each element of this collection and collecting the results. + */ case class MapCollection[IV <: SType, OV <: SType]( input: Value[SCollection[IV]], mapper: Value[SFunc]) @@ -27,6 +42,9 @@ object MapCollection extends ValueCompanion { override def opCode: OpCode = OpCodes.MapCollectionCode } +/** Puts the elements of other collection `col2` after the elements of `input` collection + * (concatenation of two collections). + */ case class Append[IV <: SType](input: Value[SCollection[IV]], col2: Value[SCollection[IV]]) extends Transformer[SCollection[IV], SCollection[IV]] { override def companion = Append @@ -37,6 +55,14 @@ object Append extends ValueCompanion { override def opCode: OpCode = OpCodes.AppendCode } +/** Selects an interval of elements. The returned collection is made up + * of all elements `x` which satisfy the invariant: + * {{{ + * from <= indexOf(x) < until + * }}} + * @param from the lowest index to include from this collection. + * @param until the lowest index to EXCLUDE from this collection. + */ case class Slice[IV <: SType](input: Value[SCollection[IV]], from: Value[SInt.type], until: Value[SInt.type]) extends Transformer[SCollection[IV], SCollection[IV]] { override def companion = Slice @@ -50,6 +76,13 @@ object Slice extends ValueCompanion { override def opCode: OpCode = OpCodes.SliceCode } +/** Selects all elements of `input` collection which satisfy the condition. + * + * @param input the collection to be filtered + * @param condition the predicate used to test elements. + * @return a new collection consisting of all elements of this collection that satisfy + * the given `condition`. The order of the elements is preserved. + */ case class Filter[IV <: SType](input: Value[SCollection[IV]], condition: Value[SFunc]) extends Transformer[SCollection[IV], SCollection[IV]] { @@ -61,6 +94,7 @@ object Filter extends ValueCompanion { override def opCode: OpCode = OpCodes.FilterCode } +/** Transforms a collection of values to a boolean (see [[Exists]], [[ForAll]]). */ trait BooleanTransformer[IV <: SType] extends Transformer[SCollection[IV], SBoolean.type] { override val input: Value[SCollection[IV]] val condition: Value[SFunc] @@ -70,6 +104,13 @@ trait BooleanTransformerCompanion extends ValueCompanion { def argInfos: Seq[ArgInfo] } +/** Tests whether a predicate holds for at least one element of this collection. + * + * @param input the collection to be tested + * @param condition the predicate used to test elements. + * @return `true` if the given `condition` is satisfied by at least one element of this + * collection, otherwise `false` + */ case class Exists[IV <: SType](override val input: Value[SCollection[IV]], override val condition: Value[SFunc]) extends BooleanTransformer[IV] { @@ -81,6 +122,13 @@ object Exists extends BooleanTransformerCompanion { override def argInfos: Seq[ArgInfo] = ExistsInfo.argInfos } +/** Tests whether a predicate holds for all elements of this collection. + * + * @param input the collection to be tested + * @param condition the predicate used to test elements. + * @return `true` if this collection is empty or the given `condition` + * holds for all elements of this collection, otherwise `false`. + */ case class ForAll[IV <: SType](override val input: Value[SCollection[IV]], override val condition: Value[SFunc]) extends BooleanTransformer[IV] { @@ -92,6 +140,21 @@ object ForAll extends BooleanTransformerCompanion { override def argInfos: Seq[ArgInfo] = ForAllInfo.argInfos } +/** Applies a binary function to a start value and all elements of this collection, + * going left to right. + * + * @param input the collection to iterate + * @param zero the start value. + * @param foldOp the binary function. + * @tparam OV the result type of the binary operator. + * @return the result of inserting `foldOp` between consecutive elements of this collection, + * going left to right with the start value `zero` on the left: + * {{{ + * foldOp(...foldOp(zero, x_1), x_2, ..., x_n) + * }}} + * where `x_1, ..., x_n` are the elements of this collection. + * Returns `zero` if this collection is empty. + */ case class Fold[IV <: SType, OV <: SType](input: Value[SCollection[IV]], zero: Value[OV], foldOp: Value[SFunc]) @@ -113,6 +176,15 @@ object Fold extends ValueCompanion { ) } +/** The element of the collection or default value. + * If an index is out of bounds (`i < 0 || i >= length`) then `default` value is returned. + * + * @param input the zero-based indexed collection + * @param index the index of the requested element (zero-based) + * @tparam V the type of elements + * @return the element at the given index or `default` value if index is out or bounds + * @throws ArrayIndexOutOfBoundsException if `index < 0` or `length <= index` + */ case class ByIndex[V <: SType](input: Value[SCollection[V]], index: Value[SInt.type], default: Option[Value[V]] = None) @@ -125,7 +197,9 @@ object ByIndex extends ValueCompanion { override def opCode: OpCode = OpCodes.ByIndexCode } -/** Select tuple field by its 1-based index. E.g. input._1 is transformed to SelectField(input, 1) */ +/** Select tuple field by its 1-based index. E.g. input._1 is transformed to + * SelectField(input, 1) + */ case class SelectField(input: Value[STuple], fieldIndex: Byte) extends Transformer[STuple, SType] with NotReadyValue[SType] { override def companion = SelectField @@ -162,6 +236,8 @@ object SigmaPropBytes extends ValueCompanion { trait SimpleTransformerCompanion extends ValueCompanion { def argInfos: Seq[ArgInfo] } + +/** The length of the collection (aka size). */ case class SizeOf[V <: SType](input: Value[SCollection[V]]) extends Transformer[SCollection[V], SInt.type] with NotReadyValueInt { override def companion = SizeOf @@ -176,6 +252,7 @@ object SizeOf extends SimpleTransformerCompanion { sealed trait Extract[V <: SType] extends Transformer[SBox.type, V] { } +/** Extracts the monetary value, in Ergo tokens (NanoErg unit of measure) from input Box. */ case class ExtractAmount(input: Value[SBox.type]) extends Extract[SLong.type] with NotReadyValueLong { override def companion = ExtractAmount override def opType = ExtractAmount.OpType @@ -186,6 +263,10 @@ object ExtractAmount extends SimpleTransformerCompanion { override def argInfos: Seq[ArgInfo] = ExtractAmountInfo.argInfos } +/** Extract serialized bytes of guarding script. + * As a reminder, the script should be evaluated to true in order to + * open this box. (aka spend it in a transaction). + */ case class ExtractScriptBytes(input: Value[SBox.type]) extends Extract[SByteArray] with NotReadyValueByteArray { override def companion = ExtractScriptBytes override def opType = ExtractScriptBytes.OpType @@ -196,6 +277,7 @@ object ExtractScriptBytes extends SimpleTransformerCompanion { override def argInfos: Seq[ArgInfo] = ExtractScriptBytesInfo.argInfos } +/** Extracts serialized bytes of this box's content, including proposition bytes. */ case class ExtractBytes(input: Value[SBox.type]) extends Extract[SByteArray] with NotReadyValueByteArray { override def companion = ExtractBytes override def opType = ExtractBytes.OpType @@ -206,6 +288,7 @@ object ExtractBytes extends SimpleTransformerCompanion { override def argInfos: Seq[ArgInfo] = ExtractBytesInfo.argInfos } +/** Extracts serialized bytes of this box's content, excluding transactionId and index of output. */ case class ExtractBytesWithNoRef(input: Value[SBox.type]) extends Extract[SByteArray] with NotReadyValueByteArray { override def companion = ExtractBytesWithNoRef override def opType = ExtractBytesWithNoRef.OpType @@ -216,6 +299,7 @@ object ExtractBytesWithNoRef extends SimpleTransformerCompanion { override def argInfos: Seq[ArgInfo] = ExtractBytesWithNoRefInfo.argInfos } +/** Extracts Blake2b256 hash of this box's content, basically equals to `blake2b256(bytes)` */ case class ExtractId(input: Value[SBox.type]) extends Extract[SByteArray] with NotReadyValueByteArray { override def companion = ExtractId override def opType = ExtractId.OpType @@ -226,6 +310,7 @@ object ExtractId extends SimpleTransformerCompanion { override def argInfos: Seq[ArgInfo] = ExtractIdInfo.argInfos } +/** See [[Box.getReg()]]*/ case class ExtractRegisterAs[V <: SType]( input: Value[SBox.type], registerId: RegisterId, override val tpe: SOption[V]) @@ -290,6 +375,7 @@ object DeserializeRegister extends ValueCompanion { override def opCode: OpCode = OpCodes.DeserializeRegisterCode } +/** See [[special.sigma.Context.getVar()]] for detailed description. */ case class GetVar[V <: SType](varId: Byte, override val tpe: SOption[V]) extends NotReadyValue[SOption[V]] { override def companion = GetVar override val opType = SFunc(Array(SContext, SByte), tpe) @@ -299,6 +385,11 @@ object GetVar extends ValueCompanion { def apply[V <: SType](varId: Byte, innerTpe: V): GetVar[V] = GetVar[V](varId, SOption(innerTpe)) } +/** Returns the option's value. + * + * @note The option must be nonempty. + * @throws java.util.NoSuchElementException if the option is empty. + */ case class OptionGet[V <: SType](input: Value[SOption[V]]) extends Transformer[SOption[V], V] { override def companion = OptionGet override val opType = SFunc(input.tpe, tpe) @@ -310,6 +401,13 @@ object OptionGet extends SimpleTransformerCompanion { override def argInfos: Seq[ArgInfo] = OptionGetInfo.argInfos } +/** Returns the option's value if the option is nonempty, otherwise + * return the result of evaluating `default`. + * NOTE: the `default` is evaluated even if the option contains the value + * i.e. not lazily. + * + * @param default the default expression. + */ case class OptionGetOrElse[V <: SType](input: Value[SOption[V]], default: Value[V]) extends Transformer[SOption[V], V] { override def companion = OptionGetOrElse @@ -320,6 +418,7 @@ object OptionGetOrElse extends ValueCompanion { override def opCode: OpCode = OpCodes.OptionGetOrElseCode } +/** Returns false if the option is None, true otherwise. */ case class OptionIsDefined[V <: SType](input: Value[SOption[V]]) extends Transformer[SOption[V], SBoolean.type] { override def companion = OptionIsDefined From cd8b1c43f0072c4a287786d82b5736a3a1e97673 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Thu, 1 Jul 2021 23:45:42 +0400 Subject: [PATCH 18/56] prepare-v5.0: part 3: changes in specs --- .../ergoplatform/ErgoAddressSpecification.scala | 17 +++++++++-------- .../org/ergoplatform/ErgoScriptPredefSpec.scala | 2 +- .../sigmastate/CalcSha256Specification.scala | 4 ++-- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/sigmastate/src/test/scala/org/ergoplatform/ErgoAddressSpecification.scala b/sigmastate/src/test/scala/org/ergoplatform/ErgoAddressSpecification.scala index 743f95e90d..f8dca4c300 100644 --- a/sigmastate/src/test/scala/org/ergoplatform/ErgoAddressSpecification.scala +++ b/sigmastate/src/test/scala/org/ergoplatform/ErgoAddressSpecification.scala @@ -2,26 +2,27 @@ package org.ergoplatform import java.math.BigInteger -import org.ergoplatform.ErgoAddressEncoder.{MainnetNetworkPrefix, TestnetNetworkPrefix, hash256} +import org.ergoplatform.ErgoAddressEncoder.{hash256, MainnetNetworkPrefix, TestnetNetworkPrefix} import org.ergoplatform.SigmaConstants.ScriptCostLimit import org.ergoplatform.validation.{ValidationException, ValidationRules} import org.scalatest.{Assertion, TryValues} import scorex.crypto.hash.Blake2b256 import sigmastate.basics.DLogProtocol -import sigmastate.basics.DLogProtocol.{DLogProverInput, ProveDlog} +import sigmastate.basics.DLogProtocol.{ProveDlog, DLogProverInput} import sigmastate.serialization.ErgoTreeSerializer.DefaultSerializer -import sigmastate.serialization.{GroupElementSerializer, ValueSerializer} +import sigmastate.serialization.{ValueSerializer, GroupElementSerializer} import scorex.util.encode.Base58 -import sigmastate.{CrossVersionProps, SType, SigmaAnd} -import sigmastate.Values.{ByteArrayConstant, Constant, ErgoTree, EvaluatedValue, IntConstant, UnparsedErgoTree} +import sigmastate.{CrossVersionProps, SigmaAnd, SType} +import sigmastate.Values.{UnparsedErgoTree, Constant, EvaluatedValue, ByteArrayConstant, IntConstant, ErgoTree} import sigmastate.eval.IRContext import sigmastate.helpers._ import sigmastate.helpers.TestingHelpers._ +import sigmastate.interpreter.ContextExtension.VarBinding import sigmastate.interpreter.CryptoConstants.dlogGroup import sigmastate.interpreter.{ContextExtension, CostedProverResult} -import sigmastate.interpreter.Interpreter.{ScriptEnv, ScriptNameProp} +import sigmastate.interpreter.Interpreter.{ScriptNameProp, ScriptEnv} import sigmastate.lang.Terms.ValueOps -import sigmastate.lang.exceptions.{CostLimitException, CosterException} +import sigmastate.lang.exceptions.{CosterException, CostLimitException} import sigmastate.utils.Helpers._ import special.sigma.SigmaDslTesting @@ -259,7 +260,7 @@ class ErgoAddressSpecification extends SigmaDslTesting property("negative cases: deserialized script + costing exceptions") { implicit lazy val IR = new TestingIRContext - def testPay2SHAddress(address: Pay2SHAddress, script: (Byte, EvaluatedValue[_ <: SType]), costLimit: Int = ScriptCostLimit.value): CostedProverResult = { + def testPay2SHAddress(address: Pay2SHAddress, script: VarBinding, costLimit: Int = ScriptCostLimit.value): CostedProverResult = { val boxToSpend = testBox(10, address.script, creationHeight = 5) val ctx = copyContext(ErgoLikeContextTesting.dummy(boxToSpend, activatedVersionInTests) .withExtension(ContextExtension(Seq( diff --git a/sigmastate/src/test/scala/org/ergoplatform/ErgoScriptPredefSpec.scala b/sigmastate/src/test/scala/org/ergoplatform/ErgoScriptPredefSpec.scala index 1d9991520b..883365f4fb 100644 --- a/sigmastate/src/test/scala/org/ergoplatform/ErgoScriptPredefSpec.scala +++ b/sigmastate/src/test/scala/org/ergoplatform/ErgoScriptPredefSpec.scala @@ -164,7 +164,7 @@ class ErgoScriptPredefSpec extends SigmaTestingCommons with CrossVersionProps { // collect coins during the fixed rate period forAll(Gen.choose(1, settings.fixedRatePeriod)) { height => val currentRate = emission.minersRewardAtHeight(height) - createRewardTx(currentRate, height, minerProp) shouldBe 'success + createRewardTx(currentRate, height, minerProp).getOrThrow createRewardTx(currentRate + 1, height, minerProp) shouldBe 'failure createRewardTx(currentRate - 1, height, minerProp) shouldBe 'failure } diff --git a/sigmastate/src/test/scala/sigmastate/CalcSha256Specification.scala b/sigmastate/src/test/scala/sigmastate/CalcSha256Specification.scala index 28a28c9d91..db3922960e 100644 --- a/sigmastate/src/test/scala/sigmastate/CalcSha256Specification.scala +++ b/sigmastate/src/test/scala/sigmastate/CalcSha256Specification.scala @@ -24,7 +24,7 @@ class CalcSha256Specification extends SigmaTestingCommons ("", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"), ("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1"), ("abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu", "cf5b16a778af8380036ce59e7b0492370b249b11e8f07a51afac45037afee9d1"), - (Array.fill[String](1000000)("a").mkString, "cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0") + ("a" * 1000, "41edece42d63e8d9bf515a9ba6932e1c20cbc9f5a5d134645adb5db1b9737ea3") ) property("CalcSha256: Should pass standard tests.") { @@ -33,7 +33,7 @@ class CalcSha256Specification extends SigmaTestingCommons forAll(objects) { (in, result) => val expectedResult = decodeString(result) val calcSha256 = EQ(CalcSha256(stringToByteConstant(in)), expectedResult) - val res = int.reduceToCrypto(ctx, calcSha256).get.value + val res = int.reduceToCrypto(ctx, calcSha256.toSigmaProp).get.value res shouldBe TrivialProp.TrueProp } } From 1cc15b9476c8cf741330657f71a5332b26d10245 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Fri, 2 Jul 2021 23:24:10 +0400 Subject: [PATCH 19/56] prepare-v5.0: part 3: changes in specs (2) --- .../TestingInterpreterSpecification.scala | 56 +++++++++--------- .../sigmastate/helpers/NegativeTesting.scala | 57 ++++++++++++++++++- .../sigmastate/lang/SigmaTyperTest.scala | 24 -------- 3 files changed, 84 insertions(+), 53 deletions(-) diff --git a/sigmastate/src/test/scala/sigmastate/TestingInterpreterSpecification.scala b/sigmastate/src/test/scala/sigmastate/TestingInterpreterSpecification.scala index e50723f5f0..8f475aa3e7 100644 --- a/sigmastate/src/test/scala/sigmastate/TestingInterpreterSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/TestingInterpreterSpecification.scala @@ -48,30 +48,30 @@ class TestingInterpreterSpecification extends SigmaTestingCommons forAll() { i: Int => val h = i.toAbs whenever(h > 0 && h < Int.MaxValue - 1) { - val dk1 = SigmaPropConstant(DLogProverInput.random().publicImage).isProven + val dk1 = SigmaPropConstant(DLogProverInput.random().publicImage) val ctx = testingContext(h) - prover.reduceToCrypto(ctx, AND(GE(Height, IntConstant(h - 1)), dk1)).get.value should( + prover.reduceToCrypto(ctx, SigmaAnd(GE(Height, IntConstant(h - 1)), dk1)).get.value should( matchPattern { case _: SigmaBoolean => }) - prover.reduceToCrypto(ctx, AND(GE(Height, IntConstant(h)), dk1)).get.value should ( + prover.reduceToCrypto(ctx, SigmaAnd(GE(Height, IntConstant(h)), dk1)).get.value should ( matchPattern { case _: SigmaBoolean => }) { - val res = prover.reduceToCrypto(ctx, AND(GE(Height, IntConstant(h + 1)), dk1)).get.value + val res = prover.reduceToCrypto(ctx, SigmaAnd(GE(Height, IntConstant(h + 1)), dk1)).get.value res should matchPattern { case TrivialProp.FalseProp => } } { - val res = prover.reduceToCrypto(ctx, OR(GE(Height, IntConstant(h - 1)), dk1)).get.value + val res = prover.reduceToCrypto(ctx, SigmaOr(GE(Height, IntConstant(h - 1)), dk1)).get.value res should matchPattern { case TrivialProp.TrueProp => } } { - val res = prover.reduceToCrypto(ctx, OR(GE(Height, IntConstant(h)), dk1)).get.value + val res = prover.reduceToCrypto(ctx, SigmaOr(GE(Height, IntConstant(h)), dk1)).get.value res should matchPattern { case TrivialProp.TrueProp => } } { - val res = prover.reduceToCrypto(ctx, OR(GE(Height, IntConstant(h + 1)), dk1)).get.value + val res = prover.reduceToCrypto(ctx, SigmaOr(GE(Height, IntConstant(h + 1)), dk1)).get.value res should matchPattern { case _: SigmaBoolean => } } } @@ -83,32 +83,32 @@ class TestingInterpreterSpecification extends SigmaTestingCommons val h = i.toAbs whenever(h > 0 && h < Int.MaxValue - 1) { - val dk1 = DLogProverInput.random().publicImage.isProven - val dk2 = DLogProverInput.random().publicImage.isProven + val dk1 = DLogProverInput.random().publicImage + val dk2 = DLogProverInput.random().publicImage val ctx = testingContext(h) - assert(prover.reduceToCrypto(ctx, OR( - AND(LE(Height, IntConstant(h + 1)), AND(dk1, dk2)), - AND(GT(Height, IntConstant(h + 1)), dk1) + assert(prover.reduceToCrypto(ctx, SigmaOr( + SigmaAnd(LE(Height, IntConstant(h + 1)), SigmaAnd(dk1, dk2)), + SigmaAnd(GT(Height, IntConstant(h + 1)), dk1) )).get.value.isInstanceOf[CAND]) - assert(prover.reduceToCrypto(ctx, OR( - AND(LE(Height, IntConstant(h - 1)), AND(dk1, dk2)), - AND(GT(Height, IntConstant(h - 1)), dk1) + assert(prover.reduceToCrypto(ctx, SigmaOr( + SigmaAnd(LE(Height, IntConstant(h - 1)), SigmaAnd(dk1, dk2)), + SigmaAnd(GT(Height, IntConstant(h - 1)), dk1) )).get.value.isInstanceOf[ProveDlog]) - prover.reduceToCrypto(ctx, OR( - AND(LE(Height, IntConstant(h - 1)), AND(dk1, dk2)), - AND(GT(Height, IntConstant(h + 1)), dk1) + prover.reduceToCrypto(ctx, SigmaOr( + SigmaAnd(LE(Height, IntConstant(h - 1)), SigmaAnd(dk1, dk2)), + SigmaAnd(GT(Height, IntConstant(h + 1)), dk1) )).get.value shouldBe TrivialProp.FalseProp prover.reduceToCrypto(ctx, - OR( - OR( - AND(LE(Height, IntConstant(h - 1)), AND(dk1, dk2)), - AND(GT(Height, IntConstant(h + 1)), dk1) + SigmaOr( + SigmaOr( + SigmaAnd(LE(Height, IntConstant(h - 1)), SigmaAnd(dk1, dk2)), + SigmaAnd(GT(Height, IntConstant(h + 1)), dk1) ), AND(GT(Height, IntConstant(h - 1)), LE(Height, IntConstant(h + 1))) ) @@ -260,16 +260,16 @@ class TestingInterpreterSpecification extends SigmaTestingCommons } property("Evaluation example #1") { - val dk1 = prover.dlogSecrets(0).publicImage.isProven - val dk2 = prover.dlogSecrets(1).publicImage.isProven + val dk1 = prover.dlogSecrets(0).publicImage + val dk2 = prover.dlogSecrets(1).publicImage val env1 = testingContext(99) val env2 = testingContext(101) - val prop = mkTestErgoTree(OR( - AND(LE(Height, IntConstant(100)), AND(dk1, dk2)), - AND(GT(Height, IntConstant(100)), dk1) - ).toSigmaProp) + val prop = mkTestErgoTree(SigmaOr( + SigmaAnd(LE(Height, IntConstant(100)), SigmaAnd(dk1, dk2)), + SigmaAnd(GT(Height, IntConstant(100)), dk1) + )) val challenge = Array.fill(32)(Random.nextInt(100).toByte) diff --git a/sigmastate/src/test/scala/sigmastate/helpers/NegativeTesting.scala b/sigmastate/src/test/scala/sigmastate/helpers/NegativeTesting.scala index a4bda66032..6748826b35 100644 --- a/sigmastate/src/test/scala/sigmastate/helpers/NegativeTesting.scala +++ b/sigmastate/src/test/scala/sigmastate/helpers/NegativeTesting.scala @@ -3,6 +3,8 @@ package sigmastate.helpers import org.scalatest.Matchers import scala.annotation.tailrec +import scala.util.{Failure, Success, Try} +import spire.syntax.all.cfor import scala.reflect.ClassTag trait NegativeTesting extends Matchers { @@ -34,6 +36,12 @@ trait NegativeTesting extends Matchers { if (t.getCause == null) t else rootCause(t.getCause) + /** If error then maps it to the root cause, otherwise returns the original value. */ + final def rootCause[A](x: Try[A]): Try[A] = x match { + case s: Success[_] => s + case Failure(t) => Failure(rootCause(t)) + } + /** Creates an assertion which checks the given type and message contents. * * @tparam E expected type of exception @@ -41,9 +49,56 @@ trait NegativeTesting extends Matchers { * @return the assertion which can be used in assertExceptionThrown method */ def exceptionLike[E <: Throwable : ClassTag] - (msgParts: String*): Throwable => Boolean = { + (msgParts: String*): Throwable => Boolean = { case t: E => msgParts.forall(t.getMessage.contains(_)) case _ => false } + /** Checks that both computations either succeed with the same value or fail with the same + * error. If this is not true, exception is thrown. + * + * @param f first computation + * @param g second computation + * @return result of the second computation `g` + */ + def sameResultOrError[B](f: => B, g: => B): Try[B] = { + val b1 = Try(f); val b2 = Try(g) + (b1, b2) match { + case (Success(b1), res @ Success(b2)) => + assert(b1 == b2) + res + case (Failure(t1), res @ Failure(t2)) => + val c1 = rootCause(t1).getClass + val c2 = rootCause(t2).getClass + c1 shouldBe c2 + res + case _ => + val cause = if (b1.isFailure) + rootCause(b1.asInstanceOf[Failure[_]].exception) + else + rootCause(b2.asInstanceOf[Failure[_]].exception) + + sys.error( + s"""Should succeed with the same value or fail with the same exception, but was: + |First result: $b1 + |Second result: $b2 + |Root cause: $cause + |""".stripMargin) + } + } + + /** Repeat the given `block` computation `nIters` times. + * + * @param nIters number of iterations to repeat the computation + * @param block the computation to execute on each iteration + * @return the result of the last iteration + */ + def repeatAndReturnLast[A](nIters: Int)(block: => A): A = { + require(nIters > 0) + var res = block + cfor(1)(_ < nIters, _ + 1) { i => + res = block + } + res + } } diff --git a/sigmastate/src/test/scala/sigmastate/lang/SigmaTyperTest.scala b/sigmastate/src/test/scala/sigmastate/lang/SigmaTyperTest.scala index 25ea44b839..e29977cd09 100644 --- a/sigmastate/src/test/scala/sigmastate/lang/SigmaTyperTest.scala +++ b/sigmastate/src/test/scala/sigmastate/lang/SigmaTyperTest.scala @@ -614,30 +614,6 @@ class SigmaTyperTest extends PropSpec with PropertyChecks with Matchers with Lan typefail(env, "true >>> false", 1, 1) } - // TODO soft-fork: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 - ignore("Collection.BitShiftLeft") { - typecheck(env, "Coll(1,2) << 2") shouldBe SCollection(SInt) - an [TyperException] should be thrownBy typecheck(env, "Coll(1,2) << true") - an [TyperException] should be thrownBy typecheck(env, "Coll(1,2) << 2L") - an [TyperException] should be thrownBy typecheck(env, "Coll(1,2) << (2L, 3)") - } - - // TODO soft-fork: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 - ignore("Collection.BitShiftRight") { - typecheck(env, "Coll(1,2) >> 2") shouldBe SCollection(SInt) - an [TyperException] should be thrownBy typecheck(env, "Coll(1,2) >> 2L") - an [TyperException] should be thrownBy typecheck(env, "Coll(1,2) >> true") - an [TyperException] should be thrownBy typecheck(env, "Coll(1,2) >> (2L, 3)") - } - - // TODO soft-fork: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 - ignore("Collection.BitShiftRightZeroed") { - typecheck(env, "Coll(true, false) >>> 2") shouldBe SCollection(SBoolean) - an [TyperException] should be thrownBy typecheck(env, "Coll(1,2) >>> 2") - an [TyperException] should be thrownBy typecheck(env, "Coll(true, false) >>> true") - an [TyperException] should be thrownBy typecheck(env, "Coll(true, false) >>> (2L, 3)") - } - property("SCollection.indices") { typecheck(env, "Coll(1).indices") shouldBe SCollection(SInt) typecheck(env, "INPUTS.indices") shouldBe SCollection(SInt) From 3d5514c8d619d70c6fda89b855c394b3edc6d99d Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Sat, 3 Jul 2021 18:07:13 +0400 Subject: [PATCH 20/56] prepare-v5.0: part 3: changes in specs (3) --- .../SelectFieldSerializerSpecification.scala | 1 + .../SigSerializerSpecification.scala | 9 ++++- .../utxo/BasicOpsSpecification.scala | 27 ++------------- .../ErgoLikeInterpreterSpecification.scala | 3 +- .../examples/CoinEmissionSpecification.scala | 4 +-- .../OracleExamplesSpecification.scala | 34 +++++++------------ 6 files changed, 29 insertions(+), 49 deletions(-) diff --git a/sigmastate/src/test/scala/sigmastate/serialization/SelectFieldSerializerSpecification.scala b/sigmastate/src/test/scala/sigmastate/serialization/SelectFieldSerializerSpecification.scala index 960ac4bddb..678ff1a241 100644 --- a/sigmastate/src/test/scala/sigmastate/serialization/SelectFieldSerializerSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/serialization/SelectFieldSerializerSpecification.scala @@ -9,6 +9,7 @@ class SelectFieldSerializerSpecification extends TableSerializationSpecification property("SelectField: Serializer round trip ") { forAll(tupleGen(2, 10)) { tuple: Tuple => + // TODO refactor: avoid usage of extension method `length` val index = Gen.chooseNum(1, tuple.length - 1).sample.get roundTripTest(SelectField(tuple, index.toByte)) } diff --git a/sigmastate/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala b/sigmastate/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala index 9cb3f8ee26..97c94c73a3 100644 --- a/sigmastate/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/serialization/SigSerializerSpecification.scala @@ -517,12 +517,19 @@ class SigSerializerSpecification extends SigmaTestingCommons property("Invalid signature parsing") { forAll { bytes: Array[Byte] => val r = SigmaSerializer.startReader(bytes) - val readBytes = r.getBytesUnsafe(bytes.length + 1) // request more than present + val nRequested = bytes.length + 1 // request more than present + val readBytes = r.getBytesUnsafe(nRequested) readBytes shouldBe bytes // we now at the limit position of reader, and still can get all buffer bytes val allBytes = r.getAllBufferBytes allBytes shouldBe bytes + + r.position = 0 + var reported = false + val res = SigSerializer.readBytesChecked(r, nRequested, msg => reported = true) + res shouldBe bytes + reported shouldBe true } } } diff --git a/sigmastate/src/test/scala/sigmastate/utxo/BasicOpsSpecification.scala b/sigmastate/src/test/scala/sigmastate/utxo/BasicOpsSpecification.scala index a08959d40b..9baade2639 100644 --- a/sigmastate/src/test/scala/sigmastate/utxo/BasicOpsSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/utxo/BasicOpsSpecification.scala @@ -15,6 +15,7 @@ import sigmastate.interpreter.Interpreter._ import sigmastate.lang.Terms._ import special.sigma.InvalidType import SType.AnyOps +import sigmastate.interpreter.ContextExtension.VarBinding import sigmastate.interpreter.CryptoConstants import sigmastate.utils.Helpers._ @@ -40,7 +41,7 @@ class BasicOpsSpecification extends SigmaTestingCommons val propVar2 = 11.toByte val lastExtVar = propVar2 - val ext: Seq[(Byte, EvaluatedValue[_ <: SType])] = Seq( + val ext: Seq[VarBinding] = Seq( (intVar1, IntConstant(1)), (intVar2, IntConstant(2)), (byteVar1, ByteConstant(1)), (byteVar2, ByteConstant(2)), (bigIntVar1, BigIntConstant(BigInt(10).underlying())), (bigIntVar2, BigIntConstant(BigInt(20).underlying())), @@ -55,7 +56,7 @@ class BasicOpsSpecification extends SigmaTestingCommons ) def test(name: String, env: ScriptEnv, - ext: Seq[(Byte, EvaluatedValue[_ <: SType])], + ext: Seq[VarBinding], script: String, propExp: SValue, onlyPositive: Boolean = true) = { val prover = new ContextEnrichingTestProvingInterpreter() { @@ -354,22 +355,6 @@ class BasicOpsSpecification extends SigmaTestingCommons rootCause(_).isInstanceOf[InvalidType]) } - // TODO related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/416 - ignore("Box.getReg") { - test("Extract1", env, ext, - "{ SELF.getReg[Int]( (getVar[Int](intVar1).get + 4)).get == 1}", - BoolToSigmaProp( - EQ( - MethodCall(Self, SBox.getRegMethod, - IndexedSeq(Plus(GetVarInt(1).get, IntConstant(4))), Map(SType.tT -> SInt) - ).asInstanceOf[Value[SOption[SType]]].get, - IntConstant(1) - ) - ), - true - ) - } - property("OptionGet success (SomeValue)") { test("Opt1", env, ext, "{ getVar[Int](intVar2).get == 2 }", @@ -524,12 +509,6 @@ class BasicOpsSpecification extends SigmaTestingCommons // println(CostTableStat.costTableString) } - //TODO soft-fork: related to https://github.com/ScorexFoundation/sigmastate-interpreter/issues/236 - ignore("ZKProof") { - test("zk1", env, ext, "ZKProof { sigmaProp(HEIGHT >= 0) }", - ZKProofBlock(BoolToSigmaProp(GE(Height, LongConstant(0)))), true) - } - property("numeric cast") { test("downcast", env, ext, "{ getVar[Int](intVar2).get.toByte == 2.toByte }", diff --git a/sigmastate/src/test/scala/sigmastate/utxo/ErgoLikeInterpreterSpecification.scala b/sigmastate/src/test/scala/sigmastate/utxo/ErgoLikeInterpreterSpecification.scala index 39d82cf304..5c304f6411 100644 --- a/sigmastate/src/test/scala/sigmastate/utxo/ErgoLikeInterpreterSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/utxo/ErgoLikeInterpreterSpecification.scala @@ -15,6 +15,7 @@ import sigmastate.basics.DLogProtocol.ProveDlog import sigmastate.basics.ProveDHTuple import sigmastate.helpers._ import sigmastate.helpers.TestingHelpers._ +import sigmastate.interpreter.ContextExtension.VarBinding import sigmastate.interpreter.{ContextExtension, CostedProverResult} import sigmastate.lang.Terms._ import sigmastate.serialization.{ValueSerializer, SerializationSpecification} @@ -718,7 +719,7 @@ class ErgoLikeInterpreterSpecification extends SigmaTestingCommons } property("DeserializeContext can return expression of non-Boolean/SigmaProp type") { - def prove(ergoTree: ErgoTree, script: (Byte, EvaluatedValue[_ <: SType])): CostedProverResult = { + def prove(ergoTree: ErgoTree, script: VarBinding): CostedProverResult = { val boxToSpend = testBox(10, ergoTree, creationHeight = 5) val ctx = ErgoLikeContextTesting.dummy(boxToSpend, activatedVersionInTests) .withExtension( diff --git a/sigmastate/src/test/scala/sigmastate/utxo/examples/CoinEmissionSpecification.scala b/sigmastate/src/test/scala/sigmastate/utxo/examples/CoinEmissionSpecification.scala index c3cff93dd8..6d40489759 100644 --- a/sigmastate/src/test/scala/sigmastate/utxo/examples/CoinEmissionSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/utxo/examples/CoinEmissionSpecification.scala @@ -130,8 +130,8 @@ block 1600 in 1622 ms, 30000000000 coins remain, defs: 61661 | val heightIncreased = HEIGHT > SELF.R4[Int].get | val heightCorrect = out.R4[Int].get == HEIGHT | val lastCoins = SELF.value <= oneEpochReduction - | allOf(Coll(heightCorrect, heightIncreased, sameScriptRule, correctCoinsConsumed)) || (heightIncreased && lastCoins) - |}""".stripMargin).asBoolValue.toSigmaProp + | sigmaProp(allOf(Coll(heightCorrect, heightIncreased, sameScriptRule, correctCoinsConsumed)) || (heightIncreased && lastCoins)) + |}""".stripMargin).asSigmaProp prop1 shouldEqual prop diff --git a/sigmastate/src/test/scala/sigmastate/utxo/examples/OracleExamplesSpecification.scala b/sigmastate/src/test/scala/sigmastate/utxo/examples/OracleExamplesSpecification.scala index c5fd41b6ae..7aaa2ef3a1 100644 --- a/sigmastate/src/test/scala/sigmastate/utxo/examples/OracleExamplesSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/utxo/examples/OracleExamplesSpecification.scala @@ -126,14 +126,14 @@ class OracleExamplesSpecification extends SigmaTestingCommons def withinTimeframe(sinceHeight: Int, timeoutHeight: Int, - fallback: Value[SBoolean.type])(script: Value[SBoolean.type]) = - OR(AND(GE(Height, IntConstant(sinceHeight)), LT(Height, IntConstant(timeoutHeight)), script), - AND(GE(Height, IntConstant(timeoutHeight)), fallback)) + fallback: SigmaPropValue)(script: SigmaPropValue) = + SigmaOr(SigmaAnd(GE(Height, IntConstant(sinceHeight)), LT(Height, IntConstant(timeoutHeight)), script), + SigmaAnd(GE(Height, IntConstant(timeoutHeight)), fallback)) - val contractLogic = OR(AND(GT(extract[SLong.type](reg1), LongConstant(15)), alicePubKey.isProven), - AND(LE(extract[SLong.type](reg1), LongConstant(15)), bobPubKey.isProven)) + val contractLogic = SigmaOr(SigmaAnd(GT(extract[SLong.type](reg1), LongConstant(15)), alicePubKey), + SigmaAnd(LE(extract[SLong.type](reg1), LongConstant(15)), bobPubKey)) - val oracleProp = AND( + val oracleProp = SigmaAnd( OptionIsDefined(IR.builder.mkMethodCall( LastBlockUtxoRootHash, SAvlTree.getMethod, IndexedSeq(ExtractId(GetVarBox(22: Byte).get), GetVarByteArray(23: Byte).get)).asOption[SByteArray]), @@ -164,7 +164,7 @@ class OracleExamplesSpecification extends SigmaTestingCommons val timeout = 60 val propAlice = mkTestErgoTree( - withinTimeframe(sinceHeight, timeout, alicePubKey.isProven)(oracleProp).toSigmaProp) + withinTimeframe(sinceHeight, timeout, alicePubKey)(oracleProp)) val sAlice = testBox(10, propAlice, 0, Seq(), Map(), boxIndex = 3) @@ -173,7 +173,7 @@ class OracleExamplesSpecification extends SigmaTestingCommons EQ(SizeOf(Inputs), IntConstant(2)), EQ(ExtractId(ByIndex(Inputs, 0)), ByteArrayConstant(sAlice.id))) val propBob = mkTestErgoTree( - withinTimeframe(sinceHeight, timeout, bobPubKey.isProven)(propAlong).toSigmaProp) + withinTimeframe(sinceHeight, timeout, bobPubKey)(propAlong)) val sBob = testBox(10, propBob, 0, Seq(), Map(), boxIndex = 4) val ctx = ErgoLikeContextTesting( @@ -236,26 +236,18 @@ class OracleExamplesSpecification extends SigmaTestingCommons additionalRegisters = Map(reg1 -> LongConstant(temperature)) ) - val contractLogic = OR( - AND( - GT( - ExtractRegisterAs[SLong.type](ByIndex(Inputs, 0), reg1).get, - LongConstant(15)), - alicePubKey.isProven), - AND( - LE( - ExtractRegisterAs[SLong.type](ByIndex(Inputs, 0), reg1).get, - LongConstant(15)), - bobPubKey.isProven) + val contractLogic = SigmaOr( + SigmaAnd(GT(ExtractRegisterAs[SLong.type](ByIndex(Inputs, 0), reg1).get, LongConstant(15)), alicePubKey), + SigmaAnd(LE(ExtractRegisterAs[SLong.type](ByIndex(Inputs, 0), reg1).get, LongConstant(15)), bobPubKey) ) - val prop = mkTestErgoTree(AND( + val prop = SigmaOr( EQ(SizeOf(Inputs), IntConstant(3)), EQ( ExtractScriptBytes(ByIndex(Inputs, 0)), ByteArrayConstant(mkTestErgoTree(oraclePubKey).bytes)), contractLogic - ).toSigmaProp) + ) val sOracle = oracleBox val sAlice = testBox(10, prop, 0, Seq(), Map()) From 65b4e9635ec1adf8c8b41ca5f95b60b68c762a5e Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Thu, 15 Jul 2021 19:06:28 +0300 Subject: [PATCH 21/56] prepare-v5.0: part 4: added Profiler and related code (CostItem, etc) --- build.sbt | 3 +- .../src/main/scala/sigmastate/CostKind.scala | 41 +++ .../src/main/scala/sigmastate/Values.scala | 27 +- .../main/scala/sigmastate/eval/Profiler.scala | 325 ++++++++++++++++++ .../interpreter/CostAccumulator.scala | 75 ++++ .../sigmastate/interpreter/CostDetails.scala | 57 +++ .../sigmastate/interpreter/CostItem.scala | 94 +++++ .../interpreter/OperationDesc.scala | 43 +++ .../lang/exceptions/Exceptions.scala | 3 + .../src/main/scala/sigmastate/types.scala | 7 +- 10 files changed, 665 insertions(+), 10 deletions(-) create mode 100644 sigmastate/src/main/scala/sigmastate/CostKind.scala create mode 100644 sigmastate/src/main/scala/sigmastate/eval/Profiler.scala create mode 100644 sigmastate/src/main/scala/sigmastate/interpreter/CostAccumulator.scala create mode 100644 sigmastate/src/main/scala/sigmastate/interpreter/CostDetails.scala create mode 100644 sigmastate/src/main/scala/sigmastate/interpreter/CostItem.scala create mode 100644 sigmastate/src/main/scala/sigmastate/interpreter/OperationDesc.scala diff --git a/build.sbt b/build.sbt index 0c8bfebbe8..7ec0e2c643 100644 --- a/build.sbt +++ b/build.sbt @@ -61,6 +61,7 @@ val debox = "org.spire-math" %% "debox" % "0.8.0" val kiama = "org.bitbucket.inkytonik.kiama" %% "kiama" % "2.1.0" val fastparse = "com.lihaoyi" %% "fastparse" % "1.0.0" val commonsIo = "commons-io" % "commons-io" % "2.5" +val commonsMath3 = "org.apache.commons" % "commons-math3" % "3.2" val specialVersion = "0.6.1" val meta = "io.github.scalan" %% "meta" % specialVersion @@ -256,7 +257,7 @@ lazy val sigmastate = (project in file("sigmastate")) .dependsOn(sigmaimpl % allConfigDependency, sigmalibrary % allConfigDependency) .settings(libraryDefSettings) .settings(libraryDependencies ++= Seq( - scorexUtil, kiama, fastparse, + scorexUtil, kiama, fastparse, commonsMath3, if (scalaVersion.value == scala211) circeCore211 else circeCore, if (scalaVersion.value == scala211) circeGeneric211 else circeGeneric, if (scalaVersion.value == scala211) circeParser211 else circeParser diff --git a/sigmastate/src/main/scala/sigmastate/CostKind.scala b/sigmastate/src/main/scala/sigmastate/CostKind.scala new file mode 100644 index 0000000000..85d20c9799 --- /dev/null +++ b/sigmastate/src/main/scala/sigmastate/CostKind.scala @@ -0,0 +1,41 @@ +package sigmastate + +/** Cost descriptor of a single operation, usually associated with + * [[sigmastate.interpreter.OperationDesc]]. + */ +sealed abstract class CostKind + +/** Descriptor of the simple fixed cost. + * @param cost given cost of the operation */ +case class FixedCost(cost: Int) extends CostKind + +/** Cost of operation over collection of the known length. + * See for example [[Exists]], [[MapCollection]]. + * @param baseCost cost of operation factored out of the loop iterations + * @param perChunkCost cost associated with each chunk of items + * @param chunkSize number of items in a chunk + */ +case class PerItemCost(baseCost: Int, perChunkCost: Int, chunkSize: Int) extends CostKind { + /** Compute number of chunks necessary to cover the given number of items. */ + def chunks(nItems: Int) = (nItems - 1) / chunkSize + 1 + + /** Computes the cost for the given number of items. */ + def cost (nItems: Int): Int = { + val nChunks = chunks(nItems) + Math.addExact(baseCost, Math.multiplyExact(perChunkCost, nChunks)) + } +} + +/** Descriptor of the cost which depends on type. */ +abstract class TypeBasedCost extends CostKind { + /** Returns cost value depending on the given type. */ + def costFunc(tpe: SType): Int +} + +/** Cost of operation cannot be described using fixed set of parameters. + * In this case the operation cost is a sum of sub-operation costs. + * See [[EQ]], [[NEQ]]. */ +case object DynamicCost extends CostKind + + + diff --git a/sigmastate/src/main/scala/sigmastate/Values.scala b/sigmastate/src/main/scala/sigmastate/Values.scala index 2c8c0a6589..ef05623286 100644 --- a/sigmastate/src/main/scala/sigmastate/Values.scala +++ b/sigmastate/src/main/scala/sigmastate/Values.scala @@ -4,14 +4,13 @@ import java.math.BigInteger import java.util import java.util.Objects -import org.bitbucket.inkytonik.kiama.relation.Tree -import org.bitbucket.inkytonik.kiama.rewriting.Rewriter.{strategy, everywherebu, count} +import org.bitbucket.inkytonik.kiama.rewriting.Rewriter.{strategy, count, everywherebu} import org.ergoplatform.validation.ValidationException import scalan.{Nullable, RType} import scalan.util.CollectionUtil._ import sigmastate.SCollection.{SIntArray, SByteArray} import sigmastate.interpreter.CryptoConstants.EcPointType -import sigmastate.interpreter.CryptoConstants +import sigmastate.interpreter.{CryptoConstants, CompanionDesc} import sigmastate.serialization.{OpCodes, ConstantStore, _} import sigmastate.serialization.OpCodes._ import sigmastate.TrivialProp.{FalseProp, TrueProp} @@ -40,14 +39,12 @@ import scala.collection.mutable object Values { - type SigmaTree = Tree[SigmaNode, SValue] type SValue = Value[SType] - type Idn = String /** Base class for all ErgoTree expression nodes. * @see [[sigmastate.Values.ErgoTree]] */ - trait Value[+S <: SType] extends SigmaNode { + abstract class Value[+S <: SType] extends SigmaNode { /** The companion node descriptor with opCode, cost and other metadata. */ def companion: ValueCompanion @@ -144,6 +141,9 @@ object Values { /** Unique id of the node class used in serialization of ErgoTree. */ def opCode: OpCode + /** Returns cost descriptor of this operation. */ + def costKind: CostKind = ??? + override def toString: String = s"${this.getClass.getSimpleName}(${opCode.toUByte})" def typeName: String = this.getClass.getSimpleName.replace("$", "") @@ -156,13 +156,26 @@ object Values { init() + val opDesc = CompanionDesc(this) } object ValueCompanion { private val _allOperations: mutable.HashMap[Byte, ValueCompanion] = mutable.HashMap.empty lazy val allOperations = _allOperations.toMap } - trait EvaluatedValue[+S <: SType] extends Value[S] { + /** Should be inherited by companion objects of operations with fixed cost kind. */ + trait FixedCostValueCompanion extends ValueCompanion { + /** Returns cost descriptor of this operation. */ + override def costKind: FixedCost = ??? + } + + /** Should be inherited by companion objects of operations with per-item cost kind. */ + trait PerItemCostValueCompanion extends ValueCompanion { + /** Returns cost descriptor of this operation. */ + override def costKind: PerItemCost = ??? + } + + abstract class EvaluatedValue[+S <: SType] extends Value[S] { val value: S#WrappedType def opType: SFunc = { val resType = tpe match { diff --git a/sigmastate/src/main/scala/sigmastate/eval/Profiler.scala b/sigmastate/src/main/scala/sigmastate/eval/Profiler.scala new file mode 100644 index 0000000000..0806de841a --- /dev/null +++ b/sigmastate/src/main/scala/sigmastate/eval/Profiler.scala @@ -0,0 +1,325 @@ +package sigmastate.eval + +import sigmastate.{FixedCost, SMethod} +import sigmastate.Values.SValue +import sigmastate.serialization.OpCodes +import sigmastate.serialization.OpCodes.OpCode +import sigmastate.serialization.ValueSerializer.getSerializer +import scalan.util.Extensions.ByteOps +import debox.{Buffer => DBuffer, Map => DMap} +import org.apache.commons.math3.util.Precision +import sigmastate.interpreter.{CostItem, FixedCostItem, SeqCostItem, TypeBasedCostItem} +import sigmastate.lang.Terms.{MethodCall, PropertyCall} +import spire.{math, sp} + +import scala.reflect.ClassTag + +abstract class StatItem[@sp (Long, Double) V] { + /** How many data points has been collected */ + def count: Int + + /** Sum of all data points */ + def sum: V + + /** Returns arithmetic average value. */ + def avg: V + + /** Returns arithmetic mean value (excluding 10% of smallest and 10% of highest values). + */ + def mean: (V, Int) +} + +class StatCollection[@sp(Int) K, @sp(Long, Double) V] + (implicit n: math.Numeric[V], ctK: ClassTag[K], ctV: ClassTag[V]) { + + private def calcAvg(buf: DBuffer[V]): V = { + n.div(buf.sum, n.fromInt(buf.length)) + } + + // NOTE: this class is mutable so better to keep it private + class StatItemImpl extends StatItem[V] { + final val NumMaxPoints = 10000 + + val dataPoints: DBuffer[V] = DBuffer.ofSize[V](256) + + def addPoint(point: V) = { + if (dataPoints.length < NumMaxPoints) { + dataPoints += point + } + } + + override def count: Int = dataPoints.length + override def sum: V = dataPoints.sum + override def avg: V = calcAvg(dataPoints) + + override def mean: (V, Int) = { + val nCropped = dataPoints.length / 10 + if (nCropped == 0) { + (calcAvg(dataPoints), dataPoints.length) + } + else { + val sorted = dataPoints.copy() + sorted.sort + val slice = sorted.slice(nCropped, sorted.length - nCropped) + (calcAvg(slice), slice.length) + } + } + } + + /** Timings of op codes. For performance debox.Map is used, which keeps keys unboxed. */ + private val opStat = DMap[K, StatItemImpl]() + + final def getMean(key: K): Option[(V, Int)] = opStat.get(key).map(_.mean) + + /** Update time measurement stats for a given operation. */ + final def addPoint(key: K, point: V) = { + val item = opStat.getOrElse(key, null) + if (item != null) { + item.addPoint(point) + } else { + val item = new StatItemImpl + item.addPoint(point) + opStat(key) = item + } + } + + final def mapToArray[@sp C: ClassTag](f: (K, StatItem[V]) => C): Array[C] = { + opStat.mapToArray(f) + } +} + +/** A simple profiler to measure average execution times of ErgoTree operations. */ +class Profiler { + + // NOTE: this class is mutable so better to keep it private + private class OpStat( + /** The node on the evaluation stack. */ + val node: SValue, + /** The time then this node evaluation was started. */ + val outerStart: Long, + /** The accumulated time of evaluating all the sub-nodes */ + var innerTime: Long, + /** The time then this nodes evaluation finished */ + val outerEnd: Long + ) + + /** If every recursive evaluation of every Value is marked with + * [[onBeforeNode()]]/[[onAfterNode()]], then this stack corresponds to the stack of + * recursive invocations of the evaluator. */ + private var opStack: List[OpStat] = Nil + + /** Called from evaluator (like [[sigmastate.interpreter.ErgoTreeEvaluator]]) + * immediately before the evaluator start recursive evaluation of the given node. + */ + def onBeforeNode(node: SValue) = { + val t = System.nanoTime() + opStack = new OpStat(node, t, 0, t) :: opStack + } + + /** Called from evaluator (like [[sigmastate.interpreter.ErgoTreeEvaluator]]) + * immediately after the evaluator finishes recursive evaluation of the given node. + */ + def onAfterNode(node: SValue) = { + val t = System.nanoTime() + + val op = opStack.head // always non empty at this point + opStack = opStack.tail // pop current op + assert(op.node.opCode == node.opCode, s"Inconsistent stack at ${op :: opStack}") + + val opFullTime = t - op.outerStart // full time spent in this op + + // add this time to parent's innerTime (if any parent) + if (opStack.nonEmpty) { + val parent = opStack.head + parent.innerTime += opFullTime + } else { + // we are on top level, do nothing + } + + val opSelfTime = opFullTime - op.innerTime + + // update timing stats + addOpTime(node.opCode, opSelfTime) + } + + /** Timings of op codes. For performance debox implementation of Map is used. */ + private val opStat = new StatCollection[Int, Long]() + + /** Update time measurement stats for a given operation. */ + @inline private final def addOpTime(op: OpCode, time: Long) = { + opStat.addPoint(OpCode.raw(op), time) + } + + /** Timings of method calls */ + private val mcStat = new StatCollection[Int, Long]() + + /** Update time measurement stats for a given method. */ + @inline private final def addMcTime(typeId: Byte, methodId: Byte, time: Long) = { + val key = typeId << 8 | methodId + mcStat.addPoint(key, time) + } + + /** Wrapper class which implementes special equality between CostItem instances, + * suitable for collecting of the statistics. */ + class CostItemKey(val costItem: CostItem) { + override def hashCode(): Int = costItem match { + case sci: SeqCostItem => 31 * sci.opDesc.hashCode + sci.chunks + case _ => costItem.hashCode() + } + + override def equals(obj: scala.Any): Boolean = (obj != null) && + (this.eq(obj.asInstanceOf[AnyRef]) || { + (obj match { + case that: CostItemKey => + this.costItem match { + case sciThis: SeqCostItem => + that.costItem match { + case sciThat: SeqCostItem => + sciThis.opDesc == sciThat.opDesc && sciThis.chunks == sciThat.chunks + case _ => false + } + case _ => this.costItem == that.costItem + } + case _ => false + }) + }) + + } + + /** Timings of cost items */ + private val costItemsStat = new StatCollection[CostItemKey, Long]() + + def addCostItem(costItem: CostItem, time: Long) = { + costItemsStat.addPoint(new CostItemKey(costItem), time) + } + + /** Estimation cost for each script */ + private val estimationCostStat = new StatCollection[String, Int]() + /** Estimation cost for each script */ + private val measuredTimeStat = new StatCollection[String, Long]() + + /** Returns relative error between estimated and actual values. */ + def relativeError(est: Double, act: Double): Double = { + val delta = Math.abs(est - act) + delta / act + } + + /** Adds estimated cost and actual measured time data point to the StatCollection for + * the given script. + */ + def addEstimation(script: String, cost: Int, actualTimeNano: Long) = { + estimationCostStat.addPoint(script, cost) + measuredTimeStat.addPoint(script, actualTimeNano) + } + + def suggestCost(time: Long): Int = { + ((time - 1) / 100 + 1).toInt + } + + /** Prints the operation timing table using collected execution profile information. + */ + def generateReport(): String = { + val opCodeLines = opStat.mapToArray { case (key, stat) => + val (time, count) = stat.mean + val opCode = OpCode @@ key.toByte + val ser = getSerializer(opCode) + val opDesc = ser.opDesc + val (opName, cost) = opDesc.costKind match { + case FixedCost(c) if opDesc != MethodCall && opDesc != PropertyCall => + (opDesc.typeName, c) + case _ => ("", 0) + } + val suggestedCost = suggestCost(time) + val warn = if (suggestedCost > cost) "!!!" else "" + val comment = s"count: $count, suggestedCost: $suggestedCost, actualCost: $cost$warn" + (opName, (opCode.toUByte - OpCodes.LastConstantCode).toString, time, comment) + }.filter(_._1.nonEmpty).sortBy(_._3)(Ordering[Long].reverse) + + val mcLines = mcStat.mapToArray { case (key, stat) => + val methodId = (key & 0xFF).toByte + val typeId = (key >> 8).toByte + val (time, count) = stat.mean + val m = SMethod.fromIds(typeId, methodId) + val typeName = m.objType.typeName + (s"$typeName.${m.name}", typeId, methodId, time, count.toString) + }.sortBy(r => (r._2,r._3))(Ordering[(Byte,Byte)].reverse) + + val ciLines = costItemsStat.mapToArray { case (ciKey, stat) => + val (name, timePerItem, time, comment) = { + val (time, count) = stat.mean + val suggestedCost = suggestCost(time) + val warn = if (suggestedCost > ciKey.costItem.cost) "!!!" else "" + ciKey.costItem match { + case ci: FixedCostItem => + val comment = s"count: $count, suggested: $suggestedCost, actCost: ${ci.cost}$warn" + (ci.opName, time, time, comment) + case ci: TypeBasedCostItem => + val comment = s"count: $count, suggested: $suggestedCost, actCost: ${ci.cost}$warn" + (ci.opName, time, time, comment) + case ci @ SeqCostItem(_, costKind, nItems) => + val nChunks = ci.chunks + val timePerChunk = if (nChunks > 0) time / nChunks else time + val name = s"${ci.opName}(nChunks: $nChunks)" + val comment = s"count: $count, suggested: $suggestedCost, actCost: ${ci.cost}$warn, kind: $costKind" + (name, timePerChunk, time, comment) + } + } + (name, timePerItem, time, comment) + }.sortBy({ case (name, tpi, t, c) => (name, tpi)})(Ordering[(String, Long)]) + + val estLines = estimationCostStat.mapToArray { case (script, stat) => + val (cost, count) = stat.mean + val (timeNano, _) = measuredTimeStat.getMean(script).get + val actualTimeMicro = timeNano.toDouble / 100 + val actualCost = cost.toDouble + val error = relativeError(actualCost, actualTimeMicro) + (script, error, cost, Math.round(actualTimeMicro), count.toString) + }.sortBy(_._2)(Ordering[Double].reverse) + + + val rows = opCodeLines + .map { case (opName, opCode, time, comment) => + val key = s"$opName".padTo(26, ' ') + s"$key -> time: $time ns, $comment " + } + .mkString("\n") + + val mcRows = mcLines + .map { case (opName, typeId, methodId, time, count) => + val key = s"($typeId.toByte, $methodId.toByte)".padTo(25, ' ') + s"$key -> $time, // count = $count, $opName " + } + .mkString("\n") + + val ciRows = ciLines + .map { case (opName, timePerItem, time, comment) => + val key = s"$opName".padTo(40, ' ') + val totalTime = if (time != timePerItem) s"($time)" else "" + s"$key -> $timePerItem${totalTime} ns, $comment" + } + .mkString("\n") + + val estRows = estLines + .map { case (opName, error, cost, time, count) => + val key = s"$opName".padTo(30, ' ') + val warn = if (cost < time) "!!!" else "" + val err = Precision.round(error, 4) + s"$key -> ($err, $cost$warn, $time), // count = $count " + } + .mkString("\n") + + s""" + |----------- + |$rows + |----------- + |$mcRows + |----------- + |$ciRows + |----------- + |$estRows + |----------- + """.stripMargin + } + +} + diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/CostAccumulator.scala b/sigmastate/src/main/scala/sigmastate/interpreter/CostAccumulator.scala new file mode 100644 index 0000000000..4371925e88 --- /dev/null +++ b/sigmastate/src/main/scala/sigmastate/interpreter/CostAccumulator.scala @@ -0,0 +1,75 @@ +package sigmastate.interpreter + +import sigmastate.lang.exceptions.CostLimitException + +/** Encapsulate simple monotonic (add only) counter with reset. */ +class CostCounter(val initialCost: Int) { + private var _currentCost: Int = initialCost + + @inline def += (n: Int) = { + this._currentCost = java.lang.Math.addExact(this._currentCost, n) + } + @inline def currentCost: Int = _currentCost + @inline def resetCost() = { _currentCost = initialCost } +} + +/** Implements finite state machine with stack of graph blocks (scopes), + * which correspond to lambdas and thunks. + * It accepts messages: startScope(), endScope(), add(), reset() + * At any time `totalCost` is the currently accumulated cost. */ +class CostAccumulator(initialCost: Int, costLimit: Option[Long]) { + + @inline private def initialStack() = List(new Scope(initialCost)) + private var _scopeStack: List[Scope] = initialStack + + @inline def currentScope: Scope = _scopeStack.head + + /** Represents a single scope during execution of the graph. + * The lifetime of each instance is bound to scope execution. + * When the evaluation enters a new scope (e.g. calling a lambda) a new Scope instance is created and pushed + * to _scopeStack, then is starts receiving `add` method calls. + * When the evaluation leaves the scope, the top is popped off the stack. */ + class Scope(initialCost: Int) extends CostCounter(initialCost) { + + + @inline def add(opCost: Int): Unit = { + this += opCost + } + + /** Called by nested Scopes to communicate accumulated cost back to parent scope. + * When current scope terminates, it communicates accumulated cost up to its parent scope. + * This value is used at the root scope to obtain total accumulated scope. + */ + private var _resultRegister: Int = 0 + @inline def childScopeResult: Int = _resultRegister + @inline def childScopeResult_=(resultCost: Int): Unit = { + _resultRegister = resultCost + } + + } + + /** Called once for each operation of a scope (lambda or thunk). + */ + def add(opCost: Int): Unit = { + currentScope.add(opCost) + + // check that we are still withing the limit + if (costLimit.isDefined) { + val limit = costLimit.get + // the cost we accumulated so far + val accumulatedCost = currentScope.currentCost + if (accumulatedCost > limit) { + throw new CostLimitException( + accumulatedCost, CostLimitException.msgCostLimitError(accumulatedCost, limit), None) + } + } + } + + /** Resets this accumulator into initial state to be ready for new graph execution. */ + @inline def reset() = { + _scopeStack = initialStack() + } + + /** Returns total accumulated cost */ + @inline def totalCost: Int = currentScope.currentCost +} diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/CostDetails.scala b/sigmastate/src/main/scala/sigmastate/interpreter/CostDetails.scala new file mode 100644 index 0000000000..cb3ae724e4 --- /dev/null +++ b/sigmastate/src/main/scala/sigmastate/interpreter/CostDetails.scala @@ -0,0 +1,57 @@ +package sigmastate.interpreter + +import scala.collection.mutable + +/** Abstract representation of cost results obtained during evaluation. */ +abstract class CostDetails { + /** The total cost of evaluation. */ + def cost: Int + /** The trace of costed operations performed during evaluation. */ + def trace: Seq[CostItem] + /** Actual execution time (in nanoseconds) if defined. */ + def actualTimeNano: Option[Long] +} + +/** Detailed results of cost evaluation represented by trace. + * NOTE: the `trace` is obtained during execution of [[ErgoTree]] operations. + * @param trace accumulated trace of all cost items (empty for AOT costing) + * @param actualTimeNano measured time of execution (if some) + */ +case class TracedCost(trace: Seq[CostItem], + actualTimeNano: Option[Long] = None) extends CostDetails { + /** Total cost of all cost items. */ + def cost: Int = trace.foldLeft(0)(_ + _.cost) +} + +/** Result of cost evaluation represented using simple given value. + * Used to represent cost of AOT costing. + * @param cost the given value of the total cost + * @param actualTimeNano measured time of execution (if some) + */ +case class GivenCost(cost: Int, + actualTimeNano: Option[Long] = None) extends CostDetails { + /** The trait is empty for this representation of CostDetails. + */ + override def trace: Seq[CostItem] = mutable.WrappedArray.empty +} + +object CostDetails { + /** Empty sequence of cost items. Should be used whenever possible to avoid allocations. */ + val EmptyTrace: Seq[CostItem] = mutable.WrappedArray.empty + + /** CostDetails with empty trace have also zero total cost. */ + val ZeroCost = TracedCost(EmptyTrace) + + /** Helper factory method to create CostDetails from the given trace. */ + def apply(trace: Seq[CostItem]): CostDetails = TracedCost(trace) + + /** Helper recognizer to work with different representations of costs in patterns + * uniformly. + */ + def unapply(d: CostDetails): Option[(Int, Seq[CostItem])] = d match { + case TracedCost(t, _) => Some((d.cost, t)) + case GivenCost(c, _) => Some((c, EmptyTrace)) + case _ => None + } +} + diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/CostItem.scala b/sigmastate/src/main/scala/sigmastate/interpreter/CostItem.scala new file mode 100644 index 0000000000..f6479fc315 --- /dev/null +++ b/sigmastate/src/main/scala/sigmastate/interpreter/CostItem.scala @@ -0,0 +1,94 @@ +package sigmastate.interpreter + +import sigmastate.{FixedCost, PerItemCost, SMethod, SType, TypeBasedCost} +import sigmastate.Values.{FixedCostValueCompanion, PerItemCostValueCompanion, ValueCompanion} +import sigmastate.lang.Terms.MethodCall + +/** An item in the cost accumulation trace of a [[sigmastate.Values.ErgoTree]] evaluation. */ +abstract class CostItem { + def opName: String + def cost: Int +} + +/** An item in the cost accumulation trace of a [[sigmastate.Values.ErgoTree]] evaluation. + * Represents cost of simple operation. + * Used for debugging, testing and profiling of costing. + * @param opDesc descriptor of the ErgoTree operation + * @param costKind kind of the cost to be added to accumulator + */ +case class FixedCostItem(opDesc: OperationDesc, costKind: FixedCost) extends CostItem { + override def opName: String = opDesc.operationName + override def cost: Int = costKind.cost +} +object FixedCostItem { + def apply(companion: FixedCostValueCompanion): FixedCostItem = { + FixedCostItem(companion.opDesc, companion.costKind) + } + def apply(method: SMethod, costKind: FixedCost): FixedCostItem = { + FixedCostItem(MethodDesc(method), costKind) + } +} + +/** An item in the cost accumulation trace of a [[sigmastate.Values.ErgoTree]] evaluation. + * Represents cost of an operation which depends on type (e.g. type of arguments). + * Used for debugging, testing and profiling of costing. + * @param opDesc descriptor of the ErgoTree operation + * @param costKind type based cost descriptor added to accumulator + * @param tpe concrete type on this the operation is executed + * @see [[sigmastate.LE]], [[sigmastate.GT]] + */ +case class TypeBasedCostItem( + opDesc: OperationDesc, + costKind: TypeBasedCost, + tpe: SType) extends CostItem { + override def opName: String = { + val name = opDesc.operationName + s"$name[$tpe]" + } + override def cost: Int = costKind.costFunc(tpe) + override def equals(obj: Any): Boolean = + (this eq obj.asInstanceOf[AnyRef]) || (obj != null && (obj match { + case that: TypeBasedCostItem => + opDesc == that.opDesc && tpe == that.tpe + case _ => false + })) + override def hashCode(): Int = 31 * opDesc.hashCode() + tpe.hashCode() +} +object TypeBasedCostItem { + def apply(companion: ValueCompanion, tpe: SType): TypeBasedCostItem = { + TypeBasedCostItem(companion.opDesc, companion.costKind.asInstanceOf[TypeBasedCost], tpe) + } +} + +/** An item in the cost accumulation trace of a [[sigmastate.Values.ErgoTree]] evaluation. + * Represents cost of a sequence of operation. + * Used for debugging, testing and profiling of costing. + * + * @param opDesc descriptor of the ErgoTree operation + * @param costKind descriptor of the cost added to accumulator + * @param nItems number of items in the sequence + */ +case class SeqCostItem(opDesc: OperationDesc, costKind: PerItemCost, nItems: Int) + extends CostItem { + override def opName: String = opDesc.operationName + override def cost: Int = costKind.cost(nItems) + /** How many data chunks in this cost item. */ + def chunks: Int = costKind.chunks(nItems) +} +object SeqCostItem { + def apply(companion: PerItemCostValueCompanion, nItems: Int): SeqCostItem = + SeqCostItem(companion.opDesc, companion.costKind, nItems) +} + +/** An item in the cost accumulation trace of a [[sigmastate.Values.ErgoTree]] evaluation. + * Represents cost of MethodCall operation. + * Used for debugging, testing and profiling of costing. + * + * @param items cost details obtained as part of MethodCall evaluation + */ +case class MethodCallCostItem(items: CostDetails) extends CostItem { + override def opName: String = MethodCall.typeName + override def cost: Int = items.cost +} + + diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/OperationDesc.scala b/sigmastate/src/main/scala/sigmastate/interpreter/OperationDesc.scala new file mode 100644 index 0000000000..ca7fc9b217 --- /dev/null +++ b/sigmastate/src/main/scala/sigmastate/interpreter/OperationDesc.scala @@ -0,0 +1,43 @@ +package sigmastate.interpreter + +import sigmastate.{CostKind, SMethod} +import sigmastate.Values.ValueCompanion + +/** Each costable operation is described in one of the following ways: + * 1) using [[ValueCompanion]] - operation with separate node class + * 2) using [[SMethod]] - operation represented as method. + * 3) using string name - intermediate sub-operation present in cost model, but + * which is not a separate operation of ErgoTree. + */ +abstract class OperationDesc { + def operationName: String +} + +/** Operation descriptor based on [[ValueCompanion]]. */ +case class CompanionDesc(companion: ValueCompanion) extends OperationDesc { + override def operationName: String = companion.typeName +} + +/** Operation descriptor based on [[SMethod]]. */ +case class MethodDesc(method: SMethod) extends OperationDesc { + override def operationName: String = method.opName + + override def toString: String = s"MethodDesc(${method.opName})" + + override def hashCode(): Int = (method.objType.typeId << 8) | method.methodId + + override def equals(obj: Any): Boolean = + this.eq(obj.asInstanceOf[AnyRef]) || (obj != null && (obj match { + case that: MethodDesc => + method.objType.typeId == that.method.objType.typeId && + method.methodId == that.method.methodId + case _ => false + })) +} + +/** Operation descriptor based on name. */ +case class NamedDesc(operationName: String) extends OperationDesc + +/** Operation costing descriptors combined together. */ +case class OperationCostInfo[C <: CostKind](costKind: C, opDesc: OperationDesc) + diff --git a/sigmastate/src/main/scala/sigmastate/lang/exceptions/Exceptions.scala b/sigmastate/src/main/scala/sigmastate/lang/exceptions/Exceptions.scala index 34fe32bfaa..af3aafd1e2 100644 --- a/sigmastate/src/main/scala/sigmastate/lang/exceptions/Exceptions.scala +++ b/sigmastate/src/main/scala/sigmastate/lang/exceptions/Exceptions.scala @@ -36,3 +36,6 @@ class InterpreterException(message: String, source: Option[SourceContext] = None class CostLimitException(val estimatedCost: Long, message: String, cause: Option[Throwable] = None) extends SigmaException(message, None, cause) +object CostLimitException { + def msgCostLimitError(cost: Long, limit: Long) = s"Estimated execution cost $cost exceeds the limit $limit" +} diff --git a/sigmastate/src/main/scala/sigmastate/types.scala b/sigmastate/src/main/scala/sigmastate/types.scala index 16e6487efa..028afde28d 100644 --- a/sigmastate/src/main/scala/sigmastate/types.scala +++ b/sigmastate/src/main/scala/sigmastate/types.scala @@ -1,13 +1,14 @@ package sigmastate +import java.lang.reflect.Method import java.math.BigInteger import org.ergoplatform._ import org.ergoplatform.validation._ -import scalan.{RType, Nullable} +import scalan.{Nullable, RType} import scalan.RType.GeneralType import sigmastate.SType.{TypeCode, AnyOps} -import sigmastate.interpreter.CryptoConstants +import sigmastate.interpreter._ import sigmastate.utils.Overloading.Overload1 import sigmastate.utils.SparseArrayContainer import scalan.util.Extensions._ @@ -459,6 +460,8 @@ case class SMethod( def withConcreteTypes(subst: Map[STypeVar, SType]): SMethod = withSType(stype.withSubstTypes(subst).asFunc) + def opName = objType.getClass.getSimpleName + "." + name + /** Returns [[OperationId]] for AOT costing. */ def opId: OperationId = { val opName = objType.getClass.getSimpleName + "." + name From 54f9af1fc820300ec0aa70559bfde206a984923c Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Sat, 17 Jul 2021 16:35:42 +0300 Subject: [PATCH 22/56] prepare-v5.0: part 3: addressed review comments --- .../sigmastate/interpreter/CryptoFunctions.scala | 6 +++++- .../scala/sigmastate/interpreter/Interpreter.scala | 12 +++++++----- .../main/scala/sigmastate/lang/SigmaCompiler.scala | 6 +++++- .../main/scala/sigmastate/utxo/transformers.scala | 3 --- 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/CryptoFunctions.scala b/sigmastate/src/main/scala/sigmastate/interpreter/CryptoFunctions.scala index b9852d5211..44cbfa73e3 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/CryptoFunctions.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/CryptoFunctions.scala @@ -5,7 +5,11 @@ import scorex.crypto.hash.Blake2b256 object CryptoFunctions { lazy val soundnessBytes: Int = CryptoConstants.soundnessBits / 8 - /** @hotspot don't beautify this code. Used in Interpreter.verify. */ + /** Hashes the given `input` into 32 bytes hash, then returns the first `soundnessBytes` + * bytes of the hash in a new array. + * The method trims `32 - soundnessBytes` bytes at the end of the hash array. + * + * @hotspot don't beautify this code. Used in Interpreter.verify. */ def hashFn(input: Array[Byte]): Array[Byte] = { val h = Blake2b256.hash(input) val res = new Array[Byte](soundnessBytes) diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala index 611dc6d9f0..4fafa5ef0d 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala @@ -117,9 +117,10 @@ trait Interpreter extends ScorexLogging { * If cost is above limit then exception is returned and `exp` is not executed * else `exp` is computed in the given context and the resulting SigmaBoolean returned. * - * @param context the context in which `exp` should be executed - * @param env environment of system variables used by the interpreter internally - * @param exp expression to be executed in the given `context` + * @param context the context in which `exp` should be executed + * @param env environment of variables used by the interpreter internally. + * Note, this is not system environment variables. + * @param exp expression to be executed in the given `context` * @return result of script reduction * @see `ReductionResult` */ @@ -149,11 +150,12 @@ trait Interpreter extends ScorexLogging { } } - /** Reduces `exp` to SigmaProp under the default (empty) environment. */ + /** Helper convenience overload which uses empty environment. + * @see other overloads for details. + */ def reduceToCrypto(context: CTX, exp: Value[SType]): Try[ReductionResult] = reduceToCrypto(context, Interpreter.emptyEnv, exp) - /** * Full reduction of initial expression given in the ErgoTree form to a SigmaBoolean value * (which encodes whether a sigma-protocol proposition or a boolean value, so true or false). diff --git a/sigmastate/src/main/scala/sigmastate/lang/SigmaCompiler.scala b/sigmastate/src/main/scala/sigmastate/lang/SigmaCompiler.scala index 659e23e19b..d5fe6d93c5 100644 --- a/sigmastate/src/main/scala/sigmastate/lang/SigmaCompiler.scala +++ b/sigmastate/src/main/scala/sigmastate/lang/SigmaCompiler.scala @@ -14,7 +14,11 @@ import sigmastate.lang.syntax.ParserException * @param networkPrefix network prefix to decode an ergo address from string (PK op) * @param builder used to create ErgoTree nodes * @param lowerMethodCalls if true, then MethodCall nodes are lowered to ErgoTree nodes - * when [[sigmastate.SMethod.irInfo.irBuilder]] is defined + * when [[sigmastate.SMethod.irInfo.irBuilder]] is defined. For + * example, in the `coll.map(x => x+1)` code, the `map` method + * call can be lowered to MapCollection node. + * The lowering if preferable, because it is more compact (1 byte + * for MapCollection instead of 3 bytes for MethodCall). */ case class CompilerSettings( networkPrefix: NetworkPrefix, diff --git a/sigmastate/src/main/scala/sigmastate/utxo/transformers.scala b/sigmastate/src/main/scala/sigmastate/utxo/transformers.scala index 11376c95f8..a31311eddf 100644 --- a/sigmastate/src/main/scala/sigmastate/utxo/transformers.scala +++ b/sigmastate/src/main/scala/sigmastate/utxo/transformers.scala @@ -11,9 +11,6 @@ import sigmastate.Operations._ import sigmastate.lang.exceptions.InterpreterException import special.sigma.Box - -// TODO refactor: remove this trait as it doesn't have semantic meaning - /** Every operation is a transformer of some kind. * This trait is used merely to simplify implementation and avoid copy-paste. */ From d76f889bf19c2c21fc599a2fa41316e7d8d89d22 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Wed, 21 Jul 2021 15:46:59 +0300 Subject: [PATCH 23/56] prepare-v5.0: part 4: CrossVersionProps.scala --- .../scala/sigmastate/CrossVersionProps.scala | 52 +++++++++++++++---- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/sigmastate/src/test/scala/sigmastate/CrossVersionProps.scala b/sigmastate/src/test/scala/sigmastate/CrossVersionProps.scala index 2dd25fb151..11ae6342b5 100644 --- a/sigmastate/src/test/scala/sigmastate/CrossVersionProps.scala +++ b/sigmastate/src/test/scala/sigmastate/CrossVersionProps.scala @@ -2,17 +2,51 @@ package sigmastate import org.scalatest.{PropSpecLike, Tag} import org.scalactic.source.Position +import sigmastate.eval.Profiler import spire.syntax.all.cfor +import scala.util.DynamicVariable + trait CrossVersionProps extends PropSpecLike with TestsBase { val printVersions: Boolean = false + /** Number of times each test property is warmed up (i.e. executed before final execution). */ + def perTestWarmUpIters: Int = 0 + + private[sigmastate] val _warmupProfiler = new DynamicVariable[Option[Profiler]](None) + def warmupProfiler: Option[Profiler] = _warmupProfiler.value + + protected def testFun_Run(testName: String, testFun: => Any): Unit = { + def msg = s"""property("$testName")(ActivatedVersion = $activatedVersionInTests; ErgoTree version = $ergoTreeVersionInTests)""" + if (printVersions) println(msg) + try testFun + catch { + case t: Throwable => + if (!printVersions) { + // wasn't printed, print it now + println(msg) + } + throw t + } + } + override protected def property(testName: String, testTags: Tag*) (testFun: => Any) (implicit pos: Position): Unit = { super.property(testName, testTags:_*) { + // do warmup if necessary + if (perTestWarmUpIters > 0) { + _warmupProfiler.withValue(Some(new Profiler)) { + cfor(0)(_ < perTestWarmUpIters, _ + 1) { _ => + testFun_Run(testName, testFun) + } + } + System.gc() + Thread.sleep(100) // give it some time to finish warm-up + } + cfor(0)(_ < activatedVersions.length, _ + 1) { i => val activatedVersion = activatedVersions(i) _currActivatedVersion.withValue(activatedVersion) { @@ -22,22 +56,18 @@ trait CrossVersionProps extends PropSpecLike with TestsBase { _ + 1) { j => val treeVersion = ergoTreeVersions(j) _currErgoTreeVersion.withValue(treeVersion) { - def msg = s"""property("$testName")(ActivatedVersion = $activatedVersionInTests; ErgoTree version = $ergoTreeVersionInTests)""" - if (printVersions) println(msg) - try testFun - catch { - case t: Throwable => - if (!printVersions) { - // wasn't printed, print it now - println(msg) - } - throw t - } + testFun_Run(testName, testFun) } } } } + + if (okRunTestsWithoutMCLowering) { + _lowerMethodCalls.withValue(false) { + testFun_Run(testName, testFun) + } + } } } } From ce3cb601a271ec01079b9ca6c8977fd76d048809 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Mon, 26 Jul 2021 12:27:31 +0300 Subject: [PATCH 24/56] prepare-v5.0: part 4: some TODOs to clarify intention --- sigmastate/src/main/scala/sigmastate/Values.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/Values.scala b/sigmastate/src/main/scala/sigmastate/Values.scala index ef05623286..09cc9616aa 100644 --- a/sigmastate/src/main/scala/sigmastate/Values.scala +++ b/sigmastate/src/main/scala/sigmastate/Values.scala @@ -142,7 +142,7 @@ object Values { def opCode: OpCode /** Returns cost descriptor of this operation. */ - def costKind: CostKind = ??? + def costKind: CostKind = ??? // TODO v5.0: make abstract override def toString: String = s"${this.getClass.getSimpleName}(${opCode.toUByte})" @@ -166,13 +166,13 @@ object Values { /** Should be inherited by companion objects of operations with fixed cost kind. */ trait FixedCostValueCompanion extends ValueCompanion { /** Returns cost descriptor of this operation. */ - override def costKind: FixedCost = ??? + override def costKind: FixedCost = ??? // TODO v5.0: make abstract } /** Should be inherited by companion objects of operations with per-item cost kind. */ trait PerItemCostValueCompanion extends ValueCompanion { /** Returns cost descriptor of this operation. */ - override def costKind: PerItemCost = ??? + override def costKind: PerItemCost = ??? // TODO v5.0: make abstract } abstract class EvaluatedValue[+S <: SType] extends Value[S] { From 4cfaac4fb707389e1947c1075d4da8fcedb943ec Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Mon, 26 Jul 2021 17:33:30 +0300 Subject: [PATCH 25/56] prepare-v5.0: part 5: implementation of all eval methods --- common/src/main/scala/scalan/AnyVals.scala | 9 + .../org/ergoplatform/ErgoLikeContext.scala | 55 +- .../scala/sigmastate/DataValueComparer.scala | 391 ++++++++ .../src/main/scala/sigmastate/Values.scala | 275 +++++- .../sigmastate/basics/DLogProtocol.scala | 2 +- .../basics/DiffieHellmanTupleProtocol.scala | 1 + .../scala/sigmastate/eval/BigIntegerOps.scala | 21 +- .../sigmastate/eval/CostingDataContext.scala | 2 +- .../interpreter/ErgoTreeEvaluator.scala | 473 +++++++++ .../scala/sigmastate/lang/SigmaTyper.scala | 2 +- .../main/scala/sigmastate/lang/Terms.scala | 81 +- .../serialization/ErgoTreeSerializer.scala | 4 +- .../src/main/scala/sigmastate/trees.scala | 632 +++++++++++- .../src/main/scala/sigmastate/types.scala | 935 +++++++++++++++--- .../scala/sigmastate/utxo/CostTable.scala | 1 - .../scala/sigmastate/utxo/transformers.scala | 225 ++++- .../SoftForkabilitySpecification.scala | 1 + 17 files changed, 2915 insertions(+), 195 deletions(-) create mode 100644 sigmastate/src/main/scala/sigmastate/DataValueComparer.scala create mode 100644 sigmastate/src/main/scala/sigmastate/interpreter/ErgoTreeEvaluator.scala diff --git a/common/src/main/scala/scalan/AnyVals.scala b/common/src/main/scala/scalan/AnyVals.scala index 6da152e524..f1e40348da 100644 --- a/common/src/main/scala/scalan/AnyVals.scala +++ b/common/src/main/scala/scalan/AnyVals.scala @@ -45,5 +45,14 @@ class AVHashMap[K,V](val hashMap: HashMap[K,V]) extends AnyVal { object AVHashMap { /** Helper method to create a new map with the given capacity. */ def apply[K,V](initialCapacity: Int) = new AVHashMap[K,V](new HashMap[K,V](initialCapacity)) + + /** Helper method to create a new map form sequence of K, V pairs. */ + def fromSeq[K,V](items: Seq[(K, V)]): AVHashMap[K,V] = { + val map = new AVHashMap[K,V](new HashMap[K,V](items.length)) + items.foreach { case (k, v) => + map.put(k, v) + } + map + } } diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeContext.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeContext.scala index aa59a85d67..33c1ea0a4a 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeContext.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeContext.scala @@ -8,7 +8,8 @@ import sigmastate.Values._ import sigmastate._ import sigmastate.eval.Extensions._ import sigmastate.eval._ -import sigmastate.interpreter.{ContextExtension, InterpreterContext} +import sigmastate.interpreter.ErgoTreeEvaluator.DataEnv +import sigmastate.interpreter.{ContextExtension, InterpreterContext, ErgoTreeEvaluator} import sigmastate.serialization.OpCodes import sigmastate.serialization.OpCodes.OpCode import special.collection.Coll @@ -228,38 +229,70 @@ object ErgoLikeContext { /** When interpreted evaluates to a ByteArrayConstant built from Context.minerPubkey */ case object MinerPubkey extends NotReadyValueByteArray with ValueCompanion { override def opCode: OpCode = OpCodes.MinerPubkeyCode + /** Cost of calling Context.minerPubkey Scala method. */ + override val costKind = FixedCost(20) override val opType = SFunc(SContext, SCollection.SByteArray) override def companion = this + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + addCost(this.costKind) + E.context.minerPubKey + } } /** When interpreted evaluates to a IntConstant built from Context.currentHeight */ case object Height extends NotReadyValueInt with ValueCompanion { override def companion = this override def opCode: OpCode = OpCodes.HeightCode + /** Cost of: 1) Calling Context.HEIGHT Scala method. */ + override val costKind = FixedCost(26) override val opType = SFunc(SContext, SInt) + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + addCost(this.costKind) + E.context.HEIGHT + } } /** When interpreted evaluates to a collection of BoxConstant built from Context.boxesToSpend */ case object Inputs extends LazyCollection[SBox.type] with ValueCompanion { override def companion = this override def opCode: OpCode = OpCodes.InputsCode + /** Cost of: 1) Calling Context.INPUTS Scala method. */ + override val costKind = FixedCost(10) override def tpe = SCollection.SBoxArray override val opType = SFunc(SContext, tpe) + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + addCost(this.costKind) + E.context.INPUTS + } } /** When interpreted evaluates to a collection of BoxConstant built from Context.spendingTransaction.outputs */ case object Outputs extends LazyCollection[SBox.type] with ValueCompanion { override def companion = this override def opCode: OpCode = OpCodes.OutputsCode + /** Cost of: 1) Calling Context.OUTPUTS Scala method. */ + override val costKind = FixedCost(10) override def tpe = SCollection.SBoxArray override val opType = SFunc(SContext, tpe) + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + addCost(this.costKind) + E.context.OUTPUTS + } } /** When interpreted evaluates to a AvlTreeConstant built from Context.lastBlockUtxoRoot */ case object LastBlockUtxoRootHash extends NotReadyValueAvlTree with ValueCompanion { override def companion = this override def opCode: OpCode = OpCodes.LastBlockUtxoRootHashCode + + /** Cost of: 1) Calling Context.LastBlockUtxoRootHash Scala method. */ + override val costKind = FixedCost(15) + override val opType = SFunc(SContext, tpe) + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + addCost(this.costKind) + E.context.LastBlockUtxoRootHash + } } @@ -267,7 +300,13 @@ case object LastBlockUtxoRootHash extends NotReadyValueAvlTree with ValueCompani case object Self extends NotReadyValueBox with ValueCompanion { override def companion = this override def opCode: OpCode = OpCodes.SelfCode + /** Cost of: 1) Calling Context.SELF Scala method. */ + override val costKind = FixedCost(10) override val opType = SFunc(SContext, SBox) + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + addCost(this.costKind) + E.context.SELF + } } /** When interpreted evaluates to the singleton instance of [[special.sigma.Context]]. @@ -276,8 +315,16 @@ case object Self extends NotReadyValueBox with ValueCompanion { case object Context extends NotReadyValue[SContext.type] with ValueCompanion { override def companion = this override def opCode: OpCode = OpCodes.ContextCode + + /** Cost of: 1) accessing global Context instance. */ + override val costKind = FixedCost(1) + override def tpe: SContext.type = SContext override val opType: SFunc = SFunc(SUnit, SContext) + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + addCost(this.costKind) + E.context + } } /** When interpreted evaluates to the singleton instance of [[special.sigma.SigmaDslBuilder]]. @@ -286,6 +333,12 @@ case object Context extends NotReadyValue[SContext.type] with ValueCompanion { case object Global extends NotReadyValue[SGlobal.type] with ValueCompanion { override def companion = this override def opCode: OpCode = OpCodes.GlobalCode + /** Cost of: 1) accessing Global instance. */ + override val costKind = FixedCost(5) override def tpe: SGlobal.type = SGlobal override val opType: SFunc = SFunc(SUnit, SGlobal) + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + addCost(this.costKind) + CostingSigmaDslBuilder + } } diff --git a/sigmastate/src/main/scala/sigmastate/DataValueComparer.scala b/sigmastate/src/main/scala/sigmastate/DataValueComparer.scala new file mode 100644 index 0000000000..72c9bfdba7 --- /dev/null +++ b/sigmastate/src/main/scala/sigmastate/DataValueComparer.scala @@ -0,0 +1,391 @@ +package sigmastate + +import scalan.{AVHashMap, Nullable, RType} +import scalan.RType._ +import sigmastate.Values.SigmaBoolean +import sigmastate.basics.DLogProtocol.ProveDlog +import sigmastate.basics.ProveDHTuple +import sigmastate.eval.SigmaDsl +import sigmastate.interpreter.CryptoConstants.EcPointType +import spire.sp +import sigmastate.interpreter.{ErgoTreeEvaluator, NamedDesc, OperationCostInfo} +import special.sigma.{AvlTree, AvlTreeRType, BigInt, BigIntRType, Box, BoxRType, GroupElement, GroupElementRType, Header, HeaderRType, PreHeader, PreHeaderRType, SigmaProp} +import special.collection.{Coll, CollOverArray, PairOfCols} +import spire.syntax.all.cfor + +/** Implementation of data equality for two arbitrary ErgoTree data types. + * @see [[DataValueComparer.equalDataValues]] + */ +object DataValueComparer { + + /** NOTE: The cost of most equality operations depends on the position in `match` statement. + * Thus the full cost to compare x and y equals DispatchCost * OperationCost, where + * DispatchCost = CasePosition * CostOf_MatchType, + * OperationCost is the type specific cost. + * For this reason reordering of cases may lead to divergence between an estimated and + * the actual execution cost (time). + * The constants are part of the consensus procotol and cannot be changed without forking. + */ + final val CostOf_MatchType = 1 + final val CostKind_MatchType = FixedCost(CostOf_MatchType) + final val OpDesc_MatchType = NamedDesc("MatchType") + final val MatchType = OperationCostInfo(CostKind_MatchType, OpDesc_MatchType) + + final val CostKind_EQ_Prim = FixedCost(3) // case 1 + final val OpDesc_EQ_Prim = NamedDesc("EQ_Prim") + final val EQ_Prim = OperationCostInfo(CostKind_EQ_Prim, OpDesc_EQ_Prim) + + + /** Equals two Colls of non-primitive (boxed) types. + */ + final val CostKind_EQ_Coll = PerItemCost(10, 2, 1) // case 2 + final val OpDesc_EQ_Coll = NamedDesc("EQ_Coll") + final val EQ_Coll = OperationCostInfo(CostKind_EQ_Coll, OpDesc_EQ_Coll) + + /** NOTE: In the formula `(2 + 1)` the 1 corresponds to the second type match. */ + final val CostKind_EQ_Tuple = FixedCost(4) // case 3 + final val OpDesc_EQ_Tuple = NamedDesc("EQ_Tuple") + final val EQ_Tuple = OperationCostInfo(CostKind_EQ_Tuple, OpDesc_EQ_Tuple) + + /** NOTE: the value is set based on benchmarking of SigmaDslSpecification. */ + final val CostKind_EQ_GroupElement = FixedCost(172) // case 4 + final val OpDesc_EQ_GroupElement = NamedDesc("EQ_GroupElement") + final val EQ_GroupElement = OperationCostInfo(CostKind_EQ_GroupElement, OpDesc_EQ_GroupElement) + + final val CostKind_EQ_BigInt = FixedCost(5) // case 5 + final val OpDesc_EQ_BigInt = NamedDesc("EQ_BigInt") + final val EQ_BigInt = OperationCostInfo(CostKind_EQ_BigInt, OpDesc_EQ_BigInt) + + final val CostKind_EQ_AvlTree = FixedCost(3 + (6 * CostOf_MatchType) / 2) // case 6 + final val OpDesc_EQ_AvlTree = NamedDesc("EQ_AvlTree") + final val EQ_AvlTree = OperationCostInfo(CostKind_EQ_AvlTree, OpDesc_EQ_AvlTree) + + // TODO v5.0: update value after serialization is avoided to compute ErgoBox.id + final val CostKind_EQ_Box = FixedCost(6) // case 7 + final val OpDesc_EQ_Box = NamedDesc("EQ_Box") + final val EQ_Box = OperationCostInfo(CostKind_EQ_Box, OpDesc_EQ_Box) + + /** NOTE: In the formula `(7 + 1)` the 1 corresponds to the second type match. */ + final val CostKind_EQ_Option = FixedCost(1 + (7 + 1) * CostOf_MatchType / 2 - 1) // case 8 + final val OpDesc_EQ_Option = NamedDesc("EQ_Option") + final val EQ_Option = OperationCostInfo(CostKind_EQ_Option, OpDesc_EQ_Option) + + final val CostKind_EQ_PreHeader = FixedCost(4) // case 9 + final val OpDesc_EQ_PreHeader = NamedDesc("EQ_PreHeader") + final val EQ_PreHeader = OperationCostInfo(CostKind_EQ_PreHeader, OpDesc_EQ_PreHeader) + + final val CostKind_EQ_Header = FixedCost(6) // case 10 + final val OpDesc_EQ_Header = NamedDesc("EQ_Header") + final val EQ_Header = OperationCostInfo(CostKind_EQ_Header, OpDesc_EQ_Header) + + /** Equals two CollOverArray of Boolean type. */ + final val CostKind_EQ_COA_Boolean = PerItemCost(15, 2, 128) + final val OpDesc_EQ_COA_Boolean = NamedDesc("EQ_COA_Boolean") + final val EQ_COA_Boolean = OperationCostInfo(CostKind_EQ_COA_Boolean, OpDesc_EQ_COA_Boolean) + + /** Equals two CollOverArray of Byte type. */ + final val CostKind_EQ_COA_Byte = PerItemCost(15, 2, 128) + final val OpDesc_EQ_COA_Byte = NamedDesc("EQ_COA_Byte") + final val EQ_COA_Byte = OperationCostInfo(CostKind_EQ_COA_Byte, OpDesc_EQ_COA_Byte) + + /** Equals two CollOverArray of Short type. */ + final val CostKind_EQ_COA_Short = PerItemCost(15, 2, 96) + final val OpDesc_EQ_COA_Short = NamedDesc("EQ_COA_Short") + final val EQ_COA_Short = OperationCostInfo(CostKind_EQ_COA_Short, OpDesc_EQ_COA_Short) + + /** Equals two CollOverArray of Int type. */ + final val CostKind_EQ_COA_Int = PerItemCost(15, 2, 64) + final val OpDesc_EQ_COA_Int = NamedDesc("EQ_COA_Int") + final val EQ_COA_Int = OperationCostInfo(CostKind_EQ_COA_Int, OpDesc_EQ_COA_Int) + + /** Equals two CollOverArray of Long type. */ + final val CostKind_EQ_COA_Long = PerItemCost(15, 2, 48) + final val OpDesc_EQ_COA_Long = NamedDesc("EQ_COA_Long") + final val EQ_COA_Long = OperationCostInfo(CostKind_EQ_COA_Long, OpDesc_EQ_COA_Long) + + /** Equals two CollOverArray of GroupElement type. */ + final val CostKind_EQ_COA_GroupElement = PerItemCost(15, 5, 1) + final val OpDesc_EQ_COA_GroupElement = NamedDesc("EQ_COA_GroupElement") + final val EQ_COA_GroupElement = OperationCostInfo(CostKind_EQ_COA_GroupElement, OpDesc_EQ_COA_GroupElement) + + /** Equals two CollOverArray of BigInt type. */ + final val CostKind_EQ_COA_BigInt = PerItemCost(15, 7, 5) + final val OpDesc_EQ_COA_BigInt = NamedDesc("EQ_COA_BigInt") + final val EQ_COA_BigInt = OperationCostInfo(CostKind_EQ_COA_BigInt, OpDesc_EQ_COA_BigInt) + + /** Equals two CollOverArray of AvlTree type. */ + final val CostKind_EQ_COA_AvlTree = PerItemCost(15, 5, 2) + final val OpDesc_EQ_COA_AvlTree = NamedDesc("EQ_COA_AvlTree") + final val EQ_COA_AvlTree = OperationCostInfo(CostKind_EQ_COA_AvlTree, OpDesc_EQ_COA_AvlTree) + + // TODO v5.0: update value after serialization is avoided to compute ErgoBox.id + /** Equals two CollOverArray of Box type. */ + final val CostKind_EQ_COA_Box = PerItemCost(15, 5, 1) + final val OpDesc_EQ_COA_Box = NamedDesc("EQ_COA_Box") + final val EQ_COA_Box = OperationCostInfo(CostKind_EQ_COA_Box, OpDesc_EQ_COA_Box) + + /** Equals two CollOverArray of PreHeader type. */ + final val CostKind_EQ_COA_PreHeader = PerItemCost(15, 3, 1) + final val OpDesc_EQ_COA_PreHeader = NamedDesc("EQ_COA_PreHeader") + final val EQ_COA_PreHeader = OperationCostInfo(CostKind_EQ_COA_PreHeader, OpDesc_EQ_COA_PreHeader) + + /** Equals two CollOverArray of Header type. */ + final val CostKind_EQ_COA_Header = PerItemCost(15, 5, 1) + final val OpDesc_EQ_COA_Header = NamedDesc("EQ_COA_Header") + final val EQ_COA_Header = OperationCostInfo(CostKind_EQ_COA_Header, OpDesc_EQ_COA_Header) + + val descriptors: AVHashMap[RType[_], (OperationCostInfo[FixedCost], OperationCostInfo[PerItemCost])] = + AVHashMap.fromSeq(Array[(RType[_], (OperationCostInfo[FixedCost], OperationCostInfo[PerItemCost]))]( + (BigIntRType, (EQ_BigInt, EQ_COA_BigInt)), + (GroupElementRType, (EQ_GroupElement, EQ_COA_GroupElement)), + (AvlTreeRType, (EQ_AvlTree, EQ_COA_AvlTree)), + (BoxRType, (EQ_Box, EQ_COA_Box)), + (PreHeaderRType, (EQ_PreHeader, EQ_COA_PreHeader)), + (HeaderRType, (EQ_Header, EQ_COA_Header)) + )) + + type COA[A] = CollOverArray[A] + type POC[A,B] = PairOfCols[A, B] + + /** This method is specialized for numeric types and thus Scala generates four + * specialized methods (one for each type) which implement unboxed comparison or arrays + * in a most efficient way. This efficient implementation is reflected in the cost + * parameters, which are part of the protocol. Thus any alternative protocol + * implementation should implement comparison is the same way. + */ + private def equalCOA_Prim[@sp(Boolean, Byte, Short, Int, Long) A] + (c1: COA[A], c2: COA[A], costInfo: OperationCostInfo[PerItemCost]) + (implicit E: ErgoTreeEvaluator): Boolean = { + var okEqual = true + E.addSeqCost(costInfo.costKind, costInfo.opDesc) { () => + // TODO v5.0: this loop is bounded when MaxCollSize limit is enforced + val len = c1.length + var i = 0 + val a1 = c1.toArray + val a2 = c2.toArray + while (i < len && okEqual) { + okEqual = a1(i) == a2(i) + i += 1 + } + i // return the number of actually compared items + } + okEqual + } + + def equalColls[A](c1: Coll[A], c2: Coll[A])(implicit E: ErgoTreeEvaluator): Boolean = { + var okEqual = true + E.addSeqCost(CostKind_EQ_Coll, OpDesc_EQ_Coll) { () => + // TODO v5.0: this loop is bounded when MaxCollSize limit is enforced + val len = c1.length + var i = 0 + while(i < len && okEqual) { + okEqual = equalDataValues(c1(i), c2(i)) + i += 1 + } + i + } + okEqual + } + + def equalColls_Dispatch[A](coll1: Coll[A], coll2: Coll[A])(implicit E: ErgoTreeEvaluator): Boolean = { + coll1.tItem match { + case BooleanType => + equalCOA_Prim( + coll1.asInstanceOf[COA[Boolean]], + coll2.asInstanceOf[COA[Boolean]], EQ_COA_Boolean) + + case ByteType => + equalCOA_Prim( + coll1.asInstanceOf[COA[Byte]], + coll2.asInstanceOf[COA[Byte]], EQ_COA_Byte) + + case ShortType => + equalCOA_Prim( + coll1.asInstanceOf[COA[Short]], + coll2.asInstanceOf[COA[Short]], EQ_COA_Short) + + case IntType => + equalCOA_Prim( + coll1.asInstanceOf[COA[Int]], + coll2.asInstanceOf[COA[Int]], EQ_COA_Int) + + case LongType => + equalCOA_Prim( + coll1.asInstanceOf[COA[Long]], + coll2.asInstanceOf[COA[Long]], EQ_COA_Long) + + case t => + descriptors.get(t) match { + case Nullable((_, info)) => + equalCOA_Prim( + coll1.asInstanceOf[COA[A]], + coll2.asInstanceOf[COA[A]], info) + case _ => + equalColls(coll1, coll2) + } + } + } + + def equalSigmaBooleans(xs: Seq[SigmaBoolean], ys: Seq[SigmaBoolean]) + (implicit E: ErgoTreeEvaluator): Boolean = { + val len = xs.length + if (len != ys.length) return false + var okEqual = true + cfor(0)(_ < len && okEqual, _ + 1) { i => + okEqual = equalSigmaBoolean(xs(i), ys(i)) + } + okEqual + } + + def equalSigmaBoolean(l: SigmaBoolean, r: SigmaBoolean) + (implicit E: ErgoTreeEvaluator): Boolean = { + E.addCost(MatchType) // once for every node of the SigmaBoolean tree + l match { + case ProveDlog(x) => r match { + case ProveDlog(y) => equalECPoint(x, y) + case _ => false + } + case x: ProveDHTuple => r match { + case y: ProveDHTuple => + equalECPoint(x.gv, y.gv) && equalECPoint(x.hv, y.hv) && + equalECPoint(x.uv, y.uv) && equalECPoint(x.vv, y.vv) + case _ => false + } + case x: TrivialProp => r match { + case y: TrivialProp => x.condition == y.condition + case _ => false + } + case CAND(children) if r.isInstanceOf[CAND] => + equalSigmaBooleans(children, r.asInstanceOf[CAND].children) + case COR(children) if r.isInstanceOf[COR] => + equalSigmaBooleans(children, r.asInstanceOf[COR].children) + case CTHRESHOLD(k, children) if r.isInstanceOf[CTHRESHOLD] => + val sb2 = r.asInstanceOf[CTHRESHOLD] + k == sb2.k && equalSigmaBooleans(children, sb2.children) + case _ => + ErgoTreeEvaluator.error( + s"Cannot compare SigmaBoolean values $l and $r: unknown type") + } + } + + def equalGroupElement(ge1: GroupElement, r: Any)(implicit E: ErgoTreeEvaluator): Boolean = { + var okEqual = true + E.addFixedCost(EQ_GroupElement) { + okEqual = ge1 == r + } + okEqual + } + + def equalECPoint(p1: EcPointType, r: Any)(implicit E: ErgoTreeEvaluator): Boolean = { + var okEqual = true + E.addFixedCost(EQ_GroupElement) { + okEqual = p1 == r + } + okEqual + } + + // TODO v5.0: introduce a new limit on structural depth of data values + def equalDataValues(l: Any, r: Any)(implicit E: ErgoTreeEvaluator): Boolean = { + var okEqual: Boolean = false + l match { + case _: java.lang.Number | _: Boolean => /** case 1 (see [[EQ_Prim]]) */ + E.addFixedCost(EQ_Prim) { + okEqual = l == r + } + + case coll1: Coll[a] => /** case 2 (see [[EQ_Coll]]) */ + E.addCost(MatchType) // for second match below + okEqual = r match { + case coll2: Coll[_] => + val len = coll1.length + if (len != coll2.length || coll1.tItem != coll2.tItem) + return false + + equalColls_Dispatch(coll1, coll2.asInstanceOf[Coll[a]]) + + case _ => false + } + + case tup1: Tuple2[_,_] => /** case 3 (see [[EQ_Tuple]]) */ + E.addFixedCost(EQ_Tuple) { + okEqual = r match { + case tup2: Tuple2[_,_] => + equalDataValues(tup1._1, tup2._1) && equalDataValues(tup1._2, tup2._2) + case _ => false + } + } + + case ge1: GroupElement => /** case 4 (see [[EQ_GroupElement]]) */ + okEqual = equalGroupElement(ge1, r) + + case bi: BigInt => /** case 5 (see [[EQ_BigInt]]) */ + E.addFixedCost(EQ_BigInt) { + okEqual = bi == r + } + + case sp1: SigmaProp => + E.addCost(MatchType) // for second match below + okEqual = r match { + case sp2: SigmaProp => + equalSigmaBoolean( + SigmaDsl.toSigmaBoolean(sp1), + SigmaDsl.toSigmaBoolean(sp2)) + case _ => false + } + + case bi: AvlTree => /** case 6 (see [[EQ_AvlTree]]) */ + E.addFixedCost(EQ_AvlTree) { + okEqual = bi == r + } + + case opt1: Option[_] => /** case 7 (see [[EQ_Option]]) */ + E.addFixedCost(EQ_Option) { + okEqual = r match { + case opt2: Option[_] => + if (opt1.isDefined) { + if (opt2.isDefined) { + equalDataValues(opt1.get, opt2.get) + } else + false // right is not Some + } else { + // here left in None + opt2.isEmpty // return if the right is also None + } + case _ => + false // right is not an Option + } + } + case ph: PreHeader => /** case 8 (see [[EQ_PreHeader]]) */ + E.addFixedCost(EQ_PreHeader) { + okEqual = ph == r + } + case h: Header => /** case 9 (see [[EQ_Header]]) */ + E.addFixedCost(EQ_Header) { + okEqual = h == r + } + case box: Box => /** case 10 (see [[EQ_Box]]) */ + E.addFixedCost(EQ_Box) { + okEqual = box == r + } + case s1: String => + E.addCost(MatchType) // for second match below + okEqual = r match { + case s2: String => + val len = s1.length + if (len != s2.length) + return false + E.addSeqCost(EQ_COA_Short, len) { () => + s1 == s2 + } + case _ => false + } + case _: Unit => + okEqual = r.isInstanceOf[Unit] + + case _ => + ErgoTreeEvaluator.error(s"Cannot compare $l and $r: unknown type") + } + okEqual + } + +} diff --git a/sigmastate/src/main/scala/sigmastate/Values.scala b/sigmastate/src/main/scala/sigmastate/Values.scala index 09cc9616aa..d77661569a 100644 --- a/sigmastate/src/main/scala/sigmastate/Values.scala +++ b/sigmastate/src/main/scala/sigmastate/Values.scala @@ -4,14 +4,15 @@ import java.math.BigInteger import java.util import java.util.Objects -import org.bitbucket.inkytonik.kiama.rewriting.Rewriter.{strategy, count, everywherebu} +import org.bitbucket.inkytonik.kiama.rewriting.Rewriter.{count, everywherebu, strategy} +import org.ergoplatform.settings.ErgoAlgos import org.ergoplatform.validation.ValidationException import scalan.{Nullable, RType} import scalan.util.CollectionUtil._ -import sigmastate.SCollection.{SIntArray, SByteArray} +import sigmastate.SCollection.{SByteArray, SIntArray} import sigmastate.interpreter.CryptoConstants.EcPointType -import sigmastate.interpreter.{CryptoConstants, CompanionDesc} -import sigmastate.serialization.{OpCodes, ConstantStore, _} +import sigmastate.interpreter.{CompanionDesc, CryptoConstants, ErgoTreeEvaluator, NamedDesc} +import sigmastate.serialization.{ConstantStore, OpCodes, _} import sigmastate.serialization.OpCodes._ import sigmastate.TrivialProp.{FalseProp, TrueProp} import sigmastate.Values.ErgoTree.substConstants @@ -23,6 +24,7 @@ import special.sigma.Extensions._ import sigmastate.eval._ import sigmastate.eval.Extensions._ import scalan.util.Extensions.ByteOps +import sigmastate.interpreter.ErgoTreeEvaluator._ import spire.syntax.all.cfor import scala.language.implicitConversions @@ -31,8 +33,9 @@ import sigmastate.lang.CheckingSigmaBuilder._ import sigmastate.serialization.ErgoTreeSerializer.DefaultSerializer import sigmastate.serialization.transformers.ProveDHTupleSerializer import sigmastate.utils.{SigmaByteReader, SigmaByteWriter} -import special.sigma.{AvlTree, PreHeader, Header, _} +import special.sigma.{AvlTree, Header, PreHeader, _} import sigmastate.lang.SourceContext +import sigmastate.lang.exceptions.InterpreterException import special.collection.Coll import scala.collection.mutable @@ -91,6 +94,65 @@ object Values { } else { sys.error("_sourceContext can be set only once") } + + /** Defines an evaluation semantics of this tree node (aka Value or expression) in the given data environment. + * Should be implemented by all the ErgoTree nodes (aka operations). + * Thus, the ErgoTree interpreter implementation consists of combined implementations of this method. + * NOTE, this method shouldn't be called directly, instead use `evalTo` method. + * + * @param E Evaluator which defines evaluation context, cost accumulator, settings etc. + * @param env immutable map, which binds variables (given by ids) to the values + * @return the data value which is the result of evaluation + */ + protected def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = + sys.error(s"Should be overriden in ${this.getClass}: $this") + + /** Evaluates this node to the value of the given expected type. + * This method should called from all `eval` implementations. + * + * @tparam T expected type of the resulting value + * @param E Evaluator which defines evaluation context, cost accumulator, settings etc. + * @param env immutable map, which binds variables (given by ids) to the values + * @return the data value which is the result of evaluation + */ + @inline + final def evalTo[T](env: DataEnv)(implicit E: ErgoTreeEvaluator): T = { + if (E.settings.isMeasureOperationTime) E.profiler.onBeforeNode(this) + val v = eval(env) + if (E.settings.isMeasureOperationTime) E.profiler.onAfterNode(this) + v.asInstanceOf[T] + } + + /** Add the cost given by the kind to the accumulator and associate it with this operation + * node. + */ + @inline + final def addCost(costKind: FixedCost)(implicit E: ErgoTreeEvaluator): Unit = { + E.addCost(costKind, this.companion.opDesc) + } + + /** Add the cost given by the descriptor to the accumulator and associate it with this operation + * node. + */ + @inline + final def addCost[R](costKind: TypeBasedCost, tpe: SType)(block: () => R)(implicit E: ErgoTreeEvaluator): R = { + E.addTypeBasedCost(costKind, tpe, this.companion.opDesc)(block) + } + + /** Add the cost of a repeated operation to the accumulator and associate it with this + * operation. The number of items (loop iterations) is known in advance (like in + * Coll.map operation) + * + * @param costKind cost descriptor of the operation + * @param nItems number of operations known in advance (before loop execution) + * @param block operation executed under the given cost + * @tparam R result type of the operation + */ + @inline + final def addSeqCost[R](costKind: PerItemCost, nItems: Int) + (block: () => R)(implicit E: ErgoTreeEvaluator): R = { + E.addSeqCost(costKind, nItems, this.companion.opDesc)(block) + } } object Value { @@ -133,6 +195,35 @@ object Values { val c = count(deserializeNode)(exp) c > 0 } + + def typeError(node: SValue, evalResult: Any) = { + val tpe = node.tpe + throw new InterpreterException( + s"""Invalid type returned by evaluator: + | expression: $node + | expected type: $tpe + | resulting value: $evalResult + """.stripMargin) + } + + def typeError(tpe: SType, evalResult: Any) = { + throw new InterpreterException( + s"""Invalid type returned by evaluator: + | expected type: $tpe + | resulting value: $evalResult + """.stripMargin) + } + + def checkType(node: SValue, evalResult: Any) = { + val tpe = node.tpe + if (!SType.isValueOfType(evalResult, tpe)) + typeError(node, evalResult) + } + + def checkType(tpe: SType, evalResult: Any) = { + if (!SType.isValueOfType(evalResult, tpe)) + typeError(tpe, evalResult) + } } /** Base class for all companion objects which are used as operation descriptors. */ @@ -142,7 +233,7 @@ object Values { def opCode: OpCode /** Returns cost descriptor of this operation. */ - def costKind: CostKind = ??? // TODO v5.0: make abstract + def costKind: CostKind override def toString: String = s"${this.getClass.getSimpleName}(${opCode.toUByte})" @@ -166,13 +257,13 @@ object Values { /** Should be inherited by companion objects of operations with fixed cost kind. */ trait FixedCostValueCompanion extends ValueCompanion { /** Returns cost descriptor of this operation. */ - override def costKind: FixedCost = ??? // TODO v5.0: make abstract + override def costKind: FixedCost } /** Should be inherited by companion objects of operations with per-item cost kind. */ trait PerItemCostValueCompanion extends ValueCompanion { /** Returns cost descriptor of this operation. */ - override def costKind: PerItemCost = ??? // TODO v5.0: make abstract + override def costKind: PerItemCost } abstract class EvaluatedValue[+S <: SType] extends Value[S] { @@ -197,6 +288,11 @@ object Values { override def opCode: OpCode = companion.opCode override def opName: String = s"Const" + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + addCost(Constant.costKind) + value + } + override def equals(obj: scala.Any): Boolean = (obj != null) && (this.eq(obj.asInstanceOf[AnyRef]) || (obj match { case c: Constant[_] => tpe == c.tpe && Objects.deepEquals(value, c.value) case _ => false @@ -217,8 +313,10 @@ object Values { } } - object Constant extends ValueCompanion { + object Constant extends FixedCostValueCompanion { override def opCode: OpCode = ConstantCode + /** Cost of: returning value from Constant node. */ + override val costKind = FixedCost(5) /** Immutable empty array, can be used to save allocations in many places. */ val EmptyArray = Array.empty[Constant[SType]] @@ -272,9 +370,18 @@ object Values { case class ConstantPlaceholder[S <: SType](id: Int, override val tpe: S) extends Value[S] { def opType = SFunc(SInt, tpe) override def companion: ValueCompanion = ConstantPlaceholder + override protected def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val c = E.constants(id) + addCost(ConstantPlaceholder.costKind) + val res = c.value + Value.checkType(c, res) + res + } } object ConstantPlaceholder extends ValueCompanion { override def opCode: OpCode = ConstantPlaceholderCode + /** Cost of: accessing Constant in array by index. */ + override val costKind = FixedCost(1) } trait NotReadyValue[S <: SType] extends Value[S] { @@ -297,6 +404,7 @@ object Values { object TaggedVariable extends ValueCompanion { override def opCode: OpCode = TaggedVariableCode + override def costKind: CostKind = FixedCost(1) def apply[T <: SType](varId: Byte, tpe: T): TaggedVariable[T] = TaggedVariableNode(varId, tpe) } @@ -308,6 +416,7 @@ object Values { } object UnitConstant extends ValueCompanion { override def opCode = UnitConstantCode + override def costKind = Constant.costKind } type BoolValue = Value[SBoolean.type] @@ -547,9 +656,14 @@ object Values { case object GroupGenerator extends EvaluatedValue[SGroupElement.type] with ValueCompanion { override def opCode: OpCode = OpCodes.GroupGeneratorCode + override val costKind = FixedCost(10) override def tpe = SGroupElement override val value = SigmaDsl.GroupElement(CryptoConstants.dlogGroup.generator) override def companion = this + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + addCost(costKind) + SigmaDsl.groupGenerator + } } @@ -571,11 +685,13 @@ object Values { object TrueLeaf extends ConstantNode[SBoolean.type](true, SBoolean) with ValueCompanion { override def companion = this override def opCode: OpCode = TrueCode + override def costKind: FixedCost = Constant.costKind } object FalseLeaf extends ConstantNode[SBoolean.type](false, SBoolean) with ValueCompanion { override def companion = this override def opCode: OpCode = FalseCode + override def costKind: FixedCost = Constant.costKind } trait NotReadyValueBoolean extends NotReadyValue[SBoolean.type] { @@ -588,6 +704,8 @@ object Values { trait SigmaBoolean { /** Unique id of the node class used in serialization of SigmaBoolean. */ val opCode: OpCode + /** Size of the proposition tree (number of nodes). */ + def size: Int } object SigmaBoolean { @@ -627,6 +745,16 @@ object Values { } } + /** Compute total size of the trees in the collection of children. */ + def totalSize(children: Seq[SigmaBoolean]): Int = { + var res = 0 + val len = children.length + cfor(0)(_ < len, _ + 1) { i => + res += children(i).size + } + res + } + /** HOTSPOT: don't beautify this code */ object serializer extends SigmaSerializer[SigmaBoolean, SigmaBoolean] { val dhtSerializer = ProveDHTupleSerializer(ProveDHTuple.apply) @@ -716,10 +844,33 @@ object Values { val xs = items.cast[EvaluatedValue[SAny.type]].map(_.value) Colls.fromArray(xs.toArray(SAny.classTag.asInstanceOf[ClassTag[SAny.WrappedType]]))(RType.AnyType) } + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + if (items.length != 2) + error(s"Invalid tuple $this") + + val res: Any = if (items.length == 2) { + val item0 = items(0) + val x = item0.evalTo[Any](env) + Value.checkType(item0, x) + + val item1 = items(1) + val y = item1.evalTo[Any](env) + Value.checkType(item1, y) + + (x, y) // special representation for pairs (to pass directly to Coll primitives) + } + else + items.map(_.evalTo[Any](env)) // general case + + addCost(Tuple.costKind) + res + } } - object Tuple extends ValueCompanion { + object Tuple extends FixedCostValueCompanion { override def opCode: OpCode = TupleCode + /** Cost of: 1) allocating a new tuple (of limited max size)*/ + override val costKind = FixedCost(15) def apply(items: Value[SType]*): Tuple = Tuple(items.toIndexedSeq) } @@ -735,6 +886,7 @@ object Values { } object SomeValue extends ValueCompanion { override val opCode = SomeValueCode + override def costKind: CostKind = Constant.costKind } case class NoneValue[T <: SType](elemType: T) extends OptionValue[T] { @@ -744,6 +896,7 @@ object Values { } object NoneValue extends ValueCompanion { override val opCode = NoneValueCode + override def costKind: CostKind = Constant.costKind } case class ConcreteCollection[V <: SType](items: Seq[Value[V]], elementType: V) @@ -774,9 +927,25 @@ object Values { val xs = items.cast[EvaluatedValue[V]].map(_.value) Colls.fromArray(xs.toArray(elementType.classTag.asInstanceOf[ClassTag[V#WrappedType]])) } + + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val len = items.length + addCost(ConcreteCollection.costKind) + val is = Array.ofDim[V#WrappedType](len)(tElement.classTag) + cfor(0)(_ < len, _ + 1) { i => + val item = items(i) + val itemV = item.evalTo[V#WrappedType](env) + Value.checkType(item, itemV) // necessary because cast to V#WrappedType is erased + is(i) = itemV + } + Colls.fromArray(is) + } } object ConcreteCollection extends ValueCompanion { override def opCode: OpCode = ConcreteCollectionCode + /** Cost of: allocating new collection + * @see ConcreteCollection_PerItem */ + override val costKind = FixedCost(20) def fromSeq[V <: SType](items: Seq[Value[V]])(implicit tV: V): ConcreteCollection[V] = ConcreteCollection(items, tV) @@ -786,6 +955,7 @@ object Values { } object ConcreteCollectionBooleanConstant extends ValueCompanion { override def opCode: OpCode = ConcreteCollectionBooleanConstantCode + override def costKind = ConcreteCollection.costKind } trait LazyCollection[V <: SType] extends NotReadyValue[SCollection[V]] @@ -864,10 +1034,12 @@ object Values { } object ValDef extends ValueCompanion { override def opCode: OpCode = ValDefCode + override def costKind = Value.notSupportedError(this, "costKind") def apply(id: Int, rhs: SValue): ValDef = ValDef(id, Nil, rhs) } object FunDef extends ValueCompanion { override def opCode: OpCode = FunDefCode + override def costKind = Value.notSupportedError(this, "costKind") def unapply(d: BlockItem): Option[(Int, Seq[STypeVar], SValue)] = d match { case ValDef(id, targs, rhs) if !d.isValDef => Some((id, targs, rhs)) case _ => None @@ -875,14 +1047,23 @@ object Values { } /** Special node which represents a reference to ValDef it was introduced as result of - * CSE. */ + * CSE. */ case class ValUse[T <: SType](valId: Int, tpe: T) extends NotReadyValue[T] { override def companion = ValUse /** This is not used as operation, but rather to form a program structure */ def opType: SFunc = Value.notSupportedError(this, "opType") + + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + addCost(ValUse.costKind) + val res = env.getOrElse(valId, error(s"cannot resolve $this")) + Value.checkType(this, res) + res + } } - object ValUse extends ValueCompanion { + object ValUse extends FixedCostValueCompanion { override def opCode: OpCode = ValUseCode + /** Cost of: 1) Lookup in immutable HashMap by valId: Int 2) alloc of Some(v) */ + override val costKind = FixedCost(5) } /** The order of ValDefs in the block is used to assign ids to ValUse(id) nodes @@ -898,9 +1079,28 @@ object Values { /** This is not used as operation, but rather to form a program structure */ def opType: SFunc = Value.notSupportedError(this, "opType") + + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + var curEnv = env + val len = items.length + addSeqCost(BlockValue.costKind, len)(null) + cfor(0)(_ < len, _ + 1) { i => + val vd = items(i).asInstanceOf[ValDef] + val v = vd.rhs.evalTo[Any](curEnv) + Value.checkType(vd, v) + E.addFixedCost(FuncValue.AddToEnvironmentDesc_CostKind, + FuncValue.AddToEnvironmentDesc) { + curEnv = curEnv + (vd.id -> v) + } + } + val res = result.evalTo[Any](curEnv) + Value.checkType(result, res) + res + } } object BlockValue extends ValueCompanion { override def opCode: OpCode = BlockValueCode + override val costKind = PerItemCost(1, 1, 10) } /** * @param args parameters list, where each parameter has an id and a type. @@ -918,9 +1118,55 @@ object Values { } /** This is not used as operation, but rather to form a program structure */ override def opType: SFunc = SFunc(mutable.WrappedArray.empty, tpe) + + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + addCost(FuncValue.costKind) + if (args.length == 0) { + // TODO coverage + () => { + body.evalTo[Any](env) + } + } + else if (args.length == 1) { + val arg0 = args(0) + (vArg: Any) => { + Value.checkType(arg0._2, vArg) + var env1: DataEnv = null + E.addFixedCost(FuncValue.AddToEnvironmentDesc_CostKind, + FuncValue.AddToEnvironmentDesc) { + env1 = env + (arg0._1 -> vArg) + } + val res = body.evalTo[Any](env1) + Value.checkType(body, res) + res + } + } + else { + // TODO coverage + (vArgs: Seq[Any]) => { + var env1 = env + val len = args.length + cfor(0)(_ < len, _ + 1) { i => + val id = args(i)._1 + val v = vArgs(i) + E.addFixedCost(FuncValue.AddToEnvironmentDesc_CostKind, + FuncValue.AddToEnvironmentDesc) { + env1 = env1 + (id -> v) + } + } + body.evalTo[Any](env1) + } + } + } } - object FuncValue extends ValueCompanion { + object FuncValue extends FixedCostValueCompanion { + val AddToEnvironmentDesc = NamedDesc("AddToEnvironment") + /** Cost of: adding value to evaluator environment */ + val AddToEnvironmentDesc_CostKind = FixedCost(5) override def opCode: OpCode = FuncValueCode + /** Cost of: 1) switch on the number of args 2) allocating a new Scala closure + * Old cost: ("Lambda", "() => (D1) => R", lambdaCost),*/ + override val costKind = FixedCost(5) def apply(argId: Int, tArg: SType, body: SValue): FuncValue = FuncValue(IndexedSeq((argId,tArg)), body) } @@ -1055,6 +1301,9 @@ object Values { _bytes } + /** Hexadecimal encoded string of ErgoTree.bytes. */ + final def bytesHex: String = ErgoAlgos.encode(bytes) + private var _complexity: Int = givenComplexity /** Structural complexity estimation of this tree. diff --git a/sigmastate/src/main/scala/sigmastate/basics/DLogProtocol.scala b/sigmastate/src/main/scala/sigmastate/basics/DLogProtocol.scala index 8e7dce3162..75db9c69d2 100644 --- a/sigmastate/src/main/scala/sigmastate/basics/DLogProtocol.scala +++ b/sigmastate/src/main/scala/sigmastate/basics/DLogProtocol.scala @@ -25,7 +25,7 @@ object DLogProtocol { /** Construct a new SigmaBoolean value representing public key of discrete logarithm signature protocol. */ case class ProveDlog(value: EcPointType) extends SigmaProofOfKnowledgeLeaf[DLogSigmaProtocol, DLogProverInput] { - + override def size: Int = 1 override val opCode: OpCode = OpCodes.ProveDlogCode lazy val h: EcPointType = value lazy val pkBytes: Array[Byte] = GroupElementSerializer.toBytes(h) diff --git a/sigmastate/src/main/scala/sigmastate/basics/DiffieHellmanTupleProtocol.scala b/sigmastate/src/main/scala/sigmastate/basics/DiffieHellmanTupleProtocol.scala index 221122982b..5316bc70c9 100644 --- a/sigmastate/src/main/scala/sigmastate/basics/DiffieHellmanTupleProtocol.scala +++ b/sigmastate/src/main/scala/sigmastate/basics/DiffieHellmanTupleProtocol.scala @@ -65,6 +65,7 @@ case class SecondDiffieHellmanTupleProverMessage(z: BigInteger) extends SecondPr case class ProveDHTuple(gv: EcPointType, hv: EcPointType, uv: EcPointType, vv: EcPointType) extends SigmaProofOfKnowledgeLeaf[DiffieHellmanTupleProtocol, DiffieHellmanTupleProverInput] { override val opCode: OpCode = OpCodes.ProveDiffieHellmanTupleCode + override def size: Int = 4 // one node for each EcPoint lazy val g = gv lazy val h = hv lazy val u = uv diff --git a/sigmastate/src/main/scala/sigmastate/eval/BigIntegerOps.scala b/sigmastate/src/main/scala/sigmastate/eval/BigIntegerOps.scala index a03ba6fca0..c2deaaba90 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/BigIntegerOps.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/BigIntegerOps.scala @@ -2,7 +2,7 @@ package sigmastate.eval import java.math.BigInteger -import scalan.{ExactNumeric, ExactOrderingImpl} +import scalan.{ExactNumeric, ExactOrderingImpl, ExactIntegral} import scala.math.{Integral, Ordering} import special.sigma._ @@ -41,7 +41,17 @@ object NumericOps { trait BigIntIsIntegral extends Integral[BigInt] { def quot(x: BigInt, y: BigInt): BigInt = x.divide(y) - def rem(x: BigInt, y: BigInt): BigInt = x.remainder(y) + + /** This method is used in ErgoTreeEvaluator based interpreter, to implement '%' operation. + * Even though it is called `rem`, the semantics of ErgoTree language requires + * it to correspond to [[java.math.BigInteger]].mod method. + * Note also that there is no distinction between `mod` and `reminder` methods in + * [[scala.math.Integral]] trait the same way as it is in BigInteger class. + * For this reason we define implementation of this `rem` method using + * [[java.math.BigInteger]].mod. + */ + def rem(x: BigInt, y: BigInt): BigInt = x.mod(y) + def plus(x: BigInt, y: BigInt): BigInt = x.add(y) def minus(x: BigInt, y: BigInt): BigInt = x.subtract(y) def times(x: BigInt, y: BigInt): BigInt = x.multiply(y) @@ -72,6 +82,13 @@ object NumericOps { override def times(x: BigInt, y: BigInt): BigInt = n.times(x, y) } + implicit object BigIntIsExactIntegral extends ExactIntegral[BigInt] { + val n = BigIntIsIntegral + override def plus(x: BigInt, y: BigInt): BigInt = n.plus(x, y) + override def minus(x: BigInt, y: BigInt): BigInt = n.minus(x, y) + override def times(x: BigInt, y: BigInt): BigInt = n.times(x, y) + } + implicit object BigIntIsExactOrdering extends ExactOrderingImpl[BigInt](BigIntIsIntegral) } diff --git a/sigmastate/src/main/scala/sigmastate/eval/CostingDataContext.scala b/sigmastate/src/main/scala/sigmastate/eval/CostingDataContext.scala index 9b7ad8a88c..07d3705711 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/CostingDataContext.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/CostingDataContext.scala @@ -691,7 +691,7 @@ class CostingSigmaDslBuilder extends TestSigmaDslBuilder { dsl => case _ => sys.error(s"Cannot evaluate substConstants($scriptBytes, $positions, $newValues): cannot lift value $v") }) - val res = SubstConstants.eval(scriptBytes.toArray, positions.toArray, typedNewVals)(validationSettings) + val (res, _) = SubstConstants.eval(scriptBytes.toArray, positions.toArray, typedNewVals)(validationSettings) Colls.fromArray(res) } diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/ErgoTreeEvaluator.scala b/sigmastate/src/main/scala/sigmastate/interpreter/ErgoTreeEvaluator.scala new file mode 100644 index 0000000000..13ef9957d4 --- /dev/null +++ b/sigmastate/src/main/scala/sigmastate/interpreter/ErgoTreeEvaluator.scala @@ -0,0 +1,473 @@ +package sigmastate.interpreter + +import org.ergoplatform.ErgoLikeContext +import org.ergoplatform.SigmaConstants.ScriptCostLimit +import sigmastate.{FixedCost, PerItemCost, SType, TypeBasedCost} +import sigmastate.Values._ +import sigmastate.eval.Profiler +import sigmastate.interpreter.ErgoTreeEvaluator.DataEnv +import sigmastate.interpreter.Interpreter.ReductionResult +import special.sigma.{Context, SigmaProp} +import scalan.util.Extensions._ +import sigmastate.lang.Terms.MethodCall +import spire.syntax.all.cfor + +import scala.collection.mutable +import scala.util.DynamicVariable + +/** Configuration parameters of the evaluation run. */ +case class EvalSettings( + /** Used together with [[ErgoTreeEvaluator.profiler]] to measure individual operations timings. */ + isMeasureOperationTime: Boolean, + /** Used together with [[ErgoTreeEvaluator.profiler]] to measure script timings. */ + isMeasureScriptTime: Boolean, + /** Used by [[ErgoTreeEvaluator]] to conditionally perform debug mode operations. */ + isDebug: Boolean = false, + /** Used by [[ErgoTreeEvaluator]] to conditionally emit log messages. */ + isLogEnabled: Boolean = false, + /** Used by [[ErgoTreeEvaluator]] to conditionally build a trace of added costs. + * @see Value.addCost + */ + costTracingEnabled: Boolean = false, + /** Profiler which, when defined, should be used in [[ErgoTreeEvaluator]] constructor. */ + profilerOpt: Option[Profiler] = None, + /** Should be set to true, if evaluation is performed as part of test suite. + * In such a case, additional operations may be performed (such as sanity checks). */ + isTestRun: Boolean = false, + /** If true, then expected test vectors are pretty-printed. */ + printTestVectors: Boolean = false) + +/** Implements a simple and fast direct-style interpreter of ErgoTrees. + * + * ### Motivation + * [[ErgoTree]] is a simple declarative intermediate representation for Ergo contracts. It is + * designed to be compact in serialized form and directly executable, i.e. no additional + * transformation is necessary before it can be efficiently executed. + * + * This class implements a big-step recursive interpreter that works directly with + * ErgoTree HOAS. Because of this the evaluator is very simple and follows denotational + * semantics of ErgoTree (see https://ergoplatform.org/docs/ErgoTree.pdf). Or, the other + * way around, this implementation of ErgoTreeEvaluator is purely functional with + * immutable data structures and can be used as definition of ErgoTree's semantics. + * + * ### Implementation + * ErgoTreeEvaluator takes ErgoTree directly as it is deserialized as part of a + * transaction. No additional transformation is performed. + * ErgoTree is interpreted directly and all the intermediate data is stored in the + * runtime types. + * The runtime types are such types as [[special.collection.Coll]], + * [[special.sigma.SigmaProp]], [[special.sigma.AvlTree]], [[BigInt]], etc. + * It also use immutable Map to keep current [[DataEnv]] of computed [[ValDef]]s, as + * result only addition is used from the map, and deletion is essentially a garbage + * collection. + * + * ### Performance + * Since this interpreter directly works with SigmaDsl types (Coll, BigInt, SigmaProp + * etc), it turns out to be very fast. Since it also does JIT style costing instead of + * AOT style, it is 5-6x faster than existing implementation. + * + * @param context Represents blockchain data context for ErgoTree evaluation + * @param constants Segregated constants from ErgoTree, to lookup them from + * [[ConstantPlaceholder]] evaluation. + * @param coster Accumulates computation costs. + * @param profiler Performs operations profiling and time measurements (if enabled in settings). + * @param settings Settings to be used during evaluation. + */ +class ErgoTreeEvaluator( + val context: Context, + val constants: Seq[Constant[SType]], + protected val coster: CostAccumulator, + val profiler: Profiler, + val settings: EvalSettings) { + + /** Evaluates the given expression in the given data environment. */ + def eval(env: DataEnv, exp: SValue): Any = { + ErgoTreeEvaluator.currentEvaluator.withValue(this) { + exp.evalTo[Any](env)(this) + } + } + + /** Evaluates the given expression in the given data environment. */ + def evalWithCost(env: DataEnv, exp: SValue): (Any, Int) = { + val res = eval(env, exp) + val cost = coster.totalCost + (res, cost) + } + + /** Trace of cost items accumulated during execution of `eval` method. + * Call [[ArrayBuffer.clear()]] before each `eval` invocation. */ + val costTrace = { + val b = mutable.ArrayBuilder.make[CostItem] + b.sizeHint(1000) + b + } + + /** Adds the given cost to the `coster`. If tracing is enabled, associates the cost with + * the given operation. + * + * @param costKind kind of the cost to be added to `coster` + * @param opDesc operation descriptor to associate the cost with (when costTracingEnabled) + */ + final def addCost(costKind: FixedCost, opDesc: OperationDesc): Unit = { + coster.add(costKind.cost) + if (settings.costTracingEnabled) { + costTrace += FixedCostItem(opDesc, costKind) + } + } + + @inline final def addCost(costInfo: OperationCostInfo[FixedCost]): Unit = { + addCost(costInfo.costKind, costInfo.opDesc) + } + + /** Add the cost given by the cost descriptor and the type to the accumulator and + * associate it with this operation descriptor. + * + * @param costKind descriptor of the cost + * @param tpe specific type for which the cost should be computed by this descriptor + * (see costFunc method) + * @param opDesc operation which is associated with this cost + */ + @inline + final def addTypeBasedCost[R](costKind: TypeBasedCost, + tpe: SType, opDesc: OperationDesc)(block: () => R): R = { + var costItem: TypeBasedCostItem = null + if (settings.costTracingEnabled) { + costItem = TypeBasedCostItem(opDesc, costKind, tpe) + costTrace += costItem + } + if (settings.isMeasureOperationTime && block != null) { + if (costItem == null) { + costItem = TypeBasedCostItem(opDesc, costKind, tpe) + } + val start = System.nanoTime() + val cost = costKind.costFunc(tpe) // should be measured as part of the operation + coster.add(cost) + val res = block() + val end = System.nanoTime() + profiler.addCostItem(costItem, end - start) + res + } else { + val cost = costKind.costFunc(tpe) + coster.add(cost) + if (block == null) null.asInstanceOf[R] else block() + } + } + + /** Adds the given cost to the `coster`. If tracing is enabled, associates the cost with + * the given operation. + * @param costKind kind of the cost to be added to `coster` + * @param opDesc the operation descriptor to associate the cost with (when costTracingEnabled) + * @param block operation executed under the given cost + * @hotspot don't beautify the code + */ + final def addFixedCost(costKind: FixedCost, opDesc: OperationDesc)(block: => Unit): Unit = { + var costItem: FixedCostItem = null + if (settings.costTracingEnabled) { + costItem = FixedCostItem(opDesc, costKind) + costTrace += costItem + } + if (settings.isMeasureOperationTime) { + if (costItem == null) { + costItem = FixedCostItem(opDesc, costKind) + } + val start = System.nanoTime() + coster.add(costKind.cost) + val _ = block + val end = System.nanoTime() + profiler.addCostItem(costItem, end - start) + } else { + coster.add(costKind.cost) + block + } + } + + @inline + final def addFixedCost(costInfo: OperationCostInfo[FixedCost])(block: => Unit): Unit = { + addFixedCost(costInfo.costKind, costInfo.opDesc)(block) + } + + /** Adds the given cost to the `coster`. If tracing is enabled, creates a new cost item + * with the given operation. + * + * @param costKind the cost to be added to `coster` for each item + * @param nItems the number of items + * @param opDesc the operation to associate the cost with (when costTracingEnabled) + * @param block operation executed under the given cost + * @tparam R result type of the operation + * @hotspot don't beautify the code + */ + final def addSeqCost[R](costKind: PerItemCost, nItems: Int, opDesc: OperationDesc)(block: () => R): R = { + var costItem: SeqCostItem = null + if (settings.costTracingEnabled) { + costItem = SeqCostItem(opDesc, costKind, nItems) + costTrace += costItem + } + if (settings.isMeasureOperationTime && block != null) { + if (costItem == null) { + costItem = SeqCostItem(opDesc, costKind, nItems) + } + val start = System.nanoTime() + val cost = costKind.cost(nItems) // should be measured as part of the operation + coster.add(cost) + val res = block() + val end = System.nanoTime() + profiler.addCostItem(costItem, end - start) + res + } else { + val cost = costKind.cost(nItems) + coster.add(cost) + if (block == null) null.asInstanceOf[R] else block() + } + } + + @inline + final def addSeqCost[R](costInfo: OperationCostInfo[PerItemCost], nItems: Int) + (block: () => R): R = { + addSeqCost(costInfo.costKind, nItems, costInfo.opDesc)(block) + } + + /** Adds the cost to the `coster`. If tracing is enabled, creates a new cost item with + * the given operation descriptor and cost kind. If time measuring is enabled also + * performs profiling. + * + * WARNING: The cost is accumulated AFTER the block is executed. + * Each usage of this method should be accompanied with a proof of why this cannot lead + * to unbounded execution (see all usages). + * + * @param costKind the cost descriptor to be used to compute the cost based on the + * actual number of items returned by the `block` + * @param opDesc the operation to associate the cost with (when costTracingEnabled) + * @param block operation executed under the given cost descriptors, returns the + * actual number of items processed + * @hotspot don't beautify the code + */ + final def addSeqCost(costKind: PerItemCost, opDesc: OperationDesc)(block: () => Int): Unit = { + var costItem: SeqCostItem = null + var nItems = 0 + if (settings.isMeasureOperationTime) { + val start = System.nanoTime() + nItems = block() + val cost = costKind.cost(nItems) // should be measured as part of the operation + coster.add(cost) + val end = System.nanoTime() + + costItem = SeqCostItem(opDesc, costKind, nItems) + profiler.addCostItem(costItem, end - start) + } else { + nItems = block() + val cost = costKind.cost(nItems) + coster.add(cost) + } + if (settings.costTracingEnabled) { + if (costItem == null) + costItem = SeqCostItem(opDesc, costKind, nItems) + costTrace += costItem + } + } + + final def addSeqCost(costInfo: OperationCostInfo[PerItemCost])(block: () => Int): Unit = { + addSeqCost(costInfo.costKind, costInfo.opDesc)(block) + } + + + final def addMethodCallCost[R](mc: MethodCall, obj: Any, args: Array[Any]) + (block: => R): R = { + val costDetails = ErgoTreeEvaluator.calcCost(mc, obj, args)(this) + if (settings.costTracingEnabled) { + costTrace += MethodCallCostItem(costDetails) + } + + if (settings.isMeasureOperationTime) { + coster.add(costDetails.cost) + // measure time + val start = System.nanoTime() + val res = block + val end = System.nanoTime() + val time = end - start + + val len = costDetails.trace.length + val totalCost = costDetails.cost + + // spread the measured time between individial cost items + cfor(0)(_ < len, _ + 1) { i => + val costItem = costDetails.trace(i) + profiler.addCostItem(costItem, time * costItem.cost / totalCost) + } + res + } else { + coster.add(costDetails.cost) + block + } + } +} + +object ErgoTreeEvaluator { + /** Immutable data environment used to assign data values to graph nodes. */ + type DataEnv = Map[Int, Any] + + /** Size of data block in bytes. Used in JIT cost calculations. + * @see [[sigmastate.NEQ]], + */ + val DataBlockSize: Int = 512 + + /** Empty data environment. */ + val EmptyDataEnv: DataEnv = Map.empty + + /** A profiler which is used by default if [[EvalSettings.isMeasureOperationTime]] is enabled. */ + val DefaultProfiler = new Profiler + + /** Default global [[EvalSettings]] instance. */ + val DefaultEvalSettings = EvalSettings( + isMeasureOperationTime = false, + isMeasureScriptTime = false) + + /** Helper method to compute cost details for the given method call. */ + def calcCost(mc: MethodCall, obj: Any, args: Array[Any]) + (implicit E: ErgoTreeEvaluator): CostDetails = { + // add approximated cost of invoked method (if specified) + val cost = mc.method.costFunc match { + case Some(costFunc) => costFunc(E, mc, obj, args) + case _ => CostDetails.ZeroCost // TODO v5.0: throw exception if not defined + } + cost + } + + /** Evaluator currently is being executed on the current thread. + * This variable is set in a single place, specifically in the `eval` method of + * [[ErgoTreeEvaluator]]. + * @see getCurrentEvaluator + */ + private[sigmastate] val currentEvaluator = new DynamicVariable[ErgoTreeEvaluator](null) + + /** Returns a current evaluator for the current thread. */ + def getCurrentEvaluator: ErgoTreeEvaluator = currentEvaluator.value + + /** Creates a new [[ErgoTreeEvaluator]] instance with the given profiler and settings. + * The returned evaluator can be used to initialize the `currentEvaluator` variable. + * As a result, cost-aware operations (code blocks) can be implemented, even when those + * operations don't involve ErgoTree evaluation. + * As an example, see methods in [[sigmastate.SigSerializer]] and + * [[sigmastate.FiatShamirTree]] where cost-aware code blocks are used. + */ + def forProfiling(profiler: Profiler, evalSettings: EvalSettings): ErgoTreeEvaluator = { + val acc = new CostAccumulator(0, Some(ScriptCostLimit.value)) + new ErgoTreeEvaluator( + context = null, + constants = mutable.WrappedArray.empty, + acc, profiler, evalSettings.copy(profilerOpt = Some(profiler))) + } + + /** Executes [[FixedCost]] code `block` and use the given evaluator `E` to perform + * profiling and cost tracing. + * This helper method allows implementation of cost-aware code blocks by using + * thread-local instance of [[ErgoTreeEvaluator]]. + * If the `currentEvaluator` [[DynamicVariable]] is not initialized (equals to null), + * then the block is executed with minimal overhead. + * + * @param costInfo operation descriptor + * @param block block of code to be executed (given as lazy by-name argument) + * @param E evaluator to be used (or null if it is not avaialble on the + * current thread) + * @return result of code block execution + */ + def fixedCostOp[R <: AnyRef](costInfo: OperationCostInfo[FixedCost]) + (block: => R)(implicit E: ErgoTreeEvaluator): R = { + if (E != null) { + var res: R = null.asInstanceOf[R] + E.addFixedCost(costInfo) { + res = block + } + res + } else + block + } + + /** Executes [[PerItemCost]] code `block` and use the given evaluator `E` to perform + * profiling and cost tracing. + * This helper method allows implementation of cost-aware code blocks by using + * thread-local instance of [[ErgoTreeEvaluator]]. + * If the `currentEvaluator` [[DynamicVariable]] is not initialized (equals to null), + * then the block is executed with minimal overhead. + * + * @param costInfo operation descriptor + * @param nItems number of data items in the operation + * @param block block of code to be executed (given as lazy by-name argument) + * @param E evaluator to be used (or null if it is not avaialble on the + * current thread) + * @return result of code block execution + */ + def perItemCostOp[R](costInfo: OperationCostInfo[PerItemCost], nItems: Int) + (block: () => R)(implicit E: ErgoTreeEvaluator): R = { + if (E != null) { + E.addSeqCost(costInfo, nItems)(block) + } else + block() + } + + /** Evaluate the given [[ErgoTree]] in the given Ergo context using the given settings. + * The given ErgoTree is evaluated as-is and is not changed during evaluation. + * + * @param context [[ErgoLikeContext]] used for script execution + * @param ergoTree script represented as [[ErgoTree]] + * @param evalSettings evaluation settings + * @return a sigma protocol proposition (as [[SigmaBoolean]]) and accumulated JIT cost estimation. + */ + def evalToCrypto(context: ErgoLikeContext, ergoTree: ErgoTree, evalSettings: EvalSettings): ReductionResult = { + val (res, cost) = eval(context, ergoTree.constants, ergoTree.toProposition(replaceConstants = false), evalSettings) + val sb = res match { + case sp: SigmaProp => + sigmastate.eval.SigmaDsl.toSigmaBoolean(sp) + case sb: SigmaBoolean => sb + case _ => error(s"Expected SigmaBoolean but was: $res") + } + ReductionResult(sb, cost) + } + + /** Evaluate the given expression in the given Ergo context using the given settings. + * The given Value is evaluated as-is and is not changed during evaluation. + * + * @param context [[ErgoLikeContext]] used for script execution + * @param constants collection of segregated constants which can be refered by + * [[ConstantPlaceholder]]s in `exp` + * @param exp ErgoTree expression represented as [[Value]] + * @param evalSettings evaluation settings + * @return 1) the result of evaluating `exp` in a given context and + * 2) an accumulated JIT cost estimation. + */ + def eval(context: ErgoLikeContext, + constants: Seq[Constant[SType]], + exp: SValue, + evalSettings: EvalSettings): (Any, Int) = { + val costAccumulator = new CostAccumulator(context.initCost.toIntExact, Some(context.costLimit)) + val sigmaContext = context.toSigmaContext(isCost = false) + eval(sigmaContext, costAccumulator, constants, exp, evalSettings) + } + + /** Evaluate the given expression in the given Ergo context using the given settings. + * The given Value is evaluated as-is and is not changed during evaluation. + * + * @param sigmaContext [[special.sigma.Context]] instance used for script execution + * @param costAccumulator [[CostAccumulator]] instance used for accumulating costs + * @param constants collection of segregated constants which can be refered by + * [[ConstantPlaceholder]]s in `exp` + * @param exp ErgoTree expression represented as [[sigmastate.Values.Value]] + * @param evalSettings evaluation settings + * @return 1) the result of evaluating `exp` in a given context and + * 2) an accumulated JIT cost estimation. + */ + def eval(sigmaContext: Context, + costAccumulator: CostAccumulator, + constants: Seq[Constant[SType]], + exp: SValue, + evalSettings: EvalSettings): (Any, Int) = { + val evaluator = new ErgoTreeEvaluator( + sigmaContext, constants, costAccumulator, DefaultProfiler, evalSettings) + val res = evaluator.eval(Map(), exp) + val cost = costAccumulator.totalCost + (res, cost) + } + + def error(msg: String) = sys.error(msg) +} + + diff --git a/sigmastate/src/main/scala/sigmastate/lang/SigmaTyper.scala b/sigmastate/src/main/scala/sigmastate/lang/SigmaTyper.scala index 4e24c9c2d5..f7855fcf0b 100644 --- a/sigmastate/src/main/scala/sigmastate/lang/SigmaTyper.scala +++ b/sigmastate/src/main/scala/sigmastate/lang/SigmaTyper.scala @@ -139,7 +139,7 @@ class SigmaTyper(val builder: SigmaBuilder, obj.tpe match { case p: SProduct => p.method(n) match { - case Some(method @ SMethod(_, _, genFunTpe @ SFunc(_, _, _), _, _, _)) => + case Some(method @ SMethod(_, _, genFunTpe @ SFunc(_, _, _), _, _, _, _, _)) => val subst = Map(genFunTpe.tpeParams.head.ident -> rangeTpe) val concrFunTpe = applySubst(genFunTpe, subst) val expectedArgs = concrFunTpe.asFunc.tDom.tail diff --git a/sigmastate/src/main/scala/sigmastate/lang/Terms.scala b/sigmastate/src/main/scala/sigmastate/lang/Terms.scala index ac7cbac32c..5ba0449c6f 100644 --- a/sigmastate/src/main/scala/sigmastate/lang/Terms.scala +++ b/sigmastate/src/main/scala/sigmastate/lang/Terms.scala @@ -2,14 +2,17 @@ package sigmastate.lang import org.bitbucket.inkytonik.kiama.rewriting.Rewriter._ import scalan.Nullable -import sigmastate.SCollection.{SByteArray, SIntArray} +import sigmastate.SCollection.{SIntArray, SByteArray} import sigmastate.Values._ import sigmastate.utils.Overloading.Overload1 import sigmastate._ +import sigmastate.interpreter.ErgoTreeEvaluator +import sigmastate.interpreter.ErgoTreeEvaluator.DataEnv import sigmastate.serialization.OpCodes import sigmastate.serialization.OpCodes.OpCode import sigmastate.lang.TransformingSigmaBuilder._ +import scala.collection.mutable import scala.language.implicitConversions import scala.collection.mutable.WrappedArray import spire.syntax.all.cfor @@ -28,6 +31,7 @@ object Terms { } object Block extends ValueCompanion { override def opCode: OpCode = OpCodes.Undefined + override def costKind: CostKind = Value.notSupportedError(this, "costKind") def apply(let: Val, result: SValue)(implicit o1: Overload1): Block = Block(Seq(let), result) } @@ -51,6 +55,7 @@ object Terms { } object ZKProofBlock extends ValueCompanion { override def opCode: OpCode = OpCodes.Undefined + override def costKind: CostKind = Value.notSupportedError(this, "costKind") val OpType = SFunc(SSigmaProp, SBoolean) } @@ -78,6 +83,7 @@ object Terms { } object ValNode extends ValueCompanion { override def opCode: OpCode = OpCodes.Undefined + override def costKind: CostKind = Value.notSupportedError(this, "costKind") } /** Frontend node to select a field from an object. Should be transformed to SelectField*/ @@ -94,6 +100,7 @@ object Terms { } object Select extends ValueCompanion { override def opCode: OpCode = OpCodes.Undefined + override def costKind: CostKind = Value.notSupportedError(this, "costKind") } /** Frontend node to represent variable names parsed in a source code. @@ -105,6 +112,7 @@ object Terms { } object Ident extends ValueCompanion { override def opCode: OpCode = OpCodes.Undefined + override def costKind: CostKind = Value.notSupportedError(this, "costKind") def apply(name: String): Ident = Ident(name, NoType) } @@ -129,9 +137,32 @@ object Terms { } SFunc(argTypes, tpe) } + + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + addCost(Apply.costKind) + if (args.isEmpty) { + // TODO coverage + val fV = func.evalTo[() => Any](env) + fV() + } + else if (args.length == 1) { + val fV = func.evalTo[Any => Any](env) + val argV = args(0).evalTo[Any](env) + fV(argV) + } + else { + // TODO coverage + val f = func.evalTo[Seq[Any] => Any](env) + val argsV = args.map(a => a.evalTo[Any](env)) + f(argsV) + } + } } - object Apply extends ValueCompanion { + object Apply extends FixedCostValueCompanion { override def opCode: OpCode = OpCodes.FuncApplyCode + /** Cost of: 1) switch on the number of args 2) Scala method call 3) add args to env + * Old cost: lambdaInvoke == 30 */ + override val costKind = FixedCost(30) } /** Apply types for type parameters of input value. */ @@ -148,6 +179,7 @@ object Terms { } object ApplyTypes extends ValueCompanion { override def opCode: OpCode = OpCodes.Undefined + override def costKind: CostKind = Value.notSupportedError(this, "costKind") } /** Frontend node to represent potential method call in a source code. @@ -159,6 +191,7 @@ object Terms { } object MethodCallLike extends ValueCompanion { override def opCode: OpCode = OpCodes.Undefined + override def costKind: CostKind = Value.notSupportedError(this, "costKind") } /** Represents in ErgoTree an invocation of method of the object `obj` with arguments `args`. @@ -186,9 +219,48 @@ object Terms { case f: SFunc => f.tRange.withSubstTypes(typeSubst) case t => t.withSubstTypes(typeSubst) } + + /** @hotspot don't beautify this code */ + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val objV = obj.evalTo[Any](env) + addCost(MethodCall.costKind) // MethodCall overhead + method.costKind match { + case fixed: FixedCost => + val extra = method.extraDescriptors + val extraLen = extra.length + val len = args.length + val argsBuf = new Array[Any](len + extraLen) + cfor(0)(_ < len, _ + 1) { i => + argsBuf(i) = args(i).evalTo[Any](env) + } + cfor(0)(_ < extraLen, _ + 1) { i => + argsBuf(len + i) = extra(i) + } + var res: Any = null + E.addFixedCost(fixed, method.opDesc) { + res = method.invokeFixed(objV, argsBuf) + } + res + case _ => + val len = args.length + val argsBuf = new Array[Any](len + 3) + argsBuf(0) = this + argsBuf(1) = objV + cfor(0)(_ < len, _ + 1) { i => + argsBuf(i + 2) = args(i).evalTo[Any](env) + } + argsBuf(argsBuf.length - 1) = E + + val evalMethod = method.genericMethod.evalMethod + evalMethod.invoke(method.objType, argsBuf.asInstanceOf[Array[AnyRef]]:_*) + } + } } + object MethodCall extends ValueCompanion { override def opCode: OpCode = OpCodes.MethodCallCode + /** Cost of: 1) packing args into Array 2) java.lang.reflect.Method.invoke */ + override val costKind = FixedCost(4) /** Helper constructor which allows to cast the resulting node to the specified * [[sigmastate.Values.Value]] type `T`. @@ -201,8 +273,10 @@ object Terms { MethodCall(obj, method, args, typeSubst).asInstanceOf[T] } } - object PropertyCall extends ValueCompanion { + object PropertyCall extends FixedCostValueCompanion { override def opCode: OpCode = OpCodes.PropertyCallCode + /** Cost of: 1) packing args into Array 2) java.lang.reflect.Method.invoke */ + override val costKind = FixedCost(4) } case class STypeParam(ident: STypeVar, upperBound: Option[SType] = None, lowerBound: Option[SType] = None) { @@ -231,6 +305,7 @@ object Terms { } object Lambda extends ValueCompanion { override def opCode: OpCode = OpCodes.Undefined + override def costKind: CostKind = Value.notSupportedError(this, "costKind") def apply(args: IndexedSeq[(String,SType)], resTpe: SType, body: Value[SType]): Lambda = Lambda(Nil, args, resTpe, Some(body)) def apply(args: IndexedSeq[(String,SType)], resTpe: SType, body: Option[Value[SType]]): Lambda = diff --git a/sigmastate/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala index a4aa1b33b7..5bc96450ae 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala @@ -248,7 +248,7 @@ class ErgoTreeSerializer { def substituteConstants(scriptBytes: Array[Byte], positions: Array[Int], - newVals: Array[Value[SType]])(implicit vs: SigmaValidationSettings): Array[Byte] = { + newVals: Array[Value[SType]])(implicit vs: SigmaValidationSettings): (Array[Byte], Int) = { require(positions.length == newVals.length, s"expected positions and newVals to have the same length, got: positions: ${positions.toSeq},\n newVals: ${newVals.toSeq}") val r = SigmaSerializer.startReader(scriptBytes) @@ -276,7 +276,7 @@ class ErgoTreeSerializer { } w.putBytes(treeBytes) - w.toBytes + (w.toBytes, constants.length) } } diff --git a/sigmastate/src/main/scala/sigmastate/trees.scala b/sigmastate/src/main/scala/sigmastate/trees.scala index 2be2afb08f..de84d6b33b 100644 --- a/sigmastate/src/main/scala/sigmastate/trees.scala +++ b/sigmastate/src/main/scala/sigmastate/trees.scala @@ -2,16 +2,29 @@ package sigmastate import org.ergoplatform.SigmaConstants import org.ergoplatform.validation.SigmaValidationSettings +import scalan.{ExactIntegral, ExactNumeric, ExactOrdering, Nullable} import scalan.OverloadHack.Overloaded1 -import scorex.crypto.hash.{Sha256, Blake2b256, CryptographicHash32} +import scorex.crypto.hash.{Blake2b256, CryptographicHash32, Sha256} import sigmastate.Operations._ -import sigmastate.SCollection.{SIntArray, SByteArray} +import sigmastate.SCollection.{SByteArray, SIntArray} import sigmastate.SOption.SIntOption import sigmastate.Values._ -import sigmastate.basics.{SigmaProtocol, SigmaProtocolPrivateInput, SigmaProtocolCommonInput} +import sigmastate.basics.{SigmaProtocol, SigmaProtocolCommonInput, SigmaProtocolPrivateInput} +import sigmastate.interpreter.ErgoTreeEvaluator +import sigmastate.interpreter.ErgoTreeEvaluator.DataEnv import sigmastate.serialization.OpCodes._ import sigmastate.serialization._ -import sigmastate.utxo.{Transformer, SimpleTransformerCompanion} +import sigmastate.utxo.{SimpleTransformerCompanion, Transformer} +import debox.{Map => DMap} +import scalan.ExactIntegral._ +import scalan.ExactNumeric._ +import scalan.ExactOrdering._ +import sigmastate.ArithOp.OperationImpl +import sigmastate.eval.NumericOps.{BigIntIsExactIntegral, BigIntIsExactNumeric, BigIntIsExactOrdering} +import sigmastate.eval.{Colls, SigmaDsl} +import sigmastate.lang.TransformingSigmaBuilder +import special.collection.Coll +import special.sigma.{GroupElement, SigmaProp} import scala.collection.mutable import scala.collection.mutable.ArrayBuffer @@ -37,6 +50,7 @@ trait SigmaProofOfKnowledgeLeaf[SP <: SigmaProtocol[SP], S <: SigmaProtocolPriva case class CAND(override val children: Seq[SigmaBoolean]) extends SigmaConjecture { /** The same code is used for AND operation, but they belong to different type hierarchies. */ override val opCode: OpCode = OpCodes.AndCode + override val size: Int = SigmaBoolean.totalSize(children) + 1 } object CAND { @@ -72,6 +86,7 @@ object CAND { case class COR(children: Seq[SigmaBoolean]) extends SigmaConjecture { /** The same code is also used for OR operation, but they belong to different type hierarchies. */ override val opCode: OpCode = OpCodes.OrCode + override val size: Int = SigmaBoolean.totalSize(children) + 1 } object COR { @@ -109,6 +124,7 @@ case class CTHRESHOLD(k: Int, children: Seq[SigmaBoolean]) extends SigmaConjectu require(k >= 0 && k <= children.length && children.length <= 255) override val opCode: OpCode = OpCodes.AtLeastCode + override val size: Int = SigmaBoolean.totalSize(children) + 1 } @@ -129,10 +145,12 @@ object TrivialProp { val FalseProp = new TrivialProp(false) { override val opCode: OpCode = OpCodes.TrivialPropFalseCode + override def size: Int = 1 override def toString = "FalseProp" } val TrueProp = new TrivialProp(true) { override val opCode: OpCode = OpCodes.TrivialPropTrueCode + override def size: Int = 1 override def toString = "TrueProp" } } @@ -145,9 +163,15 @@ case class BoolToSigmaProp(value: BoolValue) extends SigmaPropValue { override def companion = BoolToSigmaProp override def tpe = SSigmaProp override def opType = BoolToSigmaProp.OpType + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val v = value.evalTo[Boolean](env) + addCost(BoolToSigmaProp.costKind) + SigmaDsl.sigmaProp(v) + } } object BoolToSigmaProp extends ValueCompanion { override def opCode: OpCode = OpCodes.BoolToSigmaPropCode + override val costKind = FixedCost(15) val OpType = SFunc(SBoolean, SSigmaProp) } @@ -157,9 +181,15 @@ case class CreateProveDlog(value: Value[SGroupElement.type]) extends SigmaPropVa override def companion = CreateProveDlog override def tpe = SSigmaProp override def opType = CreateProveDlog.OpType + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val v = value.evalTo[GroupElement](env) + addCost(CreateProveDlog.costKind) + SigmaDsl.proveDlog(v) + } } object CreateProveDlog extends ValueCompanion { override def opCode: OpCode = OpCodes.ProveDlogCode + override val costKind = FixedCost(10) val OpType = SFunc(SGroupElement, SSigmaProp) } @@ -175,6 +205,7 @@ case class CreateAvlTree(operationFlags: ByteValue, } object CreateAvlTree extends ValueCompanion { override def opCode: OpCode = OpCodes.AvlTreeCode + override def costKind: CostKind = Value.notSupportedError(this, "costKind") val OpType = SFunc(Array(SByte, SByteArray, SInt, SIntOption), SAvlTree) } @@ -188,9 +219,18 @@ case class CreateProveDHTuple(gv: Value[SGroupElement.type], override def companion = CreateProveDHTuple override def tpe = SSigmaProp override def opType = SFunc(IndexedSeq(SGroupElement, SGroupElement, SGroupElement, SGroupElement), SSigmaProp) + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val g = gv.evalTo[GroupElement](env) + val h = hv.evalTo[GroupElement](env) + val u = uv.evalTo[GroupElement](env) + val v = vv.evalTo[GroupElement](env) + addCost(CreateProveDHTuple.costKind) + SigmaDsl.proveDHTuple(g, h, u, v) + } } object CreateProveDHTuple extends ValueCompanion { override def opCode: OpCode = OpCodes.ProveDiffieHellmanTupleCode + override val costKind = FixedCost(20) } trait SigmaTransformer[IV <: SigmaPropValue, OV <: SigmaPropValue] extends SigmaPropValue { @@ -199,6 +239,7 @@ trait SigmaTransformer[IV <: SigmaPropValue, OV <: SigmaPropValue] extends Sigma trait SigmaTransformerCompanion extends ValueCompanion { def argInfos: Seq[ArgInfo] } + /** * AND conjunction for sigma propositions */ @@ -206,10 +247,25 @@ case class SigmaAnd(items: Seq[SigmaPropValue]) extends SigmaTransformer[SigmaPr override def companion = SigmaAnd override def tpe = SSigmaProp override def opType = SigmaAnd.OpType + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val len = items.length + val is = new Array[SigmaProp](len) + cfor(0)(_ < len, _ + 1) { i => + is(i) = items(i).evalTo[SigmaProp](env) + } + addSeqCost(SigmaAnd.costKind, len) { () => + SigmaDsl.allZK(Colls.fromArray(is)) + } + } } object SigmaAnd extends SigmaTransformerCompanion { val OpType = SFunc(SCollection.SSigmaPropArray, SSigmaProp) override def opCode: OpCode = OpCodes.SigmaAndCode + /** BaseCost: + * - constructing new CSigmaProp and allocation collection + * - one iteration over collection of items + */ + override val costKind = PerItemCost(baseCost = 10, perChunkCost = 2, chunkSize = 1) override def argInfos: Seq[ArgInfo] = SigmaAndInfo.argInfos def apply(first: SigmaPropValue, second: SigmaPropValue, tail: SigmaPropValue*): SigmaAnd = SigmaAnd(Array(first, second) ++ tail) } @@ -221,11 +277,25 @@ case class SigmaOr(items: Seq[SigmaPropValue]) extends SigmaTransformer[SigmaPro override def companion = SigmaOr override def tpe = SSigmaProp override def opType = SigmaOr.OpType + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val len = items.length + val is = new Array[SigmaProp](len) + cfor(0)(_ < len, _ + 1) { i => + is(i) = items(i).evalTo[SigmaProp](env) + } + addSeqCost(SigmaOr.costKind, len) { () => + SigmaDsl.anyZK(Colls.fromArray(is)) + } + } } object SigmaOr extends SigmaTransformerCompanion { val OpType = SFunc(SCollection.SSigmaPropArray, SSigmaProp) override def opCode: OpCode = OpCodes.SigmaOrCode + /** BaseCost: + * - constructing new CSigmaProp and allocation collection + * - one iteration over collection of items */ + override val costKind = PerItemCost(baseCost = 10, perChunkCost = 2, chunkSize = 1) override def argInfos: Seq[ArgInfo] = SigmaOrInfo.argInfos def apply(head: SigmaPropValue, tail: SigmaPropValue*): SigmaOr = SigmaOr(head +: tail) } @@ -243,10 +313,30 @@ case class OR(input: Value[SCollection[SBoolean.type]]) extends Transformer[SCollection[SBoolean.type], SBoolean.type] with NotReadyValueBoolean { override def companion = OR override def opType = OR.OpType + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val inputV = input.evalTo[Coll[Boolean]](env) + var res = false + val len = inputV.length + var i = 0 + E.addSeqCost(OR.costKind, this.companion.opDesc) { () => + // this loop is bounded since ErgoTree is bounded by MaxBoxSize + while (i < len && !res) { + res ||= inputV(i) + i += 1 + } + i // return actual number of processed items + } + res + } } object OR extends LogicalTransformerCompanion { override def opCode: OpCode = OrCode + /** Base cost: operations factored out of reduction loop. + * Per-chunk cost: cost of scala `||` operations amortized over a chunk of boolean values. + * @see BinOr + * @see AND */ + override val costKind = PerItemCost(5, 5, 64/*size of cache line in bytes*/) override def argInfos: Seq[ArgInfo] = Operations.ORInfo.argInfos def apply(children: Seq[Value[SBoolean.type]]): OR = @@ -261,10 +351,35 @@ case class XorOf(input: Value[SCollection[SBoolean.type]]) extends Transformer[SCollection[SBoolean.type], SBoolean.type] with NotReadyValueBoolean { override def companion = XorOf override def opType = XorOf.OpType + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val inputV = input.evalTo[Coll[Boolean]](env) + val len = inputV.length + addSeqCost(XorOf.costKind, len) { () => + val res = if (E.context.activatedScriptVersion >= 2) { + if (len == 0) false + else if (len == 1) inputV(0) + else { + var res = inputV(0) + cfor(1)(_ < len, _ + 1) { i => + res ^= inputV(i) + } + res + } + } else { + SigmaDsl.xorOf(inputV) + } + res + } + } } object XorOf extends LogicalTransformerCompanion { override def opCode: OpCode = XorOfCode + /** Base cost: operations factored out of reduction loop. + * Per-chunk cost: cost of scala `||` operations amortized over a chunk of boolean values. + * @see BinOr + * @see AND */ + override val costKind = PerItemCost(20, 5, 32) override def argInfos: Seq[ArgInfo] = Operations.XorOfInfo.argInfos def apply(children: Seq[Value[SBoolean.type]]): XorOf = @@ -279,10 +394,30 @@ case class AND(input: Value[SCollection[SBoolean.type]]) with NotReadyValueBoolean { override def companion = AND override def opType = AND.OpType + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val inputV = input.evalTo[Coll[Boolean]](env) + var res = true + val len = inputV.length + var i = 0 + E.addSeqCost(AND.costKind, this.companion.opDesc) { () => + // this loop is bounded since ErgoTree is bounded by MaxBoxSize + while (i < len && res) { + res &&= inputV(i) + i += 1 + } + i // return actual number of processed items + } + res + } } object AND extends LogicalTransformerCompanion { override def opCode: OpCode = AndCode + /** Base cost: operations factored out of reduction loop. + * Per-chunk cost: cost of scala `&&` operations amortized over a chunk of boolean values. + * @see BinAnd + * @see OR */ + override val costKind = PerItemCost(10, 5, 32/* half size of cache line in bytes */) override def argInfos: Seq[ArgInfo] = Operations.ANDInfo.argInfos def apply(children: Seq[Value[SBoolean.type]]): AND = @@ -304,10 +439,21 @@ case class AtLeast(bound: Value[SInt.type], input: Value[SCollection[SSigmaProp. override def tpe: SSigmaProp.type = SSigmaProp override def opType: SFunc = AtLeast.OpType + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val b = bound.evalTo[Int](env) + val props = input.evalTo[Coll[SigmaProp]](env) + addSeqCost(AtLeast.costKind, props.length) { () => + SigmaDsl.atLeast(b, props) + } + } } object AtLeast extends ValueCompanion { override def opCode: OpCode = AtLeastCode + /** Base cost: constructing new CSigmaProp value + * Per chunk cost: obtaining SigmaBooleans for each chunk in AtLeast + */ + override val costKind = PerItemCost(20, 3, 5) val OpType: SFunc = SFunc(Array(SInt, SCollection.SBooleanArray), SBoolean) val MaxChildrenCount: Int = SigmaConstants.MaxChildrenCountForAtLeastOp.value @@ -379,12 +525,33 @@ case class Upcast[T <: SNumericType, R <: SNumericType](input: Value[T], tpe: R) require(input.tpe.isInstanceOf[SNumericType], s"Cannot create Upcast node for non-numeric type ${input.tpe}") override def companion = Upcast override def opType = Upcast.OpType + + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val inputV = input.evalTo[AnyVal](env) + addCost(Upcast.costKind, tpe) { () => + tpe.upcast(inputV) + } + } } /** Base class for Upcast and Downcast companion objects. */ trait NumericCastCompanion extends ValueCompanion { def argInfos: Seq[ArgInfo] val OpType = SFunc(Array(SType.tT), SType.tR) + /** Returns cost descriptor of this operation. */ + def costKind: TypeBasedCost = NumericCastCostKind +} + +/** Cost of: + * 1) converting numeric value to the numeric value of the given type, i.e. Byte -> Int + * NOTE: the cost of BigInt casting is the same in JITC (comparing to AOTC) to simplify + * implementation. + */ +object NumericCastCostKind extends TypeBasedCost { + override def costFunc(targetTpe: SType): Int = targetTpe match { + case SBigInt => 30 + case _ => 10 + } } object Upcast extends NumericCastCompanion { @@ -392,7 +559,7 @@ object Upcast extends NumericCastCompanion { override def argInfos: Seq[ArgInfo] = UpcastInfo.argInfos def tT = SType.tT def tR = SType.tR - val BigIntOpType = SFunc(tT, SBigInt) + val BigIntOpType = SFunc(tT, SBigInt) // TODO check usage } /** @@ -403,6 +570,12 @@ case class Downcast[T <: SNumericType, R <: SNumericType](input: Value[T], tpe: require(input.tpe.isInstanceOf[SNumericType], s"Cannot create Downcast node for non-numeric type ${input.tpe}") override def companion = Downcast override def opType = Downcast.OpType + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val inputV = input.evalTo[AnyVal](env) + addCost(Downcast.costKind, tpe) { () => + tpe.downcast(inputV) + } + } } object Downcast extends NumericCastCompanion { @@ -410,7 +583,7 @@ object Downcast extends NumericCastCompanion { override def argInfos: Seq[ArgInfo] = DowncastInfo.argInfos def tT = SType.tT def tR = SType.tR - val BigIntOpType = SFunc(SBigInt, tR) + val BigIntOpType = SFunc(SBigInt, tR) // TODO check usage } /** @@ -420,10 +593,16 @@ case class LongToByteArray(input: Value[SLong.type]) extends Transformer[SLong.type, SByteArray] with NotReadyValueByteArray { override def companion = LongToByteArray override def opType = LongToByteArray.OpType + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val inputV = input.evalTo[Long](env) + addCost(LongToByteArray.costKind) + SigmaDsl.longToByteArray(inputV) + } } object LongToByteArray extends SimpleTransformerCompanion { val OpType = SFunc(SLong, SByteArray) override def opCode: OpCode = OpCodes.LongToByteArrayCode + override val costKind = FixedCost(17) override def argInfos: Seq[ArgInfo] = LongToByteArrayInfo.argInfos } @@ -434,10 +613,16 @@ case class ByteArrayToLong(input: Value[SByteArray]) extends Transformer[SByteArray, SLong.type] with NotReadyValueLong { override def companion = ByteArrayToLong override def opType = ByteArrayToLong.OpType + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val inputV = input.evalTo[Coll[Byte]](env) + addCost(ByteArrayToLong.costKind) + SigmaDsl.byteArrayToLong(inputV) + } } object ByteArrayToLong extends SimpleTransformerCompanion { val OpType = SFunc(SByteArray, SLong) override def opCode: OpCode = OpCodes.ByteArrayToLongCode + override val costKind = FixedCost(16) override def argInfos: Seq[ArgInfo] = ByteArrayToLongInfo.argInfos } @@ -448,10 +633,16 @@ case class ByteArrayToBigInt(input: Value[SByteArray]) extends Transformer[SByteArray, SBigInt.type] with NotReadyValueBigInt { override def companion = ByteArrayToBigInt override val opType = ByteArrayToBigInt.OpType + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val inputV = input.evalTo[Coll[Byte]](env) + addCost(ByteArrayToBigInt.costKind) + SigmaDsl.byteArrayToBigInt(inputV) + } } object ByteArrayToBigInt extends SimpleTransformerCompanion { val OpType = SFunc(SByteArray, SBigInt) override def opCode: OpCode = OpCodes.ByteArrayToBigIntCode + override val costKind = FixedCost(30) override def argInfos: Seq[ArgInfo] = ByteArrayToBigIntInfo.argInfos } @@ -462,10 +653,20 @@ case class DecodePoint(input: Value[SByteArray]) extends Transformer[SByteArray, SGroupElement.type] with NotReadyValueGroupElement { override def companion = DecodePoint override def opType = DecodePoint.OpType + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val inputV = input.evalTo[Coll[Byte]](env) + addCost(DecodePoint.costKind) + SigmaDsl.decodePoint(inputV) + } } -object DecodePoint extends SimpleTransformerCompanion { +object DecodePoint extends SimpleTransformerCompanion with FixedCostValueCompanion { val OpType = SFunc(SByteArray, SGroupElement) override def opCode: OpCode = OpCodes.DecodePointCode + /** Cost of: + * 1) create reader and read bytes in a new array + * 2) calling curve.decodePoint and obtain EcPoint + * 3) wrap EcPoint in GroupElement*/ + override val costKind = FixedCost(300) override def argInfos: Seq[ArgInfo] = DecodePointInfo.argInfos } @@ -483,9 +684,42 @@ object CalcHash { case class CalcBlake2b256(override val input: Value[SByteArray]) extends CalcHash { override def companion = CalcBlake2b256 override val hashFn: CryptographicHash32 = Blake2b256 + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val inputV = input.evalTo[Coll[Byte]](env) + addSeqCost(CalcBlake2b256.costKind, inputV.length) { () => + SigmaDsl.blake2b256(inputV) + } + } } object CalcBlake2b256 extends SimpleTransformerCompanion { override def opCode: OpCode = OpCodes.CalcBlake2b256Code + + /** Cost of: of hashing 1 block of data. + * + * This cost is used as a baseline to connect cost units with absolute time. + * The block validation have 1000000 of cost units budget, and we want this to + * correspond to 1 second. Thus we can assume 1 cost unit == 1 micro-second. + * + * It takes approximately 1 micro-seconds on average to compute hash of 128 bytes + * block on MacBook Pro (16-inch, 2019) 2.3 GHz 8-Core Intel Core i9. + * + * Thus per block cost of Blake2b256 hashing can be limited by 1 cost units. + * However, on a less powerful processor it may take much more time, so we add + * a factor of 3 for that. Additionally, the interpreter have an overhead so that + * performing 1000 of hashes in a tight loop is 3-4 times faster then doing the same + * via ErgoTreeEvaluator. Thus we should add another factor of 2 and this takes + * place for all operations. So we will use a total factor of 10 to convert + * actual operation micro-seconds time (obtained via benchmarking) to cost unit + * estimation (used for cost prediction). + * + * Cost_in_units = time_in_micro-seconds * 7 = 7 + * + * NOTE, 128 is the size of message chunk processed by Blake2b256 algorithm. + * + * @see [[sigmastate.interpreter.ErgoTreeEvaluator.DataBlockSize]] + */ + override val costKind = PerItemCost(baseCost = 20, perChunkCost = 7, chunkSize = 128) + override def argInfos: Seq[ArgInfo] = CalcBlake2b256Info.argInfos } @@ -495,9 +729,17 @@ object CalcBlake2b256 extends SimpleTransformerCompanion { case class CalcSha256(override val input: Value[SByteArray]) extends CalcHash { override def companion = CalcSha256 override val hashFn: CryptographicHash32 = Sha256 + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val inputV = input.evalTo[Coll[Byte]](env) + addSeqCost(CalcSha256.costKind, inputV.length) { () => + SigmaDsl.sha256(inputV) + } + } } object CalcSha256 extends SimpleTransformerCompanion { override def opCode: OpCode = OpCodes.CalcSha256Code + /** perChunkCost - cost of hashing 64 bytes of data (see also CalcBlake2b256). */ + override val costKind = PerItemCost(baseCost = 80, perChunkCost = 8, chunkSize = 64) override def argInfos: Seq[ArgInfo] = CalcSha256Info.argInfos } @@ -520,14 +762,39 @@ case class SubstConstants[T <: SType](scriptBytes: Value[SByteArray], positions: extends NotReadyValueByteArray { override def companion = SubstConstants override val opType = SubstConstants.OpType + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val scriptBytesV = scriptBytes.evalTo[Coll[Byte]](env) + val positionsV = positions.evalTo[Coll[Int]](env) + val newValuesV = newValues.evalTo[Coll[T#WrappedType]](env) + var res: Coll[Byte] = null + E.addSeqCost(SubstConstants.costKind, SubstConstants.opDesc) { () => + val typedNewVals: Array[SValue] = newValuesV.toArray.map { v => + TransformingSigmaBuilder.liftAny(v) match { + case Nullable(v) => v + case _ => sys.error(s"Cannot evaluate substConstants($scriptBytesV, $positionsV, $newValuesV): cannot lift value $v") + } + } + + val (newBytes, nConstants) = SubstConstants.eval( + scriptBytes = scriptBytesV.toArray, + positions = positionsV.toArray, + newVals = typedNewVals)(SigmaDsl.validationSettings) + + res = Colls.fromArray(newBytes) + nConstants + } + res + } } object SubstConstants extends ValueCompanion { override def opCode: OpCode = OpCodes.SubstConstantsCode + override val costKind = PerItemCost(100, 100, 1) + val OpType = SFunc(Array(SByteArray, SIntArray, SCollection(SType.tT)), SByteArray) def eval(scriptBytes: Array[Byte], positions: Array[Int], - newVals: Array[Value[SType]])(implicit vs: SigmaValidationSettings): Array[Byte] = + newVals: Array[Value[SType]])(implicit vs: SigmaValidationSettings): (Array[Byte], Int) = ErgoTreeSerializer.DefaultSerializer.substituteConstants(scriptBytes, positions, newVals) } @@ -572,23 +839,138 @@ case class ArithOp[T <: SType](left: Value[T], right: Value[T], override val opC case OpCodes.MinCode => s"Min($left, $right)" case OpCodes.MaxCode => s"Max($left, $right)" } + + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val x = left.evalTo[Any](env) + val y = right.evalTo[Any](env) + companion.eval(this, tpe.typeCode, x, y) // NOTE: cost is added as part of eval call + } } /** NOTE: by-name argument is required for correct initialization order. */ -class ArithOpCompanion(val opCode: OpCode, val name: String, _argInfos: => Seq[ArgInfo]) extends TwoArgumentOperationCompanion { +abstract class ArithOpCompanion(val opCode: OpCode, val name: String, _argInfos: => Seq[ArgInfo]) + extends TwoArgumentOperationCompanion { override def argInfos: Seq[ArgInfo] = _argInfos + override def costKind: TypeBasedCost + @inline final def eval(node: SValue, typeCode: SType.TypeCode, x: Any, y: Any)(implicit E: ErgoTreeEvaluator): Any = { + val impl = ArithOp.numerics(typeCode) + node.addCost(costKind, impl.argTpe) { () => + eval(impl, x, y) + } + } + def eval(impl: OperationImpl, x: Any, y: Any): Any } + object ArithOp { import OpCodes._ - object Plus extends ArithOpCompanion(PlusCode, "+", PlusInfo.argInfos) - object Minus extends ArithOpCompanion(MinusCode, "-", MinusInfo.argInfos) - object Multiply extends ArithOpCompanion(MultiplyCode, "*", MultiplyInfo.argInfos) - object Division extends ArithOpCompanion(DivisionCode, "/", DivisionInfo.argInfos) - object Modulo extends ArithOpCompanion(ModuloCode, "%", ModuloInfo.argInfos) - object Min extends ArithOpCompanion(MinCode, "min", MinInfo.argInfos) - object Max extends ArithOpCompanion(MaxCode, "max", MaxInfo.argInfos) + object Plus extends ArithOpCompanion(PlusCode, "+", PlusInfo.argInfos) { + def eval(impl: OperationImpl, x: Any, y: Any): Any = impl.n.plus(x, y) + /** Cost of: + * 1) resolving ArithOpCompanion by typeCode + * 2) calling method of Numeric + */ + override val costKind = new TypeBasedCost { + override def costFunc(tpe: SType): Int = tpe match { + case SBigInt => 20 + case _ => 15 + } + } + } + object Minus extends ArithOpCompanion(MinusCode, "-", MinusInfo.argInfos) { + def eval(impl: OperationImpl, x: Any, y: Any): Any = impl.n.minus(x, y) + /** Cost of: + * 1) resolving ArithOpCompanion by typeCode + * 2) calling method of Numeric + */ + override val costKind = new TypeBasedCost { + override def costFunc(tpe: SType): Int = tpe match { + case SBigInt => 20 + case _ => 15 + } + } + } + object Multiply extends ArithOpCompanion(MultiplyCode, "*", MultiplyInfo.argInfos) { + def eval(impl: OperationImpl, x: Any, y: Any): Any = impl.n.times(x, y) + /** Cost of: + * 1) resolving ArithOpCompanion by typeCode + * 2) calling method of Numeric + */ + override val costKind = new TypeBasedCost { + override def costFunc(tpe: SType): Int = tpe match { + case SBigInt => 25 + case _ => 15 + } + } + } + object Division extends ArithOpCompanion(DivisionCode, "/", DivisionInfo.argInfos) { + def eval(impl: OperationImpl, x: Any, y: Any): Any = impl.i.quot(x, y) + /** Cost of: + * 1) resolving ArithOpCompanion by typeCode + * 2) calling method of Integral + */ + override val costKind = new TypeBasedCost { + override def costFunc(tpe: SType): Int = tpe match { + case SBigInt => 25 + case _ => 15 + } + } + } + object Modulo extends ArithOpCompanion(ModuloCode, "%", ModuloInfo.argInfos) { + def eval(impl: OperationImpl, x: Any, y: Any): Any = impl.i.rem(x, y) + /** Cost of: + * 1) resolving ArithOpCompanion by typeCode + * 2) calling method of Integral + */ + override val costKind = new TypeBasedCost { + override def costFunc(tpe: SType): Int = tpe match { + case SBigInt => 25 + case _ => 15 + } + } + } + object Min extends ArithOpCompanion(MinCode, "min", MinInfo.argInfos) { + def eval(impl: OperationImpl, x: Any, y: Any): Any = impl.o.min(x, y) + /** Cost of: + * 1) resolving ArithOpCompanion by typeCode + * 2) calling method of ExactOrdering + */ + override val costKind = new TypeBasedCost { + override def costFunc(tpe: SType): Int = tpe match { + case SBigInt => 10 + case _ => 5 + } + } + } + object Max extends ArithOpCompanion(MaxCode, "max", MaxInfo.argInfos) { + def eval(impl: OperationImpl, x: Any, y: Any): Any = impl.o.max(x, y) + /** Cost of: + * 1) resolving ArithOpCompanion by typeCode + * 2) calling method of ExactOrdering + */ + override val costKind = new TypeBasedCost { + override def costFunc(tpe: SType): Int = tpe match { + case SBigInt => 10 + case _ => 5 + } + } + } + + private[sigmastate] val operations: DMap[Byte, ArithOpCompanion] = + DMap.fromIterable(Seq(Plus, Minus, Multiply, Division, Modulo, Min, Max).map(o => (o.opCode, o))) + + class OperationImpl(_n: ExactNumeric[_], _i: ExactIntegral[_], _o: ExactOrdering[_], val argTpe: SType) { + val n = _n.asInstanceOf[ExactNumeric[Any]] + val i = _i.asInstanceOf[ExactIntegral[Any]] + val o = _o.asInstanceOf[ExactOrdering[Any]] + } - val operations: Map[Byte, ArithOpCompanion] = - Seq(Plus, Minus, Multiply, Division, Modulo, Min, Max).map(o => (o.opCode, o)).toMap + private[sigmastate] val numerics: DMap[SType.TypeCode, OperationImpl] = + DMap.fromIterable(Seq( + SByte -> new OperationImpl(ByteIsExactNumeric, ByteIsExactIntegral, ByteIsExactOrdering, SByte), + SShort -> new OperationImpl(ShortIsExactNumeric, ShortIsExactIntegral, ShortIsExactOrdering, SShort), + SInt -> new OperationImpl(IntIsExactNumeric, IntIsExactIntegral, IntIsExactOrdering, SInt), + SLong -> new OperationImpl(LongIsExactNumeric, LongIsExactIntegral, LongIsExactOrdering, SLong), + SBigInt -> new OperationImpl(BigIntIsExactNumeric, BigIntIsExactIntegral, BigIntIsExactOrdering, SBigInt) + ).map { case (t, n) => (t.typeCode, n) }) def opcodeToArithOpName(opCode: Byte): String = operations.get(opCode) match { case Some(c) => c.name @@ -601,9 +983,16 @@ case class Negation[T <: SType](input: Value[T]) extends OneArgumentOperation[T, require(input.tpe.isNumTypeOrNoType, s"invalid type ${input.tpe}") override def companion = Negation override def tpe: T = input.tpe + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val inputV = input.evalTo[AnyVal](env) + val n = ArithOp.numerics(input.tpe.typeCode).n + addCost(Negation.costKind) + n.negate(inputV) + } } object Negation extends OneArgumentOperationCompanion { override def opCode: OpCode = OpCodes.NegationCode + override val costKind = FixedCost(30) override def argInfos: Seq[ArgInfo] = NegationInfo.argInfos } @@ -614,6 +1003,7 @@ case class BitInversion[T <: SType](input: Value[T]) extends OneArgumentOperatio } object BitInversion extends OneArgumentOperationCompanion { override def opCode: OpCode = OpCodes.BitInversionCode + override def costKind: CostKind = Value.notSupportedError(this, "costKind") override def argInfos: Seq[ArgInfo] = BitInversionInfo.argInfos } @@ -625,18 +1015,30 @@ case class BitOp[T <: SType](left: Value[T], right: Value[T], override val opCod override val opType = SFunc(Array[SType](left.tpe, right.tpe), tpe) } /** NOTE: by-name argument is required for correct initialization order. */ -class BitOpCompanion(val opCode: OpCode, val name: String, _argInfos: => Seq[ArgInfo]) extends TwoArgumentOperationCompanion { +abstract class BitOpCompanion(val opCode: OpCode, val name: String, _argInfos: => Seq[ArgInfo]) extends TwoArgumentOperationCompanion { override def argInfos: Seq[ArgInfo] = _argInfos } object BitOp { import OpCodes._ - object BitOr extends BitOpCompanion(BitOrCode, "|", BitOrInfo.argInfos) - object BitAnd extends BitOpCompanion(BitAndCode, "&", BitAndInfo.argInfos) - object BitXor extends BitOpCompanion(BitXorCode, "^", BitXorInfo.argInfos) - object BitShiftRight extends BitOpCompanion(BitShiftRightCode, ">>", BitShiftRightInfo.argInfos) - object BitShiftLeft extends BitOpCompanion(BitShiftLeftCode, "<<", BitShiftLeftInfo.argInfos) - object BitShiftRightZeroed extends BitOpCompanion(BitShiftRightZeroedCode, ">>>", BitShiftRightZeroedInfo.argInfos) + object BitOr extends BitOpCompanion(BitOrCode, "|", BitOrInfo.argInfos) { + override val costKind = FixedCost(1) + } + object BitAnd extends BitOpCompanion(BitAndCode, "&", BitAndInfo.argInfos) { + override val costKind = FixedCost(1) + } + object BitXor extends BitOpCompanion(BitXorCode, "^", BitXorInfo.argInfos) { + override val costKind = FixedCost(1) + } + object BitShiftRight extends BitOpCompanion(BitShiftRightCode, ">>", BitShiftRightInfo.argInfos) { + override val costKind = FixedCost(1) + } + object BitShiftLeft extends BitOpCompanion(BitShiftLeftCode, "<<", BitShiftLeftInfo.argInfos) { + override val costKind = FixedCost(1) + } + object BitShiftRightZeroed extends BitOpCompanion(BitShiftRightZeroedCode, ">>>", BitShiftRightZeroedInfo.argInfos) { + override val costKind = FixedCost(1) + } val operations: Map[Byte, BitOpCompanion] = Seq(BitOr, BitAnd, BitXor, BitShiftRight, BitShiftLeft, BitShiftRightZeroed).map(o => (o.opCode, o)).toMap @@ -656,6 +1058,7 @@ case class ModQ(input: Value[SBigInt.type]) } object ModQ extends ValueCompanion { override def opCode: OpCode = OpCodes.ModQCode + override val costKind: CostKind = FixedCost(1) } case class ModQArithOp(left: Value[SBigInt.type], right: Value[SBigInt.type], override val opCode: OpCode) @@ -666,6 +1069,7 @@ case class ModQArithOp(left: Value[SBigInt.type], right: Value[SBigInt.type], ov } abstract class ModQArithOpCompanion(val opCode: OpCode, val name: String) extends ValueCompanion { def argInfos: Seq[ArgInfo] + override val costKind: CostKind = FixedCost(1) } trait OpGroup[C <: ValueCompanion] { @@ -701,10 +1105,18 @@ case class Xor(override val left: Value[SByteArray], with NotReadyValueByteArray { override def companion = Xor override def opType = Xor.OpType + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val lV = left.evalTo[Coll[Byte]](env) + val rV = right.evalTo[Coll[Byte]](env) + addSeqCost(Xor.costKind, lV.length) { () => + Colls.xor(lV, rV) + } + } } object Xor extends TwoArgumentOperationCompanion { val OpType = SFunc(Array(SByteArray, SByteArray), SByteArray) override def opCode: OpCode = XorCode + override val costKind = PerItemCost(10, 2, 128) override def argInfos: Seq[ArgInfo] = XorInfo.argInfos } @@ -714,10 +1126,19 @@ case class Exponentiate(override val left: Value[SGroupElement.type], with NotReadyValueGroupElement { override def companion = Exponentiate override def opType = Exponentiate.OpType + + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val leftV = left.evalTo[GroupElement](env) + val rightV = right.evalTo[special.sigma.BigInt](env) + addCost(Exponentiate.costKind) + leftV.exp(rightV) + } } -object Exponentiate extends TwoArgumentOperationCompanion { +object Exponentiate extends TwoArgumentOperationCompanion with FixedCostValueCompanion { val OpType = SFunc(Array(SGroupElement, SBigInt), SGroupElement) override def opCode: OpCode = ExponentiateCode + /** Cost of: 1) calling EcPoint.multiply 2) wrapping in GroupElement */ + override val costKind = FixedCost(900) override def argInfos: Seq[ArgInfo] = ExponentiateInfo.argInfos } @@ -727,10 +1148,18 @@ case class MultiplyGroup(override val left: Value[SGroupElement.type], with NotReadyValueGroupElement { override def companion = MultiplyGroup override def opType = MultiplyGroup.OpType + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val leftV = left.evalTo[GroupElement](env) + val rightV = right.evalTo[GroupElement](env) + addCost(MultiplyGroup.costKind) + leftV.multiply(rightV) + } } -object MultiplyGroup extends TwoArgumentOperationCompanion { +object MultiplyGroup extends TwoArgumentOperationCompanion with FixedCostValueCompanion { val OpType = SFunc(Array(SGroupElement, SGroupElement), SGroupElement) override def opCode: OpCode = MultiplyGroupCode + /** Cost of: 1) calling EcPoint.add 2) wrapping in GroupElement */ + override val costKind = FixedCost(40) override def argInfos: Seq[ArgInfo] = MultiplyGroupInfo.argInfos } // Relation @@ -740,6 +1169,7 @@ sealed trait Relation[LIV <: SType, RIV <: SType] extends Triple[LIV, RIV, SBool trait SimpleRelation[T <: SType] extends Relation[T, T] { override def opType = SimpleRelation.GenericOpType + lazy val opImpl = ArithOp.numerics(left.tpe.typeCode) } object SimpleRelation { val GenericOpType = SFunc(SType.IndexedSeqOfT2, SBoolean) @@ -754,9 +1184,26 @@ trait RelationCompanion extends ValueCompanion { */ case class LT[T <: SType](override val left: Value[T], override val right: Value[T]) extends SimpleRelation[T] { override def companion = LT + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val lV = left.evalTo[Any](env) + val rV = right.evalTo[Any](env) + addCost(LT.costKind, left.tpe) { () => + opImpl.o.lt(lV, rV) + } + } } object LT extends RelationCompanion { override def opCode: OpCode = LtCode + /** Cost of: + * 1) resolving ArithOpCompanion by typeCode + * 2) calling method of Numeric + */ + override val costKind = new TypeBasedCost { + override def costFunc(tpe: SType): Int = tpe match { + case SBigInt => 20 + case _ => 20 + } + } override def argInfos: Seq[ArgInfo] = LTInfo.argInfos } /** @@ -764,19 +1211,53 @@ object LT extends RelationCompanion { */ case class LE[T <: SType](override val left: Value[T], override val right: Value[T]) extends SimpleRelation[T] { override def companion = LE + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val lV = left.evalTo[Any](env) + val rV = right.evalTo[Any](env) + addCost(LE.costKind, left.tpe) { () => + opImpl.o.lteq(lV, rV) + } + } } object LE extends RelationCompanion { override def opCode: OpCode = LeCode + /** Cost of: + * 1) resolving ArithOpCompanion by typeCode + * 2) calling method of Numeric + */ + override val costKind = new TypeBasedCost { + override def costFunc(tpe: SType): Int = tpe match { + case SBigInt => 20 // cf. comparisonBigInt + case _ => 20 // cf. comparisonCost + } + } override def argInfos: Seq[ArgInfo] = LEInfo.argInfos } /** - * Greater operation for SInt + * Greater operation for [[SNumericType]] values */ case class GT[T <: SType](override val left: Value[T], override val right: Value[T]) extends SimpleRelation[T] { override def companion = GT + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val lV = left.evalTo[Any](env) + val rV = right.evalTo[Any](env) + addCost(GT.costKind, left.tpe) { () => + opImpl.o.gt(lV, rV) + } + } } object GT extends RelationCompanion { override def opCode: OpCode = GtCode + /** Cost of: + * 1) resolving ArithOpCompanion by typeCode + * 2) calling method of Numeric + */ + override val costKind = new TypeBasedCost { + override def costFunc(tpe: SType): Int = tpe match { + case SBigInt => 20 // cf. comparisonBigInt + case _ => 20 // cf. comparisonCost + } + } override def argInfos: Seq[ArgInfo] = GTInfo.argInfos } /** @@ -784,9 +1265,26 @@ object GT extends RelationCompanion { */ case class GE[T <: SType](override val left: Value[T], override val right: Value[T]) extends SimpleRelation[T] { override def companion = GE + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val lV = left.evalTo[Any](env) + val rV = right.evalTo[Any](env) + addCost(GE.costKind, left.tpe) { () => + opImpl.o.gteq(lV, rV) + } + } } object GE extends RelationCompanion { override def opCode: OpCode = GeCode + /** Cost of: + * 1) resolving ArithOpCompanion by typeCode + * 2) calling method of Numeric + */ + override val costKind = new TypeBasedCost { + override def costFunc(tpe: SType): Int = tpe match { + case SBigInt => 20 + case _ => 20 + } + } override def argInfos: Seq[ArgInfo] = GEInfo.argInfos } @@ -794,12 +1292,21 @@ object GE extends RelationCompanion { * Equals operation for SType * todo: make EQ to really accept only values of the same type, now EQ(TrueLeaf, IntConstant(5)) is valid */ -case class EQ[S <: SType](override val left: Value[S], override val right: Value[S]) - extends SimpleRelation[S] { +case class EQ[S <: SType]( + override val left: Value[S], + override val right: Value[S]) extends SimpleRelation[S] { override def companion = EQ + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val l = left.evalTo[S#WrappedType](env) + Value.checkType(left, l) // necessary because cast to S#WrappedType is erased + val r = right.evalTo[S#WrappedType](env) + Value.checkType(right, r) // necessary because cast to S#WrappedType is erased + DataValueComparer.equalDataValues(l, r) + } } object EQ extends RelationCompanion { override def opCode: OpCode = EqCode + override def costKind = DynamicCost override def argInfos: Seq[ArgInfo] = EQInfo.argInfos } @@ -809,9 +1316,17 @@ object EQ extends RelationCompanion { case class NEQ[S <: SType](override val left: Value[S], override val right: Value[S]) extends SimpleRelation[S] { override def companion = NEQ + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val l = left.evalTo[S#WrappedType](env) + Value.checkType(left, l) // necessary because cast to S#WrappedType is erased + val r = right.evalTo[S#WrappedType](env) + Value.checkType(right, r) // necessary because cast to S#WrappedType is erased + !DataValueComparer.equalDataValues(l, r) + } } object NEQ extends RelationCompanion { override def opCode: OpCode = NeqCode + override def costKind = DynamicCost override def argInfos: Seq[ArgInfo] = NEQInfo.argInfos } @@ -823,10 +1338,18 @@ case class BinOr(override val left: BoolValue, override val right: BoolValue) extends Relation[SBoolean.type, SBoolean.type] { override def companion = BinOr override def opType = BinOr.OpType + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val l = left.evalTo[Boolean](env) + addCost(BinOr.costKind) + l || right.evalTo[Boolean](env) // rely on short-cutting semantics of Scala's || + } } -object BinOr extends RelationCompanion { +object BinOr extends RelationCompanion with FixedCostValueCompanion { val OpType = SFunc(Array(SBoolean, SBoolean), SBoolean) override def opCode: OpCode = BinOrCode + /** Cost of: scala `||` operation + * Old cost: ("BinOr", "(Boolean, Boolean) => Boolean", logicCost) */ + override val costKind = FixedCost(20) override def argInfos: Seq[ArgInfo] = BinOrInfo.argInfos } @@ -838,10 +1361,18 @@ case class BinAnd(override val left: BoolValue, override val right: BoolValue) extends Relation[SBoolean.type, SBoolean.type] { override def companion = BinAnd override def opType = BinAnd.OpType + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val l = left.evalTo[Boolean](env) + addCost(BinAnd.costKind) + l && right.evalTo[Boolean](env) // rely on short-cutting semantics of Scala's && + } } -object BinAnd extends RelationCompanion { +object BinAnd extends RelationCompanion with FixedCostValueCompanion { val OpType = SFunc(Array(SBoolean, SBoolean), SBoolean) override def opCode: OpCode = BinAndCode + /** Cost of: scala `&&` operation + * Old cost: ("BinAnd", "(Boolean, Boolean) => Boolean", logicCost) */ + override val costKind = FixedCost(20) override def argInfos: Seq[ArgInfo] = BinAndInfo.argInfos } @@ -849,10 +1380,19 @@ case class BinXor(override val left: BoolValue, override val right: BoolValue) extends Relation[SBoolean.type, SBoolean.type] { override def companion = BinXor override def opType = BinXor.OpType + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val leftV = left.evalTo[Boolean](env) + val rightV = right.evalTo[Boolean](env) + addCost(BinXor.costKind) + leftV ^ rightV + } } -object BinXor extends RelationCompanion { +object BinXor extends RelationCompanion with FixedCostValueCompanion { val OpType = SFunc(Array(SBoolean, SBoolean), SBoolean) override def opCode: OpCode = BinXorCode + /** Cost of: scala `^` operation + * Old cost: ("BinXor", "(Boolean, Boolean) => Boolean", logicCost) */ + override val costKind = FixedCost(20) override def argInfos: Seq[ArgInfo] = BinXorInfo.argInfos } @@ -888,6 +1428,7 @@ trait QuadrupleCompanion extends ValueCompanion { } object TreeLookup extends QuadrupleCompanion { override def opCode: OpCode = OpCodes.AvlTreeGetCode + override def costKind: CostKind = Value.notSupportedError(this, "costKind") override def argInfos: Seq[ArgInfo] = TreeLookupInfo.argInfos } @@ -906,9 +1447,25 @@ case class If[T <: SType](condition: Value[SBoolean.type], trueBranch: Value[T], override lazy val first = condition override lazy val second = trueBranch override lazy val third = falseBranch + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val c = condition.evalTo[Boolean](env) + addCost(If.costKind) + if (c) { + val res = trueBranch.evalTo[T#WrappedType](env) + Value.checkType(trueBranch, res) // necessary because cast to T#WrappedType is erased + res + } else { + val res = falseBranch.evalTo[T#WrappedType](env) + Value.checkType(falseBranch, res) // necessary because cast to T#WrappedType is erased + res + } + } } object If extends QuadrupleCompanion { override def opCode: OpCode = OpCodes.IfCode + /** Cost of: conditional switching to the right branch (excluding the cost both + * condition itself and the branches) */ + override val costKind = FixedCost(10) override def argInfos: Seq[ArgInfo] = IfInfo.argInfos val GenericOpType = SFunc(Array(SBoolean, SType.tT, SType.tT), SType.tT) } @@ -916,10 +1473,17 @@ object If extends QuadrupleCompanion { case class LogicalNot(input: Value[SBoolean.type]) extends NotReadyValueBoolean { override def companion = LogicalNot override def opType = LogicalNot.OpType + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val inputV = input.evalTo[Boolean](env) + addCost(LogicalNot.costKind) + !inputV + } } -object LogicalNot extends ValueCompanion { +object LogicalNot extends FixedCostValueCompanion { val OpType = SFunc(Array(SBoolean), SBoolean) override def opCode: OpCode = OpCodes.LogicalNotCode + /** Cost of: scala `!` operation */ + override val costKind = FixedCost(15) } diff --git a/sigmastate/src/main/scala/sigmastate/types.scala b/sigmastate/src/main/scala/sigmastate/types.scala index 028afde28d..96c80c61db 100644 --- a/sigmastate/src/main/scala/sigmastate/types.scala +++ b/sigmastate/src/main/scala/sigmastate/types.scala @@ -12,8 +12,11 @@ import sigmastate.interpreter._ import sigmastate.utils.Overloading.Overload1 import sigmastate.utils.SparseArrayContainer import scalan.util.Extensions._ +import scorex.crypto.authds.{ADKey, ADValue} +import scorex.crypto.authds.avltree.batch.{Lookup, Insert, Update, Remove} +import scorex.crypto.hash.Blake2b256 import sigmastate.Values._ -import sigmastate.lang.Terms._ +import sigmastate.lang.Terms.{MethodCall, _} import sigmastate.lang.{SigmaBuilder, SigmaTyper} import sigmastate.SCollection._ import sigmastate.interpreter.CryptoConstants.{EcPointType, hashLength} @@ -24,7 +27,7 @@ import sigmastate.eval.RuntimeCosting import scala.language.implicitConversions import scala.reflect.{ClassTag, classTag} -import sigmastate.SMethod.MethodCallIrBuilder +import sigmastate.SMethod.{InvokeDescBuilder, MethodCostFunc, givenCost, javaMethodOf, MethodCallIrBuilder} import sigmastate.utxo.ByIndex import sigmastate.utxo.ExtractCreationInfo import sigmastate.utxo._ @@ -32,8 +35,12 @@ import special.sigma.{Header, Box, SigmaProp, AvlTree, SigmaDslBuilder, PreHeade import sigmastate.lang.SigmaTyper.STypeSubst import sigmastate.eval.Evaluation.stypeToRType import sigmastate.eval._ +import sigmastate.lang.exceptions.MethodNotFound import spire.syntax.all.cfor +import scala.collection.mutable +import scala.util.{Success, Failure} + /** Base type for all AST nodes of sigma lang. */ trait SigmaNode extends Product @@ -341,6 +348,10 @@ trait STypeCompanion { /** CosterFactory associated with this type. */ def coster: Option[CosterFactory] = None + + /** Class which represents values of this type. When method call is executed, the corresponding method + * of this class is invoked via reflection [[java.lang.reflect.Method]].invoke(). */ + def reprClass: Class[_] } /** Defines recognizer method which allows the derived object to be used in patterns @@ -430,7 +441,9 @@ object OperationInfo { * the ErgoScript compiler. */ case class MethodIRInfo( - irBuilder: Option[PartialFunction[(SigmaBuilder, SValue, SMethod, Seq[SValue], STypeSubst), SValue]] + irBuilder: Option[PartialFunction[(SigmaBuilder, SValue, SMethod, Seq[SValue], STypeSubst), SValue]], + javaMethod: Option[Method], + invokeDescsBuilder: Option[InvokeDescBuilder] ) /** Represents method descriptor. @@ -449,12 +462,100 @@ case class SMethod( name: String, stype: SFunc, methodId: Byte, + costKind: CostKind, irInfo: MethodIRInfo, - docInfo: Option[OperationInfo]) { + docInfo: Option[OperationInfo], + costFunc: Option[MethodCostFunc]) { + + /** Operation descriptor of this method. */ + lazy val opDesc = MethodDesc(this) + + /** Do some checks the method descriptor is well configured and consistent. */ + def checkWellDefined(): Boolean = { + docInfo match { + case Some(opInfo) => + opInfo.opDesc match { + case Some(opDesc) if (opDesc == MethodCall || opDesc == PropertyCall) => + if (costFunc.isEmpty) + throw new IllegalStateException( + s"Neither costKind not costFunc is defined for $this") + else true + case _ => true + } + case None => true + } + } + + /** Finds and keeps the [[Method]] instance which corresponds to this method descriptor. + * The lazy value is forced only if irInfo.javaMethod == None + */ + lazy val javaMethod: Method = { + irInfo.javaMethod.getOrElse { + val paramTypes = stype.tDom.drop(1).map(t => t match { + case _: STypeVar => classOf[AnyRef] + case _: SFunc => classOf[_ => _] + case _ => Evaluation.stypeToRType(t).classTag.runtimeClass + }).toArray + val m = objType.reprClass.getMethod(name, paramTypes:_*) + m + } + } + + lazy val extraDescriptors: Seq[RType[_]] = { + irInfo.invokeDescsBuilder match { + case Some(builder) => + builder(stype).map(Evaluation.stypeToRType) + case None => + mutable.WrappedArray.empty[RType[_]] + } + } + + /** Invoke this method on the given object with the arguments. + * This is used for methods with FixedCost costKind. */ + def invokeFixed(obj: Any, args: Array[Any])(implicit E: ErgoTreeEvaluator): AnyRef = { + javaMethod.invoke(obj, args.asInstanceOf[Array[AnyRef]]:_*) + } + + // TODO optimize: avoid lookup when this SMethod is created via `specializeFor` + /** Return generic template of this method. */ + @inline final def genericMethod: SMethod = { + objType.getMethodById(methodId).get + } + + /** @hotspot don't beautify the code */ + lazy val evalMethod: Method = { + val argTypes = stype.tDom + val nArgs = argTypes.length + val paramTypes = new Array[Class[_]](nArgs + 2) + paramTypes(0) = classOf[MethodCall] + cfor(0)(_ < nArgs, _ + 1) { i => + paramTypes(i + 1) = argTypes(i) match { + case _: STypeVar => classOf[AnyRef] + case _: SFunc => classOf[_ => _] + case _: SCollectionType[_] => classOf[Coll[_]] + case _: SOption[_] => classOf[Option[_]] + case t => + Evaluation.stypeToRType(t).classTag.runtimeClass + } + } + paramTypes(paramTypes.length - 1) = classOf[ErgoTreeEvaluator] + + val methodName = name + "_eval" + val m = try { + objType.getClass().getMethod(methodName, paramTypes:_*) + } + catch { case e: MethodNotFound => + throw new RuntimeException(s"Cannot find eval method def $methodName(${Seq(paramTypes:_*)})", e) + } + m + } /** Create a new instance with the given stype. */ def withSType(newSType: SFunc): SMethod = copy(stype = newSType) + /** Create a new instance with the given cost function. */ + def withCost(costFunc: MethodCostFunc): SMethod = copy(costFunc = Some(costFunc)) + /** Create a new instance in which the `stype` field transformed using * the given substitution. */ def withConcreteTypes(subst: Map[STypeVar, SType]): SMethod = @@ -464,7 +565,6 @@ case class SMethod( /** Returns [[OperationId]] for AOT costing. */ def opId: OperationId = { - val opName = objType.getClass.getSimpleName + "." + name OperationId(opName, stype) } @@ -500,8 +600,10 @@ case class SMethod( /** Create a new instance with the given IR builder (aka MethodCall rewriter) parameter. */ def withIRInfo( - irBuilder: PartialFunction[(SigmaBuilder, SValue, SMethod, Seq[SValue], STypeSubst), SValue]): SMethod = { - this.copy(irInfo = MethodIRInfo(Some(irBuilder))) + irBuilder: PartialFunction[(SigmaBuilder, SValue, SMethod, Seq[SValue], STypeSubst), SValue], + javaMethod: Method = null, + invokeHandler: InvokeDescBuilder = null): SMethod = { + this.copy(irInfo = MethodIRInfo(Some(irBuilder), Option(javaMethod), Option(invokeHandler))) } /** Lookup [[ArgInfo]] for the given argName or throw an exception. */ @@ -511,6 +613,64 @@ case class SMethod( object SMethod { + type RCosted[A] = RuntimeCosting#RCosted[A] + + /** Type of functions used to assign cost to method call nodes. + * For a function `f: (mc, obj, args) => cost` it is called before the evaluation of + * the `mc` node with the given `obj` as method receiver and `args` as method + * arguments. + */ + abstract class MethodCostFunc extends Function4[ErgoTreeEvaluator, MethodCall, Any, Array[Any], CostDetails] { + /** + * The function returns an estimated cost of evaluation BEFORE actual evaluation of + * the method. For this reason [[MethodCostFunc]] is not used for higher-order + * operations like `Coll.map`, `Coll.filter` etc. + */ + def apply(E: ErgoTreeEvaluator, mc: MethodCall, obj: Any, args: Array[Any]): CostDetails + } + + /** Returns a cost function which always returs the given cost. */ + def givenCost(costKind: FixedCost): MethodCostFunc = new MethodCostFunc { + override def apply(E: ErgoTreeEvaluator, + mc: MethodCall, + obj: Any, args: Array[Any]): CostDetails = { + if (E.settings.costTracingEnabled) + TracedCost(Array(FixedCostItem(MethodDesc(mc.method), costKind))) + else + GivenCost(costKind.cost) + } + } + + /** Returns a cost function which expects `obj` to be of `Coll[T]` type and + * uses its length to compute SeqCostItem */ + def perItemCost(costKind: PerItemCost): MethodCostFunc = new MethodCostFunc { + override def apply(E: ErgoTreeEvaluator, + mc: MethodCall, + obj: Any, args: Array[Any]): CostDetails = obj match { + case coll: Coll[a] => + if (E.settings.costTracingEnabled) { + val desc = MethodDesc(mc.method) + TracedCost(Array(SeqCostItem(desc, costKind, coll.length))) + } + else + GivenCost(costKind.cost(coll.length)) + case _ => + ErgoTreeEvaluator.error( + s"Invalid object $obj of method call $mc: Coll type is expected") + } + } + + /** Some runtime methods (like Coll.map, Coll.flatMap) require additional RType descriptors. + * The builder can extract those descriptors from the given type of the method signature. + */ + type InvokeDescBuilder = SFunc => Seq[SType] + + def javaMethodOf[T, A1](methodName: String)(implicit cT: ClassTag[T], cA1: ClassTag[A1]) = + cT.runtimeClass.getMethod(methodName, cA1.runtimeClass) + + def javaMethodOf[T, A1, A2](methodName: String)(implicit cT: ClassTag[T], cA1: ClassTag[A1], cA2: ClassTag[A2]) = + cT.runtimeClass.getMethod(methodName, cA1.runtimeClass, cA2.runtimeClass) + /** Default fallback method call recognizer which builds MethodCall ErgoTree nodes. */ val MethodCallIrBuilder: PartialFunction[(SigmaBuilder, SValue, SMethod, Seq[SValue], STypeSubst), SValue] = { @@ -519,8 +679,12 @@ object SMethod { } /** Convenience factory method. */ - def apply(objType: STypeCompanion, name: String, stype: SFunc, methodId: Byte): SMethod = { - SMethod(objType, name, stype, methodId, MethodIRInfo(None), None) + def apply(objType: STypeCompanion, name: String, stype: SFunc, + methodId: Byte, + costKind: CostKind): SMethod = { + SMethod( + objType, name, stype, methodId, costKind, + MethodIRInfo(None, None, None), None, None) } /** Looks up [[SMethod]] instance for the given type and method ids. @@ -637,27 +801,60 @@ object SNumericType extends STypeCompanion { // see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/667 override def typeId: TypeCode = 106: Byte + /** Since this object is not used in SMethod instances. */ + override def reprClass: Class[_] = sys.error(s"Shouldn't be called.") + /** Type variable used in generic signatures of method descriptors. */ val tNum = STypeVar("TNum") - val ToByteMethod: SMethod = SMethod(this, "toByte", SFunc(tNum, SByte), 1) + /** Cost function which is assigned for numeric cast MethodCall nodes in ErgoTree. + * It is called as part of MethodCall.eval method. */ + val costOfNumericCast: MethodCostFunc = new MethodCostFunc { + override def apply(E: ErgoTreeEvaluator, + mc: MethodCall, + obj: Any, + args: Array[Any]): CostDetails = { + val targetTpe = mc.method.stype.tRange + val cast = getNumericCast(mc.obj.tpe, mc.method.name, targetTpe).get + val costKind = if (cast == Downcast) Downcast.costKind else Upcast.costKind + TracedCost(Array(TypeBasedCostItem(MethodDesc(mc.method), costKind, targetTpe))) + } + } + + val ToByteMethod: SMethod = SMethod(this, "toByte", SFunc(tNum, SByte), 1, null) + .withCost(costOfNumericCast) .withInfo(PropertyCall, "Converts this numeric value to \\lst{Byte}, throwing exception if overflow.") - val ToShortMethod: SMethod = SMethod(this, "toShort", SFunc(tNum, SShort), 2) + val ToShortMethod: SMethod = SMethod(this, "toShort", SFunc(tNum, SShort), 2, null) + .withCost(costOfNumericCast) .withInfo(PropertyCall, "Converts this numeric value to \\lst{Short}, throwing exception if overflow.") - val ToIntMethod: SMethod = SMethod(this, "toInt", SFunc(tNum, SInt), 3) + val ToIntMethod: SMethod = SMethod(this, "toInt", SFunc(tNum, SInt), 3, null) + .withCost(costOfNumericCast) .withInfo(PropertyCall, "Converts this numeric value to \\lst{Int}, throwing exception if overflow.") - val ToLongMethod: SMethod = SMethod(this, "toLong", SFunc(tNum, SLong), 4) + val ToLongMethod: SMethod = SMethod(this, "toLong", SFunc(tNum, SLong), 4, null) + .withCost(costOfNumericCast) .withInfo(PropertyCall, "Converts this numeric value to \\lst{Long}, throwing exception if overflow.") - val ToBigIntMethod: SMethod = SMethod(this, "toBigInt", SFunc(tNum, SBigInt), 5) + val ToBigIntMethod: SMethod = SMethod(this, "toBigInt", SFunc(tNum, SBigInt), 5, null) + .withCost(costOfNumericCast) .withInfo(PropertyCall, "Converts this numeric value to \\lst{BigInt}") - val ToBytesMethod: SMethod = SMethod(this, "toBytes", SFunc(tNum, SByteArray), 6) + + /** Cost of: 1) creating Byte collection from a numeric value */ + val ToBytes_CostKind = FixedCost(5) + + val ToBytesMethod: SMethod = SMethod( + this, "toBytes", SFunc(tNum, SByteArray), 6, ToBytes_CostKind) .withIRInfo(MethodCallIrBuilder) .withInfo(PropertyCall, """ Returns a big-endian representation of this numeric value in a collection of bytes. | For example, the \lst{Int} value \lst{0x12131415} would yield the | collection of bytes \lst{[0x12, 0x13, 0x14, 0x15]}. """.stripMargin) - val ToBitsMethod: SMethod = SMethod(this, "toBits", SFunc(tNum, SBooleanArray), 7) + + /** Cost of: 1) creating Boolean collection (one bool for each bit) from a numeric + * value. */ + val ToBits_CostKind = FixedCost(5) + + val ToBitsMethod: SMethod = SMethod( + this, "toBits", SFunc(tNum, SBooleanArray), 7, ToBits_CostKind) .withIRInfo(MethodCallIrBuilder) .withInfo(PropertyCall, """ Returns a big-endian representation of this numeric in a collection of Booleans. @@ -672,12 +869,24 @@ object SNumericType extends STypeCompanion { ToBigIntMethod, // see Downcast ToBytesMethod, ToBitsMethod - ) + ) // TODO v5.0: .ensuring(_.forall { m => m.checkWellDefined() }) /** Collection of names of numeric casting methods (like `toByte`, `toInt`, etc). */ val castMethods: Array[String] = Array(ToByteMethod, ToShortMethod, ToIntMethod, ToLongMethod, ToBigIntMethod) .map(_.name) + + /** Checks the given name is numeric type cast method (like toByte, toInt, etc.).*/ + def isCastMethod(name: String): Boolean = castMethods.contains(name) + + /** Convert the given method to a cast operation from fromTpe to resTpe. */ + def getNumericCast(fromTpe: SType, methodName: String, resTpe: SType): Option[NumericCastCompanion] = (fromTpe, resTpe) match { + case (from: SNumericType, to: SNumericType) if isCastMethod(methodName) => + val op = if (to > from) Upcast else Downcast + Some(op) + case _ => None // the method in not numeric type cast + } + } trait SLogical extends SType { @@ -687,13 +896,13 @@ trait SLogical extends SType { * @see `SGenericType` */ trait SMonoType extends SType with STypeCompanion { - protected def property(name: String, tpeRes: SType, id: Byte): SMethod = - SMethod(this, name, SFunc(this, tpeRes), id) + protected def propertyCall(name: String, tpeRes: SType, id: Byte, costKind: CostKind): SMethod = + SMethod(this, name, SFunc(this, tpeRes), id, costKind) .withIRInfo(MethodCallIrBuilder) .withInfo(PropertyCall, "") protected def property(name: String, tpeRes: SType, id: Byte, valueCompanion: ValueCompanion): SMethod = - SMethod(this, name, SFunc(this, tpeRes), id) + SMethod(this, name, SFunc(this, tpeRes), id, valueCompanion.costKind) .withIRInfo(MethodCallIrBuilder) .withInfo(valueCompanion, "") } @@ -702,6 +911,8 @@ case object SBoolean extends SPrimType with SEmbeddable with SLogical with SProd override type WrappedType = Boolean override val typeCode: TypeCode = 1: Byte override def typeId = typeCode + override val reprClass: Class[_] = classOf[Boolean] + val ToByte = "toByte" protected override def getMethods() = super.getMethods() /* TODO soft-fork: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 @@ -717,6 +928,7 @@ case object SBoolean extends SPrimType with SEmbeddable with SLogical with SProd case object SByte extends SPrimType with SEmbeddable with SNumericType with SMonoType { override type WrappedType = Byte override val typeCode: TypeCode = 2: Byte + override val reprClass: Class[_] = classOf[Byte] override def typeId = typeCode override def dataSize(v: SType#WrappedType): Long = 1 override def isConstantSize = true @@ -737,6 +949,7 @@ case object SByte extends SPrimType with SEmbeddable with SNumericType with SMon case object SShort extends SPrimType with SEmbeddable with SNumericType with SMonoType { override type WrappedType = Short override val typeCode: TypeCode = 3: Byte + override val reprClass: Class[_] = classOf[Short] override def typeId = typeCode override def dataSize(v: SType#WrappedType): Long = 2 override def isConstantSize = true @@ -757,6 +970,7 @@ case object SShort extends SPrimType with SEmbeddable with SNumericType with SMo case object SInt extends SPrimType with SEmbeddable with SNumericType with SMonoType { override type WrappedType = Int override val typeCode: TypeCode = 4: Byte + override val reprClass: Class[_] = classOf[Int] override def typeId = typeCode override def dataSize(v: SType#WrappedType): Long = 4 override def isConstantSize = true @@ -779,6 +993,7 @@ case object SInt extends SPrimType with SEmbeddable with SNumericType with SMono case object SLong extends SPrimType with SEmbeddable with SNumericType with SMonoType { override type WrappedType = Long override val typeCode: TypeCode = 5: Byte + override val reprClass: Class[_] = classOf[Long] override def typeId = typeCode override def dataSize(v: SType#WrappedType): Long = 8 override def isConstantSize = true @@ -803,6 +1018,8 @@ case object SLong extends SPrimType with SEmbeddable with SNumericType with SMon case object SBigInt extends SPrimType with SEmbeddable with SNumericType with SMonoType { override type WrappedType = BigInt override val typeCode: TypeCode = 6: Byte + override val reprClass: Class[_] = classOf[BigInt] + override def typeId = typeCode /** Type of Relation binary op like GE, LE, etc. */ @@ -842,15 +1059,16 @@ case object SBigInt extends SPrimType with SEmbeddable with SNumericType with SM SigmaDsl.BigInt(bi) } - val ModQMethod = SMethod(this, "modQ", SFunc(this, SBigInt), 1) + val ModQMethod = SMethod(this, "modQ", SFunc(this, SBigInt), 1, FixedCost(1)) .withInfo(ModQ, "Returns this \\lst{mod} Q, i.e. remainder of division by Q, where Q is an order of the cryprographic group.") - val PlusModQMethod = SMethod(this, "plusModQ", SFunc(IndexedSeq(this, SBigInt), SBigInt), 2) + val PlusModQMethod = SMethod(this, "plusModQ", SFunc(IndexedSeq(this, SBigInt), SBigInt), 2, FixedCost(1)) .withInfo(ModQArithOp.PlusModQ, "Adds this number with \\lst{other} by module Q.", ArgInfo("other", "Number to add to this.")) - val MinusModQMethod = SMethod(this, "minusModQ", SFunc(IndexedSeq(this, SBigInt), SBigInt), 3) + val MinusModQMethod = SMethod(this, "minusModQ", SFunc(IndexedSeq(this, SBigInt), SBigInt), 3, FixedCost(1)) .withInfo(ModQArithOp.MinusModQ, "Subtracts \\lst{other} number from this by module Q.", ArgInfo("other", "Number to subtract from this.")) - val MultModQMethod = SMethod(this, "multModQ", SFunc(IndexedSeq(this, SBigInt), SBigInt), 4) + val MultModQMethod = SMethod(this, "multModQ", SFunc(IndexedSeq(this, SBigInt), SBigInt), 4, FixedCost(1)) .withIRInfo(MethodCallIrBuilder) .withInfo(MethodCall, "Multiply this number with \\lst{other} by module Q.", ArgInfo("other", "Number to multiply with this.")) + protected override def getMethods() = super.getMethods() ++ Seq( // ModQMethod, // PlusModQMethod, @@ -867,31 +1085,47 @@ case object SString extends SProduct with SMonoType { override def typeId = typeCode override def dataSize(v: SType#WrappedType): Long = v.asInstanceOf[String].length override def isConstantSize = false + override def reprClass: Class[_] = classOf[String] } /** NOTE: this descriptor both type and type companion */ case object SGroupElement extends SProduct with SPrimType with SEmbeddable with SMonoType { override type WrappedType = GroupElement override val typeCode: TypeCode = 7: Byte + override val reprClass: Class[_] = classOf[GroupElement] + override def typeId = typeCode override def coster: Option[CosterFactory] = Some(Coster(_.GroupElementCoster)) - lazy val GetEncodedMethod: SMethod = SMethod(this, "getEncoded", SFunc(IndexedSeq(this), SByteArray), 2) + /** Cost of: 1) serializing EcPointType to bytes 2) packing them in Coll. */ + val GetEncodedCostKind = FixedCost(250) + + lazy val GetEncodedMethod: SMethod = SMethod( + this, "getEncoded", SFunc(Array(this), SByteArray), 2, GetEncodedCostKind) .withIRInfo(MethodCallIrBuilder) .withInfo(PropertyCall, "Get an encoding of the point value.") - lazy val ExponentiateMethod: SMethod = SMethod(this, "exp", SFunc(IndexedSeq(this, SBigInt), this), 3) + + lazy val ExponentiateMethod: SMethod = SMethod( + this, "exp", SFunc(Array(this, SBigInt), this), 3, Exponentiate.costKind) .withIRInfo({ case (builder, obj, _, Seq(arg), _) => builder.mkExponentiate(obj.asGroupElement, arg.asBigInt) }) .withInfo(Exponentiate, "Exponentiate this \\lst{GroupElement} to the given number. Returns this to the power of k", ArgInfo("k", "The power")) - lazy val MultiplyMethod: SMethod = SMethod(this, "multiply", SFunc(IndexedSeq(this, SGroupElement), this), 4) + + lazy val MultiplyMethod: SMethod = SMethod( + this, "multiply", SFunc(Array(this, SGroupElement), this), 4, MultiplyGroup.costKind) .withIRInfo({ case (builder, obj, _, Seq(arg), _) => builder.mkMultiplyGroup(obj.asGroupElement, arg.asGroupElement) }) .withInfo(MultiplyGroup, "Group operation.", ArgInfo("other", "other element of the group")) - lazy val NegateMethod: SMethod = SMethod(this, "negate", SFunc(this, this), 5) + + /** Cost of: 1) calling EcPoint.negate 2) wrapping in GroupElement. */ + val Negate_CostKind = FixedCost(45) + + lazy val NegateMethod: SMethod = SMethod( + this, "negate", SFunc(this, this), 5, Negate_CostKind) .withIRInfo(MethodCallIrBuilder) .withInfo(PropertyCall, "Inverse element of the group.") @@ -913,6 +1147,7 @@ case object SSigmaProp extends SProduct with SPrimType with SEmbeddable with SLo import SType._ override type WrappedType = SigmaProp override val typeCode: TypeCode = 8: Byte + override val reprClass: Class[_] = classOf[SigmaProp] override def typeId = typeCode /** The maximum size of SigmaProp value in serialized byte array representation. */ @@ -923,11 +1158,14 @@ case object SSigmaProp extends SProduct with SPrimType with SEmbeddable with SLo override def isConstantSize = true val PropBytes = "propBytes" val IsProven = "isProven" - lazy val PropBytesMethod = SMethod(this, PropBytes, SFunc(this, SByteArray), 1) - .withInfo(SigmaPropBytes, "Serialized bytes of this sigma proposition taken as ErgoTree.") - lazy val IsProvenMethod = SMethod(this, IsProven, SFunc(this, SBoolean), 2) + lazy val PropBytesMethod = SMethod( + this, PropBytes, SFunc(this, SByteArray), 1, SigmaPropBytes.costKind) + .withInfo(SigmaPropBytes, "Serialized bytes of this sigma proposition taken as ErgoTree.") + + lazy val IsProvenMethod = SMethod(this, IsProven, SFunc(this, SBoolean), 2, null) .withInfo(// available only at frontend of ErgoScript "Verify that sigma proposition is proven.") + protected override def getMethods() = super.getMethods() ++ Seq( PropBytesMethod, IsProvenMethod ) @@ -979,9 +1217,11 @@ object SOption extends STypeCompanion { val OptionTypeCode: TypeCode = ((SPrimType.MaxPrimTypeCode + 1) * OptionTypeConstrId).toByte val OptionCollectionTypeConstrId = 4 val OptionCollectionTypeCode: TypeCode = ((SPrimType.MaxPrimTypeCode + 1) * OptionCollectionTypeConstrId).toByte + override def typeId = OptionTypeCode override val coster: Option[CosterFactory] = Some(Coster(_.OptionCoster)) + override val reprClass: Class[_] = classOf[Option[_]] type SBooleanOption = SOption[SBoolean.type] type SByteOption = SOption[SByte.type] @@ -1013,21 +1253,24 @@ object SOption extends STypeCompanion { import SType.{tT, tR, paramT, paramR} val ThisType = SOption(tT) - val IsDefinedMethod = SMethod(this, IsDefined, SFunc(ThisType, SBoolean), 2) + val IsDefinedMethod = SMethod( + this, IsDefined, SFunc(ThisType, SBoolean), 2, OptionIsDefined.costKind) .withInfo(OptionIsDefined, "Returns \\lst{true} if the option is an instance of \\lst{Some}, \\lst{false} otherwise.") - val GetMethod = SMethod(this, Get, SFunc(ThisType, tT), 3) + val GetMethod = SMethod(this, Get, SFunc(ThisType, tT), 3, OptionGet.costKind) .withInfo(OptionGet, """Returns the option's value. The option must be nonempty. Throws exception if the option is empty.""") - val GetOrElseMethod = SMethod(this, GetOrElse, SFunc(IndexedSeq(ThisType, tT), tT, Seq(tT)), 4) + lazy val GetOrElseMethod = SMethod( + this, GetOrElse, SFunc(Array(ThisType, tT), tT, Array[STypeParam](tT)), 4, OptionGetOrElse.costKind) .withInfo(OptionGetOrElse, """Returns the option's value if the option is nonempty, otherwise |return the result of evaluating \lst{default}. """.stripMargin, ArgInfo("default", "the default value")) - val FoldMethod = SMethod(this, Fold, SFunc(Array(ThisType, tR, SFunc(tT, tR)), tR, Seq(tT, tR)), 5) + val FoldMethod = SMethod( + this, Fold, SFunc(Array(ThisType, tR, SFunc(tT, tR)), tR, Array[STypeParam](tT, tR)), 5, FixedCost(1)) .withInfo(MethodCall, """Returns the result of applying \lst{f} to this option's | value if the option is nonempty. Otherwise, evaluates @@ -1037,8 +1280,8 @@ object SOption extends STypeCompanion { ArgInfo("ifEmpty", "the expression to evaluate if empty"), ArgInfo("f", "the function to apply if nonempty")) - val MapMethod = SMethod(this, "map", - SFunc(Array(ThisType, SFunc(tT, tR)), SOption(tR), Array(paramT, paramR)), 7) + val MapMethod = SMethod(this, "map", + SFunc(Array(ThisType, SFunc(tT, tR)), SOption(tR), Array(paramT, paramR)), 7, FixedCost(20)) .withIRInfo(MethodCallIrBuilder) .withInfo(MethodCall, """Returns a \lst{Some} containing the result of applying \lst{f} to this option's @@ -1046,8 +1289,8 @@ object SOption extends STypeCompanion { | Otherwise return \lst{None}. """.stripMargin, ArgInfo("f", "the function to apply")) - val FilterMethod = SMethod(this, "filter", - SFunc(Array(ThisType, SFunc(tT, SBoolean)), ThisType, Array(paramT)), 8) + val FilterMethod = SMethod(this, "filter", + SFunc(Array(ThisType, SFunc(tT, SBoolean)), ThisType, Array(paramT)), 8, FixedCost(20)) .withIRInfo(MethodCallIrBuilder) .withInfo(MethodCall, """Returns this option if it is nonempty and applying the predicate \lst{p} to @@ -1097,10 +1340,11 @@ object SCollectionType { } object SCollection extends STypeCompanion with MethodByNameUnapply { + override val reprClass: Class[_] = classOf[Coll[_]] override def typeId = SCollectionType.CollectionTypeCode override def coster: Option[CosterFactory] = Some(Coster(_.CollCoster)) - import SType.{tK, tV, paramIV, paramOV} + import SType.{tK, tV, paramIV, paramIVSeq, paramOV} def tIV = SType.tIV def tOV = SType.tOV @@ -1109,10 +1353,11 @@ object SCollection extends STypeCompanion with MethodByNameUnapply { val tOVColl = SCollection(tOV) val tPredicate = SFunc(tIV, SBoolean) - val SizeMethod = SMethod(this, "size", SFunc(ThisType, SInt), 1) + val SizeMethod = SMethod(this, "size", SFunc(ThisType, SInt), 1, SizeOf.costKind) .withInfo(SizeOf, "The size of the collection in elements.") - val GetOrElseMethod = SMethod(this, "getOrElse", SFunc(IndexedSeq(ThisType, SInt, tIV), tIV, Seq(paramIV)), 2) + val GetOrElseMethod = SMethod( + this, "getOrElse", SFunc(Array(ThisType, SInt, tIV), tIV, paramIVSeq), 2, ByIndex.costKind) .withIRInfo({ case (builder, obj, _, Seq(index, defaultValue), _) => val index1 = index.asValue[SInt.type] val defaultValue1 = defaultValue.asValue[SType] @@ -1122,7 +1367,8 @@ object SCollection extends STypeCompanion with MethodByNameUnapply { ArgInfo("index", "index of the element of this collection"), ArgInfo("default", "value to return when \\lst{index} is out of range")) - val MapMethod = SMethod(this, "map", SFunc(IndexedSeq(ThisType, SFunc(tIV, tOV)), tOVColl, Seq(paramIV, paramOV)), 3) + val MapMethod = SMethod(this, "map", + SFunc(Array(ThisType, SFunc(tIV, tOV)), tOVColl, Array(paramIV, paramOV)), 3, MapCollection.costKind) .withInfo(MapCollection, """ Builds a new collection by applying a function to all elements of this collection. | Returns a new collection of type \lst{Coll[B]} resulting from applying the given function @@ -1130,19 +1376,31 @@ object SCollection extends STypeCompanion with MethodByNameUnapply { """.stripMargin, ArgInfo("f", "the function to apply to each element")) - val ExistsMethod = SMethod(this, "exists", SFunc(IndexedSeq(ThisType, tPredicate), SBoolean, Seq(paramIV)), 4) + def map_eval[A,B](mc: MethodCall, xs: Coll[A], f: A => B)(implicit E: ErgoTreeEvaluator): Coll[B] = { + val tpeB = mc.tpe.asInstanceOf[SCollection[SType]].elemType + val tB = Evaluation.stypeToRType(tpeB).asInstanceOf[RType[B]] + E.addSeqCost(MapCollection.costKind, xs.length, mc.method.opDesc)(null) + xs.map(f)(tB) + } + + val ExistsMethod = SMethod(this, "exists", + SFunc(Array(ThisType, tPredicate), SBoolean, paramIVSeq), 4, Exists.costKind) .withInfo(Exists, """Tests whether a predicate holds for at least one element of this collection. |Returns \lst{true} if the given predicate \lst{p} is satisfied by at least one element of this collection, otherwise \lst{false} """.stripMargin, ArgInfo("p", "the predicate used to test elements")) - val FoldMethod = SMethod(this, "fold", SFunc(IndexedSeq(ThisType, tOV, SFunc(IndexedSeq(tOV, tIV), tOV)), tOV, Seq(paramIV, paramOV)), 5) + val FoldMethod = SMethod( + this, "fold", + SFunc(Array(ThisType, tOV, SFunc(Array(tOV, tIV), tOV)), tOV, Array(paramIV, paramOV)), + 5, Fold.costKind) .withInfo(Fold, "Applies a binary operator to a start value and all elements of this collection, going left to right.", ArgInfo("zero", "a starting value"), ArgInfo("op", "the binary operator")) - val ForallMethod = SMethod(this, "forall", SFunc(IndexedSeq(ThisType, tPredicate), SBoolean, Seq(paramIV)), 6) + val ForallMethod = SMethod(this, "forall", + SFunc(Array(ThisType, tPredicate), SBoolean, paramIVSeq), 6, ForAll.costKind) .withInfo(ForAll, """Tests whether a predicate holds for all elements of this collection. |Returns \lst{true} if this collection is empty or the given predicate \lst{p} @@ -1150,7 +1408,8 @@ object SCollection extends STypeCompanion with MethodByNameUnapply { """.stripMargin, ArgInfo("p", "the predicate used to test elements")) - val SliceMethod = SMethod(this, "slice", SFunc(IndexedSeq(ThisType, SInt, SInt), ThisType, Seq(paramIV)), 7) + val SliceMethod = SMethod(this, "slice", + SFunc(Array(ThisType, SInt, SInt), ThisType, paramIVSeq), 7, Slice.costKind) .withInfo(Slice, """Selects an interval of elements. The returned collection is made up | of all elements \lst{x} which satisfy the invariant: @@ -1161,7 +1420,8 @@ object SCollection extends STypeCompanion with MethodByNameUnapply { ArgInfo("from", "the lowest index to include from this collection"), ArgInfo("until", "the lowest index to EXCLUDE from this collection")) - val FilterMethod = SMethod(this, "filter", SFunc(IndexedSeq(ThisType, tPredicate), ThisType, Seq(paramIV)), 8) + val FilterMethod = SMethod(this, "filter", + SFunc(Array(ThisType, tPredicate), ThisType, paramIVSeq), 8, Filter.costKind) .withIRInfo({ case (builder, obj, _, Seq(l), _) => builder.mkFilter(obj.asValue[SCollection[SType]], l.asFunc) }) @@ -1172,14 +1432,17 @@ object SCollection extends STypeCompanion with MethodByNameUnapply { """.stripMargin, ArgInfo("p", "the predicate used to test elements.")) - val AppendMethod = SMethod(this, "append", SFunc(IndexedSeq(ThisType, ThisType), ThisType, Seq(paramIV)), 9) - .withIRInfo({ - case (builder, obj, _, Seq(xs), _) => - builder.mkAppend(obj.asCollection[SType], xs.asCollection[SType]) - }) + val AppendMethod = SMethod(this, "append", + SFunc(Array(ThisType, ThisType), ThisType, paramIVSeq), 9, Append.costKind) + .withIRInfo({ + case (builder, obj, _, Seq(xs), _) => + builder.mkAppend(obj.asCollection[SType], xs.asCollection[SType]) + }) .withInfo(Append, "Puts the elements of other collection after the elements of this collection (concatenation of 2 collections)", ArgInfo("other", "the collection to append at the end of this")) - val ApplyMethod = SMethod(this, "apply", SFunc(IndexedSeq(ThisType, SInt), tIV, Seq(tIV)), 10) + + val ApplyMethod = SMethod(this, "apply", + SFunc(Array(ThisType, SInt), tIV, Array[STypeParam](tIV)), 10, ByIndex.costKind) .withInfo(ByIndex, """The element at given index. | Indices start at \lst{0}; \lst{xs.apply(0)} is the first element of collection \lst{xs}. @@ -1188,16 +1451,39 @@ object SCollection extends STypeCompanion with MethodByNameUnapply { | Throws an exception if \lst{i < 0} or \lst{length <= i} """.stripMargin, ArgInfo("i", "the index")) - val IndicesMethod = SMethod(this, "indices", SFunc(ThisType, SCollection(SInt)), 14) + /** Cost of creating a collection of indices */ + val IndicesMethod_CostKind = PerItemCost(baseCost = 20, perChunkCost = 2, chunkSize = 16) + + val IndicesMethod = SMethod( + this, "indices", SFunc(ThisType, SCollection(SInt)), 14, IndicesMethod_CostKind) .withIRInfo(MethodCallIrBuilder) .withInfo(PropertyCall, """Produces the range of all indices of this collection as a new collection | containing [0 .. length-1] values. """.stripMargin) + def indices_eval[A, B](mc: MethodCall, xs: Coll[A]) + (implicit E: ErgoTreeEvaluator): Coll[Int] = { + val m = mc.method + E.addSeqCost(m.costKind.asInstanceOf[PerItemCost], xs.length, m.opDesc) { () => + xs.indices + } + } + /** BaseCost: + * 1) base cost of Coll.flatMap + * PerChunkCost: + * 1) cost of Coll.flatMap (per item) + * 2) new collection is allocated for each item + * 3) each collection is then appended to the resulting collection */ + val FlatMapMethod_CostKind = PerItemCost(baseCost = 30, perChunkCost = 5, chunkSize = 8) + val FlatMapMethod = SMethod(this, "flatMap", - SFunc(IndexedSeq(ThisType, SFunc(tIV, tOVColl)), tOVColl, Seq(paramIV, paramOV)), 15) - .withIRInfo(MethodCallIrBuilder) + SFunc(Array(ThisType, SFunc(tIV, tOVColl)), tOVColl, Array(paramIV, paramOV)), + 15, FlatMapMethod_CostKind) + .withIRInfo( + MethodCallIrBuilder, + javaMethodOf[Coll[_], Function1[_,_], RType[_]]("flatMap"), + { mtype => Array(mtype.tRange.asCollection[SType].elemType) }) .withInfo(MethodCall, """ Builds a new collection by applying a function to all elements of this collection | and using the elements of the resulting collections. @@ -1207,27 +1493,181 @@ object SCollection extends STypeCompanion with MethodByNameUnapply { | \lst{f} to each element of this collection and concatenating the results. """.stripMargin, ArgInfo("f", "the function to apply to each element.")) + /** We assume all flatMap body patterns have similar executon cost. */ + final val CheckFlatmapBody_Info = OperationCostInfo( + PerItemCost(20, 20, 1), NamedDesc("CheckFlatmapBody")) + + + /** This patterns recognize all expressions, which are allowed as lambda body + * of flatMap. Other bodies are rejected with throwing exception. + */ + val flatMap_BodyPatterns = Array[PartialFunction[SValue, Int]]( + { case MethodCall(ValUse(id, tpe), m, args, _) if args.isEmpty => id }, + { case ExtractScriptBytes(ValUse(id, _)) => id }, + { case ExtractId(ValUse(id, _)) => id }, + { case SigmaPropBytes(ValUse(id, _)) => id }, + { case ExtractBytes(ValUse(id, _)) => id }, + { case ExtractBytesWithNoRef(ValUse(id, _)) => id } + ) + + /** Check the given expression is valid body of flatMap argument lambda. + * @param varId id of lambda variable (see [[FuncValue]].args) + * @param expr expression with is expected to use varId in ValUse node. + * @return true if the body is allowed + */ + def isValidPropertyAccess(varId: Int, expr: SValue) + (implicit E: ErgoTreeEvaluator): Boolean = { + var found = false + // NOTE: the cost depends on the position of the pattern since + // we are checking until the first matching pattern found. + E.addSeqCost(CheckFlatmapBody_Info) { () => + // the loop is bounded because flatMap_BodyPatterns is fixed + var i = 0 + val nPatterns = flatMap_BodyPatterns.length + while (i < nPatterns && !found) { + val p = flatMap_BodyPatterns(i) + found = p.lift(expr) match { + case Some(id) => id == varId // `id` in the pattern is equal to lambda `varId` + case None => false + } + i += 1 + } + i // how many patterns checked + } + found + } + + final val MatchSingleArgMethodCall_Info = OperationCostInfo( + FixedCost(30), NamedDesc("MatchSingleArgMethodCall")) + + object IsSingleArgMethodCall { + def unapply(mc:MethodCall) + (implicit E: ErgoTreeEvaluator): Nullable[(Int, SValue)] = { + var res: Nullable[(Int, SValue)] = Nullable.None + E.addFixedCost(MatchSingleArgMethodCall_Info) { + res = mc match { + case MethodCall(_, m, Seq(FuncValue(args, body)), _) if args.length == 1 => + val id = args(0)._1 + Nullable((id, body)) + case _ => + Nullable.None + } + } + res + } + } + + def checkValidFlatmap(mc: MethodCall)(implicit E: ErgoTreeEvaluator) = { + mc match { + case IsSingleArgMethodCall(varId, lambdaBody) + if isValidPropertyAccess(varId, lambdaBody) => + // ok, do nothing + case _ => + ErgoTreeEvaluator.error( + s"Unsupported lambda in flatMap: allowed usage `xs.flatMap(x => x.property)`: $mc") + } + } + + def flatMap_eval[A, B](mc: MethodCall, xs: Coll[A], f: A => Coll[B]) + (implicit E: ErgoTreeEvaluator): Coll[B] = { + checkValidFlatmap(mc) + val m = mc.method + var res: Coll[B] = null + E.addSeqCost(m.costKind.asInstanceOf[PerItemCost], m.opDesc) { () => + val tpeB = mc.tpe.asInstanceOf[SCollection[SType]].elemType + val tB = Evaluation.stypeToRType(tpeB).asInstanceOf[RType[B]] + res = xs.flatMap(f)(tB) + res.length + } + res + } val PatchMethod = SMethod(this, "patch", - SFunc(IndexedSeq(ThisType, SInt, ThisType, SInt), ThisType, Seq(paramIV)), 19) + SFunc(Array(ThisType, SInt, ThisType, SInt), ThisType, paramIVSeq), + 19, PerItemCost(30, 2, 10)) .withIRInfo(MethodCallIrBuilder) .withInfo(MethodCall, "") + def patch_eval[A](mc: MethodCall, xs: Coll[A], from: Int, patch: Coll[A], replaced: Int) + (implicit E: ErgoTreeEvaluator): Coll[A] = { + val m = mc.method + val nItems = xs.length + patch.length + E.addSeqCost(m.costKind.asInstanceOf[PerItemCost], nItems, m.opDesc) { () => + xs.patch(from, patch, replaced) + } + } + val UpdatedMethod = SMethod(this, "updated", - SFunc(IndexedSeq(ThisType, SInt, tIV), ThisType, Seq(paramIV)), 20) - .withIRInfo(MethodCallIrBuilder).withInfo(MethodCall, "") + SFunc(Array(ThisType, SInt, tIV), ThisType, paramIVSeq), + 20, PerItemCost(20, 1, 10)) + .withIRInfo(MethodCallIrBuilder, javaMethodOf[Coll[_], Int, Any]("updated")) + .withInfo(MethodCall, "") + + def updated_eval[A](mc: MethodCall, coll: Coll[A], index: Int, elem: A) + (implicit E: ErgoTreeEvaluator): Coll[A] = { + val m = mc.method + val costKind = m.costKind.asInstanceOf[PerItemCost] + E.addSeqCost(costKind, coll.length, m.opDesc) { () => + coll.updated(index, elem) + } + } val UpdateManyMethod = SMethod(this, "updateMany", - SFunc(IndexedSeq(ThisType, SCollection(SInt), ThisType), ThisType, Seq(paramIV)), 21) + SFunc(Array(ThisType, SCollection(SInt), ThisType), ThisType, paramIVSeq), + 21, PerItemCost(20, 2, 10)) .withIRInfo(MethodCallIrBuilder).withInfo(MethodCall, "") + def updateMany_eval[A](mc: MethodCall, coll: Coll[A], indexes: Coll[Int], values: Coll[A]) + (implicit E: ErgoTreeEvaluator): Coll[A] = { + val costKind = mc.method.costKind.asInstanceOf[PerItemCost] + E.addSeqCost(costKind, coll.length, mc.method.opDesc) { () => + coll.updateMany(indexes, values) + } + } + val IndexOfMethod = SMethod(this, "indexOf", - SFunc(IndexedSeq(ThisType, tIV, SInt), SInt, Seq(paramIV)), 26) - .withIRInfo(MethodCallIrBuilder).withInfo(MethodCall, "") + SFunc(Array(ThisType, tIV, SInt), SInt, paramIVSeq), 26, PerItemCost(20, 10, 2)) + .withIRInfo(MethodCallIrBuilder, javaMethodOf[Coll[_], Any, Int]("indexOf")) + .withInfo(MethodCall, "") + + // TODO v5.0: optimize using specialization for numeric and predefined types + /** This method is called via Reflection as part of evaluating the given MethodCall. */ + def indexOf_eval[A](mc: MethodCall, xs: Coll[A], elem: A, from: Int) + (implicit E: ErgoTreeEvaluator): Int = { + val costKind = mc.method.costKind.asInstanceOf[PerItemCost] + var res: Int = -1 + E.addSeqCost(costKind, mc.method.opDesc) { () => + // TODO v5.0: this loop is bounded when MaxCollSize limit is enforced + val len = xs.length + val start = math.max(from, 0) + var i = start + var different = true + while (i < len && different) { + different = !DataValueComparer.equalDataValues(xs(i), elem) + i += 1 + } + if (!different) + res = i - 1 + i - start // return number of performed iterations + } + res + } + + val Zip_CostKind = PerItemCost(baseCost = 10, perChunkCost = 1, chunkSize = 10) val ZipMethod = SMethod(this, "zip", - SFunc(IndexedSeq(ThisType, tOVColl), SCollection(STuple(tIV, tOV)), Seq(tIV, tOV)), 29) - .withIRInfo(MethodCallIrBuilder).withInfo(MethodCall, "") + SFunc(Array(ThisType, tOVColl), SCollection(STuple(tIV, tOV)), Array[STypeParam](tIV, tOV)), + 29, Zip_CostKind) + .withIRInfo(MethodCallIrBuilder) + .withInfo(MethodCall, "") + + def zip_eval[A, B](mc: MethodCall, xs: Coll[A], ys: Coll[B]) + (implicit E: ErgoTreeEvaluator): Coll[(A,B)] = { + val m = mc.method + E.addSeqCost(m.costKind.asInstanceOf[PerItemCost], xs.length, m.opDesc) { () => + xs.zip(ys) + } + } lazy val methods: Seq[SMethod] = Seq( SizeMethod, @@ -1331,7 +1771,9 @@ case class STuple(items: IndexedSeq[SType]) extends SCollection[SAny.type] { protected override def getMethods() = { val tupleMethods = Array.tabulate(items.size) { i => - SMethod(STuple, componentNameByIndex(i), SFunc(this, items(i)), (i + 1).toByte) + SMethod( + STuple, componentNameByIndex(i), SFunc(this, items(i)), + (i + 1).toByte, SelectField.costKind) } colMethods ++ tupleMethods } @@ -1358,6 +1800,8 @@ object STuple extends STypeCompanion { def typeId = TupleTypeCode + override val reprClass: Class[_] = classOf[Product2[_,_]] + lazy val colMethods = { val subst = Map(SType.tIV -> SAny) // TODO: implement other @@ -1461,6 +1905,7 @@ case object SBox extends SProduct with SPredefType with SMonoType { import ErgoBox._ override type WrappedType = Box override val typeCode: TypeCode = 99: Byte + override val reprClass: Class[_] = classOf[Box] override def typeId = typeCode override def dataSize(v: SType#WrappedType): Long = { val box = v.asInstanceOf[this.WrappedType] @@ -1476,10 +1921,12 @@ case object SBox extends SProduct with SPredefType with SMonoType { allRegisters.map { i => i match { case r: MandatoryRegisterId => - SMethod(this, s"R${i.asIndex}", GetRegFuncType, (idOfs + i.asIndex + 1).toByte) + SMethod(this, s"R${i.asIndex}", + GetRegFuncType, (idOfs + i.asIndex + 1).toByte, ExtractRegisterAs.costKind) .withInfo(ExtractRegisterAs, r.purpose) case _ => - SMethod(this, s"R${i.asIndex}", GetRegFuncType, (idOfs + i.asIndex + 1).toByte) + SMethod(this, s"R${i.asIndex}", + GetRegFuncType, (idOfs + i.asIndex + 1).toByte, ExtractRegisterAs.costKind) .withInfo(ExtractRegisterAs, "Non-mandatory register") } } @@ -1494,34 +1941,40 @@ case object SBox extends SProduct with SPredefType with SMonoType { val GetReg = "getReg" // should be lazy, otherwise lead to initialization error - lazy val ValueMethod = SMethod(this, Value, SFunc(SBox, SLong), 1) + lazy val ValueMethod = SMethod( + this, Value, SFunc(SBox, SLong), 1, ExtractAmount.costKind) .withInfo(ExtractAmount, "Mandatory: Monetary value, in Ergo tokens (NanoErg unit of measure)") - lazy val PropositionBytesMethod = SMethod(this, PropositionBytes, SFunc(SBox, SByteArray), 2) + lazy val PropositionBytesMethod = SMethod( + this, PropositionBytes, SFunc(SBox, SByteArray), 2, ExtractScriptBytes.costKind) .withInfo(ExtractScriptBytes, "Serialized bytes of guarding script, which should be evaluated to true in order to\n" + " open this box. (aka spend it in a transaction)") - lazy val BytesMethod = SMethod(this, Bytes, SFunc(SBox, SByteArray), 3) + lazy val BytesMethod = SMethod( + this, Bytes, SFunc(SBox, SByteArray), 3, ExtractBytes.costKind) .withInfo(ExtractBytes, "Serialized bytes of this box's content, including proposition bytes.") - lazy val BytesWithoutRefMethod = SMethod(this, BytesWithoutRef, SFunc(SBox, SByteArray), 4) + lazy val BytesWithoutRefMethod = SMethod( + this, BytesWithoutRef, SFunc(SBox, SByteArray), 4, ExtractBytesWithNoRef.costKind) .withInfo(ExtractBytesWithNoRef, "Serialized bytes of this box's content, excluding transactionId and index of output.") - lazy val IdMethod = SMethod(this, Id, SFunc(SBox, SByteArray), 5) + lazy val IdMethod = SMethod(this, Id, SFunc(SBox, SByteArray), 5, ExtractId.costKind) .withInfo(ExtractId, "Blake2b256 hash of this box's content, basically equals to \\lst{blake2b256(bytes)}") - lazy val creationInfoMethod = SMethod(this, CreationInfo, ExtractCreationInfo.OpType, 6) + lazy val creationInfoMethod = SMethod( + this, CreationInfo, ExtractCreationInfo.OpType, 6, ExtractCreationInfo.costKind) .withInfo(ExtractCreationInfo, """ If \lst{tx} is a transaction which generated this box, then \lst{creationInfo._1} | is a height of the tx's block. The \lst{creationInfo._2} is a serialized transaction | identifier followed by box index in the transaction outputs. """.stripMargin ) // see ExtractCreationInfo - lazy val getRegMethod = SMethod(this, "getReg", SFunc(Array(SBox, SInt), SOption(tT), Array(paramT)), 7) + lazy val getRegMethod = SMethod(this, "getReg", + SFunc(Array(SBox, SInt), SOption(tT), Array(paramT)), 7, ExtractRegisterAs.costKind) .withInfo(ExtractRegisterAs, """ Extracts register by id and type. | Type param \lst{T} expected type of the register. @@ -1529,7 +1982,8 @@ case object SBox extends SProduct with SPredefType with SMonoType { """.stripMargin, ArgInfo("regId", "zero-based identifier of the register.")) - lazy val tokensMethod = SMethod(this, "tokens", SFunc(SBox, ErgoBox.STokensRegType), 8) + lazy val tokensMethod = SMethod( + this, "tokens", SFunc(SBox, ErgoBox.STokensRegType), 8, FixedCost(15)) .withIRInfo(MethodCallIrBuilder) .withInfo(PropertyCall, "Secondary tokens") @@ -1552,6 +2006,7 @@ case object SBox extends SProduct with SPredefType with SMonoType { case object SAvlTree extends SProduct with SPredefType with SMonoType { override type WrappedType = AvlTree override val typeCode: TypeCode = 100: Byte + override val reprClass: Class[_] = classOf[AvlTree] override def typeId = typeCode override def dataSize(v: SType#WrappedType): Long = AvlTreeData.TreeDataSize override def isConstantSize = true @@ -1560,14 +2015,20 @@ case object SAvlTree extends SProduct with SPredefType with SMonoType { lazy val TCollOptionCollByte = SCollection(SByteArrayOption) lazy val CollKeyValue = SCollection(STuple(SByteArray, SByteArray)) - lazy val digestMethod = SMethod(this, "digest", SFunc(this, SByteArray), 1) + lazy val digestMethod = SMethod(this, "digest", SFunc(this, SByteArray), 1, FixedCost(15)) .withIRInfo(MethodCallIrBuilder) .withInfo(PropertyCall, """Returns digest of the state represented by this tree. | Authenticated tree \lst{digest} = \lst{root hash bytes} ++ \lst{tree height} """.stripMargin) - lazy val enabledOperationsMethod = SMethod(this, "enabledOperations", SFunc(this, SByte), 2) + lazy val digest_Info = { + val m = digestMethod + OperationCostInfo(m.costKind.asInstanceOf[FixedCost], m.opDesc) + } + + lazy val enabledOperationsMethod = SMethod( + this, "enabledOperations", SFunc(this, SByte), 2, FixedCost(15)) .withIRInfo(MethodCallIrBuilder) .withInfo(PropertyCall, """ Flags of enabled operations packed in single byte. @@ -1575,47 +2036,72 @@ case object SAvlTree extends SProduct with SPredefType with SMonoType { | \lst{isUpdateAllowed == (enabledOperations & 0x02) != 0}\newline | \lst{isRemoveAllowed == (enabledOperations & 0x04) != 0} """.stripMargin) - lazy val keyLengthMethod = SMethod(this, "keyLength", SFunc(this, SInt), 3) + + lazy val keyLengthMethod = SMethod( + this, "keyLength", SFunc(this, SInt), 3, FixedCost(15)) .withIRInfo(MethodCallIrBuilder) .withInfo(PropertyCall, - """ - | - """.stripMargin) - lazy val valueLengthOptMethod = SMethod(this, "valueLengthOpt", SFunc(this, SIntOption), 4) + """ + | + """.stripMargin) + + lazy val valueLengthOptMethod = SMethod( + this, "valueLengthOpt", SFunc(this, SIntOption), 4, FixedCost(15)) .withIRInfo(MethodCallIrBuilder) .withInfo(PropertyCall, """ | """.stripMargin) - lazy val isInsertAllowedMethod = SMethod(this, "isInsertAllowed", SFunc(this, SBoolean), 5) + + lazy val isInsertAllowedMethod = SMethod( + this, "isInsertAllowed", SFunc(this, SBoolean), 5, FixedCost(15)) .withIRInfo(MethodCallIrBuilder) .withInfo(PropertyCall, """ | """.stripMargin) - lazy val isUpdateAllowedMethod = SMethod(this, "isUpdateAllowed", SFunc(this, SBoolean), 6) + + lazy val isInsertAllowed_Info = { + val m = isInsertAllowedMethod + OperationCostInfo(m.costKind.asInstanceOf[FixedCost], m.opDesc) + } + + lazy val isUpdateAllowedMethod = SMethod( + this, "isUpdateAllowed", SFunc(this, SBoolean), 6, FixedCost(15)) .withIRInfo(MethodCallIrBuilder) .withInfo(PropertyCall, """ | """.stripMargin) - lazy val isRemoveAllowedMethod = SMethod(this, "isRemoveAllowed", SFunc(this, SBoolean), 7) + + lazy val isUpdateAllowed_Info = { + val m = isUpdateAllowedMethod + OperationCostInfo(m.costKind.asInstanceOf[FixedCost], m.opDesc) + } + + lazy val isRemoveAllowedMethod = SMethod( + this, "isRemoveAllowed", SFunc(this, SBoolean), 7, FixedCost(15)) .withIRInfo(MethodCallIrBuilder) .withInfo(PropertyCall, """ | """.stripMargin) + lazy val isRemoveAllowed_Info = { + val m = isRemoveAllowedMethod + OperationCostInfo(m.costKind.asInstanceOf[FixedCost], m.opDesc) + } + lazy val updateOperationsMethod = SMethod(this, "updateOperations", - SFunc(IndexedSeq(SAvlTree, SByte), SAvlTree), 8) + SFunc(Array(SAvlTree, SByte), SAvlTree), 8, FixedCost(45)) .withIRInfo(MethodCallIrBuilder) .withInfo(MethodCall, """ | """.stripMargin) - lazy val containsMethod = SMethod(this, "contains", - SFunc(IndexedSeq(SAvlTree, SByteArray, SByteArray), SBoolean), 9) + lazy val containsMethod = SMethod(this, "contains", + SFunc(Array(SAvlTree, SByteArray, SByteArray), SBoolean), 9, DynamicCost) .withIRInfo(MethodCallIrBuilder) .withInfo(MethodCall, """ @@ -1631,8 +2117,53 @@ case object SAvlTree extends SProduct with SPredefType with SMonoType { | """.stripMargin) - lazy val getMethod = SMethod(this, "get", - SFunc(IndexedSeq(SAvlTree, SByteArray, SByteArray), SByteArrayOption), 10) + /** The proof may contain keys, labels and values, we don't know for sure how many, + * but we assume the cost is O(proof.length). + * So the following is an approximation of the proof parsing cost. + */ + final val CreateAvlVerifier_Info = OperationCostInfo( + PerItemCost(110, 20, 64), NamedDesc("CreateAvlVerifier")) + + final val LookupAvlTree_Info = OperationCostInfo( + PerItemCost(40, 10, 1), NamedDesc("LookupAvlTree")) + + final val InsertIntoAvlTree_Info = OperationCostInfo( + PerItemCost(40, 10, 1), NamedDesc("InsertIntoAvlTree")) + + final val UpdateAvlTree_Info = OperationCostInfo( + PerItemCost(120, 20, 1), NamedDesc("UpdateAvlTree")) + + final val RemoveAvlTree_Info = OperationCostInfo( + PerItemCost(100, 15, 1), NamedDesc("RemoveAvlTree")) + + def createVerifier(tree: AvlTree, proof: Coll[Byte])(implicit E: ErgoTreeEvaluator) = { + // the cost of tree reconstruction from proof is O(proof.length) + E.addSeqCost(CreateAvlVerifier_Info, proof.length) { () => + tree.createVerifier(proof) + } + } + + def contains_eval(mc: MethodCall, tree: AvlTree, key: Coll[Byte], proof: Coll[Byte]) + (implicit E: ErgoTreeEvaluator): Boolean = { + val bv = createVerifier(tree, proof) + val nItems = bv.treeHeight + + var res = false + // the cost of tree lookup is O(bv.treeHeight) + E.addSeqCost(LookupAvlTree_Info, nItems) { () => + res = bv.performOneOperation(Lookup(ADKey @@ key.toArray)) match { + case Success(r) => r match { + case Some(_) => true + case _ => false + } + case Failure(_) => false + } + } + res + } + + lazy val getMethod = SMethod(this, "get", + SFunc(Array(SAvlTree, SByteArray, SByteArray), SByteArrayOption), 10, DynamicCost) .withIRInfo(MethodCallIrBuilder) .withInfo(MethodCall, """ @@ -1648,8 +2179,25 @@ case object SAvlTree extends SProduct with SPredefType with SMonoType { | """.stripMargin) - lazy val getManyMethod = SMethod(this, "getMany", - SFunc(IndexedSeq(SAvlTree, SByteArray2, SByteArray), TCollOptionCollByte), 11) + def get_eval(mc: MethodCall, tree: AvlTree, key: Coll[Byte], proof: Coll[Byte]) + (implicit E: ErgoTreeEvaluator): Option[Coll[Byte]] = { + val bv = createVerifier(tree, proof) + val nItems = bv.treeHeight + + // the cost of tree lookup is O(bv.treeHeight) + E.addSeqCost(LookupAvlTree_Info, nItems) { () => + bv.performOneOperation(Lookup(ADKey @@ key.toArray)) match { + case Success(r) => r match { + case Some(v) => Some(Colls.fromArray(v)) + case _ => None + } + case Failure(_) => Interpreter.error(s"Tree proof is incorrect $tree") + } + } + } + + lazy val getManyMethod = SMethod(this, "getMany", + SFunc(Array(SAvlTree, SByteArray2, SByteArray), TCollOptionCollByte), 11, DynamicCost) .withIRInfo(MethodCallIrBuilder) .withInfo(MethodCall, """ @@ -1663,8 +2211,26 @@ case object SAvlTree extends SProduct with SPredefType with SMonoType { | """.stripMargin) - lazy val insertMethod = SMethod(this, "insert", - SFunc(IndexedSeq(SAvlTree, CollKeyValue, SByteArray), SAvlTreeOption), 12) + def getMany_eval(mc: MethodCall, tree: AvlTree, keys: Coll[Coll[Byte]], proof: Coll[Byte]) + (implicit E: ErgoTreeEvaluator): Coll[Option[Coll[Byte]]] = { + val bv = createVerifier(tree, proof) + val nItems = bv.treeHeight + keys.map { key => + // the cost of tree lookup is O(bv.treeHeight) + E.addSeqCost(LookupAvlTree_Info, nItems) { () => + bv.performOneOperation(Lookup(ADKey @@ key.toArray)) match { + case Success(r) => r match { + case Some(v) => Some(Colls.fromArray(v)) + case _ => None + } + case Failure(_) => Interpreter.error(s"Tree proof is incorrect $tree") + } + } + } + } + + lazy val insertMethod = SMethod(this, "insert", + SFunc(Array(SAvlTree, CollKeyValue, SByteArray), SAvlTreeOption), 12, DynamicCost) .withIRInfo(MethodCallIrBuilder) .withInfo(MethodCall, """ @@ -1680,8 +2246,42 @@ case object SAvlTree extends SProduct with SPredefType with SMonoType { | """.stripMargin) - lazy val updateMethod = SMethod(this, "update", - SFunc(IndexedSeq(SAvlTree, CollKeyValue, SByteArray), SAvlTreeOption), 13) + def insert_eval(mc: MethodCall, tree: AvlTree, entries: Coll[(Coll[Byte], Coll[Byte])], proof: Coll[Byte]) + (implicit E: ErgoTreeEvaluator): Option[AvlTree] = { + E.addCost(isInsertAllowed_Info) + if (!tree.isInsertAllowed) { + None + } else { + val bv = createVerifier(tree, proof) + // when the tree is empty we still need to add the insert cost + val nItems = Math.max(bv.treeHeight, 1) + + entries.forall { case (key, value) => + var res = true + // the cost of tree lookup is O(bv.treeHeight) + E.addSeqCost(InsertIntoAvlTree_Info, nItems) { () => + val insert = Insert(ADKey @@ key.toArray, ADValue @@ value.toArray) + val insertRes = bv.performOneOperation(insert) + // TODO v5.0: throwing exception is not consistent with update semantics + // however it preserves v4.0 semantics + if (insertRes.isFailure) { + Interpreter.error(s"Incorrect insert for $tree (key: $key, value: $value, digest: ${tree.digest}): ${insertRes.failed.get}}") + } + res = insertRes.isSuccess + } + res + } + bv.digest match { + case Some(d) => + E.addCost(updateDigest_Info) + Some(tree.updateDigest(Colls.fromArray(d))) + case _ => None + } + } + } + + lazy val updateMethod = SMethod(this, "update", + SFunc(Array(SAvlTree, CollKeyValue, SByteArray), SAvlTreeOption), 13, DynamicCost) .withIRInfo(MethodCallIrBuilder) .withInfo(MethodCall, """ @@ -1697,8 +2297,39 @@ case object SAvlTree extends SProduct with SPredefType with SMonoType { | """.stripMargin) - lazy val removeMethod = SMethod(this, "remove", - SFunc(IndexedSeq(SAvlTree, SByteArray2, SByteArray), SAvlTreeOption), 14) + def update_eval(mc: MethodCall, tree: AvlTree, + operations: Coll[(Coll[Byte], Coll[Byte])], proof: Coll[Byte]) + (implicit E: ErgoTreeEvaluator): Option[AvlTree] = { + E.addCost(isUpdateAllowed_Info) + if (!tree.isUpdateAllowed) { + None + } else { + val bv = createVerifier(tree, proof) + // when the tree is empty we still need to add the insert cost + val nItems = Math.max(bv.treeHeight, 1) + + // here we use forall as looping with fast break on first failed tree oparation + operations.forall { case (key, value) => + var res = true + // the cost of tree update is O(bv.treeHeight) + E.addSeqCost(UpdateAvlTree_Info, nItems) { () => + val op = Update(ADKey @@ key.toArray, ADValue @@ value.toArray) + val updateRes = bv.performOneOperation(op) + res = updateRes.isSuccess + } + res + } + bv.digest match { + case Some(d) => + E.addCost(updateDigest_Info) + Some(tree.updateDigest(Colls.fromArray(d))) + case _ => None + } + } + } + + lazy val removeMethod = SMethod(this, "remove", + SFunc(Array(SAvlTree, SByteArray2, SByteArray), SAvlTreeOption), 14, DynamicCost) .withIRInfo(MethodCallIrBuilder) .withInfo(MethodCall, """ @@ -1714,14 +2345,47 @@ case object SAvlTree extends SProduct with SPredefType with SMonoType { | """.stripMargin) - lazy val updateDigestMethod = SMethod(this, "updateDigest", - SFunc(IndexedSeq(SAvlTree, SByteArray), SAvlTree), 15) + def remove_eval(mc: MethodCall, tree: AvlTree, + operations: Coll[Coll[Byte]], proof: Coll[Byte]) + (implicit E: ErgoTreeEvaluator): Option[AvlTree] = { + E.addCost(isRemoveAllowed_Info) + if (!tree.isRemoveAllowed) { + None + } else { + val bv = createVerifier(tree, proof) + // when the tree is empty we still need to add the insert cost + val nItems = Math.max(bv.treeHeight, 1) + + cfor(0)(_ < operations.length, _ + 1) { i => + E.addSeqCost(RemoveAvlTree_Info, nItems) { () => + val key = operations(i).toArray + bv.performOneOperation(Remove(ADKey @@ key)) + } + } + + E.addCost(digest_Info) + bv.digest match { + case Some(d) => + E.addCost(updateDigest_Info) + Some(tree.updateDigest(Colls.fromArray(d))) + case _ => None + } + } + } + + lazy val updateDigestMethod = SMethod(this, "updateDigest", + SFunc(Array(SAvlTree, SByteArray), SAvlTree), 15, FixedCost(40)) .withIRInfo(MethodCallIrBuilder) .withInfo(MethodCall, """ | """.stripMargin) + lazy val updateDigest_Info = { + val m = updateDigestMethod + OperationCostInfo(m.costKind.asInstanceOf[FixedCost], m.opDesc) + } + protected override def getMethods(): Seq[SMethod] = super.getMethods() ++ Seq( digestMethod, enabledOperationsMethod, @@ -1745,6 +2409,9 @@ case object SAvlTree extends SProduct with SPredefType with SMonoType { case object SContext extends SProduct with SPredefType with SMonoType { override type WrappedType = Context override val typeCode: TypeCode = 101: Byte + + override def reprClass: Class[_] = classOf[Context] + override def typeId = typeCode /** Approximate data size of the given context without ContextExtension. */ @@ -1755,17 +2422,18 @@ case object SContext extends SProduct with SPredefType with SMonoType { import SType.{tT, paramT} - lazy val dataInputsMethod = property("dataInputs", SBoxArray, 1) - lazy val headersMethod = property("headers", SHeaderArray, 2) - lazy val preHeaderMethod = property("preHeader", SPreHeader, 3) + lazy val dataInputsMethod = propertyCall("dataInputs", SBoxArray, 1, FixedCost(15)) + lazy val headersMethod = propertyCall("headers", SHeaderArray, 2, FixedCost(15)) + lazy val preHeaderMethod = propertyCall("preHeader", SPreHeader, 3, FixedCost(15)) lazy val inputsMethod = property("INPUTS", SBoxArray, 4, Inputs) lazy val outputsMethod = property("OUTPUTS", SBoxArray, 5, Outputs) lazy val heightMethod = property("HEIGHT", SInt, 6, Height) lazy val selfMethod = property("SELF", SBox, 7, Self) - lazy val selfBoxIndexMethod = property("selfBoxIndex", SInt, 8) + lazy val selfBoxIndexMethod = propertyCall("selfBoxIndex", SInt, 8, FixedCost(20)) lazy val lastBlockUtxoRootHashMethod = property("LastBlockUtxoRootHash", SAvlTree, 9, LastBlockUtxoRootHash) lazy val minerPubKeyMethod = property("minerPubKey", SByteArray, 10, MinerPubkey) - lazy val getVarMethod = SMethod(this, "getVar", SFunc(Array(SContext, SByte), SOption(tT), Array(paramT)), 11) + lazy val getVarMethod = SMethod( + this, "getVar", SFunc(Array(SContext, SByte), SOption(tT), Array(paramT)), 11, GetVar.costKind) .withInfo(GetVar, "Get context variable with given \\lst{varId} and type.", ArgInfo("varId", "\\lst{Byte} identifier of context variable")) @@ -1779,6 +2447,7 @@ case object SContext extends SProduct with SPredefType with SMonoType { case object SHeader extends SProduct with SPredefType with SMonoType { override type WrappedType = Header override val typeCode: TypeCode = 104: Byte + override val reprClass: Class[_] = classOf[Header] override def typeId = typeCode /** Approximate data size of the given context without ContextExtension. */ @@ -1801,21 +2470,21 @@ case object SHeader extends SProduct with SPredefType with SMonoType { } override def isConstantSize = true - lazy val idMethod = property("id", SByteArray, 1) - lazy val versionMethod = property("version", SByte, 2) - lazy val parentIdMethod = property("parentId", SByteArray, 3) - lazy val ADProofsRootMethod = property("ADProofsRoot", SByteArray, 4) - lazy val stateRootMethod = property("stateRoot", SAvlTree, 5) - lazy val transactionsRootMethod = property("transactionsRoot", SByteArray, 6) - lazy val timestampMethod = property("timestamp", SLong, 7) - lazy val nBitsMethod = property("nBits", SLong, 8) - lazy val heightMethod = property("height", SInt, 9) - lazy val extensionRootMethod = property("extensionRoot", SByteArray, 10) - lazy val minerPkMethod = property("minerPk", SGroupElement, 11) - lazy val powOnetimePkMethod = property("powOnetimePk", SGroupElement, 12) - lazy val powNonceMethod = property("powNonce", SByteArray, 13) - lazy val powDistanceMethod = property("powDistance", SBigInt, 14) - lazy val votesMethod = property("votes", SByteArray, 15) + lazy val idMethod = propertyCall("id", SByteArray, 1, FixedCost(10)) + lazy val versionMethod = propertyCall("version", SByte, 2, FixedCost(10)) + lazy val parentIdMethod = propertyCall("parentId", SByteArray, 3, FixedCost(10)) + lazy val ADProofsRootMethod = propertyCall("ADProofsRoot", SByteArray, 4, FixedCost(10)) + lazy val stateRootMethod = propertyCall("stateRoot", SAvlTree, 5, FixedCost(10)) + lazy val transactionsRootMethod = propertyCall("transactionsRoot", SByteArray, 6, FixedCost(10)) + lazy val timestampMethod = propertyCall("timestamp", SLong, 7, FixedCost(10)) + lazy val nBitsMethod = propertyCall("nBits", SLong, 8, FixedCost(10)) + lazy val heightMethod = propertyCall("height", SInt, 9, FixedCost(10)) + lazy val extensionRootMethod = propertyCall("extensionRoot", SByteArray, 10, FixedCost(10)) + lazy val minerPkMethod = propertyCall("minerPk", SGroupElement, 11, FixedCost(10)) + lazy val powOnetimePkMethod = propertyCall("powOnetimePk", SGroupElement, 12, FixedCost(10)) + lazy val powNonceMethod = propertyCall("powNonce", SByteArray, 13, FixedCost(10)) + lazy val powDistanceMethod = propertyCall("powDistance", SBigInt, 14, FixedCost(10)) + lazy val votesMethod = propertyCall("votes", SByteArray, 15, FixedCost(10)) protected override def getMethods() = super.getMethods() ++ Seq( idMethod, versionMethod, parentIdMethod, ADProofsRootMethod, stateRootMethod, transactionsRootMethod, @@ -1828,6 +2497,8 @@ case object SHeader extends SProduct with SPredefType with SMonoType { case object SPreHeader extends SProduct with SPredefType with SMonoType { override type WrappedType = PreHeader override val typeCode: TypeCode = 105: Byte + override val reprClass: Class[_] = classOf[PreHeader] + override def typeId = typeCode /** Approximate data size of the given context without ContextExtension. */ @@ -1842,13 +2513,13 @@ case object SPreHeader extends SProduct with SPredefType with SMonoType { } override def isConstantSize = true - lazy val versionMethod = property("version", SByte, 1) - lazy val parentIdMethod = property("parentId", SByteArray, 2) - lazy val timestampMethod = property("timestamp", SLong, 3) - lazy val nBitsMethod = property("nBits", SLong, 4) - lazy val heightMethod = property("height", SInt, 5) - lazy val minerPkMethod = property("minerPk", SGroupElement, 6) - lazy val votesMethod = property("votes", SByteArray, 7) + lazy val versionMethod = propertyCall("version", SByte, 1, FixedCost(10)) + lazy val parentIdMethod = propertyCall("parentId", SByteArray, 2, FixedCost(10)) + lazy val timestampMethod = propertyCall("timestamp", SLong, 3, FixedCost(10)) + lazy val nBitsMethod = propertyCall("nBits", SLong, 4, FixedCost(10)) + lazy val heightMethod = propertyCall("height", SInt, 5, FixedCost(10)) + lazy val minerPkMethod = propertyCall("minerPk", SGroupElement, 6, FixedCost(10)) + lazy val votesMethod = propertyCall("votes", SByteArray, 7, FixedCost(10)) protected override def getMethods() = super.getMethods() ++ Seq( versionMethod, parentIdMethod, timestampMethod, nBitsMethod, heightMethod, minerPkMethod, votesMethod @@ -1873,6 +2544,7 @@ case object SPreHeader extends SProduct with SPredefType with SMonoType { case object SGlobal extends SProduct with SPredefType with SMonoType { override type WrappedType = SigmaDslBuilder override val typeCode: TypeCode = 106: Byte + override val reprClass: Class[_] = classOf[SigmaDslBuilder] override def typeId = typeCode /** Approximate data size of the given context without ContextExtension. */ override def dataSize(v: SType#WrappedType): Long = { @@ -1882,10 +2554,13 @@ case object SGlobal extends SProduct with SPredefType with SMonoType { import SType.tT - lazy val groupGeneratorMethod = SMethod(this, "groupGenerator", SFunc(this, SGroupElement), 1) + lazy val groupGeneratorMethod = SMethod( + this, "groupGenerator", SFunc(this, SGroupElement), 1, GroupGenerator.costKind) .withIRInfo({ case (builder, obj, method, args, tparamSubst) => GroupGenerator }) .withInfo(GroupGenerator, "") - lazy val xorMethod = SMethod(this, "xor", SFunc(IndexedSeq(this, SByteArray, SByteArray), SByteArray), 2) + + lazy val xorMethod = SMethod( + this, "xor", SFunc(Array(this, SByteArray, SByteArray), SByteArray), 2, Xor.costKind) .withIRInfo({ case (_, _, _, Seq(l, r), _) => Xor(l.asByteArray, r.asByteArray) }) diff --git a/sigmastate/src/main/scala/sigmastate/utxo/CostTable.scala b/sigmastate/src/main/scala/sigmastate/utxo/CostTable.scala index 5084552d03..004405f09c 100644 --- a/sigmastate/src/main/scala/sigmastate/utxo/CostTable.scala +++ b/sigmastate/src/main/scala/sigmastate/utxo/CostTable.scala @@ -304,7 +304,6 @@ object CostTable { //Maximum number of expressions in initial(non-reduced script) val MaxExpressions = 300 - } diff --git a/sigmastate/src/main/scala/sigmastate/utxo/transformers.scala b/sigmastate/src/main/scala/sigmastate/utxo/transformers.scala index a31311eddf..1e4efa7865 100644 --- a/sigmastate/src/main/scala/sigmastate/utxo/transformers.scala +++ b/sigmastate/src/main/scala/sigmastate/utxo/transformers.scala @@ -7,9 +7,16 @@ import sigmastate._ import sigmastate.serialization.OpCodes.OpCode import sigmastate.serialization.OpCodes import org.ergoplatform.ErgoBox.RegisterId +import scalan.RType import sigmastate.Operations._ +import sigmastate.eval.{Evaluation, SigmaDsl} +import sigmastate.interpreter.ErgoTreeEvaluator +import sigmastate.interpreter.ErgoTreeEvaluator.{DataEnv, error} import sigmastate.lang.exceptions.InterpreterException -import special.sigma.Box +import special.collection.Coll +import special.sigma.{Box, SigmaProp} + +// TODO refactor: remove this trait as it doesn't have semantic meaning /** Every operation is a transformer of some kind. * This trait is used merely to simplify implementation and avoid copy-paste. @@ -34,9 +41,19 @@ case class MapCollection[IV <: SType, OV <: SType]( override def companion = MapCollection override val tpe = SCollection[OV](mapper.tpe.tRange.asInstanceOf[OV]) override val opType = SCollection.MapMethod.stype.asFunc + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val inputV = input.evalTo[Coll[Any]](env) + val mapperV = mapper.evalTo[Any => Any](env) + val tResItem = Evaluation.stypeToRType(mapper.tpe.tRange).asInstanceOf[RType[Any]] + addSeqCost(MapCollection.costKind, inputV.length)(null) + inputV.map(mapperV)(tResItem) + } } object MapCollection extends ValueCompanion { override def opCode: OpCode = OpCodes.MapCollectionCode + /** Cost of: 1) obtain result RType 2) invoke map method 3) allocation of resulting + * collection */ + override val costKind = PerItemCost(20, 1, 10) } /** Puts the elements of other collection `col2` after the elements of `input` collection @@ -47,9 +64,17 @@ case class Append[IV <: SType](input: Value[SCollection[IV]], col2: Value[SColle override def companion = Append override val tpe = input.tpe override val opType = SCollection.AppendMethod.stype + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val inputV = input.evalTo[Coll[IV#WrappedType]](env) + val col2V = col2.evalTo[Coll[IV#WrappedType]](env) + addSeqCost(Append.costKind, inputV.length + col2V.length) { () => + inputV.append(col2V) + } + } } object Append extends ValueCompanion { override def opCode: OpCode = OpCodes.AppendCode + override val costKind = PerItemCost(10, 2, 100) } /** Selects an interval of elements. The returned collection is made up @@ -68,9 +93,19 @@ case class Slice[IV <: SType](input: Value[SCollection[IV]], from: Value[SInt.ty val tpeColl = SCollection(input.tpe.typeParams.head.ident) SFunc(Array(tpeColl, SInt, SInt), tpeColl) } + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val inputV = input.evalTo[Coll[Any]](env) + val fromV = from.evalTo[Int](env) + val untilV = until.evalTo[Int](env) + val len = Math.max(0, untilV - fromV) + addSeqCost(Slice.costKind, len) { () => + inputV.slice(fromV, untilV) + } + } } object Slice extends ValueCompanion { override def opCode: OpCode = OpCodes.SliceCode + override val costKind = PerItemCost(10, 2, 100) } /** Selects all elements of `input` collection which satisfy the condition. @@ -86,9 +121,18 @@ case class Filter[IV <: SType](input: Value[SCollection[IV]], override def companion = Filter override def tpe: SCollection[IV] = input.tpe override val opType = SCollection.FilterMethod.stype + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val inputV = input.evalTo[Coll[Any]](env) + val conditionV = condition.evalTo[Any => Boolean](env) + addSeqCost(Filter.costKind, inputV.length)(null) + inputV.filter(conditionV) + } } object Filter extends ValueCompanion { override def opCode: OpCode = OpCodes.FilterCode + /** Cost of: 1) invoke Coll.filter method 2) allocation of resulting + * collection */ + override val costKind = PerItemCost(20, 1, 10) } /** Transforms a collection of values to a boolean (see [[Exists]], [[ForAll]]). */ @@ -113,9 +157,17 @@ case class Exists[IV <: SType](override val input: Value[SCollection[IV]], extends BooleanTransformer[IV] { override def companion = Exists override val opType = SCollection.ExistsMethod.stype + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val inputV = input.evalTo[Coll[Any]](env) + val conditionV = condition.evalTo[Any => Boolean](env) + addSeqCost(Exists.costKind, inputV.length)(null) + inputV.exists(conditionV) + } } object Exists extends BooleanTransformerCompanion { override def opCode: OpCode = OpCodes.ExistsCode + /** Cost of: invoke exists method */ + override val costKind = PerItemCost(3, 1, 10) override def argInfos: Seq[ArgInfo] = ExistsInfo.argInfos } @@ -131,9 +183,17 @@ case class ForAll[IV <: SType](override val input: Value[SCollection[IV]], extends BooleanTransformer[IV] { override def companion = ForAll override val opType = SCollection.ForallMethod.stype + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val inputV = input.evalTo[Coll[Any]](env) + val conditionV = condition.evalTo[Any => Boolean](env) + addSeqCost(ForAll.costKind, inputV.length)(null) + inputV.forall(conditionV) + } } object ForAll extends BooleanTransformerCompanion { override def opCode: OpCode = OpCodes.ForAllCode + /** Cost of: invoke forall method */ + override val costKind = PerItemCost(3, 1, 10) override def argInfos: Seq[ArgInfo] = ForAllInfo.argInfos } @@ -159,10 +219,19 @@ case class Fold[IV <: SType, OV <: SType](input: Value[SCollection[IV]], override def companion = Fold implicit override def tpe: OV = zero.tpe val opType: SFunc = SCollection.FoldMethod.stype + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val inputV = input.evalTo[Coll[IV#WrappedType]](env) + val zeroV = zero.evalTo[OV#WrappedType](env) + Value.checkType(zero, zeroV) // necessary because cast to OV#WrappedType is erased + val foldOpV = foldOp.evalTo[((OV#WrappedType, IV#WrappedType)) => OV#WrappedType](env) + addSeqCost(Fold.costKind, inputV.length)(null) + inputV.foldLeft(zeroV, foldOpV) + } } object Fold extends ValueCompanion { override def opCode: OpCode = OpCodes.FoldCode + override val costKind = PerItemCost(3, 1, 10) def sum[T <: SNumericType](input: Value[SCollection[T]], varId: Int)(implicit tT: T) = Fold(input, Constant(tT.upcast(0.toByte), tT), @@ -189,9 +258,24 @@ case class ByIndex[V <: SType](input: Value[SCollection[V]], override def companion = ByIndex override val tpe = input.tpe.elemType override val opType = SCollection.ApplyMethod.stype.asFunc + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val inputV = input.evalTo[Coll[V#WrappedType]](env) + val indexV = index.evalTo[Int](env) + default match { + case Some(d) => + val dV = d.evalTo[V#WrappedType](env) + Value.checkType(d, dV) // necessary because cast to V#WrappedType is erased + addCost(ByIndex.costKind) + inputV.getOrElse(indexV, dV) + case _ => + addCost(ByIndex.costKind) + inputV.apply(indexV) + } + } } object ByIndex extends ValueCompanion { override def opCode: OpCode = OpCodes.ByIndexCode + override val costKind = FixedCost(30) } /** Select tuple field by its 1-based index. E.g. input._1 is transformed to @@ -202,9 +286,25 @@ case class SelectField(input: Value[STuple], fieldIndex: Byte) override def companion = SelectField override val tpe = input.tpe.items(fieldIndex - 1) override val opType = SFunc(input.tpe, tpe) + + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val inputV = input.evalTo[Any](env) + addCost(SelectField.costKind) + inputV match { + case p: Tuple2[_,_] => + if (fieldIndex == 1) p._1 + else if (fieldIndex == 2) p._2 + else error(s"Unknown fieldIndex $fieldIndex to select from $p: evaluating tree $this") + case _ => + Value.typeError(input, inputV) + } + } } -object SelectField extends ValueCompanion { +object SelectField extends FixedCostValueCompanion { override def opCode: OpCode = OpCodes.SelectFieldCode + /** Cost of: 1) Calling Tuple2.{_1, _2} Scala methods. + * Old cost: ("SelectField", "() => Unit", selectField) */ + override val costKind = FixedCost(10) def typed[T <: SValue](input: Value[STuple], fieldIndex: Byte): T = { SelectField(input, fieldIndex).asInstanceOf[T] } @@ -218,6 +318,7 @@ case class SigmaPropIsProven(input: Value[SSigmaProp.type]) } object SigmaPropIsProven extends ValueCompanion { override def opCode: OpCode = OpCodes.SigmaPropIsProvenCode + override def costKind: CostKind = Value.notSupportedError(this, "costKind") } /** Extract serialized bytes of a SigmaProp value */ @@ -226,9 +327,19 @@ case class SigmaPropBytes(input: Value[SSigmaProp.type]) override def companion = SigmaPropBytes override def tpe = SByteArray override val opType = SFunc(input.tpe, tpe) + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val inputV = input.evalTo[SigmaProp](env) + val numNodes = SigmaDsl.toSigmaBoolean(inputV).size + addSeqCost(SigmaPropBytes.costKind, numNodes) { () => + inputV.propBytes + } + } } -object SigmaPropBytes extends ValueCompanion { +object SigmaPropBytes extends PerItemCostValueCompanion { override def opCode: OpCode = OpCodes.SigmaPropBytesCode + /** BaseCost: serializing one node of SigmaBoolean proposition + * PerChunkCost: serializing one node of SigmaBoolean proposition */ + override val costKind = PerItemCost(baseCost = 35, perChunkCost = 6, chunkSize = 1) } trait SimpleTransformerCompanion extends ValueCompanion { def argInfos: Seq[ArgInfo] @@ -239,10 +350,19 @@ case class SizeOf[V <: SType](input: Value[SCollection[V]]) extends Transformer[SCollection[V], SInt.type] with NotReadyValueInt { override def companion = SizeOf override def opType = SizeOf.OpType + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val inputV = input.evalTo[Coll[Any]](env) + addCost(SizeOf.costKind) + inputV.length + } } object SizeOf extends SimpleTransformerCompanion { val OpType = SFunc(SCollection(SType.tIV), SInt) override def opCode: OpCode = OpCodes.SizeOfCode + /** Cost of: 1) calling Coll.length method (guaranteed to be O(1)) + * Twice the cost of SelectField. + * Old cost: ("SizeOf", "(Coll[IV]) => Int", collLength) */ + override val costKind = FixedCost(14) override def argInfos: Seq[ArgInfo] = SizeOfInfo.argInfos } @@ -253,10 +373,17 @@ sealed trait Extract[V <: SType] extends Transformer[SBox.type, V] { case class ExtractAmount(input: Value[SBox.type]) extends Extract[SLong.type] with NotReadyValueLong { override def companion = ExtractAmount override def opType = ExtractAmount.OpType + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val inputV = input.evalTo[Box](env) + addCost(ExtractAmount.costKind) + inputV.value + } } object ExtractAmount extends SimpleTransformerCompanion { val OpType = SFunc(SBox, SLong) override def opCode: OpCode = OpCodes.ExtractAmountCode + /** Cost of: 1) access `value` property of a [[special.sigma.Box]] */ + override val costKind = FixedCost(8) override def argInfos: Seq[ArgInfo] = ExtractAmountInfo.argInfos } @@ -267,10 +394,23 @@ object ExtractAmount extends SimpleTransformerCompanion { case class ExtractScriptBytes(input: Value[SBox.type]) extends Extract[SByteArray] with NotReadyValueByteArray { override def companion = ExtractScriptBytes override def opType = ExtractScriptBytes.OpType + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val inputV = input.evalTo[Box](env) + addCost(ExtractScriptBytes.costKind) + inputV.propositionBytes + } } object ExtractScriptBytes extends SimpleTransformerCompanion { val OpType = SFunc(SBox, SByteArray) override def opCode: OpCode = OpCodes.ExtractScriptBytesCode + + // TODO v5.0: ensure the following is true + /** The cost is fixed and doesn't include serialization of ErgoTree because + * the ErgoTree is expected to be constructed with non-null propositionBytes. + * This is (and must be) guaranteed by ErgoTree deserializer. + * CostOf: accessing ErgoBox.propositionBytes + */ + override val costKind = FixedCost(10) override def argInfos: Seq[ArgInfo] = ExtractScriptBytesInfo.argInfos } @@ -278,10 +418,19 @@ object ExtractScriptBytes extends SimpleTransformerCompanion { case class ExtractBytes(input: Value[SBox.type]) extends Extract[SByteArray] with NotReadyValueByteArray { override def companion = ExtractBytes override def opType = ExtractBytes.OpType + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val inputV = input.evalTo[Box](env) + addCost(ExtractBytes.costKind) + inputV.bytes + } } object ExtractBytes extends SimpleTransformerCompanion { val OpType = SFunc(SBox, SByteArray) override def opCode: OpCode = OpCodes.ExtractBytesCode + /** The cost is fixed and doesn't include serialization of ErgoBox because + * the ErgoBox is expected to be constructed with non-null `bytes`. + * TODO v5.0: This is not, but must be guaranteed by ErgoBox deserializer. */ + override val costKind = FixedCost(12) override def argInfos: Seq[ArgInfo] = ExtractBytesInfo.argInfos } @@ -289,10 +438,20 @@ object ExtractBytes extends SimpleTransformerCompanion { case class ExtractBytesWithNoRef(input: Value[SBox.type]) extends Extract[SByteArray] with NotReadyValueByteArray { override def companion = ExtractBytesWithNoRef override def opType = ExtractBytesWithNoRef.OpType + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val inputV = input.evalTo[Box](env) + addCost(ExtractBytesWithNoRef.costKind) + inputV.bytesWithoutRef + } } object ExtractBytesWithNoRef extends SimpleTransformerCompanion { val OpType = SFunc(SBox, SByteArray) override def opCode: OpCode = OpCodes.ExtractBytesWithNoRefCode + + /** The cost if fixed and doesn't include serialization of ErgoBox because + * the ErgoBox is expected to be constructed with non-null `bytes`. */ + override val costKind = FixedCost(12) + override def argInfos: Seq[ArgInfo] = ExtractBytesWithNoRefInfo.argInfos } @@ -300,10 +459,17 @@ object ExtractBytesWithNoRef extends SimpleTransformerCompanion { case class ExtractId(input: Value[SBox.type]) extends Extract[SByteArray] with NotReadyValueByteArray { override def companion = ExtractId override def opType = ExtractId.OpType + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val inputV = input.evalTo[Box](env) + addCost(ExtractId.costKind) + inputV.id + } } object ExtractId extends SimpleTransformerCompanion { val OpType = SFunc(SBox, SByteArray) override def opCode: OpCode = OpCodes.ExtractIdCode + /** CostOf: cost of computing hash from `ErgoBox.bytes` */ + override val costKind = FixedCost(12) override def argInfos: Seq[ArgInfo] = ExtractIdInfo.argInfos } @@ -314,9 +480,17 @@ case class ExtractRegisterAs[V <: SType]( input: Value[SBox.type], extends Extract[SOption[V]] with NotReadyValue[SOption[V]] { override def companion = ExtractRegisterAs override val opType = SFunc(ExtractRegisterAs.BoxAndByte, tpe) + lazy val tV = Evaluation.stypeToRType(tpe.elemType) + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val inputV = input.evalTo[Box](env) + addCost(ExtractRegisterAs.costKind) + inputV.getReg(registerId.number)(tV) + } } object ExtractRegisterAs extends ValueCompanion { override def opCode: OpCode = OpCodes.ExtractRegisterAs + /** CostOf: 1) accessing `registers` collection 2) comparing types 3) allocating Some()*/ + override val costKind = FixedCost(50) //HOTSPOT:: avoids thousands of allocations per second private val BoxAndByte: IndexedSeq[SType] = Array(SBox, SByte) @@ -335,9 +509,15 @@ case class ExtractCreationInfo(input: Value[SBox.type]) extends Extract[STuple] override def companion = ExtractCreationInfo override def tpe: STuple = ResultType override def opType = OpType + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val inputV = input.evalTo[Box](env) + addCost(ExtractCreationInfo.costKind) + inputV.creationInfo + } } object ExtractCreationInfo extends SimpleTransformerCompanion { override def opCode: OpCode = OpCodes.ExtractCreationInfoCode + override val costKind = FixedCost(16) override def argInfos: Seq[ArgInfo] = ExtractCreationInfoInfo.argInfos val ResultType = STuple(SInt, SByteArray) val OpType = SFunc(SBox, ResultType) @@ -359,6 +539,7 @@ case class DeserializeContext[V <: SType](id: Byte, tpe: V) extends Deserialize[ } object DeserializeContext extends ValueCompanion { override def opCode: OpCode = OpCodes.DeserializeContextCode + override val costKind = PerItemCost(1, 10, 128) } /** Extract register of SELF box as Coll[Byte], deserialize it into Value and inline into executing script. @@ -370,15 +551,24 @@ case class DeserializeRegister[V <: SType](reg: RegisterId, tpe: V, default: Opt } object DeserializeRegister extends ValueCompanion { override def opCode: OpCode = OpCodes.DeserializeRegisterCode + override val costKind = PerItemCost(1, 10, 128) } /** See [[special.sigma.Context.getVar()]] for detailed description. */ case class GetVar[V <: SType](varId: Byte, override val tpe: SOption[V]) extends NotReadyValue[SOption[V]] { override def companion = GetVar - override val opType = SFunc(Array(SContext, SByte), tpe) + override val opType = SFunc(Array(SContext, SByte), tpe) // TODO optimize: avoid Array allocation + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val t = Evaluation.stypeToRType(tpe.elemType) + addCost(GetVar.costKind) + E.context.getVar(varId)(t) + } } -object GetVar extends ValueCompanion { +object GetVar extends FixedCostValueCompanion { override def opCode: OpCode = OpCodes.GetVarCode + /** Cost of: 1) accessing to array of context vars by index + * Old cost: ("GetVar", "(Context, Byte) => Option[T]", getVarCost) */ + override val costKind = FixedCost(10) def apply[V <: SType](varId: Byte, innerTpe: V): GetVar[V] = GetVar[V](varId, SOption(innerTpe)) } @@ -392,9 +582,16 @@ case class OptionGet[V <: SType](input: Value[SOption[V]]) extends Transformer[S override val opType = SFunc(input.tpe, tpe) override def tpe: V = input.tpe.elemType override def toString: String = s"$input.get" + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val opt = input.evalTo[Option[V#WrappedType]](env) + addCost(OptionGet.costKind) + opt.get + } } -object OptionGet extends SimpleTransformerCompanion { +object OptionGet extends SimpleTransformerCompanion with FixedCostValueCompanion { override def opCode: OpCode = OpCodes.OptionGetCode + /** Cost of: 1) Calling Option.get Scala method. */ + override val costKind = FixedCost(15) override def argInfos: Seq[ArgInfo] = OptionGetInfo.argInfos } @@ -410,9 +607,18 @@ case class OptionGetOrElse[V <: SType](input: Value[SOption[V]], default: Value[ override def companion = OptionGetOrElse override val opType = SFunc(IndexedSeq(input.tpe, tpe), tpe) override def tpe: V = input.tpe.elemType + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val inputV = input.evalTo[Option[V#WrappedType]](env) + val dV = default.evalTo[V#WrappedType](env) // TODO v6.0: execute lazily + Value.checkType(default, dV) // necessary because cast to V#WrappedType is erased + addCost(OptionGetOrElse.costKind) + inputV.getOrElse(dV) + } } object OptionGetOrElse extends ValueCompanion { override def opCode: OpCode = OpCodes.OptionGetOrElseCode + /** Cost of: 1) Calling Option.getOrElse Scala method. */ + override val costKind = FixedCost(20) } /** Returns false if the option is None, true otherwise. */ @@ -421,8 +627,15 @@ case class OptionIsDefined[V <: SType](input: Value[SOption[V]]) override def companion = OptionIsDefined override val opType = SFunc(input.tpe, SBoolean) override def tpe= SBoolean + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + val inputV = input.evalTo[Option[V#WrappedType]](env) + addCost(OptionIsDefined.costKind) + inputV.isDefined + } } object OptionIsDefined extends SimpleTransformerCompanion { override def opCode: OpCode = OpCodes.OptionIsDefinedCode + /** Cost of: 1) Calling Option.isDefined Scala method. */ + override val costKind = FixedCost(10) override def argInfos: Seq[ArgInfo] = OptionIsDefinedInfo.argInfos } diff --git a/sigmastate/src/test/scala/sigmastate/SoftForkabilitySpecification.scala b/sigmastate/src/test/scala/sigmastate/SoftForkabilitySpecification.scala index 27efc5a695..341c29428a 100644 --- a/sigmastate/src/test/scala/sigmastate/SoftForkabilitySpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/SoftForkabilitySpecification.scala @@ -107,6 +107,7 @@ class SoftForkabilitySpecification extends SigmaTestingData with BeforeAndAfterA override def companion = this override val opCode: OpCode = Height2Code // use reserved code override val opType = SFunc(SContext, SInt) + override val costKind: CostKind = Height.costKind } val Height2Ser = CaseObjectSerialization(Height2, Height2) From 19aeff2244d3e29773e9680a636d8ec2434a78d7 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Mon, 2 Aug 2021 16:14:50 +0300 Subject: [PATCH 26/56] prepare-v5.0: part 4: more ScalaDocs --- .../main/scala/sigmastate/eval/Profiler.scala | 40 +++++++++++++------ .../interpreter/CostAccumulator.scala | 1 + 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/eval/Profiler.scala b/sigmastate/src/main/scala/sigmastate/eval/Profiler.scala index 0806de841a..cfc3cba11c 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/Profiler.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/Profiler.scala @@ -14,8 +14,12 @@ import spire.{math, sp} import scala.reflect.ClassTag -abstract class StatItem[@sp (Long, Double) V] { - /** How many data points has been collected */ +/** Holds a series of profile measurements associated with a key. + * Allows to compute simple statistic data. + * @tparam V type of the measured numeric value + */ +abstract class StatHolder[@sp (Long, Double) V] { + /** How many data points have been collected */ def count: Int /** Sum of all data points */ @@ -29,6 +33,11 @@ abstract class StatItem[@sp (Long, Double) V] { def mean: (V, Int) } +/** Collects profiler measured data points associated with keys. + * Group points by key into [[StatHolder]]s. + * @tparam K type of the mapping key + * @tparam V type of the measured numeric value + */ class StatCollection[@sp(Int) K, @sp(Long, Double) V] (implicit n: math.Numeric[V], ctK: ClassTag[K], ctV: ClassTag[V]) { @@ -37,12 +46,13 @@ class StatCollection[@sp(Int) K, @sp(Long, Double) V] } // NOTE: this class is mutable so better to keep it private - class StatItemImpl extends StatItem[V] { + private class StatHolderImpl extends StatHolder[V] { final val NumMaxPoints = 10000 val dataPoints: DBuffer[V] = DBuffer.ofSize[V](256) def addPoint(point: V) = { + // collect data points until the threshold if (dataPoints.length < NumMaxPoints) { dataPoints += point } @@ -67,23 +77,29 @@ class StatCollection[@sp(Int) K, @sp(Long, Double) V] } /** Timings of op codes. For performance debox.Map is used, which keeps keys unboxed. */ - private val opStat = DMap[K, StatItemImpl]() + private val opStat = DMap[K, StatHolderImpl]() + /** Returns arithmetic mean value (excluding 10% of smallest and 10% of highest values) + * for the given key. + */ final def getMean(key: K): Option[(V, Int)] = opStat.get(key).map(_.mean) - /** Update time measurement stats for a given operation. */ + /** Update measurement stats for a given operation. */ final def addPoint(key: K, point: V) = { val item = opStat.getOrElse(key, null) if (item != null) { item.addPoint(point) } else { - val item = new StatItemImpl + val item = new StatHolderImpl item.addPoint(point) opStat(key) = item } } - final def mapToArray[@sp C: ClassTag](f: (K, StatItem[V]) => C): Array[C] = { + /** Maps each entry of the collected mapping to a new array of values using the given + * function. + */ + final def mapToArray[@sp C: ClassTag](f: (K, StatHolder[V]) => C): Array[C] = { opStat.mapToArray(f) } } @@ -108,16 +124,16 @@ class Profiler { * recursive invocations of the evaluator. */ private var opStack: List[OpStat] = Nil - /** Called from evaluator (like [[sigmastate.interpreter.ErgoTreeEvaluator]]) - * immediately before the evaluator start recursive evaluation of the given node. + /** Called from evaluator immediately before the evaluator start recursive evaluation of + * the given node. */ def onBeforeNode(node: SValue) = { val t = System.nanoTime() opStack = new OpStat(node, t, 0, t) :: opStack } - /** Called from evaluator (like [[sigmastate.interpreter.ErgoTreeEvaluator]]) - * immediately after the evaluator finishes recursive evaluation of the given node. + /** Called from evaluator immediately after the evaluator finishes recursive evaluation + * of the given node. */ def onAfterNode(node: SValue) = { val t = System.nanoTime() @@ -159,7 +175,7 @@ class Profiler { mcStat.addPoint(key, time) } - /** Wrapper class which implementes special equality between CostItem instances, + /** Wrapper class which implements special equality between CostItem instances, * suitable for collecting of the statistics. */ class CostItemKey(val costItem: CostItem) { override def hashCode(): Int = costItem match { diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/CostAccumulator.scala b/sigmastate/src/main/scala/sigmastate/interpreter/CostAccumulator.scala index 4371925e88..62f42bca4f 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/CostAccumulator.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/CostAccumulator.scala @@ -49,6 +49,7 @@ class CostAccumulator(initialCost: Int, costLimit: Option[Long]) { } /** Called once for each operation of a scope (lambda or thunk). + * @throws CostLimitException when current accumulated cost exceeds `costLimit` */ def add(opCost: Int): Unit = { currentScope.add(opCost) From 7405392fd0cdaab01842e4705dfe616262590501 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Wed, 4 Aug 2021 13:41:23 +0300 Subject: [PATCH 27/56] i735-java7: added java7.compat.Math and used where necessary --- common/src/main/java/java7/compat/Math.java | 134 ++++++++++++++++++ .../src/main/scala/scalan/ExactIntegral.scala | 12 +- .../src/main/scala/scalan/ExactNumeric.scala | 12 +- .../special/collection/MonoidInstances.scala | 8 +- .../src/main/scala/sigmastate/Values.scala | 2 +- .../scala/sigmastate/eval/Evaluation.scala | 8 +- .../scala/sigmastate/eval/IRContext.scala | 2 +- 7 files changed, 156 insertions(+), 22 deletions(-) create mode 100644 common/src/main/java/java7/compat/Math.java diff --git a/common/src/main/java/java7/compat/Math.java b/common/src/main/java/java7/compat/Math.java new file mode 100644 index 0000000000..ced20e575f --- /dev/null +++ b/common/src/main/java/java7/compat/Math.java @@ -0,0 +1,134 @@ +package java7.compat; + +/** + * Contains methods introduced since Java 1.8 which are not available in Java 1.7. + * Using this methods supports compatibility with Java 1.7 in non-JVM contexts like + * RoboVM. + * The implementations are copies from JDK 1.8 sources. + *

+ * See + * this issue + */ +public final class Math { + /** + * Returns the sum of its arguments, + * throwing an exception if the result overflows an {@code int}. + * + * @param x the first value + * @param y the second value + * @return the result + * @throws ArithmeticException if the result overflows an int + * @since 1.8 + */ + public static int addExact(int x, int y) { + int r = x + y; + // HD 2-12 Overflow iff both arguments have the opposite sign of the result + if (((x ^ r) & (y ^ r)) < 0) { + throw new ArithmeticException("integer overflow"); + } + return r; + } + + /** + * Returns the sum of its arguments, + * throwing an exception if the result overflows a {@code long}. + * + * @param x the first value + * @param y the second value + * @return the result + * @throws ArithmeticException if the result overflows a long + * @since 1.8 + */ + public static long addExact(long x, long y) { + long r = x + y; + // HD 2-12 Overflow iff both arguments have the opposite sign of the result + if (((x ^ r) & (y ^ r)) < 0) { + throw new ArithmeticException("long overflow"); + } + return r; + } + + /** + * Returns the difference of the arguments, + * throwing an exception if the result overflows an {@code int}. + * + * @param x the first value + * @param y the second value to subtract from the first + * @return the result + * @throws ArithmeticException if the result overflows an int + * @since 1.8 + */ + public static int subtractExact(int x, int y) { + int r = x - y; + // HD 2-12 Overflow iff the arguments have different signs and + // the sign of the result is different than the sign of x + if (((x ^ y) & (x ^ r)) < 0) { + throw new ArithmeticException("integer overflow"); + } + return r; + } + + /** + * Returns the difference of the arguments, + * throwing an exception if the result overflows a {@code long}. + * + * @param x the first value + * @param y the second value to subtract from the first + * @return the result + * @throws ArithmeticException if the result overflows a long + * @since 1.8 + */ + public static long subtractExact(long x, long y) { + long r = x - y; + // HD 2-12 Overflow iff the arguments have different signs and + // the sign of the result is different than the sign of x + if (((x ^ y) & (x ^ r)) < 0) { + throw new ArithmeticException("long overflow"); + } + return r; + } + + /** + * Returns the product of the arguments, + * throwing an exception if the result overflows an {@code int}. + * + * @param x the first value + * @param y the second value + * @return the result + * @throws ArithmeticException if the result overflows an int + * @since 1.8 + */ + public static int multiplyExact(int x, int y) { + long r = (long)x * (long)y; + if ((int)r != r) { + throw new ArithmeticException("integer overflow"); + } + return (int)r; + } + + /** + * Returns the product of the arguments, + * throwing an exception if the result overflows a {@code long}. + * + * @param x the first value + * @param y the second value + * @return the result + * @throws ArithmeticException if the result overflows a long + * @since 1.8 + */ + public static long multiplyExact(long x, long y) { + long r = x * y; + long ax = java.lang.Math.abs(x); + long ay = java.lang.Math.abs(y); + if (((ax | ay) >>> 31 != 0)) { + // Some bits greater than 2^31 that might cause overflow + // Check the result using the divide operator + // and check for the special case of Long.MIN_VALUE * -1 + if (((y != 0) && (r / y != x)) || + (x == Long.MIN_VALUE && y == -1)) { + throw new ArithmeticException("long overflow"); + } + } + return r; + } +} diff --git a/common/src/main/scala/scalan/ExactIntegral.scala b/common/src/main/scala/scalan/ExactIntegral.scala index 74d85462ab..c8cbc0d861 100644 --- a/common/src/main/scala/scalan/ExactIntegral.scala +++ b/common/src/main/scala/scalan/ExactIntegral.scala @@ -43,15 +43,15 @@ object ExactIntegral { implicit object IntIsExactIntegral extends ExactIntegral[Int] { val n = IntIsIntegral - override def plus(x: Int, y: Int): Int = Math.addExact(x, y) - override def minus(x: Int, y: Int): Int = Math.subtractExact(x, y) - override def times(x: Int, y: Int): Int = Math.multiplyExact(x, y) + override def plus(x: Int, y: Int): Int = java7.compat.Math.addExact(x, y) + override def minus(x: Int, y: Int): Int = java7.compat.Math.subtractExact(x, y) + override def times(x: Int, y: Int): Int = java7.compat.Math.multiplyExact(x, y) } implicit object LongIsExactIntegral extends ExactIntegral[Long] { val n = LongIsIntegral - override def plus(x: Long, y: Long): Long = Math.addExact(x, y) - override def minus(x: Long, y: Long): Long = Math.subtractExact(x, y) - override def times(x: Long, y: Long): Long = Math.multiplyExact(x, y) + override def plus(x: Long, y: Long): Long = java7.compat.Math.addExact(x, y) + override def minus(x: Long, y: Long): Long = java7.compat.Math.subtractExact(x, y) + override def times(x: Long, y: Long): Long = java7.compat.Math.multiplyExact(x, y) } } diff --git a/common/src/main/scala/scalan/ExactNumeric.scala b/common/src/main/scala/scalan/ExactNumeric.scala index 734938ce34..a16e87e4a5 100644 --- a/common/src/main/scala/scalan/ExactNumeric.scala +++ b/common/src/main/scala/scalan/ExactNumeric.scala @@ -47,15 +47,15 @@ object ExactNumeric { implicit object IntIsExactNumeric extends ExactNumeric[Int] { val n = IntIsIntegral - override def plus(x: Int, y: Int): Int = Math.addExact(x, y) - override def minus(x: Int, y: Int): Int = Math.subtractExact(x, y) - override def times(x: Int, y: Int): Int = Math.multiplyExact(x, y) + override def plus(x: Int, y: Int): Int = java7.compat.Math.addExact(x, y) + override def minus(x: Int, y: Int): Int = java7.compat.Math.subtractExact(x, y) + override def times(x: Int, y: Int): Int = java7.compat.Math.multiplyExact(x, y) } implicit object LongIsExactNumeric extends ExactNumeric[Long] { val n = LongIsIntegral - override def plus(x: Long, y: Long): Long = Math.addExact(x, y) - override def minus(x: Long, y: Long): Long = Math.subtractExact(x, y) - override def times(x: Long, y: Long): Long = Math.multiplyExact(x, y) + override def plus(x: Long, y: Long): Long = java7.compat.Math.addExact(x, y) + override def minus(x: Long, y: Long): Long = java7.compat.Math.subtractExact(x, y) + override def times(x: Long, y: Long): Long = java7.compat.Math.multiplyExact(x, y) } } diff --git a/library-impl/src/main/scala/special/collection/MonoidInstances.scala b/library-impl/src/main/scala/special/collection/MonoidInstances.scala index 1183b9378e..d33c9bafae 100644 --- a/library-impl/src/main/scala/special/collection/MonoidInstances.scala +++ b/library-impl/src/main/scala/special/collection/MonoidInstances.scala @@ -21,8 +21,8 @@ class MonoidBuilderInst extends MonoidBuilder { } class IntPlusMonoid(val zero: Int) extends Monoid[Int] { - def plus(x: Int, y: Int): Int = Math.addExact(x, y) - def power(x: Int, n: Int): Int = Math.multiplyExact(x, n) + def plus(x: Int, y: Int): Int = java7.compat.Math.addExact(x, y) + def power(x: Int, n: Int): Int = java7.compat.Math.multiplyExact(x, n) } @Internal @@ -42,8 +42,8 @@ class IntMinMonoid(val zero: Int) extends Monoid[Int] { } class LongPlusMonoid(val zero: Long) extends Monoid[Long] { - def plus(x: Long, y: Long): Long = Math.addExact(x, y) - def power(x: Long, n: Int): Long = Math.multiplyExact(x, n.toLong) + def plus(x: Long, y: Long): Long = java7.compat.Math.addExact(x, y) + def power(x: Long, n: Int): Long = java7.compat.Math.multiplyExact(x, n.toLong) } @Internal diff --git a/sigmastate/src/main/scala/sigmastate/Values.scala b/sigmastate/src/main/scala/sigmastate/Values.scala index 2c8c0a6589..d3f212d7f8 100644 --- a/sigmastate/src/main/scala/sigmastate/Values.scala +++ b/sigmastate/src/main/scala/sigmastate/Values.scala @@ -595,7 +595,7 @@ object Values { var sum = 0 cfor(0)(_ < nChildren, _ + 1) { i => val c = estimateCost(childrenArr(i)) - sum = Math.addExact(sum, c) + sum = java7.compat.Math.addExact(sum, c) } sum } diff --git a/sigmastate/src/main/scala/sigmastate/eval/Evaluation.scala b/sigmastate/src/main/scala/sigmastate/eval/Evaluation.scala index 94204c9a0d..b36f5998e2 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/Evaluation.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/Evaluation.scala @@ -375,7 +375,7 @@ trait Evaluation extends RuntimeCosting { IR: IRContext => @inline def += (n: Int) = { // println(s"${_currentCost} + $n") - this._currentCost = java.lang.Math.addExact(this._currentCost, n) + this._currentCost = java7.compat.Math.addExact(this._currentCost, n) } @inline def currentCost: Int = _currentCost } @@ -469,7 +469,7 @@ trait Evaluation extends RuntimeCosting { IR: IRContext => if (costLimit.isDefined) { val limit = costLimit.get val loopCost = if (_loopStack.isEmpty) 0 else _loopStack.head.accumulatedCost - val accumulatedCost = java.lang.Math.addExact(cost, loopCost) + val accumulatedCost = java7.compat.Math.addExact(cost, loopCost) if (accumulatedCost > limit) { throw new CostLimitException(accumulatedCost, Evaluation.msgCostLimitError(accumulatedCost, limit), None) } @@ -496,7 +496,7 @@ trait Evaluation extends RuntimeCosting { IR: IRContext => if (_loopStack.nonEmpty && _loopStack.head.body == body) { // every time we exit the body of the loop we need to update accumulated cost val h = _loopStack.head - h.accumulatedCost = java.lang.Math.addExact(h.accumulatedCost, deltaCost) + h.accumulatedCost = java7.compat.Math.addExact(h.accumulatedCost, deltaCost) } } @@ -865,7 +865,7 @@ object Evaluation { * @throws CostLimitException */ def addCostChecked(current: Long, more: Long, limit: Long): Long = { - val newCost = Math.addExact(current, more) + val newCost = java7.compat.Math.addExact(current, more) if (newCost > limit) { throw new CostLimitException( estimatedCost = newCost, diff --git a/sigmastate/src/main/scala/sigmastate/eval/IRContext.scala b/sigmastate/src/main/scala/sigmastate/eval/IRContext.scala index f5b59538c2..f102ceeb9a 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/IRContext.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/IRContext.scala @@ -134,7 +134,7 @@ trait IRContext extends Evaluation with TreeBuilding { !!!(s"Estimated cost $estimatedCost should be equal $accCost") } - val scaledCost = JMath.multiplyExact(estimatedCost.toLong, CostTable.costFactorIncrease.toLong) / CostTable.costFactorDecrease + val scaledCost = java7.compat.Math.multiplyExact(estimatedCost.toLong, CostTable.costFactorIncrease.toLong) / CostTable.costFactorDecrease val totalCost = Evaluation.addCostChecked(initCost, scaledCost, maxCost) totalCost.toInt } From e1755d11d65989fb40afa72666c686ee32c1a0f4 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Fri, 13 Aug 2021 17:29:50 +0300 Subject: [PATCH 28/56] copyright notice added --- common/src/main/java/java7/compat/Math.java | 25 +++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/common/src/main/java/java7/compat/Math.java b/common/src/main/java/java7/compat/Math.java index ced20e575f..72ed8d5de0 100644 --- a/common/src/main/java/java7/compat/Math.java +++ b/common/src/main/java/java7/compat/Math.java @@ -1,3 +1,28 @@ +/* + * Copyright (c) 1994, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + package java7.compat; /** From db181ab76023e32fb66ad0daaff03c988f9cb36f Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Wed, 25 Aug 2021 15:55:44 +0300 Subject: [PATCH 29/56] more ScalaDocs and some refactoring --- common/src/main/scala/scalan/AnyVals.scala | 2 +- .../scala/sigmastate/DataValueComparer.scala | 18 ++++++++-- .../src/main/scala/sigmastate/Values.scala | 15 +++++++- .../interpreter/ErgoTreeEvaluator.scala | 36 +++++++++++++++---- .../src/main/scala/sigmastate/types.scala | 2 +- .../scala/sigmastate/utxo/transformers.scala | 10 +++--- 6 files changed, 66 insertions(+), 17 deletions(-) diff --git a/common/src/main/scala/scalan/AnyVals.scala b/common/src/main/scala/scalan/AnyVals.scala index f1e40348da..e1d5235d42 100644 --- a/common/src/main/scala/scalan/AnyVals.scala +++ b/common/src/main/scala/scalan/AnyVals.scala @@ -46,7 +46,7 @@ object AVHashMap { /** Helper method to create a new map with the given capacity. */ def apply[K,V](initialCapacity: Int) = new AVHashMap[K,V](new HashMap[K,V](initialCapacity)) - /** Helper method to create a new map form sequence of K, V pairs. */ + /** Helper method to create a new map from the sequence of K, V pairs. */ def fromSeq[K,V](items: Seq[(K, V)]): AVHashMap[K,V] = { val map = new AVHashMap[K,V](new HashMap[K,V](items.length)) items.foreach { case (k, v) => diff --git a/sigmastate/src/main/scala/sigmastate/DataValueComparer.scala b/sigmastate/src/main/scala/sigmastate/DataValueComparer.scala index 72c9bfdba7..959cbd2dcb 100644 --- a/sigmastate/src/main/scala/sigmastate/DataValueComparer.scala +++ b/sigmastate/src/main/scala/sigmastate/DataValueComparer.scala @@ -24,7 +24,7 @@ object DataValueComparer { * OperationCost is the type specific cost. * For this reason reordering of cases may lead to divergence between an estimated and * the actual execution cost (time). - * The constants are part of the consensus procotol and cannot be changed without forking. + * The constants are part of the consensus protocol and cannot be changed without forking. */ final val CostOf_MatchType = 1 final val CostKind_MatchType = FixedCost(CostOf_MatchType) @@ -42,7 +42,6 @@ object DataValueComparer { final val OpDesc_EQ_Coll = NamedDesc("EQ_Coll") final val EQ_Coll = OperationCostInfo(CostKind_EQ_Coll, OpDesc_EQ_Coll) - /** NOTE: In the formula `(2 + 1)` the 1 corresponds to the second type match. */ final val CostKind_EQ_Tuple = FixedCost(4) // case 3 final val OpDesc_EQ_Tuple = NamedDesc("EQ_Tuple") final val EQ_Tuple = OperationCostInfo(CostKind_EQ_Tuple, OpDesc_EQ_Tuple) @@ -172,6 +171,11 @@ object DataValueComparer { okEqual } + /** Compare two collections for equality. Used when the element type A is NOT known + * statically. When the type A is scalar, each collection item is boxed before + * comparison, which have significant performace overhead. + * For this reason, this method is used as a fallback case. + */ def equalColls[A](c1: Coll[A], c2: Coll[A])(implicit E: ErgoTreeEvaluator): Boolean = { var okEqual = true E.addSeqCost(CostKind_EQ_Coll, OpDesc_EQ_Coll) { () => @@ -187,6 +191,9 @@ object DataValueComparer { okEqual } + /** Compares two collections by dispatching to the most efficient implementation + * depending on the actual type A. + * */ def equalColls_Dispatch[A](coll1: Coll[A], coll2: Coll[A])(implicit E: ErgoTreeEvaluator): Boolean = { coll1.tItem match { case BooleanType => @@ -226,6 +233,7 @@ object DataValueComparer { } } + /** Compare equality of two sequences of SigmaBoolean trees. */ def equalSigmaBooleans(xs: Seq[SigmaBoolean], ys: Seq[SigmaBoolean]) (implicit E: ErgoTreeEvaluator): Boolean = { val len = xs.length @@ -237,6 +245,7 @@ object DataValueComparer { okEqual } + /** Compare equality of two SigmaBoolean trees. */ def equalSigmaBoolean(l: SigmaBoolean, r: SigmaBoolean) (implicit E: ErgoTreeEvaluator): Boolean = { E.addCost(MatchType) // once for every node of the SigmaBoolean tree @@ -268,6 +277,7 @@ object DataValueComparer { } } + /** Returns true if the given GroupElement is equal to the given object. */ def equalGroupElement(ge1: GroupElement, r: Any)(implicit E: ErgoTreeEvaluator): Boolean = { var okEqual = true E.addFixedCost(EQ_GroupElement) { @@ -276,6 +286,7 @@ object DataValueComparer { okEqual } + /** Returns true if the given EcPointType is equal to the given object. */ def equalECPoint(p1: EcPointType, r: Any)(implicit E: ErgoTreeEvaluator): Boolean = { var okEqual = true E.addFixedCost(EQ_GroupElement) { @@ -285,6 +296,9 @@ object DataValueComparer { } // TODO v5.0: introduce a new limit on structural depth of data values + /** Generic comparison of any two data values. The method dispatches on a type of the + * left value and then performs the specific comparison. + */ def equalDataValues(l: Any, r: Any)(implicit E: ErgoTreeEvaluator): Boolean = { var okEqual: Boolean = false l match { diff --git a/sigmastate/src/main/scala/sigmastate/Values.scala b/sigmastate/src/main/scala/sigmastate/Values.scala index a0c50c6278..1904b089d1 100644 --- a/sigmastate/src/main/scala/sigmastate/Values.scala +++ b/sigmastate/src/main/scala/sigmastate/Values.scala @@ -139,6 +139,19 @@ object Values { E.addTypeBasedCost(costKind, tpe, this.companion.opDesc)(block) } + /** Add the cost of a repeated operation to the accumulator and associate it with this + * operation. The number of items (loop iterations) is known in advance (like in + * Coll.map operation) + * + * @param costKind cost descriptor of the operation + * @param nItems number of operations known in advance (before loop execution) + */ + @inline + final def addSeqCostNoOp(costKind: PerItemCost, nItems: Int) + (implicit E: ErgoTreeEvaluator): Unit = { + E.addSeqCostNoOp(costKind, nItems, this.companion.opDesc) + } + /** Add the cost of a repeated operation to the accumulator and associate it with this * operation. The number of items (loop iterations) is known in advance (like in * Coll.map operation) @@ -1083,7 +1096,7 @@ object Values { protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { var curEnv = env val len = items.length - addSeqCost(BlockValue.costKind, len)(null) + addSeqCostNoOp(BlockValue.costKind, len) cfor(0)(_ < len, _ + 1) { i => val vd = items(i).asInstanceOf[ValDef] val v = vd.rhs.evalTo[Any](curEnv) diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/ErgoTreeEvaluator.scala b/sigmastate/src/main/scala/sigmastate/interpreter/ErgoTreeEvaluator.scala index 13ef9957d4..8fe6ed10d3 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/ErgoTreeEvaluator.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/ErgoTreeEvaluator.scala @@ -87,15 +87,19 @@ class ErgoTreeEvaluator( } } - /** Evaluates the given expression in the given data environment. */ + /** Evaluates the given expression in the given data environment and accrue the cost + * into the `coster` of this evaluator. + * @return the value of the expression and the total accumulated cost in the coster. + * The returned cost includes the initial cost accumulated in the `coster` + * prior to calling this method. */ def evalWithCost(env: DataEnv, exp: SValue): (Any, Int) = { val res = eval(env, exp) val cost = coster.totalCost (res, cost) } - /** Trace of cost items accumulated during execution of `eval` method. - * Call [[ArrayBuffer.clear()]] before each `eval` invocation. */ + /** Trace of cost items accumulated during execution of `eval` method. Call + * [[scala.collection.mutable.ArrayBuffer.clear()]] before each `eval` invocation. */ val costTrace = { val b = mutable.ArrayBuilder.make[CostItem] b.sizeHint(1000) @@ -135,7 +139,7 @@ class ErgoTreeEvaluator( costItem = TypeBasedCostItem(opDesc, costKind, tpe) costTrace += costItem } - if (settings.isMeasureOperationTime && block != null) { + if (settings.isMeasureOperationTime) { if (costItem == null) { costItem = TypeBasedCostItem(opDesc, costKind, tpe) } @@ -149,7 +153,7 @@ class ErgoTreeEvaluator( } else { val cost = costKind.costFunc(tpe) coster.add(cost) - if (block == null) null.asInstanceOf[R] else block() + block() } } @@ -186,6 +190,24 @@ class ErgoTreeEvaluator( addFixedCost(costInfo.costKind, costInfo.opDesc)(block) } + /** Adds the given cost to the `coster`. If tracing is enabled, creates a new cost item + * with the given operation. + * + * @param costKind the cost to be added to `coster` for each item + * @param nItems the number of items + * @param opDesc the operation to associate the cost with (when costTracingEnabled) + * @hotspot don't beautify the code + */ + final def addSeqCostNoOp(costKind: PerItemCost, nItems: Int, opDesc: OperationDesc): Unit = { + var costItem: SeqCostItem = null + if (settings.costTracingEnabled) { + costItem = SeqCostItem(opDesc, costKind, nItems) + costTrace += costItem + } + val cost = costKind.cost(nItems) + coster.add(cost) + } + /** Adds the given cost to the `coster`. If tracing is enabled, creates a new cost item * with the given operation. * @@ -202,7 +224,7 @@ class ErgoTreeEvaluator( costItem = SeqCostItem(opDesc, costKind, nItems) costTrace += costItem } - if (settings.isMeasureOperationTime && block != null) { + if (settings.isMeasureOperationTime) { if (costItem == null) { costItem = SeqCostItem(opDesc, costKind, nItems) } @@ -216,7 +238,7 @@ class ErgoTreeEvaluator( } else { val cost = costKind.cost(nItems) coster.add(cost) - if (block == null) null.asInstanceOf[R] else block() + block() } } diff --git a/sigmastate/src/main/scala/sigmastate/types.scala b/sigmastate/src/main/scala/sigmastate/types.scala index 96c80c61db..b431f8cf28 100644 --- a/sigmastate/src/main/scala/sigmastate/types.scala +++ b/sigmastate/src/main/scala/sigmastate/types.scala @@ -1379,7 +1379,7 @@ object SCollection extends STypeCompanion with MethodByNameUnapply { def map_eval[A,B](mc: MethodCall, xs: Coll[A], f: A => B)(implicit E: ErgoTreeEvaluator): Coll[B] = { val tpeB = mc.tpe.asInstanceOf[SCollection[SType]].elemType val tB = Evaluation.stypeToRType(tpeB).asInstanceOf[RType[B]] - E.addSeqCost(MapCollection.costKind, xs.length, mc.method.opDesc)(null) + E.addSeqCostNoOp(MapCollection.costKind, xs.length, mc.method.opDesc) xs.map(f)(tB) } diff --git a/sigmastate/src/main/scala/sigmastate/utxo/transformers.scala b/sigmastate/src/main/scala/sigmastate/utxo/transformers.scala index 1e4efa7865..d7c2086de6 100644 --- a/sigmastate/src/main/scala/sigmastate/utxo/transformers.scala +++ b/sigmastate/src/main/scala/sigmastate/utxo/transformers.scala @@ -45,7 +45,7 @@ case class MapCollection[IV <: SType, OV <: SType]( val inputV = input.evalTo[Coll[Any]](env) val mapperV = mapper.evalTo[Any => Any](env) val tResItem = Evaluation.stypeToRType(mapper.tpe.tRange).asInstanceOf[RType[Any]] - addSeqCost(MapCollection.costKind, inputV.length)(null) + addSeqCostNoOp(MapCollection.costKind, inputV.length) inputV.map(mapperV)(tResItem) } } @@ -124,7 +124,7 @@ case class Filter[IV <: SType](input: Value[SCollection[IV]], protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { val inputV = input.evalTo[Coll[Any]](env) val conditionV = condition.evalTo[Any => Boolean](env) - addSeqCost(Filter.costKind, inputV.length)(null) + addSeqCostNoOp(Filter.costKind, inputV.length) inputV.filter(conditionV) } } @@ -160,7 +160,7 @@ case class Exists[IV <: SType](override val input: Value[SCollection[IV]], protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { val inputV = input.evalTo[Coll[Any]](env) val conditionV = condition.evalTo[Any => Boolean](env) - addSeqCost(Exists.costKind, inputV.length)(null) + addSeqCostNoOp(Exists.costKind, inputV.length) inputV.exists(conditionV) } } @@ -186,7 +186,7 @@ case class ForAll[IV <: SType](override val input: Value[SCollection[IV]], protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { val inputV = input.evalTo[Coll[Any]](env) val conditionV = condition.evalTo[Any => Boolean](env) - addSeqCost(ForAll.costKind, inputV.length)(null) + addSeqCostNoOp(ForAll.costKind, inputV.length) inputV.forall(conditionV) } } @@ -224,7 +224,7 @@ case class Fold[IV <: SType, OV <: SType](input: Value[SCollection[IV]], val zeroV = zero.evalTo[OV#WrappedType](env) Value.checkType(zero, zeroV) // necessary because cast to OV#WrappedType is erased val foldOpV = foldOp.evalTo[((OV#WrappedType, IV#WrappedType)) => OV#WrappedType](env) - addSeqCost(Fold.costKind, inputV.length)(null) + addSeqCostNoOp(Fold.costKind, inputV.length) inputV.foldLeft(zeroV, foldOpV) } } From 93387a4985d52dc3a5263ac832d5fb7a7662cc99 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Sat, 16 Oct 2021 18:11:12 +0300 Subject: [PATCH 30/56] prepare-v5.0-evaluator: ScalaDocs for substituteConstants --- .../serialization/ErgoTreeSerializer.scala | 13 +++++++++++++ sigmastate/src/main/scala/sigmastate/trees.scala | 14 ++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/sigmastate/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala index 5bc96450ae..c6c7977b27 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala @@ -246,6 +246,19 @@ class ErgoTreeSerializer { (header, sizeOpt, constants, treeBytes) } + /** Transforms serialized bytes of ErgoTree with segregated constants by + * replacing constants at given positions with new values. This operation + * allow to use serialized scripts as pre-defined templates. + * See [[sigmastate.SubstConstants]] for details. + * + * @param scriptBytes serialized ErgoTree with ConstantSegregationFlag set to 1. + * @param positions zero based indexes in ErgoTree.constants array which + * should be replaced with new values + * @param newVals new values to be injected into the corresponding + * positions in ErgoTree.constants array + * @return original scriptBytes array where only specified constants are + * replaced and all other bytes remain exactly the same + */ def substituteConstants(scriptBytes: Array[Byte], positions: Array[Int], newVals: Array[Value[SType]])(implicit vs: SigmaValidationSettings): (Array[Byte], Int) = { diff --git a/sigmastate/src/main/scala/sigmastate/trees.scala b/sigmastate/src/main/scala/sigmastate/trees.scala index de84d6b33b..028cb712c9 100644 --- a/sigmastate/src/main/scala/sigmastate/trees.scala +++ b/sigmastate/src/main/scala/sigmastate/trees.scala @@ -792,6 +792,20 @@ object SubstConstants extends ValueCompanion { override val costKind = PerItemCost(100, 100, 1) val OpType = SFunc(Array(SByteArray, SIntArray, SCollection(SType.tT)), SByteArray) + + /** Transforms serialized bytes of ErgoTree with segregated constants by + * replacing constants at given positions with new values. This operation + * allow to use serialized scripts as pre-defined templates. + * See [[sigmastate.SubstConstants]] for details. + * + * @param scriptBytes serialized ErgoTree with ConstantSegregationFlag set to 1. + * @param positions zero based indexes in ErgoTree.constants array which + * should be replaced with new values + * @param newVals new values to be injected into the corresponding + * positions in ErgoTree.constants array + * @return original scriptBytes array where only specified constants are + * replaced and all other bytes remain exactly the same + */ def eval(scriptBytes: Array[Byte], positions: Array[Int], newVals: Array[Value[SType]])(implicit vs: SigmaValidationSettings): (Array[Byte], Int) = From e64ca7930ff818403bb3020eadd4b5d8c029d9b6 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Sat, 16 Oct 2021 20:22:52 +0300 Subject: [PATCH 31/56] prepare-v5.0-evaluator: better ScalaDoc for BigIntIsIntegral.rem method --- .../scala/sigmastate/eval/BigIntegerOps.scala | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/eval/BigIntegerOps.scala b/sigmastate/src/main/scala/sigmastate/eval/BigIntegerOps.scala index c2deaaba90..5edfb74ed3 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/BigIntegerOps.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/BigIntegerOps.scala @@ -42,13 +42,20 @@ object NumericOps { trait BigIntIsIntegral extends Integral[BigInt] { def quot(x: BigInt, y: BigInt): BigInt = x.divide(y) - /** This method is used in ErgoTreeEvaluator based interpreter, to implement '%' operation. - * Even though it is called `rem`, the semantics of ErgoTree language requires - * it to correspond to [[java.math.BigInteger]].mod method. - * Note also that there is no distinction between `mod` and `reminder` methods in - * [[scala.math.Integral]] trait the same way as it is in BigInteger class. + /** This method is used in ErgoTreeEvaluator based interpreter, to implement + * '%' operation of ErgoTree (i.e. `%: (T, T) => T` operation) for all + * numeric types T including BigInt. + * + * In the v4.x interpreter, however, the `%` operation is implemented using + * [[CBigInt]].mod method (see implementation in [[TestBigInt]], which + * delegates to [[java.math.BigInteger]].mod method. + * + * Even though this method is called `rem`, the semantics of ErgoTree + * language requires it to correspond to [[java.math.BigInteger]].mod + * method. + * * For this reason we define implementation of this `rem` method using - * [[java.math.BigInteger]].mod. + * [[BigInt]].mod. */ def rem(x: BigInt, y: BigInt): BigInt = x.mod(y) From 5ae4631ace1a788a871a76e44ccf819912717161 Mon Sep 17 00:00:00 2001 From: Alex Chepurnoy Date: Mon, 18 Oct 2021 14:21:05 +0300 Subject: [PATCH 32/56] removing unused code --- sigma-impl/src/main/scala/sigma/types/View.scala | 12 ------------ .../test/scala/sigmastate/utxo/SigmaContract.scala | 9 --------- 2 files changed, 21 deletions(-) delete mode 100644 sigma-impl/src/main/scala/sigma/types/View.scala delete mode 100644 sigmastate/src/test/scala/sigmastate/utxo/SigmaContract.scala diff --git a/sigma-impl/src/main/scala/sigma/types/View.scala b/sigma-impl/src/main/scala/sigma/types/View.scala deleted file mode 100644 index fafb84e277..0000000000 --- a/sigma-impl/src/main/scala/sigma/types/View.scala +++ /dev/null @@ -1,12 +0,0 @@ -package sigma.types - -import spire.util.Opt - -object View { - def mkPrimView[Val](value: Val): Opt[PrimView[Val]] = (value match { - case x: scala.Boolean => Opt(CBoolean(x)) - case x: scala.Byte => Opt(CByte(x)) - case x: scala.Int => Opt(CInt(x)) - case _ => Opt.empty[Val] - }).asInstanceOf[Opt[PrimView[Val]]] -} diff --git a/sigmastate/src/test/scala/sigmastate/utxo/SigmaContract.scala b/sigmastate/src/test/scala/sigmastate/utxo/SigmaContract.scala deleted file mode 100644 index 33ebc9c1ff..0000000000 --- a/sigmastate/src/test/scala/sigmastate/utxo/SigmaContract.scala +++ /dev/null @@ -1,9 +0,0 @@ -package sigmastate.utxo - -import org.ergoplatform.ErgoAddressEncoder.TestnetNetworkPrefix -import sigmastate.lang.{TransformingSigmaBuilder, SigmaCompiler, CompilerSettings} - -abstract class SigmaContract { - val compiler = new SigmaCompiler(CompilerSettings( - TestnetNetworkPrefix, TransformingSigmaBuilder, lowerMethodCalls = true)) -} From 05bb3f0bf6d8d6a2add4053abe015ec00a7380a2 Mon Sep 17 00:00:00 2001 From: Alex Chepurnoy Date: Mon, 18 Oct 2021 14:50:39 +0300 Subject: [PATCH 33/56] unused entries in CostTable --- sigmastate/src/main/scala/sigmastate/utxo/CostTable.scala | 7 ------- .../test/scala/org/ergoplatform/ErgoScriptPredefSpec.scala | 4 ++-- .../src/test/scala/sigmastate/eval/ErgoScriptTestkit.scala | 3 +-- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/utxo/CostTable.scala b/sigmastate/src/main/scala/sigmastate/utxo/CostTable.scala index 5084552d03..700ede9c30 100644 --- a/sigmastate/src/main/scala/sigmastate/utxo/CostTable.scala +++ b/sigmastate/src/main/scala/sigmastate/utxo/CostTable.scala @@ -1,6 +1,5 @@ package sigmastate.utxo -import org.ergoplatform.SigmaConstants import sigmastate.{Downcast, Upcast} import sigmastate.lang.SigmaParser import sigmastate.lang.Terms.OperationId @@ -299,12 +298,6 @@ object CostTable { CostTable(parsed.toMap) } - //Maximum cost of a script - val ScriptLimit = SigmaConstants.ScriptCostLimit.value - - //Maximum number of expressions in initial(non-reduced script) - val MaxExpressions = 300 - } diff --git a/sigmastate/src/test/scala/org/ergoplatform/ErgoScriptPredefSpec.scala b/sigmastate/src/test/scala/org/ergoplatform/ErgoScriptPredefSpec.scala index 883365f4fb..fbf24cec1d 100644 --- a/sigmastate/src/test/scala/org/ergoplatform/ErgoScriptPredefSpec.scala +++ b/sigmastate/src/test/scala/org/ergoplatform/ErgoScriptPredefSpec.scala @@ -16,7 +16,7 @@ import sigmastate.interpreter.Interpreter.{ScriptNameProp, emptyEnv} import sigmastate.interpreter.{ProverResult, ContextExtension} import sigmastate.lang.Terms.ValueOps import sigmastate.serialization.ValueSerializer -import sigmastate.utxo.{CostTable, ExtractCreationInfo, ByIndex, SelectField} +import sigmastate.utxo.{ExtractCreationInfo, ByIndex, SelectField} import scalan.util.BenchmarkUtil._ import sigmastate.utils.Helpers._ @@ -226,7 +226,7 @@ class ErgoScriptPredefSpec extends SigmaTestingCommons with CrossVersionProps { boxesToSpend = inputBoxes, spendingTransaction, self = inputBoxes.head, - activatedVersionInTests).withCostLimit(CostTable.ScriptLimit * 10) + activatedVersionInTests).withCostLimit(SigmaConstants.ScriptCostLimit.value * 10) val pr = prover.prove(emptyEnv + (ScriptNameProp -> "tokenThresholdScript_prove"), prop, ctx, fakeMessage).getOrThrow verifier.verify(emptyEnv + (ScriptNameProp -> "tokenThresholdScript_verify"), prop, ctx, pr, fakeMessage).getOrThrow._1 shouldBe true diff --git a/sigmastate/src/test/scala/sigmastate/eval/ErgoScriptTestkit.scala b/sigmastate/src/test/scala/sigmastate/eval/ErgoScriptTestkit.scala index 4bb14d65ee..604d68714d 100644 --- a/sigmastate/src/test/scala/sigmastate/eval/ErgoScriptTestkit.scala +++ b/sigmastate/src/test/scala/sigmastate/eval/ErgoScriptTestkit.scala @@ -14,7 +14,7 @@ import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeConte import sigmastate.helpers.TestingHelpers._ import sigmastate.interpreter.ContextExtension import sigmastate.interpreter.Interpreter.ScriptEnv -import special.sigma.{ContractsTestkit, Context => DContext, _} +import special.sigma.{ContractsTestkit, Context => DContext} import sigmastate.serialization.ErgoTreeSerializer.DefaultSerializer import scala.language.implicitConversions @@ -186,7 +186,6 @@ trait ErgoScriptTestkit extends ContractsTestkit with LangTests // check cost val costCtx = ergoCtx.get.toSigmaContext(isCost = true) - val estimatedCost = IR.checkCost(costCtx, tree, costF, CostTable.ScriptLimit) // check size { From 22edd1a08d689028a54c13de9a49af497bb02529 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Wed, 20 Oct 2021 14:25:08 +0300 Subject: [PATCH 34/56] removals: rollback checkCost invocation --- .../src/test/scala/sigmastate/eval/ErgoScriptTestkit.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/sigmastate/src/test/scala/sigmastate/eval/ErgoScriptTestkit.scala b/sigmastate/src/test/scala/sigmastate/eval/ErgoScriptTestkit.scala index 604d68714d..83798dfabc 100644 --- a/sigmastate/src/test/scala/sigmastate/eval/ErgoScriptTestkit.scala +++ b/sigmastate/src/test/scala/sigmastate/eval/ErgoScriptTestkit.scala @@ -186,6 +186,7 @@ trait ErgoScriptTestkit extends ContractsTestkit with LangTests // check cost val costCtx = ergoCtx.get.toSigmaContext(isCost = true) + IR.checkCost(costCtx, tree, costF, SigmaConstants.ScriptCostLimit.value) // check size { From a71825424e75bc21feecffa49b4579348cc7529e Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Wed, 20 Oct 2021 14:48:36 +0300 Subject: [PATCH 35/56] removals: removed quot and rem implementations --- sigmastate/src/main/scala/sigmastate/eval/BigIntegerOps.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/eval/BigIntegerOps.scala b/sigmastate/src/main/scala/sigmastate/eval/BigIntegerOps.scala index a03ba6fca0..052ebef44e 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/BigIntegerOps.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/BigIntegerOps.scala @@ -40,8 +40,8 @@ object NumericOps { implicit object BigIntegerIsIntegral extends BigIntegerIsIntegral with OrderingOps.BigIntegerOrdering trait BigIntIsIntegral extends Integral[BigInt] { - def quot(x: BigInt, y: BigInt): BigInt = x.divide(y) - def rem(x: BigInt, y: BigInt): BigInt = x.remainder(y) + def quot(x: BigInt, y: BigInt): BigInt = ??? // this method should not be used in v4.x + def rem(x: BigInt, y: BigInt): BigInt = ??? // this method should not be used in v4.x def plus(x: BigInt, y: BigInt): BigInt = x.add(y) def minus(x: BigInt, y: BigInt): BigInt = x.subtract(y) def times(x: BigInt, y: BigInt): BigInt = x.multiply(y) From 815bc6d8173fb9732857369f3f2af9fa0ab791a7 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Wed, 20 Oct 2021 15:16:03 +0300 Subject: [PATCH 36/56] prepare-v5.0-evaluator: simplified Tuple.eval --- .../src/main/scala/sigmastate/Values.scala | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/Values.scala b/sigmastate/src/main/scala/sigmastate/Values.scala index 1904b089d1..ade01b6dff 100644 --- a/sigmastate/src/main/scala/sigmastate/Values.scala +++ b/sigmastate/src/main/scala/sigmastate/Values.scala @@ -858,22 +858,19 @@ object Values { Colls.fromArray(xs.toArray(SAny.classTag.asInstanceOf[ClassTag[SAny.WrappedType]]))(RType.AnyType) } protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { + // in v5.0 version we support only tuples of 2 elements to be equivalent with v4.x if (items.length != 2) error(s"Invalid tuple $this") - val res: Any = if (items.length == 2) { - val item0 = items(0) - val x = item0.evalTo[Any](env) - Value.checkType(item0, x) + val item0 = items(0) + val x = item0.evalTo[Any](env) + Value.checkType(item0, x) - val item1 = items(1) - val y = item1.evalTo[Any](env) - Value.checkType(item1, y) + val item1 = items(1) + val y = item1.evalTo[Any](env) + Value.checkType(item1, y) - (x, y) // special representation for pairs (to pass directly to Coll primitives) - } - else - items.map(_.evalTo[Any](env)) // general case + val res = (x, y) // special representation for pairs (to pass directly to Coll primitives) addCost(Tuple.costKind) res From 3c3f6f1d7c194451e414bf28ef6408ed17e45bbd Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Wed, 20 Oct 2021 15:40:35 +0300 Subject: [PATCH 37/56] prepare-v5.0-evaluator: more ScalaDocs --- .../src/main/scala/sigmastate/types.scala | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/sigmastate/src/main/scala/sigmastate/types.scala b/sigmastate/src/main/scala/sigmastate/types.scala index b431f8cf28..bdb6a2c147 100644 --- a/sigmastate/src/main/scala/sigmastate/types.scala +++ b/sigmastate/src/main/scala/sigmastate/types.scala @@ -416,6 +416,7 @@ case class Coster(selector: RuntimeCosting => RuntimeCosting#CostingHandler[_]) case class ArgInfo(name: String, description: String) /** Meta information which can be attached to SMethod. + * @param opDesc optional operation descriptor * @param description human readable description of the method * @param args one item for each argument */ case class OperationInfo(opDesc: Option[ValueCompanion], description: String, args: Seq[ArgInfo]) { @@ -439,6 +440,9 @@ object OperationInfo { * (builder, obj, m, args, subst) it transforms it to a new ErgoTree * node, which is then used in the resuting ErgoTree coming out of * the ErgoScript compiler. + * @param javaMethod Java [[Method]] which should be used to evaluate + * [[sigmastate.lang.Terms.MethodCall]] node of ErgoTree. + * @param invokeDescsBuilder optional builder of additional type descriptors (see extraDescriptors) */ case class MethodIRInfo( irBuilder: Option[PartialFunction[(SigmaBuilder, SValue, SMethod, Seq[SValue], STypeSubst), SValue]], @@ -454,8 +458,11 @@ case class MethodIRInfo( * where `stype.tDom`` - argument type and * `stype.tRange` - method result type. * @param methodId method code, it should be unique among methods of the same objType. + * @param costKind cost descriptor for this method * @param irInfo meta information connecting SMethod with ErgoTree (see [[MethodIRInfo]]) * @param docInfo optional human readable method description data + * @param costFunc optional specification of how the cost should be computed for the + * given method call (See ErgoTreeEvaluator.calcCost method). */ case class SMethod( objType: STypeCompanion, @@ -501,6 +508,10 @@ case class SMethod( } } + /** Additional type descriptors, which are necessary to perform invocation of Method + * associated with this instance. + * @see MethodCall.eval + */ lazy val extraDescriptors: Seq[RType[_]] = { irInfo.invokeDescsBuilder match { case Some(builder) => @@ -522,7 +533,10 @@ case class SMethod( objType.getMethodById(methodId).get } - /** @hotspot don't beautify the code */ + /** Returns Java refection [[Method]] which must be invoked to evaluate this method. + * The method is resolved by its name using `name + "_eval"` naming convention. + * @see `map_eval`, `flatMap_eval` and other `*_eval` methods. + * @hotspot don't beautify the code */ lazy val evalMethod: Method = { val argTypes = stype.tDom val nArgs = argTypes.length @@ -561,6 +575,7 @@ case class SMethod( def withConcreteTypes(subst: Map[STypeVar, SType]): SMethod = withSType(stype.withSubstTypes(subst).asFunc) + /** Name of a language operation represented by this method. */ def opName = objType.getClass.getSimpleName + "." + name /** Returns [[OperationId]] for AOT costing. */ From cec28552d22718b34aee2b0dd7624b8554dbe8b8 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Wed, 20 Oct 2021 15:54:38 +0300 Subject: [PATCH 38/56] prepare-v5.0-evaluator: removed invalid checkWellDefined method --- .../src/main/scala/sigmastate/types.scala | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/types.scala b/sigmastate/src/main/scala/sigmastate/types.scala index bdb6a2c147..f2430ec5cf 100644 --- a/sigmastate/src/main/scala/sigmastate/types.scala +++ b/sigmastate/src/main/scala/sigmastate/types.scala @@ -477,22 +477,6 @@ case class SMethod( /** Operation descriptor of this method. */ lazy val opDesc = MethodDesc(this) - /** Do some checks the method descriptor is well configured and consistent. */ - def checkWellDefined(): Boolean = { - docInfo match { - case Some(opInfo) => - opInfo.opDesc match { - case Some(opDesc) if (opDesc == MethodCall || opDesc == PropertyCall) => - if (costFunc.isEmpty) - throw new IllegalStateException( - s"Neither costKind not costFunc is defined for $this") - else true - case _ => true - } - case None => true - } - } - /** Finds and keeps the [[Method]] instance which corresponds to this method descriptor. * The lazy value is forced only if irInfo.javaMethod == None */ @@ -884,7 +868,7 @@ object SNumericType extends STypeCompanion { ToBigIntMethod, // see Downcast ToBytesMethod, ToBitsMethod - ) // TODO v5.0: .ensuring(_.forall { m => m.checkWellDefined() }) + ) /** Collection of names of numeric casting methods (like `toByte`, `toInt`, etc). */ val castMethods: Array[String] = From f248dc13084107c272bc05d7902dc7c0884bdd85 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Wed, 20 Oct 2021 16:01:09 +0300 Subject: [PATCH 39/56] prepare-v5.0-evaluator: comment for modQ methods --- sigmastate/src/main/scala/sigmastate/types.scala | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sigmastate/src/main/scala/sigmastate/types.scala b/sigmastate/src/main/scala/sigmastate/types.scala index f2430ec5cf..ec4b96274b 100644 --- a/sigmastate/src/main/scala/sigmastate/types.scala +++ b/sigmastate/src/main/scala/sigmastate/types.scala @@ -1058,6 +1058,11 @@ case object SBigInt extends SPrimType with SEmbeddable with SNumericType with SM SigmaDsl.BigInt(bi) } + /** The following `modQ` methods are not fully implemented in v4.x and this descriptors. + * This descritors are remain here in the code and are waiting for full implementation + * is upcoming soft-forks at which point the cost parameters should be calculated and + * changed. + */ val ModQMethod = SMethod(this, "modQ", SFunc(this, SBigInt), 1, FixedCost(1)) .withInfo(ModQ, "Returns this \\lst{mod} Q, i.e. remainder of division by Q, where Q is an order of the cryprographic group.") val PlusModQMethod = SMethod(this, "plusModQ", SFunc(IndexedSeq(this, SBigInt), SBigInt), 2, FixedCost(1)) From 2ad6e44597f24afbbd36e0697f12f7550dfed618 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Wed, 20 Oct 2021 23:58:51 +0300 Subject: [PATCH 40/56] prepare-v5.0-evaluator: more ScalaDocs and code cleanup --- .../scala/sigmastate/eval/BigIntegerOps.scala | 30 +++++++------------ 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/eval/BigIntegerOps.scala b/sigmastate/src/main/scala/sigmastate/eval/BigIntegerOps.scala index 5edfb74ed3..7fe5d815e7 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/BigIntegerOps.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/BigIntegerOps.scala @@ -24,21 +24,7 @@ object OrderingOps { object NumericOps { - trait BigIntegerIsIntegral extends Integral[BigInteger] { - def quot(x: BigInteger, y: BigInteger): BigInteger = x.divide(y) - def rem(x: BigInteger, y: BigInteger): BigInteger = x.remainder(y) - def plus(x: BigInteger, y: BigInteger): BigInteger = x.add(y) - def minus(x: BigInteger, y: BigInteger): BigInteger = x.subtract(y) - def times(x: BigInteger, y: BigInteger): BigInteger = x.multiply(y) - def negate(x: BigInteger): BigInteger = x.negate() - def fromInt(x: Int): BigInteger = BigInteger.valueOf(x) - def toInt(x: BigInteger): Int = x.intValueExact() - def toLong(x: BigInteger): Long = x.longValueExact() - def toFloat(x: BigInteger): Float = x.floatValue() - def toDouble(x: BigInteger): Double = x.doubleValue() - } - implicit object BigIntegerIsIntegral extends BigIntegerIsIntegral with OrderingOps.BigIntegerOrdering - + /** Base implementation of Integral methods for BigInt. */ trait BigIntIsIntegral extends Integral[BigInt] { def quot(x: BigInt, y: BigInt): BigInt = x.divide(y) @@ -72,16 +58,18 @@ object NumericOps { /** The instance of Integral for BigInt. * - * Note: ExactIntegral is not defined for [[special.sigma.BigInt]]. - * This is because arithmetic BigInt operations are handled specially + * Note: ExactIntegral was not defined for [[special.sigma.BigInt]] in v4.x. + * This is because arithmetic BigInt operations were handled in a special way * (see `case op: ArithOp[t] if op.tpe == SBigInt =>` in RuntimeCosting.scala). - * As result [[scalan.primitives.UnBinOps.ApplyBinOp]] nodes are not created for BigInt - * operations, and hence operation descriptors such as + * As result [[scalan.primitives.UnBinOps.ApplyBinOp]] nodes were not created for + * BigInt operations in v4.x., and hence operation descriptors such as * [[scalan.primitives.NumericOps.IntegralDivide]] and - * [[scalan.primitives.NumericOps.IntegralMod]] are not used for BigInt. + * [[scalan.primitives.NumericOps.IntegralMod]] were not used for BigInt. + * NOTE: this instance is used in the new v5.0 interpreter. */ implicit object BigIntIsIntegral extends BigIntIsIntegral with OrderingOps.BigIntOrdering + /** The instance of [[ExactNumeric]] typeclass for [[BigInt]]. */ implicit object BigIntIsExactNumeric extends ExactNumeric[BigInt] { val n = BigIntIsIntegral override def plus(x: BigInt, y: BigInt): BigInt = n.plus(x, y) @@ -89,6 +77,7 @@ object NumericOps { override def times(x: BigInt, y: BigInt): BigInt = n.times(x, y) } + /** The instance of [[ExactIntegral]] typeclass for [[BigInt]]. */ implicit object BigIntIsExactIntegral extends ExactIntegral[BigInt] { val n = BigIntIsIntegral override def plus(x: BigInt, y: BigInt): BigInt = n.plus(x, y) @@ -96,6 +85,7 @@ object NumericOps { override def times(x: BigInt, y: BigInt): BigInt = n.times(x, y) } + /** The instance of [[scalan.ExactOrdering]] typeclass for [[BigInt]]. */ implicit object BigIntIsExactOrdering extends ExactOrderingImpl[BigInt](BigIntIsIntegral) } From c9d5f1a7a4839b66f8a9dee4df41c048a8fe280d Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Fri, 22 Oct 2021 15:51:34 +0300 Subject: [PATCH 41/56] prepare-v5.0-evaluator: more ScalaDocs and cleanup --- .../interpreter/ErgoTreeEvaluator.scala | 82 +----- .../src/main/scala/sigmastate/types.scala | 236 ++++++++++++++---- 2 files changed, 197 insertions(+), 121 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/ErgoTreeEvaluator.scala b/sigmastate/src/main/scala/sigmastate/interpreter/ErgoTreeEvaluator.scala index 8fe6ed10d3..a531263d3f 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/ErgoTreeEvaluator.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/ErgoTreeEvaluator.scala @@ -100,7 +100,7 @@ class ErgoTreeEvaluator( /** Trace of cost items accumulated during execution of `eval` method. Call * [[scala.collection.mutable.ArrayBuffer.clear()]] before each `eval` invocation. */ - val costTrace = { + private lazy val costTrace = { val b = mutable.ArrayBuilder.make[CostItem] b.sizeHint(1000) b @@ -242,6 +242,7 @@ class ErgoTreeEvaluator( } } + /** Adds the cost to the `coster`. See the other overload for details. */ @inline final def addSeqCost[R](costInfo: OperationCostInfo[PerItemCost], nItems: Int) (block: () => R): R = { @@ -287,40 +288,10 @@ class ErgoTreeEvaluator( } } + /** Adds the cost to the `coster`. See the other overload for details. */ final def addSeqCost(costInfo: OperationCostInfo[PerItemCost])(block: () => Int): Unit = { addSeqCost(costInfo.costKind, costInfo.opDesc)(block) } - - - final def addMethodCallCost[R](mc: MethodCall, obj: Any, args: Array[Any]) - (block: => R): R = { - val costDetails = ErgoTreeEvaluator.calcCost(mc, obj, args)(this) - if (settings.costTracingEnabled) { - costTrace += MethodCallCostItem(costDetails) - } - - if (settings.isMeasureOperationTime) { - coster.add(costDetails.cost) - // measure time - val start = System.nanoTime() - val res = block - val end = System.nanoTime() - val time = end - start - - val len = costDetails.trace.length - val totalCost = costDetails.cost - - // spread the measured time between individial cost items - cfor(0)(_ < len, _ + 1) { i => - val costItem = costDetails.trace(i) - profiler.addCostItem(costItem, time * costItem.cost / totalCost) - } - res - } else { - coster.add(costDetails.cost) - block - } - } } object ErgoTreeEvaluator { @@ -379,53 +350,6 @@ object ErgoTreeEvaluator { acc, profiler, evalSettings.copy(profilerOpt = Some(profiler))) } - /** Executes [[FixedCost]] code `block` and use the given evaluator `E` to perform - * profiling and cost tracing. - * This helper method allows implementation of cost-aware code blocks by using - * thread-local instance of [[ErgoTreeEvaluator]]. - * If the `currentEvaluator` [[DynamicVariable]] is not initialized (equals to null), - * then the block is executed with minimal overhead. - * - * @param costInfo operation descriptor - * @param block block of code to be executed (given as lazy by-name argument) - * @param E evaluator to be used (or null if it is not avaialble on the - * current thread) - * @return result of code block execution - */ - def fixedCostOp[R <: AnyRef](costInfo: OperationCostInfo[FixedCost]) - (block: => R)(implicit E: ErgoTreeEvaluator): R = { - if (E != null) { - var res: R = null.asInstanceOf[R] - E.addFixedCost(costInfo) { - res = block - } - res - } else - block - } - - /** Executes [[PerItemCost]] code `block` and use the given evaluator `E` to perform - * profiling and cost tracing. - * This helper method allows implementation of cost-aware code blocks by using - * thread-local instance of [[ErgoTreeEvaluator]]. - * If the `currentEvaluator` [[DynamicVariable]] is not initialized (equals to null), - * then the block is executed with minimal overhead. - * - * @param costInfo operation descriptor - * @param nItems number of data items in the operation - * @param block block of code to be executed (given as lazy by-name argument) - * @param E evaluator to be used (or null if it is not avaialble on the - * current thread) - * @return result of code block execution - */ - def perItemCostOp[R](costInfo: OperationCostInfo[PerItemCost], nItems: Int) - (block: () => R)(implicit E: ErgoTreeEvaluator): R = { - if (E != null) { - E.addSeqCost(costInfo, nItems)(block) - } else - block() - } - /** Evaluate the given [[ErgoTree]] in the given Ergo context using the given settings. * The given ErgoTree is evaluated as-is and is not changed during evaluation. * diff --git a/sigmastate/src/main/scala/sigmastate/types.scala b/sigmastate/src/main/scala/sigmastate/types.scala index ec4b96274b..77c3fdce1a 100644 --- a/sigmastate/src/main/scala/sigmastate/types.scala +++ b/sigmastate/src/main/scala/sigmastate/types.scala @@ -123,6 +123,7 @@ object SType { /** Costructs a collection type with the given type of elements. */ implicit def typeCollection[V <: SType](implicit tV: V): SCollection[V] = SCollection[V](tV) + /** RType descriptors for predefined types used in AOTC-based interpreter. */ implicit val SigmaBooleanRType: RType[SigmaBoolean] = RType.fromClassTag(classTag[SigmaBoolean]) implicit val ErgoBoxRType: RType[ErgoBox] = RType.fromClassTag(classTag[ErgoBox]) implicit val ErgoBoxCandidateRType: RType[ErgoBoxCandidate] = RType.fromClassTag(classTag[ErgoBoxCandidate]) @@ -271,6 +272,8 @@ object SType { def asOption[T <: SType]: SOption[T] = tpe.asInstanceOf[SOption[T]] def whenFunc[T](action: SFunc => Unit) = if(tpe.isInstanceOf[SFunc]) action(tpe.asFunc) def asCollection[T <: SType] = tpe.asInstanceOf[SCollection[T]] + + /** Returns the [[ClassTag]] for the given [[SType]]. */ def classTag[T <: SType#WrappedType]: ClassTag[T] = (tpe match { case SBoolean => reflect.classTag[Boolean] case SByte => reflect.classTag[Byte] @@ -295,6 +298,7 @@ object SType { } implicit class AnyOps(val x: Any) extends AnyVal { + /** Helper method to simplify type casts. */ def asWrappedType: SType#WrappedType = x.asInstanceOf[SType#WrappedType] } } @@ -310,6 +314,9 @@ trait STypeCompanion { /** Type identifier to use in method serialization */ def typeId: Byte + /** If this is SType instance then returns the name of the corresponding RType. + * Otherwise returns the name of type companion object (e.g. SCollection). + */ def typeName: String = { this match { case t: SType => @@ -664,13 +671,26 @@ object SMethod { */ type InvokeDescBuilder = SFunc => Seq[SType] - def javaMethodOf[T, A1](methodName: String)(implicit cT: ClassTag[T], cA1: ClassTag[A1]) = + /** Return [[Method]] descriptor for the given `methodName` on the given `cT` type. + * @param methodName the name of the method to lookup + * @param cT the class where to search the methodName + * @param cA1 the class of the method argument + */ + def javaMethodOf[T, A1](methodName: String) + (implicit cT: ClassTag[T], cA1: ClassTag[A1]): Method = cT.runtimeClass.getMethod(methodName, cA1.runtimeClass) - def javaMethodOf[T, A1, A2](methodName: String)(implicit cT: ClassTag[T], cA1: ClassTag[A1], cA2: ClassTag[A2]) = + /** Return [[Method]] descriptor for the given `methodName` on the given `cT` type. + * @param methodName the name of the method to lookup + * @param cT the class where to search the methodName + * @param cA1 the class of the method's first argument + * @param cA2 the class of the method's second argument + */ + def javaMethodOf[T, A1, A2] + (methodName: String) + (implicit cT: ClassTag[T], cA1: ClassTag[A1], cA2: ClassTag[A2]): Method = cT.runtimeClass.getMethod(methodName, cA1.runtimeClass, cA2.runtimeClass) - /** Default fallback method call recognizer which builds MethodCall ErgoTree nodes. */ val MethodCallIrBuilder: PartialFunction[(SigmaBuilder, SValue, SMethod, Seq[SValue], STypeSubst), SValue] = { case (builder, obj, method, args, tparamSubst) => @@ -747,6 +767,8 @@ object SPrimType { /** Upper limit of the interval of valid type codes for primitive types */ final val MaxPrimTypeCode: Byte = 11: Byte + + /** Max possible number of primitive types. */ final val PrimRange: Byte = (MaxPrimTypeCode + 1).toByte } @@ -820,6 +842,10 @@ object SNumericType extends STypeCompanion { } } + /** The following SMethod instances are descriptors of methods available on all numeric + * types. + * @see `val methods` below + * */ val ToByteMethod: SMethod = SMethod(this, "toByte", SFunc(tNum, SByte), 1, null) .withCost(costOfNumericCast) .withInfo(PropertyCall, "Converts this numeric value to \\lst{Byte}, throwing exception if overflow.") @@ -888,6 +914,7 @@ object SNumericType extends STypeCompanion { } +/** Base type for SBoolean and SSigmaProp. */ trait SLogical extends SType { } @@ -895,17 +922,20 @@ trait SLogical extends SType { * @see `SGenericType` */ trait SMonoType extends SType with STypeCompanion { + /** Helper method to create method descriptors for properties (i.e. methods without args). */ protected def propertyCall(name: String, tpeRes: SType, id: Byte, costKind: CostKind): SMethod = SMethod(this, name, SFunc(this, tpeRes), id, costKind) .withIRInfo(MethodCallIrBuilder) .withInfo(PropertyCall, "") + /** Helper method to create method descriptors for properties (i.e. methods without args). */ protected def property(name: String, tpeRes: SType, id: Byte, valueCompanion: ValueCompanion): SMethod = SMethod(this, name, SFunc(this, tpeRes), id, valueCompanion.costKind) .withIRInfo(MethodCallIrBuilder) .withInfo(valueCompanion, "") } +/** Descriptor of ErgoTree type `Boolean` holding `true` or `false` values. */ case object SBoolean extends SPrimType with SEmbeddable with SLogical with SProduct with SMonoType { override type WrappedType = Boolean override val typeCode: TypeCode = 1: Byte @@ -924,6 +954,7 @@ case object SBoolean extends SPrimType with SEmbeddable with SLogical with SProd override def isConstantSize = true } +/** Descriptor of ErgoTree type `Byte` - 8-bit signed integer. */ case object SByte extends SPrimType with SEmbeddable with SNumericType with SMonoType { override type WrappedType = Byte override val typeCode: TypeCode = 2: Byte @@ -936,7 +967,7 @@ case object SByte extends SPrimType with SEmbeddable with SNumericType with SMon case b: Byte => b case _ => sys.error(s"Cannot upcast value $v to the type $this") } - def downcast(v: AnyVal): Byte = v match { + override def downcast(v: AnyVal): Byte = v match { case b: Byte => b case s: Short => s.toByteExact case i: Int => i.toByteExact @@ -945,6 +976,7 @@ case object SByte extends SPrimType with SEmbeddable with SNumericType with SMon } } +/** Descriptor of ErgoTree type `Short` - 16-bit signed integer. */ case object SShort extends SPrimType with SEmbeddable with SNumericType with SMonoType { override type WrappedType = Short override val typeCode: TypeCode = 3: Byte @@ -966,6 +998,7 @@ case object SShort extends SPrimType with SEmbeddable with SNumericType with SMo } } +/** Descriptor of ErgoTree type `Int` - 32-bit signed integer. */ case object SInt extends SPrimType with SEmbeddable with SNumericType with SMonoType { override type WrappedType = Int override val typeCode: TypeCode = 4: Byte @@ -989,6 +1022,7 @@ case object SInt extends SPrimType with SEmbeddable with SNumericType with SMono } } +/** Descriptor of ErgoTree type `Long` - 64-bit signed integer. */ case object SLong extends SPrimType with SEmbeddable with SNumericType with SMonoType { override type WrappedType = Long override val typeCode: TypeCode = 5: Byte @@ -1033,8 +1067,6 @@ case object SBigInt extends SPrimType with SEmbeddable with SNumericType with SM * In sigma we limit the size by the fixed constant and thus BigInt is a constant size type. */ override def isConstantSize = true - val Max: BigInt = SigmaDsl.BigInt(CryptoConstants.dlogGroup.order) - override protected def numericTypeIndex: Int = 4 override def upcast(v: AnyVal): BigInt = { @@ -1082,7 +1114,8 @@ case object SBigInt extends SPrimType with SEmbeddable with SNumericType with SM ) } -/** NOTE: this descriptor both type and type companion */ +/** Descriptor of type `String` which is not used in ErgoTree, but used in ErgoScript. + * NOTE: this descriptor both type and type companion */ case object SString extends SProduct with SMonoType { override type WrappedType = String override val typeCode: TypeCode = 102: Byte @@ -1092,7 +1125,8 @@ case object SString extends SProduct with SMonoType { override def reprClass: Class[_] = classOf[String] } -/** NOTE: this descriptor both type and type companion */ +/** Descriptor of ErgoTree type `GroupElement`. + * NOTE: this descriptor both type and type companion */ case object SGroupElement extends SProduct with SPrimType with SEmbeddable with SMonoType { override type WrappedType = GroupElement override val typeCode: TypeCode = 7: Byte @@ -1104,6 +1138,7 @@ case object SGroupElement extends SProduct with SPrimType with SEmbeddable with /** Cost of: 1) serializing EcPointType to bytes 2) packing them in Coll. */ val GetEncodedCostKind = FixedCost(250) + /** The following SMethod instances are descriptors of methods defined in `GroupElement` type. */ lazy val GetEncodedMethod: SMethod = SMethod( this, "getEncoded", SFunc(Array(this), SByteArray), 2, GetEncodedCostKind) .withIRInfo(MethodCallIrBuilder) @@ -1147,6 +1182,7 @@ case object SGroupElement extends SProduct with SPrimType with SEmbeddable with override def isConstantSize = true } +/** Descriptor of ErgoTree type `SigmaProp` which represent sigma-protocol propositions. */ case object SSigmaProp extends SProduct with SPrimType with SEmbeddable with SLogical with SMonoType { import SType._ override type WrappedType = SigmaProp @@ -1204,22 +1240,20 @@ case class SOption[ElemType <: SType](elemType: ElemType) extends SProduct with } override def isConstantSize = elemType.isConstantSize protected override def getMethods() = super.getMethods() ++ SOption.methods -// override lazy val methods: Seq[SMethod] = { -// val subst = Map(SOption.tT -> elemType) -// SOption.methods.map { method => -// method.copy(stype = SigmaTyper.applySubst(method.stype, subst)) -// } -// } override def toString = s"Option[$elemType]" override def toTermString: String = s"Option[${elemType.toTermString}]" - lazy val typeParams: Seq[STypeParam] = Array(SType.paramT) + override lazy val typeParams: Seq[STypeParam] = Array(SType.paramT) } object SOption extends STypeCompanion { + /** Code of `Option[_]` type constructor. */ val OptionTypeConstrId = 3 + /** Type code for `Option[T] for some T` type used in TypeSerializer. */ val OptionTypeCode: TypeCode = ((SPrimType.MaxPrimTypeCode + 1) * OptionTypeConstrId).toByte + /** Code of `Option[Coll[_]]` type constructor. */ val OptionCollectionTypeConstrId = 4 + /** Type code for `Option[Coll[T]] for some T` type used in TypeSerializer. */ val OptionCollectionTypeCode: TypeCode = ((SPrimType.MaxPrimTypeCode + 1) * OptionCollectionTypeConstrId).toByte override def typeId = OptionTypeCode @@ -1237,6 +1271,7 @@ object SOption extends STypeCompanion { type SBoxOption = SOption[SBox.type] type SAvlTreeOption = SOption[SAvlTree.type] + /** This descriptors are instantiated once here and then reused. */ implicit val SByteOption = SOption(SByte) implicit val SByteArrayOption = SOption(SByteArray) implicit val SShortOption = SOption(SShort) @@ -1255,8 +1290,11 @@ object SOption extends STypeCompanion { val Fold = "fold" import SType.{tT, tR, paramT, paramR} + + /** Type descriptor of `this` argument used in the methods below. */ val ThisType = SOption(tT) + /** The following SMethod instances are descriptors of methods defined in `Option` type. */ val IsDefinedMethod = SMethod( this, IsDefined, SFunc(ThisType, SBoolean), 2, OptionIsDefined.costKind) .withInfo(OptionIsDefined, @@ -1273,16 +1311,17 @@ object SOption extends STypeCompanion { |return the result of evaluating \lst{default}. """.stripMargin, ArgInfo("default", "the default value")) - val FoldMethod = SMethod( - this, Fold, SFunc(Array(ThisType, tR, SFunc(tT, tR)), tR, Array[STypeParam](tT, tR)), 5, FixedCost(1)) - .withInfo(MethodCall, - """Returns the result of applying \lst{f} to this option's - | value if the option is nonempty. Otherwise, evaluates - | expression \lst{ifEmpty}. - | This is equivalent to \lst{option map f getOrElse ifEmpty}. - """.stripMargin, - ArgInfo("ifEmpty", "the expression to evaluate if empty"), - ArgInfo("f", "the function to apply if nonempty")) +// TODO soft-fork: https://github.com/ScorexFoundation/sigmastate-interpreter/issues/479 +// val FoldMethod = SMethod( +// this, Fold, SFunc(Array(ThisType, tR, SFunc(tT, tR)), tR, Array[STypeParam](tT, tR)), 5, FixedCost(1)) +// .withInfo(MethodCall, +// """Returns the result of applying \lst{f} to this option's +// | value if the option is nonempty. Otherwise, evaluates +// | expression \lst{ifEmpty}. +// | This is equivalent to \lst{option map f getOrElse ifEmpty}. +// """.stripMargin, +// ArgInfo("ifEmpty", "the expression to evaluate if empty"), +// ArgInfo("f", "the function to apply if nonempty")) val MapMethod = SMethod(this, "map", SFunc(Array(ThisType, SFunc(tT, tR)), SOption(tR), Array(paramT, paramR)), 7, FixedCost(20)) @@ -1312,15 +1351,16 @@ object SOption extends STypeCompanion { FilterMethod ) def apply[T <: SType](implicit elemType: T, ov: Overload1): SOption[T] = SOption(elemType) -// def unapply[T <: SType](tOpt: SOption[T]): Option[T] = Some(tOpt.elemType) } +/** Base class for descriptors of `Coll[T]` ErgoTree type for some elemType T. */ trait SCollection[T <: SType] extends SProduct with SGenericType { def elemType: T override type WrappedType = Coll[T#WrappedType] override def isConstantSize = false } +/** Descriptor of `Coll[T]` ErgoTree type for some elemType T. */ case class SCollectionType[T <: SType](elemType: T) extends SCollection[T] { override val typeCode: TypeCode = SCollectionType.CollectionTypeCode @@ -1329,17 +1369,26 @@ case class SCollectionType[T <: SType](elemType: T) extends SCollection[T] { implicit val sT = Sized.typeToSized(Evaluation.stypeToRType(elemType)) Sized.sizeOf(coll).dataSize } - def typeParams: Seq[STypeParam] = SCollectionType.typeParams + override def typeParams: Seq[STypeParam] = SCollectionType.typeParams protected override def getMethods() = super.getMethods() ++ SCollection.methods override def toString = s"Coll[$elemType]" override def toTermString = s"Coll[${elemType.toTermString}]" } object SCollectionType { + /** Code of `Coll[_]` type constructor. */ val CollectionTypeConstrId = 1 + + /** Type code for `Coll[T] for some T` type used in TypeSerializer. */ val CollectionTypeCode: TypeCode = ((SPrimType.MaxPrimTypeCode + 1) * CollectionTypeConstrId).toByte + + /** Code of `Coll[Coll[_]]` type constructor. */ val NestedCollectionTypeConstrId = 2 + + /** Type code for `Coll[Coll[T]] for some T` type used in TypeSerializer. */ val NestedCollectionTypeCode: TypeCode = ((SPrimType.MaxPrimTypeCode + 1) * NestedCollectionTypeConstrId).toByte + + /** Array of generic type parameters reused in all SCollectionType instances. */ val typeParams: Seq[STypeParam] = Array(SType.paramIV) } @@ -1350,13 +1399,16 @@ object SCollection extends STypeCompanion with MethodByNameUnapply { import SType.{tK, tV, paramIV, paramIVSeq, paramOV} + /** Helper descriptors reused across different method descriptors. */ def tIV = SType.tIV def tOV = SType.tOV + /** This descriptors are instantiated once here and then reused. */ val ThisType = SCollection(tIV) val tOVColl = SCollection(tOV) val tPredicate = SFunc(tIV, SBoolean) + /** The following SMethod instances are descriptors of methods defined in `Coll` type. */ val SizeMethod = SMethod(this, "size", SFunc(ThisType, SInt), 1, SizeOf.costKind) .withInfo(SizeOf, "The size of the collection in elements.") @@ -1380,6 +1432,10 @@ object SCollection extends STypeCompanion with MethodByNameUnapply { """.stripMargin, ArgInfo("f", "the function to apply to each element")) + /** Implements evaluation of Coll.map method call ErgoTree node. + * Called via reflection based on naming convention. + * @see SMethod.evalMethod + */ def map_eval[A,B](mc: MethodCall, xs: Coll[A], f: A => B)(implicit E: ErgoTreeEvaluator): Coll[B] = { val tpeB = mc.tpe.asInstanceOf[SCollection[SType]].elemType val tB = Evaluation.stypeToRType(tpeB).asInstanceOf[RType[B]] @@ -1466,6 +1522,10 @@ object SCollection extends STypeCompanion with MethodByNameUnapply { | containing [0 .. length-1] values. """.stripMargin) + /** Implements evaluation of Coll.indices method call ErgoTree node. + * Called via reflection based on naming convention. + * @see SMethod.evalMethod + */ def indices_eval[A, B](mc: MethodCall, xs: Coll[A]) (implicit E: ErgoTreeEvaluator): Coll[Int] = { val m = mc.method @@ -1541,9 +1601,11 @@ object SCollection extends STypeCompanion with MethodByNameUnapply { found } + /** Operation descriptor for matching `flatMap` method calls with valid lambdas. */ final val MatchSingleArgMethodCall_Info = OperationCostInfo( FixedCost(30), NamedDesc("MatchSingleArgMethodCall")) + /** Recognizer of `flatMap` method calls with valid lambdas. */ object IsSingleArgMethodCall { def unapply(mc:MethodCall) (implicit E: ErgoTreeEvaluator): Nullable[(Int, SValue)] = { @@ -1561,6 +1623,7 @@ object SCollection extends STypeCompanion with MethodByNameUnapply { } } + /** Checks that the given [[MethodCall]] operation is valid flatMap. */ def checkValidFlatmap(mc: MethodCall)(implicit E: ErgoTreeEvaluator) = { mc match { case IsSingleArgMethodCall(varId, lambdaBody) @@ -1572,6 +1635,10 @@ object SCollection extends STypeCompanion with MethodByNameUnapply { } } + /** Implements evaluation of Coll.flatMap method call ErgoTree node. + * Called via reflection based on naming convention. + * @see SMethod.evalMethod + */ def flatMap_eval[A, B](mc: MethodCall, xs: Coll[A], f: A => Coll[B]) (implicit E: ErgoTreeEvaluator): Coll[B] = { checkValidFlatmap(mc) @@ -1590,8 +1657,13 @@ object SCollection extends STypeCompanion with MethodByNameUnapply { SFunc(Array(ThisType, SInt, ThisType, SInt), ThisType, paramIVSeq), 19, PerItemCost(30, 2, 10)) .withIRInfo(MethodCallIrBuilder) - .withInfo(MethodCall, "") + .withInfo(MethodCall, + "Produces a new Coll where a slice of elements in this Coll is replaced by another Coll.") + /** Implements evaluation of Coll.patch method call ErgoTree node. + * Called via reflection based on naming convention. + * @see SMethod.evalMethod + */ def patch_eval[A](mc: MethodCall, xs: Coll[A], from: Int, patch: Coll[A], replaced: Int) (implicit E: ErgoTreeEvaluator): Coll[A] = { val m = mc.method @@ -1605,8 +1677,13 @@ object SCollection extends STypeCompanion with MethodByNameUnapply { SFunc(Array(ThisType, SInt, tIV), ThisType, paramIVSeq), 20, PerItemCost(20, 1, 10)) .withIRInfo(MethodCallIrBuilder, javaMethodOf[Coll[_], Int, Any]("updated")) - .withInfo(MethodCall, "") + .withInfo(MethodCall, + "A copy of this Coll with one single replaced element.") + /** Implements evaluation of Coll.updated method call ErgoTree node. + * Called via reflection based on naming convention. + * @see SMethod.evalMethod + */ def updated_eval[A](mc: MethodCall, coll: Coll[A], index: Int, elem: A) (implicit E: ErgoTreeEvaluator): Coll[A] = { val m = mc.method @@ -1621,6 +1698,10 @@ object SCollection extends STypeCompanion with MethodByNameUnapply { 21, PerItemCost(20, 2, 10)) .withIRInfo(MethodCallIrBuilder).withInfo(MethodCall, "") + /** Implements evaluation of Coll.updateMany method call ErgoTree node. + * Called via reflection based on naming convention. + * @see SMethod.evalMethod + */ def updateMany_eval[A](mc: MethodCall, coll: Coll[A], indexes: Coll[Int], values: Coll[A]) (implicit E: ErgoTreeEvaluator): Coll[A] = { val costKind = mc.method.costKind.asInstanceOf[PerItemCost] @@ -1635,7 +1716,10 @@ object SCollection extends STypeCompanion with MethodByNameUnapply { .withInfo(MethodCall, "") // TODO v5.0: optimize using specialization for numeric and predefined types - /** This method is called via Reflection as part of evaluating the given MethodCall. */ + /** Implements evaluation of Coll.indexOf method call ErgoTree node. + * Called via reflection based on naming convention. + * @see SMethod.evalMethod + */ def indexOf_eval[A](mc: MethodCall, xs: Coll[A], elem: A, from: Int) (implicit E: ErgoTreeEvaluator): Int = { val costKind = mc.method.costKind.asInstanceOf[PerItemCost] @@ -1657,6 +1741,7 @@ object SCollection extends STypeCompanion with MethodByNameUnapply { res } + /** Cost descriptor of Coll.zip operation. */ val Zip_CostKind = PerItemCost(baseCost = 10, perChunkCost = 1, chunkSize = 10) val ZipMethod = SMethod(this, "zip", @@ -1665,6 +1750,10 @@ object SCollection extends STypeCompanion with MethodByNameUnapply { .withIRInfo(MethodCallIrBuilder) .withInfo(MethodCall, "") + /** Implements evaluation of Coll.zip method call ErgoTree node. + * Called via reflection based on naming convention. + * @see SMethod.evalMethod + */ def zip_eval[A, B](mc: MethodCall, xs: Coll[A], ys: Coll[B]) (implicit E: ErgoTreeEvaluator): Coll[(A,B)] = { val m = mc.method @@ -1673,7 +1762,7 @@ object SCollection extends STypeCompanion with MethodByNameUnapply { } } - lazy val methods: Seq[SMethod] = Seq( + override lazy val methods: Seq[SMethod] = Seq( SizeMethod, GetOrElseMethod, MapMethod, @@ -1692,6 +1781,8 @@ object SCollection extends STypeCompanion with MethodByNameUnapply { IndexOfMethod, ZipMethod ) + + /** Helper constructors. */ def apply[T <: SType](elemType: T): SCollection[T] = SCollectionType(elemType) def apply[T <: SType](implicit elemType: T, ov: Overload1): SCollection[T] = SCollectionType(elemType) @@ -1705,6 +1796,7 @@ object SCollection extends STypeCompanion with MethodByNameUnapply { type SBoxArray = SCollection[SBox.type] type SAvlTreeArray = SCollection[SAvlTree.type] + /** This descriptors are instantiated once here and then reused. */ val SBooleanArray = SCollection(SBoolean) val SByteArray = SCollection(SByte) val SByteArray2 = SCollection(SCollection(SByte)) @@ -1719,6 +1811,7 @@ object SCollection extends STypeCompanion with MethodByNameUnapply { val SHeaderArray = SCollection(SHeader) } +/** Type descriptor of tuple type. */ case class STuple(items: IndexedSeq[SType]) extends SCollection[SAny.type] { import STuple._ override val typeCode = STuple.TupleTypeCode @@ -1729,12 +1822,15 @@ case class STuple(items: IndexedSeq[SType]) extends SCollection[SAny.type] { @volatile private var _isConstantSizeCode: Byte = 0.toByte - /** use lazy pattern to support O(1) amortized complexity over n invocations. */ + /** use lazy pattern to support O(1) amortized complexity over n invocations. + * Not thread safe! + */ override def isConstantSize: Boolean = { val code = _isConstantSizeCode if (code == 0) { val len = items.length var isConst: Boolean = true + // looking for a first non-const item type, or run out of items cfor(0)(_ < len && isConst, _ + 1) { i => val t = items(i) isConst = t.isConstantSize @@ -1782,44 +1878,56 @@ case class STuple(items: IndexedSeq[SType]) extends SCollection[SAny.type] { colMethods ++ tupleMethods } - val typeParams = Nil + override val typeParams = Nil override def toTermString = s"(${items.map(_.toTermString).mkString(",")})" override def toString = s"(${items.mkString(",")})" } object STuple extends STypeCompanion { + /** Code of `(_, T) for some embeddable T` type constructor. */ val Pair1TypeConstrId = 5 + /** Type code for `(E, T) for some embeddable T` type used in TypeSerializer. */ val Pair1TypeCode: TypeCode = ((SPrimType.MaxPrimTypeCode + 1) * Pair1TypeConstrId).toByte + /** Code of `(T, _) for some embeddable T` type constructor. */ val Pair2TypeConstrId = 6 + /** Type code for `(T, E) for some embeddable T` type used in TypeSerializer. */ val Pair2TypeCode: TypeCode = ((SPrimType.MaxPrimTypeCode + 1) * Pair2TypeConstrId).toByte val TripleTypeCode: TypeCode = Pair2TypeCode + /** Type constructor code of symmetric pair `(T, T)` for some embeddable T. */ val PairSymmetricTypeConstrId = 7 + /** Type code of symmetric pair `(T, T)` for some embeddable T. */ val PairSymmetricTypeCode: TypeCode = ((SPrimType.MaxPrimTypeCode + 1) * PairSymmetricTypeConstrId).toByte val QuadrupleTypeCode: TypeCode = PairSymmetricTypeCode + /** Type code of generic tuple type. */ val TupleTypeCode = ((SPrimType.MaxPrimTypeCode + 1) * 8).toByte - def typeId = TupleTypeCode + override def typeId = TupleTypeCode override val reprClass: Class[_] = classOf[Product2[_,_]] - lazy val colMethods = { + /** A list of Coll methods inherited from Coll type and available as method of tuple. */ + lazy val colMethods: Seq[SMethod] = { val subst = Map(SType.tIV -> SAny) - // TODO: implement other - val activeMethods = Set(1.toByte, 10.toByte) + // TODO: implement other methods + val activeMethods = Set(1.toByte /*Coll.size*/, 10.toByte /*Coll.apply*/) SCollection.methods.filter(m => activeMethods.contains(m.methodId)).map { m => m.copy(stype = SigmaTyper.applySubst(m.stype, subst).asFunc) } } - def methods: Seq[SMethod] = sys.error(s"Shouldn't be called.") + override def methods: Seq[SMethod] = sys.error(s"Shouldn't be called.") + /** Helper factory method. */ def apply(items: SType*): STuple = STuple(items.toArray) - val MaxTupleLength: Int = SigmaConstants.MaxTupleLength.value + + private val MaxTupleLength: Int = SigmaConstants.MaxTupleLength.value private val componentNames = Array.tabulate(MaxTupleLength){ i => s"_${i + 1}" } + + /** Returns method name for the tuple component accessor (i.e. `_1`, `_2`, etc.) */ def componentNameByIndex(i: Int): String = try componentNames(i) catch { @@ -1838,6 +1946,7 @@ object SPair { } } +/** Type descriptor of lambda types. */ case class SFunc(tDom: IndexedSeq[SType], tRange: SType, tpeParams: Seq[STypeParam] = Nil) extends SType with SGenericType { @@ -1854,14 +1963,19 @@ case class SFunc(tDom: IndexedSeq[SType], tRange: SType, tpeParams: Seq[STypePa } override def dataSize(v: SType#WrappedType) = 8L import SFunc._ - val typeParams: Seq[STypeParam] = tpeParams + override val typeParams: Seq[STypeParam] = tpeParams + /** Generalize this type and return a new descriptor. */ def getGenericType: SFunc = { val typeParams: Seq[STypeParam] = tDom.zipWithIndex .map { case (_, i) => STypeParam(SType.tD.name + (i + 1)) } :+ STypeParam(SType.tR.name) val ts = typeParams.map(_.ident) SFunc(ts.init.toIndexedSeq, ts.last, Nil) } + + /** Transform function into method type by adding the given `objType` as the first + * argument type (aka method receiver type). + */ def withReceiverType(objType: SType) = this.copy(tDom = objType +: tDom) } @@ -1871,7 +1985,9 @@ object SFunc { val identity = { x: Any => x } } - +/** Used by ErgoScript compiler IR and eliminated during compilation. + * It is not used in ErgoTree. + */ case class STypeApply(name: String, args: IndexedSeq[SType] = IndexedSeq()) extends SType { override type WrappedType = Any override val typeCode = STypeApply.TypeCode @@ -1883,7 +1999,10 @@ object STypeApply { val TypeCode = 94: Byte } -/** Type variable which is used in generic method/func signatures. */ +/** Type variable which is used in generic method/func signatures. + * Used by ErgoScript compiler IR and eliminated during compilation. + * It is not used in ErgoTree. + */ case class STypeVar(name: String) extends SType { require(name.length <= 255, "name is too long") override type WrappedType = Any @@ -1905,6 +2024,7 @@ object STypeVar { val EmptySeq: IndexedSeq[STypeVar] = EmptyArray } +/** Type descriptor of `Box` type of ErgoTree. */ case object SBox extends SProduct with SPredefType with SMonoType { import ErgoBox._ override type WrappedType = Box @@ -1919,8 +2039,10 @@ case object SBox extends SProduct with SPredefType with SMonoType { import SType.{tT, paramT} + /** Defined once here and then reused in SMethod descriptors. */ lazy val GetRegFuncType = SFunc(Array(SBox), SOption(tT), Array(paramT)) + /** Creates a descriptor for the given register method. (i.e. R1, R2, etc) */ def registers(idOfs: Int): Seq[SMethod] = { allRegisters.map { i => i match { @@ -2007,6 +2129,7 @@ case object SBox extends SProduct with SPredefType with SMonoType { override val coster = Some(Coster(_.BoxCoster)) } +/** Type descriptor of `AvlTree` type of ErgoTree. */ case object SAvlTree extends SProduct with SPredefType with SMonoType { override type WrappedType = AvlTree override val typeCode: TypeCode = 100: Byte @@ -2026,6 +2149,7 @@ case object SAvlTree extends SProduct with SPredefType with SMonoType { | Authenticated tree \lst{digest} = \lst{root hash bytes} ++ \lst{tree height} """.stripMargin) + /** Cost descriptor of `digest` method. */ lazy val digest_Info = { val m = digestMethod OperationCostInfo(m.costKind.asInstanceOf[FixedCost], m.opDesc) @@ -2140,6 +2264,7 @@ case object SAvlTree extends SProduct with SPredefType with SMonoType { final val RemoveAvlTree_Info = OperationCostInfo( PerItemCost(100, 15, 1), NamedDesc("RemoveAvlTree")) + /** Creates [[AvlTreeVerifier]] for the given tree and proof. */ def createVerifier(tree: AvlTree, proof: Coll[Byte])(implicit E: ErgoTreeEvaluator) = { // the cost of tree reconstruction from proof is O(proof.length) E.addSeqCost(CreateAvlVerifier_Info, proof.length) { () => @@ -2147,6 +2272,10 @@ case object SAvlTree extends SProduct with SPredefType with SMonoType { } } + /** Implements evaluation of AvlTree.contains method call ErgoTree node. + * Called via reflection based on naming convention. + * @see SMethod.evalMethod + */ def contains_eval(mc: MethodCall, tree: AvlTree, key: Coll[Byte], proof: Coll[Byte]) (implicit E: ErgoTreeEvaluator): Boolean = { val bv = createVerifier(tree, proof) @@ -2183,6 +2312,10 @@ case object SAvlTree extends SProduct with SPredefType with SMonoType { | """.stripMargin) + /** Implements evaluation of AvlTree.get method call ErgoTree node. + * Called via reflection based on naming convention. + * @see SMethod.evalMethod + */ def get_eval(mc: MethodCall, tree: AvlTree, key: Coll[Byte], proof: Coll[Byte]) (implicit E: ErgoTreeEvaluator): Option[Coll[Byte]] = { val bv = createVerifier(tree, proof) @@ -2215,6 +2348,10 @@ case object SAvlTree extends SProduct with SPredefType with SMonoType { | """.stripMargin) + /** Implements evaluation of AvlTree.getMany method call ErgoTree node. + * Called via reflection based on naming convention. + * @see SMethod.evalMethod + */ def getMany_eval(mc: MethodCall, tree: AvlTree, keys: Coll[Coll[Byte]], proof: Coll[Byte]) (implicit E: ErgoTreeEvaluator): Coll[Option[Coll[Byte]]] = { val bv = createVerifier(tree, proof) @@ -2250,6 +2387,10 @@ case object SAvlTree extends SProduct with SPredefType with SMonoType { | """.stripMargin) + /** Implements evaluation of AvlTree.insert method call ErgoTree node. + * Called via reflection based on naming convention. + * @see SMethod.evalMethod + */ def insert_eval(mc: MethodCall, tree: AvlTree, entries: Coll[(Coll[Byte], Coll[Byte])], proof: Coll[Byte]) (implicit E: ErgoTreeEvaluator): Option[AvlTree] = { E.addCost(isInsertAllowed_Info) @@ -2301,6 +2442,10 @@ case object SAvlTree extends SProduct with SPredefType with SMonoType { | """.stripMargin) + /** Implements evaluation of AvlTree.update method call ErgoTree node. + * Called via reflection based on naming convention. + * @see SMethod.evalMethod + */ def update_eval(mc: MethodCall, tree: AvlTree, operations: Coll[(Coll[Byte], Coll[Byte])], proof: Coll[Byte]) (implicit E: ErgoTreeEvaluator): Option[AvlTree] = { @@ -2349,6 +2494,10 @@ case object SAvlTree extends SProduct with SPredefType with SMonoType { | """.stripMargin) + /** Implements evaluation of AvlTree.remove method call ErgoTree node. + * Called via reflection based on naming convention. + * @see SMethod.evalMethod + */ def remove_eval(mc: MethodCall, tree: AvlTree, operations: Coll[Coll[Byte]], proof: Coll[Byte]) (implicit E: ErgoTreeEvaluator): Option[AvlTree] = { @@ -2410,6 +2559,7 @@ case object SAvlTree extends SProduct with SPredefType with SMonoType { override val coster = Some(Coster(_.AvlTreeCoster)) } +/** Type descriptor of `Context` type of ErgoTree. */ case object SContext extends SProduct with SPredefType with SMonoType { override type WrappedType = Context override val typeCode: TypeCode = 101: Byte @@ -2448,6 +2598,7 @@ case object SContext extends SProduct with SPredefType with SMonoType { override val coster = Some(Coster(_.ContextCoster)) } +/** Type descriptor of `Header` type of ErgoTree. */ case object SHeader extends SProduct with SPredefType with SMonoType { override type WrappedType = Header override val typeCode: TypeCode = 104: Byte @@ -2498,6 +2649,7 @@ case object SHeader extends SProduct with SPredefType with SMonoType { override val coster = Some(Coster(_.HeaderCoster)) } +/** Type descriptor of `PreHeader` type of ErgoTree. */ case object SPreHeader extends SProduct with SPredefType with SMonoType { override type WrappedType = PreHeader override val typeCode: TypeCode = 105: Byte From 0bfa6319811bed9c77af87ddc8871bda91f084a2 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Wed, 27 Oct 2021 15:00:17 +0300 Subject: [PATCH 42/56] division-remainder: ExactIntegral don't inherit from Intergral, renaming `rem` --- .../src/main/scala/scalan/ExactIntegral.scala | 35 +++++++++++-------- .../scala/scalan/primitives/NumericOps.scala | 2 +- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/common/src/main/scala/scalan/ExactIntegral.scala b/common/src/main/scala/scalan/ExactIntegral.scala index c8cbc0d861..93c5f5056d 100644 --- a/common/src/main/scala/scalan/ExactIntegral.scala +++ b/common/src/main/scala/scalan/ExactIntegral.scala @@ -11,45 +11,50 @@ import scala.math.Numeric.{ByteIsIntegral, LongIsIntegral, ShortIsIntegral, IntI * standard Scala library. * This trait is used in core IR to avoid implicitly using standard scala implementations. */ -trait ExactIntegral[T] extends Integral[T] { +trait ExactIntegral[T] { val n: Integral[T] - override def quot(x: T, y: T): T = n.quot(x, y) - override def rem(x: T, y: T): T = n.rem(x, y) - override def negate(x: T): T = n.negate(x) - override def fromInt(x: Int): T = n.fromInt(x) - override def toInt(x: T): Int = n.toInt(x) - override def toLong(x: T): Long = n.toLong(x) - override def toFloat(x: T): Float = n.toFloat(x) - override def toDouble(x: T): Double = n.toDouble(x) - override def compare(x: T, y: T): Int = n.compare(x, y) + def zero = fromInt(0) + def one = fromInt(1) + def plus(x: T, y: T): T = n.plus(x, y) + def minus(x: T, y: T): T = n.minus(x, y) + def times(x: T, y: T): T = n.times(x, y) + def quot(x: T, y: T): T = n.quot(x, y) + def divisionRemainder(x: T, y: T): T = n.rem(x, y) + def negate(x: T): T = n.negate(x) + def fromInt(x: Int): T = n.fromInt(x) + def toInt(x: T): Int = n.toInt(x) + def toLong(x: T): Long = n.toLong(x) + def toFloat(x: T): Float = n.toFloat(x) + def toDouble(x: T): Double = n.toDouble(x) + def compare(x: T, y: T): Int = n.compare(x, y) } -/** ExactNumeric instances for all types. */ +/** ExactIntegral instances for all types. */ object ExactIntegral { implicit object ByteIsExactIntegral extends ExactIntegral[Byte] { - val n = ByteIsIntegral + val n = scala.math.Numeric.ByteIsIntegral override def plus(x: Byte, y: Byte): Byte = x.addExact(y) override def minus(x: Byte, y: Byte): Byte = x.subtractExact(y) override def times(x: Byte, y: Byte): Byte = x.multiplyExact(y) } implicit object ShortIsExactIntegral extends ExactIntegral[Short] { - val n = ShortIsIntegral + val n = scala.math.Numeric.ShortIsIntegral override def plus(x: Short, y: Short): Short = x.addExact(y) override def minus(x: Short, y: Short): Short = x.subtractExact(y) override def times(x: Short, y: Short): Short = x.multiplyExact(y) } implicit object IntIsExactIntegral extends ExactIntegral[Int] { - val n = IntIsIntegral + val n = scala.math.Numeric.IntIsIntegral override def plus(x: Int, y: Int): Int = java7.compat.Math.addExact(x, y) override def minus(x: Int, y: Int): Int = java7.compat.Math.subtractExact(x, y) override def times(x: Int, y: Int): Int = java7.compat.Math.multiplyExact(x, y) } implicit object LongIsExactIntegral extends ExactIntegral[Long] { - val n = LongIsIntegral + val n = scala.math.Numeric.LongIsIntegral override def plus(x: Long, y: Long): Long = java7.compat.Math.addExact(x, y) override def minus(x: Long, y: Long): Long = java7.compat.Math.subtractExact(x, y) override def times(x: Long, y: Long): Long = java7.compat.Math.multiplyExact(x, y) diff --git a/core/src/main/scala/scalan/primitives/NumericOps.scala b/core/src/main/scala/scalan/primitives/NumericOps.scala index deca140dc4..bcc7b9a3ff 100644 --- a/core/src/main/scala/scalan/primitives/NumericOps.scala +++ b/core/src/main/scala/scalan/primitives/NumericOps.scala @@ -95,7 +95,7 @@ case class IntegralMod[T](i: ExactIntegral[T])(implicit elem: Elem[T]) extends D * numeric types. * @see sigmastate.eval.NumericOps.BigIntIsIntegral */ - override def applySeq(x: T, y: T): T = i.rem(x, y) + override def applySeq(x: T, y: T): T = i.divisionRemainder(x, y) } /** Compares the given value with zero of the given ExactNumeric instance. */ From f553bf148d2059662da981d61ea5f5cecb5365ee Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Thu, 28 Oct 2021 18:09:11 +0300 Subject: [PATCH 43/56] division-remainder: ExactIntegral and ExactNumeric instances --- .../src/main/scala/scalan/ExactIntegral.scala | 42 ++++++------- .../src/main/scala/scalan/ExactNumeric.scala | 63 ++++++++----------- 2 files changed, 48 insertions(+), 57 deletions(-) diff --git a/common/src/main/scala/scalan/ExactIntegral.scala b/common/src/main/scala/scalan/ExactIntegral.scala index 93c5f5056d..70dcb48621 100644 --- a/common/src/main/scala/scalan/ExactIntegral.scala +++ b/common/src/main/scala/scalan/ExactIntegral.scala @@ -2,31 +2,31 @@ package scalan import scalan.util.Extensions._ -import scala.math.Numeric.{ByteIsIntegral, LongIsIntegral, ShortIsIntegral, IntIsIntegral} - -/** Integral operations with overflow checks. - * Raise exception when overflow is detected. - * Each instance of this typeclass overrides three methods `plus`, `minus`, `times`. - * All other methods are implemented by delegating to the corresponding Integral instance from - * standard Scala library. +/** Type-class which defines the operations on Integral types (Byte, Short, Int, Long) + * with overflow checks. + * + * An exception is raised when an overflow is detected. + * Each concrete instance of this type-class overrides three methods `plus`, `minus`, + * `times`. + * + * By default all the methods are implemented by delegating to the corresponding Integral + * instance from the standard Scala library. + * * This trait is used in core IR to avoid implicitly using standard scala implementations. */ -trait ExactIntegral[T] { - val n: Integral[T] - def zero = fromInt(0) - def one = fromInt(1) - def plus(x: T, y: T): T = n.plus(x, y) - def minus(x: T, y: T): T = n.minus(x, y) - def times(x: T, y: T): T = n.times(x, y) +trait ExactIntegral[T] extends ExactNumeric[T] { + protected val n: Integral[T] + + /** Integer division operation `x / y`. */ def quot(x: T, y: T): T = n.quot(x, y) + + /** Operation which returns reminder from dividing x by y. + * The exact rules are defined in the concrete instance of the type T. + * A default implementation delegates to Integral[T].rem method for the corresponding + * type T. + * The default implementation can be overridden for any concrete type T. + */ def divisionRemainder(x: T, y: T): T = n.rem(x, y) - def negate(x: T): T = n.negate(x) - def fromInt(x: Int): T = n.fromInt(x) - def toInt(x: T): Int = n.toInt(x) - def toLong(x: T): Long = n.toLong(x) - def toFloat(x: T): Float = n.toFloat(x) - def toDouble(x: T): Double = n.toDouble(x) - def compare(x: T, y: T): Int = n.compare(x, y) } /** ExactIntegral instances for all types. */ diff --git a/common/src/main/scala/scalan/ExactNumeric.scala b/common/src/main/scala/scalan/ExactNumeric.scala index a16e87e4a5..8cd3f13c11 100644 --- a/common/src/main/scala/scalan/ExactNumeric.scala +++ b/common/src/main/scala/scalan/ExactNumeric.scala @@ -1,8 +1,6 @@ package scalan -import scalan.util.Extensions._ - -import scala.math.Numeric.{ByteIsIntegral, LongIsIntegral, ShortIsIntegral, IntIsIntegral} +import scalan.ExactIntegral._ /** Numeric operations with overflow checks. * Raise exception when overflow is detected. @@ -12,50 +10,43 @@ import scala.math.Numeric.{ByteIsIntegral, LongIsIntegral, ShortIsIntegral, IntI * This trait is used in core IR to avoid implicitly using standard scala implementations */ trait ExactNumeric[T] { - val n: Numeric[T] + protected val n: Numeric[T] + + /** Addition operation `x + y`. */ def plus(x: T, y: T): T + + /** Subtraction operation `x - y`. */ def minus(x: T, y: T): T + + /** Multiplication operation `x * y`. */ def times(x: T, y: T): T + + /** Returns negative value `-x`. */ def negate(x: T): T = n.negate(x) + + /** Returns a value of type T, which corresponds to the given integer value `x`. */ def fromInt(x: Int): T = n.fromInt(x) + def toInt(x: T): Int = n.toInt(x) def toLong(x: T): Long = n.toLong(x) def toFloat(x: T): Float = n.toFloat(x) def toDouble(x: T): Double = n.toDouble(x) - def compare(x: T, y: T): Int = n.compare(x, y) - def zero: T = n.zero - def one: T = n.one + + /** A value of type T which corresponds to integer 0. */ + lazy val zero: T = fromInt(0) + + /** A value of type T which corresponds to integer 1. */ + lazy val one: T = fromInt(1) + def abs(x: T): T = n.abs(x) } -/** ExactNumeric instances for all types. */ object ExactNumeric { - - implicit object ByteIsExactNumeric extends ExactNumeric[Byte] { - val n = ByteIsIntegral - override def plus(x: Byte, y: Byte): Byte = x.addExact(y) - override def minus(x: Byte, y: Byte): Byte = x.subtractExact(y) - override def times(x: Byte, y: Byte): Byte = x.multiplyExact(y) - } - - implicit object ShortIsExactNumeric extends ExactNumeric[Short] { - val n = ShortIsIntegral - override def plus(x: Short, y: Short): Short = x.addExact(y) - override def minus(x: Short, y: Short): Short = x.subtractExact(y) - override def times(x: Short, y: Short): Short = x.multiplyExact(y) - } - - implicit object IntIsExactNumeric extends ExactNumeric[Int] { - val n = IntIsIntegral - override def plus(x: Int, y: Int): Int = java7.compat.Math.addExact(x, y) - override def minus(x: Int, y: Int): Int = java7.compat.Math.subtractExact(x, y) - override def times(x: Int, y: Int): Int = java7.compat.Math.multiplyExact(x, y) - } - - implicit object LongIsExactNumeric extends ExactNumeric[Long] { - val n = LongIsIntegral - override def plus(x: Long, y: Long): Long = java7.compat.Math.addExact(x, y) - override def minus(x: Long, y: Long): Long = java7.compat.Math.subtractExact(x, y) - override def times(x: Long, y: Long): Long = java7.compat.Math.multiplyExact(x, y) - } + /** ExactNumeric instances for all types are the same as ExactIntegral. + * This values are implicitly used wherever ExactNumeric is needed. + */ + implicit val ByteIsExactNumeric: ExactNumeric[Byte] = ByteIsExactIntegral + implicit val ShortIsExactNumeric: ExactNumeric[Short] = ShortIsExactIntegral + implicit val IntIsExactNumeric: ExactNumeric[Int] = IntIsExactIntegral + implicit val LongIsExactNumeric: ExactNumeric[Long] = LongIsExactIntegral } From d5a93477570b5cf39a8437bbbda167b647f90a4f Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Thu, 28 Oct 2021 18:31:40 +0300 Subject: [PATCH 44/56] division-remainder: BigIntIsExactIntegral instance added --- .../scala/sigmastate/eval/BigIntegerOps.scala | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/eval/BigIntegerOps.scala b/sigmastate/src/main/scala/sigmastate/eval/BigIntegerOps.scala index 052ebef44e..e2b97e22f7 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/BigIntegerOps.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/BigIntegerOps.scala @@ -2,7 +2,7 @@ package sigmastate.eval import java.math.BigInteger -import scalan.{ExactNumeric, ExactOrderingImpl} +import scalan.{ExactNumeric, ExactOrderingImpl, ExactIntegral} import scala.math.{Integral, Ordering} import special.sigma._ @@ -24,21 +24,7 @@ object OrderingOps { object NumericOps { - trait BigIntegerIsIntegral extends Integral[BigInteger] { - def quot(x: BigInteger, y: BigInteger): BigInteger = x.divide(y) - def rem(x: BigInteger, y: BigInteger): BigInteger = x.remainder(y) - def plus(x: BigInteger, y: BigInteger): BigInteger = x.add(y) - def minus(x: BigInteger, y: BigInteger): BigInteger = x.subtract(y) - def times(x: BigInteger, y: BigInteger): BigInteger = x.multiply(y) - def negate(x: BigInteger): BigInteger = x.negate() - def fromInt(x: Int): BigInteger = BigInteger.valueOf(x) - def toInt(x: BigInteger): Int = x.intValueExact() - def toLong(x: BigInteger): Long = x.longValueExact() - def toFloat(x: BigInteger): Float = x.floatValue() - def toDouble(x: BigInteger): Double = x.doubleValue() - } - implicit object BigIntegerIsIntegral extends BigIntegerIsIntegral with OrderingOps.BigIntegerOrdering - + /** Base implementation of Integral methods for BigInt. */ trait BigIntIsIntegral extends Integral[BigInt] { def quot(x: BigInt, y: BigInt): BigInt = ??? // this method should not be used in v4.x def rem(x: BigInt, y: BigInt): BigInt = ??? // this method should not be used in v4.x @@ -65,13 +51,23 @@ object NumericOps { */ implicit object BigIntIsIntegral extends BigIntIsIntegral with OrderingOps.BigIntOrdering - implicit object BigIntIsExactNumeric extends ExactNumeric[BigInt] { + /** The instance of [[ExactIntegral]] typeclass for [[BigInt]]. */ + implicit object BigIntIsExactIntegral extends ExactIntegral[BigInt] { val n = BigIntIsIntegral override def plus(x: BigInt, y: BigInt): BigInt = n.plus(x, y) override def minus(x: BigInt, y: BigInt): BigInt = n.minus(x, y) override def times(x: BigInt, y: BigInt): BigInt = n.times(x, y) + + override def quot(x: BigInt, y: BigInt): BigInt = + ??? // this method should not be used in v4.x + + override def divisionRemainder(x: BigInt, y: BigInt): BigInt = + ??? // this method should not be used in v4.x } + implicit val BigIntIsExactNumeric: ExactNumeric[BigInt] = BigIntIsExactIntegral + + /** The instance of [[scalan.ExactOrdering]] typeclass for [[BigInt]]. */ implicit object BigIntIsExactOrdering extends ExactOrderingImpl[BigInt](BigIntIsIntegral) } From 262a14d17ae3090fa97d1de1effd0b338562e4db Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Sun, 31 Oct 2021 11:22:40 +0300 Subject: [PATCH 45/56] prepare-v5.0-evaluator: addressing review comments --- .../scala/sigmastate/serialization/ErgoTreeSerializer.scala | 6 ++++-- sigmastate/src/main/scala/sigmastate/trees.scala | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala index c6c7977b27..4719633c98 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala @@ -256,8 +256,10 @@ class ErgoTreeSerializer { * should be replaced with new values * @param newVals new values to be injected into the corresponding * positions in ErgoTree.constants array - * @return original scriptBytes array where only specified constants are - * replaced and all other bytes remain exactly the same + * @return a pair (newBytes, len), where: + * newBytes - the original array scriptBytes such that only specified constants + * are replaced and all other bytes remain exactly the same + * len - length of the `constants` array of the given ErgoTree bytes */ def substituteConstants(scriptBytes: Array[Byte], positions: Array[Int], diff --git a/sigmastate/src/main/scala/sigmastate/trees.scala b/sigmastate/src/main/scala/sigmastate/trees.scala index 028cb712c9..99b0fa58b5 100644 --- a/sigmastate/src/main/scala/sigmastate/trees.scala +++ b/sigmastate/src/main/scala/sigmastate/trees.scala @@ -705,8 +705,8 @@ object CalcBlake2b256 extends SimpleTransformerCompanion { * * Thus per block cost of Blake2b256 hashing can be limited by 1 cost units. * However, on a less powerful processor it may take much more time, so we add - * a factor of 3 for that. Additionally, the interpreter have an overhead so that - * performing 1000 of hashes in a tight loop is 3-4 times faster then doing the same + * a factor of 3 for that. Additionally, the interpreter has overhead so that + * performing 1000 of hashes in a tight loop is 3-4 times faster than doing the same * via ErgoTreeEvaluator. Thus we should add another factor of 2 and this takes * place for all operations. So we will use a total factor of 10 to convert * actual operation micro-seconds time (obtained via benchmarking) to cost unit From 158809dd2422c51e92dd95a9a807ceb446e4aea7 Mon Sep 17 00:00:00 2001 From: Alex Chepurnoy Date: Sun, 31 Oct 2021 12:43:47 +0300 Subject: [PATCH 46/56] no scalanizer --- build.sbt | 31 ++----------------- .../src/main/resources/scalac-plugin.xml | 4 --- .../sigma/scalanizer/SigmaPlugin.scala | 19 ------------ 3 files changed, 2 insertions(+), 52 deletions(-) delete mode 100644 scalanizer/src/main/resources/scalac-plugin.xml delete mode 100644 scalanizer/src/main/scala/special/sigma/scalanizer/SigmaPlugin.scala diff --git a/build.sbt b/build.sbt index 7ec0e2c643..ed75d9f040 100644 --- a/build.sbt +++ b/build.sbt @@ -145,11 +145,7 @@ pgpSecretRing := file("ci/secring.asc") pgpPassphrase := sys.env.get("PGP_PASSPHRASE").map(_.toArray) usePgpKeyHex("C1FD62B4D44BDF702CDF2B726FF59DA944B150DD") -def libraryDefSettings = commonSettings ++ testSettings ++ Seq( - scalacOptions ++= Seq( -// s"-Xplugin:${file(".").absolutePath }/scalanizer/target/scala-2.12/scalanizer-assembly-core-opt-0d03a785-SNAPSHOT.jar" - ) -) +def libraryDefSettings = commonSettings ++ testSettings lazy val common = Project("common", file("common")) .settings(commonSettings ++ testSettings, @@ -197,29 +193,6 @@ lazy val sigmaconf = Project("sigma-conf", file("sigma-conf")) ) .settings(publish / skip := true) -lazy val scalanizer = Project("scalanizer", file("scalanizer")) - .dependsOn(sigmaconf, libraryapi, libraryimpl) - .settings(commonSettings, - libraryDependencies ++= ( - if(scalaBinaryVersion.value == "2.11") - Seq.empty - else - Seq(meta, plugin) - ), - skip in compile := scalaBinaryVersion.value == "2.11", - assemblyOption in assembly ~= { _.copy(includeScala = false, includeDependency = true) }, - assemblyMergeStrategy in assembly := { - case PathList("scalan", xs @ _*) => MergeStrategy.first - case other => (assemblyMergeStrategy in assembly).value(other) - }, - artifact in(Compile, assembly) := { - val art = (artifact in(Compile, assembly)).value - art.withClassifier(Some("assembly")) - }, - addArtifact(artifact in(Compile, assembly), assembly) - ) - .settings(publish / skip := true) - lazy val sigmaapi = Project("sigma-api", file("sigma-api")) .dependsOn(common, libraryapi) .settings(libraryDefSettings :+ addCompilerPlugin(paradise), @@ -267,7 +240,7 @@ lazy val sigmastate = (project in file("sigmastate")) lazy val sigma = (project in file(".")) .aggregate( sigmastate, common, core, libraryapi, libraryimpl, library, - sigmaapi, sigmaimpl, sigmalibrary, sigmaconf, scalanizer) + sigmaapi, sigmaimpl, sigmalibrary, sigmaconf) .settings(libraryDefSettings, rootSettings) .settings(publish / aggregate := false) .settings(publishLocal / aggregate := false) diff --git a/scalanizer/src/main/resources/scalac-plugin.xml b/scalanizer/src/main/resources/scalac-plugin.xml deleted file mode 100644 index 7dd58fc0bb..0000000000 --- a/scalanizer/src/main/resources/scalac-plugin.xml +++ /dev/null @@ -1,4 +0,0 @@ - - scalanizer - special.sigma.scalanizer.SigmaPlugin - \ No newline at end of file diff --git a/scalanizer/src/main/scala/special/sigma/scalanizer/SigmaPlugin.scala b/scalanizer/src/main/scala/special/sigma/scalanizer/SigmaPlugin.scala deleted file mode 100644 index fd5c45ee0c..0000000000 --- a/scalanizer/src/main/scala/special/sigma/scalanizer/SigmaPlugin.scala +++ /dev/null @@ -1,19 +0,0 @@ -package special.sigma.scalanizer - -import scala.tools.nsc.Global -import scalan.meta.{ConfMap, TargetModuleConf, SourceModuleConf} -import scalan.meta.scalanizer.ScalanizerConfig -import scalan.plugin.{ScalanizerPluginConfig, ScalanizerPlugin} -import special.sigma.config.SigmaLibraryConfig - -class SigmaPlugin(g: Global) extends ScalanizerPlugin(g) { plugin => - override def createScalanizerConfig(): ScalanizerConfig = new SigmaScalanizerConfig -} - -class SigmaScalanizerConfig extends ScalanizerPluginConfig { - val sigma = new SigmaLibraryConfig() - - /** Modules that contain units to be virtualized by scalan-meta. */ - override val sourceModules: ConfMap[SourceModuleConf] = ConfMap(sigma.sourceModules: _*) - override val targetModules: ConfMap[TargetModuleConf] = ConfMap(sigma.targetModules: _*) -} From 3dab7ddd5c502697b1f7bba9bbf3eac36ac6e6b1 Mon Sep 17 00:00:00 2001 From: Alex Chepurnoy Date: Tue, 2 Nov 2021 00:00:35 +0300 Subject: [PATCH 47/56] paradise dependency removed --- build.sbt | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/build.sbt b/build.sbt index 7ec0e2c643..f9505bcee6 100644 --- a/build.sbt +++ b/build.sbt @@ -1,5 +1,4 @@ import scala.language.postfixOps -import scala.util.Try import scala.sys.process._ organization := "org.scorexfoundation" @@ -56,7 +55,6 @@ val bouncycastleBcprov = "org.bouncycastle" % "bcprov-jdk15on" % "1.64" val scrypto = "org.scorexfoundation" %% "scrypto" % "2.1.10" val scorexUtil = "org.scorexfoundation" %% "scorex-util" % "0.1.8" val macroCompat = "org.typelevel" %% "macro-compat" % "1.1.1" -val paradise = "org.scalamacros" %% "paradise" % "2.1.0" cross CrossVersion.full val debox = "org.spire-math" %% "debox" % "0.8.0" val kiama = "org.bitbucket.inkytonik.kiama" %% "kiama" % "2.1.0" val fastparse = "com.lihaoyi" %% "fastparse" % "1.0.0" @@ -161,9 +159,8 @@ lazy val common = Project("common", file("common")) lazy val libraryapi = Project("library-api", file("library-api")) .dependsOn(common % allConfigDependency) - .settings(libraryDefSettings :+ addCompilerPlugin(paradise), - libraryDependencies ++= Seq( - )) + .settings(libraryDefSettings, + libraryDependencies ++= Seq()) .settings(publish / skip := true) lazy val libraryimpl = Project("library-impl", file("library-impl")) @@ -222,7 +219,7 @@ lazy val scalanizer = Project("scalanizer", file("scalanizer")) lazy val sigmaapi = Project("sigma-api", file("sigma-api")) .dependsOn(common, libraryapi) - .settings(libraryDefSettings :+ addCompilerPlugin(paradise), + .settings(libraryDefSettings, libraryDependencies ++= Seq( macroCompat, scrypto, bouncycastleBcprov )) From 5a575dc92af7a08b2b6a2df6c231ecb08c160b21 Mon Sep 17 00:00:00 2001 From: Alex Chepurnoy Date: Tue, 2 Nov 2021 00:09:12 +0300 Subject: [PATCH 48/56] macro-compat dependency removed --- build.sbt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index f9505bcee6..54871334ac 100644 --- a/build.sbt +++ b/build.sbt @@ -54,7 +54,6 @@ dynverSeparator in ThisBuild := "-" val bouncycastleBcprov = "org.bouncycastle" % "bcprov-jdk15on" % "1.64" val scrypto = "org.scorexfoundation" %% "scrypto" % "2.1.10" val scorexUtil = "org.scorexfoundation" %% "scorex-util" % "0.1.8" -val macroCompat = "org.typelevel" %% "macro-compat" % "1.1.1" val debox = "org.spire-math" %% "debox" % "0.8.0" val kiama = "org.bitbucket.inkytonik.kiama" %% "kiama" % "2.1.0" val fastparse = "com.lihaoyi" %% "fastparse" % "1.0.0" @@ -221,7 +220,7 @@ lazy val sigmaapi = Project("sigma-api", file("sigma-api")) .dependsOn(common, libraryapi) .settings(libraryDefSettings, libraryDependencies ++= Seq( - macroCompat, scrypto, bouncycastleBcprov + scrypto, bouncycastleBcprov )) .settings(publish / skip := true) From d07b59d3ec0057eb11d7aff703edd4fe0a0db2f1 Mon Sep 17 00:00:00 2001 From: Alex Chepurnoy Date: Thu, 4 Nov 2021 21:32:45 +0300 Subject: [PATCH 49/56] sigma-conf removed --- .../sigma/config/SigmaLibraryConfig.scala | 34 ------------------- 1 file changed, 34 deletions(-) delete mode 100644 sigma-conf/src/main/scala/special/sigma/config/SigmaLibraryConfig.scala diff --git a/sigma-conf/src/main/scala/special/sigma/config/SigmaLibraryConfig.scala b/sigma-conf/src/main/scala/special/sigma/config/SigmaLibraryConfig.scala deleted file mode 100644 index 2e93a3fb91..0000000000 --- a/sigma-conf/src/main/scala/special/sigma/config/SigmaLibraryConfig.scala +++ /dev/null @@ -1,34 +0,0 @@ -package special.sigma.config - -import special.library.config.SpecialLibraryConfig -import scalan.meta.ScalanAst.WrapperConf -import scalan.meta.{LibraryConfig, ConfMap, TargetModuleConf, SourceModuleConf} - -class SigmaLibraryConfig extends LibraryConfig { - def name = "sigma" - def baseDir = "" - val specialLibrary = new SpecialLibraryConfig - - def wrapperConfigs: Map[String, WrapperConf] = List[WrapperConf]( -// example wrapper declaration -// WrapperConf(baseDir, packageName = "special.sigma", name = "SigmaPredef", -// annotations = List(classOf[WithMethodCallRecognizers]).map(_.getSimpleName)) - ).map(w => (w.name, w)).toMap - - val ApiModule: SourceModuleConf = new SourceModuleConf(baseDir, "sigma-api") - .moduleDependencies(specialLibrary.ApiModule) - .addUnit("special/sigma/SigmaDsl.scala") - .addUnit("special/sigma/CostedObjects.scala") - - val ImplModule = new SourceModuleConf(baseDir, "sigma-impl") - .moduleDependencies(specialLibrary.ApiModule, specialLibrary.ImplModule) - .dependsOn(ApiModule) - - val TargetModule = new TargetModuleConf(baseDir, "sigma-library", - sourceModules = ConfMap() - .add(ApiModule) - .add(ImplModule)) - - def sourceModules = List(ApiModule, ImplModule) - def targetModules = List(TargetModule) -} From 5b05d3e5315616ad441b20337839dc7f0dd8a755 Mon Sep 17 00:00:00 2001 From: Alex Chepurnoy Date: Thu, 4 Nov 2021 21:40:52 +0300 Subject: [PATCH 50/56] unused dependencies removed --- build.sbt | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/build.sbt b/build.sbt index 11e47ee552..2dadab320c 100644 --- a/build.sbt +++ b/build.sbt @@ -60,11 +60,6 @@ val fastparse = "com.lihaoyi" %% "fastparse" % "1.0.0" val commonsIo = "commons-io" % "commons-io" % "2.5" val commonsMath3 = "org.apache.commons" % "commons-math3" % "3.2" -val specialVersion = "0.6.1" -val meta = "io.github.scalan" %% "meta" % specialVersion -val plugin = "io.github.scalan" %% "plugin" % specialVersion -val libraryconf = "io.github.scalan" %% "library-conf" % specialVersion - val testingDependencies = Seq( "org.scalatest" %% "scalatest" % "3.0.5" % "test", "org.scalactic" %% "scalactic" % "3.0.+" % "test", @@ -177,18 +172,6 @@ lazy val library = Project("library", file("library")) libraryDependencies ++= Seq( debox )) .settings(publish / skip := true) -lazy val sigmaconf = Project("sigma-conf", file("sigma-conf")) - .settings(commonSettings, - libraryDependencies ++= ( - if(scalaBinaryVersion.value == "2.11") - Seq.empty - else - Seq(plugin, libraryconf) - ), - skip in compile := scalaBinaryVersion.value == "2.11" - ) - .settings(publish / skip := true) - lazy val sigmaapi = Project("sigma-api", file("sigma-api")) .dependsOn(common, libraryapi) .settings(libraryDefSettings, @@ -236,7 +219,7 @@ lazy val sigmastate = (project in file("sigmastate")) lazy val sigma = (project in file(".")) .aggregate( sigmastate, common, core, libraryapi, libraryimpl, library, - sigmaapi, sigmaimpl, sigmalibrary, sigmaconf) + sigmaapi, sigmaimpl, sigmalibrary) .settings(libraryDefSettings, rootSettings) .settings(publish / aggregate := false) .settings(publishLocal / aggregate := false) @@ -312,7 +295,6 @@ commands += Command.command("ergoItTest") { state => def runSpamTestTask(task: String, sigmastateVersion: String, log: Logger): Unit = { val spamBranch = "master" val envVars = Seq("SIGMASTATE_VERSION" -> sigmastateVersion, - "SPECIAL_VERSION" -> specialVersion, // SSH_SPAM_REPO_KEY should be set (see Jenkins Credentials Binding Plugin) "GIT_SSH_COMMAND" -> "ssh -i $SSH_SPAM_REPO_KEY") From a72ab701db1e17842d4de1f60176547907c75d68 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Mon, 15 Nov 2021 13:24:40 +0300 Subject: [PATCH 51/56] division-remainder: more ScalaDocs --- .../src/main/scala/sigmastate/trees.scala | 39 ++++++++++++++----- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/trees.scala b/sigmastate/src/main/scala/sigmastate/trees.scala index 99b0fa58b5..6275255024 100644 --- a/sigmastate/src/main/scala/sigmastate/trees.scala +++ b/sigmastate/src/main/scala/sigmastate/trees.scala @@ -836,6 +836,7 @@ trait TwoArgumentOperationCompanion extends ValueCompanion { def argInfos: Seq[ArgInfo] } +/** Represents binary operation with the given opCode. */ case class ArithOp[T <: SType](left: Value[T], right: Value[T], override val opCode: OpCode) extends TwoArgumentsOperation[T, T, T] with NotReadyValue[T] { override def companion: ArithOpCompanion = ArithOp.operations(opCode) @@ -876,8 +877,10 @@ abstract class ArithOpCompanion(val opCode: OpCode, val name: String, _argInfos: object ArithOp { import OpCodes._ + + /** Addition operation `x + y`. */ object Plus extends ArithOpCompanion(PlusCode, "+", PlusInfo.argInfos) { - def eval(impl: OperationImpl, x: Any, y: Any): Any = impl.n.plus(x, y) + override def eval(impl: OperationImpl, x: Any, y: Any): Any = impl.n.plus(x, y) /** Cost of: * 1) resolving ArithOpCompanion by typeCode * 2) calling method of Numeric @@ -889,8 +892,10 @@ object ArithOp { } } } + + /** Subtraction operation `x - y`. */ object Minus extends ArithOpCompanion(MinusCode, "-", MinusInfo.argInfos) { - def eval(impl: OperationImpl, x: Any, y: Any): Any = impl.n.minus(x, y) + override def eval(impl: OperationImpl, x: Any, y: Any): Any = impl.n.minus(x, y) /** Cost of: * 1) resolving ArithOpCompanion by typeCode * 2) calling method of Numeric @@ -902,8 +907,10 @@ object ArithOp { } } } + + /** Multiplication operation `x * y`. */ object Multiply extends ArithOpCompanion(MultiplyCode, "*", MultiplyInfo.argInfos) { - def eval(impl: OperationImpl, x: Any, y: Any): Any = impl.n.times(x, y) + override def eval(impl: OperationImpl, x: Any, y: Any): Any = impl.n.times(x, y) /** Cost of: * 1) resolving ArithOpCompanion by typeCode * 2) calling method of Numeric @@ -915,8 +922,10 @@ object ArithOp { } } } + + /** Integer division operation `x / y`. */ object Division extends ArithOpCompanion(DivisionCode, "/", DivisionInfo.argInfos) { - def eval(impl: OperationImpl, x: Any, y: Any): Any = impl.i.quot(x, y) + override def eval(impl: OperationImpl, x: Any, y: Any): Any = impl.i.quot(x, y) /** Cost of: * 1) resolving ArithOpCompanion by typeCode * 2) calling method of Integral @@ -928,8 +937,12 @@ object ArithOp { } } } - object Modulo extends ArithOpCompanion(ModuloCode, "%", ModuloInfo.argInfos) { - def eval(impl: OperationImpl, x: Any, y: Any): Any = impl.i.rem(x, y) + + /** Operation which returns reminder from dividing x by y. + * See ExactIntegral.divisionRemainder implementation for the concrete numeric type. + */ + object Modulo extends ArithOpCompanion(ModuloCode, "%", ModuloInfo.argInfos) { + override def eval(impl: OperationImpl, x: Any, y: Any): Any = impl.i.divisionRemainder(x, y) /** Cost of: * 1) resolving ArithOpCompanion by typeCode * 2) calling method of Integral @@ -941,8 +954,10 @@ object ArithOp { } } } - object Min extends ArithOpCompanion(MinCode, "min", MinInfo.argInfos) { - def eval(impl: OperationImpl, x: Any, y: Any): Any = impl.o.min(x, y) + + /** Return `x` if `x` <= `y`, otherwise `y`. */ + object Min extends ArithOpCompanion(MinCode, "min", MinInfo.argInfos) { + override def eval(impl: OperationImpl, x: Any, y: Any): Any = impl.o.min(x, y) /** Cost of: * 1) resolving ArithOpCompanion by typeCode * 2) calling method of ExactOrdering @@ -954,8 +969,10 @@ object ArithOp { } } } - object Max extends ArithOpCompanion(MaxCode, "max", MaxInfo.argInfos) { - def eval(impl: OperationImpl, x: Any, y: Any): Any = impl.o.max(x, y) + + /** Return `x` if `x` >= `y`, otherwise `y`. */ + object Max extends ArithOpCompanion(MaxCode, "max", MaxInfo.argInfos) { + override def eval(impl: OperationImpl, x: Any, y: Any): Any = impl.o.max(x, y) /** Cost of: * 1) resolving ArithOpCompanion by typeCode * 2) calling method of ExactOrdering @@ -971,6 +988,7 @@ object ArithOp { private[sigmastate] val operations: DMap[Byte, ArithOpCompanion] = DMap.fromIterable(Seq(Plus, Minus, Multiply, Division, Modulo, Min, Max).map(o => (o.opCode, o))) + /** Represents implementation of numeric Arith operations for the given type argTpe. */ class OperationImpl(_n: ExactNumeric[_], _i: ExactIntegral[_], _o: ExactOrdering[_], val argTpe: SType) { val n = _n.asInstanceOf[ExactNumeric[Any]] val i = _i.asInstanceOf[ExactIntegral[Any]] @@ -986,6 +1004,7 @@ object ArithOp { SBigInt -> new OperationImpl(BigIntIsExactNumeric, BigIntIsExactIntegral, BigIntIsExactOrdering, SBigInt) ).map { case (t, n) => (t.typeCode, n) }) + /** Returns operation name for the given opCode. */ def opcodeToArithOpName(opCode: Byte): String = operations.get(opCode) match { case Some(c) => c.name case _ => sys.error(s"Cannot find ArithOpName for opcode $opCode") From 1044a7b6c9085534a8ff258b3b3e16dead30ba02 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Mon, 15 Nov 2021 14:31:44 +0300 Subject: [PATCH 52/56] division-remainder: removed ExactNumeric.toFloat/toDouble --- common/src/main/scala/scalan/ExactNumeric.scala | 2 -- core/src/main/scala/scalan/DefRewriting.scala | 4 ---- .../scala/scalan/compilation/GraphVizExport.scala | 2 -- .../main/scala/scalan/primitives/NumericOps.scala | 12 ------------ sigmastate/src/main/scala/sigmastate/trees.scala | 6 +++++- 5 files changed, 5 insertions(+), 21 deletions(-) diff --git a/common/src/main/scala/scalan/ExactNumeric.scala b/common/src/main/scala/scalan/ExactNumeric.scala index 8cd3f13c11..7ee5c27347 100644 --- a/common/src/main/scala/scalan/ExactNumeric.scala +++ b/common/src/main/scala/scalan/ExactNumeric.scala @@ -29,8 +29,6 @@ trait ExactNumeric[T] { def toInt(x: T): Int = n.toInt(x) def toLong(x: T): Long = n.toLong(x) - def toFloat(x: T): Float = n.toFloat(x) - def toDouble(x: T): Double = n.toDouble(x) /** A value of type T which corresponds to integer 0. */ lazy val zero: T = fromInt(0) diff --git a/core/src/main/scala/scalan/DefRewriting.scala b/core/src/main/scala/scalan/DefRewriting.scala index 3b88b1638e..f915239d46 100644 --- a/core/src/main/scala/scalan/DefRewriting.scala +++ b/core/src/main/scala/scalan/DefRewriting.scala @@ -60,10 +60,6 @@ trait DefRewriting { scalan: Scalan => case NumericToInt(_) if x.elem == IntElement => x // (x: Long).toLong => x case NumericToLong(_) if x.elem == LongElement => x - // (x: Float).toFloat => x - case NumericToFloat(_) if x.elem == FloatElement => x - // (x: Double).toDouble => x - case NumericToDouble(_) if x.elem == DoubleElement => x case _ if op == Not => x.node match { // Rule: !(x op y) ==> diff --git a/core/src/main/scala/scalan/compilation/GraphVizExport.scala b/core/src/main/scala/scalan/compilation/GraphVizExport.scala index 7c3b4e20bd..099593773a 100644 --- a/core/src/main/scala/scalan/compilation/GraphVizExport.scala +++ b/core/src/main/scala/scalan/compilation/GraphVizExport.scala @@ -104,8 +104,6 @@ trait GraphVizExport extends Base { self: Scalan => case Second(pair) => s"$pair._2" case ApplyBinOp(op, lhs, rhs) => s"$lhs ${op.opName} $rhs" case ApplyUnOp(op, arg) => op match { - case NumericToFloat(_) => s"$arg.toFloat" - case NumericToDouble(_) => s"$arg.toDouble" case NumericToInt(_) => s"$arg.toInt" case ToString() => s"$arg.toString" case HashCode() => s"$arg.hashCode" diff --git a/core/src/main/scala/scalan/primitives/NumericOps.scala b/core/src/main/scala/scalan/primitives/NumericOps.scala index bcc7b9a3ff..2b6f06db91 100644 --- a/core/src/main/scala/scalan/primitives/NumericOps.scala +++ b/core/src/main/scala/scalan/primitives/NumericOps.scala @@ -12,8 +12,6 @@ trait NumericOps extends Base { self: Scalan => def *(y: Ref[T]): Ref[T] = NumericTimes(n)(x.elem).apply(x, y) def unary_- : Ref[T] = NumericNegate(n)(x.elem).apply(x) def abs: Ref[T] = Abs(n)(x.elem).apply(x) - def toFloat: Ref[Float] = NumericToFloat(n).apply(x) - def toDouble: Ref[Double] = NumericToDouble(n).apply(x) def toInt: Ref[Int] = NumericToInt(n).apply(x) def toLong: Ref[Long] = NumericToLong(n).apply(x) } @@ -58,16 +56,6 @@ trait NumericOps extends Base { self: Scalan => override def applySeq(x: T): T = n.negate(x) } - /** Descriptor of unary `ToDouble` conversion operation. */ - case class NumericToDouble[T](n: ExactNumeric[T]) extends UnOp[T,Double]("ToDouble") { - override def applySeq(x: T): Double = n.toDouble(x) - } - - /** Descriptor of unary `ToFloat` conversion operation. */ - case class NumericToFloat[T](n: ExactNumeric[T]) extends UnOp[T, Float]("ToFloat") { - override def applySeq(x: T): Float = n.toFloat(x) - } - /** Descriptor of unary `ToInt` conversion operation. */ case class NumericToInt[T](n: ExactNumeric[T]) extends UnOp[T,Int]("ToInt") { override def applySeq(x: T): Int = n.toInt(x) diff --git a/sigmastate/src/main/scala/sigmastate/trees.scala b/sigmastate/src/main/scala/sigmastate/trees.scala index 6275255024..d769451bca 100644 --- a/sigmastate/src/main/scala/sigmastate/trees.scala +++ b/sigmastate/src/main/scala/sigmastate/trees.scala @@ -1011,7 +1011,9 @@ object ArithOp { } } -/** Negation operation on numeric type T. */ +/** Negation operation on numeric type T. + * See ExactNumeric instance for the corresponding type T. + */ case class Negation[T <: SType](input: Value[T]) extends OneArgumentOperation[T, T] { require(input.tpe.isNumTypeOrNoType, s"invalid type ${input.tpe}") override def companion = Negation @@ -1029,6 +1031,7 @@ object Negation extends OneArgumentOperationCompanion { override def argInfos: Seq[ArgInfo] = NegationInfo.argInfos } +/** Not implemented in v4.x. */ case class BitInversion[T <: SType](input: Value[T]) extends OneArgumentOperation[T, T] { require(input.tpe.isNumTypeOrNoType, s"invalid type ${input.tpe}") override def companion = BitInversion @@ -1040,6 +1043,7 @@ object BitInversion extends OneArgumentOperationCompanion { override def argInfos: Seq[ArgInfo] = BitInversionInfo.argInfos } +/** ErgoTree node which represents a binary bit-wise operation with the given opCode. */ case class BitOp[T <: SType](left: Value[T], right: Value[T], override val opCode: OpCode) extends TwoArgumentsOperation[T, T, T] with NotReadyValue[T] { require(left.tpe.isNumTypeOrNoType && right.tpe.isNumTypeOrNoType, s"invalid types left:${left.tpe}, right:${right.tpe}") From a5e0679640871f71e6b6f67f125c2547decff966 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Mon, 15 Nov 2021 14:46:14 +0300 Subject: [PATCH 53/56] division-remainder: removed ExactNumeric.abs --- common/src/main/scala/scalan/ExactNumeric.scala | 2 -- core/src/main/scala/scalan/primitives/NumericOps.scala | 6 ------ .../src/test/scala/sigmastate/lang/SigmaParserTest.scala | 4 ---- 3 files changed, 12 deletions(-) diff --git a/common/src/main/scala/scalan/ExactNumeric.scala b/common/src/main/scala/scalan/ExactNumeric.scala index 7ee5c27347..01cb1b8f25 100644 --- a/common/src/main/scala/scalan/ExactNumeric.scala +++ b/common/src/main/scala/scalan/ExactNumeric.scala @@ -35,8 +35,6 @@ trait ExactNumeric[T] { /** A value of type T which corresponds to integer 1. */ lazy val one: T = fromInt(1) - - def abs(x: T): T = n.abs(x) } object ExactNumeric { diff --git a/core/src/main/scala/scalan/primitives/NumericOps.scala b/core/src/main/scala/scalan/primitives/NumericOps.scala index 2b6f06db91..b00e1bfecd 100644 --- a/core/src/main/scala/scalan/primitives/NumericOps.scala +++ b/core/src/main/scala/scalan/primitives/NumericOps.scala @@ -11,7 +11,6 @@ trait NumericOps extends Base { self: Scalan => def -(y: Ref[T]): Ref[T] = NumericMinus(n)(x.elem).apply(x, y) def *(y: Ref[T]): Ref[T] = NumericTimes(n)(x.elem).apply(x, y) def unary_- : Ref[T] = NumericNegate(n)(x.elem).apply(x) - def abs: Ref[T] = Abs(n)(x.elem).apply(x) def toInt: Ref[Int] = NumericToInt(n).apply(x) def toLong: Ref[Long] = NumericToLong(n).apply(x) } @@ -66,11 +65,6 @@ trait NumericOps extends Base { self: Scalan => override def applySeq(x: T): Long = n.toLong(x) } - /** Descriptor of unary `abs` operation. */ - case class Abs[T: Elem](n: ExactNumeric[T]) extends UnOp[T, T]("Abs") { - override def applySeq(x: T): T = n.abs(x) - } - /** Descriptor of binary `/` operation (integral division). */ case class IntegralDivide[T](i: ExactIntegral[T])(implicit elem: Elem[T]) extends DivOp[T]("/", i) { override def applySeq(x: T, y: T): T = i.quot(x, y) diff --git a/sigmastate/src/test/scala/sigmastate/lang/SigmaParserTest.scala b/sigmastate/src/test/scala/sigmastate/lang/SigmaParserTest.scala index dff4713b27..45e87b3cb8 100644 --- a/sigmastate/src/test/scala/sigmastate/lang/SigmaParserTest.scala +++ b/sigmastate/src/test/scala/sigmastate/lang/SigmaParserTest.scala @@ -731,10 +731,6 @@ class SigmaParserTest extends PropSpec with PropertyChecks with Matchers with La parse("1L.toBits") shouldBe Select(LongConstant(1), "toBits") } - property("SNumeric.abs") { - parse("1.abs") shouldBe Select(IntConstant(1), "abs") - } - property("SNumeric.compare") { parse("1.compare(2)") shouldBe Apply(Select(IntConstant(1), "compare", None), Vector(IntConstant(2))) } From a3068e8b7c497a6dd1b7871708a7473c5fe0c830 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Mon, 15 Nov 2021 16:44:38 +0300 Subject: [PATCH 54/56] division-remainder: fix reminder -> remainder --- common/src/main/scala/scalan/ExactIntegral.scala | 2 +- core/src/main/scala/scalan/primitives/NumericOps.scala | 2 +- docs/spec/generated/ergotree_serialization1.tex | 2 +- docs/spec/generated/predeffunc_rows.tex | 2 +- docs/spec/generated/predeffunc_sections.tex | 2 +- sigmastate/src/main/scala/sigmastate/lang/SigmaPredef.scala | 2 +- sigmastate/src/main/scala/sigmastate/trees.scala | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/common/src/main/scala/scalan/ExactIntegral.scala b/common/src/main/scala/scalan/ExactIntegral.scala index 70dcb48621..3b049a76f9 100644 --- a/common/src/main/scala/scalan/ExactIntegral.scala +++ b/common/src/main/scala/scalan/ExactIntegral.scala @@ -20,7 +20,7 @@ trait ExactIntegral[T] extends ExactNumeric[T] { /** Integer division operation `x / y`. */ def quot(x: T, y: T): T = n.quot(x, y) - /** Operation which returns reminder from dividing x by y. + /** Operation which returns remainder from dividing x by y. * The exact rules are defined in the concrete instance of the type T. * A default implementation delegates to Integral[T].rem method for the corresponding * type T. diff --git a/core/src/main/scala/scalan/primitives/NumericOps.scala b/core/src/main/scala/scalan/primitives/NumericOps.scala index b00e1bfecd..16889dcaac 100644 --- a/core/src/main/scala/scalan/primitives/NumericOps.scala +++ b/core/src/main/scala/scalan/primitives/NumericOps.scala @@ -70,7 +70,7 @@ trait NumericOps extends Base { self: Scalan => override def applySeq(x: T, y: T): T = i.quot(x, y) } - /** Descriptor of binary `%` operation (reminder of integral division). */ + /** Descriptor of binary `%` operation (remainder of integral division). */ case class IntegralMod[T](i: ExactIntegral[T])(implicit elem: Elem[T]) extends DivOp[T]("%", i) { /** Note, this is implemented using `ExactIntegral.rem` method which delegates to * `scala.math.Integral.rem`. The later also implements `%` operator in Scala for diff --git a/docs/spec/generated/ergotree_serialization1.tex b/docs/spec/generated/ergotree_serialization1.tex index 3db72a9d30..ae73a7eb17 100644 --- a/docs/spec/generated/ergotree_serialization1.tex +++ b/docs/spec/generated/ergotree_serialization1.tex @@ -375,7 +375,7 @@ \subsubsection{\lst{Division} operation (OpCode 157)} \subsubsection{\lst{Modulo} operation (OpCode 158)} \label{sec:serialization:operation:Modulo} -Reminder from division of the first operand by the second operand. See~\hyperref[sec:appendix:primops:Modulo]{\lst{\%}} +Remainder from division of the first operand by the second operand. See~\hyperref[sec:appendix:primops:Modulo]{\lst{\%}} \noindent \(\begin{tabularx}{\textwidth}{| l | l | l | X |} diff --git a/docs/spec/generated/predeffunc_rows.tex b/docs/spec/generated/predeffunc_rows.tex index 401d345644..65ae8bf6d4 100644 --- a/docs/spec/generated/predeffunc_rows.tex +++ b/docs/spec/generated/predeffunc_rows.tex @@ -44,7 +44,7 @@ \hline 157 & \hyperref[sec:serialization:operation:Division]{\lst{Division}} & \parbox{4cm}{\lst{/:} \\ \lst{(T, T)} \\ \lst{ => T}} & Integer division of the first operand by the second operand. \\ \hline - 158 & \hyperref[sec:serialization:operation:Modulo]{\lst{Modulo}} & \parbox{4cm}{\lst{\%:} \\ \lst{(T, T)} \\ \lst{ => T}} & Reminder from division of the first operand by the second operand. \\ + 158 & \hyperref[sec:serialization:operation:Modulo]{\lst{Modulo}} & \parbox{4cm}{\lst{\%:} \\ \lst{(T, T)} \\ \lst{ => T}} & Remainder from division of the first operand by the second operand. \\ \hline 161 & \hyperref[sec:serialization:operation:Min]{\lst{Min}} & \parbox{4cm}{\lst{min:} \\ \lst{(T, T)} \\ \lst{ => T}} & Minimum value of two operands. \\ \hline diff --git a/docs/spec/generated/predeffunc_sections.tex b/docs/spec/generated/predeffunc_sections.tex index 8d6ac2f782..ee62c477c3 100644 --- a/docs/spec/generated/predeffunc_sections.tex +++ b/docs/spec/generated/predeffunc_sections.tex @@ -540,7 +540,7 @@ \subsubsection{\lst{\%} method (Code 158)} \noindent \begin{tabularx}{\textwidth}{| l | X |} \hline - \bf{Description} & Reminder from division of the first operand by the second operand. \\ + \bf{Description} & Remainder from division of the first operand by the second operand. \\ \hline \bf{Parameters} & diff --git a/sigmastate/src/main/scala/sigmastate/lang/SigmaPredef.scala b/sigmastate/src/main/scala/sigmastate/lang/SigmaPredef.scala index ca7d94e147..942a110d52 100644 --- a/sigmastate/src/main/scala/sigmastate/lang/SigmaPredef.scala +++ b/sigmastate/src/main/scala/sigmastate/lang/SigmaPredef.scala @@ -446,7 +446,7 @@ object SigmaPredef { Seq(ArgInfo("left", "left operand"), ArgInfo("right", "right operand"))), binaryOp("/", ArithOp.Division, "Integer division of the first operand by the second operand.", Seq(ArgInfo("left", "left operand"), ArgInfo("right", "right operand"))), - binaryOp("%", ArithOp.Modulo, "Reminder from division of the first operand by the second operand.", + binaryOp("%", ArithOp.Modulo, "Remainder from division of the first operand by the second operand.", Seq(ArgInfo("left", "left operand"), ArgInfo("right", "right operand"))), binaryOp("min", ArithOp.Min, "Minimum value of two operands.", Seq(ArgInfo("left", "left operand"), ArgInfo("right", "right operand"))), diff --git a/sigmastate/src/main/scala/sigmastate/trees.scala b/sigmastate/src/main/scala/sigmastate/trees.scala index d769451bca..1811ef5ded 100644 --- a/sigmastate/src/main/scala/sigmastate/trees.scala +++ b/sigmastate/src/main/scala/sigmastate/trees.scala @@ -938,7 +938,7 @@ object ArithOp { } } - /** Operation which returns reminder from dividing x by y. + /** Operation which returns remainder from dividing x by y. * See ExactIntegral.divisionRemainder implementation for the concrete numeric type. */ object Modulo extends ArithOpCompanion(ModuloCode, "%", ModuloInfo.argInfos) { From 71925c8cef82f87b3995044ad17ed3739438e23f Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Mon, 15 Nov 2021 19:01:35 +0300 Subject: [PATCH 55/56] division-remainder: use ExactIntergal instances instead of ExactNumeric --- .../src/main/scala/scalan/ExactIntegral.scala | 4 +-- .../src/main/scala/scalan/ExactNumeric.scala | 10 +++--- .../scala/scalan/primitives/NumericOps.scala | 7 +--- library/src/main/scala/scalan/Library.scala | 2 +- .../scala/sigmastate/eval/BigIntegerOps.scala | 4 +-- .../sigmastate/eval/RuntimeCosting.scala | 10 +++--- .../src/main/scala/sigmastate/trees.scala | 32 +++++++++---------- .../special/sigma/SigmaDslSpecification.scala | 8 ++--- 8 files changed, 33 insertions(+), 44 deletions(-) diff --git a/common/src/main/scala/scalan/ExactIntegral.scala b/common/src/main/scala/scalan/ExactIntegral.scala index 3b049a76f9..57e6e55dd3 100644 --- a/common/src/main/scala/scalan/ExactIntegral.scala +++ b/common/src/main/scala/scalan/ExactIntegral.scala @@ -2,7 +2,7 @@ package scalan import scalan.util.Extensions._ -/** Type-class which defines the operations on Integral types (Byte, Short, Int, Long) +/** Type-class which defines the operations on Integral types (Byte, Short, Int, Long, BigInt) * with overflow checks. * * An exception is raised when an overflow is detected. @@ -15,7 +15,7 @@ import scalan.util.Extensions._ * This trait is used in core IR to avoid implicitly using standard scala implementations. */ trait ExactIntegral[T] extends ExactNumeric[T] { - protected val n: Integral[T] + protected val n: scala.math.Integral[T] /** Integer division operation `x / y`. */ def quot(x: T, y: T): T = n.quot(x, y) diff --git a/common/src/main/scala/scalan/ExactNumeric.scala b/common/src/main/scala/scalan/ExactNumeric.scala index 01cb1b8f25..5967785de5 100644 --- a/common/src/main/scala/scalan/ExactNumeric.scala +++ b/common/src/main/scala/scalan/ExactNumeric.scala @@ -38,11 +38,9 @@ trait ExactNumeric[T] { } object ExactNumeric { - /** ExactNumeric instances for all types are the same as ExactIntegral. - * This values are implicitly used wherever ExactNumeric is needed. + /** ExactNumeric instances are the same as ExactIntegral. + * This values are implicitly used wherever ExactNumeric is needed (see usages). */ - implicit val ByteIsExactNumeric: ExactNumeric[Byte] = ByteIsExactIntegral - implicit val ShortIsExactNumeric: ExactNumeric[Short] = ShortIsExactIntegral - implicit val IntIsExactNumeric: ExactNumeric[Int] = IntIsExactIntegral - implicit val LongIsExactNumeric: ExactNumeric[Long] = LongIsExactIntegral + implicit def IntIsExactNumeric: ExactNumeric[Int] = IntIsExactIntegral + implicit def LongIsExactNumeric: ExactNumeric[Long] = LongIsExactIntegral } diff --git a/core/src/main/scala/scalan/primitives/NumericOps.scala b/core/src/main/scala/scalan/primitives/NumericOps.scala index 16889dcaac..39c93b49ae 100644 --- a/core/src/main/scala/scalan/primitives/NumericOps.scala +++ b/core/src/main/scala/scalan/primitives/NumericOps.scala @@ -71,12 +71,7 @@ trait NumericOps extends Base { self: Scalan => } /** Descriptor of binary `%` operation (remainder of integral division). */ -case class IntegralMod[T](i: ExactIntegral[T])(implicit elem: Elem[T]) extends DivOp[T]("%", i) { - /** Note, this is implemented using `ExactIntegral.rem` method which delegates to - * `scala.math.Integral.rem`. The later also implements `%` operator in Scala for - * numeric types. - * @see sigmastate.eval.NumericOps.BigIntIsIntegral - */ + case class IntegralMod[T](i: ExactIntegral[T])(implicit elem: Elem[T]) extends DivOp[T]("%", i) { override def applySeq(x: T, y: T): T = i.divisionRemainder(x, y) } diff --git a/library/src/main/scala/scalan/Library.scala b/library/src/main/scala/scalan/Library.scala index 8874c50d93..4058baff9d 100644 --- a/library/src/main/scala/scalan/Library.scala +++ b/library/src/main/scala/scalan/Library.scala @@ -4,7 +4,7 @@ import scala.language.implicitConversions import special.collection._ import special.wrappers.{WrappersSpecModule, WrappersModule} import scalan.util.{MemoizedFunc} -import scalan.ExactNumeric._ +import scalan.ExactIntegral._ trait Library extends Scalan with WrappersModule diff --git a/sigmastate/src/main/scala/sigmastate/eval/BigIntegerOps.scala b/sigmastate/src/main/scala/sigmastate/eval/BigIntegerOps.scala index 8726579066..9ff7c033dc 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/BigIntegerOps.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/BigIntegerOps.scala @@ -70,7 +70,7 @@ object NumericOps { * [[scalan.primitives.NumericOps.IntegralMod]] were not used for BigInt. * NOTE: this instance is used in the new v5.0 interpreter. */ - implicit object BigIntIsIntegral extends BigIntIsIntegral with OrderingOps.BigIntOrdering + object BigIntIsIntegral extends BigIntIsIntegral with OrderingOps.BigIntOrdering /** The instance of [[ExactIntegral]] typeclass for [[BigInt]]. */ implicit object BigIntIsExactIntegral extends ExactIntegral[BigInt] { @@ -86,8 +86,6 @@ object NumericOps { ??? // this method should not be used in v4.x } - implicit val BigIntIsExactNumeric: ExactNumeric[BigInt] = BigIntIsExactIntegral - /** The instance of [[scalan.ExactOrdering]] typeclass for [[BigInt]]. */ implicit object BigIntIsExactOrdering extends ExactOrderingImpl[BigInt](BigIntIsIntegral) } diff --git a/sigmastate/src/main/scala/sigmastate/eval/RuntimeCosting.scala b/sigmastate/src/main/scala/sigmastate/eval/RuntimeCosting.scala index c0d9b2a0c9..f6ae303cf3 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/RuntimeCosting.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/RuntimeCosting.scala @@ -881,11 +881,11 @@ trait RuntimeCosting extends CostingRules { IR: IRContext => import NumericOps._ private lazy val elemToExactNumericMap = Map[Elem[_], ExactNumeric[_]]( - (ByteElement, ByteIsExactNumeric), - (ShortElement, ShortIsExactNumeric), - (IntElement, IntIsExactNumeric), - (LongElement, LongIsExactNumeric), - (bigIntElement, BigIntIsExactNumeric) + (ByteElement, ByteIsExactIntegral), + (ShortElement, ShortIsExactIntegral), + (IntElement, IntIsExactIntegral), + (LongElement, LongIsExactIntegral), + (bigIntElement, BigIntIsExactIntegral) ) private lazy val elemToExactIntegralMap = Map[Elem[_], ExactIntegral[_]]( (ByteElement, ByteIsExactIntegral), diff --git a/sigmastate/src/main/scala/sigmastate/trees.scala b/sigmastate/src/main/scala/sigmastate/trees.scala index 1811ef5ded..d30592c1b3 100644 --- a/sigmastate/src/main/scala/sigmastate/trees.scala +++ b/sigmastate/src/main/scala/sigmastate/trees.scala @@ -17,10 +17,9 @@ import sigmastate.serialization._ import sigmastate.utxo.{SimpleTransformerCompanion, Transformer} import debox.{Map => DMap} import scalan.ExactIntegral._ -import scalan.ExactNumeric._ import scalan.ExactOrdering._ import sigmastate.ArithOp.OperationImpl -import sigmastate.eval.NumericOps.{BigIntIsExactIntegral, BigIntIsExactNumeric, BigIntIsExactOrdering} +import sigmastate.eval.NumericOps.{BigIntIsExactIntegral, BigIntIsExactOrdering} import sigmastate.eval.{Colls, SigmaDsl} import sigmastate.lang.TransformingSigmaBuilder import special.collection.Coll @@ -867,7 +866,7 @@ abstract class ArithOpCompanion(val opCode: OpCode, val name: String, _argInfos: override def argInfos: Seq[ArgInfo] = _argInfos override def costKind: TypeBasedCost @inline final def eval(node: SValue, typeCode: SType.TypeCode, x: Any, y: Any)(implicit E: ErgoTreeEvaluator): Any = { - val impl = ArithOp.numerics(typeCode) + val impl = ArithOp.impls(typeCode) node.addCost(costKind, impl.argTpe) { () => eval(impl, x, y) } @@ -880,7 +879,7 @@ object ArithOp { /** Addition operation `x + y`. */ object Plus extends ArithOpCompanion(PlusCode, "+", PlusInfo.argInfos) { - override def eval(impl: OperationImpl, x: Any, y: Any): Any = impl.n.plus(x, y) + override def eval(impl: OperationImpl, x: Any, y: Any): Any = impl.i.plus(x, y) /** Cost of: * 1) resolving ArithOpCompanion by typeCode * 2) calling method of Numeric @@ -895,7 +894,7 @@ object ArithOp { /** Subtraction operation `x - y`. */ object Minus extends ArithOpCompanion(MinusCode, "-", MinusInfo.argInfos) { - override def eval(impl: OperationImpl, x: Any, y: Any): Any = impl.n.minus(x, y) + override def eval(impl: OperationImpl, x: Any, y: Any): Any = impl.i.minus(x, y) /** Cost of: * 1) resolving ArithOpCompanion by typeCode * 2) calling method of Numeric @@ -910,7 +909,7 @@ object ArithOp { /** Multiplication operation `x * y`. */ object Multiply extends ArithOpCompanion(MultiplyCode, "*", MultiplyInfo.argInfos) { - override def eval(impl: OperationImpl, x: Any, y: Any): Any = impl.n.times(x, y) + override def eval(impl: OperationImpl, x: Any, y: Any): Any = impl.i.times(x, y) /** Cost of: * 1) resolving ArithOpCompanion by typeCode * 2) calling method of Numeric @@ -989,19 +988,18 @@ object ArithOp { DMap.fromIterable(Seq(Plus, Minus, Multiply, Division, Modulo, Min, Max).map(o => (o.opCode, o))) /** Represents implementation of numeric Arith operations for the given type argTpe. */ - class OperationImpl(_n: ExactNumeric[_], _i: ExactIntegral[_], _o: ExactOrdering[_], val argTpe: SType) { - val n = _n.asInstanceOf[ExactNumeric[Any]] + class OperationImpl(_i: ExactIntegral[_], _o: ExactOrdering[_], val argTpe: SType) { val i = _i.asInstanceOf[ExactIntegral[Any]] val o = _o.asInstanceOf[ExactOrdering[Any]] } - private[sigmastate] val numerics: DMap[SType.TypeCode, OperationImpl] = + private[sigmastate] val impls: DMap[SType.TypeCode, OperationImpl] = DMap.fromIterable(Seq( - SByte -> new OperationImpl(ByteIsExactNumeric, ByteIsExactIntegral, ByteIsExactOrdering, SByte), - SShort -> new OperationImpl(ShortIsExactNumeric, ShortIsExactIntegral, ShortIsExactOrdering, SShort), - SInt -> new OperationImpl(IntIsExactNumeric, IntIsExactIntegral, IntIsExactOrdering, SInt), - SLong -> new OperationImpl(LongIsExactNumeric, LongIsExactIntegral, LongIsExactOrdering, SLong), - SBigInt -> new OperationImpl(BigIntIsExactNumeric, BigIntIsExactIntegral, BigIntIsExactOrdering, SBigInt) + SByte -> new OperationImpl(ByteIsExactIntegral, ByteIsExactOrdering, SByte), + SShort -> new OperationImpl(ShortIsExactIntegral, ShortIsExactOrdering, SShort), + SInt -> new OperationImpl(IntIsExactIntegral, IntIsExactOrdering, SInt), + SLong -> new OperationImpl(LongIsExactIntegral, LongIsExactOrdering, SLong), + SBigInt -> new OperationImpl(BigIntIsExactIntegral, BigIntIsExactOrdering, SBigInt) ).map { case (t, n) => (t.typeCode, n) }) /** Returns operation name for the given opCode. */ @@ -1020,9 +1018,9 @@ case class Negation[T <: SType](input: Value[T]) extends OneArgumentOperation[T, override def tpe: T = input.tpe protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { val inputV = input.evalTo[AnyVal](env) - val n = ArithOp.numerics(input.tpe.typeCode).n + val i = ArithOp.impls(input.tpe.typeCode).i addCost(Negation.costKind) - n.negate(inputV) + i.negate(inputV) } } object Negation extends OneArgumentOperationCompanion { @@ -1206,7 +1204,7 @@ sealed trait Relation[LIV <: SType, RIV <: SType] extends Triple[LIV, RIV, SBool trait SimpleRelation[T <: SType] extends Relation[T, T] { override def opType = SimpleRelation.GenericOpType - lazy val opImpl = ArithOp.numerics(left.tpe.typeCode) + lazy val opImpl = ArithOp.impls(left.tpe.typeCode) } object SimpleRelation { val GenericOpType = SFunc(SType.IndexedSeqOfT2, SBoolean) diff --git a/sigmastate/src/test/scala/special/sigma/SigmaDslSpecification.scala b/sigmastate/src/test/scala/special/sigma/SigmaDslSpecification.scala index 383f062371..400818ed8e 100644 --- a/sigmastate/src/test/scala/special/sigma/SigmaDslSpecification.scala +++ b/sigmastate/src/test/scala/special/sigma/SigmaDslSpecification.scala @@ -6,7 +6,7 @@ import java.math.BigInteger import org.ergoplatform._ import org.ergoplatform.settings.ErgoAlgos import org.scalacheck.{Arbitrary, Gen} -import scalan.{ExactOrdering, ExactNumeric, RType} +import scalan.{ExactOrdering, ExactNumeric, RType, ExactIntegral} import scorex.crypto.authds.avltree.batch._ import scorex.crypto.authds.{ADDigest, ADKey, ADValue} import scorex.crypto.hash.{Digest32, Blake2b256} @@ -492,7 +492,7 @@ class SigmaDslSpecification extends SigmaDslTesting with CrossVersionProps { sui (x: Byte) => x.toBigInt, "{ (x: Byte) => x.toBigInt }", FuncValue(Vector((1, SByte)), Upcast(ValUse(1, SByte), SBigInt)))) - val n = ExactNumeric.ByteIsExactNumeric + val n = ExactIntegral.ByteIsExactIntegral verifyCases( { def success[T](v: (T, (T, (T, (T, T))))) = Expected(Success(v), 39654) @@ -833,7 +833,7 @@ class SigmaDslSpecification extends SigmaDslTesting with CrossVersionProps { sui "{ (x: Short) => x.toBigInt }", FuncValue(Vector((1, SShort)), Upcast(ValUse(1, SShort), SBigInt)))) - val n = ExactNumeric.ShortIsExactNumeric + val n = ExactIntegral.ShortIsExactIntegral verifyCases( { def success[T](v: T) = Expected(Success(v), 39654) @@ -1667,7 +1667,7 @@ class SigmaDslSpecification extends SigmaDslTesting with CrossVersionProps { sui "{ (x: BigInt) => x.toBigInt }", FuncValue(Vector((1, SBigInt)), ValUse(1, SBigInt)))) - val n = NumericOps.BigIntIsExactNumeric + val n = NumericOps.BigIntIsExactIntegral verifyCases( { def success(v: (BigInt, (BigInt, (BigInt, (BigInt, BigInt))))) = Expected(Success(v), 39774) From 8262706707ea04b01a6a81ec7f05d9ef5ee82e14 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Mon, 29 Nov 2021 21:41:26 +0300 Subject: [PATCH 56/56] using newArray method --- .../scala/org/ergoplatform/ErgoBoxCandidate.scala | 6 +++--- .../org/ergoplatform/ErgoLikeTransaction.scala | 10 +++++----- .../src/main/scala/sigmastate/SigSerializer.scala | 11 +++++------ sigmastate/src/main/scala/sigmastate/Values.scala | 6 +++--- .../serialization/BlockValueSerializer.scala | 2 +- ...ncreteCollectionBooleanConstantSerializer.scala | 2 +- .../ConcreteCollectionSerializer.scala | 2 +- .../serialization/ErgoTreeSerializer.scala | 2 +- .../serialization/FuncValueSerializer.scala | 2 +- .../serialization/GroupElementSerializer.scala | 2 +- .../serialization/MethodCallSerializer.scala | 2 +- .../sigmastate/serialization/SigmaSerializer.scala | 13 +++++++++++++ .../sigmastate/serialization/TupleSerializer.scala | 2 +- .../sigmastate/serialization/TypeSerializer.scala | 2 +- .../serialization/ValDefSerializer.scala | 2 +- .../transformers/SigmaTransformerSerializer.scala | 2 +- .../scala/sigmastate/utils/SigmaByteReader.scala | 2 +- .../utxo/SerializationRoundTripSpec.scala | 14 ++++++++++++++ 18 files changed, 55 insertions(+), 29 deletions(-) diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala index 19211b6aa0..894dcd3bbf 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala @@ -8,7 +8,7 @@ import scorex.util.{bytesToId, ModifierId} import sigmastate.Values._ import sigmastate._ import sigmastate.SType.AnyOps -import sigmastate.serialization.SigmaSerializer +import sigmastate.serialization.{SigmaSerializer, ValueSerializer} import sigmastate.utils.{SigmaByteReader, SigmaByteWriter} import special.collection.Coll import sigmastate.eval._ @@ -187,8 +187,8 @@ object ErgoBoxCandidate { val tree = DefaultSerializer.deserializeErgoTree(r, SigmaSerializer.MaxPropositionSize) // READ val creationHeight = r.getUInt().toInt // READ val nTokens = r.getUByte() // READ - val tokenIds = new Array[Array[Byte]](nTokens) - val tokenAmounts = new Array[Long](nTokens) + val tokenIds = ValueSerializer.newArray[Array[Byte]](nTokens) + val tokenAmounts = ValueSerializer.newArray[Long](nTokens) if (digestsInTx != null) { val nDigests = digestsInTx.length cfor(0)(_ < nTokens, _ + 1) { i => diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala index c747ff9684..b673a3bb86 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala @@ -8,7 +8,7 @@ import sigmastate.SType._ import sigmastate.eval.Extensions._ import sigmastate.eval._ import sigmastate.interpreter.ProverResult -import sigmastate.serialization.SigmaSerializer +import sigmastate.serialization.{SigmaSerializer, ValueSerializer} import sigmastate.utils.{SigmaByteReader, SigmaByteWriter} import special.collection.ExtensionMethods._ import spire.syntax.all.cfor @@ -147,28 +147,28 @@ object ErgoLikeTransactionSerializer extends SigmaSerializer[ErgoLikeTransaction override def parse(r: SigmaByteReader): ErgoLikeTransaction = { // parse transaction inputs val inputsCount = r.getUShort() - val inputs = new Array[Input](inputsCount) + val inputs = ValueSerializer.newArray[Input](inputsCount) cfor(0)(_ < inputsCount, _ + 1) { i => inputs(i) = Input.serializer.parse(r) } // parse transaction data inputs val dataInputsCount = r.getUShort() - val dataInputs = new Array[DataInput](dataInputsCount) + val dataInputs = ValueSerializer.newArray[DataInput](dataInputsCount) cfor(0)(_ < dataInputsCount, _ + 1) { i => dataInputs(i) = DataInput(ADKey @@ r.getBytes(ErgoBox.BoxId.size)) } // parse distinct ids of tokens in transaction outputs val tokensCount = r.getUInt().toInt - val tokens = new Array[Array[Byte]](tokensCount) + val tokens = ValueSerializer.newArray[Array[Byte]](tokensCount) cfor(0)(_ < tokensCount, _ + 1) { i => tokens(i) = r.getBytes(TokenId.size) } // parse outputs val outsCount = r.getUShort() - val outputCandidates = new Array[ErgoBoxCandidate](outsCount) + val outputCandidates = ValueSerializer.newArray[ErgoBoxCandidate](outsCount) cfor(0)(_ < outsCount, _ + 1) { i => outputCandidates(i) = ErgoBoxCandidate.serializer.parseBodyWithIndexedDigests(tokens, r) } diff --git a/sigmastate/src/main/scala/sigmastate/SigSerializer.scala b/sigmastate/src/main/scala/sigmastate/SigSerializer.scala index cbcb93f0a9..a1133bc8eb 100644 --- a/sigmastate/src/main/scala/sigmastate/SigSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/SigSerializer.scala @@ -4,15 +4,14 @@ import com.typesafe.scalalogging.LazyLogging import gf2t.GF2_192_Poly import org.bouncycastle.util.BigIntegers import scorex.util.encode.Base16 -import sigmastate.SigSerializer.{logger, readBytesChecked} import sigmastate.Values.SigmaBoolean import sigmastate.basics.DLogProtocol.{ProveDlog, SecondDLogProverMessage} import sigmastate.basics.VerifierMessage.Challenge import sigmastate.basics.{ProveDHTuple, SecondDiffieHellmanTupleProverMessage} import sigmastate.interpreter.CryptoConstants import sigmastate.lang.exceptions.SerializerException -import sigmastate.serialization.SigmaSerializer -import sigmastate.utils.{Helpers, SigmaByteReader, SigmaByteWriter} +import sigmastate.serialization.{SigmaSerializer, ValueSerializer} +import sigmastate.utils.{SigmaByteReader, SigmaByteWriter, Helpers} import spire.syntax.all.cfor object SigSerializer extends LazyLogging { @@ -171,7 +170,7 @@ object SigSerializer extends LazyLogging { case and: CAND => // Verifier Step 2: If the node is AND, then all of its children get e_0 as the challenge val nChildren = and.children.length - val children = new Array[UncheckedSigmaTree](nChildren) + val children = ValueSerializer.newArray[UncheckedSigmaTree](nChildren) cfor(0)(_ < nChildren, _ + 1) { i => children(i) = parseAndComputeChallenges(and.children(i), r, challenge) } @@ -184,7 +183,7 @@ object SigSerializer extends LazyLogging { // Read all the children but the last and compute the XOR of all the challenges including e_0 val nChildren = or.children.length - val children = new Array[UncheckedSigmaTree](nChildren) + val children = ValueSerializer.newArray[UncheckedSigmaTree](nChildren) val xorBuf = challenge.clone() val iLastChild = nChildren - 1 cfor(0)(_ < iLastChild, _ + 1) { i => @@ -211,7 +210,7 @@ object SigSerializer extends LazyLogging { hex => warn(s"Invalid coeffBytes for $th: $hex")) val polynomial = GF2_192_Poly.fromByteArray(challenge, coeffBytes) - val children = new Array[UncheckedSigmaTree](nChildren) + val children = ValueSerializer.newArray[UncheckedSigmaTree](nChildren) cfor(0)(_ < nChildren, _ + 1) { i => val c = Challenge @@ polynomial.evaluate((i + 1).toByte).toByteArray children(i) = parseAndComputeChallenges(th.children(i), r, c) diff --git a/sigmastate/src/main/scala/sigmastate/Values.scala b/sigmastate/src/main/scala/sigmastate/Values.scala index ade01b6dff..ed198420d7 100644 --- a/sigmastate/src/main/scala/sigmastate/Values.scala +++ b/sigmastate/src/main/scala/sigmastate/Values.scala @@ -817,14 +817,14 @@ object Values { case ProveDiffieHellmanTupleCode => dhtSerializer.parse(r) case AndCode => val n = r.getUShort() - val children = new Array[SigmaBoolean](n) + val children = ValueSerializer.newArray[SigmaBoolean](n) cfor(0)(_ < n, _ + 1) { i => children(i) = serializer.parse(r) } CAND(children) case OrCode => val n = r.getUShort() - val children = new Array[SigmaBoolean](n) + val children = ValueSerializer.newArray[SigmaBoolean](n) cfor(0)(_ < n, _ + 1) { i => children(i) = serializer.parse(r) } @@ -832,7 +832,7 @@ object Values { case AtLeastCode => val k = r.getUShort() val n = r.getUShort() - val children = new Array[SigmaBoolean](n) + val children = ValueSerializer.newArray[SigmaBoolean](n) cfor(0)(_ < n, _ + 1) { i => children(i) = serializer.parse(r) } diff --git a/sigmastate/src/main/scala/sigmastate/serialization/BlockValueSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/BlockValueSerializer.scala index c0461b8148..6bd7cd91ec 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/BlockValueSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/BlockValueSerializer.scala @@ -29,7 +29,7 @@ case class BlockValueSerializer(cons: (IndexedSeq[BlockItem], Value[SType]) => V BlockItem.EmptySeq else { // HOTSPOT:: allocate new array only if it is not empty - val buf = new Array[BlockItem](itemsSize) + val buf = ValueSerializer.newArray[BlockItem](itemsSize) cfor(0)(_ < itemsSize, _ + 1) { i => buf(i) = r.getValue().asInstanceOf[BlockItem] } diff --git a/sigmastate/src/main/scala/sigmastate/serialization/ConcreteCollectionBooleanConstantSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/ConcreteCollectionBooleanConstantSerializer.scala index 36a0e22801..0eb17c0aa6 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/ConcreteCollectionBooleanConstantSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/ConcreteCollectionBooleanConstantSerializer.scala @@ -36,7 +36,7 @@ case class ConcreteCollectionBooleanConstantSerializer(cons: (IndexedSeq[Value[S // reusing pre-allocated immutable instances Value.EmptySeq.asInstanceOf[IndexedSeq[Value[SBoolean.type]]] } else { - val items = new Array[BoolValue](size) + val items = ValueSerializer.newArray[BoolValue](size) cfor(0)(_ < size, _ + 1) { i => items(i) = BooleanConstant.fromBoolean(bits(i)) } diff --git a/sigmastate/src/main/scala/sigmastate/serialization/ConcreteCollectionSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/ConcreteCollectionSerializer.scala index d44f3adf2a..31ada23423 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/ConcreteCollectionSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/ConcreteCollectionSerializer.scala @@ -29,7 +29,7 @@ case class ConcreteCollectionSerializer(cons: (IndexedSeq[Value[SType]], SType) // reusing pre-allocated immutable instances Value.EmptySeq } else { - val values = new Array[SValue](size) + val values = ValueSerializer.newArray[SValue](size) cfor(0)(_ < size, _ + 1) { i => val v = r.getValue() // READ values(i) = v diff --git a/sigmastate/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala index 4719633c98..6dee796e68 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala @@ -224,7 +224,7 @@ class ErgoTreeSerializer { val nConsts = r.getUInt().toInt if (nConsts > 0) { // HOTSPOT:: allocate new array only if it is not empty - val res = new Array[Constant[SType]](nConsts) + val res = ValueSerializer.newArray[Constant[SType]](nConsts) cfor(0)(_ < nConsts, _ + 1) { i => res(i) = constantSerializer.deserialize(r) } diff --git a/sigmastate/src/main/scala/sigmastate/serialization/FuncValueSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/FuncValueSerializer.scala index 5448963a65..2bb35be485 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/FuncValueSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/FuncValueSerializer.scala @@ -27,7 +27,7 @@ case class FuncValueSerializer(cons: (IndexedSeq[(Int, SType)], Value[SType]) => override def parse(r: SigmaByteReader): Value[SType] = { val argsSize = r.getUInt().toIntExact - val args = new Array[(Int, SType)](argsSize) + val args = ValueSerializer.newArray[(Int, SType)](argsSize) cfor(0)(_ < argsSize, _ + 1) { i => val id = r.getUInt().toInt val tpe = r.getType() diff --git a/sigmastate/src/main/scala/sigmastate/serialization/GroupElementSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/GroupElementSerializer.scala index 4998ab94d7..d5aa3b8427 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/GroupElementSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/GroupElementSerializer.scala @@ -26,7 +26,7 @@ object GroupElementSerializer extends SigmaSerializer[EcPointType, EcPointType] val normed = point.normalize() val ySign = normed.getAffineYCoord.testBitZero() val X = normed.getXCoord.getEncoded - val PO = new Array[Byte](X.length + 1) + val PO = ValueSerializer.newArray[Byte](X.length + 1) PO(0) = (if (ySign) 0x03 else 0x02).toByte System.arraycopy(X, 0, PO, 1, X.length) PO diff --git a/sigmastate/src/main/scala/sigmastate/serialization/MethodCallSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/MethodCallSerializer.scala index 9e609a8763..3b86a30bf6 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/MethodCallSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/MethodCallSerializer.scala @@ -53,7 +53,7 @@ case class MethodCallSerializer(cons: (Value[SType], SMethod, IndexedSeq[Value[S val types: Seq[SType] = if (nArgs == 0) SType.EmptySeq else { - val types = new Array[SType](nArgs) + val types = ValueSerializer.newArray[SType](nArgs) cfor(0)(_ < nArgs, _ + 1) { i => types(i) = args(i).tpe } diff --git a/sigmastate/src/main/scala/sigmastate/serialization/SigmaSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/SigmaSerializer.scala index c486cf34bb..bd00670129 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/SigmaSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/SigmaSerializer.scala @@ -10,6 +10,8 @@ import sigmastate.utils._ import scorex.util.serialization._ import sigmastate.serialization.OpCodes.OpCode +import scala.reflect.ClassTag + object SigmaSerializer { type Position = Int type Consumed = Int @@ -104,6 +106,17 @@ abstract class SigmaSerializer[TFamily, T <: TFamily] extends Serializer[TFamily trait SigmaSerializerCompanion[TFamily] { + /** Maximum length of a serializable collection. */ + val MaxArrayLength: Int = 100000 + + /** Allocates a new array with `len` items of type `A`. */ + final def newArray[A](len: Int)(implicit tA: ClassTag[A]): Array[A] = { + if (len > MaxArrayLength) + throw new SerializerException( + s"Cannot allocate array of $tA with $len items: max limit is $MaxArrayLength") + new Array[A](len) + } + def getSerializer(opCode: OpCode): SigmaSerializer[TFamily, _ <: TFamily] def deserialize(r: SigmaByteReader): TFamily def serialize(v: TFamily, w: SigmaByteWriter): Unit diff --git a/sigmastate/src/main/scala/sigmastate/serialization/TupleSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/TupleSerializer.scala index ec97631f01..ee84f994ef 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/TupleSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/TupleSerializer.scala @@ -24,7 +24,7 @@ case class TupleSerializer(cons: Seq[Value[SType]] => Value[SType]) override def parse(r: SigmaByteReader): Value[SType] = { val size = r.getByte() - val values = new Array[SValue](size) // assume size > 0 so always create a new array + val values = ValueSerializer.newArray[SValue](size) // assume size > 0 so always create a new array cfor(0)(_ < size, _ + 1) { i => values(i) = r.getValue() } diff --git a/sigmastate/src/main/scala/sigmastate/serialization/TypeSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/TypeSerializer.scala index fb51333d51..4ac603714e 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/TypeSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/TypeSerializer.scala @@ -174,7 +174,7 @@ object TypeSerializer { c match { case STuple.TupleTypeCode => { val len = r.getUByte() - val items = new Array[SType](len) + val items = ValueSerializer.newArray[SType](len) cfor(0)(_ < len, _ + 1) { i => items(i) = deserialize(r, depth + 1) } diff --git a/sigmastate/src/main/scala/sigmastate/serialization/ValDefSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/ValDefSerializer.scala index e485df7580..ff093d061f 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/ValDefSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/ValDefSerializer.scala @@ -33,7 +33,7 @@ case class ValDefSerializer(override val opDesc: ValueCompanion) extends ValueSe val tpeArgs: Seq[STypeVar] = opCode match { case FunDefCode => val nTpeArgs = r.getByte() - val inputs = new Array[STypeVar](nTpeArgs) + val inputs = ValueSerializer.newArray[STypeVar](nTpeArgs) cfor(0)(_ < nTpeArgs, _ + 1) { i => inputs(i) = r.getType().asInstanceOf[STypeVar] } diff --git a/sigmastate/src/main/scala/sigmastate/serialization/transformers/SigmaTransformerSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/transformers/SigmaTransformerSerializer.scala index ab195146af..7e30a5786a 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/transformers/SigmaTransformerSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/transformers/SigmaTransformerSerializer.scala @@ -18,7 +18,7 @@ case class SigmaTransformerSerializer[I <: SigmaPropValue, O <: SigmaPropValue] override def parse(r: SigmaByteReader): SigmaPropValue = { val itemsSize = r.getUInt().toInt - val res = new Array[SigmaPropValue](itemsSize) + val res = ValueSerializer.newArray[SigmaPropValue](itemsSize) cfor(0)(_ < itemsSize, _ + 1) { i => res(i) = r.getValue().asInstanceOf[SigmaPropValue] } diff --git a/sigmastate/src/main/scala/sigmastate/utils/SigmaByteReader.scala b/sigmastate/src/main/scala/sigmastate/utils/SigmaByteReader.scala index c3d1826742..550743cb69 100644 --- a/sigmastate/src/main/scala/sigmastate/utils/SigmaByteReader.scala +++ b/sigmastate/src/main/scala/sigmastate/utils/SigmaByteReader.scala @@ -158,7 +158,7 @@ class SigmaByteReader(val r: Reader, val size = getUInt().toIntExact if (size == 0) Value.EmptySeq // quick short-cut when there is nothing to read else { - val xs = new Array[SValue](size) + val xs = ValueSerializer.newArray[SValue](size) cfor(0)(_ < size, _ + 1) { i => xs(i) = getValue() } diff --git a/sigmastate/src/test/scala/sigmastate/utxo/SerializationRoundTripSpec.scala b/sigmastate/src/test/scala/sigmastate/utxo/SerializationRoundTripSpec.scala index c4d32cfd39..e1d61f3591 100644 --- a/sigmastate/src/test/scala/sigmastate/utxo/SerializationRoundTripSpec.scala +++ b/sigmastate/src/test/scala/sigmastate/utxo/SerializationRoundTripSpec.scala @@ -7,7 +7,9 @@ import scalan.util.BenchmarkUtil import sigmastate.helpers.SigmaTestingCommons import sigmastate.interpreter.{ProverResult, ContextExtension} import sigmastate.serialization.generators.ObjectGenerators +import sigmastate.serialization.ValueSerializer._ import debox.{Buffer => DBuffer} +import sigmastate.lang.exceptions.SerializerException import spire.algebra._ import spire.std.int._ @@ -21,6 +23,18 @@ class SerializationRoundTripSpec extends PropSpec implicit val orderRun = Order.by((r: Run) => r.size) + property("ValueSerializer.newArray") { + newArray[Int](0).length shouldBe 0 + newArray[Int](MaxArrayLength).length shouldBe MaxArrayLength + + // test vector to catch constant changes + MaxArrayLength shouldBe 100000 + + assertExceptionThrown( + newArray[Int](MaxArrayLength + 1), + exceptionLike[SerializerException]("Cannot allocate array of Int")) + } + property("ErgoBoxCandidate: Serializer round trip benchmark") { val runs = DBuffer.empty[Run] forAll(MinSuccessful(20)) { t: ErgoBoxCandidate =>