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] Implementation of Numeric.toBigEndianBytes #984

Closed
wants to merge 34 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
4004cc9
i486-toBytes: tests added to SigmaTyperTest
aslesarenko May 14, 2024
b55d336
i486-toBytes: ExactNumeric.toBytes implemented
aslesarenko May 14, 2024
9d83125
Merge branch 'develop' into i486-toBytes
aslesarenko May 14, 2024
e865264
Merge remote-tracking branch 'origin/v6.0.0-fix-tests' into i486-toBytes
aslesarenko May 14, 2024
d4ff7b5
Merge remote-tracking branch 'refs/remotes/origin/v6.0.0-refactor-ir-…
aslesarenko May 16, 2024
c1d3ce1
v6.0-serialize: SigmaDslBuilder.serialize added SMethod declaration
aslesarenko May 16, 2024
eeb0d4a
v6.0-serialize: added SMethod.runtimeTypeArgs
aslesarenko May 17, 2024
5cbd065
v6.0-serialize: support of explicitTypeArgs in MethodCallSerializer
aslesarenko May 17, 2024
3f4508a
v6.0-serialize: SigmaParserTest for serialize()
aslesarenko May 17, 2024
06386d4
v6.0-serialize: SigmaBinderTest and SigmaTyperTest
aslesarenko May 17, 2024
f7beab4
v6.0-serialize: refactor SigmaDslSpecification towards LanguageSpecif…
aslesarenko May 17, 2024
cfd6e7b
v6.0-serialize: feature tests for Byte an Short
aslesarenko May 19, 2024
4fb3daa
v6.0-serialize: all new features move to LanguageSpecificationV6
aslesarenko May 19, 2024
d09d735
i994-fix-subst-constants: implementation + tests
aslesarenko May 22, 2024
d4bbccc
v6.0-serialize: fix JS tests (added reflection data)
aslesarenko May 23, 2024
9e312bb
Merge remote-tracking branch 'refs/remotes/origin/v6.0-serialize' int…
aslesarenko May 23, 2024
de18eeb
i994-fix-subst-constants: more tests
aslesarenko May 23, 2024
727be9b
Merge remote-tracking branch 'refs/remotes/origin/i994-fix-subst-cons…
aslesarenko May 23, 2024
3ccf11e
i486-toBytes: fixes after merge
aslesarenko May 23, 2024
e5a32c8
i486-toBytes: Versioned.scala added
aslesarenko May 25, 2024
bba2230
i486-toBytes: ensure VersionContext.current in tests
aslesarenko May 25, 2024
dc361a1
i486-toBytes: ErgoTreeSpecification versioned and updated
aslesarenko May 25, 2024
70786a0
i486-toBytes: clarified description of demotion of SNumericType
aslesarenko May 25, 2024
c76ac1a
i486-toBytes: GraphBuilding to handle numeric methods
aslesarenko May 25, 2024
2427db3
i486-toBytes: added userDefinedInvoke handler to SMethod
aslesarenko May 25, 2024
02e2d06
i486-toBytes: toBigEndianBytes implemented
aslesarenko May 25, 2024
07b8644
moving newFeature related code from serializePR
kushti Jun 6, 2024
dd7bdc6
removing serialize
kushti Jun 7, 2024
4b27c86
merging w. 6.0-newfeature
kushti Jun 7, 2024
e1bfb1c
remove serialize from GraphBuilding and ErgoTreeSpecification
kushti Jun 7, 2024
312167b
merging w. 6.0.0
kushti Jun 10, 2024
a731962
simplifying the code
kushti Jul 5, 2024
f577fd3
passing test (compilation fixed)
kushti Jul 8, 2024
cadf53d
tests for Byte and BigInt
kushti Jul 8, 2024
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
55 changes: 38 additions & 17 deletions core/shared/src/main/scala/sigma/ast/SType.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import sigma.data.OverloadHack.Overloaded1
import sigma.data.{CBigInt, Nullable, SigmaConstants}
import sigma.reflection.{RClass, RMethod, ReflectionData}
import sigma.util.Extensions.{IntOps, LongOps, ShortOps}
import sigma.{AvlTree, BigInt, Box, Coll, Context, Evaluation, GroupElement, Header, PreHeader, SigmaDslBuilder, SigmaProp}
import sigma.{AvlTree, BigInt, Box, Coll, Context, Evaluation, GroupElement, Header, PreHeader, SigmaDslBuilder, SigmaProp, VersionContext}

