Skip to content

Commit

Permalink
Serialize SFunc in TypeSerializer
Browse files Browse the repository at this point in the history
  • Loading branch information
kushti committed Sep 3, 2024
1 parent 8e46b69 commit b068b10
Show file tree
Hide file tree
Showing 11 changed files with 145 additions and 66 deletions.
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

0 comments on commit b068b10

Please sign in to comment.