Restrict degree to which Speedy.Machine exposes its mutable state

* Restrict degree to which Speedy.Machine exposes its mutable state

CHANGELOG_BEGIN
CHANGELOG_END
This commit is contained in:
Carl Pulley 2022-09-20 09:30:03 +01:00 committed by GitHub
parent cfa7a34022
commit f9b166ab35
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 118 additions and 104 deletions

View File

@ -368,7 +368,7 @@ class Engine(val config: EngineConfig = Engine.StableConfig) {
time: Time.Timestamp,
): Result[(SubmittedTransaction, Tx.Metadata)] = machine.withOnLedger("Daml Engine") { onLedger =>
def detailMsg = Some(
s"Last location: ${Pretty.prettyLoc(machine.lastLocation).render(80)}, partial transaction: ${onLedger.nodesToString}"
s"Last location: ${Pretty.prettyLoc(machine.getLastLocation).render(80)}, partial transaction: ${onLedger.nodesToString}"
)
def versionDisclosedContract(d: speedy.DisclosedContract): Versioned[DisclosedContract] = {
val version = machine.tmplId2TxVersion(d.templateId)

View File

@ -4,18 +4,19 @@
package com.daml.lf
package speedy
import com.daml.lf.speedy.Speedy.{Machine, Control}
import scala.collection.mutable.Map
import com.daml.lf.speedy.Speedy.{Control, Machine}
import scala.collection.mutable
private[speedy] object Classify { // classify the machine state w.r.t what step occurs next
final class Counts(
var ctrlExpr: Int = 0,
var ctrlValue: Int = 0,
var exprs: Map[String, Int] = Map.empty,
var konts: Map[String, Int] = Map.empty,
var exprs: mutable.Map[String, Int] = mutable.Map.empty,
var konts: mutable.Map[String, Int] = mutable.Map.empty,
) {
def steps = ctrlExpr + ctrlValue
def steps: Int = ctrlExpr + ctrlValue
def pp: String = {
val lines =
(("CtrlExpr:", ctrlExpr) :: exprs.toList.map { case (expr, n) => ("- " + expr, n) }) ++
@ -25,16 +26,16 @@ private[speedy] object Classify { // classify the machine state w.r.t what step
}
def classifyMachine(machine: Machine, counts: Counts): Unit = {
machine.control match {
machine.currentControl match {
case Control.Value(_) =>
// classify a value by the continution it is about to return to
counts.ctrlValue += 1
val kont = machine.kontStack.get(machine.kontStack.size - 1).getClass.getSimpleName
val _ = counts.konts += kont -> (counts.konts.get(kont).getOrElse(0) + 1)
val kont = machine.peekKontStackEnd().getClass.getSimpleName
val _ = counts.konts += kont -> (counts.konts.getOrElse(kont, 0) + 1)
case Control.Expression(exp) =>
counts.ctrlExpr += 1
val expr = exp.getClass.getSimpleName
val _ = counts.exprs += expr -> (counts.exprs.get(expr).getOrElse(0) + 1)
val _ = counts.exprs += expr -> (counts.exprs.getOrElse(expr, 0) + 1)
case _ => ()
}
}

View File

@ -4,7 +4,6 @@
package com.daml.lf
package speedy
import java.util
import scala.jdk.CollectionConverters._
import com.daml.lf.speedy.Speedy._
@ -14,7 +13,7 @@ import com.daml.lf.speedy.SValue._
private[speedy] object PrettyLightweight { // lightweight pretty printer for CEK machine states
def ppMachine(m: Machine): String = {
s"[${m.envBase}] ${ppEnv(m.env)} -- ${ppCtrl(m.control)} -- ${ppKontStack(m.kontStack)}"
s"[${m.currentEnvBase}] ${ppEnv(m.currentEnv)} -- ${ppCtrl(m.currentControl)} -- ${ppKontStack(m)}"
}
def ppCtrl(control: Control): String =
@ -31,8 +30,8 @@ private[speedy] object PrettyLightweight { // lightweight pretty printer for CEK
s"#${env.size()}={${commas(env.asScala.map(pp))}}"
}
def ppKontStack(ks: util.ArrayList[Kont]): String = {
s"[${ppKont(ks.get(ks.size - 1))}... #${ks.size()}]" // head kont & size
def ppKontStack(m: Machine): String = {
s"[${ppKont(m.peekKontStackEnd())}... #${m.kontDepth()}]" // head kont & size
}
def ppKont(k: Kont): String = k.getClass.getSimpleName

View File

@ -963,7 +963,7 @@ private[lf] object SBuiltin {
templateId = cached.templateId,
arg = createArgValue,
agreementText = agreement,
optLocation = machine.lastLocation,
optLocation = machine.getLastLocation,
signatories = cached.signatories,
stakeholders = cached.stakeholders,
key = cached.key,
@ -1029,7 +1029,7 @@ private[lf] object SBuiltin {
templateId = templateId,
interfaceId = interfaceId,
choiceId = choiceId,
optLocation = machine.lastLocation,
optLocation = machine.getLastLocation,
consuming = consuming,
actingParties = ctrls,
signatories = sigs,
@ -1454,7 +1454,7 @@ private[lf] object SBuiltin {
onLedger.ptx.insertFetch(
coid = coid,
templateId = templateId,
optLocation = machine.lastLocation,
optLocation = machine.getLastLocation,
signatories = signatories,
observers = observers,
key = key,
@ -1498,7 +1498,7 @@ private[lf] object SBuiltin {
}
onLedger.ptx.insertLookup(
templateId = templateId,
optLocation = machine.lastLocation,
optLocation = machine.getLastLocation,
key = Node.KeyWithMaintainers(
key = keyWithMaintainers.key,
maintainers = keyWithMaintainers.maintainers,
@ -1769,7 +1769,7 @@ private[lf] object SBuiltin {
machine: Machine,
): Control = {
val message = getSText(args, 0)
machine.traceLog.add(message, machine.lastLocation)(machine.loggingContext)
machine.traceLog.add(message, machine.getLastLocation)(machine.loggingContext)
Control.Value(args.get(1))
}
}

View File

@ -234,7 +234,7 @@ object SExpr {
/** A let-expression with a single RHS */
final case class SELet1General(rhs: SExpr, body: SExpr) extends SExpr with SomeArrayEquals {
def execute(machine: Machine): Control = {
machine.pushKont(KPushTo(machine, machine.env, body))
machine.pushKont(KPushTo(machine, machine.currentEnv, body))
Control.Expression(rhs)
}
}

View File

@ -113,22 +113,22 @@ private[lf] object Speedy {
throw SError.SErrorDamlException(IError.Limit(error(limit)))
private[lf] final case class OnLedger(
val validating: Boolean,
val contractKeyUniqueness: ContractKeyUniquenessMode,
validating: Boolean,
contractKeyUniqueness: ContractKeyUniquenessMode,
/* The current partial transaction */
private[speedy] var ptx: PartialTransaction,
/* Committers of the action. */
val committers: Set[Party],
committers: Set[Party],
/* Additional readers (besides committers) for visibility checks. */
val readAs: Set[Party],
readAs: Set[Party],
/* Commit location, if a scenario commit is in progress. */
val commitLocation: Option[Location],
commitLocation: Option[Location],
/* Flag to trace usage of get_time builtins */
var dependsOnTime: Boolean,
// global contract discriminators, that are discriminators from contract created in previous transactions
var cachedContracts: Map[V.ContractId, CachedContract],
var numInputContracts: Int,
val limits: interpretation.Limits,
limits: interpretation.Limits,
) extends LedgerMode {
private[lf] val visibleToStakeholders: Set[Party] => SVisibleToStakeholders =
if (validating) { _ => SVisibleToStakeholders.Visible }
@ -298,25 +298,7 @@ private[lf] object Speedy {
/** The speedy CEK machine. */
final class Machine(
/* The machine control is either an expression or a value. */
var control: Control,
/* Frame: to access values for a closure's free-vars. */
var frame: Frame,
/* Actuals: to access values for a function application's arguments. */
var actuals: Actuals,
/* [env] is a stack of temporary values for: let-bindings and pattern-matches. */
var env: Env,
/* [envBase] is the depth of the temporaries-stack when the current code-context was
* begun. We revert to this depth when entering a closure, or returning to the top
* continuation on the kontStack.
*/
var envBase: Int,
/* Kont, or continuation specifies what should be done next
* once the control has been evaluated.
*/
var kontStack: util.ArrayList[Kont],
/* The last encountered location */
var lastLocation: Option[Location],
val sexpr: SExpr,
/* The trace log. */
val traceLog: TraceLog,
/* Engine-generated warnings. */
@ -325,22 +307,61 @@ private[lf] object Speedy {
implicit val loggingContext: LoggingContext,
/* Compiled packages (Daml-LF ast + compiled speedy expressions). */
var compiledPackages: CompiledPackages,
/* Used when enableLightweightStepTracing is true */
var steps: Int,
/* Used when enableInstrumentation is true */
var track: Instrumentation,
/* Profile of the run when the packages haven been compiled with profiling enabled. */
var profile: Profile,
val profile: Profile = new Profile(),
val submissionTime: Time.Timestamp,
/* True if we are running on ledger building transactions, false if we
are running off-ledger code, e.g., Daml Script or
Triggers. It is safe to use on ledger for off ledger code but
not the other way around.
*/
val submissionTime: Time.Timestamp,
val ledgerMode: LedgerMode,
val disclosureTable: DisclosureTable,
) {
/* The machine control is either an expression or a value. */
private[this] var control: Control = Control.Expression(sexpr)
/* Frame: to access values for a closure's free-vars. */
private[this] var frame: Frame = null
/* Actuals: to access values for a function application's arguments. */
private[this] var actuals: Actuals = null
/* [env] is a stack of temporary values for: let-bindings and pattern-matches. */
private[speedy] var env: Env = emptyEnv
/* [envBase] is the depth of the temporaries-stack when the current code-context was
* begun. We revert to this depth when entering a closure, or returning to the top
* continuation on the kontStack.
*/
private[this] var envBase: Int = 0
/* Kont, or continuation specifies what should be done next
* once the control has been evaluated.
*/
private[speedy] var kontStack: util.ArrayList[Kont] = initialKontStack()
/* The last encountered location */
private[this] var lastLocation: Option[Location] = None
/* Used when enableLightweightStepTracing is true */
private[this] var steps: Int = 0
/* Used when enableInstrumentation is true */
private[this] var track: Instrumentation = Instrumentation()
private[speedy] def currentControl: Control = control
private[speedy] def currentFrame: Frame = frame
private[speedy] def currentActuals: Actuals = actuals
private[speedy] def currentEnv: Env = env
private[speedy] def currentEnvBase: Int = envBase
private[speedy] def currentKontStack: util.ArrayList[Kont] = kontStack
private[lf] def getLastLocation: Option[Location] = lastLocation
private[speedy] def clearEnv(): Unit = {
env.clear()
envBase = 0
}
def tmplId2TxVersion(tmplId: TypeConName): TransactionVersion =
TransactionVersion.assignNodeVersion(
compiledPackages.pkgInterface.packageLanguageVersion(tmplId.packageId)
@ -351,6 +372,8 @@ private[lf] object Speedy {
/* kont manipulation... */
private[speedy] def clearKontStack(): Unit = kontStack.clear()
@inline
private[speedy] def kontDepth(): Int = kontStack.size()
@ -374,6 +397,16 @@ private[lf] object Speedy {
kontStack.remove(kontStack.size - 1)
}
@inline
private[speedy] def peekKontStackEnd(): Kont = {
kontStack.get(kontStack.size - 1)
}
@inline
private[speedy] def peekKontStackTop(): Kont = {
kontStack.get(0)
}
/* env manipulation... */
// The environment is partitioned into three locations: Stack, Args, Free
@ -928,13 +961,7 @@ private[lf] object Speedy {
disclosedContracts: ImmArray[speedy.DisclosedContract],
)(implicit loggingContext: LoggingContext): Machine = {
new Machine(
control = Control.Expression(expr),
frame = null,
actuals = null,
env = emptyEnv,
envBase = 0,
kontStack = initialKontStack(),
lastLocation = None,
sexpr = expr,
submissionTime = submissionTime,
ledgerMode = OnLedger(
validating = validating,
@ -958,9 +985,6 @@ private[lf] object Speedy {
warningLog = warningLog,
loggingContext = loggingContext,
compiledPackages = compiledPackages,
steps = 0,
track = Instrumentation(),
profile = new Profile(),
disclosureTable = buildDiscTable(disclosedContracts, compiledPackages.pkgInterface),
)
}
@ -977,6 +1001,7 @@ private[lf] object Speedy {
limits: interpretation.Limits = interpretation.Limits.Lenient,
)(implicit loggingContext: LoggingContext): Machine = {
val updateSE: SExpr = compiledPackages.compiler.unsafeCompile(updateE)
fromUpdateSExpr(
compiledPackages,
transactionSeed,
@ -1050,22 +1075,13 @@ private[lf] object Speedy {
warningLog: WarningLog = newWarningLog,
)(implicit loggingContext: LoggingContext): Machine = {
new Machine(
control = Control.Expression(expr),
frame = null,
actuals = null,
env = emptyEnv,
envBase = 0,
kontStack = initialKontStack(),
lastLocation = None,
sexpr = expr,
submissionTime = Time.Timestamp.Epoch,
ledgerMode = OffLedger,
traceLog = traceLog,
warningLog = warningLog,
loggingContext = loggingContext,
compiledPackages = compiledPackages,
steps = 0,
track = Instrumentation(),
profile = new Profile(),
disclosureTable = buildDiscTable(disclosedContracts, compiledPackages.pkgInterface),
)
}
@ -1137,8 +1153,8 @@ private[lf] object Speedy {
with SomeArrayEquals {
private[this] val savedBase = machine.markBase()
private[this] val frame = machine.frame
private[this] val actuals = machine.actuals
private[this] val frame = machine.currentFrame
private[this] val actuals = machine.currentActuals
def execute(vfun: SValue): Control = {
machine.restoreBase(savedBase);
@ -1155,8 +1171,8 @@ private[lf] object Speedy {
with SomeArrayEquals {
private[this] val savedBase = machine.markBase()
private[this] val frame = machine.frame
private[this] val actuals = machine.actuals
private[this] val frame = machine.currentFrame
private[this] val actuals = machine.currentActuals
def execute(vfun: SValue): Control = {
machine.restoreBase(savedBase);
@ -1315,8 +1331,8 @@ private[lf] object Speedy {
with SomeArrayEquals {
private[this] val savedBase = machine.markBase()
private[this] val frame = machine.frame
private[this] val actuals = machine.actuals
private[this] val frame = machine.currentFrame
private[this] val actuals = machine.currentActuals
def execute(v: SValue): Control = {
machine.restoreBase(savedBase);
@ -1329,7 +1345,7 @@ private[lf] object Speedy {
* This continuation is used to implement both function application and lets. In
* the case of function application the arguments are pushed into the 'actuals' array of
* the PAP that is being built, and in the case of lets the evaluated value is pushed
* direy into the environment.
* directly into the environment.
*/
private[speedy] final case class KPushTo(
machine: Machine,
@ -1339,8 +1355,8 @@ private[lf] object Speedy {
with SomeArrayEquals {
private[this] val savedBase = machine.markBase()
private[this] val frame = machine.frame
private[this] val actuals = machine.actuals
private[this] val frame = machine.currentFrame
private[this] val actuals = machine.currentActuals
def execute(v: SValue): Control = {
machine.restoreBase(savedBase);
@ -1357,8 +1373,8 @@ private[lf] object Speedy {
) extends Kont
with SomeArrayEquals {
private[this] val frame = machine.frame
private[this] val actuals = machine.actuals
private[this] val frame = machine.currentFrame
private[this] val actuals = machine.currentActuals
def execute(acc: SValue): Control = {
list.pop match {
@ -1383,8 +1399,8 @@ private[lf] object Speedy {
) extends Kont
with SomeArrayEquals {
private[this] val frame = machine.frame
private[this] val actuals = machine.actuals
private[this] val frame = machine.currentFrame
private[this] val actuals = machine.currentActuals
def execute(acc: SValue): Control = {
if (lastIndex > 0) {
@ -1411,8 +1427,8 @@ private[lf] object Speedy {
) extends Kont
with SomeArrayEquals {
private[this] val frame = machine.frame
private[this] val actuals = machine.actuals
private[this] val frame = machine.currentFrame
private[this] val actuals = machine.currentActuals
def execute(closure: SValue): Control = {
revClosures = closure +: revClosures
@ -1437,8 +1453,8 @@ private[lf] object Speedy {
) extends Kont
with SomeArrayEquals {
private[this] val frame = machine.frame
private[this] val actuals = machine.actuals
private[this] val frame = machine.currentFrame
private[this] val actuals = machine.currentActuals
def execute(acc: SValue): Control = {
revClosures.pop match {
@ -1488,7 +1504,6 @@ private[lf] object Speedy {
Control.Value(cached.any)
}
}
}
private[speedy] final case class KCheckKeyVisibility(
@ -1497,6 +1512,7 @@ private[lf] object Speedy {
cid: V.ContractId,
handleKeyFound: (Machine, V.ContractId) => Control,
) extends Kont {
def execute(sv: SValue): Control = {
machine.withOnLedger("KCheckKeyVisibitiy") { onLedger =>
machine.checkKeyVisibility(onLedger, gKey, cid, handleKeyFound)
@ -1530,8 +1546,8 @@ private[lf] object Speedy {
with SomeArrayEquals {
private[this] val savedBase = machine.markBase()
private[this] val frame = machine.frame
private[this] val actuals = machine.actuals
private[this] val frame = machine.currentFrame
private[this] val actuals = machine.currentActuals
// we must restore when catching a throw, or for normal execution
def restore(): Unit = {
@ -1597,9 +1613,8 @@ private[lf] object Speedy {
// We must abort, because the transaction has failed in a way that is
// unrecoverable (it depends on the state of an input contract that
// we may not have the authority to fetch).
machine.kontStack.clear()
machine.env.clear()
machine.envBase = 0
machine.clearKontStack()
machine.clearEnv()
k.abort()
case KPreventException(_) =>
throw SError.SErrorDamlException(
@ -1620,9 +1635,8 @@ private[lf] object Speedy {
machine.pushEnv(excep) // payload on stack where handler expects it
Control.Expression(kh.handler)
case None =>
machine.kontStack.clear()
machine.env.clear()
machine.envBase = 0
machine.clearKontStack()
machine.clearEnv()
Control.Error(
IError.UnhandledException(excep.ty, excep.value.toUnnormalizedValue)
)
@ -1636,7 +1650,7 @@ private[lf] object Speedy {
}
}
/** Continuation produced by [[SELabelClsoure]] expressions. This is only
/** Continuation produced by [[SELabelClosure]] expressions. This is only
* used during profiling. Its purpose is to attach a label to closures such
* that entering the closure can write an "open event" with that label.
*/

View File

@ -118,13 +118,13 @@ class TailCallTest extends AnyWordSpec with Matchers with TableDrivenPropertyChe
case None => ()
case Some(bound) =>
val onlyKont: Speedy.Kont =
if (machine.kontStack.size != 1) {
crash(s"setBoundedKontStack, unexpected size of kont-stack: ${machine.kontStack.size}")
if (machine.kontDepth() != 1) {
crash(s"setBoundedKontStack, unexpected size of kont-stack: ${machine.kontDepth()}")
} else {
machine.kontStack.get(0)
machine.peekKontStackTop()
}
machine.kontStack = new BoundedArrayList[Speedy.Kont](bound)
machine.kontStack.add(onlyKont)
machine.pushKont(onlyKont)
}
// run the machine
machine.run() match {

View File

@ -558,7 +558,7 @@ object Repl {
case error: ScenarioRunner.ScenarioError =>
println(
"failed at " +
prettyLoc(machine.lastLocation).render(128) +
prettyLoc(machine.getLastLocation).render(128) +
": " + PrettyScenario.prettyError(error.error).render(128)
)
failures += 1

View File

@ -61,7 +61,7 @@ class CollectAuthorityState {
compiledPackages,
expr,
)
the_sexpr = machine.control match {
the_sexpr = machine.currentControl match {
case Control.Expression(exp) => exp
case x => crash(s"expected Control.Expression, got: $x")
}