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] Common SMethod and MethodCallSerializer new features #1002

Merged
merged 3 commits into from
Aug 6, 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
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
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package sigma.serialization

import sigma.ast.syntax._
import sigma.ast.{MethodCall, SContextMethods, 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.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
Expand All @@ -23,6 +25,10 @@ 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)
mc.method.explicitTypeArgs.foreach { a =>
val tpe = mc.typeSubst(a) // existence is checked in MethodCall constructor
w.putType(tpe)
}
}

/** The SMethod instances in STypeCompanions may have type STypeIdent in methods types,
Expand All @@ -43,9 +49,36 @@ 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
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 @@ -55,12 +88,6 @@ case class MethodCallSerializer(cons: (Value[SType], SMethod, IndexedSeq[Value[S
types
}

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

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

cons(obj, specMethod, args, Map.empty)
method.specializeFor(obj.tpe, argTypes)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,8 @@ class SigmaTyper(val builder: SigmaBuilder,
obj.tpe match {
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 @@ -511,6 +512,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