Skip to content

Commit

Permalink
v6.0-serialize: cost accumulation in SigmaByteWriter and serialize_ev…
Browse files Browse the repository at this point in the history
…al method
  • Loading branch information
aslesarenko committed May 29, 2024
1 parent a5321af commit 432addc
Show file tree
Hide file tree
Showing 8 changed files with 243 additions and 153 deletions.
115 changes: 28 additions & 87 deletions core/shared/src/main/scala/sigma/serialization/CoreByteWriter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,188 +3,132 @@ package sigma.serialization
import scorex.util.serialization.Writer.Aux
import scorex.util.serialization.{VLQByteBufferWriter, Writer}
import sigma.ast.SType
import sigma.serialization.CoreByteWriter.{Bits, CostLimitChecker, DataInfo, U, Vlq, ZigZag}
import sigma.serialization.CoreByteWriter._

/** Implementation of [[Writer]] provided by `sigma-core` module.
*
* @param w destination [[Writer]] to which all the call got delegated.
* @param checkCostLimitOpt callback to check if the cost limit at current writing position
* is reached. The callback will throw an exception if the limit is reached.
* Note, the cost of serialization is approximated using formula
* `this.length * CostPerByte + InitialCost`
*/
class CoreByteWriter(val w: Writer, val checkCostLimitOpt: Option[CostLimitChecker]) extends Writer {
class CoreByteWriter(val w: Writer) extends Writer {
type CH = w.CH

/** Check the current writer length against the cost limit. */
@inline protected def checkCostLimit(): Unit = {
if (checkCostLimitOpt.isDefined)
checkCostLimitOpt.get(this.length())
}

@inline override def length(): Int = w.length()

@inline override def newWriter(): Aux[CH] = w.newWriter()

@inline override def putChunk(chunk: CH): this.type = {
w.putChunk(chunk);
checkCostLimit()
this
w.putChunk(chunk); this
}

@inline override def result(): CH = w.result()

@inline def put(x: Byte): this.type = {
w.put(x);
checkCostLimit()
this
@inline override def put(x: Byte): this.type = {
w.put(x); this
}

@inline def put(x: Byte, info: DataInfo[Byte]): this.type = {
w.put(x);
checkCostLimit()
this
w.put(x); this
}

override def putUByte(x: Int): this.type = {
super.putUByte(x)
}

def putUByte(x: Int, info: DataInfo[U[Byte]]): this.type = {
super.putUByte(x)
}

@inline def putBoolean(x: Boolean): this.type = {
w.putBoolean(x);
checkCostLimit()
this
w.putBoolean(x); this
}

@inline def putBoolean(x: Boolean, info: DataInfo[Boolean]): this.type = {
w.putBoolean(x);
checkCostLimit()
this
w.putBoolean(x); this
}

@inline def putShort(x: Short): this.type = {
w.putShort(x);
checkCostLimit()
this
w.putShort(x); this
}

@inline def putShort(x: Short, info: DataInfo[Short]): this.type = {
w.putShort(x);
checkCostLimit()
this
w.putShort(x); this
}

@inline def putUShort(x: Int): this.type = {
w.putUShort(x);
checkCostLimit()
this
w.putUShort(x); this
}

@inline def putUShort(x: Int, info: DataInfo[Vlq[U[Short]]]): this.type = {
w.putUShort(x);
checkCostLimit()
this
w.putUShort(x); this
}

@inline def putInt(x: Int): this.type = {
w.putInt(x);
checkCostLimit()
this
w.putInt(x); this
}

@inline def putInt(x: Int, info: DataInfo[Int]): this.type = {
w.putInt(x);
checkCostLimit()
this
w.putInt(x); this
}

@inline def putUInt(x: Long): this.type = {
w.putUInt(x);
checkCostLimit()
this
w.putUInt(x); this
}
@inline def putUInt(x: Long, info: DataInfo[Vlq[U[Int]]]): this.type = {
w.putUInt(x);
checkCostLimit()
this
w.putUInt(x); this
}

@inline def putLong(x: Long): this.type = {
w.putLong(x);
checkCostLimit()
this
w.putLong(x); this
}
@inline def putLong(x: Long, info: DataInfo[Vlq[ZigZag[Long]]]): this.type = {
w.putLong(x);
checkCostLimit()
this
w.putLong(x); this
}

@inline def putULong(x: Long): this.type = {
w.putULong(x);
checkCostLimit()
this
w.putULong(x); this
}

@inline def putULong(x: Long, info: DataInfo[Vlq[U[Long]]]): this.type = {
w.putULong(x);
checkCostLimit()
this
w.putULong(x); this
}

override def putBytes(xs: Array[Byte],
offset: Int,
length: Int): this.type = {
w.putBytes(xs, offset, length);
checkCostLimit()
this
w.putBytes(xs, offset, length); this
}

@inline def putBytes(xs: Array[Byte]): this.type = {
w.putBytes(xs);
checkCostLimit()
this
w.putBytes(xs); this
}
@inline def putBytes(xs: Array[Byte], info: DataInfo[Array[Byte]]): this.type = {
w.putBytes(xs);
checkCostLimit()
this
w.putBytes(xs); this
}

/** Put the two bytes of the big-endian representation of the Short value into the
* writer. */
@inline def putShortBytes(value: Short): this.type = {
w.put((value >> 8).toByte)
w.put(value.toByte)
checkCostLimit()
this
}

@inline def putBits(xs: Array[Boolean]): this.type = {
w.putBits(xs);
checkCostLimit()
this
w.putBits(xs); this
}
@inline def putBits(xs: Array[Boolean], info: DataInfo[Bits]): this.type = {
w.putBits(xs);
checkCostLimit()
this
w.putBits(xs); this
}

@inline def putOption[T](x: Option[T])(putValueC: (this.type, T) => Unit): this.type = {
w.putOption(x) { (_, v) =>
putValueC(this, v)
}
checkCostLimit()
this
}

@inline def putShortString(s: String): this.type = {
w.putShortString(s);
checkCostLimit()
this
}

Expand All @@ -194,11 +138,11 @@ class CoreByteWriter(val w: Writer, val checkCostLimitOpt: Option[CostLimitCheck
}

@inline def putType[T <: SType](x: T): this.type = {
TypeSerializer.serialize(x, this); // the cost is checked in TypeSerializer
TypeSerializer.serialize(x, this)
this
}
@inline def putType[T <: SType](x: T, info: DataInfo[SType]): this.type = {
TypeSerializer.serialize(x, this); // the cost is checked in TypeSerializer
TypeSerializer.serialize(x, this)
this
}

Expand All @@ -207,9 +151,6 @@ class CoreByteWriter(val w: Writer, val checkCostLimitOpt: Option[CostLimitCheck
object CoreByteWriter {
import scala.language.implicitConversions

/** Callback type of cost limit checker. */
type CostLimitChecker = Int => Unit

/** Format descriptor type family. */
trait FormatDescriptor[T] {
/** Size formula associated with this format */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ object CoreSerializer {
def startWriter(): CoreByteWriter = {
val b = new ByteArrayBuilder()
val wi = new VLQByteBufferWriter(b)
val w = new CoreByteWriter(wi, None)
val w = new CoreByteWriter(wi)
w
}

Expand Down
2 changes: 1 addition & 1 deletion data/shared/src/main/scala/sigma/ast/ErgoTree.scala
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ object ErgoTree {
* */
def withSegregation(header: HeaderType, prop: SigmaPropValue): ErgoTree = {
val constantStore = new ConstantStore()
val w = SigmaSerializer.startWriter(constantStore)
val w = SigmaSerializer.startWriter(Some(constantStore))
// serialize value and segregate constants into constantStore
ValueSerializer.serialize(prop, w)
val extractedConstants = constantStore.getAll
Expand Down
15 changes: 12 additions & 3 deletions data/shared/src/main/scala/sigma/ast/methods.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import sigma.data.{DataValueComparer, KeyValueColl, Nullable, RType, SigmaConsta
import sigma.eval.{CostDetails, ErgoTreeEvaluator, TracedCost}
import sigma.reflection.RClass
import sigma.serialization.CoreByteWriter.ArgInfo
import sigma.serialization.{DataSerializer, SigmaSerializer}
import sigma.utils.SparseArrayContainer

import scala.annotation.unused
Expand Down Expand Up @@ -1532,9 +1533,17 @@ case object SGlobalMethods extends MonoTypeMethods {
*/
def serialize_eval(mc: MethodCall, G: SigmaDslBuilder, value: SType#WrappedType)
(implicit E: ErgoTreeEvaluator): Coll[Byte] = {
// TODO v6.0: accumulate cost
val t = Evaluation.stypeToRType(mc.args(0).tpe)
G.serialize(value)(t)

val addFixedCostCallback = { (costInfo: OperationCostInfo[FixedCost]) =>
E.addCost(costInfo)
}
val addPerItemCostCallback = { (info: OperationCostInfo[PerItemCost], nItems: Int) =>
E.addSeqCostNoOp(info.costKind, nItems, info.opDesc)
}
val w = SigmaSerializer.startWriter(None,
Some(addFixedCostCallback), Some(addPerItemCostCallback))
DataSerializer.serialize(value, mc.args(0).tpe, w)
Colls.fromArray(w.toBytes)
}

protected override def getMethods() = super.getMethods() ++ {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ class ErgoTreeSerializer {
val newVal = newVals(positions.indexOf(i))
// we need to get newVal's serialized constant value (see ProveDlogSerializer for example)
val constantStore = new ConstantStore()
val valW = SigmaSerializer.startWriter(constantStore)
val valW = SigmaSerializer.startWriter(Some(constantStore))
valW.putValue(newVal)
val newConsts = constantStore.getAll
require(newConsts.length == 1)
Expand Down
Loading

0 comments on commit 432addc

Please sign in to comment.