From b2988bc79a0bddec0468ff3333ccf303f4e450b0 Mon Sep 17 00:00:00 2001 From: Remy Date: Tue, 12 Oct 2021 16:48:36 +0200 Subject: [PATCH] LF: type and compile Interface fixed choices (#11175) * LF: type and compile Interface fixed choices CHANGELOG_BEGIN CHANGELOG_END * cleanup * fix some token * cleanup * Address sophia's review * add comment * cosmetic * fix --- .../DA/Experimental/Interface.daml | 8 +- .../daml/lf/speedy/Compiler.scala | 86 ++++++--- .../daml/lf/speedy/SBuiltin.scala | 166 ++++++------------ .../daml/lf/validation/Typing.scala | 15 +- 4 files changed, 134 insertions(+), 141 deletions(-) diff --git a/compiler/damlc/daml-stdlib-src/DA/Experimental/Interface.daml b/compiler/damlc/daml-stdlib-src/DA/Experimental/Interface.daml index fccb9ecc414..1960e4c6b25 100644 --- a/compiler/damlc/daml-stdlib-src/DA/Experimental/Interface.daml +++ b/compiler/damlc/daml-stdlib-src/DA/Experimental/Interface.daml @@ -1,8 +1,6 @@ -- Copyright (c) 2021 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -- SPDX-License-Identifier: Apache-2.0 -{-# LANGUAGE CPP #-} - module DA.Experimental.Interface( interfaceCreate, interfaceSignatory, @@ -12,10 +10,10 @@ module DA.Experimental.Interface( import GHC.Types (primitive) interfaceCreate: t -> Update (ContractId t) -interfaceCreate = primitive @"$INTERFACE_CREATE" +interfaceCreate payload = primitive @"$RESOLVE_VIRTUAL_CREATE" payload payload interfaceSignatory: t -> [Party] -interfaceSignatory = primitive @"$INTERFACE_SIGNATORIES" +interfaceSignatory payload = primitive @"$RESOLVE_VIRTUAL_SIGNATORY" payload payload interfaceObserver: t -> [Party] -interfaceObserver = primitive @"$INTERFACE_OBSERVERS" +interfaceObserver payload = primitive @"$RESOLVE_VIRTUAL_OBSERVER" payload payload 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 2ed326844d2..82867ceea40 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 @@ -338,8 +338,11 @@ private[lf] final class Compiler( module.interfaces.foreach { case (ifaceName, iface) => val identifier = Identifier(pkgId, QualifiedName(module.name, ifaceName)) addDef(compileFetchInterface(identifier)) + iface.fixedChoices.values.foreach( + builder += compileFixedChoice(identifier, iface.param, _) + ) iface.virtualChoices.values.foreach( - builder += compileChoiceInterface(identifier, _) + builder += compileVirtualChoice(identifier, _) ) } @@ -1016,22 +1019,70 @@ private[lf] final class Compiler( } } - private[this] def compileChoiceInterface( + // TODO https://github.com/digital-asset/daml/issues/10810: + // Try to factorise this with compileChoiceBody above. + private[this] def compileFixedChoiceBody( + ifaceId: TypeConName, + param: ExprVarName, + choice: TemplateChoice, + )( + choiceArgPos: Position, + cidPos: Position, + tokenPos: Position, + ) = withEnv { _ => + let(SBUFetchInterface(ifaceId)(svar(cidPos))) { payloadPos => + addExprVar(param, payloadPos) + addExprVar(choice.argBinder._1, choiceArgPos) + let( + // TODO https://github.com/digital-asset/daml/issues/10810: + // Here we insert an Exercise Node with the interface Id instead of the template Id. \ + // Review if that can cause issues. + SBUBeginExercise(ifaceId, choice.name, choice.consuming, byKey = false)( + svar(choiceArgPos), + svar(cidPos), + compile(choice.controllers), + choice.choiceObservers match { + case Some(observers) => compile(observers) + case None => SEValue.EmptyList + }, + ) + ) { _ => + addExprVar(choice.selfBinder, cidPos) + SEScopeExercise(ifaceId, app(compile(choice.update), svar(tokenPos))) + } + } + } + + // TODO https://github.com/digital-asset/daml/issues/10810: + // Here we fetch twice, once by interface Id once by template Id. Try to bypass the second fetch. + private[this] def compileVirtualChoice( ifaceId: TypeConName, choice: InterfaceChoice, ): (SDefinitionRef, SDefinition) = - topLevelFunction(ChoiceDefRef(ifaceId, choice.name), 3) { case List(cidPos, choiceArgPos, _) => - withEnv { _ => - let( - SBUPreFetchInterface(ifaceId)(svar(cidPos)) - ) { tmplArgPos => - SBUChoiceInterface(ifaceId, choice.name)( + topLevelFunction(ChoiceDefRef(ifaceId, choice.name), 3) { + case List(cidPos, choiceArgPos, tokenPos) => + let(SBUFetchInterface(ifaceId)(svar(cidPos))) { payloadPos => + SBResolveVirtualChoice(choice.name)( + svar(payloadPos), svar(cidPos), svar(choiceArgPos), - svar(tmplArgPos), + svar(tokenPos), ) } - } + } + + private[this] def compileFixedChoice( + ifaceId: TypeConName, + param: ExprVarName, + choice: TemplateChoice, + ): (SDefinitionRef, SDefinition) = + topLevelFunction(ChoiceDefRef(ifaceId, choice.name), 3) { + case List(cidPos, choiceArgPos, tokenPos) => + compileFixedChoiceBody(ifaceId, param, choice)( + choiceArgPos, + cidPos, + tokenPos, + ) } private[this] def compileChoice( @@ -1425,19 +1476,14 @@ private[lf] final class Compiler( compileFetchBody(tmplId, tmpl)(cidPos, None, tokenPos) } + // TODO https://github.com/digital-asset/daml/issues/10810: + // Here we fetch twice, once by interface Id once by template Id. Try to bypass the second fetch. 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), - ) - } + topLevelFunction(FetchDefRef(ifaceId), 2) { case List(cidPos, tokenPos) => + let(SBUFetchInterface(ifaceId)(svar(cidPos))) { payloadPos => + SBResolveVirtualFetch(svar(payloadPos), svar(cidPos), svar(tokenPos)) } } 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 a240c3ba197..d624c39ae96 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,8 +1072,8 @@ 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) { + // Similar to SBUFetch but and is never performed "by key". + final case class SBUFetchInterface(ifaceId: TypeConName) extends OnLedgerBuiltin(1) { override protected def execute( args: util.ArrayList[SValue], machine: Machine, @@ -1081,105 +1081,63 @@ private[lf] object SBuiltin { ): Unit = { val coid = getSContractId(args, 0) onLedger.cachedContracts.get(coid) match { - case Some(cached) => { - machine.returnValue = cached.value - } - case None => { + case Some(cached) => + machine.compiledPackages.getDefinition( + ImplementsDefRef(cached.templateId, ifaceId) + ) match { + case Some(_) => + machine.returnValue = cached.value + case None => + machine.ctrl = SEDamlException( + // TODO https://github.com/digital-asset/daml/issues/10810: + // Create a more specific exception. + IE.WronglyTypedContract(coid, ifaceId, cached.templateId) + ) + } + 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, - ), - ) + machine.compiledPackages.getDefinition( + ImplementsDefRef(actualTmplId, ifaceId) + ) match { + case Some(_) => + 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))), + SEApp(SEVal(KeyDefRef(actualTmplId)), Array(SELocS(1))), + ), + ) + case None => + machine.ctrl = + // TODO https://github.com/digital-asset/daml/issues/10810: + // Create a more specific exception. + SEDamlException(IE.WronglyTypedContract(coid, ifaceId, actualTmplId)) + } }, ) ) - } } } } - // 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. - ) - } - } + // Return a definition matching the templateId of a given payload + sealed class SBResolveVirtual(toDef: Ref.Identifier => SDefinitionRef) extends SBuiltin(1) { + override private[speedy] def execute(args: util.ArrayList[SValue], machine: Machine): Unit = + machine.ctrl = SEVal(toDef(getSRecord(args, 0).id)) } - // 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(3) { - 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. - ) - } - } - } + final case object SBResolveVirtualFetch extends SBResolveVirtual(FetchDefRef) + + final case class SBResolveVirtualChoice(choiceName: ChoiceName) + extends SBResolveVirtual(ChoiceDefRef(_, choiceName)) // Convert an interface to a given template type if possible. Since interfaces have the // same representation as the underlying template, we only need to perform a check @@ -1693,36 +1651,18 @@ private[lf] object SBuiltin { object SBExperimental { - private sealed abstract class SBExperimental(val name: String, arity: Int) - extends SBuiltin(arity) - - private object SBExperimentalAnswer extends SBExperimental("ANSWER", 1) { - override private[speedy] def execute(args: util.ArrayList[SValue], machine: Machine) = { + private object SBExperimentalAnswer extends SBuiltin(1) { + override private[speedy] def execute(args: util.ArrayList[SValue], machine: Machine) = machine.returnValue = SInt64(42L) - } } - private final class SBExperimentalInterfaceDef( - toSDefRef: Identifier => SDefinitionRef, - name: String, - arity: Int, - ) extends SBExperimental(name, arity) { - override private[speedy] def execute( - args: util.ArrayList[SValue], - machine: Machine, - ): Unit = { - val tmplId = getSRecord(args, 0).id - machine.ctrl = SEApp(SEVal(toSDefRef(tmplId)), args.asScala.view.map(SEValue(_)).toArray) - } - } - - private val mapping: Map[String, SEBuiltin] = - List[SBExperimental]( - SBExperimentalAnswer, - new SBExperimentalInterfaceDef(CreateDefRef, "INTERFACE_CREATE", 2), - new SBExperimentalInterfaceDef(SignatoriesDefRef, "INTERFACE_SIGNATORIES", 1), - new SBExperimentalInterfaceDef(ObserversDefRef, "INTERFACE_OBSERVERS", 1), - ).map(x => x.name -> SEBuiltin(x)).toMap + private val mapping: Map[String, SExpr] = + List( + "ANSWER" -> SBExperimentalAnswer, + "RESOLVE_VIRTUAL_CREATE" -> new SBResolveVirtual(CreateDefRef), + "RESOLVE_VIRTUAL_SIGNATORY" -> new SBResolveVirtual(SignatoriesDefRef), + "RESOLVE_VIRTUAL_OBSERVER" -> new SBResolveVirtual(ObserversDefRef), + ).view.map { case (name, builtin) => name -> SEBuiltin(builtin) }.toMap def apply(name: String): SExpr = mapping.getOrElse(name, SBError(SEValue(SText(s"experimental $name not supported.")))) diff --git a/daml-lf/validation/src/main/scala/com/digitalasset/daml/lf/validation/Typing.scala b/daml-lf/validation/src/main/scala/com/digitalasset/daml/lf/validation/Typing.scala index ee1cb0993f6..791759f44e3 100644 --- a/daml-lf/validation/src/main/scala/com/digitalasset/daml/lf/validation/Typing.scala +++ b/daml-lf/validation/src/main/scala/com/digitalasset/daml/lf/validation/Typing.scala @@ -304,12 +304,11 @@ private[validation] object Typing { throw EExpectedExceptionableType(env.ctx, tyConName) } } - mod.interfaces.foreach { case (ifaceName, defInterface) => + mod.interfaces.foreach { case (ifaceName, iface) => // uniquess of choice names is already checked on construction of the choice map. val tyConName = TypeConName(pkgId, QualifiedName(mod.name, ifaceName)) val env = Env(langVersion, interface, ContextDefInterface(tyConName), Map.empty) - defInterface.virtualChoices.values.foreach(env.checkIfaceChoice(_)) - defInterface.methods.values.foreach(env.checkIfaceMethod(_)) + env.checkDefIface(tyConName, iface) } } @@ -458,6 +457,16 @@ private[validation] object Typing { implementations.values.foreach(env.checkIfaceImplementation(tplName, _)) } + def checkDefIface(ifaceName: TypeConName, iface: DefInterface): Unit = + iface match { + case DefInterface(param, virtualChoices, fixedChoices, methods) => + fixedChoices.values.foreach( + introExprVar(param, TTyCon(ifaceName)).checkChoice(ifaceName, _) + ) + virtualChoices.values.foreach(checkIfaceChoice) + methods.values.foreach(checkIfaceMethod) + } + def checkIfaceChoice(choice: InterfaceChoice): Unit = { checkType(choice.argType, KStar) checkType(choice.returnType, KStar)