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
This commit is contained in:
Sofia Faro 2021-09-17 11:44:40 +01:00 committed by GitHub
parent d01f8e1c35
commit 61d214e451
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 177 additions and 6 deletions

View File

@ -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,

View File

@ -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) =>

View File

@ -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)

View File

@ -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