Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[6.0] Serialize SFunc in TypeSerializer #1020

Merged
merged 1 commit into from
Sep 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions core/shared/src/main/scala/sigma/ast/SType.scala
Original file line number Diff line number Diff line change
Expand Up @@ -587,7 +587,7 @@ case class SFunc(tDom: IndexedSeq[SType], tRange: SType, tpeParams: Seq[STypePa
}

object SFunc {
final val FuncTypeCode: TypeCode = TypeCodes.FirstFuncType
final val FuncTypeCode: TypeCode = TypeCodes.FuncType
def apply(tDom: SType, tRange: SType): SFunc = SFunc(Array(tDom), tRange) // HOTSPOT:
val identity = { x: Any => x }
}
Expand Down Expand Up @@ -654,7 +654,6 @@ object SOption extends STypeCompanion {
def apply[T <: SType](implicit elemType: T, ov: Overloaded1): SOption[T] = SOption(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
Expand Down
14 changes: 3 additions & 11 deletions core/shared/src/main/scala/sigma/ast/STypeParam.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,10 @@ package sigma.ast

/** Represents a type parameter in a type system.
*
* @param ident The identifier for this type parameter.
* @param upperBound The upper bound of this type parameter, if exists.
* @param lowerBound The lower bound of this type parameter, if exists.
* @note Type parameters with bounds are currently not supported.
* @param ident The identifier for this type parameter
*/
case class STypeParam(
ident: STypeVar,
upperBound: Option[SType] = None,
lowerBound: Option[SType] = None) {
assert(upperBound.isEmpty && lowerBound.isEmpty, s"Type parameters with bounds are not supported, but found $this")

override def toString = ident.toString + upperBound.fold("")(u => s" <: $u") + lowerBound.fold("")(l => s" >: $l")
case class STypeParam(ident: STypeVar) {
override def toString = ident.toString
}

object STypeParam {
Expand Down
6 changes: 2 additions & 4 deletions core/shared/src/main/scala/sigma/ast/TypeCodes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,8 @@ object TypeCodes {

val LastDataType : TypeCode = TypeCode @@ 111.toByte

/** SFunc types occupy remaining space of byte values [FirstFuncType .. 255] */
val FirstFuncType: TypeCode = TypeCode @@ (LastDataType + 1).toByte

val LastFuncType : TypeCode = TypeCode @@ 255.toByte
/** SFunc type */
val FuncType: TypeCode = TypeCode @@ (LastDataType + 1).toByte

/** We use optimized encoding of constant values to save space in serialization.
* Since Box registers are stored as Constant nodes we save 1 byte for each register.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package sigma.serialization

import debox.cfor
import sigma.VersionContext
import sigma.ast.SCollectionType.{CollectionTypeCode, NestedCollectionTypeCode}
import sigma.ast._
import sigma.serialization.{CoreByteReader, CoreByteWriter, InvalidTypePrefix}
Expand Down Expand Up @@ -101,6 +102,17 @@ class TypeSerializer {
// `Tuple` type with more than 4 items `(Int, Byte, Box, Boolean, Int)`
serializeTuple(tup, w)
}
case SFunc(tDom, tRange, tpeParams) =>
w.put(SFunc.FuncTypeCode)
w.putUByte(tDom.length)
tDom.foreach { st =>
serialize(st, w)
}
serialize(tRange, w)
w.putUByte(tpeParams.length)
tpeParams.foreach { tp =>
serialize(tp.ident, w)
}
case typeIdent: STypeVar => {
w.put(typeIdent.typeCode)
val bytes = typeIdent.name.getBytes(StandardCharsets.UTF_8)
Expand Down Expand Up @@ -189,7 +201,24 @@ class TypeSerializer {
case SHeader.typeCode => SHeader
case SPreHeader.typeCode => SPreHeader
case SGlobal.typeCode => SGlobal
case SFunc.FuncTypeCode if VersionContext.current.isV6SoftForkActivated =>
val tdLength = r.getUByte()

val tDom = (1 to tdLength).map { _ =>
deserialize(r)
}
val tRange = deserialize(r)
val tpeParamsLength = r.getUByte()
val tpeParams = (1 to tpeParamsLength).map { _ =>
val ident = deserialize(r)
require(ident.isInstanceOf[STypeVar])
STypeParam(ident.asInstanceOf[STypeVar])
}
SFunc(tDom, tRange, tpeParams)
// todo: serialize tParams
case _ =>
// todo: 6.0: replace 1008 check with identical behavior but other opcode, to activate
// ReplacedRule(1008 -> new opcode) during 6.0 activation
CheckTypeCode(c.toByte)
NoType
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,28 +24,6 @@ class MethodCallSerializerSpecification extends SerializationSpecification {
roundTripTest(expr)
}

property("MethodCall deserialization round trip for BigInt.nbits") {
def code = {
val bi = BigIntConstant(5)
val expr = MethodCall(bi,
SBigIntMethods.ToNBits,
Vector(),
Map()
)
roundTripTest(expr)
}

VersionContext.withVersions(VersionContext.V6SoftForkVersion, 1) {
code
}

an[ValidationException] should be thrownBy (
VersionContext.withVersions((VersionContext.V6SoftForkVersion - 1).toByte, 1) {
code
}
)
}

property("MethodCall deserialization round trip for Header.checkPow") {
def code = {
val bi = HeaderConstant(headerGen.sample.get)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import sigma.util.Extensions.EcpOps
import sigma.validation.{ChangedRule, DisabledRule, EnabledRule, ReplacedRule, RuleStatus}
import sigma.validation.ValidationRules.FirstRuleId
import ErgoTree.ZeroHeader
import sigma.data.{AvlTreeData, AvlTreeFlags, CAND, CBox, COR, CTHRESHOLD, Digest32Coll, ProveDHTuple, ProveDlog, RType, SigmaBoolean}
import sigma.data.{AvlTreeData, AvlTreeFlags, CAND, CBox, CHeader, COR, CTHRESHOLD, Digest32Coll, ProveDHTuple, ProveDlog, RType, SigmaBoolean}
import sigma.eval.Extensions.{EvalIterableOps, SigmaBooleanOps}
import sigma.eval.SigmaDsl
import sigma.interpreter.{ContextExtension, ProverResult}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ object SigmaPPrint extends PPrinter {
s"SOption[${typeName(ot.elemType)}]"
case _: STuple =>
"STuple"
case _: SFunc =>
s"SFunc"
case _ =>
sys.error(s"Cannot get typeName($tpe)")
}
Expand Down
24 changes: 0 additions & 24 deletions sc/shared/src/test/scala/sigma/LanguageSpecificationV5.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9820,30 +9820,6 @@ class LanguageSpecificationV5 extends LanguageSpecificationBase { suite =>
}
}

property("higher order lambdas") {
val f = existingFeature(
{ (xs: Coll[Int]) =>
val inc = { (x: Int) => x + 1 }

def apply(in: (Int => Int, Int)) = in._1(in._2)

xs.map { (x: Int) => apply((inc, x)) }
},
"""{(xs: Coll[Int]) =>
| val inc = { (x: Int) => x + 1 }
| def apply(in: (Int => Int, Int)) = in._1(in._2)
| xs.map { (x: Int) => apply((inc, x)) }
| }
|""".stripMargin
)

// TODO v6.0: Add support of SFunc in TypeSerializer (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/847)
assertExceptionThrown(
f.verifyCase(Coll[Int](), Expected(Success(Coll[Int]()), 0)),
exceptionLike[MatchError]("(SInt$) => SInt$ (of class sigma.ast.SFunc)")
)
}

override protected def afterAll(): Unit = {
printDebug(CErgoTreeEvaluator.DefaultProfiler.generateReport)
printDebug("==========================================================")
Expand Down
72 changes: 72 additions & 0 deletions sc/shared/src/test/scala/sigma/LanguageSpecificationV6.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import sigma.ast.syntax.TrueSigmaProp
import sigma.ast._
import sigma.data.{CBigInt, CHeader, CBox, ExactNumeric}
import sigma.eval.{CostDetails, SigmaDsl, TracedCost}
import sigma.serialization.ValueCodes.OpCode
import sigma.util.Extensions.{BooleanOps, ByteOps, IntOps, LongOps}
import sigmastate.exceptions.MethodNotFound
import sigmastate.utils.Helpers
Expand Down Expand Up @@ -513,4 +514,75 @@ class LanguageSpecificationV6 extends LanguageSpecificationBase { suite =>
)
}

property("higher order lambdas") {
val f = newFeature[Coll[Int], Coll[Int]](
{ (xs: Coll[Int]) =>
val inc = { (x: Int) => x + 1 }

def apply(in: (Int => Int, Int)) = in._1(in._2)

xs.map { (x: Int) => apply((inc, x)) }
},
"""{(xs: Coll[Int]) =>
| val inc = { (x: Int) => x + 1 }
| def apply(in: (Int => Int, Int)) = in._1(in._2)
| xs.map { (x: Int) => apply((inc, x)) }
| }
|""".stripMargin,
FuncValue(
Array((1, SCollectionType(SInt))),
MapCollection(
ValUse(1, SCollectionType(SInt)),
FuncValue(
Array((3, SInt)),
Apply(
FuncValue(
Array((5, SPair(SFunc(Array(SInt), SInt, List()), SInt))),
Apply(
SelectField.typed[Value[SFunc]](
ValUse(5, SPair(SFunc(Array(SInt), SInt, List()), SInt)),
1.toByte
),
Array(
SelectField.typed[Value[SInt.type]](
ValUse(5, SPair(SFunc(Array(SInt), SInt, List()), SInt)),
2.toByte
)
)
)
),
Array(
Tuple(
Vector(
FuncValue(
Array((5, SInt)),
ArithOp(ValUse(5, SInt), IntConstant(1), OpCode @@ (-102.toByte))
),
ValUse(3, SInt)
)
)
)
)
)
)
),
sinceVersion = VersionContext.V6SoftForkVersion
)

verifyCases(
Seq(
Coll(1, 2) -> Expected(
Success(Coll(2, 3)),
cost = 1793,
expectedDetails = CostDetails.ZeroCost
)
),
f,
preGeneratedSamples = Some(Seq(
Coll(Int.MinValue, Int.MaxValue - 1),
Coll(0, 1, 2, 3, 100, 1000)
))
)
}

}
11 changes: 9 additions & 2 deletions sc/shared/src/test/scala/sigma/SigmaDslTesting.scala
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ class SigmaDslTesting extends AnyPropSpec
s"""Should succeed with the same value or fail with the same exception, but was:
|First result: $b1
|Second result: $b2
|Input: $x
|Root cause: $cause
|""".stripMargin)
}
Expand Down Expand Up @@ -715,11 +716,17 @@ class SigmaDslTesting extends AnyPropSpec
override def checkEquality(input: A, logInputOutput: Boolean = false): Try[(B, CostDetails)] = {
// check the old implementation against Scala semantic function
var oldRes: Try[(B, CostDetails)] = null
if (ergoTreeVersionInTests < VersionContext.JitActivationVersion)
oldRes = VersionContext.withVersions(activatedVersionInTests, ergoTreeVersionInTests) {
try checkEq(scalaFunc)(oldF)(input)
catch {
case e: TestFailedException => throw e
case e: TestFailedException =>
if(activatedVersionInTests < changedInVersion) {
throw e
} else {
// old ergoscript may succeed in new version while old scalafunc may fail,
// see e.g. "Option.getOrElse with lazy default" test
Failure(e)
}
case t: Throwable =>
Failure(t)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,32 @@ class BasicOpsSpecification extends CompilerTestingCommons
rootCause(_).isInstanceOf[NoSuchElementException])
}

property("higher order lambdas") {
def holTest() = test("HOL", env, ext,
"""
| {
| val c = Coll(Coll(1))
| def fn(xs: Coll[Int]) = {
| val inc = { (x: Int) => x + 1 }
| def apply(in: (Int => Int, Int)) = in._1(in._2)
| val ys = xs.map { (x: Int) => apply((inc, x)) }
| ys.size == xs.size && ys != xs
| }
|
| c.exists(fn)
| }
|""".stripMargin,
null,
true
)

if(VersionContext.current.isV6SoftForkActivated) {
holTest()
} else {
an[Exception] shouldBe thrownBy(holTest())
}
}

property("OptionGetOrElse") {
test("OptGet1", env, ext,
"{ SELF.R5[Int].getOrElse(3) == 1 }",
Expand Down
Loading