From 61d214e45117c0bb8d0762227e8129052b9c8211 Mon Sep 17 00:00:00 2001 From: Sofia Faro Date: Fri, 17 Sep 2021 11:44:40 +0100 Subject: [PATCH] Add fetch, exercise implementations for interfaces in speedy. (#10911) * Draft: Daml Interfaces Speedy PoC Part of #10810 Extracted from #10670 changelog_begin changelog_end * Improve cacheing situation, add implements checks * scalafmt * Add comment for ImplementsDefRef * compile the new update expressions --- .../daml/lf/speedy/Compiler.scala | 65 ++++++++++- .../digitalasset/daml/lf/speedy/Profile.scala | 3 + .../daml/lf/speedy/SBuiltin.scala | 109 ++++++++++++++++++ .../digitalasset/daml/lf/speedy/SExpr.scala | 6 + 4 files changed, 177 insertions(+), 6 deletions(-) diff --git a/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/Compiler.scala b/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/Compiler.scala index 9e41bbe906cc..68b25156f2a1 100644 --- a/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/Compiler.scala +++ b/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/Compiler.scala @@ -317,6 +317,7 @@ private[lf] final class Compiler( builder += compileKey(identifier, tmpl) builder += compileSignatories(identifier, tmpl) builder += compileObservers(identifier, tmpl) + tmpl.implements.foreach(builder += compileImplements(identifier, _)) tmpl.choices.values.foreach(builder += compileChoice(identifier, tmpl, _)) @@ -330,6 +331,14 @@ private[lf] final class Compiler( } } + module.interfaces.foreach { case (ifaceName, iface) => + val identifier = Identifier(pkgId, QualifiedName(module.name, ifaceName)) + builder += compileFetchInterface(identifier) + iface.choices.values.foreach( + builder += compileChoiceInterface(identifier, _) + ) + } + builder.result() } @@ -773,9 +782,8 @@ private[lf] final class Compiler( compileBlock(bindings, body) case UpdateFetch(tmplId, coidE) => FetchDefRef(tmplId)(compile(coidE)) - case UpdateFetchInterface(_, _) => - // TODO https://github.com/digital-asset/daml/issues/10810 - sys.error("Interfaces not supported") + case UpdateFetchInterface(ifaceId, coidE) => + FetchDefRef(ifaceId)(compile(coidE)) case UpdateEmbedExpr(_, e) => compileEmbedExpr(e) case UpdateCreate(tmplId, arg) => @@ -787,9 +795,8 @@ private[lf] final class Compiler( choiceId = chId, argument = compile(argE), ) - case UpdateExerciseInterface(_, _, _, _) => - // TODO https://github.com/digital-asset/daml/issues/10810 - sys.error("Interfaces not supported") + case UpdateExerciseInterface(ifaceId, chId, cidE, argE) => + ChoiceDefRef(ifaceId, chId)(compile(cidE), compile(argE)) case UpdateExerciseByKey(tmplId, chId, keyE, argE) => compileExerciseByKey(tmplId, compile(keyE), chId, compile(argE)) case UpdateGetTime => @@ -1006,6 +1013,24 @@ private[lf] final class Compiler( } } + private[this] def compileChoiceInterface( + ifaceId: TypeConName, + choice: InterfaceChoice, + ): (SDefinitionRef, SDefinition) = + topLevelFunction(ChoiceDefRef(ifaceId, choice.name), 2) { case List(cidPos, choiceArgPos, _) => + withEnv { _ => + let( + SBUPreFetchInterface(ifaceId)(svar(cidPos)) + ) { tmplArgPos => + SBUChoiceInterface(ifaceId, choice.name)( + svar(cidPos), + svar(choiceArgPos), + svar(tmplArgPos), + ) + } + } + } + private[this] def compileChoice( tmplId: TypeConName, tmpl: Template, @@ -1397,6 +1422,22 @@ private[lf] final class Compiler( compileFetchBody(tmplId, tmpl)(cidPos, None, tokenPos) } + private[this] def compileFetchInterface( + ifaceId: Identifier + ): (SDefinitionRef, SDefinition) = + topLevelFunction(FetchDefRef(ifaceId), 2) { case List(cidPos, _) => + withEnv { _ => + let( + SBUPreFetchInterface(ifaceId)(svar(cidPos)) + ) { tmplArgPos => + SBUFetchInterface(ifaceId)( + svar(cidPos), + svar(tmplArgPos), + ) + } + } + } + private[this] def compileKey( tmplId: Identifier, tmpl: Template, @@ -1424,6 +1465,18 @@ private[lf] final class Compiler( compile(tmpl.observers) } + // Turn a template value into an interface value. Since interfaces have a + // toll-free representation (for now), this is just the identity function. + // But the existence of ImplementsDefRef implies that the template implements + // the interface, which is useful in itself. + private[this] def compileImplements( + tmplId: Identifier, + ifaceId: Identifier, + ): (SDefinitionRef, SDefinition) = + topLevelFunction(ImplementsDefRef(tmplId, ifaceId), 1) { case List(tmplPos) => + svar(tmplPos) + } + private[this] def compileCreate( tmplId: Identifier, tmpl: Template, diff --git a/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/Profile.scala b/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/Profile.scala index 1f361d3a8e01..23a2616b2f50 100644 --- a/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/Profile.scala +++ b/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/Profile.scala @@ -238,6 +238,7 @@ object Profile { implicit val keyDefRef: Allowed[KeyDefRef] = allowAll implicit val signatoriesDefRef: Allowed[SignatoriesDefRef] = allowAll implicit val observersDefRef: Allowed[ObserversDefRef] = allowAll + implicit val implementsDefRef: Allowed[ImplementsDefRef] = allowAll implicit val choiceDefRef: Allowed[ChoiceDefRef] = allowAll implicit val fetchDefRef: Allowed[FetchDefRef] = allowAll implicit val choiceByKeyDefRef: Allowed[ChoiceByKeyDefRef] = allowAll @@ -260,6 +261,8 @@ object Profile { case KeyDefRef(tmplRef) => s"keyAndMaintainers @${tmplRef.qualifiedName}" case SignatoriesDefRef(tmplRef) => s"signatories @${tmplRef.qualifiedName}" case ObserversDefRef(tmplRef) => s"observers @${tmplRef.qualifiedName}" + case ImplementsDefRef(tmplRef, ifaceId) => + s"implements @${tmplRef.qualifiedName} @${ifaceId.qualifiedName}" case ChoiceDefRef(tmplRef, name) => s"exercise @${tmplRef.qualifiedName} ${name}" case FetchDefRef(tmplRef) => s"fetch @${tmplRef.qualifiedName}" case ChoiceByKeyDefRef(tmplRef, name) => diff --git a/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/SBuiltin.scala b/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/SBuiltin.scala index 868784f40568..c812d1842e29 100644 --- a/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/SBuiltin.scala +++ b/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/SBuiltin.scala @@ -1072,6 +1072,115 @@ private[lf] object SBuiltin { } } + // Similar to SBUFetch but doesn't perform any checks on the template id, and is never "by key". + final case class SBUPreFetchInterface(ifaceId: TypeConName) extends OnLedgerBuiltin(1) { + override protected def execute( + args: util.ArrayList[SValue], + machine: Machine, + onLedger: OnLedger, + ): Unit = { + val coid = getSContractId(args, 0) + onLedger.cachedContracts.get(coid) match { + case Some(cached) => { + machine.returnValue = cached.value + } + case None => { + throw SpeedyHungry( + SResultNeedContract( + coid, + ifaceId, // not actually used, maybe this param should be dropped from SResultNeedContract + onLedger.committers, + { case V.ContractInst(actualTmplId, V.VersionedValue(_, arg), _) => + val keyExpr = SEApp(SEVal(KeyDefRef(actualTmplId)), Array(SELocS(1))) + machine.pushKont(KCacheContract(machine, actualTmplId, coid)) + machine.ctrl = SELet1( + SEImportValue(Ast.TTyCon(actualTmplId), arg), + cachedContractStruct( + SELocS(1), + SEApp(SEVal(SignatoriesDefRef(actualTmplId)), Array(SELocS(1))), + SEApp(SEVal(ObserversDefRef(actualTmplId)), Array(SELocS(1))), + keyExpr, + ), + ) + }, + ) + ) + } + } + } + } + + // SBUFetchInterface uses the contract payload obtained from SBUPreFetchInterface + // to call the proper FetchDefRef with the actual template id, and performs an + // implements check. + // + // Interfaces have a "toll-free" representation with the underlying template, + // since the template's SRecord already includes the type constructor (templateId) + // of its template, we shouldn't need to wrap or change the fetched template value + // in any way. (Unless we later need to enforce the distinction between templates + // and interfaces at the speedy value level.) + final case class SBUFetchInterface( + ifaceId: TypeConName + ) extends SBuiltin(2) { + override private[speedy] def execute( + args: util.ArrayList[SValue], + machine: Machine, + ): Unit = { + val coid = getSContractId(args, 0) + val SRecord(tmplId, _, _) = getSRecord(args, 1) + // After SBUPreFetchInterface, the template's package should already be loaded + // in compiledPackages, so SImplementsDefRef will be defined if the template + // implements the interface. + machine.compiledPackages.getDefinition(ImplementsDefRef(tmplId, ifaceId)) match { + case Some(_) => + machine.ctrl = FetchDefRef(tmplId)( + SEValue(SContractId(coid)), + SEValue(SToken), + ) + case None => + machine.ctrl = SEDamlException( + IE.WronglyTypedContract(coid, ifaceId, tmplId) + // TODO https://github.com/digital-asset/daml/issues/10810: + // Maybe create a more specific exception. + ) + } + } + } + + // Very similar to SBUFetchInterface. We use the payload from SBUPreFetchInterface + // to call the appropriate ChoiceDefRef with the actual template id, and performs + // an implements check. + final case class SBUChoiceInterface( + ifaceId: TypeConName, + choiceName: ChoiceName, + ) extends SBuiltin(2) { + override private[speedy] def execute( + args: util.ArrayList[SValue], + machine: Machine, + ): Unit = { + val coid = getSContractId(args, 0) + val choiceArg = args.get(1) + val SRecord(tmplId, _, _) = getSRecord(args, 2) + // After SBUPreFetchInterface, the template's package should already be loaded + // in compiledPackages, so SImplementsDefRef will be defined if the template + // implements the interface. + machine.compiledPackages.getDefinition(ImplementsDefRef(tmplId, ifaceId)) match { + case Some(_) => + machine.ctrl = ChoiceDefRef(tmplId, choiceName)( + SEValue(SContractId(coid)), + SEValue(choiceArg), + SEValue(SToken), + ) + case None => + machine.ctrl = SEDamlException( + IE.WronglyTypedContract(coid, ifaceId, tmplId) + // TODO https://github.com/digital-asset/daml/issues/10810: + // Maybe create a more specific exception. + ) + } + } + } + /** $insertFetch[tid] * :: ContractId a * -> List Party (signatories) diff --git a/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/SExpr.scala b/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/SExpr.scala index 3d1ed831f2cc..7bcfeeb5c945 100644 --- a/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/SExpr.scala +++ b/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/SExpr.scala @@ -466,6 +466,12 @@ object SExpr { final case class SignatoriesDefRef(ref: DefinitionRef) extends SDefinitionRef final case class ObserversDefRef(ref: DefinitionRef) extends SDefinitionRef + /** ImplementsDefRef(ref=templateId, ifaceId) points to a function that converts a + * template value to an interface value. (This is currently an identity function.) + * The existence of this definition signals that the template implements the interface. + */ + final case class ImplementsDefRef(ref: DefinitionRef, ifaceId: TypeConName) extends SDefinitionRef + // // List builtins (equalList) are implemented as recursive // definition to save java stack