import java.math.BigInteger

Expand Down Expand Up @@ -113,27 +113,48 @@ object SType {
* typeId this map contains a companion object which can be used to access the list of
* corresponding methods.
*
* NOTE: in the current implementation only monomorphic methods are supported (without
* type parameters)
* @note starting from v6.0 methods with type parameters are also supported.
*
* NOTE2: in v3.x SNumericType.typeId is silently shadowed by SGlobal.typeId as part of
* `toMap` operation. As a result, the methods collected into SByte.methods cannot be
* @note on versioning:
* In v3.x-5.x SNumericType.typeId is silently shadowed by SGlobal.typeId as part of
* `toMap` operation. As a result, SNumericTypeMethods container cannot be resolved by
* typeId = 106, because SNumericType was being silently removed when `_types` map is
* constructed. See `property("SNumericType.typeId resolves to SGlobal")`.
* In addition, the methods associated with the concrete numeric types cannot be
* resolved (using SMethod.fromIds()) for all numeric types (SByte, SShort, SInt,
* SLong, SBigInt). See the corresponding regression `property("MethodCall on numerics")`.
* SLong) because these types are not registered in the `_types` map.
* See the corresponding property("MethodCall on numerics")`.
* However, this "shadowing" is not a problem since all casting methods are implemented
* via Downcast, Upcast opcodes and the remaining `toBytes`, `toBits` methods are not
* implemented at all.
* In order to allow MethodCalls on numeric types in future versions the SNumericType.typeId
* should be changed and SGlobal.typeId should be preserved. The regression tests in
* `property("MethodCall Codes")` should pass.
* via lowering to Downcast, Upcast opcodes and the remaining `toBytes`, `toBits`
* methods are not implemented at all.
*
* Starting from v6.0 the SNumericType.typeId is demoted as a receiver object of
* method calls and:
* 1) numeric type SByte, SShort, SInt, SLong are promoted as receivers and added to
* the _types map.
* 2) all methods from SNumericTypeMethods are copied to all the concrete numeric types
* (SByte, SShort, SInt, SLong, SBigInt) and the generic tNum type parameter is
* specialized accordingly.
*
* This difference in behaviour is tested by `property("MethodCall on numerics")`.
*
* The regression tests in `property("MethodCall Codes")` should pass.
*/
// TODO v6.0: should contain all numeric types (including also SNumericType)
// to support method calls like 10.toByte which encoded as MethodCall with typeId = 4, methodId = 1
// see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/667
lazy val types: Map[Byte, STypeCompanion] = Seq(
SBoolean, SNumericType, SString, STuple, SGroupElement, SSigmaProp, SContext, SGlobal, SHeader, SPreHeader,
private val v5Types = Seq(
SBoolean, SString, STuple, SGroupElement, SSigmaProp, SContext, SGlobal, SHeader, SPreHeader,
SAvlTree, SBox, SOption, SCollection, SBigInt
).map { t => (t.typeId, t) }.toMap
)
private val v6Types = v5Types ++ Seq(SByte, SShort, SInt, SLong)

private val v5TypesMap = v5Types.map { t => (t.typeId, t) }.toMap

private val v6TypesMap = v6Types.map { t => (t.typeId, t) }.toMap

def types: Map[Byte, STypeCompanion] = if (VersionContext.current.isV6SoftForkActivated) {
v6TypesMap
} else {
v5TypesMap
}

/** Checks that the type of the value corresponds to the descriptor `tpe`.
* If the value has complex structure only root type constructor is checked.
Expand Down
6 changes: 6 additions & 0 deletions core/shared/src/main/scala/sigma/ast/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,12 @@ package object ast {

def asNumType: SNumericType = tpe.asInstanceOf[SNumericType]

/** Cast this type to numeric type or else throws the given error. */
def asNumTypeOrElse(error: => Exception): SNumericType = tpe match {
case nt: SNumericType => nt
case _ => throw error
}

def asFunc: SFunc = tpe.asInstanceOf[SFunc]

def asProduct: SProduct = tpe.asInstanceOf[SProduct]
Expand Down
58 changes: 42 additions & 16 deletions data/shared/src/main/scala/sigma/ast/SMethod.scala
Original file line number Diff line number Diff line change
Expand Up @@ -50,31 +50,39 @@ case class MethodIRInfo(

/** Represents method descriptor.
*
* @param objType type or type constructor descriptor
* @param name method name
* @param stype method signature type,
* 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).
* @param objType type or type constructor descriptor
* @param name method name
* @param stype method signature type,
* 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 explicitTypeArgs list of type parameters which require explicit
* serialization in [[MethodCall]]s (i.e for deserialize[T], getVar[T], getReg[T])
* @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: MethodsContainer,
name: String,
stype: SFunc,
methodId: Byte,
costKind: CostKind,
explicitTypeArgs: Seq[STypeVar],
irInfo: MethodIRInfo,
docInfo: Option[OperationInfo],
costFunc: Option[MethodCostFunc]) {
costFunc: Option[MethodCostFunc],
userDefinedInvoke: Option[SMethod.InvokeHandler]
) {

/** Operation descriptor of this method. */
lazy val opDesc = MethodDesc(this)

/** Return true if this method has runtime type parameters */
def hasExplicitTypeArgs: Boolean = explicitTypeArgs.nonEmpty

/** Finds and keeps the [[RMethod]] instance which corresponds to this method descriptor.
* The lazy value is forced only if irInfo.javaMethod == None
*/
Expand Down Expand Up @@ -106,7 +114,12 @@ case class SMethod(
/** 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]): Any = {
javaMethod.invoke(obj, args.asInstanceOf[Array[AnyRef]]:_*)
userDefinedInvoke match {
case Some(h) =>
h(this, obj, args)
case None =>
javaMethod.invoke(obj, args.asInstanceOf[Array[AnyRef]]:_*)
}
}

// TODO optimize: avoid lookup when this SMethod is created via `specializeFor`
Expand Down Expand Up @@ -146,6 +159,11 @@ case class SMethod(
m
}

/** Create a new instance with the given user-defined invoke handler. */
def withUserDefinedInvoke(handler: SMethod.InvokeHandler): SMethod = {
copy(userDefinedInvoke = Some(handler))
}

/** Create a new instance with the given stype. */
def withSType(newSType: SFunc): SMethod = copy(stype = newSType)

Expand Down Expand Up @@ -255,6 +273,12 @@ object SMethod {
*/
type InvokeDescBuilder = SFunc => Seq[SType]

/** Type of user-defined function which is called to handle method invocation.
* Instances of this type can be attached to [[SMethod]] instances.
* @see SNumericTypeMethods.ToBytesMethod
*/
type InvokeHandler = (SMethod, Any, Array[Any]) => Any

/** 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
Expand Down Expand Up @@ -284,10 +308,12 @@ object SMethod {
/** Convenience factory method. */
def apply(objType: MethodsContainer, name: String, stype: SFunc,
methodId: Byte,
costKind: CostKind): SMethod = {
costKind: CostKind,
explicitTypeArgs: Seq[STypeVar] = Nil
): SMethod = {
SMethod(
objType, name, stype, methodId, costKind,
MethodIRInfo(None, None, None), None, None)
objType, name, stype, methodId, costKind, explicitTypeArgs,
MethodIRInfo(None, None, None), None, None, None)
}


Expand Down
2 changes: 1 addition & 1 deletion data/shared/src/main/scala/sigma/ast/SigmaPredef.scala
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,7 @@ object SigmaPredef {

val funcs: Map[String, PredefinedFunc] = globalFuncs ++ infixFuncs ++ unaryFuncs

/** WARNING: This operations are not used in frontend, and should be be used.
/** WARNING: This operations are not used in frontend, and should not be used.
* They are used in SpecGen only the source of metadata for the corresponding ErgoTree nodes.
*/
val specialFuncs: Map[String, PredefinedFunc] = Seq(
Expand Down
37 changes: 33 additions & 4 deletions data/shared/src/main/scala/sigma/ast/methods.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import sigma.ast.SCollection.{SBooleanArray, SBoxArray, SByteArray, SByteArray2,
import sigma.ast.SMethod.{MethodCallIrBuilder, MethodCostFunc, javaMethodOf}
import sigma.ast.SType.TypeCode
import sigma.ast.syntax.{SValue, ValueOps}
import sigma.data.ExactIntegral.{ByteIsExactIntegral, IntIsExactIntegral, LongIsExactIntegral, ShortIsExactIntegral}
import sigma.data.OverloadHack.Overloaded1
import sigma.data.{DataValueComparer, KeyValueColl, Nullable, RType, SigmaConstants}
import sigma.eval.{CostDetails, ErgoTreeEvaluator, TracedCost}
Expand Down Expand Up @@ -156,9 +157,28 @@ trait MonoTypeMethods extends MethodsContainer {

trait SNumericTypeMethods extends MonoTypeMethods {
import SNumericTypeMethods.tNum

private val subst = Map(tNum -> this.ownerType)

private val v5Methods = {
SNumericTypeMethods.methods.map { m =>
m.copy(stype = applySubst(m.stype, subst).asFunc)
}
}

private val v6Methods = {
SNumericTypeMethods.methods.map { m =>
m.copy(
objType = this, // associate the method with the concrete numeric type
stype = applySubst(m.stype, subst).asFunc
)}
}

protected override def getMethods(): Seq[SMethod] = {
super.getMethods() ++ SNumericTypeMethods.methods.map {
m => m.copy(stype = applySubst(m.stype, Map(tNum -> this.ownerType)).asFunc)
if (VersionContext.current.isV6SoftForkActivated) {
super.getMethods() ++ v6Methods
} else {
super.getMethods() ++ v5Methods
}
}
}
Expand Down Expand Up @@ -216,6 +236,15 @@ object SNumericTypeMethods extends MethodsContainer {
val ToBytesMethod: SMethod = SMethod(
this, "toBytes", SFunc(tNum, SByteArray), 6, ToBytes_CostKind)
.withIRInfo(MethodCallIrBuilder)
.withUserDefinedInvoke({ (m: SMethod, obj: Any, _: Array[Any]) =>
m.objType match {
case SByteMethods => ByteIsExactIntegral.toBigEndianBytes(obj.asInstanceOf[Byte])
case SShortMethods => ShortIsExactIntegral.toBigEndianBytes(obj.asInstanceOf[Short])
case SIntMethods => IntIsExactIntegral.toBigEndianBytes(obj.asInstanceOf[Int])
case SLongMethods => LongIsExactIntegral.toBigEndianBytes(obj.asInstanceOf[Long])
case SBigIntMethods => obj.asInstanceOf[BigInt].toBytes
}
})
.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
Expand Down Expand Up @@ -312,8 +341,8 @@ case object SBigIntMethods extends SNumericTypeMethods {
final val ToNBitsCostInfo = OperationCostInfo(
FixedCost(JitCost(5)), NamedDesc("NBitsMethodCall"))

//id = 8 to make it after toBits
val ToNBits = SMethod(this, "nbits", SFunc(this.ownerType, SLong), 8, ToNBitsCostInfo.costKind)
//id = 20 to make it after toBits and reserve space for future methods at SNumericTypeMethods
val ToNBits = SMethod(this, "nbits", SFunc(this.ownerType, SLong), 20, ToNBitsCostInfo.costKind)
.withInfo(ModQ, "Encode this big integer value as NBits")

/** The following `modQ` methods are not fully implemented in v4.x and this descriptors.
Expand Down
2 changes: 2 additions & 0 deletions data/shared/src/main/scala/sigma/ast/values.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1298,6 +1298,8 @@ case class MethodCall(
method: SMethod,
args: IndexedSeq[Value[SType]],
typeSubst: Map[STypeVar, SType]) extends Value[SType] {
require(method.explicitTypeArgs.forall(tyArg => typeSubst.contains(tyArg)),
s"Runtime Generic method call should have concrete type for each runtime type parameter, but was: $this")
override def companion = if (args.isEmpty) PropertyCall else MethodCall

override def opType: SFunc = SFunc(obj.tpe +: args.map(_.tpe), tpe)
Expand Down
3 changes: 3 additions & 0 deletions data/shared/src/main/scala/sigma/data/BigIntegerOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package sigma.data

import sigma._
import sigma.eval.Extensions.IntExt
import sigma.util.Extensions.BigIntOps

import scala.math.{Integral, Ordering}

Expand Down Expand Up @@ -89,6 +90,8 @@ object NumericOps {
* NOTE: This method should not be used in v4.x
*/
override def divisionRemainder(x: BigInt, y: BigInt): BigInt = x.mod(y)

override def toBigEndianBytes(x: BigInt): Coll[Byte] = Colls.fromArray(x.toBigInteger.toByteArray)
}

/** The instance of [[scalan.ExactOrdering]] typeclass for [[BigInt]]. */
Expand Down
7 changes: 7 additions & 0 deletions data/shared/src/main/scala/sigma/data/ExactIntegral.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package sigma.data

import sigma.{Coll, Colls}
import sigma.util.Extensions.{ByteOps, ShortOps}

/** Type-class which defines the operations on Integral types (Byte, Short, Int, Long, BigInt)
Expand Down Expand Up @@ -37,26 +38,32 @@ object ExactIntegral {
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)
override def toBigEndianBytes(x: Byte): Coll[Byte] = Colls.fromItems(x)
}

implicit object ShortIsExactIntegral extends ExactIntegral[Short] {
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)
override def toBigEndianBytes(x: Short): Coll[Byte] = Colls.fromItems((x >> 8).toByte, x.toByte)
}

implicit object IntIsExactIntegral extends ExactIntegral[Int] {
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)
override def toBigEndianBytes(x: Int): Coll[Byte] =
Colls.fromItems((x >> 24).toByte, (x >> 16).toByte, (x >> 8).toByte, x.toByte)
}

implicit object LongIsExactIntegral extends ExactIntegral[Long] {
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)
override def toBigEndianBytes(x: Long): Coll[Byte] =
Colls.fromItems((x >> 56).toByte, (x >> 48).toByte, (x >> 40).toByte, (x >> 32).toByte, (x >> 24).toByte, (x >> 16).toByte, (x >> 8).toByte, x.toByte)
}
}
7 changes: 7 additions & 0 deletions data/shared/src/main/scala/sigma/data/ExactNumeric.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package sigma.data

import sigma.Coll
import sigma.data.ExactIntegral._

/** Numeric operations with overflow checks.
Expand Down Expand Up @@ -30,6 +31,12 @@ trait ExactNumeric[T] {
def toInt(x: T): Int = n.toInt(x)
def toLong(x: T): Long = n.toLong(x)

/** Returns a big-endian representation of this value in a collection of bytes.
* For example, the `Int` value `0x12131415` would yield the
* collection of bytes [0x12, 0x13, 0x14, 0x15]
*/
def toBigEndianBytes(x: T): Coll[Byte]

/** A value of type T which corresponds to integer 0. */
lazy val zero: T = fromInt(0)

Expand Down
Loading
Loading