Skip to content

Commit

Permalink
mmerging w. 6.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
kushti committed Aug 14, 2024
2 parents 2bf10bb + 2cfd2ff commit b4aa573
Show file tree
Hide file tree
Showing 13 changed files with 824 additions and 634 deletions.
59 changes: 43 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,40 @@ 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).
* @param userDefinedInvoke optional custom method evaluation function
*/
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 +115,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 +160,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 +274,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 +309,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
3 changes: 1 addition & 2 deletions data/shared/src/main/scala/sigma/ast/SigmaPredef.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import org.ergoplatform.{ErgoAddressEncoder, P2PKAddress}
import scorex.util.encode.{Base16, Base58, Base64}
import sigma.ast.SCollection.{SByteArray, SIntArray}
import sigma.ast.SOption.SIntOption
import sigma.ast.SigmaPropConstant
import sigma.ast.syntax._
import sigma.data.Nullable
import sigma.exceptions.InvalidArguments
Expand Down Expand Up @@ -544,7 +543,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
7 changes: 4 additions & 3 deletions data/shared/src/main/scala/sigma/ast/methods.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1536,9 +1536,10 @@ case object SGlobalMethods extends MonoTypeMethods {
lazy val desJava = ownerType.reprClass.getMethod("deserializeTo", classOf[SType], classOf[Coll[Byte]], classOf[RType[_]])

lazy val deserializeToMethod = SMethod(
this, "deserializeTo", SFunc(Array(SGlobal, SByteArray), tT, Array(paramT)), 3, Xor.costKind) // todo: cost
.copy(irInfo = MethodIRInfo(None, Some(desJava), None))
.withInfo(Xor, "Byte-wise XOR of two collections of bytes", // todo: desc
this, "deserializeTo", SFunc(Array(SGlobal, SByteArray), tT, Array(paramT)), 3, Xor.costKind, Seq(tT)) // todo: cost
.withIRInfo(MethodCallIrBuilder, desJava)
// .copy(irInfo = MethodIRInfo(None, Some(desJava), None))
.withInfo(MethodCall, "Byte-wise XOR of two collections of bytes", // todo: desc
ArgInfo("left", "left operand"), ArgInfo("right", "right operand"))

/** Implements evaluation of Global.xor method call ErgoTree node.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,9 @@ class ErgoTreeSerializer {
* allow to use serialized scripts as pre-defined templates.
* See [[SubstConstants]] for details.
*
* Note, this operation doesn't require (de)serialization of ErgoTree expression,
* thus it is more efficient than serialization roundtrip.
*
* @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
Expand All @@ -304,39 +307,62 @@ class ErgoTreeSerializer {
s"expected positions and newVals to have the same length, got: positions: ${positions.toSeq},\n newVals: ${newVals.toSeq}")
val r = SigmaSerializer.startReader(scriptBytes)
val (header, _, constants, treeBytes) = deserializeHeaderWithTreeBytes(r)
val w = SigmaSerializer.startWriter()
w.put(header)
val nConstants = constants.length

val resBytes = if (VersionContext.current.isJitActivated) {
// need to measure the serialized size of the new constants
// by serializing them into a separate writer
val constW = SigmaSerializer.startWriter()

if (VersionContext.current.isJitActivated) {
// The following `constants.length` should not be serialized when segregation is off
// in the `header`, because in this case there is no `constants` section in the
// ErgoTree serialization format. Thus, applying this `substituteConstants` for
// non-segregated trees will return non-parsable ErgoTree bytes (when
// `constants.length` is put in `w`).
if (ErgoTree.isConstantSegregation(header)) {
w.putUInt(constants.length)
constW.putUInt(constants.length)
}

// The following is optimized O(nConstants + position.length) implementation
val nConstants = constants.length
if (nConstants > 0) {
val backrefs = getPositionsBackref(positions, nConstants)
cfor(0)(_ < nConstants, _ + 1) { i =>
val c = constants(i)
val iPos = backrefs(i) // index to `positions`
if (iPos == -1) {
// no position => no substitution, serialize original constant
constantSerializer.serialize(c, w)
constantSerializer.serialize(c, constW)
} else {
assert(positions(iPos) == i) // INV: backrefs and positions are mutually inverse
require(positions(iPos) == i) // INV: backrefs and positions are mutually inverse
val newConst = newVals(iPos)
require(c.tpe == newConst.tpe,
s"expected new constant to have the same ${c.tpe} tpe, got ${newConst.tpe}")
constantSerializer.serialize(newConst, w)
constantSerializer.serialize(newConst, constW)
}
}
}

val constBytes = constW.toBytes // nConstants + serialized new constants

// start composing the resulting tree bytes
val w = SigmaSerializer.startWriter()
w.put(header) // header byte

if (VersionContext.current.isV6SoftForkActivated) {
// fix in v6.0 to save tree size to respect size bit of the original tree
if (ErgoTree.hasSize(header)) {
val size = constBytes.length + treeBytes.length
w.putUInt(size) // tree size
}
}

w.putBytes(constBytes) // constants section
w.putBytes(treeBytes) // tree section
w.toBytes
} else {
val w = SigmaSerializer.startWriter()
w.put(header)

// for v4.x compatibility we save constants.length here (see the above comment to
// understand the consequences)
w.putUInt(constants.length)
Expand All @@ -357,10 +383,12 @@ class ErgoTreeSerializer {
case (c, _) =>
constantSerializer.serialize(c, w)
}

w.putBytes(treeBytes)
w.toBytes
}

w.putBytes(treeBytes)
(w.toBytes, constants.length)
(resBytes, nConstants)
}

}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
package sigma.serialization

import sigma.ast.syntax._
import sigma.ast.{MethodCall, SContextMethods, SGlobalMethods, SMethod, SType, STypeSubst, Value, ValueCompanion}
import sigma.ast.{MethodCall, SContextMethods, SMethod, SType, STypeSubst, STypeVar, Value, ValueCompanion}
import sigma.util.safeNewArray
import SigmaByteWriter._
import debox.cfor
import sigma.ast.SContextMethods.BlockchainContextMethodNames
import sigma.ast.SType.tT
import sigma.serialization.CoreByteWriter.{ArgInfo, DataInfo}

import scala.collection.compat.immutable.ArraySeq

case class MethodCallSerializer(cons: (Value[SType], SMethod, IndexedSeq[Value[SType]], STypeSubst) => Value[SType])
extends ValueSerializer[MethodCall] {
override def opDesc: ValueCompanion = MethodCall
val typeCodeInfo: DataInfo[Byte] = ArgInfo("typeCode", "type of the method (see Table~\\ref{table:predeftypes})")
val methodCodeInfo: DataInfo[Byte] = ArgInfo("methodCode", "a code of the method")
val objInfo: DataInfo[SValue] = ArgInfo("obj", "receiver object of this method call")
val argsInfo: DataInfo[Seq[SValue]] = ArgInfo("args", "arguments of the method call")
val typeInfo: DataInfo[SType] = ArgInfo("type", "expected type of method result")
val argsItemInfo = valuesItemInfo(argsInfo)

override def serialize(mc: MethodCall, w: SigmaByteWriter): Unit = {
Expand All @@ -25,8 +25,9 @@ case class MethodCallSerializer(cons: (Value[SType], SMethod, IndexedSeq[Value[S
w.putValue(mc.obj, objInfo)
assert(mc.args.nonEmpty)
w.putValues(mc.args, argsInfo, argsItemInfo)
if(mc.method.name == SGlobalMethods.deserializeToMethod.name){
w.putType(mc.tpe, typeInfo)
mc.method.explicitTypeArgs.foreach { a =>
val tpe = mc.typeSubst(a) // existence is checked in MethodCall constructor
w.putType(tpe)
}
}

Expand All @@ -48,9 +49,37 @@ case class MethodCallSerializer(cons: (Value[SType], SMethod, IndexedSeq[Value[S
val obj = r.getValue()
val args = r.getValues()
val method = SMethod.fromIds(typeId, methodId)
val nArgs = args.length

val types: Seq[SType] =
val explicitTypes = if (method.hasExplicitTypeArgs) {
val nTypes = method.explicitTypeArgs.length
val res = safeNewArray[SType](nTypes)
cfor(0)(_ < nTypes, _ + 1) { i =>
res(i) = r.getType()
}
ArraySeq.unsafeWrapArray(res)
} else SType.EmptySeq

val explicitTypeSubst = method.explicitTypeArgs.zip(explicitTypes).toMap
val specMethod = getSpecializedMethodFor(method, explicitTypeSubst, obj, args)

var isUsingBlockchainContext = specMethod.objType == SContextMethods &&
BlockchainContextMethodNames.contains(method.name)
r.wasUsingBlockchainContext ||= isUsingBlockchainContext

cons(obj, specMethod, args, explicitTypeSubst)
}

def getSpecializedMethodFor(
methodTemplate: SMethod,
explicitTypeSubst: STypeSubst,
obj: SValue,
args: Seq[SValue]
): SMethod = {
// TODO optimize: avoid repeated transformation of method type (by morphic)
// TODO: should we use the method with .withConcreteTypes done? It breaks ergotree roundtrip tests
val method = methodTemplate.withConcreteTypes(explicitTypeSubst)
val nArgs = args.length
val argTypes: Seq[SType] =
if (nArgs == 0) SType.EmptySeq
else {
val types = safeNewArray[SType](nArgs)
Expand All @@ -60,19 +89,6 @@ case class MethodCallSerializer(cons: (Value[SType], SMethod, IndexedSeq[Value[S
types
}

val specMethod = method.specializeFor(obj.tpe, types)

val subst: STypeSubst = if(method.name == SGlobalMethods.deserializeToMethod.name) {
val tpe = r.getType()
Map(tT -> tpe)
} else {
Map.empty
}

var isUsingBlockchainContext = specMethod.objType == SContextMethods &&
BlockchainContextMethodNames.contains(method.name)
r.wasUsingBlockchainContext ||= isUsingBlockchainContext

cons(obj, specMethod, args, subst)
methodTemplate.specializeFor(obj.tpe, argTypes)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,14 @@ class MethodCallSerializerSpecification extends SerializationSpecification {
val h = HeaderConstant(headerGen.sample.get)
val expr = MethodCall(h,
SGlobalMethods.deserializeToMethod,
Vector(h),
Array(h),
Map(tT -> SHeader)
)
roundTripTest(expr)
}

println(SGlobalMethods.deserializeToMethod.hasExplicitTypeArgs)

VersionContext.withVersions(VersionContext.V6SoftForkVersion, 1) {
code
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,8 @@ class SigmaTyper(val builder: SigmaBuilder,
}
case p: SProduct =>
MethodsContainer.getMethod(p, n) match {
case Some(method @ SMethod(_, _, genFunTpe @ SFunc(_, _, _), _, _, _, _, _)) =>
case Some(method: SMethod) =>
val genFunTpe = method.stype
val subst = Map(genFunTpe.tpeParams.head.ident -> rangeTpe)
val concrFunTpe = applySubst(genFunTpe, subst)
val expectedArgs = concrFunTpe.asFunc.tDom.tail
Expand Down Expand Up @@ -524,6 +525,7 @@ class SigmaTyper(val builder: SigmaBuilder,
case v: SigmaBoolean => v
case v: Upcast[_, _] => v
case v @ Select(_, _, Some(_)) => v
case v @ MethodCall(_, _, _, _) => v
case v =>
error(s"Don't know how to assignType($v)", v.sourceContext)
}).ensuring(v => v.tpe != NoType,
Expand Down
Loading

0 comments on commit b4aa573

Please sign in to comment.