From 203899826e5f4a77532f85511417f3fa86ff3927 Mon Sep 17 00:00:00 2001 From: Remy Haemmerle Date: Mon, 4 Aug 2025 18:04:13 +0200 Subject: [PATCH 1/3] Initial cost model --- .../daml/lf/speedy/CostModel.scala | 193 ++++++++++++++++++ .../digitalasset/daml/lf/speedy/Speedy.scala | 2 + 2 files changed, 195 insertions(+) create mode 100644 sdk/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/CostModel.scala diff --git a/sdk/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/CostModel.scala b/sdk/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/CostModel.scala new file mode 100644 index 000000000000..a8a939bcf0c4 --- /dev/null +++ b/sdk/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/CostModel.scala @@ -0,0 +1,193 @@ +// Copyright (c) 2025 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.digitalasset.daml.lf + +import com.digitalasset.daml.lf.speedy.SValue +import com.digitalasset.daml.lf.speedy.SValue._ +import value.Value +import data.Ref +import sttp.tapir.SchemaType.SOption + +abstract class CostModel { + + import CostModel._ + + val AddNumeric: Function2[Numeric, Numeric] + val SubNumeric: Function2[Numeric, Numeric] + val MulNumeric: Function3[Numeric, Numeric, Numeric] + val DivNumeric: Function3[Numeric, Numeric, Numeric] + val BRoundNumeric: Function2[Int, Numeric] + val BCastNumeric: Function3[Numeric, Numeric, Numeric] + val BShiftNumeric: Function3[Numeric, Numeric, Numeric] + val BAddInt64: Function2[Int64, Int64] + val BSubInt64: Function2[Int64, Int64] + val BMulInt64: Function2[Int64, Int64] + val BDivInt64: Function2[Int64, Int64] + val BModInt64: Function2[Int64, Int64] + val BExpInt64: Function2[Int64, Int64] + val BInt64ToNumeric: Function2[Numeric, Int64] + val BNumericToInt64: Function1[Numeric] + val BFoldl = NotDefined + val BFoldr = NotDefined + lazy val BTextMapEmpty = BGenMapEmpty + lazy val BTextMapInsert: Function3[Value, Value, TextMap] = BGenMapInsert + lazy val BTextMapLookup: Function2[Value, TextMap] = BGenMapLookup + lazy val BTextMapDelete: Function2[Value, TextMap] = BGenMapDelete + val BTextMapToList: Function1[TextMap] + lazy val BTextMapSize = BGenMapSize + val BGenMapEmpty = NotDefined + val BGenMapInsert: Function3[Value, Value, GenMap] + val BGenMapLookup: Function2[Value, GenMap] + val BGenMapDelete: Function2[Value, GenMap] + val BGenMapKeys: Function1[GenMap] + val BGenMapValues: Function1[GenMap] + val BGenMapSize: Function1[GenMap] + val BAppendText: Function2[Text, Text] + val BError = NotDefined + val BInt64ToText: Function1[Int64] + val BNumericToText: Function1[Numeric] + val BTimestampToText: Function1[Timestamp] + val BPartyToText: Function1[Party] + val BContractIdToText: Function1[ContractId] + val BCodePointsToText: Function1[List] + val BTextToParty: Function1[Text] + val BTextToInt64: Function1[Text] + val BTextToNumeric: Function1[Text] + val BTextToCodePoints: Function1[Text] + val BSHA256Text: Function1[Text] + val BKECCAK256Text: Function1[Text] + val BSECP256K1Bool: Function1[Text] + val BDecodeHex: Function1[Text] + val BEncodeHex: Function1[Text] + val BTextToContractId: Function1[Text] + val BDateToUnixDays: Function1[Date] + val BExplodeText: Function1[Text] + val BImplodeText = CostAware + val BTimestampToUnixMicroseconds: Function1[Timestamp] + val BDateToText: Function1[Date] + val BUnixDaysToDate: Function1[Int64] + val BUnixMicrosecondsToTimestamp: Function1[Int64] + val BEqual: Function2[Value, Value] + val BLess: Function2[Value, Value] + val BLessEq: Function2[Value, Value] + val BGreater: Function2[Value, Value] + val BGreaterEq: Function2[Value, Value] + val BEqualList = NotDefined + val BTrace = NotDefined + val BCoerceContractId: Function1[ContractId] + val BScaleBigNumeric = NotDefined // Available in 2.dev + val BPrecisionBigNumeric = NotDefined // Available in 2.dev + val BAddBigNumeric = NotDefined // Available in 2.dev + val BSubBigNumeric = NotDefined // Available in 2.dev + val BMulBigNumeric = NotDefined // Available in 2.dev + val BDivBigNumeric = NotDefined // Available in 2.dev + val BShiftRightBigNumeric = NotDefined // Available in 2.dev + val BBigNumericToNumeric = NotDefined // Available in 2.dev + val BNumericToBigNumeric = NotDefined // Available in 2.dev + val BBigNumericToText = NotDefined // Available in 2.dev + val BAnyExceptionMessage = NotDefined + val BTypeRepTyConName = NotDefined + val BFailWithStatus = NotDefined + +} + +object CostModel { + + type Cost = Int + + type Int64 = Long + type Numeric = data.Numeric + type Text = String + type Date = Int + type Timestamp = Long + type TextMap = SMap + type GenMap = SMap + type List = SList + type ContractId = value.Value.ContractId + type Value = _ + type Party = Ref.Party + + object NotDefined + + object CostAware + + sealed abstract class Function1[X] { + def cost(x: X): Cost + } + + case class Constant1[X](c: Cost) extends Function1[X] { + override def cost(x: X): Cost = c + } + + sealed abstract class Function2[X, Y] { + def cost(x: X, y: Y): Cost + } + + case class Constant2[X, Y](c: Cost) extends Function2[X, Y] { + override def cost(x: X, y: Y): Cost = c + } + + sealed abstract class Function3[X, Y, Z] { + def cost(x: Y, y: Y, z: Z): Cost + } + + case class Constant3[X, Y, Z](c: Cost) extends Function3[X, Y, Z] { + override def cost(x: Y, y: Y, z: Z): Cost = c + } + + object Empty extends CostModel { + override val AddNumeric: Function2[Numeric, Numeric] = Constant2(0) + override val SubNumeric: Function2[Numeric, Numeric] = Constant2(0) + override val MulNumeric: Function3[Numeric, Numeric, Numeric] = Constant3(0) + override val DivNumeric: Function3[Numeric, Numeric, Numeric] = Constant3(0) + override val BRoundNumeric: Function2[Cost, Numeric] = Constant2(0) + override val BCastNumeric: Function3[Numeric, Numeric, Numeric] = Constant3(0) + override val BShiftNumeric: Function3[Numeric, Numeric, Numeric] = Constant3(0) + override val BAddInt64: Function2[Int64, Int64] = Constant2(0) + override val BSubInt64: Function2[Int64, Int64] = Constant2(0) + override val BMulInt64: Function2[Int64, Int64] = Constant2(0) + override val BDivInt64: Function2[Int64, Int64] = Constant2(0) + override val BModInt64: Function2[Int64, Int64] = Constant2(0) + override val BExpInt64: Function2[Int64, Int64] = Constant2(0) + override val BInt64ToNumeric: Function2[Numeric, Int64] = Constant2(0) + override val BNumericToInt64: Function1[Numeric] = Constant1(0) + override val BTextMapToList: Function1[TextMap] = Constant1(0) + override val BGenMapInsert: Function3[Any, Any, GenMap] = Constant3(0) + override val BGenMapLookup: Function2[Any, GenMap] = Constant2(0) + override val BGenMapDelete: Function2[Any, GenMap] = Constant2(0) + override val BGenMapKeys: Function1[GenMap] = Constant1(0) + override val BGenMapValues: Function1[GenMap] = Constant1(0) + override val BGenMapSize: Function1[GenMap] = Constant1(0) + override val BAppendText: Function2[Text, Text] = Constant2(0) + override val BInt64ToText: Function1[Int64] = Constant1(0) + override val BNumericToText: Function1[Numeric] = Constant1(0) + override val BTimestampToText: Function1[Timestamp] = Constant1(0) + override val BPartyToText: Function1[Party] = Constant1(0) + override val BContractIdToText: Function1[ContractId] = Constant1(0) + override val BCodePointsToText: Function1[List] = Constant1(0) + override val BTextToParty: Function1[Text] = Constant1(0) + override val BTextToInt64: Function1[Text] = Constant1(0) + override val BTextToNumeric: Function1[Text] = Constant1(0) + override val BTextToCodePoints: Function1[Text] = Constant1(0) + override val BSHA256Text: Function1[Text] = Constant1(0) + override val BKECCAK256Text: Function1[Text] = Constant1(0) + override val BSECP256K1Bool: Function1[Text] = Constant1(0) + override val BDecodeHex: Function1[Text] = Constant1(0) + override val BEncodeHex: Function1[Text] = Constant1(0) + override val BTextToContractId: Function1[Text] = Constant1(0) + override val BDateToUnixDays: Function1[Date] = Constant1(0) + override val BExplodeText: Function1[Text] = Constant1(0) + override val BTimestampToUnixMicroseconds: Function1[Timestamp] = Constant1(0) + override val BDateToText: Function1[Date] = Constant1(0) + override val BUnixDaysToDate: Function1[Int64] = Constant1(0) + override val BUnixMicrosecondsToTimestamp: Function1[Int64] = Constant1(0) + override val BEqual: Function2[Any, Any] = Constant2(0) + override val BLess: Function2[Any, Any] = Constant2(0) + override val BLessEq: Function2[Any, Any] = Constant2(0) + override val BGreater: Function2[Any, Any] = Constant2(0) + override val BGreaterEq: Function2[Any, Any] = Constant2(0) + override val BCoerceContractId: Function1[ContractId] = Constant1(0) + } + +} diff --git a/sdk/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/Speedy.scala b/sdk/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/Speedy.scala index 57996f846563..2093043d64ee 100644 --- a/sdk/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/Speedy.scala +++ b/sdk/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/Speedy.scala @@ -1435,6 +1435,8 @@ private[lf] object Speedy { } Control.Value(go(typ0, value0)) } + + def costModel: CostModel = CostModel.Empty } object Machine { From 66c2cc604fe6e1bfa935d31e5424a3c26fdd8bdd Mon Sep 17 00:00:00 2001 From: Carl Pulley Date: Mon, 11 Aug 2025 16:36:50 +0100 Subject: [PATCH 2/3] Renames to avoid more generic name choices --- .../daml/lf/speedy/CostModel.scala | 254 ++++++++++-------- .../digitalasset/daml/lf/speedy/Speedy.scala | 2 +- 2 files changed, 136 insertions(+), 120 deletions(-) diff --git a/sdk/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/CostModel.scala b/sdk/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/CostModel.scala index a8a939bcf0c4..08449e6e63fe 100644 --- a/sdk/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/CostModel.scala +++ b/sdk/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/CostModel.scala @@ -2,80 +2,79 @@ // SPDX-License-Identifier: Apache-2.0 package com.digitalasset.daml.lf +package speedy -import com.digitalasset.daml.lf.speedy.SValue import com.digitalasset.daml.lf.speedy.SValue._ -import value.Value -import data.Ref -import sttp.tapir.SchemaType.SOption +import data.{FrontStack, Ref} abstract class CostModel { import CostModel._ - val AddNumeric: Function2[Numeric, Numeric] - val SubNumeric: Function2[Numeric, Numeric] - val MulNumeric: Function3[Numeric, Numeric, Numeric] - val DivNumeric: Function3[Numeric, Numeric, Numeric] - val BRoundNumeric: Function2[Int, Numeric] - val BCastNumeric: Function3[Numeric, Numeric, Numeric] - val BShiftNumeric: Function3[Numeric, Numeric, Numeric] - val BAddInt64: Function2[Int64, Int64] - val BSubInt64: Function2[Int64, Int64] - val BMulInt64: Function2[Int64, Int64] - val BDivInt64: Function2[Int64, Int64] - val BModInt64: Function2[Int64, Int64] - val BExpInt64: Function2[Int64, Int64] - val BInt64ToNumeric: Function2[Numeric, Int64] - val BNumericToInt64: Function1[Numeric] + val AddNumeric: CostFunction2[Numeric, Numeric] + val SubNumeric: CostFunction2[Numeric, Numeric] + val MulNumeric: CostFunction3[Numeric, Numeric, Numeric] + val DivNumeric: CostFunction3[Numeric, Numeric, Numeric] + val BRoundNumeric: CostFunction2[Int, Numeric] + val BCastNumeric: CostFunction3[Numeric, Numeric, Numeric] + val BShiftNumeric: CostFunction2[Int, Numeric] + val BAddInt64: CostFunction2[Int64, Int64] + val BSubInt64: CostFunction2[Int64, Int64] + val BMulInt64: CostFunction2[Int64, Int64] + val BDivInt64: CostFunction2[Int64, Int64] + val BModInt64: CostFunction2[Int64, Int64] + val BExpInt64: CostFunction2[Int64, Int64] + val BInt64ToNumeric: CostFunction2[Numeric, Int64] + val BNumericToInt64: CostFunction1[Numeric] val BFoldl = NotDefined val BFoldr = NotDefined lazy val BTextMapEmpty = BGenMapEmpty - lazy val BTextMapInsert: Function3[Value, Value, TextMap] = BGenMapInsert - lazy val BTextMapLookup: Function2[Value, TextMap] = BGenMapLookup - lazy val BTextMapDelete: Function2[Value, TextMap] = BGenMapDelete - val BTextMapToList: Function1[TextMap] + lazy val BTextMapInsert: CostFunction3[Value, Value, TextMap] = BGenMapInsert + lazy val BTextMapLookup: CostFunction2[Value, TextMap] = BGenMapLookup + lazy val BTextMapDelete: CostFunction2[Value, TextMap] = BGenMapDelete + val BTextMapToList: CostFunction1[TextMap] lazy val BTextMapSize = BGenMapSize val BGenMapEmpty = NotDefined - val BGenMapInsert: Function3[Value, Value, GenMap] - val BGenMapLookup: Function2[Value, GenMap] - val BGenMapDelete: Function2[Value, GenMap] - val BGenMapKeys: Function1[GenMap] - val BGenMapValues: Function1[GenMap] - val BGenMapSize: Function1[GenMap] - val BAppendText: Function2[Text, Text] + val BGenMapInsert: CostFunction3[Value, Value, GenMap] + val BGenMapLookup: CostFunction2[Value, GenMap] + val BGenMapDelete: CostFunction2[Value, GenMap] + val BGenMapToList: CostFunction1[GenMap] + val BGenMapKeys: CostFunction1[GenMap] + val BGenMapValues: CostFunction1[GenMap] + val BGenMapSize: CostFunction1[GenMap] + val BAppendText: CostFunction2[Text, Text] val BError = NotDefined - val BInt64ToText: Function1[Int64] - val BNumericToText: Function1[Numeric] - val BTimestampToText: Function1[Timestamp] - val BPartyToText: Function1[Party] - val BContractIdToText: Function1[ContractId] - val BCodePointsToText: Function1[List] - val BTextToParty: Function1[Text] - val BTextToInt64: Function1[Text] - val BTextToNumeric: Function1[Text] - val BTextToCodePoints: Function1[Text] - val BSHA256Text: Function1[Text] - val BKECCAK256Text: Function1[Text] - val BSECP256K1Bool: Function1[Text] - val BDecodeHex: Function1[Text] - val BEncodeHex: Function1[Text] - val BTextToContractId: Function1[Text] - val BDateToUnixDays: Function1[Date] - val BExplodeText: Function1[Text] + val BInt64ToText: CostFunction1[Int64] + val BNumericToText: CostFunction1[Numeric] + val BTimestampToText: CostFunction1[Timestamp] + val BPartyToText: CostFunction1[Party] + val BContractIdToText: CostFunction1[ContractId] + val BCodePointsToText: CostFunction1[FrontStack[Long]] + val BTextToParty: CostFunction1[Text] + val BTextToInt64: CostFunction1[Text] + val BTextToNumeric: CostFunction2[Int, Text] + val BTextToCodePoints: CostFunction1[Text] + val BSHA256Text: CostFunction1[Text] + val BKECCAK256Text: CostFunction1[Text] + val BSECP256K1Bool: CostFunction1[Text] + val BDecodeHex: CostFunction1[Text] + val BEncodeHex: CostFunction1[Text] + val BTextToContractId: CostFunction1[Text] + val BDateToUnixDays: CostFunction1[Date] + val BExplodeText: CostFunction1[Text] val BImplodeText = CostAware - val BTimestampToUnixMicroseconds: Function1[Timestamp] - val BDateToText: Function1[Date] - val BUnixDaysToDate: Function1[Int64] - val BUnixMicrosecondsToTimestamp: Function1[Int64] - val BEqual: Function2[Value, Value] - val BLess: Function2[Value, Value] - val BLessEq: Function2[Value, Value] - val BGreater: Function2[Value, Value] - val BGreaterEq: Function2[Value, Value] + val BTimestampToUnixMicroseconds: CostFunction1[Timestamp] + val BDateToText: CostFunction1[Date] + val BUnixDaysToDate: CostFunction1[Int64] + val BUnixMicrosecondsToTimestamp: CostFunction1[Int64] + val BEqual: CostFunction2[Value, Value] + val BLess: CostFunction2[Value, Value] + val BLessEq: CostFunction2[Value, Value] + val BGreater: CostFunction2[Value, Value] + val BGreaterEq: CostFunction2[Value, Value] val BEqualList = NotDefined val BTrace = NotDefined - val BCoerceContractId: Function1[ContractId] + val BCoerceContractId: CostFunction1[ContractId] val BScaleBigNumeric = NotDefined // Available in 2.dev val BPrecisionBigNumeric = NotDefined // Available in 2.dev val BAddBigNumeric = NotDefined // Available in 2.dev @@ -90,6 +89,13 @@ abstract class CostModel { val BTypeRepTyConName = NotDefined val BFailWithStatus = NotDefined + def update(cost: Cost): Unit + + def undefined(cost: NotDefined.type): Unit + + def costAware(cost: CostAware.type): Unit + + def getCost: Cost } object CostModel { @@ -99,95 +105,105 @@ object CostModel { type Int64 = Long type Numeric = data.Numeric type Text = String - type Date = Int + type Date = Long type Timestamp = Long type TextMap = SMap type GenMap = SMap type List = SList type ContractId = value.Value.ContractId - type Value = _ + type Value = SValue type Party = Ref.Party object NotDefined object CostAware - sealed abstract class Function1[X] { + sealed abstract class CostFunction1[X] { def cost(x: X): Cost } - case class Constant1[X](c: Cost) extends Function1[X] { + final case class ConstantCost1[X](c: Cost) extends CostFunction1[X] { override def cost(x: X): Cost = c } - sealed abstract class Function2[X, Y] { + sealed abstract class CostFunction2[X, Y] { def cost(x: X, y: Y): Cost } - case class Constant2[X, Y](c: Cost) extends Function2[X, Y] { + final case class ConstantCost2[X, Y](c: Cost) extends CostFunction2[X, Y] { override def cost(x: X, y: Y): Cost = c } - sealed abstract class Function3[X, Y, Z] { + sealed abstract class CostFunction3[X, Y, Z] { def cost(x: Y, y: Y, z: Z): Cost } - case class Constant3[X, Y, Z](c: Cost) extends Function3[X, Y, Z] { + final case class ConstantCost3[X, Y, Z](c: Cost) extends CostFunction3[X, Y, Z] { override def cost(x: Y, y: Y, z: Z): Cost = c } - object Empty extends CostModel { - override val AddNumeric: Function2[Numeric, Numeric] = Constant2(0) - override val SubNumeric: Function2[Numeric, Numeric] = Constant2(0) - override val MulNumeric: Function3[Numeric, Numeric, Numeric] = Constant3(0) - override val DivNumeric: Function3[Numeric, Numeric, Numeric] = Constant3(0) - override val BRoundNumeric: Function2[Cost, Numeric] = Constant2(0) - override val BCastNumeric: Function3[Numeric, Numeric, Numeric] = Constant3(0) - override val BShiftNumeric: Function3[Numeric, Numeric, Numeric] = Constant3(0) - override val BAddInt64: Function2[Int64, Int64] = Constant2(0) - override val BSubInt64: Function2[Int64, Int64] = Constant2(0) - override val BMulInt64: Function2[Int64, Int64] = Constant2(0) - override val BDivInt64: Function2[Int64, Int64] = Constant2(0) - override val BModInt64: Function2[Int64, Int64] = Constant2(0) - override val BExpInt64: Function2[Int64, Int64] = Constant2(0) - override val BInt64ToNumeric: Function2[Numeric, Int64] = Constant2(0) - override val BNumericToInt64: Function1[Numeric] = Constant1(0) - override val BTextMapToList: Function1[TextMap] = Constant1(0) - override val BGenMapInsert: Function3[Any, Any, GenMap] = Constant3(0) - override val BGenMapLookup: Function2[Any, GenMap] = Constant2(0) - override val BGenMapDelete: Function2[Any, GenMap] = Constant2(0) - override val BGenMapKeys: Function1[GenMap] = Constant1(0) - override val BGenMapValues: Function1[GenMap] = Constant1(0) - override val BGenMapSize: Function1[GenMap] = Constant1(0) - override val BAppendText: Function2[Text, Text] = Constant2(0) - override val BInt64ToText: Function1[Int64] = Constant1(0) - override val BNumericToText: Function1[Numeric] = Constant1(0) - override val BTimestampToText: Function1[Timestamp] = Constant1(0) - override val BPartyToText: Function1[Party] = Constant1(0) - override val BContractIdToText: Function1[ContractId] = Constant1(0) - override val BCodePointsToText: Function1[List] = Constant1(0) - override val BTextToParty: Function1[Text] = Constant1(0) - override val BTextToInt64: Function1[Text] = Constant1(0) - override val BTextToNumeric: Function1[Text] = Constant1(0) - override val BTextToCodePoints: Function1[Text] = Constant1(0) - override val BSHA256Text: Function1[Text] = Constant1(0) - override val BKECCAK256Text: Function1[Text] = Constant1(0) - override val BSECP256K1Bool: Function1[Text] = Constant1(0) - override val BDecodeHex: Function1[Text] = Constant1(0) - override val BEncodeHex: Function1[Text] = Constant1(0) - override val BTextToContractId: Function1[Text] = Constant1(0) - override val BDateToUnixDays: Function1[Date] = Constant1(0) - override val BExplodeText: Function1[Text] = Constant1(0) - override val BTimestampToUnixMicroseconds: Function1[Timestamp] = Constant1(0) - override val BDateToText: Function1[Date] = Constant1(0) - override val BUnixDaysToDate: Function1[Int64] = Constant1(0) - override val BUnixMicrosecondsToTimestamp: Function1[Int64] = Constant1(0) - override val BEqual: Function2[Any, Any] = Constant2(0) - override val BLess: Function2[Any, Any] = Constant2(0) - override val BLessEq: Function2[Any, Any] = Constant2(0) - override val BGreater: Function2[Any, Any] = Constant2(0) - override val BGreaterEq: Function2[Any, Any] = Constant2(0) - override val BCoerceContractId: Function1[ContractId] = Constant1(0) + object EmptyModel extends CostModel { + override val AddNumeric: CostFunction2[Numeric, Numeric] = ConstantCost2(0) + override val SubNumeric: CostFunction2[Numeric, Numeric] = ConstantCost2(0) + override val MulNumeric: CostFunction3[Numeric, Numeric, Numeric] = ConstantCost3(0) + override val DivNumeric: CostFunction3[Numeric, Numeric, Numeric] = ConstantCost3(0) + override val BRoundNumeric: CostFunction2[Cost, Numeric] = ConstantCost2(0) + override val BCastNumeric: CostFunction3[Numeric, Numeric, Numeric] = ConstantCost3(0) + override val BShiftNumeric: CostFunction2[Int, Numeric] = ConstantCost2(0) + override val BAddInt64: CostFunction2[Int64, Int64] = ConstantCost2(0) + override val BSubInt64: CostFunction2[Int64, Int64] = ConstantCost2(0) + override val BMulInt64: CostFunction2[Int64, Int64] = ConstantCost2(0) + override val BDivInt64: CostFunction2[Int64, Int64] = ConstantCost2(0) + override val BModInt64: CostFunction2[Int64, Int64] = ConstantCost2(0) + override val BExpInt64: CostFunction2[Int64, Int64] = ConstantCost2(0) + override val BInt64ToNumeric: CostFunction2[Numeric, Int64] = ConstantCost2(0) + override val BNumericToInt64: CostFunction1[Numeric] = ConstantCost1(0) + override val BTextMapToList: CostFunction1[TextMap] = ConstantCost1(0) + override val BGenMapInsert: CostFunction3[Value, Value, GenMap] = ConstantCost3(0) + override val BGenMapLookup: CostFunction2[Value, GenMap] = ConstantCost2(0) + override val BGenMapDelete: CostFunction2[Value, GenMap] = ConstantCost2(0) + override val BGenMapToList: CostFunction1[GenMap] = ConstantCost1(0) + override val BGenMapKeys: CostFunction1[GenMap] = ConstantCost1(0) + override val BGenMapValues: CostFunction1[GenMap] = ConstantCost1(0) + override val BGenMapSize: CostFunction1[GenMap] = ConstantCost1(0) + override val BAppendText: CostFunction2[Text, Text] = ConstantCost2(0) + override val BInt64ToText: CostFunction1[Int64] = ConstantCost1(0) + override val BNumericToText: CostFunction1[Numeric] = ConstantCost1(0) + override val BTimestampToText: CostFunction1[Timestamp] = ConstantCost1(0) + override val BPartyToText: CostFunction1[Party] = ConstantCost1(0) + override val BContractIdToText: CostFunction1[ContractId] = ConstantCost1(0) + override val BCodePointsToText: CostFunction1[FrontStack[Long]] = ConstantCost1(0) + override val BTextToParty: CostFunction1[Text] = ConstantCost1(0) + override val BTextToInt64: CostFunction1[Text] = ConstantCost1(0) + override val BTextToNumeric: CostFunction2[Int, Text] = ConstantCost2(0) + override val BTextToCodePoints: CostFunction1[Text] = ConstantCost1(0) + override val BSHA256Text: CostFunction1[Text] = ConstantCost1(0) + override val BKECCAK256Text: CostFunction1[Text] = ConstantCost1(0) + override val BSECP256K1Bool: CostFunction1[Text] = ConstantCost1(0) + override val BDecodeHex: CostFunction1[Text] = ConstantCost1(0) + override val BEncodeHex: CostFunction1[Text] = ConstantCost1(0) + override val BTextToContractId: CostFunction1[Text] = ConstantCost1(0) + override val BDateToUnixDays: CostFunction1[Date] = ConstantCost1(0) + override val BExplodeText: CostFunction1[Text] = ConstantCost1(0) + override val BTimestampToUnixMicroseconds: CostFunction1[Timestamp] = ConstantCost1(0) + override val BDateToText: CostFunction1[Date] = ConstantCost1(0) + override val BUnixDaysToDate: CostFunction1[Int64] = ConstantCost1(0) + override val BUnixMicrosecondsToTimestamp: CostFunction1[Int64] = ConstantCost1(0) + override val BEqual: CostFunction2[Value, Value] = ConstantCost2(0) + override val BLess: CostFunction2[Value, Value] = ConstantCost2(0) + override val BLessEq: CostFunction2[Value, Value] = ConstantCost2(0) + override val BGreater: CostFunction2[Value, Value] = ConstantCost2(0) + override val BGreaterEq: CostFunction2[Value, Value] = ConstantCost2(0) + override val BCoerceContractId: CostFunction1[ContractId] = ConstantCost1(0) + + override def update(cost: Cost): Unit = {} + + override def undefined(cost: NotDefined.type): Unit = {} + + override def costAware(cost: CostAware.type): Unit = {} + + override def getCost: Cost = { + 0 + } } - } diff --git a/sdk/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/Speedy.scala b/sdk/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/Speedy.scala index 2093043d64ee..af4247f175f7 100644 --- a/sdk/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/Speedy.scala +++ b/sdk/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/Speedy.scala @@ -1436,7 +1436,7 @@ private[lf] object Speedy { Control.Value(go(typ0, value0)) } - def costModel: CostModel = CostModel.Empty + def costModel: CostModel = CostModel.EmptyModel } object Machine { From a093aff51be3e6457dd9f66daea404ecb2159f73 Mon Sep 17 00:00:00 2001 From: Carl Pulley <106966370+carlpulley-da@users.noreply.github.com> Date: Fri, 15 Aug 2025 08:22:26 +0100 Subject: [PATCH 3/3] Implement Daml/Engine cost model infrastructure for pure builtins (#21668) * Implement Daml/Engine cost model infrastructure for pure builtins * Add in checks for pure builtins being within cost budgets * Build fixes * grpc parser fixes * More ci fixes * script service protobuf fixes * compiler fixes * doc sync * Move Cost.Error into Dev scope * build fix * doc sync --- .../daml/lf/script/Conversions.scala | 2 + .../daml/lf/speedy/CostModel.scala | 32 +- .../digitalasset/daml/lf/speedy/Pretty.scala | 3 + .../daml/lf/speedy/SBuiltinFun.scala | 477 +++++++++++++++--- .../digitalasset/daml/lf/speedy/SExpr.scala | 2 +- .../daml/lf/interpretation/Error.scala | 8 + 6 files changed, 435 insertions(+), 89 deletions(-) diff --git a/sdk/compiler/script-service/server/src/main/scala/com/digitalasset/daml/lf/script/Conversions.scala b/sdk/compiler/script-service/server/src/main/scala/com/digitalasset/daml/lf/script/Conversions.scala index bb5250c2ca75..476c0c78bd03 100644 --- a/sdk/compiler/script-service/server/src/main/scala/com/digitalasset/daml/lf/script/Conversions.scala +++ b/sdk/compiler/script-service/server/src/main/scala/com/digitalasset/daml/lf/script/Conversions.scala @@ -278,6 +278,8 @@ final class Conversions( .setExpected(convertIdentifier(expected)) .addAllAccepted(accepted.map(convertIdentifier(_)).asJava) ) + case Dev.Cost(Dev.Cost.BudgetExceeded(cause)) => + builder.setCrash(cause) } case _: Upgrade => builder.setUpgradeError( diff --git a/sdk/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/CostModel.scala b/sdk/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/CostModel.scala index 08449e6e63fe..a6ee340983d8 100644 --- a/sdk/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/CostModel.scala +++ b/sdk/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/CostModel.scala @@ -4,10 +4,11 @@ package com.digitalasset.daml.lf package speedy +import com.digitalasset.daml.lf.speedy.SError.SErrorCrash import com.digitalasset.daml.lf.speedy.SValue._ import data.{FrontStack, Ref} -abstract class CostModel { +abstract class CostModel(maximumCost: CostModel.Cost) { import CostModel._ @@ -89,13 +90,22 @@ abstract class CostModel { val BTypeRepTyConName = NotDefined val BFailWithStatus = NotDefined - def update(cost: Cost): Unit + private[this] var totalCost: Cost = 0 - def undefined(cost: NotDefined.type): Unit + private[speedy] final def update(cost: Cost): Unit = { + if (totalCost + cost <= maximumCost) { + totalCost += cost + } else { + throw SErrorCrash( + getClass.getCanonicalName, + s"Current interpretation has used $totalCost, so an operation with cost $cost will exceed maximum budgeted cost of $maximumCost", + ) + } + } - def costAware(cost: CostAware.type): Unit + private[speedy] final def undefined(cost: NotDefined.type): Unit = {} - def getCost: Cost + private[speedy] final def costAware(cost: CostAware.type): Unit = {} } object CostModel { @@ -142,7 +152,7 @@ object CostModel { override def cost(x: Y, y: Y, z: Z): Cost = c } - object EmptyModel extends CostModel { + object EmptyModel extends CostModel(0) { override val AddNumeric: CostFunction2[Numeric, Numeric] = ConstantCost2(0) override val SubNumeric: CostFunction2[Numeric, Numeric] = ConstantCost2(0) override val MulNumeric: CostFunction3[Numeric, Numeric, Numeric] = ConstantCost3(0) @@ -195,15 +205,5 @@ object CostModel { override val BGreater: CostFunction2[Value, Value] = ConstantCost2(0) override val BGreaterEq: CostFunction2[Value, Value] = ConstantCost2(0) override val BCoerceContractId: CostFunction1[ContractId] = ConstantCost1(0) - - override def update(cost: Cost): Unit = {} - - override def undefined(cost: NotDefined.type): Unit = {} - - override def costAware(cost: CostAware.type): Unit = {} - - override def getCost: Cost = { - 0 - } } } diff --git a/sdk/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/Pretty.scala b/sdk/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/Pretty.scala index 6cf10f75b35f..13b18592b8ae 100644 --- a/sdk/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/Pretty.scala +++ b/sdk/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/Pretty.scala @@ -254,6 +254,9 @@ private[lf] object Pretty { ) & prettyTypeConId( actual ) + case Dev.Cost(Dev.Cost.BudgetExceeded(cause)) => + text("Cost budget has been exceeded:") / + text(cause) } } } diff --git a/sdk/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/SBuiltinFun.scala b/sdk/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/SBuiltinFun.scala index 53688c9dbbf5..21babfd0cdf1 100644 --- a/sdk/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/SBuiltinFun.scala +++ b/sdk/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/SBuiltinFun.scala @@ -85,16 +85,27 @@ private[speedy] sealed abstract class SBuiltinPure(arity: Int) extends SBuiltinF /** Pure builtins do not modify the machine state and do not ask questions of the ledger. As a result, pure builtin * execution is immediate. * + * @param costModel cost model used by this machine instance * @param args arguments for executing the pure builtin * @return the pure builtin's resulting value (wrapped as a Control value) */ - private[speedy] def executePure(args: Array[SValue]): SValue + private[speedy] def executePure(costModel: CostModel, args: Array[SValue]): SValue override private[speedy] final def execute[Q]( args: Array[SValue], machine: Machine[Q], - ): Control.Value = { - Control.Value(executePure(args)) + ): Control[Q] = { + try { + Control.Value(executePure(machine.costModel, args)) + } catch { + case SErrorCrash(_, cause) => + Control.Error( + IE.Dev( + NameOf.qualifiedNameOfCurrentFunc, + IE.Dev.Cost(IE.Dev.Cost.BudgetExceeded(cause)), + ) + ) + } } } @@ -421,9 +432,15 @@ private[lf] object SBuiltinFun { } final case object SBShiftNumeric extends SBuiltinPure(2) { - override private[speedy] def executePure(args: Array[SValue]): SNumeric = { + override private[speedy] def executePure( + costModel: CostModel, + args: Array[SValue], + ): SNumeric = { val outputScale = getSScale(args, 0) val x = getSNumeric(args, 1) + + costModel.update(costModel.BShiftNumeric.cost(outputScale, x)) + val inputScale = x.scale SNumeric( Numeric.assertFromBigDecimal(outputScale, x.scaleByPowerOfTen(inputScale - outputScale)) @@ -435,24 +452,47 @@ private[lf] object SBuiltinFun { // Text functions // final case object SBExplodeText extends SBuiltinPure(1) { - override private[speedy] def executePure(args: Array[SValue]): SList = - SList(FrontStack.from(Utf8.explode(getSText(args, 0)).map(SText))) + override private[speedy] def executePure( + costModel: CostModel, + args: Array[SValue], + ): SList = { + val arg0 = getSText(args, 0) + + costModel.update(costModel.BExplodeText.cost(arg0)) + + SList(FrontStack.from(Utf8.explode(arg0).map(SText))) + } } final case object SBImplodeText extends SBuiltinPure(1) { - override private[speedy] def executePure(args: Array[SValue]): SText = { + override private[speedy] def executePure( + costModel: CostModel, + args: Array[SValue], + ): SText = { val xs = getSList(args, 0) val ts = xs.map { case SText(t) => t case v => crash(s"type mismatch implodeText: expected SText, got $v") } + + costModel.costAware(costModel.BImplodeText) + SText(Utf8.implode(ts.toImmArray)) } } final case object SBAppendText extends SBuiltinPure(2) { - override private[speedy] def executePure(args: Array[SValue]): SText = - SText(getSText(args, 0) + getSText(args, 1)) + override private[speedy] def executePure( + costModel: CostModel, + args: Array[SValue], + ): SText = { + val arg0 = getSText(args, 0) + val arg1 = getSText(args, 1) + + costModel.update(costModel.BAppendText.cost(arg0, arg1)) + + SText(arg0 + arg1) + } } private[this] def litToText(location: String, x: SValue): String = @@ -472,8 +512,14 @@ private[lf] object SBuiltinFun { } final case object SBToText extends SBuiltinPure(1) { - override private[speedy] def executePure(args: Array[SValue]): SText = + override private[speedy] def executePure( + costModel: CostModel, + args: Array[SValue], + ): SText = { + // No cost model update as new data created + SText(litToText(NameOf.qualifiedNameOfCurrentFunc, args(0))) + } } final case object SBContractIdToText extends SBuiltinFun(1) { @@ -492,13 +538,27 @@ private[lf] object SBuiltinFun { } final case object SBPartyToQuotedText extends SBuiltinPure(1) { - override private[speedy] def executePure(args: Array[SValue]): SText = - SText(s"'${getSParty(args, 0): String}'") + override private[speedy] def executePure( + costModel: CostModel, + args: Array[SValue], + ): SText = { + val arg0 = getSParty(args, 0) + + costModel.update(costModel.BPartyToText.cost(arg0)) + + SText(s"'${arg0: String}'") + } } final case object SBCodePointsToText extends SBuiltinPure(1) { - override private[speedy] def executePure(args: Array[SValue]): SText = { + override private[speedy] def executePure( + costModel: CostModel, + args: Array[SValue], + ): SText = { val codePoints = getSList(args, 0).map(_.asInstanceOf[SInt64].value) + + costModel.update(costModel.BCodePointsToText.cost(codePoints)) + Utf8.pack(codePoints.toImmArray) match { case Right(value) => SText(value) @@ -509,8 +569,15 @@ private[lf] object SBuiltinFun { } final case object SBTextToParty extends SBuiltinPure(1) { - override private[speedy] def executePure(args: Array[SValue]): SOptional = { - Party.fromString(getSText(args, 0)) match { + override private[speedy] def executePure( + costModel: CostModel, + args: Array[SValue], + ): SOptional = { + val arg0 = getSText(args, 0) + + costModel.update(costModel.BTextToParty.cost(arg0)) + + Party.fromString(arg0) match { case Left(_) => SV.None case Right(p) => SOptional(Some(SParty(p))) } @@ -520,8 +587,14 @@ private[lf] object SBuiltinFun { final case object SBTextToInt64 extends SBuiltinPure(1) { private val pattern = """[+-]?\d+""".r.pattern - override private[speedy] def executePure(args: Array[SValue]): SOptional = { + override private[speedy] def executePure( + costModel: CostModel, + args: Array[SValue], + ): SOptional = { val s = getSText(args, 0) + + costModel.update(costModel.BTextToInt64.cost(s)) + if (pattern.matcher(s).matches()) try { SOptional(Some(SInt64(java.lang.Long.parseLong(s)))) @@ -542,9 +615,15 @@ private[lf] object SBuiltinFun { private val validFormat = """([+-]?)0*(\d+)(\.(\d*[1-9]|0)0*)?""".r - override private[speedy] def executePure(args: Array[SValue]): SOptional = { + override private[speedy] def executePure( + costModel: CostModel, + args: Array[SValue], + ): SOptional = { val scale = getSScale(args, 0) val string = getSText(args, 1) + + costModel.update(costModel.BTextToNumeric.cost(scale, string)) + string match { case validFormat(signPart, intPart, _, decPartOrNull) => val decPart = Option(decPartOrNull).filterNot(_ == "0").getOrElse("") @@ -570,8 +649,14 @@ private[lf] object SBuiltinFun { } final case object SBTextToCodePoints extends SBuiltinPure(1) { - override private[speedy] def executePure(args: Array[SValue]): SList = { + override private[speedy] def executePure( + costModel: CostModel, + args: Array[SValue], + ): SList = { val string = getSText(args, 0) + + costModel.update(costModel.BTextToCodePoints.cost(string)) + val codePoints = Utf8.unpack(string) SList(FrontStack.from(codePoints.map(SInt64))) } @@ -591,7 +676,7 @@ private[lf] object SBuiltinFun { } final case object SBSHA256Text extends SBuiltinPure(1) { - override private[speedy] def executePure(args: Array[SValue]): SText = + override private[speedy] def executePure(costModel: CostModel, args: Array[SValue]): SText = SText(Utf8.sha256(Utf8.getBytes(getSText(args, 0)))) } @@ -599,7 +684,7 @@ private[lf] object SBuiltinFun { override private[speedy] def execute[Q]( args: Array[SValue], machine: Machine[Q], - ): Control[Q] = { + ): Control[Nothing] = { val hex = getSText(args, 0) Ref.HexString.fromString(hex) match { @@ -741,10 +826,15 @@ private[lf] object SBuiltinFun { } final case object SBEncodeHex extends SBuiltinPure(1) { - override private[speedy] def executePure(args: Array[SValue]): SValue = { - val arg = getSText(args, 0) - val hexArg = Ref.HexString.encode(Bytes.fromStringUtf8(arg)) + override private[speedy] def executePure( + costModel: CostModel, + args: Array[SValue], + ): SValue = { + val arg0 = getSText(args, 0) + + costModel.update(costModel.BEncodeHex.cost(arg0)) + val hexArg = Ref.HexString.encode(Bytes.fromStringUtf8(arg0)) SText(hexArg) } } @@ -817,38 +907,118 @@ private[lf] object SBuiltinFun { final case object SBMapToList extends SBuiltinPure(1) { - override private[speedy] def executePure(args: Array[SValue]): SList = - SValue.toList(getSMap(args, 0).entries) + override private[speedy] def executePure( + costModel: CostModel, + args: Array[SValue], + ): SList = { + val arg0 = getSMap(args, 0) + + if (arg0.isTextMap) { + costModel.update(costModel.BTextMapToList.cost(arg0)) + } else { + costModel.update(costModel.BGenMapToList.cost(arg0)) + } + + SValue.toList(arg0.entries) + } } final case object SBMapInsert extends SBuiltinPure(3) { - override private[speedy] def executePure(args: Array[SValue]): SMap = - getSMap(args, 2).insert(getSMapKey(args, 0), args(1)) + override private[speedy] def executePure( + costModel: CostModel, + args: Array[SValue], + ): SMap = { + val arg0 = getSMapKey(args, 0) + val arg1 = args(1) + val arg2 = getSMap(args, 2) + + if (arg2.isTextMap) { + costModel.update(costModel.BTextMapInsert.cost(arg0, arg1, arg2)) + } else { + costModel.update(costModel.BGenMapInsert.cost(arg0, arg1, arg2)) + } + + arg2.insert(arg0, arg1) + } } final case object SBMapLookup extends SBuiltinPure(2) { - override private[speedy] def executePure(args: Array[SValue]): SOptional = - SOptional(getSMap(args, 1).get(getSMapKey(args, 0))) + override private[speedy] def executePure( + costModel: CostModel, + args: Array[SValue], + ): SOptional = { + val arg0 = getSMapKey(args, 0) + val arg1 = getSMap(args, 1) + + if (arg1.isTextMap) { + costModel.update(costModel.BTextMapLookup.cost(arg0, arg1)) + } else { + costModel.update(costModel.BGenMapLookup.cost(arg0, arg1)) + } + + SOptional(arg1.get(arg0)) + } } final case object SBMapDelete extends SBuiltinPure(2) { - override private[speedy] def executePure(args: Array[SValue]): SMap = - getSMap(args, 1).delete(getSMapKey(args, 0)) + override private[speedy] def executePure( + costModel: CostModel, + args: Array[SValue], + ): SMap = { + val arg0 = getSMapKey(args, 0) + val arg1 = getSMap(args, 1) + + if (arg1.isTextMap) { + costModel.update(costModel.BTextMapDelete.cost(arg0, arg1)) + } else { + costModel.update(costModel.BGenMapDelete.cost(arg0, arg1)) + } + + arg1.delete(arg0) + } } final case object SBMapKeys extends SBuiltinPure(1) { - override private[speedy] def executePure(args: Array[SValue]): SList = - SList(getSMap(args, 0).entries.keys.to(FrontStack)) + override private[speedy] def executePure( + costModel: CostModel, + args: Array[SValue], + ): SList = { + val arg0 = getSMap(args, 0) + + costModel.update(costModel.BGenMapKeys.cost(arg0)) + + SList(arg0.entries.keys.to(FrontStack)) + } } final case object SBMapValues extends SBuiltinPure(1) { - override private[speedy] def executePure(args: Array[SValue]): SList = - SList(getSMap(args, 0).entries.values.to(FrontStack)) + override private[speedy] def executePure( + costModel: CostModel, + args: Array[SValue], + ): SList = { + val arg0 = getSMap(args, 0) + + costModel.update(costModel.BGenMapValues.cost(arg0)) + + SList(arg0.entries.values.to(FrontStack)) + } } final case object SBMapSize extends SBuiltinPure(1) { - override private[speedy] def executePure(args: Array[SValue]): SInt64 = - SInt64(getSMap(args, 0).entries.size.toLong) + override private[speedy] def executePure( + costModel: CostModel, + args: Array[SValue], + ): SInt64 = { + val arg0 = getSMap(args, 0) + + if (arg0.isTextMap) { + costModel.update(costModel.BTextMapSize.cost(arg0)) + } else { + costModel.update(costModel.BGenMapSize.cost(arg0)) + } + + SInt64(arg0.entries.size.toLong) + } } // @@ -871,8 +1041,16 @@ private[lf] object SBuiltinFun { } final case object SBDateToUnixDays extends SBuiltinPure(1) { - override private[speedy] def executePure(args: Array[SValue]): SInt64 = - SInt64(getSDate(args, 0).days.toLong) + override private[speedy] def executePure( + costModel: CostModel, + args: Array[SValue], + ): SInt64 = { + val date = getSDate(args, 0).days.toLong + + costModel.update(costModel.BDateToUnixDays.cost(date)) + + SInt64(date) + } } final case object SBUnixDaysToDate extends SBuiltinArithmetic("UNIX_DAYS_TO_DATE", 1) { @@ -883,8 +1061,16 @@ private[lf] object SBuiltinFun { } final case object SBTimestampToUnixMicroseconds extends SBuiltinPure(1) { - override private[speedy] def executePure(args: Array[SValue]): SInt64 = - SInt64(getSTimestamp(args, 0).micros) + override private[speedy] def executePure( + costModel: CostModel, + args: Array[SValue], + ): SInt64 = { + val arg0 = getSTimestamp(args, 0).micros + + costModel.update(costModel.BTimestampToUnixMicroseconds.cost(arg0)) + + SInt64(arg0) + } } final case object SBUnixMicrosecondsToTimestamp @@ -899,38 +1085,87 @@ private[lf] object SBuiltinFun { // Equality and comparisons // final case object SBEqual extends SBuiltinPure(2) { - override private[speedy] def executePure(args: Array[SValue]): SBool = { - SBool(svalue.Equality.areEqual(args(0), args(1))) + override private[speedy] def executePure( + costModel: CostModel, + args: Array[SValue], + ): SBool = { + val arg0 = args(0) + val arg1 = args(1) + + costModel.update(costModel.BEqual.cost(arg0, arg1)) + + SBool(svalue.Equality.areEqual(arg0, arg1)) } } sealed abstract class SBCompare(pred: Int => Boolean) extends SBuiltinPure(2) { - override private[speedy] def executePure(args: Array[SValue]): SBool = { - SBool(pred(svalue.Ordering.compare(args(0), args(1)))) + def predCost(costModel: CostModel, arg0: SValue, arg1: SValue): CostModel.Cost + + override private[speedy] def executePure( + costModel: CostModel, + args: Array[SValue], + ): SBool = { + val arg0 = args(0) + val arg1 = args(1) + + costModel.update(predCost(costModel, arg0, arg1)) + + SBool(pred(svalue.Ordering.compare(arg0, arg1))) } } - final case object SBLess extends SBCompare(_ < 0) - final case object SBLessEq extends SBCompare(_ <= 0) - final case object SBGreater extends SBCompare(_ > 0) - final case object SBGreaterEq extends SBCompare(_ >= 0) + final case object SBLess extends SBCompare(_ < 0) { + override def predCost(costModel: CostModel, arg0: SValue, arg1: SValue): CostModel.Cost = + costModel.BLess.cost(arg0, arg1) + } + final case object SBLessEq extends SBCompare(_ <= 0) { + override def predCost(costModel: CostModel, arg0: SValue, arg1: SValue): CostModel.Cost = + costModel.BLessEq.cost(arg0, arg1) + } + final case object SBGreater extends SBCompare(_ > 0) { + override def predCost(costModel: CostModel, arg0: SValue, arg1: SValue): CostModel.Cost = + costModel.BGreater.cost(arg0, arg1) + } + final case object SBGreaterEq extends SBCompare(_ >= 0) { + override def predCost(costModel: CostModel, arg0: SValue, arg1: SValue): CostModel.Cost = + costModel.BGreaterEq.cost(arg0, arg1) + } /** $consMany[n] :: a -> ... -> List a -> List a */ final case class SBConsMany(n: Int) extends SBuiltinPure(1 + n) { - override private[speedy] def executePure(args: Array[SValue]): SList = + override private[speedy] def executePure( + costModel: CostModel, + args: Array[SValue], + ): SList = { + // No cost model update as no new data is created + SList(args.view.slice(0, n).to(ImmArray) ++: getSList(args, n)) + } } /** $cons :: a -> List a -> List a */ final case object SBCons extends SBuiltinPure(2) { - override private[speedy] def executePure(args: Array[SValue]): SList = { - SList(args(0) +: getSList(args, 1)) + override private[speedy] def executePure( + costModel: CostModel, + args: Array[SValue], + ): SList = { + val headSValue = args(0) + val tailSList = getSList(args, 1) + + // No cost model update as no new data is created + + SList(headSValue +: tailSList) } } /** $some :: a -> Optional a */ final case object SBSome extends SBuiltinPure(1) { - override private[speedy] def executePure(args: Array[SValue]): SOptional = { + override private[speedy] def executePure( + costModel: CostModel, + args: Array[SValue], + ): SOptional = { + // No cost model update as no new data is created + SOptional(Some(args(0))) } } @@ -938,15 +1173,26 @@ private[lf] object SBuiltinFun { /** $rcon[R, fields] :: a -> b -> ... -> R */ final case class SBRecCon(id: Identifier, fields: ImmArray[Name]) extends SBuiltinPure(fields.length) { - override private[speedy] final def executePure(args: Array[SValue]): SValue = { + override private[speedy] final def executePure( + costModel: CostModel, + args: Array[SValue], + ): SValue = { + // No cost model update as no new data is created + SRecord(id, fields, args) } } /** $rupd[R, field] :: R -> a -> R */ final case class SBRecUpd(id: Identifier, field: Int) extends SBuiltinPure(2) { - override private[speedy] final def executePure(args: Array[SValue]): SValue = { + override private[speedy] final def executePure( + costModel: CostModel, + args: Array[SValue], + ): SValue = { val record = getSRecord(args, 0) + + // No cost model update as no new data is created + if (record.id != id) { crash(s"type mismatch on record update: expected $id, got record of type ${record.id}") } @@ -959,8 +1205,14 @@ private[lf] object SBuiltinFun { /** $rupdmulti[R, [field_1, ..., field_n]] :: R -> a_1 -> ... -> a_n -> R */ final case class SBRecUpdMulti(id: Identifier, updateFields: List[Int]) extends SBuiltinPure(1 + updateFields.length) { - override private[speedy] final def executePure(args: Array[SValue]): SValue = { + override private[speedy] final def executePure( + costModel: CostModel, + args: Array[SValue], + ): SValue = { val record = getSRecord(args, 0) + + // No cost model update as no new data is created + if (record.id != id) { crash(s"type mismatch on record update: expected $id, got record of type ${record.id}") } @@ -974,8 +1226,14 @@ private[lf] object SBuiltinFun { /** $rproj[R, field] :: R -> a */ final case class SBRecProj(id: Identifier, field: Int) extends SBuiltinPure(1) { - override private[speedy] final def executePure(args: Array[SValue]): SValue = + override private[speedy] final def executePure( + costModel: CostModel, + args: Array[SValue], + ): SValue = { + // No cost model update as no new data is created + getSRecord(args, 0).values(field) + } } // SBStructCon sorts the field after evaluation of its arguments to preserve @@ -984,13 +1242,19 @@ private[lf] object SBuiltinFun { final case class SBStructCon(inputFieldsOrder: Struct[Int]) extends SBuiltinPure(inputFieldsOrder.size) { private[this] val fieldNames = inputFieldsOrder.mapValues(_ => ()) - override private[speedy] def executePure(args: Array[SValue]): SStruct = { + override private[speedy] def executePure( + costModel: CostModel, + args: Array[SValue], + ): SStruct = { val sortedFields = Array.ofDim[SValue](inputFieldsOrder.size) var j = 0 inputFieldsOrder.values.foreach { i => sortedFields(j) = args(i) j += 1 } + + // No cost model update as no new data is created + SStruct(fieldNames, sortedFields) } } @@ -1002,8 +1266,14 @@ private[lf] object SBuiltinFun { // avoid its reevaluations, hence obtaining an amortized constant // complexity. private[this] var fieldIndex = -1 - override private[speedy] def executePure(args: Array[SValue]): SValue = { + override private[speedy] def executePure( + costModel: CostModel, + args: Array[SValue], + ): SValue = { val struct = getSStruct(args, 0) + + // No cost model update as no new data is created + if (fieldIndex < 0) fieldIndex = struct.fieldNames.indexOf(field) struct.values(fieldIndex) } @@ -1011,8 +1281,14 @@ private[lf] object SBuiltinFun { /** $tupd[field] :: Struct -> a -> Struct */ final case class SBStructUpd(field: Ast.FieldName) extends SBuiltinPure(2) { - override private[speedy] def executePure(args: Array[SValue]): SStruct = { + override private[speedy] def executePure( + costModel: CostModel, + args: Array[SValue], + ): SStruct = { val struct = getSStruct(args, 0) + + // No cost model update as no new data is created + val values = struct.values.clone discard(values(struct.fieldNames.indexOf(field)) = args(1)) struct.copy(values = values) @@ -1022,20 +1298,39 @@ private[lf] object SBuiltinFun { /** $vcon[V, variant] :: a -> V */ final case class SBVariantCon(id: Identifier, variant: Ast.VariantConName, constructorRank: Int) extends SBuiltinPure(1) { - override private[speedy] def executePure(args: Array[SValue]): SVariant = { + override private[speedy] def executePure( + costModel: CostModel, + args: Array[SValue], + ): SVariant = { + // No cost model update as no new data is created + SVariant(id, variant, constructorRank, args(0)) } } final object SBScaleBigNumeric extends SBuiltinPure(1) { - override private[speedy] def executePure(args: Array[SValue]): SInt64 = { - SInt64(getSBigNumeric(args, 0).scale().toLong) + override private[speedy] def executePure( + costModel: CostModel, + args: Array[SValue], + ): SInt64 = { + val arg0 = getSBigNumeric(args, 0) + + costModel.undefined(costModel.BScaleBigNumeric) + + SInt64(arg0.scale().toLong) } } final object SBPrecisionBigNumeric extends SBuiltinPure(1) { - override private[speedy] def executePure(args: Array[SValue]): SInt64 = { - SInt64(getSBigNumeric(args, 0).precision().toLong) + override private[speedy] def executePure( + costModel: CostModel, + args: Array[SValue], + ): SInt64 = { + val arg0 = getSBigNumeric(args, 0) + + costModel.undefined(costModel.BPrecisionBigNumeric) + + SInt64(arg0.precision().toLong) } } @@ -1093,8 +1388,14 @@ private[lf] object SBuiltinFun { } final object SBNumericToBigNumeric extends SBuiltinPure(1) { - override private[speedy] def executePure(args: Array[SValue]): SBigNumeric = { + override private[speedy] def executePure( + costModel: CostModel, + args: Array[SValue], + ): SBigNumeric = { val x = getSNumeric(args, 0) + + costModel.undefined(costModel.BNumericToBigNumeric) + SBigNumeric.fromNumeric(x) } } @@ -1396,9 +1697,13 @@ private[lf] object SBuiltinFun { final case object SBGuardConstTrue extends SBuiltinPure(1) { override private[speedy] def executePure( - args: Array[SValue] + costModel: CostModel, + args: Array[SValue], ): SBool = { discard(getSAnyContract(args, 0)) + + // No cost model update as no new data is created + SBool(true) } } @@ -1493,7 +1798,12 @@ private[lf] object SBuiltinFun { // This wraps a contract record into an SAny where the type argument corresponds to // the record's templateId. final case class SBToAnyContract(tplId: TypeConId) extends SBuiltinPure(1) { - override private[speedy] def executePure(args: Array[SValue]): SAny = { + override private[speedy] def executePure( + costModel: CostModel, + args: Array[SValue], + ): SAny = { + // No cost model update as no new data is created + SAnyContract(tplId, getSRecord(args, 0)) } } @@ -2033,7 +2343,12 @@ private[lf] object SBuiltinFun { * -> Any (where t = ty) */ final case class SBToAny(ty: Ast.Type) extends SBuiltinPure(1) { - override private[speedy] def executePure(args: Array[SValue]): SAny = { + override private[speedy] def executePure( + costModel: CostModel, + args: Array[SValue], + ): SAny = { + // No cost model update as no new data is created + SAny(ty, args(0)) } } @@ -2043,8 +2358,14 @@ private[lf] object SBuiltinFun { * -> Optional t (where t = expectedType) */ final case class SBFromAny(expectedTy: Ast.Type) extends SBuiltinPure(1) { - override private[speedy] def executePure(args: Array[SValue]): SOptional = { + override private[speedy] def executePure( + costModel: CostModel, + args: Array[SValue], + ): SOptional = { val any = getSAny(args, 0) + + // No cost model update as no new data is created + if (any.ty == expectedTy) SOptional(Some(any.value)) else SValue.SValue.None } } @@ -2054,8 +2375,14 @@ private[lf] object SBuiltinFun { * -> TypeRep (where t = TTyCon(_)) */ final case class SBInterfaceTemplateTypeRep(tycon: TypeConId) extends SBuiltinPure(1) { - override private[speedy] def executePure(args: Array[SValue]): STypeRep = { + override private[speedy] def executePure( + costModel: CostModel, + args: Array[SValue], + ): STypeRep = { val (tyCon, _) = getSAnyContract(args, 0) + + // No cost model update as no new data is created + STypeRep(Ast.TTyCon(tyCon)) } } @@ -2065,11 +2392,17 @@ private[lf] object SBuiltinFun { * -> Optional Text */ final case object SBTypeRepTyConName extends SBuiltinPure(1) { - override private[speedy] def executePure(args: Array[SValue]): SOptional = + override private[speedy] def executePure( + costModel: CostModel, + args: Array[SValue], + ): SOptional = { + // No cost model update as no new data is created + getSTypeRep(args, 0) match { case Ast.TTyCon(name) => SOptional(Some(SText(name.toString))) case _ => SOptional(None) } + } } /** EQUAL_LIST :: (a -> a -> Bool) -> [a] -> [a] -> Bool */ diff --git a/sdk/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/SExpr.scala b/sdk/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/SExpr.scala index 026d2d14cdc1..357164ba41e6 100644 --- a/sdk/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/SExpr.scala +++ b/sdk/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/SExpr.scala @@ -229,7 +229,7 @@ private[lf] object SExpr { actuals(i) = arg.lookupValue(machine) i += 1 } - val v = builtin.executePure(actuals) + val v = builtin.executePure(machine.costModel, actuals) machine.pushEnv(v) // use pushEnv not env.add so instrumentation is updated Control.Expression(body) } diff --git a/sdk/daml-lf/transaction/src/main/scala/com/digitalasset/daml/lf/interpretation/Error.scala b/sdk/daml-lf/transaction/src/main/scala/com/digitalasset/daml/lf/interpretation/Error.scala index c06dd8f6e30f..8aa1bd1a0a6e 100644 --- a/sdk/daml-lf/transaction/src/main/scala/com/digitalasset/daml/lf/interpretation/Error.scala +++ b/sdk/daml-lf/transaction/src/main/scala/com/digitalasset/daml/lf/interpretation/Error.scala @@ -282,6 +282,14 @@ object Error { final case class TransactionInputContracts(limit: Int) extends Error } + + sealed case class Cost(error: Cost.Error) extends Error + + object Cost { + sealed abstract class Error extends Serializable with Product + + final case class BudgetExceeded(cause: String) extends Error + } } }